diff --git a/crates/auto_update2/src/update_notification.rs b/crates/auto_update2/src/update_notification.rs index b77682c9aea527100aa03c49ac5e249f19569b8c..03a71bcabbba6111953a0754ef3455b317f771dd 100644 --- a/crates/auto_update2/src/update_notification.rs +++ b/crates/auto_update2/src/update_notification.rs @@ -1,4 +1,4 @@ -use gpui::{div, Div, EventEmitter, ParentComponent, Render, SemanticVersion, ViewContext}; +use gpui::{div, Div, EventEmitter, ParentElement, Render, SemanticVersion, ViewContext}; use menu::Cancel; use workspace::notifications::NotificationEvent; @@ -8,7 +8,7 @@ pub struct UpdateNotification { impl EventEmitter for UpdateNotification {} -impl Render for UpdateNotification { +impl Render for UpdateNotification { type Element = Div; fn render(&mut self, _cx: &mut gpui::ViewContext) -> Self::Element { diff --git a/crates/collab_ui2/src/collab_panel.rs b/crates/collab_ui2/src/collab_panel.rs index 20e77d7023db0caeb99893c6fff35f8695f9f3dd..901348d2e29b34454bac87b3dc7dff250b2ab835 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; @@ -3294,7 +3294,7 @@ impl CollabPanel { // .with_width(size.x()) // } -impl Render for CollabPanel { +impl Render for CollabPanel { type Element = Focusable>; fn render(&mut self, _cx: &mut ViewContext) -> Self::Element { diff --git a/crates/collab_ui2/src/collab_titlebar_item.rs b/crates/collab_ui2/src/collab_titlebar_item.rs index ed010cc500d6f05444c56fd9c1d9d83ee9f1fce4..42800269c74c31769f15f1008e15ef301a75808b 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, Div, InteractiveElement, Model, ParentElement, Render, RenderOnce, + Stateful, StatefulInteractiveElement, Styled, Subscription, ViewContext, VisualContext, + WeakView, WindowBounds, }; use project::Project; use theme::ActiveTheme; @@ -81,7 +81,7 @@ pub struct CollabTitlebarItem { _subscriptions: Vec, } -impl Render for CollabTitlebarItem { +impl Render for CollabTitlebarItem { type Element = Stateful>; fn render(&mut self, cx: &mut ViewContext) -> Self::Element { diff --git a/crates/command_palette2/src/command_palette.rs b/crates/command_palette2/src/command_palette.rs index f3573c1a3dae3524631ad6824b3d746faa6612ab..1296f35c55f05424f13045e2688a740b248b1704 100644 --- a/crates/command_palette2/src/command_palette.rs +++ b/crates/command_palette2/src/command_palette.rs @@ -1,9 +1,8 @@ use collections::{CommandPaletteFilter, HashMap}; use fuzzy::{StringMatch, StringMatchCandidate}; use gpui::{ - actions, div, prelude::*, Action, AppContext, Component, Div, EventEmitter, FocusHandle, - FocusableView, Keystroke, Manager, ParentComponent, Render, Styled, View, ViewContext, - VisualContext, WeakView, + actions, div, prelude::*, Action, AppContext, Div, EventEmitter, FocusHandle, FocusableView, + Keystroke, Manager, ParentElement, Render, Styled, View, ViewContext, VisualContext, WeakView, }; use picker::{Picker, PickerDelegate}; use std::{ @@ -77,7 +76,7 @@ impl FocusableView for CommandPalette { } } -impl Render for CommandPalette { +impl Render for CommandPalette { type Element = Div; fn render(&mut self, _cx: &mut ViewContext) -> Self::Element { diff --git a/crates/diagnostics2/src/diagnostics.rs b/crates/diagnostics2/src/diagnostics.rs index 188027baedc5aeaf8d9408607befb1222af267b4..1e90ac6cab73ee43c268633c217dcd863714dae8 100644 --- a/crates/diagnostics2/src/diagnostics.rs +++ b/crates/diagnostics2/src/diagnostics.rs @@ -13,9 +13,9 @@ use editor::{ }; use futures::future::try_join_all; use gpui::{ - actions, div, AnyElement, AnyView, AppContext, Component, Context, Div, EventEmitter, - FocusEvent, FocusHandle, Focusable, FocusableComponent, FocusableView, InteractiveComponent, - Model, ParentComponent, Render, SharedString, Styled, Subscription, Task, View, ViewContext, + actions, div, AnyElement, AnyView, AppContext, Context, Div, EventEmitter, FocusEvent, + FocusHandle, Focusable, FocusableElement, FocusableView, InteractiveElement, Model, + ParentElement, Render, RenderOnce, SharedString, Styled, Subscription, Task, View, ViewContext, VisualContext, WeakView, }; use language::{ @@ -90,7 +90,7 @@ struct DiagnosticGroupState { impl EventEmitter for ProjectDiagnosticsEditor {} -impl Render for ProjectDiagnosticsEditor { +impl Render for ProjectDiagnosticsEditor { type Element = Focusable>; fn render(&mut self, cx: &mut ViewContext) -> Self::Element { @@ -792,13 +792,15 @@ fn diagnostic_header_renderer(diagnostic: Diagnostic) -> RenderBlock { .when_some(diagnostic.code.as_ref(), |stack, code| { stack.child(Label::new(code.clone())) }) - .render() + .render_into_any() }) } pub(crate) fn render_summary(summary: &DiagnosticSummary) -> AnyElement { if summary.error_count == 0 && summary.warning_count == 0 { - Label::new("No problems").render() + let label = Label::new("No problems"); + label.render_into_any() + //.render() } else { h_stack() .bg(gpui::red()) @@ -806,7 +808,7 @@ pub(crate) fn render_summary(summary: &DiagnosticSummary) -> AnyElem .child(Label::new(summary.error_count.to_string())) .child(IconElement::new(Icon::ExclamationTriangle)) .child(Label::new(summary.warning_count.to_string())) - .render() + .render_into_any() } } diff --git a/crates/diagnostics2/src/items.rs b/crates/diagnostics2/src/items.rs index dd1b7d98cf627c95d904a9483d56b6dcd55df35e..1d5183634f06bba5fd094c62cd8dfbfb753b9983 100644 --- a/crates/diagnostics2/src/items.rs +++ b/crates/diagnostics2/src/items.rs @@ -1,8 +1,8 @@ use collections::HashSet; use editor::{Editor, GoToDiagnostic}; use gpui::{ - rems, Div, EventEmitter, InteractiveComponent, ParentComponent, Render, Stateful, - StatefulInteractiveComponent, Styled, Subscription, View, ViewContext, WeakView, + rems, Div, EventEmitter, InteractiveElement, ParentElement, Render, Stateful, + StatefulInteractiveElement, Styled, Subscription, View, ViewContext, WeakView, }; use language::Diagnostic; use lsp::LanguageServerId; @@ -21,7 +21,7 @@ pub struct DiagnosticIndicator { _observe_active_editor: Option, } -impl Render for DiagnosticIndicator { +impl Render for DiagnosticIndicator { type Element = Stateful>; fn render(&mut self, cx: &mut ViewContext) -> Self::Element { diff --git a/crates/diagnostics2/src/toolbar_controls.rs b/crates/diagnostics2/src/toolbar_controls.rs index 0c6f132427438a1c544e38fea575da28e81cef1e..8b8eaf6af7a3f5754d6e85a54ecd81a13eb70eb8 100644 --- a/crates/diagnostics2/src/toolbar_controls.rs +++ b/crates/diagnostics2/src/toolbar_controls.rs @@ -1,5 +1,5 @@ use crate::ProjectDiagnosticsEditor; -use gpui::{div, Div, EventEmitter, ParentComponent, Render, ViewContext, WeakView}; +use gpui::{div, Div, EventEmitter, ParentElement, Render, ViewContext, WeakView}; use ui::{Icon, IconButton, Tooltip}; use workspace::{item::ItemHandle, ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView}; @@ -7,7 +7,7 @@ pub struct ToolbarControls { editor: Option>, } -impl Render for ToolbarControls { +impl Render for ToolbarControls { type Element = Div; fn render(&mut self, cx: &mut ViewContext) -> Self::Element { diff --git a/crates/editor2/src/editor.rs b/crates/editor2/src/editor.rs index f8962b3f94f44f188e6e13291ef8b1f0d5a4565f..5ffeddc7b7121f8fca83df45b80e5b16796dd13b 100644 --- a/crates/editor2/src/editor.rs +++ b/crates/editor2/src/editor.rs @@ -42,9 +42,9 @@ 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, - Subscription, Task, TextStyle, UniformListScrollHandle, View, ViewContext, VisualContext, - WeakView, WindowContext, + Hsla, InputHandler, KeyContext, Model, MouseButton, ParentElement, Pixels, Render, + SharedString, Styled, Subscription, Task, TextStyle, UniformListScrollHandle, View, + ViewContext, VisualContext, WeakView, WindowContext, }; use highlight_matching_bracket::refresh_matching_bracket_highlights; use hover_popover::{hide_hover, HoverState}; @@ -1580,7 +1580,8 @@ impl CodeActionsMenu { ) .map(|task| task.detach_and_log_err(cx)); }) - .child(action.lsp_action.title.clone()) + // TASK: It would be good to make lsp_action.title a SharedString to avoid allocating here. + .child(SharedString::from(action.lsp_action.title.clone())) }) .collect() }, @@ -1595,7 +1596,7 @@ impl CodeActionsMenu { .max_by_key(|(_, action)| action.lsp_action.title.chars().count()) .map(|(ix, _)| ix), ) - .render(); + .render_into_any(); if self.deployed_from_indicator { *cursor_position.column_mut() = 0; @@ -4354,19 +4355,19 @@ impl Editor { style: &EditorStyle, is_active: bool, cx: &mut ViewContext, - ) -> Option> { + ) -> Option> { if self.available_code_actions.is_some() { Some( - IconButton::new("code_actions_indicator", ui::Icon::Bolt) - .on_click(|editor: &mut Editor, cx| { + IconButton::new("code_actions_indicator", ui::Icon::Bolt).on_click( + |editor: &mut Editor, cx| { editor.toggle_code_actions( &ToggleCodeActions { deployed_from_indicator: true, }, cx, ); - }) - .render(), + }, + ), ) } else { None @@ -4381,7 +4382,7 @@ impl Editor { line_height: Pixels, gutter_margin: Pixels, cx: &mut ViewContext, - ) -> Vec>> { + ) -> Vec>> { fold_data .iter() .enumerate() @@ -4403,7 +4404,6 @@ impl Editor { } }) .color(ui::TextColor::Muted) - .render() }) }) .flatten() @@ -7794,7 +7794,7 @@ impl Editor { cx.editor_style.diagnostic_style.clone(), }, ))) - .render() + .render_into_any() } }), disposition: BlockDisposition::Below, @@ -9383,7 +9383,7 @@ impl FocusableView for Editor { } } -impl Render for Editor { +impl Render for Editor { type Element = EditorElement; fn render(&mut self, cx: &mut ViewContext) -> Self::Element { @@ -10001,7 +10001,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_into_any() }) } diff --git a/crates/editor2/src/editor_tests.rs b/crates/editor2/src/editor_tests.rs index 06a8636f8c829b7cbca2cb5b3cc633640e56460b..f0609fc9a8507a4adf5db179c90ff1d519378dd8 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().into_any()), }], Some(Autoscroll::fit()), cx, diff --git a/crates/editor2/src/element.rs b/crates/editor2/src/element.rs index 220fe89ea65cd092a38330d3aa12f56b3f919f56..42cb47da4951c2f616a470bc589fbd333d922c8c 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, RenderOnce, + ScrollWheelEvent, ShapedLine, SharedString, Size, StatefulInteractiveElement, Style, Styled, TextRun, TextStyle, View, ViewContext, WindowContext, WrappedLine, }; use itertools::Itertools; @@ -488,8 +488,9 @@ impl EditorElement { } } - for (ix, fold_indicator) in layout.fold_indicators.iter_mut().enumerate() { - if let Some(fold_indicator) = fold_indicator.as_mut() { + for (ix, fold_indicator) in layout.fold_indicators.drain(..).enumerate() { + if let Some(mut fold_indicator) = fold_indicator { + let mut fold_indicator = fold_indicator.render_into_any(); let available_space = size( AvailableSpace::MinContent, AvailableSpace::Definite(line_height * 0.55), @@ -509,20 +510,21 @@ impl EditorElement { } } - if let Some(indicator) = layout.code_actions_indicator.as_mut() { + if let Some(indicator) = layout.code_actions_indicator.take() { + let mut button = indicator.button.render_into_any(); let available_space = size( AvailableSpace::MinContent, AvailableSpace::Definite(line_height), ); - let indicator_size = indicator.element.measure(available_space, editor, cx); + let indicator_size = button.measure(available_space, editor, cx); + let mut x = Pixels::ZERO; let mut y = indicator.row as f32 * line_height - scroll_top; // Center indicator. x += ((layout.gutter_padding + layout.gutter_margin) - indicator_size.width) / 2.; y += (line_height - indicator_size.height) / 2.; - indicator - .element - .draw(bounds.origin + point(x, y), available_space, editor, cx); + + button.draw(bounds.origin + point(x, y), available_space, editor, cx); } } @@ -840,7 +842,7 @@ impl EditorElement { } }); - if let Some((position, context_menu)) = layout.context_menu.as_mut() { + if let Some((position, mut context_menu)) = layout.context_menu.take() { cx.with_z_index(1, |cx| { let line_height = self.style.text.line_height_in_pixels(cx.rem_size()); let available_space = size( @@ -1224,7 +1226,7 @@ impl EditorElement { let scroll_left = scroll_position.x * layout.position_map.em_width; let scroll_top = scroll_position.y * layout.position_map.line_height; - for block in &mut layout.blocks { + for block in layout.blocks.drain(..) { let mut origin = bounds.origin + point( Pixels::ZERO, @@ -1810,7 +1812,7 @@ impl EditorElement { .render_code_actions_indicator(&style, active, cx) .map(|element| CodeActionsIndicator { row: newest_selection_head.row(), - element, + button: element, }); } } @@ -2043,15 +2045,20 @@ impl EditorElement { // Can't use .and_then() because `.file_name()` and `.parent()` return references :( if let Some(path) = path { filename = path.file_name().map(|f| f.to_string_lossy().to_string()); - parent_path = - path.parent().map(|p| p.to_string_lossy().to_string() + "/"); + parent_path = path + .parent() + .map(|p| SharedString::from(p.to_string_lossy().to_string() + "/")); } h_stack() .id("path header block") .size_full() .bg(gpui::red()) - .child(filename.unwrap_or_else(|| "untitled".to_string())) + .child( + filename + .map(SharedString::from) + .unwrap_or_else(|| "untitled".into()), + ) .children(parent_path) .children(jump_icon) // .p_x(gutter_padding) } else { @@ -2063,7 +2070,7 @@ impl EditorElement { .child("⋯") .children(jump_icon) // .p_x(gutter_padding) }; - element.render() + element.into_any() } }; @@ -2393,18 +2400,14 @@ enum Invisible { } impl Element for EditorElement { - type ElementState = (); - - fn element_id(&self) -> Option { - Some(self.editor_id.into()) - } + type State = (); fn layout( &mut self, editor: &mut Editor, - element_state: Option, + element_state: Option, cx: &mut gpui::ViewContext, - ) -> (gpui::LayoutId, Self::ElementState) { + ) -> (gpui::LayoutId, Self::State) { editor.style = Some(self.style.clone()); // Long-term, we'd like to eliminate this. let rem_size = cx.rem_size(); @@ -2420,10 +2423,10 @@ impl Element for EditorElement { } fn paint( - &mut self, + mut self, bounds: Bounds, editor: &mut Editor, - element_state: &mut Self::ElementState, + element_state: &mut Self::State, cx: &mut gpui::ViewContext, ) { let mut layout = self.compute_layout(editor, cx, bounds); @@ -2470,9 +2473,15 @@ impl Element for EditorElement { } } -impl Component for EditorElement { - fn render(self) -> AnyElement { - AnyElement::new(self) +impl RenderOnce for EditorElement { + type Element = Self; + + fn element_id(&self) -> Option { + Some(self.editor_id.into()) + } + + fn render_once(self) -> Self::Element { + self } } @@ -3100,14 +3109,14 @@ pub struct LayoutState { context_menu: Option<(DisplayPoint, AnyElement)>, code_actions_indicator: Option, // hover_popovers: Option<(DisplayPoint, Vec>)>, - fold_indicators: Vec>>, + fold_indicators: Vec>>, tab_invisible: ShapedLine, space_invisible: ShapedLine, } struct CodeActionsIndicator { row: u32, - element: AnyElement, + button: IconButton, } struct PositionMap { diff --git a/crates/editor2/src/items.rs b/crates/editor2/src/items.rs index 1bde7b9878bedf53374ebc3f8de853a7c164b9aa..0098b33203a94d53dc87a630176638dd7888eb4e 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 3691e69f5dcbf301ede3076e3df10249d09fe746..2e7655298a4ddd151bc89222f4e34cb1722c6c1d 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, Div, EventEmitter, FocusHandle, FocusableView, - InteractiveComponent, Manager, Model, ParentComponent, Render, Styled, Task, View, ViewContext, + actions, div, AppContext, Div, EventEmitter, FocusHandle, FocusableView, InteractiveElement, + Manager, Model, ParentElement, Render, RenderOnce, Styled, Task, View, ViewContext, VisualContext, WeakView, }; use picker::{Picker, PickerDelegate}; @@ -117,7 +117,7 @@ impl FocusableView for FileFinder { self.picker.focus_handle(cx) } } -impl Render for FileFinder { +impl Render for FileFinder { type Element = Div; fn render(&mut self, _cx: &mut ViewContext) -> Self::Element { diff --git a/crates/go_to_line2/src/go_to_line.rs b/crates/go_to_line2/src/go_to_line.rs index 8c7ac97d0b062da54ac798af4199a2ef9f7e8b74..9b3666ea5c02df827b0d575a8c0c2dee9395e9f6 100644 --- a/crates/go_to_line2/src/go_to_line.rs +++ b/crates/go_to_line2/src/go_to_line.rs @@ -1,8 +1,7 @@ use editor::{display_map::ToDisplayPoint, scroll::autoscroll::Autoscroll, Editor}; use gpui::{ actions, div, prelude::*, AppContext, Div, EventEmitter, FocusHandle, FocusableView, Manager, - ParentComponent, Render, SharedString, Styled, Subscription, View, ViewContext, VisualContext, - WindowContext, + Render, SharedString, Styled, Subscription, View, ViewContext, VisualContext, WindowContext, }; use text::{Bias, Point}; use theme::ActiveTheme; @@ -145,7 +144,7 @@ impl GoToLine { } } -impl Render for GoToLine { +impl Render for GoToLine { type Element = Div; fn render(&mut self, cx: &mut ViewContext) -> Self::Element { diff --git a/crates/gpui2/src/app.rs b/crates/gpui2/src/app.rs index ca96ba210ecf90486602a86f0eb73502b09ca550..ff601db37262e57bc08436c4344e024c24b59f42 100644 --- a/crates/gpui2/src/app.rs +++ b/crates/gpui2/src/app.rs @@ -14,7 +14,7 @@ use smallvec::SmallVec; pub use test_context::*; use crate::{ - current_platform, image_cache::ImageCache, Action, ActionRegistry, AnyBox, AnyView, + current_platform, image_cache::ImageCache, Action, ActionRegistry, Any, AnyView, AnyWindowHandle, AppMetadata, AssetSource, BackgroundExecutor, ClipboardItem, Context, DispatchPhase, DisplayId, Entity, EventEmitter, FocusEvent, FocusHandle, FocusId, ForegroundExecutor, KeyBinding, Keymap, LayoutId, PathPromptOptions, Pixels, Platform, @@ -28,7 +28,7 @@ use futures::{channel::oneshot, future::LocalBoxFuture, Future}; use parking_lot::Mutex; use slotmap::SlotMap; use std::{ - any::{type_name, Any, TypeId}, + any::{type_name, TypeId}, cell::{Ref, RefCell, RefMut}, marker::PhantomData, mem, @@ -194,7 +194,7 @@ pub struct AppContext { asset_source: Arc, pub(crate) image_cache: ImageCache, pub(crate) text_style_stack: Vec, - pub(crate) globals_by_type: HashMap, + pub(crate) globals_by_type: HashMap>, pub(crate) entities: EntityMap, pub(crate) new_view_observers: SubscriberSet, pub(crate) windows: SlotMap>, @@ -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, @@ -1104,12 +1104,12 @@ pub(crate) enum Effect { /// Wraps a global variable value during `update_global` while the value has been moved to the stack. pub(crate) struct GlobalLease { - global: AnyBox, + global: Box, global_type: PhantomData, } impl GlobalLease { - fn new(global: AnyBox) -> Self { + fn new(global: Box) -> Self { GlobalLease { global, global_type: PhantomData, diff --git a/crates/gpui2/src/app/async_context.rs b/crates/gpui2/src/app/async_context.rs index 65c006b0621ce0e4dcfee685727e08beef41314c..3fdff1102adfc5a18dc88d04dbbf93f76ad377f1 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/entity_map.rs b/crates/gpui2/src/app/entity_map.rs index f1e7fad6a1967dbe238f32f7d1768799b9a69439..a34582f4f4024c5eeab0363d45896be7eaa2ee95 100644 --- a/crates/gpui2/src/app/entity_map.rs +++ b/crates/gpui2/src/app/entity_map.rs @@ -1,10 +1,10 @@ -use crate::{private::Sealed, AnyBox, AppContext, Context, Entity, ModelContext}; +use crate::{private::Sealed, AppContext, Context, Entity, ModelContext}; use anyhow::{anyhow, Result}; use derive_more::{Deref, DerefMut}; use parking_lot::{RwLock, RwLockUpgradableReadGuard}; use slotmap::{SecondaryMap, SlotMap}; use std::{ - any::{type_name, TypeId}, + any::{type_name, Any, TypeId}, fmt::{self, Display}, hash::{Hash, Hasher}, marker::PhantomData, @@ -31,7 +31,7 @@ impl Display for EntityId { } pub(crate) struct EntityMap { - entities: SecondaryMap, + entities: SecondaryMap>, ref_counts: Arc>, } @@ -102,7 +102,7 @@ impl EntityMap { ); } - pub fn take_dropped(&mut self) -> Vec<(EntityId, AnyBox)> { + pub fn take_dropped(&mut self) -> Vec<(EntityId, Box)> { let mut ref_counts = self.ref_counts.write(); let dropped_entity_ids = mem::take(&mut ref_counts.dropped_entity_ids); @@ -122,7 +122,7 @@ impl EntityMap { } pub struct Lease<'a, T> { - entity: Option, + entity: Option>, pub model: &'a Model, entity_type: PhantomData, } diff --git a/crates/gpui2/src/app/test_context.rs b/crates/gpui2/src/app/test_context.rs index 9025cc1f81e7dc57d2cfc13aa379552de0da8280..3822ba6ab47d4ce3c2c0d253906ebcd954ff35e0 100644 --- a/crates/gpui2/src/app/test_context.rs +++ b/crates/gpui2/src/app/test_context.rs @@ -127,7 +127,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)) @@ -144,7 +144,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)); @@ -568,7 +568,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)) @@ -590,7 +590,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)) @@ -618,7 +618,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, @@ -629,7 +629,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 c773bb6f65207c1d3a2ef4bc8643072def5a30ce..990de68b063ec3c22bc39dabce0ff4c1d64cb968 100644 --- a/crates/gpui2/src/element.rs +++ b/crates/gpui2/src/element.rs @@ -3,27 +3,24 @@ use crate::{ }; use derive_more::{Deref, DerefMut}; pub(crate) use smallvec::SmallVec; -use std::{any::Any, fmt::Debug, mem}; +use std::{any::Any, fmt::Debug, marker::PhantomData}; -pub trait Element { - type ElementState: 'static; +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 element_id(&self) -> Option; - fn layout( - &mut self, - view_state: &mut V, - element_state: Option, - cx: &mut ViewContext, - ) -> (LayoutId, Self::ElementState); + fn render_once(self) -> Self::Element; - fn paint( - &mut self, - bounds: Bounds, - view_state: &mut V, - element_state: &mut Self::ElementState, - cx: &mut ViewContext, - ); + fn render_into_any(self) -> AnyElement { + self.render_once().into_any() + } fn draw( self, @@ -31,57 +28,187 @@ pub trait Element { available_space: Size, view_state: &mut V, cx: &mut ViewContext, - f: impl FnOnce(&Self::ElementState, &mut ViewContext) -> R, + f: impl FnOnce(&mut >::State, &mut ViewContext) -> R, ) -> R where - Self: Sized, T: Clone + Default + Debug + Into, { - let mut element = RenderedElement { - element: self, - phase: ElementRenderPhase::Start, + let element = self.render_once(); + let element_id = element.element_id(); + let element = DrawableElement { + element: Some(element), + phase: ElementDrawPhase::Start, }; - element.draw(origin, available_space.map(Into::into), view_state, cx); - if let ElementRenderPhase::Painted { frame_state } = &element.phase { - if let Some(frame_state) = frame_state.as_ref() { - f(&frame_state, cx) + let frame_state = DrawableElement::draw( + element, + origin, + available_space.map(Into::into), + view_state, + cx, + ); + + if let Some(mut frame_state) = frame_state { + f(&mut frame_state, cx) + } else { + cx.with_element_state(element_id.unwrap(), |element_state, cx| { + let mut element_state = element_state.unwrap(); + let result = f(&mut element_state, cx); + (result, element_state) + }) + } + } + + 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 { - let element_id = element - .element - .element_id() - .expect("we either have some frame_state or some element_id"); - cx.with_element_state(element_id, |element_state, cx| { - let element_state = element_state.unwrap(); - let result = f(&element_state, cx); - (result, element_state) - }) + this } - } else { - unreachable!() + }) + } +} + +pub trait Element: 'static + RenderOnce { + type State: 'static; + + fn layout( + &mut self, + view_state: &mut V, + element_state: Option, + cx: &mut ViewContext, + ) -> (LayoutId, Self::State); + + fn paint( + self, + bounds: Bounds, + view_state: &mut V, + element_state: &mut Self::State, + cx: &mut ViewContext, + ); + + fn into_any(self) -> AnyElement { + AnyElement::new(self) + } +} + +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 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 element_id(&self) -> Option { + None + } + + 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 Component) -> Self + fn child(mut self, child: impl RenderOnce) -> Self where Self: Sized, { - self.children_mut().push(child.render()); + self.children_mut().push(child.render_once().into_any()); self } - fn children(mut self, iter: impl IntoIterator>) -> Self + fn children(mut self, children: impl IntoIterator>) -> Self where Self: Sized, { - self.children_mut() - .extend(iter.into_iter().map(|item| item.render())); + self.children_mut().extend( + children + .into_iter() + .map(|child| child.render_once().into_any()), + ); self } } @@ -105,105 +232,103 @@ trait ElementObject { ); } -struct RenderedElement> { - element: E, - phase: ElementRenderPhase, +pub struct DrawableElement> { + element: Option, + phase: ElementDrawPhase, } #[derive(Default)] -enum ElementRenderPhase { +enum ElementDrawPhase { #[default] Start, LayoutRequested { layout_id: LayoutId, - frame_state: Option, + frame_state: Option, }, LayoutComputed { layout_id: LayoutId, available_space: Size, - frame_state: Option, - }, - Painted { - frame_state: Option, + frame_state: Option, }, } -/// Internal struct that wraps an element to store Layout and ElementState after the element is rendered. -/// It's allocated as a trait object to erase the element type and wrapped in AnyElement for -/// improved usability. -impl> RenderedElement { +/// A wrapper around an implementer of [Element] that allows it to be drawn in a window. +impl> DrawableElement { fn new(element: E) -> Self { - RenderedElement { - element, - phase: ElementRenderPhase::Start, + DrawableElement { + element: Some(element), + phase: ElementDrawPhase::Start, } } -} -impl ElementObject for RenderedElement -where - E: Element, - E::ElementState: 'static, -{ fn element_id(&self) -> Option { - self.element.element_id() + self.element.as_ref()?.element_id() } fn layout(&mut self, state: &mut V, cx: &mut ViewContext) -> LayoutId { - let (layout_id, frame_state) = match mem::take(&mut self.phase) { - ElementRenderPhase::Start => { - if let Some(id) = self.element.element_id() { - let layout_id = cx.with_element_state(id, |element_state, cx| { - self.element.layout(state, element_state, cx) - }); - (layout_id, None) - } else { - let (layout_id, frame_state) = self.element.layout(state, None, cx); - (layout_id, Some(frame_state)) - } - } - ElementRenderPhase::LayoutRequested { .. } - | ElementRenderPhase::LayoutComputed { .. } - | ElementRenderPhase::Painted { .. } => { - panic!("element rendered twice") - } + let (layout_id, frame_state) = if let Some(id) = self.element.as_ref().unwrap().element_id() + { + let layout_id = cx.with_element_state(id, |element_state, cx| { + self.element + .as_mut() + .unwrap() + .layout(state, element_state, cx) + }); + (layout_id, None) + } else { + let (layout_id, frame_state) = self.element.as_mut().unwrap().layout(state, None, cx); + (layout_id, Some(frame_state)) }; - self.phase = ElementRenderPhase::LayoutRequested { + self.phase = ElementDrawPhase::LayoutRequested { layout_id, frame_state, }; layout_id } - fn paint(&mut self, view_state: &mut V, cx: &mut ViewContext) { - self.phase = match mem::take(&mut self.phase) { - ElementRenderPhase::LayoutRequested { + fn paint(mut self, view_state: &mut V, cx: &mut ViewContext) -> Option { + match self.phase { + ElementDrawPhase::LayoutRequested { layout_id, - mut frame_state, + frame_state, } - | ElementRenderPhase::LayoutComputed { + | ElementDrawPhase::LayoutComputed { layout_id, - mut frame_state, + frame_state, .. } => { let bounds = cx.layout_bounds(layout_id); - if let Some(id) = self.element.element_id() { - cx.with_element_state(id, |element_state, cx| { + + if let Some(mut frame_state) = frame_state { + self.element + .take() + .unwrap() + .paint(bounds, view_state, &mut frame_state, cx); + Some(frame_state) + } else { + let element_id = self + .element + .as_ref() + .unwrap() + .element_id() + .expect("if we don't have frame state, we should have element state"); + cx.with_element_state(element_id, |element_state, cx| { let mut element_state = element_state.unwrap(); - self.element - .paint(bounds, view_state, &mut element_state, cx); + self.element.take().unwrap().paint( + bounds, + view_state, + &mut element_state, + cx, + ); ((), element_state) }); - } else { - self.element - .paint(bounds, view_state, frame_state.as_mut().unwrap(), cx); + None } - ElementRenderPhase::Painted { frame_state } } _ => panic!("must call layout before paint"), - }; + } } fn measure( @@ -212,25 +337,25 @@ where view_state: &mut V, cx: &mut ViewContext, ) -> Size { - if matches!(&self.phase, ElementRenderPhase::Start) { + if matches!(&self.phase, ElementDrawPhase::Start) { self.layout(view_state, cx); } let layout_id = match &mut self.phase { - ElementRenderPhase::LayoutRequested { + ElementDrawPhase::LayoutRequested { layout_id, frame_state, } => { cx.compute_layout(*layout_id, available_space); let layout_id = *layout_id; - self.phase = ElementRenderPhase::LayoutComputed { + self.phase = ElementDrawPhase::LayoutComputed { layout_id, available_space, frame_state: frame_state.take(), }; layout_id } - ElementRenderPhase::LayoutComputed { + ElementDrawPhase::LayoutComputed { layout_id, available_space: prev_available_space, .. @@ -248,27 +373,105 @@ where } fn draw( - &mut self, + mut self, origin: Point, available_space: Size, view_state: &mut V, cx: &mut ViewContext, - ) { + ) -> Option { self.measure(available_space, view_state, cx); cx.with_absolute_element_offset(origin, |cx| self.paint(view_state, cx)) } } +// impl> Element for DrawableElement { +// type State = >::State; + +// fn layout( +// &mut self, +// view_state: &mut V, +// element_state: Option, +// cx: &mut ViewContext, +// ) -> (LayoutId, Self::State) { + +// } + +// fn paint( +// self, +// bounds: Bounds, +// view_state: &mut V, +// element_state: &mut Self::State, +// cx: &mut ViewContext, +// ) { +// todo!() +// } +// } + +// impl> RenderOnce for DrawableElement { +// type Element = Self; + +// fn element_id(&self) -> Option { +// self.element.as_ref()?.element_id() +// } + +// fn render_once(self) -> Self::Element { +// self +// } +// } + +impl ElementObject for Option> +where + E: Element, + E::State: 'static, +{ + fn element_id(&self) -> Option { + self.as_ref().unwrap().element_id() + } + + fn layout(&mut self, view_state: &mut V, cx: &mut ViewContext) -> LayoutId { + DrawableElement::layout(self.as_mut().unwrap(), view_state, cx) + } + + fn paint(&mut self, view_state: &mut V, cx: &mut ViewContext) { + DrawableElement::paint(self.take().unwrap(), view_state, cx); + } + + fn measure( + &mut self, + available_space: Size, + view_state: &mut V, + cx: &mut ViewContext, + ) -> Size { + DrawableElement::measure(self.as_mut().unwrap(), available_space, view_state, cx) + } + + fn draw( + &mut self, + origin: Point, + available_space: Size, + view_state: &mut V, + cx: &mut ViewContext, + ) { + DrawableElement::draw( + self.take().unwrap(), + origin, + available_space, + view_state, + cx, + ); + } +} + pub struct AnyElement(Box>); -impl AnyElement { +impl AnyElement { pub fn new(element: E) -> Self where V: 'static, E: 'static + Element, - E::ElementState: Any, + E::State: Any, { - AnyElement(Box::new(RenderedElement::new(element))) + AnyElement(Box::new(Some(DrawableElement::new(element))) as Box>) } pub fn element_id(&self) -> Option { @@ -279,7 +482,7 @@ impl AnyElement { self.0.layout(view_state, cx) } - pub fn paint(&mut self, view_state: &mut V, cx: &mut ViewContext) { + pub fn paint(mut self, view_state: &mut V, cx: &mut ViewContext) { self.0.paint(view_state, cx) } @@ -295,7 +498,7 @@ impl AnyElement { /// Initializes this element and performs layout in the available space, then paints it at the given origin. pub fn draw( - &mut self, + mut self, origin: Point, available_space: Size, view_state: &mut V, @@ -303,99 +506,93 @@ impl AnyElement { ) { self.0.draw(origin, available_space, view_state, cx) } -} -pub trait Component { - fn render(self) -> AnyElement; - - 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 { - self + /// 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 Option -where - V: 'static, - E: 'static + Component, - F: FnOnce(&mut V, &mut ViewContext<'_, V>) -> E + 'static, -{ - type ElementState = AnyElement; - - fn element_id(&self) -> Option { - None - } +impl Element for AnyElement { + type State = (); fn layout( &mut self, view_state: &mut V, - _: Option, + _: Option, cx: &mut ViewContext, - ) -> (LayoutId, Self::ElementState) { - 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, rendered_element) + ) -> (LayoutId, Self::State) { + let layout_id = self.layout(view_state, cx); + (layout_id, ()) } fn paint( - &mut self, + self, _bounds: Bounds, view_state: &mut V, - rendered_element: &mut Self::ElementState, + _: &mut Self::State, cx: &mut ViewContext, ) { - rendered_element.paint(view_state, cx) + self.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 RenderOnce for AnyElement { + type Element = Self; + + fn element_id(&self) -> Option { + AnyElement::element_id(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)) + fn render_once(self) -> Self::Element { + self } } + +// impl Element for Option +// where +// V: 'static, +// E: Element, +// 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 element = (render)(view_state, cx).into_any(); +// let layout_id = element.layout(view_state, cx); +// (layout_id, Some(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 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 a37e3dee2adb5898b1526e3fd944491ed637c6b2..8838e5f6edfa0593c0ba24ac59b979a4765a9f61 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,31 +587,27 @@ 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 } } impl Element for Div { - type ElementState = DivState; - - fn element_id(&self) -> Option { - self.interactivity.element_id.clone() - } + type State = DivState; fn layout( &mut self, view_state: &mut V, - element_state: Option, + element_state: Option, cx: &mut ViewContext, - ) -> (LayoutId, Self::ElementState) { + ) -> (LayoutId, Self::State) { let mut child_layout_ids = SmallVec::new(); let mut interactivity = mem::take(&mut self.interactivity); let (layout_id, interactive_state) = interactivity.layout( @@ -639,10 +635,10 @@ impl Element for Div { } fn paint( - &mut self, + self, bounds: Bounds, view_state: &mut V, - element_state: &mut Self::ElementState, + element_state: &mut Self::State, cx: &mut ViewContext, ) { let mut child_min = point(Pixels::MAX, Pixels::MAX); @@ -658,8 +654,7 @@ impl Element for Div { (child_max - child_min).into() }; - let mut interactivity = mem::take(&mut self.interactivity); - interactivity.paint( + self.interactivity.paint( bounds, content_size, &mut element_state.interactive_state, @@ -679,7 +674,7 @@ impl Element for Div { cx.with_text_style(style.text_style().cloned(), |cx| { cx.with_content_mask(style.overflow_mask(bounds), |cx| { cx.with_element_offset(scroll_offset, |cx| { - for child in &mut self.children { + for child in self.children { child.paint(view_state, cx); } }) @@ -689,13 +684,18 @@ impl Element for Div { }) }, ); - self.interactivity = interactivity; } } -impl Component for Div { - fn render(self) -> AnyElement { - AnyElement::new(self) +impl RenderOnce for Div { + type Element = Self; + + fn element_id(&self) -> Option { + self.interactivity.element_id.clone() + } + + fn render_once(self) -> Self::Element { + self } } @@ -770,7 +770,7 @@ where } pub fn paint( - &mut self, + mut self, bounds: Bounds, content_size: Size, element_state: &mut InteractiveElementState, @@ -786,25 +786,25 @@ where } } - for listener in self.mouse_down_listeners.drain(..) { + for listener in self.mouse_down_listeners { cx.on_mouse_event(move |state, event: &MouseDownEvent, phase, cx| { listener(state, event, &bounds, phase, cx); }) } - for listener in self.mouse_up_listeners.drain(..) { + for listener in self.mouse_up_listeners { cx.on_mouse_event(move |state, event: &MouseUpEvent, phase, cx| { listener(state, event, &bounds, phase, cx); }) } - for listener in self.mouse_move_listeners.drain(..) { + for listener in self.mouse_move_listeners { cx.on_mouse_event(move |state, event: &MouseMoveEvent, phase, cx| { listener(state, event, &bounds, phase, cx); }) } - for listener in self.scroll_wheel_listeners.drain(..) { + for listener in self.scroll_wheel_listeners { cx.on_mouse_event(move |state, event: &ScrollWheelEvent, phase, cx| { listener(state, event, &bounds, phase, cx); }) @@ -840,7 +840,7 @@ where } if cx.active_drag.is_some() { - let drop_listeners = mem::take(&mut self.drop_listeners); + let drop_listeners = self.drop_listeners; cx.on_mouse_event(move |view, event: &MouseUpEvent, phase, cx| { if phase == DispatchPhase::Bubble && bounds.contains_point(&event.position) { if let Some(drag_state_type) = @@ -1062,24 +1062,24 @@ where self.key_context.clone(), element_state.focus_handle.clone(), |_, cx| { - for listener in self.key_down_listeners.drain(..) { + for listener in self.key_down_listeners { cx.on_key_event(move |state, event: &KeyDownEvent, phase, cx| { listener(state, event, phase, cx); }) } - for listener in self.key_up_listeners.drain(..) { + for listener in self.key_up_listeners { cx.on_key_event(move |state, event: &KeyUpEvent, phase, cx| { listener(state, event, phase, cx); }) } - for (action_type, listener) in self.action_listeners.drain(..) { + for (action_type, listener) in self.action_listeners { cx.on_action(action_type, listener) } if let Some(focus_handle) = element_state.focus_handle.as_ref() { - for listener in self.focus_listeners.drain(..) { + for listener in self.focus_listeners { let focus_handle = focus_handle.clone(); cx.on_focus_changed(move |view, event, cx| { listener(view, &focus_handle, event, cx) @@ -1264,19 +1264,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 { } @@ -1293,49 +1293,51 @@ where impl Element for Focusable where - V: 'static, + V: 'static + Render, E: Element, { - type ElementState = E::ElementState; - - fn element_id(&self) -> Option { - self.element.element_id() - } + type State = E::State; fn layout( &mut self, view_state: &mut V, - element_state: Option, + element_state: Option, cx: &mut ViewContext, - ) -> (LayoutId, Self::ElementState) { + ) -> (LayoutId, Self::State) { self.element.layout(view_state, element_state, cx) } fn paint( - &mut self, + self, bounds: Bounds, view_state: &mut V, - element_state: &mut Self::ElementState, + element_state: &mut Self::State, cx: &mut ViewContext, ) { - self.element.paint(bounds, view_state, element_state, cx); + self.element.paint(bounds, view_state, element_state, cx) } } -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 element_id(&self) -> Option { + self.element.element_id() + } + + 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() @@ -1357,71 +1359,73 @@ 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 V: 'static, E: Element, { - type ElementState = E::ElementState; - - fn element_id(&self) -> Option { - self.element.element_id() - } + type State = E::State; fn layout( &mut self, view_state: &mut V, - element_state: Option, + element_state: Option, cx: &mut ViewContext, - ) -> (LayoutId, Self::ElementState) { + ) -> (LayoutId, Self::State) { self.element.layout(view_state, element_state, cx) } fn paint( - &mut self, + self, bounds: Bounds, view_state: &mut V, - element_state: &mut Self::ElementState, + element_state: &mut Self::State, cx: &mut ViewContext, ) { self.element.paint(bounds, view_state, element_state, cx) } } -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 element_id(&self) -> Option { + self.element.element_id() + } + + 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 5376c4001227f952709d3075e8149c0f1fe3008c..a6579b022a649fc5cce99a4b188a9bb017cb4a47 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,35 +34,25 @@ where } } -impl Component for Img { - fn render(self) -> AnyElement { - AnyElement::new(self) - } -} - impl Element for Img { - type ElementState = InteractiveElementState; - - fn element_id(&self) -> Option { - self.interactivity.element_id.clone() - } + type State = InteractiveElementState; fn layout( &mut self, _view_state: &mut V, - element_state: Option, + element_state: Option, cx: &mut ViewContext, - ) -> (LayoutId, Self::ElementState) { + ) -> (LayoutId, Self::State) { self.interactivity.layout(element_state, cx, |style, cx| { cx.request_layout(&style, None) }) } fn paint( - &mut self, + self, bounds: Bounds, _view_state: &mut V, - element_state: &mut Self::ElementState, + element_state: &mut Self::State, cx: &mut ViewContext, ) { self.interactivity.paint( @@ -102,13 +91,25 @@ impl Element for Img { } } +impl RenderOnce for Img { + type Element = Self; + + fn element_id(&self) -> Option { + self.interactivity.element_id.clone() + } + + 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 14a8048d398176bbd8bec49b37bc96b261450ed9..79a3643bdc2bb6e3921e85d9171979bbc8634507 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,31 +51,21 @@ 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 ElementState = OverlayState; - - fn element_id(&self) -> Option { - None - } + type State = OverlayState; fn layout( &mut self, view_state: &mut V, - _: Option, + _: Option, cx: &mut crate::ViewContext, - ) -> (crate::LayoutId, Self::ElementState) { + ) -> (crate::LayoutId, Self::State) { let child_layout_ids = self .children .iter_mut() @@ -92,10 +82,10 @@ impl Element for Overlay { } fn paint( - &mut self, + self, bounds: crate::Bounds, view_state: &mut V, - element_state: &mut Self::ElementState, + element_state: &mut Self::State, cx: &mut crate::ViewContext, ) { if element_state.child_layout_ids.is_empty() { @@ -156,13 +146,25 @@ impl Element for Overlay { } cx.with_element_offset(desired.origin - bounds.origin, |cx| { - for child in &mut self.children { + for child in self.children { child.paint(view_state, cx); } }) } } +impl RenderOnce for Overlay { + type Element = Self; + + fn element_id(&self) -> Option { + None + } + + 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 c1c7691fbf41371cca9cba173485523df2dd7fb3..0940bdd1eb2bf52c18cacc56ba9f8d2776ab43e7 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,35 +23,25 @@ impl Svg { } } -impl Component for Svg { - fn render(self) -> AnyElement { - AnyElement::new(self) - } -} - impl Element for Svg { - type ElementState = InteractiveElementState; - - fn element_id(&self) -> Option { - self.interactivity.element_id.clone() - } + type State = InteractiveElementState; fn layout( &mut self, _view_state: &mut V, - element_state: Option, + element_state: Option, cx: &mut ViewContext, - ) -> (LayoutId, Self::ElementState) { + ) -> (LayoutId, Self::State) { self.interactivity.layout(element_state, cx, |style, cx| { cx.request_layout(&style, None) }) } fn paint( - &mut self, + self, bounds: Bounds, _view_state: &mut V, - element_state: &mut Self::ElementState, + element_state: &mut Self::State, cx: &mut ViewContext, ) where Self: Sized, @@ -66,13 +55,25 @@ impl Element for Svg { } } +impl RenderOnce for Svg { + type Element = Self; + + fn element_id(&self) -> Option { + self.interactivity.element_id.clone() + } + + 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 e3c63523e1244b3005fd1803e6af2fef4e3c5576..c68684c8d39dc676294a62cfdd98da8e81bb27b3 100644 --- a/crates/gpui2/src/elements/text.rs +++ b/crates/gpui2/src/elements/text.rs @@ -1,50 +1,116 @@ use crate::{ - AnyElement, BorrowWindow, Bounds, Component, Element, ElementId, LayoutId, Pixels, - SharedString, Size, TextRun, ViewContext, WrappedLine, + BorrowWindow, Bounds, Element, ElementId, LayoutId, Pixels, RenderOnce, SharedString, Size, + TextRun, ViewContext, WindowContext, WrappedLine, }; +use anyhow::anyhow; use parking_lot::{Mutex, MutexGuard}; use smallvec::SmallVec; use std::{cell::Cell, rc::Rc, sync::Arc}; use util::ResultExt; -pub struct Text { +impl Element for &'static str { + type State = TextState; + + fn layout( + &mut self, + _: &mut V, + _: Option, + cx: &mut ViewContext, + ) -> (LayoutId, Self::State) { + let mut state = TextState::default(); + let layout_id = state.layout(SharedString::from(*self), None, cx); + (layout_id, state) + } + + fn paint( + self, + bounds: Bounds, + _: &mut V, + state: &mut TextState, + cx: &mut ViewContext, + ) { + state.paint(bounds, self, cx) + } +} + +impl RenderOnce for &'static str { + type Element = Self; + + fn element_id(&self) -> Option { + None + } + + fn render_once(self) -> Self::Element { + self + } +} + +impl Element for SharedString { + type State = TextState; + + fn layout( + &mut self, + _: &mut V, + _: Option, + cx: &mut ViewContext, + ) -> (LayoutId, Self::State) { + let mut state = TextState::default(); + let layout_id = state.layout(self.clone(), None, cx); + (layout_id, state) + } + + fn paint( + self, + bounds: Bounds, + _: &mut V, + state: &mut TextState, + cx: &mut ViewContext, + ) { + let text_str: &str = self.as_ref(); + state.paint(bounds, text_str, cx) + } +} + +impl RenderOnce for SharedString { + type Element = Self; + + fn element_id(&self) -> Option { + Some(self.clone().into()) + } + + 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 { - type ElementState = TextState; - - fn element_id(&self) -> Option { - None - } +impl Element for StyledText { + type State = TextState; fn layout( &mut self, _view: &mut V, - element_state: Option, + element_state: Option, cx: &mut ViewContext, - ) -> (LayoutId, Self::ElementState) { + ) -> (LayoutId, Self::State) { let element_state = element_state.unwrap_or_default(); let text_system = cx.text_system().clone(); let text_style = cx.text_style(); @@ -118,16 +184,16 @@ impl Element for Text { } fn paint( - &mut self, + self, bounds: Bounds, _: &mut V, - element_state: &mut Self::ElementState, + element_state: &mut Self::State, cx: &mut ViewContext, ) { let element_state = element_state.lock(); let element_state = element_state .as_ref() - .ok_or_else(|| anyhow::anyhow!("measurement has not been performed on {}", &self.text)) + .ok_or_else(|| anyhow!("measurement has not been performed on {}", &self.text)) .unwrap(); let line_height = element_state.line_height; @@ -139,15 +205,21 @@ impl Element for Text { } } -#[derive(Default, Clone)] -pub struct TextState(Arc>>); +impl RenderOnce for StyledText { + type Element = Self; -impl TextState { - fn lock(&self) -> MutexGuard> { - self.0.lock() + fn element_id(&self) -> Option { + None + } + + fn render_once(self) -> Self::Element { + self } } +#[derive(Default, Clone)] +pub struct TextState(Arc>>); + struct TextStateInner { lines: SmallVec<[WrappedLine; 1]>, line_height: Pixels, @@ -155,9 +227,108 @@ struct TextStateInner { size: Option>, } +impl TextState { + fn lock(&self) -> MutexGuard> { + self.0.lock() + } + + fn layout( + &mut self, + text: SharedString, + runs: Option>, + cx: &mut WindowContext, + ) -> LayoutId { + let text_system = cx.text_system().clone(); + let text_style = cx.text_style(); + let font_size = text_style.font_size.to_pixels(cx.rem_size()); + let line_height = text_style + .line_height + .to_pixels(font_size.into(), cx.rem_size()); + let text = SharedString::from(text); + + let rem_size = cx.rem_size(); + + let runs = if let Some(runs) = runs { + runs + } else { + vec![text_style.to_run(text.len())] + }; + + let layout_id = cx.request_measured_layout(Default::default(), rem_size, { + let element_state = self.clone(); + + move |known_dimensions, available_space| { + let wrap_width = known_dimensions.width.or(match available_space.width { + crate::AvailableSpace::Definite(x) => Some(x), + _ => None, + }); + + if let Some(text_state) = element_state.0.lock().as_ref() { + if text_state.size.is_some() + && (wrap_width.is_none() || wrap_width == text_state.wrap_width) + { + return text_state.size.unwrap(); + } + } + + let Some(lines) = text_system + .shape_text( + &text, + font_size, + &runs[..], + wrap_width, // Wrap if we know the width. + ) + .log_err() + else { + element_state.lock().replace(TextStateInner { + lines: Default::default(), + line_height, + wrap_width, + size: Some(Size::default()), + }); + return Size::default(); + }; + + let mut size: Size = Size::default(); + for line in &lines { + let line_size = line.size(line_height); + size.height += line_size.height; + size.width = size.width.max(line_size.width); + } + + element_state.lock().replace(TextStateInner { + lines, + line_height, + wrap_width, + size: Some(size), + }); + + size + } + }); + + layout_id + } + + fn paint(&mut self, bounds: Bounds, text: &str, cx: &mut WindowContext) { + let element_state = self.lock(); + let element_state = element_state + .as_ref() + .ok_or_else(|| anyhow!("measurement has not been performed on {}", text)) + .unwrap(); + + let line_height = element_state.line_height; + let mut line_origin = bounds.origin; + for line in &element_state.lines { + line.paint(line_origin, line_height, cx).log_err(); + line_origin.y += line.size(line_height).height; + } + } +} + struct InteractiveText { id: ElementId, - text: Text, + text: StyledText, } struct InteractiveTextState { @@ -166,18 +337,14 @@ struct InteractiveTextState { } impl Element for InteractiveText { - type ElementState = InteractiveTextState; - - fn element_id(&self) -> Option { - Some(self.id.clone()) - } + type State = InteractiveTextState; fn layout( &mut self, view_state: &mut V, - element_state: Option, + element_state: Option, cx: &mut ViewContext, - ) -> (LayoutId, Self::ElementState) { + ) -> (LayoutId, Self::State) { if let Some(InteractiveTextState { text_state, clicked_range_ixs, @@ -200,10 +367,10 @@ impl Element for InteractiveText { } fn paint( - &mut self, + self, bounds: Bounds, view_state: &mut V, - element_state: &mut Self::ElementState, + element_state: &mut Self::State, cx: &mut ViewContext, ) { self.text @@ -211,34 +378,14 @@ 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() + fn element_id(&self) -> Option { + Some(self.id.clone()) } -} -// 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 773f9ec8aacfb2ce0ac77cc96b4c30311a406ba5..caf18962ec03f76b9c88298b8642109c2ac350dc 100644 --- a/crates/gpui2/src/elements/uniform_list.rs +++ b/crates/gpui2/src/elements/uniform_list.rs @@ -1,24 +1,24 @@ 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, mem, ops::Range, rc::Rc}; +use std::{cell::RefCell, cmp, ops::Range, rc::Rc}; 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 { @@ -102,18 +102,14 @@ pub struct UniformListState { } impl Element for UniformList { - type ElementState = UniformListState; - - fn element_id(&self) -> Option { - Some(self.id.clone()) - } + type State = UniformListState; fn layout( &mut self, view_state: &mut V, - element_state: Option, + element_state: Option, cx: &mut ViewContext, - ) -> (LayoutId, Self::ElementState) { + ) -> (LayoutId, Self::State) { let max_items = self.item_count; let rem_size = cx.rem_size(); let item_size = element_state @@ -159,10 +155,10 @@ impl Element for UniformList { } fn paint( - &mut self, + self, bounds: Bounds, view_state: &mut V, - element_state: &mut Self::ElementState, + element_state: &mut Self::State, cx: &mut ViewContext, ) { let style = @@ -183,14 +179,17 @@ impl Element for UniformList { height: item_size.height * self.item_count, }; - let mut interactivity = mem::take(&mut self.interactivity); let shared_scroll_offset = element_state .interactive .scroll_offset .get_or_insert_with(Rc::default) .clone(); - interactivity.paint( + let item_height = self + .measure_item(view_state, Some(padded_bounds.size.width), cx) + .height; + + self.interactivity.paint( bounds, content_size, &mut element_state.interactive, @@ -209,9 +208,6 @@ impl Element for UniformList { style.paint(bounds, cx); if self.item_count > 0 { - let item_height = self - .measure_item(view_state, Some(padded_bounds.size.width), cx) - .height; if let Some(scroll_handle) = self.scroll_handle.clone() { scroll_handle.0.borrow_mut().replace(ScrollHandleState { item_height, @@ -233,9 +229,9 @@ impl Element for UniformList { self.item_count, ); - let mut items = (self.render_items)(view_state, visible_range.clone(), cx); + let items = (self.render_items)(view_state, visible_range.clone(), cx); cx.with_z_index(1, |cx| { - for (item, ix) in items.iter_mut().zip(visible_range) { + for (item, ix) in items.into_iter().zip(visible_range) { let item_origin = padded_bounds.origin + point(px(0.), item_height * ix + scroll_offset.y); let available_space = size( @@ -249,7 +245,18 @@ impl Element for UniformList { }) }, ); - self.interactivity = interactivity; + } +} + +impl RenderOnce for UniformList { + type Element = Self; + + fn element_id(&self) -> Option { + Some(self.id.clone()) + } + + fn render_once(self) -> Self::Element { + self } } @@ -287,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 a24509386b5a229102cc483b64084b5c2319849e..98e1c043cd32b4ef168512e58a32d710277731b6 100644 --- a/crates/gpui2/src/gpui2.rs +++ b/crates/gpui2/src/gpui2.rs @@ -78,8 +78,6 @@ use std::{ }; use taffy::TaffyLayoutEngine; -type AnyBox = Box; - pub trait Context { type Result; @@ -123,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, @@ -136,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 80a89ef6257497459c848df60e68187a01e7142d..e988697ca70a9a3e27e33ece81ccc63b9e95d58f 100644 --- a/crates/gpui2/src/interactive.rs +++ b/crates/gpui2/src/interactive.rs @@ -1,6 +1,5 @@ use crate::{ - div, point, Component, Div, FocusHandle, Keystroke, Modifiers, Pixels, Point, Render, - ViewContext, + div, point, Div, Element, FocusHandle, Keystroke, Modifiers, Pixels, Point, Render, ViewContext, }; use smallvec::SmallVec; use std::{any::Any, fmt::Debug, marker::PhantomData, ops::Deref, path::PathBuf}; @@ -64,7 +63,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 +74,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 +192,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 +285,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, Render, Stateful, TestAppContext, VisualContext, }; struct TestView { @@ -298,7 +297,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 { @@ -307,12 +306,7 @@ mod test { .key_context("parent") .on_key_down(|this: &mut TestView, _, _, _| this.saw_key_down = true) .on_action(|this: &mut TestView, _: &TestAction, _| this.saw_action = true) - .child(|this: &mut Self, _cx: &mut ViewContext| { - div() - .key_context("nested") - .track_focus(&this.focus_handle) - .render() - }), + .child(div().key_context("nested").track_focus(&self.focus_handle)), ) } } diff --git a/crates/gpui2/src/prelude.rs b/crates/gpui2/src/prelude.rs index 7c2ad3f07ff8877d83342f1ac60087b9859eacbc..50f48596bcecab152bc5cb933d07bfc49b256602 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 c1750d6dc54bf3c33005d49b8b57d5e453b837de..a1ed8373c5e18d8ba7c46bd172e8dff314957a91 100644 --- a/crates/gpui2/src/view.rs +++ b/crates/gpui2/src/view.rs @@ -1,7 +1,7 @@ use crate::{ - private::Sealed, AnyBox, AnyElement, AnyModel, AnyWeakModel, AppContext, AvailableSpace, - BorrowWindow, Bounds, Component, Element, ElementId, Entity, EntityId, Flatten, FocusHandle, - FocusableView, LayoutId, Model, Pixels, Point, Size, ViewContext, VisualContext, WeakModel, + private::Sealed, AnyElement, AnyModel, AnyWeakModel, AppContext, AvailableSpace, BorrowWindow, + Bounds, Element, ElementId, Entity, EntityId, Flatten, FocusHandle, FocusableView, LayoutId, + Model, Pixels, Point, Render, RenderOnce, Size, ViewContext, VisualContext, WeakModel, WindowContext, }; use anyhow::{Context, Result}; @@ -10,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 {} @@ -65,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), } } @@ -105,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, } @@ -164,7 +152,7 @@ impl Eq for WeakView {} pub struct AnyView { model: AnyModel, layout: fn(&AnyView, &mut WindowContext) -> (LayoutId, Box), - paint: fn(&AnyView, &mut AnyBox, &mut WindowContext), + paint: fn(&AnyView, Box, &mut WindowContext), } impl AnyView { @@ -202,22 +190,16 @@ impl AnyView { cx: &mut WindowContext, ) { cx.with_absolute_element_offset(origin, |cx| { - let (layout_id, mut rendered_element) = (self.layout)(self, cx); + let (layout_id, rendered_element) = (self.layout)(self, cx); cx.window .layout_engine .compute_layout(layout_id, available_space); - (self.paint)(self, &mut rendered_element, cx); + (self.paint)(self, rendered_element, cx); }) } } -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(), @@ -227,37 +209,87 @@ impl From> for AnyView { } } -impl Element for AnyView { - type ElementState = Box; +impl, ParentV: 'static> Element for View { + type State = Option>; + + 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 element_id(&self) -> Option { Some(self.model.entity_id.into()) } + fn render_once(self) -> Self::Element { + self + } +} + +impl Element for AnyView { + type State = Option>; + fn layout( &mut self, - _view_state: &mut ParentViewState, - _element_state: Option, - cx: &mut ViewContext, - ) -> (LayoutId, Self::ElementState) { - (self.layout)(self, cx) + _view_state: &mut V, + _element_state: Option, + cx: &mut ViewContext, + ) -> (LayoutId, Self::State) { + let (layout_id, rendered_element) = (self.layout)(self, cx); + (layout_id, Some(rendered_element)) } fn paint( - &mut self, + mut self, _bounds: Bounds, - _view_state: &mut ParentViewState, - rendered_element: &mut Self::ElementState, - cx: &mut ViewContext, + _view_state: &mut V, + rendered_element: &mut Self::State, + cx: &mut ViewContext, ) { - (self.paint)(self, rendered_element, cx) + (self.paint)(&mut self, rendered_element.take().unwrap(), cx) + } +} + +impl RenderOnce for AnyView { + type Element = Self; + + fn element_id(&self) -> Option { + Some(self.model.entity_id.into()) + } + + fn render_once(self) -> Self::Element { + self } } pub struct AnyWeakView { model: AnyWeakModel, layout: fn(&AnyView, &mut WindowContext) -> (LayoutId, Box), - paint: fn(&AnyView, &mut AnyBox, &mut WindowContext), + paint: fn(&AnyView, Box, &mut WindowContext), } impl AnyWeakView { @@ -271,7 +303,7 @@ impl AnyWeakView { } } -impl From> for AnyWeakView { +impl> From> for AnyWeakView { fn from(view: WeakView) -> Self { Self { model: view.model.into(), @@ -281,67 +313,58 @@ impl From> for AnyWeakView { } } -// impl Render for T -// where -// T: 'static + FnMut(&mut WindowContext) -> E, -// E: 'static + Send + Element, -// { -// type Element = E; - -// fn render(&mut self, cx: &mut ViewContext) -> Self::Element { -// (self)(cx) -// } -// } - -pub struct RenderViewWith { +pub struct RenderViewWith { view: View, - component: Option, -} - -impl Component for RenderViewWith -where - C: 'static + Component, - ParentViewState: 'static, - ViewState: 'static, -{ - fn render(self) -> AnyElement { - AnyElement::new(self) - } + element: Option, } -impl Element for RenderViewWith +impl Element for RenderViewWith where - C: 'static + Component, - ParentViewState: 'static, - ViewState: 'static, + E: 'static + Element, + ParentV: 'static, + V: 'static, { - type ElementState = AnyElement; - - fn element_id(&self) -> Option { - Some(self.view.entity_id().into()) - } + type State = Option>; fn layout( &mut self, - _: &mut ParentViewState, - _: Option, - cx: &mut ViewContext, - ) -> (LayoutId, Self::ElementState) { + _: &mut ParentV, + _: Option, + 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, element) + (layout_id, Some(element)) }) } fn paint( - &mut self, + self, _: Bounds, - _: &mut ParentViewState, - element: &mut Self::ElementState, - cx: &mut ViewContext, + _: &mut ParentV, + element: &mut Self::State, + cx: &mut ViewContext, ) { - self.view.update(cx, |view, cx| element.paint(view, cx)) + 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 element_id(&self) -> Option { + self.element.as_ref().unwrap().element_id() + } + + fn render_once(self) -> Self::Element { + self } } @@ -349,7 +372,7 @@ 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) { @@ -363,14 +386,14 @@ mod any_view { }) } - pub(crate) fn paint( + pub(crate) fn paint>( view: &AnyView, - element: &mut Box, + 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_mut::>().unwrap(); + let element = element.downcast::>().unwrap(); view.update(cx, |view, cx| element.paint(view, cx)) }) } diff --git a/crates/gpui2/src/window.rs b/crates/gpui2/src/window.rs index a3fe05d39fe38fb0df0388a6731559f43a5b0638..c17e7e06f4cf103f7ec23e621ea6f47a57885b9c 100644 --- a/crates/gpui2/src/window.rs +++ b/crates/gpui2/src/window.rs @@ -1,5 +1,5 @@ use crate::{ - key_dispatch::DispatchActionListener, px, size, Action, AnyBox, AnyDrag, AnyView, AppContext, + key_dispatch::DispatchActionListener, px, size, Action, AnyDrag, AnyView, AppContext, AsyncWindowContext, AvailableSpace, Bounds, BoxShadow, Context, Corners, CursorStyle, DevicePixels, DispatchNodeId, DispatchTree, DisplayId, Edges, Effect, Entity, EntityId, EventEmitter, FileDropEvent, Flatten, FocusEvent, FontId, GlobalElementId, GlyphId, Hsla, @@ -187,7 +187,7 @@ 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; } @@ -232,7 +232,7 @@ pub struct Window { // #[derive(Default)] pub(crate) struct Frame { - pub(crate) element_states: HashMap, + pub(crate) element_states: HashMap>, mouse_listeners: HashMap>, pub(crate) dispatch_tree: DispatchTree, pub(crate) focus_listeners: Vec, @@ -1520,7 +1520,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 { @@ -1559,7 +1559,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 { @@ -2335,7 +2335,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> { @@ -2355,7 +2355,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) } @@ -2400,7 +2400,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_render_once.rs b/crates/gpui2_macros/src/derive_render_once.rs new file mode 100644 index 0000000000000000000000000000000000000000..732f2df21f10ebf409935ed04ce17ea5553d9321 --- /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 element_id(&self) -> Option { + None + } + + fn render_once(self) -> Self::Element { + gpui::CompositeElement::new(self) + } + } + }; + + 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 3ce8373689d077f64702584d91394f2285e909f5..6dd817e28033fe1239c4525e69f2f5deeead9042 100644 --- a/crates/gpui2_macros/src/gpui2_macros.rs +++ b/crates/gpui2_macros/src/gpui2_macros.rs @@ -1,16 +1,12 @@ mod action; mod derive_component; +mod derive_render_once; mod register_action; mod style_helpers; mod test; use proc_macro::TokenStream; -#[proc_macro] -pub fn style_helpers(args: TokenStream) -> TokenStream { - style_helpers::style_helpers(args) -} - #[proc_macro_derive(Action)] pub fn action(input: TokenStream) -> TokenStream { action::action(input) @@ -26,6 +22,16 @@ pub fn derive_component(input: TokenStream) -> TokenStream { derive_component::derive_component(input) } +#[proc_macro_derive(RenderOnce, attributes(view))] +pub fn derive_render_once(input: TokenStream) -> TokenStream { + derive_render_once::derive_render_once(input) +} + +#[proc_macro] +pub fn style_helpers(input: TokenStream) -> TokenStream { + style_helpers::style_helpers(input) +} + #[proc_macro_attribute] pub fn test(args: TokenStream, function: TokenStream) -> TokenStream { test::test(args, function) diff --git a/crates/picker2/src/picker2.rs b/crates/picker2/src/picker2.rs index 80c0e2b2199c9a109d8ee6a200279088824b4f70..f29312234c42f69286ffd120b07dac63b5031aff 100644 --- a/crates/picker2/src/picker2.rs +++ b/crates/picker2/src/picker2.rs @@ -1,7 +1,7 @@ use editor::Editor; use gpui::{ - div, prelude::*, uniform_list, AppContext, Component, Div, FocusHandle, FocusableView, - MouseButton, Render, Task, UniformListScrollHandle, View, ViewContext, WindowContext, + div, prelude::*, uniform_list, AppContext, Div, FocusHandle, FocusableView, MouseButton, + Render, Task, UniformListScrollHandle, View, ViewContext, WindowContext, }; use std::{cmp, sync::Arc}; use ui::{prelude::*, v_stack, Divider, Label, TextColor}; @@ -15,7 +15,7 @@ pub struct Picker { } pub trait PickerDelegate: Sized + 'static { - type ListItem: Component>; + type ListItem: RenderOnce>; fn match_count(&self) -> usize; fn selected_index(&self) -> usize; @@ -180,7 +180,7 @@ impl Picker { } } -impl Render for Picker { +impl Render for Picker { type Element = Div; fn render(&mut self, cx: &mut ViewContext) -> Self::Element { diff --git a/crates/project_panel2/src/project_panel.rs b/crates/project_panel2/src/project_panel.rs index 57ede0c961e037cb22d961ee87a1c24b91f7170c..da3ada4c10936a626f143f0392544724787b7217 100644 --- a/crates/project_panel2/src/project_panel.rs +++ b/crates/project_panel2/src/project_panel.rs @@ -9,10 +9,10 @@ use file_associations::FileAssociations; 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, + ClipboardItem, Div, EventEmitter, FocusHandle, Focusable, FocusableView, InteractiveElement, + Model, MouseButton, ParentElement, Pixels, Point, PromptLevel, Render, RenderOnce, Stateful, + StatefulInteractiveElement, Styled, Task, UniformListScrollHandle, View, ViewContext, + VisualContext as _, WeakView, WindowContext, }; use menu::{Confirm, SelectNext, SelectPrev}; use project::{ @@ -247,7 +247,6 @@ impl ProjectPanel { let mut old_dock_position = this.position(cx); ProjectPanelSettings::register(cx); cx.observe_global::(move |this, cx| { - dbg!("OLA!"); let new_dock_position = this.position(cx); if new_dock_position != old_dock_position { old_dock_position = new_dock_position; @@ -1424,7 +1423,7 @@ impl ProjectPanel { } } -impl Render for ProjectPanel { +impl Render for ProjectPanel { type Element = Focusable>>; fn render(&mut self, _cx: &mut gpui::ViewContext) -> Self::Element { diff --git a/crates/search2/src/buffer_search.rs b/crates/search2/src/buffer_search.rs index af876f807ae73059956192777e604adcca141703..3a8368f937f890a9cdc77fbd2b04f5686ba2310c 100644 --- a/crates/search2/src/buffer_search.rs +++ b/crates/search2/src/buffer_search.rs @@ -10,8 +10,8 @@ use collections::HashMap; use editor::Editor; use futures::channel::oneshot; use gpui::{ - actions, div, red, Action, AppContext, Component, Div, EventEmitter, InteractiveComponent, - ParentComponent as _, Render, Styled, Subscription, Task, View, ViewContext, + actions, div, red, Action, AppContext, Div, EventEmitter, InteractiveElement as _, + ParentElement as _, Render, RenderOnce, Styled, Subscription, Task, View, ViewContext, VisualContext as _, WindowContext, }; use project::search::SearchQuery; @@ -63,7 +63,7 @@ pub struct BufferSearchBar { impl EventEmitter for BufferSearchBar {} impl EventEmitter for BufferSearchBar {} -impl Render for BufferSearchBar { +impl Render for BufferSearchBar { type Element = Div; fn render(&mut self, cx: &mut ViewContext) -> Self::Element { // let query_container_style = if self.query_contains_error { @@ -534,7 +534,7 @@ impl BufferSearchBar { self.update_matches(cx) } - fn render_action_button(&self) -> impl Component { + fn render_action_button(&self) -> impl RenderOnce { // let tooltip_style = theme.tooltip.clone(); // let style = theme.search.action_button.clone(); diff --git a/crates/search2/src/search.rs b/crates/search2/src/search.rs index 233975839f847583b5e3fac4998b4cfc95e2f7d3..c138c49d34238d091d274c06264c7783b46ba887 100644 --- a/crates/search2/src/search.rs +++ b/crates/search2/src/search.rs @@ -1,6 +1,6 @@ use bitflags::bitflags; pub use buffer_search::BufferSearchBar; -use gpui::{actions, Action, AppContext, Component}; +use gpui::{actions, Action, AppContext, RenderOnce}; pub use mode::SearchMode; use project::search::SearchQuery; use ui::ButtonVariant; @@ -82,7 +82,7 @@ impl SearchOptions { options } - pub fn as_button(&self, active: bool) -> impl Component { + pub fn as_button(&self, active: bool) -> impl RenderOnce { ui::IconButton::new(0, self.icon()) .on_click({ let action = self.to_toggle_action(); @@ -95,7 +95,7 @@ impl SearchOptions { } } -fn toggle_replace_button(active: bool) -> impl Component { +fn toggle_replace_button(active: bool) -> impl RenderOnce { // todo: add toggle_replace button ui::IconButton::new(0, ui::Icon::Replace) .on_click(|_: &mut V, cx| { @@ -109,7 +109,7 @@ fn toggle_replace_button(active: bool) -> impl Component { fn render_replace_button( action: impl Action + 'static + Send + Sync, icon: ui::Icon, -) -> impl Component { +) -> impl RenderOnce { // todo: add tooltip ui::IconButton::new(0, icon).on_click(move |_: &mut V, cx| { cx.dispatch_action(action.boxed_clone()); diff --git a/crates/search2/src/search_bar.rs b/crates/search2/src/search_bar.rs index 1c4f2a17a6d1c5e13ee6905378272d24f3c96ac8..ffb7c99e27725c533673a0d2d5835f9a6b90bee1 100644 --- a/crates/search2/src/search_bar.rs +++ b/crates/search2/src/search_bar.rs @@ -1,6 +1,6 @@ use std::sync::Arc; -use gpui::{Component, ViewContext}; +use gpui::{RenderOnce, ViewContext}; use ui::{Button, ButtonVariant, IconButton}; use crate::mode::SearchMode; @@ -9,7 +9,7 @@ pub(super) fn render_nav_button( icon: ui::Icon, _active: bool, on_click: impl Fn(&mut V, &mut ViewContext) + 'static + Send + Sync, -) -> impl Component { +) -> impl RenderOnce { // let tooltip_style = cx.theme().tooltip.clone(); // let cursor_style = if active { // CursorStyle::PointingHand diff --git a/crates/storybook2/src/stories/colors.rs b/crates/storybook2/src/stories/colors.rs index 4f8c54fa6fa67404453737f33a79113e018e346f..b690435e01417a2ced66a685f85db4fd9c0cfba1 100644 --- a/crates/storybook2/src/stories/colors.rs +++ b/crates/storybook2/src/stories/colors.rs @@ -5,7 +5,7 @@ use ui::prelude::*; pub struct ColorsStory; -impl Render for ColorsStory { +impl Render for ColorsStory { type Element = Div; fn render(&mut self, cx: &mut ViewContext) -> Self::Element { @@ -28,7 +28,7 @@ impl Render for ColorsStory { div() .w(px(75.)) .line_height(px(24.)) - .child(scale.name().to_string()), + .child(scale.name().clone()), ) .child( div() diff --git a/crates/storybook2/src/stories/focus.rs b/crates/storybook2/src/stories/focus.rs index 571882f1f29c92eeed4e8ec56374879280bf27af..12c7ea81a063181791b0873500242f56a36337b8 100644 --- a/crates/storybook2/src/stories/focus.rs +++ b/crates/storybook2/src/stories/focus.rs @@ -26,7 +26,7 @@ impl FocusStory { } } -impl Render for FocusStory { +impl Render for FocusStory { type Element = Focusable>>; fn render(&mut self, cx: &mut gpui::ViewContext) -> Self::Element { diff --git a/crates/storybook2/src/stories/kitchen_sink.rs b/crates/storybook2/src/stories/kitchen_sink.rs index 507aa8db2d5db7f0d24fd33aaacc34f04a700170..2d31cefed6e15cf51a7e368ef6b1aab99b11639b 100644 --- a/crates/storybook2/src/stories/kitchen_sink.rs +++ b/crates/storybook2/src/stories/kitchen_sink.rs @@ -11,7 +11,7 @@ impl KitchenSinkStory { } } -impl Render for KitchenSinkStory { +impl Render for KitchenSinkStory { type Element = Stateful>; fn render(&mut self, cx: &mut ViewContext) -> Self::Element { diff --git a/crates/storybook2/src/stories/picker.rs b/crates/storybook2/src/stories/picker.rs index a3f9ef5eb82a018619c52f314c00d2578d4e9b7b..7c2412a02ff2cccd2316aa06e65a7b57a79541a5 100644 --- a/crates/storybook2/src/stories/picker.rs +++ b/crates/storybook2/src/stories/picker.rs @@ -1,5 +1,7 @@ use fuzzy::StringMatchCandidate; -use gpui::{div, prelude::*, Div, KeyBinding, Render, Styled, Task, View, WindowContext}; +use gpui::{ + div, prelude::*, Div, KeyBinding, Render, SharedString, Styled, Task, View, WindowContext, +}; use picker::{Picker, PickerDelegate}; use std::sync::Arc; use theme2::ActiveTheme; @@ -54,7 +56,8 @@ impl PickerDelegate for Delegate { let Some(candidate_ix) = self.matches.get(ix) else { return div(); }; - let candidate = self.candidates[*candidate_ix].string.clone(); + // TASK: Make StringMatchCandidate::string a SharedString + let candidate = SharedString::from(self.candidates[*candidate_ix].string.clone()); div() .text_color(colors.text) @@ -202,7 +205,7 @@ impl PickerStory { } } -impl Render for PickerStory { +impl Render for PickerStory { type Element = Div; fn render(&mut self, cx: &mut gpui::ViewContext) -> Self::Element { diff --git a/crates/storybook2/src/stories/scroll.rs b/crates/storybook2/src/stories/scroll.rs index f1bb7b4e7cacb05b0e2cd43a7eae3cfe3275092c..bbab0b1d110a33781447f500c14a01df0ea776e8 100644 --- a/crates/storybook2/src/stories/scroll.rs +++ b/crates/storybook2/src/stories/scroll.rs @@ -10,7 +10,7 @@ impl ScrollStory { } } -impl Render for ScrollStory { +impl Render for ScrollStory { type Element = Stateful>; fn render(&mut self, cx: &mut gpui::ViewContext) -> Self::Element { diff --git a/crates/storybook2/src/stories/text.rs b/crates/storybook2/src/stories/text.rs index 512d680d377564b8240d77b9f4b56f5a8c07efe7..716004dea7132f87c16b6aebe393aa8d662d7cca 100644 --- a/crates/storybook2/src/stories/text.rs +++ b/crates/storybook2/src/stories/text.rs @@ -1,5 +1,5 @@ use gpui::{ - blue, div, red, white, Div, ParentComponent, Render, Styled, View, VisualContext, WindowContext, + blue, div, red, white, Div, ParentElement, Render, Styled, View, VisualContext, WindowContext, }; use ui::v_stack; @@ -11,7 +11,7 @@ impl TextStory { } } -impl Render for TextStory { +impl Render for TextStory { type Element = Div; fn render(&mut self, cx: &mut gpui::ViewContext) -> Self::Element { diff --git a/crates/storybook2/src/stories/z_index.rs b/crates/storybook2/src/stories/z_index.rs index 46ec0f4a3511ea2b3a3cc7999e203a62d35a01d6..087ed913fd711c3e101efb1d400cd7dacfbd2556 100644 --- a/crates/storybook2/src/stories/z_index.rs +++ b/crates/storybook2/src/stories/z_index.rs @@ -1,4 +1,4 @@ -use gpui::{px, rgb, Div, Hsla, Render}; +use gpui::{px, rgb, Div, Hsla, Render, RenderOnce}; use ui::prelude::*; use crate::story::Story; @@ -7,7 +7,7 @@ use crate::story::Story; /// [https://developer.mozilla.org/en-US/docs/Web/CSS/z-index](https://developer.mozilla.org/en-US/docs/Web/CSS/z-index). pub struct ZIndexStory; -impl Render for ZIndexStory { +impl Render for ZIndexStory { type Element = Div; fn render(&mut self, cx: &mut ViewContext) -> Self::Element { @@ -79,17 +79,15 @@ trait Styles: Styled + Sized { impl Styles for Div {} -#[derive(Component)] +#[derive(RenderOnce)] struct ZIndexExample { z_index: u32, } -impl ZIndexExample { - pub fn new(z_index: u32) -> Self { - Self { z_index } - } +impl Component for ZIndexExample { + type Rendered = Div; - fn render(self, _view: &mut V, cx: &mut ViewContext) -> impl Component { + fn render(self, view: &mut V, cx: &mut ViewContext) -> Self::Rendered { div() .relative() .size_full() @@ -109,14 +107,14 @@ impl ZIndexExample { // HACK: Simulate `text-align: center`. .pl(px(24.)) .z_index(self.z_index) - .child(format!( + .child(SharedString::from(format!( "z-index: {}", if self.z_index == 0 { "auto".to_string() } else { self.z_index.to_string() } - )), + ))), ) // Blue blocks. .child( @@ -173,3 +171,9 @@ impl ZIndexExample { ) } } + +impl ZIndexExample { + pub fn new(z_index: u32) -> Self { + Self { z_index } + } +} diff --git a/crates/storybook2/src/storybook2.rs b/crates/storybook2/src/storybook2.rs index a0bc7cd72f10e25fc68674071afce253582cacf9..2a22d91382c45cc2201725f35d32ee9cccc62297 100644 --- a/crates/storybook2/src/storybook2.rs +++ b/crates/storybook2/src/storybook2.rs @@ -105,7 +105,7 @@ impl StoryWrapper { } } -impl Render for StoryWrapper { +impl Render for StoryWrapper { type Element = Div; fn render(&mut self, cx: &mut ViewContext) -> Self::Element { diff --git a/crates/storybook3/src/storybook3.rs b/crates/storybook3/src/storybook3.rs index 9885208b4140cb5cee55026a4a5ed19a45dced57..cb64bd7f0dfabdf11724bf09cc0b6cff3846d34b 100644 --- a/crates/storybook3/src/storybook3.rs +++ b/crates/storybook3/src/storybook3.rs @@ -60,7 +60,7 @@ struct TestView { story: AnyView, } -impl Render for TestView { +impl Render for TestView { type Element = Div; fn render(&mut self, _cx: &mut ViewContext) -> Self::Element { diff --git a/crates/terminal_view2/src/terminal_panel.rs b/crates/terminal_view2/src/terminal_panel.rs index 944cd912bebf57ff7153dcac7ee0452734632e1e..46885913ed97e29e8ec948d9475cae80a8a3a32e 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; @@ -335,7 +335,7 @@ impl TerminalPanel { impl EventEmitter for TerminalPanel {} -impl Render for TerminalPanel { +impl Render for TerminalPanel { type Element = Div; fn render(&mut self, _cx: &mut ViewContext) -> Self::Element { diff --git a/crates/terminal_view2/src/terminal_view.rs b/crates/terminal_view2/src/terminal_view.rs index d8cdcf7c494ba4206ded77ac03045452b7d69a02..a990759d8101fa100c357b91855408aab9d9ee78 100644 --- a/crates/terminal_view2/src/terminal_view.rs +++ b/crates/terminal_view2/src/terminal_view.rs @@ -9,10 +9,10 @@ pub mod terminal_panel; // use crate::terminal_element::TerminalElement; use editor::{scroll::autoscroll::Autoscroll, Editor}; use gpui::{ - actions, div, 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, + actions, div, Action, AnyElement, AppContext, DispatchPhase, Div, Element, 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; @@ -537,7 +537,7 @@ impl TerminalView { } } -impl Render for TerminalView { +impl Render for TerminalView { type Element = Focusable>; fn render(&mut self, cx: &mut ViewContext) -> Self::Element { @@ -577,7 +577,7 @@ impl Render for TerminalView { .children( self.context_menu .clone() - .map(|context_menu| div().z_index(1).absolute().child(context_menu.render())), + .map(|context_menu| div().z_index(1).absolute().child(context_menu)), ) .track_focus(&self.focus_handle) .on_focus_in(Self::focus_in) @@ -755,8 +755,8 @@ impl Item for TerminalView { div() .child(IconElement::new(Icon::Terminal)) - .child(title) - .render() + .child(Label::new(title)) + .into_any() } fn clone_on_split( diff --git a/crates/theme2/src/story.rs b/crates/theme2/src/story.rs index 4296d4f99c4e2f4dfad0cbf1b60eef90efa3d0d0..e0c802fcc72ac3cbd234cfda1bb65e7224e47cdd 100644 --- a/crates/theme2/src/story.rs +++ b/crates/theme2/src/story.rs @@ -1,4 +1,4 @@ -use gpui::{div, Component, Div, ParentComponent, Styled, ViewContext}; +use gpui::{div, Div, Element, ParentElement, SharedString, Styled, ViewContext}; use crate::ActiveTheme; @@ -16,23 +16,26 @@ impl Story { .bg(cx.theme().colors().background) } - pub fn title(cx: &mut ViewContext, title: &str) -> impl Component { + pub fn title(cx: &mut ViewContext, title: SharedString) -> impl Element { div() .text_xl() .text_color(cx.theme().colors().text) - .child(title.to_owned()) + .child(title) } - pub fn title_for(cx: &mut ViewContext) -> impl Component { - Self::title(cx, std::any::type_name::()) + pub fn title_for(cx: &mut ViewContext) -> impl Element { + Self::title(cx, std::any::type_name::().into()) } - pub fn label(cx: &mut ViewContext, label: &str) -> impl Component { + 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/theme2/src/styles/players.rs b/crates/theme2/src/styles/players.rs index b8a983ba51a9cfe784305d947df13e14828b804e..726e4bac56c7d05646c2c0d30e9c0f8df254b116 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/docs/hello-world.md b/crates/ui2/docs/hello-world.md index e8ed3bb9445464e310e22dbc40d9773bf419d2b3..f48dd460b83abcf9bd3a9ccb6528adef06ddf04d 100644 --- a/crates/ui2/docs/hello-world.md +++ b/crates/ui2/docs/hello-world.md @@ -49,13 +49,13 @@ use gpui::hsla impl TodoList { // ... - fn render(self, _view: &mut V, cx: &mut ViewContext) -> impl Component { + fn render(self, _view: &mut V, cx: &mut ViewContext) -> impl Element { div().size_4().bg(hsla(50.0/360.0, 1.0, 0.5, 1.0)) } } ~~~ -Every component needs a render method, and it should return `impl Component`. This basic component will render a 16x16px yellow square on the screen. +Every component needs a render method, and it should return `impl Element`. This basic component will render a 16x16px yellow square on the screen. A couple of questions might come to mind: @@ -87,7 +87,7 @@ We can access the current theme's colors like this: ~~~rust impl TodoList { // ... - fn render(self, _view: &mut V, cx: &mut ViewContext) -> impl Component { + fn render(self, _view: &mut V, cx: &mut ViewContext) -> impl Element { let color = cx.theme().colors() div().size_4().hsla(50.0/360.0, 1.0, 0.5, 1.0) @@ -102,7 +102,7 @@ use gpui::hsla impl TodoList { // ... - fn render(self, _view: &mut V, cx: &mut ViewContext) -> impl Component { + fn render(self, _view: &mut V, cx: &mut ViewContext) -> impl Element { let color = cx.theme().colors() div().size_4().bg(color.surface) @@ -117,7 +117,7 @@ use gpui::hsla impl TodoList { // ... - fn render(self, _view: &mut V, cx: &mut ViewContext) -> impl Component { + fn render(self, _view: &mut V, cx: &mut ViewContext) -> impl Element { let color = cx.theme().colors() div() diff --git a/crates/ui2/src/components/avatar.rs b/crates/ui2/src/components/avatar.rs index d083d8fd463e144e236e5c625caa92f237f3fce4..da76a95cfa8cc5c7b6842802d626a94d567b8652 100644 --- a/crates/ui2/src/components/avatar.rs +++ b/crates/ui2/src/components/avatar.rs @@ -1,27 +1,16 @@ -use gpui::img; - use crate::prelude::*; +use gpui::{img, Img, RenderOnce}; -#[derive(Component)] +#[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, - } - } - - pub fn shape(mut self, shape: Shape) -> Self { - self.shape = shape; - self - } +impl Component for Avatar { + type Rendered = Img; - fn render(self, _view: &mut V, cx: &mut ViewContext) -> impl Component { + fn render(self, _view: &mut V, cx: &mut ViewContext) -> Self::Rendered { let mut img = img(); if self.shape == Shape::Circle { @@ -37,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::*; @@ -48,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 1bb611a86e7559b544d8a49731fe4c85d5128ead..efbb4c9e72e3fc5ff5a7f4a37873cee4bf2e167d 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::{DefiniteLength, Hsla, MouseButton, StatefulInteractiveComponent, WindowContext}; +use gpui::{ + 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 { @@ -164,7 +219,7 @@ impl Button { self.icon.map(|i| IconElement::new(i).color(icon_color)) } - pub fn render(self, _view: &mut V, cx: &mut ViewContext) -> impl Component { + pub fn render(self, _view: &mut V, cx: &mut ViewContext) -> impl Element { let (icon_color, label_color) = match (self.disabled, self.color) { (true, _) => (TextColor::Disabled, TextColor::Disabled), (_, None) => (TextColor::Default, TextColor::Default), @@ -212,24 +267,28 @@ impl Button { } } -#[derive(Component)] +#[derive(RenderOnce)] pub struct ButtonGroup { buttons: Vec>, } -impl ButtonGroup { - pub fn new(buttons: Vec>) -> Self { - Self { buttons } - } +impl Component for ButtonGroup { + type Rendered = Div; - fn render(self, _view: &mut V, cx: &mut ViewContext) -> impl Component { - let mut el = h_stack().text_ui(); + fn render(self, view: &mut V, cx: &mut ViewContext) -> Self::Rendered { + let mut group = h_stack(); - for button in self.buttons { - el = el.child(button.render(_view, cx)); + for button in self.buttons.into_iter() { + group = group.child(button.render(view, cx)); } - el + group + } +} + +impl ButtonGroup { + pub fn new(buttons: Vec>) -> Self { + Self { buttons } } } @@ -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 5b9db177858d8f47a518766ecce925096f5177b5..3e1d4ff6698162f9479b8d384013be5ed5c70ca0 100644 --- a/crates/ui2/src/components/checkbox.rs +++ b/crates/ui2/src/components/checkbox.rs @@ -1,4 +1,4 @@ -use gpui::{div, prelude::*, Component, 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 { @@ -42,7 +166,7 @@ impl Checkbox { self } - pub fn render(self, _view: &mut V, cx: &mut ViewContext) -> impl Component { + pub fn render(self, _view: &mut V, cx: &mut ViewContext) -> impl Element { let group_id = format!("checkbox_group_{:?}", self.id); let icon = match self.checked { @@ -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 fe18cfaff9939d058012c1cf24dae2bf2fc257f6..3814dca4711ab6ef319093af11de58a6554a8413 100644 --- a/crates/ui2/src/components/context_menu.rs +++ b/crates/ui2/src/components/context_menu.rs @@ -1,12 +1,12 @@ use std::cell::RefCell; use std::rc::Rc; -use crate::prelude::*; -use crate::{v_stack, Label, List, ListEntry, ListItem, ListSeparator, ListSubHeader}; +use crate::{prelude::*, v_stack, List, ListItem}; +use crate::{ListEntry, ListSeparator, ListSubHeader}; use gpui::{ overlay, px, Action, AnchorCorner, AnyElement, AppContext, Bounds, DispatchPhase, Div, EventEmitter, FocusHandle, FocusableView, LayoutId, ManagedView, Manager, MouseButton, - MouseDownEvent, Pixels, Point, Render, View, VisualContext, WeakView, + MouseDownEvent, Pixels, Point, Render, RenderOnce, View, VisualContext, WeakView, }; pub enum ContextMenuItem { @@ -24,15 +24,15 @@ pub struct ContextMenu { handle: WeakView, } -impl FocusableView for ContextMenu { +impl FocusableView for ContextMenu { fn focus_handle(&self, _cx: &AppContext) -> FocusHandle { self.focus_handle.clone() } } -impl EventEmitter for ContextMenu {} +impl EventEmitter for ContextMenu {} -impl ContextMenu { +impl ContextMenu { pub fn build( cx: &mut ViewContext, f: impl FnOnce(Self, &mut ViewContext) -> Self, @@ -86,7 +86,7 @@ impl ContextMenu { } } -impl Render for ContextMenu { +impl Render for ContextMenu { type Element = Div; fn render(&mut self, cx: &mut ViewContext) -> Self::Element { @@ -129,7 +129,7 @@ impl Render for ContextMenu { } pub struct MenuHandle { - id: Option, + id: ElementId, child_builder: Option AnyElement + 'static>>, menu_builder: Option) -> View + 'static>>, @@ -138,18 +138,13 @@ pub struct MenuHandle { } impl MenuHandle { - pub fn id(mut self, id: impl Into) -> Self { - self.id = Some(id.into()); - self - } - pub fn menu(mut self, f: impl Fn(&mut V, &mut ViewContext) -> View + 'static) -> Self { self.menu_builder = Some(Rc::new(f)); 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 } @@ -167,9 +162,9 @@ impl MenuHandle { } } -pub fn menu_handle() -> MenuHandle { +pub fn menu_handle(id: impl Into) -> MenuHandle { MenuHandle { - id: None, + id: id.into(), child_builder: None, menu_builder: None, anchor: None, @@ -185,18 +180,14 @@ pub struct MenuHandleState { menu_element: Option>, } impl Element for MenuHandle { - type ElementState = MenuHandleState; - - fn element_id(&self) -> Option { - Some(self.id.clone().expect("menu_handle must have an id()")) - } + type State = MenuHandleState; fn layout( &mut self, view_state: &mut V, - element_state: Option, + element_state: Option, cx: &mut crate::ViewContext, - ) -> (gpui::LayoutId, Self::ElementState) { + ) -> (gpui::LayoutId, Self::State) { let (menu, position) = if let Some(element_state) = element_state { (element_state.menu, element_state.position) } else { @@ -212,9 +203,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 @@ -244,22 +235,22 @@ impl Element for MenuHandle { } fn paint( - &mut self, + self, bounds: Bounds, view_state: &mut V, - element_state: &mut Self::ElementState, + element_state: &mut Self::State, cx: &mut crate::ViewContext, ) { - if let Some(child) = element_state.child_element.as_mut() { + if let Some(child) = element_state.child_element.take() { child.paint(view_state, cx); } - if let Some(menu) = element_state.menu_element.as_mut() { + if let Some(menu) = element_state.menu_element.take() { menu.paint(view_state, cx); return; } - let Some(builder) = self.menu_builder.clone() else { + let Some(builder) = self.menu_builder else { return; }; let menu = element_state.menu.clone(); @@ -300,9 +291,15 @@ impl Element for MenuHandle { } } -impl Component for MenuHandle { - fn render(self) -> AnyElement { - AnyElement::new(self) +impl RenderOnce for MenuHandle { + type Element = Self; + + fn element_id(&self) -> Option { + Some(self.id.clone()) + } + + fn render_once(self) -> Self::Element { + self } } @@ -312,12 +309,12 @@ pub use stories::*; #[cfg(feature = "stories")] mod stories { use super::*; - use crate::story::Story; + use crate::{story::Story, Label}; use gpui::{actions, Div, Render}; actions!(PrintCurrentDate, PrintBestFood); - fn build_menu( + fn build_menu>( cx: &mut ViewContext, header: impl Into, ) -> View> { @@ -337,7 +334,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 { @@ -360,28 +357,24 @@ mod stories { .flex_col() .justify_between() .child( - menu_handle() - .id("test2") + menu_handle("test2") .child(|is_open| { Label::new(if is_open { "TOP LEFT" } else { "RIGHT CLICK ME" }) - .render() }) .menu(move |_, cx| build_menu(cx, "top left")), ) .child( - menu_handle() - .id("test1") + menu_handle("test1") .child(|is_open| { Label::new(if is_open { "BOTTOM LEFT" } else { "RIGHT CLICK ME" }) - .render() }) .anchor(AnchorCorner::BottomLeft) .attach(AnchorCorner::TopLeft) @@ -394,29 +387,25 @@ mod stories { .flex_col() .justify_between() .child( - menu_handle() - .id("test3") + menu_handle("test3") .child(|is_open| { Label::new(if is_open { "TOP RIGHT" } else { "RIGHT CLICK ME" }) - .render() }) .anchor(AnchorCorner::TopRight) .menu(move |_, cx| build_menu(cx, "top right")), ) .child( - menu_handle() - .id("test4") + menu_handle("test4") .child(|is_open| { Label::new(if is_open { "BOTTOM RIGHT" } 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 f138290f17fd3b6fc325f0972f62f6aa6f996ed1..95750d5b47e77ab60e6320218339a01d15f9685c 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 Component { - 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 5ebfc7a4ff234167ee2fed2f98acd88a4e423047..206f2e26db2219c8006a67c4081c0753a75d20fb 100644 --- a/crates/ui2/src/components/divider.rs +++ b/crates/ui2/src/components/divider.rs @@ -1,3 +1,5 @@ +use gpui::{Div, RenderOnce}; + use crate::prelude::*; enum DividerDirection { @@ -5,12 +7,29 @@ enum DividerDirection { Vertical, } -#[derive(Component)] +#[derive(RenderOnce)] pub struct Divider { direction: DividerDirection, inset: bool, } +impl Component for Divider { + type Rendered = Div; + + fn render(self, view: &mut V, cx: &mut ViewContext) -> Self::Rendered { + div() + .map(|this| match self.direction { + DividerDirection::Horizontal => { + this.h_px().w_full().when(self.inset, |this| this.mx_1p5()) + } + DividerDirection::Vertical => { + this.w_px().h_full().when(self.inset, |this| this.my_1p5()) + } + }) + .bg(cx.theme().colors().border_variant) + } +} + impl Divider { pub fn horizontal() -> Self { Self { @@ -31,7 +50,7 @@ impl Divider { self } - fn render(self, _view: &mut V, cx: &mut ViewContext) -> impl Component { + fn render(self, _view: &mut V, cx: &mut ViewContext) -> impl Element { div() .map(|this| match self.direction { DividerDirection::Horizontal => { diff --git a/crates/ui2/src/components/facepile.rs b/crates/ui2/src/components/facepile.rs index efac4925f80a579bfc9deeca273fbf64aad10c5f..8bb3c9b2e08ffe0a40487422ad33423622515954 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 Component { + 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 92c09ed975b996757c21f0e3d18b81bbda7f9c80..72c2807144fece8810dab0667614a317a42c4e84 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::*; @@ -133,13 +133,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 { @@ -167,7 +184,7 @@ impl IconElement { self } - fn render(self, _view: &mut V, cx: &mut ViewContext) -> impl Component { + fn render(self, _view: &mut V, cx: &mut ViewContext) -> impl Element { let svg_size = match self.size { IconSize::Small => rems(0.75), IconSize::Medium => rems(0.9375), @@ -195,7 +212,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 23d920835e63a04a437e97f9d708264f767d5747..416c14fe2df2cce8a9a076c16c547388aa270482 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,64 @@ 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() + // Nate: Trying to figure out the right places we want to show a + // hover state here. I think it is a bit heavy to have it on every + // place we use an icon button. + // .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,58 +137,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 Component { - 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() - // Nate: Trying to figure out the right places we want to show a - // hover state here. I think it is a bit heavy to have it on every - // place we use an icon button. - // .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 83030ebbee066b213ab9594106b7c6610e7f64e3..33c0307cd41d97c8b4b63c0abd663e25b2d0e602 100644 --- a/crates/ui2/src/components/indicator.rs +++ b/crates/ui2/src/components/indicator.rs @@ -1,16 +1,30 @@ -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 } - fn render(self, _view: &mut V, cx: &mut ViewContext) -> impl Component { + fn render(self, _view: &mut V, cx: &mut ViewContext) -> impl Element { div() .rounded_full() .border_2() diff --git a/crates/ui2/src/components/input.rs b/crates/ui2/src/components/input.rs index 42de03db126f6b4d1060f739e350fae7c480b48f..ea0312ab825d5338de8cf70fa179b572c5707d6e 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,44 +18,10 @@ pub struct Input { is_active: bool, } -impl Input { - pub fn new(placeholder: impl Into) -> Self { - Self { - placeholder: placeholder.into(), - value: "".to_string(), - state: InteractionState::default(), - variant: InputVariant::default(), - disabled: false, - is_active: false, - } - } +impl Component for Input { + type Rendered = Stateful>; - pub fn value(mut self, value: String) -> Self { - self.value = value; - self - } - - pub fn state(mut self, state: InteractionState) -> Self { - self.state = state; - self - } - - pub fn variant(mut self, variant: InputVariant) -> Self { - self.variant = variant; - self - } - - pub fn disabled(mut self, disabled: bool) -> Self { - self.disabled = disabled; - self - } - - pub fn is_active(mut self, is_active: bool) -> Self { - self.is_active = is_active; - self - } - - fn render(self, _view: &mut V, cx: &mut ViewContext) -> impl Component { + 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, @@ -93,7 +59,7 @@ impl Input { .active(|style| style.bg(input_active_bg)) .flex() .items_center() - .child(div().flex().items_center().text_ui_sm().map(|this| { + .child(div().flex().items_center().text_ui_sm().map(move |this| { if self.value.is_empty() { this.child(placeholder_label) } else { @@ -103,6 +69,44 @@ impl Input { } } +impl Input { + pub fn new(placeholder: impl Into) -> Self { + Self { + placeholder: placeholder.into(), + value: "".to_string(), + state: InteractionState::default(), + variant: InputVariant::default(), + disabled: false, + is_active: false, + } + } + + pub fn value(mut self, value: String) -> Self { + self.value = value; + self + } + + pub fn state(mut self, state: InteractionState) -> Self { + self.state = state; + self + } + + pub fn variant(mut self, variant: InputVariant) -> Self { + self.variant = variant; + self + } + + pub fn disabled(mut self, disabled: bool) -> Self { + self.disabled = disabled; + self + } + + pub fn is_active(mut self, is_active: bool) -> Self { + self.is_active = is_active; + self + } +} + #[cfg(feature = "stories")] pub use 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 69396274faba449d8e32c6eace4536e35c7a2085..7056dcce56fbe20b3057e3b83fc3d21d3fbbfcc0 100644 --- a/crates/ui2/src/components/keybinding.rs +++ b/crates/ui2/src/components/keybinding.rs @@ -1,9 +1,7 @@ -use gpui::{actions, Action}; -use strum::EnumIter; - use crate::prelude::*; +use gpui::{Action, Div, RenderOnce}; -#[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 +10,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)) - } - - pub fn new(key_binding: gpui::KeyBinding) -> Self { - Self { key_binding } - } +impl Component for KeyBinding { + type Rendered = Div; - fn render(self, _view: &mut V, cx: &mut ViewContext) -> impl Component { + fn render(self, view: &mut V, cx: &mut ViewContext) -> Self::Rendered { div() .flex() .gap_2() @@ -42,17 +31,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 Component { + fn render(self, view: &mut V, cx: &mut ViewContext) -> Self::Rendered { + let _view: &mut V = view; div() .px_2() .py_0() @@ -64,20 +65,10 @@ impl Key { } } -// 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)] -pub enum ModifierKey { - Control, - Alt, - Command, - Shift, -} - -actions!(NoAction); - -pub fn binding(key: &str) -> gpui::KeyBinding { - gpui::KeyBinding::new(key, NoAction {}, None) +impl Key { + pub fn new(key: impl Into) -> Self { + Self { key: key.into() } + } } #[cfg(feature = "stories")] @@ -87,12 +78,18 @@ pub use stories::*; mod stories { use super::*; pub use crate::KeyBinding; - use crate::{binding, Story}; - use gpui::{Div, Render}; + use crate::Story; + use gpui::{actions, Div, Render}; use itertools::Itertools; pub struct KeybindingStory; - impl Render for KeybindingStory { + actions!(NoAction); + + pub fn binding(key: &str) -> gpui::KeyBinding { + gpui::KeyBinding::new(key, NoAction {}, None) + } + + 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 1beee5c8b731bdcf984e53818bec4e960f6f41da..7afb00a96ece904d51f0c1dfd4afc35637500b00 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,38 +68,10 @@ pub struct Label { strikethrough: bool, } -impl Label { - pub fn new(label: impl Into) -> Self { - Self { - label: label.into(), - size: LabelSize::Default, - line_height_style: LineHeightStyle::default(), - color: TextColor::Default, - 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 line_height_style(mut self, line_height_style: LineHeightStyle) -> Self { - self.line_height_style = line_height_style; - self - } - - pub fn set_strikethrough(mut self, strikethrough: bool) -> Self { - self.strikethrough = strikethrough; - self - } +impl Component for Label { + type Rendered = Div; - fn render(self, _view: &mut V, cx: &mut ViewContext) -> impl Component { + fn render(self, _view: &mut V, cx: &mut ViewContext) -> Self::Rendered { div() .when(self.strikethrough, |this| { this.relative().child( @@ -124,24 +95,13 @@ impl Label { } } -#[derive(Component)] -pub struct HighlightedLabel { - label: SharedString, - size: LabelSize, - color: TextColor, - highlight_indices: Vec, - 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 { +impl Label { + pub fn new(label: impl Into) -> Self { Self { label: label.into(), size: LabelSize::Default, + line_height_style: LineHeightStyle::default(), color: TextColor::Default, - highlight_indices, strikethrough: false, } } @@ -156,12 +116,30 @@ impl HighlightedLabel { self } + pub fn line_height_style(mut self, line_height_style: LineHeightStyle) -> Self { + self.line_height_style = line_height_style; + self + } + pub fn set_strikethrough(mut self, strikethrough: bool) -> Self { self.strikethrough = strikethrough; self } +} - fn render(self, _view: &mut V, cx: &mut ViewContext) -> impl Component { +#[derive(RenderOnce)] +pub struct HighlightedLabel { + label: SharedString, + size: LabelSize, + color: TextColor, + highlight_indices: Vec, + strikethrough: bool, +} + +impl Component for HighlightedLabel { + type Rendered = Div; + + 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 07ff577ce02f45732d7712eccea6ed06c85be707..485253d4dfc2db6918be7133518e3e9ce0bda77f 100644 --- a/crates/ui2/src/components/list.rs +++ b/crates/ui2/src/components/list.rs @@ -1,7 +1,6 @@ +use gpui::{div, Div, RenderOnce, Stateful, StatefulInteractiveElement}; use std::rc::Rc; -use gpui::{div, Div, Stateful, StatefulInteractiveComponent}; - use crate::settings::user_settings; use crate::{ disclosure_control, h_stack, v_stack, Avatar, Icon, IconElement, IconSize, Label, Toggle, @@ -24,7 +23,7 @@ pub enum ListHeaderMeta { Text(Label), } -#[derive(Component)] +#[derive(RenderOnce)] pub struct ListHeader { label: SharedString, left_icon: Option, @@ -33,33 +32,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 Component { + fn render(self, view: &mut V, cx: &mut ViewContext) -> Self::Rendered { let disclosure_control = disclosure_control(self.toggle); let meta = match self.meta { @@ -81,11 +57,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() @@ -119,7 +90,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, @@ -140,7 +198,7 @@ impl ListSubHeader { self } - fn render(self, _view: &mut V, cx: &mut ViewContext) -> impl Component { + fn render(self, _view: &mut V, cx: &mut ViewContext) -> impl Element { h_stack().flex_1().w_full().relative().py_1().child( div() .h_6() @@ -200,14 +258,6 @@ impl From for ListItem { } impl ListItem { - fn render(self, view: &mut V, ix: usize, cx: &mut ViewContext) -> impl Component { - match self { - ListItem::Entry(entry) => div().child(entry.render(ix, cx)), - ListItem::Separator(separator) => div().child(separator.render(view, cx)), - ListItem::Header(header) => div().child(header.render(view, cx)), - } - } - pub fn new(label: Label) -> Self { Self::Entry(ListEntry::new(label)) } @@ -219,8 +269,17 @@ impl ListItem { None } } + + fn render(self, view: &mut V, ix: usize, cx: &mut ViewContext) -> Div { + match self { + ListItem::Entry(entry) => div().child(entry.render(ix, cx)), + ListItem::Separator(separator) => div().child(separator.render(view, cx)), + ListItem::Header(header) => div().child(header.render(view, cx)), + } + } } +// #[derive(RenderOnce)] pub struct ListEntry { disabled: bool, // TODO: Reintroduce this @@ -376,20 +435,24 @@ impl ListEntry { } } -#[derive(Clone, Component)] +#[derive(RenderOnce, Clone)] pub struct ListSeparator; impl ListSeparator { pub fn new() -> Self { Self } +} + +impl Component for ListSeparator { + type Rendered = Div; - fn render(self, _view: &mut V, cx: &mut ViewContext) -> impl Component { + 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 @@ -399,6 +462,31 @@ 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 + .into_iter() + .enumerate() + .map(|(ix, item)| item.render(view, ix, cx)), + ), + (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 { @@ -424,7 +512,7 @@ impl List { self } - fn render(self, view: &mut V, cx: &mut ViewContext) -> impl Component { + fn render(self, view: &mut V, cx: &mut ViewContext) -> impl Element { let list_content = match (self.items.is_empty(), self.toggle) { (false, _) => div().children( self.items diff --git a/crates/ui2/src/components/modal.rs b/crates/ui2/src/components/modal.rs index c3d71a78d8dd873d1d898da43edb69fadca66188..da76a79b29b2c4a7c9910d00a2ab88641668f996 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(), - } - } - - 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 - } +impl Component for Modal { + type Rendered = Stateful>; - 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 Component { + 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 aeb2aa6ed901f8f867109f6687e100d0dc061ccb..dd5165e49fd59863d6bc845f603e47bda5890773 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, @@ -22,7 +22,7 @@ impl NotificationToast { self } - fn render(self, _view: &mut V, cx: &mut ViewContext) -> impl Component { + fn render(self, _view: &mut V, cx: &mut ViewContext) -> impl Element { h_stack() .z_index(5) .absolute() diff --git a/crates/ui2/src/components/palette.rs b/crates/ui2/src/components/palette.rs index 5adf794a5efcc6435a7f302fe94f0c59bf530b63..9870c9e08675dc77a682f054023c926bcd0bad16 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,41 +12,12 @@ pub struct Palette { default_order: OrderMethod, } -impl Palette { - pub fn new(id: impl Into) -> Self { - Self { - id: id.into(), - input_placeholder: "Find something...".into(), - empty_string: "No items found.".into(), - items: vec![], - default_order: OrderMethod::default(), - } - } - - pub fn items(mut self, items: Vec) -> Self { - self.items = items; - self - } - - pub fn placeholder(mut self, input_placeholder: impl Into) -> Self { - self.input_placeholder = input_placeholder.into(); - self - } - - pub fn empty_string(mut self, empty_string: impl Into) -> Self { - self.empty_string = empty_string.into(); - self - } +impl Component for Palette { + type Rendered = Stateful>; - // TODO: Hook up sort order - pub fn default_order(mut self, default_order: OrderMethod) -> Self { - self.default_order = default_order; - self - } - - fn render(self, _view: &mut V, cx: &mut ViewContext) -> impl Component { + fn render(self, view: &mut V, cx: &mut ViewContext) -> Self::Rendered { v_stack() - .id(self.id.clone()) + .id(self.id) .w_96() .rounded_lg() .bg(cx.theme().colors().elevated_surface_background) @@ -53,9 +26,11 @@ impl Palette { .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(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() @@ -72,12 +47,9 @@ impl Palette { .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), - ), - ) + Some(h_stack().justify_between().px_2().py_1().child( + Label::new(self.empty_string).color(TextColor::Muted), + )) } else { None }] @@ -104,13 +76,64 @@ impl Palette { } } -#[derive(Component)] +impl Palette { + pub fn new(id: impl Into) -> Self { + Self { + id: id.into(), + input_placeholder: "Find something...".into(), + empty_string: "No items found.".into(), + items: vec![], + default_order: OrderMethod::default(), + } + } + + pub fn items(mut self, items: Vec) -> Self { + self.items = items; + self + } + + pub fn placeholder(mut self, input_placeholder: impl Into) -> Self { + self.input_placeholder = input_placeholder.into(); + self + } + + pub fn empty_string(mut self, empty_string: impl Into) -> Self { + self.empty_string = empty_string.into(); + self + } + + // TODO: Hook up sort order + pub fn default_order(mut self, default_order: OrderMethod) -> Self { + self.default_order = default_order; + self + } +} + +#[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 Component { - 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 d9fc50dd923cc00084f257387efcbb1705efa9f1..38d68ab3eaf7e89ee59b301a296b6aed1c092b3a 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 Component { - 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 1a1231e6c4567e9e45cf98bc6e46038626a69309..1af3ab5c5a2e545a32bfb69a1860f18bfff84e79 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 Component { + 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 820fe5b361c93560dc7c208313ac80f7b23c069f..2d7dadac1a3d2300e428a538c5b13c93b2c954a2 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, - } - } - - 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 - } +impl Component for Tab { + type Rendered = Stateful>; - 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 Component { + 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 0fcfe6038b97e3b6ba8cc3f88db8cd12f8b2bfc0..153f9c7fdb69c6bebe51863ce80e2093be9f583e 100644 --- a/crates/ui2/src/components/toast.rs +++ b/crates/ui2/src/components/toast.rs @@ -1,5 +1,6 @@ use crate::prelude::*; -use gpui::{prelude::*, AnyElement}; +use gpui::{prelude::*, AnyElement, RenderOnce}; +use gpui::{Div, Element}; use smallvec::SmallVec; #[derive(Default, Debug, PartialEq, Eq, Clone, Copy)] @@ -21,12 +22,38 @@ pub enum ToastOrigin { /// they are actively showing the a process in progress. /// /// Only one toast may be visible at a time. -#[derive(Component)] +#[derive(RenderOnce)] pub struct Toast { origin: ToastOrigin, children: SmallVec<[AnyElement; 2]>, } +impl Component for Toast { + type Rendered = Div; + + fn render(self, view: &mut V, cx: &mut ViewContext) -> Self::Rendered { + let mut div = div(); + + if self.origin == ToastOrigin::Bottom { + div = div.right_1_2(); + } else { + div = div.right_2(); + } + + 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 { Self { @@ -35,7 +62,7 @@ impl Toast { } } - fn render(self, _view: &mut V, cx: &mut ViewContext) -> impl Component { + fn render(self, _view: &mut V, cx: &mut ViewContext) -> impl Element { let mut div = div(); if self.origin == ToastOrigin::Bottom { @@ -58,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 } @@ -77,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 8388e2753108c5f3cbec688dbae1ea61f985a597..afd03c8b8908d5c8731c6d7ca29eed8086607d48 100644 --- a/crates/ui2/src/components/toggle.rs +++ b/crates/ui2/src/components/toggle.rs @@ -1,4 +1,4 @@ -use gpui::{div, Component, ParentComponent}; +use gpui::{div, Element, ParentElement}; use crate::{Icon, IconElement, IconSize, TextColor}; @@ -44,7 +44,7 @@ impl From for Toggle { } } -pub fn disclosure_control(toggle: Toggle) -> impl Component { +pub fn disclosure_control(toggle: Toggle) -> impl Element { match (toggle.is_toggleable(), toggle.is_toggled()) { (false, _) => div(), (_, true) => div().child( diff --git a/crates/ui2/src/components/tool_divider.rs b/crates/ui2/src/components/tool_divider.rs index 8a9bbad97f1938c86660a877c8d7562f26b2eb2a..4b59ffe2d867eee875366a36bbd9d382dea7cf14 100644 --- a/crates/ui2/src/components/tool_divider.rs +++ b/crates/ui2/src/components/tool_divider.rs @@ -1,14 +1,23 @@ 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 } - fn render(self, _view: &mut V, cx: &mut ViewContext) -> impl Component { + fn render(self, _view: &mut V, cx: &mut ViewContext) -> impl Element { div().w_px().h_3().bg(cx.theme().colors().border) } } diff --git a/crates/ui2/src/components/tooltip.rs b/crates/ui2/src/components/tooltip.rs index a8dae6c97fd4486d848905784b1db83cedf56143..33fadf122f924d66c0da19be45e733f20eb252f9 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 d4abb78c21c701dae15e5b35264f50cde3e4c438..876fec555efc82eaf58c133ef1748929fd64a97b 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 dd296cee5d4587619f901ed90f97af4fc7c707fb..673766c41108b2dd170e0ce08414bbe25c6872a9 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 c98cfa012f261c8a3e77251370e2265e057158ec..8a482e155aade7a2e707769a442a85234736c47f 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 Component { + 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 Component { + 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 Component { + 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 f111dad83024f539d033701bfab66a7c10dbb446..33f4e4b2083a8389624f5729ae32c4ed2e318ad2 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 Component { + 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 fd43a5b3bf58556ca003e00114b1f060f82ba453..532776fd7f5377f78c7c2b7fb8c9b79ff5e11365 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 } - } - - fn render_separator(&self, cx: &WindowContext) -> Div { - div() - .child(" › ") - .text_color(cx.theme().colors().text_muted) - } +impl Component for Breadcrumb { + type Rendered = Stateful>; - fn render(self, view_state: &mut V, cx: &mut ViewContext) -> impl Component { + 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 aa4bebc9d5d5cdaa23f1429af7d64b71751c99c0..ac4665614b33da04d8bad30df4256f1d2a74d9db 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 { @@ -154,7 +169,7 @@ impl Buffer { self } - fn render_row(row: BufferRow, cx: &WindowContext) -> impl Component { + fn render_row(row: BufferRow, cx: &WindowContext) -> impl Element { let line_background = if row.current { cx.theme().colors().editor_active_line_background } else { @@ -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())), ), ) }) @@ -202,7 +217,7 @@ impl Buffer { })) } - fn render_rows(&self, cx: &WindowContext) -> Vec> { + fn render_rows(&self, cx: &WindowContext) -> Vec> { match &self.rows { Some(rows) => rows .rows @@ -213,7 +228,7 @@ impl Buffer { } } - fn render(self, _view: &mut V, cx: &mut ViewContext) -> impl Component { + fn render(self, _view: &mut V, cx: &mut ViewContext) -> impl Element { let rows = self.render_rows(cx); v_stack() @@ -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 996ac6d2531306d444f81c8d67e7451db4a9e312..3a894659114a5d976d1a476c7419aa9fde92b46c 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 7e2846a3f6b66f8079672a8edab09dd00c19f120..00655364bfe7e3dd78177c116b783db264cf0772 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(), - } - } - - pub fn messages(mut self, messages: Vec) -> Self { - self.messages = messages; - self - } +impl Component for ChatPanel { + type Rendered = Stateful>; - fn render(self, _view: &mut V, cx: &mut ViewContext) -> impl Component { + 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 Component { + 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 256a648c0d42e39cfaccc9c4115d0ff431df8c85..e5fafa62f143a15415610236a460f4271874f527 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 Component { + 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 8a9461c796d053e45fb768f3db4c14f1364f0d27..032e3bc639a771736e65e08ea5d0b9d37e559f86 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 Component { + 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 c5622f5be6ddcbfe735b6befa4abd8d8ea47ae95..d0b11a3330c5cc992554d914058eb8ace274d14d 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 Component { - 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 bd34c22805a972fca02c62a3996778c144feebe2..799716c2705d5b3d50f34a196294ab5b26706ceb 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 694ca78e9c92353a7097f33d7ec133278b063bb0..c6a8457e0b26e9cc15be7fc6173a9d580c60084c 100644 --- a/crates/ui2/src/to_extract/language_selector.rs +++ b/crates/ui2/src/to_extract/language_selector.rs @@ -1,17 +1,42 @@ 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() } } - fn render(self, _view: &mut V, cx: &mut ViewContext) -> impl Component { + fn render(self, _view: &mut V, cx: &mut ViewContext) -> impl Element { div().id(self.id.clone()).child( Palette::new("palette") .items(vec![ @@ -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 78a22d51d078bc6293e849f8898b53ee2c363109..7967a1e590eded5add114cff2eff0f82ac2bfaea 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 Component { + 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 f56194fc473d90507fba44b1cb3814b3f1b814b7..f45b12f0c283d035827b7355f8318a1f479f2897 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 Component { + 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, @@ -241,7 +324,7 @@ impl Notification { self } - fn render_meta_items(&self, cx: &mut ViewContext) -> impl Component { + fn render_meta_items(&self, cx: &mut ViewContext) -> impl Element { if let Some(meta) = &self.meta { h_stack().children( meta.items @@ -260,87 +343,12 @@ impl Notification { } } - fn render_slot(&self, cx: &mut ViewContext) -> impl Component { + 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 Component { - 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 288419d8bf8ebdb1af71b668684ebf38619ce73b..92bf87783fdb5c0ee7a0b95cad7b5591c5b35766 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 Component { + 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 Component { + 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 018f9a4bf10154504df580a1c95e31fe0a4a1016..06735356c7f0b178900220742d4a6617c3dea9da 100644 --- a/crates/ui2/src/to_extract/project_panel.rs +++ b/crates/ui2/src/to_extract/project_panel.rs @@ -3,18 +3,56 @@ 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() } } - fn render(self, _view: &mut V, cx: &mut ViewContext) -> impl Component { + fn render(self, _view: &mut V, cx: &mut ViewContext) -> impl Element { div() .id(self.id.clone()) .flex() @@ -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 3d4f551490a47ef5eab616ebf1e2b1f6f0a0a726..3ebffb6beffe31920cdccd0a91b9e1970b16ae29 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 Component { + 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 eee96ecad85f0b39ecda110fe3df86340190b155..b07c45aa75c92e00dc8254adb33cdb30054baa8e 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 3b4b5cc2205ebd015ba40b050d058384897be1e4..80c165b97ea8e98d9f752c85dcd2c4d077392acc 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 Component { + 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 6c36f35152d8082a3da7fda855a5fc58084b3ae2..4cc43d49831cea1327a2b80b71d8879ddb87e677 100644 --- a/crates/ui2/src/to_extract/terminal.rs +++ b/crates/ui2/src/to_extract/terminal.rs @@ -1,17 +1,84 @@ -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 } - fn render(self, _view: &mut V, cx: &mut ViewContext) -> impl Component { + fn render(self, _view: &mut V, cx: &mut ViewContext) -> impl Element { let can_navigate_back = true; let can_navigate_forward = false; @@ -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 7f911b50bfa8d9b6273126053a7e791e41754989..7b08195a57b5460f1d9782a03a25a003b094eb5e 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 Component { + 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 1a106cbf7a5708143aed61b4c6aa5f32c955415a..167d8ca0bdd4140aa71db6dd2ad017ba5a633a3a 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 81918f34a790b1ec91aa4ac33cad210972b191cf..884a72d2c75183817313c2823d1ba827a8b46dfa 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 Component) -> 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 Component) -> 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 Component { - 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 245ff377f2146579f5532123a456f2f1ae498cfc..5164e74159fa4a87a04648610396a24dbc516cc3 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 Component { + 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)] -pub struct TrafficLights { - window_has_focus: bool, -} - -impl TrafficLights { - pub fn new() -> Self { +impl TrafficLight { + fn new(color: TrafficLightColor, window_has_focus: bool) -> Self { Self { - window_has_focus: true, + color, + window_has_focus, } } +} - pub fn window_has_focus(mut self, window_has_focus: bool) -> Self { - self.window_has_focus = window_has_focus; - self - } +#[derive(RenderOnce)] +pub struct TrafficLights { + window_has_focus: bool, +} + +impl Component for TrafficLights { + type Rendered = Div; - fn render(self, _view: &mut V, cx: &mut ViewContext) -> impl Component { + 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 0451a9d032666f827335d843c2c06124650300d1..f31009cbb5433077cbd789264eef0039e7d2d867 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 5df9ce7f455b4cbb99e82bf96b66efc0347e7c13..9d812676038b9e41a00e1848f48d93ca05cab77a 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, + div, px, Action, AnchorCorner, AnyView, AppContext, Div, Entity, EntityId, EventEmitter, + FocusHandle, FocusableView, ParentElement, Render, RenderOnce, SharedString, Styled, Subscription, View, ViewContext, VisualContext, WeakView, WindowContext, }; use schemars::JsonSchema; @@ -476,7 +476,7 @@ impl Dock { } } -impl Render for Dock { +impl Render for Dock { type Element = Div; fn render(&mut self, cx: &mut ViewContext) -> Self::Element { @@ -662,7 +662,7 @@ impl PanelButtons { // } // here be kittens -impl Render for PanelButtons { +impl Render for PanelButtons { type Element = Div; fn render(&mut self, cx: &mut ViewContext) -> Self::Element { @@ -705,8 +705,7 @@ impl Render for PanelButtons { }; Some( - menu_handle() - .id(name) + menu_handle(name) .menu(move |_, cx| { const POSITIONS: [DockPosition; 3] = [ DockPosition::Left, @@ -781,7 +780,7 @@ pub mod test { } } - impl Render for TestPanel { + impl Render for TestPanel { type Element = Div; fn render(&mut self, _cx: &mut ViewContext) -> Self::Element { diff --git a/crates/workspace2/src/modal_layer.rs b/crates/workspace2/src/modal_layer.rs index 8afd8317f94ed5452e49106c50b5e69f056a6e6e..3cbd8458f4065b190fc95d3799f14e868e8fac8b 100644 --- a/crates/workspace2/src/modal_layer.rs +++ b/crates/workspace2/src/modal_layer.rs @@ -71,7 +71,7 @@ impl ModalLayer { } } -impl Render for ModalLayer { +impl Render for ModalLayer { type Element = Div; fn render(&mut self, cx: &mut ViewContext) -> Self::Element { diff --git a/crates/workspace2/src/notifications.rs b/crates/workspace2/src/notifications.rs index b1df74c61af08431ab98b5bd2c5d268011755984..e132d821ef97db447a777b7d6d0b696c0a03b735 100644 --- a/crates/workspace2/src/notifications.rs +++ b/crates/workspace2/src/notifications.rs @@ -13,9 +13,9 @@ pub enum NotificationEvent { Dismiss, } -pub trait Notification: EventEmitter + Render {} +pub trait Notification: EventEmitter + Render {} -impl + Render> Notification for V {} +impl + Render> Notification for V {} pub trait NotificationHandle: Send { fn id(&self) -> EntityId; @@ -253,7 +253,7 @@ pub mod simple_message_notification { // } } - impl Render for MessageNotification { + impl Render for MessageNotification { type Element = Div; fn render(&mut self, cx: &mut ViewContext) -> Self::Element { diff --git a/crates/workspace2/src/pane.rs b/crates/workspace2/src/pane.rs index 18298756dbfb0db0d851234821bd9b4abd493160..5320406dd23db6aff34f2713b6d170eed55772cc 100644 --- a/crates/workspace2/src/pane.rs +++ b/crates/workspace2/src/pane.rs @@ -7,9 +7,9 @@ use crate::{ use anyhow::Result; use collections::{HashMap, HashSet, VecDeque}; use gpui::{ - actions, prelude::*, Action, AppContext, AsyncWindowContext, Component, Div, EntityId, - EventEmitter, FocusHandle, Focusable, FocusableView, Model, Pixels, Point, PromptLevel, Render, - Task, View, ViewContext, VisualContext, WeakView, WindowContext, + actions, prelude::*, Action, AppContext, AsyncWindowContext, Div, EntityId, EventEmitter, + FocusHandle, Focusable, FocusableView, Model, Pixels, Point, PromptLevel, Render, Task, View, + ViewContext, VisualContext, WeakView, WindowContext, }; use parking_lot::Mutex; use project2::{Project, ProjectEntryId, ProjectPath}; @@ -594,7 +594,7 @@ impl Pane { self.items.iter() } - pub fn items_of_type(&self) -> impl '_ + Iterator> { + pub fn items_of_type>(&self) -> impl '_ + Iterator> { self.items .iter() .filter_map(|item| item.to_any().downcast().ok()) @@ -1344,7 +1344,7 @@ impl Pane { item: &Box, detail: usize, cx: &mut ViewContext<'_, Pane>, - ) -> impl Component { + ) -> impl RenderOnce { let label = item.tab_content(Some(detail), cx); let close_icon = || { let id = item.item_id(); @@ -1437,7 +1437,7 @@ impl Pane { ) } - fn render_tab_bar(&mut self, cx: &mut ViewContext<'_, Pane>) -> impl Component { + fn render_tab_bar(&mut self, cx: &mut ViewContext<'_, Pane>) -> impl RenderOnce { div() .group("tab_bar") .id("tab_bar") @@ -1891,7 +1891,7 @@ impl FocusableView for Pane { } } -impl Render for Pane { +impl Render for Pane { type Element = Focusable>; fn render(&mut self, cx: &mut ViewContext) -> Self::Element { @@ -2945,7 +2945,7 @@ struct DraggedTab { title: String, } -impl Render for DraggedTab { +impl Render for DraggedTab { type Element = Div; fn render(&mut self, cx: &mut ViewContext) -> Self::Element { diff --git a/crates/workspace2/src/pane_group.rs b/crates/workspace2/src/pane_group.rs index d46757ff9f64982557cff50cd979d5d7cbabd898..5d02214f9f7ccec30944fe483bbf9adb3f9fd330 100644 --- a/crates/workspace2/src/pane_group.rs +++ b/crates/workspace2/src/pane_group.rs @@ -6,7 +6,9 @@ use db2::sqlez::{ bindable::{Bind, Column, StaticColumnCount}, statement::Statement, }; -use gpui::{point, size, AnyElement, AnyWeakView, Bounds, Model, Pixels, Point, View, ViewContext}; +use gpui::{ + point, size, AnyWeakView, Bounds, Div, Model, Pixels, Point, RenderOnce, View, ViewContext, +}; use parking_lot::Mutex; use project2::Project; use serde::Deserialize; @@ -130,7 +132,7 @@ impl PaneGroup { zoomed: Option<&AnyWeakView>, app_state: &Arc, cx: &mut ViewContext, - ) -> impl Component { + ) -> impl RenderOnce { self.root.render( project, 0, @@ -202,7 +204,7 @@ impl Member { zoomed: Option<&AnyWeakView>, app_state: &Arc, cx: &mut ViewContext, - ) -> impl Component { + ) -> impl RenderOnce { match self { Member::Pane(pane) => { // todo!() @@ -212,7 +214,7 @@ impl Member { // Some(pane) // }; - div().size_full().child(pane.clone()).render() + div().size_full().child(pane.clone()) // Stack::new() // .with_child(pane_element.contained().with_border(leader_border)) @@ -559,7 +561,7 @@ impl PaneAxis { zoomed: Option<&AnyWeakView>, app_state: &Arc, cx: &mut ViewContext, - ) -> AnyElement { + ) -> Div { debug_assert!(self.members.len() == self.flexes.lock().len()); div() @@ -582,11 +584,10 @@ impl PaneAxis { app_state, cx, ) - .render(), - Member::Pane(pane) => pane.clone().render(), + .render_into_any(), + Member::Pane(pane) => pane.clone().render_into_any(), } })) - .render() // let mut pane_axis = PaneAxisElement::new( // self.axis, diff --git a/crates/workspace2/src/status_bar.rs b/crates/workspace2/src/status_bar.rs index 2293b7475c60821d2e1f3b19af075e750f9e8160..894229a31df3d17a261b34b9b763ac528b03ef8a 100644 --- a/crates/workspace2/src/status_bar.rs +++ b/crates/workspace2/src/status_bar.rs @@ -2,14 +2,14 @@ use std::any::TypeId; use crate::{ItemHandle, Pane}; use gpui::{ - div, AnyView, Component, Div, ParentComponent, Render, Styled, Subscription, View, ViewContext, + div, AnyView, Div, ParentElement, Render, RenderOnce, Styled, Subscription, View, ViewContext, WindowContext, }; use theme2::ActiveTheme; use ui::h_stack; use util::ResultExt; -pub trait StatusItemView: Render { +pub trait StatusItemView: Render { fn set_active_pane_item( &mut self, active_pane_item: Option<&dyn crate::ItemHandle>, @@ -34,7 +34,7 @@ pub struct StatusBar { _observe_active_pane: Subscription, } -impl Render for StatusBar { +impl Render for StatusBar { type Element = Div; fn render(&mut self, cx: &mut ViewContext) -> Self::Element { @@ -53,14 +53,14 @@ impl Render for StatusBar { } impl StatusBar { - fn render_left_tools(&self, cx: &mut ViewContext) -> impl Component { + fn render_left_tools(&self, cx: &mut ViewContext) -> impl RenderOnce { h_stack() .items_center() .gap_2() .children(self.left_items.iter().map(|item| item.to_any())) } - fn render_right_tools(&self, cx: &mut ViewContext) -> impl Component { + fn render_right_tools(&self, cx: &mut ViewContext) -> impl RenderOnce { h_stack() .items_center() .gap_2() diff --git a/crates/workspace2/src/toolbar.rs b/crates/workspace2/src/toolbar.rs index db9b189e0fb372fee6692ad71de11bdfa958106c..8a231b753358caeeba7428e84d278a723edfbb07 100644 --- a/crates/workspace2/src/toolbar.rs +++ b/crates/workspace2/src/toolbar.rs @@ -1,6 +1,6 @@ use crate::ItemHandle; use gpui::{ - AnyView, Div, Entity, EntityId, EventEmitter, ParentComponent, Render, Styled, View, + AnyView, Div, Entity, EntityId, EventEmitter, ParentElement as _, Render, Styled, View, ViewContext, WindowContext, }; use theme2::ActiveTheme; @@ -10,7 +10,7 @@ pub enum ToolbarItemEvent { ChangeLocation(ToolbarItemLocation), } -pub trait ToolbarItemView: Render + EventEmitter { +pub trait ToolbarItemView: Render + EventEmitter { fn set_active_pane_item( &mut self, active_pane_item: Option<&dyn crate::ItemHandle>, @@ -76,7 +76,7 @@ impl Toolbar { } } -impl Render for Toolbar { +impl Render for Toolbar { type Element = Div; fn render(&mut self, cx: &mut ViewContext) -> Self::Element { diff --git a/crates/workspace2/src/workspace2.rs b/crates/workspace2/src/workspace2.rs index 78499a9d227311e9110fbedffc88b2a2ad0bdb69..27d6c23fb5d9bc4d4eca8c17313f7f2e1fe11995 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; @@ -3594,7 +3594,7 @@ impl FocusableView for Workspace { } } -impl Render for Workspace { +impl Render for Workspace { type Element = Div; fn render(&mut self, cx: &mut ViewContext) -> Self::Element {