Merge branch 'gpui2' into gpui2-ui

Marshall Bowers created

Change summary

crates/gpui/src/fonts.rs                                     |   5 
crates/gpui3/src/active.rs                                   |  25 
crates/gpui3/src/element.rs                                  |  68 
crates/gpui3/src/elements.rs                                 |  11 
crates/gpui3/src/elements/clickable.rs                       | 144 -
crates/gpui3/src/elements/div.rs                             | 550 ++++-
crates/gpui3/src/elements/group.rs                           | 103 -
crates/gpui3/src/elements/hoverable.rs                       | 138 -
crates/gpui3/src/elements/identified.rs                      |  69 
crates/gpui3/src/elements/img.rs                             | 119 
crates/gpui3/src/elements/nested.rs                          | 258 --
crates/gpui3/src/elements/pressable.rs                       | 167 -
crates/gpui3/src/elements/svg.rs                             | 122 
crates/gpui3/src/elements/text.rs                            |  60 
crates/gpui3/src/geometry.rs                                 |  10 
crates/gpui3/src/gpui3.rs                                    |   9 
crates/gpui3/src/hover.rs                                    |  25 
crates/gpui3/src/interactive.rs                              | 122 
crates/gpui3/src/platform.rs                                 |   8 
crates/gpui3/src/platform/mac/text_system.rs                 |  31 
crates/gpui3/src/style.rs                                    |  29 
crates/gpui3/src/style_helpers.rs                            | 387 ----
crates/gpui3/src/styled.rs                                   | 526 +++++
crates/gpui3/src/taffy.rs                                    |   2 
crates/gpui3/src/text_system.rs                              | 121 
crates/gpui3/src/text_system/line.rs                         | 326 --
crates/gpui3/src/text_system/line_layout.rs                  | 295 +++
crates/gpui3/src/text_system/line_wrapper.rs                 | 211 -
crates/gpui3/src/text_system/text_layout_cache.rs            | 153 -
crates/gpui3/src/view.rs                                     |  22 
crates/gpui3/src/window.rs                                   |  25 
crates/gpui3_macros/src/derive_element.rs                    |   3 
crates/gpui3_macros/src/style_helpers.rs                     |   4 
crates/refineable/derive_refineable/src/derive_refineable.rs |  42 
crates/storybook2/src/collab_panel.rs                        |  28 
crates/storybook2/src/stories.rs                             |   9 
crates/storybook2/src/stories/text.rs                        |  20 
crates/storybook2/src/stories/z_index.rs                     |   8 
crates/storybook2/src/story_selector.rs                      |  15 
crates/storybook2/src/workspace.rs                           |  80 
crates/ui2/src/components/breadcrumb.rs                      |  15 
crates/ui2/src/components/buffer.rs                          |  12 
crates/ui2/src/components/buffer_search.rs                   |   2 
crates/ui2/src/components/collab_panel.rs                    |  36 
crates/ui2/src/components/context_menu.rs                    |   8 
crates/ui2/src/components/icon_button.rs                     |   5 
crates/ui2/src/components/keybinding.rs                      |   8 
crates/ui2/src/components/list.rs                            |  11 
crates/ui2/src/components/multi_buffer.rs                    |   2 
crates/ui2/src/components/notification.rs                    |   2 
crates/ui2/src/components/palette.rs                         |  13 
crates/ui2/src/components/panel.rs                           |  10 
crates/ui2/src/components/panes.rs                           |  10 
crates/ui2/src/components/player_stack.rs                    |  14 
crates/ui2/src/components/project_panel.rs                   |   2 
crates/ui2/src/components/status_bar.rs                      |   2 
crates/ui2/src/components/tab.rs                             |   8 
crates/ui2/src/components/tab_bar.rs                         |   8 
crates/ui2/src/components/terminal.rs                        |   2 
crates/ui2/src/components/title_bar.rs                       |   2 
crates/ui2/src/components/toast.rs                           |   6 
crates/ui2/src/components/toolbar.rs                         |   2 
crates/ui2/src/components/traffic_lights.rs                  |   8 
crates/ui2/src/components/workspace.rs                       |   2 
crates/ui2/src/elements/avatar.rs                            |   8 
crates/ui2/src/elements/button.rs                            |   2 
crates/ui2/src/elements/icon.rs                              |   5 
crates/ui2/src/elements/input.rs                             |  19 
crates/ui2/src/elements/label.rs                             |   2 
crates/ui2/src/elements/stack.rs                             |   2 
crates/ui2/src/elements/tool_divider.rs                      |   2 
crates/ui2/src/prelude.rs                                    |   2 
crates/ui2/src/story.rs                                      |   2 
crates/ui2/src/theme.rs                                      |   2 
74 files changed, 2,129 insertions(+), 2,457 deletions(-)

Detailed changes

crates/gpui/src/fonts.rs 🔗

@@ -155,8 +155,9 @@ impl Refineable for TextStyleRefinement {
         }
     }
 
-    fn refined(self, refinement: Self::Refinement) -> Self {
-        todo!()
+    fn refined(mut self, refinement: Self::Refinement) -> Self {
+        self.refine(&refinement);
+        self
     }
 }
 

crates/gpui3/src/active.rs 🔗

@@ -0,0 +1,25 @@
+use crate::{SharedString, StyleRefinement};
+
+pub trait Active {
+    fn set_active_style(&mut self, group_name: Option<SharedString>, style: StyleRefinement);
+
+    fn active(mut self, f: impl FnOnce(StyleRefinement) -> StyleRefinement) -> Self
+    where
+        Self: Sized,
+    {
+        self.set_active_style(None, f(StyleRefinement::default()));
+        self
+    }
+
+    fn group_active(
+        mut self,
+        group_name: impl Into<SharedString>,
+        f: impl FnOnce(StyleRefinement) -> StyleRefinement,
+    ) -> Self
+    where
+        Self: Sized,
+    {
+        self.set_active_style(Some(group_name.into()), f(StyleRefinement::default()));
+        self
+    }
+}

crates/gpui3/src/element.rs 🔗

@@ -1,9 +1,4 @@
-use std::sync::Arc;
-
-use crate::{
-    BorrowWindow, Bounds, Clickable, ElementId, Group, LayoutId, MouseDownEvent, MouseUpEvent,
-    Pixels, Point, SharedString, ViewContext,
-};
+use crate::{BorrowWindow, Bounds, ElementId, LayoutId, Pixels, Point, ViewContext};
 use derive_more::{Deref, DerefMut};
 pub(crate) use smallvec::SmallVec;
 
@@ -11,7 +6,7 @@ pub trait Element: 'static + Send + Sync + IntoAnyElement<Self::ViewState> {
     type ViewState: 'static + Send + Sync;
     type ElementState: 'static + Send + Sync;
 
-    fn element_id(&self) -> Option<ElementId>;
+    fn id(&self) -> Option<ElementId>;
 
     fn layout(
         &mut self,
@@ -27,46 +22,34 @@ pub trait Element: 'static + Send + Sync + IntoAnyElement<Self::ViewState> {
         element_state: &mut Self::ElementState,
         cx: &mut ViewContext<Self::ViewState>,
     );
+}
 
-    fn group(self, name: impl Into<SharedString>) -> Group<Self>
-    where
-        Self: Sized,
-    {
-        Group::new(name.into(), self)
-    }
+#[derive(Deref, DerefMut, Default, Clone, Debug, Eq, PartialEq, Hash)]
+pub(crate) struct GlobalElementId(SmallVec<[ElementId; 8]>);
+
+pub trait ElementIdentity: 'static + Send + Sync {
+    fn id(&self) -> Option<ElementId>;
 }
 
