mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-08 22:28:09 +00:00
Merge pull request #6196 from KaboomFox/fix/opening-fit-settles
Canvas: a document opens at the zoom of the settled window
This commit is contained in:
@@ -89,6 +89,7 @@ impl VectorcraftApp {
|
||||
view_moved: None,
|
||||
hover_cursor: None,
|
||||
presented: None,
|
||||
opening_fit: None,
|
||||
panes: Default::default(),
|
||||
windows: crate::doc_windows::Windows::new(known),
|
||||
},
|
||||
|
||||
@@ -65,6 +65,10 @@ pub struct CanvasCache {
|
||||
/// In Presentation Mode, the canvas rect the active artboard was last fitted to: it is fitted
|
||||
/// again when the rect changes (the window going full screen).
|
||||
pub presented: Option<egui::Rect>,
|
||||
/// The canvas a document's opening view was fitted to, with the view the fit gave. The window's
|
||||
/// layout can still move the canvas's edges over the next frames, so until the view is moved or
|
||||
/// the user acts, the opening view is fitted again when the canvas changes ([`canvas::show`]).
|
||||
pub opening_fit: Option<canvas::ViewKey>,
|
||||
/// Window › Arrange › Tile: the renders the panes of the other documents draw.
|
||||
pub panes: canvas::Panes,
|
||||
/// Window › Arrange › Float in Window: what the floating document windows keep between frames.
|
||||
|
||||
@@ -186,6 +186,36 @@ fn view_rects(app: &VectorcraftApp, outer: egui::Rect, ppp: f32) -> (egui::Rect,
|
||||
(full, rect.round_to_pixels(ppp))
|
||||
}
|
||||
|
||||
/// Whether the user pressed a button or a key, typed, scrolled or zoomed in this frame: the
|
||||
/// opening fit stops following the canvas then ([`crate::caches::CanvasCache::opening_fit`]).
|
||||
fn acted(i: &egui::InputState) -> bool {
|
||||
i.pointer.any_pressed()
|
||||
|| i.events.iter().any(|e| {
|
||||
matches!(
|
||||
e,
|
||||
egui::Event::Key { pressed: true, .. }
|
||||
| egui::Event::Text(_)
|
||||
| egui::Event::MouseWheel { .. }
|
||||
| egui::Event::Zoom(_)
|
||||
| egui::Event::Touch { .. }
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
/// The view `v` shows in the canvas `rect`, as the opening fit records it.
|
||||
fn view_key(v: &View, rect: egui::Rect) -> ViewKey {
|
||||
ViewKey { rect, zoom: v.zoom, center: v.center, rotation: v.rotation }
|
||||
}
|
||||
|
||||
/// Whether the opening fit still follows the canvas `rect` (the view is unfitted, or unmoved since
|
||||
/// the fit and the user hasn't acted), and whether the canvas moved under it this frame, so the view
|
||||
/// is fitted again.
|
||||
fn opening_fit_follows(app: &VectorcraftApp, ui: &Ui, rect: egui::Rect, fitted: bool) -> (bool, bool) {
|
||||
let following = !fitted || (!ui.input(acted) && app.canvas.opening_fit.zip(app.view()).is_some_and(|(was, v)| view_key(v, was.rect) == was));
|
||||
let settling = fitted && following && app.canvas.opening_fit.is_some_and(|was| was.rect != rect);
|
||||
(following, settling)
|
||||
}
|
||||
|
||||
/// Paint the document canvas, complete or cached artwork, navigation and tool overlays.
|
||||
pub fn show(app: &mut VectorcraftApp, ui: &mut Ui) {
|
||||
let t = Tokens::get(ui.ctx());
|
||||
@@ -225,13 +255,19 @@ pub fn show(app: &mut VectorcraftApp, ui: &mut Ui) {
|
||||
let (full, rect) = view_rects(app, outer, ui.ctx().pixels_per_point());
|
||||
app.canvas_rect = Some(rect);
|
||||
let fitted = app.view().is_some_and(|v| v.fitted);
|
||||
// A document's opening view is fitted to the canvas of the first frame that shows it, and the
|
||||
// window's layout can still move the canvas's edges over the next frames. Until the view is
|
||||
// moved or the user acts, the opening fit follows the canvas, so that the document opens at the
|
||||
// zoom of the settled window.
|
||||
let (following, settling) = opening_fit_follows(app, ui, rect, fitted);
|
||||
// Presentation Mode shows the active artboard on the whole screen and nothing but the art:
|
||||
// no selection, guides, grids or artboard edges (#4365).
|
||||
let presenting = app.ui.screen_mode == 3;
|
||||
if !fitted || (presenting && app.canvas.presented != Some(rect)) {
|
||||
if !fitted || settling || (presenting && app.canvas.presented != Some(rect)) {
|
||||
fit(app, "view.fitArtboard");
|
||||
}
|
||||
app.canvas.presented = presenting.then_some(rect);
|
||||
app.canvas.opening_fit = following.then(|| app.view().map(|v| view_key(v, rect))).flatten();
|
||||
// Before the canvas's own interaction, so the view the canvas draws is the one they moved to.
|
||||
if bars {
|
||||
scroll_bars(app, ui, rect, outer, &t);
|
||||
|
||||
@@ -87,6 +87,60 @@ fn subpixel_dock_layout_does_not_change_pointer_document_coordinates() {
|
||||
assert_ne!(observed[0], observed[2], "a real device-pixel resize must still change the mapping");
|
||||
}
|
||||
|
||||
/// A document's opening view is fitted to the canvas of the first frame that shows it, and the
|
||||
/// window's layout can still move the canvas's edges over the next frames. Two starts of the app
|
||||
/// then showed one file at zooms a hair apart, on canvases alike once the window had settled. So
|
||||
/// until the view is moved or the user presses, types or scrolls, the opening fit follows the
|
||||
/// canvas.
|
||||
#[test]
|
||||
fn the_opening_fit_follows_the_canvas_until_the_view_moves_or_the_user_acts() {
|
||||
// A frame that shows the canvas `width` points wide, which limits the fit of the 800 × 600
|
||||
// artboard, with `events`: the view after it.
|
||||
fn frame(app: &mut VectorcraftApp, ctx: &egui::Context, width: f32, events: Vec<egui::Event>) -> View {
|
||||
let raw = egui::RawInput {
|
||||
screen_rect: Some(egui::Rect::from_min_size(Pos2::ZERO, vec2(1800.0, 900.0))),
|
||||
max_texture_side: Some(8192),
|
||||
events,
|
||||
..Default::default()
|
||||
};
|
||||
let mut out = ctx.run_ui(raw, |ui| {
|
||||
let area = egui::Rect::from_min_size(pos2(54.0, 72.0), vec2(width + SCROLL_BAR, 636.0 + SCROLL_BAR));
|
||||
let mut child = ui.new_child(egui::UiBuilder::new().max_rect(area));
|
||||
show(app, &mut child);
|
||||
});
|
||||
out.textures_delta.clear();
|
||||
*app.view().unwrap()
|
||||
}
|
||||
fn opened() -> (VectorcraftApp, egui::Context) {
|
||||
let mut app = VectorcraftApp::new(Session::new(), Default::default());
|
||||
app.session.execute("file.new", &json!({"width": 800, "height": 600, "created": 0})).unwrap();
|
||||
(app, egui::Context::default())
|
||||
}
|
||||
let first_shown_at = |width: f32| {
|
||||
let (mut app, ctx) = opened();
|
||||
frame(&mut app, &ctx, width, vec![]).zoom
|
||||
};
|
||||
let (narrow, wide) = (first_shown_at(700.0), first_shown_at(702.0));
|
||||
assert!(narrow < wide, "the canvas's width limits the fit: {narrow} and {wide}");
|
||||
let escape = || egui::Event::Key { key: egui::Key::Escape, physical_key: None, pressed: true, repeat: false, modifiers: egui::Modifiers::NONE };
|
||||
|
||||
// The first frame lays the canvas out 2 points narrower than the next: the document opens at
|
||||
// the zoom of the settled canvas, as it does when the first frame has it already.
|
||||
let (mut app, ctx) = opened();
|
||||
frame(&mut app, &ctx, 700.0, vec![]);
|
||||
assert_eq!(frame(&mut app, &ctx, 702.0, vec![]).zoom, wide);
|
||||
// Once the user has pressed a key, the canvas changing keeps the view, as resizing the window
|
||||
// does.
|
||||
assert_eq!(frame(&mut app, &ctx, 702.0, vec![escape()]).zoom, wide);
|
||||
assert_eq!(frame(&mut app, &ctx, 700.0, vec![]).zoom, wide);
|
||||
|
||||
// A view moved before the canvas changes stays where it was moved to.
|
||||
let (mut app, ctx) = opened();
|
||||
frame(&mut app, &ctx, 700.0, vec![]);
|
||||
app.view_mut().unwrap().zoom = 0.5;
|
||||
assert_eq!(frame(&mut app, &ctx, 702.0, vec![]).zoom, 0.5);
|
||||
}
|
||||
|
||||
/// A rendered raster becomes the image egui's own conversion makes of it, half-transparent
|
||||
/// edges and all; one whose bytes don't match its size becomes an empty image of that size.
|
||||
#[test]
|
||||
|
||||
Reference in New Issue
Block a user