Add component traits to GPUI (#2850)

Mikayla Maki created

Release Notes:

- N/A

Change summary

crates/gpui/examples/components.rs    | 118 ++--------------------------
crates/gpui/src/elements.rs           |   7 
crates/gpui/src/elements/component.rs |  87 +++++++++++++++++++++
3 files changed, 101 insertions(+), 111 deletions(-)

Detailed changes

crates/gpui/examples/components.rs 🔗

@@ -1,9 +1,8 @@
 use button_component::Button;
 
-use component::AdaptComponent;
 use gpui::{
     color::Color,
-    elements::{ContainerStyle, Flex, Label, ParentElement},
+    elements::{Component, ContainerStyle, Flex, Label, ParentElement},
     fonts::{self, TextStyle},
     platform::WindowOptions,
     AnyElement, App, Element, Entity, View, ViewContext,
@@ -14,6 +13,8 @@ use simplelog::SimpleLogger;
 use theme::Toggleable;
 use toggleable_button::ToggleableButton;
 
+// cargo run -p gpui --example components
+
 fn main() {
     SimpleLogger::init(LevelFilter::Info, Default::default()).expect("could not initialize logger");
 
@@ -113,12 +114,12 @@ mod theme {
 // Component creation:
 mod toggleable_button {
     use gpui::{
-        elements::{ContainerStyle, LabelStyle},
+        elements::{Component, ContainerStyle, LabelStyle},
         scene::MouseClick,
         EventContext, View,
     };
 
-    use crate::{button_component::Button, component::Component, theme::Toggleable};
+    use crate::{button_component::Button, theme::Toggleable};
 
     pub struct ToggleableButton<V: View> {
         active: bool,
@@ -155,14 +156,8 @@ mod toggleable_button {
         }
     }
 
-    impl<V: View> Component for ToggleableButton<V> {
-        type View = V;
-
-        fn render(
-            self,
-            v: &mut Self::View,
-            cx: &mut gpui::ViewContext<Self::View>,
-        ) -> gpui::AnyElement<V> {
+    impl<V: View> Component<V> for ToggleableButton<V> {
+        fn render(self, v: &mut V, cx: &mut gpui::ViewContext<V>) -> gpui::AnyElement<V> {
             let button = if let Some(style) = self.style {
                 self.button.with_style(*style.style_for(self.active))
             } else {
@@ -176,14 +171,12 @@ mod toggleable_button {
 mod button_component {
 
     use gpui::{
-        elements::{ContainerStyle, Label, LabelStyle, MouseEventHandler},
+        elements::{Component, ContainerStyle, Label, LabelStyle, MouseEventHandler},
         platform::MouseButton,
         scene::MouseClick,
         AnyElement, Element, EventContext, TypeTag, View, ViewContext,
     };
 
-    use crate::component::Component;
-
     type ClickHandler<V> = Box<dyn Fn(MouseClick, &mut V, &mut EventContext<V>)>;
 
     pub struct Button<V: View> {
@@ -219,10 +212,8 @@ mod button_component {
         }
     }
 
-    impl<V: View> Component for Button<V> {
-        type View = V;
-
-        fn render(self, _: &mut Self::View, cx: &mut ViewContext<V>) -> AnyElement<Self::View> {
+    impl<V: View> Component<V> for Button<V> {
+        fn render(self, _: &mut V, cx: &mut ViewContext<V>) -> AnyElement<V> {
             let click_handler = self.click_handler;
 
             let result = MouseEventHandler::new_dynamic(self.tag, 0, cx, |_, _| {
@@ -244,92 +235,3 @@ mod button_component {
         }
     }
 }
-
-mod component {
-
-    use gpui::{AnyElement, Element, View, ViewContext};
-    use pathfinder_geometry::vector::Vector2F;
-
-    // Public API:
-    pub trait Component {
-        type View: View;
-
-        fn render(
-            self,
-            v: &mut Self::View,
-            cx: &mut ViewContext<Self::View>,
-        ) -> AnyElement<Self::View>;
-    }
-
-    pub struct ComponentAdapter<E> {
-        component: Option<E>,
-    }
-
-    impl<E> ComponentAdapter<E> {
-        pub fn new(e: E) -> Self {
-            Self { component: Some(e) }
-        }
-    }
-
-    pub trait AdaptComponent<C: Component>: Sized {
-        fn into_element(self) -> ComponentAdapter<Self> {
-            ComponentAdapter::new(self)
-        }
-    }
-
-    impl<C: Component> AdaptComponent<C> for C {}
-
-    impl<C: Component + 'static> Element<C::View> for ComponentAdapter<C> {
-        type LayoutState = AnyElement<C::View>;
-
-        type PaintState = ();
-
-        fn layout(
-            &mut self,
-            constraint: gpui::SizeConstraint,
-            view: &mut C::View,
-            cx: &mut gpui::LayoutContext<C::View>,
-        ) -> (Vector2F, Self::LayoutState) {
-            let component = self.component.take().unwrap();
-            let mut element = component.render(view, cx.view_context());
-            let constraint = element.layout(constraint, view, cx);
-            (constraint, element)
-        }
-
-        fn paint(
-            &mut self,
-            scene: &mut gpui::SceneBuilder,
-            bounds: gpui::geometry::rect::RectF,
-            visible_bounds: gpui::geometry::rect::RectF,
-            layout: &mut Self::LayoutState,
-            view: &mut C::View,
-            cx: &mut gpui::PaintContext<C::View>,
-        ) -> Self::PaintState {
-            layout.paint(scene, bounds.origin(), visible_bounds, view, cx)
-        }
-
-        fn rect_for_text_range(
-            &self,
-            _: std::ops::Range<usize>,
-            _: gpui::geometry::rect::RectF,
-            _: gpui::geometry::rect::RectF,
-            _: &Self::LayoutState,
-            _: &Self::PaintState,
-            _: &C::View,
-            _: &ViewContext<C::View>,
-        ) -> Option<gpui::geometry::rect::RectF> {
-            todo!()
-        }
-
-        fn debug(
-            &self,
-            _: gpui::geometry::rect::RectF,
-            _: &Self::LayoutState,
-            _: &Self::PaintState,
-            _: &C::View,
-            _: &ViewContext<C::View>,
-        ) -> serde_json::Value {
-            todo!()
-        }
-    }
-}

crates/gpui/src/elements.rs 🔗

@@ -1,6 +1,7 @@
 mod align;
 mod canvas;
 mod clipped;
+mod component;
 mod constrained_box;
 mod container;
 mod empty;
@@ -21,9 +22,9 @@ mod tooltip;
 mod uniform_list;
 
 pub use self::{
-    align::*, canvas::*, constrained_box::*, container::*, empty::*, flex::*, hook::*, image::*,
-    keystroke_label::*, label::*, list::*, mouse_event_handler::*, overlay::*, resizable::*,
-    stack::*, svg::*, text::*, tooltip::*, uniform_list::*,
+    align::*, canvas::*, component::*, constrained_box::*, container::*, empty::*, flex::*,
+    hook::*, image::*, keystroke_label::*, label::*, list::*, mouse_event_handler::*, overlay::*,
+    resizable::*, stack::*, svg::*, text::*, tooltip::*, uniform_list::*,
 };
 pub use crate::window::ChildView;
 

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

@@ -0,0 +1,87 @@
+use std::marker::PhantomData;
+
+use pathfinder_geometry::{rect::RectF, vector::Vector2F};
+
+use crate::{
+    AnyElement, Element, LayoutContext, PaintContext, SceneBuilder, SizeConstraint, View,
+    ViewContext,
+};
+
+pub trait Component<V: View> {
+    fn render(self, v: &mut V, cx: &mut ViewContext<V>) -> AnyElement<V>;
+
+    fn into_element(self) -> ComponentAdapter<V, Self>
+    where
+        Self: Sized,
+    {
+        ComponentAdapter::new(self)
+    }
+}
+
+pub struct ComponentAdapter<V, E> {
+    component: Option<E>,
+    phantom: PhantomData<V>,
+}
+
+impl<E, V> ComponentAdapter<V, E> {
+    pub fn new(e: E) -> Self {
+        Self {
+            component: Some(e),
+            phantom: PhantomData,
+        }
+    }
+}
+
+impl<V: View, C: Component<V> + 'static> Element<V> for ComponentAdapter<V, C> {
+    type LayoutState = AnyElement<V>;
+
+    type PaintState = ();
+
+    fn layout(
+        &mut self,
+        constraint: SizeConstraint,
+        view: &mut V,
+        cx: &mut LayoutContext<V>,
+    ) -> (Vector2F, Self::LayoutState) {
+        let component = self.component.take().unwrap();
+        let mut element = component.render(view, cx.view_context());
+        let constraint = element.layout(constraint, view, cx);
+        (constraint, element)
+    }
+
+    fn paint(
+        &mut self,
+        scene: &mut SceneBuilder,
+        bounds: RectF,
+        visible_bounds: RectF,
+        layout: &mut Self::LayoutState,
+        view: &mut V,
+        cx: &mut PaintContext<V>,
+    ) -> Self::PaintState {
+        layout.paint(scene, bounds.origin(), visible_bounds, view, cx)
+    }
+
+    fn rect_for_text_range(
+        &self,
+        range_utf16: std::ops::Range<usize>,
+        _: RectF,
+        _: RectF,
+        element: &Self::LayoutState,
+        _: &Self::PaintState,
+        view: &V,
+        cx: &ViewContext<V>,
+    ) -> Option<RectF> {
+        element.rect_for_text_range(range_utf16, view, cx)
+    }
+
+    fn debug(
+        &self,
+        _: RectF,
+        element: &Self::LayoutState,
+        _: &Self::PaintState,
+        view: &V,
+        cx: &ViewContext<V>,
+    ) -> serde_json::Value {
+        element.debug(view, cx)
+    }
+}