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
|
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**
|
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
|
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
|
cursors and the ruler for Measure, a signal rising above its trigger level and the channel list
|
||||||
Signals (the same picture the toolbar's trigger button carries), drawn as a
|
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
|
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
|
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,
|
`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
|
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 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:
|
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-measure-symbolic` is two cursors beside the ruler they read against — each a cap over two
|
||||||
`udpscope-tab-signals-symbolic` is the trigger's edge above the channel list it acts on. They are not
|
bold dashes, beside a ruler of two bands with three marks between them and **no end caps** (a closed
|
||||||
reuses because a glyph the toolbar already carries means two things the moment a tab shows it too. Both
|
frame reads as a rectangle, and costs a fifth of the ink on two lines that say nothing) — and
|
||||||
are **32x16** composites: the Measure pill is two cursors *standing on* the scale they read against — the
|
`udpscope-tab-signals-symbolic` is a signal rising above its level, beside two bold channel rows. They
|
||||||
one band with alternating marks, each marker centred on one of them, which is one instrument rather
|
are not reuses, because a glyph the toolbar already carries means two things the moment a tab shows it
|
||||||
than two icons in a box — and the Signals pill a sine with a dashed level through it beside the
|
too; instead the *trigger's* own icon (`udpscope-trigger-symbolic`) was redrawn to the Signals pill's
|
||||||
channel list. That picture is shared on
|
picture, so the pill and the button that turns the trigger on are recognisably about the same thing.
|
||||||
purpose: the trigger's *own* toolbar icon (`udpscope-trigger-symbolic`) is the same sine and the same
|
Both are **32x16** composites drawn **bold** — one thick band, two thick dashes, few features — because
|
||||||
level, one period of one-pixel stairs, so the pill and the button that turns the trigger on are
|
at this size a thin wave or a 1px tick reads as *texture* rather than as a shape, which is what "not
|
||||||
recognisably about the same thing. The trigger glyph used to be a rising edge, and one edge names only
|
clear" looks like. Wider than tall on purpose: a 16px square cell carries one motif, not a pair. And
|
||||||
one of the four shape triggers the panel offers — a wave crossing a level names the trigger's job.
|
**GDK paints an icon into a square box whatever the document's aspect** (measured:
|
||||||
Wider than tall on purpose — a 16px square cell carries
|
`gtk_icon_theme_lookup_icon()` at 32 reports `32x32`, and librsvg fits it to `32x16`), so the pair is
|
||||||
one motif, and squeezing a pair into it is what turns both into a smear — and the pill has the width for
|
letterboxed rather than stretched — and the pill's image is given `pixel_size` 32 in `name_side_tabs()`,
|
||||||
it, in a 300px column whose switcher has two pills in it. **GDK paints an icon into a square box whatever
|
which draws the artwork at the size it is written, so its bars stay crisp. Every glyph here is
|
||||||
the document's aspect** (measured: `gtk_icon_theme_lookup_icon()` at 32 reports `32x32`, and librsvg
|
**inset two pixels**, because art flush with the cell edge is what a button's padding or a crop takes a
|
||||||
fits it to `32x16`), so the pair is letterboxed, not stretched — and the pill's image is given
|
pixel off, and a fractional scale is what would blur it, which is why nothing is drawn at 24 or 48. The
|
||||||
`pixel_size` 32 in `name_side_tabs()`, which draws the artwork at exactly the size it is written, so its
|
two tab glyphs measure 29% and 30% ink, inside the range the icons above them set.
|
||||||
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`.
|
|
||||||
- **A shortcut table row's `accel` is GTK accelerator syntax, and a pointer gesture has
|
- **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
|
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"`,
|
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
|
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**
|
`-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
|
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
|
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
|
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`
|
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"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<!-- SPDX-License-Identifier: MIT -->
|
<!-- SPDX-License-Identifier: MIT -->
|
||||||
<!-- SPDX-FileCopyrightText: 2026 Martino Ferrari -->
|
<!-- SPDX-FileCopyrightText: 2026 Martino Ferrari -->
|
||||||
<!-- The Measure tab: two cursors standing on the scale they read against. The scale is one band with
|
<!-- The Measure tab: two cursors beside the ruler they read against. Each cursor is a cap over two
|
||||||
alternating marks rather than the closed ruler body `udpscope-measure-symbolic` draws for the
|
bold dashes — the mark the toolbar's cursors button carries, at a weight that survives this size.
|
||||||
Preferences window: at 32x16 a frame costs a fifth of the ink on two lines that say nothing, and
|
The ruler is two bands with three marks between them and *no end caps*: a closed frame reads as a
|
||||||
the cursors are what this tab is *for*. Each marker is centred on one of the marks — a cap over a
|
rectangle, and it costs a fifth of the ink on two lines that say nothing. Both motifs are centred
|
||||||
dashed shaft that meets the scale instead of hovering over it — so the two read as one instrument
|
on the same line and share one weight, and everything is inset two pixels. -->
|
||||||
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. -->
|
|
||||||
<svg height="16px" width="32px" viewBox="0 0 32 16" xmlns="http://www.w3.org/2000/svg">
|
<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
|
<path fill="#2e3436" d="M2 2 H7 V5 H2 Z
|
||||||
M5 9 H7 V12 H5 Z
|
M3 6 H6 V10 H3 Z
|
||||||
M9 10 H11 V12 H9 Z
|
M3 11 H6 V14 H3 Z
|
||||||
M13 9 H15 V12 H13 Z
|
M9 2 H14 V5 H9 Z
|
||||||
M17 10 H19 V12 H17 Z
|
M10 6 H13 V10 H10 Z
|
||||||
M21 9 H23 V12 H21 Z
|
M10 11 H13 V14 H10 Z
|
||||||
M25 10 H27 V12 H25 Z
|
M17 4 H30 V6 H17 Z
|
||||||
M7 3 H12 V6 H7 Z
|
M17 10 H30 V12 H17 Z
|
||||||
M8 7 H11 V10 H8 Z
|
M18 6 H20 V10 H18 Z
|
||||||
M8 10 H11 V12 H8 Z
|
M22 6 H24 V10 H22 Z
|
||||||
M19 3 H24 V6 H19 Z
|
M26 6 H28 V10 H26 Z"/>
|
||||||
M20 7 H23 V10 H20 Z
|
|
||||||
M20 10 H23 V12 H20 Z"/>
|
|
||||||
</svg>
|
</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"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<!-- SPDX-License-Identifier: MIT -->
|
<!-- SPDX-License-Identifier: MIT -->
|
||||||
<!-- SPDX-FileCopyrightText: 2026 Martino Ferrari -->
|
<!-- SPDX-FileCopyrightText: 2026 Martino Ferrari -->
|
||||||
<!-- The Signals tab: the toolbar's trigger picture — a sine with a dashed level through it — beside
|
<!-- The Signals tab: the toolbar's trigger picture — a signal above its level — beside the channel
|
||||||
the channel list. Sharing that picture is the point: the pill and the toolbar button that turns
|
list. Sharing that picture is the point: the pill and the button that turns the trigger on should
|
||||||
the trigger on should be recognisably about the same thing. Three channel rows on the right, on
|
be recognisably about the same thing. Two channel rows on the right, drawn bold for the same
|
||||||
the left half's centre line, and everything inset two pixels, because art flush with the cell
|
reason the wave is, and everything inset two pixels. -->
|
||||||
edge is what a button or a crop clips. -->
|
|
||||||
<svg height="16px" width="32px" viewBox="0 0 32 16" xmlns="http://www.w3.org/2000/svg">
|
<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
|
<path fill="#2e3436" d="M3 7 H4 V12 H3 Z
|
||||||
M3 4 H4 V8 H3 Z
|
M4 6 H5 V11 H4 Z
|
||||||
M4 3 H5 V7 H4 Z
|
M5 4 H6 V9 H5 Z
|
||||||
M5 2 H6 V6 H5 Z
|
M6 3 H7 V8 H6 Z
|
||||||
M6 3 H7 V7 H6 Z
|
M7 2 H8 V7 H7 Z
|
||||||
M7 4 H8 V8 H7 Z
|
M8 2 H9 V7 H8 Z
|
||||||
M8 6 H9 V10 H8 Z
|
M9 2 H10 V7 H9 Z
|
||||||
M9 8 H10 V12 H9 Z
|
M10 2 H11 V7 H10 Z
|
||||||
M10 9 H11 V13 H10 Z
|
M11 3 H12 V8 H11 Z
|
||||||
M11 10 H12 V14 H11 Z
|
M12 4 H13 V9 H12 Z
|
||||||
M12 9 H13 V13 H12 Z
|
M13 6 H14 V11 H13 Z
|
||||||
M13 8 H14 V12 H13 Z
|
M14 7 H15 V12 H14 Z
|
||||||
M14 6 H15 V10 H14 Z
|
M2 8 H3 V10 H2 Z
|
||||||
M2 7 H5 V9 H2 Z
|
M15 8 H17 V10 H15 Z
|
||||||
M7 7 H10 V9 H7 Z
|
M18 3 H23 V8 H18 Z
|
||||||
M12 7 H15 V9 H12 Z
|
M24 4 H30 V7 H24 Z
|
||||||
M18 2 H21 V5 H18 Z
|
M18 10 H23 V15 H18 Z
|
||||||
M23 3 H30 V5 H23 Z
|
M24 11 H30 V14 H24 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"/>
|
|
||||||
</svg>
|
</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"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<!-- SPDX-License-Identifier: MIT -->
|
<!-- SPDX-License-Identifier: MIT -->
|
||||||
<!-- SPDX-FileCopyrightText: 2026 Martino Ferrari -->
|
<!-- 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
|
<!-- The trigger: a signal rising above the level it is looked for at, with the level showing on both
|
||||||
waveform that is not a step. It used to be a rising edge, and one edge names only one of the four
|
sides of it. One event, which is what the trigger is for. Bold on purpose: at this size a
|
||||||
shape triggers the panel offers — a wave crossing a level names the trigger's job instead. One
|
thinner wave reads as texture, and the level has to survive as two clear marks rather than as a
|
||||||
period, one-pixel stairs, the way the plot rasterises its own traces, with the level through the
|
dotted line. Inset two pixels like the tabs' glyphs, so no art sits flush with an edge a button
|
||||||
centre so the crest and the trough stand clear and the crossings fall on the steep parts. Inset
|
might clip. Whole-pixel stairs, the way the plot rasterises its own traces. -->
|
||||||
two pixels, like the tabs' glyphs, so no art is flush with an edge a button might clip. -->
|
|
||||||
<svg height="16px" width="16px" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
|
<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
|
<path fill="#2e3436" d="M3 7 H4 V11 H3 Z
|
||||||
M3 5 H4 V8 H3 Z
|
M4 5 H5 V9 H4 Z
|
||||||
M4 3 H5 V6 H4 Z
|
M5 4 H6 V8 H5 Z
|
||||||
M5 3 H6 V6 H5 Z
|
M6 3 H7 V7 H6 Z
|
||||||
M6 4 H7 V7 H6 Z
|
M7 2 H8 V6 H7 Z
|
||||||
M7 6 H8 V9 H7 Z
|
M8 2 H9 V6 H8 Z
|
||||||
M8 8 H9 V11 H8 Z
|
M9 3 H10 V7 H9 Z
|
||||||
M9 10 H10 V13 H9 Z
|
M10 4 H11 V8 H10 Z
|
||||||
M10 11 H11 V14 H10 Z
|
M11 5 H12 V9 H11 Z
|
||||||
M11 11 H12 V14 H11 Z
|
M12 7 H13 V11 H12 Z
|
||||||
M12 9 H13 V12 H12 Z
|
M2 8 H3 V10 H2 Z
|
||||||
M13 7 H14 V10 H13 Z
|
M13 8 H15 V10 H13 Z"/>
|
||||||
M2 7 H5 V9 H2 Z
|
|
||||||
M7 7 H10 V9 H7 Z
|
|
||||||
M11 7 H14 V9 H11 Z"/>
|
|
||||||
</svg>
|
</svg>
|
||||||
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.2 KiB |
Reference in new issue
Block a user