Finish building out adapters and names

Mikayla created

Document core traits
Add start for a component storybook

Change summary

Cargo.lock                            |  11 +
Cargo.toml                            |   1 
crates/collab_ui/src/collab_panel.rs  |  11 -
crates/gpui/examples/components.rs    |   4 
crates/gpui/src/elements.rs           |  12 
crates/gpui/src/elements/component.rs | 192 ++++++++++++++++++++--------
crates/search/src/search.rs           |   8 
crates/theme/src/components.rs        | 117 +++++++----------
8 files changed, 208 insertions(+), 148 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -1562,6 +1562,17 @@ dependencies = [
  "workspace",
 ]
 
+[[package]]
+name = "component_test"
+version = "0.1.0"
+dependencies = [
+ "gpui",
+ "settings",
+ "theme",
+ "util",
+ "workspace",
+]
+
 [[package]]
 name = "concurrent-queue"
 version = "2.2.0"

Cargo.toml 🔗

@@ -13,6 +13,7 @@ members = [
     "crates/collab_ui",
     "crates/collections",
     "crates/command_palette",
+    "crates/component_test",
     "crates/context_menu",
     "crates/copilot",
     "crates/copilot_button",

crates/collab_ui/src/collab_panel.rs 🔗

@@ -17,8 +17,8 @@ use gpui::{
     actions,
     elements::{
         Canvas, ChildView, Component, Empty, Flex, Image, Label, List, ListOffset, ListState,
-        MouseEventHandler, Orientation, OverlayPositionMode, Padding, ParentElement, Stack,
-        StyleableComponent, Svg,
+        MouseEventHandler, Orientation, OverlayPositionMode, Padding, ParentElement, SafeStylable,
+        Stack, Svg,
     },
     geometry::{
         rect::RectF,
@@ -1633,11 +1633,8 @@ impl CollabPanel {
                 })
                 .align_children_center()
                 .styleable_component()
-                .disclosable(
-                    disclosed,
-                    Box::new(ToggleCollapsed { channel_id }),
-                    channel_id as usize,
-                )
+                .disclosable(disclosed, Box::new(ToggleCollapsed { channel_id }))
+                .with_id(channel_id as usize)
                 .with_style(theme.disclosure.clone())
                 .element()
                 .constrained()

crates/gpui/examples/components.rs 🔗

@@ -72,7 +72,7 @@ impl View for TestView {
                         TextStyle::for_color(Color::blue()),
                     )
                     .with_style(ButtonStyle::fill(Color::yellow()))
-                    .stateful_element(),
+                    .element(),
                 )
                 .with_child(
                     ToggleableButton::new(self.is_doubling, move |_, v: &mut Self, cx| {
@@ -84,7 +84,7 @@ impl View for TestView {
                         inactive: ButtonStyle::fill(Color::red()),
                         active: ButtonStyle::fill(Color::green()),
                     })
-                    .stateful_element(),
+                    .element(),
                 )
                 .expanded()
                 .contained()

crates/gpui/src/elements.rs 🔗

@@ -230,25 +230,25 @@ pub trait Element<V: View>: 'static {
         MouseEventHandler::for_child::<Tag>(self.into_any(), region_id)
     }
 
-    fn stateful_component(self) -> ElementAdapter<V>
+    fn component(self) -> StatelessElementAdapter
     where
         Self: Sized,
     {
-        ElementAdapter::new(self.into_any())
+        StatelessElementAdapter::new(self.into_any())
     }
 
-    fn component(self) -> DynamicElementAdapter
+    fn stateful_component(self) -> StatefulElementAdapter<V>
     where
         Self: Sized,
     {
-        DynamicElementAdapter::new(self.into_any())
+        StatefulElementAdapter::new(self.into_any())
     }
 
-    fn styleable_component(self) -> StylableAdapter<DynamicElementAdapter>
+    fn styleable_component(self) -> StylableAdapter<StatelessElementAdapter>
     where
         Self: Sized,
     {
-        DynamicElementAdapter::new(self.into_any()).stylable()
+        StatelessElementAdapter::new(self.into_any()).stylable()
     }
 }
 

