Fine tune UX for new numeric stepper
This commit is contained in:
parent
b775a2e3cb
commit
e47b9d3b38
4 changed files with 193 additions and 140 deletions
|
@ -69,6 +69,20 @@ impl<'a, T: 'static> Context<'a, T> {
|
|||
})
|
||||
}
|
||||
|
||||
/// Observe changes to ourselves
|
||||
pub fn observe_self(
|
||||
&mut self,
|
||||
mut on_event: impl FnMut(&mut T, &mut Context<T>) + 'static,
|
||||
) -> Subscription
|
||||
where
|
||||
T: 'static,
|
||||
{
|
||||
let this = self.entity();
|
||||
self.app.observe(&this, move |this, cx| {
|
||||
this.update(cx, |this, cx| on_event(this, cx))
|
||||
})
|
||||
}
|
||||
|
||||
/// Subscribe to an event type from another entity
|
||||
pub fn subscribe<T2, Evt>(
|
||||
&mut self,
|
||||
|
|
|
@ -4838,6 +4838,12 @@ impl<T: Into<SharedString>> From<(ElementId, T)> for ElementId {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<core::panic::Location<'static>> for ElementId {
|
||||
fn from(value: core::panic::Location<'static>) -> Self {
|
||||
Self::CodeLocation(value)
|
||||
}
|
||||
}
|
||||
|
||||
/// A rectangle to be rendered in the window at the given position and size.
|
||||
/// Passed as an argument [`Window::paint_quad`].
|
||||
#[derive(Clone)]
|
||||
|
|
|
@ -15,6 +15,7 @@ use ui::{
|
|||
ButtonLike, ListItem, ListItemSpacing, PopoverMenu, SwitchField, ToggleButtonGroup,
|
||||
ToggleButtonGroupStyle, ToggleButtonSimple, ToggleState, Tooltip, prelude::*,
|
||||
};
|
||||
use ui_input::NumericStepper;
|
||||
|
||||
use crate::{ImportCursorSettings, ImportVsCodeSettings, SettingsImportState};
|
||||
|
||||
|
@ -109,7 +110,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);
|
||||
|
||||
update_settings_file::<ThemeSettings>(fs, cx, move |theme_settings, _| {
|
||||
|
@ -117,7 +118,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);
|
||||
|
||||
update_settings_file::<ThemeSettings>(fs, cx, move |theme_settings, _| {
|
||||
|
@ -277,10 +278,10 @@ fn render_font_customization_section(
|
|||
cx: &mut App,
|
||||
) -> impl IntoElement {
|
||||
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 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 =
|
||||
cx.new(|cx| font_picker(ui_font_family.clone(), write_ui_font_family, window, cx));
|
||||
|
@ -306,62 +307,53 @@ fn render_font_customization_section(
|
|||
.gap_1()
|
||||
.child(Label::new("UI Font"))
|
||||
.child(
|
||||
h_flex().w_full().justify_between().gap_2().child(
|
||||
PopoverMenu::new("ui-font-picker")
|
||||
.menu({
|
||||
let ui_font_picker = ui_font_picker.clone();
|
||||
move |_window, _cx| Some(ui_font_picker.clone())
|
||||
})
|
||||
.trigger(
|
||||
ButtonLike::new("ui-font-family-button")
|
||||
.style(ButtonStyle::Outlined)
|
||||
.size(ButtonSize::Medium)
|
||||
.full_width()
|
||||
.tab_index({
|
||||
*tab_index += 1;
|
||||
*tab_index - 1
|
||||
})
|
||||
.child(
|
||||
h_flex()
|
||||
.w_full()
|
||||
.justify_between()
|
||||
.child(Label::new(ui_font_family))
|
||||
.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)
|
||||
// .tab_index({
|
||||
// *tab_index += 2;
|
||||
// *tab_index - 2
|
||||
// }),
|
||||
// ),
|
||||
h_flex()
|
||||
.w_full()
|
||||
.justify_between()
|
||||
.gap_2()
|
||||
.child(
|
||||
PopoverMenu::new("ui-font-picker")
|
||||
.menu({
|
||||
let ui_font_picker = ui_font_picker.clone();
|
||||
move |_window, _cx| Some(ui_font_picker.clone())
|
||||
})
|
||||
.trigger(
|
||||
ButtonLike::new("ui-font-family-button")
|
||||
.style(ButtonStyle::Outlined)
|
||||
.size(ButtonSize::Medium)
|
||||
.full_width()
|
||||
.tab_index({
|
||||
*tab_index += 1;
|
||||
*tab_index - 1
|
||||
})
|
||||
.child(
|
||||
h_flex()
|
||||
.w_full()
|
||||
.justify_between()
|
||||
.child(Label::new(ui_font_family))
|
||||
.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(font_picker_stepper(
|
||||
"ui-font-size",
|
||||
&ui_font_size,
|
||||
tab_index,
|
||||
write_ui_font_size,
|
||||
window,
|
||||
cx,
|
||||
)),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
|
@ -370,66 +362,99 @@ fn render_font_customization_section(
|
|||
.gap_1()
|
||||
.child(Label::new("Editor Font"))
|
||||
.child(
|
||||
h_flex().w_full().justify_between().gap_2().child(
|
||||
PopoverMenu::new("buffer-font-picker")
|
||||
.menu({
|
||||
let buffer_font_picker = buffer_font_picker.clone();
|
||||
move |_window, _cx| Some(buffer_font_picker.clone())
|
||||
})
|
||||
.trigger(
|
||||
ButtonLike::new("buffer-font-family-button")
|
||||
.style(ButtonStyle::Outlined)
|
||||
.size(ButtonSize::Medium)
|
||||
.full_width()
|
||||
.tab_index({
|
||||
*tab_index += 1;
|
||||
*tab_index - 1
|
||||
})
|
||||
.child(
|
||||
h_flex()
|
||||
.w_full()
|
||||
.justify_between()
|
||||
.child(Label::new(buffer_font_family))
|
||||
.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( 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
|
||||
// }),
|
||||
// ),
|
||||
h_flex()
|
||||
.w_full()
|
||||
.justify_between()
|
||||
.gap_2()
|
||||
.child(
|
||||
PopoverMenu::new("buffer-font-picker")
|
||||
.menu({
|
||||
let buffer_font_picker = buffer_font_picker.clone();
|
||||
move |_window, _cx| Some(buffer_font_picker.clone())
|
||||
})
|
||||
.trigger(
|
||||
ButtonLike::new("buffer-font-family-button")
|
||||
.style(ButtonStyle::Outlined)
|
||||
.size(ButtonSize::Medium)
|
||||
.full_width()
|
||||
.tab_index({
|
||||
*tab_index += 1;
|
||||
*tab_index - 1
|
||||
})
|
||||
.child(
|
||||
h_flex()
|
||||
.w_full()
|
||||
.justify_between()
|
||||
.child(Label::new(buffer_font_family))
|
||||
.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(font_picker_stepper(
|
||||
"buffer-font-size",
|
||||
&buffer_font_size,
|
||||
tab_index,
|
||||
write_buffer_font_size,
|
||||
window,
|
||||
cx,
|
||||
)),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
fn font_picker_stepper(
|
||||
id: &'static str,
|
||||
font_size: &Pixels,
|
||||
tab_index: &mut isize,
|
||||
write_font_size: fn(Pixels, &mut App),
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) -> NumericStepper<u32> {
|
||||
window.with_id(id, |window| {
|
||||
let optimistic_font_size: gpui::Entity<Option<u32>> = window.use_state(cx, |_, _| None);
|
||||
optimistic_font_size.update(cx, |optimistic_font_size, _| {
|
||||
if let Some(optimistic_font_size_val) = optimistic_font_size {
|
||||
if *optimistic_font_size_val == font_size.0 as u32 {
|
||||
*optimistic_font_size = None;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let stepper_font_size = optimistic_font_size
|
||||
.read(cx)
|
||||
.unwrap_or_else(|| font_size.0 as u32);
|
||||
|
||||
NumericStepper::new(
|
||||
SharedString::new(format!("{}-stepper", id)),
|
||||
stepper_font_size,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
.on_change(move |new_value, _, cx| {
|
||||
optimistic_font_size.write(cx, Some(*new_value));
|
||||
write_font_size(Pixels::from(*new_value), cx);
|
||||
})
|
||||
.style(ui_input::NumericStepperStyle::Outlined)
|
||||
.tab_index({
|
||||
*tab_index += 2;
|
||||
*tab_index - 2
|
||||
})
|
||||
.min(6)
|
||||
.max(32)
|
||||
})
|
||||
}
|
||||
|
||||
type FontPicker = Picker<FontPickerDelegate>;
|
||||
|
||||
pub struct FontPickerDelegate {
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
use std::{
|
||||
fmt::Display,
|
||||
ops::{Add, Sub},
|
||||
rc::Rc,
|
||||
str::FromStr,
|
||||
};
|
||||
|
||||
|
@ -115,7 +116,7 @@ impl_numeric_stepper_int!(u64);
|
|||
#[derive(RegisterComponent)]
|
||||
pub struct NumericStepper<T = usize> {
|
||||
id: ElementId,
|
||||
value: Entity<T>,
|
||||
value: T,
|
||||
style: NumericStepperStyle,
|
||||
focus_handle: FocusHandle,
|
||||
mode: Entity<NumericStepperMode>,
|
||||
|
@ -126,16 +127,12 @@ pub struct NumericStepper<T = usize> {
|
|||
min_value: T,
|
||||
max_value: T,
|
||||
on_reset: Option<Box<dyn Fn(&ClickEvent, &mut Window, &mut App) + 'static>>,
|
||||
on_change: Rc<dyn Fn(&T, &mut Window, &mut App) + 'static>,
|
||||
tab_index: Option<isize>,
|
||||
}
|
||||
|
||||
impl<T: NumericStepperType> NumericStepper<T> {
|
||||
pub fn new(
|
||||
id: impl Into<ElementId>,
|
||||
value: Entity<T>,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) -> Self {
|
||||
pub fn new(id: impl Into<ElementId>, value: T, window: &mut Window, cx: &mut App) -> Self {
|
||||
let id = id.into();
|
||||
|
||||
let (mode, focus_handle) = window.with_id(id.clone(), |window| {
|
||||
|
@ -157,6 +154,7 @@ impl<T: NumericStepperType> NumericStepper<T> {
|
|||
min_value: T::min_value(),
|
||||
max_value: T::max_value(),
|
||||
on_reset: None,
|
||||
on_change: Rc::new(|_, _, _| {}),
|
||||
tab_index: None,
|
||||
}
|
||||
}
|
||||
|
@ -208,6 +206,11 @@ impl<T: NumericStepperType> NumericStepper<T> {
|
|||
self.tab_index = Some(tab_index);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn on_change(mut self, on_change: impl Fn(&T, &mut Window, &mut App) + 'static) -> Self {
|
||||
self.on_change = Rc::new(on_change);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: NumericStepperType> IntoElement for NumericStepper<T> {
|
||||
|
@ -274,14 +277,14 @@ impl<T: NumericStepperType> RenderOnce for NumericStepper<T> {
|
|||
.map(|decrement| {
|
||||
let decrement_handler = {
|
||||
let value = self.value.clone();
|
||||
let on_change = self.on_change.clone();
|
||||
let focus = self.focus_handle.clone();
|
||||
let min = self.min_value;
|
||||
move |click: &ClickEvent, window: &mut Window, cx: &mut App| {
|
||||
let step = get_step(click.modifiers());
|
||||
let current_value = *value.read(cx);
|
||||
let new_value = current_value - step;
|
||||
let new_value = value - step;
|
||||
let new_value = if new_value < min { min } else { new_value };
|
||||
value.write(cx, new_value);
|
||||
on_change(&new_value, window, cx);
|
||||
window.focus(&focus);
|
||||
}
|
||||
};
|
||||
|
@ -328,7 +331,7 @@ impl<T: NumericStepperType> RenderOnce for NumericStepper<T> {
|
|||
.child(match *self.mode.read(cx) {
|
||||
NumericStepperMode::Read => div()
|
||||
.id("numeric_stepper_label")
|
||||
.child(Label::new((self.format)(self.value.read(cx))).mx_3())
|
||||
.child(Label::new((self.format)(&self.value)).mx_3())
|
||||
.on_click({
|
||||
let mode = self.mode.clone();
|
||||
|
||||
|
@ -347,17 +350,13 @@ impl<T: NumericStepperType> RenderOnce for NumericStepper<T> {
|
|||
|window, cx| {
|
||||
let mut editor = Editor::single_line(window, cx);
|
||||
|
||||
editor.set_text(
|
||||
format!("{}", self.value.read(cx)),
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
editor.set_text(format!("{}", self.value), window, cx);
|
||||
cx.on_focus_out(&editor.focus_handle(cx), window, {
|
||||
let mode = self.mode.clone();
|
||||
let value = self.value.clone();
|
||||
let min = self.min_value;
|
||||
let max = self.max_value;
|
||||
move |this, _, _window, cx| {
|
||||
let on_change = self.on_change.clone();
|
||||
move |this, _, window, cx| {
|
||||
if let Ok(new_value) =
|
||||
this.text(cx).parse::<T>()
|
||||
{
|
||||
|
@ -368,7 +367,8 @@ impl<T: NumericStepperType> RenderOnce for NumericStepper<T> {
|
|||
} else {
|
||||
new_value
|
||||
};
|
||||
value.write(cx, new_value);
|
||||
|
||||
on_change(&new_value, window, cx);
|
||||
};
|
||||
mode.write(cx, NumericStepperMode::Read);
|
||||
}
|
||||
|
@ -397,13 +397,13 @@ impl<T: NumericStepperType> RenderOnce for NumericStepper<T> {
|
|||
let increment_handler = {
|
||||
let value = self.value.clone();
|
||||
let focus = self.focus_handle.clone();
|
||||
let on_change = self.on_change.clone();
|
||||
let max = self.max_value;
|
||||
move |click: &ClickEvent, window: &mut Window, cx: &mut App| {
|
||||
let step = get_step(click.modifiers());
|
||||
let current_value = *value.read(cx);
|
||||
let new_value = current_value + step;
|
||||
let new_value = value + step;
|
||||
let new_value = if new_value > max { max } else { new_value };
|
||||
value.write(cx, new_value);
|
||||
on_change(&new_value, window, cx);
|
||||
window.focus(&focus);
|
||||
}
|
||||
};
|
||||
|
@ -474,20 +474,28 @@ impl Component for NumericStepper<usize> {
|
|||
"Default",
|
||||
NumericStepper::new(
|
||||
"numeric-stepper-component-preview",
|
||||
first_stepper,
|
||||
*first_stepper.read(cx),
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
.on_change({
|
||||
let first_stepper = first_stepper.clone();
|
||||
move |value, _, cx| first_stepper.write(cx, *value)
|
||||
})
|
||||
.into_any_element(),
|
||||
),
|
||||
single_example(
|
||||
"Outlined",
|
||||
NumericStepper::new(
|
||||
"numeric-stepper-with-border-component-preview",
|
||||
second_stepper,
|
||||
*second_stepper.read(cx),
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
.on_change({
|
||||
let second_stepper = second_stepper.clone();
|
||||
move |value, _, cx| second_stepper.write(cx, *value)
|
||||
})
|
||||
.min(1.0)
|
||||
.max(100.0)
|
||||
.style(NumericStepperStyle::Outlined)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue