//! Div is the central, reusable element that most GPUI trees will be built from. //! It functions as a container for other elements, and provides a number of //! useful features for laying out and styling its children as well as binding //! mouse events and action handlers. It is meant to be similar to the HTML `
` //! element, but for GPUI. //! //! # Build your own div //! //! GPUI does not directly provide APIs for stateful, multi step events like `click` //! and `drag`. We want GPUI users to be able to build their own abstractions for //! their own needs. However, as a UI framework, we're also obliged to provide some //! building blocks to make the process of building your own elements easier. //! For this we have the [`Interactivity`] and the [`StyleRefinement`] structs, as well //! as several associated traits. Together, these provide the full suite of Dom-like events //! and Tailwind-like styling that you can use to build your own custom elements. Div is //! constructed by combining these two systems into an all-in-one element. use crate::{ AbsoluteLength, Action, AnyDrag, AnyElement, AnyTooltip, AnyView, App, Bounds, ClickEvent, DispatchPhase, Display, Element, ElementId, Entity, FocusHandle, Global, GlobalElementId, Hitbox, HitboxBehavior, HitboxId, InspectorElementId, IntoElement, IsZero, KeyContext, KeyDownEvent, KeyUpEvent, KeyboardButton, KeyboardClickEvent, LayoutId, ModifiersChangedEvent, MouseButton, MouseClickEvent, MouseDownEvent, MouseMoveEvent, MouseUpEvent, Overflow, ParentElement, Pixels, Point, Render, ScrollWheelEvent, SharedString, Size, Style, StyleRefinement, Styled, Task, TooltipId, Visibility, Window, WindowControlArea, point, px, size, }; use collections::HashMap; use refineable::Refineable; use smallvec::SmallVec; use stacksafe::{StackSafe, stacksafe}; use std::{ any::{Any, TypeId}, cell::RefCell, cmp::Ordering, fmt::Debug, marker::PhantomData, mem, rc::Rc, sync::Arc, time::Duration, }; use util::ResultExt; use super::ImageCacheProvider; const DRAG_THRESHOLD: f64 = 2.; const TOOLTIP_SHOW_DELAY: Duration = Duration::from_millis(500); const HOVERABLE_TOOLTIP_HIDE_DELAY: Duration = Duration::from_millis(500); /// The styling information for a given group. pub struct GroupStyle { /// The identifier for this group. pub group: SharedString, /// The specific style refinement that this group would apply /// to its children. pub style: Box, } /// An event for when a drag is moving over this element, with the given state type. pub struct DragMoveEvent { /// The mouse move event that triggered this drag move event. pub event: MouseMoveEvent, /// The bounds of this element. pub bounds: Bounds, drag: PhantomData, dragged_item: Arc, } impl DragMoveEvent { /// Returns the drag state for this event. pub fn drag<'b>(&self, cx: &'b App) -> &'b T { cx.active_drag .as_ref() .and_then(|drag| drag.value.downcast_ref::()) .expect("DragMoveEvent is only valid when the stored active drag is of the same type.") } /// An item that is about to be dropped. pub fn dragged_item(&self) -> &dyn Any { self.dragged_item.as_ref() } } impl Interactivity { /// Create an `Interactivity`, capturing the caller location in debug mode. #[cfg(any(feature = "inspector", debug_assertions))] #[track_caller] pub fn new() -> Interactivity { Interactivity { source_location: Some(core::panic::Location::caller()), ..Default::default() } } /// Create an `Interactivity`, capturing the caller location in debug mode. #[cfg(not(any(feature = "inspector", debug_assertions)))] pub fn new() -> Interactivity { Interactivity::default() } /// Gets the source location of construction. Returns `None` when not in debug mode. pub fn source_location(&self) -> Option<&'static std::panic::Location<'static>> { #[cfg(any(feature = "inspector", debug_assertions))] { self.source_location } #[cfg(not(any(feature = "inspector", debug_assertions)))] { None } } /// Bind the given callback to the mouse down event for the given mouse button, during the bubble phase. /// The imperative API equivalent of [`InteractiveElement::on_mouse_down`]. /// /// See [`Context::listener`](crate::Context::listener) to get access to the view state from this callback. pub fn on_mouse_down( &mut self, button: MouseButton, listener: impl Fn(&MouseDownEvent, &mut Window, &mut App) + 'static, ) { self.mouse_down_listeners .push(Box::new(move |event, phase, hitbox, window, cx| { if phase == DispatchPhase::Bubble && event.button == button && hitbox.is_hovered(window) { (listener)(event, window, cx) } })); } /// Bind the given callback to the mouse down event for any button, during the capture phase. /// The imperative API equivalent of [`InteractiveElement::capture_any_mouse_down`]. /// /// See [`Context::listener`](crate::Context::listener) to get access to a view's state from this callback. pub fn capture_any_mouse_down( &mut self, listener: impl Fn(&MouseDownEvent, &mut Window, &mut App) + 'static, ) { self.mouse_down_listeners .push(Box::new(move |event, phase, hitbox, window, cx| { if phase == DispatchPhase::Capture && hitbox.is_hovered(window) { (listener)(event, window, cx) } })); } /// Bind the given callback to the mouse down event for any button, during the bubble phase. /// The imperative API equivalent to [`InteractiveElement::on_any_mouse_down`]. /// /// See [`Context::listener`](crate::Context::listener) to get access to a view's state from this callback. pub fn on_any_mouse_down( &mut self, listener: impl Fn(&MouseDownEvent, &mut Window, &mut App) + 'static, ) { self.mouse_down_listeners .push(Box::new(move |event, phase, hitbox, window, cx| { if phase == DispatchPhase::Bubble && hitbox.is_hovered(window) { (listener)(event, window, cx) } })); } /// Bind the given callback to the mouse up event for the given button, during the bubble phase. /// The imperative API equivalent to [`InteractiveElement::on_mouse_up`]. /// /// See [`Context::listener`](crate::Context::listener) to get access to a view's state from this callback. pub fn on_mouse_up( &mut self, button: MouseButton, listener: impl Fn(&MouseUpEvent, &mut Window, &mut App) + 'static, ) { self.mouse_up_listeners .push(Box::new(move |event, phase, hitbox, window, cx| { if phase == DispatchPhase::Bubble && event.button == button && hitbox.is_hovered(window) { (listener)(event, window, cx) } })); } /// Bind the given callback to the mouse up event for any button, during the capture phase. /// The imperative API equivalent to [`InteractiveElement::capture_any_mouse_up`]. /// /// See [`Context::listener`](crate::Context::listener) to get access to a view's state from this callback. pub fn capture_any_mouse_up( &mut self, listener: impl Fn(&MouseUpEvent, &mut Window, &mut App) + 'static, ) { self.mouse_up_listeners .push(Box::new(move |event, phase, hitbox, window, cx| { if phase == DispatchPhase::Capture && hitbox.is_hovered(window) { (listener)(event, window, cx) } })); } /// Bind the given callback to the mouse up event for any button, during the bubble phase. /// The imperative API equivalent to [`Interactivity::on_any_mouse_up`]. /// /// See [`Context::listener`](crate::Context::listener) to get access to a view's state from this callback. pub fn on_any_mouse_up( &mut self, listener: impl Fn(&MouseUpEvent, &mut Window, &mut App) + 'static, ) { self.mouse_up_listeners .push(Box::new(move |event, phase, hitbox, window, cx| { if phase == DispatchPhase::Bubble && hitbox.is_hovered(window) { (listener)(event, window, cx) } })); } /// Bind the given callback to the mouse down event, on any button, during the capture phase, /// when the mouse is outside of the bounds of this element. /// The imperative API equivalent to [`InteractiveElement::on_mouse_down_out`]. /// /// See [`Context::listener`](crate::Context::listener) to get access to a view's state from this callback. pub fn on_mouse_down_out( &mut self, listener: impl Fn(&MouseDownEvent, &mut Window, &mut App) + 'static, ) { self.mouse_down_listeners .push(Box::new(move |event, phase, hitbox, window, cx| { if phase == DispatchPhase::Capture && !hitbox.contains(&window.mouse_position()) { (listener)(event, window, cx) } })); } /// Bind the given callback to the mouse up event, for the given button, during the capture phase, /// when the mouse is outside of the bounds of this element. /// The imperative API equivalent to [`InteractiveElement::on_mouse_up_out`]. /// /// See [`Context::listener`](crate::Context::listener) to get access to a view's state from this callback. pub fn on_mouse_up_out( &mut self, button: MouseButton, listener: impl Fn(&MouseUpEvent, &mut Window, &mut App) + 'static, ) { self.mouse_up_listeners .push(Box::new(move |event, phase, hitbox, window, cx| { if phase == DispatchPhase::Capture && event.button == button && !hitbox.is_hovered(window) { (listener)(event, window, cx); } })); } /// Bind the given callback to the mouse move event, during the bubble phase. /// The imperative API equivalent to [`InteractiveElement::on_mouse_move`]. /// /// See [`Context::listener`](crate::Context::listener) to get access to a view's state from this callback. pub fn on_mouse_move( &mut self, listener: impl Fn(&MouseMoveEvent, &mut Window, &mut App) + 'static, ) { self.mouse_move_listeners .push(Box::new(move |event, phase, hitbox, window, cx| { if phase == DispatchPhase::Bubble && hitbox.is_hovered(window) { (listener)(event, window, cx); } })); } /// Bind the given callback to the mouse drag event of the given type. Note that this /// will be called for all move events, inside or outside of this element, as long as the /// drag was started with this element under the mouse. Useful for implementing draggable /// UIs that don't conform to a drag and drop style interaction, like resizing. /// The imperative API equivalent to [`InteractiveElement::on_drag_move`]. /// /// See [`Context::listener`](crate::Context::listener) to get access to a view's state from this callback. pub fn on_drag_move( &mut self, listener: impl Fn(&DragMoveEvent, &mut Window, &mut App) + 'static, ) where T: 'static, { self.mouse_move_listeners .push(Box::new(move |event, phase, hitbox, window, cx| { if phase == DispatchPhase::Capture && let Some(drag) = &cx.active_drag && drag.value.as_ref().type_id() == TypeId::of::() { (listener)( &DragMoveEvent { event: event.clone(), bounds: hitbox.bounds, drag: PhantomData, dragged_item: Arc::clone(&drag.value), }, window, cx, ); } })); } /// Bind the given callback to scroll wheel events during the bubble phase. /// The imperative API equivalent to [`InteractiveElement::on_scroll_wheel`]. /// /// See [`Context::listener`](crate::Context::listener) to get access to a view's state from this callback. pub fn on_scroll_wheel( &mut self, listener: impl Fn(&ScrollWheelEvent, &mut Window, &mut App) + 'static, ) { self.scroll_wheel_listeners .push(Box::new(move |event, phase, hitbox, window, cx| { if phase == DispatchPhase::Bubble && hitbox.should_handle_scroll(window) { (listener)(event, window, cx); } })); } /// Bind the given callback to an action dispatch during the capture phase. /// The imperative API equivalent to [`InteractiveElement::capture_action`]. /// /// See [`Context::listener`](crate::Context::listener) to get access to a view's state from this callback. pub fn capture_action( &mut self, listener: impl Fn(&A, &mut Window, &mut App) + 'static, ) { self.action_listeners.push(( TypeId::of::(), Box::new(move |action, phase, window, cx| { let action = action.downcast_ref().unwrap(); if phase == DispatchPhase::Capture { (listener)(action, window, cx) } else { cx.propagate(); } }), )); } /// Bind the given callback to an action dispatch during the bubble phase. /// The imperative API equivalent to [`InteractiveElement::on_action`]. /// /// See [`Context::listener`](crate::Context::listener) to get access to a view's state from this callback. pub fn on_action(&mut self, listener: impl Fn(&A, &mut Window, &mut App) + 'static) { self.action_listeners.push(( TypeId::of::(), Box::new(move |action, phase, window, cx| { let action = action.downcast_ref().unwrap(); if phase == DispatchPhase::Bubble { (listener)(action, window, cx) } }), )); } /// Bind the given callback to an action dispatch, based on a dynamic action parameter /// instead of a type parameter. Useful for component libraries that want to expose /// action bindings to their users. /// The imperative API equivalent to [`InteractiveElement::on_boxed_action`]. /// /// See [`Context::listener`](crate::Context::listener) to get access to a view's state from this callback. pub fn on_boxed_action( &mut self, action: &dyn Action, listener: impl Fn(&dyn Action, &mut Window, &mut App) + 'static, ) { let action = action.boxed_clone(); self.action_listeners.push(( (*action).type_id(), Box::new(move |_, phase, window, cx| { if phase == DispatchPhase::Bubble { (listener)(&*action, window, cx) } }), )); } /// Bind the given callback to key down events during the bubble phase. /// The imperative API equivalent to [`InteractiveElement::on_key_down`]. /// /// See [`Context::listener`](crate::Context::listener) to get access to a view's state from this callback. pub fn on_key_down( &mut self, listener: impl Fn(&KeyDownEvent, &mut Window, &mut App) + 'static, ) { self.key_down_listeners .push(Box::new(move |event, phase, window, cx| { if phase == DispatchPhase::Bubble { (listener)(event, window, cx) } })); } /// Bind the given callback to key down events during the capture phase. /// The imperative API equivalent to [`InteractiveElement::capture_key_down`]. /// /// See [`Context::listener`](crate::Context::listener) to get access to a view's state from this callback. pub fn capture_key_down( &mut self, listener: impl Fn(&KeyDownEvent, &mut Window, &mut App) + 'static, ) { self.key_down_listeners .push(Box::new(move |event, phase, window, cx| { if phase == DispatchPhase::Capture { listener(event, window, cx) } })); } /// Bind the given callback to key up events during the bubble phase. /// The imperative API equivalent to [`InteractiveElement::on_key_up`]. /// /// See [`Context::listener`](crate::Context::listener) to get access to a view's state from this callback. pub fn on_key_up(&mut self, listener: impl Fn(&KeyUpEvent, &mut Window, &mut App) + 'static) { self.key_up_listeners .push(Box::new(move |event, phase, window, cx| { if phase == DispatchPhase::Bubble { listener(event, window, cx) } })); } /// Bind the given callback to key up events during the capture phase. /// The imperative API equivalent to [`InteractiveElement::on_key_up`]. /// /// See [`Context::listener`](crate::Context::listener) to get access to a view's state from this callback. pub fn capture_key_up( &mut self, listener: impl Fn(&KeyUpEvent, &mut Window, &mut App) + 'static, ) { self.key_up_listeners .push(Box::new(move |event, phase, window, cx| { if phase == DispatchPhase::Capture { listener(event, window, cx) } })); } /// Bind the given callback to modifiers changing events. /// The imperative API equivalent to [`InteractiveElement::on_modifiers_changed`]. /// /// See [`Context::listener`](crate::Context::listener) to get access to a view's state from this callback. pub fn on_modifiers_changed( &mut self, listener: impl Fn(&ModifiersChangedEvent, &mut Window, &mut App) + 'static, ) { self.modifiers_changed_listeners .push(Box::new(move |event, window, cx| { listener(event, window, cx) })); } /// Bind the given callback to drop events of the given type, whether or not the drag started on this element. /// The imperative API equivalent to [`InteractiveElement::on_drop`]. /// /// See [`Context::listener`](crate::Context::listener) to get access to a view's state from this callback. pub fn on_drop(&mut self, listener: impl Fn(&T, &mut Window, &mut App) + 'static) { self.drop_listeners.push(( TypeId::of::(), Box::new(move |dragged_value, window, cx| { listener(dragged_value.downcast_ref().unwrap(), window, cx); }), )); } /// Use the given predicate to determine whether or not a drop event should be dispatched to this element. /// The imperative API equivalent to [`InteractiveElement::can_drop`]. pub fn can_drop( &mut self, predicate: impl Fn(&dyn Any, &mut Window, &mut App) -> bool + 'static, ) { self.can_drop_predicate = Some(Box::new(predicate)); } /// Bind the given callback to click events of this element. /// The imperative API equivalent to [`StatefulInteractiveElement::on_click`]. /// /// See [`Context::listener`](crate::Context::listener) to get access to a view's state from this callback. pub fn on_click(&mut self, listener: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static) where Self: Sized, { self.click_listeners.push(Rc::new(move |event, window, cx| { listener(event, window, cx) })); } /// On drag initiation, this callback will be used to create a new view to render the dragged value for a /// drag and drop operation. This API should also be used as the equivalent of 'on drag start' with /// the [`Self::on_drag_move`] API. /// The imperative API equivalent to [`StatefulInteractiveElement::on_drag`]. /// /// See [`Context::listener`](crate::Context::listener) to get access to a view's state from this callback. pub fn on_drag( &mut self, value: T, constructor: impl Fn(&T, Point, &mut Window, &mut App) -> Entity + 'static, ) where Self: Sized, T: 'static, W: 'static + Render, { debug_assert!( self.drag_listener.is_none(), "calling on_drag more than once on the same element is not supported" ); self.drag_listener = Some(( Arc::new(value), Box::new(move |value, offset, window, cx| { constructor(value.downcast_ref().unwrap(), offset, window, cx).into() }), )); } /// Bind the given callback on the hover start and end events of this element. Note that the boolean /// passed to the callback is true when the hover starts and false when it ends. /// The imperative API equivalent to [`StatefulInteractiveElement::on_hover`]. /// /// See [`Context::listener`](crate::Context::listener) to get access to a view's state from this callback. pub fn on_hover(&mut self, listener: impl Fn(&bool, &mut Window, &mut App) + 'static) where Self: Sized, { debug_assert!( self.hover_listener.is_none(), "calling on_hover more than once on the same element is not supported" ); self.hover_listener = Some(Box::new(listener)); } /// Use the given callback to construct a new tooltip view when the mouse hovers over this element. /// The imperative API equivalent to [`StatefulInteractiveElement::tooltip`]. pub fn tooltip(&mut self, build_tooltip: impl Fn(&mut Window, &mut App) -> AnyView + 'static) where Self: Sized, { debug_assert!( self.tooltip_builder.is_none(), "calling tooltip more than once on the same element is not supported" ); self.tooltip_builder = Some(TooltipBuilder { build: Rc::new(build_tooltip), hoverable: false, }); } /// Use the given callback to construct a new tooltip view when the mouse hovers over this element. /// The tooltip itself is also hoverable and won't disappear when the user moves the mouse into /// the tooltip. The imperative API equivalent to [`StatefulInteractiveElement::hoverable_tooltip`]. pub fn hoverable_tooltip( &mut self, build_tooltip: impl Fn(&mut Window, &mut App) -> AnyView + 'static, ) where Self: Sized, { debug_assert!( self.tooltip_builder.is_none(), "calling tooltip more than once on the same element is not supported" ); self.tooltip_builder = Some(TooltipBuilder { build: Rc::new(build_tooltip), hoverable: true, }); } /// Block the mouse from all interactions with elements behind this element's hitbox. Typically /// `block_mouse_except_scroll` should be preferred. /// /// The imperative API equivalent to [`InteractiveElement::occlude`] pub fn occlude_mouse(&mut self) { self.hitbox_behavior = HitboxBehavior::BlockMouse; } /// Set the bounds of this element as a window control area for the platform window. /// The imperative API equivalent to [`InteractiveElement::window_control_area`] pub fn window_control_area(&mut self, area: WindowControlArea) { self.window_control = Some(area); } /// Block non-scroll mouse interactions with elements behind this element's hitbox. /// The imperative API equivalent to [`InteractiveElement::block_mouse_except_scroll`]. /// /// See [`Hitbox::is_hovered`] for details. pub fn block_mouse_except_scroll(&mut self) { self.hitbox_behavior = HitboxBehavior::BlockMouseExceptScroll; } } /// A trait for elements that want to use the standard GPUI event handlers that don't /// require any state. pub trait InteractiveElement: Sized { /// Retrieve the interactivity state associated with this element fn interactivity(&mut self) -> &mut Interactivity; /// Assign this element to a group of elements that can be styled together fn group(mut self, group: impl Into) -> Self { self.interactivity().group = Some(group.into()); self } /// Assign this element an ID, so that it can be used with interactivity fn id(mut self, id: impl Into) -> Stateful { self.interactivity().element_id = Some(id.into()); Stateful { element: self } } /// Track the focus state of the given focus handle on this element. /// If the focus handle is focused by the application, this element will /// apply its focused styles. fn track_focus(mut self, focus_handle: &FocusHandle) -> Self { self.interactivity().focusable = true; self.interactivity().tracked_focus_handle = Some(focus_handle.clone()); self } /// Set whether this element is a tab stop. /// /// When false, the element remains in tab-index order but cannot be reached via keyboard navigation. /// Useful for container elements: focus the container, then call `window.focus_next()` to focus /// the first tab stop inside it while having the container element itself be unreachable via the keyboard. /// Should only be used with `tab_index`. fn tab_stop(mut self, tab_stop: bool) -> Self { self.interactivity().tab_stop = tab_stop; self } /// Set index of the tab stop order, and set this node as a tab stop. /// This will default the element to being a tab stop. See [`Self::tab_stop`] for more information. /// This should only be used in conjunction with `tab_group` /// in order to not interfere with the tab index of other elements. fn tab_index(mut self, index: isize) -> Self { self.interactivity().focusable = true; self.interactivity().tab_index = Some(index); self.interactivity().tab_stop = true; self } /// Designate this div as a "tab group". Tab groups have their own location in the tab-index order, /// but for children of the tab group, the tab index is reset to 0. This can be useful for swapping /// the order of tab stops within the group, without having to renumber all the tab stops in the whole /// application. fn tab_group(mut self) -> Self { self.interactivity().tab_group = true; if self.interactivity().tab_index.is_none() { self.interactivity().tab_index = Some(0); } self } /// Set the keymap context for this element. This will be used to determine /// which action to dispatch from the keymap. fn key_context(mut self, key_context: C) -> Self where C: TryInto, E: Debug, { if let Some(key_context) = key_context.try_into().log_err() { self.interactivity().key_context = Some(key_context); } self } /// Apply the given style to this element when the mouse hovers over it fn hover(mut self, f: impl FnOnce(StyleRefinement) -> StyleRefinement) -> Self { debug_assert!( self.interactivity().hover_style.is_none(), "hover style already set" ); self.interactivity().hover_style = Some(Box::new(f(StyleRefinement::default()))); self } /// Apply the given style to this element when the mouse hovers over a group member fn group_hover( mut self, group_name: impl Into, f: impl FnOnce(StyleRefinement) -> StyleRefinement, ) -> Self { self.interactivity().group_hover_style = Some(GroupStyle { group: group_name.into(), style: Box::new(f(StyleRefinement::default())), }); self } /// Bind the given callback to the mouse down event for the given mouse button. /// The fluent API equivalent to [`Interactivity::on_mouse_down`]. /// /// See [`Context::listener`](crate::Context::listener) to get access to the view state from this callback. fn on_mouse_down( mut self, button: MouseButton, listener: impl Fn(&MouseDownEvent, &mut Window, &mut App) + 'static, ) -> Self { self.interactivity().on_mouse_down(button, listener); self } #[cfg(any(test, feature = "test-support"))] /// Set a key that can be used to look up this element's bounds /// in the [`crate::VisualTestContext::debug_bounds`] map /// This is a noop in release builds fn debug_selector(mut self, f: impl FnOnce() -> String) -> Self { self.interactivity().debug_selector = Some(f()); self } #[cfg(not(any(test, feature = "test-support")))] /// Set a key that can be used to look up this element's bounds /// in the [`crate::VisualTestContext::debug_bounds`] map /// This is a noop in release builds #[inline] fn debug_selector(self, _: impl FnOnce() -> String) -> Self { self } /// Bind the given callback to the mouse down event for any button, during the capture phase. /// The fluent API equivalent to [`Interactivity::capture_any_mouse_down`]. /// /// See [`Context::listener`](crate::Context::listener) to get access to a view's state from this callback. fn capture_any_mouse_down( mut self, listener: impl Fn(&MouseDownEvent, &mut Window, &mut App) + 'static, ) -> Self { self.interactivity().capture_any_mouse_down(listener); self } /// Bind the given callback to the mouse down event for any button, during the capture phase. /// The fluent API equivalent to [`Interactivity::on_any_mouse_down`]. /// /// See [`Context::listener`](crate::Context::listener) to get access to a view's state from this callback. fn on_any_mouse_down( mut self, listener: impl Fn(&MouseDownEvent, &mut Window, &mut App) + 'static, ) -> Self { self.interactivity().on_any_mouse_down(listener); self } /// Bind the given callback to the mouse up event for the given button, during the bubble phase. /// The fluent API equivalent to [`Interactivity::on_mouse_up`]. /// /// See [`Context::listener`](crate::Context::listener) to get access to a view's state from this callback. fn on_mouse_up( mut self, button: MouseButton, listener: impl Fn(&MouseUpEvent, &mut Window, &mut App) + 'static, ) -> Self { self.interactivity().on_mouse_up(button, listener); self } /// Bind the given callback to the mouse up event for any button, during the capture phase. /// The fluent API equivalent to [`Interactivity::capture_any_mouse_up`]. /// /// See [`Context::listener`](crate::Context::listener) to get access to a view's state from this callback. fn capture_any_mouse_up( mut self, listener: impl Fn(&MouseUpEvent, &mut Window, &mut App) + 'static, ) -> Self { self.interactivity().capture_any_mouse_up(listener); self } /// Bind the given callback to the mouse down event, on any button, during the capture phase, /// when the mouse is outside of the bounds of this element. /// The fluent API equivalent to [`Interactivity::on_mouse_down_out`]. /// /// See [`Context::listener`](crate::Context::listener) to get access to a view's state from this callback. fn on_mouse_down_out( mut self, listener: impl Fn(&MouseDownEvent, &mut Window, &mut App) + 'static, ) -> Self { self.interactivity().on_mouse_down_out(listener); self } /// Bind the given callback to the mouse up event, for the given button, during the capture phase, /// when the mouse is outside of the bounds of this element. /// The fluent API equivalent to [`Interactivity::on_mouse_up_out`]. /// /// See [`Context::listener`](crate::Context::listener) to get access to a view's state from this callback. fn on_mouse_up_out( mut self, button: MouseButton, listener: impl Fn(&MouseUpEvent, &mut Window, &mut App) + 'static, ) -> Self { self.interactivity().on_mouse_up_out(button, listener); self } /// Bind the given callback to the mouse move event, during the bubble phase. /// The fluent API equivalent to [`Interactivity::on_mouse_move`]. /// /// See [`Context::listener`](crate::Context::listener) to get access to a view's state from this callback. fn on_mouse_move( mut self, listener: impl Fn(&MouseMoveEvent, &mut Window, &mut App) + 'static, ) -> Self { self.interactivity().on_mouse_move(listener); self } /// Bind the given callback to the mouse drag event of the given type. Note that this /// will be called for all move events, inside or outside of this element, as long as the /// drag was started with this element under the mouse. Useful for implementing draggable /// UIs that don't conform to a drag and drop style interaction, like resizing. /// The fluent API equivalent to [`Interactivity::on_drag_move`]. /// /// See [`Context::listener`](crate::Context::listener) to get access to a view's state from this callback. fn on_drag_move( mut self, listener: impl Fn(&DragMoveEvent, &mut Window, &mut App) + 'static, ) -> Self { self.interactivity().on_drag_move(listener); self } /// Bind the given callback to scroll wheel events during the bubble phase. /// The fluent API equivalent to [`Interactivity::on_scroll_wheel`]. /// /// See [`Context::listener`](crate::Context::listener) to get access to a view's state from this callback. fn on_scroll_wheel( mut self, listener: impl Fn(&ScrollWheelEvent, &mut Window, &mut App) + 'static, ) -> Self { self.interactivity().on_scroll_wheel(listener); self } /// Capture the given action, before normal action dispatch can fire. /// The fluent API equivalent to [`Interactivity::capture_action`]. /// /// See [`Context::listener`](crate::Context::listener) to get access to a view's state from this callback. fn capture_action( mut self, listener: impl Fn(&A, &mut Window, &mut App) + 'static, ) -> Self { self.interactivity().capture_action(listener); self } /// Bind the given callback to an action dispatch during the bubble phase. /// The fluent API equivalent to [`Interactivity::on_action`]. /// /// See [`Context::listener`](crate::Context::listener) to get access to a view's state from this callback. fn on_action( mut self, listener: impl Fn(&A, &mut Window, &mut App) + 'static, ) -> Self { self.interactivity().on_action(listener); self } /// Bind the given callback to an action dispatch, based on a dynamic action parameter /// instead of a type parameter. Useful for component libraries that want to expose /// action bindings to their users. /// The fluent API equivalent to [`Interactivity::on_boxed_action`]. /// /// See [`Context::listener`](crate::Context::listener) to get access to a view's state from this callback. fn on_boxed_action( mut self, action: &dyn Action, listener: impl Fn(&dyn Action, &mut Window, &mut App) + 'static, ) -> Self { self.interactivity().on_boxed_action(action, listener); self } /// Bind the given callback to key down events during the bubble phase. /// The fluent API equivalent to [`Interactivity::on_key_down`]. /// /// See [`Context::listener`](crate::Context::listener) to get access to a view's state from this callback. fn on_key_down( mut self, listener: impl Fn(&KeyDownEvent, &mut Window, &mut App) + 'static, ) -> Self { self.interactivity().on_key_down(listener); self } /// Bind the given callback to key down events during the capture phase. /// The fluent API equivalent to [`Interactivity::capture_key_down`]. /// /// See [`Context::listener`](crate::Context::listener) to get access to a view's state from this callback. fn capture_key_down( mut self, listener: impl Fn(&KeyDownEvent, &mut Window, &mut App) + 'static, ) -> Self { self.interactivity().capture_key_down(listener); self } /// Bind the given callback to key up events during the bubble phase. /// The fluent API equivalent to [`Interactivity::on_key_up`]. /// /// See [`Context::listener`](crate::Context::listener) to get access to a view's state from this callback. fn on_key_up( mut self, listener: impl Fn(&KeyUpEvent, &mut Window, &mut App) + 'static, ) -> Self { self.interactivity().on_key_up(listener); self } /// Bind the given callback to key up events during the capture phase. /// The fluent API equivalent to [`Interactivity::capture_key_up`]. /// /// See [`Context::listener`](crate::Context::listener) to get access to a view's state from this callback. fn capture_key_up( mut self, listener: impl Fn(&KeyUpEvent, &mut Window, &mut App) + 'static, ) -> Self { self.interactivity().capture_key_up(listener); self } /// Bind the given callback to modifiers changing events. /// The fluent API equivalent to [`Interactivity::on_modifiers_changed`]. /// /// See [`Context::listener`](crate::Context::listener) to get access to a view's state from this callback. fn on_modifiers_changed( mut self, listener: impl Fn(&ModifiersChangedEvent, &mut Window, &mut App) + 'static, ) -> Self { self.interactivity().on_modifiers_changed(listener); self } /// Apply the given style when the given data type is dragged over this element fn drag_over( mut self, f: impl 'static + Fn(StyleRefinement, &S, &mut Window, &mut App) -> StyleRefinement, ) -> Self { self.interactivity().drag_over_styles.push(( TypeId::of::(), Box::new(move |currently_dragged: &dyn Any, window, cx| { f( StyleRefinement::default(), currently_dragged.downcast_ref::().unwrap(), window, cx, ) }), )); self } /// Apply the given style when the given data type is dragged over this element's group fn group_drag_over( mut self, group_name: impl Into, f: impl FnOnce(StyleRefinement) -> StyleRefinement, ) -> Self { self.interactivity().group_drag_over_styles.push(( TypeId::of::(), GroupStyle { group: group_name.into(), style: Box::new(f(StyleRefinement::default())), }, )); self } /// Bind the given callback to drop events of the given type, whether or not the drag started on this element. /// The fluent API equivalent to [`Interactivity::on_drop`]. /// /// See [`Context::listener`](crate::Context::listener) to get access to a view's state from this callback. fn on_drop( mut self, listener: impl Fn(&T, &mut Window, &mut App) + 'static, ) -> Self { self.interactivity().on_drop(listener); self } /// Use the given predicate to determine whether or not a drop event should be dispatched to this element. /// The fluent API equivalent to [`Interactivity::can_drop`]. fn can_drop( mut self, predicate: impl Fn(&dyn Any, &mut Window, &mut App) -> bool + 'static, ) -> Self { self.interactivity().can_drop(predicate); self } /// Block the mouse from all interactions with elements behind this element's hitbox. Typically /// `block_mouse_except_scroll` should be preferred. /// The fluent API equivalent to [`Interactivity::occlude_mouse`]. fn occlude(mut self) -> Self { self.interactivity().occlude_mouse(); self } /// Set the bounds of this element as a window control area for the platform window. /// The fluent API equivalent to [`Interactivity::window_control_area`]. fn window_control_area(mut self, area: WindowControlArea) -> Self { self.interactivity().window_control_area(area); self } /// Block non-scroll mouse interactions with elements behind this element's hitbox. /// The fluent API equivalent to [`Interactivity::block_mouse_except_scroll`]. /// /// See [`Hitbox::is_hovered`] for details. fn block_mouse_except_scroll(mut self) -> Self { self.interactivity().block_mouse_except_scroll(); self } /// Set the given styles to be applied when this element, specifically, is focused. /// Requires that the element is focusable. Elements can be made focusable using [`InteractiveElement::track_focus`]. fn focus(mut self, f: impl FnOnce(StyleRefinement) -> StyleRefinement) -> Self where Self: Sized, { self.interactivity().focus_style = Some(Box::new(f(StyleRefinement::default()))); self } /// Set the given styles to be applied when this element is inside another element that is focused. /// Requires that the element is focusable. Elements can be made focusable using [`InteractiveElement::track_focus`]. fn in_focus(mut self, f: impl FnOnce(StyleRefinement) -> StyleRefinement) -> Self where Self: Sized, { self.interactivity().in_focus_style = Some(Box::new(f(StyleRefinement::default()))); self } /// Set the given styles to be applied when this element is focused via keyboard navigation. /// This is similar to CSS's `:focus-visible` pseudo-class - it only applies when the element /// is focused AND the user is navigating via keyboard (not mouse clicks). /// Requires that the element is focusable. Elements can be made focusable using [`InteractiveElement::track_focus`]. fn focus_visible(mut self, f: impl FnOnce(StyleRefinement) -> StyleRefinement) -> Self where Self: Sized, { self.interactivity().focus_visible_style = Some(Box::new(f(StyleRefinement::default()))); self } } /// A trait for elements that want to use the standard GPUI interactivity features /// that require state. pub trait StatefulInteractiveElement: InteractiveElement { /// Set this element to focusable. fn focusable(mut self) -> Self { self.interactivity().focusable = true; self } /// Set the overflow x and y to scroll. fn overflow_scroll(mut self) -> Self { self.interactivity().base_style.overflow.x = Some(Overflow::Scroll); self.interactivity().base_style.overflow.y = Some(Overflow::Scroll); self } /// Set the overflow x to scroll. fn overflow_x_scroll(mut self) -> Self { self.interactivity().base_style.overflow.x = Some(Overflow::Scroll); self } /// Set the overflow y to scroll. fn overflow_y_scroll(mut self) -> Self { self.interactivity().base_style.overflow.y = Some(Overflow::Scroll); self } /// Set the space to be reserved for rendering the scrollbar. /// /// This will only affect the layout of the element when overflow for this element is set to /// `Overflow::Scroll`. fn scrollbar_width(mut self, width: impl Into) -> Self { self.interactivity().base_style.scrollbar_width = Some(width.into()); self } /// Track the scroll state of this element with the given handle. fn track_scroll(mut self, scroll_handle: &ScrollHandle) -> Self { self.interactivity().tracked_scroll_handle = Some(scroll_handle.clone()); self } /// Track the scroll state of this element with the given handle. fn anchor_scroll(mut self, scroll_anchor: Option) -> Self { self.interactivity().scroll_anchor = scroll_anchor; self } /// Set the given styles to be applied when this element is active. fn active(mut self, f: impl FnOnce(StyleRefinement) -> StyleRefinement) -> Self where Self: Sized, { self.interactivity().active_style = Some(Box::new(f(StyleRefinement::default()))); self } /// Set the given styles to be applied when this element's group is active. fn group_active( mut self, group_name: impl Into, f: impl FnOnce(StyleRefinement) -> StyleRefinement, ) -> Self where Self: Sized, { self.interactivity().group_active_style = Some(GroupStyle { group: group_name.into(), style: Box::new(f(StyleRefinement::default())), }); self } /// Bind the given callback to click events of this element. /// The fluent API equivalent to [`Interactivity::on_click`]. /// /// See [`Context::listener`](crate::Context::listener) to get access to a view's state from this callback. fn on_click(mut self, listener: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static) -> Self where Self: Sized, { self.interactivity().on_click(listener); self } /// On drag initiation, this callback will be used to create a new view to render the dragged value for a /// drag and drop operation. This API should also be used as the equivalent of 'on drag start' with /// the [`InteractiveElement::on_drag_move`] API. /// The callback also has access to the offset of triggering click from the origin of parent element. /// The fluent API equivalent to [`Interactivity::on_drag`]. /// /// See [`Context::listener`](crate::Context::listener) to get access to a view's state from this callback. fn on_drag( mut self, value: T, constructor: impl Fn(&T, Point, &mut Window, &mut App) -> Entity + 'static, ) -> Self where Self: Sized, T: 'static, W: 'static + Render, { self.interactivity().on_drag(value, constructor); self } /// Bind the given callback on the hover start and end events of this element. Note that the boolean /// passed to the callback is true when the hover starts and false when it ends. /// The fluent API equivalent to [`Interactivity::on_hover`]. /// /// See [`Context::listener`](crate::Context::listener) to get access to a view's state from this callback. fn on_hover(mut self, listener: impl Fn(&bool, &mut Window, &mut App) + 'static) -> Self where Self: Sized, { self.interactivity().on_hover(listener); self } /// Use the given callback to construct a new tooltip view when the mouse hovers over this element. /// The fluent API equivalent to [`Interactivity::tooltip`]. fn tooltip(mut self, build_tooltip: impl Fn(&mut Window, &mut App) -> AnyView + 'static) -> Self where Self: Sized, { self.interactivity().tooltip(build_tooltip); self } /// Use the given callback to construct a new tooltip view when the mouse hovers over this element. /// The tooltip itself is also hoverable and won't disappear when the user moves the mouse into /// the tooltip. The fluent API equivalent to [`Interactivity::hoverable_tooltip`]. fn hoverable_tooltip( mut self, build_tooltip: impl Fn(&mut Window, &mut App) -> AnyView + 'static, ) -> Self where Self: Sized, { self.interactivity().hoverable_tooltip(build_tooltip); self } } pub(crate) type MouseDownListener = Box; pub(crate) type MouseUpListener = Box; pub(crate) type MouseMoveListener = Box; pub(crate) type ScrollWheelListener = Box; pub(crate) type ClickListener = Rc; pub(crate) type DragListener = Box, &mut Window, &mut App) -> AnyView + 'static>; type DropListener = Box; type CanDropPredicate = Box bool + 'static>; pub(crate) struct TooltipBuilder { build: Rc AnyView + 'static>, hoverable: bool, } pub(crate) type KeyDownListener = Box; pub(crate) type KeyUpListener = Box; pub(crate) type ModifiersChangedListener = Box; pub(crate) type ActionListener = Box; /// Construct a new [`Div`] element #[track_caller] pub fn div() -> Div { Div { interactivity: Interactivity::new(), children: SmallVec::default(), prepaint_listener: None, image_cache: None, } } /// A [`Div`] element, the all-in-one element for building complex UIs in GPUI pub struct Div { interactivity: Interactivity, children: SmallVec<[StackSafe; 2]>, prepaint_listener: Option>, &mut Window, &mut App) + 'static>>, image_cache: Option>, } impl Div { /// Add a listener to be called when the children of this `Div` are prepainted. /// This allows you to store the [`Bounds`] of the children for later use. pub fn on_children_prepainted( mut self, listener: impl Fn(Vec>, &mut Window, &mut App) + 'static, ) -> Self { self.prepaint_listener = Some(Box::new(listener)); self } /// Add an image cache at the location of this div in the element tree. pub fn image_cache(mut self, cache: impl ImageCacheProvider) -> Self { self.image_cache = Some(Box::new(cache)); self } } /// A frame state for a `Div` element, which contains layout IDs for its children. /// /// This struct is used internally by the `Div` element to manage the layout state of its children /// during the UI update cycle. It holds a small vector of `LayoutId` values, each corresponding to /// a child element of the `Div`. These IDs are used to query the layout engine for the computed /// bounds of the children after the layout phase is complete. pub struct DivFrameState { child_layout_ids: SmallVec<[LayoutId; 2]>, } /// Interactivity state displayed an manipulated in the inspector. #[derive(Clone)] pub struct DivInspectorState { /// The inspected element's base style. This is used for both inspecting and modifying the /// state. In the future it will make sense to separate the read and write, possibly tracking /// the modifications. #[cfg(any(feature = "inspector", debug_assertions))] pub base_style: Box, /// Inspects the bounds of the element. pub bounds: Bounds, /// Size of the children of the element, or `bounds.size` if it has no children. pub content_size: Size, } impl Styled for Div { fn style(&mut self) -> &mut StyleRefinement { &mut self.interactivity.base_style } } impl InteractiveElement for Div { fn interactivity(&mut self) -> &mut Interactivity { &mut self.interactivity } } impl ParentElement for Div { fn extend(&mut self, elements: impl IntoIterator) { self.children .extend(elements.into_iter().map(StackSafe::new)) } } impl Element for Div { type RequestLayoutState = DivFrameState; type PrepaintState = Option; fn id(&self) -> Option { self.interactivity.element_id.clone() } fn source_location(&self) -> Option<&'static std::panic::Location<'static>> { self.interactivity.source_location() } #[stacksafe] fn request_layout( &mut self, global_id: Option<&GlobalElementId>, inspector_id: Option<&InspectorElementId>, window: &mut Window, cx: &mut App, ) -> (LayoutId, Self::RequestLayoutState) { let mut child_layout_ids = SmallVec::new(); let image_cache = self .image_cache .as_mut() .map(|provider| provider.provide(window, cx)); let layout_id = window.with_image_cache(image_cache, |window| { self.interactivity.request_layout( global_id, inspector_id, window, cx, |style, window, cx| { window.with_text_style(style.text_style().cloned(), |window| { child_layout_ids = self .children .iter_mut() .map(|child| child.request_layout(window, cx)) .collect::>(); window.request_layout(style, child_layout_ids.iter().copied(), cx) }) }, ) }); (layout_id, DivFrameState { child_layout_ids }) } #[stacksafe] fn prepaint( &mut self, global_id: Option<&GlobalElementId>, inspector_id: Option<&InspectorElementId>, bounds: Bounds, request_layout: &mut Self::RequestLayoutState, window: &mut Window, cx: &mut App, ) -> Option { let has_prepaint_listener = self.prepaint_listener.is_some(); let mut children_bounds = Vec::with_capacity(if has_prepaint_listener { request_layout.child_layout_ids.len() } else { 0 }); let mut child_min = point(Pixels::MAX, Pixels::MAX); let mut child_max = Point::default(); if let Some(handle) = self.interactivity.scroll_anchor.as_ref() { *handle.last_origin.borrow_mut() = bounds.origin - window.element_offset(); } let content_size = if request_layout.child_layout_ids.is_empty() { bounds.size } else if let Some(scroll_handle) = self.interactivity.tracked_scroll_handle.as_ref() { let mut state = scroll_handle.0.borrow_mut(); state.child_bounds = Vec::with_capacity(request_layout.child_layout_ids.len()); for child_layout_id in &request_layout.child_layout_ids { let child_bounds = window.layout_bounds(*child_layout_id); child_min = child_min.min(&child_bounds.origin); child_max = child_max.max(&child_bounds.bottom_right()); state.child_bounds.push(child_bounds); } (child_max - child_min).into() } else { for child_layout_id in &request_layout.child_layout_ids { let child_bounds = window.layout_bounds(*child_layout_id); child_min = child_min.min(&child_bounds.origin); child_max = child_max.max(&child_bounds.bottom_right()); if has_prepaint_listener { children_bounds.push(child_bounds); } } (child_max - child_min).into() }; if let Some(scroll_handle) = self.interactivity.tracked_scroll_handle.as_ref() { scroll_handle.scroll_to_active_item(); } self.interactivity.prepaint( global_id, inspector_id, bounds, content_size, window, cx, |style, scroll_offset, hitbox, window, cx| { // skip children if style.display == Display::None { return hitbox; } window.with_element_offset(scroll_offset, |window| { for child in &mut self.children { child.prepaint(window, cx); } }); if let Some(listener) = self.prepaint_listener.as_ref() { listener(children_bounds, window, cx); } hitbox }, ) } #[stacksafe] fn paint( &mut self, global_id: Option<&GlobalElementId>, inspector_id: Option<&InspectorElementId>, bounds: Bounds, _request_layout: &mut Self::RequestLayoutState, hitbox: &mut Option, window: &mut Window, cx: &mut App, ) { let image_cache = self .image_cache .as_mut() .map(|provider| provider.provide(window, cx)); window.with_image_cache(image_cache, |window| { self.interactivity.paint( global_id, inspector_id, bounds, hitbox.as_ref(), window, cx, |style, window, cx| { // skip children if style.display == Display::None { return; } for child in &mut self.children { child.paint(window, cx); } }, ) }); } } impl IntoElement for Div { type Element = Self; fn into_element(self) -> Self::Element { self } } /// The interactivity struct. Powers all of the general-purpose /// interactivity in the `Div` element. #[derive(Default)] pub struct Interactivity { /// The element ID of the element. In id is required to support a stateful subset of the interactivity such as on_click. pub element_id: Option, /// Whether the element was clicked. This will only be present after layout. pub active: Option, /// Whether the element was hovered. This will only be present after paint if an hitbox /// was created for the interactive element. pub hovered: Option, pub(crate) tooltip_id: Option, pub(crate) content_size: Size, pub(crate) key_context: Option, pub(crate) focusable: bool, pub(crate) tracked_focus_handle: Option, pub(crate) tracked_scroll_handle: Option, pub(crate) scroll_anchor: Option, pub(crate) scroll_offset: Option>>>, pub(crate) group: Option, /// The base style of the element, before any modifications are applied /// by focus, active, etc. pub base_style: Box, pub(crate) focus_style: Option>, pub(crate) in_focus_style: Option>, pub(crate) focus_visible_style: Option>, pub(crate) hover_style: Option>, pub(crate) group_hover_style: Option, pub(crate) active_style: Option>, pub(crate) group_active_style: Option, pub(crate) drag_over_styles: Vec<( TypeId, Box StyleRefinement>, )>, pub(crate) group_drag_over_styles: Vec<(TypeId, GroupStyle)>, pub(crate) mouse_down_listeners: Vec, pub(crate) mouse_up_listeners: Vec, pub(crate) mouse_move_listeners: Vec, pub(crate) scroll_wheel_listeners: Vec, pub(crate) key_down_listeners: Vec, pub(crate) key_up_listeners: Vec, pub(crate) modifiers_changed_listeners: Vec, pub(crate) action_listeners: Vec<(TypeId, ActionListener)>, pub(crate) drop_listeners: Vec<(TypeId, DropListener)>, pub(crate) can_drop_predicate: Option, pub(crate) click_listeners: Vec, pub(crate) drag_listener: Option<(Arc, DragListener)>, pub(crate) hover_listener: Option>, pub(crate) tooltip_builder: Option, pub(crate) window_control: Option, pub(crate) hitbox_behavior: HitboxBehavior, pub(crate) tab_index: Option, pub(crate) tab_group: bool, pub(crate) tab_stop: bool, #[cfg(any(feature = "inspector", debug_assertions))] pub(crate) source_location: Option<&'static core::panic::Location<'static>>, #[cfg(any(test, feature = "test-support"))] pub(crate) debug_selector: Option, } impl Interactivity { /// Layout this element according to this interactivity state's configured styles pub fn request_layout( &mut self, global_id: Option<&GlobalElementId>, _inspector_id: Option<&InspectorElementId>, window: &mut Window, cx: &mut App, f: impl FnOnce(Style, &mut Window, &mut App) -> LayoutId, ) -> LayoutId { #[cfg(any(feature = "inspector", debug_assertions))] window.with_inspector_state( _inspector_id, cx, |inspector_state: &mut Option, _window| { if let Some(inspector_state) = inspector_state { self.base_style = inspector_state.base_style.clone(); } else { *inspector_state = Some(DivInspectorState { base_style: self.base_style.clone(), bounds: Default::default(), content_size: Default::default(), }) } }, ); window.with_optional_element_state::( global_id, |element_state, window| { let mut element_state = element_state.map(|element_state| element_state.unwrap_or_default()); if let Some(element_state) = element_state.as_ref() && cx.has_active_drag() { if let Some(pending_mouse_down) = element_state.pending_mouse_down.as_ref() { *pending_mouse_down.borrow_mut() = None; } if let Some(clicked_state) = element_state.clicked_state.as_ref() { *clicked_state.borrow_mut() = ElementClickedState::default(); } } // Ensure we store a focus handle in our element state if we're focusable. // If there's an explicit focus handle we're tracking, use that. Otherwise // create a new handle and store it in the element state, which lives for as // as frames contain an element with this id. if self.focusable && self.tracked_focus_handle.is_none() && let Some(element_state) = element_state.as_mut() { let mut handle = element_state .focus_handle .get_or_insert_with(|| cx.focus_handle()) .clone() .tab_stop(self.tab_stop); if let Some(index) = self.tab_index { handle = handle.tab_index(index); } self.tracked_focus_handle = Some(handle); } if let Some(scroll_handle) = self.tracked_scroll_handle.as_ref() { self.scroll_offset = Some(scroll_handle.0.borrow().offset.clone()); } else if (self.base_style.overflow.x == Some(Overflow::Scroll) || self.base_style.overflow.y == Some(Overflow::Scroll)) && let Some(element_state) = element_state.as_mut() { self.scroll_offset = Some( element_state .scroll_offset .get_or_insert_with(Rc::default) .clone(), ); } let style = self.compute_style_internal(None, element_state.as_mut(), window, cx); let layout_id = f(style, window, cx); (layout_id, element_state) }, ) } /// Commit the bounds of this element according to this interactivity state's configured styles. pub fn prepaint( &mut self, global_id: Option<&GlobalElementId>, _inspector_id: Option<&InspectorElementId>, bounds: Bounds, content_size: Size, window: &mut Window, cx: &mut App, f: impl FnOnce(&Style, Point, Option, &mut Window, &mut App) -> R, ) -> R { self.content_size = content_size; #[cfg(any(feature = "inspector", debug_assertions))] window.with_inspector_state( _inspector_id, cx, |inspector_state: &mut Option, _window| { if let Some(inspector_state) = inspector_state { inspector_state.bounds = bounds; inspector_state.content_size = content_size; } }, ); if let Some(focus_handle) = self.tracked_focus_handle.as_ref() { window.set_focus_handle(focus_handle, cx); } window.with_optional_element_state::( global_id, |element_state, window| { let mut element_state = element_state.map(|element_state| element_state.unwrap_or_default()); let style = self.compute_style_internal(None, element_state.as_mut(), window, cx); if let Some(element_state) = element_state.as_mut() { if let Some(clicked_state) = element_state.clicked_state.as_ref() { let clicked_state = clicked_state.borrow(); self.active = Some(clicked_state.element); } if let Some(active_tooltip) = element_state.active_tooltip.as_ref() { if self.tooltip_builder.is_some() { self.tooltip_id = set_tooltip_on_window(active_tooltip, window); } else { // If there is no longer a tooltip builder, remove the active tooltip. element_state.active_tooltip.take(); } } } window.with_text_style(style.text_style().cloned(), |window| { window.with_content_mask( style.overflow_mask(bounds, window.rem_size()), |window| { let hitbox = if self.should_insert_hitbox(&style, window, cx) { Some(window.insert_hitbox(bounds, self.hitbox_behavior)) } else { None }; let scroll_offset = self.clamp_scroll_position(bounds, &style, window, cx); let result = f(&style, scroll_offset, hitbox, window, cx); (result, element_state) }, ) }) }, ) } fn should_insert_hitbox(&self, style: &Style, window: &Window, cx: &App) -> bool { self.hitbox_behavior != HitboxBehavior::Normal || self.window_control.is_some() || style.mouse_cursor.is_some() || self.group.is_some() || self.scroll_offset.is_some() || self.tracked_focus_handle.is_some() || self.hover_style.is_some() || self.group_hover_style.is_some() || self.hover_listener.is_some() || !self.mouse_up_listeners.is_empty() || !self.mouse_down_listeners.is_empty() || !self.mouse_move_listeners.is_empty() || !self.click_listeners.is_empty() || !self.scroll_wheel_listeners.is_empty() || self.drag_listener.is_some() || !self.drop_listeners.is_empty() || self.tooltip_builder.is_some() || window.is_inspector_picking(cx) } fn clamp_scroll_position( &self, bounds: Bounds, style: &Style, window: &mut Window, _cx: &mut App, ) -> Point { fn round_to_two_decimals(pixels: Pixels) -> Pixels { const ROUNDING_FACTOR: f32 = 100.0; (pixels * ROUNDING_FACTOR).round() / ROUNDING_FACTOR } if let Some(scroll_offset) = self.scroll_offset.as_ref() { let mut scroll_to_bottom = false; let mut tracked_scroll_handle = self .tracked_scroll_handle .as_ref() .map(|handle| handle.0.borrow_mut()); if let Some(mut scroll_handle_state) = tracked_scroll_handle.as_deref_mut() { scroll_handle_state.overflow = style.overflow; scroll_to_bottom = mem::take(&mut scroll_handle_state.scroll_to_bottom); } let rem_size = window.rem_size(); let padding = style.padding.to_pixels(bounds.size.into(), rem_size); let padding_size = size(padding.left + padding.right, padding.top + padding.bottom); // The floating point values produced by Taffy and ours often vary // slightly after ~5 decimal places. This can lead to cases where after // subtracting these, the container becomes scrollable for less than // 0.00000x pixels. As we generally don't benefit from a precision that // high for the maximum scroll, we round the scroll max to 2 decimal // places here. let padded_content_size = self.content_size + padding_size; let scroll_max = (padded_content_size - bounds.size) .map(round_to_two_decimals) .max(&Default::default()); // Clamp scroll offset in case scroll max is smaller now (e.g., if children // were removed or the bounds became larger). let mut scroll_offset = scroll_offset.borrow_mut(); scroll_offset.x = scroll_offset.x.clamp(-scroll_max.width, px(0.)); if scroll_to_bottom { scroll_offset.y = -scroll_max.height; } else { scroll_offset.y = scroll_offset.y.clamp(-scroll_max.height, px(0.)); } if let Some(mut scroll_handle_state) = tracked_scroll_handle { scroll_handle_state.max_offset = scroll_max; scroll_handle_state.bounds = bounds; } *scroll_offset } else { Point::default() } } /// Paint this element according to this interactivity state's configured styles /// and bind the element's mouse and keyboard events. /// /// content_size is the size of the content of the element, which may be larger than the /// element's bounds if the element is scrollable. /// /// the final computed style will be passed to the provided function, along /// with the current scroll offset pub fn paint( &mut self, global_id: Option<&GlobalElementId>, _inspector_id: Option<&InspectorElementId>, bounds: Bounds, hitbox: Option<&Hitbox>, window: &mut Window, cx: &mut App, f: impl FnOnce(&Style, &mut Window, &mut App), ) { self.hovered = hitbox.map(|hitbox| hitbox.is_hovered(window)); window.with_optional_element_state::( global_id, |element_state, window| { let mut element_state = element_state.map(|element_state| element_state.unwrap_or_default()); let style = self.compute_style_internal(hitbox, element_state.as_mut(), window, cx); #[cfg(any(feature = "test-support", test))] if let Some(debug_selector) = &self.debug_selector { window .next_frame .debug_bounds .insert(debug_selector.clone(), bounds); } self.paint_hover_group_handler(window, cx); if style.visibility == Visibility::Hidden { return ((), element_state); } let mut tab_group = None; if self.tab_group { tab_group = self.tab_index; } if let Some(focus_handle) = &self.tracked_focus_handle { window.next_frame.tab_stops.insert(focus_handle); } window.with_element_opacity(style.opacity, |window| { style.paint(bounds, window, cx, |window: &mut Window, cx: &mut App| { window.with_text_style(style.text_style().cloned(), |window| { window.with_content_mask( style.overflow_mask(bounds, window.rem_size()), |window| { window.with_tab_group(tab_group, |window| { if let Some(hitbox) = hitbox { #[cfg(debug_assertions)] self.paint_debug_info( global_id, hitbox, &style, window, cx, ); if let Some(drag) = cx.active_drag.as_ref() { if let Some(mouse_cursor) = drag.cursor_style { window.set_window_cursor_style(mouse_cursor); } } else { if let Some(mouse_cursor) = style.mouse_cursor { window.set_cursor_style(mouse_cursor, hitbox); } } if let Some(group) = self.group.clone() { GroupHitboxes::push(group, hitbox.id, cx); } if let Some(area) = self.window_control { window.insert_window_control_hitbox( area, hitbox.clone(), ); } self.paint_mouse_listeners( hitbox, element_state.as_mut(), window, cx, ); self.paint_scroll_listener(hitbox, &style, window, cx); } self.paint_keyboard_listeners(window, cx); f(&style, window, cx); if let Some(_hitbox) = hitbox { #[cfg(any(feature = "inspector", debug_assertions))] window.insert_inspector_hitbox( _hitbox.id, _inspector_id, cx, ); if let Some(group) = self.group.as_ref() { GroupHitboxes::pop(group, cx); } } }) }, ); }); }); }); ((), element_state) }, ); } #[cfg(debug_assertions)] fn paint_debug_info( &self, global_id: Option<&GlobalElementId>, hitbox: &Hitbox, style: &Style, window: &mut Window, cx: &mut App, ) { use crate::{BorderStyle, TextAlign}; if global_id.is_some() && (style.debug || style.debug_below || cx.has_global::()) && hitbox.is_hovered(window) { const FONT_SIZE: crate::Pixels = crate::Pixels(10.); let element_id = format!("{:?}", global_id.unwrap()); let str_len = element_id.len(); let render_debug_text = |window: &mut Window| { if let Some(text) = window .text_system() .shape_text( element_id.into(), FONT_SIZE, &[window.text_style().to_run(str_len)], None, None, ) .ok() .and_then(|mut text| text.pop()) { text.paint(hitbox.origin, FONT_SIZE, TextAlign::Left, None, window, cx) .ok(); let text_bounds = crate::Bounds { origin: hitbox.origin, size: text.size(FONT_SIZE), }; if self.source_location.is_some() && text_bounds.contains(&window.mouse_position()) && window.modifiers().secondary() { let secondary_held = window.modifiers().secondary(); window.on_key_event({ move |e: &crate::ModifiersChangedEvent, _phase, window, _cx| { if e.modifiers.secondary() != secondary_held && text_bounds.contains(&window.mouse_position()) { window.refresh(); } } }); let was_hovered = hitbox.is_hovered(window); let current_view = window.current_view(); window.on_mouse_event({ let hitbox = hitbox.clone(); move |_: &MouseMoveEvent, phase, window, cx| { if phase == DispatchPhase::Capture { let hovered = hitbox.is_hovered(window); if hovered != was_hovered { cx.notify(current_view) } } } }); window.on_mouse_event({ let hitbox = hitbox.clone(); let location = self.source_location.unwrap(); move |e: &crate::MouseDownEvent, phase, window, cx| { if text_bounds.contains(&e.position) && phase.capture() && hitbox.is_hovered(window) { cx.stop_propagation(); let Ok(dir) = std::env::current_dir() else { return; }; eprintln!( "This element was created at:\n{}:{}:{}", dir.join(location.file()).to_string_lossy(), location.line(), location.column() ); } } }); window.paint_quad(crate::outline( crate::Bounds { origin: hitbox.origin + crate::point(crate::px(0.), FONT_SIZE - px(2.)), size: crate::Size { width: text_bounds.size.width, height: crate::px(1.), }, }, crate::red(), BorderStyle::default(), )) } } }; window.with_text_style( Some(crate::TextStyleRefinement { color: Some(crate::red()), line_height: Some(FONT_SIZE.into()), background_color: Some(crate::white()), ..Default::default() }), render_debug_text, ) } } fn paint_mouse_listeners( &mut self, hitbox: &Hitbox, element_state: Option<&mut InteractiveElementState>, window: &mut Window, cx: &mut App, ) { let is_focused = self .tracked_focus_handle .as_ref() .map(|handle| handle.is_focused(window)) .unwrap_or(false); // If this element can be focused, register a mouse down listener // that will automatically transfer focus when hitting the element. // This behavior can be suppressed by using `cx.prevent_default()`. if let Some(focus_handle) = self.tracked_focus_handle.clone() { let hitbox = hitbox.clone(); window.on_mouse_event(move |_: &MouseDownEvent, phase, window, _| { if phase == DispatchPhase::Bubble && hitbox.is_hovered(window) && !window.default_prevented() { window.focus(&focus_handle); // If there is a parent that is also focusable, prevent it // from transferring focus because we already did so. window.prevent_default(); } }); } for listener in self.mouse_down_listeners.drain(..) { let hitbox = hitbox.clone(); window.on_mouse_event(move |event: &MouseDownEvent, phase, window, cx| { listener(event, phase, &hitbox, window, cx); }) } for listener in self.mouse_up_listeners.drain(..) { let hitbox = hitbox.clone(); window.on_mouse_event(move |event: &MouseUpEvent, phase, window, cx| { listener(event, phase, &hitbox, window, cx); }) } for listener in self.mouse_move_listeners.drain(..) { let hitbox = hitbox.clone(); window.on_mouse_event(move |event: &MouseMoveEvent, phase, window, cx| { listener(event, phase, &hitbox, window, cx); }) } for listener in self.scroll_wheel_listeners.drain(..) { let hitbox = hitbox.clone(); window.on_mouse_event(move |event: &ScrollWheelEvent, phase, window, cx| { listener(event, phase, &hitbox, window, cx); }) } if self.hover_style.is_some() || self.base_style.mouse_cursor.is_some() || cx.active_drag.is_some() && !self.drag_over_styles.is_empty() { let hitbox = hitbox.clone(); let was_hovered = hitbox.is_hovered(window); let current_view = window.current_view(); window.on_mouse_event(move |_: &MouseMoveEvent, phase, window, cx| { let hovered = hitbox.is_hovered(window); if phase == DispatchPhase::Capture && hovered != was_hovered { cx.notify(current_view); } }); } let drag_cursor_style = self.base_style.as_ref().mouse_cursor; let mut drag_listener = mem::take(&mut self.drag_listener); let drop_listeners = mem::take(&mut self.drop_listeners); let click_listeners = mem::take(&mut self.click_listeners); let can_drop_predicate = mem::take(&mut self.can_drop_predicate); if !drop_listeners.is_empty() { let hitbox = hitbox.clone(); window.on_mouse_event({ move |_: &MouseUpEvent, phase, window, cx| { if let Some(drag) = &cx.active_drag && phase == DispatchPhase::Bubble && hitbox.is_hovered(window) { let drag_state_type = drag.value.as_ref().type_id(); for (drop_state_type, listener) in &drop_listeners { if *drop_state_type == drag_state_type { let drag = cx .active_drag .take() .expect("checked for type drag state type above"); let mut can_drop = true; if let Some(predicate) = &can_drop_predicate { can_drop = predicate(drag.value.as_ref(), window, cx); } if can_drop { listener(drag.value.as_ref(), window, cx); window.refresh(); cx.stop_propagation(); } } } } } }); } if let Some(element_state) = element_state { if !click_listeners.is_empty() || drag_listener.is_some() { let pending_mouse_down = element_state .pending_mouse_down .get_or_insert_with(Default::default) .clone(); let clicked_state = element_state .clicked_state .get_or_insert_with(Default::default) .clone(); window.on_mouse_event({ let pending_mouse_down = pending_mouse_down.clone(); let hitbox = hitbox.clone(); move |event: &MouseDownEvent, phase, window, _cx| { if phase == DispatchPhase::Bubble && event.button == MouseButton::Left && hitbox.is_hovered(window) { *pending_mouse_down.borrow_mut() = Some(event.clone()); window.refresh(); } } }); window.on_mouse_event({ let pending_mouse_down = pending_mouse_down.clone(); let hitbox = hitbox.clone(); move |event: &MouseMoveEvent, phase, window, cx| { if phase == DispatchPhase::Capture { return; } let mut pending_mouse_down = pending_mouse_down.borrow_mut(); if let Some(mouse_down) = pending_mouse_down.clone() && !cx.has_active_drag() && (event.position - mouse_down.position).magnitude() > DRAG_THRESHOLD && let Some((drag_value, drag_listener)) = drag_listener.take() { *clicked_state.borrow_mut() = ElementClickedState::default(); let cursor_offset = event.position - hitbox.origin; let drag = (drag_listener)(drag_value.as_ref(), cursor_offset, window, cx); cx.active_drag = Some(AnyDrag { view: drag, value: drag_value, cursor_offset, cursor_style: drag_cursor_style, }); pending_mouse_down.take(); window.refresh(); cx.stop_propagation(); } } }); if is_focused { // Press enter, space to trigger click, when the element is focused. window.on_key_event({ let click_listeners = click_listeners.clone(); let hitbox = hitbox.clone(); move |event: &KeyUpEvent, phase, window, cx| { if phase.bubble() && !window.default_prevented() { let stroke = &event.keystroke; let keyboard_button = if stroke.key.eq("enter") { Some(KeyboardButton::Enter) } else if stroke.key.eq("space") { Some(KeyboardButton::Space) } else { None }; if let Some(button) = keyboard_button && !stroke.modifiers.modified() { let click_event = ClickEvent::Keyboard(KeyboardClickEvent { button, bounds: hitbox.bounds, }); for listener in &click_listeners { listener(&click_event, window, cx); } } } } }); } window.on_mouse_event({ let mut captured_mouse_down = None; let hitbox = hitbox.clone(); move |event: &MouseUpEvent, phase, window, cx| match phase { // Clear the pending mouse down during the capture phase, // so that it happens even if another event handler stops // propagation. DispatchPhase::Capture => { let mut pending_mouse_down = pending_mouse_down.borrow_mut(); if pending_mouse_down.is_some() && hitbox.is_hovered(window) { captured_mouse_down = pending_mouse_down.take(); window.refresh(); } else if pending_mouse_down.is_some() { // Clear the pending mouse down event (without firing click handlers) // if the hitbox is not being hovered. // This avoids dragging elements that changed their position // immediately after being clicked. // See https://github.com/zed-industries/zed/issues/24600 for more details pending_mouse_down.take(); window.refresh(); } } // Fire click handlers during the bubble phase. DispatchPhase::Bubble => { if let Some(mouse_down) = captured_mouse_down.take() { let mouse_click = ClickEvent::Mouse(MouseClickEvent { down: mouse_down, up: event.clone(), }); for listener in &click_listeners { listener(&mouse_click, window, cx); } } } } }); } if let Some(hover_listener) = self.hover_listener.take() { let hitbox = hitbox.clone(); let was_hovered = element_state .hover_state .get_or_insert_with(Default::default) .clone(); let has_mouse_down = element_state .pending_mouse_down .get_or_insert_with(Default::default) .clone(); window.on_mouse_event(move |_: &MouseMoveEvent, phase, window, cx| { if phase != DispatchPhase::Bubble { return; } let is_hovered = has_mouse_down.borrow().is_none() && !cx.has_active_drag() && hitbox.is_hovered(window); let mut was_hovered = was_hovered.borrow_mut(); if is_hovered != *was_hovered { *was_hovered = is_hovered; drop(was_hovered); hover_listener(&is_hovered, window, cx); } }); } if let Some(tooltip_builder) = self.tooltip_builder.take() { let active_tooltip = element_state .active_tooltip .get_or_insert_with(Default::default) .clone(); let pending_mouse_down = element_state .pending_mouse_down .get_or_insert_with(Default::default) .clone(); let tooltip_is_hoverable = tooltip_builder.hoverable; let build_tooltip = Rc::new(move |window: &mut Window, cx: &mut App| { Some(((tooltip_builder.build)(window, cx), tooltip_is_hoverable)) }); // Use bounds instead of testing hitbox since this is called during prepaint. let check_is_hovered_during_prepaint = Rc::new({ let pending_mouse_down = pending_mouse_down.clone(); let source_bounds = hitbox.bounds; move |window: &Window| { pending_mouse_down.borrow().is_none() && source_bounds.contains(&window.mouse_position()) } }); let check_is_hovered = Rc::new({ let hitbox = hitbox.clone(); move |window: &Window| { pending_mouse_down.borrow().is_none() && hitbox.is_hovered(window) } }); register_tooltip_mouse_handlers( &active_tooltip, self.tooltip_id, build_tooltip, check_is_hovered, check_is_hovered_during_prepaint, window, ); } let active_state = element_state .clicked_state .get_or_insert_with(Default::default) .clone(); if active_state.borrow().is_clicked() { window.on_mouse_event(move |_: &MouseUpEvent, phase, window, _cx| { if phase == DispatchPhase::Capture { *active_state.borrow_mut() = ElementClickedState::default(); window.refresh(); } }); } else { let active_group_hitbox = self .group_active_style .as_ref() .and_then(|group_active| GroupHitboxes::get(&group_active.group, cx)); let hitbox = hitbox.clone(); window.on_mouse_event(move |_: &MouseDownEvent, phase, window, _cx| { if phase == DispatchPhase::Bubble && !window.default_prevented() { let group_hovered = active_group_hitbox .is_some_and(|group_hitbox_id| group_hitbox_id.is_hovered(window)); let element_hovered = hitbox.is_hovered(window); if group_hovered || element_hovered { *active_state.borrow_mut() = ElementClickedState { group: group_hovered, element: element_hovered, }; window.refresh(); } } }); } } } fn paint_keyboard_listeners(&mut self, window: &mut Window, _cx: &mut App) { let key_down_listeners = mem::take(&mut self.key_down_listeners); let key_up_listeners = mem::take(&mut self.key_up_listeners); let modifiers_changed_listeners = mem::take(&mut self.modifiers_changed_listeners); let action_listeners = mem::take(&mut self.action_listeners); if let Some(context) = self.key_context.clone() { window.set_key_context(context); } for listener in key_down_listeners { window.on_key_event(move |event: &KeyDownEvent, phase, window, cx| { listener(event, phase, window, cx); }) } for listener in key_up_listeners { window.on_key_event(move |event: &KeyUpEvent, phase, window, cx| { listener(event, phase, window, cx); }) } for listener in modifiers_changed_listeners { window.on_modifiers_changed(move |event: &ModifiersChangedEvent, window, cx| { listener(event, window, cx); }) } for (action_type, listener) in action_listeners { window.on_action(action_type, listener) } } fn paint_hover_group_handler(&self, window: &mut Window, cx: &mut App) { let group_hitbox = self .group_hover_style .as_ref() .and_then(|group_hover| GroupHitboxes::get(&group_hover.group, cx)); if let Some(group_hitbox) = group_hitbox { let was_hovered = group_hitbox.is_hovered(window); let current_view = window.current_view(); window.on_mouse_event(move |_: &MouseMoveEvent, phase, window, cx| { let hovered = group_hitbox.is_hovered(window); if phase == DispatchPhase::Capture && hovered != was_hovered { cx.notify(current_view); } }); } } fn paint_scroll_listener( &self, hitbox: &Hitbox, style: &Style, window: &mut Window, _cx: &mut App, ) { if let Some(scroll_offset) = self.scroll_offset.clone() { let overflow = style.overflow; let allow_concurrent_scroll = style.allow_concurrent_scroll; let restrict_scroll_to_axis = style.restrict_scroll_to_axis; let line_height = window.line_height(); let hitbox = hitbox.clone(); let current_view = window.current_view(); window.on_mouse_event(move |event: &ScrollWheelEvent, phase, window, cx| { if phase == DispatchPhase::Bubble && hitbox.should_handle_scroll(window) { let mut scroll_offset = scroll_offset.borrow_mut(); let old_scroll_offset = *scroll_offset; let delta = event.delta.pixel_delta(line_height); let mut delta_x = Pixels::ZERO; if overflow.x == Overflow::Scroll { if !delta.x.is_zero() { delta_x = delta.x; } else if !restrict_scroll_to_axis && overflow.y != Overflow::Scroll { delta_x = delta.y; } } let mut delta_y = Pixels::ZERO; if overflow.y == Overflow::Scroll { if !delta.y.is_zero() { delta_y = delta.y; } else if !restrict_scroll_to_axis && overflow.x != Overflow::Scroll { delta_y = delta.x; } } if !allow_concurrent_scroll && !delta_x.is_zero() && !delta_y.is_zero() { if delta_x.abs() > delta_y.abs() { delta_y = Pixels::ZERO; } else { delta_x = Pixels::ZERO; } } scroll_offset.y += delta_y; scroll_offset.x += delta_x; if *scroll_offset != old_scroll_offset { cx.notify(current_view); } } }); } } /// Compute the visual style for this element, based on the current bounds and the element's state. pub fn compute_style( &self, global_id: Option<&GlobalElementId>, hitbox: Option<&Hitbox>, window: &mut Window, cx: &mut App, ) -> Style { window.with_optional_element_state(global_id, |element_state, window| { let mut element_state = element_state.map(|element_state| element_state.unwrap_or_default()); let style = self.compute_style_internal(hitbox, element_state.as_mut(), window, cx); (style, element_state) }) } /// Called from internal methods that have already called with_element_state. fn compute_style_internal( &self, hitbox: Option<&Hitbox>, element_state: Option<&mut InteractiveElementState>, window: &mut Window, cx: &mut App, ) -> Style { let mut style = Style::default(); style.refine(&self.base_style); if let Some(focus_handle) = self.tracked_focus_handle.as_ref() { if let Some(in_focus_style) = self.in_focus_style.as_ref() && focus_handle.within_focused(window, cx) { style.refine(in_focus_style); } if let Some(focus_style) = self.focus_style.as_ref() && focus_handle.is_focused(window) { style.refine(focus_style); } if let Some(focus_visible_style) = self.focus_visible_style.as_ref() && focus_handle.is_focused(window) && window.last_input_was_keyboard() { style.refine(focus_visible_style); } } if let Some(hitbox) = hitbox { if !cx.has_active_drag() { if let Some(group_hover) = self.group_hover_style.as_ref() && let Some(group_hitbox_id) = GroupHitboxes::get(&group_hover.group, cx) && group_hitbox_id.is_hovered(window) { style.refine(&group_hover.style); } if let Some(hover_style) = self.hover_style.as_ref() && hitbox.is_hovered(window) { style.refine(hover_style); } } if let Some(drag) = cx.active_drag.take() { let mut can_drop = true; if let Some(can_drop_predicate) = &self.can_drop_predicate { can_drop = can_drop_predicate(drag.value.as_ref(), window, cx); } if can_drop { for (state_type, group_drag_style) in &self.group_drag_over_styles { if let Some(group_hitbox_id) = GroupHitboxes::get(&group_drag_style.group, cx) && *state_type == drag.value.as_ref().type_id() && group_hitbox_id.is_hovered(window) { style.refine(&group_drag_style.style); } } for (state_type, build_drag_over_style) in &self.drag_over_styles { if *state_type == drag.value.as_ref().type_id() && hitbox.is_hovered(window) { style.refine(&build_drag_over_style(drag.value.as_ref(), window, cx)); } } } style.mouse_cursor = drag.cursor_style; cx.active_drag = Some(drag); } } if let Some(element_state) = element_state { let clicked_state = element_state .clicked_state .get_or_insert_with(Default::default) .borrow(); if clicked_state.group && let Some(group) = self.group_active_style.as_ref() { style.refine(&group.style) } if let Some(active_style) = self.active_style.as_ref() && clicked_state.element { style.refine(active_style) } } style } } /// The per-frame state of an interactive element. Used for tracking stateful interactions like clicks /// and scroll offsets. #[derive(Default)] pub struct InteractiveElementState { pub(crate) focus_handle: Option, pub(crate) clicked_state: Option>>, pub(crate) hover_state: Option>>, pub(crate) pending_mouse_down: Option>>>, pub(crate) scroll_offset: Option>>>, pub(crate) active_tooltip: Option>>>, } /// Whether or not the element or a group that contains it is clicked by the mouse. #[derive(Copy, Clone, Default, Eq, PartialEq)] pub struct ElementClickedState { /// True if this element's group has been clicked, false otherwise pub group: bool, /// True if this element has been clicked, false otherwise pub element: bool, } impl ElementClickedState { fn is_clicked(&self) -> bool { self.group || self.element } } pub(crate) enum ActiveTooltip { /// Currently delaying before showing the tooltip. WaitingForShow { _task: Task<()> }, /// Tooltip is visible, element was hovered or for hoverable tooltips, the tooltip was hovered. Visible { tooltip: AnyTooltip, is_hoverable: bool, }, /// Tooltip is visible and hoverable, but the mouse is no longer hovering. Currently delaying /// before hiding it. WaitingForHide { tooltip: AnyTooltip, _task: Task<()>, }, } pub(crate) fn clear_active_tooltip( active_tooltip: &Rc>>, window: &mut Window, ) { match active_tooltip.borrow_mut().take() { None => {} Some(ActiveTooltip::WaitingForShow { .. }) => {} Some(ActiveTooltip::Visible { .. }) => window.refresh(), Some(ActiveTooltip::WaitingForHide { .. }) => window.refresh(), } } pub(crate) fn clear_active_tooltip_if_not_hoverable( active_tooltip: &Rc>>, window: &mut Window, ) { let should_clear = match active_tooltip.borrow().as_ref() { None => false, Some(ActiveTooltip::WaitingForShow { .. }) => false, Some(ActiveTooltip::Visible { is_hoverable, .. }) => !is_hoverable, Some(ActiveTooltip::WaitingForHide { .. }) => false, }; if should_clear { active_tooltip.borrow_mut().take(); window.refresh(); } } pub(crate) fn set_tooltip_on_window( active_tooltip: &Rc>>, window: &mut Window, ) -> Option { let tooltip = match active_tooltip.borrow().as_ref() { None => return None, Some(ActiveTooltip::WaitingForShow { .. }) => return None, Some(ActiveTooltip::Visible { tooltip, .. }) => tooltip.clone(), Some(ActiveTooltip::WaitingForHide { tooltip, .. }) => tooltip.clone(), }; Some(window.set_tooltip(tooltip)) } pub(crate) fn register_tooltip_mouse_handlers( active_tooltip: &Rc>>, tooltip_id: Option, build_tooltip: Rc Option<(AnyView, bool)>>, check_is_hovered: Rc bool>, check_is_hovered_during_prepaint: Rc bool>, window: &mut Window, ) { window.on_mouse_event({ let active_tooltip = active_tooltip.clone(); let build_tooltip = build_tooltip.clone(); let check_is_hovered = check_is_hovered.clone(); move |_: &MouseMoveEvent, phase, window, cx| { handle_tooltip_mouse_move( &active_tooltip, &build_tooltip, &check_is_hovered, &check_is_hovered_during_prepaint, phase, window, cx, ) } }); window.on_mouse_event({ let active_tooltip = active_tooltip.clone(); move |_: &MouseDownEvent, _phase, window: &mut Window, _cx| { if !tooltip_id.is_some_and(|tooltip_id| tooltip_id.is_hovered(window)) { clear_active_tooltip_if_not_hoverable(&active_tooltip, window); } } }); window.on_mouse_event({ let active_tooltip = active_tooltip.clone(); move |_: &ScrollWheelEvent, _phase, window: &mut Window, _cx| { if !tooltip_id.is_some_and(|tooltip_id| tooltip_id.is_hovered(window)) { clear_active_tooltip_if_not_hoverable(&active_tooltip, window); } } }); } /// Handles displaying tooltips when an element is hovered. /// /// The mouse hovering logic also relies on being called from window prepaint in order to handle the /// case where the element the tooltip is on is not rendered - in that case its mouse listeners are /// also not registered. During window prepaint, the hitbox information is not available, so /// `check_is_hovered_during_prepaint` is used which bases the check off of the absolute bounds of /// the element. /// /// TODO: There's a minor bug due to the use of absolute bounds while checking during prepaint - it /// does not know if the hitbox is occluded. In the case where a tooltip gets displayed and then /// gets occluded after display, it will stick around until the mouse exits the hover bounds. fn handle_tooltip_mouse_move( active_tooltip: &Rc>>, build_tooltip: &Rc Option<(AnyView, bool)>>, check_is_hovered: &Rc bool>, check_is_hovered_during_prepaint: &Rc bool>, phase: DispatchPhase, window: &mut Window, cx: &mut App, ) { // Separates logic for what mutation should occur from applying it, to avoid overlapping // RefCell borrows. enum Action { None, CancelShow, ScheduleShow, } let action = match active_tooltip.borrow().as_ref() { None => { let is_hovered = check_is_hovered(window); if is_hovered && phase.bubble() { Action::ScheduleShow } else { Action::None } } Some(ActiveTooltip::WaitingForShow { .. }) => { let is_hovered = check_is_hovered(window); if is_hovered { Action::None } else { Action::CancelShow } } // These are handled in check_visible_and_update. Some(ActiveTooltip::Visible { .. }) | Some(ActiveTooltip::WaitingForHide { .. }) => { Action::None } }; match action { Action::None => {} Action::CancelShow => { // Cancel waiting to show tooltip when it is no longer hovered. active_tooltip.borrow_mut().take(); } Action::ScheduleShow => { let delayed_show_task = window.spawn(cx, { let active_tooltip = active_tooltip.clone(); let build_tooltip = build_tooltip.clone(); let check_is_hovered_during_prepaint = check_is_hovered_during_prepaint.clone(); async move |cx| { cx.background_executor().timer(TOOLTIP_SHOW_DELAY).await; cx.update(|window, cx| { let new_tooltip = build_tooltip(window, cx).map(|(view, tooltip_is_hoverable)| { let active_tooltip = active_tooltip.clone(); ActiveTooltip::Visible { tooltip: AnyTooltip { view, mouse_position: window.mouse_position(), check_visible_and_update: Rc::new( move |tooltip_bounds, window, cx| { handle_tooltip_check_visible_and_update( &active_tooltip, tooltip_is_hoverable, &check_is_hovered_during_prepaint, tooltip_bounds, window, cx, ) }, ), }, is_hoverable: tooltip_is_hoverable, } }); *active_tooltip.borrow_mut() = new_tooltip; window.refresh(); }) .ok(); } }); active_tooltip .borrow_mut() .replace(ActiveTooltip::WaitingForShow { _task: delayed_show_task, }); } } } /// Returns a callback which will be called by window prepaint to update tooltip visibility. The /// purpose of doing this logic here instead of the mouse move handler is that the mouse move /// handler won't get called when the element is not painted (e.g. via use of `visible_on_hover`). fn handle_tooltip_check_visible_and_update( active_tooltip: &Rc>>, tooltip_is_hoverable: bool, check_is_hovered: &Rc bool>, tooltip_bounds: Bounds, window: &mut Window, cx: &mut App, ) -> bool { // Separates logic for what mutation should occur from applying it, to avoid overlapping RefCell // borrows. enum Action { None, Hide, ScheduleHide(AnyTooltip), CancelHide(AnyTooltip), } let is_hovered = check_is_hovered(window) || (tooltip_is_hoverable && tooltip_bounds.contains(&window.mouse_position())); let action = match active_tooltip.borrow().as_ref() { Some(ActiveTooltip::Visible { tooltip, .. }) => { if is_hovered { Action::None } else { if tooltip_is_hoverable { Action::ScheduleHide(tooltip.clone()) } else { Action::Hide } } } Some(ActiveTooltip::WaitingForHide { tooltip, .. }) => { if is_hovered { Action::CancelHide(tooltip.clone()) } else { Action::None } } None | Some(ActiveTooltip::WaitingForShow { .. }) => Action::None, }; match action { Action::None => {} Action::Hide => clear_active_tooltip(active_tooltip, window), Action::ScheduleHide(tooltip) => { let delayed_hide_task = window.spawn(cx, { let active_tooltip = active_tooltip.clone(); async move |cx| { cx.background_executor() .timer(HOVERABLE_TOOLTIP_HIDE_DELAY) .await; if active_tooltip.borrow_mut().take().is_some() { cx.update(|window, _cx| window.refresh()).ok(); } } }); active_tooltip .borrow_mut() .replace(ActiveTooltip::WaitingForHide { tooltip, _task: delayed_hide_task, }); } Action::CancelHide(tooltip) => { // Cancel waiting to hide tooltip when it becomes hovered. active_tooltip.borrow_mut().replace(ActiveTooltip::Visible { tooltip, is_hoverable: true, }); } } active_tooltip.borrow().is_some() } #[derive(Default)] pub(crate) struct GroupHitboxes(HashMap>); impl Global for GroupHitboxes {} impl GroupHitboxes { pub fn get(name: &SharedString, cx: &mut App) -> Option { cx.default_global::() .0 .get(name) .and_then(|bounds_stack| bounds_stack.last()) .cloned() } pub fn push(name: SharedString, hitbox_id: HitboxId, cx: &mut App) { cx.default_global::() .0 .entry(name) .or_default() .push(hitbox_id); } pub fn pop(name: &SharedString, cx: &mut App) { cx.default_global::().0.get_mut(name).unwrap().pop(); } } /// A wrapper around an element that can store state, produced after assigning an ElementId. pub struct Stateful { pub(crate) element: E, } impl Styled for Stateful where E: Styled, { fn style(&mut self) -> &mut StyleRefinement { self.element.style() } } impl StatefulInteractiveElement for Stateful where E: Element, Self: InteractiveElement, { } impl InteractiveElement for Stateful where E: InteractiveElement, { fn interactivity(&mut self) -> &mut Interactivity { self.element.interactivity() } } impl Element for Stateful where E: Element, { type RequestLayoutState = E::RequestLayoutState; type PrepaintState = E::PrepaintState; fn id(&self) -> Option { self.element.id() } fn source_location(&self) -> Option<&'static core::panic::Location<'static>> { self.element.source_location() } fn request_layout( &mut self, id: Option<&GlobalElementId>, inspector_id: Option<&InspectorElementId>, window: &mut Window, cx: &mut App, ) -> (LayoutId, Self::RequestLayoutState) { self.element.request_layout(id, inspector_id, window, cx) } fn prepaint( &mut self, id: Option<&GlobalElementId>, inspector_id: Option<&InspectorElementId>, bounds: Bounds, state: &mut Self::RequestLayoutState, window: &mut Window, cx: &mut App, ) -> E::PrepaintState { self.element .prepaint(id, inspector_id, bounds, state, window, cx) } fn paint( &mut self, id: Option<&GlobalElementId>, inspector_id: Option<&InspectorElementId>, bounds: Bounds, request_layout: &mut Self::RequestLayoutState, prepaint: &mut Self::PrepaintState, window: &mut Window, cx: &mut App, ) { self.element.paint( id, inspector_id, bounds, request_layout, prepaint, window, cx, ); } } impl IntoElement for Stateful where E: Element, { type Element = Self; fn into_element(self) -> Self::Element { self } } impl ParentElement for Stateful where E: ParentElement, { fn extend(&mut self, elements: impl IntoIterator) { self.element.extend(elements) } } /// Represents an element that can be scrolled *to* in its parent element. /// Contrary to [ScrollHandle::scroll_to_active_item], an anchored element does not have to be an immediate child of the parent. #[derive(Clone)] pub struct ScrollAnchor { handle: ScrollHandle, last_origin: Rc>>, } impl ScrollAnchor { /// Creates a [ScrollAnchor] associated with a given [ScrollHandle]. pub fn for_handle(handle: ScrollHandle) -> Self { Self { handle, last_origin: Default::default(), } } /// Request scroll to this item on the next frame. pub fn scroll_to(&self, window: &mut Window, _cx: &mut App) { let this = self.clone(); window.on_next_frame(move |_, _| { let viewport_bounds = this.handle.bounds(); let self_bounds = *this.last_origin.borrow(); this.handle.set_offset(viewport_bounds.origin - self_bounds); }); } } #[derive(Default, Debug)] struct ScrollHandleState { offset: Rc>>, bounds: Bounds, max_offset: Size, child_bounds: Vec>, scroll_to_bottom: bool, overflow: Point, active_item: Option, } #[derive(Default, Debug, Clone, Copy)] struct ScrollActiveItem { index: usize, strategy: ScrollStrategy, } #[derive(Default, Debug, Clone, Copy)] enum ScrollStrategy { #[default] FirstVisible, Top, } /// A handle to the scrollable aspects of an element. /// Used for accessing scroll state, like the current scroll offset, /// and for mutating the scroll state, like scrolling to a specific child. #[derive(Clone, Debug)] pub struct ScrollHandle(Rc>); impl Default for ScrollHandle { fn default() -> Self { Self::new() } } impl ScrollHandle { /// Construct a new scroll handle. pub fn new() -> Self { Self(Rc::default()) } /// Get the current scroll offset. pub fn offset(&self) -> Point { *self.0.borrow().offset.borrow() } /// Get the maximum scroll offset. pub fn max_offset(&self) -> Size { self.0.borrow().max_offset } /// Get the top child that's scrolled into view. pub fn top_item(&self) -> usize { let state = self.0.borrow(); let top = state.bounds.top() - state.offset.borrow().y; match state.child_bounds.binary_search_by(|bounds| { if top < bounds.top() { Ordering::Greater } else if top > bounds.bottom() { Ordering::Less } else { Ordering::Equal } }) { Ok(ix) => ix, Err(ix) => ix.min(state.child_bounds.len().saturating_sub(1)), } } /// Get the bottom child that's scrolled into view. pub fn bottom_item(&self) -> usize { let state = self.0.borrow(); let bottom = state.bounds.bottom() - state.offset.borrow().y; match state.child_bounds.binary_search_by(|bounds| { if bottom < bounds.top() { Ordering::Greater } else if bottom > bounds.bottom() { Ordering::Less } else { Ordering::Equal } }) { Ok(ix) => ix, Err(ix) => ix.min(state.child_bounds.len().saturating_sub(1)), } } /// Return the bounds into which this child is painted pub fn bounds(&self) -> Bounds { self.0.borrow().bounds } /// Get the bounds for a specific child. pub fn bounds_for_item(&self, ix: usize) -> Option> { self.0.borrow().child_bounds.get(ix).cloned() } /// Update [ScrollHandleState]'s active item for scrolling to in prepaint pub fn scroll_to_item(&self, ix: usize) { let mut state = self.0.borrow_mut(); state.active_item = Some(ScrollActiveItem { index: ix, strategy: ScrollStrategy::default(), }); } /// Update [ScrollHandleState]'s active item for scrolling to in prepaint /// This scrolls the minimal amount to ensure that the child is the first visible element pub fn scroll_to_top_of_item(&self, ix: usize) { let mut state = self.0.borrow_mut(); state.active_item = Some(ScrollActiveItem { index: ix, strategy: ScrollStrategy::Top, }); } /// Scrolls the minimal amount to either ensure that the child is /// fully visible or the top element of the view depends on the /// scroll strategy fn scroll_to_active_item(&self) { let mut state = self.0.borrow_mut(); let Some(active_item) = state.active_item else { return; }; let active_item = match state.child_bounds.get(active_item.index) { Some(bounds) => { let mut scroll_offset = state.offset.borrow_mut(); match active_item.strategy { ScrollStrategy::FirstVisible => { if state.overflow.y == Overflow::Scroll { if bounds.top() + scroll_offset.y < state.bounds.top() { scroll_offset.y = state.bounds.top() - bounds.top(); } else if bounds.bottom() + scroll_offset.y > state.bounds.bottom() { scroll_offset.y = state.bounds.bottom() - bounds.bottom(); } } } ScrollStrategy::Top => { scroll_offset.y = state.bounds.top() - bounds.top(); } } if state.overflow.x == Overflow::Scroll { if bounds.left() + scroll_offset.x < state.bounds.left() { scroll_offset.x = state.bounds.left() - bounds.left(); } else if bounds.right() + scroll_offset.x > state.bounds.right() { scroll_offset.x = state.bounds.right() - bounds.right(); } } None } None => Some(active_item), }; state.active_item = active_item; } /// Scrolls to the bottom. pub fn scroll_to_bottom(&self) { let mut state = self.0.borrow_mut(); state.scroll_to_bottom = true; } /// Set the offset explicitly. The offset is the distance from the top left of the /// parent container to the top left of the first child. /// As you scroll further down the offset becomes more negative. pub fn set_offset(&self, mut position: Point) { let state = self.0.borrow(); *state.offset.borrow_mut() = position; } /// Get the logical scroll top, based on a child index and a pixel offset. pub fn logical_scroll_top(&self) -> (usize, Pixels) { let ix = self.top_item(); let state = self.0.borrow(); if let Some(child_bounds) = state.child_bounds.get(ix) { ( ix, child_bounds.top() + state.offset.borrow().y - state.bounds.top(), ) } else { (ix, px(0.)) } } /// Get the logical scroll bottom, based on a child index and a pixel offset. pub fn logical_scroll_bottom(&self) -> (usize, Pixels) { let ix = self.bottom_item(); let state = self.0.borrow(); if let Some(child_bounds) = state.child_bounds.get(ix) { ( ix, child_bounds.bottom() + state.offset.borrow().y - state.bounds.bottom(), ) } else { (ix, px(0.)) } } /// Get the count of children for scrollable item. pub fn children_count(&self) -> usize { self.0.borrow().child_bounds.len() } }