mirror of
https://github.com/storytold/designcraft.git
synced 2026-11-10 04:22:34 +00:00
Merge #575: Effects panel: the level row, Isolate Blending and Knockout Group
Conflict: crates/ui-egui/src/i18n.rs: kept next's Step and Repeat rows and the PR's Effects rows Conflict: crates/ui-egui/src/i18n/ar.rs, it.rs, ja.rs, pt_br.rs, uk.rs: kept both sides' rows Conflict: ROADMAP.md: kept both progress-log lines
This commit is contained in:
commit
1f69461ff5
8 files changed
+111
No files matched your search
@@ -128,6 +128,7 @@ list) · [roadmap.md](docs/roadmap.md) (milestones, plan) · [parity-checklist.m
|
||||
|
||||
Newest first.
|
||||
|
||||
- **2026-10-12** · The Effects panel shows the level its settings apply to ("Group: Color Dodge 100%") and, as in InDesign, Isolate Blending and Knockout Group for a selected group. No readiness percentage change.
|
||||
- **2026-10-12** · Edit › Step and Repeat has its InDesign dialog: Repeat (Count, or Rows and Columns with Create as a grid), Offset (Vertical, Horizontal, in ruler units) and Preview, which shows the copies while the dialog is open; Cancel takes them away and OK makes them as one undo step. No readiness percentage change.
|
||||
- **2026-10-12** · Load Styles: the Paragraph Styles and Character Styles panels load styles from another DesignCraft document or an IDML file, with the styles, swatches and lists they name; a dialog picks the styles and what happens to one the document defines differently (`style.load`). No readiness percentage change.
|
||||
- **2026-10-12** · On screen, blend modes blend with the objects below and not with the paper, as in InDesign and in PDF export: a Color Dodge or Screen object over blank paper shows as it is. No readiness percentage change.
|
||||
|
||||
@@ -1100,6 +1100,10 @@ const TABLE: &[(&str, [&str; 5])] = &[
|
||||
("Vertical:", ["Vertikal:", "Vertical :", "Vertical:", "垂直方向:", "垂直:"]),
|
||||
("Horizontal:", ["Horizontal:", "Horizontal :", "Horizontal:", "水平方向:", "水平:"]),
|
||||
("Invalid value", ["Ungültiger Wert", "Valeur non valide", "Valor no válido", "無効な値", "无效值"]),
|
||||
("Isolate Blending", ["Füllmethode isolieren", "Isoler le mode de fusion", "Aislar fusión", "描画モードを分離", "分离混合"]),
|
||||
("Knockout Group", ["Aussparungsgruppe", "Groupe de masquage", "Grupo de exclusión", "グループの抜き", "挖空组"]),
|
||||
("Object:", ["Objekt:", "Objet :", "Objeto:", "オブジェクト:", "对象:"]),
|
||||
("Group:", ["Gruppe:", "Groupe :", "Grupo:", "グループ:", "编组:"]),
|
||||
("Apply Condition", ["Apply Condition", "Apply Condition", "Apply Condition", "Apply Condition", "应用条件"]),
|
||||
(
|
||||
"Apply Parent to Pages…",
|
||||
|
||||
@@ -1068,6 +1068,10 @@ pub(super) const TABLE: &[(&str, &str)] = &[
|
||||
("Vertical:", "رأسي:"),
|
||||
("Horizontal:", "أفقي:"),
|
||||
("Invalid value", "قيمة غير صالحة"),
|
||||
("Isolate Blending", "عزل المزج"),
|
||||
("Knockout Group", "مجموعة مفرغة"),
|
||||
("Object:", "الكائن:"),
|
||||
("Group:", "المجموعة:"),
|
||||
("Apply None", "إزالة اللون"),
|
||||
("Height Only", "الارتفاع فقط"),
|
||||
("Width Only", "العرض فقط"),
|
||||
|
||||
@@ -422,6 +422,10 @@ pub(super) const TABLE: &[(&str, &str)] = &[
|
||||
("Vertical:", "Verticale:"),
|
||||
("Horizontal:", "Orizzontale:"),
|
||||
("Invalid value", "Valore non valido"),
|
||||
("Isolate Blending", "Isola fusione"),
|
||||
("Knockout Group", "Gruppo di foratura"),
|
||||
("Object:", "Oggetto:"),
|
||||
("Group:", "Gruppo:"),
|
||||
("Apply Condition", "Applica condizione"),
|
||||
("Apply Parent to Pages…", "Applica pagina mastro alle pagine…"),
|
||||
("ArtCraft App Page…", "Pagina dell'app ArtCraft…"),
|
||||
|
||||
@@ -537,6 +537,10 @@ pub(super) const TABLE: &[(&str, &str)] = &[
|
||||
("Create as a grid", "グリッドとして作成"),
|
||||
("Rows:", "行:"),
|
||||
("Invalid value", "無効な値"),
|
||||
("Isolate Blending", "描画モードを分離"),
|
||||
("Knockout Group", "グループの抜き"),
|
||||
("Object:", "オブジェクト:"),
|
||||
("Group:", "グループ:"),
|
||||
("Apply Condition", "条件を適用"),
|
||||
("Apply Parent to Pages…", "ページに親ページを適用…"),
|
||||
("ArtCraft App Page…", "ArtCraft アプリページ…"),
|
||||
|
||||
@@ -1065,6 +1065,10 @@ pub(super) const TABLE: &[(&str, &str)] = &[
|
||||
("Vertical:", "Vertical:"),
|
||||
("Horizontal:", "Horizontal:"),
|
||||
("Invalid value", "Valor inválido"),
|
||||
("Isolate Blending", "Isolar mesclagem"),
|
||||
("Knockout Group", "Grupo de vazamento"),
|
||||
("Object:", "Objeto:"),
|
||||
("Group:", "Grupo:"),
|
||||
("Apply None", "Aplicar nenhum"),
|
||||
("Height Only", "Somente altura"),
|
||||
("Width Only", "Somente largura"),
|
||||
|
||||
@@ -423,6 +423,10 @@ pub(super) const TABLE: &[(&str, &str)] = &[
|
||||
("Vertical:", "Вертикально:"),
|
||||
("Horizontal:", "Горизонтально:"),
|
||||
("Invalid value", "Неприпустиме значення"),
|
||||
("Isolate Blending", "Ізолювати змішування"),
|
||||
("Knockout Group", "Група вибивання"),
|
||||
("Object:", "Об’єкт:"),
|
||||
("Group:", "Група:"),
|
||||
("Apply Condition", "Застосувати умову"),
|
||||
("Apply Parent to Pages…", "Застосувати шаблон до сторінок…"),
|
||||
("ArtCraft App Page…", "Сторінка програми ArtCraft…"),
|
||||
|
||||
@@ -2240,6 +2240,13 @@ pub fn effects_panel(app: &mut DesignApp, ui: &mut egui::Ui) {
|
||||
let _ = app.run("object.opacity", json!({"opacity": (v / 100.0).clamp(0.0, 1.0)}));
|
||||
}
|
||||
});
|
||||
// The level the settings apply to, with its blend mode and opacity ("Group: Color Dodge 100%").
|
||||
let group = matches!(it.content, designcraft_doc::Content::Group { .. });
|
||||
ui.horizontal(|ui| {
|
||||
let level = crate::i18n::tr(&app.ui.language, if group { "Group:" } else { "Object:" });
|
||||
crate::rtl::label(ui, egui::RichText::new(level).font(crate::theme::semibold(12.0)));
|
||||
crate::rtl::label(ui, format!("{} {}%", crate::i18n::tr(&app.ui.language, it.blend.label()), (it.opacity * 100.0).round()));
|
||||
});
|
||||
divider(ui);
|
||||
let ds = it.effects.drop_shadow.clone();
|
||||
let mut on = ds.on;
|
||||
@@ -2282,6 +2289,20 @@ pub fn effects_panel(app: &mut DesignApp, ui: &mut egui::Ui) {
|
||||
ui.end_row();
|
||||
});
|
||||
}
|
||||
divider(ui);
|
||||
// Groups only, as in InDesign (greyed for other objects).
|
||||
ui.horizontal(|ui| {
|
||||
let mut iso = it.isolate;
|
||||
let label = crate::rtl::widget(ui, crate::i18n::tr(&app.ui.language, "Isolate Blending"));
|
||||
if ui.add_enabled(group, egui::Checkbox::new(&mut iso, label)).changed() {
|
||||
let _ = app.run("object.transparencyGroup", json!({"isolate": iso}));
|
||||
}
|
||||
let mut ko = it.knockout;
|
||||
let label = crate::rtl::widget(ui, crate::i18n::tr(&app.ui.language, "Knockout Group"));
|
||||
if ui.add_enabled(group, egui::Checkbox::new(&mut ko, label)).changed() {
|
||||
let _ = app.run("object.transparencyGroup", json!({"knockout": ko}));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
pub fn preflight_panel(app: &mut DesignApp, ui: &mut egui::Ui) {
|
||||
@@ -2615,3 +2636,68 @@ mod tests {
|
||||
assert!(h.query_by(|n| n.role() == egui::accesskit::Role::TextInput && n.value().as_deref() == Some("90°")).is_some());
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod effects_panel_tests {
|
||||
use super::*;
|
||||
|
||||
/// One frame of the Effects panel: the text painted and where.
|
||||
fn frame(app: &mut DesignApp, ctx: &egui::Context, events: Vec<egui::Event>) -> Vec<(String, egui::Rect)> {
|
||||
let screen = egui::Rect::from_min_size(egui::Pos2::ZERO, vec2(320.0, 400.0));
|
||||
let time = ctx.input(|i| i.time) + 1.0 / 60.0;
|
||||
let mut out =
|
||||
ctx.run_ui(egui::RawInput { screen_rect: Some(screen), time: Some(time), events, ..Default::default() }, |ui| effects_panel(app, ui));
|
||||
out.textures_delta.clear();
|
||||
let mut v = Vec::new();
|
||||
for s in out.shapes {
|
||||
if let egui::Shape::Text(t) = s.shape {
|
||||
v.push((t.galley.job.text.clone(), t.visual_bounding_rect()));
|
||||
}
|
||||
}
|
||||
v
|
||||
}
|
||||
|
||||
fn click(app: &mut DesignApp, ctx: &egui::Context, label: &str) {
|
||||
let labels = frame(app, ctx, vec![]);
|
||||
let pos = labels.iter().find(|(t, _)| t == label).unwrap_or_else(|| panic!("{label} is painted")).1.center();
|
||||
for pressed in [true, false] {
|
||||
frame(
|
||||
app,
|
||||
ctx,
|
||||
vec![
|
||||
egui::Event::PointerMoved(pos),
|
||||
egui::Event::PointerButton { pos, button: egui::PointerButton::Primary, pressed, modifiers: egui::Modifiers::NONE },
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_effects_panel_shows_the_level_and_isolate_blending_and_knockout_group_for_groups() {
|
||||
let mut app = DesignApp::new(designcraft_engine::Session::new(), Default::default());
|
||||
app.run("file.new", json!({})).unwrap();
|
||||
let a = app.run("frame.create", json!({"rect": [72, 72, 144, 144]})).unwrap()["id"].as_u64().unwrap();
|
||||
let b = app.run("frame.create", json!({"rect": [100, 100, 200, 200]})).unwrap()["id"].as_u64().unwrap();
|
||||
let g = app.run("object.group", json!({"ids": [a, b]})).unwrap()["id"].as_u64().unwrap();
|
||||
app.run("selection.set", json!({"ids": [g]})).unwrap();
|
||||
app.run("object.opacity", json!({"opacity": 1.0, "blend": "colorDodge"})).unwrap();
|
||||
let ctx = egui::Context::default();
|
||||
crate::theme::install_fonts(&ctx, "en");
|
||||
crate::theme::apply(&ctx, &Tokens::for_brightness(crate::theme::Brightness::default()));
|
||||
let texts: Vec<String> = frame(&mut app, &ctx, vec![]).into_iter().map(|(t, _)| t).collect();
|
||||
for expected in ["Group:", "Color Dodge 100%", "Isolate Blending", "Knockout Group"] {
|
||||
assert!(texts.iter().any(|t| t == expected), "{expected} in {texts:?}");
|
||||
}
|
||||
let item = |app: &DesignApp, id: u64| app.session.doc().unwrap().doc.item(designcraft_doc::ItemId(id)).cloned().unwrap();
|
||||
click(&mut app, &ctx, "Isolate Blending");
|
||||
assert!(item(&app, g).isolate);
|
||||
click(&mut app, &ctx, "Knockout Group");
|
||||
assert!(item(&app, g).knockout);
|
||||
// Another object: the level is Object, and the group options are greyed.
|
||||
app.run("selection.set", json!({"ids": [a]})).unwrap();
|
||||
let texts: Vec<String> = frame(&mut app, &ctx, vec![]).into_iter().map(|(t, _)| t).collect();
|
||||
assert!(texts.iter().any(|t| t == "Object:"), "{texts:?}");
|
||||
click(&mut app, &ctx, "Knockout Group");
|
||||
assert!(!item(&app, a).knockout);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user