Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`.

---

Expand Down
5 changes: 3 additions & 2 deletions config.example.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
2 changes: 1 addition & 1 deletion configurator/src/app/pages/tablet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ pub(super) fn build(sender: &ComponentSender<ConfiguratorApp>) -> BuiltPage {
)
.switch_row(
"Enable pressure-to-thickness",
"",
"Freehand Pen strokes only",
|app| app.draft.tablet_pressure_enabled,
|value| Message::ToggleChanged(ToggleField::TabletPressureEnabled, value),
)
Expand Down
3 changes: 3 additions & 0 deletions configurator/src/app/search/terms.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
1 change: 1 addition & 0 deletions configurator/src/app/search/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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",
] {
Expand Down
1 change: 1 addition & 0 deletions docs/CONFIG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
6 changes: 4 additions & 2 deletions src/backend/wayland/handlers/tablet/frame.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
6 changes: 3 additions & 3 deletions src/config/types/tablet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,

Expand Down
88 changes: 78 additions & 10 deletions src/input/tablet/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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)]
Expand Down Expand Up @@ -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);
Expand All @@ -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);
Expand All @@ -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);
Expand All @@ -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);
Expand All @@ -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);

Expand All @@ -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();
Expand Down
11 changes: 11 additions & 0 deletions src/input/tool/catalog.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
..
}
)
}
}
11 changes: 11 additions & 0 deletions src/input/tool/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Loading