Checkpoint

Nathan Sobo created

Change summary

crates/gpui3/src/app.rs                           | 64 ++++++++++
crates/gpui3/src/elements/div.rs                  |  2 
crates/gpui3/src/elements/text.rs                 | 17 +-
crates/gpui3/src/gpui3.rs                         | 24 ++++
crates/gpui3/src/platform.rs                      |  4 
crates/gpui3/src/platform/mac/text_system.rs      | 40 +-----
crates/gpui3/src/text_system.rs                   | 70 ++++++++---
crates/gpui3/src/text_system/line_wrapper.rs      | 61 +++++-----
crates/gpui3/src/text_system/text_layout_cache.rs | 41 ++----
crates/gpui3/src/window.rs                        | 94 +++++-----------
crates/storybook2/src/collab_panel.rs             |  4 
crates/storybook2/src/element_ext.rs              | 17 ---
crates/storybook2/src/storybook2.rs               | 53 ---------
crates/storybook2/src/theme.rs                    | 25 ++-
crates/storybook2/src/workspace.rs                | 79 +++++++------
15 files changed, 287 insertions(+), 308 deletions(-)

Detailed changes

crates/gpui3/src/app.rs 🔗

@@ -5,10 +5,11 @@ mod model_context;
 pub use async_context::*;
 pub use entity_map::*;
 pub use model_context::*;
+use refineable::Refineable;
 
 use crate::{
-    current_platform, Context, LayoutId, MainThreadOnly, Platform, RootView, TextSystem, Window,
-    WindowContext, WindowHandle, WindowId,
+    current_platform, Context, LayoutId, MainThreadOnly, Platform, RootView, TextStyle,
+    TextStyleRefinement, TextSystem, Window, WindowContext, WindowHandle, WindowId,
 };
 use anyhow::{anyhow, Result};
 use collections::{HashMap, VecDeque};
@@ -16,7 +17,10 @@ use futures::{future, Future};
 use parking_lot::Mutex;
 use slotmap::SlotMap;
 use smallvec::SmallVec;
-use std::sync::{Arc, Weak};
+use std::{
+    any::{type_name, Any, TypeId},
+    sync::{Arc, Weak},
+};
 use util::ResultExt;
 
 #[derive(Clone)]
@@ -42,10 +46,12 @@ impl App {
                 this: this.clone(),
                 platform: MainThreadOnly::new(platform, dispatcher),
                 text_system,
+                pending_updates: 0,
+                text_style_stack: Vec::new(),
+                state_stacks_by_type: HashMap::default(),
                 unit_entity,
                 entities,
                 windows: SlotMap::with_key(),
-                pending_updates: 0,
                 pending_effects: Default::default(),
                 observers: Default::default(),
                 layout_id_buffer: Default::default(),
@@ -73,6 +79,8 @@ pub struct AppContext {
     platform: MainThreadOnly<dyn Platform>,
     text_system: Arc<TextSystem>,
     pending_updates: usize,
+    pub(crate) text_style_stack: Vec<TextStyleRefinement>,
+    pub(crate) state_stacks_by_type: HashMap<TypeId, Vec<Box<dyn Any + Send + Sync>>>,
     pub(crate) unit_entity: Handle<()>,
     pub(crate) entities: EntityMap,
     pub(crate) windows: SlotMap<WindowId, Option<Window>>,
@@ -121,6 +129,54 @@ impl AppContext {
         })
     }
 
