Brand the panel: yellow IP/DNS line, white toggle
- Colour the IP / DNS subtitle under each machine with the logo yellow (#FAE204, sampled as the dominant opaque colour of offscale-logo.png). Stock draws that line through `dim` = Qt.darker(foreground, 1.55), which on yellow shifts the hue to olive (#A19203) rather than quietening it, so it takes the flat accent instead. Everything else keeps the themed foreground: section headers, machine names, account names and all the iconography. - Add OffscaleToggle.qml so the switch reads white when on. The stock ToggleSwitch resolves its checked colours through Style.selectedStateColor / selectedFillFor, which read [controls] selected-color; Ethereal sets that to a literal "#ffcead" and Style.resolveStateColor returns literals verbatim, ignoring the foreground and accent properties entirely. No property on the stock switch can reach it, and overriding the token globally would recolour every selected state in the shell. The copy differs only in the checked state and needs an explicit `import qs.Ui`, which the original gets implicitly by living inside that module. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,125 @@
|
|||||||
|
// Local copy of qs.Ui.ToggleSwitch, changed in exactly one respect: the
|
||||||
|
// "on" colours come from `foreground` instead of the theme's selected-state
|
||||||
|
// tokens.
|
||||||
|
//
|
||||||
|
// Stock resolves them through Style.selectedStateColor / selectedFillFor,
|
||||||
|
// which read [controls] selected-color from the active theme. Ethereal sets
|
||||||
|
// that to a literal "#ffcead", and Style.resolveStateColor returns a literal
|
||||||
|
// verbatim — the `foreground` and `accent` properties are ignored entirely.
|
||||||
|
// So no property on the stock switch can make the checked knob white; only
|
||||||
|
// overriding [controls] selected-color globally would, and that would recolour
|
||||||
|
// every selected state across the shell (buttons, sliders, and the audio,
|
||||||
|
// bluetooth, network and clock panels).
|
||||||
|
//
|
||||||
|
import QtQuick
|
||||||
|
import qs.Commons
|
||||||
|
import qs.Ui // BorderSurface: implicit for the original inside qs.Ui, explicit here
|
||||||
|
|
||||||
|
// Bare on/off switch: a track with a sliding knob and no label. This is the
|
||||||
|
// switch `Toggle` parks at the end of its labeled row, factored out so panel
|
||||||
|
// headers and other compact controls render the identical thing.
|
||||||
|
//
|
||||||
|
// The caller owns the value: bind `checked` to real state and flip it in
|
||||||
|
// response to `toggled()`. Services that already track a desired state
|
||||||
|
// optimistically (see the Tailscale service's `_desired`) get an instant knob
|
||||||
|
// throw for free, because `checked` is already the optimistic value.
|
||||||
|
//
|
||||||
|
// `busy` marks an operation in flight and swallows further clicks, but leaves
|
||||||
|
// hover, cursor, and tooltips alone so the control does not flicker every time
|
||||||
|
// a background refresh runs.
|
||||||
|
//
|
||||||
|
// The cursor is a ring drawn outside the track rather than a state on the
|
||||||
|
// track itself: themes give normal chrome a stronger border than hover-cursor
|
||||||
|
// (0.4 vs 0.25 by default), which is right for controls that are borderless at
|
||||||
|
// rest but would make a bordered track go *fainter* under the cursor. On the
|
||||||
|
// panel background the ring reads immediately. `cursorRing` follows
|
||||||
|
// `interactive` — a switch whose surrounding row owns the click owns the
|
||||||
|
// cursor too.
|
||||||
|
//
|
||||||
|
// `rounded` auto-detects from Style.cornerRadius so the switch follows the
|
||||||
|
// theme: pill shape when Hyprland corners are rounded, square on sharp.
|
||||||
|
Item {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
property bool checked: false
|
||||||
|
property bool busy: false
|
||||||
|
|
||||||
|
// Off when the surrounding row owns the click, as in `Toggle`.
|
||||||
|
property bool interactive: true
|
||||||
|
|
||||||
|
// Panel-cursor flag. Same role as Button.hasCursor: panels with their own
|
||||||
|
// keyboard cursor bind this to drive the highlight separately from hover.
|
||||||
|
property bool hasCursor: false
|
||||||
|
|
||||||
|
property bool cursorRing: interactive
|
||||||
|
property int cursorPad: Style.space(6)
|
||||||
|
property bool rounded: Style.cornerRadius > 0
|
||||||
|
property color foreground: Color.foreground
|
||||||
|
property color accent: Color.accent
|
||||||
|
|
||||||
|
signal toggled()
|
||||||
|
signal hovered(bool isHovered)
|
||||||
|
|
||||||
|
readonly property alias containsMouse: mouse.containsMouse
|
||||||
|
readonly property bool hot: hasCursor || mouse.containsMouse
|
||||||
|
|
||||||
|
// `trackHeight` is settable so a compact placement — a switch riding a panel
|
||||||
|
// section header, say — can ask for a genuinely smaller control instead of
|
||||||
|
// scaling a big one down, which lands the track and knob on fractional pixels
|
||||||
|
// and blurs their edges. The derived sizes only carry floors low enough to
|
||||||
|
// stay out of an override's way; at the default track height each one is
|
||||||
|
// already above its floor, so nothing about the normal switch changes.
|
||||||
|
property int trackHeight: Math.max(22, Math.round(Style.spacing.controlHeight * 0.55))
|
||||||
|
property int trackWidth: Math.round(trackHeight * 1.9)
|
||||||
|
property int knobSize: Math.max(6, Math.round(trackHeight * 0.72))
|
||||||
|
property int knobInset: Math.max(1, Math.round((trackHeight - knobSize) / 2))
|
||||||
|
|
||||||
|
readonly property int _pad: cursorRing ? cursorPad : 0
|
||||||
|
|
||||||
|
implicitWidth: trackWidth + _pad * 2
|
||||||
|
implicitHeight: trackHeight + _pad * 2
|
||||||
|
|
||||||
|
BorderSurface {
|
||||||
|
anchors.fill: parent
|
||||||
|
visible: root.cursorRing && root.hot
|
||||||
|
color: "transparent"
|
||||||
|
radius: Style.cornerRadius
|
||||||
|
borderSpec: Border.controlSpec("hover-cursor", root.foreground, root.accent)
|
||||||
|
}
|
||||||
|
|
||||||
|
BorderSurface {
|
||||||
|
id: track
|
||||||
|
width: root.trackWidth
|
||||||
|
height: root.trackHeight
|
||||||
|
anchors.centerIn: parent
|
||||||
|
radius: root.rounded ? height / 2 : 0
|
||||||
|
color: root.checked
|
||||||
|
? Qt.rgba(root.foreground.r, root.foreground.g, root.foreground.b, 0.18)
|
||||||
|
: Style.normalFillFor(root.foreground, root.accent)
|
||||||
|
borderSpec: Border.controlSpec("normal", root.foreground, root.accent)
|
||||||
|
|
||||||
|
Behavior on color { ColorAnimation { duration: 120 } }
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
width: root.knobSize
|
||||||
|
height: root.knobSize
|
||||||
|
radius: root.rounded ? height / 2 : 0
|
||||||
|
x: root.checked ? track.width - width - root.knobInset : root.knobInset
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
color: root.checked ? root.foreground : Qt.darker(root.foreground, 1.25)
|
||||||
|
|
||||||
|
Behavior on x { NumberAnimation { duration: 120; easing.type: Easing.OutCubic } }
|
||||||
|
Behavior on color { ColorAnimation { duration: 120 } }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: mouse
|
||||||
|
anchors.fill: parent
|
||||||
|
enabled: root.interactive
|
||||||
|
hoverEnabled: true
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onContainsMouseChanged: root.hovered(containsMouse)
|
||||||
|
onClicked: if (!root.busy) root.toggled()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -23,9 +23,18 @@ Panel {
|
|||||||
property bool mullvadPickerOpen: false
|
property bool mullvadPickerOpen: false
|
||||||
property string mullvadQuery: ""
|
property string mullvadQuery: ""
|
||||||
|
|
||||||
|
// Text stays on the bar's themed colour: section headers, machine names and
|
||||||
|
// account names read white.
|
||||||
readonly property color foreground: bar ? bar.foreground : Color.foreground
|
readonly property color foreground: bar ? bar.foreground : Color.foreground
|
||||||
readonly property color urgent: bar ? bar.urgent : Color.urgent
|
readonly property color urgent: bar ? bar.urgent : Color.urgent
|
||||||
readonly property color dim: Qt.darker(foreground, 1.55)
|
readonly property color dim: Qt.darker(foreground, 1.55)
|
||||||
|
|
||||||
|
// Brand yellow, sampled as the dominant opaque colour of offscale-logo.png.
|
||||||
|
// Used for exactly one thing: the IP / DNS subtitle under each machine, at
|
||||||
|
// full strength. Stock renders that line through `dim`, but darkening yellow
|
||||||
|
// shifts its hue to olive (#A19203) rather than merely quietening it, so it
|
||||||
|
// gets the flat accent instead. Icons stay on the themed foreground.
|
||||||
|
readonly property color accent: "#FAE204"
|
||||||
readonly property string fontFamily: bar ? bar.fontFamily : Style.font.family
|
readonly property string fontFamily: bar ? bar.fontFamily : Style.font.family
|
||||||
readonly property bool showConnections: tailscale.accounts.length > 1 || tailscale.accountsAccessDenied
|
readonly property bool showConnections: tailscale.accounts.length > 1 || tailscale.accountsAccessDenied
|
||||||
readonly property bool showPeers: tailscale.active && tailscale.peers.length > 0
|
readonly property bool showPeers: tailscale.active && tailscale.peers.length > 0
|
||||||
@@ -499,13 +508,19 @@ Panel {
|
|||||||
// header's only cursor target. The service already flips `active`
|
// header's only cursor target. The service already flips `active`
|
||||||
// optimistically, so the knob throws the instant you click it.
|
// optimistically, so the knob throws the instant you click it.
|
||||||
trailingControl: Component {
|
trailingControl: Component {
|
||||||
ToggleSwitch {
|
OffscaleToggle {
|
||||||
id: powerSwitch
|
id: powerSwitch
|
||||||
visible: tailscale.installed
|
visible: tailscale.installed
|
||||||
checked: tailscale.active
|
checked: tailscale.active
|
||||||
busy: tailscale.busy
|
busy: tailscale.busy
|
||||||
hasCursor: header.ringVisible
|
hasCursor: header.ringVisible
|
||||||
foreground: hero.foreground
|
foreground: hero.foreground
|
||||||
|
// ToggleSwitch defaults `accent` to Color.accent — the theme
|
||||||
|
// accent, which is where the off-white/peach knob and border
|
||||||
|
// came from. The hero only forwards `foreground`, so pin
|
||||||
|
// accent to it as well and the switch reads white in both the
|
||||||
|
// connected and disconnected states.
|
||||||
|
accent: hero.foreground
|
||||||
onHovered: function(on) { if (on) header.focusHero() }
|
onHovered: function(on) { if (on) header.focusHero() }
|
||||||
onToggled: tailscale.toggleOffscale()
|
onToggled: tailscale.toggleOffscale()
|
||||||
|
|
||||||
@@ -998,7 +1013,7 @@ Panel {
|
|||||||
if (peerRow.peerDns !== "") parts.push(peerRow.peerDns)
|
if (peerRow.peerDns !== "") parts.push(peerRow.peerDns)
|
||||||
return parts.join(" · ")
|
return parts.join(" · ")
|
||||||
}
|
}
|
||||||
color: root.dim
|
color: root.accent
|
||||||
font.family: root.fontFamily
|
font.family: root.fontFamily
|
||||||
font.pixelSize: Style.font.caption
|
font.pixelSize: Style.font.caption
|
||||||
elide: Text.ElideRight
|
elide: Text.ElideRight
|
||||||
|
|||||||
Reference in New Issue
Block a user