Compare commits
19 Commits
v0.212.4
...
add-compon
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f8103b9ffa | ||
|
|
7719afeafa | ||
|
|
27b25100c9 | ||
|
|
4d16a3a2a8 | ||
|
|
d36230e46e | ||
|
|
8dddbf31bf | ||
|
|
0c8bb20424 | ||
|
|
5edb86511b | ||
|
|
7e75244620 | ||
|
|
175290c43a | ||
|
|
27c7541e5b | ||
|
|
ae9f51d0e0 | ||
|
|
f609429ac4 | ||
|
|
7cbf651cf9 | ||
|
|
81475ef182 | ||
|
|
9ce110f9ad | ||
|
|
f500de9c3b | ||
|
|
9d810a44fd | ||
|
|
3248bb9172 |
31
Cargo.lock
generated
31
Cargo.lock
generated
@@ -2862,6 +2862,29 @@ dependencies = [
|
||||
"gpui",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "component_preview"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"component_system",
|
||||
"gpui",
|
||||
"strum 0.25.0",
|
||||
"ui",
|
||||
"workspace",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "component_system"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"collections",
|
||||
"gpui",
|
||||
"lazy_static",
|
||||
"linkme",
|
||||
"once_cell",
|
||||
"parking_lot",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "concurrent-queue"
|
||||
version = "2.5.0"
|
||||
@@ -13809,9 +13832,14 @@ name = "ui"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"collections",
|
||||
"component_system",
|
||||
"gpui",
|
||||
"itertools 0.13.0",
|
||||
"lazy_static",
|
||||
"linkme",
|
||||
"menu",
|
||||
"once_cell",
|
||||
"serde",
|
||||
"settings",
|
||||
"smallvec",
|
||||
@@ -13837,6 +13865,7 @@ dependencies = [
|
||||
name = "ui_macros"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"component_system",
|
||||
"convert_case 0.6.0",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -15620,6 +15649,7 @@ dependencies = [
|
||||
"client",
|
||||
"clock",
|
||||
"collections",
|
||||
"component_system",
|
||||
"db",
|
||||
"derive_more",
|
||||
"env_logger 0.11.5",
|
||||
@@ -16051,6 +16081,7 @@ dependencies = [
|
||||
"collections",
|
||||
"command_palette",
|
||||
"command_palette_hooks",
|
||||
"component_preview",
|
||||
"copilot",
|
||||
"db",
|
||||
"diagnostics",
|
||||
|
||||
@@ -21,6 +21,8 @@ members = [
|
||||
"crates/collab",
|
||||
"crates/collab_ui",
|
||||
"crates/collections",
|
||||
"crates/component_preview",
|
||||
"crates/component_system",
|
||||
"crates/command_palette",
|
||||
"crates/command_palette_hooks",
|
||||
"crates/context_server",
|
||||
@@ -207,6 +209,8 @@ clock = { path = "crates/clock" }
|
||||
collab = { path = "crates/collab" }
|
||||
collab_ui = { path = "crates/collab_ui" }
|
||||
collections = { path = "crates/collections" }
|
||||
component_system = { path = "crates/component_system" }
|
||||
component_preview = { path = "crates/component_preview" }
|
||||
command_palette = { path = "crates/command_palette" }
|
||||
command_palette_hooks = { path = "crates/command_palette_hooks" }
|
||||
context_server = { path = "crates/context_server" }
|
||||
@@ -393,9 +397,11 @@ itertools = "0.13.0"
|
||||
jsonwebtoken = "9.3"
|
||||
jupyter-protocol = { version = "0.5.0" }
|
||||
jupyter-websocket-client = { version = "0.8.0" }
|
||||
lazy_static = "1.5.0"
|
||||
libc = "0.2"
|
||||
libsqlite3-sys = { version = "0.30.1", features = ["bundled"] }
|
||||
linkify = "0.10.0"
|
||||
linkme = "0.3"
|
||||
livekit = { git = "https://github.com/zed-industries/rust-sdks", rev="799f10133d93ba2a88642cd480d01ec4da53408c", features = ["dispatcher", "services-dispatcher", "rustls-tls-native-roots"], default-features = false }
|
||||
log = { version = "0.4.16", features = ["kv_unstable_serde", "serde"] }
|
||||
markup5ever_rcdom = "0.3.0"
|
||||
|
||||
22
crates/component_preview/Cargo.toml
Normal file
22
crates/component_preview/Cargo.toml
Normal file
@@ -0,0 +1,22 @@
|
||||
[package]
|
||||
name = "component_preview"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
publish = false
|
||||
license = "GPL-3.0-or-later"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[lib]
|
||||
path = "src/component_preview.rs"
|
||||
|
||||
[dependencies]
|
||||
gpui.workspace = true
|
||||
ui.workspace = true
|
||||
component_system.workspace = true
|
||||
strum.workspace = true
|
||||
workspace.workspace = true
|
||||
|
||||
[features]
|
||||
default = []
|
||||
110
crates/component_preview/src/component_preview.rs
Normal file
110
crates/component_preview/src/component_preview.rs
Normal file
@@ -0,0 +1,110 @@
|
||||
//! # Component Preview
|
||||
//!
|
||||
//! A view for exploring Zed components.
|
||||
|
||||
use component_system::components;
|
||||
use gpui::{prelude::*, AppContext, EventEmitter, FocusHandle, FocusableView};
|
||||
use strum::{EnumIter, IntoEnumIterator};
|
||||
use ui::{prelude::*, TintColor};
|
||||
|
||||
use workspace::{item::ItemEvent, Item, Workspace, WorkspaceId};
|
||||
|
||||
pub fn init(cx: &mut AppContext) {
|
||||
cx.observe_new_views(|workspace: &mut Workspace, _cx| {
|
||||
workspace.register_action(|workspace, _: &workspace::ComponentPreview, cx| {
|
||||
let component_preview = cx.new_view(ComponentPreview::new);
|
||||
workspace.add_item_to_active_pane(Box::new(component_preview), None, true, cx)
|
||||
});
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
|
||||
struct ComponentPreview {
|
||||
focus_handle: FocusHandle,
|
||||
}
|
||||
|
||||
impl ComponentPreview {
|
||||
pub fn new(cx: &mut ViewContext<Self>) -> Self {
|
||||
Self {
|
||||
focus_handle: cx.focus_handle(),
|
||||
}
|
||||
}
|
||||
|
||||
fn render_sidebar(&self, cx: &ViewContext<Self>) -> impl IntoElement {
|
||||
h_flex().children(components().all().iter().map(|component| {
|
||||
Button::new(component.name().clone(), component.name()).on_click(cx.listener(
|
||||
move |_this, _, _cx| {
|
||||
// Handle button click
|
||||
},
|
||||
))
|
||||
}))
|
||||
}
|
||||
|
||||
fn render_preview(&self, cx: &ViewContext<Self>) -> impl IntoElement {
|
||||
v_flex()
|
||||
.size_full()
|
||||
.children(components().all_previews().iter().map(|component| {
|
||||
if let Some(preview) = component.preview() {
|
||||
preview(cx)
|
||||
} else {
|
||||
div().into_any_element()
|
||||
}
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for ComponentPreview {
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
h_flex()
|
||||
.id("component-preview")
|
||||
.key_context("ComponentPreview")
|
||||
.items_start()
|
||||
.overflow_hidden()
|
||||
.size_full()
|
||||
.max_h_full()
|
||||
.track_focus(&self.focus_handle)
|
||||
.px_2()
|
||||
.bg(cx.theme().colors().editor_background)
|
||||
.child(self.render_sidebar(cx))
|
||||
.child(self.render_preview(cx))
|
||||
}
|
||||
}
|
||||
|
||||
impl EventEmitter<ItemEvent> for ComponentPreview {}
|
||||
|
||||
impl FocusableView for ComponentPreview {
|
||||
fn focus_handle(&self, _: &AppContext) -> gpui::FocusHandle {
|
||||
self.focus_handle.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl Item for ComponentPreview {
|
||||
type Event = ItemEvent;
|
||||
|
||||
fn tab_content_text(&self, _cx: &WindowContext) -> Option<SharedString> {
|
||||
Some("Component Preview".into())
|
||||
}
|
||||
|
||||
fn telemetry_event_text(&self) -> Option<&'static str> {
|
||||
None
|
||||
}
|
||||
|
||||
fn show_toolbar(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn clone_on_split(
|
||||
&self,
|
||||
_workspace_id: Option<WorkspaceId>,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Option<gpui::View<Self>>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
Some(cx.new_view(Self::new))
|
||||
}
|
||||
|
||||
fn to_item_events(event: &Self::Event, mut f: impl FnMut(workspace::item::ItemEvent)) {
|
||||
f(*event)
|
||||
}
|
||||
}
|
||||
23
crates/component_system/Cargo.toml
Normal file
23
crates/component_system/Cargo.toml
Normal file
@@ -0,0 +1,23 @@
|
||||
[package]
|
||||
name = "component_system"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
publish = false
|
||||
license = "GPL-3.0-or-later"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[lib]
|
||||
path = "src/component_system.rs"
|
||||
|
||||
[dependencies]
|
||||
gpui.workspace = true
|
||||
lazy_static.workspace = true
|
||||
linkme.workspace = true
|
||||
once_cell.workspace = true
|
||||
collections.workspace = true
|
||||
parking_lot.workspace = true
|
||||
|
||||
[features]
|
||||
default = []
|
||||
142
crates/component_system/src/component_system.rs
Normal file
142
crates/component_system/src/component_system.rs
Normal file
@@ -0,0 +1,142 @@
|
||||
use collections::HashMap;
|
||||
use gpui::{AnyElement, SharedString, WindowContext};
|
||||
use linkme::distributed_slice;
|
||||
use once_cell::sync::Lazy;
|
||||
use parking_lot::RwLock;
|
||||
|
||||
pub trait Component {
|
||||
fn scope() -> Option<&'static str>;
|
||||
fn name() -> &'static str {
|
||||
std::any::type_name::<Self>()
|
||||
}
|
||||
fn description() -> Option<&'static str> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub trait ComponentPreview: Component {
|
||||
fn preview(_cx: &WindowContext) -> AnyElement;
|
||||
}
|
||||
|
||||
#[distributed_slice]
|
||||
pub static __ALL_COMPONENTS: [fn()] = [..];
|
||||
|
||||
#[distributed_slice]
|
||||
pub static __ALL_PREVIEWS: [fn()] = [..];
|
||||
|
||||
pub static COMPONENT_DATA: Lazy<RwLock<ComponentData>> =
|
||||
Lazy::new(|| RwLock::new(ComponentData::new()));
|
||||
|
||||
pub struct ComponentData {
|
||||
components: Vec<(Option<&'static str>, &'static str, Option<&'static str>)>,
|
||||
previews: HashMap<&'static str, fn(&WindowContext) -> AnyElement>,
|
||||
}
|
||||
|
||||
impl ComponentData {
|
||||
fn new() -> Self {
|
||||
ComponentData {
|
||||
components: Vec::new(),
|
||||
previews: HashMap::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn init() {
|
||||
let component_fns: Vec<_> = __ALL_COMPONENTS.iter().cloned().collect();
|
||||
let preview_fns: Vec<_> = __ALL_PREVIEWS.iter().cloned().collect();
|
||||
|
||||
for f in component_fns {
|
||||
f();
|
||||
}
|
||||
for f in preview_fns {
|
||||
f();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn register_component<T: Component>() {
|
||||
let component_data = (T::scope(), T::name(), T::description());
|
||||
COMPONENT_DATA.write().components.push(component_data);
|
||||
}
|
||||
|
||||
pub fn register_preview<T: ComponentPreview>() {
|
||||
let preview_data = (T::name(), T::preview as fn(&WindowContext) -> AnyElement);
|
||||
COMPONENT_DATA
|
||||
.write()
|
||||
.previews
|
||||
.insert(preview_data.0, preview_data.1);
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub struct ComponentId(pub &'static str);
|
||||
|
||||
pub struct ComponentMetadata {
|
||||
name: SharedString,
|
||||
scope: Option<SharedString>,
|
||||
description: Option<SharedString>,
|
||||
preview: Option<fn(&WindowContext) -> AnyElement>,
|
||||
}
|
||||
|
||||
impl ComponentMetadata {
|
||||
pub fn name(&self) -> SharedString {
|
||||
self.name.clone()
|
||||
}
|
||||
|
||||
pub fn scope(&self) -> Option<SharedString> {
|
||||
self.scope.clone()
|
||||
}
|
||||
|
||||
pub fn description(&self) -> Option<SharedString> {
|
||||
self.description.clone()
|
||||
}
|
||||
|
||||
pub fn preview(&self) -> Option<fn(&WindowContext) -> AnyElement> {
|
||||
self.preview
|
||||
}
|
||||
}
|
||||
|
||||
pub struct AllComponents(pub HashMap<ComponentId, ComponentMetadata>);
|
||||
|
||||
impl AllComponents {
|
||||
pub fn new() -> Self {
|
||||
AllComponents(HashMap::default())
|
||||
}
|
||||
|
||||
pub fn add(&mut self, id: ComponentId, metadata: ComponentMetadata) {
|
||||
self.0.insert(id, metadata);
|
||||
}
|
||||
|
||||
pub fn get(&self, id: &ComponentId) -> Option<&ComponentMetadata> {
|
||||
self.0.get(id)
|
||||
}
|
||||
|
||||
/// Returns all components with previews
|
||||
pub fn all_previews(&self) -> Vec<&ComponentMetadata> {
|
||||
self.0.values().filter(|c| c.preview.is_some()).collect()
|
||||
}
|
||||
|
||||
/// Returns all components
|
||||
pub fn all(&self) -> Vec<&ComponentMetadata> {
|
||||
self.0.values().collect()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn components() -> AllComponents {
|
||||
let data = COMPONENT_DATA.read();
|
||||
let mut all_components = AllComponents::new();
|
||||
|
||||
for &(scope, name, description) in &data.components {
|
||||
let scope = scope.map(Into::into);
|
||||
let preview = data.previews.get(name).cloned();
|
||||
all_components.add(
|
||||
ComponentId(name),
|
||||
ComponentMetadata {
|
||||
name: name.into(),
|
||||
scope,
|
||||
description: description.map(Into::into),
|
||||
preview,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
all_components
|
||||
}
|
||||
@@ -80,7 +80,7 @@ gpui_macros.workspace = true
|
||||
http_client = { optional = true, workspace = true }
|
||||
image = "0.25.1"
|
||||
itertools.workspace = true
|
||||
linkme = "0.3"
|
||||
linkme.workspace = true
|
||||
log.workspace = true
|
||||
num_cpus = "1.13"
|
||||
parking = "2.0.0"
|
||||
|
||||
@@ -14,8 +14,12 @@ path = "src/ui.rs"
|
||||
|
||||
[dependencies]
|
||||
chrono.workspace = true
|
||||
collections.workspace = true
|
||||
gpui.workspace = true
|
||||
itertools = { workspace = true, optional = true }
|
||||
lazy_static.workspace = true
|
||||
linkme.workspace = true
|
||||
once_cell.workspace = true
|
||||
menu.workspace = true
|
||||
serde.workspace = true
|
||||
settings.workspace = true
|
||||
@@ -24,6 +28,7 @@ story = { workspace = true, optional = true }
|
||||
strum = { workspace = true, features = ["derive"] }
|
||||
theme.workspace = true
|
||||
ui_macros.workspace = true
|
||||
component_system.workspace = true
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
windows.workspace = true
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
use crate::prelude::*;
|
||||
use crate::{
|
||||
prelude::*,
|
||||
utils::{component_preview, component_preview_group},
|
||||
Indicator,
|
||||
};
|
||||
|
||||
use component_system::ComponentPreview;
|
||||
use gpui::{img, AnyElement, Hsla, ImageSource, Img, IntoElement, Styled};
|
||||
use ui_macros::IntoComponent;
|
||||
|
||||
/// An element that renders a user avatar with customizable appearance options.
|
||||
///
|
||||
@@ -14,7 +20,7 @@ use gpui::{img, AnyElement, Hsla, ImageSource, Img, IntoElement, Styled};
|
||||
/// .grayscale(true)
|
||||
/// .border_color(gpui::red());
|
||||
/// ```
|
||||
#[derive(IntoElement)]
|
||||
#[derive(IntoElement, IntoComponent)]
|
||||
pub struct Avatar {
|
||||
image: Img,
|
||||
size: Option<AbsoluteLength>,
|
||||
@@ -96,3 +102,35 @@ impl RenderOnce for Avatar {
|
||||
.children(self.indicator.map(|indicator| div().child(indicator)))
|
||||
}
|
||||
}
|
||||
|
||||
impl ComponentPreview for Avatar {
|
||||
fn preview(cx: &WindowContext) -> AnyElement {
|
||||
let avatar_1 = "https://avatars.githubusercontent.com/u/1789?s=70&v=4";
|
||||
let avatar_2 = "https://avatars.githubusercontent.com/u/482957?s=70&v=4";
|
||||
let avatar_3 = "https://avatars.githubusercontent.com/u/326587?s=70&v=4";
|
||||
|
||||
v_flex()
|
||||
.gap_4()
|
||||
.child(
|
||||
component_preview_group()
|
||||
.child(component_preview("Default").child(Avatar::new(avatar_1)))
|
||||
.child(
|
||||
component_preview("Custom Size").child(Avatar::new(avatar_2).size(px(48.))),
|
||||
)
|
||||
.child(
|
||||
component_preview("Grayscale").child(Avatar::new(avatar_3).grayscale(true)),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
component_preview_group()
|
||||
.child(
|
||||
component_preview("With Border")
|
||||
.child(Avatar::new(avatar_1).border_color(cx.theme().colors().border)),
|
||||
)
|
||||
.child(component_preview("With Indicator").child(
|
||||
Avatar::new(avatar_2).indicator(Indicator::dot().color(Color::Success)),
|
||||
)),
|
||||
)
|
||||
.into_any_element()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#![allow(missing_docs)]
|
||||
use gpui::{AnyView, DefiniteLength};
|
||||
use gpui::{AnyElement, AnyView, DefiniteLength};
|
||||
|
||||
use crate::{
|
||||
prelude::*, Color, DynamicSpacing, ElevationIndex, IconPosition, KeyBinding, TintColor,
|
||||
@@ -440,102 +440,87 @@ impl RenderOnce for Button {
|
||||
}
|
||||
}
|
||||
|
||||
impl ComponentPreview for Button {
|
||||
fn description() -> impl Into<Option<&'static str>> {
|
||||
"A button allows users to take actions, and make choices, with a single tap."
|
||||
}
|
||||
// register_components!(input, [Button]);
|
||||
|
||||
fn examples(_: &WindowContext) -> Vec<ComponentExampleGroup<Self>> {
|
||||
vec![
|
||||
example_group_with_title(
|
||||
"Styles",
|
||||
vec![
|
||||
single_example("Default", Button::new("default", "Default")),
|
||||
single_example(
|
||||
"Filled",
|
||||
Button::new("filled", "Filled").style(ButtonStyle::Filled),
|
||||
),
|
||||
single_example(
|
||||
"Subtle",
|
||||
Button::new("outline", "Subtle").style(ButtonStyle::Subtle),
|
||||
),
|
||||
single_example(
|
||||
"Transparent",
|
||||
Button::new("transparent", "Transparent").style(ButtonStyle::Transparent),
|
||||
),
|
||||
],
|
||||
),
|
||||
example_group_with_title(
|
||||
"Tinted",
|
||||
vec![
|
||||
single_example(
|
||||
"Accent",
|
||||
Button::new("tinted_accent", "Accent")
|
||||
.style(ButtonStyle::Tinted(TintColor::Accent)),
|
||||
),
|
||||
single_example(
|
||||
"Negative",
|
||||
Button::new("tinted_negative", "Negative")
|
||||
.style(ButtonStyle::Tinted(TintColor::Negative)),
|
||||
),
|
||||
single_example(
|
||||
"Warning",
|
||||
Button::new("tinted_warning", "Warning")
|
||||
.style(ButtonStyle::Tinted(TintColor::Warning)),
|
||||
),
|
||||
single_example(
|
||||
"Positive",
|
||||
Button::new("tinted_positive", "Positive")
|
||||
.style(ButtonStyle::Tinted(TintColor::Positive)),
|
||||
),
|
||||
],
|
||||
),
|
||||
example_group_with_title(
|
||||
"States",
|
||||
vec![
|
||||
single_example("Default", Button::new("default_state", "Default")),
|
||||
single_example(
|
||||
"Disabled",
|
||||
Button::new("disabled", "Disabled").disabled(true),
|
||||
),
|
||||
single_example(
|
||||
"Selected",
|
||||
Button::new("selected", "Selected").selected(true),
|
||||
),
|
||||
],
|
||||
),
|
||||
example_group_with_title(
|
||||
"With Icons",
|
||||
vec![
|
||||
single_example(
|
||||
"Icon Start",
|
||||
Button::new("icon_start", "Icon Start")
|
||||
.icon(IconName::Check)
|
||||
.icon_position(IconPosition::Start),
|
||||
),
|
||||
single_example(
|
||||
"Icon End",
|
||||
Button::new("icon_end", "Icon End")
|
||||
.icon(IconName::Check)
|
||||
.icon_position(IconPosition::End),
|
||||
),
|
||||
single_example(
|
||||
"Icon Color",
|
||||
Button::new("icon_color", "Icon Color")
|
||||
.icon(IconName::Check)
|
||||
.icon_color(Color::Accent),
|
||||
),
|
||||
single_example(
|
||||
"Tinted Icons",
|
||||
Button::new("icon_color", "Delete")
|
||||
.style(ButtonStyle::Tinted(TintColor::Negative))
|
||||
.color(Color::Error)
|
||||
.icon_color(Color::Error)
|
||||
.icon(IconName::Trash)
|
||||
.icon_position(IconPosition::Start),
|
||||
),
|
||||
],
|
||||
),
|
||||
]
|
||||
}
|
||||
}
|
||||
// impl ComponentElement for Button {
|
||||
// fn scope() -> &'static str {
|
||||
// "input"
|
||||
// }
|
||||
|
||||
// fn description() -> impl Into<Option<&'static str>> {
|
||||
// "A button allows users to take actions, and make choices, with a single tap."
|
||||
// }
|
||||
|
||||
// fn preview(_cx: &WindowContext) -> Option<AnyElement> {
|
||||
// Some(
|
||||
// v_flex()
|
||||
// .gap_4()
|
||||
// .child(
|
||||
// h_flex()
|
||||
// .gap_4()
|
||||
// .child(Button::new("default", "Default"))
|
||||
// .child(Button::new("filled", "Filled").style(ButtonStyle::Filled))
|
||||
// .child(Button::new("subtle", "Subtle").style(ButtonStyle::Subtle))
|
||||
// .child(
|
||||
// Button::new("transparent", "Transparent")
|
||||
// .style(ButtonStyle::Transparent),
|
||||
// ),
|
||||
// )
|
||||
// .child(
|
||||
// h_flex()
|
||||
// .gap_4()
|
||||
// .child(
|
||||
// Button::new("tinted_accent", "Accent")
|
||||
// .style(ButtonStyle::Tinted(TintColor::Accent)),
|
||||
// )
|
||||
// .child(
|
||||
// Button::new("tinted_negative", "Negative")
|
||||
// .style(ButtonStyle::Tinted(TintColor::Negative)),
|
||||
// )
|
||||
// .child(
|
||||
// Button::new("tinted_warning", "Warning")
|
||||
// .style(ButtonStyle::Tinted(TintColor::Warning)),
|
||||
// )
|
||||
// .child(
|
||||
// Button::new("tinted_positive", "Positive")
|
||||
// .style(ButtonStyle::Tinted(TintColor::Positive)),
|
||||
// ),
|
||||
// )
|
||||
// .child(
|
||||
// h_flex()
|
||||
// .gap_4()
|
||||
// .child(Button::new("default_state", "Default"))
|
||||
// .child(Button::new("disabled", "Disabled").disabled(true))
|
||||
// .child(Button::new("selected", "Selected").selected(true)),
|
||||
// )
|
||||
// .child(
|
||||
// h_flex()
|
||||
// .gap_4()
|
||||
// .child(
|
||||
// Button::new("icon_start", "Icon Start")
|
||||
// .icon(IconName::Check)
|
||||
// .icon_position(IconPosition::Start),
|
||||
// )
|
||||
// .child(
|
||||
// Button::new("icon_end", "Icon End")
|
||||
// .icon(IconName::Check)
|
||||
// .icon_position(IconPosition::End),
|
||||
// )
|
||||
// .child(
|
||||
// Button::new("icon_color", "Icon Color")
|
||||
// .icon(IconName::Check)
|
||||
// .icon_color(Color::Accent),
|
||||
// )
|
||||
// .child(
|
||||
// Button::new("icon_color", "Delete")
|
||||
// .style(ButtonStyle::Tinted(TintColor::Negative))
|
||||
// .color(Color::Error)
|
||||
// .icon_color(Color::Error)
|
||||
// .icon(IconName::Trash)
|
||||
// .icon_position(IconPosition::Start),
|
||||
// ),
|
||||
// )
|
||||
// .into_any_element(),
|
||||
// )
|
||||
// }
|
||||
// }
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
#![allow(missing_docs)]
|
||||
use crate::{prelude::*, Icon, IconName, IconSize};
|
||||
use gpui::AnyElement;
|
||||
|
||||
use crate::{prelude::*, Icon, IconName, IconSize, TintColor};
|
||||
|
||||
/// An icon that appears within a button.
|
||||
///
|
||||
@@ -99,3 +101,53 @@ impl RenderOnce for ButtonIcon {
|
||||
Icon::new(icon).size(self.size).color(icon_color)
|
||||
}
|
||||
}
|
||||
|
||||
// register_components!(input, [ButtonIcon]);
|
||||
|
||||
// impl ComponentElement for ButtonIcon {
|
||||
// fn scope() -> &'static str {
|
||||
// "input"
|
||||
// }
|
||||
|
||||
// fn description() -> impl Into<Option<&'static str>> {
|
||||
// "A ButtonIcon is an icon that appears within a button, used either alongside a label or as a standalone icon."
|
||||
// }
|
||||
|
||||
// fn preview(_cx: &WindowContext) -> Option<AnyElement> {
|
||||
// Some(
|
||||
// v_flex()
|
||||
// .gap_4()
|
||||
// .child(
|
||||
// h_flex()
|
||||
// .gap_4()
|
||||
// .child(ButtonIcon::new(IconName::Check))
|
||||
// .child(ButtonIcon::new(IconName::Check).color(Color::Accent))
|
||||
// .child(ButtonIcon::new(IconName::Check).size(IconSize::XSmall))
|
||||
// .child(ButtonIcon::new(IconName::Check).size(IconSize::Small)),
|
||||
// )
|
||||
// .child(
|
||||
// h_flex()
|
||||
// .gap_4()
|
||||
// .child(ButtonIcon::new(IconName::Check).selected(true))
|
||||
// .child(ButtonIcon::new(IconName::Check).disabled(true))
|
||||
// .child(ButtonIcon::new(IconName::Check).selected_icon(IconName::X))
|
||||
// .child(ButtonIcon::new(IconName::Check).selected_icon_color(Color::Error)),
|
||||
// )
|
||||
// .child(
|
||||
// h_flex()
|
||||
// .gap_4()
|
||||
// .child(ButtonIcon::new(IconName::Check).selected_style(ButtonStyle::Filled))
|
||||
// .child(ButtonIcon::new(IconName::Check).selected_style(ButtonStyle::Subtle))
|
||||
// .child(
|
||||
// ButtonIcon::new(IconName::Check)
|
||||
// .selected_style(ButtonStyle::Transparent),
|
||||
// )
|
||||
// .child(
|
||||
// ButtonIcon::new(IconName::Check)
|
||||
// .selected_style(ButtonStyle::Tinted(TintColor::Accent)),
|
||||
// ),
|
||||
// )
|
||||
// .into_any_element(),
|
||||
// )
|
||||
// }
|
||||
// }
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#![allow(missing_docs)]
|
||||
use gpui::{AnyView, DefiniteLength};
|
||||
use gpui::{AnyElement, AnyView, DefiniteLength};
|
||||
|
||||
use super::button_like::{ButtonCommon, ButtonLike, ButtonSize, ButtonStyle};
|
||||
use crate::{prelude::*, ElevationIndex, SelectableButton};
|
||||
use crate::{prelude::*, ElevationIndex, SelectableButton, TintColor};
|
||||
use crate::{IconName, IconSize};
|
||||
|
||||
use super::button_icon::ButtonIcon;
|
||||
@@ -165,3 +165,86 @@ impl RenderOnce for IconButton {
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// register_components!(input, [IconButton]);
|
||||
|
||||
// impl ComponentElement for IconButton {
|
||||
// fn scope() -> &'static str {
|
||||
// "input"
|
||||
// }
|
||||
|
||||
// fn description() -> impl Into<Option<&'static str>> {
|
||||
// "An icon button allows users to take actions with a single tap, represented by an icon."
|
||||
// }
|
||||
|
||||
// fn preview(_cx: &WindowContext) -> Option<AnyElement> {
|
||||
// Some(
|
||||
// v_flex()
|
||||
// .gap_4()
|
||||
// .child(
|
||||
// h_flex()
|
||||
// .gap_4()
|
||||
// .child(IconButton::new("default", IconName::Check))
|
||||
// .child(
|
||||
// IconButton::new("filled", IconName::Check).style(ButtonStyle::Filled),
|
||||
// )
|
||||
// .child(
|
||||
// IconButton::new("subtle", IconName::Check).style(ButtonStyle::Subtle),
|
||||
// )
|
||||
// .child(
|
||||
// IconButton::new("transparent", IconName::Check)
|
||||
// .style(ButtonStyle::Transparent),
|
||||
// ),
|
||||
// )
|
||||
// .child(
|
||||
// h_flex()
|
||||
// .gap_4()
|
||||
// .child(
|
||||
// IconButton::new("tinted_accent", IconName::Check)
|
||||
// .style(ButtonStyle::Tinted(TintColor::Accent)),
|
||||
// )
|
||||
// .child(
|
||||
// IconButton::new("tinted_negative", IconName::Check)
|
||||
// .style(ButtonStyle::Tinted(TintColor::Negative)),
|
||||
// )
|
||||
// .child(
|
||||
// IconButton::new("tinted_warning", IconName::Check)
|
||||
// .style(ButtonStyle::Tinted(TintColor::Warning)),
|
||||
// )
|
||||
// .child(
|
||||
// IconButton::new("tinted_positive", IconName::Check)
|
||||
// .style(ButtonStyle::Tinted(TintColor::Positive)),
|
||||
// ),
|
||||
// )
|
||||
// .child(
|
||||
// h_flex()
|
||||
// .gap_4()
|
||||
// .child(IconButton::new("default_state", IconName::Check))
|
||||
// .child(IconButton::new("disabled", IconName::Check).disabled(true))
|
||||
// .child(IconButton::new("selected", IconName::Check).selected(true)),
|
||||
// )
|
||||
// .child(
|
||||
// h_flex()
|
||||
// .gap_4()
|
||||
// .child(
|
||||
// IconButton::new("icon_size_small", IconName::Check)
|
||||
// .icon_size(IconSize::Small),
|
||||
// )
|
||||
// .child(
|
||||
// IconButton::new("icon_size_medium", IconName::Check)
|
||||
// .icon_size(IconSize::Medium),
|
||||
// )
|
||||
// .child(
|
||||
// IconButton::new("icon_color", IconName::Check)
|
||||
// .icon_color(Color::Accent),
|
||||
// )
|
||||
// .child(
|
||||
// IconButton::new("icon_delete", IconName::Trash)
|
||||
// .style(ButtonStyle::Tinted(TintColor::Negative))
|
||||
// .icon_color(Color::Error),
|
||||
// ),
|
||||
// )
|
||||
// .into_any_element(),
|
||||
// )
|
||||
// }
|
||||
// }
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
#![allow(missing_docs)]
|
||||
|
||||
use gpui::{div, prelude::*, ElementId, IntoElement, Styled, WindowContext};
|
||||
|
||||
use crate::prelude::*;
|
||||
use crate::{Color, Icon, IconName, Selection};
|
||||
use gpui::{div, prelude::*, ElementId, IntoElement, Styled, WindowContext};
|
||||
use std::sync::Arc;
|
||||
|
||||
/// # Checkbox
|
||||
///
|
||||
@@ -113,55 +112,38 @@ impl RenderOnce for Checkbox {
|
||||
}
|
||||
}
|
||||
|
||||
impl ComponentPreview for Checkbox {
|
||||
fn description() -> impl Into<Option<&'static str>> {
|
||||
"A checkbox lets people choose between a pair of opposing states, like enabled and disabled, using a different appearance to indicate each state."
|
||||
}
|
||||
// impl ComponentElement for Checkbox {
|
||||
// fn description() -> impl Into<Option<&'static str>> {
|
||||
// "A checkbox allows people to toggle between two states, typically representing on/off, or a pair of opposites."
|
||||
// }
|
||||
|
||||
fn examples(_: &WindowContext) -> Vec<ComponentExampleGroup<Self>> {
|
||||
vec![
|
||||
example_group_with_title(
|
||||
"Default",
|
||||
vec![
|
||||
single_example(
|
||||
"Unselected",
|
||||
Checkbox::new("checkbox_unselected", Selection::Unselected),
|
||||
),
|
||||
single_example(
|
||||
"Indeterminate",
|
||||
Checkbox::new("checkbox_indeterminate", Selection::Indeterminate),
|
||||
),
|
||||
single_example(
|
||||
"Selected",
|
||||
Checkbox::new("checkbox_selected", Selection::Selected),
|
||||
),
|
||||
],
|
||||
),
|
||||
example_group_with_title(
|
||||
"Disabled",
|
||||
vec![
|
||||
single_example(
|
||||
"Unselected",
|
||||
Checkbox::new("checkbox_disabled_unselected", Selection::Unselected)
|
||||
.disabled(true),
|
||||
),
|
||||
single_example(
|
||||
"Indeterminate",
|
||||
Checkbox::new("checkbox_disabled_indeterminate", Selection::Indeterminate)
|
||||
.disabled(true),
|
||||
),
|
||||
single_example(
|
||||
"Selected",
|
||||
Checkbox::new("checkbox_disabled_selected", Selection::Selected)
|
||||
.disabled(true),
|
||||
),
|
||||
],
|
||||
),
|
||||
]
|
||||
}
|
||||
}
|
||||
// fn scope() -> &'static str {
|
||||
// "input"
|
||||
// }
|
||||
|
||||
use std::sync::Arc;
|
||||
// fn preview(_cx: &WindowContext) -> Option<gpui::AnyElement> {
|
||||
// Some(
|
||||
// component_preview_group()
|
||||
// .child(
|
||||
// component_preview("Default")
|
||||
// .child(Checkbox::new("checkbox-1", Selection::Unselected)),
|
||||
// )
|
||||
// .child(
|
||||
// component_preview("Selected")
|
||||
// .child(Checkbox::new("checkbox-2", Selection::Selected)),
|
||||
// )
|
||||
// .child(
|
||||
// component_preview("Indeterminate")
|
||||
// .child(Checkbox::new("checkbox-3", Selection::Indeterminate)),
|
||||
// )
|
||||
// .child(
|
||||
// component_preview("Disabled")
|
||||
// .child(Checkbox::new("checkbox-4", Selection::Selected).disabled(true)),
|
||||
// )
|
||||
// .into_any_element(),
|
||||
// )
|
||||
// }
|
||||
// }
|
||||
|
||||
/// A [`Checkbox`] that has a [`Label`].
|
||||
#[derive(IntoElement)]
|
||||
@@ -209,40 +191,64 @@ impl RenderOnce for CheckboxWithLabel {
|
||||
}
|
||||
}
|
||||
|
||||
impl ComponentPreview for CheckboxWithLabel {
|
||||
fn description() -> impl Into<Option<&'static str>> {
|
||||
"A checkbox with an associated label, allowing users to select an option while providing a descriptive text."
|
||||
}
|
||||
// impl ComponentElement for CheckboxWithLabel {
|
||||
// fn description() -> impl Into<Option<&'static str>> {
|
||||
// "A checkbox with an associated label."
|
||||
// }
|
||||
|
||||
fn examples(_: &WindowContext) -> Vec<ComponentExampleGroup<Self>> {
|
||||
vec![example_group(vec![
|
||||
single_example(
|
||||
"Unselected",
|
||||
CheckboxWithLabel::new(
|
||||
"checkbox_with_label_unselected",
|
||||
Label::new("Always save on quit"),
|
||||
Selection::Unselected,
|
||||
|_, _| {},
|
||||
),
|
||||
),
|
||||
single_example(
|
||||
"Indeterminate",
|
||||
CheckboxWithLabel::new(
|
||||
"checkbox_with_label_indeterminate",
|
||||
Label::new("Always save on quit"),
|
||||
Selection::Indeterminate,
|
||||
|_, _| {},
|
||||
),
|
||||
),
|
||||
single_example(
|
||||
"Selected",
|
||||
CheckboxWithLabel::new(
|
||||
"checkbox_with_label_selected",
|
||||
Label::new("Always save on quit"),
|
||||
Selection::Selected,
|
||||
|_, _| {},
|
||||
),
|
||||
),
|
||||
])]
|
||||
}
|
||||
}
|
||||
// fn scope() -> &'static str {
|
||||
// "input"
|
||||
// }
|
||||
|
||||
// fn preview(_cx: &WindowContext) -> Option<gpui::AnyElement> {
|
||||
// Some(
|
||||
// v_flex()
|
||||
// .gap_3()
|
||||
// .child(
|
||||
// component_preview_group()
|
||||
// .child(component_preview("Default").child(CheckboxWithLabel::new(
|
||||
// "checkbox-1",
|
||||
// Label::new("Show Completions"),
|
||||
// Selection::Unselected,
|
||||
// |_, _| {},
|
||||
// )))
|
||||
// .child(component_preview("Selected").child(CheckboxWithLabel::new(
|
||||
// "checkbox-2",
|
||||
// Label::new("Show Completions"),
|
||||
// Selection::Selected,
|
||||
// |_, _| {},
|
||||
// ))),
|
||||
// )
|
||||
// .child(
|
||||
// component_preview_group().child(
|
||||
// component_preview("Indeterminate").child(
|
||||
// v_flex()
|
||||
// .child(CheckboxWithLabel::new(
|
||||
// "checkbox-3",
|
||||
// Label::new("Show Completions"),
|
||||
// Selection::Indeterminate,
|
||||
// |_, _| {},
|
||||
// ))
|
||||
// .child(h_flex().child(div().w_5().h_full()).child(
|
||||
// CheckboxWithLabel::new(
|
||||
// "checkbox-4",
|
||||
// Label::new("Editor"),
|
||||
// Selection::Selected,
|
||||
// |_, _| {},
|
||||
// ),
|
||||
// ))
|
||||
// .child(h_flex().child(div().w_5().h_full()).child(
|
||||
// CheckboxWithLabel::new(
|
||||
// "checkbox-5",
|
||||
// Label::new("Assistant"),
|
||||
// Selection::Unselected,
|
||||
// |_, _| {},
|
||||
// ),
|
||||
// )),
|
||||
// ),
|
||||
// ),
|
||||
// )
|
||||
// .into_any_element(),
|
||||
// )
|
||||
// }
|
||||
// }
|
||||
|
||||
@@ -26,7 +26,9 @@ pub fn h_group() -> ContentGroup {
|
||||
pub struct ContentGroup {
|
||||
base: Div,
|
||||
border: bool,
|
||||
padding: Pixels,
|
||||
fill: bool,
|
||||
outset_amount: Option<AbsoluteLength>,
|
||||
children: SmallVec<[AnyElement; 2]>,
|
||||
}
|
||||
|
||||
@@ -36,7 +38,9 @@ impl ContentGroup {
|
||||
Self {
|
||||
base: div(),
|
||||
border: true,
|
||||
padding: px(8.),
|
||||
fill: true,
|
||||
outset_amount: None,
|
||||
children: SmallVec::new(),
|
||||
}
|
||||
}
|
||||
@@ -52,6 +56,25 @@ impl ContentGroup {
|
||||
self.fill = false;
|
||||
self
|
||||
}
|
||||
|
||||
/// Outset the [ContentBox] to create a visual "outset" effect.
|
||||
///
|
||||
/// This sets a default outset amount which matches the width of the border and padding,
|
||||
/// useful for lining up content with other elements.
|
||||
pub fn outset(mut self) -> Self {
|
||||
let border_width = if self.border { px(1.) } else { px(0.) };
|
||||
let outset_amount = self.padding + border_width;
|
||||
self.outset_amount = Some(outset_amount.into());
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the amount of outset to apply to the [ContentBox].
|
||||
///
|
||||
/// This will add negative left and right margin to the [ContentBox] to create a visual "outset" effect.
|
||||
pub fn outset_amount(mut self, amount: impl Into<AbsoluteLength>) -> Self {
|
||||
self.outset_amount = Some(amount.into());
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl ParentElement for ContentGroup {
|
||||
@@ -81,55 +104,14 @@ impl RenderOnce for ContentGroup {
|
||||
this.border_1().border_color(cx.theme().colors().border)
|
||||
})
|
||||
.rounded_md()
|
||||
.p_2()
|
||||
.p(self.padding)
|
||||
.when_some(self.outset_amount, |this, amount| {
|
||||
let outset_rems: Rems = match amount {
|
||||
AbsoluteLength::Pixels(p) => rems(p / cx.rem_size()),
|
||||
AbsoluteLength::Rems(r) => r,
|
||||
};
|
||||
this.mx(-outset_rems)
|
||||
})
|
||||
.children(self.children)
|
||||
}
|
||||
}
|
||||
|
||||
impl ComponentPreview for ContentGroup {
|
||||
fn description() -> impl Into<Option<&'static str>> {
|
||||
"A flexible container component that can hold other elements. It can be customized with or without a border and background fill."
|
||||
}
|
||||
|
||||
fn example_label_side() -> ExampleLabelSide {
|
||||
ExampleLabelSide::Bottom
|
||||
}
|
||||
|
||||
fn examples(_: &WindowContext) -> Vec<ComponentExampleGroup<Self>> {
|
||||
vec![example_group(vec![
|
||||
single_example(
|
||||
"Default",
|
||||
ContentGroup::new()
|
||||
.flex_1()
|
||||
.items_center()
|
||||
.justify_center()
|
||||
.h_48()
|
||||
.child(Label::new("Default ContentBox")),
|
||||
)
|
||||
.grow(),
|
||||
single_example(
|
||||
"Without Border",
|
||||
ContentGroup::new()
|
||||
.flex_1()
|
||||
.items_center()
|
||||
.justify_center()
|
||||
.h_48()
|
||||
.borderless()
|
||||
.child(Label::new("Borderless ContentBox")),
|
||||
)
|
||||
.grow(),
|
||||
single_example(
|
||||
"Without Fill",
|
||||
ContentGroup::new()
|
||||
.flex_1()
|
||||
.items_center()
|
||||
.justify_center()
|
||||
.h_48()
|
||||
.unfilled()
|
||||
.child(Label::new("Unfilled ContentBox")),
|
||||
)
|
||||
.grow(),
|
||||
])
|
||||
.grow()]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::{prelude::*, Avatar};
|
||||
use crate::prelude::*;
|
||||
use gpui::{AnyElement, StyleRefinement};
|
||||
use smallvec::SmallVec;
|
||||
|
||||
@@ -59,61 +59,3 @@ impl RenderOnce for Facepile {
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl ComponentPreview for Facepile {
|
||||
fn description() -> impl Into<Option<&'static str>> {
|
||||
"A facepile is a collection of faces stacked horizontally–\
|
||||
always with the leftmost face on top and descending in z-index.\
|
||||
\n\nFacepiles are used to display a group of people or things,\
|
||||
such as a list of participants in a collaboration session."
|
||||
}
|
||||
fn examples(_: &WindowContext) -> Vec<ComponentExampleGroup<Self>> {
|
||||
let few_faces: [&'static str; 3] = [
|
||||
"https://avatars.githubusercontent.com/u/1714999?s=60&v=4",
|
||||
"https://avatars.githubusercontent.com/u/67129314?s=60&v=4",
|
||||
"https://avatars.githubusercontent.com/u/482957?s=60&v=4",
|
||||
];
|
||||
|
||||
let many_faces: [&'static str; 6] = [
|
||||
"https://avatars.githubusercontent.com/u/326587?s=60&v=4",
|
||||
"https://avatars.githubusercontent.com/u/2280405?s=60&v=4",
|
||||
"https://avatars.githubusercontent.com/u/1789?s=60&v=4",
|
||||
"https://avatars.githubusercontent.com/u/67129314?s=60&v=4",
|
||||
"https://avatars.githubusercontent.com/u/482957?s=60&v=4",
|
||||
"https://avatars.githubusercontent.com/u/1714999?s=60&v=4",
|
||||
];
|
||||
|
||||
vec![example_group_with_title(
|
||||
"Examples",
|
||||
vec![
|
||||
single_example(
|
||||
"Few Faces",
|
||||
Facepile::new(
|
||||
few_faces
|
||||
.iter()
|
||||
.map(|&url| Avatar::new(url).into_any_element())
|
||||
.collect(),
|
||||
),
|
||||
),
|
||||
single_example(
|
||||
"Many Faces",
|
||||
Facepile::new(
|
||||
many_faces
|
||||
.iter()
|
||||
.map(|&url| Avatar::new(url).into_any_element())
|
||||
.collect(),
|
||||
),
|
||||
),
|
||||
single_example(
|
||||
"Custom Size",
|
||||
Facepile::new(
|
||||
few_faces
|
||||
.iter()
|
||||
.map(|&url| Avatar::new(url).size(px(24.)).into_any_element())
|
||||
.collect(),
|
||||
),
|
||||
),
|
||||
],
|
||||
)]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,10 @@
|
||||
#![allow(missing_docs)]
|
||||
use gpui::{svg, AnimationElement, Hsla, IntoElement, Point, Rems, Transformation};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use strum::{EnumIter, EnumString, IntoEnumIterator, IntoStaticStr};
|
||||
use strum::{EnumIter, EnumString, IntoStaticStr};
|
||||
use ui_macros::DerivePathStr;
|
||||
|
||||
use crate::{
|
||||
prelude::*,
|
||||
traits::component_preview::{ComponentExample, ComponentPreview},
|
||||
Indicator,
|
||||
};
|
||||
use crate::{prelude::*, Indicator};
|
||||
|
||||
#[derive(IntoElement)]
|
||||
pub enum AnyIcon {
|
||||
@@ -491,26 +487,6 @@ impl RenderOnce for IconDecoration {
|
||||
}
|
||||
}
|
||||
|
||||
impl ComponentPreview for IconDecoration {
|
||||
fn examples(cx: &WindowContext) -> Vec<ComponentExampleGroup<Self>> {
|
||||
let all_kinds = IconDecorationKind::iter().collect::<Vec<_>>();
|
||||
|
||||
let examples = all_kinds
|
||||
.iter()
|
||||
.map(|kind| {
|
||||
let name = format!("{:?}", kind).to_string();
|
||||
|
||||
single_example(
|
||||
name,
|
||||
IconDecoration::new(*kind, cx.theme().colors().surface_background, cx),
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
|
||||
vec![example_group(examples)]
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(IntoElement)]
|
||||
pub struct DecoratedIcon {
|
||||
icon: Icon,
|
||||
@@ -533,66 +509,6 @@ impl RenderOnce for DecoratedIcon {
|
||||
}
|
||||
}
|
||||
|
||||
impl ComponentPreview for DecoratedIcon {
|
||||
fn examples(cx: &WindowContext) -> Vec<ComponentExampleGroup<Self>> {
|
||||
let icon_1 = Icon::new(IconName::FileDoc);
|
||||
let icon_2 = Icon::new(IconName::FileDoc);
|
||||
let icon_3 = Icon::new(IconName::FileDoc);
|
||||
let icon_4 = Icon::new(IconName::FileDoc);
|
||||
|
||||
let decoration_x = IconDecoration::new(
|
||||
IconDecorationKind::X,
|
||||
cx.theme().colors().surface_background,
|
||||
cx,
|
||||
)
|
||||
.color(cx.theme().status().error)
|
||||
.position(Point {
|
||||
x: px(-2.),
|
||||
y: px(-2.),
|
||||
});
|
||||
|
||||
let decoration_triangle = IconDecoration::new(
|
||||
IconDecorationKind::Triangle,
|
||||
cx.theme().colors().surface_background,
|
||||
cx,
|
||||
)
|
||||
.color(cx.theme().status().error)
|
||||
.position(Point {
|
||||
x: px(-2.),
|
||||
y: px(-2.),
|
||||
});
|
||||
|
||||
let decoration_dot = IconDecoration::new(
|
||||
IconDecorationKind::Dot,
|
||||
cx.theme().colors().surface_background,
|
||||
cx,
|
||||
)
|
||||
.color(cx.theme().status().error)
|
||||
.position(Point {
|
||||
x: px(-2.),
|
||||
y: px(-2.),
|
||||
});
|
||||
|
||||
let examples = vec![
|
||||
single_example("no_decoration", DecoratedIcon::new(icon_1, None)),
|
||||
single_example(
|
||||
"with_decoration",
|
||||
DecoratedIcon::new(icon_2, Some(decoration_x)),
|
||||
),
|
||||
single_example(
|
||||
"with_decoration",
|
||||
DecoratedIcon::new(icon_3, Some(decoration_triangle)),
|
||||
),
|
||||
single_example(
|
||||
"with_decoration",
|
||||
DecoratedIcon::new(icon_4, Some(decoration_dot)),
|
||||
),
|
||||
];
|
||||
|
||||
vec![example_group(examples)]
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(IntoElement)]
|
||||
pub struct IconWithIndicator {
|
||||
icon: Icon,
|
||||
@@ -651,26 +567,3 @@ impl RenderOnce for IconWithIndicator {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl ComponentPreview for Icon {
|
||||
fn examples(_cx: &WindowContext) -> Vec<ComponentExampleGroup<Icon>> {
|
||||
let arrow_icons = vec![
|
||||
IconName::ArrowDown,
|
||||
IconName::ArrowLeft,
|
||||
IconName::ArrowRight,
|
||||
IconName::ArrowUp,
|
||||
IconName::ArrowCircle,
|
||||
];
|
||||
|
||||
vec![example_group_with_title(
|
||||
"Arrow Icons",
|
||||
arrow_icons
|
||||
.into_iter()
|
||||
.map(|icon| {
|
||||
let name = format!("{:?}", icon).to_string();
|
||||
ComponentExample::new(name, Icon::new(icon))
|
||||
})
|
||||
.collect(),
|
||||
)]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,34 +83,3 @@ impl RenderOnce for Indicator {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ComponentPreview for Indicator {
|
||||
fn description() -> impl Into<Option<&'static str>> {
|
||||
"An indicator visually represents a status or state."
|
||||
}
|
||||
|
||||
fn examples(_: &WindowContext) -> Vec<ComponentExampleGroup<Self>> {
|
||||
vec![
|
||||
example_group_with_title(
|
||||
"Types",
|
||||
vec![
|
||||
single_example("Dot", Indicator::dot().color(Color::Info)),
|
||||
single_example("Bar", Indicator::bar().color(Color::Player(2))),
|
||||
single_example(
|
||||
"Icon",
|
||||
Indicator::icon(Icon::new(IconName::Check).color(Color::Success)),
|
||||
),
|
||||
],
|
||||
),
|
||||
example_group_with_title(
|
||||
"Examples",
|
||||
vec![
|
||||
single_example("Info", Indicator::dot().color(Color::Info)),
|
||||
single_example("Success", Indicator::dot().color(Color::Success)),
|
||||
single_example("Warning", Indicator::dot().color(Color::Warning)),
|
||||
single_example("Error", Indicator::dot().color(Color::Error)),
|
||||
],
|
||||
),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::{prelude::*, Indicator};
|
||||
use crate::prelude::*;
|
||||
use gpui::{div, AnyElement, FontWeight, IntoElement, Length};
|
||||
|
||||
/// A table component
|
||||
@@ -150,90 +150,3 @@ where
|
||||
TableCell::String(e.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl ComponentPreview for Table {
|
||||
fn description() -> impl Into<Option<&'static str>> {
|
||||
"Used for showing tabular data. Tables may show both text and elements in their cells."
|
||||
}
|
||||
|
||||
fn example_label_side() -> ExampleLabelSide {
|
||||
ExampleLabelSide::Top
|
||||
}
|
||||
|
||||
fn examples(_: &WindowContext) -> Vec<ComponentExampleGroup<Self>> {
|
||||
vec![
|
||||
example_group(vec![
|
||||
single_example(
|
||||
"Simple Table",
|
||||
Table::new(vec!["Name", "Age", "City"])
|
||||
.width(px(400.))
|
||||
.row(vec!["Alice", "28", "New York"])
|
||||
.row(vec!["Bob", "32", "San Francisco"])
|
||||
.row(vec!["Charlie", "25", "London"]),
|
||||
),
|
||||
single_example(
|
||||
"Two Column Table",
|
||||
Table::new(vec!["Category", "Value"])
|
||||
.width(px(300.))
|
||||
.row(vec!["Revenue", "$100,000"])
|
||||
.row(vec!["Expenses", "$75,000"])
|
||||
.row(vec!["Profit", "$25,000"]),
|
||||
),
|
||||
]),
|
||||
example_group(vec![single_example(
|
||||
"Striped Table",
|
||||
Table::new(vec!["Product", "Price", "Stock"])
|
||||
.width(px(600.))
|
||||
.striped()
|
||||
.row(vec!["Laptop", "$999", "In Stock"])
|
||||
.row(vec!["Phone", "$599", "Low Stock"])
|
||||
.row(vec!["Tablet", "$399", "Out of Stock"])
|
||||
.row(vec!["Headphones", "$199", "In Stock"]),
|
||||
)]),
|
||||
example_group_with_title(
|
||||
"Mixed Content Table",
|
||||
vec![single_example(
|
||||
"Table with Elements",
|
||||
Table::new(vec!["Status", "Name", "Priority", "Deadline", "Action"])
|
||||
.width(px(840.))
|
||||
.row(vec![
|
||||
element_cell(Indicator::dot().color(Color::Success).into_any_element()),
|
||||
string_cell("Project A"),
|
||||
string_cell("High"),
|
||||
string_cell("2023-12-31"),
|
||||
element_cell(
|
||||
Button::new("view_a", "View")
|
||||
.style(ButtonStyle::Filled)
|
||||
.full_width()
|
||||
.into_any_element(),
|
||||
),
|
||||
])
|
||||
.row(vec![
|
||||
element_cell(Indicator::dot().color(Color::Warning).into_any_element()),
|
||||
string_cell("Project B"),
|
||||
string_cell("Medium"),
|
||||
string_cell("2024-03-15"),
|
||||
element_cell(
|
||||
Button::new("view_b", "View")
|
||||
.style(ButtonStyle::Filled)
|
||||
.full_width()
|
||||
.into_any_element(),
|
||||
),
|
||||
])
|
||||
.row(vec![
|
||||
element_cell(Indicator::dot().color(Color::Error).into_any_element()),
|
||||
string_cell("Project C"),
|
||||
string_cell("Low"),
|
||||
string_cell("2024-06-30"),
|
||||
element_cell(
|
||||
Button::new("view_c", "View")
|
||||
.style(ButtonStyle::Filled)
|
||||
.full_width()
|
||||
.into_any_element(),
|
||||
),
|
||||
]),
|
||||
)],
|
||||
),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@ pub use gpui::{
|
||||
|
||||
pub use crate::styles::{rems_from_px, vh, vw, PlatformStyle, StyledTypography, TextSize};
|
||||
pub use crate::traits::clickable::*;
|
||||
pub use crate::traits::component_preview::*;
|
||||
pub use crate::traits::disableable::*;
|
||||
pub use crate::traits::fixed::*;
|
||||
pub use crate::traits::selectable::*;
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
pub mod clickable;
|
||||
pub mod component_preview;
|
||||
pub mod disableable;
|
||||
pub mod fixed;
|
||||
pub mod selectable;
|
||||
|
||||
@@ -1,200 +0,0 @@
|
||||
#![allow(missing_docs)]
|
||||
use crate::prelude::*;
|
||||
use gpui::{AnyElement, SharedString};
|
||||
|
||||
/// Which side of the preview to show labels on
|
||||
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum ExampleLabelSide {
|
||||
/// Left side
|
||||
Left,
|
||||
/// Right side
|
||||
Right,
|
||||
#[default]
|
||||
/// Top side
|
||||
Top,
|
||||
/// Bottom side
|
||||
Bottom,
|
||||
}
|
||||
|
||||
/// Implement this trait to enable rich UI previews with metadata in the Theme Preview tool.
|
||||
pub trait ComponentPreview: IntoElement {
|
||||
fn title() -> &'static str {
|
||||
std::any::type_name::<Self>()
|
||||
}
|
||||
|
||||
fn description() -> impl Into<Option<&'static str>> {
|
||||
None
|
||||
}
|
||||
|
||||
fn example_label_side() -> ExampleLabelSide {
|
||||
ExampleLabelSide::default()
|
||||
}
|
||||
|
||||
fn examples(_cx: &WindowContext) -> Vec<ComponentExampleGroup<Self>>;
|
||||
|
||||
fn custom_example(_cx: &WindowContext) -> impl Into<Option<AnyElement>> {
|
||||
None::<AnyElement>
|
||||
}
|
||||
|
||||
fn component_previews(cx: &WindowContext) -> Vec<AnyElement> {
|
||||
Self::examples(cx)
|
||||
.into_iter()
|
||||
.map(|example| Self::render_example_group(example))
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn render_component_previews(cx: &WindowContext) -> AnyElement {
|
||||
let title = Self::title();
|
||||
let (source, title) = title
|
||||
.rsplit_once("::")
|
||||
.map_or((None, title), |(s, t)| (Some(s), t));
|
||||
let description = Self::description().into();
|
||||
|
||||
v_flex()
|
||||
.w_full()
|
||||
.gap_6()
|
||||
.p_4()
|
||||
.border_1()
|
||||
.border_color(cx.theme().colors().border)
|
||||
.rounded_md()
|
||||
.child(
|
||||
v_flex()
|
||||
.gap_1()
|
||||
.child(
|
||||
h_flex()
|
||||
.gap_1()
|
||||
.child(Headline::new(title).size(HeadlineSize::Small))
|
||||
.when_some(source, |this, source| {
|
||||
this.child(Label::new(format!("({})", source)).color(Color::Muted))
|
||||
}),
|
||||
)
|
||||
.when_some(description, |this, description| {
|
||||
this.child(
|
||||
div()
|
||||
.text_ui_sm(cx)
|
||||
.text_color(cx.theme().colors().text_muted)
|
||||
.max_w(px(600.0))
|
||||
.child(description),
|
||||
)
|
||||
}),
|
||||
)
|
||||
.when_some(Self::custom_example(cx).into(), |this, custom_example| {
|
||||
this.child(custom_example)
|
||||
})
|
||||
.children(Self::component_previews(cx))
|
||||
.into_any_element()
|
||||
}
|
||||
|
||||
fn render_example_group(group: ComponentExampleGroup<Self>) -> AnyElement {
|
||||
v_flex()
|
||||
.gap_6()
|
||||
.when(group.grow, |this| this.w_full().flex_1())
|
||||
.when_some(group.title, |this, title| {
|
||||
this.child(Label::new(title).size(LabelSize::Small))
|
||||
})
|
||||
.child(
|
||||
h_flex()
|
||||
.w_full()
|
||||
.gap_6()
|
||||
.children(group.examples.into_iter().map(Self::render_example))
|
||||
.into_any_element(),
|
||||
)
|
||||
.into_any_element()
|
||||
}
|
||||
|
||||
fn render_example(example: ComponentExample<Self>) -> AnyElement {
|
||||
let base = div().flex();
|
||||
|
||||
let base = match Self::example_label_side() {
|
||||
ExampleLabelSide::Right => base.flex_row(),
|
||||
ExampleLabelSide::Left => base.flex_row_reverse(),
|
||||
ExampleLabelSide::Bottom => base.flex_col(),
|
||||
ExampleLabelSide::Top => base.flex_col_reverse(),
|
||||
};
|
||||
|
||||
base.gap_1()
|
||||
.when(example.grow, |this| this.flex_1())
|
||||
.child(example.element)
|
||||
.child(
|
||||
Label::new(example.variant_name)
|
||||
.size(LabelSize::XSmall)
|
||||
.color(Color::Muted),
|
||||
)
|
||||
.into_any_element()
|
||||
}
|
||||
}
|
||||
|
||||
/// A single example of a component.
|
||||
pub struct ComponentExample<T> {
|
||||
variant_name: SharedString,
|
||||
element: T,
|
||||
grow: bool,
|
||||
}
|
||||
|
||||
impl<T> ComponentExample<T> {
|
||||
/// Create a new example with the given variant name and example value.
|
||||
pub fn new(variant_name: impl Into<SharedString>, example: T) -> Self {
|
||||
Self {
|
||||
variant_name: variant_name.into(),
|
||||
element: example,
|
||||
grow: false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the example to grow to fill the available horizontal space.
|
||||
pub fn grow(mut self) -> Self {
|
||||
self.grow = true;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// A group of component examples.
|
||||
pub struct ComponentExampleGroup<T> {
|
||||
pub title: Option<SharedString>,
|
||||
pub examples: Vec<ComponentExample<T>>,
|
||||
pub grow: bool,
|
||||
}
|
||||
|
||||
impl<T> ComponentExampleGroup<T> {
|
||||
/// Create a new group of examples with the given title.
|
||||
pub fn new(examples: Vec<ComponentExample<T>>) -> Self {
|
||||
Self {
|
||||
title: None,
|
||||
examples,
|
||||
grow: false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new group of examples with the given title.
|
||||
pub fn with_title(title: impl Into<SharedString>, examples: Vec<ComponentExample<T>>) -> Self {
|
||||
Self {
|
||||
title: Some(title.into()),
|
||||
examples,
|
||||
grow: false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the group to grow to fill the available horizontal space.
|
||||
pub fn grow(mut self) -> Self {
|
||||
self.grow = true;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a single example
|
||||
pub fn single_example<T>(variant_name: impl Into<SharedString>, example: T) -> ComponentExample<T> {
|
||||
ComponentExample::new(variant_name, example)
|
||||
}
|
||||
|
||||
/// Create a group of examples without a title
|
||||
pub fn example_group<T>(examples: Vec<ComponentExample<T>>) -> ComponentExampleGroup<T> {
|
||||
ComponentExampleGroup::new(examples)
|
||||
}
|
||||
|
||||
/// Create a group of examples with a title
|
||||
pub fn example_group_with_title<T>(
|
||||
title: impl Into<SharedString>,
|
||||
examples: Vec<ComponentExample<T>>,
|
||||
) -> ComponentExampleGroup<T> {
|
||||
ComponentExampleGroup::with_title(title, examples)
|
||||
}
|
||||
@@ -1,11 +1,13 @@
|
||||
//! UI-related utilities
|
||||
|
||||
mod color_contrast;
|
||||
mod component_preview;
|
||||
mod format_distance;
|
||||
mod search_input;
|
||||
mod with_rem_size;
|
||||
|
||||
pub use color_contrast::*;
|
||||
pub use component_preview::*;
|
||||
pub use format_distance::*;
|
||||
pub use search_input::*;
|
||||
pub use with_rem_size::*;
|
||||
|
||||
119
crates/ui/src/utils/component_preview.rs
Normal file
119
crates/ui/src/utils/component_preview.rs
Normal file
@@ -0,0 +1,119 @@
|
||||
use crate::prelude::*;
|
||||
use gpui::{AnyElement, Axis};
|
||||
use smallvec::SmallVec;
|
||||
|
||||
/// A component preview with a label and children.
|
||||
#[derive(IntoElement)]
|
||||
pub struct ComponentPreview {
|
||||
label: Option<SharedString>,
|
||||
children: SmallVec<[AnyElement; 2]>,
|
||||
}
|
||||
|
||||
impl ComponentPreview {
|
||||
/// Creates a new ComponentPreview with the specified label side.
|
||||
pub fn new(label: impl Into<SharedString>) -> Self {
|
||||
Self {
|
||||
label: Some(label.into()),
|
||||
children: SmallVec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a new ComponentPreview with no label.
|
||||
pub fn no_label() -> Self {
|
||||
Self {
|
||||
label: None,
|
||||
children: SmallVec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets the label for the ComponentPreview.
|
||||
pub fn label(mut self, label: impl Into<SharedString>) -> Self {
|
||||
self.label = Some(label.into());
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl RenderOnce for ComponentPreview {
|
||||
fn render(self, _cx: &mut WindowContext) -> impl IntoElement {
|
||||
v_flex()
|
||||
.flex_none()
|
||||
.gap_3()
|
||||
.when_some(self.label, |this, label| {
|
||||
this.child(Label::new(label).color(Color::Muted))
|
||||
})
|
||||
.child(
|
||||
h_flex()
|
||||
.gap_1()
|
||||
.w_full()
|
||||
.flex_none()
|
||||
.children(self.children),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl ParentElement for ComponentPreview {
|
||||
fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
|
||||
self.children.extend(elements)
|
||||
}
|
||||
}
|
||||
|
||||
/// A group of component previews.
|
||||
#[derive(IntoElement)]
|
||||
pub struct ComponentPreviewGroup {
|
||||
direction: Axis,
|
||||
children: SmallVec<[AnyElement; 2]>,
|
||||
}
|
||||
|
||||
impl ComponentPreviewGroup {
|
||||
/// Creates a new ComponentPreviewGroup.
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
direction: Axis::Horizontal,
|
||||
children: SmallVec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Lay out the previews horizontally.
|
||||
pub fn horizontal(mut self) -> Self {
|
||||
self.direction = Axis::Horizontal;
|
||||
self
|
||||
}
|
||||
|
||||
/// Lay out the previews vertically.
|
||||
pub fn vertical(mut self) -> Self {
|
||||
self.direction = Axis::Vertical;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl ParentElement for ComponentPreviewGroup {
|
||||
fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
|
||||
self.children.extend(elements)
|
||||
}
|
||||
}
|
||||
|
||||
impl RenderOnce for ComponentPreviewGroup {
|
||||
fn render(self, _cx: &mut WindowContext) -> impl IntoElement {
|
||||
let group = match self.direction {
|
||||
Axis::Horizontal => h_group(),
|
||||
Axis::Vertical => v_group(),
|
||||
};
|
||||
|
||||
group
|
||||
.size_full()
|
||||
.items_start()
|
||||
.outset()
|
||||
.gap_3()
|
||||
.children(self.children)
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a new [ComponentPreview]
|
||||
pub fn component_preview(label: impl Into<SharedString>) -> ComponentPreview {
|
||||
ComponentPreview::new(label)
|
||||
}
|
||||
|
||||
/// Creates a new [ComponentPreviewGroup]
|
||||
pub fn component_preview_group() -> ComponentPreviewGroup {
|
||||
ComponentPreviewGroup::new()
|
||||
}
|
||||
@@ -17,3 +17,4 @@ proc-macro2 = "1.0.66"
|
||||
quote = "1.0.9"
|
||||
syn = { version = "1.0.72", features = ["full", "extra-traits"] }
|
||||
convert_case.workspace = true
|
||||
component_system.workspace = true
|
||||
|
||||
81
crates/ui_macros/src/derive_into_component.rs
Normal file
81
crates/ui_macros/src/derive_into_component.rs
Normal file
@@ -0,0 +1,81 @@
|
||||
use proc_macro::TokenStream;
|
||||
use quote::quote;
|
||||
use syn::{parse_macro_input, DeriveInput, Lit, Meta, MetaList, MetaNameValue, NestedMeta};
|
||||
|
||||
pub fn derive_into_component(input: TokenStream) -> TokenStream {
|
||||
let input = parse_macro_input!(input as DeriveInput);
|
||||
let mut scope_val = None;
|
||||
let mut description_val = None;
|
||||
|
||||
for attr in &input.attrs {
|
||||
if attr.path.is_ident("component") {
|
||||
if let Ok(Meta::List(MetaList { nested, .. })) = attr.parse_meta() {
|
||||
for item in nested {
|
||||
if let NestedMeta::Meta(Meta::NameValue(MetaNameValue {
|
||||
path,
|
||||
lit: Lit::Str(s),
|
||||
..
|
||||
})) = item
|
||||
{
|
||||
let ident = path.get_ident().map(|i| i.to_string()).unwrap_or_default();
|
||||
if ident == "scope" {
|
||||
scope_val = Some(s.value());
|
||||
} else if ident == "description" {
|
||||
description_val = Some(s.value());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let name = &input.ident;
|
||||
|
||||
let scope_impl = if let Some(s) = scope_val {
|
||||
quote! {
|
||||
fn scope() -> Option<&'static str> {
|
||||
Some(#s)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
quote! {
|
||||
fn scope() -> Option<&'static str> {
|
||||
None
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let description_impl = if let Some(desc) = description_val {
|
||||
quote! {
|
||||
fn description() -> Option<&'static str> {
|
||||
Some(#desc)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
quote! {}
|
||||
};
|
||||
|
||||
let expanded = quote! {
|
||||
impl component_system::Component for #name {
|
||||
#scope_impl
|
||||
|
||||
fn name() -> &'static str {
|
||||
stringify!(#name)
|
||||
}
|
||||
|
||||
#description_impl
|
||||
}
|
||||
|
||||
#[linkme::distributed_slice(component_system::__ALL_COMPONENTS)]
|
||||
fn __register_component() {
|
||||
component_system::register_component::<#name>();
|
||||
}
|
||||
|
||||
#[linkme::distributed_slice(component_system::__ALL_PREVIEWS)]
|
||||
fn __register_preview() {
|
||||
component_system::register_preview::<#name>();
|
||||
}
|
||||
};
|
||||
|
||||
expanded.into()
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
mod derive_into_component;
|
||||
mod derive_path_str;
|
||||
mod dynamic_spacing;
|
||||
|
||||
@@ -58,3 +59,27 @@ pub fn path_str(_args: TokenStream, input: TokenStream) -> TokenStream {
|
||||
pub fn derive_dynamic_spacing(input: TokenStream) -> TokenStream {
|
||||
dynamic_spacing::derive_spacing(input)
|
||||
}
|
||||
|
||||
/// Derives the `IntoComponent` trait for a struct.
|
||||
///
|
||||
/// This macro generates implementations for the `Component` trait and associated
|
||||
/// registration functions for the component system.
|
||||
///
|
||||
/// # Attributes
|
||||
///
|
||||
/// - `#[component(scope = "...")]`: Required. Specifies the scope of the component.
|
||||
/// - `#[component(description = "...")]`: Optional. Provides a description for the component.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use ui_macros::IntoComponent;
|
||||
///
|
||||
/// #[derive(IntoComponent)]
|
||||
/// #[component(scope = "my_scope", description = "A sample component")]
|
||||
/// struct MyComponent;
|
||||
/// ```
|
||||
#[proc_macro_derive(IntoComponent, attributes(component))]
|
||||
pub fn derive_into_component(input: TokenStream) -> TokenStream {
|
||||
derive_into_component::derive_into_component(input)
|
||||
}
|
||||
|
||||
@@ -28,14 +28,15 @@ livekit-macos = ["call/livekit-macos"]
|
||||
livekit-cross-platform = ["call/livekit-cross-platform"]
|
||||
|
||||
[dependencies]
|
||||
anyhow.workspace = true
|
||||
any_vec.workspace = true
|
||||
anyhow.workspace = true
|
||||
async-recursion.workspace = true
|
||||
bincode = "1.2.1"
|
||||
call.workspace = true
|
||||
client.workspace = true
|
||||
clock.workspace = true
|
||||
collections.workspace = true
|
||||
component_system.workspace = true
|
||||
db.workspace = true
|
||||
derive_more.workspace = true
|
||||
fs.workspace = true
|
||||
@@ -49,7 +50,6 @@ node_runtime.workspace = true
|
||||
parking_lot.workspace = true
|
||||
postage.workspace = true
|
||||
project.workspace = true
|
||||
task.workspace = true
|
||||
remote.workspace = true
|
||||
schemars.workspace = true
|
||||
serde.workspace = true
|
||||
@@ -58,11 +58,12 @@ session.workspace = true
|
||||
settings.workspace = true
|
||||
smallvec.workspace = true
|
||||
sqlez.workspace = true
|
||||
strum.workspace = true
|
||||
task.workspace = true
|
||||
theme.workspace = true
|
||||
ui.workspace = true
|
||||
util.workspace = true
|
||||
uuid.workspace = true
|
||||
strum.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
call = { workspace = true, features = ["test-support"] }
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
#![allow(unused, dead_code)]
|
||||
use gpui::{actions, hsla, AnyElement, AppContext, EventEmitter, FocusHandle, FocusableView, Hsla};
|
||||
use gpui::{actions, AppContext, EventEmitter, FocusHandle, FocusableView, Hsla};
|
||||
use strum::IntoEnumIterator;
|
||||
use theme::all_theme_colors;
|
||||
use ui::{
|
||||
element_cell, prelude::*, string_cell, utils::calculate_contrast_ratio, AudioStatus,
|
||||
Availability, Avatar, AvatarAudioStatusIndicator, AvatarAvailabilityIndicator, ButtonLike,
|
||||
Checkbox, CheckboxWithLabel, ContentGroup, DecoratedIcon, ElevationIndex, Facepile,
|
||||
IconDecoration, Indicator, Table, TintColor, Tooltip,
|
||||
prelude::*, utils::calculate_contrast_ratio, AudioStatus, Availability, Avatar,
|
||||
AvatarAudioStatusIndicator, AvatarAvailabilityIndicator, ButtonLike, ElevationIndex, Facepile,
|
||||
TintColor, Tooltip,
|
||||
};
|
||||
|
||||
use crate::{Item, Workspace};
|
||||
@@ -27,7 +25,6 @@ pub fn init(cx: &mut AppContext) {
|
||||
enum ThemePreviewPage {
|
||||
Overview,
|
||||
Typography,
|
||||
Components,
|
||||
}
|
||||
|
||||
impl ThemePreviewPage {
|
||||
@@ -35,7 +32,6 @@ impl ThemePreviewPage {
|
||||
match self {
|
||||
Self::Overview => "Overview",
|
||||
Self::Typography => "Typography",
|
||||
Self::Components => "Components",
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -61,7 +57,6 @@ impl ThemePreview {
|
||||
match page {
|
||||
ThemePreviewPage::Overview => self.render_overview_page(cx).into_any_element(),
|
||||
ThemePreviewPage::Typography => self.render_typography_page(cx).into_any_element(),
|
||||
ThemePreviewPage::Components => self.render_components_page(cx).into_any_element(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -101,154 +96,11 @@ impl Item for ThemePreview {
|
||||
}
|
||||
}
|
||||
|
||||
const AVATAR_URL: &str = "https://avatars.githubusercontent.com/u/1714999?v=4";
|
||||
|
||||
impl ThemePreview {
|
||||
fn preview_bg(cx: &WindowContext) -> Hsla {
|
||||
cx.theme().colors().editor_background
|
||||
}
|
||||
|
||||
fn render_avatars(&self, cx: &ViewContext<Self>) -> impl IntoElement {
|
||||
v_flex()
|
||||
.gap_1()
|
||||
.child(
|
||||
Headline::new("Avatars")
|
||||
.size(HeadlineSize::Small)
|
||||
.color(Color::Muted),
|
||||
)
|
||||
.child(
|
||||
h_flex()
|
||||
.items_start()
|
||||
.gap_4()
|
||||
.child(Avatar::new(AVATAR_URL).size(px(24.)))
|
||||
.child(Avatar::new(AVATAR_URL).size(px(24.)).grayscale(true))
|
||||
.child(
|
||||
Avatar::new(AVATAR_URL)
|
||||
.size(px(24.))
|
||||
.indicator(AvatarAudioStatusIndicator::new(AudioStatus::Muted)),
|
||||
)
|
||||
.child(
|
||||
Avatar::new(AVATAR_URL)
|
||||
.size(px(24.))
|
||||
.indicator(AvatarAudioStatusIndicator::new(AudioStatus::Deafened)),
|
||||
)
|
||||
.child(
|
||||
Avatar::new(AVATAR_URL)
|
||||
.size(px(24.))
|
||||
.indicator(AvatarAvailabilityIndicator::new(Availability::Free)),
|
||||
)
|
||||
.child(
|
||||
Avatar::new(AVATAR_URL)
|
||||
.size(px(24.))
|
||||
.indicator(AvatarAvailabilityIndicator::new(Availability::Free)),
|
||||
)
|
||||
.child(
|
||||
Facepile::empty()
|
||||
.child(
|
||||
Avatar::new(AVATAR_URL)
|
||||
.border_color(Self::preview_bg(cx))
|
||||
.size(px(22.))
|
||||
.into_any_element(),
|
||||
)
|
||||
.child(
|
||||
Avatar::new(AVATAR_URL)
|
||||
.border_color(Self::preview_bg(cx))
|
||||
.size(px(22.))
|
||||
.into_any_element(),
|
||||
)
|
||||
.child(
|
||||
Avatar::new(AVATAR_URL)
|
||||
.border_color(Self::preview_bg(cx))
|
||||
.size(px(22.))
|
||||
.into_any_element(),
|
||||
)
|
||||
.child(
|
||||
Avatar::new(AVATAR_URL)
|
||||
.border_color(Self::preview_bg(cx))
|
||||
.size(px(22.))
|
||||
.into_any_element(),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
fn render_buttons(&self, layer: ElevationIndex, cx: &ViewContext<Self>) -> impl IntoElement {
|
||||
v_flex()
|
||||
.gap_1()
|
||||
.child(
|
||||
Headline::new("Buttons")
|
||||
.size(HeadlineSize::Small)
|
||||
.color(Color::Muted),
|
||||
)
|
||||
.child(
|
||||
h_flex()
|
||||
.items_start()
|
||||
.gap_px()
|
||||
.child(
|
||||
IconButton::new("icon_button_transparent", IconName::Check)
|
||||
.style(ButtonStyle::Transparent),
|
||||
)
|
||||
.child(
|
||||
IconButton::new("icon_button_subtle", IconName::Check)
|
||||
.style(ButtonStyle::Subtle),
|
||||
)
|
||||
.child(
|
||||
IconButton::new("icon_button_filled", IconName::Check)
|
||||
.style(ButtonStyle::Filled),
|
||||
)
|
||||
.child(
|
||||
IconButton::new("icon_button_selected_accent", IconName::Check)
|
||||
.selected_style(ButtonStyle::Tinted(TintColor::Accent))
|
||||
.selected(true),
|
||||
)
|
||||
.child(IconButton::new("icon_button_selected", IconName::Check).selected(true))
|
||||
.child(
|
||||
IconButton::new("icon_button_positive", IconName::Check)
|
||||
.style(ButtonStyle::Tinted(TintColor::Positive)),
|
||||
)
|
||||
.child(
|
||||
IconButton::new("icon_button_warning", IconName::Check)
|
||||
.style(ButtonStyle::Tinted(TintColor::Warning)),
|
||||
)
|
||||
.child(
|
||||
IconButton::new("icon_button_negative", IconName::Check)
|
||||
.style(ButtonStyle::Tinted(TintColor::Negative)),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
h_flex()
|
||||
.gap_px()
|
||||
.child(
|
||||
Button::new("button_transparent", "Transparent")
|
||||
.style(ButtonStyle::Transparent),
|
||||
)
|
||||
.child(Button::new("button_subtle", "Subtle").style(ButtonStyle::Subtle))
|
||||
.child(Button::new("button_filled", "Filled").style(ButtonStyle::Filled))
|
||||
.child(
|
||||
Button::new("button_selected", "Selected")
|
||||
.selected_style(ButtonStyle::Tinted(TintColor::Accent))
|
||||
.selected(true),
|
||||
)
|
||||
.child(
|
||||
Button::new("button_selected_tinted", "Selected (Tinted)")
|
||||
.selected_style(ButtonStyle::Tinted(TintColor::Accent))
|
||||
.selected(true),
|
||||
)
|
||||
.child(
|
||||
Button::new("button_positive", "Tint::Positive")
|
||||
.style(ButtonStyle::Tinted(TintColor::Positive)),
|
||||
)
|
||||
.child(
|
||||
Button::new("button_warning", "Tint::Warning")
|
||||
.style(ButtonStyle::Tinted(TintColor::Warning)),
|
||||
)
|
||||
.child(
|
||||
Button::new("button_negative", "Tint::Negative")
|
||||
.style(ButtonStyle::Tinted(TintColor::Negative)),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
fn render_text(&self, layer: ElevationIndex, cx: &ViewContext<Self>) -> impl IntoElement {
|
||||
let bg = layer.bg(cx);
|
||||
|
||||
@@ -410,8 +262,7 @@ impl ThemePreview {
|
||||
)
|
||||
}
|
||||
|
||||
fn render_colors(&self, layer: ElevationIndex, cx: &ViewContext<Self>) -> impl IntoElement {
|
||||
let bg = layer.bg(cx);
|
||||
fn render_colors(&self, _layer: ElevationIndex, cx: &ViewContext<Self>) -> impl IntoElement {
|
||||
let all_colors = all_theme_colors(cx);
|
||||
|
||||
v_flex()
|
||||
@@ -502,28 +353,6 @@ impl ThemePreview {
|
||||
)
|
||||
}
|
||||
|
||||
fn render_components_page(&self, cx: &ViewContext<Self>) -> impl IntoElement {
|
||||
let layer = ElevationIndex::Surface;
|
||||
|
||||
v_flex()
|
||||
.id("theme-preview-components")
|
||||
.overflow_scroll()
|
||||
.size_full()
|
||||
.gap_2()
|
||||
.child(ContentGroup::render_component_previews(cx))
|
||||
.child(IconDecoration::render_component_previews(cx))
|
||||
.child(DecoratedIcon::render_component_previews(cx))
|
||||
.child(Checkbox::render_component_previews(cx))
|
||||
.child(CheckboxWithLabel::render_component_previews(cx))
|
||||
.child(Facepile::render_component_previews(cx))
|
||||
.child(Button::render_component_previews(cx))
|
||||
.child(Indicator::render_component_previews(cx))
|
||||
.child(Icon::render_component_previews(cx))
|
||||
.child(Table::render_component_previews(cx))
|
||||
.child(self.render_avatars(cx))
|
||||
.child(self.render_buttons(layer, cx))
|
||||
}
|
||||
|
||||
fn render_page_nav(&self, cx: &ViewContext<Self>) -> impl IntoElement {
|
||||
h_flex()
|
||||
.id("theme-preview-nav")
|
||||
|
||||
@@ -143,12 +143,13 @@ actions!(
|
||||
FollowNextCollaborator,
|
||||
NewCenterTerminal,
|
||||
NewFile,
|
||||
NewFileSplitVertical,
|
||||
NewFileSplitHorizontal,
|
||||
NewFileSplitVertical,
|
||||
NewSearch,
|
||||
NewTerminal,
|
||||
NewWindow,
|
||||
Open,
|
||||
ComponentPreview,
|
||||
OpenInTerminal,
|
||||
ReloadActiveItem,
|
||||
SaveAs,
|
||||
@@ -323,6 +324,7 @@ pub fn init_settings(cx: &mut AppContext) {
|
||||
pub fn init(app_state: Arc<AppState>, cx: &mut AppContext) {
|
||||
init_settings(cx);
|
||||
notifications::init(cx);
|
||||
component_system::init();
|
||||
theme_preview::init(cx);
|
||||
|
||||
cx.on_action(Workspace::close_global);
|
||||
|
||||
@@ -37,6 +37,7 @@ collab_ui.workspace = true
|
||||
collections.workspace = true
|
||||
command_palette.workspace = true
|
||||
command_palette_hooks.workspace = true
|
||||
component_preview.workspace = true
|
||||
copilot.workspace = true
|
||||
db.workspace = true
|
||||
diagnostics.workspace = true
|
||||
|
||||
@@ -461,6 +461,7 @@ fn main() {
|
||||
feedback::init(cx);
|
||||
markdown_preview::init(cx);
|
||||
welcome::init(cx);
|
||||
component_preview::init(cx);
|
||||
settings_ui::init(cx);
|
||||
extensions_ui::init(cx);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user