use eframe::egui::{Pos2, Rect, Vec2};
#[derive(Clone, Copy)]
pub struct View {
pub zoom: f32,
pub pan: Vec2,
pub fitted: bool,
}
impl View {
pub fn new() -> Self {
Self { zoom: 1.0, pan: Vec2::ZERO, fitted: false }
}
pub fn to_screen(self, canvas: Rect, p: Pos2) -> Pos2 {
canvas.min + self.pan + p.to_vec2() * self.zoom
}
pub fn to_image(self, canvas: Rect, p: Pos2) -> Pos2 {
Pos2::ZERO + (p - canvas.min - self.pan) / self.zoom
}
pub fn rect_to_screen(&self, canvas: Rect, r: Rect) -> Rect {
Rect::from_min_max(self.to_screen(canvas, r.min), self.to_screen(canvas, r.max))
}
pub fn frame(&mut self, rect: Rect, canvas_size: Vec2) {
if rect.width() <= 0.0
|| rect.height() <= 0.0
|| canvas_size.x <= 0.0
|| canvas_size.y <= 0.0
{
return;
}
self.zoom = (canvas_size.x / rect.width())
.min(canvas_size.y / rect.height())
.clamp(0.02, 1.0);
self.pan = canvas_size * 0.5 - rect.center().to_vec2() * self.zoom;
self.fitted = true;
}
pub fn fit(&mut self, img: Vec2, canvas_size: Vec2) {
self.frame(Rect::from_min_size(Pos2::ZERO, img), canvas_size);
}
pub fn zoom_about(&mut self, canvas: Rect, pointer: Pos2, factor: f32) {
let new_zoom = (self.zoom * factor).clamp(0.02, 16.0);
let factor = new_zoom / self.zoom;
let rel = pointer - canvas.min - self.pan;
self.pan += rel * (1.0 - factor);
self.zoom = new_zoom;
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn screen_image_roundtrip() {
let mut v = View::new();
v.zoom = 0.5;
v.pan = Vec2::new(30.0, 40.0);
let canvas = Rect::from_min_max(Pos2::new(10.0, 20.0), Pos2::new(810.0, 620.0));
let p = Pos2::new(123.0, 456.0);
let back = v.to_image(canvas, v.to_screen(canvas, p));
assert!((back - p).length() < 1e-3);
}
#[test]
fn fit_centers_and_caps_at_one() {
let mut v = View::new();
v.fit(Vec2::new(100.0, 100.0), Vec2::new(1000.0, 500.0));
assert_eq!(v.zoom, 1.0); assert_eq!(v.pan, Vec2::new(450.0, 200.0));
v.fit(Vec2::new(2000.0, 1000.0), Vec2::new(1000.0, 500.0));
assert!((v.zoom - 0.5).abs() < 1e-6);
}
#[test]
fn frame_centers_a_sub_rect() {
let mut v = View::new();
let rect = Rect::from_min_max(Pos2::new(1920.0, 0.0), Pos2::new(3840.0, 1080.0));
v.frame(rect, Vec2::new(1920.0, 1080.0));
assert_eq!(v.zoom, 1.0);
let canvas = Rect::from_min_size(Pos2::ZERO, Vec2::new(1920.0, 1080.0));
assert_eq!(v.to_screen(canvas, rect.center()), Pos2::new(960.0, 540.0));
}
}