mirror of
https://github.com/storytold/lightcraft.git
synced 2026-10-24 10:09:01 -06:00
LR-VIEW-ZOOM: a pinch holds the loupe's fit size, so it no longer renders at every zoom step
This commit is contained in:
@@ -575,6 +575,9 @@ pub fn show(app: &mut LightcraftApp, ui: &mut egui::Ui) {
|
||||
}
|
||||
// a pinch changes the zoom every frame: keep the sizes of before it until it is quiet
|
||||
let plan = app.size_hold.apply(id, now, navigating, plan);
|
||||
// the drawn size in pixels, clamped first: a NaN or negative float must not become a size
|
||||
let drawn_px = |points: f32| (points * ppp).round().clamp(0.0, 65_536.0) as usize;
|
||||
let fit_px = app.size_hold.display_fit(now, navigating, [drawn_px(target_rect.width()), drawn_px(target_rect.height())]);
|
||||
if app.size_hold.holding(now) {
|
||||
ui.ctx().request_repaint_after(std::time::Duration::from_secs_f64(crate::region::HOLD_SECS));
|
||||
}
|
||||
@@ -603,11 +606,7 @@ pub fn show(app: &mut LightcraftApp, ui: &mut egui::Ui) {
|
||||
// at rest the render is shrunk to exactly the size it is drawn at with a Lanczos filter on
|
||||
// the worker, so the window draws it texel for texel instead of shrinking it with 2-tap
|
||||
// sampling (the render is up to a 64 px step larger, or the preview level)
|
||||
let job = if interacting {
|
||||
job
|
||||
} else {
|
||||
job.with_display_fit((target_rect.width() * ppp).round() as usize, (target_rect.height() * ppp).round() as usize)
|
||||
};
|
||||
let job = if interacting { job } else { job.with_display_fit(fit_px[0], fit_px[1]) };
|
||||
let job = job.with_overlay(overlay).with_proof(app.ui.soft_proof.then_some(app.ui.proof));
|
||||
let job = job.with_hidden_grading(app.ui.grading_hidden_for(id));
|
||||
app.renderer.request(Slot::Main, job, 100);
|
||||
@@ -626,7 +625,7 @@ pub fn show(app: &mut LightcraftApp, ui: &mut egui::Ui) {
|
||||
let hover_key = match app.hover_preview.clone() {
|
||||
Some(h) if !interacting => app.session.preview_job(id, sw.max(8), sh.max(8), !crop_tool, &h.settings).map(|job| {
|
||||
// (the same display fit as the After view, drawn texel for texel like it)
|
||||
let job = job.with_display_fit((target_rect.width() * ppp).round() as usize, (target_rect.height() * ppp).round() as usize);
|
||||
let job = job.with_display_fit(fit_px[0], fit_px[1]);
|
||||
let key = job.key;
|
||||
app.renderer.request(Slot::Hover, job, 105);
|
||||
(key, h.label)
|
||||
@@ -666,11 +665,7 @@ pub fn show(app: &mut LightcraftApp, ui: &mut egui::Ui) {
|
||||
&& let Some(job) = app.session.render_job(id, sw.max(8), sh.max(8), true, !crop_tool)
|
||||
{
|
||||
// (the same display fit as the After view, so both panes are the same texture size)
|
||||
let job = if interacting {
|
||||
job
|
||||
} else {
|
||||
job.with_display_fit((target_rect.width() * ppp).round() as usize, (target_rect.height() * ppp).round() as usize)
|
||||
};
|
||||
let job = if interacting { job } else { job.with_display_fit(fit_px[0], fit_px[1]) };
|
||||
app.renderer.request(Slot::Before, job, 90);
|
||||
}
|
||||
// zoomed past what the whole-frame render holds: also render the part on screen, at no more
|
||||
|
||||
@@ -331,6 +331,7 @@ pub const HOLD_SECS: f64 = 0.25;
|
||||
pub struct SizeHold {
|
||||
photo: Option<lightcraft_catalog::PhotoId>,
|
||||
plan: Option<LoupePlan>,
|
||||
fit: Option<[usize; 2]>,
|
||||
until: f64,
|
||||
}
|
||||
|
||||
@@ -339,7 +340,7 @@ impl SizeHold {
|
||||
/// one is running (`gesturing`) and for [`HOLD_SECS`] after. `now` is in seconds.
|
||||
pub fn apply(&mut self, photo: lightcraft_catalog::PhotoId, now: f64, gesturing: bool, fresh: LoupePlan) -> LoupePlan {
|
||||
if self.photo != Some(photo) {
|
||||
*self = SizeHold { photo: Some(photo), plan: Some(fresh), until: f64::NEG_INFINITY };
|
||||
*self = SizeHold { photo: Some(photo), plan: Some(fresh), fit: None, until: f64::NEG_INFINITY };
|
||||
return fresh;
|
||||
}
|
||||
if gesturing {
|
||||
@@ -354,6 +355,18 @@ impl SizeHold {
|
||||
}
|
||||
}
|
||||
|
||||
/// The size the whole frame is shrunk to at rest (the drawn size): the one from before the
|
||||
/// gesture while sizes are held (call after [`Self::apply`] in the same frame), else `fresh`.
|
||||
pub fn display_fit(&mut self, now: f64, gesturing: bool, fresh: [usize; 2]) -> [usize; 2] {
|
||||
match self.fit {
|
||||
Some(held) if gesturing || now < self.until => held,
|
||||
_ => {
|
||||
self.fit = Some(fresh);
|
||||
fresh
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether sizes are being held at `now` (the caller asks for a repaint when it ends).
|
||||
pub fn holding(&self, now: f64) -> bool {
|
||||
now < self.until
|
||||
@@ -889,6 +902,34 @@ mod tests {
|
||||
assert_eq!(hold.apply(p, 1.4, false, zoomed), zoomed);
|
||||
}
|
||||
|
||||
// Given a pinch, the display-fit size holds like the render sizes and is retaken after it
|
||||
#[test]
|
||||
fn a_pinch_holds_the_display_fit_size() {
|
||||
use lightcraft_catalog::PhotoId;
|
||||
let (fit, zoomed) = (plan(&auto(), sizes(1400.0, 1400.0, 6000)), plan(&auto(), sizes(24000.0, 1400.0, 6000)));
|
||||
let mut hold = SizeHold::default();
|
||||
let p = PhotoId(1);
|
||||
hold.apply(p, 0.0, false, fit);
|
||||
assert_eq!(hold.display_fit(0.0, false, [1000, 800]), [1000, 800]);
|
||||
for k in 1..60 {
|
||||
let now = k as f64 / 60.0;
|
||||
hold.apply(p, now, true, zoomed);
|
||||
assert_eq!(hold.display_fit(now, true, [1000 + k, 800 + k]), [1000, 800], "frame {k}");
|
||||
}
|
||||
// stopped at t = 59/60: still held within the hold time…
|
||||
hold.apply(p, 1.1, false, zoomed);
|
||||
assert_eq!(hold.display_fit(1.1, false, [2560, 2048]), [1000, 800]);
|
||||
// …then the size it ended at is taken, and kept while nothing moves
|
||||
hold.apply(p, 1.3, false, zoomed);
|
||||
assert_eq!(hold.display_fit(1.3, false, [2560, 2048]), [2560, 2048]);
|
||||
assert_eq!(hold.display_fit(1.4, false, [2561, 2049]), [2561, 2049]);
|
||||
// another photo forgets the held size
|
||||
hold.apply(p, 1.5, true, zoomed);
|
||||
hold.display_fit(1.5, true, [2561, 2049]);
|
||||
hold.apply(PhotoId(2), 1.6, true, zoomed);
|
||||
assert_eq!(hold.display_fit(1.6, true, [640, 480]), [640, 480]);
|
||||
}
|
||||
|
||||
// Given another photo, the held sizes are forgotten
|
||||
#[test]
|
||||
fn another_photo_is_planned_afresh() {
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1,5 +1,11 @@
|
||||
# What's new in LightCraft
|
||||
|
||||
## 2026-10-21
|
||||
|
||||
### A pinch no longer re-renders the photo at every zoom step
|
||||
- Pinching the loupe now keeps the size the fit view is rendered at until the gesture is quiet, as it already did for
|
||||
the render sizes and windows. Before, each frame of a pinch asked for a new full-frame render at a new size.
|
||||
|
||||
## 2026-10-19
|
||||
|
||||
### Hidden Local locations come back one at a time
|
||||
|
||||
Reference in New Issue
Block a user