Detailed changes
@@ -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",
@@ -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" }
@@ -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::<ComponentTest>(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>) {
+ workspace.add_item(Box::new(cx.add_view(|_| ComponentTest::new())), cx);
+ }
+
+ fn toggle_disclosure(&mut self, _: &ToggleDisclosure, cx: &mut ViewContext<Self>) {
+ self.disclosed = !self.disclosed;
+ cx.notify();
+ }
+
+ fn toggle_toggle(&mut self, _: &ToggleToggle, cx: &mut ViewContext<Self>) {
+ 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<Self>) -> gpui::AnyElement<Self> {
+ 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<V: View>(
+ &self,
+ _: Option<usize>,
+ style: &theme::Tab,
+ _: &AppContext,
+ ) -> gpui::AnyElement<V> {
+ gpui::elements::Label::new("Component test", style.label.clone()).into_any()
+ }
+
+ fn serialized_item_kind() -> Option<&'static str> {
+ Some("ComponentTest")
+ }
+
+ fn deserialize(
+ _project: ModelHandle<Project>,
+ _workspace: WeakViewHandle<Workspace>,
+ _workspace_id: WorkspaceId,
+ _item_id: ItemId,
+ cx: &mut ViewContext<Pane>,
+ ) -> Task<anyhow::Result<ViewHandle<Self>>> {
+ Task::ready(Ok(cx.add_view(|_| Self::new())))
+ }
+}
@@ -128,7 +128,7 @@ impl<V: View, C: Component> StatefulComponent<V> for C {
pub trait StatefulStylable<V: View>: StatefulComponent<V> {
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<V: View> {
type Style: Clone;
type Output: StatefulComponent<V>;
- 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<V: View, C: SafeStylable> StatefulSafeStylable<V> 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<C: StatefulComponent<V>, V: View> StatefulSafeStylable<V> for StatefulStyla
type Output = C;
- fn stateful_with_style(self, _: Self::Style) -> Self::Output {
+ fn with_style(self, _: Self::Style) -> Self::Output {
self.component
}
}
@@ -22,6 +22,7 @@ pub struct Flex<V: View> {
children: Vec<AnyElement<V>>,
scroll_state: Option<(ElementStateHandle<Rc<ScrollState>>, usize)>,
child_alignment: f32,
+ spacing: f32,
}
impl<V: View> Flex<V> {
@@ -31,6 +32,7 @@ impl<V: View> Flex<V> {
children: Default::default(),
scroll_state: None,
child_alignment: -1.,
+ spacing: 0.,
}
}
@@ -51,6 +53,11 @@ impl<V: View> Flex<V> {
self
}
+ pub fn with_spacing(mut self, spacing: f32) -> Self {
+ self.spacing = spacing;
+ self
+ }
+
pub fn scrollable<Tag>(
mut self,
element_id: usize,
@@ -81,7 +88,8 @@ impl<V: View> Flex<V> {
cx: &mut LayoutContext<V>,
) {
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::<FlexParentData>() {
if let Some((flex, expanded)) = metadata.flex {
if expanded != layout_expanded {
@@ -93,6 +101,10 @@ impl<V: View> Flex<V> {
} 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<V: View> Element<V> for Flex<V> {
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::<FlexParentData>();
contains_float |= metadata.map_or(false, |metadata| metadata.float);
@@ -155,7 +168,12 @@ impl<V: View> Element<V> for Flex<V> {
),
};
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<V: View> Element<V> for Flex<V> {
}
}
- 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::<FlexParentData>() {
if metadata.float {
@@ -353,9 +372,11 @@ impl<V: View> Element<V> for Flex<V> {
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),
}
}
@@ -74,8 +74,9 @@ pub mod disclosure {
}
impl<C> Disclosable<C, ()> {
- pub fn with_id(self, id: usize) -> Disclosable<C, ()> {
- Disclosable { id, ..self }
+ pub fn with_id(mut self, id: usize) -> Disclosable<C, ()> {
+ 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<dyn Action>) -> Self {
+ pub fn dynamic_action(action: Box<dyn Action>) -> 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<V>| {
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<LabelStyle>;
fn with_style(self, style: Self::Style) -> Self::Output {
Label {
text: self.text,
- style,
+ style: style.into(),
}
}
}
@@ -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<ButtonStyle<TextStyle>>,
+ pub toggle: Toggleable<Interactive<ButtonStyle<TextStyle>>>,
+ pub disclosure: DisclosureStyle<TextStyle>,
+}
+
#[derive(Deserialize, Default, JsonSchema)]
pub struct TabbedModal {
pub tab_button: Toggleable<Interactive<ContainedText>>,
@@ -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" }
@@ -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()))
@@ -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(),
}
}
@@ -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,
+ }
+ }
+}