mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-10-24 09:46:59 -06:00
Layers: sublayers, groups and objects are clickable rows in the panel
Rows at every depth highlight on click (Shift range, Ctrl toggle) and make their layer current; selection squares select a row's art and drag the selection to another row; eye and lock on every row with drag-down-the-column in one undo step and Ctrl-click for Outline; Alt-click triangles; drag and drop rows above, below or into layers and groups with a drop line (Alt copies); Delete key; the full panel menu, Layer Options and Panel Options dialogs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,283 @@
|
||||
//! Layer Options (double-click a layer row, or ≡ › Options for Selection…) and the same dialog for
|
||||
//! a new layer or sublayer (≡ › New Layer…, Alt-click Create New Layer). Objects' rows get Name,
|
||||
//! Show and Lock only.
|
||||
//!
|
||||
//! Fields: `ids` (the rows edited; none for a new layer), `mode` (`edit`, `new` or `newSublayer`),
|
||||
//! `layer` (the rows are layers), `name`, `color` (a preset's name such as "Light Blue", or
|
||||
//! `#rrggbb`), `template`, `locked`, `visible`, `printable`, `preview`, `dimImages` and
|
||||
//! `dimPercent`. OK runs `layer.setProps`, `layer.new` or `layer.newSublayer`.
|
||||
|
||||
use serde_json::{Value, json};
|
||||
use vectorcraft_doc::{LAYER_COLORS, LayerColor, Node, NodeId, NodeKind};
|
||||
|
||||
use super::swatch_options::{grid, label};
|
||||
use super::{DialogSpec, form, run_and_close};
|
||||
use crate::state::Dialog;
|
||||
use crate::{VectorcraftApp, widgets};
|
||||
|
||||
/// The dialog kind of Layer Options.
|
||||
pub const KIND: &str = "layerOptions";
|
||||
|
||||
pub(super) const SPEC: DialogSpec = DialogSpec { heading, body, confirm, min_width: 320.0, ..DialogSpec::FORM };
|
||||
|
||||
fn heading(d: &Dialog) -> String {
|
||||
if d.bool("layer") || d.str("mode") != "edit" { tl!("Layer Options").into() } else { tl!("Options").into() }
|
||||
}
|
||||
|
||||
/// A layer colour as the dialog shows it: a preset's name, else `#rrggbb`.
|
||||
fn color_field(c: LayerColor) -> String {
|
||||
match c {
|
||||
LayerColor::Preset(i) => LAYER_COLORS.get(i as usize).map_or("Light Blue", |(n, _)| *n).to_string(),
|
||||
LayerColor::Custom([r, g, b]) => format!("#{r:02x}{g:02x}{b:02x}"),
|
||||
}
|
||||
}
|
||||
|
||||
/// The fields for `n` (a layer or any other row).
|
||||
fn fields_of(n: &Node) -> serde_json::Map<String, Value> {
|
||||
let mut f = serde_json::Map::new();
|
||||
f.insert("name".into(), json!(n.display_name()));
|
||||
f.insert("visible".into(), json!(n.visible));
|
||||
f.insert("locked".into(), json!(n.locked));
|
||||
f.insert("layer".into(), json!(n.is_layer()));
|
||||
if let NodeKind::Layer { color, template, printable, preview, dim_images, .. } = &n.kind {
|
||||
f.insert("color".into(), json!(color_field(*color)));
|
||||
f.insert("template".into(), json!(template));
|
||||
f.insert("printable".into(), json!(printable));
|
||||
f.insert("preview".into(), json!(preview));
|
||||
f.insert("dimImages".into(), json!(dim_images.is_some()));
|
||||
f.insert("dimPercent".into(), json!(dim_images.unwrap_or(50)));
|
||||
}
|
||||
f
|
||||
}
|
||||
|
||||
/// `ui.layerOptions {ids?|id?}`: open Layer Options for rows (default: the highlighted rows, else
|
||||
/// the current layer), filled in from the first.
|
||||
pub fn open(app: &mut VectorcraftApp, p: &Value) -> Result<Value, String> {
|
||||
let st = app.session.active().ok_or("no document open")?;
|
||||
let ids: Vec<NodeId> = match (p.get("ids").and_then(Value::as_array), p.get("id").and_then(Value::as_u64)) {
|
||||
(Some(a), _) => a.iter().filter_map(Value::as_u64).map(NodeId).collect(),
|
||||
(None, Some(id)) => vec![NodeId(id)],
|
||||
(None, None) => {
|
||||
let rows = st.highlighted_rows();
|
||||
if rows.is_empty() { st.current_layer().into_iter().collect() } else { rows }
|
||||
}
|
||||
};
|
||||
let first = ids.first().and_then(|id| st.doc.node(*id)).ok_or("no such layer or object")?;
|
||||
let mut f = fields_of(first);
|
||||
f.insert("ids".into(), json!(ids.iter().map(|i| i.0).collect::<Vec<_>>()));
|
||||
f.insert("mode".into(), json!("edit"));
|
||||
app.ui.dialog = Some(Dialog { kind: KIND.into(), fields: f });
|
||||
Ok(Value::Null)
|
||||
}
|
||||
|
||||
/// `ui.newLayer {sublayer?}`: Layer Options for a new layer (or sublayer), with the name and
|
||||
/// colour it would get.
|
||||
pub fn open_new(app: &mut VectorcraftApp, p: &Value) -> Result<Value, String> {
|
||||
let st = app.session.active().ok_or("no document open")?;
|
||||
let mut layers = vec![];
|
||||
st.doc.walk(|n| {
|
||||
if n.is_layer() {
|
||||
layers.push(n.display_name());
|
||||
}
|
||||
});
|
||||
let mut i = layers.len() + 1;
|
||||
while layers.contains(&format!("Layer {i}")) {
|
||||
i += 1;
|
||||
}
|
||||
let sublayer = p.get("sublayer").and_then(Value::as_bool).unwrap_or(false);
|
||||
let color = LayerColor::Preset((layers.len() % LAYER_COLORS.len()) as u8);
|
||||
let fields = json!({
|
||||
"mode": if sublayer { "newSublayer" } else { "new" }, "layer": true, "name": format!("Layer {i}"), "color": color_field(color),
|
||||
"template": false, "locked": false, "visible": true, "printable": true, "preview": true, "dimImages": false, "dimPercent": 50,
|
||||
});
|
||||
app.ui.dialog = Some(Dialog::new(KIND, fields));
|
||||
Ok(Value::Null)
|
||||
}
|
||||
|
||||
fn toggle(ui: &mut egui::Ui, d: &mut Dialog, key: &str, text: &str, enabled: bool) -> bool {
|
||||
let v = d.bool(key);
|
||||
let changed = widgets::check(ui, text, v, enabled);
|
||||
if changed {
|
||||
d.fields.insert(key.into(), json!(!v));
|
||||
}
|
||||
changed
|
||||
}
|
||||
|
||||
fn body(_: &mut VectorcraftApp, ui: &mut egui::Ui, d: &mut Dialog) -> bool {
|
||||
let layer = d.bool("layer");
|
||||
let many = d.fields.get("ids").and_then(Value::as_array).is_some_and(|a| a.len() > 1);
|
||||
grid(ui, |ui| {
|
||||
if !many {
|
||||
label(ui, tl!("Name:"));
|
||||
form::text(ui, d, "name", 190.0);
|
||||
ui.end_row();
|
||||
}
|
||||
if layer {
|
||||
label(ui, tl!("Color:"));
|
||||
ui.horizontal(|ui| {
|
||||
let cur = d.str("color");
|
||||
let names: Vec<&str> = LAYER_COLORS.iter().map(|(n, _)| *n).chain(["Custom"]).collect();
|
||||
let shown = if LAYER_COLORS.iter().any(|(n, _)| n.eq_ignore_ascii_case(&cur)) { cur.clone() } else { "Custom".into() };
|
||||
if let Some((n, _)) = widgets::dropdown(ui, "layer-color", &shown, &names, 140.0).and_then(|i| LAYER_COLORS.get(i)) {
|
||||
d.fields.insert("color".into(), json!(n));
|
||||
}
|
||||
let [r, g, b] = LAYER_COLORS
|
||||
.iter()
|
||||
.find(|(n, _)| n.eq_ignore_ascii_case(&cur))
|
||||
.map(|(_, c)| *c)
|
||||
.or_else(|| {
|
||||
vectorcraft_color::Color::from_hex(&cur).map(|c| {
|
||||
let [r, g, b, _] = c.to_rgba8(1.0);
|
||||
[r, g, b]
|
||||
})
|
||||
})
|
||||
.unwrap_or(LAYER_COLORS[0].1);
|
||||
let mut rgb = [r, g, b];
|
||||
if ui.color_edit_button_srgb(&mut rgb).changed() {
|
||||
d.fields.insert("color".into(), json!(format!("#{:02x}{:02x}{:02x}", rgb[0], rgb[1], rgb[2])));
|
||||
}
|
||||
});
|
||||
ui.end_row();
|
||||
}
|
||||
});
|
||||
ui.add_space(8.0);
|
||||
if layer {
|
||||
ui.horizontal(|ui| {
|
||||
ui.vertical(|ui| {
|
||||
// A template is locked and dims its images; turning it off frees both.
|
||||
if toggle(ui, d, "template", tl!("Template"), true) {
|
||||
let on = d.bool("template");
|
||||
d.fields.insert("locked".into(), json!(on));
|
||||
d.fields.insert("dimImages".into(), json!(on));
|
||||
}
|
||||
toggle(ui, d, "visible", tl!("Show"), true);
|
||||
toggle(ui, d, "preview", tl!("Preview"), true);
|
||||
ui.horizontal(|ui| {
|
||||
toggle(ui, d, "dimImages", tl!("Dim Images to:"), true);
|
||||
let mut v = d.f64("dimPercent", 50.0);
|
||||
if ui.add_enabled(d.bool("dimImages"), egui::DragValue::new(&mut v).range(0.0..=100.0).suffix("%")).changed() {
|
||||
d.fields.insert("dimPercent".into(), json!(v.round()));
|
||||
}
|
||||
});
|
||||
});
|
||||
ui.add_space(24.0);
|
||||
ui.vertical(|ui| {
|
||||
let template = d.bool("template");
|
||||
toggle(ui, d, "locked", tl!("Lock"), !template);
|
||||
toggle(ui, d, "printable", tl!("Print"), !template);
|
||||
});
|
||||
});
|
||||
} else {
|
||||
toggle(ui, d, "visible", tl!("Show"), true);
|
||||
toggle(ui, d, "locked", tl!("Lock"), true);
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
/// The command and params OK runs.
|
||||
pub(crate) fn params(d: &Dialog) -> (&'static str, Value) {
|
||||
let mut p = serde_json::Map::new();
|
||||
let many = d.fields.get("ids").and_then(Value::as_array).is_some_and(|a| a.len() > 1);
|
||||
if !many {
|
||||
p.insert("name".into(), json!(d.str("name")));
|
||||
}
|
||||
p.insert("visible".into(), json!(d.bool("visible")));
|
||||
p.insert("locked".into(), json!(d.bool("locked")));
|
||||
if d.bool("layer") {
|
||||
p.insert("color".into(), json!(d.str("color")));
|
||||
p.insert("template".into(), json!(d.bool("template")));
|
||||
p.insert("printable".into(), json!(d.bool("printable")));
|
||||
p.insert("preview".into(), json!(d.bool("preview")));
|
||||
p.insert("dimImages".into(), if d.bool("dimImages") { json!(d.f64("dimPercent", 50.0).clamp(0.0, 100.0)) } else { json!(false) });
|
||||
}
|
||||
match d.str("mode").as_str() {
|
||||
"new" => ("layer.new", Value::Object(p)),
|
||||
"newSublayer" => ("layer.newSublayer", Value::Object(p)),
|
||||
_ => {
|
||||
p.insert("ids".into(), d.fields.get("ids").cloned().unwrap_or(json!([])));
|
||||
("layer.setProps", Value::Object(p))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn confirm(app: &mut VectorcraftApp, d: &Dialog) -> Result<Value, String> {
|
||||
let (cmd, p) = params(d);
|
||||
run_and_close(app, cmd, p)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use vectorcraft_engine::Session;
|
||||
|
||||
fn app() -> VectorcraftApp {
|
||||
let mut app = VectorcraftApp::new(Session::new(), Default::default());
|
||||
app.session.execute("file.new", &json!({"width": 100, "height": 100})).unwrap();
|
||||
app
|
||||
}
|
||||
|
||||
fn draw(app: &mut VectorcraftApp) {
|
||||
let ctx = egui::Context::default();
|
||||
crate::theme::install_fonts(&ctx);
|
||||
let mut out = ctx.run_ui(Default::default(), |ui| super::super::show(app, ui.ctx()));
|
||||
out.textures_delta.clear();
|
||||
}
|
||||
|
||||
fn set(app: &mut VectorcraftApp, k: &str, v: Value) {
|
||||
app.ui.dialog.as_mut().unwrap().fields.insert(k.into(), v);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn layer_options_edit_the_layer_and_ok_is_one_undo_step() {
|
||||
let mut app = app();
|
||||
let layer = app.session.doc().unwrap().doc.layers[0].id;
|
||||
app.run("ui.layerOptions", json!({})).unwrap();
|
||||
let d = app.ui.dialog.clone().unwrap();
|
||||
assert_eq!((d.kind.as_str(), d.str("name"), d.str("color")), (KIND, "Layer 1".to_string(), "Light Blue".to_string()));
|
||||
draw(&mut app);
|
||||
set(&mut app, "name", json!("Sky"));
|
||||
set(&mut app, "color", json!("#00ff00"));
|
||||
set(&mut app, "dimImages", json!(true));
|
||||
set(&mut app, "dimPercent", json!(30));
|
||||
set(&mut app, "preview", json!(false));
|
||||
let undo = app.session.doc().unwrap().history.undo.len();
|
||||
super::super::confirm(&mut app).unwrap();
|
||||
assert!(app.ui.dialog.is_none());
|
||||
let n = app.session.doc().unwrap().doc.node(layer).unwrap().clone();
|
||||
assert_eq!(n.display_name(), "Sky");
|
||||
assert!(matches!(n.kind, NodeKind::Layer { color: LayerColor::Custom([0, 255, 0]), dim_images: Some(30), preview: false, .. }));
|
||||
assert_eq!(app.session.doc().unwrap().history.undo.len(), undo + 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn new_layer_options_make_a_layer_and_objects_get_name_show_and_lock() {
|
||||
let mut app = app();
|
||||
app.run("ui.newLayer", json!({})).unwrap();
|
||||
assert_eq!(app.ui.dialog.as_ref().unwrap().str("name"), "Layer 2");
|
||||
set(&mut app, "name", json!("Ink"));
|
||||
set(&mut app, "template", json!(true));
|
||||
set(&mut app, "locked", json!(true));
|
||||
draw(&mut app);
|
||||
super::super::confirm(&mut app).unwrap();
|
||||
let d = &app.session.doc().unwrap().doc;
|
||||
let top = d.layers.last().unwrap();
|
||||
assert_eq!(top.display_name(), "Ink");
|
||||
assert!(top.is_template() && top.locked);
|
||||
// An object's row: Name, Show and Lock.
|
||||
let r = app.session.execute("shape.rectangle", &json!({"x": 0, "y": 0, "width": 10, "height": 10}));
|
||||
assert!(r.is_ok());
|
||||
app.session.execute("layer.setCurrent", &json!({"id": app.session.doc().unwrap().doc.layers[0].id.0})).unwrap();
|
||||
let rect = app.session.execute("shape.rectangle", &json!({"x": 0, "y": 0, "width": 10, "height": 10})).unwrap()["id"].as_u64().unwrap();
|
||||
app.run("ui.layerOptions", json!({"id": rect})).unwrap();
|
||||
assert!(!app.ui.dialog.as_ref().unwrap().bool("layer"));
|
||||
draw(&mut app);
|
||||
set(&mut app, "name", json!("Box"));
|
||||
set(&mut app, "locked", json!(true));
|
||||
let (cmd, p) = params(app.ui.dialog.as_ref().unwrap());
|
||||
assert_eq!(cmd, "layer.setProps");
|
||||
assert!(p.get("template").is_none());
|
||||
super::super::confirm(&mut app).unwrap();
|
||||
let n = app.session.doc().unwrap().doc.node(NodeId(rect)).unwrap().clone();
|
||||
assert_eq!((n.display_name(), n.locked), ("Box".to_string(), true));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
//! Layers panel › Panel Options…: Show Layers Only, the row size and which rows show thumbnails.
|
||||
//!
|
||||
//! Fields: `layersOnly`, `rowSize` (`small`, `medium`, `large` or `other`), `otherSize` (points,
|
||||
//! 12–100, for `other`), `thumbLayers`, `thumbGroups` and `thumbObjects`. OK applies them to the
|
||||
//! panel (kept with the UI state).
|
||||
|
||||
use serde_json::{Value, json};
|
||||
|
||||
use super::DialogSpec;
|
||||
use super::swatch_options::{grid, label};
|
||||
use crate::panels::layers::{PanelOptions, ROW_LARGE, ROW_MEDIUM, ROW_SMALL};
|
||||
use crate::state::Dialog;
|
||||
use crate::{VectorcraftApp, widgets};
|
||||
|
||||
/// The dialog kind of the Layers Panel Options.
|
||||
pub const KIND: &str = "layersPanelOptions";
|
||||
|
||||
pub(super) const SPEC: DialogSpec =
|
||||
DialogSpec { heading: |_| tl!("Layers Panel Options").into(), body, confirm, min_width: 300.0, ..DialogSpec::FORM };
|
||||
|
||||
const SIZES: [(&str, &str, f32); 3] = [("small", "Small", ROW_SMALL), ("medium", "Medium", ROW_MEDIUM), ("large", "Large", ROW_LARGE)];
|
||||
|
||||
/// `ui.layersPanelOptions`: open the dialog on the panel's options.
|
||||
pub fn open(app: &mut VectorcraftApp) -> Result<Value, String> {
|
||||
let o = &app.ui.layers_panel;
|
||||
let size = SIZES.iter().find(|s| (s.2 - o.row()).abs() < 0.01).map_or("other", |s| s.0);
|
||||
let fields = json!({
|
||||
"layersOnly": o.layers_only, "rowSize": size, "otherSize": o.row(),
|
||||
"thumbLayers": o.thumb_layers, "thumbGroups": o.thumb_groups, "thumbObjects": o.thumb_objects,
|
||||
});
|
||||
app.ui.dialog = Some(Dialog::new(KIND, fields));
|
||||
Ok(Value::Null)
|
||||
}
|
||||
|
||||
fn check(ui: &mut egui::Ui, d: &mut Dialog, key: &str, text: &str) {
|
||||
let v = d.bool(key);
|
||||
if widgets::check(ui, text, v, true) {
|
||||
d.fields.insert(key.into(), json!(!v));
|
||||
}
|
||||
}
|
||||
|
||||
fn body(_: &mut VectorcraftApp, ui: &mut egui::Ui, d: &mut Dialog) -> bool {
|
||||
check(ui, d, "layersOnly", tl!("Show Layers Only"));
|
||||
ui.add_space(8.0);
|
||||
grid(ui, |ui| {
|
||||
label(ui, tl!("Row Size:"));
|
||||
ui.horizontal(|ui| {
|
||||
let cur = d.str("rowSize");
|
||||
let labels: Vec<&str> = SIZES.iter().map(|s| s.1).chain(["Other"]).collect();
|
||||
let shown = SIZES.iter().find(|s| s.0 == cur).map_or("Other", |s| s.1);
|
||||
if let Some(i) = widgets::dropdown(ui, "layers-row-size", shown, &labels, 110.0) {
|
||||
d.fields.insert("rowSize".into(), json!(SIZES.get(i).map_or("other", |s| s.0)));
|
||||
}
|
||||
let mut v = d.f64("otherSize", f64::from(ROW_MEDIUM));
|
||||
if ui.add_enabled(d.str("rowSize") == "other", egui::DragValue::new(&mut v).range(12.0..=100.0).suffix(" pt")).changed() {
|
||||
d.fields.insert("otherSize".into(), json!(v.round()));
|
||||
}
|
||||
});
|
||||
ui.end_row();
|
||||
});
|
||||
ui.add_space(8.0);
|
||||
label(ui, tl!("Thumbnails:"));
|
||||
check(ui, d, "thumbLayers", tl!("Layers"));
|
||||
check(ui, d, "thumbGroups", tl!("Group"));
|
||||
check(ui, d, "thumbObjects", tl!("Object"));
|
||||
false
|
||||
}
|
||||
|
||||
/// The options the fields describe.
|
||||
pub(crate) fn options(d: &Dialog) -> PanelOptions {
|
||||
let size = d.str("rowSize");
|
||||
let row_size = SIZES.iter().find(|s| s.0 == size).map_or_else(|| (d.f64("otherSize", f64::from(ROW_MEDIUM)) as f32).clamp(12.0, 100.0), |s| s.2);
|
||||
PanelOptions {
|
||||
layers_only: d.bool("layersOnly"),
|
||||
row_size: if row_size.is_finite() { row_size } else { ROW_MEDIUM },
|
||||
thumb_layers: d.bool("thumbLayers"),
|
||||
thumb_groups: d.bool("thumbGroups"),
|
||||
thumb_objects: d.bool("thumbObjects"),
|
||||
}
|
||||
}
|
||||
|
||||
fn confirm(app: &mut VectorcraftApp, d: &Dialog) -> Result<Value, String> {
|
||||
app.ui.layers_panel = options(d);
|
||||
app.ui.dialog = None;
|
||||
Ok(serde_json::to_value(&app.ui.layers_panel).unwrap_or_default())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use vectorcraft_engine::Session;
|
||||
|
||||
#[test]
|
||||
fn panel_options_round_trip_and_apply() {
|
||||
let mut app = VectorcraftApp::new(Session::new(), Default::default());
|
||||
app.session.execute("file.new", &json!({"width": 100, "height": 100})).unwrap();
|
||||
app.run("ui.layersPanelOptions", json!({})).unwrap();
|
||||
assert_eq!(app.ui.dialog.as_ref().unwrap().str("rowSize"), "medium");
|
||||
let ctx = egui::Context::default();
|
||||
crate::theme::install_fonts(&ctx);
|
||||
let mut out = ctx.run_ui(Default::default(), |ui| super::super::show(&mut app, ui.ctx()));
|
||||
out.textures_delta.clear();
|
||||
let d = app.ui.dialog.as_mut().unwrap();
|
||||
d.fields.insert("layersOnly".into(), json!(true));
|
||||
d.fields.insert("rowSize".into(), json!("other"));
|
||||
d.fields.insert("otherSize".into(), json!(500));
|
||||
d.fields.insert("thumbObjects".into(), json!(false));
|
||||
super::super::confirm(&mut app).unwrap();
|
||||
let o = &app.ui.layers_panel;
|
||||
assert!(o.layers_only && !o.thumb_objects && o.thumb_layers);
|
||||
assert_eq!(o.row(), 100.0, "kept to 100 pt");
|
||||
app.run("ui.layersPanelOptions", json!({})).unwrap();
|
||||
app.ui.dialog.as_mut().unwrap().fields.insert("rowSize".into(), json!("large"));
|
||||
super::super::confirm(&mut app).unwrap();
|
||||
assert_eq!(app.ui.layers_panel.row(), ROW_LARGE);
|
||||
}
|
||||
}
|
||||
@@ -33,6 +33,8 @@ mod gradient_stop;
|
||||
pub mod graphic_style_options;
|
||||
pub mod halftone;
|
||||
pub mod import_pdf;
|
||||
pub mod layer_options;
|
||||
pub mod layers_panel_options;
|
||||
pub mod liquify;
|
||||
pub mod missing_links;
|
||||
pub mod new_color_group;
|
||||
@@ -267,6 +269,8 @@ registry! {
|
||||
PerspectiveGridOptions: [perspective_options::KIND] => perspective_options::SPEC,
|
||||
BlendOptions: [blend_options::KIND] => blend_options::SPEC,
|
||||
PerspectivePlane: [perspective_plane::KIND] => perspective_plane::SPEC,
|
||||
LayerOptions: [layer_options::KIND] => layer_options::SPEC,
|
||||
LayersPanelOptions: [layers_panel_options::KIND] => layers_panel_options::SPEC,
|
||||
}
|
||||
|
||||
/// The button labels the shared dialog frame can show (OK, discard and the fixed Cancel/Close),
|
||||
|
||||
@@ -602,6 +602,7 @@
|
||||
Difference 差異化
|
||||
Differences from {preset}: 與{preset}的差異:
|
||||
Dim Copies to: 將拷貝調暗至:
|
||||
Dim Images to: 影像變暗至:
|
||||
Dimensions 尺寸
|
||||
Direct Selection Tool 直接選取工具
|
||||
Direction Handles 控制把手
|
||||
@@ -697,6 +698,7 @@
|
||||
Duplicate Layer 製作圖層複本
|
||||
Duplicate Paragraph Style 製作段落樣式複本
|
||||
Duplicate Selected Item 製作所選項目的複本
|
||||
Duplicate Selection 製作選取範圍複本
|
||||
Duplicate Slice 製作切片複本
|
||||
Duplicate Swatch 製作色票複本
|
||||
Duplicate Symbol 製作符號複本
|
||||
@@ -817,6 +819,7 @@
|
||||
Expand Compound Shape 展開複合形狀
|
||||
Expand Gradient Mesh 展開漸層網格
|
||||
Expand Gradient To 展開漸層為
|
||||
Expand Layers Panel Rows 展開圖層面板列
|
||||
Expand Options 展開選項
|
||||
Expand Panels 展開面板
|
||||
Expand Repeat 展開重複
|
||||
@@ -924,6 +927,7 @@
|
||||
Flare Tool Options 光暈工具選項
|
||||
Flat Image 平面影像
|
||||
Flatness: 平滑度:
|
||||
Flatten Artwork 將圖稿平面化
|
||||
Flatten Transparency 透明度平面化
|
||||
Flatten Transparency… 透明度平面化…
|
||||
Flattener preset: 平面化預設集:
|
||||
@@ -1070,6 +1074,7 @@
|
||||
Hide Hidden Characters 隱藏隱藏字元
|
||||
Hide Note 隱藏註解
|
||||
Hide Options 隱藏選項
|
||||
Hide Others 隱藏其他
|
||||
Hide Print Tiling 隱藏列印分頁
|
||||
Hide Rulers 隱藏尺標
|
||||
Hide Slices 隱藏切片
|
||||
@@ -1082,6 +1087,7 @@
|
||||
High Performance (discrete) 高效能 (獨立顯示卡)
|
||||
Highlight Alternate Glyphs 醒目提示替代字符
|
||||
Highlight anchors on mouse over 滑鼠移過時醒目提示錨點
|
||||
Highlight Rows 反白顯示列
|
||||
Highlight Substituted Fonts 醒目提示被取代的字體
|
||||
Highlight Substituted Glyphs 醒目提示被取代的字符
|
||||
History 步驟記錄
|
||||
@@ -1222,8 +1228,10 @@
|
||||
Latin-1 Supplement 拉丁文-1 補充
|
||||
Layer 圖層
|
||||
Layer Names 圖層名稱
|
||||
Layer Options 圖層選項
|
||||
Layer Options… 圖層選項…
|
||||
Layers 圖層
|
||||
Layers Panel Options 圖層面板選項
|
||||
Layout 版面配置
|
||||
Layout: 版面配置:
|
||||
Leader: 前導字元:
|
||||
@@ -1307,6 +1315,7 @@
|
||||
Lock (⌘2) 鎖定 (⌘2)
|
||||
Lock Grid 鎖定格點
|
||||
Lock Guides 鎖定參考線
|
||||
Lock Others 鎖定其他
|
||||
Lock Slices 鎖定切片
|
||||
Lock Station Point 鎖定基準點
|
||||
Lossless (lossy WebP isn't available yet) 無失真 (尚未提供有失真的 WebP)
|
||||
@@ -1371,6 +1380,7 @@
|
||||
Merge Graphic Styles 合併繪圖樣式
|
||||
Merge Graphic Styles… 合併繪圖樣式…
|
||||
Merge Layers 合併圖層
|
||||
Merge Selected 合併選取的項目
|
||||
Merge Swatches 合併色票
|
||||
Mesh Tool 網格工具
|
||||
Message: 訊息:
|
||||
@@ -1423,6 +1433,7 @@
|
||||
Move Mesh Point 移動網格點
|
||||
Move Plane 移動平面
|
||||
Move Plane to Match Object 移動平面以符合物件
|
||||
Move Rows 移動列
|
||||
Move Slices 移動切片
|
||||
Move Spine Point 移動脊線點
|
||||
Move Swatch 移動色票
|
||||
@@ -1459,6 +1470,7 @@
|
||||
New Graphic Style 新增繪圖樣式
|
||||
New Graphic Style (Alt-click to name it) 新增繪圖樣式 (Alt+點擊可命名)
|
||||
New Graphic Style… 新增繪圖樣式…
|
||||
New Layer Options… 新圖層選項…
|
||||
New Layer… 新增圖層…
|
||||
New Opacity Masks Are Clipping 新的不透明度遮色片為剪裁
|
||||
New Opacity Masks Are Inverted 新的不透明度遮色片為反轉
|
||||
@@ -1627,6 +1639,7 @@
|
||||
Optimize for fast web view 最佳化以便快速網頁檢視
|
||||
Optimized 最佳化
|
||||
Options 選項
|
||||
Options for Selection… 選取範圍的選項…
|
||||
Options: 選項:
|
||||
Options… 選項…
|
||||
or more spaces with a tab 個以上的空格取代為定位字元
|
||||
@@ -1646,6 +1659,7 @@
|
||||
Out Tangent Handles 外切線控制把手
|
||||
Outer Glow… 外光暈…
|
||||
Outline 外框
|
||||
Outline Others 其他以外框檢視
|
||||
Outline Stroke 外框筆畫
|
||||
Outline Text 外框文字
|
||||
Outlines and Rules 外框與規則線
|
||||
@@ -1815,6 +1829,7 @@
|
||||
Presets: 預設集:
|
||||
Press 按下
|
||||
Preview 預覽
|
||||
Preview All Layers 預覽所有圖層
|
||||
Preview in Browser 在瀏覽器中預覽
|
||||
Preview in Browser needs the desktop app (the web downloads files instead) 在瀏覽器中預覽需要桌面版應用程式 (網頁版會改為下載檔案)
|
||||
Preview Mode 預覽模式
|
||||
@@ -1962,6 +1977,8 @@
|
||||
Release Guides 釋放參考線
|
||||
Release Opacity Mask 釋放不透明度遮色片
|
||||
Release Selection 釋放選取範圍
|
||||
Release to Layers (Build) 釋放至圖層 (累加)
|
||||
Release to Layers (Sequence) 釋放至圖層 (順序)
|
||||
Release with Perspective 釋放透視
|
||||
Relink 重新連結
|
||||
Relink Linked Files to the Document 將連結檔案重新連結至文件
|
||||
@@ -2073,6 +2090,7 @@
|
||||
Rounded Rectangle 圓角矩形
|
||||
Rounded Rectangle Tool 圓角矩形工具
|
||||
Rounded Rectangle… 圓角矩形…
|
||||
Row Size: 列尺寸:
|
||||
Rows: 列數:
|
||||
Rule: 規則線:
|
||||
Rulers 尺標
|
||||
@@ -2206,6 +2224,7 @@
|
||||
Select an image to trace 選取要描圖的影像
|
||||
Select Anchors 選取錨點
|
||||
Select Appearance Item 選取外觀項目
|
||||
Select Art 選取圖稿
|
||||
Select art and choose Object › Pattern › Make. 選取圖稿,然後選擇「物件 › 圖樣 › 製作」。
|
||||
Select art and click New Symbol to make one. 選取圖稿並按一下「新增符號」即可建立。
|
||||
Select art and use New Brush to make one. 選取圖稿,然後使用「新增筆刷」來製作。
|
||||
@@ -2280,6 +2299,7 @@
|
||||
Show Active Plane Widget 顯示作用中平面 Widget
|
||||
Show All 全部顯示
|
||||
Show All Hidden Attributes 顯示所有隱藏屬性
|
||||
Show All Layers 顯示所有圖層
|
||||
Show All Plates 顯示所有色版
|
||||
Show all plates 顯示所有色版
|
||||
Show All Swatches 顯示所有色票
|
||||
@@ -2308,6 +2328,7 @@
|
||||
Show Images in Outline Mode 在外框模式中顯示影像
|
||||
Show in Folder 在資料夾中顯示
|
||||
Show Indic Options 顯示印度文字選項
|
||||
Show Layers Only 僅顯示圖層
|
||||
Show Link Info 顯示連結資訊
|
||||
Show List View 顯示清單檢視
|
||||
Show Mesh 顯示網格
|
||||
@@ -2557,6 +2578,7 @@
|
||||
Three Point Perspective Preset 5 三點透視預設集 5
|
||||
Threshold 臨界值
|
||||
Thumbnail View 縮圖檢視
|
||||
Thumbnails: 縮圖:
|
||||
Tile 並排顯示
|
||||
Tile Edge Color 拼貼邊緣顏色
|
||||
Tile Edge Color… 拼貼邊緣顏色…
|
||||
@@ -2672,6 +2694,7 @@
|
||||
Unlink the mask 取消連結遮色片
|
||||
Unlock 解除鎖定
|
||||
Unlock All 全部解除鎖定
|
||||
Unlock All Layers 解除鎖定所有圖層
|
||||
Unlock Grid 解除鎖定格點
|
||||
Unlock Guides 解除鎖定參考線
|
||||
Unnamed Objects 未命名物件
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 47.
|
@@ -305,6 +305,30 @@ pub const UI_COMMANDS: &[(&str, &str, &str, &str)] = &[
|
||||
"",
|
||||
"{names: [two or more styles]} open Graphic Style Options (dialog `graphicStyleOptions`, field `name`) to name the style OK merges from them (graphicStyle.merge)",
|
||||
),
|
||||
(
|
||||
"ui.layerOptions",
|
||||
"Options for Selection…",
|
||||
"",
|
||||
"{ids?|id?} open Layer Options for Layers panel rows (default: the highlighted rows, else the current layer), filled in from the first: dialog `layerOptions`, fields name, color (a preset name such as \"Light Blue\" or #rrggbb), template, locked, visible, printable, preview, dimImages (bool), dimPercent (0–100); an object's row has name, visible and locked. OK runs layer.setProps (one undo step)",
|
||||
),
|
||||
(
|
||||
"ui.newLayer",
|
||||
"New Layer Options…",
|
||||
"",
|
||||
"{sublayer?: bool} open Layer Options for a new layer (or a sublayer of the current layer), named and coloured as it would be: dialog `layerOptions` with mode new|newSublayer; OK runs layer.new or layer.newSublayer with the fields",
|
||||
),
|
||||
(
|
||||
"ui.layersPanelOptions",
|
||||
"Panel Options…",
|
||||
"",
|
||||
"{} open the Layers panel's Panel Options: dialog `layersPanelOptions`, fields layersOnly, rowSize (small|medium|large|other), otherSize (12–100 pt), thumbLayers, thumbGroups, thumbObjects; OK applies them (kept with the UI state)",
|
||||
),
|
||||
(
|
||||
"ui.layersExpand",
|
||||
"Expand Layers Panel Rows",
|
||||
"",
|
||||
"{ids?: [id…] (default: every row that holds others), open?: bool (default true)} open or close rows of the Layers panel, as clicking their triangles does → {count}",
|
||||
),
|
||||
(
|
||||
"ui.tileEdgeColor",
|
||||
"Tile Edge Color…",
|
||||
@@ -1022,6 +1046,10 @@ pub fn run_ui_command(app: &mut VectorcraftApp, id: &str, p: &Value) -> Option<R
|
||||
crate::dialogs::graphic_style_options::open_merge(app, names.unwrap_or_default())
|
||||
}
|
||||
"ui.tileEdgeColor" => crate::dialogs::tile_edge_color::open(app),
|
||||
"ui.layerOptions" => crate::dialogs::layer_options::open(app, p),
|
||||
"ui.newLayer" => crate::dialogs::layer_options::open_new(app, p),
|
||||
"ui.layersPanelOptions" => crate::dialogs::layers_panel_options::open(app),
|
||||
"ui.layersExpand" => crate::panels::layers::expand(app, p),
|
||||
"ui.flattenTransparencyDialog" => {
|
||||
crate::dialogs::flatten::open(app);
|
||||
Ok(Value::Null)
|
||||
|
||||
+851
-189
File diff suppressed because it is too large
Load Diff
@@ -339,6 +339,12 @@ pub struct UiState {
|
||||
/// becoming active, leaves it.
|
||||
#[serde(skip)]
|
||||
pub home: Option<(Option<u64>, usize)>,
|
||||
/// Layers panel › Panel Options… (row size, thumbnails, Show Layers Only).
|
||||
#[serde(default)]
|
||||
pub layers_panel: crate::panels::layers::PanelOptions,
|
||||
/// The Layers panel's open rows, per open document (`DocState::uid` → node ids).
|
||||
#[serde(skip)]
|
||||
pub layers_expanded: std::collections::HashMap<u64, std::collections::HashSet<u64>>,
|
||||
/// The desktop window's size, position and maximized state, saved when the app quits and
|
||||
/// restored at the next launch (the desktop host reads and writes it; none on the web).
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
@@ -422,6 +428,8 @@ impl Default for UiState {
|
||||
eps_options: Value::Null,
|
||||
dxf_import: Value::Null,
|
||||
home: None,
|
||||
layers_panel: Default::default(),
|
||||
layers_expanded: Default::default(),
|
||||
window: None,
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user