Merge branch 'gpui2' into gpui2ui-debug-panel

Marshall Bowers created

Change summary

crates/gpui3/src/elements/div.rs             |  27 ++-
crates/gpui3/src/events.rs                   |  31 ++--
crates/gpui3/src/focus.rs                    |  26 ++
crates/gpui3/src/interactive.rs              |  16 +
crates/gpui3/src/window.rs                   | 164 +++++++++------------
crates/storybook2/src/stories/z_index.rs     |   6 
crates/storybook2/src/storybook2.rs          |  29 ++-
crates/ui2/src/components/assistant_panel.rs |   2 
crates/ui2/src/components/chat_panel.rs      |  13 -
crates/ui2/src/components/icon_button.rs     |   4 
crates/ui2/src/components/list.rs            |   4 
crates/ui2/src/components/panel.rs           |  10 
crates/ui2/src/components/project_panel.rs   |   2 
crates/ui2/src/components/title_bar.rs       |   4 
crates/ui2/src/components/workspace.rs       |  25 +--
crates/ui2/src/elements/button.rs            |   4 
crates/ui2/src/elements/icon.rs              |   4 
crates/ui2/src/elements/label.rs             |   2 
crates/ui2/src/lib.rs                        |   8 +
crates/ui2/src/prelude.rs                    |   6 
crates/ui2/src/settings.rs                   |  96 +++++++++++-
21 files changed, 284 insertions(+), 199 deletions(-)

Detailed changes

crates/gpui3/src/elements/div.rs 🔗

@@ -423,18 +423,27 @@ where
         element_state: Option<Self::ElementState>,
         cx: &mut ViewContext<Self::ViewState>,
     ) -> Self::ElementState {
-        cx.with_focus(
-            self.focusability.focus_handle().cloned(),
-            mem::take(&mut self.listeners.key_down),
-            mem::take(&mut self.listeners.key_up),
-            mem::take(&mut self.listeners.focus),
-            |cx| {
+        for listener in self.listeners.focus.iter().cloned() {
+            cx.on_focus_changed(move |view, event, cx| listener(view, event, cx));
+        }
+
+        let key_listeners = mem::take(&mut self.listeners.key);
+        cx.with_key_listeners(&key_listeners, |cx| {
+            if let Some(focus_handle) = self.focusability.focus_handle().cloned() {
+                cx.with_focus(focus_handle, |cx| {
+                    for child in &mut self.children {
+                        child.initialize(view_state, cx);
+                    }
+                })
+            } else {
                 for child in &mut self.children {
                     child.initialize(view_state, cx);
                 }
-                element_state.unwrap_or_default()
-            },
-        )
+            }
+        });
+        self.listeners.key = key_listeners;
+
+        element_state.unwrap_or_default()
     }
 
     fn layout(

crates/gpui3/src/events.rs 🔗

@@ -2,7 +2,11 @@ use crate::{
     point, Bounds, DispatchPhase, FocusHandle, Keystroke, Modifiers, Pixels, Point, ViewContext,
 };
 use smallvec::SmallVec;
-use std::{any::Any, ops::Deref};
+use std::{
+    any::{Any, TypeId},
+    ops::Deref,
+    sync::Arc,
+};
 
 #[derive(Clone, Debug, Eq, PartialEq)]
 pub struct KeyDownEvent {
@@ -221,43 +225,40 @@ pub struct FocusEvent {
     pub focused: Option<FocusHandle>,
 }
 
-pub type MouseDownListener<V> = Box<
+pub type MouseDownListener<V> = Arc<
     dyn Fn(&mut V, &MouseDownEvent, &Bounds<Pixels>, DispatchPhase, &mut ViewContext<V>)
         + Send
         + Sync
         + 'static,
 >;
-pub type MouseUpListener<V> = Box<
+pub type MouseUpListener<V> = Arc<
     dyn Fn(&mut V, &MouseUpEvent, &Bounds<Pixels>, DispatchPhase, &mut ViewContext<V>)
         + Send
         + Sync
         + 'static,
 >;
 pub type MouseClickListener<V> =
-    Box<dyn Fn(&mut V, &MouseClickEvent, &mut ViewContext<V>) + Send + Sync + 'static>;
+    Arc<dyn Fn(&mut V, &MouseClickEvent, &mut ViewContext<V>) + Send + Sync + 'static>;
 
-pub type MouseMoveListener<V> = Box<
+pub type MouseMoveListener<V> = Arc<
     dyn Fn(&mut V, &MouseMoveEvent, &Bounds<Pixels>, DispatchPhase, &mut ViewContext<V>)
         + Send
         + Sync
         + 'static,
 >;
 
-pub type ScrollWheelListener<V> = Box<
+pub type ScrollWheelListener<V> = Arc<
     dyn Fn(&mut V, &ScrollWheelEvent, &Bounds<Pixels>, DispatchPhase, &mut ViewContext<V>)
         + Send
         + Sync
         + 'static,
 >;
 
-pub type KeyDownListener<V> =
-    Box<dyn Fn(&mut V, &KeyDownEvent, DispatchPhase, &mut ViewContext<V>) + Send + Sync + 'static>;
-
-pub type KeyUpListener<V> =
-    Box<dyn Fn(&mut V, &KeyUpEvent, DispatchPhase, &mut ViewContext<V>) + Send + Sync + 'static>;
+pub type KeyListener<V> =
+    Arc<dyn Fn(&mut V, &dyn Any, DispatchPhase, &mut ViewContext<V>) + Send + Sync + 'static>;
 
 pub type FocusListener<V> =
-    Box<dyn Fn(&mut V, &FocusEvent, &mut ViewContext<V>) + Send + Sync + 'static>;
+    Arc<dyn Fn(&mut V, &FocusEvent, &mut ViewContext<V>) + Send + Sync + 'static>;
 
 pub struct EventListeners<V: 'static> {
     pub mouse_down: SmallVec<[MouseDownListener<V>; 2]>,
@@ -265,8 +266,7 @@ pub struct EventListeners<V: 'static> {
     pub mouse_click: SmallVec<[MouseClickListener<V>; 2]>,
     pub mouse_move: SmallVec<[MouseMoveListener<V>; 2]>,
     pub scroll_wheel: SmallVec<[ScrollWheelListener<V>; 2]>,
-    pub key_down: SmallVec<[KeyDownListener<V>; 2]>,
-    pub key_up: SmallVec<[KeyUpListener<V>; 2]>,
+    pub key: SmallVec<[(TypeId, KeyListener<V>); 32]>,
     pub focus: SmallVec<[FocusListener<V>; 2]>,
 }
 
@@ -278,8 +278,7 @@ impl<V> Default for EventListeners<V> {
             mouse_click: SmallVec::new(),
             mouse_move: SmallVec::new(),
             scroll_wheel: SmallVec::new(),
-            key_down: SmallVec::new(),
-            key_up: SmallVec::new(),
+            key: SmallVec::new(),
             focus: SmallVec::new(),
         }
     }

