mirror of
https://github.com/storytold/designcraft.git
synced 2026-11-01 06:06:49 -06:00
Merge PR #1141: Color panel: edit future-shape colors with nothing selected
This commit is contained in:
@@ -392,6 +392,7 @@ pub(super) const TABLE: &[(&str, &str)] = &[
|
||||
("Object Layer Options", "خيارات طبقات الكائن"),
|
||||
("Colour Picker", "منتقي الألوان"),
|
||||
("Color Picker", "أداة اختيار الألوان"),
|
||||
("Color spectrum", "طيف الألوان"),
|
||||
("Color Settings", "إعدادات الألوان"),
|
||||
("User Dictionary", "قاموس المستخدم"),
|
||||
("Menu Customization", "تخصيص القوائم"),
|
||||
|
||||
@@ -195,6 +195,7 @@ pub(super) const TABLE: &[(&str, &str)] = &[
|
||||
("Choose Image…", "Vybrat obrázek…"),
|
||||
("Class:", "Třída:"),
|
||||
("Color Picker", "Výběr barvy"),
|
||||
("Color spectrum", "Barevné spektrum"),
|
||||
("Color Settings", "Nastavení barev"),
|
||||
("Color:", "Barva:"),
|
||||
("Columns", "Sloupce"),
|
||||
|
||||
@@ -224,6 +224,7 @@ pub(super) const TABLE: &[(&str, &str)] = &[
|
||||
("Choose Image…", "Bild auswählen …"),
|
||||
("Class:", "Klasse:"),
|
||||
("Color Picker", "Farbwähler"),
|
||||
("Color spectrum", "Farbspektrum"),
|
||||
("Color Settings", "Farbeinstellungen"),
|
||||
("Color:", "Farbe:"),
|
||||
("Columns", "Spalten"),
|
||||
|
||||
@@ -306,6 +306,7 @@ pub(super) const TABLE: &[(&str, &str)] = &[
|
||||
("Choose Image…", "Scegli immagine…"),
|
||||
("Class:", "Classe:"),
|
||||
("Color Picker", "Selettore colore"),
|
||||
("Color spectrum", "Spettro dei colori"),
|
||||
("Color Settings", "Impostazioni colore"),
|
||||
("Color:", "Colore:"),
|
||||
("Columns", "Colonne"),
|
||||
|
||||
@@ -409,6 +409,7 @@ pub(super) const TABLE: &[(&str, &str)] = &[
|
||||
("Choose Image…", "画像を選択…"),
|
||||
("Class:", "クラス:"),
|
||||
("Color Picker", "カラーピッカー"),
|
||||
("Color spectrum", "カラースペクトル"),
|
||||
("Color Settings", "カラー設定"),
|
||||
("Color:", "カラー:"),
|
||||
("Columns", "列数"),
|
||||
|
||||
@@ -228,6 +228,7 @@ pub(super) const TABLE: &[(&str, &str)] = &[
|
||||
("Choose Image…", "გამოსახულების არჩევა…"),
|
||||
("Class:", "კლასი:"),
|
||||
("Color Picker", "ფერის ამრჩევი"),
|
||||
("Color spectrum", "ფერთა სპექტრი"),
|
||||
("Color Settings", "ფერის პარამეტრები"),
|
||||
("Color:", "ფერი:"),
|
||||
("Columns", "სვეტები"),
|
||||
|
||||
@@ -195,6 +195,7 @@ pub(super) const TABLE: &[(&str, &str)] = &[
|
||||
("Choose Image…", "Wybierz obraz…"),
|
||||
("Class:", "Klasa:"),
|
||||
("Color Picker", "Próbnik kolorów"),
|
||||
("Color spectrum", "Spektrum kolorów"),
|
||||
("Color Settings", "Ustawienia kolorów"),
|
||||
("Color:", "Kolor:"),
|
||||
("Columns", "Łamy"),
|
||||
|
||||
@@ -400,6 +400,7 @@ pub(super) const TABLE: &[(&str, &str)] = &[
|
||||
("Object Layer Options", "Opções de camada do objeto"),
|
||||
("Colour Picker", "Seletor de cores"),
|
||||
("Color Picker", "Seletor de cores"),
|
||||
("Color spectrum", "Espectro de cores"),
|
||||
("Color Settings", "Configurações de cores"),
|
||||
("User Dictionary", "Dicionário do usuário"),
|
||||
("Menu Customization", "Personalização de menus"),
|
||||
|
||||
@@ -195,6 +195,7 @@ pub(super) const TABLE: &[(&str, &str)] = &[
|
||||
("Choose Image…", "Выбрать изображение…"),
|
||||
("Class:", "Класс:"),
|
||||
("Color Picker", "Палитра цветов"),
|
||||
("Color spectrum", "Цветовой спектр"),
|
||||
("Color Settings", "Настройка цветов"),
|
||||
("Color:", "Цвет:"),
|
||||
("Columns", "Колонки"),
|
||||
|
||||
@@ -959,6 +959,7 @@ pub(super) 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", "拾色器"]),
|
||||
("Color spectrum", ["Farbspektrum", "Spectre de couleurs", "Espectro de color", "カラースペクトル", "色谱"]),
|
||||
("Color Settings", ["Color Settings", "Réglages des couleurs", "Color Settings", "Color Settings", "颜色设置"]),
|
||||
("Color:", ["Color:", "Couleur :", "Color:", "Color:", "颜色:"]),
|
||||
("Columns", ["Columns", "Colonnes", "Columns", "Columns", "栏"]),
|
||||
|
||||
@@ -309,6 +309,7 @@ pub(super) const TABLE: &[(&str, &str)] = &[
|
||||
("Choose Image…", "Вибрати зображення…"),
|
||||
("Class:", "Клас:"),
|
||||
("Color Picker", "Палітра кольорів"),
|
||||
("Color spectrum", "Колірний спектр"),
|
||||
("Color Settings", "Налаштування кольору"),
|
||||
("Color:", "Колір:"),
|
||||
("Columns", "Колонки"),
|
||||
|
||||
@@ -82,6 +82,19 @@ pub fn color_target(app: &mut DesignApp) -> Option<(String, f32, String, f32)> {
|
||||
Some((i.fill, sel_tint(app, false), i.stroke, sel_tint(app, true)))
|
||||
}
|
||||
|
||||
/// [`color_target`], or with nothing selected the colours new shapes get (the Color and Swatches
|
||||
/// panels then edit those, as InDesign's do).
|
||||
pub fn color_target_or_defaults(app: &mut DesignApp) -> Option<(String, f32, String, f32)> {
|
||||
color_target(app).or_else(|| {
|
||||
let state = app.session.active()?;
|
||||
if !state.selection.items.is_empty() || state.selection.text.is_some() || state.selection.cells.is_some() {
|
||||
return None;
|
||||
}
|
||||
let colors = &state.doc.settings.new_object;
|
||||
Some((colors.fill.clone(), colors.fill_tint, colors.stroke.clone(), colors.stroke_tint))
|
||||
})
|
||||
}
|
||||
|
||||
/// Apply a swatch (and tint) to the fill or stroke of what colour edits apply to.
|
||||
pub fn apply_swatch(app: &mut DesignApp, stroke: bool, swatch: &str, tint: Option<f32>) -> Result<Value, String> {
|
||||
remember_swatch(app, swatch);
|
||||
|
||||
@@ -11,7 +11,8 @@ pub fn show(app: &mut DesignApp, ui: &mut egui::Ui) {
|
||||
let t = Tokens::get(ui.ctx());
|
||||
let Some(st) = app.session.active() else { return };
|
||||
let doc = st.doc.clone();
|
||||
let target = super::color_target(app);
|
||||
// With nothing selected, the proxy, Tint field and highlighted row are those of new shapes.
|
||||
let target = super::color_target_or_defaults(app);
|
||||
header(app, ui, &doc, target.as_ref());
|
||||
// The row of the colour in front of the proxy is highlighted.
|
||||
let fill_cur = target.map(|(fill, _, stroke, _)| if app.ui.stroke_front { stroke } else { fill });
|
||||
@@ -510,7 +511,8 @@ pub enum ColorMode {
|
||||
/// swatch), the spectrum ramp, Add to Swatches. Edits apply unnamed colours (`object.color`).
|
||||
pub fn color_panel(app: &mut DesignApp, ui: &mut egui::Ui) {
|
||||
let t = Tokens::get(ui.ctx());
|
||||
let Some((fill, fill_tint, stroke_sw, stroke_tint)) = super::color_target(app) else {
|
||||
let target = super::color_target_or_defaults(app);
|
||||
let Some((fill, fill_tint, stroke_sw, stroke_tint)) = target else {
|
||||
ui.label(crate::rtl::widget(
|
||||
ui,
|
||||
egui::RichText::new(crate::i18n::tr(&app.ui.language, "Select an object or text to color its fill or stroke.")).color(t.text_dim),
|
||||
@@ -723,6 +725,7 @@ fn cmyk_ramp(k: usize, x: f32) -> egui::Color32 {
|
||||
/// The spectrum ramp: hue across, light to dark down; click to apply.
|
||||
fn spectrum(app: &mut DesignApp, ui: &mut egui::Ui, target: &str) {
|
||||
let (r, resp) = ui.allocate_exact_size(vec2(ui.available_width(), 36.0), Sense::click());
|
||||
resp.widget_info(|| egui::WidgetInfo::labeled(egui::Role::Button, true, crate::i18n::tr(&app.ui.language, "Color spectrum")));
|
||||
let at = |u: f32, v: f32| -> [f32; 3] {
|
||||
let c = egui::ecolor::Hsva::new(u, (v * 2.0).min(1.0), (2.0 - v * 2.0).min(1.0), 1.0).to_rgb();
|
||||
[c[0], c[1], c[2]]
|
||||
@@ -768,6 +771,92 @@ fn color_json(c: &designcraft_color::Color) -> serde_json::Value {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod color_panel_defaults_tests {
|
||||
use super::*;
|
||||
use egui_kittest::kittest::Queryable;
|
||||
|
||||
#[test]
|
||||
fn color_panel_with_no_selection_edits_future_shapes_without_recoloring_existing_ones() {
|
||||
let mut app = DesignApp::new(designcraft_engine::Session::new(), crate::Services::default());
|
||||
app.run("file.new", json!({})).unwrap();
|
||||
let old =
|
||||
designcraft_doc::ItemId(app.run("frame.create", json!({"rect":[72,72,200,200],"content":"unassigned"})).unwrap()["id"].as_u64().unwrap());
|
||||
let original = app.session.active().unwrap().doc.item(old).unwrap().stroke.clone();
|
||||
app.run("selection.set", json!({"ids":[]})).unwrap();
|
||||
app.ui.stroke_front = true;
|
||||
app.run("window.panel", json!({"panel":"color"})).unwrap();
|
||||
let mut h = crate::test_window::open(app, vec2(1440.0, 900.0));
|
||||
assert!(h.query_by_label("Select an object or text to color its fill or stroke.").is_none());
|
||||
let ramp = h.get_by_label("Color spectrum").rect();
|
||||
crate::test_window::click_at(&mut h, egui::pos2(ramp.left() + ramp.width() / 3.0, ramp.center().y));
|
||||
let defaults = h.state().app.session.active().unwrap().doc.settings.new_object.clone();
|
||||
assert_ne!(defaults.stroke, original.swatch);
|
||||
assert_eq!(h.state().app.session.active().unwrap().doc.item(old).unwrap().stroke, original);
|
||||
h.state_mut().app.run("selection.set", json!({"ids":[old.0]})).unwrap();
|
||||
assert_eq!(crate::panels::color_target(&mut h.state_mut().app).unwrap().2, original.swatch);
|
||||
h.state_mut().app.run("selection.set", json!({"ids":[]})).unwrap();
|
||||
let new = designcraft_doc::ItemId(
|
||||
h.state_mut().app.run("frame.create", json!({"rect":[240,72,368,200],"content":"unassigned"})).unwrap()["id"].as_u64().unwrap(),
|
||||
);
|
||||
assert_eq!(h.state().app.session.active().unwrap().doc.item(new).unwrap().stroke.swatch, defaults.stroke);
|
||||
// Two undo steps: the new frame, then the spectrum click's default stroke.
|
||||
h.state_mut().app.run("edit.undo", json!({})).unwrap();
|
||||
h.state_mut().app.run("edit.undo", json!({})).unwrap();
|
||||
assert_eq!(h.state().app.session.active().unwrap().doc.settings.new_object.stroke, original.swatch);
|
||||
assert_eq!(h.state().app.session.active().unwrap().doc.item(old).unwrap().stroke, original);
|
||||
// With the Fill box in front, the ramp sets the default fill instead.
|
||||
let original_fill = h.state().app.session.active().unwrap().doc.item(old).unwrap().fill.clone();
|
||||
h.state_mut().app.ui.stroke_front = false;
|
||||
h.run_steps(2);
|
||||
let ramp = h.get_by_label("Color spectrum").rect();
|
||||
crate::test_window::click_at(&mut h, egui::pos2(ramp.left() + ramp.width() * 2.0 / 3.0, ramp.center().y));
|
||||
let defaults = h.state().app.session.active().unwrap().doc.settings.new_object.clone();
|
||||
assert_ne!(defaults.fill, original_fill.swatch);
|
||||
assert_eq!(defaults.stroke, original.swatch, "the stroke default is left alone");
|
||||
assert_eq!(h.state().app.session.active().unwrap().doc.item(old).unwrap().fill, original_fill);
|
||||
}
|
||||
|
||||
/// The Swatches panel shows the same defaults: its Tint field reads the default fill's tint,
|
||||
/// and a click on a swatch row sets the default fill, not an existing frame's.
|
||||
#[test]
|
||||
fn swatches_panel_with_no_selection_shows_and_edits_future_shape_colors() {
|
||||
use egui::accesskit::Role;
|
||||
let mut app = DesignApp::new(designcraft_engine::Session::new(), crate::Services::default());
|
||||
app.run("file.new", json!({})).unwrap();
|
||||
let old =
|
||||
designcraft_doc::ItemId(app.run("frame.create", json!({"rect":[72,72,200,200],"content":"unassigned"})).unwrap()["id"].as_u64().unwrap());
|
||||
let original = app.session.active().unwrap().doc.item(old).unwrap().fill.clone();
|
||||
app.run("selection.set", json!({"ids":[]})).unwrap();
|
||||
app.run("object.fill", json!({"swatch": "[Black]", "tint": 0.4})).unwrap();
|
||||
app.ui.stroke_front = false;
|
||||
let mut h = egui_kittest::Harness::builder().with_size(vec2(320.0, 480.0)).build_ui_state(
|
||||
|ui, app: &mut DesignApp| {
|
||||
// The panel's fonts are installed on the first frame and used from the next.
|
||||
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);
|
||||
let fields = h.query_all_by(|n| matches!(n.role(), Role::SpinButton | Role::TextInput)).filter_map(|n| n.value()).collect::<Vec<_>>();
|
||||
assert!(fields.iter().any(|v| v.starts_with("40")), "the Tint field shows the default fill's 40 %: {fields:?}");
|
||||
let row = h.get_by_label("[Paper]").rect().center();
|
||||
h.hover_at(row);
|
||||
h.drag_at(row);
|
||||
h.drop_at(row);
|
||||
h.run_steps(3);
|
||||
let defaults = h.state().session.active().unwrap().doc.settings.new_object.clone();
|
||||
assert_eq!((defaults.fill.as_str(), defaults.fill_tint), ("[Paper]", 1.0));
|
||||
assert_eq!(h.state().session.active().unwrap().doc.item(old).unwrap().fill, original);
|
||||
}
|
||||
}
|
||||
|
||||
/// Stops in `object.gradient` form: a stop on a swatch also names it, so it keeps its inks.
|
||||
fn stops_json(g: &designcraft_color::Gradient) -> Vec<serde_json::Value> {
|
||||
g.stops
|
||||
|
||||
Reference in New Issue
Block a user