Merge remote-tracking branch 'origin/main' into cache

# Conflicts:
#	crates/copilot/src/sign_in.rs
#	crates/gpui/src/window.rs
#	crates/workspace/src/pane_group.rs
This commit is contained in:
Antonio Scandurra 2024-01-10 22:57:47 +01:00
commit 1c260e6dfd
216 changed files with 4218 additions and 2389 deletions

View file

@ -1,6 +1,6 @@
use gpui::{ClickEvent, WindowContext};
/// A trait for elements that can be clicked.
/// A trait for elements that can be clicked. Enables the use of the `on_click` method.
pub trait Clickable {
/// Sets the click handler that will fire whenever the element is clicked.
fn on_click(self, handler: impl Fn(&ClickEvent, &mut WindowContext) + 'static) -> Self;

View file

@ -1,13 +1,28 @@
use crate::prelude::*;
use gpui::{img, Hsla, ImageSource, Img, IntoElement, Styled};
/// The shape of an [`Avatar`].
#[derive(Debug, Default, PartialEq, Clone)]
pub enum Shape {
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,
@ -18,7 +33,7 @@ pub struct Avatar {
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(Shape::Circle);
self = self.shape(AvatarShape::Circle);
}
let size = cx.rem_size();
@ -66,14 +81,35 @@ impl Avatar {
}
}
pub fn shape(mut self, shape: Shape) -> Self {
/// Sets the shape of the avatar image.
///
/// This method allows the shape of the avatar to be specified using a [`Shape`].
/// 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 {
Shape::Circle => self.image.rounded_full(),
Shape::RoundedRectangle => self.image.rounded_md(),
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

View file

@ -2,11 +2,78 @@ use gpui::{AnyView, DefiniteLength};
use crate::{prelude::*, IconPosition, KeyBinding};
use crate::{
ButtonCommon, ButtonLike, ButtonSize, ButtonStyle, Icon, IconSize, Label, LineHeightStyle,
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,
@ -14,15 +81,21 @@ pub struct Button {
label_color: Option<Color>,
label_size: Option<LabelSize>,
selected_label: Option<SharedString>,
icon: Option<Icon>,
icon: Option<IconName>,
icon_position: Option<IconPosition>,
icon_size: Option<IconSize>,
icon_color: Option<Color>,
selected_icon: Option<Icon>,
selected_icon: Option<IconName>,
key_binding: Option<KeyBinding>,
}
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<ElementId>, label: impl Into<SharedString>) -> Self {
Self {
base: ButtonLike::new(id),
@ -39,46 +112,55 @@ impl Button {
}
}
/// Sets the color of the button's label.
pub fn color(mut self, label_color: impl Into<Option<Color>>) -> 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<Option<LabelSize>>) -> Self {
self.label_size = label_size.into();
self
}
/// Sets the label used when the button is in a selected state.
pub fn selected_label<L: Into<SharedString>>(mut self, label: impl Into<Option<L>>) -> Self {
self.selected_label = label.into().map(Into::into);
self
}
pub fn icon(mut self, icon: impl Into<Option<Icon>>) -> Self {
/// Assigns an icon to the button.
pub fn icon(mut self, icon: impl Into<Option<IconName>>) -> 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<Option<IconPosition>>) -> 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<Option<IconSize>>) -> 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<Option<Color>>) -> Self {
self.icon_color = icon_color.into();
self
}
pub fn selected_icon(mut self, icon: impl Into<Option<Icon>>) -> Self {
/// Chooses an icon to display when the button is in a selected state.
pub fn selected_icon(mut self, icon: impl Into<Option<IconName>>) -> Self {
self.selected_icon = icon.into();
self
}
/// Binds a key combination to the button for keyboard shortcuts.
pub fn key_binding(mut self, key_binding: impl Into<Option<KeyBinding>>) -> Self {
self.key_binding = key_binding.into();
self
@ -86,6 +168,24 @@ impl Button {
}
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
@ -93,6 +193,22 @@ impl Selectable for Button {
}
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
@ -100,6 +216,24 @@ impl SelectableButton for Button {
}
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
@ -107,6 +241,7 @@ impl Disableable for Button {
}
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,
@ -117,11 +252,44 @@ impl Clickable for Button {
}
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
@ -129,20 +297,45 @@ impl FixedWidth for Button {
}
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

View file

@ -1,4 +1,4 @@
use crate::{prelude::*, Icon, IconElement, IconSize};
use crate::{prelude::*, Icon, IconName, IconSize};
/// An icon that appears within a button.
///
@ -6,17 +6,17 @@ use crate::{prelude::*, Icon, IconElement, IconSize};
/// or as a standalone icon, like in [`IconButton`](crate::IconButton).
#[derive(IntoElement)]
pub(super) struct ButtonIcon {
icon: Icon,
icon: IconName,
size: IconSize,
color: Color,
disabled: bool,
selected: bool,
selected_icon: Option<Icon>,
selected_icon: Option<IconName>,
selected_style: Option<ButtonStyle>,
}
impl ButtonIcon {
pub fn new(icon: Icon) -> Self {
pub fn new(icon: IconName) -> Self {
Self {
icon,
size: IconSize::default(),
@ -44,7 +44,7 @@ impl ButtonIcon {
self
}
pub fn selected_icon(mut self, icon: impl Into<Option<Icon>>) -> Self {
pub fn selected_icon(mut self, icon: impl Into<Option<IconName>>) -> Self {
self.selected_icon = icon.into();
self
}
@ -88,6 +88,6 @@ impl RenderOnce for ButtonIcon {
self.color
};
IconElement::new(icon).size(self.size).color(icon_color)
Icon::new(icon).size(self.size).color(icon_color)
}
}

View file

@ -4,10 +4,12 @@ use smallvec::SmallVec;
use crate::prelude::*;
/// A trait for buttons that can be Selected. Enables setting the [`ButtonStyle`] of a button when it is selected.
pub trait SelectableButton: Selectable {
fn selected_style(self, style: ButtonStyle) -> Self;
}
/// A common set of traits all buttons must implement.
pub trait ButtonCommon: Clickable + Disableable {
/// A unique element ID to identify the button.
fn id(&self) -> &ElementId;
@ -93,6 +95,7 @@ impl From<ButtonStyle> for Color {
}
}
/// The visual appearance of a button.
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, Default)]
pub enum ButtonStyle {
/// A filled button with a solid background color. Provides emphasis versus
@ -260,8 +263,9 @@ impl ButtonStyle {
}
}
/// ButtonSize can also be used to help build non-button elements
/// that are consistently sized with buttons.
/// The height of a button.
///
/// Can also be used to size non-button elements to align with [`Button`]s.
#[derive(Default, PartialEq, Clone, Copy)]
pub enum ButtonSize {
Large,

View file

@ -1,21 +1,21 @@
use gpui::{AnyView, DefiniteLength};
use crate::{prelude::*, SelectableButton};
use crate::{ButtonCommon, ButtonLike, ButtonSize, ButtonStyle, Icon, IconSize};
use crate::{ButtonCommon, ButtonLike, ButtonSize, ButtonStyle, IconName, IconSize};
use super::button_icon::ButtonIcon;
#[derive(IntoElement)]
pub struct IconButton {
base: ButtonLike,
icon: Icon,
icon: IconName,
icon_size: IconSize,
icon_color: Color,
selected_icon: Option<Icon>,
selected_icon: Option<IconName>,
}
impl IconButton {
pub fn new(id: impl Into<ElementId>, icon: Icon) -> Self {
pub fn new(id: impl Into<ElementId>, icon: IconName) -> Self {
Self {
base: ButtonLike::new(id),
icon,
@ -35,7 +35,7 @@ impl IconButton {
self
}
pub fn selected_icon(mut self, icon: impl Into<Option<Icon>>) -> Self {
pub fn selected_icon(mut self, icon: impl Into<Option<IconName>>) -> Self {
self.selected_icon = icon.into();
self
}

View file

@ -1,7 +1,7 @@
use gpui::{div, prelude::*, ElementId, IntoElement, Styled, WindowContext};
use crate::prelude::*;
use crate::{Color, Icon, IconElement, Selection};
use crate::{Color, Icon, IconName, Selection};
pub type CheckHandler = Box<dyn Fn(&Selection, &mut WindowContext) + 'static>;
@ -47,7 +47,7 @@ impl RenderOnce for Checkbox {
let group_id = format!("checkbox_group_{:?}", self.id);
let icon = match self.checked {
Selection::Selected => Some(IconElement::new(Icon::Check).size(IconSize::Small).color(
Selection::Selected => Some(Icon::new(IconName::Check).size(IconSize::Small).color(
if self.disabled {
Color::Disabled
} else {
@ -55,7 +55,7 @@ impl RenderOnce for Checkbox {
},
)),
Selection::Indeterminate => Some(
IconElement::new(Icon::Dash)
Icon::new(IconName::Dash)
.size(IconSize::Small)
.color(if self.disabled {
Color::Disabled
@ -73,7 +73,7 @@ impl RenderOnce for Checkbox {
// - a previously agreed to license that has been updated
//
// For the sake of styles we treat the indeterminate state as selected,
// but it's icon will be different.
// but its icon will be different.
let selected =
self.checked == Selection::Selected || self.checked == Selection::Indeterminate;

View file

@ -1,6 +1,6 @@
use crate::{
h_stack, prelude::*, v_stack, Icon, IconElement, KeyBinding, Label, List, ListItem,
ListSeparator, ListSubHeader,
h_stack, prelude::*, v_stack, Icon, IconName, KeyBinding, Label, List, ListItem, ListSeparator,
ListSubHeader,
};
use gpui::{
px, Action, AnyElement, AppContext, DismissEvent, EventEmitter, FocusHandle, FocusableView,
@ -14,7 +14,7 @@ enum ContextMenuItem {
Header(SharedString),
Entry {
label: SharedString,
icon: Option<Icon>,
icon: Option<IconName>,
handler: Rc<dyn Fn(&mut WindowContext)>,
action: Option<Box<dyn Action>>,
},
@ -117,7 +117,7 @@ impl ContextMenu {
label: label.into(),
action: Some(action.boxed_clone()),
handler: Rc::new(move |cx| cx.dispatch_action(action.boxed_clone())),
icon: Some(Icon::Link),
icon: Some(IconName::Link),
});
self
}
@ -280,7 +280,7 @@ impl Render for ContextMenu {
h_stack()
.gap_1()
.child(Label::new(label.clone()))
.child(IconElement::new(*icon))
.child(Icon::new(*icon))
.into_any_element()
} else {
Label::new(label.clone()).into_any_element()

View file

@ -1,6 +1,6 @@
use gpui::ClickEvent;
use crate::{prelude::*, Color, Icon, IconButton, IconSize};
use crate::{prelude::*, Color, IconButton, IconName, IconSize};
#[derive(IntoElement)]
pub struct Disclosure {
@ -32,8 +32,8 @@ impl RenderOnce for Disclosure {
IconButton::new(
self.id,
match self.is_open {
true => Icon::ChevronDown,
false => Icon::ChevronRight,
true => IconName::ChevronDown,
false => IconName::ChevronRight,
},
)
.icon_color(Color::Muted)

View file

@ -7,6 +7,7 @@ enum DividerDirection {
Vertical,
}
/// The color of a [`Divider`].
#[derive(Default)]
pub enum DividerColor {
Border,

View file

@ -22,7 +22,7 @@ impl IconSize {
}
#[derive(Debug, PartialEq, Copy, Clone, EnumIter)]
pub enum Icon {
pub enum IconName {
Ai,
ArrowDown,
ArrowLeft,
@ -111,118 +111,108 @@ pub enum Icon {
ZedXCopilot,
}
impl Icon {
impl IconName {
pub fn path(self) -> &'static str {
match self {
Icon::Ai => "icons/ai.svg",
Icon::ArrowDown => "icons/arrow_down.svg",
Icon::ArrowLeft => "icons/arrow_left.svg",
Icon::ArrowRight => "icons/arrow_right.svg",
Icon::ArrowUp => "icons/arrow_up.svg",
Icon::ArrowUpRight => "icons/arrow_up_right.svg",
Icon::ArrowCircle => "icons/arrow_circle.svg",
Icon::AtSign => "icons/at_sign.svg",
Icon::AudioOff => "icons/speaker_off.svg",
Icon::AudioOn => "icons/speaker_loud.svg",
Icon::Backspace => "icons/backspace.svg",
Icon::Bell => "icons/bell.svg",
Icon::BellOff => "icons/bell_off.svg",
Icon::BellRing => "icons/bell_ring.svg",
Icon::Bolt => "icons/bolt.svg",
Icon::CaseSensitive => "icons/case_insensitive.svg",
Icon::Check => "icons/check.svg",
Icon::ChevronDown => "icons/chevron_down.svg",
Icon::ChevronLeft => "icons/chevron_left.svg",
Icon::ChevronRight => "icons/chevron_right.svg",
Icon::ChevronUp => "icons/chevron_up.svg",
Icon::Close => "icons/x.svg",
Icon::Collab => "icons/user_group_16.svg",
Icon::Command => "icons/command.svg",
Icon::Control => "icons/control.svg",
Icon::Copilot => "icons/copilot.svg",
Icon::CopilotDisabled => "icons/copilot_disabled.svg",
Icon::CopilotError => "icons/copilot_error.svg",
Icon::CopilotInit => "icons/copilot_init.svg",
Icon::Copy => "icons/copy.svg",
Icon::Dash => "icons/dash.svg",
Icon::Delete => "icons/delete.svg",
Icon::Disconnected => "icons/disconnected.svg",
Icon::Ellipsis => "icons/ellipsis.svg",
Icon::Envelope => "icons/feedback.svg",
Icon::Escape => "icons/escape.svg",
Icon::ExclamationTriangle => "icons/warning.svg",
Icon::Exit => "icons/exit.svg",
Icon::ExternalLink => "icons/external_link.svg",
Icon::File => "icons/file.svg",
Icon::FileDoc => "icons/file_icons/book.svg",
Icon::FileGeneric => "icons/file_icons/file.svg",
Icon::FileGit => "icons/file_icons/git.svg",
Icon::FileLock => "icons/file_icons/lock.svg",
Icon::FileRust => "icons/file_icons/rust.svg",
Icon::FileToml => "icons/file_icons/toml.svg",
Icon::FileTree => "icons/project.svg",
Icon::Filter => "icons/filter.svg",
Icon::Folder => "icons/file_icons/folder.svg",
Icon::FolderOpen => "icons/file_icons/folder_open.svg",
Icon::FolderX => "icons/stop_sharing.svg",
Icon::Github => "icons/github.svg",
Icon::Hash => "icons/hash.svg",
Icon::InlayHint => "icons/inlay_hint.svg",
Icon::Link => "icons/link.svg",
Icon::MagicWand => "icons/magic_wand.svg",
Icon::MagnifyingGlass => "icons/magnifying_glass.svg",
Icon::MailOpen => "icons/mail_open.svg",
Icon::Maximize => "icons/maximize.svg",
Icon::Menu => "icons/menu.svg",
Icon::MessageBubbles => "icons/conversations.svg",
Icon::Mic => "icons/mic.svg",
Icon::MicMute => "icons/mic_mute.svg",
Icon::Minimize => "icons/minimize.svg",
Icon::Option => "icons/option.svg",
Icon::PageDown => "icons/page_down.svg",
Icon::PageUp => "icons/page_up.svg",
Icon::Plus => "icons/plus.svg",
Icon::Public => "icons/public.svg",
Icon::Quote => "icons/quote.svg",
Icon::Replace => "icons/replace.svg",
Icon::ReplaceAll => "icons/replace_all.svg",
Icon::ReplaceNext => "icons/replace_next.svg",
Icon::Return => "icons/return.svg",
Icon::Screen => "icons/desktop.svg",
Icon::SelectAll => "icons/select_all.svg",
Icon::Shift => "icons/shift.svg",
Icon::Snip => "icons/snip.svg",
Icon::Space => "icons/space.svg",
Icon::Split => "icons/split.svg",
Icon::Tab => "icons/tab.svg",
Icon::Terminal => "icons/terminal.svg",
Icon::Update => "icons/update.svg",
Icon::WholeWord => "icons/word_search.svg",
Icon::XCircle => "icons/error.svg",
Icon::ZedXCopilot => "icons/zed_x_copilot.svg",
IconName::Ai => "icons/ai.svg",
IconName::ArrowDown => "icons/arrow_down.svg",
IconName::ArrowLeft => "icons/arrow_left.svg",
IconName::ArrowRight => "icons/arrow_right.svg",
IconName::ArrowUp => "icons/arrow_up.svg",
IconName::ArrowUpRight => "icons/arrow_up_right.svg",
IconName::ArrowCircle => "icons/arrow_circle.svg",
IconName::AtSign => "icons/at_sign.svg",
IconName::AudioOff => "icons/speaker_off.svg",
IconName::AudioOn => "icons/speaker_loud.svg",
IconName::Backspace => "icons/backspace.svg",
IconName::Bell => "icons/bell.svg",
IconName::BellOff => "icons/bell_off.svg",
IconName::BellRing => "icons/bell_ring.svg",
IconName::Bolt => "icons/bolt.svg",
IconName::CaseSensitive => "icons/case_insensitive.svg",
IconName::Check => "icons/check.svg",
IconName::ChevronDown => "icons/chevron_down.svg",
IconName::ChevronLeft => "icons/chevron_left.svg",
IconName::ChevronRight => "icons/chevron_right.svg",
IconName::ChevronUp => "icons/chevron_up.svg",
IconName::Close => "icons/x.svg",
IconName::Collab => "icons/user_group_16.svg",
IconName::Command => "icons/command.svg",
IconName::Control => "icons/control.svg",
IconName::Copilot => "icons/copilot.svg",
IconName::CopilotDisabled => "icons/copilot_disabled.svg",
IconName::CopilotError => "icons/copilot_error.svg",
IconName::CopilotInit => "icons/copilot_init.svg",
IconName::Copy => "icons/copy.svg",
IconName::Dash => "icons/dash.svg",
IconName::Delete => "icons/delete.svg",
IconName::Disconnected => "icons/disconnected.svg",
IconName::Ellipsis => "icons/ellipsis.svg",
IconName::Envelope => "icons/feedback.svg",
IconName::Escape => "icons/escape.svg",
IconName::ExclamationTriangle => "icons/warning.svg",
IconName::Exit => "icons/exit.svg",
IconName::ExternalLink => "icons/external_link.svg",
IconName::File => "icons/file.svg",
IconName::FileDoc => "icons/file_icons/book.svg",
IconName::FileGeneric => "icons/file_icons/file.svg",
IconName::FileGit => "icons/file_icons/git.svg",
IconName::FileLock => "icons/file_icons/lock.svg",
IconName::FileRust => "icons/file_icons/rust.svg",
IconName::FileToml => "icons/file_icons/toml.svg",
IconName::FileTree => "icons/project.svg",
IconName::Filter => "icons/filter.svg",
IconName::Folder => "icons/file_icons/folder.svg",
IconName::FolderOpen => "icons/file_icons/folder_open.svg",
IconName::FolderX => "icons/stop_sharing.svg",
IconName::Github => "icons/github.svg",
IconName::Hash => "icons/hash.svg",
IconName::InlayHint => "icons/inlay_hint.svg",
IconName::Link => "icons/link.svg",
IconName::MagicWand => "icons/magic_wand.svg",
IconName::MagnifyingGlass => "icons/magnifying_glass.svg",
IconName::MailOpen => "icons/mail_open.svg",
IconName::Maximize => "icons/maximize.svg",
IconName::Menu => "icons/menu.svg",
IconName::MessageBubbles => "icons/conversations.svg",
IconName::Mic => "icons/mic.svg",
IconName::MicMute => "icons/mic_mute.svg",
IconName::Minimize => "icons/minimize.svg",
IconName::Option => "icons/option.svg",
IconName::PageDown => "icons/page_down.svg",
IconName::PageUp => "icons/page_up.svg",
IconName::Plus => "icons/plus.svg",
IconName::Public => "icons/public.svg",
IconName::Quote => "icons/quote.svg",
IconName::Replace => "icons/replace.svg",
IconName::ReplaceAll => "icons/replace_all.svg",
IconName::ReplaceNext => "icons/replace_next.svg",
IconName::Return => "icons/return.svg",
IconName::Screen => "icons/desktop.svg",
IconName::SelectAll => "icons/select_all.svg",
IconName::Shift => "icons/shift.svg",
IconName::Snip => "icons/snip.svg",
IconName::Space => "icons/space.svg",
IconName::Split => "icons/split.svg",
IconName::Tab => "icons/tab.svg",
IconName::Terminal => "icons/terminal.svg",
IconName::Update => "icons/update.svg",
IconName::WholeWord => "icons/word_search.svg",
IconName::XCircle => "icons/error.svg",
IconName::ZedXCopilot => "icons/zed_x_copilot.svg",
}
}
}
#[derive(IntoElement)]
pub struct IconElement {
pub struct Icon {
path: SharedString,
color: Color,
size: IconSize,
}
impl RenderOnce for IconElement {
fn render(self, cx: &mut WindowContext) -> impl IntoElement {
svg()
.size(self.size.rems())
.flex_none()
.path(self.path)
.text_color(self.color.color(cx))
}
}
impl IconElement {
pub fn new(icon: Icon) -> Self {
impl Icon {
pub fn new(icon: IconName) -> Self {
Self {
path: icon.path().into(),
color: Color::default(),
@ -248,3 +238,13 @@ impl IconElement {
self
}
}
impl RenderOnce for Icon {
fn render(self, cx: &mut WindowContext) -> impl IntoElement {
svg()
.size(self.size.rems())
.flex_none()
.path(self.path)
.text_color(self.color.color(cx))
}
}

View file

@ -1,4 +1,4 @@
use crate::{h_stack, prelude::*, Icon, IconElement, IconSize};
use crate::{h_stack, prelude::*, Icon, IconName, IconSize};
use gpui::{relative, rems, Action, FocusHandle, IntoElement, Keystroke};
#[derive(IntoElement, Clone)]
@ -26,16 +26,16 @@ impl RenderOnce for KeyBinding {
.text_color(cx.theme().colors().text_muted)
.when(keystroke.modifiers.function, |el| el.child(Key::new("fn")))
.when(keystroke.modifiers.control, |el| {
el.child(KeyIcon::new(Icon::Control))
el.child(KeyIcon::new(IconName::Control))
})
.when(keystroke.modifiers.alt, |el| {
el.child(KeyIcon::new(Icon::Option))
el.child(KeyIcon::new(IconName::Option))
})
.when(keystroke.modifiers.command, |el| {
el.child(KeyIcon::new(Icon::Command))
el.child(KeyIcon::new(IconName::Command))
})
.when(keystroke.modifiers.shift, |el| {
el.child(KeyIcon::new(Icon::Shift))
el.child(KeyIcon::new(IconName::Shift))
})
.when_some(key_icon, |el, icon| el.child(KeyIcon::new(icon)))
.when(key_icon.is_none(), |el| {
@ -62,21 +62,21 @@ impl KeyBinding {
Some(Self::new(key_binding))
}
fn icon_for_key(keystroke: &Keystroke) -> Option<Icon> {
fn icon_for_key(keystroke: &Keystroke) -> Option<IconName> {
match keystroke.key.as_str() {
"left" => Some(Icon::ArrowLeft),
"right" => Some(Icon::ArrowRight),
"up" => Some(Icon::ArrowUp),
"down" => Some(Icon::ArrowDown),
"backspace" => Some(Icon::Backspace),
"delete" => Some(Icon::Delete),
"return" => Some(Icon::Return),
"enter" => Some(Icon::Return),
"tab" => Some(Icon::Tab),
"space" => Some(Icon::Space),
"escape" => Some(Icon::Escape),
"pagedown" => Some(Icon::PageDown),
"pageup" => Some(Icon::PageUp),
"left" => Some(IconName::ArrowLeft),
"right" => Some(IconName::ArrowRight),
"up" => Some(IconName::ArrowUp),
"down" => Some(IconName::ArrowDown),
"backspace" => Some(IconName::Backspace),
"delete" => Some(IconName::Delete),
"return" => Some(IconName::Return),
"enter" => Some(IconName::Return),
"tab" => Some(IconName::Tab),
"space" => Some(IconName::Space),
"escape" => Some(IconName::Escape),
"pagedown" => Some(IconName::PageDown),
"pageup" => Some(IconName::PageUp),
_ => None,
}
}
@ -120,13 +120,13 @@ impl Key {
#[derive(IntoElement)]
pub struct KeyIcon {
icon: Icon,
icon: IconName,
}
impl RenderOnce for KeyIcon {
fn render(self, _cx: &mut WindowContext) -> impl IntoElement {
div().w(rems(14. / 16.)).child(
IconElement::new(self.icon)
Icon::new(self.icon)
.size(IconSize::Small)
.color(Color::Muted),
)
@ -134,7 +134,7 @@ impl RenderOnce for KeyIcon {
}
impl KeyIcon {
pub fn new(icon: Icon) -> Self {
pub fn new(icon: IconName) -> Self {
Self { icon }
}
}

View file

@ -2,6 +2,34 @@ use gpui::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,
@ -9,6 +37,15 @@ pub struct Label {
}
impl Label {
/// Create a new [`Label`] with the given text.
///
/// # Examples
///
/// ```
/// use ui::prelude::*;
///
/// let my_label = Label::new("Hello, World!");
/// ```
pub fn new(label: impl Into<SharedString>) -> Self {
Self {
base: LabelLike::new(),
@ -18,21 +55,57 @@ impl Label {
}
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
}
/// 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

View file

@ -19,10 +19,18 @@ pub enum LineHeightStyle {
UiLabel,
}
/// A common set of traits all labels must implement.
pub trait LabelCommon {
/// Sets the size of the label using a [`LabelSize`].
fn size(self, size: LabelSize) -> Self;
/// Sets the line height style of the label using a [`LineHeightStyle`].
fn line_height_style(self, line_height_style: LineHeightStyle) -> Self;
/// Sets the color of the label using a [`Color`].
fn color(self, color: Color) -> Self;
/// Sets the strikethrough property of the label.
fn strikethrough(self, strikethrough: bool) -> Self;
}

View file

@ -1,10 +1,10 @@
use crate::prelude::*;
use crate::{h_stack, Icon, IconElement, IconSize, Label};
use crate::{h_stack, Icon, IconName, IconSize, Label};
#[derive(IntoElement)]
pub struct ListSubHeader {
label: SharedString,
start_slot: Option<Icon>,
start_slot: Option<IconName>,
inset: bool,
}
@ -17,7 +17,7 @@ impl ListSubHeader {
}
}
pub fn left_icon(mut self, left_icon: Option<Icon>) -> Self {
pub fn left_icon(mut self, left_icon: Option<IconName>) -> Self {
self.start_slot = left_icon;
self
}
@ -40,11 +40,10 @@ impl RenderOnce for ListSubHeader {
.flex()
.gap_1()
.items_center()
.children(self.start_slot.map(|i| {
IconElement::new(i)
.color(Color::Muted)
.size(IconSize::Small)
}))
.children(
self.start_slot
.map(|i| Icon::new(i).color(Color::Muted).size(IconSize::Small)),
)
.child(Label::new(self.label.clone()).color(Color::Muted)),
),
)

View file

@ -108,6 +108,7 @@ impl<M: ManagedView> PopoverMenu<M> {
}
}
/// Creates a [`PopoverMenu`]
pub fn popover_menu<M: ManagedView>(id: impl Into<ElementId>) -> PopoverMenu<M> {
PopoverMenu {
id: id.into(),

View file

@ -39,6 +39,7 @@ impl<M: ManagedView> RightClickMenu<M> {
}
}
/// Creates a [`RightClickMenu`]
pub fn right_click_menu<M: ManagedView>(id: impl Into<ElementId>) -> RightClickMenu<M> {
RightClickMenu {
id: id.into(),

View file

@ -2,17 +2,13 @@ use gpui::{div, Div};
use crate::StyledExt;
/// Horizontally stacks elements.
///
/// Sets `flex()`, `flex_row()`, `items_center()`
/// Horizontally stacks elements. Sets `flex()`, `flex_row()`, `items_center()`
#[track_caller]
pub fn h_stack() -> Div {
div().h_flex()
}
/// Vertically stacks elements.
///
/// Sets `flex()`, `flex_col()`
/// Vertically stacks elements. Sets `flex()`, `flex_col()`
#[track_caller]
pub fn v_stack() -> Div {
div().v_flex()

View file

@ -1,7 +1,7 @@
use gpui::Render;
use story::Story;
use crate::{prelude::*, Icon};
use crate::{prelude::*, IconName};
use crate::{Button, ButtonStyle};
pub struct ButtonStory;
@ -23,12 +23,12 @@ impl Render for ButtonStory {
.child(Story::label("With `label_color`"))
.child(Button::new("filled_with_label_color", "Click me").color(Color::Created))
.child(Story::label("With `icon`"))
.child(Button::new("filled_with_icon", "Click me").icon(Icon::FileGit))
.child(Button::new("filled_with_icon", "Click me").icon(IconName::FileGit))
.child(Story::label("Selected with `icon`"))
.child(
Button::new("filled_and_selected_with_icon", "Click me")
.selected(true)
.icon(Icon::FileGit),
.icon(IconName::FileGit),
)
.child(Story::label("Default (Subtle)"))
.child(Button::new("default_subtle", "Click me").style(ButtonStyle::Subtle))

View file

@ -3,17 +3,17 @@ use story::Story;
use strum::IntoEnumIterator;
use crate::prelude::*;
use crate::{Icon, IconElement};
use crate::{Icon, IconName};
pub struct IconStory;
impl Render for IconStory {
fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
let icons = Icon::iter();
let icons = IconName::iter();
Story::container()
.child(Story::title_for::<IconElement>())
.child(Story::title_for::<Icon>())
.child(Story::label("All Icons"))
.child(div().flex().gap_3().children(icons.map(IconElement::new)))
.child(div().flex().gap_3().children(icons.map(Icon::new)))
}
}

View file

@ -2,7 +2,7 @@ use gpui::Render;
use story::{StoryContainer, StoryItem, StorySection};
use crate::{prelude::*, Tooltip};
use crate::{Icon, IconButton};
use crate::{IconButton, IconName};
pub struct IconButtonStory;
@ -10,7 +10,7 @@ impl Render for IconButtonStory {
fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
let default_button = StoryItem::new(
"Default",
IconButton::new("default_icon_button", Icon::Hash),
IconButton::new("default_icon_button", IconName::Hash),
)
.description("Displays an icon button.")
.usage(
@ -21,7 +21,7 @@ impl Render for IconButtonStory {
let selected_button = StoryItem::new(
"Selected",
IconButton::new("selected_icon_button", Icon::Hash).selected(true),
IconButton::new("selected_icon_button", IconName::Hash).selected(true),
)
.description("Displays an icon button that is selected.")
.usage(
@ -32,9 +32,9 @@ impl Render for IconButtonStory {
let selected_with_selected_icon = StoryItem::new(
"Selected with `selected_icon`",
IconButton::new("selected_with_selected_icon_button", Icon::AudioOn)
IconButton::new("selected_with_selected_icon_button", IconName::AudioOn)
.selected(true)
.selected_icon(Icon::AudioOff),
.selected_icon(IconName::AudioOff),
)
.description(
"Displays an icon button that is selected and shows a different icon when selected.",
@ -49,7 +49,7 @@ impl Render for IconButtonStory {
let disabled_button = StoryItem::new(
"Disabled",
IconButton::new("disabled_icon_button", Icon::Hash).disabled(true),
IconButton::new("disabled_icon_button", IconName::Hash).disabled(true),
)
.description("Displays an icon button that is disabled.")
.usage(
@ -60,7 +60,7 @@ impl Render for IconButtonStory {
let with_on_click_button = StoryItem::new(
"With `on_click`",
IconButton::new("with_on_click_button", Icon::Ai).on_click(|_event, _cx| {
IconButton::new("with_on_click_button", IconName::Ai).on_click(|_event, _cx| {
println!("Clicked!");
}),
)
@ -75,7 +75,7 @@ impl Render for IconButtonStory {
let with_tooltip_button = StoryItem::new(
"With `tooltip`",
IconButton::new("with_tooltip_button", Icon::MessageBubbles)
IconButton::new("with_tooltip_button", IconName::MessageBubbles)
.tooltip(|cx| Tooltip::text("Open messages", cx)),
)
.description("Displays an icon button that has a tooltip when hovered.")
@ -88,7 +88,7 @@ impl Render for IconButtonStory {
let selected_with_tooltip_button = StoryItem::new(
"Selected with `tooltip`",
IconButton::new("selected_with_tooltip_button", Icon::InlayHint)
IconButton::new("selected_with_tooltip_button", IconName::InlayHint)
.selected(true)
.tooltip(|cx| Tooltip::text("Toggle inlay hints", cx)),
)

View file

@ -2,7 +2,7 @@ use gpui::Render;
use story::Story;
use crate::{prelude::*, IconButton};
use crate::{Icon, ListHeader};
use crate::{IconName, ListHeader};
pub struct ListHeaderStory;
@ -13,19 +13,19 @@ impl Render for ListHeaderStory {
.child(Story::label("Default"))
.child(ListHeader::new("Section 1"))
.child(Story::label("With left icon"))
.child(ListHeader::new("Section 2").start_slot(IconElement::new(Icon::Bell)))
.child(ListHeader::new("Section 2").start_slot(Icon::new(IconName::Bell)))
.child(Story::label("With left icon and meta"))
.child(
ListHeader::new("Section 3")
.start_slot(IconElement::new(Icon::BellOff))
.end_slot(IconButton::new("action_1", Icon::Bolt)),
.start_slot(Icon::new(IconName::BellOff))
.end_slot(IconButton::new("action_1", IconName::Bolt)),
)
.child(Story::label("With multiple meta"))
.child(
ListHeader::new("Section 4")
.end_slot(IconButton::new("action_1", Icon::Bolt))
.end_slot(IconButton::new("action_2", Icon::ExclamationTriangle))
.end_slot(IconButton::new("action_3", Icon::Plus)),
.end_slot(IconButton::new("action_1", IconName::Bolt))
.end_slot(IconButton::new("action_2", IconName::ExclamationTriangle))
.end_slot(IconButton::new("action_3", IconName::Plus)),
)
}
}

View file

@ -1,8 +1,8 @@
use gpui::Render;
use gpui::{Render, SharedUrl};
use story::Story;
use crate::{prelude::*, Avatar};
use crate::{Icon, ListItem};
use crate::{IconName, ListItem};
pub struct ListItemStory;
@ -18,13 +18,13 @@ impl Render for ListItemStory {
ListItem::new("inset_list_item")
.inset(true)
.start_slot(
IconElement::new(Icon::Bell)
Icon::new(IconName::Bell)
.size(IconSize::Small)
.color(Color::Muted),
)
.child("Hello, world!")
.end_slot(
IconElement::new(Icon::Bell)
Icon::new(IconName::Bell)
.size(IconSize::Small)
.color(Color::Muted),
),
@ -34,7 +34,7 @@ impl Render for ListItemStory {
ListItem::new("with start slot_icon")
.child("Hello, world!")
.start_slot(
IconElement::new(Icon::Bell)
Icon::new(IconName::Bell)
.size(IconSize::Small)
.color(Color::Muted),
),
@ -43,7 +43,7 @@ impl Render for ListItemStory {
.child(
ListItem::new("with_start slot avatar")
.child("Hello, world!")
.start_slot(Avatar::new(SharedString::from(
.start_slot(Avatar::new(SharedUrl::from(
"https://avatars.githubusercontent.com/u/1714999?v=4",
))),
)
@ -51,7 +51,7 @@ impl Render for ListItemStory {
.child(
ListItem::new("with_left_avatar")
.child("Hello, world!")
.end_slot(Avatar::new(SharedString::from(
.end_slot(Avatar::new(SharedUrl::from(
"https://avatars.githubusercontent.com/u/1714999?v=4",
))),
)
@ -62,23 +62,23 @@ impl Render for ListItemStory {
.end_slot(
h_stack()
.gap_2()
.child(Avatar::new(SharedString::from(
.child(Avatar::new(SharedUrl::from(
"https://avatars.githubusercontent.com/u/1789?v=4",
)))
.child(Avatar::new(SharedString::from(
.child(Avatar::new(SharedUrl::from(
"https://avatars.githubusercontent.com/u/1789?v=4",
)))
.child(Avatar::new(SharedString::from(
.child(Avatar::new(SharedUrl::from(
"https://avatars.githubusercontent.com/u/1789?v=4",
)))
.child(Avatar::new(SharedString::from(
.child(Avatar::new(SharedUrl::from(
"https://avatars.githubusercontent.com/u/1789?v=4",
)))
.child(Avatar::new(SharedString::from(
.child(Avatar::new(SharedUrl::from(
"https://avatars.githubusercontent.com/u/1789?v=4",
))),
)
.end_hover_slot(Avatar::new(SharedString::from(
.end_hover_slot(Avatar::new(SharedUrl::from(
"https://avatars.githubusercontent.com/u/1714999?v=4",
))),
)

View file

@ -27,7 +27,7 @@ impl Render for TabStory {
h_stack().child(
Tab::new("tab_1")
.end_slot(
IconButton::new("close_button", Icon::Close)
IconButton::new("close_button", IconName::Close)
.icon_color(Color::Muted)
.size(ButtonSize::None)
.icon_size(IconSize::XSmall),

View file

@ -38,16 +38,19 @@ impl Render for TabBarStory {
h_stack().child(
TabBar::new("tab_bar_1")
.start_child(
IconButton::new("navigate_backward", Icon::ArrowLeft)
IconButton::new("navigate_backward", IconName::ArrowLeft)
.icon_size(IconSize::Small),
)
.start_child(
IconButton::new("navigate_forward", Icon::ArrowRight)
IconButton::new("navigate_forward", IconName::ArrowRight)
.icon_size(IconSize::Small),
)
.end_child(IconButton::new("new", Icon::Plus).icon_size(IconSize::Small))
.end_child(
IconButton::new("split_pane", Icon::Split).icon_size(IconSize::Small),
IconButton::new("new", IconName::Plus).icon_size(IconSize::Small),
)
.end_child(
IconButton::new("split_pane", IconName::Split)
.icon_size(IconSize::Small),
)
.children(tabs),
),

View file

@ -1,4 +1,4 @@
/// A trait for elements that can be disabled.
/// A trait for elements that can be disabled. Generally used to implement disabling an element's interactivity and changing its appearance to reflect that it is disabled.
pub trait Disableable {
/// Sets whether the element is disabled.
fn disabled(self, disabled: bool) -> Self;

View file

@ -1,6 +1,6 @@
use gpui::DefiniteLength;
/// A trait for elements that have a fixed with.
/// A trait for elements that can have a fixed with. Enables the use of the `width` and `full_width` methods.
pub trait FixedWidth {
/// Sets the width of the element.
fn width(self, width: DefiniteLength) -> Self;

View file

@ -1,3 +1,5 @@
//! The prelude of this crate. When building UI in Zed you almost always want to import this.
pub use gpui::prelude::*;
pub use gpui::{
div, px, relative, rems, AbsoluteLength, DefiniteLength, Div, Element, ElementId,
@ -14,6 +16,7 @@ pub use crate::visible_on_hover::*;
pub use crate::{h_stack, v_stack};
pub use crate::{Button, ButtonSize, ButtonStyle, IconButton, SelectableButton};
pub use crate::{ButtonCommon, Color, StyledExt};
pub use crate::{Icon, IconElement, IconPosition, IconSize};
pub use crate::{Headline, HeadlineSize};
pub use crate::{Icon, IconName, IconPosition, IconSize};
pub use crate::{Label, LabelCommon, LabelSize, LineHeightStyle};
pub use theme::ActiveTheme;

View file

@ -1,18 +1,27 @@
/// A trait for elements that can be selected.
///
/// Generally used to enable "toggle" or "active" behavior and styles on an element through the [`Selection`] status.
pub trait Selectable {
/// Sets whether the element is selected.
fn selected(self, selected: bool) -> Self;
}
/// Represents the selection status of an element.
#[derive(Debug, Default, PartialEq, Eq, Hash, Clone, Copy)]
pub enum Selection {
/// The element is not selected.
#[default]
Unselected,
/// The selection state of the element is indeterminate.
Indeterminate,
/// The element is selected.
Selected,
}
impl Selection {
/// Returns the inverse of the current selection status.
///
/// Indeterminate states become selected if inverted.
pub fn inverse(&self) -> Self {
match self {
Self::Unselected | Self::Indeterminate => Self::Selected,

View file

@ -14,7 +14,7 @@ fn elevated<E: Styled>(this: E, cx: &mut WindowContext, index: ElevationIndex) -
.shadow(index.shadow())
}
/// Extends [`Styled`](gpui::Styled) with Zed specific styling methods.
/// Extends [`gpui::Styled`] with Zed-specific styling methods.
pub trait StyledExt: Styled + Sized {
/// Horizontally stacks elements.
///
@ -30,6 +30,7 @@ pub trait StyledExt: Styled + Sized {
self.flex().flex_col()
}
/// Sets the text size using a [`UiTextSize`].
fn text_ui_size(self, size: UiTextSize) -> Self {
self.text_size(size.rems())
}
@ -40,7 +41,7 @@ pub trait StyledExt: Styled + Sized {
///
/// Note: The absolute size of this text will change based on a user's `ui_scale` setting.
///
/// Use [`text_ui_sm`] for regular-sized text.
/// Use `text_ui_sm` for smaller text.
fn text_ui(self) -> Self {
self.text_size(UiTextSize::default().rems())
}
@ -51,7 +52,7 @@ pub trait StyledExt: Styled + Sized {
///
/// Note: The absolute size of this text will change based on a user's `ui_scale` setting.
///
/// Use [`text_ui`] for regular-sized text.
/// Use `text_ui` for regular-sized text.
fn text_ui_sm(self) -> Self {
self.text_size(UiTextSize::Small.rems())
}
@ -62,7 +63,7 @@ pub trait StyledExt: Styled + Sized {
///
/// Note: The absolute size of this text will change based on a user's `ui_scale` setting.
///
/// Use [`text_ui`] for regular-sized text.
/// Use `text_ui` for regular-sized text.
fn text_ui_xs(self) -> Self {
self.text_size(UiTextSize::XSmall.rems())
}
@ -78,7 +79,7 @@ pub trait StyledExt: Styled + Sized {
self.text_size(settings.buffer_font_size(cx))
}
/// The [`Surface`](ui::ElevationIndex::Surface) elevation level, located above the app background, is the standard level for all elements
/// The [`Surface`](ElevationIndex::Surface) elevation level, located above the app background, is the standard level for all elements
///
/// Sets `bg()`, `rounded_lg()`, `border()`, `border_color()`, `shadow()`
///
@ -87,7 +88,7 @@ pub trait StyledExt: Styled + Sized {
elevated(self, cx, ElevationIndex::Surface)
}
/// Non-Modal Elevated Surfaces appear above the [`Surface`](ui::ElevationIndex::Surface) layer and is used for things that should appear above most UI elements like an editor or panel, but not elements like popovers, context menus, modals, etc.
/// Non-Modal Elevated Surfaces appear above the [`Surface`](ElevationIndex::Surface) layer and is used for things that should appear above most UI elements like an editor or panel, but not elements like popovers, context menus, modals, etc.
///
/// Sets `bg()`, `rounded_lg()`, `border()`, `border_color()`, `shadow()`
///
@ -100,7 +101,7 @@ pub trait StyledExt: Styled + Sized {
///
/// Elements rendered at this layer should have an enforced behavior: Any interaction outside of the modal will either dismiss the modal or prompt an action (Save your progress, etc) then dismiss the modal.
///
/// If the element does not have this behavior, it should be rendered at the [`Elevated Surface`](ui::ElevationIndex::ElevatedSurface) layer.
/// If the element does not have this behavior, it should be rendered at the [`Elevated Surface`](ElevationIndex::ElevatedSurface) layer.
///
/// Sets `bg()`, `rounded_lg()`, `border()`, `border_color()`, `shadow()`
///
@ -119,26 +120,32 @@ pub trait StyledExt: Styled + Sized {
self.border_color(cx.theme().colors().border_variant)
}
/// Sets the background color to red for debugging when building UI.
fn debug_bg_red(self) -> Self {
self.bg(hsla(0. / 360., 1., 0.5, 1.))
}
/// Sets the background color to green for debugging when building UI.
fn debug_bg_green(self) -> Self {
self.bg(hsla(120. / 360., 1., 0.5, 1.))
}
/// Sets the background color to blue for debugging when building UI.
fn debug_bg_blue(self) -> Self {
self.bg(hsla(240. / 360., 1., 0.5, 1.))
}
/// Sets the background color to yellow for debugging when building UI.
fn debug_bg_yellow(self) -> Self {
self.bg(hsla(60. / 360., 1., 0.5, 1.))
}
/// Sets the background color to cyan for debugging when building UI.
fn debug_bg_cyan(self) -> Self {
self.bg(hsla(160. / 360., 1., 0.5, 1.))
}
/// Sets the background color to magenta for debugging when building UI.
fn debug_bg_magenta(self) -> Self {
self.bg(hsla(300. / 360., 1., 0.5, 1.))
}

View file

@ -1,6 +1,7 @@
use gpui::{Hsla, WindowContext};
use theme::ActiveTheme;
/// Sets a color that has a consistent meaning across all themes.
#[derive(Debug, Default, PartialEq, Copy, Clone)]
pub enum Color {
#[default]

View file

@ -85,6 +85,7 @@ impl LayerIndex {
}
}
/// An appropriate z-index for the given layer based on its intended useage.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ElementIndex {
Effect,

View file

@ -1,4 +1,8 @@
use gpui::{rems, Rems};
use gpui::{
div, rems, IntoElement, ParentElement, Rems, RenderOnce, SharedString, Styled, WindowContext,
};
use settings::Settings;
use theme::{ActiveTheme, ThemeSettings};
#[derive(Debug, Default, Clone)]
pub enum UiTextSize {
@ -33,3 +37,70 @@ impl UiTextSize {
}
}
}
/// The size of a [`Headline`] element
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, Default)]
pub enum HeadlineSize {
XSmall,
Small,
#[default]
Medium,
Large,
XLarge,
}
impl HeadlineSize {
pub fn size(self) -> Rems {
match self {
// Based on the Major Second scale
Self::XSmall => rems(0.88),
Self::Small => rems(1.0),
Self::Medium => rems(1.125),
Self::Large => rems(1.27),
Self::XLarge => rems(1.43),
}
}
pub fn line_height(self) -> Rems {
match self {
Self::XSmall => rems(1.6),
Self::Small => rems(1.6),
Self::Medium => rems(1.6),
Self::Large => rems(1.6),
Self::XLarge => rems(1.6),
}
}
}
#[derive(IntoElement)]
pub struct Headline {
size: HeadlineSize,
text: SharedString,
}
impl RenderOnce for Headline {
fn render(self, cx: &mut WindowContext) -> impl IntoElement {
let ui_font = ThemeSettings::get_global(cx).ui_font.family.clone();
div()
.font(ui_font)
.line_height(self.size.line_height())
.text_size(self.size.size())
.text_color(cx.theme().colors().text)
.child(self.text)
}
}
impl Headline {
pub fn new(text: impl Into<SharedString>) -> Self {
Self {
size: HeadlineSize::default(),
text: text.into(),
}
}
pub fn size(mut self, size: HeadlineSize) -> Self {
self.size = size;
self
}
}

View file

@ -3,8 +3,6 @@
//! This crate provides a set of UI primitives and components that are used to build all of the elements in Zed's UI.
//!
#![doc = include_str!("../docs/building-ui.md")]
mod clickable;
mod components;
mod disableable;

View file

@ -1,3 +1,5 @@
//! UI-related utilities (e.g. converting dates to a human-readable form).
mod format_distance;
pub use format_distance::*;

View file

@ -7,10 +7,10 @@ pub enum DateTimeType {
}
impl DateTimeType {
/// Converts the DateTimeType to a NaiveDateTime.
/// Converts the [`DateTimeType`] to a [`NaiveDateTime`].
///
/// If the DateTimeType is already a NaiveDateTime, it will be returned as is.
/// If the DateTimeType is a DateTime<Local>, it will be converted to a NaiveDateTime.
/// If the [`DateTimeType`] is already a [`NaiveDateTime`], it will be returned as is.
/// If the [`DateTimeType`] is a [`DateTime<Local>`], it will be converted to a [`NaiveDateTime`].
pub fn to_naive(&self) -> NaiveDateTime {
match self {
DateTimeType::Naive(naive) => *naive,
@ -68,13 +68,13 @@ impl FormatDistance {
}
}
/// Calculates the distance in seconds between two NaiveDateTime objects.
/// Calculates the distance in seconds between two [`NaiveDateTime`] objects.
/// It returns a signed integer denoting the difference. If `date` is earlier than `base_date`, the returned value will be negative.
///
/// ## Arguments
///
/// * `date` - A NaiveDateTime object representing the date of interest
/// * `base_date` - A NaiveDateTime object representing the base date against which the comparison is made
/// * `date` - A [NaiveDateTime`] object representing the date of interest
/// * `base_date` - A [NaiveDateTime`] object representing the base date against which the comparison is made
fn distance_in_seconds(date: NaiveDateTime, base_date: NaiveDateTime) -> i64 {
let duration = date.signed_duration_since(base_date);
-duration.num_seconds()
@ -233,29 +233,7 @@ fn distance_string(
///
/// For example, "less than a minute ago", "about 2 hours ago", "3 months from now", etc.
///
/// Use [naive_format_distance_from_now] to compare a NaiveDateTime against now.
///
/// # Arguments
///
/// * `date` - The NaiveDateTime to compare.
/// * `base_date` - The NaiveDateTime to compare against.
/// * `include_seconds` - A boolean. If true, distances less than a minute are more detailed
/// * `add_suffix` - A boolean. If true, result indicates if the time is in the past or future
///
/// # Example
///
/// ```rust
/// use chrono::DateTime;
/// use ui::utils::format_distance;
///
/// fn time_between_moon_landings() -> String {
/// let date = DateTime::parse_from_rfc3339("1969-07-20T00:00:00Z").unwrap().naive_local();
/// let base_date = DateTime::parse_from_rfc3339("1972-12-14T00:00:00Z").unwrap().naive_local();
/// format!("There was {} between the first and last crewed moon landings.", naive_format_distance(date, base_date, false, false))
/// }
/// ```
///
/// Output: `"There was about 3 years between the first and last crewed moon landings."`
/// Use [`format_distance_from_now`] to compare a NaiveDateTime against now.
pub fn format_distance(
date: DateTimeType,
base_date: NaiveDateTime,
@ -271,26 +249,6 @@ pub fn format_distance(
/// Get the time difference between a date and now as relative human readable string.
///
/// For example, "less than a minute ago", "about 2 hours ago", "3 months from now", etc.
///
/// # Arguments
///
/// * `datetime` - The NaiveDateTime to compare with the current time.
/// * `include_seconds` - A boolean. If true, distances less than a minute are more detailed
/// * `add_suffix` - A boolean. If true, result indicates if the time is in the past or future
///
/// # Example
///
/// ```rust
/// use chrono::DateTime;
/// use ui::utils::naive_format_distance_from_now;
///
/// fn time_since_first_moon_landing() -> String {
/// let date = DateTime::parse_from_rfc3339("1969-07-20T00:00:00Z").unwrap().naive_local();
/// format!("It's been {} since Apollo 11 first landed on the moon.", naive_format_distance_from_now(date, false, false))
/// }
/// ```
///
/// Output: `It's been over 54 years since Apollo 11 first landed on the moon.`
pub fn format_distance_from_now(
datetime: DateTimeType,
include_seconds: bool,