mirror of
https://github.com/storytold/designcraft.git
synced 2026-11-14 22:35:26 +00:00
fix(transform): rotate and flip turn about the chosen reference point
This commit is contained in:
1 parent
258fd2d7c9
commit
6477435e81
1 file changed
+33
-14
@@ -56,7 +56,7 @@ pub fn specs() -> Vec<CommandSpec> {
|
||||
has_selection,
|
||||
transform_resize
|
||||
),
|
||||
cmd!("transform.rotate", "Rotate", ["Object", "Transform"], None, "{angle (degrees, CCW), ids?}", has_selection, transform_rotate),
|
||||
cmd!("transform.rotate", "Rotate", ["Object", "Transform"], None, "{angle (degrees, CCW), ref?: 0..8 (reference point of the selection bounds, default 4: centre), ids?}", has_selection, transform_rotate),
|
||||
cmd!("transform.scale", "Scale", ["Object", "Transform"], None, "{sx, sy, ids?} (about the selection centre)", has_selection, |s, p| {
|
||||
let sx = f64_or(p, "sx", 1.0);
|
||||
let sy = f64_or(p, "sy", sx);
|
||||
@@ -94,9 +94,9 @@ pub fn specs() -> Vec<CommandSpec> {
|
||||
has_selection,
|
||||
clear_transformations
|
||||
),
|
||||
cmd!("transform.shear", "Shear", ["Object", "Transform"], None, "{angle (degrees), ids?}", has_selection, |s, p| {
|
||||
cmd!("transform.shear", "Shear", ["Object", "Transform"], None, "{angle (degrees), ref?: 0..8 (reference point of the selection bounds, default 4: centre), ids?}", has_selection, |s, p| {
|
||||
let a = f64_or(p, "angle", 0.0).clamp(-85.0, 85.0).to_radians().tan();
|
||||
apply_about_center(s, p, Affine::new([1.0, 0.0, a, 1.0, 0.0, 0.0]))
|
||||
apply_about_ref(s, p, Affine::new([1.0, 0.0, a, 1.0, 0.0, 0.0]))
|
||||
}),
|
||||
cmd!(
|
||||
"object.matchAttributes",
|
||||
@@ -107,9 +107,9 @@ pub fn specs() -> Vec<CommandSpec> {
|
||||
has_selection,
|
||||
match_attributes
|
||||
),
|
||||
cmd!("transform.flip", "Flip", ["Object", "Transform"], None, "{axis: horizontal|vertical, ids?}", has_selection, |s, p| {
|
||||
cmd!("transform.flip", "Flip", ["Object", "Transform"], None, "{axis: horizontal|vertical, ref?: 0..8 (reference point of the selection bounds, default 4: centre), ids?}", has_selection, |s, p| {
|
||||
let h = str_param(p, "axis") != Some("vertical");
|
||||
apply_about_center(s, p, if h { Affine::scale_non_uniform(-1.0, 1.0) } else { Affine::scale_non_uniform(1.0, -1.0) })
|
||||
apply_about_ref(s, p, if h { Affine::scale_non_uniform(-1.0, 1.0) } else { Affine::scale_non_uniform(1.0, -1.0) })
|
||||
}),
|
||||
cmd!(
|
||||
"transform.set",
|
||||
@@ -1233,16 +1233,12 @@ fn clear_transformations(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
})
|
||||
}
|
||||
|
||||
fn apply_about_center(s: &mut Session, p: &Value, a: Affine) -> Result<Value> {
|
||||
/// Apply `a` to the targets about the `ref` point (0..8, default the centre) of their combined bounds.
|
||||
fn apply_about_ref(s: &mut Session, p: &Value, a: Affine) -> Result<Value> {
|
||||
let ids = targets(s, p)?;
|
||||
let rf = p.get("ref").and_then(Value::as_u64).unwrap_or(4).min(8) as usize;
|
||||
s.edit(|d, _| {
|
||||
let mut b: Option<Rect> = None;
|
||||
for id in &ids {
|
||||
if let Some(it) = d.item(*id) {
|
||||
b = Some(b.map_or(it.bounds(), |r| r.union(it.bounds())));
|
||||
}
|
||||
}
|
||||
let c = b.unwrap_or(Rect::ZERO).center();
|
||||
let c = designcraft_geom::reference_point(union_bounds(d, &ids), rf);
|
||||
let m = Affine::translate(c.to_vec2()) * a * Affine::translate(-c.to_vec2());
|
||||
for id in &ids {
|
||||
if let Some(it) = d.item_mut(*id) {
|
||||
@@ -1255,7 +1251,7 @@ fn apply_about_center(s: &mut Session, p: &Value, a: Affine) -> Result<Value> {
|
||||
|
||||
fn transform_rotate(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
let a = f64_or(p, "angle", 0.0);
|
||||
apply_about_center(s, p, Affine::rotate(-a.to_radians()))
|
||||
apply_about_ref(s, p, Affine::rotate(-a.to_radians()))
|
||||
}
|
||||
|
||||
/// The transform an item's panel values are measured from: its own (relative to its container)
|
||||
@@ -3527,6 +3523,29 @@ mod gradient_tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod reference_point_tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn rotate_and_flip_turn_about_the_reference_point() {
|
||||
let mut s = Session::new();
|
||||
s.execute("file.new", &json!({})).unwrap();
|
||||
let id = ItemId(s.execute("frame.create", &json!({"rect": [100, 100, 200, 150]})).unwrap()["id"].as_u64().unwrap());
|
||||
let item = |s: &Session| s.doc().unwrap().doc.item(id).unwrap().clone();
|
||||
// 90° counterclockwise about the top-left: that corner stays, the frame swings up.
|
||||
s.execute("transform.rotate", &json!({"angle": 90, "ref": 0, "ids": [id.0]})).unwrap();
|
||||
let it = item(&s);
|
||||
assert!((it.xf * Point::new(100.0, 100.0) - Point::new(100.0, 100.0)).hypot() < 1e-9, "{:?}", it.xf);
|
||||
let b = it.bounds();
|
||||
assert!((b.x0 - 100.0).abs() < 1e-9 && (b.y0 - 0.0).abs() < 1e-9 && (b.x1 - 150.0).abs() < 1e-9 && (b.y1 - 100.0).abs() < 1e-9, "{b:?}");
|
||||
// Flip horizontally about the right edge's middle: the frame lands to the right of it.
|
||||
s.execute("transform.flip", &json!({"axis": "horizontal", "ref": 5, "ids": [id.0]})).unwrap();
|
||||
let b = item(&s).bounds();
|
||||
assert!((b.x0 - 150.0).abs() < 1e-9 && (b.x1 - 200.0).abs() < 1e-9 && (b.y0 - 0.0).abs() < 1e-9, "{b:?}");
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod again_tests {
|
||||
use super::*;
|
||||
|
||||
Reference in new issue
Block a user