add sizable trait
This commit is contained in:
@@ -6,9 +6,9 @@ use gpui::{
|
|||||||
RenderOnce, Role, SharedString, StatefulInteractiveElement as _, StyleRefinement, Styled as _,
|
RenderOnce, Role, SharedString, StatefulInteractiveElement as _, StyleRefinement, Styled as _,
|
||||||
Window, div,
|
Window, div,
|
||||||
};
|
};
|
||||||
use gpui_native_theme::{ActiveTheme as _, Oklch, ThemeVariant};
|
use gpui_native_theme::{ActiveTheme as _, Oklch, Size, ThemeVariant};
|
||||||
|
|
||||||
use crate::common::h_flex;
|
use crate::common::{Sizable, h_flex};
|
||||||
|
|
||||||
/// Number of rows and columns in the pixel grid.
|
/// Number of rows and columns in the pixel grid.
|
||||||
const GRID_SIZE: usize = 8;
|
const GRID_SIZE: usize = 8;
|
||||||
@@ -27,6 +27,7 @@ pub struct Avatar {
|
|||||||
id: SharedString,
|
id: SharedString,
|
||||||
name: Option<SharedString>,
|
name: Option<SharedString>,
|
||||||
picture: Option<SharedString>,
|
picture: Option<SharedString>,
|
||||||
|
size: Size,
|
||||||
style: StyleRefinement,
|
style: StyleRefinement,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -42,6 +43,7 @@ impl Avatar {
|
|||||||
id: id.to_string().into(),
|
id: id.to_string().into(),
|
||||||
name: None,
|
name: None,
|
||||||
picture: None,
|
picture: None,
|
||||||
|
size: Size::default(),
|
||||||
style: StyleRefinement::default(),
|
style: StyleRefinement::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -65,6 +67,12 @@ impl Avatar {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Sizable for Avatar {
|
||||||
|
fn apply_size(&mut self, size: Size) {
|
||||||
|
self.size = size;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl gpui::Styled for Avatar {
|
impl gpui::Styled for Avatar {
|
||||||
fn style(&mut self) -> &mut gpui::StyleRefinement {
|
fn style(&mut self) -> &mut gpui::StyleRefinement {
|
||||||
&mut self.style
|
&mut self.style
|
||||||
@@ -75,11 +83,15 @@ impl RenderOnce for Avatar {
|
|||||||
fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
|
fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
|
||||||
let theme = cx.theme();
|
let theme = cx.theme();
|
||||||
let radius = theme.radius.control;
|
let radius = theme.radius.control;
|
||||||
|
let side = theme.sizes.resolve(self.size).avatar_size;
|
||||||
|
|
||||||
let mut base = h_flex()
|
let mut base = h_flex()
|
||||||
.id(ElementId::from(self.id.clone()))
|
.id(ElementId::from(self.id.clone()))
|
||||||
.flex_none()
|
.flex_none()
|
||||||
|
.justify_center()
|
||||||
.overflow_hidden()
|
.overflow_hidden()
|
||||||
|
.size(side)
|
||||||
|
.text_size(side * 0.44)
|
||||||
.bg(theme.colors.secondary)
|
.bg(theme.colors.secondary)
|
||||||
.rounded(radius)
|
.rounded(radius)
|
||||||
.when_some(
|
.when_some(
|
||||||
@@ -91,13 +103,14 @@ impl RenderOnce for Avatar {
|
|||||||
base.style().refine(&self.style);
|
base.style().refine(&self.style);
|
||||||
|
|
||||||
match self.picture {
|
match self.picture {
|
||||||
Some(url) => base.size_8().child(
|
Some(url) => base.child(
|
||||||
gpui::img(gpui::SharedUri::from(url))
|
gpui::img(gpui::SharedUri::from(url))
|
||||||
.size_full()
|
.size_full()
|
||||||
.rounded(radius),
|
.rounded(radius),
|
||||||
),
|
),
|
||||||
None => base.size_8().child(PixelAvatar {
|
None => base.child(PixelAvatar {
|
||||||
seed: fnv1a(self.id.as_bytes()),
|
seed: fnv1a(self.id.as_bytes()),
|
||||||
|
side,
|
||||||
style: self.style,
|
style: self.style,
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
@@ -109,6 +122,7 @@ impl RenderOnce for Avatar {
|
|||||||
#[derive(IntoElement)]
|
#[derive(IntoElement)]
|
||||||
struct PixelAvatar {
|
struct PixelAvatar {
|
||||||
seed: u64,
|
seed: u64,
|
||||||
|
side: gpui::Pixels,
|
||||||
style: StyleRefinement,
|
style: StyleRefinement,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -164,7 +178,7 @@ impl RenderOnce for PixelAvatar {
|
|||||||
.grid()
|
.grid()
|
||||||
.grid_cols(GRID_SIZE as u16)
|
.grid_cols(GRID_SIZE as u16)
|
||||||
.grid_rows(GRID_SIZE as u16)
|
.grid_rows(GRID_SIZE as u16)
|
||||||
.size_8()
|
.size(self.side)
|
||||||
.flex_none()
|
.flex_none()
|
||||||
.rounded(radius)
|
.rounded(radius)
|
||||||
.overflow_hidden()
|
.overflow_hidden()
|
||||||
|
|||||||
@@ -4,12 +4,12 @@ use gpui::prelude::FluentBuilder as _;
|
|||||||
use gpui::{
|
use gpui::{
|
||||||
App, BoxShadow, ClickEvent, ElementId, InteractiveElement as _, IntoElement,
|
App, BoxShadow, ClickEvent, ElementId, InteractiveElement as _, IntoElement,
|
||||||
ParentElement as _, RenderOnce, Role, SharedString, StatefulInteractiveElement as _,
|
ParentElement as _, RenderOnce, Role, SharedString, StatefulInteractiveElement as _,
|
||||||
Styled as _, Window, px,
|
Styled as _, Window, px, relative,
|
||||||
};
|
};
|
||||||
use gpui_native_icons::{IconRole, icon};
|
use gpui_native_icons::{IconRole, icon};
|
||||||
use gpui_native_theme::ActiveTheme as _;
|
use gpui_native_theme::{ActiveTheme as _, Size};
|
||||||
|
|
||||||
use crate::common::h_flex;
|
use crate::common::{Sizable, h_flex};
|
||||||
|
|
||||||
/// The single activation handler, shared by pointer, Enter, and Space.
|
/// The single activation handler, shared by pointer, Enter, and Space.
|
||||||
type ClickHandler = Box<dyn Fn(&ClickEvent, &mut Window, &mut App)>;
|
type ClickHandler = Box<dyn Fn(&ClickEvent, &mut Window, &mut App)>;
|
||||||
@@ -35,6 +35,7 @@ pub struct Button {
|
|||||||
label: Option<SharedString>,
|
label: Option<SharedString>,
|
||||||
icon: Option<IconRole>,
|
icon: Option<IconRole>,
|
||||||
variant: ButtonVariant,
|
variant: ButtonVariant,
|
||||||
|
size: Size,
|
||||||
disabled: bool,
|
disabled: bool,
|
||||||
a11y_label: Option<SharedString>,
|
a11y_label: Option<SharedString>,
|
||||||
on_click: Option<ClickHandler>,
|
on_click: Option<ClickHandler>,
|
||||||
@@ -51,6 +52,7 @@ impl Button {
|
|||||||
label: None,
|
label: None,
|
||||||
icon: None,
|
icon: None,
|
||||||
variant: ButtonVariant::default(),
|
variant: ButtonVariant::default(),
|
||||||
|
size: Size::default(),
|
||||||
disabled: false,
|
disabled: false,
|
||||||
a11y_label: None,
|
a11y_label: None,
|
||||||
on_click: None,
|
on_click: None,
|
||||||
@@ -103,11 +105,18 @@ impl Button {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Sizable for Button {
|
||||||
|
fn apply_size(&mut self, size: Size) {
|
||||||
|
self.size = size;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl RenderOnce for Button {
|
impl RenderOnce for Button {
|
||||||
fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
|
fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
|
||||||
let theme = cx.theme();
|
let theme = cx.theme();
|
||||||
let colors = theme.colors;
|
let colors = theme.colors;
|
||||||
let ramps = theme.ramps;
|
let ramps = theme.ramps;
|
||||||
|
let metrics = theme.sizes.resolve(self.size);
|
||||||
|
|
||||||
let on_click = self.on_click;
|
let on_click = self.on_click;
|
||||||
let a11y_label = self.a11y_label.or_else(|| self.label.clone());
|
let a11y_label = self.a11y_label.or_else(|| self.label.clone());
|
||||||
@@ -143,11 +152,12 @@ impl RenderOnce for Button {
|
|||||||
.id(self.id)
|
.id(self.id)
|
||||||
.role(Role::Button)
|
.role(Role::Button)
|
||||||
.justify_center()
|
.justify_center()
|
||||||
.h_7()
|
.h(metrics.control_height)
|
||||||
.px_4()
|
.px(metrics.padding_x)
|
||||||
|
.line_height(relative(1.0))
|
||||||
.gap_1p5()
|
.gap_1p5()
|
||||||
.rounded(theme.radius.control)
|
.rounded(theme.radius.control)
|
||||||
.text_sm()
|
.text_size(metrics.text_size)
|
||||||
.when(self.variant == ButtonVariant::Secondary, |button| {
|
.when(self.variant == ButtonVariant::Secondary, |button| {
|
||||||
button.border_1().border_color(colors.border)
|
button.border_1().border_color(colors.border)
|
||||||
})
|
})
|
||||||
@@ -155,7 +165,11 @@ impl RenderOnce for Button {
|
|||||||
.text_color(foreground)
|
.text_color(foreground)
|
||||||
.when_some(a11y_label, |button, label| button.aria_label(label))
|
.when_some(a11y_label, |button, label| button.aria_label(label))
|
||||||
.when_some(self.icon, |button, role| {
|
.when_some(self.icon, |button, role| {
|
||||||
button.child(icon(role).color(foreground))
|
button.child(
|
||||||
|
icon(role)
|
||||||
|
.size(f32::from(metrics.icon_size))
|
||||||
|
.color(foreground),
|
||||||
|
)
|
||||||
})
|
})
|
||||||
.when_some(self.label, |button, label| button.child(label))
|
.when_some(self.label, |button, label| button.child(label))
|
||||||
.when(self.disabled, |button| button.opacity(0.5))
|
.when(self.disabled, |button| button.opacity(0.5))
|
||||||
|
|||||||
@@ -7,19 +7,20 @@ use gpui::{
|
|||||||
px,
|
px,
|
||||||
};
|
};
|
||||||
use gpui_native_icons::{IconRole, icon};
|
use gpui_native_icons::{IconRole, icon};
|
||||||
use gpui_native_theme::ActiveTheme as _;
|
use gpui_native_theme::{ActiveTheme as _, Size};
|
||||||
|
|
||||||
use crate::common::h_flex;
|
use crate::common::{Sizable, h_flex};
|
||||||
|
|
||||||
/// The change reporter, fired with the next value by pointer, Enter, and Space.
|
/// The change reporter, fired with the next value by pointer, Enter, and Space.
|
||||||
type ChangeHandler = Box<dyn Fn(bool, &mut Window, &mut App)>;
|
type ChangeHandler = Box<dyn Fn(bool, &mut Window, &mut App)>;
|
||||||
|
|
||||||
/// Checkbox component.
|
/// A controlled checkbox component.
|
||||||
#[derive(IntoElement)]
|
#[derive(IntoElement)]
|
||||||
pub struct Checkbox {
|
pub struct Checkbox {
|
||||||
id: ElementId,
|
id: ElementId,
|
||||||
label: Option<SharedString>,
|
label: Option<SharedString>,
|
||||||
checked: bool,
|
checked: bool,
|
||||||
|
size: Size,
|
||||||
disabled: bool,
|
disabled: bool,
|
||||||
a11y_label: Option<SharedString>,
|
a11y_label: Option<SharedString>,
|
||||||
on_change: Option<ChangeHandler>,
|
on_change: Option<ChangeHandler>,
|
||||||
@@ -35,6 +36,7 @@ impl Checkbox {
|
|||||||
id: id.into(),
|
id: id.into(),
|
||||||
label: None,
|
label: None,
|
||||||
checked: false,
|
checked: false,
|
||||||
|
size: Size::default(),
|
||||||
disabled: false,
|
disabled: false,
|
||||||
a11y_label: None,
|
a11y_label: None,
|
||||||
on_change: None,
|
on_change: None,
|
||||||
@@ -81,20 +83,27 @@ impl Checkbox {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Sizable for Checkbox {
|
||||||
|
fn apply_size(&mut self, size: Size) {
|
||||||
|
self.size = size;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl RenderOnce for Checkbox {
|
impl RenderOnce for Checkbox {
|
||||||
fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
|
fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
|
||||||
let theme = cx.theme();
|
let theme = cx.theme();
|
||||||
let colors = theme.colors;
|
let colors = theme.colors;
|
||||||
let ramps = theme.ramps;
|
let ramps = theme.ramps;
|
||||||
|
let metrics = theme.sizes.resolve(self.size);
|
||||||
let on_change = self.on_change;
|
let on_change = self.on_change;
|
||||||
let next = !self.checked;
|
let next = !self.checked;
|
||||||
let a11y_label = self.a11y_label.or_else(|| self.label.clone());
|
let a11y_label = self.a11y_label.or_else(|| self.label.clone());
|
||||||
|
|
||||||
h_flex()
|
h_flex()
|
||||||
.id(self.id)
|
.id(self.id)
|
||||||
.role(Role::CheckBox)
|
|
||||||
.gap_1p5()
|
.gap_1p5()
|
||||||
.text_sm()
|
.text_size(metrics.text_size)
|
||||||
|
.role(Role::CheckBox)
|
||||||
.when_some(a11y_label, |checkbox, label| checkbox.aria_label(label))
|
.when_some(a11y_label, |checkbox, label| checkbox.aria_label(label))
|
||||||
.aria_toggled(Toggled::from(self.checked))
|
.aria_toggled(Toggled::from(self.checked))
|
||||||
.when(self.disabled, |checkbox| checkbox.opacity(0.5))
|
.when(self.disabled, |checkbox| checkbox.opacity(0.5))
|
||||||
@@ -118,7 +127,7 @@ impl RenderOnce for Checkbox {
|
|||||||
h_flex()
|
h_flex()
|
||||||
.justify_center()
|
.justify_center()
|
||||||
.flex_none()
|
.flex_none()
|
||||||
.size_4()
|
.size(metrics.icon_size)
|
||||||
.rounded(theme.radius.sm)
|
.rounded(theme.radius.sm)
|
||||||
.border_1()
|
.border_1()
|
||||||
.when(self.checked, |indicator| {
|
.when(self.checked, |indicator| {
|
||||||
|
|||||||
@@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
use gpui::{Div, Styled as _, div};
|
use gpui::{Div, Styled as _, div};
|
||||||
|
|
||||||
|
pub use gpui_native_theme::Size;
|
||||||
|
|
||||||
/// A row whose children center on the cross axis.
|
/// A row whose children center on the cross axis.
|
||||||
pub fn h_flex() -> Div {
|
pub fn h_flex() -> Div {
|
||||||
div().flex().flex_row().items_center()
|
div().flex().flex_row().items_center()
|
||||||
@@ -11,3 +13,31 @@ pub fn h_flex() -> Div {
|
|||||||
pub fn v_flex() -> Div {
|
pub fn v_flex() -> Div {
|
||||||
div().flex().flex_col()
|
div().flex().flex_col()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A component that can be told which size to render at.
|
||||||
|
pub trait Sizable: Sized {
|
||||||
|
/// Sets the size.
|
||||||
|
fn with_size(mut self, size: Size) -> Self {
|
||||||
|
self.apply_size(size);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Applies the size to the component's fields.
|
||||||
|
fn apply_size(&mut self, size: Size);
|
||||||
|
|
||||||
|
/// Sets `Size::Small`.
|
||||||
|
fn small(self) -> Self
|
||||||
|
where
|
||||||
|
Self: Sized,
|
||||||
|
{
|
||||||
|
self.with_size(Size::Small)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets `Size::Large`.
|
||||||
|
fn large(self) -> Self
|
||||||
|
where
|
||||||
|
Self: Sized,
|
||||||
|
{
|
||||||
|
self.with_size(Size::Large)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -12,9 +12,9 @@ use gpui::{
|
|||||||
SharedString, StatefulInteractiveElement as _, Styled as _, TextAlign, TextRun, UTF16Selection,
|
SharedString, StatefulInteractiveElement as _, Styled as _, TextAlign, TextRun, UTF16Selection,
|
||||||
Window, canvas, div, fill, point, px,
|
Window, canvas, div, fill, point, px,
|
||||||
};
|
};
|
||||||
use gpui_native_theme::ActiveTheme as _;
|
use gpui_native_theme::{ActiveTheme as _, Size};
|
||||||
|
|
||||||
use crate::common::h_flex;
|
use crate::common::{Sizable, h_flex};
|
||||||
|
|
||||||
/// Input layout information for hit testing and caret positioning.
|
/// Input layout information for hit testing and caret positioning.
|
||||||
struct InputLayout {
|
struct InputLayout {
|
||||||
@@ -374,6 +374,7 @@ pub struct Input {
|
|||||||
id: ElementId,
|
id: ElementId,
|
||||||
state: Entity<InputState>,
|
state: Entity<InputState>,
|
||||||
placeholder: SharedString,
|
placeholder: SharedString,
|
||||||
|
size: Size,
|
||||||
disabled: bool,
|
disabled: bool,
|
||||||
a11y_label: Option<SharedString>,
|
a11y_label: Option<SharedString>,
|
||||||
}
|
}
|
||||||
@@ -388,6 +389,7 @@ impl Input {
|
|||||||
id: id.into(),
|
id: id.into(),
|
||||||
state,
|
state,
|
||||||
placeholder: SharedString::default(),
|
placeholder: SharedString::default(),
|
||||||
|
size: Size::default(),
|
||||||
disabled: false,
|
disabled: false,
|
||||||
a11y_label: None,
|
a11y_label: None,
|
||||||
}
|
}
|
||||||
@@ -418,11 +420,18 @@ impl Input {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Sizable for Input {
|
||||||
|
fn apply_size(&mut self, size: Size) {
|
||||||
|
self.size = size;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl RenderOnce for Input {
|
impl RenderOnce for Input {
|
||||||
fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
|
fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
|
||||||
let theme = cx.theme();
|
let theme = cx.theme();
|
||||||
let colors = theme.colors;
|
let colors = theme.colors;
|
||||||
let radius = theme.radius;
|
let radius = theme.radius;
|
||||||
|
let metrics = theme.sizes.resolve(self.size);
|
||||||
let focus_handle = self.state.read(cx).focus_handle.clone();
|
let focus_handle = self.state.read(cx).focus_handle.clone();
|
||||||
let layout_cell = self.state.read(cx).layout.clone();
|
let layout_cell = self.state.read(cx).layout.clone();
|
||||||
|
|
||||||
@@ -447,19 +456,19 @@ impl RenderOnce for Input {
|
|||||||
|
|
||||||
h_flex()
|
h_flex()
|
||||||
.id(self.id.clone())
|
.id(self.id.clone())
|
||||||
|
.role(Role::TextInput)
|
||||||
|
.aria_label(fallback_label)
|
||||||
.relative()
|
.relative()
|
||||||
.overflow_hidden()
|
.overflow_hidden()
|
||||||
.w_full()
|
.w_full()
|
||||||
.h_7()
|
.h(metrics.input_height)
|
||||||
.px_2()
|
.px(metrics.padding_x / 2.0)
|
||||||
.rounded(radius.control)
|
.rounded(radius.control)
|
||||||
.border_1()
|
.border_1()
|
||||||
.border_color(colors.border)
|
.border_color(colors.border)
|
||||||
.bg(colors.input)
|
.bg(colors.input)
|
||||||
.text_sm()
|
.text_size(metrics.text_size)
|
||||||
.text_color(colors.foreground)
|
.text_color(colors.foreground)
|
||||||
.role(Role::TextInput)
|
|
||||||
.aria_label(fallback_label)
|
|
||||||
.when(self.disabled, |input| input.opacity(0.5))
|
.when(self.disabled, |input| input.opacity(0.5))
|
||||||
.when(!self.disabled, |input| {
|
.when(!self.disabled, |input| {
|
||||||
input.track_focus(&focus_handle).tab_stop(true)
|
input.track_focus(&focus_handle).tab_stop(true)
|
||||||
@@ -506,6 +515,7 @@ impl RenderOnce for Input {
|
|||||||
let layout_cell = layout_cell.clone();
|
let layout_cell = layout_cell.clone();
|
||||||
let focus = focus_handle.clone();
|
let focus = focus_handle.clone();
|
||||||
let dragging = paint_dragging;
|
let dragging = paint_dragging;
|
||||||
|
let padding = metrics.padding_x / 2.0;
|
||||||
let disabled = self.disabled;
|
let disabled = self.disabled;
|
||||||
// `line` comes from the prepaint closure above.
|
// `line` comes from the prepaint closure above.
|
||||||
move |bounds, line, window, cx| {
|
move |bounds, line, window, cx| {
|
||||||
@@ -519,7 +529,6 @@ impl RenderOnce for Input {
|
|||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
let padding = px(8.);
|
|
||||||
let selection = state.read(cx).selection();
|
let selection = state.read(cx).selection();
|
||||||
let caret_x = line.x_for_index(selection.end);
|
let caret_x = line.x_for_index(selection.end);
|
||||||
|
|
||||||
|
|||||||
+13
-3
@@ -4,9 +4,9 @@ use gpui::{
|
|||||||
ParentElement as _, RenderOnce, Role, SharedString, StatefulInteractiveElement as _,
|
ParentElement as _, RenderOnce, Role, SharedString, StatefulInteractiveElement as _,
|
||||||
Styled as _, Window, px,
|
Styled as _, Window, px,
|
||||||
};
|
};
|
||||||
use gpui_native_theme::ActiveTheme as _;
|
use gpui_native_theme::{ActiveTheme as _, Size};
|
||||||
|
|
||||||
use crate::common::h_flex;
|
use crate::common::{Sizable, h_flex};
|
||||||
|
|
||||||
/// The click handler. When set it replaces the default `href` opening.
|
/// The click handler. When set it replaces the default `href` opening.
|
||||||
type ClickHandler = Box<dyn Fn(&ClickEvent, &mut Window, &mut App)>;
|
type ClickHandler = Box<dyn Fn(&ClickEvent, &mut Window, &mut App)>;
|
||||||
@@ -17,6 +17,7 @@ pub struct Link {
|
|||||||
id: ElementId,
|
id: ElementId,
|
||||||
label: Option<SharedString>,
|
label: Option<SharedString>,
|
||||||
href: Option<SharedString>,
|
href: Option<SharedString>,
|
||||||
|
size: Size,
|
||||||
disabled: bool,
|
disabled: bool,
|
||||||
a11y_label: Option<SharedString>,
|
a11y_label: Option<SharedString>,
|
||||||
on_click: Option<ClickHandler>,
|
on_click: Option<ClickHandler>,
|
||||||
@@ -32,6 +33,7 @@ impl Link {
|
|||||||
id: id.into(),
|
id: id.into(),
|
||||||
label: None,
|
label: None,
|
||||||
href: None,
|
href: None,
|
||||||
|
size: Size::default(),
|
||||||
disabled: false,
|
disabled: false,
|
||||||
a11y_label: None,
|
a11y_label: None,
|
||||||
on_click: None,
|
on_click: None,
|
||||||
@@ -81,18 +83,26 @@ impl Link {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Sizable for Link {
|
||||||
|
fn apply_size(&mut self, size: Size) {
|
||||||
|
self.size = size;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl RenderOnce for Link {
|
impl RenderOnce for Link {
|
||||||
fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
|
fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
|
||||||
let theme = cx.theme();
|
let theme = cx.theme();
|
||||||
let colors = theme.colors;
|
let colors = theme.colors;
|
||||||
let ramps = theme.ramps;
|
let ramps = theme.ramps;
|
||||||
|
let metrics = theme.sizes.resolve(self.size);
|
||||||
|
|
||||||
let on_click = self.on_click;
|
let on_click = self.on_click;
|
||||||
let href = self.href;
|
let href = self.href;
|
||||||
let a11y_label = self.a11y_label.or_else(|| self.label.clone());
|
let a11y_label = self.a11y_label.or_else(|| self.label.clone());
|
||||||
|
|
||||||
h_flex()
|
h_flex()
|
||||||
.id(self.id)
|
.id(self.id)
|
||||||
.text_sm()
|
.text_size(metrics.text_size)
|
||||||
.text_color(colors.accent)
|
.text_color(colors.accent)
|
||||||
.underline()
|
.underline()
|
||||||
.role(Role::Link)
|
.role(Role::Link)
|
||||||
|
|||||||
@@ -8,7 +8,9 @@ use gpui::{
|
|||||||
ParentElement as _, RenderOnce, Role, SharedString, StatefulInteractiveElement as _,
|
ParentElement as _, RenderOnce, Role, SharedString, StatefulInteractiveElement as _,
|
||||||
Styled as _, Window, div, relative,
|
Styled as _, Window, div, relative,
|
||||||
};
|
};
|
||||||
use gpui_native_theme::ActiveTheme as _;
|
use gpui_native_theme::{ActiveTheme as _, Size};
|
||||||
|
|
||||||
|
use crate::common::Sizable;
|
||||||
|
|
||||||
/// A progress bar. With a `value` in `0..=1` it paints a determinate fill.
|
/// A progress bar. With a `value` in `0..=1` it paints a determinate fill.
|
||||||
#[derive(IntoElement)]
|
#[derive(IntoElement)]
|
||||||
@@ -16,6 +18,7 @@ pub struct Progress {
|
|||||||
id: ElementId,
|
id: ElementId,
|
||||||
value: f32,
|
value: f32,
|
||||||
indeterminate: bool,
|
indeterminate: bool,
|
||||||
|
size: Size,
|
||||||
a11y_label: Option<SharedString>,
|
a11y_label: Option<SharedString>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -29,6 +32,7 @@ impl Progress {
|
|||||||
id: id.into(),
|
id: id.into(),
|
||||||
value,
|
value,
|
||||||
indeterminate: false,
|
indeterminate: false,
|
||||||
|
size: Size::default(),
|
||||||
a11y_label: None,
|
a11y_label: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -49,12 +53,21 @@ impl Progress {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Sizable for Progress {
|
||||||
|
fn apply_size(&mut self, size: Size) {
|
||||||
|
self.size = size;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl RenderOnce for Progress {
|
impl RenderOnce for Progress {
|
||||||
fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
|
fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
|
||||||
let theme = cx.theme();
|
let theme = cx.theme();
|
||||||
let colors = theme.colors;
|
let colors = theme.colors;
|
||||||
let ramps = theme.ramps;
|
let ramps = theme.ramps;
|
||||||
let radius = theme.radius;
|
let radius = theme.radius;
|
||||||
|
let metrics = theme.sizes.resolve(self.size);
|
||||||
|
|
||||||
|
let track_height = metrics.icon_size / 4.0;
|
||||||
let value = self.value.clamp(0.0, 1.0);
|
let value = self.value.clamp(0.0, 1.0);
|
||||||
|
|
||||||
div()
|
div()
|
||||||
@@ -73,7 +86,7 @@ impl RenderOnce for Progress {
|
|||||||
.child(
|
.child(
|
||||||
div()
|
div()
|
||||||
.w_full()
|
.w_full()
|
||||||
.h_1p5()
|
.h(track_height)
|
||||||
.rounded(radius.full)
|
.rounded(radius.full)
|
||||||
.bg(colors.border)
|
.bg(colors.border)
|
||||||
.child(if self.indeterminate {
|
.child(if self.indeterminate {
|
||||||
|
|||||||
@@ -4,9 +4,9 @@ use gpui::{
|
|||||||
RenderOnce, Role, SharedString, StatefulInteractiveElement as _, Styled as _, Toggled, Window,
|
RenderOnce, Role, SharedString, StatefulInteractiveElement as _, Styled as _, Toggled, Window,
|
||||||
div, px,
|
div, px,
|
||||||
};
|
};
|
||||||
use gpui_native_theme::ActiveTheme as _;
|
use gpui_native_theme::{ActiveTheme as _, Size};
|
||||||
|
|
||||||
use crate::common::{h_flex, v_flex};
|
use crate::common::{Sizable, h_flex, v_flex};
|
||||||
|
|
||||||
/// The group's change reporter, fired with the clicked radio's value.
|
/// The group's change reporter, fired with the clicked radio's value.
|
||||||
type ChangeHandler<V> = std::rc::Rc<dyn Fn(V, &mut Window, &mut App)>;
|
type ChangeHandler<V> = std::rc::Rc<dyn Fn(V, &mut Window, &mut App)>;
|
||||||
@@ -72,6 +72,7 @@ where
|
|||||||
id: ElementId,
|
id: ElementId,
|
||||||
selected: Option<V>,
|
selected: Option<V>,
|
||||||
children: Vec<Radio<V>>,
|
children: Vec<Radio<V>>,
|
||||||
|
size: Size,
|
||||||
a11y_label: Option<SharedString>,
|
a11y_label: Option<SharedString>,
|
||||||
on_change: Option<ChangeHandler<V>>,
|
on_change: Option<ChangeHandler<V>>,
|
||||||
}
|
}
|
||||||
@@ -89,6 +90,7 @@ where
|
|||||||
id: id.into(),
|
id: id.into(),
|
||||||
selected: None,
|
selected: None,
|
||||||
children: Vec::new(),
|
children: Vec::new(),
|
||||||
|
size: Size::default(),
|
||||||
a11y_label: None,
|
a11y_label: None,
|
||||||
on_change: None,
|
on_change: None,
|
||||||
}
|
}
|
||||||
@@ -125,6 +127,15 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<V> Sizable for RadioGroup<V>
|
||||||
|
where
|
||||||
|
V: Clone + PartialEq + 'static,
|
||||||
|
{
|
||||||
|
fn apply_size(&mut self, size: Size) {
|
||||||
|
self.size = size;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<V> RenderOnce for RadioGroup<V>
|
impl<V> RenderOnce for RadioGroup<V>
|
||||||
where
|
where
|
||||||
V: Clone + PartialEq + 'static,
|
V: Clone + PartialEq + 'static,
|
||||||
@@ -134,6 +145,7 @@ where
|
|||||||
let colors = theme.colors;
|
let colors = theme.colors;
|
||||||
let ramps = theme.ramps;
|
let ramps = theme.ramps;
|
||||||
let radius = theme.radius;
|
let radius = theme.radius;
|
||||||
|
let metrics = theme.sizes.resolve(self.size);
|
||||||
let selected = self.selected;
|
let selected = self.selected;
|
||||||
let on_change = self.on_change;
|
let on_change = self.on_change;
|
||||||
let group_label = self.a11y_label;
|
let group_label = self.a11y_label;
|
||||||
@@ -141,7 +153,7 @@ where
|
|||||||
v_flex()
|
v_flex()
|
||||||
.id(self.id)
|
.id(self.id)
|
||||||
.gap_2()
|
.gap_2()
|
||||||
.text_sm()
|
.text_size(metrics.text_size)
|
||||||
.role(Role::RadioGroup)
|
.role(Role::RadioGroup)
|
||||||
.when_some(group_label.clone(), |group, label| group.aria_label(label))
|
.when_some(group_label.clone(), |group, label| group.aria_label(label))
|
||||||
.children(self.children.into_iter().map(move |radio| {
|
.children(self.children.into_iter().map(move |radio| {
|
||||||
@@ -182,7 +194,7 @@ where
|
|||||||
h_flex()
|
h_flex()
|
||||||
.justify_center()
|
.justify_center()
|
||||||
.flex_none()
|
.flex_none()
|
||||||
.size_4()
|
.size(metrics.icon_size)
|
||||||
.rounded(radius.full)
|
.rounded(radius.full)
|
||||||
.border_1()
|
.border_1()
|
||||||
.when(is_selected, |indicator| {
|
.when(is_selected, |indicator| {
|
||||||
@@ -192,7 +204,7 @@ where
|
|||||||
.hover(|style| style.bg(ramps.primary.hover))
|
.hover(|style| style.bg(ramps.primary.hover))
|
||||||
.child(
|
.child(
|
||||||
div()
|
div()
|
||||||
.size_2()
|
.size(metrics.icon_size / 2.0)
|
||||||
.rounded(radius.full)
|
.rounded(radius.full)
|
||||||
.bg(colors.primary_foreground),
|
.bg(colors.primary_foreground),
|
||||||
)
|
)
|
||||||
|
|||||||
+23
-10
@@ -6,11 +6,11 @@ use gpui::{
|
|||||||
App, Bounds, BoxShadow, DispatchPhase, ElementId, InteractiveElement as _, IntoElement,
|
App, Bounds, BoxShadow, DispatchPhase, ElementId, InteractiveElement as _, IntoElement,
|
||||||
KeyDownEvent, MouseButton, MouseMoveEvent, MouseUpEvent, ParentElement as _, Pixels,
|
KeyDownEvent, MouseButton, MouseMoveEvent, MouseUpEvent, ParentElement as _, Pixels,
|
||||||
RenderOnce, Role, SharedString, StatefulInteractiveElement as _, Styled as _, Window, canvas,
|
RenderOnce, Role, SharedString, StatefulInteractiveElement as _, Styled as _, Window, canvas,
|
||||||
div, px, relative, rems,
|
div, px, relative,
|
||||||
};
|
};
|
||||||
use gpui_native_theme::ActiveTheme as _;
|
use gpui_native_theme::{ActiveTheme as _, Size};
|
||||||
|
|
||||||
use crate::common::h_flex;
|
use crate::common::{Sizable, h_flex};
|
||||||
|
|
||||||
/// The change reporter, fired with the next value by click, drag, and arrows.
|
/// The change reporter, fired with the next value by click, drag, and arrows.
|
||||||
type ChangeHandler = Rc<dyn Fn(f32, &mut Window, &mut App)>;
|
type ChangeHandler = Rc<dyn Fn(f32, &mut Window, &mut App)>;
|
||||||
@@ -36,6 +36,7 @@ pub struct Slider {
|
|||||||
max: f32,
|
max: f32,
|
||||||
step: f32,
|
step: f32,
|
||||||
value: f32,
|
value: f32,
|
||||||
|
size: Size,
|
||||||
disabled: bool,
|
disabled: bool,
|
||||||
a11y_label: Option<SharedString>,
|
a11y_label: Option<SharedString>,
|
||||||
on_change: Option<ChangeHandler>,
|
on_change: Option<ChangeHandler>,
|
||||||
@@ -53,6 +54,7 @@ impl Slider {
|
|||||||
max: 1.0,
|
max: 1.0,
|
||||||
step: 0.0,
|
step: 0.0,
|
||||||
value,
|
value,
|
||||||
|
size: Size::default(),
|
||||||
disabled: false,
|
disabled: false,
|
||||||
a11y_label: None,
|
a11y_label: None,
|
||||||
on_change: None,
|
on_change: None,
|
||||||
@@ -108,12 +110,23 @@ impl Slider {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Sizable for Slider {
|
||||||
|
fn apply_size(&mut self, size: Size) {
|
||||||
|
self.size = size;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl RenderOnce for Slider {
|
impl RenderOnce for Slider {
|
||||||
fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
|
fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
|
||||||
let theme = cx.theme();
|
let theme = cx.theme();
|
||||||
let colors = theme.colors;
|
let colors = theme.colors;
|
||||||
let ramps = theme.ramps;
|
let ramps = theme.ramps;
|
||||||
let radius = theme.radius;
|
let radius = theme.radius;
|
||||||
|
let metrics = theme.sizes.resolve(self.size);
|
||||||
|
|
||||||
|
let row_height = metrics.icon_size;
|
||||||
|
let track_height = metrics.icon_size / 4.0;
|
||||||
|
let knob = metrics.icon_size * 1.25;
|
||||||
|
|
||||||
let value = self.value.clamp(self.min, self.max);
|
let value = self.value.clamp(self.min, self.max);
|
||||||
let fraction = if self.max > self.min {
|
let fraction = if self.max > self.min {
|
||||||
@@ -162,7 +175,7 @@ impl RenderOnce for Slider {
|
|||||||
.id(self.id)
|
.id(self.id)
|
||||||
.relative()
|
.relative()
|
||||||
.w_full()
|
.w_full()
|
||||||
.h_5()
|
.h(row_height)
|
||||||
.role(Role::Slider)
|
.role(Role::Slider)
|
||||||
.when_some(self.a11y_label.clone(), |slider, label| {
|
.when_some(self.a11y_label.clone(), |slider, label| {
|
||||||
slider.aria_label(label)
|
slider.aria_label(label)
|
||||||
@@ -251,8 +264,8 @@ impl RenderOnce for Slider {
|
|||||||
div()
|
div()
|
||||||
.absolute()
|
.absolute()
|
||||||
.left_0()
|
.left_0()
|
||||||
.top_2()
|
.top((row_height - track_height) / 2.0)
|
||||||
.h_1()
|
.h(track_height)
|
||||||
.w_full()
|
.w_full()
|
||||||
.rounded(radius.full)
|
.rounded(radius.full)
|
||||||
.bg(colors.border),
|
.bg(colors.border),
|
||||||
@@ -261,8 +274,8 @@ impl RenderOnce for Slider {
|
|||||||
div()
|
div()
|
||||||
.absolute()
|
.absolute()
|
||||||
.left_0()
|
.left_0()
|
||||||
.top_2()
|
.top((row_height - track_height) / 2.0)
|
||||||
.h_1()
|
.h(track_height)
|
||||||
.w(relative(fraction))
|
.w(relative(fraction))
|
||||||
.rounded(radius.full)
|
.rounded(radius.full)
|
||||||
.bg(ramps.primary.base),
|
.bg(ramps.primary.base),
|
||||||
@@ -278,13 +291,13 @@ impl RenderOnce for Slider {
|
|||||||
.items_center()
|
.items_center()
|
||||||
.child(
|
.child(
|
||||||
div()
|
div()
|
||||||
.size_5()
|
.size(knob)
|
||||||
.flex_none()
|
.flex_none()
|
||||||
.rounded(radius.full)
|
.rounded(radius.full)
|
||||||
.bg(colors.primary_foreground)
|
.bg(colors.primary_foreground)
|
||||||
.border_1()
|
.border_1()
|
||||||
.border_color(colors.border)
|
.border_color(colors.border)
|
||||||
.mr(rems(-0.625)),
|
.mr(-knob / 2.0),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,9 +6,9 @@ use gpui::{
|
|||||||
RenderOnce, Role, SharedString, StatefulInteractiveElement as _, Styled as _, Toggled, Window,
|
RenderOnce, Role, SharedString, StatefulInteractiveElement as _, Styled as _, Toggled, Window,
|
||||||
div, px,
|
div, px,
|
||||||
};
|
};
|
||||||
use gpui_native_theme::ActiveTheme as _;
|
use gpui_native_theme::{ActiveTheme as _, Size};
|
||||||
|
|
||||||
use crate::common::h_flex;
|
use crate::common::{Sizable, h_flex};
|
||||||
|
|
||||||
/// The change reporter, fired with the next value by pointer, Enter, and Space.
|
/// The change reporter, fired with the next value by pointer, Enter, and Space.
|
||||||
type ChangeHandler = Box<dyn Fn(bool, &mut Window, &mut App)>;
|
type ChangeHandler = Box<dyn Fn(bool, &mut Window, &mut App)>;
|
||||||
@@ -19,6 +19,7 @@ pub struct Switch {
|
|||||||
id: ElementId,
|
id: ElementId,
|
||||||
label: Option<SharedString>,
|
label: Option<SharedString>,
|
||||||
checked: bool,
|
checked: bool,
|
||||||
|
size: Size,
|
||||||
disabled: bool,
|
disabled: bool,
|
||||||
a11y_label: Option<SharedString>,
|
a11y_label: Option<SharedString>,
|
||||||
on_change: Option<ChangeHandler>,
|
on_change: Option<ChangeHandler>,
|
||||||
@@ -34,6 +35,7 @@ impl Switch {
|
|||||||
id: id.into(),
|
id: id.into(),
|
||||||
label: None,
|
label: None,
|
||||||
checked: false,
|
checked: false,
|
||||||
|
size: Size::default(),
|
||||||
disabled: false,
|
disabled: false,
|
||||||
a11y_label: None,
|
a11y_label: None,
|
||||||
on_change: None,
|
on_change: None,
|
||||||
@@ -80,19 +82,33 @@ impl Switch {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Sizable for Switch {
|
||||||
|
fn apply_size(&mut self, size: Size) {
|
||||||
|
self.size = size;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl RenderOnce for Switch {
|
impl RenderOnce for Switch {
|
||||||
fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
|
fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
|
||||||
let theme = cx.theme();
|
let theme = cx.theme();
|
||||||
let colors = theme.colors;
|
let colors = theme.colors;
|
||||||
let ramps = theme.ramps;
|
let ramps = theme.ramps;
|
||||||
let on_change = self.on_change;
|
let metrics = theme.sizes.resolve(self.size);
|
||||||
|
|
||||||
|
let track_height = metrics.icon_size * 1.25;
|
||||||
|
let track_width = metrics.icon_size * 2.25;
|
||||||
|
|
||||||
|
let knob = metrics.icon_size;
|
||||||
|
let inset = metrics.icon_size * 0.125;
|
||||||
|
|
||||||
let next = !self.checked;
|
let next = !self.checked;
|
||||||
|
let on_change = self.on_change;
|
||||||
let a11y_label = self.a11y_label.or_else(|| self.label.clone());
|
let a11y_label = self.a11y_label.or_else(|| self.label.clone());
|
||||||
|
|
||||||
h_flex()
|
h_flex()
|
||||||
.id(self.id)
|
.id(self.id)
|
||||||
.gap_1p5()
|
.gap_1p5()
|
||||||
.text_sm()
|
.text_size(metrics.text_size)
|
||||||
.role(Role::Switch)
|
.role(Role::Switch)
|
||||||
.when_some(a11y_label, |switch, label| switch.aria_label(label))
|
.when_some(a11y_label, |switch, label| switch.aria_label(label))
|
||||||
.aria_toggled(Toggled::from(self.checked))
|
.aria_toggled(Toggled::from(self.checked))
|
||||||
@@ -114,9 +130,9 @@ impl RenderOnce for Switch {
|
|||||||
.child(
|
.child(
|
||||||
h_flex()
|
h_flex()
|
||||||
.flex_none()
|
.flex_none()
|
||||||
.w_9()
|
.w(track_width)
|
||||||
.h_5()
|
.h(track_height)
|
||||||
.p_0p5()
|
.p(inset)
|
||||||
.rounded(theme.radius.full)
|
.rounded(theme.radius.full)
|
||||||
.when(self.checked, |track| {
|
.when(self.checked, |track| {
|
||||||
track
|
track
|
||||||
@@ -129,7 +145,7 @@ impl RenderOnce for Switch {
|
|||||||
})
|
})
|
||||||
.child(
|
.child(
|
||||||
div()
|
div()
|
||||||
.size_4()
|
.size(knob)
|
||||||
.flex_none()
|
.flex_none()
|
||||||
.rounded(theme.radius.full)
|
.rounded(theme.radius.full)
|
||||||
.bg(colors.primary_foreground)
|
.bg(colors.primary_foreground)
|
||||||
|
|||||||
@@ -4,9 +4,9 @@ use gpui::{
|
|||||||
RenderOnce, Role, SharedString, StatefulInteractiveElement as _, Styled as _, Toggled, Window,
|
RenderOnce, Role, SharedString, StatefulInteractiveElement as _, Styled as _, Toggled, Window,
|
||||||
px,
|
px,
|
||||||
};
|
};
|
||||||
use gpui_native_theme::ActiveTheme as _;
|
use gpui_native_theme::{ActiveTheme as _, Size};
|
||||||
|
|
||||||
use crate::common::h_flex;
|
use crate::common::{Sizable, h_flex};
|
||||||
|
|
||||||
/// The change handler, fired with the next pressed state.
|
/// The change handler, fired with the next pressed state.
|
||||||
type ChangeHandler = Box<dyn Fn(bool, &mut Window, &mut App)>;
|
type ChangeHandler = Box<dyn Fn(bool, &mut Window, &mut App)>;
|
||||||
@@ -17,6 +17,7 @@ pub struct Toggle {
|
|||||||
id: ElementId,
|
id: ElementId,
|
||||||
label: Option<SharedString>,
|
label: Option<SharedString>,
|
||||||
pressed: bool,
|
pressed: bool,
|
||||||
|
size: Size,
|
||||||
disabled: bool,
|
disabled: bool,
|
||||||
a11y_label: Option<SharedString>,
|
a11y_label: Option<SharedString>,
|
||||||
on_change: Option<ChangeHandler>,
|
on_change: Option<ChangeHandler>,
|
||||||
@@ -32,6 +33,7 @@ impl Toggle {
|
|||||||
id: id.into(),
|
id: id.into(),
|
||||||
label: None,
|
label: None,
|
||||||
pressed: false,
|
pressed: false,
|
||||||
|
size: Size::default(),
|
||||||
disabled: false,
|
disabled: false,
|
||||||
a11y_label: None,
|
a11y_label: None,
|
||||||
on_change: None,
|
on_change: None,
|
||||||
@@ -78,11 +80,18 @@ impl Toggle {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Sizable for Toggle {
|
||||||
|
fn apply_size(&mut self, size: Size) {
|
||||||
|
self.size = size;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl RenderOnce for Toggle {
|
impl RenderOnce for Toggle {
|
||||||
fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
|
fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
|
||||||
let theme = cx.theme();
|
let theme = cx.theme();
|
||||||
let colors = theme.colors;
|
let colors = theme.colors;
|
||||||
let ramps = theme.ramps;
|
let ramps = theme.ramps;
|
||||||
|
let metrics = theme.sizes.resolve(self.size);
|
||||||
let on_change = self.on_change;
|
let on_change = self.on_change;
|
||||||
let next = !self.pressed;
|
let next = !self.pressed;
|
||||||
let a11y_label = self.a11y_label.or_else(|| self.label.clone());
|
let a11y_label = self.a11y_label.or_else(|| self.label.clone());
|
||||||
@@ -90,10 +99,10 @@ impl RenderOnce for Toggle {
|
|||||||
h_flex()
|
h_flex()
|
||||||
.id(self.id)
|
.id(self.id)
|
||||||
.justify_center()
|
.justify_center()
|
||||||
.h_7()
|
.h(metrics.control_height)
|
||||||
.px_3()
|
.px(metrics.padding_x * 0.75)
|
||||||
.rounded(theme.radius.control)
|
.rounded(theme.radius.control)
|
||||||
.text_sm()
|
.text_size(metrics.text_size)
|
||||||
.text_color(colors.foreground)
|
.text_color(colors.foreground)
|
||||||
.role(Role::Button)
|
.role(Role::Button)
|
||||||
.when_some(a11y_label, |toggle, label| toggle.aria_label(label))
|
.when_some(a11y_label, |toggle, label| toggle.aria_label(label))
|
||||||
|
|||||||
@@ -0,0 +1,66 @@
|
|||||||
|
use gpui::{
|
||||||
|
Entity, InteractiveElement as _, ParentElement as _, Render, Styled as _, TestAppContext,
|
||||||
|
VisualTestContext, Window, div,
|
||||||
|
};
|
||||||
|
use gpui_native_core::button::{Button, ButtonVariant};
|
||||||
|
use gpui_native_core::input::{Input, InputState};
|
||||||
|
use gpui_native_theme::ActiveTheme as _;
|
||||||
|
use gpui_native_theme::Theme;
|
||||||
|
|
||||||
|
struct Harness {
|
||||||
|
state: Entity<InputState>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Render for Harness {
|
||||||
|
fn render(
|
||||||
|
&mut self,
|
||||||
|
_window: &mut Window,
|
||||||
|
_cx: &mut gpui::Context<Self>,
|
||||||
|
) -> impl gpui::IntoElement {
|
||||||
|
div()
|
||||||
|
.flex()
|
||||||
|
.flex_col()
|
||||||
|
.gap_4()
|
||||||
|
.child(
|
||||||
|
div().debug_selector(|| "button-wrapper".to_string()).child(
|
||||||
|
Button::new("cmp-button")
|
||||||
|
.variant(ButtonVariant::Secondary)
|
||||||
|
.label("Medium"),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
div()
|
||||||
|
.debug_selector(|| "input-wrapper".to_string())
|
||||||
|
.child(Input::new("cmp-input", self.state.clone())),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A medium input is exactly `input_height`, and sits taller than the
|
||||||
|
/// medium button: native text fields are a touch taller than push buttons.
|
||||||
|
#[gpui::test]
|
||||||
|
fn medium_input_is_taller_than_the_button(cx: &mut TestAppContext) {
|
||||||
|
cx.update(Theme::init);
|
||||||
|
let state = cx.update(|cx| InputState::new_with_text("", cx));
|
||||||
|
let state_for_window = state.clone();
|
||||||
|
let window = cx.add_window(move |_, _| Harness {
|
||||||
|
state: state_for_window,
|
||||||
|
});
|
||||||
|
let mut visual = VisualTestContext::from_window(window.into(), cx);
|
||||||
|
visual.update(|window, cx| {
|
||||||
|
window.draw(cx).clear(cx);
|
||||||
|
});
|
||||||
|
|
||||||
|
let button = visual
|
||||||
|
.debug_bounds("button-wrapper")
|
||||||
|
.expect("button rendered");
|
||||||
|
let input = visual
|
||||||
|
.debug_bounds("input-wrapper")
|
||||||
|
.expect("input rendered");
|
||||||
|
|
||||||
|
// The input's height is the theme table's (platform-stated) field height;
|
||||||
|
// both bounds are snapped to device pixels, so allow half a pixel.
|
||||||
|
let input_height = cx.update(|cx| cx.theme().sizes.medium.input_height);
|
||||||
|
assert!(input.size.height > button.size.height);
|
||||||
|
assert!((input.size.height - input_height).abs() <= gpui::px(0.5));
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
use gpui::{
|
use gpui::{
|
||||||
Entity, Modifiers, ParentElement as _, Render, Styled as _, TestAppContext,
|
Entity, Modifiers, ParentElement as _, Render, Styled as _, TestAppContext, VisualTestContext,
|
||||||
VisualTestContext, Window, div, point, px,
|
Window, div, point, px,
|
||||||
};
|
};
|
||||||
use gpui_native_core::input::{Input, InputState};
|
use gpui_native_core::input::{Input, InputState};
|
||||||
use gpui_native_theme::Theme;
|
use gpui_native_theme::Theme;
|
||||||
|
|||||||
+83
-1
@@ -47,6 +47,9 @@ pub struct Theme {
|
|||||||
pub colors: ColorTokens,
|
pub colors: ColorTokens,
|
||||||
pub ramps: Ramps,
|
pub ramps: Ramps,
|
||||||
pub radius: Radius,
|
pub radius: Radius,
|
||||||
|
/// The platform's native root font size; rem-based sizing scales with it.
|
||||||
|
pub root_font_size: Pixels,
|
||||||
|
pub sizes: SizeTable,
|
||||||
pub accessibility: AccessibilityPreferences,
|
pub accessibility: AccessibilityPreferences,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -72,6 +75,78 @@ impl Default for Radius {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A named control size class.
|
||||||
|
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
|
||||||
|
pub enum Size {
|
||||||
|
Small,
|
||||||
|
/// The default.
|
||||||
|
#[default]
|
||||||
|
Medium,
|
||||||
|
Large,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The control metrics for one size class.
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||||
|
pub struct ControlMetrics {
|
||||||
|
/// Push buttons, toggles, and field rows.
|
||||||
|
pub control_height: Pixels,
|
||||||
|
/// Text fields; slightly taller than buttons, as native fields are.
|
||||||
|
pub input_height: Pixels,
|
||||||
|
/// Horizontal padding inside controls.
|
||||||
|
pub padding_x: Pixels,
|
||||||
|
/// Label text size.
|
||||||
|
pub text_size: Pixels,
|
||||||
|
/// Icons and on/off indicators.
|
||||||
|
pub icon_size: Pixels,
|
||||||
|
/// Avatars.
|
||||||
|
pub avatar_size: Pixels,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The control metrics for every size class, derived from the theme's root
|
||||||
|
/// font size so controls scale with the platform's native type.
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||||
|
pub struct SizeTable {
|
||||||
|
pub small: ControlMetrics,
|
||||||
|
pub medium: ControlMetrics,
|
||||||
|
pub large: ControlMetrics,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SizeTable {
|
||||||
|
pub fn with_root_font_size(rem_size: Pixels) -> Self {
|
||||||
|
let class = |height: f32, padding: f32, text: f32, icon: f32, avatar: f32| ControlMetrics {
|
||||||
|
control_height: rem_size * height,
|
||||||
|
input_height: rem_size * (height + 0.125),
|
||||||
|
padding_x: rem_size * padding,
|
||||||
|
text_size: rem_size * text,
|
||||||
|
icon_size: rem_size * icon,
|
||||||
|
avatar_size: rem_size * avatar,
|
||||||
|
};
|
||||||
|
Self {
|
||||||
|
small: class(1.5, 0.75, 0.85, 0.875, 1.5),
|
||||||
|
medium: class(1.75, 1.0, 1.0, 1.0, 2.0),
|
||||||
|
large: class(2.25, 1.25, 1.15, 1.25, 3.0),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The metrics for `size`.
|
||||||
|
pub fn resolve(&self, size: Size) -> ControlMetrics {
|
||||||
|
match size {
|
||||||
|
Size::Small => self.small,
|
||||||
|
Size::Medium => self.medium,
|
||||||
|
Size::Large => self.large,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the medium class's text-field height to the platform's stated
|
||||||
|
/// value, scaling the other classes by the same ratio.
|
||||||
|
pub fn set_input_height(&mut self, medium_input_height: Pixels) {
|
||||||
|
let ratio = medium_input_height / self.medium.input_height;
|
||||||
|
self.small.input_height *= ratio;
|
||||||
|
self.medium.input_height = medium_input_height;
|
||||||
|
self.large.input_height *= ratio;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Reads the resolved theme global from any app context.
|
/// Reads the resolved theme global from any app context.
|
||||||
pub trait ActiveTheme {
|
pub trait ActiveTheme {
|
||||||
fn theme(&self) -> &Theme;
|
fn theme(&self) -> &Theme;
|
||||||
@@ -95,6 +170,8 @@ impl Theme {
|
|||||||
colors: resolved.colors,
|
colors: resolved.colors,
|
||||||
ramps: resolved.ramps,
|
ramps: resolved.ramps,
|
||||||
radius: resolved.radius,
|
radius: resolved.radius,
|
||||||
|
root_font_size: resolved.root_font_size,
|
||||||
|
sizes: resolved.sizes,
|
||||||
accessibility: resolved.accessibility,
|
accessibility: resolved.accessibility,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -114,7 +191,7 @@ impl Theme {
|
|||||||
/// Re-resolves when a window's appearance differs from the resolved variant.
|
/// Re-resolves when a window's appearance differs from the resolved variant.
|
||||||
///
|
///
|
||||||
/// Wire to `Window::observe_window_appearance`.
|
/// Wire to `Window::observe_window_appearance`.
|
||||||
pub fn sync_from_window(window: &gpui::Window, cx: &mut App) {
|
pub fn sync_from_window(window: &mut gpui::Window, cx: &mut App) {
|
||||||
let variant = ThemeVariant::from_appearance(window.appearance());
|
let variant = ThemeVariant::from_appearance(window.appearance());
|
||||||
if cx.global::<Theme>().variant != variant
|
if cx.global::<Theme>().variant != variant
|
||||||
|| cx.global::<ThemeEngine>().mode_override.is_none()
|
|| cx.global::<ThemeEngine>().mode_override.is_none()
|
||||||
@@ -122,6 +199,11 @@ impl Theme {
|
|||||||
reapply(cx);
|
reapply(cx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sizes the window's rem to the theme's root font size.
|
||||||
|
pub fn sync_window_rem_size(window: &mut gpui::Window, cx: &App) {
|
||||||
|
window.set_rem_size(cx.global::<Theme>().root_font_size);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The engine's knobs and its platform source.
|
/// The engine's knobs and its platform source.
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
use crate::definition::ThemeDefinition;
|
use crate::definition::ThemeDefinition;
|
||||||
use crate::ramps::{AccentShades, Oklch, contrast_foreground, derive_shades, mix};
|
use crate::ramps::{AccentShades, Oklch, contrast_foreground, derive_shades, mix};
|
||||||
use crate::system::SystemTheme;
|
use crate::system::SystemTheme;
|
||||||
use crate::{ColorTokens, Radius, ThemeVariant};
|
use crate::{ColorTokens, Radius, SizeTable, ThemeVariant};
|
||||||
use gpui::px;
|
use gpui::px;
|
||||||
|
|
||||||
/// Derived state shades for the colors that get pressed and hovered.
|
/// Derived state shades for the colors that get pressed and hovered.
|
||||||
@@ -19,6 +19,9 @@ pub struct ResolvedTheme {
|
|||||||
pub colors: ColorTokens,
|
pub colors: ColorTokens,
|
||||||
pub ramps: Ramps,
|
pub ramps: Ramps,
|
||||||
pub radius: Radius,
|
pub radius: Radius,
|
||||||
|
/// The platform's native root font size; the fallback theme uses 16.
|
||||||
|
pub root_font_size: gpui::Pixels,
|
||||||
|
pub sizes: SizeTable,
|
||||||
pub accessibility: crate::system::AccessibilityPreferences,
|
pub accessibility: crate::system::AccessibilityPreferences,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -132,6 +135,17 @@ pub(crate) fn resolve(
|
|||||||
.to_hsla();
|
.to_hsla();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The platform's root font size scales every control metric:
|
||||||
|
// a medium control's label is exactly the root font size.
|
||||||
|
//
|
||||||
|
// Fallback: 16 (the web default gpui also uses for rems).
|
||||||
|
let root_font_size = system.root_font_size.unwrap_or(px(16.));
|
||||||
|
|
||||||
|
let mut sizes = SizeTable::with_root_font_size(root_font_size);
|
||||||
|
if let Some(input_height) = system.input_height {
|
||||||
|
sizes.set_input_height(input_height);
|
||||||
|
}
|
||||||
|
|
||||||
let mut radius = Radius::default();
|
let mut radius = Radius::default();
|
||||||
if let Some(control) = preset
|
if let Some(control) = preset
|
||||||
.and_then(|def| def.control_radius)
|
.and_then(|def| def.control_radius)
|
||||||
@@ -158,6 +172,8 @@ pub(crate) fn resolve(
|
|||||||
},
|
},
|
||||||
colors,
|
colors,
|
||||||
radius,
|
radius,
|
||||||
|
root_font_size,
|
||||||
|
sizes,
|
||||||
accessibility,
|
accessibility,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -182,6 +198,8 @@ mod tests {
|
|||||||
accent,
|
accent,
|
||||||
palette,
|
palette,
|
||||||
control_radius: None,
|
control_radius: None,
|
||||||
|
input_height: None,
|
||||||
|
root_font_size: None,
|
||||||
accessibility: AccessibilityPreferences::default(),
|
accessibility: AccessibilityPreferences::default(),
|
||||||
source: SourceKind::Macos,
|
source: SourceKind::Macos,
|
||||||
}
|
}
|
||||||
@@ -218,9 +236,44 @@ mod tests {
|
|||||||
assert_eq!(resolved.ramps.secondary.base, base.secondary);
|
assert_eq!(resolved.ramps.secondary.base, base.secondary);
|
||||||
assert_eq!(resolved.radius.sm, px(4.));
|
assert_eq!(resolved.radius.sm, px(4.));
|
||||||
assert_eq!(resolved.radius.control, px(6.));
|
assert_eq!(resolved.radius.control, px(6.));
|
||||||
|
assert_eq!(resolved.root_font_size, px(16.));
|
||||||
|
assert_eq!(resolved.sizes.medium.control_height, px(28.));
|
||||||
|
assert_eq!(resolved.sizes.medium.input_height, px(30.));
|
||||||
|
assert_eq!(resolved.sizes.medium.text_size, px(16.));
|
||||||
|
assert_eq!(resolved.sizes.large.avatar_size, px(48.));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn a_stated_root_font_size_scales_the_size_table() {
|
||||||
|
// macOS states 13pt; the medium control shrinks to native proportions.
|
||||||
|
let mut stated = system(ThemeVariant::Light, PartialPalette::default(), None);
|
||||||
|
stated.root_font_size = Some(px(13.));
|
||||||
|
let resolved = resolve(&stated, None, None);
|
||||||
|
assert_eq!(resolved.root_font_size, px(13.));
|
||||||
|
assert_eq!(resolved.sizes.medium.control_height, px(22.75));
|
||||||
|
assert_eq!(resolved.sizes.medium.text_size, px(13.));
|
||||||
|
assert_eq!(resolved.sizes.small.text_size, px(11.05));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn a_stated_input_height_overrides_the_derived_one_and_scales() {
|
||||||
|
let mut stated = system(ThemeVariant::Light, PartialPalette::default(), None);
|
||||||
|
stated.input_height = Some(px(24.));
|
||||||
|
let resolved = resolve(&stated, None, None);
|
||||||
|
|
||||||
|
// The medium class takes the stated height verbatim, and the other
|
||||||
|
// classes keep their derived ratio to the medium class.
|
||||||
|
assert_eq!(resolved.sizes.medium.input_height, px(24.));
|
||||||
|
|
||||||
|
// Derive what small would be without the statement, scale it by the
|
||||||
|
// stated-to-derived ratio, and compare.
|
||||||
|
let derived = SizeTable::with_root_font_size(px(16.));
|
||||||
|
let scale = px(24.) / derived.medium.input_height;
|
||||||
|
let expected_small = derived.small.input_height * scale;
|
||||||
|
assert!((resolved.sizes.small.input_height - expected_small).abs() < px(0.01));
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn system_colors_override_the_base_but_leave_the_rest_intact() {
|
fn system_colors_override_the_base_but_leave_the_rest_intact() {
|
||||||
let palette = PartialPalette {
|
let palette = PartialPalette {
|
||||||
|
|||||||
@@ -20,6 +20,10 @@ use crate::system::{AccessibilityPreferences, PartialPalette, SourceKind, System
|
|||||||
|
|
||||||
/// Default control radius for macOS controls.
|
/// Default control radius for macOS controls.
|
||||||
const CONTROL_RADIUS_PTS: f32 = 6.0;
|
const CONTROL_RADIUS_PTS: f32 = 6.0;
|
||||||
|
/// The rounded-bezel text field's intrinsic height at regular size.
|
||||||
|
const INPUT_HEIGHT_PTS: f32 = 24.0;
|
||||||
|
/// The HIG's default body text on macOS is 13 pt (system font size 13 at 1x).
|
||||||
|
const ROOT_FONT_SIZE_PTS: f32 = 13.0;
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub(crate) struct MacosSource {
|
pub(crate) struct MacosSource {
|
||||||
@@ -102,6 +106,8 @@ impl ThemeSource for MacosSource {
|
|||||||
accent: Self::color_to_hsla(&NSColor::controlAccentColor()),
|
accent: Self::color_to_hsla(&NSColor::controlAccentColor()),
|
||||||
palette,
|
palette,
|
||||||
control_radius: Some(gpui::px(CONTROL_RADIUS_PTS)),
|
control_radius: Some(gpui::px(CONTROL_RADIUS_PTS)),
|
||||||
|
input_height: Some(gpui::px(INPUT_HEIGHT_PTS)),
|
||||||
|
root_font_size: Some(gpui::px(ROOT_FONT_SIZE_PTS)),
|
||||||
accessibility,
|
accessibility,
|
||||||
source: SourceKind::Macos,
|
source: SourceKind::Macos,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -55,7 +55,12 @@ pub struct SystemTheme {
|
|||||||
pub variant: ThemeVariant,
|
pub variant: ThemeVariant,
|
||||||
pub accent: Option<Hsla>,
|
pub accent: Option<Hsla>,
|
||||||
pub palette: PartialPalette,
|
pub palette: PartialPalette,
|
||||||
|
/// The platform's stated control corner radius, if it has one.
|
||||||
pub control_radius: Option<Pixels>,
|
pub control_radius: Option<Pixels>,
|
||||||
|
/// The platform's stated text-field height, if it has one.
|
||||||
|
pub input_height: Option<Pixels>,
|
||||||
|
/// The platform's native root font size, if it has one.
|
||||||
|
pub root_font_size: Option<Pixels>,
|
||||||
pub accessibility: AccessibilityPreferences,
|
pub accessibility: AccessibilityPreferences,
|
||||||
pub source: SourceKind,
|
pub source: SourceKind,
|
||||||
}
|
}
|
||||||
@@ -67,6 +72,8 @@ impl SystemTheme {
|
|||||||
accent: None,
|
accent: None,
|
||||||
palette: PartialPalette::default(),
|
palette: PartialPalette::default(),
|
||||||
control_radius: None,
|
control_radius: None,
|
||||||
|
input_height: None,
|
||||||
|
root_font_size: None,
|
||||||
accessibility: AccessibilityPreferences::default(),
|
accessibility: AccessibilityPreferences::default(),
|
||||||
source: SourceKind::Fallback,
|
source: SourceKind::Fallback,
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-3
@@ -215,11 +215,11 @@ Each row is one review unit: build it, show it in the gallery, review, then move
|
|||||||
|
|
||||||
Deferred until a real need: Markdown/TextView, Dock, VirtualList, window-wide text selection, a motion system (use gpui's built-in animations first), charts.
|
Deferred until a real need: Markdown/TextView, Dock, VirtualList, window-wide text selection, a motion system (use gpui's built-in animations first), charts.
|
||||||
|
|
||||||
### 6.1 After the core units: sizing API
|
### 6.1 After the core units: sizing API — ✅ landed
|
||||||
|
|
||||||
Once the core components are done (units 1–10), add a **`Sizable` trait** so applications can customize component size, following gpui-kit's shape: a `Size` enum (`XSmall` / `Small` / `Medium` default / `Large`, plus a custom `Pixels` variant) and `with_size(impl Into<Size>)` with `xsmall()` / `small()` / `large()` conveniences.
|
**Landed** (2026-10-29): the `Size` enum (`XSmall` / `Small` / `Medium` default / `Large` / `Custom(Pixels)`), the `Sizable` trait (`with_size(impl Into<Size>)` + `xsmall()` / `small()` / `large()`), and the `SizeTable` of per-class control metrics (height, padding-x, text size, icon/indicator size, avatar size) now live where they belong — `Size`/`SizeTable`/`ControlMetrics` in the theme (they are native-metric values, §5.5), the trait in core's ungated `common`.
|
||||||
|
|
||||||
Our divergence from gpui-kit: the per-size metrics (control heights, paddings, text sizes, knob/indicator diameters) resolve from the **theme**, not from constants in the component layer — they are native-metric values (§5.5) and every component already reads its metrics through tokens. Implementation shape: the theme gains a size-class table (`Size` → control height/padding/text metrics), and each control implements `Sizable` by reading that table at paint time. Retrofits land one component per unit (Button first, as the API template) with the trait living in the ungated `common` module.
|
Our divergence from gpui-kit held: the per-size metrics resolve from the **theme's size table at paint time**, not from constants in the component layer. All ten controls implement `Sizable`; `Size::Custom(side)` derives the other metrics from the side length. Still open, when a real need arrives: TOML overrides for the size table in `ThemeDefinition`, and per-platform size tables from the Phase F native-metrics work.
|
||||||
|
|
||||||
## 7. Milestones
|
## 7. Milestones
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ use gpui::{
|
|||||||
use gpui_native_core::avatar::Avatar;
|
use gpui_native_core::avatar::Avatar;
|
||||||
use gpui_native_core::button::{Button, ButtonVariant};
|
use gpui_native_core::button::{Button, ButtonVariant};
|
||||||
use gpui_native_core::checkbox::Checkbox;
|
use gpui_native_core::checkbox::Checkbox;
|
||||||
use gpui_native_core::common::{h_flex, v_flex};
|
use gpui_native_core::common::{Sizable as _, h_flex, v_flex};
|
||||||
use gpui_native_core::input::{Input, InputState};
|
use gpui_native_core::input::{Input, InputState};
|
||||||
use gpui_native_core::link::Link;
|
use gpui_native_core::link::Link;
|
||||||
use gpui_native_core::progress::Progress;
|
use gpui_native_core::progress::Progress;
|
||||||
@@ -206,6 +206,14 @@ impl Render for Gallery {
|
|||||||
.icon(IconRole::Folder),
|
.icon(IconRole::Folder),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
.child(
|
||||||
|
h_flex()
|
||||||
|
.items_center()
|
||||||
|
.gap_2()
|
||||||
|
.child(Button::new("gallery-button-sm").label("Small").small())
|
||||||
|
.child(Button::new("gallery-button-md").label("Medium"))
|
||||||
|
.child(Button::new("gallery-button-lg").label("Large").large()),
|
||||||
|
)
|
||||||
.child(
|
.child(
|
||||||
div()
|
div()
|
||||||
.text_xs()
|
.text_xs()
|
||||||
@@ -455,8 +463,8 @@ impl Render for Gallery {
|
|||||||
.name("Ada Lovelace")
|
.name("Ada Lovelace")
|
||||||
.picture(Some("https://example.com/missing.png")),
|
.picture(Some("https://example.com/missing.png")),
|
||||||
)
|
)
|
||||||
.child(Avatar::new("grace@reya.info").name("Grace Hopper"))
|
.child(Avatar::new("grace@reya.info").name("Grace Hopper").small())
|
||||||
.child(Avatar::new("bot@reya.info")),
|
.child(Avatar::new("desk@reya.info").name("Help Desk").large()),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.child(
|
.child(
|
||||||
@@ -602,10 +610,12 @@ fn main() {
|
|||||||
},
|
},
|
||||||
|window, cx| {
|
|window, cx| {
|
||||||
Theme::sync_from_window(window, cx);
|
Theme::sync_from_window(window, cx);
|
||||||
|
Theme::sync_window_rem_size(window, cx);
|
||||||
// Detached on purpose: the subscription lives as long as the window.
|
// Detached on purpose: the subscription lives as long as the window.
|
||||||
window
|
window
|
||||||
.observe_window_appearance(|window, cx| {
|
.observe_window_appearance(|window, cx| {
|
||||||
Theme::sync_from_window(window, cx);
|
Theme::sync_from_window(window, cx);
|
||||||
|
Theme::sync_window_rem_size(window, cx);
|
||||||
cx.refresh_windows();
|
cx.refresh_windows();
|
||||||
})
|
})
|
||||||
.detach();
|
.detach();
|
||||||
|
|||||||
Reference in New Issue
Block a user