Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/facett

facett-graphpan/tests/focus_flight.rs

//! **📌 Stay / 🎯 Focus on the graphpan canvas** (the universe star-field and every
//! `draw_graph` board), rendered: real frames, a real click, the UI clock at 60 fps.

use egui::{pos2, Color32, Pos2, Rect, Vec2};
use facett_core::fly::NavMode;
use facett_graphpan::{draw_graph, Decorations, GraphEdge, GraphModel, GraphNode, GraphView, FOCUS_ZOOM};

const SCREEN: Vec2 = Vec2::new(1200.0, 800.0);

fn model() -> GraphModel {
    let n = |id: &str, x: f32, y: f32| GraphNode { id: id.into(), label: id.into(), fill: Color32::from_rgb(120, 200, 255), stroke: Color32::BLACK, pos: pos2(x, y) };
    GraphModel {
        nodes: vec![n("a", 0.0, 0.0), n("b", 900.0, 500.0), n("c", -400.0, 300.0)],
        edges: vec![GraphEdge { from: "a".into(), to: "b".into(), color: Color32::GRAY, dashed: false, label: None }],
    }
}

struct Rig {
    ctx: egui::Context,
    t: f64,
    canvas: Rect,
    view: GraphView,
}

impl Rig {
    fn new(mode: NavMode) -> Self {
        let ctx = egui::Context::default();
        ctx.set_pixels_per_point(1.0);
        mode.remember(&ctx);
        Self { ctx, t: 1.0, canvas: Rect::NOTHING, view: GraphView { zoom: 0.5, pan: Vec2::new(-100.0, -40.0), ..Default::default() } }
    }

    fn frame(&mut self, events: Vec<egui::Event>) {
        self.t += 1.0 / 60.0;
        let input = egui::RawInput { screen_rect: Some(Rect::from_min_size(Pos2::ZERO, SCREEN)), time: Some(self.t), events, ..Default::default() };
        let (m, d) = (model(), Decorations::default());
        let view = &mut self.view;
        let mut canvas = Rect::NOTHING;
        let mut out = self.ctx.run_ui(input, |ui| {
            egui::CentralPanel::default().show(ui, |ui| {
                canvas = ui.available_rect_before_wrap();
                let _ = draw_graph(ui, &m, &d, view, Color32::BLACK, Color32::WHITE, Color32::YELLOW, Color32::GRAY);
            });
        });
        out.textures_delta.clear();
        self.canvas = canvas;
    }

    fn screen_of(&self, id: &str) -> Pos2 {
        let p = model().nodes.into_iter().find(|n| n.id == id).unwrap().pos;
        self.view.project(self.canvas.center() + self.view.pan, p)
    }

    fn click(&mut self, p: Pos2) {
        self.frame(vec![egui::Event::PointerMoved(p)]);
        let b = |pressed| egui::Event::PointerButton { pos: p, button: egui::PointerButton::Primary, pressed, modifiers: Default::default() };
        self.frame(vec![b(true)]);
        self.frame(vec![b(false)]);
    }
}

/// 🎯 Focus: the clicked node flies to the canvas centre (±2 px) at the focus zoom.
#[test]
fn focus_flies_the_clicked_node_to_the_centre() {
    let mut rig = Rig::new(NavMode::Focus);
    rig.frame(Vec::new());
    let start = rig.screen_of("b");
    rig.click(start);
    let mut frames = 0;
    let mut prev = rig.screen_of("b");
    let total = (rig.canvas.center() - start).length();
    for _ in 0..120 {
        rig.frame(Vec::new());
        frames += 1;
        let p = rig.screen_of("b");
        assert!((p - prev).length() <= 0.25 * total, "a frame jumped {}", (p - prev).length());
        prev = p;
        if (p - rig.canvas.center()).length() < 0.01 && (rig.view.zoom - FOCUS_ZOOM).abs() < 1e-5 {
            break;
        }
    }
    assert!(frames >= 20, "a flight, not a jump: {frames}");
    assert!((rig.screen_of("b") - rig.canvas.center()).length() <= 2.0, "b at the centre: {:?} vs {:?}", rig.screen_of("b"), rig.canvas.center());
    assert!((rig.view.zoom - FOCUS_ZOOM).abs() < 1e-4, "at the focus zoom: {}", rig.view.zoom);
}

/// 📌 Stay: the click selects, the camera never moves.
#[test]
fn stay_a_click_never_moves_the_camera() {
    let mut rig = Rig::new(NavMode::Stay);
    rig.frame(Vec::new());
    let start = rig.screen_of("c");
    rig.click(start);
    for _ in 0..40 {
        rig.frame(Vec::new());
    }
    assert_eq!(rig.view.selected.as_deref(), Some("c"));
    assert_eq!((rig.view.zoom, rig.view.pan), (0.5, Vec2::new(-100.0, -40.0)));
    assert_eq!(rig.screen_of("c"), start);
}