From 58130e39eb8bc3963b36f325cd458b59063f6c0b Mon Sep 17 00:00:00 2001 From: Jason Lee Date: Tue, 4 Feb 2025 18:40:13 +0800 Subject: [PATCH] Add with_context_mask to ScrollBar. --- crates/ui/src/scroll/scrollbar.rs | 360 +++++++++++++++--------------- 1 file changed, 184 insertions(+), 176 deletions(-) diff --git a/crates/ui/src/scroll/scrollbar.rs b/crates/ui/src/scroll/scrollbar.rs index 396d91ae..875c5283 100644 --- a/crates/ui/src/scroll/scrollbar.rs +++ b/crates/ui/src/scroll/scrollbar.rs @@ -589,201 +589,209 @@ impl Element for Scrollbar { let is_visible = self.state.get().is_scrollbar_visible(); let is_hover_to_show = cx.theme().scrollbar_show.is_hover(); - for state in prepaint.states.iter() { - let axis = state.axis; - let radius = state.radius; - let bounds = state.bounds; - let thumb_bounds = state.thumb_bounds; - let scroll_area_size = state.scroll_size; - let container_size = state.container_size; - let thumb_size = state.thumb_size; - let margin_end = state.margin_end; - let is_vertical = axis.is_vertical(); - - window.set_cursor_style(CursorStyle::default(), &state.bar_hitbox); - - window.paint_layer(hitbox_bounds, |cx| { - cx.paint_quad(fill(state.bounds, state.bg)); - - cx.paint_quad(PaintQuad { - bounds, - corner_radii: (0.).into(), - background: gpui::transparent_black().into(), - border_widths: if is_vertical { - Edges { - top: px(0.), - right: px(0.), - bottom: px(0.), - left: BORDER_WIDTH, - } - } else { - Edges { - top: BORDER_WIDTH, - right: px(0.), - bottom: px(0.), - left: px(0.), - } - }, - border_color: state.border, - }); - - cx.paint_quad(fill(state.thumb_fill_bounds, state.thumb_bg).corner_radii(radius)); - }); - - window.on_mouse_event({ - let state = self.state.clone(); - let view_id = self.view_id; - let scroll_handle = self.scroll_handle.clone(); - - move |event: &ScrollWheelEvent, phase, _, cx| { - if phase.bubble() && hitbox_bounds.contains(&event.position) { - if scroll_handle.offset() != state.get().last_scroll_offset { - state.set( - state - .get() - .with_last_scroll(scroll_handle.offset(), Some(Instant::now())), - ); - cx.notify(view_id); + window.with_content_mask( + Some(ContentMask { + bounds: hitbox_bounds, + }), + |window| { + for state in prepaint.states.iter() { + let axis = state.axis; + let radius = state.radius; + let bounds = state.bounds; + let thumb_bounds = state.thumb_bounds; + let scroll_area_size = state.scroll_size; + let container_size = state.container_size; + let thumb_size = state.thumb_size; + let margin_end = state.margin_end; + let is_vertical = axis.is_vertical(); + + window.set_cursor_style(CursorStyle::default(), &state.bar_hitbox); + + window.paint_layer(hitbox_bounds, |cx| { + cx.paint_quad(fill(state.bounds, state.bg)); + + cx.paint_quad(PaintQuad { + bounds, + corner_radii: (0.).into(), + background: gpui::transparent_black().into(), + border_widths: if is_vertical { + Edges { + top: px(0.), + right: px(0.), + bottom: px(0.), + left: BORDER_WIDTH, + } + } else { + Edges { + top: BORDER_WIDTH, + right: px(0.), + bottom: px(0.), + left: px(0.), + } + }, + border_color: state.border, + }); + + cx.paint_quad( + fill(state.thumb_fill_bounds, state.thumb_bg).corner_radii(radius), + ); + }); + + window.on_mouse_event({ + let state = self.state.clone(); + let view_id = self.view_id; + let scroll_handle = self.scroll_handle.clone(); + + move |event: &ScrollWheelEvent, phase, _, cx| { + if phase.bubble() && hitbox_bounds.contains(&event.position) { + if scroll_handle.offset() != state.get().last_scroll_offset { + state.set(state.get().with_last_scroll( + scroll_handle.offset(), + Some(Instant::now()), + )); + cx.notify(view_id); + } + } } + }); + + let safe_range = (-scroll_area_size + container_size)..px(0.); + + if is_hover_to_show || is_visible { + window.on_mouse_event({ + let state = self.state.clone(); + let view_id = self.view_id; + let scroll_handle = self.scroll_handle.clone(); + + move |event: &MouseDownEvent, phase, _, cx| { + if phase.bubble() && bounds.contains(&event.position) { + cx.stop_propagation(); + + if thumb_bounds.contains(&event.position) { + // click on the thumb bar, set the drag position + let pos = event.position - thumb_bounds.origin; + + state.set(state.get().with_drag_pos(axis, pos)); + + cx.notify(view_id); + } else { + // click on the scrollbar, jump to the position + // Set the thumb bar center to the click position + let offset = scroll_handle.offset(); + let percentage = if is_vertical { + (event.position.y - thumb_size / 2. - bounds.origin.y) + / (bounds.size.height - thumb_size) + } else { + (event.position.x - thumb_size / 2. - bounds.origin.x) + / (bounds.size.width - thumb_size) + } + .min(1.); + + if is_vertical { + scroll_handle.set_offset(point( + offset.x, + (-scroll_area_size * percentage) + .clamp(safe_range.start, safe_range.end), + )); + } else { + scroll_handle.set_offset(point( + (-scroll_area_size * percentage) + .clamp(safe_range.start, safe_range.end), + offset.y, + )); + } + } + } + } + }); } - } - }); - let safe_range = (-scroll_area_size + container_size)..px(0.); - - if is_hover_to_show || is_visible { - window.on_mouse_event({ - let state = self.state.clone(); - let view_id = self.view_id; - let scroll_handle = self.scroll_handle.clone(); - - move |event: &MouseDownEvent, phase, _, cx| { - if phase.bubble() && bounds.contains(&event.position) { - cx.stop_propagation(); + window.on_mouse_event({ + let scroll_handle = self.scroll_handle.clone(); + let state = self.state.clone(); + let view_id = self.view_id; + + move |event: &MouseMoveEvent, _, _, cx| { + // Update hovered state for scrollbar + if bounds.contains(&event.position) { + if state.get().hovered_axis != Some(axis) { + state.set(state.get().with_hovered(Some(axis))); + cx.notify(view_id); + } + } else { + if state.get().hovered_axis == Some(axis) { + if state.get().hovered_axis.is_some() { + state.set(state.get().with_hovered(None)); + cx.notify(view_id); + } + } + } + // Update hovered state for scrollbar thumb if thumb_bounds.contains(&event.position) { - // click on the thumb bar, set the drag position - let pos = event.position - thumb_bounds.origin; + if state.get().hovered_on_thumb != Some(axis) { + state.set(state.get().with_hovered_on_thumb(Some(axis))); + cx.notify(view_id); + } + } else { + if state.get().hovered_on_thumb == Some(axis) { + state.set(state.get().with_hovered_on_thumb(None)); + cx.notify(view_id); + } + } - state.set(state.get().with_drag_pos(axis, pos)); + // Move thumb position on dragging + if state.get().dragged_axis == Some(axis) && event.dragging() { + // drag_pos is the position of the mouse down event + // We need to keep the thumb bar still at the origin down position + let drag_pos = state.get().drag_pos; - cx.notify(view_id); - } else { - // click on the scrollbar, jump to the position - // Set the thumb bar center to the click position - let offset = scroll_handle.offset(); - let percentage = if is_vertical { - (event.position.y - thumb_size / 2. - bounds.origin.y) + let percentage = (if is_vertical { + (event.position.y - drag_pos.y - bounds.origin.y) / (bounds.size.height - thumb_size) } else { - (event.position.x - thumb_size / 2. - bounds.origin.x) - / (bounds.size.width - thumb_size) - } - .min(1.); - - if is_vertical { - scroll_handle.set_offset(point( - offset.x, - (-scroll_area_size * percentage) + (event.position.x - drag_pos.x - bounds.origin.x) + / (bounds.size.width - thumb_size - margin_end) + }) + .clamp(0., 1.); + + let offset = if is_vertical { + point( + scroll_handle.offset().x, + (-(scroll_area_size - container_size) * percentage) .clamp(safe_range.start, safe_range.end), - )); + ) } else { - scroll_handle.set_offset(point( - (-scroll_area_size * percentage) + point( + (-(scroll_area_size - container_size) * percentage) .clamp(safe_range.start, safe_range.end), - offset.y, - )); + scroll_handle.offset().y, + ) + }; + + if (scroll_handle.offset().y - offset.y).abs() > px(1.) + || (scroll_handle.offset().x - offset.x).abs() > px(1.) + { + scroll_handle.set_offset(offset); + cx.notify(view_id); } } } - } - }); - } + }); - window.on_mouse_event({ - let scroll_handle = self.scroll_handle.clone(); - let state = self.state.clone(); - let view_id = self.view_id; - - move |event: &MouseMoveEvent, _, _, cx| { - // Update hovered state for scrollbar - if bounds.contains(&event.position) { - if state.get().hovered_axis != Some(axis) { - state.set(state.get().with_hovered(Some(axis))); - cx.notify(view_id); - } - } else { - if state.get().hovered_axis == Some(axis) { - if state.get().hovered_axis.is_some() { - state.set(state.get().with_hovered(None)); + window.on_mouse_event({ + let view_id = self.view_id; + let state = self.state.clone(); + + move |_event: &MouseUpEvent, phase, _, cx| { + if phase.bubble() { + state.set(state.get().with_unset_drag_pos()); cx.notify(view_id); } } - } - - // Update hovered state for scrollbar thumb - if thumb_bounds.contains(&event.position) { - if state.get().hovered_on_thumb != Some(axis) { - state.set(state.get().with_hovered_on_thumb(Some(axis))); - cx.notify(view_id); - } - } else { - if state.get().hovered_on_thumb == Some(axis) { - state.set(state.get().with_hovered_on_thumb(None)); - cx.notify(view_id); - } - } - - // Move thumb position on dragging - if state.get().dragged_axis == Some(axis) && event.dragging() { - // drag_pos is the position of the mouse down event - // We need to keep the thumb bar still at the origin down position - let drag_pos = state.get().drag_pos; - - let percentage = (if is_vertical { - (event.position.y - drag_pos.y - bounds.origin.y) - / (bounds.size.height - thumb_size) - } else { - (event.position.x - drag_pos.x - bounds.origin.x) - / (bounds.size.width - thumb_size - margin_end) - }) - .clamp(0., 1.); - - let offset = if is_vertical { - point( - scroll_handle.offset().x, - (-(scroll_area_size - container_size) * percentage) - .clamp(safe_range.start, safe_range.end), - ) - } else { - point( - (-(scroll_area_size - container_size) * percentage) - .clamp(safe_range.start, safe_range.end), - scroll_handle.offset().y, - ) - }; - - if (scroll_handle.offset().y - offset.y).abs() > px(1.) - || (scroll_handle.offset().x - offset.x).abs() > px(1.) - { - scroll_handle.set_offset(offset); - cx.notify(view_id); - } - } + }); } - }); - - window.on_mouse_event({ - let view_id = self.view_id; - let state = self.state.clone(); - - move |_event: &MouseUpEvent, phase, _, cx| { - if phase.bubble() { - state.set(state.get().with_unset_drag_pos()); - cx.notify(view_id); - } - } - }); - } + }, + ); } }