feat(pens): seven pens in the toolbar (Issue #56)

Pens 5-7 are stored in a new field, so versions before 0.44 still read
the first four instead of resetting all settings.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
haraldandClaude Opus 5.5 committed 2026-11-09 22:34:35 +01:00
1 parent 14abadbd03
commit 3b0b4f51fb
5 files changed
+87 -15

No files matched your search

+1 -1
View File
@@ -22,7 +22,7 @@ Die Oberfläche gibt es auf Deutsch und Englisch; ink-notes folgt der Systemspra
## Funktionen
### Schreiben und Zeichnen
- **Vier Stifte** in der Werkzeugleiste, jeder mit eigener Farbe und Stärke (0,15–5 mm, mit Druckstärke). Ändert man Farbe oder Stärke, merkt sich der gewählte Stift das – auch über einen Neustart hinweg. **Textmarker** (halbtransparent, 4–8 mm) in vier Farben; innerhalb eines Strichs wird er nicht dunkler, erst ein neuer Strich deckt stärker.
- **Sieben Stifte** in der Werkzeugleiste, jeder mit eigener Farbe und Stärke (0,15–5 mm, mit Druckstärke). Ändert man Farbe oder Stärke, merkt sich der gewählte Stift das – auch über einen Neustart hinweg. **Textmarker** (halbtransparent, 4–8 mm) in vier Farben; innerhalb eines Strichs wird er nicht dunkler, erst ein neuer Strich deckt stärker.
- **Eigene Farben:** Jede Farbe in der Werkzeugleiste (Stifte, Textmarker, Text) lässt sich anpassen – gewählte Farbe erneut antippen, eine Vorgabe oder mit 🎨 einen freien Farbton wählen. Die Leiste merkt sich die Farben. Am Bildschirm erscheinen sie genauso wie im PDF- und Bildexport.
- **Glättung in zwei Stufen:** Während des Schreibens glättet ein 1€-Filter das Zittern, die Linie erscheint sofort. Beim Absetzen wird der Strich nachbearbeitet: Ecken (Knicke über 65°) bleiben erhalten und werden rund verbunden, der Druckverlauf wird geglättet, überflüssige Punkte entfallen, die Enden werden rund.
- **Druckkurve** im ☰-Menü an den eigenen Stift anpassen (weicher/härter, Schwelle gegen Grundrauschen).
+1 -1
View File
@@ -22,7 +22,7 @@ The interface is available in English and German; ink-notes follows the system l
## Features
### Writing and drawing
- **Four pens** in the toolbar, each with its own color and width (0.15–5 mm, pressure-sensitive). When you change a pen's color or width, it remembers that – also across restarts. **Highlighter** (semi-transparent, 4–8 mm) in four colors; it does not get darker within one stroke, only a new stroke covers more strongly.
- **Seven pens** in the toolbar, each with its own color and width (0.15–5 mm, pressure-sensitive). When you change a pen's color or width, it remembers that – also across restarts. **Highlighter** (semi-transparent, 4–8 mm) in four colors; it does not get darker within one stroke, only a new stroke covers more strongly.
- **Custom colors:** Every color in the toolbar (pens, highlighter, text) can be changed – tap the selected color again and pick a preset or any shade with 🎨. The toolbar remembers the colors. They look the same on screen as in PDF and image exports.
- **Two-stage smoothing:** While you write, a 1€ filter removes jitter and the line appears immediately. When you lift the pen, the stroke is refined: corners (bends over 65°) are kept and drawn round, the pressure curve is smoothed, redundant points are dropped and the ends become round.
- **Pressure curve** adjustable to your pen in the ☰ menu (softer/harder, threshold against base noise).
+2 -2
View File
@@ -1467,7 +1467,7 @@ pub fn run_with(
ui_state.pattern_color = ui_settings.pattern_color;
ui_state.pressure_curve = ui_settings.pressure_curve;
ui_state.continuous_scroll = ui_settings.continuous_scroll;
ui_state.pen_presets = ui_settings.pen_presets;
ui_state.pen_presets = ui_settings.pens();
ui_state.stylus_buttons = ui_settings.stylus_buttons.clone();
ui_state.set_eraser_radius_mm(ui_settings.eraser_radius_mm);
ui_state.math.set_enabled(ui_settings.math_assist);
@@ -5594,7 +5594,7 @@ pub fn run_with(
ui_state.sync_tool_colors();
if !egui_ctx.is_using_pointer() {
let mut updated = ui_settings.clone();
updated.pen_presets = ui_state.pen_presets;
updated.set_pens(ui_state.pen_presets);
updated.active_pen = ui_state.active_pen;
updated.highlighter_colors = ui_state.highlighter_colors;
updated.active_highlighter = ui_state.active_highlighter;
+82 -10
View File
@@ -100,10 +100,16 @@ impl PenPreset {
}
}
/// Anzahl der Stifte in der Werkzeugleiste.
pub const PEN_PRESET_COUNT: usize = 4;
/// Anzahl der Stifte in der Werkzeugleiste (Issue #56: seven).
pub const PEN_PRESET_COUNT: usize = 7;
/// Standardstifte: Schwarz, Blau und Rot fein, Grün etwas kräftiger.
/// Pens stored in `pen_presets`. Versions before 0.44 expect exactly four there and would
/// reset all settings for any other length, so the further pens go to `more_pen_presets` and a
/// rollback keeps the first four.
pub const STORED_PEN_PRESETS: usize = 4;
/// Standardstifte: Schwarz, Blau und Rot fein, Grün etwas kräftiger, dazu Violett, Orange und
/// Grau.
pub const DEFAULT_PEN_PRESETS: [PenPreset; PEN_PRESET_COUNT] = [
PenPreset {
color: [0.12, 0.12, 0.14, 1.0],
@@ -121,6 +127,18 @@ pub const DEFAULT_PEN_PRESETS: [PenPreset; PEN_PRESET_COUNT] = [
color: [0.18, 0.65, 0.35, 1.0],
width_mm: 0.8,
},
PenPreset {
color: [0.55, 0.25, 0.75, 1.0],
width_mm: 0.5,
},
PenPreset {
color: [0.95, 0.55, 0.10, 1.0],
width_mm: 0.5,
},
PenPreset {
color: [0.45, 0.45, 0.48, 1.0],
width_mm: 0.5,
},
];
/// Farben des Textmarkers in der Werkzeugleiste (halbtransparent).
@@ -215,8 +233,10 @@ pub struct UiSettings {
/// Frühere globale Einstellung „Seiten durchgehend scrollen“: gilt nur noch für Notizbücher
/// ohne eigene Anordnung im Manifest (vor 0.28 angelegt), wird nicht mehr geändert
pub continuous_scroll: bool,
/// Stifte der Werkzeugleiste und der gewählte davon
pub pen_presets: [PenPreset; PEN_PRESET_COUNT],
/// Stifte der Werkzeugleiste und der gewählte davon: the first four here, the others in
/// `more_pen_presets` (see [`STORED_PEN_PRESETS`]); use [`Self::pens`]
pub pen_presets: [PenPreset; STORED_PEN_PRESETS],
pub more_pen_presets: Vec<PenPreset>,
pub active_pen: usize,
/// Eigene Farben von Textmarker und Text-Werkzeug und die gewählte davon
pub highlighter_colors: [[f32; 4]; 4],
@@ -248,7 +268,13 @@ impl Default for UiSettings {
pattern_color: PatternColor::default(),
pressure_curve: PressureCurve::default(),
continuous_scroll: true,
pen_presets: DEFAULT_PEN_PRESETS,
pen_presets: [
DEFAULT_PEN_PRESETS[0],
DEFAULT_PEN_PRESETS[1],
DEFAULT_PEN_PRESETS[2],
DEFAULT_PEN_PRESETS[3],
],
more_pen_presets: DEFAULT_PEN_PRESETS[STORED_PEN_PRESETS..].to_vec(),
active_pen: 0,
highlighter_colors: DEFAULT_HIGHLIGHTER_COLORS,
active_highlighter: 0,
@@ -283,9 +309,12 @@ impl UiSettings {
Err(_) => Self::default(),
};
settings.pressure_curve = settings.pressure_curve.sanitized();
for (preset, fallback) in settings.pen_presets.iter_mut().zip(DEFAULT_PEN_PRESETS) {
*preset = preset.sanitized(fallback);
let pens = settings.pens();
let mut sanitized = DEFAULT_PEN_PRESETS;
for ((preset, pen), fallback) in sanitized.iter_mut().zip(pens).zip(DEFAULT_PEN_PRESETS) {
*preset = pen.sanitized(fallback);
}
settings.set_pens(sanitized);
settings.active_pen = settings.active_pen.min(PEN_PRESET_COUNT - 1);
for (color, fallback) in settings
.highlighter_colors
@@ -307,6 +336,22 @@ impl UiSettings {
settings
}
/// All pens of the toolbar; pens missing in the file (older versions had four) are the
/// default pens.
pub fn pens(&self) -> [PenPreset; PEN_PRESET_COUNT] {
let mut pens = DEFAULT_PEN_PRESETS;
let stored = self.pen_presets.iter().chain(&self.more_pen_presets);
for (pen, stored) in pens.iter_mut().zip(stored) {
*pen = *stored;
}
pens
}
pub fn set_pens(&mut self, pens: [PenPreset; PEN_PRESET_COUNT]) {
self.pen_presets.copy_from_slice(&pens[..STORED_PEN_PRESETS]);
self.more_pen_presets = pens[STORED_PEN_PRESETS..].to_vec();
}
/// Speichert die Einstellungen atomar.
pub fn save_to(&self, path: &Path) -> Result<(), StorageError> {
storage::atomic_write_json(path, self)
@@ -338,7 +383,14 @@ mod tests {
pen_presets: [PenPreset {
color: [0.5, 0.2, 0.7, 1.0],
width_mm: 1.5,
}; PEN_PRESET_COUNT],
}; STORED_PEN_PRESETS],
more_pen_presets: vec![
PenPreset {
color: [0.1, 0.6, 0.7, 1.0],
width_mm: 2.0,
};
PEN_PRESET_COUNT - STORED_PEN_PRESETS
],
active_pen: 2,
highlighter_colors: [[0.9, 0.5, 0.1, 0.35]; 4],
active_highlighter: 3,
@@ -372,6 +424,24 @@ mod tests {
assert_eq!(UiSettings::load_from(&path), UiSettings::default());
}
#[test]
fn test_seven_pens_stay_readable_for_older_versions() {
// Versions before 0.44 read `pen_presets` as exactly four pens (Issue #56)
#[derive(Deserialize)]
struct OldSettings {
pen_presets: [PenPreset; 4],
}
let mut settings = UiSettings::default();
let mut pens = DEFAULT_PEN_PRESETS;
pens[6].color = [0.3, 0.3, 0.9, 1.0];
settings.set_pens(pens);
let json = serde_json::to_string(&settings).unwrap();
let old: OldSettings = serde_json::from_str(&json).unwrap();
assert_eq!(old.pen_presets[..], DEFAULT_PEN_PRESETS[..4]);
let back: UiSettings = serde_json::from_str(&json).unwrap();
assert_eq!(back.pens(), pens);
}
#[test]
fn test_old_settings_file_keeps_new_defaults() {
// ui.json aus v0.3.x kennt nur die Seite der Werkzeugleiste
@@ -397,7 +467,7 @@ mod tests {
assert_eq!(curve.threshold, 0.0);
// Ohne Stifte: die Standardstifte; unsinnige Stifte werden begrenzt
assert_eq!(settings.pen_presets, DEFAULT_PEN_PRESETS);
assert_eq!(settings.pens(), DEFAULT_PEN_PRESETS);
assert_eq!(settings.active_pen, 0);
std::fs::write(
&path,
@@ -414,6 +484,8 @@ mod tests {
assert_eq!(settings.pen_presets[0].color, [1.0, 0.0, 0.5, 1.0]);
assert_eq!(settings.pen_presets[0].width_mm, 5.0);
assert_eq!(settings.pen_presets[1].width_mm, 0.15);
// Files from before 0.44 have four pens: the others are the default pens
assert_eq!(settings.pens()[4..], DEFAULT_PEN_PRESETS[4..]);
// Eigene Farben: Textmarker bleibt halbtransparent, Text deckend
std::fs::write(
+1 -1
View File
@@ -721,7 +721,7 @@ impl UiState {
}
/// Länge der längsten Farbpalette; so viel Platz hält die Leiste immer frei.
pub const MAX_PALETTE_LEN: usize = 5;
pub const MAX_PALETTE_LEN: usize = PEN_PRESET_COUNT;
/// Vertical gap between toolbar buttons and the height of a divider line.
pub const SIDEBAR_SPACING: f32 = 2.0;
pub const SIDEBAR_DIVIDER: f32 = 3.0;