Merge branch 'main' into callback-handles

This commit is contained in:
Conrad Irwin 2023-11-20 12:21:42 -07:00
commit d0dd44faad
117 changed files with 3170 additions and 2027 deletions

2
Cargo.lock generated
View file

@ -1550,6 +1550,7 @@ dependencies = [
"anyhow", "anyhow",
"async-recursion 0.3.2", "async-recursion 0.3.2",
"async-tungstenite", "async-tungstenite",
"chrono",
"collections", "collections",
"db", "db",
"feature_flags", "feature_flags",
@ -1586,6 +1587,7 @@ dependencies = [
"anyhow", "anyhow",
"async-recursion 0.3.2", "async-recursion 0.3.2",
"async-tungstenite", "async-tungstenite",
"chrono",
"collections", "collections",
"db2", "db2",
"feature_flags2", "feature_flags2",

View file

@ -15,7 +15,7 @@ use ai::{
use ai::prompts::repository_context::PromptCodeSnippet; use ai::prompts::repository_context::PromptCodeSnippet;
use anyhow::{anyhow, Result}; use anyhow::{anyhow, Result};
use chrono::{DateTime, Local}; use chrono::{DateTime, Local};
use client::{telemetry::AssistantKind, ClickhouseEvent, TelemetrySettings}; use client::{telemetry::AssistantKind, TelemetrySettings};
use collections::{hash_map, HashMap, HashSet, VecDeque}; use collections::{hash_map, HashMap, HashSet, VecDeque};
use editor::{ use editor::{
display_map::{ display_map::{
@ -3803,12 +3803,12 @@ fn report_assistant_event(
.default_open_ai_model .default_open_ai_model
.clone(); .clone();
let event = ClickhouseEvent::Assistant {
conversation_id,
kind: assistant_kind,
model: model.full_name(),
};
let telemetry_settings = *settings::get::<TelemetrySettings>(cx); let telemetry_settings = *settings::get::<TelemetrySettings>(cx);
telemetry.report_clickhouse_event(event, telemetry_settings) telemetry.report_assistant_event(
telemetry_settings,
conversation_id,
assistant_kind,
model.full_name(),
)
} }

View file

@ -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 menu::Cancel;
use workspace::notifications::NotificationEvent; use workspace::notifications::NotificationEvent;
@ -8,7 +8,7 @@ pub struct UpdateNotification {
impl EventEmitter<NotificationEvent> for UpdateNotification {} impl EventEmitter<NotificationEvent> for UpdateNotification {}
impl Render for UpdateNotification { impl Render<Self> for UpdateNotification {
type Element = Div<Self>; type Element = Div<Self>;
fn render(&mut self, _cx: &mut gpui::ViewContext<Self>) -> Self::Element { fn render(&mut self, _cx: &mut gpui::ViewContext<Self>) -> Self::Element {

View file

@ -5,10 +5,7 @@ pub mod room;
use anyhow::{anyhow, Result}; use anyhow::{anyhow, Result};
use audio::Audio; use audio::Audio;
use call_settings::CallSettings; use call_settings::CallSettings;
use client::{ use client::{proto, Client, TelemetrySettings, TypedEnvelope, User, UserStore, ZED_ALWAYS_ACTIVE};
proto, ClickhouseEvent, Client, TelemetrySettings, TypedEnvelope, User, UserStore,
ZED_ALWAYS_ACTIVE,
};
use collections::HashSet; use collections::HashSet;
use futures::{channel::oneshot, future::Shared, Future, FutureExt}; use futures::{channel::oneshot, future::Shared, Future, FutureExt};
use gpui::{ use gpui::{
@ -485,12 +482,8 @@ pub fn report_call_event_for_room(
) { ) {
let telemetry = client.telemetry(); let telemetry = client.telemetry();
let telemetry_settings = *settings::get::<TelemetrySettings>(cx); let telemetry_settings = *settings::get::<TelemetrySettings>(cx);
let event = ClickhouseEvent::Call {
operation, telemetry.report_call_event(telemetry_settings, operation, Some(room_id), channel_id)
room_id: Some(room_id),
channel_id,
};
telemetry.report_clickhouse_event(event, telemetry_settings);
} }
pub fn report_call_event_for_channel( pub fn report_call_event_for_channel(
@ -504,12 +497,12 @@ pub fn report_call_event_for_channel(
let telemetry = client.telemetry(); let telemetry = client.telemetry();
let telemetry_settings = *settings::get::<TelemetrySettings>(cx); let telemetry_settings = *settings::get::<TelemetrySettings>(cx);
let event = ClickhouseEvent::Call { telemetry.report_call_event(
telemetry_settings,
operation, operation,
room_id: room.map(|r| r.read(cx).id()), room.map(|r| r.read(cx).id()),
channel_id: Some(channel_id), Some(channel_id),
}; )
telemetry.report_clickhouse_event(event, telemetry_settings);
} }
#[cfg(test)] #[cfg(test)]

View file

@ -5,10 +5,7 @@ pub mod room;
use anyhow::{anyhow, Result}; use anyhow::{anyhow, Result};
use audio::Audio; use audio::Audio;
use call_settings::CallSettings; use call_settings::CallSettings;
use client::{ use client::{proto, Client, TelemetrySettings, TypedEnvelope, User, UserStore, ZED_ALWAYS_ACTIVE};
proto, ClickhouseEvent, Client, TelemetrySettings, TypedEnvelope, User, UserStore,
ZED_ALWAYS_ACTIVE,
};
use collections::HashSet; use collections::HashSet;
use futures::{channel::oneshot, future::Shared, Future, FutureExt}; use futures::{channel::oneshot, future::Shared, Future, FutureExt};
use gpui::{ use gpui::{
@ -484,12 +481,8 @@ pub fn report_call_event_for_room(
) { ) {
let telemetry = client.telemetry(); let telemetry = client.telemetry();
let telemetry_settings = *TelemetrySettings::get_global(cx); let telemetry_settings = *TelemetrySettings::get_global(cx);
let event = ClickhouseEvent::Call {
operation, telemetry.report_call_event(telemetry_settings, operation, Some(room_id), channel_id)
room_id: Some(room_id),
channel_id,
};
telemetry.report_clickhouse_event(event, telemetry_settings);
} }
pub fn report_call_event_for_channel( 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 telemetry_settings = *TelemetrySettings::get_global(cx);
let event = ClickhouseEvent::Call { telemetry.report_call_event(
telemetry_settings,
operation, operation,
room_id: room.map(|r| r.read(cx).id()), room.map(|r| r.read(cx).id()),
channel_id: Some(channel_id), Some(channel_id),
}; )
telemetry.report_clickhouse_event(event, telemetry_settings);
} }
#[cfg(test)] #[cfg(test)]

View file

@ -12,6 +12,7 @@ doctest = false
test-support = ["collections/test-support", "gpui/test-support", "rpc/test-support"] test-support = ["collections/test-support", "gpui/test-support", "rpc/test-support"]
[dependencies] [dependencies]
chrono = { version = "0.4", features = ["serde"] }
collections = { path = "../collections" } collections = { path = "../collections" }
db = { path = "../db" } db = { path = "../db" }
gpui = { path = "../gpui" } gpui = { path = "../gpui" }

View file

@ -1,4 +1,5 @@
use crate::{TelemetrySettings, ZED_SECRET_CLIENT_TOKEN, ZED_SERVER_URL}; use crate::{TelemetrySettings, ZED_SECRET_CLIENT_TOKEN, ZED_SERVER_URL};
use chrono::{DateTime, Utc};
use gpui::{executor::Background, serde_json, AppContext, Task}; use gpui::{executor::Background, serde_json, AppContext, Task};
use lazy_static::lazy_static; use lazy_static::lazy_static;
use parking_lot::Mutex; use parking_lot::Mutex;
@ -31,6 +32,7 @@ struct TelemetryState {
flush_clickhouse_events_task: Option<Task<()>>, flush_clickhouse_events_task: Option<Task<()>>,
log_file: Option<NamedTempFile>, log_file: Option<NamedTempFile>,
is_staff: Option<bool>, is_staff: Option<bool>,
first_event_datetime: Option<DateTime<Utc>>,
} }
const CLICKHOUSE_EVENTS_URL_PATH: &'static str = "/api/events"; const CLICKHOUSE_EVENTS_URL_PATH: &'static str = "/api/events";
@ -77,29 +79,35 @@ pub enum ClickhouseEvent {
vim_mode: bool, vim_mode: bool,
copilot_enabled: bool, copilot_enabled: bool,
copilot_enabled_for_language: bool, copilot_enabled_for_language: bool,
milliseconds_since_first_event: i64,
}, },
Copilot { Copilot {
suggestion_id: Option<String>, suggestion_id: Option<String>,
suggestion_accepted: bool, suggestion_accepted: bool,
file_extension: Option<String>, file_extension: Option<String>,
milliseconds_since_first_event: i64,
}, },
Call { Call {
operation: &'static str, operation: &'static str,
room_id: Option<u64>, room_id: Option<u64>,
channel_id: Option<u64>, channel_id: Option<u64>,
milliseconds_since_first_event: i64,
}, },
Assistant { Assistant {
conversation_id: Option<String>, conversation_id: Option<String>,
kind: AssistantKind, kind: AssistantKind,
model: &'static str, model: &'static str,
milliseconds_since_first_event: i64,
}, },
Cpu { Cpu {
usage_as_percentage: f32, usage_as_percentage: f32,
core_count: u32, core_count: u32,
milliseconds_since_first_event: i64,
}, },
Memory { Memory {
memory_in_bytes: u64, memory_in_bytes: u64,
virtual_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(), flush_clickhouse_events_task: Default::default(),
log_file: None, log_file: None,
is_staff: None, is_staff: None,
first_event_datetime: None,
}), }),
}); });
@ -195,20 +204,18 @@ impl Telemetry {
return; 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::<TelemetrySettings>(cx)); let telemetry_settings = cx.update(|cx| *settings::get::<TelemetrySettings>(cx));
this.report_clickhouse_event(memory_event, telemetry_settings); this.report_memory_event(
this.report_clickhouse_event(cpu_event, telemetry_settings); telemetry_settings,
process.memory(),
process.virtual_memory(),
);
this.report_cpu_event(
telemetry_settings,
process.cpu_usage(),
system.cpus().len() as u32,
);
} }
}) })
.detach(); .detach();
@ -231,7 +238,123 @@ impl Telemetry {
drop(state); drop(state);
} }
pub fn report_clickhouse_event( pub fn report_editor_event(
self: &Arc<Self>,
telemetry_settings: TelemetrySettings,
file_extension: Option<String>,
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<Self>,
telemetry_settings: TelemetrySettings,
suggestion_id: Option<String>,
suggestion_accepted: bool,
file_extension: Option<String>,
) {
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<Self>,
telemetry_settings: TelemetrySettings,
conversation_id: Option<String>,
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<Self>,
telemetry_settings: TelemetrySettings,
operation: &'static str,
room_id: Option<u64>,
channel_id: Option<u64>,
) {
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<Self>,
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<Self>,
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> = 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<Self>, self: &Arc<Self>,
event: ClickhouseEvent, event: ClickhouseEvent,
telemetry_settings: TelemetrySettings, telemetry_settings: TelemetrySettings,
@ -275,6 +398,7 @@ impl Telemetry {
fn flush_clickhouse_events(self: &Arc<Self>) { fn flush_clickhouse_events(self: &Arc<Self>) {
let mut state = self.state.lock(); let mut state = self.state.lock();
state.first_event_datetime = None;
let mut events = mem::take(&mut state.clickhouse_events_queue); let mut events = mem::take(&mut state.clickhouse_events_queue);
state.flush_clickhouse_events_task.take(); state.flush_clickhouse_events_task.take();
drop(state); drop(state);

View file

@ -12,6 +12,7 @@ doctest = false
test-support = ["collections/test-support", "gpui/test-support", "rpc/test-support"] test-support = ["collections/test-support", "gpui/test-support", "rpc/test-support"]
[dependencies] [dependencies]
chrono = { version = "0.4", features = ["serde"] }
collections = { path = "../collections" } collections = { path = "../collections" }
db = { package = "db2", path = "../db2" } db = { package = "db2", path = "../db2" }
gpui = { package = "gpui2", path = "../gpui2" } gpui = { package = "gpui2", path = "../gpui2" }

View file

@ -1,4 +1,5 @@
use crate::{TelemetrySettings, ZED_SECRET_CLIENT_TOKEN, ZED_SERVER_URL}; use crate::{TelemetrySettings, ZED_SECRET_CLIENT_TOKEN, ZED_SERVER_URL};
use chrono::{DateTime, Utc};
use gpui::{serde_json, AppContext, AppMetadata, BackgroundExecutor, Task}; use gpui::{serde_json, AppContext, AppMetadata, BackgroundExecutor, Task};
use lazy_static::lazy_static; use lazy_static::lazy_static;
use parking_lot::Mutex; use parking_lot::Mutex;
@ -29,6 +30,7 @@ struct TelemetryState {
flush_clickhouse_events_task: Option<Task<()>>, flush_clickhouse_events_task: Option<Task<()>>,
log_file: Option<NamedTempFile>, log_file: Option<NamedTempFile>,
is_staff: Option<bool>, is_staff: Option<bool>,
first_event_datetime: Option<DateTime<Utc>>,
} }
const CLICKHOUSE_EVENTS_URL_PATH: &'static str = "/api/events"; const CLICKHOUSE_EVENTS_URL_PATH: &'static str = "/api/events";
@ -75,29 +77,35 @@ pub enum ClickhouseEvent {
vim_mode: bool, vim_mode: bool,
copilot_enabled: bool, copilot_enabled: bool,
copilot_enabled_for_language: bool, copilot_enabled_for_language: bool,
milliseconds_since_first_event: i64,
}, },
Copilot { Copilot {
suggestion_id: Option<String>, suggestion_id: Option<String>,
suggestion_accepted: bool, suggestion_accepted: bool,
file_extension: Option<String>, file_extension: Option<String>,
milliseconds_since_first_event: i64,
}, },
Call { Call {
operation: &'static str, operation: &'static str,
room_id: Option<u64>, room_id: Option<u64>,
channel_id: Option<u64>, channel_id: Option<u64>,
milliseconds_since_first_event: i64,
}, },
Assistant { Assistant {
conversation_id: Option<String>, conversation_id: Option<String>,
kind: AssistantKind, kind: AssistantKind,
model: &'static str, model: &'static str,
milliseconds_since_first_event: i64,
}, },
Cpu { Cpu {
usage_as_percentage: f32, usage_as_percentage: f32,
core_count: u32, core_count: u32,
milliseconds_since_first_event: i64,
}, },
Memory { Memory {
memory_in_bytes: u64, memory_in_bytes: u64,
virtual_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(), flush_clickhouse_events_task: Default::default(),
log_file: None, log_file: None,
is_staff: None, is_staff: None,
first_event_datetime: None,
}), }),
}); });
@ -190,16 +199,6 @@ impl Telemetry {
return; 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) = let telemetry_settings = if let Ok(telemetry_settings) =
cx.update(|cx| *TelemetrySettings::get_global(cx)) cx.update(|cx| *TelemetrySettings::get_global(cx))
{ {
@ -208,8 +207,16 @@ impl Telemetry {
break; break;
}; };
this.report_clickhouse_event(memory_event, telemetry_settings); this.report_memory_event(
this.report_clickhouse_event(cpu_event, telemetry_settings); telemetry_settings,
process.memory(),
process.virtual_memory(),
);
this.report_cpu_event(
telemetry_settings,
process.cpu_usage(),
system.cpus().len() as u32,
);
} }
}) })
.detach(); .detach();
@ -232,7 +239,123 @@ impl Telemetry {
drop(state); drop(state);
} }
pub fn report_clickhouse_event( pub fn report_editor_event(
self: &Arc<Self>,
telemetry_settings: TelemetrySettings,
file_extension: Option<String>,
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<Self>,
telemetry_settings: TelemetrySettings,
suggestion_id: Option<String>,
suggestion_accepted: bool,
file_extension: Option<String>,
) {
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<Self>,
telemetry_settings: TelemetrySettings,
conversation_id: Option<String>,
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<Self>,
telemetry_settings: TelemetrySettings,
operation: &'static str,
room_id: Option<u64>,
channel_id: Option<u64>,
) {
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<Self>,
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<Self>,
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> = 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<Self>, self: &Arc<Self>,
event: ClickhouseEvent, event: ClickhouseEvent,
telemetry_settings: TelemetrySettings, telemetry_settings: TelemetrySettings,
@ -276,6 +399,7 @@ impl Telemetry {
fn flush_clickhouse_events(self: &Arc<Self>) { fn flush_clickhouse_events(self: &Arc<Self>) {
let mut state = self.state.lock(); let mut state = self.state.lock();
state.first_event_datetime = None;
let mut events = mem::take(&mut state.clickhouse_events_queue); let mut events = mem::take(&mut state.clickhouse_events_queue);
state.flush_clickhouse_events_task.take(); state.flush_clickhouse_events_task.take();
drop(state); drop(state);

View file

@ -160,7 +160,7 @@ use std::sync::Arc;
use db::kvp::KEY_VALUE_STORE; use db::kvp::KEY_VALUE_STORE;
use gpui::{ use gpui::{
actions, div, serde_json, AppContext, AsyncWindowContext, Div, EventEmitter, FocusHandle, 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, VisualContext, WeakView,
}; };
use project::Fs; use project::Fs;
@ -3294,7 +3294,7 @@ impl CollabPanel {
// .with_width(size.x()) // .with_width(size.x())
// } // }
impl Render for CollabPanel { impl Render<Self> for CollabPanel {
type Element = Focusable<Self, Div<Self>>; type Element = Focusable<Self, Div<Self>>;
fn render(&mut self, _cx: &mut ViewContext<Self>) -> Self::Element { fn render(&mut self, _cx: &mut ViewContext<Self>) -> Self::Element {

View file

@ -31,9 +31,9 @@ use std::sync::Arc;
use call::ActiveCall; use call::ActiveCall;
use client::{Client, UserStore}; use client::{Client, UserStore};
use gpui::{ use gpui::{
div, px, rems, AppContext, Component, Div, InteractiveComponent, Model, ParentComponent, div, px, rems, AppContext, Div, InteractiveElement, Model, ParentElement, Render, RenderOnce,
Render, Stateful, StatefulInteractiveComponent, Styled, Subscription, ViewContext, Stateful, StatefulInteractiveElement, Styled, Subscription, ViewContext, VisualContext,
VisualContext, WeakView, WindowBounds, WeakView, WindowBounds,
}; };
use project::Project; use project::Project;
use theme::ActiveTheme; use theme::ActiveTheme;
@ -81,7 +81,7 @@ pub struct CollabTitlebarItem {
_subscriptions: Vec<Subscription>, _subscriptions: Vec<Subscription>,
} }
impl Render for CollabTitlebarItem { impl Render<Self> for CollabTitlebarItem {
type Element = Stateful<Self, Div<Self>>; type Element = Stateful<Self, Div<Self>>;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element { fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {

View file

@ -1,9 +1,8 @@
use collections::{CommandPaletteFilter, HashMap}; use collections::{CommandPaletteFilter, HashMap};
use fuzzy::{StringMatch, StringMatchCandidate}; use fuzzy::{StringMatch, StringMatchCandidate};
use gpui::{ use gpui::{
actions, div, prelude::*, Action, AppContext, Component, Div, EventEmitter, FocusHandle, actions, div, prelude::*, Action, AppContext, Div, EventEmitter, FocusHandle, FocusableView,
FocusableView, Keystroke, Manager, ParentComponent, Render, Styled, View, ViewContext, Keystroke, Manager, ParentElement, Render, Styled, View, ViewContext, VisualContext, WeakView,
VisualContext, WeakView,
}; };
use picker::{Picker, PickerDelegate}; use picker::{Picker, PickerDelegate};
use std::{ use std::{
@ -77,7 +76,7 @@ impl FocusableView for CommandPalette {
} }
} }
impl Render for CommandPalette { impl Render<Self> for CommandPalette {
type Element = Div<Self>; type Element = Div<Self>;
fn render(&mut self, _cx: &mut ViewContext<Self>) -> Self::Element { fn render(&mut self, _cx: &mut ViewContext<Self>) -> Self::Element {

View file

@ -13,9 +13,9 @@ use editor::{
}; };
use futures::future::try_join_all; use futures::future::try_join_all;
use gpui::{ use gpui::{
actions, div, AnyElement, AnyView, AppContext, Component, Context, Div, EventEmitter, actions, div, AnyElement, AnyView, AppContext, Context, Div, EventEmitter, FocusEvent,
FocusEvent, FocusHandle, Focusable, FocusableComponent, FocusableView, InteractiveComponent, FocusHandle, Focusable, FocusableElement, FocusableView, InteractiveElement, Model,
Model, ParentComponent, Render, SharedString, Styled, Subscription, Task, View, ViewContext, ParentElement, Render, RenderOnce, SharedString, Styled, Subscription, Task, View, ViewContext,
VisualContext, WeakView, VisualContext, WeakView,
}; };
use language::{ use language::{
@ -90,7 +90,7 @@ struct DiagnosticGroupState {
impl EventEmitter<ItemEvent> for ProjectDiagnosticsEditor {} impl EventEmitter<ItemEvent> for ProjectDiagnosticsEditor {}
impl Render for ProjectDiagnosticsEditor { impl Render<Self> for ProjectDiagnosticsEditor {
type Element = Focusable<Self, Div<Self>>; type Element = Focusable<Self, Div<Self>>;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element { fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
@ -792,13 +792,15 @@ fn diagnostic_header_renderer(diagnostic: Diagnostic) -> RenderBlock {
.when_some(diagnostic.code.as_ref(), |stack, code| { .when_some(diagnostic.code.as_ref(), |stack, code| {
stack.child(Label::new(code.clone())) stack.child(Label::new(code.clone()))
}) })
.render() .render_into_any()
}) })
} }
pub(crate) fn render_summary<T: 'static>(summary: &DiagnosticSummary) -> AnyElement<T> { pub(crate) fn render_summary<T: 'static>(summary: &DiagnosticSummary) -> AnyElement<T> {
if summary.error_count == 0 && summary.warning_count == 0 { 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 { } else {
h_stack() h_stack()
.bg(gpui::red()) .bg(gpui::red())
@ -806,7 +808,7 @@ pub(crate) fn render_summary<T: 'static>(summary: &DiagnosticSummary) -> AnyElem
.child(Label::new(summary.error_count.to_string())) .child(Label::new(summary.error_count.to_string()))
.child(IconElement::new(Icon::ExclamationTriangle)) .child(IconElement::new(Icon::ExclamationTriangle))
.child(Label::new(summary.warning_count.to_string())) .child(Label::new(summary.warning_count.to_string()))
.render() .render_into_any()
} }
} }

View file

@ -1,8 +1,8 @@
use collections::HashSet; use collections::HashSet;
use editor::{Editor, GoToDiagnostic}; use editor::{Editor, GoToDiagnostic};
use gpui::{ use gpui::{
rems, Div, EventEmitter, InteractiveComponent, ParentComponent, Render, Stateful, rems, Div, EventEmitter, InteractiveElement, ParentElement, Render, Stateful,
StatefulInteractiveComponent, Styled, Subscription, View, ViewContext, WeakView, StatefulInteractiveElement, Styled, Subscription, View, ViewContext, WeakView,
}; };
use language::Diagnostic; use language::Diagnostic;
use lsp::LanguageServerId; use lsp::LanguageServerId;
@ -21,7 +21,7 @@ pub struct DiagnosticIndicator {
_observe_active_editor: Option<Subscription>, _observe_active_editor: Option<Subscription>,
} }
impl Render for DiagnosticIndicator { impl Render<Self> for DiagnosticIndicator {
type Element = Stateful<Self, Div<Self>>; type Element = Stateful<Self, Div<Self>>;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element { fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {

View file

@ -1,5 +1,5 @@
use crate::ProjectDiagnosticsEditor; 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 ui::{Icon, IconButton, Tooltip};
use workspace::{item::ItemHandle, ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView}; use workspace::{item::ItemHandle, ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView};
@ -7,7 +7,7 @@ pub struct ToolbarControls {
editor: Option<WeakView<ProjectDiagnosticsEditor>>, editor: Option<WeakView<ProjectDiagnosticsEditor>>,
} }
impl Render for ToolbarControls { impl Render<Self> for ToolbarControls {
type Element = Div<Self>; type Element = Div<Self>;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element { fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {

View file

@ -24,7 +24,7 @@ use ::git::diff::DiffHunk;
use aho_corasick::AhoCorasick; use aho_corasick::AhoCorasick;
use anyhow::{anyhow, Context, Result}; use anyhow::{anyhow, Context, Result};
use blink_manager::BlinkManager; use blink_manager::BlinkManager;
use client::{ClickhouseEvent, Client, Collaborator, ParticipantIndex, TelemetrySettings}; use client::{Client, Collaborator, ParticipantIndex, TelemetrySettings};
use clock::{Global, ReplicaId}; use clock::{Global, ReplicaId};
use collections::{BTreeMap, Bound, HashMap, HashSet, VecDeque}; use collections::{BTreeMap, Bound, HashMap, HashSet, VecDeque};
use convert_case::{Case, Casing}; use convert_case::{Case, Casing};
@ -8946,12 +8946,12 @@ impl Editor {
let telemetry = project.read(cx).client().telemetry().clone(); let telemetry = project.read(cx).client().telemetry().clone();
let telemetry_settings = *settings::get::<TelemetrySettings>(cx); let telemetry_settings = *settings::get::<TelemetrySettings>(cx);
let event = ClickhouseEvent::Copilot { telemetry.report_copilot_event(
telemetry_settings,
suggestion_id, suggestion_id,
suggestion_accepted, suggestion_accepted,
file_extension, file_extension,
}; )
telemetry.report_clickhouse_event(event, telemetry_settings);
} }
#[cfg(any(test, feature = "test-support"))] #[cfg(any(test, feature = "test-support"))]
@ -8998,14 +8998,14 @@ impl Editor {
.show_copilot_suggestions; .show_copilot_suggestions;
let telemetry = project.read(cx).client().telemetry().clone(); let telemetry = project.read(cx).client().telemetry().clone();
let event = ClickhouseEvent::Editor { telemetry.report_editor_event(
telemetry_settings,
file_extension, file_extension,
vim_mode, vim_mode,
operation, operation,
copilot_enabled, copilot_enabled,
copilot_enabled_for_language, 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, /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,

View file

@ -24,7 +24,7 @@ use ::git::diff::DiffHunk;
use aho_corasick::AhoCorasick; use aho_corasick::AhoCorasick;
use anyhow::{anyhow, Context as _, Result}; use anyhow::{anyhow, Context as _, Result};
use blink_manager::BlinkManager; use blink_manager::BlinkManager;
use client::{ClickhouseEvent, Client, Collaborator, ParticipantIndex, TelemetrySettings}; use client::{Client, Collaborator, ParticipantIndex, TelemetrySettings};
use clock::ReplicaId; use clock::ReplicaId;
use collections::{BTreeMap, Bound, HashMap, HashSet, VecDeque}; use collections::{BTreeMap, Bound, HashMap, HashSet, VecDeque};
use convert_case::{Case, Casing}; use convert_case::{Case, Casing};
@ -42,9 +42,9 @@ use gpui::{
actions, div, point, prelude::*, px, relative, rems, size, uniform_list, Action, AnyElement, actions, div, point, prelude::*, px, relative, rems, size, uniform_list, Action, AnyElement,
AppContext, AsyncWindowContext, BackgroundExecutor, Bounds, ClipboardItem, Component, Context, AppContext, AsyncWindowContext, BackgroundExecutor, Bounds, ClipboardItem, Component, Context,
EventEmitter, FocusHandle, FocusableView, FontFeatures, FontStyle, FontWeight, HighlightStyle, EventEmitter, FocusHandle, FocusableView, FontFeatures, FontStyle, FontWeight, HighlightStyle,
Hsla, InputHandler, KeyContext, Model, MouseButton, ParentComponent, Pixels, Render, Styled, Hsla, InputHandler, KeyContext, Model, MouseButton, ParentElement, Pixels, Render,
Subscription, Task, TextStyle, UniformListScrollHandle, View, ViewContext, VisualContext, SharedString, Styled, Subscription, Task, TextStyle, UniformListScrollHandle, View,
WeakView, WindowContext, ViewContext, VisualContext, WeakView, WindowContext,
}; };
use highlight_matching_bracket::refresh_matching_bracket_highlights; use highlight_matching_bracket::refresh_matching_bracket_highlights;
use hover_popover::{hide_hover, HoverState}; use hover_popover::{hide_hover, HoverState};
@ -1580,7 +1580,8 @@ impl CodeActionsMenu {
) )
.map(|task| task.detach_and_log_err(cx)); .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() .collect()
}, },
@ -1595,7 +1596,7 @@ impl CodeActionsMenu {
.max_by_key(|(_, action)| action.lsp_action.title.chars().count()) .max_by_key(|(_, action)| action.lsp_action.title.chars().count())
.map(|(ix, _)| ix), .map(|(ix, _)| ix),
) )
.render(); .render_into_any();
if self.deployed_from_indicator { if self.deployed_from_indicator {
*cursor_position.column_mut() = 0; *cursor_position.column_mut() = 0;
@ -4353,19 +4354,19 @@ impl Editor {
style: &EditorStyle, style: &EditorStyle,
is_active: bool, is_active: bool,
cx: &mut ViewContext<Self>, cx: &mut ViewContext<Self>,
) -> Option<AnyElement<Self>> { ) -> Option<IconButton<Self>> {
if self.available_code_actions.is_some() { if self.available_code_actions.is_some() {
Some( Some(
IconButton::new("code_actions_indicator", ui::Icon::Bolt) IconButton::new("code_actions_indicator", ui::Icon::Bolt).on_click(
.on_click(|editor: &mut Editor, cx| { |editor: &mut Editor, cx| {
editor.toggle_code_actions( editor.toggle_code_actions(
&ToggleCodeActions { &ToggleCodeActions {
deployed_from_indicator: true, deployed_from_indicator: true,
}, },
cx, cx,
); );
}) },
.render(), ),
) )
} else { } else {
None None
@ -4380,7 +4381,7 @@ impl Editor {
line_height: Pixels, line_height: Pixels,
gutter_margin: Pixels, gutter_margin: Pixels,
cx: &mut ViewContext<Self>, cx: &mut ViewContext<Self>,
) -> Vec<Option<AnyElement<Self>>> { ) -> Vec<Option<IconButton<Self>>> {
fold_data fold_data
.iter() .iter()
.enumerate() .enumerate()
@ -4402,7 +4403,6 @@ impl Editor {
} }
}) })
.color(ui::TextColor::Muted) .color(ui::TextColor::Muted)
.render()
}) })
}) })
.flatten() .flatten()
@ -7793,7 +7793,7 @@ impl Editor {
cx.editor_style.diagnostic_style.clone(), cx.editor_style.diagnostic_style.clone(),
}, },
))) )))
.render() .render_into_any()
} }
}), }),
disposition: BlockDisposition::Below, disposition: BlockDisposition::Below,
@ -8968,12 +8968,12 @@ impl Editor {
let telemetry = project.read(cx).client().telemetry().clone(); let telemetry = project.read(cx).client().telemetry().clone();
let telemetry_settings = *TelemetrySettings::get_global(cx); let telemetry_settings = *TelemetrySettings::get_global(cx);
let event = ClickhouseEvent::Copilot { telemetry.report_copilot_event(
telemetry_settings,
suggestion_id, suggestion_id,
suggestion_accepted, suggestion_accepted,
file_extension, file_extension,
}; )
telemetry.report_clickhouse_event(event, telemetry_settings);
} }
#[cfg(any(test, feature = "test-support"))] #[cfg(any(test, feature = "test-support"))]
@ -9020,14 +9020,14 @@ impl Editor {
.show_copilot_suggestions; .show_copilot_suggestions;
let telemetry = project.read(cx).client().telemetry().clone(); let telemetry = project.read(cx).client().telemetry().clone();
let event = ClickhouseEvent::Editor { telemetry.report_editor_event(
telemetry_settings,
file_extension, file_extension,
vim_mode, vim_mode,
operation, operation,
copilot_enabled, copilot_enabled,
copilot_enabled_for_language, 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, /// 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<Self> for Editor {
type Element = EditorElement; type Element = EditorElement;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element { fn render(&mut self, cx: &mut ViewContext<Self>) -> 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())); cx.write_to_clipboard(ClipboardItem::new(message.clone()));
}) })
.tooltip(|_, cx| Tooltip::text("Copy diagnostic message", cx)) .tooltip(|_, cx| Tooltip::text("Copy diagnostic message", cx))
.render() .render_into_any()
}) })
} }

