Jason Lee 2024-07-12 17:33:05 +08:00 committed by GitHub
parent ff8d39d4a9
commit 7c99584862
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
18 changed files with 219 additions and 109 deletions

1
Cargo.lock generated
View file

@ -5427,7 +5427,6 @@ dependencies = [
"taffy",
"unicode-segmentation",
"usvg",
"uuid",
"windows",
"wry",
]

View file

@ -37,6 +37,7 @@ This is an example of build app by using GPUI.
- [x] Button with Icon
- [x] IconButton
- [x] Glost / Outline Button
- [x] Loading
- [x] Label
- [x] Icon
- [x] Checkbox
@ -58,8 +59,8 @@ This is an example of build app by using GPUI.
- [x] Dockpanel
- [x] Resizable
- [x] Progress
- [ ] ProgressBar
- [ ] Loading
- [x] ProgressBar
- [x] Indicator
- [ ] Skeleton
- [ ] DatePicker
- [ ] DateTimePicker

View file

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-loader-circle">
<path d="M21 12a9 9 0 1 1-6.219-8.56"/>
</svg>

After

Width:  |  Height:  |  Size: 266 B

10
assets/icons/loader.svg Normal file
View file

@ -0,0 +1,10 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-loader">
<path d="M12 2v4"/>
<path d="m16.2 7.8 2.9-2.9"/>
<path d="M18 12h4"/>
<path d="m16.2 16.2 2.9 2.9"/>
<path d="M12 18v4"/>
<path d="m4.9 19.1 2.9-2.9"/>
<path d="M2 12h4"/>
<path d="m4.9 4.9 2.9 2.9"/>
</svg>

After

Width:  |  Height:  |  Size: 435 B

View file

