diff --git a/dash-frontend/assets/lang/en.json b/dash-frontend/assets/lang/en.json index 7f022b21..b4a356c9 100644 --- a/dash-frontend/assets/lang/en.json +++ b/dash-frontend/assets/lang/en.json @@ -230,6 +230,7 @@ "NEED_TO_DOWNLOAD_MODELS": "The swipe-to-type model needs to be downloaded.\nDownload it now?", "MODELS_DOWNLOADED": "Model downloaded", "MODELS_NOT_DOWNLOADED": "Model not downloaded", + "MODEL_REQUIRED_TO_ENABLE": "Download the swipe-to-type model to enable this option.", "REMOVE_MODELS": "Remove swipe to type model", "DOWNLOAD_MODELS": "Download swipe to type model", "DOWNLOAD_MODELS_HELP": "Swipe to type only works on qwerty layout keyboards" diff --git a/dash-frontend/src/tab/settings/mod.rs b/dash-frontend/src/tab/settings/mod.rs index 25d425dd..6e61c475 100644 --- a/dash-frontend/src/tab/settings/mod.rs +++ b/dash-frontend/src/tab/settings/mod.rs @@ -552,7 +552,7 @@ impl SettingType { Self::InvertScrollDirectionY => Ok("APP_SETTINGS.INVERT_SCROLL_DIRECTION_Y"), Self::KeyboardMiddleClick => Ok("APP_SETTINGS.KEYBOARD_MIDDLE_CLICK"), Self::KeyboardSoundEnabled => Ok("APP_SETTINGS.KEYBOARD_SOUND_ENABLED"), - Self::KeyboardSwipeToTypeEnabled => Ok("APP_SETTINGS.KEYBOARD_SWIPE_TYPE_ENABLED"), + Self::KeyboardSwipeToTypeEnabled => Ok("APP_SETTINGS.KEYBOARD_SWIPE_TO_TYPE_ENABLED"), Self::Language => Ok("APP_SETTINGS.LANGUAGE"), Self::LeftHandedMouse => Ok("APP_SETTINGS.LEFT_HANDED_MOUSE"), Self::LongPressDuration => Ok("APP_SETTINGS.LONG_PRESS_DURATION"), diff --git a/dash-frontend/src/tab/settings/tab_features.rs b/dash-frontend/src/tab/settings/tab_features.rs index 89a3edc1..d58a1aff 100644 --- a/dash-frontend/src/tab/settings/tab_features.rs +++ b/dash-frontend/src/tab/settings/tab_features.rs @@ -2,7 +2,10 @@ use std::path::PathBuf; use std::rc::Rc; use wgui::{ - components::button::{ButtonClickEvent, ComponentButton}, + components::{ + button::{ButtonClickEvent, ComponentButton}, + checkbox::ComponentCheckbox, + }, globals::WguiGlobals, i18n::Translation, layout::WidgetID, @@ -18,7 +21,10 @@ use crate::{ frontend::FrontendTasks, tab::settings::{ SettingType, SettingsMountParams, SettingsTab, TabNameEnum, Task as ParentTask, horiz_cell, - macros::{MacroParams, options_category, options_checkbox, options_range_f32}, + mount_requires_restart, + macros::{ + MacroParams, options_category, options_checkbox, options_range_f32, + }, }, util::{ popup_manager::PopupHolder, @@ -206,7 +212,7 @@ impl State { if par.feats.swipe_to_type { swipe_type_models_button(par.mp, c)?; } - options_checkbox(par.mp, c, SettingType::KeyboardSwipeToTypeEnabled)?; + swipe_type_enabled_checkbox(par.mp, c)?; options_checkbox(par.mp, c, SettingType::NotificationsEnabled)?; options_checkbox(par.mp, c, SettingType::NotificationsSoundEnabled)?; options_checkbox(par.mp, c, SettingType::KeyboardSoundEnabled)?; @@ -494,5 +500,61 @@ fn swipe_type_models_button(mp: &mut MacroParams, parent: WidgetID) -> anyhow::R } })); + Ok(()) +} + +fn swipe_type_enabled_checkbox(mp: &mut MacroParams, parent: WidgetID) -> anyhow::Result<()> { + let id = mp.idx.to_string(); + mp.idx += 1; + + let setting = SettingType::KeyboardSwipeToTypeEnabled; + + // cannot enable swipe-to-type until the model is downloaded + let model_downloaded = swwipe_type_model_downloaded().unwrap_or_default(); + + let mut params = TemplateParams::new(); + params.insert("id", &id); + + match setting.get_translation() { + Ok(translation) => params.insert("translation", translation), + Err(raw_text) => params.insert("text", raw_text), + }; + + let tooltip = if model_downloaded { + setting.get_tooltip() + } else { + Some("APP_SETTINGS.SWIPE_TYPE.MODEL_REQUIRED_TO_ENABLE") + }; + if let Some(tooltip) = tooltip { + params.insert("tooltip", tooltip); + } + + let checked = if *setting.mut_bool(mp.config) { "1" } else { "0" }; + params.insert("checked", checked); + + let id_cell = horiz_cell(mp.layout, parent)?; + + mp.parser_state + .instantiate_template(mp.doc_params, "CheckBoxSetting", mp.layout, id_cell, params)?; + + if setting.requires_restart() { + mount_requires_restart(mp.layout, id_cell)?; + } + + let checkbox = mp.parser_state.fetch_component_as::(&id)?; + + if !model_downloaded { + let mut common = mp.layout.common(); + checkbox.set_disabled(&mut common, true); + } + + checkbox.on_toggle(Box::new({ + let tasks = mp.tasks.clone(); + move |_common, e| { + tasks.push(ParentTask::UpdateBool(setting, e.checked)); + Ok(()) + } + })); + Ok(()) } \ No newline at end of file diff --git a/wgui/src/components/checkbox.rs b/wgui/src/components/checkbox.rs index 9f6f9700..4bd78414 100644 --- a/wgui/src/components/checkbox.rs +++ b/wgui/src/components/checkbox.rs @@ -66,6 +66,7 @@ struct State { checked: bool, hovered: bool, down: bool, + disabled: bool, on_toggle: Option, self_ref: Weak, active_tooltip: Option>, @@ -99,6 +100,32 @@ pub struct ComponentCheckbox { const COLOR_UNCHECKED: WguiColor = WguiColor::Raw(drawing::Color::new(0., 0., 0., 0.)); const COLOR_HOVERED: WguiColor = WguiColorName::Tertiary.to_wgui_color(); +fn disabled_color() -> WguiColor { + WguiColor::from(WguiColorName::OnBackground).with_alpha(0.35) +} + +/// Color for the inner checkbox box, for any checked/hover/disabled combination. +fn box_fill_color(checked: bool, hovered: bool, disabled: bool, checked_color: WguiColor) -> WguiColor { + if disabled { + if checked { disabled_color() } else { COLOR_UNCHECKED } + } else if checked { + if hovered { COLOR_HOVERED } else { checked_color } + } else { + COLOR_UNCHECKED + } +} + +/// Color for the checkbox border, for any hover/press/disabled combination. +fn box_border_color(hovered: bool, down: bool, disabled: bool) -> WguiColor { + if disabled { + disabled_color() + } else if hovered || down { + COLOR_HOVERED + } else { + WguiColorName::OnBackground.into() + } +} + impl ComponentTrait for ComponentCheckbox { fn base(&self) -> &ComponentBase { &self.base @@ -120,17 +147,9 @@ impl ComponentTrait for ComponentCheckbox { } } -fn set_box_checked(widgets: &layout::WidgetMap, data: &Data, checked: bool, hovered: bool) { +fn set_box_checked(widgets: &layout::WidgetMap, data: &Data, checked: bool, hovered: bool, disabled: bool) { widgets.call(data.id_inner_box, |rect: &mut WidgetRectangle| { - rect.params.color = if checked { - if hovered { - COLOR_HOVERED.into() - } else { - data.color_checked - } - } else { - COLOR_UNCHECKED.into() - } + rect.params.color = box_fill_color(checked, hovered, disabled, data.color_checked); }); } @@ -145,6 +164,7 @@ impl ComponentCheckbox { pub fn set_checked(&self, common: &mut CallbackDataCommon, checked: bool) { let hovered; + let disabled; { let mut state = self.state.borrow_mut(); if state.checked == checked { @@ -152,8 +172,9 @@ impl ComponentCheckbox { } state.checked = checked; hovered = state.hovered; + disabled = state.disabled; } - set_box_checked(&common.state.widgets, &self.data, checked, hovered); + set_box_checked(&common.state.widgets, &self.data, checked, hovered, disabled); common.alterables.mark_redraw(); } @@ -174,16 +195,46 @@ impl ComponentCheckbox { pub fn on_toggle(&self, func: CheckboxToggleCallback) { self.state.borrow_mut().on_toggle = Some(func); } + + /// Enable or disable user interaction with the checkbox and dim its appearance. + pub fn set_disabled(&self, common: &mut CallbackDataCommon, disabled: bool) { + let checked; + let hovered; + let down; + { + let mut state = self.state.borrow_mut(); + if state.disabled == disabled { + return; + } + state.disabled = disabled; + checked = state.checked; + hovered = state.hovered; + down = state.down; + } + + set_box_checked(&common.state.widgets, &self.data, checked, hovered, disabled); + + common.state.widgets.call(self.data.id_outer_box, |rect: &mut WidgetRectangle| { + rect.params.border_color = box_border_color(hovered, down, disabled); + }); + + if let Some(mut label) = common.state.widgets.get_as::(self.data.id_label) { + let color = if disabled { disabled_color() } else { WguiColorName::OnBackground.into() }; + label.set_color(common, color, true); + } + + common.alterables.mark_redraw(); + } + + pub fn get_disabled(&self) -> bool { + self.state.borrow().disabled + } } -fn anim_hover(anim_data: &mut crate::animation::CallbackData<'_>, pos: f32, _pressed: bool) { +fn anim_hover(anim_data: &mut crate::animation::CallbackData<'_>, pos: f32, pressed: bool, disabled: bool) { let rect = anim_data.obj.as_any_mut().downcast_mut::().unwrap(); rect.params.border = 2.0; - rect.params.border_color = if pos > 0.0 { - COLOR_HOVERED.into() - } else { - WguiColorName::OnBackground.into() - }; + rect.params.border_color = box_border_color(pos > 0.0, pressed, disabled); } fn anim_hover_in(state: Rc>, data: Rc, anim_mult: f32) -> Animation { @@ -193,7 +244,8 @@ fn anim_hover_in(state: Rc>, data: Rc, anim_mult: f32) -> A (5. * anim_mult) as _, AnimationEasing::OutQuad, Box::new(move |common, anim_data| { - anim_hover(anim_data, anim_data.pos, down); + let disabled = state.borrow().disabled; + anim_hover(anim_data, anim_data.pos, down, disabled); common.alterables.mark_redraw(); }), ) @@ -206,7 +258,8 @@ fn anim_hover_out(state: Rc>, data: Rc, anim_mult: f32) -> (8. * anim_mult) as _, AnimationEasing::OutQuad, Box::new(move |common, anim_data| { - anim_hover(anim_data, 1.0 - anim_data.pos, down); + let disabled = state.borrow().disabled; + anim_hover(anim_data, 1.0 - anim_data.pos, down, disabled); common.alterables.mark_redraw(); }), ) @@ -222,28 +275,33 @@ fn register_event_mouse_enter( listeners.register( EventListenerKind::MouseEnter, Box::new(move |common, _event_data, (), ()| { - common.alterables.trigger_haptics(); - common - .alterables - .animate(anim_hover_in(state.clone(), data.clone(), anim_mult)); + let checked; + let disabled; + { + let mut state = state.borrow_mut(); + checked = state.checked; + disabled = state.disabled; + state.hovered = true; + } + + if !disabled { + common.alterables.trigger_haptics(); + common + .alterables + .animate(anim_hover_in(state.clone(), data.clone(), anim_mult)); + + if checked { + common + .state + .widgets + .call(data.id_inner_box, |rect: &mut WidgetRectangle| { + rect.params.color = box_fill_color(checked, true, false, data.color_checked); + }); + } + } ComponentTooltip::register_hover_in(common, &tooltip_info, data.id_container, state.clone()); - let checked = { - let mut state = state.borrow_mut(); - state.hovered = true; - state.checked - }; - - if checked { - common - .state - .widgets - .call(data.id_inner_box, |rect: &mut WidgetRectangle| { - rect.params.color = COLOR_HOVERED.into(); - }); - } - Ok(EventResult::Pass) }), ) @@ -258,25 +316,30 @@ fn register_event_mouse_leave( listeners.register( EventListenerKind::MouseLeave, Box::new(move |common, _event_data, (), ()| { - common.alterables.trigger_haptics(); - common - .alterables - .animate(anim_hover_out(state.clone(), data.clone(), anim_mult)); - - let checked = { + let checked; + let disabled; + { let mut state = state.borrow_mut(); + checked = state.checked; + disabled = state.disabled; state.hovered = false; state.active_tooltip = None; - state.checked - }; + } - if checked { + if !disabled { + common.alterables.trigger_haptics(); common - .state - .widgets - .call(data.id_inner_box, |rect: &mut WidgetRectangle| { - rect.params.color = data.color_checked; - }); + .alterables + .animate(anim_hover_out(state.clone(), data.clone(), anim_mult)); + + if checked { + common + .state + .widgets + .call(data.id_inner_box, |rect: &mut WidgetRectangle| { + rect.params.color = box_fill_color(checked, false, false, data.color_checked); + }); + } } Ok(EventResult::Pass) @@ -311,18 +374,14 @@ fn register_event_mouse_press( .widgets .call(data.id_outer_box, |rect: &mut WidgetRectangle| { rect.params.border = 2.0; - rect.params.border_color = if pressed_hovered { - COLOR_HOVERED.into() - } else { - WguiColorName::OnBackground.into() - }; + rect.params.border_color = box_border_color(pressed_hovered, false, state.disabled); }); common.alterables.trigger_haptics(); common.alterables.mark_redraw(); common.alterables.unfocus(); - if state.hovered { + if state.hovered && !state.disabled { state.down = true; Ok(EventResult::Consumed) } else { @@ -349,17 +408,13 @@ fn register_event_mouse_release( .widgets .call(data.id_outer_box, |rect: &mut WidgetRectangle| { rect.params.border = 2.0; - rect.params.border_color = if released_hovered { - COLOR_HOVERED.into() - } else { - WguiColorName::OnBackground.into() - }; + rect.params.border_color = box_border_color(released_hovered, was_down, state.disabled); }); common.alterables.trigger_haptics(); common.alterables.mark_redraw(); - if was_down { + if !state.disabled && was_down { state.down = false; if let Some(self_ref) = state.self_ref.upgrade() @@ -377,7 +432,7 @@ fn register_event_mouse_release( }); } - set_box_checked(&common.state.widgets, &data, state.checked, state.hovered); + set_box_checked(&common.state.widgets, &data, state.checked, state.hovered, state.disabled); if state.hovered && let Some(on_toggle) = &state.on_toggle { @@ -512,6 +567,7 @@ pub fn construct(ess: &mut ConstructEssentials, params: Params) -> anyhow::Resul checked: params.checked, down: false, hovered: false, + disabled: false, on_toggle: None, self_ref: Weak::new(), active_tooltip: None,