View file

@ -3048,7 +3048,7 @@ fn test_move_line_up_down_with_blocks(cx: &mut TestAppContext) {
position: snapshot.anchor_after(Point::new(2, 0)), position: snapshot.anchor_after(Point::new(2, 0)),
disposition: BlockDisposition::Below, disposition: BlockDisposition::Below,
height: 1, height: 1,
render: Arc::new(|_| div().render()), render: Arc::new(|_| div().into_any()),
}], }],
Some(Autoscroll::fit()), Some(Autoscroll::fit()),
cx, cx,

View file

@ -20,9 +20,9 @@ use collections::{BTreeMap, HashMap};
use gpui::{ use gpui::{
div, point, px, relative, size, transparent_black, Action, AnyElement, AvailableSpace, div, point, px, relative, size, transparent_black, Action, AnyElement, AvailableSpace,
BorrowWindow, Bounds, Component, ContentMask, Corners, DispatchPhase, Edges, Element, BorrowWindow, Bounds, Component, ContentMask, Corners, DispatchPhase, Edges, Element,
ElementId, ElementInputHandler, Entity, EntityId, Hsla, InteractiveComponent, LineLayout, ElementId, ElementInputHandler, Entity, EntityId, Hsla, InteractiveElement, LineLayout,
MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, ParentComponent, Pixels, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, ParentElement, Pixels, RenderOnce,
ScrollWheelEvent, ShapedLine, SharedString, Size, StatefulInteractiveComponent, Style, Styled, ScrollWheelEvent, ShapedLine, SharedString, Size, StatefulInteractiveElement, Style, Styled,
TextRun, TextStyle, View, ViewContext, WindowContext, WrappedLine, TextRun, TextStyle, View, ViewContext, WindowContext, WrappedLine,
}; };
use itertools::Itertools; use itertools::Itertools;
@ -488,8 +488,9 @@ impl EditorElement {
} }
} }
for (ix, fold_indicator) in layout.fold_indicators.iter_mut().enumerate() { for (ix, fold_indicator) in layout.fold_indicators.drain(..).enumerate() {
if let Some(fold_indicator) = fold_indicator.as_mut() { if let Some(mut fold_indicator) = fold_indicator {
let mut fold_indicator = fold_indicator.render_into_any();
let available_space = size( let available_space = size(
AvailableSpace::MinContent, AvailableSpace::MinContent,
AvailableSpace::Definite(line_height * 0.55), 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( let available_space = size(
AvailableSpace::MinContent, AvailableSpace::MinContent,
AvailableSpace::Definite(line_height), 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 x = Pixels::ZERO;
let mut y = indicator.row as f32 * line_height - scroll_top; let mut y = indicator.row as f32 * line_height - scroll_top;
// Center indicator. // Center indicator.
x += ((layout.gutter_padding + layout.gutter_margin) - indicator_size.width) / 2.; x += ((layout.gutter_padding + layout.gutter_margin) - indicator_size.width) / 2.;
y += (line_height - indicator_size.height) / 2.; y += (line_height - indicator_size.height) / 2.;
indicator
.element button.draw(bounds.origin + point(x, y), available_space, editor, cx);
.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| { cx.with_z_index(1, |cx| {
let line_height = self.style.text.line_height_in_pixels(cx.rem_size()); let line_height = self.style.text.line_height_in_pixels(cx.rem_size());
let available_space = size( let available_space = size(
@ -1224,7 +1226,7 @@ impl EditorElement {
let scroll_left = scroll_position.x * layout.position_map.em_width; let scroll_left = scroll_position.x * layout.position_map.em_width;
let scroll_top = scroll_position.y * layout.position_map.line_height; 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 let mut origin = bounds.origin
+ point( + point(
Pixels::ZERO, Pixels::ZERO,
@ -1810,7 +1812,7 @@ impl EditorElement {
.render_code_actions_indicator(&style, active, cx) .render_code_actions_indicator(&style, active, cx)
.map(|element| CodeActionsIndicator { .map(|element| CodeActionsIndicator {
row: newest_selection_head.row(), 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 :( // Can't use .and_then() because `.file_name()` and `.parent()` return references :(
if let Some(path) = path { if let Some(path) = path {
filename = path.file_name().map(|f| f.to_string_lossy().to_string()); filename = path.file_name().map(|f| f.to_string_lossy().to_string());
parent_path = parent_path = path
path.parent().map(|p| p.to_string_lossy().to_string() + "/"); .parent()
.map(|p| SharedString::from(p.to_string_lossy().to_string() + "/"));
} }
h_stack() h_stack()
.id("path header block") .id("path header block")
.size_full() .size_full()
.bg(gpui::red()) .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(parent_path)
.children(jump_icon) // .p_x(gutter_padding) .children(jump_icon) // .p_x(gutter_padding)
} else { } else {
@ -2063,7 +2070,7 @@ impl EditorElement {
.child("") .child("")
.children(jump_icon) // .p_x(gutter_padding) .children(jump_icon) // .p_x(gutter_padding)
}; };
element.render() element.into_any()
} }
}; };
@ -2393,18 +2400,14 @@ enum Invisible {
} }
impl Element<Editor> for EditorElement { impl Element<Editor> for EditorElement {
type ElementState = (); type State = ();
fn element_id(&self) -> Option<gpui::ElementId> {
Some(self.editor_id.into())
}
fn layout( fn layout(
&mut self, &mut self,
editor: &mut Editor, editor: &mut Editor,
element_state: Option<Self::ElementState>, element_state: Option<Self::State>,
cx: &mut gpui::ViewContext<Editor>, cx: &mut gpui::ViewContext<Editor>,
) -> (gpui::LayoutId, Self::ElementState) { ) -> (gpui::LayoutId, Self::State) {
editor.style = Some(self.style.clone()); // Long-term, we'd like to eliminate this. editor.style = Some(self.style.clone()); // Long-term, we'd like to eliminate this.
let rem_size = cx.rem_size(); let rem_size = cx.rem_size();
@ -2420,10 +2423,10 @@ impl Element<Editor> for EditorElement {
} }
fn paint( fn paint(
&mut self, mut self,
bounds: Bounds<gpui::Pixels>, bounds: Bounds<gpui::Pixels>,
editor: &mut Editor, editor: &mut Editor,
element_state: &mut Self::ElementState, element_state: &mut Self::State,
cx: &mut gpui::ViewContext<Editor>, cx: &mut gpui::ViewContext<Editor>,
) { ) {
let mut layout = self.compute_layout(editor, cx, bounds); let mut layout = self.compute_layout(editor, cx, bounds);
@ -2470,9 +2473,15 @@ impl Element<Editor> for EditorElement {
} }
} }
impl Component<Editor> for EditorElement { impl RenderOnce<Editor> for EditorElement {
fn render(self) -> AnyElement<Editor> { type Element = Self;
AnyElement::new(self)
fn element_id(&self) -> Option<gpui::ElementId> {
Some(self.editor_id.into())
}
fn render_once(self) -> Self::Element {
self
} }
} }
@ -3100,14 +3109,14 @@ pub struct LayoutState {
context_menu: Option<(DisplayPoint, AnyElement<Editor>)>, context_menu: Option<(DisplayPoint, AnyElement<Editor>)>,
code_actions_indicator: Option<CodeActionsIndicator>, code_actions_indicator: Option<CodeActionsIndicator>,
// hover_popovers: Option<(DisplayPoint, Vec<AnyElement<Editor>>)>, // hover_popovers: Option<(DisplayPoint, Vec<AnyElement<Editor>>)>,
fold_indicators: Vec<Option<AnyElement<Editor>>>, fold_indicators: Vec<Option<IconButton<Editor>>>,
tab_invisible: ShapedLine, tab_invisible: ShapedLine,
space_invisible: ShapedLine, space_invisible: ShapedLine,
} }
struct CodeActionsIndicator { struct CodeActionsIndicator {
row: u32, row: u32,
element: AnyElement<Editor>, button: IconButton<Editor>,
} }
struct PositionMap { struct PositionMap {

View file

@ -9,7 +9,7 @@ use collections::HashSet;
use futures::future::try_join_all; use futures::future::try_join_all;
use gpui::{ use gpui::{
div, point, AnyElement, AppContext, AsyncAppContext, Entity, EntityId, EventEmitter, 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, ViewContext, VisualContext, WeakView,
}; };
use language::{ use language::{

View file

@ -2,8 +2,8 @@ use collections::HashMap;
use editor::{scroll::autoscroll::Autoscroll, Bias, Editor}; use editor::{scroll::autoscroll::Autoscroll, Bias, Editor};
use fuzzy::{CharBag, PathMatch, PathMatchCandidate}; use fuzzy::{CharBag, PathMatch, PathMatchCandidate};
use gpui::{ use gpui::{
actions, div, AppContext, Component, Div, EventEmitter, FocusHandle, FocusableView, actions, div, AppContext, Div, EventEmitter, FocusHandle, FocusableView, InteractiveElement,
InteractiveComponent, Manager, Model, ParentComponent, Render, Styled, Task, View, ViewContext, Manager, Model, ParentElement, Render, RenderOnce, Styled, Task, View, ViewContext,
VisualContext, WeakView, VisualContext, WeakView,
}; };
use picker::{Picker, PickerDelegate}; use picker::{Picker, PickerDelegate};
@ -117,7 +117,7 @@ impl FocusableView for FileFinder {
self.picker.focus_handle(cx) self.picker.focus_handle(cx)
} }
} }
impl Render for FileFinder { impl Render<Self> for FileFinder {
type Element = Div<Self>; type Element = Div<Self>;
fn render(&mut self, _cx: &mut ViewContext<Self>) -> Self::Element { fn render(&mut self, _cx: &mut ViewContext<Self>) -> Self::Element {

View file

@ -1,8 +1,7 @@
use editor::{display_map::ToDisplayPoint, scroll::autoscroll::Autoscroll, Editor}; use editor::{display_map::ToDisplayPoint, scroll::autoscroll::Autoscroll, Editor};
use gpui::{ use gpui::{
actions, div, prelude::*, AppContext, Div, EventEmitter, FocusHandle, FocusableView, Manager, actions, div, prelude::*, AppContext, Div, EventEmitter, FocusHandle, FocusableView, Manager,
ParentComponent, Render, SharedString, Styled, Subscription, View, ViewContext, VisualContext, Render, SharedString, Styled, Subscription, View, ViewContext, VisualContext, WindowContext,
WindowContext,
}; };
use text::{Bias, Point}; use text::{Bias, Point};
use theme::ActiveTheme; use theme::ActiveTheme;
@ -145,7 +144,7 @@ impl GoToLine {
} }
} }
impl Render for GoToLine { impl Render<Self> for GoToLine {
type Element = Div<Self>; type Element = Div<Self>;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element { fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {

View file

@ -14,7 +14,7 @@ use smallvec::SmallVec;
pub use test_context::*; pub use test_context::*;
use crate::{ 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, AnyWindowHandle, AppMetadata, AssetSource, BackgroundExecutor, ClipboardItem, Context,
DispatchPhase, DisplayId, Entity, EventEmitter, FocusEvent, FocusHandle, FocusId, DispatchPhase, DisplayId, Entity, EventEmitter, FocusEvent, FocusHandle, FocusId,
ForegroundExecutor, KeyBinding, Keymap, LayoutId, PathPromptOptions, Pixels, Platform, ForegroundExecutor, KeyBinding, Keymap, LayoutId, PathPromptOptions, Pixels, Platform,
@ -28,7 +28,7 @@ use futures::{channel::oneshot, future::LocalBoxFuture, Future};
use parking_lot::Mutex; use parking_lot::Mutex;
use slotmap::SlotMap; use slotmap::SlotMap;
use std::{ use std::{
any::{type_name, Any, TypeId}, any::{type_name, TypeId},
cell::{Ref, RefCell, RefMut}, cell::{Ref, RefCell, RefMut},
marker::PhantomData, marker::PhantomData,
mem, mem,
@ -194,7 +194,7 @@ pub struct AppContext {
asset_source: Arc<dyn AssetSource>, asset_source: Arc<dyn AssetSource>,
pub(crate) image_cache: ImageCache, pub(crate) image_cache: ImageCache,
pub(crate) text_style_stack: Vec<TextStyleRefinement>, pub(crate) text_style_stack: Vec<TextStyleRefinement>,
pub(crate) globals_by_type: HashMap<TypeId, AnyBox>, pub(crate) globals_by_type: HashMap<TypeId, Box<dyn Any>>,
pub(crate) entities: EntityMap, pub(crate) entities: EntityMap,
pub(crate) new_view_observers: SubscriberSet<TypeId, NewViewListener>, pub(crate) new_view_observers: SubscriberSet<TypeId, NewViewListener>,
pub(crate) windows: SlotMap<WindowId, Option<Window>>, pub(crate) windows: SlotMap<WindowId, Option<Window>>,
@ -424,7 +424,7 @@ impl AppContext {
/// Opens a new window with the given option and the root view returned by the given function. /// 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 /// The function is invoked with a `WindowContext`, which can be used to interact with window-specific
/// functionality. /// functionality.
pub fn open_window<V: Render>( pub fn open_window<V: 'static + Render>(
&mut self, &mut self,
options: crate::WindowOptions, options: crate::WindowOptions,
build_root_view: impl FnOnce(&mut WindowContext) -> View<V>, build_root_view: impl FnOnce(&mut WindowContext) -> View<V>,
@ -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. /// Wraps a global variable value during `update_global` while the value has been moved to the stack.
pub(crate) struct GlobalLease<G: 'static> { pub(crate) struct GlobalLease<G: 'static> {
global: AnyBox, global: Box<dyn Any>,
global_type: PhantomData<G>, global_type: PhantomData<G>,
} }
impl<G: 'static> GlobalLease<G> { impl<G: 'static> GlobalLease<G> {
fn new(global: AnyBox) -> Self { fn new(global: Box<dyn Any>) -> Self {
GlobalLease { GlobalLease {
global, global,
global_type: PhantomData, global_type: PhantomData,

View file

@ -115,7 +115,7 @@ impl AsyncAppContext {
build_root_view: impl FnOnce(&mut WindowContext) -> View<V>, build_root_view: impl FnOnce(&mut WindowContext) -> View<V>,
) -> Result<WindowHandle<V>> ) -> Result<WindowHandle<V>>
where where
V: Render, V: 'static + Render,
{ {
let app = self let app = self
.app .app
@ -306,7 +306,7 @@ impl VisualContext for AsyncWindowContext {
build_view: impl FnOnce(&mut ViewContext<'_, V>) -> V, build_view: impl FnOnce(&mut ViewContext<'_, V>) -> V,
) -> Self::Result<View<V>> ) -> Self::Result<View<V>>
where where
V: Render, V: 'static + Render,
{ {
self.window self.window
.update(self, |_, cx| cx.replace_root_view(build_view)) .update(self, |_, cx| cx.replace_root_view(build_view))

View file

@ -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 anyhow::{anyhow, Result};
use derive_more::{Deref, DerefMut}; use derive_more::{Deref, DerefMut};
use parking_lot::{RwLock, RwLockUpgradableReadGuard}; use parking_lot::{RwLock, RwLockUpgradableReadGuard};
use slotmap::{SecondaryMap, SlotMap}; use slotmap::{SecondaryMap, SlotMap};
use std::{ use std::{
any::{type_name, TypeId}, any::{type_name, Any, TypeId},
fmt::{self, Display}, fmt::{self, Display},
hash::{Hash, Hasher}, hash::{Hash, Hasher},
marker::PhantomData, marker::PhantomData,
@ -31,7 +31,7 @@ impl Display for EntityId {
} }
pub(crate) struct EntityMap { pub(crate) struct EntityMap {
entities: SecondaryMap<EntityId, AnyBox>, entities: SecondaryMap<EntityId, Box<dyn Any>>,
ref_counts: Arc<RwLock<EntityRefCounts>>, ref_counts: Arc<RwLock<EntityRefCounts>>,
} }
@ -102,7 +102,7 @@ impl EntityMap {
); );
} }
pub fn take_dropped(&mut self) -> Vec<(EntityId, AnyBox)> { pub fn take_dropped(&mut self) -> Vec<(EntityId, Box<dyn Any>)> {
let mut ref_counts = self.ref_counts.write(); let mut ref_counts = self.ref_counts.write();
let dropped_entity_ids = mem::take(&mut ref_counts.dropped_entity_ids); let dropped_entity_ids = mem::take(&mut ref_counts.dropped_entity_ids);
@ -122,7 +122,7 @@ impl EntityMap {
} }
pub struct Lease<'a, T> { pub struct Lease<'a, T> {
entity: Option<AnyBox>, entity: Option<Box<dyn Any>>,
pub model: &'a Model<T>, pub model: &'a Model<T>,
entity_type: PhantomData<T>, entity_type: PhantomData<T>,
} }

View file

@ -126,7 +126,7 @@ impl TestAppContext {
pub fn add_window<F, V>(&mut self, build_window: F) -> WindowHandle<V> pub fn add_window<F, V>(&mut self, build_window: F) -> WindowHandle<V>
where where
F: FnOnce(&mut ViewContext<V>) -> V, F: FnOnce(&mut ViewContext<V>) -> V,
V: Render, V: 'static + Render,
{ {
let mut cx = self.app.borrow_mut(); let mut cx = self.app.borrow_mut();
cx.open_window(WindowOptions::default(), |cx| cx.build_view(build_window)) cx.open_window(WindowOptions::default(), |cx| cx.build_view(build_window))
@ -143,7 +143,7 @@ impl TestAppContext {
pub fn add_window_view<F, V>(&mut self, build_window: F) -> (View<V>, &mut VisualTestContext) pub fn add_window_view<F, V>(&mut self, build_window: F) -> (View<V>, &mut VisualTestContext)
where where
F: FnOnce(&mut ViewContext<V>) -> V, F: FnOnce(&mut ViewContext<V>) -> V,
V: Render, V: 'static + Render,
{ {
let mut cx = self.app.borrow_mut(); let mut cx = self.app.borrow_mut();
let window = cx.open_window(WindowOptions::default(), |cx| cx.build_view(build_window)); 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, build_view: impl FnOnce(&mut ViewContext<'_, V>) -> V,
) -> Self::Result<View<V>> ) -> Self::Result<View<V>>
where where
V: Render, V: 'static + Render,
{ {
self.window self.window
.update(self.cx, |_, cx| cx.replace_root_view(build_view)) .update(self.cx, |_, cx| cx.replace_root_view(build_view))

View file

@ -1,306 +1,63 @@
use crate::{ 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}; use derive_more::{Deref, DerefMut};
pub(crate) use smallvec::SmallVec; pub(crate) use smallvec::SmallVec;
use std::{any::Any, fmt::Debug, mem}; use std::{any::Any, fmt::Debug};
pub trait Element { pub trait Render: 'static + Sized {
type ElementState: 'static; type Element: Element + 'static;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element;
}
pub trait RenderOnce: Sized {
type Element: Element + 'static;
fn element_id(&self) -> Option<ElementId>; fn element_id(&self) -> Option<ElementId>;
fn layout( fn render_once(self) -> Self::Element;
&mut self,
element_state: Option<Self::ElementState>,
cx: &mut WindowContext,
) -> (LayoutId, Self::ElementState);
fn paint( fn render_into_any(self) -> AnyElement {
&mut self, self.render_once().into_any()
bounds: Bounds<Pixels>, }
element_state: &mut Self::ElementState,
cx: &mut WindowContext,
);
fn draw<T, R>( fn draw<T, R>(
self, self,
origin: Point<Pixels>, origin: Point<Pixels>,
available_space: Size<T>, available_space: Size<T>,
cx: &mut WindowContext, cx: &mut WindowContext,
f: impl FnOnce(&Self::ElementState, &mut WindowContext) -> R, f: impl FnOnce(&mut <Self::Element as Element>::State, &mut WindowContext) -> R,
) -> R ) -> R
where where
Self: Sized,
T: Clone + Default + Debug + Into<AvailableSpace>, T: Clone + Default + Debug + Into<AvailableSpace>,
{ {
let mut element = RenderedElement { let element = self.render_once();
element: self, let element_id = element.element_id();
phase: ElementRenderPhase::Start, 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 { let frame_state =
if let Some(frame_state) = frame_state.as_ref() { DrawableElement::draw(element, origin, available_space.map(Into::into), cx);
f(&frame_state, cx)
} else { if let Some(mut frame_state) = frame_state {
let element_id = element f(&mut frame_state, cx)
.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)
})
}
} else { } 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<Item = impl Component>) -> Self
where
Self: Sized,
{
self.children_mut()
.extend(iter.into_iter().map(|item| item.render()));
self
}
}
trait ElementObject {
fn element_id(&self) -> Option<ElementId>;
fn layout(&mut self, cx: &mut WindowContext) -> LayoutId;
fn paint(&mut self, cx: &mut WindowContext);
fn measure(
&mut self,
available_space: Size<AvailableSpace>,
cx: &mut WindowContext,
) -> Size<Pixels>;
fn draw(
&mut self,
origin: Point<Pixels>,
available_space: Size<AvailableSpace>,
cx: &mut WindowContext,
);
}
struct RenderedElement<E: Element> {
element: E,
phase: ElementRenderPhase<E::ElementState>,
}
#[derive(Default)]
enum ElementRenderPhase<V> {
#[default]
Start,
LayoutRequested {
layout_id: LayoutId,
frame_state: Option<V>,
},
LayoutComputed {
layout_id: LayoutId,
available_space: Size<AvailableSpace>,
frame_state: Option<V>,
},
Painted {
frame_state: Option<V>,
},
}
/// 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<E::State> for
/// improved usability.
impl<E: Element> RenderedElement<E> {
fn new(element: E) -> Self {
RenderedElement {
element,
phase: ElementRenderPhase::Start,
}
}
}
impl<E> ElementObject for RenderedElement<E>
where
E: Element,
E::ElementState: 'static,
{
fn element_id(&self) -> Option<ElementId> {
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<AvailableSpace>,
cx: &mut WindowContext,
) -> Size<Pixels> {
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<Pixels>,
available_space: Size<AvailableSpace>,
cx: &mut WindowContext,
) {
self.measure(available_space, cx);
cx.with_absolute_element_offset(origin, |cx| self.paint(cx))
}
}
pub struct AnyElement(Box<dyn ElementObject>);
impl AnyElement {
pub fn new<E>(element: E) -> Self
where
E: 'static + Element,
E::ElementState: Any,
{
AnyElement(Box::new(RenderedElement::new(element)))
}
pub fn element_id(&self) -> Option<ElementId> {
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<AvailableSpace>,
cx: &mut WindowContext,
) -> Size<Pixels> {
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<Pixels>,
available_space: Size<AvailableSpace>,
cx: &mut WindowContext,
) {
self.0.draw(origin, available_space, cx)
}
}
pub trait Component {
fn render(self) -> AnyElement;
fn map<U>(self, f: impl FnOnce(Self) -> U) -> U fn map<U>(self, f: impl FnOnce(Self) -> U) -> U
where where
Self: Sized, Self: Sized,
U: Component, U: RenderOnce,
{ {
f(self) f(self)
} }
@ -326,60 +83,463 @@ pub trait Component {
} }
} }
impl Component for AnyElement { pub trait Element: 'static + RenderOnce {
fn render(self) -> AnyElement { type State: 'static;
self
fn layout(
&mut self,
state: Option<Self::State>,
cx: &mut WindowContext,
) -> (LayoutId, Self::State);
fn paint(self, bounds: Bounds<Pixels>, state: &mut Self::State, cx: &mut WindowContext);
fn into_any(self) -> AnyElement {
AnyElement::new(self)
} }
} }
impl<E, F> Element for Option<F> pub trait Component: 'static {
where type Rendered: RenderOnce;
E: 'static + Component,
F: FnOnce(&mut WindowContext) -> E + 'static, fn render(self, cx: &mut WindowContext) -> Self::Rendered;
{ }
type ElementState = AnyElement;
pub struct CompositeElement<C> {
component: Option<C>,
}
pub struct CompositeElementState<C: Component> {
rendered_element: Option<<C::Rendered as RenderOnce>::Element>,
rendered_element_state: <<C::Rendered as RenderOnce>::Element as Element>::State,
}
impl<C> CompositeElement<C> {
pub fn new(component: C) -> Self {
CompositeElement {
component: Some(component),
}
}
}
impl<C: Component> Element for CompositeElement<C> {
type State = CompositeElementState<C>;
fn layout(
&mut self,
state: Option<Self::State>,
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<Pixels>, state: &mut Self::State, cx: &mut WindowContext) {
state
.rendered_element
.take()
.unwrap()
.paint(bounds, &mut state.rendered_element_state, cx);
}
}
impl<C: Component> RenderOnce for CompositeElement<C> {
type Element = Self;
fn element_id(&self) -> Option<ElementId> { fn element_id(&self) -> Option<ElementId> {
None None
} }
fn layout( fn render_once(self) -> Self::Element {
&mut self, self
_: Option<Self::ElementState>,
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<Pixels>,
rendered_element: &mut Self::ElementState,
cx: &mut WindowContext,
) {
rendered_element.paint(cx)
} }
} }
impl<E, F> Component for Option<F> #[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<Item = impl RenderOnce>) -> 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<ElementId>;
fn layout(&mut self, cx: &mut WindowContext) -> LayoutId;
fn paint(&mut self, cx: &mut WindowContext);
fn measure(
&mut self,
available_space: Size<AvailableSpace>,
cx: &mut WindowContext,
) -> Size<Pixels>;
fn draw(
&mut self,
origin: Point<Pixels>,
available_space: Size<AvailableSpace>,
cx: &mut WindowContext,
);
}
pub struct DrawableElement<E: Element> {
element: Option<E>,
phase: ElementDrawPhase<E::State>,
}
#[derive(Default)]
enum ElementDrawPhase<S> {
#[default]
Start,
LayoutRequested {
layout_id: LayoutId,
frame_state: Option<S>,
},
LayoutComputed {
layout_id: LayoutId,
available_space: Size<AvailableSpace>,
frame_state: Option<S>,
},
}
/// A wrapper around an implementer of [Element] that allows it to be drawn in a window.
impl<E: Element> DrawableElement<E> {
fn new(element: E) -> Self {
DrawableElement {
element: Some(element),
phase: ElementDrawPhase::Start,
}
}
fn element_id(&self) -> Option<ElementId> {
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<E::State> {
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<AvailableSpace>,
cx: &mut WindowContext,
) -> Size<Pixels> {
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<Pixels>,
available_space: Size<AvailableSpace>,
cx: &mut WindowContext,
) -> Option<E::State> {
self.measure(available_space, cx);
cx.with_absolute_element_offset(origin, |cx| self.paint(cx))
}
}
// impl<V: 'static, E: Element> Element for DrawableElement<V, E> {
// type State = <E::Element as Element>::State;
// fn layout(
// &mut self,
// element_state: Option<Self::State>,
// cx: &mut WindowContext,
// ) -> (LayoutId, Self::State) {
// }
// fn paint(
// self,
// bounds: Bounds<Pixels>,
// element_state: &mut Self::State,
// cx: &mut WindowContext,
// ) {
// todo!()
// }
// }
// impl<V: 'static, E: 'static + Element> RenderOnce for DrawableElement<V, E> {
// type Element = Self;
// fn element_id(&self) -> Option<ElementId> {
// self.element.as_ref()?.element_id()
// }
// fn render_once(self) -> Self::Element {
// self
// }
// }
impl<E> ElementObject for Option<DrawableElement<E>>
where where
E: 'static + Component, E: Element,
F: FnOnce(&mut WindowContext) -> E + 'static, E::State: 'static,
{ {
fn render(self) -> AnyElement { fn element_id(&self) -> Option<ElementId> {
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<AvailableSpace>,
cx: &mut WindowContext,
) -> Size<Pixels> {
DrawableElement::measure(self.as_mut().unwrap(), available_space, cx)
}
fn draw(
&mut self,
origin: Point<Pixels>,
available_space: Size<AvailableSpace>,
cx: &mut WindowContext,
) {
DrawableElement::draw(self.take().unwrap(), origin, available_space, cx);
}
}
pub struct AnyElement(Box<dyn ElementObject>);
impl AnyElement {
pub fn new<E>(element: E) -> Self
where
E: 'static + Element,
E::State: Any,
{
AnyElement(Box::new(Some(DrawableElement::new(element))) as Box<dyn ElementObject>)
}
pub fn element_id(&self) -> Option<ElementId> {
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<AvailableSpace>,
cx: &mut WindowContext,
) -> Size<Pixels> {
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<Pixels>,
available_space: Size<AvailableSpace>,
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) AnyElement::new(self)
} }
} }
impl<E, F> Component for F impl Element for AnyElement {
where type State = ();
E: 'static + Component,
F: FnOnce(&mut WindowContext) -> E + 'static, fn layout(
{ &mut self,
fn render(self) -> AnyElement { _: Option<Self::State>,
AnyElement::new(Some(self)) cx: &mut WindowContext,
) -> (LayoutId, Self::State) {
let layout_id = self.layout(cx);
(layout_id, ())
}
fn paint(self, _: Bounds<Pixels>, _: &mut Self::State, cx: &mut WindowContext) {
self.paint(cx);
} }
} }
impl RenderOnce for AnyElement {
type Element = Self;
fn element_id(&self) -> Option<ElementId> {
AnyElement::element_id(self)
}
fn render_once(self) -> Self::Element {
self
}
}
// impl<V, E, F> Element for Option<F>
// where
// V: 'static,
// E: Element,
// F: FnOnce(&mut V, &mut WindowContext<'_, V>) -> E + 'static,
// {
// type State = Option<AnyElement>;
// fn element_id(&self) -> Option<ElementId> {
// None
// }
// fn layout(
// &mut self,
// _: Option<Self::State>,
// 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<Pixels>,
// rendered_element: &mut Self::State,
// cx: &mut WindowContext,
// ) {
// rendered_element.take().unwrap().paint(view_state, cx);
// }
// }
// impl<V, E, F> RenderOnce for Option<F>
// where
// V: 'static,
// E: Element,
// F: FnOnce(&mut V, &mut WindowContext) -> E + 'static,
// {
// type Element = Self;
// fn render(self) -> Self::Element {
// self
// }
// }

View file

@ -1,10 +1,10 @@
use crate::{ use crate::{
point, px, Action, AnyDrag, AnyElement, AnyTooltip, AnyView, AppContext, BorrowAppContext, point, px, Action, AnyDrag, AnyElement, AnyTooltip, AnyView, AppContext, BorrowAppContext,
BorrowWindow, Bounds, CallbackHandle, ClickEvent, Component, ConstructorHandle, DispatchPhase, BorrowWindow, Bounds, CallbackHandle, ClickEvent, ConstructorHandle, DispatchPhase, Element,
Element, ElementId, FocusEvent, FocusHandle, KeyContext, KeyDownEvent, KeyUpEvent, LayoutId, ElementId, FocusEvent, FocusHandle, KeyContext, KeyDownEvent, KeyUpEvent, LayoutId,
MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, ParentComponent, Pixels, Point, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, ParentElement, Pixels, Point,
Render, ScrollWheelEvent, SharedString, Size, Style, StyleRefinement, Styled, Task, View, Render, RenderOnce, ScrollWheelEvent, SharedString, Size, Style, StyleRefinement, Styled, Task,
Visibility, WindowContext, View, Visibility, WindowContext,
}; };
use collections::HashMap; use collections::HashMap;
use refineable::Refineable; use refineable::Refineable;
@ -28,7 +28,7 @@ pub struct GroupStyle {
pub style: StyleRefinement, pub style: StyleRefinement,
} }
pub trait InteractiveComponent: Sized + Element { pub trait InteractiveElement: Sized + Element {
fn interactivity(&mut self) -> &mut Interactivity; fn interactivity(&mut self) -> &mut Interactivity;
fn group(mut self, group: impl Into<SharedString>) -> Self { fn group(mut self, group: impl Into<SharedString>) -> Self {
@ -319,7 +319,7 @@ pub trait InteractiveComponent: Sized + Element {
} }
} }
pub trait StatefulInteractiveComponent: InteractiveComponent { pub trait StatefulInteractiveElement: InteractiveElement {
fn focusable(mut self) -> Focusable<Self> { fn focusable(mut self) -> Focusable<Self> {
self.interactivity().focusable = true; self.interactivity().focusable = true;
Focusable { element: self } 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 fn focus(mut self, f: impl FnOnce(StyleRefinement) -> StyleRefinement) -> Self
where where
Self: Sized, Self: Sized,
@ -563,30 +563,26 @@ impl Styled for Div {
} }
} }
impl InteractiveComponent for Div { impl InteractiveElement for Div {
fn interactivity(&mut self) -> &mut Interactivity { fn interactivity(&mut self) -> &mut Interactivity {
&mut self.interactivity &mut self.interactivity
} }
} }
impl ParentComponent for Div { impl ParentElement for Div {
fn children_mut(&mut self) -> &mut SmallVec<[AnyElement; 2]> { fn children_mut(&mut self) -> &mut SmallVec<[AnyElement; 2]> {
&mut self.children &mut self.children
} }
} }
impl Element for Div { impl Element for Div {
type ElementState = DivState; type State = DivState;
fn element_id(&self) -> Option<ElementId> {
self.interactivity.element_id.clone()
}
fn layout( fn layout(
&mut self, &mut self,
element_state: Option<Self::ElementState>, element_state: Option<Self::State>,
cx: &mut WindowContext, cx: &mut WindowContext,
) -> (LayoutId, Self::ElementState) { ) -> (LayoutId, Self::State) {
let mut child_layout_ids = SmallVec::new(); let mut child_layout_ids = SmallVec::new();
let mut interactivity = mem::take(&mut self.interactivity); let mut interactivity = mem::take(&mut self.interactivity);
let (layout_id, interactive_state) = interactivity.layout( let (layout_id, interactive_state) = interactivity.layout(
@ -614,9 +610,9 @@ impl Element for Div {
} }
fn paint( fn paint(
&mut self, self,
bounds: Bounds<Pixels>, bounds: Bounds<Pixels>,
element_state: &mut Self::ElementState, element_state: &mut Self::State,
cx: &mut WindowContext, cx: &mut WindowContext,
) { ) {
let mut child_min = point(Pixels::MAX, Pixels::MAX); let mut child_min = point(Pixels::MAX, Pixels::MAX);
@ -632,8 +628,7 @@ impl Element for Div {
(child_max - child_min).into() (child_max - child_min).into()
}; };
let mut interactivity = mem::take(&mut self.interactivity); self.interactivity.paint(
interactivity.paint(
bounds, bounds,
content_size, content_size,
&mut element_state.interactive_state, &mut element_state.interactive_state,
@ -653,7 +648,7 @@ impl Element for Div {
cx.with_text_style(style.text_style().cloned(), |cx| { cx.with_text_style(style.text_style().cloned(), |cx| {
cx.with_content_mask(style.overflow_mask(bounds), |cx| { cx.with_content_mask(style.overflow_mask(bounds), |cx| {
cx.with_element_offset(scroll_offset, |cx| { cx.with_element_offset(scroll_offset, |cx| {
for child in &mut self.children { for child in self.children {
child.paint(cx); child.paint(cx);
} }
}) })
@ -663,13 +658,18 @@ impl Element for Div {
}) })
}, },
); );
self.interactivity = interactivity;
} }
} }
impl Component for Div { impl RenderOnce for Div {
fn render(self) -> AnyElement { type Element = Self;
AnyElement::new(self)
fn element_id(&self) -> Option<ElementId> {
self.interactivity.element_id.clone()
}
fn render_once(self) -> Self::Element {
self
} }
} }
@ -741,7 +741,7 @@ impl Interactivity {
} }
pub fn paint( pub fn paint(
&mut self, mut self,
bounds: Bounds<Pixels>, bounds: Bounds<Pixels>,
content_size: Size<Pixels>, content_size: Size<Pixels>,
element_state: &mut InteractiveElementState, 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) cx.on_action(action_type, listener)
} }
if let Some(focus_handle) = element_state.focus_handle.as_ref() { 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(); let focus_handle = focus_handle.clone();
cx.on_focus_changed(move |event, cx| listener(&focus_handle, event, cx)); cx.on_focus_changed(move |event, cx| listener(&focus_handle, event, cx));
} }
@ -1232,18 +1232,18 @@ pub struct Focusable<E> {
element: E, element: E,
} }
impl<E: InteractiveComponent> FocusableComponent for Focusable<E> {} impl<E: InteractiveElement> FocusableElement for Focusable<E> {}
impl<E> InteractiveComponent for Focusable<E> impl<E> InteractiveElement for Focusable<E>
where where
E: InteractiveComponent, E: InteractiveElement,
{ {
fn interactivity(&mut self) -> &mut Interactivity { fn interactivity(&mut self) -> &mut Interactivity {
self.element.interactivity() self.element.interactivity()
} }
} }
impl<E: StatefulInteractiveComponent> StatefulInteractiveComponent for Focusable<E> {} impl<E: StatefulInteractiveElement> StatefulInteractiveElement for Focusable<E> {}
impl<E> Styled for Focusable<E> impl<E> Styled for Focusable<E>
where where
@ -1258,42 +1258,39 @@ impl<E> Element for Focusable<E>
where where
E: Element, E: Element,
{ {
type ElementState = E::ElementState; type State = E::State;
fn layout(
&mut self,
state: Option<Self::State>,
cx: &mut WindowContext,
) -> (LayoutId, Self::State) {
self.element.layout(state, cx)
}
fn paint(self, bounds: Bounds<Pixels>, state: &mut Self::State, cx: &mut WindowContext) {
self.element.paint(bounds, state, cx)
}
}
impl<E> RenderOnce for Focusable<E>
where
E: Element,
{
type Element = E;
fn element_id(&self) -> Option<ElementId> { fn element_id(&self) -> Option<ElementId> {
self.element.element_id() self.element.element_id()
} }
fn layout( fn render_once(self) -> Self::Element {
&mut self, self.element
element_state: Option<Self::ElementState>,
cx: &mut WindowContext,
) -> (LayoutId, Self::ElementState) {
self.element.layout(element_state, cx)
}
fn paint(
&mut self,
bounds: Bounds<Pixels>,
element_state: &mut Self::ElementState,
cx: &mut WindowContext,
) {
self.element.paint(bounds, element_state, cx);
} }
} }
impl<E> Component for Focusable<E> impl<E> ParentElement for Focusable<E>
where where
E: 'static + Element, E: ParentElement,
{
fn render(self) -> AnyElement {
AnyElement::new(self)
}
}
impl<E> ParentComponent for Focusable<E>
where
E: ParentComponent,
{ {
fn children_mut(&mut self) -> &mut SmallVec<[AnyElement; 2]> { fn children_mut(&mut self) -> &mut SmallVec<[AnyElement; 2]> {
self.element.children_mut() self.element.children_mut()
@ -1313,64 +1310,61 @@ where
} }
} }
impl<E> StatefulInteractiveComponent for Stateful<E> impl<E> StatefulInteractiveElement for Stateful<E>
where where
E: Element, E: Element,
Self: InteractiveComponent, Self: InteractiveElement,
{ {
} }
impl<E> InteractiveComponent for Stateful<E> impl<E> InteractiveElement for Stateful<E>
where where
E: InteractiveComponent, E: InteractiveElement,
{ {
fn interactivity(&mut self) -> &mut Interactivity { fn interactivity(&mut self) -> &mut Interactivity {
self.element.interactivity() self.element.interactivity()
} }
} }
impl<E: FocusableComponent> FocusableComponent for Stateful<E> {} impl<E: FocusableElement> FocusableElement for Stateful<E> {}
impl<E> Element for Stateful<E> impl<E> Element for Stateful<E>
where where
E: Element, E: Element,
{ {
type ElementState = E::ElementState; type State = E::State;
fn layout(
&mut self,
state: Option<Self::State>,
cx: &mut WindowContext,
) -> (LayoutId, Self::State) {
self.element.layout(state, cx)
}
fn paint(self, bounds: Bounds<Pixels>, state: &mut Self::State, cx: &mut WindowContext) {
self.element.paint(bounds, state, cx)
}
}
impl<E> RenderOnce for Stateful<E>
where
E: Element,
{
type Element = Self;
fn element_id(&self) -> Option<ElementId> { fn element_id(&self) -> Option<ElementId> {
self.element.element_id() self.element.element_id()
} }
fn layout( fn render_once(self) -> Self::Element {
&mut self, self
element_state: Option<Self::ElementState>,
cx: &mut WindowContext,
) -> (LayoutId, Self::ElementState) {
self.element.layout(element_state, cx)
}
fn paint(
&mut self,
bounds: Bounds<Pixels>,
element_state: &mut Self::ElementState,
cx: &mut WindowContext,
) {
self.element.paint(bounds, element_state, cx)
} }
} }
impl<E> Component for Stateful<E> impl<E> ParentElement for Stateful<E>
where where
E: 'static + Element, E: ParentElement,
{
fn render(self) -> AnyElement {
AnyElement::new(self)
}
}
impl<E> ParentComponent for Stateful<E>
where
E: ParentComponent,
{ {
fn children_mut(&mut self) -> &mut SmallVec<[AnyElement; 2]> { fn children_mut(&mut self) -> &mut SmallVec<[AnyElement; 2]> {
self.element.children_mut() self.element.children_mut()

View file

@ -1,6 +1,6 @@
use crate::{ use crate::{
AnyElement, Bounds, Component, Element, InteractiveComponent, InteractiveElementState, Bounds, Element, InteractiveElement, InteractiveElementState, Interactivity, LayoutId, Pixels,
Interactivity, LayoutId, Pixels, SharedString, StyleRefinement, Styled, WindowContext, RenderOnce, SharedString, StyleRefinement, Styled, WindowContext,
}; };
use futures::FutureExt; use futures::FutureExt;
use util::ResultExt; use util::ResultExt;
@ -31,33 +31,23 @@ impl Img {
} }
} }
impl Component for Img {
fn render(self) -> AnyElement {
AnyElement::new(self)
}
}
impl Element for Img { impl Element for Img {
type ElementState = InteractiveElementState; type State = InteractiveElementState;
fn element_id(&self) -> Option<crate::ElementId> {
self.interactivity.element_id.clone()
}
fn layout( fn layout(
&mut self, &mut self,
element_state: Option<Self::ElementState>, element_state: Option<Self::State>,
cx: &mut WindowContext, cx: &mut WindowContext,
) -> (LayoutId, Self::ElementState) { ) -> (LayoutId, Self::State) {
self.interactivity.layout(element_state, cx, |style, cx| { self.interactivity.layout(element_state, cx, |style, cx| {
cx.request_layout(&style, None) cx.request_layout(&style, None)
}) })
} }
fn paint( fn paint(
&mut self, self,
bounds: Bounds<Pixels>, bounds: Bounds<Pixels>,
element_state: &mut Self::ElementState, element_state: &mut Self::State,
cx: &mut WindowContext, cx: &mut WindowContext,
) { ) {
self.interactivity.paint( self.interactivity.paint(
@ -96,13 +86,25 @@ impl Element for Img {
} }
} }
impl RenderOnce for Img {
type Element = Self;
fn element_id(&self) -> Option<crate::ElementId> {
self.interactivity.element_id.clone()
}
fn render_once(self) -> Self::Element {
self
}
}
impl Styled for Img { impl Styled for Img {
fn style(&mut self) -> &mut StyleRefinement { fn style(&mut self) -> &mut StyleRefinement {
&mut self.interactivity.base_style &mut self.interactivity.base_style
} }
} }
impl InteractiveComponent for Img { impl InteractiveElement for Img {
fn interactivity(&mut self) -> &mut Interactivity { fn interactivity(&mut self) -> &mut Interactivity {
&mut self.interactivity &mut self.interactivity
} }

View file

@ -2,8 +2,8 @@ use smallvec::SmallVec;
use taffy::style::{Display, Position}; use taffy::style::{Display, Position};
use crate::{ use crate::{
point, AnyElement, BorrowWindow, Bounds, Component, Element, LayoutId, ParentComponent, Pixels, point, AnyElement, BorrowWindow, Bounds, Element, LayoutId, ParentElement, Pixels, Point,
Point, Size, Style, WindowContext, RenderOnce, Size, Style, WindowContext,
}; };
pub struct OverlayState { 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]> { fn children_mut(&mut self) -> &mut SmallVec<[AnyElement; 2]> {
&mut self.children &mut self.children
} }
} }
impl Component for Overlay {
fn render(self) -> AnyElement {
AnyElement::new(self)
}
}
impl Element for Overlay { impl Element for Overlay {
type ElementState = OverlayState; type State = OverlayState;
fn element_id(&self) -> Option<crate::ElementId> {
None
}
fn layout( fn layout(
&mut self, &mut self,
_: Option<Self::ElementState>, _: Option<Self::State>,
cx: &mut WindowContext, cx: &mut WindowContext,
) -> (crate::LayoutId, Self::ElementState) { ) -> (crate::LayoutId, Self::State) {
let child_layout_ids = self let child_layout_ids = self
.children .children
.iter_mut() .iter_mut()
@ -91,9 +81,9 @@ impl Element for Overlay {
} }
fn paint( fn paint(
&mut self, self,
bounds: crate::Bounds<crate::Pixels>, bounds: crate::Bounds<crate::Pixels>,
element_state: &mut Self::ElementState, element_state: &mut Self::State,
cx: &mut WindowContext, cx: &mut WindowContext,
) { ) {
if element_state.child_layout_ids.is_empty() { 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| { cx.with_element_offset(desired.origin - bounds.origin, |cx| {
for child in &mut self.children { for child in self.children {
child.paint(cx); child.paint(cx);
} }
}) })
} }
} }
impl RenderOnce for Overlay {
type Element = Self;
fn element_id(&self) -> Option<crate::ElementId> {
None
}
fn render_once(self) -> Self::Element {
self
}
}
enum Axis { enum Axis {
Horizontal, Horizontal,
Vertical, Vertical,

View file

@ -1,7 +1,6 @@
use crate::{ use crate::{
AnyElement, Bounds, Component, Element, ElementId, InteractiveComponent, Bounds, Element, ElementId, InteractiveElement, InteractiveElementState, Interactivity,
InteractiveElementState, Interactivity, LayoutId, Pixels, SharedString, StyleRefinement, LayoutId, Pixels, RenderOnce, SharedString, StyleRefinement, Styled, WindowContext,
Styled, WindowContext,
}; };
use util::ResultExt; use util::ResultExt;
@ -24,35 +23,21 @@ impl Svg {
} }
} }
impl Component for Svg {
fn render(self) -> AnyElement {
AnyElement::new(self)
}
}
impl Element for Svg { impl Element for Svg {
type ElementState = InteractiveElementState; type State = InteractiveElementState;
fn element_id(&self) -> Option<ElementId> {
self.interactivity.element_id.clone()
}
fn layout( fn layout(
&mut self, &mut self,
element_state: Option<Self::ElementState>, element_state: Option<Self::State>,
cx: &mut WindowContext, cx: &mut WindowContext,
) -> (LayoutId, Self::ElementState) { ) -> (LayoutId, Self::State) {
self.interactivity.layout(element_state, cx, |style, cx| { self.interactivity.layout(element_state, cx, |style, cx| {
cx.request_layout(&style, None) cx.request_layout(&style, None)
}) })
} }
fn paint( fn paint(self, bounds: Bounds<Pixels>, element_state: &mut Self::State, cx: &mut WindowContext)
&mut self, where
bounds: Bounds<Pixels>,
element_state: &mut Self::ElementState,
cx: &mut WindowContext,
) where
Self: Sized, Self: Sized,
{ {
self.interactivity self.interactivity
@ -64,13 +49,25 @@ impl Element for Svg {
} }
} }
impl RenderOnce for Svg {
type Element = Self;
fn element_id(&self) -> Option<ElementId> {
self.interactivity.element_id.clone()
}
fn render_once(self) -> Self::Element {
self
}
}
impl Styled for Svg { impl Styled for Svg {
fn style(&mut self) -> &mut StyleRefinement { fn style(&mut self) -> &mut StyleRefinement {
&mut self.interactivity.base_style &mut self.interactivity.base_style
} }
} }
impl InteractiveComponent for Svg { impl InteractiveElement for Svg {
fn interactivity(&mut self) -> &mut Interactivity { fn interactivity(&mut self) -> &mut Interactivity {
&mut self.interactivity &mut self.interactivity
} }

View file

@ -1,68 +1,163 @@
use crate::{ use crate::{
AnyElement, Bounds, Component, Element, ElementId, LayoutId, Pixels, SharedString, Size, Bounds, Element, ElementId, LayoutId, Pixels, RenderOnce, SharedString, Size, TextRun,
TextRun, WindowContext, WrappedLine, WindowContext, WrappedLine,
}; };
use anyhow::anyhow;
use parking_lot::{Mutex, MutexGuard}; use parking_lot::{Mutex, MutexGuard};
use smallvec::SmallVec; use smallvec::SmallVec;
use std::{cell::Cell, rc::Rc, sync::Arc}; use std::{cell::Cell, rc::Rc, sync::Arc};
use util::ResultExt; use util::ResultExt;
pub struct Text { impl Element for &'static str {
type State = TextState;
fn layout(
&mut self,
_: Option<Self::State>,
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<Pixels>, state: &mut TextState, cx: &mut WindowContext) {
state.paint(bounds, self, cx)
}
}
impl RenderOnce for &'static str {
type Element = Self;
fn element_id(&self) -> Option<ElementId> {
None
}
fn render_once(self) -> Self::Element {
self
}
}
impl Element for SharedString {
type State = TextState;
fn layout(
&mut self,
_: Option<Self::State>,
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<Pixels>, 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<ElementId> {
None
}
fn render_once(self) -> Self::Element {
self
}
}
pub struct StyledText {
text: SharedString, text: SharedString,
runs: Option<Vec<TextRun>>, runs: Option<Vec<TextRun>>,
} }
impl Text { impl StyledText {
/// Renders text with runs of different styles. /// Renders text with runs of different styles.
/// ///
/// Callers are responsible for setting the correct style for each run. /// Callers are responsible for setting the correct style for each run.
/// For text with a uniform style, you can usually avoid calling this constructor /// For text with a uniform style, you can usually avoid calling this constructor
/// and just pass text directly. /// and just pass text directly.
pub fn styled(text: SharedString, runs: Vec<TextRun>) -> Self { pub fn new(text: SharedString, runs: Vec<TextRun>) -> Self {
Text { StyledText {
text, text,
runs: Some(runs), runs: Some(runs),
} }
} }
} }
impl Component for Text { impl Element for StyledText {
fn render(self) -> AnyElement { type State = TextState;
AnyElement::new(self)
fn layout(
&mut self,
_: Option<Self::State>,
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<Pixels>, state: &mut Self::State, cx: &mut WindowContext) {
state.paint(bounds, &self.text, cx)
} }
} }
impl Element for Text { impl RenderOnce for StyledText {
type ElementState = TextState; type Element = Self;
fn element_id(&self) -> Option<crate::ElementId> { fn element_id(&self) -> Option<crate::ElementId> {
None None
} }
fn render_once(self) -> Self::Element {
self
}
}
#[derive(Default, Clone)]
pub struct TextState(Arc<Mutex<Option<TextStateInner>>>);
struct TextStateInner {
lines: SmallVec<[WrappedLine; 1]>,
line_height: Pixels,
wrap_width: Option<Pixels>,
size: Option<Size<Pixels>>,
}
impl TextState {
fn lock(&self) -> MutexGuard<Option<TextStateInner>> {
self.0.lock()
}
fn layout( fn layout(
&mut self, &mut self,
element_state: Option<Self::ElementState>, text: SharedString,
runs: Option<Vec<TextRun>>,
cx: &mut WindowContext, cx: &mut WindowContext,
) -> (LayoutId, Self::ElementState) { ) -> LayoutId {
let element_state = element_state.unwrap_or_default();
let text_system = cx.text_system().clone(); let text_system = cx.text_system().clone();
let text_style = cx.text_style(); let text_style = cx.text_style();
let font_size = text_style.font_size.to_pixels(cx.rem_size()); let font_size = text_style.font_size.to_pixels(cx.rem_size());
let line_height = text_style let line_height = text_style
.line_height .line_height
.to_pixels(font_size.into(), cx.rem_size()); .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 rem_size = cx.rem_size();
let runs = if let Some(runs) = self.runs.take() { let runs = if let Some(runs) = runs {
runs runs
} else { } else {
vec![text_style.to_run(text.len())] vec![text_style.to_run(text.len())]
}; };
let layout_id = cx.request_measured_layout(Default::default(), rem_size, { 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| { move |known_dimensions, available_space| {
let wrap_width = known_dimensions.width.or(match available_space.width { let wrap_width = known_dimensions.width.or(match available_space.width {
crate::AvailableSpace::Definite(x) => Some(x), crate::AvailableSpace::Definite(x) => Some(x),
@ -113,19 +208,14 @@ impl Element for Text {
} }
}); });
(layout_id, element_state) layout_id
} }
fn paint( fn paint(&mut self, bounds: Bounds<Pixels>, text: &str, cx: &mut WindowContext) {
&mut self, let element_state = self.lock();
bounds: Bounds<Pixels>,
element_state: &mut Self::ElementState,
cx: &mut WindowContext,
) {
let element_state = element_state.lock();
let element_state = element_state let element_state = element_state
.as_ref() .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(); .unwrap();
let line_height = element_state.line_height; let line_height = element_state.line_height;
@ -137,25 +227,9 @@ impl Element for Text {
} }
} }
#[derive(Default, Clone)]
pub struct TextState(Arc<Mutex<Option<TextStateInner>>>);
impl TextState {
fn lock(&self) -> MutexGuard<Option<TextStateInner>> {
self.0.lock()
}
}
struct TextStateInner {
lines: SmallVec<[WrappedLine; 1]>,
line_height: Pixels,
wrap_width: Option<Pixels>,
size: Option<Size<Pixels>>,
}
struct InteractiveText { struct InteractiveText {
id: ElementId, element_id: ElementId,
text: Text, text: StyledText,
} }
struct InteractiveTextState { struct InteractiveTextState {
@ -164,21 +238,17 @@ struct InteractiveTextState {
} }
impl Element for InteractiveText { impl Element for InteractiveText {
type ElementState = InteractiveTextState; type State = InteractiveTextState;
fn element_id(&self) -> Option<ElementId> {
Some(self.id.clone())
}
fn layout( fn layout(
&mut self, &mut self,
element_state: Option<Self::ElementState>, state: Option<Self::State>,
cx: &mut WindowContext, cx: &mut WindowContext,
) -> (LayoutId, Self::ElementState) { ) -> (LayoutId, Self::State) {
if let Some(InteractiveTextState { if let Some(InteractiveTextState {
text_state, text_state,
clicked_range_ixs, clicked_range_ixs,
}) = element_state }) = state
{ {
let (layout_id, text_state) = self.text.layout(Some(text_state), cx); let (layout_id, text_state) = self.text.layout(Some(text_state), cx);
let element_state = InteractiveTextState { let element_state = InteractiveTextState {
@ -196,44 +266,19 @@ impl Element for InteractiveText {
} }
} }
fn paint( fn paint(self, bounds: Bounds<Pixels>, state: &mut Self::State, cx: &mut WindowContext) {
&mut self, self.text.paint(bounds, &mut state.text_state, cx)
bounds: Bounds<Pixels>,
element_state: &mut Self::ElementState,
cx: &mut WindowContext,
) {
self.text.paint(bounds, &mut element_state.text_state, cx)
} }
} }
impl Component for SharedString { impl RenderOnce for InteractiveText {
fn render(self) -> AnyElement { type Element = Self;
Text {
text: self,
runs: None,
}
.render()
}
}
impl Component for &'static str { fn element_id(&self) -> Option<ElementId> {
fn render(self) -> AnyElement { Some(self.element_id.clone())
Text {
text: self.into(),
runs: None,
}
.render()
} }
}
// TODO: Figure out how to pass `String` to `child` without this. fn render_once(self) -> Self::Element {
// This impl doesn't exist in the `gpui2` crate. self
impl Component for String {
fn render(self) -> AnyElement {
Text {
text: self.into(),
runs: None,
}
.render()
} }
} }

View file

@ -1,23 +1,23 @@
use crate::{ use crate::{
point, px, size, AnyElement, AvailableSpace, Bounds, Component, Element, ElementId, point, px, size, AnyElement, AvailableSpace, Bounds, Element, ElementId, InteractiveElement,
InteractiveComponent, InteractiveElementState, Interactivity, LayoutId, Pixels, Point, Size, InteractiveElementState, Interactivity, LayoutId, Pixels, Point, RenderOnce, Size,
StyleRefinement, Styled, WindowContext, StyleRefinement, Styled, WindowContext,
}; };
use smallvec::SmallVec; 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; use taffy::style::Overflow;
/// uniform_list provides lazy rendering for a set of items that are of uniform height. /// 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, /// 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. /// uniform_list will only render the visibile subset of items.
pub fn uniform_list<I, C>( pub fn uniform_list<I, R>(
id: I, id: I,
item_count: usize, item_count: usize,
f: impl 'static + Fn(Range<usize>, &mut WindowContext) -> Vec<C>, f: impl 'static + Fn(Range<usize>, &mut WindowContext) -> Vec<R>,
) -> UniformList ) -> UniformList
where where
I: Into<ElementId>, I: Into<ElementId>,
C: Component, R: RenderOnce,
{ {
let id = id.into(); let id = id.into();
let mut style = StyleRefinement::default(); let mut style = StyleRefinement::default();
@ -31,7 +31,7 @@ where
render_items: Box::new(move |visible_range, cx| { render_items: Box::new(move |visible_range, cx| {
f(visible_range, cx) f(visible_range, cx)
.into_iter() .into_iter()
.map(|component| component.render()) .map(|component| component.render_into_any())
.collect() .collect()
}), }),
interactivity: Interactivity { interactivity: Interactivity {
@ -96,27 +96,23 @@ pub struct UniformListState {
} }
impl Element for UniformList { impl Element for UniformList {
type ElementState = UniformListState; type State = UniformListState;
fn element_id(&self) -> Option<crate::ElementId> {
Some(self.id.clone())
}
fn layout( fn layout(
&mut self, &mut self,
element_state: Option<Self::ElementState>, state: Option<Self::State>,
cx: &mut WindowContext, cx: &mut WindowContext,
) -> (LayoutId, Self::ElementState) { ) -> (LayoutId, Self::State) {
let max_items = self.item_count; let max_items = self.item_count;
let rem_size = cx.rem_size(); let rem_size = cx.rem_size();
let item_size = element_state let item_size = state
.as_ref() .as_ref()
.map(|s| s.item_size) .map(|s| s.item_size)
.unwrap_or_else(|| self.measure_item(None, cx)); .unwrap_or_else(|| self.measure_item(None, cx));
let (layout_id, interactive) = let (layout_id, interactive) =
self.interactivity 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( cx.request_measured_layout(
style, style,
rem_size, rem_size,
@ -152,9 +148,9 @@ impl Element for UniformList {
} }
fn paint( fn paint(
&mut self, self,
bounds: Bounds<crate::Pixels>, bounds: Bounds<crate::Pixels>,
element_state: &mut Self::ElementState, element_state: &mut Self::State,
cx: &mut WindowContext, cx: &mut WindowContext,
) { ) {
let style = let style =
@ -175,14 +171,15 @@ impl Element for UniformList {
height: item_size.height * self.item_count, height: item_size.height * self.item_count,
}; };
let mut interactivity = mem::take(&mut self.interactivity);
let shared_scroll_offset = element_state let shared_scroll_offset = element_state
.interactive .interactive
.scroll_offset .scroll_offset
.get_or_insert_with(Rc::default) .get_or_insert_with(Rc::default)
.clone(); .clone();
interactivity.paint( let item_height = self.measure_item(Some(padded_bounds.size.width), cx).height;
self.interactivity.paint(
bounds, bounds,
content_size, content_size,
&mut element_state.interactive, &mut element_state.interactive,
@ -201,8 +198,6 @@ impl Element for UniformList {
style.paint(bounds, cx); style.paint(bounds, cx);
if self.item_count > 0 { 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() { if let Some(scroll_handle) = self.scroll_handle.clone() {
scroll_handle.0.borrow_mut().replace(ScrollHandleState { scroll_handle.0.borrow_mut().replace(ScrollHandleState {
item_height, item_height,
@ -224,9 +219,9 @@ impl Element for UniformList {
self.item_count, 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| { 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 let item_origin = padded_bounds.origin
+ point(px(0.), item_height * ix + scroll_offset.y); + point(px(0.), item_height * ix + scroll_offset.y);
let available_space = size( 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<crate::ElementId> {
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 { fn interactivity(&mut self) -> &mut crate::Interactivity {
&mut self.interactivity &mut self.interactivity
} }
} }
impl Component for UniformList {
fn render(self) -> AnyElement {
AnyElement::new(self)
}
}

View file

@ -78,8 +78,6 @@ use std::{
}; };
use taffy::TaffyLayoutEngine; use taffy::TaffyLayoutEngine;
type AnyBox = Box<dyn Any>;
pub trait Context { pub trait Context {
type Result<T>; type Result<T>;
@ -136,7 +134,7 @@ pub trait VisualContext: Context {
build_view: impl FnOnce(&mut ViewContext<'_, V>) -> V, build_view: impl FnOnce(&mut ViewContext<'_, V>) -> V,
) -> Self::Result<View<V>> ) -> Self::Result<View<V>>
where where
V: Render; V: 'static + Render;
fn focus_view<V>(&mut self, view: &View<V>) -> Self::Result<()> fn focus_view<V>(&mut self, view: &View<V>) -> Self::Result<()>
where where

View file

@ -1,8 +1,9 @@
use crate::{ 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 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)] #[derive(Clone, Debug, Eq, PartialEq)]
pub struct KeyDownEvent { pub struct KeyDownEvent {
@ -59,6 +60,32 @@ pub struct ClickEvent {
pub up: MouseUpEvent, pub up: MouseUpEvent,
} }
pub struct Drag<S, R, V, E>
where
R: Fn(&mut V, &mut ViewContext<V>) -> E,
V: 'static,
E: RenderOnce,
{
pub state: S,
pub render_drag_handle: R,
view_element_types: PhantomData<(V, E)>,
}
impl<S, R, V, E> Drag<S, R, V, E>
where
R: Fn(&mut V, &mut ViewContext<V>) -> 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)] #[derive(Hash, PartialEq, Eq, Copy, Clone, Debug)]
pub enum MouseButton { pub enum MouseButton {
Left, Left,
@ -259,8 +286,8 @@ pub struct FocusEvent {
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use crate::{ use crate::{
self as gpui, div, Component, Div, FocusHandle, InteractiveComponent, KeyBinding, self as gpui, div, Div, FocusHandle, InteractiveElement, KeyBinding, Keystroke,
Keystroke, ParentComponent, Render, Stateful, TestAppContext, VisualContext, ParentElement, Render, RenderOnce, Stateful, TestAppContext, VisualContext,
}; };
struct TestView { struct TestView {
@ -288,7 +315,7 @@ mod test {
div() div()
.key_context("nested") .key_context("nested")
.track_focus(&self.focus_handle) .track_focus(&self.focus_handle)
.render(), .render_once(),
), ),
) )
} }

View file

@ -1,4 +1,5 @@
pub use crate::{ pub use crate::{
BorrowAppContext, BorrowWindow, Component, Context, FocusableComponent, InteractiveComponent, BorrowAppContext, BorrowWindow, Component, Context, Element, FocusableElement,
ParentComponent, Refineable, Render, StatefulInteractiveComponent, Styled, VisualContext, InteractiveElement, ParentElement, Refineable, Render, RenderOnce, StatefulInteractiveElement,
Styled, VisualContext,
}; };

View file

@ -1,23 +1,17 @@
use crate::{ use crate::{
private::Sealed, AnyBox, AnyElement, AnyModel, AnyWeakModel, AppContext, AvailableSpace, private::Sealed, AnyElement, AnyModel, AnyWeakModel, AppContext, AvailableSpace, BorrowWindow,
BorrowWindow, Bounds, Component, Element, ElementId, Entity, EntityId, Flatten, FocusHandle, Bounds, Element, ElementId, Entity, EntityId, Flatten, FocusHandle, FocusableView, LayoutId,
FocusableView, LayoutId, Model, Pixels, Point, Size, ViewContext, VisualContext, WeakModel, Model, Pixels, Point, Render, RenderOnce, Size, ViewContext, VisualContext, WeakModel,
WindowContext, WindowContext,
}; };
use anyhow::{Context, Result}; use anyhow::{Context, Result};
use std::{ use std::{
any::{Any, TypeId}, any::TypeId,
hash::{Hash, Hasher}, hash::{Hash, Hasher},
}; };
pub trait Render: 'static + Sized {
type Element: Element + 'static;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element;
}
pub struct View<V> { pub struct View<V> {
pub(crate) model: Model<V>, pub model: Model<V>,
} }
impl<V> Sealed for View<V> {} impl<V> Sealed for View<V> {}
@ -65,15 +59,15 @@ impl<V: 'static> View<V> {
self.model.read(cx) self.model.read(cx)
} }
pub fn render_with<C>(&self, component: C) -> RenderViewWith<C, V> // pub fn render_with<E>(&self, component: E) -> RenderViewWith<E, V>
where // where
C: 'static + Component, // E: 'static + Element,
{ // {
RenderViewWith { // RenderViewWith {
view: self.clone(), // view: self.clone(),
component: Some(component), // element: Some(component),
} // }
} // }
pub fn focus_handle(&self, cx: &AppContext) -> FocusHandle pub fn focus_handle(&self, cx: &AppContext) -> FocusHandle
where where
@ -83,6 +77,26 @@ impl<V: 'static> View<V> {
} }
} }
impl<V: Render> Element for View<V> {
type State = Option<AnyElement>;
fn layout(
&mut self,
_state: Option<Self::State>,
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<Pixels>, element: &mut Self::State, cx: &mut WindowContext) {
element.take().unwrap().paint(cx);
}
}
impl<V> Clone for View<V> { impl<V> Clone for View<V> {
fn clone(&self) -> Self { fn clone(&self) -> Self {
Self { Self {
@ -105,12 +119,6 @@ impl<V> PartialEq for View<V> {
impl<V> Eq for View<V> {} impl<V> Eq for View<V> {}
impl<V: Render> Component for View<V> {
fn render(self) -> AnyElement {
AnyElement::new(AnyView::from(self))
}
}
pub struct WeakView<V> { pub struct WeakView<V> {
pub(crate) model: WeakModel<V>, pub(crate) model: WeakModel<V>,
} }
@ -163,8 +171,8 @@ impl<V> Eq for WeakView<V> {}
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct AnyView { pub struct AnyView {
model: AnyModel, model: AnyModel,
layout: fn(&AnyView, &mut WindowContext) -> (LayoutId, Box<dyn Any>), layout: fn(&AnyView, &mut WindowContext) -> (LayoutId, AnyElement),
paint: fn(&AnyView, &mut AnyBox, &mut WindowContext), paint: fn(&AnyView, AnyElement, &mut WindowContext),
} }
impl AnyView { impl AnyView {
@ -202,21 +210,15 @@ impl AnyView {
cx: &mut WindowContext, cx: &mut WindowContext,
) { ) {
cx.with_absolute_element_offset(origin, |cx| { 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 cx.window
.layout_engine .layout_engine
.compute_layout(layout_id, available_space); .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<V: Render> From<View<V>> for AnyView { impl<V: Render> From<View<V>> for AnyView {
fn from(value: View<V>) -> Self { fn from(value: View<V>) -> Self {
AnyView { AnyView {
@ -228,34 +230,50 @@ impl<V: Render> From<View<V>> for AnyView {
} }
impl Element for AnyView { impl Element for AnyView {
type ElementState = Box<dyn Any>; type State = Option<AnyElement>;
fn layout(
&mut self,
_state: Option<Self::State>,
cx: &mut WindowContext,
) -> (LayoutId, Self::State) {
let (layout_id, state) = (self.layout)(self, cx);
(layout_id, Some(state))
}
fn paint(self, _: Bounds<Pixels>, state: &mut Self::State, cx: &mut WindowContext) {
(self.paint)(&self, state.take().unwrap(), cx)
}
}
impl<V: 'static + Render> RenderOnce for View<V> {
type Element = View<V>;
fn element_id(&self) -> Option<ElementId> { fn element_id(&self) -> Option<ElementId> {
Some(self.model.entity_id.into()) Some(self.model.entity_id.into())
} }
fn layout( fn render_once(self) -> Self::Element {
&mut self, self
_element_state: Option<Self::ElementState>, }
cx: &mut WindowContext, }
) -> (LayoutId, Self::ElementState) {
(self.layout)(self, cx) impl RenderOnce for AnyView {
type Element = Self;
fn element_id(&self) -> Option<ElementId> {
Some(self.model.entity_id.into())
} }
fn paint( fn render_once(self) -> Self::Element {
&mut self, self
_bounds: Bounds<Pixels>,
rendered_element: &mut Self::ElementState,
cx: &mut WindowContext,
) {
(self.paint)(self, rendered_element, cx)
} }
} }
pub struct AnyWeakView { pub struct AnyWeakView {
model: AnyWeakModel, model: AnyWeakModel,
layout: fn(&AnyView, &mut WindowContext) -> (LayoutId, Box<dyn Any>), layout: fn(&AnyView, &mut WindowContext) -> (LayoutId, AnyElement),
paint: fn(&AnyView, &mut AnyBox, &mut WindowContext), paint: fn(&AnyView, AnyElement, &mut WindowContext),
} }
impl AnyWeakView { impl AnyWeakView {
@ -269,7 +287,7 @@ impl AnyWeakView {
} }
} }
impl<V: Render> From<WeakView<V>> for AnyWeakView { impl<V: 'static + Render> From<WeakView<V>> for AnyWeakView {
fn from(view: WeakView<V>) -> Self { fn from(view: WeakView<V>) -> Self {
Self { Self {
model: view.model.into(), model: view.model.into(),
@ -291,77 +309,73 @@ where
} }
} }
pub struct RenderViewWith<C, V> { // pub struct RenderViewWith<E, V> {
view: View<V>, // view: View<V>,
component: Option<C>, // element: Option<E>,
} // }
impl<C, V> Component for RenderViewWith<C, V> // impl<E> Element for RenderViewWith<E>
where // where
V: 'static + Render, // E: 'static + Element,
C: 'static + Component, // {
{ // type State = Option<AnyElement>;
fn render(self) -> AnyElement {
AnyElement::new(self)
}
}
impl<C, V> Element for RenderViewWith<C, V> // fn layout(
where // &mut self,
V: 'static + Render, // _: Option<Self::State>,
C: 'static + Component, // cx: &mut WindowContext,
{ // ) -> (LayoutId, Self::State) {
type ElementState = AnyElement; // 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<ElementId> { // fn paint(self, _: Bounds<Pixels>, element: &mut Self::ElementState, cx: &mut WindowContext) {
Some(self.view.entity_id().into()) // element.paint(cx)
} // }
// }
fn layout( // impl<E> RenderOnce for RenderViewWith<E>
&mut self, // where
_: Option<Self::ElementState>, // E: 'static + Element<V>,
cx: &mut WindowContext, // ParentV: 'static,
) -> (LayoutId, Self::ElementState) { // {
let mut element = self.component.take().unwrap().render(); // type Element = Self;
let layout_id = element.layout(cx);
(layout_id, element)
}
fn paint( // fn element_id(&self) -> Option<ElementId> {
&mut self, // self.element.as_ref().unwrap().element_id()
_: Bounds<Pixels>, // }
element: &mut Self::ElementState,
cx: &mut WindowContext, // fn render_once(self) -> Self::Element {
) { // self
element.paint(cx) // }
} // }
}
mod any_view { mod any_view {
use crate::{AnyElement, AnyView, BorrowWindow, LayoutId, Render, WindowContext}; use crate::{AnyElement, AnyView, BorrowWindow, Element, LayoutId, Render, WindowContext};
use std::any::Any;
pub(crate) fn layout<V: Render>( pub(crate) fn layout<V: 'static + Render>(
view: &AnyView, view: &AnyView,
cx: &mut WindowContext, cx: &mut WindowContext,
) -> (LayoutId, Box<dyn Any>) { ) -> (LayoutId, AnyElement) {
cx.with_element_id(Some(view.model.entity_id), |cx| { cx.with_element_id(Some(view.model.entity_id), |cx| {
let view = view.clone().downcast::<V>().unwrap(); let view = view.clone().downcast::<V>().unwrap();
view.update(cx, |view, cx| { 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); let layout_id = element.layout(cx);
(layout_id, Box::new(element) as Box<dyn Any>) (layout_id, element)
}) })
}) })
} }
pub(crate) fn paint<V: Render>( pub(crate) fn paint<V: 'static + Render>(
view: &AnyView, view: &AnyView,
element: &mut Box<dyn Any>, element: AnyElement,
cx: &mut WindowContext, cx: &mut WindowContext,
) { ) {
cx.with_element_id(Some(view.model.entity_id), |cx| { cx.with_element_id(Some(view.model.entity_id), |cx| {
let element = element.downcast_mut::<AnyElement>().unwrap();
element.paint(cx); element.paint(cx);
}) })
} }

View file

@ -1,5 +1,5 @@
use crate::{ 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, AsyncWindowContext, AvailableSpace, Bounds, BoxShadow, CallbackHandle, ConstructorHandle,
Context, Corners, CursorStyle, DevicePixels, DispatchNodeId, DispatchTree, DisplayId, Edges, Context, Corners, CursorStyle, DevicePixels, DispatchNodeId, DispatchTree, DisplayId, Edges,
Effect, Entity, EntityId, EventEmitter, FileDropEvent, Flatten, FocusEvent, FontId, Effect, Entity, EntityId, EventEmitter, FileDropEvent, Flatten, FocusEvent, FontId,
@ -187,7 +187,7 @@ impl Drop for FocusHandle {
/// FocusableView allows users of your view to easily /// FocusableView allows users of your view to easily
/// focus it (using cx.focus_view(view)) /// focus it (using cx.focus_view(view))
pub trait FocusableView: Render { pub trait FocusableView: 'static + Render {
fn focus_handle(&self, cx: &AppContext) -> FocusHandle; fn focus_handle(&self, cx: &AppContext) -> FocusHandle;
} }
@ -232,7 +232,7 @@ pub struct Window {
// #[derive(Default)] // #[derive(Default)]
pub(crate) struct Frame { pub(crate) struct Frame {
pub(crate) element_states: HashMap<GlobalElementId, AnyBox>, pub(crate) element_states: HashMap<GlobalElementId, Box<dyn Any>>,
mouse_listeners: HashMap<TypeId, Vec<(StackingOrder, AnyMouseListener)>>, mouse_listeners: HashMap<TypeId, Vec<(StackingOrder, AnyMouseListener)>>,
pub(crate) dispatch_tree: DispatchTree, pub(crate) dispatch_tree: DispatchTree,
pub(crate) focus_listeners: Vec<AnyFocusListener>, pub(crate) focus_listeners: Vec<AnyFocusListener>,
@ -1600,7 +1600,7 @@ impl VisualContext for WindowContext<'_> {
build_view: impl FnOnce(&mut ViewContext<'_, V>) -> V, build_view: impl FnOnce(&mut ViewContext<'_, V>) -> V,
) -> Self::Result<View<V>> ) -> Self::Result<View<V>>
where where
V: Render, V: 'static + Render,
{ {
let slot = self.app.entities.reserve(); let slot = self.app.entities.reserve();
let view = View { let view = View {
@ -2372,7 +2372,7 @@ impl<V: 'static> VisualContext for ViewContext<'_, V> {
build_view: impl FnOnce(&mut ViewContext<'_, W>) -> W, build_view: impl FnOnce(&mut ViewContext<'_, W>) -> W,
) -> Self::Result<View<W>> ) -> Self::Result<View<W>>
where where
W: Render, W: 'static + Render,
{ {
self.window_cx.replace_root_view(build_view) self.window_cx.replace_root_view(build_view)
} }

View file

@ -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<ElementId> {
None
}
fn render_once(self) -> Self::Element {
gpui::CompositeElement::new(self)
}
}
};
gen.into()
}
fn specified_view_type(ast: &DeriveInput) -> Option<proc_macro2::Ident> {
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::<syn::Ident>()
.expect("Failed to parse view_type"),
);
}
}
}
None
})
}

View file

@ -1,16 +1,12 @@
mod action; mod action;
mod derive_component; mod derive_component;
mod derive_render_once;
mod register_action; mod register_action;
mod style_helpers; mod style_helpers;
mod test; mod test;
use proc_macro::TokenStream; use proc_macro::TokenStream;
#[proc_macro]
pub fn style_helpers(args: TokenStream) -> TokenStream {
style_helpers::style_helpers(args)
}
#[proc_macro_derive(Action)] #[proc_macro_derive(Action)]
pub fn action(input: TokenStream) -> TokenStream { pub fn action(input: TokenStream) -> TokenStream {
action::action(input) action::action(input)
@ -26,6 +22,16 @@ pub fn derive_component(input: TokenStream) -> TokenStream {
derive_component::derive_component(input) 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] #[proc_macro_attribute]
pub fn test(args: TokenStream, function: TokenStream) -> TokenStream { pub fn test(args: TokenStream, function: TokenStream) -> TokenStream {
test::test(args, function) test::test(args, function)

View file

@ -1,7 +1,7 @@
use editor::Editor; use editor::Editor;
use gpui::{ use gpui::{
div, prelude::*, uniform_list, AppContext, Component, Div, FocusHandle, FocusableView, div, prelude::*, uniform_list, AppContext, Div, FocusHandle, FocusableView, MouseButton,
MouseButton, Render, Task, UniformListScrollHandle, View, ViewContext, WindowContext, Render, Task, UniformListScrollHandle, View, ViewContext, WindowContext,
}; };
use std::{cmp, sync::Arc}; use std::{cmp, sync::Arc};
use ui::{prelude::*, v_stack, Divider, Label, TextColor}; use ui::{prelude::*, v_stack, Divider, Label, TextColor};
@ -15,7 +15,7 @@ pub struct Picker<D: PickerDelegate> {
} }
pub trait PickerDelegate: Sized + 'static { pub trait PickerDelegate: Sized + 'static {
type ListItem: Component<Picker<Self>>; type ListItem: RenderOnce<Picker<Self>>;
fn match_count(&self) -> usize; fn match_count(&self) -> usize;
fn selected_index(&self) -> usize; fn selected_index(&self) -> usize;
@ -180,7 +180,7 @@ impl<D: PickerDelegate> Picker<D> {
} }
} }
impl<D: PickerDelegate> Render for Picker<D> { impl<D: PickerDelegate> Render<Self> for Picker<D> {
type Element = Div<Self>; type Element = Div<Self>;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element { fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {

View file

@ -9,10 +9,10 @@ use file_associations::FileAssociations;
use anyhow::{anyhow, Result}; use anyhow::{anyhow, Result};
use gpui::{ use gpui::{
actions, div, px, uniform_list, Action, AppContext, AssetSource, AsyncWindowContext, actions, div, px, uniform_list, Action, AppContext, AssetSource, AsyncWindowContext,
ClipboardItem, Component, Div, EventEmitter, FocusHandle, Focusable, FocusableView, ClipboardItem, Div, EventEmitter, FocusHandle, Focusable, FocusableView, InteractiveElement,
InteractiveComponent, Model, MouseButton, ParentComponent, Pixels, Point, PromptLevel, Render, Model, MouseButton, ParentElement, Pixels, Point, PromptLevel, Render, RenderOnce, Stateful,
Stateful, StatefulInteractiveComponent, Styled, Task, UniformListScrollHandle, View, StatefulInteractiveElement, Styled, Task, UniformListScrollHandle, View, ViewContext,
ViewContext, VisualContext as _, WeakView, WindowContext, VisualContext as _, WeakView, WindowContext,
}; };
use menu::{Confirm, SelectNext, SelectPrev}; use menu::{Confirm, SelectNext, SelectPrev};
use project::{ use project::{
@ -247,7 +247,6 @@ impl ProjectPanel {
let mut old_dock_position = this.position(cx); let mut old_dock_position = this.position(cx);
ProjectPanelSettings::register(cx); ProjectPanelSettings::register(cx);
cx.observe_global::<SettingsStore>(move |this, cx| { cx.observe_global::<SettingsStore>(move |this, cx| {
dbg!("OLA!");
let new_dock_position = this.position(cx); let new_dock_position = this.position(cx);
if new_dock_position != old_dock_position { if new_dock_position != old_dock_position {
old_dock_position = new_dock_position; old_dock_position = new_dock_position;
@ -1424,7 +1423,7 @@ impl ProjectPanel {
} }
} }
impl Render for ProjectPanel { impl Render<Self> for ProjectPanel {
type Element = Focusable<Self, Stateful<Self, Div<Self>>>; type Element = Focusable<Self, Stateful<Self, Div<Self>>>;
fn render(&mut self, _cx: &mut gpui::ViewContext<Self>) -> Self::Element { fn render(&mut self, _cx: &mut gpui::ViewContext<Self>) -> Self::Element {

View file

@ -5,7 +5,7 @@ use ui::prelude::*;
pub struct ColorsStory; pub struct ColorsStory;
impl Render for ColorsStory { impl Render<Self> for ColorsStory {
type Element = Div<Self>; type Element = Div<Self>;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element { fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
@ -28,7 +28,7 @@ impl Render for ColorsStory {
div() div()
.w(px(75.)) .w(px(75.))
.line_height(px(24.)) .line_height(px(24.))
.child(scale.name().to_string()), .child(scale.name().clone()),
) )
.child( .child(
div() div()

View file

@ -26,7 +26,7 @@ impl FocusStory {
} }
} }
impl Render for FocusStory { impl Render<Self> for FocusStory {
type Element = Focusable<Self, Stateful<Self, Div<Self>>>; type Element = Focusable<Self, Stateful<Self, Div<Self>>>;
fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> Self::Element { fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> Self::Element {

View file

@ -11,7 +11,7 @@ impl KitchenSinkStory {
} }
} }
impl Render for KitchenSinkStory { impl Render<Self> for KitchenSinkStory {
type Element = Stateful<Self, Div<Self>>; type Element = Stateful<Self, Div<Self>>;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element { fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {

View file

@ -1,5 +1,7 @@
use fuzzy::StringMatchCandidate; 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 picker::{Picker, PickerDelegate};
use std::sync::Arc; use std::sync::Arc;
use theme2::ActiveTheme; use theme2::ActiveTheme;
@ -54,7 +56,8 @@ impl PickerDelegate for Delegate {
let Some(candidate_ix) = self.matches.get(ix) else { let Some(candidate_ix) = self.matches.get(ix) else {
return div(); 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() div()
.text_color(colors.text) .text_color(colors.text)
@ -202,7 +205,7 @@ impl PickerStory {
} }
} }
impl Render for PickerStory { impl Render<Self> for PickerStory {
type Element = Div<Self>; type Element = Div<Self>;
fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> Self::Element { fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> Self::Element {

View file

@ -10,7 +10,7 @@ impl ScrollStory {
} }
} }
impl Render for ScrollStory { impl Render<Self> for ScrollStory {
type Element = Stateful<Self, Div<Self>>; type Element = Stateful<Self, Div<Self>>;
fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> Self::Element { fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> Self::Element {

View file

@ -1,5 +1,5 @@
use gpui::{ 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; use ui::v_stack;
@ -11,7 +11,7 @@ impl TextStory {
} }
} }
impl Render for TextStory { impl Render<Self> for TextStory {
type Element = Div<Self>; type Element = Div<Self>;
fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> Self::Element { fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> Self::Element {

View file

@ -1,4 +1,4 @@
use gpui::{px, rgb, Div, Hsla, Render}; use gpui::{px, rgb, Div, Hsla, Render, RenderOnce};
use ui::prelude::*; use ui::prelude::*;
use crate::story::Story; 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). /// [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; pub struct ZIndexStory;
impl Render for ZIndexStory { impl Render<Self> for ZIndexStory {
type Element = Div<Self>; type Element = Div<Self>;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element { fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
@ -79,17 +79,15 @@ trait Styles: Styled + Sized {
impl<V: 'static> Styles for Div<V> {} impl<V: 'static> Styles for Div<V> {}
#[derive(Component)] #[derive(RenderOnce)]
struct ZIndexExample { struct ZIndexExample {
z_index: u32, z_index: u32,
} }
impl ZIndexExample { impl<V: 'static> Component<V> for ZIndexExample {
pub fn new(z_index: u32) -> Self { type Rendered = Div<V>;
Self { z_index }
}
fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> { fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
div() div()
.relative() .relative()
.size_full() .size_full()
@ -109,14 +107,14 @@ impl ZIndexExample {
// HACK: Simulate `text-align: center`. // HACK: Simulate `text-align: center`.
.pl(px(24.)) .pl(px(24.))
.z_index(self.z_index) .z_index(self.z_index)
.child(format!( .child(SharedString::from(format!(
"z-index: {}", "z-index: {}",
if self.z_index == 0 { if self.z_index == 0 {
"auto".to_string() "auto".to_string()
} else { } else {
self.z_index.to_string() self.z_index.to_string()
} }
)), ))),
) )
// Blue blocks. // Blue blocks.
.child( .child(
@ -173,3 +171,9 @@ impl ZIndexExample {
) )
} }
} }
impl ZIndexExample {
pub fn new(z_index: u32) -> Self {
Self { z_index }
}
}

View file

@ -105,7 +105,7 @@ impl StoryWrapper {
} }
} }
impl Render for StoryWrapper { impl Render<Self> for StoryWrapper {
type Element = Div<Self>; type Element = Div<Self>;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element { fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {

View file

@ -60,7 +60,7 @@ struct TestView {
story: AnyView, story: AnyView,
} }
impl Render for TestView { impl Render<Self> for TestView {
type Element = Div<Self>; type Element = Div<Self>;
fn render(&mut self, _cx: &mut ViewContext<Self>) -> Self::Element { fn render(&mut self, _cx: &mut ViewContext<Self>) -> Self::Element {

View file

@ -4,7 +4,7 @@ use crate::TerminalView;
use db::kvp::KEY_VALUE_STORE; use db::kvp::KEY_VALUE_STORE;
use gpui::{ use gpui::{
actions, div, serde_json, AppContext, AsyncWindowContext, Div, Entity, EventEmitter, 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, VisualContext, WeakView, WindowContext,
}; };
use project::Fs; use project::Fs;
@ -335,7 +335,7 @@ impl TerminalPanel {
impl EventEmitter<PanelEvent> for TerminalPanel {} impl EventEmitter<PanelEvent> for TerminalPanel {}
impl Render for TerminalPanel { impl Render<Self> for TerminalPanel {
type Element = Div<Self>; type Element = Div<Self>;
fn render(&mut self, _cx: &mut ViewContext<Self>) -> Self::Element { fn render(&mut self, _cx: &mut ViewContext<Self>) -> Self::Element {

View file

@ -9,10 +9,10 @@ pub mod terminal_panel;
// use crate::terminal_element::TerminalElement; // use crate::terminal_element::TerminalElement;
use editor::{scroll::autoscroll::Autoscroll, Editor}; use editor::{scroll::autoscroll::Autoscroll, Editor};
use gpui::{ use gpui::{
actions, div, Action, AnyElement, AppContext, Component, DispatchPhase, Div, EventEmitter, actions, div, Action, AnyElement, AppContext, DispatchPhase, Div, Element, EventEmitter,
FocusEvent, FocusHandle, Focusable, FocusableComponent, FocusableView, InputHandler, FocusEvent, FocusHandle, Focusable, FocusableElement, FocusableView, InputHandler,
InteractiveComponent, KeyDownEvent, Keystroke, Model, MouseButton, ParentComponent, Pixels, InteractiveElement, KeyDownEvent, Keystroke, Model, MouseButton, ParentElement, Pixels, Render,
Render, SharedString, Styled, Task, View, ViewContext, VisualContext, WeakView, SharedString, Styled, Task, View, ViewContext, VisualContext, WeakView,
}; };
use language::Bias; use language::Bias;
use persistence::TERMINAL_DB; use persistence::TERMINAL_DB;
@ -537,7 +537,7 @@ impl TerminalView {
} }
} }
impl Render for TerminalView { impl Render<Self> for TerminalView {
type Element = Focusable<Self, Div<Self>>; type Element = Focusable<Self, Div<Self>>;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element { fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
@ -577,7 +577,7 @@ impl Render for TerminalView {
.children( .children(
self.context_menu self.context_menu
.clone() .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) .track_focus(&self.focus_handle)
.on_focus_in(Self::focus_in) .on_focus_in(Self::focus_in)
@ -755,8 +755,8 @@ impl Item for TerminalView {
div() div()
.child(IconElement::new(Icon::Terminal)) .child(IconElement::new(Icon::Terminal))
.child(title) .child(Label::new(title))
.render() .into_any()
} }
fn clone_on_split( fn clone_on_split(

View file

@ -1,4 +1,4 @@
use gpui::{div, Component, Div, ParentComponent, Styled, ViewContext}; use gpui::{div, Div, Element, ParentElement, SharedString, Styled, ViewContext};
use crate::ActiveTheme; use crate::ActiveTheme;
@ -16,23 +16,26 @@ impl Story {
.bg(cx.theme().colors().background) .bg(cx.theme().colors().background)
} }
pub fn title<V: 'static>(cx: &mut ViewContext<V>, title: &str) -> impl Component<V> { pub fn title<V: 'static>(cx: &mut ViewContext<V>, title: SharedString) -> impl Element<V> {
div() div()
.text_xl() .text_xl()
.text_color(cx.theme().colors().text) .text_color(cx.theme().colors().text)
.child(title.to_owned()) .child(title)
} }
pub fn title_for<V: 'static, T>(cx: &mut ViewContext<V>) -> impl Component<V> { pub fn title_for<V: 'static, T>(cx: &mut ViewContext<V>) -> impl Element<V> {
Self::title(cx, std::any::type_name::<T>()) Self::title(cx, std::any::type_name::<T>().into())
} }
pub fn label<V: 'static>(cx: &mut ViewContext<V>, label: &str) -> impl Component<V> { pub fn label<V: 'static>(
cx: &mut ViewContext<V>,
label: impl Into<SharedString>,
) -> impl Element<V> {
div() div()
.mt_4() .mt_4()
.mb_2() .mb_2()
.text_xs() .text_xs()
.text_color(cx.theme().colors().text) .text_color(cx.theme().colors().text)
.child(label.to_owned()) .child(label.into())
} }
} }

View file

@ -143,11 +143,11 @@ use crate::{amber, blue, jade, lime, orange, pink, purple, red};
mod stories { mod stories {
use super::*; use super::*;
use crate::{ActiveTheme, Story}; 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; pub struct PlayerStory;
impl Render for PlayerStory { impl Render<Self> for PlayerStory {
type Element = Div<Self>; type Element = Div<Self>;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element { fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {

View file

@ -49,13 +49,13 @@ use gpui::hsla
impl<V: 'static> TodoList<V> { impl<V: 'static> TodoList<V> {
// ... // ...
fn render(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> { fn render(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Element<V> {
div().size_4().bg(hsla(50.0/360.0, 1.0, 0.5, 1.0)) 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<V>`. This basic component will render a 16x16px yellow square on the screen. Every component needs a render method, and it should return `impl Element<V>`. This basic component will render a 16x16px yellow square on the screen.
A couple of questions might come to mind: A couple of questions might come to mind:
@ -87,7 +87,7 @@ We can access the current theme's colors like this:
~~~rust ~~~rust
impl<V: 'static> TodoList<V> { impl<V: 'static> TodoList<V> {
// ... // ...
fn render(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> { fn render(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Element<V> {
let color = cx.theme().colors() let color = cx.theme().colors()
div().size_4().hsla(50.0/360.0, 1.0, 0.5, 1.0) div().size_4().hsla(50.0/360.0, 1.0, 0.5, 1.0)
@ -102,7 +102,7 @@ use gpui::hsla
impl<V: 'static> TodoList<V> { impl<V: 'static> TodoList<V> {
// ... // ...
fn render(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> { fn render(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Element<V> {
let color = cx.theme().colors() let color = cx.theme().colors()
div().size_4().bg(color.surface) div().size_4().bg(color.surface)
@ -117,7 +117,7 @@ use gpui::hsla
impl<V: 'static> TodoList<V> { impl<V: 'static> TodoList<V> {
// ... // ...
fn render(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> { fn render(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Element<V> {
let color = cx.theme().colors() let color = cx.theme().colors()
div() div()

View file

@ -1,27 +1,16 @@
use gpui::img;
use crate::prelude::*; use crate::prelude::*;
use gpui::{img, Img, RenderOnce};
#[derive(Component)] #[derive(RenderOnce)]
pub struct Avatar { pub struct Avatar {
src: SharedString, src: SharedString,
shape: Shape, shape: Shape,
} }
impl Avatar { impl<V: 'static> Component<V> for Avatar {
pub fn new(src: impl Into<SharedString>) -> Self { type Rendered = Img<V>;
Self {
src: src.into(),
shape: Shape::Circle,
}
}
pub fn shape(mut self, shape: Shape) -> Self { fn render(self, _view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
self.shape = shape;
self
}
fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
let mut img = img(); let mut img = img();
if self.shape == Shape::Circle { if self.shape == Shape::Circle {
@ -37,6 +26,20 @@ impl Avatar {
} }
} }
impl Avatar {
pub fn new(src: impl Into<SharedString>) -> Self {
Self {
src: src.into(),
shape: Shape::Circle,
}
}
pub fn shape(mut self, shape: Shape) -> Self {
self.shape = shape;
self
}
}
#[cfg(feature = "stories")] #[cfg(feature = "stories")]
pub use stories::*; pub use stories::*;
@ -48,7 +51,7 @@ mod stories {
pub struct AvatarStory; pub struct AvatarStory;
impl Render for AvatarStory { impl Render<Self> for AvatarStory {
type Element = Div<Self>; type Element = Div<Self>;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element { fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {

View file

@ -1,7 +1,8 @@
use std::sync::Arc; use std::sync::Arc;
use gpui::{ use gpui::{
CallbackHandle, DefiniteLength, Hsla, MouseButton, StatefulInteractiveComponent, WindowContext, DefiniteLength, DefiniteLength, Div, Hsla, MouseButton, RenderOnce, Stateful,
StatefulInteractiveElement, WindowContext,
}; };
use crate::prelude::*; use crate::prelude::*;
@ -63,7 +64,6 @@ impl ButtonVariant {
} }
} }
// #[derive(Component)] <- todo
pub struct Button { pub struct Button {
disabled: bool, disabled: bool,
click_handler: Option<CallbackHandle<()>>, click_handler: Option<CallbackHandle<()>>,
@ -75,6 +75,57 @@ pub struct Button {
color: Option<TextColor>, color: Option<TextColor>,
} }
impl RenderOnce for Button {
type Element = Stateful<Div>;
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 { impl Button {
pub fn new(label: impl Into<SharedString>) -> Self { pub fn new(label: impl Into<SharedString>) -> Self {
Self { Self {
@ -150,72 +201,30 @@ impl Button {
fn render_icon(&self, icon_color: TextColor) -> Option<IconElement> { fn render_icon(&self, icon_color: TextColor) -> Option<IconElement> {
self.icon.map(|i| IconElement::new(i).color(icon_color)) 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 { pub struct ButtonGroup {
buttons: Vec<Button>, buttons: Vec<Button>,
} }
impl ButtonGroup { impl Component for ButtonGroup {
pub fn new(buttons: Vec<Button>) -> Self { type Rendered = Div;
Self { buttons }
}
fn render(self, cx: &mut WindowContext) -> impl Component { fn render(self, cx: &mut WindowContext) -> Self::Rendered {
let mut el = h_stack().text_ui(); let mut group = h_stack();
for button in self.buttons { for button in self.buttons.into_iter() {
el = el.child(button.render(cx)); group = group.child(button.render(view, cx));
} }
el group
}
}
impl<V: 'static> ButtonGroup<V> {
pub fn new(buttons: Vec<Button<V>>) -> Self {
Self { buttons }
} }
} }

View file

@ -1,4 +1,4 @@
use gpui::{div, prelude::*, Component, ElementId, Styled, ViewContext}; use gpui::{div, prelude::*, Div, Element, ElementId, RenderOnce, Stateful, Styled, ViewContext};
use std::sync::Arc; use std::sync::Arc;
use theme2::ActiveTheme; use theme2::ActiveTheme;
@ -11,7 +11,7 @@ pub type CheckHandler<V> = Arc<dyn Fn(Selection, &mut V, &mut ViewContext<V>) +
/// Checkboxes are used for multiple choices, not for mutually exclusive choices. /// Checkboxes are used for multiple choices, not for mutually exclusive choices.
/// Each checkbox works independently from other checkboxes in the list, /// Each checkbox works independently from other checkboxes in the list,
/// therefore checking an additional box does not affect any other selections. /// therefore checking an additional box does not affect any other selections.
#[derive(Component)] #[derive(RenderOnce)]
pub struct Checkbox<V: 'static> { pub struct Checkbox<V: 'static> {
id: ElementId, id: ElementId,
checked: Selection, checked: Selection,
@ -19,6 +19,130 @@ pub struct Checkbox<V: 'static> {
on_click: Option<CheckHandler<V>>, on_click: Option<CheckHandler<V>>,
} }
impl<V: 'static> Component<V> for Checkbox<V> {
type Rendered = Stateful<V, Div<V>>;
fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
let group_id = format!("checkbox_group_{:?}", self.id);
let icon = match self.checked {
// When selected, we show a checkmark.
Selection::Selected => {
Some(
IconElement::new(Icon::Check)
.size(crate::IconSize::Small)
.color(
// If the checkbox is disabled we change the color of the icon.
if self.disabled {
TextColor::Disabled
} else {
TextColor::Selected
},
),
)
}
// In an indeterminate state, we show a dash.
Selection::Indeterminate => {
Some(
IconElement::new(Icon::Dash)
.size(crate::IconSize::Small)
.color(
// If the checkbox is disabled we change the color of the icon.
if self.disabled {
TextColor::Disabled
} else {
TextColor::Selected
},
),
)
}
// When unselected, we show nothing.
Selection::Unselected => None,
};
// A checkbox could be in an indeterminate state,
// for example the indeterminate state could represent:
// - a group of options of which only some are selected
// - an enabled option that is no longer available
// - a previously agreed to license that has been updated
//
// For the sake of styles we treat the indeterminate state as selected,
// but it's icon will be different.
let selected =
self.checked == Selection::Selected || self.checked == Selection::Indeterminate;
// We could use something like this to make the checkbox background when selected:
//
// ~~~rust
// ...
// .when(selected, |this| {
// this.bg(cx.theme().colors().element_selected)
// })
// ~~~
//
// But we use a match instead here because the checkbox might be disabled,
// and it could be disabled _while_ it is selected, as well as while it is not selected.
let (bg_color, border_color) = match (self.disabled, selected) {
(true, _) => (
cx.theme().colors().ghost_element_disabled,
cx.theme().colors().border_disabled,
),
(false, true) => (
cx.theme().colors().element_selected,
cx.theme().colors().border,
),
(false, false) => (
cx.theme().colors().element_background,
cx.theme().colors().border,
),
};
div()
.id(self.id)
// Rather than adding `px_1()` to add some space around the checkbox,
// we use a larger parent element to create a slightly larger
// click area for the checkbox.
.size_5()
// Because we've enlarged the click area, we need to create a
// `group` to pass down interactivity events to the checkbox.
.group(group_id.clone())
.child(
div()
.flex()
// This prevent the flex element from growing
// or shrinking in response to any size changes
.flex_none()
// The combo of `justify_center()` and `items_center()`
// is used frequently to center elements in a flex container.
//
// We use this to center the icon in the checkbox.
.justify_center()
.items_center()
.m_1()
.size_4()
.rounded_sm()
.bg(bg_color)
.border()
.border_color(border_color)
// We only want the interactivity states to fire when we
// are in a checkbox that isn't disabled.
.when(!self.disabled, |this| {
// Here instead of `hover()` we use `group_hover()`
// to pass it the group id.
this.group_hover(group_id.clone(), |el| {
el.bg(cx.theme().colors().element_hover)
})
})
.children(icon),
)
.when_some(
self.on_click.filter(|_| !self.disabled),
|this, on_click| {
this.on_click(move |view, _, cx| on_click(self.checked.inverse(), view, cx))
},
)
}
}
impl<V: 'static> Checkbox<V> { impl<V: 'static> Checkbox<V> {
pub fn new(id: impl Into<ElementId>, checked: Selection) -> Self { pub fn new(id: impl Into<ElementId>, checked: Selection) -> Self {
Self { Self {
@ -42,7 +166,7 @@ impl<V: 'static> Checkbox<V> {
self self
} }
pub fn render(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> { pub fn render(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Element<V> {
let group_id = format!("checkbox_group_{:?}", self.id); let group_id = format!("checkbox_group_{:?}", self.id);
let icon = match self.checked { let icon = match self.checked {
@ -175,7 +299,7 @@ mod stories {
pub struct CheckboxStory; pub struct CheckboxStory;
impl Render for CheckboxStory { impl Render<Self> for CheckboxStory {
type Element = Div<Self>; type Element = Div<Self>;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element { fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {

View file

@ -1,12 +1,12 @@
use std::cell::RefCell; use std::cell::RefCell;
use std::rc::Rc; use std::rc::Rc;
use crate::prelude::*; use crate::{prelude::*, v_stack, List, ListItem};
use crate::{v_stack, Label, List, ListEntry, ListItem, ListSeparator, ListSubHeader}; use crate::{ListEntry, ListSeparator, ListSubHeader};
use gpui::{ use gpui::{
overlay, px, Action, AnchorCorner, AnyElement, AppContext, Bounds, DispatchPhase, Div, overlay, px, Action, AnchorCorner, AnyElement, AppContext, Bounds, DispatchPhase, Div,
EventEmitter, FocusHandle, FocusableView, LayoutId, ManagedView, Manager, MouseButton, EventEmitter, FocusHandle, FocusableView, LayoutId, ManagedView, Manager, MouseButton,
MouseDownEvent, Pixels, Point, Render, View, VisualContext, WeakView, MouseDownEvent, Pixels, Point, Render, RenderOnce, View, VisualContext, WeakView,
}; };
pub enum ContextMenuItem<V> { pub enum ContextMenuItem<V> {
@ -24,15 +24,15 @@ pub struct ContextMenu<V> {
handle: WeakView<V>, handle: WeakView<V>,
} }
impl<V: Render> FocusableView for ContextMenu<V> { impl<V: 'static> FocusableView for ContextMenu<V> {
fn focus_handle(&self, _cx: &AppContext) -> FocusHandle { fn focus_handle(&self, _cx: &AppContext) -> FocusHandle {
self.focus_handle.clone() self.focus_handle.clone()
} }
} }
impl<V: Render> EventEmitter<Manager> for ContextMenu<V> {} impl<V: 'static> EventEmitter<Manager> for ContextMenu<V> {}
impl<V: Render> ContextMenu<V> { impl<V: 'static> ContextMenu<V> {
pub fn build( pub fn build(
cx: &mut ViewContext<V>, cx: &mut ViewContext<V>,
f: impl FnOnce(Self, &mut ViewContext<Self>) -> Self, f: impl FnOnce(Self, &mut ViewContext<Self>) -> Self,
@ -86,7 +86,7 @@ impl<V: Render> ContextMenu<V> {
} }
} }
impl<V: Render> Render for ContextMenu<V> { impl<V: 'static> Render<Self> for ContextMenu<V> {
type Element = Div<Self>; type Element = Div<Self>;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element { fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
@ -129,7 +129,7 @@ impl<V: Render> Render for ContextMenu<V> {
} }
pub struct MenuHandle<V: 'static, M: ManagedView> { pub struct MenuHandle<V: 'static, M: ManagedView> {
id: Option<ElementId>, id: ElementId,
child_builder: Option<Box<dyn FnOnce(bool) -> AnyElement<V> + 'static>>, child_builder: Option<Box<dyn FnOnce(bool) -> AnyElement<V> + 'static>>,
menu_builder: Option<Rc<dyn Fn(&mut V, &mut ViewContext<V>) -> View<M> + 'static>>, menu_builder: Option<Rc<dyn Fn(&mut V, &mut ViewContext<V>) -> View<M> + 'static>>,
@ -138,18 +138,13 @@ pub struct MenuHandle<V: 'static, M: ManagedView> {
} }
impl<V: 'static, M: ManagedView> MenuHandle<V, M> { impl<V: 'static, M: ManagedView> MenuHandle<V, M> {
pub fn id(mut self, id: impl Into<ElementId>) -> Self {
self.id = Some(id.into());
self
}
pub fn menu(mut self, f: impl Fn(&mut V, &mut ViewContext<V>) -> View<M> + 'static) -> Self { pub fn menu(mut self, f: impl Fn(&mut V, &mut ViewContext<V>) -> View<M> + 'static) -> Self {
self.menu_builder = Some(Rc::new(f)); self.menu_builder = Some(Rc::new(f));
self self
} }
pub fn child<R: Component<V>>(mut self, f: impl FnOnce(bool) -> R + 'static) -> Self { pub fn child<R: RenderOnce<V>>(mut self, f: impl FnOnce(bool) -> R + 'static) -> Self {
self.child_builder = Some(Box::new(|b| f(b).render())); self.child_builder = Some(Box::new(|b| f(b).render_once().into_any()));
self self
} }
@ -167,9 +162,9 @@ impl<V: 'static, M: ManagedView> MenuHandle<V, M> {
} }
} }
pub fn menu_handle<V: 'static, M: ManagedView>() -> MenuHandle<V, M> { pub fn menu_handle<V: 'static, M: ManagedView>(id: impl Into<ElementId>) -> MenuHandle<V, M> {
MenuHandle { MenuHandle {
id: None, id: id.into(),
child_builder: None, child_builder: None,
menu_builder: None, menu_builder: None,
anchor: None, anchor: None,
@ -185,18 +180,14 @@ pub struct MenuHandleState<V, M> {
menu_element: Option<AnyElement<V>>, menu_element: Option<AnyElement<V>>,
} }
impl<V: 'static, M: ManagedView> Element<V> for MenuHandle<V, M> { impl<V: 'static, M: ManagedView> Element<V> for MenuHandle<V, M> {
type ElementState = MenuHandleState<V, M>; type State = MenuHandleState<V, M>;
fn element_id(&self) -> Option<gpui::ElementId> {
Some(self.id.clone().expect("menu_handle must have an id()"))
}
fn layout( fn layout(
&mut self, &mut self,
view_state: &mut V, view_state: &mut V,
element_state: Option<Self::ElementState>, element_state: Option<Self::State>,
cx: &mut crate::ViewContext<V>, cx: &mut crate::ViewContext<V>,
) -> (gpui::LayoutId, Self::ElementState) { ) -> (gpui::LayoutId, Self::State) {
let (menu, position) = if let Some(element_state) = element_state { let (menu, position) = if let Some(element_state) = element_state {
(element_state.menu, element_state.position) (element_state.menu, element_state.position)
} else { } else {
@ -212,9 +203,9 @@ impl<V: 'static, M: ManagedView> Element<V> for MenuHandle<V, M> {
} }
overlay = overlay.position(*position.borrow()); overlay = overlay.position(*position.borrow());
let mut view = overlay.child(menu.clone()).render(); let mut element = overlay.child(menu.clone()).into_any();
menu_layout_id = Some(view.layout(view_state, cx)); menu_layout_id = Some(element.layout(view_state, cx));
view element
}); });
let mut child_element = self let mut child_element = self
@ -244,22 +235,22 @@ impl<V: 'static, M: ManagedView> Element<V> for MenuHandle<V, M> {
} }
fn paint( fn paint(
&mut self, self,
bounds: Bounds<gpui::Pixels>, bounds: Bounds<gpui::Pixels>,
view_state: &mut V, view_state: &mut V,
element_state: &mut Self::ElementState, element_state: &mut Self::State,
cx: &mut crate::ViewContext<V>, cx: &mut crate::ViewContext<V>,
) { ) {
if let Some(child) = element_state.child_element.as_mut() { if let Some(child) = element_state.child_element.take() {
child.paint(view_state, cx); child.paint(view_state, cx);
} }
if let Some(menu) = element_state.menu_element.as_mut() { if let Some(menu) = element_state.menu_element.take() {
menu.paint(view_state, cx); menu.paint(view_state, cx);
return; return;
} }
let Some(builder) = self.menu_builder.clone() else { let Some(builder) = self.menu_builder else {
return; return;
}; };
let menu = element_state.menu.clone(); let menu = element_state.menu.clone();
@ -300,9 +291,15 @@ impl<V: 'static, M: ManagedView> Element<V> for MenuHandle<V, M> {
} }
} }
impl<V: 'static, M: ManagedView> Component<V> for MenuHandle<V, M> { impl<V: 'static, M: ManagedView> RenderOnce<V> for MenuHandle<V, M> {
fn render(self) -> AnyElement<V> { type Element = Self;
AnyElement::new(self)
fn element_id(&self) -> Option<gpui::ElementId> {
Some(self.id.clone())
}
fn render_once(self) -> Self::Element {
self
} }
} }
@ -312,12 +309,12 @@ pub use stories::*;
#[cfg(feature = "stories")] #[cfg(feature = "stories")]
mod stories { mod stories {
use super::*; use super::*;
use crate::story::Story; use crate::{story::Story, Label};
use gpui::{actions, Div, Render}; use gpui::{actions, Div, Render};
actions!(PrintCurrentDate, PrintBestFood); actions!(PrintCurrentDate, PrintBestFood);
fn build_menu<V: Render>( fn build_menu<V: Render<V>>(
cx: &mut ViewContext<V>, cx: &mut ViewContext<V>,
header: impl Into<SharedString>, header: impl Into<SharedString>,
) -> View<ContextMenu<V>> { ) -> View<ContextMenu<V>> {
@ -337,7 +334,7 @@ mod stories {
pub struct ContextMenuStory; pub struct ContextMenuStory;
impl Render for ContextMenuStory { impl Render<Self> for ContextMenuStory {
type Element = Div<Self>; type Element = Div<Self>;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element { fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
@ -360,28 +357,24 @@ mod stories {
.flex_col() .flex_col()
.justify_between() .justify_between()
.child( .child(
menu_handle() menu_handle("test2")
.id("test2")
.child(|is_open| { .child(|is_open| {
Label::new(if is_open { Label::new(if is_open {
"TOP LEFT" "TOP LEFT"
} else { } else {
"RIGHT CLICK ME" "RIGHT CLICK ME"
}) })
.render()
}) })
.menu(move |_, cx| build_menu(cx, "top left")), .menu(move |_, cx| build_menu(cx, "top left")),
) )
.child( .child(
menu_handle() menu_handle("test1")
.id("test1")
.child(|is_open| { .child(|is_open| {
Label::new(if is_open { Label::new(if is_open {
"BOTTOM LEFT" "BOTTOM LEFT"
} else { } else {
"RIGHT CLICK ME" "RIGHT CLICK ME"
}) })
.render()
}) })
.anchor(AnchorCorner::BottomLeft) .anchor(AnchorCorner::BottomLeft)
.attach(AnchorCorner::TopLeft) .attach(AnchorCorner::TopLeft)
@ -394,29 +387,25 @@ mod stories {
.flex_col() .flex_col()
.justify_between() .justify_between()
.child( .child(
menu_handle() menu_handle("test3")
.id("test3")
.child(|is_open| { .child(|is_open| {
Label::new(if is_open { Label::new(if is_open {
"TOP RIGHT" "TOP RIGHT"
} else { } else {
"RIGHT CLICK ME" "RIGHT CLICK ME"
}) })
.render()
}) })
.anchor(AnchorCorner::TopRight) .anchor(AnchorCorner::TopRight)
.menu(move |_, cx| build_menu(cx, "top right")), .menu(move |_, cx| build_menu(cx, "top right")),
) )
.child( .child(
menu_handle() menu_handle("test4")
.id("test4")
.child(|is_open| { .child(|is_open| {
Label::new(if is_open { Label::new(if is_open {
"BOTTOM RIGHT" "BOTTOM RIGHT"
} else { } else {
"RIGHT CLICK ME" "RIGHT CLICK ME"
}) })
.render()
}) })
.anchor(AnchorCorner::BottomRight) .anchor(AnchorCorner::BottomRight)
.attach(AnchorCorner::TopRight) .attach(AnchorCorner::TopRight)

View file

@ -1,13 +1,29 @@
use crate::prelude::*; use crate::prelude::*;
use crate::{v_stack, ButtonGroup}; use crate::{v_stack, ButtonGroup};
#[derive(Component)] #[derive(RenderOnce)]
pub struct Details<V: 'static> { pub struct Details<V: 'static> {
text: &'static str, text: &'static str,
meta: Option<&'static str>, meta: Option<&'static str>,
actions: Option<ButtonGroup<V>>, actions: Option<ButtonGroup<V>>,
} }
impl<V: 'static> Component<V> for Details<V> {
type Rendered = Div<V>;
fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
v_stack()
.p_1()
.gap_0p5()
.text_ui_sm()
.text_color(cx.theme().colors().text)
.size_full()
.child(self.text)
.children(self.meta.map(|m| m))
.children(self.actions.map(|a| a))
}
}
impl<V: 'static> Details<V> { impl<V: 'static> Details<V> {
pub fn new(text: &'static str) -> Self { pub fn new(text: &'static str) -> Self {
Self { Self {
@ -26,20 +42,9 @@ impl<V: 'static> Details<V> {
self.actions = Some(actions); self.actions = Some(actions);
self self
} }
fn render(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
v_stack()
.p_1()
.gap_0p5()
.text_ui_sm()
.text_color(cx.theme().colors().text)
.size_full()
.child(self.text)
.children(self.meta.map(|m| m))
.children(self.actions.map(|a| a))
}
} }
use gpui::{Div, RenderOnce};
#[cfg(feature = "stories")] #[cfg(feature = "stories")]
pub use stories::*; pub use stories::*;
@ -51,7 +56,7 @@ mod stories {
pub struct DetailsStory; pub struct DetailsStory;
impl Render for DetailsStory { impl Render<Self> for DetailsStory {
type Element = Div<Self>; type Element = Div<Self>;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element { fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {

View file

@ -1,3 +1,5 @@
use gpui::{Div, RenderOnce};
use crate::prelude::*; use crate::prelude::*;
enum DividerDirection { enum DividerDirection {
@ -5,12 +7,29 @@ enum DividerDirection {
Vertical, Vertical,
} }
#[derive(Component)] #[derive(RenderOnce)]
pub struct Divider { pub struct Divider {
direction: DividerDirection, direction: DividerDirection,
inset: bool, inset: bool,
} }
impl<V: 'static> Component<V> for Divider {
type Rendered = Div<V>;
fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
div()
.map(|this| match self.direction {
DividerDirection::Horizontal => {
this.h_px().w_full().when(self.inset, |this| this.mx_1p5())
}
DividerDirection::Vertical => {
this.w_px().h_full().when(self.inset, |this| this.my_1p5())
}
})
.bg(cx.theme().colors().border_variant)
}
}
impl Divider { impl Divider {
pub fn horizontal() -> Self { pub fn horizontal() -> Self {
Self { Self {
@ -31,7 +50,7 @@ impl Divider {
self self
} }
fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> { fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Element<V> {
div() div()
.map(|this| match self.direction { .map(|this| match self.direction {
DividerDirection::Horizontal => { DividerDirection::Horizontal => {

View file

@ -1,19 +1,15 @@
use crate::prelude::*; use crate::prelude::*;
use crate::{Avatar, Player}; use crate::{Avatar, Player};
#[derive(Component)] #[derive(RenderOnce)]
pub struct Facepile { pub struct Facepile {
players: Vec<Player>, players: Vec<Player>,
} }
impl Facepile { impl<V: 'static> Component<V> for Facepile {
pub fn new<P: Iterator<Item = Player>>(players: P) -> Self { type Rendered = Div<V>;
Self {
players: players.collect(),
}
}
fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> { fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
let player_count = self.players.len(); let player_count = self.players.len();
let player_list = self.players.iter().enumerate().map(|(ix, player)| { let player_list = self.players.iter().enumerate().map(|(ix, player)| {
let isnt_last = ix < player_count - 1; let isnt_last = ix < player_count - 1;
@ -26,6 +22,15 @@ impl Facepile {
} }
} }
impl Facepile {
pub fn new<P: Iterator<Item = Player>>(players: P) -> Self {
Self {
players: players.collect(),
}
}
}
use gpui::{Div, RenderOnce};
#[cfg(feature = "stories")] #[cfg(feature = "stories")]
pub use stories::*; pub use stories::*;
@ -37,7 +42,7 @@ mod stories {
pub struct FacepileStory; pub struct FacepileStory;
impl Render for FacepileStory { impl Render<Self> for FacepileStory {
type Element = Div<Self>; type Element = Div<Self>;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element { fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {

View file

@ -1,4 +1,4 @@
use gpui::{rems, svg}; use gpui::{rems, svg, RenderOnce, Svg};
use strum::EnumIter; use strum::EnumIter;
use crate::prelude::*; use crate::prelude::*;
@ -129,13 +129,30 @@ impl Icon {
} }
} }
#[derive(Component)] #[derive(RenderOnce)]
pub struct IconElement { pub struct IconElement {
path: SharedString, path: SharedString,
color: TextColor, color: TextColor,
size: IconSize, size: IconSize,
} }
impl<V: 'static> Component<V> for IconElement {
type Rendered = Svg<V>;
fn render(self, _view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
let svg_size = match self.size {
IconSize::Small => rems(0.75),
IconSize::Medium => rems(0.9375),
};
svg()
.size(svg_size)
.flex_none()
.path(self.path)
.text_color(self.color.color(cx))
}
}
impl IconElement { impl IconElement {
pub fn new(icon: Icon) -> Self { pub fn new(icon: Icon) -> Self {
Self { Self {
@ -163,7 +180,7 @@ impl IconElement {
self self
} }
fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> { fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Element<V> {
let svg_size = match self.size { let svg_size = match self.size {
IconSize::Small => rems(0.75), IconSize::Small => rems(0.75),
IconSize::Medium => rems(0.9375), IconSize::Medium => rems(0.9375),
@ -191,7 +208,7 @@ mod stories {
pub struct IconStory; pub struct IconStory;
impl Render for IconStory { impl Render<Self> for IconStory {
type Element = Div<Self>; type Element = Div<Self>;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element { fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {

View file

@ -1,5 +1,5 @@
use crate::{h_stack, prelude::*, ClickHandler, Icon, IconElement}; use crate::{h_stack, prelude::*, ClickHandler, Icon, IconElement};
use gpui::{prelude::*, Action, AnyView, MouseButton}; use gpui::{prelude::*, Action, AnyView, Div, MouseButton, Stateful};
use std::sync::Arc; use std::sync::Arc;
struct IconButtonHandlers<V: 'static> { struct IconButtonHandlers<V: 'static> {
@ -12,7 +12,7 @@ impl<V: 'static> Default for IconButtonHandlers<V> {
} }
} }
#[derive(Component)] #[derive(RenderOnce)]
pub struct IconButton<V: 'static> { pub struct IconButton<V: 'static> {
id: ElementId, id: ElementId,
icon: Icon, icon: Icon,
@ -24,6 +24,64 @@ pub struct IconButton<V: 'static> {
handlers: IconButtonHandlers<V>, handlers: IconButtonHandlers<V>,
} }
impl<V: 'static> Component<V> for IconButton<V> {
type Rendered = Stateful<V, Div<V>>;
fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
let icon_color = match (self.state, self.color) {
(InteractionState::Disabled, _) => TextColor::Disabled,
(InteractionState::Active, _) => TextColor::Selected,
_ => self.color,
};
let (mut bg_color, bg_hover_color, bg_active_color) = match self.variant {
ButtonVariant::Filled => (
cx.theme().colors().element_background,
cx.theme().colors().element_hover,
cx.theme().colors().element_active,
),
ButtonVariant::Ghost => (
cx.theme().colors().ghost_element_background,
cx.theme().colors().ghost_element_hover,
cx.theme().colors().ghost_element_active,
),
};
if self.selected {
bg_color = bg_hover_color;
}
let mut button = h_stack()
.id(self.id.clone())
.justify_center()
.rounded_md()
.p_1()
.bg(bg_color)
.cursor_pointer()
// Nate: Trying to figure out the right places we want to show a
// hover state here. I think it is a bit heavy to have it on every
// place we use an icon button.
// .hover(|style| style.bg(bg_hover_color))
.active(|style| style.bg(bg_active_color))
.child(IconElement::new(self.icon).color(icon_color));
if let Some(click_handler) = self.handlers.click.clone() {
button = button.on_mouse_down(MouseButton::Left, move |state, event, cx| {
cx.stop_propagation();
click_handler(state, cx);
})
}
if let Some(tooltip) = self.tooltip {
if !self.selected {
button = button.tooltip(move |view: &mut V, cx| (tooltip)(view, cx))
}
}
button
}
}
impl<V: 'static> IconButton<V> { impl<V: 'static> IconButton<V> {
pub fn new(id: impl Into<ElementId>, icon: Icon) -> Self { pub fn new(id: impl Into<ElementId>, icon: Icon) -> Self {
Self { Self {
@ -79,58 +137,4 @@ impl<V: 'static> IconButton<V> {
pub fn action(self, action: Box<dyn Action>) -> Self { pub fn action(self, action: Box<dyn Action>) -> Self {
self.on_click(move |this, cx| cx.dispatch_action(action.boxed_clone())) self.on_click(move |this, cx| cx.dispatch_action(action.boxed_clone()))
} }
fn render(mut self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
let icon_color = match (self.state, self.color) {
(InteractionState::Disabled, _) => TextColor::Disabled,
(InteractionState::Active, _) => TextColor::Selected,
_ => self.color,
};
let (mut bg_color, bg_hover_color, bg_active_color) = match self.variant {
ButtonVariant::Filled => (
cx.theme().colors().element_background,
cx.theme().colors().element_hover,
cx.theme().colors().element_active,
),
ButtonVariant::Ghost => (
cx.theme().colors().ghost_element_background,
cx.theme().colors().ghost_element_hover,
cx.theme().colors().ghost_element_active,
),
};
if self.selected {
bg_color = bg_hover_color;
}
let mut button = h_stack()
.id(self.id.clone())
.justify_center()
.rounded_md()
.p_1()
.bg(bg_color)
.cursor_pointer()
// Nate: Trying to figure out the right places we want to show a
// hover state here. I think it is a bit heavy to have it on every
// place we use an icon button.
// .hover(|style| style.bg(bg_hover_color))
.active(|style| style.bg(bg_active_color))
.child(IconElement::new(self.icon).color(icon_color));
if let Some(click_handler) = self.handlers.click.clone() {
button = button.on_mouse_down(MouseButton::Left, move |state, event, cx| {
cx.stop_propagation();
click_handler(state, cx);
})
}
if let Some(tooltip) = self.tooltip.take() {
if !self.selected {
button = button.tooltip(move |view: &mut V, cx| (tooltip)(view, cx))
}
}
button
}
} }

View file

@ -1,16 +1,30 @@
use gpui::px;
use crate::prelude::*; use crate::prelude::*;
use gpui::{px, Div, RenderOnce};
#[derive(Component)] #[derive(RenderOnce)]
pub struct UnreadIndicator; pub struct UnreadIndicator;
impl UnreadIndicator { impl<V: 'static> Component<V> for UnreadIndicator {
pub fn new() -> Self { type Rendered = Div<V>;
Self
}
fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> { fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
div()
.rounded_full()
.border_2()
.border_color(cx.theme().colors().surface_background)
.w(px(9.0))
.h(px(9.0))
.z_index(2)
.bg(cx.theme().status().info)
}
}
impl UnreadIndicator {
pub fn new() -> Self {
Self
}
fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Element<V> {
div() div()
.rounded_full() .rounded_full()
.border_2() .border_2()

View file

@ -1,5 +1,5 @@
use crate::{prelude::*, Label}; use crate::{prelude::*, Label};
use gpui::prelude::*; use gpui::{prelude::*, Div, RenderOnce, Stateful};
#[derive(Default, PartialEq)] #[derive(Default, PartialEq)]
pub enum InputVariant { pub enum InputVariant {
@ -8,7 +8,7 @@ pub enum InputVariant {
Filled, Filled,
} }
#[derive(Component)] #[derive(RenderOnce)]
pub struct Input { pub struct Input {
placeholder: SharedString, placeholder: SharedString,
value: String, value: String,
@ -18,6 +18,57 @@ pub struct Input {
is_active: bool, is_active: bool,
} }
impl<V: 'static> Component<V> for Input {
type Rendered = Stateful<V, Div<V>>;
fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
let (input_bg, input_hover_bg, input_active_bg) = match self.variant {
InputVariant::Ghost => (
cx.theme().colors().ghost_element_background,
cx.theme().colors().ghost_element_hover,
cx.theme().colors().ghost_element_active,
),
InputVariant::Filled => (
cx.theme().colors().element_background,
cx.theme().colors().element_hover,
cx.theme().colors().element_active,
),
};
let placeholder_label = Label::new(self.placeholder.clone()).color(if self.disabled {
TextColor::Disabled
} else {
TextColor::Placeholder
});
let label = Label::new(self.value.clone()).color(if self.disabled {
TextColor::Disabled
} else {
TextColor::Default
});
div()
.id("input")
.h_7()
.w_full()
.px_2()
.border()
.border_color(cx.theme().styles.system.transparent)
.bg(input_bg)
.hover(|style| style.bg(input_hover_bg))
.active(|style| style.bg(input_active_bg))
.flex()
.items_center()
.child(div().flex().items_center().text_ui_sm().map(move |this| {
if self.value.is_empty() {
this.child(placeholder_label)
} else {
this.child(label)
}
}))
}
}
impl Input { impl Input {
pub fn new(placeholder: impl Into<SharedString>) -> Self { pub fn new(placeholder: impl Into<SharedString>) -> Self {
Self { Self {
@ -54,53 +105,6 @@ impl Input {
self.is_active = is_active; self.is_active = is_active;
self self
} }
fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
let (input_bg, input_hover_bg, input_active_bg) = match self.variant {
InputVariant::Ghost => (
cx.theme().colors().ghost_element_background,
cx.theme().colors().ghost_element_hover,
cx.theme().colors().ghost_element_active,
),
InputVariant::Filled => (
cx.theme().colors().element_background,
cx.theme().colors().element_hover,
cx.theme().colors().element_active,
),
};
let placeholder_label = Label::new(self.placeholder.clone()).color(if self.disabled {
TextColor::Disabled
} else {
TextColor::Placeholder
});
let label = Label::new(self.value.clone()).color(if self.disabled {
TextColor::Disabled
} else {
TextColor::Default
});
div()
.id("input")
.h_7()
.w_full()
.px_2()
.border()
.border_color(cx.theme().styles.system.transparent)
.bg(input_bg)
.hover(|style| style.bg(input_hover_bg))
.active(|style| style.bg(input_active_bg))
.flex()
.items_center()
.child(div().flex().items_center().text_ui_sm().map(|this| {
if self.value.is_empty() {
this.child(placeholder_label)
} else {
this.child(label)
}
}))
}
} }
#[cfg(feature = "stories")] #[cfg(feature = "stories")]
@ -114,7 +118,7 @@ mod stories {
pub struct InputStory; pub struct InputStory;
impl Render for InputStory { impl Render<Self> for InputStory {
type Element = Div<Self>; type Element = Div<Self>;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element { fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {

View file

@ -1,9 +1,7 @@
use gpui::{actions, Action};
use strum::EnumIter;
use crate::prelude::*; use crate::prelude::*;
use gpui::{Action, Div, RenderOnce};
#[derive(Component, Clone)] #[derive(RenderOnce, Clone)]
pub struct KeyBinding { pub struct KeyBinding {
/// A keybinding consists of a key and a set of modifier keys. /// A keybinding consists of a key and a set of modifier keys.
/// More then one keybinding produces a chord. /// More then one keybinding produces a chord.
@ -12,19 +10,10 @@ pub struct KeyBinding {
key_binding: gpui::KeyBinding, key_binding: gpui::KeyBinding,
} }
impl KeyBinding { impl<V: 'static> Component<V> for KeyBinding {
pub fn for_action(action: &dyn Action, cx: &mut WindowContext) -> Option<Self> { type Rendered = Div<V>;
// todo! this last is arbitrary, we want to prefer users key bindings over defaults,
// and vim over normal (in vim mode), etc.
let key_binding = cx.bindings_for_action(action).last().cloned()?;
Some(Self::new(key_binding))
}
pub fn new(key_binding: gpui::KeyBinding) -> Self { fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
Self { key_binding }
}
fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
div() div()
.flex() .flex()
.gap_2() .gap_2()
@ -42,17 +31,29 @@ impl KeyBinding {
} }
} }
#[derive(Component)] impl KeyBinding {
pub fn for_action(action: &dyn Action, cx: &mut WindowContext) -> Option<Self> {
// todo! this last is arbitrary, we want to prefer users key bindings over defaults,
// and vim over normal (in vim mode), etc.
let key_binding = cx.bindings_for_action(action).last().cloned()?;
Some(Self::new(key_binding))
}
pub fn new(key_binding: gpui::KeyBinding) -> Self {
Self { key_binding }
}
}
#[derive(RenderOnce)]
pub struct Key { pub struct Key {
key: SharedString, key: SharedString,
} }
impl Key { impl<V: 'static> Component<V> for Key {
pub fn new(key: impl Into<SharedString>) -> Self { type Rendered = Div<V>;
Self { key: key.into() }
}
fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> { fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
let _view: &mut V = view;
div() div()
.px_2() .px_2()
.py_0() .py_0()
@ -64,20 +65,10 @@ impl Key {
} }
} }
// NOTE: The order the modifier keys appear in this enum impacts the order in impl Key {
// which they are rendered in the UI. pub fn new(key: impl Into<SharedString>) -> Self {
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, EnumIter)] Self { key: key.into() }
pub enum ModifierKey { }
Control,
Alt,
Command,
Shift,
}
actions!(NoAction);
pub fn binding(key: &str) -> gpui::KeyBinding {
gpui::KeyBinding::new(key, NoAction {}, None)
} }
#[cfg(feature = "stories")] #[cfg(feature = "stories")]
@ -87,12 +78,18 @@ pub use stories::*;
mod stories { mod stories {
use super::*; use super::*;
pub use crate::KeyBinding; pub use crate::KeyBinding;
use crate::{binding, Story}; use crate::Story;
use gpui::{Div, Render}; use gpui::{actions, Div, Render};
use itertools::Itertools; use itertools::Itertools;
pub struct KeybindingStory; pub struct KeybindingStory;
impl Render for KeybindingStory { actions!(NoAction);
pub fn binding(key: &str) -> gpui::KeyBinding {
gpui::KeyBinding::new(key, NoAction {}, None)
}
impl Render<Self> for KeybindingStory {
type Element = Div<Self>; type Element = Div<Self>;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element { fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {

View file

@ -1,7 +1,6 @@
use gpui::{relative, Hsla, Text, TextRun, WindowContext};
use crate::prelude::*; use crate::prelude::*;
use crate::styled_ext::StyledExt; use crate::styled_ext::StyledExt;
use gpui::{relative, Div, Hsla, RenderOnce, StyledText, TextRun, WindowContext};
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, Default)] #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, Default)]
pub enum LabelSize { pub enum LabelSize {
@ -60,7 +59,7 @@ pub enum LineHeightStyle {
UILabel, UILabel,
} }
#[derive(Clone, Component)] #[derive(Clone, RenderOnce)]
pub struct Label { pub struct Label {
label: SharedString, label: SharedString,
size: LabelSize, size: LabelSize,
@ -69,6 +68,33 @@ pub struct Label {
strikethrough: bool, strikethrough: bool,
} }
impl<V: 'static> Component<V> for Label {
type Rendered = Div<V>;
fn render(self, _view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
div()
.when(self.strikethrough, |this| {
this.relative().child(
div()
.absolute()
.top_1_2()
.w_full()
.h_px()
.bg(TextColor::Hidden.color(cx)),
)
})
.map(|this| match self.size {
LabelSize::Default => this.text_ui(),
LabelSize::Small => this.text_ui_sm(),
})
.when(self.line_height_style == LineHeightStyle::UILabel, |this| {
this.line_height(relative(1.))
})
.text_color(self.color.color(cx))
.child(self.label.clone())
}
}
impl Label { impl Label {
pub fn new(label: impl Into<SharedString>) -> Self { pub fn new(label: impl Into<SharedString>) -> Self {
Self { Self {
@ -99,32 +125,9 @@ impl Label {
self.strikethrough = strikethrough; self.strikethrough = strikethrough;
self self
} }
fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
div()
.when(self.strikethrough, |this| {
this.relative().child(
div()
.absolute()
.top_1_2()
.w_full()
.h_px()
.bg(TextColor::Hidden.color(cx)),
)
})
.map(|this| match self.size {
LabelSize::Default => this.text_ui(),
LabelSize::Small => this.text_ui_sm(),
})
.when(self.line_height_style == LineHeightStyle::UILabel, |this| {
this.line_height(relative(1.))
})
.text_color(self.color.color(cx))
.child(self.label.clone())
}
} }
#[derive(Component)] #[derive(RenderOnce)]
pub struct HighlightedLabel { pub struct HighlightedLabel {
label: SharedString, label: SharedString,
size: LabelSize, size: LabelSize,
@ -133,35 +136,10 @@ pub struct HighlightedLabel {
strikethrough: bool, strikethrough: bool,
} }
impl HighlightedLabel { impl<V: 'static> Component<V> for HighlightedLabel {
/// shows a label with the given characters highlighted. type Rendered = Div<V>;
/// characters are identified by utf8 byte position.
pub fn new(label: impl Into<SharedString>, highlight_indices: Vec<usize>) -> Self {
Self {
label: label.into(),
size: LabelSize::Default,
color: TextColor::Default,
highlight_indices,
strikethrough: false,
}
}
pub fn size(mut self, size: LabelSize) -> Self { fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
self.size = size;
self
}
pub fn color(mut self, color: TextColor) -> Self {
self.color = color;
self
}
pub fn set_strikethrough(mut self, strikethrough: bool) -> Self {
self.strikethrough = strikethrough;
self
}
fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
let highlight_color = cx.theme().colors().text_accent; let highlight_color = cx.theme().colors().text_accent;
let mut text_style = cx.text_style().clone(); let mut text_style = cx.text_style().clone();
@ -214,7 +192,36 @@ impl HighlightedLabel {
LabelSize::Default => this.text_ui(), LabelSize::Default => this.text_ui(),
LabelSize::Small => this.text_ui_sm(), LabelSize::Small => this.text_ui_sm(),
}) })
.child(Text::styled(self.label, runs)) .child(StyledText::new(self.label, runs))
}
}
impl HighlightedLabel {
/// shows a label with the given characters highlighted.
/// characters are identified by utf8 byte position.
pub fn new(label: impl Into<SharedString>, highlight_indices: Vec<usize>) -> Self {
Self {
label: label.into(),
size: LabelSize::Default,
color: TextColor::Default,
highlight_indices,
strikethrough: false,
}
}
pub fn size(mut self, size: LabelSize) -> Self {
self.size = size;
self
}
pub fn color(mut self, color: TextColor) -> Self {
self.color = color;
self
}
pub fn set_strikethrough(mut self, strikethrough: bool) -> Self {
self.strikethrough = strikethrough;
self
} }
} }
@ -235,7 +242,7 @@ mod stories {
pub struct LabelStory; pub struct LabelStory;
impl Render for LabelStory { impl Render<Self> for LabelStory {
type Element = Div<Self>; type Element = Div<Self>;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element { fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {

View file

@ -1,7 +1,6 @@
use gpui::{div, Div, RenderOnce, Stateful, StatefulInteractiveElement};
use std::rc::Rc; use std::rc::Rc;
use gpui::{div, Div, Stateful, StatefulInteractiveComponent};
use crate::settings::user_settings; use crate::settings::user_settings;
use crate::{ use crate::{
disclosure_control, h_stack, v_stack, Avatar, Icon, IconElement, IconSize, Label, Toggle, disclosure_control, h_stack, v_stack, Avatar, Icon, IconElement, IconSize, Label, Toggle,
@ -24,7 +23,7 @@ pub enum ListHeaderMeta {
Text(Label), Text(Label),
} }
#[derive(Component)] #[derive(RenderOnce)]
pub struct ListHeader { pub struct ListHeader {
label: SharedString, label: SharedString,
left_icon: Option<Icon>, left_icon: Option<Icon>,
@ -33,33 +32,10 @@ pub struct ListHeader {
toggle: Toggle, toggle: Toggle,
} }
impl ListHeader { impl<V: 'static> Component<V> for ListHeader {
pub fn new(label: impl Into<SharedString>) -> Self { type Rendered = Div<V>;
Self {
label: label.into(),
left_icon: None,
meta: None,
variant: ListItemVariant::default(),
toggle: Toggle::NotToggleable,
}
}
pub fn toggle(mut self, toggle: Toggle) -> Self { fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
self.toggle = toggle;
self
}
pub fn left_icon(mut self, left_icon: Option<Icon>) -> Self {
self.left_icon = left_icon;
self
}
pub fn meta(mut self, meta: Option<ListHeaderMeta>) -> Self {
self.meta = meta;
self
}
fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
let disclosure_control = disclosure_control(self.toggle); let disclosure_control = disclosure_control(self.toggle);
let meta = match self.meta { let meta = match self.meta {
@ -81,11 +57,6 @@ impl ListHeader {
h_stack() h_stack()
.w_full() .w_full()
.bg(cx.theme().colors().surface_background) .bg(cx.theme().colors().surface_background)
// TODO: Add focus state
// .when(self.state == InteractionState::Focused, |this| {
// this.border()
// .border_color(cx.theme().colors().border_focused)
// })
.relative() .relative()
.child( .child(
div() div()
@ -119,7 +90,94 @@ impl ListHeader {
} }
} }
#[derive(Component, Clone)] impl ListHeader {
pub fn new(label: impl Into<SharedString>) -> Self {
Self {
label: label.into(),
left_icon: None,
meta: None,
variant: ListItemVariant::default(),
toggle: Toggle::NotToggleable,
}
}
pub fn toggle(mut self, toggle: Toggle) -> Self {
self.toggle = toggle;
self
}
pub fn left_icon(mut self, left_icon: Option<Icon>) -> Self {
self.left_icon = left_icon;
self
}
pub fn meta(mut self, meta: Option<ListHeaderMeta>) -> Self {
self.meta = meta;
self
}
// before_ship!("delete")
// fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Element<V> {
// let disclosure_control = disclosure_control(self.toggle);
// let meta = match self.meta {
// Some(ListHeaderMeta::Tools(icons)) => div().child(
// h_stack()
// .gap_2()
// .items_center()
// .children(icons.into_iter().map(|i| {
// IconElement::new(i)
// .color(TextColor::Muted)
// .size(IconSize::Small)
// })),
// ),
// Some(ListHeaderMeta::Button(label)) => div().child(label),
// Some(ListHeaderMeta::Text(label)) => div().child(label),
// None => div(),
// };
// h_stack()
// .w_full()
// .bg(cx.theme().colors().surface_background)
// // TODO: Add focus state
// // .when(self.state == InteractionState::Focused, |this| {
// // this.border()
// // .border_color(cx.theme().colors().border_focused)
// // })
// .relative()
// .child(
// div()
// .h_5()
// .when(self.variant == ListItemVariant::Inset, |this| this.px_2())
// .flex()
// .flex_1()
// .items_center()
// .justify_between()
// .w_full()
// .gap_1()
// .child(
// h_stack()
// .gap_1()
// .child(
// div()
// .flex()
// .gap_1()
// .items_center()
// .children(self.left_icon.map(|i| {
// IconElement::new(i)
// .color(TextColor::Muted)
// .size(IconSize::Small)
// }))
// .child(Label::new(self.label.clone()).color(TextColor::Muted)),
// )
// .child(disclosure_control),
// )
// .child(meta),
// )
// }
}
#[derive(Clone)]
pub struct ListSubHeader { pub struct ListSubHeader {
label: SharedString, label: SharedString,
left_icon: Option<Icon>, left_icon: Option<Icon>,
@ -140,7 +198,7 @@ impl ListSubHeader {
self self
} }
fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> { fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Element<V> {
h_stack().flex_1().w_full().relative().py_1().child( h_stack().flex_1().w_full().relative().py_1().child(
div() div()
.h_6() .h_6()
@ -200,14 +258,6 @@ impl<V: 'static> From<ListSubHeader> for ListItem<V> {
} }
impl<V: 'static> ListItem<V> { impl<V: 'static> ListItem<V> {
fn render(self, view: &mut V, ix: usize, cx: &mut ViewContext<V>) -> impl Component<V> {
match self {
ListItem::Entry(entry) => div().child(entry.render(ix, cx)),
ListItem::Separator(separator) => div().child(separator.render(view, cx)),
ListItem::Header(header) => div().child(header.render(view, cx)),
}
}
pub fn new(label: Label) -> Self { pub fn new(label: Label) -> Self {
Self::Entry(ListEntry::new(label)) Self::Entry(ListEntry::new(label))
} }
@ -219,8 +269,17 @@ impl<V: 'static> ListItem<V> {
None None
} }
} }
fn render(self, view: &mut V, ix: usize, cx: &mut ViewContext<V>) -> Div<V> {
match self {
ListItem::Entry(entry) => div().child(entry.render(ix, cx)),
ListItem::Separator(separator) => div().child(separator.render(view, cx)),
ListItem::Header(header) => div().child(header.render(view, cx)),
}
}
} }
// #[derive(RenderOnce)]
pub struct ListEntry<V> { pub struct ListEntry<V> {
disabled: bool, disabled: bool,
// TODO: Reintroduce this // TODO: Reintroduce this
@ -376,20 +435,24 @@ impl<V: 'static> ListEntry<V> {
} }
} }
#[derive(Clone, Component)] #[derive(RenderOnce, Clone)]
pub struct ListSeparator; pub struct ListSeparator;
impl ListSeparator { impl ListSeparator {
pub fn new() -> Self { pub fn new() -> Self {
Self Self
} }
}
fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> { impl<V: 'static> Component<V> for ListSeparator {
type Rendered = Div<V>;
fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
div().h_px().w_full().bg(cx.theme().colors().border_variant) div().h_px().w_full().bg(cx.theme().colors().border_variant)
} }
} }
#[derive(Component)] #[derive(RenderOnce)]
pub struct List<V: 'static> { pub struct List<V: 'static> {
items: Vec<ListItem<V>>, items: Vec<ListItem<V>>,
/// Message to display when the list is empty /// Message to display when the list is empty
@ -399,6 +462,31 @@ pub struct List<V: 'static> {
toggle: Toggle, toggle: Toggle,
} }
impl<V: 'static> Component<V> for List<V> {
type Rendered = Div<V>;
fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
let list_content = match (self.items.is_empty(), self.toggle) {
(false, _) => div().children(
self.items
.into_iter()
.enumerate()
.map(|(ix, item)| item.render(view, ix, cx)),
),
(true, Toggle::Toggled(false)) => div(),
(true, _) => {
div().child(Label::new(self.empty_message.clone()).color(TextColor::Muted))
}
};
v_stack()
.w_full()
.py_1()
.children(self.header.map(|header| header))
.child(list_content)
}
}
impl<V: 'static> List<V> { impl<V: 'static> List<V> {
pub fn new(items: Vec<ListItem<V>>) -> Self { pub fn new(items: Vec<ListItem<V>>) -> Self {
Self { Self {
@ -424,7 +512,7 @@ impl<V: 'static> List<V> {
self self
} }
fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> { fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> impl Element<V> {
let list_content = match (self.items.is_empty(), self.toggle) { let list_content = match (self.items.is_empty(), self.toggle) {
(false, _) => div().children( (false, _) => div().children(
self.items self.items

View file

@ -1,9 +1,9 @@
use gpui::AnyElement; use gpui::{AnyElement, Div, RenderOnce, Stateful};
use smallvec::SmallVec; use smallvec::SmallVec;
use crate::{h_stack, prelude::*, v_stack, Button, Icon, IconButton, Label}; use crate::{h_stack, prelude::*, v_stack, Button, Icon, IconButton, Label};
#[derive(Component)] #[derive(RenderOnce)]
pub struct Modal<V: 'static> { pub struct Modal<V: 'static> {
id: ElementId, id: ElementId,
title: Option<SharedString>, title: Option<SharedString>,
@ -12,33 +12,11 @@ pub struct Modal<V: 'static> {
children: SmallVec<[AnyElement<V>; 2]>, children: SmallVec<[AnyElement<V>; 2]>,
} }
impl<V: 'static> Modal<V> { impl<V: 'static> Component<V> for Modal<V> {
pub fn new(id: impl Into<ElementId>) -> Self { type Rendered = Stateful<V, Div<V>>;
Self {
id: id.into(),
title: None,
primary_action: None,
secondary_action: None,
children: SmallVec::new(),
}
}
pub fn title(mut self, title: impl Into<SharedString>) -> Self { fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
self.title = Some(title.into()); let _view: &mut V = view;
self
}
pub fn primary_action(mut self, action: Button<V>) -> Self {
self.primary_action = Some(action);
self
}
pub fn secondary_action(mut self, action: Button<V>) -> Self {
self.secondary_action = Some(action);
self
}
fn render(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
v_stack() v_stack()
.id(self.id.clone()) .id(self.id.clone())
.w_96() .w_96()
@ -74,7 +52,34 @@ impl<V: 'static> Modal<V> {
} }
} }
impl<V: 'static> ParentComponent<V> for Modal<V> { impl<V: 'static> Modal<V> {
pub fn new(id: impl Into<ElementId>) -> Self {
Self {
id: id.into(),
title: None,
primary_action: None,
secondary_action: None,
children: SmallVec::new(),
}
}
pub fn title(mut self, title: impl Into<SharedString>) -> Self {
self.title = Some(title.into());
self
}
pub fn primary_action(mut self, action: Button<V>) -> Self {
self.primary_action = Some(action);
self
}
pub fn secondary_action(mut self, action: Button<V>) -> Self {
self.secondary_action = Some(action);
self
}
}
impl<V: 'static> ParentElement<V> for Modal<V> {
fn children_mut(&mut self) -> &mut SmallVec<[AnyElement<V>; 2]> { fn children_mut(&mut self) -> &mut SmallVec<[AnyElement<V>; 2]> {
&mut self.children &mut self.children
} }

View file

@ -3,7 +3,7 @@ use gpui::rems;
use crate::prelude::*; use crate::prelude::*;
use crate::{h_stack, Icon}; use crate::{h_stack, Icon};
#[derive(Component)] // #[derive(RenderOnce)]
pub struct NotificationToast { pub struct NotificationToast {
label: SharedString, label: SharedString,
icon: Option<Icon>, icon: Option<Icon>,
@ -22,7 +22,7 @@ impl NotificationToast {
self self
} }
fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> { fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Element<V> {
h_stack() h_stack()
.z_index(5) .z_index(5)
.absolute() .absolute()

View file

@ -1,7 +1,9 @@
use crate::{h_stack, prelude::*, v_stack, KeyBinding, Label}; use crate::{h_stack, prelude::*, v_stack, KeyBinding, Label};
use gpui::prelude::*; use gpui::prelude::*;
use gpui::Div;
use gpui::Stateful;
#[derive(Component)] #[derive(RenderOnce)]
pub struct Palette { pub struct Palette {
id: ElementId, id: ElementId,
input_placeholder: SharedString, input_placeholder: SharedString,
@ -10,6 +12,70 @@ pub struct Palette {
default_order: OrderMethod, default_order: OrderMethod,
} }
impl<V: 'static> Component<V> for Palette {
type Rendered = Stateful<V, Div<V>>;
fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
v_stack()
.id(self.id)
.w_96()
.rounded_lg()
.bg(cx.theme().colors().elevated_surface_background)
.border()
.border_color(cx.theme().colors().border)
.child(
v_stack()
.gap_px()
.child(v_stack().py_0p5().px_1().child(
div().px_2().py_0p5().child(
Label::new(self.input_placeholder).color(TextColor::Placeholder),
),
))
.child(
div()
.h_px()
.w_full()
.bg(cx.theme().colors().element_background),
)
.child(
v_stack()
.id("items")
.py_0p5()
.px_1()
.grow()
.max_h_96()
.overflow_y_scroll()
.children(
vec![if self.items.is_empty() {
Some(h_stack().justify_between().px_2().py_1().child(
Label::new(self.empty_string).color(TextColor::Muted),
))
} else {
None
}]
.into_iter()
.flatten(),
)
.children(self.items.into_iter().enumerate().map(|(index, item)| {
h_stack()
.id(index)
.justify_between()
.px_2()
.py_0p5()
.rounded_lg()
.hover(|style| {
style.bg(cx.theme().colors().ghost_element_hover)
})
.active(|style| {
style.bg(cx.theme().colors().ghost_element_active)
})
.child(item)
})),
),
)
}
}
impl Palette { impl Palette {
pub fn new(id: impl Into<ElementId>) -> Self { pub fn new(id: impl Into<ElementId>) -> Self {
Self { Self {
@ -41,76 +107,33 @@ impl Palette {
self.default_order = default_order; self.default_order = default_order;
self self
} }
fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
v_stack()
.id(self.id.clone())
.w_96()
.rounded_lg()
.bg(cx.theme().colors().elevated_surface_background)
.border()
.border_color(cx.theme().colors().border)
.child(
v_stack()
.gap_px()
.child(v_stack().py_0p5().px_1().child(div().px_2().py_0p5().child(
Label::new(self.input_placeholder.clone()).color(TextColor::Placeholder),
)))
.child(
div()
.h_px()
.w_full()
.bg(cx.theme().colors().element_background),
)
.child(
v_stack()
.id("items")
.py_0p5()
.px_1()
.grow()
.max_h_96()
.overflow_y_scroll()
.children(
vec![if self.items.is_empty() {
Some(
h_stack().justify_between().px_2().py_1().child(
Label::new(self.empty_string.clone())
.color(TextColor::Muted),
),
)
} else {
None
}]
.into_iter()
.flatten(),
)
.children(self.items.into_iter().enumerate().map(|(index, item)| {
h_stack()
.id(index)
.justify_between()
.px_2()
.py_0p5()
.rounded_lg()
.hover(|style| {
style.bg(cx.theme().colors().ghost_element_hover)
})
.active(|style| {
style.bg(cx.theme().colors().ghost_element_active)
})
.child(item)
})),
),
)
}
} }
#[derive(Component)] #[derive(RenderOnce)]
pub struct PaletteItem { pub struct PaletteItem {
pub label: SharedString, pub label: SharedString,
pub sublabel: Option<SharedString>, pub sublabel: Option<SharedString>,
pub key_binding: Option<KeyBinding>, pub key_binding: Option<KeyBinding>,
} }
impl<V: 'static> Component<V> for PaletteItem {
type Rendered = Div<V>;
fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
div()
.flex()
.flex_row()
.grow()
.justify_between()
.child(
v_stack()
.child(Label::new(self.label))
.children(self.sublabel.map(|sublabel| Label::new(sublabel))),
)
.children(self.key_binding)
}
}
impl PaletteItem { impl PaletteItem {
pub fn new(label: impl Into<SharedString>) -> Self { pub fn new(label: impl Into<SharedString>) -> Self {
Self { Self {
@ -134,20 +157,6 @@ impl PaletteItem {
self.key_binding = key_binding.into(); self.key_binding = key_binding.into();
self self
} }
fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
div()
.flex()
.flex_row()
.grow()
.justify_between()
.child(
v_stack()
.child(Label::new(self.label.clone()))
.children(self.sublabel.clone().map(|sublabel| Label::new(sublabel))),
)
.children(self.key_binding)
}
} }
use gpui::ElementId; use gpui::ElementId;
@ -164,7 +173,7 @@ mod stories {
pub struct PaletteStory; pub struct PaletteStory;
impl Render for PaletteStory { impl Render<Self> for PaletteStory {
type Element = Div<Self>; type Element = Div<Self>;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element { fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {

View file

@ -1,4 +1,4 @@
use gpui::{prelude::*, AbsoluteLength, AnyElement}; use gpui::{prelude::*, AbsoluteLength, AnyElement, Div, RenderOnce, Stateful};
use smallvec::SmallVec; use smallvec::SmallVec;
use crate::prelude::*; use crate::prelude::*;
@ -38,7 +38,7 @@ pub enum PanelSide {
use std::collections::HashSet; use std::collections::HashSet;
#[derive(Component)] #[derive(RenderOnce)]
pub struct Panel<V: 'static> { pub struct Panel<V: 'static> {
id: ElementId, id: ElementId,
current_side: PanelSide, current_side: PanelSide,
@ -49,6 +49,30 @@ pub struct Panel<V: 'static> {
children: SmallVec<[AnyElement<V>; 2]>, children: SmallVec<[AnyElement<V>; 2]>,
} }
impl<V: 'static> Component<V> for Panel<V> {
type Rendered = Stateful<V, Div<V>>;
fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
let current_size = self.width.unwrap_or(self.initial_width);
v_stack()
.id(self.id.clone())
.flex_initial()
.map(|this| match self.current_side {
PanelSide::Left | PanelSide::Right => this.h_full().w(current_size),
PanelSide::Bottom => this,
})
.map(|this| match self.current_side {
PanelSide::Left => this.border_r(),
PanelSide::Right => this.border_l(),
PanelSide::Bottom => this.border_b().w_full().h(current_size),
})
.bg(cx.theme().colors().surface_background)
.border_color(cx.theme().colors().border)
.children(self.children)
}
}
impl<V: 'static> Panel<V> { impl<V: 'static> Panel<V> {
pub fn new(id: impl Into<ElementId>, cx: &mut WindowContext) -> Self { pub fn new(id: impl Into<ElementId>, cx: &mut WindowContext) -> Self {
let settings = user_settings(cx); let settings = user_settings(cx);
@ -91,29 +115,9 @@ impl<V: 'static> Panel<V> {
} }
self self
} }
fn render(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
let current_size = self.width.unwrap_or(self.initial_width);
v_stack()
.id(self.id.clone())
.flex_initial()
.map(|this| match self.current_side {
PanelSide::Left | PanelSide::Right => this.h_full().w(current_size),
PanelSide::Bottom => this,
})
.map(|this| match self.current_side {
PanelSide::Left => this.border_r(),
PanelSide::Right => this.border_l(),
PanelSide::Bottom => this.border_b().w_full().h(current_size),
})
.bg(cx.theme().colors().surface_background)
.border_color(cx.theme().colors().border)
.children(self.children)
}
} }
impl<V: 'static> ParentComponent<V> for Panel<V> { impl<V: 'static> ParentElement<V> for Panel<V> {
fn children_mut(&mut self) -> &mut SmallVec<[AnyElement<V>; 2]> { fn children_mut(&mut self) -> &mut SmallVec<[AnyElement<V>; 2]> {
&mut self.children &mut self.children
} }
@ -126,11 +130,11 @@ pub use stories::*;
mod stories { mod stories {
use super::*; use super::*;
use crate::{Label, Story}; use crate::{Label, Story};
use gpui::{Div, InteractiveComponent, Render}; use gpui::{Div, InteractiveElement, Render};
pub struct PanelStory; pub struct PanelStory;
impl Render for PanelStory { impl Render<Self> for PanelStory {
type Element = Div<Self>; type Element = Div<Self>;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element { fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {

View file

@ -1,19 +1,17 @@
use gpui::{Div, RenderOnce};
use crate::prelude::*; use crate::prelude::*;
use crate::{Avatar, Facepile, PlayerWithCallStatus}; use crate::{Avatar, Facepile, PlayerWithCallStatus};
#[derive(Component)] #[derive(RenderOnce)]
pub struct PlayerStack { pub struct PlayerStack {
player_with_call_status: PlayerWithCallStatus, player_with_call_status: PlayerWithCallStatus,
} }
impl PlayerStack { impl<V: 'static> Component<V> for PlayerStack {
pub fn new(player_with_call_status: PlayerWithCallStatus) -> Self { type Rendered = Div<V>;
Self {
player_with_call_status,
}
}
fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> { fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
let player = self.player_with_call_status.get_player(); let player = self.player_with_call_status.get_player();
let followers = self let followers = self
@ -59,3 +57,11 @@ impl PlayerStack {
) )
} }
} }
impl PlayerStack {
pub fn new(player_with_call_status: PlayerWithCallStatus) -> Self {
Self {
player_with_call_status,
}
}
}

View file

@ -1,8 +1,8 @@
use crate::prelude::*; use crate::prelude::*;
use crate::{Icon, IconElement, Label, TextColor}; use crate::{Icon, IconElement, Label, TextColor};
use gpui::{prelude::*, red, Div, ElementId, Render, View}; use gpui::{prelude::*, red, Div, ElementId, Render, RenderOnce, Stateful, View};
#[derive(Component, Clone)] #[derive(RenderOnce, Clone)]
pub struct Tab { pub struct Tab {
id: ElementId, id: ElementId,
title: String, title: String,
@ -20,7 +20,7 @@ struct TabDragState {
title: String, title: String,
} }
impl Render for TabDragState { impl Render<Self> for TabDragState {
type Element = Div<Self>; type Element = Div<Self>;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element { fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
@ -28,65 +28,10 @@ impl Render for TabDragState {
} }
} }
impl Tab { impl<V: 'static> Component<V> for Tab {
pub fn new(id: impl Into<ElementId>) -> Self { type Rendered = Stateful<V, Div<V>>;
Self {
id: id.into(),
title: "untitled".to_string(),
icon: None,
current: false,
dirty: false,
fs_status: FileSystemStatus::None,
git_status: GitStatus::None,
diagnostic_status: DiagnosticStatus::None,
close_side: IconSide::Right,
}
}
pub fn current(mut self, current: bool) -> Self { fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
self.current = current;
self
}
pub fn title(mut self, title: String) -> Self {
self.title = title;
self
}
pub fn icon<I>(mut self, icon: I) -> Self
where
I: Into<Option<Icon>>,
{
self.icon = icon.into();
self
}
pub fn dirty(mut self, dirty: bool) -> Self {
self.dirty = dirty;
self
}
pub fn fs_status(mut self, fs_status: FileSystemStatus) -> Self {
self.fs_status = fs_status;
self
}
pub fn git_status(mut self, git_status: GitStatus) -> Self {
self.git_status = git_status;
self
}
pub fn diagnostic_status(mut self, diagnostic_status: DiagnosticStatus) -> Self {
self.diagnostic_status = diagnostic_status;
self
}
pub fn close_side(mut self, close_side: IconSide) -> Self {
self.close_side = close_side;
self
}
fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
let has_fs_conflict = self.fs_status == FileSystemStatus::Conflict; let has_fs_conflict = self.fs_status == FileSystemStatus::Conflict;
let is_deleted = self.fs_status == FileSystemStatus::Deleted; let is_deleted = self.fs_status == FileSystemStatus::Deleted;
@ -164,6 +109,65 @@ impl Tab {
} }
} }
impl Tab {
pub fn new(id: impl Into<ElementId>) -> Self {
Self {
id: id.into(),
title: "untitled".to_string(),
icon: None,
current: false,
dirty: false,
fs_status: FileSystemStatus::None,
git_status: GitStatus::None,
diagnostic_status: DiagnosticStatus::None,
close_side: IconSide::Right,
}
}
pub fn current(mut self, current: bool) -> Self {
self.current = current;
self
}
pub fn title(mut self, title: String) -> Self {
self.title = title;
self
}
pub fn icon<I>(mut self, icon: I) -> Self
where
I: Into<Option<Icon>>,
{
self.icon = icon.into();
self
}
pub fn dirty(mut self, dirty: bool) -> Self {
self.dirty = dirty;
self
}
pub fn fs_status(mut self, fs_status: FileSystemStatus) -> Self {
self.fs_status = fs_status;
self
}
pub fn git_status(mut self, git_status: GitStatus) -> Self {
self.git_status = git_status;
self
}
pub fn diagnostic_status(mut self, diagnostic_status: DiagnosticStatus) -> Self {
self.diagnostic_status = diagnostic_status;
self
}
pub fn close_side(mut self, close_side: IconSide) -> Self {
self.close_side = close_side;
self
}
}
#[cfg(feature = "stories")] #[cfg(feature = "stories")]
pub use stories::*; pub use stories::*;
@ -175,7 +179,7 @@ mod stories {
pub struct TabStory; pub struct TabStory;
impl Render for TabStory { impl Render<Self> for TabStory {
type Element = Div<Self>; type Element = Div<Self>;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element { fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {

View file

@ -1,5 +1,6 @@
use crate::prelude::*; use crate::prelude::*;
use gpui::{prelude::*, AnyElement}; use gpui::{prelude::*, AnyElement, RenderOnce};
use gpui::{Div, Element};
use smallvec::SmallVec; use smallvec::SmallVec;
#[derive(Default, Debug, PartialEq, Eq, Clone, Copy)] #[derive(Default, Debug, PartialEq, Eq, Clone, Copy)]
@ -21,21 +22,16 @@ pub enum ToastOrigin {
/// they are actively showing the a process in progress. /// they are actively showing the a process in progress.
/// ///
/// Only one toast may be visible at a time. /// Only one toast may be visible at a time.
#[derive(Component)] #[derive(RenderOnce)]
pub struct Toast<V: 'static> { pub struct Toast<V: 'static> {
origin: ToastOrigin, origin: ToastOrigin,
children: SmallVec<[AnyElement<V>; 2]>, children: SmallVec<[AnyElement<V>; 2]>,
} }
impl<V: 'static> Toast<V> { impl<V: 'static> Component<V> for Toast<V> {
pub fn new(origin: ToastOrigin) -> Self { type Rendered = Div<V>;
Self {
origin,
children: SmallVec::new(),
}
}
fn render(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> { fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
let mut div = div(); let mut div = div();
if self.origin == ToastOrigin::Bottom { if self.origin == ToastOrigin::Bottom {
@ -58,7 +54,38 @@ impl<V: 'static> Toast<V> {
} }
} }
impl<V: 'static> ParentComponent<V> for Toast<V> { impl<V: 'static> Toast<V> {
pub fn new(origin: ToastOrigin) -> Self {
Self {
origin,
children: SmallVec::new(),
}
}
fn render(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Element<V> {
let mut div = div();
if self.origin == ToastOrigin::Bottom {
div = div.right_1_2();
} else {
div = div.right_2();
}
div.z_index(5)
.absolute()
.bottom_9()
.flex()
.py_1()
.px_1p5()
.rounded_lg()
.shadow_md()
.overflow_hidden()
.bg(cx.theme().colors().elevated_surface_background)
.children(self.children)
}
}
impl<V: 'static> ParentElement<V> for Toast<V> {
fn children_mut(&mut self) -> &mut SmallVec<[AnyElement<V>; 2]> { fn children_mut(&mut self) -> &mut SmallVec<[AnyElement<V>; 2]> {
&mut self.children &mut self.children
} }
@ -77,7 +104,7 @@ mod stories {
pub struct ToastStory; pub struct ToastStory;
impl Render for ToastStory { impl Render<Self> for ToastStory {
type Element = Div<Self>; type Element = Div<Self>;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element { fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {

View file

@ -1,4 +1,4 @@
use gpui::{div, Component, ParentComponent}; use gpui::{div, Element, ParentElement};
use crate::{Icon, IconElement, IconSize, TextColor}; use crate::{Icon, IconElement, IconSize, TextColor};
@ -44,7 +44,7 @@ impl From<bool> for Toggle {
} }
} }
pub fn disclosure_control<V: 'static>(toggle: Toggle) -> impl Component<V> { pub fn disclosure_control<V: 'static>(toggle: Toggle) -> impl Element<V> {
match (toggle.is_toggleable(), toggle.is_toggled()) { match (toggle.is_toggleable(), toggle.is_toggled()) {
(false, _) => div(), (false, _) => div(),
(_, true) => div().child( (_, true) => div().child(

View file

@ -1,14 +1,23 @@
use crate::prelude::*; use crate::prelude::*;
use gpui::{Div, RenderOnce};
#[derive(Component)] #[derive(RenderOnce)]
pub struct ToolDivider; pub struct ToolDivider;
impl<V: 'static> Component<V> for ToolDivider {
type Rendered = Div<V>;
fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
div().w_px().h_3().bg(cx.theme().colors().border)
}
}
impl ToolDivider { impl ToolDivider {
pub fn new() -> Self { pub fn new() -> Self {
Self Self
} }
fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> { fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Element<V> {
div().w_px().h_3().bg(cx.theme().colors().border) div().w_px().h_3().bg(cx.theme().colors().border)
} }
} }

View file

@ -1,4 +1,4 @@
use gpui::{overlay, Action, AnyView, Overlay, Render, VisualContext}; use gpui::{overlay, Action, AnyView, Overlay, Render, RenderOnce, VisualContext};
use settings2::Settings; use settings2::Settings;
use theme2::{ActiveTheme, ThemeSettings}; use theme2::{ActiveTheme, ThemeSettings};
@ -67,7 +67,7 @@ impl Tooltip {
} }
} }
impl Render for Tooltip { impl Render<Self> for Tooltip {
type Element = Overlay<Self>; type Element = Overlay<Self>;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element { fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {

View file

@ -1,8 +1,8 @@
use gpui::rems; use gpui::rems;
use gpui::Rems; use gpui::Rems;
pub use gpui::{ pub use gpui::{
div, Component, Element, ElementId, InteractiveComponent, ParentComponent, SharedString, div, Component, Element, ElementId, InteractiveElement, ParentElement, SharedString, Styled,
Styled, ViewContext, WindowContext, ViewContext, WindowContext,
}; };
pub use crate::elevation::*; pub use crate::elevation::*;

View file

@ -745,11 +745,11 @@ pub fn hello_world_rust_editor_example(cx: &mut ViewContext<EditorPane>) -> Edit
PathBuf::from_str("crates/ui/src/static_data.rs").unwrap(), PathBuf::from_str("crates/ui/src/static_data.rs").unwrap(),
vec![Symbol(vec![ vec![Symbol(vec![
HighlightedText { HighlightedText {
text: "fn ".to_string(), text: "fn ".into(),
color: cx.theme().syntax_color("keyword"), color: cx.theme().syntax_color("keyword"),
}, },
HighlightedText { HighlightedText {
text: "main".to_string(), text: "main".into(),
color: cx.theme().syntax_color("function"), color: cx.theme().syntax_color("function"),
}, },
])], ])],
@ -779,15 +779,15 @@ pub fn hello_world_rust_buffer_rows(cx: &AppContext) -> Vec<BufferRow> {
line: Some(HighlightedLine { line: Some(HighlightedLine {
highlighted_texts: vec![ highlighted_texts: vec![
HighlightedText { HighlightedText {
text: "fn ".to_string(), text: "fn ".into(),
color: cx.theme().syntax_color("keyword"), color: cx.theme().syntax_color("keyword"),
}, },
HighlightedText { HighlightedText {
text: "main".to_string(), text: "main".into(),
color: cx.theme().syntax_color("function"), color: cx.theme().syntax_color("function"),
}, },
HighlightedText { HighlightedText {
text: "() {".to_string(), text: "() {".into(),
color: cx.theme().colors().text, color: cx.theme().colors().text,
}, },
], ],
@ -803,7 +803,7 @@ pub fn hello_world_rust_buffer_rows(cx: &AppContext) -> Vec<BufferRow> {
line: Some(HighlightedLine { line: Some(HighlightedLine {
highlighted_texts: vec![HighlightedText { highlighted_texts: vec![HighlightedText {
text: " // Statements here are executed when the compiled binary is called." text: " // Statements here are executed when the compiled binary is called."
.to_string(), .into(),
color: cx.theme().syntax_color("comment"), color: cx.theme().syntax_color("comment"),
}], }],
}), }),
@ -826,7 +826,7 @@ pub fn hello_world_rust_buffer_rows(cx: &AppContext) -> Vec<BufferRow> {
current: false, current: false,
line: Some(HighlightedLine { line: Some(HighlightedLine {
highlighted_texts: vec![HighlightedText { highlighted_texts: vec![HighlightedText {
text: " // Print text to the console.".to_string(), text: " // Print text to the console.".into(),
color: cx.theme().syntax_color("comment"), color: cx.theme().syntax_color("comment"),
}], }],
}), }),
@ -841,15 +841,15 @@ pub fn hello_world_rust_buffer_rows(cx: &AppContext) -> Vec<BufferRow> {
line: Some(HighlightedLine { line: Some(HighlightedLine {
highlighted_texts: vec![ highlighted_texts: vec![
HighlightedText { HighlightedText {
text: " println!(".to_string(), text: " println!(".into(),
color: cx.theme().colors().text, color: cx.theme().colors().text,
}, },
HighlightedText { HighlightedText {
text: "\"Hello, world!\"".to_string(), text: "\"Hello, world!\"".into(),
color: cx.theme().syntax_color("string"), color: cx.theme().syntax_color("string"),
}, },
HighlightedText { HighlightedText {
text: ");".to_string(), text: ");".into(),
color: cx.theme().colors().text, color: cx.theme().colors().text,
}, },
], ],
@ -864,7 +864,7 @@ pub fn hello_world_rust_buffer_rows(cx: &AppContext) -> Vec<BufferRow> {
current: false, current: false,
line: Some(HighlightedLine { line: Some(HighlightedLine {
highlighted_texts: vec![HighlightedText { highlighted_texts: vec![HighlightedText {
text: "}".to_string(), text: "}".into(),
color: cx.theme().colors().text, color: cx.theme().colors().text,
}], }],
}), }),
@ -882,11 +882,11 @@ pub fn hello_world_rust_editor_with_status_example(cx: &mut ViewContext<EditorPa
PathBuf::from_str("crates/ui/src/static_data.rs").unwrap(), PathBuf::from_str("crates/ui/src/static_data.rs").unwrap(),
vec![Symbol(vec![ vec![Symbol(vec![
HighlightedText { HighlightedText {
text: "fn ".to_string(), text: "fn ".into(),
color: cx.theme().syntax_color("keyword"), color: cx.theme().syntax_color("keyword"),
}, },
HighlightedText { HighlightedText {
text: "main".to_string(), text: "main".into(),
color: cx.theme().syntax_color("function"), color: cx.theme().syntax_color("function"),
}, },
])], ])],
@ -916,15 +916,15 @@ pub fn hello_world_rust_with_status_buffer_rows(cx: &AppContext) -> Vec<BufferRo
line: Some(HighlightedLine { line: Some(HighlightedLine {
highlighted_texts: vec![ highlighted_texts: vec![
HighlightedText { HighlightedText {
text: "fn ".to_string(), text: "fn ".into(),
color: cx.theme().syntax_color("keyword"), color: cx.theme().syntax_color("keyword"),
}, },
HighlightedText { HighlightedText {
text: "main".to_string(), text: "main".into(),
color: cx.theme().syntax_color("function"), color: cx.theme().syntax_color("function"),
}, },
HighlightedText { HighlightedText {
text: "() {".to_string(), text: "() {".into(),
color: cx.theme().colors().text, color: cx.theme().colors().text,
}, },
], ],
@ -940,7 +940,7 @@ pub fn hello_world_rust_with_status_buffer_rows(cx: &AppContext) -> Vec<BufferRo
line: Some(HighlightedLine { line: Some(HighlightedLine {
highlighted_texts: vec![HighlightedText { highlighted_texts: vec![HighlightedText {
text: "// Statements here are executed when the compiled binary is called." text: "// Statements here are executed when the compiled binary is called."
.to_string(), .into(),
color: cx.theme().syntax_color("comment"), color: cx.theme().syntax_color("comment"),
}], }],
}), }),
@ -963,7 +963,7 @@ pub fn hello_world_rust_with_status_buffer_rows(cx: &AppContext) -> Vec<BufferRo
current: false, current: false,
line: Some(HighlightedLine { line: Some(HighlightedLine {
highlighted_texts: vec![HighlightedText { highlighted_texts: vec![HighlightedText {
text: " // Print text to the console.".to_string(), text: " // Print text to the console.".into(),
color: cx.theme().syntax_color("comment"), color: cx.theme().syntax_color("comment"),
}], }],
}), }),
@ -978,15 +978,15 @@ pub fn hello_world_rust_with_status_buffer_rows(cx: &AppContext) -> Vec<BufferRo
line: Some(HighlightedLine { line: Some(HighlightedLine {
highlighted_texts: vec![ highlighted_texts: vec![
HighlightedText { HighlightedText {
text: " println!(".to_string(), text: " println!(".into(),
color: cx.theme().colors().text, color: cx.theme().colors().text,
}, },
HighlightedText { HighlightedText {
text: "\"Hello, world!\"".to_string(), text: "\"Hello, world!\"".into(),
color: cx.theme().syntax_color("string"), color: cx.theme().syntax_color("string"),
}, },
HighlightedText { HighlightedText {
text: ");".to_string(), text: ");".into(),
color: cx.theme().colors().text, color: cx.theme().colors().text,
}, },
], ],
@ -1001,7 +1001,7 @@ pub fn hello_world_rust_with_status_buffer_rows(cx: &AppContext) -> Vec<BufferRo
current: false, current: false,
line: Some(HighlightedLine { line: Some(HighlightedLine {
highlighted_texts: vec![HighlightedText { highlighted_texts: vec![HighlightedText {
text: "}".to_string(), text: "}".into(),
color: cx.theme().colors().text, color: cx.theme().colors().text,
}], }],
}), }),
@ -1015,7 +1015,7 @@ pub fn hello_world_rust_with_status_buffer_rows(cx: &AppContext) -> Vec<BufferRo
current: false, current: false,
line: Some(HighlightedLine { line: Some(HighlightedLine {
highlighted_texts: vec![HighlightedText { highlighted_texts: vec![HighlightedText {
text: "".to_string(), text: "".into(),
color: cx.theme().colors().text, color: cx.theme().colors().text,
}], }],
}), }),
@ -1029,7 +1029,7 @@ pub fn hello_world_rust_with_status_buffer_rows(cx: &AppContext) -> Vec<BufferRo
current: false, current: false,
line: Some(HighlightedLine { line: Some(HighlightedLine {
highlighted_texts: vec![HighlightedText { highlighted_texts: vec![HighlightedText {
text: "// Marshall and Nate were here".to_string(), text: "// Marshall and Nate were here".into(),
color: cx.theme().syntax_color("comment"), color: cx.theme().syntax_color("comment"),
}], }],
}), }),
@ -1042,7 +1042,7 @@ pub fn hello_world_rust_with_status_buffer_rows(cx: &AppContext) -> Vec<BufferRo
pub fn terminal_buffer(cx: &AppContext) -> Buffer { pub fn terminal_buffer(cx: &AppContext) -> Buffer {
Buffer::new("terminal") Buffer::new("terminal")
.set_title("zed — fish".to_string()) .set_title(Some("zed — fish".into()))
.set_rows(Some(BufferRows { .set_rows(Some(BufferRows {
show_line_numbers: false, show_line_numbers: false,
rows: terminal_buffer_rows(cx), rows: terminal_buffer_rows(cx),
@ -1060,31 +1060,31 @@ pub fn terminal_buffer_rows(cx: &AppContext) -> Vec<BufferRow> {
line: Some(HighlightedLine { line: Some(HighlightedLine {
highlighted_texts: vec![ highlighted_texts: vec![
HighlightedText { HighlightedText {
text: "maxdeviant ".to_string(), text: "maxdeviant ".into(),
color: cx.theme().syntax_color("keyword"), color: cx.theme().syntax_color("keyword"),
}, },
HighlightedText { HighlightedText {
text: "in ".to_string(), text: "in ".into(),
color: cx.theme().colors().text, color: cx.theme().colors().text,
}, },
HighlightedText { HighlightedText {
text: "profaned-capital ".to_string(), text: "profaned-capital ".into(),
color: cx.theme().syntax_color("function"), color: cx.theme().syntax_color("function"),
}, },
HighlightedText { HighlightedText {
text: "in ".to_string(), text: "in ".into(),
color: cx.theme().colors().text, color: cx.theme().colors().text,
}, },
HighlightedText { HighlightedText {
text: "~/p/zed ".to_string(), text: "~/p/zed ".into(),
color: cx.theme().syntax_color("function"), color: cx.theme().syntax_color("function"),
}, },
HighlightedText { HighlightedText {
text: "on ".to_string(), text: "on ".into(),
color: cx.theme().colors().text, color: cx.theme().colors().text,
}, },
HighlightedText { HighlightedText {
text: " gpui2-ui ".to_string(), text: " gpui2-ui ".into(),
color: cx.theme().syntax_color("keyword"), color: cx.theme().syntax_color("keyword"),
}, },
], ],
@ -1099,7 +1099,7 @@ pub fn terminal_buffer_rows(cx: &AppContext) -> Vec<BufferRow> {
current: false, current: false,
line: Some(HighlightedLine { line: Some(HighlightedLine {
highlighted_texts: vec![HighlightedText { highlighted_texts: vec![HighlightedText {
text: "λ ".to_string(), text: "λ ".into(),
color: cx.theme().syntax_color("string"), color: cx.theme().syntax_color("string"),
}], }],
}), }),

View file

@ -15,23 +15,29 @@ impl Story {
.bg(cx.theme().colors().background) .bg(cx.theme().colors().background)
} }
pub fn title<V: 'static>(cx: &mut ViewContext<V>, title: &str) -> impl Component<V> { pub fn title<V: 'static>(
cx: &mut ViewContext<V>,
title: impl Into<SharedString>,
) -> impl Element<V> {
div() div()
.text_xl() .text_xl()
.text_color(cx.theme().colors().text) .text_color(cx.theme().colors().text)
.child(title.to_owned()) .child(title.into())
} }
pub fn title_for<V: 'static, T>(cx: &mut ViewContext<V>) -> impl Component<V> { pub fn title_for<V: 'static, T>(cx: &mut ViewContext<V>) -> impl Element<V> {
Self::title(cx, std::any::type_name::<T>()) Self::title(cx, std::any::type_name::<T>())
} }
pub fn label<V: 'static>(cx: &mut ViewContext<V>, label: &str) -> impl Component<V> { pub fn label<V: 'static>(
cx: &mut ViewContext<V>,
label: impl Into<SharedString>,
) -> impl Element<V> {
div() div()
.mt_4() .mt_4()
.mb_2() .mb_2()
.text_xs() .text_xs()
.text_color(cx.theme().colors().text) .text_color(cx.theme().colors().text)
.child(label.to_owned()) .child(label.into())
} }
} }

View file

@ -1,27 +1,17 @@
use crate::prelude::*; use crate::prelude::*;
use crate::{Icon, IconButton, Label, Panel, PanelSide}; use crate::{Icon, IconButton, Label, Panel, PanelSide};
use gpui::{prelude::*, rems, AbsoluteLength}; use gpui::{prelude::*, rems, AbsoluteLength, RenderOnce};
#[derive(Component)] #[derive(RenderOnce)]
pub struct AssistantPanel { pub struct AssistantPanel {
id: ElementId, id: ElementId,
current_side: PanelSide, current_side: PanelSide,
} }
impl AssistantPanel { impl<V: 'static> Component<V> for AssistantPanel {
pub fn new(id: impl Into<ElementId>) -> Self { type Rendered = Panel<V>;
Self {
id: id.into(),
current_side: PanelSide::default(),
}
}
pub fn side(mut self, side: PanelSide) -> Self { fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
self.current_side = side;
self
}
fn render<V: 'static>(self, view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
Panel::new(self.id.clone(), cx) Panel::new(self.id.clone(), cx)
.children(vec![div() .children(vec![div()
.flex() .flex()
@ -64,12 +54,26 @@ impl AssistantPanel {
.overflow_y_scroll() .overflow_y_scroll()
.child(Label::new("Is this thing on?")), .child(Label::new("Is this thing on?")),
) )
.render()]) .into_any()])
.side(self.current_side) .side(self.current_side)
.width(AbsoluteLength::Rems(rems(32.))) .width(AbsoluteLength::Rems(rems(32.)))
} }
} }
impl AssistantPanel {
pub fn new(id: impl Into<ElementId>) -> Self {
Self {
id: id.into(),
current_side: PanelSide::default(),
}
}
pub fn side(mut self, side: PanelSide) -> Self {
self.current_side = side;
self
}
}
#[cfg(feature = "stories")] #[cfg(feature = "stories")]
pub use stories::*; pub use stories::*;
@ -80,7 +84,7 @@ mod stories {
use gpui::{Div, Render}; use gpui::{Div, Render};
pub struct AssistantPanelStory; pub struct AssistantPanelStory;
impl Render for AssistantPanelStory { impl Render<Self> for AssistantPanelStory {
type Element = Div<Self>; type Element = Div<Self>;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element { fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {

View file

@ -1,30 +1,21 @@
use crate::{h_stack, prelude::*, HighlightedText}; use crate::{h_stack, prelude::*, HighlightedText};
use gpui::{prelude::*, Div}; use gpui::{prelude::*, Div, Stateful};
use std::path::PathBuf; use std::path::PathBuf;
#[derive(Clone)] #[derive(Clone)]
pub struct Symbol(pub Vec<HighlightedText>); pub struct Symbol(pub Vec<HighlightedText>);
#[derive(Component)] #[derive(RenderOnce)]
pub struct Breadcrumb { pub struct Breadcrumb {
path: PathBuf, path: PathBuf,
symbols: Vec<Symbol>, symbols: Vec<Symbol>,
} }
impl Breadcrumb { impl<V: 'static> Component<V> for Breadcrumb {
pub fn new(path: PathBuf, symbols: Vec<Symbol>) -> Self { type Rendered = Stateful<V, Div<V>>;
Self { path, symbols }
}
fn render_separator<V: 'static>(&self, cx: &WindowContext) -> Div<V> { fn render(self, view_state: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
div()
.child(" ")
.text_color(cx.theme().colors().text_muted)
}
fn render<V: 'static>(self, view_state: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
let symbols_len = self.symbols.len(); let symbols_len = self.symbols.len();
h_stack() h_stack()
.id("breadcrumb") .id("breadcrumb")
.px_1() .px_1()
@ -33,7 +24,9 @@ impl Breadcrumb {
.rounded_md() .rounded_md()
.hover(|style| style.bg(cx.theme().colors().ghost_element_hover)) .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
.active(|style| style.bg(cx.theme().colors().ghost_element_active)) .active(|style| style.bg(cx.theme().colors().ghost_element_active))
.child(self.path.clone().to_str().unwrap().to_string()) .child(SharedString::from(
self.path.clone().to_str().unwrap().to_string(),
))
.child(if !self.symbols.is_empty() { .child(if !self.symbols.is_empty() {
self.render_separator(cx) self.render_separator(cx)
} else { } else {
@ -64,6 +57,18 @@ impl Breadcrumb {
} }
} }
impl Breadcrumb {
pub fn new(path: PathBuf, symbols: Vec<Symbol>) -> Self {
Self { path, symbols }
}
fn render_separator<V: 'static>(&self, cx: &WindowContext) -> Div<V> {
div()
.child(" ")
.text_color(cx.theme().colors().text_muted)
}
}
#[cfg(feature = "stories")] #[cfg(feature = "stories")]
pub use stories::*; pub use stories::*;
@ -76,7 +81,7 @@ mod stories {
pub struct BreadcrumbStory; pub struct BreadcrumbStory;
impl Render for BreadcrumbStory { impl Render<Self> for BreadcrumbStory {
type Element = Div<Self>; type Element = Div<Self>;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element { fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
@ -88,21 +93,21 @@ mod stories {
vec![ vec![
Symbol(vec![ Symbol(vec![
HighlightedText { HighlightedText {
text: "impl ".to_string(), text: "impl ".into(),
color: cx.theme().syntax_color("keyword"), color: cx.theme().syntax_color("keyword"),
}, },
HighlightedText { HighlightedText {
text: "BreadcrumbStory".to_string(), text: "BreadcrumbStory".into(),
color: cx.theme().syntax_color("function"), color: cx.theme().syntax_color("function"),
}, },
]), ]),
Symbol(vec![ Symbol(vec![
HighlightedText { HighlightedText {
text: "fn ".to_string(), text: "fn ".into(),
color: cx.theme().syntax_color("keyword"), color: cx.theme().syntax_color("keyword"),
}, },
HighlightedText { HighlightedText {
text: "render".to_string(), text: "render".into(),
color: cx.theme().syntax_color("function"), color: cx.theme().syntax_color("function"),
}, },
]), ]),

View file

@ -1,4 +1,4 @@
use gpui::{Hsla, WindowContext}; use gpui::{Div, Hsla, RenderOnce, WindowContext};
use crate::prelude::*; use crate::prelude::*;
use crate::{h_stack, v_stack, Icon, IconElement}; use crate::{h_stack, v_stack, Icon, IconElement};
@ -11,7 +11,7 @@ pub struct PlayerCursor {
#[derive(Default, PartialEq, Clone)] #[derive(Default, PartialEq, Clone)]
pub struct HighlightedText { pub struct HighlightedText {
pub text: String, pub text: SharedString,
pub color: Hsla, pub color: Hsla,
} }
@ -107,7 +107,7 @@ impl BufferRow {
} }
} }
#[derive(Component, Clone)] #[derive(RenderOnce, Clone)]
pub struct Buffer { pub struct Buffer {
id: ElementId, id: ElementId,
rows: Option<BufferRows>, rows: Option<BufferRows>,
@ -117,6 +117,21 @@ pub struct Buffer {
path: Option<String>, path: Option<String>,
} }
impl<V: 'static> Component<V> for Buffer {
type Rendered = Div<V>;
fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
let rows = self.render_rows(cx);
v_stack()
.flex_1()
.w_full()
.h_full()
.bg(cx.theme().colors().editor_background)
.children(rows)
}
}
impl Buffer { impl Buffer {
pub fn new(id: impl Into<ElementId>) -> Self { pub fn new(id: impl Into<ElementId>) -> Self {
Self { Self {
@ -154,7 +169,7 @@ impl Buffer {
self self
} }
fn render_row<V: 'static>(row: BufferRow, cx: &WindowContext) -> impl Component<V> { fn render_row<V: 'static>(row: BufferRow, cx: &WindowContext) -> impl Element<V> {
let line_background = if row.current { let line_background = if row.current {
cx.theme().colors().editor_active_line_background cx.theme().colors().editor_active_line_background
} else { } else {
@ -186,7 +201,7 @@ impl Buffer {
h_stack().justify_end().px_0p5().w_3().child( h_stack().justify_end().px_0p5().w_3().child(
div() div()
.text_color(line_number_color) .text_color(line_number_color)
.child(row.line_number.to_string()), .child(SharedString::from(row.line_number.to_string())),
), ),
) )
}) })
@ -202,7 +217,7 @@ impl Buffer {
})) }))
} }
fn render_rows<V: 'static>(&self, cx: &WindowContext) -> Vec<impl Component<V>> { fn render_rows<V: 'static>(&self, cx: &WindowContext) -> Vec<impl Element<V>> {
match &self.rows { match &self.rows {
Some(rows) => rows Some(rows) => rows
.rows .rows
@ -213,7 +228,7 @@ impl Buffer {
} }
} }
fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> { fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Element<V> {
let rows = self.render_rows(cx); let rows = self.render_rows(cx);
v_stack() v_stack()
@ -239,7 +254,7 @@ mod stories {
pub struct BufferStory; pub struct BufferStory;
impl Render for BufferStory { impl Render<Self> for BufferStory {
type Element = Div<Self>; type Element = Div<Self>;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element { fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {

View file

@ -1,7 +1,6 @@
use gpui::{Div, Render, View, VisualContext};
use crate::prelude::*; use crate::prelude::*;
use crate::{h_stack, Icon, IconButton, Input, TextColor}; use crate::{h_stack, Icon, IconButton, Input, TextColor};
use gpui::{Div, Render, RenderOnce, View, VisualContext};
#[derive(Clone)] #[derive(Clone)]
pub struct BufferSearch { pub struct BufferSearch {
@ -26,7 +25,7 @@ impl BufferSearch {
} }
} }
impl Render for BufferSearch { impl Render<Self> for BufferSearch {
type Element = Div<Self>; type Element = Div<Self>;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Div<Self> { fn render(&mut self, cx: &mut ViewContext<Self>) -> Div<Self> {

View file

@ -1,27 +1,17 @@
use crate::{prelude::*, Icon, IconButton, Input, Label}; use crate::{prelude::*, Icon, IconButton, Input, Label};
use chrono::NaiveDateTime; use chrono::NaiveDateTime;
use gpui::prelude::*; use gpui::{prelude::*, Div, Stateful};
#[derive(Component)] #[derive(RenderOnce)]
pub struct ChatPanel { pub struct ChatPanel {
element_id: ElementId, element_id: ElementId,
messages: Vec<ChatMessage>, messages: Vec<ChatMessage>,
} }
impl ChatPanel { impl<V: 'static> Component<V> for ChatPanel {
pub fn new(element_id: impl Into<ElementId>) -> Self { type Rendered = Stateful<V, Div<V>>;
Self {
element_id: element_id.into(),
messages: Vec::new(),
}
}
pub fn messages(mut self, messages: Vec<ChatMessage>) -> Self { fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
self.messages = messages;
self
}
fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
div() div()
.id(self.element_id.clone()) .id(self.element_id.clone())
.flex() .flex()
@ -67,23 +57,31 @@ impl ChatPanel {
} }
} }
#[derive(Component)] impl ChatPanel {
pub fn new(element_id: impl Into<ElementId>) -> Self {
Self {
element_id: element_id.into(),
messages: Vec::new(),
}
}
pub fn messages(mut self, messages: Vec<ChatMessage>) -> Self {
self.messages = messages;
self
}
}
#[derive(RenderOnce)]
pub struct ChatMessage { pub struct ChatMessage {
author: String, author: String,
text: String, text: String,
sent_at: NaiveDateTime, sent_at: NaiveDateTime,
} }
impl ChatMessage { impl<V: 'static> Component<V> for ChatMessage {
pub fn new(author: String, text: String, sent_at: NaiveDateTime) -> Self { type Rendered = Div<V>;
Self {
author,
text,
sent_at,
}
}
fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> { fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
div() div()
.flex() .flex()
.flex_col() .flex_col()
@ -101,6 +99,16 @@ impl ChatMessage {
} }
} }
impl ChatMessage {
pub fn new(author: String, text: String, sent_at: NaiveDateTime) -> Self {
Self {
author,
text,
sent_at,
}
}
}
#[cfg(feature = "stories")] #[cfg(feature = "stories")]
pub use stories::*; pub use stories::*;
@ -115,7 +123,7 @@ mod stories {
pub struct ChatPanelStory; pub struct ChatPanelStory;
impl Render for ChatPanelStory { impl Render<Self> for ChatPanelStory {
type Element = Div<Self>; type Element = Div<Self>;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element { fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {

View file

@ -2,19 +2,17 @@ use crate::{
prelude::*, static_collab_panel_channels, static_collab_panel_current_call, v_stack, Icon, prelude::*, static_collab_panel_channels, static_collab_panel_current_call, v_stack, Icon,
List, ListHeader, Toggle, List, ListHeader, Toggle,
}; };
use gpui::prelude::*; use gpui::{prelude::*, Div, Stateful};
#[derive(Component)] #[derive(RenderOnce)]
pub struct CollabPanel { pub struct CollabPanel {
id: ElementId, id: ElementId,
} }
impl CollabPanel { impl<V: 'static> Component<V> for CollabPanel {
pub fn new(id: impl Into<ElementId>) -> Self { type Rendered = Stateful<V, Div<V>>;
Self { id: id.into() }
}
fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> { fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
v_stack() v_stack()
.id(self.id.clone()) .id(self.id.clone())
.h_full() .h_full()
@ -86,6 +84,12 @@ impl CollabPanel {
} }
} }
impl CollabPanel {
pub fn new(id: impl Into<ElementId>) -> Self {
Self { id: id.into() }
}
}
#[cfg(feature = "stories")] #[cfg(feature = "stories")]
pub use stories::*; pub use stories::*;
@ -97,7 +101,7 @@ mod stories {
pub struct CollabPanelStory; pub struct CollabPanelStory;
impl Render for CollabPanelStory { impl Render<Self> for CollabPanelStory {
type Element = Div<Self>; type Element = Div<Self>;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element { fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {

View file

@ -1,17 +1,15 @@
use crate::prelude::*; use crate::prelude::*;
use crate::{example_editor_actions, OrderMethod, Palette}; use crate::{example_editor_actions, OrderMethod, Palette};
#[derive(Component)] #[derive(RenderOnce)]
pub struct CommandPalette { pub struct CommandPalette {
id: ElementId, id: ElementId,
} }
impl CommandPalette { impl<V: 'static> Component<V> for CommandPalette {
pub fn new(id: impl Into<ElementId>) -> Self { type Rendered = Stateful<V, Div<V>>;
Self { id: id.into() }
}
fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> { fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
div().id(self.id.clone()).child( div().id(self.id.clone()).child(
Palette::new("palette") Palette::new("palette")
.items(example_editor_actions()) .items(example_editor_actions())
@ -22,6 +20,13 @@ impl CommandPalette {
} }
} }
impl CommandPalette {
pub fn new(id: impl Into<ElementId>) -> Self {
Self { id: id.into() }
}
}
use gpui::{Div, RenderOnce, Stateful};
#[cfg(feature = "stories")] #[cfg(feature = "stories")]
pub use stories::*; pub use stories::*;
@ -35,7 +40,7 @@ mod stories {
pub struct CommandPaletteStory; pub struct CommandPaletteStory;
impl Render for CommandPaletteStory { impl Render<Self> for CommandPaletteStory {
type Element = Div<Self>; type Element = Div<Self>;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element { fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {

View file

@ -1,39 +1,42 @@
use crate::{prelude::*, Button, Label, Modal, TextColor}; use crate::{prelude::*, Button, Label, Modal, TextColor};
#[derive(Component)] #[derive(RenderOnce)]
pub struct CopilotModal { pub struct CopilotModal {
id: ElementId, id: ElementId,
} }
impl<V: 'static> Component<V> for CopilotModal {
type Rendered = Stateful<V, Div<V>>;
fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
div().id(self.id.clone()).child(
Modal::new("some-id")
.title("Connect Copilot to Zed")
.child(Label::new("You can update your settings or sign out from the Copilot menu in the status bar.").color(TextColor::Muted))
.primary_action(Button::new("Connect to Github").variant(ButtonVariant::Filled)),
)
}
}
impl CopilotModal { impl CopilotModal {
pub fn new(id: impl Into<ElementId>) -> Self { pub fn new(id: impl Into<ElementId>) -> Self {
Self { id: id.into() } Self { id: id.into() }
} }
fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
div().id(self.id.clone()).child(
Modal::new("some-id")
.title("Connect Copilot to Zed")
.child(Label::new("You can update your settings or sign out from the Copilot menu in the status bar.").color(TextColor::Muted))
.primary_action(Button::new("Connect to Github").variant(ButtonVariant::Filled)),
)
}
} }
use gpui::{Div, RenderOnce, Stateful};
#[cfg(feature = "stories")] #[cfg(feature = "stories")]
pub use stories::*; pub use stories::*;
#[cfg(feature = "stories")] #[cfg(feature = "stories")]
mod stories { mod stories {
use gpui::{Div, Render};
use crate::Story;
use super::*; use super::*;
use crate::Story;
use gpui::{Div, Render};
pub struct CopilotModalStory; pub struct CopilotModalStory;
impl Render for CopilotModalStory { impl Render<Self> for CopilotModalStory {
type Element = Div<Self>; type Element = Div<Self>;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element { fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {

View file

@ -1,6 +1,6 @@
use std::path::PathBuf; use std::path::PathBuf;
use gpui::{Div, Render, View, VisualContext}; use gpui::{Div, Render, RenderOnce, View, VisualContext};
use crate::prelude::*; use crate::prelude::*;
use crate::{ use crate::{
@ -47,7 +47,7 @@ impl EditorPane {
} }
} }
impl Render for EditorPane { impl Render<Self> for EditorPane {
type Element = Div<Self>; type Element = Div<Self>;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Div<Self> { fn render(&mut self, cx: &mut ViewContext<Self>) -> Div<Self> {

View file

@ -1,17 +1,15 @@
use crate::prelude::*; use crate::prelude::*;
use crate::{OrderMethod, Palette, PaletteItem}; use crate::{OrderMethod, Palette, PaletteItem};
#[derive(Component)] #[derive(RenderOnce)]
pub struct LanguageSelector { pub struct LanguageSelector {
id: ElementId, id: ElementId,
} }
impl LanguageSelector { impl<V: 'static> Component<V> for LanguageSelector {
pub fn new(id: impl Into<ElementId>) -> Self { type Rendered = Stateful<V, Div<V>>;
Self { id: id.into() }
}
fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> { fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
div().id(self.id.clone()).child( div().id(self.id.clone()).child(
Palette::new("palette") Palette::new("palette")
.items(vec![ .items(vec![
@ -33,6 +31,34 @@ impl LanguageSelector {
} }
} }
impl LanguageSelector {
pub fn new(id: impl Into<ElementId>) -> Self {
Self { id: id.into() }
}
fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Element<V> {
div().id(self.id.clone()).child(
Palette::new("palette")
.items(vec![
PaletteItem::new("C"),
PaletteItem::new("C++"),
PaletteItem::new("CSS"),
PaletteItem::new("Elixir"),
PaletteItem::new("Elm"),
PaletteItem::new("ERB"),
PaletteItem::new("Rust (current)"),
PaletteItem::new("Scheme"),
PaletteItem::new("TOML"),
PaletteItem::new("TypeScript"),
])
.placeholder("Select a language...")
.empty_string("No matches")
.default_order(OrderMethod::Ascending),
)
}
}
use gpui::{Div, RenderOnce, Stateful};
#[cfg(feature = "stories")] #[cfg(feature = "stories")]
pub use stories::*; pub use stories::*;
@ -44,7 +70,7 @@ mod stories {
pub struct LanguageSelectorStory; pub struct LanguageSelectorStory;
impl Render for LanguageSelectorStory { impl Render<Self> for LanguageSelectorStory {
type Element = Div<Self>; type Element = Div<Self>;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element { fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {

View file

@ -1,17 +1,15 @@
use crate::prelude::*; use crate::prelude::*;
use crate::{v_stack, Buffer, Icon, IconButton, Label}; use crate::{v_stack, Buffer, Icon, IconButton, Label};
#[derive(Component)] #[derive(RenderOnce)]
pub struct MultiBuffer { pub struct MultiBuffer {
buffers: Vec<Buffer>, buffers: Vec<Buffer>,
} }
impl MultiBuffer { impl<V: 'static> Component<V> for MultiBuffer {
pub fn new(buffers: Vec<Buffer>) -> Self { type Rendered = Div<V>;
Self { buffers }
}
fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> { fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
v_stack() v_stack()
.w_full() .w_full()
.h_full() .h_full()
@ -33,6 +31,13 @@ impl MultiBuffer {
} }
} }
impl MultiBuffer {
pub fn new(buffers: Vec<Buffer>) -> Self {
Self { buffers }
}
}
use gpui::{Div, RenderOnce};
#[cfg(feature = "stories")] #[cfg(feature = "stories")]
pub use stories::*; pub use stories::*;
@ -44,7 +49,7 @@ mod stories {
pub struct MultiBufferStory; pub struct MultiBufferStory;
impl Render for MultiBufferStory { impl Render<Self> for MultiBufferStory {
type Element = Div<Self>; type Element = Div<Self>;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element { fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {

View file

@ -3,19 +3,17 @@ use crate::{
v_stack, Avatar, ButtonOrIconButton, ClickHandler, Icon, IconElement, Label, LineHeightStyle, v_stack, Avatar, ButtonOrIconButton, ClickHandler, Icon, IconElement, Label, LineHeightStyle,
ListHeader, ListHeaderMeta, ListSeparator, PublicPlayer, TextColor, UnreadIndicator, ListHeader, ListHeaderMeta, ListSeparator, PublicPlayer, TextColor, UnreadIndicator,
}; };
use gpui::prelude::*; use gpui::{prelude::*, Div, Stateful};
#[derive(Component)] #[derive(RenderOnce)]
pub struct NotificationsPanel { pub struct NotificationsPanel {
id: ElementId, id: ElementId,
} }
impl NotificationsPanel { impl<V: 'static> Component<V> for NotificationsPanel {
pub fn new(id: impl Into<ElementId>) -> Self { type Rendered = Stateful<V, Div<V>>;
Self { id: id.into() }
}
fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> { fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
div() div()
.id(self.id.clone()) .id(self.id.clone())
.flex() .flex()
@ -56,6 +54,12 @@ impl NotificationsPanel {
} }
} }
impl NotificationsPanel {
pub fn new(id: impl Into<ElementId>) -> Self {
Self { id: id.into() }
}
}
pub struct NotificationAction<V: 'static> { pub struct NotificationAction<V: 'static> {
button: ButtonOrIconButton<V>, button: ButtonOrIconButton<V>,
tooltip: SharedString, tooltip: SharedString,
@ -102,7 +106,7 @@ impl<V: 'static> Default for NotificationHandlers<V> {
} }
} }
#[derive(Component)] #[derive(RenderOnce)]
pub struct Notification<V: 'static> { pub struct Notification<V: 'static> {
id: ElementId, id: ElementId,
slot: ActorOrIcon, slot: ActorOrIcon,
@ -116,6 +120,85 @@ pub struct Notification<V: 'static> {
handlers: NotificationHandlers<V>, handlers: NotificationHandlers<V>,
} }
impl<V: 'static> Component<V> for Notification<V> {
type Rendered = Stateful<V, Div<V>>;
fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
div()
.relative()
.id(self.id.clone())
.p_1()
.flex()
.flex_col()
.w_full()
.children(
Some(
div()
.absolute()
.left(px(3.0))
.top_3()
.z_index(2)
.child(UnreadIndicator::new()),
)
.filter(|_| self.unread),
)
.child(
v_stack()
.z_index(1)
.gap_1()
.w_full()
.child(
h_stack()
.w_full()
.gap_2()
.child(self.render_slot(cx))
.child(div().flex_1().child(Label::new(self.message.clone()))),
)
.child(
h_stack()
.justify_between()
.child(
h_stack()
.gap_1()
.child(
Label::new(naive_format_distance_from_now(
self.date_received,
true,
true,
))
.color(TextColor::Muted),
)
.child(self.render_meta_items(cx)),
)
.child(match (self.actions, self.action_taken) {
// Show nothing
(None, _) => div(),
// Show the taken_message
(Some(_), Some(action_taken)) => h_stack()
.children(action_taken.taken_message.0.map(|icon| {
IconElement::new(icon).color(crate::TextColor::Muted)
}))
.child(
Label::new(action_taken.taken_message.1.clone())
.color(TextColor::Muted),
),
// Show the actions
(Some(actions), None) => {
h_stack().children(actions.map(|action| match action.button {
ButtonOrIconButton::Button(button) => {
button.render_into_any()
}
ButtonOrIconButton::IconButton(icon_button) => {
icon_button.render_into_any()
}
}))
}
}),
),
)
}
}
impl<V> Notification<V> { impl<V> Notification<V> {
fn new( fn new(
id: ElementId, id: ElementId,
@ -241,7 +324,7 @@ impl<V> Notification<V> {
self self
} }
fn render_meta_items(&self, cx: &mut ViewContext<V>) -> impl Component<V> { fn render_meta_items(&self, cx: &mut ViewContext<V>) -> impl Element<V> {
if let Some(meta) = &self.meta { if let Some(meta) = &self.meta {
h_stack().children( h_stack().children(
meta.items meta.items
@ -260,87 +343,12 @@ impl<V> Notification<V> {
} }
} }
fn render_slot(&self, cx: &mut ViewContext<V>) -> impl Component<V> { fn render_slot(&self, cx: &mut ViewContext<V>) -> impl Element<V> {
match &self.slot { match &self.slot {
ActorOrIcon::Actor(actor) => Avatar::new(actor.avatar.clone()).render(), ActorOrIcon::Actor(actor) => Avatar::new(actor.avatar.clone()).render_into_any(),
ActorOrIcon::Icon(icon) => IconElement::new(icon.clone()).render(), ActorOrIcon::Icon(icon) => IconElement::new(icon.clone()).render_into_any(),
} }
} }
fn render(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
div()
.relative()
.id(self.id.clone())
.p_1()
.flex()
.flex_col()
.w_full()
.children(
Some(
div()
.absolute()
.left(px(3.0))
.top_3()
.z_index(2)
.child(UnreadIndicator::new()),
)
.filter(|_| self.unread),
)
.child(
v_stack()
.z_index(1)
.gap_1()
.w_full()
.child(
h_stack()
.w_full()
.gap_2()
.child(self.render_slot(cx))
.child(div().flex_1().child(Label::new(self.message.clone()))),
)
.child(
h_stack()
.justify_between()
.child(
h_stack()
.gap_1()
.child(
Label::new(naive_format_distance_from_now(
self.date_received,
true,
true,
))
.color(TextColor::Muted),
)
.child(self.render_meta_items(cx)),
)
.child(match (self.actions, self.action_taken) {
// Show nothing
(None, _) => div(),
// Show the taken_message
(Some(_), Some(action_taken)) => h_stack()
.children(action_taken.taken_message.0.map(|icon| {
IconElement::new(icon).color(crate::TextColor::Muted)
}))
.child(
Label::new(action_taken.taken_message.1.clone())
.color(TextColor::Muted),
),
// Show the actions
(Some(actions), None) => {
h_stack().children(actions.map(|action| match action.button {
ButtonOrIconButton::Button(button) => {
Component::render(button)
}
ButtonOrIconButton::IconButton(icon_button) => {
Component::render(icon_button)
}
}))
}
}),
),
)
}
} }
use chrono::NaiveDateTime; use chrono::NaiveDateTime;
@ -356,7 +364,7 @@ mod stories {
pub struct NotificationsPanelStory; pub struct NotificationsPanelStory;
impl Render for NotificationsPanelStory { impl Render<Self> for NotificationsPanelStory {
type Element = Div<Self>; type Element = Div<Self>;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element { fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {

View file

@ -1,4 +1,7 @@
use gpui::{hsla, red, AnyElement, ElementId, ExternalPaths, Hsla, Length, Size, View}; use gpui::{
hsla, red, AnyElement, Div, ElementId, ExternalPaths, Hsla, Length, RenderOnce, Size, Stateful,
View,
};
use smallvec::SmallVec; use smallvec::SmallVec;
use crate::prelude::*; use crate::prelude::*;
@ -10,7 +13,7 @@ pub enum SplitDirection {
Vertical, Vertical,
} }
#[derive(Component)] #[derive(RenderOnce)]
pub struct Pane<V: 'static> { pub struct Pane<V: 'static> {
id: ElementId, id: ElementId,
size: Size<Length>, size: Size<Length>,
@ -18,24 +21,10 @@ pub struct Pane<V: 'static> {
children: SmallVec<[AnyElement<V>; 2]>, children: SmallVec<[AnyElement<V>; 2]>,
} }
impl<V: 'static> Pane<V> { impl<V: 'static> Component<V> for Pane<V> {
pub fn new(id: impl Into<ElementId>, size: Size<Length>) -> Self { type Rendered = Stateful<V, Div<V>>;
// Fill is only here for debugging purposes, remove before release
Self { fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
id: id.into(),
size,
fill: hsla(0.3, 0.3, 0.3, 1.),
children: SmallVec::new(),
}
}
pub fn fill(mut self, fill: Hsla) -> Self {
self.fill = fill;
self
}
fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
div() div()
.id(self.id.clone()) .id(self.id.clone())
.flex() .flex()
@ -59,37 +48,41 @@ impl<V: 'static> Pane<V> {
} }
} }
impl<V: 'static> ParentComponent<V> for Pane<V> { impl<V: 'static> Pane<V> {
pub fn new(id: impl Into<ElementId>, size: Size<Length>) -> Self {
// Fill is only here for debugging purposes, remove before release
Self {
id: id.into(),
size,
fill: hsla(0.3, 0.3, 0.3, 1.),
children: SmallVec::new(),
}
}
pub fn fill(mut self, fill: Hsla) -> Self {
self.fill = fill;
self
}
}
impl<V: 'static> ParentElement<V> for Pane<V> {
fn children_mut(&mut self) -> &mut SmallVec<[AnyElement<V>; 2]> { fn children_mut(&mut self) -> &mut SmallVec<[AnyElement<V>; 2]> {
&mut self.children &mut self.children
} }
} }
#[derive(Component)] #[derive(RenderOnce)]
pub struct PaneGroup<V: 'static> { pub struct PaneGroup<V: 'static> {
groups: Vec<PaneGroup<V>>, groups: Vec<PaneGroup<V>>,
panes: Vec<Pane<V>>, panes: Vec<Pane<V>>,
split_direction: SplitDirection, split_direction: SplitDirection,
} }
impl<V: 'static> PaneGroup<V> { impl<V: 'static> Component<V> for PaneGroup<V> {
pub fn new_groups(groups: Vec<PaneGroup<V>>, split_direction: SplitDirection) -> Self { type Rendered = Div<V>;
Self {
groups,
panes: Vec::new(),
split_direction,
}
}
pub fn new_panes(panes: Vec<Pane<V>>, split_direction: SplitDirection) -> Self { fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
Self {
groups: Vec::new(),
panes,
split_direction,
}
}
fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
if !self.panes.is_empty() { if !self.panes.is_empty() {
let el = div() let el = div()
.flex() .flex()
@ -126,3 +119,21 @@ impl<V: 'static> PaneGroup<V> {
unreachable!() unreachable!()
} }
} }
impl<V: 'static> PaneGroup<V> {
pub fn new_groups(groups: Vec<PaneGroup<V>>, split_direction: SplitDirection) -> Self {
Self {
groups,
panes: Vec::new(),
split_direction,
}
}
pub fn new_panes(panes: Vec<Pane<V>>, split_direction: SplitDirection) -> Self {
Self {
groups: Vec::new(),
panes,
split_direction,
}
}
}

View file

@ -3,18 +3,56 @@ use crate::{
ListHeader, ListHeader,
}; };
use gpui::prelude::*; use gpui::prelude::*;
use gpui::Div;
use gpui::Stateful;
#[derive(Component)] #[derive(RenderOnce)]
pub struct ProjectPanel { pub struct ProjectPanel {
id: ElementId, id: ElementId,
} }
impl<V: 'static> Component<V> for ProjectPanel {
type Rendered = Stateful<V, Div<V>>;
fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
div()
.id(self.id.clone())
.flex()
.flex_col()
.size_full()
.bg(cx.theme().colors().surface_background)
.child(
div()
.id("project-panel-contents")
.w_full()
.flex()
.flex_col()
.overflow_y_scroll()
.child(
List::new(static_project_panel_single_items())
.header(ListHeader::new("FILES"))
.empty_message("No files in directory"),
)
.child(
List::new(static_project_panel_project_items())
.header(ListHeader::new("PROJECT"))
.empty_message("No folders in directory"),
),
)
.child(
Input::new("Find something...")
.value("buffe".to_string())
.state(InteractionState::Focused),
)
}
}
impl ProjectPanel { impl ProjectPanel {
pub fn new(id: impl Into<ElementId>) -> Self { pub fn new(id: impl Into<ElementId>) -> Self {
Self { id: id.into() } Self { id: id.into() }
} }
fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> { fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Element<V> {
div() div()
.id(self.id.clone()) .id(self.id.clone())
.flex() .flex()
@ -59,7 +97,7 @@ mod stories {
pub struct ProjectPanelStory; pub struct ProjectPanelStory;
impl Render for ProjectPanelStory { impl Render<Self> for ProjectPanelStory {
type Element = Div<Self>; type Element = Div<Self>;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element { fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {

View file

@ -1,17 +1,15 @@
use crate::prelude::*; use crate::prelude::*;
use crate::{OrderMethod, Palette, PaletteItem}; use crate::{OrderMethod, Palette, PaletteItem};
#[derive(Component)] #[derive(RenderOnce)]
pub struct RecentProjects { pub struct RecentProjects {
id: ElementId, id: ElementId,
} }
impl RecentProjects { impl<V: 'static> Component<V> for RecentProjects {
pub fn new(id: impl Into<ElementId>) -> Self { type Rendered = Stateful<V, Div<V>>;
Self { id: id.into() }
}
fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> { fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
div().id(self.id.clone()).child( div().id(self.id.clone()).child(
Palette::new("palette") Palette::new("palette")
.items(vec![ .items(vec![
@ -29,6 +27,13 @@ impl RecentProjects {
} }
} }
impl RecentProjects {
pub fn new(id: impl Into<ElementId>) -> Self {
Self { id: id.into() }
}
}
use gpui::{Div, RenderOnce, Stateful};
#[cfg(feature = "stories")] #[cfg(feature = "stories")]
pub use stories::*; pub use stories::*;
@ -40,7 +45,7 @@ mod stories {
pub struct RecentProjectsStory; pub struct RecentProjectsStory;
impl Render for RecentProjectsStory { impl Render<Self> for RecentProjectsStory {
type Element = Div<Self>; type Element = Div<Self>;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element { fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {

View file

@ -1,5 +1,7 @@
use std::sync::Arc; use std::sync::Arc;
use gpui::{Div, RenderOnce};
use crate::prelude::*; use crate::prelude::*;
use crate::{Button, Icon, IconButton, TextColor, ToolDivider, Workspace}; use crate::{Button, Icon, IconButton, TextColor, ToolDivider, Workspace};
@ -28,14 +30,31 @@ impl Default for ToolGroup {
} }
} }
#[derive(Component)] #[derive(RenderOnce)]
#[component(view_type = "Workspace")] #[view = "Workspace"]
pub struct StatusBar { pub struct StatusBar {
left_tools: Option<ToolGroup>, left_tools: Option<ToolGroup>,
right_tools: Option<ToolGroup>, right_tools: Option<ToolGroup>,
bottom_tools: Option<ToolGroup>, bottom_tools: Option<ToolGroup>,
} }
impl Component<Workspace> for StatusBar {
type Rendered = Div<Workspace>;
fn render(self, view: &mut Workspace, cx: &mut ViewContext<Workspace>) -> Self::Rendered {
div()
.py_0p5()
.px_1()
.flex()
.items_center()
.justify_between()
.w_full()
.bg(cx.theme().colors().status_bar_background)
.child(self.left_tools(view, cx))
.child(self.right_tools(view, cx))
}
}
impl StatusBar { impl StatusBar {
pub fn new() -> Self { pub fn new() -> Self {
Self { Self {
@ -81,28 +100,7 @@ impl StatusBar {
self self
} }
fn render( fn left_tools(&self, workspace: &mut Workspace, cx: &WindowContext) -> impl Element<Workspace> {
self,
view: &mut Workspace,
cx: &mut ViewContext<Workspace>,
) -> impl Component<Workspace> {
div()
.py_0p5()
.px_1()
.flex()
.items_center()
.justify_between()
.w_full()
.bg(cx.theme().colors().status_bar_background)
.child(self.left_tools(view, cx))
.child(self.right_tools(view, cx))
}
fn left_tools(
&self,
workspace: &mut Workspace,
cx: &WindowContext,
) -> impl Component<Workspace> {
div() div()
.flex() .flex()
.items_center() .items_center()
@ -133,7 +131,7 @@ impl StatusBar {
&self, &self,
workspace: &mut Workspace, workspace: &mut Workspace,
cx: &WindowContext, cx: &WindowContext,
) -> impl Component<Workspace> { ) -> impl Element<Workspace> {
div() div()
.flex() .flex()
.items_center() .items_center()

View file

@ -1,7 +1,9 @@
use crate::{prelude::*, Icon, IconButton, Tab}; use crate::{prelude::*, Icon, IconButton, Tab};
use gpui::prelude::*; use gpui::prelude::*;
use gpui::Div;
use gpui::Stateful;
#[derive(Component)] #[derive(RenderOnce)]
pub struct TabBar { pub struct TabBar {
id: ElementId, id: ElementId,
/// Backwards, Forwards /// Backwards, Forwards
@ -9,21 +11,10 @@ pub struct TabBar {
tabs: Vec<Tab>, tabs: Vec<Tab>,
} }
impl TabBar { impl<V: 'static> Component<V> for TabBar {
pub fn new(id: impl Into<ElementId>, tabs: Vec<Tab>) -> Self { type Rendered = Stateful<V, Div<V>>;
Self {
id: id.into(),
can_navigate: (false, false),
tabs,
}
}
pub fn can_navigate(mut self, can_navigate: (bool, bool)) -> Self { fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
self.can_navigate = can_navigate;
self
}
fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
let (can_navigate_back, can_navigate_forward) = self.can_navigate; let (can_navigate_back, can_navigate_forward) = self.can_navigate;
div() div()
@ -92,6 +83,21 @@ impl TabBar {
} }
} }
impl TabBar {
pub fn new(id: impl Into<ElementId>, tabs: Vec<Tab>) -> Self {
Self {
id: id.into(),
can_navigate: (false, false),
tabs,
}
}
pub fn can_navigate(mut self, can_navigate: (bool, bool)) -> Self {
self.can_navigate = can_navigate;
self
}
}
use gpui::ElementId; use gpui::ElementId;
#[cfg(feature = "stories")] #[cfg(feature = "stories")]
pub use stories::*; pub use stories::*;
@ -104,7 +110,7 @@ mod stories {
pub struct TabBarStory; pub struct TabBarStory;
impl Render for TabBarStory { impl Render<Self> for TabBarStory {
type Element = Div<Self>; type Element = Div<Self>;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element { fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {

Some files were not shown because too many files have changed in this diff Show more