use std::fmt::{self, Display, Formatter}; use crate::{ scroll::{Scrollable, ScrollbarAxis}, ActiveTheme, }; use gpui::{ div, px, App, Axis, DefiniteLength, Div, Edges, Element, ElementId, FocusHandle, Pixels, Refineable, StyleRefinement, Styled, Window, }; use serde::{Deserialize, Serialize}; /// Returns a `Div` as horizontal flex layout. #[inline] pub fn h_flex() -> Div { div().h_flex() } /// Returns a `Div` as vertical flex layout. #[inline] pub fn v_flex() -> Div { div().v_flex() } macro_rules! font_weight { ($fn:ident, $const:ident) => { /// [docs](https://tailwindcss.com/docs/font-weight) #[inline] fn $fn(self) -> Self { self.font_weight(gpui::FontWeight::$const) } }; } /// Extends [`gpui::Styled`] with specific styling methods. pub trait StyledExt: Styled + Sized { /// Refine the style of this element, applying the given style refinement. fn refine_style(mut self, style: &StyleRefinement) -> Self { self.style().refine(style); self } /// Apply self into a horizontal flex layout. #[inline] fn h_flex(self) -> Self { self.flex().flex_row().items_center() } /// Apply self into a vertical flex layout. #[inline] fn v_flex(self) -> Self { self.flex().flex_col() } /// Apply paddings to the element. fn paddings(self, paddings: impl Into>) -> Self where L: Into + Clone + Default + std::fmt::Debug + PartialEq, { let paddings = paddings.into(); self.pt(paddings.top.into()) .pb(paddings.bottom.into()) .pl(paddings.left.into()) .pr(paddings.right.into()) } /// Apply margins to the element. fn margins(self, margins: impl Into>) -> Self where L: Into + Clone + Default + std::fmt::Debug + PartialEq, { let margins = margins.into(); self.mt(margins.top.into()) .mb(margins.bottom.into()) .ml(margins.left.into()) .mr(margins.right.into()) } /// Render a border with a width of 1px, color red fn debug_red(self) -> Self { if cfg!(debug_assertions) { self.border_1().border_color(crate::red_500()) } else { self } } /// Render a border with a width of 1px, color blue fn debug_blue(self) -> Self { if cfg!(debug_assertions) { self.border_1().border_color(crate::blue_500()) } else { self } } /// Render a border with a width of 1px, color yellow fn debug_yellow(self) -> Self { if cfg!(debug_assertions) { self.border_1().border_color(crate::yellow_500()) } else { self } } /// Render a border with a width of 1px, color green fn debug_green(self) -> Self { if cfg!(debug_assertions) { self.border_1().border_color(crate::green_500()) } else { self } } /// Render a border with a width of 1px, color pink fn debug_pink(self) -> Self { if cfg!(debug_assertions) { self.border_1().border_color(crate::pink_500()) } else { self } } /// Render a 1px blue border, when if the element is focused fn debug_focused(self, focus_handle: &FocusHandle, window: &Window, cx: &App) -> Self { if cfg!(debug_assertions) { if focus_handle.contains_focused(window, cx) { self.debug_blue() } else { self } } else { self } } /// Render a border with a width of 1px, color ring color #[inline] fn focused_border(self, cx: &App) -> Self { self.border_color(cx.theme().ring) } /// Wraps the element in a ScrollView. /// /// Current this is only have a vertical scrollbar. #[inline] fn scrollable(self, axis: impl Into) -> Scrollable where Self: Element, { Scrollable::new(axis, self) } font_weight!(font_thin, THIN); font_weight!(font_extralight, EXTRA_LIGHT); font_weight!(font_light, LIGHT); font_weight!(font_normal, NORMAL); font_weight!(font_medium, MEDIUM); font_weight!(font_semibold, SEMIBOLD); font_weight!(font_bold, BOLD); font_weight!(font_extrabold, EXTRA_BOLD); font_weight!(font_black, BLACK); /// Set as Popover style #[inline] fn popover_style(self, cx: &mut App) -> Self { self.bg(cx.theme().popover) .border_1() .border_color(cx.theme().border) .shadow_lg() .rounded(cx.theme().radius) } } impl StyledExt for E {} /// A size for elements. #[derive(Clone, Default, Copy, PartialEq, Eq, Debug, Deserialize, Serialize)] pub enum Size { Size(Pixels), XSmall, Small, #[default] Medium, Large, } impl Size { fn as_f32(&self) -> f32 { match self { Size::Size(val) => val.0, Size::XSmall => 0., Size::Small => 1., Size::Medium => 2., Size::Large => 3., } } /// Returns the height for table row. #[inline] pub fn table_row_height(&self) -> Pixels { match self { Size::XSmall => px(26.), Size::Small => px(30.), Size::Large => px(40.), _ => px(32.), } } /// Returns the padding for a table cell. #[inline] pub fn table_cell_padding(&self) -> Edges { match self { Size::XSmall => Edges { top: px(2.), bottom: px(2.), left: px(4.), right: px(4.), }, Size::Small => Edges { top: px(3.), bottom: px(3.), left: px(6.), right: px(6.), }, Size::Large => Edges { top: px(8.), bottom: px(8.), left: px(12.), right: px(12.), }, _ => Edges { top: px(4.), bottom: px(4.), left: px(8.), right: px(8.), }, } } /// Returns a smaller size. pub fn smaller(&self) -> Self { match self { Size::XSmall => Size::XSmall, Size::Small => Size::XSmall, Size::Medium => Size::Small, Size::Large => Size::Medium, Size::Size(val) => Size::Size(*val * 0.2), } } /// Returns a larger size. pub fn larger(&self) -> Self { match self { Size::XSmall => Size::Small, Size::Small => Size::Medium, Size::Medium => Size::Large, Size::Large => Size::Large, Size::Size(val) => Size::Size(*val * 1.2), } } /// Return the max size between two sizes. /// /// e.g. `Size::XSmall.max(Size::Small)` will return `Size::XSmall`. pub fn max(&self, other: Self) -> Self { match (self, other) { (Size::Size(a), Size::Size(b)) => Size::Size(px(a.0.min(b.0))), (Size::Size(a), _) => Size::Size(*a), (_, Size::Size(b)) => Size::Size(b), (a, b) if a.as_f32() < b.as_f32() => *a, _ => other, } } /// Return the min size between two sizes. /// /// e.g. `Size::XSmall.min(Size::Small)` will return `Size::Small`. pub fn min(&self, other: Self) -> Self { match (self, other) { (Size::Size(a), Size::Size(b)) => Size::Size(px(a.0.max(b.0))), (Size::Size(a), _) => Size::Size(*a), (_, Size::Size(b)) => Size::Size(b), (a, b) if a.as_f32() > b.as_f32() => *a, _ => other, } } pub fn input_px(&self) -> Pixels { match self { Self::Large => px(20.), Self::Medium => px(12.), Self::Small => px(8.), Self::XSmall => px(4.), _ => px(8.), } } pub fn input_py(&self) -> Pixels { match self { Size::Large => px(16.), Size::Medium => px(8.), Size::Small => px(4.), Size::XSmall => px(0.), _ => px(4.), } } } impl From for Size { fn from(size: Pixels) -> Self { Size::Size(size) } } /// A trait for defining element that can be selected. pub trait Selectable: Sized { /// Returns the element id of the element. fn element_id(&self) -> &ElementId; /// Set the selected state of the element. fn selected(self, selected: bool) -> Self; /// Returns true if the element is selected. fn is_selected(&self) -> bool; } /// A trait for defining element that can be disabled. pub trait Disableable { /// Set the disabled state of the element. fn disabled(self, disabled: bool) -> Self; } /// A trait for setting the size of an element. /// Size::Medium is use by default. pub trait Sizable: Sized { /// Set the ui::Size of this element. /// /// Also can receive a `ButtonSize` to convert to `IconSize`, /// Or a `Pixels` to set a custom size: `px(30.)` fn with_size(self, size: impl Into) -> Self; /// Set to Size::XSmall fn xsmall(self) -> Self { self.with_size(Size::XSmall) } /// Set to Size::Small fn small(self) -> Self { self.with_size(Size::Small) } /// Set to Size::Large fn large(self) -> Self { self.with_size(Size::Large) } } #[allow(unused)] pub trait StyleSized { fn input_text_size(self, size: Size) -> Self; fn input_size(self, size: Size) -> Self; fn input_pl(self, size: Size) -> Self; fn input_pr(self, size: Size) -> Self; fn input_px(self, size: Size) -> Self; fn input_py(self, size: Size) -> Self; fn input_h(self, size: Size) -> Self; fn list_size(self, size: Size) -> Self; fn list_px(self, size: Size) -> Self; fn list_py(self, size: Size) -> Self; /// Apply size with the given `Size`. fn size_with(self, size: Size) -> Self; /// Apply the table cell size (Font size, padding) with the given `Size`. fn table_cell_size(self, size: Size) -> Self; fn button_text_size(self, size: Size) -> Self; } impl StyleSized for T { #[inline] fn input_text_size(self, size: Size) -> Self { match size { Size::XSmall => self.text_xs(), Size::Small => self.text_sm(), Size::Medium => self.text_base(), Size::Large => self.text_lg(), Size::Size(size) => self.text_size(size), } } #[inline] fn input_size(self, size: Size) -> Self { self.input_px(size).input_py(size).input_h(size) } #[inline] fn input_pl(self, size: Size) -> Self { self.pl(size.input_px()) } #[inline] fn input_pr(self, size: Size) -> Self { self.pr(size.input_px()) } #[inline] fn input_px(self, size: Size) -> Self { self.px(size.input_px()) } #[inline] fn input_py(self, size: Size) -> Self { self.py(size.input_py()) } #[inline] fn input_h(self, size: Size) -> Self { match size { Size::Large => self.h_11(), Size::Medium => self.h_8(), Size::Small => self.h(px(26.)), Size::XSmall => self.h(px(20.)), _ => self.h(px(26.)), } .input_text_size(size) } #[inline] fn list_size(self, size: Size) -> Self { self.list_px(size).list_py(size).input_text_size(size) } #[inline] fn list_px(self, size: Size) -> Self { match size { Size::Small => self.px_2(), _ => self.px_3(), } } #[inline] fn list_py(self, size: Size) -> Self { match size { Size::Large => self.py_2(), Size::Medium => self.py_1(), Size::Small => self.py_0p5(), _ => self.py_1(), } } #[inline] fn size_with(self, size: Size) -> Self { match size { Size::Large => self.size_11(), Size::Medium => self.size_8(), Size::Small => self.size_5(), Size::XSmall => self.size_4(), Size::Size(size) => self.size(size), } } #[inline] fn table_cell_size(self, size: Size) -> Self { let padding = size.table_cell_padding(); match size { Size::XSmall => self.text_sm(), Size::Small => self.text_sm(), _ => self, } .pl(padding.left) .pr(padding.right) .pt(padding.top) .pb(padding.bottom) } fn button_text_size(self, size: Size) -> Self { match size { Size::XSmall => self.text_xs(), Size::Small => self.text_sm(), _ => self.text_base(), } } } pub trait AxisExt { fn is_horizontal(self) -> bool; fn is_vertical(self) -> bool; } impl AxisExt for Axis { #[inline] fn is_horizontal(self) -> bool { self == Axis::Horizontal } #[inline] fn is_vertical(self) -> bool { self == Axis::Vertical } } #[derive(Clone, Copy, PartialEq, Eq, Debug)] pub enum Placement { Top, Bottom, Left, Right, } impl Display for Placement { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { match self { Placement::Top => write!(f, "Top"), Placement::Bottom => write!(f, "Bottom"), Placement::Left => write!(f, "Left"), Placement::Right => write!(f, "Right"), } } } impl Placement { #[inline] pub fn is_horizontal(&self) -> bool { match self { Placement::Left | Placement::Right => true, _ => false, } } #[inline] pub fn is_vertical(&self) -> bool { match self { Placement::Top | Placement::Bottom => true, _ => false, } } #[inline] pub fn axis(&self) -> Axis { match self { Placement::Top | Placement::Bottom => Axis::Vertical, Placement::Left | Placement::Right => Axis::Horizontal, } } } /// A enum for defining the side of the element. #[derive(Clone, Copy, PartialEq, Eq, Debug)] pub enum Side { Left, Right, } impl Side { /// Returns true if the side is left. #[inline] pub fn is_left(&self) -> bool { matches!(self, Self::Left) } /// Returns true if the side is right. #[inline] pub fn is_right(&self) -> bool { matches!(self, Self::Right) } } /// A trait for defining element that can be collapsed. pub trait Collapsible { fn collapsed(self, collapsed: bool) -> Self; fn is_collapsed(&self) -> bool; } #[cfg(test)] mod tests { use gpui::px; use crate::Size; #[test] fn test_size_max_min() { assert_eq!(Size::Small.min(Size::XSmall), Size::Small); assert_eq!(Size::XSmall.min(Size::Small), Size::Small); assert_eq!(Size::Small.min(Size::Medium), Size::Medium); assert_eq!(Size::Medium.min(Size::Large), Size::Large); assert_eq!(Size::Large.min(Size::Small), Size::Large); assert_eq!( Size::Size(px(10.)).min(Size::Size(px(20.))), Size::Size(px(20.)) ); // Min assert_eq!(Size::Small.max(Size::XSmall), Size::XSmall); assert_eq!(Size::XSmall.max(Size::Small), Size::XSmall); assert_eq!(Size::Small.max(Size::Medium), Size::Small); assert_eq!(Size::Medium.max(Size::Large), Size::Medium); assert_eq!(Size::Large.max(Size::Small), Size::Small); assert_eq!( Size::Size(px(10.)).max(Size::Size(px(20.))), Size::Size(px(10.)) ); } }