Merge PR #1499: Properties: a double-click on a Fill or Stroke chip opens the Color Picker

This commit is contained in:
aiongg
2026-10-30 19:44:04 +00:00
4 changed files with 80 additions and 1 deletions
+1
View File
@@ -456,6 +456,7 @@ Newest first.
- **2026-10-16** · IDML: a page keeps column guides that were dragged off centre (InDesign's Custom columns). They were divided evenly on import, so the gutter sprang back to the middle; the imported positions now place the columns, with the canvas guides, snapping and text placement following them, and they are written back on export. Layout › Margins and Columns re-divides them evenly when the count or gutter changes, as InDesign's does. No readiness percentage change.
- **2026-10-16** · IDML: custom column positions are checked before they are used (#1096). A set must give two edges per column, ascending from the left margin to the right one; any other (a wrong count, unsorted or non-numeric values, a list longer than 216 columns allow, or positions left behind by a margin or page-size change) is the even split, in the canvas, on import and on export, instead of columns drawn outside the margins. No readiness percentage change.
- **2026-10-21** · Effects dialog: the Angle field of the shadows, Bevel and Emboss, Satin and Gradient Feather has InDesign's dial beside it, its radius pointing where the light comes from; a click or drag on it sets the angle. Found in the Learn Skills Daily course, chapter 14. No readiness percentage change.
- **2026-10-21** · A double-click on a Fill or Stroke chip of Properties › Appearance opens the Color Picker with its colour, as in InDesign (a click still opens the swatches); for selected text, its own chips open it for the text. Found in the Learn Skills Daily course, chapter 23. No readiness percentage change.
- **2026-10-21** · Properties › Character and Paragraph: ••• (More Options) expands the section in place, as InDesign's: Character adds vertical and horizontal scale, baseline shift, skew, language and OpenType, and All Caps, Small Caps, Superscript, Subscript, Underline and Strikethrough; Paragraph adds the left, right, first line and last line indents, space before and after, the drop cap's lines and characters, Align to Baseline Grid and Hyphenate. It used to open the Character or Paragraph panel; the language field moved under ••• as in InDesign. Found in the Learn Skills Daily course, chapter 16. No readiness percentage change.
- **2026-10-21** · The text Control panel has InDesign's two modes: A (Character Formatting Controls: font and style, size and leading, All Caps/Superscript/Underline and Small Caps/Subscript/Strikethrough, kerning and tracking, vertical and horizontal scale, baseline shift and skew, character style and language) and ¶ (Paragraph Formatting Controls: the nine alignments, left/first line and right/last line indents, space before and after, the drop cap's lines and characters, paragraph style, Hyphenate and Align to Baseline Grid). It used to show a fixed mix of both. Found in the Learn Skills Daily course, chapter 16. No readiness percentage change.
- **2026-10-20** · With the Type tool, the Control panel's font size, leading and tracking fields have a spinner and a list of common values, as in InDesign (auto leading in parentheses). No readiness percentage change.
@@ -289,6 +289,9 @@ pub(super) fn appearance_section(app: &mut DesignApp, ui: &mut Ui, i: &SelInfo)
));
resp.widget_info(|| egui::WidgetInfo::labeled(egui::Role::Button, true, "Fill swatch"));
swatch_menu(app, &resp, &doc, &i.fill, false, i.is_text && i.count == 1, false);
if resp.double_clicked() {
open_color_picker(app, &doc, &i.fill, false, false);
}
crate::rtl::paint(
ui.painter(),
b.min + vec2(34.0, FIELD_H / 2.0),
@@ -311,6 +314,9 @@ pub(super) fn appearance_section(app: &mut DesignApp, ui: &mut Ui, i: &SelInfo)
));
resp.widget_info(|| egui::WidgetInfo::labeled(egui::Role::Button, true, "Stroke swatch"));
swatch_menu(app, &resp, &doc, &i.stroke, true, i.is_text && i.count == 1, false);
if resp.double_clicked() {
open_color_picker(app, &doc, &i.stroke, true, false);
}
if place(ui, sub(b, 34.0, 0.0, 56.0, FIELD_H), |ui| link_label(ui, crate::i18n::tr(&app.ui.language, "Stroke"), 56.0)).clicked() {
show_panel(app, "stroke");
}
@@ -370,6 +376,14 @@ pub(super) fn appearance_section(app: &mut DesignApp, ui: &mut Ui, i: &SelInfo)
});
}
/// The Color Picker for a Fill or Stroke chip (a double-click on it, as in InDesign), with the
/// chip's colour: the objects', or with `text` the selected text's.
pub(super) fn open_color_picker(app: &mut DesignApp, doc: &designcraft_doc::Document, swatch: &str, stroke: bool, text: bool) {
let mut fields = crate::panels::swatches::picker_fields(doc.resolve_color(swatch, 1.0), stroke);
fields["text"] = json!(text);
app.ui.dialog = Some(crate::dialogs::Dialog::new("colorPicker", fields));
}
/// The Fill (or Stroke) popup of Properties › Appearance, as InDesign's: Apply To (a text frame's
/// frame or its text), the tint, then the swatches. `cur` is the frame's swatch. With `text_only`
/// (text selected) it colours the selected text.
@@ -638,6 +638,66 @@ fn clicking_a_swatch_chip_in_the_appearance_fill_menu_applies_it() {
assert_eq!(after, name, "clicking the swatch chip applies it");
}
/// The course's chapter 23: a double-click on Properties › Appearance › Fill opens the Color
/// Picker with the fill's colour, as InDesign's (a click opens the swatches). For selected
/// text, the text's Fill chip opens it for the text.
#[test]
fn double_clicking_a_fill_chip_opens_the_color_picker() {
let double_click = |h: &mut Harness<'_, DesignApp>, label: &str| {
let at = h.get_by_label(label).rect().center();
h.hover_at(at);
h.step();
for pressed in [true, false, true, false] {
h.event(egui::Event::PointerButton { pos: at, button: egui::PointerButton::Primary, pressed, modifiers: egui::Modifiers::NONE });
h.step();
}
h.run_steps(3);
};
// Frames 1 ms apart, so two clicks make a double-click.
let panel = |app: DesignApp| {
let mut h = Harness::builder().with_size(vec2(320.0, 1400.0)).with_step_dt(0.001).build_ui_state(
|ui, app: &mut DesignApp| {
let id = egui::Id::unique("test_fonts");
if ui.data(|d| d.get_temp::<bool>(id)).is_none() {
crate::theme::install_fonts(ui.ctx(), "");
ui.data_mut(|d| d.insert_temp(id, true));
return;
}
show(app, ui);
},
app,
);
h.run_steps(3);
h
};
let frame = || {
let mut app = DesignApp::new(designcraft_engine::Session::new(), crate::Services::default());
app.run("file.new", json!({})).unwrap();
let id = app.run("frame.create", json!({"rect": [72, 72, 300, 200], "content": "unassigned"})).unwrap()["id"].as_u64().unwrap();
app.run("selection.set", json!({"ids": [id]})).unwrap();
app.run("object.fill", json!({"swatch": "C=0 M=0 Y=100 K=0"})).unwrap();
app
};
let mut h = panel(frame());
double_click(&mut h, "Fill swatch");
assert!(h.query_by_label("Tint:").is_none(), "the swatches popup isn't left open too");
let d = h.state().ui.dialog.clone().expect("the Color Picker");
assert_eq!((d.id.as_str(), d.s("target"), d.s("model"), d.b("text")), ("colorPicker", "fill".into(), "cmyk".into(), false));
assert_eq!(d.fields["cmyk"], json!([0.0, 0.0, 100.0, 0.0]));
let mut h = panel(frame());
double_click(&mut h, "Stroke swatch");
assert_eq!(h.state().ui.dialog.as_ref().map(|d| d.s("target")), Some("stroke".into()));
// Selected text: its own Fill chip.
let mut app = DesignApp::new(designcraft_engine::Session::new(), crate::Services::default());
app.run("file.new", json!({})).unwrap();
let f = app.run("frame.create", json!({"rect": [72, 72, 300, 200], "content": "text", "text": "Lemon"})).unwrap();
app.run("text.select", json!({"story": f["story"], "anchor": 0, "focus": 5})).unwrap();
let mut h = panel(app);
double_click(&mut h, "Text fill swatch");
let d = h.state().ui.dialog.clone().expect("the Color Picker");
assert_eq!((d.id.as_str(), d.s("target"), d.b("text")), ("colorPicker", "fill".into(), true));
}
fn panel(app: DesignApp) -> Harness<'static, DesignApp> {
let mut h = Harness::builder().with_size(vec2(320.0, 1400.0)).build_ui_state(
|ui, app: &mut DesignApp| {
+5 -1
View File
@@ -1,4 +1,4 @@
use super::object::swatch_menu;
use super::object::{open_color_picker, swatch_menu};
use super::typography::{para_field_min, show_typography_panel};
use super::{
Align, DesignApp, FIELD_H, NumField, Rect, Sense, Stroke, StrokeKind, Tokens, Ui, Value, block, full_width, icon_toggle, icons, json, link_label,
@@ -128,7 +128,11 @@ pub(super) fn text_appearance_section(app: &mut DesignApp, ui: &mut Ui) {
ui.painter().text(chip.center(), egui::Align2::CENTER_CENTER, "T", crate::theme::semibold(12.0), tc);
}
let resp = ui.interact(chip, ui.scope_id().with(("tchip", k)), Sense::click()).on_hover_text(format!("{label}: {sw}"));
resp.widget_info(|| egui::WidgetInfo::labeled(egui::Role::Button, true, if k == 0 { "Text fill swatch" } else { "Text stroke swatch" }));
swatch_menu(app, &resp, &doc, &sw, k != 0, false, true);
if resp.double_clicked() {
open_color_picker(app, &doc, &sw, k != 0, true);
}
if k == 0 {
crate::rtl::paint(
ui.painter(),