feat: an app icon in colour and symbolic form, validated at build time
Both are drawn to the GNOME HIG: a 128px instrument face that leaves its margins, carries one 4px front profile for depth and no gradients or shadows, and a 16px symbolic reduction of the same metaphor in fills only. The colour icon strokes its sine because it is never recoloured; the symbolic one cannot. Every file in the manifest now preprocesses through xmllint, because a malformed SVG is the one icon failure no runtime check can see: librsvg refuses the file while the name still resolves, so the icon draws nothing and has_icon() says it is fine.
This commit is contained in:
1 parent
69eab02729
commit
f3d68cfdd4
4 files changed
+84
-10
No files matched your search
+3
-2
@@ -39,7 +39,7 @@ if(CMAKE_C_COMPILER_ID MATCHES "GNU|Clang")
|
||||
target_compile_options(udpsclient PRIVATE -Wall -Wextra)
|
||||
endif()
|
||||
|
||||
# --- icons: four symbolic SVGs compiled into the binary -----------------------
|
||||
# --- icons: six SVGs compiled into the binary --------------------------------
|
||||
# There is no stock icon for an oscilloscope trigger, a pair of cursors or a measurement
|
||||
# ruler, so this app draws its own. They go in a GResource rather than beside the binary
|
||||
# because udpscope is run straight out of the build tree and has no install step: a
|
||||
@@ -48,7 +48,8 @@ endif()
|
||||
find_program(GLIB_COMPILE_RESOURCES NAMES glib-compile-resources REQUIRED)
|
||||
set(UDPSCOPE_ICON_DIR ${CMAKE_CURRENT_SOURCE_DIR}/src/icons)
|
||||
file(GLOB UDPSCOPE_ICON_FILES CONFIGURE_DEPENDS
|
||||
${UDPSCOPE_ICON_DIR}/scalable/actions/*.svg)
|
||||
${UDPSCOPE_ICON_DIR}/scalable/actions/*.svg
|
||||
${UDPSCOPE_ICON_DIR}/scalable/apps/*.svg)
|
||||
add_custom_command(
|
||||
OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/udpscope_resources.c
|
||||
COMMAND ${GLIB_COMPILE_RESOURCES}
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- SPDX-License-Identifier: MIT -->
|
||||
<!-- SPDX-FileCopyrightText: 2026 Martino Ferrari -->
|
||||
<!--
|
||||
The symbolic form of the app icon: the same metaphor reduced to a screen frame with one
|
||||
wave inside it, following the UI-icon rules rather than the app-icon ones.
|
||||
|
||||
Filled paths only, with no stroke anywhere. GTK's symbolic recolour wraps the file in a
|
||||
stylesheet that reaches `fill` on `path`, `rect` and `circle` only, so a stroked line keeps
|
||||
the colour written here and stays dark on a dark theme. The wave is therefore a closed
|
||||
2px band: a sine along its top edge and the same sine 2px lower along its bottom, which is
|
||||
how a stroke's look is had from a fill.
|
||||
|
||||
`fill-rule="evenodd"` over three subpaths. The frame is the outer rect minus the inner one,
|
||||
so the screen's interior is two crossings deep and empty; the wave inside it is three, which
|
||||
is odd, so it fills. Whole-pixel coordinates throughout, because this is drawn at 16px far
|
||||
more often than not and a half-pixel feature is a grey smear at 1x.
|
||||
-->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
|
||||
<path fill="#2e3436" fill-rule="evenodd"
|
||||
d="M 1 2 H 15 V 14 H 1 Z
|
||||
M 2 3 H 14 V 13 H 2 Z
|
||||
M 3 7 C 5 3 6 3 8 7 C 10 11 11 11 13 7 L 13 9 C 11 13 10 13 8 9 C 6 5 5 5 3 9 Z"/>
|
||||
</svg>
|
||||
@@ -0,0 +1,39 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- SPDX-License-Identifier: MIT -->
|
||||
<!-- SPDX-FileCopyrightText: 2026 Martino Ferrari -->
|
||||
<!--
|
||||
The application icon: an oscilloscope's face, which is the clearest metaphor available and
|
||||
the kind of physical object the HIG's top-and-front depth language was written for.
|
||||
|
||||
Drawn to <https://developer.gnome.org/hig/guidelines/app-icons.html>. The artwork spans
|
||||
x 12..116 and y 22..106, so it does not fill the 128px canvas, and every feature sits on
|
||||
the 2px grid. Depth is the 4px front profile along the bottom and nothing else: that is
|
||||
the HIG's maximum of 2 detail units, and since every surface here is straight, the rest
|
||||
is flat colour with no gradients and no shadows.
|
||||
|
||||
The profile is the bottom 4px of the chassis silhouette, clipped to it. A plain rect from
|
||||
(12, 102) to (116, 106) would protrude past the radius-8 corners, which reads as a
|
||||
misdrawn bump at 128px and as a dirty edge at 32px; the clip follows the corners exactly
|
||||
and keeps every coordinate even, which hand-computing the arc would not.
|
||||
|
||||
Two deliberate omissions. There is no graticule: the grid lines a real scope face carries
|
||||
cannot survive 32px, and the trace carries the metaphor without them. And the trace is
|
||||
one cycle, a single crest and a single trough, because at 32px the screen is about
|
||||
22x16 px and anything busier reads as a smudge.
|
||||
|
||||
Colours are the GNOME palette: Dark 3 chassis, Dark 2 profile, Dark 4 screen, Green 1 trace.
|
||||
-->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="128" height="128" viewBox="0 0 128 128">
|
||||
<defs>
|
||||
<clipPath id="chassis-silhouette">
|
||||
<rect x="12" y="22" width="104" height="84" rx="8"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
<rect x="12" y="22" width="104" height="84" rx="8" fill="#3d3846"/>
|
||||
<g clip-path="url(#chassis-silhouette)">
|
||||
<rect x="12" y="102" width="104" height="4" fill="#5e5c64"/>
|
||||
</g>
|
||||
<rect x="20" y="30" width="88" height="64" rx="4" fill="#241f31"/>
|
||||
<path d="M 24 62 C 37 38 51 38 64 62 C 77 86 91 86 104 62"
|
||||
fill="none" stroke="#8ff0a4" stroke-width="6" stroke-linecap="round"/>
|
||||
</svg>
|
||||
@@ -2,16 +2,26 @@
|
||||
<!-- SPDX-License-Identifier: MIT -->
|
||||
<!-- SPDX-FileCopyrightText: 2026 Martino Ferrari -->
|
||||
<!--
|
||||
The four icons this app had to draw for itself, compiled into the binary so there is
|
||||
still exactly one file to run. `gtk_icon_theme_add_resource_path()` wants the tree an
|
||||
icon theme has, so the prefix is followed by `<size>/<context>`; `scalable` is the one
|
||||
that means "an SVG, use it at any size".
|
||||
The icons this app had to draw for itself, compiled into the binary so there is still
|
||||
exactly one file to run. `gtk_icon_theme_add_resource_path()` wants the tree an icon theme
|
||||
has, so the prefix is followed by `<size>/<context>`; `scalable` is the one that means
|
||||
"an SVG, use it at any size", and `apps` is where an application icon belongs.
|
||||
|
||||
Every file carries `preprocess="xml-stripblanks"`, which makes glib-compile-resources pipe
|
||||
it through xmllint. That is the only thing that catches a malformed SVG: librsvg refuses
|
||||
such a file outright, but the icon *name* still resolves, so `gtk_icon_theme_has_icon()`
|
||||
returns true and the icon simply draws nothing. No runtime GTK call can tell the two apart
|
||||
(`lookup_icon()` never returns NULL and always reports the size it was asked for), so the
|
||||
check belongs here, where a double hyphen inside a comment fails the build with a file,
|
||||
line and column instead.
|
||||
-->
|
||||
<gresources>
|
||||
<gresource prefix="/org/udpscope/icons">
|
||||
<file>scalable/actions/udpscope-trigger-symbolic.svg</file>
|
||||
<file>scalable/actions/udpscope-cursors-symbolic.svg</file>
|
||||
<file>scalable/actions/udpscope-measure-symbolic.svg</file>
|
||||
<file>scalable/actions/udpscope-math-symbolic.svg</file>
|
||||
<file preprocess="xml-stripblanks">scalable/actions/udpscope-trigger-symbolic.svg</file>
|
||||
<file preprocess="xml-stripblanks">scalable/actions/udpscope-cursors-symbolic.svg</file>
|
||||
<file preprocess="xml-stripblanks">scalable/actions/udpscope-measure-symbolic.svg</file>
|
||||
<file preprocess="xml-stripblanks">scalable/actions/udpscope-math-symbolic.svg</file>
|
||||
<file preprocess="xml-stripblanks">scalable/apps/org.udpscope.app.svg</file>
|
||||
<file preprocess="xml-stripblanks">scalable/apps/org.udpscope.app-symbolic.svg</file>
|
||||
</gresource>
|
||||
</gresources>
|
||||
Reference in new issue
Block a user