diff --git a/crates/collab_ui2/src/collab_panel.rs b/crates/collab_ui2/src/collab_panel.rs index 20e77d7023..22f6dc9d88 100644 --- a/crates/collab_ui2/src/collab_panel.rs +++ b/crates/collab_ui2/src/collab_panel.rs @@ -160,7 +160,7 @@ use std::sync::Arc; use db::kvp::KEY_VALUE_STORE; use gpui::{ actions, div, serde_json, AppContext, AsyncWindowContext, Div, EventEmitter, FocusHandle, - Focusable, FocusableView, InteractiveComponent, ParentComponent, Render, View, ViewContext, + Focusable, FocusableView, InteractiveElement, ParentElement, Render, View, ViewContext, VisualContext, WeakView, }; use project::Fs; diff --git a/crates/collab_ui2/src/collab_titlebar_item.rs b/crates/collab_ui2/src/collab_titlebar_item.rs index ed010cc500..570ff9f45e 100644 --- a/crates/collab_ui2/src/collab_titlebar_item.rs +++ b/crates/collab_ui2/src/collab_titlebar_item.rs @@ -31,9 +31,9 @@ use std::sync::Arc; use call::ActiveCall; use client::{Client, UserStore}; use gpui::{ - div, px, rems, AppContext, Component, Div, InteractiveComponent, Model, ParentComponent, - Render, Stateful, StatefulInteractiveComponent, Styled, Subscription, ViewContext, - VisualContext, WeakView, WindowBounds, + div, px, rems, AppContext, Component, Div, InteractiveElement, Model, ParentElement, Render, + Stateful, StatefulInteractiveElement, Styled, Subscription, ViewContext, VisualContext, + WeakView, WindowBounds, }; use project::Project; use theme::ActiveTheme; diff --git a/crates/command_palette2/src/command_palette.rs b/crates/command_palette2/src/command_palette.rs index 9463cab68c..7e4994638e 100644 --- a/crates/command_palette2/src/command_palette.rs +++ b/crates/command_palette2/src/command_palette.rs @@ -2,7 +2,7 @@ use collections::{CommandPaletteFilter, HashMap}; use fuzzy::{StringMatch, StringMatchCandidate}; use gpui::{ actions, div, prelude::*, Action, AppContext, Component, Dismiss, Div, FocusHandle, Keystroke, - ManagedView, ParentComponent, Render, Styled, View, ViewContext, VisualContext, WeakView, + ManagedView, ParentElement, Render, Styled, View, ViewContext, VisualContext, WeakView, }; use picker::{Picker, PickerDelegate}; use std::{ diff --git a/crates/editor2/src/editor.rs b/crates/editor2/src/editor.rs index b1dc76852d..214c46551f 100644 --- a/crates/editor2/src/editor.rs +++ b/crates/editor2/src/editor.rs @@ -42,7 +42,7 @@ use gpui::{ actions, div, point, prelude::*, px, relative, rems, size, uniform_list, Action, AnyElement, AppContext, AsyncWindowContext, BackgroundExecutor, Bounds, ClipboardItem, Component, Context, EventEmitter, FocusHandle, FocusableView, FontFeatures, FontStyle, FontWeight, HighlightStyle, - Hsla, InputHandler, KeyContext, Model, MouseButton, ParentComponent, Pixels, Render, Styled, + Hsla, InputHandler, KeyContext, Model, MouseButton, ParentElement, Pixels, Render, Styled, Subscription, Task, TextStyle, UniformListScrollHandle, View, ViewContext, VisualContext, WeakView, WindowContext, }; @@ -1595,7 +1595,7 @@ impl CodeActionsMenu { .max_by_key(|(_, action)| action.lsp_action.title.chars().count()) .map(|(ix, _)| ix), ) - .render(); + .render_once(); if self.deployed_from_indicator { *cursor_position.column_mut() = 0; @@ -4365,7 +4365,7 @@ impl Editor { cx, ); }) - .render(), + .into_any(), ) } else { None @@ -4401,7 +4401,7 @@ impl Editor { editor.fold_at(&FoldAt { buffer_row }, cx); } }) - .render() + .into_any() }) }) .flatten() @@ -7792,7 +7792,7 @@ impl Editor { cx.editor_style.diagnostic_style.clone(), }, ))) - .render() + .render_once() } }), disposition: BlockDisposition::Below, @@ -9994,7 +9994,7 @@ pub fn diagnostic_block_renderer(diagnostic: Diagnostic, is_valid: bool) -> Rend cx.write_to_clipboard(ClipboardItem::new(message.clone())); }) .tooltip(|_, cx| Tooltip::text("Copy diagnostic message", cx)) - .render() + .render_once() }) } diff --git a/crates/editor2/src/editor_tests.rs b/crates/editor2/src/editor_tests.rs index bd69e7acdf..f23618aa21 100644 --- a/crates/editor2/src/editor_tests.rs +++ b/crates/editor2/src/editor_tests.rs @@ -3048,7 +3048,7 @@ fn test_move_line_up_down_with_blocks(cx: &mut TestAppContext) { position: snapshot.anchor_after(Point::new(2, 0)), disposition: BlockDisposition::Below, height: 1, - render: Arc::new(|_| div().render()), + render: Arc::new(|_| div().render_once()), }], Some(Autoscroll::fit()), cx, diff --git a/crates/editor2/src/element.rs b/crates/editor2/src/element.rs index 2de9182901..0ee710da47 100644 --- a/crates/editor2/src/element.rs +++ b/crates/editor2/src/element.rs @@ -20,9 +20,9 @@ use collections::{BTreeMap, HashMap}; use gpui::{ div, point, px, relative, size, transparent_black, Action, AnyElement, AvailableSpace, BorrowWindow, Bounds, Component, ContentMask, Corners, DispatchPhase, Edges, Element, - ElementId, ElementInputHandler, Entity, EntityId, Hsla, InteractiveComponent, LineLayout, - MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, ParentComponent, Pixels, - ScrollWheelEvent, ShapedLine, SharedString, Size, StatefulInteractiveComponent, Style, Styled, + ElementId, ElementInputHandler, Entity, EntityId, Hsla, InteractiveElement, LineLayout, + MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, ParentElement, Pixels, + ScrollWheelEvent, ShapedLine, SharedString, Size, StatefulInteractiveElement, Style, Styled, TextRun, TextStyle, View, ViewContext, WindowContext, WrappedLine, }; use itertools::Itertools; diff --git a/crates/editor2/src/items.rs b/crates/editor2/src/items.rs index cf2bf5b6dc..ef0f48193c 100644 --- a/crates/editor2/src/items.rs +++ b/crates/editor2/src/items.rs @@ -9,7 +9,7 @@ use collections::HashSet; use futures::future::try_join_all; use gpui::{ div, point, AnyElement, AppContext, AsyncAppContext, Entity, EntityId, EventEmitter, - FocusHandle, Model, ParentComponent, Pixels, SharedString, Styled, Subscription, Task, View, + FocusHandle, Model, ParentElement, Pixels, SharedString, Styled, Subscription, Task, View, ViewContext, VisualContext, WeakView, }; use language::{ diff --git a/crates/file_finder2/src/file_finder.rs b/crates/file_finder2/src/file_finder.rs index 0fee5102e6..3488b6916c 100644 --- a/crates/file_finder2/src/file_finder.rs +++ b/crates/file_finder2/src/file_finder.rs @@ -2,8 +2,8 @@ use collections::HashMap; use editor::{scroll::autoscroll::Autoscroll, Bias, Editor}; use fuzzy::{CharBag, PathMatch, PathMatchCandidate}; use gpui::{ - actions, div, AppContext, Component, Dismiss, Div, FocusHandle, InteractiveComponent, - ManagedView, Model, ParentComponent, Render, Styled, Task, View, ViewContext, VisualContext, + actions, div, AppContext, Component, Dismiss, Div, FocusHandle, InteractiveElement, + ManagedView, Model, ParentElement, Render, Styled, Task, View, ViewContext, VisualContext, WeakView, }; use picker::{Picker, PickerDelegate}; diff --git a/crates/go_to_line2/src/go_to_line.rs b/crates/go_to_line2/src/go_to_line.rs index 565afb5e93..26641bfa09 100644 --- a/crates/go_to_line2/src/go_to_line.rs +++ b/crates/go_to_line2/src/go_to_line.rs @@ -1,6 +1,6 @@ use editor::{display_map::ToDisplayPoint, scroll::autoscroll::Autoscroll, Editor}; use gpui::{ - actions, div, prelude::*, AppContext, Dismiss, Div, FocusHandle, ManagedView, ParentComponent, + actions, div, prelude::*, AppContext, Dismiss, Div, FocusHandle, ManagedView, ParentElement, Render, SharedString, Styled, Subscription, View, ViewContext, VisualContext, WindowContext, }; use text::{Bias, Point}; diff --git a/crates/gpui2/src/app.rs b/crates/gpui2/src/app.rs index 397b770c2b..0a1e571872 100644 --- a/crates/gpui2/src/app.rs +++ b/crates/gpui2/src/app.rs @@ -424,7 +424,7 @@ impl AppContext { /// Opens a new window with the given option and the root view returned by the given function. /// The function is invoked with a `WindowContext`, which can be used to interact with window-specific /// functionality. - pub fn open_window( + pub fn open_window>( &mut self, options: crate::WindowOptions, build_root_view: impl FnOnce(&mut WindowContext) -> View, diff --git a/crates/gpui2/src/app/async_context.rs b/crates/gpui2/src/app/async_context.rs index 83b3ccebe7..afb6050037 100644 --- a/crates/gpui2/src/app/async_context.rs +++ b/crates/gpui2/src/app/async_context.rs @@ -115,7 +115,7 @@ impl AsyncAppContext { build_root_view: impl FnOnce(&mut WindowContext) -> View, ) -> Result> where - V: Render, + V: 'static + Render, { let app = self .app @@ -286,7 +286,7 @@ impl VisualContext for AsyncWindowContext { build_view_state: impl FnOnce(&mut ViewContext<'_, V>) -> V, ) -> Self::Result> where - V: 'static + Render, + V: 'static + Render, { self.window .update(self, |_, cx| cx.build_view(build_view_state)) @@ -306,7 +306,7 @@ impl VisualContext for AsyncWindowContext { build_view: impl FnOnce(&mut ViewContext<'_, V>) -> V, ) -> Self::Result> where - V: Render, + V: 'static + Render, { self.window .update(self, |_, cx| cx.replace_root_view(build_view)) diff --git a/crates/gpui2/src/app/test_context.rs b/crates/gpui2/src/app/test_context.rs index 940492573f..06893a1772 100644 --- a/crates/gpui2/src/app/test_context.rs +++ b/crates/gpui2/src/app/test_context.rs @@ -126,7 +126,7 @@ impl TestAppContext { pub fn add_window(&mut self, build_window: F) -> WindowHandle where F: FnOnce(&mut ViewContext) -> V, - V: Render, + V: 'static + Render, { let mut cx = self.app.borrow_mut(); cx.open_window(WindowOptions::default(), |cx| cx.build_view(build_window)) @@ -143,7 +143,7 @@ impl TestAppContext { pub fn add_window_view(&mut self, build_window: F) -> (View, &mut VisualTestContext) where F: FnOnce(&mut ViewContext) -> V, - V: Render, + V: 'static + Render, { let mut cx = self.app.borrow_mut(); let window = cx.open_window(WindowOptions::default(), |cx| cx.build_view(build_window)); @@ -543,7 +543,7 @@ impl<'a> VisualContext for VisualTestContext<'a> { build_view: impl FnOnce(&mut ViewContext<'_, V>) -> V, ) -> Self::Result> where - V: 'static + Render, + V: 'static + Render, { self.window .update(self.cx, |_, cx| cx.build_view(build_view)) @@ -565,7 +565,7 @@ impl<'a> VisualContext for VisualTestContext<'a> { build_view: impl FnOnce(&mut ViewContext<'_, V>) -> V, ) -> Self::Result> where - V: Render, + V: 'static + Render, { self.window .update(self.cx, |_, cx| cx.replace_root_view(build_view)) @@ -582,7 +582,7 @@ impl<'a> VisualContext for VisualTestContext<'a> { } impl AnyWindowHandle { - pub fn build_view( + pub fn build_view + 'static>( &self, cx: &mut TestAppContext, build_view: impl FnOnce(&mut ViewContext<'_, V>) -> V, @@ -593,7 +593,7 @@ impl AnyWindowHandle { pub struct EmptyView {} -impl Render for EmptyView { +impl Render for EmptyView { type Element = Div; fn render(&mut self, _cx: &mut crate::ViewContext) -> Self::Element { diff --git a/crates/gpui2/src/element.rs b/crates/gpui2/src/element.rs index f296e9214c..c60c902e25 100644 --- a/crates/gpui2/src/element.rs +++ b/crates/gpui2/src/element.rs @@ -3,9 +3,53 @@ use crate::{ }; use derive_more::{Deref, DerefMut}; pub(crate) use smallvec::SmallVec; -use std::{any::Any, fmt::Debug}; +use std::{any::Any, fmt::Debug, marker::PhantomData}; -pub trait Element: 'static + Sized { +pub trait Render: 'static + Sized { + type Element: Element + 'static; + + fn render(&mut self, cx: &mut ViewContext) -> Self::Element; +} + +pub trait RenderOnce: Sized { + type Element: Element + 'static; + + fn render_once(self) -> Self::Element; + + fn render_into_any(self) -> AnyElement { + self.render_once().into_any() + } + + fn map(self, f: impl FnOnce(Self) -> U) -> U + where + Self: Sized, + U: RenderOnce, + { + f(self) + } + + fn when(self, condition: bool, then: impl FnOnce(Self) -> Self) -> Self + where + Self: Sized, + { + self.map(|this| if condition { then(this) } else { this }) + } + + fn when_some(self, option: Option, then: impl FnOnce(Self, T) -> Self) -> Self + where + Self: Sized, + { + self.map(|this| { + if let Some(value) = option { + then(this, value) + } else { + this + } + }) + } +} + +pub trait Element: 'static + RenderOnce { type State: 'static; fn element_id(&self) -> Option; @@ -59,26 +103,105 @@ pub trait Element: 'static + Sized { } } +pub trait Component: 'static { + type Rendered: RenderOnce; + + fn render(self, view: &mut V, cx: &mut ViewContext) -> Self::Rendered; +} + +pub struct CompositeElement { + component: Option, + view_type: PhantomData, +} + +pub struct CompositeElementState> { + rendered_element: Option<>::Element>, + rendered_element_state: <>::Element as Element>::State, +} + +impl CompositeElement { + pub fn new(component: C) -> Self { + CompositeElement { + component: Some(component), + view_type: PhantomData, + } + } +} + +impl> Element for CompositeElement { + type State = CompositeElementState; + + fn element_id(&self) -> Option { + None + } + + fn layout( + &mut self, + view: &mut V, + state: Option, + cx: &mut ViewContext, + ) -> (LayoutId, Self::State) { + let mut element = self + .component + .take() + .unwrap() + .render(view, cx) + .render_once(); + let (layout_id, state) = element.layout(view, state.map(|s| s.rendered_element_state), cx); + let state = CompositeElementState { + rendered_element: Some(element), + rendered_element_state: state, + }; + (layout_id, state) + } + + fn paint( + self, + bounds: Bounds, + view: &mut V, + state: &mut Self::State, + cx: &mut ViewContext, + ) { + state.rendered_element.take().unwrap().paint( + bounds, + view, + &mut state.rendered_element_state, + cx, + ); + } +} + +impl> RenderOnce for CompositeElement { + type Element = Self; + + fn render_once(self) -> Self::Element { + self + } +} + #[derive(Deref, DerefMut, Default, Clone, Debug, Eq, PartialEq, Hash)] pub struct GlobalElementId(SmallVec<[ElementId; 32]>); -pub trait ParentComponent { +pub trait ParentElement { fn children_mut(&mut self) -> &mut SmallVec<[AnyElement; 2]>; - fn child(mut self, child: impl Element) -> Self + fn child(mut self, child: impl RenderOnce) -> Self where Self: Sized, { - self.children_mut().push(child.into_any()); + self.children_mut().push(child.render_once().into_any()); self } - fn children(mut self, children: impl IntoIterator>) -> Self + fn children(mut self, children: impl IntoIterator>) -> Self where Self: Sized, { - self.children_mut() - .extend(children.into_iter().map(Element::into_any)); + self.children_mut().extend( + children + .into_iter() + .map(|child| child.render_once().into_any()), + ); self } } @@ -301,7 +424,7 @@ where pub struct AnyElement(Box>); -impl AnyElement { +impl AnyElement { pub fn new(element: E) -> Self where V: 'static, @@ -343,6 +466,11 @@ impl AnyElement { ) { self.0.draw(origin, available_space, view_state, cx) } + + /// Converts this `AnyElement` into a trait object that can be stored and manipulated. + pub fn into_any(self) -> AnyElement { + AnyElement::new(self) + } } impl Element for AnyElement { @@ -373,105 +501,18 @@ impl Element for AnyElement { } } -pub trait Component { - fn render(self) -> AnyElement; +impl RenderOnce for AnyElement { + type Element = Self; - fn map(self, f: impl FnOnce(Self) -> U) -> U - where - Self: Sized, - U: Component, - { - f(self) - } - - fn when(self, condition: bool, then: impl FnOnce(Self) -> Self) -> Self - where - Self: Sized, - { - self.map(|this| if condition { then(this) } else { this }) - } - - fn when_some(self, option: Option, then: impl FnOnce(Self, T) -> Self) -> Self - where - Self: Sized, - { - self.map(|this| { - if let Some(value) = option { - then(this, value) - } else { - this - } - }) - } -} - -impl Component for AnyElement { - fn render(self) -> AnyElement { + fn render_once(self) -> Self::Element { self } } -impl Element for Option -where - V: 'static, - E: 'static + Component, - F: FnOnce(&mut V, &mut ViewContext<'_, V>) -> E + 'static, -{ - type State = Option>; - - fn element_id(&self) -> Option { - None - } - - fn layout( - &mut self, - view_state: &mut V, - _: Option, - cx: &mut ViewContext, - ) -> (LayoutId, Self::State) { - let render = self.take().unwrap(); - let mut rendered_element = (render)(view_state, cx).render(); - let layout_id = rendered_element.layout(view_state, cx); - (layout_id, Some(rendered_element)) - } - - fn paint( - self, - _bounds: Bounds, - view_state: &mut V, - rendered_element: &mut Self::State, - cx: &mut ViewContext, - ) { - rendered_element.take().unwrap().paint(view_state, cx); - } -} - -impl Component for Option -where - V: 'static, - E: 'static + Component, - F: FnOnce(&mut V, &mut ViewContext<'_, V>) -> E + 'static, -{ - fn render(self) -> AnyElement { - AnyElement::new(self) - } -} - -impl Component for F -where - V: 'static, - E: 'static + Component, - F: FnOnce(&mut V, &mut ViewContext<'_, V>) -> E + 'static, -{ - fn render(self) -> AnyElement { - AnyElement::new(Some(self)) - } -} - -// impl Element for F +// impl Element for Option // where // V: 'static, -// E: 'static + Component, +// E: Element, // F: FnOnce(&mut V, &mut ViewContext<'_, V>) -> E + 'static, // { // type State = Option>; @@ -483,21 +524,35 @@ where // fn layout( // &mut self, // view_state: &mut V, -// element_state: Option, +// _: Option, // cx: &mut ViewContext, // ) -> (LayoutId, Self::State) { - -// self(view_state) - +// let render = self.take().unwrap(); +// let mut element = (render)(view_state, cx).into_any(); +// let layout_id = element.layout(view_state, cx); +// (layout_id, Some(element)) // } // fn paint( // self, -// bounds: Bounds, +// _bounds: Bounds, // view_state: &mut V, -// element_state: &mut Self::State, +// rendered_element: &mut Self::State, // cx: &mut ViewContext, // ) { -// todo!() +// rendered_element.take().unwrap().paint(view_state, cx); +// } +// } + +// impl RenderOnce for Option +// where +// V: 'static, +// E: Element, +// F: FnOnce(&mut V, &mut ViewContext) -> E + 'static, +// { +// type Element = Self; + +// fn render(self) -> Self::Element { +// self // } // } diff --git a/crates/gpui2/src/elements/div.rs b/crates/gpui2/src/elements/div.rs index 1a70a9bab2..26de4ea25f 100644 --- a/crates/gpui2/src/elements/div.rs +++ b/crates/gpui2/src/elements/div.rs @@ -1,9 +1,9 @@ use crate::{ point, px, Action, AnyDrag, AnyElement, AnyTooltip, AnyView, AppContext, BorrowAppContext, - BorrowWindow, Bounds, ClickEvent, Component, DispatchPhase, Element, ElementId, FocusEvent, - FocusHandle, KeyContext, KeyDownEvent, KeyUpEvent, LayoutId, MouseButton, MouseDownEvent, - MouseMoveEvent, MouseUpEvent, ParentComponent, Pixels, Point, Render, ScrollWheelEvent, - SharedString, Size, Style, StyleRefinement, Styled, Task, View, ViewContext, Visibility, + BorrowWindow, Bounds, ClickEvent, DispatchPhase, Element, ElementId, FocusEvent, FocusHandle, + KeyContext, KeyDownEvent, KeyUpEvent, LayoutId, MouseButton, MouseDownEvent, MouseMoveEvent, + MouseUpEvent, ParentElement, Pixels, Point, Render, RenderOnce, ScrollWheelEvent, SharedString, + Size, Style, StyleRefinement, Styled, Task, View, ViewContext, Visibility, }; use collections::HashMap; use refineable::Refineable; @@ -28,7 +28,7 @@ pub struct GroupStyle { pub style: StyleRefinement, } -pub trait InteractiveComponent: Sized + Element { +pub trait InteractiveElement: Sized + Element { fn interactivity(&mut self) -> &mut Interactivity; fn group(mut self, group: impl Into) -> Self { @@ -314,7 +314,7 @@ pub trait InteractiveComponent: Sized + Element { } } -pub trait StatefulInteractiveComponent>: InteractiveComponent { +pub trait StatefulInteractiveElement>: InteractiveElement { fn focusable(mut self) -> Focusable { self.interactivity().focusable = true; Focusable { @@ -381,7 +381,7 @@ pub trait StatefulInteractiveComponent>: InteractiveCo ) -> Self where Self: Sized, - W: 'static + Render, + W: 'static + Render, { debug_assert!( self.interactivity().drag_listener.is_none(), @@ -425,7 +425,7 @@ pub trait StatefulInteractiveComponent>: InteractiveCo } } -pub trait FocusableComponent: InteractiveComponent { +pub trait FocusableElement: InteractiveElement { fn focus(mut self, f: impl FnOnce(StyleRefinement) -> StyleRefinement) -> Self where Self: Sized, @@ -587,13 +587,13 @@ impl Styled for Div { } } -impl InteractiveComponent for Div { +impl InteractiveElement for Div { fn interactivity(&mut self) -> &mut Interactivity { &mut self.interactivity } } -impl ParentComponent for Div { +impl ParentElement for Div { fn children_mut(&mut self) -> &mut SmallVec<[AnyElement; 2]> { &mut self.children } @@ -691,9 +691,11 @@ impl Element for Div { } } -impl Component for Div { - fn render(self) -> AnyElement { - AnyElement::new(self) +impl RenderOnce for Div { + type Element = Self; + + fn render_once(self) -> Self::Element { + self } } @@ -1257,19 +1259,19 @@ pub struct Focusable { view_type: PhantomData, } -impl> FocusableComponent for Focusable {} +impl, E: InteractiveElement> FocusableElement for Focusable {} -impl InteractiveComponent for Focusable +impl InteractiveElement for Focusable where - V: 'static, - E: InteractiveComponent, + V: 'static + Render, + E: InteractiveElement, { fn interactivity(&mut self) -> &mut Interactivity { self.element.interactivity() } } -impl> StatefulInteractiveComponent +impl, E: StatefulInteractiveElement> StatefulInteractiveElement for Focusable { } @@ -1286,7 +1288,7 @@ where impl Element for Focusable where - V: 'static, + V: 'static + Render, E: Element, { type State = E::State; @@ -1315,20 +1317,22 @@ where } } -impl Component for Focusable +impl RenderOnce for Focusable where - V: 'static, - E: 'static + Element, + V: 'static + Render, + E: Element, { - fn render(self) -> AnyElement { - AnyElement::new(self) + type Element = Self; + + fn render_once(self) -> Self::Element { + self } } -impl ParentComponent for Focusable +impl ParentElement for Focusable where V: 'static, - E: ParentComponent, + E: ParentElement, { fn children_mut(&mut self) -> &mut SmallVec<[AnyElement; 2]> { self.element.children_mut() @@ -1350,25 +1354,25 @@ where } } -impl StatefulInteractiveComponent for Stateful +impl StatefulInteractiveElement for Stateful where V: 'static, E: Element, - Self: InteractiveComponent, + Self: InteractiveElement, { } -impl InteractiveComponent for Stateful +impl InteractiveElement for Stateful where V: 'static, - E: InteractiveComponent, + E: InteractiveElement, { fn interactivity(&mut self) -> &mut Interactivity { self.element.interactivity() } } -impl> FocusableComponent for Stateful {} +impl> FocusableElement for Stateful {} impl Element for Stateful where @@ -1401,20 +1405,22 @@ where } } -impl Component for Stateful +impl RenderOnce for Stateful where V: 'static, - E: 'static + Element, + E: Element, { - fn render(self) -> AnyElement { - AnyElement::new(self) + type Element = Self; + + fn render_once(self) -> Self::Element { + self } } -impl ParentComponent for Stateful +impl ParentElement for Stateful where V: 'static, - E: ParentComponent, + E: ParentElement, { fn children_mut(&mut self) -> &mut SmallVec<[AnyElement; 2]> { self.element.children_mut() diff --git a/crates/gpui2/src/elements/img.rs b/crates/gpui2/src/elements/img.rs index db55bd0dc6..16f20869ab 100644 --- a/crates/gpui2/src/elements/img.rs +++ b/crates/gpui2/src/elements/img.rs @@ -1,7 +1,6 @@ use crate::{ - AnyElement, BorrowWindow, Bounds, Component, Element, InteractiveComponent, - InteractiveElementState, Interactivity, LayoutId, Pixels, SharedString, StyleRefinement, - Styled, ViewContext, + BorrowWindow, Bounds, Element, InteractiveElement, InteractiveElementState, Interactivity, + LayoutId, Pixels, RenderOnce, SharedString, StyleRefinement, Styled, ViewContext, }; use futures::FutureExt; use util::ResultExt; @@ -35,12 +34,6 @@ where } } -impl Component for Img { - fn render(self) -> AnyElement { - AnyElement::new(self) - } -} - impl Element for Img { type State = InteractiveElementState; @@ -102,13 +95,21 @@ impl Element for Img { } } +impl RenderOnce for Img { + type Element = Self; + + fn render_once(self) -> Self::Element { + self + } +} + impl Styled for Img { fn style(&mut self) -> &mut StyleRefinement { &mut self.interactivity.base_style } } -impl InteractiveComponent for Img { +impl InteractiveElement for Img { fn interactivity(&mut self) -> &mut Interactivity { &mut self.interactivity } diff --git a/crates/gpui2/src/elements/overlay.rs b/crates/gpui2/src/elements/overlay.rs index 394030f3d7..4e01b8aad1 100644 --- a/crates/gpui2/src/elements/overlay.rs +++ b/crates/gpui2/src/elements/overlay.rs @@ -2,8 +2,8 @@ use smallvec::SmallVec; use taffy::style::{Display, Position}; use crate::{ - point, AnyElement, BorrowWindow, Bounds, Component, Element, LayoutId, ParentComponent, Pixels, - Point, Size, Style, + point, AnyElement, BorrowWindow, Bounds, Element, LayoutId, ParentElement, Pixels, Point, + RenderOnce, Size, Style, }; pub struct OverlayState { @@ -51,18 +51,12 @@ impl Overlay { } } -impl ParentComponent for Overlay { +impl ParentElement for Overlay { fn children_mut(&mut self) -> &mut SmallVec<[AnyElement; 2]> { &mut self.children } } -impl Component for Overlay { - fn render(self) -> AnyElement { - AnyElement::new(self) - } -} - impl Element for Overlay { type State = OverlayState; @@ -163,6 +157,14 @@ impl Element for Overlay { } } +impl RenderOnce for Overlay { + type Element = Self; + + fn render_once(self) -> Self::Element { + self + } +} + enum Axis { Horizontal, Vertical, diff --git a/crates/gpui2/src/elements/svg.rs b/crates/gpui2/src/elements/svg.rs index 7d9c04b729..f6823c50d5 100644 --- a/crates/gpui2/src/elements/svg.rs +++ b/crates/gpui2/src/elements/svg.rs @@ -1,7 +1,6 @@ use crate::{ - AnyElement, Bounds, Component, Element, ElementId, InteractiveComponent, - InteractiveElementState, Interactivity, LayoutId, Pixels, SharedString, StyleRefinement, - Styled, ViewContext, + Bounds, Element, ElementId, InteractiveElement, InteractiveElementState, Interactivity, + LayoutId, Pixels, RenderOnce, SharedString, StyleRefinement, Styled, ViewContext, }; use util::ResultExt; @@ -24,12 +23,6 @@ impl Svg { } } -impl Component for Svg { - fn render(self) -> AnyElement { - AnyElement::new(self) - } -} - impl Element for Svg { type State = InteractiveElementState; @@ -66,13 +59,21 @@ impl Element for Svg { } } +impl RenderOnce for Svg { + type Element = Self; + + fn render_once(self) -> Self::Element { + self + } +} + impl Styled for Svg { fn style(&mut self) -> &mut StyleRefinement { &mut self.interactivity.base_style } } -impl InteractiveComponent for Svg { +impl InteractiveElement for Svg { fn interactivity(&mut self) -> &mut Interactivity { &mut self.interactivity } diff --git a/crates/gpui2/src/elements/text.rs b/crates/gpui2/src/elements/text.rs index c6090fdfb4..bfd63d3c3d 100644 --- a/crates/gpui2/src/elements/text.rs +++ b/crates/gpui2/src/elements/text.rs @@ -1,6 +1,6 @@ use crate::{ - AnyElement, BorrowWindow, Bounds, Component, Element, ElementId, LayoutId, Pixels, - SharedString, Size, TextRun, ViewContext, WindowContext, WrappedLine, + BorrowWindow, Bounds, Element, ElementId, LayoutId, Pixels, RenderOnce, SharedString, Size, + TextRun, ViewContext, WindowContext, WrappedLine, }; use anyhow::anyhow; use parking_lot::{Mutex, MutexGuard}; @@ -37,6 +37,14 @@ impl Element for &'static str { } } +impl RenderOnce for &'static str { + type Element = Self; + + fn render_once(self) -> Self::Element { + self + } +} + impl Element for SharedString { type State = TextState; @@ -67,32 +75,34 @@ impl Element for SharedString { } } -pub struct Text { +impl RenderOnce for SharedString { + type Element = Self; + + fn render_once(self) -> Self::Element { + self + } +} + +pub struct StyledText { text: SharedString, runs: Option>, } -impl Text { +impl StyledText { /// Renders text with runs of different styles. /// /// Callers are responsible for setting the correct style for each run. /// For text with a uniform style, you can usually avoid calling this constructor /// and just pass text directly. - pub fn styled(text: SharedString, runs: Vec) -> Self { - Text { + pub fn new(text: SharedString, runs: Vec) -> Self { + StyledText { text, runs: Some(runs), } } } -impl Component for Text { - fn render(self) -> AnyElement { - AnyElement::new(self) - } -} - -impl Element for Text { +impl Element for StyledText { type State = TextState; fn element_id(&self) -> Option { @@ -181,9 +191,22 @@ impl Element for Text { } } +impl RenderOnce for StyledText { + type Element = Self; + + fn render_once(self) -> Self::Element { + self + } +} + #[derive(Default, Clone)] pub struct TextState(Arc>>); +struct TextStateInner { + lines: SmallVec<[WrappedLine; 1]>, + line_height: Pixels, +} + impl TextState { fn lock(&self) -> MutexGuard> { self.0.lock() @@ -264,14 +287,9 @@ impl TextState { } } -struct TextStateInner { - lines: SmallVec<[WrappedLine; 1]>, - line_height: Pixels, -} - struct InteractiveText { id: ElementId, - text: Text, + text: StyledText, } struct InteractiveTextState { @@ -325,34 +343,10 @@ impl Element for InteractiveText { } } -impl Component for SharedString { - fn render(self) -> AnyElement { - Text { - text: self, - runs: None, - } - .render() - } -} +impl RenderOnce for InteractiveText { + type Element = Self; -impl Component for &'static str { - fn render(self) -> AnyElement { - Text { - text: self.into(), - runs: None, - } - .render() - } -} - -// TODO: Figure out how to pass `String` to `child` without this. -// This impl doesn't exist in the `gpui2` crate. -impl Component for String { - fn render(self) -> AnyElement { - Text { - text: self.into(), - runs: None, - } - .render() + fn render_once(self) -> Self::Element { + self } } diff --git a/crates/gpui2/src/elements/uniform_list.rs b/crates/gpui2/src/elements/uniform_list.rs index d7339d00ab..997904d913 100644 --- a/crates/gpui2/src/elements/uniform_list.rs +++ b/crates/gpui2/src/elements/uniform_list.rs @@ -1,7 +1,7 @@ use crate::{ - point, px, size, AnyElement, AvailableSpace, BorrowWindow, Bounds, Component, Element, - ElementId, InteractiveComponent, InteractiveElementState, Interactivity, LayoutId, Pixels, - Point, Size, StyleRefinement, Styled, ViewContext, + point, px, size, AnyElement, AvailableSpace, BorrowWindow, Bounds, Element, ElementId, + InteractiveElement, InteractiveElementState, Interactivity, LayoutId, Pixels, Point, + RenderOnce, Size, StyleRefinement, Styled, ViewContext, }; use smallvec::SmallVec; use std::{cell::RefCell, cmp, ops::Range, rc::Rc}; @@ -10,15 +10,15 @@ use taffy::style::Overflow; /// uniform_list provides lazy rendering for a set of items that are of uniform height. /// When rendered into a container with overflow-y: hidden and a fixed (or max) height, /// uniform_list will only render the visibile subset of items. -pub fn uniform_list( +pub fn uniform_list( id: I, item_count: usize, - f: impl 'static + Fn(&mut V, Range, &mut ViewContext) -> Vec, + f: impl 'static + Fn(&mut V, Range, &mut ViewContext) -> Vec, ) -> UniformList where I: Into, V: 'static, - C: Component, + E: Element, { let id = id.into(); let mut style = StyleRefinement::default(); @@ -32,7 +32,7 @@ where render_items: Box::new(move |view, visible_range, cx| { f(view, visible_range, cx) .into_iter() - .map(|component| component.render()) + .map(|component| component.into_any()) .collect() }), interactivity: Interactivity { @@ -252,6 +252,14 @@ impl Element for UniformList { } } +impl RenderOnce for UniformList { + type Element = Self; + + fn render_once(self) -> Self::Element { + self + } +} + impl UniformList { pub fn with_width_from_item(mut self, item_index: Option) -> Self { self.item_to_measure_index = item_index.unwrap_or(0); @@ -286,14 +294,8 @@ impl UniformList { } } -impl InteractiveComponent for UniformList { +impl InteractiveElement for UniformList { fn interactivity(&mut self) -> &mut crate::Interactivity { &mut self.interactivity } } - -impl Component for UniformList { - fn render(self) -> AnyElement { - AnyElement::new(self) - } -} diff --git a/crates/gpui2/src/gpui2.rs b/crates/gpui2/src/gpui2.rs index 4423986e3d..a815c1f596 100644 --- a/crates/gpui2/src/gpui2.rs +++ b/crates/gpui2/src/gpui2.rs @@ -121,7 +121,7 @@ pub trait VisualContext: Context { build_view: impl FnOnce(&mut ViewContext<'_, V>) -> V, ) -> Self::Result> where - V: 'static + Render; + V: 'static + Render; fn update_view( &mut self, @@ -134,7 +134,7 @@ pub trait VisualContext: Context { build_view: impl FnOnce(&mut ViewContext<'_, V>) -> V, ) -> Self::Result> where - V: Render; + V: 'static + Render; fn focus_view(&mut self, view: &View) -> Self::Result<()> where diff --git a/crates/gpui2/src/interactive.rs b/crates/gpui2/src/interactive.rs index 83bcc5c8c5..617972e3c0 100644 --- a/crates/gpui2/src/interactive.rs +++ b/crates/gpui2/src/interactive.rs @@ -1,5 +1,5 @@ use crate::{ - div, point, Component, Div, FocusHandle, Keystroke, Modifiers, Pixels, Point, Render, + div, point, Div, Element, FocusHandle, Keystroke, Modifiers, Pixels, Point, Render, ViewContext, }; use smallvec::SmallVec; @@ -64,7 +64,7 @@ pub struct Drag where R: Fn(&mut V, &mut ViewContext) -> E, V: 'static, - E: Component<()>, + E: Element<()>, { pub state: S, pub render_drag_handle: R, @@ -75,7 +75,7 @@ impl Drag where R: Fn(&mut V, &mut ViewContext) -> E, V: 'static, - E: Component<()>, + E: Element<()>, { pub fn new(state: S, render_drag_handle: R) -> Self { Drag { @@ -193,7 +193,7 @@ impl Deref for MouseExitEvent { #[derive(Debug, Clone, Default)] pub struct ExternalPaths(pub(crate) SmallVec<[PathBuf; 2]>); -impl Render for ExternalPaths { +impl Render for ExternalPaths { type Element = Div; fn render(&mut self, _: &mut ViewContext) -> Self::Element { @@ -286,8 +286,8 @@ pub struct FocusEvent { #[cfg(test)] mod test { use crate::{ - self as gpui, div, Component, Div, FocusHandle, InteractiveComponent, KeyBinding, - Keystroke, ParentComponent, Render, Stateful, TestAppContext, ViewContext, VisualContext, + self as gpui, div, Div, FocusHandle, InteractiveElement, KeyBinding, Keystroke, + ParentElement, Stateful, TestAppContext, Render, VisualContext, }; struct TestView { @@ -298,7 +298,7 @@ mod test { actions!(TestAction); - impl Render for TestView { + impl Render for TestView { type Element = Stateful>; fn render(&mut self, _: &mut gpui::ViewContext) -> Self::Element { diff --git a/crates/gpui2/src/prelude.rs b/crates/gpui2/src/prelude.rs index 7c2ad3f07f..50f48596bc 100644 --- a/crates/gpui2/src/prelude.rs +++ b/crates/gpui2/src/prelude.rs @@ -1,4 +1,5 @@ pub use crate::{ - BorrowAppContext, BorrowWindow, Component, Context, FocusableComponent, InteractiveComponent, - ParentComponent, Refineable, Render, StatefulInteractiveComponent, Styled, VisualContext, + BorrowAppContext, BorrowWindow, Component, Context, Element, FocusableElement, + InteractiveElement, ParentElement, Refineable, Render, RenderOnce, StatefulInteractiveElement, + Styled, VisualContext, }; diff --git a/crates/gpui2/src/view.rs b/crates/gpui2/src/view.rs index 51675e814c..c32bc70e4a 100644 --- a/crates/gpui2/src/view.rs +++ b/crates/gpui2/src/view.rs @@ -1,7 +1,8 @@ use crate::{ private::Sealed, AnyElement, AnyModel, AnyWeakModel, AppContext, AvailableSpace, BorrowWindow, - Bounds, Component, Element, ElementId, Entity, EntityId, Flatten, FocusHandle, FocusableView, - LayoutId, Model, Pixels, Point, Size, ViewContext, VisualContext, WeakModel, WindowContext, + Bounds, Element, ElementId, Entity, EntityId, Flatten, FocusHandle, FocusableView, LayoutId, + Model, Pixels, Point, Render, RenderOnce, Size, ViewContext, VisualContext, WeakModel, + WindowContext, }; use anyhow::{Context, Result}; use std::{ @@ -9,14 +10,8 @@ use std::{ hash::{Hash, Hasher}, }; -pub trait Render: 'static + Sized { - type Element: Element + 'static; - - fn render(&mut self, cx: &mut ViewContext) -> Self::Element; -} - pub struct View { - pub(crate) model: Model, + pub model: Model, } impl Sealed for View {} @@ -64,13 +59,13 @@ impl View { self.model.read(cx) } - pub fn render_with(&self, component: C) -> RenderViewWith + pub fn render_with(&self, component: E) -> RenderViewWith where - C: 'static + Component, + E: 'static + Element, { RenderViewWith { view: self.clone(), - component: Some(component), + element: Some(component), } } @@ -104,12 +99,6 @@ impl PartialEq for View { impl Eq for View {} -impl Component for View { - fn render(self) -> AnyElement { - AnyElement::new(AnyView::from(self)) - } -} - pub struct WeakView { pub(crate) model: WeakModel, } @@ -206,13 +195,7 @@ impl AnyView { } } -impl Component for AnyView { - fn render(self) -> AnyElement { - AnyElement::new(self) - } -} - -impl From> for AnyView { +impl> From> for AnyView { fn from(value: View) -> Self { AnyView { model: value.model.into_any(), @@ -222,7 +205,48 @@ impl From> for AnyView { } } -impl Element for AnyView { +impl, ParentV: 'static> Element for View { + type State = Option>; + + fn element_id(&self) -> Option { + Some(self.model.entity_id.into()) + } + + fn layout( + &mut self, + _parent_view: &mut ParentV, + _state: Option, + cx: &mut ViewContext, + ) -> (LayoutId, Self::State) { + self.update(cx, |view, cx| { + let mut element = view.render(cx).into_any(); + let layout_id = element.layout(view, cx); + (layout_id, Some(element)) + }) + } + + fn paint( + self, + _: Bounds, + _parent: &mut ParentV, + element: &mut Self::State, + cx: &mut ViewContext, + ) { + self.update(cx, |view, cx| { + element.take().unwrap().paint(view, cx); + }); + } +} + +impl, ParentV: 'static> RenderOnce for View { + type Element = View; + + fn render_once(self) -> Self::Element { + self + } +} + +impl Element for AnyView { type State = Option>; fn element_id(&self) -> Option { @@ -231,9 +255,9 @@ impl Element for AnyView { fn layout( &mut self, - _view_state: &mut ParentViewState, + _view_state: &mut V, _element_state: Option, - cx: &mut ViewContext, + cx: &mut ViewContext, ) -> (LayoutId, Self::State) { let (layout_id, rendered_element) = (self.layout)(self, cx); (layout_id, Some(rendered_element)) @@ -242,14 +266,22 @@ impl Element for AnyView { fn paint( mut self, _bounds: Bounds, - _view_state: &mut ParentViewState, + _view_state: &mut V, rendered_element: &mut Self::State, - cx: &mut ViewContext, + cx: &mut ViewContext, ) { (self.paint)(&mut self, rendered_element.take().unwrap(), cx) } } +impl RenderOnce for AnyView { + type Element = Self; + + fn render_once(self) -> Self::Element { + self + } +} + pub struct AnyWeakView { model: AnyWeakModel, layout: fn(&AnyView, &mut WindowContext) -> (LayoutId, Box), @@ -267,7 +299,7 @@ impl AnyWeakView { } } -impl From> for AnyWeakView { +impl> From> for AnyWeakView { fn from(view: WeakView) -> Self { Self { model: view.model.into(), @@ -277,10 +309,10 @@ impl From> for AnyWeakView { } } -impl Render for T +impl Render for F where - T: 'static + FnMut(&mut WindowContext) -> E, - E: 'static + Send + Element, + F: 'static + FnMut(&mut WindowContext) -> E, + E: 'static + Send + Element, { type Element = E; @@ -289,29 +321,18 @@ where } } -pub struct RenderViewWith { +pub struct RenderViewWith { view: View, - component: Option, + element: Option, } -impl Component for RenderViewWith +impl Element for RenderViewWith where - C: 'static + Component, - ParentViewState: 'static, - ViewState: 'static, + E: 'static + Element, + ParentV: 'static, + V: 'static, { - fn render(self) -> AnyElement { - AnyElement::new(self) - } -} - -impl Element for RenderViewWith -where - C: 'static + Component, - ParentViewState: 'static, - ViewState: 'static, -{ - type State = Option>; + type State = Option>; fn element_id(&self) -> Option { Some(self.view.entity_id().into()) @@ -319,12 +340,12 @@ where fn layout( &mut self, - _: &mut ParentViewState, + _: &mut ParentV, _: Option, - cx: &mut ViewContext, + cx: &mut ViewContext, ) -> (LayoutId, Self::State) { self.view.update(cx, |view, cx| { - let mut element = self.component.take().unwrap().render(); + let mut element = self.element.take().unwrap().into_any(); let layout_id = element.layout(view, cx); (layout_id, Some(element)) }) @@ -333,20 +354,33 @@ where fn paint( self, _: Bounds, - _: &mut ParentViewState, + _: &mut ParentV, element: &mut Self::State, - cx: &mut ViewContext, + cx: &mut ViewContext, ) { self.view .update(cx, |view, cx| element.take().unwrap().paint(view, cx)) } } +impl RenderOnce for RenderViewWith +where + E: 'static + Element, + V: 'static, + ParentV: 'static, +{ + type Element = Self; + + fn render_once(self) -> Self::Element { + self + } +} + mod any_view { use crate::{AnyElement, AnyView, BorrowWindow, LayoutId, Render, WindowContext}; use std::any::Any; - pub(crate) fn layout( + pub(crate) fn layout>( view: &AnyView, cx: &mut WindowContext, ) -> (LayoutId, Box) { @@ -360,7 +394,11 @@ mod any_view { }) } - pub(crate) fn paint(view: &AnyView, element: Box, cx: &mut WindowContext) { + pub(crate) fn paint>( + view: &AnyView, + element: Box, + cx: &mut WindowContext, + ) { cx.with_element_id(Some(view.model.entity_id), |cx| { let view = view.clone().downcast::().unwrap(); let element = element.downcast::>().unwrap(); diff --git a/crates/gpui2/src/window.rs b/crates/gpui2/src/window.rs index a9a2c60acb..1b3b37c290 100644 --- a/crates/gpui2/src/window.rs +++ b/crates/gpui2/src/window.rs @@ -6,8 +6,8 @@ use crate::{ InputEvent, IsZero, KeyBinding, KeyContext, KeyDownEvent, LayoutId, Model, ModelContext, Modifiers, MonochromeSprite, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, Path, Pixels, PlatformAtlas, PlatformDisplay, PlatformInputHandler, PlatformWindow, Point, - PolychromeSprite, PromptLevel, Quad, Render, RenderGlyphParams, RenderImageParams, - RenderSvgParams, ScaledPixels, SceneBuilder, Shadow, SharedString, Size, Style, SubscriberSet, + PolychromeSprite, PromptLevel, Quad, RenderGlyphParams, RenderImageParams, RenderSvgParams, + Render, ScaledPixels, SceneBuilder, Shadow, SharedString, Size, Style, SubscriberSet, Subscription, TaffyLayoutEngine, Task, Underline, UnderlineStyle, View, VisualContext, WeakView, WindowBounds, WindowOptions, SUBPIXEL_VARIANTS, }; @@ -187,13 +187,13 @@ impl Drop for FocusHandle { /// FocusableView allows users of your view to easily /// focus it (using cx.focus_view(view)) -pub trait FocusableView: Render { +pub trait FocusableView: 'static + Render { fn focus_handle(&self, cx: &AppContext) -> FocusHandle; } /// ManagedView is a view (like a Modal, Popover, Menu, etc.) /// where the lifecycle of the view is handled by another view. -pub trait ManagedView: Render { +pub trait ManagedView: 'static + Render { fn focus_handle(&self, cx: &AppContext) -> FocusHandle; } @@ -1525,7 +1525,7 @@ impl VisualContext for WindowContext<'_> { build_view_state: impl FnOnce(&mut ViewContext<'_, V>) -> V, ) -> Self::Result> where - V: 'static + Render, + V: 'static + Render, { let slot = self.app.entities.reserve(); let view = View { @@ -1564,7 +1564,7 @@ impl VisualContext for WindowContext<'_> { build_view: impl FnOnce(&mut ViewContext<'_, V>) -> V, ) -> Self::Result> where - V: Render, + V: 'static + Render, { let slot = self.app.entities.reserve(); let view = View { @@ -2326,7 +2326,7 @@ impl Context for ViewContext<'_, V> { } impl VisualContext for ViewContext<'_, V> { - fn build_view( + fn build_view + 'static>( &mut self, build_view_state: impl FnOnce(&mut ViewContext<'_, W>) -> W, ) -> Self::Result> { @@ -2346,7 +2346,7 @@ impl VisualContext for ViewContext<'_, V> { build_view: impl FnOnce(&mut ViewContext<'_, W>) -> W, ) -> Self::Result> where - W: Render, + W: 'static + Render, { self.window_cx.replace_root_view(build_view) } @@ -2387,7 +2387,7 @@ pub struct WindowHandle { state_type: PhantomData, } -impl WindowHandle { +impl> WindowHandle { pub fn new(id: WindowId) -> Self { WindowHandle { any_handle: AnyWindowHandle { diff --git a/crates/gpui2_macros/src/derive_element.rs b/crates/gpui2_macros/src/derive_element.rs deleted file mode 100644 index e8c698c1fe..0000000000 --- a/crates/gpui2_macros/src/derive_element.rs +++ /dev/null @@ -1,75 +0,0 @@ -use proc_macro::TokenStream; -use quote::quote; -use syn::{parse_macro_input, DeriveInput, GenericParam}; - -pub fn derive_element(input: TokenStream) -> TokenStream { - let ast = parse_macro_input!(input as DeriveInput); - let type_name = ast.ident; - - let mut view_state_ty = quote! { V }; - - for param in &ast.generics.params { - if let GenericParam::Type(type_param) = param { - let type_ident = &type_param.ident; - view_state_ty = quote! {#type_ident}; - break; - } - } - - let attrs = &ast.attrs; - for attr in attrs { - if attr.path.is_ident("element") { - match attr.parse_meta() { - Ok(syn::Meta::List(i)) => { - for nested_meta in i.nested { - if let syn::NestedMeta::Meta(syn::Meta::NameValue(nv)) = nested_meta { - if nv.path.is_ident("view_state") { - if let syn::Lit::Str(lit_str) = nv.lit { - view_state_ty = lit_str.value().parse().unwrap(); - } - } - } - } - } - _ => (), - } - } - } - - let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl(); - - let gen = quote! { - impl #impl_generics gpui::Element<#view_state_ty> for #type_name #ty_generics - #where_clause - { - type State = Option>; - - fn element_id(&self) -> Option { - None - } - - fn layout( - &mut self, - view_state: &mut #view_state_ty, - _element_state: Option, - cx: &mut gpui::ViewContext<#view_state_ty>, - ) -> (gpui::LayoutId, Self::State) { - let mut element = self.render(view_state, cx).into_any(); - let layout_id = element.layout(view_state, cx); - (layout_id, Some(element)) - } - - fn paint( - self, - _bounds: gpui::Bounds, - view_state: &mut #view_state_ty, - rendered_element: &mut Self::State, - cx: &mut gpui::ViewContext<#view_state_ty>, - ) { - rendered_element.take().unwrap().paint(view_state, cx) - } - } - }; - - gen.into() -} diff --git a/crates/gpui2_macros/src/derive_render_once.rs b/crates/gpui2_macros/src/derive_render_once.rs new file mode 100644 index 0000000000..ee01e22358 --- /dev/null +++ b/crates/gpui2_macros/src/derive_render_once.rs @@ -0,0 +1,64 @@ +use proc_macro::TokenStream; +use quote::quote; +use syn::{parse_macro_input, parse_quote, DeriveInput}; + +pub fn derive_render_once(input: TokenStream) -> TokenStream { + let ast = parse_macro_input!(input as DeriveInput); + let type_name = &ast.ident; + + let mut trait_generics = ast.generics.clone(); + let view_type = if let Some(view_type) = specified_view_type(&ast) { + quote! { #view_type } + } else { + if let Some(first_type_param) = ast.generics.params.iter().find_map(|param| { + if let syn::GenericParam::Type(type_param) = param { + Some(type_param.ident.clone()) + } else { + None + } + }) { + quote! { #first_type_param } + } else { + trait_generics.params.push(parse_quote! { V: 'static }); + quote! { V } + } + }; + + let (impl_generics, _, where_clause) = trait_generics.split_for_impl(); + let (_, type_generics, _) = ast.generics.split_for_impl(); + + let gen = quote! { + impl #impl_generics gpui::RenderOnce<#view_type> for #type_name #type_generics + #where_clause + { + type Element = gpui::CompositeElement<#view_type, Self>; + + fn render_once(self) -> Self::Element { + gpui::CompositeElement::new(self) + } + } + }; + + if type_name == "Avatar" { + println!("{gen}"); + } + + gen.into() +} + +fn specified_view_type(ast: &DeriveInput) -> Option { + ast.attrs.iter().find_map(|attr| { + if attr.path.is_ident("view") { + if let Ok(syn::Meta::NameValue(meta_name_value)) = attr.parse_meta() { + if let syn::Lit::Str(lit_str) = meta_name_value.lit { + return Some( + lit_str + .parse::() + .expect("Failed to parse view_type"), + ); + } + } + } + None + }) +} diff --git a/crates/gpui2_macros/src/gpui2_macros.rs b/crates/gpui2_macros/src/gpui2_macros.rs index a611727066..6dd817e280 100644 --- a/crates/gpui2_macros/src/gpui2_macros.rs +++ b/crates/gpui2_macros/src/gpui2_macros.rs @@ -1,6 +1,6 @@ mod action; mod derive_component; -mod derive_element; +mod derive_render_once; mod register_action; mod style_helpers; mod test; @@ -22,9 +22,9 @@ pub fn derive_component(input: TokenStream) -> TokenStream { derive_component::derive_component(input) } -#[proc_macro_derive(Element, attributes(element))] -pub fn derive_element(input: TokenStream) -> TokenStream { - derive_element::derive_element(input) +#[proc_macro_derive(RenderOnce, attributes(view))] +pub fn derive_render_once(input: TokenStream) -> TokenStream { + derive_render_once::derive_render_once(input) } #[proc_macro] diff --git a/crates/project_panel2/src/project_panel.rs b/crates/project_panel2/src/project_panel.rs index 7a455fe8ce..46ad7a7a41 100644 --- a/crates/project_panel2/src/project_panel.rs +++ b/crates/project_panel2/src/project_panel.rs @@ -10,9 +10,9 @@ use anyhow::{anyhow, Result}; use gpui::{ actions, div, px, uniform_list, Action, AppContext, AssetSource, AsyncWindowContext, ClipboardItem, Component, Div, EventEmitter, FocusHandle, Focusable, FocusableView, - InteractiveComponent, Model, MouseButton, ParentComponent, Pixels, Point, PromptLevel, Render, - Stateful, StatefulInteractiveComponent, Styled, Task, UniformListScrollHandle, View, - ViewContext, VisualContext as _, WeakView, WindowContext, + InteractiveElement, Model, MouseButton, ParentElement, Pixels, Point, PromptLevel, Render, + Stateful, StatefulInteractiveElement, Styled, Task, UniformListScrollHandle, View, ViewContext, + VisualContext as _, WeakView, WindowContext, }; use menu::{Confirm, SelectNext, SelectPrev}; use project::{ diff --git a/crates/storybook2/src/stories/text.rs b/crates/storybook2/src/stories/text.rs index 6fc76ab907..94c9d0d51f 100644 --- a/crates/storybook2/src/stories/text.rs +++ b/crates/storybook2/src/stories/text.rs @@ -1,4 +1,6 @@ -use gpui::{div, white, Div, ParentComponent, Render, Styled, View, VisualContext, WindowContext}; +use gpui::{ + div, white, Div, ParentElement, Render, Styled, View, VisualContext, WindowContext, +}; pub struct TextStory; diff --git a/crates/storybook2/src/stories/z_index.rs b/crates/storybook2/src/stories/z_index.rs index 02165db0cd..4916f192b1 100644 --- a/crates/storybook2/src/stories/z_index.rs +++ b/crates/storybook2/src/stories/z_index.rs @@ -79,7 +79,7 @@ trait Styles: Styled + Sized { impl Styles for Div {} -#[derive(Component)] +// #[derive(RenderOnce)] struct ZIndexExample { z_index: u32, } diff --git a/crates/terminal_view2/src/terminal_panel.rs b/crates/terminal_view2/src/terminal_panel.rs index 944cd912be..6321e61e35 100644 --- a/crates/terminal_view2/src/terminal_panel.rs +++ b/crates/terminal_view2/src/terminal_panel.rs @@ -4,7 +4,7 @@ use crate::TerminalView; use db::kvp::KEY_VALUE_STORE; use gpui::{ actions, div, serde_json, AppContext, AsyncWindowContext, Div, Entity, EventEmitter, - FocusHandle, FocusableView, ParentComponent, Render, Subscription, Task, View, ViewContext, + FocusHandle, FocusableView, ParentElement, Render, Subscription, Task, View, ViewContext, VisualContext, WeakView, WindowContext, }; use project::Fs; diff --git a/crates/terminal_view2/src/terminal_view.rs b/crates/terminal_view2/src/terminal_view.rs index b6ab7e86b9..8f5aeb5630 100644 --- a/crates/terminal_view2/src/terminal_view.rs +++ b/crates/terminal_view2/src/terminal_view.rs @@ -10,10 +10,9 @@ pub mod terminal_panel; use editor::{scroll::autoscroll::Autoscroll, Editor}; use gpui::{ actions, div, img, red, Action, AnyElement, AppContext, Component, DispatchPhase, Div, - EventEmitter, FocusEvent, FocusHandle, Focusable, FocusableComponent, FocusableView, - InputHandler, InteractiveComponent, KeyDownEvent, Keystroke, Model, MouseButton, - ParentComponent, Pixels, Render, SharedString, Styled, Task, View, ViewContext, VisualContext, - WeakView, + EventEmitter, FocusEvent, FocusHandle, Focusable, FocusableElement, FocusableView, + InputHandler, InteractiveElement, KeyDownEvent, Keystroke, Model, MouseButton, ParentElement, + Pixels, Render, SharedString, Styled, Task, View, ViewContext, VisualContext, WeakView, }; use language::Bias; use persistence::TERMINAL_DB; diff --git a/crates/theme2/src/story.rs b/crates/theme2/src/story.rs index 9cf78cf943..e0c802fcc7 100644 --- a/crates/theme2/src/story.rs +++ b/crates/theme2/src/story.rs @@ -1,4 +1,4 @@ -use gpui::{div, Component, Div, Element, ParentComponent, SharedString, Styled, ViewContext}; +use gpui::{div, Div, Element, ParentElement, SharedString, Styled, ViewContext}; use crate::ActiveTheme; diff --git a/crates/theme2/src/styles/players.rs b/crates/theme2/src/styles/players.rs index dfb0a6ff4e..2eba4e8275 100644 --- a/crates/theme2/src/styles/players.rs +++ b/crates/theme2/src/styles/players.rs @@ -143,11 +143,11 @@ use crate::{amber, blue, jade, lime, orange, pink, purple, red}; mod stories { use super::*; use crate::{ActiveTheme, Story}; - use gpui::{div, img, px, Div, ParentComponent, Render, Styled, ViewContext}; + use gpui::{div, img, px, Div, ParentElement, Render, Styled, ViewContext}; pub struct PlayerStory; - impl Render for PlayerStory { + impl Render for PlayerStory { type Element = Div; fn render(&mut self, cx: &mut ViewContext) -> Self::Element { diff --git a/crates/ui2/src/components/avatar.rs b/crates/ui2/src/components/avatar.rs index 95e0653d36..da76a95cfa 100644 --- a/crates/ui2/src/components/avatar.rs +++ b/crates/ui2/src/components/avatar.rs @@ -1,26 +1,16 @@ use crate::prelude::*; -use gpui::img; +use gpui::{img, Img, RenderOnce}; -#[derive(Element)] +#[derive(RenderOnce)] pub struct Avatar { src: SharedString, shape: Shape, } -impl Avatar { - pub fn new(src: impl Into) -> Self { - Self { - src: src.into(), - shape: Shape::Circle, - } - } +impl Component for Avatar { + type Rendered = Img; - pub fn shape(mut self, shape: Shape) -> Self { - self.shape = shape; - self - } - - fn render(self, _view: &mut V, cx: &mut ViewContext) -> impl Element { + fn render(self, _view: &mut V, cx: &mut ViewContext) -> Self::Rendered { let mut img = img(); if self.shape == Shape::Circle { @@ -36,6 +26,20 @@ impl Avatar { } } +impl Avatar { + pub fn new(src: impl Into) -> Self { + Self { + src: src.into(), + shape: Shape::Circle, + } + } + + pub fn shape(mut self, shape: Shape) -> Self { + self.shape = shape; + self + } +} + #[cfg(feature = "stories")] pub use stories::*; @@ -47,7 +51,7 @@ mod stories { pub struct AvatarStory; - impl Render for AvatarStory { + impl Render for AvatarStory { type Element = Div; fn render(&mut self, cx: &mut ViewContext) -> Self::Element { diff --git a/crates/ui2/src/components/button.rs b/crates/ui2/src/components/button.rs index b2c6893793..f22727ae64 100644 --- a/crates/ui2/src/components/button.rs +++ b/crates/ui2/src/components/button.rs @@ -1,6 +1,9 @@ use std::sync::Arc; -use gpui::{div, DefiniteLength, Hsla, MouseButton, StatefulInteractiveComponent, WindowContext}; +use gpui::{ + div, DefiniteLength, Div, Hsla, MouseButton, RenderOnce, Stateful, StatefulInteractiveElement, + WindowContext, +}; use crate::prelude::*; use crate::{h_stack, Icon, IconButton, IconElement, Label, LineHeightStyle, TextColor}; @@ -76,7 +79,7 @@ impl Default for ButtonHandlers { } } -#[derive(Component)] +#[derive(RenderOnce)] pub struct Button { disabled: bool, handlers: ButtonHandlers, @@ -88,6 +91,58 @@ pub struct Button { color: Option, } +impl Component for Button { + type Rendered = Stateful>; + + fn render(self, view: &mut V, cx: &mut ViewContext) -> Self::Rendered { + let _view: &mut V = view; + let (icon_color, label_color) = match (self.disabled, self.color) { + (true, _) => (TextColor::Disabled, TextColor::Disabled), + (_, None) => (TextColor::Default, TextColor::Default), + (_, Some(color)) => (TextColor::from(color), color), + }; + + let mut button = h_stack() + .id(SharedString::from(format!("{}", self.label))) + .relative() + .p_1() + .text_ui() + .rounded_md() + .bg(self.variant.bg_color(cx)) + .cursor_pointer() + .hover(|style| style.bg(self.variant.bg_color_hover(cx))) + .active(|style| style.bg(self.variant.bg_color_active(cx))); + + match (self.icon, self.icon_position) { + (Some(_), Some(IconPosition::Left)) => { + button = button + .gap_1() + .child(self.render_label(label_color)) + .children(self.render_icon(icon_color)) + } + (Some(_), Some(IconPosition::Right)) => { + button = button + .gap_1() + .children(self.render_icon(icon_color)) + .child(self.render_label(label_color)) + } + (_, _) => button = button.child(self.render_label(label_color)), + } + + if let Some(width) = self.width { + button = button.w(width).justify_center(); + } + + if let Some(click_handler) = self.handlers.click.clone() { + button = button.on_mouse_down(MouseButton::Left, move |state, event, cx| { + click_handler(state, cx); + }); + } + + button + } +} + impl Button { pub fn new(label: impl Into) -> Self { Self { @@ -212,25 +267,29 @@ impl Button { } } -#[derive(Component)] +#[derive(RenderOnce)] pub struct ButtonGroup { buttons: Vec>, } +impl Component for ButtonGroup { + type Rendered = Div; + + fn render(self, view: &mut V, cx: &mut ViewContext) -> Self::Rendered { + let mut group = h_stack(); + + for button in self.buttons.into_iter() { + group = group.child(button.render(view, cx)); + } + + group + } +} + impl ButtonGroup { pub fn new(buttons: Vec>) -> Self { Self { buttons } } - - fn render(self, _view: &mut V, cx: &mut ViewContext) -> impl Element { - let mut el = h_stack().text_ui(); - - for button in self.buttons { - el = el.child(button.render(_view, cx)); - } - - el - } } #[cfg(feature = "stories")] @@ -245,7 +304,7 @@ mod stories { pub struct ButtonStory; - impl Render for ButtonStory { + impl Render for ButtonStory { type Element = Div; fn render(&mut self, cx: &mut ViewContext) -> Self::Element { diff --git a/crates/ui2/src/components/checkbox.rs b/crates/ui2/src/components/checkbox.rs index 0b3141a9f3..3e1d4ff669 100644 --- a/crates/ui2/src/components/checkbox.rs +++ b/crates/ui2/src/components/checkbox.rs @@ -1,4 +1,4 @@ -use gpui::{div, prelude::*, Component, Element, ElementId, Styled, ViewContext}; +use gpui::{div, prelude::*, Div, Element, ElementId, RenderOnce, Stateful, Styled, ViewContext}; use std::sync::Arc; use theme2::ActiveTheme; @@ -11,7 +11,7 @@ pub type CheckHandler = Arc) + /// Checkboxes are used for multiple choices, not for mutually exclusive choices. /// Each checkbox works independently from other checkboxes in the list, /// therefore checking an additional box does not affect any other selections. -#[derive(Component)] +#[derive(RenderOnce)] pub struct Checkbox { id: ElementId, checked: Selection, @@ -19,6 +19,130 @@ pub struct Checkbox { on_click: Option>, } +impl Component for Checkbox { + type Rendered = Stateful>; + + fn render(self, view: &mut V, cx: &mut ViewContext) -> Self::Rendered { + let group_id = format!("checkbox_group_{:?}", self.id); + + let icon = match self.checked { + // When selected, we show a checkmark. + Selection::Selected => { + Some( + IconElement::new(Icon::Check) + .size(crate::IconSize::Small) + .color( + // If the checkbox is disabled we change the color of the icon. + if self.disabled { + TextColor::Disabled + } else { + TextColor::Selected + }, + ), + ) + } + // In an indeterminate state, we show a dash. + Selection::Indeterminate => { + Some( + IconElement::new(Icon::Dash) + .size(crate::IconSize::Small) + .color( + // If the checkbox is disabled we change the color of the icon. + if self.disabled { + TextColor::Disabled + } else { + TextColor::Selected + }, + ), + ) + } + // When unselected, we show nothing. + Selection::Unselected => None, + }; + + // A checkbox could be in an indeterminate state, + // for example the indeterminate state could represent: + // - a group of options of which only some are selected + // - an enabled option that is no longer available + // - a previously agreed to license that has been updated + // + // For the sake of styles we treat the indeterminate state as selected, + // but it's icon will be different. + let selected = + self.checked == Selection::Selected || self.checked == Selection::Indeterminate; + + // We could use something like this to make the checkbox background when selected: + // + // ~~~rust + // ... + // .when(selected, |this| { + // this.bg(cx.theme().colors().element_selected) + // }) + // ~~~ + // + // But we use a match instead here because the checkbox might be disabled, + // and it could be disabled _while_ it is selected, as well as while it is not selected. + let (bg_color, border_color) = match (self.disabled, selected) { + (true, _) => ( + cx.theme().colors().ghost_element_disabled, + cx.theme().colors().border_disabled, + ), + (false, true) => ( + cx.theme().colors().element_selected, + cx.theme().colors().border, + ), + (false, false) => ( + cx.theme().colors().element_background, + cx.theme().colors().border, + ), + }; + + div() + .id(self.id) + // Rather than adding `px_1()` to add some space around the checkbox, + // we use a larger parent element to create a slightly larger + // click area for the checkbox. + .size_5() + // Because we've enlarged the click area, we need to create a + // `group` to pass down interactivity events to the checkbox. + .group(group_id.clone()) + .child( + div() + .flex() + // This prevent the flex element from growing + // or shrinking in response to any size changes + .flex_none() + // The combo of `justify_center()` and `items_center()` + // is used frequently to center elements in a flex container. + // + // We use this to center the icon in the checkbox. + .justify_center() + .items_center() + .m_1() + .size_4() + .rounded_sm() + .bg(bg_color) + .border() + .border_color(border_color) + // We only want the interactivity states to fire when we + // are in a checkbox that isn't disabled. + .when(!self.disabled, |this| { + // Here instead of `hover()` we use `group_hover()` + // to pass it the group id. + this.group_hover(group_id.clone(), |el| { + el.bg(cx.theme().colors().element_hover) + }) + }) + .children(icon), + ) + .when_some( + self.on_click.filter(|_| !self.disabled), + |this, on_click| { + this.on_click(move |view, _, cx| on_click(self.checked.inverse(), view, cx)) + }, + ) + } +} impl Checkbox { pub fn new(id: impl Into, checked: Selection) -> Self { Self { @@ -175,7 +299,7 @@ mod stories { pub struct CheckboxStory; - impl Render for CheckboxStory { + impl Render for CheckboxStory { type Element = Div; fn render(&mut self, cx: &mut ViewContext) -> Self::Element { diff --git a/crates/ui2/src/components/context_menu.rs b/crates/ui2/src/components/context_menu.rs index c6d2382f56..41675e6bcd 100644 --- a/crates/ui2/src/components/context_menu.rs +++ b/crates/ui2/src/components/context_menu.rs @@ -5,7 +5,8 @@ use crate::prelude::*; use crate::{v_stack, Label, List, ListEntry, ListItem, ListSeparator, ListSubHeader}; use gpui::{ overlay, px, Action, AnchorCorner, AnyElement, Bounds, Dismiss, DispatchPhase, Div, - FocusHandle, LayoutId, ManagedView, MouseButton, MouseDownEvent, Pixels, Point, Render, View, + FocusHandle, LayoutId, ManagedView, MouseButton, MouseDownEvent, Pixels, Point, Render, + RenderOnce, View, }; pub struct ContextMenu { @@ -52,7 +53,7 @@ impl ContextMenu { } } -impl Render for ContextMenu { +impl Render for ContextMenu { type Element = Div; // todo!() fn render(&mut self, cx: &mut ViewContext) -> Self::Element { @@ -96,8 +97,8 @@ impl MenuHandle { self } - pub fn child>(mut self, f: impl FnOnce(bool) -> R + 'static) -> Self { - self.child_builder = Some(Box::new(|b| f(b).render())); + pub fn child>(mut self, f: impl FnOnce(bool) -> R + 'static) -> Self { + self.child_builder = Some(Box::new(|b| f(b).render_once().into_any())); self } @@ -160,9 +161,9 @@ impl Element for MenuHandle { } overlay = overlay.position(*position.borrow()); - let mut view = overlay.child(menu.clone()).render(); - menu_layout_id = Some(view.layout(view_state, cx)); - view + let mut element = overlay.child(menu.clone()).into_any(); + menu_layout_id = Some(element.layout(view_state, cx)); + element }); let mut child_element = self @@ -247,9 +248,11 @@ impl Element for MenuHandle { } } -impl Component for MenuHandle { - fn render(self) -> AnyElement { - AnyElement::new(self) +impl RenderOnce for MenuHandle { + type Element = Self; + + fn render_once(self) -> Self::Element { + self } } @@ -275,7 +278,7 @@ mod stories { pub struct ContextMenuStory; - impl Render for ContextMenuStory { + impl Render for ContextMenuStory { type Element = Div; fn render(&mut self, cx: &mut ViewContext) -> Self::Element { @@ -302,7 +305,6 @@ mod stories { } else { "RIGHT CLICK ME" }) - .render() }) .menu(move |_, cx| build_menu(cx, "top left")), ) @@ -315,7 +317,6 @@ mod stories { } else { "RIGHT CLICK ME" }) - .render() }) .anchor(AnchorCorner::BottomLeft) .attach(AnchorCorner::TopLeft) @@ -336,7 +337,6 @@ mod stories { } else { "RIGHT CLICK ME" }) - .render() }) .anchor(AnchorCorner::TopRight) .menu(move |_, cx| build_menu(cx, "top right")), @@ -350,7 +350,6 @@ mod stories { } else { "RIGHT CLICK ME" }) - .render() }) .anchor(AnchorCorner::BottomRight) .attach(AnchorCorner::TopRight) diff --git a/crates/ui2/src/components/details.rs b/crates/ui2/src/components/details.rs index 41b3e45a95..95750d5b47 100644 --- a/crates/ui2/src/components/details.rs +++ b/crates/ui2/src/components/details.rs @@ -1,13 +1,29 @@ use crate::prelude::*; use crate::{v_stack, ButtonGroup}; -#[derive(Component)] +#[derive(RenderOnce)] pub struct Details { text: &'static str, meta: Option<&'static str>, actions: Option>, } +impl Component for Details { + type Rendered = Div; + + fn render(self, view: &mut V, cx: &mut ViewContext) -> Self::Rendered { + v_stack() + .p_1() + .gap_0p5() + .text_ui_sm() + .text_color(cx.theme().colors().text) + .size_full() + .child(self.text) + .children(self.meta.map(|m| m)) + .children(self.actions.map(|a| a)) + } +} + impl Details { pub fn new(text: &'static str) -> Self { Self { @@ -26,20 +42,9 @@ impl Details { self.actions = Some(actions); self } - - fn render(self, _view: &mut V, cx: &mut ViewContext) -> impl Element { - v_stack() - .p_1() - .gap_0p5() - .text_ui_sm() - .text_color(cx.theme().colors().text) - .size_full() - .child(self.text) - .children(self.meta.map(|m| m)) - .children(self.actions.map(|a| a)) - } } +use gpui::{Div, RenderOnce}; #[cfg(feature = "stories")] pub use stories::*; @@ -51,7 +56,7 @@ mod stories { pub struct DetailsStory; - impl Render for DetailsStory { + impl Render for DetailsStory { type Element = Div; fn render(&mut self, cx: &mut ViewContext) -> Self::Element { diff --git a/crates/ui2/src/components/divider.rs b/crates/ui2/src/components/divider.rs index 7025a99a9f..a651ab08af 100644 --- a/crates/ui2/src/components/divider.rs +++ b/crates/ui2/src/components/divider.rs @@ -1,3 +1,5 @@ +use gpui::RenderOnce; + use crate::prelude::*; enum DividerDirection { @@ -5,7 +7,7 @@ enum DividerDirection { Vertical, } -#[derive(Component)] +// #[derive(RenderOnce)] pub struct Divider { direction: DividerDirection, inset: bool, diff --git a/crates/ui2/src/components/facepile.rs b/crates/ui2/src/components/facepile.rs index a3ec7db7b1..8bb3c9b2e0 100644 --- a/crates/ui2/src/components/facepile.rs +++ b/crates/ui2/src/components/facepile.rs @@ -1,19 +1,15 @@ use crate::prelude::*; use crate::{Avatar, Player}; -#[derive(Component)] +#[derive(RenderOnce)] pub struct Facepile { players: Vec, } -impl Facepile { - pub fn new>(players: P) -> Self { - Self { - players: players.collect(), - } - } +impl Component for Facepile { + type Rendered = Div; - fn render(self, _view: &mut V, cx: &mut ViewContext) -> impl Element { + fn render(self, view: &mut V, cx: &mut ViewContext) -> Self::Rendered { let player_count = self.players.len(); let player_list = self.players.iter().enumerate().map(|(ix, player)| { let isnt_last = ix < player_count - 1; @@ -26,6 +22,15 @@ impl Facepile { } } +impl Facepile { + pub fn new>(players: P) -> Self { + Self { + players: players.collect(), + } + } +} + +use gpui::{Div, RenderOnce}; #[cfg(feature = "stories")] pub use stories::*; @@ -37,7 +42,7 @@ mod stories { pub struct FacepileStory; - impl Render for FacepileStory { + impl Render for FacepileStory { type Element = Div; fn render(&mut self, cx: &mut ViewContext) -> Self::Element { diff --git a/crates/ui2/src/components/icon.rs b/crates/ui2/src/components/icon.rs index 7dc9385a4f..0bf2828ad6 100644 --- a/crates/ui2/src/components/icon.rs +++ b/crates/ui2/src/components/icon.rs @@ -1,4 +1,4 @@ -use gpui::{rems, svg}; +use gpui::{rems, svg, RenderOnce, Svg}; use strum::EnumIter; use crate::prelude::*; @@ -129,13 +129,30 @@ impl Icon { } } -#[derive(Component)] +#[derive(RenderOnce)] pub struct IconElement { path: SharedString, color: TextColor, size: IconSize, } +impl Component for IconElement { + type Rendered = Svg; + + fn render(self, _view: &mut V, cx: &mut ViewContext) -> Self::Rendered { + let svg_size = match self.size { + IconSize::Small => rems(0.75), + IconSize::Medium => rems(0.9375), + }; + + svg() + .size(svg_size) + .flex_none() + .path(self.path) + .text_color(self.color.color(cx)) + } +} + impl IconElement { pub fn new(icon: Icon) -> Self { Self { @@ -191,7 +208,7 @@ mod stories { pub struct IconStory; - impl Render for IconStory { + impl Render for IconStory { type Element = Div; fn render(&mut self, cx: &mut ViewContext) -> Self::Element { diff --git a/crates/ui2/src/components/icon_button.rs b/crates/ui2/src/components/icon_button.rs index 5bb26cdb91..ea015e95a6 100644 --- a/crates/ui2/src/components/icon_button.rs +++ b/crates/ui2/src/components/icon_button.rs @@ -1,5 +1,5 @@ use crate::{h_stack, prelude::*, ClickHandler, Icon, IconElement}; -use gpui::{prelude::*, Action, AnyView, MouseButton}; +use gpui::{prelude::*, Action, AnyView, Div, MouseButton, Stateful}; use std::sync::Arc; struct IconButtonHandlers { @@ -12,7 +12,7 @@ impl Default for IconButtonHandlers { } } -#[derive(Component)] +#[derive(RenderOnce)] pub struct IconButton { id: ElementId, icon: Icon, @@ -24,6 +24,61 @@ pub struct IconButton { handlers: IconButtonHandlers, } +impl Component for IconButton { + type Rendered = Stateful>; + + fn render(self, view: &mut V, cx: &mut ViewContext) -> Self::Rendered { + let icon_color = match (self.state, self.color) { + (InteractionState::Disabled, _) => TextColor::Disabled, + (InteractionState::Active, _) => TextColor::Selected, + _ => self.color, + }; + + let (mut bg_color, bg_hover_color, bg_active_color) = match self.variant { + ButtonVariant::Filled => ( + cx.theme().colors().element_background, + cx.theme().colors().element_hover, + cx.theme().colors().element_active, + ), + ButtonVariant::Ghost => ( + cx.theme().colors().ghost_element_background, + cx.theme().colors().ghost_element_hover, + cx.theme().colors().ghost_element_active, + ), + }; + + if self.selected { + bg_color = bg_hover_color; + } + + let mut button = h_stack() + .id(self.id.clone()) + .justify_center() + .rounded_md() + .p_1() + .bg(bg_color) + .cursor_pointer() + .hover(|style| style.bg(bg_hover_color)) + .active(|style| style.bg(bg_active_color)) + .child(IconElement::new(self.icon).color(icon_color)); + + if let Some(click_handler) = self.handlers.click.clone() { + button = button.on_mouse_down(MouseButton::Left, move |state, event, cx| { + cx.stop_propagation(); + click_handler(state, cx); + }) + } + + if let Some(tooltip) = self.tooltip { + if !self.selected { + button = button.tooltip(move |view: &mut V, cx| (tooltip)(view, cx)) + } + } + + button + } +} + impl IconButton { pub fn new(id: impl Into, icon: Icon) -> Self { Self { @@ -79,55 +134,4 @@ impl IconButton { pub fn action(self, action: Box) -> Self { self.on_click(move |this, cx| cx.dispatch_action(action.boxed_clone())) } - - fn render(mut self, _view: &mut V, cx: &mut ViewContext) -> impl Element { - let icon_color = match (self.state, self.color) { - (InteractionState::Disabled, _) => TextColor::Disabled, - (InteractionState::Active, _) => TextColor::Selected, - _ => self.color, - }; - - let (mut bg_color, bg_hover_color, bg_active_color) = match self.variant { - ButtonVariant::Filled => ( - cx.theme().colors().element_background, - cx.theme().colors().element_hover, - cx.theme().colors().element_active, - ), - ButtonVariant::Ghost => ( - cx.theme().colors().ghost_element_background, - cx.theme().colors().ghost_element_hover, - cx.theme().colors().ghost_element_active, - ), - }; - - if self.selected { - bg_color = bg_hover_color; - } - - let mut button = h_stack() - .id(self.id.clone()) - .justify_center() - .rounded_md() - .p_1() - .bg(bg_color) - .cursor_pointer() - .hover(|style| style.bg(bg_hover_color)) - .active(|style| style.bg(bg_active_color)) - .child(IconElement::new(self.icon).color(icon_color)); - - if let Some(click_handler) = self.handlers.click.clone() { - button = button.on_mouse_down(MouseButton::Left, move |state, event, cx| { - cx.stop_propagation(); - click_handler(state, cx); - }) - } - - if let Some(tooltip) = self.tooltip.take() { - if !self.selected { - button = button.tooltip(move |view: &mut V, cx| (tooltip)(view, cx)) - } - } - - button - } } diff --git a/crates/ui2/src/components/indicator.rs b/crates/ui2/src/components/indicator.rs index 02760852b1..33c0307cd4 100644 --- a/crates/ui2/src/components/indicator.rs +++ b/crates/ui2/src/components/indicator.rs @@ -1,10 +1,24 @@ -use gpui::px; - use crate::prelude::*; +use gpui::{px, Div, RenderOnce}; -#[derive(Component)] +#[derive(RenderOnce)] pub struct UnreadIndicator; +impl Component for UnreadIndicator { + type Rendered = Div; + + fn render(self, view: &mut V, cx: &mut ViewContext) -> Self::Rendered { + div() + .rounded_full() + .border_2() + .border_color(cx.theme().colors().surface_background) + .w(px(9.0)) + .h(px(9.0)) + .z_index(2) + .bg(cx.theme().status().info) + } +} + impl UnreadIndicator { pub fn new() -> Self { Self diff --git a/crates/ui2/src/components/input.rs b/crates/ui2/src/components/input.rs index 1389d83ce2..ea0312ab82 100644 --- a/crates/ui2/src/components/input.rs +++ b/crates/ui2/src/components/input.rs @@ -1,5 +1,5 @@ use crate::{prelude::*, Label}; -use gpui::prelude::*; +use gpui::{prelude::*, Div, RenderOnce, Stateful}; #[derive(Default, PartialEq)] pub enum InputVariant { @@ -8,7 +8,7 @@ pub enum InputVariant { Filled, } -#[derive(Component)] +#[derive(RenderOnce)] pub struct Input { placeholder: SharedString, value: String, @@ -18,6 +18,57 @@ pub struct Input { is_active: bool, } +impl Component for Input { + type Rendered = Stateful>; + + fn render(self, view: &mut V, cx: &mut ViewContext) -> Self::Rendered { + let (input_bg, input_hover_bg, input_active_bg) = match self.variant { + InputVariant::Ghost => ( + cx.theme().colors().ghost_element_background, + cx.theme().colors().ghost_element_hover, + cx.theme().colors().ghost_element_active, + ), + InputVariant::Filled => ( + cx.theme().colors().element_background, + cx.theme().colors().element_hover, + cx.theme().colors().element_active, + ), + }; + + let placeholder_label = Label::new(self.placeholder.clone()).color(if self.disabled { + TextColor::Disabled + } else { + TextColor::Placeholder + }); + + let label = Label::new(self.value.clone()).color(if self.disabled { + TextColor::Disabled + } else { + TextColor::Default + }); + + div() + .id("input") + .h_7() + .w_full() + .px_2() + .border() + .border_color(cx.theme().styles.system.transparent) + .bg(input_bg) + .hover(|style| style.bg(input_hover_bg)) + .active(|style| style.bg(input_active_bg)) + .flex() + .items_center() + .child(div().flex().items_center().text_ui_sm().map(move |this| { + if self.value.is_empty() { + this.child(placeholder_label) + } else { + this.child(label) + } + })) + } +} + impl Input { pub fn new(placeholder: impl Into) -> Self { Self { @@ -54,53 +105,6 @@ impl Input { self.is_active = is_active; self } - - fn render(self, _view: &mut V, cx: &mut ViewContext) -> impl Element { - let (input_bg, input_hover_bg, input_active_bg) = match self.variant { - InputVariant::Ghost => ( - cx.theme().colors().ghost_element_background, - cx.theme().colors().ghost_element_hover, - cx.theme().colors().ghost_element_active, - ), - InputVariant::Filled => ( - cx.theme().colors().element_background, - cx.theme().colors().element_hover, - cx.theme().colors().element_active, - ), - }; - - let placeholder_label = Label::new(self.placeholder.clone()).color(if self.disabled { - TextColor::Disabled - } else { - TextColor::Placeholder - }); - - let label = Label::new(self.value.clone()).color(if self.disabled { - TextColor::Disabled - } else { - TextColor::Default - }); - - div() - .id("input") - .h_7() - .w_full() - .px_2() - .border() - .border_color(cx.theme().styles.system.transparent) - .bg(input_bg) - .hover(|style| style.bg(input_hover_bg)) - .active(|style| style.bg(input_active_bg)) - .flex() - .items_center() - .child(div().flex().items_center().text_ui_sm().map(|this| { - if self.value.is_empty() { - this.child(placeholder_label) - } else { - this.child(label) - } - })) - } } #[cfg(feature = "stories")] @@ -114,7 +118,7 @@ mod stories { pub struct InputStory; - impl Render for InputStory { + impl Render for InputStory { type Element = Div; fn render(&mut self, cx: &mut ViewContext) -> Self::Element { diff --git a/crates/ui2/src/components/keybinding.rs b/crates/ui2/src/components/keybinding.rs index 608e3ab0b8..ce7c408fb2 100644 --- a/crates/ui2/src/components/keybinding.rs +++ b/crates/ui2/src/components/keybinding.rs @@ -1,9 +1,8 @@ -use gpui::Action; +use crate::prelude::*; +use gpui::{Action, Div, RenderOnce}; use strum::EnumIter; -use crate::prelude::*; - -#[derive(Component, Clone)] +#[derive(RenderOnce, Clone)] pub struct KeyBinding { /// A keybinding consists of a key and a set of modifier keys. /// More then one keybinding produces a chord. @@ -12,19 +11,10 @@ pub struct KeyBinding { key_binding: gpui::KeyBinding, } -impl KeyBinding { - pub fn for_action(action: &dyn Action, cx: &mut WindowContext) -> Option { - // todo! this last is arbitrary, we want to prefer users key bindings over defaults, - // and vim over normal (in vim mode), etc. - let key_binding = cx.bindings_for_action(action).last().cloned()?; - Some(Self::new(key_binding)) - } +impl Component for KeyBinding { + type Rendered = Div; - pub fn new(key_binding: gpui::KeyBinding) -> Self { - Self { key_binding } - } - - fn render(self, _view: &mut V, cx: &mut ViewContext) -> impl Element { + fn render(self, view: &mut V, cx: &mut ViewContext) -> Self::Rendered { div() .flex() .gap_2() @@ -42,17 +32,29 @@ impl KeyBinding { } } -#[derive(Component)] +impl KeyBinding { + pub fn for_action(action: &dyn Action, cx: &mut WindowContext) -> Option { + // todo! this last is arbitrary, we want to prefer users key bindings over defaults, + // and vim over normal (in vim mode), etc. + let key_binding = cx.bindings_for_action(action).last().cloned()?; + Some(Self::new(key_binding)) + } + + pub fn new(key_binding: gpui::KeyBinding) -> Self { + Self { key_binding } + } +} + +#[derive(RenderOnce)] pub struct Key { key: SharedString, } -impl Key { - pub fn new(key: impl Into) -> Self { - Self { key: key.into() } - } +impl Component for Key { + type Rendered = Div; - fn render(self, _view: &mut V, cx: &mut ViewContext) -> impl Element { + fn render(self, view: &mut V, cx: &mut ViewContext) -> Self::Rendered { + let _view: &mut V = view; div() .px_2() .py_0() @@ -64,6 +66,12 @@ impl Key { } } +impl Key { + pub fn new(key: impl Into) -> Self { + Self { key: key.into() } + } +} + // NOTE: The order the modifier keys appear in this enum impacts the order in // which they are rendered in the UI. #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, EnumIter)] @@ -92,7 +100,7 @@ mod stories { gpui::KeyBinding::new(key, NoAction {}, None) } - impl Render for KeybindingStory { + impl Render for KeybindingStory { type Element = Div; fn render(&mut self, cx: &mut ViewContext) -> Self::Element { diff --git a/crates/ui2/src/components/label.rs b/crates/ui2/src/components/label.rs index c07c467000..7afb00a96e 100644 --- a/crates/ui2/src/components/label.rs +++ b/crates/ui2/src/components/label.rs @@ -1,7 +1,6 @@ -use gpui::{relative, Hsla, Text, TextRun, WindowContext}; - use crate::prelude::*; use crate::styled_ext::StyledExt; +use gpui::{relative, Div, Hsla, RenderOnce, StyledText, TextRun, WindowContext}; #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, Default)] pub enum LabelSize { @@ -60,7 +59,7 @@ pub enum LineHeightStyle { UILabel, } -#[derive(Clone, Component)] +#[derive(Clone, RenderOnce)] pub struct Label { label: SharedString, size: LabelSize, @@ -69,6 +68,33 @@ pub struct Label { strikethrough: bool, } +impl Component for Label { + type Rendered = Div; + + fn render(self, _view: &mut V, cx: &mut ViewContext) -> Self::Rendered { + div() + .when(self.strikethrough, |this| { + this.relative().child( + div() + .absolute() + .top_1_2() + .w_full() + .h_px() + .bg(TextColor::Hidden.color(cx)), + ) + }) + .map(|this| match self.size { + LabelSize::Default => this.text_ui(), + LabelSize::Small => this.text_ui_sm(), + }) + .when(self.line_height_style == LineHeightStyle::UILabel, |this| { + this.line_height(relative(1.)) + }) + .text_color(self.color.color(cx)) + .child(self.label.clone()) + } +} + impl Label { pub fn new(label: impl Into) -> Self { Self { @@ -99,32 +125,9 @@ impl Label { self.strikethrough = strikethrough; self } - - fn render(self, _view: &mut V, cx: &mut ViewContext) -> impl Element { - div() - .when(self.strikethrough, |this| { - this.relative().child( - div() - .absolute() - .top_1_2() - .w_full() - .h_px() - .bg(TextColor::Hidden.color(cx)), - ) - }) - .map(|this| match self.size { - LabelSize::Default => this.text_ui(), - LabelSize::Small => this.text_ui_sm(), - }) - .when(self.line_height_style == LineHeightStyle::UILabel, |this| { - this.line_height(relative(1.)) - }) - .text_color(self.color.color(cx)) - .child(self.label.clone()) - } } -#[derive(Component)] +#[derive(RenderOnce)] pub struct HighlightedLabel { label: SharedString, size: LabelSize, @@ -133,35 +136,10 @@ pub struct HighlightedLabel { strikethrough: bool, } -impl HighlightedLabel { - /// shows a label with the given characters highlighted. - /// characters are identified by utf8 byte position. - pub fn new(label: impl Into, highlight_indices: Vec) -> Self { - Self { - label: label.into(), - size: LabelSize::Default, - color: TextColor::Default, - highlight_indices, - strikethrough: false, - } - } +impl Component for HighlightedLabel { + type Rendered = Div; - pub fn size(mut self, size: LabelSize) -> Self { - self.size = size; - self - } - - pub fn color(mut self, color: TextColor) -> Self { - self.color = color; - self - } - - pub fn set_strikethrough(mut self, strikethrough: bool) -> Self { - self.strikethrough = strikethrough; - self - } - - fn render(self, _view: &mut V, cx: &mut ViewContext) -> impl Element { + fn render(self, view: &mut V, cx: &mut ViewContext) -> Self::Rendered { let highlight_color = cx.theme().colors().text_accent; let mut text_style = cx.text_style().clone(); @@ -214,7 +192,36 @@ impl HighlightedLabel { LabelSize::Default => this.text_ui(), LabelSize::Small => this.text_ui_sm(), }) - .child(Text::styled(self.label, runs)) + .child(StyledText::new(self.label, runs)) + } +} + +impl HighlightedLabel { + /// shows a label with the given characters highlighted. + /// characters are identified by utf8 byte position. + pub fn new(label: impl Into, highlight_indices: Vec) -> Self { + Self { + label: label.into(), + size: LabelSize::Default, + color: TextColor::Default, + highlight_indices, + strikethrough: false, + } + } + + pub fn size(mut self, size: LabelSize) -> Self { + self.size = size; + self + } + + pub fn color(mut self, color: TextColor) -> Self { + self.color = color; + self + } + + pub fn set_strikethrough(mut self, strikethrough: bool) -> Self { + self.strikethrough = strikethrough; + self } } @@ -235,7 +242,7 @@ mod stories { pub struct LabelStory; - impl Render for LabelStory { + impl Render for LabelStory { type Element = Div; fn render(&mut self, cx: &mut ViewContext) -> Self::Element { diff --git a/crates/ui2/src/components/list.rs b/crates/ui2/src/components/list.rs index 8756d2934c..b183c149fc 100644 --- a/crates/ui2/src/components/list.rs +++ b/crates/ui2/src/components/list.rs @@ -1,4 +1,4 @@ -use gpui::{div, Action}; +use gpui::{div, Action, Div, RenderOnce}; use crate::settings::user_settings; use crate::{ @@ -22,7 +22,7 @@ pub enum ListHeaderMeta { Text(Label), } -#[derive(Component)] +#[derive(RenderOnce)] pub struct ListHeader { label: SharedString, left_icon: Option, @@ -31,33 +31,10 @@ pub struct ListHeader { toggle: Toggle, } -impl ListHeader { - pub fn new(label: impl Into) -> Self { - Self { - label: label.into(), - left_icon: None, - meta: None, - variant: ListItemVariant::default(), - toggle: Toggle::NotToggleable, - } - } +impl Component for ListHeader { + type Rendered = Div; - pub fn toggle(mut self, toggle: Toggle) -> Self { - self.toggle = toggle; - self - } - - pub fn left_icon(mut self, left_icon: Option) -> Self { - self.left_icon = left_icon; - self - } - - pub fn meta(mut self, meta: Option) -> Self { - self.meta = meta; - self - } - - fn render(self, _view: &mut V, cx: &mut ViewContext) -> impl Element { + fn render(self, view: &mut V, cx: &mut ViewContext) -> Self::Rendered { let disclosure_control = disclosure_control(self.toggle); let meta = match self.meta { @@ -79,11 +56,6 @@ impl ListHeader { h_stack() .w_full() .bg(cx.theme().colors().surface_background) - // TODO: Add focus state - // .when(self.state == InteractionState::Focused, |this| { - // this.border() - // .border_color(cx.theme().colors().border_focused) - // }) .relative() .child( div() @@ -117,7 +89,94 @@ impl ListHeader { } } -#[derive(Component, Clone)] +impl ListHeader { + pub fn new(label: impl Into) -> Self { + Self { + label: label.into(), + left_icon: None, + meta: None, + variant: ListItemVariant::default(), + toggle: Toggle::NotToggleable, + } + } + + pub fn toggle(mut self, toggle: Toggle) -> Self { + self.toggle = toggle; + self + } + + pub fn left_icon(mut self, left_icon: Option) -> Self { + self.left_icon = left_icon; + self + } + + pub fn meta(mut self, meta: Option) -> Self { + self.meta = meta; + self + } + + // before_ship!("delete") + // fn render(self, _view: &mut V, cx: &mut ViewContext) -> impl Element { + // let disclosure_control = disclosure_control(self.toggle); + + // let meta = match self.meta { + // Some(ListHeaderMeta::Tools(icons)) => div().child( + // h_stack() + // .gap_2() + // .items_center() + // .children(icons.into_iter().map(|i| { + // IconElement::new(i) + // .color(TextColor::Muted) + // .size(IconSize::Small) + // })), + // ), + // Some(ListHeaderMeta::Button(label)) => div().child(label), + // Some(ListHeaderMeta::Text(label)) => div().child(label), + // None => div(), + // }; + + // h_stack() + // .w_full() + // .bg(cx.theme().colors().surface_background) + // // TODO: Add focus state + // // .when(self.state == InteractionState::Focused, |this| { + // // this.border() + // // .border_color(cx.theme().colors().border_focused) + // // }) + // .relative() + // .child( + // div() + // .h_5() + // .when(self.variant == ListItemVariant::Inset, |this| this.px_2()) + // .flex() + // .flex_1() + // .items_center() + // .justify_between() + // .w_full() + // .gap_1() + // .child( + // h_stack() + // .gap_1() + // .child( + // div() + // .flex() + // .gap_1() + // .items_center() + // .children(self.left_icon.map(|i| { + // IconElement::new(i) + // .color(TextColor::Muted) + // .size(IconSize::Small) + // })) + // .child(Label::new(self.label.clone()).color(TextColor::Muted)), + // ) + // .child(disclosure_control), + // ) + // .child(meta), + // ) + // } +} + +#[derive(Clone)] pub struct ListSubHeader { label: SharedString, left_icon: Option, @@ -172,7 +231,7 @@ pub enum ListEntrySize { Medium, } -#[derive(Component, Clone)] +#[derive(RenderOnce, Clone)] pub enum ListItem { Entry(ListEntry), Separator(ListSeparator), @@ -197,15 +256,19 @@ impl From for ListItem { } } -impl ListItem { - fn render(self, view: &mut V, cx: &mut ViewContext) -> impl Element { +impl Component for ListItem { + type Rendered = Div; + + fn render(self, view: &mut V, cx: &mut ViewContext) -> Self::Rendered { match self { ListItem::Entry(entry) => div().child(entry.render(view, cx)), ListItem::Separator(separator) => div().child(separator.render(view, cx)), ListItem::Header(header) => div().child(header.render(view, cx)), } } +} +impl ListItem { pub fn new(label: Label) -> Self { Self::Entry(ListEntry::new(label)) } @@ -219,7 +282,7 @@ impl ListItem { } } -#[derive(Component)] +// #[derive(RenderOnce)] pub struct ListEntry { disabled: bool, // TODO: Reintroduce this @@ -377,20 +440,24 @@ impl ListEntry { } } -#[derive(Clone, Component)] +#[derive(RenderOnce, Clone)] pub struct ListSeparator; impl ListSeparator { pub fn new() -> Self { Self } +} - fn render(self, _view: &mut V, cx: &mut ViewContext) -> impl Element { +impl Component for ListSeparator { + type Rendered = Div; + + fn render(self, view: &mut V, cx: &mut ViewContext) -> Self::Rendered { div().h_px().w_full().bg(cx.theme().colors().border_variant) } } -#[derive(Component)] +#[derive(RenderOnce)] pub struct List { items: Vec, /// Message to display when the list is empty @@ -400,6 +467,26 @@ pub struct List { toggle: Toggle, } +impl Component for List { + type Rendered = Div; + + fn render(self, view: &mut V, cx: &mut ViewContext) -> Self::Rendered { + let list_content = match (self.items.is_empty(), self.toggle) { + (false, _) => div().children(self.items), + (true, Toggle::Toggled(false)) => div(), + (true, _) => { + div().child(Label::new(self.empty_message.clone()).color(TextColor::Muted)) + } + }; + + v_stack() + .w_full() + .py_1() + .children(self.header.map(|header| header)) + .child(list_content) + } +} + impl List { pub fn new(items: Vec) -> Self { Self { diff --git a/crates/ui2/src/components/modal.rs b/crates/ui2/src/components/modal.rs index ac4cb4017f..da76a79b29 100644 --- a/crates/ui2/src/components/modal.rs +++ b/crates/ui2/src/components/modal.rs @@ -1,9 +1,9 @@ -use gpui::AnyElement; +use gpui::{AnyElement, Div, RenderOnce, Stateful}; use smallvec::SmallVec; use crate::{h_stack, prelude::*, v_stack, Button, Icon, IconButton, Label}; -#[derive(Component)] +#[derive(RenderOnce)] pub struct Modal { id: ElementId, title: Option, @@ -12,33 +12,11 @@ pub struct Modal { children: SmallVec<[AnyElement; 2]>, } -impl Modal { - pub fn new(id: impl Into) -> Self { - Self { - id: id.into(), - title: None, - primary_action: None, - secondary_action: None, - children: SmallVec::new(), - } - } +impl Component for Modal { + type Rendered = Stateful>; - pub fn title(mut self, title: impl Into) -> Self { - self.title = Some(title.into()); - self - } - - pub fn primary_action(mut self, action: Button) -> Self { - self.primary_action = Some(action); - self - } - - pub fn secondary_action(mut self, action: Button) -> Self { - self.secondary_action = Some(action); - self - } - - fn render(self, _view: &mut V, cx: &mut ViewContext) -> impl Element { + fn render(self, view: &mut V, cx: &mut ViewContext) -> Self::Rendered { + let _view: &mut V = view; v_stack() .id(self.id.clone()) .w_96() @@ -74,7 +52,34 @@ impl Modal { } } -impl ParentComponent for Modal { +impl Modal { + pub fn new(id: impl Into) -> Self { + Self { + id: id.into(), + title: None, + primary_action: None, + secondary_action: None, + children: SmallVec::new(), + } + } + + pub fn title(mut self, title: impl Into) -> Self { + self.title = Some(title.into()); + self + } + + pub fn primary_action(mut self, action: Button) -> Self { + self.primary_action = Some(action); + self + } + + pub fn secondary_action(mut self, action: Button) -> Self { + self.secondary_action = Some(action); + self + } +} + +impl ParentElement for Modal { fn children_mut(&mut self) -> &mut SmallVec<[AnyElement; 2]> { &mut self.children } diff --git a/crates/ui2/src/components/notification_toast.rs b/crates/ui2/src/components/notification_toast.rs index ec0d2ac216..dd5165e49f 100644 --- a/crates/ui2/src/components/notification_toast.rs +++ b/crates/ui2/src/components/notification_toast.rs @@ -3,7 +3,7 @@ use gpui::rems; use crate::prelude::*; use crate::{h_stack, Icon}; -#[derive(Component)] +// #[derive(RenderOnce)] pub struct NotificationToast { label: SharedString, icon: Option, diff --git a/crates/ui2/src/components/palette.rs b/crates/ui2/src/components/palette.rs index 4b6daea0f9..9870c9e086 100644 --- a/crates/ui2/src/components/palette.rs +++ b/crates/ui2/src/components/palette.rs @@ -1,7 +1,9 @@ use crate::{h_stack, prelude::*, v_stack, KeyBinding, Label}; use gpui::prelude::*; +use gpui::Div; +use gpui::Stateful; -#[derive(Component)] +#[derive(RenderOnce)] pub struct Palette { id: ElementId, input_placeholder: SharedString, @@ -10,6 +12,70 @@ pub struct Palette { default_order: OrderMethod, } +impl Component for Palette { + type Rendered = Stateful>; + + fn render(self, view: &mut V, cx: &mut ViewContext) -> Self::Rendered { + v_stack() + .id(self.id) + .w_96() + .rounded_lg() + .bg(cx.theme().colors().elevated_surface_background) + .border() + .border_color(cx.theme().colors().border) + .child( + v_stack() + .gap_px() + .child(v_stack().py_0p5().px_1().child( + div().px_2().py_0p5().child( + Label::new(self.input_placeholder).color(TextColor::Placeholder), + ), + )) + .child( + div() + .h_px() + .w_full() + .bg(cx.theme().colors().element_background), + ) + .child( + v_stack() + .id("items") + .py_0p5() + .px_1() + .grow() + .max_h_96() + .overflow_y_scroll() + .children( + vec![if self.items.is_empty() { + Some(h_stack().justify_between().px_2().py_1().child( + Label::new(self.empty_string).color(TextColor::Muted), + )) + } else { + None + }] + .into_iter() + .flatten(), + ) + .children(self.items.into_iter().enumerate().map(|(index, item)| { + h_stack() + .id(index) + .justify_between() + .px_2() + .py_0p5() + .rounded_lg() + .hover(|style| { + style.bg(cx.theme().colors().ghost_element_hover) + }) + .active(|style| { + style.bg(cx.theme().colors().ghost_element_active) + }) + .child(item) + })), + ), + ) + } +} + impl Palette { pub fn new(id: impl Into) -> Self { Self { @@ -41,76 +107,33 @@ impl Palette { self.default_order = default_order; self } - - fn render(self, _view: &mut V, cx: &mut ViewContext) -> impl Element { - v_stack() - .id(self.id.clone()) - .w_96() - .rounded_lg() - .bg(cx.theme().colors().elevated_surface_background) - .border() - .border_color(cx.theme().colors().border) - .child( - v_stack() - .gap_px() - .child(v_stack().py_0p5().px_1().child(div().px_2().py_0p5().child( - Label::new(self.input_placeholder.clone()).color(TextColor::Placeholder), - ))) - .child( - div() - .h_px() - .w_full() - .bg(cx.theme().colors().element_background), - ) - .child( - v_stack() - .id("items") - .py_0p5() - .px_1() - .grow() - .max_h_96() - .overflow_y_scroll() - .children( - vec![if self.items.is_empty() { - Some( - h_stack().justify_between().px_2().py_1().child( - Label::new(self.empty_string.clone()) - .color(TextColor::Muted), - ), - ) - } else { - None - }] - .into_iter() - .flatten(), - ) - .children(self.items.into_iter().enumerate().map(|(index, item)| { - h_stack() - .id(index) - .justify_between() - .px_2() - .py_0p5() - .rounded_lg() - .hover(|style| { - style.bg(cx.theme().colors().ghost_element_hover) - }) - .active(|style| { - style.bg(cx.theme().colors().ghost_element_active) - }) - .child(item) - })), - ), - ) - } } -#[derive(Component)] +#[derive(RenderOnce)] pub struct PaletteItem { pub label: SharedString, pub sublabel: Option, pub key_binding: Option, } +impl Component for PaletteItem { + type Rendered = Div; + + fn render(self, view: &mut V, cx: &mut ViewContext) -> Self::Rendered { + div() + .flex() + .flex_row() + .grow() + .justify_between() + .child( + v_stack() + .child(Label::new(self.label)) + .children(self.sublabel.map(|sublabel| Label::new(sublabel))), + ) + .children(self.key_binding) + } +} + impl PaletteItem { pub fn new(label: impl Into) -> Self { Self { @@ -134,20 +157,6 @@ impl PaletteItem { self.key_binding = key_binding.into(); self } - - fn render(self, _view: &mut V, cx: &mut ViewContext) -> impl Element { - div() - .flex() - .flex_row() - .grow() - .justify_between() - .child( - v_stack() - .child(Label::new(self.label.clone())) - .children(self.sublabel.clone().map(|sublabel| Label::new(sublabel))), - ) - .children(self.key_binding) - } } use gpui::ElementId; @@ -164,7 +173,7 @@ mod stories { pub struct PaletteStory; - impl Render for PaletteStory { + impl Render for PaletteStory { type Element = Div; fn render(&mut self, cx: &mut ViewContext) -> Self::Element { diff --git a/crates/ui2/src/components/panel.rs b/crates/ui2/src/components/panel.rs index 88aca620da..38d68ab3ea 100644 --- a/crates/ui2/src/components/panel.rs +++ b/crates/ui2/src/components/panel.rs @@ -1,4 +1,4 @@ -use gpui::{prelude::*, AbsoluteLength, AnyElement}; +use gpui::{prelude::*, AbsoluteLength, AnyElement, Div, RenderOnce, Stateful}; use smallvec::SmallVec; use crate::prelude::*; @@ -38,7 +38,7 @@ pub enum PanelSide { use std::collections::HashSet; -#[derive(Component)] +#[derive(RenderOnce)] pub struct Panel { id: ElementId, current_side: PanelSide, @@ -49,6 +49,30 @@ pub struct Panel { children: SmallVec<[AnyElement; 2]>, } +impl Component for Panel { + type Rendered = Stateful>; + + fn render(self, view: &mut V, cx: &mut ViewContext) -> Self::Rendered { + let current_size = self.width.unwrap_or(self.initial_width); + + v_stack() + .id(self.id.clone()) + .flex_initial() + .map(|this| match self.current_side { + PanelSide::Left | PanelSide::Right => this.h_full().w(current_size), + PanelSide::Bottom => this, + }) + .map(|this| match self.current_side { + PanelSide::Left => this.border_r(), + PanelSide::Right => this.border_l(), + PanelSide::Bottom => this.border_b().w_full().h(current_size), + }) + .bg(cx.theme().colors().surface_background) + .border_color(cx.theme().colors().border) + .children(self.children) + } +} + impl Panel { pub fn new(id: impl Into, cx: &mut WindowContext) -> Self { let settings = user_settings(cx); @@ -91,29 +115,9 @@ impl Panel { } self } - - fn render(self, _view: &mut V, cx: &mut ViewContext) -> impl Element { - let current_size = self.width.unwrap_or(self.initial_width); - - v_stack() - .id(self.id.clone()) - .flex_initial() - .map(|this| match self.current_side { - PanelSide::Left | PanelSide::Right => this.h_full().w(current_size), - PanelSide::Bottom => this, - }) - .map(|this| match self.current_side { - PanelSide::Left => this.border_r(), - PanelSide::Right => this.border_l(), - PanelSide::Bottom => this.border_b().w_full().h(current_size), - }) - .bg(cx.theme().colors().surface_background) - .border_color(cx.theme().colors().border) - .children(self.children) - } } -impl ParentComponent for Panel { +impl ParentElement for Panel { fn children_mut(&mut self) -> &mut SmallVec<[AnyElement; 2]> { &mut self.children } @@ -126,11 +130,11 @@ pub use stories::*; mod stories { use super::*; use crate::{Label, Story}; - use gpui::{Div, InteractiveComponent, Render}; + use gpui::{Div, InteractiveElement, Render}; pub struct PanelStory; - impl Render for PanelStory { + impl Render for PanelStory { type Element = Div; fn render(&mut self, cx: &mut ViewContext) -> Self::Element { diff --git a/crates/ui2/src/components/player_stack.rs b/crates/ui2/src/components/player_stack.rs index b883f76d35..1af3ab5c5a 100644 --- a/crates/ui2/src/components/player_stack.rs +++ b/crates/ui2/src/components/player_stack.rs @@ -1,19 +1,17 @@ +use gpui::{Div, RenderOnce}; + use crate::prelude::*; use crate::{Avatar, Facepile, PlayerWithCallStatus}; -#[derive(Component)] +#[derive(RenderOnce)] pub struct PlayerStack { player_with_call_status: PlayerWithCallStatus, } -impl PlayerStack { - pub fn new(player_with_call_status: PlayerWithCallStatus) -> Self { - Self { - player_with_call_status, - } - } +impl Component for PlayerStack { + type Rendered = Div; - fn render(self, _view: &mut V, cx: &mut ViewContext) -> impl Element { + fn render(self, view: &mut V, cx: &mut ViewContext) -> Self::Rendered { let player = self.player_with_call_status.get_player(); let followers = self @@ -59,3 +57,11 @@ impl PlayerStack { ) } } + +impl PlayerStack { + pub fn new(player_with_call_status: PlayerWithCallStatus) -> Self { + Self { + player_with_call_status, + } + } +} diff --git a/crates/ui2/src/components/tab.rs b/crates/ui2/src/components/tab.rs index 7238dbc337..2d7dadac1a 100644 --- a/crates/ui2/src/components/tab.rs +++ b/crates/ui2/src/components/tab.rs @@ -1,8 +1,8 @@ use crate::prelude::*; use crate::{Icon, IconElement, Label, TextColor}; -use gpui::{prelude::*, red, Div, ElementId, Render, View}; +use gpui::{prelude::*, red, Div, ElementId, Render, RenderOnce, Stateful, View}; -#[derive(Component, Clone)] +#[derive(RenderOnce, Clone)] pub struct Tab { id: ElementId, title: String, @@ -20,7 +20,7 @@ struct TabDragState { title: String, } -impl Render for TabDragState { +impl Render for TabDragState { type Element = Div; fn render(&mut self, cx: &mut ViewContext) -> Self::Element { @@ -28,65 +28,10 @@ impl Render for TabDragState { } } -impl Tab { - pub fn new(id: impl Into) -> Self { - Self { - id: id.into(), - title: "untitled".to_string(), - icon: None, - current: false, - dirty: false, - fs_status: FileSystemStatus::None, - git_status: GitStatus::None, - diagnostic_status: DiagnosticStatus::None, - close_side: IconSide::Right, - } - } +impl Component for Tab { + type Rendered = Stateful>; - pub fn current(mut self, current: bool) -> Self { - self.current = current; - self - } - - pub fn title(mut self, title: String) -> Self { - self.title = title; - self - } - - pub fn icon(mut self, icon: I) -> Self - where - I: Into>, - { - self.icon = icon.into(); - self - } - - pub fn dirty(mut self, dirty: bool) -> Self { - self.dirty = dirty; - self - } - - pub fn fs_status(mut self, fs_status: FileSystemStatus) -> Self { - self.fs_status = fs_status; - self - } - - pub fn git_status(mut self, git_status: GitStatus) -> Self { - self.git_status = git_status; - self - } - - pub fn diagnostic_status(mut self, diagnostic_status: DiagnosticStatus) -> Self { - self.diagnostic_status = diagnostic_status; - self - } - - pub fn close_side(mut self, close_side: IconSide) -> Self { - self.close_side = close_side; - self - } - - fn render(self, _view: &mut V, cx: &mut ViewContext) -> impl Element { + fn render(self, view: &mut V, cx: &mut ViewContext) -> Self::Rendered { let has_fs_conflict = self.fs_status == FileSystemStatus::Conflict; let is_deleted = self.fs_status == FileSystemStatus::Deleted; @@ -164,6 +109,65 @@ impl Tab { } } +impl Tab { + pub fn new(id: impl Into) -> Self { + Self { + id: id.into(), + title: "untitled".to_string(), + icon: None, + current: false, + dirty: false, + fs_status: FileSystemStatus::None, + git_status: GitStatus::None, + diagnostic_status: DiagnosticStatus::None, + close_side: IconSide::Right, + } + } + + pub fn current(mut self, current: bool) -> Self { + self.current = current; + self + } + + pub fn title(mut self, title: String) -> Self { + self.title = title; + self + } + + pub fn icon(mut self, icon: I) -> Self + where + I: Into>, + { + self.icon = icon.into(); + self + } + + pub fn dirty(mut self, dirty: bool) -> Self { + self.dirty = dirty; + self + } + + pub fn fs_status(mut self, fs_status: FileSystemStatus) -> Self { + self.fs_status = fs_status; + self + } + + pub fn git_status(mut self, git_status: GitStatus) -> Self { + self.git_status = git_status; + self + } + + pub fn diagnostic_status(mut self, diagnostic_status: DiagnosticStatus) -> Self { + self.diagnostic_status = diagnostic_status; + self + } + + pub fn close_side(mut self, close_side: IconSide) -> Self { + self.close_side = close_side; + self + } +} + #[cfg(feature = "stories")] pub use stories::*; @@ -175,7 +179,7 @@ mod stories { pub struct TabStory; - impl Render for TabStory { + impl Render for TabStory { type Element = Div; fn render(&mut self, cx: &mut ViewContext) -> Self::Element { diff --git a/crates/ui2/src/components/toast.rs b/crates/ui2/src/components/toast.rs index a40f3cace4..153f9c7fdb 100644 --- a/crates/ui2/src/components/toast.rs +++ b/crates/ui2/src/components/toast.rs @@ -1,6 +1,6 @@ use crate::prelude::*; -use gpui::Element; -use gpui::{prelude::*, AnyElement}; +use gpui::{prelude::*, AnyElement, RenderOnce}; +use gpui::{Div, Element}; use smallvec::SmallVec; #[derive(Default, Debug, PartialEq, Eq, Clone, Copy)] @@ -22,41 +22,37 @@ pub enum ToastOrigin { /// they are actively showing the a process in progress. /// /// Only one toast may be visible at a time. -#[derive(Element)] +#[derive(RenderOnce)] pub struct Toast { origin: ToastOrigin, children: SmallVec<[AnyElement; 2]>, } -// impl Element for Toast { -// type State = Option>; +impl Component for Toast { + type Rendered = Div; -// fn element_id(&self) -> Option { -// None -// } + fn render(self, view: &mut V, cx: &mut ViewContext) -> Self::Rendered { + let mut div = div(); -// fn layout( -// &mut self, -// view_state: &mut V, -// _element_state: Option, -// cx: &mut ViewContext, -// ) -> (gpui::LayoutId, Self::State) { -// let mut element = self.render(view_state, cx).into_any(); -// let layout_id = element.layout(view_state, cx); -// (layout_id, Some(element)) -// } + if self.origin == ToastOrigin::Bottom { + div = div.right_1_2(); + } else { + div = div.right_2(); + } -// fn paint( -// self, -// bounds: gpui::Bounds, -// view_state: &mut V, -// element: &mut Self::State, -// cx: &mut ViewContext, -// ) { -// let element = element.take().unwrap(); -// element.paint(view_state, cx); -// } -// } + div.z_index(5) + .absolute() + .bottom_9() + .flex() + .py_1() + .px_1p5() + .rounded_lg() + .shadow_md() + .overflow_hidden() + .bg(cx.theme().colors().elevated_surface_background) + .children(self.children) + } +} impl Toast { pub fn new(origin: ToastOrigin) -> Self { @@ -89,7 +85,7 @@ impl Toast { } } -impl ParentComponent for Toast { +impl ParentElement for Toast { fn children_mut(&mut self) -> &mut SmallVec<[AnyElement; 2]> { &mut self.children } @@ -108,7 +104,7 @@ mod stories { pub struct ToastStory; - impl Render for ToastStory { + impl Render for ToastStory { type Element = Div; fn render(&mut self, cx: &mut ViewContext) -> Self::Element { diff --git a/crates/ui2/src/components/toggle.rs b/crates/ui2/src/components/toggle.rs index 5919a19fc6..afd03c8b89 100644 --- a/crates/ui2/src/components/toggle.rs +++ b/crates/ui2/src/components/toggle.rs @@ -1,4 +1,4 @@ -use gpui::{div, Component, Element, ParentComponent}; +use gpui::{div, Element, ParentElement}; use crate::{Icon, IconElement, IconSize, TextColor}; diff --git a/crates/ui2/src/components/tool_divider.rs b/crates/ui2/src/components/tool_divider.rs index ca443ccb7f..4b59ffe2d8 100644 --- a/crates/ui2/src/components/tool_divider.rs +++ b/crates/ui2/src/components/tool_divider.rs @@ -1,8 +1,17 @@ use crate::prelude::*; +use gpui::{Div, RenderOnce}; -#[derive(Component)] +#[derive(RenderOnce)] pub struct ToolDivider; +impl Component for ToolDivider { + type Rendered = Div; + + fn render(self, view: &mut V, cx: &mut ViewContext) -> Self::Rendered { + div().w_px().h_3().bg(cx.theme().colors().border) + } +} + impl ToolDivider { pub fn new() -> Self { Self diff --git a/crates/ui2/src/components/tooltip.rs b/crates/ui2/src/components/tooltip.rs index a8dae6c97f..33fadf122f 100644 --- a/crates/ui2/src/components/tooltip.rs +++ b/crates/ui2/src/components/tooltip.rs @@ -1,4 +1,4 @@ -use gpui::{overlay, Action, AnyView, Overlay, Render, VisualContext}; +use gpui::{overlay, Action, AnyView, Overlay, Render, RenderOnce, VisualContext}; use settings2::Settings; use theme2::{ActiveTheme, ThemeSettings}; @@ -67,7 +67,7 @@ impl Tooltip { } } -impl Render for Tooltip { +impl Render for Tooltip { type Element = Overlay; fn render(&mut self, cx: &mut ViewContext) -> Self::Element { diff --git a/crates/ui2/src/prelude.rs b/crates/ui2/src/prelude.rs index d4abb78c21..876fec555e 100644 --- a/crates/ui2/src/prelude.rs +++ b/crates/ui2/src/prelude.rs @@ -1,8 +1,8 @@ use gpui::rems; use gpui::Rems; pub use gpui::{ - div, Component, Element, ElementId, InteractiveComponent, ParentComponent, SharedString, - Styled, ViewContext, WindowContext, + div, Component, Element, ElementId, InteractiveElement, ParentElement, SharedString, Styled, + ViewContext, WindowContext, }; pub use crate::elevation::*; diff --git a/crates/ui2/src/static_data.rs b/crates/ui2/src/static_data.rs index bb81d6230f..37649ff8c5 100644 --- a/crates/ui2/src/static_data.rs +++ b/crates/ui2/src/static_data.rs @@ -745,11 +745,11 @@ pub fn hello_world_rust_editor_example(cx: &mut ViewContext) -> Edit PathBuf::from_str("crates/ui/src/static_data.rs").unwrap(), vec![Symbol(vec![ HighlightedText { - text: "fn ".to_string(), + text: "fn ".into(), color: cx.theme().syntax_color("keyword"), }, HighlightedText { - text: "main".to_string(), + text: "main".into(), color: cx.theme().syntax_color("function"), }, ])], @@ -779,15 +779,15 @@ pub fn hello_world_rust_buffer_rows(cx: &AppContext) -> Vec { line: Some(HighlightedLine { highlighted_texts: vec![ HighlightedText { - text: "fn ".to_string(), + text: "fn ".into(), color: cx.theme().syntax_color("keyword"), }, HighlightedText { - text: "main".to_string(), + text: "main".into(), color: cx.theme().syntax_color("function"), }, HighlightedText { - text: "() {".to_string(), + text: "() {".into(), color: cx.theme().colors().text, }, ], @@ -803,7 +803,7 @@ pub fn hello_world_rust_buffer_rows(cx: &AppContext) -> Vec { line: Some(HighlightedLine { highlighted_texts: vec![HighlightedText { text: " // Statements here are executed when the compiled binary is called." - .to_string(), + .into(), color: cx.theme().syntax_color("comment"), }], }), @@ -826,7 +826,7 @@ pub fn hello_world_rust_buffer_rows(cx: &AppContext) -> Vec { current: false, line: Some(HighlightedLine { highlighted_texts: vec![HighlightedText { - text: " // Print text to the console.".to_string(), + text: " // Print text to the console.".into(), color: cx.theme().syntax_color("comment"), }], }), @@ -841,15 +841,15 @@ pub fn hello_world_rust_buffer_rows(cx: &AppContext) -> Vec { line: Some(HighlightedLine { highlighted_texts: vec![ HighlightedText { - text: " println!(".to_string(), + text: " println!(".into(), color: cx.theme().colors().text, }, HighlightedText { - text: "\"Hello, world!\"".to_string(), + text: "\"Hello, world!\"".into(), color: cx.theme().syntax_color("string"), }, HighlightedText { - text: ");".to_string(), + text: ");".into(), color: cx.theme().colors().text, }, ], @@ -864,7 +864,7 @@ pub fn hello_world_rust_buffer_rows(cx: &AppContext) -> Vec { current: false, line: Some(HighlightedLine { highlighted_texts: vec![HighlightedText { - text: "}".to_string(), + text: "}".into(), color: cx.theme().colors().text, }], }), @@ -882,11 +882,11 @@ pub fn hello_world_rust_editor_with_status_example(cx: &mut ViewContext Vec Vec Vec Vec Vec Vec Vec Vec Buffer { Buffer::new("terminal") - .set_title("zed — fish".to_string()) + .set_title(Some("zed — fish".into())) .set_rows(Some(BufferRows { show_line_numbers: false, rows: terminal_buffer_rows(cx), @@ -1060,31 +1060,31 @@ pub fn terminal_buffer_rows(cx: &AppContext) -> Vec { line: Some(HighlightedLine { highlighted_texts: vec![ HighlightedText { - text: "maxdeviant ".to_string(), + text: "maxdeviant ".into(), color: cx.theme().syntax_color("keyword"), }, HighlightedText { - text: "in ".to_string(), + text: "in ".into(), color: cx.theme().colors().text, }, HighlightedText { - text: "profaned-capital ".to_string(), + text: "profaned-capital ".into(), color: cx.theme().syntax_color("function"), }, HighlightedText { - text: "in ".to_string(), + text: "in ".into(), color: cx.theme().colors().text, }, HighlightedText { - text: "~/p/zed ".to_string(), + text: "~/p/zed ".into(), color: cx.theme().syntax_color("function"), }, HighlightedText { - text: "on ".to_string(), + text: "on ".into(), color: cx.theme().colors().text, }, HighlightedText { - text: " gpui2-ui ".to_string(), + text: " gpui2-ui ".into(), color: cx.theme().syntax_color("keyword"), }, ], @@ -1099,7 +1099,7 @@ pub fn terminal_buffer_rows(cx: &AppContext) -> Vec { current: false, line: Some(HighlightedLine { highlighted_texts: vec![HighlightedText { - text: "λ ".to_string(), + text: "λ ".into(), color: cx.theme().syntax_color("string"), }], }), diff --git a/crates/ui2/src/story.rs b/crates/ui2/src/story.rs index b5fef606b7..8a482e155a 100644 --- a/crates/ui2/src/story.rs +++ b/crates/ui2/src/story.rs @@ -15,23 +15,29 @@ impl Story { .bg(cx.theme().colors().background) } - pub fn title(cx: &mut ViewContext, title: &str) -> impl Element { + pub fn title( + cx: &mut ViewContext, + title: impl Into, + ) -> impl Element { div() .text_xl() .text_color(cx.theme().colors().text) - .child(title.to_owned()) + .child(title.into()) } pub fn title_for(cx: &mut ViewContext) -> impl Element { Self::title(cx, std::any::type_name::()) } - pub fn label(cx: &mut ViewContext, label: &str) -> impl Element { + pub fn label( + cx: &mut ViewContext, + label: impl Into, + ) -> impl Element { div() .mt_4() .mb_2() .text_xs() .text_color(cx.theme().colors().text) - .child(label.to_owned()) + .child(label.into()) } } diff --git a/crates/ui2/src/to_extract/assistant_panel.rs b/crates/ui2/src/to_extract/assistant_panel.rs index 708d271b5b..33f4e4b208 100644 --- a/crates/ui2/src/to_extract/assistant_panel.rs +++ b/crates/ui2/src/to_extract/assistant_panel.rs @@ -1,27 +1,17 @@ use crate::prelude::*; use crate::{Icon, IconButton, Label, Panel, PanelSide}; -use gpui::{prelude::*, rems, AbsoluteLength}; +use gpui::{prelude::*, rems, AbsoluteLength, RenderOnce}; -#[derive(Component)] +#[derive(RenderOnce)] pub struct AssistantPanel { id: ElementId, current_side: PanelSide, } -impl AssistantPanel { - pub fn new(id: impl Into) -> Self { - Self { - id: id.into(), - current_side: PanelSide::default(), - } - } +impl Component for AssistantPanel { + type Rendered = Panel; - pub fn side(mut self, side: PanelSide) -> Self { - self.current_side = side; - self - } - - fn render(self, view: &mut V, cx: &mut ViewContext) -> impl Element { + fn render(self, view: &mut V, cx: &mut ViewContext) -> Self::Rendered { Panel::new(self.id.clone(), cx) .children(vec![div() .flex() @@ -64,12 +54,26 @@ impl AssistantPanel { .overflow_y_scroll() .child(Label::new("Is this thing on?")), ) - .render()]) + .into_any()]) .side(self.current_side) .width(AbsoluteLength::Rems(rems(32.))) } } +impl AssistantPanel { + pub fn new(id: impl Into) -> Self { + Self { + id: id.into(), + current_side: PanelSide::default(), + } + } + + pub fn side(mut self, side: PanelSide) -> Self { + self.current_side = side; + self + } +} + #[cfg(feature = "stories")] pub use stories::*; @@ -80,7 +84,7 @@ mod stories { use gpui::{Div, Render}; pub struct AssistantPanelStory; - impl Render for AssistantPanelStory { + impl Render for AssistantPanelStory { type Element = Div; fn render(&mut self, cx: &mut ViewContext) -> Self::Element { diff --git a/crates/ui2/src/to_extract/breadcrumb.rs b/crates/ui2/src/to_extract/breadcrumb.rs index 6990890054..532776fd7f 100644 --- a/crates/ui2/src/to_extract/breadcrumb.rs +++ b/crates/ui2/src/to_extract/breadcrumb.rs @@ -1,30 +1,21 @@ use crate::{h_stack, prelude::*, HighlightedText}; -use gpui::{prelude::*, Div}; +use gpui::{prelude::*, Div, Stateful}; use std::path::PathBuf; #[derive(Clone)] pub struct Symbol(pub Vec); -#[derive(Component)] +#[derive(RenderOnce)] pub struct Breadcrumb { path: PathBuf, symbols: Vec, } -impl Breadcrumb { - pub fn new(path: PathBuf, symbols: Vec) -> Self { - Self { path, symbols } - } +impl Component for Breadcrumb { + type Rendered = Stateful>; - fn render_separator(&self, cx: &WindowContext) -> Div { - div() - .child(" › ") - .text_color(cx.theme().colors().text_muted) - } - - fn render(self, view_state: &mut V, cx: &mut ViewContext) -> impl Element { + fn render(self, view_state: &mut V, cx: &mut ViewContext) -> Self::Rendered { let symbols_len = self.symbols.len(); - h_stack() .id("breadcrumb") .px_1() @@ -33,7 +24,9 @@ impl Breadcrumb { .rounded_md() .hover(|style| style.bg(cx.theme().colors().ghost_element_hover)) .active(|style| style.bg(cx.theme().colors().ghost_element_active)) - .child(self.path.clone().to_str().unwrap().to_string()) + .child(SharedString::from( + self.path.clone().to_str().unwrap().to_string(), + )) .child(if !self.symbols.is_empty() { self.render_separator(cx) } else { @@ -64,6 +57,18 @@ impl Breadcrumb { } } +impl Breadcrumb { + pub fn new(path: PathBuf, symbols: Vec) -> Self { + Self { path, symbols } + } + + fn render_separator(&self, cx: &WindowContext) -> Div { + div() + .child(" › ") + .text_color(cx.theme().colors().text_muted) + } +} + #[cfg(feature = "stories")] pub use stories::*; @@ -76,7 +81,7 @@ mod stories { pub struct BreadcrumbStory; - impl Render for BreadcrumbStory { + impl Render for BreadcrumbStory { type Element = Div; fn render(&mut self, cx: &mut ViewContext) -> Self::Element { @@ -88,21 +93,21 @@ mod stories { vec![ Symbol(vec![ HighlightedText { - text: "impl ".to_string(), + text: "impl ".into(), color: cx.theme().syntax_color("keyword"), }, HighlightedText { - text: "BreadcrumbStory".to_string(), + text: "BreadcrumbStory".into(), color: cx.theme().syntax_color("function"), }, ]), Symbol(vec![ HighlightedText { - text: "fn ".to_string(), + text: "fn ".into(), color: cx.theme().syntax_color("keyword"), }, HighlightedText { - text: "render".to_string(), + text: "render".into(), color: cx.theme().syntax_color("function"), }, ]), diff --git a/crates/ui2/src/to_extract/buffer.rs b/crates/ui2/src/to_extract/buffer.rs index c8223daf2f..ac4665614b 100644 --- a/crates/ui2/src/to_extract/buffer.rs +++ b/crates/ui2/src/to_extract/buffer.rs @@ -1,4 +1,4 @@ -use gpui::{Hsla, WindowContext}; +use gpui::{Div, Hsla, RenderOnce, WindowContext}; use crate::prelude::*; use crate::{h_stack, v_stack, Icon, IconElement}; @@ -11,7 +11,7 @@ pub struct PlayerCursor { #[derive(Default, PartialEq, Clone)] pub struct HighlightedText { - pub text: String, + pub text: SharedString, pub color: Hsla, } @@ -107,7 +107,7 @@ impl BufferRow { } } -#[derive(Component, Clone)] +#[derive(RenderOnce, Clone)] pub struct Buffer { id: ElementId, rows: Option, @@ -117,6 +117,21 @@ pub struct Buffer { path: Option, } +impl Component for Buffer { + type Rendered = Div; + + fn render(self, view: &mut V, cx: &mut ViewContext) -> Self::Rendered { + let rows = self.render_rows(cx); + + v_stack() + .flex_1() + .w_full() + .h_full() + .bg(cx.theme().colors().editor_background) + .children(rows) + } +} + impl Buffer { pub fn new(id: impl Into) -> Self { Self { @@ -186,7 +201,7 @@ impl Buffer { h_stack().justify_end().px_0p5().w_3().child( div() .text_color(line_number_color) - .child(row.line_number.to_string()), + .child(SharedString::from(row.line_number.to_string())), ), ) }) @@ -239,7 +254,7 @@ mod stories { pub struct BufferStory; - impl Render for BufferStory { + impl Render for BufferStory { type Element = Div; fn render(&mut self, cx: &mut ViewContext) -> Self::Element { diff --git a/crates/ui2/src/to_extract/buffer_search.rs b/crates/ui2/src/to_extract/buffer_search.rs index 996ac6d253..3a89465911 100644 --- a/crates/ui2/src/to_extract/buffer_search.rs +++ b/crates/ui2/src/to_extract/buffer_search.rs @@ -1,7 +1,6 @@ -use gpui::{Div, Render, View, VisualContext}; - use crate::prelude::*; use crate::{h_stack, Icon, IconButton, Input, TextColor}; +use gpui::{Div, Render, RenderOnce, View, VisualContext}; #[derive(Clone)] pub struct BufferSearch { @@ -26,7 +25,7 @@ impl BufferSearch { } } -impl Render for BufferSearch { +impl Render for BufferSearch { type Element = Div; fn render(&mut self, cx: &mut ViewContext) -> Div { diff --git a/crates/ui2/src/to_extract/chat_panel.rs b/crates/ui2/src/to_extract/chat_panel.rs index 528e40f903..00655364bf 100644 --- a/crates/ui2/src/to_extract/chat_panel.rs +++ b/crates/ui2/src/to_extract/chat_panel.rs @@ -1,27 +1,17 @@ use crate::{prelude::*, Icon, IconButton, Input, Label}; use chrono::NaiveDateTime; -use gpui::prelude::*; +use gpui::{prelude::*, Div, Stateful}; -#[derive(Component)] +#[derive(RenderOnce)] pub struct ChatPanel { element_id: ElementId, messages: Vec, } -impl ChatPanel { - pub fn new(element_id: impl Into) -> Self { - Self { - element_id: element_id.into(), - messages: Vec::new(), - } - } +impl Component for ChatPanel { + type Rendered = Stateful>; - pub fn messages(mut self, messages: Vec) -> Self { - self.messages = messages; - self - } - - fn render(self, _view: &mut V, cx: &mut ViewContext) -> impl Element { + fn render(self, view: &mut V, cx: &mut ViewContext) -> Self::Rendered { div() .id(self.element_id.clone()) .flex() @@ -67,23 +57,31 @@ impl ChatPanel { } } -#[derive(Component)] +impl ChatPanel { + pub fn new(element_id: impl Into) -> Self { + Self { + element_id: element_id.into(), + messages: Vec::new(), + } + } + + pub fn messages(mut self, messages: Vec) -> Self { + self.messages = messages; + self + } +} + +#[derive(RenderOnce)] pub struct ChatMessage { author: String, text: String, sent_at: NaiveDateTime, } -impl ChatMessage { - pub fn new(author: String, text: String, sent_at: NaiveDateTime) -> Self { - Self { - author, - text, - sent_at, - } - } +impl Component for ChatMessage { + type Rendered = Div; - fn render(self, _view: &mut V, cx: &mut ViewContext) -> impl Element { + fn render(self, view: &mut V, cx: &mut ViewContext) -> Self::Rendered { div() .flex() .flex_col() @@ -101,6 +99,16 @@ impl ChatMessage { } } +impl ChatMessage { + pub fn new(author: String, text: String, sent_at: NaiveDateTime) -> Self { + Self { + author, + text, + sent_at, + } + } +} + #[cfg(feature = "stories")] pub use stories::*; @@ -115,7 +123,7 @@ mod stories { pub struct ChatPanelStory; - impl Render for ChatPanelStory { + impl Render for ChatPanelStory { type Element = Div; fn render(&mut self, cx: &mut ViewContext) -> Self::Element { diff --git a/crates/ui2/src/to_extract/collab_panel.rs b/crates/ui2/src/to_extract/collab_panel.rs index 55efbb5ec5..e5fafa62f1 100644 --- a/crates/ui2/src/to_extract/collab_panel.rs +++ b/crates/ui2/src/to_extract/collab_panel.rs @@ -2,19 +2,17 @@ use crate::{ prelude::*, static_collab_panel_channels, static_collab_panel_current_call, v_stack, Icon, List, ListHeader, Toggle, }; -use gpui::prelude::*; +use gpui::{prelude::*, Div, Stateful}; -#[derive(Component)] +#[derive(RenderOnce)] pub struct CollabPanel { id: ElementId, } -impl CollabPanel { - pub fn new(id: impl Into) -> Self { - Self { id: id.into() } - } +impl Component for CollabPanel { + type Rendered = Stateful>; - fn render(self, _view: &mut V, cx: &mut ViewContext) -> impl Element { + fn render(self, view: &mut V, cx: &mut ViewContext) -> Self::Rendered { v_stack() .id(self.id.clone()) .h_full() @@ -86,6 +84,12 @@ impl CollabPanel { } } +impl CollabPanel { + pub fn new(id: impl Into) -> Self { + Self { id: id.into() } + } +} + #[cfg(feature = "stories")] pub use stories::*; @@ -97,7 +101,7 @@ mod stories { pub struct CollabPanelStory; - impl Render for CollabPanelStory { + impl Render for CollabPanelStory { type Element = Div; fn render(&mut self, cx: &mut ViewContext) -> Self::Element { diff --git a/crates/ui2/src/to_extract/command_palette.rs b/crates/ui2/src/to_extract/command_palette.rs index 353e1daac8..032e3bc639 100644 --- a/crates/ui2/src/to_extract/command_palette.rs +++ b/crates/ui2/src/to_extract/command_palette.rs @@ -1,17 +1,15 @@ use crate::prelude::*; use crate::{example_editor_actions, OrderMethod, Palette}; -#[derive(Component)] +#[derive(RenderOnce)] pub struct CommandPalette { id: ElementId, } -impl CommandPalette { - pub fn new(id: impl Into) -> Self { - Self { id: id.into() } - } +impl Component for CommandPalette { + type Rendered = Stateful>; - fn render(self, _view: &mut V, cx: &mut ViewContext) -> impl Element { + fn render(self, view: &mut V, cx: &mut ViewContext) -> Self::Rendered { div().id(self.id.clone()).child( Palette::new("palette") .items(example_editor_actions()) @@ -22,6 +20,13 @@ impl CommandPalette { } } +impl CommandPalette { + pub fn new(id: impl Into) -> Self { + Self { id: id.into() } + } +} + +use gpui::{Div, RenderOnce, Stateful}; #[cfg(feature = "stories")] pub use stories::*; @@ -35,7 +40,7 @@ mod stories { pub struct CommandPaletteStory; - impl Render for CommandPaletteStory { + impl Render for CommandPaletteStory { type Element = Div; fn render(&mut self, cx: &mut ViewContext) -> Self::Element { diff --git a/crates/ui2/src/to_extract/copilot.rs b/crates/ui2/src/to_extract/copilot.rs index b07c562c13..d0b11a3330 100644 --- a/crates/ui2/src/to_extract/copilot.rs +++ b/crates/ui2/src/to_extract/copilot.rs @@ -1,39 +1,42 @@ use crate::{prelude::*, Button, Label, Modal, TextColor}; -#[derive(Component)] +#[derive(RenderOnce)] pub struct CopilotModal { id: ElementId, } +impl Component for CopilotModal { + type Rendered = Stateful>; + + fn render(self, view: &mut V, cx: &mut ViewContext) -> Self::Rendered { + div().id(self.id.clone()).child( + Modal::new("some-id") + .title("Connect Copilot to Zed") + .child(Label::new("You can update your settings or sign out from the Copilot menu in the status bar.").color(TextColor::Muted)) + .primary_action(Button::new("Connect to Github").variant(ButtonVariant::Filled)), + ) + } +} + impl CopilotModal { pub fn new(id: impl Into) -> Self { Self { id: id.into() } } - - fn render(self, _view: &mut V, cx: &mut ViewContext) -> impl Element { - div().id(self.id.clone()).child( - Modal::new("some-id") - .title("Connect Copilot to Zed") - .child(Label::new("You can update your settings or sign out from the Copilot menu in the status bar.").color(TextColor::Muted)) - .primary_action(Button::new("Connect to Github").variant(ButtonVariant::Filled)), - ) - } } +use gpui::{Div, RenderOnce, Stateful}; #[cfg(feature = "stories")] pub use stories::*; #[cfg(feature = "stories")] mod stories { - use gpui::{Div, Render}; - - use crate::Story; - use super::*; + use crate::Story; + use gpui::{Div, Render}; pub struct CopilotModalStory; - impl Render for CopilotModalStory { + impl Render for CopilotModalStory { type Element = Div; fn render(&mut self, cx: &mut ViewContext) -> Self::Element { diff --git a/crates/ui2/src/to_extract/editor_pane.rs b/crates/ui2/src/to_extract/editor_pane.rs index bd34c22805..799716c270 100644 --- a/crates/ui2/src/to_extract/editor_pane.rs +++ b/crates/ui2/src/to_extract/editor_pane.rs @@ -1,6 +1,6 @@ use std::path::PathBuf; -use gpui::{Div, Render, View, VisualContext}; +use gpui::{Div, Render, RenderOnce, View, VisualContext}; use crate::prelude::*; use crate::{ @@ -47,7 +47,7 @@ impl EditorPane { } } -impl Render for EditorPane { +impl Render for EditorPane { type Element = Div; fn render(&mut self, cx: &mut ViewContext) -> Div { diff --git a/crates/ui2/src/to_extract/language_selector.rs b/crates/ui2/src/to_extract/language_selector.rs index e05145e8c8..c6a8457e0b 100644 --- a/crates/ui2/src/to_extract/language_selector.rs +++ b/crates/ui2/src/to_extract/language_selector.rs @@ -1,11 +1,36 @@ use crate::prelude::*; use crate::{OrderMethod, Palette, PaletteItem}; -#[derive(Component)] +#[derive(RenderOnce)] pub struct LanguageSelector { id: ElementId, } +impl Component for LanguageSelector { + type Rendered = Stateful>; + + fn render(self, view: &mut V, cx: &mut ViewContext) -> Self::Rendered { + div().id(self.id.clone()).child( + Palette::new("palette") + .items(vec![ + PaletteItem::new("C"), + PaletteItem::new("C++"), + PaletteItem::new("CSS"), + PaletteItem::new("Elixir"), + PaletteItem::new("Elm"), + PaletteItem::new("ERB"), + PaletteItem::new("Rust (current)"), + PaletteItem::new("Scheme"), + PaletteItem::new("TOML"), + PaletteItem::new("TypeScript"), + ]) + .placeholder("Select a language...") + .empty_string("No matches") + .default_order(OrderMethod::Ascending), + ) + } +} + impl LanguageSelector { pub fn new(id: impl Into) -> Self { Self { id: id.into() } @@ -33,6 +58,7 @@ impl LanguageSelector { } } +use gpui::{Div, RenderOnce, Stateful}; #[cfg(feature = "stories")] pub use stories::*; @@ -44,7 +70,7 @@ mod stories { pub struct LanguageSelectorStory; - impl Render for LanguageSelectorStory { + impl Render for LanguageSelectorStory { type Element = Div; fn render(&mut self, cx: &mut ViewContext) -> Self::Element { diff --git a/crates/ui2/src/to_extract/multi_buffer.rs b/crates/ui2/src/to_extract/multi_buffer.rs index 186303365b..7967a1e590 100644 --- a/crates/ui2/src/to_extract/multi_buffer.rs +++ b/crates/ui2/src/to_extract/multi_buffer.rs @@ -1,17 +1,15 @@ use crate::prelude::*; use crate::{v_stack, Buffer, Icon, IconButton, Label}; -#[derive(Component)] +#[derive(RenderOnce)] pub struct MultiBuffer { buffers: Vec, } -impl MultiBuffer { - pub fn new(buffers: Vec) -> Self { - Self { buffers } - } +impl Component for MultiBuffer { + type Rendered = Div; - fn render(self, _view: &mut V, cx: &mut ViewContext) -> impl Element { + fn render(self, view: &mut V, cx: &mut ViewContext) -> Self::Rendered { v_stack() .w_full() .h_full() @@ -33,6 +31,13 @@ impl MultiBuffer { } } +impl MultiBuffer { + pub fn new(buffers: Vec) -> Self { + Self { buffers } + } +} + +use gpui::{Div, RenderOnce}; #[cfg(feature = "stories")] pub use stories::*; @@ -44,7 +49,7 @@ mod stories { pub struct MultiBufferStory; - impl Render for MultiBufferStory { + impl Render for MultiBufferStory { type Element = Div; fn render(&mut self, cx: &mut ViewContext) -> Self::Element { diff --git a/crates/ui2/src/to_extract/notifications_panel.rs b/crates/ui2/src/to_extract/notifications_panel.rs index fa7f8c2bd5..f45b12f0c2 100644 --- a/crates/ui2/src/to_extract/notifications_panel.rs +++ b/crates/ui2/src/to_extract/notifications_panel.rs @@ -3,19 +3,17 @@ use crate::{ v_stack, Avatar, ButtonOrIconButton, ClickHandler, Icon, IconElement, Label, LineHeightStyle, ListHeader, ListHeaderMeta, ListSeparator, PublicPlayer, TextColor, UnreadIndicator, }; -use gpui::prelude::*; +use gpui::{prelude::*, Div, Stateful}; -#[derive(Component)] +#[derive(RenderOnce)] pub struct NotificationsPanel { id: ElementId, } -impl NotificationsPanel { - pub fn new(id: impl Into) -> Self { - Self { id: id.into() } - } +impl Component for NotificationsPanel { + type Rendered = Stateful>; - fn render(self, _view: &mut V, cx: &mut ViewContext) -> impl Element { + fn render(self, view: &mut V, cx: &mut ViewContext) -> Self::Rendered { div() .id(self.id.clone()) .flex() @@ -56,6 +54,12 @@ impl NotificationsPanel { } } +impl NotificationsPanel { + pub fn new(id: impl Into) -> Self { + Self { id: id.into() } + } +} + pub struct NotificationAction { button: ButtonOrIconButton, tooltip: SharedString, @@ -102,7 +106,7 @@ impl Default for NotificationHandlers { } } -#[derive(Component)] +#[derive(RenderOnce)] pub struct Notification { id: ElementId, slot: ActorOrIcon, @@ -116,6 +120,85 @@ pub struct Notification { handlers: NotificationHandlers, } +impl Component for Notification { + type Rendered = Stateful>; + + fn render(self, view: &mut V, cx: &mut ViewContext) -> Self::Rendered { + div() + .relative() + .id(self.id.clone()) + .p_1() + .flex() + .flex_col() + .w_full() + .children( + Some( + div() + .absolute() + .left(px(3.0)) + .top_3() + .z_index(2) + .child(UnreadIndicator::new()), + ) + .filter(|_| self.unread), + ) + .child( + v_stack() + .z_index(1) + .gap_1() + .w_full() + .child( + h_stack() + .w_full() + .gap_2() + .child(self.render_slot(cx)) + .child(div().flex_1().child(Label::new(self.message.clone()))), + ) + .child( + h_stack() + .justify_between() + .child( + h_stack() + .gap_1() + .child( + Label::new(naive_format_distance_from_now( + self.date_received, + true, + true, + )) + .color(TextColor::Muted), + ) + .child(self.render_meta_items(cx)), + ) + .child(match (self.actions, self.action_taken) { + // Show nothing + (None, _) => div(), + // Show the taken_message + (Some(_), Some(action_taken)) => h_stack() + .children(action_taken.taken_message.0.map(|icon| { + IconElement::new(icon).color(crate::TextColor::Muted) + })) + .child( + Label::new(action_taken.taken_message.1.clone()) + .color(TextColor::Muted), + ), + // Show the actions + (Some(actions), None) => { + h_stack().children(actions.map(|action| match action.button { + ButtonOrIconButton::Button(button) => { + button.render_into_any() + } + ButtonOrIconButton::IconButton(icon_button) => { + icon_button.render_into_any() + } + })) + } + }), + ), + ) + } +} + impl Notification { fn new( id: ElementId, @@ -262,85 +345,10 @@ impl Notification { fn render_slot(&self, cx: &mut ViewContext) -> impl Element { match &self.slot { - ActorOrIcon::Actor(actor) => Avatar::new(actor.avatar.clone()).render(), - ActorOrIcon::Icon(icon) => IconElement::new(icon.clone()).render(), + ActorOrIcon::Actor(actor) => Avatar::new(actor.avatar.clone()).render_into_any(), + ActorOrIcon::Icon(icon) => IconElement::new(icon.clone()).render_into_any(), } } - - fn render(self, _view: &mut V, cx: &mut ViewContext) -> impl Element { - div() - .relative() - .id(self.id.clone()) - .p_1() - .flex() - .flex_col() - .w_full() - .children( - Some( - div() - .absolute() - .left(px(3.0)) - .top_3() - .z_index(2) - .child(UnreadIndicator::new()), - ) - .filter(|_| self.unread), - ) - .child( - v_stack() - .z_index(1) - .gap_1() - .w_full() - .child( - h_stack() - .w_full() - .gap_2() - .child(self.render_slot(cx)) - .child(div().flex_1().child(Label::new(self.message.clone()))), - ) - .child( - h_stack() - .justify_between() - .child( - h_stack() - .gap_1() - .child( - Label::new(naive_format_distance_from_now( - self.date_received, - true, - true, - )) - .color(TextColor::Muted), - ) - .child(self.render_meta_items(cx)), - ) - .child(match (self.actions, self.action_taken) { - // Show nothing - (None, _) => div(), - // Show the taken_message - (Some(_), Some(action_taken)) => h_stack() - .children(action_taken.taken_message.0.map(|icon| { - IconElement::new(icon).color(crate::TextColor::Muted) - })) - .child( - Label::new(action_taken.taken_message.1.clone()) - .color(TextColor::Muted), - ), - // Show the actions - (Some(actions), None) => { - h_stack().children(actions.map(|action| match action.button { - ButtonOrIconButton::Button(button) => { - Component::render(button) - } - ButtonOrIconButton::IconButton(icon_button) => { - Component::render(icon_button) - } - })) - } - }), - ), - ) - } } use chrono::NaiveDateTime; @@ -356,7 +364,7 @@ mod stories { pub struct NotificationsPanelStory; - impl Render for NotificationsPanelStory { + impl Render for NotificationsPanelStory { type Element = Div; fn render(&mut self, cx: &mut ViewContext) -> Self::Element { diff --git a/crates/ui2/src/to_extract/panes.rs b/crates/ui2/src/to_extract/panes.rs index 3b75d55d77..92bf87783f 100644 --- a/crates/ui2/src/to_extract/panes.rs +++ b/crates/ui2/src/to_extract/panes.rs @@ -1,4 +1,7 @@ -use gpui::{hsla, red, AnyElement, ElementId, ExternalPaths, Hsla, Length, Size, View}; +use gpui::{ + hsla, red, AnyElement, Div, ElementId, ExternalPaths, Hsla, Length, RenderOnce, Size, Stateful, + View, +}; use smallvec::SmallVec; use crate::prelude::*; @@ -10,7 +13,7 @@ pub enum SplitDirection { Vertical, } -#[derive(Component)] +#[derive(RenderOnce)] pub struct Pane { id: ElementId, size: Size, @@ -18,24 +21,10 @@ pub struct Pane { children: SmallVec<[AnyElement; 2]>, } -impl Pane { - pub fn new(id: impl Into, size: Size) -> Self { - // Fill is only here for debugging purposes, remove before release +impl Component for Pane { + type Rendered = Stateful>; - Self { - id: id.into(), - size, - fill: hsla(0.3, 0.3, 0.3, 1.), - children: SmallVec::new(), - } - } - - pub fn fill(mut self, fill: Hsla) -> Self { - self.fill = fill; - self - } - - fn render(self, view: &mut V, cx: &mut ViewContext) -> impl Element { + fn render(self, view: &mut V, cx: &mut ViewContext) -> Self::Rendered { div() .id(self.id.clone()) .flex() @@ -59,37 +48,41 @@ impl Pane { } } -impl ParentComponent for Pane { +impl Pane { + pub fn new(id: impl Into, size: Size) -> Self { + // Fill is only here for debugging purposes, remove before release + + Self { + id: id.into(), + size, + fill: hsla(0.3, 0.3, 0.3, 1.), + children: SmallVec::new(), + } + } + + pub fn fill(mut self, fill: Hsla) -> Self { + self.fill = fill; + self + } +} + +impl ParentElement for Pane { fn children_mut(&mut self) -> &mut SmallVec<[AnyElement; 2]> { &mut self.children } } -#[derive(Component)] +#[derive(RenderOnce)] pub struct PaneGroup { groups: Vec>, panes: Vec>, split_direction: SplitDirection, } -impl PaneGroup { - pub fn new_groups(groups: Vec>, split_direction: SplitDirection) -> Self { - Self { - groups, - panes: Vec::new(), - split_direction, - } - } +impl Component for PaneGroup { + type Rendered = Div; - pub fn new_panes(panes: Vec>, split_direction: SplitDirection) -> Self { - Self { - groups: Vec::new(), - panes, - split_direction, - } - } - - fn render(self, view: &mut V, cx: &mut ViewContext) -> impl Element { + fn render(self, view: &mut V, cx: &mut ViewContext) -> Self::Rendered { if !self.panes.is_empty() { let el = div() .flex() @@ -126,3 +119,21 @@ impl PaneGroup { unreachable!() } } + +impl PaneGroup { + pub fn new_groups(groups: Vec>, split_direction: SplitDirection) -> Self { + Self { + groups, + panes: Vec::new(), + split_direction, + } + } + + pub fn new_panes(panes: Vec>, split_direction: SplitDirection) -> Self { + Self { + groups: Vec::new(), + panes, + split_direction, + } + } +} diff --git a/crates/ui2/src/to_extract/project_panel.rs b/crates/ui2/src/to_extract/project_panel.rs index ccaaecf2ce..06735356c7 100644 --- a/crates/ui2/src/to_extract/project_panel.rs +++ b/crates/ui2/src/to_extract/project_panel.rs @@ -3,12 +3,50 @@ use crate::{ ListHeader, }; use gpui::prelude::*; +use gpui::Div; +use gpui::Stateful; -#[derive(Component)] +#[derive(RenderOnce)] pub struct ProjectPanel { id: ElementId, } +impl Component for ProjectPanel { + type Rendered = Stateful>; + + fn render(self, view: &mut V, cx: &mut ViewContext) -> Self::Rendered { + div() + .id(self.id.clone()) + .flex() + .flex_col() + .size_full() + .bg(cx.theme().colors().surface_background) + .child( + div() + .id("project-panel-contents") + .w_full() + .flex() + .flex_col() + .overflow_y_scroll() + .child( + List::new(static_project_panel_single_items()) + .header(ListHeader::new("FILES")) + .empty_message("No files in directory"), + ) + .child( + List::new(static_project_panel_project_items()) + .header(ListHeader::new("PROJECT")) + .empty_message("No folders in directory"), + ), + ) + .child( + Input::new("Find something...") + .value("buffe".to_string()) + .state(InteractionState::Focused), + ) + } +} + impl ProjectPanel { pub fn new(id: impl Into) -> Self { Self { id: id.into() } @@ -59,7 +97,7 @@ mod stories { pub struct ProjectPanelStory; - impl Render for ProjectPanelStory { + impl Render for ProjectPanelStory { type Element = Div; fn render(&mut self, cx: &mut ViewContext) -> Self::Element { diff --git a/crates/ui2/src/to_extract/recent_projects.rs b/crates/ui2/src/to_extract/recent_projects.rs index 2db7d94eac..3ebffb6bef 100644 --- a/crates/ui2/src/to_extract/recent_projects.rs +++ b/crates/ui2/src/to_extract/recent_projects.rs @@ -1,17 +1,15 @@ use crate::prelude::*; use crate::{OrderMethod, Palette, PaletteItem}; -#[derive(Component)] +#[derive(RenderOnce)] pub struct RecentProjects { id: ElementId, } -impl RecentProjects { - pub fn new(id: impl Into) -> Self { - Self { id: id.into() } - } +impl Component for RecentProjects { + type Rendered = Stateful>; - fn render(self, _view: &mut V, cx: &mut ViewContext) -> impl Element { + fn render(self, view: &mut V, cx: &mut ViewContext) -> Self::Rendered { div().id(self.id.clone()).child( Palette::new("palette") .items(vec![ @@ -29,6 +27,13 @@ impl RecentProjects { } } +impl RecentProjects { + pub fn new(id: impl Into) -> Self { + Self { id: id.into() } + } +} + +use gpui::{Div, RenderOnce, Stateful}; #[cfg(feature = "stories")] pub use stories::*; @@ -40,7 +45,7 @@ mod stories { pub struct RecentProjectsStory; - impl Render for RecentProjectsStory { + impl Render for RecentProjectsStory { type Element = Div; fn render(&mut self, cx: &mut ViewContext) -> Self::Element { diff --git a/crates/ui2/src/to_extract/status_bar.rs b/crates/ui2/src/to_extract/status_bar.rs index eee96ecad8..b07c45aa75 100644 --- a/crates/ui2/src/to_extract/status_bar.rs +++ b/crates/ui2/src/to_extract/status_bar.rs @@ -1,5 +1,7 @@ use std::sync::Arc; +use gpui::{Div, RenderOnce}; + use crate::prelude::*; use crate::{Button, Icon, IconButton, TextColor, ToolDivider, Workspace}; @@ -28,14 +30,31 @@ impl Default for ToolGroup { } } -#[derive(Component)] -#[component(view_type = "Workspace")] +#[derive(RenderOnce)] +#[view = "Workspace"] pub struct StatusBar { left_tools: Option, right_tools: Option, bottom_tools: Option, } +impl Component for StatusBar { + type Rendered = Div; + + fn render(self, view: &mut Workspace, cx: &mut ViewContext) -> Self::Rendered { + div() + .py_0p5() + .px_1() + .flex() + .items_center() + .justify_between() + .w_full() + .bg(cx.theme().colors().status_bar_background) + .child(self.left_tools(view, cx)) + .child(self.right_tools(view, cx)) + } +} + impl StatusBar { pub fn new() -> Self { Self { @@ -81,28 +100,7 @@ impl StatusBar { self } - fn render( - self, - view: &mut Workspace, - cx: &mut ViewContext, - ) -> impl Component { - div() - .py_0p5() - .px_1() - .flex() - .items_center() - .justify_between() - .w_full() - .bg(cx.theme().colors().status_bar_background) - .child(self.left_tools(view, cx)) - .child(self.right_tools(view, cx)) - } - - fn left_tools( - &self, - workspace: &mut Workspace, - cx: &WindowContext, - ) -> impl Component { + fn left_tools(&self, workspace: &mut Workspace, cx: &WindowContext) -> impl Element { div() .flex() .items_center() @@ -133,7 +131,7 @@ impl StatusBar { &self, workspace: &mut Workspace, cx: &WindowContext, - ) -> impl Component { + ) -> impl Element { div() .flex() .items_center() diff --git a/crates/ui2/src/to_extract/tab_bar.rs b/crates/ui2/src/to_extract/tab_bar.rs index 8888620dae..80c165b97e 100644 --- a/crates/ui2/src/to_extract/tab_bar.rs +++ b/crates/ui2/src/to_extract/tab_bar.rs @@ -1,7 +1,9 @@ use crate::{prelude::*, Icon, IconButton, Tab}; use gpui::prelude::*; +use gpui::Div; +use gpui::Stateful; -#[derive(Component)] +#[derive(RenderOnce)] pub struct TabBar { id: ElementId, /// Backwards, Forwards @@ -9,21 +11,10 @@ pub struct TabBar { tabs: Vec, } -impl TabBar { - pub fn new(id: impl Into, tabs: Vec) -> Self { - Self { - id: id.into(), - can_navigate: (false, false), - tabs, - } - } +impl Component for TabBar { + type Rendered = Stateful>; - pub fn can_navigate(mut self, can_navigate: (bool, bool)) -> Self { - self.can_navigate = can_navigate; - self - } - - fn render(self, _view: &mut V, cx: &mut ViewContext) -> impl Element { + fn render(self, view: &mut V, cx: &mut ViewContext) -> Self::Rendered { let (can_navigate_back, can_navigate_forward) = self.can_navigate; div() @@ -92,6 +83,21 @@ impl TabBar { } } +impl TabBar { + pub fn new(id: impl Into, tabs: Vec) -> Self { + Self { + id: id.into(), + can_navigate: (false, false), + tabs, + } + } + + pub fn can_navigate(mut self, can_navigate: (bool, bool)) -> Self { + self.can_navigate = can_navigate; + self + } +} + use gpui::ElementId; #[cfg(feature = "stories")] pub use stories::*; @@ -104,7 +110,7 @@ mod stories { pub struct TabBarStory; - impl Render for TabBarStory { + impl Render for TabBarStory { type Element = Div; fn render(&mut self, cx: &mut ViewContext) -> Self::Element { diff --git a/crates/ui2/src/to_extract/terminal.rs b/crates/ui2/src/to_extract/terminal.rs index d639986082..4cc43d4983 100644 --- a/crates/ui2/src/to_extract/terminal.rs +++ b/crates/ui2/src/to_extract/terminal.rs @@ -1,11 +1,78 @@ -use gpui::{relative, rems, Size}; - use crate::prelude::*; use crate::{Icon, IconButton, Pane, Tab}; +use gpui::{relative, rems, Div, RenderOnce, Size}; -#[derive(Component)] +#[derive(RenderOnce)] pub struct Terminal; +impl Component for Terminal { + type Rendered = Div; + + fn render(self, view: &mut V, cx: &mut ViewContext) -> Self::Rendered { + let can_navigate_back = true; + let can_navigate_forward = false; + + div() + .flex() + .flex_col() + .w_full() + .child( + // Terminal Tabs. + div() + .w_full() + .flex() + .bg(cx.theme().colors().surface_background) + .child( + div().px_1().flex().flex_none().gap_2().child( + div() + .flex() + .items_center() + .gap_px() + .child( + IconButton::new("arrow_left", Icon::ArrowLeft).state( + InteractionState::Enabled.if_enabled(can_navigate_back), + ), + ) + .child(IconButton::new("arrow_right", Icon::ArrowRight).state( + InteractionState::Enabled.if_enabled(can_navigate_forward), + )), + ), + ) + .child( + div().w_0().flex_1().h_full().child( + div() + .flex() + .child( + Tab::new(1) + .title("zed — fish".to_string()) + .icon(Icon::Terminal) + .close_side(IconSide::Right) + .current(true), + ) + .child( + Tab::new(2) + .title("zed — fish".to_string()) + .icon(Icon::Terminal) + .close_side(IconSide::Right) + .current(false), + ), + ), + ), + ) + // Terminal Pane. + .child( + Pane::new( + "terminal", + Size { + width: relative(1.).into(), + height: rems(36.).into(), + }, + ) + .child(crate::static_data::terminal_buffer(cx)), + ) + } +} + impl Terminal { pub fn new() -> Self { Self @@ -86,7 +153,7 @@ mod stories { use gpui::{Div, Render}; pub struct TerminalStory; - impl Render for TerminalStory { + impl Render for TerminalStory { type Element = Div; fn render(&mut self, cx: &mut ViewContext) -> Self::Element { diff --git a/crates/ui2/src/to_extract/theme_selector.rs b/crates/ui2/src/to_extract/theme_selector.rs index 175d126133..7b08195a57 100644 --- a/crates/ui2/src/to_extract/theme_selector.rs +++ b/crates/ui2/src/to_extract/theme_selector.rs @@ -1,17 +1,16 @@ use crate::prelude::*; use crate::{OrderMethod, Palette, PaletteItem}; -#[derive(Component)] +#[derive(RenderOnce)] pub struct ThemeSelector { id: ElementId, } -impl ThemeSelector { - pub fn new(id: impl Into) -> Self { - Self { id: id.into() } - } +impl Component for ThemeSelector { + type Rendered = Div; - fn render(self, _view: &mut V, cx: &mut ViewContext) -> impl Element { + fn render(self, view: &mut V, cx: &mut ViewContext) -> Self::Rendered { + let cx: &mut ViewContext = cx; div().child( Palette::new(self.id.clone()) .items(vec![ @@ -34,6 +33,13 @@ impl ThemeSelector { } } +impl ThemeSelector { + pub fn new(id: impl Into) -> Self { + Self { id: id.into() } + } +} + +use gpui::{Div, RenderOnce}; #[cfg(feature = "stories")] pub use stories::*; @@ -47,7 +53,7 @@ mod stories { pub struct ThemeSelectorStory; - impl Render for ThemeSelectorStory { + impl Render for ThemeSelectorStory { type Element = Div; fn render(&mut self, cx: &mut ViewContext) -> Self::Element { diff --git a/crates/ui2/src/to_extract/title_bar.rs b/crates/ui2/src/to_extract/title_bar.rs index 1a106cbf7a..167d8ca0bd 100644 --- a/crates/ui2/src/to_extract/title_bar.rs +++ b/crates/ui2/src/to_extract/title_bar.rs @@ -1,7 +1,7 @@ use std::sync::atomic::AtomicBool; use std::sync::Arc; -use gpui::{Div, Render, View, VisualContext}; +use gpui::{Div, Render, RenderOnce, View, VisualContext}; use crate::prelude::*; use crate::settings::user_settings; @@ -85,7 +85,7 @@ impl TitleBar { } } -impl Render for TitleBar { +impl Render for TitleBar { type Element = Div; fn render(&mut self, cx: &mut ViewContext) -> Div { @@ -205,7 +205,7 @@ mod stories { } } - impl Render for TitleBarStory { + impl Render for TitleBarStory { type Element = Div; fn render(&mut self, cx: &mut ViewContext) -> Div { diff --git a/crates/ui2/src/to_extract/toolbar.rs b/crates/ui2/src/to_extract/toolbar.rs index b7738ffb1c..884a72d2c7 100644 --- a/crates/ui2/src/to_extract/toolbar.rs +++ b/crates/ui2/src/to_extract/toolbar.rs @@ -1,4 +1,4 @@ -use gpui::AnyElement; +use gpui::{AnyElement, Div, RenderOnce}; use smallvec::SmallVec; use crate::prelude::*; @@ -6,12 +6,26 @@ use crate::prelude::*; #[derive(Clone)] pub struct ToolbarItem {} -#[derive(Component)] +#[derive(RenderOnce)] pub struct Toolbar { left_items: SmallVec<[AnyElement; 2]>, right_items: SmallVec<[AnyElement; 2]>, } +impl Component for Toolbar { + type Rendered = Div; + + fn render(self, view: &mut V, cx: &mut ViewContext) -> Self::Rendered { + div() + .bg(cx.theme().colors().toolbar_background) + .p_2() + .flex() + .justify_between() + .child(div().flex().children(self.left_items)) + .child(div().flex().children(self.right_items)) + } +} + impl Toolbar { pub fn new() -> Self { Self { @@ -20,49 +34,39 @@ impl Toolbar { } } - pub fn left_item(mut self, child: impl Element) -> Self + pub fn left_item(mut self, child: impl RenderOnce) -> Self where Self: Sized, { - self.left_items.push(child.render()); + self.left_items.push(child.render_into_any()); self } - pub fn left_items(mut self, iter: impl IntoIterator>) -> Self + pub fn left_items(mut self, iter: impl IntoIterator>) -> Self where Self: Sized, { self.left_items - .extend(iter.into_iter().map(|item| item.render())); + .extend(iter.into_iter().map(|item| item.render_into_any())); self } - pub fn right_item(mut self, child: impl Element) -> Self + pub fn right_item(mut self, child: impl RenderOnce) -> Self where Self: Sized, { - self.right_items.push(child.render()); + self.right_items.push(child.render_into_any()); self } - pub fn right_items(mut self, iter: impl IntoIterator>) -> Self + pub fn right_items(mut self, iter: impl IntoIterator>) -> Self where Self: Sized, { self.right_items - .extend(iter.into_iter().map(|item| item.render())); + .extend(iter.into_iter().map(|item| item.render_into_any())); self } - - fn render(self, _view: &mut V, cx: &mut ViewContext) -> impl Element { - div() - .bg(cx.theme().colors().toolbar_background) - .p_2() - .flex() - .justify_between() - .child(div().flex().children(self.left_items)) - .child(div().flex().children(self.right_items)) - } } #[cfg(feature = "stories")] @@ -81,7 +85,7 @@ mod stories { pub struct ToolbarStory; - impl Render for ToolbarStory { + impl Render for ToolbarStory { type Element = Div; fn render(&mut self, cx: &mut ViewContext) -> Self::Element { @@ -95,21 +99,21 @@ mod stories { vec![ Symbol(vec![ HighlightedText { - text: "impl ".to_string(), + text: "impl ".into(), color: cx.theme().syntax_color("keyword"), }, HighlightedText { - text: "ToolbarStory".to_string(), + text: "ToolbarStory".into(), color: cx.theme().syntax_color("function"), }, ]), Symbol(vec![ HighlightedText { - text: "fn ".to_string(), + text: "fn ".into(), color: cx.theme().syntax_color("keyword"), }, HighlightedText { - text: "render".to_string(), + text: "render".into(), color: cx.theme().syntax_color("function"), }, ]), diff --git a/crates/ui2/src/to_extract/traffic_lights.rs b/crates/ui2/src/to_extract/traffic_lights.rs index 8cab5594e6..5164e74159 100644 --- a/crates/ui2/src/to_extract/traffic_lights.rs +++ b/crates/ui2/src/to_extract/traffic_lights.rs @@ -7,21 +7,16 @@ enum TrafficLightColor { Green, } -#[derive(Component)] +#[derive(RenderOnce)] struct TrafficLight { color: TrafficLightColor, window_has_focus: bool, } -impl TrafficLight { - fn new(color: TrafficLightColor, window_has_focus: bool) -> Self { - Self { - color, - window_has_focus, - } - } +impl Component for TrafficLight { + type Rendered = Div; - fn render(self, _view: &mut V, cx: &mut ViewContext) -> impl Element { + fn render(self, view: &mut V, cx: &mut ViewContext) -> Self::Rendered { let system_colors = &cx.theme().styles.system; let fill = match (self.window_has_focus, self.color) { @@ -35,24 +30,24 @@ impl TrafficLight { } } -#[derive(Component)] +impl TrafficLight { + fn new(color: TrafficLightColor, window_has_focus: bool) -> Self { + Self { + color, + window_has_focus, + } + } +} + +#[derive(RenderOnce)] pub struct TrafficLights { window_has_focus: bool, } -impl TrafficLights { - pub fn new() -> Self { - Self { - window_has_focus: true, - } - } +impl Component for TrafficLights { + type Rendered = Div; - pub fn window_has_focus(mut self, window_has_focus: bool) -> Self { - self.window_has_focus = window_has_focus; - self - } - - fn render(self, _view: &mut V, cx: &mut ViewContext) -> impl Element { + fn render(self, view: &mut V, cx: &mut ViewContext) -> Self::Rendered { div() .flex() .items_center() @@ -72,6 +67,20 @@ impl TrafficLights { } } +impl TrafficLights { + pub fn new() -> Self { + Self { + window_has_focus: true, + } + } + + pub fn window_has_focus(mut self, window_has_focus: bool) -> Self { + self.window_has_focus = window_has_focus; + self + } +} + +use gpui::{Div, RenderOnce}; #[cfg(feature = "stories")] pub use stories::*; @@ -85,7 +94,7 @@ mod stories { pub struct TrafficLightsStory; - impl Render for TrafficLightsStory { + impl Render for TrafficLightsStory { type Element = Div; fn render(&mut self, cx: &mut ViewContext) -> Self::Element { diff --git a/crates/ui2/src/to_extract/workspace.rs b/crates/ui2/src/to_extract/workspace.rs index 0451a9d032..f31009cbb5 100644 --- a/crates/ui2/src/to_extract/workspace.rs +++ b/crates/ui2/src/to_extract/workspace.rs @@ -1,7 +1,7 @@ use std::sync::Arc; use chrono::DateTime; -use gpui::{px, relative, Div, Render, Size, View, VisualContext}; +use gpui::{px, relative, Div, Render, RenderOnce, Size, View, VisualContext}; use settings2::Settings; use theme2::ThemeSettings; @@ -191,7 +191,7 @@ impl Workspace { } } -impl Render for Workspace { +impl Render for Workspace { type Element = Div; fn render(&mut self, cx: &mut ViewContext) -> Div { @@ -388,7 +388,7 @@ mod stories { } } - impl Render for WorkspaceStory { + impl Render for WorkspaceStory { type Element = Div; fn render(&mut self, cx: &mut ViewContext) -> Self::Element { diff --git a/crates/workspace2/src/dock.rs b/crates/workspace2/src/dock.rs index 9603875aed..406a7a33d1 100644 --- a/crates/workspace2/src/dock.rs +++ b/crates/workspace2/src/dock.rs @@ -1,7 +1,7 @@ use crate::{status_bar::StatusItemView, Axis, Workspace}; use gpui::{ div, px, Action, AnchorCorner, AnyView, AppContext, Component, Div, Entity, EntityId, - EventEmitter, FocusHandle, FocusableView, ParentComponent, Render, SharedString, Styled, + EventEmitter, FocusHandle, FocusableView, ParentElement, Render, SharedString, Styled, Subscription, View, ViewContext, VisualContext, WeakView, WindowContext, }; use schemars::JsonSchema; diff --git a/crates/workspace2/src/status_bar.rs b/crates/workspace2/src/status_bar.rs index 327e7c09ed..b0f905f845 100644 --- a/crates/workspace2/src/status_bar.rs +++ b/crates/workspace2/src/status_bar.rs @@ -2,8 +2,8 @@ use std::any::TypeId; use crate::{ItemHandle, Pane}; use gpui::{ - div, AnyView, Component, Div, ParentComponent, Render, Styled, Subscription, View, ViewContext, - WindowContext, + div, AnyView, Component, Div, ParentElement, Render, Styled, Subscription, View, + ViewContext, WindowContext, }; use theme2::ActiveTheme; use ui::h_stack; diff --git a/crates/workspace2/src/workspace2.rs b/crates/workspace2/src/workspace2.rs index 08d248f6f2..af61091ce4 100644 --- a/crates/workspace2/src/workspace2.rs +++ b/crates/workspace2/src/workspace2.rs @@ -31,10 +31,10 @@ use futures::{ use gpui::{ actions, div, point, size, Action, AnyModel, AnyView, AnyWeakView, AppContext, AsyncAppContext, AsyncWindowContext, Bounds, Context, Div, Entity, EntityId, EventEmitter, FocusHandle, - FocusableView, GlobalPixels, InteractiveComponent, KeyContext, ManagedView, Model, - ModelContext, ParentComponent, PathPromptOptions, Point, PromptLevel, Render, Size, Styled, - Subscription, Task, View, ViewContext, VisualContext, WeakView, WindowBounds, WindowContext, - WindowHandle, WindowOptions, + FocusableView, GlobalPixels, InteractiveElement, KeyContext, ManagedView, Model, ModelContext, + ParentElement, PathPromptOptions, Point, PromptLevel, Render, Size, Styled, Subscription, Task, + View, ViewContext, VisualContext, WeakView, WindowBounds, WindowContext, WindowHandle, + WindowOptions, }; use item::{FollowableItem, FollowableItemHandle, Item, ItemHandle, ItemSettings, ProjectItem}; use itertools::Itertools;