ui: bolder tab glyphs, and the trigger shares the Signals pill's picture
The previous pair were detailed miniatures — 1px ticks, 2px dashes, a 13-column wave — and at 32x16 that reads as *texture* rather than as a shape, which is what "not clear" looks like. All three glyphs are redrawn bold: one thick band, two thick dashes, few features. - **Measure** is two cursors beside the ruler they read against. Each cursor is a cap over two bold dashes (4px), where three 2px dashes read as a dotted column; the ruler is two bands with three 2x4 marks between them and **no end caps**, because a closed frame reads as a rectangle and costs a fifth of the ink on two lines that say nothing. This is the layout whose first attempt looked like two icons in a box — the difference is entirely the weight and the open ends. - **Signals** is a signal rising above its level beside two bold channel rows. Half a period instead of a full one: at this size a full period is a zigzag and a hump is unmistakably a wave, and the level's marks survive on both sides of it instead of being swallowed by the crest. - **The trigger's own icon** is now the same picture as the Signals pill, so the pill and the button that turns the trigger on are recognisably about the same thing. Each was rendered and read back as an ink map at 1:1 and at 2x before it was installed, and three candidates were compared for the Measure glyph (markers *on* a scale, cursors *over* a ruler, and this): the first two turned the marks and the shafts into blobs at the crossings. Ink is 18%, 30% and 29%, inside the range the icons above them set, and everything stays inset two pixels. Verified: `toolbar icons: trigger=udpscope-trigger-symbolic(ok)`; `page icons: measure=udpscope-tab-measure-symbolic(ok) signals=udpscope-tab-signals-symbolic(ok)`; `switcher: policy=narrow pills=2 labels=0 names=2 icon=32x32 size=308x36`; `[side-tab-probe] … advanced=4`; the Preferences dialog opened in the same run with no "not present"; `make test-ui` 14 files; zero `Gtk-CRITICAL`. The icons' blob is 11774 bytes, still over the C99 limit, so the clean-build warning count stays four.
This commit is contained in:
1 parent
574f47f769
commit
8c9c6ff7fd
6 files changed
+76
-140
No files matched your search
@@ -2394,8 +2394,8 @@ now — they are listed so they are not mistaken for anything else:
|
||||
section was short of room, and finding one meant scrolling past the others. The built
|
||||
sidebar is a **Measure** tab (the cursors readout and the measurements) and a **Signals**
|
||||
tab (the trigger and the channels), **each a pill with its own glyph and no text** — the
|
||||
cursors and the ruler for Measure, a sine crossing its trigger level and the channel list for
|
||||
Signals (the same picture the toolbar's trigger button carries), drawn as a
|
||||
cursors and the ruler for Measure, a signal rising above its trigger level and the channel list
|
||||
for Signals (the same picture the toolbar's trigger button carries), drawn as a
|
||||
32x16 pair so both motifs are legible at a glance — with the words kept as each pill's tooltip and
|
||||
accessible name. The design spec had the switcher showing *titles* and neither page an
|
||||
`icon-name`; `narrow` alone does not do it (libadwaita still draws a label beside the icon,
|
||||
|
||||
@@ -61,74 +61,24 @@ Part of [CLAUDE.md](../../CLAUDE.md). These are rules that cost a defect to lear
|
||||
an icon with a live user is not an orphan, whatever the toggle that introduced it did -- and it is
|
||||
the Preferences window's icon, not the tab's.
|
||||
|
||||
The two **tabs** got glyphs of their own, and each is a *pair* rather than one of the icons above:
|
||||
`udpscope-tab-measure-symbolic` is the cursors and the ruler they read against, and
|
||||
`udpscope-tab-signals-symbolic` is the trigger's edge above the channel list it acts on. They are not
|
||||
reuses because a glyph the toolbar already carries means two things the moment a tab shows it too. Both
|
||||
are **32x16** composites: the Measure pill is two cursors *standing on* the scale they read against — the
|
||||
one band with alternating marks, each marker centred on one of them, which is one instrument rather
|
||||
than two icons in a box — and the Signals pill a sine with a dashed level through it beside the
|
||||
channel list. That picture is shared on
|
||||
purpose: the trigger's *own* toolbar icon (`udpscope-trigger-symbolic`) is the same sine and the same
|
||||
level, one period of one-pixel stairs, so the pill and the button that turns the trigger on are
|
||||
recognisably about the same thing. The trigger glyph used to be a rising edge, and one edge names only
|
||||
one of the four shape triggers the panel offers — a wave crossing a level names the trigger's job.
|
||||
Wider than tall on purpose — a 16px square cell carries
|
||||
one motif, and squeezing a pair into it is what turns both into a smear — and the pill has the width for
|
||||
it, in a 300px column whose switcher has two pills in it. **GDK paints an icon into a square box whatever
|
||||
the document's aspect** (measured: `gtk_icon_theme_lookup_icon()` at 32 reports `32x32`, and librsvg
|
||||
fits it to `32x16`), so the pair is letterboxed, not stretched — and the pill's image is given
|
||||
`pixel_size` 32 in `name_side_tabs()`, which draws the artwork at exactly the size it is written, so its
|
||||
2px bars stay crisp. A fractional scale is what blurs them, which is why nothing here is drawn at 24 or
|
||||
48. The bars in the Signals list are all one length, because unequal ones read as a bar chart and a chart
|
||||
is not a list; their ink fractions (23% and 33%) sit inside the range the icons above set, which is the
|
||||
check that a composite has not turned into a blot. Beside them in
|
||||
`src/icons/scalable/apps/` are the two
|
||||
**application** icons, `org.udpscope.app.svg` (128px colour artwork) and
|
||||
`org.udpscope.app-symbolic.svg` (16px). The `apps` context is where an icon theme keeps an
|
||||
application's own icon and is where the **in-process** window icon resolves from; it is also
|
||||
the layout that `cmake/install_desktop_entry.cmake` mirrors when it copies the same two files
|
||||
into `$XDG_DATA_HOME/icons/hicolor/scalable/apps` and `.../symbolic/apps` — those installed
|
||||
copies are what makes `Icon=org.udpscope.app` in the desktop entry resolve (a desktop shell
|
||||
cannot read a GResource compiled into the binary).
|
||||
All nine are bundled by `src/icons/udpscope.gresource.xml` (CMake runs
|
||||
`glib-compile-resources`) and reached through **one**
|
||||
`gtk_icon_theme_add_resource_path()` in `run_ui()`, right after `adw_init()` — not at the top
|
||||
of `build_toolbar()`, where it used to be: there are two consumers now (the window's own icon,
|
||||
set before the toolbar is built, and the toolbar's buttons) and `run_ui()` is the one place
|
||||
ahead of both. `adw_init()` calls `gtk_init()`, which is what makes
|
||||
`gdk_display_get_default()` valid there.
|
||||
A resource rather than files beside the binary because udpscope is run straight out of the
|
||||
build tree and has no install step. Five authoring rules, each of which fails quietly:
|
||||
* **GTK's symbolic recolour only reaches `fill` on `path`, `rect` and `circle`.** It wraps the
|
||||
SVG in a stylesheet for those selectors, so a `stroke`, a `line` or a `polyline` keeps the
|
||||
colour written in the file and stays dark on a dark theme. **In a symbolic icon everything is
|
||||
therefore a filled `path`** — including anything that wants to look stroked, which is drawn as
|
||||
a closed band (two offset curves joined) instead. The rule is scoped to symbolic icons on
|
||||
purpose: `org.udpscope.app.svg` is 128px colour artwork that is never recoloured, so it strokes
|
||||
its sine freely, which is the sane way to draw a smooth one. Do not "fix" one to match the
|
||||
other.
|
||||
* **Whole-pixel coordinates**, because these are drawn at 16 px far more often than not: the
|
||||
first draft placed features on half-pixels and the whole icon was a grey smear at 1x.
|
||||
* `fill-rule="evenodd"` is how the ruler's body is a hole *and* its ticks still fill -- a tick
|
||||
inside the hole is three crossings deep, which is odd. Nonzero would fill the hole back in.
|
||||
* Overlapping subpaths under the default nonzero rule must be **wound the same way** or they
|
||||
cancel instead of uniting. All of them run clockwise.
|
||||
* **A malformed SVG is a build error, and that is the only place it can be caught.** Every
|
||||
`<file>` in `src/icons/udpscope.gresource.xml` carries `preprocess="xml-stripblanks"`, which
|
||||
makes `glib-compile-resources` pipe it through `xmllint` — so `--` inside an XML comment, which
|
||||
is illegal and which librsvg refuses the whole file for, fails the build with a file, a line
|
||||
and a caret under the offending character (`…app-symbolic.svg:5: parser error : Double hyphen
|
||||
within comment`). It has to be here because **no runtime check can see it**: librsvg refuses
|
||||
the file but the icon *name* still resolves, so `gtk_icon_theme_has_icon()` returns true and
|
||||
the icon draws nothing. `gtk_icon_theme_lookup_icon()` cannot tell either (see the `app icons:` line under
|
||||
Verification), `GMarkup` accepts the double hyphen because it is a lenient subset parser, and
|
||||
`gdk_pixbuf` goes through glycin here, which sandboxes a subprocess per load and refuses this
|
||||
project's own icons outright. The cost is that the `xmllint` binary is a build dependency; it
|
||||
ships with libxml2, which GTK already requires.
|
||||
`gtk_icon_theme_has_icon()` is checked for every toolbar button and reported by the dump (see
|
||||
`toolbar icons:` in [probe-readings.md](probe-readings.md)): a resource path that was never added does not raise an
|
||||
error, it draws `image-missing`.
|
||||
The two **tabs** got glyphs of their own, and each is a *pair* rather than one of the icons above.
|
||||
`udpscope-tab-measure-symbolic` is two cursors beside the ruler they read against — each a cap over two
|
||||
bold dashes, beside a ruler of two bands with three marks between them and **no end caps** (a closed
|
||||
frame reads as a rectangle, and costs a fifth of the ink on two lines that say nothing) — and
|
||||
`udpscope-tab-signals-symbolic` is a signal rising above its level, beside two bold channel rows. They
|
||||
are not reuses, because a glyph the toolbar already carries means two things the moment a tab shows it
|
||||
too; instead the *trigger's* own icon (`udpscope-trigger-symbolic`) was redrawn to the Signals pill's
|
||||
picture, so the pill and the button that turns the trigger on are recognisably about the same thing.
|
||||
Both are **32x16** composites drawn **bold** — one thick band, two thick dashes, few features — because
|
||||
at this size a thin wave or a 1px tick reads as *texture* rather than as a shape, which is what "not
|
||||
clear" looks like. Wider than tall on purpose: a 16px square cell carries one motif, not a pair. And
|
||||
**GDK paints an icon into a square box whatever the document's aspect** (measured:
|
||||
`gtk_icon_theme_lookup_icon()` at 32 reports `32x32`, and librsvg fits it to `32x16`), so the pair is
|
||||
letterboxed rather than stretched — and the pill's image is given `pixel_size` 32 in `name_side_tabs()`,
|
||||
which draws the artwork at the size it is written, so its bars stay crisp. Every glyph here is
|
||||
**inset two pixels**, because art flush with the cell edge is what a button's padding or a crop takes a
|
||||
pixel off, and a fractional scale is what would blur it, which is why nothing is drawn at 24 or 48. The
|
||||
two tab glyphs measure 29% and 30% ink, inside the range the icons above them set.
|
||||
- **A shortcut table row's `accel` is GTK accelerator syntax, and a pointer gesture has
|
||||
none.** `src/shortcuts.cpp`'s strings are handed to `AdwShortcutsItem`, which parses them
|
||||
with `gtk_accelerator_parse()` — so they are `"space"`, `"<Control>e"`, `"<Control>slash"`,
|
||||
|
||||
@@ -16,7 +16,7 @@ xvfb-run -a env UDPSCOPE_EXIT_AFTER_MS=8000 UDPSCOPE_ENABLE_ALL_MS=1000 \
|
||||
Two baselines every run is read against. A clean build emits **four** warnings and no more: two
|
||||
`-Wmissing-field-initializers` on `MathNode::source` and `-Woverlength-strings` on **both**
|
||||
generated GResource `.c` files — the help pages' (**514660** bytes) and the icons', whose blob is
|
||||
**12624** against C99's 4095-byte string-literal limit with the seven action icons and the two app icons
|
||||
**11774** against C99's 4095-byte string-literal limit with the seven action icons and the two app icons
|
||||
that remain (a note about a generated file, which GCC itself supports) -- re-measured when
|
||||
the tab work changed the set (that icon left, the two tab glyphs arrived, and this file's
|
||||
help page was rewritten), which did not move the count; re-measure by touching each `*.gresource.xml`
|
||||
|
||||
@@ -1,26 +1,21 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- SPDX-License-Identifier: MIT -->
|
||||
<!-- SPDX-FileCopyrightText: 2026 Martino Ferrari -->
|
||||
<!-- The Measure tab: two cursors standing on the scale they read against. The scale is one band with
|
||||
alternating marks rather than the closed ruler body `udpscope-measure-symbolic` draws for the
|
||||
Preferences window: at 32x16 a frame costs a fifth of the ink on two lines that say nothing, and
|
||||
the cursors are what this tab is *for*. Each marker is centred on one of the marks — a cap over a
|
||||
dashed shaft that meets the scale instead of hovering over it — so the two read as one instrument
|
||||
rather than as two icons sharing a box. Both use the 2px weight of every other icon here, and
|
||||
everything is inset two pixels, because art flush with the cell edge is what a button or a crop
|
||||
clips. -->
|
||||
<!-- The Measure tab: two cursors beside the ruler they read against. Each cursor is a cap over two
|
||||
bold dashes — the mark the toolbar's cursors button carries, at a weight that survives this size.
|
||||
The ruler is two bands with three marks between them and *no end caps*: a closed frame reads as a
|
||||
rectangle, and it costs a fifth of the ink on two lines that say nothing. Both motifs are centred
|
||||
on the same line and share one weight, and everything is inset two pixels. -->
|
||||
<svg height="16px" width="32px" viewBox="0 0 32 16" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill="#2e3436" d="M2 12 H30 V14 H2 Z
|
||||
M5 9 H7 V12 H5 Z
|
||||
M9 10 H11 V12 H9 Z
|
||||
M13 9 H15 V12 H13 Z
|
||||
M17 10 H19 V12 H17 Z
|
||||
M21 9 H23 V12 H21 Z
|
||||
M25 10 H27 V12 H25 Z
|
||||
M7 3 H12 V6 H7 Z
|
||||
M8 7 H11 V10 H8 Z
|
||||
M8 10 H11 V12 H8 Z
|
||||
M19 3 H24 V6 H19 Z
|
||||
M20 7 H23 V10 H20 Z
|
||||
M20 10 H23 V12 H20 Z"/>
|
||||
<path fill="#2e3436" d="M2 2 H7 V5 H2 Z
|
||||
M3 6 H6 V10 H3 Z
|
||||
M3 11 H6 V14 H3 Z
|
||||
M9 2 H14 V5 H9 Z
|
||||
M10 6 H13 V10 H10 Z
|
||||
M10 11 H13 V14 H10 Z
|
||||
M17 4 H30 V6 H17 Z
|
||||
M17 10 H30 V12 H17 Z
|
||||
M18 6 H20 V10 H18 Z
|
||||
M22 6 H24 V10 H22 Z
|
||||
M26 6 H28 V10 H26 Z"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.2 KiB |
@@ -1,32 +1,27 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- SPDX-License-Identifier: MIT -->
|
||||
<!-- SPDX-FileCopyrightText: 2026 Martino Ferrari -->
|
||||
<!-- The Signals tab: the toolbar's trigger picture — a sine with a dashed level through it — beside
|
||||
the channel list. Sharing that picture is the point: the pill and the toolbar button that turns
|
||||
the trigger on should be recognisably about the same thing. Three channel rows on the right, on
|
||||
the left half's centre line, and everything inset two pixels, because art flush with the cell
|
||||
edge is what a button or a crop clips. -->
|
||||
<!-- The Signals tab: the toolbar's trigger picture — a signal above its level — beside the channel
|
||||
list. Sharing that picture is the point: the pill and the button that turns the trigger on should
|
||||
be recognisably about the same thing. Two channel rows on the right, drawn bold for the same
|
||||
reason the wave is, and everything inset two pixels. -->
|
||||
<svg height="16px" width="32px" viewBox="0 0 32 16" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill="#2e3436" d="M2 6 H3 V10 H2 Z
|
||||
M3 4 H4 V8 H3 Z
|
||||
M4 3 H5 V7 H4 Z
|
||||
M5 2 H6 V6 H5 Z
|
||||
M6 3 H7 V7 H6 Z
|
||||
M7 4 H8 V8 H7 Z
|
||||
M8 6 H9 V10 H8 Z
|
||||
M9 8 H10 V12 H9 Z
|
||||
M10 9 H11 V13 H10 Z
|
||||
M11 10 H12 V14 H11 Z
|
||||
M12 9 H13 V13 H12 Z
|
||||
M13 8 H14 V12 H13 Z
|
||||
M14 6 H15 V10 H14 Z
|
||||
M2 7 H5 V9 H2 Z
|
||||
M7 7 H10 V9 H7 Z
|
||||
M12 7 H15 V9 H12 Z
|
||||
M18 2 H21 V5 H18 Z
|
||||
M23 3 H30 V5 H23 Z
|
||||
M18 7 H21 V10 H18 Z
|
||||
M23 8 H30 V10 H23 Z
|
||||
M18 12 H21 V15 H18 Z
|
||||
M23 13 H30 V15 H23 Z"/>
|
||||
<path fill="#2e3436" d="M3 7 H4 V12 H3 Z
|
||||
M4 6 H5 V11 H4 Z
|
||||
M5 4 H6 V9 H5 Z
|
||||
M6 3 H7 V8 H6 Z
|
||||
M7 2 H8 V7 H7 Z
|
||||
M8 2 H9 V7 H8 Z
|
||||
M9 2 H10 V7 H9 Z
|
||||
M10 2 H11 V7 H10 Z
|
||||
M11 3 H12 V8 H11 Z
|
||||
M12 4 H13 V9 H12 Z
|
||||
M13 6 H14 V11 H13 Z
|
||||
M14 7 H15 V12 H14 Z
|
||||
M2 8 H3 V10 H2 Z
|
||||
M15 8 H17 V10 H15 Z
|
||||
M18 3 H23 V8 H18 Z
|
||||
M24 4 H30 V7 H24 Z
|
||||
M18 10 H23 V15 H18 Z
|
||||
M24 11 H30 V14 H24 Z"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.4 KiB |
@@ -1,26 +1,22 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- SPDX-License-Identifier: MIT -->
|
||||
<!-- SPDX-FileCopyrightText: 2026 Martino Ferrari -->
|
||||
<!-- The trigger: a sine and the dashed level it is looked for at, which is what a trigger *is* on a
|
||||
waveform that is not a step. It used to be a rising edge, and one edge names only one of the four
|
||||
shape triggers the panel offers — a wave crossing a level names the trigger's job instead. One
|
||||
period, one-pixel stairs, the way the plot rasterises its own traces, with the level through the
|
||||
centre so the crest and the trough stand clear and the crossings fall on the steep parts. Inset
|
||||
two pixels, like the tabs' glyphs, so no art is flush with an edge a button might clip. -->
|
||||
<!-- The trigger: a signal rising above the level it is looked for at, with the level showing on both
|
||||
sides of it. One event, which is what the trigger is for. Bold on purpose: at this size a
|
||||
thinner wave reads as texture, and the level has to survive as two clear marks rather than as a
|
||||
dotted line. Inset two pixels like the tabs' glyphs, so no art sits flush with an edge a button
|
||||
might clip. Whole-pixel stairs, the way the plot rasterises its own traces. -->
|
||||
<svg height="16px" width="16px" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill="#2e3436" d="M2 7 H3 V10 H2 Z
|
||||
M3 5 H4 V8 H3 Z
|
||||
M4 3 H5 V6 H4 Z
|
||||
M5 3 H6 V6 H5 Z
|
||||
M6 4 H7 V7 H6 Z
|
||||
M7 6 H8 V9 H7 Z
|
||||
M8 8 H9 V11 H8 Z
|
||||
M9 10 H10 V13 H9 Z
|
||||
M10 11 H11 V14 H10 Z
|
||||
M11 11 H12 V14 H11 Z
|
||||
M12 9 H13 V12 H12 Z
|
||||
M13 7 H14 V10 H13 Z
|
||||
M2 7 H5 V9 H2 Z
|
||||
M7 7 H10 V9 H7 Z
|
||||
M11 7 H14 V9 H11 Z"/>
|
||||
<path fill="#2e3436" d="M3 7 H4 V11 H3 Z
|
||||
M4 5 H5 V9 H4 Z
|
||||
M5 4 H6 V8 H5 Z
|
||||
M6 3 H7 V7 H6 Z
|
||||
M7 2 H8 V6 H7 Z
|
||||
M8 2 H9 V6 H8 Z
|
||||
M9 3 H10 V7 H9 Z
|
||||
M10 4 H11 V8 H10 Z
|
||||
M11 5 H12 V9 H11 Z
|
||||
M12 7 H13 V11 H12 Z
|
||||
M2 8 H3 V10 H2 Z
|
||||
M13 8 H15 V10 H13 Z"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.2 KiB |
Reference in new issue
Block a user