@ -9,9 +9,9 @@ use workspace::{dock::DockPosition, TitleBar, Workspace};
use std::sync::Arc;
use ui::{
button::ButtonSize,
switch::{LabelSide, Switch},
theme::{ActiveTheme, Theme},
Size,
};
use crate::app_state::AppState;
@ -173,7 +173,7 @@ impl StoryWorkspace {
appears_transparent: true,
traffic_light_position: Some(point(px(9.0), px(9.0))),
}),
window_min_size: Some(Size {
window_min_size: Some(gpui::Size {
width: px(640.),
height: px(480.),
}),
@ -255,7 +255,7 @@ impl Render for StoryWorkspace {
.mr_3()
.child(
Switch::new("theme-mode")
.size(ButtonSize::Small)
.size(Size::Small)
.checked(cx.theme().mode.is_dark())
.label_side(LabelSide::Left)
.label("Dark Mode")

View file

@ -4,8 +4,8 @@ use gpui::{
};
use ui::{
button::{Button, ButtonSize, ButtonStyle},
h_flex, v_flex, Clickable, Disableable as _, Icon, IconName, Selectable,
button::{Button, ButtonStyle},
h_flex, v_flex, Clickable, Disableable as _, Icon, IconName, Selectable, Size,
};
use crate::section;
@ -96,21 +96,22 @@ impl Render for ButtonStory {
Button::new("button-6", cx)
.label("Primary Button")
.style(ButtonStyle::Primary)
.size(ButtonSize::Small)
.size(Size::Small)
.loading(true)
.on_click(Self::on_click),
)
.child(
Button::new("button-7", cx)
.label("Secondary Button")
.style(ButtonStyle::Secondary)
.size(ButtonSize::Small)
.size(Size::Small)
.on_click(Self::on_click),
)
.child(
Button::new("button-8", cx)
.label("Danger Button")
.style(ButtonStyle::Danger)
.size(ButtonSize::Small)
.size(Size::Small)
.on_click(Self::on_click),
),
)
@ -120,21 +121,22 @@ impl Render for ButtonStory {
Button::new("button-xs-1", cx)
.label("Primary Button")
.style(ButtonStyle::Primary)
.size(ButtonSize::XSmall)
.size(Size::XSmall)
.on_click(Self::on_click),
)
.child(
Button::new("button-xs-2", cx)
.label("Secondary Button")
.style(ButtonStyle::Secondary)
.size(ButtonSize::XSmall)
.size(Size::XSmall)
.loading(true)
.on_click(Self::on_click),
)
.child(
Button::new("button-xs-3", cx)
.label("Danger Button")
.style(ButtonStyle::Danger)
.size(ButtonSize::XSmall)
.size(Size::XSmall)
.on_click(Self::on_click),
),
),
@ -163,7 +165,8 @@ impl Render for ButtonStory {
.label("Disabled Button")
.style(ButtonStyle::Danger)
.on_click(Self::on_click)
.disabled(true),
.disabled(true)
.loading(true),
),
)
.child(
@ -195,7 +198,11 @@ impl Render for ButtonStory {
.icon(IconName::Search)
.style(ButtonStyle::Primary),
)
.child(Button::new("icon-button-1", cx).icon(IconName::Info))
.child(
Button::new("icon-button-1", cx)
.icon(IconName::Info)
.loading(true),
)
.child(
Button::new("icon-button-2", cx)
.icon(IconName::Close)
@ -204,7 +211,7 @@ impl Render for ButtonStory {
.child(
Button::new("icon-button-3", cx)
.icon(IconName::Search)
.size(ButtonSize::Small)
.size(Size::Small)
.style(ButtonStyle::Primary),
)
.child(
@ -223,29 +230,29 @@ impl Render for ButtonStory {
.child(
Button::new("icon-button-4", cx)
.icon(IconName::Info)
.size(ButtonSize::Small),
.size(Size::Small),
)
.child(
Button::new("icon-button-5", cx)
.icon(IconName::Close)
.size(ButtonSize::Small)
.size(Size::Small)
.style(ButtonStyle::Danger),
)
.child(
Button::new("icon-button-6", cx)
.icon(IconName::Search)
.size(ButtonSize::XSmall)
.size(Size::XSmall)
.style(ButtonStyle::Primary),
)
.child(
Button::new("icon-button-7", cx)
.icon(IconName::Info)
.size(ButtonSize::XSmall),
.size(Size::XSmall),
)
.child(
Button::new("icon-button-8", cx)
.icon(IconName::Close)
.size(ButtonSize::XSmall)
.size(Size::XSmall)
.style(ButtonStyle::Danger),
),
)

View file

@ -4,7 +4,7 @@ use gpui::{
ParentElement as _, Render, Styled as _, View, ViewContext, VisualContext, WindowContext,
};
use ui::{
button::{Button, ButtonSize},
button::Button,
divider::Divider,
h_flex,
input::TextInput,
@ -12,7 +12,7 @@ use ui::{
popup_menu::PopupMenu,
prelude::FluentBuilder,
switch::Switch,
v_flex, Clickable, IconName,
v_flex, Clickable, IconName, Size,
};
actions!(popover_story, [Copy, Paste, Cut, SearchAll]);
@ -141,7 +141,7 @@ impl Render for PopoverStory {
Button::new("info1", cx)
.label("Yes")
.width(px(80.))
.size(ButtonSize::Small),
.size(Size::Small),
)
.into_any()
})
@ -164,7 +164,7 @@ impl Render for PopoverStory {
Button::new("info1", cx)
.label("Yes")
.width(px(80.))
.size(ButtonSize::Small),
.size(Size::Small),
)
.into_any()
})
@ -234,7 +234,7 @@ impl Render for PopoverStory {
Button::new("info1", cx)
.label("Yes")
.width(px(80.))
.size(ButtonSize::Small),
.size(Size::Small),
)
.into_any()
})

View file

