diff --git a/Cargo.lock b/Cargo.lock index 01c74fb4ce..0081626cab 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1566,7 +1566,9 @@ dependencies = [ name = "component_test" version = "0.1.0" dependencies = [ + "anyhow", "gpui", + "project", "settings", "theme", "util", @@ -9668,6 +9670,7 @@ dependencies = [ "collab_ui", "collections", "command_palette", + "component_test", "context_menu", "copilot", "copilot_button", diff --git a/crates/component_test/Cargo.toml b/crates/component_test/Cargo.toml new file mode 100644 index 0000000000..d714f6f72f --- /dev/null +++ b/crates/component_test/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "component_test" +version = "0.1.0" +edition = "2021" +publish = false + +[lib] +path = "src/component_test.rs" +doctest = false + +[dependencies] +anyhow.workspace = true +gpui = { path = "../gpui" } +settings = { path = "../settings" } +util = { path = "../util" } +theme = { path = "../theme" } +workspace = { path = "../workspace" } +project = { path = "../project" } diff --git a/crates/component_test/src/component_test.rs b/crates/component_test/src/component_test.rs new file mode 100644 index 0000000000..30fd431290 --- /dev/null +++ b/crates/component_test/src/component_test.rs @@ -0,0 +1,122 @@ +use gpui::{ + actions, + color::Color, + elements::{Component, Flex, ParentElement, SafeStylable}, + AppContext, Element, Entity, ModelHandle, Task, View, ViewContext, ViewHandle, WeakViewHandle, +}; +use project::Project; +use theme::components::{action_button::Button, label::Label, ComponentExt}; +use workspace::{ + item::Item, register_deserializable_item, ItemId, Pane, PaneBackdrop, Workspace, WorkspaceId, +}; + +pub fn init(cx: &mut AppContext) { + cx.add_action(ComponentTest::toggle_disclosure); + cx.add_action(ComponentTest::toggle_toggle); + cx.add_action(ComponentTest::deploy); + register_deserializable_item::(cx); +} + +actions!( + test, + [NoAction, ToggleDisclosure, ToggleToggle, NewComponentTest] +); + +struct ComponentTest { + disclosed: bool, + toggled: bool, +} + +impl ComponentTest { + fn new() -> Self { + Self { + disclosed: false, + toggled: false, + } + } + + fn deploy(workspace: &mut Workspace, _: &NewComponentTest, cx: &mut ViewContext) { + workspace.add_item(Box::new(cx.add_view(|_| ComponentTest::new())), cx); + } + + fn toggle_disclosure(&mut self, _: &ToggleDisclosure, cx: &mut ViewContext) { + self.disclosed = !self.disclosed; + cx.notify(); + } + + fn toggle_toggle(&mut self, _: &ToggleToggle, cx: &mut ViewContext) { + self.toggled = !self.toggled; + cx.notify(); + } +} + +impl Entity for ComponentTest { + type Event = (); +} + +impl View for ComponentTest { + fn ui_name() -> &'static str { + "Component Test" + } + + fn render(&mut self, cx: &mut gpui::ViewContext) -> gpui::AnyElement { + let theme = theme::current(cx); + + PaneBackdrop::new( + cx.view_id(), + Flex::column() + .with_spacing(10.) + .with_child( + Button::action(NoAction) + .with_tooltip("Here's what a tooltip looks like", theme.tooltip.clone()) + .with_contents(Label::new("Click me!")) + .with_style(theme.component_test.button.clone()) + .element(), + ) + .with_child( + Button::action(ToggleToggle) + .with_tooltip("Here's what a tooltip looks like", theme.tooltip.clone()) + .with_contents(Label::new("Toggle me!")) + .toggleable(self.toggled) + .with_style(theme.component_test.toggle.clone()) + .element(), + ) + .with_child( + Label::new("A disclosure") + .disclosable(Some(self.disclosed), Box::new(ToggleDisclosure)) + .with_style(theme.component_test.disclosure.clone()) + .element(), + ) + .constrained() + .with_width(200.) + .aligned() + .into_any(), + ) + .into_any() + } +} + +impl Item for ComponentTest { + fn tab_content( + &self, + _: Option, + style: &theme::Tab, + _: &AppContext, + ) -> gpui::AnyElement { + gpui::elements::Label::new("Component test", style.label.clone()).into_any() + } + + fn serialized_item_kind() -> Option<&'static str> { + Some("ComponentTest") + } + + fn deserialize( + _project: ModelHandle, + _workspace: WeakViewHandle, + _workspace_id: WorkspaceId, + _item_id: ItemId, + cx: &mut ViewContext, + ) -> Task>> { + Task::ready(Ok(cx.add_view(|_| Self::new()))) + } +} diff --git a/crates/gpui/src/elements/component.rs b/crates/gpui/src/elements/component.rs index e391ff9b05..3776abcaa0 100644 --- a/crates/gpui/src/elements/component.rs +++ b/crates/gpui/src/elements/component.rs @@ -128,7 +128,7 @@ impl StatefulComponent for C { pub trait StatefulStylable: StatefulComponent { type Style: Clone; - fn stateful_with_style(self, style: Self::Style) -> Self; + fn with_style(self, style: Self::Style) -> Self; } /// Same as SafeStylable, but generic over a view type @@ -136,7 +136,7 @@ pub trait StatefulSafeStylable { type Style: Clone; type Output: StatefulComponent; - fn stateful_with_style(self, style: Self::Style) -> Self::Output; + fn with_style(self, style: Self::Style) -> Self::Output; } /// Converting from stateless to stateful @@ -145,7 +145,7 @@ impl StatefulSafeStylable for C { type Output = C::Output; - fn stateful_with_style(self, style: Self::Style) -> Self::Output { + fn with_style(self, style: Self::Style) -> Self::Output { self.with_style(style) } } @@ -192,7 +192,7 @@ impl, V: View> StatefulSafeStylable for StatefulStyla type Output = C; - fn stateful_with_style(self, _: Self::Style) -> Self::Output { + fn with_style(self, _: Self::Style) -> Self::Output { self.component } } diff --git a/crates/gpui/src/elements/flex.rs b/crates/gpui/src/elements/flex.rs index 3000b9575d..9d175afc03 100644 --- a/crates/gpui/src/elements/flex.rs +++ b/crates/gpui/src/elements/flex.rs @@ -22,6 +22,7 @@ pub struct Flex { children: Vec>, scroll_state: Option<(ElementStateHandle>, usize)>, child_alignment: f32, + spacing: f32, } impl Flex { @@ -31,6 +32,7 @@ impl Flex { children: Default::default(), scroll_state: None, child_alignment: -1., + spacing: 0., } } @@ -51,6 +53,11 @@ impl Flex { self } + pub fn with_spacing(mut self, spacing: f32) -> Self { + self.spacing = spacing; + self + } + pub fn scrollable( mut self, element_id: usize, @@ -81,7 +88,8 @@ impl Flex { cx: &mut LayoutContext, ) { let cross_axis = self.axis.invert(); - for child in &mut self.children { + let last = self.children.len() - 1; + for (ix, child) in &mut self.children.iter_mut().enumerate() { if let Some(metadata) = child.metadata::() { if let Some((flex, expanded)) = metadata.flex { if expanded != layout_expanded { @@ -93,6 +101,10 @@ impl Flex { } else { let space_per_flex = *remaining_space / *remaining_flex; space_per_flex * flex + } - if ix == 0 || ix == last { + self.spacing / 2. + } else { + self.spacing }; let child_min = if expanded { child_max } else { 0. }; let child_constraint = match self.axis { @@ -137,7 +149,8 @@ impl Element for Flex { let cross_axis = self.axis.invert(); let mut cross_axis_max: f32 = 0.0; - for child in &mut self.children { + let last = self.children.len().saturating_sub(1); + for (ix, child) in &mut self.children.iter_mut().enumerate() { let metadata = child.metadata::(); contains_float |= metadata.map_or(false, |metadata| metadata.float); @@ -155,7 +168,12 @@ impl Element for Flex { ), }; let size = child.layout(child_constraint, view, cx); - fixed_space += size.along(self.axis); + fixed_space += size.along(self.axis) + + if ix == 0 || ix == last { + self.spacing / 2. + } else { + self.spacing + }; cross_axis_max = cross_axis_max.max(size.along(cross_axis)); } } @@ -315,7 +333,8 @@ impl Element for Flex { } } - for child in &mut self.children { + let last = self.children.len().saturating_sub(1); + for (ix, child) in &mut self.children.iter_mut().enumerate() { if remaining_space > 0. { if let Some(metadata) = child.metadata::() { if metadata.float { @@ -353,9 +372,11 @@ impl Element for Flex { child.paint(scene, aligned_child_origin, visible_bounds, view, cx); + let spacing = if ix == last { 0. } else { self.spacing }; + match self.axis { - Axis::Horizontal => child_origin += vec2f(child.size().x(), 0.0), - Axis::Vertical => child_origin += vec2f(0.0, child.size().y()), + Axis::Horizontal => child_origin += vec2f(child.size().x() + spacing, 0.0), + Axis::Vertical => child_origin += vec2f(0.0, child.size().y() + spacing), } } diff --git a/crates/theme/src/components.rs b/crates/theme/src/components.rs index d9a857915c..fc208954cf 100644 --- a/crates/theme/src/components.rs +++ b/crates/theme/src/components.rs @@ -74,8 +74,9 @@ pub mod disclosure { } impl Disclosable { - pub fn with_id(self, id: usize) -> Disclosable { - Disclosable { id, ..self } + pub fn with_id(mut self, id: usize) -> Disclosable { + self.id = id; + self } } @@ -181,7 +182,7 @@ pub mod action_button { use gpui::{ elements::{Component, ContainerStyle, MouseEventHandler, SafeStylable, TooltipStyle}, platform::{CursorStyle, MouseButton}, - Action, Element, TypeTag, View, + Action, Element, EventContext, TypeTag, View, }; use schemars::JsonSchema; use serde_derive::Deserialize; @@ -211,14 +212,14 @@ pub mod action_button { } impl Button<(), ()> { - pub fn dynamic_action(action: Box) -> Self { + pub fn dynamic_action(action: Box) -> Button<(), ()> { Self { contents: (), tag: action.type_tag(), + action, style: Interactive::new_blank(), tooltip: None, id: 0, - action, } } @@ -292,7 +293,7 @@ pub mod action_button { }) .on_click(MouseButton::Left, { let action = self.action.boxed_clone(); - move |_, _, cx| { + move |_, _, cx: &mut EventContext| { let window = cx.window(); let view = cx.view_id(); let action = action.boxed_clone(); @@ -437,6 +438,7 @@ pub mod label { use gpui::{ elements::{Component, LabelStyle, SafeStylable}, + fonts::TextStyle, Element, }; @@ -455,14 +457,14 @@ pub mod label { } impl SafeStylable for Label<()> { - type Style = LabelStyle; + type Style = TextStyle; type Output = Label; fn with_style(self, style: Self::Style) -> Self::Output { Label { text: self.text, - style, + style: style.into(), } } } diff --git a/crates/theme/src/theme.rs b/crates/theme/src/theme.rs index 08da9af299..0f34963708 100644 --- a/crates/theme/src/theme.rs +++ b/crates/theme/src/theme.rs @@ -3,7 +3,7 @@ mod theme_registry; mod theme_settings; pub mod ui; -use components::{disclosure::DisclosureStyle, ToggleIconButtonStyle}; +use components::{action_button::ButtonStyle, disclosure::DisclosureStyle, ToggleIconButtonStyle}; use gpui::{ color::Color, elements::{ContainerStyle, ImageStyle, LabelStyle, Shadow, SvgStyle, TooltipStyle}, @@ -66,6 +66,7 @@ pub struct Theme { pub feedback: FeedbackStyle, pub welcome: WelcomeStyle, pub titlebar: Titlebar, + pub component_test: ComponentTest, } #[derive(Deserialize, Default, Clone, JsonSchema)] @@ -260,6 +261,13 @@ pub struct CollabPanel { pub face_overlap: f32, } +#[derive(Deserialize, Default, JsonSchema)] +pub struct ComponentTest { + pub button: Interactive>, + pub toggle: Toggleable>>, + pub disclosure: DisclosureStyle, +} + #[derive(Deserialize, Default, JsonSchema)] pub struct TabbedModal { pub tab_button: Toggleable>, diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml index 988648d4b1..cb66a6b587 100644 --- a/crates/zed/Cargo.toml +++ b/crates/zed/Cargo.toml @@ -25,6 +25,7 @@ cli = { path = "../cli" } collab_ui = { path = "../collab_ui" } collections = { path = "../collections" } command_palette = { path = "../command_palette" } +component_test = { path = "../component_test" } context_menu = { path = "../context_menu" } client = { path = "../client" } clock = { path = "../clock" } diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index deef3b85eb..caeaeceded 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -166,6 +166,7 @@ fn main() { terminal_view::init(cx); copilot::init(http.clone(), node_runtime, cx); ai::init(cx); + component_test::init(cx); cx.spawn(|cx| watch_themes(fs.clone(), cx)).detach(); cx.spawn(|_| watch_languages(fs.clone(), languages.clone())) diff --git a/styles/src/style_tree/app.ts b/styles/src/style_tree/app.ts index ee5e19e111..cf7e0538a7 100644 --- a/styles/src/style_tree/app.ts +++ b/styles/src/style_tree/app.ts @@ -12,7 +12,6 @@ import simple_message_notification from "./simple_message_notification" import project_shared_notification from "./project_shared_notification" import tooltip from "./tooltip" import terminal from "./terminal" -import contact_finder from "./contact_finder" import collab_panel from "./collab_panel" import toolbar_dropdown_menu from "./toolbar_dropdown_menu" import incoming_call_notification from "./incoming_call_notification" @@ -22,6 +21,7 @@ import assistant from "./assistant" import { titlebar } from "./titlebar" import editor from "./editor" import feedback from "./feedback" +import component_test from "./component_test" import { useTheme } from "../common" export default function app(): any { @@ -54,6 +54,7 @@ export default function app(): any { tooltip: tooltip(), terminal: terminal(), assistant: assistant(), - feedback: feedback() + feedback: feedback(), + component_test: component_test(), } } diff --git a/styles/src/style_tree/component_test.ts b/styles/src/style_tree/component_test.ts new file mode 100644 index 0000000000..eadbb5c2f1 --- /dev/null +++ b/styles/src/style_tree/component_test.ts @@ -0,0 +1,19 @@ +import { toggle_label_button_style } from "../component/label_button" +import { useTheme } from "../common" +import { text_button } from "../component/text_button" +import { toggleable_icon_button } from "../component/icon_button" +import { text } from "./components" + +export default function contacts_panel(): any { + const theme = useTheme() + + return { + button: text_button({}), + toggle: toggle_label_button_style({ active_color: "accent" }), + disclosure: { + ...text(theme.lowest, "sans", "base"), + button: toggleable_icon_button(theme, {}), + spacing: 4, + } + } +}