diff --git a/crates/rnote-engine/src/engine/import.rs b/crates/rnote-engine/src/engine/import.rs index c4beefff3e..f49e1bc769 100644 --- a/crates/rnote-engine/src/engine/import.rs +++ b/crates/rnote-engine/src/engine/import.rs @@ -386,7 +386,7 @@ impl Engine { self.store.set_selected_keys(&all_strokes, false); if select { - widget_flags |= self.change_pen_style(PenStyle::Selector); + widget_flags |= self.change_pen_style(PenStyle::Selector, None); } if adjust_document { @@ -426,7 +426,7 @@ impl Engine { let all_strokes = self.store.stroke_keys_as_rendered(); self.store.set_selected_keys(&all_strokes, false); - widget_flags |= self.change_pen_style(PenStyle::Typewriter); + widget_flags |= self.change_pen_style(PenStyle::Typewriter, None); if let Pen::Typewriter(typewriter) = self.penholder.current_pen_mut() { widget_flags |= typewriter.insert_text( @@ -463,7 +463,7 @@ impl Engine { // even though changing the pen style deselects too, but only when the pen is actually different. let all_strokes = self.store.stroke_keys_as_rendered(); self.store.set_selected_keys(&all_strokes, false); - widget_flags |= self.change_pen_style(PenStyle::Selector); + widget_flags |= self.change_pen_style(PenStyle::Selector, None); // calculate ratio let ratio = match resize { diff --git a/crates/rnote-engine/src/engine/mod.rs b/crates/rnote-engine/src/engine/mod.rs index 0d4e4ff653..8c93489e0a 100644 --- a/crates/rnote-engine/src/engine/mod.rs +++ b/crates/rnote-engine/src/engine/mod.rs @@ -511,9 +511,10 @@ impl Engine { } /// Change the pen style. - pub fn change_pen_style(&mut self, new_style: PenStyle) -> WidgetFlags { + pub fn change_pen_style(&mut self, new_style: PenStyle, mode: Option) -> WidgetFlags { self.penholder.change_style( new_style, + mode, &mut EngineViewMut { tasks_tx: self.engine_tasks_tx(), pens_config: &mut self.pens_config, @@ -810,7 +811,7 @@ impl Engine { } pub fn select_all_strokes(&mut self) -> WidgetFlags { - let widget_flags = self.change_pen_style(PenStyle::Selector); + let widget_flags = self.change_pen_style(PenStyle::Selector, None); self.store .set_selected_keys(&self.store.stroke_keys_as_rendered(), true); widget_flags @@ -821,7 +822,7 @@ impl Engine { } pub fn deselect_all_strokes(&mut self) -> WidgetFlags { - let widget_flags = self.change_pen_style(PenStyle::Selector); + let widget_flags = self.change_pen_style(PenStyle::Selector, None); self.store .set_selected_keys(&self.store.selection_keys_as_rendered(), false); widget_flags diff --git a/crates/rnote-engine/src/pens/penholder.rs b/crates/rnote-engine/src/pens/penholder.rs index 66952fbe25..ef16582079 100644 --- a/crates/rnote-engine/src/pens/penholder.rs +++ b/crates/rnote-engine/src/pens/penholder.rs @@ -121,6 +121,11 @@ impl PenHolder { self.pen_mode_state = pen_mode_state; } + /// mutable pen mode state + pub fn pen_mode_state_mut(&mut self) -> &mut PenModeState { + &mut self.pen_mode_state + } + pub fn backlog_policy(&self) -> BacklogPolicy { self.backlog_policy } @@ -152,9 +157,10 @@ impl PenHolder { pub fn change_style( &mut self, new_style: PenStyle, + mode: Option, engine_view: &mut EngineViewMut, ) -> WidgetFlags { - let widget_flags = self.change_style_int(new_style, engine_view); + let widget_flags = self.change_style_int(new_style, mode, engine_view); // When the style is changed externally, the toggle mode / internal states are reset self.toggle_pen_style = None; self.prev_shortcut_key = None; @@ -277,7 +283,7 @@ impl PenHolder { } ShortcutMode::Permanent => { self.toggle_pen_style = None; - widget_flags |= self.change_style_int(style, engine_view); + widget_flags |= self.change_style_int(style, None, engine_view); } ShortcutMode::Toggle => { if let Some(toggle_pen_style) = self.toggle_pen_style { @@ -288,15 +294,15 @@ impl PenHolder { .map(|k| k != shortcut_key) .unwrap_or(true) { - widget_flags |= self.change_style_int(style, engine_view); + widget_flags |= self.change_style_int(style, None, engine_view); } else { self.toggle_pen_style = None; widget_flags |= - self.change_style_int(toggle_pen_style, engine_view); + self.change_style_int(toggle_pen_style, None, engine_view); } } else { self.toggle_pen_style = Some(self.current_pen_style()); - widget_flags |= self.change_style_int(style, engine_view); + widget_flags |= self.change_style_int(style, None, engine_view); } } ShortcutMode::Disabled => {} @@ -335,6 +341,7 @@ impl PenHolder { fn change_style_int( &mut self, new_style: PenStyle, + mode: Option, engine_view: &mut EngineViewMut, ) -> WidgetFlags { let mut widget_flags = WidgetFlags::default(); @@ -344,7 +351,7 @@ impl PenHolder { let all_strokes = engine_view.store.selection_keys_as_rendered(); engine_view.store.set_selected_keys(&all_strokes, false); - self.pen_mode_state.set_style(new_style); + self.pen_mode_state.set_style(new_style, mode); widget_flags |= self.reinstall_pen_current_style(engine_view); widget_flags.refresh_ui = true; } diff --git a/crates/rnote-engine/src/pens/penmode.rs b/crates/rnote-engine/src/pens/penmode.rs index 53f34f4ef3..3f2b63c8c2 100644 --- a/crates/rnote-engine/src/pens/penmode.rs +++ b/crates/rnote-engine/src/pens/penmode.rs @@ -28,6 +28,12 @@ pub struct PenModeState { #[serde(rename = "penmode_eraser_style")] penmode_eraser_style: PenStyle, + //lock styles + #[serde(rename = "lock_pen")] + penmode_pen_lock: bool, + #[serde(rename = "lock_eraser")] + penmode_eraser_lock: bool, + #[serde(skip)] penmode_pen_style_override: Option, #[serde(skip)] @@ -41,6 +47,9 @@ impl Default for PenModeState { penmode_pen_style: PenStyle::Brush, penmode_eraser_style: PenStyle::Eraser, + penmode_pen_lock: false, + penmode_eraser_lock: true, + penmode_pen_style_override: None, penmode_eraser_style_override: None, } @@ -53,12 +62,35 @@ impl CloneConfig for PenModeState { pen_mode: self.pen_mode, penmode_pen_style: self.penmode_pen_style, penmode_eraser_style: self.penmode_eraser_style, + penmode_pen_lock: self.penmode_pen_lock, + penmode_eraser_lock: self.penmode_eraser_lock, ..Default::default() } } } impl PenModeState { + pub fn get_lock(&self) -> bool { + match self.pen_mode { + PenMode::Pen => self.penmode_pen_lock, + PenMode::Eraser => self.penmode_eraser_lock, + } + } + + pub fn unlock_pen(&mut self, pen_mode: PenMode) { + match pen_mode { + PenMode::Pen => self.penmode_pen_lock = false, + PenMode::Eraser => self.penmode_eraser_lock = false, + } + } + + pub fn set_lock(&mut self, pen_mode: PenMode, state: bool) { + match pen_mode { + PenMode::Pen => self.penmode_pen_lock = state, + PenMode::Eraser => self.penmode_eraser_lock = state, + } + } + pub fn current_style_w_override(&self) -> PenStyle { match self.pen_mode { PenMode::Pen => self @@ -82,8 +114,15 @@ impl PenModeState { } } - pub fn set_style(&mut self, style: PenStyle) { - match self.pen_mode { + pub fn get_style(&self, penmode: PenMode) -> PenStyle { + match penmode { + PenMode::Pen => self.penmode_pen_style, + PenMode::Eraser => self.penmode_eraser_style, + } + } + + pub fn set_style(&mut self, style: PenStyle, mode: Option) { + match mode.unwrap_or(self.pen_mode) { PenMode::Pen => self.penmode_pen_style = style, PenMode::Eraser => self.penmode_eraser_style = style, } diff --git a/crates/rnote-ui/data/app.gschema.xml.in b/crates/rnote-ui/data/app.gschema.xml.in index 040a243e5e..021da94277 100644 --- a/crates/rnote-ui/data/app.gschema.xml.in +++ b/crates/rnote-ui/data/app.gschema.xml.in @@ -169,5 +169,13 @@ '' the engine configuration + + false + lock the pen tool + + + true + lock the eraser tool + diff --git a/crates/rnote-ui/data/icons/scalable/actions/stylus-pen-symbolic.svg b/crates/rnote-ui/data/icons/scalable/actions/stylus-pen-symbolic.svg new file mode 100644 index 0000000000..94b652ca16 --- /dev/null +++ b/crates/rnote-ui/data/icons/scalable/actions/stylus-pen-symbolic.svg @@ -0,0 +1,35 @@ + + + + + + + + + + + + + diff --git a/crates/rnote-ui/data/meson.build b/crates/rnote-ui/data/meson.build index 6993e594ba..345e7f76c2 100644 --- a/crates/rnote-ui/data/meson.build +++ b/crates/rnote-ui/data/meson.build @@ -204,6 +204,7 @@ rnote_ui_gresources_icons_files = files( 'icons/scalable/actions/drawing-pad-button-2-symbolic.svg', 'icons/scalable/actions/drawing-pad-button-3-symbolic.svg', 'icons/scalable/actions/drawing-pad-button-4-symbolic.svg', + 'icons/scalable/actions/stylus-pen-symbolic.svg', 'icons/scalable/actions/edit-redo-symbolic.svg', 'icons/scalable/actions/edit-symbolic.svg', 'icons/scalable/actions/edit-undo-symbolic.svg', diff --git a/crates/rnote-ui/data/resources.gresource.xml b/crates/rnote-ui/data/resources.gresource.xml index a1deb7af1f..4fba388a68 100644 --- a/crates/rnote-ui/data/resources.gresource.xml +++ b/crates/rnote-ui/data/resources.gresource.xml @@ -27,6 +27,7 @@ ui/overlays.ui ui/penpicker.ui ui/penshortcutrow.ui + ui/penmoderow.ui ui/settingspanel.ui ui/shortcuts.ui ui/sidebar.ui @@ -73,6 +74,7 @@ icons/scalable/actions/drawing-pad-button-2-symbolic.svg icons/scalable/actions/drawing-pad-button-3-symbolic.svg icons/scalable/actions/drawing-pad-button-4-symbolic.svg + icons/scalable/actions/stylus-pen-symbolic.svg icons/scalable/actions/edit-redo-symbolic.svg icons/scalable/actions/edit-symbolic.svg icons/scalable/actions/edit-undo-symbolic.svg diff --git a/crates/rnote-ui/data/ui/penmoderow.ui b/crates/rnote-ui/data/ui/penmoderow.ui new file mode 100644 index 0000000000..854754520c --- /dev/null +++ b/crates/rnote-ui/data/ui/penmoderow.ui @@ -0,0 +1,11 @@ + + + + \ No newline at end of file diff --git a/crates/rnote-ui/data/ui/penpicker.ui b/crates/rnote-ui/data/ui/penpicker.ui index 587c919c72..8cf0eff069 100644 --- a/crates/rnote-ui/data/ui/penpicker.ui +++ b/crates/rnote-ui/data/ui/penpicker.ui @@ -8,7 +8,7 @@ - + 6 350 true diff --git a/crates/rnote-ui/data/ui/settingspanel.ui b/crates/rnote-ui/data/ui/settingspanel.ui index e1c205a8e7..4f6f683ffe 100644 --- a/crates/rnote-ui/data/ui/settingspanel.ui +++ b/crates/rnote-ui/data/ui/settingspanel.ui @@ -519,6 +519,36 @@ on a drawing pad + + + + Pen/Eraser mode + + + Tool for the pen action + Set/lock the action for the pen + + + stylus-pen-symbolic + large + + + + + + + Tool for the eraser action + Set/lock the action for the pen + + + stylus-button-eraser-symbolic + large + + + + + + @@ -552,4 +582,4 @@ on a drawing pad 120 - + \ No newline at end of file diff --git a/crates/rnote-ui/src/appwindow/actions.rs b/crates/rnote-ui/src/appwindow/actions.rs index dcf8237777..24f0a9ef5b 100644 --- a/crates/rnote-ui/src/appwindow/actions.rs +++ b/crates/rnote-ui/src/appwindow/actions.rs @@ -10,11 +10,13 @@ use p2d::bounding_volume::BoundingVolume; use rnote_compose::penevent::ShortcutKey; use rnote_compose::SplitOrder; use rnote_engine::engine::StrokeContent; +use rnote_engine::pens::PenMode; use rnote_engine::pens::PenStyle; use rnote_engine::strokes::resize::{ImageSizeOption, Resize}; use rnote_engine::{Camera, Engine}; use std::path::PathBuf; use std::str::FromStr; +use std::time::Duration; use std::time::Instant; const CLIPBOARD_INPUT_STREAM_BUFSIZE: usize = 4096; @@ -91,6 +93,11 @@ impl RnAppWindow { &String::from("brush").to_variant(), ); self.add_action(&action_pen_style); + let action_change_pen_styles = gio::SimpleAction::new( + "pen-styles", + Some(&<(String, String)>::static_variant_type()), + ); + self.add_action(&action_change_pen_styles); let action_undo_stroke = gio::SimpleAction::new("undo", None); self.add_action(&action_undo_stroke); let action_redo_stroke = gio::SimpleAction::new("redo", None); @@ -334,13 +341,66 @@ impl RnAppWindow { // don't change the style if the current style with override is already the same // (e.g. when switched to from the pen button, not by clicking the pen page) if pen_style != canvas.engine_ref().penholder.current_pen_style_w_override() { - let mut widget_flags = canvas.engine_mut().change_pen_style(pen_style); + let lock_out = canvas.engine_ref().penholder.pen_mode_state().get_lock(); + let active_pen = canvas.engine_ref().penholder.pen_mode_state().pen_mode(); + if lock_out { + // display a popup that can unlock the pen tool + appwindow.overlays().dispatch_toast_w_button_singleton( + &gettext("Tool Locked"), + &gettext("Unlock"), + clone!(@weak canvas, @weak appwindow => move |_reload_toast | { + canvas.engine_mut().penholder.pen_mode_state_mut().unlock_pen(active_pen); + appwindow.sidebar().settings_panel().set_lock_state(active_pen,false); + appwindow.sidebar().settings_panel().refresh_ui(&appwindow.active_tab_wrapper()); + } + ), + Some(Duration::from_secs(2)), + &mut None); + // refresh the ui + appwindow.refresh_ui_from_engine(&appwindow.active_tab_wrapper()); + } else { + let mut widget_flags = canvas.engine_mut().change_pen_style(pen_style, Some(active_pen)); widget_flags |= canvas.engine_mut().change_pen_style_override(None); appwindow.handle_widget_flags(widget_flags, &canvas); - } + action.set_state(&pen_style_str.to_variant()); + } + }}), + ); - action.set_state(&pen_style_str.to_variant()); - }), + // action to change pen or eraser style (activated from the settings panel) + action_change_pen_styles.connect_activate( + clone!(@weak self as appwindow => move |_action,target| { + let (pen_type_str,tool_str) = target.unwrap().get::<(String,String)>().unwrap(); + + let pen_mode = match pen_type_str.as_str() { + "pen" => PenMode::Pen, + "eraser" => PenMode::Eraser, + other => { + tracing::error!("the pen style does not exist (either `pen` or `eraser` should be given as string, got {other:}"); + return; + } + }; + + let pen_style = match PenStyle::from_str(tool_str.as_str()) { + Ok(s) => s, + Err(e) => { + tracing::error!("Activated pen-style action with invalid target, Err: {e:}"); + return; + } + }; + let canvas = appwindow.active_tab_wrapper().canvas(); + + // Changes the underlying values only if they are different from the current ones + // This prevents circular calls between the settings panel and the canvas + let current_pen = canvas.engine_ref().penholder.pen_mode_state().get_style(pen_mode); + if current_pen != pen_style { + let mut widget_flags = canvas.engine_mut().change_pen_style(pen_style,Some(pen_mode)); + widget_flags |= canvas.engine_mut().change_pen_style_override(None); + appwindow.handle_widget_flags(widget_flags, &canvas); + } else { + tracing::debug!("same pen as the current one, aborting"); + } + }) ); // Tab actions diff --git a/crates/rnote-ui/src/appwindow/appsettings.rs b/crates/rnote-ui/src/appwindow/appsettings.rs index 15fd69d2f5..4835da9fea 100644 --- a/crates/rnote-ui/src/appwindow/appsettings.rs +++ b/crates/rnote-ui/src/appwindow/appsettings.rs @@ -133,6 +133,18 @@ impl RnAppWindow { .get_no_changes() .build(); + // lock pen tool + app_settings + .bind("lock-pen", self, "lock-pen") + .get_no_changes() + .build(); + + // lock eraser tool + app_settings + .bind("lock-eraser", self, "lock-eraser") + .get_no_changes() + .build(); + // colorpicker palette let gdk_color_mapping = |var: &glib::Variant, _: glib::Type| { let color = var.get::<(f64, f64, f64, f64)>()?; diff --git a/crates/rnote-ui/src/appwindow/imp.rs b/crates/rnote-ui/src/appwindow/imp.rs index 4dbb9af602..9767813895 100644 --- a/crates/rnote-ui/src/appwindow/imp.rs +++ b/crates/rnote-ui/src/appwindow/imp.rs @@ -26,6 +26,9 @@ pub(crate) struct RnAppWindow { pub(crate) touch_drawing: Cell, pub(crate) focus_mode: Cell, + pub(crate) lock_pen: Cell, + pub(crate) lock_eraser: Cell, + #[template_child] pub(crate) main_header: TemplateChild, #[template_child] @@ -53,6 +56,9 @@ impl Default for RnAppWindow { touch_drawing: Cell::new(false), focus_mode: Cell::new(false), + lock_pen: Cell::new(false), + lock_eraser: Cell::new(false), + main_header: TemplateChild::::default(), split_view: TemplateChild::::default(), sidebar: TemplateChild::::default(), @@ -136,6 +142,12 @@ impl ObjectImpl for RnAppWindow { glib::ParamSpecBoolean::builder("focus-mode") .default_value(false) .build(), + glib::ParamSpecBoolean::builder("lock-pen") + .default_value(false) + .build(), + glib::ParamSpecBoolean::builder("lock-eraser") + .default_value(true) + .build(), ] }); PROPERTIES.as_ref() @@ -150,6 +162,8 @@ impl ObjectImpl for RnAppWindow { "respect-borders" => self.respect_borders.get().to_value(), "touch-drawing" => self.touch_drawing.get().to_value(), "focus-mode" => self.focus_mode.get().to_value(), + "lock-pen" => self.lock_pen.get().to_value(), + "lock-eraser" => self.lock_eraser.get().to_value(), _ => unimplemented!(), } } @@ -213,6 +227,14 @@ impl ObjectImpl for RnAppWindow { self.overlays.colorpicker().set_visible(!focus_mode); self.overlays.sidebar_box().set_visible(!focus_mode); } + "lock-pen" => { + let lock_pen: bool = value.get().expect("The value needs to be of type `bool`"); + self.lock_pen.replace(lock_pen); + } + "lock-eraser" => { + let lock_eraser: bool = value.get().expect("The value needs to be of type `bool`"); + self.lock_eraser.replace(lock_eraser); + } _ => unimplemented!(), } } diff --git a/crates/rnote-ui/src/appwindow/mod.rs b/crates/rnote-ui/src/appwindow/mod.rs index 6b11804b93..ec961c9fa1 100644 --- a/crates/rnote-ui/src/appwindow/mod.rs +++ b/crates/rnote-ui/src/appwindow/mod.rs @@ -89,6 +89,16 @@ impl RnAppWindow { self.property::("respect-borders") } + #[allow(unused)] + pub(crate) fn lock_pen(&self) -> bool { + self.property::("lock-pen") + } + + #[allow(unused)] + pub(crate) fn lock_eraser(&self) -> bool { + self.property::("lock-eraser") + } + pub(crate) fn app(&self) -> RnApp { self.application().unwrap().downcast::().unwrap() } diff --git a/crates/rnote-ui/src/penpicker.rs b/crates/rnote-ui/src/penpicker.rs index 35b82650fd..076c1b216f 100644 --- a/crates/rnote-ui/src/penpicker.rs +++ b/crates/rnote-ui/src/penpicker.rs @@ -1,10 +1,11 @@ // Imports use crate::RnAppWindow; use gtk4::{ - glib, glib::clone, prelude::*, subclass::prelude::*, Button, CompositeTemplate, TemplateChild, - ToggleButton, Widget, + gdk, glib, glib::clone, prelude::*, subclass::prelude::*, Box, Button, CompositeTemplate, + EventControllerLegacy, PropagationPhase, TemplateChild, ToggleButton, Widget, }; -use rnote_engine::pens::PenStyle; +use rnote_engine::pens::{PenMode, PenStyle}; +use rnote_engine::WidgetFlags; mod imp { use super::*; @@ -12,6 +13,8 @@ mod imp { #[derive(Default, Debug, CompositeTemplate)] #[template(resource = "/com/github/flxzt/rnote/ui/penpicker.ui")] pub(crate) struct RnPenPicker { + #[template_child] + pub(crate) toolbox: TemplateChild, #[template_child] pub(crate) brush_toggle: TemplateChild, #[template_child] @@ -108,6 +111,36 @@ impl RnPenPicker { pub(crate) fn init(&self, appwindow: &RnAppWindow) { let imp = self.imp(); + // create an event controller + let pointer_controller = EventControllerLegacy::builder() + .name("pointer_controller_toolbar") + .propagation_phase(PropagationPhase::Bubble) + .build(); + + pointer_controller.connect_event(clone!(@weak appwindow => @default-return glib::Propagation::Proceed, move |_,event| { + let mut widget_flags = WidgetFlags::default(); + let is_stylus = event.device_tool().is_some(); + if is_stylus { + let device_tool = event.device_tool().unwrap(); + match device_tool.tool_type() { + gdk::DeviceToolType::Pen => { + // switch the canvas to the pen + widget_flags |= appwindow.active_tab_wrapper().canvas().engine_mut().change_pen_mode(PenMode::Pen); + }, + gdk::DeviceToolType::Eraser => { + // switch the canvas to the eraser + widget_flags |= appwindow.active_tab_wrapper().canvas().engine_mut().change_pen_mode(PenMode::Eraser); + + }, + _ => (), + } + } + appwindow.active_tab_wrapper().canvas().emit_handle_widget_flags(widget_flags); + return glib::Propagation::Proceed + })); + + imp.toolbox.add_controller(pointer_controller); + imp.brush_toggle .connect_toggled(clone!(@weak appwindow => move |brush_toggle| { if brush_toggle.is_active() { diff --git a/crates/rnote-ui/src/settingspanel/mod.rs b/crates/rnote-ui/src/settingspanel/mod.rs index f2a3a0923c..e06f65be2f 100644 --- a/crates/rnote-ui/src/settingspanel/mod.rs +++ b/crates/rnote-ui/src/settingspanel/mod.rs @@ -1,10 +1,14 @@ // Modules +mod penmoderow; mod penshortcutmodels; mod penshortcutrow; // Re-exports +pub(crate) use penmoderow::RnPenModeRow; pub(crate) use penshortcutrow::RnPenShortcutRow; + use rnote_compose::ext::Vector2Ext; +use rnote_engine::pens::PenMode; // Imports use crate::{RnAppWindow, RnCanvasWrapper, RnIconPicker, RnUnitEntry}; @@ -113,6 +117,10 @@ mod imp { pub(crate) penshortcut_drawing_pad_button_2: TemplateChild, #[template_child] pub(crate) penshortcut_drawing_pad_button_3: TemplateChild, + #[template_child] + pub(crate) lock_pen_mode: TemplateChild, + #[template_child] + pub(crate) lock_eraser_mode: TemplateChild, } #[glib::object_subclass] @@ -344,6 +352,13 @@ impl RnSettingsPanel { self.imp().general_inertial_scrolling_row.clone() } + pub(crate) fn set_lock_state(&self, pen_mode: PenMode, state: bool) { + match pen_mode { + PenMode::Pen => self.imp().lock_pen_mode.get().set_lock_state(state), + PenMode::Eraser => self.imp().lock_eraser_mode.get().set_lock_state(state), + } + } + pub(crate) fn document_layout(&self) -> Layout { Layout::try_from(self.imp().doc_document_layout_row.get().selected()).unwrap() } @@ -359,6 +374,7 @@ impl RnSettingsPanel { self.refresh_format_ui(active_tab); self.refresh_doc_ui(active_tab); self.refresh_shortcuts_ui(active_tab); + self.refresh_locks_ui(active_tab); } fn refresh_general_ui(&self, active_tab: &RnCanvasWrapper) { @@ -453,6 +469,27 @@ impl RnSettingsPanel { }); } + fn refresh_locks_ui(&self, active_tab: &RnCanvasWrapper) { + let imp = self.imp(); + let canvas = active_tab.canvas(); + + imp.lock_pen_mode.set_action( + canvas + .engine_ref() + .penholder + .pen_mode_state() + .get_style(PenMode::Pen), + ); + + imp.lock_eraser_mode.set_action( + canvas + .engine_ref() + .penholder + .pen_mode_state() + .get_style(PenMode::Eraser), + ); + } + pub(crate) fn init(&self, appwindow: &RnAppWindow) { self.setup_general(appwindow); self.setup_format(appwindow); @@ -575,6 +612,48 @@ impl RnSettingsPanel { } }), ); + + // lock pen + imp.lock_pen_mode + .imp() + .mode + .bind_property("active", appwindow, "lock-pen") + .sync_create() + .bidirectional() + .build(); + + imp.lock_pen_mode.imp().mode.connect_active_notify( + clone!(@weak appwindow => move |switch| { + appwindow + .active_tab_wrapper() + .canvas() + .engine_mut() + .penholder + .pen_mode_state_mut() + .set_lock(rnote_engine::pens::PenMode::Pen,switch.is_active()); + }), + ); + + // lock eraser + imp.lock_eraser_mode + .imp() + .mode + .bind_property("active", appwindow, "lock-eraser") + .sync_create() + .bidirectional() + .build(); + + imp.lock_eraser_mode.imp().mode.connect_active_notify( + clone!(@weak appwindow => move |switch| { + appwindow + .active_tab_wrapper() + .canvas() + .engine_mut() + .penholder + .pen_mode_state_mut() + .set_lock(rnote_engine::pens::PenMode::Eraser,switch.is_active()); + }), + ); } fn setup_format(&self, appwindow: &RnAppWindow) { @@ -826,6 +905,28 @@ impl RnSettingsPanel { appwindow.active_tab_wrapper().canvas().engine_mut().penholder.register_shortcut(ShortcutKey::DrawingPadButton3, action); None })); + + let lock_pen = imp.lock_pen_mode.get(); + let lock_eraser = imp.lock_eraser_mode.get(); + + imp.lock_pen_mode.connect_local( + "action-changed", + false, + clone!(@weak lock_pen, @weak appwindow => @default-return None, move |_values| { + let action = lock_pen.get_action(); + adw::prelude::ActionGroupExt::activate_action(&appwindow, "pen-styles", Some(&("pen",action.to_string()).to_variant())); + None + }), + ); + + imp.lock_eraser_mode.connect_local( + "action-changed", + false, + clone!(@weak lock_eraser, @weak appwindow => @default-return None, move |_values| { + let action = lock_eraser.get_action(); + adw::prelude::ActionGroupExt::activate_action(&appwindow, "pen-styles", Some(&("eraser",action.to_string()).to_variant())); + None + })); } fn revert_format(&self, appwindow: &RnAppWindow) { diff --git a/crates/rnote-ui/src/settingspanel/penmoderow.rs b/crates/rnote-ui/src/settingspanel/penmoderow.rs new file mode 100644 index 0000000000..1113782766 --- /dev/null +++ b/crates/rnote-ui/src/settingspanel/penmoderow.rs @@ -0,0 +1,151 @@ +// Imports +use super::penshortcutmodels::{ + ChangePenStyleIconFactory, ChangePenStyleListFactory, ChangePenStyleListModel, +}; +use adw::{prelude::*, subclass::prelude::*}; +use gtk4::{glib, glib::subclass::*, CompositeTemplate}; +use num_traits::ToPrimitive; +use once_cell::sync::Lazy; +use rnote_engine::pens::PenStyle; +use std::cell::RefCell; + +mod imp { + use super::*; + + #[derive(Debug, CompositeTemplate)] + #[template(resource = "/com/github/flxzt/rnote/ui/penmoderow.ui")] + pub(crate) struct RnPenModeRow { + pub(crate) action: RefCell, + pub(crate) changepenstyle_model: ChangePenStyleListModel, + + #[template_child] + pub(crate) mode: TemplateChild, + } + + impl Default for RnPenModeRow { + fn default() -> Self { + Self { + action: RefCell::new(PenStyle::Eraser), + changepenstyle_model: ChangePenStyleListModel::default(), + + mode: TemplateChild::default(), + } + } + } + + #[glib::object_subclass] + impl ObjectSubclass for RnPenModeRow { + const NAME: &'static str = "RnPenModeRow"; + type Type = super::RnPenModeRow; + type ParentType = adw::ComboRow; + type Interfaces = (); + + fn class_init(klass: &mut Self::Class) { + klass.bind_template(); + } + + fn instance_init(obj: &glib::subclass::InitializingObject) { + obj.init_template(); + } + } + + impl ObjectImpl for RnPenModeRow { + fn constructed(&self) { + self.parent_constructed(); + let obj = self.obj(); + + let list_factory = ChangePenStyleListFactory::default(); + let icon_factory = ChangePenStyleIconFactory::default(); + + obj.set_model(Some(&*self.changepenstyle_model)); + obj.set_list_factory(Some(&*list_factory)); + obj.set_factory(Some(&*icon_factory)); + + obj.connect_selected_item_notify(move |row| { + let new_pen_style = row.pen_style(); + let trigger_action: bool = { + let current_style_res = row.imp().action.try_borrow_mut(); + match current_style_res { + Ok(mut current_style) => { + // when set from the canvas, both are changed at the same time + // it's not the case when a user change the selection + if *current_style != new_pen_style { + *current_style = new_pen_style; + true + } else { + false + } + } + Err(_) => false, // already used somewhere else, aborting + } + }; + if trigger_action { + row.emit_by_name::<()>("action-changed", &[]); + } + }); + } + + fn dispose(&self) { + self.dispose_template(); + while let Some(child) = self.obj().first_child() { + child.unparent(); + } + } + fn signals() -> &'static [Signal] { + static SIGNALS: Lazy> = + Lazy::new(|| vec![Signal::builder("action-changed").build()]); + SIGNALS.as_ref() + } + } + + impl WidgetImpl for RnPenModeRow {} + impl ListBoxRowImpl for RnPenModeRow {} + impl PreferencesRowImpl for RnPenModeRow {} + impl ActionRowImpl for RnPenModeRow {} + impl ComboRowImpl for RnPenModeRow {} +} + +glib::wrapper! { + pub(crate) struct RnPenModeRow(ObjectSubclass) + @extends adw::ComboRow, adw::ActionRow, adw::PreferencesRow, gtk4::ListBoxRow, gtk4::Widget, + @implements gtk4::Accessible, gtk4::Actionable, gtk4::Buildable, gtk4::ConstraintTarget; +} + +impl RnPenModeRow { + #[allow(clippy::new_without_default)] + #[allow(unused)] + pub(crate) fn new() -> Self { + glib::Object::new() + } + + /// get the action immutably + pub(crate) fn get_action(&self) -> PenStyle { + *self.imp().action.borrow() + } + + /// incoming change of state (from the canvas to the settings panel) + pub(crate) fn set_action(&self, action: PenStyle) { + match self.imp().action.try_borrow_mut() { + Ok(mut value) => { + *value = action; + self.set_pen_style(action); + } + Err(e) => { + tracing::debug!("Error borrowing action {:?}", e) + } + } + } + + pub(crate) fn pen_style(&self) -> PenStyle { + PenStyle::try_from(self.selected()).unwrap() + } + + pub(crate) fn set_pen_style(&self, style: PenStyle) { + self.set_selected(style.to_u32().unwrap()) + } + + pub(crate) fn set_lock_state(&self, state: bool) { + self.imp().mode.get().set_state(state); + self.imp().mode.get().set_active(state); + } +}