graphic design tool
git clone https://git.lucas.co/cce-designer.git
fix: the transport and the dialog's marks are cce-icons glyphs
The playbar's play, pause and step buttons drew their symbols from
quads and vectors; they are the play / pause / step-back / step-forward
glyphs now, through PaintCtx::icon, on a square half the button's side.
The dialog's row painter draws the page mark a leading row carries in
its chord column as the chevron-right glyph, and a label that begins
with the toolkit's BACK_MARK (the visualizer's Back to Visualizers) as
chevron-left, where both were characters in the font. The context
menus' switch marks spell cce-ui's MARK_ON / MARK_OFF, which the
toolkit already draws as circle / circle-outline.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
CLAUDE.md | 17 ++++++++++++++---
src/app.rs | 22 +++++++++++-----------
src/dialog.rs | 48 ++++++++++++++++++++++++++++++++++++------------
src/plate_menu.rs | 12 ++++++------
src/playbar.rs | 46 +++++++++++++++-------------------------------
src/visualizer.rs | 2 +-
6 files changed, 83 insertions(+), 64 deletions(-)
diff --git a/CLAUDE.md b/CLAUDE.md
index 3a2207c..dee5689 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -2663,7 +2663,14 @@ and **Attribute Visualizers** (the dialog); the network menu's **Add Node**
are); the node menu's **Rename** (the dialog). Inside the dialog the rows
that turn it into another list are marked `›` in the chord column and take
the forward swipe too (`dialog::dialog_row_leads`): the palette's Group
-Markers and Attribute Visualizers, a visualizer and Add Visualizer.
+Markers and Attribute Visualizers, a visualizer and Add Visualizer. The
+mark rides the row's chord TEXT (cce-ui's `PAGE_MARK`), and a label that
+begins with `BACK_MARK` (the visualizer's Back to Visualizers) wears the
+other; the dialog's row painter draws them as the `chevron-right` /
+`chevron-left` glyphs, as the toolkit menu does, never as the characters
+(since 2026-10-05, the cce-icons rule: every symbol the app draws is a
+glyph). The menus' `● ` / `○ ` switch marks are cce-ui's `MARK_ON` /
+`MARK_OFF`, which the toolkit draws as `circle` / `circle-outline`.
- **`State::run_menu_turn` is the one dispatch**, reached by a left press
(`press_menu_turn`, ahead of every menu's own click handler) and by a swipe
@@ -3025,7 +3032,7 @@ in its `playbar` block. `the_playbar_menu_sets_the_rate_and_the_range`
drives it by pointer.
**The step buttons** (since 2026-10-01): Previous Frame and Next Frame
-stand either side of the play button (|◁ ▷ ▷|), each a whole frame off
+stand either side of the play button, each a whole frame off
the ROUNDED frame and inside the range, without pausing —
`Playbar::step`, which the Left / Right chords (`frame_prev` /
`frame_next`) share. Show Step Buttons (`toggle_playbar_step_buttons`,
@@ -3033,7 +3040,11 @@ unbound, a switch in the palette too) takes them away and the track
widens into their room; on by default, persisted as top-level
`playbar_step_buttons` in state.kdl beside `playbar_repeat`, and reported
as `step_buttons` in `get_state`'s playbar block.
-`the_playbar_step_buttons_step_and_can_be_hidden` is the test.
+`the_playbar_step_buttons_step_and_can_be_hidden` is the test. The
+transport's symbols are cce-icons glyphs (`play` / `pause`,
+`step-back` / `step-forward`, since 2026-10-05) drawn through
+`PaintCtx::icon` on a square half the button's side; they were a
+triangle of vectors and bars built from quads.
### Display mode: the viewport menu, and smooth shading
diff --git a/src/app.rs b/src/app.rs
index 7af2480..dec0215 100644
--- a/src/app.rs
+++ b/src/app.rs
@@ -5878,11 +5878,11 @@ impl State {
pub(crate) fn playbar_menu_rows(&self) -> (Vec<String>, Vec<PlaybarMenuAction>) {
let mut options = Vec::new();
let mut actions = Vec::new();
- let mark = |on: bool| if on { "●" } else { "○" };
+ let mark = |on: bool| if on { cce_ui::widget::context_menu::MARK_ON } else { cce_ui::widget::context_menu::MARK_OFF };
let mut command = |options: &mut Vec<String>, actions: &mut Vec<PlaybarMenuAction>, id: &'static str| {
let Some(c) = crate::command::by_id(id) else { return };
let label = match self.command_toggle_state(id) {
- Some(on) => format!("{} {}", mark(on), c.label),
+ Some(on) => format!("{}{}", mark(on), c.label),
None => c.label.to_string(),
};
options.push(label);
@@ -6435,7 +6435,7 @@ impl State {
pub(crate) fn viewport_menu_rows_of(&self, page: Option<ViewportMenuPage>) -> (Vec<String>, Vec<ViewportMenuAction>) {
let mut options: Vec<String> = Vec::new();
let mut actions: Vec<ViewportMenuAction> = Vec::new();
- let mark = |on: bool| if on { "●" } else { "○" };
+ let mark = |on: bool| if on { cce_ui::widget::context_menu::MARK_ON } else { cce_ui::widget::context_menu::MARK_OFF };
let label = |id: &str, fallback: &'static str| crate::command::by_id(id).map(|c| c.label).unwrap_or(fallback);
let row = |options: &mut Vec<String>, actions: &mut Vec<ViewportMenuAction>, text: String, a: ViewportMenuAction| {
options.push(text);
@@ -6444,7 +6444,7 @@ impl State {
let sep = ViewportMenuAction::Separator;
let toggle = |options: &mut Vec<String>, actions: &mut Vec<ViewportMenuAction>, id: &'static str| {
let on = self.command_toggle_state(id).unwrap_or(false);
- options.push(format!("{} {}", mark(on), label(id, id)));
+ options.push(format!("{}{}", mark(on), label(id, id)));
actions.push(ViewportMenuAction::Command(id));
};
@@ -6457,8 +6457,8 @@ impl State {
// Surface.
row(&mut options, &mut actions, "-".into(), sep);
- row(&mut options, &mut actions, format!("{} Flat Shading", mark(!self.smooth_shading)), ViewportMenuAction::Shading(false));
- row(&mut options, &mut actions, format!("{} Smooth Shading", mark(self.smooth_shading)), ViewportMenuAction::Shading(true));
+ row(&mut options, &mut actions, format!("{}Flat Shading", mark(!self.smooth_shading)), ViewportMenuAction::Shading(false));
+ row(&mut options, &mut actions, format!("{}Smooth Shading", mark(self.smooth_shading)), ViewportMenuAction::Shading(true));
row(&mut options, &mut actions, "Opacity".into(), ViewportMenuAction::OpacitySlider);
toggle(&mut options, &mut actions, "toggle_show_occluded");
return (options, actions);
@@ -6524,16 +6524,16 @@ impl State {
if self.tab_dock_of_pane(crate::slots::NETWORK_PANEL2_IDX).is_some() {
options.push("-".to_string());
actions.push(ViewportMenuAction::Separator);
- let mark = |on: bool| if on { "●" } else { "○" };
- options.push(format!("{} Follow Active Editor", mark(self.viewport_pin.is_none())));
+ let mark = |on: bool| if on { cce_ui::widget::context_menu::MARK_ON } else { cce_ui::widget::context_menu::MARK_OFF };
+ options.push(format!("{}Follow Active Editor", mark(self.viewport_pin.is_none())));
actions.push(ViewportMenuAction::PinFollow);
options.push(format!(
- "{} Pin: Network",
+ "{}Pin: Network",
mark(self.viewport_pin == Some(CONTENT_IDX))
));
actions.push(ViewportMenuAction::PinTo(CONTENT_IDX));
options.push(format!(
- "{} Pin: Network 2",
+ "{}Pin: Network 2",
mark(self.viewport_pin == Some(crate::slots::CONTENT2_IDX))
));
actions.push(ViewportMenuAction::PinTo(crate::slots::CONTENT2_IDX));
@@ -6668,7 +6668,7 @@ impl State {
Some(id) => {
let Some(cmd) = crate::command::by_id(id) else { continue };
options.push(match self.command_toggle_state(id) {
- Some(on) => format!("{} {}", if on { "●" } else { "○" }, cmd.label),
+ Some(on) => format!("{}{}", if on { cce_ui::widget::context_menu::MARK_ON } else { cce_ui::widget::context_menu::MARK_OFF }, cmd.label),
None => cmd.label.to_string(),
});
actions.push(NetworkMenuAction::Command(cmd.id));
diff --git a/src/dialog.rs b/src/dialog.rs
index 4a19f2b..7c6bc8d 100644
--- a/src/dialog.rs
+++ b/src/dialog.rs
@@ -1070,12 +1070,6 @@ impl Paint for Dialog {
}
let ty = cce_ui::layout::align_text_y(r.y, r.height, font_size, 0.0);
let label_color = if i == self.selected { [0xf4, 0xf4, 0xfa] } else { [0xcc, 0xcc, 0xd4] };
- // What the chord column shows: the chord, or a choice row's
- // current option and its dropdown mark — a value, so it wears
- // the label's colour rather than the chord's grey — in a well
- // that reads as the dropdown's trigger and lifts under the
- // pointer. The mark is cce-icons' chevron, left out if the icon
- // set is missing (the font has no glyph for a triangle).
// What the chord column shows: the chord. A choice row shows a
// dropdown in the control band instead, as a slider or a colour
// row does.
@@ -1083,8 +1077,22 @@ impl Paint for Dialog {
Some(Control::Choice { .. }) => (String::new(), label_color),
_ => (row.chord.clone(), [0x85, 0x85, 0x92]),
};
+ // A row that leads to another list ends in the page mark
+ // (`refresh_dialog_rows`): drawn as the `chevron-right` glyph a
+ // menu's page row wears, never as the character. Likewise the
+ // back mark that leads a label (a visualizer's Back row) is the
+ // `chevron-left` glyph, as a menu page's back band is.
+ let (right_text, leads) = match right_text.strip_suffix(PAGE_MARK) {
+ Some(t) => (t.trim_end().to_string(), true),
+ None => (right_text, false),
+ };
+ let (label, back) = match row.label.strip_prefix(BACK_MARK) {
+ Some(rest) => (rest.trim_start(), true),
+ None => (row.label.as_str(), false),
+ };
+ let chev = (font_size * 0.8).round();
let text_w = if right_text.is_empty() { 0.0 } else { shaped_width(&right_text, &family, font_size) };
- let right_w = text_w;
+ let right_w = text_w + if !leads { 0.0 } else if text_w > 0.0 { chev + GLYPH_GAP } else { chev };
// The label's clip stops short of the chord column so a long
// label is cut by it rather than running under it — or short of
// the band, for a choice row.
@@ -1093,12 +1101,18 @@ impl Paint for Dialog {
Some(Control::Choice { .. }) => self.slider_band_rect(r).x - 12.0,
_ => chord_right - if right_w > 0.0 { right_w + 12.0 } else { 0.0 },
};
- let label_x = r.x + 8.0;
+ let glyph_y = r.y + (r.height - chev) * 0.5;
+ let glyph_color = |c: [u8; 3]| [c[0] as f32 / 255.0, c[1] as f32 / 255.0, c[2] as f32 / 255.0, 1.0];
+ if back {
+ let at = Rect { x: r.x + 8.0, y: glyph_y, width: chev, height: chev };
+ ctx.icon("chevron-left", at, glyph_color(label_color));
+ }
+ let label_x = r.x + 8.0 + if back { chev + GLYPH_GAP } else { 0.0 };
ctx.text_with(
if row.truncate_head {
- fit_head(&row.label, label_right - label_x)
+ fit_head(label, label_right - label_x)
} else {
- fit(&row.label, label_right - label_x)
+ fit(label, label_right - label_x)
},
label_x,
ty,
@@ -1110,6 +1124,10 @@ impl Paint for Dialog {
if text_w > 0.0 {
ctx.text_with(right_text, chord_right - right_w, ty, font_size, right_color, Some(family.clone()), own);
}
+ if leads {
+ let at = Rect { x: chord_right - chev, y: glyph_y, width: chev, height: chev };
+ ctx.icon("chevron-right", at, glyph_color(right_color));
+ }
match &row.control {
Some(Control::Toggle(on)) => {
let tr = Rect {
@@ -1588,8 +1606,14 @@ pub const SETTINGS: &[Setting] = &[
Setting::field("Origin Size", "origin_size", Ctl::Spin { min: 1.0, max: 50.0, unit: 10.0 }),
];
-/// The page mark, as a menu's page row wears it.
-use cce_ui::widget::context_menu::PAGE_MARK;
+/// The page mark, as a menu's page row wears it, and the back mark a menu
+/// page's back band leads with. Both ride the row's TEXT (the chord and the
+/// label) and are drawn as cce-icons chevrons by the row painter.
+pub use cce_ui::widget::context_menu::{BACK_MARK, PAGE_MARK};
+
+/// The gap between a row's chevron glyph and the text beside it — the
+/// toolkit menu's own.
+const GLYPH_GAP: f32 = 6.0;
/// Whether a row of the dialog in `mode` turns it into another list: the
/// palette's Group Markers and Attribute Visualizers, a visualizer of the
diff --git a/src/plate_menu.rs b/src/plate_menu.rs
index fe99f0b..932ac70 100644
--- a/src/plate_menu.rs
+++ b/src/plate_menu.rs
@@ -167,16 +167,16 @@ impl State {
{
separate(&mut options, &mut actions);
let pin = if idx == PARAM_IDX { self.params_pin } else { self.spreadsheet_pin };
- let mark = |on: bool| if on { "●" } else { "○" };
- options.push(format!("{} Follow Active Editor", mark(pin.is_none())));
+ let mark = |on: bool| if on { cce_ui::widget::context_menu::MARK_ON } else { cce_ui::widget::context_menu::MARK_OFF };
+ options.push(format!("{}Follow Active Editor", mark(pin.is_none())));
actions.push(PlateMenuAction::PinFollow);
options.push(format!(
- "{} Pin: Network",
+ "{}Pin: Network",
mark(pin == Some(crate::slots::CONTENT_IDX))
));
actions.push(PlateMenuAction::PinTo(crate::slots::CONTENT_IDX));
options.push(format!(
- "{} Pin: Network 2",
+ "{}Pin: Network 2",
mark(pin == Some(crate::slots::CONTENT2_IDX))
));
actions.push(PlateMenuAction::PinTo(crate::slots::CONTENT2_IDX));
@@ -193,8 +193,8 @@ impl State {
// not just as actions.
separate(&mut options, &mut actions);
for &t in &self.dock_tabs[d as usize] {
- let mark = if t == idx { "●" } else { "○" };
- options.push(format!("{mark} {}", plate_title(t)));
+ let mark = if t == idx { cce_ui::widget::context_menu::MARK_ON } else { cce_ui::widget::context_menu::MARK_OFF };
+ options.push(format!("{mark}{}", plate_title(t)));
actions.push(PlateMenuAction::ShowTab(t));
}
let mut managed = false;
diff --git a/src/playbar.rs b/src/playbar.rs
index 681d176..2f9655b 100644
--- a/src/playbar.rs
+++ b/src/playbar.rs
@@ -7,7 +7,7 @@
use cce_ui::colors;
use cce_ui::scene::layout::Rect;
-use cce_ui::scene::paint::{Cap, PaintCtx};
+use cce_ui::scene::paint::PaintCtx;
use cce_ui::widget::*;
#[derive(Debug, Clone)]
@@ -269,41 +269,25 @@ impl Paint for Playbar {
}
impl Playbar {
+ /// The play button's glyph: `pause` while playing, else `play`.
fn paint_play_icon(&self, b: Rect, icon: [f32; 4], ctx: &mut PaintCtx) {
- if self.playing {
- // Pause: two bars.
- let bw = b.width * 0.16;
- let bh = b.height * 0.44;
- let by = b.y + (b.height - bh) * 0.5;
- ctx.quad(Rect { x: b.x + b.width * 0.32 - bw * 0.5, y: by, width: bw, height: bh }, icon);
- ctx.quad(Rect { x: b.x + b.width * 0.68 - bw * 0.5, y: by, width: bw, height: bh }, icon);
- } else {
- // Play: triangle outline (no filled-triangle prim; the DE's line
- // aesthetic reads fine here).
- let (cx, cy) = (b.x + b.width * 0.54, b.y + b.height * 0.5);
- let r = b.width * 0.24;
- let (x0, y0) = (cx - r * 0.6, cy - r);
- let (x1, y1) = (cx - r * 0.6, cy + r);
- let (x2, y2) = (cx + r, cy);
- ctx.vector(x0, y0, x1, y1, 1.5, icon, Cap::Round);
- ctx.vector(x1, y1, x2, y2, 1.5, icon, Cap::Round);
- ctx.vector(x2, y2, x0, y0, 1.5, icon, Cap::Round);
- }
+ ctx.icon(if self.playing { "pause" } else { "play" }, glyph_rect(b), icon);
}
}
-/// A step button's icon: a triangle outline toward the step and a bar at
-/// its point, the transport's frame-step glyph (|◁ and ▷|).
+/// A step button's glyph: cce-icons' `step-back` / `step-forward`, the
+/// transport's frame-step pair.
fn paint_step_icon(b: Rect, forward: bool, icon: [f32; 4], ctx: &mut PaintCtx) {
- let dir = if forward { 1.0 } else { -1.0 };
- let (cx, cy) = (b.x + b.width * 0.5, b.y + b.height * 0.5);
- let r = b.width * 0.2;
- let (x0, x1) = (cx - dir * r * 0.75, cx + dir * r * 0.65);
- ctx.vector(x0, cy - r, x0, cy + r, 1.5, icon, Cap::Round);
- ctx.vector(x0, cy + r, x1, cy, 1.5, icon, Cap::Round);
- ctx.vector(x1, cy, x0, cy - r, 1.5, icon, Cap::Round);
- let bx = x1 + dir * 1.5;
- ctx.vector(bx, cy - r, bx, cy + r, 1.5, icon, Cap::Round);
+ ctx.icon(if forward { "step-forward" } else { "step-back" }, glyph_rect(b), icon);
+}
+
+/// Where a transport glyph stands in its button: a square half the
+/// button's shorter side, centred — the footprint the hand-drawn triangle
+/// and bars had. A missing icon set draws nothing; there is no character
+/// to fall back to that is not itself a symbol.
+fn glyph_rect(b: Rect) -> Rect {
+ let side = (b.width.min(b.height) * 0.5).round();
+ Rect { x: (b.x + (b.width - side) * 0.5).round(), y: (b.y + (b.height - side) * 0.5).round(), width: side, height: side }
}
impl Input for Playbar {
diff --git a/src/visualizer.rs b/src/visualizer.rs
index c693b5e..59ff996 100644
--- a/src/visualizer.rs
+++ b/src/visualizer.rs
@@ -357,7 +357,7 @@ impl State {
let index = if v.group.is_empty() { 0 } else { groups.iter().position(|g| *g == v.group).unwrap_or(0) };
rows.push(row("group", "Group", Some(Control::Choice { options: groups, index })));
rows.push(row("delete", "Delete Visualizer", None));
- rows.push(row("back", "‹ Back to Visualizers", None));
+ rows.push(row("back", &format!("{} Back to Visualizers", crate::dialog::BACK_MARK), None));
rows
}