Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f76fad92be | ||
|
|
2c60a03460 | ||
|
|
b6580f197d |
Generated
+1
-1
@@ -1048,7 +1048,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ink-notes"
|
name = "ink-notes"
|
||||||
version = "0.3.2"
|
version = "0.3.3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytemuck",
|
"bytemuck",
|
||||||
"egui",
|
"egui",
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "ink-notes"
|
name = "ink-notes"
|
||||||
version = "0.3.2"
|
version = "0.3.3"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
description = "Latenzoptimierte Vektor-Notizbuch- und Inking-Anwendung für Linux"
|
description = "Latenzoptimierte Vektor-Notizbuch- und Inking-Anwendung für Linux"
|
||||||
repository = "https://gitea.pansi.eu/harald/ink-notes"
|
repository = "https://gitea.pansi.eu/harald/ink-notes"
|
||||||
|
|||||||
@@ -3,8 +3,9 @@
|
|||||||
Latenzoptimierte Vektor-Notizbuch- und Inking-Anwendung für Linux (Zorin OS 18 / Ubuntu 24.04).
|
Latenzoptimierte Vektor-Notizbuch- und Inking-Anwendung für Linux (Zorin OS 18 / Ubuntu 24.04).
|
||||||
|
|
||||||
### Highlights:
|
### Highlights:
|
||||||
|
- **Wichtige Fehlerbehebung**: Nach einem Neustart auf einer späteren Seite konnte Weiterschreiben den bisherigen Inhalt dieser Seite überschreiben (betrifft v0.2.3 – v0.3.2)
|
||||||
- **Neue Werkzeugleiste**: Schmale senkrechte Leiste mit Symbol-Knöpfen, links oder rechts andockbar; selten genutzte Funktionen im ☰-Menü, das Blatt wird daneben zentriert
|
- **Neue Werkzeugleiste**: Schmale senkrechte Leiste mit Symbol-Knöpfen, links oder rechts andockbar; selten genutzte Funktionen im ☰-Menü, das Blatt wird daneben zentriert
|
||||||
- **Einheitliche Dialoge**: Deckende Fenster ohne doppelte Titel, Seitenauswahl als Kachelraster, größere Bedienelemente in allen Popovern
|
- **Einheitliche Dialoge**: Deckende Fenster ohne doppelte Titel, Seitenauswahl mit Vorschaubildern, größere Bedienelemente in allen Popovern
|
||||||
- **UI per Stift und Touch bedienbar**: Werkzeugleiste, Popover und Dialoge reagieren jetzt auch auf Stift und Finger, auch bei Display-Skalierung
|
- **UI per Stift und Touch bedienbar**: Werkzeugleiste, Popover und Dialoge reagieren jetzt auch auf Stift und Finger, auch bei Display-Skalierung
|
||||||
- **Aktive Stylus-Unterstützung (Lazarite / MPP / AES)**: Natives Wayland-Tablet-Protokoll mit präziser Druck- und Neigungserfassung
|
- **Aktive Stylus-Unterstützung (Lazarite / MPP / AES)**: Natives Wayland-Tablet-Protokoll mit präziser Druck- und Neigungserfassung
|
||||||
- **Touchscreen Multi-Touch**: Stufenloses 2-Finger Pinch-to-Zoom und Panning mit umschaltbarem Finger-Zeichenmodus
|
- **Touchscreen Multi-Touch**: Stufenloses 2-Finger Pinch-to-Zoom und Panning mit umschaltbarem Finger-Zeichenmodus
|
||||||
|
|||||||
+6
-1
@@ -312,7 +312,12 @@ impl AutosaveController {
|
|||||||
|
|
||||||
/// Markiert eine Seite als geändert und startet/erneuert das Debounce-Fenster.
|
/// Markiert eine Seite als geändert und startet/erneuert das Debounce-Fenster.
|
||||||
pub fn mark_page_dirty(&mut self, page: &Page, manifest: &NotebookManifest) {
|
pub fn mark_page_dirty(&mut self, page: &Page, manifest: &NotebookManifest) {
|
||||||
if self.read_only || page.persist == crate::document::PersistState::ReadOnlyAfterLoadError {
|
// Nicht geladene Platzhalter nie schreiben: Sie sind leer und würden den Inhalt
|
||||||
|
// der Seitendatei überschreiben
|
||||||
|
if self.read_only
|
||||||
|
|| !page.is_loaded
|
||||||
|
|| page.persist == crate::document::PersistState::ReadOnlyAfterLoadError
|
||||||
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let page_data = PageData::from(page);
|
let page_data = PageData::from(page);
|
||||||
|
|||||||
+10
@@ -2068,6 +2068,16 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
update_window_title(&window, ¬ebook, &manifest, ui_state.active_tool, autosave.status());
|
update_window_title(&window, ¬ebook, &manifest, ui_state.active_tool, autosave.status());
|
||||||
window.request_redraw();
|
window.request_redraw();
|
||||||
}
|
}
|
||||||
|
UiAction::LoadAllPages => {
|
||||||
|
// Für die Vorschaubilder der Seitenauswahl; Fehler werden wie beim
|
||||||
|
// Blättern behandelt (Ersatzseite, Warnung beim Öffnen der Seite)
|
||||||
|
for idx in 0..notebook.pages.len() {
|
||||||
|
if !notebook.pages[idx].is_loaded {
|
||||||
|
let _ = storage::ensure_page_loaded(&storage_dir, &mut notebook, idx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
window.request_redraw();
|
||||||
|
}
|
||||||
UiAction::GoToPage(idx) => {
|
UiAction::GoToPage(idx) => {
|
||||||
if idx < notebook.pages.len() {
|
if idx < notebook.pages.len() {
|
||||||
selection_state.clear();
|
selection_state.clear();
|
||||||
|
|||||||
@@ -697,6 +697,15 @@ pub fn load_notebook(notebook_dir: &Path) -> Result<(Notebook, NotebookManifest)
|
|||||||
);
|
);
|
||||||
notebook.load_warnings = load_warnings;
|
notebook.load_warnings = load_warnings;
|
||||||
|
|
||||||
|
// Die zuletzt geöffnete Seite sofort laden: Sie ist nach dem Start die aktuelle Seite,
|
||||||
|
// und ein leerer Platzhalter würde beim Weiterschreiben ihren Inhalt überschreiben.
|
||||||
|
let current = notebook.current_page_index;
|
||||||
|
if !notebook.pages[current].is_loaded {
|
||||||
|
if let Err(e) = ensure_page_loaded(notebook_dir, &mut notebook, current) {
|
||||||
|
eprintln!("[Storage] Aktuelle Seite {current} konnte nicht geladen werden: {e}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Ok((notebook, manifest))
|
Ok((notebook, manifest))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2167,4 +2176,61 @@ mod tests {
|
|||||||
assert_eq!(loaded.pages[1].background, PageBackground::Lined);
|
assert_eq!(loaded.pages[1].background, PageBackground::Lined);
|
||||||
assert!(crate::export::generate_pdf_document(&loaded, Default::default()).is_ok());
|
assert!(crate::export::generate_pdf_document(&loaded, Default::default()).is_ok());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_restart_on_later_page_keeps_its_content() {
|
||||||
|
// Regression: Beim Start wurde nur Seite 1 geladen; war zuletzt eine spätere Seite
|
||||||
|
// offen, war sie ein leerer Platzhalter, und Weiterschreiben überschrieb ihren Inhalt.
|
||||||
|
let temp_dir = tempfile::tempdir().unwrap();
|
||||||
|
let (mut notebook, mut manifest, dir) =
|
||||||
|
create_new_notebook(temp_dir.path(), "Neustart").unwrap();
|
||||||
|
notebook.add_page(PageBackground::Lined);
|
||||||
|
notebook.pages[1] = page_with_one_stroke(notebook.pages[1].id, PageBackground::Lined);
|
||||||
|
notebook.current_page_index = 1;
|
||||||
|
save_notebook(&dir, ¬ebook, &mut manifest).unwrap();
|
||||||
|
|
||||||
|
let (mut loaded, mut loaded_manifest) = load_notebook(&dir).unwrap();
|
||||||
|
assert_eq!(loaded.current_page_index, 1);
|
||||||
|
assert!(
|
||||||
|
loaded.current_page().is_loaded,
|
||||||
|
"aktuelle Seite muss geladen sein"
|
||||||
|
);
|
||||||
|
assert_eq!(loaded.current_page().strokes.len(), 1);
|
||||||
|
|
||||||
|
// Weiterschreiben und speichern darf den bisherigen Inhalt nicht verlieren
|
||||||
|
let extra = page_with_one_stroke(99, PageBackground::Lined).strokes[0].clone();
|
||||||
|
loaded.current_page_mut().strokes.push(extra);
|
||||||
|
let mut autosave = AutosaveController::new(dir.clone(), Duration::from_secs(60));
|
||||||
|
autosave.mark_dirty(&loaded, &mut loaded_manifest);
|
||||||
|
autosave.flush().unwrap();
|
||||||
|
autosave.shutdown().unwrap();
|
||||||
|
|
||||||
|
let (reloaded, _) = load_notebook(&dir).unwrap();
|
||||||
|
assert_eq!(reloaded.current_page().strokes.len(), 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_autosave_never_writes_unloaded_placeholder() {
|
||||||
|
let temp_dir = tempfile::tempdir().unwrap();
|
||||||
|
let (mut notebook, mut manifest, dir) =
|
||||||
|
create_new_notebook(temp_dir.path(), "Platzhalter").unwrap();
|
||||||
|
notebook.add_page(PageBackground::Lined);
|
||||||
|
notebook.pages[1] = page_with_one_stroke(notebook.pages[1].id, PageBackground::Lined);
|
||||||
|
save_notebook(&dir, ¬ebook, &mut manifest).unwrap();
|
||||||
|
let page_file = dir.join(format!("page_{}.json", notebook.pages[1].id));
|
||||||
|
let before = fs::read(&page_file).unwrap();
|
||||||
|
|
||||||
|
// Platzhalter (nicht geladen) als aktuelle Seite markieren und speichern
|
||||||
|
let placeholder = Page::new_unloaded(notebook.pages[1].id, PageBackground::Lined);
|
||||||
|
let mut autosave = AutosaveController::new(dir.clone(), Duration::from_secs(60));
|
||||||
|
autosave.mark_page_dirty(&placeholder, &manifest);
|
||||||
|
autosave.flush().unwrap();
|
||||||
|
autosave.shutdown().unwrap();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
fs::read(&page_file).unwrap(),
|
||||||
|
before,
|
||||||
|
"Seitendatei wurde überschrieben"
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
use std::time::Instant;
|
use std::time::Instant;
|
||||||
|
|
||||||
use crate::autosave::AutosaveStatus;
|
use crate::autosave::AutosaveStatus;
|
||||||
use crate::document::{Notebook, PageBackground};
|
use crate::document::{Notebook, Page, PageBackground, StrokeTool, PAGE_HEIGHT, PAGE_WIDTH};
|
||||||
use crate::settings::SidebarSide;
|
use crate::settings::SidebarSide;
|
||||||
use crate::storage::NotebookSummary;
|
use crate::storage::NotebookSummary;
|
||||||
|
|
||||||
@@ -96,6 +96,8 @@ pub enum UiAction {
|
|||||||
NextPage,
|
NextPage,
|
||||||
AddPage,
|
AddPage,
|
||||||
GoToPage(usize),
|
GoToPage(usize),
|
||||||
|
/// Alle noch nicht geladenen Seiten laden (für die Vorschaubilder der Seitenauswahl)
|
||||||
|
LoadAllPages,
|
||||||
SetBackground(PageBackground),
|
SetBackground(PageBackground),
|
||||||
ExportPdf,
|
ExportPdf,
|
||||||
ZoomIn,
|
ZoomIn,
|
||||||
@@ -699,8 +701,94 @@ fn color_circle_swatch(
|
|||||||
|
|
||||||
/// Spalten der Seitenauswahl.
|
/// Spalten der Seitenauswahl.
|
||||||
const PAGE_PICKER_COLUMNS: usize = 4;
|
const PAGE_PICKER_COLUMNS: usize = 4;
|
||||||
/// Größe einer Kachel der Seitenauswahl.
|
/// Größe einer Kachel der Seitenauswahl: A4-Vorschau plus Zeile für die Seitenzahl.
|
||||||
const PAGE_TILE: egui::Vec2 = egui::vec2(56.0, 48.0);
|
const PAGE_TILE: egui::Vec2 = egui::vec2(66.0, 66.0 * PAGE_HEIGHT / PAGE_WIDTH + 18.0);
|
||||||
|
/// Höchstzahl gezeichneter Punkte pro Vorschau (dünnt volle Seiten aus).
|
||||||
|
const THUMBNAIL_POINT_BUDGET: usize = 1500;
|
||||||
|
|
||||||
|
/// Zeichenbefehle für die verkleinerte Vorschau einer Seite in `rect` (A4-Format).
|
||||||
|
/// Striche werden als Linien gezeichnet und bei vielen Punkten gleichmäßig ausgedünnt.
|
||||||
|
pub fn page_thumbnail_shapes(page: &Page, rect: egui::Rect) -> Vec<egui::Shape> {
|
||||||
|
let mut shapes = vec![egui::Shape::rect_filled(
|
||||||
|
rect,
|
||||||
|
egui::Rounding::same(3.0),
|
||||||
|
egui::Color32::WHITE,
|
||||||
|
)];
|
||||||
|
let scale = rect.width() / PAGE_WIDTH;
|
||||||
|
let to_screen = |p: glam::Vec2| rect.min + egui::vec2(p.x * scale, p.y * scale);
|
||||||
|
|
||||||
|
let total: usize = page.strokes.iter().map(|s| s.points.len()).sum();
|
||||||
|
let step = total.div_ceil(THUMBNAIL_POINT_BUDGET).max(1);
|
||||||
|
|
||||||
|
for stroke in &page.strokes {
|
||||||
|
let mut color = color32_from(stroke.color);
|
||||||
|
if stroke.tool == StrokeTool::Highlighter {
|
||||||
|
color = color.gamma_multiply(0.45);
|
||||||
|
}
|
||||||
|
let width = (stroke.width * scale).clamp(0.6, 4.0);
|
||||||
|
let last = stroke.points.len().saturating_sub(1);
|
||||||
|
let points: Vec<egui::Pos2> = stroke
|
||||||
|
.points
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.filter(|(i, _)| i % step == 0 || *i == last)
|
||||||
|
.map(|(_, s)| to_screen(s.position))
|
||||||
|
.collect();
|
||||||
|
match points.len() {
|
||||||
|
0 => {}
|
||||||
|
1 => shapes.push(egui::Shape::circle_filled(points[0], width / 2.0, color)),
|
||||||
|
_ => shapes.push(egui::Shape::line(points, egui::Stroke::new(width, color))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
shapes
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Kachel der Seitenauswahl: Vorschau der Seite mit Seitenzahl darunter.
|
||||||
|
fn page_thumbnail(
|
||||||
|
ui: &mut egui::Ui,
|
||||||
|
page: &Page,
|
||||||
|
number: usize,
|
||||||
|
is_current: bool,
|
||||||
|
) -> egui::Response {
|
||||||
|
let (rect, response) = ui.allocate_exact_size(PAGE_TILE, egui::Sense::click());
|
||||||
|
if ui.is_rect_visible(rect) {
|
||||||
|
let thumb = egui::Rect::from_min_size(
|
||||||
|
rect.min,
|
||||||
|
egui::vec2(PAGE_TILE.x, PAGE_TILE.x * PAGE_HEIGHT / PAGE_WIDTH),
|
||||||
|
);
|
||||||
|
let painter = ui.painter();
|
||||||
|
painter.extend(page_thumbnail_shapes(page, thumb));
|
||||||
|
if !page.is_loaded {
|
||||||
|
painter.text(
|
||||||
|
thumb.center(),
|
||||||
|
egui::Align2::CENTER_CENTER,
|
||||||
|
"…",
|
||||||
|
egui::FontId::proportional(16.0),
|
||||||
|
egui::Color32::from_gray(140),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
let border = if is_current {
|
||||||
|
egui::Stroke::new(2.5_f32, egui::Color32::from_rgb(90, 150, 255))
|
||||||
|
} else if response.hovered() {
|
||||||
|
egui::Stroke::new(1.5_f32, egui::Color32::from_white_alpha(200))
|
||||||
|
} else {
|
||||||
|
egui::Stroke::new(1.0_f32, egui::Color32::from_white_alpha(60))
|
||||||
|
};
|
||||||
|
painter.rect_stroke(thumb, egui::Rounding::same(3.0), border);
|
||||||
|
painter.text(
|
||||||
|
egui::pos2(rect.center().x, rect.max.y - 8.0),
|
||||||
|
egui::Align2::CENTER_CENTER,
|
||||||
|
number.to_string(),
|
||||||
|
egui::FontId::proportional(13.0),
|
||||||
|
if is_current {
|
||||||
|
egui::Color32::WHITE
|
||||||
|
} else {
|
||||||
|
egui::Color32::from_gray(180)
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
response.on_hover_text(format!("Seite {number}"))
|
||||||
|
}
|
||||||
|
|
||||||
/// Rahmen der Popover neben der Werkzeugleiste.
|
/// Rahmen der Popover neben der Werkzeugleiste.
|
||||||
fn popover_frame() -> egui::Frame {
|
fn popover_frame() -> egui::Frame {
|
||||||
@@ -1696,24 +1784,19 @@ pub fn render_ui(
|
|||||||
if dialog_header(ui, &title, true) {
|
if dialog_header(ui, &title, true) {
|
||||||
state.show_page_jump_popup = false;
|
state.show_page_jump_popup = false;
|
||||||
}
|
}
|
||||||
|
// Noch nicht geladene Seiten für die Vorschau nachladen lassen
|
||||||
|
if notebook.pages.iter().any(|p| !p.is_loaded) {
|
||||||
|
actions.push(UiAction::LoadAllPages);
|
||||||
|
}
|
||||||
egui::ScrollArea::vertical()
|
egui::ScrollArea::vertical()
|
||||||
.max_height(320.0)
|
.max_height(420.0)
|
||||||
.show(ui, |ui| {
|
.show(ui, |ui| {
|
||||||
egui::Grid::new("page_picker_grid")
|
egui::Grid::new("page_picker_grid")
|
||||||
.spacing([8.0, 8.0])
|
.spacing([8.0, 8.0])
|
||||||
.show(ui, |ui| {
|
.show(ui, |ui| {
|
||||||
for idx in 0..notebook.pages.len() {
|
for (idx, page) in notebook.pages.iter().enumerate() {
|
||||||
let is_current = idx == notebook.current_page_index;
|
let is_current = idx == notebook.current_page_index;
|
||||||
let label =
|
if page_thumbnail(ui, page, idx + 1, is_current)
|
||||||
egui::RichText::new(format!("{}", idx + 1))
|
|
||||||
.size(16.0);
|
|
||||||
if ui
|
|
||||||
.add_sized(
|
|
||||||
[56.0, 48.0],
|
|
||||||
egui::SelectableLabel::new(
|
|
||||||
is_current, label,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.clicked()
|
.clicked()
|
||||||
{
|
{
|
||||||
actions.push(UiAction::GoToPage(idx));
|
actions.push(UiAction::GoToPage(idx));
|
||||||
@@ -3005,4 +3088,90 @@ mod tests {
|
|||||||
}
|
}
|
||||||
assert_eq!(reached, (0..6).collect(), "alle Seiten erreichbar");
|
assert_eq!(reached, (0..6).collect(), "alle Seiten erreichbar");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Seite mit `strokes` Strichen à `points` Punkten (diagonal über die Seite).
|
||||||
|
fn page_with_strokes(strokes: usize, points: usize) -> Page {
|
||||||
|
let mut page = Page::new(1, PageBackground::Grid5mm);
|
||||||
|
for i in 0..strokes {
|
||||||
|
let samples: Vec<crate::document::StylusSample> = (0..points)
|
||||||
|
.map(|j| crate::document::StylusSample {
|
||||||
|
position: glam::Vec2::new(
|
||||||
|
100.0 + j as f32 * 1800.0 / points as f32,
|
||||||
|
100.0 + i as f32 * 20.0,
|
||||||
|
),
|
||||||
|
pressure: 0.5,
|
||||||
|
timestamp: j as f64 * 0.01,
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
page.strokes.push(crate::document::Stroke::new(
|
||||||
|
i as u64 + 1,
|
||||||
|
samples,
|
||||||
|
Vec::new(),
|
||||||
|
[0.1, 0.1, 0.1, 1.0],
|
||||||
|
));
|
||||||
|
}
|
||||||
|
page
|
||||||
|
}
|
||||||
|
|
||||||
|
fn shape_points(shapes: &[egui::Shape]) -> Vec<egui::Pos2> {
|
||||||
|
shapes
|
||||||
|
.iter()
|
||||||
|
.flat_map(|s| match s {
|
||||||
|
egui::Shape::Path(p) => p.points.clone(),
|
||||||
|
egui::Shape::Circle(c) => vec![c.center],
|
||||||
|
_ => Vec::new(),
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_page_thumbnail_shapes() {
|
||||||
|
let rect = egui::Rect::from_min_size(egui::pos2(50.0, 80.0), egui::vec2(66.0, 93.0));
|
||||||
|
|
||||||
|
// Leere Seite: nur das weiße Blatt
|
||||||
|
let empty = Page::new(1, PageBackground::Blank);
|
||||||
|
assert_eq!(page_thumbnail_shapes(&empty, rect).len(), 1);
|
||||||
|
|
||||||
|
// Striche liegen innerhalb der Kachel
|
||||||
|
let page = page_with_strokes(3, 20);
|
||||||
|
let shapes = page_thumbnail_shapes(&page, rect);
|
||||||
|
assert_eq!(shapes.len(), 1 + 3);
|
||||||
|
let pts = shape_points(&shapes);
|
||||||
|
assert!(!pts.is_empty());
|
||||||
|
assert!(
|
||||||
|
pts.iter().all(|p| rect.contains(*p)),
|
||||||
|
"Punkte außerhalb der Kachel"
|
||||||
|
);
|
||||||
|
|
||||||
|
// Sehr volle Seite wird auf das Punktbudget ausgedünnt (+ Endpunkte je Strich)
|
||||||
|
let full = page_with_strokes(100, 1000);
|
||||||
|
let pts = shape_points(&page_thumbnail_shapes(&full, rect));
|
||||||
|
assert!(
|
||||||
|
pts.len() <= THUMBNAIL_POINT_BUDGET + 2 * 100,
|
||||||
|
"{} Punkte statt höchstens {}",
|
||||||
|
pts.len(),
|
||||||
|
THUMBNAIL_POINT_BUDGET + 200
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_page_picker_requests_loading_of_unloaded_pages() {
|
||||||
|
let updater = crate::updater::UpdaterController::new();
|
||||||
|
for (unloaded, expected) in [(false, false), (true, true)] {
|
||||||
|
let mut notebook = Notebook::new();
|
||||||
|
notebook.add_page(PageBackground::Grid5mm);
|
||||||
|
if unloaded {
|
||||||
|
notebook.pages[1] =
|
||||||
|
Page::new_unloaded(notebook.pages[1].id, PageBackground::Grid5mm);
|
||||||
|
}
|
||||||
|
let mut state = UiState::new();
|
||||||
|
state.show_page_jump_popup = true;
|
||||||
|
let ctx = render_frames(&mut state, ¬ebook, 3);
|
||||||
|
ctx.begin_frame(egui::RawInput::default());
|
||||||
|
let actions = render_ui(&ctx, &mut state, ¬ebook, AutosaveStatus::Saved, &updater);
|
||||||
|
let _ = ctx.end_frame();
|
||||||
|
let requested = actions.iter().any(|a| matches!(a, UiAction::LoadAllPages));
|
||||||
|
assert_eq!(requested, expected, "ungeladene Seite: {unloaded}");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user