feat(appearance): unify QuickShell and Sway theme engine
This commit is contained in:
parent
aa140b0de5
commit
9274fbfd3a
50 changed files with 809 additions and 238 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -5,6 +5,8 @@ hyprwhspr/.local/share/
|
|||
**/__pycache__/
|
||||
*.py[cod]
|
||||
**/node_modules/
|
||||
sway/.config/sway/generated/theme.conf
|
||||
sway/.config/sway/generated/theme.conf.tmp
|
||||
|
||||
# Local editor/application backups
|
||||
*.bak
|
||||
|
|
|
|||
135
bin/.local/bin/generate-appearance.py
Executable file
135
bin/.local/bin/generate-appearance.py
Executable file
|
|
@ -0,0 +1,135 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Générer et appliquer les couleurs Sway depuis la palette Catppuccin unique."""
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[3]
|
||||
PALETTE = ROOT / 'quickshell/.config/quickshell/labfy-sway/theme/catppuccin.json'
|
||||
SWAY = Path(os.environ.get('XDG_CONFIG_HOME', Path.home() / '.config')) / 'sway'
|
||||
STATE = Path(os.environ.get('XDG_STATE_HOME', Path.home() / '.local/state')) / 'labfy-appearance/effective.json'
|
||||
ACCENTS = ('rosewater', 'flamingo', 'pink', 'mauve', 'red', 'maroon', 'peach',
|
||||
'yellow', 'green', 'teal', 'sky', 'sapphire', 'blue', 'lavender')
|
||||
HEX = re.compile(r'#[0-9a-fA-F]{6}\Z')
|
||||
|
||||
|
||||
def palette_data():
|
||||
data = json.loads(PALETTE.read_text(encoding='utf-8'))
|
||||
if set(data) != {'latte', 'frappe', 'macchiato', 'mocha'}:
|
||||
raise ValueError('Palette incomplète')
|
||||
for colors in data.values():
|
||||
if len(colors) != 26 or not all(HEX.fullmatch(v) for v in colors.values()):
|
||||
raise ValueError('Palette invalide')
|
||||
return data
|
||||
|
||||
|
||||
def render(flavor, accent, profile='default'):
|
||||
data = palette_data()
|
||||
if flavor not in data:
|
||||
raise ValueError('flavor invalide: ' + flavor)
|
||||
if accent not in ACCENTS:
|
||||
raise ValueError('accent invalide: ' + accent)
|
||||
if profile != 'default':
|
||||
raise ValueError('profile invalide: ' + profile)
|
||||
p = data[flavor]
|
||||
# CONTRACT: toutes les valeurs interpolées proviennent de clés bornées ou
|
||||
# de chaînes hex validées ; aucune entrée utilisateur ne devient une commande.
|
||||
lines = ['# Généré depuis theme/catppuccin.json ; ne pas éditer.']
|
||||
lines += [f'set ${name} {value}' for name, value in p.items()]
|
||||
lines += [f'set $accent {p[accent]}',
|
||||
'set $shadow_active #00000055',
|
||||
'set $shadow_inactive #00000035',
|
||||
'set $inactive_opacity 0.85']
|
||||
return '\n'.join(lines) + '\n'
|
||||
|
||||
|
||||
def atomic_write(path, content):
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
tmp = path.with_name(path.name + '.tmp')
|
||||
# CONTRACT: le fichier précédent reste intact jusqu'au rename sur le même FS.
|
||||
try:
|
||||
with open(tmp, 'wb') as stream:
|
||||
stream.write(content)
|
||||
stream.flush()
|
||||
os.fsync(stream.fileno())
|
||||
os.replace(tmp, path)
|
||||
fd = os.open(path.parent, os.O_DIRECTORY)
|
||||
try:
|
||||
os.fsync(fd)
|
||||
finally:
|
||||
os.close(fd)
|
||||
finally:
|
||||
tmp.unlink(missing_ok=True)
|
||||
|
||||
|
||||
def checked(args):
|
||||
return subprocess.run(args, check=True, capture_output=True, text=True)
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument('flavor', choices=('latte', 'frappe', 'macchiato', 'mocha'))
|
||||
parser.add_argument('--accent', default='lavender', choices=ACCENTS)
|
||||
parser.add_argument('--profile', default='default')
|
||||
parser.add_argument('--output', type=Path, default=SWAY / 'generated/theme.conf')
|
||||
parser.add_argument('--apply', action='store_true', help='valider puis recharger Sway et QuickShell')
|
||||
args = parser.parse_args()
|
||||
if args.apply and args.output != SWAY / 'generated/theme.conf':
|
||||
parser.error('--apply exige la sortie runtime par défaut')
|
||||
try:
|
||||
content = render(args.flavor, args.accent, args.profile).encode('utf-8')
|
||||
old = args.output.read_bytes() if args.output.exists() else None
|
||||
old_state = STATE.read_bytes() if args.apply and STATE.exists() else None
|
||||
previous_theme = None
|
||||
if args.apply:
|
||||
previous_theme = checked(['qs', '-c', 'labfy-sway', 'ipc', 'call',
|
||||
'appearance', 'currentTheme']).stdout.strip()
|
||||
atomic_write(args.output, content)
|
||||
if args.apply:
|
||||
try:
|
||||
checked(['sway', '--validate', '-c', str(SWAY / 'config')])
|
||||
checked(['swaymsg', 'reload'])
|
||||
previous = json.loads(old_state) if old_state else {}
|
||||
changed = (previous.get('effectiveFlavor') != args.flavor
|
||||
or previous.get('effectiveAccent') != args.accent
|
||||
or previous.get('effectiveMode') != 'normal')
|
||||
revision = max(0, int(previous.get('revision', 0))) + int(changed)
|
||||
state = {'version': 1, 'effectiveFlavor': args.flavor,
|
||||
'effectiveMode': 'normal', 'effectiveDark': args.flavor != 'latte',
|
||||
'effectiveHighContrast': False, 'effectiveAccent': args.accent,
|
||||
'revision': revision}
|
||||
# CONTRACT: le backend persiste l'état après validation Sway ;
|
||||
# le contrôleur l'expose aux consommateurs et le lit au restart.
|
||||
atomic_write(STATE, (json.dumps(state, separators=(',', ':')) + '\n').encode('utf-8'))
|
||||
result = checked(['qs', '-c', 'labfy-sway', 'ipc', 'call', 'appearance',
|
||||
'setTheme', args.flavor, args.accent])
|
||||
if result.stdout.strip() != 'true':
|
||||
raise RuntimeError('QuickShell a refusé le thème')
|
||||
except Exception:
|
||||
if old is None:
|
||||
args.output.unlink(missing_ok=True)
|
||||
else:
|
||||
atomic_write(args.output, old)
|
||||
if old_state is None:
|
||||
STATE.unlink(missing_ok=True)
|
||||
else:
|
||||
atomic_write(STATE, old_state)
|
||||
checked(['swaymsg', 'reload'])
|
||||
if previous_theme and '/' in previous_theme:
|
||||
old_flavor, old_accent = previous_theme.split('/', 1)
|
||||
checked(['qs', '-c', 'labfy-sway', 'ipc', 'call', 'appearance',
|
||||
'setTheme', old_flavor, old_accent])
|
||||
raise
|
||||
print(f'{args.flavor}/{args.accent}: {args.output}')
|
||||
except (OSError, ValueError, subprocess.CalledProcessError, RuntimeError) as exc:
|
||||
print(f'Erreur : {exc}', file=sys.stderr)
|
||||
return 1
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
raise SystemExit(main())
|
||||
|
|
@ -8,6 +8,7 @@ import "datecenter"
|
|||
import "notifications"
|
||||
import "status"
|
||||
import "systemmonitor"
|
||||
import "theme"
|
||||
|
||||
PanelWindow {
|
||||
id: bar
|
||||
|
|
@ -37,7 +38,7 @@ PanelWindow {
|
|||
// layer-shell est focusable dès son ouverture. OnDemand n'accapare pas
|
||||
// le clavier hors interaction avec la barre ou son popup.
|
||||
focusable: true
|
||||
color: "#eb1e1e2e"
|
||||
color: Theme.panelBackground
|
||||
|
||||
// CONTRACT: toute la capsule centrale partage le même popup et la même exclusion XOR.
|
||||
function toggleDateCenter() {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,8 @@
|
|||
# QuickShell labfy-sway
|
||||
|
||||
Le [moteur de thème commun](theme/README.md) fournit les couleurs effectives
|
||||
à QuickShell et Sway/SwayFX depuis une seule palette Catppuccin versionnée.
|
||||
|
||||
Barre principale et serveur de notifications de la session SwayFX.
|
||||
|
||||
```text
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import QtQuick
|
||||
import "../theme"
|
||||
|
||||
Text {
|
||||
font.family: "JetBrainsMono Nerd Font Mono"
|
||||
|
|
@ -6,5 +7,5 @@ Text {
|
|||
font.pixelSize: 18
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
color: "#cdd6f4"
|
||||
color: Theme.foreground
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import QtQuick
|
||||
import "../theme"
|
||||
|
||||
Rectangle {
|
||||
id: button
|
||||
|
|
@ -8,13 +9,13 @@ Rectangle {
|
|||
implicitWidth: caption.implicitWidth + 20
|
||||
implicitHeight: 30
|
||||
radius: 4
|
||||
color: !enabled ? "#313244" : pointer.containsMouse ? "#585b70" : "#45475a"
|
||||
color: !enabled ? Theme.buttonBackground : pointer.containsMouse ? Theme.strongBorder : Theme.border
|
||||
opacity: enabled ? 1 : 0.55
|
||||
Text {
|
||||
id: caption
|
||||
anchors.centerIn: parent
|
||||
text: button.label
|
||||
color: button.danger ? "#f38ba8" : "#cdd6f4"
|
||||
color: button.danger ? Theme.danger : Theme.foreground
|
||||
font.pixelSize: 12
|
||||
}
|
||||
MouseArea {
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import QtQuick.Controls
|
|||
import Quickshell.Io
|
||||
import Quickshell.Services.UPower
|
||||
import "../components"
|
||||
import "../theme"
|
||||
|
||||
Item {
|
||||
id: page
|
||||
|
|
@ -211,7 +212,7 @@ Item {
|
|||
anchors.leftMargin: 8
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: "Batterie"
|
||||
color: "#cdd6f4"
|
||||
color: Theme.foreground
|
||||
font.pixelSize: 16
|
||||
font.bold: true
|
||||
}
|
||||
|
|
@ -228,7 +229,7 @@ Item {
|
|||
width: parent.width
|
||||
height: 68
|
||||
radius: 4
|
||||
color: "#313244"
|
||||
color: Theme.buttonBackground
|
||||
NerdIcon {
|
||||
id: batteryIcon
|
||||
anchors.left: parent.left
|
||||
|
|
@ -245,12 +246,12 @@ Item {
|
|||
spacing: 2
|
||||
Text {
|
||||
text: page.available ? page.percent + " %" : "Batterie indisponible"
|
||||
color: "#cdd6f4"
|
||||
color: Theme.foreground
|
||||
font.pixelSize: 16
|
||||
}
|
||||
Text {
|
||||
text: page.batteryState
|
||||
color: "#a6adc8"
|
||||
color: Theme.secondaryForeground
|
||||
font.pixelSize: 12
|
||||
}
|
||||
}
|
||||
|
|
@ -258,7 +259,7 @@ Item {
|
|||
|
||||
Text {
|
||||
text: "Limite de charge"
|
||||
color: "#cdd6f4"
|
||||
color: Theme.foreground
|
||||
font.pixelSize: 14
|
||||
}
|
||||
|
||||
|
|
@ -267,8 +268,8 @@ Item {
|
|||
spacing: 8
|
||||
Rectangle {
|
||||
width: 42; height: 38; radius: 4
|
||||
color: minusPointer.containsMouse ? "#45475a" : "#313244"
|
||||
Text { anchors.centerIn: parent; text: "−"; color: "#cdd6f4"; font.pixelSize: 18 }
|
||||
color: minusPointer.containsMouse ? Theme.border : Theme.buttonBackground
|
||||
Text { anchors.centerIn: parent; text: "−"; color: Theme.foreground; font.pixelSize: 18 }
|
||||
MouseArea {
|
||||
id: minusPointer
|
||||
anchors.fill: parent
|
||||
|
|
@ -283,7 +284,7 @@ Item {
|
|||
width: parent.width - 100
|
||||
height: 38
|
||||
horizontalAlignment: TextInput.AlignHCenter
|
||||
color: "#cdd6f4"
|
||||
color: Theme.foreground
|
||||
font.pixelSize: 16
|
||||
maximumLength: 3
|
||||
inputMethodHints: Qt.ImhDigitsOnly
|
||||
|
|
@ -303,22 +304,22 @@ Item {
|
|||
}
|
||||
background: Rectangle {
|
||||
radius: 4
|
||||
color: "#313244"
|
||||
border.color: page.validProposed ? "#45475a" : "#f38ba8"
|
||||
color: Theme.buttonBackground
|
||||
border.color: page.validProposed ? Theme.border : Theme.danger
|
||||
}
|
||||
Text {
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 12
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: "%"
|
||||
color: "#a6adc8"
|
||||
color: Theme.secondaryForeground
|
||||
font.pixelSize: 13
|
||||
}
|
||||
}
|
||||
Rectangle {
|
||||
width: 42; height: 38; radius: 4
|
||||
color: plusPointer.containsMouse ? "#45475a" : "#313244"
|
||||
Text { anchors.centerIn: parent; text: "+"; color: "#cdd6f4"; font.pixelSize: 18 }
|
||||
color: plusPointer.containsMouse ? Theme.border : Theme.buttonBackground
|
||||
Text { anchors.centerIn: parent; text: "+"; color: Theme.foreground; font.pixelSize: 18 }
|
||||
MouseArea {
|
||||
id: plusPointer
|
||||
anchors.fill: parent
|
||||
|
|
@ -344,11 +345,11 @@ Item {
|
|||
height: 32
|
||||
radius: 4
|
||||
opacity: modelData !== "" ? 1 : 0
|
||||
color: keyPointer.containsMouse ? "#45475a" : "#313244"
|
||||
color: keyPointer.containsMouse ? Theme.border : Theme.buttonBackground
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
text: modelData
|
||||
color: "#cdd6f4"
|
||||
color: Theme.foreground
|
||||
font.pixelSize: 13
|
||||
}
|
||||
MouseArea {
|
||||
|
|
@ -369,13 +370,13 @@ Item {
|
|||
delegate: Rectangle {
|
||||
required property int modelData
|
||||
width: 60; height: 32; radius: 4
|
||||
color: page.reportedThreshold === modelData ? "#585b70" : "#313244"
|
||||
color: page.reportedThreshold === modelData ? Theme.strongBorder : Theme.buttonBackground
|
||||
border.color: page.validProposed && Number(valueField.text) === modelData
|
||||
? "#cba6f7" : "transparent"
|
||||
? Theme.accent : "transparent"
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
text: modelData
|
||||
color: "#cdd6f4"
|
||||
color: Theme.foreground
|
||||
font.pixelSize: 13
|
||||
}
|
||||
MouseArea {
|
||||
|
|
@ -390,7 +391,7 @@ Item {
|
|||
Text {
|
||||
text: page.reportedThreshold >= 0
|
||||
? "Limite rapportée : " + page.reportedThreshold + " %" : "Limite indisponible"
|
||||
color: "#a6adc8"
|
||||
color: Theme.secondaryForeground
|
||||
font.pixelSize: 12
|
||||
}
|
||||
|
||||
|
|
@ -398,12 +399,12 @@ Item {
|
|||
width: parent.width
|
||||
height: 36
|
||||
radius: 4
|
||||
color: applyPointer.containsMouse && applyPointer.enabled ? "#b4befe" : "#cba6f7"
|
||||
color: Theme.accent
|
||||
opacity: applyPointer.enabled ? 1 : 0.5
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
text: page.phase === "Applying" ? "Application…" : "Appliquer"
|
||||
color: "#1e1e2e"
|
||||
color: Theme.onAccent
|
||||
font.pixelSize: 13
|
||||
}
|
||||
MouseArea {
|
||||
|
|
@ -423,7 +424,7 @@ Item {
|
|||
: !page.validProposed ? "Valeur comprise entre 40 et 100"
|
||||
: !page.helperReady ? "Service de charge non déployé" : ""
|
||||
color: page.phase === "Error" || !page.validProposed || !page.helperReady
|
||||
? "#f38ba8" : "#a6e3a1"
|
||||
? Theme.danger : Theme.success
|
||||
font.pixelSize: 12
|
||||
wrapMode: Text.WordWrap
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import QtQuick.Controls
|
|||
import Quickshell
|
||||
import Quickshell.Bluetooth
|
||||
import "../components"
|
||||
import "../theme"
|
||||
|
||||
Item {
|
||||
id: page
|
||||
|
|
@ -61,10 +62,10 @@ Item {
|
|||
Text {
|
||||
width: parent.width - 60; height: 30; verticalAlignment: Text.AlignVCenter
|
||||
text: page.stage === "main" ? "Bluetooth" : page.nameOf(page.selectedDevice)
|
||||
color: "#cdd6f4"; font.pixelSize: 16; font.bold: true; elide: Text.ElideRight
|
||||
color: Theme.foreground; font.pixelSize: 16; font.bold: true; elide: Text.ElideRight
|
||||
}
|
||||
}
|
||||
Text { width: parent.width; visible: page.message.length > 0; text: page.message; wrapMode: Text.Wrap; color: "#f9e2af"; font.pixelSize: 12 }
|
||||
Text { width: parent.width; visible: page.message.length > 0; text: page.message; wrapMode: Text.Wrap; color: Theme.warningForeground; font.pixelSize: 12 }
|
||||
Loader {
|
||||
width: parent.width; height: Math.max(0, parent.height - y)
|
||||
sourceComponent: page.stage === "main" ? mainComponent : page.stage === "details"
|
||||
|
|
@ -80,22 +81,22 @@ Item {
|
|||
width: page.width; spacing: 10
|
||||
Row {
|
||||
width: parent.width; spacing: 8
|
||||
Text { width: parent.width - 100; text: !page.adapter ? "Adaptateur indisponible" : page.adapter.enabled ? "Bluetooth activé" : "Bluetooth désactivé"; color: "#cdd6f4"; font.pixelSize: 13 }
|
||||
Text { width: parent.width - 100; text: !page.adapter ? "Adaptateur indisponible" : page.adapter.enabled ? "Bluetooth activé" : "Bluetooth désactivé"; color: Theme.foreground; font.pixelSize: 13 }
|
||||
ActionButton { label: page.adapter && page.adapter.enabled ? "Désactiver" : "Activer"; enabled: !!page.adapter; onClicked: page.adapter.enabled = !page.adapter.enabled }
|
||||
}
|
||||
Text { text: page.scanWanted ? "Recherche d'appareils…" : "Scan arrêté"; color: "#a6adc8"; font.pixelSize: 11 }
|
||||
Text { visible: page.devices.length === 0; text: "Aucun appareil connu"; color: "#a6adc8"; font.pixelSize: 12 }
|
||||
Text { visible: page.connectedDevices.length > 0; text: "Connectés"; color: "#cba6f7"; font.bold: true; font.pixelSize: 13 }
|
||||
Text { text: page.scanWanted ? "Recherche d'appareils…" : "Scan arrêté"; color: Theme.secondaryForeground; font.pixelSize: 11 }
|
||||
Text { visible: page.devices.length === 0; text: "Aucun appareil connu"; color: Theme.secondaryForeground; font.pixelSize: 12 }
|
||||
Text { visible: page.connectedDevices.length > 0; text: "Connectés"; color: Theme.accentForeground; font.bold: true; font.pixelSize: 13 }
|
||||
Repeater {
|
||||
model: ScriptModel { values: page.connectedDevices; objectProp: "address" }
|
||||
delegate: deviceRowComponent
|
||||
}
|
||||
Text { visible: page.pairedDevices.length > 0; text: "Appairés"; color: "#cba6f7"; font.bold: true; font.pixelSize: 13 }
|
||||
Text { visible: page.pairedDevices.length > 0; text: "Appairés"; color: Theme.accentForeground; font.bold: true; font.pixelSize: 13 }
|
||||
Repeater {
|
||||
model: ScriptModel { values: page.pairedDevices; objectProp: "address" }
|
||||
delegate: deviceRowComponent
|
||||
}
|
||||
Text { visible: page.newDevices.length > 0; text: "Nouveaux appareils"; color: "#cba6f7"; font.bold: true; font.pixelSize: 13 }
|
||||
Text { visible: page.newDevices.length > 0; text: "Nouveaux appareils"; color: Theme.accentForeground; font.bold: true; font.pixelSize: 13 }
|
||||
Repeater {
|
||||
model: ScriptModel { values: page.newDevices; objectProp: "address" }
|
||||
delegate: deviceRowComponent
|
||||
|
|
@ -109,7 +110,7 @@ Item {
|
|||
required property var modelData
|
||||
readonly property var device: modelData
|
||||
width: page.width; height: 48; radius: 4
|
||||
color: pointer.containsMouse ? "#45475a" : "#313244"
|
||||
color: pointer.containsMouse ? Theme.border : Theme.buttonBackground
|
||||
Image {
|
||||
id: iconImage; anchors.left: parent.left; anchors.leftMargin: 9; anchors.verticalCenter: parent.verticalCenter
|
||||
width: 19; height: 19; source: device.icon ? Quickshell.iconPath(device.icon, true) : ""
|
||||
|
|
@ -117,13 +118,13 @@ Item {
|
|||
}
|
||||
NerdIcon {
|
||||
anchors.centerIn: iconImage; visible: !iconImage.visible
|
||||
text: ""; color: "#cba6f7"; font.pixelSize: 17
|
||||
text: ""; color: Theme.accentForeground; font.pixelSize: 17
|
||||
}
|
||||
Column {
|
||||
anchors.left: parent.left; anchors.leftMargin: 38; anchors.right: parent.right
|
||||
anchors.rightMargin: 10; anchors.verticalCenter: parent.verticalCenter; spacing: 2
|
||||
Text { width: parent.width; text: page.nameOf(device); color: "#cdd6f4"; font.pixelSize: 12; elide: Text.ElideRight }
|
||||
Text { text: page.stateOf(device) + (device.batteryAvailable ? " • " + Math.round(device.battery * 100) + " %" : ""); color: "#a6adc8"; font.pixelSize: 11 }
|
||||
Text { width: parent.width; text: page.nameOf(device); color: Theme.foreground; font.pixelSize: 12; elide: Text.ElideRight }
|
||||
Text { text: page.stateOf(device) + (device.batteryAvailable ? " • " + Math.round(device.battery * 100) + " %" : ""); color: Theme.secondaryForeground; font.pixelSize: 11 }
|
||||
}
|
||||
MouseArea { id: pointer; anchors.fill: parent; hoverEnabled: true; onClicked: page.choose(device) }
|
||||
}
|
||||
|
|
@ -134,10 +135,10 @@ Item {
|
|||
clip: true; ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
|
||||
Column {
|
||||
width: page.width; spacing: 10
|
||||
Text { text: page.selectedDevice ? page.stateOf(page.selectedDevice) : "Appareil indisponible"; color: "#cdd6f4"; font.pixelSize: 13 }
|
||||
Text { visible: !!page.selectedDevice && page.selectedDevice.deviceName !== page.selectedDevice.name; text: "Nom d'origine : " + (page.selectedDevice ? page.selectedDevice.deviceName : ""); color: "#a6adc8"; font.pixelSize: 11 }
|
||||
Text { visible: !!page.selectedDevice && page.selectedDevice.batteryAvailable; text: "Batterie : " + (page.selectedDevice ? Math.round(page.selectedDevice.battery * 100) : 0) + " %"; color: "#cdd6f4"; font.pixelSize: 12 }
|
||||
Text { text: page.selectedDevice ? "Appairé : " + (page.selectedDevice.paired ? "oui" : "non") + " • Lié : " + (page.selectedDevice.bonded ? "oui" : "non") : ""; color: "#a6adc8"; font.pixelSize: 11 }
|
||||
Text { text: page.selectedDevice ? page.stateOf(page.selectedDevice) : "Appareil indisponible"; color: Theme.foreground; font.pixelSize: 13 }
|
||||
Text { visible: !!page.selectedDevice && page.selectedDevice.deviceName !== page.selectedDevice.name; text: "Nom d'origine : " + (page.selectedDevice ? page.selectedDevice.deviceName : ""); color: Theme.secondaryForeground; font.pixelSize: 11 }
|
||||
Text { visible: !!page.selectedDevice && page.selectedDevice.batteryAvailable; text: "Batterie : " + (page.selectedDevice ? Math.round(page.selectedDevice.battery * 100) : 0) + " %"; color: Theme.foreground; font.pixelSize: 12 }
|
||||
Text { text: page.selectedDevice ? "Appairé : " + (page.selectedDevice.paired ? "oui" : "non") + " • Lié : " + (page.selectedDevice.bonded ? "oui" : "non") : ""; color: Theme.secondaryForeground; font.pixelSize: 11 }
|
||||
Row {
|
||||
spacing: 8
|
||||
ActionButton {
|
||||
|
|
@ -159,37 +160,37 @@ Item {
|
|||
}
|
||||
Row {
|
||||
spacing: 8
|
||||
Text { width: 165; text: "Appareil de confiance"; color: "#cdd6f4"; font.pixelSize: 12 }
|
||||
Text { width: 165; text: "Appareil de confiance"; color: Theme.foreground; font.pixelSize: 12 }
|
||||
ActionButton { label: page.selectedDevice && page.selectedDevice.trusted ? "Activé" : "Désactivé"; enabled: !!page.selectedDevice && page.selectedDevice.paired; onClicked: page.selectedDevice.trusted = !page.selectedDevice.trusted }
|
||||
}
|
||||
Row {
|
||||
spacing: 8
|
||||
Text { width: 165; text: "Bloqué"; color: "#cdd6f4"; font.pixelSize: 12 }
|
||||
Text { width: 165; text: "Bloqué"; color: Theme.foreground; font.pixelSize: 12 }
|
||||
ActionButton { label: page.selectedDevice && page.selectedDevice.blocked ? "Oui" : "Non"; enabled: !!page.selectedDevice; onClicked: { if (page.selectedDevice.blocked) page.selectedDevice.blocked = false; else page.stage = "block"; } }
|
||||
}
|
||||
Text { width: parent.width; wrapMode: Text.Wrap; text: "Un appareil bloqué ne peut pas se connecter."; color: "#a6adc8"; font.pixelSize: 11 }
|
||||
Text { width: parent.width; wrapMode: Text.Wrap; text: "Un appareil bloqué ne peut pas se connecter."; color: Theme.secondaryForeground; font.pixelSize: 11 }
|
||||
Row {
|
||||
visible: !!page.selectedDevice && page.selectedDevice.paired
|
||||
&& page.adapter && page.adapter.enabled
|
||||
&& page.selectedDevice.wakeAllowed !== undefined
|
||||
spacing: 8
|
||||
Text { width: 165; text: "Autoriser le réveil"; color: "#cdd6f4"; font.pixelSize: 12 }
|
||||
Text { width: 165; text: "Autoriser le réveil"; color: Theme.foreground; font.pixelSize: 12 }
|
||||
ActionButton { label: page.selectedDevice && page.selectedDevice.wakeAllowed ? "Activé" : "Désactivé"; onClicked: page.selectedDevice.wakeAllowed = !page.selectedDevice.wakeAllowed }
|
||||
}
|
||||
Text { text: "Nom local"; color: "#cba6f7"; font.pixelSize: 13; font.bold: true }
|
||||
Text { text: "Nom local"; color: Theme.accentForeground; font.pixelSize: 13; font.bold: true }
|
||||
TextField {
|
||||
id: aliasInput; width: page.width
|
||||
text: page.selectedDevice ? page.selectedDevice.name : ""
|
||||
placeholderText: "Alias local"; selectByMouse: true
|
||||
color: "#cdd6f4"; placeholderTextColor: "#6c7086"
|
||||
background: Rectangle { color: "#313244"; border.color: aliasInput.activeFocus ? "#cba6f7" : "#585b70"; radius: 4 }
|
||||
color: Theme.foreground; placeholderTextColor: Theme.disabledForeground
|
||||
background: Rectangle { color: Theme.buttonBackground; border.color: aliasInput.activeFocus ? Theme.accent : Theme.strongBorder; radius: 4 }
|
||||
}
|
||||
Row {
|
||||
spacing: 8
|
||||
ActionButton { label: "Réinitialiser"; enabled: !!page.selectedDevice; onClicked: { page.selectedDevice.name = ""; aliasInput.text = page.selectedDevice.name; } }
|
||||
ActionButton { label: "Enregistrer"; enabled: !!page.selectedDevice; onClicked: page.selectedDevice.name = aliasInput.text.trim() }
|
||||
}
|
||||
Text { text: "Informations techniques : " + (page.selectedDevice ? page.selectedDevice.address : ""); color: "#6c7086"; font.pixelSize: 10 }
|
||||
Text { text: "Informations techniques : " + (page.selectedDevice ? page.selectedDevice.address : ""); color: Theme.disabledForeground; font.pixelSize: 10 }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -197,8 +198,8 @@ Item {
|
|||
id: forgetComponent
|
||||
Column {
|
||||
spacing: 10
|
||||
Text { width: page.width; wrapMode: Text.Wrap; text: "Oublier " + page.nameOf(page.selectedDevice) + " ?"; color: "#f38ba8"; font.pixelSize: 13 }
|
||||
Text { width: page.width; wrapMode: Text.Wrap; text: "Cela supprimera les informations de pairage."; color: "#a6adc8"; font.pixelSize: 12 }
|
||||
Text { width: page.width; wrapMode: Text.Wrap; text: "Oublier " + page.nameOf(page.selectedDevice) + " ?"; color: Theme.danger; font.pixelSize: 13 }
|
||||
Text { width: page.width; wrapMode: Text.Wrap; text: "Cela supprimera les informations de pairage."; color: Theme.secondaryForeground; font.pixelSize: 12 }
|
||||
Row {
|
||||
spacing: 8
|
||||
ActionButton { label: "Annuler"; onClicked: page.stage = "details" }
|
||||
|
|
@ -210,8 +211,8 @@ Item {
|
|||
id: blockComponent
|
||||
Column {
|
||||
spacing: 10
|
||||
Text { width: page.width; wrapMode: Text.Wrap; text: "Bloquer " + page.nameOf(page.selectedDevice) + " ?"; color: "#f38ba8"; font.pixelSize: 13 }
|
||||
Text { width: page.width; wrapMode: Text.Wrap; text: "Un appareil bloqué ne peut pas se connecter."; color: "#a6adc8"; font.pixelSize: 12 }
|
||||
Text { width: page.width; wrapMode: Text.Wrap; text: "Bloquer " + page.nameOf(page.selectedDevice) + " ?"; color: Theme.danger; font.pixelSize: 13 }
|
||||
Text { width: page.width; wrapMode: Text.Wrap; text: "Un appareil bloqué ne peut pas se connecter."; color: Theme.secondaryForeground; font.pixelSize: 12 }
|
||||
Row {
|
||||
spacing: 8
|
||||
ActionButton { label: "Annuler"; onClicked: page.stage = "details" }
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import QtQuick
|
|||
import QtQuick.Controls
|
||||
import Quickshell.Io
|
||||
import "../components"
|
||||
import "../theme"
|
||||
|
||||
Item {
|
||||
id: brightnessControl
|
||||
|
|
@ -44,7 +45,7 @@ Item {
|
|||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: ""
|
||||
font.pixelSize: 18
|
||||
color: "#cdd6f4"
|
||||
color: Theme.foreground
|
||||
}
|
||||
|
||||
Text {
|
||||
|
|
@ -52,7 +53,7 @@ Item {
|
|||
anchors.leftMargin: 10
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: "Luminosité"
|
||||
color: "#cdd6f4"
|
||||
color: Theme.foreground
|
||||
font.pixelSize: 14
|
||||
}
|
||||
|
||||
|
|
@ -60,7 +61,7 @@ Item {
|
|||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: brightnessControl.available ? brightnessControl.percent + "%" : "Indisponible"
|
||||
color: "#a6adc8"
|
||||
color: Theme.secondaryForeground
|
||||
font.pixelSize: 14
|
||||
}
|
||||
}
|
||||
|
|
@ -88,13 +89,13 @@ Item {
|
|||
width: slider.availableWidth
|
||||
height: 8
|
||||
radius: 4
|
||||
color: "#313244"
|
||||
color: Theme.buttonBackground
|
||||
|
||||
Rectangle {
|
||||
width: slider.visualPosition * parent.width
|
||||
height: parent.height
|
||||
radius: parent.radius
|
||||
color: "#cba6f7"
|
||||
color: Theme.accent
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -104,7 +105,7 @@ Item {
|
|||
width: 16
|
||||
height: 16
|
||||
radius: 8
|
||||
color: "#cdd6f4"
|
||||
color: Theme.foreground
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import QtQuick
|
||||
import "../theme"
|
||||
|
||||
Item {
|
||||
id: confirmation
|
||||
|
|
@ -19,7 +20,7 @@ Item {
|
|||
Text {
|
||||
width: parent.width
|
||||
text: confirmation.actionInfo.confirmTitle
|
||||
color: "#cdd6f4"
|
||||
color: Theme.foreground
|
||||
font.pixelSize: 16
|
||||
font.bold: true
|
||||
wrapMode: Text.Wrap
|
||||
|
|
@ -28,7 +29,7 @@ Item {
|
|||
Text {
|
||||
width: parent.width
|
||||
text: confirmation.actionInfo.description
|
||||
color: "#a6adc8"
|
||||
color: Theme.secondaryForeground
|
||||
font.pixelSize: 13
|
||||
wrapMode: Text.Wrap
|
||||
}
|
||||
|
|
@ -42,12 +43,12 @@ Item {
|
|||
width: (parent.width - parent.spacing) / 2
|
||||
height: parent.height
|
||||
radius: 4
|
||||
color: cancelPointer.containsMouse ? "#45475a" : "#313244"
|
||||
color: cancelPointer.containsMouse ? Theme.border : Theme.buttonBackground
|
||||
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
text: "Annuler"
|
||||
color: "#cdd6f4"
|
||||
color: Theme.foreground
|
||||
font.pixelSize: 13
|
||||
}
|
||||
MouseArea {
|
||||
|
|
@ -68,7 +69,7 @@ Item {
|
|||
Text {
|
||||
anchors.centerIn: parent
|
||||
text: confirmation.actionInfo.label
|
||||
color: "#1e1e2e"
|
||||
color: Theme.contrastingText(confirmation.actionInfo.accent)
|
||||
font.pixelSize: 13
|
||||
font.bold: true
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import Quickshell.I3
|
|||
import Quickshell.Networking
|
||||
import Quickshell.Bluetooth
|
||||
import "../components"
|
||||
import "../theme"
|
||||
|
||||
PopupWindow {
|
||||
id: popup
|
||||
|
|
@ -15,19 +16,19 @@ PopupWindow {
|
|||
property var pendingAction: null
|
||||
// Une seule table associe les libellés, confirmations et commandes de session.
|
||||
readonly property var sessionActions: [
|
||||
{ id: "lock", label: "Verrouiller", icon: "", accent: "#b4befe", command: ["swaylock"] },
|
||||
{ id: "suspend", label: "Veille", icon: "", accent: "#b4befe",
|
||||
{ id: "lock", label: "Verrouiller", icon: "", accent: Theme.accent, command: ["swaylock"] },
|
||||
{ id: "suspend", label: "Veille", icon: "", accent: Theme.accent,
|
||||
confirmTitle: "Mettre l'ordinateur en veille ?",
|
||||
description: "La session sera verrouillée avant la veille.",
|
||||
command: ["systemctl", "suspend"] },
|
||||
{ id: "logout", label: "Déconnexion", icon: "", accent: "#fab387",
|
||||
{ id: "logout", label: "Déconnexion", icon: "", accent: Theme.urgent,
|
||||
confirmTitle: "Se déconnecter ?",
|
||||
description: "La session SwayFX en cours sera fermée.", command: null },
|
||||
{ id: "reboot", label: "Redémarrer", icon: "", accent: "#f9e2af",
|
||||
{ id: "reboot", label: "Redémarrer", icon: "", accent: Theme.warning,
|
||||
confirmTitle: "Redémarrer l'ordinateur ?",
|
||||
description: "La session en cours sera fermée.",
|
||||
command: ["systemctl", "reboot"] },
|
||||
{ id: "poweroff", label: "Éteindre", icon: "", accent: "#f38ba8",
|
||||
{ id: "poweroff", label: "Éteindre", icon: "", accent: Theme.danger,
|
||||
confirmTitle: "Éteindre l'ordinateur ?",
|
||||
description: "La session en cours sera fermée.",
|
||||
command: ["systemctl", "poweroff"] }
|
||||
|
|
@ -82,8 +83,8 @@ PopupWindow {
|
|||
Rectangle {
|
||||
anchors.fill: parent
|
||||
radius: 4
|
||||
color: "#1e1e2e"
|
||||
border.color: "#45475a"
|
||||
color: Theme.popupBackground
|
||||
border.color: Theme.border
|
||||
|
||||
StackLayout {
|
||||
anchors.fill: parent
|
||||
|
|
@ -96,7 +97,7 @@ PopupWindow {
|
|||
|
||||
Text {
|
||||
text: "Réglages rapides"
|
||||
color: "#cdd6f4"
|
||||
color: Theme.foreground
|
||||
font.pixelSize: 16
|
||||
font.bold: true
|
||||
}
|
||||
|
|
@ -130,7 +131,7 @@ PopupWindow {
|
|||
width: 40
|
||||
height: 30
|
||||
radius: 4
|
||||
color: powerPointer.containsMouse ? "#45475a" : "#313244"
|
||||
color: powerPointer.containsMouse ? Theme.border : Theme.buttonBackground
|
||||
|
||||
NerdIcon {
|
||||
anchors.centerIn: parent
|
||||
|
|
@ -171,7 +172,7 @@ PopupWindow {
|
|||
ConfirmAction {
|
||||
id: confirmationPage
|
||||
actionInfo: popup.pendingAction || ({ id: "", label: "", confirmTitle: "",
|
||||
description: "", accent: "#cdd6f4" })
|
||||
description: "", accent: Theme.foreground })
|
||||
onCancelled: {
|
||||
popup.pendingAction = null;
|
||||
popup.currentPage = 3;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import QtQuick
|
||||
import "../components"
|
||||
import "../theme"
|
||||
|
||||
Rectangle {
|
||||
id: controlButton
|
||||
|
|
@ -10,14 +11,14 @@ Rectangle {
|
|||
width: 48
|
||||
height: 26
|
||||
radius: 4
|
||||
color: open ? "#cba6f7" : pointer.containsMouse ? "#45475a" : "#313244"
|
||||
color: open ? Theme.accent : pointer.containsMouse ? Theme.border : Theme.buttonBackground
|
||||
|
||||
NerdIcon {
|
||||
anchors.centerIn: parent
|
||||
text: ""
|
||||
// WHY: garder le même poids visuel que les statuts voisins dans la capsule fixe.
|
||||
font.pixelSize: 20
|
||||
color: controlButton.open ? "#1e1e2e" : "#cdd6f4"
|
||||
color: controlButton.open ? Theme.onAccent : Theme.foreground
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import QtQuick
|
||||
import Quickshell.Services.UPower
|
||||
import "../components"
|
||||
import "../theme"
|
||||
|
||||
Item {
|
||||
id: powerControl
|
||||
|
|
@ -22,7 +23,7 @@ Item {
|
|||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: ""
|
||||
font.pixelSize: 18
|
||||
color: "#cdd6f4"
|
||||
color: Theme.foreground
|
||||
}
|
||||
|
||||
Text {
|
||||
|
|
@ -30,7 +31,7 @@ Item {
|
|||
anchors.leftMargin: 10
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: "Profil d'énergie"
|
||||
color: "#cdd6f4"
|
||||
color: Theme.foreground
|
||||
font.pixelSize: 14
|
||||
}
|
||||
}
|
||||
|
|
@ -57,12 +58,12 @@ Item {
|
|||
width: (choices.width - 2 * choices.spacing) / 3
|
||||
height: choices.height
|
||||
radius: 4
|
||||
color: !available ? "#313244" : selected ? "#cba6f7" : hover.hovered ? "#45475a" : "#313244"
|
||||
color: !available ? Theme.buttonBackground : selected ? Theme.accent : hover.hovered ? Theme.border : Theme.buttonBackground
|
||||
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
text: parent.modelData.label
|
||||
color: !parent.available ? "#6c7086" : parent.selected ? "#1e1e2e" : "#cdd6f4"
|
||||
color: !parent.available ? Theme.disabledForeground : parent.selected ? Theme.onAccent : Theme.foreground
|
||||
font.pixelSize: 12
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import QtQuick
|
||||
import "../components"
|
||||
import "../theme"
|
||||
|
||||
Rectangle {
|
||||
id: tile
|
||||
|
|
@ -14,8 +15,8 @@ Rectangle {
|
|||
|
||||
height: 58
|
||||
radius: 4
|
||||
color: active ? "#cba6f7" : "#313244"
|
||||
readonly property color foreground: active ? "#1e1e2e" : "#cdd6f4"
|
||||
color: active ? Theme.accent : Theme.buttonBackground
|
||||
readonly property color foreground: active ? Theme.onAccent : Theme.foreground
|
||||
|
||||
NerdIcon {
|
||||
id: symbol
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import QtQuick
|
||||
import "../components"
|
||||
import "../theme"
|
||||
|
||||
Rectangle {
|
||||
id: actionButton
|
||||
|
|
@ -9,7 +10,7 @@ Rectangle {
|
|||
|
||||
height: 48
|
||||
radius: 4
|
||||
color: pointer.containsMouse ? "#45475a" : "#313244"
|
||||
color: pointer.containsMouse ? Theme.border : Theme.buttonBackground
|
||||
|
||||
NerdIcon {
|
||||
id: icon
|
||||
|
|
@ -27,7 +28,7 @@ Rectangle {
|
|||
anchors.leftMargin: 12
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: actionButton.actionInfo.label
|
||||
color: "#cdd6f4"
|
||||
color: Theme.foreground
|
||||
font.pixelSize: 14
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import QtQuick
|
||||
import Quickshell
|
||||
import "../components"
|
||||
import "../theme"
|
||||
|
||||
Item {
|
||||
id: page
|
||||
|
|
@ -34,7 +35,7 @@ Item {
|
|||
anchors.leftMargin: 8
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: "Session"
|
||||
color: "#cdd6f4"
|
||||
color: Theme.foreground
|
||||
font.pixelSize: 16
|
||||
font.bold: true
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import QtQuick
|
|||
import QtQuick.Controls
|
||||
import Quickshell.Services.Pipewire
|
||||
import "../components"
|
||||
import "../theme"
|
||||
|
||||
Item {
|
||||
id: volumeControl
|
||||
|
|
@ -33,7 +34,7 @@ Item {
|
|||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: volumeControl.sinkAudio ? (volumeControl.muted ? "" : "") : ""
|
||||
font.pixelSize: 18
|
||||
color: volumeControl.muted ? "#f38ba8" : "#cdd6f4"
|
||||
color: volumeControl.muted ? Theme.danger : Theme.foreground
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
|
|
@ -48,7 +49,7 @@ Item {
|
|||
anchors.leftMargin: 10
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: "Volume"
|
||||
color: "#cdd6f4"
|
||||
color: Theme.foreground
|
||||
font.pixelSize: 14
|
||||
}
|
||||
|
||||
|
|
@ -56,7 +57,7 @@ Item {
|
|||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: volumeControl.sinkAudio ? volumeControl.percent + "%" : "Indisponible"
|
||||
color: volumeControl.muted ? "#f38ba8" : "#a6adc8"
|
||||
color: volumeControl.muted ? Theme.danger : Theme.secondaryForeground
|
||||
font.pixelSize: 14
|
||||
}
|
||||
}
|
||||
|
|
@ -82,13 +83,13 @@ Item {
|
|||
width: slider.availableWidth
|
||||
height: 8
|
||||
radius: 4
|
||||
color: "#313244"
|
||||
color: Theme.buttonBackground
|
||||
|
||||
Rectangle {
|
||||
width: slider.visualPosition * parent.width
|
||||
height: parent.height
|
||||
radius: parent.radius
|
||||
color: "#cba6f7"
|
||||
color: Theme.accent
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -98,7 +99,7 @@ Item {
|
|||
width: 16
|
||||
height: 16
|
||||
radius: 8
|
||||
color: "#cdd6f4"
|
||||
color: Theme.foreground
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import QtQuick.Controls
|
|||
import Quickshell
|
||||
import Quickshell.Networking
|
||||
import "../components"
|
||||
import "../theme"
|
||||
|
||||
Item {
|
||||
id: page
|
||||
|
|
@ -122,12 +123,12 @@ Item {
|
|||
text: page.stage === "main" ? "Wi-Fi" : page.stage === "password"
|
||||
? "Se connecter à " + page.selectedName : page.stage === "forget"
|
||||
? "Oublier un réseau" : page.selectedName
|
||||
color: "#cdd6f4"; font.pixelSize: 16; font.bold: true; elide: Text.ElideRight
|
||||
color: Theme.foreground; font.pixelSize: 16; font.bold: true; elide: Text.ElideRight
|
||||
}
|
||||
}
|
||||
Text {
|
||||
width: parent.width; visible: page.message.length > 0
|
||||
text: page.message; wrapMode: Text.Wrap; color: "#f9e2af"; font.pixelSize: 12
|
||||
text: page.message; wrapMode: Text.Wrap; color: Theme.warningForeground; font.pixelSize: 12
|
||||
}
|
||||
Loader {
|
||||
id: body
|
||||
|
|
@ -147,27 +148,27 @@ Item {
|
|||
width: page.width; spacing: 9
|
||||
Row {
|
||||
width: parent.width; spacing: 8
|
||||
Text { width: parent.width - 100; text: "Wi-Fi " + (Networking.wifiEnabled ? "activé" : "désactivé"); color: "#cdd6f4"; font.pixelSize: 13 }
|
||||
Text { width: parent.width - 100; text: "Wi-Fi " + (Networking.wifiEnabled ? "activé" : "désactivé"); color: Theme.foreground; font.pixelSize: 13 }
|
||||
ActionButton {
|
||||
label: Networking.wifiEnabled ? "Désactiver" : "Activer"
|
||||
enabled: Networking.wifiHardwareEnabled
|
||||
onClicked: Networking.wifiEnabled = !Networking.wifiEnabled
|
||||
}
|
||||
}
|
||||
Text { text: page.scanWanted ? "Recherche de réseaux…" : "Scan arrêté"; color: "#a6adc8"; font.pixelSize: 11 }
|
||||
Text { text: "Réseau actuel"; color: "#cba6f7"; font.bold: true; font.pixelSize: 13 }
|
||||
Text { text: page.scanWanted ? "Recherche de réseaux…" : "Scan arrêté"; color: Theme.secondaryForeground; font.pixelSize: 11 }
|
||||
Text { text: "Réseau actuel"; color: Theme.accentForeground; font.bold: true; font.pixelSize: 13 }
|
||||
Rectangle {
|
||||
width: parent.width; height: 66; radius: 4; color: "#313244"
|
||||
width: parent.width; height: 66; radius: 4; color: Theme.buttonBackground
|
||||
Text {
|
||||
anchors.left: parent.left; anchors.leftMargin: 10; anchors.top: parent.top; anchors.topMargin: 8
|
||||
text: page.currentNetwork ? page.currentNetwork.name : "Aucun réseau connecté"
|
||||
color: "#cdd6f4"; font.pixelSize: 13; font.bold: true
|
||||
color: Theme.foreground; font.pixelSize: 13; font.bold: true
|
||||
}
|
||||
Text {
|
||||
anchors.left: parent.left; anchors.leftMargin: 10; anchors.bottom: parent.bottom; anchors.bottomMargin: 8
|
||||
text: page.currentNetwork ? page.securityLabel(page.currentNetwork) + " • "
|
||||
+ Math.round(page.currentNetwork.signalStrength * 100) + " %" : ""
|
||||
color: "#a6adc8"; font.pixelSize: 11
|
||||
color: Theme.secondaryForeground; font.pixelSize: 11
|
||||
}
|
||||
ActionButton {
|
||||
anchors.right: parent.right; anchors.rightMargin: 8; anchors.verticalCenter: parent.verticalCenter
|
||||
|
|
@ -175,11 +176,11 @@ Item {
|
|||
onClicked: page.choose(page.currentNetwork)
|
||||
}
|
||||
}
|
||||
Text { text: "Réseaux disponibles"; color: "#cba6f7"; font.bold: true; font.pixelSize: 13 }
|
||||
Text { text: "Réseaux disponibles"; color: Theme.accentForeground; font.bold: true; font.pixelSize: 13 }
|
||||
Text {
|
||||
visible: page.sortedNetworks.length === 0
|
||||
text: !Networking.wifiHardwareEnabled ? "Wi-Fi bloqué matériellement" : "Aucun réseau visible"
|
||||
color: "#a6adc8"; font.pixelSize: 12
|
||||
color: Theme.secondaryForeground; font.pixelSize: 12
|
||||
}
|
||||
Repeater {
|
||||
model: ScriptModel { values: page.sortedNetworks; objectProp: "name" }
|
||||
|
|
@ -187,23 +188,23 @@ Item {
|
|||
required property var modelData
|
||||
readonly property var network: modelData
|
||||
width: page.width; height: 44; radius: 4
|
||||
color: pointer.containsMouse ? "#45475a" : "#313244"
|
||||
color: pointer.containsMouse ? Theme.border : Theme.buttonBackground
|
||||
NerdIcon {
|
||||
anchors.left: parent.left; anchors.leftMargin: 9; anchors.verticalCenter: parent.verticalCenter
|
||||
text: network.connected ? "" : network.security === WifiSecurityType.Open ? "" : ""
|
||||
color: network.connected ? "#a6e3a1" : "#cdd6f4"; font.pixelSize: 16
|
||||
color: network.connected ? Theme.success : Theme.foreground; font.pixelSize: 16
|
||||
}
|
||||
Text {
|
||||
anchors.left: parent.left; anchors.leftMargin: 37; anchors.right: strength.left
|
||||
anchors.rightMargin: 8; anchors.verticalCenter: parent.verticalCenter
|
||||
text: network.name + " • " + page.stateLabel(network)
|
||||
color: "#cdd6f4"; font.pixelSize: 12; elide: Text.ElideRight
|
||||
color: Theme.foreground; font.pixelSize: 12; elide: Text.ElideRight
|
||||
}
|
||||
Text {
|
||||
id: strength; anchors.right: parent.right; anchors.rightMargin: 9
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: Math.round(network.signalStrength * 100) + "%"
|
||||
color: "#a6adc8"; font.pixelSize: 11
|
||||
color: Theme.secondaryForeground; font.pixelSize: 11
|
||||
}
|
||||
MouseArea {
|
||||
id: pointer; anchors.fill: parent; hoverEnabled: true
|
||||
|
|
@ -215,7 +216,7 @@ Item {
|
|||
}
|
||||
}
|
||||
}
|
||||
Text { text: "Réseaux enregistrés visibles"; color: "#cba6f7"; font.bold: true; font.pixelSize: 13 }
|
||||
Text { text: "Réseaux enregistrés visibles"; color: Theme.accentForeground; font.bold: true; font.pixelSize: 13 }
|
||||
Repeater {
|
||||
model: ScriptModel { values: page.sortedNetworks.filter(n => n.known); objectProp: "name" }
|
||||
delegate: ActionButton {
|
||||
|
|
@ -233,7 +234,7 @@ Item {
|
|||
clip: true; ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
|
||||
Column {
|
||||
width: page.width; spacing: 10
|
||||
Text { text: page.selectedNetwork ? page.stateLabel(page.selectedNetwork) + " • " + page.securityLabel(page.selectedNetwork) + " • " + Math.round(page.selectedNetwork.signalStrength * 100) + " %" : "Réseau indisponible"; color: "#cdd6f4"; font.pixelSize: 13 }
|
||||
Text { text: page.selectedNetwork ? page.stateLabel(page.selectedNetwork) + " • " + page.securityLabel(page.selectedNetwork) + " • " + Math.round(page.selectedNetwork.signalStrength * 100) + " %" : "Réseau indisponible"; color: Theme.foreground; font.pixelSize: 13 }
|
||||
Row {
|
||||
spacing: 8
|
||||
ActionButton {
|
||||
|
|
@ -250,15 +251,15 @@ Item {
|
|||
onClicked: { page.selectedProfile = -1; page.stage = "forget"; }
|
||||
}
|
||||
}
|
||||
Text { text: "Profils NetworkManager"; color: "#cba6f7"; font.pixelSize: 13; font.bold: true }
|
||||
Text { visible: page.profiles.length === 0; text: "Aucun profil enregistré"; color: "#a6adc8"; font.pixelSize: 12 }
|
||||
Text { text: "Profils NetworkManager"; color: Theme.accentForeground; font.pixelSize: 13; font.bold: true }
|
||||
Text { visible: page.profiles.length === 0; text: "Aucun profil enregistré"; color: Theme.secondaryForeground; font.pixelSize: 12 }
|
||||
Repeater {
|
||||
model: page.profiles
|
||||
delegate: Row {
|
||||
required property var modelData
|
||||
required property int index
|
||||
spacing: 6
|
||||
Text { width: 205; text: modelData.id + "\n" + modelData.uuid; color: "#cdd6f4"; font.pixelSize: 11; wrapMode: Text.WrapAnywhere }
|
||||
Text { width: 205; text: modelData.id + "\n" + modelData.uuid; color: Theme.foreground; font.pixelSize: 11; wrapMode: Text.WrapAnywhere }
|
||||
ActionButton { label: "Utiliser"; onClicked: { page.message = ""; page.selectedNetwork.connectWithSettings(modelData); } }
|
||||
ActionButton { label: "Oublier"; danger: true; onClicked: { page.selectedProfile = index; page.stage = "forget"; } }
|
||||
}
|
||||
|
|
@ -271,12 +272,12 @@ Item {
|
|||
Column {
|
||||
property alias passwordInput: input
|
||||
spacing: 10
|
||||
Text { text: "Mot de passe"; color: "#cdd6f4"; font.pixelSize: 13 }
|
||||
Text { text: "Mot de passe"; color: Theme.foreground; font.pixelSize: 13 }
|
||||
TextField {
|
||||
id: input; width: page.width; echoMode: TextInput.Password
|
||||
placeholderText: "Mot de passe Wi-Fi"; selectByMouse: true
|
||||
color: "#cdd6f4"; placeholderTextColor: "#6c7086"
|
||||
background: Rectangle { color: "#313244"; border.color: input.activeFocus ? "#cba6f7" : "#585b70"; radius: 4 }
|
||||
color: Theme.foreground; placeholderTextColor: Theme.disabledForeground
|
||||
background: Rectangle { color: Theme.buttonBackground; border.color: input.activeFocus ? Theme.accent : Theme.strongBorder; radius: 4 }
|
||||
onAccepted: submit.clicked()
|
||||
}
|
||||
Row {
|
||||
|
|
@ -303,8 +304,8 @@ Item {
|
|||
id: forgetComponent
|
||||
Column {
|
||||
spacing: 10
|
||||
Text { width: page.width; wrapMode: Text.Wrap; text: "Oublier " + page.selectedName + " ?"; color: "#f38ba8"; font.pixelSize: 13 }
|
||||
Text { width: page.width; wrapMode: Text.Wrap; text: page.selectedProfile >= 0 ? "Seul le profil sélectionné sera supprimé." : "Tous les profils de ce réseau seront supprimés."; color: "#a6adc8"; font.pixelSize: 12 }
|
||||
Text { width: page.width; wrapMode: Text.Wrap; text: "Oublier " + page.selectedName + " ?"; color: Theme.danger; font.pixelSize: 13 }
|
||||
Text { width: page.width; wrapMode: Text.Wrap; text: page.selectedProfile >= 0 ? "Seul le profil sélectionné sera supprimé." : "Tous les profils de ce réseau seront supprimés."; color: Theme.secondaryForeground; font.pixelSize: 12 }
|
||||
Row {
|
||||
spacing: 8
|
||||
ActionButton { label: "Annuler"; onClicked: page.stage = "details" }
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import QtQuick
|
||||
import Quickshell
|
||||
import "../theme"
|
||||
|
||||
PopupWindow {
|
||||
id: popup
|
||||
|
|
@ -32,8 +33,8 @@ PopupWindow {
|
|||
Rectangle {
|
||||
anchors.fill: parent
|
||||
radius: 4
|
||||
color: "#1e1e2e"
|
||||
border.color: "#45475a"
|
||||
color: Theme.popupBackground
|
||||
border.color: Theme.border
|
||||
Column {
|
||||
id: content
|
||||
anchors.left: parent.left
|
||||
|
|
@ -43,7 +44,7 @@ PopupWindow {
|
|||
spacing: 14
|
||||
Text {
|
||||
text: clock.date.toLocaleDateString(Qt.locale("fr_FR"), "dddd d MMMM")
|
||||
color: "#cba6f7"; font.pixelSize: 17; font.bold: true
|
||||
color: Theme.accentForeground; font.pixelSize: 17; font.bold: true
|
||||
}
|
||||
MediaPlayer { width: parent.width }
|
||||
NotificationList { width: parent.width; service: popup.notificationService }
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import QtQuick
|
|||
import QtQuick.Layouts
|
||||
import Quickshell.Services.Mpris
|
||||
import "../components"
|
||||
import "../theme"
|
||||
|
||||
Rectangle {
|
||||
id: card
|
||||
|
|
@ -22,7 +23,7 @@ Rectangle {
|
|||
implicitHeight: visible ? 116 : 0
|
||||
height: implicitHeight
|
||||
radius: 4
|
||||
color: "#313244"
|
||||
color: Theme.buttonBackground
|
||||
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
|
|
@ -33,7 +34,7 @@ Rectangle {
|
|||
Layout.preferredWidth: 72
|
||||
Layout.preferredHeight: 72
|
||||
radius: 4
|
||||
color: "#45475a"
|
||||
color: Theme.border
|
||||
clip: true
|
||||
|
||||
Image {
|
||||
|
|
@ -48,7 +49,7 @@ Rectangle {
|
|||
anchors.centerIn: parent
|
||||
visible: !artwork.visible
|
||||
text: ""
|
||||
color: "#cba6f7"
|
||||
color: Theme.accent
|
||||
font.pixelSize: 27
|
||||
}
|
||||
}
|
||||
|
|
@ -60,7 +61,7 @@ Rectangle {
|
|||
Text {
|
||||
Layout.fillWidth: true
|
||||
text: card.player ? card.player.trackTitle : ""
|
||||
color: "#cdd6f4"
|
||||
color: Theme.foreground
|
||||
font.pixelSize: 13
|
||||
font.bold: true
|
||||
elide: Text.ElideRight
|
||||
|
|
@ -69,7 +70,7 @@ Rectangle {
|
|||
Layout.fillWidth: true
|
||||
text: card.player ? card.player.trackArtist : ""
|
||||
visible: text.length > 0
|
||||
color: "#a6adc8"
|
||||
color: Theme.secondaryForeground
|
||||
font.pixelSize: 11
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
|
@ -92,12 +93,12 @@ Rectangle {
|
|||
width: 32
|
||||
height: 27
|
||||
radius: 4
|
||||
color: controlPointer.containsMouse && available ? "#585b70" : "#45475a"
|
||||
color: controlPointer.containsMouse && available ? Theme.strongBorder : Theme.border
|
||||
|
||||
NerdIcon {
|
||||
anchors.centerIn: parent
|
||||
text: modelData.icon
|
||||
color: parent.available ? "#cdd6f4" : "#6c7086"
|
||||
color: parent.available ? Theme.foreground : Theme.disabledForeground
|
||||
font.pixelSize: 14
|
||||
}
|
||||
MouseArea {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import QtQuick
|
||||
import Quickshell
|
||||
import "../components"
|
||||
import "../theme"
|
||||
|
||||
Rectangle {
|
||||
id: card
|
||||
|
|
@ -19,7 +20,7 @@ Rectangle {
|
|||
implicitHeight: bodyText.visible || image.visible || actions.visible ? 95 + image.height + actions.height : 64
|
||||
height: implicitHeight
|
||||
radius: 4
|
||||
color: "#313244"
|
||||
color: Theme.buttonBackground
|
||||
Column {
|
||||
anchors.fill: parent; anchors.margins: 10; spacing: 4
|
||||
Row {
|
||||
|
|
@ -27,21 +28,21 @@ Rectangle {
|
|||
Item {
|
||||
width: 14; height: 14
|
||||
Image { id: appIcon; anchors.fill: parent; source: card.iconSource; fillMode: Image.PreserveAspectFit; visible: status === Image.Ready }
|
||||
NerdIcon { anchors.centerIn: parent; visible: !appIcon.visible; text: ""; color: "#cba6f7"; font.pixelSize: 13 }
|
||||
NerdIcon { anchors.centerIn: parent; visible: !appIcon.visible; text: ""; color: Theme.accent; font.pixelSize: 13 }
|
||||
}
|
||||
Text {
|
||||
width: parent.width - 48
|
||||
text: card.notification.appName || card.notification.desktopEntry || "Notification"
|
||||
textFormat: Text.PlainText; color: "#a6adc8"; font.pixelSize: 11; elide: Text.ElideRight
|
||||
textFormat: Text.PlainText; color: Theme.secondaryForeground; font.pixelSize: 11; elide: Text.ElideRight
|
||||
}
|
||||
Text { visible: !!card.live; text: "●"; color: "#a6e3a1"; font.pixelSize: 10 }
|
||||
Text { visible: !!card.live; text: "●"; color: Theme.successForeground; font.pixelSize: 10 }
|
||||
NerdIcon {
|
||||
text: ""; color: closePointer.containsMouse ? "#f38ba8" : "#a6adc8"; font.pixelSize: 12
|
||||
text: ""; color: closePointer.containsMouse ? Theme.danger : Theme.secondaryForeground; font.pixelSize: 12
|
||||
MouseArea { id: closePointer; anchors.fill: parent; anchors.margins: -5; hoverEnabled: true; onClicked: card.service.dismiss(card.notification.internalId) }
|
||||
}
|
||||
}
|
||||
Text { width: parent.width; text: card.notification.summary; textFormat: Text.PlainText; color: "#cdd6f4"; font.pixelSize: 12; font.bold: true; elide: Text.ElideRight }
|
||||
Text { id: bodyText; width: parent.width; visible: text.length > 0; text: card.notification.body || ""; textFormat: Text.PlainText; color: "#bac2de"; font.pixelSize: 11; wrapMode: Text.Wrap; maximumLineCount: 2; elide: Text.ElideRight }
|
||||
Text { width: parent.width; text: card.notification.summary; textFormat: Text.PlainText; color: Theme.foreground; font.pixelSize: 12; font.bold: true; elide: Text.ElideRight }
|
||||
Text { id: bodyText; width: parent.width; visible: text.length > 0; text: card.notification.body || ""; textFormat: Text.PlainText; color: Theme.subtext1; font.pixelSize: 11; wrapMode: Text.Wrap; maximumLineCount: 2; elide: Text.ElideRight }
|
||||
Image { id: image; width: parent.width; height: status === Image.Ready ? Math.min(110, implicitHeight) : 0; visible: status === Image.Ready; source: card.notification.image || ""; fillMode: Image.PreserveAspectFit }
|
||||
Flow {
|
||||
id: actions
|
||||
|
|
@ -53,8 +54,8 @@ Rectangle {
|
|||
model: card.live ? card.live.actions : []
|
||||
delegate: Rectangle {
|
||||
required property var modelData
|
||||
width: label.implicitWidth + 16; height: 24; radius: 4; color: "#45475a"
|
||||
Text { id: label; anchors.centerIn: parent; text: modelData.text; textFormat: Text.PlainText; color: "#cdd6f4"; font.pixelSize: 11 }
|
||||
width: label.implicitWidth + 16; height: 24; radius: 4; color: Theme.border
|
||||
Text { id: label; anchors.centerIn: parent; text: modelData.text; textFormat: Text.PlainText; color: Theme.foreground; font.pixelSize: 11 }
|
||||
MouseArea { anchors.fill: parent; onClicked: modelData.invoke() }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import QtQuick
|
||||
import "../theme"
|
||||
|
||||
Item {
|
||||
id: list
|
||||
|
|
@ -10,12 +11,12 @@ Item {
|
|||
implicitHeight: rows.length ? 28 + Math.min(307, rows.length * 94) : 80
|
||||
height: implicitHeight
|
||||
Timer { interval: 5000; running: list.confirmClear; onTriggered: list.confirmClear = false }
|
||||
Text { id: title; text: "Notifications"; color: "#cdd6f4"; font.pixelSize: 14; font.bold: true }
|
||||
Text { id: title; text: "Notifications"; color: Theme.foreground; font.pixelSize: 14; font.bold: true }
|
||||
Text {
|
||||
anchors.right: parent.right
|
||||
text: list.confirmClear ? "Confirmer : tout effacer" : "Tout effacer"
|
||||
visible: list.rows.length > 0
|
||||
color: confirm.containsMouse ? "#f38ba8" : "#a6adc8"
|
||||
color: confirm.containsMouse ? Theme.danger : Theme.secondaryForeground
|
||||
font.pixelSize: 11
|
||||
MouseArea {
|
||||
id: confirm
|
||||
|
|
@ -31,7 +32,7 @@ Item {
|
|||
visible: list.rows.length === 0
|
||||
anchors.top: title.bottom; anchors.topMargin: 8
|
||||
text: "Aucune notification récente"
|
||||
color: "#a6adc8"; font.pixelSize: 12
|
||||
color: Theme.secondaryForeground; font.pixelSize: 12
|
||||
}
|
||||
ListView {
|
||||
anchors.top: title.bottom; anchors.topMargin: 8
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import QtQuick
|
||||
import Quickshell
|
||||
import "../notifications"
|
||||
import "../theme"
|
||||
|
||||
Rectangle {
|
||||
id: clockCapsule
|
||||
|
|
@ -12,7 +13,7 @@ Rectangle {
|
|||
width: 184
|
||||
height: 26
|
||||
radius: 4
|
||||
color: open ? "#cba6f7" : pointer.containsMouse ? "#45475a" : "#313244"
|
||||
color: open ? Theme.accent : pointer.containsMouse ? Theme.border : Theme.buttonBackground
|
||||
Accessible.role: Accessible.Button
|
||||
Accessible.name: unreadCount === 0 ? "Date et heure"
|
||||
: "Date et heure, " + unreadCount + (unreadCount === 1
|
||||
|
|
@ -28,7 +29,7 @@ Rectangle {
|
|||
// CONTRACT: le centre du texte coïncide avec celui de la barre, sans dépendre du badge.
|
||||
anchors.centerIn: parent
|
||||
text: Qt.formatDateTime(clock.date, "dd/MM HH:mm")
|
||||
color: clockCapsule.open ? "#1e1e2e" : "#cdd6f4"
|
||||
color: clockCapsule.open ? Theme.onAccent : Theme.foreground
|
||||
font.pixelSize: 13
|
||||
font.weight: Font.Bold
|
||||
}
|
||||
|
|
@ -63,15 +64,15 @@ Rectangle {
|
|||
Rectangle {
|
||||
anchors.fill: parent
|
||||
radius: 4
|
||||
color: "#313244"
|
||||
border.color: "#45475a"
|
||||
color: Theme.buttonBackground
|
||||
border.color: Theme.border
|
||||
Text {
|
||||
id: tooltipText
|
||||
anchors.centerIn: parent
|
||||
text: clockCapsule.unreadCount === 0 ? "Date et notifications"
|
||||
: clockCapsule.unreadCount + (clockCapsule.unreadCount === 1
|
||||
? " notification non lue" : " notifications non lues")
|
||||
color: "#cdd6f4"
|
||||
color: Theme.foreground
|
||||
font.pixelSize: 11
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import QtQuick
|
|||
import Quickshell
|
||||
import Quickshell.I3
|
||||
import "../status"
|
||||
import "../theme"
|
||||
|
||||
Row {
|
||||
id: workspaces
|
||||
|
|
@ -58,9 +59,9 @@ Row {
|
|||
width: button.active ? 24 : 16
|
||||
height: button.active ? 16 : 11
|
||||
radius: 4
|
||||
color: button.urgent ? "#fab387"
|
||||
: button.active ? "#cba6f7"
|
||||
: pointer.containsMouse ? "#6c7086" : "#585b70"
|
||||
color: button.urgent ? Theme.urgent
|
||||
: button.active ? Theme.accent
|
||||
: pointer.containsMouse ? Theme.disabledForeground : Theme.strongBorder
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import QtQuick
|
|||
import Quickshell
|
||||
import Quickshell.Services.Notifications
|
||||
import "../components"
|
||||
import "../theme"
|
||||
|
||||
Rectangle {
|
||||
id: card
|
||||
|
|
@ -23,8 +24,8 @@ Rectangle {
|
|||
width: 370
|
||||
implicitHeight: content.implicitHeight + 20
|
||||
radius: 4
|
||||
color: "#1e1e2e"
|
||||
border.color: notification.urgency === NotificationUrgency.Critical ? "#f38ba8" : "#45475a"
|
||||
color: Theme.popupBackground
|
||||
border.color: notification.urgency === NotificationUrgency.Critical ? Theme.danger : Theme.border
|
||||
// CONTRACT: 0 interdit l'expiration automatique. Les durées sont en ms.
|
||||
Timer {
|
||||
interval: Math.max(1, card.timeout)
|
||||
|
|
@ -50,23 +51,23 @@ Rectangle {
|
|||
fillMode: Image.PreserveAspectFit
|
||||
visible: status === Image.Ready
|
||||
}
|
||||
NerdIcon { anchors.centerIn: parent; visible: !icon.visible; text: ""; color: "#cba6f7" }
|
||||
NerdIcon { anchors.centerIn: parent; visible: !icon.visible; text: ""; color: Theme.accent }
|
||||
}
|
||||
Text {
|
||||
width: parent.width - 48
|
||||
text: card.notification.appName || "Notification"
|
||||
textFormat: Text.PlainText
|
||||
color: "#a6adc8"
|
||||
color: Theme.secondaryForeground
|
||||
font.pixelSize: 11
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
NerdIcon {
|
||||
text: ""; color: "#a6adc8"
|
||||
text: ""; color: Theme.secondaryForeground
|
||||
MouseArea { anchors.fill: parent; anchors.margins: -4; onClicked: card.service.dismiss(card.entry.internalId) }
|
||||
}
|
||||
}
|
||||
Text { width: parent.width; text: card.notification.summary; textFormat: Text.PlainText; color: "#cdd6f4"; font.bold: true; wrapMode: Text.Wrap; maximumLineCount: 2 }
|
||||
Text { width: parent.width; visible: text.length > 0; text: card.notification.body; textFormat: Text.PlainText; color: "#bac2de"; font.pixelSize: 12; wrapMode: Text.Wrap; maximumLineCount: 3; elide: Text.ElideRight }
|
||||
Text { width: parent.width; text: card.notification.summary; textFormat: Text.PlainText; color: Theme.foreground; font.bold: true; wrapMode: Text.Wrap; maximumLineCount: 2 }
|
||||
Text { width: parent.width; visible: text.length > 0; text: card.notification.body; textFormat: Text.PlainText; color: Theme.subtext1; font.pixelSize: 12; wrapMode: Text.Wrap; maximumLineCount: 3; elide: Text.ElideRight }
|
||||
Image {
|
||||
width: parent.width; height: status === Image.Ready ? Math.min(120, implicitHeight) : 0
|
||||
visible: status === Image.Ready
|
||||
|
|
@ -80,8 +81,8 @@ Rectangle {
|
|||
model: card.notification.actions
|
||||
delegate: Rectangle {
|
||||
required property var modelData
|
||||
width: actionLabel.implicitWidth + 16; height: 25; radius: 4; color: "#45475a"
|
||||
Text { id: actionLabel; anchors.centerIn: parent; text: modelData.text; textFormat: Text.PlainText; color: "#cdd6f4"; font.pixelSize: 11 }
|
||||
width: actionLabel.implicitWidth + 16; height: 25; radius: 4; color: Theme.border
|
||||
Text { id: actionLabel; anchors.centerIn: parent; text: modelData.text; textFormat: Text.PlainText; color: Theme.foreground; font.pixelSize: 11 }
|
||||
MouseArea { anchors.fill: parent; onClicked: modelData.invoke() }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import QtQuick
|
||||
import "../components"
|
||||
import "../theme"
|
||||
|
||||
Item {
|
||||
id: indicator
|
||||
|
|
@ -19,16 +20,16 @@ Item {
|
|||
id: bell
|
||||
// Le glyphe fa-bell est présent dans JetBrainsMono Nerd Font Mono.
|
||||
text: ""
|
||||
color: indicator.inverted ? "#1e1e2e"
|
||||
: indicator.criticalUnreadCount > 0 ? "#f38ba8" : "#cba6f7"
|
||||
color: indicator.inverted ? Theme.onAccent
|
||||
: indicator.criticalUnreadCount > 0 ? Theme.danger : Theme.accent
|
||||
// CONTRACT: la cloche gagne en lisibilité sans agrandir le badge ni déplacer la date.
|
||||
font.pixelSize: 16
|
||||
}
|
||||
Text {
|
||||
id: countLabel
|
||||
text: indicator.unreadCount > 9 ? "9+" : String(indicator.unreadCount)
|
||||
color: indicator.inverted ? "#1e1e2e"
|
||||
: indicator.criticalUnreadCount > 0 ? "#f38ba8" : "#cdd6f4"
|
||||
color: indicator.inverted ? Theme.onAccent
|
||||
: indicator.criticalUnreadCount > 0 ? Theme.danger : Theme.foreground
|
||||
font.pixelSize: 10
|
||||
font.bold: true
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,24 @@
|
|||
//@ pragma UseQApplication
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import "notifications"
|
||||
import "theme"
|
||||
|
||||
ShellRoot {
|
||||
// CONTRACT: seul ce point d'entrée reçoit les changements live du backend.
|
||||
// Aucun état de thème n'est écrit par Theme.qml.
|
||||
IpcHandler {
|
||||
target: "appearance"
|
||||
function setTheme(flavor: string, accent: string): bool {
|
||||
return AppearanceController.applyTheme(flavor, accent);
|
||||
}
|
||||
function currentTheme(): string {
|
||||
return AppearanceController.effectiveFlavor + "/" + AppearanceController.effectiveAccent;
|
||||
}
|
||||
function effectiveState(): string {
|
||||
return AppearanceController.effectiveState();
|
||||
}
|
||||
}
|
||||
// Une barre par écran, y compris si les sorties changent pendant la session.
|
||||
// CONTRACT: un seul serveur D-Bus pour toutes les sorties.
|
||||
NotificationService { id: rootNotificationService }
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import QtQuick
|
|||
import Quickshell.Io
|
||||
import Quickshell.Services.UPower
|
||||
import "../components"
|
||||
import "../theme"
|
||||
|
||||
Item {
|
||||
id: indicator
|
||||
|
|
@ -42,7 +43,7 @@ Item {
|
|||
onFileChanged: reload()
|
||||
}
|
||||
|
||||
Rectangle { anchors.fill: parent; radius: 4; color: pointer.containsMouse ? "#45475a" : "transparent" }
|
||||
Rectangle { anchors.fill: parent; radius: 4; color: pointer.containsMouse ? Theme.border : "transparent" }
|
||||
NerdIcon {
|
||||
id: icon
|
||||
width: 18
|
||||
|
|
@ -57,8 +58,8 @@ Item {
|
|||
: indicator.percent <= 30 ? ""
|
||||
: indicator.percent <= 50 ? ""
|
||||
: indicator.percent <= 75 ? "" : ""
|
||||
color: indicator.percent <= 15 ? "#f38ba8"
|
||||
: indicator.percent <= 30 ? "#fab387" : "#cdd6f4"
|
||||
color: indicator.percent <= 15 ? Theme.danger
|
||||
: indicator.percent <= 30 ? Theme.urgentForeground : Theme.foreground
|
||||
}
|
||||
Text {
|
||||
id: value
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import QtQuick
|
||||
import "../components"
|
||||
import "../theme"
|
||||
|
||||
Item {
|
||||
id: indicator
|
||||
|
|
@ -16,14 +17,14 @@ Item {
|
|||
: "Bluetooth activé\n" + connectedDevices.length + " appareils connectés\n"
|
||||
+ connectedDevices.map(device => device.name).join("\n")
|
||||
|
||||
Rectangle { anchors.fill: parent; radius: 4; color: pointer.containsMouse ? "#45475a" : "transparent" }
|
||||
Rectangle { anchors.fill: parent; radius: 4; color: pointer.containsMouse ? Theme.border : "transparent" }
|
||||
NerdIcon {
|
||||
anchors.centerIn: parent
|
||||
// WHY: la silhouette Bluetooth est étroite ; seul le dessin grandit, pas la hitbox.
|
||||
font.pixelSize: 22
|
||||
text: indicator.adapter && indicator.adapter.enabled ? "" : ""
|
||||
color: !indicator.adapter || !indicator.adapter.enabled ? "#a6adc8"
|
||||
: indicator.connectedDevices.length > 0 ? "#cba6f7" : "#cdd6f4"
|
||||
color: !indicator.adapter || !indicator.adapter.enabled ? Theme.secondaryForeground
|
||||
: indicator.connectedDevices.length > 0 ? Theme.accent : Theme.foreground
|
||||
}
|
||||
MouseArea {
|
||||
id: pointer
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import QtQuick
|
||||
import Quickshell.Networking
|
||||
import "../components"
|
||||
import "../theme"
|
||||
|
||||
Item {
|
||||
id: indicator
|
||||
|
|
@ -19,7 +20,7 @@ Item {
|
|||
: !connectedNetwork ? "Wi-Fi\nNon connecté"
|
||||
: "Wi-Fi\n" + connectedNetwork.name + "\nSignal : " + signalPercent + " %"
|
||||
|
||||
Rectangle { anchors.fill: parent; radius: 4; color: pointer.containsMouse ? "#45475a" : "transparent" }
|
||||
Rectangle { anchors.fill: parent; radius: 4; color: pointer.containsMouse ? Theme.border : "transparent" }
|
||||
NerdIcon {
|
||||
anchors.centerIn: parent
|
||||
// WHY: md-wifi remplit mieux la hauteur visuelle que les quatre glyphes
|
||||
|
|
@ -30,8 +31,8 @@ Item {
|
|||
text: !Networking.wifiHardwareEnabled ? ""
|
||||
: !Networking.wifiEnabled ? ""
|
||||
: !indicator.connectedNetwork ? "" : ""
|
||||
color: !Networking.wifiHardwareEnabled ? "#f38ba8"
|
||||
: !Networking.wifiEnabled ? "#a6adc8" : "#cdd6f4"
|
||||
color: !Networking.wifiHardwareEnabled ? Theme.danger
|
||||
: !Networking.wifiEnabled ? Theme.secondaryForeground : Theme.foreground
|
||||
}
|
||||
MouseArea {
|
||||
id: pointer
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import QtQuick
|
||||
import Quickshell
|
||||
import "../theme"
|
||||
|
||||
PopupWindow {
|
||||
id: tip
|
||||
|
|
@ -25,15 +26,15 @@ PopupWindow {
|
|||
Rectangle {
|
||||
anchors.fill: parent
|
||||
radius: 4
|
||||
color: "#313244"
|
||||
border.color: "#45475a"
|
||||
color: Theme.buttonBackground
|
||||
border.color: Theme.border
|
||||
Text {
|
||||
id: content
|
||||
anchors.centerIn: parent
|
||||
width: Math.min(304, implicitWidth)
|
||||
text: tip.message
|
||||
wrapMode: Text.Wrap
|
||||
color: "#cdd6f4"
|
||||
color: Theme.foreground
|
||||
font.pixelSize: 11
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import QtQuick
|
|||
import Quickshell
|
||||
import Quickshell.Services.SystemTray
|
||||
import Quickshell.Widgets
|
||||
import "../theme"
|
||||
|
||||
Item {
|
||||
id: entry
|
||||
|
|
@ -14,7 +15,7 @@ Item {
|
|||
return heading + (item.tooltipDescription ? "\n" + item.tooltipDescription : "");
|
||||
}
|
||||
|
||||
Rectangle { anchors.fill: parent; radius: 4; color: pointer.containsMouse ? "#45475a" : "transparent" }
|
||||
Rectangle { anchors.fill: parent; radius: 4; color: pointer.containsMouse ? Theme.border : "transparent" }
|
||||
IconImage {
|
||||
anchors.centerIn: parent
|
||||
width: 18
|
||||
|
|
@ -25,7 +26,7 @@ Item {
|
|||
Rectangle {
|
||||
visible: entry.item.status === Status.NeedsAttention
|
||||
width: 4; height: 4; radius: 2
|
||||
color: "#cba6f7"
|
||||
color: Theme.accent
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import QtQuick
|
|||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import "../components"
|
||||
import "../theme"
|
||||
|
||||
Item {
|
||||
id: indicator
|
||||
|
|
@ -46,7 +47,7 @@ Item {
|
|||
running: !snapshot.loaded
|
||||
onTriggered: snapshot.reload()
|
||||
}
|
||||
Rectangle { anchors.fill: parent; radius: 4; color: pointer.containsMouse ? "#45475a" : "transparent" }
|
||||
Rectangle { anchors.fill: parent; radius: 4; color: pointer.containsMouse ? Theme.border : "transparent" }
|
||||
NerdIcon {
|
||||
id: icon
|
||||
width: 18
|
||||
|
|
@ -56,7 +57,7 @@ Item {
|
|||
anchors.leftMargin: 3
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: indicator.hasError ? "" : ""
|
||||
color: indicator.hasError ? "#fab387" : "#cba6f7"
|
||||
color: indicator.hasError ? Theme.urgentForeground : Theme.accent
|
||||
}
|
||||
Text {
|
||||
id: count
|
||||
|
|
@ -65,7 +66,7 @@ Item {
|
|||
anchors.verticalCenter: parent.verticalCenter
|
||||
visible: !indicator.hasError
|
||||
text: indicator.state.totalUpdates
|
||||
color: "#cba6f7"
|
||||
color: Theme.accent
|
||||
font.family: "JetBrainsMono Nerd Font Mono"
|
||||
font.pixelSize: 12
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,25 +1,26 @@
|
|||
import QtQuick
|
||||
import "../theme"
|
||||
|
||||
Column {
|
||||
id: row
|
||||
required property string label
|
||||
required property string value
|
||||
required property real percentage
|
||||
property string accent: "#cba6f7"
|
||||
property color accent: Theme.mauve
|
||||
width: parent.width
|
||||
spacing: 6
|
||||
|
||||
Row {
|
||||
width: parent.width
|
||||
Text { id: labelText; text: row.label; color: "#cdd6f4"; font.pixelSize: 12 }
|
||||
Text { id: labelText; text: row.label; color: Theme.foreground; font.pixelSize: 12 }
|
||||
Item { width: Math.max(0, parent.width - valueText.width - labelText.width); height: 1 }
|
||||
Text { id: valueText; text: row.value; color: "#bac2de"; font.pixelSize: 12 }
|
||||
Text { id: valueText; text: row.value; color: Theme.subtext1; font.pixelSize: 12 }
|
||||
}
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: 5
|
||||
radius: 3
|
||||
color: "#313244"
|
||||
color: Theme.buttonBackground
|
||||
Rectangle {
|
||||
width: parent.width * Math.max(0, Math.min(100, row.percentage)) / 100
|
||||
height: parent.height
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import QtQuick
|
||||
import Quickshell
|
||||
import "../components"
|
||||
import "../theme"
|
||||
|
||||
PopupWindow {
|
||||
id: popup
|
||||
|
|
@ -24,8 +25,8 @@ PopupWindow {
|
|||
Rectangle {
|
||||
anchors.fill: parent
|
||||
radius: 4
|
||||
color: "#1e1e2e"
|
||||
border.color: "#45475a"
|
||||
color: Theme.popupBackground
|
||||
border.color: Theme.border
|
||||
|
||||
Column {
|
||||
id: content
|
||||
|
|
@ -38,7 +39,7 @@ PopupWindow {
|
|||
Row {
|
||||
spacing: 8
|
||||
NerdIcon { text: ""; font.pixelSize: 18 }
|
||||
Text { text: "Moniteur système"; color: "#cdd6f4"; font.pixelSize: 16; font.bold: true }
|
||||
Text { text: "Moniteur système"; color: Theme.foreground; font.pixelSize: 16; font.bold: true }
|
||||
}
|
||||
|
||||
MetricRow {
|
||||
|
|
@ -46,7 +47,7 @@ PopupWindow {
|
|||
percentage: metrics.cpuPercent
|
||||
value: (metrics.cpuPercent >= 0 ? Math.round(metrics.cpuPercent) + " %" : "—")
|
||||
+ (metrics.cpuTemp >= 0 ? " " + Math.round(metrics.cpuTemp) + " °C" : "")
|
||||
accent: metrics.cpuPercent >= 90 ? "#f38ba8" : metrics.cpuPercent >= 70 ? "#fab387" : "#cba6f7"
|
||||
accent: metrics.cpuPercent >= 90 ? Theme.danger : metrics.cpuPercent >= 70 ? Theme.urgent : Theme.mauve
|
||||
}
|
||||
|
||||
MetricRow {
|
||||
|
|
@ -55,7 +56,7 @@ PopupWindow {
|
|||
value: metrics.ramPercent >= 0
|
||||
? metrics.ramUsedGiB.toFixed(1) + " / " + metrics.ramTotalGiB.toFixed(1)
|
||||
+ " GiB " + Math.round(metrics.ramPercent) + " %" : "—"
|
||||
accent: metrics.ramPercent >= 90 ? "#f38ba8" : metrics.ramPercent >= 75 ? "#fab387" : "#cba6f7"
|
||||
accent: metrics.ramPercent >= 90 ? Theme.danger : metrics.ramPercent >= 75 ? Theme.urgent : Theme.mauve
|
||||
}
|
||||
|
||||
MetricRow {
|
||||
|
|
@ -77,15 +78,15 @@ PopupWindow {
|
|||
|
||||
Row {
|
||||
width: parent.width
|
||||
Text { id: loadLabel; text: "Charge (1/5/15 min)"; color: "#cdd6f4"; font.pixelSize: 12 }
|
||||
Text { id: loadLabel; text: "Charge (1/5/15 min)"; color: Theme.foreground; font.pixelSize: 12 }
|
||||
Item { width: Math.max(0, parent.width - loadValue.width - loadLabel.width); height: 1 }
|
||||
Text { id: loadValue; text: metrics.loadAverage || "—"; color: "#bac2de"; font.pixelSize: 12 }
|
||||
Text { id: loadValue; text: metrics.loadAverage || "—"; color: Theme.subtext1; font.pixelSize: 12 }
|
||||
}
|
||||
Row {
|
||||
width: parent.width
|
||||
Text { id: uptimeLabel; text: "En marche"; color: "#cdd6f4"; font.pixelSize: 12 }
|
||||
Text { id: uptimeLabel; text: "En marche"; color: Theme.foreground; font.pixelSize: 12 }
|
||||
Item { width: Math.max(0, parent.width - uptimeValue.width - uptimeLabel.width); height: 1 }
|
||||
Text { id: uptimeValue; text: metrics.uptimeText || "—"; color: "#bac2de"; font.pixelSize: 12 }
|
||||
Text { id: uptimeValue; text: metrics.uptimeText || "—"; color: Theme.subtext1; font.pixelSize: 12 }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import QtQuick
|
||||
import "../components"
|
||||
import "../status"
|
||||
import "../theme"
|
||||
|
||||
Rectangle {
|
||||
id: button
|
||||
|
|
@ -10,14 +11,14 @@ Rectangle {
|
|||
width: 26
|
||||
height: 26
|
||||
radius: 4
|
||||
color: open ? "#cba6f7" : pointer.containsMouse ? "#45475a" : "#313244"
|
||||
color: open ? Theme.accent : pointer.containsMouse ? Theme.border : Theme.buttonBackground
|
||||
|
||||
NerdIcon {
|
||||
anchors.centerIn: parent
|
||||
text: ""
|
||||
// WHY: compenser la surface plus faible du glyphe sans agrandir le bouton.
|
||||
font.pixelSize: 20
|
||||
color: button.open ? "#1e1e2e" : "#cdd6f4"
|
||||
color: button.open ? Theme.onAccent : Theme.foreground
|
||||
}
|
||||
MouseArea {
|
||||
id: pointer
|
||||
|
|
|
|||
|
|
@ -0,0 +1,60 @@
|
|||
pragma Singleton
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
|
||||
Singleton {
|
||||
id: appearance
|
||||
// CONTRACT: état effectif unique du bureau. Les futurs adaptateurs ne
|
||||
// déduisent jamais Light/Dark du nom de flavor ; ils lisent effectiveDark.
|
||||
// 17B ne définit que le mode normal ; les décisions Sun arrivent après.
|
||||
readonly property var flavors: ["latte", "frappe", "macchiato", "mocha"]
|
||||
readonly property var accents: ["rosewater", "flamingo", "pink", "mauve", "red", "maroon", "peach", "yellow", "green", "teal", "sky", "sapphire", "blue", "lavender"]
|
||||
// CONTRACT: le backend écrit cet état atomiquement après validation Sway.
|
||||
// Le repli Mocha fonctionne avant la première génération ; aucune lecture
|
||||
// par frame ni polling. Les futurs adaptateurs lisent cet état via l'IPC.
|
||||
FileView {
|
||||
id: stateFile
|
||||
path: (Quickshell.env("XDG_STATE_HOME") || Quickshell.env("HOME") + "/.local/state")
|
||||
+ "/labfy-appearance/effective.json"
|
||||
blockLoading: true
|
||||
printErrors: false
|
||||
}
|
||||
function initialState() {
|
||||
try {
|
||||
const value = JSON.parse(stateFile.text());
|
||||
if (value.version === 1 && flavors.includes(value.effectiveFlavor)
|
||||
&& accents.includes(value.effectiveAccent)
|
||||
&& value.effectiveMode === "normal") return value;
|
||||
} catch (_) {}
|
||||
return { effectiveFlavor: "mocha", effectiveAccent: "lavender", revision: 0 };
|
||||
}
|
||||
readonly property var initial: initialState()
|
||||
property string effectiveFlavor: initial.effectiveFlavor
|
||||
property string effectiveMode: "normal"
|
||||
readonly property bool effectiveDark: effectiveFlavor !== "latte"
|
||||
property bool effectiveHighContrast: false
|
||||
property string effectiveAccent: initial.effectiveAccent
|
||||
property int revision: Number.isSafeInteger(initial.revision) && initial.revision >= 0
|
||||
? initial.revision : 0
|
||||
|
||||
// INVARIANT: une paire refusée ne change aucun champ ni la révision.
|
||||
// La révision avance une seule fois après tout changement effectif.
|
||||
function applyTheme(flavor, accent) {
|
||||
if (!flavors.includes(flavor) || !accents.includes(accent)) return false;
|
||||
if (effectiveFlavor === flavor && effectiveAccent === accent
|
||||
&& effectiveMode === "normal" && !effectiveHighContrast) return true;
|
||||
effectiveFlavor = flavor;
|
||||
effectiveAccent = accent;
|
||||
effectiveMode = "normal";
|
||||
effectiveHighContrast = false;
|
||||
revision++;
|
||||
return true;
|
||||
}
|
||||
|
||||
function effectiveState() {
|
||||
return JSON.stringify({ effectiveFlavor: effectiveFlavor,
|
||||
effectiveMode: effectiveMode, effectiveDark: effectiveDark,
|
||||
effectiveHighContrast: effectiveHighContrast,
|
||||
effectiveAccent: effectiveAccent, revision: revision });
|
||||
}
|
||||
}
|
||||
68
quickshell/.config/quickshell/labfy-sway/theme/README.md
Normal file
68
quickshell/.config/quickshell/labfy-sway/theme/README.md
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
# Moteur d'apparence 17B
|
||||
|
||||
`catppuccin.json` est la **seule source versionnée complète** des quatre
|
||||
palettes. Les 26 valeurs de chaque flavor ont été importées sans modification
|
||||
depuis [Catppuccin Palette v1.8.0](https://github.com/catppuccin/palette/blob/v1.8.0/palette.json),
|
||||
révision `07d02aa110ef9eb7e7427afca5c73ba9cf7f8ebd`, le 4 octobre 2026.
|
||||
Le JSON local conserve seulement les noms et valeurs hex, sans les métadonnées
|
||||
RGB/HSL/OKLCH de l'amont.
|
||||
|
||||
```text
|
||||
catppuccin.json (versionné)
|
||||
→ AppearanceController.qml (API live, sans persistance)
|
||||
→ Theme.qml (singleton effectif, rôles UI)
|
||||
→ composants QuickShell
|
||||
|
||||
catppuccin.json (versionné)
|
||||
→ generate-appearance.py (versionné)
|
||||
→ generated/theme.conf (runtime, ignoré par Git)
|
||||
→ Sway/SwayFX
|
||||
```
|
||||
|
||||
`Theme.qml` lit le JSON une fois au chargement par `FileView` avec
|
||||
`blockLoading: true`, sans processus externe, relecture par frame ou polling.
|
||||
`AppearanceController.qml` détient l'état effectif du bureau :
|
||||
`effectiveFlavor`, `effectiveMode`, `effectiveDark`,
|
||||
`effectiveHighContrast`, `effectiveAccent`, `revision`. Le backend persiste
|
||||
l'état effectif dans `$XDG_STATE_HOME/labfy-appearance/effective.json` avec
|
||||
écriture atomique, puis le contrôleur le relit au démarrage. En 17B,
|
||||
`effectiveMode` vaut `normal`, `effectiveHighContrast` vaut `false`, et la
|
||||
révision augmente une fois par changement accepté. `effectiveDark` est
|
||||
publié par le contrôleur pour les futurs consommateurs GTK, Qt, Kitty,
|
||||
Neovim, Firefox et autres ; ces applications n'auront pas à déduire le mode
|
||||
clair/sombre de la palette. Sun Light et Sun Dark viendront plus tard.
|
||||
`Theme.qml` consomme cet état et expose les couleurs et rôles QuickShell.
|
||||
Le flavor initial est Mocha et l'accent initial Lavender. `panelOpacity = 235/255`
|
||||
recrée le fond de barre historique ; un profil ultérieur peut le rendre opaque.
|
||||
`onAccent` choisit noir ou blanc technique par luminance WCAG. Lavender Latte
|
||||
n'atteint pas 4,5:1 avec une couleur Catppuccin ; le noir atteint environ
|
||||
6,6:1. Le texte nu accentué prend `text` en Latte pour rester lisible.
|
||||
|
||||
Les 34 usages historiques de Mauve ont été classés par rôle : sélection,
|
||||
focus, indicateur ou contrôle utilisent maintenant l'accent principal
|
||||
Lavender. Les séries CPU/RAM du System Monitor restent intentionnellement
|
||||
Mauve. Ce choix rend les contrôles historiques Mauve légèrement plus bleus
|
||||
qu'avant. Les trois anciens Lavender restent l'accent principal.
|
||||
|
||||
L'API pour 17C est `AppearanceController.applyTheme(flavor, accent)` ; l'IPC
|
||||
`qs -c labfy-sway ipc call appearance effectiveState` expose l'état complet
|
||||
et `qs -c labfy-sway ipc call appearance setTheme mocha lavender` change
|
||||
QuickShell en direct. La commande unifiée depuis le dépôt est :
|
||||
|
||||
```sh
|
||||
python bin/.local/bin/generate-appearance.py latte --accent lavender --apply
|
||||
```
|
||||
|
||||
Elle écrit atomiquement le fichier runtime Sway, valide la configuration,
|
||||
recharge Sway, persiste l'état effectif, puis met à jour QuickShell. En cas
|
||||
d'échec elle restaure la dernière génération valide, l'état persistant et
|
||||
l'état QuickShell précédent. Aucune décision automatique de profil ni gestion
|
||||
du wallpaper n'existe encore dans le QML.
|
||||
Le repli versionné `sway/theme-default.conf` fournit Mocha même avant toute
|
||||
génération. Ses quelques variables sont un artefact dérivé, pas une palette
|
||||
complète indépendante. `sway/style` inclut le repli, puis un glob de fichier
|
||||
runtime ; Sway accepte ce glob vide après un nouveau clone/Stow.
|
||||
|
||||
Les ombres `#00000055` et `#00000035` sont des valeurs de profil structurelles,
|
||||
pas des couleurs Catppuccin. La transparence inactive reste 0,85 ; le
|
||||
générateur fournit une variable Sway pour le futur profil Sun Mode.
|
||||
96
quickshell/.config/quickshell/labfy-sway/theme/Theme.qml
Normal file
96
quickshell/.config/quickshell/labfy-sway/theme/Theme.qml
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
pragma Singleton
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
|
||||
Singleton {
|
||||
id: theme
|
||||
|
||||
// CONTRACT: la palette officielle versionnée est l'unique source des quatre flavors.
|
||||
// Le chargement bloquant unique précède l'évaluation des couleurs des fenêtres.
|
||||
FileView {
|
||||
id: paletteFile
|
||||
path: Qt.resolvedUrl("catppuccin.json")
|
||||
blockLoading: true
|
||||
}
|
||||
readonly property var palettes: JSON.parse(paletteFile.text())
|
||||
readonly property string flavor: AppearanceController.effectiveFlavor
|
||||
readonly property string accentName: AppearanceController.effectiveAccent
|
||||
readonly property var palette: palettes[flavor]
|
||||
|
||||
function setFlavor(value) {
|
||||
return AppearanceController.applyTheme(value, accentName);
|
||||
}
|
||||
function setAccent(value) {
|
||||
return AppearanceController.applyTheme(flavor, value);
|
||||
}
|
||||
readonly property var accentNames: AppearanceController.accents
|
||||
|
||||
readonly property color rosewater: palette.rosewater
|
||||
readonly property color flamingo: palette.flamingo
|
||||
readonly property color pink: palette.pink
|
||||
readonly property color mauve: palette.mauve
|
||||
readonly property color red: palette.red
|
||||
readonly property color maroon: palette.maroon
|
||||
readonly property color peach: palette.peach
|
||||
readonly property color yellow: palette.yellow
|
||||
readonly property color green: palette.green
|
||||
readonly property color teal: palette.teal
|
||||
readonly property color sky: palette.sky
|
||||
readonly property color sapphire: palette.sapphire
|
||||
readonly property color blue: palette.blue
|
||||
readonly property color lavender: palette.lavender
|
||||
readonly property color text: palette.text
|
||||
readonly property color subtext1: palette.subtext1
|
||||
readonly property color subtext0: palette.subtext0
|
||||
readonly property color overlay2: palette.overlay2
|
||||
readonly property color overlay1: palette.overlay1
|
||||
readonly property color overlay0: palette.overlay0
|
||||
readonly property color surface2: palette.surface2
|
||||
readonly property color surface1: palette.surface1
|
||||
readonly property color surface0: palette.surface0
|
||||
readonly property color base: palette.base
|
||||
readonly property color mantle: palette.mantle
|
||||
readonly property color crust: palette.crust
|
||||
|
||||
// CONTRACT: l'alpha de la barre est un profil UI, jamais une entrée Catppuccin.
|
||||
property real panelOpacity: 235 / 255
|
||||
readonly property color panelBackground: Qt.rgba(base.r, base.g, base.b, panelOpacity)
|
||||
readonly property color popupBackground: base
|
||||
readonly property color buttonBackground: surface0
|
||||
readonly property color buttonHover: surface1
|
||||
readonly property color buttonPressed: surface2
|
||||
readonly property color foreground: text
|
||||
// Latte sur surface0 a besoin d'un texte plus sombre que subtext0.
|
||||
readonly property color secondaryForeground: flavor === "latte" ? text : subtext0
|
||||
readonly property color disabledForeground: flavor === "latte" ? subtext1 : overlay0
|
||||
readonly property color border: surface1
|
||||
readonly property color strongBorder: surface2
|
||||
readonly property color accent: palette[accentName]
|
||||
// WHY: Lavender Latte n'offre aucun texte Catppuccin à contraste 4.5:1.
|
||||
// Choisir le noir ou le blanc technique par contraste WCAG pour chaque accent.
|
||||
function luminance(c) {
|
||||
const channel = x => x <= 0.04045 ? x / 12.92 : Math.pow((x + 0.055) / 1.055, 2.4);
|
||||
return 0.2126 * channel(c.r) + 0.7152 * channel(c.g) + 0.0722 * channel(c.b);
|
||||
}
|
||||
function contrastingText(background) {
|
||||
return luminance(background) > 0.179 ? "#000000" : "#ffffff";
|
||||
}
|
||||
readonly property color onAccent: contrastingText(accent)
|
||||
// Latte Lavender ne contraste pas assez avec base pour du texte nu.
|
||||
readonly property color accentForeground: flavor === "latte" ? text : accent
|
||||
readonly property color focus: accent
|
||||
readonly property color urgent: peach
|
||||
readonly property color warning: yellow
|
||||
readonly property color warningForeground: flavor === "latte" ? text : yellow
|
||||
readonly property color success: green
|
||||
readonly property color danger: red
|
||||
readonly property color urgentForeground: flavor === "latte" ? text : peach
|
||||
readonly property color successForeground: flavor === "latte" ? text : green
|
||||
readonly property color shadow: "#00000055"
|
||||
readonly property color separator: surface1
|
||||
readonly property color inputBackground: surface0
|
||||
readonly property color inputBorder: surface2
|
||||
readonly property color selectedBackground: accent
|
||||
readonly property color selectedForeground: onAccent
|
||||
}
|
||||
114
quickshell/.config/quickshell/labfy-sway/theme/catppuccin.json
Normal file
114
quickshell/.config/quickshell/labfy-sway/theme/catppuccin.json
Normal file
|
|
@ -0,0 +1,114 @@
|
|||
{
|
||||
"latte": {
|
||||
"rosewater": "#dc8a78",
|
||||
"flamingo": "#dd7878",
|
||||
"pink": "#ea76cb",
|
||||
"mauve": "#8839ef",
|
||||
"red": "#d20f39",
|
||||
"maroon": "#e64553",
|
||||
"peach": "#fe640b",
|
||||
"yellow": "#df8e1d",
|
||||
"green": "#40a02b",
|
||||
"teal": "#179299",
|
||||
"sky": "#04a5e5",
|
||||
"sapphire": "#209fb5",
|
||||
"blue": "#1e66f5",
|
||||
"lavender": "#7287fd",
|
||||
"text": "#4c4f69",
|
||||
"subtext1": "#5c5f77",
|
||||
"subtext0": "#6c6f85",
|
||||
"overlay2": "#7c7f93",
|
||||
"overlay1": "#8c8fa1",
|
||||
"overlay0": "#9ca0b0",
|
||||
"surface2": "#acb0be",
|
||||
"surface1": "#bcc0cc",
|
||||
"surface0": "#ccd0da",
|
||||
"base": "#eff1f5",
|
||||
"mantle": "#e6e9ef",
|
||||
"crust": "#dce0e8"
|
||||
},
|
||||
"frappe": {
|
||||
"rosewater": "#f2d5cf",
|
||||
"flamingo": "#eebebe",
|
||||
"pink": "#f4b8e4",
|
||||
"mauve": "#ca9ee6",
|
||||
"red": "#e78284",
|
||||
"maroon": "#ea999c",
|
||||
"peach": "#ef9f76",
|
||||
"yellow": "#e5c890",
|
||||
"green": "#a6d189",
|
||||
"teal": "#81c8be",
|
||||
"sky": "#99d1db",
|
||||
"sapphire": "#85c1dc",
|
||||
"blue": "#8caaee",
|
||||
"lavender": "#babbf1",
|
||||
"text": "#c6d0f5",
|
||||
"subtext1": "#b5bfe2",
|
||||
"subtext0": "#a5adce",
|
||||
"overlay2": "#949cbb",
|
||||
"overlay1": "#838ba7",
|
||||
"overlay0": "#737994",
|
||||
"surface2": "#626880",
|
||||
"surface1": "#51576d",
|
||||
"surface0": "#414559",
|
||||
"base": "#303446",
|
||||
"mantle": "#292c3c",
|
||||
"crust": "#232634"
|
||||
},
|
||||
"macchiato": {
|
||||
"rosewater": "#f4dbd6",
|
||||
"flamingo": "#f0c6c6",
|
||||
"pink": "#f5bde6",
|
||||
"mauve": "#c6a0f6",
|
||||
"red": "#ed8796",
|
||||
"maroon": "#ee99a0",
|
||||
"peach": "#f5a97f",
|
||||
"yellow": "#eed49f",
|
||||
"green": "#a6da95",
|
||||
"teal": "#8bd5ca",
|
||||
"sky": "#91d7e3",
|
||||
"sapphire": "#7dc4e4",
|
||||
"blue": "#8aadf4",
|
||||
"lavender": "#b7bdf8",
|
||||
"text": "#cad3f5",
|
||||
"subtext1": "#b8c0e0",
|
||||
"subtext0": "#a5adcb",
|
||||
"overlay2": "#939ab7",
|
||||
"overlay1": "#8087a2",
|
||||
"overlay0": "#6e738d",
|
||||
"surface2": "#5b6078",
|
||||
"surface1": "#494d64",
|
||||
"surface0": "#363a4f",
|
||||
"base": "#24273a",
|
||||
"mantle": "#1e2030",
|
||||
"crust": "#181926"
|
||||
},
|
||||
"mocha": {
|
||||
"rosewater": "#f5e0dc",
|
||||
"flamingo": "#f2cdcd",
|
||||
"pink": "#f5c2e7",
|
||||
"mauve": "#cba6f7",
|
||||
"red": "#f38ba8",
|
||||
"maroon": "#eba0ac",
|
||||
"peach": "#fab387",
|
||||
"yellow": "#f9e2af",
|
||||
"green": "#a6e3a1",
|
||||
"teal": "#94e2d5",
|
||||
"sky": "#89dceb",
|
||||
"sapphire": "#74c7ec",
|
||||
"blue": "#89b4fa",
|
||||
"lavender": "#b4befe",
|
||||
"text": "#cdd6f4",
|
||||
"subtext1": "#bac2de",
|
||||
"subtext0": "#a6adc8",
|
||||
"overlay2": "#9399b2",
|
||||
"overlay1": "#7f849c",
|
||||
"overlay0": "#6c7086",
|
||||
"surface2": "#585b70",
|
||||
"surface1": "#45475a",
|
||||
"surface0": "#313244",
|
||||
"base": "#1e1e2e",
|
||||
"mantle": "#181825",
|
||||
"crust": "#11111b"
|
||||
}
|
||||
}
|
||||
2
quickshell/.config/quickshell/labfy-sway/theme/qmldir
Normal file
2
quickshell/.config/quickshell/labfy-sway/theme/qmldir
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
singleton Theme 1.0 Theme.qml
|
||||
singleton AppearanceController 1.0 AppearanceController.qml
|
||||
|
|
@ -2,6 +2,7 @@ import QtQuick
|
|||
import Quickshell
|
||||
import Quickshell.I3
|
||||
import "../components"
|
||||
import "../theme"
|
||||
|
||||
Rectangle {
|
||||
id: button
|
||||
|
|
@ -27,9 +28,9 @@ Rectangle {
|
|||
: 26
|
||||
height: 26
|
||||
radius: 4
|
||||
color: primary ? "#45475a" : pointer.containsMouse ? "#45475a" : "#313244"
|
||||
color: primary ? Theme.border : pointer.containsMouse ? Theme.border : Theme.buttonBackground
|
||||
border.width: primary ? 1 : 0
|
||||
border.color: "#cba6f7"
|
||||
border.color: Theme.accent
|
||||
|
||||
NerdIcon {
|
||||
id: icon
|
||||
|
|
@ -51,7 +52,7 @@ Rectangle {
|
|||
anchors.rightMargin: 8
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: button.fullTitle
|
||||
color: "#cdd6f4"
|
||||
color: Theme.foreground
|
||||
font.pixelSize: 13
|
||||
elide: Text.ElideRight
|
||||
maximumLineCount: 1
|
||||
|
|
@ -105,8 +106,8 @@ Rectangle {
|
|||
Rectangle {
|
||||
anchors.fill: parent
|
||||
radius: 4
|
||||
color: "#313244"
|
||||
border.color: "#45475a"
|
||||
color: Theme.buttonBackground
|
||||
border.color: Theme.border
|
||||
|
||||
Column {
|
||||
id: tooltipContent
|
||||
|
|
@ -118,14 +119,14 @@ Rectangle {
|
|||
|
||||
Text {
|
||||
text: button.appName
|
||||
color: "#cdd6f4"
|
||||
color: Theme.foreground
|
||||
font.pixelSize: 12
|
||||
font.bold: true
|
||||
}
|
||||
Text {
|
||||
width: parent.width
|
||||
text: button.fullTitle
|
||||
color: "#a6adc8"
|
||||
color: Theme.secondaryForeground
|
||||
font.pixelSize: 12
|
||||
wrapMode: Text.Wrap
|
||||
maximumLineCount: 3
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.I3
|
||||
import "../theme"
|
||||
|
||||
PopupWindow {
|
||||
id: menu
|
||||
|
|
@ -30,8 +31,8 @@ PopupWindow {
|
|||
Rectangle {
|
||||
anchors.fill: parent
|
||||
radius: 4
|
||||
color: "#1e1e2e"
|
||||
border.color: "#45475a"
|
||||
color: Theme.popupBackground
|
||||
border.color: Theme.border
|
||||
|
||||
Column {
|
||||
id: content
|
||||
|
|
@ -45,7 +46,7 @@ PopupWindow {
|
|||
width: parent.width
|
||||
text: menu.entry ? menu.entry.name
|
||||
: menu.selectedWindow ? menu.selectedWindow.appId : ""
|
||||
color: "#cdd6f4"
|
||||
color: Theme.foreground
|
||||
font.pixelSize: 15
|
||||
font.bold: true
|
||||
elide: Text.ElideRight
|
||||
|
|
@ -53,7 +54,7 @@ PopupWindow {
|
|||
|
||||
Text {
|
||||
text: "Fenêtres ouvertes"
|
||||
color: "#a6adc8"
|
||||
color: Theme.secondaryForeground
|
||||
font.pixelSize: 11
|
||||
}
|
||||
|
||||
|
|
@ -65,7 +66,7 @@ PopupWindow {
|
|||
width: content.width
|
||||
height: 30
|
||||
radius: 4
|
||||
color: pointer.containsMouse ? "#45475a" : "#313244"
|
||||
color: pointer.containsMouse ? Theme.border : Theme.buttonBackground
|
||||
|
||||
Text {
|
||||
anchors.left: parent.left
|
||||
|
|
@ -74,7 +75,7 @@ PopupWindow {
|
|||
anchors.rightMargin: 8
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: modelData.title
|
||||
color: "#cdd6f4"
|
||||
color: Theme.foreground
|
||||
font.pixelSize: 12
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
|
@ -94,7 +95,7 @@ PopupWindow {
|
|||
Text {
|
||||
visible: menu.actions.length > 0
|
||||
text: "Actions"
|
||||
color: "#a6adc8"
|
||||
color: Theme.secondaryForeground
|
||||
font.pixelSize: 11
|
||||
}
|
||||
|
||||
|
|
@ -106,7 +107,7 @@ PopupWindow {
|
|||
width: content.width
|
||||
height: 30
|
||||
radius: 4
|
||||
color: actionPointer.containsMouse ? "#45475a" : "#313244"
|
||||
color: actionPointer.containsMouse ? Theme.border : Theme.buttonBackground
|
||||
|
||||
Text {
|
||||
anchors.left: parent.left
|
||||
|
|
@ -115,7 +116,7 @@ PopupWindow {
|
|||
anchors.rightMargin: 8
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: modelData.name
|
||||
color: "#cdd6f4"
|
||||
color: Theme.foreground
|
||||
font.pixelSize: 12
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,11 @@
|
|||
# Configuration SwayFX
|
||||
|
||||
Les couleurs et variables d'apparence viennent du
|
||||
[moteur commun](../../../../quickshell/.config/quickshell/labfy-sway/theme/README.md).
|
||||
`theme-default.conf` assure le démarrage Mocha avant génération ;
|
||||
`generated/theme.conf` est l'état runtime créé atomiquement par
|
||||
`bin/.local/bin/generate-appearance.py` depuis la palette QuickShell.
|
||||
|
||||
Le compositeur actif est **SwayFX 0.6** avec `scenefx`. La session UWSM
|
||||
sélectionne `sway.desktop` et charge `config` : `bind`, `input`, `rules`,
|
||||
`style`, `swayfx` et `autostart`. `eDP-1` est l'unique écran configuré, à
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
# =================================================
|
||||
|
||||
# Listener IPC démarré une fois par session ; exec ne se rejoue pas au reload.
|
||||
exec python3 ~/.config/sway/scripts/inactive-windows-transparency.py
|
||||
exec python3 ~/.config/sway/scripts/inactive-windows-transparency.py --opacity $inactive_opacity
|
||||
|
||||
# =================================================
|
||||
# Règles d'assignation automatique
|
||||
|
|
|
|||
|
|
@ -1,26 +0,0 @@
|
|||
set $rosewater #f5e0dc
|
||||
set $flamingo #f2cdcd
|
||||
set $pink #f5c2e7
|
||||
set $mauve #cba6f7
|
||||
set $red #f38ba8
|
||||
set $maroon #eba0ac
|
||||
set $peach #fab387
|
||||
set $yellow #f9e2af
|
||||
set $green #a6e3a1
|
||||
set $teal #94e2d5
|
||||
set $sky #89dceb
|
||||
set $sapphire #74c7ec
|
||||
set $blue #89b4fa
|
||||
set $lavender #b4befe
|
||||
set $text #cdd6f4
|
||||
set $subtext1 #bac2de
|
||||
set $subtext0 #a6adc8
|
||||
set $overlay2 #9399b2
|
||||
set $overlay1 #7f849c
|
||||
set $overlay0 #6c7086
|
||||
set $surface2 #585b70
|
||||
set $surface1 #45475a
|
||||
set $surface0 #313244
|
||||
set $base #1e1e2e
|
||||
set $mantle #181825
|
||||
set $crust #11111b
|
||||
|
|
@ -10,10 +10,13 @@
|
|||
#
|
||||
# Style
|
||||
#
|
||||
include ~/.config/sway/catppuccin-mocha
|
||||
# CONTRACT: le glob vide est valide dans Sway ; le repli fonctionne dès Stow.
|
||||
# Le fichier runtime, s'il existe, remplace les variables sans palette parallèle.
|
||||
include ~/.config/sway/theme-default.conf
|
||||
include ~/.config/sway/generated/*.conf
|
||||
|
||||
# target title bg text indicator border
|
||||
client.focused $lavender $base $text $rosewater $lavender
|
||||
client.focused $accent $base $text $rosewater $accent
|
||||
client.focused_inactive $overlay0 $base $text $rosewater $overlay0
|
||||
client.unfocused $overlay0 $base $text $rosewater $overlay0
|
||||
client.urgent $peach $base $peach $overlay0 $peach
|
||||
|
|
|
|||
|
|
@ -5,8 +5,8 @@ corner_radius 4
|
|||
shadows enable
|
||||
shadows_on_csd disable
|
||||
shadow_blur_radius 10
|
||||
shadow_color #00000055
|
||||
shadow_inactive_color #00000035
|
||||
shadow_color $shadow_active
|
||||
shadow_inactive_color $shadow_inactive
|
||||
shadow_offset 0 2
|
||||
|
||||
default_dim_inactive 0
|
||||
|
|
|
|||
12
sway/.config/sway/theme-default.conf
Normal file
12
sway/.config/sway/theme-default.conf
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
# Repli Mocha avant la première génération runtime. Valeurs dérivées de
|
||||
# theme/catppuccin.json ; seules les variables nécessaires à Sway sont ici.
|
||||
set $lavender #b4befe
|
||||
set $base #1e1e2e
|
||||
set $text #cdd6f4
|
||||
set $rosewater #f5e0dc
|
||||
set $overlay0 #6c7086
|
||||
set $peach #fab387
|
||||
set $accent #b4befe
|
||||
set $shadow_active #00000055
|
||||
set $shadow_inactive #00000035
|
||||
set $inactive_opacity 0.85
|
||||
46
tests/test_generate_appearance.py
Normal file
46
tests/test_generate_appearance.py
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
"""Contrats du générateur : validation et remplacement non destructif."""
|
||||
import importlib.util
|
||||
from pathlib import Path
|
||||
import tempfile
|
||||
import unittest
|
||||
from unittest.mock import patch
|
||||
|
||||
SCRIPT = Path(__file__).resolve().parents[1] / 'bin/.local/bin/generate-appearance.py'
|
||||
spec = importlib.util.spec_from_file_location('appearance_generator', SCRIPT)
|
||||
module = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(module)
|
||||
|
||||
|
||||
class AppearanceGeneratorTest(unittest.TestCase):
|
||||
def test_all_flavors_and_valid_accent(self):
|
||||
palette = module.palette_data()
|
||||
for flavor in palette:
|
||||
result = module.render(flavor, 'lavender')
|
||||
self.assertIn(f'set $accent {palette[flavor]["lavender"]}', result)
|
||||
self.assertIn(f'set $base {palette[flavor]["base"]}', result)
|
||||
self.assertIn('set $accent ' + palette['mocha']['mauve'],
|
||||
module.render('mocha', 'mauve'))
|
||||
|
||||
def test_invalid_inputs(self):
|
||||
for flavor, accent, profile in [('invalid', 'lavender', 'default'),
|
||||
('mocha', 'invalid', 'default'),
|
||||
('mocha', 'lavender', 'invalid')]:
|
||||
with self.subTest(flavor=flavor, accent=accent, profile=profile):
|
||||
with self.assertRaises(ValueError):
|
||||
module.render(flavor, accent, profile)
|
||||
|
||||
def test_atomic_replace_and_failed_replace(self):
|
||||
with tempfile.TemporaryDirectory() as folder:
|
||||
output = Path(folder) / 'theme.conf'
|
||||
output.write_bytes(b'old\n')
|
||||
module.atomic_write(output, b'new\n')
|
||||
self.assertEqual(output.read_bytes(), b'new\n')
|
||||
with patch.object(module.os, 'replace', side_effect=OSError('échec')):
|
||||
with self.assertRaises(OSError):
|
||||
module.atomic_write(output, b'broken\n')
|
||||
self.assertEqual(output.read_bytes(), b'new\n')
|
||||
self.assertFalse((Path(folder) / 'theme.conf.tmp').exists())
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
Loading…
Reference in a new issue