mirror of
https://github.com/storytold/photocraft.git
synced 2026-11-12 21:23:02 +00:00
Merge pull request #11252 from NaufalDzz/fix-transform-several-selection-rules
fix(transform): refuse the Background among several layers and apply the box on a selection change
This commit is contained in:
40 files changed
+513
-69
No files matched your search
@@ -272,9 +272,9 @@ pub fn locked_for_transform(doc: &Document, id: LayerId) -> bool {
|
||||
}
|
||||
|
||||
/// The layers Free Transform moves with the Layers panel's selection, bottom-to-top: with several
|
||||
/// layers selected, every one that no selected group already carries, as in Photoshop (#6501), but
|
||||
/// the Background, which Show Transform Controls doesn't frame either. With one layer selected (or
|
||||
/// only the Background besides it), that layer; the active layer when nothing else is.
|
||||
/// layers selected, every one that no selected group already carries, as in Photoshop (#6501). The
|
||||
/// Background stays among them, so such a box refuses as locked (#11101, [`background_among`]).
|
||||
/// With one layer selected, that layer; the active layer when nothing else is.
|
||||
pub fn selection_targets(st: &crate::DocState) -> Vec<LayerId> {
|
||||
targets_of(&st.doc, &st.selected_layers(), st.active_layer)
|
||||
}
|
||||
@@ -285,14 +285,23 @@ pub(crate) fn targets_of(doc: &Document, selected: &[LayerId], active: Option<La
|
||||
if selected.len() < 2 {
|
||||
return active.into_iter().collect();
|
||||
}
|
||||
let ids: Vec<LayerId> = crate::layer_multi_cmds::top_level(doc, selected).into_iter().filter(|id| !doc.is_background(*id)).collect();
|
||||
let ids = crate::layer_multi_cmds::top_level(doc, selected);
|
||||
if ids.is_empty() { active.into_iter().collect() } else { ids }
|
||||
}
|
||||
|
||||
/// The Background when it is among several layers one box would move: Photoshop refuses that Free
|
||||
/// Transform with its locked-layer alert rather than leaving the Background out (#11101).
|
||||
pub fn background_among(doc: &Document, ids: &[LayerId]) -> Option<LayerId> {
|
||||
if ids.len() < 2 {
|
||||
return None;
|
||||
}
|
||||
ids.iter().copied().find(|id| doc.is_background(*id))
|
||||
}
|
||||
|
||||
/// The layers one box over `targets` moves: `targets` with the layers linked to them, as the Move
|
||||
/// tool takes them along. In Photoshop transforming one linked layer transforms the whole link
|
||||
/// group (#11217). A linked Background joins only when it is a target itself: `targets_of`
|
||||
/// leaves it out of a box over other layers, and a link doesn't bring it back in.
|
||||
/// group (#11217). A linked Background joins only when it is a target itself: a link doesn't
|
||||
/// bring it into a box over other layers.
|
||||
pub fn box_targets(doc: &Document, targets: &[LayerId]) -> Vec<LayerId> {
|
||||
let mut ids = crate::layer_multi_cmds::move_targets(doc, targets);
|
||||
ids.retain(|id| targets.contains(id) || !doc.is_background(*id));
|
||||
@@ -874,7 +883,14 @@ fn transform(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
(Some(ids), _) => ids,
|
||||
_ if lone.is_some() => Vec::new(),
|
||||
(None, Some(id)) => vec![id],
|
||||
(None, None) => selection_targets(st),
|
||||
(None, None) => {
|
||||
let ids = selection_targets(st);
|
||||
if let Some(bg) = background_among(&st.doc, &ids) {
|
||||
let name = st.doc.layer(bg).map_or("Background", |l| l.name.as_str());
|
||||
return Err(EngineError::Other(format!("layer \"{name}\" is locked")));
|
||||
}
|
||||
ids
|
||||
}
|
||||
};
|
||||
if let Some(missing) = ids.iter().find(|id| st.doc.layer(**id).is_none()) {
|
||||
return Err(EngineError::NoLayer(*missing));
|
||||
@@ -1016,7 +1032,7 @@ pub fn specs() -> Vec<CommandSpec> {
|
||||
label: "Free Transform",
|
||||
menu: &[],
|
||||
shortcut: None,
|
||||
params: r##"{"layer":id?,"layers":[id,…]? (several layers moved by one box, one step; default with neither = the selected layers but the Background, as Photoshop's Free Transform with several selected),"rect":[x0,y0,x1,y1]? (source frame; default = the layers' content ∩ selection),"quad":[[x,y]×4]? (where the frame's corners go, clockwise from top-left),"matrix":[a,b,c,d,e,f]? (affine alternative),"scaleX":%?,"scaleY":%?,"rotate":deg?,"dx":px?,"dy":px?,"reference":"center|topLeft|top|topRight|left|right|bottomLeft|bottom|bottomRight"|[x,y]="center" (relative alternative: scale and rotate about this point, then move),"interpolation":"bicubic|bilinear|nearest|bicubicSharper|bicubicSmoother|bicubicAutomatic"="bicubic" (Photoshop's bicubic variants render as Bicubic),"stepName":"Transform Path"? (History step name; the shape tools' bounding box sends it; default "Free Transform"; any other value is refused),"target":"pixels"|"mask"|"quickMask"|{"channel":i}=Channels panel target (an unlinked mask, an alpha channel or the Quick Mask transforms alone; a linked mask moves with its layer)}"##,
|
||||
params: r##"{"layer":id?,"layers":[id,…]? (several layers moved by one box, one step; default with neither = the selected layers, as Photoshop's Free Transform with several selected; refused as locked when the Background is among several),"rect":[x0,y0,x1,y1]? (source frame; default = the layers' content ∩ selection),"quad":[[x,y]×4]? (where the frame's corners go, clockwise from top-left),"matrix":[a,b,c,d,e,f]? (affine alternative),"scaleX":%?,"scaleY":%?,"rotate":deg?,"dx":px?,"dy":px?,"reference":"center|topLeft|top|topRight|left|right|bottomLeft|bottom|bottomRight"|[x,y]="center" (relative alternative: scale and rotate about this point, then move),"interpolation":"bicubic|bilinear|nearest|bicubicSharper|bicubicSmoother|bicubicAutomatic"="bicubic" (Photoshop's bicubic variants render as Bicubic),"stepName":"Transform Path"? (History step name; the shape tools' bounding box sends it; default "Free Transform"; any other value is refused),"target":"pixels"|"mask"|"quickMask"|{"channel":i}=Channels panel target (an unlinked mask, an alpha channel or the Quick Mask transforms alone; a linked mask moves with its layer)}"##,
|
||||
enabled: has_layer,
|
||||
journal: true,
|
||||
run: transform,
|
||||
|
||||
@@ -91,13 +91,17 @@ fn the_layers_param_names_the_layers_and_a_listed_group_carries_its_children() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_background_stays_out_of_a_selection_of_several() {
|
||||
fn the_background_among_a_selection_of_several_refuses_as_locked() {
|
||||
// Photoshop shows its locked-layer alert rather than leaving the Background out (#11101).
|
||||
let (mut s, paint, text) = two_layers();
|
||||
let bg = s.active().unwrap().doc.layers[0].id;
|
||||
s.execute("layer.select", json!({"layer": bg.0, "mode": "add"})).unwrap();
|
||||
let r = s.execute("edit.transform", json!({"matrix": [1, 0, 0, 1, 3, 0]})).unwrap();
|
||||
assert_eq!(r["layers"], json!([paint.0, text.0]));
|
||||
let st = s.active().unwrap();
|
||||
let (before, revision) = (st.doc.clone(), st.revision);
|
||||
let err = s.execute("edit.transform", json!({"matrix": [1, 0, 0, 1, 3, 0]})).unwrap_err().to_string();
|
||||
assert!(err.contains("\"Background\" is locked"), "{err}");
|
||||
let st = s.active().unwrap();
|
||||
assert!(st.doc == before && st.revision == revision, "nothing moved, not even {paint:?} or {text:?}");
|
||||
assert!(st.doc.is_background(bg), "still the Background");
|
||||
assert_eq!(st.doc.layer(bg).unwrap().surface().unwrap().content_bounds(), Rect::new(0, 0, 200, 200));
|
||||
}
|
||||
|
||||
@@ -118,6 +118,7 @@ use textures::{clamp_texture_side, preview_bounds, texture_factor};
|
||||
use textures::{ensure_gpu, redraw_focus, visible_doc_rect};
|
||||
pub use tool_input::{ToolEvent, selection_drag_kind, tool_event};
|
||||
pub(crate) use tool_input::{command_guide_at, command_moves_layer, event_tool};
|
||||
pub(crate) use transform_controls::ControlsPress;
|
||||
pub(crate) use transform_controls::{LAYER_EDGES, layer_edges_rect};
|
||||
use transform_controls::{begin_transform_controls_at, draw_transform_controls, transform_controls_cursor, transform_controls_rect, transform_controls_turn};
|
||||
#[cfg(test)]
|
||||
@@ -1036,12 +1037,13 @@ fn handle_canvas_tool_stroke(
|
||||
if buttons.started
|
||||
&& let Some(p) = ui.input(|i| i.pointer.press_origin()).filter(|p| rect.contains(*p)).or(response.interact_pointer_pos())
|
||||
{
|
||||
if tool == Tool::Move && app.ui.transform.is_none() {
|
||||
begin_transform_controls_at(app, ctx, xf, p);
|
||||
// A press the controls refused (the Background among the layers) goes nowhere else (#11101).
|
||||
let refused = tool == Tool::Move && app.ui.transform.is_none() && begin_transform_controls_at(app, ctx, xf, p) == ControlsPress::Refused;
|
||||
if !refused {
|
||||
let d = xf.to_doc(p);
|
||||
let press_mods = if capture_press { ctx.data(|d| d.get_temp(tool_press)).unwrap_or(mods) } else { mods };
|
||||
tool_event(app, ToolEvent::Down { x: d[0], y: d[1], pressure: app.stylus.pressure() }, press_mods);
|
||||
}
|
||||
let d = xf.to_doc(p);
|
||||
let press_mods = if capture_press { ctx.data(|d| d.get_temp(tool_press)).unwrap_or(mods) } else { mods };
|
||||
tool_event(app, ToolEvent::Down { x: d[0], y: d[1], pressure: app.stylus.pressure() }, press_mods);
|
||||
}
|
||||
// A stroke started on the press takes its moves from the press on, not only once egui
|
||||
// recognises a drag (past its click distance): otherwise the first few points are lost
|
||||
@@ -1200,10 +1202,10 @@ fn handle_canvas_tool_clicks(
|
||||
app.ui.status = e;
|
||||
}
|
||||
}
|
||||
// A click the transform controls refused (the Background among the layers) goes
|
||||
// nowhere else (#11101).
|
||||
_ if tool == Tool::Move && app.ui.transform.is_none() && begin_transform_controls_at(app, ctx, xf, p) == ControlsPress::Refused => {}
|
||||
_ => {
|
||||
if tool == Tool::Move && app.ui.transform.is_none() {
|
||||
begin_transform_controls_at(app, ctx, xf, p);
|
||||
}
|
||||
// If this tool was not handled on pointer-down, fall back to a
|
||||
// click-sized gesture while preserving pen pressure (mouse stays at 1).
|
||||
tool_event(app, ToolEvent::Down { x: d[0], y: d[1], pressure: app.stylus.pressure() }, click_mods);
|
||||
|
||||
@@ -22,7 +22,9 @@ pub(crate) fn layer_edges_rect(app: &PhotocraftApp, idx: usize) -> Option<photoc
|
||||
Some(b.translate(dx, dy))
|
||||
}
|
||||
|
||||
/// The layers Move › Show Transform Controls frames: every selected layer but the Background.
|
||||
/// The layers Move › Show Transform Controls frames: every selected layer, the Background too when
|
||||
/// it is among several (a handle drag then refuses, [`begin_transform_controls_at`]), but not the
|
||||
/// Background alone (#11101).
|
||||
/// A corner radius drag hides the box, as in Photoshop. So does View › Extras (⌘H, #6699): the
|
||||
/// controls are an Extra, hidden and inert until Extras comes back, while Show Transform Controls
|
||||
/// keeps its setting.
|
||||
@@ -37,21 +39,21 @@ pub(super) fn transform_controls_layers(app: &PhotocraftApp) -> Vec<&photocraft_
|
||||
return Vec::new();
|
||||
}
|
||||
let Some(st) = app.session.active() else { return Vec::new() };
|
||||
st.selected_layers().into_iter().filter_map(|id| st.doc.layer(id)).filter(|l| !crate::doc_props_ui::is_background(&st.doc, l)).collect()
|
||||
let selected = st.selected_layers();
|
||||
let several = selected.len() > 1;
|
||||
selected.into_iter().filter_map(|id| st.doc.layer(id)).filter(|l| several || !crate::doc_props_ui::is_background(&st.doc, l)).collect()
|
||||
}
|
||||
|
||||
impl PhotocraftApp {
|
||||
/// Control-channel presses use document coordinates, but the canvas owns the screen-sized
|
||||
/// handle and rotation hit areas. Start the same session before forwarding the press.
|
||||
pub(crate) fn begin_move_transform_controls(&mut self, ctx: &egui::Context, x: f64, y: f64) {
|
||||
pub(crate) fn begin_move_transform_controls(&mut self, ctx: &egui::Context, x: f64, y: f64) -> ControlsPress {
|
||||
if !crate::menus::modal_allows(self, "edit.freeTransform") {
|
||||
return;
|
||||
return ControlsPress::Missed;
|
||||
}
|
||||
let Some(xf) = ViewXform::active(self) else { return };
|
||||
let Some(xf) = ViewXform::active(self) else { return ControlsPress::Missed };
|
||||
let p = xf.to_screen(x as f32, y as f32);
|
||||
if p.is_finite() && xf.rect.contains(p) {
|
||||
begin_transform_controls_at(self, ctx, &xf, p);
|
||||
}
|
||||
if p.is_finite() && xf.rect.contains(p) { begin_transform_controls_at(self, ctx, &xf, p) } else { ControlsPress::Missed }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -119,18 +121,40 @@ pub(super) fn transform_controls_hit(r: Rect, p: Pos2) -> bool {
|
||||
transform_controls_cursor(r, p).is_some()
|
||||
}
|
||||
|
||||
/// The reference application's alert when a Move-tool transform control is dragged on a frame
|
||||
/// that holds the Background among other layers (measured, #11101).
|
||||
pub(crate) const CONTROLS_LOCKED: &str = "Could not transform because the layer is locked.";
|
||||
|
||||
/// What a press on the Move tool's transform controls did.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub(crate) enum ControlsPress {
|
||||
/// Not on a control, or no session could start: the press is the Move tool's.
|
||||
Missed,
|
||||
/// A Free Transform session started; the press goes on to drive it.
|
||||
Started,
|
||||
/// Refused with the locked-layer alert: the press goes nowhere else (#11101).
|
||||
Refused,
|
||||
}
|
||||
|
||||
/// Enter the existing Free Transform session when a Move-tool transform control is pressed: on
|
||||
/// every selected layer, as the frame shows them (#6501).
|
||||
pub(super) fn begin_transform_controls_at(app: &mut PhotocraftApp, ctx: &egui::Context, xf: &ViewXform, p: Pos2) -> bool {
|
||||
let Some(r) = transform_controls_rect(app, xf) else { return false };
|
||||
/// every selected layer, as the frame shows them (#6501). With the Background among them the press
|
||||
/// shows the locked-layer alert and changes nothing (#11101).
|
||||
pub(super) fn begin_transform_controls_at(app: &mut PhotocraftApp, ctx: &egui::Context, xf: &ViewXform, p: Pos2) -> ControlsPress {
|
||||
let Some(r) = transform_controls_rect(app, xf) else { return ControlsPress::Missed };
|
||||
if !transform_controls_hit(r, p) {
|
||||
return false;
|
||||
return ControlsPress::Missed;
|
||||
}
|
||||
let background = app.session.active().is_some_and(|st| photocraft_engine::transform_cmds::background_among(&st.doc, &st.selected_layers()).is_some());
|
||||
if background {
|
||||
let message = serde_json::json!({ "message": tl!(CONTROLS_LOCKED) });
|
||||
app.ui.open_dialog(crate::state::DialogKind::Error, message.as_object().cloned().unwrap_or_default());
|
||||
return ControlsPress::Refused;
|
||||
}
|
||||
match crate::transform_tool::begin(app, ctx) {
|
||||
Ok(()) => true,
|
||||
Ok(()) => ControlsPress::Started,
|
||||
Err(e) => {
|
||||
app.ui.status = e;
|
||||
false
|
||||
ControlsPress::Missed
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -146,13 +146,16 @@ fn several_selected_layers_share_one_frame_that_transforms_them_all() {
|
||||
assert!(both.contains_rect(one) && both.width() > one.width() && both.height() > one.height(), "{both:?} frames both");
|
||||
let (lt, rb) = (xf.to_doc(both.left_top()), xf.to_doc(both.right_bottom()));
|
||||
assert!((lt[0] - 20.0).abs() < 1e-3 && (lt[1] - 30.0).abs() < 1e-3 && (rb[0] - 150.0).abs() < 1e-3 && (rb[1] - 150.0).abs() < 1e-3, "{lt:?} {rb:?}");
|
||||
// The Background never joins the frame.
|
||||
// The Background among them joins the frame, as in Photoshop; its handles refuse (#11101).
|
||||
let bg = app.session.active().unwrap().doc.layers[0].id;
|
||||
app.session.execute("layer.select", json!({"layer": bg.0, "mode": "add"})).unwrap();
|
||||
assert_eq!(transform_controls_layers(&app).len(), 3);
|
||||
assert_eq!(transform_controls_rect(&app, &xf), Some(Rect::from_min_size(Pos2::ZERO, vec2(200.0, 200.0))));
|
||||
app.session.execute("layer.select", json!({"layer": bg.0, "mode": "toggle"})).unwrap();
|
||||
assert_eq!(transform_controls_layers(&app).len(), 2);
|
||||
assert_eq!(transform_controls_rect(&app, &xf), Some(both));
|
||||
let ctx = egui::Context::default();
|
||||
assert!(begin_transform_controls_at(&mut app, &ctx, &xf, both.right_bottom()));
|
||||
assert_eq!(begin_transform_controls_at(&mut app, &ctx, &xf, both.right_bottom()), ControlsPress::Started);
|
||||
let t = app.ui.transform.as_ref().expect("the shared frame's handle starts Free Transform");
|
||||
assert_eq!(t.rect, [20.0, 30.0, 150.0, 150.0]);
|
||||
assert_eq!(t.layers, [a.0, b.0]);
|
||||
@@ -173,7 +176,7 @@ fn move_transform_controls_start_free_transform_for_vector_shapes() {
|
||||
assert!(transform_controls_hit(r, r.right_bottom()));
|
||||
|
||||
let ctx = egui::Context::default();
|
||||
assert!(begin_transform_controls_at(&mut app, &ctx, &xf, r.right_bottom()));
|
||||
assert_eq!(begin_transform_controls_at(&mut app, &ctx, &xf, r.right_bottom()), ControlsPress::Started);
|
||||
let t = app.ui.transform.as_ref().expect("control press starts Free Transform");
|
||||
assert_eq!(t.rect, [20.0, 30.0, 100.0, 70.0]);
|
||||
}
|
||||
@@ -210,7 +213,7 @@ fn hiding_extras_hides_the_move_transform_controls() {
|
||||
assert!(transform_controls_rect(&app, &xf).is_none(), "no box with Extras hidden");
|
||||
assert_eq!(painted(&mut app), 0, "nothing is drawn");
|
||||
// Hidden handles don't act: a press on a corner doesn't start Free Transform.
|
||||
assert!(!begin_transform_controls_at(&mut app, &ctx, &xf, shown.right_bottom()));
|
||||
assert_eq!(begin_transform_controls_at(&mut app, &ctx, &xf, shown.right_bottom()), ControlsPress::Missed);
|
||||
assert!(app.ui.transform.is_none());
|
||||
assert!(app.ui.tool_options.move_show_transform, "Show Transform Controls is unchanged");
|
||||
assert_eq!(app.session.active().unwrap().doc.layers, layer, "the layer is unchanged");
|
||||
@@ -235,7 +238,7 @@ fn move_transform_controls_follow_floating_selected_pixels() {
|
||||
let r = transform_controls_rect(&app, &xf).unwrap();
|
||||
assert_eq!(r, Rect::from_min_max(pos2(200.0, 150.0), pos2(300.0, 210.0)));
|
||||
let ctx = egui::Context::default();
|
||||
assert!(begin_transform_controls_at(&mut app, &ctx, &xf, r.right_bottom()));
|
||||
assert_eq!(begin_transform_controls_at(&mut app, &ctx, &xf, r.right_bottom()), ControlsPress::Started);
|
||||
assert_eq!(app.ui.transform.as_ref().unwrap().rect, [200.0, 150.0, 300.0, 210.0]);
|
||||
}
|
||||
|
||||
@@ -256,7 +259,7 @@ fn move_transform_controls_frame_the_selection() {
|
||||
let r = transform_controls_rect(&app, &xf).unwrap();
|
||||
assert_eq!(r, Rect::from_min_max(pos2(60.0, 80.0), pos2(160.0, 200.0)));
|
||||
let ctx = egui::Context::default();
|
||||
assert!(begin_transform_controls_at(&mut app, &ctx, &xf, r.right_bottom()));
|
||||
assert_eq!(begin_transform_controls_at(&mut app, &ctx, &xf, r.right_bottom()), ControlsPress::Started);
|
||||
assert_eq!(app.ui.transform.as_ref().unwrap().rect, [60.0, 80.0, 160.0, 200.0]);
|
||||
}
|
||||
|
||||
@@ -296,7 +299,7 @@ fn free_transform_on_a_locked_layer_is_refused_up_front() {
|
||||
let xf =
|
||||
ViewXform { rect: Rect::from_min_size(Pos2::ZERO, vec2(200.0, 200.0)), zoom: 1.0, center: [100.0, 100.0], flip: false, rotation: 0.0, aspect: 1.0 };
|
||||
let r = transform_controls_rect(&app, &xf).unwrap();
|
||||
assert!(!begin_transform_controls_at(&mut app, &ctx, &xf, r.right_bottom()));
|
||||
assert_eq!(begin_transform_controls_at(&mut app, &ctx, &xf, r.right_bottom()), ControlsPress::Missed);
|
||||
assert!(app.ui.transform.is_none());
|
||||
assert_eq!(errors(&app), [crate::transform_tool::LOCKED]);
|
||||
}
|
||||
@@ -357,3 +360,42 @@ fn transform_control_hit_keeps_move_interior_free_and_has_rotation_band() {
|
||||
assert!(!transform_controls_hit(r, r.center()));
|
||||
assert!(!transform_controls_hit(r, pos2(60.0, 4.0)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_background_among_several_joins_the_frame_and_its_handles_refuse() {
|
||||
// Measured in the reference application (#11101): the box frames them all, the Background
|
||||
// included, and a handle drag shows its locked alert and changes nothing.
|
||||
let mut app = PhotocraftApp::new(photocraft_engine::Session::new(), crate::Services::default());
|
||||
app.session.execute("file.new", json!({"width": 200, "height": 200})).unwrap();
|
||||
app.session.execute("shape.create", json!({"kind": "rect", "rect": [20, 30, 40, 40], "fill": "#ff0000"})).unwrap();
|
||||
let a = app.session.active().unwrap().active_layer.unwrap();
|
||||
app.session.execute("shape.create", json!({"kind": "rect", "rect": [100, 120, 50, 30], "fill": "#ff0000"})).unwrap();
|
||||
let b = app.session.active().unwrap().active_layer.unwrap();
|
||||
let bg = app.session.active().unwrap().doc.layers[0].id;
|
||||
app.session.execute("layer.select", json!({"layer": bg.0, "mode": "add"})).unwrap();
|
||||
app.sync_views();
|
||||
app.ui.tool = Tool::Move;
|
||||
app.ui.tool_options.move_show_transform = true;
|
||||
let xf =
|
||||
ViewXform { rect: Rect::from_min_size(Pos2::ZERO, vec2(200.0, 200.0)), zoom: 1.0, center: [100.0, 100.0], flip: false, rotation: 0.0, aspect: 1.0 };
|
||||
let r = transform_controls_rect(&app, &xf).expect("the box frames them all");
|
||||
assert_eq!(r, Rect::from_min_size(Pos2::ZERO, vec2(200.0, 200.0)), "the Background included");
|
||||
let before = app.session.active().unwrap().doc.clone();
|
||||
let ctx = egui::Context::default();
|
||||
assert_eq!(begin_transform_controls_at(&mut app, &ctx, &xf, r.right_bottom()), ControlsPress::Refused, "the press is taken, not a move");
|
||||
assert!(app.ui.transform.is_none(), "no box opens");
|
||||
let alerts: Vec<_> =
|
||||
app.ui.dialogs.iter().filter(|d| d.kind == crate::state::DialogKind::Error).filter_map(|d| d.fields.get("message")?.as_str()).collect();
|
||||
assert_eq!(alerts, [super::transform_controls::CONTROLS_LOCKED]);
|
||||
assert!(app.session.active().unwrap().doc == before, "nothing changed");
|
||||
// The Background alone: no box at all.
|
||||
app.session.execute("layer.select", json!({"layer": bg.0})).unwrap();
|
||||
assert_eq!(transform_controls_rect(&app, &xf), None);
|
||||
// Without the Background the handles start Free Transform, as before.
|
||||
app.ui.dialogs.clear();
|
||||
app.session.execute("layer.select", json!({"layer": a.0})).unwrap();
|
||||
app.session.execute("layer.select", json!({"layer": b.0, "mode": "add"})).unwrap();
|
||||
let r = transform_controls_rect(&app, &xf).unwrap();
|
||||
assert_eq!(begin_transform_controls_at(&mut app, &ctx, &xf, r.right_bottom()), ControlsPress::Started);
|
||||
assert_eq!(app.ui.transform.as_ref().unwrap().layers, [a.0, b.0]);
|
||||
}
|
||||
@@ -1272,7 +1272,10 @@ fn pointer(app: &mut PhotocraftApp, ctx: &egui::Context, p: &Value) -> Outcome {
|
||||
continue;
|
||||
}
|
||||
if app.ui.tool == Tool::Move && !gesture.secondary && !gesture.middle && !gesture.space && matches!(sample.ev, ToolEvent::Down { .. }) {
|
||||
app.begin_move_transform_controls(ctx, sample.x, sample.y);
|
||||
// A press the controls refused (the Background among the layers) goes nowhere else (#11101).
|
||||
if app.begin_move_transform_controls(ctx, sample.x, sample.y) == crate::canvas::ControlsPress::Refused {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
forward_pointer_event(app, &gesture, sample);
|
||||
}
|
||||
|
||||
@@ -1787,6 +1787,7 @@
|
||||
Move Tool أداة التحريك
|
||||
Could not use the move tool because the layer is locked. تعذر استخدام أداة التحريك لأن الطبقة مقفلة.
|
||||
Could not complete the Free Transform command because the layer is locked. تعذر إكمال أمر التحويل الحر لأن الطبقة مقفلة.
|
||||
Could not transform because the layer is locked. تعذر التحويل لأن الطبقة مقفلة.
|
||||
Multiply ضرب
|
||||
My Brushes الفُرش الخاصة بي
|
||||
Neutrals الدرجات المحايدة
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 47.
|
@@ -1711,6 +1711,7 @@
|
||||
Move Tool Alat za pomjeranje
|
||||
Could not use the move tool because the layer is locked. Nije moguće koristiti alat za pomjeranje jer je sloj zaključan.
|
||||
Could not complete the Free Transform command because the layer is locked. Naredbu Slobodna transformacija nije moguće dovršiti jer je sloj zaključan.
|
||||
Could not transform because the layer is locked. Transformacija nije moguća jer je sloj zaključan.
|
||||
Multiply Množenje
|
||||
My Brushes Moje četkice
|
||||
Neutrals Neutralni tonovi
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 47.
|
@@ -1901,6 +1901,7 @@
|
||||
Move Tool Nástroj přesun
|
||||
Could not use the move tool because the layer is locked. Nástroj Přesun nelze použít, protože vrstva je zamčená.
|
||||
Could not complete the Free Transform command because the layer is locked. Příkaz Volná transformace nelze dokončit, protože vrstva je zamčená.
|
||||
Could not transform because the layer is locked. Transformaci nelze provést, protože vrstva je zamčená.
|
||||
Multiply Násobit
|
||||
My Brushes Moje štětce
|
||||
Neutrals Neutrální
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 47.
|
@@ -1638,6 +1638,7 @@
|
||||
Move Tool Flytteværktøj
|
||||
Could not use the move tool because the layer is locked. Kunne ikke bruge flytteværktøjet, fordi laget er låst.
|
||||
Could not complete the Free Transform command because the layer is locked. Kunne ikke fuldføre kommandoen Fri transformering, fordi laget er låst.
|
||||
Could not transform because the layer is locked. Kunne ikke transformere, fordi laget er låst.
|
||||
Rectangular Marquee Tool Rektangulært markeringsværktøj
|
||||
Elliptical Marquee Tool Elliptisk markeringsværktøj
|
||||
Single Row Marquee Tool Markeringsværktøj til enkelt række
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 47.
|
@@ -1862,6 +1862,7 @@
|
||||
Move Tool Verschieben-Werkzeug
|
||||
Could not use the move tool because the layer is locked. Das Verschieben-Werkzeug konnte nicht verwendet werden, weil die Ebene gesperrt ist.
|
||||
Could not complete the Free Transform command because the layer is locked. Der Befehl „Frei transformieren“ konnte nicht ausgeführt werden, weil die Ebene gesperrt ist.
|
||||
Could not transform because the layer is locked. Transformieren nicht möglich, weil die Ebene gesperrt ist.
|
||||
Multiply Multiplizieren
|
||||
My Brushes Meine Pinsel
|
||||
Neutrals Neutrale
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 47.
|
@@ -3481,6 +3481,7 @@ cameraRaw Yellow Κίτρινο
|
||||
Move Tool Μετακίνηση (Move Tool)
|
||||
Could not use the move tool because the layer is locked. Δεν ήταν δυνατή η χρήση του εργαλείου Μετακίνηση, επειδή το επίπεδο είναι κλειδωμένο.
|
||||
Could not complete the Free Transform command because the layer is locked. Δεν ήταν δυνατή η ολοκλήρωση της εντολής Ελεύθερος μετασχηματισμός, επειδή το επίπεδο είναι κλειδωμένο.
|
||||
Could not transform because the layer is locked. Δεν ήταν δυνατός ο μετασχηματισμός, επειδή το επίπεδο είναι κλειδωμένο.
|
||||
Note Tool Σημείωση (Note Tool)
|
||||
Object Selection Tool Επιλογή αντικειμένου (Object Selection Tool)
|
||||
Paint Bucket Tool Κουβάς γεμίσματος (Paint Bucket Tool)
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 47 and column 53.
|
@@ -1832,6 +1832,7 @@
|
||||
Move Tool Herramienta Mover
|
||||
Could not use the move tool because the layer is locked. No se pudo usar la herramienta Mover porque la capa está bloqueada.
|
||||
Could not complete the Free Transform command because the layer is locked. No se pudo completar el comando Transformación libre porque la capa está bloqueada.
|
||||
Could not transform because the layer is locked. No se pudo transformar porque la capa está bloqueada.
|
||||
Multiply Multiplicar
|
||||
My Brushes Mis pinceles
|
||||
Neutrals Neutros
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 4 and column 47.
|
@@ -1639,6 +1639,7 @@
|
||||
Move Tool Siirtotyökalu
|
||||
Could not use the move tool because the layer is locked. Siirtotyökalua ei voitu käyttää, koska taso on lukittu.
|
||||
Could not complete the Free Transform command because the layer is locked. Vapaa muunnos -komentoa ei voitu suorittaa, koska taso on lukittu.
|
||||
Could not transform because the layer is locked. Muuntaminen ei onnistunut, koska taso on lukittu.
|
||||
Rectangular Marquee Tool Suorakulmainen valintakehys
|
||||
Elliptical Marquee Tool Elliptinen valintakehys
|
||||
Single Row Marquee Tool Yhden rivin valintakehys
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 47.
|
@@ -1840,6 +1840,7 @@
|
||||
Move Tool Outil Déplacement
|
||||
Could not use the move tool because the layer is locked. Impossible d'utiliser l'outil Déplacement, car le calque est verrouillé.
|
||||
Could not complete the Free Transform command because the layer is locked. Impossible d'exécuter la commande Transformation libre, car le calque est verrouillé.
|
||||
Could not transform because the layer is locked. Impossible de transformer, car le calque est verrouillé.
|
||||
Multiply Produit
|
||||
My Brushes Mes pinceaux
|
||||
Neutrals Neutres
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 47.
|
@@ -1073,6 +1073,7 @@
|
||||
Control: Vezérlés:
|
||||
Corners Sarkok
|
||||
Could not complete the Free Transform command because the layer is locked. A szabad alakítás nem hajtható végre, mert a réteg zárolva van.
|
||||
Could not transform because the layer is locked. Az alakítás nem hajtható végre, mert a réteg zárolva van.
|
||||
Could not transform: the layer is empty Az alakítás nem hajtható végre: a réteg üres
|
||||
Could not use Puppet Warp: no opaque pixels A bábtorzítás nem használható: nincsenek átlátszatlan pixelek
|
||||
Could not use Puppet Warp: the layer is empty A bábtorzítás nem használható: a réteg üres
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 47.
|
@@ -1851,6 +1851,7 @@
|
||||
Move Tool Alat Pindah
|
||||
Could not use the move tool because the layer is locked. Tidak dapat menggunakan alat Pindahkan karena layer terkunci.
|
||||
Could not complete the Free Transform command because the layer is locked. Tidak dapat menyelesaikan perintah Transformasi Bebas karena layer terkunci.
|
||||
Could not transform because the layer is locked. Tidak dapat mentransformasi karena layer terkunci.
|
||||
Multiply Multiply
|
||||
My Brushes Kuas Saya
|
||||
Neutrals Netral
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 4 and column 47.
|
@@ -1877,6 +1877,7 @@
|
||||
Move Tool Strumento Sposta
|
||||
Could not use the move tool because the layer is locked. Impossibile usare lo strumento Sposta perché il livello è bloccato.
|
||||
Could not complete the Free Transform command because the layer is locked. Impossibile completare il comando Trasformazione libera perché il livello è bloccato.
|
||||
Could not transform because the layer is locked. Impossibile trasformare perché il livello è bloccato.
|
||||
Multiply Moltiplica
|
||||
My Brushes I miei pennelli
|
||||
Neutrals Neutri
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 79 and column 53.
|
@@ -1888,6 +1888,7 @@
|
||||
Move Tool 移動ツール
|
||||
Could not use the move tool because the layer is locked. レイヤーがロックされているため、移動ツールを使用できません。
|
||||
Could not complete the Free Transform command because the layer is locked. レイヤーがロックされているため、自由変形コマンドを完了できません。
|
||||
Could not transform because the layer is locked. レイヤーがロックされているため、変形できません。
|
||||
Multiply 乗算
|
||||
My Brushes マイブラシ
|
||||
Neutrals 中間色系
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 47.
|
@@ -1863,6 +1863,7 @@
|
||||
Move Tool 이동 도구
|
||||
Could not use the move tool because the layer is locked. 레이어가 잠겨 있어 이동 도구를 사용할 수 없습니다.
|
||||
Could not complete the Free Transform command because the layer is locked. 레이어가 잠겨 있어 자유 변형 명령을 완료할 수 없습니다.
|
||||
Could not transform because the layer is locked. 레이어가 잠겨 있어 변형할 수 없습니다.
|
||||
Multiply 곱하기
|
||||
My Brushes 내 브러시
|
||||
Neutrals 중립색
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 63 and column 53.
|
@@ -1638,6 +1638,7 @@
|
||||
Move Tool Flytteverktøy
|
||||
Could not use the move tool because the layer is locked. Kunne ikke bruke flytteverktøyet fordi laget er låst.
|
||||
Could not complete the Free Transform command because the layer is locked. Kunne ikke fullføre Fri transformering fordi laget er låst.
|
||||
Could not transform because the layer is locked. Kunne ikke transformere fordi laget er låst.
|
||||
Rectangular Marquee Tool Rektangulær markeringsramme
|
||||
Elliptical Marquee Tool Elliptisk markeringsramme
|
||||
Single Row Marquee Tool Markeringsramme for enkeltrad
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 47.
|
@@ -1809,6 +1809,7 @@
|
||||
Move Tool Verplaatsgereedschap
|
||||
Could not use the move tool because the layer is locked. Kan het verplaatsgereedschap niet gebruiken omdat de laag is vergrendeld.
|
||||
Could not complete the Free Transform command because the layer is locked. Kan de opdracht Vrije transformatie niet voltooien omdat de laag is vergrendeld.
|
||||
Could not transform because the layer is locked. Kan niet transformeren omdat de laag is vergrendeld.
|
||||
Multiply Vermenigvuldigen
|
||||
My Brushes Mijn penselen
|
||||
Neutrals Neutrale tinten
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 8 and column 34.
|
@@ -1874,6 +1874,7 @@
|
||||
Move Tool Narzędzie Przesunięcie
|
||||
Could not use the move tool because the layer is locked. Nie można użyć narzędzia Przesunięcie, ponieważ warstwa jest zablokowana.
|
||||
Could not complete the Free Transform command because the layer is locked. Nie można ukończyć polecenia Dowolne przekształcanie, ponieważ warstwa jest zablokowana.
|
||||
Could not transform because the layer is locked. Nie można przekształcić, ponieważ warstwa jest zablokowana.
|
||||
Multiply Mnożenie
|
||||
My Brushes Moje pędzle
|
||||
Neutrals Neutralne
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 47.
|
@@ -1834,6 +1834,7 @@
|
||||
Move Tool Ferramenta Mover
|
||||
Could not use the move tool because the layer is locked. Não foi possível usar a ferramenta Mover porque a camada está bloqueada.
|
||||
Could not complete the Free Transform command because the layer is locked. Não foi possível concluir o comando Transformação livre porque a camada está bloqueada.
|
||||
Could not transform because the layer is locked. Não foi possível transformar porque a camada está bloqueada.
|
||||
Multiply Multiplicação
|
||||
My Brushes Meus pincéis
|
||||
Neutrals Neutros
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 47.
|
@@ -1749,6 +1749,7 @@
|
||||
Move Tool Перемещение
|
||||
Could not use the move tool because the layer is locked. Не удалось использовать инструмент «Перемещение», потому что слой заблокирован.
|
||||
Could not complete the Free Transform command because the layer is locked. Не удалось выполнить команду «Свободное трансформирование», потому что слой заблокирован.
|
||||
Could not transform because the layer is locked. Не удалось выполнить трансформирование, потому что слой заблокирован.
|
||||
Rectangular Marquee Tool Прямоугольная область
|
||||
Elliptical Marquee Tool Овальная область
|
||||
Single Row Marquee Tool Область (горизонтальная строка)
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 47.
|
@@ -1847,6 +1847,7 @@
|
||||
Move Tool Alatka za pomeranje
|
||||
Could not use the move tool because the layer is locked. Alatka za pomeranje ne može da se koristi jer je sloj zaključan.
|
||||
Could not complete the Free Transform command because the layer is locked. Komanda Slobodna transformacija ne može da se izvrši jer je sloj zaključan.
|
||||
Could not transform because the layer is locked. Transformacija nije moguća jer je sloj zaključan.
|
||||
Multiply Množenje
|
||||
My Brushes Moje četkice
|
||||
Neutrals Neutralne
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 47.
|
@@ -1636,6 +1636,7 @@
|
||||
Move Tool Flyttverktyg
|
||||
Could not use the move tool because the layer is locked. Det gick inte att använda flyttverktyget eftersom lagret är låst.
|
||||
Could not complete the Free Transform command because the layer is locked. Det gick inte att slutföra kommandot Ombilda fritt eftersom lagret är låst.
|
||||
Could not transform because the layer is locked. Det gick inte att omforma eftersom lagret är låst.
|
||||
Rectangular Marquee Tool Verktyget Rektangulär markeringsram
|
||||
Elliptical Marquee Tool Verktyget Elliptisk markeringsram
|
||||
Single Row Marquee Tool Verktyget Enradsmarkeringsram
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 47.
|
@@ -1762,6 +1762,7 @@
|
||||
Move Tool Taşıma Aracı
|
||||
Could not use the move tool because the layer is locked. Katman kilitli olduğundan taşıma aracı kullanılamadı.
|
||||
Could not complete the Free Transform command because the layer is locked. Katman kilitli olduğundan Serbest Dönüştürme komutu tamamlanamadı.
|
||||
Could not transform because the layer is locked. Katman kilitli olduğundan dönüştürme yapılamadı.
|
||||
Multiply Çoğalt
|
||||
My Brushes Fırçalarım
|
||||
Neutrals Nötrler
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 8 and column 34.
|
@@ -1719,6 +1719,7 @@
|
||||
Move Tool Інструмент «Переміщення»
|
||||
Could not use the move tool because the layer is locked. Не вдалося використати інструмент «Переміщення», бо шар заблоковано.
|
||||
Could not complete the Free Transform command because the layer is locked. Не вдалося виконати команду «Вільна трансформація», бо шар заблоковано.
|
||||
Could not transform because the layer is locked. Не вдалося трансформувати, бо шар заблоковано.
|
||||
Rectangular Marquee Tool Інструмент «Прямокутне виділення»
|
||||
Elliptical Marquee Tool Інструмент «Еліптичне виділення»
|
||||
Single Row Marquee Tool Інструмент «Виділення одного рядка»
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 152 and column 53.
|
@@ -2718,6 +2718,7 @@ shape Appearance Diện mạo
|
||||
Measurement Scale Marker Dấu tỉ lệ đo
|
||||
Could not use the move tool because the layer is locked. Không thể dùng công cụ di chuyển vì lớp đã bị khoá.
|
||||
Could not complete the Free Transform command because the layer is locked. Không thể hoàn tất lệnh Biến đổi tự do vì lớp đã bị khoá.
|
||||
Could not transform because the layer is locked. Không thể biến đổi vì lớp đã bị khoá.
|
||||
My Brushes Cọ của tôi
|
||||
Neutrals Vùng trung tính
|
||||
New Workspace Không gian làm việc mới
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 47.
|
@@ -1912,6 +1912,7 @@
|
||||
Move Tool 移动工具
|
||||
Could not use the move tool because the layer is locked. 无法使用移动工具,因为图层已锁定。
|
||||
Could not complete the Free Transform command because the layer is locked. 无法完成“自由变换”命令,因为图层已锁定。
|
||||
Could not transform because the layer is locked. 无法变换,因为图层已锁定。
|
||||
Multiply 正片叠底
|
||||
My Brushes 我的画笔
|
||||
Neutrals 中性色
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 63 and column 53.
|
@@ -1882,6 +1882,7 @@
|
||||
Move Tool 移動工具
|
||||
Could not use the move tool because the layer is locked. 無法使用移動工具,因為圖層已鎖定。
|
||||
Could not complete the Free Transform command because the layer is locked. 無法完成「任意變形」指令,因為圖層已鎖定。
|
||||
Could not transform because the layer is locked. 無法變形,因為圖層已鎖定。
|
||||
Multiply 色彩增值
|
||||
My Brushes 我的筆刷
|
||||
Neutrals 中間色
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 47.
|
@@ -848,6 +848,9 @@ mod transform_reference_point_tests;
|
||||
#[cfg(test)]
|
||||
mod transform_several_tests;
|
||||
|
||||
#[cfg(test)]
|
||||
mod transform_selection_rules_tests;
|
||||
|
||||
#[cfg(test)]
|
||||
mod psd_open_tests;
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -127,8 +127,9 @@ pub fn disabled(app: &crate::PhotocraftApp, id: &str) -> bool {
|
||||
if transform && (target.is_object() || target == "quickMask") {
|
||||
return false;
|
||||
}
|
||||
// Several layers selected: one box moves them all, the Background left out (#6501). It doesn't
|
||||
// warp, and doesn't distort while editable type is among them.
|
||||
// Several layers selected: one box moves them all (#6501); with the Background among them it
|
||||
// refuses as locked when it opens (#11101). It doesn't warp, and doesn't distort while
|
||||
// editable type is among them.
|
||||
if transform {
|
||||
let targets = photocraft_engine::transform_cmds::selection_targets(st);
|
||||
// Judged on the layers the box moves, linked ones included (#11217): one linked layer
|
||||
|
||||
@@ -73,8 +73,8 @@ pub struct TransformSession {
|
||||
pub skew_v: f64,
|
||||
/// Free Transform of several selected layers (#6501): every layer the box moves, in
|
||||
/// `edit.transform`'s `layers` order. Empty when it moves `layer` alone. `layer` stays the
|
||||
/// active layer, which need not be one of them (the Background is left out, a selected group
|
||||
/// carries its selected children).
|
||||
/// active layer, which need not be one of them (a selected group carries its selected
|
||||
/// children).
|
||||
#[serde(default)]
|
||||
pub layers: Vec<u64>,
|
||||
}
|
||||
|
||||
@@ -228,6 +228,9 @@ struct Follow {
|
||||
revision: u64,
|
||||
/// The moving layers hidden in `doc` (nothing was lifted out of them).
|
||||
hidden: Vec<LayerId>,
|
||||
/// The Layers panel's selection when the box opened, sorted; `None` when the box doesn't
|
||||
/// follow it. Any change to it applies the box (`end_if_left`, #11101).
|
||||
selected: Option<Vec<LayerId>>,
|
||||
}
|
||||
|
||||
/// Photoshop's alert when Free Transform (⌘T, Edit › Transform, the Move tool's transform controls)
|
||||
|
||||
@@ -85,7 +85,7 @@ pub fn begin(app: &mut PhotocraftApp, ctx: &egui::Context) -> Result<(), String>
|
||||
opacity: layer.opacity * layer.fill_opacity,
|
||||
above: (!frame_only).then_some(above),
|
||||
styled: (styled && !frame_only).then(|| crate::transform_fx::StyledPreview::new(format!("transform-{session}-styled"))),
|
||||
follow: Some(Follow { revision, hidden: if lifted.is_none() && !frame_only { vec![id] } else { Vec::new() } }),
|
||||
follow: Some(Follow { revision, hidden: if lifted.is_none() && !frame_only { vec![id] } else { Vec::new() }, selected: selected_now(app) }),
|
||||
committing: None,
|
||||
gesture: None,
|
||||
warp_drag: None,
|
||||
@@ -136,7 +136,8 @@ pub(super) fn interpolation_preference(interpolation: &str) -> Option<photocraft
|
||||
/// layer move when there is a selection; type, shapes, smart objects and groups move whole.
|
||||
fn begin_several(app: &mut PhotocraftApp, ctx: &egui::Context, doc: Arc<Document>, revision: u64, active: LayerId, ids: Vec<LayerId>) -> Result<(), String> {
|
||||
use photocraft_engine::transform_cmds as tc;
|
||||
if ids.iter().any(|id| tc::locked_for_transform(&doc, *id)) {
|
||||
// The Background among them refuses the same way, rather than being left out (#11101).
|
||||
if tc::background_among(&doc, &ids).is_some() || ids.iter().any(|id| tc::locked_for_transform(&doc, *id)) {
|
||||
app.ui.open_dialog(crate::state::DialogKind::Error, json!({"message": tl!(LOCKED)}).as_object().cloned().unwrap_or_default());
|
||||
return Err(LOCKED.into());
|
||||
}
|
||||
@@ -192,7 +193,7 @@ fn begin_several(app: &mut PhotocraftApp, ctx: &egui::Context, doc: Arc<Document
|
||||
opacity: 1.0,
|
||||
above: Some(above),
|
||||
styled: None,
|
||||
follow: Some(Follow { revision, hidden }),
|
||||
follow: Some(Follow { revision, hidden, selected: selected_now(app) }),
|
||||
committing: None,
|
||||
gesture: None,
|
||||
warp_drag: None,
|
||||
@@ -240,6 +241,13 @@ pub(crate) fn moving_texel(app: &PhotocraftApp, p: [i32; 2]) -> (Option<Color32>
|
||||
(texel, texture_quad(t, pv))
|
||||
}
|
||||
|
||||
/// The Layers panel's selection, sorted, as a layer's box records it when it opens (#11101).
|
||||
pub(super) fn selected_now(app: &PhotocraftApp) -> Option<Vec<LayerId>> {
|
||||
let mut ids = app.session.active()?.selected_layers();
|
||||
ids.sort_unstable();
|
||||
Some(ids)
|
||||
}
|
||||
|
||||
/// The layers the box moves: every layer of a several-layer session (#6501), else its one layer.
|
||||
pub(crate) fn moving_layers(t: &TransformSession) -> Vec<LayerId> {
|
||||
if t.layers.is_empty() { vec![LayerId(t.layer)] } else { t.layers.iter().copied().map(LayerId).collect() }
|
||||
@@ -343,7 +351,7 @@ pub(crate) fn begin_content_aware(
|
||||
texture,
|
||||
opacity: layer.opacity * layer.fill_opacity,
|
||||
above: Some(above),
|
||||
follow: Some(Follow { revision, hidden: Vec::new() }),
|
||||
follow: Some(Follow { revision, hidden: Vec::new(), selected: None }),
|
||||
committing: None,
|
||||
gesture: None,
|
||||
warp_drag: None,
|
||||
|
||||
@@ -205,11 +205,30 @@ pub fn end_if_left(app: &mut PhotocraftApp) {
|
||||
}
|
||||
}
|
||||
|
||||
/// Another tool picked, or (outside Transform Selection) another layer selected, since the box
|
||||
/// opened: the session ends ([`end_if_left`], #1400).
|
||||
/// Another tool picked, or (outside Transform Selection) another layer selected or the selected
|
||||
/// layers changed, since the box opened: the session ends ([`end_if_left`], #1400, #11101).
|
||||
fn switched_away(app: &PhotocraftApp) -> bool {
|
||||
let (Some(t), Some(st)) = (&app.ui.transform, app.session.active()) else { return false };
|
||||
app.transform_preview.as_ref().is_some_and(|pv| pv.tool != app.ui.tool) || (!t.selection && st.active_layer.is_some_and(|active| active.0 != t.layer))
|
||||
let opened = app.transform_preview.as_ref().and_then(|pv| pv.follow.as_ref()).and_then(|f| f.selected.as_deref());
|
||||
app.transform_preview.as_ref().is_some_and(|pv| pv.tool != app.ui.tool)
|
||||
|| (!t.selection && st.active_layer.is_some_and(|active| active.0 != t.layer))
|
||||
|| selected_layers_changed(st, t, opened)
|
||||
}
|
||||
|
||||
/// A layer's box no longer matches the Layers panel's selection it opened with (`opened`): a layer
|
||||
/// was added to the selection or taken out of it, a group's child included. Photoshop applies the
|
||||
/// box first, to the layers it moves, and doesn't open it again (#11101). The selection itself is
|
||||
/// compared, not the layers the box moves: those also hold the layers linked to it (#11217) and
|
||||
/// leave out the children of a selected group. Boxes over a selection, a mask or channel, a path,
|
||||
/// a placed file or Content-Aware Scale don't follow the selection.
|
||||
fn selected_layers_changed(st: &photocraft_engine::DocState, t: &TransformSession, opened: Option<&[LayerId]>) -> bool {
|
||||
if t.selection || t.target.is_some() || t.path.is_some() || t.made == Some(MadeLayer::Place) || t.content_aware.is_some() {
|
||||
return false;
|
||||
}
|
||||
let Some(opened) = opened else { return false };
|
||||
let mut now = st.selected_layers();
|
||||
now.sort_unstable();
|
||||
now != opened
|
||||
}
|
||||
|
||||
/// Edits made while the box is open show in the preview at once, not only after the commit
|
||||
|
||||
@@ -0,0 +1,303 @@
|
||||
//! Free Transform's rules for the Layers panel's selection (#11101): the Background among several
|
||||
//! selected layers refuses with the locked-layer alert, and any change to the selected layers
|
||||
//! while the box is open applies the box first, as in Photoshop.
|
||||
|
||||
use photocraft_doc::LayerId;
|
||||
use photocraft_geom::Rect;
|
||||
use serde_json::json;
|
||||
|
||||
use crate::PhotocraftApp;
|
||||
use crate::transform_tool::LOCKED;
|
||||
|
||||
/// A 200 × 200 document on a white Background: "A" with a red block at x 10..50, y 10..30, and
|
||||
/// "B" with a blue one at x 100..140, y 100..120. Only B is selected.
|
||||
fn app() -> (PhotocraftApp, LayerId, LayerId, LayerId) {
|
||||
let mut app = PhotocraftApp::new(photocraft_engine::Session::new(), crate::Services::default());
|
||||
app.run("file.new", json!({"width": 200, "height": 200, "background": "white"})).unwrap();
|
||||
app.sync_views();
|
||||
let bg = app.session.active().unwrap().doc.layers[0].id;
|
||||
let mut block = |name: &str, rect: [i32; 4], color: &str| {
|
||||
let id = LayerId(app.run("layer.new.layer", json!({"name": name})).unwrap()["layer"].as_u64().unwrap());
|
||||
app.run("select.rect", json!({"x": rect[0], "y": rect[1], "width": rect[2], "height": rect[3]})).unwrap();
|
||||
app.run("edit.fill", json!({"contents": "color", "color": color})).unwrap();
|
||||
app.run("select.deselect", json!({})).unwrap();
|
||||
id
|
||||
};
|
||||
let a = block("A", [10, 10, 40, 20], "#ff0000");
|
||||
let b = block("B", [100, 100, 40, 20], "#0000ff");
|
||||
(app, bg, a, b)
|
||||
}
|
||||
|
||||
fn bounds(app: &PhotocraftApp, id: LayerId) -> Rect {
|
||||
let st = app.session.active().unwrap();
|
||||
photocraft_engine::transform_cmds::transform_bounds(&st.doc, st.doc.layer(id).unwrap())
|
||||
}
|
||||
|
||||
fn select(app: &mut PhotocraftApp, id: LayerId, mode: &str) {
|
||||
app.run("layer.select", json!({"layer": id.0, "mode": mode})).unwrap();
|
||||
}
|
||||
|
||||
fn invoke(app: &mut PhotocraftApp, id: &str) -> Result<(), String> {
|
||||
crate::menus::invoke(app, &egui::Context::default(), id, json!({})).map(|_| ())
|
||||
}
|
||||
|
||||
fn alerts(app: &PhotocraftApp) -> Vec<String> {
|
||||
app.ui.dialogs.iter().filter(|d| d.kind == crate::state::DialogKind::Error).filter_map(|d| d.fields.get("message")?.as_str().map(String::from)).collect()
|
||||
}
|
||||
|
||||
/// Drag the open box `dx` px right, with nearest neighbour so the blocks keep their edges.
|
||||
fn nudge(app: &mut PhotocraftApp, dx: f64) {
|
||||
let t = app.ui.transform.as_mut().expect("a box is open");
|
||||
t.interpolation = "nearest".into();
|
||||
t.quad = t.quad.map(|[x, y]| [x + dx, y]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_background_among_several_layers_shows_the_locked_alert_and_opens_no_box() {
|
||||
for id in ["edit.freeTransform", "edit.transform.scale", "edit.transform.rotate"] {
|
||||
let (mut app, bg, a, _) = app();
|
||||
select(&mut app, a, "replace");
|
||||
select(&mut app, bg, "add");
|
||||
let before = app.session.active().unwrap().doc.clone();
|
||||
assert_eq!(invoke(&mut app, id), Err(LOCKED.into()), "{id}");
|
||||
assert!(app.ui.transform.is_none(), "{id}: no box opens");
|
||||
assert_eq!(alerts(&app), [LOCKED], "{id}: the same alert as a position-locked layer");
|
||||
assert!(app.session.active().unwrap().doc == before, "{id}: nothing moved");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_background_is_refused_whichever_of_them_is_active() {
|
||||
let (mut app, bg, a, _) = app();
|
||||
select(&mut app, a, "replace");
|
||||
select(&mut app, bg, "add");
|
||||
select(&mut app, a, "add");
|
||||
assert_eq!(app.session.active().unwrap().active_layer, Some(a));
|
||||
assert_eq!(invoke(&mut app, "edit.freeTransform"), Err(LOCKED.into()));
|
||||
assert!(app.ui.transform.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_engines_default_targets_refuse_the_background_but_an_explicit_list_is_kept() {
|
||||
let (mut app, bg, a, b) = app();
|
||||
select(&mut app, a, "replace");
|
||||
select(&mut app, bg, "add");
|
||||
let err = app.run("edit.transform", json!({"matrix": [1, 0, 0, 1, 5, 0]})).unwrap_err();
|
||||
assert!(err.contains("locked"), "{err}");
|
||||
// A recorded or scripted step naming its layers moves those, as before.
|
||||
app.run("edit.transform", json!({"layers": [a.0, b.0], "matrix": [1, 0, 0, 1, 5, 0]})).unwrap();
|
||||
assert_eq!(bounds(&app, a), Rect::new(15, 10, 55, 30));
|
||||
assert_eq!(bounds(&app, b), Rect::new(105, 100, 145, 120));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn several_layers_without_the_background_still_open_one_box() {
|
||||
let (mut app, _, a, b) = app();
|
||||
select(&mut app, a, "add");
|
||||
invoke(&mut app, "edit.freeTransform").unwrap();
|
||||
let t = app.ui.transform.as_ref().unwrap();
|
||||
assert_eq!(t.layers, [a.0, b.0]);
|
||||
assert!(alerts(&app).is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn taking_a_layer_out_of_the_selection_applies_the_box_to_both_first() {
|
||||
// The reporter's case: the box over A and B (B active), then ⌘-click A in the Layers panel.
|
||||
let (mut app, _, a, b) = app();
|
||||
select(&mut app, a, "replace");
|
||||
select(&mut app, b, "add");
|
||||
assert_eq!(app.session.active().unwrap().active_layer, Some(b));
|
||||
invoke(&mut app, "edit.freeTransform").unwrap();
|
||||
let steps = app.session.active().unwrap().history.past_len();
|
||||
nudge(&mut app, 10.0);
|
||||
select(&mut app, a, "toggle");
|
||||
assert_eq!(app.session.active().unwrap().active_layer, Some(b), "the active layer didn't change");
|
||||
crate::transform_tool::end_if_left(&mut app);
|
||||
assert!(app.ui.transform.is_none(), "the box is applied and not opened again");
|
||||
assert_eq!(bounds(&app, a), Rect::new(20, 10, 60, 30), "A moved with the box");
|
||||
assert_eq!(bounds(&app, b), Rect::new(110, 100, 150, 120));
|
||||
let st = app.session.active().unwrap();
|
||||
assert_eq!(st.history.past_len(), steps + 1, "one Free Transform step");
|
||||
assert_eq!(st.selected_layers(), [b], "then A leaves the selection");
|
||||
// Nudging B now moves B alone: the old box is gone.
|
||||
invoke(&mut app, "edit.freeTransform").unwrap();
|
||||
assert!(app.ui.transform.as_ref().unwrap().layers.is_empty(), "a box over B alone");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn adding_a_layer_to_the_selection_applies_the_box_first() {
|
||||
let (mut app, _, a, b) = app();
|
||||
invoke(&mut app, "edit.freeTransform").unwrap();
|
||||
nudge(&mut app, 10.0);
|
||||
// ⌘-click A, then make B active again: the set changed though the active layer is B.
|
||||
select(&mut app, a, "add");
|
||||
select(&mut app, b, "add");
|
||||
assert_eq!(app.session.active().unwrap().active_layer, Some(b));
|
||||
crate::transform_tool::end_if_left(&mut app);
|
||||
assert!(app.ui.transform.is_none());
|
||||
assert_eq!(bounds(&app, b), Rect::new(110, 100, 150, 120), "B moved");
|
||||
assert_eq!(bounds(&app, a), Rect::new(10, 10, 50, 30), "A wasn't in the box");
|
||||
assert_eq!(app.session.active().unwrap().selected_layers(), [a, b]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn an_unchanged_selection_keeps_the_box_open() {
|
||||
let (mut app, _, a, b) = app();
|
||||
select(&mut app, a, "add");
|
||||
invoke(&mut app, "edit.freeTransform").unwrap();
|
||||
nudge(&mut app, 10.0);
|
||||
// Selecting what is selected already, or editing a moving layer, isn't a change.
|
||||
select(&mut app, a, "add");
|
||||
app.run("layer.setProps", json!({"layer": b.0, "opacity": 0.5})).unwrap();
|
||||
crate::transform_tool::follow_edits(&mut app);
|
||||
crate::transform_tool::end_if_left(&mut app);
|
||||
assert!(app.ui.transform.is_some(), "the box stays open");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_background_alone_keeps_todays_box() {
|
||||
let (mut app, bg, _, _) = app();
|
||||
select(&mut app, bg, "replace");
|
||||
let r = invoke(&mut app, "edit.freeTransform");
|
||||
assert_ne!(r, Err(LOCKED.into()), "not the several-layers alert");
|
||||
assert!(alerts(&app).is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_box_over_a_linked_layer_stays_open_while_the_selection_stays() {
|
||||
// The box over A also moves B, linked to it (#11217); the selection is still A alone, so
|
||||
// nothing changed and the box stays open.
|
||||
let (mut app, _, a, b) = app();
|
||||
select(&mut app, a, "add");
|
||||
app.run("layer.linkLayers", json!({})).unwrap();
|
||||
select(&mut app, a, "replace");
|
||||
invoke(&mut app, "edit.freeTransform").unwrap();
|
||||
assert_eq!(app.ui.transform.as_ref().unwrap().layers, [a.0, b.0], "the box moves the link group");
|
||||
nudge(&mut app, 10.0);
|
||||
crate::transform_tool::end_if_left(&mut app);
|
||||
assert!(app.ui.transform.is_some(), "the box stays open");
|
||||
// Adding B to the selection is a change, though the box already moves B.
|
||||
select(&mut app, b, "add");
|
||||
select(&mut app, a, "add");
|
||||
crate::transform_tool::end_if_left(&mut app);
|
||||
assert!(app.ui.transform.is_none(), "the box is applied");
|
||||
assert_eq!(bounds(&app, a), Rect::new(20, 10, 60, 30));
|
||||
assert_eq!(bounds(&app, b), Rect::new(110, 100, 150, 120));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn taking_a_child_of_a_selected_group_out_of_the_selection_applies_the_box() {
|
||||
// The box over group G moves its child A whether A is selected or not; ⌘-clicking A out of
|
||||
// the selection still changes the selection, so the box is applied first.
|
||||
let (mut app, _, a, b) = app();
|
||||
select(&mut app, a, "replace");
|
||||
let g = LayerId(app.run("layer.new.groupFromLayers", json!({"name": "G"})).unwrap()["layer"].as_u64().unwrap());
|
||||
select(&mut app, g, "replace");
|
||||
select(&mut app, a, "add");
|
||||
select(&mut app, b, "add");
|
||||
select(&mut app, g, "add");
|
||||
invoke(&mut app, "edit.freeTransform").unwrap();
|
||||
nudge(&mut app, 10.0);
|
||||
select(&mut app, a, "toggle");
|
||||
assert_eq!(app.session.active().unwrap().active_layer, Some(g));
|
||||
crate::transform_tool::end_if_left(&mut app);
|
||||
assert!(app.ui.transform.is_none(), "the box is applied and not opened again");
|
||||
assert_eq!(bounds(&app, a), Rect::new(20, 10, 60, 30), "A moved with its group");
|
||||
assert_eq!(bounds(&app, b), Rect::new(110, 100, 150, 120));
|
||||
}
|
||||
|
||||
/// An unselected layer "C" under the frame's bottom-right handle (x, y 180..200), so a press there
|
||||
/// that reached the Move tool's Auto-Select would select it.
|
||||
fn corner_layer(app: &mut PhotocraftApp) -> LayerId {
|
||||
let c = LayerId(app.run("layer.new.layer", json!({"name": "C"})).unwrap()["layer"].as_u64().unwrap());
|
||||
app.run("select.rect", json!({"x": 180, "y": 180, "width": 20, "height": 20})).unwrap();
|
||||
app.run("edit.fill", json!({"contents": "color", "color": "#00ff00"})).unwrap();
|
||||
app.run("select.deselect", json!({})).unwrap();
|
||||
c
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_refused_handle_press_on_the_canvas_reaches_no_move_tool() {
|
||||
// The Background among the selected layers: a press on the Move tool's transform handle shows
|
||||
// the locked-layer alert and goes nowhere else. Auto-Select (on) used to take it and select
|
||||
// C under the handle, a click and a drag alike.
|
||||
use egui::{Event, Modifiers, PointerButton, Pos2, vec2};
|
||||
for drag in [false, true] {
|
||||
let (mut app, bg, a, _) = app();
|
||||
corner_layer(&mut app);
|
||||
select(&mut app, a, "replace");
|
||||
select(&mut app, bg, "add");
|
||||
app.ui.tool = crate::state::Tool::Move;
|
||||
app.ui.tool_options.move_show_transform = true;
|
||||
app.ui.tool_options.move_auto_select = true;
|
||||
app.sync_views();
|
||||
let mut view = app.ui.views[0].clone();
|
||||
view.zoom = 1.0;
|
||||
view.center = [100.0, 100.0];
|
||||
view.fit_pending = false;
|
||||
view.fill_pending = false;
|
||||
let rect = egui::Rect::from_min_size(Pos2::ZERO, vec2(300.0, 300.0));
|
||||
let ctx = egui::Context::default();
|
||||
let mut frame = |app: &mut PhotocraftApp, events: Vec<Event>| {
|
||||
let input = egui::RawInput { screen_rect: Some(rect), events, ..Default::default() };
|
||||
let mut out = ctx.run_ui(input, |ui| {
|
||||
view = crate::canvas::canvas_view(app, ui, 0, rect, view.clone(), true);
|
||||
});
|
||||
out.textures_delta.clear();
|
||||
};
|
||||
let press = |pos: Pos2, pressed: bool| Event::PointerButton { pos, button: PointerButton::Primary, pressed, modifiers: Modifiers::NONE };
|
||||
// The frame holds the Background, the whole canvas: its bottom-right handle is at doc
|
||||
// (200, 200), screen (250, 250); the press is inside its grab area, over C.
|
||||
let at = Pos2::new(246.0, 246.0);
|
||||
let before = app.session.active().unwrap().doc.clone();
|
||||
let selected = app.session.active().unwrap().selected_layers();
|
||||
frame(&mut app, vec![Event::PointerMoved(at)]);
|
||||
frame(&mut app, vec![press(at, true)]);
|
||||
let mut end = at;
|
||||
if drag {
|
||||
for i in 1..=6 {
|
||||
end = at + vec2(-8.0 * i as f32, -8.0 * i as f32);
|
||||
frame(&mut app, vec![Event::PointerMoved(end)]);
|
||||
}
|
||||
}
|
||||
frame(&mut app, vec![press(end, false)]);
|
||||
frame(&mut app, vec![]);
|
||||
assert!(app.ui.transform.is_none(), "drag {drag}: no box opens");
|
||||
assert!(alerts(&app).iter().any(|m| m == "Could not transform because the layer is locked."), "drag {drag}: {:?}", alerts(&app));
|
||||
let st = app.session.active().unwrap();
|
||||
assert_eq!(st.selected_layers(), selected, "drag {drag}: the selection is unchanged");
|
||||
assert!(st.doc == before, "drag {drag}: the document is unchanged");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_refused_handle_press_over_the_control_channel_reaches_no_move_tool() {
|
||||
// `ui.pointer` takes the same path: the alert, and the press is not forwarded to the Move tool.
|
||||
use crate::control::{ControlRequest, Outcome};
|
||||
let (mut app, bg, a, _) = app();
|
||||
corner_layer(&mut app);
|
||||
select(&mut app, a, "replace");
|
||||
select(&mut app, bg, "add");
|
||||
app.sync_views();
|
||||
app.last_canvas_rect = egui::Rect::from_min_size(egui::Pos2::ZERO, egui::vec2(300.0, 300.0));
|
||||
app.ui.tool_options.move_auto_select = true;
|
||||
let ctx = egui::Context::default();
|
||||
let call = |app: &mut PhotocraftApp, method: &str, params: serde_json::Value| {
|
||||
let (request, _receiver) = ControlRequest::new(method, params);
|
||||
let Outcome::Done(reply) = crate::control::handle(app, &ctx, &request) else { panic!("expected an immediate {method} reply") };
|
||||
assert_eq!(reply["ok"], true, "{method}: {reply}");
|
||||
};
|
||||
call(&mut app, "ui.set", json!({"tool": "Move", "moveShowTransform": true, "zoom": 1.0, "center": [100, 100]}));
|
||||
let before = app.session.active().unwrap().doc.clone();
|
||||
let selected = app.session.active().unwrap().selected_layers();
|
||||
call(
|
||||
&mut app,
|
||||
"ui.pointer",
|
||||
json!({"events": [{"kind": "down", "x": 196, "y": 196}, {"kind": "move", "x": 180, "y": 180}, {"kind": "up", "x": 180, "y": 180}]}),
|
||||
);
|
||||
assert!(app.ui.transform.is_none(), "no box opens");
|
||||
assert!(alerts(&app).iter().any(|m| m == "Could not transform because the layer is locked."), "{:?}", alerts(&app));
|
||||
let st = app.session.active().unwrap();
|
||||
assert_eq!(st.selected_layers(), selected, "the selection is unchanged");
|
||||
assert!(st.doc == before, "the document is unchanged");
|
||||
}
|
||||
@@ -153,26 +153,16 @@ fn a_locked_layer_among_them_refuses_before_the_box_opens() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn with_the_background_active_the_box_moves_the_other_selected_layers() {
|
||||
let (mut app, paint, text) = app();
|
||||
fn with_the_background_active_among_them_the_box_refuses_as_locked() {
|
||||
// Photoshop's locked-layer alert, not a box that leaves the Background out (#11101).
|
||||
let (mut app, _, _) = app();
|
||||
let bg = app.session.active().unwrap().doc.layers[0].id;
|
||||
app.run("layer.select", json!({"layer": bg.0, "mode": "add"})).unwrap();
|
||||
assert_eq!(app.session.active().unwrap().active_layer, Some(bg));
|
||||
assert!(crate::menus::is_enabled(&app, "edit.freeTransform"), "not greyed for the Background");
|
||||
free_transform(&mut app);
|
||||
let t = app.ui.transform.as_ref().unwrap();
|
||||
assert_eq!((t.layer, t.layers.clone()), (bg.0, vec![paint.0, text.0]));
|
||||
let p0 = bounds(&app, paint);
|
||||
if let Some(t) = app.ui.transform.as_mut() {
|
||||
// Nearest neighbour: no resampling fringe at the block's edge.
|
||||
t.interpolation = "nearest".into();
|
||||
t.quad = t.quad.map(|[x, y]| [x + 5.0, y]);
|
||||
}
|
||||
crate::transform_tool::commit(&mut app);
|
||||
assert_eq!(app.ui.status, "");
|
||||
let st = app.session.active().unwrap();
|
||||
assert!(st.doc.is_background(bg), "the Background is left alone");
|
||||
assert_eq!(bounds(&app, paint), p0.translate(5, 0));
|
||||
assert!(crate::menus::is_enabled(&app, "edit.freeTransform"), "not greyed: choosing it shows the alert");
|
||||
let r = crate::menus::invoke(&mut app, &egui::Context::default(), "edit.freeTransform", json!({}));
|
||||
assert_eq!(r, Err(crate::transform_tool::LOCKED.into()));
|
||||
assert!(app.ui.transform.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
Reference in new issue
Block a user