Add support for typing in the numeric stepper componenets

This commit is contained in:
Anthony 2025-08-19 14:29:23 -04:00
parent 43b4363b34
commit c5a258e0b8
9 changed files with 156 additions and 31 deletions

2
Cargo.lock generated
View file

@ -11095,6 +11095,7 @@ dependencies = [
"telemetry",
"theme",
"ui",
"ui_input",
"util",
"vim_mode_setting",
"workspace",
@ -17514,6 +17515,7 @@ dependencies = [
"component",
"editor",
"gpui",
"menu",
"settings",
"theme",
"ui",

View file

@ -5,8 +5,7 @@ use project::project_settings::{InlineBlameSettings, ProjectSettings};
use settings::{EditableSettingControl, Settings};
use theme::{FontFamilyCache, FontFamilyName, ThemeSettings};
use ui::{
CheckboxWithLabel, ContextMenu, DropdownMenu, NumericStepper, SettingsContainer, SettingsGroup,
prelude::*,
CheckboxWithLabel, ContextMenu, DropdownMenu, SettingsContainer, SettingsGroup, prelude::*,
};
use crate::EditorSettings;
@ -139,21 +138,12 @@ impl EditableSettingControl for BufferFontSizeControl {
impl RenderOnce for BufferFontSizeControl {
fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
let value = Self::read(cx);
let _value = Self::read(cx);
h_flex()
.gap_2()
.child(Icon::new(IconName::FontSize))
.child(NumericStepper::new(
"buffer-font-size",
value.to_string(),
move |_, _, cx| {
Self::write(value - px(1.), cx);
},
move |_, _, cx| {
Self::write(value + px(1.), cx);
},
))
.child(div()) // todo!(Numeric stepper was here)
}
}

View file

@ -39,6 +39,7 @@ settings.workspace = true
telemetry.workspace = true
theme.workspace = true
ui.workspace = true
ui_input.workspace = true
util.workspace = true
vim_mode_setting.workspace = true
workspace-hack.workspace = true

View file

@ -12,10 +12,10 @@ use project::project_settings::ProjectSettings;
use settings::{Settings as _, update_settings_file};
use theme::{FontFamilyCache, FontFamilyName, ThemeSettings};
use ui::{
ButtonLike, ListItem, ListItemSpacing, NumericStepper, PopoverMenu, SwitchField,
ToggleButtonGroup, ToggleButtonGroupStyle, ToggleButtonSimple, ToggleState, Tooltip,
prelude::*,
ButtonLike, ListItem, ListItemSpacing, PopoverMenu, SwitchField, ToggleButtonGroup,
ToggleButtonGroupStyle, ToggleButtonSimple, ToggleState, Tooltip, prelude::*,
};
use ui_input::NumericStepper;
use crate::{ImportCursorSettings, ImportVsCodeSettings, SettingsImportState};
@ -350,14 +350,19 @@ fn render_font_customization_section(
NumericStepper::new(
"ui-font-size",
ui_font_size.to_string(),
move |size, cx| {
write_ui_font_size(Pixels::from(size), cx);
},
move |_, _, cx| {
write_ui_font_size(ui_font_size - px(1.), cx);
},
move |_, _, cx| {
write_ui_font_size(ui_font_size + px(1.), cx);
},
window,
cx,
)
.style(ui::NumericStepperStyle::Outlined)
.style(ui_input::NumericStepperStyle::Outlined)
.tab_index({
*tab_index += 2;
*tab_index - 2
@ -414,14 +419,19 @@ fn render_font_customization_section(
NumericStepper::new(
"buffer-font-size",
buffer_font_size.to_string(),
move |size, cx| {
write_buffer_font_size(Pixels::from(size), cx);
},
move |_, _, cx| {
write_buffer_font_size(buffer_font_size - px(1.), cx);
},
move |_, _, cx| {
write_buffer_font_size(buffer_font_size + px(1.), cx);
},
window,
cx,
)
.style(ui::NumericStepperStyle::Outlined)
.style(ui_input::NumericStepperStyle::Outlined)
.tab_index({
*tab_index += 2;
*tab_index - 2

View file

@ -6,9 +6,10 @@ use theme::{
FontFamilyCache, FontFamilyName, SystemAppearance, ThemeMode, ThemeRegistry, ThemeSettings,
};
use ui::{
CheckboxWithLabel, ContextMenu, DropdownMenu, NumericStepper, SettingsContainer, SettingsGroup,
ToggleButton, prelude::*,
CheckboxWithLabel, ContextMenu, DropdownMenu, SettingsContainer, SettingsGroup, ToggleButton,
prelude::*,
};
use ui_input::NumericStepper;
#[derive(IntoElement)]
pub struct AppearanceSettingsControls {}
@ -254,7 +255,7 @@ impl EditableSettingControl for UiFontSizeControl {
}
impl RenderOnce for UiFontSizeControl {
fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
let value = Self::read(cx);
h_flex()
@ -263,12 +264,17 @@ impl RenderOnce for UiFontSizeControl {
.child(NumericStepper::new(
"ui-font-size",
value.to_string(),
move |size, cx| {
Self::write(Pixels::from(size), cx);
},
move |_, _, cx| {
Self::write(value - px(1.), cx);
},
move |_, _, cx| {
Self::write(value + px(1.), cx);
},
window,
cx,
))
}
}

View file

@ -22,7 +22,6 @@ mod list;
mod modal;
mod navigable;
mod notification;
mod numeric_stepper;
mod popover;
mod popover_menu;
mod progress;
@ -65,7 +64,6 @@ pub use list::*;
pub use modal::*;
pub use navigable::*;
pub use notification::*;
pub use numeric_stepper::*;
pub use popover::*;
pub use popover_menu::*;
pub use progress::*;

View file

@ -15,6 +15,7 @@ path = "src/ui_input.rs"
component.workspace = true
editor.workspace = true
gpui.workspace = true
menu.workspace = true
settings.workspace = true
theme.workspace = true
ui.workspace = true

View file

@ -1,6 +1,7 @@
use gpui::ClickEvent;
use editor::Editor;
use gpui::{ClickEvent, Entity, Focusable};
use crate::{IconButtonShape, prelude::*};
use ui::{IconButtonShape, prelude::*};
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
pub enum NumericStepperStyle {
@ -9,11 +10,21 @@ pub enum NumericStepperStyle {
Ghost,
}
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
pub enum NumericStepperMode {
#[default]
Read,
Edit,
}
#[derive(IntoElement, RegisterComponent)]
pub struct NumericStepper {
id: ElementId,
value: SharedString,
style: NumericStepperStyle,
input_field: Entity<Editor>,
mode: Entity<NumericStepperMode>,
set_value_to: Box<dyn Fn(usize, &mut App) + 'static>,
on_decrement: Box<dyn Fn(&ClickEvent, &mut Window, &mut App) + 'static>,
on_increment: Box<dyn Fn(&ClickEvent, &mut Window, &mut App) + 'static>,
/// Whether to reserve space for the reset button.
@ -26,15 +37,61 @@ impl NumericStepper {
pub fn new(
id: impl Into<ElementId>,
value: impl Into<SharedString>,
set_value_to: impl Fn(usize, &mut App) + 'static,
on_decrement: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static,
on_increment: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static,
window: &mut Window,
cx: &mut App,
) -> Self {
let id = id.into();
let value = value.into();
let (input_field, mode) = window.with_global_id(id.clone(), |global_id, window| {
// todo! Make sure that using this api is inline and appropriate with the codebase
window.with_element_state::<(Entity<Editor>, Entity<NumericStepperMode>), _>(
global_id,
|mut editor, window| {
let state = editor
.get_or_insert_with(|| {
let mode = cx.new(|_| NumericStepperMode::default());
let weak_mode = mode.downgrade();
let editor = cx.new(|cx| {
let editor = Editor::single_line(window, cx);
cx.on_focus_out(
&editor.focus_handle(cx),
window,
move |this, _, window, cx| {
this.clear(window, cx);
weak_mode
.update(cx, |mode, _| *mode = NumericStepperMode::Read)
.ok();
},
)
.detach();
editor
});
(editor, mode)
})
.clone();
(state.clone(), state)
},
)
});
Self {
id: id.into(),
value: value.into(),
style: NumericStepperStyle::default(),
id,
value,
input_field,
mode,
set_value_to: Box::new(set_value_to),
on_decrement: Box::new(on_decrement),
on_increment: Box::new(on_increment),
style: NumericStepperStyle::default(),
reserve_space_for_reset: false,
on_reset: None,
tab_index: None,
@ -74,7 +131,7 @@ impl RenderOnce for NumericStepper {
let mut tab_index = self.tab_index;
h_flex()
.id(self.id)
.id(self.id.clone())
.gap_1()
.map(|element| {
if let Some(on_reset) = self.on_reset {
@ -146,7 +203,59 @@ impl RenderOnce for NumericStepper {
)
}
})
.child(Label::new(self.value).mx_3())
.child(if matches!(self.mode.read(cx), NumericStepperMode::Read) {
div()
.id(SharedString::new(format!(
"numeric_stepper_label{}",
&self.id,
)))
.child(Label::new(self.value).mx_3())
.on_click({
let mode = self.mode.downgrade();
let input_field_focus_handle = self.input_field.focus_handle(cx);
move |click, window, cx| {
if click.click_count() == 2 {
mode.update(cx, |mode, _| {
*mode = NumericStepperMode::Edit;
})
.ok();
window.focus(&input_field_focus_handle);
}
}
})
.into_any_element()
} else {
div()
.child(self.input_field.clone())
.child("todo!(This should be removed. It's only here to get input_field to render correctly)")
.on_action::<menu::Confirm>({
let input_field = self.input_field.downgrade();
let mode = self.mode.downgrade();
let set_value = self.set_value_to;
move |_, _, cx| {
input_field
.update(cx, |input_field, cx| {
if let Some(number) =
input_field.text(cx).parse::<usize>().ok()
{
set_value(number, cx);
mode.update(cx, |mode, _| {
*mode = NumericStepperMode::Read
})
.ok();
}
})
.ok();
}
})
.w_full()
.mx_3()
.into_any_element()
})
.map(|increment| {
if is_outlined {
increment.child(
@ -201,7 +310,7 @@ impl Component for NumericStepper {
Some("A button used to increment or decrement a numeric value.")
}
fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
fn preview(window: &mut Window, cx: &mut App) -> Option<AnyElement> {
Some(
v_flex()
.gap_6()
@ -213,8 +322,11 @@ impl Component for NumericStepper {
NumericStepper::new(
"numeric-stepper-component-preview",
"10",
move |_, _| {},
move |_, _, _| {},
move |_, _, _| {},
window,
cx,
)
.into_any_element(),
),
@ -223,8 +335,11 @@ impl Component for NumericStepper {
NumericStepper::new(
"numeric-stepper-with-border-component-preview",
"10",
move |_, _| {},
move |_, _, _| {},
move |_, _, _| {},
window,
cx,
)
.style(NumericStepperStyle::Outlined)
.into_any_element(),

View file

@ -4,10 +4,12 @@
//!
//! It can't be located in the `ui` crate because it depends on `editor`.
//!
mod numeric_stepper;
use component::{example_group, single_example};
use editor::{Editor, EditorElement, EditorStyle};
use gpui::{App, Entity, FocusHandle, Focusable, FontStyle, Hsla, TextStyle};
pub use numeric_stepper::*;
use settings::Settings;
use theme::ThemeSettings;
use ui::prelude::*;