crates/gpui3/src/focus.rs 🔗

@@ -1,3 +1,5 @@
+use std::{any::TypeId, sync::Arc};
+
 use crate::{
     DispatchPhase, FocusEvent, FocusHandle, Interactive, KeyDownEvent, KeyUpEvent, StyleRefinement,
     ViewContext,
@@ -46,7 +48,7 @@ pub trait Focus: Interactive {
         let handle = self.handle().clone();
         self.listeners()
             .focus
-            .push(Box::new(move |view, event, cx| {
+            .push(Arc::new(move |view, event, cx| {
                 if event.focused.as_ref() == Some(&handle) {
                     listener(view, event, cx)
                 }
@@ -67,7 +69,7 @@ pub trait Focus: Interactive {
         let handle = self.handle().clone();
         self.listeners()
             .focus
-            .push(Box::new(move |view, event, cx| {
+            .push(Arc::new(move |view, event, cx| {
                 if event.blurred.as_ref() == Some(&handle) {
                     listener(view, event, cx)
                 }
@@ -88,7 +90,7 @@ pub trait Focus: Interactive {
         let handle = self.handle().clone();
         self.listeners()
             .focus
-            .push(Box::new(move |view, event, cx| {
+            .push(Arc::new(move |view, event, cx| {
                 let descendant_blurred = event
                     .blurred
                     .as_ref()
@@ -118,7 +120,7 @@ pub trait Focus: Interactive {
         let handle = self.handle().clone();
         self.listeners()
             .focus
-            .push(Box::new(move |view, event, cx| {
+            .push(Arc::new(move |view, event, cx| {
                 let descendant_blurred = event
                     .blurred
                     .as_ref()
@@ -148,7 +150,13 @@ pub trait Focus: Interactive {
     where
         Self: Sized,
     {
-        self.listeners().key_down.push(Box::new(listener));
+        self.listeners().key.push((
+            TypeId::of::<KeyDownEvent>(),
+            Arc::new(move |view, event, phase, cx| {
+                let event = event.downcast_ref().unwrap();
+                listener(view, event, phase, cx)
+            }),
+        ));
         self
     }
 
@@ -162,7 +170,13 @@ pub trait Focus: Interactive {
     where
         Self: Sized,
     {
-        self.listeners().key_up.push(Box::new(listener));
+        self.listeners().key.push((
+            TypeId::of::<KeyUpEvent>(),
+            Arc::new(move |view, event, phase, cx| {
+                let event = event.downcast_ref().unwrap();
+                listener(view, event, phase, cx)
+            }),
+        ));
         self
     }
 }

crates/gpui3/src/interactive.rs 🔗

@@ -1,3 +1,5 @@
+use std::sync::Arc;
+
 use crate::{
     DispatchPhase, Element, EventListeners, MouseButton, MouseClickEvent, MouseDownEvent,
     MouseMoveEvent, MouseUpEvent, ScrollWheelEvent, ViewContext,
@@ -19,7 +21,7 @@ pub trait Interactive: Element {
     {
         self.listeners()
             .mouse_down
-            .push(Box::new(move |view, event, bounds, phase, cx| {
+            .push(Arc::new(move |view, event, bounds, phase, cx| {
                 if phase == DispatchPhase::Bubble
                     && event.button == button
                     && bounds.contains_point(&event.position)
@@ -43,7 +45,7 @@ pub trait Interactive: Element {
     {
         self.listeners()
             .mouse_up
-            .push(Box::new(move |view, event, bounds, phase, cx| {
+            .push(Arc::new(move |view, event, bounds, phase, cx| {
                 if phase == DispatchPhase::Bubble
                     && event.button == button
                     && bounds.contains_point(&event.position)
@@ -67,7 +69,7 @@ pub trait Interactive: Element {
     {
         self.listeners()
             .mouse_down
-            .push(Box::new(move |view, event, bounds, phase, cx| {
+            .push(Arc::new(move |view, event, bounds, phase, cx| {
                 if phase == DispatchPhase::Capture
                     && event.button == button
                     && !bounds.contains_point(&event.position)
@@ -91,7 +93,7 @@ pub trait Interactive: Element {
     {
         self.listeners()
             .mouse_up
-            .push(Box::new(move |view, event, bounds, phase, cx| {
+            .push(Arc::new(move |view, event, bounds, phase, cx| {
                 if phase == DispatchPhase::Capture
                     && event.button == button
                     && !bounds.contains_point(&event.position)
@@ -114,7 +116,7 @@ pub trait Interactive: Element {
     {
         self.listeners()
             .mouse_move
-            .push(Box::new(move |view, event, bounds, phase, cx| {
+            .push(Arc::new(move |view, event, bounds, phase, cx| {
                 if phase == DispatchPhase::Bubble && bounds.contains_point(&event.position) {
                     handler(view, event, cx);
                 }
@@ -134,7 +136,7 @@ pub trait Interactive: Element {
     {
         self.listeners()
             .scroll_wheel
-            .push(Box::new(move |view, event, bounds, phase, cx| {
+            .push(Arc::new(move |view, event, bounds, phase, cx| {
                 if phase == DispatchPhase::Bubble && bounds.contains_point(&event.position) {
                     handler(view, event, cx);
                 }
@@ -156,7 +158,7 @@ pub trait Click: Interactive {
     {
         self.listeners()
             .mouse_click
-            .push(Box::new(move |view, event, cx| handler(view, event, cx)));
+            .push(Arc::new(move |view, event, cx| handler(view, event, cx)));
         self
     }
 }

crates/gpui3/src/window.rs 🔗

@@ -1,13 +1,12 @@
 use crate::{
     px, size, AnyBox, AnyView, AppContext, AsyncWindowContext, AvailableSpace, BorrowAppContext,
     Bounds, BoxShadow, Context, Corners, DevicePixels, DisplayId, Edges, Effect, Element, EntityId,
-    EventEmitter, FocusEvent, FocusListener, FontId, GlobalElementId, GlyphId, Handle, Hsla,
-    ImageData, InputEvent, IsZero, KeyDownEvent, KeyDownListener, KeyUpEvent, KeyUpListener,
-    LayoutId, MainThread, MainThreadOnly, MonochromeSprite, MouseMoveEvent, Path, Pixels, Platform,
-    PlatformAtlas, PlatformWindow, Point, PolychromeSprite, Quad, Reference, RenderGlyphParams,
-    RenderImageParams, RenderSvgParams, ScaledPixels, SceneBuilder, Shadow, SharedString, Size,
-    Style, Subscription, TaffyLayoutEngine, Task, Underline, UnderlineStyle, WeakHandle,
-    WindowOptions, SUBPIXEL_VARIANTS,
+    EventEmitter, FocusEvent, FontId, GlobalElementId, GlyphId, Handle, Hsla, ImageData,
+    InputEvent, IsZero, KeyListener, LayoutId, MainThread, MainThreadOnly, MonochromeSprite,
+    MouseMoveEvent, Path, Pixels, Platform, PlatformAtlas, PlatformWindow, Point, PolychromeSprite,
+    Quad, Reference, RenderGlyphParams, RenderImageParams, RenderSvgParams, ScaledPixels,
+    SceneBuilder, Shadow, SharedString, Size, Style, Subscription, TaffyLayoutEngine, Task,
+    Underline, UnderlineStyle, WeakHandle, WindowOptions, SUBPIXEL_VARIANTS,
 };
 use anyhow::Result;
 use collections::HashMap;
@@ -45,15 +44,8 @@ pub enum DispatchPhase {
     Capture,
 }
 
-type AnyMouseEventListener =
-    Box<dyn Fn(&dyn Any, DispatchPhase, &mut WindowContext) + Send + Sync + 'static>;
-type AnyKeyboardEventListener =
-    Box<dyn Fn(&dyn Any, DispatchPhase, &mut WindowContext) + Send + Sync + 'static>;
-type AnyFocusListener = Box<dyn Fn(&FocusEvent, &mut WindowContext) + Send + Sync + 'static>;
-type AnyKeyDownListener =
-    Box<dyn Fn(&KeyDownEvent, DispatchPhase, &mut WindowContext) + Send + Sync + 'static>;
-type AnyKeyUpListener =
-    Box<dyn Fn(&KeyUpEvent, DispatchPhase, &mut WindowContext) + Send + Sync + 'static>;
+type AnyListener = Arc<dyn Fn(&dyn Any, DispatchPhase, &mut WindowContext) + Send + Sync + 'static>;
+type AnyFocusListener = Arc<dyn Fn(&FocusEvent, &mut WindowContext) + Send + Sync + 'static>;
 
 slotmap::new_key_type! { pub struct FocusId; }
 
@@ -153,9 +145,10 @@ pub struct Window {
     element_states: HashMap<GlobalElementId, AnyBox>,
     z_index_stack: StackingOrder,
     content_mask_stack: Vec<ContentMask<Pixels>>,
-    mouse_listeners: HashMap<TypeId, Vec<(StackingOrder, AnyMouseEventListener)>>,
-    keyboard_listeners: HashMap<TypeId, Vec<AnyKeyboardEventListener>>,
-    focus_stack: Vec<FocusStackFrame>,
+    mouse_listeners: HashMap<TypeId, Vec<(StackingOrder, AnyListener)>>,
+    key_listeners: HashMap<TypeId, Vec<AnyListener>>,
+    key_events_enabled: bool,
+    focus_stack: Vec<FocusId>,
     focus_parents_by_child: HashMap<FocusId, FocusId>,
     pub(crate) focus_listeners: Vec<AnyFocusListener>,
     pub(crate) focus_handles: Arc<RwLock<SlotMap<FocusId, AtomicUsize>>>,
@@ -226,7 +219,8 @@ impl Window {
             z_index_stack: StackingOrder(SmallVec::new()),
             content_mask_stack: Vec::new(),
             mouse_listeners: HashMap::default(),
-            keyboard_listeners: HashMap::default(),
+            key_listeners: HashMap::default(),
+            key_events_enabled: true,
             focus_stack: Vec::new(),
             focus_parents_by_child: HashMap::default(),
             focus_listeners: Vec::new(),
@@ -262,12 +256,6 @@ impl ContentMask<Pixels> {
     }
 }
 
-struct FocusStackFrame {
-    handle: FocusHandle,
-    key_down_listeners: SmallVec<[AnyKeyDownListener; 2]>,
-    key_up_listeners: SmallVec<[AnyKeyUpListener; 2]>,
-}
-
 pub struct WindowContext<'a, 'w> {
     app: Reference<'a, AppContext>,
     pub(crate) window: Reference<'w, Window>,
@@ -468,7 +456,7 @@ impl<'a, 'w> WindowContext<'a, 'w> {
             .or_default()
             .push((
                 order,
-                Box::new(move |event: &dyn Any, phase, cx| {
+                Arc::new(move |event: &dyn Any, phase, cx| {
                     handler(event.downcast_ref().unwrap(), phase, cx)
                 }),
             ))
@@ -479,10 +467,10 @@ impl<'a, 'w> WindowContext<'a, 'w> {
         handler: impl Fn(&Event, DispatchPhase, &mut WindowContext) + Send + Sync + 'static,
     ) {
         self.window
-            .keyboard_listeners
+            .key_listeners
             .entry(TypeId::of::<Event>())
             .or_default()
-            .push(Box::new(move |event: &dyn Any, phase, cx| {
+            .push(Arc::new(move |event: &dyn Any, phase, cx| {
                 handler(event.downcast_ref().unwrap(), phase, cx)
             }))
     }
@@ -833,8 +821,9 @@ impl<'a, 'w> WindowContext<'a, 'w> {
         // Clear focus state, because we determine what is focused when the new elements
         // in the upcoming frame are initialized.
         window.focus_listeners.clear();
-        window.keyboard_listeners.values_mut().for_each(Vec::clear);
+        window.key_listeners.values_mut().for_each(Vec::clear);
         window.focus_parents_by_child.clear();
+        window.key_events_enabled = true;
     }
 
     fn end_frame(&mut self) {
@@ -893,7 +882,7 @@ impl<'a, 'w> WindowContext<'a, 'w> {
         } else if let Some(any_keyboard_event) = event.keyboard_event() {
             if let Some(mut handlers) = self
                 .window
-                .keyboard_listeners
+                .key_listeners
                 .remove(&any_keyboard_event.type_id())
             {
                 for handler in &handlers {
@@ -914,13 +903,13 @@ impl<'a, 'w> WindowContext<'a, 'w> {
 
                 handlers.extend(
                     self.window
-                        .keyboard_listeners
+                        .key_listeners
                         .get_mut(&any_keyboard_event.type_id())
                         .into_iter()
                         .flat_map(|handlers| handlers.drain(..)),
                 );
                 self.window
-                    .keyboard_listeners
+                    .key_listeners
                     .insert(any_keyboard_event.type_id(), handlers);
             }
         }
@@ -1218,78 +1207,69 @@ impl<'a, 'w, V: Send + Sync + 'static> ViewContext<'a, 'w, V> {
         });
     }
 
-    pub fn with_focus<R>(
+    pub fn on_focus_changed(
         &mut self,
-        focus_handle: Option<FocusHandle>,
-        key_down: impl IntoIterator<Item = KeyDownListener<V>>,
-        key_up: impl IntoIterator<Item = KeyUpListener<V>>,
-        focus: impl IntoIterator<Item = FocusListener<V>>,
-        f: impl FnOnce(&mut Self) -> R,
-    ) -> R {
-        let Some(focus_handle) = focus_handle else {
-            return f(self);
-        };
-
+        listener: impl Fn(&mut V, &FocusEvent, &mut ViewContext<V>) + Send + Sync + 'static,
+    ) {
         let handle = self.handle();
-        let window = &mut *self.window;
+        self.window.focus_listeners.push(Arc::new(move |event, cx| {
+            handle
+                .update(cx, |view, cx| listener(view, event, cx))
+                .log_err();
+        }));
+    }
 
-        for listener in focus {
-            let handle = handle.clone();
-            window.focus_listeners.push(Box::new(move |event, cx| {
-                handle
-                    .update(cx, |view, cx| listener(view, event, cx))
-                    .log_err();
-            }));
+    pub fn with_key_listeners<R>(
+        &mut self,
+        key_listeners: &[(TypeId, KeyListener<V>)],
+        f: impl FnOnce(&mut Self) -> R,
+    ) -> R {
+        if self.window.key_events_enabled {
+            let handle = self.handle();
+            for (type_id, listener) in key_listeners {
+                let handle = handle.clone();
+                let listener = listener.clone();
+                self.window
+                    .key_listeners
+                    .entry(*type_id)
+                    .or_default()
+                    .push(Arc::new(move |event, phase, cx| {
+                        handle
+                            .update(cx, |view, cx| listener(view, event, phase, cx))
+                            .log_err();
+                    }));
+            }
         }
 
-        let mut focus_stack = mem::take(&mut window.focus_stack);
-        if let Some(parent_frame) = focus_stack.last() {
-            window
-                .focus_parents_by_child
-                .insert(focus_handle.id, parent_frame.handle.id);
+        let result = f(self);
+
+        if self.window.key_events_enabled {
+            for (type_id, _) in key_listeners {
+                self.window.key_listeners.get_mut(type_id).unwrap().pop();
+            }
         }
 
-        let mut frame = FocusStackFrame {
-            handle: focus_handle.clone(),
-            key_down_listeners: SmallVec::new(),
-            key_up_listeners: SmallVec::new(),
-        };
+        result
+    }
 
-        for listener in key_down {
-            let handle = handle.clone();
-            frame
-                .key_down_listeners
-                .push(Box::new(move |event, phase, cx| {
-                    handle
-                        .update(cx, |view, cx| listener(view, event, phase, cx))
-                        .log_err();
-                }));
-        }
-        for listener in key_up {
-            let handle = handle.clone();
-            frame
-                .key_up_listeners
-                .push(Box::new(move |event, phase, cx| {
-                    handle
-                        .update(cx, |view, cx| listener(view, event, phase, cx))
-                        .log_err();
-                }));
+    pub fn with_focus<R>(
+        &mut self,
+        focus_handle: FocusHandle,
+        f: impl FnOnce(&mut Self) -> R,
+    ) -> R {
+        if let Some(parent_focus_id) = self.window.focus_stack.last().copied() {
+            self.window
+                .focus_parents_by_child
+                .insert(focus_handle.id, parent_focus_id);
         }
-        focus_stack.push(frame);
+        self.window.focus_stack.push(focus_handle.id);
 
-        if Some(focus_handle.id) == window.focus {
-            for focus_frame in &mut focus_stack {
-                for listener in focus_frame.key_down_listeners.drain(..) {
-                    self.window_cx.on_keyboard_event(listener);
-                }
-                for listener in focus_frame.key_up_listeners.drain(..) {
-                    self.window_cx.on_keyboard_event(listener);
-                }
-            }
+        if Some(focus_handle.id) == self.window.focus {
+            self.window.key_events_enabled = false;
         }
 
-        self.window.focus_stack = focus_stack;
         let result = f(self);
+
         self.window.focus_stack.pop();
         result
     }

crates/storybook2/src/stories/z_index.rs 🔗

@@ -72,8 +72,7 @@ trait Styles: Styled + Sized {
         self.bg(rgb::<Hsla>(0xe5e8fc))
             .border_5()
             .border_color(rgb::<Hsla>(0x112382))
-            // HACK: Simulate `line-height: 55px`.
-            .pt(px(16.))
+            .line_height(px(55.))
             // HACK: Simulate `text-align: center`.
             .pl(px(24.))
     }
@@ -119,8 +118,7 @@ impl<S: 'static + Send + Sync> ZIndexExample<S> {
                     .text_color(rgb::<Hsla>(0x000000))
                     .border_5()
                     .border_color(rgb::<Hsla>(0xe3e0a1))
-                    // HACK: Simulate `line-height: 215px`.
-                    .pt(px(100.))
+                    .line_height(px(215.))
                     // HACK: Simulate `text-align: center`.
                     .pl(px(24.))
                     .z_index(self.z_index)

crates/storybook2/src/storybook2.rs 🔗

@@ -17,7 +17,7 @@ use log::LevelFilter;
 use simplelog::SimpleLogger;
 use story_selector::ComponentStory;
 use ui::prelude::*;
-use ui::themed;
+use ui::{themed, with_settings, FakeSettings};
 
 use crate::assets::Assets;
 use crate::story_selector::StorySelector;
@@ -68,8 +68,10 @@ fn main() {
             move |cx| {
                 view(
                     cx.entity(|cx| {
-                        cx.with_global(theme.clone(), |cx| {
-                            StoryWrapper::new(selector.story(cx), theme)
+                        cx.with_global(FakeSettings::default(), |cx| {
+                            cx.with_global(theme.clone(), |cx| {
+                                StoryWrapper::new(selector.story(cx), theme)
+                            })
                         })
                     }),
                     StoryWrapper::render,
@@ -85,20 +87,27 @@ fn main() {
 pub struct StoryWrapper {
     story: AnyView,
     theme: Theme,
+    settings: FakeSettings,
 }
 
 impl StoryWrapper {
     pub(crate) fn new(story: AnyView, theme: Theme) -> Self {
-        Self { story, theme }
+        Self {
+            story,
+            theme,
+            settings: FakeSettings::default(),
+        }
     }
 
     fn render(&mut self, cx: &mut ViewContext<Self>) -> impl Element<ViewState = Self> {
-        themed(self.theme.clone(), cx, |cx| {
-            div()
-                .flex()
-                .flex_col()
-                .size_full()
-                .child(self.story.clone())
+        with_settings(self.settings.clone(), cx, |cx| {
+            themed(self.theme.clone(), cx, |cx| {
+                div()
+                    .flex()
+                    .flex_col()
+                    .size_full()
+                    .child(self.story.clone())
+            })
         })
     }
 }

crates/ui2/src/components/assistant_panel.rs 🔗

@@ -29,7 +29,7 @@ impl<S: 'static + Send + Sync + Clone> AssistantPanel<S> {
     fn render(&mut self, view: &mut S, cx: &mut ViewContext<S>) -> impl Element<ViewState = S> {
         let theme = theme(cx);
 
-        Panel::new(self.scroll_state.clone())
+        Panel::new(cx)
             .children(vec![div()
                 .flex()
                 .flex_col()

crates/ui2/src/components/chat_panel.rs 🔗

@@ -138,13 +138,10 @@ mod stories {
             Story::container(cx)
                 .child(Story::title_for::<_, ChatPanel<S>>(cx))
                 .child(Story::label(cx, "Default"))
-                .child(
-                    Panel::new(ScrollState::default())
-                        .child(ChatPanel::new(ScrollState::default())),
-                )
+                .child(Panel::new(cx).child(ChatPanel::new(ScrollState::default())))
                 .child(Story::label(cx, "With Mesages"))
-                .child(Panel::new(ScrollState::default()).child(
-                    ChatPanel::new(ScrollState::default()).messages(vec![
+                .child(
+                    Panel::new(cx).child(ChatPanel::new(ScrollState::default()).messages(vec![
                         ChatMessage::new(
                             "osiewicz".to_string(),
                             "is this thing on?".to_string(),
@@ -159,8 +156,8 @@ mod stories {
                                 .unwrap()
                                 .naive_local(),
                         ),
-                    ]),
-                ))
+                    ])),
+                )
         }
     }
 }

crates/ui2/src/components/icon_button.rs 🔗

@@ -78,8 +78,8 @@ impl<S: 'static + Send + Sync> IconButton<S> {
         let mut button = h_stack()
             .justify_center()
             .rounded_md()
-            .py(ui_size(0.25))
-            .px(ui_size(6. / 14.))
+            .py(ui_size(cx, 0.25))
+            .px(ui_size(cx, 6. / 14.))
             .when(self.variant == ButtonVariant::Filled, |this| {
                 this.bg(color.filled_element)
             })

crates/ui2/src/components/list.rs 🔗

@@ -363,7 +363,7 @@ impl<S: 'static + Send + Sync + Clone> ListEntry<S> {
         let theme = theme(cx);
         let system_color = SystemColor::new();
         let color = ThemeColor::new(cx);
-        let setting = user_settings();
+        let settings = user_settings(cx);
 
         let left_content = match self.left_content.clone() {
             Some(LeftContent::Icon(i)) => Some(
@@ -395,7 +395,7 @@ impl<S: 'static + Send + Sync + Clone> ListEntry<S> {
                     // .ml(rems(0.75 * self.indent_level as f32))
                     .children((0..self.indent_level).map(|_| {
                         div()
-                            .w(*setting.list_indent_depth)
+                            .w(*settings.list_indent_depth)
                             .h_full()
                             .flex()
                             .justify_center()

crates/ui2/src/components/panel.rs 🔗

@@ -53,15 +53,15 @@ pub struct Panel<S: 'static + Send + Sync> {
 }
 
 impl<S: 'static + Send + Sync> Panel<S> {
-    pub fn new(scroll_state: ScrollState) -> Self {
-        let setting = user_settings();
+    pub fn new(cx: &mut WindowContext) -> Self {
+        let settings = user_settings(cx);
 
         Self {
             state_type: PhantomData,
-            scroll_state,
+            scroll_state: ScrollState::default(),
             current_side: PanelSide::default(),
             allowed_sides: PanelAllowedSides::default(),
-            initial_width: *setting.default_panel_size,
+            initial_width: *settings.default_panel_size,
             width: None,
             children: SmallVec::new(),
         }
@@ -175,7 +175,7 @@ mod stories {
                 .child(Story::title_for::<_, Panel<S>>(cx))
                 .child(Story::label(cx, "Default"))
                 .child(
-                    Panel::new(ScrollState::default()).child(
+                    Panel::new(cx).child(
                         div()
                             .overflow_y_scroll(ScrollState::default())
                             .children((0..100).map(|ix| Label::new(format!("Item {}", ix + 1)))),

crates/ui2/src/components/project_panel.rs 🔗

@@ -87,7 +87,7 @@ mod stories {
                 .child(Story::title_for::<_, ProjectPanel<S>>(cx))
                 .child(Story::label(cx, "Default"))
                 .child(
-                    Panel::new(ScrollState::default())
+                    Panel::new(cx)
                         .child(ProjectPanel::new(ScrollState::default())),
                 )
         }

crates/ui2/src/components/title_bar.rs 🔗

@@ -95,7 +95,7 @@ impl TitleBar {
     fn render(&mut self, cx: &mut ViewContext<Self>) -> impl Element<ViewState = Self> {
         let theme = theme(cx);
         let color = ThemeColor::new(cx);
-        let setting = user_settings();
+        let settings = user_settings(cx);
 
         // let has_focus = cx.window_is_active();
         let has_focus = true;
@@ -127,7 +127,7 @@ impl TitleBar {
                             .flex()
                             .items_center()
                             .gap_1()
-                            .when(*setting.titlebar.show_project_owner, |this| {
+                            .when(*settings.titlebar.show_project_owner, |this| {
                                 this.child(Button::new("iamnbutler"))
                             })
                             .child(Button::new("zed"))

crates/ui2/src/components/workspace.rs 🔗

@@ -224,7 +224,7 @@ impl Workspace {
                     .border_color(theme.lowest.base.default.border)
                     .children(
                         Some(
-                            Panel::new(self.left_panel_scroll_state.clone())
+                            Panel::new(cx)
                                 .side(PanelSide::Left)
                                 .child(ProjectPanel::new(ScrollState::default())),
                         )
@@ -232,7 +232,7 @@ impl Workspace {
                     )
                     .children(
                         Some(
-                            Panel::new(self.left_panel_scroll_state.clone())
+                            Panel::new(cx)
                                 .child(CollabPanel::new(ScrollState::default()))
                                 .side(PanelSide::Left),
                         )
@@ -245,7 +245,7 @@ impl Workspace {
                             .child(div().flex().flex_1().child(root_group))
                             .children(
                                 Some(
-                                    Panel::new(self.bottom_panel_scroll_state.clone())
+                                    Panel::new(cx)
                                         .child(Terminal::new())
                                         .allowed_sides(PanelAllowedSides::BottomOnly)
                                         .side(PanelSide::Bottom),
@@ -254,10 +254,8 @@ impl Workspace {
                             ),
                     )
                     .children(
-                        Some(
-                            Panel::new(self.right_panel_scroll_state.clone())
-                                .side(PanelSide::Right)
-                                .child(ChatPanel::new(ScrollState::default()).messages(vec![
+                        Some(Panel::new(cx).side(PanelSide::Right).child(
+                            ChatPanel::new(ScrollState::default()).messages(vec![
                                     ChatMessage::new(
                                         "osiewicz".to_string(),
                                         "is this thing on?".to_string(),
@@ -272,24 +270,21 @@ impl Workspace {
                                             .unwrap()
                                             .naive_local(),
                                     ),
-                                ])),
-                        )
+                                ]),
+                        ))
                         .filter(|_| self.is_chat_panel_open()),
                     )
                     .children(
                         Some(
-                            Panel::new(self.right_panel_scroll_state.clone())
+                            Panel::new(cx)
                                 .side(PanelSide::Right)
                                 .child(div().w_96().h_full().child("Notifications")),
                         )
                         .filter(|_| self.is_notifications_panel_open()),
                     )
                     .children(
-                        Some(
-                            Panel::new(self.right_panel_scroll_state.clone())
-                                .child(AssistantPanel::new()),
-                        )
-                        .filter(|_| self.is_assistant_panel_open()),
+                        Some(Panel::new(cx).child(AssistantPanel::new()))
+                            .filter(|_| self.is_assistant_panel_open()),
                     ),
             )
             .child(StatusBar::new())

crates/ui2/src/elements/button.rs 🔗

@@ -149,11 +149,11 @@ impl<S: 'static + Send + Sync + Clone> Button<S> {
     fn render(&mut self, _view: &mut S, cx: &mut ViewContext<S>) -> impl Element<ViewState = S> {
         let icon_color = self.icon_color();
         let border_color = self.border_color(cx);
-        let setting = user_settings();
+        let settings = user_settings(cx);
 
         let mut el = h_stack()
             .p_1()
-            .text_size(ui_size(1.))
+            .text_size(ui_size(cx, 1.))
             .rounded_md()
             .border()
             .border_color(border_color)

crates/ui2/src/elements/icon.rs 🔗

@@ -180,8 +180,8 @@ impl<S: 'static + Send + Sync> IconElement<S> {
         let theme = theme(cx);
         let fill = self.color.color(theme);
         let svg_size = match self.size {
-            IconSize::Small => ui_size(12. / 14.),
-            IconSize::Medium => ui_size(15. / 14.),
+            IconSize::Small => ui_size(cx, 12. / 14.),
+            IconSize::Medium => ui_size(cx, 15. / 14.),
         };
 
         svg()

crates/ui2/src/elements/label.rs 🔗

@@ -96,7 +96,7 @@ impl<S: 'static + Send + Sync + Clone> Label<S> {
                         .bg(LabelColor::Hidden.hsla(cx)),
                 )
             })
-            .text_size(ui_size(1.))
+            .text_size(ui_size(cx, 1.))
             .when(self.line_height_style == LineHeightStyle::UILabel, |this| {
                 this.line_height(relative(1.))
             })

crates/ui2/src/lib.rs 🔗

@@ -14,6 +14,14 @@ pub use elements::*;
 pub use prelude::*;
 pub use static_data::*;
 
+// This needs to be fully qualified with `crate::` otherwise we get a panic
+// at:
+//   thread '<unnamed>' panicked at crates/gpui3/src/platform/mac/platform.rs:66:81:
+//   called `Option::unwrap()` on a `None` value
+//
+// AFAICT this is something to do with conflicting names between crates and modules that
+// interfaces with declaring the `ClassDecl`.
+pub use crate::settings::*;
 pub use crate::theme::*;
 
 #[cfg(feature = "stories")]

crates/ui2/src/prelude.rs 🔗

@@ -142,12 +142,12 @@ impl HighlightColor {
     }
 }
 
-pub fn ui_size(size: f32) -> Rems {
+pub fn ui_size(cx: &mut WindowContext, size: f32) -> Rems {
     const UI_SCALE_RATIO: f32 = 0.875;
 
-    let setting = user_settings();
+    let settings = user_settings(cx);
 
-    rems(*setting.ui_scale * UI_SCALE_RATIO * size)
+    rems(*settings.ui_scale * UI_SCALE_RATIO * size)
 }
 
 #[derive(Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, EnumIter)]

crates/ui2/src/settings.rs 🔗

@@ -1,15 +1,14 @@
 use std::ops::Deref;
 
-use gpui3::{rems, AbsoluteLength};
+use gpui3::{
+    rems, AbsoluteLength, AnyElement, BorrowAppContext, Bounds, LayoutId, Pixels, WindowContext,
+};
 
-use crate::DisclosureControlStyle;
+use crate::prelude::*;
 
-// This is a fake static example of user settings overriding the default settings
-pub fn user_settings() -> Settings {
-    let mut settings = Settings::default();
-    settings.list_indent_depth = SettingValue::UserDefined(rems(0.5).into());
-    // settings.ui_scale = SettingValue::UserDefined(2.);
-    settings
+/// Returns the user settings.
+pub fn user_settings(cx: &WindowContext) -> FakeSettings {
+    cx.global::<FakeSettings>().clone()
 }
 
 #[derive(Clone)]
@@ -48,7 +47,7 @@ impl Default for TitlebarSettings {
 
 // These should be merged into settings
 #[derive(Clone)]
-pub struct Settings {
+pub struct FakeSettings {
     pub default_panel_size: SettingValue<AbsoluteLength>,
     pub list_disclosure_style: SettingValue<DisclosureControlStyle>,
     pub list_indent_depth: SettingValue<AbsoluteLength>,
@@ -56,7 +55,7 @@ pub struct Settings {
     pub ui_scale: SettingValue<f32>,
 }
 
-impl Default for Settings {
+impl Default for FakeSettings {
     fn default() -> Self {
         Self {
             titlebar: TitlebarSettings::default(),
@@ -68,4 +67,79 @@ impl Default for Settings {
     }
 }
 
-impl Settings {}
+impl FakeSettings {}
+
+pub fn with_settings<E, F>(
+    settings: FakeSettings,
+    cx: &mut ViewContext<E::ViewState>,
+    build_child: F,
+) -> WithSettings<E>
+where
+    E: Element,
+    F: FnOnce(&mut ViewContext<E::ViewState>) -> E,
+{
+    let child = cx.with_global(theme.clone(), |cx| build_child(cx));
+    WithSettings { settings, child }
+}
+
+pub struct WithSettings<E> {
+    pub(crate) settings: FakeSettings,
+    pub(crate) child: E,
+}
+
+impl<E> IntoAnyElement<E::ViewState> for WithSettings<E>
+where
+    E: Element,
+{
+    fn into_any(self) -> AnyElement<E::ViewState> {
+        AnyElement::new(self)
+    }
+}
+
+impl<E: Element> Element for WithSettings<E> {
+    type ViewState = E::ViewState;
+    type ElementState = E::ElementState;
+
+    fn id(&self) -> Option<gpui3::ElementId> {
+        None
+    }
+
+    fn initialize(
+        &mut self,
+        view_state: &mut Self::ViewState,
+        element_state: Option<Self::ElementState>,
+        cx: &mut ViewContext<Self::ViewState>,
+    ) -> Self::ElementState {
+        cx.with_global(self.settings.clone(), |cx| {
+            self.child.initialize(view_state, element_state, cx)
+        })
+    }
+
+    fn layout(
+        &mut self,
+        view_state: &mut E::ViewState,
+        element_state: &mut Self::ElementState,
+        cx: &mut ViewContext<E::ViewState>,
+    ) -> LayoutId
+    where
+        Self: Sized,
+    {
+        cx.with_global(self.settings.clone(), |cx| {
+            self.child.layout(view_state, element_state, cx)
+        })
+    }
+
+    fn paint(
+        &mut self,
+        bounds: Bounds<Pixels>,
+        view_state: &mut Self::ViewState,
+        frame_state: &mut Self::ElementState,
+        cx: &mut ViewContext<Self::ViewState>,
+    ) where
+        Self: Sized,
+    {
+        cx.with_global(self.settings.clone(), |cx| {
+            self.child.paint(bounds, view_state, frame_state, cx);
+        });
+    }
+}