A 1970s hi-fi faceplate rebuilt in Qt Quick, and the control library that came out of building it.
Every pixel above is drawn at run time. There are no PNGs, no SVGs, no icon
font and no assets folder anywhere in the source. Knobs, faders, switches,
lamps, the level window and every shadow are made from Rectangles, gradients
and Context2D paths, so the whole thing stays sharp at any size and recolours
from a single token file.
Three pieces, sharing one set of drawing primitives:
| The faceplate | main.qml. The amplifier itself, rebuilt from the reference artwork. |
controls/ |
45 standalone controls with no QtQuick.Controls dependency, including the parts Qt has no equivalent for: Knob, Fader, VuScreen, LevelMeter, SevenSegment, CircularGauge, Led, TapeButton, LeverSwitch. |
style/D707/ |
A real Qt Quick Controls style. All 53 stylable types in Qt 6.8. Drop it into an existing app and the app keeps working. |
Both control sets draw from primitives/, so they look identical side by side.
The colour comes from one token file, so a skin is a handful of values rather than a new set of graphics. Press 1, 2, 3 or Space to cycle, or pick a colour from the button in the top right.
| Teal | Ochre | Grey |
|---|---|---|
![]() |
![]() |
![]() |
Those three are the reference artwork the faceplate was rebuilt from. How closely the running app matches them is measured below.
Press G for a gallery that walks through every control, what it is for, and the properties you can set on it. It reflows from a wide desktop down to a narrow window.
Press N for a window that contains no custom types at all —
every control in it is the stock QtQuick.Controls one, wearing the D707
style.
This is the part that matters if you already have an app. Each file in
style/D707/ is a QtQuick.Templates control with only its delegates
replaced, so you keep everything Qt gives you:
- keyboard navigation, focus handling and tab order
stepSize,snapMode,live,wheelEnabled, drag behaviour- mirroring for right-to-left languages
ToolTipattached properties,Dialog.standardButtons,ButtonGroup- the full property and signal surface of every control
The only thing added is an accent property on the pressable controls. It is
the face colour, and the gradients, borders and shading all derive from it:
Button { text: "Delete"; accent: "#E45555" }
Slider { accent: "#F9D95C" }
Dial { accent: "#4A8ED9" }Leave it alone and you get machined grey.
Copy style/D707/ and primitives/ into your project and add them to your
.qrc. In Qt 6 a custom style is a QML module, not a plain folder, so it
needs three things:
1. A qmldir beside the style files
module D707
Button 1.0 Button.qml
Slider 1.0 Slider.qml
...
2. The module reachable at :/D707/, via an alias in your .qrc:
<file alias="D707/Button.qml">style/D707/Button.qml</file>3. The style selected, and the resource root on the import path
qtquickcontrols2.conf:
[Controls]
Style=D707
FallbackStyle=Basicmain.cpp:
QQuickStyle::setFallbackStyle("Basic");
QQmlApplicationEngine engine;
engine.addImportPath("qrc:/");The fallback matters. It is what Qt uses for any type the style does not provide, and it is what keeps a partial style from crashing an app.
The faceplate was rebuilt by measuring the reference artwork rather than by eye: run-length scans for edges, colour sampling for every surface, and a numerical fit for the shadow falloff.
- Mean absolute difference: 3.58 out of 255 across the whole 1472 × 987 frame
- Knobs come in at 3.3; the panels and the level window are essentially exact
| Reference artwork | Headless render of the app |
|---|---|
![]() |
![]() |
render.sh builds that render and writes render.png, which you can diff
against docs/faceplate-teal.png to check a change has not drifted.
The window fills. There is no fixed size and no letterboxing.
- The faceplate is a
RowLayoutof four columns, each aColumnLayout. Panel frames, margins and gaps all stretch. - Inside each frame the artwork keeps its original reference coordinates and rides one shared scale taken from the window, so a knob in SOURCE 1 is always the same size as the identical knob in SOURCE 2, whatever shape the window is.
Column and row sizes are solved directly against the reference grid rather than left to layout stretch factors. At the reference shape every element lands on its original pixel; at any other shape the solved units grow faster than the artwork scale, which is what guarantees a control can never overflow the frame holding it.
There is no compiler for QML delegates, so verify.py does what it can
statically across all 125 QML files:
$ python3 verify.py
checked 125 QML files
all checks passed
Every rule in it exists because that exact mistake shipped once and cost a build cycle:
- bracket balance, duplicate property assignments
- QML ids that start with a capital letter, or that are JavaScript reserved
words (
nativeis one, and it fails with a parse error three lines later) font.families, which is not assignable here, and CSS-style font stacks- types used without an import that resolves them
- style files that import
QtQuick.Controls, which is what they implement implicitIndicator*on a template that has more than one indicator- style files missing from
qmldiror fromqml.qrc, which is what producesmodule "D707" is not installed
Qt 6.5 or newer. Qt 6.8.8 is what this is developed against.
cmake -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build
./build/IfiDesignIf you change a file and the app does not seem to notice, touch qml.qrc so
AUTORCC re-runs.
| Key | |
|---|---|
| 1 2 3 Space | Cycle the faceplate colour |
| G | Open the control gallery |
| N | Open the stock Qt Quick Controls window |
main.qml the faceplate
Gallery.qml the documented control gallery
Native.qml stock QtQuick.Controls, D707 style, zero custom types
primitives/ Style tokens, Raised, Recessed, SoftShadow, GripSurface,
Glyph, Cap, Caption
controls/ 46 standalone controls
style/D707/ 53 Qt Quick Controls style files + qmldir
gallery/ the gallery shell and its pages
verify.py static checks
render.sh headless render for pixel diffing
NOTES.md how the reconstruction was done
ROADMAP.md what is still missing, in priority order
The faceplate, the control library and the style are all working. What is not done yet is written down in ROADMAP.md. The short version:
- No style file reads
paletteyet, so an app that sets its own accent colour is ignored. This is the biggest blocker to real drop-in use. - No
Accessibleproperties anywhere, so screen readers see nothing. - No run-time tests.
verify.pyreads the files, it never runs them. - No install rules, so the style only works from inside this repository.
No licence has been chosen yet.






