Refactor Numeric stepper to use generics
Users now pass in an entity<T: NumericStepperType> that can observes T for changes and can react to those changes Co-authored-by: Mikayla Maki <mikayla.c.maki@gmail.com>
This commit is contained in:
parent
c5a258e0b8
commit
e6f06f14fd
6 changed files with 338 additions and 289 deletions
|
@ -143,7 +143,7 @@ impl RenderOnce for BufferFontSizeControl {
|
||||||
h_flex()
|
h_flex()
|
||||||
.gap_2()
|
.gap_2()
|
||||||
.child(Icon::new(IconName::FontSize))
|
.child(Icon::new(IconName::FontSize))
|
||||||
.child(div()) // todo!(Numeric stepper was here)
|
.child(div()) // TODO: Re-evaluate this whole crate once settings UI is complete
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2504,7 +2504,7 @@ impl Window {
|
||||||
&mut self,
|
&mut self,
|
||||||
key: impl Into<ElementId>,
|
key: impl Into<ElementId>,
|
||||||
cx: &mut App,
|
cx: &mut App,
|
||||||
init: impl FnOnce(&mut Self, &mut App) -> S,
|
init: impl FnOnce(&mut Self, &mut Context<S>) -> S,
|
||||||
) -> Entity<S> {
|
) -> Entity<S> {
|
||||||
let current_view = self.current_view();
|
let current_view = self.current_view();
|
||||||
self.with_global_id(key.into(), |global_id, window| {
|
self.with_global_id(key.into(), |global_id, window| {
|
||||||
|
@ -2537,7 +2537,7 @@ impl Window {
|
||||||
pub fn use_state<S: 'static>(
|
pub fn use_state<S: 'static>(
|
||||||
&mut self,
|
&mut self,
|
||||||
cx: &mut App,
|
cx: &mut App,
|
||||||
init: impl FnOnce(&mut Self, &mut App) -> S,
|
init: impl FnOnce(&mut Self, &mut Context<S>) -> S,
|
||||||
) -> Entity<S> {
|
) -> Entity<S> {
|
||||||
self.use_keyed_state(
|
self.use_keyed_state(
|
||||||
ElementId::CodeLocation(*core::panic::Location::caller()),
|
ElementId::CodeLocation(*core::panic::Location::caller()),
|
||||||
|
|
|
@ -15,7 +15,6 @@ use ui::{
|
||||||
ButtonLike, ListItem, ListItemSpacing, PopoverMenu, SwitchField, ToggleButtonGroup,
|
ButtonLike, ListItem, ListItemSpacing, PopoverMenu, SwitchField, ToggleButtonGroup,
|
||||||
ToggleButtonGroupStyle, ToggleButtonSimple, ToggleState, Tooltip, prelude::*,
|
ToggleButtonGroupStyle, ToggleButtonSimple, ToggleState, Tooltip, prelude::*,
|
||||||
};
|
};
|
||||||
use ui_input::NumericStepper;
|
|
||||||
|
|
||||||
use crate::{ImportCursorSettings, ImportVsCodeSettings, SettingsImportState};
|
use crate::{ImportCursorSettings, ImportVsCodeSettings, SettingsImportState};
|
||||||
|
|
||||||
|
@ -110,7 +109,7 @@ fn write_ui_font_family(font: SharedString, cx: &mut App) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write_ui_font_size(size: Pixels, cx: &mut App) {
|
fn _write_ui_font_size(size: Pixels, cx: &mut App) {
|
||||||
let fs = <dyn Fs>::global(cx);
|
let fs = <dyn Fs>::global(cx);
|
||||||
|
|
||||||
update_settings_file::<ThemeSettings>(fs, cx, move |theme_settings, _| {
|
update_settings_file::<ThemeSettings>(fs, cx, move |theme_settings, _| {
|
||||||
|
@ -118,7 +117,7 @@ fn write_ui_font_size(size: Pixels, cx: &mut App) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write_buffer_font_size(size: Pixels, cx: &mut App) {
|
fn _write_buffer_font_size(size: Pixels, cx: &mut App) {
|
||||||
let fs = <dyn Fs>::global(cx);
|
let fs = <dyn Fs>::global(cx);
|
||||||
|
|
||||||
update_settings_file::<ThemeSettings>(fs, cx, move |theme_settings, _| {
|
update_settings_file::<ThemeSettings>(fs, cx, move |theme_settings, _| {
|
||||||
|
@ -278,10 +277,10 @@ fn render_font_customization_section(
|
||||||
cx: &mut App,
|
cx: &mut App,
|
||||||
) -> impl IntoElement {
|
) -> impl IntoElement {
|
||||||
let theme_settings = ThemeSettings::get_global(cx);
|
let theme_settings = ThemeSettings::get_global(cx);
|
||||||
let ui_font_size = theme_settings.ui_font_size(cx);
|
// let ui_font_size = theme_settings.ui_font_size(cx);
|
||||||
let ui_font_family = theme_settings.ui_font.family.clone();
|
let ui_font_family = theme_settings.ui_font.family.clone();
|
||||||
let buffer_font_family = theme_settings.buffer_font.family.clone();
|
let buffer_font_family = theme_settings.buffer_font.family.clone();
|
||||||
let buffer_font_size = theme_settings.buffer_font_size(cx);
|
// let buffer_font_size = theme_settings.buffer_font_size(cx);
|
||||||
|
|
||||||
let ui_font_picker =
|
let ui_font_picker =
|
||||||
cx.new(|cx| font_picker(ui_font_family.clone(), write_ui_font_family, window, cx));
|
cx.new(|cx| font_picker(ui_font_family.clone(), write_ui_font_family, window, cx));
|
||||||
|
@ -307,67 +306,62 @@ fn render_font_customization_section(
|
||||||
.gap_1()
|
.gap_1()
|
||||||
.child(Label::new("UI Font"))
|
.child(Label::new("UI Font"))
|
||||||
.child(
|
.child(
|
||||||
h_flex()
|
h_flex().w_full().justify_between().gap_2().child(
|
||||||
.w_full()
|
PopoverMenu::new("ui-font-picker")
|
||||||
.justify_between()
|
.menu({
|
||||||
.gap_2()
|
let ui_font_picker = ui_font_picker.clone();
|
||||||
.child(
|
move |_window, _cx| Some(ui_font_picker.clone())
|
||||||
PopoverMenu::new("ui-font-picker")
|
})
|
||||||
.menu({
|
.trigger(
|
||||||
let ui_font_picker = ui_font_picker.clone();
|
ButtonLike::new("ui-font-family-button")
|
||||||
move |_window, _cx| Some(ui_font_picker.clone())
|
.style(ButtonStyle::Outlined)
|
||||||
})
|
.size(ButtonSize::Medium)
|
||||||
.trigger(
|
.full_width()
|
||||||
ButtonLike::new("ui-font-family-button")
|
.tab_index({
|
||||||
.style(ButtonStyle::Outlined)
|
*tab_index += 1;
|
||||||
.size(ButtonSize::Medium)
|
*tab_index - 1
|
||||||
.full_width()
|
})
|
||||||
.tab_index({
|
.child(
|
||||||
*tab_index += 1;
|
h_flex()
|
||||||
*tab_index - 1
|
.w_full()
|
||||||
})
|
.justify_between()
|
||||||
.child(
|
.child(Label::new(ui_font_family))
|
||||||
h_flex()
|
.child(
|
||||||
.w_full()
|
Icon::new(IconName::ChevronUpDown)
|
||||||
.justify_between()
|
.color(Color::Muted)
|
||||||
.child(Label::new(ui_font_family))
|
.size(IconSize::XSmall),
|
||||||
.child(
|
),
|
||||||
Icon::new(IconName::ChevronUpDown)
|
),
|
||||||
.color(Color::Muted)
|
|
||||||
.size(IconSize::XSmall),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.full_width(true)
|
|
||||||
.anchor(gpui::Corner::TopLeft)
|
|
||||||
.offset(gpui::Point {
|
|
||||||
x: px(0.0),
|
|
||||||
y: px(4.0),
|
|
||||||
})
|
|
||||||
.with_handle(ui_font_handle),
|
|
||||||
)
|
|
||||||
.child(
|
|
||||||
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_input::NumericStepperStyle::Outlined)
|
.full_width(true)
|
||||||
.tab_index({
|
.anchor(gpui::Corner::TopLeft)
|
||||||
*tab_index += 2;
|
.offset(gpui::Point {
|
||||||
*tab_index - 2
|
x: px(0.0),
|
||||||
}),
|
y: px(4.0),
|
||||||
),
|
})
|
||||||
|
.with_handle(ui_font_handle),
|
||||||
|
), // .child(
|
||||||
|
// 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_input::NumericStepperStyle::Outlined)
|
||||||
|
// .tab_index({
|
||||||
|
// *tab_index += 2;
|
||||||
|
// *tab_index - 2
|
||||||
|
// }),
|
||||||
|
// ),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.child(
|
.child(
|
||||||
|
@ -376,67 +370,62 @@ fn render_font_customization_section(
|
||||||
.gap_1()
|
.gap_1()
|
||||||
.child(Label::new("Editor Font"))
|
.child(Label::new("Editor Font"))
|
||||||
.child(
|
.child(
|
||||||
h_flex()
|
h_flex().w_full().justify_between().gap_2().child(
|
||||||
.w_full()
|
PopoverMenu::new("buffer-font-picker")
|
||||||
.justify_between()
|
.menu({
|
||||||
.gap_2()
|
let buffer_font_picker = buffer_font_picker.clone();
|
||||||
.child(
|
move |_window, _cx| Some(buffer_font_picker.clone())
|
||||||
PopoverMenu::new("buffer-font-picker")
|
})
|
||||||
.menu({
|
.trigger(
|
||||||
let buffer_font_picker = buffer_font_picker.clone();
|
ButtonLike::new("buffer-font-family-button")
|
||||||
move |_window, _cx| Some(buffer_font_picker.clone())
|
.style(ButtonStyle::Outlined)
|
||||||
})
|
.size(ButtonSize::Medium)
|
||||||
.trigger(
|
.full_width()
|
||||||
ButtonLike::new("buffer-font-family-button")
|
.tab_index({
|
||||||
.style(ButtonStyle::Outlined)
|
*tab_index += 1;
|
||||||
.size(ButtonSize::Medium)
|
*tab_index - 1
|
||||||
.full_width()
|
})
|
||||||
.tab_index({
|
.child(
|
||||||
*tab_index += 1;
|
h_flex()
|
||||||
*tab_index - 1
|
.w_full()
|
||||||
})
|
.justify_between()
|
||||||
.child(
|
.child(Label::new(buffer_font_family))
|
||||||
h_flex()
|
.child(
|
||||||
.w_full()
|
Icon::new(IconName::ChevronUpDown)
|
||||||
.justify_between()
|
.color(Color::Muted)
|
||||||
.child(Label::new(buffer_font_family))
|
.size(IconSize::XSmall),
|
||||||
.child(
|
),
|
||||||
Icon::new(IconName::ChevronUpDown)
|
),
|
||||||
.color(Color::Muted)
|
|
||||||
.size(IconSize::XSmall),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.full_width(true)
|
|
||||||
.anchor(gpui::Corner::TopLeft)
|
|
||||||
.offset(gpui::Point {
|
|
||||||
x: px(0.0),
|
|
||||||
y: px(4.0),
|
|
||||||
})
|
|
||||||
.with_handle(buffer_font_handle),
|
|
||||||
)
|
|
||||||
.child(
|
|
||||||
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_input::NumericStepperStyle::Outlined)
|
.full_width(true)
|
||||||
.tab_index({
|
.anchor(gpui::Corner::TopLeft)
|
||||||
*tab_index += 2;
|
.offset(gpui::Point {
|
||||||
*tab_index - 2
|
x: px(0.0),
|
||||||
}),
|
y: px(4.0),
|
||||||
),
|
})
|
||||||
|
.with_handle(buffer_font_handle),
|
||||||
|
), // .child( todo!
|
||||||
|
// 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_input::NumericStepperStyle::Outlined)
|
||||||
|
// .tab_index({
|
||||||
|
// *tab_index += 2;
|
||||||
|
// *tab_index - 2
|
||||||
|
// }),
|
||||||
|
// ),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,7 @@ use ui::{
|
||||||
CheckboxWithLabel, ContextMenu, DropdownMenu, SettingsContainer, SettingsGroup, ToggleButton,
|
CheckboxWithLabel, ContextMenu, DropdownMenu, SettingsContainer, SettingsGroup, ToggleButton,
|
||||||
prelude::*,
|
prelude::*,
|
||||||
};
|
};
|
||||||
use ui_input::NumericStepper;
|
// use ui_input::NumericStepper;
|
||||||
|
|
||||||
#[derive(IntoElement)]
|
#[derive(IntoElement)]
|
||||||
pub struct AppearanceSettingsControls {}
|
pub struct AppearanceSettingsControls {}
|
||||||
|
@ -255,27 +255,26 @@ impl EditableSettingControl for UiFontSizeControl {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RenderOnce 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);
|
// let value = Self::read(cx);
|
||||||
|
|
||||||
h_flex()
|
h_flex().gap_2().child(Icon::new(IconName::FontSize))
|
||||||
.gap_2()
|
// TODO: Re-evaluate this whole crate once settings UI project starts
|
||||||
.child(Icon::new(IconName::FontSize))
|
// .child(NumericStepper::new(
|
||||||
.child(NumericStepper::new(
|
// "ui-font-size",
|
||||||
"ui-font-size",
|
// value.to_string(),
|
||||||
value.to_string(),
|
// move |size, cx| {
|
||||||
move |size, cx| {
|
// Self::write(Pixels::from(size), cx);
|
||||||
Self::write(Pixels::from(size), cx);
|
// },
|
||||||
},
|
// move |_, _, cx| {
|
||||||
move |_, _, cx| {
|
// Self::write(value - px(1.), cx);
|
||||||
Self::write(value - px(1.), cx);
|
// },
|
||||||
},
|
// move |_, _, cx| {
|
||||||
move |_, _, cx| {
|
// Self::write(value + px(1.), cx);
|
||||||
Self::write(value + px(1.), cx);
|
// },
|
||||||
},
|
// window,
|
||||||
window,
|
// cx,
|
||||||
cx,
|
// ))
|
||||||
))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,11 @@
|
||||||
|
use std::{
|
||||||
|
fmt::Display,
|
||||||
|
ops::{Add, Sub},
|
||||||
|
str::FromStr,
|
||||||
|
};
|
||||||
|
|
||||||
use editor::Editor;
|
use editor::Editor;
|
||||||
use gpui::{ClickEvent, Entity, Focusable};
|
use gpui::{ClickEvent, Entity, FocusHandle, Focusable, Modifiers};
|
||||||
|
|
||||||
use ui::{IconButtonShape, prelude::*};
|
use ui::{IconButtonShape, prelude::*};
|
||||||
|
|
||||||
|
@ -17,94 +23,128 @@ pub enum NumericStepperMode {
|
||||||
Edit,
|
Edit,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(IntoElement, RegisterComponent)]
|
pub trait NumericStepperType:
|
||||||
pub struct NumericStepper {
|
Display + Add<Output = Self> + Sub<Output = Self> + Copy + Clone + Sized + FromStr + 'static
|
||||||
|
{
|
||||||
|
fn default_format(value: &Self) -> String {
|
||||||
|
format!("{}", value)
|
||||||
|
}
|
||||||
|
fn default_step() -> Self;
|
||||||
|
fn large_step() -> Self;
|
||||||
|
fn small_step() -> Self;
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! impl_numeric_stepper_int {
|
||||||
|
($type:ident) => {
|
||||||
|
impl NumericStepperType for $type {
|
||||||
|
fn default_step() -> Self {
|
||||||
|
1
|
||||||
|
}
|
||||||
|
|
||||||
|
fn large_step() -> Self {
|
||||||
|
10
|
||||||
|
}
|
||||||
|
|
||||||
|
fn small_step() -> Self {
|
||||||
|
1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! impl_numeric_stepper_float {
|
||||||
|
($type:ident) => {
|
||||||
|
impl NumericStepperType for $type {
|
||||||
|
fn default_format(value: &Self) -> String {
|
||||||
|
format!("{:.2}", value)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn default_step() -> Self {
|
||||||
|
1.0
|
||||||
|
}
|
||||||
|
|
||||||
|
fn large_step() -> Self {
|
||||||
|
10.0
|
||||||
|
}
|
||||||
|
|
||||||
|
fn small_step() -> Self {
|
||||||
|
0.1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
impl_numeric_stepper_float!(f32);
|
||||||
|
impl_numeric_stepper_float!(f64);
|
||||||
|
impl_numeric_stepper_int!(isize);
|
||||||
|
impl_numeric_stepper_int!(usize);
|
||||||
|
impl_numeric_stepper_int!(i32);
|
||||||
|
impl_numeric_stepper_int!(u32);
|
||||||
|
impl_numeric_stepper_int!(i64);
|
||||||
|
impl_numeric_stepper_int!(u64);
|
||||||
|
|
||||||
|
// TODO: Add a new register component macro to support a specific type when using generics
|
||||||
|
pub struct NumericStepper<T> {
|
||||||
id: ElementId,
|
id: ElementId,
|
||||||
value: SharedString,
|
value: Entity<T>,
|
||||||
style: NumericStepperStyle,
|
style: NumericStepperStyle,
|
||||||
input_field: Entity<Editor>,
|
focus_handle: FocusHandle,
|
||||||
mode: Entity<NumericStepperMode>,
|
mode: Entity<NumericStepperMode>,
|
||||||
set_value_to: Box<dyn Fn(usize, &mut App) + 'static>,
|
format: Box<dyn FnOnce(&T) -> String>,
|
||||||
on_decrement: Box<dyn Fn(&ClickEvent, &mut Window, &mut App) + 'static>,
|
large_step: T,
|
||||||
on_increment: Box<dyn Fn(&ClickEvent, &mut Window, &mut App) + 'static>,
|
small_step: T,
|
||||||
/// Whether to reserve space for the reset button.
|
step: T,
|
||||||
reserve_space_for_reset: bool,
|
|
||||||
on_reset: Option<Box<dyn Fn(&ClickEvent, &mut Window, &mut App) + 'static>>,
|
on_reset: Option<Box<dyn Fn(&ClickEvent, &mut Window, &mut App) + 'static>>,
|
||||||
tab_index: Option<isize>,
|
tab_index: Option<isize>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl NumericStepper {
|
impl<T: NumericStepperType> NumericStepper<T> {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
id: impl Into<ElementId>,
|
id: impl Into<ElementId>,
|
||||||
value: impl Into<SharedString>,
|
value: Entity<T>,
|
||||||
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,
|
window: &mut Window,
|
||||||
cx: &mut App,
|
cx: &mut App,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let id = id.into();
|
let id = id.into();
|
||||||
let value = value.into();
|
let mode = window.use_state(cx, |_, _| NumericStepperMode::default());
|
||||||
|
|
||||||
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 {
|
Self {
|
||||||
id,
|
id,
|
||||||
value,
|
focus_handle: cx.focus_handle(),
|
||||||
input_field,
|
|
||||||
mode,
|
mode,
|
||||||
set_value_to: Box::new(set_value_to),
|
value,
|
||||||
on_decrement: Box::new(on_decrement),
|
|
||||||
on_increment: Box::new(on_increment),
|
|
||||||
style: NumericStepperStyle::default(),
|
style: NumericStepperStyle::default(),
|
||||||
reserve_space_for_reset: false,
|
format: Box::new(T::default_format),
|
||||||
|
large_step: T::large_step(),
|
||||||
|
step: T::default_step(),
|
||||||
|
small_step: T::small_step(),
|
||||||
on_reset: None,
|
on_reset: None,
|
||||||
tab_index: None,
|
tab_index: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn style(mut self, style: NumericStepperStyle) -> Self {
|
pub fn format(mut self, format: impl FnOnce(&T) -> String + 'static) -> Self {
|
||||||
self.style = style;
|
self.format = Box::new(format);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn reserve_space_for_reset(mut self, reserve_space_for_reset: bool) -> Self {
|
pub fn small_step(mut self, step: T) -> Self {
|
||||||
self.reserve_space_for_reset = reserve_space_for_reset;
|
self.small_step = step;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn normal_step(mut self, step: T) -> Self {
|
||||||
|
self.step = step;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn large_step(mut self, step: T) -> Self {
|
||||||
|
self.large_step = step;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn style(mut self, style: NumericStepperStyle) -> Self {
|
||||||
|
self.style = style;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -122,7 +162,15 @@ impl NumericStepper {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RenderOnce for NumericStepper {
|
impl<T: NumericStepperType> IntoElement for NumericStepper<T> {
|
||||||
|
type Element = gpui::Component<Self>;
|
||||||
|
|
||||||
|
fn into_element(self) -> Self::Element {
|
||||||
|
gpui::Component::new(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: NumericStepperType> RenderOnce for NumericStepper<T> {
|
||||||
fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
|
fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
|
||||||
let shape = IconButtonShape::Square;
|
let shape = IconButtonShape::Square;
|
||||||
let icon_size = IconSize::Small;
|
let icon_size = IconSize::Small;
|
||||||
|
@ -130,31 +178,36 @@ impl RenderOnce for NumericStepper {
|
||||||
let is_outlined = matches!(self.style, NumericStepperStyle::Outlined);
|
let is_outlined = matches!(self.style, NumericStepperStyle::Outlined);
|
||||||
let mut tab_index = self.tab_index;
|
let mut tab_index = self.tab_index;
|
||||||
|
|
||||||
|
let get_step = {
|
||||||
|
let large_step = self.large_step;
|
||||||
|
let step = self.step;
|
||||||
|
let small_step = self.small_step;
|
||||||
|
move |modifiers: Modifiers| -> T {
|
||||||
|
if modifiers.shift {
|
||||||
|
large_step
|
||||||
|
} else if modifiers.alt {
|
||||||
|
small_step
|
||||||
|
} else {
|
||||||
|
step
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
h_flex()
|
h_flex()
|
||||||
.id(self.id.clone())
|
.id(self.id.clone())
|
||||||
|
.track_focus(&self.focus_handle)
|
||||||
.gap_1()
|
.gap_1()
|
||||||
.map(|element| {
|
.when_some(self.on_reset, |this, on_reset| {
|
||||||
if let Some(on_reset) = self.on_reset {
|
this.child(
|
||||||
element.child(
|
IconButton::new("reset", IconName::RotateCcw)
|
||||||
IconButton::new("reset", IconName::RotateCcw)
|
.shape(shape)
|
||||||
.shape(shape)
|
.icon_size(icon_size)
|
||||||
.icon_size(icon_size)
|
.when_some(tab_index.as_mut(), |this, tab_index| {
|
||||||
.when_some(tab_index.as_mut(), |this, tab_index| {
|
*tab_index += 1;
|
||||||
*tab_index += 1;
|
this.tab_index(*tab_index - 1)
|
||||||
this.tab_index(*tab_index - 1)
|
})
|
||||||
})
|
.on_click(on_reset),
|
||||||
.on_click(on_reset),
|
)
|
||||||
)
|
|
||||||
} else if self.reserve_space_for_reset {
|
|
||||||
element.child(
|
|
||||||
h_flex()
|
|
||||||
.size(icon_size.square(window, cx))
|
|
||||||
.flex_none()
|
|
||||||
.into_any_element(),
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
element
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
.child(
|
.child(
|
||||||
h_flex()
|
h_flex()
|
||||||
|
@ -171,6 +224,15 @@ impl RenderOnce for NumericStepper {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.map(|decrement| {
|
.map(|decrement| {
|
||||||
|
let decrement_handler = {
|
||||||
|
let value = self.value.clone();
|
||||||
|
move |click: &ClickEvent, _: &mut Window, cx: &mut App| {
|
||||||
|
let step = get_step(click.modifiers());
|
||||||
|
let current_value = *value.read(cx);
|
||||||
|
value.write(cx, current_value - step);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
if is_outlined {
|
if is_outlined {
|
||||||
decrement.child(
|
decrement.child(
|
||||||
h_flex()
|
h_flex()
|
||||||
|
@ -188,7 +250,7 @@ impl RenderOnce for NumericStepper {
|
||||||
style.bg(cx.theme().colors().element_hover)
|
style.bg(cx.theme().colors().element_hover)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.on_click(self.on_decrement),
|
.on_click(decrement_handler),
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
decrement.child(
|
decrement.child(
|
||||||
|
@ -199,64 +261,66 @@ impl RenderOnce for NumericStepper {
|
||||||
*tab_index += 1;
|
*tab_index += 1;
|
||||||
this.tab_index(*tab_index - 1)
|
this.tab_index(*tab_index - 1)
|
||||||
})
|
})
|
||||||
.on_click(self.on_decrement),
|
.on_click(decrement_handler),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.child(if matches!(self.mode.read(cx), NumericStepperMode::Read) {
|
.child(match *self.mode.read(cx) {
|
||||||
div()
|
NumericStepperMode::Read => div()
|
||||||
.id(SharedString::new(format!(
|
.id("numeric_stepper_label")
|
||||||
"numeric_stepper_label{}",
|
.child(Label::new((self.format)(self.value.read(cx))).mx_3())
|
||||||
&self.id,
|
|
||||||
)))
|
|
||||||
.child(Label::new(self.value).mx_3())
|
|
||||||
.on_click({
|
.on_click({
|
||||||
let mode = self.mode.downgrade();
|
let mode = self.mode.clone();
|
||||||
let input_field_focus_handle = self.input_field.focus_handle(cx);
|
|
||||||
|
|
||||||
move |click, window, cx| {
|
move |click, _, cx| {
|
||||||
if click.click_count() == 2 {
|
if click.click_count() == 2 {
|
||||||
mode.update(cx, |mode, _| {
|
mode.write(cx, NumericStepperMode::Edit);
|
||||||
*mode = NumericStepperMode::Edit;
|
|
||||||
})
|
|
||||||
.ok();
|
|
||||||
|
|
||||||
window.focus(&input_field_focus_handle);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.into_any_element()
|
.into_any_element(),
|
||||||
} else {
|
NumericStepperMode::Edit => div()
|
||||||
div()
|
.child(window.use_state(cx, {
|
||||||
.child(self.input_field.clone())
|
|window, cx| {
|
||||||
.child("todo!(This should be removed. It's only here to get input_field to render correctly)")
|
let mut editor = Editor::single_line(window, cx);
|
||||||
|
editor.set_text(format!("{}", self.value.read(cx)), window, cx);
|
||||||
|
cx.on_focus_out(&editor.focus_handle(cx), window, {
|
||||||
|
let mode = self.mode.clone();
|
||||||
|
let value = self.value.clone();
|
||||||
|
move |this, _, _window, cx| {
|
||||||
|
if let Ok(new_value) = this.text(cx).parse::<T>() {
|
||||||
|
value.write(cx, new_value);
|
||||||
|
};
|
||||||
|
mode.write(cx, NumericStepperMode::Read);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.detach();
|
||||||
|
|
||||||
|
window.focus(&editor.focus_handle(cx));
|
||||||
|
|
||||||
|
editor
|
||||||
|
}
|
||||||
|
}))
|
||||||
.on_action::<menu::Confirm>({
|
.on_action::<menu::Confirm>({
|
||||||
let input_field = self.input_field.downgrade();
|
let focus = self.focus_handle.clone();
|
||||||
let mode = self.mode.downgrade();
|
move |_, window, _| {
|
||||||
let set_value = self.set_value_to;
|
window.focus(&focus);
|
||||||
|
|
||||||
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()
|
.w_full()
|
||||||
.mx_3()
|
.mx_3()
|
||||||
.into_any_element()
|
.into_any_element(),
|
||||||
})
|
})
|
||||||
.map(|increment| {
|
.map(|increment| {
|
||||||
|
let increment_handler = {
|
||||||
|
let value = self.value.clone();
|
||||||
|
move |click: &ClickEvent, _: &mut Window, cx: &mut App| {
|
||||||
|
let step = get_step(click.modifiers());
|
||||||
|
let current_value = *value.read(cx);
|
||||||
|
value.write(cx, current_value + step);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
if is_outlined {
|
if is_outlined {
|
||||||
increment.child(
|
increment.child(
|
||||||
h_flex()
|
h_flex()
|
||||||
|
@ -274,7 +338,7 @@ impl RenderOnce for NumericStepper {
|
||||||
style.bg(cx.theme().colors().element_hover)
|
style.bg(cx.theme().colors().element_hover)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.on_click(self.on_increment),
|
.on_click(increment_handler),
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
increment.child(
|
increment.child(
|
||||||
|
@ -285,7 +349,7 @@ impl RenderOnce for NumericStepper {
|
||||||
*tab_index += 1;
|
*tab_index += 1;
|
||||||
this.tab_index(*tab_index - 1)
|
this.tab_index(*tab_index - 1)
|
||||||
})
|
})
|
||||||
.on_click(self.on_increment),
|
.on_click(increment_handler),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
|
@ -293,7 +357,7 @@ impl RenderOnce for NumericStepper {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Component for NumericStepper {
|
impl<T: NumericStepperType> Component for NumericStepper<T> {
|
||||||
fn scope() -> ComponentScope {
|
fn scope() -> ComponentScope {
|
||||||
ComponentScope::Input
|
ComponentScope::Input
|
||||||
}
|
}
|
||||||
|
@ -311,6 +375,8 @@ impl Component for NumericStepper {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn preview(window: &mut Window, cx: &mut App) -> Option<AnyElement> {
|
fn preview(window: &mut Window, cx: &mut App) -> Option<AnyElement> {
|
||||||
|
let first_stepper = window.use_state(cx, |_, _| 10usize);
|
||||||
|
let second_stepper = window.use_state(cx, |_, _| 10.0);
|
||||||
Some(
|
Some(
|
||||||
v_flex()
|
v_flex()
|
||||||
.gap_6()
|
.gap_6()
|
||||||
|
@ -321,10 +387,7 @@ impl Component for NumericStepper {
|
||||||
"Default",
|
"Default",
|
||||||
NumericStepper::new(
|
NumericStepper::new(
|
||||||
"numeric-stepper-component-preview",
|
"numeric-stepper-component-preview",
|
||||||
"10",
|
first_stepper,
|
||||||
move |_, _| {},
|
|
||||||
move |_, _, _| {},
|
|
||||||
move |_, _, _| {},
|
|
||||||
window,
|
window,
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
|
@ -334,10 +397,7 @@ impl Component for NumericStepper {
|
||||||
"Outlined",
|
"Outlined",
|
||||||
NumericStepper::new(
|
NumericStepper::new(
|
||||||
"numeric-stepper-with-border-component-preview",
|
"numeric-stepper-with-border-component-preview",
|
||||||
"10",
|
second_stepper,
|
||||||
move |_, _| {},
|
|
||||||
move |_, _, _| {},
|
|
||||||
move |_, _, _| {},
|
|
||||||
window,
|
window,
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
|
|
|
@ -4,6 +4,7 @@ use syn::{DeriveInput, parse_macro_input};
|
||||||
|
|
||||||
pub fn derive_register_component(input: TokenStream) -> TokenStream {
|
pub fn derive_register_component(input: TokenStream) -> TokenStream {
|
||||||
let input = parse_macro_input!(input as DeriveInput);
|
let input = parse_macro_input!(input as DeriveInput);
|
||||||
|
|
||||||
let name = input.ident;
|
let name = input.ident;
|
||||||
let register_fn_name = syn::Ident::new(
|
let register_fn_name = syn::Ident::new(
|
||||||
&format!("__component_registry_internal_register_{}", name),
|
&format!("__component_registry_internal_register_{}", name),
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue