quickshell lets gooo
This commit is contained in:
174
dots/quickshell/modules/bar/Bar.qml
Normal file
174
dots/quickshell/modules/bar/Bar.qml
Normal file
@@ -0,0 +1,174 @@
|
||||
import "root:/widgets"
|
||||
import "root:/services"
|
||||
import "root:/config"
|
||||
import "root:/modules/bar/popouts" as BarPopouts
|
||||
import "components"
|
||||
import "components/workspaces"
|
||||
import Quickshell
|
||||
import QtQuick
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
required property ShellScreen screen
|
||||
required property BarPopouts.Wrapper popouts
|
||||
|
||||
function checkPopout(y: real): void {
|
||||
const spacing = Appearance.spacing.small;
|
||||
const aw = activeWindow.child;
|
||||
const awy = activeWindow.y + aw.y;
|
||||
|
||||
const ty = tray.y;
|
||||
const th = tray.implicitHeight;
|
||||
const trayItems = tray.items;
|
||||
|
||||
const n = statusIconsInner.network;
|
||||
const ny = statusIcons.y + statusIconsInner.y + n.y - spacing / 2;
|
||||
|
||||
const bls = statusIcons.y + statusIconsInner.y + statusIconsInner.bs - spacing / 2;
|
||||
const ble = statusIcons.y + statusIconsInner.y + statusIconsInner.be + spacing / 2;
|
||||
|
||||
const b = statusIconsInner.battery;
|
||||
const by = statusIcons.y + statusIconsInner.y + b.y - spacing / 2;
|
||||
|
||||
if (y >= awy && y <= awy + aw.implicitHeight) {
|
||||
popouts.currentName = "activewindow";
|
||||
popouts.currentCenter = Qt.binding(() => activeWindow.y + aw.y + aw.implicitHeight / 2);
|
||||
popouts.hasCurrent = true;
|
||||
} else if (y > ty && y < ty + th) {
|
||||
const index = Math.floor(((y - ty) / th) * trayItems.count);
|
||||
const item = trayItems.itemAt(index);
|
||||
|
||||
popouts.currentName = `traymenu${index}`;
|
||||
popouts.currentCenter = Qt.binding(() => tray.y + item.y + item.implicitHeight / 2);
|
||||
popouts.hasCurrent = true;
|
||||
} else if (y >= ny && y <= ny + n.implicitHeight + spacing) {
|
||||
popouts.currentName = "network";
|
||||
popouts.currentCenter = Qt.binding(() => statusIcons.y + statusIconsInner.y + n.y + n.implicitHeight / 2);
|
||||
popouts.hasCurrent = true;
|
||||
} else if (y >= bls && y <= ble) {
|
||||
popouts.currentName = "bluetooth";
|
||||
popouts.currentCenter = Qt.binding(() => statusIcons.y + statusIconsInner.y + statusIconsInner.bs + (statusIconsInner.be - statusIconsInner.bs) / 2);
|
||||
popouts.hasCurrent = true;
|
||||
} else if (y >= by && y <= by + b.implicitHeight + spacing) {
|
||||
popouts.currentName = "battery";
|
||||
popouts.currentCenter = Qt.binding(() => statusIcons.y + statusIconsInner.y + b.y + b.implicitHeight / 2);
|
||||
popouts.hasCurrent = true;
|
||||
} else {
|
||||
popouts.hasCurrent = false;
|
||||
}
|
||||
}
|
||||
|
||||
anchors.top: parent.top
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.left: parent.left
|
||||
|
||||
implicitWidth: child.implicitWidth + Config.border.thickness * 2
|
||||
|
||||
Item {
|
||||
id: child
|
||||
|
||||
anchors.top: parent.top
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
|
||||
implicitWidth: Math.max(osIcon.implicitWidth, workspaces.implicitWidth, activeWindow.implicitWidth, tray.implicitWidth, clock.implicitWidth, statusIcons.implicitWidth, power.implicitWidth)
|
||||
|
||||
OsIcon {
|
||||
id: osIcon
|
||||
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: Appearance.padding.large
|
||||
}
|
||||
|
||||
StyledRect {
|
||||
id: workspaces
|
||||
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.top: osIcon.bottom
|
||||
anchors.topMargin: Appearance.spacing.normal
|
||||
|
||||
radius: Appearance.rounding.full
|
||||
color: Colours.palette.m3surfaceContainer
|
||||
|
||||
implicitWidth: workspacesInner.implicitWidth + Appearance.padding.small * 2
|
||||
implicitHeight: workspacesInner.implicitHeight + Appearance.padding.small * 2
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
anchors.leftMargin: -Config.border.thickness
|
||||
anchors.rightMargin: -Config.border.thickness
|
||||
|
||||
onWheel: event => {
|
||||
const activeWs = Hyprland.activeClient?.workspace?.name;
|
||||
if (activeWs?.startsWith("special:"))
|
||||
Hyprland.dispatch(`togglespecialworkspace ${activeWs.slice(8)}`);
|
||||
else if (event.angleDelta.y < 0 || Hyprland.activeWsId > 1)
|
||||
Hyprland.dispatch(`workspace r${event.angleDelta.y > 0 ? "-" : "+"}1`);
|
||||
}
|
||||
}
|
||||
|
||||
Workspaces {
|
||||
id: workspacesInner
|
||||
|
||||
anchors.centerIn: parent
|
||||
}
|
||||
}
|
||||
|
||||
ActiveWindow {
|
||||
id: activeWindow
|
||||
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.top: workspaces.bottom
|
||||
anchors.bottom: tray.top
|
||||
anchors.margins: Appearance.spacing.large
|
||||
|
||||
monitor: Brightness.getMonitorForScreen(root.screen)
|
||||
}
|
||||
|
||||
Tray {
|
||||
id: tray
|
||||
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.bottom: clock.top
|
||||
anchors.bottomMargin: Appearance.spacing.larger
|
||||
}
|
||||
|
||||
Clock {
|
||||
id: clock
|
||||
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.bottom: statusIcons.top
|
||||
anchors.bottomMargin: Appearance.spacing.normal
|
||||
}
|
||||
|
||||
StyledRect {
|
||||
id: statusIcons
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: power.top
|
||||
anchors.bottomMargin: Appearance.spacing.normal
|
||||
|
||||
radius: Appearance.rounding.full
|
||||
color: Colours.palette.m3surfaceContainer
|
||||
|
||||
implicitHeight: statusIconsInner.implicitHeight + Appearance.padding.normal * 2
|
||||
|
||||
StatusIcons {
|
||||
id: statusIconsInner
|
||||
|
||||
anchors.centerIn: parent
|
||||
}
|
||||
}
|
||||
|
||||
Power {
|
||||
id: power
|
||||
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.bottomMargin: Appearance.padding.large
|
||||
}
|
||||
}
|
||||
}
|
||||
140
dots/quickshell/modules/bar/components/ActiveWindow.qml
Normal file
140
dots/quickshell/modules/bar/components/ActiveWindow.qml
Normal file
@@ -0,0 +1,140 @@
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import "root:/widgets"
|
||||
import "root:/services"
|
||||
import "root:/utils"
|
||||
import "root:/config"
|
||||
import QtQuick
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
required property Brightness.Monitor monitor
|
||||
property color colour: Colours.palette.m3primary
|
||||
readonly property Item child: child
|
||||
|
||||
implicitWidth: child.implicitWidth
|
||||
implicitHeight: child.implicitHeight
|
||||
|
||||
MouseArea {
|
||||
anchors.top: parent.top
|
||||
anchors.bottom: child.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
|
||||
onWheel: event => {
|
||||
if (event.angleDelta.y > 0)
|
||||
Audio.setVolume(Audio.volume + 0.1);
|
||||
else if (event.angleDelta.y < 0)
|
||||
Audio.setVolume(Audio.volume - 0.1);
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.top: child.bottom
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
|
||||
onWheel: event => {
|
||||
const monitor = root.monitor;
|
||||
if (event.angleDelta.y > 0)
|
||||
monitor.setBrightness(monitor.brightness + 0.1);
|
||||
else if (event.angleDelta.y < 0)
|
||||
monitor.setBrightness(monitor.brightness - 0.1);
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
id: child
|
||||
|
||||
property Item current: text1
|
||||
|
||||
anchors.centerIn: parent
|
||||
|
||||
clip: true
|
||||
implicitWidth: Math.max(icon.implicitWidth, current.implicitHeight)
|
||||
implicitHeight: icon.implicitHeight + current.implicitWidth + current.anchors.topMargin
|
||||
|
||||
MaterialIcon {
|
||||
id: icon
|
||||
|
||||
animate: true
|
||||
text: Icons.getAppCategoryIcon(Hyprland.activeClient?.wmClass, "desktop_windows")
|
||||
color: root.colour
|
||||
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
|
||||
Title {
|
||||
id: text1
|
||||
}
|
||||
|
||||
Title {
|
||||
id: text2
|
||||
}
|
||||
|
||||
TextMetrics {
|
||||
id: metrics
|
||||
|
||||
text: Hyprland.activeClient?.title ?? qsTr("Desktop")
|
||||
font.pointSize: Appearance.font.size.smaller
|
||||
font.family: Appearance.font.family.mono
|
||||
elide: Qt.ElideRight
|
||||
elideWidth: root.height - icon.height
|
||||
|
||||
onTextChanged: {
|
||||
const next = child.current === text1 ? text2 : text1;
|
||||
next.text = elidedText;
|
||||
child.current = next;
|
||||
}
|
||||
onElideWidthChanged: child.current.text = elidedText
|
||||
}
|
||||
|
||||
Behavior on implicitWidth {
|
||||
NumberAnimation {
|
||||
duration: Appearance.anim.durations.normal
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: Appearance.anim.curves.emphasized
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on implicitHeight {
|
||||
NumberAnimation {
|
||||
duration: Appearance.anim.durations.normal
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: Appearance.anim.curves.emphasized
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
component Title: StyledText {
|
||||
id: text
|
||||
|
||||
anchors.horizontalCenter: icon.horizontalCenter
|
||||
anchors.top: icon.bottom
|
||||
anchors.topMargin: Appearance.spacing.small
|
||||
|
||||
font.pointSize: metrics.font.pointSize
|
||||
font.family: metrics.font.family
|
||||
color: root.colour
|
||||
opacity: child.current === this ? 1 : 0
|
||||
|
||||
transform: Rotation {
|
||||
angle: 90
|
||||
origin.x: text.implicitHeight / 2
|
||||
origin.y: text.implicitHeight / 2
|
||||
}
|
||||
|
||||
width: implicitHeight
|
||||
height: implicitWidth
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: Appearance.anim.durations.normal
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: Appearance.anim.curves.standard
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
33
dots/quickshell/modules/bar/components/Clock.qml
Normal file
33
dots/quickshell/modules/bar/components/Clock.qml
Normal file
@@ -0,0 +1,33 @@
|
||||
import "root:/widgets"
|
||||
import "root:/services"
|
||||
import "root:/config"
|
||||
import QtQuick
|
||||
|
||||
Column {
|
||||
id: root
|
||||
|
||||
property color colour: Colours.palette.m3tertiary
|
||||
|
||||
spacing: Appearance.spacing.small
|
||||
|
||||
MaterialIcon {
|
||||
id: icon
|
||||
|
||||
text: "calendar_month"
|
||||
color: root.colour
|
||||
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
|
||||
StyledText {
|
||||
id: text
|
||||
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
|
||||
horizontalAlignment: StyledText.AlignHCenter
|
||||
text: Time.format("hh\nmm")
|
||||
font.pointSize: Appearance.font.size.smaller
|
||||
font.family: Appearance.font.family.mono
|
||||
color: root.colour
|
||||
}
|
||||
}
|
||||
11
dots/quickshell/modules/bar/components/OsIcon.qml
Normal file
11
dots/quickshell/modules/bar/components/OsIcon.qml
Normal file
@@ -0,0 +1,11 @@
|
||||
import "root:/widgets"
|
||||
import "root:/services"
|
||||
import "root:/utils"
|
||||
import "root:/config"
|
||||
|
||||
StyledText {
|
||||
text: Icons.osIcon
|
||||
font.pointSize: Appearance.font.size.smaller
|
||||
font.family: Appearance.font.family.mono
|
||||
color: Colours.palette.m3tertiary
|
||||
}
|
||||
27
dots/quickshell/modules/bar/components/Power.qml
Normal file
27
dots/quickshell/modules/bar/components/Power.qml
Normal file
@@ -0,0 +1,27 @@
|
||||
import "root:/widgets"
|
||||
import "root:/services"
|
||||
import "root:/config"
|
||||
import Quickshell
|
||||
|
||||
MaterialIcon {
|
||||
text: "power_settings_new"
|
||||
color: Colours.palette.m3error
|
||||
font.bold: true
|
||||
font.pointSize: Appearance.font.size.normal
|
||||
|
||||
StateLayer {
|
||||
anchors.fill: undefined
|
||||
anchors.centerIn: parent
|
||||
anchors.horizontalCenterOffset: 1
|
||||
|
||||
implicitWidth: parent.implicitHeight + Appearance.padding.small * 2
|
||||
implicitHeight: implicitWidth
|
||||
|
||||
radius: Appearance.rounding.full
|
||||
|
||||
function onClicked(): void {
|
||||
const v = Visibilities.screens[QsWindow.window.screen];
|
||||
v.session = !v.session;
|
||||
}
|
||||
}
|
||||
}
|
||||
114
dots/quickshell/modules/bar/components/StatusIcons.qml
Normal file
114
dots/quickshell/modules/bar/components/StatusIcons.qml
Normal file
@@ -0,0 +1,114 @@
|
||||
import "root:/widgets"
|
||||
import "root:/services"
|
||||
import "root:/utils"
|
||||
import "root:/config"
|
||||
import Quickshell
|
||||
import Quickshell.Services.UPower
|
||||
import QtQuick
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
property color colour: Colours.palette.m3secondary
|
||||
|
||||
readonly property Item network: network
|
||||
readonly property real bs: bluetooth.y
|
||||
readonly property real be: repeater.count > 0 ? devices.y + devices.implicitHeight : bluetooth.y + bluetooth.implicitHeight
|
||||
readonly property Item battery: battery
|
||||
|
||||
clip: true
|
||||
implicitWidth: Math.max(network.implicitWidth, bluetooth.implicitWidth, devices.implicitWidth, battery.implicitWidth)
|
||||
implicitHeight: network.implicitHeight + bluetooth.implicitHeight + bluetooth.anchors.topMargin + (repeater.count > 0 ? devices.implicitHeight + devices.anchors.topMargin : 0) + battery.implicitHeight + battery.anchors.topMargin
|
||||
|
||||
MaterialIcon {
|
||||
id: network
|
||||
|
||||
animate: true
|
||||
text: Network.active ? Icons.getNetworkIcon(Network.active.strength ?? 0) : "wifi_off"
|
||||
color: root.colour
|
||||
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
|
||||
MaterialIcon {
|
||||
id: bluetooth
|
||||
|
||||
anchors.horizontalCenter: network.horizontalCenter
|
||||
anchors.top: network.bottom
|
||||
anchors.topMargin: Appearance.spacing.small
|
||||
|
||||
animate: true
|
||||
text: Bluetooth.powered ? "bluetooth" : "bluetooth_disabled"
|
||||
color: root.colour
|
||||
}
|
||||
|
||||
Column {
|
||||
id: devices
|
||||
|
||||
anchors.horizontalCenter: bluetooth.horizontalCenter
|
||||
anchors.top: bluetooth.bottom
|
||||
anchors.topMargin: Appearance.spacing.small
|
||||
|
||||
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)
|
||||
color: root.colour
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MaterialIcon {
|
||||
id: battery
|
||||
|
||||
anchors.horizontalCenter: devices.horizontalCenter
|
||||
anchors.top: repeater.count > 0 ? devices.bottom : bluetooth.bottom
|
||||
anchors.topMargin: Appearance.spacing.small
|
||||
|
||||
animate: true
|
||||
text: {
|
||||
if (!UPower.displayDevice.isLaptopBattery) {
|
||||
if (PowerProfiles.profile === PowerProfile.PowerSaver)
|
||||
return "energy_savings_leaf";
|
||||
if (PowerProfiles.profile === PowerProfile.Performance)
|
||||
return "rocket_launch";
|
||||
return "balance";
|
||||
}
|
||||
|
||||
const perc = UPower.displayDevice.percentage;
|
||||
const charging = !UPower.onBattery;
|
||||
if (perc === 1)
|
||||
return charging ? "battery_charging_full" : "battery_full";
|
||||
let level = Math.floor(perc * 7);
|
||||
if (charging && (level === 4 || level === 1))
|
||||
level--;
|
||||
return charging ? `battery_charging_${(level + 3) * 10}` : `battery_${level}_bar`;
|
||||
}
|
||||
color: !UPower.onBattery || UPower.displayDevice.percentage > 0.2 ? root.colour : Colours.palette.m3error
|
||||
fill: 1
|
||||
}
|
||||
|
||||
Behavior on implicitWidth {
|
||||
NumberAnimation {
|
||||
duration: Appearance.anim.durations.normal
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: Appearance.anim.curves.emphasized
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on implicitHeight {
|
||||
NumberAnimation {
|
||||
duration: Appearance.anim.durations.normal
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: Appearance.anim.curves.emphasized
|
||||
}
|
||||
}
|
||||
}
|
||||
56
dots/quickshell/modules/bar/components/Tray.qml
Normal file
56
dots/quickshell/modules/bar/components/Tray.qml
Normal file
@@ -0,0 +1,56 @@
|
||||
import "root:/config"
|
||||
import Quickshell.Services.SystemTray
|
||||
import QtQuick
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
readonly property Repeater items: items
|
||||
|
||||
clip: true
|
||||
visible: width > 0 && height > 0 // To avoid warnings about being visible with no size
|
||||
|
||||
implicitWidth: layout.implicitWidth
|
||||
implicitHeight: layout.implicitHeight
|
||||
|
||||
Column {
|
||||
id: layout
|
||||
|
||||
spacing: Appearance.spacing.small
|
||||
|
||||
add: Transition {
|
||||
NumberAnimation {
|
||||
properties: "scale"
|
||||
from: 0
|
||||
to: 1
|
||||
duration: Appearance.anim.durations.normal
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: Appearance.anim.curves.standardDecel
|
||||
}
|
||||
}
|
||||
|
||||
Repeater {
|
||||
id: items
|
||||
|
||||
model: SystemTray.items
|
||||
|
||||
TrayItem {}
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on implicitWidth {
|
||||
NumberAnimation {
|
||||
duration: Appearance.anim.durations.normal
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: Appearance.anim.curves.emphasized
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on implicitHeight {
|
||||
NumberAnimation {
|
||||
duration: Appearance.anim.durations.normal
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: Appearance.anim.curves.emphasized
|
||||
}
|
||||
}
|
||||
}
|
||||
48
dots/quickshell/modules/bar/components/TrayItem.qml
Normal file
48
dots/quickshell/modules/bar/components/TrayItem.qml
Normal file
@@ -0,0 +1,48 @@
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import "root:/widgets"
|
||||
import "root:/config"
|
||||
import Quickshell
|
||||
import Quickshell.Widgets
|
||||
import Quickshell.Services.SystemTray
|
||||
import QtQuick
|
||||
|
||||
MouseArea {
|
||||
id: root
|
||||
|
||||
required property SystemTrayItem modelData
|
||||
|
||||
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
||||
implicitWidth: Appearance.font.size.small * 2
|
||||
implicitHeight: Appearance.font.size.small * 2
|
||||
|
||||
onClicked: event => {
|
||||
if (event.button === Qt.LeftButton)
|
||||
modelData.activate();
|
||||
else if (modelData.hasMenu)
|
||||
menu.open();
|
||||
}
|
||||
|
||||
// TODO custom menu
|
||||
QsMenuAnchor {
|
||||
id: menu
|
||||
|
||||
menu: root.modelData.menu
|
||||
anchor.window: this.QsWindow.window
|
||||
}
|
||||
|
||||
IconImage {
|
||||
id: icon
|
||||
|
||||
source: {
|
||||
let icon = root.modelData.icon;
|
||||
if (icon.includes("?path=")) {
|
||||
const [name, path] = icon.split("?path=");
|
||||
icon = `file://${path}/${name.slice(name.lastIndexOf("/") + 1)}`;
|
||||
}
|
||||
return icon;
|
||||
}
|
||||
asynchronous: true
|
||||
anchors.fill: parent
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
import "root:/widgets"
|
||||
import "root:/services"
|
||||
import "root:/config"
|
||||
import QtQuick
|
||||
import QtQuick.Effects
|
||||
|
||||
StyledRect {
|
||||
id: root
|
||||
|
||||
required property list<Workspace> workspaces
|
||||
required property Item mask
|
||||
required property real maskWidth
|
||||
required property real maskHeight
|
||||
required property int groupOffset
|
||||
|
||||
readonly property int currentWsIdx: Hyprland.activeWsId - 1 - groupOffset
|
||||
property real leading: getWsY(currentWsIdx)
|
||||
property real trailing: getWsY(currentWsIdx)
|
||||
property real currentSize: workspaces[currentWsIdx]?.size ?? 0
|
||||
property real offset: Math.min(leading, trailing)
|
||||
property real size: {
|
||||
const s = Math.abs(leading - trailing) + currentSize;
|
||||
if (Config.bar.workspaces.activeTrail && lastWs > currentWsIdx)
|
||||
return Math.min(getWsY(lastWs) + (workspaces[lastWs]?.size ?? 0) - offset, s);
|
||||
return s;
|
||||
}
|
||||
|
||||
property int cWs
|
||||
property int lastWs
|
||||
|
||||
function getWsY(idx: int): real {
|
||||
let y = 0;
|
||||
for (let i = 0; i < idx; i++)
|
||||
y += workspaces[i]?.size ?? 0;
|
||||
return y;
|
||||
}
|
||||
|
||||
onCurrentWsIdxChanged: {
|
||||
lastWs = cWs;
|
||||
cWs = currentWsIdx;
|
||||
}
|
||||
|
||||
clip: true
|
||||
x: 1
|
||||
y: offset + 1
|
||||
implicitWidth: Config.bar.sizes.innerHeight - 2
|
||||
implicitHeight: size - 2
|
||||
radius: Config.bar.workspaces.rounded ? Appearance.rounding.full : 0
|
||||
color: Colours.palette.m3primary
|
||||
|
||||
StyledRect {
|
||||
id: base
|
||||
|
||||
visible: false
|
||||
anchors.fill: parent
|
||||
color: Colours.palette.m3onPrimary
|
||||
}
|
||||
|
||||
MultiEffect {
|
||||
source: base
|
||||
maskSource: root.mask
|
||||
maskEnabled: true
|
||||
maskSpreadAtMin: 1
|
||||
maskThresholdMin: 0.5
|
||||
|
||||
x: 0
|
||||
y: -parent.offset
|
||||
implicitWidth: root.maskWidth
|
||||
implicitHeight: root.maskHeight
|
||||
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
|
||||
Behavior on leading {
|
||||
enabled: Config.bar.workspaces.activeTrail
|
||||
|
||||
Anim {}
|
||||
}
|
||||
|
||||
Behavior on trailing {
|
||||
enabled: Config.bar.workspaces.activeTrail
|
||||
|
||||
Anim {
|
||||
duration: Appearance.anim.durations.normal * 2
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on currentSize {
|
||||
enabled: Config.bar.workspaces.activeTrail
|
||||
|
||||
Anim {}
|
||||
}
|
||||
|
||||
Behavior on offset {
|
||||
enabled: !Config.bar.workspaces.activeTrail
|
||||
|
||||
Anim {}
|
||||
}
|
||||
|
||||
Behavior on size {
|
||||
enabled: !Config.bar.workspaces.activeTrail
|
||||
|
||||
Anim {}
|
||||
}
|
||||
|
||||
component Anim: NumberAnimation {
|
||||
duration: Appearance.anim.durations.normal
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: Appearance.anim.curves.emphasized
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import "root:/widgets"
|
||||
import "root:/services"
|
||||
import "root:/config"
|
||||
import Quickshell
|
||||
import QtQuick
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
required property list<Workspace> workspaces
|
||||
required property var occupied
|
||||
required property int groupOffset
|
||||
|
||||
property list<var> pills: []
|
||||
|
||||
onOccupiedChanged: {
|
||||
let count = 0;
|
||||
const start = groupOffset;
|
||||
const end = start + Config.bar.workspaces.shown;
|
||||
for (const [ws, occ] of Object.entries(occupied)) {
|
||||
if (ws > start && ws <= end && occ) {
|
||||
if (!occupied[ws - 1]) {
|
||||
if (pills[count])
|
||||
pills[count].start = ws;
|
||||
else
|
||||
pills.push(pillComp.createObject(root, {
|
||||
start: ws
|
||||
}));
|
||||
count++;
|
||||
}
|
||||
if (!occupied[ws + 1])
|
||||
pills[count - 1].end = ws;
|
||||
}
|
||||
}
|
||||
if (pills.length > count)
|
||||
pills.splice(count, pills.length - count).forEach(p => p.destroy());
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: ScriptModel {
|
||||
values: root.pills.filter(p => p)
|
||||
}
|
||||
|
||||
StyledRect {
|
||||
id: rect
|
||||
|
||||
required property var modelData
|
||||
|
||||
readonly property Workspace start: root.workspaces[modelData.start - 1 - root.groupOffset] ?? null
|
||||
readonly property Workspace end: root.workspaces[modelData.end - 1 - root.groupOffset] ?? null
|
||||
|
||||
color: Colours.alpha(Colours.palette.m3surfaceContainerHigh, true)
|
||||
radius: Config.bar.workspaces.rounded ? Appearance.rounding.full : 0
|
||||
|
||||
x: start?.x ?? 0
|
||||
y: start?.y ?? 0
|
||||
implicitWidth: Config.bar.sizes.innerHeight
|
||||
implicitHeight: end?.y + end?.height - start?.y
|
||||
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
|
||||
scale: 0
|
||||
Component.onCompleted: scale = 1
|
||||
|
||||
Behavior on scale {
|
||||
Anim {
|
||||
easing.bezierCurve: Appearance.anim.curves.standardDecel
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on x {
|
||||
Anim {}
|
||||
}
|
||||
|
||||
Behavior on y {
|
||||
Anim {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
component Anim: NumberAnimation {
|
||||
duration: Appearance.anim.durations.normal
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: Appearance.anim.curves.standard
|
||||
}
|
||||
|
||||
component Pill: QtObject {
|
||||
property int start
|
||||
property int end
|
||||
}
|
||||
|
||||
Component {
|
||||
id: pillComp
|
||||
|
||||
Pill {}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
import "root:/widgets"
|
||||
import "root:/services"
|
||||
import "root:/utils"
|
||||
import "root:/config"
|
||||
import Quickshell
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
required property int index
|
||||
required property var occupied
|
||||
required property int groupOffset
|
||||
|
||||
readonly property bool isWorkspace: true // Flag for finding workspace children
|
||||
// Unanimated prop for others to use as reference
|
||||
readonly property real size: childrenRect.height + (hasWindows ? Appearance.padding.normal : 0)
|
||||
|
||||
readonly property int ws: groupOffset + index + 1
|
||||
readonly property bool isOccupied: occupied[ws] ?? false
|
||||
readonly property bool hasWindows: isOccupied && Config.bar.workspaces.showWindows
|
||||
|
||||
Layout.preferredWidth: childrenRect.width
|
||||
Layout.preferredHeight: size
|
||||
|
||||
StyledText {
|
||||
id: indicator
|
||||
|
||||
readonly property string label: Config.bar.workspaces.label || root.ws
|
||||
readonly property string occupiedLabel: Config.bar.workspaces.occupiedLabel || label
|
||||
readonly property string activeLabel: Config.bar.workspaces.activeLabel || (root.isOccupied ? occupiedLabel : label)
|
||||
|
||||
animate: true
|
||||
text: Hyprland.activeWsId === root.ws ? activeLabel : root.isOccupied ? occupiedLabel : label
|
||||
color: Config.bar.workspaces.occupiedBg || root.isOccupied || Hyprland.activeWsId === root.ws ? Colours.palette.m3onSurface : Colours.palette.m3outlineVariant
|
||||
horizontalAlignment: StyledText.AlignHCenter
|
||||
verticalAlignment: StyledText.AlignVCenter
|
||||
|
||||
width: Config.bar.sizes.innerHeight
|
||||
height: Config.bar.sizes.innerHeight
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: windows
|
||||
|
||||
active: Config.bar.workspaces.showWindows
|
||||
asynchronous: true
|
||||
|
||||
anchors.horizontalCenter: indicator.horizontalCenter
|
||||
anchors.top: indicator.bottom
|
||||
|
||||
sourceComponent: Column {
|
||||
spacing: Appearance.spacing.small
|
||||
|
||||
add: Transition {
|
||||
Anim {
|
||||
properties: "scale"
|
||||
from: 0
|
||||
to: 1
|
||||
easing.bezierCurve: Appearance.anim.curves.standardDecel
|
||||
}
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: ScriptModel {
|
||||
values: Hyprland.clients.filter(c => c.workspace?.id === root.ws)
|
||||
}
|
||||
|
||||
MaterialIcon {
|
||||
required property Hyprland.Client modelData
|
||||
|
||||
text: Icons.getAppCategoryIcon(modelData.wmClass, "terminal")
|
||||
color: Colours.palette.m3onSurfaceVariant
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on Layout.preferredWidth {
|
||||
Anim {}
|
||||
}
|
||||
|
||||
Behavior on Layout.preferredHeight {
|
||||
Anim {}
|
||||
}
|
||||
|
||||
component Anim: NumberAnimation {
|
||||
duration: Appearance.anim.durations.normal
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: Appearance.anim.curves.standard
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import "root:/widgets"
|
||||
import "root:/services"
|
||||
import "root:/config"
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
readonly property list<Workspace> workspaces: layout.children.filter(c => c.isWorkspace).sort((w1, w2) => w1.ws - w2.ws)
|
||||
readonly property var occupied: Hyprland.workspaces.values.reduce((acc, curr) => {
|
||||
acc[curr.id] = curr.lastIpcObject.windows > 0;
|
||||
return acc;
|
||||
}, {})
|
||||
readonly property int groupOffset: Math.floor((Hyprland.activeWsId - 1) / Config.bar.workspaces.shown) * Config.bar.workspaces.shown
|
||||
|
||||
implicitWidth: layout.implicitWidth
|
||||
implicitHeight: layout.implicitHeight
|
||||
|
||||
ColumnLayout {
|
||||
id: layout
|
||||
|
||||
spacing: 0
|
||||
layer.enabled: true
|
||||
layer.smooth: true
|
||||
|
||||
Repeater {
|
||||
model: Config.bar.workspaces.shown
|
||||
|
||||
Workspace {
|
||||
occupied: root.occupied
|
||||
groupOffset: root.groupOffset
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
active: Config.bar.workspaces.occupiedBg
|
||||
asynchronous: true
|
||||
|
||||
z: -1
|
||||
anchors.fill: parent
|
||||
|
||||
sourceComponent: OccupiedBg {
|
||||
workspaces: root.workspaces
|
||||
occupied: root.occupied
|
||||
groupOffset: root.groupOffset
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
active: Config.bar.workspaces.activeIndicator
|
||||
asynchronous: true
|
||||
|
||||
sourceComponent: ActiveIndicator {
|
||||
workspaces: root.workspaces
|
||||
mask: layout
|
||||
maskWidth: root.width
|
||||
maskHeight: root.height
|
||||
groupOffset: root.groupOffset
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
|
||||
onPressed: event => {
|
||||
const ws = layout.childAt(event.x, event.y).index + root.groupOffset + 1;
|
||||
if (Hyprland.activeWsId !== ws)
|
||||
Hyprland.dispatch(`workspace ${ws}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
75
dots/quickshell/modules/bar/popouts/ActiveWindow.qml
Normal file
75
dots/quickshell/modules/bar/popouts/ActiveWindow.qml
Normal file
@@ -0,0 +1,75 @@
|
||||
import "root:/widgets"
|
||||
import "root:/services"
|
||||
import "root:/utils"
|
||||
import "root:/config"
|
||||
import Quickshell.Widgets
|
||||
import Quickshell.Wayland
|
||||
import QtQuick
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
implicitWidth: Hyprland.activeClient ? child.implicitWidth : -Appearance.padding.large * 2
|
||||
implicitHeight: child.implicitHeight
|
||||
|
||||
Column {
|
||||
id: child
|
||||
|
||||
anchors.centerIn: parent
|
||||
spacing: Appearance.spacing.normal
|
||||
|
||||
Row {
|
||||
id: detailsRow
|
||||
|
||||
spacing: Appearance.spacing.normal
|
||||
|
||||
IconImage {
|
||||
id: icon
|
||||
|
||||
implicitSize: details.implicitHeight
|
||||
source: Icons.getAppIcon(Hyprland.activeClient?.wmClass ?? "", "image-missing")
|
||||
}
|
||||
|
||||
Column {
|
||||
id: details
|
||||
|
||||
StyledText {
|
||||
text: Hyprland.activeClient?.title ?? ""
|
||||
font.pointSize: Appearance.font.size.normal
|
||||
|
||||
elide: Text.ElideRight
|
||||
width: preview.implicitWidth - icon.implicitWidth - detailsRow.spacing
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: Hyprland.activeClient?.wmClass ?? ""
|
||||
color: Colours.palette.m3onSurfaceVariant
|
||||
|
||||
elide: Text.ElideRight
|
||||
width: preview.implicitWidth - icon.implicitWidth - detailsRow.spacing
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ClippingWrapperRectangle {
|
||||
color: "transparent"
|
||||
radius: Appearance.rounding.small
|
||||
|
||||
ScreencopyView {
|
||||
id: preview
|
||||
|
||||
captureSource: Hyprland.activeClient ? ToplevelManager.activeToplevel : null
|
||||
live: visible
|
||||
|
||||
constraintSize.width: Config.bar.sizes.windowPreviewSize
|
||||
constraintSize.height: Config.bar.sizes.windowPreviewSize
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
component Anim: NumberAnimation {
|
||||
duration: Appearance.anim.durations.normal
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: Appearance.anim.curves.emphasized
|
||||
}
|
||||
}
|
||||
74
dots/quickshell/modules/bar/popouts/Background.qml
Normal file
74
dots/quickshell/modules/bar/popouts/Background.qml
Normal file
@@ -0,0 +1,74 @@
|
||||
import "root:/services"
|
||||
import "root:/config"
|
||||
import QtQuick
|
||||
import QtQuick.Shapes
|
||||
|
||||
ShapePath {
|
||||
id: root
|
||||
|
||||
required property Wrapper wrapper
|
||||
required property bool invertBottomRounding
|
||||
readonly property real rounding: Config.border.rounding
|
||||
readonly property bool flatten: wrapper.width < rounding * 2
|
||||
readonly property real roundingX: flatten ? wrapper.width / 2 : rounding
|
||||
property real ibr: invertBottomRounding ? -1 : 1
|
||||
|
||||
strokeWidth: -1
|
||||
fillColor: Config.border.colour
|
||||
|
||||
PathArc {
|
||||
relativeX: root.roundingX
|
||||
relativeY: root.rounding
|
||||
radiusX: Math.min(root.rounding, root.wrapper.width)
|
||||
radiusY: root.rounding
|
||||
direction: PathArc.Counterclockwise
|
||||
}
|
||||
PathLine {
|
||||
relativeX: root.wrapper.width - root.roundingX * 2
|
||||
relativeY: 0
|
||||
}
|
||||
PathArc {
|
||||
relativeX: root.roundingX
|
||||
relativeY: root.rounding
|
||||
radiusX: Math.min(root.rounding, root.wrapper.width)
|
||||
radiusY: root.rounding
|
||||
}
|
||||
PathLine {
|
||||
relativeX: 0
|
||||
relativeY: root.wrapper.height - root.rounding * 2
|
||||
}
|
||||
PathArc {
|
||||
relativeX: -root.roundingX * root.ibr
|
||||
relativeY: root.rounding
|
||||
radiusX: Math.min(root.rounding, root.wrapper.width)
|
||||
radiusY: root.rounding
|
||||
direction: root.ibr < 0 ? PathArc.Counterclockwise : PathArc.Clockwise
|
||||
}
|
||||
PathLine {
|
||||
relativeX: -(root.wrapper.width - root.roundingX - root.roundingX * root.ibr)
|
||||
relativeY: 0
|
||||
}
|
||||
PathArc {
|
||||
relativeX: -root.roundingX
|
||||
relativeY: root.rounding
|
||||
radiusX: Math.min(root.rounding, root.wrapper.width)
|
||||
radiusY: root.rounding
|
||||
direction: PathArc.Counterclockwise
|
||||
}
|
||||
|
||||
Behavior on fillColor {
|
||||
ColorAnimation {
|
||||
duration: Appearance.anim.durations.normal
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: Appearance.anim.curves.standard
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on ibr {
|
||||
NumberAnimation {
|
||||
duration: Appearance.anim.durations.normal
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: Appearance.anim.curves.standard
|
||||
}
|
||||
}
|
||||
}
|
||||
235
dots/quickshell/modules/bar/popouts/Battery.qml
Normal file
235
dots/quickshell/modules/bar/popouts/Battery.qml
Normal file
@@ -0,0 +1,235 @@
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import "root:/widgets"
|
||||
import "root:/services"
|
||||
import "root:/config"
|
||||
import Quickshell.Services.UPower
|
||||
import QtQuick
|
||||
|
||||
Column {
|
||||
id: root
|
||||
|
||||
spacing: Appearance.spacing.normal
|
||||
width: Config.bar.sizes.batteryWidth
|
||||
|
||||
StyledText {
|
||||
text: UPower.displayDevice.isLaptopBattery ? qsTr("Remaining: %1%").arg(Math.round(UPower.displayDevice.percentage * 100)) : qsTr("No battery detected")
|
||||
}
|
||||
|
||||
StyledText {
|
||||
function formatSeconds(s: int, fallback: string): string {
|
||||
const day = Math.floor(s / 86400);
|
||||
const hr = Math.floor(s / 3600) % 60;
|
||||
const min = Math.floor(s / 60) % 60;
|
||||
|
||||
let comps = [];
|
||||
if (day > 0)
|
||||
comps.push(`${day} days`);
|
||||
if (hr > 0)
|
||||
comps.push(`${hr} hours`);
|
||||
if (min > 0)
|
||||
comps.push(`${min} mins`);
|
||||
|
||||
return comps.join(", ") || fallback;
|
||||
}
|
||||
|
||||
text: UPower.displayDevice.isLaptopBattery ? qsTr("Time %1: %2").arg(UPower.onBattery ? "remaining" : "until charged").arg(UPower.onBattery ? formatSeconds(UPower.displayDevice.timeToEmpty, "Calculating...") : formatSeconds(UPower.displayDevice.timeToFull, "Fully charged!")) : qsTr("Power profile: %1").arg(PowerProfile.toString(PowerProfiles.profile))
|
||||
}
|
||||
|
||||
Loader {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
|
||||
active: PowerProfiles.degradationReason !== PerformanceDegradationReason.None
|
||||
asynchronous: true
|
||||
|
||||
height: active ? (item?.implicitHeight ?? 0) : 0
|
||||
|
||||
sourceComponent: StyledRect {
|
||||
implicitWidth: child.implicitWidth + Appearance.padding.normal * 2
|
||||
implicitHeight: child.implicitHeight + Appearance.padding.smaller * 2
|
||||
|
||||
color: Colours.palette.m3error
|
||||
radius: Appearance.rounding.normal
|
||||
|
||||
Column {
|
||||
id: child
|
||||
|
||||
anchors.centerIn: parent
|
||||
|
||||
Row {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
spacing: Appearance.spacing.small
|
||||
|
||||
MaterialIcon {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.verticalCenterOffset: -font.pointSize / 10
|
||||
|
||||
text: "warning"
|
||||
color: Colours.palette.m3onError
|
||||
}
|
||||
|
||||
StyledText {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: qsTr("Performance Degraded")
|
||||
color: Colours.palette.m3onError
|
||||
font.family: Appearance.font.family.mono
|
||||
font.weight: 500
|
||||
}
|
||||
|
||||
MaterialIcon {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.verticalCenterOffset: -font.pointSize / 10
|
||||
|
||||
text: "warning"
|
||||
color: Colours.palette.m3onError
|
||||
}
|
||||
}
|
||||
|
||||
StyledText {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
|
||||
text: qsTr("Reason: %1").arg(PerformanceDegradationReason.toString(PowerProfiles.degradationReason))
|
||||
color: Colours.palette.m3onError
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
StyledRect {
|
||||
id: profiles
|
||||
|
||||
property string current: {
|
||||
const p = PowerProfiles.profile;
|
||||
if (p === PowerProfile.PowerSaver)
|
||||
return saver.icon;
|
||||
if (p === PowerProfile.Performance)
|
||||
return perf.icon;
|
||||
return balance.icon;
|
||||
}
|
||||
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
|
||||
implicitWidth: saver.implicitHeight + balance.implicitHeight + perf.implicitHeight + Appearance.padding.normal * 2 + Appearance.spacing.large * 2
|
||||
implicitHeight: Math.max(saver.implicitHeight, balance.implicitHeight, perf.implicitHeight) + Appearance.padding.small * 2
|
||||
|
||||
color: Colours.palette.m3surfaceContainer
|
||||
radius: Appearance.rounding.full
|
||||
|
||||
StyledRect {
|
||||
id: indicator
|
||||
|
||||
color: Colours.palette.m3primary
|
||||
radius: Appearance.rounding.full
|
||||
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: Appearance.anim.durations.normal
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: Appearance.anim.curves.emphasized
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Profile {
|
||||
id: saver
|
||||
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: Appearance.padding.small
|
||||
|
||||
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: Appearance.padding.small
|
||||
|
||||
profile: PowerProfile.Performance
|
||||
icon: "rocket_launch"
|
||||
}
|
||||
}
|
||||
|
||||
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: Item {
|
||||
required property string icon
|
||||
required property int profile
|
||||
|
||||
implicitWidth: icon.implicitHeight + Appearance.padding.small * 2
|
||||
implicitHeight: icon.implicitHeight + Appearance.padding.small * 2
|
||||
|
||||
StateLayer {
|
||||
radius: Appearance.rounding.full
|
||||
color: profiles.current === parent.icon ? Colours.palette.m3onPrimary : Colours.palette.m3onSurface
|
||||
|
||||
function onClicked(): void {
|
||||
PowerProfiles.profile = parent.profile;
|
||||
}
|
||||
}
|
||||
|
||||
MaterialIcon {
|
||||
id: icon
|
||||
|
||||
anchors.centerIn: parent
|
||||
|
||||
text: parent.icon
|
||||
font.pointSize: Appearance.font.size.large
|
||||
color: profiles.current === text ? Colours.palette.m3onPrimary : Colours.palette.m3onSurface
|
||||
fill: profiles.current === text ? 1 : 0
|
||||
|
||||
Behavior on fill {
|
||||
NumberAnimation {
|
||||
duration: Appearance.anim.durations.normal
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: Appearance.anim.curves.standard
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
18
dots/quickshell/modules/bar/popouts/Bluetooth.qml
Normal file
18
dots/quickshell/modules/bar/popouts/Bluetooth.qml
Normal file
@@ -0,0 +1,18 @@
|
||||
import "root:/widgets"
|
||||
import "root:/services"
|
||||
import "root:/config"
|
||||
import QtQuick
|
||||
|
||||
Column {
|
||||
id: root
|
||||
|
||||
spacing: Appearance.spacing.normal
|
||||
|
||||
StyledText {
|
||||
text: qsTr("Bluetooth %1").arg(Bluetooth.powered ? "enabled" : "disabled")
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: Bluetooth.devices.some(d => d.connected) ? qsTr("Connected to: %1").arg(Bluetooth.devices.filter(d => d.connected).map(d => d.alias).join(", ")) : qsTr("No devices connected")
|
||||
}
|
||||
}
|
||||
175
dots/quickshell/modules/bar/popouts/Content.qml
Normal file
175
dots/quickshell/modules/bar/popouts/Content.qml
Normal file
@@ -0,0 +1,175 @@
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import "root:/services"
|
||||
import "root:/config"
|
||||
import Quickshell
|
||||
import Quickshell.Services.SystemTray
|
||||
import QtQuick
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
required property ShellScreen screen
|
||||
|
||||
property string currentName
|
||||
property real currentCenter
|
||||
property bool hasCurrent
|
||||
|
||||
anchors.centerIn: parent
|
||||
|
||||
implicitWidth: hasCurrent ? (content.children.find(c => c.shouldBeActive)?.implicitWidth ?? 0) + Appearance.padding.large * 2 : 0
|
||||
implicitHeight: (content.children.find(c => c.shouldBeActive)?.implicitHeight ?? 0) + Appearance.padding.large * 2
|
||||
|
||||
Item {
|
||||
id: content
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.margins: Appearance.padding.large
|
||||
|
||||
clip: true
|
||||
|
||||
Popout {
|
||||
name: "activewindow"
|
||||
source: "ActiveWindow.qml"
|
||||
}
|
||||
|
||||
Popout {
|
||||
name: "network"
|
||||
source: "Network.qml"
|
||||
}
|
||||
|
||||
Popout {
|
||||
name: "bluetooth"
|
||||
source: "Bluetooth.qml"
|
||||
}
|
||||
|
||||
Popout {
|
||||
name: "battery"
|
||||
source: "Battery.qml"
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: ScriptModel {
|
||||
values: [...SystemTray.items.values]
|
||||
}
|
||||
|
||||
Popout {
|
||||
id: trayMenu
|
||||
|
||||
required property SystemTrayItem modelData
|
||||
required property int index
|
||||
|
||||
name: `traymenu${index}`
|
||||
sourceComponent: trayMenuComp
|
||||
|
||||
Connections {
|
||||
target: root
|
||||
|
||||
function onHasCurrentChanged(): void {
|
||||
if (root.hasCurrent && trayMenu.shouldBeActive) {
|
||||
trayMenu.sourceComponent = null;
|
||||
trayMenu.sourceComponent = trayMenuComp;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: trayMenuComp
|
||||
|
||||
TrayMenu {
|
||||
popouts: root
|
||||
trayItem: trayMenu.modelData.menu
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on implicitWidth {
|
||||
Anim {
|
||||
easing.bezierCurve: Appearance.anim.curves.emphasized
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on implicitHeight {
|
||||
enabled: root.implicitWidth > 0
|
||||
|
||||
Anim {
|
||||
easing.bezierCurve: Appearance.anim.curves.emphasized
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on currentCenter {
|
||||
enabled: root.implicitWidth > 0
|
||||
|
||||
NumberAnimation {
|
||||
duration: Appearance.anim.durations.normal
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: Appearance.anim.curves.emphasized
|
||||
}
|
||||
}
|
||||
|
||||
component Popout: Loader {
|
||||
id: popout
|
||||
|
||||
required property string name
|
||||
property bool shouldBeActive: root.currentName === name
|
||||
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
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: "active"
|
||||
to: ""
|
||||
|
||||
SequentialAnimation {
|
||||
Anim {
|
||||
properties: "opacity,scale"
|
||||
duration: Appearance.anim.durations.small
|
||||
}
|
||||
PropertyAction {
|
||||
target: popout
|
||||
property: "active"
|
||||
}
|
||||
}
|
||||
},
|
||||
Transition {
|
||||
from: ""
|
||||
to: "active"
|
||||
|
||||
SequentialAnimation {
|
||||
PropertyAction {
|
||||
target: popout
|
||||
property: "active"
|
||||
}
|
||||
Anim {
|
||||
properties: "opacity,scale"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
component Anim: NumberAnimation {
|
||||
duration: Appearance.anim.durations.normal
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: Appearance.anim.curves.standard
|
||||
}
|
||||
}
|
||||
22
dots/quickshell/modules/bar/popouts/Network.qml
Normal file
22
dots/quickshell/modules/bar/popouts/Network.qml
Normal file
@@ -0,0 +1,22 @@
|
||||
import "root:/widgets"
|
||||
import "root:/services"
|
||||
import "root:/config"
|
||||
import QtQuick
|
||||
|
||||
Column {
|
||||
id: root
|
||||
|
||||
spacing: Appearance.spacing.normal
|
||||
|
||||
StyledText {
|
||||
text: qsTr("Connected to: %1").arg(Network.active?.ssid ?? "None")
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: qsTr("Strength: %1/100").arg(Network.active?.strength ?? 0)
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: qsTr("Frequency: %1 MHz").arg(Network.active?.frequency ?? 0)
|
||||
}
|
||||
}
|
||||
237
dots/quickshell/modules/bar/popouts/TrayMenu.qml
Normal file
237
dots/quickshell/modules/bar/popouts/TrayMenu.qml
Normal file
@@ -0,0 +1,237 @@
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import "root:/widgets"
|
||||
import "root:/services"
|
||||
import "root:/config"
|
||||
import Quickshell
|
||||
import Quickshell.Widgets
|
||||
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: Anim {}
|
||||
pushExit: Anim {}
|
||||
popEnter: Anim {}
|
||||
popExit: Anim {}
|
||||
|
||||
component Anim: Transition {
|
||||
NumberAnimation {
|
||||
duration: 0
|
||||
}
|
||||
}
|
||||
|
||||
component SubMenu: Column {
|
||||
id: menu
|
||||
|
||||
required property QsMenuHandle handle
|
||||
property bool isSubMenu
|
||||
property bool shown
|
||||
|
||||
padding: Appearance.padding.smaller
|
||||
spacing: Appearance.spacing.small
|
||||
|
||||
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 {
|
||||
NumberAnimation {
|
||||
duration: Appearance.anim.durations.normal
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: Appearance.anim.curves.standard
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on scale {
|
||||
NumberAnimation {
|
||||
duration: Appearance.anim.durations.normal
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: Appearance.anim.curves.standard
|
||||
}
|
||||
}
|
||||
|
||||
QsMenuOpener {
|
||||
id: menuOpener
|
||||
|
||||
menu: menu.handle
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: menuOpener.children
|
||||
|
||||
StyledRect {
|
||||
id: item
|
||||
|
||||
required property QsMenuEntry modelData
|
||||
|
||||
implicitWidth: Config.bar.sizes.trayMenuWidth
|
||||
implicitHeight: modelData.isSeparator ? 1 : children.implicitHeight
|
||||
|
||||
radius: Appearance.rounding.full
|
||||
color: modelData.isSeparator ? Colours.palette.m3outlineVariant : "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.margins: -Appearance.padding.small / 2
|
||||
anchors.leftMargin: -Appearance.padding.smaller
|
||||
anchors.rightMargin: -Appearance.padding.smaller
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
StyledText {
|
||||
id: label
|
||||
|
||||
anchors.left: icon.right
|
||||
anchors.leftMargin: icon.active ? Appearance.spacing.smaller : 0
|
||||
|
||||
text: labelMetrics.elidedText
|
||||
color: item.modelData.enabled ? Colours.palette.m3onSurface : Colours.palette.m3outline
|
||||
}
|
||||
|
||||
TextMetrics {
|
||||
id: labelMetrics
|
||||
|
||||
text: item.modelData.text
|
||||
font.pointSize: label.font.pointSize
|
||||
font.family: label.font.family
|
||||
|
||||
elide: Text.ElideRight
|
||||
elideWidth: Config.bar.sizes.trayMenuWidth - (icon.active ? icon.implicitWidth + label.anchors.leftMargin : 0) - (expand.active ? expand.implicitWidth + Appearance.spacing.normal : 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 ? Colours.palette.m3onSurface : Colours.palette.m3outline
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
StyledRect {
|
||||
anchors.fill: parent
|
||||
anchors.margins: -Appearance.padding.small / 2
|
||||
anchors.leftMargin: -Appearance.padding.smaller
|
||||
anchors.rightMargin: -Appearance.padding.smaller * 2
|
||||
|
||||
radius: Appearance.rounding.full
|
||||
color: Colours.palette.m3secondaryContainer
|
||||
|
||||
StateLayer {
|
||||
radius: parent.radius
|
||||
color: Colours.palette.m3onSecondaryContainer
|
||||
|
||||
function onClicked(): void {
|
||||
root.pop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
id: back
|
||||
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
MaterialIcon {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: "chevron_left"
|
||||
color: Colours.palette.m3onSecondaryContainer
|
||||
}
|
||||
|
||||
StyledText {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: qsTr("Back")
|
||||
color: Colours.palette.m3onSecondaryContainer
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: subMenuComp
|
||||
|
||||
SubMenu {}
|
||||
}
|
||||
}
|
||||
25
dots/quickshell/modules/bar/popouts/Wrapper.qml
Normal file
25
dots/quickshell/modules/bar/popouts/Wrapper.qml
Normal file
@@ -0,0 +1,25 @@
|
||||
import "root:/services"
|
||||
import "root:/config"
|
||||
import Quickshell
|
||||
import QtQuick
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
required property ShellScreen screen
|
||||
|
||||
property alias currentName: content.currentName
|
||||
property alias currentCenter: content.currentCenter
|
||||
property alias hasCurrent: content.hasCurrent
|
||||
|
||||
visible: width > 0 && height > 0
|
||||
|
||||
implicitWidth: content.implicitWidth
|
||||
implicitHeight: content.implicitHeight
|
||||
|
||||
Content {
|
||||
id: content
|
||||
|
||||
screen: root.screen
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user