chore: Update controls detail. (#352)

- Better icon style in Input, Dropdown, DatePicker.
This commit is contained in:
Jason Lee 2024-10-16 17:51:39 +08:00 committed by GitHub
parent 000c0bec57
commit 41c64e2912
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
25 changed files with 73 additions and 85 deletions

View file

@ -143,6 +143,7 @@ impl ButtonStyle {
} }
} }
/// A Button element.
#[derive(IntoElement)] #[derive(IntoElement)]
pub struct Button { pub struct Button {
pub base: Div, pub base: Div,

View file

@ -9,6 +9,7 @@ use crate::{
Disableable, Sizable, Size, Disableable, Sizable, Size,
}; };
/// A ButtonGroup element, to wrap multiple buttons in a group.
#[derive(IntoElement)] #[derive(IntoElement)]
pub struct ButtonGroup { pub struct ButtonGroup {
pub base: Div, pub base: Div,

View file

@ -1,21 +1,17 @@
use crate::{h_flex, theme::ActiveTheme, v_flex, Disableable, IconName, Selectable};
use gpui::{ use gpui::{
div, prelude::FluentBuilder as _, relative, svg, ElementId, InteractiveElement, IntoElement, div, prelude::FluentBuilder as _, relative, svg, ElementId, InteractiveElement, IntoElement,
ParentElement, RenderOnce, SharedString, StatefulInteractiveElement as _, Styled as _, ParentElement, RenderOnce, SharedString, StatefulInteractiveElement as _, Styled as _,
WindowContext, WindowContext,
}; };
use crate::{ /// A Checkbox element.
h_flex,
theme::{ActiveTheme, Colorize as _},
v_flex, Disableable, IconName, Selectable,
};
#[derive(IntoElement)] #[derive(IntoElement)]
pub struct Checkbox { pub struct Checkbox {
id: ElementId, id: ElementId,
label: Option<SharedString>,
checked: bool, checked: bool,
disabled: bool, disabled: bool,
label: Option<SharedString>,
on_click: Option<Box<dyn Fn(&bool, &mut WindowContext) + 'static>>, on_click: Option<Box<dyn Fn(&bool, &mut WindowContext) + 'static>>,
} }
@ -23,9 +19,9 @@ impl Checkbox {
pub fn new(id: impl Into<ElementId>) -> Self { pub fn new(id: impl Into<ElementId>) -> Self {
Self { Self {
id: id.into(), id: id.into(),
label: None,
checked: false, checked: false,
disabled: false, disabled: false,
label: None,
on_click: None, on_click: None,
} }
} }
@ -65,22 +61,17 @@ impl Selectable for Checkbox {
impl RenderOnce for Checkbox { impl RenderOnce for Checkbox {
fn render(self, cx: &mut WindowContext) -> impl IntoElement { fn render(self, cx: &mut WindowContext) -> impl IntoElement {
let theme = cx.theme();
let group_id = format!("checkbox_group_{:?}", self.id);
let (color, icon_color) = if self.disabled { let (color, icon_color) = if self.disabled {
( (
theme.primary.opacity(0.5), cx.theme().primary.opacity(0.5),
theme.primary_foreground.opacity(0.5), cx.theme().primary_foreground.opacity(0.5),
) )
} else { } else {
(theme.primary, theme.primary_foreground) (cx.theme().primary, cx.theme().primary_foreground)
}; };
h_flex() h_flex()
.id(self.id) .id(self.id)
.group(group_id.clone())
.gap_2() .gap_2()
.items_center() .items_center()
.line_height(relative(1.)) .line_height(relative(1.))
@ -93,16 +84,9 @@ impl RenderOnce for Checkbox {
.size_4() .size_4()
.flex_shrink_0() .flex_shrink_0()
.map(|this| match self.checked { .map(|this| match self.checked {
false => this.bg(theme.transparent), false => this.bg(cx.theme().transparent),
_ => this.bg(color), _ => this.bg(color),
}) })
.group_hover(group_id, |this| {
if self.disabled {
return this;
}
this.border_color(theme.primary.divide(0.9))
})
.child( .child(
svg() svg()
.absolute() .absolute()
@ -140,7 +124,6 @@ impl RenderOnce for Checkbox {
this.on_click(move |_, cx| { this.on_click(move |_, cx| {
let checked = !self.checked; let checked = !self.checked;
on_click(&checked, cx); on_click(&checked, cx);
cx.refresh()
}) })
}, },
) )

View file

@ -23,6 +23,7 @@ pub trait ContextMenuExt: ParentElement + Sized {
impl<E> ContextMenuExt for Stateful<E> where E: ParentElement {} impl<E> ContextMenuExt for Stateful<E> where E: ParentElement {}
impl<E> ContextMenuExt for Focusable<E> where E: ParentElement {} impl<E> ContextMenuExt for Focusable<E> where E: ParentElement {}
/// A context menu that can be shown on right-click.
pub struct ContextMenu { pub struct ContextMenu {
id: ElementId, id: ElementId,
menu: Option<Box<dyn Fn(PopupMenu, &mut ViewContext<PopupMenu>) -> PopupMenu + 'static>>, menu: Option<Box<dyn Fn(PopupMenu, &mut ViewContext<PopupMenu>) -> PopupMenu + 'static>>,

View file

@ -5,6 +5,7 @@ use gpui::{
use crate::theme::ActiveTheme; use crate::theme::ActiveTheme;
/// A divider that can be either vertical or horizontal.
#[derive(IntoElement)] #[derive(IntoElement)]
pub struct Divider { pub struct Divider {
base: Div, base: Div,

View file

@ -212,6 +212,7 @@ pub enum DropdownEvent<D: DropdownDelegate + 'static> {
Confirm(Option<<D::Item as DropdownItem>::Value>), Confirm(Option<<D::Item as DropdownItem>::Value>),
} }
/// A Dropdown element.
pub struct Dropdown<D: DropdownDelegate + 'static> { pub struct Dropdown<D: DropdownDelegate + 'static> {
id: ElementId, id: ElementId,
focus_handle: FocusHandle, focus_handle: FocusHandle,
@ -659,9 +660,10 @@ where
this.child( this.child(
Icon::new(icon) Icon::new(icon)
.xsmall()
.text_color(match self.disabled { .text_color(match self.disabled {
true => cx.theme().muted_foreground, true => cx.theme().muted_foreground.opacity(0.5),
false => cx.theme().accent_foreground, false => cx.theme().muted_foreground,
}) })
.when(self.disabled, |this| this.cursor_not_allowed()), .when(self.disabled, |this| this.cursor_not_allowed()),
) )

View file

@ -19,10 +19,3 @@ pub trait InteractiveElementExt: InteractiveElement {
} }
impl<E: InteractiveElement> InteractiveElementExt for Focusable<E> {} impl<E: InteractiveElement> InteractiveElementExt for Focusable<E> {}
// impl<E> InteractiveElementExt for Stateful<E>
// where
// E: Element,
// Self: InteractiveElement,
// {
// }

View file

@ -8,6 +8,11 @@ pub trait HistoryItem: Clone {
fn set_version(&mut self, version: usize); fn set_version(&mut self, version: usize);
} }
/// The History is used to keep track of changes to a model and to allow undo and redo operations.
///
/// This is now used in Input for undo/redo operations. You can also use this in
/// your own models to keep track of changes, for example to track the tab
/// history for prev/next features.
#[derive(Debug)] #[derive(Debug)]
pub struct History<I: HistoryItem> { pub struct History<I: HistoryItem> {
undos: Vec<I>, undos: Vec<I>,

View file

@ -1,17 +1,18 @@
use gpui::{px, WindowContext}; use gpui::{Styled, WindowContext};
use crate::{ use crate::{
button::{Button, ButtonStyled as _}, button::{Button, ButtonStyled as _},
IconName, Sizable as _, theme::ActiveTheme as _,
Icon, IconName, Sizable as _,
}; };
pub(crate) struct ClearButton {} pub(crate) struct ClearButton {}
impl ClearButton { impl ClearButton {
pub fn new(_: &mut WindowContext) -> Button { pub fn new(cx: &mut WindowContext) -> Button {
Button::new("clean") Button::new("clean")
.icon(IconName::CircleX) .icon(Icon::new(IconName::CircleX).text_color(cx.theme().muted_foreground))
.ghost() .ghost()
.with_size(px(14.)) .xsmall()
} }
} }

View file

@ -1152,7 +1152,9 @@ impl Render for TextInput {
input: cx.view().clone(), input: cx.view().clone(),
}), }),
) )
.when(self.loading, |this| this.child(Indicator::new())) .when(self.loading, |this| {
this.child(Indicator::new().color(cx.theme().muted_foreground))
})
.when( .when(
self.cleanable && !self.loading && !self.text.is_empty(), self.cleanable && !self.loading && !self.text.is_empty(),
|this| this.child(ClearButton::new(cx).on_click(cx.listener(Self::clean))), |this| this.child(ClearButton::new(cx).on_click(cx.listener(Self::clean))),

View file

@ -13,6 +13,14 @@ pub enum InputOptEvent {
Change(SharedString), Change(SharedString),
} }
/// A One Time Password (OTP) input element.
///
/// This can accept a fixed length number and can be masked.
///
/// Use case example:
///
/// - SMS OTP
/// - Authenticator OTP
pub struct OtpInput { pub struct OtpInput {
focus_handle: FocusHandle, focus_handle: FocusHandle,
length: usize, length: usize,

View file

@ -5,6 +5,8 @@ use gpui::{
use crate::{h_flex, theme::ActiveTheme}; use crate::{h_flex, theme::ActiveTheme};
const MASKED: &'static str = "";
#[derive(Default, PartialEq, Eq)] #[derive(Default, PartialEq, Eq)]
pub enum TextAlign { pub enum TextAlign {
#[default] #[default]
@ -63,8 +65,6 @@ impl Styled for Label {
} }
} }
const MASKED: &'static str = "";
impl RenderOnce for Label { impl RenderOnce for Label {
fn render(self, cx: &mut WindowContext) -> impl IntoElement { fn render(self, cx: &mut WindowContext) -> impl IntoElement {
let text = self.label; let text = self.label;

View file

@ -58,7 +58,14 @@ pub use colors::*;
pub use icon::*; pub use icon::*;
pub use svg_img::*; pub use svg_img::*;
use std::ops::Deref;
rust_i18n::i18n!("locales", fallback = "en");
/// Initialize the UI module. /// Initialize the UI module.
///
/// This must be called before using any of the UI components.
/// You can initialize the UI module at your application's entry point.
pub fn init(cx: &mut gpui::AppContext) { pub fn init(cx: &mut gpui::AppContext) {
theme::init(cx); theme::init(cx);
context_menu::init(cx); context_menu::init(cx);
@ -75,8 +82,6 @@ pub fn init(cx: &mut gpui::AppContext) {
webview::init(cx); webview::init(cx);
} }
rust_i18n::i18n!("locales", fallback = "en");
use std::ops::Deref;
pub fn locale() -> impl Deref<Target = str> { pub fn locale() -> impl Deref<Target = str> {
rust_i18n::locale() rust_i18n::locale()
} }

View file

@ -5,6 +5,7 @@ use gpui::{
use crate::theme::ActiveTheme as _; use crate::theme::ActiveTheme as _;
/// A Link element like a `<a>` tag in HTML.
#[derive(IntoElement)] #[derive(IntoElement)]
pub struct Link { pub struct Link {
base: Stateful<Div>, base: Stateful<Div>,

View file

@ -1,6 +1,7 @@
use std::time::Duration; use std::time::Duration;
use std::{cell::Cell, rc::Rc}; use std::{cell::Cell, rc::Rc};
use crate::Icon;
use crate::{ use crate::{
input::{InputEvent, TextInput}, input::{InputEvent, TextInput},
scroll::{Scrollbar, ScrollbarState}, scroll::{Scrollbar, ScrollbarState},
@ -102,7 +103,7 @@ where
let query_input = cx.new_view(|cx| { let query_input = cx.new_view(|cx| {
TextInput::new(cx) TextInput::new(cx)
.appearance(false) .appearance(false)
.prefix(|_| IconName::Search) .prefix(|cx| Icon::new(IconName::Search).text_color(cx.theme().muted_foreground))
.placeholder("Search...") .placeholder("Search...")
.cleanable() .cleanable()
}); });

View file

@ -1,12 +1,11 @@
use crate::{h_flex, theme::ActiveTheme, Disableable, Icon, IconName, Selectable, Sizable as _};
use gpui::{ use gpui::{
div, prelude::FluentBuilder as _, AnyElement, ClickEvent, Div, ElementId, InteractiveElement, div, prelude::FluentBuilder as _, AnyElement, ClickEvent, Div, ElementId, InteractiveElement,
IntoElement, MouseButton, MouseMoveEvent, ParentElement, RenderOnce, SharedString, Stateful, IntoElement, MouseButton, MouseMoveEvent, ParentElement, RenderOnce, Stateful,
StatefulInteractiveElement as _, Styled, WindowContext, StatefulInteractiveElement as _, Styled, WindowContext,
}; };
use smallvec::SmallVec; use smallvec::SmallVec;
use crate::{h_flex, theme::ActiveTheme, Disableable, Icon, IconName, Selectable, Sizable as _};
#[derive(IntoElement)] #[derive(IntoElement)]
pub struct ListItem { pub struct ListItem {
id: ElementId, id: ElementId,
@ -15,7 +14,6 @@ pub struct ListItem {
selected: bool, selected: bool,
confirmed: bool, confirmed: bool,
check_icon: Option<Icon>, check_icon: Option<Icon>,
group_id: Option<SharedString>,
on_click: Option<Box<dyn Fn(&ClickEvent, &mut WindowContext) + 'static>>, on_click: Option<Box<dyn Fn(&ClickEvent, &mut WindowContext) + 'static>>,
on_mouse_enter: Option<Box<dyn Fn(&MouseMoveEvent, &mut WindowContext) + 'static>>, on_mouse_enter: Option<Box<dyn Fn(&MouseMoveEvent, &mut WindowContext) + 'static>>,
suffix: Option<Box<dyn Fn(&mut WindowContext) -> AnyElement + 'static>>, suffix: Option<Box<dyn Fn(&mut WindowContext) -> AnyElement + 'static>>,
@ -35,17 +33,10 @@ impl ListItem {
on_mouse_enter: None, on_mouse_enter: None,
check_icon: None, check_icon: None,
suffix: None, suffix: None,
group_id: None,
children: SmallVec::new(), children: SmallVec::new(),
} }
} }
/// Set group_id
pub fn group(mut self, group_id: impl Into<SharedString>) -> Self {
self.group_id = Some(group_id.into());
self
}
/// Set to show check icon, default is None. /// Set to show check icon, default is None.
pub fn check_icon(mut self, icon: IconName) -> Self { pub fn check_icon(mut self, icon: IconName) -> Self {
self.check_icon = Some(Icon::new(icon)); self.check_icon = Some(Icon::new(icon));
@ -128,7 +119,6 @@ impl RenderOnce for ListItem {
let is_active = self.selected || self.confirmed; let is_active = self.selected || self.confirmed;
self.base self.base
.when_some(self.group_id, |this, group_id| this.group(group_id))
.text_color(cx.theme().foreground) .text_color(cx.theme().foreground)
.relative() .relative()
.items_center() .items_center()

View file

@ -40,6 +40,7 @@ impl From<(TypeId, ElementId)> for NotificationId {
} }
} }
/// A notification element.
pub struct Notification { pub struct Notification {
/// The id is used make the notification unique. /// The id is used make the notification unique.
/// Then you push a notification with the same id, the previous notification will be replaced. /// Then you push a notification with the same id, the previous notification will be replaced.

View file

@ -522,10 +522,7 @@ impl Render for PopupMenu {
!(*ix == items_count - 1 && item.is_separator()) !(*ix == items_count - 1 && item.is_separator())
}) })
.map(|(ix, item)| { .map(|(ix, item)| {
let group_id = format!("item:{}", ix);
let this = ListItem::new(("menu-item", ix)) let this = ListItem::new(("menu-item", ix))
.group(group_id.clone())
.relative() .relative()
.text_sm() .text_sm()
.py_0() .py_0()

View file

@ -1,10 +1,10 @@
use crate::theme::ActiveTheme;
use gpui::{ use gpui::{
div, prelude::FluentBuilder, px, relative, IntoElement, ParentElement, RenderOnce, Styled, div, prelude::FluentBuilder, px, relative, IntoElement, ParentElement, RenderOnce, Styled,
WindowContext, WindowContext,
}; };
use crate::theme::ActiveTheme; /// A Progress bar element.
#[derive(IntoElement)] #[derive(IntoElement)]
pub struct Progress { pub struct Progress {
value: f32, value: f32,

View file

@ -1,11 +1,12 @@
use crate::{h_flex, theme::ActiveTheme, IconName};
use gpui::{ use gpui::{
div, prelude::FluentBuilder, relative, svg, CursorStyle, ElementId, InteractiveElement, div, prelude::FluentBuilder, relative, svg, ElementId, InteractiveElement, IntoElement,
IntoElement, ParentElement, RenderOnce, SharedString, StatefulInteractiveElement, Styled, ParentElement, RenderOnce, SharedString, StatefulInteractiveElement, Styled, WindowContext,
WindowContext,
}; };
use crate::{h_flex, theme::ActiveTheme, IconName}; /// A Radio element.
///
/// This is not included the Radio group implementation, you can manage the group by yourself.
#[derive(IntoElement)] #[derive(IntoElement)]
pub struct Radio { pub struct Radio {
id: ElementId, id: ElementId,
@ -58,7 +59,6 @@ impl RenderOnce for Radio {
h_flex() h_flex()
.id(self.id) .id(self.id)
.gap_x_2() .gap_x_2()
.cursor(CursorStyle::PointingHand)
.text_color(cx.theme().foreground) .text_color(cx.theme().foreground)
.items_center() .items_center()
.line_height(relative(1.)) .line_height(relative(1.))

View file

@ -1,3 +1,9 @@
use crate::{
drawer::Drawer,
modal::Modal,
notification::{Notification, NotificationList},
theme::ActiveTheme,
};
use gpui::{ use gpui::{
div, AnyView, FocusHandle, InteractiveElement, IntoElement, ParentElement as _, Render, Styled, div, AnyView, FocusHandle, InteractiveElement, IntoElement, ParentElement as _, Render, Styled,
View, ViewContext, VisualContext as _, WindowContext, View, ViewContext, VisualContext as _, WindowContext,
@ -7,13 +13,6 @@ use std::{
rc::Rc, rc::Rc,
}; };
use crate::{
drawer::Drawer,
modal::Modal,
notification::{Notification, NotificationList},
theme::ActiveTheme,
};
/// Extension trait for [`WindowContext`] and [`ViewContext`] to add drawer functionality. /// Extension trait for [`WindowContext`] and [`ViewContext`] to add drawer functionality.
pub trait ContextModal: Sized { pub trait ContextModal: Sized {
/// Opens a Drawer. /// Opens a Drawer.

View file

@ -1,11 +1,9 @@
use std::time::Duration; use crate::theme::ActiveTheme;
use gpui::{ use gpui::{
bounce, div, ease_in_out, Animation, AnimationExt, Div, IntoElement, ParentElement as _, bounce, div, ease_in_out, Animation, AnimationExt, Div, IntoElement, ParentElement as _,
RenderOnce, Styled, RenderOnce, Styled,
}; };
use std::time::Duration;
use crate::theme::ActiveTheme;
#[derive(IntoElement)] #[derive(IntoElement)]
pub struct Skeleton { pub struct Skeleton {

View file

@ -13,7 +13,7 @@ pub enum SliderEvent {
Change(f32), Change(f32),
} }
/// A slider component. /// A Slider element.
pub struct Slider { pub struct Slider {
axis: Axis, axis: Axis,
min: f32, min: f32,

View file

@ -1,13 +1,10 @@
use std::{cell::RefCell, rc::Rc, time::Duration};
use crate::{h_flex, theme::ActiveTheme, Disableable, Sizable, Size}; use crate::{h_flex, theme::ActiveTheme, Disableable, Sizable, Size};
use gpui::{ use gpui::{
div, prelude::FluentBuilder as _, px, Animation, AnimationExt as _, AnyElement, Element, div, prelude::FluentBuilder as _, px, Animation, AnimationExt as _, AnyElement, Element,
ElementId, GlobalElementId, InteractiveElement, IntoElement, LayoutId, ParentElement as _, ElementId, GlobalElementId, InteractiveElement, IntoElement, LayoutId, ParentElement as _,
SharedString, Styled as _, WindowContext, SharedString, Styled as _, WindowContext,
}; };
use std::{cell::RefCell, rc::Rc, time::Duration};
type OnClick = Rc<dyn Fn(&bool, &mut WindowContext)>;
pub enum LabelSide { pub enum LabelSide {
Left, Left,
@ -26,7 +23,7 @@ pub struct Switch {
disabled: bool, disabled: bool,
label: Option<SharedString>, label: Option<SharedString>,
label_side: LabelSide, label_side: LabelSide,
on_click: Option<OnClick>, on_click: Option<Rc<dyn Fn(&bool, &mut WindowContext)>>,
size: Size, size: Size,
} }

View file

@ -238,6 +238,7 @@ impl Render for DatePicker {
.when(!show_clean, |this| { .when(!show_clean, |this| {
this.child( this.child(
Icon::new(IconName::Calendar) Icon::new(IconName::Calendar)
.xsmall()
.text_color(cx.theme().muted_foreground), .text_color(cx.theme().muted_foreground),
) )
}), }),