-pub trait IdentifiedElement: Element {
-    fn element_id(&self) -> ElementId {
-        Element::element_id(self).unwrap()
-    }
+pub struct IdentifiedElement(pub(crate) ElementId);
+pub struct AnonymousElement;
 
-    fn on_click(
-        self,
-        listener: impl Fn(
-                &mut Self::ViewState,
-                (&MouseDownEvent, &MouseUpEvent),
-                &mut ViewContext<Self::ViewState>,
-            ) + Send
-            + Sync
-            + 'static,
-    ) -> Clickable<Self>
-    where
-        Self: Sized,
-    {
-        Clickable::new(self, Arc::from(listener))
+impl ElementIdentity for IdentifiedElement {
+    fn id(&self) -> Option<ElementId> {
+        Some(self.0.clone())
     }
 }
 
-#[derive(Deref, DerefMut, Default, Clone, Debug, Eq, PartialEq, Hash)]
-pub(crate) struct GlobalElementId(SmallVec<[ElementId; 8]>);
-
-pub trait ParentElement {
-    type State;
+impl ElementIdentity for AnonymousElement {
+    fn id(&self) -> Option<ElementId> {
+        None
+    }
+}
 
-    fn children_mut(&mut self) -> &mut SmallVec<[AnyElement<Self::State>; 2]>;
+pub trait ParentElement: Element {
+    fn children_mut(&mut self) -> &mut SmallVec<[AnyElement<Self::ViewState>; 2]>;
 
-    fn child(mut self, child: impl IntoAnyElement<Self::State>) -> Self
+    fn child(mut self, child: impl IntoAnyElement<Self::ViewState>) -> Self
     where
         Self: Sized,
     {
@@ -74,7 +57,10 @@ pub trait ParentElement {
         self
     }
 
-    fn children(mut self, iter: impl IntoIterator<Item = impl IntoAnyElement<Self::State>>) -> Self
+    fn children(
+        mut self,
+        iter: impl IntoIterator<Item = impl IntoAnyElement<Self::ViewState>>,
+    ) -> Self
     where
         Self: Sized,
     {
@@ -126,7 +112,7 @@ impl<E: Element> RenderedElement<E> {
         frame_state: &mut Option<E::ElementState>,
         cx: &mut ViewContext<E::ViewState>,
     ) {
-        if let Some(id) = self.element.element_id() {
+        if let Some(id) = self.element.id() {
             cx.with_element_state(id, |element_state, cx| {
                 let mut element_state = element_state.unwrap();
                 self.element
@@ -146,7 +132,7 @@ where
     S: 'static + Send + Sync,
 {
     fn layout(&mut self, state: &mut E::ViewState, cx: &mut ViewContext<E::ViewState>) -> LayoutId {
-        let (layout_id, frame_state) = if let Some(id) = self.element.element_id() {
+        let (layout_id, frame_state) = if let Some(id) = self.element.id() {
             let layout_id = cx.with_element_state(id, |element_state, cx| {
                 self.element.layout(state, element_state, cx)
             });

crates/gpui3/src/elements.rs 🔗

@@ -1,20 +1,9 @@
-mod clickable;
 mod div;
-mod group;
-mod hoverable;
-mod identified;
 mod img;
-mod nested;
-mod pressable;
 mod svg;
 mod text;
 
-pub use clickable::*;
 pub use div::*;
-pub use group::*;
-pub use hoverable::*;
-pub use identified::*;
 pub use img::*;
-pub use pressable::*;
 pub use svg::*;
 pub use text::*;

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

@@ -1,144 +0,0 @@
-use crate::{
-    AnyElement, Bounds, DispatchPhase, Element, IdentifiedElement, Interactive, IntoAnyElement,
-    MouseDownEvent, MouseEventListeners, MouseUpEvent, ParentElement, Pixels, Styled, ViewContext,
-};
-use parking_lot::Mutex;
-use refineable::Cascade;
-use smallvec::SmallVec;
-use std::sync::Arc;
-
-pub type ClickListener<S> =
-    dyn Fn(&mut S, (&MouseDownEvent, &MouseUpEvent), &mut ViewContext<S>) + Send + Sync + 'static;
-
-pub struct Clickable<E: Element> {
-    child: E,
-    listener: Arc<ClickListener<E::ViewState>>,
-}
-
-pub struct ClickableState<S> {
-    last_mouse_down: Arc<Mutex<Option<MouseDownEvent>>>,
-    child_state: S,
-}
-
-impl<E: Element> Clickable<E> {
-    pub fn new(child: E, listener: Arc<ClickListener<E::ViewState>>) -> Self {
-        Self { child, listener }
-    }
-}
-
-impl<E> Styled for Clickable<E>
-where
-    E: Styled + IdentifiedElement,
-{
-    type Style = E::Style;
-
-    fn style_cascade(&mut self) -> &mut Cascade<E::Style> {
-        self.child.style_cascade()
-    }
-
-    fn declared_style(&mut self) -> &mut <Self::Style as refineable::Refineable>::Refinement {
-        self.child.declared_style()
-    }
-}
-
-impl<S, E> Interactive<S> for Clickable<E>
-where
-    S: 'static + Send + Sync,
-    E: IdentifiedElement + Interactive<S>,
-{
-    fn listeners(&mut self) -> &mut MouseEventListeners<S> {
-        self.child.listeners()
-    }
-}
-
-impl<E: IdentifiedElement> IntoAnyElement<E::ViewState> for Clickable<E> {
-    fn into_any(self) -> AnyElement<E::ViewState> {
-        AnyElement::new(self)
-    }
-}
-
-impl<E> Element for Clickable<E>
-where
-    E: IdentifiedElement,
-{
-    type ViewState = E::ViewState;
-    type ElementState = ClickableState<E::ElementState>;
-
-    fn element_id(&self) -> Option<crate::ElementId> {
-        Some(IdentifiedElement::element_id(&self.child))
-    }
-
-    fn layout(
-        &mut self,
-        state: &mut Self::ViewState,
-        element_state: Option<Self::ElementState>,
-        cx: &mut ViewContext<Self::ViewState>,
-    ) -> (crate::LayoutId, Self::ElementState) {
-        if let Some(element_state) = element_state {
-            let (layout_id, child_state) =
-                self.child
-                    .layout(state, Some(element_state.child_state), cx);
-
-            let element_state = ClickableState {
-                last_mouse_down: element_state.last_mouse_down,
-                child_state,
-            };
-            (layout_id, element_state)
-        } else {
-            let (layout_id, child_state) = self.child.layout(state, None, cx);
-            let element_state = ClickableState {
-                last_mouse_down: Default::default(),
-                child_state,
-            };
-            (layout_id, element_state)
-        }
-    }
-
-    fn paint(
-        &mut self,
-        bounds: Bounds<Pixels>,
-        state: &mut Self::ViewState,
-        element_state: &mut Self::ElementState,
-        cx: &mut ViewContext<Self::ViewState>,
-    ) {
-        let last_mouse_down = element_state.last_mouse_down.clone();
-        let is_some = last_mouse_down.lock().is_some();
-
-        if is_some {
-            let listener = self.listener.clone();
-            cx.on_mouse_event(move |view, up_event: &MouseUpEvent, phase, cx| {
-                if phase == DispatchPhase::Capture && !bounds.contains_point(up_event.position) {
-                    *last_mouse_down.lock() = None;
-                } else if phase == DispatchPhase::Bubble && bounds.contains_point(up_event.position)
-                {
-                    if let Some(down_event) = last_mouse_down.lock().take() {
-                        listener(view, (&down_event, up_event), cx);
-                    } else {
-                        log::error!("No mouse down event found for click event");
-                    }
-                }
-            })
-        } else {
-            cx.on_mouse_event(move |_, event: &MouseDownEvent, phase, _| {
-                if phase == DispatchPhase::Bubble {
-                    if bounds.contains_point(event.position) {
-                        *last_mouse_down.lock() = Some(event.clone());
-                    }
-                }
-            })
-        }
-
-        self.child
-            .paint(bounds, state, &mut element_state.child_state, cx);
-    }
-}
-
-impl<E: IdentifiedElement + ParentElement> ParentElement for Clickable<E> {
-    type State = E::State;
-
-    fn children_mut(&mut self) -> &mut SmallVec<[AnyElement<Self::State>; 2]> {
-        self.child.children_mut()
-    }
-}
-
-impl<E> IdentifiedElement for Clickable<E> where E: IdentifiedElement + Styled {}

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

@@ -1,246 +1,494 @@
 use crate::{
-    AnyElement, BorrowWindow, Bounds, Cascade, Element, ElementId, IdentifiedElement, Interactive,
-    IntoAnyElement, LayoutId, MouseEventListeners, Overflow, ParentElement, Pixels, Point,
-    Refineable, Style, Styled, ViewContext,
+    Active, AnonymousElement, AnyElement, AppContext, BorrowWindow, Bounds, Click, DispatchPhase,
+    Element, ElementId, ElementIdentity, Hover, IdentifiedElement, Interactive, IntoAnyElement,
+    LayoutId, MouseClickEvent, MouseDownEvent, MouseEventListeners, MouseMoveEvent, MouseUpEvent,
+    Overflow, ParentElement, Pixels, Point, ScrollWheelEvent, SharedString, Style, StyleRefinement,
+    Styled, ViewContext,
 };
+use collections::HashMap;
 use parking_lot::Mutex;
+use refineable::Refineable;
 use smallvec::SmallVec;
-use std::{marker::PhantomData, sync::Arc};
+use std::sync::Arc;
 
-pub enum HasId {}
+#[derive(Default)]
+pub struct DivState {
+    active_state: Arc<Mutex<ActiveState>>,
+    pending_click: Arc<Mutex<Option<MouseDownEvent>>>,
+}
 
-pub struct Div<S: 'static, I = ()> {
-    styles: Cascade<Style>,
-    id: Option<ElementId>,
-    listeners: MouseEventListeners<S>,
-    children: SmallVec<[AnyElement<S>; 2]>,
-    scroll_state: Option<ScrollState>,
-    identified: PhantomData<I>,
+#[derive(Copy, Clone, Default, Eq, PartialEq)]
+struct ActiveState {
+    group: bool,
+    element: bool,
 }
 
-pub fn div<S>() -> Div<S> {
-    Div {
-        styles: Default::default(),
-        id: None,
-        listeners: Default::default(),
-        children: Default::default(),
-        scroll_state: None,
-        identified: PhantomData,
+impl ActiveState {
+    pub fn is_none(&self) -> bool {
+        !self.group && !self.element
     }
 }
 
-impl<S, Marker> IntoAnyElement<S> for Div<S, Marker>
-where
-    S: 'static + Send + Sync,
-    Marker: 'static + Send + Sync,
-{
-    fn into_any(self) -> AnyElement<S> {
-        AnyElement::new(self)
-    }
+#[derive(Default)]
+struct GroupBounds(HashMap<SharedString, SmallVec<[Bounds<Pixels>; 1]>>);
+
+pub fn group_bounds(name: &SharedString, cx: &mut AppContext) -> Option<Bounds<Pixels>> {
+    cx.default_global::<GroupBounds>()
+        .0
+        .get(name)
+        .and_then(|bounds_stack| bounds_stack.last().cloned())
 }
 
-impl<S, Marker> Element for Div<S, Marker>
-where
-    S: 'static + Send + Sync,
-    Marker: 'static + Send + Sync,
-{
-    type ViewState = S;
-    type ElementState = ();
+#[derive(Default, Clone)]
+pub struct ScrollState(Arc<Mutex<Point<Pixels>>>);
 
-    fn element_id(&self) -> Option<ElementId> {
-        self.id.clone()
+impl ScrollState {
+    pub fn x(&self) -> Pixels {
+        self.0.lock().x
     }
 
-    fn layout(
-        &mut self,
-        view: &mut S,
-        _: Option<Self::ElementState>,
-        cx: &mut ViewContext<S>,
-    ) -> (LayoutId, Self::ElementState) {
-        let style = self.computed_style();
-        let child_layout_ids = style.apply_text_style(cx, |cx| {
-            self.with_element_id(cx, |this, cx| this.layout_children(view, cx))
-        });
-        let layout_id = cx.request_layout(style.into(), child_layout_ids.clone());
-        (layout_id, ())
+    pub fn set_x(&self, value: Pixels) {
+        self.0.lock().x = value;
     }
 
-    fn paint(
-        &mut self,
-        bounds: Bounds<Pixels>,
-        state: &mut S,
-        _: &mut (),
-        cx: &mut ViewContext<S>,
-    ) {
-        let style = self.computed_style();
-        let z_index = style.z_index.unwrap_or(0);
-        cx.stack(z_index, |cx| style.paint(bounds, cx));
-
-        let overflow = &style.overflow;
+    pub fn y(&self) -> Pixels {
+        self.0.lock().y
+    }
 
-        style.apply_text_style(cx, |cx| {
-            cx.stack(z_index + 1, |cx| {
-                style.apply_overflow(bounds, cx, |cx| {
-                    self.with_element_id(cx, |this, cx| {
-                        this.listeners.paint(bounds, cx);
-                        this.paint_children(overflow, state, cx)
-                    });
-                })
-            })
-        });
+    pub fn set_y(&self, value: Pixels) {
+        self.0.lock().y = value;
     }
 }
 
-impl<S> Div<S, ()>
+pub fn div<S>() -> Div<S, AnonymousElement>
 where
     S: 'static + Send + Sync,
 {
-    pub fn id(self, id: impl Into<ElementId>) -> Div<S, HasId> {
+    Div {
+        kind: AnonymousElement,
+        children: SmallVec::new(),
+        group: None,
+        base_style: StyleRefinement::default(),
+        hover_style: StyleRefinement::default(),
+        group_hover: None,
+        active_style: StyleRefinement::default(),
+        group_active: None,
+        listeners: MouseEventListeners::default(),
+    }
+}
+
+pub struct Div<V: 'static + Send + Sync, K: ElementIdentity = AnonymousElement> {
+    kind: K,
+    children: SmallVec<[AnyElement<V>; 2]>,
+    group: Option<SharedString>,
+    base_style: StyleRefinement,
+    hover_style: StyleRefinement,
+    group_hover: Option<GroupStyle>,
+    active_style: StyleRefinement,
+    group_active: Option<GroupStyle>,
+    listeners: MouseEventListeners<V>,
+}
+
+struct GroupStyle {
+    group: SharedString,
+    style: StyleRefinement,
+}
+
+impl<V> Div<V, AnonymousElement>
+where
+    V: 'static + Send + Sync,
+{
+    pub fn id(self, id: impl Into<ElementId>) -> Div<V, IdentifiedElement> {
         Div {
-            styles: self.styles,
-            id: Some(id.into()),
-            listeners: self.listeners,
+            kind: IdentifiedElement(id.into()),
             children: self.children,
-            scroll_state: self.scroll_state,
-            identified: PhantomData,
+            group: self.group,
+            base_style: self.base_style,
+            hover_style: self.hover_style,
+            group_hover: self.group_hover,
+            active_style: self.active_style,
+            group_active: self.group_active,
+            listeners: self.listeners,
         }
     }
 }
 
-impl<S, Marker> Div<S, Marker>
+impl<V, K> Div<V, K>
 where
-    S: 'static + Send + Sync,
-    Marker: 'static + Send + Sync,
+    V: 'static + Send + Sync,
+    K: ElementIdentity,
 {
+    pub fn group(mut self, group: impl Into<SharedString>) -> Self {
+        self.group = Some(group.into());
+        self
+    }
+
     pub fn z_index(mut self, z_index: u32) -> Self {
-        self.declared_style().z_index = Some(z_index);
+        self.base_style.z_index = Some(z_index);
         self
     }
 
     pub fn overflow_hidden(mut self) -> Self {
-        self.declared_style().overflow.x = Some(Overflow::Hidden);
-        self.declared_style().overflow.y = Some(Overflow::Hidden);
+        self.base_style.overflow.x = Some(Overflow::Hidden);
+        self.base_style.overflow.y = Some(Overflow::Hidden);
         self
     }
 
     pub fn overflow_hidden_x(mut self) -> Self {
-        self.declared_style().overflow.x = Some(Overflow::Hidden);
+        self.base_style.overflow.x = Some(Overflow::Hidden);
         self
     }
 
     pub fn overflow_hidden_y(mut self) -> Self {
-        self.declared_style().overflow.y = Some(Overflow::Hidden);
+        self.base_style.overflow.y = Some(Overflow::Hidden);
         self
     }
 
-    pub fn overflow_scroll(mut self, scroll_state: ScrollState) -> Self {
-        self.scroll_state = Some(scroll_state);
-        self.declared_style().overflow.x = Some(Overflow::Scroll);
-        self.declared_style().overflow.y = Some(Overflow::Scroll);
+    pub fn overflow_scroll(mut self, _scroll_state: ScrollState) -> Self {
+        // todo!("impl scrolling")
+        // self.scroll_state = Some(scroll_state);
+        self.base_style.overflow.x = Some(Overflow::Scroll);
+        self.base_style.overflow.y = Some(Overflow::Scroll);
         self
     }
 
-    pub fn overflow_x_scroll(mut self, scroll_state: ScrollState) -> Self {
-        self.scroll_state = Some(scroll_state);
-        self.declared_style().overflow.x = Some(Overflow::Scroll);
+    pub fn overflow_x_scroll(mut self, _scroll_state: ScrollState) -> Self {
+        // todo!("impl scrolling")
+        // self.scroll_state = Some(scroll_state);
+        self.base_style.overflow.x = Some(Overflow::Scroll);
         self
     }
 
-    pub fn overflow_y_scroll(mut self, scroll_state: ScrollState) -> Self {
-        self.scroll_state = Some(scroll_state);
-        self.declared_style().overflow.y = Some(Overflow::Scroll);
+    pub fn overflow_y_scroll(mut self, _scroll_state: ScrollState) -> Self {
+        // todo!("impl scrolling")
+        // self.scroll_state = Some(scroll_state);
+        self.base_style.overflow.y = Some(Overflow::Scroll);
         self
     }
 
-    fn scroll_offset(&self, overflow: &Point<Overflow>) -> Point<Pixels> {
-        let mut offset = Point::default();
-        if overflow.y == Overflow::Scroll {
-            offset.y = self.scroll_state.as_ref().unwrap().y();
+    fn with_element_id<R>(
+        &mut self,
+        cx: &mut ViewContext<V>,
+        f: impl FnOnce(&mut Self, &mut ViewContext<V>) -> R,
+    ) -> R {
+        if let Some(id) = self.id() {
+            cx.with_element_id(id, |cx| f(self, cx))
+        } else {
+            f(self, cx)
+        }
+    }
+
+    pub fn compute_style(
+        &self,
+        bounds: Bounds<Pixels>,
+        state: &DivState,
+        cx: &mut ViewContext<V>,
+    ) -> Style {
+        let mut computed_style = Style::default();
+        computed_style.refine(&self.base_style);
+
+        let mouse_position = cx.mouse_position();
+
+        if let Some(group_hover) = self.group_hover.as_ref() {
+            if let Some(group_bounds) = group_bounds(&group_hover.group, cx) {
+                if group_bounds.contains_point(&mouse_position) {
+                    computed_style.refine(&group_hover.style);
+                }
+            }
         }
-        if overflow.x == Overflow::Scroll {
-            offset.x = self.scroll_state.as_ref().unwrap().x();
+        if bounds.contains_point(&mouse_position) {
+            computed_style.refine(&self.hover_style);
         }
 
-        offset
+        let active_state = *state.active_state.lock();
+        if active_state.group {
+            if let Some(GroupStyle { style, .. }) = self.group_active.as_ref() {
+                computed_style.refine(style);
+            }
+        }
+        if active_state.element {
+            computed_style.refine(&self.active_style);
+        }
+
+        computed_style
     }
 
-    fn layout_children(&mut self, view: &mut S, cx: &mut ViewContext<S>) -> Vec<LayoutId> {
-        self.children
-            .iter_mut()
-            .map(|child| child.layout(view, cx))
-            .collect()
+    fn paint_hover_listeners(
+        &self,
+        bounds: Bounds<Pixels>,
+        group_bounds: Option<Bounds<Pixels>>,
+        cx: &mut ViewContext<V>,
+    ) {
+        if let Some(group_bounds) = group_bounds {
+            paint_hover_listener(group_bounds, cx);
+        }
+
+        if self.hover_style.is_some() {
+            paint_hover_listener(bounds, cx);
+        }
     }
 
-    fn paint_children(
-        &mut self,
-        overflow: &Point<Overflow>,
-        state: &mut S,
-        cx: &mut ViewContext<S>,
+    fn paint_active_listener(
+        &self,
+        bounds: Bounds<Pixels>,
+        group_bounds: Option<Bounds<Pixels>>,
+        active_state: Arc<Mutex<ActiveState>>,
+        cx: &mut ViewContext<V>,
     ) {
-        let scroll_offset = self.scroll_offset(overflow);
-        for child in &mut self.children {
-            child.paint(state, Some(scroll_offset), cx);
+        if active_state.lock().is_none() {
+            cx.on_mouse_event(move |_view, down: &MouseDownEvent, phase, cx| {
+                if phase == DispatchPhase::Bubble {
+                    let group =
+                        group_bounds.map_or(false, |bounds| bounds.contains_point(&down.position));
+                    let element = bounds.contains_point(&down.position);
+                    if group || element {
+                        *active_state.lock() = ActiveState { group, element };
+                        cx.notify();
+                    }
+                }
+            });
+        } else {
+            cx.on_mouse_event(move |_, _: &MouseUpEvent, phase, cx| {
+                if phase == DispatchPhase::Capture {
+                    *active_state.lock() = ActiveState::default();
+                    cx.notify();
+                }
+            });
         }
     }
 
-    fn with_element_id<R>(
-        &mut self,
-        cx: &mut ViewContext<S>,
-        f: impl FnOnce(&mut Self, &mut ViewContext<S>) -> R,
-    ) -> R {
-        if let Some(element_id) = self.element_id() {
-            cx.with_element_id(element_id, |cx| f(self, cx))
+    fn paint_event_listeners(
+        &self,
+        bounds: Bounds<Pixels>,
+        pending_click: Arc<Mutex<Option<MouseDownEvent>>>,
+        cx: &mut ViewContext<V>,
+    ) {
+        let click_listeners = self.listeners.mouse_click.clone();
+        let mouse_down = pending_click.lock().clone();
+        if let Some(mouse_down) = mouse_down {
+            cx.on_mouse_event(move |state, event: &MouseUpEvent, phase, cx| {
+                if phase == DispatchPhase::Bubble && bounds.contains_point(&event.position) {
+                    let mouse_click = MouseClickEvent {
+                        down: mouse_down.clone(),
+                        up: event.clone(),
+                    };
+                    for listener in &click_listeners {
+                        listener(state, &mouse_click, cx);
+                    }
+                }
+
+                *pending_click.lock() = None;
+            });
         } else {
-            f(self, cx)
+            cx.on_mouse_event(move |_state, event: &MouseDownEvent, phase, _cx| {
+                if phase == DispatchPhase::Bubble && bounds.contains_point(&event.position) {
+                    *pending_click.lock() = Some(event.clone());
+                }
+            });
+        }
+
+        for listener in self.listeners.mouse_down.iter().cloned() {
+            cx.on_mouse_event(move |state, event: &MouseDownEvent, phase, cx| {
+                listener(state, event, &bounds, phase, cx);
+            })
+        }
+
+        for listener in self.listeners.mouse_up.iter().cloned() {
+            cx.on_mouse_event(move |state, event: &MouseUpEvent, phase, cx| {
+                listener(state, event, &bounds, phase, cx);
+            })
+        }
+
+        for listener in self.listeners.mouse_move.iter().cloned() {
+            cx.on_mouse_event(move |state, event: &MouseMoveEvent, phase, cx| {
+                listener(state, event, &bounds, phase, cx);
+            })
+        }
+
+        for listener in self.listeners.scroll_wheel.iter().cloned() {
+            cx.on_mouse_event(move |state, event: &ScrollWheelEvent, phase, cx| {
+                listener(state, event, &bounds, phase, cx);
+            })
         }
     }
 }
 
-impl<V: 'static + Send + Sync, Marker: 'static + Send + Sync> Styled for Div<V, Marker> {
-    type Style = Style;
+impl<V, K> Element for Div<V, K>
+where
+    V: 'static + Send + Sync,
+    K: ElementIdentity,
+{
+    type ViewState = V;
+    type ElementState = DivState;
+
+    fn id(&self) -> Option<ElementId> {
+        self.kind.id()
+    }
 
-    fn style_cascade(&mut self) -> &mut Cascade<Self::Style> {
-        &mut self.styles
+    fn layout(
+        &mut self,
+        view_state: &mut Self::ViewState,
+        element_state: Option<Self::ElementState>,
+        cx: &mut ViewContext<Self::ViewState>,
+    ) -> (LayoutId, Self::ElementState) {
+        let element_state = element_state.unwrap_or_default();
+        let style = self.compute_style(Bounds::default(), &element_state, cx);
+        style.apply_text_style(cx, |cx| {
+            self.with_element_id(cx, |this, cx| {
+                let layout_ids = this
+                    .children
+                    .iter_mut()
+                    .map(|child| child.layout(view_state, cx))
+                    .collect::<Vec<_>>();
+
+                let layout_id = cx.request_layout(&style, layout_ids);
+                (layout_id, element_state)
+            })
+        })
     }
 
-    fn declared_style(&mut self) -> &mut <Self::Style as Refineable>::Refinement {
-        self.styles.base()
+    fn paint(
+        &mut self,
+        bounds: Bounds<Pixels>,
+        view_state: &mut Self::ViewState,
+        element_state: &mut Self::ElementState,
+        cx: &mut ViewContext<Self::ViewState>,
+    ) {
+        self.with_element_id(cx, |this, cx| {
+            if let Some(group) = this.group.clone() {
+                cx.default_global::<GroupBounds>()
+                    .0
+                    .entry(group)
+                    .or_default()
+                    .push(bounds);
+            }
+
+            let hover_group_bounds = this
+                .group_hover
+                .as_ref()
+                .and_then(|group_hover| group_bounds(&group_hover.group, cx));
+            let active_group_bounds = this
+                .group_active
+                .as_ref()
+                .and_then(|group_active| group_bounds(&group_active.group, cx));
+            let style = this.compute_style(bounds, element_state, cx);
+            let z_index = style.z_index.unwrap_or(0);
+
+            // Paint background and event handlers.
+            cx.stack(z_index, |cx| {
+                cx.stack(0, |cx| {
+                    style.paint(bounds, cx);
+                    this.paint_hover_listeners(bounds, hover_group_bounds, cx);
+                    this.paint_active_listener(
+                        bounds,
+                        active_group_bounds,
+                        element_state.active_state.clone(),
+                        cx,
+                    );
+                    this.paint_event_listeners(bounds, element_state.pending_click.clone(), cx);
+                });
+
+                cx.stack(1, |cx| {
+                    style.apply_text_style(cx, |cx| {
+                        style.apply_overflow(bounds, cx, |cx| {
+                            for child in &mut this.children {
+                                child.paint(view_state, None, cx);
+                            }
+                        })
+                    })
+                });
+            });
+
+            if let Some(group) = this.group.as_ref() {
+                cx.default_global::<GroupBounds>()
+                    .0
+                    .get_mut(group)
+                    .unwrap()
+                    .pop();
+            }
+        })
     }
 }
 
-impl<V: Send + Sync + 'static> IdentifiedElement for Div<V, HasId> {}
-
-impl<V: Send + Sync + 'static, Marker: 'static + Send + Sync> Interactive<V> for Div<V, Marker> {
-    fn listeners(&mut self) -> &mut MouseEventListeners<V> {
-        &mut self.listeners
+impl<V, K> IntoAnyElement<V> for Div<V, K>
+where
+    V: 'static + Send + Sync,
+    K: ElementIdentity,
+{
+    fn into_any(self) -> AnyElement<V> {
+        AnyElement::new(self)
     }
 }
 
-impl<V: 'static, Marker: 'static + Send + Sync> ParentElement for Div<V, Marker> {
-    type State = V;
-
-    fn children_mut(&mut self) -> &mut SmallVec<[AnyElement<V>; 2]> {
+impl<V, K> ParentElement for Div<V, K>
+where
+    V: 'static + Send + Sync,
+    K: ElementIdentity,
+{
+    fn children_mut(&mut self) -> &mut SmallVec<[AnyElement<Self::ViewState>; 2]> {
         &mut self.children
     }
 }
 
-#[derive(Default, Clone)]
-pub struct ScrollState(Arc<Mutex<Point<Pixels>>>);
-
-impl ScrollState {
-    pub fn x(&self) -> Pixels {
-        self.0.lock().x
+impl<V, K> Styled for Div<V, K>
+where
+    V: 'static + Send + Sync,
+    K: ElementIdentity,
+{
+    fn style(&mut self) -> &mut StyleRefinement {
+        &mut self.base_style
     }
+}
 
-    pub fn set_x(&self, value: Pixels) {
-        self.0.lock().x = value;
+impl<V, K> Interactive for Div<V, K>
+where
+    V: 'static + Send + Sync,
+    K: ElementIdentity,
+{
+    fn listeners(&mut self) -> &mut MouseEventListeners<V> {
+        &mut self.listeners
     }
+}
 
-    pub fn y(&self) -> Pixels {
-        self.0.lock().y
+impl<V, K> Hover for Div<V, K>
+where
+    V: 'static + Send + Sync,
+    K: ElementIdentity,
+{
+    fn set_hover_style(&mut self, group: Option<SharedString>, style: StyleRefinement) {
+        if let Some(group) = group {
+            self.group_hover = Some(GroupStyle { group, style });
+        } else {
+            self.hover_style = style;
+        }
     }
+}
 
-    pub fn set_y(&self, value: Pixels) {
-        self.0.lock().y = value;
+impl<V> Click for Div<V, IdentifiedElement> where V: 'static + Send + Sync {}
+
+impl<V> Active for Div<V, IdentifiedElement>
+where
+    V: 'static + Send + Sync,
+{
+    fn set_active_style(&mut self, group: Option<SharedString>, style: StyleRefinement) {
+        if let Some(group) = group {
+            self.group_active = Some(GroupStyle { group, style });
+        } else {
+            self.active_style = style;
+        }
     }
 }
+
+fn paint_hover_listener<V>(bounds: Bounds<Pixels>, cx: &mut ViewContext<V>)
+where
+    V: 'static + Send + Sync,
+{
+    let hovered = bounds.contains_point(&cx.mouse_position());
+    cx.on_mouse_event(move |_, event: &MouseMoveEvent, phase, cx| {
+        if phase == DispatchPhase::Capture {
+            if bounds.contains_point(&event.position) != hovered {
+                cx.notify();
+            }
+        }
+    });
+}

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

@@ -1,103 +0,0 @@
-use crate::{
-    AnyElement, AppContext, Bounds, Element, ElementId, IdentifiedElement, Interactive,
-    IntoAnyElement, MouseEventListeners, ParentElement, Pixels, SharedString, Styled, ViewContext,
-};
-use collections::HashMap;
-use refineable::Cascade;
-use smallvec::SmallVec;
-
-#[derive(Default)]
-struct GroupBounds(HashMap<SharedString, SmallVec<[Bounds<Pixels>; 1]>>);
-
-pub fn group_bounds(name: &SharedString, cx: &mut AppContext) -> Option<Bounds<Pixels>> {
-    cx.default_global::<GroupBounds>()
-        .0
-        .get(name)
-        .and_then(|bounds_stack| bounds_stack.last().cloned())
-}
-
-pub struct Group<E> {
-    name: SharedString,
-    child: E,
-}
-
-impl<E> Group<E> {
-    pub fn new(name: SharedString, child: E) -> Self {
-        Group { name, child }
-    }
-}
-
-impl<E: Element> IntoAnyElement<E::ViewState> for Group<E> {
-    fn into_any(self) -> AnyElement<E::ViewState> {
-        AnyElement::new(self)
-    }
-}
-
-impl<E: Element> Element for Group<E> {
-    type ViewState = E::ViewState;
-    type ElementState = E::ElementState;
-
-    fn element_id(&self) -> Option<ElementId> {
-        self.child.element_id()
-    }
-
-    fn layout(
-        &mut self,
-        state: &mut Self::ViewState,
-        element_state: Option<Self::ElementState>,
-        cx: &mut ViewContext<Self::ViewState>,
-    ) -> (crate::LayoutId, Self::ElementState) {
-        self.child.layout(state, element_state, cx)
-    }
-
-    fn paint(
-        &mut self,
-        bounds: Bounds<Pixels>,
-        state: &mut Self::ViewState,
-        element_state: &mut Self::ElementState,
-        cx: &mut ViewContext<Self::ViewState>,
-    ) {
-        cx.default_global::<GroupBounds>()
-            .0
-            .entry(self.name.clone())
-            .or_default()
-            .push(bounds);
-        self.child.paint(bounds, state, element_state, cx);
-        cx.default_global::<GroupBounds>()
-            .0
-            .get_mut(&self.name)
-            .unwrap()
-            .pop();
-    }
-}
-
-impl<E: ParentElement> ParentElement for Group<E> {
-    type State = E::State;
-
-    fn children_mut(&mut self) -> &mut SmallVec<[AnyElement<Self::State>; 2]> {
-        self.child.children_mut()
-    }
-}
-
-impl<E> IdentifiedElement for Group<E> where E: IdentifiedElement {}
-
-impl<E> Styled for Group<E>
-where
-    E: Styled,
-{
-    type Style = E::Style;
-
-    fn style_cascade(&mut self) -> &mut Cascade<E::Style> {
-        self.child.style_cascade()
-    }
-
-    fn declared_style(&mut self) -> &mut <Self::Style as refineable::Refineable>::Refinement {
-        self.child.declared_style()
-    }
-}
-
-impl<S: 'static + Send + Sync, E: Interactive<S> + Styled> Interactive<S> for Group<E> {
-    fn listeners(&mut self) -> &mut MouseEventListeners<S> {
-        self.child.listeners()
-    }
-}

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

@@ -1,138 +0,0 @@
-use crate::{
-    group_bounds, AnyElement, Bounds, DispatchPhase, Element, ElementId, IdentifiedElement,
-    Interactive, IntoAnyElement, MouseEventListeners, MouseMoveEvent, ParentElement, Pixels,
-    SharedString, Styled, ViewContext,
-};
-use refineable::{Cascade, CascadeSlot, Refineable};
-use smallvec::SmallVec;
-use std::sync::{
-    atomic::{AtomicBool, Ordering::SeqCst},
-    Arc,
-};
-
-pub struct Hoverable<E: Styled> {
-    group: Option<SharedString>,
-    hovered: Arc<AtomicBool>,
-    cascade_slot: CascadeSlot,
-    hovered_style: <E::Style as Refineable>::Refinement,
-    child: E,
-}
-
-impl<E: Styled> Hoverable<E> {
-    pub fn new(mut child: E, hover_group: Option<SharedString>) -> Self {
-        Self {
-            group: hover_group,
-            hovered: Arc::new(AtomicBool::new(false)),
-            cascade_slot: child.style_cascade().reserve(),
-            hovered_style: Default::default(),
-            child,
-        }
-    }
-}
-
-impl<E> Styled for Hoverable<E>
-where
-    E: Styled,
-{
-    type Style = E::Style;
-
-    fn style_cascade(&mut self) -> &mut Cascade<E::Style> {
-        self.child.style_cascade()
-    }
-
-    fn declared_style(&mut self) -> &mut <Self::Style as refineable::Refineable>::Refinement {
-        &mut self.hovered_style
-    }
-}
-
-impl<S: 'static + Send + Sync, E: Interactive<S> + Styled> Interactive<S> for Hoverable<E> {
-    fn listeners(&mut self) -> &mut MouseEventListeners<S> {
-        self.child.listeners()
-    }
-}
-
-impl<E> IntoAnyElement<E::ViewState> for Hoverable<E>
-where
-    E: Element + Styled,
-    <E as Styled>::Style: 'static + Refineable + Send + Sync + Default,
-    <<E as Styled>::Style as Refineable>::Refinement: 'static + Refineable + Send + Sync + Default,
-{
-    fn into_any(self) -> AnyElement<E::ViewState> {
-        AnyElement::new(self)
-    }
-}
-
-impl<E> Element for Hoverable<E>
-where
-    E: Element + Styled,
-    <E as Styled>::Style: 'static + Refineable + Send + Sync + Default,
-    <<E as Styled>::Style as Refineable>::Refinement: 'static + Refineable + Send + Sync + Default,
-{
-    type ViewState = E::ViewState;
-    type ElementState = E::ElementState;
-
-    fn element_id(&self) -> Option<ElementId> {
-        self.child.element_id()
-    }
-
-    fn layout(
-        &mut self,
-        state: &mut Self::ViewState,
-        element_state: Option<Self::ElementState>,
-        cx: &mut ViewContext<Self::ViewState>,
-    ) -> (crate::LayoutId, Self::ElementState) {
-        self.child.layout(state, element_state, cx)
-    }
-
-    fn paint(
-        &mut self,
-        bounds: Bounds<Pixels>,
-        state: &mut Self::ViewState,
-        element_state: &mut Self::ElementState,
-        cx: &mut ViewContext<Self::ViewState>,
-    ) {
-        let target_bounds = self
-            .group
-            .as_ref()
-            .and_then(|group| group_bounds(group, cx))
-            .unwrap_or(bounds);
-
-        let hovered = target_bounds.contains_point(cx.mouse_position());
-
-        let slot = self.cascade_slot;
-        let style = hovered.then_some(self.hovered_style.clone());
-        self.style_cascade().set(slot, style);
-        self.hovered.store(hovered, SeqCst);
-
-        cx.on_mouse_event({
-            let hovered = self.hovered.clone();
-
-            move |_, event: &MouseMoveEvent, phase, cx| {
-                if phase == DispatchPhase::Bubble {
-                    if target_bounds.contains_point(event.position) != hovered.load(SeqCst) {
-                        cx.notify();
-                        cx.stop_propagation();
-                    }
-                }
-            }
-        });
-
-        self.child.paint(bounds, state, element_state, cx);
-    }
-}
-
-impl<E: ParentElement + Styled> ParentElement for Hoverable<E> {
-    type State = E::State;
-
-    fn children_mut(&mut self) -> &mut SmallVec<[AnyElement<Self::State>; 2]> {
-        self.child.children_mut()
-    }
-}
-
-impl<E> IdentifiedElement for Hoverable<E>
-where
-    E: IdentifiedElement + Styled,
-    <E as Styled>::Style: 'static + Refineable + Send + Sync + Default,
-    <<E as Styled>::Style as Refineable>::Refinement: 'static + Refineable + Send + Sync + Default,
-{
-}

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

@@ -1,69 +0,0 @@
-use refineable::{Cascade, Refineable};
-use smallvec::SmallVec;
-
-use crate::{
-    AnyElement, BorrowWindow, Bounds, Element, ElementId, IdentifiedElement, IntoAnyElement,
-    LayoutId, ParentElement, Styled, ViewContext,
-};
-
-pub struct Identified<E> {
-    pub(crate) element: E,
-    pub(crate) id: ElementId,
-}
-
-impl<E: Element> IntoAnyElement<E::ViewState> for Identified<E> {
-    fn into_any(self) -> AnyElement<E::ViewState> {
-        AnyElement::new(self)
-    }
-}
-
-impl<E: Element> Element for Identified<E> {
-    type ViewState = E::ViewState;
-    type ElementState = E::ElementState;
-
-    fn element_id(&self) -> Option<ElementId> {
-        Some(self.id.clone())
-    }
-
-    fn layout(
-        &mut self,
-        state: &mut Self::ViewState,
-        element_state: Option<Self::ElementState>,
-        cx: &mut ViewContext<Self::ViewState>,
-    ) -> (LayoutId, Self::ElementState) {
-        self.element.layout(state, element_state, cx)
-    }
-
-    fn paint(
-        &mut self,
-        bounds: Bounds<crate::Pixels>,
-        state: &mut Self::ViewState,
-        element_state: &mut Self::ElementState,
-        cx: &mut ViewContext<Self::ViewState>,
-    ) {
-        cx.with_element_id(self.id.clone(), |cx| {
-            self.element.paint(bounds, state, element_state, cx)
-        })
-    }
-}
-
-impl<E: Element> IdentifiedElement for Identified<E> {}
-
-impl<E: Styled> Styled for Identified<E> {
-    type Style = E::Style;
-
-    fn style_cascade(&mut self) -> &mut Cascade<Self::Style> {
-        self.element.style_cascade()
-    }
-    fn declared_style(&mut self) -> &mut <Self::Style as Refineable>::Refinement {
-        self.element.declared_style()
-    }
-}
-
-impl<E: ParentElement> ParentElement for Identified<E> {
-    type State = E::State;
-
-    fn children_mut(&mut self) -> &mut SmallVec<[AnyElement<Self::State>; 2]> {
-        self.element.children_mut()
-    }
-}

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

@@ -1,29 +1,33 @@
 use crate::{
-    AnyElement, BorrowWindow, Bounds, Element, IntoAnyElement, LayoutId, Pixels, SharedString,
-    Style, Styled, ViewContext,
+    div, Active, AnonymousElement, AnyElement, BorrowWindow, Bounds, Click, Div, DivState, Element,
+    ElementId, ElementIdentity, Hover, IdentifiedElement, Interactive, IntoAnyElement, LayoutId,
+    MouseEventListeners, Pixels, SharedString, StyleRefinement, Styled, ViewContext,
 };
 use futures::FutureExt;
-use refineable::Cascade;
-use std::marker::PhantomData;
 use util::ResultExt;
 
-pub struct Img<S> {
-    style: Cascade<Style>,
+pub struct Img<V: 'static + Send + Sync, K: ElementIdentity = AnonymousElement> {
+    base: Div<V, K>,
     uri: Option<SharedString>,
     grayscale: bool,
-    state_type: PhantomData<S>,
 }
 
-pub fn img<S>() -> Img<S> {
+pub fn img<V>() -> Img<V, AnonymousElement>
+where
+    V: 'static + Send + Sync,
+{
     Img {
-        style: Cascade::default(),
+        base: div(),
         uri: None,
         grayscale: false,
-        state_type: PhantomData,
     }
 }
 
-impl<S> Img<S> {
+impl<V, K> Img<V, K>
+where
+    V: 'static + Send + Sync,
+    K: ElementIdentity,
+{
     pub fn uri(mut self, uri: impl Into<SharedString>) -> Self {
         self.uri = Some(uri.into());
         self
@@ -35,47 +39,63 @@ impl<S> Img<S> {
     }
 }
 
-impl<S> IntoAnyElement<S> for Img<S>
+impl<V: 'static + Send + Sync> Img<V, AnonymousElement> {
+    pub fn id(self, id: impl Into<ElementId>) -> Img<V, IdentifiedElement> {
+        Img {
+            base: self.base.id(id),
+            uri: self.uri,
+            grayscale: self.grayscale,
+        }
+    }
+}
+
+impl<V, K> IntoAnyElement<V> for Img<V, K>
 where
-    S: 'static + Send + Sync,
+    V: 'static + Send + Sync,
+    K: ElementIdentity,
 {
-    fn into_any(self) -> AnyElement<S> {
+    fn into_any(self) -> AnyElement<V> {
         AnyElement::new(self)
     }
 }
 
-impl<S: Send + Sync + 'static> Element for Img<S> {
-    type ViewState = S;
-    type ElementState = ();
+impl<V, K> Element for Img<V, K>
+where
+    V: Send + Sync + 'static,
+    K: ElementIdentity,
+{
+    type ViewState = V;
+    type ElementState = DivState;
 
-    fn element_id(&self) -> Option<crate::ElementId> {
-        None
+    fn id(&self) -> Option<crate::ElementId> {
+        self.base.id()
     }
 
     fn layout(
         &mut self,
-        _: &mut Self::ViewState,
-        _: Option<Self::ElementState>,
+        view_state: &mut Self::ViewState,
+        element_state: Option<Self::ElementState>,
         cx: &mut ViewContext<Self::ViewState>,
     ) -> (LayoutId, Self::ElementState)
     where
         Self: Sized,
     {
-        let style = self.computed_style();
-        let layout_id = cx.request_layout(style, []);
-        (layout_id, ())
+        self.base.layout(view_state, element_state, cx)
     }
 
     fn paint(
         &mut self,
         bounds: Bounds<Pixels>,
-        _: &mut Self::ViewState,
-        _: &mut Self::ElementState,
+        view: &mut Self::ViewState,
+        element_state: &mut Self::ElementState,
         cx: &mut ViewContext<Self::ViewState>,
     ) {
-        let style = self.computed_style();
+        cx.stack(0, |cx| {
+            self.base.paint(bounds, view, element_state, cx);
+        });
 
-        style.paint(bounds, cx);
+        let style = self.base.compute_style(bounds, element_state, cx);
+        let corner_radii = style.corner_radii;
 
         if let Some(uri) = self.uri.clone() {
             let image_future = cx.image_cache.get(uri);
@@ -84,7 +104,7 @@ impl<S: Send + Sync + 'static> Element for Img<S> {
                 .now_or_never()
                 .and_then(ResultExt::log_err)
             {
-                let corner_radii = style.corner_radii.to_pixels(bounds.size, cx.rem_size());
+                let corner_radii = corner_radii.to_pixels(bounds.size, cx.rem_size());
                 cx.stack(1, |cx| {
                     cx.paint_image(bounds, corner_radii, data, self.grayscale)
                         .log_err()
@@ -101,14 +121,43 @@ impl<S: Send + Sync + 'static> Element for Img<S> {
     }
 }
 
-impl<S> Styled for Img<S> {
-    type Style = Style;
+impl<V, K> Styled for Img<V, K>
+where
+    V: 'static + Send + Sync,
+    K: ElementIdentity,
+{
+    fn style(&mut self) -> &mut StyleRefinement {
+        self.base.style()
+    }
+}
+
+impl<V, K> Interactive for Img<V, K>
+where
+    V: 'static + Send + Sync,
+    K: ElementIdentity,
+{
+    fn listeners(&mut self) -> &mut MouseEventListeners<V> {
+        self.base.listeners()
+    }
+}
 
-    fn style_cascade(&mut self) -> &mut Cascade<Self::Style> {
-        &mut self.style
+impl<V, K> Hover for Img<V, K>
+where
+    V: 'static + Send + Sync,
+    K: ElementIdentity,
+{
+    fn set_hover_style(&mut self, group: Option<SharedString>, style: StyleRefinement) {
+        self.base.set_hover_style(group, style);
     }
+}
 
-    fn declared_style(&mut self) -> &mut <Self::Style as refineable::Refineable>::Refinement {
-        self.style.base()
+impl<V> Click for Img<V, IdentifiedElement> where V: 'static + Send + Sync {}
+
+impl<V> Active for Img<V, IdentifiedElement>
+where
+    V: 'static + Send + Sync,
+{
+    fn set_active_style(&mut self, group: Option<SharedString>, style: StyleRefinement) {
+        self.base.set_active_style(group, style)
     }
 }

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

@@ -1,258 +0,0 @@
-use crate::{
-    group_bounds, AnyElement, DispatchPhase, Element, IntoAnyElement, MouseMoveEvent, SharedString,
-    Style, StyleCascade, StyleRefinement,
-};
-use refineable::{CascadeSlot, Refineable};
-use smallvec::SmallVec;
-use std::sync::{
-    atomic::{AtomicBool, Ordering::SeqCst},
-    Arc,
-};
-
-trait LayoutNode<V: 'static + Send + Sync> {
-    fn state(&mut self) -> &mut LayoutNodeState<V>;
-
-    fn child(mut self, child: impl IntoAnyElement<V>) -> Self
-    where
-        Self: Sized,
-    {
-        self.state().children.push(child.into_any());
-        self
-    }
-
-    fn children<C, E>(mut self, children: C) -> Self
-    where
-        C: IntoIterator<Item = E>,
-        E: IntoAnyElement<V>,
-        Self: Sized,
-    {
-        for child in children {
-            self.state().children.push(child.into_any());
-        }
-        self
-    }
-}
-
-struct LayoutNodeState<V: 'static + Send + Sync> {
-    style_cascade: StyleCascade,
-    computed_style: Option<Style>,
-    children: SmallVec<[AnyElement<V>; 2]>,
-}
-
-impl<V> IntoAnyElement<V> for LayoutNodeState<V>
-where
-    V: 'static + Send + Sync,
-{
-    fn into_any(self) -> AnyElement<V> {
-        AnyElement::new(self)
-    }
-}
-
-impl<V: 'static + Send + Sync> Element for LayoutNodeState<V> {
-    type ViewState = V;
-    type ElementState = ();
-
-    fn element_id(&self) -> Option<crate::ElementId> {
-        None
-    }
-
-    fn layout(
-        &mut self,
-        state: &mut Self::ViewState,
-        _: Option<Self::ElementState>,
-        cx: &mut crate::ViewContext<Self::ViewState>,
-    ) -> (crate::LayoutId, Self::ElementState) {
-        let layout_ids = self
-            .children
-            .iter_mut()
-            .map(|child| child.layout(state, cx))
-            .collect::<Vec<_>>();
-
-        // todo!("pass just the style cascade")
-        let style = self.computed_style().clone();
-        let layout_id = cx.request_layout(style, layout_ids);
-        (layout_id, ())
-    }
-
-    fn paint(
-        &mut self,
-        _: crate::Bounds<crate::Pixels>,
-        state: &mut Self::ViewState,
-        _: &mut Self::ElementState,
-        cx: &mut crate::ViewContext<Self::ViewState>,
-    ) {
-        for child in &mut self.children {
-            child.paint(state, None, cx);
-        }
-    }
-}
-
-pub trait Styled {
-    fn style_cascade(&mut self) -> &mut StyleCascade;
-    fn computed_style(&mut self) -> &Style;
-}
-
-pub struct StyledElement<E> {
-    child: E,
-}
-
-impl<E> IntoAnyElement<E::ViewState> for StyledElement<E>
-where
-    E: Element + Styled,
-{
-    fn into_any(self) -> AnyElement<E::ViewState> {
-        AnyElement::new(self)
-    }
-}
-
-impl<E: Element + Styled> Element for StyledElement<E> {
-    type ViewState = E::ViewState;
-    type ElementState = E::ElementState;
-
-    fn element_id(&self) -> Option<crate::ElementId> {
-        self.child.element_id()
-    }
-
-    fn layout(
-        &mut self,
-        state: &mut Self::ViewState,
-        element_state: Option<Self::ElementState>,
-        cx: &mut crate::ViewContext<Self::ViewState>,
-    ) -> (crate::LayoutId, Self::ElementState) {
-        self.child.layout(state, element_state, cx)
-    }
-
-    fn paint(
-        &mut self,
-        bounds: crate::Bounds<crate::Pixels>,
-        state: &mut Self::ViewState,
-        element_state: &mut Self::ElementState,
-        cx: &mut crate::ViewContext<Self::ViewState>,
-    ) {
-        self.child.computed_style().paint(bounds, cx);
-        self.child.paint(bounds, state, element_state, cx);
-    }
-}
-
-pub trait Hoverable {
-    fn hover_style(&mut self) -> &mut StyleRefinement;
-
-    fn hover(mut self, f: impl FnOnce(&mut StyleRefinement) -> &mut StyleRefinement) -> Self
-    where
-        Self: Sized,
-    {
-        f(self.hover_style());
-        self
-    }
-}
-
-struct HoverableElement<Child> {
-    hover_style: StyleRefinement,
-    group: Option<SharedString>,
-    cascade_slot: CascadeSlot,
-    hovered: Arc<AtomicBool>,
-    child: Child,
-}
-
-impl<Child: Styled + Element> HoverableElement<Child> {
-    fn hover_style(&mut self) -> &mut StyleRefinement {
-        &mut self.hover_style
-    }
-}
-
-impl<E> IntoAnyElement<E::ViewState> for HoverableElement<E>
-where
-    E: Element + Styled,
-{
-    fn into_any(self) -> AnyElement<E::ViewState> {
-        AnyElement::new(self)
-    }
-}
-
-impl<E> Element for HoverableElement<E>
-where
-    E: Element + Styled,
-{
-    type ViewState = E::ViewState;
-    type ElementState = E::ElementState;
-
-    fn element_id(&self) -> Option<crate::ElementId> {
-        self.child.element_id()
-    }
-
-    fn layout(
-        &mut self,
-        state: &mut Self::ViewState,
-        element_state: Option<Self::ElementState>,
-        cx: &mut crate::ViewContext<Self::ViewState>,
-    ) -> (crate::LayoutId, Self::ElementState) {
-        self.child.layout(state, element_state, cx)
-    }
-
-    fn paint(
-        &mut self,
-        bounds: crate::Bounds<crate::Pixels>,
-        state: &mut Self::ViewState,
-        element_state: &mut Self::ElementState,
-        cx: &mut crate::ViewContext<Self::ViewState>,
-    ) {
-        let target_bounds = self
-            .group
-            .as_ref()
-            .and_then(|group| group_bounds(group, cx))
-            .unwrap_or(bounds);
-
-        let hovered = target_bounds.contains_point(cx.mouse_position());
-
-        let slot = self.cascade_slot;
-        let style = hovered.then_some(self.hover_style.clone());
-        self.child.style_cascade().set(slot, style);
-        self.hovered.store(hovered, SeqCst);
-
-        let hovered = self.hovered.clone();
-        cx.on_mouse_event(move |_, event: &MouseMoveEvent, phase, cx| {
-            if phase == DispatchPhase::Capture {
-                if target_bounds.contains_point(event.position) != hovered.load(SeqCst) {
-                    cx.notify();
-                }
-            }
-        });
-
-        self.child.paint(bounds, state, element_state, cx);
-    }
-}
-
-struct Div<V: 'static + Send + Sync>(HoverableElement<LayoutNodeState<V>>);
-
-impl<V: 'static + Send + Sync> LayoutNode<V> for Div<V> {
-    fn state(&mut self) -> &mut LayoutNodeState<V> {
-        &mut self.0.child
-    }
-}
-
-impl<V: 'static + Send + Sync> Styled for LayoutNodeState<V> {
-    fn style_cascade(&mut self) -> &mut StyleCascade {
-        &mut self.style_cascade
-    }
-
-    fn computed_style(&mut self) -> &Style {
-        self.computed_style
-            .get_or_insert_with(|| Style::default().refined(self.style_cascade.merged()))
-    }
-}
-
-impl<V: 'static + Send + Sync> Styled for Div<V> {
-    fn style_cascade(&mut self) -> &mut StyleCascade {
-        self.0.child.style_cascade()
-    }
-
-    fn computed_style(&mut self) -> &Style {
-        self.0.child.computed_style()
-    }
-}
-
-impl<V: 'static + Send + Sync> Hoverable for Div<V> {
-    fn hover_style(&mut self) -> &mut StyleRefinement {
-        self.0.hover_style()
-    }
-}

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

@@ -1,167 +0,0 @@
-use crate::{
-    group_bounds, AnyElement, Bounds, DispatchPhase, Element, IdentifiedElement, Interactive,
-    IntoAnyElement, MouseDownEvent, MouseEventListeners, MouseUpEvent, ParentElement, Pixels,
-    SharedString, Styled, ViewContext,
-};
-use refineable::{Cascade, CascadeSlot, Refineable};
-use smallvec::SmallVec;
-use std::sync::{
-    atomic::{AtomicBool, Ordering::SeqCst},
-    Arc,
-};
-
-pub struct Pressable<E: Styled> {
-    group: Option<SharedString>,
-    cascade_slot: CascadeSlot,
-    pressed_style: <E::Style as Refineable>::Refinement,
-    child: E,
-}
-
-pub struct PressableState<S> {
-    pressed: Arc<AtomicBool>,
-    child_state: S,
-}
-
-impl<E: Styled> Pressable<E> {
-    pub fn new(mut child: E, group: Option<SharedString>) -> Self {
-        Self {
-            group,
-            cascade_slot: child.style_cascade().reserve(),
-            pressed_style: Default::default(),
-            child,
-        }
-    }
-}
-
-impl<E> Styled for Pressable<E>
-where
-    E: Styled,
-{
-    type Style = E::Style;
-
-    fn style_cascade(&mut self) -> &mut Cascade<E::Style> {
-        self.child.style_cascade()
-    }
-
-    fn declared_style(&mut self) -> &mut <Self::Style as refineable::Refineable>::Refinement {
-        &mut self.pressed_style
-    }
-}
-
-impl<S: 'static + Send + Sync, E: Interactive<S> + Styled> Interactive<S> for Pressable<E> {
-    fn listeners(&mut self) -> &mut MouseEventListeners<S> {
-        self.child.listeners()
-    }
-}
-
-impl<E> IntoAnyElement<E::ViewState> for Pressable<E>
-where
-    E: Styled + IdentifiedElement,
-    <E as Styled>::Style: 'static + Refineable + Send + Sync + Default,
-    <<E as Styled>::Style as Refineable>::Refinement: 'static + Refineable + Send + Sync + Default,
-{
-    fn into_any(self) -> AnyElement<E::ViewState> {
-        AnyElement::new(self)
-    }
-}
-
-impl<E> Element for Pressable<E>
-where
-    E: Styled + IdentifiedElement,
-    <E as Styled>::Style: 'static + Refineable + Send + Sync + Default,
-    <<E as Styled>::Style as Refineable>::Refinement: 'static + Refineable + Send + Sync + Default,
-{
-    type ViewState = E::ViewState;
-    type ElementState = PressableState<E::ElementState>;
-
-    fn element_id(&self) -> Option<crate::ElementId> {
-        Some(IdentifiedElement::element_id(&self.child))
-    }
-
-    fn layout(
-        &mut self,
-        state: &mut Self::ViewState,
-        element_state: Option<Self::ElementState>,
-        cx: &mut ViewContext<Self::ViewState>,
-    ) -> (crate::LayoutId, Self::ElementState) {
-        if let Some(element_state) = element_state {
-            let (id, child_state) = self
-                .child
-                .layout(state, Some(element_state.child_state), cx);
-            let element_state = PressableState {
-                pressed: element_state.pressed,
-                child_state,
-            };
-            (id, element_state)
-        } else {
-            let (id, child_state) = self.child.layout(state, None, cx);
-            let element_state = PressableState {
-                pressed: Default::default(),
-                child_state,
-            };
-            (id, element_state)
-        }
-    }
-
-    fn paint(
-        &mut self,
-        bounds: Bounds<Pixels>,
-        state: &mut Self::ViewState,
-        element_state: &mut Self::ElementState,
-        cx: &mut ViewContext<Self::ViewState>,
-    ) {
-        let target_bounds = self
-            .group
-            .as_ref()
-            .and_then(|group| group_bounds(group, cx))
-            .unwrap_or(bounds);
-
-        let style = element_state
-            .pressed
-            .load(SeqCst)
-            .then_some(self.pressed_style.clone());
-        let slot = self.cascade_slot;
-        self.style_cascade().set(slot, style);
-
-        let pressed = element_state.pressed.clone();
-        cx.on_mouse_event(move |_, event: &MouseDownEvent, phase, cx| {
-            if phase == DispatchPhase::Bubble {
-                if target_bounds.contains_point(event.position) {
-                    pressed.store(true, SeqCst);
-                    cx.notify();
-                }
-            }
-        });
-        let pressed = element_state.pressed.clone();
-        cx.on_mouse_event(move |_, _: &MouseUpEvent, phase, cx| {
-            if phase == DispatchPhase::Capture {
-                if pressed.load(SeqCst) {
-                    pressed.store(false, SeqCst);
-                    cx.notify();
-                }
-            }
-        });
-
-        self.child
-            .paint(bounds, state, &mut element_state.child_state, cx);
-    }
-}
-
-impl<E> ParentElement for Pressable<E>
-where
-    E: ParentElement + IdentifiedElement + Styled,
-{
-    type State = E::State;
-
-    fn children_mut(&mut self) -> &mut SmallVec<[AnyElement<Self::State>; 2]> {
-        self.child.children_mut()
-    }
-}
-
-impl<E> IdentifiedElement for Pressable<E>
-where
-    E: IdentifiedElement + Styled,
-    <E as Styled>::Style: 'static + Refineable + Send + Sync + Default,
-    <<E as Styled>::Style as Refineable>::Refinement: 'static + Refineable + Send + Sync + Default,
-{
-}

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

@@ -1,85 +1,137 @@
 use crate::{
-    AnyElement, Bounds, Element, IntoAnyElement, LayoutId, Pixels, SharedString, Style, Styled,
+    div, Active, AnonymousElement, AnyElement, Bounds, Click, Div, DivState, Element, ElementId,
+    ElementIdentity, Hover, IdentifiedElement, Interactive, IntoAnyElement, LayoutId,
+    MouseEventListeners, Pixels, SharedString, StyleRefinement, Styled,
 };
-use refineable::Cascade;
-use std::marker::PhantomData;
 use util::ResultExt;
 
-pub struct Svg<S> {
+pub struct Svg<V: 'static + Send + Sync, K: ElementIdentity = AnonymousElement> {
+    base: Div<V, K>,
     path: Option<SharedString>,
-    style: Cascade<Style>,
-    state_type: PhantomData<S>,
 }
 
-pub fn svg<S>() -> Svg<S> {
+pub fn svg<V>() -> Svg<V, AnonymousElement>
+where
+    V: 'static + Send + Sync,
+{
     Svg {
+        base: div(),
         path: None,
-        style: Cascade::<Style>::default(),
-        state_type: PhantomData,
     }
 }
 
-impl<S> Svg<S> {
+impl<V, K> Svg<V, K>
+where
+    V: 'static + Send + Sync,
+    K: ElementIdentity,
+{
     pub fn path(mut self, path: impl Into<SharedString>) -> Self {
         self.path = Some(path.into());
         self
     }
 }
 
-impl<S> IntoAnyElement<S> for Svg<S>
+impl<V: 'static + Send + Sync> Svg<V, AnonymousElement> {
+    pub fn id(self, id: impl Into<ElementId>) -> Svg<V, IdentifiedElement> {
+        Svg {
+            base: self.base.id(id),
+            path: self.path,
+        }
+    }
+}
+
+impl<V, K> IntoAnyElement<V> for Svg<V, K>
 where
-    S: 'static + Send + Sync,
+    V: 'static + Send + Sync,
+    K: ElementIdentity,
 {
-    fn into_any(self) -> AnyElement<S> {
+    fn into_any(self) -> AnyElement<V> {
         AnyElement::new(self)
     }
 }
 
-impl<S: 'static + Send + Sync> Element for Svg<S> {
-    type ViewState = S;
-    type ElementState = ();
+impl<V, K> Element for Svg<V, K>
+where
+    V: 'static + Send + Sync,
+    K: ElementIdentity,
+{
+    type ViewState = V;
+    type ElementState = DivState;
 
-    fn element_id(&self) -> Option<crate::ElementId> {
-        None
+    fn id(&self) -> Option<crate::ElementId> {
+        self.base.id()
     }
 
     fn layout(
         &mut self,
-        _: &mut S,
-        _: Option<Self::ElementState>,
-        cx: &mut crate::ViewContext<S>,
+        view: &mut V,
+        element_state: Option<Self::ElementState>,
+        cx: &mut crate::ViewContext<V>,
     ) -> (LayoutId, Self::ElementState)
     where
         Self: Sized,
     {
-        let style = self.computed_style();
-        (cx.request_layout(style, []), ())
+        self.base.layout(view, element_state, cx)
     }
 
     fn paint(
         &mut self,
         bounds: Bounds<Pixels>,
-        _: &mut Self::ViewState,
-        _: &mut Self::ElementState,
-        cx: &mut crate::ViewContext<S>,
+        view: &mut Self::ViewState,
+        element_state: &mut Self::ElementState,
+        cx: &mut crate::ViewContext<V>,
     ) where
         Self: Sized,
     {
-        let fill_color = self.computed_style().fill.and_then(|fill| fill.color());
-        if let Some((path, fill_color)) = self.path.as_ref().zip(fill_color) {
-            cx.paint_svg(bounds, path.clone(), fill_color).log_err();
+        self.base.paint(bounds, view, element_state, cx);
+        let color = self
+            .base
+            .compute_style(bounds, element_state, cx)
+            .text
+            .color;
+        if let Some((path, color)) = self.path.as_ref().zip(color) {
+            cx.paint_svg(bounds, path.clone(), color).log_err();
         }
     }
 }
 
-impl<S: 'static + Send + Sync> Styled for Svg<S> {
-    type Style = Style;
+impl<V, K> Styled for Svg<V, K>
+where
+    V: 'static + Send + Sync,
+    K: ElementIdentity,
+{
+    fn style(&mut self) -> &mut StyleRefinement {
+        self.base.style()
+    }
+}
+
+impl<V, K> Interactive for Svg<V, K>
+where
+    V: 'static + Send + Sync,
+    K: ElementIdentity,
+{
+    fn listeners(&mut self) -> &mut MouseEventListeners<V> {
+        self.base.listeners()
+    }
+}
 
-    fn style_cascade(&mut self) -> &mut refineable::Cascade<Self::Style> {
-        &mut self.style
+impl<V, K> Hover for Svg<V, K>
+where
+    V: 'static + Send + Sync,
+    K: ElementIdentity,
+{
+    fn set_hover_style(&mut self, group: Option<SharedString>, style: StyleRefinement) {
+        self.base.set_hover_style(group, style);
     }
+}
 
-    fn declared_style(&mut self) -> &mut <Self::Style as refineable::Refineable>::Refinement {
-        self.style.base()
+impl<V> Click for Svg<V, IdentifiedElement> where V: 'static + Send + Sync {}
+
+impl<V> Active for Svg<V, IdentifiedElement>
+where
+    V: 'static + Send + Sync,
+{
+    fn set_active_style(&mut self, group: Option<SharedString>, style: StyleRefinement) {
+        self.base.set_active_style(group, style)
     }
 }

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

@@ -1,11 +1,13 @@
 use crate::{
-    AnyElement, Bounds, Element, IntoAnyElement, LayoutId, Line, Pixels, Size, ViewContext,
+    AnyElement, BorrowWindow, Bounds, Element, IntoAnyElement, LayoutId, Line, Pixels,
+    SharedString, Size, ViewContext,
 };
 use parking_lot::Mutex;
+use smallvec::SmallVec;
 use std::{marker::PhantomData, sync::Arc};
-use util::{arc_cow::ArcCow, ResultExt};
+use util::ResultExt;
 
-impl<S: 'static + Send + Sync> IntoAnyElement<S> for ArcCow<'static, str> {
+impl<S: 'static + Send + Sync> IntoAnyElement<S> for SharedString {
     fn into_any(self) -> AnyElement<S> {
         Text {
             text: self,
@@ -18,7 +20,7 @@ impl<S: 'static + Send + Sync> IntoAnyElement<S> for ArcCow<'static, str> {
 impl<V: 'static + Send + Sync> IntoAnyElement<V> for &'static str {
     fn into_any(self) -> AnyElement<V> {
         Text {
-            text: ArcCow::from(self),
+            text: self.into(),
             state_type: PhantomData,
         }
         .into_any()
@@ -30,7 +32,7 @@ impl<V: 'static + Send + Sync> IntoAnyElement<V> for &'static str {
 impl<S: 'static + Send + Sync> IntoAnyElement<S> for String {
     fn into_any(self) -> AnyElement<S> {
         Text {
-            text: ArcCow::from(self),
+            text: self.into(),
             state_type: PhantomData,
         }
         .into_any()
@@ -38,7 +40,7 @@ impl<S: 'static + Send + Sync> IntoAnyElement<S> for String {
 }
 
 pub struct Text<S> {
-    text: ArcCow<'static, str>,
+    text: SharedString,
     state_type: PhantomData<S>,
 }
 
@@ -52,7 +54,7 @@ impl<S: 'static + Send + Sync> Element for Text<S> {
     type ViewState = S;
     type ElementState = Arc<Mutex<Option<TextElementState>>>;
 
-    fn element_id(&self) -> Option<crate::ElementId> {
+    fn id(&self) -> Option<crate::ElementId> {
         None
     }
 
@@ -74,12 +76,13 @@ impl<S: 'static + Send + Sync> Element for Text<S> {
         let rem_size = cx.rem_size();
         let layout_id = cx.request_measured_layout(Default::default(), rem_size, {
             let element_state = element_state.clone();
-            move |_, _| {
-                let Some(line_layout) = text_system
-                    .layout_line(
-                        text.as_ref(),
+            move |known_dimensions, _| {
+                let Some(lines) = text_system
+                    .layout_text(
+                        &text,
                         font_size,
-                        &[(text.len(), text_style.to_run())],
+                        &[text_style.to_run(text.len())],
+                        known_dimensions.width, // Wrap if we know the width.
                     )
                     .log_err()
                 else {
@@ -87,14 +90,13 @@ impl<S: 'static + Send + Sync> Element for Text<S> {
                 };
 
                 let size = Size {
-                    width: line_layout.width(),
-                    height: line_height,
+                    width: lines.iter().map(|line| line.layout.width).max().unwrap(),
+                    height: line_height * lines.len(),
                 };
 
-                element_state.lock().replace(TextElementState {
-                    line: Arc::new(line_layout),
-                    line_height,
-                });
+                element_state
+                    .lock()
+                    .replace(TextElementState { lines, line_height });
 
                 size
             }
@@ -110,22 +112,20 @@ impl<S: 'static + Send + Sync> Element for Text<S> {
         element_state: &mut Self::ElementState,
         cx: &mut ViewContext<S>,
     ) {
-        let line;
-        let line_height;
-        {
-            let element_state = element_state.lock();
-            let element_state = element_state
-                .as_ref()
-                .expect("measurement has not been performed");
-            line = element_state.line.clone();
-            line_height = element_state.line_height;
+        let element_state = element_state.lock();
+        let element_state = element_state
+            .as_ref()
+            .expect("measurement has not been performed");
+        let line_height = element_state.line_height;
+        let mut line_origin = bounds.origin;
+        for line in &element_state.lines {
+            line.paint(line_origin, line_height, cx).log_err();
+            line_origin.y += line.size(line_height).height;
         }
-
-        line.paint(bounds, bounds, line_height, cx).log_err();
     }
 }
 
 pub struct TextElementState {
-    line: Arc<Line>,
+    lines: SmallVec<[Line; 1]>,
     line_height: Pixels,
 }

crates/gpui3/src/geometry.rs 🔗

@@ -413,7 +413,7 @@ impl<T> Bounds<T>
 where
     T: Add<T, Output = T> + PartialOrd + Clone + Default + Debug,
 {
-    pub fn contains_point(&self, point: Point<T>) -> bool {
+    pub fn contains_point(&self, point: &Point<T>) -> bool {
         point.x >= self.origin.x
             && point.x <= self.origin.x.clone() + self.size.width.clone()
             && point.y >= self.origin.y
@@ -656,6 +656,14 @@ impl Mul<f32> for Pixels {
     }
 }
 
+impl Mul<usize> for Pixels {
+    type Output = Pixels;
+
+    fn mul(self, other: usize) -> Pixels {
+        Pixels(self.0 * other as f32)
+    }
+}
+
 impl Mul<Pixels> for f32 {
     type Output = Pixels;
 

crates/gpui3/src/gpui3.rs 🔗

@@ -1,3 +1,4 @@
+mod active;
 mod app;
 mod assets;
 mod color;
@@ -6,12 +7,12 @@ mod elements;
 mod events;
 mod executor;
 mod geometry;
+mod hover;
 mod image_cache;
 mod interactive;
 mod platform;
 mod scene;
 mod style;
-mod style_helpers;
 mod styled;
 mod subscription;
 mod svg_renderer;
@@ -21,6 +22,7 @@ mod util;
 mod view;
 mod window;
 
+pub use active::*;
 pub use anyhow::Result;
 pub use app::*;
 pub use assets::*;
@@ -31,6 +33,7 @@ pub use events::*;
 pub use executor::*;
 pub use geometry::*;
 pub use gpui3_macros::*;
+pub use hover::*;
 pub use image_cache::*;
 pub use interactive::*;
 pub use platform::*;
@@ -41,7 +44,6 @@ pub use serde_json;
 pub use smallvec;
 pub use smol::Timer;
 pub use style::*;
-pub use style_helpers::*;
 pub use styled::*;
 pub use subscription::*;
 pub use svg_renderer::*;
@@ -51,6 +53,7 @@ pub use util::arc_cow::ArcCow;
 pub use view::*;
 pub use window::*;
 
+use derive_more::{Deref, DerefMut};
 use std::{
     any::{Any, TypeId},
     mem,
@@ -180,7 +183,7 @@ impl<T> Flatten<T> for Result<T> {
     }
 }
 
-#[derive(Clone, Eq, PartialEq, Hash)]
+#[derive(Deref, DerefMut, Eq, PartialEq, Hash, Clone)]
 pub struct SharedString(ArcCow<'static, str>);
 
 impl Default for SharedString {

crates/gpui3/src/hover.rs 🔗

@@ -0,0 +1,25 @@
+use crate::{SharedString, StyleRefinement};
+
+pub trait Hover {
+    fn set_hover_style(&mut self, group_name: Option<SharedString>, style: StyleRefinement);
+
+    fn hover(mut self, f: impl FnOnce(StyleRefinement) -> StyleRefinement) -> Self
+    where
+        Self: Sized,
+    {
+        self.set_hover_style(None, f(StyleRefinement::default()));
+        self
+    }
+
+    fn group_hover(
+        mut self,
+        group_name: impl Into<SharedString>,
+        f: impl FnOnce(StyleRefinement) -> StyleRefinement,
+    ) -> Self
+    where
+        Self: Sized,
+    {
+        self.set_hover_style(Some(group_name.into()), f(StyleRefinement::default()));
+        self
+    }
+}

crates/gpui3/src/interactive.rs 🔗

@@ -1,17 +1,21 @@
+use smallvec::SmallVec;
+
 use crate::{
-    Bounds, DispatchPhase, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, Pixels,
-    ScrollWheelEvent, ViewContext,
+    Bounds, DispatchPhase, Element, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent,
+    Pixels, ScrollWheelEvent, ViewContext,
 };
-use smallvec::SmallVec;
 use std::sync::Arc;
 
-pub trait Interactive<S: 'static + Send + Sync> {
-    fn listeners(&mut self) -> &mut MouseEventListeners<S>;
+pub trait Interactive: Element {
+    fn listeners(&mut self) -> &mut MouseEventListeners<Self::ViewState>;
 
     fn on_mouse_down(
         mut self,
         button: MouseButton,
-        handler: impl Fn(&mut S, &MouseDownEvent, &mut ViewContext<S>) + Send + Sync + 'static,
+        handler: impl Fn(&mut Self::ViewState, &MouseDownEvent, &mut ViewContext<Self::ViewState>)
+            + Send
+            + Sync
+            + 'static,
     ) -> Self
     where
         Self: Sized,
@@ -21,7 +25,7 @@ pub trait Interactive<S: 'static + Send + Sync> {
             .push(Arc::new(move |view, event, bounds, phase, cx| {
                 if phase == DispatchPhase::Bubble
                     && event.button == button
-                    && bounds.contains_point(event.position)
+                    && bounds.contains_point(&event.position)
                 {
                     handler(view, event, cx)
                 }
@@ -32,7 +36,10 @@ pub trait Interactive<S: 'static + Send + Sync> {
     fn on_mouse_up(
         mut self,
         button: MouseButton,
-        handler: impl Fn(&mut S, &MouseUpEvent, &mut ViewContext<S>) + Send + Sync + 'static,
+        handler: impl Fn(&mut Self::ViewState, &MouseUpEvent, &mut ViewContext<Self::ViewState>)
+            + Send
+            + Sync
+            + 'static,
     ) -> Self
     where
         Self: Sized,
@@ -42,7 +49,7 @@ pub trait Interactive<S: 'static + Send + Sync> {
             .push(Arc::new(move |view, event, bounds, phase, cx| {
                 if phase == DispatchPhase::Bubble
                     && event.button == button
-                    && bounds.contains_point(event.position)
+                    && bounds.contains_point(&event.position)
                 {
                     handler(view, event, cx)
                 }
@@ -53,7 +60,10 @@ pub trait Interactive<S: 'static + Send + Sync> {
     fn on_mouse_down_out(
         mut self,
         button: MouseButton,
-        handler: impl Fn(&mut S, &MouseDownEvent, &mut ViewContext<S>) + Send + Sync + 'static,
+        handler: impl Fn(&mut Self::ViewState, &MouseDownEvent, &mut ViewContext<Self::ViewState>)
+            + Send
+            + Sync
+            + 'static,
     ) -> Self
     where
         Self: Sized,
@@ -63,7 +73,7 @@ pub trait Interactive<S: 'static + Send + Sync> {
             .push(Arc::new(move |view, event, bounds, phase, cx| {
                 if phase == DispatchPhase::Capture
                     && event.button == button
-                    && !bounds.contains_point(event.position)
+                    && !bounds.contains_point(&event.position)
                 {
                     handler(view, event, cx)
                 }
@@ -74,7 +84,10 @@ pub trait Interactive<S: 'static + Send + Sync> {
     fn on_mouse_up_out(
         mut self,
         button: MouseButton,
-        handler: impl Fn(&mut S, &MouseUpEvent, &mut ViewContext<S>) + Send + Sync + 'static,
+        handler: impl Fn(&mut Self::ViewState, &MouseUpEvent, &mut ViewContext<Self::ViewState>)
+            + Send
+            + Sync
+            + 'static,
     ) -> Self
     where
         Self: Sized,
@@ -84,7 +97,7 @@ pub trait Interactive<S: 'static + Send + Sync> {
             .push(Arc::new(move |view, event, bounds, phase, cx| {
                 if phase == DispatchPhase::Capture
                     && event.button == button
-                    && !bounds.contains_point(event.position)
+                    && !bounds.contains_point(&event.position)
                 {
                     handler(view, event, cx);
                 }
@@ -94,7 +107,10 @@ pub trait Interactive<S: 'static + Send + Sync> {
 
     fn on_mouse_move(
         mut self,
-        handler: impl Fn(&mut S, &MouseMoveEvent, &mut ViewContext<S>) + Send + Sync + 'static,
+        handler: impl Fn(&mut Self::ViewState, &MouseMoveEvent, &mut ViewContext<Self::ViewState>)
+            + Send
+            + Sync
+            + 'static,
     ) -> Self
     where
         Self: Sized,
@@ -102,7 +118,7 @@ pub trait Interactive<S: 'static + Send + Sync> {
         self.listeners()
             .mouse_move
             .push(Arc::new(move |view, event, bounds, phase, cx| {
-                if phase == DispatchPhase::Bubble && bounds.contains_point(event.position) {
+                if phase == DispatchPhase::Bubble && bounds.contains_point(&event.position) {
                     handler(view, event, cx);
                 }
             }));
@@ -111,7 +127,10 @@ pub trait Interactive<S: 'static + Send + Sync> {
 
     fn on_scroll_wheel(
         mut self,
-        handler: impl Fn(&mut S, &ScrollWheelEvent, &mut ViewContext<S>) + Send + Sync + 'static,
+        handler: impl Fn(&mut Self::ViewState, &ScrollWheelEvent, &mut ViewContext<Self::ViewState>)
+            + Send
+            + Sync
+            + 'static,
     ) -> Self
     where
         Self: Sized,
@@ -119,7 +138,7 @@ pub trait Interactive<S: 'static + Send + Sync> {
         self.listeners()
             .scroll_wheel
             .push(Arc::new(move |view, event, bounds, phase, cx| {
-                if phase == DispatchPhase::Bubble && bounds.contains_point(event.position) {
+                if phase == DispatchPhase::Bubble && bounds.contains_point(&event.position) {
                     handler(view, event, cx);
                 }
             }));
@@ -127,6 +146,24 @@ pub trait Interactive<S: 'static + Send + Sync> {
     }
 }
 
+pub trait Click: Interactive {
+    fn on_click(
+        mut self,
+        handler: impl Fn(&mut Self::ViewState, &MouseClickEvent, &mut ViewContext<Self::ViewState>)
+            + Send
+            + Sync
+            + 'static,
+    ) -> Self
+    where
+        Self: Sized,
+    {
+        self.listeners()
+            .mouse_click
+            .push(Arc::new(move |view, event, cx| handler(view, event, cx)));
+        self
+    }
+}
+
 type MouseDownHandler<V> = Arc<
     dyn Fn(&mut V, &MouseDownEvent, &Bounds<Pixels>, DispatchPhase, &mut ViewContext<V>)
         + Send
@@ -139,6 +176,8 @@ type MouseUpHandler<V> = Arc<
         + Sync
         + 'static,
 >;
+type MouseClickHandler<V> =
+    Arc<dyn Fn(&mut V, &MouseClickEvent, &mut ViewContext<V>) + Send + Sync + 'static>;
 
 type MouseMoveHandler<V> = Arc<
     dyn Fn(&mut V, &MouseMoveEvent, &Bounds<Pixels>, DispatchPhase, &mut ViewContext<V>)
@@ -154,45 +193,26 @@ type ScrollWheelHandler<V> = Arc<
 >;
 
 pub struct MouseEventListeners<V: 'static> {
-    mouse_down: SmallVec<[MouseDownHandler<V>; 2]>,
-    mouse_up: SmallVec<[MouseUpHandler<V>; 2]>,
-    mouse_move: SmallVec<[MouseMoveHandler<V>; 2]>,
-    scroll_wheel: SmallVec<[ScrollWheelHandler<V>; 2]>,
-}
-
-impl<S: Send + Sync + 'static> MouseEventListeners<S> {
-    pub fn paint(&self, bounds: Bounds<Pixels>, cx: &mut ViewContext<S>) {
-        for handler in self.mouse_down.iter().cloned() {
-            cx.on_mouse_event(move |view, event: &MouseDownEvent, phase, cx| {
-                handler(view, event, &bounds, phase, cx);
-            })
-        }
-        for handler in self.mouse_up.iter().cloned() {
-            cx.on_mouse_event(move |view, event: &MouseUpEvent, phase, cx| {
-                handler(view, event, &bounds, phase, cx);
-            })
-        }
-        for handler in self.mouse_move.iter().cloned() {
-            cx.on_mouse_event(move |view, event: &MouseMoveEvent, phase, cx| {
-                handler(view, event, &bounds, phase, cx);
-            })
-        }
-
-        for handler in self.scroll_wheel.iter().cloned() {
-            cx.on_mouse_event(move |view, event: &ScrollWheelEvent, phase, cx| {
-                handler(view, event, &bounds, phase, cx);
-            })
-        }
-    }
+    pub mouse_down: SmallVec<[MouseDownHandler<V>; 2]>,
+    pub mouse_up: SmallVec<[MouseUpHandler<V>; 2]>,
+    pub mouse_click: SmallVec<[MouseClickHandler<V>; 2]>,
+    pub mouse_move: SmallVec<[MouseMoveHandler<V>; 2]>,
+    pub scroll_wheel: SmallVec<[ScrollWheelHandler<V>; 2]>,
 }
 
 impl<V> Default for MouseEventListeners<V> {
     fn default() -> Self {
         Self {
-            mouse_down: Default::default(),
-            mouse_up: Default::default(),
-            mouse_move: Default::default(),
-            scroll_wheel: Default::default(),
+            mouse_down: SmallVec::new(),
+            mouse_up: SmallVec::new(),
+            mouse_click: SmallVec::new(),
+            mouse_move: SmallVec::new(),
+            scroll_wheel: SmallVec::new(),
         }
     }
 }
+
+pub struct MouseClickEvent {
+    pub down: MouseDownEvent,
+    pub up: MouseUpEvent,
+}

crates/gpui3/src/platform.rs 🔗

@@ -5,9 +5,9 @@ mod mac;
 mod test;
 
 use crate::{
-    AnyWindowHandle, Bounds, DevicePixels, Event, Executor, Font, FontId, FontMetrics,
-    GlobalPixels, GlyphId, Pixels, Point, RenderGlyphParams, RenderImageParams, RenderSvgParams,
-    Result, Scene, ShapedLine, SharedString, Size,
+    AnyWindowHandle, Bounds, DevicePixels, Event, Executor, Font, FontId, FontMetrics, FontRun,
+    GlobalPixels, GlyphId, LineLayout, Pixels, Point, RenderGlyphParams, RenderImageParams,
+    RenderSvgParams, Result, Scene, SharedString, Size,
 };
 use anyhow::anyhow;
 use async_task::Runnable;
@@ -171,7 +171,7 @@ pub trait PlatformTextSystem: Send + Sync {
     fn glyph_for_char(&self, font_id: FontId, ch: char) -> Option<GlyphId>;
     fn glyph_raster_bounds(&self, params: &RenderGlyphParams) -> Result<Bounds<DevicePixels>>;
     fn rasterize_glyph(&self, params: &RenderGlyphParams) -> Result<(Size<DevicePixels>, Vec<u8>)>;
-    fn layout_line(&self, text: &str, font_size: Pixels, runs: &[(usize, FontId)]) -> ShapedLine;
+    fn layout_line(&self, text: &str, font_size: Pixels, runs: &[FontRun]) -> 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, DevicePixels, Font, FontFeatures, FontId, FontMetrics, FontStyle,
-    FontWeight, GlyphId, Pixels, PlatformTextSystem, Point, RenderGlyphParams, Result, ShapedGlyph,
-    ShapedLine, ShapedRun, SharedString, Size, SUBPIXEL_VARIANTS,
+    point, px, size, Bounds, DevicePixels, Font, FontFeatures, FontId, FontMetrics, FontRun,
+    FontStyle, FontWeight, GlyphId, LineLayout, Pixels, PlatformTextSystem, Point,
+    RenderGlyphParams, Result, ShapedGlyph, ShapedRun, SharedString, Size, SUBPIXEL_VARIANTS,
 };
 use anyhow::anyhow;
 use cocoa::appkit::{CGFloat, CGPoint};
@@ -149,12 +149,7 @@ impl PlatformTextSystem for MacTextSystem {
         self.0.read().rasterize_glyph(glyph_id)
     }
 
-    fn layout_line(
-        &self,
-        text: &str,
-        font_size: Pixels,
-        font_runs: &[(usize, FontId)],
-    ) -> ShapedLine {
+    fn layout_line(&self, text: &str, font_size: Pixels, font_runs: &[FontRun]) -> LineLayout {
         self.0.write().layout_line(text, font_size, font_runs)
     }
 
@@ -337,12 +332,7 @@ impl MacTextSystemState {
         }
     }
 
-    fn layout_line(
-        &mut self,
-        text: &str,
-        font_size: Pixels,
-        font_runs: &[(usize, FontId)],
-    ) -> ShapedLine {
+    fn layout_line(&mut self, text: &str, font_size: Pixels, font_runs: &[FontRun]) -> LineLayout {
         // Construct the attributed string, converting UTF8 ranges to UTF16 ranges.
         let mut string = CFMutableAttributedString::new();
         {
@@ -350,8 +340,8 @@ impl MacTextSystemState {
             let utf16_line_len = string.char_len() as usize;
 
             let mut ix_converter = StringIndexConverter::new(text);
-            for (run_len, font_id) in font_runs {
-                let utf8_end = ix_converter.utf8_ix + run_len;
+            for run in font_runs {
+                let utf8_end = ix_converter.utf8_ix + run.len;
                 let utf16_start = ix_converter.utf16_ix;
 
                 if utf16_start >= utf16_line_len {
@@ -364,7 +354,7 @@ impl MacTextSystemState {
                 let cf_range =
                     CFRange::init(utf16_start as isize, (utf16_end - utf16_start) as isize);
 
-                let font: &FontKitFont = &self.fonts[font_id.0];
+                let font: &FontKitFont = &self.fonts[run.font_id.0];
                 unsafe {
                     string.set_attribute(
                         cf_range,
@@ -394,7 +384,7 @@ impl MacTextSystemState {
             let font_id = self.id_for_native_font(font);
 
             let mut ix_converter = StringIndexConverter::new(text);
-            let mut glyphs = Vec::new();
+            let mut glyphs = SmallVec::new();
             for ((glyph_id, position), glyph_utf16_ix) in run
                 .glyphs()
                 .iter()
@@ -415,13 +405,12 @@ impl MacTextSystemState {
         }
 
         let typographic_bounds = line.get_typographic_bounds();
-        ShapedLine {
+        LineLayout {
             width: typographic_bounds.width.into(),
             ascent: typographic_bounds.ascent.into(),
             descent: typographic_bounds.descent.into(),
             runs,
             font_size,
-            len: text.len(),
         }
     }
 

crates/gpui3/src/style.rs 🔗

@@ -1,8 +1,8 @@
 use crate::{
-    phi, point, rems, AbsoluteLength, BorrowAppContext, BorrowWindow, Bounds, ContentMask, Corners,
-    CornersRefinement, DefiniteLength, Edges, EdgesRefinement, Font, FontFeatures, FontStyle,
-    FontWeight, Hsla, Length, Pixels, Point, PointRefinement, Rems, Result, RunStyle, SharedString,
-    Size, SizeRefinement, ViewContext, WindowContext,
+    black, phi, point, rems, AbsoluteLength, BorrowAppContext, BorrowWindow, Bounds, ContentMask,
+    Corners, CornersRefinement, DefiniteLength, Edges, EdgesRefinement, Font, FontFeatures,
+    FontStyle, FontWeight, Hsla, Length, Pixels, Point, PointRefinement, Rems, Result,
+    SharedString, Size, SizeRefinement, Styled, TextRun, ViewContext, WindowContext,
 };
 use refineable::{Cascade, Refineable};
 use smallvec::SmallVec;
@@ -83,7 +83,7 @@ pub struct Style {
     pub flex_shrink: f32,
 
     /// The fill color of this element
-    pub fill: Option<Fill>,
+    pub background: Option<Fill>,
 
     /// The border color of this element
     pub border_color: Option<Hsla>,
@@ -101,6 +101,12 @@ pub struct Style {
     pub z_index: Option<u32>,
 }
 
+impl Styled for StyleRefinement {
+    fn style(&mut self) -> &mut StyleRefinement {
+        self
+    }
+}
+
 #[derive(Clone, Debug)]
 pub struct BoxShadow {
     pub color: Hsla,
@@ -125,8 +131,8 @@ pub struct TextStyle {
 impl Default for TextStyle {
     fn default() -> Self {
         TextStyle {
-            color: Hsla::default(),
-            font_family: SharedString::default(),
+            color: black(),
+            font_family: "Helvetica".into(), // todo!("Get a font we know exists on the system")
             font_features: FontFeatures::default(),
             font_size: rems(1.),
             line_height: phi(),
@@ -161,8 +167,9 @@ impl TextStyle {
         Ok(self)
     }
 
-    pub fn to_run(&self) -> RunStyle {
-        RunStyle {
+    pub fn to_run(&self, len: usize) -> TextRun {
+        TextRun {
+            len,
             font: Font {
                 family: self.font_family.clone(),
                 features: Default::default(),
@@ -256,7 +263,7 @@ impl Style {
             );
         });
 
-        let background_color = self.fill.as_ref().and_then(Fill::color);
+        let background_color = self.background.as_ref().and_then(Fill::color);
         if background_color.is_some() || self.is_border_visible() {
             cx.stack(1, |cx| {
                 cx.paint_quad(
@@ -307,7 +314,7 @@ impl Default for Style {
             flex_grow: 0.0,
             flex_shrink: 1.0,
             flex_basis: Length::Auto,
-            fill: None,
+            background: None,
             border_color: None,
             corner_radii: Corners::default(),
             box_shadow: Default::default(),

crates/gpui3/src/style_helpers.rs 🔗

@@ -1,387 +0,0 @@
-use crate::{
-    self as gpui3, hsla, point, px, relative, rems, AlignItems, BoxShadow, Display, Fill,
-    FlexDirection, Hsla, JustifyContent, Length, Position, SharedString, Style, StyleRefinement,
-    Styled, TextStyleRefinement,
-};
-use smallvec::smallvec;
-
-pub trait StyleHelpers: Sized + Styled<Style = Style> {
-    gpui3_macros::style_helpers!();
-
-    /// Sets the size of the element to the full width and height.
-    fn full(mut self) -> Self {
-        self.declared_style().size.width = Some(relative(1.).into());
-        self.declared_style().size.height = Some(relative(1.).into());
-        self
-    }
-
-    /// Sets the position of the element to `relative`.
-    /// [Docs](https://tailwindcss.com/docs/position)
-    fn relative(mut self) -> Self {
-        self.declared_style().position = Some(Position::Relative);
-        self
-    }
-
-    /// Sets the position of the element to `absolute`.
-    /// [Docs](https://tailwindcss.com/docs/position)
-    fn absolute(mut self) -> Self {
-        self.declared_style().position = Some(Position::Absolute);
-        self
-    }
-
-    /// Sets the display type of the element to `block`.
-    /// [Docs](https://tailwindcss.com/docs/display)
-    fn block(mut self) -> Self {
-        self.declared_style().display = Some(Display::Block);
-        self
-    }
-
-    /// Sets the display type of the element to `flex`.
-    /// [Docs](https://tailwindcss.com/docs/display)
-    fn flex(mut self) -> Self {
-        self.declared_style().display = Some(Display::Flex);
-        self
-    }
-
-    /// Sets the flex directino of the element to `column`.
-    /// [Docs](https://tailwindcss.com/docs/flex-direction#column)
-    fn flex_col(mut self) -> Self {
-        self.declared_style().flex_direction = Some(FlexDirection::Column);
-        self
-    }
-
-    /// Sets the flex directino of the element to `row`.
-    /// [Docs](https://tailwindcss.com/docs/flex-direction#row)
-    fn flex_row(mut self) -> Self {
-        self.declared_style().flex_direction = Some(FlexDirection::Row);
-        self
-    }
-
-    /// Sets the element to allow a flex item to grow and shrink as needed, ignoring its initial size.
-    /// [Docs](https://tailwindcss.com/docs/flex#flex-1)
-    fn flex_1(mut self) -> Self {
-        self.declared_style().flex_grow = Some(1.);
-        self.declared_style().flex_shrink = Some(1.);
-        self.declared_style().flex_basis = Some(relative(0.).into());
-        self
-    }
-
-    /// Sets the element to allow a flex item to grow and shrink, taking into account its initial size.
-    /// [Docs](https://tailwindcss.com/docs/flex#auto)
-    fn flex_auto(mut self) -> Self {
-        self.declared_style().flex_grow = Some(1.);
-        self.declared_style().flex_shrink = Some(1.);
-        self.declared_style().flex_basis = Some(Length::Auto);
-        self
-    }
-
-    /// Sets the element to allow a flex item to shrink but not grow, taking into account its initial size.
-    /// [Docs](https://tailwindcss.com/docs/flex#initial)
-    fn flex_initial(mut self) -> Self {
-        self.declared_style().flex_grow = Some(0.);
-        self.declared_style().flex_shrink = Some(1.);
-        self.declared_style().flex_basis = Some(Length::Auto);
-        self
-    }
-
-    /// Sets the element to prevent a flex item from growing or shrinking.
-    /// [Docs](https://tailwindcss.com/docs/flex#none)
-    fn flex_none(mut self) -> Self {
-        self.declared_style().flex_grow = Some(0.);
-        self.declared_style().flex_shrink = Some(0.);
-        self
-    }
-
-    /// Sets the element to allow a flex item to grow to fill any available space.
-    /// [Docs](https://tailwindcss.com/docs/flex-grow)
-    fn grow(mut self) -> Self {
-        self.declared_style().flex_grow = Some(1.);
-        self
-    }
-
-    fn items_start(mut self) -> Self {
-        self.declared_style().align_items = Some(AlignItems::FlexStart);
-        self
-    }
-
-    fn items_end(mut self) -> Self {
-        self.declared_style().align_items = Some(AlignItems::FlexEnd);
-        self
-    }
-
-    fn items_center(mut self) -> Self {
-        self.declared_style().align_items = Some(AlignItems::Center);
-        self
-    }
-
-    fn justify_between(mut self) -> Self {
-        self.declared_style().justify_content = Some(JustifyContent::SpaceBetween);
-        self
-    }
-
-    fn justify_center(mut self) -> Self {
-        self.declared_style().justify_content = Some(JustifyContent::Center);
-        self
-    }
-
-    fn justify_start(mut self) -> Self {
-        self.declared_style().justify_content = Some(JustifyContent::Start);
-        self
-    }
-
-    fn justify_end(mut self) -> Self {
-        self.declared_style().justify_content = Some(JustifyContent::End);
-        self
-    }
-
-    fn justify_around(mut self) -> Self {
-        self.declared_style().justify_content = Some(JustifyContent::SpaceAround);
-        self
-    }
-
-    fn fill<F>(mut self, fill: F) -> Self
-    where
-        F: Into<Fill>,
-        Self: Sized,
-    {
-        self.declared_style().fill = Some(fill.into());
-        self
-    }
-
-    fn border_color<C>(mut self, border_color: C) -> Self
-    where
-        C: Into<Hsla>,
-        Self: Sized,
-    {
-        self.declared_style().border_color = Some(border_color.into());
-        self
-    }
-
-    fn shadow(mut self) -> Self {
-        self.declared_style().box_shadow = Some(smallvec![
-            BoxShadow {
-                color: hsla(0., 0., 0., 0.1),
-                offset: point(px(0.), px(1.)),
-                blur_radius: px(3.),
-                spread_radius: px(0.),
-            },
-            BoxShadow {
-                color: hsla(0., 0., 0., 0.1),
-                offset: point(px(0.), px(1.)),
-                blur_radius: px(2.),
-                spread_radius: px(-1.),
-            }
-        ]);
-        self
-    }
-
-    fn shadow_none(mut self) -> Self {
-        self.declared_style().box_shadow = Some(Default::default());
-        self
-    }
-
-    fn shadow_sm(mut self) -> Self {
-        self.declared_style().box_shadow = Some(smallvec![BoxShadow {
-            color: hsla(0., 0., 0., 0.05),
-            offset: point(px(0.), px(1.)),
-            blur_radius: px(2.),
-            spread_radius: px(0.),
-        }]);
-        self
-    }
-
-    fn shadow_md(mut self) -> Self {
-        self.declared_style().box_shadow = Some(smallvec![
-            BoxShadow {
-                color: hsla(0.5, 0., 0., 0.1),
-                offset: point(px(0.), px(4.)),
-                blur_radius: px(6.),
-                spread_radius: px(-1.),
-            },
-            BoxShadow {
-                color: hsla(0., 0., 0., 0.1),
-                offset: point(px(0.), px(2.)),
-                blur_radius: px(4.),
-                spread_radius: px(-2.),
-            }
-        ]);
-        self
-    }
-
-    fn shadow_lg(mut self) -> Self {
-        self.declared_style().box_shadow = Some(smallvec![
-            BoxShadow {
-                color: hsla(0., 0., 0., 0.1),
-                offset: point(px(0.), px(10.)),
-                blur_radius: px(15.),
-                spread_radius: px(-3.),
-            },
-            BoxShadow {
-                color: hsla(0., 0., 0., 0.1),
-                offset: point(px(0.), px(4.)),
-                blur_radius: px(6.),
-                spread_radius: px(-4.),
-            }
-        ]);
-        self
-    }
-
-    fn shadow_xl(mut self) -> Self {
-        self.declared_style().box_shadow = Some(smallvec![
-            BoxShadow {
-                color: hsla(0., 0., 0., 0.1),
-                offset: point(px(0.), px(20.)),
-                blur_radius: px(25.),
-                spread_radius: px(-5.),
-            },
-            BoxShadow {
-                color: hsla(0., 0., 0., 0.1),
-                offset: point(px(0.), px(8.)),
-                blur_radius: px(10.),
-                spread_radius: px(-6.),
-            }
-        ]);
-        self
-    }
-
-    fn shadow_2xl(mut self) -> Self {
-        self.declared_style().box_shadow = Some(smallvec![BoxShadow {
-            color: hsla(0., 0., 0., 0.25),
-            offset: point(px(0.), px(25.)),
-            blur_radius: px(50.),
-            spread_radius: px(-12.),
-        }]);
-        self
-    }
-
-    fn text_style(&mut self) -> &mut Option<TextStyleRefinement> {
-        let style: &mut StyleRefinement = self.declared_style();
-        &mut style.text
-    }
-
-    fn text_color(mut self, color: impl Into<Hsla>) -> Self {
-        self.text_style().get_or_insert_with(Default::default).color = Some(color.into());
-        self
-    }
-
-    fn text_xs(mut self) -> Self {
-        self.text_style()
-            .get_or_insert_with(Default::default)
-            .font_size = Some(rems(0.75));
-        self
-    }
-
-    fn text_sm(mut self) -> Self {
-        self.text_style()
-            .get_or_insert_with(Default::default)
-            .font_size = Some(rems(0.875));
-        self
-    }
-
-    fn text_base(mut self) -> Self {
-        self.text_style()
-            .get_or_insert_with(Default::default)
-            .font_size = Some(rems(1.0));
-        self
-    }
-
-    fn text_lg(mut self) -> Self {
-        self.text_style()
-            .get_or_insert_with(Default::default)
-            .font_size = Some(rems(1.125));
-        self
-    }
-
-    fn text_xl(mut self) -> Self {
-        self.text_style()
-            .get_or_insert_with(Default::default)
-            .font_size = Some(rems(1.25));
-        self
-    }
-
-    fn text_2xl(mut self) -> Self {
-        self.text_style()
-            .get_or_insert_with(Default::default)
-            .font_size = Some(rems(1.5));
-        self
-    }
-
-    fn text_3xl(mut self) -> Self {
-        self.text_style()
-            .get_or_insert_with(Default::default)
-            .font_size = Some(rems(1.875));
-        self
-    }
-
-    fn text_decoration_none(mut self) -> Self {
-        self.text_style()
-            .get_or_insert_with(Default::default)
-            .underline = None;
-        self
-    }
-
-    fn text_decoration_color(mut self, color: impl Into<Hsla>) -> Self {
-        let style = self.text_style().get_or_insert_with(Default::default);
-        let underline = style.underline.get_or_insert_with(Default::default);
-        underline.color = Some(color.into());
-        self
-    }
-
-    fn text_decoration_solid(mut self) -> Self {
-        let style = self.text_style().get_or_insert_with(Default::default);
-        let underline = style.underline.get_or_insert_with(Default::default);
-        underline.wavy = false;
-        self
-    }
-
-    fn text_decoration_wavy(mut self) -> Self {
-        let style = self.text_style().get_or_insert_with(Default::default);
-        let underline = style.underline.get_or_insert_with(Default::default);
-        underline.wavy = true;
-        self
-    }
-
-    fn text_decoration_0(mut self) -> Self {
-        let style = self.text_style().get_or_insert_with(Default::default);
-        let underline = style.underline.get_or_insert_with(Default::default);
-        underline.thickness = px(0.);
-        self
-    }
-
-    fn text_decoration_1(mut self) -> Self {
-        let style = self.text_style().get_or_insert_with(Default::default);
-        let underline = style.underline.get_or_insert_with(Default::default);
-        underline.thickness = px(1.);
-        self
-    }
-
-    fn text_decoration_2(mut self) -> Self {
-        let style = self.text_style().get_or_insert_with(Default::default);
-        let underline = style.underline.get_or_insert_with(Default::default);
-        underline.thickness = px(2.);
-        self
-    }
-
-    fn text_decoration_4(mut self) -> Self {
-        let style = self.text_style().get_or_insert_with(Default::default);
-        let underline = style.underline.get_or_insert_with(Default::default);
-        underline.thickness = px(4.);
-        self
-    }
-
-    fn text_decoration_8(mut self) -> Self {
-        let style = self.text_style().get_or_insert_with(Default::default);
-        let underline = style.underline.get_or_insert_with(Default::default);
-        underline.thickness = px(8.);
-        self
-    }
-
-    fn font(mut self, family_name: impl Into<SharedString>) -> Self {
-        self.text_style()
-            .get_or_insert_with(Default::default)
-            .font_family = Some(family_name.into());
-        self
-    }
-}
-
-impl<E: Styled<Style = Style>> StyleHelpers for E {}

crates/gpui3/src/styled.rs 🔗

@@ -1,48 +1,522 @@
-use crate::{Cascade, Hoverable, Pressable, Refineable, SharedString};
+use crate::{
+    self as gpui3, hsla, point, px, relative, rems, AlignItems, Display, Fill, FlexDirection, Hsla,
+    JustifyContent, Length, Position, SharedString, StyleRefinement,
+};
+use crate::{BoxShadow, TextStyleRefinement};
+use smallvec::smallvec;
 
 pub trait Styled {
-    type Style: 'static + Refineable + Send + Sync + Default;
+    fn style(&mut self) -> &mut StyleRefinement;
 
-    fn style_cascade(&mut self) -> &mut Cascade<Self::Style>;
-    fn declared_style(&mut self) -> &mut <Self::Style as Refineable>::Refinement;
+    gpui3_macros::style_helpers!();
 
-    fn computed_style(&mut self) -> Self::Style {
-        Self::Style::default().refined(self.style_cascade().merged())
+    /// Sets the size of the element to the full width and height.
+    fn full(mut self) -> Self
+    where
+        Self: Sized,
+    {
+        self.style().size.width = Some(relative(1.).into());
+        self.style().size.height = Some(relative(1.).into());
+        self
+    }
+
+    /// Sets the position of the element to `relative`.
+    /// [Docs](https://tailwindcss.com/docs/position)
+    fn relative(mut self) -> Self
+    where
+        Self: Sized,
+    {
+        self.style().position = Some(Position::Relative);
+        self
+    }
+
+    /// Sets the position of the element to `absolute`.
+    /// [Docs](https://tailwindcss.com/docs/position)
+    fn absolute(mut self) -> Self
+    where
+        Self: Sized,
+    {
+        self.style().position = Some(Position::Absolute);
+        self
+    }
+
+    /// Sets the display type of the element to `block`.
+    /// [Docs](https://tailwindcss.com/docs/display)
+    fn block(mut self) -> Self
+    where
+        Self: Sized,
+    {
+        self.style().display = Some(Display::Block);
+        self
+    }
+
+    /// Sets the display type of the element to `flex`.
+    /// [Docs](https://tailwindcss.com/docs/display)
+    fn flex(mut self) -> Self
+    where
+        Self: Sized,
+    {
+        self.style().display = Some(Display::Flex);
+        self
+    }
+
+    /// Sets the flex directino of the element to `column`.
+    /// [Docs](https://tailwindcss.com/docs/flex-direction#column)
+    fn flex_col(mut self) -> Self
+    where
+        Self: Sized,
+    {
+        self.style().flex_direction = Some(FlexDirection::Column);
+        self
+    }
+
+    /// Sets the flex directino of the element to `row`.
+    /// [Docs](https://tailwindcss.com/docs/flex-direction#row)
+    fn flex_row(mut self) -> Self
+    where
+        Self: Sized,
+    {
+        self.style().flex_direction = Some(FlexDirection::Row);
+        self
+    }
+
+    /// Sets the element to allow a flex item to grow and shrink as needed, ignoring its initial size.
+    /// [Docs](https://tailwindcss.com/docs/flex#flex-1)
+    fn flex_1(mut self) -> Self
+    where
+        Self: Sized,
+    {
+        self.style().flex_grow = Some(1.);
+        self.style().flex_shrink = Some(1.);
+        self.style().flex_basis = Some(relative(0.).into());
+        self
+    }
+
+    /// Sets the element to allow a flex item to grow and shrink, taking into account its initial size.
+    /// [Docs](https://tailwindcss.com/docs/flex#auto)
+    fn flex_auto(mut self) -> Self
+    where
+        Self: Sized,
+    {
+        self.style().flex_grow = Some(1.);
+        self.style().flex_shrink = Some(1.);
+        self.style().flex_basis = Some(Length::Auto);
+        self
+    }
+
+    /// Sets the element to allow a flex item to shrink but not grow, taking into account its initial size.
+    /// [Docs](https://tailwindcss.com/docs/flex#initial)
+    fn flex_initial(mut self) -> Self
+    where
+        Self: Sized,
+    {
+        self.style().flex_grow = Some(0.);
+        self.style().flex_shrink = Some(1.);
+        self.style().flex_basis = Some(Length::Auto);
+        self
+    }
+
+    /// Sets the element to prevent a flex item from growing or shrinking.
+    /// [Docs](https://tailwindcss.com/docs/flex#none)
+    fn flex_none(mut self) -> Self
+    where
+        Self: Sized,
+    {
+        self.style().flex_grow = Some(0.);
+        self.style().flex_shrink = Some(0.);
+        self
+    }
+
+    /// Sets the element to allow a flex item to grow to fill any available space.
+    /// [Docs](https://tailwindcss.com/docs/flex-grow)
+    fn grow(mut self) -> Self
+    where
+        Self: Sized,
+    {
+        self.style().flex_grow = Some(1.);
+        self
+    }
+
+    fn items_start(mut self) -> Self
+    where
+        Self: Sized,
+    {
+        self.style().align_items = Some(AlignItems::FlexStart);
+        self
+    }
+
+    fn items_end(mut self) -> Self
+    where
+        Self: Sized,
+    {
+        self.style().align_items = Some(AlignItems::FlexEnd);
+        self
+    }
+
+    fn items_center(mut self) -> Self
+    where
+        Self: Sized,
+    {
+        self.style().align_items = Some(AlignItems::Center);
+        self
+    }
+
+    fn justify_between(mut self) -> Self
+    where
+        Self: Sized,
+    {
+        self.style().justify_content = Some(JustifyContent::SpaceBetween);
+        self
+    }
+
+    fn justify_center(mut self) -> Self
+    where
+        Self: Sized,
+    {
+        self.style().justify_content = Some(JustifyContent::Center);
+        self
+    }
+
+    fn justify_start(mut self) -> Self
+    where
+        Self: Sized,
+    {
+        self.style().justify_content = Some(JustifyContent::Start);
+        self
+    }
+
+    fn justify_end(mut self) -> Self
+    where
+        Self: Sized,
+    {
+        self.style().justify_content = Some(JustifyContent::End);
+        self
+    }
+
+    fn justify_around(mut self) -> Self
+    where
+        Self: Sized,
+    {
+        self.style().justify_content = Some(JustifyContent::SpaceAround);
+        self
+    }
+
+    fn bg<F>(mut self, fill: F) -> Self
+    where
+        F: Into<Fill>,
+        Self: Sized,
+    {
+        self.style().background = Some(fill.into());
+        self
+    }
+
+    fn border_color<C>(mut self, border_color: C) -> Self
+    where
+        C: Into<Hsla>,
+        Self: Sized,
+    {
+        self.style().border_color = Some(border_color.into());
+        self
+    }
+
+    fn shadow(mut self) -> Self
+    where
+        Self: Sized,
+    {
+        self.style().box_shadow = Some(smallvec![
+            BoxShadow {
+                color: hsla(0., 0., 0., 0.1),
+                offset: point(px(0.), px(1.)),
+                blur_radius: px(3.),
+                spread_radius: px(0.),
+            },
+            BoxShadow {
+                color: hsla(0., 0., 0., 0.1),
+                offset: point(px(0.), px(1.)),
+                blur_radius: px(2.),
+                spread_radius: px(-1.),
+            }
+        ]);
+        self
+    }
+
+    fn shadow_none(mut self) -> Self
+    where
+        Self: Sized,
+    {
+        self.style().box_shadow = Some(Default::default());
+        self
+    }
+
+    fn shadow_sm(mut self) -> Self
+    where
+        Self: Sized,
+    {
+        self.style().box_shadow = Some(smallvec::smallvec![BoxShadow {
+            color: hsla(0., 0., 0., 0.05),
+            offset: point(px(0.), px(1.)),
+            blur_radius: px(2.),
+            spread_radius: px(0.),
+        }]);
+        self
+    }
+
+    fn shadow_md(mut self) -> Self
+    where
+        Self: Sized,
+    {
+        self.style().box_shadow = Some(smallvec![
+            BoxShadow {
+                color: hsla(0.5, 0., 0., 0.1),
+                offset: point(px(0.), px(4.)),
+                blur_radius: px(6.),
+                spread_radius: px(-1.),
+            },
+            BoxShadow {
+                color: hsla(0., 0., 0., 0.1),
+                offset: point(px(0.), px(2.)),
+                blur_radius: px(4.),
+                spread_radius: px(-2.),
+            }
+        ]);
+        self
+    }
+
+    fn shadow_lg(mut self) -> Self
+    where
+        Self: Sized,
+    {
+        self.style().box_shadow = Some(smallvec![
+            BoxShadow {
+                color: hsla(0., 0., 0., 0.1),
+                offset: point(px(0.), px(10.)),
+                blur_radius: px(15.),
+                spread_radius: px(-3.),
+            },
+            BoxShadow {
+                color: hsla(0., 0., 0., 0.1),
+                offset: point(px(0.), px(4.)),
+                blur_radius: px(6.),
+                spread_radius: px(-4.),
+            }
+        ]);
+        self
+    }
+
+    fn shadow_xl(mut self) -> Self
+    where
+        Self: Sized,
+    {
+        self.style().box_shadow = Some(smallvec![
+            BoxShadow {
+                color: hsla(0., 0., 0., 0.1),
+                offset: point(px(0.), px(20.)),
+                blur_radius: px(25.),
+                spread_radius: px(-5.),
+            },
+            BoxShadow {
+                color: hsla(0., 0., 0., 0.1),
+                offset: point(px(0.), px(8.)),
+                blur_radius: px(10.),
+                spread_radius: px(-6.),
+            }
+        ]);
+        self
+    }
+
+    fn shadow_2xl(mut self) -> Self
+    where
+        Self: Sized,
+    {
+        self.style().box_shadow = Some(smallvec![BoxShadow {
+            color: hsla(0., 0., 0., 0.25),
+            offset: point(px(0.), px(25.)),
+            blur_radius: px(50.),
+            spread_radius: px(-12.),
+        }]);
+        self
+    }
+
+    fn text_style(&mut self) -> &mut Option<TextStyleRefinement> {
+        let style: &mut StyleRefinement = self.style();
+        &mut style.text
+    }
+
+    fn text_color(mut self, color: impl Into<Hsla>) -> Self
+    where
+        Self: Sized,
+    {
+        self.text_style().get_or_insert_with(Default::default).color = Some(color.into());
+        self
+    }
+
+    fn text_xs(mut self) -> Self
+    where
+        Self: Sized,
+    {
+        self.text_style()
+            .get_or_insert_with(Default::default)
+            .font_size = Some(rems(0.75));
+        self
+    }
+
+    fn text_sm(mut self) -> Self
+    where
+        Self: Sized,
+    {
+        self.text_style()
+            .get_or_insert_with(Default::default)
+            .font_size = Some(rems(0.875));
+        self
+    }
+
+    fn text_base(mut self) -> Self
+    where
+        Self: Sized,
+    {
+        self.text_style()
+            .get_or_insert_with(Default::default)
+            .font_size = Some(rems(1.0));
+        self
+    }
+
+    fn text_lg(mut self) -> Self
+    where
+        Self: Sized,
+    {
+        self.text_style()
+            .get_or_insert_with(Default::default)
+            .font_size = Some(rems(1.125));
+        self
+    }
+
+    fn text_xl(mut self) -> Self
+    where
+        Self: Sized,
+    {
+        self.text_style()
+            .get_or_insert_with(Default::default)
+            .font_size = Some(rems(1.25));
+        self
+    }
+
+    fn text_2xl(mut self) -> Self
+    where
+        Self: Sized,
+    {
+        self.text_style()
+            .get_or_insert_with(Default::default)
+            .font_size = Some(rems(1.5));
+        self
+    }
+
+    fn text_3xl(mut self) -> Self
+    where
+        Self: Sized,
+    {
+        self.text_style()
+            .get_or_insert_with(Default::default)
+            .font_size = Some(rems(1.875));
+        self
+    }
+
+    fn text_decoration_none(mut self) -> Self
+    where
+        Self: Sized,
+    {
+        self.text_style()
+            .get_or_insert_with(Default::default)
+            .underline = None;
+        self
+    }
+
+    fn text_decoration_color(mut self, color: impl Into<Hsla>) -> Self
+    where
+        Self: Sized,
+    {
+        let style = self.text_style().get_or_insert_with(Default::default);
+        let underline = style.underline.get_or_insert_with(Default::default);
+        underline.color = Some(color.into());
+        self
+    }
+
+    fn text_decoration_solid(mut self) -> Self
+    where
+        Self: Sized,
+    {
+        let style = self.text_style().get_or_insert_with(Default::default);
+        let underline = style.underline.get_or_insert_with(Default::default);
+        underline.wavy = false;
+        self
+    }
+
+    fn text_decoration_wavy(mut self) -> Self
+    where
+        Self: Sized,
+    {
+        let style = self.text_style().get_or_insert_with(Default::default);
+        let underline = style.underline.get_or_insert_with(Default::default);
+        underline.wavy = true;
+        self
+    }
+
+    fn text_decoration_0(mut self) -> Self
+    where
+        Self: Sized,
+    {
+        let style = self.text_style().get_or_insert_with(Default::default);
+        let underline = style.underline.get_or_insert_with(Default::default);
+        underline.thickness = px(0.);
+        self
+    }
+
+    fn text_decoration_1(mut self) -> Self
+    where
+        Self: Sized,
+    {
+        let style = self.text_style().get_or_insert_with(Default::default);
+        let underline = style.underline.get_or_insert_with(Default::default);
+        underline.thickness = px(1.);
+        self
     }
 
-    fn hover(self) -> Hoverable<Self>
+    fn text_decoration_2(mut self) -> Self
     where
-        Self: 'static + Sized + Send + Sync,
-        Self::Style: 'static + Refineable + Default + Send + Sync,
-        <Self::Style as Refineable>::Refinement: 'static + Default + Send + Sync,
+        Self: Sized,
     {
-        Hoverable::new(self, None)
+        let style = self.text_style().get_or_insert_with(Default::default);
+        let underline = style.underline.get_or_insert_with(Default::default);
+        underline.thickness = px(2.);
+        self
     }
 
-    fn group_hover(self, group_name: impl Into<SharedString>) -> Hoverable<Self>
+    fn text_decoration_4(mut self) -> Self
     where
-        Self: 'static + Sized + Send + Sync,
-        Self::Style: 'static + Refineable + Default + Send + Sync,
-        <Self::Style as Refineable>::Refinement: 'static + Default + Send + Sync,
+        Self: Sized,
     {
-        Hoverable::new(self, Some(group_name.into()))
+        let style = self.text_style().get_or_insert_with(Default::default);
+        let underline = style.underline.get_or_insert_with(Default::default);
+        underline.thickness = px(4.);
+        self
     }
 
-    fn active(self) -> Pressable<Self>
+    fn text_decoration_8(mut self) -> Self
     where
-        Self: 'static + Sized + Send + Sync,
-        Self::Style: 'static + Refineable + Default + Send + Sync,
-        <Self::Style as Refineable>::Refinement: 'static + Default + Send + Sync,
+        Self: Sized,
     {
-        Pressable::new(self, None)
+        let style = self.text_style().get_or_insert_with(Default::default);
+        let underline = style.underline.get_or_insert_with(Default::default);
+        underline.thickness = px(8.);
+        self
     }
 
-    fn group_active(self, group_name: impl Into<SharedString>) -> Pressable<Self>
+    fn font(mut self, family_name: impl Into<SharedString>) -> Self
     where
-        Self: 'static + Sized + Send + Sync,
-        Self::Style: 'static + Refineable + Default + Send + Sync,
-        <Self::Style as Refineable>::Refinement: 'static + Default + Send + Sync,
+        Self: Sized,
     {
-        Pressable::new(self, Some(group_name.into()))
+        self.text_style()
+            .get_or_insert_with(Default::default)
+            .font_family = Some(family_name.into());
+        self
     }
 }

crates/gpui3/src/taffy.rs 🔗

@@ -28,7 +28,7 @@ impl TaffyLayoutEngine {
 
     pub fn request_layout(
         &mut self,
-        style: Style,
+        style: &Style,
         rem_size: Pixels,
         children: &[LayoutId],
     ) -> LayoutId {

crates/gpui3/src/text_system.rs 🔗

@@ -1,13 +1,14 @@
 mod font_features;
 mod line;
+mod line_layout;
 mod line_wrapper;
-mod text_layout_cache;
 
 use anyhow::anyhow;
 pub use font_features::*;
 pub use line::*;
+pub use line_layout::*;
 use line_wrapper::*;
-pub use text_layout_cache::*;
+use smallvec::SmallVec;
 
 use crate::{
     px, Bounds, DevicePixels, Hsla, Pixels, PlatformTextSystem, Point, Result, SharedString, Size,
@@ -17,6 +18,7 @@ use collections::HashMap;
 use core::fmt;
 use parking_lot::{Mutex, RwLock, RwLockUpgradableReadGuard};
 use std::{
+    cmp,
     fmt::{Debug, Display, Formatter},
     hash::{Hash, Hasher},
     ops::{Deref, DerefMut},
@@ -33,18 +35,18 @@ pub struct FontFamilyId(pub usize);
 pub const SUBPIXEL_VARIANTS: u8 = 4;
 
 pub struct TextSystem {
-    text_layout_cache: Arc<TextLayoutCache>,
+    line_layout_cache: Arc<LineLayoutCache>,
     platform_text_system: Arc<dyn PlatformTextSystem>,
     font_ids_by_font: RwLock<HashMap<Font, FontId>>,
     font_metrics: RwLock<HashMap<FontId, FontMetrics>>,
     wrapper_pool: Mutex<HashMap<FontIdWithSize, Vec<LineWrapper>>>,
-    font_runs_pool: Mutex<Vec<Vec<(usize, FontId)>>>,
+    font_runs_pool: Mutex<Vec<Vec<FontRun>>>,
 }
 
 impl TextSystem {
     pub fn new(platform_text_system: Arc<dyn PlatformTextSystem>) -> Self {
         TextSystem {
-            text_layout_cache: Arc::new(TextLayoutCache::new(platform_text_system.clone())),
+            line_layout_cache: Arc::new(LineLayoutCache::new(platform_text_system.clone())),
             platform_text_system,
             font_metrics: RwLock::new(HashMap::default()),
             font_ids_by_font: RwLock::new(HashMap::default()),
@@ -143,38 +145,82 @@ impl TextSystem {
         }
     }
 
-    pub fn layout_line(
+    pub fn layout_text(
         &self,
-        text: &str,
+        text: &SharedString,
         font_size: Pixels,
-        runs: &[(usize, RunStyle)],
-    ) -> Result<Line> {
+        runs: &[TextRun],
+        wrap_width: Option<Pixels>,
+    ) -> Result<SmallVec<[Line; 1]>> {
+        let mut runs = runs.iter().cloned().peekable();
         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;
+        let mut lines = SmallVec::new();
+        let mut line_start = 0;
+        for line_text in text.split('\n') {
+            let line_text = SharedString::from(line_text.to_string());
+            let line_end = line_start + line_text.len();
+
+            let mut last_font: Option<Font> = None;
+            let mut decoration_runs = SmallVec::<[DecorationRun; 32]>::new();
+            let mut run_start = line_start;
+            while run_start < line_end {
+                let Some(run) = runs.peek_mut() else {
+                    break;
+                };
+
+                let run_len_within_line = cmp::min(line_end, run_start + run.len) - run_start;
+
+                if last_font == Some(run.font.clone()) {
+                    font_runs.last_mut().unwrap().len += run_len_within_line;
+                } else {
+                    last_font = Some(run.font.clone());
+                    font_runs.push(FontRun {
+                        len: run_len_within_line,
+                        font_id: self.platform_text_system.font_id(&run.font)?,
+                    });
                 }
+
+                if decoration_runs.last().map_or(false, |last_run| {
+                    last_run.color == run.color && last_run.underline == run.underline
+                }) {
+                    decoration_runs.last_mut().unwrap().len += run_len_within_line as u32;
+                } else {
+                    decoration_runs.push(DecorationRun {
+                        len: run_len_within_line as u32,
+                        color: run.color,
+                        underline: run.underline.clone(),
+                    });
+                }
+
+                if run_len_within_line == run.len {
+                    runs.next();
+                } else {
+                    // Preserve the remainder of the run for the next line
+                    run.len -= run_len_within_line;
+                }
+                run_start += run_len_within_line;
             }
-            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);
+            let layout = self
+                .line_layout_cache
+                .layout_line(&line_text, font_size, &font_runs, wrap_width);
+            lines.push(Line {
+                layout,
+                decorations: decoration_runs,
+            });
+
+            line_start = line_end + 1; // Skip `\n` character.
+            font_runs.clear();
+        }
 
-        font_runs.clear();
         self.font_runs_pool.lock().push(font_runs);
 
-        Ok(Line::new(layout.clone(), runs))
+        Ok(lines)
     }
 
     pub fn end_frame(&self) {
-        self.text_layout_cache.end_frame()
+        self.line_layout_cache.end_frame()
     }
 
     pub fn line_wrapper(
@@ -317,7 +363,8 @@ impl Display for FontStyle {
 }
 
 #[derive(Clone, Debug, PartialEq, Eq)]
-pub struct RunStyle {
+pub struct TextRun {
+    pub len: usize,
     pub font: Font,
     pub color: Hsla,
     pub underline: Option<UnderlineStyle>,
@@ -345,30 +392,6 @@ impl From<u32> for GlyphId {
     }
 }
 
-#[derive(Default, Debug)]
-pub struct ShapedLine {
-    pub font_size: Pixels,
-    pub width: Pixels,
-    pub ascent: Pixels,
-    pub descent: Pixels,
-    pub runs: Vec<ShapedRun>,
-    pub len: usize,
-}
-
-#[derive(Debug)]
-pub struct ShapedRun {
-    pub font_id: FontId,
-    pub glyphs: Vec<ShapedGlyph>,
-}
-
-#[derive(Clone, Debug)]
-pub struct ShapedGlyph {
-    pub id: GlyphId,
-    pub position: Point<Pixels>,
-    pub index: usize,
-    pub is_emoji: bool,
-}
-
 #[derive(Clone, Debug, PartialEq)]
 pub struct RenderGlyphParams {
     pub(crate) font_id: FontId,

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

@@ -1,144 +1,94 @@
 use crate::{
-    black, point, px, Bounds, FontId, Hsla, Pixels, Point, RunStyle, ShapedBoundary, ShapedLine,
-    ShapedRun, UnderlineStyle, WindowContext,
+    black, point, px, size, BorrowWindow, Bounds, Hsla, Pixels, Point, Result, Size,
+    UnderlineStyle, WindowContext, WrapBoundary, WrappedLineLayout,
 };
-use anyhow::Result;
 use smallvec::SmallVec;
 use std::sync::Arc;
 
-#[derive(Default, Debug, Clone)]
-pub struct Line {
-    layout: Arc<ShapedLine>,
-    style_runs: SmallVec<[StyleRun; 32]>,
+#[derive(Debug, Clone)]
+pub struct DecorationRun {
+    pub len: u32,
+    pub color: Hsla,
+    pub underline: Option<UnderlineStyle>,
 }
 
-#[derive(Debug, Clone)]
-struct StyleRun {
-    len: u32,
-    color: Hsla,
-    underline: UnderlineStyle,
+#[derive(Clone, Default, Debug)]
+pub struct Line {
+    pub(crate) layout: Arc<WrappedLineLayout>,
+    pub(crate) decorations: SmallVec<[DecorationRun; 32]>,
 }
 
 impl Line {
-    pub fn new(layout: Arc<ShapedLine>, runs: &[(usize, RunStyle)]) -> Self {
-        let mut style_runs = SmallVec::new();
-        for (len, style) in runs {
-            style_runs.push(StyleRun {
-                len: *len as u32,
-                color: style.color,
-                underline: style.underline.clone().unwrap_or_default(),
-            });
-        }
-        Self { layout, style_runs }
-    }
-
-    pub fn runs(&self) -> &[ShapedRun] {
-        &self.layout.runs
-    }
-
-    pub fn width(&self) -> Pixels {
-        self.layout.width
-    }
-
-    pub fn font_size(&self) -> Pixels {
-        self.layout.font_size
-    }
-
-    pub fn x_for_index(&self, index: usize) -> Pixels {
-        for run in &self.layout.runs {
-            for glyph in &run.glyphs {
-                if glyph.index >= index {
-                    return glyph.position.x;
-                }
-            }
-        }
-        self.layout.width
-    }
-
-    pub fn font_for_index(&self, index: usize) -> Option<FontId> {
-        for run in &self.layout.runs {
-            for glyph in &run.glyphs {
-                if glyph.index >= index {
-                    return Some(run.font_id);
-                }
-            }
-        }
-
-        None
-    }
-
-    pub fn len(&self) -> usize {
-        self.layout.len
-    }
-
-    pub fn is_empty(&self) -> bool {
-        self.layout.len == 0
-    }
-
-    pub fn index_for_x(&self, x: Pixels) -> Option<usize> {
-        if x >= self.layout.width {
-            None
-        } else {
-            for run in self.layout.runs.iter().rev() {
-                for glyph in run.glyphs.iter().rev() {
-                    if glyph.position.x <= x {
-                        return Some(glyph.index);
-                    }
-                }
-            }
-            Some(0)
-        }
+    pub fn size(&self, line_height: Pixels) -> Size<Pixels> {
+        size(
+            self.layout.width,
+            line_height * (self.layout.wrap_boundaries.len() + 1),
+        )
     }
 
     pub fn paint(
         &self,
-        bounds: Bounds<Pixels>,
-        visible_bounds: Bounds<Pixels>, // todo!("use clipping")
+        origin: Point<Pixels>,
         line_height: Pixels,
         cx: &mut WindowContext,
     ) -> Result<()> {
-        let origin = bounds.origin;
-        let padding_top = (line_height - self.layout.ascent - self.layout.descent) / 2.;
-        let baseline_offset = point(px(0.), padding_top + self.layout.ascent);
+        let padding_top =
+            (line_height - self.layout.layout.ascent - self.layout.layout.descent) / 2.;
+        let baseline_offset = point(px(0.), padding_top + self.layout.layout.ascent);
 
-        let mut style_runs = self.style_runs.iter();
+        let mut style_runs = self.decorations.iter();
+        let mut wraps = self.layout.wrap_boundaries.iter().peekable();
         let mut run_end = 0;
         let mut color = black();
-        let mut underline = None;
+        let mut current_underline: Option<(Point<Pixels>, UnderlineStyle)> = None;
         let text_system = cx.text_system().clone();
 
-        for run in &self.layout.runs {
-            let max_glyph_width = text_system
-                .bounding_box(run.font_id, self.layout.font_size)?
-                .size
-                .width;
+        let mut glyph_origin = origin;
+        let mut prev_glyph_position = Point::default();
+        for (run_ix, run) in self.layout.layout.runs.iter().enumerate() {
+            let max_glyph_size = text_system
+                .bounding_box(run.font_id, self.layout.layout.font_size)?
+                .size;
+
+            for (glyph_ix, glyph) in run.glyphs.iter().enumerate() {
+                glyph_origin.x += glyph.position.x - prev_glyph_position.x;
 
-            for glyph in &run.glyphs {
-                let glyph_origin = origin + baseline_offset + glyph.position;
-                if glyph_origin.x > visible_bounds.upper_right().x {
-                    break;
+                if wraps.peek() == Some(&&WrapBoundary { run_ix, glyph_ix }) {
+                    wraps.next();
+                    if let Some((underline_origin, underline_style)) = current_underline.take() {
+                        cx.paint_underline(
+                            underline_origin,
+                            glyph_origin.x - underline_origin.x,
+                            &underline_style,
+                        )?;
+                    }
+
+                    glyph_origin.x = origin.x;
+                    glyph_origin.y += line_height;
                 }
+                prev_glyph_position = glyph.position;
+                let glyph_origin = glyph_origin + baseline_offset;
 
                 let mut finished_underline: Option<(Point<Pixels>, UnderlineStyle)> = None;
                 if glyph.index >= run_end {
                     if let Some(style_run) = style_runs.next() {
-                        if let Some((_, underline_style)) = &mut underline {
-                            if style_run.underline != *underline_style {
-                                finished_underline = underline.take();
+                        if let Some((_, underline_style)) = &mut current_underline {
+                            if style_run.underline.as_ref() != Some(underline_style) {
+                                finished_underline = current_underline.take();
                             }
                         }
-                        if style_run.underline.thickness > px(0.) {
-                            underline.get_or_insert((
+                        if let Some(run_underline) = style_run.underline.as_ref() {
+                            current_underline.get_or_insert((
                                 point(
                                     glyph_origin.x,
-                                    origin.y + baseline_offset.y + (self.layout.descent * 0.618),
+                                    origin.y
+                                        + baseline_offset.y
+                                        + (self.layout.layout.descent * 0.618),
                                 ),
                                 UnderlineStyle {
-                                    color: Some(
-                                        style_run.underline.color.unwrap_or(style_run.color),
-                                    ),
-                                    thickness: style_run.underline.thickness,
-                                    wavy: style_run.underline.wavy,
+                                    color: Some(run_underline.color.unwrap_or(style_run.color)),
+                                    thickness: run_underline.thickness,
+                                    wavy: run_underline.wavy,
                                 },
                             ));
                         }
@@ -146,15 +96,11 @@ impl Line {
                         run_end += style_run.len as usize;
                         color = style_run.color;
                     } else {
-                        run_end = self.layout.len;
-                        finished_underline = underline.take();
+                        run_end = self.layout.text.len();
+                        finished_underline = current_underline.take();
                     }
                 }
 
-                if glyph_origin.x + max_glyph_width < visible_bounds.origin.x {
-                    continue;
-                }
-
                 if let Some((underline_origin, underline_style)) = finished_underline {
                     cx.paint_underline(
                         underline_origin,
@@ -163,146 +109,38 @@ impl Line {
                     )?;
                 }
 
-                if glyph.is_emoji {
-                    cx.paint_emoji(glyph_origin, run.font_id, glyph.id, self.layout.font_size)?;
-                } else {
-                    cx.paint_glyph(
-                        glyph_origin,
-                        run.font_id,
-                        glyph.id,
-                        self.layout.font_size,
-                        color,
-                    )?;
-                }
-            }
-        }
-
-        if let Some((underline_start, underline_style)) = underline.take() {
-            let line_end_x = origin.x + self.layout.width;
-            cx.paint_underline(
-                underline_start,
-                line_end_x - underline_start.x,
-                &underline_style,
-            )?;
-        }
-
-        Ok(())
-    }
-
-    pub fn paint_wrapped(
-        &self,
-        origin: Point<Pixels>,
-        _visible_bounds: Bounds<Pixels>, // todo!("use clipping")
-        line_height: Pixels,
-        boundaries: &[ShapedBoundary],
-        cx: &mut WindowContext,
-    ) -> Result<()> {
-        let padding_top = (line_height - self.layout.ascent - self.layout.descent) / 2.;
-        let baseline_offset = point(px(0.), padding_top + self.layout.ascent);
-
-        let mut boundaries = boundaries.into_iter().peekable();
-        let mut color_runs = self.style_runs.iter();
-        let mut style_run_end = 0;
-        let mut _color = black(); // todo!
-        let mut underline: Option<(Point<Pixels>, UnderlineStyle)> = None;
-
-        let mut glyph_origin = origin;
-        let mut prev_position = px(0.);
-        for (run_ix, run) in self.layout.runs.iter().enumerate() {
-            for (glyph_ix, glyph) in run.glyphs.iter().enumerate() {
-                glyph_origin.x += glyph.position.x - prev_position;
+                let max_glyph_bounds = Bounds {
+                    origin: glyph_origin,
+                    size: max_glyph_size,
+                };
 
-                if boundaries
-                    .peek()
-                    .map_or(false, |b| b.run_ix == run_ix && b.glyph_ix == glyph_ix)
-                {
-                    boundaries.next();
-                    if let Some((underline_origin, underline_style)) = underline.take() {
-                        cx.paint_underline(
-                            underline_origin,
-                            glyph_origin.x - underline_origin.x,
-                            &underline_style,
+                let content_mask = cx.content_mask();
+                if max_glyph_bounds.intersects(&content_mask.bounds) {
+                    if glyph.is_emoji {
+                        cx.paint_emoji(
+                            glyph_origin,
+                            run.font_id,
+                            glyph.id,
+                            self.layout.layout.font_size,
                         )?;
-                    }
-
-                    glyph_origin = point(origin.x, glyph_origin.y + line_height);
-                }
-                prev_position = glyph.position.x;
-
-                let mut finished_underline = None;
-                if glyph.index >= style_run_end {
-                    if let Some(style_run) = color_runs.next() {
-                        style_run_end += style_run.len as usize;
-                        _color = style_run.color;
-                        if let Some((_, underline_style)) = &mut underline {
-                            if style_run.underline != *underline_style {
-                                finished_underline = underline.take();
-                            }
-                        }
-                        if style_run.underline.thickness > px(0.) {
-                            underline.get_or_insert((
-                                glyph_origin
-                                    + point(
-                                        px(0.),
-                                        baseline_offset.y + (self.layout.descent * 0.618),
-                                    ),
-                                UnderlineStyle {
-                                    color: Some(
-                                        style_run.underline.color.unwrap_or(style_run.color),
-                                    ),
-                                    thickness: style_run.underline.thickness,
-                                    wavy: style_run.underline.wavy,
-                                },
-                            ));
-                        }
                     } else {
-                        style_run_end = self.layout.len;
-                        _color = black();
-                        finished_underline = underline.take();
+                        cx.paint_glyph(
+                            glyph_origin,
+                            run.font_id,
+                            glyph.id,
+                            self.layout.layout.font_size,
+                            color,
+                        )?;
                     }
                 }
-
-                if let Some((underline_origin, underline_style)) = finished_underline {
-                    cx.paint_underline(
-                        underline_origin,
-                        glyph_origin.x - underline_origin.x,
-                        &underline_style,
-                    )?;
-                }
-
-                let text_system = cx.text_system();
-                let _glyph_bounds = Bounds {
-                    origin: glyph_origin,
-                    size: text_system
-                        .bounding_box(run.font_id, self.layout.font_size)?
-                        .size,
-                };
-                // if glyph_bounds.intersects(visible_bounds) {
-                //     if glyph.is_emoji {
-                //         cx.scene().push_image_glyph(scene::ImageGlyph {
-                //             font_id: run.font_id,
-                //             font_size: self.layout.font_size,
-                //             id: glyph.id,
-                //             origin: glyph_bounds.origin() + baseline_offset,
-                //         });
-                //     } else {
-                //         cx.scene().push_glyph(scene::Glyph {
-                //             font_id: run.font_id,
-                //             font_size: self.layout.font_size,
-                //             id: glyph.id,
-                //             origin: glyph_bounds.origin() + baseline_offset,
-                //             color,
-                //         });
-                //     }
-                // }
             }
         }
 
-        if let Some((underline_origin, underline_style)) = underline.take() {
-            let line_end_x = glyph_origin.x + self.layout.width - prev_position;
+        if let Some((underline_start, underline_style)) = current_underline.take() {
+            let line_end_x = origin.x + self.layout.layout.width;
             cx.paint_underline(
-                underline_origin,
-                line_end_x - underline_origin.x,
+                underline_start,
+                line_end_x - underline_start.x,
                 &underline_style,
             )?;
         }

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

@@ -0,0 +1,295 @@
+use crate::{px, FontId, GlyphId, Pixels, PlatformTextSystem, Point, SharedString};
+use derive_more::{Deref, DerefMut};
+use parking_lot::{Mutex, RwLock, RwLockUpgradableReadGuard};
+use smallvec::SmallVec;
+use std::{
+    borrow::Borrow,
+    collections::HashMap,
+    hash::{Hash, Hasher},
+    sync::Arc,
+};
+
+#[derive(Default, Debug)]
+pub struct LineLayout {
+    pub font_size: Pixels,
+    pub width: Pixels,
+    pub ascent: Pixels,
+    pub descent: Pixels,
+    pub runs: Vec<ShapedRun>,
+}
+
+#[derive(Debug)]
+pub struct ShapedRun {
+    pub font_id: FontId,
+    pub glyphs: SmallVec<[ShapedGlyph; 8]>,
+}
+
+#[derive(Clone, Debug)]
+pub struct ShapedGlyph {
+    pub id: GlyphId,
+    pub position: Point<Pixels>,
+    pub index: usize,
+    pub is_emoji: bool,
+}
+
+impl LineLayout {
+    pub fn index_for_x(&self, x: Pixels) -> Option<usize> {
+        if x >= self.width {
+            None
+        } else {
+            for run in self.runs.iter().rev() {
+                for glyph in run.glyphs.iter().rev() {
+                    if glyph.position.x <= x {
+                        return Some(glyph.index);
+                    }
+                }
+            }
+            Some(0)
+        }
+    }
+
+    pub fn x_for_index(&self, index: usize) -> Pixels {
+        for run in &self.runs {
+            for glyph in &run.glyphs {
+                if glyph.index >= index {
+                    return glyph.position.x;
+                }
+            }
+        }
+        self.width
+    }
+
+    pub fn font_for_index(&self, index: usize) -> Option<FontId> {
+        for run in &self.runs {
+            for glyph in &run.glyphs {
+                if glyph.index >= index {
+                    return Some(run.font_id);
+                }
+            }
+        }
+
+        None
+    }
+
+    fn compute_wrap_boundaries(
+        &self,
+        text: &str,
+        wrap_width: Pixels,
+    ) -> SmallVec<[WrapBoundary; 1]> {
+        let mut boundaries = SmallVec::new();
+
+        let mut first_non_whitespace_ix = None;
+        let mut last_candidate_ix = None;
+        let mut last_candidate_x = px(0.);
+        let mut last_boundary = WrapBoundary {
+            run_ix: 0,
+            glyph_ix: 0,
+        };
+        let mut last_boundary_x = px(0.);
+        let mut prev_ch = '\0';
+        let mut glyphs = self
+            .runs
+            .iter()
+            .enumerate()
+            .flat_map(move |(run_ix, run)| {
+                run.glyphs.iter().enumerate().map(move |(glyph_ix, glyph)| {
+                    let character = text[glyph.index..].chars().next().unwrap();
+                    (
+                        WrapBoundary { run_ix, glyph_ix },
+                        character,
+                        glyph.position.x,
+                    )
+                })
+            })
+            .peekable();
+
+        while let Some((boundary, ch, x)) = glyphs.next() {
+            if ch == '\n' {
+                continue;
+            }
+
+            if prev_ch == ' ' && ch != ' ' && first_non_whitespace_ix.is_some() {
+                last_candidate_ix = Some(boundary);
+                last_candidate_x = x;
+            }
+
+            if ch != ' ' && first_non_whitespace_ix.is_none() {
+                first_non_whitespace_ix = Some(boundary);
+            }
+
+            let next_x = glyphs.peek().map_or(self.width, |(_, _, x)| *x);
+            let width = next_x - last_boundary_x;
+            if width > wrap_width && boundary > last_boundary {
+                if let Some(last_candidate_ix) = last_candidate_ix.take() {
+                    last_boundary = last_candidate_ix;
+                    last_boundary_x = last_candidate_x;
+                } else {
+                    last_boundary = boundary;
+                    last_boundary_x = x;
+                }
+
+                boundaries.push(last_boundary);
+            }
+            prev_ch = ch;
+        }
+
+        boundaries
+    }
+}
+
+#[derive(Deref, DerefMut, Default, Debug)]
+pub struct WrappedLineLayout {
+    #[deref]
+    #[deref_mut]
+    pub layout: LineLayout,
+    pub text: SharedString,
+    pub wrap_boundaries: SmallVec<[WrapBoundary; 1]>,
+}
+
+#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
+pub struct WrapBoundary {
+    pub run_ix: usize,
+    pub glyph_ix: usize,
+}
+
+pub(crate) struct LineLayoutCache {
+    prev_frame: Mutex<HashMap<CacheKey, Arc<WrappedLineLayout>>>,
+    curr_frame: RwLock<HashMap<CacheKey, Arc<WrappedLineLayout>>>,
+    platform_text_system: Arc<dyn PlatformTextSystem>,
+}
+
+impl LineLayoutCache {
+    pub fn new(platform_text_system: Arc<dyn PlatformTextSystem>) -> Self {
+        Self {
+            prev_frame: Mutex::new(HashMap::new()),
+            curr_frame: RwLock::new(HashMap::new()),
+            platform_text_system,
+        }
+    }
+
+    pub fn end_frame(&self) {
+        let mut prev_frame = self.prev_frame.lock();
+        let mut curr_frame = self.curr_frame.write();
+        std::mem::swap(&mut *prev_frame, &mut *curr_frame);
+        curr_frame.clear();
+    }
+
+    pub fn layout_line(
+        &self,
+        text: &SharedString,
+        font_size: Pixels,
+        runs: &[FontRun],
+        wrap_width: Option<Pixels>,
+    ) -> Arc<WrappedLineLayout> {
+        let key = &CacheKeyRef {
+            text,
+            font_size,
+            runs,
+            wrap_width,
+        } as &dyn AsCacheKeyRef;
+        let curr_frame = self.curr_frame.upgradable_read();
+        if let Some(layout) = curr_frame.get(key) {
+            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());
+            layout
+        } else {
+            let layout = self.platform_text_system.layout_line(text, font_size, runs);
+            let wrap_boundaries = wrap_width
+                .map(|wrap_width| layout.compute_wrap_boundaries(text.as_ref(), wrap_width))
+                .unwrap_or_default();
+            let wrapped_line = Arc::new(WrappedLineLayout {
+                layout,
+                text: text.clone(),
+                wrap_boundaries,
+            });
+
+            let key = CacheKey {
+                text: text.clone(),
+                font_size,
+                runs: SmallVec::from(runs),
+                wrap_width,
+            };
+            curr_frame.insert(key, wrapped_line.clone());
+            wrapped_line
+        }
+    }
+}
+
+#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
+pub struct FontRun {
+    pub(crate) len: usize,
+    pub(crate) font_id: FontId,
+}
+
+trait AsCacheKeyRef {
+    fn as_cache_key_ref(&self) -> CacheKeyRef;
+}
+
+#[derive(Eq)]
+struct CacheKey {
+    text: SharedString,
+    font_size: Pixels,
+    runs: SmallVec<[FontRun; 1]>,
+    wrap_width: Option<Pixels>,
+}
+
+#[derive(Copy, Clone, PartialEq, Eq, Hash)]
+struct CacheKeyRef<'a> {
+    text: &'a str,
+    font_size: Pixels,
+    runs: &'a [FontRun],
+    wrap_width: Option<Pixels>,
+}
+
+impl<'a> PartialEq for (dyn AsCacheKeyRef + 'a) {
+    fn eq(&self, other: &dyn AsCacheKeyRef) -> bool {
+        self.as_cache_key_ref() == other.as_cache_key_ref()
+    }
+}
+
+impl<'a> Eq for (dyn AsCacheKeyRef + 'a) {}
+
+impl<'a> Hash for (dyn AsCacheKeyRef + 'a) {
+    fn hash<H: Hasher>(&self, state: &mut H) {
+        self.as_cache_key_ref().hash(state)
+    }
+}
+
+impl AsCacheKeyRef for CacheKey {
+    fn as_cache_key_ref(&self) -> CacheKeyRef {
+        CacheKeyRef {
+            text: &self.text,
+            font_size: self.font_size,
+            runs: self.runs.as_slice(),
+            wrap_width: self.wrap_width,
+        }
+    }
+}
+
+impl PartialEq for CacheKey {
+    fn eq(&self, other: &Self) -> bool {
+        self.as_cache_key_ref().eq(&other.as_cache_key_ref())
+    }
+}
+
+impl Hash for CacheKey {
+    fn hash<H: Hasher>(&self, state: &mut H) {
+        self.as_cache_key_ref().hash(state);
+    }
+}
+
+impl<'a> Borrow<dyn AsCacheKeyRef + 'a> for CacheKey {
+    fn borrow(&self) -> &(dyn AsCacheKeyRef + 'a) {
+        self as &dyn AsCacheKeyRef
+    }
+}
+
+impl<'a> AsCacheKeyRef for CacheKeyRef<'a> {
+    fn as_cache_key_ref(&self) -> CacheKeyRef {
+        *self
+    }
+}

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

@@ -1,4 +1,4 @@
-use crate::{px, FontId, Line, Pixels, PlatformTextSystem, ShapedBoundary};
+use crate::{px, FontId, FontRun, Pixels, PlatformTextSystem};
 use collections::HashMap;
 use std::{iter, sync::Arc};
 
@@ -46,7 +46,7 @@ impl LineWrapper {
                     continue;
                 }
 
-                if self.is_boundary(prev_c, c) && first_non_whitespace_ix.is_some() {
+                if prev_c == ' ' && c != ' ' && first_non_whitespace_ix.is_some() {
                     last_candidate_ix = ix;
                     last_candidate_width = width;
                 }
@@ -87,79 +87,6 @@ impl LineWrapper {
         })
     }
 
-    pub fn wrap_shaped_line<'a>(
-        &'a mut self,
-        str: &'a str,
-        line: &'a Line,
-        wrap_width: Pixels,
-    ) -> impl Iterator<Item = ShapedBoundary> + 'a {
-        let mut first_non_whitespace_ix = None;
-        let mut last_candidate_ix = None;
-        let mut last_candidate_x = px(0.);
-        let mut last_wrap_ix = ShapedBoundary {
-            run_ix: 0,
-            glyph_ix: 0,
-        };
-        let mut last_wrap_x = px(0.);
-        let mut prev_c = '\0';
-        let mut glyphs = line
-            .runs()
-            .iter()
-            .enumerate()
-            .flat_map(move |(run_ix, run)| {
-                run.glyphs()
-                    .iter()
-                    .enumerate()
-                    .map(move |(glyph_ix, glyph)| {
-                        let character = str[glyph.index..].chars().next().unwrap();
-                        (
-                            ShapedBoundary { run_ix, glyph_ix },
-                            character,
-                            glyph.position.x,
-                        )
-                    })
-            })
-            .peekable();
-
-        iter::from_fn(move || {
-            while let Some((ix, c, x)) = glyphs.next() {
-                if c == '\n' {
-                    continue;
-                }
-
-                if self.is_boundary(prev_c, c) && first_non_whitespace_ix.is_some() {
-                    last_candidate_ix = Some(ix);
-                    last_candidate_x = x;
-                }
-
-                if c != ' ' && first_non_whitespace_ix.is_none() {
-                    first_non_whitespace_ix = Some(ix);
-                }
-
-                let next_x = glyphs.peek().map_or(line.width(), |(_, _, x)| *x);
-                let width = next_x - last_wrap_x;
-                if width > wrap_width && ix > last_wrap_ix {
-                    if let Some(last_candidate_ix) = last_candidate_ix.take() {
-                        last_wrap_ix = last_candidate_ix;
-                        last_wrap_x = last_candidate_x;
-                    } else {
-                        last_wrap_ix = ix;
-                        last_wrap_x = x;
-                    }
-
-                    return Some(last_wrap_ix);
-                }
-                prev_c = c;
-            }
-
-            None
-        })
-    }
-
-    fn is_boundary(&self, prev: char, next: char) -> bool {
-        (prev == ' ') && (next != ' ')
-    }
-
     #[inline(always)]
     fn width_for_char(&mut self, c: char) -> Pixels {
         if (c as u32) < 128 {
@@ -182,8 +109,17 @@ impl LineWrapper {
     }
 
     fn compute_width_for_char(&self, c: char) -> Pixels {
+        let mut buffer = [0; 4];
+        let buffer = c.encode_utf8(&mut buffer);
         self.platform_text_system
-            .layout_line(&c.to_string(), self.font_size, &[(1, self.font_id)])
+            .layout_line(
+                buffer,
+                self.font_size,
+                &[FontRun {
+                    len: 1,
+                    font_id: self.font_id,
+                }],
+            )
             .width
     }
 }
@@ -203,7 +139,7 @@ impl Boundary {
 #[cfg(test)]
 mod tests {
     use super::*;
-    use crate::{font, App, RunStyle};
+    use crate::{font, App};
 
     #[test]
     fn test_wrap_line() {
@@ -268,62 +204,75 @@ mod tests {
         });
     }
 
+    // todo!("move this to a test on TextSystem::layout_text")
     // todo! repeat this test
-    #[test]
-    fn test_wrap_shaped_line() {
-        App::test().run(|cx| {
-            let text_system = cx.text_system().clone();
+    // #[test]
+    // fn test_wrap_shaped_line() {
+    //     App::test().run(|cx| {
+    //         let text_system = cx.text_system().clone();
 
-            let normal = RunStyle {
-                font: font("Helvetica"),
-                color: Default::default(),
-                underline: Default::default(),
-            };
-            let bold = RunStyle {
-                font: font("Helvetica").bold(),
-                color: Default::default(),
-                underline: Default::default(),
-            };
+    //         let normal = TextRun {
+    //             len: 0,
+    //             font: font("Helvetica"),
+    //             color: Default::default(),
+    //             underline: Default::default(),
+    //         };
+    //         let bold = TextRun {
+    //             len: 0,
+    //             font: font("Helvetica").bold(),
+    //             color: Default::default(),
+    //             underline: Default::default(),
+    //         };
 
-            let text = "aa bbb cccc ddddd eeee";
-            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();
+    //         impl TextRun {
+    //             fn with_len(&self, len: usize) -> Self {
+    //                 let mut this = self.clone();
+    //                 this.len = len;
+    //                 this
+    //             }
+    //         }
 
-            let mut wrapper = LineWrapper::new(
-                text_system.font_id(&normal.font).unwrap(),
-                px(16.),
-                text_system.platform_text_system.clone(),
-            );
-            assert_eq!(
-                wrapper
-                    .wrap_shaped_line(text, &line, px(72.))
-                    .collect::<Vec<_>>(),
-                &[
-                    ShapedBoundary {
-                        run_ix: 1,
-                        glyph_ix: 3
-                    },
-                    ShapedBoundary {
-                        run_ix: 2,
-                        glyph_ix: 3
-                    },
-                    ShapedBoundary {
-                        run_ix: 4,
-                        glyph_ix: 2
-                    }
-                ],
-            );
-        });
-    }
+    //         let text = "aa bbb cccc ddddd eeee".into();
+    //         let lines = text_system
+    //             .layout_text(
+    //                 &text,
+    //                 px(16.),
+    //                 &[
+    //                     normal.with_len(4),
+    //                     bold.with_len(5),
+    //                     normal.with_len(6),
+    //                     bold.with_len(1),
+    //                     normal.with_len(7),
+    //                 ],
+    //                 None,
+    //             )
+    //             .unwrap();
+    //         let line = &lines[0];
+
+    //         let mut wrapper = LineWrapper::new(
+    //             text_system.font_id(&normal.font).unwrap(),
+    //             px(16.),
+    //             text_system.platform_text_system.clone(),
+    //         );
+    //         assert_eq!(
+    //             wrapper
+    //                 .wrap_shaped_line(&text, &line, px(72.))
+    //                 .collect::<Vec<_>>(),
+    //             &[
+    //                 ShapedBoundary {
+    //                     run_ix: 1,
+    //                     glyph_ix: 3
+    //                 },
+    //                 ShapedBoundary {
+    //                     run_ix: 2,
+    //                     glyph_ix: 3
+    //                 },
+    //                 ShapedBoundary {
+    //                     run_ix: 4,
+    //                     glyph_ix: 2
+    //                 }
+    //             ],
+    //         );
+    //     });
+    // }
 }

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

@@ -1,153 +0,0 @@
-use crate::{FontId, Pixels, PlatformTextSystem, ShapedGlyph, ShapedLine, ShapedRun};
-use parking_lot::{Mutex, RwLock, RwLockUpgradableReadGuard};
-use smallvec::SmallVec;
-use std::{
-    borrow::Borrow,
-    collections::HashMap,
-    hash::{Hash, Hasher},
-    sync::Arc,
-};
-
-pub(crate) struct TextLayoutCache {
-    prev_frame: Mutex<HashMap<CacheKeyValue, Arc<ShapedLine>>>,
-    curr_frame: RwLock<HashMap<CacheKeyValue, Arc<ShapedLine>>>,
-    platform_text_system: Arc<dyn PlatformTextSystem>,
-}
-
-impl TextLayoutCache {
-    pub fn new(fonts: Arc<dyn PlatformTextSystem>) -> Self {
-        Self {
-            prev_frame: Mutex::new(HashMap::new()),
-            curr_frame: RwLock::new(HashMap::new()),
-            platform_text_system: fonts,
-        }
-    }
-
-    pub fn end_frame(&self) {
-        let mut prev_frame = self.prev_frame.lock();
-        let mut curr_frame = self.curr_frame.write();
-        std::mem::swap(&mut *prev_frame, &mut *curr_frame);
-        curr_frame.clear();
-    }
-
-    pub fn layout_line<'a>(
-        &'a self,
-        text: &'a str,
-        font_size: Pixels,
-        runs: &[(usize, FontId)],
-    ) -> Arc<ShapedLine> {
-        let key = &CacheKeyRef {
-            text,
-            font_size,
-            runs,
-        } as &dyn CacheKey;
-        let curr_frame = self.curr_frame.upgradable_read();
-        if let Some(layout) = curr_frame.get(key) {
-            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());
-            layout
-        } else {
-            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());
-            layout
-        }
-    }
-}
-
-trait CacheKey {
-    fn key(&self) -> CacheKeyRef;
-}
-
-impl<'a> PartialEq for (dyn CacheKey + 'a) {
-    fn eq(&self, other: &dyn CacheKey) -> bool {
-        self.key() == other.key()
-    }
-}
-
-impl<'a> Eq for (dyn CacheKey + 'a) {}
-
-impl<'a> Hash for (dyn CacheKey + 'a) {
-    fn hash<H: Hasher>(&self, state: &mut H) {
-        self.key().hash(state)
-    }
-}
-
-#[derive(Eq)]
-struct CacheKeyValue {
-    text: String,
-    font_size: Pixels,
-    runs: SmallVec<[(usize, FontId); 1]>,
-}
-
-impl CacheKey for CacheKeyValue {
-    fn key(&self) -> CacheKeyRef {
-        CacheKeyRef {
-            text: self.text.as_str(),
-            font_size: self.font_size,
-            runs: self.runs.as_slice(),
-        }
-    }
-}
-
-impl PartialEq for CacheKeyValue {
-    fn eq(&self, other: &Self) -> bool {
-        self.key().eq(&other.key())
-    }
-}
-
-impl Hash for CacheKeyValue {
-    fn hash<H: Hasher>(&self, state: &mut H) {
-        self.key().hash(state);
-    }
-}
-
-impl<'a> Borrow<dyn CacheKey + 'a> for CacheKeyValue {
-    fn borrow(&self) -> &(dyn CacheKey + 'a) {
-        self as &dyn CacheKey
-    }
-}
-
-#[derive(Copy, Clone, PartialEq, Eq)]
-struct CacheKeyRef<'a> {
-    text: &'a str,
-    font_size: Pixels,
-    runs: &'a [(usize, FontId)],
-}
-
-impl<'a> CacheKey for CacheKeyRef<'a> {
-    fn key(&self) -> CacheKeyRef {
-        *self
-    }
-}
-
-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, font_id) in self.runs {
-            len.hash(state);
-            font_id.hash(state);
-        }
-    }
-}
-
-#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
-pub struct ShapedBoundary {
-    pub run_ix: usize,
-    pub glyph_ix: usize,
-}
-
-impl ShapedRun {
-    pub fn glyphs(&self) -> &[ShapedGlyph] {
-        &self.glyphs
-    }
-}

crates/gpui3/src/view.rs 🔗

@@ -1,8 +1,8 @@
 use parking_lot::Mutex;
 
 use crate::{
-    AnyBox, AnyElement, BorrowWindow, Bounds, Element, ElementId, EntityId, Handle,
-    IdentifiedElement, IntoAnyElement, LayoutId, Pixels, ViewContext, WindowContext,
+    AnyBox, AnyElement, BorrowWindow, Bounds, Element, ElementId, EntityId, Handle, IntoAnyElement,
+    LayoutId, Pixels, ViewContext, WindowContext,
 };
 use std::{marker::PhantomData, sync::Arc};
 
@@ -57,7 +57,7 @@ impl<S: 'static + Send + Sync> Element for View<S> {
     type ViewState = ();
     type ElementState = AnyElement<S>;
 
-    fn element_id(&self) -> Option<crate::ElementId> {
+    fn id(&self) -> Option<crate::ElementId> {
         Some(ElementId::View(self.state.id))
     }
 
@@ -86,8 +86,6 @@ impl<S: 'static + Send + Sync> Element for View<S> {
     }
 }
 
-impl<S: Send + Sync + 'static> IdentifiedElement for View<S> {}
-
 struct EraseViewState<ViewState: 'static + Send + Sync, ParentViewState> {
     view: View<ViewState>,
     parent_view_state_type: PhantomData<ParentViewState>,
@@ -112,8 +110,8 @@ where
     type ViewState = ParentViewState;
     type ElementState = AnyBox;
 
-    fn element_id(&self) -> Option<crate::ElementId> {
-        Element::element_id(&self.view)
+    fn id(&self) -> Option<crate::ElementId> {
+        Element::id(&self.view)
     }
 
     fn layout(
@@ -148,7 +146,7 @@ impl<S: Send + Sync + 'static> ViewObject for View<S> {
     }
 
     fn layout(&mut self, cx: &mut WindowContext) -> (LayoutId, AnyBox) {
-        cx.with_element_id(IdentifiedElement::element_id(self), |cx| {
+        cx.with_element_id(self.entity_id(), |cx| {
             self.state.update(cx, |state, cx| {
                 let mut element = (self.render)(state, cx);
                 let layout_id = element.layout(state, cx);
@@ -159,7 +157,7 @@ impl<S: Send + Sync + 'static> ViewObject for View<S> {
     }
 
     fn paint(&mut self, _: Bounds<Pixels>, element: &mut AnyBox, cx: &mut WindowContext) {
-        cx.with_element_id(IdentifiedElement::element_id(self), |cx| {
+        cx.with_element_id(self.entity_id(), |cx| {
             self.state.update(cx, |state, cx| {
                 let element = element.downcast_mut::<AnyElement<S>>().unwrap();
                 element.paint(state, None, cx);
@@ -188,7 +186,7 @@ impl Element for AnyView {
     type ViewState = ();
     type ElementState = AnyBox;
 
-    fn element_id(&self) -> Option<crate::ElementId> {
+    fn id(&self) -> Option<crate::ElementId> {
         Some(ElementId::View(self.view.lock().entity_id()))
     }
 
@@ -233,8 +231,8 @@ where
     type ViewState = ParentViewState;
     type ElementState = AnyBox;
 
-    fn element_id(&self) -> Option<crate::ElementId> {
-        Element::element_id(&self.view)
+    fn id(&self) -> Option<crate::ElementId> {
+        Element::id(&self.view)
     }
 
     fn layout(

crates/gpui3/src/window.rs 🔗

@@ -45,7 +45,7 @@ type MouseEventHandler =
 pub struct Window {
     handle: AnyWindowHandle,
     platform_window: MainThreadOnly<Box<dyn PlatformWindow>>,
-    pub(crate) display_id: DisplayId, // todo!("make private again?")
+    display_id: DisplayId,
     sprite_atlas: Arc<dyn PlatformAtlas>,
     rem_size: Pixels,
     content_size: Size<Pixels>,
@@ -248,7 +248,7 @@ impl<'a, 'w> WindowContext<'a, 'w> {
 
     pub fn request_layout(
         &mut self,
-        style: Style,
+        style: &Style,
         children: impl IntoIterator<Item = LayoutId>,
     ) -> LayoutId {
         self.app.layout_id_buffer.clear();
@@ -600,7 +600,7 @@ impl<'a, 'w> WindowContext<'a, 'w> {
 
             let mut root_view = cx.window.root_view.take().unwrap();
 
-            if let Some(element_id) = root_view.element_id() {
+            if let Some(element_id) = root_view.id() {
                 cx.with_element_state(element_id, |element_state, cx| {
                     let element_state = draw_with_element_state(&mut root_view, element_state, cx);
                     ((), element_state)
@@ -1145,6 +1145,13 @@ impl From<SmallVec<[u32; 16]>> for StackingOrder {
 pub enum ElementId {
     View(EntityId),
     Number(usize),
+    Name(SharedString),
+}
+
+impl From<EntityId> for ElementId {
+    fn from(id: EntityId) -> Self {
+        ElementId::View(id)
+    }
 }
 
 impl From<usize> for ElementId {
@@ -1158,3 +1165,15 @@ impl From<i32> for ElementId {
         Self::Number(id as usize)
     }
 }
+
+impl From<SharedString> for ElementId {
+    fn from(name: SharedString) -> Self {
+        ElementId::Name(name)
+    }
+}
+
+impl From<&'static str> for ElementId {
+    fn from(name: &'static str) -> Self {
+        ElementId::Name(name.into())
+    }
+}

crates/gpui3_macros/src/derive_element.rs 🔗

@@ -53,8 +53,7 @@ pub fn derive_element(input: TokenStream) -> TokenStream {
             type ViewState = #state_type;
             type ElementState = gpui3::AnyElement<#state_type>;
 
-            fn element_id(&self) -> Option<gpui3::ElementId> {
-                // todo!("What should element_id be here?")
+            fn id(&self) -> Option<gpui3::ElementId> {
                 None
             }
 

crates/gpui3_macros/src/style_helpers.rs 🔗

@@ -131,7 +131,7 @@ fn generate_predefined_setter(
     let method = quote! {
         #[doc = #doc_string]
         fn #method_name(mut self) -> Self where Self: std::marker::Sized {
-            let mut style = self.declared_style();
+            let style = self.style();
             #(#field_assignments)*
             self
         }
@@ -164,7 +164,7 @@ fn generate_custom_value_setter(
     let method = quote! {
         #[doc = #doc_string]
         fn #method_name(mut self, length: impl std::clone::Clone + Into<gpui3::#length_type>) -> Self where Self: std::marker::Sized {
-            let mut style = self.declared_style();
+            let style = self.style();
             #(#field_assignments)*
             self
         }

crates/refineable/derive_refineable/src/derive_refineable.rs 🔗

@@ -157,7 +157,7 @@ pub fn derive_refineable(input: TokenStream) -> TokenStream {
         })
         .collect();
 
-    let refinement_refined_assignments: Vec<TokenStream2> = fields
+    let refinement_refined_assigments: Vec<TokenStream2> = fields
         .iter()
         .map(|field| {
             let name = &field.ident;
@@ -169,14 +169,37 @@ pub fn derive_refineable(input: TokenStream) -> TokenStream {
                 }
             } else {
                 quote! {
-                    if refinement.#name.is_some() {
-                        self.#name = refinement.#name;
+                    if let Some(value) = refinement.#name {
+                        self.#name = Some(value);
                     }
                 }
             }
         })
         .collect();
 
+    let from_refinement_assigments: Vec<TokenStream2> = fields
+        .iter()
+        .map(|field| {
+            let name = &field.ident;
+            let is_refineable = is_refineable_field(field);
+            let is_optional = is_optional_field(field);
+
+            if is_refineable {
+                quote! {
+                    #name: value.#name.into(),
+                }
+            } else if is_optional {
+                quote! {
+                    #name: value.#name.map(|v| v.into()),
+                }
+            } else {
+                quote! {
+                    #name: value.#name.map(|v| v.into()).unwrap_or_default(),
+                }
+            }
+        })
+        .collect();
+
     let debug_impl = if impl_debug_on_refinement {
         let refinement_field_debugs: Vec<TokenStream2> = fields
             .iter()
@@ -243,11 +266,21 @@ pub fn derive_refineable(input: TokenStream) -> TokenStream {
             }
 
             fn refined(mut self, refinement: Self::Refinement) -> Self {
-                #( #refinement_refined_assignments )*
+                #( #refinement_refined_assigments )*
                 self
             }
         }
 
+        impl #impl_generics From<#refinement_ident #ty_generics> for #ident #ty_generics
+            #where_clause
+        {
+            fn from(value: #refinement_ident #ty_generics) -> Self {
+                Self {
+                    #( #from_refinement_assigments )*
+                }
+            }
+        }
+
         impl #impl_generics ::core::default::Default for #refinement_ident #ty_generics
             #where_clause
         {
@@ -273,7 +306,6 @@ pub fn derive_refineable(input: TokenStream) -> TokenStream {
 
         #debug_impl
     };
-
     gen.into()
 }
 

crates/storybook2/src/collab_panel.rs 🔗

@@ -1,6 +1,6 @@
 use gpui3::{
-    div, svg, view, AppContext, Context, Element, ElementId, IntoAnyElement, ParentElement,
-    ScrollState, SharedString, StyleHelpers, Styled, View, ViewContext, WindowContext,
+    div, svg, view, Active, AppContext, Context, Element, ElementId, Hover, IntoAnyElement,
+    ParentElement, ScrollState, SharedString, Styled, View, ViewContext, WindowContext,
 };
 use ui::{theme, Theme};
 
@@ -34,7 +34,7 @@ impl CollabPanel {
             .text_color(theme.middle.base.default.foreground)
             .border_color(theme.middle.base.default.border)
             .border()
-            .fill(theme.middle.base.default.background)
+            .bg(theme.middle.base.default.background)
             .child(
                 div()
                     .w_full()
@@ -132,8 +132,7 @@ impl CollabPanel {
             .flex()
             .justify_between()
             .items_center()
-            .active()
-            .fill(theme.highest.accent.default.background)
+            .active(|style| style.bg(theme.highest.accent.default.background))
             .child(div().flex().gap_1().text_sm().child(label))
             .child(
                 div().flex().h_full().gap_1().items_center().child(
@@ -145,7 +144,7 @@ impl CollabPanel {
                         })
                         .w_3p5()
                         .h_3p5()
-                        .fill(theme.middle.variant.default.foreground),
+                        .text_color(theme.middle.variant.default.foreground),
                 ),
             )
     }
@@ -174,18 +173,19 @@ impl CollabPanel {
                     .text_sm()
                     .child(
                         div()
-                            .id(0)
+                            .id("avatar")
                             // .uri(avatar_uri)
                             .size_3p5()
                             .rounded_full()
-                            .fill(theme.middle.positive.default.foreground)
+                            .bg(theme.middle.positive.default.foreground)
                             .shadow()
-                            .group_hover("")
-                            .fill(theme.middle.negative.default.foreground)
-                            .hover()
-                            .fill(theme.middle.warning.default.foreground)
-                            .group_active("")
-                            .fill(theme.middle.accent.default.foreground),
+                            .group_hover("", |style| {
+                                style.bg(theme.middle.negative.default.foreground)
+                            })
+                            .hover(|style| style.bg(theme.middle.warning.default.foreground))
+                            .group_active("", |style| {
+                                style.bg(theme.middle.accent.default.foreground)
+                            }),
                     )
                     .child(label),
             )

crates/storybook2/src/stories.rs 🔗

@@ -1,2 +1,7 @@
-pub mod kitchen_sink;
-pub mod z_index;
+mod kitchen_sink;
+mod text;
+mod z_index;
+
+pub use kitchen_sink::*;
+pub use text::*;
+pub use z_index::*;

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

@@ -0,0 +1,20 @@
+use gpui3::{div, view, white, Context, ParentElement, Styled, View, WindowContext};
+
+pub struct TextStory {
+    text: View<()>,
+}
+
+impl TextStory {
+    pub fn view(cx: &mut WindowContext) -> View<()> {
+        view(cx.entity(|cx| ()), |_, cx| {
+            div()
+                .size_full()
+                .bg(white())
+                .child(concat!(
+                    "The quick brown fox jumps over the lazy dog. ",
+                    "Meanwhile, the lazy dog decided it was time for a change. ",
+                    "He started daily workout routines, ate healthier and became the fastest dog in town.",
+                ))
+        })
+    }
+}

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

@@ -59,7 +59,7 @@ impl<S: 'static + Send + Sync> ZIndexStory<S> {
     }
 }
 
-trait Styles: StyleHelpers {
+trait Styles: Styled + Sized {
     // Trailing `_` is so we don't collide with `block` style `StyleHelpers`.
     fn block_(self) -> Self {
         self.absolute()
@@ -69,7 +69,7 @@ trait Styles: StyleHelpers {
     }
 
     fn blue(self) -> Self {
-        self.fill(rgb::<Hsla>(0xe5e8fc))
+        self.bg(rgb::<Hsla>(0xe5e8fc))
             .border_5()
             .border_color(rgb::<Hsla>(0x112382))
             // HACK: Simulate `line-height: 55px`.
@@ -79,7 +79,7 @@ trait Styles: StyleHelpers {
     }
 
     fn red(self) -> Self {
-        self.fill(rgb::<Hsla>(0xfce5e7))
+        self.bg(rgb::<Hsla>(0xfce5e7))
             .border_5()
             .border_color(rgb::<Hsla>(0xe3a1a7))
             // HACK: Simulate `text-align: center`.
@@ -115,7 +115,7 @@ impl<S: 'static + Send + Sync> ZIndexExample<S> {
                     .left(px(15.))
                     .w(px(180.))
                     .h(px(230.))
-                    .fill(rgb::<Hsla>(0xfcfbe5))
+                    .bg(rgb::<Hsla>(0xfcfbe5))
                     .text_color(rgb::<Hsla>(0x000000))
                     .border_5()
                     .border_color(rgb::<Hsla>(0xe3e0a1))

crates/storybook2/src/story_selector.rs 🔗

@@ -1,12 +1,12 @@
 use std::str::FromStr;
 use std::sync::OnceLock;
 
+use crate::stories::*;
 use anyhow::anyhow;
 use clap::builder::PossibleValue;
 use clap::ValueEnum;
 use gpui3::{view, AnyView, Context};
 use strum::{EnumIter, EnumString, IntoEnumIterator};
-
 use ui::prelude::*;
 
 #[derive(Debug, PartialEq, Eq, Clone, Copy, strum::Display, EnumString, EnumIter)]
@@ -18,6 +18,7 @@ pub enum ElementStory {
     Icon,
     Input,
     Label,
+    Text,
     ZIndex,
 }
 
@@ -43,10 +44,10 @@ impl ElementStory {
             Self::Label => {
                 view(cx.entity(|cx| ()), |_, _| ui::LabelStory::new().into_any()).into_any()
             }
-            Self::ZIndex => view(cx.entity(|cx| ()), |_, _| {
-                crate::stories::z_index::ZIndexStory::new().into_any()
-            })
-            .into_any(),
+            Self::Text => TextStory::view(cx).into_any(),
+            Self::ZIndex => {
+                view(cx.entity(|cx| ()), |_, _| ZIndexStory::new().into_any()).into_any()
+            }
         }
     }
 }
@@ -212,9 +213,7 @@ impl StorySelector {
         match self {
             Self::Element(element_story) => element_story.story(cx),
             Self::Component(component_story) => component_story.story(cx),
-            Self::KitchenSink => {
-                crate::stories::kitchen_sink::KitchenSinkStory::view(cx).into_any()
-            }
+            Self::KitchenSink => KitchenSinkStory::view(cx).into_any(),
         }
     }
 }

crates/storybook2/src/workspace.rs 🔗

@@ -3,7 +3,7 @@ use crate::{
     themes::rose_pine,
 };
 use gpui3::{
-    div, img, svg, view, Context, Element, ParentElement, StyleHelpers, Styled, View, ViewContext,
+    div, img, svg, view, Context, Element, Hover, ParentElement, Styled, View, ViewContext,
     WindowContext,
 };
 use ui::{theme, themed};
@@ -34,18 +34,18 @@ impl Workspace {
                 .w_full()
                 .h_5()
                 .mt_10()
-                .fill(theme.middle.warning.default.foreground)
+                .bg(theme.middle.warning.default.foreground)
                 .flex()
                 .flex_row()
                 .justify_center()
                 .child(
                     div()
                         .size_5()
-                        .fill(theme.middle.negative.default.foreground)
-                        .group_hover("")
-                        .fill(theme.middle.positive.default.foreground)
-                        .hover()
-                        .fill(theme.middle.variant.default.foreground),
+                        .bg(theme.middle.negative.default.foreground)
+                        .group_hover("", |style| {
+                            style.bg(theme.middle.positive.default.foreground)
+                        })
+                        .hover(|style| style.bg(theme.middle.variant.default.foreground)),
                 ),
         )
     }
@@ -63,7 +63,7 @@ impl Workspace {
                 .justify_start()
                 .items_start()
                 .text_color(theme.lowest.base.default.foreground)
-                .fill(theme.middle.base.default.background)
+                .bg(theme.middle.base.default.background)
                 .child(titlebar(cx))
                 .child(
                     div()
@@ -91,7 +91,7 @@ pub fn titlebar<S: 'static + Send + Sync>(cx: &mut ViewContext<S>) -> impl Eleme
         .justify_between()
         .w_full()
         .h_8()
-        .fill(theme.lowest.base.default.background)
+        .bg(theme.lowest.base.default.background)
         .child(this.left_group(cx))
         .child(this.right_group(cx))
 }
@@ -108,7 +108,7 @@ impl Titlebar {
             .justify_between()
             .w_full()
             .h_8()
-            .fill(theme.lowest.base.default.background)
+            .bg(theme.lowest.base.default.background)
             .child(self.left_group(cx))
             .child(self.right_group(cx))
     }
@@ -131,25 +131,25 @@ impl Titlebar {
                     .items_center()
                     .gap_2()
                     .child(
-                        div()
-                            .w_3()
-                            .h_3()
-                            .rounded_full()
-                            .fill(theme.lowest.positive.default.foreground),
+                        div().w_3().h_3().rounded_full().bg(theme
+                            .lowest
+                            .positive
+                            .default
+                            .foreground),
                     )
                     .child(
-                        div()
-                            .w_3()
-                            .h_3()
-                            .rounded_full()
-                            .fill(theme.lowest.warning.default.foreground),
+                        div().w_3().h_3().rounded_full().bg(theme
+                            .lowest
+                            .warning
+                            .default
+                            .foreground),
                     )
                     .child(
-                        div()
-                            .w_3()
-                            .h_3()
-                            .rounded_full()
-                            .fill(theme.lowest.negative.default.foreground),
+                        div().w_3().h_3().rounded_full().bg(theme
+                            .lowest
+                            .negative
+                            .default
+                            .foreground),
                     ),
             )
             // === Project Info === //
@@ -215,12 +215,12 @@ impl Titlebar {
                             svg()
                                 .path("icons/exit.svg")
                                 .size_4()
-                                .fill(theme.lowest.base.default.foreground),
+                                .text_color(theme.lowest.base.default.foreground),
                         ),
                     ),
                 ),
             )
-            .child(div().w_px().h_3().fill(theme.lowest.base.default.border))
+            .child(div().w_px().h_3().bg(theme.lowest.base.default.border))
             // === Comms === //
             .child(
                 div().child(
@@ -245,7 +245,7 @@ impl Titlebar {
                                     svg()
                                         .path("icons/mic.svg")
                                         .size_3p5()
-                                        .fill(theme.lowest.base.default.foreground),
+                                        .text_color(theme.lowest.base.default.foreground),
                                 ),
                         )
                         .child(
@@ -265,7 +265,7 @@ impl Titlebar {
                                     svg()
                                         .path("icons/speaker-loud.svg")
                                         .size_3p5()
-                                        .fill(theme.lowest.base.default.foreground),
+                                        .text_color(theme.lowest.base.default.foreground),
                                 ),
                         )
                         .child(
@@ -285,12 +285,12 @@ impl Titlebar {
                                     svg()
                                         .path("icons/desktop.svg")
                                         .size_3p5()
-                                        .fill(theme.lowest.base.default.foreground),
+                                        .text_color(theme.lowest.base.default.foreground),
                                 ),
                         ),
                 ),
             )
-            .child(div().w_px().h_3().fill(theme.lowest.base.default.border))
+            .child(div().w_px().h_3().bg(theme.lowest.base.default.border))
             // User Group
             .child(
                 div().child(
@@ -311,14 +311,14 @@ impl Titlebar {
                                 .uri("https://avatars.githubusercontent.com/u/1714999?v=4")
                                 .size_4()
                                 .rounded_md()
-                                .fill(theme.middle.on.default.foreground),
+                                .bg(theme.middle.on.default.foreground),
                         )
                         .child(
                             svg()
                                 .path("icons/caret_down.svg")
                                 .w_2()
                                 .h_2()
-                                .fill(theme.lowest.variant.default.foreground),
+                                .text_color(theme.lowest.variant.default.foreground),
                         ),
                 ),
             )
@@ -341,7 +341,7 @@ mod statusbar {
             .justify_between()
             .w_full()
             .h_8()
-            .fill(theme.lowest.base.default.background)
+            .bg(theme.lowest.base.default.background)
         // .child(left_group(cx))
         // .child(right_group(cx))
     }
@@ -374,7 +374,7 @@ mod statusbar {
                                     .path("icons/project.svg")
                                     .w_4()
                                     .h_4()
-                                    .fill(theme.lowest.base.default.foreground),
+                                    .text_color(theme.lowest.base.default.foreground),
                             ),
                     )
                     .child(
@@ -389,7 +389,7 @@ mod statusbar {
                                     .path("icons/conversations.svg")
                                     .w_4()
                                     .h_4()
-                                    .fill(theme.lowest.base.default.foreground),
+                                    .text_color(theme.lowest.base.default.foreground),
                             ),
                     )
                     .child(
@@ -404,7 +404,7 @@ mod statusbar {
                                     .path("icons/file_icons/notebook.svg")
                                     .w_4()
                                     .h_4()
-                                    .fill(theme.lowest.accent.default.foreground),
+                                    .text_color(theme.lowest.accent.default.foreground),
                             ),
                     ),
             )
@@ -432,7 +432,7 @@ mod statusbar {
                                     .path("icons/error.svg")
                                     .w_4()
                                     .h_4()
-                                    .fill(theme.lowest.negative.default.foreground),
+                                    .text_color(theme.lowest.negative.default.foreground),
                             )
                             .child(div().text_sm().child("2")),
                     )
@@ -473,7 +473,7 @@ mod statusbar {
                                     .path("icons/check_circle.svg")
                                     .w_4()
                                     .h_4()
-                                    .fill(theme.lowest.base.default.foreground),
+                                    .text_color(theme.lowest.base.default.foreground),
                             ),
                     )
                     .child(
@@ -488,7 +488,7 @@ mod statusbar {
                                     .path("icons/copilot.svg")
                                     .w_4()
                                     .h_4()
-                                    .fill(theme.lowest.accent.default.foreground),
+                                    .text_color(theme.lowest.accent.default.foreground),
                             ),
                     ),
             )

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

@@ -31,7 +31,11 @@ impl<S: 'static + Send + Sync + Clone> Breadcrumb<S> {
             .text_color(HighlightColor::Default.hsla(theme))
     }
 
-    fn render(&mut self, view_state: &mut S, cx: &mut ViewContext<S>) -> impl Element<ViewState = S> {
+    fn render(
+        &mut self,
+        view_state: &mut S,
+        cx: &mut ViewContext<S>,
+    ) -> impl Element<ViewState = S> {
         let theme = theme(cx);
 
         let symbols_len = self.symbols.len();
@@ -43,8 +47,7 @@ impl<S: 'static + Send + Sync + Clone> Breadcrumb<S> {
             .text_sm()
             .text_color(theme.middle.base.default.foreground)
             .rounded_md()
-            .hover()
-            .fill(theme.highest.base.hovered.background)
+            .hover(|style| style.bg(theme.highest.base.hovered.background))
             .child(self.path.clone().to_str().unwrap().to_string())
             .child(if !self.symbols.is_empty() {
                 self.render_separator(&theme)
@@ -99,7 +102,11 @@ mod stories {
             }
         }
 
-        fn render(&mut self, view_state: &mut S, cx: &mut ViewContext<S>) -> impl Element<ViewState = S> {
+        fn render(
+            &mut self,
+            view_state: &mut S,
+            cx: &mut ViewContext<S>,
+        ) -> impl Element<ViewState = S> {
             let theme = theme(cx);
 
             Story::container(cx)

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

@@ -179,7 +179,7 @@ impl<S: 'static + Send + Sync + Clone> Buffer<S> {
         };
 
         h_stack()
-            .fill(line_background)
+            .bg(line_background)
             .w_full()
             .gap_2()
             .px_1()
@@ -201,7 +201,7 @@ impl<S: 'static + Send + Sync + Clone> Buffer<S> {
                     ),
                 )
             })
-            .child(div().mx_0p5().w_1().h_full().fill(row.status.hsla(cx)))
+            .child(div().mx_0p5().w_1().h_full().bg(row.status.hsla(cx)))
             .children(row.line.map(|line| {
                 div()
                     .flex()
@@ -232,7 +232,7 @@ impl<S: 'static + Send + Sync + Clone> Buffer<S> {
             .flex_1()
             .w_full()
             .h_full()
-            .fill(theme.highest.base.default.background)
+            .bg(theme.highest.base.default.background)
             .children(rows)
     }
 }
@@ -263,7 +263,11 @@ mod stories {
             }
         }
 
-        fn render(&mut self, _view: &mut S, cx: &mut ViewContext<S>) -> impl Element<ViewState = S> {
+        fn render(
+            &mut self,
+            _view: &mut S,
+            cx: &mut ViewContext<S>,
+        ) -> impl Element<ViewState = S> {
             let theme = theme(cx);
 
             Story::container(cx)

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

@@ -30,7 +30,7 @@ impl BufferSearch {
         let theme = theme(cx);
 
         h_stack()
-            .fill(theme.highest.base.default.background)
+            .bg(theme.highest.base.default.background)
             .p_2()
             .child(
                 h_stack()

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

@@ -1,6 +1,6 @@
 use std::marker::PhantomData;
 
-use gpui3::{img, svg, ArcCow};
+use gpui3::{img, svg, SharedString};
 
 use crate::prelude::*;
 use crate::theme::{theme, Theme};
@@ -29,14 +29,14 @@ impl<S: 'static + Send + Sync + Clone> CollabPanel<S> {
 
         v_stack()
             .h_full()
-            .fill(color.surface)
+            .bg(color.surface)
             .child(
                 v_stack()
                     .w_full()
                     .overflow_y_scroll(self.scroll_state.clone())
                     .child(
                         div()
-                            .fill(theme.lowest.base.default.background)
+                            .bg(theme.lowest.base.default.background)
                             .pb_1()
                             .border_color(theme.lowest.base.default.border)
                             .border_b()
@@ -100,7 +100,7 @@ impl<S: 'static + Send + Sync + Clone> CollabPanel<S> {
 
     fn list_section_header(
         &self,
-        label: impl Into<ArcCow<'static, str>>,
+        label: impl Into<SharedString>,
         expanded: bool,
         theme: &Theme,
     ) -> impl Element<ViewState = S> {
@@ -121,15 +121,15 @@ impl<S: 'static + Send + Sync + Clone> CollabPanel<S> {
                         })
                         .w_3p5()
                         .h_3p5()
-                        .fill(theme.middle.variant.default.foreground),
+                        .text_color(theme.middle.variant.default.foreground),
                 ),
             )
     }
 
     fn list_item(
         &self,
-        avatar_uri: impl Into<ArcCow<'static, str>>,
-        label: impl Into<ArcCow<'static, str>>,
+        avatar_uri: impl Into<SharedString>,
+        label: impl Into<SharedString>,
         theme: &Theme,
     ) -> impl Element<ViewState = S> {
         div()
@@ -137,10 +137,8 @@ impl<S: 'static + Send + Sync + Clone> CollabPanel<S> {
             .px_2()
             .flex()
             .items_center()
-            .hover()
-            .fill(theme.lowest.variant.hovered.background)
-            // .active()
-            // .fill(theme.lowest.variant.pressed.background)
+            .hover(|style| style.bg(theme.lowest.variant.hovered.background))
+            // .active(|style| style.fill(theme.lowest.variant.pressed.background))
             .child(
                 div()
                     .flex()
@@ -148,11 +146,11 @@ impl<S: 'static + Send + Sync + Clone> CollabPanel<S> {
                     .gap_1()
                     .text_sm()
                     .child(
-                        img()
-                            .uri(avatar_uri)
-                            .size_3p5()
-                            .rounded_full()
-                            .fill(theme.middle.positive.default.foreground),
+                        img().uri(avatar_uri).size_3p5().rounded_full().bg(theme
+                            .middle
+                            .positive
+                            .default
+                            .foreground),
                     )
                     .child(label.into()),
             )
@@ -180,7 +178,11 @@ mod stories {
             }
         }
 
-        fn render(&mut self, _view: &mut S, cx: &mut ViewContext<S>) -> impl Element<ViewState = S> {
+        fn render(
+            &mut self,
+            _view: &mut S,
+            cx: &mut ViewContext<S>,
+        ) -> impl Element<ViewState = S> {
             Story::container(cx)
                 .child(Story::title_for::<_, CollabPanel<S>>(cx))
                 .child(Story::label(cx, "Default"))

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

@@ -48,7 +48,7 @@ impl<S: 'static + Send + Sync + Clone> ContextMenu<S> {
 
         v_stack()
             .flex()
-            .fill(theme.lowest.base.default.background)
+            .bg(theme.lowest.base.default.background)
             .border()
             .border_color(theme.lowest.base.default.border)
             .child(
@@ -87,7 +87,11 @@ mod stories {
             }
         }
 
-        fn render(&mut self, _view: &mut S, cx: &mut ViewContext<S>) -> impl Element<ViewState = S> {
+        fn render(
+            &mut self,
+            _view: &mut S,
+            cx: &mut ViewContext<S>,
+        ) -> impl Element<ViewState = S> {
             Story::container(cx)
                 .child(Story::title_for::<_, ContextMenu<S>>(cx))
                 .child(Story::label(cx, "Default"))

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

@@ -76,7 +76,7 @@ impl<S: 'static + Send + Sync> IconButton<S> {
 
         let mut div = div();
         if self.variant == ButtonVariant::Filled {
-            div = div.fill(theme.highest.on.default.background);
+            div = div.bg(theme.highest.on.default.background);
         }
 
         if let Some(click_handler) = self.handlers.click.clone() {
@@ -91,8 +91,7 @@ impl<S: 'static + Send + Sync> IconButton<S> {
             .items_center()
             .justify_center()
             .rounded_md()
-            .hover()
-            .fill(theme.highest.base.hovered.background)
+            .hover(|style| style.bg(theme.highest.base.hovered.background))
             // .active()
             // .fill(theme.highest.base.pressed.background)
             .child(IconElement::new(self.icon).color(icon_color))

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

@@ -81,7 +81,7 @@ impl<S: 'static + Send + Sync> Key<S> {
             .rounded_md()
             .text_sm()
             .text_color(theme.lowest.on.default.foreground)
-            .fill(theme.lowest.on.default.background)
+            .bg(theme.lowest.on.default.background)
             .child(self.key.clone())
     }
 }
@@ -189,7 +189,11 @@ mod stories {
             }
         }
 
-        fn render(&mut self, _view: &mut S, cx: &mut ViewContext<S>) -> impl Element<ViewState = S> {
+        fn render(
+            &mut self,
+            _view: &mut S,
+            cx: &mut ViewContext<S>,
+        ) -> impl Element<ViewState = S> {
             let all_modifier_permutations = ModifierKey::iter().permutations(2);
 
             Story::container(cx)

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

@@ -106,7 +106,7 @@ impl<S: 'static + Send + Sync + Clone> ListHeader<S> {
         h_stack()
             .flex_1()
             .w_full()
-            .fill(color.surface)
+            .bg(color.surface)
             .when(self.state == InteractionState::Focused, |this| {
                 this.border().border_color(color.border_focused)
             })
@@ -398,7 +398,7 @@ impl<S: 'static + Send + Sync + Clone> ListEntry<S> {
         div()
             .relative()
             .group("")
-            .fill(color.surface)
+            .bg(color.surface)
             .when(self.state == InteractionState::Focused, |this| {
                 this.border().border_color(color.border_focused)
             })
@@ -412,12 +412,11 @@ impl<S: 'static + Send + Sync + Clone> ListEntry<S> {
                             .h_full()
                             .flex()
                             .justify_center()
-                            .group_hover("")
-                            .fill(color.border_focused)
+                            .group_hover("", |style| style.bg(color.border_focused))
                             .child(
                                 h_stack()
                                     .child(div().w_px().h_full())
-                                    .child(div().w_px().h_full().fill(color.border)),
+                                    .child(div().w_px().h_full().bg(color.border)),
                             )
                     }))
                     .flex()
@@ -446,7 +445,7 @@ impl<S: 'static + Send + Sync> ListSeparator<S> {
     fn render(&mut self, _view: &mut S, cx: &mut ViewContext<S>) -> impl Element<ViewState = S> {
         let color = ThemeColor::new(cx);
 
-        div().h_px().w_full().fill(color.border)
+        div().h_px().w_full().bg(color.border)
     }
 }
 

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

@@ -32,7 +32,7 @@ impl<S: 'static + Send + Sync + Clone> MultiBuffer<S> {
                             .items_center()
                             .justify_between()
                             .p_4()
-                            .fill(theme.lowest.base.default.background)
+                            .bg(theme.lowest.base.default.background)
                             .child(Label::new("main.rs").size(LabelSize::Small))
                             .child(IconButton::new(Icon::ArrowUpRight)),
                     )

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

@@ -1,6 +1,6 @@
 use std::marker::PhantomData;
 
-use gpui3::{Element, ParentElement, StyleHelpers, ViewContext};
+use gpui3::{Element, ParentElement, Styled, ViewContext};
 
 use crate::{
     h_stack, v_stack, Button, Icon, IconButton, IconElement, Label, ThemeColor, Toast, ToastOrigin,

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

@@ -53,7 +53,7 @@ impl<S: 'static + Send + Sync + Clone> Palette<S> {
         v_stack()
             .w_96()
             .rounded_lg()
-            .fill(theme.lowest.base.default.background)
+            .bg(theme.lowest.base.default.background)
             .border()
             .border_color(theme.lowest.base.default.border)
             .child(
@@ -64,7 +64,7 @@ impl<S: 'static + Send + Sync + Clone> Palette<S> {
                             Label::new(self.input_placeholder).color(LabelColor::Placeholder),
                         ),
                     ))
-                    .child(div().h_px().w_full().fill(theme.lowest.base.default.border))
+                    .child(div().h_px().w_full().bg(theme.lowest.base.default.border))
                     .child(
                         v_stack()
                             .py_0p5()
@@ -89,8 +89,7 @@ impl<S: 'static + Send + Sync + Clone> Palette<S> {
                                     .px_2()
                                     .py_0p5()
                                     .rounded_lg()
-                                    .hover()
-                                    .fill(theme.lowest.base.hovered.background)
+                                    .hover(|style| style.bg(theme.lowest.base.hovered.background))
                                     // .active()
                                     // .fill(theme.lowest.base.pressed.background)
                                     .child(item.clone())
@@ -172,7 +171,11 @@ mod stories {
             }
         }
 
-        fn render(&mut self, _view: &mut S, cx: &mut ViewContext<S>) -> impl Element<ViewState = S> {
+        fn render(
+            &mut self,
+            _view: &mut S,
+            cx: &mut ViewContext<S>,
+        ) -> impl Element<ViewState = S> {
             Story::container(cx)
                 .child(Story::title_for::<_, Palette<S>>(cx))
                 .child(Story::label(cx, "Default"))

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

@@ -109,7 +109,7 @@ impl<S: 'static + Send + Sync> Panel<S> {
                     .h_full()
                     // .w(current_width)
                     .w_64()
-                    .fill(theme.middle.base.default.background)
+                    .bg(theme.middle.base.default.background)
                     .border_r()
                     .border_color(theme.middle.base.default.border);
             }
@@ -119,7 +119,7 @@ impl<S: 'static + Send + Sync> Panel<S> {
                     .h_full()
                     // .w(current_width)
                     .w_64()
-                    .fill(theme.middle.base.default.background)
+                    .bg(theme.middle.base.default.background)
                     .border_l()
                     .border_color(theme.middle.base.default.border);
             }
@@ -129,7 +129,7 @@ impl<S: 'static + Send + Sync> Panel<S> {
                     .w_full()
                     // .h(current_width)
                     .h_64()
-                    .fill(theme.middle.base.default.background)
+                    .bg(theme.middle.base.default.background)
                     .border_t()
                     .border_color(theme.middle.base.default.border);
             }
@@ -140,9 +140,7 @@ impl<S: 'static + Send + Sync> Panel<S> {
 }
 
 impl<S: 'static + Send + Sync> ParentElement for Panel<S> {
-    type State = S;
-
-    fn children_mut(&mut self) -> &mut SmallVec<[AnyElement<Self::State>; 2]> {
+    fn children_mut(&mut self) -> &mut SmallVec<[AnyElement<Self::ViewState>; 2]> {
         &mut self.children
     }
 }

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

@@ -48,7 +48,7 @@ impl<S: 'static + Send + Sync> Pane<S> {
         div()
             .flex()
             .flex_initial()
-            .fill(self.fill)
+            .bg(self.fill)
             .w(self.size.width)
             .h(self.size.height)
             .overflow_y_scroll(self.scroll_state.clone())
@@ -57,9 +57,7 @@ impl<S: 'static + Send + Sync> Pane<S> {
 }
 
 impl<S: 'static + Send + Sync> ParentElement for Pane<S> {
-    type State = S;
-
-    fn children_mut(&mut self) -> &mut SmallVec<[AnyElement<Self::State>; 2]> {
+    fn children_mut(&mut self) -> &mut SmallVec<[AnyElement<Self::ViewState>; 2]> {
         &mut self.children
     }
 }
@@ -101,7 +99,7 @@ impl<S: 'static + Send + Sync> PaneGroup<S> {
                 .gap_px()
                 .w_full()
                 .h_full()
-                .fill(theme.lowest.base.default.background)
+                .bg(theme.lowest.base.default.background)
                 .children(self.panes.iter_mut().map(|pane| pane.render(view, cx)));
 
             if self.split_direction == SplitDirection::Horizontal {
@@ -118,7 +116,7 @@ impl<S: 'static + Send + Sync> PaneGroup<S> {
                 .gap_px()
                 .w_full()
                 .h_full()
-                .fill(theme.lowest.base.default.background)
+                .bg(theme.lowest.base.default.background)
                 .children(self.groups.iter_mut().map(|group| group.render(view, cx)));
 
             if self.split_direction == SplitDirection::Horizontal {

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

@@ -39,13 +39,11 @@ impl<S: 'static + Send + Sync> PlayerStack<S> {
             .gap_px()
             .justify_center()
             .child(
-                div().flex().justify_center().w_full().child(
-                    div()
-                        .w_4()
-                        .h_0p5()
-                        .rounded_sm()
-                        .fill(player.cursor_color(cx)),
-                ),
+                div()
+                    .flex()
+                    .justify_center()
+                    .w_full()
+                    .child(div().w_4().h_0p5().rounded_sm().bg(player.cursor_color(cx))),
             )
             .child(
                 div()
@@ -55,7 +53,7 @@ impl<S: 'static + Send + Sync> PlayerStack<S> {
                     .h_6()
                     .pl_1()
                     .rounded_lg()
-                    .fill(if followers.is_none() {
+                    .bg(if followers.is_none() {
                         system_color.transparent
                     } else {
                         player.selection_color(cx)

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

@@ -96,7 +96,7 @@ impl StatusBar {
             .items_center()
             .justify_between()
             .w_full()
-            .fill(theme.lowest.base.default.background)
+            .bg(theme.lowest.base.default.background)
             .child(self.left_tools(view, &theme))
             .child(self.right_tools(view, &theme))
     }

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

@@ -102,7 +102,7 @@ impl<S: 'static + Send + Sync + Clone> Tab<S> {
             .flex()
             .items_center()
             .justify_center()
-            .fill(if self.current {
+            .bg(if self.current {
                 theme.highest.base.default.background
             } else {
                 theme.middle.base.default.background
@@ -157,7 +157,11 @@ mod stories {
             }
         }
 
-        fn render(&mut self, _view: &mut S, cx: &mut ViewContext<S>) -> impl Element<ViewState = S> {
+        fn render(
+            &mut self,
+            _view: &mut S,
+            cx: &mut ViewContext<S>,
+        ) -> impl Element<ViewState = S> {
             let git_statuses = GitStatus::iter();
             let fs_statuses = FileSystemStatus::iter();
 

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

@@ -31,7 +31,7 @@ impl<S: 'static + Send + Sync + Clone> TabBar<S> {
         div()
             .w_full()
             .flex()
-            .fill(theme.middle.base.default.background)
+            .bg(theme.middle.base.default.background)
             // Left Side
             .child(
                 div()
@@ -105,7 +105,11 @@ mod stories {
             }
         }
 
-        fn render(&mut self, _view: &mut S, cx: &mut ViewContext<S>) -> impl Element<ViewState = S> {
+        fn render(
+            &mut self,
+            _view: &mut S,
+            cx: &mut ViewContext<S>,
+        ) -> impl Element<ViewState = S> {
             Story::container(cx)
                 .child(Story::title_for::<_, TabBar<S>>(cx))
                 .child(Story::label(cx, "Default"))

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

@@ -32,7 +32,7 @@ impl<S: 'static + Send + Sync + Clone> Terminal<S> {
                 div()
                     .w_full()
                     .flex()
-                    .fill(theme.middle.base.default.background)
+                    .bg(theme.middle.base.default.background)
                     .child(
                         div().px_1().flex().flex_none().gap_2().child(
                             div()

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

@@ -108,7 +108,7 @@ impl TitleBar {
             .justify_between()
             .w_full()
             .h_8()
-            .fill(theme.lowest.base.default.background)
+            .bg(theme.lowest.base.default.background)
             .child(
                 div()
                     .flex()

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

@@ -56,15 +56,13 @@ impl<S: 'static + Send + Sync> Toast<S> {
             .rounded_lg()
             .shadow_md()
             .overflow_hidden()
-            .fill(color.elevated_surface)
+            .bg(color.elevated_surface)
             .children(self.children.drain(..))
     }
 }
 
 impl<S: 'static + Send + Sync> ParentElement for Toast<S> {
-    type State = S;
-
-    fn children_mut(&mut self) -> &mut SmallVec<[AnyElement<Self::State>; 2]> {
+    fn children_mut(&mut self) -> &mut SmallVec<[AnyElement<Self::ViewState>; 2]> {
         &mut self.children
     }
 }

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

@@ -59,7 +59,7 @@ impl<S: 'static + Send + Sync> Toolbar<S> {
         let theme = theme(cx);
 
         div()
-            .fill(theme.highest.base.default.background)
+            .bg(theme.highest.base.default.background)
             .p_2()
             .flex()
             .justify_between()

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

@@ -37,7 +37,7 @@ impl<S: 'static + Send + Sync> TrafficLight<S> {
             (false, _) => theme.lowest.base.active.background,
         };
 
-        div().w_3().h_3().rounded_full().fill(fill)
+        div().w_3().h_3().rounded_full().bg(fill)
     }
 }
 
@@ -104,7 +104,11 @@ mod stories {
             }
         }
 
-        fn render(&mut self, _view: &mut S, cx: &mut ViewContext<S>) -> impl Element<ViewState = S> {
+        fn render(
+            &mut self,
+            _view: &mut S,
+            cx: &mut ViewContext<S>,
+        ) -> impl Element<ViewState = S> {
             Story::container(cx)
                 .child(Story::title_for::<_, TrafficLights<S>>(cx))
                 .child(Story::label(cx, "Default"))

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

@@ -151,7 +151,7 @@ impl Workspace {
             .justify_start()
             .items_start()
             .text_color(theme.lowest.base.default.foreground)
-            .fill(theme.lowest.base.default.background)
+            .bg(theme.lowest.base.default.background)
             .child(self.title_bar.clone())
             .child(
                 div()

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

@@ -39,7 +39,7 @@ impl<S: 'static + Send + Sync> Avatar<S> {
 
         img.uri(self.src.clone())
             .size_4()
-            .fill(theme.middle.warning.default.foreground)
+            .bg(theme.middle.warning.default.foreground)
     }
 }
 
@@ -64,7 +64,11 @@ mod stories {
             }
         }
 
-        fn render(&mut self, _view: &mut S, cx: &mut ViewContext<S>) -> impl Element<ViewState = S> {
+        fn render(
+            &mut self,
+            _view: &mut S,
+            cx: &mut ViewContext<S>,
+        ) -> impl Element<ViewState = S> {
             Story::container(cx)
                 .child(Story::title_for::<_, Avatar<S>>(cx))
                 .child(Story::label(cx, "Default"))

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

@@ -162,7 +162,7 @@ impl<S: 'static + Send + Sync + Clone> Button<S> {
             .rounded_md()
             .border()
             .border_color(border_color)
-            .fill(self.background_color(cx));
+            .bg(self.background_color(cx));
 
         match (self.icon, self.icon_position) {
             (Some(_), Some(IconPosition::Left)) => {

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

@@ -183,7 +183,10 @@ impl<S: 'static + Send + Sync> IconElement<S> {
             IconSize::Large => svg().size_4(),
         };
 
-        sized_svg.flex_none().path(self.icon.path()).fill(fill)
+        sized_svg
+            .flex_none()
+            .path(self.icon.path())
+            .text_color(fill)
     }
 }
 

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

@@ -87,12 +87,13 @@ impl<S: 'static + Send + Sync> Input<S> {
             .px_2()
             .border()
             .border_color(border_color_default)
-            .fill(background_color_default)
-            .hover()
-            .border_color(border_color_hover)
-            // .active()
-            // .border_color(border_color_active)
-            .fill(background_color_active)
+            .bg(background_color_default)
+            .hover(|style| {
+                style
+                    .border_color(border_color_hover)
+                    .bg(background_color_active)
+            })
+            // .active(|a| .border_color(border_color_active))
             .flex()
             .items_center()
             .child(
@@ -128,7 +129,11 @@ mod stories {
             }
         }
 
-        fn render(&mut self, _view: &mut S, cx: &mut ViewContext<S>) -> impl Element<ViewState = S> {
+        fn render(
+            &mut self,
+            _view: &mut S,
+            cx: &mut ViewContext<S>,
+        ) -> impl Element<ViewState = S> {
             Story::container(cx)
                 .child(Story::title_for::<_, Input<S>>(cx))
                 .child(Story::label(cx, "Default"))

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

@@ -141,7 +141,7 @@ impl<S: 'static + Send + Sync + Clone> Label<S> {
                         .my_auto()
                         .w_full()
                         .h_px()
-                        .fill(LabelColor::Hidden.hsla(cx)),
+                        .bg(LabelColor::Hidden.hsla(cx)),
                 )
             })
             .children(runs.into_iter().map(|run| {

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

@@ -2,7 +2,7 @@ use gpui3::{div, Div};
 
 use crate::prelude::*;
 
-pub trait Stack: StyleHelpers {
+pub trait Stack: Styled + Sized {
     /// Horizontally stacks elements.
     fn h_stack(self) -> Self {
         self.flex().flex_row().items_center()

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

@@ -18,6 +18,6 @@ impl<S: 'static + Send + Sync> ToolDivider<S> {
     fn render(&mut self, _view: &mut S, cx: &mut ViewContext<S>) -> impl Element<ViewState = S> {
         let theme = theme(cx);
 
-        div().w_px().h_3().fill(theme.lowest.base.default.border)
+        div().w_px().h_3().bg(theme.lowest.base.default.border)
     }
 }

crates/ui2/src/prelude.rs 🔗

@@ -1,5 +1,5 @@
 pub use gpui3::{
-    div, Element, IntoAnyElement, ParentElement, ScrollState, StyleHelpers, Styled, ViewContext,
+    div, Click, Element, Hover, IntoAnyElement, ParentElement, ScrollState, Styled, ViewContext,
     WindowContext,
 };
 

crates/ui2/src/story.rs 🔗

@@ -16,7 +16,7 @@ impl Story {
             .pt_2()
             .px_4()
             .font("Zed Mono Extended")
-            .fill(theme.lowest.base.default.background)
+            .bg(theme.lowest.base.default.background)
     }
 
     pub fn title<S: 'static + Send + Sync>(

crates/ui2/src/theme.rs 🔗

@@ -160,7 +160,7 @@ impl<E: Element> Element for Themed<E> {
     type ViewState = E::ViewState;
     type ElementState = E::ElementState;
 
-    fn element_id(&self) -> Option<gpui3::ElementId> {
+    fn id(&self) -> Option<gpui3::ElementId> {
         None
     }