+    pub fn text_style(&self) -> TextStyle {
+        let mut style = TextStyle::default();
+        for refinement in &self.text_style_stack {
+            style.refine(refinement);
+        }
+        style
+    }
+
+    pub fn state<S: 'static>(&self) -> &S {
+        self.state_stacks_by_type
+            .get(&TypeId::of::<S>())
+            .and_then(|stack| stack.last())
+            .and_then(|any_state| any_state.downcast_ref::<S>())
+            .ok_or_else(|| anyhow!("no state of type {} exists", type_name::<S>()))
+            .unwrap()
+    }
+
+    pub fn state_mut<S: 'static>(&mut self) -> &mut S {
+        self.state_stacks_by_type
+            .get_mut(&TypeId::of::<S>())
+            .and_then(|stack| stack.last_mut())
+            .and_then(|any_state| any_state.downcast_mut::<S>())
+            .ok_or_else(|| anyhow!("no state of type {} exists", type_name::<S>()))
+            .unwrap()
+    }
+
+    pub(crate) fn push_text_style(&mut self, text_style: TextStyleRefinement) {
+        self.text_style_stack.push(text_style);
+    }
+
+    pub(crate) fn pop_text_style(&mut self) {
+        self.text_style_stack.pop();
+    }
+
+    pub(crate) fn push_state<T: Send + Sync + 'static>(&mut self, state: T) {
+        self.state_stacks_by_type
+            .entry(TypeId::of::<T>())
+            .or_default()
+            .push(Box::new(state));
+    }
+
+    pub(crate) fn pop_state<T: 'static>(&mut self) {
+        self.state_stacks_by_type
+            .get_mut(&TypeId::of::<T>())
+            .and_then(|stack| stack.pop())
+            .expect("state stack underflow");
+    }
+
     pub(crate) fn update_window<R>(
         &mut self,
         id: WindowId,

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

@@ -1,6 +1,6 @@
 use crate::{
     AnyElement, Bounds, Element, Layout, LayoutId, Overflow, ParentElement, Pixels, Point,
-    Refineable, RefinementCascade, Result, Style, StyleHelpers, Styled, ViewContext,
+    Refineable, RefinementCascade, Result, StackContext, Style, StyleHelpers, Styled, ViewContext,
 };
 use parking_lot::Mutex;
 use smallvec::SmallVec;

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

@@ -3,7 +3,7 @@ use crate::{
 };
 use parking_lot::Mutex;
 use std::{marker::PhantomData, sync::Arc};
-use util::arc_cow::ArcCow;
+use util::{arc_cow::ArcCow, ResultExt};
 
 impl<S: 'static> IntoAnyElement<S> for ArcCow<'static, str> {
     fn into_any(self) -> AnyElement<S> {
@@ -52,11 +52,16 @@ impl<S: 'static> Element for Text<S> {
         let layout_id = cx.request_measured_layout(Default::default(), rem_size, {
             let frame_state = paint_state.clone();
             move |_, _| {
-                let line_layout = text_system.layout_str(
-                    text.as_ref(),
-                    font_size,
-                    &[(text.len(), text_style.to_run())],
-                );
+                let Some(line_layout) = text_system
+                    .layout_line(
+                        text.as_ref(),
+                        font_size,
+                        &[(text.len(), text_style.to_run())],
+                    )
+                    .log_err()
+                else {
+                    return Size::default();
+                };
 
                 let size = Size {
                     width: line_layout.width(),

crates/gpui3/src/gpui3.rs 🔗

@@ -61,6 +61,30 @@ pub trait Context {
     ) -> Self::Result<R>;
 }
 
+pub trait StackContext {
+    fn app(&mut self) -> &mut AppContext;
+
+    fn with_text_style<F, R>(&mut self, style: TextStyleRefinement, f: F) -> R
+    where
+        F: FnOnce(&mut Self) -> R,
+    {
+        self.app().push_text_style(style);
+        let result = f(self);
+        self.app().pop_text_style();
+        result
+    }
+
+    fn with_state<T: Send + Sync + 'static, F, R>(&mut self, state: T, f: F) -> R
+    where
+        F: FnOnce(&mut Self) -> R,
+    {
+        self.app().push_state(state);
+        let result = f(self);
+        self.app().pop_state::<T>();
+        result
+    }
+}
+
 pub trait Flatten<T> {
     fn flatten(self) -> Result<T>;
 }

crates/gpui3/src/platform.rs 🔗

@@ -7,7 +7,7 @@ mod test;
 
 use crate::{
     AnyWindowHandle, Bounds, Font, FontId, FontMetrics, GlyphId, LineLayout, Pixels, Point, Result,
-    RunStyle, Scene, SharedString, Size,
+    Scene, SharedString, Size,
 };
 use anyhow::anyhow;
 use async_task::Runnable;
@@ -170,7 +170,7 @@ pub trait PlatformTextSystem: Send + Sync {
         scale_factor: f32,
         options: RasterizationOptions,
     ) -> Option<(Bounds<u32>, Vec<u8>)>;
-    fn layout_line(&self, text: &str, font_size: Pixels, runs: &[(usize, RunStyle)]) -> LineLayout;
+    fn layout_line(&self, text: &str, font_size: Pixels, runs: &[(usize, FontId)]) -> LineLayout;
     fn wrap_line(
         &self,
         text: &str,

crates/gpui3/src/platform/mac/text_system.rs 🔗

@@ -1,7 +1,7 @@
 use crate::{
     point, px, size, Bounds, Font, FontFeatures, FontId, FontMetrics, FontStyle, FontWeight, Glyph,
     GlyphId, LineLayout, Pixels, PlatformTextSystem, Point, RasterizationOptions, Result, Run,
-    RunStyle, SharedString, Size,
+    SharedString, Size,
 };
 use cocoa::appkit::{CGFloat, CGPoint};
 use collections::HashMap;
@@ -33,7 +33,7 @@ use pathfinder_geometry::{
     vector::{Vector2F, Vector2I},
 };
 use smallvec::SmallVec;
-use std::{cell::RefCell, char, cmp, convert::TryFrom, ffi::c_void, sync::Arc};
+use std::{char, cmp, convert::TryFrom, ffi::c_void, sync::Arc};
 
 use super::open_type;
 
@@ -156,8 +156,13 @@ impl PlatformTextSystem for MacTextSystem {
         )
     }
 
-    fn layout_line(&self, text: &str, font_size: Pixels, runs: &[(usize, RunStyle)]) -> LineLayout {
-        self.0.write().layout_line(text, font_size, runs)
+    fn layout_line(
+        &self,
+        text: &str,
+        font_size: Pixels,
+        font_runs: &[(usize, FontId)],
+    ) -> LineLayout {
+        self.0.write().layout_line(text, font_size, font_runs)
     }
 
     fn wrap_line(
@@ -342,7 +347,7 @@ impl MacTextSystemState {
         &mut self,
         text: &str,
         font_size: Pixels,
-        runs: &[(usize, RunStyle)],
+        font_runs: &[(usize, FontId)],
     ) -> LineLayout {
         // Construct the attributed string, converting UTF8 ranges to UTF16 ranges.
         let mut string = CFMutableAttributedString::new();
@@ -350,30 +355,8 @@ impl MacTextSystemState {
             string.replace_str(&CFString::new(text), CFRange::init(0, 0));
             let utf16_line_len = string.char_len() as usize;
 
-            let last_run: RefCell<Option<(usize, Font)>> = Default::default();
-            let font_runs = runs
-                .iter()
-                .filter_map(|(len, style)| {
-                    let mut last_run = last_run.borrow_mut();
-                    if let Some((last_len, last_font)) = last_run.as_mut() {
-                        if style.font == *last_font {
-                            *last_len += *len;
-                            None
-                        } else {
-                            let result = (*last_len, last_font.clone());
-                            *last_len = *len;
-                            *last_font = style.font.clone();
-                            Some(result)
-                        }
-                    } else {
-                        *last_run = Some((*len, style.font.clone()));
-                        None
-                    }
-                })
-                .chain(std::iter::from_fn(|| last_run.borrow_mut().take()));
-
             let mut ix_converter = StringIndexConverter::new(text);
-            for (run_len, font_descriptor) in font_runs {
+            for (run_len, font_id) in font_runs {
                 let utf8_end = ix_converter.utf8_ix + run_len;
                 let utf16_start = ix_converter.utf16_ix;
 
@@ -387,7 +370,6 @@ impl MacTextSystemState {
                 let cf_range =
                     CFRange::init(utf16_start as isize, (utf16_end - utf16_start) as isize);
 
-                let font_id = self.font_selections[&font_descriptor];
                 let font: &FontKitFont = &self.fonts[font_id.0];
                 unsafe {
                     string.set_attribute(

crates/gpui3/src/text_system.rs 🔗

@@ -32,7 +32,8 @@ pub struct TextSystem {
     font_ids_by_font: RwLock<HashMap<Font, FontId>>,
     fonts_by_font_id: RwLock<HashMap<FontId, Font>>,
     font_metrics: RwLock<HashMap<Font, FontMetrics>>,
-    wrapper_pool: Mutex<HashMap<FontWithSize, Vec<LineWrapper>>>,
+    wrapper_pool: Mutex<HashMap<FontIdWithSize, Vec<LineWrapper>>>,
+    font_runs_pool: Mutex<Vec<Vec<(usize, FontId)>>>,
 }
 
 impl TextSystem {
@@ -44,6 +45,7 @@ impl TextSystem {
             font_ids_by_font: RwLock::new(HashMap::default()),
             fonts_by_font_id: RwLock::new(HashMap::default()),
             wrapper_pool: Mutex::new(HashMap::default()),
+            font_runs_pool: Default::default(),
         }
     }
 
@@ -147,41 +149,67 @@ impl TextSystem {
         }
     }
 
-    pub fn layout_str<'a>(
-        &'a self,
-        text: &'a str,
+    pub fn layout_line(
+        &self,
+        text: &str,
         font_size: Pixels,
-        runs: &'a [(usize, RunStyle)],
-    ) -> Line {
-        self.text_layout_cache.layout_str(text, font_size, runs)
+        runs: &[(usize, RunStyle)],
+    ) -> Result<Line> {
+        let mut font_runs = self.font_runs_pool.lock().pop().unwrap_or_default();
+        let mut last_font: Option<&Font> = None;
+        for (len, style) in runs {
+            if let Some(last_font) = last_font.as_ref() {
+                if **last_font == style.font {
+                    font_runs.last_mut().unwrap().0 += len;
+                    continue;
+                }
+            }
+            last_font = Some(&style.font);
+            font_runs.push((*len, self.font_id(&style.font)?));
+        }
+
+        let layout = self
+            .text_layout_cache
+            .layout_line(text, font_size, &font_runs);
+
+        font_runs.clear();
+        self.font_runs_pool.lock().push(font_runs);
+
+        Ok(Line::new(layout.clone(), runs))
     }
 
     pub fn finish_frame(&self) {
         self.text_layout_cache.finish_frame()
     }
 
-    pub fn line_wrapper(self: &Arc<Self>, font: Font, font_size: Pixels) -> LineWrapperHandle {
+    pub fn line_wrapper(
+        self: &Arc<Self>,
+        font: Font,
+        font_size: Pixels,
+    ) -> Result<LineWrapperHandle> {
         let lock = &mut self.wrapper_pool.lock();
+        let font_id = self.font_id(&font)?;
         let wrappers = lock
-            .entry(FontWithSize {
-                font: font.clone(),
-                font_size,
-            })
+            .entry(FontIdWithSize { font_id, font_size })
             .or_default();
-        let wrapper = wrappers.pop().unwrap_or_else(|| {
-            LineWrapper::new(font, font_size, self.platform_text_system.clone())
-        });
+        let wrapper = wrappers.pop().map(anyhow::Ok).unwrap_or_else(|| {
+            Ok(LineWrapper::new(
+                font_id,
+                font_size,
+                self.platform_text_system.clone(),
+            ))
+        })?;
 
-        LineWrapperHandle {
+        Ok(LineWrapperHandle {
             wrapper: Some(wrapper),
             text_system: self.clone(),
-        }
+        })
     }
 }
 
 #[derive(Hash, Eq, PartialEq)]
-struct FontWithSize {
-    font: Font,
+struct FontIdWithSize {
+    font_id: FontId,
     font_size: Pixels,
 }
 
@@ -195,8 +223,8 @@ impl Drop for LineWrapperHandle {
         let mut state = self.text_system.wrapper_pool.lock();
         let wrapper = self.wrapper.take().unwrap();
         state
-            .get_mut(&FontWithSize {
-                font: wrapper.font.clone(),
+            .get_mut(&FontIdWithSize {
+                font_id: wrapper.font_id.clone(),
                 font_size: wrapper.font_size,
             })
             .unwrap()

crates/gpui3/src/text_system/line_wrapper.rs 🔗

@@ -1,10 +1,10 @@
-use crate::{px, Font, Line, Pixels, PlatformTextSystem, RunStyle, ShapedBoundary};
+use crate::{px, FontId, Line, Pixels, PlatformTextSystem, ShapedBoundary};
 use collections::HashMap;
 use std::{iter, sync::Arc};
 
 pub struct LineWrapper {
-    text_system: Arc<dyn PlatformTextSystem>,
-    pub(crate) font: Font,
+    platform_text_system: Arc<dyn PlatformTextSystem>,
+    pub(crate) font_id: FontId,
     pub(crate) font_size: Pixels,
     cached_ascii_char_widths: [Option<Pixels>; 128],
     cached_other_char_widths: HashMap<char, Pixels>,
@@ -13,10 +13,14 @@ pub struct LineWrapper {
 impl LineWrapper {
     pub const MAX_INDENT: u32 = 256;
 
-    pub fn new(font: Font, font_size: Pixels, text_system: Arc<dyn PlatformTextSystem>) -> Self {
+    pub fn new(
+        font_id: FontId,
+        font_size: Pixels,
+        text_system: Arc<dyn PlatformTextSystem>,
+    ) -> Self {
         Self {
-            text_system,
-            font,
+            platform_text_system: text_system,
+            font_id,
             font_size,
             cached_ascii_char_widths: [None; 128],
             cached_other_char_widths: HashMap::default(),
@@ -178,19 +182,8 @@ impl LineWrapper {
     }
 
     fn compute_width_for_char(&self, c: char) -> Pixels {
-        self.text_system
-            .layout_line(
-                &c.to_string(),
-                self.font_size,
-                &[(
-                    1,
-                    RunStyle {
-                        font: self.font.clone(),
-                        color: Default::default(),
-                        underline: Default::default(),
-                    },
-                )],
-            )
+        self.platform_text_system
+            .layout_line(&c.to_string(), self.font_size, &[(1, self.font_id)])
             .width
     }
 }
@@ -210,14 +203,14 @@ impl Boundary {
 #[cfg(test)]
 mod tests {
     use super::*;
-    use crate::{font, App};
+    use crate::{font, App, RunStyle};
 
     #[test]
     fn test_wrap_line() {
         App::test().run(|cx| {
             let text_system = cx.text_system().clone();
             let mut wrapper = LineWrapper::new(
-                font("Courier"),
+                text_system.font_id(&font("Courier")).unwrap(),
                 px(16.),
                 text_system.platform_text_system.clone(),
             );
@@ -293,20 +286,22 @@ mod tests {
             };
 
             let text = "aa bbb cccc ddddd eeee";
-            let line = text_system.layout_str(
-                text,
-                px(16.),
-                &[
-                    (4, normal.clone()),
-                    (5, bold.clone()),
-                    (6, normal.clone()),
-                    (1, bold.clone()),
-                    (7, normal.clone()),
-                ],
-            );
+            let line = text_system
+                .layout_line(
+                    text,
+                    px(16.),
+                    &[
+                        (4, normal.clone()),
+                        (5, bold.clone()),
+                        (6, normal.clone()),
+                        (1, bold.clone()),
+                        (7, normal.clone()),
+                    ],
+                )
+                .unwrap();
 
             let mut wrapper = LineWrapper::new(
-                normal.font,
+                text_system.font_id(&normal.font).unwrap(),
                 px(16.),
                 text_system.platform_text_system.clone(),
             );

crates/gpui3/src/text_system/text_layout_cache.rs 🔗

@@ -15,7 +15,7 @@ use std::{
 pub(crate) struct TextLayoutCache {
     prev_frame: Mutex<HashMap<CacheKeyValue, Arc<LineLayout>>>,
     curr_frame: RwLock<HashMap<CacheKeyValue, Arc<LineLayout>>>,
-    fonts: Arc<dyn PlatformTextSystem>,
+    platform_text_system: Arc<dyn PlatformTextSystem>,
 }
 
 impl TextLayoutCache {
@@ -23,7 +23,7 @@ impl TextLayoutCache {
         Self {
             prev_frame: Mutex::new(HashMap::new()),
             curr_frame: RwLock::new(HashMap::new()),
-            fonts,
+            platform_text_system: fonts,
         }
     }
 
@@ -34,12 +34,12 @@ impl TextLayoutCache {
         curr_frame.clear();
     }
 
-    pub fn layout_str<'a>(
+    pub fn layout_line<'a>(
         &'a self,
         text: &'a str,
         font_size: Pixels,
-        runs: &'a [(usize, RunStyle)],
-    ) -> Line {
+        runs: &[(usize, FontId)],
+    ) -> Arc<LineLayout> {
         let key = &CacheKeyRef {
             text,
             font_size,
@@ -47,22 +47,22 @@ impl TextLayoutCache {
         } as &dyn CacheKey;
         let curr_frame = self.curr_frame.upgradable_read();
         if let Some(layout) = curr_frame.get(key) {
-            return Line::new(layout.clone(), runs);
+            return layout.clone();
         }
 
         let mut curr_frame = RwLockUpgradableReadGuard::upgrade(curr_frame);
         if let Some((key, layout)) = self.prev_frame.lock().remove_entry(key) {
             curr_frame.insert(key, layout.clone());
-            Line::new(layout, runs)
+            layout
         } else {
-            let layout = Arc::new(self.fonts.layout_line(text, font_size, runs));
+            let layout = Arc::new(self.platform_text_system.layout_line(text, font_size, runs));
             let key = CacheKeyValue {
                 text: text.into(),
                 font_size,
                 runs: SmallVec::from(runs),
             };
             curr_frame.insert(key, layout.clone());
-            Line::new(layout, runs)
+            layout
         }
     }
 }
@@ -89,7 +89,7 @@ impl<'a> Hash for (dyn CacheKey + 'a) {
 struct CacheKeyValue {
     text: String,
     font_size: Pixels,
-    runs: SmallVec<[(usize, RunStyle); 1]>,
+    runs: SmallVec<[(usize, FontId); 1]>,
 }
 
 impl CacheKey for CacheKeyValue {
@@ -120,11 +120,11 @@ impl<'a> Borrow<dyn CacheKey + 'a> for CacheKeyValue {
     }
 }
 
-#[derive(Copy, Clone)]
+#[derive(Copy, Clone, PartialEq, Eq)]
 struct CacheKeyRef<'a> {
     text: &'a str,
     font_size: Pixels,
-    runs: &'a [(usize, RunStyle)],
+    runs: &'a [(usize, FontId)],
 }
 
 impl<'a> CacheKey for CacheKeyRef<'a> {
@@ -133,26 +133,13 @@ impl<'a> CacheKey for CacheKeyRef<'a> {
     }
 }
 
-impl<'a> PartialEq for CacheKeyRef<'a> {
-    fn eq(&self, other: &Self) -> bool {
-        self.text == other.text
-            && self.font_size == other.font_size
-            && self.runs.len() == other.runs.len()
-            && self.runs.iter().zip(other.runs.iter()).all(
-                |((len_a, style_a), (len_b, style_b))| {
-                    len_a == len_b && style_a.font == style_b.font
-                },
-            )
-    }
-}
-
 impl<'a> Hash for CacheKeyRef<'a> {
     fn hash<H: Hasher>(&self, state: &mut H) {
         self.text.hash(state);
         self.font_size.hash(state);
-        for (len, style_id) in self.runs {
+        for (len, font_id) in self.runs {
             len.hash(state);
-            style_id.font.hash(state);
+            font_id.hash(state);
         }
     }
 }

crates/gpui3/src/window.rs 🔗

@@ -1,18 +1,11 @@
 use crate::{
     px, AnyView, AppContext, AvailableSpace, Bounds, Context, Effect, Element, EntityId, Handle,
     LayoutId, MainThreadOnly, Pixels, Platform, PlatformWindow, Point, Reference, Scene, Size,
-    Style, TaffyLayoutEngine, TextStyle, TextStyleRefinement, WeakHandle, WindowOptions,
+    StackContext, Style, TaffyLayoutEngine, WeakHandle, WindowOptions,
 };
 use anyhow::Result;
-use collections::HashMap;
 use derive_more::{Deref, DerefMut};
-use refineable::Refineable;
-use std::{
-    any::{Any, TypeId},
-    future,
-    marker::PhantomData,
-    sync::Arc,
-};
+use std::{any::TypeId, future, marker::PhantomData, sync::Arc};
 use util::ResultExt;
 
 pub struct AnyWindow {}
@@ -23,8 +16,6 @@ pub struct Window {
     rem_size: Pixels,
     content_size: Size<Pixels>,
     layout_engine: TaffyLayoutEngine,
-    text_style_stack: Vec<TextStyleRefinement>,
-    state_stacks_by_type: HashMap<TypeId, Vec<Box<dyn Any + Send + Sync>>>,
     pub(crate) root_view: Option<AnyView<()>>,
     mouse_position: Point<Pixels>,
     pub(crate) scene: Scene,
@@ -63,8 +54,6 @@ impl Window {
             rem_size: px(16.),
             content_size,
             layout_engine: TaffyLayoutEngine::new(),
-            text_style_stack: Vec::new(),
-            state_stacks_by_type: HashMap::default(),
             root_view: None,
             mouse_position,
             scene: Scene::new(scale_factor),
@@ -89,13 +78,6 @@ impl<'a, 'w> WindowContext<'a, 'w> {
         }
     }
 
-    // pub(crate) fn immutable(app: &'a AppContext, window: &'w Window) -> Self {
-    //     Self {
-    //         app: Reference::Immutable(app),
-    //         window: Reference::Immutable(window),
-    //     }
-    // }
-
     pub(crate) fn draw(&mut self) -> Result<()> {
         let unit_entity = self.unit_entity.clone();
         self.update_entity(&unit_entity, |_, cx| {
@@ -158,41 +140,6 @@ impl<'a, 'w> WindowContext<'a, 'w> {
         self.window.rem_size
     }
 
-    pub fn push_cascading_state<T: Send + Sync + 'static>(&mut self, theme: T) {
-        self.window
-            .state_stacks_by_type
-            .entry(TypeId::of::<T>())
-            .or_default()
-            .push(Box::new(theme));
-    }
-
-    pub fn pop_cascading_state<T: Send + Sync + 'static>(&mut self) {
-        self.window
-            .state_stacks_by_type
-            .get_mut(&TypeId::of::<T>())
-            .and_then(|stack| stack.pop())
-            .expect("cascading state not found");
-    }
-
-    pub fn cascading_state<T: Send + Sync + 'static>(&self) -> &T {
-        let type_id = TypeId::of::<T>();
-        self.window
-            .state_stacks_by_type
-            .get(&type_id)
-            .and_then(|stack| stack.last())
-            .expect("no cascading state of the specified type has been pushed")
-            .downcast_ref::<T>()
-            .unwrap()
-    }
-
-    pub fn text_style(&self) -> TextStyle {
-        let mut style = TextStyle::default();
-        for refinement in &self.window.text_style_stack {
-            style.refine(refinement);
-        }
-        style
-    }
-
     pub fn mouse_position(&self) -> Point<Pixels> {
         self.window.mouse_position
     }
@@ -230,6 +177,32 @@ impl Context for WindowContext<'_, '_> {
     }
 }
 
+impl<S> StackContext for ViewContext<'_, '_, S> {
+    fn app(&mut self) -> &mut AppContext {
+        &mut *self.app
+    }
+
+    fn with_text_style<F, R>(&mut self, style: crate::TextStyleRefinement, f: F) -> R
+    where
+        F: FnOnce(&mut Self) -> R,
+    {
+        self.push_text_style(style);
+        let result = f(self);
+        self.pop_text_style();
+        result
+    }
+
+    fn with_state<T: Send + Sync + 'static, F, R>(&mut self, state: T, f: F) -> R
+    where
+        F: FnOnce(&mut Self) -> R,
+    {
+        self.push_state(state);
+        let result = f(self);
+        self.pop_state::<T>();
+        result
+    }
+}
+
 #[derive(Deref, DerefMut)]
 pub struct ViewContext<'a, 'w, S> {
     #[deref]
@@ -252,17 +225,6 @@ impl<'a, 'w, S: Send + Sync + 'static> ViewContext<'a, 'w, S> {
         self.entities.weak_handle(self.entity_id)
     }
 
-    pub fn with_text_style<R>(
-        &mut self,
-        style: TextStyleRefinement,
-        f: impl FnOnce(&mut Self) -> R,
-    ) -> R {
-        self.window.text_style_stack.push(style);
-        let result = f(self);
-        self.window.text_style_stack.pop();
-        result
-    }
-
     pub fn observe<E: Send + Sync + 'static>(
         &mut self,
         handle: &Handle<E>,

crates/storybook2/src/collab_panel.rs 🔗

@@ -9,9 +9,7 @@ pub struct CollabPanel {
 }
 
 pub fn collab_panel<S: 'static>(cx: &mut WindowContext) -> View<CollabPanel, S> {
-    view(cx.entity(|cx| CollabPanel::new(cx)), |panel, cx| {
-        panel.render(cx)
-    })
+    view(cx.entity(|cx| CollabPanel::new(cx)), CollabPanel::render)
 }
 
 impl CollabPanel {

crates/storybook2/src/element_ext.rs 🔗

@@ -1,17 +0,0 @@
-use crate::theme::{Theme, Themed};
-use gpui3::Element;
-
-pub trait ElementExt: Element {
-    fn themed(self, theme: Theme) -> Themed<Self>
-    where
-        Self: Sized;
-}
-
-impl<E: Element> ElementExt for E {
-    fn themed(self, theme: Theme) -> Themed<Self>
-    where
-        Self: Sized,
-    {
-        Themed { child: self, theme }
-    }
-}

crates/storybook2/src/storybook2.rs 🔗

@@ -1,15 +1,9 @@
 #![allow(dead_code, unused_variables)]
 
-use crate::theme::Theme;
-use element_ext::ElementExt;
-use gpui3::{Element, ViewContext};
-
 use log::LevelFilter;
 use simplelog::SimpleLogger;
 
 mod collab_panel;
-// mod components;
-mod element_ext;
 mod theme;
 mod themes;
 mod workspace;
@@ -25,53 +19,6 @@ fn main() {
     gpui3::App::production().run(|cx| {
         let window = cx.open_window(Default::default(), |cx| workspace(cx));
     });
-
-    // gpui3::App::new(Assets).unwrap().run(|cx| {
-    //     let mut store = SettingsStore::default();
-    //     store
-    //         .set_default_settings(default_settings().as_ref(), cx)
-    //         .unwrap();
-    //     cx.set_global(store);
-    //     legacy_theme::init(Assets, cx);
-    //     // load_embedded_fonts(cx.platform().as_ref());
-
-    //     cx.add_window(
-    //         gpui2::WindowOptions {
-    //             bounds: WindowBounds::Fixed(RectF::new(vec2f(0., 0.), vec2f(1400., 900.))),
-    //             center: true,
-    //             ..Default::default()
-    //         },
-    //         |cx| {
-    //             view(|cx| {
-    //                 cx.enable_inspector();
-    //                 storybook(&mut ViewContext::new(cx))
-    //             })
-    //         },
-    //     );
-    //     cx.platform().activate(true);
-    // });
-}
-
-fn storybook<V: 'static>(cx: &mut ViewContext<V>) -> impl Element {
-    workspace(cx).themed(current_theme(cx))
-}
-
-// Nathan: During the transition to gpui2, we will include the base theme on the legacy Theme struct.
-fn current_theme<V: 'static>(cx: &mut ViewContext<V>) -> Theme {
-    todo!()
-    // settings::get::<ThemeSettings>(cx)
-    //     .theme
-    //     .deserialized_base_theme
-    //     .lock()
-    //     .get_or_insert_with(|| {
-    //         let theme: Theme =
-    //             serde_json::from_value(settings::get::<ThemeSettings>(cx).theme.base_theme.clone())
-    //                 .unwrap();
-    //         Box::new(theme)
-    //     })
-    //     .downcast_ref::<Theme>()
-    //     .unwrap()
-    //     .clone()
 }
 
 use rust_embed::RustEmbed;

crates/storybook2/src/theme.rs 🔗

@@ -1,4 +1,4 @@
-use gpui3::{Element, Hsla, Layout, LayoutId, Result, ViewContext, WindowContext};
+use gpui3::{Element, Hsla, Layout, LayoutId, Result, StackContext, ViewContext, WindowContext};
 use serde::{de::Visitor, Deserialize, Deserializer};
 use std::{collections::HashMap, fmt};
 
@@ -127,6 +127,15 @@ where
     deserializer.deserialize_map(SyntaxVisitor)
 }
 
+pub fn themed<E, F>(theme: Theme, cx: &mut ViewContext<E::State>, build_child: F) -> Themed<E>
+where
+    E: Element,
+    F: FnOnce(&mut ViewContext<E::State>) -> E,
+{
+    let child = cx.with_state(theme.clone(), |cx| build_child(cx));
+    Themed { theme, child }
+}
+
 pub struct Themed<E> {
     pub(crate) theme: Theme,
     pub(crate) child: E,
@@ -144,10 +153,7 @@ impl<E: Element> Element for Themed<E> {
     where
         Self: Sized,
     {
-        cx.push_cascading_state(self.theme.clone());
-        let result = self.child.layout(state, cx);
-        cx.pop_cascading_state::<Theme>();
-        result
+        cx.with_state(self.theme.clone(), |cx| self.child.layout(state, cx))
     }
 
     fn paint(
@@ -160,10 +166,9 @@ impl<E: Element> Element for Themed<E> {
     where
         Self: Sized,
     {
-        cx.push_cascading_state(self.theme.clone());
-        self.child.paint(layout, state, frame_state, cx)?;
-        cx.pop_cascading_state::<Theme>();
-        Ok(())
+        cx.with_state(self.theme.clone(), |cx| {
+            self.child.paint(layout, state, frame_state, cx)
+        })
     }
 }
 
@@ -184,5 +189,5 @@ impl<E: Element> Element for Themed<E> {
 // }
 
 pub fn theme<'a>(cx: &'a WindowContext) -> &'a Theme {
-    cx.cascading_state()
+    cx.state()
 }

crates/storybook2/src/workspace.rs 🔗

@@ -1,7 +1,6 @@
 use crate::{
     collab_panel::{collab_panel, CollabPanel},
-    element_ext::ElementExt,
-    theme::theme,
+    theme::{theme, themed},
     themes::rose_pine_dawn,
 };
 use gpui3::{
@@ -15,9 +14,7 @@ pub struct Workspace {
 }
 
 pub fn workspace(cx: &mut WindowContext) -> RootView<Workspace> {
-    view(cx.entity(|cx| Workspace::new(cx)), |workspace, cx| {
-        workspace.render(cx)
-    })
+    view(cx.entity(|cx| Workspace::new(cx)), Workspace::render)
 }
 
 impl Workspace {
@@ -31,36 +28,37 @@ impl Workspace {
     fn render(&mut self, cx: &mut ViewContext<Self>) -> impl Element<State = Self> {
         let theme = rose_pine_dawn();
 
-        div::<Self>()
-            .size_full()
-            .flex()
-            .flex_col()
-            .font("Zed Sans Extended")
-            .gap_0()
-            .justify_start()
-            .items_start()
-            .text_color(theme.lowest.base.default.foreground)
-            .fill(theme.middle.base.default.background)
-            .child(titlebar(cx))
-            .child(
-                div::<Self>()
-                    .flex_1()
-                    .w_full()
-                    .flex()
-                    .flex_row()
-                    .overflow_hidden()
-                    .child(self.left_panel.clone())
-                    .child(div().h_full().flex_1())
-                    .child(self.right_panel.clone()),
-            )
-            .child(statusbar::statusbar(cx))
-            .themed(theme)
+        themed(rose_pine_dawn(), cx, |cx| {
+            div()
+                .size_full()
+                .flex()
+                .flex_col()
+                .font("Zed Sans Extended")
+                .gap_0()
+                .justify_start()
+                .items_start()
+                .text_color(theme.lowest.base.default.foreground)
+                .fill(theme.middle.base.default.background)
+                .child(titlebar(cx))
+                .child(
+                    div()
+                        .flex_1()
+                        .w_full()
+                        .flex()
+                        .flex_row()
+                        .overflow_hidden()
+                        .child(self.left_panel.clone())
+                        .child(div().h_full().flex_1())
+                        .child(self.right_panel.clone()),
+                )
+                .child(statusbar::statusbar(cx))
+        })
     }
 }
 
 struct Titlebar;
 
-pub fn titlebar<S: 'static>(cx: &mut ViewContext<S>) -> impl Element<State = S> {
+pub fn titlebar<S: 'static + Send + Sync>(cx: &mut ViewContext<S>) -> impl Element<State = S> {
     let ref mut this = Titlebar;
     let theme = theme(cx);
     div()
@@ -75,7 +73,10 @@ pub fn titlebar<S: 'static>(cx: &mut ViewContext<S>) -> impl Element<State = S>
 }
 
 impl Titlebar {
-    fn render<V: 'static>(&mut self, cx: &mut ViewContext<V>) -> impl Element<State = V> {
+    fn render<V: 'static + Send + Sync>(
+        &mut self,
+        cx: &mut ViewContext<V>,
+    ) -> impl Element<State = V> {
         let theme = theme(cx);
         div()
             .flex()
@@ -88,7 +89,10 @@ impl Titlebar {
             .child(self.right_group(cx))
     }
 
-    fn left_group<V: 'static>(&mut self, cx: &mut ViewContext<V>) -> impl Element<State = V> {
+    fn left_group<S: 'static + Send + Sync>(
+        &mut self,
+        cx: &mut ViewContext<S>,
+    ) -> impl Element<State = S> {
         let theme = theme(cx);
         div()
             .flex()
@@ -162,7 +166,10 @@ impl Titlebar {
             )
     }
 
-    fn right_group<V: 'static>(&mut self, cx: &mut ViewContext<V>) -> impl Element<State = V> {
+    fn right_group<S: 'static + Send + Sync>(
+        &mut self,
+        cx: &mut ViewContext<S>,
+    ) -> impl Element<State = S> {
         let theme = theme(cx);
         div()
             .flex()
@@ -294,7 +301,7 @@ mod statusbar {
 
     use super::*;
 
-    pub fn statusbar<S: 'static>(cx: &mut ViewContext<S>) -> impl Element<State = S> {
+    pub fn statusbar<S: 'static + Send + Sync>(cx: &mut ViewContext<S>) -> impl Element<State = S> {
         let theme = theme(cx);
         div()
             .flex()
@@ -307,7 +314,7 @@ mod statusbar {
         // .child(right_group(cx))
     }
 
-    fn left_group<V: 'static>(cx: &mut ViewContext<V>) -> impl Element<State = V> {
+    fn left_group<V: 'static + Send + Sync>(cx: &mut ViewContext<V>) -> impl Element<State = V> {
         let theme = theme(cx);
         div()
             .flex()
@@ -404,7 +411,7 @@ mod statusbar {
             )
     }
 
-    fn right_group<S: 'static>(cx: &mut ViewContext<S>) -> impl Element<State = S> {
+    fn right_group<S: 'static + Send + Sync>(cx: &mut ViewContext<S>) -> impl Element<State = S> {
         let theme = theme(cx);
         div()
             .flex()