Free Transform: W/H fields reach 1000000% and X/Y the 300000 px document limit (#1267)

The options bar clamped W/H to ±10000% and X/Y to ±30000 px, while the engine takes any scale
and documents go up to 300000 px a side. Overlays on large stitched mosaics need more. The limits
are now named constants: ±1000000% (a 300 px layer can span the largest document) and ±300000 px.
This commit is contained in:
Marcelo Cárdenas authored and BFlat committed 2026-10-09 01:58:43 +07:00
1 parent 3bab0d4f52
commit bb723b12eb
1 file changed
+49 -4
+49 -4
View File
@@ -1305,6 +1305,12 @@ fn readout(t: &TransformSession) -> (f64, f64, f64, f64) {
/// Width of the mode, cancel and commit cluster kept on the right of the options bar.
const ACTIONS_W: f32 = 140.0;
/// Reach of the reference point X/Y fields: the largest document side (`image.canvasSize`'s limit).
const POSITION_LIMIT_PX: f32 = 300_000.0;
/// Reach of the W/H fields. The engine takes any scale; this lets a 300 px layer span the largest
/// document (#1267).
const SCALE_LIMIT_PCT: f32 = 1_000_000.0;
/// Options bar while transforming: reference point X/Y, W/H %, angle, interpolation, and, pinned
/// to the right, the warp switch, cancel and commit.
pub fn options_bar(app: &mut PhotocraftApp, ui: &mut egui::Ui) {
@@ -1336,9 +1342,9 @@ fn transform_fields(app: &mut PhotocraftApp, ui: &mut egui::Ui, t: &TransformSes
let mut px = t.pivot[0] as f32;
let mut py = t.pivot[1] as f32;
lbl(ui, "X:");
let rx = crate::widgets::value_field(ui, &mut px, -30000.0..=30000.0, "px", 72.0);
let rx = crate::widgets::value_field(ui, &mut px, -POSITION_LIMIT_PX..=POSITION_LIMIT_PX, "px", 72.0);
lbl(ui, "Y:");
let ry = crate::widgets::value_field(ui, &mut py, -30000.0..=30000.0, "px", 72.0);
let ry = crate::widgets::value_field(ui, &mut py, -POSITION_LIMIT_PX..=POSITION_LIMIT_PX, "px", 72.0);
if (rx.changed() || ry.changed())
&& let Some(s) = app.ui.transform.as_mut()
{
@@ -1349,7 +1355,7 @@ fn transform_fields(app: &mut PhotocraftApp, ui: &mut egui::Ui, t: &TransformSes
crate::widgets::vline(ui, 22.0);
let (mut w, mut h) = (sx as f32, sy as f32);
lbl(ui, "W:");
let rw = crate::widgets::value_field(ui, &mut w, -10000.0..=10000.0, "%", 66.0);
let rw = crate::widgets::value_field(ui, &mut w, -SCALE_LIMIT_PCT..=SCALE_LIMIT_PCT, "%", 66.0);
let link_id = egui::Id::new("transform-link");
let mut link: bool = ui.data(|d| d.get_temp(link_id)).unwrap_or(true);
if crate::icons::button(ui, if link { "link" } else { "unlink" }, 22.0, link, tl!("Maintain aspect ratio")).clicked() {
@@ -1357,7 +1363,7 @@ fn transform_fields(app: &mut PhotocraftApp, ui: &mut egui::Ui, t: &TransformSes
ui.data_mut(|d| d.insert_temp(link_id, link));
}
lbl(ui, "H:");
let rh = crate::widgets::value_field(ui, &mut h, -10000.0..=10000.0, "%", 66.0);
let rh = crate::widgets::value_field(ui, &mut h, -SCALE_LIMIT_PCT..=SCALE_LIMIT_PCT, "%", 66.0);
if rw.changed() || rh.changed() {
let (kx, ky) = if link {
let k = if rw.changed() { w as f64 / sx.max(1e-9) } else { h as f64 / sy.max(1e-9) };
@@ -1631,6 +1637,45 @@ mod tests {
assert!(h.state().ui.transform.is_none());
}
/// Types `text` + Enter into the `n`th numeric field of the transform bar (X, Y, W, H, angle…).
fn type_into_bar_field(n: usize, text: &str) -> TransformSession {
use egui_kittest::Harness;
use egui_kittest::kittest::Queryable;
let mut app = PhotocraftApp::new(photocraft_engine::Session::new(), crate::Services::default());
app.ui.transform = Some(session());
let mut h = Harness::builder().with_size(vec2(900.0, 48.0)).build_ui_state(
|ui, app: &mut PhotocraftApp| {
if !ui.ctx().fonts(|f| f.families().contains(&egui::FontFamily::Name("medium".into()))) {
return;
}
ui.horizontal_centered(|ui| options_bar(app, ui));
},
app,
);
PhotocraftApp::setup_context(&h.ctx, crate::theme::ThemeKind::Studio);
h.run_steps(6);
h.get_all_by_role(egui::accesskit::Role::SpinButton).nth(n).unwrap().click();
h.run();
for c in text.chars() {
h.event(egui::Event::Text(c.to_string()));
h.run();
}
h.key_press(egui::Key::Enter);
h.run_steps(2);
h.state().ui.transform.clone().unwrap()
}
/// Large documents need scales past 10000% and positions past 30000 px (#1267).
#[test]
fn transform_fields_reach_large_documents() {
let t = type_into_bar_field(2, "15000");
let (sx, sy, _, _) = readout(&t);
assert!((sx - 15000.0).abs() < 0.5 && (sy - 15000.0).abs() < 0.5, "{sx} {sy}");
assert!((t.quad[1][0] - t.quad[0][0] - 15_000.0).abs() < 1.0, "{:?}", t.quad);
let t = type_into_bar_field(0, "250000");
assert!((t.pivot[0] - 250_000.0).abs() < 1.0, "{:?}", t.pivot);
}
fn drag(s: &mut TransformSession, from: [f64; 2], to: [f64; 2], mods: egui::Modifiers) {
let g = Gesture { hit: hit(s, from, 8.0), start: from, quad0: s.quad, pivot0: s.pivot };
apply_drag(s, g, to, mods);