updated theme

This commit is contained in:
2026-08-11 01:53:26 +02:00
parent 0a439d951e
commit 105cf6b3a8
31 changed files with 1218 additions and 976 deletions
+103
View File
@@ -0,0 +1,103 @@
import QtQuick
import QtQuick.Shapes
import "root:/config"
import "root:/components"
// The shared ground for the two full-screen surfaces: a crimson wedge leaning
// at the house angle, a tiled stripe field that sweeps in from the left, and a
// halftone dot wash over the top.
//
// The lock screen and the power menu each used to draw their own version of
// this. They disagreed — the power menu had a wedge and a full stripe field,
// the lock had a 42px stripe band and no wedge, and the wedge leaned at 0.32
// where every panel in the shell leans at Theme.skew. Building both on one
// component is what makes them read as the same object seen twice rather than
// as two guesses at the same brief.
Item {
id: root
// 0 = withdrawn, 1 = fully present. Drives the stripe slide, the stripe
// opacity and the wedge together, so a caller animates one number instead
// of keeping three in sync. The power menu runs it both ways on open and
// close; the lock runs it once on entry.
property real reveal: 1
property color tone: Theme.accent
property bool wedge: true
property real wedgeOpacity: 0.35
// Bottom-left corner of the wedge and its span, as fractions of width. The
// wedge leans right going up, like every other edge in the shell, so its
// top edge sits `leanOffset` further right than its bottom.
property real wedgeLeft: 0
property real wedgeSpan: 0.34
property bool stripes: true
property real stripeOpacity: 0.10
property bool halftone: true
property real halftoneOpacity: 0.05
property color halftoneColor: Theme.primary
// ONE angle, everywhere — the same rule the panels follow.
readonly property real leanOffset: root.height * Theme.skew
// Diagonal stripe field, sliding in from off-screen left.
Image {
anchors.fill: parent
visible: root.stripes && root.reveal > 0
source: Theme.texStripes
fillMode: Image.Tile
opacity: root.stripeOpacity * root.reveal
smooth: false
layer.enabled: true
layer.effect: TintEffect {
tintColor: root.tone
}
transform: Translate {
x: -root.width * (1 - root.reveal)
}
}
// The wedge. Drawn after the stripes so it reads as a slab laid over them.
Shape {
anchors.fill: parent
visible: root.wedge && root.reveal > 0
opacity: root.reveal
preferredRendererType: Shape.CurveRenderer
ShapePath {
fillColor: Theme.alpha(Theme.accentDim, root.wedgeOpacity)
strokeColor: "transparent"
joinStyle: ShapePath.MiterJoin
PathPolyline {
path: {
const x = root.wedgeLeft * root.width;
const span = root.wedgeSpan * root.width;
const off = root.leanOffset;
return Geom.closed([
Qt.point(x + off, 0),
Qt.point(x + off + span, 0),
Qt.point(x + span, root.height),
Qt.point(x, root.height)
]);
}
}
}
}
// Screen-printed dot wash over the whole thing.
Image {
anchors.fill: parent
visible: root.halftone
source: Theme.texHalftone
fillMode: Image.Tile
opacity: root.halftoneOpacity
smooth: false
layer.enabled: true
layer.effect: TintEffect { tintColor: root.halftoneColor }
}
}
+83 -74
View File
@@ -39,7 +39,10 @@ Item {
property color halftoneColor: Theme.primary
property real halftoneOpacity: 0.07
// Inner glow along the top edge, for panels that should feel backlit.
// Accepted and ignored. This used to paint a teal-to-transparent gradient
// down the top of every panel in the shell — the single biggest source of
// the murky blue cast. Fills are flat now. Kept as a no-op property so the
// call sites that still set it don't have to be chased down.
property bool sheen: false
// The screen-print stack. A pure-black copy of the silhouette sits behind
@@ -71,6 +74,28 @@ Item {
readonly property real contentPad: padding + leanInset
// The panel outline as a mask texture. Anything that is painted from the
// raw polygon rather than from the chamfered one has to be clipped to this,
// or it escapes at the chopped corners.
Shape {
id: silhouette
anchors.fill: parent
visible: false
layer.enabled: true
layer.smooth: true
preferredRendererType: Shape.CurveRenderer
ShapePath {
fillColor: "white"
strokeColor: "transparent"
joinStyle: ShapePath.MiterJoin
PathPolyline {
path: Geom.chamfer(root.polygon, root.chop, root.chopCorners)
}
}
}
// Offset black copy of the silhouette, behind everything.
Loader {
anchors.fill: parent
@@ -138,93 +163,77 @@ Item {
anchors.fill: parent
active: root.halftone
asynchronous: true
sourceComponent: Item {
Shape {
id: maskShape
anchors.fill: parent
visible: false
layer.enabled: true
layer.smooth: true
preferredRendererType: Shape.CurveRenderer
ShapePath {
fillColor: "white"
strokeColor: "transparent"
PathPolyline {
path: Geom.chamfer(root.polygon, root.chop, root.chopCorners)
}
}
}
Image {
anchors.fill: parent
source: Theme.texHalftone
fillMode: Image.Tile
opacity: root.halftoneOpacity
smooth: false
layer.enabled: true
layer.effect: MultiEffect {
colorization: 1.0
colorizationColor: root.halftoneColor
maskEnabled: true
maskSource: maskShape
}
}
}
}
// Backlit top edge.
Loader {
anchors.fill: parent
active: root.sheen
sourceComponent: Shape {
preferredRendererType: Shape.CurveRenderer
ShapePath {
strokeColor: "transparent"
fillGradient: LinearGradient {
x1: 0; y1: 0; x2: 0; y2: root.height
GradientStop { position: 0.0; color: Theme.alpha(Theme.primary, 0.16) }
GradientStop { position: 0.55; color: "transparent" }
}
PathPolyline {
path: Geom.chamfer(root.polygon, root.chop, root.chopCorners)
}
sourceComponent: Image {
anchors.fill: parent
source: Theme.texHalftone
fillMode: Image.Tile
opacity: root.halftoneOpacity
smooth: false
layer.enabled: true
layer.effect: MultiEffect {
colorization: 1.0
colorizationColor: root.halftoneColor
maskEnabled: true
maskSource: silhouette
}
}
}
// Leading-edge slash.
//
// Its polygon is built from the panel's raw corners, so on a panel with a
// chopped corner the slash used to run straight through the cut and out the
// far side — a crimson wedge sitting a few pixels outside the silhouette,
// at an angle that matched nothing. It is masked to the outline now, so the
// slash ends exactly where the chamfer does.
Loader {
anchors.fill: parent
active: root.slash
sourceComponent: Shape {
preferredRendererType: Shape.CurveRenderer
ShapePath {
fillColor: root.slashColor
strokeColor: "transparent"
PathPolyline {
path: {
const t = root.slashWidth;
sourceComponent: Item {
Shape {
id: slashShape
anchors.fill: parent
visible: false
layer.enabled: true
layer.smooth: true
preferredRendererType: Shape.CurveRenderer
// On a rail slab the slash runs along the slanted top
// edge instead of down a side.
if (root.vertical) {
const voff = root.width * root.lean;
ShapePath {
fillColor: root.slashColor
strokeColor: "transparent"
PathPolyline {
path: {
const t = root.slashWidth;
// On a rail slab the slash runs along the slanted
// top edge instead of down a side.
if (root.vertical) {
const voff = root.width * root.lean;
return root.leanLeft
? Geom.closed([Qt.point(0, 0), Qt.point(root.width, voff),
Qt.point(root.width, voff + t), Qt.point(0, t)])
: Geom.closed([Qt.point(0, voff), Qt.point(root.width, 0),
Qt.point(root.width, t), Qt.point(0, voff + t)]);
}
const off = root.height * root.lean;
return root.leanLeft
? Geom.closed([Qt.point(0, 0), Qt.point(root.width, voff),
Qt.point(root.width, voff + t), Qt.point(0, t)])
: Geom.closed([Qt.point(0, voff), Qt.point(root.width, 0),
Qt.point(root.width, t), Qt.point(0, voff + t)]);
? Geom.closed([Qt.point(0, 0), Qt.point(t, 0),
Qt.point(off + t, root.height), Qt.point(off, root.height)])
: Geom.closed([Qt.point(off, 0), Qt.point(off + t, 0),
Qt.point(t, root.height), Qt.point(0, root.height)]);
}
const off = root.height * root.lean;
return root.leanLeft
? Geom.closed([Qt.point(0, 0), Qt.point(t, 0),
Qt.point(off + t, root.height), Qt.point(off, root.height)])
: Geom.closed([Qt.point(off, 0), Qt.point(off + t, 0),
Qt.point(t, root.height), Qt.point(0, root.height)]);
}
}
}
MultiEffect {
anchors.fill: parent
source: slashShape
maskEnabled: true
maskSource: silhouette
}
}
}