m3qs is a self-contained QML module with three layers:
- Config and theming (
Config.qml,Colors.qml,qmldir) -- pure data, no visuals. - Shared primitives (
components/common/) -- motion tokens, surface primitive, digit component, motion blur wrapper. - Clock faces (
components/) -- four independent, swappable implementations consuming the same config, color, and motion layer.
shell.qml is the composition root. It reads Config.variant, mounts the matching component inside a MotionBlurWrapper, and does nothing else visual.
shell.qml
MotionBlurWrapper
Loader { sourceComponent: faceFor(Config.variant) }
StackedDigitalClock / ScallopedAnalogClock
PillDigitalClock / WorldClock
(each composed from TonalSurface + SpringBehavior / EffectBehavior)
Only one face is ever visible. Each face owns its own animation timers; the analog face repaints a Canvas every tick. Instantiating all four permanently would keep three idle faces driving background timers and repaints. A Loader ensures only the active face's bindings are live.
The MotionBlurWrapper encloses every face and tracks linear velocity from position changes. It renders the face into an offscreen texture and applies a directional Gaussian blur along the velocity vector via a compiled GLSL shader (MotionBlur.frag.qsb). The shader uses up to 31 samples with a Gaussian-weighted kernel and velocity decay framing.
wallpaper / system theme
|
v
Matugen (matugen/config.toml + matugen/templates/colors.qml) -> Colors.qml
|
v
Components read from Colors.* -- no hardcoded hex values
Colors.qml exposes Material 3 semantic roles. The on* color roles (onPrimary, onSurface, etc.) are stored in a sub-object onRef and re-exported via property alias to avoid a QML engine conflict where property names starting with on + uppercase are interpreted as signal handler declarations.
Palettes are selected by Config.theme and read from the palette table in themes/Themes.qml — eight pre-built presets ship by default. If Matugen is installed, its template overwrites Colors.qml with a wallpaper-derived palette instead.
Every animated property uses one of two shared behavior components:
- Spatial properties (position, size, rotation, radius):
SpringBehavior. Duration 500ms timesConfig.animationSpeed,Easing.OutBackwith overshoot 1.0. - Color and opacity properties:
EffectBehavior. Duration 300ms timesConfig.animationSpeed,Easing.OutCubic, no overshoot.
Clock faces may define custom Behaviors for specific properties (for example, the analog face uses per-hand RotationAnimations with OutQuint easing), but they always read Config.animationSpeed as the duration multiplier.
- Create
components/YourClock.qml. - Build panels from
TonalSurface-- do not reimplement border and fill locally. - Use
SpringBehaviorfor spatial properties,EffectBehaviorfor color and opacity. Avoid rawNumberAnimationorColorAnimation. - Add a routing case in
shell.qml'sLoader.sourceComponentswitch. - Read all colors from
Colors.*-- no hardcoded hex values. - Update
docs/COMPONENTS.mdwith the new face's property table.