feat: Stifte, Seiten löschen, lückenlose Seiten, sauberer Textmarker (Issues #11, #12, #14, #15, #17, #19)

- #15: Koordinatensystem ohne Kerbe am Ursprung (Achsen beginnen eine halbe
  Linienbreite hinter dem Ursprung; im PDF ein Linienzug mit spitzer Ecke)
- #17: Jeder Strich deckt ein Pixel nur einmal (Stencil-Puffer), der
  Textmarker wird innerhalb eines Strichs nicht dunkler. Im PDF-Export ist der
  Textmarker jetzt halbtransparent statt deckend.
- #11: ➕ in der Werkzeugleiste fügt nach der aktuellen Seite eine Seite ein
- #12: Seite löschen im ☰-Menü mit Rückfrage, in der Sitzung wiederherstellbar
- #14: Seiten liegen lückenlos untereinander, eine dunkle Linie markiert die Grenze
- #19: Vier Stifte mit eigener Farbe und Stärke, die sich Änderungen merken
  (ui.json); Blätterpfeile nur noch in der Einzelseitenansicht

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-08 15:55:32 +02:00
co-authored by Claude Opus 5.5
parent 904b36e5f7
commit 3890b6f182
14 changed files with 844 additions and 80 deletions
+56
View File
@@ -1217,6 +1217,29 @@ impl Notebook {
index
}
/// Entfernt die aktuelle Seite und gibt sie zurück; danach ist die folgende Seite aktuell
/// (bzw. die vorige, wenn es die letzte war). Die einzige Seite eines Notizbuchs bleibt
/// stehen (`None`).
pub fn remove_current_page(&mut self) -> Option<Page> {
if self.pages.len() < 2 {
return None;
}
let page = self.pages.remove(self.current_page_index);
self.load_warnings.remove(&page.id);
self.current_page_index = self.current_page_index.min(self.pages.len() - 1);
Some(page)
}
/// Setzt eine mit [`Self::remove_current_page`] entfernte Seite wieder an `index` ein
/// (höchstens ans Ende) und macht sie zur aktuellen Seite; gibt ihren Index zurück.
pub fn restore_page(&mut self, index: usize, page: Page) -> usize {
let index = index.min(self.pages.len());
self.next_page_id = self.next_page_id.max(page.id + 1);
self.pages.insert(index, page);
self.current_page_index = index;
index
}
/// Durchgehende Ansicht: Reicht ein neuer Strich auf der letzten Seite bis in deren
/// unteres Fünftel ([`AUTO_APPEND_FRACTION`]), wird darunter eine leere Seite mit gleichem
/// Hintergrund angehängt. `true`, wenn eine Seite angehängt wurde.
@@ -1982,6 +2005,39 @@ mod tests {
assert!(new.pdf_background.is_none());
}
#[test]
fn test_remove_and_restore_current_page() {
let mut nb = Notebook::new();
// Die einzige Seite bleibt
assert!(nb.remove_current_page().is_none());
nb.add_page(PageBackground::Lined);
nb.add_page(PageBackground::DotGrid);
let ids: Vec<u64> = nb.pages.iter().map(|p| p.id).collect();
// Mittlere Seite: danach ist die folgende aktuell
nb.current_page_index = 1;
let removed = nb.remove_current_page().unwrap();
assert_eq!(removed.id, ids[1]);
assert_eq!(
nb.pages.iter().map(|p| p.id).collect::<Vec<_>>(),
[ids[0], ids[2]]
);
assert_eq!(nb.current_page_index, 1);
// Wiederherstellen an der alten Stelle
assert_eq!(nb.restore_page(1, removed), 1);
assert_eq!(nb.pages.iter().map(|p| p.id).collect::<Vec<_>>(), ids);
assert_eq!(nb.current_page_index, 1);
// Letzte Seite: danach ist die vorige aktuell
nb.current_page_index = 2;
let last = nb.remove_current_page().unwrap();
assert_eq!(last.id, ids[2]);
assert_eq!(nb.current_page_index, 1);
// Neue Seiten bekommen keine schon vergebene Kennung
assert!(nb.next_page_id > ids[2]);
}
#[test]
fn test_append_page_when_writing_at_bottom_of_last_page() {
let mut nb = Notebook::new();