@ -1,8 +1,11 @@
use gpui::{
div, IntoElement, ParentElement, Render, Styled, View, ViewContext, VisualContext,
div, px, IntoElement, ParentElement, Render, Styled, View, ViewContext, VisualContext,
WindowContext,
};
use ui::{button::Button, h_flex, progress::Progress, v_flex, Clickable, IconName};
use ui::{
button::Button, h_flex, indicator::Indicator, progress::Progress, v_flex, Clickable, IconName,
Size,
};
pub struct ProgressStory {
value: f32,
@ -74,5 +77,19 @@ impl Render for ProgressStory {
})),
),
)
.child(
h_flex()
.gap_x_2()
.child(Indicator::new().size(Size::XSmall))
.child(Indicator::new().size(Size::Small))
.child(Indicator::new())
.child(
Indicator::new()
.size(Size::Large)
.icon(IconName::LoaderCircle)
.color(ui::blue_500()),
)
.child(Indicator::new().size(px(64.))),
)
}
}

View file

@ -4,12 +4,11 @@ use gpui::{
};
use ui::{
button::ButtonSize,
h_flex,
label::Label,
switch::{LabelSide, Switch},
theme::ActiveTheme,
v_flex, Disableable as _, StyledExt,
v_flex, Disableable as _, Size, StyledExt,
};
#[derive(Default)]
@ -116,7 +115,7 @@ impl Render for SwitchStory {
.items_start().child(title("Disabled Switchs")).child(
h_flex().items_center()
.gap_6()
.child(Switch::new("switch3").checked(self.switch3).label("Small Size").size(ButtonSize::Small).on_click(cx.listener(move |view, checked, cx| {
.child(Switch::new("switch3").checked(self.switch3).label("Small Size").size(Size::Small).on_click(cx.listener(move |view, checked, cx| {
view.switch3 = *checked;
cx.notify();
})),

View file

@ -24,7 +24,6 @@ usvg = { version = "0.41.0", default-features = false }
taffy = "0.4.3"
paste = "1"
once_cell = "1.19.0"
uuid = "1.10.0"
[lints]
workspace = true

View file

@ -1,7 +1,8 @@
use crate::{
h_flex,
indicator::Indicator,
theme::{ActiveTheme, Colorize as _},
Clickable, Disableable, Icon, Selectable, StyledExt,
Clickable, Disableable, Icon, Selectable, Size, StyledExt,
};
use gpui::{
div, prelude::FluentBuilder as _, px, ClickEvent, DefiniteLength, Div, ElementId, FocusHandle,
@ -16,13 +17,6 @@ pub enum ButtonRounded {
Large,
}
#[derive(Clone, Copy)]
pub enum ButtonSize {
XSmall,
Small,
Medium,
}
#[derive(Clone, Copy)]
pub enum ButtonStyle {
Primary,
@ -45,9 +39,10 @@ pub struct Button {
height: Option<DefiniteLength>,
style: ButtonStyle,
rounded: ButtonRounded,
size: ButtonSize,
size: Size,
tooltip: Option<SharedString>,
on_click: Option<Box<dyn Fn(&ClickEvent, &mut WindowContext) + 'static>>,
loading: bool,
}
impl Button {
@ -64,9 +59,10 @@ impl Button {
width: None,
height: None,
rounded: ButtonRounded::Medium,
size: ButtonSize::Medium,
size: Size::Medium,
tooltip: None,
on_click: None,
loading: false,
}
}
@ -91,7 +87,7 @@ impl Button {
label: impl Into<SharedString>,
cx: &mut WindowContext,
) -> Self {
Self::new(id, cx).label(label).size(ButtonSize::Small)
Self::new(id, cx).label(label).size(Size::Small)
}
pub fn width(mut self, width: impl Into<DefiniteLength>) -> Self {
@ -109,8 +105,8 @@ impl Button {
self
}
pub fn size(mut self, size: ButtonSize) -> Self {
self.size = size;
pub fn size(mut self, size: impl Into<Size>) -> Self {
self.size = size.into();
self
}
@ -134,6 +130,11 @@ impl Button {
self.style = style;
self
}
pub fn loading(mut self, loading: bool) -> Self {
self.loading = loading;
self
}
}
impl Disableable for Button {
@ -180,15 +181,16 @@ impl RenderOnce for Button {
.map(|this| {
if self.label.is_none() {
match self.size {
ButtonSize::XSmall => this.size_5(),
ButtonSize::Small => this.size_6(),
ButtonSize::Medium => this.size_8(),
Size::Size(px) => this.size(px),
Size::XSmall => this.size_5(),
Size::Small => this.size_6(),
Size::Large | Size::Medium => this.size_8(),
}
} else {
match self.size {
ButtonSize::XSmall => this.px_1().py_1().h_5(),
ButtonSize::Small => this.px_3().py_2().h_6(),
ButtonSize::Medium => this.px_4().py_2().h_8(),
Size::XSmall => this.px_1().py_1().h_5(),
Size::Small => this.px_3().py_2().h_6(),
_ => this.px_4().py_2().h_8(),
}
}
})
@ -247,14 +249,19 @@ impl RenderOnce for Button {
.justify_center()
.gap_2()
.text_color(text_color)
.when_some(self.icon, |this, icon| {
this.child(icon.size(self.size).text_color(text_color))
.when(!self.loading, |this| {
this.when_some(self.icon, |this, icon| {
this.child(icon.size(self.size).text_color(text_color))
})
})
.when(self.loading, |this| {
this.child(Indicator::new().size(self.size).color(text_color))
})
.when_some(self.label, |this, label| this.child(label))
.map(|this| match self.size {
ButtonSize::XSmall => this.text_xs(),
ButtonSize::Small => this.text_sm(),
ButtonSize::Medium => this.text_base(),
Size::XSmall => this.text_xs(),
Size::Small => this.text_sm(),
_ => this.text_base(),
})
})
}

View file

@ -1,6 +1,6 @@
use crate::{button::ButtonSize, theme::ActiveTheme};
use crate::{theme::ActiveTheme, Size};
use gpui::{
prelude::FluentBuilder as _, svg, AnyElement, Hsla, IntoElement, Pixels, Render, RenderOnce,
prelude::FluentBuilder as _, svg, AnyElement, Hsla, IntoElement, Render, RenderOnce,
SharedString, StyleRefinement, Styled, Svg, View, VisualContext, WindowContext,
};
@ -21,6 +21,8 @@ pub enum IconName {
Search,
Delete,
CircleX,
Loader,
LoaderCircle,
}
impl IconName {
@ -41,6 +43,8 @@ impl IconName {
IconName::Search => "icons/search.svg",
IconName::Delete => "icons/delete.svg",
IconName::CircleX => "icons/circle-x.svg",
IconName::Loader => "icons/loader.svg",
IconName::LoaderCircle => "icons/loader-circle.svg",
}
.into()
}
@ -69,36 +73,12 @@ impl RenderOnce for IconName {
}
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum IconSize {
XSmall,
Small,
Medium,
Size(Pixels),
}
impl From<ButtonSize> for IconSize {
fn from(size: ButtonSize) -> Self {
match size {
ButtonSize::XSmall => IconSize::XSmall,
ButtonSize::Small => IconSize::Small,
ButtonSize::Medium => IconSize::Medium,
}
}
}
impl From<Pixels> for IconSize {
fn from(size: Pixels) -> Self {
IconSize::Size(size)
}
}
#[derive(IntoElement)]
pub struct Icon {
base: Svg,
path: SharedString,
text_color: Option<Hsla>,
size: IconSize,
size: Size,
}
impl Default for Icon {
@ -107,7 +87,7 @@ impl Default for Icon {
base: svg().flex_none().size_4(),
path: "".into(),
text_color: None,
size: IconSize::Medium,
size: Size::Medium,
}
}
}
@ -135,7 +115,7 @@ impl Icon {
///
/// Also can receive a `ButtonSize` to convert to `IconSize`,
/// Or a `Pixels` to set a custom size: `px(30.)`
pub fn size(mut self, size: impl Into<IconSize>) -> Self {
pub fn size(mut self, size: impl Into<Size>) -> Self {
self.size = size.into();
self
@ -145,6 +125,11 @@ impl Icon {
pub fn view(self, cx: &mut WindowContext) -> View<Icon> {
cx.new_view(|_| self)
}
pub fn transform(mut self, transformation: gpui::Transformation) -> Self {
self.base = self.base.with_transformation(transformation);
self
}
}
impl Styled for Icon {
@ -165,10 +150,11 @@ impl RenderOnce for Icon {
self.base
.text_color(text_color)
.map(|this| match self.size {
IconSize::Size(px) => this.size(px),
IconSize::XSmall => this.size_3(),
IconSize::Small => this.size_3p5(),
IconSize::Medium => this.size_4(),
Size::Size(px) => this.size(px),
Size::XSmall => this.size_3(),
Size::Small => this.size_3p5(),
Size::Medium => this.size_4(),
Size::Large => this.size_6(),
})
.path(self.path)
}
@ -189,10 +175,11 @@ impl Render for Icon {
.size_4()
.text_color(text_color)
.map(|this| match self.size {
IconSize::Size(px) => this.size(px),
IconSize::XSmall => this.size_3(),
IconSize::Small => this.size_3p5(),
IconSize::Medium => this.size_4(),
Size::Size(px) => this.size(px),
Size::XSmall => this.size_3(),
Size::Small => this.size_3p5(),
Size::Medium => this.size_4(),
Size::Large => this.size_6(),
})
.path(self.path.clone())
}

View file

@ -0,0 +1,59 @@
use std::time::Duration;
use crate::{theme::ActiveTheme, Icon, IconName, Size};
use gpui::{
div, ease_in_out, percentage, Animation, AnimationExt as _, Hsla, IntoElement, ParentElement,
RenderOnce, Styled as _, Transformation,
};
#[derive(IntoElement)]
pub struct Indicator {
size: Size,
icon: IconName,
speed: Duration,
color: Option<Hsla>,
}
impl Indicator {
pub fn new() -> Self {
Self {
size: Size::Medium,
speed: Duration::from_secs_f64(0.8),
icon: IconName::Loader,
color: None,
}
}
pub fn size(mut self, size: impl Into<Size>) -> Self {
self.size = size.into();
self
}
pub fn icon(mut self, icon: IconName) -> Self {
self.icon = icon;
self
}
pub fn color(mut self, color: Hsla) -> Self {
self.color = Some(color);
self
}
}
impl RenderOnce for Indicator {
fn render(self, cx: &mut gpui::WindowContext) -> impl IntoElement {
let color = self.color.unwrap_or_else(|| cx.theme().indicator);
div()
.child(
Icon::new(self.icon)
.size(self.size)
.text_color(color)
.with_animation(
"circle",
Animation::new(self.speed).repeat().with_easing(ease_in_out),
|this, delta| this.transform(Transformation::rotate(percentage(delta))),
),
)
.into_element()
}
}

View file

@ -4,6 +4,7 @@ mod disableable;
mod event;
mod focusable;
mod icon;
mod scroll;
mod scrollbar;
mod selectable;
@ -15,6 +16,7 @@ pub mod button;
pub mod checkbox;
pub mod divider;
pub mod dropdown;
pub mod indicator;
pub mod input;
pub mod label;
pub mod list;
@ -35,7 +37,7 @@ pub use disableable::Disableable;
pub use event::InterativeElementExt;
pub use focusable::FocusableCycle;
pub use selectable::{Selectable, Selection};
pub use styled_ext::StyledExt;
pub use styled_ext::{Size, StyledExt};
pub use colors::*;
pub use icon::*;

View file

@ -47,7 +47,7 @@ impl RenderOnce for Progress {
})
.h_full()
.w(relative_w)
.bg(cx.theme().primary),
.bg(cx.theme().progress_bar),
)
.child(
div()
@ -57,7 +57,7 @@ impl RenderOnce for Progress {
})
.h_full()
.flex_1()
.bg(cx.theme().primary.opacity(0.2)),
.bg(cx.theme().progress_bar.opacity(0.2)),
)
}
}

View file

@ -1,5 +1,5 @@
use crate::theme::ActiveTheme;
use gpui::{hsla, point, px, BoxShadow, FocusHandle, Styled, WindowContext};
use gpui::{hsla, point, px, BoxShadow, FocusHandle, Pixels, Styled, WindowContext};
use smallvec::{smallvec, SmallVec};
pub enum ElevationIndex {
@ -133,3 +133,19 @@ pub trait StyledExt: Styled + Sized {
}
impl<E: Styled> StyledExt for E {}
/// A size for elements.
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum Size {
Size(Pixels),
XSmall,
Small,
Medium,
Large,
}
impl From<Pixels> for Size {
fn from(size: Pixels) -> Self {
Size::Size(size)
}
}

View file

@ -1,8 +1,7 @@
use crate::{
button::ButtonSize,
stack::h_flex,
theme::{ActiveTheme, Colorize},
Disableable,
Disableable, Size,
};
use gpui::{
div, prelude::FluentBuilder as _, Div, InteractiveElement, IntoElement, ParentElement as _,
@ -31,7 +30,7 @@ pub struct Switch {
label: Option<SharedString>,
label_side: LabelSide,
on_click: Option<OnClick>,
size: ButtonSize,
size: Size,
}
impl Switch {
@ -46,7 +45,7 @@ impl Switch {
label: None,
on_click: None,
label_side: LabelSide::Right,
size: ButtonSize::Medium,
size: Size::Medium,
}
}
@ -60,7 +59,8 @@ impl Switch {
self
}
pub fn size(mut self, size: ButtonSize) -> Self {
/// Only supported XSmall, Small, Medium
pub fn size(mut self, size: Size) -> Self {
self.size = size;
self
}
@ -107,8 +107,8 @@ impl RenderOnce for Switch {
.child(
self.base
.map(|this| match self.size {
ButtonSize::Medium => this.w_11().h_6().rounded_xl(),
ButtonSize::XSmall | ButtonSize::Small => this.w_8().h_4().rounded_lg(),
Size::XSmall | Size::Small => this.w_8().h_4().rounded_lg(),
_ => this.w_11().h_6().rounded_xl(),
})
.flex()
.items_center()
@ -125,15 +125,15 @@ impl RenderOnce for Switch {
.rounded_full()
.bg(toggle_bg)
.map(|this| match self.size {
ButtonSize::Medium => this.w_5().h_5(),
ButtonSize::XSmall | ButtonSize::Small => this.w_3().h_3(),
Size::XSmall | Size::Small => this.w_3().h_3(),
_ => this.w_5().h_5(),
}),
),
)
.when_some(self.label, |this, label| {
this.child(div().child(label).map(|this| match self.size {
ButtonSize::Medium => this.text_base(),
ButtonSize::XSmall | ButtonSize::Small => this.text_sm(),
Size::XSmall | Size::Small => this.text_sm(),
_ => this.text_base(),
}))
})
.when_some(

View file

@ -301,6 +301,8 @@ pub struct Theme {
pub tab_active: Hsla,
pub tab_foreground: Hsla,
pub tab_active_foreground: Hsla,
pub indicator: Hsla,
pub progress_bar: Hsla,
}
impl Global for Theme {}
@ -355,6 +357,8 @@ impl From<Colors> for Theme {
tab_active: colors.background,
tab_foreground: colors.foreground,
tab_active_foreground: colors.foreground,
indicator: colors.secondary_foreground,
progress_bar: colors.primary,
}
}
}