mirror of
https://github.com/zed-industries/zed.git
synced 2024-12-28 04:21:40 +03:00
ui: Add NumericStepper
component (#13954)
This PR adds a `NumericStepper` component that can be used to display a numeric value along with controls to increment, decrement, and reset the value. The `ApplicationMenu` has been updated to use the `NumericStepper` for adjusting the buffer and UI font size. Here it is in action: https://github.com/zed-industries/zed/assets/1486634/03cffe67-1256-4283-aa3d-560fffa06dad Note: Due to the way we do font adjustments, once modified the reset button will be displayed until it is clicked (or the font size adjustment is otherwise reset). Simply returning to the original value will currently not hide the reset button. Release Notes: - N/A
This commit is contained in:
parent
97f315356d
commit
9e36a66fec
@ -381,6 +381,10 @@ pub fn adjust_buffer_font_size(cx: &mut AppContext, f: fn(&mut Pixels)) {
|
||||
cx.refresh();
|
||||
}
|
||||
|
||||
pub fn has_adjusted_buffer_font_size(cx: &mut AppContext) -> bool {
|
||||
cx.has_global::<AdjustedBufferFontSize>()
|
||||
}
|
||||
|
||||
pub fn reset_buffer_font_size(cx: &mut AppContext) {
|
||||
if cx.has_global::<AdjustedBufferFontSize>() {
|
||||
cx.remove_global::<AdjustedBufferFontSize>();
|
||||
@ -417,6 +421,10 @@ pub fn adjust_ui_font_size(cx: &mut WindowContext, f: fn(&mut Pixels)) {
|
||||
cx.refresh();
|
||||
}
|
||||
|
||||
pub fn has_adjusted_ui_font_size(cx: &mut AppContext) -> bool {
|
||||
cx.has_global::<AdjustedUiFontSize>()
|
||||
}
|
||||
|
||||
pub fn reset_ui_font_size(cx: &mut WindowContext) {
|
||||
if cx.has_global::<AdjustedUiFontSize>() {
|
||||
cx.remove_global::<AdjustedUiFontSize>();
|
||||
|
@ -42,7 +42,6 @@ project.workspace = true
|
||||
recent_projects.workspace = true
|
||||
rpc.workspace = true
|
||||
serde.workspace = true
|
||||
settings.workspace = true
|
||||
smallvec.workspace = true
|
||||
story = { workspace = true, optional = true }
|
||||
theme.workspace = true
|
||||
|
@ -1,6 +1,4 @@
|
||||
use settings::Settings;
|
||||
use theme::ThemeSettings;
|
||||
use ui::{prelude::*, ContextMenu, PopoverMenu, Tooltip};
|
||||
use ui::{prelude::*, ContextMenu, NumericStepper, PopoverMenu, Tooltip};
|
||||
|
||||
#[derive(IntoElement)]
|
||||
pub struct ApplicationMenu;
|
||||
@ -12,128 +10,77 @@ impl ApplicationMenu {
|
||||
}
|
||||
|
||||
impl RenderOnce for ApplicationMenu {
|
||||
fn render(self, cx: &mut WindowContext) -> impl IntoElement {
|
||||
let ui_font_size = ThemeSettings::get_global(cx).ui_font_size;
|
||||
let font = cx.text_style().font();
|
||||
let font_id = cx.text_system().resolve_font(&font);
|
||||
let width = cx
|
||||
.text_system()
|
||||
.typographic_bounds(font_id, ui_font_size, 'm')
|
||||
.unwrap()
|
||||
.size
|
||||
.width
|
||||
* 3.0;
|
||||
|
||||
fn render(self, _cx: &mut WindowContext) -> impl IntoElement {
|
||||
PopoverMenu::new("application-menu")
|
||||
.menu(move |cx| {
|
||||
let width = width;
|
||||
ContextMenu::build(cx, move |menu, _cx| {
|
||||
let width = width;
|
||||
menu.header("Workspace")
|
||||
.action("Open Command Palette", Box::new(command_palette::Toggle))
|
||||
.custom_row(move |cx| {
|
||||
div()
|
||||
h_flex()
|
||||
.gap_2()
|
||||
.w_full()
|
||||
.flex()
|
||||
.flex_row()
|
||||
.justify_between()
|
||||
.cursor(gpui::CursorStyle::Arrow)
|
||||
.child(Label::new("Buffer Font Size"))
|
||||
.child(
|
||||
div()
|
||||
.flex()
|
||||
.flex_row()
|
||||
.child(div().w(px(16.0)))
|
||||
.child(
|
||||
IconButton::new(
|
||||
"reset-buffer-zoom",
|
||||
IconName::RotateCcw,
|
||||
)
|
||||
.on_click(
|
||||
|_, cx| {
|
||||
cx.dispatch_action(Box::new(
|
||||
zed_actions::ResetBufferFontSize,
|
||||
))
|
||||
},
|
||||
),
|
||||
)
|
||||
.child(
|
||||
IconButton::new("--buffer-zoom", IconName::Dash)
|
||||
.on_click(|_, cx| {
|
||||
cx.dispatch_action(Box::new(
|
||||
zed_actions::DecreaseBufferFontSize,
|
||||
))
|
||||
}),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.w(width)
|
||||
.flex()
|
||||
.flex_row()
|
||||
.justify_around()
|
||||
.child(Label::new(
|
||||
theme::get_buffer_font_size(cx).to_string(),
|
||||
)),
|
||||
)
|
||||
.child(
|
||||
IconButton::new("+-buffer-zoom", IconName::Plus)
|
||||
.on_click(|_, cx| {
|
||||
cx.dispatch_action(Box::new(
|
||||
zed_actions::IncreaseBufferFontSize,
|
||||
))
|
||||
}),
|
||||
),
|
||||
NumericStepper::new(
|
||||
theme::get_buffer_font_size(cx).to_string(),
|
||||
|_, cx| {
|
||||
cx.dispatch_action(Box::new(
|
||||
zed_actions::DecreaseBufferFontSize,
|
||||
))
|
||||
},
|
||||
|_, cx| {
|
||||
cx.dispatch_action(Box::new(
|
||||
zed_actions::IncreaseBufferFontSize,
|
||||
))
|
||||
},
|
||||
)
|
||||
.when(
|
||||
theme::has_adjusted_buffer_font_size(cx),
|
||||
|stepper| {
|
||||
stepper.on_reset(|_, cx| {
|
||||
cx.dispatch_action(Box::new(
|
||||
zed_actions::ResetBufferFontSize,
|
||||
))
|
||||
})
|
||||
},
|
||||
),
|
||||
)
|
||||
.into_any_element()
|
||||
})
|
||||
.custom_row(move |cx| {
|
||||
div()
|
||||
h_flex()
|
||||
.gap_2()
|
||||
.w_full()
|
||||
.flex()
|
||||
.flex_row()
|
||||
.justify_between()
|
||||
.cursor(gpui::CursorStyle::Arrow)
|
||||
.child(Label::new("UI Font Size"))
|
||||
.child(
|
||||
div()
|
||||
.flex()
|
||||
.flex_row()
|
||||
.child(
|
||||
IconButton::new("reset-ui-zoom", IconName::RotateCcw)
|
||||
.on_click(|_, cx| {
|
||||
cx.dispatch_action(Box::new(
|
||||
zed_actions::ResetUiFontSize,
|
||||
))
|
||||
}),
|
||||
)
|
||||
.child(
|
||||
IconButton::new("--ui-zoom", IconName::Dash).on_click(
|
||||
|_, cx| {
|
||||
cx.dispatch_action(Box::new(
|
||||
zed_actions::DecreaseUiFontSize,
|
||||
))
|
||||
},
|
||||
),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.w(width)
|
||||
.flex()
|
||||
.flex_row()
|
||||
.justify_around()
|
||||
.child(Label::new(
|
||||
theme::get_ui_font_size(cx).to_string(),
|
||||
)),
|
||||
)
|
||||
.child(
|
||||
IconButton::new("+-ui-zoom", IconName::Plus).on_click(
|
||||
|_, cx| {
|
||||
cx.dispatch_action(Box::new(
|
||||
zed_actions::IncreaseUiFontSize,
|
||||
))
|
||||
},
|
||||
),
|
||||
),
|
||||
NumericStepper::new(
|
||||
theme::get_ui_font_size(cx).to_string(),
|
||||
|_, cx| {
|
||||
cx.dispatch_action(Box::new(
|
||||
zed_actions::DecreaseUiFontSize,
|
||||
))
|
||||
},
|
||||
|_, cx| {
|
||||
cx.dispatch_action(Box::new(
|
||||
zed_actions::IncreaseUiFontSize,
|
||||
))
|
||||
},
|
||||
)
|
||||
.when(
|
||||
theme::has_adjusted_ui_font_size(cx),
|
||||
|stepper| {
|
||||
stepper.on_reset(|_, cx| {
|
||||
cx.dispatch_action(Box::new(
|
||||
zed_actions::ResetUiFontSize,
|
||||
))
|
||||
})
|
||||
},
|
||||
),
|
||||
)
|
||||
.into_any_element()
|
||||
})
|
||||
|
@ -12,6 +12,7 @@ mod keybinding;
|
||||
mod label;
|
||||
mod list;
|
||||
mod modal;
|
||||
mod numeric_stepper;
|
||||
mod popover;
|
||||
mod popover_menu;
|
||||
mod radio;
|
||||
@ -40,6 +41,7 @@ pub use keybinding::*;
|
||||
pub use label::*;
|
||||
pub use list::*;
|
||||
pub use modal::*;
|
||||
pub use numeric_stepper::*;
|
||||
pub use popover::*;
|
||||
pub use popover_menu::*;
|
||||
pub use radio::*;
|
||||
|
@ -1,6 +1,6 @@
|
||||
use gpui::{AnyView, DefiniteLength};
|
||||
|
||||
use crate::{prelude::*, ElevationIndex, SelectableButton, Spacing};
|
||||
use crate::{prelude::*, ElevationIndex, SelectableButton};
|
||||
use crate::{ButtonCommon, ButtonLike, ButtonSize, ButtonStyle, IconName, IconSize};
|
||||
|
||||
use super::button_icon::ButtonIcon;
|
||||
@ -147,16 +147,8 @@ impl RenderOnce for IconButton {
|
||||
self.base
|
||||
.map(|this| match self.shape {
|
||||
IconButtonShape::Square => {
|
||||
let icon_size = self.icon_size.rems() * cx.rem_size();
|
||||
let padding = match self.icon_size {
|
||||
IconSize::Indicator => Spacing::None.px(cx),
|
||||
IconSize::XSmall => Spacing::XSmall.px(cx),
|
||||
IconSize::Small => Spacing::XSmall.px(cx),
|
||||
IconSize::Medium => Spacing::XSmall.px(cx),
|
||||
};
|
||||
|
||||
this.width((icon_size + padding * 2.).into())
|
||||
.height((icon_size + padding * 2.).into())
|
||||
let size = self.icon_size.square(cx);
|
||||
this.width(size.into()).height(size.into())
|
||||
}
|
||||
IconButtonShape::Wide => this,
|
||||
})
|
||||
|
@ -75,6 +75,19 @@ impl IconSize {
|
||||
IconSize::Medium => rems_from_px(16.),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the length of a side of the square that contains this [`IconSize`], with padding.
|
||||
pub(crate) fn square(&self, cx: &mut WindowContext) -> Pixels {
|
||||
let icon_size = self.rems() * cx.rem_size();
|
||||
let padding = match self {
|
||||
IconSize::Indicator => Spacing::None.px(cx),
|
||||
IconSize::XSmall => Spacing::XSmall.px(cx),
|
||||
IconSize::Small => Spacing::XSmall.px(cx),
|
||||
IconSize::Medium => Spacing::XSmall.px(cx),
|
||||
};
|
||||
|
||||
icon_size + padding * 2.
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Copy, Clone, EnumIter, Serialize, Deserialize)]
|
||||
|
81
crates/ui/src/components/numeric_stepper.rs
Normal file
81
crates/ui/src/components/numeric_stepper.rs
Normal file
@ -0,0 +1,81 @@
|
||||
use gpui::ClickEvent;
|
||||
|
||||
use crate::{prelude::*, IconButtonShape};
|
||||
|
||||
#[derive(IntoElement)]
|
||||
pub struct NumericStepper {
|
||||
value: SharedString,
|
||||
on_decrement: Box<dyn Fn(&ClickEvent, &mut WindowContext) + 'static>,
|
||||
on_increment: Box<dyn Fn(&ClickEvent, &mut WindowContext) + 'static>,
|
||||
on_reset: Option<Box<dyn Fn(&ClickEvent, &mut WindowContext) + 'static>>,
|
||||
}
|
||||
|
||||
impl NumericStepper {
|
||||
pub fn new(
|
||||
value: impl Into<SharedString>,
|
||||
on_decrement: impl Fn(&ClickEvent, &mut WindowContext) + 'static,
|
||||
on_increment: impl Fn(&ClickEvent, &mut WindowContext) + 'static,
|
||||
) -> Self {
|
||||
Self {
|
||||
value: value.into(),
|
||||
on_decrement: Box::new(on_decrement),
|
||||
on_increment: Box::new(on_increment),
|
||||
on_reset: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn on_reset(
|
||||
mut self,
|
||||
on_reset: impl Fn(&ClickEvent, &mut WindowContext) + 'static,
|
||||
) -> Self {
|
||||
self.on_reset = Some(Box::new(on_reset));
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl RenderOnce for NumericStepper {
|
||||
fn render(self, cx: &mut WindowContext) -> impl IntoElement {
|
||||
let shape = IconButtonShape::Square;
|
||||
let icon_size = IconSize::Small;
|
||||
|
||||
h_flex()
|
||||
.gap_1()
|
||||
.map(|element| {
|
||||
if let Some(on_reset) = self.on_reset {
|
||||
element.child(
|
||||
IconButton::new("reset", IconName::RotateCcw)
|
||||
.shape(shape)
|
||||
.icon_size(icon_size)
|
||||
.on_click(on_reset),
|
||||
)
|
||||
} else {
|
||||
element.child(
|
||||
h_flex()
|
||||
.size(icon_size.square(cx))
|
||||
.flex_none()
|
||||
.into_any_element(),
|
||||
)
|
||||
}
|
||||
})
|
||||
.child(
|
||||
h_flex()
|
||||
.gap_1()
|
||||
.px_1()
|
||||
.rounded_sm()
|
||||
.bg(cx.theme().colors().editor_background)
|
||||
.child(
|
||||
IconButton::new("decrement", IconName::Dash)
|
||||
.shape(shape)
|
||||
.icon_size(icon_size)
|
||||
.on_click(self.on_decrement),
|
||||
)
|
||||
.child(Label::new(self.value))
|
||||
.child(
|
||||
IconButton::new("increment", IconName::Plus)
|
||||
.shape(shape)
|
||||
.icon_size(icon_size)
|
||||
.on_click(self.on_increment),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
@ -1,5 +0,0 @@
|
||||
mod linux_window_controls;
|
||||
mod title_bar;
|
||||
mod windows_window_controls;
|
||||
|
||||
pub use title_bar::*;
|
Loading…
Reference in New Issue
Block a user