mirror of
https://github.com/storytold/lightcraft.git
synced 2026-10-25 02:30:15 -06:00
127 lines
5.9 KiB
Rust
127 lines
5.9 KiB
Rust
//! Real library/tool panel moves preserve the selected tool and saved arrangement.
|
|
use egui::{Event, Modifiers, PointerButton, vec2};
|
|
use egui_kittest::{Harness, kittest::Queryable};
|
|
use lightcraft_ui_egui::{LightcraftApp, Services, UiState, docking::Panel, state::RightPanel};
|
|
use serde_json::json;
|
|
use std::time::{Duration, Instant};
|
|
|
|
fn harness(state: Option<UiState>, width: f32, scale: f32) -> Harness<'static, LightcraftApp> {
|
|
let mut app = LightcraftApp::new(lightcraft_engine::Session::with_demo(), Services { png: None, ..Default::default() });
|
|
if let Some(state) = state {
|
|
app.ui = state;
|
|
} else {
|
|
app.ui.left_panel = true;
|
|
app.ui.right = RightPanel::Edit;
|
|
}
|
|
let mut h = Harness::builder().with_size(vec2(width, 900.0)).with_pixels_per_point(scale).build_ui_state(
|
|
|ui, app: &mut LightcraftApp| {
|
|
app.logic(&ui.ctx().clone());
|
|
app.ui(ui);
|
|
},
|
|
app,
|
|
);
|
|
h.run_steps(8);
|
|
h
|
|
}
|
|
|
|
fn drag(h: &mut Harness<'_, LightcraftApp>, from: egui::Pos2, to: egui::Pos2) {
|
|
h.event(Event::PointerMoved(from));
|
|
h.run_steps(2);
|
|
h.event(Event::PointerButton { pos: from, button: PointerButton::Primary, pressed: true, modifiers: Modifiers::NONE });
|
|
h.run_steps(2);
|
|
for step in 1..=8 {
|
|
h.event(Event::PointerMoved(from.lerp(to, step as f32 / 8.0)));
|
|
h.run_steps(1);
|
|
}
|
|
h.event(Event::PointerButton { pos: to, button: PointerButton::Primary, pressed: false, modifiers: Modifiers::NONE });
|
|
h.run_steps(4);
|
|
}
|
|
|
|
fn settle_photo(h: &mut Harness<'_, LightcraftApp>) {
|
|
let started = Instant::now();
|
|
let mut quiet = 0;
|
|
while quiet < 4 {
|
|
h.run_steps(2);
|
|
if h.state().renderer.in_flight() == 0 && h.state().loupe_shown.is_some_and(|(_, source)| source == "render") {
|
|
quiet += 1;
|
|
} else {
|
|
quiet = 0;
|
|
}
|
|
assert!(started.elapsed() < Duration::from_secs(30), "the synthetic photo and panel thumbnails must finish rendering before capture");
|
|
std::thread::sleep(Duration::from_millis(5));
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn real_drag_float_close_reopen_and_state_reload_keep_photo_tool_mode() {
|
|
let mut h = harness(None, 1400.0, 1.0);
|
|
let source = h.get_by_role_and_label(egui::accesskit::Role::Tab, "Tools").rect().center();
|
|
let target = h.get_by_role_and_label(egui::accesskit::Role::Tab, "Library").rect().center();
|
|
drag(&mut h, source, target);
|
|
assert_eq!(h.state().ui.docking.as_ref().unwrap().layout.location(&Panel::Tools).unwrap().anchor, Some(Panel::Library));
|
|
h.get_by_role_and_label(egui::accesskit::Role::Tab, "Tools").click_button(PointerButton::Secondary);
|
|
h.run_steps(3);
|
|
h.get_by_label("Float panel").click();
|
|
h.run_steps(5);
|
|
let rect = h.state().ui.docking.as_ref().unwrap().layout.floating.iter().find(|g| g.panels.contains(&Panel::Tools)).unwrap().rect;
|
|
h.get_by_role_and_label(egui::accesskit::Role::Tab, "Tools").click_button(PointerButton::Secondary);
|
|
h.run_steps(3);
|
|
h.get_by_label("Close panel").click();
|
|
h.run_steps(4);
|
|
assert_eq!(h.state().ui.right, RightPanel::None);
|
|
h.state_mut().ui.right = RightPanel::Crop;
|
|
h.run_steps(4);
|
|
assert_eq!(h.state().ui.docking.as_ref().unwrap().layout.floating.iter().find(|g| g.panels.contains(&Panel::Tools)).unwrap().rect, rect);
|
|
let saved = serde_json::to_string(&h.state().ui).unwrap();
|
|
let expected = serde_json::to_value(&h.state().ui.docking).unwrap();
|
|
let mut reloaded = harness(Some(serde_json::from_str(&saved).unwrap()), 1400.0, 1.0);
|
|
assert_eq!(serde_json::to_value(&reloaded.state().ui.docking).unwrap(), expected);
|
|
assert_eq!(reloaded.state().ui.right, RightPanel::Crop);
|
|
reloaded.state_mut().run("ui.dock", json!({"operation":"move","panel":"tools","target":"library","zone":"center"})).unwrap();
|
|
reloaded.run_steps(4);
|
|
assert_eq!(reloaded.state().ui.right, RightPanel::Crop);
|
|
reloaded.state_mut().run("ui.dock", json!({"operation":"close","panel":"library"})).unwrap();
|
|
reloaded.run_steps(3);
|
|
assert!(!reloaded.state().ui.left_panel);
|
|
assert_eq!(reloaded.state().ui.right, RightPanel::Crop);
|
|
}
|
|
|
|
#[test]
|
|
fn invalid_docking_requests_preserve_layout_and_selected_tool() {
|
|
let mut h = harness(None, 1400.0, 1.0);
|
|
let before = serde_json::to_value(&h.state().ui.docking).unwrap();
|
|
for params in [
|
|
json!({"operation":"close","panel":"canvas"}),
|
|
json!({"operation":"move","panel":"tools","target":"canvas","zone":"center"}),
|
|
json!({"operation":"float","panel":"tools","rect":[0,0,-1,100]}),
|
|
json!({"operation":"move","panel":"unknown","target":"library","zone":"center"}),
|
|
] {
|
|
assert!(h.state_mut().run("ui.dock", params).is_err());
|
|
assert_eq!(serde_json::to_value(&h.state().ui.docking).unwrap(), before);
|
|
assert_eq!(h.state().ui.right, RightPanel::Edit);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn docking_renders_real_photo_panels_default_floating_and_redocked() {
|
|
for width in [900.0, 1400.0] {
|
|
for scale in [1.0, 2.0] {
|
|
let mut h = harness(None, width, scale);
|
|
for state in ["default", "floating", "redocked"] {
|
|
if state == "floating" {
|
|
h.state_mut().run("ui.dock", json!({"operation":"float","panel":"tools","rect":[120,90,320,600]})).unwrap();
|
|
} else if state == "redocked" {
|
|
h.state_mut().run("ui.dock", json!({"operation":"move","panel":"tools","target":"library","zone":"center"})).unwrap();
|
|
}
|
|
h.run_steps(8);
|
|
settle_photo(&mut h);
|
|
h.state().ui.docking.as_ref().unwrap().validate().unwrap();
|
|
if let Ok(directory) = std::env::var("CRAFT_UI_DOCKING_DIR") {
|
|
std::fs::create_dir_all(&directory).unwrap();
|
|
h.render().unwrap().save(format!("{directory}/light-{width}-{scale}-{state}.png")).unwrap();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|