crates/gpui/src/elements/component.rs 🔗

@@ -9,14 +9,15 @@ use crate::{
 
 use super::Empty;
 
+/// The core stateless component trait, simply rendering an element tree
 pub trait Component {
-    fn render<V: View>(self, v: &mut V, cx: &mut ViewContext<V>) -> AnyElement<V>;
+    fn render<V: View>(self, cx: &mut ViewContext<V>) -> AnyElement<V>;
 
-    fn element<V: View>(self) -> StatefulAdapter<V, Self>
+    fn element<V: View>(self) -> ComponentAdapter<V, Self>
     where
         Self: Sized,
     {
-        StatefulAdapter::new(self)
+        ComponentAdapter::new(self)
     }
 
     fn stylable(self) -> StylableAdapter<Self>
@@ -25,8 +26,46 @@ pub trait Component {
     {
         StylableAdapter::new(self)
     }
+
+    fn stateful<V: View>(self) -> StatefulAdapter<Self, V>
+    where
+        Self: Sized,
+    {
+        StatefulAdapter::new(self)
+    }
+}
+
+/// Allows a a component's styles to be rebound in a simple way.
+pub trait Stylable: Component {
+    type Style: Clone;
+
+    fn with_style(self, style: Self::Style) -> Self;
 }
 
+/// This trait models the typestate pattern for a component's style,
+/// enforcing at compile time that a component is only usable after
+/// it has been styled while still allowing for late binding of the
+/// styling information
+pub trait SafeStylable {
+    type Style: Clone;
+    type Output: Component;
+
+    fn with_style(self, style: Self::Style) -> Self::Output;
+}
+
+/// All stylable components can trivially implement SafeStylable
+impl<C: Stylable> SafeStylable for C {
+    type Style = C::Style;
+
+    type Output = C;
+
+    fn with_style(self, style: Self::Style) -> Self::Output {
+        self.with_style(style)
+    }
+}
+
+/// Allows converting an unstylable component into a stylable one
+/// by using `()` as the style type
 pub struct StylableAdapter<C: Component> {
     component: C,
 }
@@ -37,7 +76,7 @@ impl<C: Component> StylableAdapter<C> {
     }
 }
 
-impl<C: Component> StyleableComponent for StylableAdapter<C> {
+impl<C: Component> SafeStylable for StylableAdapter<C> {
     type Style = ();
 
     type Output = C;
@@ -47,36 +86,61 @@ impl<C: Component> StyleableComponent for StylableAdapter<C> {
     }
 }
 
-pub trait StyleableComponent {
-    type Style: Clone;
-    type Output: Component;
+/// This is a secondary trait for components that can be styled
+/// which rely on their view's state. This is useful for components that, for example,
+/// want to take click handler callbacks Unfortunately, the generic bound on the
+/// Component trait makes it incompatible with the stateless components above.
+// So let's just replicate them for now
+pub trait StatefulComponent<V: View> {
+    fn render(self, v: &mut V, cx: &mut ViewContext<V>) -> AnyElement<V>;
 
-    fn with_style(self, style: Self::Style) -> Self::Output;
+    fn element(self) -> ComponentAdapter<V, Self>
+    where
+        Self: Sized,
+    {
+        ComponentAdapter::new(self)
+    }
+
+    fn styleable(self) -> StatefulStylableAdapter<Self, V>
+    where
+        Self: Sized,
+    {
+        StatefulStylableAdapter::new(self)
+    }
+
+    fn stateless(self) -> StatelessElementAdapter
+    where
+        Self: Sized + 'static,
+    {
+        StatelessElementAdapter::new(self.element().into_any())
+    }
 }
 
-impl Component for () {
-    fn render<V: View>(self, _: &mut V, _: &mut ViewContext<V>) -> AnyElement<V> {
-        Empty::new().into_any()
+/// It is trivial to convert stateless components to stateful components, so lets
+/// do so en masse. Note that the reverse is impossible without a helper.
+impl<V: View, C: Component> StatefulComponent<V> for C {
+    fn render(self, _: &mut V, cx: &mut ViewContext<V>) -> AnyElement<V> {
+        self.render(cx)
     }
 }
 
-impl StyleableComponent for () {
-    type Style = ();
-    type Output = ();
+/// Same as stylable, but generic over a view type
+pub trait StatefulStylable<V: View>: StatefulComponent<V> {
+    type Style: Clone;
 
-    fn with_style(self, _: Self::Style) -> Self::Output {
-        ()
-    }
+    fn stateful_with_style(self, style: Self::Style) -> Self;
 }
 
-pub trait StatefulStyleableComponent<V: View> {
+/// Same as SafeStylable, but generic over a view type
+pub trait StatefulSafeStylable<V: View> {
     type Style: Clone;
     type Output: StatefulComponent<V>;
 
     fn stateful_with_style(self, style: Self::Style) -> Self::Output;
 }
 
-impl<V: View, C: StyleableComponent> StatefulStyleableComponent<V> for C {
+/// Converting from stateless to stateful
+impl<V: View, C: SafeStylable> StatefulSafeStylable<V> for C {
     type Style = C::Style;
 
     type Output = C::Output;
@@ -86,31 +150,29 @@ impl<V: View, C: StyleableComponent> StatefulStyleableComponent<V> for C {
     }
 }
 
-pub trait StatefulComponent<V: View> {
-    fn render(self, v: &mut V, cx: &mut ViewContext<V>) -> AnyElement<V>;
-
-    fn stateful_element(self) -> StatefulAdapter<V, Self>
-    where
-        Self: Sized,
-    {
-        StatefulAdapter::new(self)
-    }
+// A helper for converting stateless components into stateful ones
+pub struct StatefulAdapter<C, V> {
+    component: C,
+    phantom: std::marker::PhantomData<V>,
+}
 
-    fn stateful_styleable(self) -> StatefulStylableAdapter<Self, V>
-    where
-        Self: Sized,
-    {
-        StatefulStylableAdapter::new(self)
+impl<C: Component, V: View> StatefulAdapter<C, V> {
+    pub fn new(component: C) -> Self {
+        Self {
+            component,
+            phantom: std::marker::PhantomData,
+        }
     }
 }
 
-impl<V: View, C: Component> StatefulComponent<V> for C {
-    fn render(self, v: &mut V, cx: &mut ViewContext<V>) -> AnyElement<V> {
-        self.render(v, cx)
+impl<C: Component, V: View> StatefulComponent<V> for StatefulAdapter<C, V> {
+    fn render(self, _: &mut V, cx: &mut ViewContext<V>) -> AnyElement<V> {
+        self.component.render(cx)
     }
 }
 
-// StylableComponent -> Component
+// A helper for converting stateful but style-less components into stylable ones
+// by using `()` as the style type
 pub struct StatefulStylableAdapter<C: StatefulComponent<V>, V: View> {
     component: C,
     phantom: std::marker::PhantomData<V>,
@@ -125,9 +187,7 @@ impl<C: StatefulComponent<V>, V: View> StatefulStylableAdapter<C, V> {
     }
 }
 
-impl<C: StatefulComponent<V>, V: View> StatefulStyleableComponent<V>
-    for StatefulStylableAdapter<C, V>
-{
+impl<C: StatefulComponent<V>, V: View> StatefulSafeStylable<V> for StatefulStylableAdapter<C, V> {
     type Style = ();
 
     type Output = C;
@@ -137,37 +197,37 @@ impl<C: StatefulComponent<V>, V: View> StatefulStyleableComponent<V>
     }
 }
 
-// Element -> GeneralComponent
-
-pub struct DynamicElementAdapter {
+/// A way of erasing the view generic from an element, useful
+/// for wrapping up an explicit element tree into stateless
+/// components
+pub struct StatelessElementAdapter {
     element: Box<dyn Any>,
 }
 
-impl DynamicElementAdapter {
+impl StatelessElementAdapter {
     pub fn new<V: View>(element: AnyElement<V>) -> Self {
-        DynamicElementAdapter {
+        StatelessElementAdapter {
             element: Box::new(element) as Box<dyn Any>,
         }
     }
 }
 
-impl Component for DynamicElementAdapter {
-    fn render<V: View>(self, _: &mut V, _: &mut ViewContext<V>) -> AnyElement<V> {
-        let element = self
+impl Component for StatelessElementAdapter {
+    fn render<V: View>(self, _: &mut ViewContext<V>) -> AnyElement<V> {
+        *self
             .element
             .downcast::<AnyElement<V>>()
-            .expect("Don't move elements out of their view :(");
-        *element
+            .expect("Don't move elements out of their view :(")
     }
 }
 
-// Element -> Component
-pub struct ElementAdapter<V: View> {
+// For converting elements into stateful components
+pub struct StatefulElementAdapter<V: View> {
     element: AnyElement<V>,
     _phantom: std::marker::PhantomData<V>,
 }
 
-impl<V: View> ElementAdapter<V> {
+impl<V: View> StatefulElementAdapter<V> {
     pub fn new(element: AnyElement<V>) -> Self {
         Self {
             element,
@@ -176,20 +236,35 @@ impl<V: View> ElementAdapter<V> {
     }
 }
 
-impl<V: View> StatefulComponent<V> for ElementAdapter<V> {
+impl<V: View> StatefulComponent<V> for StatefulElementAdapter<V> {
     fn render(self, _: &mut V, _: &mut ViewContext<V>) -> AnyElement<V> {
         self.element
     }
 }
 
-// Component -> Element
-pub struct StatefulAdapter<V: View, E> {
+/// A convenient shorthand for creating an empty component.
+impl Component for () {
+    fn render<V: View>(self, _: &mut ViewContext<V>) -> AnyElement<V> {
+        Empty::new().into_any()
+    }
+}
+
+impl Stylable for () {
+    type Style = ();
+
+    fn with_style(self, _: Self::Style) -> Self {
+        ()
+    }
+}
+
+// For converting components back into Elements
+pub struct ComponentAdapter<V: View, E> {
     component: Option<E>,
     element: Option<AnyElement<V>>,
     phantom: PhantomData<V>,
 }
 
-impl<E, V: View> StatefulAdapter<V, E> {
+impl<E, V: View> ComponentAdapter<V, E> {
     pub fn new(e: E) -> Self {
         Self {
             component: Some(e),
@@ -199,7 +274,7 @@ impl<E, V: View> StatefulAdapter<V, E> {
     }
 }
 
-impl<V: View, C: StatefulComponent<V> + 'static> Element<V> for StatefulAdapter<V, C> {
+impl<V: View, C: StatefulComponent<V> + 'static> Element<V> for ComponentAdapter<V, C> {
     type LayoutState = ();
 
     type PaintState = ();
@@ -262,6 +337,7 @@ impl<V: View, C: StatefulComponent<V> + 'static> Element<V> for StatefulAdapter<
     ) -> serde_json::Value {
         serde_json::json!({
             "type": "ComponentAdapter",
+            "component": std::any::type_name::<C>(),
             "child": self.element.as_ref().map(|el| el.debug(view, cx)),
         })
     }

crates/search/src/search.rs 🔗

@@ -2,15 +2,13 @@ use bitflags::bitflags;
 pub use buffer_search::BufferSearchBar;
 use gpui::{
     actions,
-    elements::{Component, StyleableComponent, TooltipStyle},
+    elements::{Component, SafeStylable, TooltipStyle},
     Action, AnyElement, AppContext, Element, View,
 };
 pub use mode::SearchMode;
 use project::search::SearchQuery;
 pub use project_search::{ProjectSearchBar, ProjectSearchView};
-use theme::components::{
-    action_button::ActionButton, svg::Svg, ComponentExt, ToggleIconButtonStyle,
-};
+use theme::components::{action_button::Button, svg::Svg, ComponentExt, ToggleIconButtonStyle};
 
 pub mod buffer_search;
 mod history;
@@ -91,7 +89,7 @@ impl SearchOptions {
         tooltip_style: TooltipStyle,
         button_style: ToggleIconButtonStyle,
     ) -> AnyElement<V> {
-        ActionButton::new_dynamic(self.to_toggle_action())
+        Button::dynamic_action(self.to_toggle_action())
             .with_tooltip(format!("Toggle {}", self.label()), tooltip_style)
             .with_contents(Svg::new(self.icon()))
             .toggleable(active)

crates/theme/src/components.rs 🔗

@@ -1,4 +1,4 @@
-use gpui::{elements::StyleableComponent, Action};
+use gpui::{elements::SafeStylable, Action};
 
 use crate::{Interactive, Toggleable};
 
@@ -6,44 +6,34 @@ use self::{action_button::ButtonStyle, disclosure::Disclosable, svg::SvgStyle, t
 
 pub type ToggleIconButtonStyle = Toggleable<Interactive<ButtonStyle<SvgStyle>>>;
 
-pub trait ComponentExt<C: StyleableComponent> {
+pub trait ComponentExt<C: SafeStylable> {
     fn toggleable(self, active: bool) -> Toggle<C, ()>;
-    fn disclosable(
-        self,
-        disclosed: Option<bool>,
-        action: Box<dyn Action>,
-        id: usize,
-    ) -> Disclosable<C, ()>;
+    fn disclosable(self, disclosed: Option<bool>, action: Box<dyn Action>) -> Disclosable<C, ()>;
 }
 
-impl<C: StyleableComponent> ComponentExt<C> for C {
+impl<C: SafeStylable> ComponentExt<C> for C {
     fn toggleable(self, active: bool) -> Toggle<C, ()> {
         Toggle::new(self, active)
     }
 
     /// Some(True) => disclosed => content is visible
     /// Some(false) => closed => content is hidden
-    /// None => No disclosure button, but reserve spacing
-    fn disclosable(
-        self,
-        disclosed: Option<bool>,
-        action: Box<dyn Action>,
-        id: usize,
-    ) -> Disclosable<C, ()> {
-        Disclosable::new(disclosed, self, action, id)
+    /// None => No disclosure button, but reserve disclosure spacing
+    fn disclosable(self, disclosed: Option<bool>, action: Box<dyn Action>) -> Disclosable<C, ()> {
+        Disclosable::new(disclosed, self, action)
     }
 }
 
 pub mod disclosure {
 
     use gpui::{
-        elements::{Component, Empty, Flex, ParentElement, StyleableComponent},
+        elements::{Component, Empty, Flex, ParentElement, SafeStylable},
         Action, Element,
     };
     use schemars::JsonSchema;
     use serde_derive::Deserialize;
 
-    use super::{action_button::ActionButton, svg::Svg, ComponentExt, ToggleIconButtonStyle};
+    use super::{action_button::Button, svg::Svg, ComponentExt, ToggleIconButtonStyle};
 
     #[derive(Clone, Default, Deserialize, JsonSchema)]
     pub struct DisclosureStyle<S> {
@@ -72,19 +62,24 @@ pub mod disclosure {
             disclosed: Option<bool>,
             content: C,
             action: Box<dyn Action>,
-            id: usize,
         ) -> Disclosable<C, ()> {
             Disclosable {
                 disclosed,
                 content,
                 action,
-                id,
+                id: 0,
                 style: (),
             }
         }
     }
 
-    impl<C: StyleableComponent> StyleableComponent for Disclosable<C, ()> {
+    impl<C> Disclosable<C, ()> {
+        pub fn with_id(self, id: usize) -> Disclosable<C, ()> {
+            Disclosable { id, ..self }
+        }
+    }
+
+    impl<C: SafeStylable> SafeStylable for Disclosable<C, ()> {
         type Style = DisclosureStyle<C::Style>;
 
         type Output = Disclosable<C, Self::Style>;
@@ -100,15 +95,11 @@ pub mod disclosure {
         }
     }
 
-    impl<C: StyleableComponent> Component for Disclosable<C, DisclosureStyle<C::Style>> {
-        fn render<V: gpui::View>(
-            self,
-            v: &mut V,
-            cx: &mut gpui::ViewContext<V>,
-        ) -> gpui::AnyElement<V> {
+    impl<C: SafeStylable> Component for Disclosable<C, DisclosureStyle<C::Style>> {
+        fn render<V: gpui::View>(self, cx: &mut gpui::ViewContext<V>) -> gpui::AnyElement<V> {
             Flex::row()
                 .with_child(if let Some(disclosed) = self.disclosed {
-                    ActionButton::new_dynamic(self.action)
+                    Button::dynamic_action(self.action)
                         .with_id(self.id)
                         .with_contents(Svg::new(if disclosed {
                             "icons/file_icons/chevron_down.svg"
@@ -131,7 +122,7 @@ pub mod disclosure {
                 .with_child(
                     self.content
                         .with_style(self.style.content)
-                        .render(v, cx)
+                        .render(cx)
                         .flex(1., true),
                 )
                 .align_children_center()
@@ -141,7 +132,7 @@ pub mod disclosure {
 }
 
 pub mod toggle {
-    use gpui::elements::{Component, StyleableComponent};
+    use gpui::elements::{Component, SafeStylable};
 
     use crate::Toggleable;
 
@@ -151,7 +142,7 @@ pub mod toggle {
         component: C,
     }
 
-    impl<C: StyleableComponent> Toggle<C, ()> {
+    impl<C: SafeStylable> Toggle<C, ()> {
         pub fn new(component: C, active: bool) -> Self {
             Toggle {
                 active,
@@ -161,7 +152,7 @@ pub mod toggle {
         }
     }
 
-    impl<C: StyleableComponent> StyleableComponent for Toggle<C, ()> {
+    impl<C: SafeStylable> SafeStylable for Toggle<C, ()> {
         type Style = Toggleable<C::Style>;
 
         type Output = Toggle<C, Self::Style>;
@@ -175,15 +166,11 @@ pub mod toggle {
         }
     }
 
-    impl<C: StyleableComponent> Component for Toggle<C, Toggleable<C::Style>> {
-        fn render<V: gpui::View>(
-            self,
-            v: &mut V,
-            cx: &mut gpui::ViewContext<V>,
-        ) -> gpui::AnyElement<V> {
+    impl<C: SafeStylable> Component for Toggle<C, Toggleable<C::Style>> {
+        fn render<V: gpui::View>(self, cx: &mut gpui::ViewContext<V>) -> gpui::AnyElement<V> {
             self.component
                 .with_style(self.style.in_state(self.active).clone())
-                .render(v, cx)
+                .render(cx)
         }
     }
 }
@@ -192,9 +179,7 @@ pub mod action_button {
     use std::borrow::Cow;
 
     use gpui::{
-        elements::{
-            Component, ContainerStyle, MouseEventHandler, StyleableComponent, TooltipStyle,
-        },
+        elements::{Component, ContainerStyle, MouseEventHandler, SafeStylable, TooltipStyle},
         platform::{CursorStyle, MouseButton},
         Action, Element, TypeTag, View,
     };
@@ -216,7 +201,7 @@ pub mod action_button {
         contents: C,
     }
 
-    pub struct ActionButton<C, S> {
+    pub struct Button<C, S> {
         action: Box<dyn Action>,
         tooltip: Option<(Cow<'static, str>, TooltipStyle)>,
         tag: TypeTag,
@@ -225,8 +210,8 @@ pub mod action_button {
         style: Interactive<S>,
     }
 
-    impl ActionButton<(), ()> {
-        pub fn new_dynamic(action: Box<dyn Action>) -> Self {
+    impl Button<(), ()> {
+        pub fn dynamic_action(action: Box<dyn Action>) -> Self {
             Self {
                 contents: (),
                 tag: action.type_tag(),
@@ -237,8 +222,8 @@ pub mod action_button {
             }
         }
 
-        pub fn new<A: Action + Clone>(action: A) -> Self {
-            Self::new_dynamic(Box::new(action))
+        pub fn action<A: Action + Clone>(action: A) -> Self {
+            Self::dynamic_action(Box::new(action))
         }
 
         pub fn with_tooltip(
@@ -255,8 +240,8 @@ pub mod action_button {
             self
         }
 
-        pub fn with_contents<C: StyleableComponent>(self, contents: C) -> ActionButton<C, ()> {
-            ActionButton {
+        pub fn with_contents<C: SafeStylable>(self, contents: C) -> Button<C, ()> {
+            Button {
                 action: self.action,
                 tag: self.tag,
                 style: self.style,
@@ -267,12 +252,12 @@ pub mod action_button {
         }
     }
 
-    impl<C: StyleableComponent> StyleableComponent for ActionButton<C, ()> {
+    impl<C: SafeStylable> SafeStylable for Button<C, ()> {
         type Style = Interactive<ButtonStyle<C::Style>>;
-        type Output = ActionButton<C, ButtonStyle<C::Style>>;
+        type Output = Button<C, ButtonStyle<C::Style>>;
 
         fn with_style(self, style: Self::Style) -> Self::Output {
-            ActionButton {
+            Button {
                 action: self.action,
                 tag: self.tag,
                 contents: self.contents,
@@ -283,14 +268,14 @@ pub mod action_button {
         }
     }
 
-    impl<C: StyleableComponent> Component for ActionButton<C, ButtonStyle<C::Style>> {
-        fn render<V: View>(self, v: &mut V, cx: &mut gpui::ViewContext<V>) -> gpui::AnyElement<V> {
+    impl<C: SafeStylable> Component for Button<C, ButtonStyle<C::Style>> {
+        fn render<V: View>(self, cx: &mut gpui::ViewContext<V>) -> gpui::AnyElement<V> {
             let mut button = MouseEventHandler::new_dynamic(self.tag, self.id, cx, |state, cx| {
                 let style = self.style.style_for(state);
                 let mut contents = self
                     .contents
                     .with_style(style.contents.to_owned())
-                    .render(v, cx)
+                    .render(cx)
                     .contained()
                     .with_style(style.container)
                     .constrained();
@@ -335,7 +320,7 @@ pub mod svg {
     use std::borrow::Cow;
 
     use gpui::{
-        elements::{Component, Empty, StyleableComponent},
+        elements::{Component, Empty, SafeStylable},
         Element,
     };
     use schemars::JsonSchema;
@@ -417,7 +402,7 @@ pub mod svg {
         }
     }
 
-    impl StyleableComponent for Svg<()> {
+    impl SafeStylable for Svg<()> {
         type Style = SvgStyle;
 
         type Output = Svg<SvgStyle>;
@@ -431,11 +416,7 @@ pub mod svg {
     }
 
     impl Component for Svg<SvgStyle> {
-        fn render<V: gpui::View>(
-            self,
-            _: &mut V,
-            _: &mut gpui::ViewContext<V>,
-        ) -> gpui::AnyElement<V> {
+        fn render<V: gpui::View>(self, _: &mut gpui::ViewContext<V>) -> gpui::AnyElement<V> {
             if let Some(path) = self.path {
                 gpui::elements::Svg::new(path)
                     .with_color(self.style.color)
@@ -455,7 +436,7 @@ pub mod label {
     use std::borrow::Cow;
 
     use gpui::{
-        elements::{Component, LabelStyle, StyleableComponent},
+        elements::{Component, LabelStyle, SafeStylable},
         Element,
     };
 
@@ -473,7 +454,7 @@ pub mod label {
         }
     }
 
-    impl StyleableComponent for Label<()> {
+    impl SafeStylable for Label<()> {
         type Style = LabelStyle;
 
         type Output = Label<LabelStyle>;
@@ -487,11 +468,7 @@ pub mod label {
     }
 
     impl Component for Label<LabelStyle> {
-        fn render<V: gpui::View>(
-            self,
-            _: &mut V,
-            _: &mut gpui::ViewContext<V>,
-        ) -> gpui::AnyElement<V> {
+        fn render<V: gpui::View>(self, _: &mut gpui::ViewContext<V>) -> gpui::AnyElement<V> {
             gpui::elements::Label::new(self.text, self.style).into_any()
         }
     }