mirror of
https://github.com/storytold/designcraft.git
synced 2026-10-31 15:49:42 +00:00
Color Picker: the eyedropper takes a colour off the page
The eyedropper beside the hex code works as InDesign's: pressed and dragged onto the page, it takes the colour shown under the pointer when let go (one device pixel rendered as the canvas renders it, over the paper); off the pages it takes nothing. Found in the Learn Skills Daily course, chapter 23. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
2a14be0dfa
commit
3430a88cbc
11 files changed
+123
-7
No files matched your search
@@ -639,6 +639,35 @@ fn draw_cursor_badge(painter: &egui::Painter, p: Pos2, c: Cursor, t: &Tokens) {
|
||||
}
|
||||
|
||||
/// Render options for the canvas texture.
|
||||
/// The colour the canvas shows at screen point `pos` (the Color Picker's eyedropper): one device
|
||||
/// pixel rendered as the page is, over its paper. None off the pages.
|
||||
pub fn color_at(app: &mut DesignApp, ctx: &egui::Context, pos: Pos2) -> Option<[u8; 3]> {
|
||||
let rect = app.canvas_rect.filter(|r| r.contains(pos))?;
|
||||
let v = *app.view()?;
|
||||
let xf = Xf::new(rect, &v);
|
||||
let at = xf.to_canvas(pos);
|
||||
let st = app.session.active()?;
|
||||
let doc = st.doc.clone();
|
||||
let layout = CanvasLayout::new(&doc, st.editing_parents);
|
||||
let on_page = layout
|
||||
.slots
|
||||
.iter()
|
||||
.any(|slot| doc.spread(slot.spread).is_some_and(|sp| sp.pages.iter().any(|page| slot.xf.transform_rect_bbox(page.bounds()).contains(at))));
|
||||
if !on_page {
|
||||
return None;
|
||||
}
|
||||
let placed: Vec<designcraft_render::Placed> = layout.slots.iter().map(|s| designcraft_render::Placed { spread: s.spread, xf: s.xf }).collect();
|
||||
let ppp = ctx.pixels_per_point() as f64;
|
||||
let view = Affine::scale(ppp) * Affine::scale(xf.zoom) * Affine::translate((-at.x, -at.y));
|
||||
let opts = screen_options(app, &layout, app.ui.screen_mode != ScreenMode::Normal, ppp);
|
||||
let img = app.canvas.patcher.render(&doc, &app.session.cache, &placed, 1, 1, view, &opts);
|
||||
let [r, g, b, a] = <[u8; 4]>::try_from(img.pixels.get(..4)?).ok()?;
|
||||
// Premultiplied over the paper.
|
||||
let paper = doc.resolve_color(designcraft_color::swatch::PAPER, 1.0).unwrap_or(designcraft_color::Color::WHITE).to_rgba8(1.0);
|
||||
let over = |c: u8, p: u8| (u32::from(c) + u32::from(p) * (255 - u32::from(a)) / 255).min(255) as u8;
|
||||
Some([over(r, paper[0]), over(g, paper[1]), over(b, paper[2])])
|
||||
}
|
||||
|
||||
fn screen_options(app: &DesignApp, layout: &CanvasLayout, preview: bool, ppp: f64) -> designcraft_render::RenderOptions {
|
||||
designcraft_render::RenderOptions {
|
||||
printing_only: preview,
|
||||
|
||||
@@ -6870,7 +6870,7 @@ fn color_picker(app: &mut DesignApp, ui: &mut egui::Ui, d: &mut Dialog) {
|
||||
if comp_now != comp {
|
||||
d.fields.insert("component".into(), json!(comp_now.key()));
|
||||
}
|
||||
// The hex code, and the eyedropper's mark beside it.
|
||||
// The hex code, and the eyedropper beside it.
|
||||
let cy = area.min.y + at::ROWS[7];
|
||||
text(ui, egui::pos2(area.min.x + 321.0, cy), egui::Align2::LEFT_CENTER, "#:");
|
||||
let hr = egui::Rect::from_min_size(egui::pos2(area.min.x + 341.0, cy - 10.5), egui::vec2(64.0, 21.0));
|
||||
@@ -6886,12 +6886,26 @@ fn color_picker(app: &mut DesignApp, ui: &mut egui::Ui, d: &mut Dialog) {
|
||||
p = Picked::from_rgb(c.to_rgb_uncalibrated(), p.hsb[0]);
|
||||
}
|
||||
d.fields.insert("hexEdit".into(), json!(h));
|
||||
crate::icons::paint(
|
||||
ui.painter(),
|
||||
egui::Rect::from_center_size(egui::pos2(hr.right() + 16.0, cy), egui::vec2(15.0, 15.0)),
|
||||
"tool-eyedropper",
|
||||
t.icon,
|
||||
);
|
||||
// The eyedropper, as InDesign's: dragged onto the page, it takes the colour shown under the
|
||||
// pointer when it is let go.
|
||||
let tip = tr("Eyedropper: drag it onto the page");
|
||||
let er = egui::Rect::from_center_size(egui::pos2(hr.right() + 16.0, cy), egui::vec2(21.0, 21.0));
|
||||
let resp = ui.interact(er, egui::Id::new("color_picker_eyedropper"), egui::Sense::click_and_drag());
|
||||
resp.widget_info(|| egui::WidgetInfo::labeled(egui::WidgetType::Button, true, &tip));
|
||||
if resp.hovered() || resp.dragged() {
|
||||
ui.painter().rect_filled(er, 3.0, t.hover);
|
||||
}
|
||||
crate::icons::paint(ui.painter(), egui::Rect::from_center_size(er.center(), egui::vec2(15.0, 15.0)), "tool-eyedropper", t.icon);
|
||||
if resp.dragged() {
|
||||
ui.ctx().set_cursor_icon(egui::CursorIcon::Crosshair);
|
||||
}
|
||||
if resp.drag_stopped()
|
||||
&& let Some(pos) = ui.input(|i| i.pointer.latest_pos())
|
||||
&& let Some(c) = crate::canvas::color_at(app, ui.ctx(), pos)
|
||||
{
|
||||
p = Picked::from_rgb(c.map(|v| f32::from(v) / 255.0), p.hsb[0]);
|
||||
}
|
||||
resp.on_hover_text(tip);
|
||||
if p != before {
|
||||
store_picked(d, &p);
|
||||
if let Some(c) = exact_cmyk {
|
||||
@@ -10017,8 +10031,63 @@ mod tests {
|
||||
|
||||
/// The Color Picker laid out as InDesign's: a field and a slider for the component whose radio
|
||||
/// is on, the new colour over the old one, and the values in every space.
|
||||
/// The course's chapter 23: the Color Picker's eyedropper, dragged from the picker onto the
|
||||
/// page and let go there, takes the colour the page shows.
|
||||
#[test]
|
||||
fn the_color_pickers_eyedropper_takes_a_colour_off_the_page() {
|
||||
use egui_kittest::{Harness, kittest::Queryable};
|
||||
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": [36, 36, 560, 700], "content": "unassigned"})).unwrap()["id"].as_u64().unwrap();
|
||||
app.run("object.fill", json!({"ids": [id], "swatch": "C=0 M=100 Y=100 K=0"})).unwrap();
|
||||
app.ui.dialog = Some(Dialog::new("colorPicker", json!({"target": "fill", "hex": "#000000"})));
|
||||
let mut h = Harness::builder().with_size(egui::vec2(1400.0, 900.0)).build_ui_state(
|
||||
|ui, app: &mut DesignApp| {
|
||||
let ctx = ui.ctx().clone();
|
||||
app.logic(&ctx);
|
||||
app.ui(ui);
|
||||
},
|
||||
app,
|
||||
);
|
||||
h.run_steps(6);
|
||||
// Where the frame shows on screen.
|
||||
let target = {
|
||||
let app = h.state();
|
||||
let doc = &app.session.doc().unwrap().doc;
|
||||
let layout = designcraft_tools::CanvasLayout::new(doc, false);
|
||||
let v = *app.view().unwrap();
|
||||
crate::canvas::Xf::new(app.canvas_rect.unwrap(), &v).to_screen(layout.slots[0].xf * designcraft_geom::Point::new(300.0, 600.0))
|
||||
};
|
||||
let from = h.get_by_label("Eyedropper: drag it onto the page").rect().center();
|
||||
h.hover_at(from);
|
||||
h.drag_at(from);
|
||||
h.run_steps(1);
|
||||
for k in 1..=4 {
|
||||
h.hover_at(from + (target - from) * (k as f32 / 4.0));
|
||||
h.run_steps(1);
|
||||
}
|
||||
h.drop_at(target);
|
||||
h.run_steps(3);
|
||||
let d = h.state().ui.dialog.as_ref().expect("the picker stays open");
|
||||
let [r, g, b, _] = h.state().session.doc().unwrap().doc.resolve_color("C=0 M=100 Y=100 K=0", 1.0).unwrap().to_rgba8(1.0);
|
||||
let got: Vec<f64> = d.fields["rgb"].as_array().unwrap().iter().map(|v| v.as_f64().unwrap()).collect();
|
||||
for (a, e) in got.iter().zip([r, g, b]) {
|
||||
assert!((a - f64::from(e)).abs() <= 3.0, "{got:?} vs {:?}", [r, g, b]);
|
||||
}
|
||||
assert!(d.b("edited"));
|
||||
// Let go off the page (here, on the dialog's own field area): no colour, nothing changes.
|
||||
let hex = d.s("hex");
|
||||
h.hover_at(from);
|
||||
h.drag_at(from);
|
||||
h.run_steps(1);
|
||||
h.hover_at(from + egui::vec2(-40.0, 0.0));
|
||||
h.run_steps(1);
|
||||
h.drop_at(egui::pos2(2.0, 2.0));
|
||||
h.run_steps(3);
|
||||
assert_eq!(h.state().ui.dialog.as_ref().unwrap().s("hex"), hex);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_color_picker_picks_on_its_field_and_slider_and_adds_a_cmyk_swatch() {
|
||||
use egui_kittest::kittest::Queryable;
|
||||
let mut app = DesignApp::new(designcraft_engine::Session::new(), crate::Services::default());
|
||||
|
||||
@@ -848,6 +848,16 @@ const TABLE: &[(&str, [&str; 5])] = &[
|
||||
("Choose Image…", ["Choose Image…", "Choisir une image…", "Choose Image…", "Choose Image…", "选择图像…"]),
|
||||
("Class:", ["Class:", "Classe :", "Class:", "Class:", "类别:"]),
|
||||
("Color Picker", ["Color Picker", "Sélecteur de couleurs", "Color Picker", "Color Picker", "拾色器"]),
|
||||
(
|
||||
"Eyedropper: drag it onto the page",
|
||||
[
|
||||
"Pipette: auf die Seite ziehen",
|
||||
"Pipette : faites-la glisser sur la page",
|
||||
"Cuentagotas: arrástrelo a la página",
|
||||
"スポイト : ページにドラッグ",
|
||||
"吸管:将其拖到页面上",
|
||||
],
|
||||
),
|
||||
("Color Settings", ["Color Settings", "Réglages des couleurs", "Color Settings", "Color Settings", "颜色设置"]),
|
||||
("Color:", ["Color:", "Couleur :", "Color:", "Color:", "颜色:"]),
|
||||
("Columns", ["Columns", "Colonnes", "Columns", "Columns", "栏"]),
|
||||
|
||||
@@ -30,6 +30,7 @@ pub(super) const TABLE: &[(&str, &str)] = &[
|
||||
("Cut", "قص"),
|
||||
("Copy", "نسخ"),
|
||||
("Paste", "لصق"),
|
||||
("Eyedropper: drag it onto the page", "القطارة: اسحبها إلى الصفحة"),
|
||||
("Paste Vector Art", "لصق رسم متجه"),
|
||||
("Baseline Options", "خيارات خط الأساس"),
|
||||
("Fixed Number", "عدد ثابت"),
|
||||
|
||||
@@ -31,6 +31,7 @@ pub(super) const TABLE: &[(&str, &str)] = &[
|
||||
("Cut", "Vyjmout"),
|
||||
("Copy", "Kopírovat"),
|
||||
("Paste", "Vložit"),
|
||||
("Eyedropper: drag it onto the page", "Kapátko: přetáhněte ho na stránku"),
|
||||
("Reverse Path", "Obrátit cestu"),
|
||||
("Set Anchor", "Nastavit kotvu"),
|
||||
("Clear", "Vymazat"),
|
||||
|
||||
@@ -28,6 +28,7 @@ pub(super) const TABLE: &[(&str, &str)] = &[
|
||||
("Cut", "Taglia"),
|
||||
("Copy", "Copia"),
|
||||
("Paste", "Incolla"),
|
||||
("Eyedropper: drag it onto the page", "Contagocce: trascinalo sulla pagina"),
|
||||
("Paste Vector Art", "Incolla grafica vettoriale"),
|
||||
("Baseline Options", "Opzioni linea di base"),
|
||||
("Fixed Number", "Numero fisso"),
|
||||
|
||||
@@ -31,6 +31,7 @@ pub(super) const TABLE: &[(&str, &str)] = &[
|
||||
("Cut", "ამოჭრა"),
|
||||
("Copy", "კოპირება"),
|
||||
("Paste", "ჩასმა"),
|
||||
("Eyedropper: drag it onto the page", "პიპეტი: გადაიტანეთ გვერდზე"),
|
||||
("Inches Decimal", "ინჩები (ათობითი)"),
|
||||
("Back", "უკან"),
|
||||
("Warning", "გაფრთხილება"),
|
||||
|
||||
@@ -29,6 +29,7 @@ pub(super) const TABLE: &[(&str, &str)] = &[
|
||||
("Cut", "Wytnij"),
|
||||
("Copy", "Kopiuj"),
|
||||
("Paste", "Wklej"),
|
||||
("Eyedropper: drag it onto the page", "Kroplomierz: przeciągnij go na stronę"),
|
||||
("Clear", "Wyczyść"),
|
||||
("Duplicate", "Powiel"),
|
||||
("Select All", "Zaznacz wszystko"),
|
||||
|
||||
@@ -31,6 +31,7 @@ pub(super) const TABLE: &[(&str, &str)] = &[
|
||||
("Cut", "Recortar"),
|
||||
("Copy", "Copiar"),
|
||||
("Paste", "Colar"),
|
||||
("Eyedropper: drag it onto the page", "Conta-gotas: arraste-o para a página"),
|
||||
("Paste Vector Art", "Colar arte vetorial"),
|
||||
("Baseline Options", "Opções de linha de base"),
|
||||
("Fixed Number", "Número fixo"),
|
||||
|
||||
@@ -33,6 +33,7 @@ pub(super) const TABLE: &[(&str, &str)] = &[
|
||||
("Cut", "Вырезать"),
|
||||
("Copy", "Копировать"),
|
||||
("Paste", "Вставить"),
|
||||
("Eyedropper: drag it onto the page", "Пипетка: перетащите её на страницу"),
|
||||
("Clear", "Очистить"),
|
||||
("Duplicate", "Дублировать"),
|
||||
("Select All", "Выделить все"),
|
||||
|
||||
@@ -29,6 +29,7 @@ pub(super) const TABLE: &[(&str, &str)] = &[
|
||||
("Cut", "Вирізати"),
|
||||
("Copy", "Копіювати"),
|
||||
("Paste", "Вставити"),
|
||||
("Eyedropper: drag it onto the page", "Піпетка: перетягніть її на сторінку"),
|
||||
("Paste Vector Art", "Вставити векторну графіку"),
|
||||
("Baseline Options", "Параметри базової лінії"),
|
||||
("Fixed Number", "Фіксована кількість"),
|
||||
|
||||
Reference in new issue
Block a user