mirror of
https://github.com/zed-industries/zed.git
synced 2024-09-20 02:47:34 +03:00
Button2 – Part1 (#3420)
## TODO - [x] Remove `InteractionState` - [ ] `Selectable` should use `Selection` instead of a boolean - [x] Clean out ui2 prelude - [ ] Build out button2 button types - [ ] Port old buttons Release Notes: - N/A --------- Co-authored-by: Marshall Bowers <1486634+maxdeviant@users.noreply.github.com>
This commit is contained in:
parent
5d59108b97
commit
a8bf0834e6
@ -37,7 +37,10 @@ use gpui::{
|
||||
};
|
||||
use project::Project;
|
||||
use theme::ActiveTheme;
|
||||
use ui::{h_stack, Avatar, Button, ButtonVariant, Color, IconButton, KeyBinding, Tooltip};
|
||||
use ui::{
|
||||
h_stack, Avatar, Button, ButtonCommon, ButtonLike, ButtonVariant, Clickable, Color, IconButton,
|
||||
IconElement, IconSize, KeyBinding, Tooltip,
|
||||
};
|
||||
use util::ResultExt;
|
||||
use workspace::{notifications::NotifyResultExt, Workspace};
|
||||
|
||||
@ -298,6 +301,27 @@ impl Render for CollabTitlebarItem {
|
||||
})
|
||||
.detach();
|
||||
}))
|
||||
// Temporary, will be removed when the last part of button2 is merged
|
||||
.child(
|
||||
div().border().border_color(gpui::blue()).child(
|
||||
ButtonLike::new("test-button")
|
||||
.children([
|
||||
Avatar::uri(
|
||||
"https://avatars.githubusercontent.com/u/1714999?v=4",
|
||||
)
|
||||
.into_element()
|
||||
.into_any(),
|
||||
IconElement::new(ui::Icon::ChevronDown)
|
||||
.size(IconSize::Small)
|
||||
.into_element()
|
||||
.into_any(),
|
||||
])
|
||||
.on_click(move |event, _cx| {
|
||||
dbg!(format!("clicked: {:?}", event.down.position));
|
||||
})
|
||||
.tooltip(|cx| Tooltip::text("Test tooltip", cx)),
|
||||
),
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -2,7 +2,7 @@ use gpui::{
|
||||
actions, div, prelude::*, Div, FocusHandle, Focusable, KeyBinding, Render, Stateful, View,
|
||||
WindowContext,
|
||||
};
|
||||
use theme2::ActiveTheme;
|
||||
use ui::prelude::*;
|
||||
|
||||
actions!(ActionA, ActionB, ActionC);
|
||||
|
||||
|
@ -4,7 +4,7 @@ use gpui::{
|
||||
};
|
||||
use picker::{Picker, PickerDelegate};
|
||||
use std::sync::Arc;
|
||||
use theme2::ActiveTheme;
|
||||
use ui::prelude::*;
|
||||
use ui::{Label, ListItem};
|
||||
|
||||
pub struct PickerStory {
|
||||
|
@ -1,5 +1,5 @@
|
||||
use gpui::{div, prelude::*, px, Div, Render, SharedString, Stateful, Styled, View, WindowContext};
|
||||
use theme2::ActiveTheme;
|
||||
use ui::prelude::*;
|
||||
use ui::Tooltip;
|
||||
|
||||
pub struct ScrollStory;
|
||||
|
@ -19,7 +19,6 @@ pub enum ComponentStory {
|
||||
Focus,
|
||||
Icon,
|
||||
IconButton,
|
||||
Input,
|
||||
Keybinding,
|
||||
Label,
|
||||
ListItem,
|
||||
@ -39,7 +38,6 @@ impl ComponentStory {
|
||||
Self::Focus => FocusStory::view(cx).into(),
|
||||
Self::Icon => cx.build_view(|_| ui::IconStory).into(),
|
||||
Self::IconButton => cx.build_view(|_| ui::IconButtonStory).into(),
|
||||
Self::Input => cx.build_view(|_| ui::InputStory).into(),
|
||||
Self::Keybinding => cx.build_view(|_| ui::KeybindingStory).into(),
|
||||
Self::Label => cx.build_view(|_| ui::LabelStory).into(),
|
||||
Self::ListItem => cx.build_view(|_| ui::ListItemStory).into(),
|
||||
|
5
crates/ui2/src/clickable.rs
Normal file
5
crates/ui2/src/clickable.rs
Normal file
@ -0,0 +1,5 @@
|
||||
use gpui::{ClickEvent, WindowContext};
|
||||
|
||||
pub trait Clickable {
|
||||
fn on_click(self, handler: impl Fn(&ClickEvent, &mut WindowContext) + 'static) -> Self;
|
||||
}
|
@ -1,12 +1,12 @@
|
||||
mod avatar;
|
||||
mod button;
|
||||
mod button2;
|
||||
mod checkbox;
|
||||
mod context_menu;
|
||||
mod disclosure;
|
||||
mod divider;
|
||||
mod icon;
|
||||
mod icon_button;
|
||||
mod input;
|
||||
mod keybinding;
|
||||
mod label;
|
||||
mod list;
|
||||
@ -21,13 +21,13 @@ mod stories;
|
||||
|
||||
pub use avatar::*;
|
||||
pub use button::*;
|
||||
pub use button2::*;
|
||||
pub use checkbox::*;
|
||||
pub use context_menu::*;
|
||||
pub use disclosure::*;
|
||||
pub use divider::*;
|
||||
pub use icon::*;
|
||||
pub use icon_button::*;
|
||||
pub use input::*;
|
||||
pub use keybinding::*;
|
||||
pub use label::*;
|
||||
pub use list::*;
|
||||
|
413
crates/ui2/src/components/button2.rs
Normal file
413
crates/ui2/src/components/button2.rs
Normal file
@ -0,0 +1,413 @@
|
||||
use gpui::{
|
||||
rems, AnyElement, AnyView, ClickEvent, Div, Hsla, IntoElement, Rems, Stateful,
|
||||
StatefulInteractiveElement, WindowContext,
|
||||
};
|
||||
use smallvec::SmallVec;
|
||||
|
||||
use crate::{h_stack, prelude::*};
|
||||
|
||||
// 🚧 Heavily WIP 🚧
|
||||
|
||||
// #[derive(Default, PartialEq, Clone, Copy)]
|
||||
// pub enum ButtonType2 {
|
||||
// #[default]
|
||||
// DefaultButton,
|
||||
// IconButton,
|
||||
// ButtonLike,
|
||||
// SplitButton,
|
||||
// ToggleButton,
|
||||
// }
|
||||
|
||||
#[derive(Default, PartialEq, Clone, Copy)]
|
||||
pub enum IconPosition2 {
|
||||
#[default]
|
||||
Before,
|
||||
After,
|
||||
}
|
||||
|
||||
#[derive(Default, PartialEq, Clone, Copy)]
|
||||
pub enum ButtonStyle2 {
|
||||
#[default]
|
||||
Filled,
|
||||
// Tinted,
|
||||
Subtle,
|
||||
Transparent,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct ButtonStyle {
|
||||
pub background: Hsla,
|
||||
pub border_color: Hsla,
|
||||
pub label_color: Hsla,
|
||||
pub icon_color: Hsla,
|
||||
}
|
||||
|
||||
impl ButtonStyle2 {
|
||||
pub fn enabled(self, cx: &mut WindowContext) -> ButtonStyle {
|
||||
match self {
|
||||
ButtonStyle2::Filled => ButtonStyle {
|
||||
background: cx.theme().colors().element_background,
|
||||
border_color: gpui::transparent_black(),
|
||||
label_color: Color::Default.color(cx),
|
||||
icon_color: Color::Default.color(cx),
|
||||
},
|
||||
ButtonStyle2::Subtle => ButtonStyle {
|
||||
background: cx.theme().colors().ghost_element_background,
|
||||
border_color: gpui::transparent_black(),
|
||||
label_color: Color::Default.color(cx),
|
||||
icon_color: Color::Default.color(cx),
|
||||
},
|
||||
ButtonStyle2::Transparent => ButtonStyle {
|
||||
background: gpui::transparent_black(),
|
||||
border_color: gpui::transparent_black(),
|
||||
label_color: Color::Default.color(cx),
|
||||
icon_color: Color::Default.color(cx),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn hovered(self, cx: &mut WindowContext) -> ButtonStyle {
|
||||
match self {
|
||||
ButtonStyle2::Filled => ButtonStyle {
|
||||
background: cx.theme().colors().element_hover,
|
||||
border_color: gpui::transparent_black(),
|
||||
label_color: Color::Default.color(cx),
|
||||
icon_color: Color::Default.color(cx),
|
||||
},
|
||||
ButtonStyle2::Subtle => ButtonStyle {
|
||||
background: cx.theme().colors().ghost_element_hover,
|
||||
border_color: gpui::transparent_black(),
|
||||
label_color: Color::Default.color(cx),
|
||||
icon_color: Color::Default.color(cx),
|
||||
},
|
||||
ButtonStyle2::Transparent => ButtonStyle {
|
||||
background: gpui::transparent_black(),
|
||||
border_color: gpui::transparent_black(),
|
||||
// TODO: These are not great
|
||||
label_color: Color::Muted.color(cx),
|
||||
// TODO: These are not great
|
||||
icon_color: Color::Muted.color(cx),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn active(self, cx: &mut WindowContext) -> ButtonStyle {
|
||||
match self {
|
||||
ButtonStyle2::Filled => ButtonStyle {
|
||||
background: cx.theme().colors().element_active,
|
||||
border_color: gpui::transparent_black(),
|
||||
label_color: Color::Default.color(cx),
|
||||
icon_color: Color::Default.color(cx),
|
||||
},
|
||||
ButtonStyle2::Subtle => ButtonStyle {
|
||||
background: cx.theme().colors().ghost_element_active,
|
||||
border_color: gpui::transparent_black(),
|
||||
label_color: Color::Default.color(cx),
|
||||
icon_color: Color::Default.color(cx),
|
||||
},
|
||||
ButtonStyle2::Transparent => ButtonStyle {
|
||||
background: gpui::transparent_black(),
|
||||
border_color: gpui::transparent_black(),
|
||||
// TODO: These are not great
|
||||
label_color: Color::Muted.color(cx),
|
||||
// TODO: These are not great
|
||||
icon_color: Color::Muted.color(cx),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn focused(self, cx: &mut WindowContext) -> ButtonStyle {
|
||||
match self {
|
||||
ButtonStyle2::Filled => ButtonStyle {
|
||||
background: cx.theme().colors().element_background,
|
||||
border_color: cx.theme().colors().border_focused,
|
||||
label_color: Color::Default.color(cx),
|
||||
icon_color: Color::Default.color(cx),
|
||||
},
|
||||
ButtonStyle2::Subtle => ButtonStyle {
|
||||
background: cx.theme().colors().ghost_element_background,
|
||||
border_color: cx.theme().colors().border_focused,
|
||||
label_color: Color::Default.color(cx),
|
||||
icon_color: Color::Default.color(cx),
|
||||
},
|
||||
ButtonStyle2::Transparent => ButtonStyle {
|
||||
background: gpui::transparent_black(),
|
||||
border_color: cx.theme().colors().border_focused,
|
||||
label_color: Color::Accent.color(cx),
|
||||
icon_color: Color::Accent.color(cx),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn disabled(self, cx: &mut WindowContext) -> ButtonStyle {
|
||||
match self {
|
||||
ButtonStyle2::Filled => ButtonStyle {
|
||||
background: cx.theme().colors().element_disabled,
|
||||
border_color: cx.theme().colors().border_disabled,
|
||||
label_color: Color::Disabled.color(cx),
|
||||
icon_color: Color::Disabled.color(cx),
|
||||
},
|
||||
ButtonStyle2::Subtle => ButtonStyle {
|
||||
background: cx.theme().colors().ghost_element_disabled,
|
||||
border_color: cx.theme().colors().border_disabled,
|
||||
label_color: Color::Disabled.color(cx),
|
||||
icon_color: Color::Disabled.color(cx),
|
||||
},
|
||||
ButtonStyle2::Transparent => ButtonStyle {
|
||||
background: gpui::transparent_black(),
|
||||
border_color: gpui::transparent_black(),
|
||||
label_color: Color::Disabled.color(cx),
|
||||
icon_color: Color::Disabled.color(cx),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, PartialEq, Clone, Copy)]
|
||||
pub enum ButtonSize2 {
|
||||
#[default]
|
||||
Default,
|
||||
Compact,
|
||||
None,
|
||||
}
|
||||
|
||||
impl ButtonSize2 {
|
||||
fn height(self) -> Rems {
|
||||
match self {
|
||||
ButtonSize2::Default => rems(22. / 16.),
|
||||
ButtonSize2::Compact => rems(18. / 16.),
|
||||
ButtonSize2::None => rems(16. / 16.),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// pub struct Button {
|
||||
// id: ElementId,
|
||||
// icon: Option<Icon>,
|
||||
// icon_color: Option<Color>,
|
||||
// icon_position: Option<IconPosition2>,
|
||||
// label: Option<Label>,
|
||||
// label_color: Option<Color>,
|
||||
// appearance: ButtonAppearance2,
|
||||
// state: InteractionState,
|
||||
// selected: bool,
|
||||
// disabled: bool,
|
||||
// tooltip: Option<Box<dyn Fn(&mut WindowContext) -> AnyView>>,
|
||||
// width: Option<DefiniteLength>,
|
||||
// action: Option<Box<dyn Fn(&MouseDownEvent, &mut WindowContext) + 'static>>,
|
||||
// secondary_action: Option<Box<dyn Fn(&MouseDownEvent, &mut WindowContext) + 'static>>,
|
||||
// /// Used to pass down some content to the button
|
||||
// /// to enable creating custom buttons.
|
||||
// children: SmallVec<[AnyElement; 2]>,
|
||||
// }
|
||||
|
||||
pub trait ButtonCommon: Clickable {
|
||||
fn id(&self) -> &ElementId;
|
||||
fn style(self, style: ButtonStyle2) -> Self;
|
||||
fn disabled(self, disabled: bool) -> Self;
|
||||
fn size(self, size: ButtonSize2) -> Self;
|
||||
fn tooltip(self, tooltip: impl Fn(&mut WindowContext) -> AnyView + 'static) -> Self;
|
||||
// fn width(&mut self, width: DefiniteLength) -> &mut Self;
|
||||
}
|
||||
|
||||
// pub struct LabelButton {
|
||||
// // Base properties...
|
||||
// id: ElementId,
|
||||
// appearance: ButtonAppearance,
|
||||
// state: InteractionState,
|
||||
// disabled: bool,
|
||||
// size: ButtonSize,
|
||||
// tooltip: Option<Box<dyn Fn(&mut WindowContext) -> AnyView>>,
|
||||
// width: Option<DefiniteLength>,
|
||||
// // Button-specific properties...
|
||||
// label: Option<SharedString>,
|
||||
// label_color: Option<Color>,
|
||||
// icon: Option<Icon>,
|
||||
// icon_color: Option<Color>,
|
||||
// icon_position: Option<IconPosition>,
|
||||
// // Define more fields for additional properties as needed
|
||||
// }
|
||||
|
||||
// impl ButtonCommon for LabelButton {
|
||||
// fn id(&self) -> &ElementId {
|
||||
// &self.id
|
||||
// }
|
||||
|
||||
// fn appearance(&mut self, appearance: ButtonAppearance) -> &mut Self {
|
||||
// self.style= style;
|
||||
// self
|
||||
// }
|
||||
// // implement methods from ButtonCommon trait...
|
||||
// }
|
||||
|
||||
// impl LabelButton {
|
||||
// pub fn new(id: impl Into<ElementId>, label: impl Into<SharedString>) -> Self {
|
||||
// Self {
|
||||
// id: id.into(),
|
||||
// label: Some(label.into()),
|
||||
// // initialize other fields with default values...
|
||||
// }
|
||||
// }
|
||||
|
||||
// // ... Define other builder methods specific to Button type...
|
||||
// }
|
||||
|
||||
// TODO: Icon Button
|
||||
|
||||
#[derive(IntoElement)]
|
||||
pub struct ButtonLike {
|
||||
id: ElementId,
|
||||
style: ButtonStyle2,
|
||||
disabled: bool,
|
||||
size: ButtonSize2,
|
||||
tooltip: Option<Box<dyn Fn(&mut WindowContext) -> AnyView>>,
|
||||
on_click: Option<Box<dyn Fn(&ClickEvent, &mut WindowContext) + 'static>>,
|
||||
children: SmallVec<[AnyElement; 2]>,
|
||||
}
|
||||
|
||||
impl ButtonLike {
|
||||
pub fn children(
|
||||
&mut self,
|
||||
children: impl IntoIterator<Item = impl Into<AnyElement>>,
|
||||
) -> &mut Self {
|
||||
self.children = children.into_iter().map(Into::into).collect();
|
||||
self
|
||||
}
|
||||
|
||||
pub fn new(id: impl Into<ElementId>) -> Self {
|
||||
Self {
|
||||
id: id.into(),
|
||||
style: ButtonStyle2::default(),
|
||||
disabled: false,
|
||||
size: ButtonSize2::Default,
|
||||
tooltip: None,
|
||||
children: SmallVec::new(),
|
||||
on_click: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Clickable for ButtonLike {
|
||||
fn on_click(mut self, handler: impl Fn(&ClickEvent, &mut WindowContext) + 'static) -> Self {
|
||||
self.on_click = Some(Box::new(handler));
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
// impl Selectable for ButtonLike {
|
||||
// fn selected(&mut self, selected: bool) -> &mut Self {
|
||||
// todo!()
|
||||
// }
|
||||
|
||||
// fn selected_tooltip(
|
||||
// &mut self,
|
||||
// tooltip: Box<dyn Fn(&mut WindowContext) -> AnyView + 'static>,
|
||||
// ) -> &mut Self {
|
||||
// todo!()
|
||||
// }
|
||||
// }
|
||||
|
||||
impl ButtonCommon for ButtonLike {
|
||||
fn id(&self) -> &ElementId {
|
||||
&self.id
|
||||
}
|
||||
|
||||
fn style(mut self, style: ButtonStyle2) -> Self {
|
||||
self.style = style;
|
||||
self
|
||||
}
|
||||
|
||||
fn disabled(mut self, disabled: bool) -> Self {
|
||||
self.disabled = disabled;
|
||||
self
|
||||
}
|
||||
|
||||
fn size(mut self, size: ButtonSize2) -> Self {
|
||||
self.size = size;
|
||||
self
|
||||
}
|
||||
|
||||
fn tooltip(mut self, tooltip: impl Fn(&mut WindowContext) -> AnyView + 'static) -> Self {
|
||||
self.tooltip = Some(Box::new(tooltip));
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl RenderOnce for ButtonLike {
|
||||
type Rendered = Stateful<Div>;
|
||||
|
||||
fn render(self, cx: &mut WindowContext) -> Self::Rendered {
|
||||
h_stack()
|
||||
.id(self.id.clone())
|
||||
.h(self.size.height())
|
||||
.rounded_md()
|
||||
.cursor_pointer()
|
||||
.gap_1()
|
||||
.px_1()
|
||||
.bg(self.style.enabled(cx).background)
|
||||
.hover(|hover| hover.bg(self.style.hovered(cx).background))
|
||||
.active(|active| active.bg(self.style.active(cx).background))
|
||||
.when_some(
|
||||
self.on_click.filter(|_| !self.disabled),
|
||||
|this, on_click| this.on_click(move |event, cx| (on_click)(event, cx)),
|
||||
)
|
||||
.when_some(self.tooltip, |this, tooltip| {
|
||||
this.tooltip(move |cx| tooltip(cx))
|
||||
})
|
||||
.children(self.children)
|
||||
}
|
||||
}
|
||||
|
||||
impl ParentElement for ButtonLike {
|
||||
fn children_mut(&mut self) -> &mut SmallVec<[AnyElement; 2]> {
|
||||
&mut self.children
|
||||
}
|
||||
}
|
||||
|
||||
// pub struct ToggleButton {
|
||||
// // based on either IconButton2 or Button, with additional 'selected: bool' property
|
||||
// }
|
||||
|
||||
// impl ButtonCommon for ToggleButton {
|
||||
// fn id(&self) -> &ElementId {
|
||||
// &self.id
|
||||
// }
|
||||
// // ... Implement other methods from ButtonCommon trait with builder patterns...
|
||||
// }
|
||||
|
||||
// impl ToggleButton {
|
||||
// pub fn new() -> Self {
|
||||
// // Initialize with default values
|
||||
// Self {
|
||||
// // ... initialize fields, possibly with defaults or required parameters...
|
||||
// }
|
||||
// }
|
||||
|
||||
// // ... Define other builder methods specific to ToggleButton type...
|
||||
// }
|
||||
|
||||
// pub struct SplitButton {
|
||||
// // Base properties...
|
||||
// id: ElementId,
|
||||
// // Button-specific properties, possibly including a DefaultButton
|
||||
// secondary_action: Option<Box<dyn Fn(&MouseDownEvent, &mut WindowContext)>>,
|
||||
// // More fields as necessary...
|
||||
// }
|
||||
|
||||
// impl ButtonCommon for SplitButton {
|
||||
// fn id(&self) -> &ElementId {
|
||||
// &self.id
|
||||
// }
|
||||
// // ... Implement other methods from ButtonCommon trait with builder patterns...
|
||||
// }
|
||||
|
||||
// impl SplitButton {
|
||||
// pub fn new(id: impl Into<ElementId>) -> Self {
|
||||
// Self {
|
||||
// id: id.into(),
|
||||
// // ... initialize other fields with default values...
|
||||
// }
|
||||
// }
|
||||
|
||||
// // ... Define other builder methods specific to SplitButton type...
|
||||
// }
|
@ -8,7 +8,7 @@ pub struct IconButton {
|
||||
color: Color,
|
||||
size: IconSize,
|
||||
variant: ButtonVariant,
|
||||
state: InteractionState,
|
||||
disabled: bool,
|
||||
selected: bool,
|
||||
tooltip: Option<Box<dyn Fn(&mut WindowContext) -> AnyView + 'static>>,
|
||||
on_click: Option<Box<dyn Fn(&ClickEvent, &mut WindowContext) + 'static>>,
|
||||
@ -18,9 +18,9 @@ impl RenderOnce for IconButton {
|
||||
type Rendered = Stateful<Div>;
|
||||
|
||||
fn render(self, cx: &mut WindowContext) -> Self::Rendered {
|
||||
let icon_color = match (self.state, self.color) {
|
||||
(InteractionState::Disabled, _) => Color::Disabled,
|
||||
(InteractionState::Active, _) => Color::Selected,
|
||||
let icon_color = match (self.disabled, self.selected, self.color) {
|
||||
(true, _, _) => Color::Disabled,
|
||||
(false, true, _) => Color::Selected,
|
||||
_ => self.color,
|
||||
};
|
||||
|
||||
@ -82,8 +82,8 @@ impl IconButton {
|
||||
color: Color::default(),
|
||||
size: Default::default(),
|
||||
variant: ButtonVariant::default(),
|
||||
state: InteractionState::default(),
|
||||
selected: false,
|
||||
disabled: false,
|
||||
tooltip: None,
|
||||
on_click: None,
|
||||
}
|
||||
@ -109,13 +109,13 @@ impl IconButton {
|
||||
self
|
||||
}
|
||||
|
||||
pub fn state(mut self, state: InteractionState) -> Self {
|
||||
self.state = state;
|
||||
pub fn selected(mut self, selected: bool) -> Self {
|
||||
self.selected = selected;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn selected(mut self, selected: bool) -> Self {
|
||||
self.selected = selected;
|
||||
pub fn disabled(mut self, disabled: bool) -> Self {
|
||||
self.disabled = disabled;
|
||||
self
|
||||
}
|
||||
|
||||
|
@ -1,108 +0,0 @@
|
||||
use crate::{prelude::*, Label};
|
||||
use gpui::{prelude::*, Div, IntoElement, Stateful};
|
||||
|
||||
#[derive(Default, PartialEq)]
|
||||
pub enum InputVariant {
|
||||
#[default]
|
||||
Ghost,
|
||||
Filled,
|
||||
}
|
||||
|
||||
#[derive(IntoElement)]
|
||||
pub struct Input {
|
||||
placeholder: SharedString,
|
||||
value: String,
|
||||
state: InteractionState,
|
||||
variant: InputVariant,
|
||||
disabled: bool,
|
||||
is_active: bool,
|
||||
}
|
||||
|
||||
impl RenderOnce for Input {
|
||||
type Rendered = Stateful<Div>;
|
||||
|
||||
fn render(self, cx: &mut WindowContext) -> Self::Rendered {
|
||||
let (input_bg, input_hover_bg, input_active_bg) = match self.variant {
|
||||
InputVariant::Ghost => (
|
||||
cx.theme().colors().ghost_element_background,
|
||||
cx.theme().colors().ghost_element_hover,
|
||||
cx.theme().colors().ghost_element_active,
|
||||
),
|
||||
InputVariant::Filled => (
|
||||
cx.theme().colors().element_background,
|
||||
cx.theme().colors().element_hover,
|
||||
cx.theme().colors().element_active,
|
||||
),
|
||||
};
|
||||
|
||||
let placeholder_label = Label::new(self.placeholder.clone()).color(if self.disabled {
|
||||
Color::Disabled
|
||||
} else {
|
||||
Color::Placeholder
|
||||
});
|
||||
|
||||
let label = Label::new(self.value.clone()).color(if self.disabled {
|
||||
Color::Disabled
|
||||
} else {
|
||||
Color::Default
|
||||
});
|
||||
|
||||
div()
|
||||
.id("input")
|
||||
.h_7()
|
||||
.w_full()
|
||||
.px_2()
|
||||
.border()
|
||||
.border_color(cx.theme().styles.system.transparent)
|
||||
.bg(input_bg)
|
||||
.hover(|style| style.bg(input_hover_bg))
|
||||
.active(|style| style.bg(input_active_bg))
|
||||
.flex()
|
||||
.items_center()
|
||||
.child(div().flex().items_center().text_ui_sm().map(move |this| {
|
||||
if self.value.is_empty() {
|
||||
this.child(placeholder_label)
|
||||
} else {
|
||||
this.child(label)
|
||||
}
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
impl Input {
|
||||
pub fn new(placeholder: impl Into<SharedString>) -> Self {
|
||||
Self {
|
||||
placeholder: placeholder.into(),
|
||||
value: "".to_string(),
|
||||
state: InteractionState::default(),
|
||||
variant: InputVariant::default(),
|
||||
disabled: false,
|
||||
is_active: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn value(mut self, value: String) -> Self {
|
||||
self.value = value;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn state(mut self, state: InteractionState) -> Self {
|
||||
self.state = state;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn variant(mut self, variant: InputVariant) -> Self {
|
||||
self.variant = variant;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn disabled(mut self, disabled: bool) -> Self {
|
||||
self.disabled = disabled;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn is_active(mut self, is_active: bool) -> Self {
|
||||
self.is_active = is_active;
|
||||
self
|
||||
}
|
||||
}
|
@ -4,18 +4,15 @@ mod checkbox;
|
||||
mod context_menu;
|
||||
mod icon;
|
||||
mod icon_button;
|
||||
mod input;
|
||||
mod keybinding;
|
||||
mod label;
|
||||
mod list_item;
|
||||
|
||||
pub use avatar::*;
|
||||
pub use button::*;
|
||||
pub use checkbox::*;
|
||||
pub use context_menu::*;
|
||||
pub use icon::*;
|
||||
pub use icon_button::*;
|
||||
pub use input::*;
|
||||
pub use keybinding::*;
|
||||
pub use label::*;
|
||||
pub use list_item::*;
|
||||
|
@ -1,9 +1,8 @@
|
||||
use gpui::{rems, Div, Render};
|
||||
use gpui::{Div, Render};
|
||||
use story::Story;
|
||||
use strum::IntoEnumIterator;
|
||||
|
||||
use crate::prelude::*;
|
||||
use crate::{h_stack, v_stack, Button, Icon, IconPosition, Label};
|
||||
use crate::{h_stack, Button, Icon, IconPosition};
|
||||
|
||||
pub struct ButtonStory;
|
||||
|
||||
@ -11,8 +10,6 @@ impl Render for ButtonStory {
|
||||
type Element = Div;
|
||||
|
||||
fn render(&mut self, _cx: &mut ViewContext<Self>) -> Self::Element {
|
||||
let states = InteractionState::iter();
|
||||
|
||||
Story::container()
|
||||
.child(Story::title_for::<Button>())
|
||||
.child(
|
||||
@ -20,121 +17,56 @@ impl Render for ButtonStory {
|
||||
.flex()
|
||||
.gap_8()
|
||||
.child(
|
||||
div()
|
||||
.child(Story::label("Ghost (Default)"))
|
||||
.child(h_stack().gap_2().children(states.clone().map(|state| {
|
||||
v_stack()
|
||||
.gap_1()
|
||||
.child(Label::new(state.to_string()).color(Color::Muted))
|
||||
.child(
|
||||
Button::new("Label").variant(ButtonVariant::Ghost), // .state(state),
|
||||
)
|
||||
})))
|
||||
.child(Story::label("Ghost – Left Icon"))
|
||||
.child(h_stack().gap_2().children(states.clone().map(|state| {
|
||||
v_stack()
|
||||
.gap_1()
|
||||
.child(Label::new(state.to_string()).color(Color::Muted))
|
||||
.child(
|
||||
Button::new("Label")
|
||||
.variant(ButtonVariant::Ghost)
|
||||
.icon(Icon::Plus)
|
||||
.icon_position(IconPosition::Left), // .state(state),
|
||||
)
|
||||
})))
|
||||
.child(Story::label("Ghost – Right Icon"))
|
||||
.child(h_stack().gap_2().children(states.clone().map(|state| {
|
||||
v_stack()
|
||||
.gap_1()
|
||||
.child(Label::new(state.to_string()).color(Color::Muted))
|
||||
.child(
|
||||
Button::new("Label")
|
||||
.variant(ButtonVariant::Ghost)
|
||||
.icon(Icon::Plus)
|
||||
.icon_position(IconPosition::Right), // .state(state),
|
||||
)
|
||||
}))),
|
||||
div().child(Story::label("Ghost (Default)")).child(
|
||||
h_stack()
|
||||
.gap_2()
|
||||
.child(Button::new("Label").variant(ButtonVariant::Ghost)),
|
||||
),
|
||||
)
|
||||
.child(Story::label("Ghost – Left Icon"))
|
||||
.child(
|
||||
div()
|
||||
.child(Story::label("Filled"))
|
||||
.child(h_stack().gap_2().children(states.clone().map(|state| {
|
||||
v_stack()
|
||||
.gap_1()
|
||||
.child(Label::new(state.to_string()).color(Color::Muted))
|
||||
.child(
|
||||
Button::new("Label").variant(ButtonVariant::Filled), // .state(state),
|
||||
)
|
||||
})))
|
||||
.child(Story::label("Filled – Left Button"))
|
||||
.child(h_stack().gap_2().children(states.clone().map(|state| {
|
||||
v_stack()
|
||||
.gap_1()
|
||||
.child(Label::new(state.to_string()).color(Color::Muted))
|
||||
.child(
|
||||
Button::new("Label")
|
||||
.variant(ButtonVariant::Filled)
|
||||
.icon(Icon::Plus)
|
||||
.icon_position(IconPosition::Left), // .state(state),
|
||||
)
|
||||
})))
|
||||
.child(Story::label("Filled – Right Button"))
|
||||
.child(h_stack().gap_2().children(states.clone().map(|state| {
|
||||
v_stack()
|
||||
.gap_1()
|
||||
.child(Label::new(state.to_string()).color(Color::Muted))
|
||||
.child(
|
||||
Button::new("Label")
|
||||
.variant(ButtonVariant::Filled)
|
||||
.icon(Icon::Plus)
|
||||
.icon_position(IconPosition::Right), // .state(state),
|
||||
)
|
||||
}))),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.child(Story::label("Fixed With"))
|
||||
.child(h_stack().gap_2().children(states.clone().map(|state| {
|
||||
v_stack()
|
||||
.gap_1()
|
||||
.child(Label::new(state.to_string()).color(Color::Muted))
|
||||
.child(
|
||||
Button::new("Label")
|
||||
.variant(ButtonVariant::Filled)
|
||||
// .state(state)
|
||||
.width(Some(rems(6.).into())),
|
||||
)
|
||||
})))
|
||||
.child(Story::label("Fixed With – Left Icon"))
|
||||
.child(h_stack().gap_2().children(states.clone().map(|state| {
|
||||
v_stack()
|
||||
.gap_1()
|
||||
.child(Label::new(state.to_string()).color(Color::Muted))
|
||||
.child(
|
||||
Button::new("Label")
|
||||
.variant(ButtonVariant::Filled)
|
||||
// .state(state)
|
||||
.icon(Icon::Plus)
|
||||
.icon_position(IconPosition::Left)
|
||||
.width(Some(rems(6.).into())),
|
||||
)
|
||||
})))
|
||||
.child(Story::label("Fixed With – Right Icon"))
|
||||
.child(h_stack().gap_2().children(states.clone().map(|state| {
|
||||
v_stack()
|
||||
.gap_1()
|
||||
.child(Label::new(state.to_string()).color(Color::Muted))
|
||||
.child(
|
||||
Button::new("Label")
|
||||
.variant(ButtonVariant::Filled)
|
||||
// .state(state)
|
||||
.icon(Icon::Plus)
|
||||
.icon_position(IconPosition::Right)
|
||||
.width(Some(rems(6.).into())),
|
||||
)
|
||||
}))),
|
||||
h_stack().gap_2().child(
|
||||
Button::new("Label")
|
||||
.variant(ButtonVariant::Ghost)
|
||||
.icon(Icon::Plus)
|
||||
.icon_position(IconPosition::Left),
|
||||
),
|
||||
),
|
||||
)
|
||||
.child(Story::label("Ghost – Right Icon"))
|
||||
.child(
|
||||
h_stack().gap_2().child(
|
||||
Button::new("Label")
|
||||
.variant(ButtonVariant::Ghost)
|
||||
.icon(Icon::Plus)
|
||||
.icon_position(IconPosition::Right),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
div().child(Story::label("Filled")).child(
|
||||
h_stack()
|
||||
.gap_2()
|
||||
.child(Button::new("Label").variant(ButtonVariant::Filled)),
|
||||
),
|
||||
)
|
||||
.child(Story::label("Filled – Left Button"))
|
||||
.child(
|
||||
h_stack().gap_2().child(
|
||||
Button::new("Label")
|
||||
.variant(ButtonVariant::Filled)
|
||||
.icon(Icon::Plus)
|
||||
.icon_position(IconPosition::Left),
|
||||
),
|
||||
)
|
||||
.child(Story::label("Filled – Right Button"))
|
||||
.child(
|
||||
h_stack().gap_2().child(
|
||||
Button::new("Label")
|
||||
.variant(ButtonVariant::Filled)
|
||||
.icon(Icon::Plus)
|
||||
.icon_position(IconPosition::Right),
|
||||
),
|
||||
)
|
||||
.child(Story::label("Button with `on_click`"))
|
||||
.child(
|
||||
Button::new("Label")
|
||||
|
@ -1,18 +0,0 @@
|
||||
use gpui::{Div, Render};
|
||||
use story::Story;
|
||||
|
||||
use crate::prelude::*;
|
||||
use crate::Input;
|
||||
|
||||
pub struct InputStory;
|
||||
|
||||
impl Render for InputStory {
|
||||
type Element = Div;
|
||||
|
||||
fn render(&mut self, _cx: &mut ViewContext<Self>) -> Self::Element {
|
||||
Story::container()
|
||||
.child(Story::title_for::<Input>())
|
||||
.child(Story::label("Default"))
|
||||
.child(div().flex().child(Input::new("Search")))
|
||||
}
|
||||
}
|
6
crates/ui2/src/fixed.rs
Normal file
6
crates/ui2/src/fixed.rs
Normal file
@ -0,0 +1,6 @@
|
||||
use gpui::DefiniteLength;
|
||||
|
||||
pub trait FixedWidth {
|
||||
fn width(self, width: DefiniteLength) -> Self;
|
||||
fn full_width(self) -> Self;
|
||||
}
|
@ -3,62 +3,9 @@ pub use gpui::{
|
||||
ViewContext, WindowContext,
|
||||
};
|
||||
|
||||
pub use crate::clickable::*;
|
||||
pub use crate::fixed::*;
|
||||
pub use crate::selectable::*;
|
||||
pub use crate::StyledExt;
|
||||
pub use crate::{ButtonVariant, Color};
|
||||
pub use theme::ActiveTheme;
|
||||
|
||||
use strum::EnumIter;
|
||||
|
||||
#[derive(Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, EnumIter)]
|
||||
pub enum IconSide {
|
||||
#[default]
|
||||
Left,
|
||||
Right,
|
||||
}
|
||||
|
||||
#[derive(Default, PartialEq, Copy, Clone, EnumIter, strum::Display)]
|
||||
pub enum InteractionState {
|
||||
/// An element that is enabled and not hovered, active, focused, or disabled.
|
||||
///
|
||||
/// This is often referred to as the "default" state.
|
||||
#[default]
|
||||
Enabled,
|
||||
/// An element that is hovered.
|
||||
Hovered,
|
||||
/// An element has an active mouse down or touch start event on it.
|
||||
Active,
|
||||
/// An element that is focused using the keyboard.
|
||||
Focused,
|
||||
/// An element that is disabled.
|
||||
Disabled,
|
||||
/// A toggleable element that is selected, like the active button in a
|
||||
/// button toggle group.
|
||||
Selected,
|
||||
}
|
||||
|
||||
impl InteractionState {
|
||||
pub fn if_enabled(&self, enabled: bool) -> Self {
|
||||
if enabled {
|
||||
*self
|
||||
} else {
|
||||
InteractionState::Disabled
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, PartialEq, Eq, Hash, Clone, Copy)]
|
||||
pub enum Selection {
|
||||
#[default]
|
||||
Unselected,
|
||||
Indeterminate,
|
||||
Selected,
|
||||
}
|
||||
|
||||
impl Selection {
|
||||
pub fn inverse(&self) -> Self {
|
||||
match self {
|
||||
Self::Unselected | Self::Indeterminate => Self::Selected,
|
||||
Self::Selected => Self::Unselected,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
26
crates/ui2/src/selectable.rs
Normal file
26
crates/ui2/src/selectable.rs
Normal file
@ -0,0 +1,26 @@
|
||||
use gpui::{AnyView, WindowContext};
|
||||
|
||||
pub trait Selectable {
|
||||
fn selected(self, selected: bool) -> Self;
|
||||
fn selected_tooltip(
|
||||
self,
|
||||
tooltip: Box<dyn Fn(&mut WindowContext) -> AnyView + 'static>,
|
||||
) -> Self;
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, PartialEq, Eq, Hash, Clone, Copy)]
|
||||
pub enum Selection {
|
||||
#[default]
|
||||
Unselected,
|
||||
Indeterminate,
|
||||
Selected,
|
||||
}
|
||||
|
||||
impl Selection {
|
||||
pub fn inverse(&self) -> Self {
|
||||
match self {
|
||||
Self::Unselected | Self::Indeterminate => Self::Selected,
|
||||
Self::Selected => Self::Unselected,
|
||||
}
|
||||
}
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
use gpui::{Hsla, WindowContext};
|
||||
use theme::ActiveTheme;
|
||||
|
||||
#[derive(Default, PartialEq, Copy, Clone)]
|
||||
#[derive(Debug, Default, PartialEq, Copy, Clone)]
|
||||
pub enum Color {
|
||||
#[default]
|
||||
Default,
|
||||
|
@ -12,13 +12,19 @@
|
||||
#![doc = include_str!("../docs/building-ui.md")]
|
||||
#![doc = include_str!("../docs/todo.md")]
|
||||
|
||||
mod clickable;
|
||||
mod components;
|
||||
mod fixed;
|
||||
pub mod prelude;
|
||||
mod selectable;
|
||||
mod styled_ext;
|
||||
mod styles;
|
||||
pub mod utils;
|
||||
|
||||
pub use clickable::*;
|
||||
pub use components::*;
|
||||
pub use fixed::*;
|
||||
pub use prelude::*;
|
||||
pub use selectable::*;
|
||||
pub use styled_ext::*;
|
||||
pub use styles::*;
|
||||
|
@ -7,8 +7,8 @@ use gpui::{
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::sync::Arc;
|
||||
use theme2::ActiveTheme;
|
||||
use ui::{h_stack, menu_handle, ContextMenu, IconButton, InteractionState, Tooltip};
|
||||
use ui::prelude::*;
|
||||
use ui::{h_stack, menu_handle, ContextMenu, IconButton, Tooltip};
|
||||
|
||||
pub enum PanelEvent {
|
||||
ChangePosition,
|
||||
@ -686,22 +686,26 @@ impl Render for PanelButtons {
|
||||
let name = entry.panel.persistent_name();
|
||||
let panel = entry.panel.clone();
|
||||
|
||||
let mut button: IconButton = if i == active_index && is_open {
|
||||
let is_active_button = i == active_index && is_open;
|
||||
|
||||
let (action, tooltip) = if is_active_button {
|
||||
let action = dock.toggle_action();
|
||||
|
||||
let tooltip: SharedString =
|
||||
format!("Close {} dock", dock.position.to_label()).into();
|
||||
IconButton::new(name, icon)
|
||||
.state(InteractionState::Active)
|
||||
.action(action.boxed_clone())
|
||||
.tooltip(move |cx| Tooltip::for_action(tooltip.clone(), &*action, cx))
|
||||
|
||||
(action, tooltip)
|
||||
} else {
|
||||
let action = entry.panel.toggle_action(cx);
|
||||
|
||||
IconButton::new(name, icon)
|
||||
.action(action.boxed_clone())
|
||||
.tooltip(move |cx| Tooltip::for_action(name, &*action, cx))
|
||||
(action, name.into())
|
||||
};
|
||||
|
||||
let button = IconButton::new(name, icon)
|
||||
.selected(is_active_button)
|
||||
.action(action.boxed_clone())
|
||||
.tooltip(move |cx| Tooltip::for_action(tooltip.clone(), &*action, cx));
|
||||
|
||||
Some(
|
||||
menu_handle(name)
|
||||
.menu(move |cx| {
|
||||
|
@ -1482,18 +1482,14 @@ impl Pane {
|
||||
.gap_px()
|
||||
.child(
|
||||
div().border().border_color(gpui::red()).child(
|
||||
IconButton::new("navigate_backward", Icon::ArrowLeft).state(
|
||||
InteractionState::Enabled
|
||||
.if_enabled(self.can_navigate_backward()),
|
||||
),
|
||||
IconButton::new("navigate_backward", Icon::ArrowLeft)
|
||||
.disabled(!self.can_navigate_backward()),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
div().border().border_color(gpui::red()).child(
|
||||
IconButton::new("navigate_forward", Icon::ArrowRight).state(
|
||||
InteractionState::Enabled
|
||||
.if_enabled(self.can_navigate_forward()),
|
||||
),
|
||||
IconButton::new("navigate_forward", Icon::ArrowRight)
|
||||
.disabled(!self.can_navigate_forward()),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
@ -5,7 +5,7 @@ use gpui::{
|
||||
div, AnyView, Div, IntoElement, ParentElement, Render, Styled, Subscription, View, ViewContext,
|
||||
WindowContext,
|
||||
};
|
||||
use theme2::ActiveTheme;
|
||||
use ui::prelude::*;
|
||||
use ui::{h_stack, Button, Icon, IconButton};
|
||||
use util::ResultExt;
|
||||
|
||||
|
@ -3,7 +3,7 @@ use gpui::{
|
||||
div, AnyView, Div, Entity, EntityId, EventEmitter, ParentElement as _, Render, Styled, View,
|
||||
ViewContext, WindowContext,
|
||||
};
|
||||
use theme2::ActiveTheme;
|
||||
use ui::prelude::*;
|
||||
use ui::{h_stack, v_stack, Button, Color, Icon, IconButton, Label};
|
||||
|
||||
pub enum ToolbarItemEvent {
|
||||
|
Loading…
Reference in New Issue
Block a user