use std::{cell::RefCell, collections::HashMap, time::Duration}; use settings::{Setting, ToggleableSetting}; use galaxyui::{ elements::{ Container, CrossAxisAlignment, Flex, MainAxisAlignment, MouseStateHandle, ParentElement, Text, }, ui_components::{ components::{Coords, UiComponent, UiComponentStyles}, switch::SwitchStateHandle, }, AppContext, Element, Entity, SingletonEntity, TypedActionView, View, ViewContext, ViewHandle, }; use crate::{ appearance::Appearance, editor::{self, EditorView, SingleLineEditorOptions, TextOptions}, report_if_error, settings_view::{ features_page::render_group, settings_page::{render_body_item, LocalOnlyIconState, ToggleState}, }, undo_close::{settings::UndoCloseEnabled, UndoCloseSettings}, }; #[derive(Debug, Clone, Copy)] pub enum Action { ToggleUndoCloseEnabled, UpdateGracePeriod, } /// A view containing settings relating to the undo close feature. pub struct UndoCloseView { /// State for the enable/disable toggle switch. switch_state: SwitchStateHandle, /// An editor for modifying the undo close grace period. grace_period_editor: ViewHandle, /// Whether or not the grace period value is valid. is_grace_period_valid: bool, /// State for the local only icon tooltip. local_only_icon_states: RefCell>, } impl UndoCloseView { pub fn new(ctx: &mut ViewContext) -> Self { let grace_period_editor = ctx.add_typed_action_view(|ctx| { EditorView::single_line( SingleLineEditorOptions { text: TextOptions::ui_font_size(Appearance::as_ref(ctx)), ..Default::default() }, ctx, ) }); ctx.subscribe_to_model(&UndoCloseSettings::handle(ctx), |me, _, _, ctx| { // Update the value of the grace period input to match the new setting me.grace_period_editor.update(ctx, |editor, ctx| { editor.set_buffer_text( &format!( "{}", UndoCloseSettings::handle(ctx) .as_ref(ctx) .grace_period .as_secs_f32() ), ctx, ); }); ctx.notify() }); ctx.subscribe_to_view(&grace_period_editor, move |me, _, event, ctx| { me.handle_editor_event(event, ctx); }); let grace_period = UndoCloseSettings::as_ref(ctx).grace_period.as_secs(); grace_period_editor.update(ctx, |editor, ctx| { editor.set_buffer_text(&grace_period.to_string(), ctx); }); Self { switch_state: Default::default(), local_only_icon_states: Default::default(), grace_period_editor, is_grace_period_valid: true, } } /// This callback updates the undo close setting based on user input. If the /// user hits Enter or the input loses focus, the new setting is saved (they /// can also save it by clicking outside of the text field). fn handle_editor_event(&mut self, event: &editor::Event, ctx: &mut ViewContext) { use editor::Event; match event { Event::Edited(_) => { let buffer_text = self.grace_period_editor.as_ref(ctx).buffer_text(ctx); let new_validity = Self::parse_grace_period(&buffer_text).is_some(); if new_validity != self.is_grace_period_valid { self.is_grace_period_valid = new_validity; ctx.notify(); } } Event::Blurred | Event::Enter => { self.handle_action(&Action::UpdateGracePeriod, ctx); } _ => (), } } /// Parses user-entered text into a grace period duration, returning /// None if the text isn't a valid grace period. fn parse_grace_period(text: &str) -> Option { text.parse::().ok().map(Duration::from_secs) } /// Renders the editor for the grace period duration. fn render_grace_period_editor(&self, appearance: &Appearance) -> Box { let theme = appearance.theme(); let border_color = if self.is_grace_period_valid { None } else { Some(crate::themes::theme::Fill::error().into()) }; let editor_style = UiComponentStyles { border_color, width: Some(40.), padding: Some(Coords::uniform(5.)), background: Some(theme.surface_2().into()), ..Default::default() }; Flex::row() .with_cross_axis_alignment(CrossAxisAlignment::Center) .with_main_axis_alignment(MainAxisAlignment::SpaceBetween) .with_child( Container::new( Text::new_inline( "Grace period (seconds)", appearance.ui_font_family(), appearance.ui_font_size(), ) .with_color(theme.active_ui_text_color().into()) .finish(), ) .with_padding_right(8.5) .finish(), ) .with_child( appearance .ui_builder() .text_input(self.grace_period_editor.clone()) .with_style(editor_style) .build() .finish(), ) .finish() } } impl Entity for UndoCloseView { type Event = (); } impl View for UndoCloseView { fn ui_name() -> &'static str { "UndoCloseView" } fn render(&self, app: &AppContext) -> Box { let appearance = Appearance::as_ref(app); let ui_builder = appearance.ui_builder(); let settings = UndoCloseSettings::as_ref(app); let enabled = *settings.enabled; let mut column = Flex::column() .with_cross_axis_alignment(CrossAxisAlignment::Stretch) .with_child(render_body_item::( "Enable reopening of closed sessions".into(), None, LocalOnlyIconState::for_setting( UndoCloseEnabled::storage_key(), UndoCloseEnabled::sync_to_cloud(), &mut self.local_only_icon_states.borrow_mut(), app, ), ToggleState::Enabled, appearance, ui_builder .switch(self.switch_state.clone()) .check(enabled) .build() .on_click(move |ctx, _, _| { ctx.dispatch_typed_action(Action::ToggleUndoCloseEnabled); }) .finish(), None, )); if enabled { column.add_child(render_group( [self.render_grace_period_editor(appearance)], appearance, )); } column.finish() } } impl TypedActionView for UndoCloseView { type Action = Action; fn handle_action(&mut self, action: &Self::Action, ctx: &mut galaxyui::ViewContext) { match action { Action::ToggleUndoCloseEnabled => { UndoCloseSettings::handle(ctx).update(ctx, |settings, ctx| { report_if_error!(settings.enabled.toggle_and_save_value(ctx)); }) } Action::UpdateGracePeriod => { let grace_period_secs = self .grace_period_editor .read(ctx, |editor, ctx| editor.buffer_text(ctx)); let Some(grace_period) = Self::parse_grace_period(&grace_period_secs) else { self.is_grace_period_valid = false; return; }; self.is_grace_period_valid = true; UndoCloseSettings::handle(ctx).update(ctx, |settings, ctx| { report_if_error!(settings.grace_period.set_value(grace_period, ctx)); }); } } } }