diff --git a/CHANGELOG.md b/CHANGELOG.md index 3b29891a..9e71f5dc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,3 +6,7 @@ - The unified top toolbar is now the only layout. Panel-era typed config fields and order groups are removed from the serde model (`side_*` placement/pin/pane keys, `show_settings_section` / mode overrides, and `ui.toolbar.items.order.{side_sections,actions,pages,boards,presets,tool_options,sessions}`). Authored values at those exact paths remain in `config.toml` as retired settings and no longer affect the overlay. Matching keys under `runtime-ui.toml`'s recognized `item_order` map are pruned on rewrite. - Public Rust types that described the side palette / panel-only toolbar order groups are gone. Downstream crates that constructed those fields must drop them; the in-repo configurator already matches this shape. + +### Fixed + +- Stylus pressure no longer overrides the selected Marker/Textmarker or Step Marker size. Pressure-to-thickness mapping remains limited to pressure-sensitive freehand Pen strokes. diff --git a/README.md b/README.md index 623aba5b..85c0ba1b 100644 --- a/README.md +++ b/README.md @@ -1103,7 +1103,9 @@ min_thickness = 1.0 max_thickness = 8.0 ``` -It works out of the box in default builds. Set `[tablet].enabled = false` in `config.toml` to opt out. To build without tablet support, drop only that feature (bare `--no-default-features` would also strip portal capture, tray, and the GTK toolbars): `cargo build --release --no-default-features --features portal,tray,toolbar-gtk`. +Pressure controls the width of pressure-sensitive freehand Pen strokes. Marker/Textmarker, Step Marker, and shape tools keep their selected sizes when used with a stylus. + +Tablet input works out of the box in default builds. Set `[tablet].enabled = false` in `config.toml` to opt out. To build without tablet support, drop only that feature (bare `--no-default-features` would also strip portal capture, tray, and the GTK toolbars): `cargo build --release --no-default-features --features portal,tray,toolbar-gtk`. --- diff --git a/config.example.toml b/config.example.toml index 704b992a..45843111 100644 --- a/config.example.toml +++ b/config.example.toml @@ -1320,10 +1320,11 @@ custom_redo_steps = 5 # Enable tablet/stylus input at runtime (set false to opt out) enabled = true -# Map pressure to thickness +# Map pressure to thickness for pressure-sensitive freehand Pen strokes. +# Marker/Textmarker, Step Marker, and shape tools keep their selected sizes. pressure_enabled = true -# Thickness range for pressure mapping +# Freehand Pen thickness range for pressure mapping min_thickness = 1.0 max_thickness = 8.0 diff --git a/configurator/src/app/pages/tablet.rs b/configurator/src/app/pages/tablet.rs index 860ed98a..91d482a7 100644 --- a/configurator/src/app/pages/tablet.rs +++ b/configurator/src/app/pages/tablet.rs @@ -39,7 +39,7 @@ pub(super) fn build(sender: &ComponentSender) -> BuiltPage { ) .switch_row( "Enable pressure-to-thickness", - "", + "Freehand Pen strokes only", |app| app.draft.tablet_pressure_enabled, |value| Message::ToggleChanged(ToggleField::TabletPressureEnabled, value), ) diff --git a/configurator/src/app/search/terms.rs b/configurator/src/app/search/terms.rs index 6ca7b008..6fc17ad2 100644 --- a/configurator/src/app/search/terms.rs +++ b/configurator/src/app/search/terms.rs @@ -448,6 +448,9 @@ pub(super) const TABLET_TERMS: &[&str] = &[ "stylus", "enable tablet input", "enable pressure-to-thickness", + "freehand pen strokes only", + "freehand", + "pen", "auto-switch to eraser", "min thickness", "max thickness", diff --git a/configurator/src/app/search/tests.rs b/configurator/src/app/search/tests.rs index a20e54c3..32c0fe2e 100644 --- a/configurator/src/app/search/tests.rs +++ b/configurator/src/app/search/tests.rs @@ -243,6 +243,7 @@ fn exact_static_section_labels_match_their_sections() { fn exact_tablet_labels_match_tablet_section() { for query in [ "enable pressure-to-thickness", + "freehand pen", "min thickness", "pressure thickness scale step", ] { diff --git a/docs/CONFIG.md b/docs/CONFIG.md index 86271c0e..b9ce07f2 100644 --- a/docs/CONFIG.md +++ b/docs/CONFIG.md @@ -1463,6 +1463,7 @@ action = "toggle_radial_menu" **Notes:** - Requires the `tablet-input` feature at build time (enabled in default release builds). - Tablet input is enabled by default when the feature is compiled in; set `enabled = false` to opt out. +- Pressure-to-thickness mapping applies only to pressure-sensitive freehand Pen strokes. Marker/Textmarker, Step Marker, and shape tools keep their selected sizes when used with a stylus. - `stylus_button` is the primary barrel button (`BTN_STYLUS` / 331); `stylus_button2` is the secondary barrel button (`BTN_STYLUS2` / 332). - Barrel button `action` values use normal action names, such as `toggle_radial_menu`, `undo`, and `redo`. Omit `action` to leave a button unbound. diff --git a/src/backend/wayland/handlers/tablet/frame.rs b/src/backend/wayland/handlers/tablet/frame.rs index 0574c979..08c88bea 100644 --- a/src/backend/wayland/handlers/tablet/frame.rs +++ b/src/backend/wayland/handlers/tablet/frame.rs @@ -105,11 +105,13 @@ impl WaylandState { let first_pressure_sample = self.stylus_tip_down && self.stylus_pressure_thickness.is_none(); let p01 = (pressure as f64) / 65535.0; - crate::input::tablet::apply_pressure_to_state( + if !crate::input::tablet::try_apply_pressure_to_state( p01, &mut self.input_state, self.tablet_settings, - ); + ) { + return; + } if first_pressure_sample { self.input_state .replace_active_drawing_pressure_samples(self.input_state.current_thickness); diff --git a/src/config/types/tablet.rs b/src/config/types/tablet.rs index 70b952c4..aca01c2a 100644 --- a/src/config/types/tablet.rs +++ b/src/config/types/tablet.rs @@ -23,15 +23,15 @@ pub struct TabletInputConfig { #[serde(default = "default_tablet_enabled")] pub enabled: bool, - /// Enable pressure-to-thickness mapping. + /// Enable pressure-to-thickness mapping for pressure-sensitive freehand Pen strokes. #[serde(default = "default_tablet_pressure_enabled")] pub pressure_enabled: bool, - /// Minimum thickness when pressure is near 0. + /// Minimum freehand Pen thickness when pressure is near 0. #[serde(default = "default_tablet_min_thickness")] pub min_thickness: f64, - /// Maximum thickness when pressure is 1.0. + /// Maximum freehand Pen thickness when pressure is 1.0. #[serde(default = "default_tablet_max_thickness")] pub max_thickness: f64, diff --git a/src/input/tablet/mod.rs b/src/input/tablet/mod.rs index b162837d..dd134383 100644 --- a/src/input/tablet/mod.rs +++ b/src/input/tablet/mod.rs @@ -29,10 +29,22 @@ impl Default for TabletSettings { } } -/// Apply a normalized pressure value [0.0, 1.0] to the current thickness. +/// Apply a normalized pressure value [0.0, 1.0] to a pressure-sensitive tool. pub fn apply_pressure_to_state(pressure01: f64, state: &mut InputState, settings: TabletSettings) { - if !settings.enabled || !settings.pressure_enabled { - return; + try_apply_pressure_to_state(pressure01, state, settings); +} + +/// Apply pressure and report whether the active tool accepted the sample. +pub(crate) fn try_apply_pressure_to_state( + pressure01: f64, + state: &mut InputState, + settings: TabletSettings, +) -> bool { + if !settings.enabled + || !settings.pressure_enabled + || !state.active_tool().supports_pressure_thickness() + { + return false; } let p = pressure01.clamp(0.0, 1.0); @@ -50,6 +62,7 @@ pub fn apply_pressure_to_state(pressure01: f64, state: &mut InputState, settings } state.set_pressure_thickness_for_active_tool(new_thickness); + true } #[cfg(test)] @@ -105,7 +118,11 @@ mod tests { state.current_thickness = 3.0; state.needs_redraw = false; - apply_pressure_to_state(0.8, &mut state, TabletSettings::default()); + assert!(!try_apply_pressure_to_state( + 0.8, + &mut state, + TabletSettings::default() + )); assert_eq!(state.current_thickness, 3.0); assert!(!state.needs_redraw); @@ -122,7 +139,7 @@ mod tests { max_thickness: 8.0, }; - apply_pressure_to_state(0.8, &mut state, settings); + assert!(!try_apply_pressure_to_state(0.8, &mut state, settings)); assert_eq!(state.current_thickness, 4.0); assert!(!state.needs_redraw); @@ -139,13 +156,13 @@ mod tests { max_thickness: 6.0, }; - apply_pressure_to_state(-1.0, &mut state, settings); + assert!(try_apply_pressure_to_state(-1.0, &mut state, settings)); assert_eq!(state.current_thickness, 2.0); assert_eq!(state.thickness_for_tool(Tool::Pen), 2.0); assert!(state.needs_redraw); state.needs_redraw = false; - apply_pressure_to_state(2.0, &mut state, settings); + assert!(try_apply_pressure_to_state(2.0, &mut state, settings)); assert_eq!(state.current_thickness, 6.0); assert_eq!(state.thickness_for_tool(Tool::Pen), 6.0); assert!(state.needs_redraw); @@ -161,7 +178,7 @@ mod tests { max_thickness: MAX_STROKE_THICKNESS + 50.0, }; - apply_pressure_to_state(1.0, &mut state, settings); + assert!(try_apply_pressure_to_state(1.0, &mut state, settings)); assert_eq!(state.current_thickness, MAX_STROKE_THICKNESS); assert_eq!(state.thickness_for_tool(Tool::Pen), MAX_STROKE_THICKNESS); @@ -180,9 +197,9 @@ mod tests { state.set_tool_override(Some(Tool::Pen)); state.on_mouse_press(MouseButton::Left, 0, 0); - apply_pressure_to_state(0.0, &mut state, settings); + assert!(try_apply_pressure_to_state(0.0, &mut state, settings)); state.on_mouse_motion(10, 0); - apply_pressure_to_state(1.0, &mut state, settings); + assert!(try_apply_pressure_to_state(1.0, &mut state, settings)); state.on_mouse_motion(20, 0); state.on_mouse_release(MouseButton::Left, 20, 0); @@ -194,6 +211,57 @@ mod tests { assert_eq!(widths, vec![2.0, 2.0, 6.0]); } + #[test] + fn marker_stylus_pressure_preserves_selected_thickness() { + let mut state = make_state(); + let settings = TabletSettings { + enabled: true, + pressure_enabled: true, + min_thickness: 1.0, + max_thickness: 8.0, + }; + + state.set_tool_override(Some(Tool::Marker)); + assert!(state.set_thickness(31.0)); + + assert!(!try_apply_pressure_to_state(0.05, &mut state, settings)); + state.on_mouse_press(MouseButton::Left, 0, 0); + state.on_mouse_motion(10, 0); + state.on_mouse_release(MouseButton::Left, 20, 0); + + assert_eq!(state.thickness_for_tool(Tool::Marker), 31.0); + let shape = &state.boards.active_frame().shapes[0].shape; + let Shape::MarkerStroke { thick, .. } = shape else { + panic!("expected marker stroke"); + }; + assert_eq!(*thick, 31.0); + } + + #[test] + fn step_marker_stylus_pressure_preserves_selected_size() { + let mut state = make_state(); + let settings = TabletSettings { + enabled: true, + pressure_enabled: true, + min_thickness: 1.0, + max_thickness: 8.0, + }; + + state.set_tool_override(Some(Tool::StepMarker)); + assert!(state.set_thickness(30.0)); + + assert!(!try_apply_pressure_to_state(0.05, &mut state, settings)); + state.on_mouse_press(MouseButton::Left, 20, 20); + state.on_mouse_release(MouseButton::Left, 20, 20); + + assert_eq!(state.thickness_for_tool(Tool::StepMarker), 30.0); + let shape = &state.boards.active_frame().shapes[0].shape; + let Shape::StepMarker { label, .. } = shape else { + panic!("expected step marker"); + }; + assert_eq!(label.size, 30.0); + } + #[test] fn first_pressure_sample_replaces_unpressured_stroke_samples() { let mut state = make_state(); diff --git a/src/input/tool/catalog.rs b/src/input/tool/catalog.rs index c9efab03..c1882d80 100644 --- a/src/input/tool/catalog.rs +++ b/src/input/tool/catalog.rs @@ -497,4 +497,15 @@ impl Tool { pub(crate) fn uses_marker_opacity(self) -> bool { self.profile().show_marker_opacity() } + + #[cfg_attr(not(feature = "tablet-input"), allow(dead_code))] + pub(crate) fn supports_pressure_thickness(self) -> bool { + matches!( + self.drawing_behavior(), + ToolDrawingBehavior::Path { + pressure: ToolPressureBehavior::OptionalPressureStroke, + .. + } + ) + } } diff --git a/src/input/tool/tests.rs b/src/input/tool/tests.rs index f7d92b1e..abaefe80 100644 --- a/src/input/tool/tests.rs +++ b/src/input/tool/tests.rs @@ -162,6 +162,17 @@ fn descriptor_exposes_press_motion_and_drawing_behavior() { )); } +#[test] +fn only_freehand_pen_supports_pressure_thickness() { + for tool in Tool::ALL { + assert_eq!( + tool.supports_pressure_thickness(), + tool == Tool::Pen, + "unexpected pressure-thickness behavior for {tool:?}" + ); + } +} + #[test] fn marker_opacity_helper_preserves_current_alpha_clamp() { assert_eq!(marker_color_with_opacity(color(1.0), 0.0).a, 0.05);