Compare commits
3
Commits
81ccd93438
...
v0.27.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8fddd9e2c3 | ||
|
|
1a98c9b8bb | ||
|
|
98b78d29e6 |
No files matched your search
Generated
+1
-1
@@ -1393,7 +1393,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ink-notes"
|
name = "ink-notes"
|
||||||
version = "0.26.2"
|
version = "0.27.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ab_glyph",
|
"ab_glyph",
|
||||||
"arboard",
|
"arboard",
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "ink-notes"
|
name = "ink-notes"
|
||||||
version = "0.26.2"
|
version = "0.27.0"
|
||||||
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.27.0" date="2026-10-18">
|
||||||
|
<description>
|
||||||
|
<p>Pictures keep their proportions when resized. A lock button in the selection tool switches between proportional and free resizing; pictures start locked, other objects free.</p>
|
||||||
|
</description>
|
||||||
|
</release>
|
||||||
<release version="0.26.2" date="2026-10-13">
|
<release version="0.26.2" date="2026-10-13">
|
||||||
<description>
|
<description>
|
||||||
<p>Smoother scrolling through large imported PDFs: pages no longer stall the display while they are prepared, and pages scrolled past quickly are skipped.</p>
|
<p>Smoother scrolling through large imported PDFs: pages no longer stall the display while they are prepared, and pages scrolled past quickly are skipped.</p>
|
||||||
|
|||||||
@@ -84,6 +84,11 @@
|
|||||||
</branding>
|
</branding>
|
||||||
<content_rating type="oars-1.1" />
|
<content_rating type="oars-1.1" />
|
||||||
<releases>
|
<releases>
|
||||||
|
<release version="0.27.0" date="2026-10-18">
|
||||||
|
<description>
|
||||||
|
<p>Pictures keep their proportions when resized. A lock button in the selection tool switches between proportional and free resizing; pictures start locked, other objects free.</p>
|
||||||
|
</description>
|
||||||
|
</release>
|
||||||
<release version="0.26.2" date="2026-10-13">
|
<release version="0.26.2" date="2026-10-13">
|
||||||
<description>
|
<description>
|
||||||
<p>Smoother scrolling through large imported PDFs: pages no longer stall the display while they are prepared, and pages scrolled past quickly are skipped.</p>
|
<p>Smoother scrolling through large imported PDFs: pages no longer stall the display while they are prepared, and pages scrolled past quickly are skipped.</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:
|
||||||
|
- **Bilder ohne Verzerren skalieren**: Beim Auswahlwerkzeug schaltet ein Schloss unter den Stiftfarben zwischen gleichmäßigem (🔒) und freiem Skalieren (🔓) um. Bilder sind zu Beginn gesperrt, Striche und Formen frei; am Rechner sperrt zusätzlich die Umschalttaste
|
||||||
- **Flüssigeres Scrollen in großen PDFs**: Neu gerenderte PDF-Seiten halten die Anzeige nicht mehr an, und Seiten, über die man schnell hinwegscrollt, werden übersprungen statt der Reihe nach gerendert
|
- **Flüssigeres Scrollen in großen PDFs**: Neu gerenderte PDF-Seiten halten die Anzeige nicht mehr an, und Seiten, über die man schnell hinwegscrollt, werden übersprungen statt der Reihe nach gerendert
|
||||||
- **Screenshots für Flathub**: Die App-Beschreibung (Metainfo) zeigt jetzt drei Screenshots; an der App selbst ändert sich nichts
|
- **Screenshots für Flathub**: Die App-Beschreibung (Metainfo) zeigt jetzt drei Screenshots; an der App selbst ändert sich nichts
|
||||||
- **Auswählen mit dem Finger**: Im Auswahlwerkzeug wählt und verschiebt ein Finger Objekte (auf leerer Fläche scrollt er weiter); Text und Platz schaffen gehen ebenfalls mit dem Finger
|
- **Auswählen mit dem Finger**: Im Auswahlwerkzeug wählt und verschiebt ein Finger Objekte (auf leerer Fläche scrollt er weiter); Text und Platz schaffen gehen ebenfalls mit dem Finger
|
||||||
|
|||||||
@@ -286,6 +286,8 @@ pub const EN: &[(&str, &str)] = &[
|
|||||||
("Seite {number}", "Page {number}"),
|
("Seite {number}", "Page {number}"),
|
||||||
("Seiten ({count})", "Pages ({count})"),
|
("Seiten ({count})", "Pages ({count})"),
|
||||||
("Seiten durchgehend scrollen", "Scroll pages continuously"),
|
("Seiten durchgehend scrollen", "Scroll pages continuously"),
|
||||||
|
("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}"),
|
("Serialisierungsfehler: {e}", "Serialization error: {e}"),
|
||||||
("Server-Einstellungen gespeichert", "Server settings saved"),
|
("Server-Einstellungen gespeichert", "Server settings saved"),
|
||||||
("Server:", "Server:"),
|
("Server:", "Server:"),
|
||||||
|
|||||||
+23
-17
@@ -31,7 +31,7 @@ use ink_notes::osk::{self, KeyboardRequester};
|
|||||||
use ink_notes::pdf_doc::PdfRenderService;
|
use ink_notes::pdf_doc::PdfRenderService;
|
||||||
use ink_notes::platform::{self, DisplayBackend};
|
use ink_notes::platform::{self, DisplayBackend};
|
||||||
use ink_notes::selection::{
|
use ink_notes::selection::{
|
||||||
render_selection_overlay, DragMode, SelectionHandleHit, SelectionState,
|
render_selection_overlay, scale_factors, DragMode, SelectionHandleHit, SelectionState,
|
||||||
};
|
};
|
||||||
use ink_notes::settings::{
|
use ink_notes::settings::{
|
||||||
SidebarSide, StylusAction, StylusButton, UiSettings, MAX_STYLUS_BUTTONS,
|
SidebarSide, StylusAction, StylusButton, UiSettings, MAX_STYLUS_BUTTONS,
|
||||||
@@ -649,12 +649,18 @@ fn selection_press(
|
|||||||
..
|
..
|
||||||
},
|
},
|
||||||
_,
|
_,
|
||||||
) => DragMode::Scale {
|
) => {
|
||||||
fixed_corner_page,
|
let initial_strokes = page.get_strokes(&selection_state.selected_ids);
|
||||||
start_pos_page,
|
// Schloss der Auswahl (Bilder: an, Issue #29); Shift sperrt zusätzlich
|
||||||
initial_box_size,
|
let keep_aspect = shift || selection_state.keeps_aspect(page);
|
||||||
initial_strokes: page.get_strokes(&selection_state.selected_ids),
|
DragMode::Scale {
|
||||||
},
|
fixed_corner_page,
|
||||||
|
start_pos_page,
|
||||||
|
initial_box_size,
|
||||||
|
initial_strokes,
|
||||||
|
keep_aspect,
|
||||||
|
}
|
||||||
|
}
|
||||||
(SelectionHandleHit::Body, Some(p)) => DragMode::Move {
|
(SelectionHandleHit::Body, Some(p)) => DragMode::Move {
|
||||||
start_page: p,
|
start_page: p,
|
||||||
initial_strokes: page.get_strokes(&selection_state.selected_ids),
|
initial_strokes: page.get_strokes(&selection_state.selected_ids),
|
||||||
@@ -729,18 +735,10 @@ fn selection_drag(
|
|||||||
DragMode::Scale {
|
DragMode::Scale {
|
||||||
fixed_corner_page,
|
fixed_corner_page,
|
||||||
start_pos_page,
|
start_pos_page,
|
||||||
|
keep_aspect,
|
||||||
..
|
..
|
||||||
} => {
|
} => {
|
||||||
let orig = *start_pos_page - *fixed_corner_page;
|
let factors = scale_factors(*fixed_corner_page, *start_pos_page, page_pt, *keep_aspect);
|
||||||
let new = page_pt - *fixed_corner_page;
|
|
||||||
let factor = |n: f32, o: f32| {
|
|
||||||
if o.abs() > 1.0 {
|
|
||||||
(n / o).clamp(0.05, 20.0)
|
|
||||||
} else {
|
|
||||||
1.0
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let factors = Vec2::new(factor(new.x, orig.x), factor(new.y, orig.y));
|
|
||||||
updated
|
updated
|
||||||
.iter_mut()
|
.iter_mut()
|
||||||
.for_each(|s| s.scale(*fixed_corner_page, factors));
|
.for_each(|s| s.scale(*fixed_corner_page, factors));
|
||||||
@@ -3103,6 +3101,8 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
// 3. Toolbar & Widgets rendern
|
// 3. Toolbar & Widgets rendern
|
||||||
updater.update();
|
updater.update();
|
||||||
ui_state.page_view = Some(page_view(&canvas, window.scale_factor() as f32));
|
ui_state.page_view = Some(page_view(&canvas, window.scale_factor() as f32));
|
||||||
|
ui_state.selection_aspect_lock = (!selection_state.is_empty())
|
||||||
|
.then(|| selection_state.keeps_aspect(notebook.current_page()));
|
||||||
ui_state.restorable_page = last_deleted_page
|
ui_state.restorable_page = last_deleted_page
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.filter(|(id, _, _)| *id == manifest.id)
|
.filter(|(id, _, _)| *id == manifest.id)
|
||||||
@@ -3203,6 +3203,12 @@ 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::ToggleAspectLock => {
|
||||||
|
if !selection_state.is_empty() {
|
||||||
|
selection_state.toggle_aspect_lock(notebook.current_page());
|
||||||
|
window.request_redraw();
|
||||||
|
}
|
||||||
|
}
|
||||||
UiAction::RotateSelected => {
|
UiAction::RotateSelected => {
|
||||||
if !selection_state.is_empty()
|
if !selection_state.is_empty()
|
||||||
&& selection_state.rotate_selected_90_deg(notebook.current_page_mut()) {
|
&& selection_state.rotate_selected_90_deg(notebook.current_page_mut()) {
|
||||||
|
|||||||
@@ -36,9 +36,34 @@ pub enum DragMode {
|
|||||||
start_pos_page: Vec2,
|
start_pos_page: Vec2,
|
||||||
initial_box_size: Vec2,
|
initial_box_size: Vec2,
|
||||||
initial_strokes: Vec<Stroke>,
|
initial_strokes: Vec<Stroke>,
|
||||||
|
/// Seitenverhältnis beibehalten (Schloss der Auswahl oder Shift gedrückt)
|
||||||
|
keep_aspect: bool,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Skalierungsfaktoren, wenn der Eckgriff von `start` (relativ zur festen Ecke `fixed`)
|
||||||
|
/// nach `current` gezogen wird. Mit `keep_aspect` wird gleichmäßig skaliert: der Faktor ist
|
||||||
|
/// die Projektion der Zugposition auf die Diagonale, damit das Seitenverhältnis bleibt.
|
||||||
|
pub fn scale_factors(fixed: Vec2, start: Vec2, current: Vec2, keep_aspect: bool) -> Vec2 {
|
||||||
|
let orig = start - fixed;
|
||||||
|
let new = current - fixed;
|
||||||
|
if keep_aspect {
|
||||||
|
let len_sq = orig.length_squared();
|
||||||
|
if len_sq <= 1.0 {
|
||||||
|
return Vec2::ONE;
|
||||||
|
}
|
||||||
|
return Vec2::splat((new.dot(orig) / len_sq).clamp(0.05, 20.0));
|
||||||
|
}
|
||||||
|
let factor = |n: f32, o: f32| {
|
||||||
|
if o.abs() > 1.0 {
|
||||||
|
(n / o).clamp(0.05, 20.0)
|
||||||
|
} else {
|
||||||
|
1.0
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Vec2::new(factor(new.x, orig.x), factor(new.y, orig.y))
|
||||||
|
}
|
||||||
|
|
||||||
/// Ergebnis eines Hit-Tests auf Auswahl-Griffe (Handles).
|
/// Ergebnis eines Hit-Tests auf Auswahl-Griffe (Handles).
|
||||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||||
pub enum SelectionHandleHit {
|
pub enum SelectionHandleHit {
|
||||||
@@ -64,6 +89,9 @@ pub struct SelectionState {
|
|||||||
pub selected_ids: Vec<u64>,
|
pub selected_ids: Vec<u64>,
|
||||||
pub drag_mode: DragMode,
|
pub drag_mode: DragMode,
|
||||||
pub clipboard: Vec<Stroke>,
|
pub clipboard: Vec<Stroke>,
|
||||||
|
/// 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`]).
|
||||||
|
aspect_lock: Option<(Vec<u64>, bool)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SelectionState {
|
impl SelectionState {
|
||||||
@@ -72,9 +100,28 @@ impl SelectionState {
|
|||||||
selected_ids: Vec::new(),
|
selected_ids: Vec::new(),
|
||||||
drag_mode: DragMode::None,
|
drag_mode: DragMode::None,
|
||||||
clipboard: Vec::new(),
|
clipboard: Vec::new(),
|
||||||
|
aspect_lock: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Ob Skalieren an den Eckgriffen das Seitenverhältnis der Auswahl beibehält. Vorgabe:
|
||||||
|
/// ja, sobald ein Bild ausgewählt ist (Issue #29); per Knopf für diese Auswahl umschaltbar.
|
||||||
|
pub fn keeps_aspect(&self, page: &Page) -> bool {
|
||||||
|
match &self.aspect_lock {
|
||||||
|
Some((ids, locked)) if *ids == self.selected_ids => *locked,
|
||||||
|
_ => page
|
||||||
|
.get_strokes(&self.selected_ids)
|
||||||
|
.iter()
|
||||||
|
.any(|s| s.is_image()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Schaltet das Seitenverhältnis-Schloss der aktuellen Auswahl um.
|
||||||
|
pub fn toggle_aspect_lock(&mut self, page: &Page) {
|
||||||
|
let locked = !self.keeps_aspect(page);
|
||||||
|
self.aspect_lock = Some((self.selected_ids.clone(), locked));
|
||||||
|
}
|
||||||
|
|
||||||
/// Prüft, ob aktuell Objekte ausgewählt sind.
|
/// Prüft, ob aktuell Objekte ausgewählt sind.
|
||||||
pub fn is_empty(&self) -> bool {
|
pub fn is_empty(&self) -> bool {
|
||||||
self.selected_ids.is_empty()
|
self.selected_ids.is_empty()
|
||||||
@@ -523,6 +570,77 @@ mod tests {
|
|||||||
assert!((orig_pos.x - restored_pos.x).abs() < 1e-4);
|
assert!((orig_pos.x - restored_pos.x).abs() < 1e-4);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_scale_factors_free_and_keep_aspect() {
|
||||||
|
let fixed = Vec2::new(0.0, 0.0);
|
||||||
|
let start = Vec2::new(200.0, 100.0);
|
||||||
|
// Frei: Breite und Höhe unabhängig
|
||||||
|
let free = scale_factors(fixed, start, Vec2::new(400.0, 100.0), false);
|
||||||
|
assert_eq!(free, Vec2::new(2.0, 1.0));
|
||||||
|
// Seitenverhältnis fest: gleicher Faktor in beiden Richtungen
|
||||||
|
let locked = scale_factors(fixed, start, Vec2::new(400.0, 100.0), true);
|
||||||
|
assert!((locked.x - locked.y).abs() < 1e-6);
|
||||||
|
assert!((locked.x - 1.8).abs() < 1e-4);
|
||||||
|
// Entlang der Diagonale genau verdoppelt
|
||||||
|
let diag = scale_factors(fixed, start, Vec2::new(400.0, 200.0), true);
|
||||||
|
assert!((diag - Vec2::splat(2.0)).length() < 1e-5);
|
||||||
|
// Über die feste Ecke hinaus: kein Spiegeln
|
||||||
|
let past = scale_factors(fixed, start, Vec2::new(-50.0, -50.0), true);
|
||||||
|
assert_eq!(past, Vec2::splat(0.05));
|
||||||
|
// Senkrechte Linie (Breite 0): frei bleibt x unverändert, fest skaliert beides
|
||||||
|
let line = Vec2::new(0.0, 100.0);
|
||||||
|
assert_eq!(
|
||||||
|
scale_factors(fixed, line, Vec2::new(30.0, 200.0), false),
|
||||||
|
Vec2::new(1.0, 2.0)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
scale_factors(fixed, line, Vec2::new(30.0, 200.0), true),
|
||||||
|
Vec2::splat(2.0)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_aspect_lock_defaults_to_images_and_toggles_per_selection() {
|
||||||
|
let mut page = Page::new(1, PageBackground::Blank);
|
||||||
|
let corners = [
|
||||||
|
Vec2::new(0.0, 0.0),
|
||||||
|
Vec2::new(100.0, 0.0),
|
||||||
|
Vec2::new(100.0, 50.0),
|
||||||
|
Vec2::new(0.0, 50.0),
|
||||||
|
];
|
||||||
|
page.add_stroke(Stroke::new_image(1, "a.png".into(), corners));
|
||||||
|
page.add_stroke(make_test_stroke(2, 200.0, 200.0, [0.1, 0.1, 0.1, 1.0]));
|
||||||
|
let mut sel = SelectionState::new();
|
||||||
|
|
||||||
|
// Vorgabe: Bilder gesperrt, Striche frei
|
||||||
|
sel.select_single(1);
|
||||||
|
assert!(sel.keeps_aspect(&page));
|
||||||
|
sel.select_single(2);
|
||||||
|
assert!(!sel.keeps_aspect(&page));
|
||||||
|
|
||||||
|
// Umschalten gilt für diese Auswahl
|
||||||
|
sel.toggle_aspect_lock(&page);
|
||||||
|
assert!(sel.keeps_aspect(&page));
|
||||||
|
sel.select_single(1);
|
||||||
|
sel.toggle_aspect_lock(&page);
|
||||||
|
assert!(!sel.keeps_aspect(&page));
|
||||||
|
sel.toggle_aspect_lock(&page);
|
||||||
|
assert!(sel.keeps_aspect(&page));
|
||||||
|
sel.toggle_aspect_lock(&page);
|
||||||
|
|
||||||
|
// Neue Auswahl: wieder die Vorgabe
|
||||||
|
sel.select_multiple(vec![1, 2]);
|
||||||
|
assert!(sel.keeps_aspect(&page));
|
||||||
|
sel.select_single(1);
|
||||||
|
assert!(
|
||||||
|
!sel.keeps_aspect(&page),
|
||||||
|
"gleiche Auswahl behält das Umschalten"
|
||||||
|
);
|
||||||
|
sel.clear();
|
||||||
|
sel.select_single(1);
|
||||||
|
assert!(!sel.keeps_aspect(&page));
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_recolor_ignores_images() {
|
fn test_recolor_ignores_images() {
|
||||||
let mut page = Page::new(1, PageBackground::Blank);
|
let mut page = Page::new(1, PageBackground::Blank);
|
||||||
|
|||||||
@@ -255,6 +255,8 @@ pub enum UiAction {
|
|||||||
/// Zwischenablage an dieser Stelle der aktuellen Seite einfügen (Kontextmenü)
|
/// Zwischenablage an dieser Stelle der aktuellen Seite einfügen (Kontextmenü)
|
||||||
PasteAt(glam::Vec2),
|
PasteAt(glam::Vec2),
|
||||||
RotateSelected,
|
RotateSelected,
|
||||||
|
/// Seitenverhältnis beim Skalieren der Auswahl sperren bzw. freigeben
|
||||||
|
ToggleAspectLock,
|
||||||
// Dokumenten- & Notizbuch-Verwaltung
|
// Dokumenten- & Notizbuch-Verwaltung
|
||||||
OpenNotebook(String),
|
OpenNotebook(String),
|
||||||
/// Neues Notizbuch im Ordner `folder` anlegen und öffnen
|
/// Neues Notizbuch im Ordner `folder` anlegen und öffnen
|
||||||
@@ -410,6 +412,8 @@ pub struct UiState {
|
|||||||
pub confirm_delete_page: bool,
|
pub confirm_delete_page: bool,
|
||||||
/// Seitenzahl der zuletzt gelöschten Seite, solange sie wiederhergestellt werden kann
|
/// Seitenzahl der zuletzt gelöschten Seite, solange sie wiederhergestellt werden kann
|
||||||
pub restorable_page: Option<usize>,
|
pub restorable_page: Option<usize>,
|
||||||
|
/// Seitenverhältnis-Schloss der Auswahl (`None`: nichts ausgewählt)
|
||||||
|
pub selection_aspect_lock: Option<bool>,
|
||||||
/// Fehler beim Flushen vor dem Beenden
|
/// Fehler beim Flushen vor dem Beenden
|
||||||
pub shutdown_error: Option<crate::autosave::AutosaveError>,
|
pub shutdown_error: Option<crate::autosave::AutosaveError>,
|
||||||
/// Fensterseite der Werkzeugleiste
|
/// Fensterseite der Werkzeugleiste
|
||||||
@@ -508,6 +512,7 @@ impl Default for UiState {
|
|||||||
pressed_stylus_button: None,
|
pressed_stylus_button: None,
|
||||||
confirm_delete_page: false,
|
confirm_delete_page: false,
|
||||||
restorable_page: None,
|
restorable_page: None,
|
||||||
|
selection_aspect_lock: None,
|
||||||
shutdown_error: None,
|
shutdown_error: None,
|
||||||
sidebar_side: SidebarSide::Left,
|
sidebar_side: SidebarSide::Left,
|
||||||
language: None,
|
language: None,
|
||||||
@@ -3045,6 +3050,30 @@ pub fn render_ui(
|
|||||||
used += btn + spacing;
|
used += btn + spacing;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Auswahl: unter den 4 Stiftfarben ist Platz für das Schloss des
|
||||||
|
// Seitenverhältnisses beim Skalieren (Bilder: gesperrt, Issue #29)
|
||||||
|
if state.active_tool == ActiveTool::Select {
|
||||||
|
let locked = state.selection_aspect_lock;
|
||||||
|
let (icon, tooltip) = if locked == Some(true) {
|
||||||
|
("🔒", t!("Seitenverhältnis gesperrt – tippen zum Freigeben"))
|
||||||
|
} else {
|
||||||
|
("🔓", t!("Seitenverhältnis frei – tippen zum Sperren"))
|
||||||
|
};
|
||||||
|
if sidebar_button(
|
||||||
|
ui,
|
||||||
|
swatch,
|
||||||
|
SidebarIcon::Glyph(icon),
|
||||||
|
locked == Some(true),
|
||||||
|
locked.is_some(),
|
||||||
|
None,
|
||||||
|
tooltip,
|
||||||
|
)
|
||||||
|
.clicked()
|
||||||
|
{
|
||||||
|
actions.push(UiAction::ToggleAspectLock);
|
||||||
|
}
|
||||||
|
used += swatch + spacing;
|
||||||
|
}
|
||||||
let reserved =
|
let reserved =
|
||||||
UiState::MAX_PALETTE_LEN as f32 * (swatch + spacing);
|
UiState::MAX_PALETTE_LEN as f32 * (swatch + spacing);
|
||||||
if reserved > used {
|
if reserved > used {
|
||||||
@@ -5189,6 +5218,35 @@ mod tests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_aspect_lock_button_only_with_selection() {
|
||||||
|
// Den Knopf in der Leiste des Auswahlwerkzeugs suchen: ohne Auswahl gesperrt
|
||||||
|
let notebook = Notebook::new();
|
||||||
|
let toggles = |lock: Option<bool>| {
|
||||||
|
let mut probe = UiState::new();
|
||||||
|
probe.active_tool = ActiveTool::Select;
|
||||||
|
render_frames(&mut probe, ¬ebook, 10);
|
||||||
|
let bar = probe.toolbar_rect.unwrap();
|
||||||
|
let mut found = 0;
|
||||||
|
let mut y = bar.min.y + 4.0;
|
||||||
|
while y < bar.max.y {
|
||||||
|
let mut state = UiState::new();
|
||||||
|
state.active_tool = ActiveTool::Select;
|
||||||
|
state.selection_aspect_lock = lock;
|
||||||
|
let ctx = render_frames(&mut state, ¬ebook, 10);
|
||||||
|
let actions = click_at(&ctx, &mut state, ¬ebook, egui::pos2(bar.center().x, y));
|
||||||
|
if actions.contains(&UiAction::ToggleAspectLock) {
|
||||||
|
found += 1;
|
||||||
|
}
|
||||||
|
y += 4.0;
|
||||||
|
}
|
||||||
|
found
|
||||||
|
};
|
||||||
|
assert_eq!(toggles(None), 0);
|
||||||
|
assert!(toggles(Some(true)) > 0);
|
||||||
|
assert!(toggles(Some(false)) > 0);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_sidebar_fits_window_height_for_every_tool() {
|
fn test_sidebar_fits_window_height_for_every_tool() {
|
||||||
let notebook = Notebook::new();
|
let notebook = Notebook::new();
|
||||||
|
|||||||
Reference in new issue
Block a user