feat: endless canvas grows only to the right and down (Issue #30)
The view stops 10 mm left of and above the start frame, or above and left of content that lies further out in notebooks from 0.29/0.30, so a new endless notebook starts at the top left like a sheet of paper. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
08a5b014e0
commit
630fe31b76
8 files changed
+96
-3
No files matched your search
@@ -843,6 +843,18 @@ impl CanvasState {
|
||||
changed
|
||||
}
|
||||
|
||||
/// Endless page: keeps the view right of and below `limit` (see
|
||||
/// [`crate::document::Page::endless_scroll_limit`]). `true` if the view was corrected.
|
||||
pub fn clamp_endless(&mut self, limit: glam::Vec2) -> bool {
|
||||
let changed = self
|
||||
.camera
|
||||
.clamp_top_left([self.ui_inset_left, 0.0], limit, &self.viewport);
|
||||
if changed {
|
||||
self.update_uniforms();
|
||||
}
|
||||
changed
|
||||
}
|
||||
|
||||
/// Durchgehende Ansicht: Scrollt senkrecht, bis die Fenstermitte auf Höhe `y`
|
||||
/// (Koordinaten der aktuellen Seite) liegt.
|
||||
pub fn scroll_center_to(&mut self, y: f32) {
|
||||
|
||||
@@ -833,6 +833,15 @@ impl Page {
|
||||
page
|
||||
}
|
||||
|
||||
/// Endless page: the view never shows anything left of or above this point – the start
|
||||
/// frame or the content, whichever reaches further, with a 10 mm margin. The canvas
|
||||
/// therefore only grows to the right and down (Issue #30).
|
||||
pub fn endless_scroll_limit(&self) -> glam::Vec2 {
|
||||
let top_left = Self::selection_aabb(&self.strokes)
|
||||
.map_or(ENDLESS_HOME, |content| content.min.min(ENDLESS_HOME));
|
||||
(top_left - ENDLESS_EXPORT_MARGIN).max(glam::Vec2::ZERO)
|
||||
}
|
||||
|
||||
/// Exportausschnitt einer endlosen Seite: der Inhalt mit 10 mm Rand (leer: der
|
||||
/// Startausschnitt). Die linke obere Ecke liegt auf dem Musterraster.
|
||||
pub fn endless_export_bounds(&self) -> (glam::Vec2, glam::Vec2) {
|
||||
@@ -2800,6 +2809,30 @@ mod tests {
|
||||
assert!(endless.screen_to_page(-5000.0, -5000.0).is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_endless_scroll_limit_includes_content_left_of_the_start() {
|
||||
let mut page = Page::new(1, PageBackground::Grid5mm).with_size(ENDLESS_PAGE_SIZE);
|
||||
assert_eq!(
|
||||
page.endless_scroll_limit(),
|
||||
ENDLESS_HOME - ENDLESS_EXPORT_MARGIN
|
||||
);
|
||||
// Content right of and below the start frame does not move the limit
|
||||
page.add_stroke(line(1, ENDLESS_HOME + 500.0, ENDLESS_HOME + 900.0));
|
||||
assert_eq!(
|
||||
page.endless_scroll_limit(),
|
||||
ENDLESS_HOME - ENDLESS_EXPORT_MARGIN
|
||||
);
|
||||
// Older notebooks may have content further up and left: it stays reachable
|
||||
let from = glam::Vec2::new(30_000.0, 40_000.0);
|
||||
page.add_stroke(line(2, from, ENDLESS_HOME));
|
||||
let limit = page.endless_scroll_limit();
|
||||
assert!(limit.cmple(from - ENDLESS_EXPORT_MARGIN).all(), "{limit:?}");
|
||||
assert!(
|
||||
limit.cmpgt(from - 2.0 * ENDLESS_EXPORT_MARGIN).all(),
|
||||
"{limit:?}"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_endless_export_bounds_follow_content() {
|
||||
let mut page = Page::new(1, PageBackground::Grid5mm).with_size(ENDLESS_PAGE_SIZE);
|
||||
|
||||
+5
-1
@@ -3156,7 +3156,11 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
// Auswahl oder beim Ziehen des Lineals). Wer auf einer Nachbarseite
|
||||
// schreibt, bleibt dort, bis wieder gescrollt wird (Undo betrifft sie).
|
||||
canvas.set_fit_box(notebook.fit_box());
|
||||
if ui_state.continuous_scroll {
|
||||
if notebook.is_endless() {
|
||||
if canvas.clamp_endless(notebook.current_page().endless_scroll_limit()) {
|
||||
fling.stop();
|
||||
}
|
||||
} else if ui_state.continuous_scroll {
|
||||
let stack = notebook.page_stack();
|
||||
if canvas.clamp_scroll(&stack) {
|
||||
// Am Anfang oder Ende des Notizbuchs angekommen
|
||||
|
||||
@@ -157,6 +157,24 @@ impl ViewportState {
|
||||
true
|
||||
}
|
||||
|
||||
/// Endless page: shifts the view so that the screen point `corner` (top left of the free
|
||||
/// area) shows nothing left of or above `limit`. `true` if the view was corrected.
|
||||
pub fn clamp_top_left(
|
||||
&mut self,
|
||||
corner: [f32; 2],
|
||||
limit: Vec2,
|
||||
page_viewport: &PageViewport,
|
||||
) -> bool {
|
||||
let c = self.screen_to_page_unclamped(corner[0], corner[1], page_viewport);
|
||||
let clamped = c.max(limit);
|
||||
if (clamped - c).length() < 1e-3 {
|
||||
return false;
|
||||
}
|
||||
self.pan_offset[0] += c.x - clamped.x;
|
||||
self.pan_offset[1] += c.y - clamped.y;
|
||||
true
|
||||
}
|
||||
|
||||
/// Passt den Pan-Offset um eine Bildschirmverschiebung (in Fensterpixeln) an.
|
||||
pub fn pan_by_screen_delta(
|
||||
&mut self,
|
||||
@@ -567,6 +585,28 @@ mod tests {
|
||||
assert!((c.x - A4.x).abs() < 0.1, "{c:?}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_endless_view_stays_right_of_and_below_the_limit() {
|
||||
use crate::document::{ENDLESS_HOME, ENDLESS_PAGE_SIZE};
|
||||
let page_vp = PageViewport::calculate_endless(1280.0, 850.0, 24.0, (0.0, 0.0));
|
||||
let limit = ENDLESS_HOME - 100.0;
|
||||
let mut vp = ViewportState::new();
|
||||
// The A4 start frame is centered: the wide window shows space left of it
|
||||
assert!(vp.clamp_top_left([0.0, 0.0], limit, &page_vp));
|
||||
let corner = vp.screen_to_page_unclamped(0.0, 0.0, &page_vp);
|
||||
assert!((corner.x - limit.x).abs() < 0.1, "{corner:?}");
|
||||
assert!(corner.y >= limit.y - 0.1, "{corner:?}");
|
||||
// Right and down stay free
|
||||
vp.pan_by_screen_delta(-5000.0, -5000.0, &page_vp);
|
||||
assert!(!vp.clamp_top_left([0.0, 0.0], limit, &page_vp));
|
||||
// Back up and left: stops at the limit
|
||||
vp.pan_by_screen_delta(20000.0, 20000.0, &page_vp);
|
||||
assert!(vp.clamp_top_left([0.0, 0.0], limit, &page_vp));
|
||||
let corner = vp.screen_to_page_unclamped(0.0, 0.0, &page_vp);
|
||||
assert!((corner - limit).length() < 0.1, "{corner:?}");
|
||||
assert!(corner.cmplt(ENDLESS_PAGE_SIZE).all());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_viewport_zoom_limits() {
|
||||
let mut vp = ViewportState::new();
|
||||
|
||||
Reference in new issue
Block a user