feat: Seitenanordnung je Notizbuch (Issue #30, Schritt 1)
Ob ein Notizbuch Seite für Seite oder durchgehend angezeigt wird, steht jetzt im Manifest (`layout`). Beim Anlegen ist „Feste Seiten“ vorgewählt; im ☰-Menü lässt sich die Anordnung des offenen Notizbuchs umschalten. Notizbücher ohne Angabe behalten die frühere globale Einstellung, ältere App-Versionen ignorieren das Feld. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
1b30f2bd26
commit
8ea20aa783
8 files changed
+128
-26
No files matched your search
+6
-3
@@ -20,8 +20,6 @@ pub const EN: &[(&str, &str)] = &[
|
||||
("Alle", "All"),
|
||||
("Alles gespeichert", "All saved"),
|
||||
("Als leere Seite bearbeiten (Schreibschutz aufheben)", "Edit as blank page (remove write protection)"),
|
||||
("An: Seiten hängen untereinander, Mausrad und Finger scrollen (Strg+Mausrad zoomt); wer unten auf der letzten Seite schreibt, bekommt automatisch eine neue Seite.\nAus: Seite für Seite blättern.", "On: pages are stacked, mouse wheel and finger scroll (Ctrl+wheel zooms); writing at the bottom of the last page adds a new page automatically.\nOff: flip page by page."),
|
||||
("ANSICHT", "VIEW"),
|
||||
("AppImage (Portabel)", "AppImage (portable)"),
|
||||
("Asset-Name darf nicht leer sein.", "Asset name must not be empty."),
|
||||
("Auch per Strg+V aus der Zwischenablage oder durch Ziehen einer Bilddatei ins Fenster", "Also with Ctrl+V from the clipboard or by dragging an image file into the window"),
|
||||
@@ -267,6 +265,7 @@ pub const EN: &[(&str, &str)] = &[
|
||||
("Schwarz", "Black"),
|
||||
("Sechseck (Hexagon)", "Hexagon"),
|
||||
("Seite als", "Page as"),
|
||||
("Seite für Seite blättern", "Flip page by page"),
|
||||
("Seite ist schreibgeschützt", "Page is read-only"),
|
||||
("Seite konnte nicht geladen werden", "Page could not be loaded"),
|
||||
("Seite wird exportiert …", "Exporting page …"),
|
||||
@@ -285,7 +284,9 @@ pub const EN: &[(&str, &str)] = &[
|
||||
("Seite {number} – {title}", "Page {number} – {title}"),
|
||||
("Seite {number}", "Page {number}"),
|
||||
("Seiten ({count})", "Pages ({count})"),
|
||||
("Seiten durchgehend scrollen", "Scroll pages continuously"),
|
||||
("SEITEN DIESES NOTIZBUCHS", "PAGES OF THIS NOTEBOOK"),
|
||||
("Seiten hängen untereinander, Mausrad und Finger scrollen (Strg+Mausrad zoomt); wer unten auf der letzten Seite schreibt, bekommt automatisch eine neue Seite.", "Pages are stacked, mouse wheel and finger scroll (Ctrl+wheel zooms); writing at the bottom of the last page adds a new page automatically."),
|
||||
("Seiten:", "Pages:"),
|
||||
("Seitenverhältnis frei – tippen zum Sperren", "Aspect ratio free – tap to lock"),
|
||||
("Seitenverhältnis gesperrt – tippen zum Freigeben", "Aspect ratio locked – tap to unlock"),
|
||||
("Serialisierungsfehler: {e}", "Serialization error: {e}"),
|
||||
@@ -418,12 +419,14 @@ pub const EN: &[(&str, &str)] = &[
|
||||
("📂 Ordner öffnen", "📂 Open folder"),
|
||||
("📄 Als PDF exportieren (Strg+E)", "📄 Export as PDF (Ctrl+E)"),
|
||||
("📄 PDF importieren …", "📄 Import PDF …"),
|
||||
("📄 Feste Seiten", "📄 Fixed pages"),
|
||||
("📄 PDF importieren", "📄 Import PDF"),
|
||||
("📋 Einfügen", "📋 Paste"),
|
||||
("📑 Inhalt", "📑 Contents"),
|
||||
("📚 Alle", "📚 All"),
|
||||
("📚 Notizbücher", "📚 Notebooks"),
|
||||
("📚 Oberste Ebene", "📚 Top level"),
|
||||
("📜 Durchgehend", "📜 Continuous"),
|
||||
("📦 Paket mit Software-Center öffnen / installieren", "📦 Open / install package with the software center"),
|
||||
("🔄 Suche nach Updates …", "🔄 Checking for updates …"),
|
||||
("🔄 Updates & Versionen", "🔄 Updates & versions"),
|
||||
|
||||
+12
-6
@@ -36,7 +36,7 @@ use ink_notes::selection::{
|
||||
use ink_notes::settings::{
|
||||
SidebarSide, StylusAction, StylusButton, UiSettings, MAX_STYLUS_BUTTONS,
|
||||
};
|
||||
use ink_notes::storage::{self, NotebookManifest};
|
||||
use ink_notes::storage::{self, NotebookLayout, NotebookManifest};
|
||||
use ink_notes::t;
|
||||
use ink_notes::ui::{self, ActiveTool, UiAction, UiState};
|
||||
use ink_notes::updater::UpdaterController;
|
||||
@@ -1522,6 +1522,9 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
}
|
||||
|
||||
event_loop.run(move |event, target| {
|
||||
// Anordnung der Seiten gehört zum offenen Notizbuch (Issue #30)
|
||||
ui_state.continuous_scroll =
|
||||
manifest.layout_or(ui_settings.continuous_scroll) == NotebookLayout::Continuous;
|
||||
match event {
|
||||
Event::AboutToWait => {
|
||||
let prev_status = autosave.status();
|
||||
@@ -3562,11 +3565,12 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
}
|
||||
window.request_redraw();
|
||||
}
|
||||
UiAction::CreateNotebook { title, folder } => {
|
||||
UiAction::CreateNotebook { title, folder, layout } => {
|
||||
let _ = autosave.flush();
|
||||
match storage::create_new_notebook(&storage_base_dir, &title) {
|
||||
Ok((new_nb, new_manifest, new_dir)) => {
|
||||
Ok((new_nb, mut new_manifest, new_dir)) => {
|
||||
let id = new_manifest.id.clone();
|
||||
new_manifest.layout = Some(layout);
|
||||
change_library(&storage_base_dir, &mut ui_state, |library| {
|
||||
library.move_notebook(&id, &folder)
|
||||
});
|
||||
@@ -3576,6 +3580,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
notebook = new_nb;
|
||||
manifest = new_manifest;
|
||||
storage_dir = new_dir;
|
||||
autosave.mark_manifest_dirty(&manifest);
|
||||
selection_state.clear();
|
||||
ui_state.current_notebook_id = manifest.id.clone();
|
||||
ui_state.document_title = manifest.title.clone();
|
||||
@@ -3820,9 +3825,10 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
save_ui_settings(&ui_settings, ui_settings_path.as_deref(), &mut ui_state);
|
||||
window.request_redraw();
|
||||
}
|
||||
UiAction::SetContinuousScroll(on) => {
|
||||
ui_settings.continuous_scroll = on;
|
||||
save_ui_settings(&ui_settings, ui_settings_path.as_deref(), &mut ui_state);
|
||||
UiAction::SetNotebookLayout(layout) => {
|
||||
manifest.layout = Some(layout);
|
||||
ui_state.continuous_scroll = layout == NotebookLayout::Continuous;
|
||||
autosave.mark_manifest_dirty(&manifest);
|
||||
window.request_redraw();
|
||||
}
|
||||
UiAction::SaveStylusButtons => {
|
||||
|
||||
+2
-1
@@ -212,7 +212,8 @@ pub struct UiSettings {
|
||||
pub pattern_color: PatternColor,
|
||||
/// Druckkennlinie des Stifts
|
||||
pub pressure_curve: PressureCurve,
|
||||
/// Seiten durchgehend untereinander anzeigen (sonst einzeln blättern)
|
||||
/// 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],
|
||||
|
||||
@@ -280,9 +280,32 @@ pub struct NotebookManifest {
|
||||
pub page_ids: Vec<u64>,
|
||||
pub next_stroke_id: u64,
|
||||
pub next_page_id: u64,
|
||||
/// Anordnung der Seiten. Fehlt bei Notizbüchern von vor 0.28; dann gilt die frühere
|
||||
/// globale Einstellung `continuous_scroll` aus `ui.json` (siehe [`Self::layout_or`]).
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub layout: Option<NotebookLayout>,
|
||||
}
|
||||
|
||||
/// Anordnung der Seiten eines Notizbuchs (Issue #30).
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
|
||||
pub enum NotebookLayout {
|
||||
/// Feste Seiten: Seite für Seite blättern
|
||||
#[default]
|
||||
Paged,
|
||||
/// Seiten hängen untereinander und werden durchgehend gescrollt
|
||||
Continuous,
|
||||
}
|
||||
|
||||
impl NotebookManifest {
|
||||
/// Anordnung des Notizbuchs; ohne eigene Angabe die frühere globale Einstellung.
|
||||
pub fn layout_or(&self, legacy_continuous: bool) -> NotebookLayout {
|
||||
self.layout.unwrap_or(if legacy_continuous {
|
||||
NotebookLayout::Continuous
|
||||
} else {
|
||||
NotebookLayout::Paged
|
||||
})
|
||||
}
|
||||
|
||||
pub fn new(id: impl Into<String>, title: impl Into<String>) -> Self {
|
||||
let now = std::time::SystemTime::now()
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
@@ -299,6 +322,7 @@ impl NotebookManifest {
|
||||
page_ids: vec![1],
|
||||
next_stroke_id: 1,
|
||||
next_page_id: 2,
|
||||
layout: Some(NotebookLayout::Paged),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1604,6 +1628,31 @@ mod tests {
|
||||
assert!(shutdown_res.is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_notebook_layout_is_saved_and_old_manifests_fall_back() {
|
||||
let temp_dir = tempfile::tempdir().unwrap();
|
||||
let (_nb, mut manifest, dir) = create_new_notebook(temp_dir.path(), "Layout").unwrap();
|
||||
// Neue Notizbücher: feste Seiten, unabhängig von der früheren globalen Einstellung
|
||||
assert_eq!(manifest.layout, Some(NotebookLayout::Paged));
|
||||
assert_eq!(manifest.layout_or(true), NotebookLayout::Paged);
|
||||
|
||||
manifest.layout = Some(NotebookLayout::Continuous);
|
||||
save_manifest(&dir, &manifest).unwrap();
|
||||
let (_nb, loaded) = load_notebook(&dir).unwrap();
|
||||
assert_eq!(loaded.layout, Some(NotebookLayout::Continuous));
|
||||
|
||||
// Manifest von vor 0.28 ohne `layout`: es gilt die globale Einstellung
|
||||
let mut json: serde_json::Value =
|
||||
serde_json::from_str(&fs::read_to_string(dir.join("manifest.json")).unwrap()).unwrap();
|
||||
json.as_object_mut().unwrap().remove("layout");
|
||||
let old: NotebookManifest = serde_json::from_value(json).unwrap();
|
||||
assert_eq!(old.layout, None);
|
||||
assert_eq!(old.layout_or(true), NotebookLayout::Continuous);
|
||||
assert_eq!(old.layout_or(false), NotebookLayout::Paged);
|
||||
// … und schreibt beim Speichern auch keins dazu
|
||||
assert!(!serde_json::to_string(&old).unwrap().contains("\"layout\""));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_notebook_management() {
|
||||
let temp_dir = tempfile::tempdir().expect("Konnte temporäres Verzeichnis nicht anlegen");
|
||||
|
||||
@@ -22,7 +22,7 @@ use crate::settings::{
|
||||
PenPreset, PressureCurve, SidebarSide, DEFAULT_HIGHLIGHTER_COLORS, DEFAULT_PEN_PRESETS,
|
||||
DEFAULT_TEXT_COLORS, PEN_PRESET_COUNT,
|
||||
};
|
||||
use crate::storage::NotebookSummary;
|
||||
use crate::storage::{NotebookLayout, NotebookSummary};
|
||||
use crate::text::{TextEditing, TextFont, TEXT_SIZES_PT};
|
||||
use crate::{t, tn};
|
||||
|
||||
@@ -263,6 +263,7 @@ pub enum UiAction {
|
||||
CreateNotebook {
|
||||
title: String,
|
||||
folder: String,
|
||||
layout: NotebookLayout,
|
||||
},
|
||||
/// Ordnerverwaltung (siehe `library.rs`)
|
||||
CreateFolder {
|
||||
@@ -317,8 +318,8 @@ pub enum UiAction {
|
||||
SaveStylusButtons,
|
||||
/// Druckkurve speichern (gilt während des Ziehens bereits über `UiState::pressure_curve`)
|
||||
SavePressureCurve(PressureCurve),
|
||||
/// Seiten durchgehend untereinander statt einzeln anzeigen (wird gespeichert)
|
||||
SetContinuousScroll(bool),
|
||||
/// Anordnung der Seiten des offenen Notizbuchs (wird im Manifest gespeichert)
|
||||
SetNotebookLayout(NotebookLayout),
|
||||
/// Farbe von Karos, Linien und Punkten (wird gespeichert, gilt auch für den Export)
|
||||
SetPatternColor(PatternColor),
|
||||
/// Bilddatei auf der aktuellen Seite einfügen
|
||||
@@ -364,6 +365,8 @@ pub struct UiState {
|
||||
pub document_title: String,
|
||||
pub available_notebooks: Vec<NotebookSummary>,
|
||||
pub new_notebook_title_input: String,
|
||||
/// Anordnung für das nächste neue Notizbuch
|
||||
pub new_notebook_layout: NotebookLayout,
|
||||
pub renaming_notebook_id: Option<String>,
|
||||
/// Ordner der Notizbücher und der gerade angezeigte Ordner im Dokumenten-Manager
|
||||
pub library: crate::library::Library,
|
||||
@@ -483,6 +486,7 @@ impl Default for UiState {
|
||||
document_title: t!("Standard-Notizbuch").to_string(),
|
||||
available_notebooks: Vec::new(),
|
||||
new_notebook_title_input: String::new(),
|
||||
new_notebook_layout: NotebookLayout::Paged,
|
||||
renaming_notebook_id: None,
|
||||
library: crate::library::Library::default(),
|
||||
notebook_folder: String::new(),
|
||||
@@ -1641,6 +1645,38 @@ fn page_title_list(ui: &mut egui::Ui, notebook: &Notebook) -> Option<usize> {
|
||||
chosen
|
||||
}
|
||||
|
||||
/// Auswahl der Seitenanordnung eines Notizbuchs. `true`, wenn sie geändert wurde.
|
||||
fn layout_picker(ui: &mut egui::Ui, layout: &mut NotebookLayout) -> bool {
|
||||
let mut changed = false;
|
||||
ui.horizontal_wrapped(|ui| {
|
||||
for (option, label, tooltip) in [
|
||||
(
|
||||
NotebookLayout::Paged,
|
||||
t!("📄 Feste Seiten"),
|
||||
t!("Seite für Seite blättern"),
|
||||
),
|
||||
(
|
||||
NotebookLayout::Continuous,
|
||||
t!("📜 Durchgehend"),
|
||||
t!("Seiten hängen untereinander, Mausrad und Finger scrollen \
|
||||
(Strg+Mausrad zoomt); wer unten auf der letzten Seite schreibt, \
|
||||
bekommt automatisch eine neue Seite."),
|
||||
),
|
||||
] {
|
||||
if ui
|
||||
.selectable_label(*layout == option, label)
|
||||
.on_hover_text(tooltip)
|
||||
.clicked()
|
||||
&& *layout != option
|
||||
{
|
||||
*layout = option;
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
changed
|
||||
}
|
||||
|
||||
/// Auswahlknopf für eine Papierfarbe: Farbmuster und Name.
|
||||
fn paper_theme_button(ui: &mut egui::Ui, theme: PaperTheme, selected: bool) -> egui::Response {
|
||||
let response = ui.add_sized(
|
||||
@@ -3753,17 +3789,15 @@ pub fn render_ui(
|
||||
}
|
||||
});
|
||||
|
||||
section(ui, t!("ANSICHT"));
|
||||
if ui
|
||||
.checkbox(&mut state.continuous_scroll, t!("Seiten durchgehend scrollen"))
|
||||
.on_hover_text(
|
||||
t!("An: Seiten hängen untereinander, Mausrad und Finger scrollen \
|
||||
(Strg+Mausrad zoomt); wer unten auf der letzten Seite schreibt, \
|
||||
bekommt automatisch eine neue Seite.\nAus: Seite für Seite blättern."),
|
||||
)
|
||||
.changed()
|
||||
{
|
||||
actions.push(UiAction::SetContinuousScroll(state.continuous_scroll));
|
||||
section(ui, t!("SEITEN DIESES NOTIZBUCHS"));
|
||||
let mut layout = if state.continuous_scroll {
|
||||
NotebookLayout::Continuous
|
||||
} else {
|
||||
NotebookLayout::Paged
|
||||
};
|
||||
if layout_picker(ui, &mut layout) {
|
||||
state.continuous_scroll = layout == NotebookLayout::Continuous;
|
||||
actions.push(UiAction::SetNotebookLayout(layout));
|
||||
}
|
||||
|
||||
section(ui, t!("ZOOM"));
|
||||
@@ -4098,6 +4132,7 @@ pub fn render_ui(
|
||||
actions.push(UiAction::CreateNotebook {
|
||||
title,
|
||||
folder: folder.clone(),
|
||||
layout: state.new_notebook_layout,
|
||||
});
|
||||
state.new_notebook_title_input.clear();
|
||||
} else if folder_btn.clicked() {
|
||||
@@ -4109,6 +4144,10 @@ pub fn render_ui(
|
||||
}
|
||||
}
|
||||
});
|
||||
ui.horizontal(|ui| {
|
||||
ui.label(t!("Seiten:"));
|
||||
layout_picker(ui, &mut state.new_notebook_layout);
|
||||
});
|
||||
|
||||
ui.add_space(8.0);
|
||||
ui.separator();
|
||||
|
||||
Reference in new issue
Block a user