mirror of
https://github.com/storytold/figcraft.git
synced 2026-10-26 19:36:46 -06:00
This commit is contained in:
@@ -145,21 +145,28 @@ pub fn rotate_world(doc: &mut Document, id: NodeId, pivot: Point, deg: f64) -> R
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Whether aligning `ids` would move anything: a layer alone on the page has nothing to align to.
|
||||
pub fn can_align(doc: &Document, ids: &[NodeId]) -> bool {
|
||||
align_target(doc, &roots(doc, ids)).is_some()
|
||||
}
|
||||
|
||||
/// What `roots` align to: one layer to its parent frame, several to their combined bounds.
|
||||
fn align_target(doc: &Document, roots: &[NodeId]) -> Option<Rect> {
|
||||
match roots {
|
||||
[] => None,
|
||||
[id] => {
|
||||
let parent = doc.node(*id)?.parent.filter(|pp| !doc.node(*pp).is_some_and(Node::is_page))?;
|
||||
Some(doc.world_bounds(parent).unwrap_or_default())
|
||||
}
|
||||
_ => Some(doc.bounds_of(roots).unwrap_or_default()),
|
||||
}
|
||||
}
|
||||
|
||||
fn align(s: &mut Session, p: &Value, a: Al) -> Result<Value> {
|
||||
let ids = req_targets("align", s, p)?;
|
||||
let doc = s.doc_mut()?;
|
||||
let ids = roots(doc, &ids);
|
||||
// One layer aligns within its parent frame; several align to their combined bounds.
|
||||
let target: Rect = if ids.len() == 1 {
|
||||
let Some(id) = ids.first() else { return ok() };
|
||||
let parent = doc.get(*id)?.parent;
|
||||
match parent.filter(|pp| !doc.node(*pp).is_some_and(Node::is_page)) {
|
||||
Some(pp) => doc.world_bounds(pp).unwrap_or_default(),
|
||||
None => return ok(),
|
||||
}
|
||||
} else {
|
||||
doc.bounds_of(&ids).unwrap_or_default()
|
||||
};
|
||||
let Some(target) = align_target(doc, &ids) else { return ok() };
|
||||
for i in ids {
|
||||
let Some(b) = doc.world_bounds(i) else { continue };
|
||||
let (dx, dy) = match a {
|
||||
|
||||
@@ -253,6 +253,27 @@ fn align_distribute_zorder() {
|
||||
assert_eq!(s.doc().unwrap().node(page).unwrap().children[0], c);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_layer_alone_on_the_page_has_nothing_to_align_to() {
|
||||
use crate::cmd::arrange::can_align;
|
||||
let mut s = sess();
|
||||
let f = id(&s.execute("frame.create", &json!({"x": 0, "y": 0, "width": 200, "height": 100})).unwrap());
|
||||
let inner = id(&s.execute("shape.rectangle", &json!({"x": 20, "y": 30, "width": 10, "height": 10, "parent": f})).unwrap());
|
||||
let loose = id(&s.execute("shape.rectangle", &json!({"x": 300, "y": 5, "width": 10, "height": 10})).unwrap());
|
||||
let doc = s.doc().unwrap();
|
||||
assert!(!can_align(doc, &[]));
|
||||
assert!(!can_align(doc, &[loose]), "alone on the page");
|
||||
assert!(!can_align(doc, &[f, inner]), "a frame with its own child is the frame alone");
|
||||
assert!(can_align(doc, &[inner]), "one in a frame aligns to it");
|
||||
assert!(can_align(doc, &[f, loose]), "two align to each other");
|
||||
// Where the buttons are off, the command still does nothing (and no harm).
|
||||
let before = doc.world_bounds(loose);
|
||||
s.execute("align.right", &json!({"ids": [loose]})).unwrap();
|
||||
assert_eq!(s.doc().unwrap().world_bounds(loose), before);
|
||||
s.execute("align.right", &json!({"ids": [inner]})).unwrap();
|
||||
assert_eq!(s.doc().unwrap().world_bounds(inner).unwrap().x1, 200.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn copy_paste_and_duplicate() {
|
||||
let mut s = sess();
|
||||
|
||||
@@ -17,6 +17,7 @@ status = "draft — needs native review"
|
||||
"1…9, 0" = "1…9, 0"
|
||||
"@{login}: {prs} PRs, {commits} commits, +{added} / −{deleted} lines (Δ {delta}), +{bin_added} / −{bin_deleted} binary assets, {first} – {last}" = "@{login}: {prs} PRs, {commits} Commits, +{added} / −{deleted} Zeilen (Δ {delta}), +{bin_added} / −{bin_deleted} Binärdateien, {first} – {last}"
|
||||
"A collection of this file with these modes and variables: override their values per mode and add variables of its own" = "Eine Sammlung dieser Datei mit diesen Modi und Variablen: Überschreiben Sie ihre Werte je Modus und fügen Sie eigene Variablen hinzu"
|
||||
"A layer on its own aligns to its frame: select more layers, or one in a frame" = "Eine einzelne Ebene richtet sich an ihrem Rahmen aus: Wählen Sie weitere Ebenen oder eine Ebene in einem Rahmen"
|
||||
"A spring plays until it settles: its mass, stiffness and damping set how long" = "Eine Feder läuft, bis sie zur Ruhe kommt: Masse, Steifigkeit und Dämpfung bestimmen die Dauer"
|
||||
"About" = "Über"
|
||||
"About FigCraft" = "Über FigCraft"
|
||||
|
||||
@@ -16,6 +16,7 @@ name = "English"
|
||||
"1…9, 0" = "1…9, 0"
|
||||
"@{login}: {prs} PRs, {commits} commits, +{added} / −{deleted} lines (Δ {delta}), +{bin_added} / −{bin_deleted} binary assets, {first} – {last}" = "@{login}: {prs} PRs, {commits} commits, +{added} / −{deleted} lines (Δ {delta}), +{bin_added} / −{bin_deleted} binary assets, {first} – {last}"
|
||||
"A collection of this file with these modes and variables: override their values per mode and add variables of its own" = "A collection of this file with these modes and variables: override their values per mode and add variables of its own"
|
||||
"A layer on its own aligns to its frame: select more layers, or one in a frame" = "A layer on its own aligns to its frame: select more layers, or one in a frame"
|
||||
"A responsive product page: hero, features, figures, a quote and a footer" = "A responsive product page: hero, features, figures, a quote and a footer"
|
||||
"A spring plays until it settles: its mass, stiffness and damping set how long" = "A spring plays until it settles: its mass, stiffness and damping set how long"
|
||||
"A title, an agenda, a big figure, a chart and a closing slide" = "A title, an agenda, a big figure, a chart and a closing slide"
|
||||
|
||||
@@ -17,6 +17,7 @@ status = "draft — needs native review"
|
||||
"1…9, 0" = "1…9, 0"
|
||||
"@{login}: {prs} PRs, {commits} commits, +{added} / −{deleted} lines (Δ {delta}), +{bin_added} / −{bin_deleted} binary assets, {first} – {last}" = "@{login}: {prs} PR, {commits} commits, +{added} / −{deleted} líneas (Δ {delta}), +{bin_added} / −{bin_deleted} recursos binarios, {first} – {last}"
|
||||
"A collection of this file with these modes and variables: override their values per mode and add variables of its own" = "Una colección de este archivo con estos modos y variables: modifica sus valores por modo y añade variables propias"
|
||||
"A layer on its own aligns to its frame: select more layers, or one in a frame" = "Una capa sola se alinea con su marco: selecciona más capas o una dentro de un marco"
|
||||
"A responsive product page: hero, features, figures, a quote and a footer" = "Una página de producto adaptable: cabecera, funciones, cifras, una cita y un pie"
|
||||
"A spring plays until it settles: its mass, stiffness and damping set how long" = "Un muelle se reproduce hasta que se asienta: su masa, rigidez y amortiguación fijan cuánto dura"
|
||||
"A title, an agenda, a big figure, a chart and a closing slide" = "Un título, una agenda, una gran cifra, un gráfico y una diapositiva de cierre"
|
||||
|
||||
@@ -17,6 +17,7 @@ status = "draft — needs native review"
|
||||
"1…9, 0" = "1…9, 0"
|
||||
"@{login}: {prs} PRs, {commits} commits, +{added} / −{deleted} lines (Δ {delta}), +{bin_added} / −{bin_deleted} binary assets, {first} – {last}" = "@{login} : {prs} PR, {commits} commits, +{added} / −{deleted} lignes (Δ {delta}), +{bin_added} / −{bin_deleted} ressources binaires, {first} – {last}"
|
||||
"A collection of this file with these modes and variables: override their values per mode and add variables of its own" = "Une collection de ce fichier avec ces modes et ces variables : remplacez leurs valeurs par mode et ajoutez vos propres variables"
|
||||
"A layer on its own aligns to its frame: select more layers, or one in a frame" = "Un calque seul s’aligne sur son cadre : sélectionnez d’autres calques, ou un calque dans un cadre"
|
||||
"A spring plays until it settles: its mass, stiffness and damping set how long" = "Un ressort joue jusqu’à ce qu’il se stabilise : sa masse, sa rigidité et son amortissement en fixent la durée"
|
||||
"About" = "À propos"
|
||||
"About FigCraft" = "À propos de FigCraft"
|
||||
|
||||
@@ -19,6 +19,7 @@ status = "draft — needs native review"
|
||||
"1…9, 0" = "1…9, 0"
|
||||
"@{login}: {prs} PRs, {commits} commits, +{added} / −{deleted} lines (Δ {delta}), +{bin_added} / −{bin_deleted} binary assets, {first} – {last}" = "@{login}:PR {prs} 件、コミット {commits} 件、+{added} / −{deleted} 行(Δ {delta})、バイナリアセット +{bin_added} / −{bin_deleted}、{first} – {last}"
|
||||
"A collection of this file with these modes and variables: override their values per mode and add variables of its own" = "このファイルのコレクションとそのモード・変数:モードごとに値を上書きし、独自の変数を追加できる"
|
||||
"A layer on its own aligns to its frame: select more layers, or one in a frame" = "単独のレイヤーはフレームに整列します:レイヤーを追加で選択するか、フレーム内のレイヤーを選択"
|
||||
"A spring plays until it settles: its mass, stiffness and damping set how long" = "スプリングは静止するまで再生。長さは質量・剛性・減衰で決まる"
|
||||
"About" = "情報"
|
||||
"About FigCraft" = "FigCraft について"
|
||||
|
||||
@@ -18,6 +18,7 @@ status = "draft — needs native review"
|
||||
"1…9, 0" = "1…9, 0"
|
||||
"@{login}: {prs} PRs, {commits} commits, +{added} / −{deleted} lines (Δ {delta}), +{bin_added} / −{bin_deleted} binary assets, {first} – {last}" = "@{login}: PR {prs}개, 커밋 {commits}개, +{added} / −{deleted}줄 (Δ {delta}), 바이너리 에셋 +{bin_added} / −{bin_deleted}, {first} – {last}"
|
||||
"A collection of this file with these modes and variables: override their values per mode and add variables of its own" = "이 파일의 컬렉션과 그 모드 및 변수: 모드별로 값을 재정의하고 자체 변수를 추가할 수 있음"
|
||||
"A layer on its own aligns to its frame: select more layers, or one in a frame" = "레이어 하나는 프레임에 맞춰 정렬됩니다: 레이어를 더 선택하거나 프레임 안의 레이어를 선택하세요"
|
||||
"A spring plays until it settles: its mass, stiffness and damping set how long" = "스프링은 멈출 때까지 재생됨. 길이는 질량, 강성, 감쇠로 결정됨"
|
||||
"About" = "정보"
|
||||
"About FigCraft" = "FigCraft 정보"
|
||||
|
||||
@@ -16,6 +16,7 @@ status = "draft — needs native review"
|
||||
"1…9, 0" = "1…9, 0"
|
||||
"@{login}: {prs} PRs, {commits} commits, +{added} / −{deleted} lines (Δ {delta}), +{bin_added} / −{bin_deleted} binary assets, {first} – {last}" = "@{login}: {prs} PRs, {commits} commits, +{added} / −{deleted} linhas (Δ {delta}), +{bin_added} / −{bin_deleted} arquivos binários, {first} – {last}"
|
||||
"A collection of this file with these modes and variables: override their values per mode and add variables of its own" = "Uma coleção deste arquivo com estes modos e variáveis: substitua os valores por modo e adicione variáveis próprias"
|
||||
"A layer on its own aligns to its frame: select more layers, or one in a frame" = "Uma camada sozinha se alinha ao seu quadro: selecione mais camadas ou uma dentro de um quadro"
|
||||
"A spring plays until it settles: its mass, stiffness and damping set how long" = "Uma mola se move até se estabilizar: sua massa, rigidez e amortecimento definem a duração"
|
||||
"About" = "Sobre"
|
||||
"About FigCraft" = "Sobre o FigCraft"
|
||||
|
||||
@@ -19,6 +19,7 @@ status = "draft — needs native review"
|
||||
"1…9, 0" = "1…9, 0"
|
||||
"@{login}: {prs} PRs, {commits} commits, +{added} / −{deleted} lines (Δ {delta}), +{bin_added} / −{bin_deleted} binary assets, {first} – {last}" = "@{login}:{prs} 个 PR,{commits} 次提交,+{added} / −{deleted} 行(Δ {delta}),二进制资源 +{bin_added} / −{bin_deleted},{first} – {last}"
|
||||
"A collection of this file with these modes and variables: override their values per mode and add variables of its own" = "本文件中带有这些模式和变量的集合:可按模式覆盖其值,并添加自己的变量"
|
||||
"A layer on its own aligns to its frame: select more layers, or one in a frame" = "单个图层与其所在画框对齐:请再选择其他图层,或选择画框内的图层"
|
||||
"A spring plays until it settles: its mass, stiffness and damping set how long" = "弹簧动画播放至静止,时长由质量、刚度和阻尼决定"
|
||||
"About" = "关于"
|
||||
"About FigCraft" = "关于 FigCraft"
|
||||
|
||||
@@ -388,3 +388,18 @@ fn every_accessibility_path_runs_with_the_tree_on_without_nested_context_locks()
|
||||
let out = u.frame_output(vec![]);
|
||||
assert!(out.platform_output.accesskit_update.is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_align_buttons_are_off_for_a_layer_alone_on_the_page() {
|
||||
let (mut u, a, b) = setup();
|
||||
let align_left = |nodes: &[Node]| {
|
||||
nodes.iter().find(|n| n.label().is_some_and(|l| l.starts_with("Align left"))).map(Node::is_disabled).expect("the Align left button")
|
||||
};
|
||||
assert!(align_left(&tree(&mut u)), "Rect A alone on the page: nothing to align to");
|
||||
u.app.run("select.set", json!({"ids": [a, b]}));
|
||||
assert!(!align_left(&tree(&mut u)), "two layers align to each other");
|
||||
let f = id(u.app.session.execute("frame.create", &json!({"x": 300, "y": 0, "width": 100, "height": 100})).unwrap());
|
||||
let inner = id(u.app.session.execute("shape.rectangle", &json!({"x": 10, "y": 10, "width": 10, "height": 10, "parent": f})).unwrap());
|
||||
u.app.run("select.set", json!({"ids": [inner]}));
|
||||
assert!(!align_left(&tree(&mut u)), "one in a frame aligns to it");
|
||||
}
|
||||
|
||||
@@ -599,12 +599,15 @@ fn position_fields(app: &mut FigApp, ui: &mut Ui, doc: &Document, sel: &[NodeId]
|
||||
(Icon::AlignBottom, tip(tr("Align bottom"), cmds[5])),
|
||||
];
|
||||
let w = col / 3.0;
|
||||
if let Some(i) = widgets::segmented(ui, &items[..3], None, w) {
|
||||
let picked = ui.add_enabled_ui(figcraft_engine::cmd::arrange::can_align(doc, sel), |ui| {
|
||||
let h = widgets::segmented(ui, &items[..3], None, w);
|
||||
let v = widgets::segmented(ui, &items[3..], None, w).map(|i| i + 3);
|
||||
h.or(v)
|
||||
});
|
||||
picked.response.on_disabled_hover_text(tr("A layer on its own aligns to its frame: select more layers, or one in a frame"));
|
||||
if let Some(i) = picked.inner {
|
||||
app.run(cmds[i], json!({"ids": ids}));
|
||||
}
|
||||
if let Some(i) = widgets::segmented(ui, &items[3..], None, w) {
|
||||
app.run(cmds[i + 3], json!({"ids": ids}));
|
||||
}
|
||||
if sel.len() > 2 {
|
||||
let more = icons::button(ui, Icon::DistributeH, 24.0, false, tr("Distribute and tidy"));
|
||||
menu_popup(&more).show(|ui| {
|
||||
|
||||
Reference in New Issue
Block a user