crates/gpui2/docs/contexts.md 🔗
Nathan Sobo created
crates/gpui2/docs/contexts.md | 0
crates/gpui2/docs/key_dispatch.md | 101 ++++
crates/gpui2/src/app.rs | 2
crates/gpui2/src/elements.rs | 1
crates/gpui2/src/elements/div.rs | 4
crates/gpui2/src/elements/node.rs | 767 +++++++++++++++++++++++++++++++++
crates/gpui2/src/style.rs | 2
crates/gpui2/src/window.rs | 21
8 files changed, 894 insertions(+), 4 deletions(-)
@@ -0,0 +1,101 @@
+# Key Dispatch
+
+GPUI is designed for keyboard-first interactivity.
+
+To expose functionality to the mouse, you render a button with a click handler.
+
+To expose functionality to the keyboard, you bind an *action* in a *key context*.
+
+Actions are similar to framework-level events like `MouseDown`, `KeyDown`, etc, but you can define them yourself:
+
+```rust
+mod menu {
+ #[gpui::action]
+ struct MoveUp;
+
+ #[gpui::action]
+ struct MoveDown;
+}
+```
+
+Actions are frequently unit structs, for which we have a macro. The above could also be written:
+
+```rust
+mod menu {
+ actions!(MoveUp, MoveDown);
+}
+```
+
+Actions can also be more complex types:
+
+```rust
+mod menu {
+ #[gpui::action]
+ struct Move {
+ direction: Direction,
+ select: bool,
+ }
+}
+```
+
+To bind actions, chain `on_action` on to your element:
+
+```rust
+impl Render for Menu {
+ fn render(&mut self, cx: &mut ViewContext<Self>) -> impl Component {
+ div()
+ .on_action(|this: &mut Menu, move: &MoveUp, cx: &mut ViewContext<Menu>| {
+ // ...
+ })
+ .on_action(|this, move: &MoveDown, cx| {
+ // ...
+ })
+ .children(todo!())
+ }
+}
+```
+
+In order to bind keys to actions, you need to declare a *key context* for part of the element tree by calling `key_context`.
+
+```rust
+impl Render for Menu {
+ fn render(&mut self, cx: &mut ViewContext<Self>) -> impl Component {
+ div()
+ .key_context("menu")
+ .on_action(|this: &mut Menu, move: &MoveUp, cx: &mut ViewContext<Menu>| {
+ // ...
+ })
+ .on_action(|this, move: &MoveDown, cx| {
+ // ...
+ })
+ .children(todo!())
+ }
+}
+```
+
+Now you can target your context in the keymap. Note how actions are identified in the keymap by their fully-qualified type name.
+
+```json
+{
+ "context": "menu",
+ "bindings": {
+ "up": "menu::MoveUp",
+ "down": "menu::MoveDown"
+ }
+}
+```
+
+If you had opted for the more complex type definition, you'd provide the serialized representation of the action alongside the name:
+
+```json
+{
+ "context": "menu",
+ "bindings": {
+ "up": ["menu::Move", {direction: "up", select: false}]
+ "down": ["menu::Move", {direction: "down", select: false}]
+ "shift-up": ["menu::Move", {direction: "up", select: true}]
+ "shift-down": ["menu::Move", {direction: "down", select: true}]
+ }
+}
+
+```
@@ -1068,7 +1068,7 @@ impl<G: 'static> DerefMut for GlobalLease<G> {
/// Contains state associated with an active drag operation, started by dragging an element
/// within the window or by dragging into the app from the underlying platform.
-pub(crate) struct AnyDrag {
+pub struct AnyDrag {
pub view: AnyView,
pub cursor_offset: Point<Pixels>,
}
@@ -1,5 +1,6 @@
mod div;
mod img;
+mod node;
mod svg;
mod text;
mod uniform_list;
@@ -252,7 +252,7 @@ where
cx: &mut ViewContext<V>,
) -> LayoutId {
let style = self.compute_style(Bounds::default(), element_state, cx);
- style.apply_text_style(cx, |cx| {
+ style.with_text_style(cx, |cx| {
self.with_element_id(cx, |this, _global_id, cx| {
let layout_ids = this
.children
@@ -318,7 +318,7 @@ where
);
});
cx.with_z_index(1, |cx| {
- style.apply_text_style(cx, |cx| {
+ style.with_text_style(cx, |cx| {
style.apply_overflow(bounds, cx, |cx| {
let scroll_offset = element_state.interactive.scroll_offset();
cx.with_element_offset(scroll_offset, |cx| {
@@ -0,0 +1,767 @@
+use crate::{
+ point, Action, AnyDrag, AnyElement, AnyView, AppContext, BorrowWindow, Bounds, ClickEvent,
+ DispatchPhase, Element, FocusHandle, KeyContext, KeyDownEvent, KeyUpEvent, LayoutId,
+ MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, Pixels, Point, Render,
+ ScrollWheelEvent, SharedString, Style, StyleRefinement, Styled, View, ViewContext, Visibility,
+};
+use collections::HashMap;
+use refineable::Refineable;
+use smallvec::SmallVec;
+use std::{
+ any::{Any, TypeId},
+ marker::PhantomData,
+ sync::Arc,
+};
+
+pub struct GroupStyle {
+ pub group: SharedString,
+ pub style: StyleRefinement,
+}
+
+pub trait InteractiveComponent<V: 'static> {
+ fn interactivity(&mut self) -> &mut Interactivity<V>;
+
+ fn hover(mut self, f: impl FnOnce(StyleRefinement) -> StyleRefinement) -> Self
+ where
+ Self: Sized,
+ {
+ self.interactivity().hover_style = f(StyleRefinement::default());
+ self
+ }
+
+ fn group_hover(
+ mut self,
+ group_name: impl Into<SharedString>,
+ f: impl FnOnce(StyleRefinement) -> StyleRefinement,
+ ) -> Self
+ where
+ Self: Sized,
+ {
+ self.interactivity().group_hover_style = Some(GroupStyle {
+ group: group_name.into(),
+ style: f(StyleRefinement::default()),
+ });
+ self
+ }
+
+ fn on_mouse_down(
+ mut self,
+ button: MouseButton,
+ handler: impl Fn(&mut V, &MouseDownEvent, &mut ViewContext<V>) + 'static,
+ ) -> Self
+ where
+ Self: Sized,
+ {
+ self.interactivity().mouse_down_listeners.push(Box::new(
+ move |view, event, bounds, phase, cx| {
+ if phase == DispatchPhase::Bubble
+ && event.button == button
+ && bounds.contains_point(&event.position)
+ {
+ handler(view, event, cx)
+ }
+ },
+ ));
+ self
+ }
+
+ fn on_mouse_up(
+ mut self,
+ button: MouseButton,
+ handler: impl Fn(&mut V, &MouseUpEvent, &mut ViewContext<V>) + 'static,
+ ) -> Self
+ where
+ Self: Sized,
+ {
+ self.interactivity().mouse_up_listeners.push(Box::new(
+ move |view, event, bounds, phase, cx| {
+ if phase == DispatchPhase::Bubble
+ && event.button == button
+ && bounds.contains_point(&event.position)
+ {
+ handler(view, event, cx)
+ }
+ },
+ ));
+ self
+ }
+
+ fn on_mouse_down_out(
+ mut self,
+ handler: impl Fn(&mut V, &MouseDownEvent, &mut ViewContext<V>) + 'static,
+ ) -> Self
+ where
+ Self: Sized,
+ {
+ self.interactivity().mouse_down_listeners.push(Box::new(
+ move |view, event, bounds, phase, cx| {
+ if phase == DispatchPhase::Capture && !bounds.contains_point(&event.position) {
+ handler(view, event, cx)
+ }
+ },
+ ));
+ self
+ }
+
+ fn on_mouse_up_out(
+ mut self,
+ button: MouseButton,
+ handler: impl Fn(&mut V, &MouseUpEvent, &mut ViewContext<V>) + 'static,
+ ) -> Self
+ where
+ Self: Sized,
+ {
+ self.interactivity().mouse_up_listeners.push(Box::new(
+ move |view, event, bounds, phase, cx| {
+ if phase == DispatchPhase::Capture
+ && event.button == button
+ && !bounds.contains_point(&event.position)
+ {
+ handler(view, event, cx);
+ }
+ },
+ ));
+ self
+ }
+
+ fn on_mouse_move(
+ mut self,
+ handler: impl Fn(&mut V, &MouseMoveEvent, &mut ViewContext<V>) + 'static,
+ ) -> Self
+ where
+ Self: Sized,
+ {
+ self.interactivity().mouse_move_listeners.push(Box::new(
+ move |view, event, bounds, phase, cx| {
+ if phase == DispatchPhase::Bubble && bounds.contains_point(&event.position) {
+ handler(view, event, cx);
+ }
+ },
+ ));
+ self
+ }
+
+ fn on_scroll_wheel(
+ mut self,
+ handler: impl Fn(&mut V, &ScrollWheelEvent, &mut ViewContext<V>) + 'static,
+ ) -> Self
+ where
+ Self: Sized,
+ {
+ self.interactivity().scroll_wheel_listeners.push(Box::new(
+ move |view, event, bounds, phase, cx| {
+ if phase == DispatchPhase::Bubble && bounds.contains_point(&event.position) {
+ handler(view, event, cx);
+ }
+ },
+ ));
+ self
+ }
+
+ /// Capture the given action, fires during the capture phase
+ fn capture_action<A: Action>(
+ mut self,
+ listener: impl Fn(&mut V, &A, &mut ViewContext<V>) + 'static,
+ ) -> Self
+ where
+ Self: Sized,
+ {
+ self.interactivity().action_listeners.push((
+ TypeId::of::<A>(),
+ Box::new(move |view, action, phase, cx| {
+ let action = action.downcast_ref().unwrap();
+ if phase == DispatchPhase::Capture {
+ listener(view, action, cx)
+ }
+ }),
+ ));
+ self
+ }
+
+ /// Add a listener for the given action, fires during the bubble event phase
+ fn on_action<A: Action>(
+ mut self,
+ listener: impl Fn(&mut V, &A, &mut ViewContext<V>) + 'static,
+ ) -> Self
+ where
+ Self: Sized,
+ {
+ self.interactivity().action_listeners.push((
+ TypeId::of::<A>(),
+ Box::new(move |view, action, phase, cx| {
+ let action = action.downcast_ref().unwrap();
+ if phase == DispatchPhase::Bubble {
+ listener(view, action, cx)
+ }
+ }),
+ ));
+ self
+ }
+
+ fn on_key_down(
+ mut self,
+ listener: impl Fn(&mut V, &KeyDownEvent, DispatchPhase, &mut ViewContext<V>) + 'static,
+ ) -> Self
+ where
+ Self: Sized,
+ {
+ self.interactivity()
+ .key_down_listeners
+ .push(Box::new(move |view, event, phase, cx| {
+ listener(view, event, phase, cx)
+ }));
+ self
+ }
+
+ fn on_key_up(
+ mut self,
+ listener: impl Fn(&mut V, &KeyUpEvent, DispatchPhase, &mut ViewContext<V>) + 'static,
+ ) -> Self
+ where
+ Self: Sized,
+ {
+ self.interactivity()
+ .key_up_listeners
+ .push(Box::new(move |view, event, phase, cx| {
+ listener(view, event, phase, cx)
+ }));
+ self
+ }
+
+ fn drag_over<S: 'static>(mut self, f: impl FnOnce(StyleRefinement) -> StyleRefinement) -> Self
+ where
+ Self: Sized,
+ {
+ self.interactivity()
+ .drag_over_styles
+ .push((TypeId::of::<S>(), f(StyleRefinement::default())));
+ self
+ }
+
+ fn group_drag_over<S: 'static>(
+ mut self,
+ group_name: impl Into<SharedString>,
+ f: impl FnOnce(StyleRefinement) -> StyleRefinement,
+ ) -> Self
+ where
+ Self: Sized,
+ {
+ self.interactivity().group_drag_over_styles.push((
+ TypeId::of::<S>(),
+ GroupStyle {
+ group: group_name.into(),
+ style: f(StyleRefinement::default()),
+ },
+ ));
+ self
+ }
+
+ fn on_drop<W: 'static>(
+ mut self,
+ listener: impl Fn(&mut V, View<W>, &mut ViewContext<V>) + 'static,
+ ) -> Self
+ where
+ Self: Sized,
+ {
+ self.interactivity().drop_listeners.push((
+ TypeId::of::<W>(),
+ Box::new(move |view, dragged_view, cx| {
+ listener(view, dragged_view.downcast().unwrap(), cx);
+ }),
+ ));
+ self
+ }
+}
+
+pub trait StatefulInteractiveComponent<V: 'static> {
+ fn interactivity(&mut self) -> &mut StatefulInteractivity<V>;
+
+ fn active(mut self, f: impl FnOnce(StyleRefinement) -> StyleRefinement) -> Self
+ where
+ Self: Sized,
+ {
+ self.interactivity().active_style = f(StyleRefinement::default());
+ self
+ }
+
+ fn group_active(
+ mut self,
+ group_name: impl Into<SharedString>,
+ f: impl FnOnce(StyleRefinement) -> StyleRefinement,
+ ) -> Self
+ where
+ Self: Sized,
+ {
+ self.interactivity().group_active_style = Some(GroupStyle {
+ group: group_name.into(),
+ style: f(StyleRefinement::default()),
+ });
+ self
+ }
+
+ fn on_click(
+ mut self,
+ listener: impl Fn(&mut V, &ClickEvent, &mut ViewContext<V>) + 'static,
+ ) -> Self
+ where
+ Self: Sized,
+ {
+ self.interactivity()
+ .click_listeners
+ .push(Box::new(move |view, event, cx| listener(view, event, cx)));
+ self
+ }
+
+ fn on_drag<W>(
+ mut self,
+ listener: impl Fn(&mut V, &mut ViewContext<V>) -> View<W> + 'static,
+ ) -> Self
+ where
+ Self: Sized,
+ W: 'static + Render,
+ {
+ debug_assert!(
+ self.interactivity().drag_listener.is_none(),
+ "calling on_drag more than once on the same element is not supported"
+ );
+ self.interactivity().drag_listener =
+ Some(Box::new(move |view_state, cursor_offset, cx| AnyDrag {
+ view: listener(view_state, cx).into(),
+ cursor_offset,
+ }));
+ self
+ }
+
+ fn on_hover(mut self, listener: impl 'static + Fn(&mut V, bool, &mut ViewContext<V>)) -> Self
+ where
+ Self: Sized,
+ {
+ debug_assert!(
+ self.interactivity().hover_listener.is_none(),
+ "calling on_hover more than once on the same element is not supported"
+ );
+ self.interactivity().hover_listener = Some(Box::new(listener));
+ self
+ }
+
+ fn tooltip<W>(
+ mut self,
+ build_tooltip: impl Fn(&mut V, &mut ViewContext<V>) -> View<W> + 'static,
+ ) -> Self
+ where
+ Self: Sized,
+ W: 'static + Render,
+ {
+ debug_assert!(
+ self.interactivity().tooltip_builder.is_none(),
+ "calling tooltip more than once on the same element is not supported"
+ );
+ self.interactivity().tooltip_builder = Some(Arc::new(move |view_state, cx| {
+ build_tooltip(view_state, cx).into()
+ }));
+
+ self
+ }
+}
+
+pub trait FocusableComponent<V> {
+ fn focusability(&mut self) -> &mut Focusability<V>;
+
+ fn focus(mut self, f: impl FnOnce(StyleRefinement) -> StyleRefinement) -> Self
+ where
+ Self: Sized,
+ {
+ self.focusability().focus_style = f(StyleRefinement::default());
+ self
+ }
+
+ fn focus_in(mut self, f: impl FnOnce(StyleRefinement) -> StyleRefinement) -> Self
+ where
+ Self: Sized,
+ {
+ self.focusability().focus_in_style = f(StyleRefinement::default());
+ self
+ }
+
+ fn in_focus(mut self, f: impl FnOnce(StyleRefinement) -> StyleRefinement) -> Self
+ where
+ Self: Sized,
+ {
+ // self.focusability(). (f(StyleRefinement::default()));
+ self
+ }
+
+ fn on_focus(
+ mut self,
+ listener: impl Fn(&mut V, &FocusEvent, &mut ViewContext<V>) + 'static,
+ ) -> Self
+ where
+ Self: Sized,
+ {
+ self.focusability()
+ .focus_listeners
+ .push(Box::new(move |view, focus_handle, event, cx| {
+ if event.focused.as_ref() == Some(focus_handle) {
+ listener(view, event, cx)
+ }
+ }));
+ self
+ }
+
+ fn on_blur(
+ mut self,
+ listener: impl Fn(&mut V, &FocusEvent, &mut ViewContext<V>) + 'static,
+ ) -> Self
+ where
+ Self: Sized,
+ {
+ self.focusability()
+ .focus_listeners
+ .push(Box::new(move |view, focus_handle, event, cx| {
+ if event.blurred.as_ref() == Some(focus_handle) {
+ listener(view, event, cx)
+ }
+ }));
+ self
+ }
+
+ fn on_focus_in(
+ mut self,
+ listener: impl Fn(&mut V, &FocusEvent, &mut ViewContext<V>) + 'static,
+ ) -> Self
+ where
+ Self: Sized,
+ {
+ self.focusability()
+ .focus_listeners
+ .push(Box::new(move |view, focus_handle, event, cx| {
+ let descendant_blurred = event
+ .blurred
+ .as_ref()
+ .map_or(false, |blurred| focus_handle.contains(blurred, cx));
+ let descendant_focused = event
+ .focused
+ .as_ref()
+ .map_or(false, |focused| focus_handle.contains(focused, cx));
+
+ if !descendant_blurred && descendant_focused {
+ listener(view, event, cx)
+ }
+ }));
+ self
+ }
+
+ fn on_focus_out(
+ mut self,
+ listener: impl Fn(&mut V, &FocusEvent, &mut ViewContext<V>) + 'static,
+ ) -> Self
+ where
+ Self: Sized,
+ {
+ self.focusability()
+ .focus_listeners
+ .push(Box::new(move |view, focus_handle, event, cx| {
+ let descendant_blurred = event
+ .blurred
+ .as_ref()
+ .map_or(false, |blurred| focus_handle.contains(blurred, cx));
+ let descendant_focused = event
+ .focused
+ .as_ref()
+ .map_or(false, |focused| focus_handle.contains(focused, cx));
+ if descendant_blurred && !descendant_focused {
+ listener(view, event, cx)
+ }
+ }));
+ self
+ }
+}
+
+pub type FocusListeners<V> = SmallVec<[FocusListener<V>; 2]>;
+
+pub type FocusListener<V> =
+ Box<dyn Fn(&mut V, &FocusHandle, &FocusEvent, &mut ViewContext<V>) + 'static>;
+
+pub type MouseDownListener<V> = Box<
+ dyn Fn(&mut V, &MouseDownEvent, &Bounds<Pixels>, DispatchPhase, &mut ViewContext<V>) + 'static,
+>;
+pub type MouseUpListener<V> = Box<
+ dyn Fn(&mut V, &MouseUpEvent, &Bounds<Pixels>, DispatchPhase, &mut ViewContext<V>) + 'static,
+>;
+
+pub type MouseMoveListener<V> = Box<
+ dyn Fn(&mut V, &MouseMoveEvent, &Bounds<Pixels>, DispatchPhase, &mut ViewContext<V>) + 'static,
+>;
+
+pub type ScrollWheelListener<V> = Box<
+ dyn Fn(&mut V, &ScrollWheelEvent, &Bounds<Pixels>, DispatchPhase, &mut ViewContext<V>)
+ + 'static,
+>;
+
+pub type ClickListener<V> = Box<dyn Fn(&mut V, &ClickEvent, &mut ViewContext<V>) + 'static>;
+
+pub type DragListener<V> =
+ Box<dyn Fn(&mut V, Point<Pixels>, &mut ViewContext<V>) -> AnyDrag + 'static>;
+
+type DropListener<V> = dyn Fn(&mut V, AnyView, &mut ViewContext<V>) + 'static;
+
+pub type HoverListener<V> = Box<dyn Fn(&mut V, bool, &mut ViewContext<V>) + 'static>;
+
+pub type TooltipBuilder<V> = Arc<dyn Fn(&mut V, &mut ViewContext<V>) -> AnyView + 'static>;
+
+pub type KeyDownListener<V> =
+ Box<dyn Fn(&mut V, &KeyDownEvent, DispatchPhase, &mut ViewContext<V>) + 'static>;
+
+pub type KeyUpListener<V> =
+ Box<dyn Fn(&mut V, &KeyUpEvent, DispatchPhase, &mut ViewContext<V>) + 'static>;
+
+pub type ActionListener<V> =
+ Box<dyn Fn(&mut V, &dyn Any, DispatchPhase, &mut ViewContext<V>) + 'static>;
+
+pub struct FocusEvent {
+ pub blurred: Option<FocusHandle>,
+ pub focused: Option<FocusHandle>,
+}
+
+pub struct Node<V> {
+ style: StyleRefinement,
+ key_context: KeyContext,
+ interactivity: Interactivity<V>,
+ children: Vec<AnyElement<V>>,
+}
+
+pub struct Interactivity<V> {
+ group: Option<SharedString>,
+ pub dispatch_context: KeyContext,
+ pub mouse_down_listeners: SmallVec<[MouseDownListener<V>; 2]>,
+ pub mouse_up_listeners: SmallVec<[MouseUpListener<V>; 2]>,
+ pub mouse_move_listeners: SmallVec<[MouseMoveListener<V>; 2]>,
+ pub scroll_wheel_listeners: SmallVec<[ScrollWheelListener<V>; 2]>,
+ pub key_down_listeners: SmallVec<[KeyDownListener<V>; 2]>,
+ pub key_up_listeners: SmallVec<[KeyUpListener<V>; 2]>,
+ pub action_listeners: SmallVec<[(TypeId, ActionListener<V>); 8]>,
+ pub hover_style: StyleRefinement,
+ pub group_hover_style: Option<GroupStyle>,
+ drag_over_styles: SmallVec<[(TypeId, StyleRefinement); 2]>,
+ group_drag_over_styles: SmallVec<[(TypeId, GroupStyle); 2]>,
+ drop_listeners: SmallVec<[(TypeId, Box<DropListener<V>>); 2]>,
+ scroll_offset: Point<Pixels>,
+}
+
+impl<V> Node<V> {
+ fn compute_style(&self) -> Style {
+ let mut style = Style::default();
+ style.refine(&self.style);
+ style
+ }
+}
+
+impl<V> Styled for Node<V> {
+ fn style(&mut self) -> &mut StyleRefinement {
+ &mut self.style
+ }
+}
+
+impl<V: 'static> InteractiveComponent<V> for Node<V> {
+ fn interactivity(&mut self) -> &mut Interactivity<V> {
+ &mut self.interactivity
+ }
+}
+
+pub struct NodeState {
+ child_layout_ids: SmallVec<[LayoutId; 4]>,
+}
+
+impl<V: 'static> Element<V> for Node<V> {
+ type ElementState = NodeState;
+
+ fn id(&self) -> Option<crate::ElementId> {
+ None
+ }
+
+ fn initialize(
+ &mut self,
+ view_state: &mut V,
+ _: Option<Self::ElementState>,
+ cx: &mut ViewContext<V>,
+ ) -> Self::ElementState {
+ for child in &mut self.children {
+ child.initialize(view_state, cx);
+ }
+ NodeState {
+ child_layout_ids: SmallVec::new(),
+ }
+ }
+
+ fn layout(
+ &mut self,
+ view_state: &mut V,
+ element_state: &mut Self::ElementState,
+ cx: &mut ViewContext<V>,
+ ) -> crate::LayoutId {
+ let style = self.compute_style();
+ style.with_text_style(cx, |cx| {
+ element_state.child_layout_ids = self
+ .children
+ .iter_mut()
+ .map(|child| child.layout(view_state, cx))
+ .collect::<SmallVec<_>>();
+ cx.request_layout(&style, element_state.child_layout_ids.iter().copied())
+ })
+ }
+
+ fn paint(
+ &mut self,
+ bounds: Bounds<Pixels>,
+ view_state: &mut V,
+ element_state: &mut Self::ElementState,
+ cx: &mut ViewContext<V>,
+ ) {
+ let style = self.compute_style();
+ if style.visibility == Visibility::Hidden {
+ return;
+ }
+
+ if let Some(mouse_cursor) = style.mouse_cursor {
+ let hovered = bounds.contains_point(&cx.mouse_position());
+ if hovered {
+ cx.set_cursor_style(mouse_cursor);
+ }
+ }
+
+ if let Some(group) = self.interactivity.group.clone() {
+ GroupBounds::push(group, bounds, cx);
+ }
+
+ let z_index = style.z_index.unwrap_or(0);
+
+ let mut child_min = point(Pixels::MAX, Pixels::MAX);
+ let mut child_max = Point::default();
+
+ let content_size = if element_state.child_layout_ids.is_empty() {
+ bounds.size
+ } else {
+ for child_layout_id in &element_state.child_layout_ids {
+ let child_bounds = cx.layout_bounds(*child_layout_id);
+ child_min = child_min.min(&child_bounds.origin);
+ child_max = child_max.max(&child_bounds.lower_right());
+ }
+ (child_max - child_min).into()
+ };
+
+ cx.with_z_index(z_index, |cx| {
+ cx.with_z_index(0, |cx| {
+ style.paint(bounds, cx);
+ });
+ cx.with_z_index(1, |cx| {
+ style.with_text_style(cx, |cx| {
+ style.apply_overflow(bounds, cx, |cx| {
+ let scroll_offset = self.interactivity.scroll_offset;
+ cx.with_element_offset2(scroll_offset, |cx| {
+ for child in &mut self.children {
+ child.paint(view_state, cx);
+ }
+ });
+ })
+ })
+ });
+ });
+
+ if let Some(group) = self.interactivity.group.as_ref() {
+ GroupBounds::pop(group, cx);
+ }
+ }
+}
+
+#[derive(Default)]
+pub struct GroupBounds(HashMap<SharedString, SmallVec<[Bounds<Pixels>; 1]>>);
+
+impl GroupBounds {
+ pub fn get(name: &SharedString, cx: &mut AppContext) -> Option<Bounds<Pixels>> {
+ cx.default_global::<Self>()
+ .0
+ .get(name)
+ .and_then(|bounds_stack| bounds_stack.last())
+ .cloned()
+ }
+
+ pub fn push(name: SharedString, bounds: Bounds<Pixels>, cx: &mut AppContext) {
+ cx.default_global::<Self>()
+ .0
+ .entry(name)
+ .or_default()
+ .push(bounds);
+ }
+
+ pub fn pop(name: &SharedString, cx: &mut AppContext) {
+ cx.default_global::<Self>().0.get_mut(name).unwrap().pop();
+ }
+}
+
+pub struct Focusable<V, E> {
+ focusability: Focusability<V>,
+ view_type: PhantomData<V>,
+ element: E,
+}
+
+pub struct Focusability<V> {
+ focus_handle: Option<FocusHandle>,
+ focus_listeners: FocusListeners<V>,
+ focus_style: StyleRefinement,
+ focus_in_style: StyleRefinement,
+ in_focus_style: StyleRefinement,
+}
+
+impl<V, E> FocusableComponent<V> for Focusable<V, E> {
+ fn focusability(&mut self) -> &mut Focusability<V> {
+ &mut self.focusability
+ }
+}
+
+impl<V: 'static, E: InteractiveComponent<V>> InteractiveComponent<V> for Focusable<V, E> {
+ fn interactivity(&mut self) -> &mut Interactivity<V> {
+ self.element.interactivity()
+ }
+}
+
+impl<V: 'static, E: StatefulInteractiveComponent<V>> StatefulInteractiveComponent<V>
+ for Focusable<V, E>
+{
+ fn interactivity(&mut self) -> &mut StatefulInteractivity<V> {
+ self.element.interactivity()
+ }
+}
+
+pub struct Stateful<V, E> {
+ id: SharedString,
+ interactivity: StatefulInteractivity<V>,
+ view_type: PhantomData<V>,
+ element: E,
+}
+
+pub struct StatefulInteractivity<V> {
+ click_listeners: SmallVec<[ClickListener<V>; 2]>,
+ active_style: StyleRefinement,
+ group_active_style: Option<GroupStyle>,
+ drag_listener: Option<DragListener<V>>,
+ hover_listener: Option<HoverListener<V>>,
+ tooltip_builder: Option<TooltipBuilder<V>>,
+}
+
+impl<V: 'static, E> StatefulInteractiveComponent<V> for Stateful<V, E> {
+ fn interactivity(&mut self) -> &mut StatefulInteractivity<V> {
+ &mut self.interactivity
+ }
+}
+
+impl<V: 'static, E: InteractiveComponent<V>> InteractiveComponent<V> for Stateful<V, E> {
+ fn interactivity(&mut self) -> &mut Interactivity<V> {
+ self.element.interactivity()
+ }
+}
+
+impl<V, E: FocusableComponent<V>> FocusableComponent<V> for Stateful<V, E> {
+ fn focusability(&mut self) -> &mut Focusability<V> {
+ self.element.focusability()
+ }
+}
@@ -228,7 +228,7 @@ impl Style {
}
}
- pub fn apply_text_style<C, F, R>(&self, cx: &mut C, f: F) -> R
+ pub fn with_text_style<C, F, R>(&self, cx: &mut C, f: F) -> R
where
C: BorrowAppContext,
F: FnOnce(&mut C) -> R,
@@ -1587,6 +1587,27 @@ pub trait BorrowWindow: BorrowMut<Window> + BorrowMut<AppContext> {
result
}
+ /// Update the global element offset based on the given offset. This is used to implement
+ /// scrolling and position drag handles.
+ fn with_element_offset2<R>(
+ &mut self,
+ offset: Point<Pixels>,
+ f: impl FnOnce(&mut Self) -> R,
+ ) -> R {
+ if offset.is_zero() {
+ return f(self);
+ };
+
+ let offset = self.element_offset() + offset;
+ self.window_mut()
+ .current_frame
+ .element_offset_stack
+ .push(offset);
+ let result = f(self);
+ self.window_mut().current_frame.element_offset_stack.pop();
+ result
+ }
+
/// Obtain the current element offset.
fn element_offset(&self) -> Point<Pixels> {
self.window()