diff --git a/Cargo.toml b/Cargo.toml index f546cd30b8f88..696f0b1d5ef2a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -578,7 +578,7 @@ new_ret_no_self = { level = "allow" } # We have a few `next` functions that differ in lifetimes # compared to Iterator::next. Yet, clippy complains about those. should_implement_trait = { level = "allow" } - +module_inception = { level = "deny" } # Individual rules that have violations in the codebase: type_complexity = "allow" diff --git a/clippy.toml b/clippy.toml new file mode 100644 index 0000000000000..787620d865cc5 --- /dev/null +++ b/clippy.toml @@ -0,0 +1 @@ +allow-private-module-inception = true diff --git a/crates/ui/src/components/avatar.rs b/crates/ui/src/components/avatar.rs index 961a57087eb35..6c2d88916e7fe 100644 --- a/crates/ui/src/components/avatar.rs +++ b/crates/ui/src/components/avatar.rs @@ -1,133 +1,7 @@ +mod avatar; mod avatar_audio_status_indicator; mod avatar_availability_indicator; +pub use avatar::*; pub use avatar_audio_status_indicator::*; pub use avatar_availability_indicator::*; - -use crate::prelude::*; - -use gpui::{img, AnyElement, Hsla, ImageSource, Img, IntoElement, Styled}; - -/// The shape of an [`Avatar`]. -#[derive(Debug, Default, PartialEq, Clone)] -pub enum AvatarShape { - /// The avatar is shown in a circle. - #[default] - Circle, - /// The avatar is shown in a rectangle with rounded corners. - RoundedRectangle, -} - -/// An element that renders a user avatar with customizable appearance options. -/// -/// # Examples -/// -/// ``` -/// use ui::{Avatar, AvatarShape}; -/// -/// Avatar::new("path/to/image.png") -/// .shape(AvatarShape::Circle) -/// .grayscale(true) -/// .border_color(gpui::red()); -/// ``` -#[derive(IntoElement)] -pub struct Avatar { - image: Img, - size: Option, - border_color: Option, - indicator: Option, -} - -impl Avatar { - pub fn new(src: impl Into) -> Self { - Avatar { - image: img(src), - size: None, - border_color: None, - indicator: None, - } - } - - /// Sets the shape of the avatar image. - /// - /// This method allows the shape of the avatar to be specified using an [`AvatarShape`]. - /// It modifies the corner radius of the image to match the specified shape. - /// - /// # Examples - /// - /// ``` - /// use ui::{Avatar, AvatarShape}; - /// - /// Avatar::new("path/to/image.png").shape(AvatarShape::Circle); - /// ``` - pub fn shape(mut self, shape: AvatarShape) -> Self { - self.image = match shape { - AvatarShape::Circle => self.image.rounded_full(), - AvatarShape::RoundedRectangle => self.image.rounded_md(), - }; - self - } - - /// Applies a grayscale filter to the avatar image. - /// - /// # Examples - /// - /// ``` - /// use ui::{Avatar, AvatarShape}; - /// - /// let avatar = Avatar::new("path/to/image.png").grayscale(true); - /// ``` - pub fn grayscale(mut self, grayscale: bool) -> Self { - self.image = self.image.grayscale(grayscale); - self - } - - pub fn border_color(mut self, color: impl Into) -> Self { - self.border_color = Some(color.into()); - self - } - - /// Size overrides the avatar size. By default they are 1rem. - pub fn size>(mut self, size: impl Into>) -> Self { - self.size = size.into().map(Into::into); - self - } - - pub fn indicator(mut self, indicator: impl Into>) -> Self { - self.indicator = indicator.into().map(IntoElement::into_any_element); - self - } -} - -impl RenderOnce for Avatar { - fn render(mut self, cx: &mut WindowContext) -> impl IntoElement { - if self.image.style().corner_radii.top_left.is_none() { - self = self.shape(AvatarShape::Circle); - } - - let border_width = if self.border_color.is_some() { - px(2.) - } else { - px(0.) - }; - - let image_size = self.size.unwrap_or_else(|| rems(1.).into()); - let container_size = image_size.to_pixels(cx.rem_size()) + border_width * 2.; - - div() - .size(container_size) - .map(|mut div| { - div.style().corner_radii = self.image.style().corner_radii.clone(); - div - }) - .when_some(self.border_color, |this, color| { - this.border(border_width).border_color(color) - }) - .child( - self.image - .size(image_size) - .bg(cx.theme().colors().ghost_element_background), - ) - .children(self.indicator.map(|indicator| div().child(indicator))) - } -} diff --git a/crates/ui/src/components/avatar/avatar.rs b/crates/ui/src/components/avatar/avatar.rs new file mode 100644 index 0000000000000..27cf86a01fb01 --- /dev/null +++ b/crates/ui/src/components/avatar/avatar.rs @@ -0,0 +1,127 @@ +use crate::prelude::*; + +use gpui::{img, AnyElement, Hsla, ImageSource, Img, IntoElement, Styled}; + +/// The shape of an [`Avatar`]. +#[derive(Debug, Default, PartialEq, Clone)] +pub enum AvatarShape { + /// The avatar is shown in a circle. + #[default] + Circle, + /// The avatar is shown in a rectangle with rounded corners. + RoundedRectangle, +} + +/// An element that renders a user avatar with customizable appearance options. +/// +/// # Examples +/// +/// ``` +/// use ui::{Avatar, AvatarShape}; +/// +/// Avatar::new("path/to/image.png") +/// .shape(AvatarShape::Circle) +/// .grayscale(true) +/// .border_color(gpui::red()); +/// ``` +#[derive(IntoElement)] +pub struct Avatar { + image: Img, + size: Option, + border_color: Option, + indicator: Option, +} + +impl Avatar { + pub fn new(src: impl Into) -> Self { + Avatar { + image: img(src), + size: None, + border_color: None, + indicator: None, + } + } + + /// Sets the shape of the avatar image. + /// + /// This method allows the shape of the avatar to be specified using an [`AvatarShape`]. + /// It modifies the corner radius of the image to match the specified shape. + /// + /// # Examples + /// + /// ``` + /// use ui::{Avatar, AvatarShape}; + /// + /// Avatar::new("path/to/image.png").shape(AvatarShape::Circle); + /// ``` + pub fn shape(mut self, shape: AvatarShape) -> Self { + self.image = match shape { + AvatarShape::Circle => self.image.rounded_full(), + AvatarShape::RoundedRectangle => self.image.rounded_md(), + }; + self + } + + /// Applies a grayscale filter to the avatar image. + /// + /// # Examples + /// + /// ``` + /// use ui::{Avatar, AvatarShape}; + /// + /// let avatar = Avatar::new("path/to/image.png").grayscale(true); + /// ``` + pub fn grayscale(mut self, grayscale: bool) -> Self { + self.image = self.image.grayscale(grayscale); + self + } + + pub fn border_color(mut self, color: impl Into) -> Self { + self.border_color = Some(color.into()); + self + } + + /// Size overrides the avatar size. By default they are 1rem. + pub fn size>(mut self, size: impl Into>) -> Self { + self.size = size.into().map(Into::into); + self + } + + pub fn indicator(mut self, indicator: impl Into>) -> Self { + self.indicator = indicator.into().map(IntoElement::into_any_element); + self + } +} + +impl RenderOnce for Avatar { + fn render(mut self, cx: &mut WindowContext) -> impl IntoElement { + if self.image.style().corner_radii.top_left.is_none() { + self = self.shape(AvatarShape::Circle); + } + + let border_width = if self.border_color.is_some() { + px(2.) + } else { + px(0.) + }; + + let image_size = self.size.unwrap_or_else(|| rems(1.).into()); + let container_size = image_size.to_pixels(cx.rem_size()) + border_width * 2.; + + div() + .size(container_size) + .map(|mut div| { + div.style().corner_radii = self.image.style().corner_radii.clone(); + div + }) + .when_some(self.border_color, |this, color| { + this.border(border_width).border_color(color) + }) + .child( + self.image + .size(image_size) + .bg(cx.theme().colors().ghost_element_background), + ) + .children(self.indicator.map(|indicator| div().child(indicator))) + } +} diff --git a/crates/ui/src/components/button.rs b/crates/ui/src/components/button.rs index 9de1f041b4243..71aaf7780ccc9 100644 --- a/crates/ui/src/components/button.rs +++ b/crates/ui/src/components/button.rs @@ -1,445 +1,10 @@ -mod button_icon; +mod button; +pub(self) mod button_icon; mod button_like; mod icon_button; mod toggle_button; +pub use button::*; pub use button_like::*; pub use icon_button::*; pub use toggle_button::*; - -use gpui::{AnyView, DefiniteLength}; - -use crate::{prelude::*, ElevationIndex, KeyBinding, Spacing}; -use crate::{IconName, IconSize, Label, LineHeightStyle}; - -use button_icon::ButtonIcon; - -/// An element that creates a button with a label and an optional icon. -/// -/// Common buttons: -/// - Label, Icon + Label: [`Button`] (this component) -/// - Icon only: [`IconButton`] -/// - Custom: [`ButtonLike`] -/// -/// To create a more complex button than what the [`Button`] or [`IconButton`] components provide, use -/// [`ButtonLike`] directly. -/// -/// # Examples -/// -/// **A button with a label**, is typically used in scenarios such as a form, where the button's label -/// indicates what action will be performed when the button is clicked. -/// -/// ``` -/// use ui::prelude::*; -/// -/// Button::new("button_id", "Click me!") -/// .on_click(|event, cx| { -/// // Handle click event -/// }); -/// ``` -/// -/// **A toggleable button**, is typically used in scenarios such as a toolbar, -/// where the button's state indicates whether a feature is enabled or not, or -/// a trigger for a popover menu, where clicking the button toggles the visibility of the menu. -/// -/// ``` -/// use ui::prelude::*; -/// -/// Button::new("button_id", "Click me!") -/// .icon(IconName::Check) -/// .selected(true) -/// .on_click(|event, cx| { -/// // Handle click event -/// }); -/// ``` -/// -/// To change the style of the button when it is selected use the [`selected_style`][Button::selected_style] method. -/// -/// ``` -/// use ui::prelude::*; -/// use ui::TintColor; -/// -/// Button::new("button_id", "Click me!") -/// .selected(true) -/// .selected_style(ButtonStyle::Tinted(TintColor::Accent)) -/// .on_click(|event, cx| { -/// // Handle click event -/// }); -/// ``` -/// This will create a button with a blue tinted background when selected. -/// -/// **A full-width button**, is typically used in scenarios such as the bottom of a modal or form, where it occupies the entire width of its container. -/// The button's content, including text and icons, is centered by default. -/// -/// ``` -/// use ui::prelude::*; -/// -/// let button = Button::new("button_id", "Click me!") -/// .full_width() -/// .on_click(|event, cx| { -/// // Handle click event -/// }); -/// ``` -/// -#[derive(IntoElement)] -pub struct Button { - base: ButtonLike, - label: SharedString, - label_color: Option, - label_size: Option, - selected_label: Option, - selected_label_color: Option, - icon: Option, - icon_position: Option, - icon_size: Option, - icon_color: Option, - selected_icon: Option, - selected_icon_color: Option, - key_binding: Option, - alpha: Option, -} - -impl Button { - /// Creates a new [`Button`] with a specified identifier and label. - /// - /// This is the primary constructor for a [`Button`] component. It initializes - /// the button with the provided identifier and label text, setting all other - /// properties to their default values, which can be customized using the - /// builder pattern methods provided by this struct. - pub fn new(id: impl Into, label: impl Into) -> Self { - Self { - base: ButtonLike::new(id), - label: label.into(), - label_color: None, - label_size: None, - selected_label: None, - selected_label_color: None, - icon: None, - icon_position: None, - icon_size: None, - icon_color: None, - selected_icon: None, - selected_icon_color: None, - key_binding: None, - alpha: None, - } - } - - /// Sets the color of the button's label. - pub fn color(mut self, label_color: impl Into>) -> Self { - self.label_color = label_color.into(); - self - } - - /// Defines the size of the button's label. - pub fn label_size(mut self, label_size: impl Into>) -> Self { - self.label_size = label_size.into(); - self - } - - /// Sets the label used when the button is in a selected state. - pub fn selected_label>(mut self, label: impl Into>) -> Self { - self.selected_label = label.into().map(Into::into); - self - } - - /// Sets the label color used when the button is in a selected state. - pub fn selected_label_color(mut self, color: impl Into>) -> Self { - self.selected_label_color = color.into(); - self - } - - /// Assigns an icon to the button. - pub fn icon(mut self, icon: impl Into>) -> Self { - self.icon = icon.into(); - self - } - - /// Sets the position of the icon relative to the label. - pub fn icon_position(mut self, icon_position: impl Into>) -> Self { - self.icon_position = icon_position.into(); - self - } - - /// Specifies the size of the button's icon. - pub fn icon_size(mut self, icon_size: impl Into>) -> Self { - self.icon_size = icon_size.into(); - self - } - - /// Sets the color of the button's icon. - pub fn icon_color(mut self, icon_color: impl Into>) -> Self { - self.icon_color = icon_color.into(); - self - } - - /// Chooses an icon to display when the button is in a selected state. - pub fn selected_icon(mut self, icon: impl Into>) -> Self { - self.selected_icon = icon.into(); - self - } - - /// Sets the icon color used when the button is in a selected state. - pub fn selected_icon_color(mut self, color: impl Into>) -> Self { - self.selected_icon_color = color.into(); - self - } - - /// Binds a key combination to the button for keyboard shortcuts. - pub fn key_binding(mut self, key_binding: impl Into>) -> Self { - self.key_binding = key_binding.into(); - self - } - - /// Sets the alpha property of the color of label. - pub fn alpha(mut self, alpha: f32) -> Self { - self.alpha = Some(alpha); - self - } -} - -impl Selectable for Button { - /// Sets the selected state of the button. - /// - /// This method allows the selection state of the button to be specified. - /// It modifies the button's appearance to reflect its selected state. - /// - /// # Examples - /// - /// ``` - /// use ui::prelude::*; - /// - /// Button::new("button_id", "Click me!") - /// .selected(true) - /// .on_click(|event, cx| { - /// // Handle click event - /// }); - /// ``` - /// - /// Use [`selected_style`](Button::selected_style) to change the style of the button when it is selected. - fn selected(mut self, selected: bool) -> Self { - self.base = self.base.selected(selected); - self - } -} - -impl SelectableButton for Button { - /// Sets the style for the button when selected. - /// - /// # Examples - /// - /// ``` - /// use ui::prelude::*; - /// use ui::TintColor; - /// - /// Button::new("button_id", "Click me!") - /// .selected(true) - /// .selected_style(ButtonStyle::Tinted(TintColor::Accent)) - /// .on_click(|event, cx| { - /// // Handle click event - /// }); - /// ``` - /// This results in a button with a blue tinted background when selected. - fn selected_style(mut self, style: ButtonStyle) -> Self { - self.base = self.base.selected_style(style); - self - } -} - -impl Disableable for Button { - /// Disables the button. - /// - /// This method allows the button to be disabled. When a button is disabled, - /// it doesn't react to user interactions and its appearance is updated to reflect this. - /// - /// # Examples - /// - /// ``` - /// use ui::prelude::*; - /// - /// Button::new("button_id", "Click me!") - /// .disabled(true) - /// .on_click(|event, cx| { - /// // Handle click event - /// }); - /// ``` - /// - /// This results in a button that is disabled and does not respond to click events. - fn disabled(mut self, disabled: bool) -> Self { - self.base = self.base.disabled(disabled); - self - } -} - -impl Clickable for Button { - /// Sets the click event handler for the button. - fn on_click( - mut self, - handler: impl Fn(&gpui::ClickEvent, &mut WindowContext) + 'static, - ) -> Self { - self.base = self.base.on_click(handler); - self - } - - fn cursor_style(mut self, cursor_style: gpui::CursorStyle) -> Self { - self.base = self.base.cursor_style(cursor_style); - self - } -} - -impl FixedWidth for Button { - /// Sets a fixed width for the button. - /// - /// This function allows a button to have a fixed width instead of automatically growing or shrinking. - /// Sets a fixed width for the button. - /// - /// # Examples - /// - /// ``` - /// use ui::prelude::*; - /// - /// Button::new("button_id", "Click me!") - /// .width(px(100.).into()) - /// .on_click(|event, cx| { - /// // Handle click event - /// }); - /// ``` - /// - /// This sets the button's width to be exactly 100 pixels. - fn width(mut self, width: DefiniteLength) -> Self { - self.base = self.base.width(width); - self - } - - /// Sets the button to occupy the full width of its container. - /// - /// # Examples - /// - /// ``` - /// use ui::prelude::*; - /// - /// Button::new("button_id", "Click me!") - /// .full_width() - /// .on_click(|event, cx| { - /// // Handle click event - /// }); - /// ``` - /// - /// This stretches the button to the full width of its container. - fn full_width(mut self) -> Self { - self.base = self.base.full_width(); - self - } -} - -impl ButtonCommon for Button { - /// Sets the button's id. - fn id(&self) -> &ElementId { - self.base.id() - } - - /// Sets the visual style of the button using a [`ButtonStyle`]. - fn style(mut self, style: ButtonStyle) -> Self { - self.base = self.base.style(style); - self - } - - /// Sets the button's size using a [`ButtonSize`]. - fn size(mut self, size: ButtonSize) -> Self { - self.base = self.base.size(size); - self - } - - /// Sets a tooltip for the button. - /// - /// This method allows a tooltip to be set for the button. The tooltip is a function that - /// takes a mutable reference to a [`WindowContext`] and returns an [`AnyView`]. The tooltip - /// is displayed when the user hovers over the button. - /// - /// # Examples - /// - /// ``` - /// use ui::prelude::*; - /// use ui::Tooltip; - /// - /// Button::new("button_id", "Click me!") - /// .tooltip(move |cx| { - /// Tooltip::text("This is a tooltip", cx) - /// }) - /// .on_click(|event, cx| { - /// // Handle click event - /// }); - /// ``` - /// - /// This will create a button with a tooltip that displays "This is a tooltip" when hovered over. - fn tooltip(mut self, tooltip: impl Fn(&mut WindowContext) -> AnyView + 'static) -> Self { - self.base = self.base.tooltip(tooltip); - self - } - - fn layer(mut self, elevation: ElevationIndex) -> Self { - self.base = self.base.layer(elevation); - self - } -} - -impl RenderOnce for Button { - #[allow(refining_impl_trait)] - fn render(self, cx: &mut WindowContext) -> ButtonLike { - let is_disabled = self.base.disabled; - let is_selected = self.base.selected; - - let label = self - .selected_label - .filter(|_| is_selected) - .unwrap_or(self.label); - - let label_color = if is_disabled { - Color::Disabled - } else if is_selected { - self.selected_label_color.unwrap_or(Color::Selected) - } else { - self.label_color.unwrap_or_default() - }; - - self.base.child( - h_flex() - .gap(Spacing::Small.rems(cx)) - .when(self.icon_position == Some(IconPosition::Start), |this| { - this.children(self.icon.map(|icon| { - ButtonIcon::new(icon) - .disabled(is_disabled) - .selected(is_selected) - .selected_icon(self.selected_icon) - .selected_icon_color(self.selected_icon_color) - .size(self.icon_size) - .color(self.icon_color) - })) - }) - .child( - h_flex() - .gap(Spacing::Medium.rems(cx)) - .justify_between() - .child( - Label::new(label) - .color(label_color) - .size(self.label_size.unwrap_or_default()) - .when_some(self.alpha, |this, alpha| this.alpha(alpha)) - .line_height_style(LineHeightStyle::UiLabel), - ) - .children(self.key_binding), - ) - .when(self.icon_position != Some(IconPosition::Start), |this| { - this.children(self.icon.map(|icon| { - ButtonIcon::new(icon) - .disabled(is_disabled) - .selected(is_selected) - .selected_icon(self.selected_icon) - .selected_icon_color(self.selected_icon_color) - .size(self.icon_size) - .color(self.icon_color) - })) - }), - ) - } -} diff --git a/crates/ui/src/components/button/button.rs b/crates/ui/src/components/button/button.rs new file mode 100644 index 0000000000000..1e11d25cc6618 --- /dev/null +++ b/crates/ui/src/components/button/button.rs @@ -0,0 +1,438 @@ +use gpui::{AnyView, DefiniteLength}; + +use crate::{prelude::*, ElevationIndex, IconPosition, KeyBinding, Spacing}; +use crate::{ + ButtonCommon, ButtonLike, ButtonSize, ButtonStyle, IconName, IconSize, Label, LineHeightStyle, +}; + +use super::button_icon::ButtonIcon; + +/// An element that creates a button with a label and an optional icon. +/// +/// Common buttons: +/// - Label, Icon + Label: [`Button`] (this component) +/// - Icon only: [`IconButton`] +/// - Custom: [`ButtonLike`] +/// +/// To create a more complex button than what the [`Button`] or [`IconButton`] components provide, use +/// [`ButtonLike`] directly. +/// +/// # Examples +/// +/// **A button with a label**, is typically used in scenarios such as a form, where the button's label +/// indicates what action will be performed when the button is clicked. +/// +/// ``` +/// use ui::prelude::*; +/// +/// Button::new("button_id", "Click me!") +/// .on_click(|event, cx| { +/// // Handle click event +/// }); +/// ``` +/// +/// **A toggleable button**, is typically used in scenarios such as a toolbar, +/// where the button's state indicates whether a feature is enabled or not, or +/// a trigger for a popover menu, where clicking the button toggles the visibility of the menu. +/// +/// ``` +/// use ui::prelude::*; +/// +/// Button::new("button_id", "Click me!") +/// .icon(IconName::Check) +/// .selected(true) +/// .on_click(|event, cx| { +/// // Handle click event +/// }); +/// ``` +/// +/// To change the style of the button when it is selected use the [`selected_style`][Button::selected_style] method. +/// +/// ``` +/// use ui::prelude::*; +/// use ui::TintColor; +/// +/// Button::new("button_id", "Click me!") +/// .selected(true) +/// .selected_style(ButtonStyle::Tinted(TintColor::Accent)) +/// .on_click(|event, cx| { +/// // Handle click event +/// }); +/// ``` +/// This will create a button with a blue tinted background when selected. +/// +/// **A full-width button**, is typically used in scenarios such as the bottom of a modal or form, where it occupies the entire width of its container. +/// The button's content, including text and icons, is centered by default. +/// +/// ``` +/// use ui::prelude::*; +/// +/// let button = Button::new("button_id", "Click me!") +/// .full_width() +/// .on_click(|event, cx| { +/// // Handle click event +/// }); +/// ``` +/// +#[derive(IntoElement)] +pub struct Button { + base: ButtonLike, + label: SharedString, + label_color: Option, + label_size: Option, + selected_label: Option, + selected_label_color: Option, + icon: Option, + icon_position: Option, + icon_size: Option, + icon_color: Option, + selected_icon: Option, + selected_icon_color: Option, + key_binding: Option, + alpha: Option, +} + +impl Button { + /// Creates a new [`Button`] with a specified identifier and label. + /// + /// This is the primary constructor for a [`Button`] component. It initializes + /// the button with the provided identifier and label text, setting all other + /// properties to their default values, which can be customized using the + /// builder pattern methods provided by this struct. + pub fn new(id: impl Into, label: impl Into) -> Self { + Self { + base: ButtonLike::new(id), + label: label.into(), + label_color: None, + label_size: None, + selected_label: None, + selected_label_color: None, + icon: None, + icon_position: None, + icon_size: None, + icon_color: None, + selected_icon: None, + selected_icon_color: None, + key_binding: None, + alpha: None, + } + } + + /// Sets the color of the button's label. + pub fn color(mut self, label_color: impl Into>) -> Self { + self.label_color = label_color.into(); + self + } + + /// Defines the size of the button's label. + pub fn label_size(mut self, label_size: impl Into>) -> Self { + self.label_size = label_size.into(); + self + } + + /// Sets the label used when the button is in a selected state. + pub fn selected_label>(mut self, label: impl Into>) -> Self { + self.selected_label = label.into().map(Into::into); + self + } + + /// Sets the label color used when the button is in a selected state. + pub fn selected_label_color(mut self, color: impl Into>) -> Self { + self.selected_label_color = color.into(); + self + } + + /// Assigns an icon to the button. + pub fn icon(mut self, icon: impl Into>) -> Self { + self.icon = icon.into(); + self + } + + /// Sets the position of the icon relative to the label. + pub fn icon_position(mut self, icon_position: impl Into>) -> Self { + self.icon_position = icon_position.into(); + self + } + + /// Specifies the size of the button's icon. + pub fn icon_size(mut self, icon_size: impl Into>) -> Self { + self.icon_size = icon_size.into(); + self + } + + /// Sets the color of the button's icon. + pub fn icon_color(mut self, icon_color: impl Into>) -> Self { + self.icon_color = icon_color.into(); + self + } + + /// Chooses an icon to display when the button is in a selected state. + pub fn selected_icon(mut self, icon: impl Into>) -> Self { + self.selected_icon = icon.into(); + self + } + + /// Sets the icon color used when the button is in a selected state. + pub fn selected_icon_color(mut self, color: impl Into>) -> Self { + self.selected_icon_color = color.into(); + self + } + + /// Binds a key combination to the button for keyboard shortcuts. + pub fn key_binding(mut self, key_binding: impl Into>) -> Self { + self.key_binding = key_binding.into(); + self + } + + /// Sets the alpha property of the color of label. + pub fn alpha(mut self, alpha: f32) -> Self { + self.alpha = Some(alpha); + self + } +} + +impl Selectable for Button { + /// Sets the selected state of the button. + /// + /// This method allows the selection state of the button to be specified. + /// It modifies the button's appearance to reflect its selected state. + /// + /// # Examples + /// + /// ``` + /// use ui::prelude::*; + /// + /// Button::new("button_id", "Click me!") + /// .selected(true) + /// .on_click(|event, cx| { + /// // Handle click event + /// }); + /// ``` + /// + /// Use [`selected_style`](Button::selected_style) to change the style of the button when it is selected. + fn selected(mut self, selected: bool) -> Self { + self.base = self.base.selected(selected); + self + } +} + +impl SelectableButton for Button { + /// Sets the style for the button when selected. + /// + /// # Examples + /// + /// ``` + /// use ui::prelude::*; + /// use ui::TintColor; + /// + /// Button::new("button_id", "Click me!") + /// .selected(true) + /// .selected_style(ButtonStyle::Tinted(TintColor::Accent)) + /// .on_click(|event, cx| { + /// // Handle click event + /// }); + /// ``` + /// This results in a button with a blue tinted background when selected. + fn selected_style(mut self, style: ButtonStyle) -> Self { + self.base = self.base.selected_style(style); + self + } +} + +impl Disableable for Button { + /// Disables the button. + /// + /// This method allows the button to be disabled. When a button is disabled, + /// it doesn't react to user interactions and its appearance is updated to reflect this. + /// + /// # Examples + /// + /// ``` + /// use ui::prelude::*; + /// + /// Button::new("button_id", "Click me!") + /// .disabled(true) + /// .on_click(|event, cx| { + /// // Handle click event + /// }); + /// ``` + /// + /// This results in a button that is disabled and does not respond to click events. + fn disabled(mut self, disabled: bool) -> Self { + self.base = self.base.disabled(disabled); + self + } +} + +impl Clickable for Button { + /// Sets the click event handler for the button. + fn on_click( + mut self, + handler: impl Fn(&gpui::ClickEvent, &mut WindowContext) + 'static, + ) -> Self { + self.base = self.base.on_click(handler); + self + } + + fn cursor_style(mut self, cursor_style: gpui::CursorStyle) -> Self { + self.base = self.base.cursor_style(cursor_style); + self + } +} + +impl FixedWidth for Button { + /// Sets a fixed width for the button. + /// + /// This function allows a button to have a fixed width instead of automatically growing or shrinking. + /// Sets a fixed width for the button. + /// + /// # Examples + /// + /// ``` + /// use ui::prelude::*; + /// + /// Button::new("button_id", "Click me!") + /// .width(px(100.).into()) + /// .on_click(|event, cx| { + /// // Handle click event + /// }); + /// ``` + /// + /// This sets the button's width to be exactly 100 pixels. + fn width(mut self, width: DefiniteLength) -> Self { + self.base = self.base.width(width); + self + } + + /// Sets the button to occupy the full width of its container. + /// + /// # Examples + /// + /// ``` + /// use ui::prelude::*; + /// + /// Button::new("button_id", "Click me!") + /// .full_width() + /// .on_click(|event, cx| { + /// // Handle click event + /// }); + /// ``` + /// + /// This stretches the button to the full width of its container. + fn full_width(mut self) -> Self { + self.base = self.base.full_width(); + self + } +} + +impl ButtonCommon for Button { + /// Sets the button's id. + fn id(&self) -> &ElementId { + self.base.id() + } + + /// Sets the visual style of the button using a [`ButtonStyle`]. + fn style(mut self, style: ButtonStyle) -> Self { + self.base = self.base.style(style); + self + } + + /// Sets the button's size using a [`ButtonSize`]. + fn size(mut self, size: ButtonSize) -> Self { + self.base = self.base.size(size); + self + } + + /// Sets a tooltip for the button. + /// + /// This method allows a tooltip to be set for the button. The tooltip is a function that + /// takes a mutable reference to a [`WindowContext`] and returns an [`AnyView`]. The tooltip + /// is displayed when the user hovers over the button. + /// + /// # Examples + /// + /// ``` + /// use ui::prelude::*; + /// use ui::Tooltip; + /// + /// Button::new("button_id", "Click me!") + /// .tooltip(move |cx| { + /// Tooltip::text("This is a tooltip", cx) + /// }) + /// .on_click(|event, cx| { + /// // Handle click event + /// }); + /// ``` + /// + /// This will create a button with a tooltip that displays "This is a tooltip" when hovered over. + fn tooltip(mut self, tooltip: impl Fn(&mut WindowContext) -> AnyView + 'static) -> Self { + self.base = self.base.tooltip(tooltip); + self + } + + fn layer(mut self, elevation: ElevationIndex) -> Self { + self.base = self.base.layer(elevation); + self + } +} + +impl RenderOnce for Button { + #[allow(refining_impl_trait)] + fn render(self, cx: &mut WindowContext) -> ButtonLike { + let is_disabled = self.base.disabled; + let is_selected = self.base.selected; + + let label = self + .selected_label + .filter(|_| is_selected) + .unwrap_or(self.label); + + let label_color = if is_disabled { + Color::Disabled + } else if is_selected { + self.selected_label_color.unwrap_or(Color::Selected) + } else { + self.label_color.unwrap_or_default() + }; + + self.base.child( + h_flex() + .gap(Spacing::Small.rems(cx)) + .when(self.icon_position == Some(IconPosition::Start), |this| { + this.children(self.icon.map(|icon| { + ButtonIcon::new(icon) + .disabled(is_disabled) + .selected(is_selected) + .selected_icon(self.selected_icon) + .selected_icon_color(self.selected_icon_color) + .size(self.icon_size) + .color(self.icon_color) + })) + }) + .child( + h_flex() + .gap(Spacing::Medium.rems(cx)) + .justify_between() + .child( + Label::new(label) + .color(label_color) + .size(self.label_size.unwrap_or_default()) + .when_some(self.alpha, |this, alpha| this.alpha(alpha)) + .line_height_style(LineHeightStyle::UiLabel), + ) + .children(self.key_binding), + ) + .when(self.icon_position != Some(IconPosition::Start), |this| { + this.children(self.icon.map(|icon| { + ButtonIcon::new(icon) + .disabled(is_disabled) + .selected(is_selected) + .selected_icon(self.selected_icon) + .selected_icon_color(self.selected_icon_color) + .size(self.icon_size) + .color(self.icon_color) + })) + }), + ) + } +} diff --git a/crates/ui/src/components/label.rs b/crates/ui/src/components/label.rs index f934540420d2a..bda97be6490d2 100644 --- a/crates/ui/src/components/label.rs +++ b/crates/ui/src/components/label.rs @@ -1,190 +1,7 @@ mod highlighted_label; +mod label; mod label_like; pub use highlighted_label::*; +pub use label::*; pub use label_like::*; - -use gpui::{StyleRefinement, WindowContext}; - -use crate::prelude::*; - -/// A struct representing a label element in the UI. -/// -/// The `Label` struct stores the label text and common properties for a label element. -/// It provides methods for modifying these properties. -/// -/// # Examples -/// -/// ``` -/// use ui::prelude::*; -/// -/// Label::new("Hello, World!"); -/// ``` -/// -/// **A colored label**, for example labeling a dangerous action: -/// -/// ``` -/// use ui::prelude::*; -/// -/// let my_label = Label::new("Delete").color(Color::Error); -/// ``` -/// -/// **A label with a strikethrough**, for example labeling something that has been deleted: -/// -/// ``` -/// use ui::prelude::*; -/// -/// let my_label = Label::new("Deleted").strikethrough(true); -/// ``` -#[derive(IntoElement)] -pub struct Label { - base: LabelLike, - label: SharedString, - single_line: bool, -} - -impl Label { - /// Creates a new [`Label`] with the given text. - /// - /// # Examples - /// - /// ``` - /// use ui::prelude::*; - /// - /// let my_label = Label::new("Hello, World!"); - /// ``` - pub fn new(label: impl Into) -> Self { - Self { - base: LabelLike::new(), - label: label.into(), - single_line: false, - } - } - - /// Make the label display in a single line mode - /// - /// # Examples - /// - /// ``` - /// use ui::prelude::*; - /// - /// let my_label = Label::new("Hello, World!").single_line(); - /// ``` - pub fn single_line(mut self) -> Self { - self.single_line = true; - self - } -} - -// Style methods. -impl Label { - fn style(&mut self) -> &mut StyleRefinement { - self.base.base.style() - } - - gpui::margin_style_methods!({ - visibility: pub - }); -} - -impl LabelCommon for Label { - /// Sets the size of the label using a [`LabelSize`]. - /// - /// # Examples - /// - /// ``` - /// use ui::prelude::*; - /// - /// let my_label = Label::new("Hello, World!").size(LabelSize::Small); - /// ``` - fn size(mut self, size: LabelSize) -> Self { - self.base = self.base.size(size); - self - } - - fn weight(mut self, weight: gpui::FontWeight) -> Self { - self.base = self.base.weight(weight); - self - } - - /// Sets the line height style of the label using a [`LineHeightStyle`]. - /// - /// # Examples - /// - /// ``` - /// use ui::prelude::*; - /// - /// let my_label = Label::new("Hello, World!").line_height_style(LineHeightStyle::UiLabel); - /// ``` - fn line_height_style(mut self, line_height_style: LineHeightStyle) -> Self { - self.base = self.base.line_height_style(line_height_style); - self - } - - /// Sets the color of the label using a [`Color`]. - /// - /// # Examples - /// - /// ``` - /// use ui::prelude::*; - /// - /// let my_label = Label::new("Hello, World!").color(Color::Accent); - /// ``` - fn color(mut self, color: Color) -> Self { - self.base = self.base.color(color); - self - } - - /// Sets the strikethrough property of the label. - /// - /// # Examples - /// - /// ``` - /// use ui::prelude::*; - /// - /// let my_label = Label::new("Hello, World!").strikethrough(true); - /// ``` - fn strikethrough(mut self, strikethrough: bool) -> Self { - self.base = self.base.strikethrough(strikethrough); - self - } - - /// Sets the italic property of the label. - /// - /// # Examples - /// - /// ``` - /// use ui::prelude::*; - /// - /// let my_label = Label::new("Hello, World!").italic(true); - /// ``` - fn italic(mut self, italic: bool) -> Self { - self.base = self.base.italic(italic); - self - } - - /// Sets the alpha property of the color of label. - /// - /// # Examples - /// - /// ``` - /// use ui::prelude::*; - /// - /// let my_label = Label::new("Hello, World!").alpha(0.5); - /// ``` - fn alpha(mut self, alpha: f32) -> Self { - self.base = self.base.alpha(alpha); - self - } -} - -impl RenderOnce for Label { - fn render(self, _cx: &mut WindowContext) -> impl IntoElement { - let target_label = if self.single_line { - SharedString::from(self.label.replace('\n', "␤")) - } else { - self.label - }; - self.base.child(target_label) - } -} diff --git a/crates/ui/src/components/label/label.rs b/crates/ui/src/components/label/label.rs new file mode 100644 index 0000000000000..f29e4656e933c --- /dev/null +++ b/crates/ui/src/components/label/label.rs @@ -0,0 +1,184 @@ +use gpui::{StyleRefinement, WindowContext}; + +use crate::{prelude::*, LabelCommon, LabelLike, LabelSize, LineHeightStyle}; + +/// A struct representing a label element in the UI. +/// +/// The `Label` struct stores the label text and common properties for a label element. +/// It provides methods for modifying these properties. +/// +/// # Examples +/// +/// ``` +/// use ui::prelude::*; +/// +/// Label::new("Hello, World!"); +/// ``` +/// +/// **A colored label**, for example labeling a dangerous action: +/// +/// ``` +/// use ui::prelude::*; +/// +/// let my_label = Label::new("Delete").color(Color::Error); +/// ``` +/// +/// **A label with a strikethrough**, for example labeling something that has been deleted: +/// +/// ``` +/// use ui::prelude::*; +/// +/// let my_label = Label::new("Deleted").strikethrough(true); +/// ``` +#[derive(IntoElement)] +pub struct Label { + base: LabelLike, + label: SharedString, + single_line: bool, +} + +impl Label { + /// Creates a new [`Label`] with the given text. + /// + /// # Examples + /// + /// ``` + /// use ui::prelude::*; + /// + /// let my_label = Label::new("Hello, World!"); + /// ``` + pub fn new(label: impl Into) -> Self { + Self { + base: LabelLike::new(), + label: label.into(), + single_line: false, + } + } + + /// Make the label display in a single line mode + /// + /// # Examples + /// + /// ``` + /// use ui::prelude::*; + /// + /// let my_label = Label::new("Hello, World!").single_line(); + /// ``` + pub fn single_line(mut self) -> Self { + self.single_line = true; + self + } +} + +// Style methods. +impl Label { + fn style(&mut self) -> &mut StyleRefinement { + self.base.base.style() + } + + gpui::margin_style_methods!({ + visibility: pub + }); +} + +impl LabelCommon for Label { + /// Sets the size of the label using a [`LabelSize`]. + /// + /// # Examples + /// + /// ``` + /// use ui::prelude::*; + /// + /// let my_label = Label::new("Hello, World!").size(LabelSize::Small); + /// ``` + fn size(mut self, size: LabelSize) -> Self { + self.base = self.base.size(size); + self + } + + fn weight(mut self, weight: gpui::FontWeight) -> Self { + self.base = self.base.weight(weight); + self + } + + /// Sets the line height style of the label using a [`LineHeightStyle`]. + /// + /// # Examples + /// + /// ``` + /// use ui::prelude::*; + /// + /// let my_label = Label::new("Hello, World!").line_height_style(LineHeightStyle::UiLabel); + /// ``` + fn line_height_style(mut self, line_height_style: LineHeightStyle) -> Self { + self.base = self.base.line_height_style(line_height_style); + self + } + + /// Sets the color of the label using a [`Color`]. + /// + /// # Examples + /// + /// ``` + /// use ui::prelude::*; + /// + /// let my_label = Label::new("Hello, World!").color(Color::Accent); + /// ``` + fn color(mut self, color: Color) -> Self { + self.base = self.base.color(color); + self + } + + /// Sets the strikethrough property of the label. + /// + /// # Examples + /// + /// ``` + /// use ui::prelude::*; + /// + /// let my_label = Label::new("Hello, World!").strikethrough(true); + /// ``` + fn strikethrough(mut self, strikethrough: bool) -> Self { + self.base = self.base.strikethrough(strikethrough); + self + } + + /// Sets the italic property of the label. + /// + /// # Examples + /// + /// ``` + /// use ui::prelude::*; + /// + /// let my_label = Label::new("Hello, World!").italic(true); + /// ``` + fn italic(mut self, italic: bool) -> Self { + self.base = self.base.italic(italic); + self + } + + /// Sets the alpha property of the color of label. + /// + /// # Examples + /// + /// ``` + /// use ui::prelude::*; + /// + /// let my_label = Label::new("Hello, World!").alpha(0.5); + /// ``` + fn alpha(mut self, alpha: f32) -> Self { + self.base = self.base.alpha(alpha); + self + } +} + +impl RenderOnce for Label { + fn render(self, _cx: &mut WindowContext) -> impl IntoElement { + let target_label = if self.single_line { + SharedString::from(self.label.replace('\n', "␤")) + } else { + self.label + }; + self.base.child(target_label) + } +} diff --git a/crates/ui/src/components/list.rs b/crates/ui/src/components/list.rs index 778275f9abeca..88650b6ae8d2a 100644 --- a/crates/ui/src/components/list.rs +++ b/crates/ui/src/components/list.rs @@ -1,76 +1,11 @@ +mod list; mod list_header; mod list_item; mod list_separator; mod list_sub_header; +pub use list::*; pub use list_header::*; pub use list_item::*; pub use list_separator::*; pub use list_sub_header::*; - -use gpui::AnyElement; -use smallvec::SmallVec; - -use crate::{prelude::*, v_flex, Label}; - -pub use ListHeader; - -#[derive(IntoElement)] -pub struct List { - /// Message to display when the list is empty - /// Defaults to "No items" - empty_message: SharedString, - header: Option, - toggle: Option, - children: SmallVec<[AnyElement; 2]>, -} - -impl Default for List { - fn default() -> Self { - Self::new() - } -} - -impl List { - pub fn new() -> Self { - Self { - empty_message: "No items".into(), - header: None, - toggle: None, - children: SmallVec::new(), - } - } - - pub fn empty_message(mut self, empty_message: impl Into) -> Self { - self.empty_message = empty_message.into(); - self - } - - pub fn header(mut self, header: impl Into>) -> Self { - self.header = header.into(); - self - } - - pub fn toggle(mut self, toggle: impl Into>) -> Self { - self.toggle = toggle.into(); - self - } -} - -impl ParentElement for List { - fn extend(&mut self, elements: impl IntoIterator) { - self.children.extend(elements) - } -} - -impl RenderOnce for List { - fn render(self, _cx: &mut WindowContext) -> impl IntoElement { - v_flex().w_full().py_1().children(self.header).map(|this| { - match (self.children.is_empty(), self.toggle) { - (false, _) => this.children(self.children), - (true, Some(false)) => this, - (true, _) => this.child(Label::new(self.empty_message.clone()).color(Color::Muted)), - } - }) - } -} diff --git a/crates/ui/src/components/list/list.rs b/crates/ui/src/components/list/list.rs index a09abf92e45cd..478a906def7d6 100644 --- a/crates/ui/src/components/list/list.rs +++ b/crates/ui/src/components/list/list.rs @@ -13,12 +13,6 @@ pub struct List { children: SmallVec<[AnyElement; 2]>, } -impl Default for List { - fn default() -> Self { - Self::new() - } -} - impl List { pub fn new() -> Self { Self {