init: working version
This commit is contained in:
commit
7d8d7dacae
109 changed files with 15066 additions and 0 deletions
137
modules/bar/Bar.qml
Normal file
137
modules/bar/Bar.qml
Normal file
|
|
@ -0,0 +1,137 @@
|
|||
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import qs.config
|
||||
import qs.custom
|
||||
import qs.services
|
||||
import "modules"
|
||||
import "popouts" as BarPopouts
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
required property PersistentProperties uiState
|
||||
required property ShellScreen screen
|
||||
required property BarPopouts.Wrapper popouts
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.top: parent.top
|
||||
anchors.right: parent.right
|
||||
implicitHeight: Config.bar.height
|
||||
|
||||
// Modules
|
||||
|
||||
NixOS {
|
||||
id: nixos
|
||||
objectName: "nixos"
|
||||
|
||||
anchors.left: parent.left
|
||||
}
|
||||
|
||||
Workspaces {
|
||||
id: workspaces
|
||||
|
||||
anchors.left: nixos.right
|
||||
|
||||
workspaces: root.uiState.workspaces
|
||||
}
|
||||
|
||||
Window {
|
||||
id: window
|
||||
objectName: "window"
|
||||
|
||||
// Expand window title from center as much as possible
|
||||
// without intersecting other modules
|
||||
anchors.centerIn: parent
|
||||
implicitWidth: 2 * Math.min(
|
||||
(parent.width / 2) - workspaces.x - workspaces.width,
|
||||
tray.x - (parent.width / 2)) - 40
|
||||
}
|
||||
|
||||
Tray {
|
||||
id: tray
|
||||
objectName: "tray"
|
||||
|
||||
anchors.right: clock.left
|
||||
anchors.rightMargin: 8
|
||||
}
|
||||
|
||||
Clock {
|
||||
id: clock
|
||||
objectName: "clock"
|
||||
|
||||
anchors.right: statusIcons.left
|
||||
anchors.rightMargin: 12
|
||||
}
|
||||
|
||||
StatusIcons {
|
||||
id: statusIcons
|
||||
objectName: "statusIcons"
|
||||
|
||||
anchors.right: power.left
|
||||
anchors.rightMargin: 6
|
||||
}
|
||||
|
||||
Power {
|
||||
id: power
|
||||
uiState: root.uiState
|
||||
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 6
|
||||
}
|
||||
|
||||
// Popout Interactions
|
||||
|
||||
function checkPopout(x: real): void {
|
||||
const ch = childAt(x, height / 2);
|
||||
if (!ch) {
|
||||
popouts.hasCurrent = false;
|
||||
return;
|
||||
}
|
||||
|
||||
const name = ch.objectName;
|
||||
const left = ch.x;
|
||||
const chWidth = ch.implicitWidth;
|
||||
|
||||
if (name === "nixos") {
|
||||
popouts.currentName = "nixos";
|
||||
popouts.currentCenter = 0;
|
||||
popouts.hasCurrent = true;
|
||||
} else if (name === "statusIcons") {
|
||||
const layout = ch.children[0];
|
||||
const icon = layout.childAt(mapToItem(layout, x, 0).x, layout.height / 2);
|
||||
if (icon && icon.objectName) {
|
||||
popouts.currentName = icon.objectName;
|
||||
popouts.currentCenter = Qt.binding(() =>
|
||||
icon.mapToItem(root, 0, icon.implicitWidth / 2).x);
|
||||
popouts.hasCurrent = true;
|
||||
} else if (icon && !icon.objectName) {
|
||||
popouts.hasCurrent = false;
|
||||
}
|
||||
} else if (name === "tray") {
|
||||
const index = Math.floor(((x - left) / chWidth) * tray.repeater.count);
|
||||
const trayItem = tray.repeater.itemAt(index);
|
||||
if (trayItem) {
|
||||
popouts.currentName = `traymenu${index}`;
|
||||
popouts.currentCenter = Qt.binding(() =>
|
||||
trayItem.mapToItem(root, 0, trayItem.implicitWidth / 2).x);
|
||||
popouts.hasCurrent = true;
|
||||
}
|
||||
} else if (name === "clock") {
|
||||
popouts.currentName = "calendar";
|
||||
popouts.currentCenter = ch.mapToItem(root, chWidth / 2, 0).x;
|
||||
popouts.hasCurrent = true;
|
||||
} else if (name === "window" && Hypr.activeToplevel) {
|
||||
const inner = ch.childAt(mapToItem(ch, x, 0).x, height / 2)
|
||||
if (inner) {
|
||||
popouts.currentName = "activewindow";
|
||||
popouts.currentCenter = ch.mapToItem(root, chWidth / 2, 0).x;
|
||||
popouts.hasCurrent = true;
|
||||
} else {
|
||||
popouts.hasCurrent = false;
|
||||
}
|
||||
} else {
|
||||
popouts.hasCurrent = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
15
modules/bar/Container.qml
Normal file
15
modules/bar/Container.qml
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import qs.config
|
||||
import qs.custom
|
||||
|
||||
CustomRect {
|
||||
color: Config.colors.container
|
||||
|
||||
implicitWidth: Math.max(childrenRect.width, height)
|
||||
implicitHeight: Config.bar.containerHeight
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
radius: 1000
|
||||
}
|
||||
35
modules/bar/modules/Clock.qml
Normal file
35
modules/bar/modules/Clock.qml
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
|
||||
import QtQuick
|
||||
import qs.services
|
||||
import qs.config
|
||||
import qs.custom
|
||||
|
||||
Row {
|
||||
id: root
|
||||
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
property color color: Config.colors.turqoise
|
||||
spacing: 4
|
||||
|
||||
MaterialIcon {
|
||||
id: icon
|
||||
|
||||
text: "calendar_month"
|
||||
font.pointSize: Config.font.size.normal + 1
|
||||
color: root.color
|
||||
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
CustomText {
|
||||
id: text
|
||||
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
text: Time.format("hh:mm")
|
||||
font.pointSize: Config.font.size.smaller
|
||||
font.family: Config.font.family.mono
|
||||
color: root.color
|
||||
}
|
||||
}
|
||||
15
modules/bar/modules/NixOS.qml
Normal file
15
modules/bar/modules/NixOS.qml
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
|
||||
import qs.config
|
||||
import qs.custom
|
||||
import QtQuick
|
||||
|
||||
CustomText {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
text: ""
|
||||
width: implicitWidth + 32
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
font.family: Config.font.family.mono
|
||||
font.pointSize: Config.font.size.normal
|
||||
color: Config.colors.nixos
|
||||
}
|
||||
29
modules/bar/modules/Power.qml
Normal file
29
modules/bar/modules/Power.qml
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
|
||||
import Quickshell
|
||||
import qs.config
|
||||
import qs.custom
|
||||
import qs.services
|
||||
|
||||
StateLayer {
|
||||
required property PersistentProperties uiState
|
||||
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
implicitWidth: icon.implicitHeight + 10
|
||||
implicitHeight: implicitWidth
|
||||
|
||||
function onClicked(): void {
|
||||
uiState.session = !uiState.session;
|
||||
}
|
||||
|
||||
MaterialIcon {
|
||||
id: icon
|
||||
anchors.centerIn: parent
|
||||
anchors.horizontalCenterOffset: 0.5
|
||||
|
||||
text: "power_settings_new"
|
||||
color: Config.colors.error
|
||||
font.bold: true
|
||||
font.pointSize: Config.font.size.smaller
|
||||
}
|
||||
}
|
||||
166
modules/bar/modules/StatusIcons.qml
Normal file
166
modules/bar/modules/StatusIcons.qml
Normal file
|
|
@ -0,0 +1,166 @@
|
|||
import Quickshell
|
||||
import Quickshell.Services.UPower
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import qs.services
|
||||
import qs.config
|
||||
import qs.custom
|
||||
import qs.util
|
||||
import qs.modules.bar
|
||||
|
||||
Container {
|
||||
id: root
|
||||
implicitWidth: layout.width + 20
|
||||
|
||||
RowLayout {
|
||||
id: layout
|
||||
|
||||
anchors.centerIn: parent
|
||||
spacing: 10
|
||||
|
||||
MaterialIcon {
|
||||
id: network
|
||||
objectName: "network"
|
||||
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
|
||||
text: Network.active ? Icons.getNetworkIcon(Network.active.strength ?? 0) : "wifi_off"
|
||||
color: text !== "wifi_off" ? Config.colors.secondary : Config.colors.tertiary
|
||||
animate: (from, to) => from === "wifi_off" || to === "wifi_off"
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: Network.toggleWifi()
|
||||
}
|
||||
}
|
||||
|
||||
/* MaterialIcon { */
|
||||
/* id: bluetooth */
|
||||
/* objectName: "bluetooth" */
|
||||
|
||||
/* anchors.verticalCenter: parent.verticalCenter */
|
||||
|
||||
/* animate: true */
|
||||
/* text: Bluetooth.powered ? "bluetooth" : "bluetooth_disabled" */
|
||||
/* } */
|
||||
|
||||
/* Row { */
|
||||
/* id: devices */
|
||||
/* objectName: "devices" */
|
||||
|
||||
/* anchors.verticalCenter: parent.verticalCenter */
|
||||
|
||||
/* Repeater { */
|
||||
/* id: repeater */
|
||||
|
||||
/* model: ScriptModel { */
|
||||
/* values: Bluetooth.devices.filter(d => d.connected) */
|
||||
/* } */
|
||||
|
||||
/* MaterialIcon { */
|
||||
/* required property Bluetooth.Device modelData */
|
||||
|
||||
/* animate: true */
|
||||
/* text: Icons.getBluetoothIcon(modelData.icon) */
|
||||
/* fill: 1 */
|
||||
/* } */
|
||||
/* } */
|
||||
/* } */
|
||||
|
||||
MaterialIcon {
|
||||
id: idleinhibit
|
||||
objectName: "idleinhibit"
|
||||
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
|
||||
text: Idle.inhibit ? "visibility" : "visibility_off"
|
||||
color: text === "visibility" ? Config.colors.secondary : Config.colors.tertiary
|
||||
fill: Idle.inhibit ? 1 : 0
|
||||
animate: true
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: Idle.inhibit = !Idle.inhibit
|
||||
}
|
||||
}
|
||||
|
||||
MaterialIcon {
|
||||
id: battery
|
||||
objectName: "battery"
|
||||
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
Layout.leftMargin: hasBattery ? -2 : 0
|
||||
Layout.topMargin: hasBattery ? 0.5 : 2
|
||||
|
||||
readonly property bool hasBattery: UPower.displayDevice.isLaptopBattery
|
||||
readonly property real percentage: UPower.displayDevice.percentage
|
||||
readonly property bool charging: !UPower.onBattery && batteryText.text !== "100"
|
||||
readonly property bool warning: UPower.onBattery && percentage < 0.15
|
||||
|
||||
text: {
|
||||
if (!hasBattery) {
|
||||
if (PowerProfiles.profile === PowerProfile.PowerSaver)
|
||||
return "energy_savings_leaf";
|
||||
if (PowerProfiles.profile === PowerProfile.Performance)
|
||||
return "rocket_launch";
|
||||
return "balance";
|
||||
}
|
||||
return `battery_android_full`;
|
||||
}
|
||||
fill: 1
|
||||
font.pointSize: hasBattery ? 18 : Config.font.size.normal
|
||||
grade: 50
|
||||
font.weight: 100
|
||||
color: !hasBattery ? Config.colors.secondary :
|
||||
warning ? Config.colors.errorBg :
|
||||
batteryText.text === "100" ? Config.colors.battery :
|
||||
Color.mute(Config.colors.battery, 0.6, 1.5)
|
||||
|
||||
CustomRect {
|
||||
anchors.top: parent.top
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.topMargin: 9
|
||||
anchors.bottomMargin: 9
|
||||
anchors.leftMargin: 3
|
||||
width: (battery.width - 7) * battery.percentage
|
||||
radius: 2
|
||||
|
||||
visible: battery.hasBattery
|
||||
color: battery.warning ? Config.colors.batteryWarning : Config.colors.battery
|
||||
}
|
||||
|
||||
Row {
|
||||
anchors.centerIn: parent
|
||||
anchors.horizontalCenterOffset: battery.charging ? width / 20 : -width / 15
|
||||
|
||||
visible: battery.hasBattery
|
||||
spacing: -1
|
||||
|
||||
CustomText {
|
||||
id: batteryText
|
||||
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.verticalCenterOffset: 0.5
|
||||
text: Math.round(battery.percentage * 100)
|
||||
color: battery.warning ? Config.colors.batteryWarning : Config.colors.bg
|
||||
font.family: Config.font.family.mono
|
||||
font.pointSize: 6
|
||||
font.weight: 800
|
||||
}
|
||||
|
||||
MaterialIcon {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
visible: battery.charging
|
||||
text: "bolt"
|
||||
fill: 1
|
||||
color: Config.colors.bg
|
||||
font.pointSize: 7
|
||||
font.weight: 300
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
92
modules/bar/modules/Tray.qml
Normal file
92
modules/bar/modules/Tray.qml
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Widgets
|
||||
import Quickshell.Services.SystemTray
|
||||
import qs.config
|
||||
import qs.custom
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
clip: true
|
||||
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
implicitWidth: layout.implicitWidth
|
||||
implicitHeight: layout.implicitHeight
|
||||
// To avoid warnings about being visible with no size
|
||||
visible: width > 0 && height > 0
|
||||
|
||||
readonly property Item repeater: repeater
|
||||
|
||||
Row {
|
||||
id: layout
|
||||
|
||||
add: Transition {
|
||||
Anim {
|
||||
property: "scale"
|
||||
from: 0
|
||||
to: 1
|
||||
easing.bezierCurve: Config.anim.curves.standardDecel
|
||||
}
|
||||
}
|
||||
|
||||
move: Transition {
|
||||
Anim {
|
||||
property: "scale"
|
||||
to: 1
|
||||
easing.bezierCurve: Config.anim.curves.standardDecel
|
||||
}
|
||||
Anim {
|
||||
properties: "x,y"
|
||||
easing.bezierCurve: Config.anim.curves.standard
|
||||
}
|
||||
}
|
||||
|
||||
Repeater {
|
||||
id: repeater
|
||||
|
||||
model: SystemTray.items
|
||||
|
||||
MouseArea {
|
||||
id: trayItem
|
||||
|
||||
required property SystemTrayItem modelData
|
||||
|
||||
implicitWidth: icon.implicitWidth + 10
|
||||
implicitHeight: icon.implicitHeight
|
||||
|
||||
onClicked: modelData.activate()
|
||||
|
||||
IconImage {
|
||||
id: icon
|
||||
anchors.centerIn: parent
|
||||
|
||||
source: {
|
||||
let icon = trayItem.modelData.icon;
|
||||
if (icon.includes("?path=")) {
|
||||
const [name, path] = icon.split("?path=");
|
||||
icon = `file://${path}/${name.slice(name.lastIndexOf("/") + 1)}`;
|
||||
}
|
||||
return icon;
|
||||
}
|
||||
asynchronous: true
|
||||
implicitSize: Config.font.size.larger
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on implicitWidth {
|
||||
Anim {
|
||||
easing.bezierCurve: Config.anim.curves.emphasized
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on implicitHeight {
|
||||
Anim {
|
||||
easing.bezierCurve: Config.anim.curves.emphasized
|
||||
}
|
||||
}
|
||||
}
|
||||
96
modules/bar/modules/Window.qml
Normal file
96
modules/bar/modules/Window.qml
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
import qs.config
|
||||
import qs.custom
|
||||
import qs.services
|
||||
import qs.util
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
property color color: Config.colors.primary
|
||||
|
||||
implicitHeight: child.implicitHeight
|
||||
|
||||
Item {
|
||||
id: child
|
||||
|
||||
anchors.centerIn: parent
|
||||
implicitWidth: icon.implicitWidth + current.implicitWidth + current.anchors.leftMargin
|
||||
implicitHeight: Math.max(icon.implicitHeight, current.implicitHeight)
|
||||
|
||||
clip: true
|
||||
|
||||
property Item current: text1
|
||||
|
||||
MaterialIcon {
|
||||
id: icon
|
||||
|
||||
animate: true
|
||||
text: {
|
||||
const cls = Hypr.activeToplevel?.lastIpcObject.class;
|
||||
if (!cls) return "desktop_windows";
|
||||
Icons.getAppCategoryIcon(cls, "ad")
|
||||
}
|
||||
|
||||
color: root.color
|
||||
font.pointSize: Config.font.size.larger
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
Title {
|
||||
id: text1
|
||||
}
|
||||
|
||||
Title {
|
||||
id: text2
|
||||
}
|
||||
|
||||
TextMetrics {
|
||||
id: metrics
|
||||
|
||||
text: Hypr.activeToplevel?.title ?? qsTr("Desktop")
|
||||
font.pointSize: Config.font.size.smaller
|
||||
font.family: Config.font.family.mono
|
||||
elide: Qt.ElideRight
|
||||
elideWidth: root.width - icon.width
|
||||
|
||||
onTextChanged: {
|
||||
const next = child.current === text1 ? text2 : text1;
|
||||
next.text = elidedText;
|
||||
child.current = next;
|
||||
}
|
||||
onElideWidthChanged: child.current.text = elidedText
|
||||
}
|
||||
|
||||
Behavior on implicitWidth {
|
||||
Anim {
|
||||
easing.bezierCurve: Config.anim.curves.emphasized
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on implicitHeight {
|
||||
Anim {
|
||||
easing.bezierCurve: Config.anim.curves.emphasized
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
component Title: CustomText {
|
||||
id: text
|
||||
|
||||
anchors.verticalCenter: icon.verticalCenter
|
||||
anchors.left: icon.right
|
||||
anchors.leftMargin: 8
|
||||
|
||||
font.pointSize: metrics.font.pointSize
|
||||
font.family: metrics.font.family
|
||||
color: root.color
|
||||
opacity: child.current === this ? 1 : 0
|
||||
|
||||
Behavior on opacity {
|
||||
Anim {}
|
||||
}
|
||||
}
|
||||
}
|
||||
234
modules/bar/modules/Workspaces.qml
Normal file
234
modules/bar/modules/Workspaces.qml
Normal file
|
|
@ -0,0 +1,234 @@
|
|||
pragma ComponentBehavior: Bound
|
||||
|
||||
import qs.services
|
||||
import qs.config
|
||||
import qs.custom
|
||||
import qs.util
|
||||
import qs.modules.bar
|
||||
import QtQuick
|
||||
import QtQuick.Effects
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import Quickshell.Hyprland
|
||||
|
||||
Container {
|
||||
id: root
|
||||
|
||||
required property ListModel workspaces
|
||||
|
||||
readonly property HyprlandMonitor monitor: Hypr.monitorFor(QsWindow.window.screen)
|
||||
|
||||
// Workspace Layout
|
||||
|
||||
implicitWidth: Math.max(list.width + Config.bar.workspaceMargin * 2, height)
|
||||
|
||||
Behavior on implicitWidth {
|
||||
Anim {
|
||||
easing.bezierCurve: Config.anim.curves.emphasized
|
||||
}
|
||||
}
|
||||
|
||||
readonly property real workspaceSize: Config.bar.containerHeight - Config.bar.workspaceMargin * 2
|
||||
|
||||
Item {
|
||||
id: listWrapper
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: Config.bar.workspaceMargin
|
||||
|
||||
width: list.width
|
||||
height: list.height
|
||||
layer.enabled: true
|
||||
layer.smooth: true
|
||||
|
||||
ListView {
|
||||
id: list
|
||||
anchors.centerIn: parent
|
||||
width: contentWidth
|
||||
height: root.workspaceSize
|
||||
acceptedButtons: Qt.NoButton
|
||||
boundsBehavior: Flickable.StopAtBounds
|
||||
|
||||
spacing: Config.bar.workspaceMargin
|
||||
orientation: ListView.Horizontal
|
||||
|
||||
model: root.workspaces
|
||||
|
||||
delegate: Item {
|
||||
id: buttonWrapper
|
||||
|
||||
required property int index
|
||||
required property HyprlandWorkspace workspace
|
||||
|
||||
width: button.width
|
||||
height: button.height
|
||||
|
||||
readonly property Item button: button
|
||||
|
||||
StateLayer {
|
||||
id: button
|
||||
|
||||
anchors.horizontalCenter: buttonWrapper.horizontalCenter
|
||||
anchors.verticalCenter: buttonWrapper.verticalCenter
|
||||
width: height
|
||||
height: root.workspaceSize
|
||||
|
||||
drag.target: this
|
||||
drag.axis: Drag.XAxis
|
||||
drag.threshold: 2
|
||||
drag.minimumX: list.x
|
||||
drag.maximumX: list.x + list.width - root.workspaceSize
|
||||
|
||||
states: State {
|
||||
name: "dragging"
|
||||
when: button.drag.active
|
||||
|
||||
ParentChange {
|
||||
target: button
|
||||
parent: listWrapper
|
||||
}
|
||||
AnchorChanges {
|
||||
target: button
|
||||
anchors.horizontalCenter: undefined
|
||||
}
|
||||
}
|
||||
|
||||
transitions: Transition {
|
||||
from: "dragging"
|
||||
to: ""
|
||||
ParentAnimation {
|
||||
AnchorAnimation {
|
||||
duration: Config.anim.durations.small
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: Config.anim.curves.standardDecel
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function onClicked(): void {
|
||||
if (root.monitor.activeWorkspace !== workspace)
|
||||
Hypr.dispatch(`workspace ${workspace.id}`);
|
||||
}
|
||||
|
||||
onXChanged: {
|
||||
if (!drag.active) return;
|
||||
const wsx = x / (width + list.spacing);
|
||||
root.workspaces.move(index, Math.round(wsx), 1);
|
||||
}
|
||||
|
||||
MaterialIcon {
|
||||
anchors.centerIn: parent
|
||||
text: Icons.getWorkspaceIcon(buttonWrapper.workspace)
|
||||
color: Config.colors.primary
|
||||
font.pointSize: Config.font.size.larger
|
||||
animate: true
|
||||
animateDuration: Config.anim.durations.small
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
add: Transition {
|
||||
Anim {
|
||||
property: "opacity"
|
||||
from: 0
|
||||
to: 1
|
||||
}
|
||||
}
|
||||
|
||||
remove: Transition {
|
||||
Anim {
|
||||
property: "opacity"
|
||||
from: 1
|
||||
to: 0
|
||||
}
|
||||
}
|
||||
|
||||
move: Transition {
|
||||
Anim {
|
||||
property: "x"
|
||||
}
|
||||
Anim {
|
||||
properties: "opacity"
|
||||
to: 1
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
displaced: Transition {
|
||||
Anim {
|
||||
property: "x"
|
||||
}
|
||||
Anim {
|
||||
properties: "opacity"
|
||||
to: 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Active Indicator
|
||||
|
||||
CustomRect {
|
||||
id: activeInd
|
||||
|
||||
readonly property Item active: {
|
||||
list.count;
|
||||
const activeWorkspace = root.monitor.activeWorkspace;
|
||||
for (let i = 0; i < (root.workspaces?.count ?? 0); i++) {
|
||||
if (root.workspaces.get(i).workspace === activeWorkspace)
|
||||
return list.itemAtIndex(i);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
x: active ? (active.button.drag.active ? active.button.x : active.x) + Config.bar.workspaceMargin : 0
|
||||
y: Config.bar.workspaceMargin
|
||||
width: active?.width ?? workspaceSize
|
||||
height: active?.height ?? workspaceSize
|
||||
|
||||
radius: 1000
|
||||
color: Config.colors.workspaces
|
||||
|
||||
clip: true
|
||||
|
||||
property bool transition: false
|
||||
onActiveChanged: transition = true
|
||||
|
||||
Behavior on x {
|
||||
enabled: activeInd.transition
|
||||
SequentialAnimation {
|
||||
Anim {
|
||||
easing.bezierCurve: Config.anim.curves.emphasized
|
||||
}
|
||||
PropertyAction {
|
||||
target: activeInd
|
||||
property: "transition"
|
||||
value: false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CustomRect {
|
||||
id: base
|
||||
|
||||
visible: false
|
||||
anchors.fill: parent
|
||||
color: Config.colors.primaryDark
|
||||
}
|
||||
|
||||
MultiEffect {
|
||||
source: base
|
||||
maskSource: listWrapper
|
||||
maskEnabled: true
|
||||
maskSpreadAtMin: 1
|
||||
maskThresholdMin: 0.5
|
||||
|
||||
x: -parent.x + Config.bar.workspaceMargin
|
||||
implicitWidth: listWrapper.width
|
||||
implicitHeight: listWrapper.height
|
||||
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
}
|
||||
425
modules/bar/popouts/ActiveWindow.qml
Normal file
425
modules/bar/popouts/ActiveWindow.qml
Normal file
|
|
@ -0,0 +1,425 @@
|
|||
import qs.config
|
||||
import qs.custom
|
||||
import qs.services
|
||||
import qs.util
|
||||
import Quickshell
|
||||
import Quickshell.Widgets
|
||||
import Quickshell.Hyprland
|
||||
import Quickshell.Wayland
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
required property PersistentProperties uiState
|
||||
required property Item wrapper
|
||||
required property HyprlandToplevel window
|
||||
|
||||
property HyprlandToplevel toplevel: Hypr.activeToplevel
|
||||
property var screen: QsWindow.window.screen
|
||||
property bool pinned: false
|
||||
|
||||
implicitWidth: childrenRect.width
|
||||
implicitHeight: childrenRect.height
|
||||
|
||||
Component.onCompleted: {
|
||||
if (window) {
|
||||
state = "detail";
|
||||
pinned = true;
|
||||
toplevel = window;
|
||||
}
|
||||
wrapper.window = null;
|
||||
}
|
||||
|
||||
// States
|
||||
|
||||
states: [
|
||||
State {
|
||||
name: "detail"
|
||||
StateChangeScript {
|
||||
script: root.wrapper.persistent = true
|
||||
}
|
||||
ParentChange {
|
||||
target: header
|
||||
parent: infobox
|
||||
}
|
||||
PropertyChanges {
|
||||
infobox { visible: true }
|
||||
preview { visible: true }
|
||||
pin { visible: true }
|
||||
visit { visible: true }
|
||||
del { visible: true }
|
||||
expand { rotation: -90 }
|
||||
title { maximumLineCount: 1 }
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
transitions: Transition {
|
||||
Anim {
|
||||
targets: [infobox, preview]
|
||||
property: "scale"
|
||||
from: 0; to: 1
|
||||
}
|
||||
Anim {
|
||||
target: buttons
|
||||
property: "opacity"
|
||||
from: 0; to: 1
|
||||
duration: Config.anim.durations.large * 2
|
||||
}
|
||||
Anim {
|
||||
targets: header
|
||||
property: "scale"
|
||||
to: 1
|
||||
}
|
||||
}
|
||||
|
||||
// Reveal on window title change
|
||||
// (or close if window is invalid)
|
||||
Anim on opacity {
|
||||
id: reveal
|
||||
from: 0
|
||||
to: 1
|
||||
}
|
||||
|
||||
onToplevelChanged: {
|
||||
root.opacity = 0;
|
||||
if (!toplevel) {
|
||||
root.wrapper.hasCurrent = false;
|
||||
} else if (!root.pinned) {
|
||||
reveal.restart();
|
||||
} else {
|
||||
root.opacity = 1;
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
id: layout
|
||||
|
||||
spacing: 15
|
||||
|
||||
RowLayout {
|
||||
id: header
|
||||
|
||||
spacing: 12
|
||||
|
||||
Binding {
|
||||
when: infobox.visible
|
||||
header {
|
||||
anchors.left: infobox.left
|
||||
anchors.right: infobox.right
|
||||
anchors.top: infobox.top
|
||||
anchors.margins: 12
|
||||
}
|
||||
}
|
||||
|
||||
IconImage {
|
||||
id: icon
|
||||
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
implicitSize: 36
|
||||
source: Icons.getAppIcon(toplevel?.lastIpcObject.class ?? "", "")
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
id: names
|
||||
|
||||
spacing: 0
|
||||
Layout.fillWidth: true
|
||||
Layout.maximumWidth: 400
|
||||
|
||||
CustomText {
|
||||
id: title
|
||||
|
||||
Layout.fillWidth: true
|
||||
text: toplevel?.title ?? ""
|
||||
color: Config.colors.secondary
|
||||
elide: Text.ElideRight
|
||||
wrapMode: Text.WrapAtWordBoundaryOrAnywhere
|
||||
|
||||
Behavior on text {
|
||||
Anim {
|
||||
target: names
|
||||
property: "opacity"
|
||||
from: 0
|
||||
to: 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CustomText {
|
||||
Layout.fillWidth: true
|
||||
text: toplevel?.lastIpcObject.class ?? ""
|
||||
font.pointSize: Config.font.size.small
|
||||
color: Config.colors.tertiary
|
||||
elide: Text.ElideRight
|
||||
|
||||
Behavior on text {
|
||||
Anim {
|
||||
target: names
|
||||
property: "opacity"
|
||||
from: 0
|
||||
to: 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CustomRect {
|
||||
id: infobox
|
||||
visible: false
|
||||
transformOrigin: Item.TopRight
|
||||
|
||||
implicitWidth: 300
|
||||
implicitHeight: 240
|
||||
|
||||
color: Config.colors.container
|
||||
radius: 17
|
||||
|
||||
ColumnLayout {
|
||||
id: infolayout
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.topMargin: 48
|
||||
anchors.margins: 12
|
||||
|
||||
spacing: 3
|
||||
|
||||
|
||||
CustomRect {
|
||||
color: Config.colors.inactive
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 1
|
||||
Layout.topMargin: 8
|
||||
Layout.bottomMargin: 6
|
||||
Layout.leftMargin: 5
|
||||
Layout.rightMargin: 5
|
||||
}
|
||||
|
||||
Detail {
|
||||
icon: "workspaces"
|
||||
text: {
|
||||
for (let i = 0; i < root.uiState.workspaces.count; i++) {
|
||||
if (root.uiState.workspaces.get(i).workspace === root.toplevel?.workspace)
|
||||
return qsTr("Workspace: %1").arg(i + 1)
|
||||
}
|
||||
return qsTr("Workspace unknown")
|
||||
}
|
||||
}
|
||||
|
||||
Detail {
|
||||
icon: "desktop_windows"
|
||||
text: {
|
||||
const mon = root.toplevel?.monitor;
|
||||
mon ? qsTr("Monitor: %1 (%2)").arg(mon.name).arg(mon.id) : qsTr("Monitor: unknown")
|
||||
}
|
||||
}
|
||||
Detail {
|
||||
icon: "location_on"
|
||||
text: qsTr("Address: %1").arg(`0x${root.toplevel?.address}` ?? "unknown")
|
||||
}
|
||||
|
||||
Detail {
|
||||
icon: "location_searching"
|
||||
text: qsTr("Position: %1, %2").arg(root.toplevel?.lastIpcObject.at[0] ?? -1).arg(root.toplevel?.lastIpcObject.at[1] ?? -1)
|
||||
}
|
||||
|
||||
Detail {
|
||||
icon: "resize"
|
||||
text: qsTr("Size: %1 ⨯ %2").arg(root.toplevel?.lastIpcObject.size[0] ?? -1).arg(root.toplevel?.lastIpcObject.size[1] ?? -1)
|
||||
color: Config.colors.tertiary
|
||||
}
|
||||
|
||||
Detail {
|
||||
icon: "account_tree"
|
||||
text: qsTr("Process id: %1").arg(Number(root.toplevel?.lastIpcObject.pid ?? -1).toLocaleString(undefined, "f"))
|
||||
color: Config.colors.primary
|
||||
}
|
||||
|
||||
Detail {
|
||||
icon: "gradient"
|
||||
text: qsTr("Xwayland: %1").arg(root.toplevel?.lastIpcObject.xwayland ? "yes" : "no")
|
||||
}
|
||||
|
||||
Detail {
|
||||
icon: "picture_in_picture_center"
|
||||
text: qsTr("Floating: %1").arg(root.toplevel?.lastIpcObject.floating ? "yes" : "no")
|
||||
color: Config.colors.secondary
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ClippingWrapperRectangle {
|
||||
id: preview
|
||||
visible: false
|
||||
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
|
||||
transformOrigin: Item.TopLeft
|
||||
color: "transparent"
|
||||
radius: 12
|
||||
|
||||
ScreencopyView {
|
||||
captureSource: root.toplevel?.wayland ?? null
|
||||
live: true
|
||||
|
||||
constraintSize.width: 375
|
||||
constraintSize.height: 240
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
id: buttons
|
||||
|
||||
Layout.fillHeight: true
|
||||
width: buttonTopLayout.width
|
||||
|
||||
property real buttonSize: 37
|
||||
|
||||
ColumnLayout {
|
||||
id: buttonTopLayout
|
||||
spacing: 2
|
||||
anchors.top: parent.top
|
||||
|
||||
StateLayer {
|
||||
id: expand
|
||||
|
||||
implicitWidth: buttons.buttonSize
|
||||
implicitHeight: buttons.buttonSize
|
||||
|
||||
function onClicked(): void {
|
||||
if (root.state === "")
|
||||
root.state = "detail";
|
||||
else
|
||||
root.wrapper.hasCurrent = false;
|
||||
}
|
||||
|
||||
MaterialIcon {
|
||||
anchors.centerIn: parent
|
||||
anchors.horizontalCenterOffset: font.pointSize * 0.05
|
||||
|
||||
text: "chevron_right"
|
||||
font.pointSize: Config.font.size.large
|
||||
}
|
||||
}
|
||||
|
||||
StateLayer {
|
||||
id: pin
|
||||
visible: false
|
||||
|
||||
implicitWidth: buttons.buttonSize
|
||||
implicitHeight: buttons.buttonSize
|
||||
|
||||
function onClicked(): void {
|
||||
if (root.pinned) {
|
||||
root.pinned = false;
|
||||
root.toplevel = Qt.binding(() => Hypr.activeToplevel);
|
||||
} else {
|
||||
root.pinned = true;
|
||||
root.toplevel = root.toplevel;
|
||||
}
|
||||
}
|
||||
|
||||
MaterialIcon {
|
||||
anchors.centerIn: parent
|
||||
|
||||
text: "keep"
|
||||
fill: root.pinned
|
||||
font.pointSize: Config.font.size.large - 3
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
id: buttonBottomLayout
|
||||
anchors.bottom: parent.bottom
|
||||
spacing: 2
|
||||
|
||||
StateLayer {
|
||||
id: visit
|
||||
visible: false
|
||||
|
||||
implicitWidth: buttons.buttonSize
|
||||
implicitHeight: buttons.buttonSize
|
||||
|
||||
disabled: Hypr.activeToplevel === root.toplevel
|
||||
function onClicked(): void {
|
||||
if (root.toplevel)
|
||||
Hypr.dispatch(`focuswindow address:0x${root.toplevel.address}`);
|
||||
}
|
||||
|
||||
MaterialIcon {
|
||||
anchors.centerIn: parent
|
||||
|
||||
text: "flip_to_front"
|
||||
color: parent.disabled ? Config.colors.inactive : Config.colors.primary
|
||||
font.pointSize: Config.font.size.large - 3
|
||||
|
||||
Behavior on color {
|
||||
CAnim {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
StateLayer {
|
||||
id: del
|
||||
visible: false
|
||||
|
||||
Layout.bottomMargin: 8
|
||||
color: Config.colors.error
|
||||
|
||||
implicitWidth: buttons.buttonSize
|
||||
implicitHeight: buttons.buttonSize
|
||||
|
||||
function onClicked(): void {
|
||||
if (root.toplevel)
|
||||
Hypr.dispatch(`killwindow address:0x${root.toplevel.address}`);
|
||||
root.wrapper.hasCurrent = false;
|
||||
}
|
||||
|
||||
MaterialIcon {
|
||||
anchors.centerIn: parent
|
||||
|
||||
text: "delete"
|
||||
color: Config.colors.error
|
||||
font.pointSize: Config.font.size.large - 3
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
component Detail: RowLayout {
|
||||
id: detail
|
||||
|
||||
required property string icon
|
||||
required property string text
|
||||
property alias color: icon.color
|
||||
|
||||
Layout.fillWidth: true
|
||||
|
||||
spacing: 7
|
||||
|
||||
MaterialIcon {
|
||||
id: icon
|
||||
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
font.pointSize: Config.font.size.smaller
|
||||
text: detail.icon
|
||||
}
|
||||
|
||||
CustomText {
|
||||
Layout.fillWidth: true
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
|
||||
text: detail.text
|
||||
elide: Text.ElideRight
|
||||
font.family: Config.font.family.mono
|
||||
font.pointSize: Config.font.size.smaller
|
||||
}
|
||||
}
|
||||
}
|
||||
80
modules/bar/popouts/Background.qml
Normal file
80
modules/bar/popouts/Background.qml
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
import qs.config
|
||||
import qs.custom
|
||||
import qs.services
|
||||
import QtQuick
|
||||
import QtQuick.Shapes
|
||||
|
||||
Shape {
|
||||
id: root
|
||||
|
||||
required property Item wrapper
|
||||
readonly property bool invertLeftRounding: wrapper.x <= 2
|
||||
readonly property bool invertRightRounding: wrapper.x + wrapper.width >= wrapper.parent.width - 2
|
||||
readonly property real rounding: Config.border.rounding
|
||||
readonly property bool flatten: wrapper.height < rounding * 2
|
||||
readonly property real roundingY: flatten ? wrapper.height / 2 : rounding
|
||||
property real ilr: invertLeftRounding ? -1 : 1
|
||||
property real irr: invertRightRounding ? -1 : 1
|
||||
|
||||
property real sideRounding: wrapper.y > 0 ? -1 : 1
|
||||
|
||||
ShapePath {
|
||||
startX: -root.rounding * root.sideRounding + (invertRightRounding ? 1 : 0)
|
||||
startY: -1
|
||||
strokeWidth: -1
|
||||
fillColor: Config.colors.bg
|
||||
|
||||
PathArc {
|
||||
relativeX: root.rounding * root.sideRounding
|
||||
relativeY: root.roundingY
|
||||
radiusX: root.rounding
|
||||
radiusY: Math.min(root.rounding, root.wrapper.height)
|
||||
direction: root.sideRounding < 0 ? PathArc.Counterclockwise : PathArc.Clockwise
|
||||
}
|
||||
PathLine {
|
||||
relativeX: 0
|
||||
relativeY: root.wrapper.height - root.roundingY - root.roundingY * root.ilr
|
||||
}
|
||||
PathArc {
|
||||
relativeX: root.rounding
|
||||
relativeY: root.roundingY * root.ilr
|
||||
radiusX: root.rounding
|
||||
radiusY: Math.min(root.rounding, root.wrapper.height)
|
||||
direction: root.ilr < 0 ? PathArc.Clockwise : PathArc.Counterclockwise
|
||||
}
|
||||
PathLine {
|
||||
relativeX: root.wrapper.width - root.rounding * 2
|
||||
relativeY: 0
|
||||
}
|
||||
PathArc {
|
||||
relativeX: root.rounding
|
||||
relativeY: -root.roundingY * root.irr
|
||||
radiusX: root.rounding
|
||||
radiusY: Math.min(root.rounding, root.wrapper.height)
|
||||
direction: root.irr < 0 ? PathArc.Clockwise : PathArc.Counterclockwise
|
||||
}
|
||||
PathLine {
|
||||
relativeX: 0
|
||||
relativeY: -(root.wrapper.height - root.roundingY - root.roundingY * root.irr)
|
||||
}
|
||||
PathArc {
|
||||
relativeX: root.rounding * root.sideRounding
|
||||
relativeY: -root.roundingY
|
||||
radiusX: root.rounding
|
||||
radiusY: Math.min(root.rounding, root.wrapper.height)
|
||||
direction: root.sideRounding < 0 ? PathArc.Counterclockwise : PathArc.Clockwise
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on ilr {
|
||||
Anim {}
|
||||
}
|
||||
|
||||
Behavior on irr {
|
||||
Anim {}
|
||||
}
|
||||
|
||||
Behavior on sideRounding {
|
||||
Anim {}
|
||||
}
|
||||
}
|
||||
351
modules/bar/popouts/Battery.qml
Normal file
351
modules/bar/popouts/Battery.qml
Normal file
|
|
@ -0,0 +1,351 @@
|
|||
pragma ComponentBehavior: Bound
|
||||
|
||||
import qs.config
|
||||
import qs.custom
|
||||
import qs.services
|
||||
import Quickshell.Services.UPower
|
||||
import QtQuick
|
||||
import QtQuick.Shapes
|
||||
import QtQuick.Layouts
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
|
||||
spacing: 4
|
||||
|
||||
readonly property color color: UPower.onBattery && UPower.displayDevice.percentage < 0.15 ?
|
||||
Config.colors.batteryWarning :
|
||||
Config.colors.battery
|
||||
|
||||
Loader {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
|
||||
active: UPower.displayDevice.isLaptopBattery
|
||||
asynchronous: true
|
||||
|
||||
height: active ? (item?.implicitHeight ?? 0) : 0
|
||||
|
||||
sourceComponent: Item {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
|
||||
implicitWidth: meter.width
|
||||
implicitHeight: meter.height + estimate.height + 8
|
||||
|
||||
Shape {
|
||||
id: meter
|
||||
|
||||
preferredRendererType: Shape.CurveRenderer
|
||||
visible: false
|
||||
|
||||
readonly property real size: 96
|
||||
readonly property real padding: 8
|
||||
readonly property real thickness: 8
|
||||
readonly property real angle: 280
|
||||
|
||||
ShapePath {
|
||||
id: path
|
||||
|
||||
fillColor: "transparent"
|
||||
strokeColor: Qt.alpha(root.color, 0.1)
|
||||
strokeWidth: meter.thickness
|
||||
capStyle: ShapePath.RoundCap
|
||||
|
||||
PathAngleArc {
|
||||
centerX: detail.x + detail.width / 2
|
||||
centerY: detail.y + detail.height / 2
|
||||
radiusX: (meter.size + meter.thickness) / 2 + meter.padding
|
||||
radiusY: radiusX
|
||||
startAngle: -90 - meter.angle / 2
|
||||
sweepAngle: meter.angle
|
||||
}
|
||||
|
||||
Behavior on strokeColor {
|
||||
CAnim {}
|
||||
}
|
||||
}
|
||||
|
||||
ShapePath {
|
||||
fillColor: "transparent"
|
||||
strokeColor: root.color
|
||||
strokeWidth: meter.thickness
|
||||
capStyle: ShapePath.RoundCap
|
||||
|
||||
PathAngleArc {
|
||||
centerX: detail.x + detail.width / 2
|
||||
centerY: detail.y + detail.height / 2
|
||||
radiusX: (meter.size + meter.thickness) / 2 + meter.padding
|
||||
radiusY: radiusX
|
||||
startAngle: -90 - meter.angle / 2
|
||||
sweepAngle: meter.angle * UPower.displayDevice.percentage
|
||||
}
|
||||
|
||||
Behavior on strokeColor {
|
||||
CAnim {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
id: detail
|
||||
|
||||
anchors.top: parent.top
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.topMargin: (meter.size + meter.thickness - height) / 2 + meter.padding
|
||||
spacing: -6
|
||||
|
||||
// HACK: Prevent load order issues
|
||||
Component.onCompleted: meter.visible = true;
|
||||
|
||||
CustomText {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
text: Math.round(UPower.displayDevice.percentage * 100) + "%"
|
||||
font.pointSize: Config.font.size.largest
|
||||
}
|
||||
|
||||
CustomText {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.bottomMargin: 10
|
||||
text: UPowerDeviceState.toString(UPower.displayDevice.state)
|
||||
animate: true
|
||||
font.pointSize: Config.font.size.smaller
|
||||
|
||||
height: implicitHeight * 1.4
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
id: estimate
|
||||
|
||||
anchors.top: meter.bottom
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.topMargin: 3
|
||||
spacing: -3
|
||||
|
||||
CustomText {
|
||||
id: estimateTime
|
||||
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
|
||||
text: UPower.onBattery ? Time.formatSeconds(UPower.displayDevice.timeToEmpty) || "--"
|
||||
: Time.formatSeconds(UPower.displayDevice.timeToFull) || "--"
|
||||
animate: (from, to) => from === "--" || to === "--"
|
||||
font.family: Config.font.family.mono
|
||||
font.pointSize: Config.font.size.normal
|
||||
}
|
||||
|
||||
CustomText {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
|
||||
text: UPower.onBattery ? "remaining" : "to full"
|
||||
animate: true
|
||||
font.family: Config.font.family.mono
|
||||
font.pointSize: Config.font.size.small
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
|
||||
active: PowerProfiles.degradationReason !== PerformanceDegradationReason.None
|
||||
asynchronous: true
|
||||
|
||||
height: active ? (item?.implicitHeight ?? 0) : 0
|
||||
|
||||
sourceComponent: CustomRect {
|
||||
implicitWidth: child.implicitWidth + 20
|
||||
implicitHeight: child.implicitHeight + 20
|
||||
|
||||
color: Config.colors.errorBg
|
||||
border.color: Config.colors.error
|
||||
radius: 12
|
||||
|
||||
Column {
|
||||
id: child
|
||||
|
||||
anchors.centerIn: parent
|
||||
|
||||
Row {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
spacing: 7
|
||||
|
||||
MaterialIcon {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.verticalCenterOffset: -font.pointSize / 10
|
||||
|
||||
text: "warning"
|
||||
color: Config.colors.error
|
||||
}
|
||||
|
||||
CustomText {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: qsTr("Performance Degraded")
|
||||
color: Config.colors.error
|
||||
font.family: Config.font.family.mono
|
||||
font.weight: 500
|
||||
}
|
||||
|
||||
MaterialIcon {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.verticalCenterOffset: -font.pointSize / 10
|
||||
|
||||
text: "warning"
|
||||
color: Config.colors.error
|
||||
}
|
||||
}
|
||||
|
||||
CustomText {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
|
||||
text: qsTr("Reason: %1").arg(PerformanceDegradationReason.toString(PowerProfiles.degradationReason))
|
||||
color: Config.colors.secondary
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CustomRect {
|
||||
id: profiles
|
||||
|
||||
Layout.topMargin: 4
|
||||
|
||||
property string current: {
|
||||
const p = PowerProfiles.profile;
|
||||
if (p === PowerProfile.PowerSaver)
|
||||
return saver.icon;
|
||||
if (p === PowerProfile.Performance)
|
||||
return perf.icon;
|
||||
return balance.icon;
|
||||
}
|
||||
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
Layout.leftMargin: 10
|
||||
Layout.rightMargin: 10
|
||||
|
||||
implicitWidth: saver.implicitHeight + balance.implicitHeight + perf.implicitHeight + 60
|
||||
implicitHeight: Math.max(saver.implicitHeight, balance.implicitHeight, perf.implicitHeight) + 8
|
||||
|
||||
color: Config.colors.container
|
||||
radius: 1000
|
||||
|
||||
CustomRect {
|
||||
id: indicator
|
||||
|
||||
color: root.color
|
||||
radius: 1000
|
||||
state: profiles.current
|
||||
|
||||
states: [
|
||||
State {
|
||||
name: saver.icon
|
||||
|
||||
Fill {
|
||||
item: saver
|
||||
}
|
||||
},
|
||||
State {
|
||||
name: balance.icon
|
||||
|
||||
Fill {
|
||||
item: balance
|
||||
}
|
||||
},
|
||||
State {
|
||||
name: perf.icon
|
||||
|
||||
Fill {
|
||||
item: perf
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
transitions: Transition {
|
||||
AnchorAnimation {
|
||||
duration: Config.anim.durations.normal
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: Config.anim.curves.emphasized
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Profile {
|
||||
id: saver
|
||||
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 4
|
||||
|
||||
profile: PowerProfile.PowerSaver
|
||||
icon: "energy_savings_leaf"
|
||||
}
|
||||
|
||||
Profile {
|
||||
id: balance
|
||||
|
||||
anchors.centerIn: parent
|
||||
|
||||
profile: PowerProfile.Balanced
|
||||
icon: "balance"
|
||||
}
|
||||
|
||||
Profile {
|
||||
id: perf
|
||||
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 4
|
||||
|
||||
profile: PowerProfile.Performance
|
||||
icon: "rocket_launch"
|
||||
}
|
||||
}
|
||||
|
||||
CustomText {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
Layout.topMargin: -2
|
||||
text: "Performance: " + PowerProfile.toString(PowerProfiles.profile)
|
||||
animate: true
|
||||
color: Config.colors.secondary
|
||||
font.pointSize: Config.font.size.small
|
||||
font.weight: 500
|
||||
}
|
||||
|
||||
|
||||
component Fill: AnchorChanges {
|
||||
required property Item item
|
||||
|
||||
target: indicator
|
||||
anchors.left: item.left
|
||||
anchors.right: item.right
|
||||
anchors.top: item.top
|
||||
anchors.bottom: item.bottom
|
||||
}
|
||||
|
||||
component Profile: StateLayer {
|
||||
required property string icon
|
||||
required property int profile
|
||||
|
||||
implicitWidth: icon.implicitHeight + 5
|
||||
implicitHeight: icon.implicitHeight + 5
|
||||
|
||||
function onClicked(): void {
|
||||
PowerProfiles.profile = profile;
|
||||
}
|
||||
|
||||
MaterialIcon {
|
||||
id: icon
|
||||
|
||||
anchors.centerIn: parent
|
||||
|
||||
text: parent.icon
|
||||
font.pointSize: Config.font.size.larger
|
||||
color: profiles.current === text ? Config.colors.primaryDark : Config.colors.primary
|
||||
fill: profiles.current === text ? 1 : 0
|
||||
|
||||
Behavior on fill {
|
||||
Anim {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
289
modules/bar/popouts/Calendar.qml
Normal file
289
modules/bar/popouts/Calendar.qml
Normal file
|
|
@ -0,0 +1,289 @@
|
|||
pragma ComponentBehavior: Bound
|
||||
|
||||
import qs.config
|
||||
import qs.custom
|
||||
import qs.services
|
||||
import qs.util
|
||||
import Quickshell
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
|
||||
spacing: 4
|
||||
|
||||
property date currentDate: new Date()
|
||||
property int currentYear: currentDate.getFullYear()
|
||||
property int currentMonth: currentDate.getMonth()
|
||||
|
||||
RowLayout {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
|
||||
MaterialIcon {
|
||||
Layout.bottomMargin: 1
|
||||
text: "calendar_month"
|
||||
color: Config.colors.primary
|
||||
font.pointSize: Config.font.size.large
|
||||
}
|
||||
|
||||
CustomText {
|
||||
text: Time.format("hh:mm:ss")
|
||||
color: Config.colors.primary
|
||||
font.weight: 600
|
||||
font.pointSize: Config.font.size.large
|
||||
font.family: Config.font.family.mono
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: 10
|
||||
Layout.rightMargin: 4
|
||||
spacing: 0
|
||||
|
||||
CustomText {
|
||||
text: Time.format("dddd, MMMM d")
|
||||
font.weight: 600
|
||||
font.pointSize: Config.font.size.normal
|
||||
}
|
||||
|
||||
CustomText {
|
||||
text: Time.format("yyyy-MM-dd")
|
||||
color: Config.colors.tertiary
|
||||
font.pointSize: Config.font.size.smaller
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Calendar grid
|
||||
GridLayout {
|
||||
id: calendarGrid
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.margins: 10
|
||||
rowSpacing: 7
|
||||
columnSpacing: 2
|
||||
|
||||
// Month navigation
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
Layout.bottomMargin: 7
|
||||
Layout.columnSpan: 2
|
||||
|
||||
CustomRect {
|
||||
implicitWidth: implicitHeight
|
||||
implicitHeight: prevIcon.implicitHeight + 8
|
||||
|
||||
radius: 1000
|
||||
color: Config.colors.container
|
||||
|
||||
StateLayer {
|
||||
anchors.fill: parent
|
||||
|
||||
function onClicked(): void {
|
||||
if (root.currentMonth !== 0) {
|
||||
root.currentMonth = root.currentMonth - 1;
|
||||
} else {
|
||||
root.currentMonth = 11;
|
||||
root.currentYear = root.currentYear - 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MaterialIcon {
|
||||
id: prevIcon
|
||||
|
||||
anchors.centerIn: parent
|
||||
text: "chevron_left"
|
||||
color: Config.colors.secondary
|
||||
}
|
||||
}
|
||||
|
||||
CustomText {
|
||||
Layout.fillWidth: true
|
||||
|
||||
readonly property list<string> monthNames:
|
||||
Array.from({ length: 12 }, (_, i) => Qt.locale().monthName(i, Qt.locale().LongFormat))
|
||||
|
||||
text: monthNames[root.currentMonth] + " " + root.currentYear
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
font.weight: 600
|
||||
font.pointSize: Config.font.size.normal
|
||||
}
|
||||
|
||||
CustomRect {
|
||||
implicitWidth: implicitHeight
|
||||
implicitHeight: nextIcon.implicitHeight + 8
|
||||
radius: 1000
|
||||
color: Config.colors.container
|
||||
|
||||
StateLayer {
|
||||
anchors.fill: parent
|
||||
|
||||
function onClicked(): void {
|
||||
if (root.currentMonth !== 11) {
|
||||
root.currentMonth = root.currentMonth + 1;
|
||||
} else {
|
||||
root.currentMonth = 0;
|
||||
root.currentYear = root.currentYear + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MaterialIcon {
|
||||
id: nextIcon
|
||||
anchors.centerIn: parent
|
||||
text: "chevron_right"
|
||||
color: Config.colors.primary
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Day headers
|
||||
DayOfWeekRow {
|
||||
Layout.row: 1
|
||||
Layout.column: 1
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: Config.font.size.largest
|
||||
|
||||
delegate: CustomText {
|
||||
required property var model
|
||||
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
text: model.shortName
|
||||
color: Config.colors.tertiary
|
||||
|
||||
font.pointSize: Config.font.size.small
|
||||
font.weight: 500
|
||||
}
|
||||
}
|
||||
|
||||
CustomText {
|
||||
Layout.row: 1
|
||||
Layout.leftMargin: -2
|
||||
text: "Week"
|
||||
color: Config.colors.tertiary
|
||||
font.pointSize: Config.font.size.small
|
||||
font.weight: 500
|
||||
font.italic: true
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
}
|
||||
|
||||
// ISO week markers
|
||||
WeekNumberColumn {
|
||||
Layout.row: 2
|
||||
Layout.fillHeight: true
|
||||
Layout.rightMargin: 15
|
||||
height: 240
|
||||
|
||||
month: root.currentMonth
|
||||
year: root.currentYear
|
||||
|
||||
delegate: CustomText {
|
||||
required property var model
|
||||
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
text: model.weekNumber
|
||||
color: Config.colors.tertiary
|
||||
|
||||
font.pointSize: Config.font.size.small
|
||||
font.weight: 600
|
||||
font.italic: true
|
||||
}
|
||||
}
|
||||
|
||||
// Calendar days grid
|
||||
MonthGrid {
|
||||
Layout.row: 2
|
||||
Layout.column: 1
|
||||
Layout.columnSpan: 2
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: implicitHeight
|
||||
Layout.margins: 6
|
||||
|
||||
month: root.currentMonth
|
||||
year: root.currentYear
|
||||
spacing: 20
|
||||
|
||||
delegate: Item {
|
||||
id: dayItem
|
||||
required property var model
|
||||
|
||||
implicitWidth: implicitHeight
|
||||
implicitHeight: dayText.implicitHeight + 10
|
||||
|
||||
CustomRect {
|
||||
anchors.centerIn: parent
|
||||
implicitWidth: parent.implicitHeight
|
||||
implicitHeight: parent.implicitHeight
|
||||
radius: 1000
|
||||
color: dayItem.model.today ? Config.colors.calendar : "transparent"
|
||||
|
||||
StateLayer {
|
||||
anchors.fill: parent
|
||||
visible: dayItem.model.month === root.currentMonth
|
||||
function onClicked(): void {}
|
||||
}
|
||||
|
||||
CustomText {
|
||||
id: dayText
|
||||
anchors.centerIn: parent
|
||||
anchors.verticalCenterOffset: 0.5
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
text: Qt.formatDate(dayItem.model.date, "d")
|
||||
color: dayItem.model.today ? Config.colors.primaryDark :
|
||||
dayItem.model.month === root.currentMonth ? Config.colors.primary : Config.colors.inactive
|
||||
|
||||
font.pointSize: Config.font.size.small
|
||||
font.weight: dayItem.model.today ? 600 : 400
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Today button
|
||||
CustomRect {
|
||||
Layout.fillWidth: true
|
||||
implicitHeight: todayBtn.implicitHeight + 10
|
||||
radius: 25
|
||||
color: Config.colors.calendar
|
||||
|
||||
StateLayer {
|
||||
anchors.fill: parent
|
||||
color: Config.colors.secondary
|
||||
|
||||
function onClicked(): void {
|
||||
const today = new Date();
|
||||
root.currentYear = today.getFullYear();
|
||||
root.currentMonth = today.getMonth();
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
id: todayBtn
|
||||
anchors.centerIn: parent
|
||||
spacing: 7
|
||||
|
||||
MaterialIcon {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
text: "today"
|
||||
color: Config.colors.primaryDark
|
||||
font.pointSize: Config.font.size.normal
|
||||
}
|
||||
|
||||
CustomText {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
text: qsTr("Today")
|
||||
color: Config.colors.primaryDark
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
157
modules/bar/popouts/Content.qml
Normal file
157
modules/bar/popouts/Content.qml
Normal file
|
|
@ -0,0 +1,157 @@
|
|||
pragma ComponentBehavior: Bound
|
||||
|
||||
import qs.config
|
||||
import qs.custom
|
||||
import Quickshell
|
||||
import Quickshell.Hyprland
|
||||
import Quickshell.Services.SystemTray
|
||||
import Quickshell.Services.UPower
|
||||
import QtQuick
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
required property PersistentProperties uiState
|
||||
required property Item wrapper
|
||||
required property HyprlandToplevel window
|
||||
|
||||
anchors.centerIn: parent
|
||||
|
||||
implicitWidth: (content.children.find(c => c.shouldBeActive)?.implicitWidth ?? 0) + 30
|
||||
implicitHeight: (content.children.find(c => c.shouldBeActive)?.implicitHeight ?? 0) + 20
|
||||
readonly property color color: content.children.find(c => c.active)?.color ?? "transparent"
|
||||
clip: true
|
||||
|
||||
Item {
|
||||
id: content
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.margins: 15
|
||||
|
||||
Popout {
|
||||
name: "nixos"
|
||||
source: "NixOS.qml"
|
||||
color: Config.colors.nixos
|
||||
}
|
||||
|
||||
Popout {
|
||||
name: "activewindow"
|
||||
|
||||
sourceComponent: ActiveWindow {
|
||||
uiState: root.uiState
|
||||
wrapper: root.wrapper
|
||||
window: root.window
|
||||
}
|
||||
color: Config.colors.activeWindow
|
||||
}
|
||||
|
||||
Popout {
|
||||
name: "calendar"
|
||||
source: "Calendar.qml"
|
||||
color: Config.colors.calendar
|
||||
}
|
||||
|
||||
Popout {
|
||||
name: "network"
|
||||
source: "Network.qml"
|
||||
color: Config.colors.network
|
||||
}
|
||||
|
||||
Popout {
|
||||
name: "idleinhibit"
|
||||
source: "IdleInhibit.qml"
|
||||
color: Config.colors.idle
|
||||
}
|
||||
|
||||
Popout {
|
||||
name: "battery"
|
||||
source: "Battery.qml"
|
||||
color: UPower.displayDevice.isLaptopBattery &&
|
||||
UPower.onBattery && UPower.displayDevice.percentage < 0.15 ?
|
||||
Config.colors.batteryWarning :
|
||||
Config.colors.battery
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: SystemTray.items
|
||||
|
||||
Popout {
|
||||
id: trayMenu
|
||||
|
||||
required property SystemTrayItem modelData
|
||||
required property int index
|
||||
|
||||
anchors.verticalCenterOffset: -5
|
||||
|
||||
name: `traymenu${index}`
|
||||
sourceComponent: trayMenuComp
|
||||
color: Qt.tint(Config.colors.brown, Qt.alpha(Config.colors.yellow, Math.min(index / 8, 1)))
|
||||
|
||||
Connections {
|
||||
target: root.wrapper
|
||||
|
||||
function onHasCurrentChanged(): void {
|
||||
if (root.wrapper.hasCurrent && trayMenu.shouldBeActive) {
|
||||
trayMenu.sourceComponent = null;
|
||||
trayMenu.sourceComponent = trayMenuComp;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: trayMenuComp
|
||||
|
||||
TrayMenu {
|
||||
popouts: root.wrapper
|
||||
trayItem: trayMenu.modelData.menu
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
component Popout: Loader {
|
||||
id: popout
|
||||
|
||||
required property string name
|
||||
property bool shouldBeActive: root.wrapper.currentName === name
|
||||
property color color
|
||||
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.verticalCenterOffset: -3
|
||||
anchors.right: parent.right
|
||||
|
||||
opacity: 0
|
||||
scale: 0.8
|
||||
active: false
|
||||
asynchronous: true
|
||||
|
||||
states: State {
|
||||
name: "active"
|
||||
when: popout.shouldBeActive
|
||||
|
||||
PropertyChanges {
|
||||
popout.active: true
|
||||
popout.opacity: 1
|
||||
popout.scale: 1
|
||||
}
|
||||
}
|
||||
|
||||
transitions: [
|
||||
Transition {
|
||||
from: ""
|
||||
to: "active"
|
||||
|
||||
SequentialAnimation {
|
||||
PropertyAction {
|
||||
target: popout
|
||||
property: "active"
|
||||
}
|
||||
Anim {
|
||||
properties: "opacity,scale"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
48
modules/bar/popouts/IdleInhibit.qml
Normal file
48
modules/bar/popouts/IdleInhibit.qml
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
pragma ComponentBehavior: Bound
|
||||
|
||||
import qs.config
|
||||
import qs.custom
|
||||
import qs.services
|
||||
import qs.util
|
||||
import Quickshell
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
|
||||
spacing: 15
|
||||
|
||||
Toggle {
|
||||
label.text: qsTr("Idle Inhibitor")
|
||||
label.font.weight: 500
|
||||
checked: Idle.inhibit
|
||||
toggle.onToggled: Idle.inhibit = !Idle.inhibit
|
||||
}
|
||||
|
||||
Toggle {
|
||||
label.text: qsTr("Inhibit While Playing Audio")
|
||||
checked: Idle.inhibitPipewire
|
||||
toggle.onToggled: Idle.toggleInhibitPipewire()
|
||||
}
|
||||
|
||||
component Toggle: RowLayout {
|
||||
property alias checked: toggle.checked
|
||||
property alias label: label
|
||||
property alias toggle: toggle
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.rightMargin: 5
|
||||
spacing: 15
|
||||
|
||||
CustomText {
|
||||
id: label
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
CustomSwitch {
|
||||
id: toggle
|
||||
accent: Color.mute(Config.colors.idle, 1.1)
|
||||
}
|
||||
}
|
||||
}
|
||||
259
modules/bar/popouts/Network.qml
Normal file
259
modules/bar/popouts/Network.qml
Normal file
|
|
@ -0,0 +1,259 @@
|
|||
pragma ComponentBehavior: Bound
|
||||
|
||||
import qs.config
|
||||
import qs.custom
|
||||
import qs.services
|
||||
import qs.util
|
||||
import Quickshell
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
|
||||
property string connectingToSsid: ""
|
||||
|
||||
spacing: 5
|
||||
width: 320
|
||||
|
||||
Toggle {
|
||||
label.text: qsTr("Wifi %1".arg(Network.wifiEnabled ? "Enabled" : "Disabled"))
|
||||
label.font.weight: 500
|
||||
checked: Network.wifiEnabled
|
||||
toggle.onToggled: Network.enableWifi(checked)
|
||||
}
|
||||
|
||||
CustomText {
|
||||
Layout.topMargin: 7
|
||||
text: qsTr("%1 networks available").arg(Network.networks.length)
|
||||
color: Config.colors.primary
|
||||
font.pointSize: Config.font.size.small
|
||||
}
|
||||
|
||||
CustomListView {
|
||||
id: list
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: Math.min(contentHeight, 240)
|
||||
spacing: 5
|
||||
clip: true
|
||||
|
||||
CustomScrollBar.vertical: CustomScrollBar {
|
||||
flickable: list
|
||||
}
|
||||
|
||||
model: ScriptModel {
|
||||
values: [...Network.networks].sort((a, b) => {
|
||||
if (a.active !== b.active)
|
||||
return b.active - a.active;
|
||||
return b.strength - a.strength;
|
||||
})
|
||||
}
|
||||
|
||||
delegate: RowLayout {
|
||||
id: networkItem
|
||||
|
||||
required property Network.AccessPoint modelData
|
||||
readonly property bool isConnecting: root.connectingToSsid === modelData.ssid
|
||||
readonly property bool loading: networkItem.isConnecting
|
||||
|
||||
readonly property color iconColor: networkItem.modelData.active ? Config.colors.primary : Config.colors.inactive
|
||||
|
||||
width: list.width - 8
|
||||
spacing: 5
|
||||
|
||||
MaterialIcon {
|
||||
text: Icons.getNetworkIcon(networkItem.modelData.strength)
|
||||
color: iconColor
|
||||
|
||||
MouseArea {
|
||||
width: networkItem.width
|
||||
height: networkItem.height
|
||||
|
||||
onClicked: {
|
||||
if (Network.wifiEnabled && !networkItem.loading) {
|
||||
if (networkItem.modelData.active) {
|
||||
Network.disconnectFromNetwork();
|
||||
} else {
|
||||
root.connectingToSsid = networkItem.modelData.ssid;
|
||||
Network.connectToNetwork(networkItem.modelData.ssid, "");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MaterialIcon {
|
||||
opacity: networkItem.modelData.isSecure ? 1 : 0
|
||||
text: "lock"
|
||||
font.pointSize: Config.font.size.smaller
|
||||
color: iconColor
|
||||
}
|
||||
|
||||
CustomText {
|
||||
Layout.fillWidth: true
|
||||
Layout.rightMargin: 10
|
||||
text: networkItem.modelData.ssid
|
||||
elide: Text.ElideRight
|
||||
font.weight: networkItem.modelData.active ? 500 : 400
|
||||
color: networkItem.modelData.active ? Config.colors.secondary : Config.colors.tertiary
|
||||
}
|
||||
|
||||
CustomBusyIndicator {
|
||||
implicitWidth: implicitHeight
|
||||
implicitHeight: Config.font.size.normal
|
||||
|
||||
running: opacity > 0
|
||||
opacity: networkItem.loading ? 1 : 0
|
||||
|
||||
Behavior on opacity {
|
||||
Anim {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
add: Transition {
|
||||
Anim {
|
||||
property: "opacity"
|
||||
from: 0
|
||||
to: 1
|
||||
}
|
||||
Anim {
|
||||
property: "scale"
|
||||
from: 0.7
|
||||
to: 1
|
||||
}
|
||||
}
|
||||
|
||||
remove: Transition {
|
||||
Anim {
|
||||
property: "opacity"
|
||||
from: 1
|
||||
to: 0
|
||||
}
|
||||
Anim {
|
||||
property: "scale"
|
||||
from: 1
|
||||
to: 0.7
|
||||
}
|
||||
}
|
||||
|
||||
addDisplaced: Transition {
|
||||
Anim {
|
||||
property: "y"
|
||||
duration: Config.anim.durations.small
|
||||
}
|
||||
Anim {
|
||||
properties: "opacity,scale"
|
||||
to: 1
|
||||
}
|
||||
}
|
||||
|
||||
displaced: Transition {
|
||||
Anim {
|
||||
property: "y"
|
||||
}
|
||||
Anim {
|
||||
properties: "opacity,scale"
|
||||
to: 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Rescan button
|
||||
CustomRect {
|
||||
Layout.topMargin: 8
|
||||
Layout.fillWidth: true
|
||||
implicitHeight: rescanBtn.implicitHeight + 10
|
||||
|
||||
radius: 17
|
||||
color: !Network.wifiEnabled ? Config.colors.inactive : Qt.alpha(Config.colors.network, Network.scanning ? 0.8 : 1)
|
||||
|
||||
Behavior on color {
|
||||
CAnim { duration: Config.anim.durations.small }
|
||||
}
|
||||
|
||||
StateLayer {
|
||||
id: layer
|
||||
anchors.fill: parent
|
||||
|
||||
color: Config.colors.primaryDark
|
||||
disabled: Network.scanning || !Network.wifiEnabled
|
||||
|
||||
function onClicked(): void {
|
||||
Network.rescanWifi();
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
id: rescanBtn
|
||||
anchors.centerIn: parent
|
||||
spacing: 7
|
||||
|
||||
property color color: layer.disabled ? Config.colors.bg : Config.colors.primaryDark
|
||||
|
||||
Behavior on color {
|
||||
CAnim { duration: Config.anim.durations.small }
|
||||
}
|
||||
|
||||
MaterialIcon {
|
||||
id: scanIcon
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
animate: true
|
||||
text: Network.scanning ? "refresh" : "wifi_find"
|
||||
color: parent.color
|
||||
|
||||
RotationAnimation on rotation {
|
||||
running: Network.scanning
|
||||
loops: Animation.Infinite
|
||||
from: 0
|
||||
to: 360
|
||||
duration: 1000
|
||||
}
|
||||
}
|
||||
|
||||
CustomText {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
text: Network.scanning ? qsTr("Scanning...") : qsTr("Rescan networks")
|
||||
color: parent.color
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Reset connecting state when network changes
|
||||
Connections {
|
||||
target: Network
|
||||
|
||||
function onActiveChanged(): void {
|
||||
if (Network.active && root.connectingToSsid === Network.active.ssid) {
|
||||
root.connectingToSsid = "";
|
||||
}
|
||||
}
|
||||
|
||||
function onScanningChanged(): void {
|
||||
if (!Network.scanning)
|
||||
scanIcon.rotation = 0;
|
||||
}
|
||||
}
|
||||
|
||||
component Toggle: RowLayout {
|
||||
property alias checked: toggle.checked
|
||||
property alias label: label
|
||||
property alias toggle: toggle
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.rightMargin: 5
|
||||
spacing: 15
|
||||
|
||||
CustomText {
|
||||
id: label
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
CustomSwitch {
|
||||
id: toggle
|
||||
accent: Color.mute(Config.colors.network)
|
||||
}
|
||||
}
|
||||
}
|
||||
161
modules/bar/popouts/NixOS.qml
Normal file
161
modules/bar/popouts/NixOS.qml
Normal file
|
|
@ -0,0 +1,161 @@
|
|||
pragma ComponentBehavior: Bound
|
||||
|
||||
import qs.config
|
||||
import qs.custom
|
||||
import qs.services
|
||||
import qs.util
|
||||
import Quickshell
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
|
||||
spacing: 7
|
||||
width: 340
|
||||
|
||||
function nixosVersionShort(version: string): string {
|
||||
const parts = version.split('.');
|
||||
return `${parts[0]}.${parts[1]}`;
|
||||
}
|
||||
|
||||
Row {
|
||||
spacing: 12
|
||||
|
||||
Image {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
readonly property real size: 72
|
||||
|
||||
source: "root:/assets/nixos-logo.svg"
|
||||
width: size
|
||||
height: size
|
||||
sourceSize.width: size
|
||||
sourceSize.height: size
|
||||
}
|
||||
|
||||
CustomText {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: "NixOS"
|
||||
color: Config.colors.secondary
|
||||
font.pointSize: Config.font.size.largest * 1.3
|
||||
}
|
||||
|
||||
Column {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.verticalCenterOffset: -2
|
||||
|
||||
CustomText {
|
||||
text: "v" + root.nixosVersionShort(NixOS.currentGen?.nixosVersion)
|
||||
font.pointSize: Config.font.size.larger
|
||||
}
|
||||
|
||||
CustomText {
|
||||
text: "Nix " + NixOS.nixVersion
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CustomRect {
|
||||
Layout.topMargin: 5
|
||||
Layout.fillWidth: true
|
||||
height: 180
|
||||
|
||||
radius: 17
|
||||
color: Config.colors.container
|
||||
|
||||
CustomText {
|
||||
id: genText
|
||||
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: 7
|
||||
|
||||
text: "Generations"
|
||||
color: Config.colors.secondary
|
||||
font.pointSize: Config.font.size.normal
|
||||
font.weight: 500
|
||||
}
|
||||
|
||||
CustomListView {
|
||||
id: list
|
||||
|
||||
anchors.top: genText.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.margins: 6
|
||||
anchors.topMargin: 8
|
||||
|
||||
spacing: 6
|
||||
clip: true
|
||||
|
||||
model: ScriptModel {
|
||||
values: [...NixOS.generations]
|
||||
objectProp: "id"
|
||||
}
|
||||
|
||||
CustomScrollBar.vertical: CustomScrollBar {
|
||||
flickable: list
|
||||
}
|
||||
|
||||
|
||||
delegate: CustomRect {
|
||||
required property NixOS.Generation modelData
|
||||
|
||||
width: list.width
|
||||
height: 42
|
||||
|
||||
radius: 12
|
||||
color: modelData.current ?
|
||||
Qt.tint(Config.colors.container, Qt.alpha(Config.colors.nixos, 0.2)) :
|
||||
Config.colors.containerAlt
|
||||
|
||||
Item {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 7
|
||||
anchors.topMargin: 2
|
||||
anchors.bottomMargin: 1
|
||||
|
||||
CustomText {
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.topMargin: 2
|
||||
|
||||
text: `Generation ${modelData.id}`
|
||||
color: modelData.current ? Config.colors.nixos : Config.colors.secondary
|
||||
}
|
||||
|
||||
CustomText {
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 6
|
||||
|
||||
text: modelData.revision !== "Unknown" ? modelData.revision : ""
|
||||
elide: Text.ElideRight
|
||||
font.family: Config.font.family.mono
|
||||
color: modelData.current ? Config.colors.secondary : Config.colors.primary
|
||||
}
|
||||
|
||||
CustomText {
|
||||
anchors.top: parent.top
|
||||
anchors.right: parent.right
|
||||
|
||||
text: `NixOS ${root.nixosVersionShort(modelData.nixosVersion)} 🞄 Linux ${modelData.kernelVersion}`
|
||||
color: modelData.current ? Config.colors.secondary : Config.colors.primary
|
||||
}
|
||||
|
||||
CustomText {
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.right: parent.right
|
||||
|
||||
text: Qt.formatDateTime(modelData.date, "yyyy-MM-dd ddd hh:mm")
|
||||
font.family: Config.font.family.mono
|
||||
color: modelData.current ? Config.colors.secondary : Config.colors.primary
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
231
modules/bar/popouts/TrayMenu.qml
Normal file
231
modules/bar/popouts/TrayMenu.qml
Normal file
|
|
@ -0,0 +1,231 @@
|
|||
pragma ComponentBehavior: Bound
|
||||
|
||||
import qs.config
|
||||
import qs.custom
|
||||
import qs.services
|
||||
import Quickshell
|
||||
import Quickshell.Widgets
|
||||
import Quickshell.Hyprland
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
|
||||
StackView {
|
||||
id: root
|
||||
|
||||
required property Item popouts
|
||||
required property QsMenuHandle trayItem
|
||||
|
||||
implicitWidth: currentItem.implicitWidth
|
||||
implicitHeight: currentItem.implicitHeight
|
||||
|
||||
initialItem: SubMenu {
|
||||
handle: root.trayItem
|
||||
}
|
||||
|
||||
pushEnter: NoAnim {}
|
||||
pushExit: NoAnim {}
|
||||
popEnter: NoAnim {}
|
||||
popExit: NoAnim {}
|
||||
|
||||
component NoAnim: Transition {
|
||||
NumberAnimation {
|
||||
duration: 0
|
||||
}
|
||||
}
|
||||
|
||||
component SubMenu: Column {
|
||||
id: menu
|
||||
|
||||
required property QsMenuHandle handle
|
||||
property bool isSubMenu
|
||||
property bool shown
|
||||
|
||||
spacing: 7
|
||||
|
||||
opacity: shown ? 1 : 0
|
||||
scale: shown ? 1 : 0.8
|
||||
|
||||
Component.onCompleted: shown = true
|
||||
StackView.onActivating: shown = true
|
||||
StackView.onDeactivating: shown = false
|
||||
StackView.onRemoved: destroy()
|
||||
|
||||
Behavior on opacity {
|
||||
Anim {}
|
||||
}
|
||||
|
||||
Behavior on scale {
|
||||
Anim {}
|
||||
}
|
||||
|
||||
QsMenuOpener {
|
||||
id: menuOpener
|
||||
|
||||
menu: menu.handle
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: menuOpener.children
|
||||
|
||||
CustomRect {
|
||||
id: item
|
||||
|
||||
required property QsMenuEntry modelData
|
||||
|
||||
implicitWidth: 200
|
||||
implicitHeight: modelData.isSeparator ? 1 : children.implicitHeight
|
||||
|
||||
radius: 100
|
||||
color: modelData.isSeparator ? Config.colors.inactive : "transparent"
|
||||
|
||||
Loader {
|
||||
id: children
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
|
||||
active: !item.modelData.isSeparator
|
||||
asynchronous: true
|
||||
|
||||
sourceComponent: Item {
|
||||
implicitHeight: label.implicitHeight
|
||||
|
||||
StateLayer {
|
||||
anchors.fill: parent
|
||||
anchors.margins: -2
|
||||
anchors.leftMargin: -7
|
||||
anchors.rightMargin: -7
|
||||
|
||||
radius: item.radius
|
||||
disabled: !item.modelData.enabled
|
||||
|
||||
function onClicked(): void {
|
||||
const entry = item.modelData;
|
||||
if (entry.hasChildren)
|
||||
root.push(subMenuComp.createObject(null, {
|
||||
handle: entry,
|
||||
isSubMenu: true
|
||||
}));
|
||||
else {
|
||||
item.modelData.triggered();
|
||||
root.popouts.hasCurrent = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: icon
|
||||
|
||||
anchors.left: parent.left
|
||||
|
||||
active: item.modelData.icon !== ""
|
||||
asynchronous: true
|
||||
|
||||
sourceComponent: IconImage {
|
||||
implicitSize: label.implicitHeight
|
||||
|
||||
source: item.modelData.icon
|
||||
}
|
||||
}
|
||||
|
||||
CustomText {
|
||||
id: label
|
||||
|
||||
anchors.left: icon.right
|
||||
anchors.leftMargin: icon.active ? 10 : 0
|
||||
|
||||
text: labelMetrics.elidedText
|
||||
color: item.modelData.enabled ? Config.colors.primary : Config.colors.tertiary
|
||||
}
|
||||
|
||||
TextMetrics {
|
||||
id: labelMetrics
|
||||
|
||||
text: item.modelData.text
|
||||
font.pointSize: label.font.pointSize
|
||||
font.family: label.font.family
|
||||
|
||||
elide: Text.ElideRight
|
||||
elideWidth: 200 - (icon.active ? icon.implicitWidth + label.anchors.leftMargin : 0) - (expand.active ? expand.implicitWidth + 12 : 0)
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: expand
|
||||
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.right: parent.right
|
||||
|
||||
active: item.modelData.hasChildren
|
||||
asynchronous: true
|
||||
|
||||
sourceComponent: MaterialIcon {
|
||||
text: "chevron_right"
|
||||
color: item.modelData.enabled ? Config.colors.primary : Config.colors.tertiary
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
active: menu.isSubMenu
|
||||
asynchronous: true
|
||||
|
||||
sourceComponent: Item {
|
||||
implicitWidth: back.implicitWidth
|
||||
implicitHeight: back.implicitHeight + Appearance.spacing.small / 2
|
||||
|
||||
Item {
|
||||
anchors.bottom: parent.bottom
|
||||
implicitWidth: back.implicitWidth
|
||||
implicitHeight: back.implicitHeight
|
||||
|
||||
CustomRect {
|
||||
anchors.fill: parent
|
||||
anchors.margins: -7
|
||||
anchors.leftMargin: -7
|
||||
anchors.rightMargin: -14
|
||||
|
||||
radius: 1000
|
||||
color: Config.colors.container
|
||||
|
||||
StateLayer {
|
||||
anchors.fill: parent
|
||||
radius: parent.radius
|
||||
color: Config.colors.primary
|
||||
|
||||
function onClicked(): void {
|
||||
root.pop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
id: back
|
||||
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
MaterialIcon {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: "chevron_left"
|
||||
color: Config.colors.primary
|
||||
}
|
||||
|
||||
CustomText {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: qsTr("Back")
|
||||
color: Config.colors.primary
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: subMenuComp
|
||||
|
||||
SubMenu {}
|
||||
}
|
||||
}
|
||||
156
modules/bar/popouts/Wrapper.qml
Normal file
156
modules/bar/popouts/Wrapper.qml
Normal file
|
|
@ -0,0 +1,156 @@
|
|||
pragma ComponentBehavior: Bound
|
||||
|
||||
import qs.config
|
||||
import qs.custom
|
||||
import qs.services
|
||||
import Quickshell
|
||||
import Quickshell.Wayland
|
||||
import Quickshell.Hyprland
|
||||
import QtQuick
|
||||
import QtQuick.Effects
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
required property PersistentProperties uiState
|
||||
required property ShellScreen screen
|
||||
|
||||
readonly property real nonAnimWidth: content.implicitWidth
|
||||
readonly property real nonAnimHeight: y > 0 || hasCurrent ? content.implicitHeight : 0
|
||||
|
||||
property string currentName
|
||||
property real currentCenter
|
||||
property bool hasCurrent
|
||||
|
||||
property real currentCenterBounded: Math.min(Math.max(currentCenter, nonAnimWidth / 2),
|
||||
parent.width - nonAnimWidth / 2)
|
||||
x: currentCenterBounded - implicitWidth / 2
|
||||
|
||||
property HyprlandToplevel window
|
||||
|
||||
property bool persistent
|
||||
|
||||
visible: width > 0 && height > 0
|
||||
|
||||
implicitWidth: nonAnimWidth
|
||||
implicitHeight: nonAnimHeight
|
||||
|
||||
Background {
|
||||
id: background
|
||||
visible: false
|
||||
wrapper: root
|
||||
}
|
||||
|
||||
MultiEffect {
|
||||
anchors.fill: background
|
||||
source: background
|
||||
shadowEnabled: true
|
||||
blurMultiplier: 0.3
|
||||
blurMax: 30
|
||||
shadowColor: content.active ? content.item.color : "transparent"
|
||||
|
||||
Behavior on shadowColor {
|
||||
CAnim {}
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
anchors.fill: parent
|
||||
clip: true
|
||||
|
||||
Comp {
|
||||
id: content
|
||||
|
||||
shouldBeActive: root.hasCurrent
|
||||
asynchronous: true
|
||||
anchors.top: parent.top
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
|
||||
sourceComponent: Content {
|
||||
id: content
|
||||
|
||||
wrapper: root
|
||||
uiState: root.uiState
|
||||
window: root.window
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on currentCenterBounded {
|
||||
enabled: root.implicitHeight > 0
|
||||
|
||||
Anim {
|
||||
easing.bezierCurve: Config.anim.curves.emphasized
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on implicitWidth {
|
||||
enabled: root.implicitHeight > 0
|
||||
|
||||
Anim {
|
||||
easing.bezierCurve: Config.anim.curves.emphasized
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on implicitHeight {
|
||||
Anim {
|
||||
easing.bezierCurve: Config.anim.curves.emphasized
|
||||
}
|
||||
}
|
||||
|
||||
component Comp: Loader {
|
||||
id: comp
|
||||
|
||||
property bool shouldBeActive
|
||||
|
||||
asynchronous: true
|
||||
active: false
|
||||
opacity: 0
|
||||
|
||||
states: State {
|
||||
name: "active"
|
||||
when: comp.shouldBeActive
|
||||
|
||||
PropertyChanges {
|
||||
comp.opacity: 1
|
||||
comp.active: true
|
||||
}
|
||||
}
|
||||
|
||||
transitions: [
|
||||
Transition {
|
||||
from: ""
|
||||
to: "active"
|
||||
|
||||
SequentialAnimation {
|
||||
PropertyAction {
|
||||
property: "active"
|
||||
}
|
||||
Anim {
|
||||
property: "opacity"
|
||||
easing.bezierCurve: Config.anim.curves.standard
|
||||
}
|
||||
}
|
||||
},
|
||||
Transition {
|
||||
from: "active"
|
||||
to: ""
|
||||
|
||||
SequentialAnimation {
|
||||
Anim {
|
||||
property: "opacity"
|
||||
easing.bezierCurve: Config.anim.curves.standard
|
||||
}
|
||||
PropertyAction {
|
||||
property: "active"
|
||||
}
|
||||
PropertyAction {
|
||||
target: root
|
||||
property: "persistent"
|
||||
value: false
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue