diff --git a/Cargo.lock b/Cargo.lock index 4153e0c8c6..bdd7b7a1d9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1550,6 +1550,7 @@ dependencies = [ "anyhow", "async-recursion 0.3.2", "async-tungstenite", + "chrono", "collections", "db", "feature_flags", @@ -1586,6 +1587,7 @@ dependencies = [ "anyhow", "async-recursion 0.3.2", "async-tungstenite", + "chrono", "collections", "db2", "feature_flags2", diff --git a/crates/assistant/src/assistant_panel.rs b/crates/assistant/src/assistant_panel.rs index 6ab96093a7..cac8bf6c54 100644 --- a/crates/assistant/src/assistant_panel.rs +++ b/crates/assistant/src/assistant_panel.rs @@ -15,7 +15,7 @@ use ai::{ use ai::prompts::repository_context::PromptCodeSnippet; use anyhow::{anyhow, Result}; use chrono::{DateTime, Local}; -use client::{telemetry::AssistantKind, ClickhouseEvent, TelemetrySettings}; +use client::{telemetry::AssistantKind, TelemetrySettings}; use collections::{hash_map, HashMap, HashSet, VecDeque}; use editor::{ display_map::{ @@ -3803,12 +3803,12 @@ fn report_assistant_event( .default_open_ai_model .clone(); - let event = ClickhouseEvent::Assistant { - conversation_id, - kind: assistant_kind, - model: model.full_name(), - }; let telemetry_settings = *settings::get::(cx); - telemetry.report_clickhouse_event(event, telemetry_settings) + telemetry.report_assistant_event( + telemetry_settings, + conversation_id, + assistant_kind, + model.full_name(), + ) } diff --git a/crates/auto_update2/src/update_notification.rs b/crates/auto_update2/src/update_notification.rs index b77682c9ae..03a71bcabb 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/call/src/call.rs b/crates/call/src/call.rs index ca1a60bd63..7959a8c7d1 100644 --- a/crates/call/src/call.rs +++ b/crates/call/src/call.rs @@ -5,10 +5,7 @@ pub mod room; use anyhow::{anyhow, Result}; use audio::Audio; use call_settings::CallSettings; -use client::{ - proto, ClickhouseEvent, Client, TelemetrySettings, TypedEnvelope, User, UserStore, - ZED_ALWAYS_ACTIVE, -}; +use client::{proto, Client, TelemetrySettings, TypedEnvelope, User, UserStore, ZED_ALWAYS_ACTIVE}; use collections::HashSet; use futures::{channel::oneshot, future::Shared, Future, FutureExt}; use gpui::{ @@ -485,12 +482,8 @@ pub fn report_call_event_for_room( ) { let telemetry = client.telemetry(); let telemetry_settings = *settings::get::(cx); - let event = ClickhouseEvent::Call { - operation, - room_id: Some(room_id), - channel_id, - }; - telemetry.report_clickhouse_event(event, telemetry_settings); + + telemetry.report_call_event(telemetry_settings, operation, Some(room_id), channel_id) } pub fn report_call_event_for_channel( @@ -504,12 +497,12 @@ pub fn report_call_event_for_channel( let telemetry = client.telemetry(); let telemetry_settings = *settings::get::(cx); - let event = ClickhouseEvent::Call { + telemetry.report_call_event( + telemetry_settings, operation, - room_id: room.map(|r| r.read(cx).id()), - channel_id: Some(channel_id), - }; - telemetry.report_clickhouse_event(event, telemetry_settings); + room.map(|r| r.read(cx).id()), + Some(channel_id), + ) } #[cfg(test)] diff --git a/crates/call2/src/call2.rs b/crates/call2/src/call2.rs index 2fab3d40ce..1f11e0650d 100644 --- a/crates/call2/src/call2.rs +++ b/crates/call2/src/call2.rs @@ -5,10 +5,7 @@ pub mod room; use anyhow::{anyhow, Result}; use audio::Audio; use call_settings::CallSettings; -use client::{ - proto, ClickhouseEvent, Client, TelemetrySettings, TypedEnvelope, User, UserStore, - ZED_ALWAYS_ACTIVE, -}; +use client::{proto, Client, TelemetrySettings, TypedEnvelope, User, UserStore, ZED_ALWAYS_ACTIVE}; use collections::HashSet; use futures::{channel::oneshot, future::Shared, Future, FutureExt}; use gpui::{ @@ -484,12 +481,8 @@ pub fn report_call_event_for_room( ) { let telemetry = client.telemetry(); let telemetry_settings = *TelemetrySettings::get_global(cx); - let event = ClickhouseEvent::Call { - operation, - room_id: Some(room_id), - channel_id, - }; - telemetry.report_clickhouse_event(event, telemetry_settings); + + telemetry.report_call_event(telemetry_settings, operation, Some(room_id), channel_id) } pub fn report_call_event_for_channel( @@ -504,12 +497,12 @@ pub fn report_call_event_for_channel( let telemetry_settings = *TelemetrySettings::get_global(cx); - let event = ClickhouseEvent::Call { + telemetry.report_call_event( + telemetry_settings, operation, - room_id: room.map(|r| r.read(cx).id()), - channel_id: Some(channel_id), - }; - telemetry.report_clickhouse_event(event, telemetry_settings); + room.map(|r| r.read(cx).id()), + Some(channel_id), + ) } #[cfg(test)] diff --git a/crates/client/Cargo.toml b/crates/client/Cargo.toml index c8085f807b..c24cbca35b 100644 --- a/crates/client/Cargo.toml +++ b/crates/client/Cargo.toml @@ -12,6 +12,7 @@ doctest = false test-support = ["collections/test-support", "gpui/test-support", "rpc/test-support"] [dependencies] +chrono = { version = "0.4", features = ["serde"] } collections = { path = "../collections" } db = { path = "../db" } gpui = { path = "../gpui" } diff --git a/crates/client/src/telemetry.rs b/crates/client/src/telemetry.rs index ad2b29c388..8f7fbeb83d 100644 --- a/crates/client/src/telemetry.rs +++ b/crates/client/src/telemetry.rs @@ -1,4 +1,5 @@ use crate::{TelemetrySettings, ZED_SECRET_CLIENT_TOKEN, ZED_SERVER_URL}; +use chrono::{DateTime, Utc}; use gpui::{executor::Background, serde_json, AppContext, Task}; use lazy_static::lazy_static; use parking_lot::Mutex; @@ -31,6 +32,7 @@ struct TelemetryState { flush_clickhouse_events_task: Option>, log_file: Option, is_staff: Option, + first_event_datetime: Option>, } const CLICKHOUSE_EVENTS_URL_PATH: &'static str = "/api/events"; @@ -77,29 +79,35 @@ pub enum ClickhouseEvent { vim_mode: bool, copilot_enabled: bool, copilot_enabled_for_language: bool, + milliseconds_since_first_event: i64, }, Copilot { suggestion_id: Option, suggestion_accepted: bool, file_extension: Option, + milliseconds_since_first_event: i64, }, Call { operation: &'static str, room_id: Option, channel_id: Option, + milliseconds_since_first_event: i64, }, Assistant { conversation_id: Option, kind: AssistantKind, model: &'static str, + milliseconds_since_first_event: i64, }, Cpu { usage_as_percentage: f32, core_count: u32, + milliseconds_since_first_event: i64, }, Memory { memory_in_bytes: u64, virtual_memory_in_bytes: u64, + milliseconds_since_first_event: i64, }, } @@ -140,6 +148,7 @@ impl Telemetry { flush_clickhouse_events_task: Default::default(), log_file: None, is_staff: None, + first_event_datetime: None, }), }); @@ -195,20 +204,18 @@ impl Telemetry { return; }; - let memory_event = ClickhouseEvent::Memory { - memory_in_bytes: process.memory(), - virtual_memory_in_bytes: process.virtual_memory(), - }; - - let cpu_event = ClickhouseEvent::Cpu { - usage_as_percentage: process.cpu_usage(), - core_count: system.cpus().len() as u32, - }; - let telemetry_settings = cx.update(|cx| *settings::get::(cx)); - this.report_clickhouse_event(memory_event, telemetry_settings); - this.report_clickhouse_event(cpu_event, telemetry_settings); + this.report_memory_event( + telemetry_settings, + process.memory(), + process.virtual_memory(), + ); + this.report_cpu_event( + telemetry_settings, + process.cpu_usage(), + system.cpus().len() as u32, + ); } }) .detach(); @@ -231,7 +238,123 @@ impl Telemetry { drop(state); } - pub fn report_clickhouse_event( + pub fn report_editor_event( + self: &Arc, + telemetry_settings: TelemetrySettings, + file_extension: Option, + vim_mode: bool, + operation: &'static str, + copilot_enabled: bool, + copilot_enabled_for_language: bool, + ) { + let event = ClickhouseEvent::Editor { + file_extension, + vim_mode, + operation, + copilot_enabled, + copilot_enabled_for_language, + milliseconds_since_first_event: self.milliseconds_since_first_event(), + }; + + self.report_clickhouse_event(event, telemetry_settings) + } + + pub fn report_copilot_event( + self: &Arc, + telemetry_settings: TelemetrySettings, + suggestion_id: Option, + suggestion_accepted: bool, + file_extension: Option, + ) { + let event = ClickhouseEvent::Copilot { + suggestion_id, + suggestion_accepted, + file_extension, + milliseconds_since_first_event: self.milliseconds_since_first_event(), + }; + + self.report_clickhouse_event(event, telemetry_settings) + } + + pub fn report_assistant_event( + self: &Arc, + telemetry_settings: TelemetrySettings, + conversation_id: Option, + kind: AssistantKind, + model: &'static str, + ) { + let event = ClickhouseEvent::Assistant { + conversation_id, + kind, + model, + milliseconds_since_first_event: self.milliseconds_since_first_event(), + }; + + self.report_clickhouse_event(event, telemetry_settings) + } + + pub fn report_call_event( + self: &Arc, + telemetry_settings: TelemetrySettings, + operation: &'static str, + room_id: Option, + channel_id: Option, + ) { + let event = ClickhouseEvent::Call { + operation, + room_id, + channel_id, + milliseconds_since_first_event: self.milliseconds_since_first_event(), + }; + + self.report_clickhouse_event(event, telemetry_settings) + } + + pub fn report_cpu_event( + self: &Arc, + telemetry_settings: TelemetrySettings, + usage_as_percentage: f32, + core_count: u32, + ) { + let event = ClickhouseEvent::Cpu { + usage_as_percentage, + core_count, + milliseconds_since_first_event: self.milliseconds_since_first_event(), + }; + + self.report_clickhouse_event(event, telemetry_settings) + } + + pub fn report_memory_event( + self: &Arc, + telemetry_settings: TelemetrySettings, + memory_in_bytes: u64, + virtual_memory_in_bytes: u64, + ) { + let event = ClickhouseEvent::Memory { + memory_in_bytes, + virtual_memory_in_bytes, + milliseconds_since_first_event: self.milliseconds_since_first_event(), + }; + + self.report_clickhouse_event(event, telemetry_settings) + } + + fn milliseconds_since_first_event(&self) -> i64 { + let mut state = self.state.lock(); + match state.first_event_datetime { + Some(first_event_datetime) => { + let now: DateTime = Utc::now(); + now.timestamp_millis() - first_event_datetime.timestamp_millis() + } + None => { + state.first_event_datetime = Some(Utc::now()); + 0 + } + } + } + + fn report_clickhouse_event( self: &Arc, event: ClickhouseEvent, telemetry_settings: TelemetrySettings, @@ -275,6 +398,7 @@ impl Telemetry { fn flush_clickhouse_events(self: &Arc) { let mut state = self.state.lock(); + state.first_event_datetime = None; let mut events = mem::take(&mut state.clickhouse_events_queue); state.flush_clickhouse_events_task.take(); drop(state); diff --git a/crates/client2/Cargo.toml b/crates/client2/Cargo.toml index ace229bc21..b1c993e3a4 100644 --- a/crates/client2/Cargo.toml +++ b/crates/client2/Cargo.toml @@ -12,6 +12,7 @@ doctest = false test-support = ["collections/test-support", "gpui/test-support", "rpc/test-support"] [dependencies] +chrono = { version = "0.4", features = ["serde"] } collections = { path = "../collections" } db = { package = "db2", path = "../db2" } gpui = { package = "gpui2", path = "../gpui2" } diff --git a/crates/client2/src/telemetry.rs b/crates/client2/src/telemetry.rs index cf5b3b765b..9c88d1102c 100644 --- a/crates/client2/src/telemetry.rs +++ b/crates/client2/src/telemetry.rs @@ -1,4 +1,5 @@ use crate::{TelemetrySettings, ZED_SECRET_CLIENT_TOKEN, ZED_SERVER_URL}; +use chrono::{DateTime, Utc}; use gpui::{serde_json, AppContext, AppMetadata, BackgroundExecutor, Task}; use lazy_static::lazy_static; use parking_lot::Mutex; @@ -29,6 +30,7 @@ struct TelemetryState { flush_clickhouse_events_task: Option>, log_file: Option, is_staff: Option, + first_event_datetime: Option>, } const CLICKHOUSE_EVENTS_URL_PATH: &'static str = "/api/events"; @@ -75,29 +77,35 @@ pub enum ClickhouseEvent { vim_mode: bool, copilot_enabled: bool, copilot_enabled_for_language: bool, + milliseconds_since_first_event: i64, }, Copilot { suggestion_id: Option, suggestion_accepted: bool, file_extension: Option, + milliseconds_since_first_event: i64, }, Call { operation: &'static str, room_id: Option, channel_id: Option, + milliseconds_since_first_event: i64, }, Assistant { conversation_id: Option, kind: AssistantKind, model: &'static str, + milliseconds_since_first_event: i64, }, Cpu { usage_as_percentage: f32, core_count: u32, + milliseconds_since_first_event: i64, }, Memory { memory_in_bytes: u64, virtual_memory_in_bytes: u64, + milliseconds_since_first_event: i64, }, } @@ -135,6 +143,7 @@ impl Telemetry { flush_clickhouse_events_task: Default::default(), log_file: None, is_staff: None, + first_event_datetime: None, }), }); @@ -190,16 +199,6 @@ impl Telemetry { return; }; - let memory_event = ClickhouseEvent::Memory { - memory_in_bytes: process.memory(), - virtual_memory_in_bytes: process.virtual_memory(), - }; - - let cpu_event = ClickhouseEvent::Cpu { - usage_as_percentage: process.cpu_usage(), - core_count: system.cpus().len() as u32, - }; - let telemetry_settings = if let Ok(telemetry_settings) = cx.update(|cx| *TelemetrySettings::get_global(cx)) { @@ -208,8 +207,16 @@ impl Telemetry { break; }; - this.report_clickhouse_event(memory_event, telemetry_settings); - this.report_clickhouse_event(cpu_event, telemetry_settings); + this.report_memory_event( + telemetry_settings, + process.memory(), + process.virtual_memory(), + ); + this.report_cpu_event( + telemetry_settings, + process.cpu_usage(), + system.cpus().len() as u32, + ); } }) .detach(); @@ -232,7 +239,123 @@ impl Telemetry { drop(state); } - pub fn report_clickhouse_event( + pub fn report_editor_event( + self: &Arc, + telemetry_settings: TelemetrySettings, + file_extension: Option, + vim_mode: bool, + operation: &'static str, + copilot_enabled: bool, + copilot_enabled_for_language: bool, + ) { + let event = ClickhouseEvent::Editor { + file_extension, + vim_mode, + operation, + copilot_enabled, + copilot_enabled_for_language, + milliseconds_since_first_event: self.milliseconds_since_first_event(), + }; + + self.report_clickhouse_event(event, telemetry_settings) + } + + pub fn report_copilot_event( + self: &Arc, + telemetry_settings: TelemetrySettings, + suggestion_id: Option, + suggestion_accepted: bool, + file_extension: Option, + ) { + let event = ClickhouseEvent::Copilot { + suggestion_id, + suggestion_accepted, + file_extension, + milliseconds_since_first_event: self.milliseconds_since_first_event(), + }; + + self.report_clickhouse_event(event, telemetry_settings) + } + + pub fn report_assistant_event( + self: &Arc, + telemetry_settings: TelemetrySettings, + conversation_id: Option, + kind: AssistantKind, + model: &'static str, + ) { + let event = ClickhouseEvent::Assistant { + conversation_id, + kind, + model, + milliseconds_since_first_event: self.milliseconds_since_first_event(), + }; + + self.report_clickhouse_event(event, telemetry_settings) + } + + pub fn report_call_event( + self: &Arc, + telemetry_settings: TelemetrySettings, + operation: &'static str, + room_id: Option, + channel_id: Option, + ) { + let event = ClickhouseEvent::Call { + operation, + room_id, + channel_id, + milliseconds_since_first_event: self.milliseconds_since_first_event(), + }; + + self.report_clickhouse_event(event, telemetry_settings) + } + + pub fn report_cpu_event( + self: &Arc, + telemetry_settings: TelemetrySettings, + usage_as_percentage: f32, + core_count: u32, + ) { + let event = ClickhouseEvent::Cpu { + usage_as_percentage, + core_count, + milliseconds_since_first_event: self.milliseconds_since_first_event(), + }; + + self.report_clickhouse_event(event, telemetry_settings) + } + + pub fn report_memory_event( + self: &Arc, + telemetry_settings: TelemetrySettings, + memory_in_bytes: u64, + virtual_memory_in_bytes: u64, + ) { + let event = ClickhouseEvent::Memory { + memory_in_bytes, + virtual_memory_in_bytes, + milliseconds_since_first_event: self.milliseconds_since_first_event(), + }; + + self.report_clickhouse_event(event, telemetry_settings) + } + + fn milliseconds_since_first_event(&self) -> i64 { + let mut state = self.state.lock(); + match state.first_event_datetime { + Some(first_event_datetime) => { + let now: DateTime = Utc::now(); + now.timestamp_millis() - first_event_datetime.timestamp_millis() + } + None => { + state.first_event_datetime = Some(Utc::now()); + 0 + } + } + } + + fn report_clickhouse_event( self: &Arc, event: ClickhouseEvent, telemetry_settings: TelemetrySettings, @@ -276,6 +399,7 @@ impl Telemetry { fn flush_clickhouse_events(self: &Arc) { let mut state = self.state.lock(); + state.first_event_datetime = None; let mut events = mem::take(&mut state.clickhouse_events_queue); state.flush_clickhouse_events_task.take(); drop(state); diff --git a/crates/collab_ui2/src/collab_panel.rs b/crates/collab_ui2/src/collab_panel.rs index 20e77d7023..901348d2e2 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 ed010cc500..42800269c7 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 f3573c1a3d..1296f35c55 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 623b636319..7203748470 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 dd1b7d98cf..1d5183634f 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 8d4efe00c3..a22217f0a1 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/editor/src/editor.rs b/crates/editor/src/editor.rs index 4e449bb7f7..2558aec121 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -24,7 +24,7 @@ use ::git::diff::DiffHunk; use aho_corasick::AhoCorasick; use anyhow::{anyhow, Context, Result}; use blink_manager::BlinkManager; -use client::{ClickhouseEvent, Client, Collaborator, ParticipantIndex, TelemetrySettings}; +use client::{Client, Collaborator, ParticipantIndex, TelemetrySettings}; use clock::{Global, ReplicaId}; use collections::{BTreeMap, Bound, HashMap, HashSet, VecDeque}; use convert_case::{Case, Casing}; @@ -8946,12 +8946,12 @@ impl Editor { let telemetry = project.read(cx).client().telemetry().clone(); let telemetry_settings = *settings::get::(cx); - let event = ClickhouseEvent::Copilot { + telemetry.report_copilot_event( + telemetry_settings, suggestion_id, suggestion_accepted, file_extension, - }; - telemetry.report_clickhouse_event(event, telemetry_settings); + ) } #[cfg(any(test, feature = "test-support"))] @@ -8998,14 +8998,14 @@ impl Editor { .show_copilot_suggestions; let telemetry = project.read(cx).client().telemetry().clone(); - let event = ClickhouseEvent::Editor { + telemetry.report_editor_event( + telemetry_settings, file_extension, vim_mode, operation, copilot_enabled, copilot_enabled_for_language, - }; - telemetry.report_clickhouse_event(event, telemetry_settings) + ) } /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines, diff --git a/crates/editor2/src/editor.rs b/crates/editor2/src/editor.rs index beed93e917..5e40f5368e 100644 --- a/crates/editor2/src/editor.rs +++ b/crates/editor2/src/editor.rs @@ -24,7 +24,7 @@ use ::git::diff::DiffHunk; use aho_corasick::AhoCorasick; use anyhow::{anyhow, Context as _, Result}; use blink_manager::BlinkManager; -use client::{ClickhouseEvent, Client, Collaborator, ParticipantIndex, TelemetrySettings}; +use client::{Client, Collaborator, ParticipantIndex, TelemetrySettings}; use clock::ReplicaId; use collections::{BTreeMap, Bound, HashMap, HashSet, VecDeque}; use convert_case::{Case, Casing}; @@ -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; @@ -4353,19 +4354,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 @@ -4380,7 +4381,7 @@ impl Editor { line_height: Pixels, gutter_margin: Pixels, cx: &mut ViewContext, - ) -> Vec>> { + ) -> Vec>> { fold_data .iter() .enumerate() @@ -4402,7 +4403,6 @@ impl Editor { } }) .color(ui::TextColor::Muted) - .render() }) }) .flatten() @@ -7793,7 +7793,7 @@ impl Editor { cx.editor_style.diagnostic_style.clone(), }, ))) - .render() + .render_into_any() } }), disposition: BlockDisposition::Below, @@ -8968,12 +8968,12 @@ impl Editor { let telemetry = project.read(cx).client().telemetry().clone(); let telemetry_settings = *TelemetrySettings::get_global(cx); - let event = ClickhouseEvent::Copilot { + telemetry.report_copilot_event( + telemetry_settings, suggestion_id, suggestion_accepted, file_extension, - }; - telemetry.report_clickhouse_event(event, telemetry_settings); + ) } #[cfg(any(test, feature = "test-support"))] @@ -9020,14 +9020,14 @@ impl Editor { .show_copilot_suggestions; let telemetry = project.read(cx).client().telemetry().clone(); - let event = ClickhouseEvent::Editor { + telemetry.report_editor_event( + telemetry_settings, file_extension, vim_mode, operation, copilot_enabled, copilot_enabled_for_language, - }; - telemetry.report_clickhouse_event(event, telemetry_settings) + ) } /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines, @@ -9382,7 +9382,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 { @@ -10000,7 +10000,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 06a8636f8c..f0609fc9a8 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 220fe89ea6..42cb47da49 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 8919e26f3b..d503ac9f5c 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 3691e69f5d..2e7655298a 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 8c7ac97d0b..9b3666ea5c 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 ca96ba210e..f645129706 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 65c006b062..cc3b0ace57 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 @@ -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 f1e7fad6a1..a34582f4f4 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 8687646b4b..8fad38c120 100644 --- a/crates/gpui2/src/app/test_context.rs +++ b/crates/gpui2/src/app/test_context.rs @@ -126,7 +126,7 @@ impl TestAppContext { pub fn add_window(&mut self, build_window: F) -> WindowHandle where F: FnOnce(&mut ViewContext) -> V, - V: Render, + V: 'static + Render, { let mut cx = self.app.borrow_mut(); cx.open_window(WindowOptions::default(), |cx| cx.build_view(build_window)) @@ -143,7 +143,7 @@ impl TestAppContext { pub fn add_window_view(&mut self, build_window: F) -> (View, &mut VisualTestContext) where F: FnOnce(&mut ViewContext) -> V, - V: Render, + V: 'static + Render, { let mut cx = self.app.borrow_mut(); let window = cx.open_window(WindowOptions::default(), |cx| cx.build_view(build_window)); @@ -591,7 +591,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)) diff --git a/crates/gpui2/src/element.rs b/crates/gpui2/src/element.rs index d36f5f1635..5cd015503d 100644 --- a/crates/gpui2/src/element.rs +++ b/crates/gpui2/src/element.rs @@ -1,306 +1,63 @@ use crate::{ - AvailableSpace, BorrowWindow, Bounds, ElementId, LayoutId, Pixels, Point, Size, WindowContext, + AvailableSpace, BorrowWindow, Bounds, ElementId, LayoutId, Pixels, Point, Size, ViewContext, + WindowContext, }; use derive_more::{Deref, DerefMut}; pub(crate) use smallvec::SmallVec; -use std::{any::Any, fmt::Debug, mem}; +use std::{any::Any, fmt::Debug}; -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, - element_state: Option, - cx: &mut WindowContext, - ) -> (LayoutId, Self::ElementState); + fn render_once(self) -> Self::Element; - fn paint( - &mut self, - bounds: Bounds, - element_state: &mut Self::ElementState, - cx: &mut WindowContext, - ); + fn render_into_any(self) -> AnyElement { + self.render_once().into_any() + } fn draw( self, origin: Point, available_space: Size, cx: &mut WindowContext, - f: impl FnOnce(&Self::ElementState, &mut WindowContext) -> R, + f: impl FnOnce(&mut ::State, &mut WindowContext) -> 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), cx); - if let ElementRenderPhase::Painted { frame_state } = &element.phase { - if let Some(frame_state) = frame_state.as_ref() { - f(&frame_state, cx) - } 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) - }) - } + + let frame_state = + DrawableElement::draw(element, origin, available_space.map(Into::into), cx); + + if let Some(mut frame_state) = frame_state { + f(&mut frame_state, cx) } else { - unreachable!() + 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) + }) } } -} - -#[derive(Deref, DerefMut, Default, Clone, Debug, Eq, PartialEq, Hash)] -pub struct GlobalElementId(SmallVec<[ElementId; 32]>); - -pub trait ParentComponent { - fn children_mut(&mut self) -> &mut SmallVec<[AnyElement; 2]>; - - fn child(mut self, child: impl Component) -> Self - where - Self: Sized, - { - self.children_mut().push(child.render()); - self - } - - fn children(mut self, iter: impl IntoIterator) -> Self - where - Self: Sized, - { - self.children_mut() - .extend(iter.into_iter().map(|item| item.render())); - self - } -} - -trait ElementObject { - fn element_id(&self) -> Option; - fn layout(&mut self, cx: &mut WindowContext) -> LayoutId; - fn paint(&mut self, cx: &mut WindowContext); - fn measure( - &mut self, - available_space: Size, - cx: &mut WindowContext, - ) -> Size; - fn draw( - &mut self, - origin: Point, - available_space: Size, - cx: &mut WindowContext, - ); -} - -struct RenderedElement { - element: E, - phase: ElementRenderPhase, -} - -#[derive(Default)] -enum ElementRenderPhase { - #[default] - Start, - LayoutRequested { - layout_id: LayoutId, - frame_state: Option, - }, - LayoutComputed { - layout_id: LayoutId, - available_space: Size, - frame_state: Option, - }, - Painted { - 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 { - fn new(element: E) -> Self { - RenderedElement { - element, - phase: ElementRenderPhase::Start, - } - } -} - -impl ElementObject for RenderedElement -where - E: Element, - E::ElementState: 'static, -{ - fn element_id(&self) -> Option { - self.element.element_id() - } - - fn layout(&mut self, cx: &mut WindowContext) -> 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(element_state, cx) - }); - (layout_id, None) - } else { - let (layout_id, frame_state) = self.element.layout(None, cx); - (layout_id, Some(frame_state)) - } - } - ElementRenderPhase::LayoutRequested { .. } - | ElementRenderPhase::LayoutComputed { .. } - | ElementRenderPhase::Painted { .. } => { - panic!("element rendered twice") - } - }; - - self.phase = ElementRenderPhase::LayoutRequested { - layout_id, - frame_state, - }; - layout_id - } - - fn paint(&mut self, cx: &mut WindowContext) { - self.phase = match mem::take(&mut self.phase) { - ElementRenderPhase::LayoutRequested { - layout_id, - mut frame_state, - } - | ElementRenderPhase::LayoutComputed { - layout_id, - mut 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| { - let mut element_state = element_state.unwrap(); - self.element.paint(bounds, &mut element_state, cx); - ((), element_state) - }); - } else { - self.element - .paint(bounds, frame_state.as_mut().unwrap(), cx); - } - ElementRenderPhase::Painted { frame_state } - } - - _ => panic!("must call layout before paint"), - }; - } - - fn measure( - &mut self, - available_space: Size, - cx: &mut WindowContext, - ) -> Size { - if matches!(&self.phase, ElementRenderPhase::Start) { - self.layout(cx); - } - - let layout_id = match &mut self.phase { - ElementRenderPhase::LayoutRequested { - layout_id, - frame_state, - } => { - cx.compute_layout(*layout_id, available_space); - let layout_id = *layout_id; - self.phase = ElementRenderPhase::LayoutComputed { - layout_id, - available_space, - frame_state: frame_state.take(), - }; - layout_id - } - ElementRenderPhase::LayoutComputed { - layout_id, - available_space: prev_available_space, - .. - } => { - if available_space != *prev_available_space { - cx.compute_layout(*layout_id, available_space); - *prev_available_space = available_space; - } - *layout_id - } - _ => panic!("cannot measure after painting"), - }; - - cx.layout_bounds(layout_id).size - } - - fn draw( - &mut self, - origin: Point, - available_space: Size, - cx: &mut WindowContext, - ) { - self.measure(available_space, cx); - cx.with_absolute_element_offset(origin, |cx| self.paint(cx)) - } -} - -pub struct AnyElement(Box); - -impl AnyElement { - pub fn new(element: E) -> Self - where - E: 'static + Element, - E::ElementState: Any, - { - AnyElement(Box::new(RenderedElement::new(element))) - } - - pub fn element_id(&self) -> Option { - self.0.element_id() - } - - pub fn layout(&mut self, cx: &mut WindowContext) -> LayoutId { - self.0.layout(cx) - } - - pub fn paint(&mut self, cx: &mut WindowContext) { - self.0.paint(cx) - } - - /// Initializes this element and performs layout within the given available space to determine its size. - pub fn measure( - &mut self, - available_space: Size, - cx: &mut WindowContext, - ) -> Size { - self.0.measure(available_space, cx) - } - - /// Initializes this element and performs layout in the available space, then paints it at the given origin. - pub fn draw( - &mut self, - origin: Point, - available_space: Size, - cx: &mut WindowContext, - ) { - self.0.draw(origin, available_space, cx) - } -} - -pub trait Component { - fn render(self) -> AnyElement; fn map(self, f: impl FnOnce(Self) -> U) -> U where Self: Sized, - U: Component, + U: RenderOnce, { f(self) } @@ -326,60 +83,463 @@ pub trait Component { } } -impl Component for AnyElement { - fn render(self) -> AnyElement { - self +pub trait Element: 'static + RenderOnce { + type State: 'static; + + fn layout( + &mut self, + state: Option, + cx: &mut WindowContext, + ) -> (LayoutId, Self::State); + + fn paint(self, bounds: Bounds, state: &mut Self::State, cx: &mut WindowContext); + + fn into_any(self) -> AnyElement { + AnyElement::new(self) } } -impl Element for Option -where - E: 'static + Component, - F: FnOnce(&mut WindowContext) -> E + 'static, -{ - type ElementState = AnyElement; +pub trait Component: 'static { + type Rendered: RenderOnce; + + fn render(self, cx: &mut WindowContext) -> Self::Rendered; +} + +pub struct CompositeElement { + component: Option, +} + +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), + } + } +} + +impl Element for CompositeElement { + type State = CompositeElementState; + + fn layout( + &mut self, + state: Option, + cx: &mut WindowContext, + ) -> (LayoutId, Self::State) { + let mut element = self.component.take().unwrap().render(cx).render_once(); + let (layout_id, state) = element.layout(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, state: &mut Self::State, cx: &mut WindowContext) { + state + .rendered_element + .take() + .unwrap() + .paint(bounds, &mut state.rendered_element_state, cx); + } +} + +impl RenderOnce for CompositeElement { + type Element = Self; fn element_id(&self) -> Option { None } - fn layout( - &mut self, - _: Option, - cx: &mut WindowContext, - ) -> (LayoutId, Self::ElementState) { - let render = self.take().unwrap(); - let mut rendered_element = (render)(cx).render(); - let layout_id = rendered_element.layout(cx); - (layout_id, rendered_element) - } - - fn paint( - &mut self, - _bounds: Bounds, - rendered_element: &mut Self::ElementState, - cx: &mut WindowContext, - ) { - rendered_element.paint(cx) + fn render_once(self) -> Self::Element { + self } } -impl Component for Option +#[derive(Deref, DerefMut, Default, Clone, Debug, Eq, PartialEq, Hash)] +pub struct GlobalElementId(SmallVec<[ElementId; 32]>); + +pub trait ParentElement { + fn children_mut(&mut self) -> &mut SmallVec<[AnyElement; 2]>; + + fn child(mut self, child: impl RenderOnce) -> Self + where + Self: Sized, + { + self.children_mut().push(child.render_once().into_any()); + self + } + + fn children(mut self, children: impl IntoIterator) -> Self + where + Self: Sized, + { + self.children_mut().extend( + children + .into_iter() + .map(|child| child.render_once().into_any()), + ); + self + } +} + +trait ElementObject { + fn element_id(&self) -> Option; + + fn layout(&mut self, cx: &mut WindowContext) -> LayoutId; + + fn paint(&mut self, cx: &mut WindowContext); + + fn measure( + &mut self, + available_space: Size, + cx: &mut WindowContext, + ) -> Size; + + fn draw( + &mut self, + origin: Point, + available_space: Size, + cx: &mut WindowContext, + ); +} + +pub struct DrawableElement { + element: Option, + phase: ElementDrawPhase, +} + +#[derive(Default)] +enum ElementDrawPhase { + #[default] + Start, + LayoutRequested { + layout_id: LayoutId, + frame_state: Option, + }, + LayoutComputed { + layout_id: LayoutId, + available_space: Size, + frame_state: Option, + }, +} + +/// A wrapper around an implementer of [Element] that allows it to be drawn in a window. +impl DrawableElement { + fn new(element: E) -> Self { + DrawableElement { + element: Some(element), + phase: ElementDrawPhase::Start, + } + } + + fn element_id(&self) -> Option { + self.element.as_ref()?.element_id() + } + + fn layout(&mut self, cx: &mut WindowContext) -> LayoutId { + 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(element_state, cx) + }); + (layout_id, None) + } else { + let (layout_id, frame_state) = self.element.as_mut().unwrap().layout(None, cx); + (layout_id, Some(frame_state)) + }; + + self.phase = ElementDrawPhase::LayoutRequested { + layout_id, + frame_state, + }; + layout_id + } + + fn paint(mut self, cx: &mut WindowContext) -> Option { + match self.phase { + ElementDrawPhase::LayoutRequested { + layout_id, + frame_state, + } + | ElementDrawPhase::LayoutComputed { + layout_id, + frame_state, + .. + } => { + let bounds = cx.layout_bounds(layout_id); + + if let Some(mut frame_state) = frame_state { + self.element + .take() + .unwrap() + .paint(bounds, &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 + .take() + .unwrap() + .paint(bounds, &mut element_state, cx); + ((), element_state) + }); + None + } + } + + _ => panic!("must call layout before paint"), + } + } + + fn measure( + &mut self, + available_space: Size, + cx: &mut WindowContext, + ) -> Size { + if matches!(&self.phase, ElementDrawPhase::Start) { + self.layout(cx); + } + + let layout_id = match &mut self.phase { + ElementDrawPhase::LayoutRequested { + layout_id, + frame_state, + } => { + cx.compute_layout(*layout_id, available_space); + let layout_id = *layout_id; + self.phase = ElementDrawPhase::LayoutComputed { + layout_id, + available_space, + frame_state: frame_state.take(), + }; + layout_id + } + ElementDrawPhase::LayoutComputed { + layout_id, + available_space: prev_available_space, + .. + } => { + if available_space != *prev_available_space { + cx.compute_layout(*layout_id, available_space); + *prev_available_space = available_space; + } + *layout_id + } + _ => panic!("cannot measure after painting"), + }; + + cx.layout_bounds(layout_id).size + } + + fn draw( + mut self, + origin: Point, + available_space: Size, + cx: &mut WindowContext, + ) -> Option { + self.measure(available_space, cx); + cx.with_absolute_element_offset(origin, |cx| self.paint(cx)) + } +} + +// impl Element for DrawableElement { +// type State = ::State; + +// fn layout( +// &mut self, +// element_state: Option, +// cx: &mut WindowContext, +// ) -> (LayoutId, Self::State) { + +// } + +// fn paint( +// self, +// bounds: Bounds, +// element_state: &mut Self::State, +// cx: &mut WindowContext, +// ) { +// 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: 'static + Component, - F: FnOnce(&mut WindowContext) -> E + 'static, + E: Element, + E::State: 'static, { - fn render(self) -> AnyElement { + fn element_id(&self) -> Option { + self.as_ref().unwrap().element_id() + } + + fn layout(&mut self, cx: &mut WindowContext) -> LayoutId { + DrawableElement::layout(self.as_mut().unwrap(), cx) + } + + fn paint(&mut self, cx: &mut WindowContext) { + DrawableElement::paint(self.take().unwrap(), cx); + } + + fn measure( + &mut self, + available_space: Size, + cx: &mut WindowContext, + ) -> Size { + DrawableElement::measure(self.as_mut().unwrap(), available_space, cx) + } + + fn draw( + &mut self, + origin: Point, + available_space: Size, + cx: &mut WindowContext, + ) { + DrawableElement::draw(self.take().unwrap(), origin, available_space, cx); + } +} + +pub struct AnyElement(Box); + +impl AnyElement { + pub fn new(element: E) -> Self + where + E: 'static + Element, + E::State: Any, + { + AnyElement(Box::new(Some(DrawableElement::new(element))) as Box) + } + + pub fn element_id(&self) -> Option { + self.0.element_id() + } + + pub fn layout(&mut self, cx: &mut WindowContext) -> LayoutId { + self.0.layout(cx) + } + + pub fn paint(mut self, cx: &mut WindowContext) { + self.0.paint(cx) + } + + /// Initializes this element and performs layout within the given available space to determine its size. + pub fn measure( + &mut self, + available_space: Size, + cx: &mut WindowContext, + ) -> Size { + self.0.measure(available_space, cx) + } + + /// Initializes this element and performs layout in the available space, then paints it at the given origin. + pub fn draw( + mut self, + origin: Point, + available_space: Size, + cx: &mut WindowContext, + ) { + self.0.draw(origin, available_space, cx) + } + + /// Converts this `AnyElement` into a trait object that can be stored and manipulated. + pub fn into_any(self) -> AnyElement { AnyElement::new(self) } } -impl Component for F -where - E: 'static + Component, - F: FnOnce(&mut WindowContext) -> E + 'static, -{ - fn render(self) -> AnyElement { - AnyElement::new(Some(self)) +impl Element for AnyElement { + type State = (); + + fn layout( + &mut self, + _: Option, + cx: &mut WindowContext, + ) -> (LayoutId, Self::State) { + let layout_id = self.layout(cx); + (layout_id, ()) + } + + fn paint(self, _: Bounds, _: &mut Self::State, cx: &mut WindowContext) { + self.paint(cx); } } + +impl RenderOnce for AnyElement { + type Element = Self; + + fn element_id(&self) -> Option { + AnyElement::element_id(self) + } + + fn render_once(self) -> Self::Element { + self + } +} + +// impl Element for Option +// where +// V: 'static, +// E: Element, +// F: FnOnce(&mut V, &mut WindowContext<'_, V>) -> E + 'static, +// { +// type State = Option; + +// fn element_id(&self) -> Option { +// None +// } + +// fn layout( +// &mut self, +// _: Option, +// cx: &mut WindowContext, +// ) -> (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, +// rendered_element: &mut Self::State, +// cx: &mut WindowContext, +// ) { +// rendered_element.take().unwrap().paint(view_state, cx); +// } +// } + +// impl RenderOnce for Option +// where +// V: 'static, +// E: Element, +// F: FnOnce(&mut V, &mut WindowContext) -> 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 6587f22552..573ac83397 100644 --- a/crates/gpui2/src/elements/div.rs +++ b/crates/gpui2/src/elements/div.rs @@ -1,10 +1,10 @@ use crate::{ point, px, Action, AnyDrag, AnyElement, AnyTooltip, AnyView, AppContext, BorrowAppContext, - BorrowWindow, Bounds, CallbackHandle, ClickEvent, Component, ConstructorHandle, 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, - Visibility, WindowContext, + BorrowWindow, Bounds, CallbackHandle, ClickEvent, ConstructorHandle, 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, Visibility, WindowContext, }; 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 { @@ -319,7 +319,7 @@ pub trait InteractiveComponent: Sized + Element { } } -pub trait StatefulInteractiveComponent: InteractiveComponent { +pub trait StatefulInteractiveElement: InteractiveElement { fn focusable(mut self) -> Focusable { self.interactivity().focusable = true; Focusable { element: self } @@ -421,7 +421,7 @@ pub trait StatefulInteractiveComponent: InteractiveComponent { } } -pub trait FocusableComponent: InteractiveComponent { +pub trait FocusableElement: InteractiveElement { fn focus(mut self, f: impl FnOnce(StyleRefinement) -> StyleRefinement) -> Self where Self: Sized, @@ -563,30 +563,26 @@ 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, - element_state: Option, + element_state: Option, cx: &mut WindowContext, - ) -> (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( @@ -614,9 +610,9 @@ impl Element for Div { } fn paint( - &mut self, + self, bounds: Bounds, - element_state: &mut Self::ElementState, + element_state: &mut Self::State, cx: &mut WindowContext, ) { let mut child_min = point(Pixels::MAX, Pixels::MAX); @@ -632,8 +628,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, @@ -653,7 +648,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(cx); } }) @@ -663,13 +658,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 } } @@ -741,7 +741,7 @@ impl Interactivity { } pub fn paint( - &mut self, + mut self, bounds: Bounds, content_size: Size, element_state: &mut InteractiveElementState, @@ -1045,12 +1045,12 @@ impl Interactivity { }) } - 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 |event, cx| listener(&focus_handle, event, cx)); } @@ -1232,18 +1232,18 @@ pub struct Focusable { element: E, } -impl FocusableComponent for Focusable {} +impl FocusableElement for Focusable {} -impl InteractiveComponent for Focusable +impl InteractiveElement for Focusable where - E: InteractiveComponent, + E: InteractiveElement, { fn interactivity(&mut self) -> &mut Interactivity { self.element.interactivity() } } -impl StatefulInteractiveComponent for Focusable {} +impl StatefulInteractiveElement for Focusable {} impl Styled for Focusable where @@ -1258,42 +1258,39 @@ impl Element for Focusable where E: Element, { - type ElementState = E::ElementState; + type State = E::State; + + fn layout( + &mut self, + state: Option, + cx: &mut WindowContext, + ) -> (LayoutId, Self::State) { + self.element.layout(state, cx) + } + + fn paint(self, bounds: Bounds, state: &mut Self::State, cx: &mut WindowContext) { + self.element.paint(bounds, state, cx) + } +} + +impl RenderOnce for Focusable +where + E: Element, +{ + type Element = E; fn element_id(&self) -> Option { self.element.element_id() } - fn layout( - &mut self, - element_state: Option, - cx: &mut WindowContext, - ) -> (LayoutId, Self::ElementState) { - self.element.layout(element_state, cx) - } - - fn paint( - &mut self, - bounds: Bounds, - element_state: &mut Self::ElementState, - cx: &mut WindowContext, - ) { - self.element.paint(bounds, element_state, cx); + fn render_once(self) -> Self::Element { + self.element } } -impl Component for Focusable +impl ParentElement for Focusable where - E: 'static + Element, -{ - fn render(self) -> AnyElement { - AnyElement::new(self) - } -} - -impl ParentComponent for Focusable -where - E: ParentComponent, + E: ParentElement, { fn children_mut(&mut self) -> &mut SmallVec<[AnyElement; 2]> { self.element.children_mut() @@ -1313,64 +1310,61 @@ where } } -impl StatefulInteractiveComponent for Stateful +impl StatefulInteractiveElement for Stateful where E: Element, - Self: InteractiveComponent, + Self: InteractiveElement, { } -impl InteractiveComponent for Stateful +impl InteractiveElement for Stateful where - 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 E: Element, { - type ElementState = E::ElementState; + type State = E::State; + + fn layout( + &mut self, + state: Option, + cx: &mut WindowContext, + ) -> (LayoutId, Self::State) { + self.element.layout(state, cx) + } + + fn paint(self, bounds: Bounds, state: &mut Self::State, cx: &mut WindowContext) { + self.element.paint(bounds, state, cx) + } +} + +impl RenderOnce for Stateful +where + E: Element, +{ + type Element = Self; fn element_id(&self) -> Option { self.element.element_id() } - fn layout( - &mut self, - element_state: Option, - cx: &mut WindowContext, - ) -> (LayoutId, Self::ElementState) { - self.element.layout(element_state, cx) - } - - fn paint( - &mut self, - bounds: Bounds, - element_state: &mut Self::ElementState, - cx: &mut WindowContext, - ) { - self.element.paint(bounds, element_state, cx) + fn render_once(self) -> Self::Element { + self } } -impl Component for Stateful +impl ParentElement for Stateful where - E: 'static + Element, -{ - fn render(self) -> AnyElement { - AnyElement::new(self) - } -} - -impl ParentComponent for Stateful -where - 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 023811dcd2..3c0f4c00be 100644 --- a/crates/gpui2/src/elements/img.rs +++ b/crates/gpui2/src/elements/img.rs @@ -1,6 +1,6 @@ use crate::{ - AnyElement, Bounds, Component, Element, InteractiveComponent, InteractiveElementState, - Interactivity, LayoutId, Pixels, SharedString, StyleRefinement, Styled, WindowContext, + Bounds, Element, InteractiveElement, InteractiveElementState, Interactivity, LayoutId, Pixels, + RenderOnce, SharedString, StyleRefinement, Styled, WindowContext, }; use futures::FutureExt; use util::ResultExt; @@ -31,33 +31,23 @@ impl Img { } } -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, - element_state: Option, + element_state: Option, cx: &mut WindowContext, - ) -> (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, - element_state: &mut Self::ElementState, + element_state: &mut Self::State, cx: &mut WindowContext, ) { self.interactivity.paint( @@ -96,13 +86,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 19dab158cc..29ac2f00c4 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, WindowContext, + point, AnyElement, BorrowWindow, Bounds, Element, LayoutId, ParentElement, Pixels, Point, + RenderOnce, Size, Style, WindowContext, }; pub struct OverlayState { @@ -51,30 +51,20 @@ 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, - _: Option, + _: Option, cx: &mut WindowContext, - ) -> (crate::LayoutId, Self::ElementState) { + ) -> (crate::LayoutId, Self::State) { let child_layout_ids = self .children .iter_mut() @@ -91,9 +81,9 @@ impl Element for Overlay { } fn paint( - &mut self, + self, bounds: crate::Bounds, - element_state: &mut Self::ElementState, + element_state: &mut Self::State, cx: &mut WindowContext, ) { if element_state.child_layout_ids.is_empty() { @@ -154,13 +144,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(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 2a0df36c94..c24e4d9b8b 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, WindowContext, + Bounds, Element, ElementId, InteractiveElement, InteractiveElementState, Interactivity, + LayoutId, Pixels, RenderOnce, SharedString, StyleRefinement, Styled, WindowContext, }; use util::ResultExt; @@ -24,35 +23,21 @@ 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, - element_state: Option, + element_state: Option, cx: &mut WindowContext, - ) -> (LayoutId, Self::ElementState) { + ) -> (LayoutId, Self::State) { self.interactivity.layout(element_state, cx, |style, cx| { cx.request_layout(&style, None) }) } - fn paint( - &mut self, - bounds: Bounds, - element_state: &mut Self::ElementState, - cx: &mut WindowContext, - ) where + fn paint(self, bounds: Bounds, element_state: &mut Self::State, cx: &mut WindowContext) + where Self: Sized, { self.interactivity @@ -64,13 +49,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 99226baaa7..05ab85ca63 100644 --- a/crates/gpui2/src/elements/text.rs +++ b/crates/gpui2/src/elements/text.rs @@ -1,68 +1,163 @@ use crate::{ - AnyElement, Bounds, Component, Element, ElementId, LayoutId, Pixels, SharedString, Size, - TextRun, WindowContext, WrappedLine, + Bounds, Element, ElementId, LayoutId, Pixels, RenderOnce, SharedString, Size, TextRun, + 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, + _: Option, + cx: &mut WindowContext, + ) -> (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, state: &mut TextState, cx: &mut WindowContext) { + 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, + _: Option, + cx: &mut WindowContext, + ) -> (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, state: &mut TextState, cx: &mut WindowContext) { + 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 { + None + } + + 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 StyledText { + type State = TextState; + + fn layout( + &mut self, + _: Option, + cx: &mut WindowContext, + ) -> (LayoutId, Self::State) { + let mut state = TextState::default(); + let layout_id = state.layout(self.text.clone(), self.runs.take(), cx); + (layout_id, state) + } + + fn paint(self, bounds: Bounds, state: &mut Self::State, cx: &mut WindowContext) { + state.paint(bounds, &self.text, cx) } } -impl Element for Text { - type ElementState = TextState; +impl RenderOnce for StyledText { + type Element = Self; 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, + wrap_width: Option, + size: Option>, +} + +impl TextState { + fn lock(&self) -> MutexGuard> { + self.0.lock() + } + fn layout( &mut self, - element_state: Option, + text: SharedString, + runs: Option>, cx: &mut WindowContext, - ) -> (LayoutId, Self::ElementState) { - let element_state = element_state.unwrap_or_default(); + ) -> 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 = self.text.clone(); + let text = SharedString::from(text); let rem_size = cx.rem_size(); - let runs = if let Some(runs) = self.runs.take() { + 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 = element_state.clone(); + 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), @@ -113,19 +208,14 @@ impl Element for Text { } }); - (layout_id, element_state) + layout_id } - fn paint( - &mut self, - bounds: Bounds, - element_state: &mut Self::ElementState, - cx: &mut WindowContext, - ) { - let element_state = element_state.lock(); + 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::anyhow!("measurement has not been performed on {}", &self.text)) + .ok_or_else(|| anyhow!("measurement has not been performed on {}", text)) .unwrap(); let line_height = element_state.line_height; @@ -137,25 +227,9 @@ impl Element for Text { } } -#[derive(Default, Clone)] -pub struct TextState(Arc>>); - -impl TextState { - fn lock(&self) -> MutexGuard> { - self.0.lock() - } -} - -struct TextStateInner { - lines: SmallVec<[WrappedLine; 1]>, - line_height: Pixels, - wrap_width: Option, - size: Option>, -} - struct InteractiveText { - id: ElementId, - text: Text, + element_id: ElementId, + text: StyledText, } struct InteractiveTextState { @@ -164,21 +238,17 @@ 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, - element_state: Option, + state: Option, cx: &mut WindowContext, - ) -> (LayoutId, Self::ElementState) { + ) -> (LayoutId, Self::State) { if let Some(InteractiveTextState { text_state, clicked_range_ixs, - }) = element_state + }) = state { let (layout_id, text_state) = self.text.layout(Some(text_state), cx); let element_state = InteractiveTextState { @@ -196,44 +266,19 @@ impl Element for InteractiveText { } } - fn paint( - &mut self, - bounds: Bounds, - element_state: &mut Self::ElementState, - cx: &mut WindowContext, - ) { - self.text.paint(bounds, &mut element_state.text_state, cx) + fn paint(self, bounds: Bounds, state: &mut Self::State, cx: &mut WindowContext) { + self.text.paint(bounds, &mut state.text_state, cx) } } -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.element_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 48d666383f..0c7e9eb3fd 100644 --- a/crates/gpui2/src/elements/uniform_list.rs +++ b/crates/gpui2/src/elements/uniform_list.rs @@ -1,23 +1,23 @@ use crate::{ - point, px, size, AnyElement, AvailableSpace, Bounds, Component, Element, ElementId, - InteractiveComponent, InteractiveElementState, Interactivity, LayoutId, Pixels, Point, Size, + point, px, size, AnyElement, AvailableSpace, Bounds, Element, ElementId, InteractiveElement, + InteractiveElementState, Interactivity, LayoutId, Pixels, Point, RenderOnce, Size, StyleRefinement, Styled, WindowContext, }; 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(Range, &mut WindowContext) -> Vec, + f: impl 'static + Fn(Range, &mut WindowContext) -> Vec, ) -> UniformList where I: Into, - C: Component, + R: RenderOnce, { let id = id.into(); let mut style = StyleRefinement::default(); @@ -31,7 +31,7 @@ where render_items: Box::new(move |visible_range, cx| { f(visible_range, cx) .into_iter() - .map(|component| component.render()) + .map(|component| component.render_into_any()) .collect() }), interactivity: Interactivity { @@ -96,27 +96,23 @@ 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, - element_state: Option, + state: Option, cx: &mut WindowContext, - ) -> (LayoutId, Self::ElementState) { + ) -> (LayoutId, Self::State) { let max_items = self.item_count; let rem_size = cx.rem_size(); - let item_size = element_state + let item_size = state .as_ref() .map(|s| s.item_size) .unwrap_or_else(|| self.measure_item(None, cx)); let (layout_id, interactive) = self.interactivity - .layout(element_state.map(|s| s.interactive), cx, |style, cx| { + .layout(state.map(|s| s.interactive), cx, |style, cx| { cx.request_measured_layout( style, rem_size, @@ -152,9 +148,9 @@ impl Element for UniformList { } fn paint( - &mut self, + self, bounds: Bounds, - element_state: &mut Self::ElementState, + element_state: &mut Self::State, cx: &mut WindowContext, ) { let style = @@ -175,14 +171,15 @@ 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(Some(padded_bounds.size.width), cx).height; + + self.interactivity.paint( bounds, content_size, &mut element_state.interactive, @@ -201,8 +198,6 @@ impl Element for UniformList { style.paint(bounds, cx); if self.item_count > 0 { - let item_height = - self.measure_item(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, @@ -224,9 +219,9 @@ impl Element for UniformList { self.item_count, ); - let mut items = (self.render_items)(visible_range.clone(), cx); + let items = (self.render_items)(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( @@ -240,7 +235,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 } } @@ -273,14 +279,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 39b148b15b..2115380c7c 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; @@ -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 2604d24f89..483f7599c5 100644 --- a/crates/gpui2/src/interactive.rs +++ b/crates/gpui2/src/interactive.rs @@ -1,8 +1,9 @@ use crate::{ - div, point, Div, FocusHandle, Keystroke, Modifiers, Pixels, Point, Render, ViewContext, + div, point, Div, Element, FocusHandle, Keystroke, Modifiers, Pixels, Point, Render, RenderOnce, + ViewContext, }; use smallvec::SmallVec; -use std::{any::Any, fmt::Debug, ops::Deref, path::PathBuf}; +use std::{any::Any, fmt::Debug, marker::PhantomData, ops::Deref, path::PathBuf}; #[derive(Clone, Debug, Eq, PartialEq)] pub struct KeyDownEvent { @@ -59,6 +60,32 @@ pub struct ClickEvent { pub up: MouseUpEvent, } +pub struct Drag +where + R: Fn(&mut V, &mut ViewContext) -> E, + V: 'static, + E: RenderOnce, +{ + pub state: S, + pub render_drag_handle: R, + view_element_types: PhantomData<(V, E)>, +} + +impl Drag +where + R: Fn(&mut V, &mut ViewContext) -> E, + V: 'static, + E: Element, +{ + pub fn new(state: S, render_drag_handle: R) -> Self { + Drag { + state, + render_drag_handle, + view_element_types: Default::default(), + } + } +} + #[derive(Hash, PartialEq, Eq, Copy, Clone, Debug)] pub enum MouseButton { Left, @@ -259,8 +286,8 @@ pub struct FocusEvent { #[cfg(test)] mod test { use crate::{ - self as gpui, div, Component, Div, FocusHandle, InteractiveComponent, KeyBinding, - Keystroke, ParentComponent, Render, Stateful, TestAppContext, VisualContext, + self as gpui, div, Div, FocusHandle, InteractiveElement, KeyBinding, Keystroke, + ParentElement, Render, RenderOnce, Stateful, TestAppContext, VisualContext, }; struct TestView { @@ -288,7 +315,7 @@ mod test { div() .key_context("nested") .track_focus(&self.focus_handle) - .render(), + .render_once(), ), ) } diff --git a/crates/gpui2/src/prelude.rs b/crates/gpui2/src/prelude.rs index 7c2ad3f07f..50f48596bc 100644 --- a/crates/gpui2/src/prelude.rs +++ b/crates/gpui2/src/prelude.rs @@ -1,4 +1,5 @@ pub use crate::{ - BorrowAppContext, BorrowWindow, Component, Context, FocusableComponent, InteractiveComponent, - ParentComponent, Refineable, Render, StatefulInteractiveComponent, Styled, VisualContext, + BorrowAppContext, BorrowWindow, Component, Context, Element, FocusableElement, + InteractiveElement, ParentElement, Refineable, Render, RenderOnce, StatefulInteractiveElement, + Styled, VisualContext, }; diff --git a/crates/gpui2/src/view.rs b/crates/gpui2/src/view.rs index 7bbbbce7af..734150ee58 100644 --- a/crates/gpui2/src/view.rs +++ b/crates/gpui2/src/view.rs @@ -1,23 +1,17 @@ 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}; use std::{ - any::{Any, TypeId}, + any::TypeId, 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,15 +59,15 @@ impl View { self.model.read(cx) } - pub fn render_with(&self, component: C) -> RenderViewWith - where - C: 'static + Component, - { - RenderViewWith { - view: self.clone(), - component: Some(component), - } - } + // pub fn render_with(&self, component: E) -> RenderViewWith + // where + // E: 'static + Element, + // { + // RenderViewWith { + // view: self.clone(), + // element: Some(component), + // } + // } pub fn focus_handle(&self, cx: &AppContext) -> FocusHandle where @@ -83,6 +77,26 @@ impl View { } } +impl Element for View { + type State = Option; + + fn layout( + &mut self, + _state: Option, + cx: &mut WindowContext, + ) -> (LayoutId, Self::State) { + self.update(cx, |view, cx| { + let mut element = view.render(cx).into_any(); + let layout_id = element.layout(cx); + (layout_id, Some(element)) + }) + } + + fn paint(self, _: Bounds, element: &mut Self::State, cx: &mut WindowContext) { + element.take().unwrap().paint(cx); + } +} + impl Clone for View { fn clone(&self) -> Self { Self { @@ -105,12 +119,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, } @@ -163,8 +171,8 @@ impl Eq for WeakView {} #[derive(Clone, Debug)] pub struct AnyView { model: AnyModel, - layout: fn(&AnyView, &mut WindowContext) -> (LayoutId, Box), - paint: fn(&AnyView, &mut AnyBox, &mut WindowContext), + layout: fn(&AnyView, &mut WindowContext) -> (LayoutId, AnyElement), + paint: fn(&AnyView, AnyElement, &mut WindowContext), } impl AnyView { @@ -202,21 +210,15 @@ 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 { fn from(value: View) -> Self { AnyView { @@ -228,34 +230,50 @@ impl From> for AnyView { } impl Element for AnyView { - type ElementState = Box; + type State = Option; + + fn layout( + &mut self, + _state: Option, + cx: &mut WindowContext, + ) -> (LayoutId, Self::State) { + let (layout_id, state) = (self.layout)(self, cx); + (layout_id, Some(state)) + } + + fn paint(self, _: Bounds, state: &mut Self::State, cx: &mut WindowContext) { + (self.paint)(&self, state.take().unwrap(), cx) + } +} + +impl RenderOnce for View { + type Element = View; fn element_id(&self) -> Option { Some(self.model.entity_id.into()) } - fn layout( - &mut self, - _element_state: Option, - cx: &mut WindowContext, - ) -> (LayoutId, Self::ElementState) { - (self.layout)(self, cx) + fn render_once(self) -> Self::Element { + self + } +} + +impl RenderOnce for AnyView { + type Element = Self; + + fn element_id(&self) -> Option { + Some(self.model.entity_id.into()) } - fn paint( - &mut self, - _bounds: Bounds, - rendered_element: &mut Self::ElementState, - cx: &mut WindowContext, - ) { - (self.paint)(self, rendered_element, cx) + 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), + layout: fn(&AnyView, &mut WindowContext) -> (LayoutId, AnyElement), + paint: fn(&AnyView, AnyElement, &mut WindowContext), } impl AnyWeakView { @@ -269,7 +287,7 @@ impl AnyWeakView { } } -impl From> for AnyWeakView { +impl From> for AnyWeakView { fn from(view: WeakView) -> Self { Self { model: view.model.into(), @@ -291,77 +309,73 @@ where } } -pub struct RenderViewWith { - view: View, - component: Option, -} +// pub struct RenderViewWith { +// view: View, +// element: Option, +// } -impl Component for RenderViewWith -where - V: 'static + Render, - C: 'static + Component, -{ - fn render(self) -> AnyElement { - AnyElement::new(self) - } -} +// impl Element for RenderViewWith +// where +// E: 'static + Element, +// { +// type State = Option; -impl Element for RenderViewWith -where - V: 'static + Render, - C: 'static + Component, -{ - type ElementState = AnyElement; +// fn layout( +// &mut self, +// _: Option, +// cx: &mut WindowContext, +// ) -> (LayoutId, Self::State) { +// self.view.update(cx, |view, cx| { +// let mut element = self.element.take().unwrap().into_any(); +// let layout_id = element.layout(view, cx); +// (layout_id, Some(element)) +// }) +// } - fn element_id(&self) -> Option { - Some(self.view.entity_id().into()) - } +// fn paint(self, _: Bounds, element: &mut Self::ElementState, cx: &mut WindowContext) { +// element.paint(cx) +// } +// } - fn layout( - &mut self, - _: Option, - cx: &mut WindowContext, - ) -> (LayoutId, Self::ElementState) { - let mut element = self.component.take().unwrap().render(); - let layout_id = element.layout(cx); - (layout_id, element) - } +// impl RenderOnce for RenderViewWith +// where +// E: 'static + Element, +// ParentV: 'static, +// { +// type Element = Self; - fn paint( - &mut self, - _: Bounds, - element: &mut Self::ElementState, - cx: &mut WindowContext, - ) { - element.paint(cx) - } -} +// fn element_id(&self) -> Option { +// self.element.as_ref().unwrap().element_id() +// } + +// fn render_once(self) -> Self::Element { +// self +// } +// } mod any_view { - use crate::{AnyElement, AnyView, BorrowWindow, LayoutId, Render, WindowContext}; - use std::any::Any; + use crate::{AnyElement, AnyView, BorrowWindow, Element, LayoutId, Render, WindowContext}; - pub(crate) fn layout( + pub(crate) fn layout( view: &AnyView, cx: &mut WindowContext, - ) -> (LayoutId, Box) { + ) -> (LayoutId, AnyElement) { cx.with_element_id(Some(view.model.entity_id), |cx| { let view = view.clone().downcast::().unwrap(); view.update(cx, |view, cx| { - let mut element = AnyElement::new(view.render(cx)); + let mut element = view.render(cx).into_any(); let layout_id = element.layout(cx); - (layout_id, Box::new(element) as Box) + (layout_id, element) }) }) } - pub(crate) fn paint( + pub(crate) fn paint( view: &AnyView, - element: &mut Box, + element: AnyElement, cx: &mut WindowContext, ) { cx.with_element_id(Some(view.model.entity_id), |cx| { - let element = element.downcast_mut::().unwrap(); element.paint(cx); }) } diff --git a/crates/gpui2/src/window.rs b/crates/gpui2/src/window.rs index d3517996c8..fdcafc03bb 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, CallbackHandle, ConstructorHandle, Context, Corners, CursorStyle, DevicePixels, DispatchNodeId, DispatchTree, DisplayId, Edges, Effect, Entity, EntityId, EventEmitter, FileDropEvent, Flatten, FocusEvent, FontId, @@ -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, @@ -1600,7 +1600,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 { @@ -2372,7 +2372,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) } diff --git a/crates/gpui2_macros/src/derive_render_once.rs b/crates/gpui2_macros/src/derive_render_once.rs new file mode 100644 index 0000000000..732f2df21f --- /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 3ce8373689..6dd817e280 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 80c0e2b219..f29312234c 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 57ede0c961..da3ada4c10 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/storybook2/src/stories/colors.rs b/crates/storybook2/src/stories/colors.rs index 4f8c54fa6f..b690435e01 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 571882f1f2..12c7ea81a0 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 507aa8db2d..2d31cefed6 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 a3f9ef5eb8..7c2412a02f 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 f1bb7b4e7c..bbab0b1d11 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 512d680d37..716004dea7 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 46ec0f4a35..087ed913fd 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 a0bc7cd72f..2a22d91382 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 9885208b41..cb64bd7f0d 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 944cd912be..46885913ed 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 27e55602fb..3cbea11072 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 4296d4f99c..e0c802fcc7 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 b8a983ba51..726e4bac56 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 e8ed3bb944..f48dd460b8 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 d083d8fd46..da76a95cfa 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, - } - } +impl Component for Avatar { + type Rendered = Img; - pub fn shape(mut self, shape: Shape) -> Self { - self.shape = shape; - self - } - - fn render(self, _view: &mut V, cx: &mut ViewContext) -> impl 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 fdfad8a435..d7d526b087 100644 --- a/crates/ui2/src/components/button.rs +++ b/crates/ui2/src/components/button.rs @@ -1,7 +1,8 @@ use std::sync::Arc; use gpui::{ - CallbackHandle, DefiniteLength, Hsla, MouseButton, StatefulInteractiveComponent, WindowContext, + DefiniteLength, DefiniteLength, Div, Hsla, MouseButton, RenderOnce, Stateful, + StatefulInteractiveElement, WindowContext, }; use crate::prelude::*; @@ -63,7 +64,6 @@ impl ButtonVariant { } } -// #[derive(Component)] <- todo pub struct Button { disabled: bool, click_handler: Option>, @@ -75,6 +75,57 @@ pub struct Button { color: Option, } +impl RenderOnce for Button { + type Element = Stateful
; + + fn render(self) -> Self::Rendered { + 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 { @@ -150,72 +201,30 @@ impl Button { fn render_icon(&self, icon_color: TextColor) -> Option { self.icon.map(|i| IconElement::new(i).color(icon_color)) } - - pub fn render(self, cx: &mut WindowContext) -> impl Component { - 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 - } } +#[derive(RenderOnce)] pub struct ButtonGroup { buttons: Vec