diff --git a/crates/gpui/src/app/context.rs b/crates/gpui/src/app/context.rs index 1112878a66..516f51b8c7 100644 --- a/crates/gpui/src/app/context.rs +++ b/crates/gpui/src/app/context.rs @@ -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) + '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( &mut self, diff --git a/crates/gpui/src/window.rs b/crates/gpui/src/window.rs index 2597c001d2..ec83fc6e30 100644 --- a/crates/gpui/src/window.rs +++ b/crates/gpui/src/window.rs @@ -4838,6 +4838,12 @@ impl> From<(ElementId, T)> for ElementId { } } +impl From> 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)] diff --git a/crates/onboarding/src/editing_page.rs b/crates/onboarding/src/editing_page.rs index 06d20c83be..ce509ea843 100644 --- a/crates/onboarding/src/editing_page.rs +++ b/crates/onboarding/src/editing_page.rs @@ -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 = ::global(cx); update_settings_file::(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 = ::global(cx); update_settings_file::(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 { + window.with_id(id, |window| { + let optimistic_font_size: gpui::Entity> = 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; pub struct FontPickerDelegate { diff --git a/crates/ui_input/src/numeric_stepper.rs b/crates/ui_input/src/numeric_stepper.rs index 97c1102302..382c344fba 100644 --- a/crates/ui_input/src/numeric_stepper.rs +++ b/crates/ui_input/src/numeric_stepper.rs @@ -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 { id: ElementId, - value: Entity, + value: T, style: NumericStepperStyle, focus_handle: FocusHandle, mode: Entity, @@ -126,16 +127,12 @@ pub struct NumericStepper { min_value: T, max_value: T, on_reset: Option>, + on_change: Rc, tab_index: Option, } impl NumericStepper { - pub fn new( - id: impl Into, - value: Entity, - window: &mut Window, - cx: &mut App, - ) -> Self { + pub fn new(id: impl Into, 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 NumericStepper { 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 NumericStepper { 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 IntoElement for NumericStepper { @@ -274,14 +277,14 @@ impl RenderOnce for NumericStepper { .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 RenderOnce for NumericStepper { .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 RenderOnce for NumericStepper { |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::() { @@ -368,7 +367,8 @@ impl RenderOnce for NumericStepper { } else { new_value }; - value.write(cx, new_value); + + on_change(&new_value, window, cx); }; mode.write(cx, NumericStepperMode::Read); } @@ -397,13 +397,13 @@ impl RenderOnce for NumericStepper { 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 { "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)