mirror of
https://github.com/storytold/photocraft.git
synced 2026-10-30 04:47:09 -06:00
* Keep the Color panel selected when switching themes (#3371) * Scope Camera Raw color checks to its settings panel (#3371) --------- Co-authored-by: prekabreki <prekabreki@users.noreply.github.com>
136 lines
7.9 KiB
Rust
136 lines
7.9 KiB
Rust
//! Camera Raw through the real app shell and its existing agent entry point.
|
|
use egui::{Event, Modifiers, MouseWheelUnit, accesskit::Role, pos2, vec2};
|
|
use egui_kittest::{
|
|
Harness,
|
|
kittest::{NodeT, Queryable},
|
|
};
|
|
use photocraft_ui_egui::{PhotocraftApp, camera_raw_ui, control, theme::ThemeKind};
|
|
use serde_json::{Value, json};
|
|
|
|
fn invoke(h: &mut Harness<'_, PhotocraftApp>, params: Value) -> Value {
|
|
let ctx = h.ctx.clone();
|
|
let (request, _) = control::ControlRequest::new("ui.menu.invoke", json!({"id": "filter.cameraRaw", "params": params}));
|
|
let outcome = control::handle(h.state_mut(), &ctx, &request);
|
|
assert!(matches!(outcome, control::Outcome::Done(_)), "dialog control failed");
|
|
control::inspect(h.state(), &ctx)["cameraRaw"].clone()
|
|
}
|
|
|
|
#[test]
|
|
fn header_stays_fixed_and_resize_hover_scroll_before_do_not_rebuild_the_proxy() {
|
|
for theme in ThemeKind::ALL {
|
|
let mut h = Harness::builder().with_size(vec2(1200.0, 800.0)).build_eframe(move |cc| {
|
|
PhotocraftApp::setup_context(&cc.egui_ctx, theme);
|
|
let mut app = PhotocraftApp::new(photocraft_engine::Session::new(), Default::default());
|
|
app.set_theme(&cc.egui_ctx, theme);
|
|
app.run("file.new", json!({"width": 64, "height": 48})).unwrap();
|
|
app.run("layer.new.layer", json!({})).unwrap();
|
|
app.run("edit.fill", json!({"color": "#708090"})).unwrap();
|
|
app
|
|
});
|
|
h.run_steps(2);
|
|
let original = invoke(&mut h, json!({}));
|
|
h.run_steps(3);
|
|
let label = photocraft_ui_egui::i18n::tr(photocraft_ui_egui::i18n::current(), "Tone Histogram");
|
|
let header = h.query_by_label(label).expect("histogram graph").rect();
|
|
h.event(Event::PointerMoved(pos2(1050.0, 400.0)));
|
|
h.event(Event::MouseWheel { unit: MouseWheelUnit::Point, phase: egui::TouchPhase::Move, delta: vec2(0.0, -320.0), modifiers: Modifiers::NONE });
|
|
h.run_steps(3);
|
|
assert_eq!(h.query_by_label(label).expect("histogram graph").rect(), header, "{theme:?}: histogram scrolled");
|
|
h.set_size(vec2(960.0, 600.0));
|
|
h.set_pixels_per_point(2.0);
|
|
h.run_steps(3);
|
|
let before = invoke(&mut h, json!({"ui": {"before": true}}));
|
|
h.run_steps(3);
|
|
let inspected = control::inspect(h.state(), &h.ctx);
|
|
assert_eq!(before["previewRevision"], original["previewRevision"]);
|
|
assert_eq!(inspected["cameraRaw"]["previewRevision"], original["previewRevision"]);
|
|
assert_eq!(before["histogram"]["red"], original["histogram"]["red"]);
|
|
assert!(h.query_by_label(label).expect("histogram graph").rect().bottom() < 600.0);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn histogram_matches_visible_preview_at_all_supported_depths_and_cancel_commit_are_reversible() {
|
|
for mode in ["rgb", "grayscale"] {
|
|
for depth in [8, 16, 32] {
|
|
let ctx = egui::Context::default();
|
|
let mut app = PhotocraftApp::new(photocraft_engine::Session::new(), Default::default());
|
|
app.run("file.new", json!({"width": 16, "height": 8, "mode": mode, "depth": depth})).unwrap();
|
|
app.run("layer.new.layer", json!({})).unwrap();
|
|
app.run("edit.fill", json!({"color": "#808080"})).unwrap();
|
|
let original = app.session.active().unwrap().doc.clone();
|
|
let steps = app.session.active().unwrap().history.past_len();
|
|
let initial = camera_raw_ui::menu(&mut app, &ctx, "filter.cameraRaw", &json!({})).unwrap().unwrap();
|
|
assert_eq!(initial["histogram"]["samples"], 128);
|
|
let changed = camera_raw_ui::menu(&mut app, &ctx, "filter.cameraRaw", &json!({"ui": {"set": {"exposure": 1.0}}})).unwrap().unwrap();
|
|
assert_ne!(changed["histogram"]["red"], initial["histogram"]["red"]);
|
|
assert_eq!(app.session.active().unwrap().history.past_len(), steps);
|
|
camera_raw_ui::menu(&mut app, &ctx, "filter.cameraRaw", &json!({"ui": {"cancel": true}})).unwrap().unwrap();
|
|
assert!(std::sync::Arc::ptr_eq(&app.session.active().unwrap().doc, &original));
|
|
camera_raw_ui::menu(&mut app, &ctx, "filter.cameraRaw", &json!({"ui": {"set": {"exposure": 1.0}, "commit": true}})).unwrap().unwrap();
|
|
assert_eq!(app.session.active().unwrap().history.past_len(), steps + 1);
|
|
app.run("edit.undo", json!({})).unwrap();
|
|
assert!(std::sync::Arc::ptr_eq(&app.session.active().unwrap().doc, &original));
|
|
app.run("edit.redo", json!({})).unwrap();
|
|
let committed = camera_raw_ui::menu(&mut app, &ctx, "filter.cameraRaw", &json!({})).unwrap().unwrap();
|
|
assert_eq!(committed["histogram"]["red"], changed["histogram"]["red"], "{mode}/{depth}: graph and committed pixels disagree");
|
|
}
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn settings_sections_group_existing_controls_without_rendering_or_document_edits() {
|
|
assert_eq!(photocraft_ui_egui::i18n::tr_ctx(photocraft_ui_egui::i18n::Lang::from_code("ru").unwrap(), "cameraRaw", "Light"), "Свет");
|
|
for theme in ThemeKind::ALL {
|
|
let mut h = Harness::builder().with_step_dt(1.0 / 60.0).with_size(vec2(1200.0, 900.0)).build_eframe(move |cc| {
|
|
PhotocraftApp::setup_context(&cc.egui_ctx, theme);
|
|
let mut app = PhotocraftApp::new(photocraft_engine::Session::new(), Default::default());
|
|
app.set_theme(&cc.egui_ctx, theme);
|
|
app.run("file.new", json!({"width":64,"height":48})).unwrap();
|
|
app.run("layer.new.layer", json!({})).unwrap();
|
|
app.run("edit.fill", json!({"color":"#708090"})).unwrap();
|
|
app
|
|
});
|
|
let before = invoke(&mut h, json!({}));
|
|
let doc = h.state().session.active().unwrap().doc.clone();
|
|
h.run_steps(4);
|
|
let lang = photocraft_ui_egui::i18n::current();
|
|
let light = photocraft_ui_egui::i18n::tr_ctx(lang, "cameraRaw", "Light");
|
|
// The dialog translates through the `cameraRaw` context; match it in any system language.
|
|
let tr = |s| photocraft_ui_egui::i18n::tr_ctx(lang, "cameraRaw", s);
|
|
let mut y = 0.0;
|
|
for label in [light, tr("Color"), tr("Effects"), tr("Curve"), tr("Color Mixer"), tr("Color Grading"), tr("Detail")] {
|
|
let next = h.get_by_role_and_label(Role::Button, label).rect().top();
|
|
assert!(next > y, "section order: {label}");
|
|
y = next;
|
|
}
|
|
assert!(h.query_by_label(tr("Basic")).is_none());
|
|
assert!(h.query_by_label(tr("Temperature")).is_none());
|
|
assert!(h.query_by_label(tr("Texture")).is_none());
|
|
assert!(h.query_by_label(tr("Exposure")).is_some());
|
|
h.get_by_role_and_label(Role::Button, light).click();
|
|
h.run_steps(12);
|
|
h.get_by_role_and_label(Role::Button, tr("Color")).click();
|
|
h.run_steps(12);
|
|
// The dock also has Saturation; query only the Camera Raw settings panel.
|
|
let settings = std::iter::successors(Some(h.get_by_role_and_label(Role::Button, tr("Color"))), |node| node.parent())
|
|
.find(|node| node.query_by_label(tr("Tone Histogram")).is_some())
|
|
.expect("Camera Raw settings panel");
|
|
for label in ["Temperature", "Tint", "Vibrance", "Saturation"] {
|
|
assert!(settings.query_by_label(tr(label)).is_some(), "color control: {label}");
|
|
}
|
|
assert!(h.query_by_label(tr("Exposure")).is_none());
|
|
h.get_by_role_and_label(Role::Button, tr("Color")).click();
|
|
h.run_steps(12);
|
|
h.get_by_role_and_label(Role::Button, tr("Effects")).click();
|
|
h.run_steps(12);
|
|
for label in ["Texture", "Clarity", "Dehaze", "Vignetting", "Grain"] {
|
|
assert!(h.query_by_label(tr(label)).is_some(), "effects control: {label}");
|
|
}
|
|
let after = control::inspect(h.state(), &h.ctx);
|
|
assert_eq!(after["cameraRaw"]["params"], before["params"]);
|
|
assert_eq!(after["cameraRaw"]["previewRevision"], before["previewRevision"]);
|
|
assert!(std::sync::Arc::ptr_eq(&doc, &h.state().session.active().unwrap().doc));
|
|
}
|
|
}
|