Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5fafff5eaf | ||
|
|
d21fa91315 | ||
|
|
b00767b457 |
Generated
+1
-1
@@ -1393,7 +1393,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ink-notes"
|
name = "ink-notes"
|
||||||
version = "0.29.0"
|
version = "0.29.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ab_glyph",
|
"ab_glyph",
|
||||||
"arboard",
|
"arboard",
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "ink-notes"
|
name = "ink-notes"
|
||||||
version = "0.29.0"
|
version = "0.29.1"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
description = "Notizbuch-App für Stift, Touch und Maus unter Linux"
|
description = "Notizbuch-App für Stift, Touch und Maus unter Linux"
|
||||||
repository = "https://gitea.pansi.eu/harald/ink-notes"
|
repository = "https://gitea.pansi.eu/harald/ink-notes"
|
||||||
|
|||||||
@@ -84,6 +84,11 @@
|
|||||||
</branding>
|
</branding>
|
||||||
<content_rating type="oars-1.1" />
|
<content_rating type="oars-1.1" />
|
||||||
<releases>
|
<releases>
|
||||||
|
<release version="0.29.1" date="2026-10-19">
|
||||||
|
<description>
|
||||||
|
<p>Moving, rotating and scaling large selections is smooth again and no longer keeps the processor busy. On endless notebooks the mouse wheel scrolls (Ctrl+wheel zooms).</p>
|
||||||
|
</description>
|
||||||
|
</release>
|
||||||
<release version="0.29.0" date="2026-10-18">
|
<release version="0.29.0" date="2026-10-18">
|
||||||
<description>
|
<description>
|
||||||
<p>Endless notebooks: one surface without edges to write, pan and zoom in every direction. Choose it when creating a notebook or switch a single-page notebook in the menu; PDF export contains only the written area.</p>
|
<p>Endless notebooks: one surface without edges to write, pan and zoom in every direction. Choose it when creating a notebook or switch a single-page notebook in the menu; PDF export contains only the written area.</p>
|
||||||
|
|||||||
@@ -84,6 +84,11 @@
|
|||||||
</branding>
|
</branding>
|
||||||
<content_rating type="oars-1.1" />
|
<content_rating type="oars-1.1" />
|
||||||
<releases>
|
<releases>
|
||||||
|
<release version="0.29.1" date="2026-10-19">
|
||||||
|
<description>
|
||||||
|
<p>Moving, rotating and scaling large selections is smooth again and no longer keeps the processor busy. On endless notebooks the mouse wheel scrolls (Ctrl+wheel zooms).</p>
|
||||||
|
</description>
|
||||||
|
</release>
|
||||||
<release version="0.29.0" date="2026-10-18">
|
<release version="0.29.0" date="2026-10-18">
|
||||||
<description>
|
<description>
|
||||||
<p>Endless notebooks: one surface without edges to write, pan and zoom in every direction. Choose it when creating a notebook or switch a single-page notebook in the menu; PDF export contains only the written area.</p>
|
<p>Endless notebooks: one surface without edges to write, pan and zoom in every direction. Choose it when creating a notebook or switch a single-page notebook in the menu; PDF export contains only the written area.</p>
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
Notizbuch-App für Stift, Touch und Maus unter Linux (getestet mit Zorin OS 18 / Ubuntu 24.04).
|
Notizbuch-App für Stift, Touch und Maus unter Linux (getestet mit Zorin OS 18 / Ubuntu 24.04).
|
||||||
|
|
||||||
### Highlights:
|
### Highlights:
|
||||||
|
- **Fehlerbehebung Auswahl**: Verschieben, Drehen und Skalieren großer Auswahlen ist wieder flüssig und lastet den Prozessor nicht mehr aus (seit 0.27 wurde die Auswahl bei jedem Bild mehrfach kopiert). Auf der endlosen Fläche scrollt das Mausrad (Strg+Mausrad zoomt)
|
||||||
- **Endlose Fläche**: Neue Seitenanordnung „♾ Endlos“ – eine Fläche ohne Rand, auf der man in alle Richtungen schreibt, verschiebt und zoomt. Wählbar beim Anlegen oder im ☰-Menü (bei Notizbüchern mit einer Seite); der PDF-Export enthält nur den beschriebenen Bereich. Neue Dateiformatversion 9: ältere App-Versionen öffnen Notizbücher dieser Version nicht mehr
|
- **Endlose Fläche**: Neue Seitenanordnung „♾ Endlos“ – eine Fläche ohne Rand, auf der man in alle Richtungen schreibt, verschiebt und zoomt. Wählbar beim Anlegen oder im ☰-Menü (bei Notizbüchern mit einer Seite); der PDF-Export enthält nur den beschriebenen Bereich. Neue Dateiformatversion 9: ältere App-Versionen öffnen Notizbücher dieser Version nicht mehr
|
||||||
- **Seitenanordnung je Notizbuch**: Beim Anlegen wählbar – „Feste Seiten“ (Vorgabe) oder „Durchgehend“ –, später im ☰-Menü umschaltbar. Bestehende Notizbücher behalten die bisherige Einstellung
|
- **Seitenanordnung je Notizbuch**: Beim Anlegen wählbar – „Feste Seiten“ (Vorgabe) oder „Durchgehend“ –, später im ☰-Menü umschaltbar. Bestehende Notizbücher behalten die bisherige Einstellung
|
||||||
- **Stifte in ihrer Farbe**: Die Stifte in der Werkzeugleiste erscheinen als Stifte in ihrer eigenen Farbe statt als dünner Strich und sind so auf einen Blick zu unterscheiden
|
- **Stifte in ihrer Farbe**: Die Stifte in der Werkzeugleiste erscheinen als Stifte in ihrer eigenen Farbe statt als dünner Strich und sind so auf einen Blick zu unterscheiden
|
||||||
|
|||||||
+72
-30
@@ -1,5 +1,6 @@
|
|||||||
//! Notizbuch- und Seitenverwaltung mit DIN-A4-Koordinatensystem und Vektor-Stroke-Modell.
|
//! Notizbuch- und Seitenverwaltung mit DIN-A4-Koordinatensystem und Vektor-Stroke-Modell.
|
||||||
|
|
||||||
|
use std::collections::{HashMap, HashSet};
|
||||||
use std::ops::Range;
|
use std::ops::Range;
|
||||||
|
|
||||||
use crate::canvas::InkVertex;
|
use crate::canvas::InkVertex;
|
||||||
@@ -613,18 +614,24 @@ impl Stroke {
|
|||||||
|
|
||||||
/// Berechnet die Bounding Box aus den aktuellen Punkten/Vertizes neu.
|
/// Berechnet die Bounding Box aus den aktuellen Punkten/Vertizes neu.
|
||||||
pub fn recompute_aabb(&mut self) {
|
pub fn recompute_aabb(&mut self) {
|
||||||
|
// Ohne Zwischenliste: läuft beim Ziehen einer Auswahl für jeden Strich und jede Bewegung
|
||||||
|
let bounds = |mut pts: Box<dyn Iterator<Item = glam::Vec2> + '_>| {
|
||||||
|
pts.next()
|
||||||
|
.map_or(Aabb::new(glam::Vec2::ZERO, glam::Vec2::ZERO), |first| {
|
||||||
|
pts.fold(Aabb::new(first, first), |mut aabb, p| {
|
||||||
|
aabb.expand_by_point(p);
|
||||||
|
aabb
|
||||||
|
})
|
||||||
|
})
|
||||||
|
};
|
||||||
self.aabb = if !self.points.is_empty() {
|
self.aabb = if !self.points.is_empty() {
|
||||||
let pts: Vec<glam::Vec2> = self.points.iter().map(|s| s.position).collect();
|
bounds(Box::new(self.points.iter().map(|s| s.position)))
|
||||||
Aabb::from_points(&pts)
|
|
||||||
} else if !self.vertices.is_empty() {
|
|
||||||
let pts: Vec<glam::Vec2> = self
|
|
||||||
.vertices
|
|
||||||
.iter()
|
|
||||||
.map(|v| glam::Vec2::new(v.position[0], v.position[1]))
|
|
||||||
.collect();
|
|
||||||
Aabb::from_points(&pts)
|
|
||||||
} else {
|
} else {
|
||||||
Aabb::new(glam::Vec2::ZERO, glam::Vec2::ZERO)
|
bounds(Box::new(
|
||||||
|
self.vertices
|
||||||
|
.iter()
|
||||||
|
.map(|v| glam::Vec2::new(v.position[0], v.position[1])),
|
||||||
|
))
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1066,6 +1073,7 @@ impl Page {
|
|||||||
|
|
||||||
/// Liefert Klone der Striche mit den angegebenen IDs.
|
/// Liefert Klone der Striche mit den angegebenen IDs.
|
||||||
pub fn get_strokes(&self, ids: &[u64]) -> Vec<Stroke> {
|
pub fn get_strokes(&self, ids: &[u64]) -> Vec<Stroke> {
|
||||||
|
let ids: HashSet<u64> = ids.iter().copied().collect();
|
||||||
self.strokes
|
self.strokes
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|s| ids.contains(&s.id))
|
.filter(|s| ids.contains(&s.id))
|
||||||
@@ -1073,24 +1081,31 @@ impl Page {
|
|||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Ersetzt die Striche mit den IDs aus `modified` an ihrer Stelle. Über eine Tabelle
|
||||||
|
/// statt je Strich die ganze Seite zu durchsuchen: Beim Ziehen einer großen Auswahl
|
||||||
|
/// läuft das bei jeder Stiftbewegung.
|
||||||
|
fn replace_strokes(&mut self, modified: &[Stroke]) {
|
||||||
|
let index: HashMap<u64, usize> = self
|
||||||
|
.strokes
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(i, s)| (s.id, i))
|
||||||
|
.collect();
|
||||||
|
for m in modified {
|
||||||
|
if let Some(&pos) = index.get(&m.id) {
|
||||||
|
self.strokes[pos] = m.clone();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Aktualisiert eine Menge geänderter Striche transaktional im History-Stack.
|
/// Aktualisiert eine Menge geänderter Striche transaktional im History-Stack.
|
||||||
pub fn modify_strokes(&mut self, modified: Vec<Stroke>) {
|
pub fn modify_strokes(&mut self, modified: Vec<Stroke>) {
|
||||||
if modified.is_empty() {
|
if modified.is_empty() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let ids: Vec<u64> = modified.iter().map(|s| s.id).collect();
|
let ids: Vec<u64> = modified.iter().map(|s| s.id).collect();
|
||||||
let before: Vec<Stroke> = self
|
let before = self.get_strokes(&ids);
|
||||||
.strokes
|
self.replace_strokes(&modified);
|
||||||
.iter()
|
|
||||||
.filter(|s| ids.contains(&s.id))
|
|
||||||
.cloned()
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
for m in modified.iter() {
|
|
||||||
if let Some(pos) = self.strokes.iter().position(|s| s.id == m.id) {
|
|
||||||
self.strokes[pos] = m.clone();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
self.history.push(HistoryAction::ModifyStrokes {
|
self.history.push(HistoryAction::ModifyStrokes {
|
||||||
before,
|
before,
|
||||||
@@ -1101,9 +1116,33 @@ impl Page {
|
|||||||
|
|
||||||
/// Aktualisiert Striche flüchtig (z. B. während des Ziehens / Dragging), OHNE die History zu verändern.
|
/// Aktualisiert Striche flüchtig (z. B. während des Ziehens / Dragging), OHNE die History zu verändern.
|
||||||
pub fn update_strokes_ephemeral(&mut self, modified: &[Stroke]) {
|
pub fn update_strokes_ephemeral(&mut self, modified: &[Stroke]) {
|
||||||
for m in modified {
|
self.replace_strokes(modified);
|
||||||
if let Some(pos) = self.strokes.iter().position(|s| s.id == m.id) {
|
self.is_dirty = true;
|
||||||
self.strokes[pos] = m.clone();
|
}
|
||||||
|
|
||||||
|
/// Wie [`Self::update_strokes_ephemeral`] für eine Transformation der Ausgangsstriche
|
||||||
|
/// `originals`: setzt die Striche auf der Seite auf ihren Ausgangszustand zurück und wendet
|
||||||
|
/// `transform` an. Verwendet die Puffer der Seitenstriche weiter, statt bei jeder
|
||||||
|
/// Stiftbewegung die ganze Auswahl zweimal zu kopieren.
|
||||||
|
pub fn transform_strokes_ephemeral(
|
||||||
|
&mut self,
|
||||||
|
originals: &[Stroke],
|
||||||
|
transform: impl Fn(&mut Stroke),
|
||||||
|
) {
|
||||||
|
let index: HashMap<u64, usize> = self
|
||||||
|
.strokes
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(i, s)| (s.id, i))
|
||||||
|
.collect();
|
||||||
|
for original in originals {
|
||||||
|
if let Some(&pos) = index.get(&original.id) {
|
||||||
|
let stroke = &mut self.strokes[pos];
|
||||||
|
// Nur was Verschieben, Drehen und Skalieren verändern
|
||||||
|
stroke.points.clone_from(&original.points);
|
||||||
|
stroke.vertices.clone_from(&original.vertices);
|
||||||
|
stroke.shape.clone_from(&original.shape);
|
||||||
|
transform(stroke);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.is_dirty = true;
|
self.is_dirty = true;
|
||||||
@@ -1114,11 +1153,7 @@ impl Page {
|
|||||||
if before.is_empty() || after.is_empty() {
|
if before.is_empty() || after.is_empty() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
for m in &after {
|
self.replace_strokes(&after);
|
||||||
if let Some(pos) = self.strokes.iter().position(|s| s.id == m.id) {
|
|
||||||
self.strokes[pos] = m.clone();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
self.history
|
self.history
|
||||||
.push(HistoryAction::ModifyStrokes { before, after });
|
.push(HistoryAction::ModifyStrokes { before, after });
|
||||||
self.is_dirty = true;
|
self.is_dirty = true;
|
||||||
@@ -2673,6 +2708,13 @@ mod tests {
|
|||||||
// History should not have been updated yet
|
// History should not have been updated yet
|
||||||
assert!(!page.history.can_redo());
|
assert!(!page.history.can_redo());
|
||||||
|
|
||||||
|
// Ziehen über die Ausgangsstriche: jede Bewegung gilt ab dem Ausgangszustand
|
||||||
|
for delta in [glam::Vec2::new(5.0, 0.0), glam::Vec2::new(20.0, 30.0)] {
|
||||||
|
page.transform_strokes_ephemeral(std::slice::from_ref(&s), |st| st.translate(delta));
|
||||||
|
}
|
||||||
|
assert_eq!(page.strokes[0], moved);
|
||||||
|
assert!(!page.history.can_redo());
|
||||||
|
|
||||||
// Transactional commit (mouse release)
|
// Transactional commit (mouse release)
|
||||||
let before = vec![s.clone()];
|
let before = vec![s.clone()];
|
||||||
let after = vec![moved.clone()];
|
let after = vec![moved.clone()];
|
||||||
|
|||||||
+42
-35
@@ -691,62 +691,65 @@ fn selection_press(
|
|||||||
_ => return false,
|
_ => return false,
|
||||||
};
|
};
|
||||||
selection_state.drag_mode = mode;
|
selection_state.drag_mode = mode;
|
||||||
|
selection_state.pending_drag = None;
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Wendet die zuletzt gemeldete Zugposition der Auswahl an (siehe [`selection_drag`]).
|
||||||
|
fn flush_selection_drag(
|
||||||
|
page: &mut ink_notes::document::Page,
|
||||||
|
canvas: &mut CanvasState,
|
||||||
|
selection_state: &mut SelectionState,
|
||||||
|
) {
|
||||||
|
if let Some(page_pt) = selection_state.pending_drag.take() {
|
||||||
|
selection_drag(page_pt, page, canvas, selection_state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Auswahlwerkzeug: Ziehen nach `page_pt` – Rahmen aufziehen oder die Auswahl verschieben,
|
/// Auswahlwerkzeug: Ziehen nach `page_pt` – Rahmen aufziehen oder die Auswahl verschieben,
|
||||||
/// drehen bzw. skalieren (vorläufig, ohne Undo-Schritt).
|
/// drehen bzw. skalieren (vorläufig, ohne Undo-Schritt). Bei Stiftbewegungen über
|
||||||
|
/// `pending_drag` und [`flush_selection_drag`] nur einmal je Bild, weil das bei großen
|
||||||
|
/// Auswahlen teuer ist.
|
||||||
fn selection_drag(
|
fn selection_drag(
|
||||||
page_pt: Vec2,
|
page_pt: Vec2,
|
||||||
page: &mut ink_notes::document::Page,
|
page: &mut ink_notes::document::Page,
|
||||||
canvas: &mut CanvasState,
|
canvas: &mut CanvasState,
|
||||||
selection_state: &mut SelectionState,
|
selection_state: &mut SelectionState,
|
||||||
) {
|
) {
|
||||||
let mut updated = match &mut selection_state.drag_mode {
|
match &mut selection_state.drag_mode {
|
||||||
DragMode::BoxSelect { current_page, .. } => {
|
DragMode::BoxSelect { current_page, .. } => {
|
||||||
*current_page = page_pt;
|
*current_page = page_pt;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
DragMode::None => return,
|
DragMode::None => return,
|
||||||
DragMode::Move {
|
DragMode::Move {
|
||||||
initial_strokes, ..
|
start_page,
|
||||||
}
|
initial_strokes,
|
||||||
| DragMode::Rotate {
|
} => {
|
||||||
initial_strokes, ..
|
|
||||||
}
|
|
||||||
| DragMode::Scale {
|
|
||||||
initial_strokes, ..
|
|
||||||
} => initial_strokes.clone(),
|
|
||||||
};
|
|
||||||
match &selection_state.drag_mode {
|
|
||||||
DragMode::Move { start_page, .. } => {
|
|
||||||
let delta = page_pt - *start_page;
|
let delta = page_pt - *start_page;
|
||||||
updated.iter_mut().for_each(|s| s.translate(delta));
|
page.transform_strokes_ephemeral(initial_strokes, |s| s.translate(delta));
|
||||||
}
|
}
|
||||||
DragMode::Rotate {
|
DragMode::Rotate {
|
||||||
center_page,
|
center_page,
|
||||||
start_angle,
|
start_angle,
|
||||||
..
|
initial_strokes,
|
||||||
} => {
|
} => {
|
||||||
let angle = (page_pt.y - center_page.y).atan2(page_pt.x - center_page.x) - start_angle;
|
let angle = (page_pt.y - center_page.y).atan2(page_pt.x - center_page.x) - *start_angle;
|
||||||
updated
|
let center = *center_page;
|
||||||
.iter_mut()
|
page.transform_strokes_ephemeral(initial_strokes, |s| s.rotate(center, angle));
|
||||||
.for_each(|s| s.rotate(*center_page, angle));
|
|
||||||
}
|
}
|
||||||
DragMode::Scale {
|
DragMode::Scale {
|
||||||
fixed_corner_page,
|
fixed_corner_page,
|
||||||
start_pos_page,
|
start_pos_page,
|
||||||
keep_aspect,
|
keep_aspect,
|
||||||
|
initial_strokes,
|
||||||
..
|
..
|
||||||
} => {
|
} => {
|
||||||
let factors = scale_factors(*fixed_corner_page, *start_pos_page, page_pt, *keep_aspect);
|
let fixed = *fixed_corner_page;
|
||||||
updated
|
let factors = scale_factors(fixed, *start_pos_page, page_pt, *keep_aspect);
|
||||||
.iter_mut()
|
page.transform_strokes_ephemeral(initial_strokes, |s| s.scale(fixed, factors));
|
||||||
.for_each(|s| s.scale(*fixed_corner_page, factors));
|
|
||||||
}
|
}
|
||||||
DragMode::BoxSelect { .. } | DragMode::None => return,
|
|
||||||
}
|
}
|
||||||
page.update_strokes_ephemeral(&updated);
|
|
||||||
canvas.sync_page(page);
|
canvas.sync_page(page);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -759,7 +762,10 @@ fn selection_release(
|
|||||||
manifest: &mut NotebookManifest,
|
manifest: &mut NotebookManifest,
|
||||||
autosave: &mut AutosaveController,
|
autosave: &mut AutosaveController,
|
||||||
selection_state: &mut SelectionState,
|
selection_state: &mut SelectionState,
|
||||||
|
canvas: &mut CanvasState,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
|
// Die letzte Bewegung gehört noch zum Ziehen
|
||||||
|
flush_selection_drag(notebook.current_page_mut(), canvas, selection_state);
|
||||||
match std::mem::take(&mut selection_state.drag_mode) {
|
match std::mem::take(&mut selection_state.drag_mode) {
|
||||||
DragMode::BoxSelect {
|
DragMode::BoxSelect {
|
||||||
start_page,
|
start_page,
|
||||||
@@ -1618,7 +1624,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
}
|
}
|
||||||
if is_stylus_selecting {
|
if is_stylus_selecting {
|
||||||
is_stylus_selecting = false;
|
is_stylus_selecting = false;
|
||||||
selection_release(false, &mut notebook, &mut manifest, &mut autosave, &mut selection_state);
|
selection_release(false, &mut notebook, &mut manifest, &mut autosave, &mut selection_state, &mut canvas);
|
||||||
window.request_redraw();
|
window.request_redraw();
|
||||||
}
|
}
|
||||||
if let Some(tool) = tool_before_button.take() {
|
if let Some(tool) = tool_before_button.take() {
|
||||||
@@ -1714,7 +1720,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
}
|
}
|
||||||
} else if is_stylus_selecting {
|
} else if is_stylus_selecting {
|
||||||
if let Some(page_pt) = canvas.screen_to_page(sx, sy) {
|
if let Some(page_pt) = canvas.screen_to_page(sx, sy) {
|
||||||
selection_drag(page_pt, notebook.current_page_mut(), &mut canvas, &mut selection_state);
|
selection_state.pending_drag = Some(page_pt);
|
||||||
window.request_redraw();
|
window.request_redraw();
|
||||||
}
|
}
|
||||||
} else if is_stylus_drawing && is_drawing {
|
} else if is_stylus_drawing && is_drawing {
|
||||||
@@ -1858,7 +1864,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
|
|
||||||
if is_stylus_selecting {
|
if is_stylus_selecting {
|
||||||
is_stylus_selecting = false;
|
is_stylus_selecting = false;
|
||||||
if selection_release(false, &mut notebook, &mut manifest, &mut autosave, &mut selection_state) {
|
if selection_release(false, &mut notebook, &mut manifest, &mut autosave, &mut selection_state, &mut canvas) {
|
||||||
window.request_redraw();
|
window.request_redraw();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2626,7 +2632,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
}
|
}
|
||||||
if ui_state.active_tool == ActiveTool::Select
|
if ui_state.active_tool == ActiveTool::Select
|
||||||
&& !is_stylus_selecting
|
&& !is_stylus_selecting
|
||||||
&& selection_release(modifiers.shift_key(), &mut notebook, &mut manifest, &mut autosave, &mut selection_state)
|
&& selection_release(modifiers.shift_key(), &mut notebook, &mut manifest, &mut autosave, &mut selection_state, &mut canvas)
|
||||||
{
|
{
|
||||||
window.request_redraw();
|
window.request_redraw();
|
||||||
}
|
}
|
||||||
@@ -2682,7 +2688,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
window.request_redraw();
|
window.request_redraw();
|
||||||
} else if ui_state.active_tool == ActiveTool::Select && selection_state.drag_mode != DragMode::None {
|
} else if ui_state.active_tool == ActiveTool::Select && selection_state.drag_mode != DragMode::None {
|
||||||
if let Some(page_pt) = canvas.screen_to_page(sx, sy) {
|
if let Some(page_pt) = canvas.screen_to_page(sx, sy) {
|
||||||
selection_drag(page_pt, notebook.current_page_mut(), &mut canvas, &mut selection_state);
|
selection_state.pending_drag = Some(page_pt);
|
||||||
window.request_redraw();
|
window.request_redraw();
|
||||||
}
|
}
|
||||||
} else if is_erasing {
|
} else if is_erasing {
|
||||||
@@ -2718,9 +2724,9 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
if is_pointer_in_ui {
|
if is_pointer_in_ui {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Durchgehende Ansicht: Mausrad scrollt (mit Umschalt waagerecht),
|
// Durchgehende Ansicht und endlose Fläche: Mausrad scrollt (mit Umschalt
|
||||||
// Strg + Mausrad zoomt
|
// waagerecht), Strg + Mausrad zoomt
|
||||||
if ui_state.continuous_scroll && !modifiers.control_key() {
|
if (ui_state.continuous_scroll || ui_state.endless) && !modifiers.control_key() {
|
||||||
let (mut dx, mut dy) = match delta {
|
let (mut dx, mut dy) = match delta {
|
||||||
MouseScrollDelta::LineDelta(x, y) => {
|
MouseScrollDelta::LineDelta(x, y) => {
|
||||||
let line = 60.0 * window.scale_factor() as f32;
|
let line = 60.0 * window.scale_factor() as f32;
|
||||||
@@ -2983,7 +2989,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
}
|
}
|
||||||
} else if ui_state.active_tool == ActiveTool::Select && selection_state.drag_mode != DragMode::None {
|
} else if ui_state.active_tool == ActiveTool::Select && selection_state.drag_mode != DragMode::None {
|
||||||
if let Some(page_pt) = canvas.screen_to_page(tx, ty) {
|
if let Some(page_pt) = canvas.screen_to_page(tx, ty) {
|
||||||
selection_drag(page_pt, notebook.current_page_mut(), &mut canvas, &mut selection_state);
|
selection_state.pending_drag = Some(page_pt);
|
||||||
window.request_redraw();
|
window.request_redraw();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3010,7 +3016,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ui_state.active_tool == ActiveTool::Select
|
if ui_state.active_tool == ActiveTool::Select
|
||||||
&& selection_release(false, &mut notebook, &mut manifest, &mut autosave, &mut selection_state)
|
&& selection_release(false, &mut notebook, &mut manifest, &mut autosave, &mut selection_state, &mut canvas)
|
||||||
{
|
{
|
||||||
window.request_redraw();
|
window.request_redraw();
|
||||||
}
|
}
|
||||||
@@ -3053,6 +3059,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
WindowEvent::RedrawRequested => {
|
WindowEvent::RedrawRequested => {
|
||||||
|
flush_selection_drag(notebook.current_page_mut(), &mut canvas, &mut selection_state);
|
||||||
// 0. Durchgehende Ansicht: Nach dem Scrollen oder Zoomen wird die Seite in
|
// 0. Durchgehende Ansicht: Nach dem Scrollen oder Zoomen wird die Seite in
|
||||||
// der Fenstermitte zur aktuellen (nicht während eines Strichs, einer
|
// der Fenstermitte zur aktuellen (nicht während eines Strichs, einer
|
||||||
// Auswahl oder beim Ziehen des Lineals). Wer auf einer Nachbarseite
|
// Auswahl oder beim Ziehen des Lineals). Wer auf einer Nachbarseite
|
||||||
|
|||||||
+14
-4
@@ -88,6 +88,9 @@ pub enum SelectionHandleHit {
|
|||||||
pub struct SelectionState {
|
pub struct SelectionState {
|
||||||
pub selected_ids: Vec<u64>,
|
pub selected_ids: Vec<u64>,
|
||||||
pub drag_mode: DragMode,
|
pub drag_mode: DragMode,
|
||||||
|
/// Letzte Zugposition, die noch nicht angewendet ist: Der Stift meldet sich öfter, als
|
||||||
|
/// gezeichnet wird; angewendet wird einmal je Bild (siehe `flush_selection_drag`).
|
||||||
|
pub pending_drag: Option<Vec2>,
|
||||||
pub clipboard: Vec<Stroke>,
|
pub clipboard: Vec<Stroke>,
|
||||||
/// Von Hand umgeschaltetes Seitenverhältnis-Schloss und die Auswahl (IDs), für die es
|
/// Von Hand umgeschaltetes Seitenverhältnis-Schloss und die Auswahl (IDs), für die es
|
||||||
/// gilt. Ändert sich die Auswahl, gilt wieder die Vorgabe (siehe [`Self::keeps_aspect`]).
|
/// gilt. Ändert sich die Auswahl, gilt wieder die Vorgabe (siehe [`Self::keeps_aspect`]).
|
||||||
@@ -99,6 +102,7 @@ impl SelectionState {
|
|||||||
Self {
|
Self {
|
||||||
selected_ids: Vec::new(),
|
selected_ids: Vec::new(),
|
||||||
drag_mode: DragMode::None,
|
drag_mode: DragMode::None,
|
||||||
|
pending_drag: None,
|
||||||
clipboard: Vec::new(),
|
clipboard: Vec::new(),
|
||||||
aspect_lock: None,
|
aspect_lock: None,
|
||||||
}
|
}
|
||||||
@@ -109,10 +113,11 @@ impl SelectionState {
|
|||||||
pub fn keeps_aspect(&self, page: &Page) -> bool {
|
pub fn keeps_aspect(&self, page: &Page) -> bool {
|
||||||
match &self.aspect_lock {
|
match &self.aspect_lock {
|
||||||
Some((ids, locked)) if *ids == self.selected_ids => *locked,
|
Some((ids, locked)) if *ids == self.selected_ids => *locked,
|
||||||
|
// Läuft bei jedem Bild: erst die (wenigen) Bilder suchen, ohne Striche zu kopieren
|
||||||
_ => page
|
_ => page
|
||||||
.get_strokes(&self.selected_ids)
|
.strokes
|
||||||
.iter()
|
.iter()
|
||||||
.any(|s| s.is_image()),
|
.any(|s| s.is_image() && self.selected_ids.contains(&s.id)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -159,8 +164,13 @@ impl SelectionState {
|
|||||||
|
|
||||||
/// Berechnet die gemeinsame AABB aller ausgewählten Objekte.
|
/// Berechnet die gemeinsame AABB aller ausgewählten Objekte.
|
||||||
pub fn get_selection_aabb(&self, page: &Page) -> Option<Aabb> {
|
pub fn get_selection_aabb(&self, page: &Page) -> Option<Aabb> {
|
||||||
let strokes = page.get_strokes(&self.selected_ids);
|
// Läuft bei jedem Bild: ohne die ausgewählten Striche zu kopieren
|
||||||
Page::selection_aabb(&strokes)
|
let ids: std::collections::HashSet<u64> = self.selected_ids.iter().copied().collect();
|
||||||
|
page.strokes
|
||||||
|
.iter()
|
||||||
|
.filter(|s| ids.contains(&s.id))
|
||||||
|
.map(|s| s.aabb)
|
||||||
|
.reduce(|a, b| a.union(&b))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Führt Hit-Testing auf die Auswahlgriffe (Drehen, Ecken, Körper) in Bildschirmkoordinaten
|
/// Führt Hit-Testing auf die Auswahlgriffe (Drehen, Ecken, Körper) in Bildschirmkoordinaten
|
||||||
|
|||||||
Reference in New Issue
Block a user