6 Commits

Author SHA1 Message Date
clsty d192a25faa Merge remote-tracking branch 'refs/remotes/origin/main'
Comment on Discussion When sdata/dist-arch/ Changes / comment_on_discussion (push) Waiting to run
2026-06-05 14:50:37 +08:00
clsty 0bfade5c36 Fix microtex build failure (#3422) 2026-06-05 14:49:57 +08:00
end-4 d619ddcd82 not use raw keycode binds for super+alt+ws (fixes #3368) 2026-06-05 08:42:06 +02:00
clsty 3cb611c04e No more sleep 0
Comment on Discussion When sdata/dist-arch/ Changes / comment_on_discussion (push) Has been cancelled
2026-05-29 13:46:40 +08:00
Minh f5b2b7548d Fix wrong dpms syntax (#3407) 2026-05-26 20:19:33 +02:00
VietNguyenx e0f2a34949 Wrong dpms syntax 2026-05-27 01:06:27 +07:00
121 changed files with 508 additions and 4967 deletions
+2 -2
View File
@@ -16,8 +16,8 @@ listener {
listener { listener {
timeout = 600 # 10mins timeout = 600 # 10mins
on-timeout = hyprctl dispatch 'hl.dsp.dpms(false)' on-timeout = hyprctl dispatch 'hl.dsp.dpms({ action = "disable" })'
on-resume = hyprctl dispatch 'hl.dsp.dpms(true)' on-resume = hyprctl dispatch 'hl.dsp.dpms({ action = "enable" })'
} }
listener { listener {
+6 -6
View File
@@ -202,12 +202,12 @@ for i = 1, 10 do
end, { description = "Window: Send to workspace " .. i }) end, { description = "Window: Send to workspace " .. i })
end end
--# We also use raw keycodes because some keyboard layouts register number keys as different chars. The codes can be verified with `wev` --# We also use raw keycodes because some keyboard layouts register number keys as different chars. The codes can be verified with `wev`
for i = 1, 10 do -- for i = 1, 10 do
local numberkey = { 10, 11, 12, 13, 14, 15, 16, 17, 18, 19 } -- local numberkey = { 10, 11, 12, 13, 14, 15, 16, 17, 18, 19 }
hl.bind("SUPER + ALT + code:" .. numberkey[i], function() -- hl.bind("SUPER + ALT + code:" .. numberkey[i], function()
hl.dispatch(hl.dsp.window.move({ workspace = workspace_in_group(i), follow = false })) -- hl.dispatch(hl.dsp.window.move({ workspace = workspace_in_group(i), follow = false }))
end) -- end)
end -- end
--# keypad numbers --# keypad numbers
for i = 1, 10 do for i = 1, 10 do
local numpadkey = { 87, 88, 89, 83, 84, 85, 79, 80, 81, 90 } local numpadkey = { 87, 88, 89, 83, 84, 85, 79, 80, 81, 90 }
+140 -152
View File
@@ -1,172 +1,160 @@
pragma ComponentBehavior: Bound
import QtQuick import QtQuick
import QtQuick.Effects
import QtQuick.Layouts import QtQuick.Layouts
import Qt5Compat.GraphicalEffects
import Quickshell import Quickshell
import Quickshell.Wayland import Quickshell.Wayland
Scope { Scope {
id: root id: root
property bool failed property bool failed;
property string errorString property string errorString;
property real progressHeight: 3
// Connect to the Quickshell global to listen for the reload signals. // Connect to the Quickshell global to listen for the reload signals.
Connections { Connections {
target: Quickshell target: Quickshell
function onReloadCompleted() { function onReloadCompleted() {
root.failed = false; root.failed = false;
popupLoader.loading = true; popupLoader.loading = true;
} }
function onReloadFailed(error: string) { function onReloadFailed(error: string) {
// Close any existing popup before making a new one. // Close any existing popup before making a new one.
popupLoader.active = false; popupLoader.active = false;
root.failed = true; root.failed = true;
root.errorString = error; root.errorString = error;
popupLoader.loading = true; popupLoader.loading = true;
} }
} }
// Keep the popup in a loader because it isn't needed most of the time // Keep the popup in a loader because it isn't needed most of the time
LazyLoader { LazyLoader {
id: popupLoader id: popupLoader
PanelWindow { PanelWindow {
id: popup id: popup
exclusiveZone: 0 exclusiveZone: 0
anchors.top: true anchors.top: true
margins.top: 0 margins.top: 0
implicitWidth: rect.width + 8 * 2 implicitWidth: rect.width + shadow.radius * 2
implicitHeight: rect.height + 8 * 2 implicitHeight: rect.height + shadow.radius * 2
WlrLayershell.namespace: "quickshell:reloadPopup" WlrLayershell.namespace: "quickshell:reloadPopup"
// color blending is a bit odd as detailed in the type reference. // color blending is a bit odd as detailed in the type reference.
color: "transparent" color: "transparent"
RectangularShadow { Rectangle {
id: rect
anchors.centerIn: parent
color: failed ? "#ffe99195" : "#ffD1E8D5"
implicitHeight: layout.implicitHeight + 30
implicitWidth: layout.implicitWidth + 30
radius: 12
// Fills the whole area of the rectangle, making any clicks go to it,
// which dismiss the popup.
MouseArea {
id: mouseArea
anchors.fill: parent
onPressed: {
popupLoader.active = false
}
// makes the mouse area track mouse hovering, so the hide animation
// can be paused when hovering.
hoverEnabled: true
}
ColumnLayout {
id: layout
spacing: 10
anchors {
top: parent.top
topMargin: 10
horizontalCenter: parent.horizontalCenter
}
Text {
renderType: Text.NativeRendering
font.family: "Google Sans Flex"
font.pointSize: 14
text: root.failed ? "Quickshell: Reload failed" : "Quickshell reloaded"
color: failed ? "#ff93000A" : "#ff0C1F13"
}
Text {
renderType: Text.NativeRendering
font.family: "JetBrains Mono NF"
font.pointSize: 11
text: root.errorString
color: failed ? "#ff93000A" : "#ff0C1F13"
// When visible is false, it also takes up no space.
visible: root.errorString != ""
}
}
// A progress bar on the bottom of the screen, showing how long until the
// popup is removed.
Rectangle {
z: 2
id: bar
color: failed ? "#ff93000A" : "#ff0C1F13"
anchors.bottom: parent.bottom
anchors.left: parent.left
anchors.margins: 10
height: 5
radius: 9999
PropertyAnimation {
id: anim
target: bar
property: "width"
from: rect.width - bar.anchors.margins * 2
to: 0
duration: failed ? 10000 : 1000
onFinished: popupLoader.active = false
// Pause the animation when the mouse is hovering over the popup,
// so it stays onscreen while reading. This updates reactively
// when the mouse moves on and off the popup.
paused: mouseArea.containsMouse
}
}
// Its bg
Rectangle {
z: 1
id: bar_bg
color: failed ? "#30af1b25" : "#4027643e"
anchors.bottom: parent.bottom
anchors.left: parent.left
anchors.margins: 10
height: 5
radius: 9999
width: rect.width - bar.anchors.margins * 2
}
// We could set `running: true` inside the animation, but the width of the
// rectangle might not be calculated yet, due to the layout.
// In the `Component.onCompleted` event handler, all of the component's
// properties and children have been initialized.
Component.onCompleted: anim.start()
}
DropShadow {
id: shadow
anchors.fill: rect anchors.fill: rect
radius: rect.radius horizontalOffset: 0
blur: 6.3 verticalOffset: 2
offset: Qt.vector2d(0.0, 1.0) radius: 6
spread: 1 samples: radius * 2 + 1 // Ideally should be 2 * radius + 1, see qt docs
color: "#55000000" color: "#44000000"
source: rect
} }
}
Rectangle { }
id: rect
anchors.centerIn: parent
color: root.failed ? "#ffe99195" : "#ffD1E8D5"
implicitHeight: layout.implicitHeight + 30
implicitWidth: layout.implicitWidth + 30
radius: 8
// Fills the whole area of the rectangle, making any clicks go to it,
// which dismiss the popup.
MouseArea {
id: mouseArea
anchors.fill: parent
onPressed: {
popupLoader.active = false;
}
// makes the mouse area track mouse hovering, so the hide animation
// can be paused when hovering.
hoverEnabled: true
}
ColumnLayout {
id: layout
spacing: 10
anchors {
top: parent.top
topMargin: 10
horizontalCenter: parent.horizontalCenter
}
Text {
id: title
renderType: Text.NativeRendering
font.family: "Google Sans Flex"
font.pointSize: 14
text: root.failed ? "Quickshell: Reload failed" : "Quickshell reloaded"
color: root.failed ? "#ff93000A" : "#ff0C1F13"
}
Text {
id: info
renderType: Text.NativeRendering
font.family: "JetBrains Mono NF"
font.pointSize: 11
text: root.errorString
color: root.failed ? "#ff93000A" : "#ff0C1F13"
// When visible is false, it also takes up no space.
visible: root.errorString != ""
}
}
// A progress bar on the bottom of the screen, showing how long until the
// popup is removed.
Rectangle {
id: bar
z: 2
color: root.failed ? "#ff93000A" : "#ff0C1F13"
property real maxWidth: Math.max(title.width, info.width)
anchors {
left: parent.left
leftMargin: (parent.width - maxWidth) / 2
bottom: parent.bottom
bottomMargin: 10
}
height: root.progressHeight
radius: 9999
PropertyAnimation {
id: anim
target: bar
property: "width"
from: Math.max(title.width, info.width)
to: 0
duration: root.failed ? 10000 : 1000
onFinished: popupLoader.active = false
// Pause the animation when the mouse is hovering over the popup,
// so it stays onscreen while reading. This updates reactively
// when the mouse moves on and off the popup.
paused: mouseArea.containsMouse
}
}
// Its bg
Rectangle {
id: bar_bg
z: 1
color: root.failed ? "#30af1b25" : "#4027643e"
property real maxWidth: Math.max(title.width, info.width)
anchors {
left: parent.left
right: parent.right
leftMargin: (parent.width - maxWidth) / 2
rightMargin: anchors.leftMargin
bottom: parent.bottom
bottomMargin: 10
}
height: root.progressHeight
radius: 9999
width: bar.width
}
// We could set `running: true` inside the animation, but the width of the
// rectangle might not be calculated yet, due to the layout.
// In the `Component.onCompleted` event handler, all of the component's
// properties and children have been initialized.
Component.onCompleted: anim.start()
}
}
}
} }
@@ -224,7 +224,7 @@ Singleton {
} }
property QtObject variableAxes: QtObject { property QtObject variableAxes: QtObject {
property var main: ({ property var main: ({
"wght": 500, "wght": 450,
"wdth": 100, "wdth": 100,
}) })
property var numbers: ({ property var numbers: ({
@@ -3,9 +3,7 @@ pragma ComponentBehavior: Bound
import QtQuick import QtQuick
import Quickshell import Quickshell
import Quickshell.Io import Quickshell.Io
import qs.modules.common.functions
import "functions"
import "config"
Singleton { Singleton {
id: root id: root
@@ -15,8 +13,6 @@ Singleton {
property int readWriteDelay: 50 // milliseconds property int readWriteDelay: 50 // milliseconds
property bool blockWrites: false property bool blockWrites: false
signal reloaded()
function setNestedValue(nestedKey, value) { function setNestedValue(nestedKey, value) {
let keys = nestedKey.split("."); let keys = nestedKey.split(".");
let obj = root.options; let obj = root.options;
@@ -52,7 +48,7 @@ Singleton {
interval: root.readWriteDelay interval: root.readWriteDelay
repeat: false repeat: false
onTriggered: { onTriggered: {
configFileView.reload(); configFileView.reload()
} }
} }
@@ -61,7 +57,7 @@ Singleton {
interval: root.readWriteDelay interval: root.readWriteDelay
repeat: false repeat: false
onTriggered: { onTriggered: {
configFileView.writeAdapter(); configFileView.writeAdapter()
} }
} }
@@ -72,10 +68,7 @@ Singleton {
blockWrites: root.blockWrites blockWrites: root.blockWrites
onFileChanged: fileReloadTimer.restart() onFileChanged: fileReloadTimer.restart()
onAdapterUpdated: fileWriteTimer.restart() onAdapterUpdated: fileWriteTimer.restart()
onLoaded: { onLoaded: root.ready = true
if (!root.ready) root.reloaded()
root.ready = true
}
onLoadFailed: error => { onLoadFailed: error => {
if (error == FileViewError.FileNotFound) { if (error == FileViewError.FileNotFound) {
writeAdapter(); writeAdapter();
@@ -159,8 +152,8 @@ Singleton {
property JsonObject apps: JsonObject { property JsonObject apps: JsonObject {
property string bluetooth: "kcmshell6 kcm_bluetooth" property string bluetooth: "kcmshell6 kcm_bluetooth"
property string changePassword: "kitty -1 --hold=yes fish -i -c 'passwd'" property string changePassword: "kitty -1 --hold=yes fish -i -c 'passwd'"
property string manageUser: "kcmshell6 kcm_users"
property string network: "kcmshell6 kcm_networkmanagement" property string network: "kcmshell6 kcm_networkmanagement"
property string manageUser: "kcmshell6 kcm_users"
property string networkEthernet: "kcmshell6 kcm_networkmanagement" property string networkEthernet: "kcmshell6 kcm_networkmanagement"
property string taskManager: "plasma-systemmonitor --page-name Processes" property string taskManager: "plasma-systemmonitor --page-name Processes"
property string terminal: "kitty -1" // This is only for shell actions property string terminal: "kitty -1" // This is only for shell actions
@@ -245,15 +238,9 @@ Singleton {
property bool floatStyleShadow: true // Show shadow behind bar when cornerStyle == 1 (Float) property bool floatStyleShadow: true // Show shadow behind bar when cornerStyle == 1 (Float)
property bool borderless: false // true for no grouping of items property bool borderless: false // true for no grouping of items
property string topLeftIcon: "spark" // Options: "distro" or any icon name in ~/.config/quickshell/ii/assets/icons property string topLeftIcon: "spark" // Options: "distro" or any icon name in ~/.config/quickshell/ii/assets/icons
property list<string> screenList: [] // List of names, like "eDP-1", find out with 'hyprctl monitors' command
property bool showBackground: true property bool showBackground: true
property bool verbose: true property bool verbose: true
property bool vertical: false property bool vertical: false
property JsonObject indicators: JsonObject {
property JsonObject notifications: JsonObject {
property bool showUnreadCount: false
}
}
property JsonObject resources: JsonObject { property JsonObject resources: JsonObject {
property bool alwaysShowSwap: true property bool alwaysShowSwap: true
property bool alwaysShowCpu: true property bool alwaysShowCpu: true
@@ -261,9 +248,7 @@ Singleton {
property int swapWarningThreshold: 85 property int swapWarningThreshold: 85
property int cpuWarningThreshold: 90 property int cpuWarningThreshold: 90
} }
property JsonObject tooltips: JsonObject { property list<string> screenList: [] // List of names, like "eDP-1", find out with 'hyprctl monitors' command
property bool clickToShow: false
}
property JsonObject utilButtons: JsonObject { property JsonObject utilButtons: JsonObject {
property bool showScreenSnip: true property bool showScreenSnip: true
property bool showColorPicker: false property bool showColorPicker: false
@@ -273,13 +258,6 @@ Singleton {
property bool showPerformanceProfileToggle: false property bool showPerformanceProfileToggle: false
property bool showScreenRecord: false property bool showScreenRecord: false
} }
property JsonObject weather: JsonObject {
property bool enable: false
property bool enableGPS: true // gps based location
property string city: "" // When 'enableGPS' is false
property bool useUSCS: false // Instead of metric (SI) units
property int fetchInterval: 10 // minutes
}
property JsonObject workspaces: JsonObject { property JsonObject workspaces: JsonObject {
property bool monochromeIcons: true property bool monochromeIcons: true
property int shown: 10 property int shown: 10
@@ -289,6 +267,21 @@ Singleton {
property list<string> numberMap: ["1", "2"] // Characters to show instead of numbers on workspace indicator property list<string> numberMap: ["1", "2"] // Characters to show instead of numbers on workspace indicator
property bool useNerdFont: false property bool useNerdFont: false
} }
property JsonObject weather: JsonObject {
property bool enable: false
property bool enableGPS: true // gps based location
property string city: "" // When 'enableGPS' is false
property bool useUSCS: false // Instead of metric (SI) units
property int fetchInterval: 10 // minutes
}
property JsonObject indicators: JsonObject {
property JsonObject notifications: JsonObject {
property bool showUnreadCount: false
}
}
property JsonObject tooltips: JsonObject {
property bool clickToShow: false
}
} }
property JsonObject battery: JsonObject { property JsonObject battery: JsonObject {
@@ -300,10 +293,7 @@ Singleton {
} }
property JsonObject calendar: JsonObject { property JsonObject calendar: JsonObject {
property string locale: "C" property string locale: "en-GB"
property bool force2CharDayOfWeek: true
property bool animate: false // Disabled by default cuz laggy
property bool weekScrollPrecision: false // One scroll advances 1 week instead of 1 month
} }
property JsonObject cheatsheet: JsonObject { property JsonObject cheatsheet: JsonObject {
@@ -351,8 +341,7 @@ Singleton {
property int mouseScrollFactor: 120 property int mouseScrollFactor: 120
property int touchpadScrollFactor: 450 property int touchpadScrollFactor: 450
} }
property JsonObject deadPixelWorkaround: JsonObject { property JsonObject deadPixelWorkaround: JsonObject { // Hyprland leaves out 1 pixel on the right for interactions
// Hyprland leaves out 1 pixel on the right for interactions
property bool enable: false property bool enable: false
} }
} }
@@ -367,7 +356,7 @@ Singleton {
} }
property JsonObject launcher: JsonObject { property JsonObject launcher: JsonObject {
property list<string> pinnedApps: ["org.kde.dolphin", "kitty", "cmake-gui"] property list<string> pinnedApps: [ "org.kde.dolphin", "kitty", "cmake-gui"]
} }
property JsonObject light: JsonObject { property JsonObject light: JsonObject {
@@ -410,7 +399,7 @@ Singleton {
property JsonObject notifications: JsonObject { property JsonObject notifications: JsonObject {
property int timeout: 7000 property int timeout: 7000
property JsonObject forceMonitor: JsonObject { property JsonObject monitor: JsonObject {
property bool enable: false property bool enable: false
property string name: "" // Name of the monitor to show notifications on, like "eDP-1". Find out with 'hyprctl monitors' command property string name: "" // Name of the monitor to show notifications on, like "eDP-1". Find out with 'hyprctl monitors' command
} }
@@ -476,7 +465,7 @@ Singleton {
property bool monochromeIcons: true property bool monochromeIcons: true
property bool showItemId: false property bool showItemId: false
property bool invertPinnedItems: true // Makes the below a whitelist for the tray and blacklist for the pinned area property bool invertPinnedItems: true // Makes the below a whitelist for the tray and blacklist for the pinned area
property list<var> pinnedItems: ["Fcitx"] property list<var> pinnedItems: [ "Fcitx" ]
property bool filterPassive: true property bool filterPassive: true
} }
@@ -540,30 +529,12 @@ Singleton {
property JsonObject android: JsonObject { property JsonObject android: JsonObject {
property int columns: 5 property int columns: 5
property list<var> toggles: [ property list<var> toggles: [
{ { "size": 2, "type": "network" },
"size": 2, { "size": 2, "type": "bluetooth" },
"type": "network" { "size": 1, "type": "idleInhibitor" },
}, { "size": 1, "type": "mic" },
{ { "size": 2, "type": "audio" },
"size": 2, { "size": 2, "type": "nightLight" }
"type": "bluetooth"
},
{
"size": 1,
"type": "idleInhibitor"
},
{
"size": 1,
"type": "mic"
},
{
"size": 2,
"type": "audio"
},
{
"size": 2,
"type": "nightLight"
}
] ]
} }
} }
@@ -577,7 +548,7 @@ Singleton {
} }
property JsonObject screenRecord: JsonObject { property JsonObject screenRecord: JsonObject {
property string savePath: Directories.videos.replace("file://", "") // strip "file://" property string savePath: Directories.videos.replace("file://","") // strip "file://"
} }
property JsonObject screenSnip: JsonObject { property JsonObject screenSnip: JsonObject {
@@ -637,8 +608,26 @@ Singleton {
} }
} }
property JsonObject hefty: HeftyConfig {} property JsonObject waffles: JsonObject {
property JsonObject waffles: WaffleConfig {} // Some spots are kinda janky/awkward. Setting the following to
// false will make (some) stuff also be like that for accuracy.
// Example: the right-click menu of the Start button
property JsonObject tweaks: JsonObject {
property bool switchHandlePositionFix: true
property bool smootherMenuAnimations: true
property bool smootherSearchBar: true
}
property JsonObject bar: JsonObject {
property bool bottom: true
property bool leftAlignApps: false
}
property JsonObject actionCenter: JsonObject {
property list<string> toggles: [ "network", "bluetooth", "easyEffects", "powerProfile", "idleInhibitor", "nightLight", "darkMode", "antiFlashbang", "cloudflareWarp", "mic", "musicRecognition", "notifications", "onScreenKeyboard", "gameMode", "screenSnip", "colorPicker" ]
}
property JsonObject calendar: JsonObject {
property bool force2CharDayOfWeek: true
}
}
} }
} }
} }
@@ -20,8 +20,7 @@ Singleton {
readonly property string music: StandardPaths.standardLocations(StandardPaths.MusicLocation)[0] readonly property string music: StandardPaths.standardLocations(StandardPaths.MusicLocation)[0]
readonly property string videos: StandardPaths.standardLocations(StandardPaths.MoviesLocation)[0] readonly property string videos: StandardPaths.standardLocations(StandardPaths.MoviesLocation)[0]
/////// Stuff below are without "file://" ///////// // Other dirs used by the shell, without "file://"
// General
property string assetsPath: Quickshell.shellPath("assets") property string assetsPath: Quickshell.shellPath("assets")
property string scriptPath: Quickshell.shellPath("scripts") property string scriptPath: Quickshell.shellPath("scripts")
property string favicons: FileUtils.trimFileProtocol(`${Directories.cache}/media/favicons`) property string favicons: FileUtils.trimFileProtocol(`${Directories.cache}/media/favicons`)
@@ -31,6 +30,9 @@ Singleton {
property string booruDownloads: FileUtils.trimFileProtocol(Directories.pictures + "/homework") property string booruDownloads: FileUtils.trimFileProtocol(Directories.pictures + "/homework")
property string booruDownloadsNsfw: FileUtils.trimFileProtocol(Directories.pictures + "/homework/🌶️") property string booruDownloadsNsfw: FileUtils.trimFileProtocol(Directories.pictures + "/homework/🌶️")
property string latexOutput: FileUtils.trimFileProtocol(`${Directories.cache}/media/latex`) property string latexOutput: FileUtils.trimFileProtocol(`${Directories.cache}/media/latex`)
property string shellConfig: FileUtils.trimFileProtocol(`${Directories.config}/illogical-impulse`)
property string shellConfigName: "config.json"
property string shellConfigPath: `${Directories.shellConfig}/${Directories.shellConfigName}`
property string todoPath: FileUtils.trimFileProtocol(`${Directories.state}/user/todo.json`) property string todoPath: FileUtils.trimFileProtocol(`${Directories.state}/user/todo.json`)
property string notesPath: FileUtils.trimFileProtocol(`${Directories.state}/user/notes.txt`) property string notesPath: FileUtils.trimFileProtocol(`${Directories.state}/user/notes.txt`)
property string conflictCachePath: FileUtils.trimFileProtocol(`${Directories.cache}/conflict-killer`) property string conflictCachePath: FileUtils.trimFileProtocol(`${Directories.cache}/conflict-killer`)
@@ -41,39 +43,24 @@ Singleton {
property string screenshotTemp: "/tmp/quickshell/media/screenshot" property string screenshotTemp: "/tmp/quickshell/media/screenshot"
property string wallpaperSwitchScriptPath: FileUtils.trimFileProtocol(`${Directories.scriptPath}/colors/switchwall.sh`) property string wallpaperSwitchScriptPath: FileUtils.trimFileProtocol(`${Directories.scriptPath}/colors/switchwall.sh`)
property string defaultAiPrompts: Quickshell.shellPath("defaults/ai/prompts") property string defaultAiPrompts: Quickshell.shellPath("defaults/ai/prompts")
property string userAiPrompts: FileUtils.trimFileProtocol(`${Directories.shellConfig}/ai/prompts`)
property string userActions: FileUtils.trimFileProtocol(`${Directories.shellConfig}/actions`)
property string aiChats: FileUtils.trimFileProtocol(`${Directories.state}/user/ai/chats`) property string aiChats: FileUtils.trimFileProtocol(`${Directories.state}/user/ai/chats`)
property string aiTranslationScriptPath: FileUtils.trimFileProtocol(`${Directories.scriptPath}/ai/gemini-translate.sh`) property string aiTranslationScriptPath: FileUtils.trimFileProtocol(`${Directories.scriptPath}/ai/gemini-translate.sh`)
property string recordScriptPath: FileUtils.trimFileProtocol(`${Directories.scriptPath}/videos/record.sh`) property string recordScriptPath: FileUtils.trimFileProtocol(`${Directories.scriptPath}/videos/record.sh`)
property string userAvatarPathAccountsService: FileUtils.trimFileProtocol(`/var/lib/AccountsService/icons/${SystemInfo.username}`) property string userAvatarPathAccountsService: FileUtils.trimFileProtocol(`/var/lib/AccountsService/icons/${SystemInfo.username}`)
property string userAvatarPathRicersAndWeirdSystems: FileUtils.trimFileProtocol(`${Directories.home}.face`) property string userAvatarPathRicersAndWeirdSystems: FileUtils.trimFileProtocol(`${Directories.home}.face`)
property string userAvatarPathRicersAndWeirdSystems2: FileUtils.trimFileProtocol(`${Directories.home}.face.icon`) property string userAvatarPathRicersAndWeirdSystems2: FileUtils.trimFileProtocol(`${Directories.home}.face.icon`)
// User
property string shellConfig: FileUtils.trimFileProtocol(`${Directories.config}/illogical-impulse`)
property string shellConfigName: "config.json"
property string shellConfigPath: `${Directories.shellConfig}/${Directories.shellConfigName}`
property string userAiPrompts: FileUtils.trimFileProtocol(`${Directories.shellConfig}/ai/prompts`)
property string userActions: FileUtils.trimFileProtocol(`${Directories.shellConfig}/actions`)
property string userComponents: FileUtils.trimFileProtocol(`${Directories.shellConfig}/components`)
// Cleanup on init // Cleanup on init
Component.onCompleted: { Component.onCompleted: {
Quickshell.execDetached(["mkdir", "-p", `${aiChats}`])
Quickshell.execDetached(["mkdir", "-p", `${favicons}`])
Quickshell.execDetached(["mkdir", "-p", `${shellConfig}`]) Quickshell.execDetached(["mkdir", "-p", `${shellConfig}`])
Quickshell.execDetached(["rm", "-rf", `${tempImages}`]) Quickshell.execDetached(["mkdir", "-p", `${favicons}`])
Quickshell.execDetached(["mkdir", "-p", `${userComponents}`]) Quickshell.execDetached(["bash", "-c", `rm -rf '${coverArt}'; mkdir -p '${coverArt}'`])
Quickshell.execDetached(["mkdir", "-p", `${userAiPrompts}`]) Quickshell.execDetached(["bash", "-c", `rm -rf '${booruPreviews}'; mkdir -p '${booruPreviews}'`])
Quickshell.execDetached(["bash", "-c", `rm -rf '${latexOutput}'; mkdir -p '${latexOutput}'`])
Quickshell.execDetached(["bash", "-c", `rm -rf '${cliphistDecode}'; mkdir -p '${cliphistDecode}'`])
Quickshell.execDetached(["mkdir", "-p", `${aiChats}`])
Quickshell.execDetached(["mkdir", "-p", `${userActions}`]) Quickshell.execDetached(["mkdir", "-p", `${userActions}`])
Quickshell.execDetached(["bash", "-c", `rm -rf '${booruPreviews}'; mkdir -p '${booruPreviews}'`]) Quickshell.execDetached(["rm", "-rf", `${tempImages}`])
Quickshell.execDetached(["bash", "-c", `rm -rf '${cliphistDecode}'; mkdir -p '${cliphistDecode}'`])
Quickshell.execDetached(["bash", "-c", `rm -rf '${coverArt}'; mkdir -p '${coverArt}'`])
Quickshell.execDetached(["bash", "-c", `rm -rf '${latexOutput}'; mkdir -p '${latexOutput}'`])
}
Component.onDestruction: {
Quickshell.execDetached(["bash", "-c", `rm -rf '${booruPreviews}'; mkdir -p '${booruPreviews}'`])
Quickshell.execDetached(["bash", "-c", `rm -rf '${cliphistDecode}'; mkdir -p '${cliphistDecode}'`])
Quickshell.execDetached(["bash", "-c", `rm -rf '${coverArt}'; mkdir -p '${coverArt}'`])
Quickshell.execDetached(["bash", "-c", `rm -rf '${latexOutput}'; mkdir -p '${latexOutput}'`])
} }
} }
@@ -1,21 +0,0 @@
import QtQuick
import Quickshell
import Quickshell.Io
JsonObject {
property JsonObject bar: JsonObject {
property list<var> leftWidgets: ["HLeftSidebarButton", "HWindowInfo"]
property list<var> centerLeftWidgets: ["HTime"]
property list<var> centerWidgets: ["HWorkspaces"]
property list<var> centerRightWidgets: ["HResources"]
property list<var> rightWidgets: ["HSystemTray", "HSystemIndicators"]
property bool m3ExpressiveGrouping: true
property JsonObject resources: JsonObject {
property bool showMemory: false
property bool showRam: false
property bool showSwap: false
property bool showCpu: false
}
}
}
@@ -1,21 +0,0 @@
import QtQuick
import Quickshell
import Quickshell.Io
JsonObject {
// Some spots are kinda janky/awkward. Setting the following to
// false will make (some) stuff also be like that for accuracy.
// Example: the right-click menu of the Start button
property JsonObject tweaks: JsonObject {
property bool switchHandlePositionFix: true
property bool smootherMenuAnimations: true
property bool smootherSearchBar: true
}
property JsonObject bar: JsonObject {
property bool bottom: true
property bool leftAlignApps: false
}
property JsonObject actionCenter: JsonObject {
property list<string> toggles: [ "network", "bluetooth", "easyEffects", "powerProfile", "idleInhibitor", "nightLight", "darkMode", "antiFlashbang", "cloudflareWarp", "mic", "musicRecognition", "notifications", "onScreenKeyboard", "gameMode", "screenSnip", "colorPicker" ]
}
}
@@ -109,8 +109,7 @@ Singleton {
*/ */
function transparentize(color, percentage = 1) { function transparentize(color, percentage = 1) {
var c = Qt.color(color); var c = Qt.color(color);
var a = c.a * (1 - clamp01(percentage)); return Qt.rgba(c.r, c.g, c.b, c.a * (1 - percentage));
return Qt.rgba(c.r, c.g, c.b, a);
} }
/** /**
@@ -122,7 +121,7 @@ Singleton {
*/ */
function applyAlpha(color, alpha) { function applyAlpha(color, alpha) {
var c = Qt.color(color); var c = Qt.color(color);
var a = clamp01(alpha); var a = Math.max(0, Math.min(1, alpha));
return Qt.rgba(c.r, c.g, c.b, a); return Qt.rgba(c.r, c.g, c.b, a);
} }
@@ -24,19 +24,4 @@ Singleton {
targetDate.setDate(firstDayDate.getDate() + i); targetDate.setDate(firstDayDate.getDate() + i);
return targetDate; return targetDate;
} }
function formatDuration(seconds) {
const d = Math.floor(seconds / 86400);
const h = Math.floor((seconds % 86400) / 3600);
const m = Math.floor((seconds % 3600) / 60);
let str = "";
if (d > 0)
str += `${d}d`;
if (h > 0)
str += `${str ? ", " : ""}${h}h`;
if (m > 0 || !str)
str += `${str ? ", " : ""}${m}m`;
return str;
}
} }
@@ -1,16 +0,0 @@
pragma Singleton
import Quickshell
Singleton {
id: root
/**
* Rounds the given number to the nearest even integer.
*
* @param {number} num - The number to round.
* @returns {number} The nearest even integer.
*/
function roundToEven(num) {
return Math.round(num / 2) * 2;
}
}
@@ -95,15 +95,4 @@ Singleton {
} }
} }
} }
function findParentWithProperty(obj, propertyName) {
let current = obj;
while (current) {
if (current.hasOwnProperty(propertyName)) {
return current;
}
current = current.parent;
}
return null;
}
} }
@@ -4,7 +4,7 @@ import QtQuick
// The former animates faster than the latter, see the NumberAnimations below // The former animates faster than the latter, see the NumberAnimations below
QtObject { QtObject {
id: root id: root
property int index required property int index
property real idx1: index property real idx1: index
property real idx2: index property real idx2: index
@@ -1,63 +0,0 @@
import QtQuick
import Quickshell.Wayland
import Quickshell.Hyprland
import qs.services
import qs.modules.common as C
NestableObject {
id: root
required property HyprlandMonitor monitor
readonly property var liveMonitorData: HyprlandData.monitors.find(m => m.id === monitor.id)
readonly property Toplevel activeWindow: ToplevelManager.activeToplevel
readonly property int activeWorkspace: monitor?.activeWorkspace?.id
readonly property bool currentWorkspaceNotFake: activeWindow?.activated ?? false // Active empty workspace = fake. At least, that's how I like to call it.
readonly property int fakeWorkspace: currentWorkspaceNotFake ? -9999 : activeWorkspace
readonly property int shownCount: C.Config.options.bar.workspaces.shown
readonly property int group: Math.floor((activeWorkspace - 1) / shownCount)
readonly property var specialWorkspace: liveMonitorData?.specialWorkspace
readonly property string specialWorkspaceName: specialWorkspace.name.replace("special:", "")
readonly property bool specialWorkspaceActive: specialWorkspaceName !== ""
property list<bool> occupied: []
property list<var> biggestWindow: occupied.map((_, index) => {
const wsId = getWorkspaceIdAt(index);
var biggestWindow = HyprlandData.biggestWindowForWorkspace(wsId);
return biggestWindow;
})
function getWorkspaceId(group, index) {
return group * root.shownCount + index + 1;
}
function getWorkspaceIdAt(index) {
return root.getWorkspaceId(root.group, index);
}
// Function to update workspaceOccupied
function updateWorkspaceOccupied() {
root.occupied = Array.from({
length: root.shownCount
}, (_, i) => {
const thisWorkspaceId = getWorkspaceId(root.group, i);
return Hyprland.workspaces.values.some(ws => ws.id === thisWorkspaceId);
});
}
// Occupied workspace updates
Component.onCompleted: updateWorkspaceOccupied()
Connections {
target: Hyprland.workspaces
function onValuesChanged() {
root.updateWorkspaceOccupied();
}
}
Connections {
target: Hyprland
function onFocusedWorkspaceChanged() {
root.updateWorkspaceOccupied();
}
}
onGroupChanged: {
updateWorkspaceOccupied();
}
}
@@ -8,7 +8,7 @@ QuickToggleModel {
name: Translation.tr("Internet") name: Translation.tr("Internet")
statusText: Network.networkName statusText: Network.networkName
tooltipText: Translation.tr("%1 | Right-click to configure").arg(Network.networkName) tooltipText: Translation.tr("%1 | Right-click to configure").arg(Network.networkName)
icon: Icons.getNetworkMaterialSymbol() icon: Network.materialSymbol
toggled: Network.wifiStatus !== "disabled" toggled: Network.wifiStatus !== "disabled"
mainAction: () => Network.toggleWifi() mainAction: () => Network.toggleWifi()
@@ -1,19 +0,0 @@
pragma ComponentBehavior: Bound
import QtQuick
import ".."
Item {
id: root
property real progress: 0
default property Item child
implicitWidth: child.implicitWidth
implicitHeight: child.implicitHeight
children: [child]
property var animation: Appearance.animation.elementMoveSmall.numberAnimation.createObject(this)
Behavior on progress {
animation: root.animation
}
}
@@ -1,62 +0,0 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Controls
Control {
id: root
property list<real> valueWeights: [1]
property list<real> values: [0.5]
property list<color> valueHighlights: ["white"]
property list<color> valueTroughs: []
readonly property list<real> normalizedValueWeights: {
const totalWeight = valueWeights.reduce((sum, weight) => sum + weight, 0)
return valueWeights.map(weight => weight / totalWeight)
}
readonly property list<real> visualEnds: {
let cumsum = 0;
let positions = [];
for (let i = 0; i < normalizedValueWeights.length; i++) {
cumsum += normalizedValueWeights[i];
positions.push(cumsum);
}
return positions;
}
readonly property list<real> visualPositions: {
let positions = [];
let lastEnd = 0;
for(let i = 0; i < visualEnds.length; i++) {
const thisEnd = visualEnds[i];
const width = thisEnd - lastEnd;
const thisPos = lastEnd + width * values[i];
positions.push(thisPos);
lastEnd = visualEnds[i];
}
return positions;
}
readonly property list<var> visualSegments: {
let segs = [];
let lastEnd = 0;
for(let i = 0; i < visualEnds.length; i++) {
const thisEnd = visualEnds[i];
const thisPos = visualPositions[i];
segs.push([lastEnd, thisPos]);
segs.push([thisPos, thisEnd]);
lastEnd = visualEnds[i];
}
return segs;
}
readonly property list<color> segmentColors: {
var cols = [];
for(let i = 0; i < valueHighlights.length; i++) {
cols.push(valueHighlights[i]);
cols.push(valueTroughs[i]);
}
return cols;
}
}
@@ -1,15 +0,0 @@
import QtQuick
import org.kde.kirigami as Kirigami
import qs.services
import qs.modules.common
Kirigami.Icon {
id: root
property real implicitSize: 26
implicitWidth: implicitSize
implicitHeight: implicitSize
roundToIconSize: false
animated: true // It's just fading from one icon to another
}
@@ -1,13 +0,0 @@
pragma ComponentBehavior: Bound
import QtQuick
StyledRectangle {
property bool vertical: false
property real startRadius
property real endRadius
topLeftRadius: startRadius
topRightRadius: vertical ? startRadius : endRadius
bottomLeftRadius: vertical ? endRadius : startRadius
bottomRightRadius: endRadius
}
@@ -1,12 +0,0 @@
import QtQuick
RectangularContainerShape {
property bool vertical: false
property real startRadius
property real endRadius
topLeftRadius: startRadius
topRightRadius: vertical ? startRadius : endRadius
bottomLeftRadius: vertical ? endRadius : startRadius
bottomRightRadius: endRadius
}
@@ -1,16 +0,0 @@
pragma ComponentBehavior: Bound
import QtQuick
// A type that's both capable of being rows and columns
// Qt Row is just a locked down Grid smh
// Calling it a Box because that's how row-or-column widget is called in Gtk
Grid {
id: root
property bool vertical: false
columns: vertical ? 1 : -1
rows: vertical ? -1 : 1
property alias spacing: root.rowSpacing
columnSpacing: rowSpacing
}
@@ -1,18 +0,0 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
// Box, Layout version
// A type that's both capable of being rows and columns
// Qt Row is just a locked down Grid smh
// Calling it a Box because that's how row-or-column widget is called in Gtk
GridLayout {
id: root
property bool vertical: false
columns: vertical ? 1 : -1
rows: vertical ? -1 : 1
property alias spacing: root.rowSpacing
columnSpacing: rowSpacing
}
@@ -1,9 +0,0 @@
pragma ComponentBehavior: Bound
import QtQuick
// MouseArea that contains good defaults for buttons
MouseArea {
id: root
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
}
@@ -1,36 +0,0 @@
pragma ComponentBehavior: Bound
import QtQuick
/**
* Replacement for QtQuick Controls DayOfWeek row.
* I have to do this because that one is somehow really unreliable in my dynamically loaded widget
*/
Row {
id: root
property Component delegate
property alias model: repeater.model
property var locale: Qt.locale()
readonly property var firstDayOfWeek: locale.firstDayOfWeek
Repeater {
id: repeater
model: Array.from({
length: 7
}, (_, i) => {
const day = (root.firstDayOfWeek + i + 7 - 1) % 7 + 1
return ({
// Convert Locale day of week enum values to that of Qt enum values for
// consistency with DayOfWeekRow. Note that Locale day of week enum values are 0-indexed,
// while Qt day of week enum values are 1-indexed.
// Refererences:
// Locale enum values: https://doc.qt.io/qt-6/qml-qtqml-locale.html#firstDayOfWeek-prop
// DayOfWeek model values: https://doc.qt.io/qt-6/qml-qtquick-controls-dayofweekrow.html#delegate-prop
// which mentions the enum values in the Qt namespace at: https://doc.qt.io/qt-6/qt.html#DayOfWeek-enum
day: day,
shortName: root.locale.toString(new Date(2024, 0, day), "ddd")
})
})
delegate: root.delegate
}
}
@@ -1,10 +1,15 @@
pragma ComponentBehavior: Bound pragma ComponentBehavior: Bound
import QtQml import QtQml
import QtQuick import QtQuick
import QtQuick.Controls
import QtQuick.Layouts import QtQuick.Layouts
import Quickshell
import qs
import qs.services
import qs.modules.common import qs.modules.common
import qs.modules.common.widgets import qs.modules.common.widgets
import qs.modules.common.functions import qs.modules.common.functions
import qs.modules.waffle.looks
Item { Item {
id: root id: root
@@ -31,23 +36,15 @@ Item {
const diffWeeks = Math.round(diffMillis / root.millisPerWeek); const diffWeeks = Math.round(diffMillis / root.millisPerWeek);
root.targetWeekDiff += diffWeeks; root.targetWeekDiff += diffWeeks;
} }
function scrollToToday() {
root.targetWeekDiff = 0;
}
property int weeksPerScroll: 1 property int weeksPerScroll: 1
property real targetWeekDiff: 0 property real targetWeekDiff: 0
property real weekDiff: targetWeekDiff property real weekDiff: targetWeekDiff
property int contentWeekDiff: weekDiff // whole part of weekDiff property int contentWeekDiff: weekDiff // whole part of weekDiff
property bool scrolling: false property bool scrolling: false
property Animation scrollAnimation: NumberAnimation {
duration: Config.options.calendar.animate ? Appearance.animation.scroll.duration : 0
easing.type: Appearance.animation.scroll.type
easing.bezierCurve: Appearance.animation.scroll.bezierCurve
}
Behavior on weekDiff { Behavior on weekDiff {
id: weekScrollBehavior id: weekScrollBehavior
animation: root.scrollAnimation animation: Looks.transition.scroll.createObject(this)
} }
Timer { Timer {
id: scrollAnimationCheckTimer id: scrollAnimationCheckTimer
@@ -59,19 +56,11 @@ Item {
scrollAnimationCheckTimer.restart(); scrollAnimationCheckTimer.restart();
} }
property var wheelAction: (wheel) => {
// Reverse cuz scrolling down should advance
const sign = wheel.angleDelta.y / 120 * -1;
if (Config.options.calendar.weekScrollPrecision) {
root.targetWeekDiff += sign * root.weeksPerScroll;
} else {
scrollMonthsAndSnap(sign);
}
}
MouseArea { MouseArea {
id: mouseArea
anchors.fill: parent anchors.fill: parent
onWheel: (wheel) => root.wheelAction(wheel) onWheel: wheel => {
root.targetWeekDiff += wheel.angleDelta.y / 120 * -root.weeksPerScroll; // Reverse cuz scrolling down should advance
}
} }
// Date calculations // Date calculations
@@ -93,16 +82,14 @@ Item {
return DateUtils.getIthDayDateOfSameWeek(dateInTargetWeek, root.focusDayOfWeekIndex - root.locale.firstDayOfWeek, root.locale.firstdayOfWeek); // 4 = Thursday return DateUtils.getIthDayDateOfSameWeek(dateInTargetWeek, root.focusDayOfWeekIndex - root.locale.firstDayOfWeek, root.locale.firstdayOfWeek); // 4 = Thursday
} }
property int focusedMonth: focusedDate.getMonth() + 1 // 0-indexed -> 1-indexed property int focusedMonth: focusedDate.getMonth() + 1 // 0-indexed -> 1-indexed
property string title: locale.toString(focusedDate, "MMMM yyyy")
// Sizes // Sizes
property real verticalPadding: 0 property real verticalPadding: 0
property real horizontalPadding: 0
property real buttonSize: 40 property real buttonSize: 40
property real buttonSpacing: 2 property real buttonSpacing: 2
property real buttonVerticalSpacing: buttonSpacing property real buttonVerticalSpacing: buttonSpacing
implicitHeight: (6 * buttonSize) + (5 * buttonVerticalSpacing) + (2 * verticalPadding) implicitHeight: (6 * buttonSize) + (5 * buttonVerticalSpacing) + (2 * verticalPadding)
implicitWidth: weeksColumn.implicitWidth + (2 * horizontalPadding) implicitWidth: weeksColumn.implicitWidth
clip: true clip: true
ColumnLayout { ColumnLayout {
@@ -110,8 +97,6 @@ Item {
anchors { anchors {
left: parent.left left: parent.left
right: parent.right right: parent.right
leftMargin: root.horizontalPadding
rightMargin: root.horizontalPadding
} }
y: { y: {
const spacePerExtraRow = root.buttonSize + root.buttonVerticalSpacing; const spacePerExtraRow = root.buttonSize + root.buttonVerticalSpacing;
@@ -1,41 +0,0 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
GridLayout {
id: root
columns: 1
property real totalDuration: 250
property real interval: totalDuration / count
property list<QtObject> choreographableChildren: children.filter(c => {
return c.hasOwnProperty("progress")
})
readonly property int count: choreographableChildren.length
property bool shown: false
onShownChanged: {
// When hiding, hide all at once
if (!shown) {
for (var i = 0; i < count; i++) {
choreographableChildren[i].progress = 0;
}
}
// When showing, choreograph
root.choreographIndex = 0;
}
property int choreographIndex: count
Timer {
id: choreographTimer
interval: root.interval
property bool step: root.shown && root.choreographIndex < root.count
running: step
repeat: step
onTriggered: {
const index = root.choreographIndex;
root.choreographableChildren[index].progress = 1;
root.choreographIndex++;
}
}
}
@@ -1,7 +0,0 @@
pragma ComponentBehavior: Bound
import QtQuick
FadeLoader {
id: root
onActiveChanged: if (active) item.shown = true
}
@@ -1,7 +1,7 @@
import QtQuick import QtQuick
Rectangle { Rectangle {
property real diameter property double diameter
implicitWidth: diameter implicitWidth: diameter
implicitHeight: diameter implicitHeight: diameter
@@ -42,7 +42,8 @@ Item {
active: root.fill active: root.fill
anchors.fill: parent anchors.fill: parent
sourceComponent: Circle { sourceComponent: Rectangle {
radius: 9999
color: root.colSecondary color: root.colSecondary
} }
} }
@@ -3,7 +3,7 @@ import qs.modules.common.functions
import qs.modules.common.widgets import qs.modules.common.widgets
import QtQuick import QtQuick
import QtQuick.Controls import QtQuick.Controls
import QtQuick.Effects import Qt5Compat.GraphicalEffects
/** /**
* A progress bar with both ends rounded and text acts as clipping like OneUI 7's battery indicator. * A progress bar with both ends rounded and text acts as clipping like OneUI 7's battery indicator.
@@ -16,17 +16,15 @@ ProgressBar {
property color highlightColor: Appearance?.colors.colOnSecondaryContainer ?? "#685496" property color highlightColor: Appearance?.colors.colOnSecondaryContainer ?? "#685496"
property color trackColor: ColorUtils.transparentize(highlightColor, 0.5) ?? "#F1D3F9" property color trackColor: ColorUtils.transparentize(highlightColor, 0.5) ?? "#F1D3F9"
property alias radius: contentItem.radius property alias radius: contentItem.radius
property alias progressRadius: progressFill.radius
property string text property string text
default property Item textMask: Item { default property Item textMask: Item {
width: root.valueBarWidth width: valueBarWidth
height: root.valueBarHeight height: valueBarHeight
VisuallyCenteredStyledText { StyledText {
anchors.fill: parent anchors.centerIn: parent
font: root.font font: root.font
text: root.text text: root.text
} }
layer.enabled: true
} }
text: Math.round(value * 100) text: Math.round(value * 100)
@@ -40,9 +38,10 @@ ProgressBar {
implicitWidth: valueBarWidth implicitWidth: valueBarWidth
} }
contentItem: Pill { contentItem: Rectangle {
id: contentItem id: contentItem
anchors.fill: parent anchors.fill: parent
radius: 9999
color: root.trackColor color: root.trackColor
visible: false visible: false
@@ -81,40 +80,22 @@ ProgressBar {
} }
} }
Rectangle { OpacityMask {
id: contentMaskRect id: roundingMask
anchors.fill: contentItem
width: contentItem.width
height: contentItem.height
radius: contentItem.radius
layer.enabled: true
visible: false visible: false
}
Item {
// textMask has to be rendered somewhere so we put it in a practically invisible item
anchors.centerIn: parent
opacity: 0
Component.onCompleted: root.textMask.layer.enabled = true // for multieffect masking
children: [root.textMask]
}
MaskMultiEffect {
id: boxClip
anchors.fill: parent anchors.fill: parent
source: contentItem source: contentItem
maskSource: contentMaskRect maskSource: Rectangle {
visible: false width: contentItem.width
height: contentItem.height
radius: contentItem.radius
}
} }
MaskMultiEffect { OpacityMask {
id: textClip
anchors.fill: parent anchors.fill: parent
implicitWidth: contentItem.implicitWidth source: roundingMask
implicitHeight: contentItem.implicitHeight invert: true
source: boxClip
maskSource: root.textMask maskSource: root.textMask
maskInverted: true
} }
} }
@@ -1,14 +0,0 @@
import QtQuick
import QtQuick.Effects
import qs.modules.common
MultiEffect {
property color sourceColor: "black"
colorization: 1
brightness: 1 - sourceColor.hslLightness
Behavior on colorizationColor {
animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this)
}
}
@@ -1,75 +0,0 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Shapes
import qs.modules.common
AbstractCombinedProgressBar {
id: root
property int implicitSize: 30
property int lineWidth: 2
property real gapAngle: 360 / 18
valueHighlights: [Appearance.colors.colPrimary, Appearance.colors.colTertiary]
valueTroughs: [Appearance.colors.colSecondaryContainer, Appearance.colors.colTertiaryContainer]
property bool enableAnimation: true
property int animationDuration: 800
property var easingType: Easing.OutCubic
implicitWidth: implicitSize
implicitHeight: implicitSize
readonly property real centerX: root.width / 2
readonly property real centerY: root.height / 2
readonly property real arcRadius: root.implicitSize / 2 - root.lineWidth
readonly property real startAngle: -90
background: Item {
implicitWidth: root.implicitSize
implicitHeight: root.implicitSize
}
function isNegligibleSegment(seg: var): bool {
const range = seg[1] - seg[0];
return range < 1 / 360; // TODO make this less arbitrary
}
Repeater {
model: root.visualSegments
delegate: Shape {
id: segShape
required property int index
required property var modelData
property bool negligible: root.isNegligibleSegment(modelData)
property bool atStart: index == 0
property bool atEnd: index == root.visualSegments.length - 1
property real displaySegStart: {
var i = index;
while ((i > 0 && root.isNegligibleSegment(root.visualSegments[i - 1])))
i--;
return root.visualSegments[i][0];
}
anchors.fill: parent
layer.enabled: true
layer.smooth: true
preferredRendererType: Shape.CurveRenderer
ShapePath {
strokeColor: segShape.negligible ? "transparent" : root.segmentColors[segShape.index % root.segmentColors.length]
strokeWidth: segShape.negligible ? 0 : root.lineWidth
capStyle: ShapePath.RoundCap
fillColor: "transparent"
PathAngleArc {
centerX: root.centerX
centerY: root.centerY
radiusX: root.arcRadius
radiusY: root.arcRadius
startAngle: root.startAngle + 360 * segShape.displaySegStart + root.gapAngle / 2
sweepAngle: 360 * (segShape.modelData[1] - segShape.displaySegStart) - root.gapAngle
}
}
}
}
}
@@ -23,10 +23,6 @@ Flow {
] ]
property var currentValue: null property var currentValue: null
function focusSelectedChild() {
children.find(c => c.value == currentValue).forceActiveFocus()
}
signal selected(var newValue) signal selected(var newValue)
Repeater { Repeater {
@@ -35,7 +31,6 @@ Flow {
id: paletteButton id: paletteButton
required property var modelData required property var modelData
required property int index required property int index
readonly property var value: modelData.value
onYChanged: { onYChanged: {
if (index === 0) { if (index === 0) {
paletteButton.leftmost = true paletteButton.leftmost = true
@@ -33,7 +33,6 @@ RippleButton {
} }
StyledSwitch { StyledSwitch {
id: switchWidget id: switchWidget
focusPolicy: Qt.NoFocus
down: root.down down: root.down
Layout.fillWidth: false Layout.fillWidth: false
checked: root.checked checked: root.checked
@@ -1,25 +0,0 @@
import QtQuick
import Quickshell
import qs.modules.common
Item {
id: root
property alias load: loader.activeAsync
property bool shown: true // By default show immediately when loaded
property alias component: loader.component
property alias fade: opacityBehavior.enabled
property alias animation: opacityBehavior.animation
opacity: loader.active && shown ? 1 : 0
visible: opacity > 0
Behavior on opacity {
id: opacityBehavior
animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
}
LazyLoader {
id: loader
}
}
@@ -1,25 +0,0 @@
import QtQuick
Loader {
id: root
property int fallbackIndex: 0
property list<url> fallbacks: []
property list<Component> fallbackComponents: []
onStatusChanged: {
if (status === Loader.Error && fallbackIndex < fallbacks.length) {
if (fallbacks[fallbackIndex]) {
source = fallbacks[fallbackIndex];
if (fallbackComponents[fallbackIndex]) {
console.warn("[FallbackLoader] Both fallbacks urls and components are set, using url fallback");
}
} else if (fallbackComponents[fallbackIndex]) {
sourceComponent = fallbackComponents[fallbackIndex];
} else {
console.error("[FallbackLoader] Out of fallbacks, tried all", fallbackIndex);
}
fallbackIndex += 1;
}
}
}
@@ -1,16 +0,0 @@
import QtQuick
Item {
id: root
property alias longestText: longestTextMetrics.text
property alias font: longestTextMetrics.font
implicitWidth: longestTextMetrics.width
implicitHeight: longestTextMetrics.height
TextMetrics {
id: longestTextMetrics
text: root.longestText
}
}
@@ -1,26 +0,0 @@
pragma ComponentBehavior: Bound
import QtQuick
AbstractChoreographable {
id: root
progress: 0
property bool vertical: true
property bool reverseDirection: false
property real distance: 15
readonly property real directionMultiplier: reverseDirection ? -1 : 1
Component.onCompleted: syncProgress()
onProgressChanged: syncProgress()
function syncProgress() {
const progressDistance = distance * (1 - progress) * directionMultiplier;
root.child.opacity = progress
if (vertical) {
root.child.y = progressDistance
} else {
root.child.x = progressDistance
}
}
}
@@ -42,7 +42,6 @@ Button {
property color colBackgroundToggled: Appearance?.colors.colPrimary ?? "#65558F" property color colBackgroundToggled: Appearance?.colors.colPrimary ?? "#65558F"
property color colBackgroundToggledHover: Appearance?.colors.colPrimaryHover ?? "#77699C" property color colBackgroundToggledHover: Appearance?.colors.colPrimaryHover ?? "#77699C"
property color colBackgroundToggledActive: Appearance?.colors.colPrimaryActive ?? "#D6CEE2" property color colBackgroundToggledActive: Appearance?.colors.colPrimaryActive ?? "#D6CEE2"
property color colFocusRing: Appearance.colors.colOnSecondaryContainer
property real radius: root.down ? root.buttonRadiusPressed : root.buttonRadius property real radius: root.down ? root.buttonRadiusPressed : root.buttonRadius
property real leftRadius: root.down ? root.buttonRadiusPressed : root.buttonRadius property real leftRadius: root.down ? root.buttonRadiusPressed : root.buttonRadius
@@ -118,6 +117,7 @@ Button {
}; };
} }
property bool tabbedTo: root.focus && (focusReason === Qt.TabFocusReason || focusReason === Qt.BacktabFocusReason)
background: Rectangle { background: Rectangle {
id: buttonBackground id: buttonBackground
topLeftRadius: root.leftRadius topLeftRadius: root.leftRadius
@@ -130,25 +130,9 @@ Button {
Behavior on color { Behavior on color {
animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this) animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this)
} }
}
z: visualFocus ? 1 : 0 border.width: root.tabbedTo ? 2 : 0
Rectangle { border.color: Appearance.colors.colSecondary
id: focusRing
topLeftRadius: root.leftRadius - anchors.margins
topRightRadius: root.rightRadius - anchors.margins
bottomLeftRadius: root.leftRadius - anchors.margins
bottomRightRadius: root.rightRadius - anchors.margins
visible: root.visualFocus
color: "transparent"
anchors {
fill: parent
margins: -4
}
border {
color: root.colFocusRing
width: 2
}
} }
contentItem: StyledText { contentItem: StyledText {
@@ -20,12 +20,11 @@ StyledText {
} }
} }
property Animation fillAnimation: NumberAnimation {
duration: Appearance?.animation.elementMoveFast.duration ?? 200
easing.type: Appearance?.animation.elementMoveFast.type ?? Easing.BezierSpline
easing.bezierCurve: Appearance?.animation.elementMoveFast.bezierCurve ?? [0.34, 0.80, 0.34, 1.00, 1, 1]
}
Behavior on fill { // Leaky leaky, no good Behavior on fill { // Leaky leaky, no good
animation: root.fillAnimation NumberAnimation {
duration: Appearance?.animation.elementMoveFast.duration ?? 200
easing.type: Appearance?.animation.elementMoveFast.type ?? Easing.BezierSpline
easing.bezierCurve: Appearance?.animation.elementMoveFast.bezierCurve ?? [0.34, 0.80, 0.34, 1.00, 1, 1]
}
} }
} }
@@ -85,7 +85,7 @@ MouseArea { // Notification group area
automaticallyReset: false automaticallyReset: false
acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton
onPressed: (mouse) => { onPressed: {
if (mouse.button === Qt.RightButton) if (mouse.button === Qt.RightButton)
root.toggleExpanded(); root.toggleExpanded();
} }
@@ -1,5 +0,0 @@
import QtQuick
Rectangle {
radius: Math.min(width, height) / 2
}
@@ -1,91 +0,0 @@
import QtQuick
import qs.modules.common.models as M
import "shapes/material-shapes.js" as MaterialShapes
import "shapes/shapes/corner-rounding.js" as CornerRounding
import "shapes/geometry/offset.js" as Offset
// For returning the points
M.NestableObject {
id: root
required property real width
required property real height
property real radius: 0
property real topLeftRadius: radius
property real topRightRadius: radius
property real bottomLeftRadius: radius
property real bottomRightRadius: radius
property real xOffset: 0
property real yOffset: 0
readonly property real radiusLimit: Math.min(width, height) / 2
readonly property real effectiveTopLeftRadius: Math.min(topLeftRadius, radiusLimit)
readonly property real effectiveTopRightRadius: Math.min(topRightRadius, radiusLimit)
readonly property real effectiveBottomLeftRadius: Math.min(bottomLeftRadius, radiusLimit)
readonly property real effectiveBottomRightRadius: Math.min(bottomRightRadius, radiusLimit)
// Clockwise starting from bottom
property list<var> bottomPoints: [
new MaterialShapes.PointNRound(new Offset.Offset(xOffset + width - effectiveBottomRightRadius, yOffset + height), new CornerRounding.CornerRounding(0)),
new MaterialShapes.PointNRound(new Offset.Offset(xOffset + width / 2, yOffset + height), new CornerRounding.CornerRounding(0)),
new MaterialShapes.PointNRound(new Offset.Offset(xOffset + effectiveBottomLeftRadius, yOffset + height), new CornerRounding.CornerRounding(0)),
]
property list<var> leftPoints: [
new MaterialShapes.PointNRound(new Offset.Offset(xOffset + 0, yOffset + height - effectiveBottomLeftRadius), new CornerRounding.CornerRounding(0)),
new MaterialShapes.PointNRound(new Offset.Offset(xOffset + 0, yOffset + height / 2), new CornerRounding.CornerRounding(0)),
new MaterialShapes.PointNRound(new Offset.Offset(xOffset + 0, yOffset + effectiveTopLeftRadius), new CornerRounding.CornerRounding(0)),
]
property list<var> topPoints: [
new MaterialShapes.PointNRound(new Offset.Offset(xOffset + effectiveTopLeftRadius, yOffset + 0), new CornerRounding.CornerRounding(0)),
new MaterialShapes.PointNRound(new Offset.Offset(xOffset + width / 2, yOffset + 0), new CornerRounding.CornerRounding(0)),
new MaterialShapes.PointNRound(new Offset.Offset(xOffset + width - effectiveTopRightRadius, yOffset + 0), new CornerRounding.CornerRounding(0)),
]
property list<var> rightPoints: [
new MaterialShapes.PointNRound(new Offset.Offset(xOffset + width, yOffset + effectiveTopRightRadius), new CornerRounding.CornerRounding(0)),
new MaterialShapes.PointNRound(new Offset.Offset(xOffset + width, yOffset + height / 2), new CornerRounding.CornerRounding(0)),
new MaterialShapes.PointNRound(new Offset.Offset(xOffset + width, yOffset + height - effectiveBottomRightRadius), new CornerRounding.CornerRounding(0)),
]
function getFirstBottomPoints() {
return bottomPoints.slice(Math.floor(bottomPoints.length / 2))
}
function getLastBottomPoints() {
return bottomPoints.slice(0, Math.floor(bottomPoints.length / 2))
}
function getBottomLeftPoint(extraXOffset = 0, extraYOffset = 0, radius = undefined) {
if (radius === undefined) radius = effectiveBottomLeftRadius;
return new MaterialShapes.PointNRound(new Offset.Offset(xOffset + extraXOffset + 0, yOffset + extraYOffset + height), new CornerRounding.CornerRounding(radius))
}
function getTopLeftPoint(extraXOffset = 0, extraYOffset = 0, radius = undefined) {
if (radius === undefined) radius = effectiveTopLeftRadius;
return new MaterialShapes.PointNRound(new Offset.Offset(xOffset + extraXOffset + 0, yOffset + extraYOffset + 0), new CornerRounding.CornerRounding(radius))
}
function getTopRightPoint(extraXOffset = 0, extraYOffset = 0, radius = undefined) {
if (radius === undefined) radius = effectiveTopRightRadius;
return new MaterialShapes.PointNRound(new Offset.Offset(xOffset + extraXOffset + width, yOffset + extraYOffset + 0), new CornerRounding.CornerRounding(radius))
}
function getBottomRightPoint(extraXOffset = 0, extraYOffset = 0, radius = undefined) {
if (radius === undefined) radius = effectiveBottomRightRadius;
return new MaterialShapes.PointNRound(new Offset.Offset(xOffset + extraXOffset + width, yOffset + extraYOffset + height), new CornerRounding.CornerRounding(radius))
}
function getFullShape() {
const points = [
...getFirstBottomPoints(),
getBottomLeftPoint(),
...leftPoints,
getTopLeftPoint(),
...topPoints,
getTopRightPoint(),
...rightPoints,
getBottomRightPoint(),
...getLastBottomPoints(),
]
return MaterialShapes.customPolygon(points);
}
}
@@ -29,7 +29,6 @@ Button {
property color colBackgroundToggledHover: Appearance?.colors.colPrimaryHover ?? "#77699C" property color colBackgroundToggledHover: Appearance?.colors.colPrimaryHover ?? "#77699C"
property color colRipple: Appearance?.colors.colLayer1Active ?? "#D6CEE2" property color colRipple: Appearance?.colors.colLayer1Active ?? "#D6CEE2"
property color colRippleToggled: Appearance?.colors.colPrimaryActive ?? "#D6CEE2" property color colRippleToggled: Appearance?.colors.colPrimaryActive ?? "#D6CEE2"
property color colFocusRing: Appearance.colors.colOnSecondaryContainer
opacity: root.enabled ? 1 : 0.4 opacity: root.enabled ? 1 : 0.4
property color buttonColor: ColorUtils.transparentize(root.toggled ? property color buttonColor: ColorUtils.transparentize(root.toggled ?
@@ -181,22 +180,6 @@ Button {
} }
} }
z: visualFocus ? 1 : 0
Rectangle {
id: focusRing
radius: buttonBackground.radius - anchors.margins
visible: root.visualFocus
color: "transparent"
anchors {
fill: parent
margins: -4
}
border {
color: root.colFocusRing
width: 2
}
}
contentItem: StyledText { contentItem: StyledText {
text: root.buttonText text: root.buttonText
} }
@@ -1,19 +0,0 @@
import QtQuick
Rectangle {
id: root
// https://m3.material.io/foundations/interaction/states/state-layers
enum State {
Hover, Focus, Press, Drag
}
property var state: StateLayer.State.Hover
opacity: switch(state) {
case StateLayer.State.Hover: return 0.08;
case StateLayer.State.Focus: return 0.1;
case StateLayer.State.Press: return 0.1;
case StateLayer.State.Drag: return 0.16;
default: return 0;
}
}
@@ -1,66 +0,0 @@
pragma ComponentBehavior: Bound
import QtQuick
import qs.modules.common as C
Rectangle {
id: root
property bool hover: false
property bool press: false
property bool drag: false
property color contentColor: C.Appearance.m3colors.m3onBackground
color: "transparent"
FadeLoader {
id: hoverLoader
anchors.fill: parent
shown: root.hover
sourceComponent: StateLayer {
state: StateLayer.State.Hover
color: root.contentColor
topLeftRadius: root.topLeftRadius
topRightRadius: root.topRightRadius
bottomLeftRadius: root.bottomLeftRadius
bottomRightRadius: root.bottomRightRadius
}
}
FadeLoader {
id: focusLoader
anchors.fill: parent
shown: root.focus
sourceComponent: StateLayer {
state: StateLayer.State.Focus
color: root.contentColor
topLeftRadius: root.topLeftRadius
topRightRadius: root.topRightRadius
bottomLeftRadius: root.bottomLeftRadius
bottomRightRadius: root.bottomRightRadius
}
}
FadeLoader {
id: pressLoader
anchors.fill: parent
shown: root.press
sourceComponent: StateLayer {
state: StateLayer.State.Press
color: root.contentColor
topLeftRadius: root.topLeftRadius
topRightRadius: root.topRightRadius
bottomLeftRadius: root.bottomLeftRadius
bottomRightRadius: root.bottomRightRadius
}
}
FadeLoader {
id: dragLoader
anchors.fill: parent
shown: root.drag
sourceComponent: StateLayer {
state: StateLayer.State.Drag
color: root.contentColor
topLeftRadius: root.topLeftRadius
topRightRadius: root.topRightRadius
bottomLeftRadius: root.bottomLeftRadius
bottomRightRadius: root.bottomRightRadius
}
}
}
@@ -1,81 +0,0 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Controls
import ".."
import "../functions"
Button {
id: root
property alias radius: bg.radius
property alias contentLayer: bg.contentLayer
property color colFocusRing: Appearance.colors.colOnSecondaryContainer
property color colBackground: checked ? colBackgroundChecked : colBackgroundUnchecked
property color colForeground: checked ? colForegroundChecked : colForegroundUnchecked
property color colBackgroundUnchecked: ColorUtils.transparentize(Appearance.colors.colLayer4Base, 1)
property color colBackgroundChecked: Appearance.colors.colPrimary
property color colForegroundUnchecked: Appearance.colors.colOnLayer4
property color colForegroundChecked: Appearance.colors.colOnPrimary
hoverEnabled: true
opacity: root.enabled ? 1 : 0.5
Behavior on colBackground {
animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this)
}
Behavior on colForeground {
animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this)
}
HoverHandler {
cursorShape: root.enabled ? Qt.PointingHandCursor : Qt.ArrowCursor
}
background: StyledRectangle {
id: bg
implicitHeight: root.contentItem.implicitHeight
implicitWidth: root.contentItem.implicitWidth
radius: Math.min(width, height) / 2
color: root.colBackground
StateOverlay {
anchors.fill: parent
hover: root.hovered && root.enabled
press: root.pressed && root.enabled
focus: false // We use a ring instead
radius: bg.radius
}
Rectangle {
id: focusRing
radius: bg.radius - anchors.margins
visible: root.visualFocus
color: "transparent"
anchors {
fill: parent
margins: -4
}
border {
color: root.colFocusRing
width: 2
}
}
}
z: visualFocus ? 1 : 0
contentItem: Item {
implicitWidth: buttonText.implicitWidth
implicitHeight: buttonText.implicitHeight
VisuallyCenteredStyledText {
id: buttonText
anchors.centerIn: parent
text: root.text
color: root.colForeground
}
}
}
@@ -1,73 +0,0 @@
pragma ComponentBehavior: Bound
import QtQuick
import qs.modules.common
AbstractCombinedProgressBar {
id: root
property real valueBarWidth: 120
property real valueBarHeight: 4
property real valueBarGap: 4
property real valueBarInnerRadius: Appearance.rounding.unsharpen
valueHighlights: [Appearance.colors.colPrimary, Appearance.colors.colTertiary]
valueTroughs: [Appearance.colors.colSecondaryContainer, Appearance.colors.colTertiaryContainer]
background: Item {
implicitWidth: root.valueBarWidth
implicitHeight: root.valueBarHeight
}
// "negligible" = too small that it'd look weird when shown
function isNegligibleSegment(seg: var): bool {
const wdth = seg[1] - seg[0];
const visualWidth = availableWidth * wdth;
return (visualWidth <= valueBarGap + valueBarHeight)
}
contentItem: Item {
Repeater {
model: root.visualSegments
delegate: Rectangle {
required property int index
required property var modelData
visible: !root.isNegligibleSegment(modelData)
property bool atStart: index == 0
property bool atEnd: index == root.visualSegments.length - 1
property real displaySegStart: { // swallow previous segments if they're "negligible"
var i = index;
while ((i > 0 && root.isNegligibleSegment(root.visualSegments[i-1])))
i--;
return root.visualSegments[i][0]
}
anchors {
top: parent.top
bottom: parent.bottom
}
x: {
var result = root.availableWidth * displaySegStart;
if (!atStart) result += root.valueBarGap / 2;
return result;
}
width: {
var result = root.availableWidth * (modelData[1] - displaySegStart)
if (atStart || atEnd) result -= root.valueBarGap / 2;
else result -= root.valueBarGap;
return result;
}
color: root.segmentColors[index % root.segmentColors.length]
property real startRadius: atStart ? height / 2 : root.valueBarInnerRadius
property real endRadius: atEnd ? height / 2 : root.valueBarInnerRadius
topLeftRadius: startRadius
bottomLeftRadius: startRadius
topRightRadius: endRadius
bottomRightRadius: endRadius
}
}
}
}
@@ -1,19 +0,0 @@
pragma ComponentBehavior: Bound
import QtQuick
StyledButton {
id: root
property alias implicitSize: root.implicitHeight
implicitWidth: implicitHeight
property alias iconSize: icon.iconSize
contentItem: Item {
MaterialSymbol {
id: icon
anchors.centerIn: parent
color: root.colForeground
text: root.text
}
}
}
@@ -1,22 +0,0 @@
import QtQuick
import qs.modules.common as C
// This is to enable future fancy styles for rectangles. Some ideas:
// - normal rounded rect
// - osk.sh
// - 3d
// i hope i actually get to this and not shrimply forget
// aaaaa i realized for this to work i would have to make this for shapes in general not just rects
Rectangle {
enum ContentLayer { Background, Pane, Group, Subgroup, Control }
property var contentLayer: StyledRectangle.ContentLayer.Pane // To appropriately add effects like shadows/3d-ization
color: switch(contentLayer) {
case StyledRectangle.ContentLayer.Background: C.Appearance.colors.colLayer0;
case StyledRectangle.ContentLayer.Pane: C.Appearance.colors.colLayer1;
case StyledRectangle.ContentLayer.Group: C.Appearance.colors.colLayer2;
case StyledRectangle.ContentLayer.Subgroup: C.Appearance.colors.colLayer3;
case StyledRectangle.ContentLayer.Control: C.Appearance.colors.colLayer4;
default: C.Appearance.colors.colLayer1;
}
}
@@ -23,7 +23,7 @@ Text {
component Anim: NumberAnimation { component Anim: NumberAnimation {
target: root target: root
duration: 130 duration: 300 / 2
easing.type: Easing.BezierSpline easing.type: Easing.BezierSpline
easing.bezierCurve: Appearance.animation.elementMoveFast.bezierCurve easing.bezierCurve: Appearance.animation.elementMoveFast.bezierCurve
} }
@@ -55,8 +55,7 @@ Text {
Anim { Anim {
property: "opacity" property: "opacity"
to: 0 to: 0
easing.type: Easing.BezierSpline easing.type: Easing.InSine
easing.bezierCurve: Appearance.animation.elementMoveFast.bezierCurve
} }
} }
PropertyAction {} // Tie the text update to this point (we don't want it to happen during the first slide+fade) PropertyAction {} // Tie the text update to this point (we don't want it to happen during the first slide+fade)
@@ -84,8 +83,7 @@ Text {
Anim { Anim {
property: "opacity" property: "opacity"
to: 1 to: 1
easing.type: Easing.BezierSpline easing.type: Easing.OutSine
easing.bezierCurve: Appearance.animation.elementMoveFast.bezierCurve
} }
} }
} }
@@ -1,17 +0,0 @@
import QtQuick
import qs.modules.common as C
FallbackLoader {
id: root
required property string componentName
property string context // Path for the builtin component
readonly property string componentNameWithExt: componentName.endsWith(".qml") ? componentName : `${componentName}.qml`
source: `${C.Directories.userComponents}/${componentNameWithExt}`
fallbacks: [
...(context ? [ `${context}/${componentNameWithExt}` ] : []),
componentNameWithExt
]
}
@@ -1,45 +0,0 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
Item {
id: root
property alias textWidget: textWidget
property alias text: textWidget.text
property alias horizontalAlignment: textWidget.horizontalAlignment
property alias verticalAlignment: textWidget.verticalAlignment
property alias font: textWidget.font
property alias color: textWidget.color
property alias elide: textWidget.elide
property alias wrapMode: textWidget.wrapMode
property alias animateChange: textWidget.animateChange
// In many cases the baseline is a bit high to accomodate the dangling parts of "g" and "y",
// making most text (especiall number-only text) not well-balanced.
// This adjusts the rounding to make sure the text gets lowered if not internally pixel-aligned
property bool lowerBias: true
implicitWidth: textMetrics.width
implicitHeight: textMetrics.height
TextMetrics {
id: textMetrics
font: root.font
text: root.text
}
StyledText {
id: textWidget
anchors {
left: parent.left
right: parent.right
}
y: {
const value = (parent.height - textMetrics.height) / 2;
return root.lowerBias ? Math.ceil(value) : Math.round(value);
}
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
}
}
@@ -1,73 +0,0 @@
import QtQuick
import Quickshell
import qs.services as S
/**
* Abstract morphed panel to be used in TopLayerPanel.
* Screen width and height are to be supplied when declared in the top layer panel
* Others are to be declared by panels deriving from this
*
* To make sure morph movements don't look weird:
* - Follow the convention of having points start from bottom-middle and go clockwise
* - Make sure the number of points is "balanced" in all directions
* - Tip: Sometimes symmetry is not enough. Try to have more intermediate points if ones you have are too spaced out and act funny.
*/
Item {
id: root
// To be fed
property int screenWidth: QsWindow.window.width
property int screenHeight: QsWindow.window.height
// Signals & loading
signal requestFocus()
signal dismissed()
signal focusGrabDismissed()
property bool load: true
property bool shown: true
// Some info
property int reservedTop: 0
property int reservedBottom: 0
property int reservedLeft: 0
property int reservedRight: 0
// Main stuff
property var backgroundPolygon
property list<Item> baseMaskItems: [root]
property list<Item> attachedMaskItems: []
property list<Item> maskItems: [...baseMaskItems, ...attachedMaskItems]
property Region maskRegion: Region {
regions: root.maskItems.map(item => regionComp.createObject(this, { "item": item }))
}
function addAttachedMaskItem(item) {
if (root.attachedMaskItems.includes(item)) return;
root.attachedMaskItems.push(item);
}
function removeAttachedMaskItem(item) {
root.attachedMaskItems = root.attachedMaskItems.filter(i => i !== item);
}
onAttachedMaskItemsChanged: {
if (attachedMaskItems.length > 0) {
S.GlobalFocusGrab.addDismissable(root.QsWindow.window);
} else {
S.GlobalFocusGrab.removeDismissable(root.QsWindow.window);
}
}
Connections {
target: S.GlobalFocusGrab
function onDismissed() {
root.attachedMaskItems = [];
root.focusGrabDismissed();
}
}
Component {
id: regionComp
Region {}
}
}
@@ -1,94 +0,0 @@
import QtQuick
import Quickshell.Hyprland
import qs
import qs.modules.common
import "../../common/widgets/shapes/material-shapes.js" as MaterialShapes
import "../../common/widgets/shapes/shapes/corner-rounding.js" as CornerRounding
import "../../common/widgets/shapes/geometry/offset.js" as Offset
HAbstractMorphedPanel {
id: root
// Own props
property int edgeGap: Appearance.sizes.hyprlandGapsOut
property real rounding: Appearance.rounding.windowRounding
property real contentHeight: 300 // For now
property real contentWidth: root.screenWidth * 0.9
property real horizontalGap: (root.screenWidth - contentWidth) / 2
// Background
backgroundPolygon: {
const bottom = Config.options.bar.bottom
const topY = bottom ? (root.screenHeight - edgeGap - contentHeight) : edgeGap
const bottomY = bottom ? (root.screenHeight - edgeGap) : (edgeGap + contentHeight)
const points = [
// bottom-middle
new MaterialShapes.PointNRound(new Offset.Offset(root.screenWidth / 2, bottomY), new CornerRounding.CornerRounding(0)),
// bottom-left
new MaterialShapes.PointNRound(new Offset.Offset(horizontalGap + rounding, bottomY), new CornerRounding.CornerRounding(rounding)),
new MaterialShapes.PointNRound(new Offset.Offset(horizontalGap, bottomY), new CornerRounding.CornerRounding(rounding)),
new MaterialShapes.PointNRound(new Offset.Offset(horizontalGap, bottomY - rounding), new CornerRounding.CornerRounding(rounding)),
// top-left
new MaterialShapes.PointNRound(new Offset.Offset(horizontalGap, topY + rounding), new CornerRounding.CornerRounding(rounding)),
new MaterialShapes.PointNRound(new Offset.Offset(horizontalGap, topY), new CornerRounding.CornerRounding(rounding)),
new MaterialShapes.PointNRound(new Offset.Offset(horizontalGap + rounding, topY), new CornerRounding.CornerRounding(rounding)),
// top-middle
new MaterialShapes.PointNRound(new Offset.Offset(root.screenWidth / 2, topY), new CornerRounding.CornerRounding(0)),
// top-right
new MaterialShapes.PointNRound(new Offset.Offset(root.screenWidth - horizontalGap - rounding, topY), new CornerRounding.CornerRounding(rounding)),
new MaterialShapes.PointNRound(new Offset.Offset(root.screenWidth - horizontalGap, topY), new CornerRounding.CornerRounding(rounding)),
new MaterialShapes.PointNRound(new Offset.Offset(root.screenWidth - horizontalGap, topY + rounding), new CornerRounding.CornerRounding(rounding)),
// bottom-right
new MaterialShapes.PointNRound(new Offset.Offset(root.screenWidth - horizontalGap, bottomY - rounding), new CornerRounding.CornerRounding(rounding)),
new MaterialShapes.PointNRound(new Offset.Offset(root.screenWidth - horizontalGap, bottomY), new CornerRounding.CornerRounding(rounding)),
new MaterialShapes.PointNRound(new Offset.Offset(root.screenWidth - horizontalGap - rounding, bottomY), new CornerRounding.CornerRounding(rounding)),
]
return MaterialShapes.customPolygon(points, 1, new Offset.Offset(root.screenWidth / 2, edgeGap + contentHeight / 2))
}
// // Keybinds
// GlobalShortcut {
// name: "searchToggle"
// description: "Toggles search on press"
// onPressed: {
// GlobalStates.overviewOpen = !GlobalStates.overviewOpen;
// }
// }
// GlobalShortcut {
// name: "searchToggleRelease"
// description: "Toggles search on release"
// onPressed: {
// GlobalStates.superReleaseMightTrigger = true;
// }
// onReleased: {
// if (!GlobalStates.superReleaseMightTrigger) {
// GlobalStates.superReleaseMightTrigger = true;
// return;
// }
// GlobalStates.overviewOpen = !GlobalStates.overviewOpen;
// }
// }
// GlobalShortcut {
// name: "searchToggleReleaseInterrupt"
// description: "Interrupts possibility of search being toggled on release. " + "This is necessary because GlobalShortcut.onReleased in quickshell triggers whether or not you press something else while holding the key. " + "To make sure this works consistently, use binditn = MODKEYS, catchall in an automatically triggered submap that includes everything."
// onPressed: {
// GlobalStates.superReleaseMightTrigger = false;
// }
// }
// Connections {
// target: GlobalStates
// function onOverviewOpenChanged() {
// if (GlobalStates.overviewOpen) {
// root.requestFocus();
// } else {
// root.dismissed();
// }
// }
// }
}
@@ -1,15 +0,0 @@
import QtQuick
import Quickshell
// The stuff that sits on the "top" layer for layershells. Not to be confused with "toplevels" as in windows.
Scope {
id: root
Variants {
model: Quickshell.screens
delegate: HTopLayerPanel {
required property var modelData
screen: modelData
}
}
}
@@ -1,170 +0,0 @@
import QtQuick
import Qt5Compat.GraphicalEffects
import Quickshell
import Quickshell.Wayland
import qs
import qs.services
import qs.modules.common
import qs.modules.common.widgets
import "../../common"
import "../../common/widgets/shapes" as S
import "../../common/widgets/shapes/material-shapes.js" as MaterialShapes
import "../../common/widgets/shapes/shapes/corner-rounding.js" as CornerRounding
import "../../common/widgets/shapes/geometry/offset.js" as Offset
import "bar"
/**
* Fullscreen layer. Uses masking to not block clicks on windows n' stuff.
*/
PanelWindow {
id: root
///////////////// Window //////////////////
property real opacity: 1 * (GlobalStates.barOpen && !GlobalStates.screenLocked)
Behavior on opacity {
animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
}
color: "transparent"
WlrLayershell.namespace: "quickshell:topLayerPanel"
exclusionMode: ExclusionMode.Ignore
anchors {
top: true
left: true
right: true
bottom: true
}
mask: root.currentPanel.maskRegion
// HyprlandWindow.visibleMask: mask // TODO: use this later to optimize hyprland's rendering
///////////////// Focus dim //////////////////
FadeLoader {
z: 0
anchors.fill: parent
shown: GlobalFocusGrab.active
sourceComponent: Rectangle {
opacity: 0.2
color: Appearance.m3colors.m3scrim
}
}
///////////////// Content //////////////////
property alias roundedPolygon: backgroundShape.roundedPolygon
property bool finishedMorphing: true
onRoundedPolygonChanged: finishedMorphing = false
Connections {
target: backgroundShape
function onProgressChanged() {
// While it overshoots because of the spring animation, waiting for the bounce to finish entirely would be too slow
// ^ (totally not an excuse for my laziness)
if (backgroundShape.progress >= 1.0) {
root.finishedMorphing = true;
}
}
}
S.ShapeCanvas {
id: backgroundShape
z: 1
anchors.fill: parent
polygonIsNormalized: false
roundedPolygon: MaterialShapes.customPolygon([new MaterialShapes.PointNRound(new Offset.Offset(root.screen.width, 0), new CornerRounding.CornerRounding(9999)),])
animation: NumberAnimation {
duration: 500
easing.type: Easing.BezierSpline
easing.bezierCurve: Appearance.animationCurves.expressiveDefaultSpatial
}
color: Appearance.colors.colLayer0
opacity: root.opacity
borderWidth: (root.currentPanel === bar && Config.options.bar.cornerStyle !== 1) ? 0 : 1
borderColor: Appearance.colors.colLayer0Border
visible: false // cuz there's already the shadow
// debug: true
}
DropShadow {
id: shadow
opacity: root.opacity
source: backgroundShape
anchors.fill: backgroundShape
radius: 10
samples: radius * 2 + 1 // Ideally radius * 2 + 1, see qt docs
color: "#44000000"
}
property HAbstractMorphedPanel currentPanel: null
Component.onCompleted: currentPanel = bar
roundedPolygon: currentPanel.backgroundPolygon
// Do we want to have reserved area always follow the bar or maybe differ per panel?
EdgeReservedArea {
anchors.top: true
exclusiveZone: bar.reservedTop
}
EdgeReservedArea {
anchors.bottom: true
exclusiveZone: bar.reservedBottom
}
EdgeReservedArea {
anchors.left: true
exclusiveZone: bar.reservedLeft
}
EdgeReservedArea {
anchors.right: true
exclusiveZone: bar.reservedRight
}
////////////// Content: Panels ///////////////
function dismiss() {
root.currentPanel = bar;
}
Item {
id: panelRootItem
anchors.fill: parent
Connections {
target: GlobalFocusGrab
function onActiveChanged() {
if (GlobalFocusGrab.active) {
panelRootItem.focus = true
}
}
}
Keys.onPressed: (event) => { // Esc to close
if (event.key === Qt.Key_Escape) {
GlobalFocusGrab.dismiss();
}
}
HBar {
id: bar
opacity: root.opacity
load: root.currentPanel === this && root.finishedMorphing // the extra condition is to prevent workspace widget from acting up when switching horizontal/vertical... should be fixed later
shown: root.finishedMorphing
}
// HOverview {
// id: overview
// opacity: root.opacity
// load: root.currentPanel === this
// shown: root.finishedMorphing
// onRequestFocus: root.currentPanel = overview
// onDismissed: root.dismiss()
// }
}
//////////////// Components /////////////////
component EdgeReservedArea: PanelWindow {
WlrLayershell.namespace: "quickshell:edgeReservedArea"
implicitWidth: 0
implicitHeight: 0
mask: Region {
item: null
}
screen: root.screen
}
}
@@ -1,234 +0,0 @@
pragma ComponentBehavior: Bound
import QtQuick
import qs.modules.common
import qs.modules.common.widgets
import "../../../common/widgets/shapes/material-shapes.js" as MaterialShapes
import "../../../common/widgets/shapes/shapes/corner-rounding.js" as CornerRounding
import "../../../common/widgets/shapes/geometry/offset.js" as Offset
import ".."
HAbstractMorphedPanel {
id: root
// Config
property bool vertical: Config.options.bar.vertical
property bool atBottom: Config.options.bar.bottom
property int cornerStyle: Config.options.bar.cornerStyle
// Own props
property int barHeight: Appearance.sizes.baseBarHeight
property int barVerticalWidth: Appearance.sizes.baseVerticalBarWidth
function getRounding(cornerStyle) {
switch(cornerStyle) {
case 0: return Appearance.rounding.screenRounding;
case 1: return Appearance.rounding.windowRounding;
case 2: return 0;
default: return Appearance.rounding.screenRounding;
}
}
function getEdgeGap(cornerStyle) {
switch(cornerStyle) {
case 0: return 0;
case 1: return Appearance.sizes.hyprlandGapsOut;
case 2: return 0;
default: return 0;
}
}
function getEdgeRounding(cornerStyle) {
switch(cornerStyle) {
case 0: return 0;
case 1: return Appearance.rounding.windowRounding;
case 2: return 0;
default: return Appearance.rounding.windowRounding;
}
}
function getHug(cornerStyle) {
return cornerStyle === 0;
}
property int reservedArea: (vertical ? barVerticalWidth : barHeight) + getEdgeGap(cornerStyle)
// Some info
reservedTop: (!atBottom && !vertical) ? reservedArea : 0
reservedBottom: (atBottom && !vertical) ? reservedArea : 0
reservedLeft: (!atBottom && vertical) ? reservedArea : 0
reservedRight: (atBottom && vertical) ? reservedArea : 0
// Background
function getBackgroundPolygon() {
// It's certainly cleaner to have the below props declared outside, but we do this
// to make sure a config change only makes this re-evaluate exactly once
const bottom = root.atBottom
const vertical = root.vertical
const cornerStyle = root.cornerStyle
const hug = root.getHug(cornerStyle)
const edgeGap = root.getEdgeGap(cornerStyle)
const edgeRounding = root.getEdgeRounding(cornerStyle)
const rounding = root.getRounding(cornerStyle)
const areaHeight = vertical ? root.screenHeight : (root.barHeight + edgeGap * 2)
const areaWidth = vertical ? (root.barVerticalWidth + edgeGap * 2) : root.screenWidth
const height = vertical ? (root.screenHeight - edgeGap * 2) : root.barHeight
const width = vertical ? root.barVerticalWidth : (root.screenWidth - edgeGap * 2)
const xLeft = (vertical && bottom) ? (root.screenWidth - edgeGap - width) : edgeGap
const xRight = (vertical && !bottom) ? (areaWidth - edgeGap) : (root.screenWidth - edgeGap)
const yTop = (!vertical && bottom) ? (root.screenHeight - edgeGap - height) : edgeGap
const yBottom = (!vertical && !bottom) ? (areaHeight - edgeGap) : (root.screenHeight - edgeGap)
const topLeftRounding = !bottom ? edgeRounding : rounding
const topRightRounding = !(bottom^vertical) ? edgeRounding : rounding
const bottomLeftRounding = !!(bottom^vertical) ? edgeRounding : rounding
const bottomRightRounding = bottom ? edgeRounding : rounding
var topCornerYDirection = 0, bottomCornerYDirection = 0, leftCornerXDirection = 0, rightCornerXDirection = 0;
if (vertical) {
topCornerYDirection = 1;
bottomCornerYDirection = -1;
} else if (cornerStyle === 2) { // Rect
topCornerYDirection = 0;
bottomCornerYDirection = 0;
} else if (cornerStyle === 1) { // Rounded
topCornerYDirection = 1;
bottomCornerYDirection = -1;
} else { // Hug
topCornerYDirection = bottom ? -1 : 1;
bottomCornerYDirection = bottom ? -1 : 1;
}
if (!vertical) {
leftCornerXDirection = 1;
rightCornerXDirection = -1
} else if (cornerStyle === 2) { // Rect
leftCornerXDirection = 0;
rightCornerXDirection = 0;
} else if (cornerStyle === 1) { // Rounded
leftCornerXDirection = 1;
rightCornerXDirection = -1;
} else { // Hug
leftCornerXDirection = bottom ? -1 : 1;
rightCornerXDirection = bottom ? -1 : 1;
}
var points = [
// bottom-middle
new MaterialShapes.PointNRound(new Offset.Offset(xLeft + width * 1/2, yBottom), new CornerRounding.CornerRounding(0)),
new MaterialShapes.PointNRound(new Offset.Offset(xLeft + width * 0.1, yBottom), new CornerRounding.CornerRounding(0)),
// bottom-left
new MaterialShapes.PointNRound(new Offset.Offset(xLeft + rounding * leftCornerXDirection, yBottom), new CornerRounding.CornerRounding(0)),
new MaterialShapes.PointNRound(new Offset.Offset(xLeft, yBottom), new CornerRounding.CornerRounding(bottomLeftRounding)),
new MaterialShapes.PointNRound(new Offset.Offset(xLeft, yBottom + rounding * bottomCornerYDirection), new CornerRounding.CornerRounding(edgeRounding)),
// middle-left
new MaterialShapes.PointNRound(new Offset.Offset(xLeft, yTop + height * 0.9), new CornerRounding.CornerRounding(0)),
new MaterialShapes.PointNRound(new Offset.Offset(xLeft, yTop + height * 1/2), new CornerRounding.CornerRounding(0)),
new MaterialShapes.PointNRound(new Offset.Offset(xLeft, yTop + height * 0.1), new CornerRounding.CornerRounding(0)),
// top-left
new MaterialShapes.PointNRound(new Offset.Offset(xLeft, yTop + rounding * topCornerYDirection), new CornerRounding.CornerRounding(0)),
new MaterialShapes.PointNRound(new Offset.Offset(xLeft, yTop), new CornerRounding.CornerRounding(topLeftRounding)),
new MaterialShapes.PointNRound(new Offset.Offset(xLeft + rounding * leftCornerXDirection, yTop), new CornerRounding.CornerRounding(0)),
// top-middle
new MaterialShapes.PointNRound(new Offset.Offset(xLeft + width * 0.1, yTop), new CornerRounding.CornerRounding(0)),
new MaterialShapes.PointNRound(new Offset.Offset(xLeft + width * 1/2, yTop), new CornerRounding.CornerRounding(0)),
new MaterialShapes.PointNRound(new Offset.Offset(xLeft + width * 0.9, yTop), new CornerRounding.CornerRounding(0)),
// top-right
new MaterialShapes.PointNRound(new Offset.Offset(xRight + rounding * rightCornerXDirection, yTop), new CornerRounding.CornerRounding(0)),
new MaterialShapes.PointNRound(new Offset.Offset(xRight, yTop), new CornerRounding.CornerRounding(topRightRounding)),
new MaterialShapes.PointNRound(new Offset.Offset(xRight, yTop + rounding * topCornerYDirection), new CornerRounding.CornerRounding(0)),
// middle-right
new MaterialShapes.PointNRound(new Offset.Offset(xRight, yTop + height * 0.1), new CornerRounding.CornerRounding(0)),
new MaterialShapes.PointNRound(new Offset.Offset(xRight, yTop + height * 1/2), new CornerRounding.CornerRounding(0)),
new MaterialShapes.PointNRound(new Offset.Offset(xRight, yTop + height * 0.9), new CornerRounding.CornerRounding(0)),
// bottom-right
new MaterialShapes.PointNRound(new Offset.Offset(xRight, yBottom + rounding * bottomCornerYDirection), new CornerRounding.CornerRounding(edgeRounding)),
new MaterialShapes.PointNRound(new Offset.Offset(xRight, yBottom), new CornerRounding.CornerRounding(bottomRightRounding)),
new MaterialShapes.PointNRound(new Offset.Offset(xRight + rounding * rightCornerXDirection, yBottom), new CornerRounding.CornerRounding(0)),
// bottom-middle
new MaterialShapes.PointNRound(new Offset.Offset(xLeft + width * 0.9, yBottom), new CornerRounding.CornerRounding(0)),
]
return MaterialShapes.customPolygon(points, 1, new Offset.Offset(root.screenWidth / 2, edgeGap + barHeight / 2))
}
backgroundPolygon: getBackgroundPolygon()
Connections {
target: Config
function onReadyChanged() {
if (Config.ready)
root.backgroundPolygon = root.getBackgroundPolygon()
}
function onReloaded() {
root.extraLoadCondition = false
root.backgroundPolygon = root.getBackgroundPolygon()
root.extraLoadCondition = true
}
}
// Content
implicitHeight: vertical ? screenHeight : (barHeight + getEdgeGap(cornerStyle) * 2)
implicitWidth: vertical ? (barVerticalWidth + getEdgeGap(cornerStyle) * 2) : screenWidth
width: implicitWidth
height: implicitHeight
anchors {
top: parent.top
bottom: undefined
left: undefined
right: undefined
}
states: [
State {
name: "bottom"
when: root.atBottom && !root.vertical
AnchorChanges {
target: root
anchors.top: undefined
anchors.bottom: parent.bottom
anchors.left: undefined
anchors.right: undefined
}
},
State {
name: "left"
when: !root.atBottom && root.vertical
AnchorChanges {
target: root
anchors.top: undefined
anchors.bottom: undefined
anchors.left: parent.left
anchors.right: undefined
}
},
State {
name: "right"
when: root.atBottom && root.vertical
AnchorChanges {
target: root
anchors.top: undefined
anchors.bottom: undefined
anchors.left: undefined
anchors.right: parent.right
}
}
]
transitions: Transition {
AnchorAnimation {
duration: 500
easing.type: Easing.BezierSpline
easing.bezierCurve: Appearance.animationCurves.expressiveDefaultSpatial
}
}
property bool extraLoadCondition: true
FadeLazyLoader {
id: contentLoader
load: root.load && root.extraLoadCondition
shown: root.shown && root.extraLoadCondition
anchors.fill: parent
component: HBarContent {
parent: contentLoader
anchors.fill: parent
}
}
}
@@ -1,101 +0,0 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import qs.modules.common as C
import qs.modules.common.widgets as W
Item {
id: root
property bool vertical: C.Config.options.bar.vertical
property real spacing: 4
property list<var> leftWidgets: C.Config.options.hefty.bar.leftWidgets
property list<var> centerLeftWidgets: C.Config.options.hefty.bar.centerLeftWidgets
property list<var> centerWidgets: C.Config.options.hefty.bar.centerWidgets
property list<var> centerRightWidgets: C.Config.options.hefty.bar.centerRightWidgets
property list<var> rightWidgets: C.Config.options.hefty.bar.rightWidgets
Side {
id: leftSide
anchors.left: parent.left
anchors.top: parent.top
anchors.right: !root.vertical ? centerLeftSide.left : parent.right
anchors.bottom: !root.vertical ? parent.bottom : centerLeftSide.top
// For accessibility
anchors.leftMargin: 0
anchors.topMargin: 0
HBarUserFallbackComponentRepeater {
componentNames: root.leftWidgets
}
Item {
Layout.fillWidth: true
Layout.fillHeight: true
}
}
Side {
id: centerLeftSide
anchors.right: !root.vertical ? centerSide.left : parent.right
anchors.bottom: !root.vertical ? parent.bottom : centerSide.top
HBarUserFallbackComponentRepeater {
componentNames: [...root.centerLeftWidgets, ...(root.centerLeftWidgets.length > 0 ? [invisibleItem] : []),]
}
}
Side {
id: centerSide
anchors.verticalCenter: root.vertical ? parent.verticalCenter : undefined
anchors.horizontalCenter: !root.vertical ? parent.horizontalCenter : undefined
HBarUserFallbackComponentRepeater {
componentNames: [...(root.centerLeftWidgets.length > 0 ? [invisibleItem] : []), ...root.centerWidgets, ...(root.centerRightWidgets.length > 0 ? [invisibleItem] : []),]
}
}
Side {
id: centerRightSide
anchors.left: !root.vertical ? centerSide.right : parent.left
anchors.top: !root.vertical ? parent.top : centerSide.bottom
HBarUserFallbackComponentRepeater {
componentNames: [...(root.centerLeftWidgets.length > 0 ? [invisibleItem] : []), ...root.centerRightWidgets,]
}
}
Side {
id: rightSide
anchors.right: parent.right
anchors.bottom: parent.bottom
anchors.left: !root.vertical ? centerRightSide.right : parent.left
anchors.top: !root.vertical ? parent.top : centerRightSide.bottom
// For accessibility
anchors.rightMargin: 0
anchors.bottomMargin: 0
Item {
Layout.fillWidth: true
Layout.fillHeight: true
}
HBarUserFallbackComponentRepeater {
componentNames: root.rightWidgets
}
}
component Side: W.BoxLayout {
anchors {
top: !root.vertical ? parent.top : undefined
bottom: !root.vertical ? parent.bottom : undefined
topMargin: root.spacing * root.vertical
bottomMargin: root.spacing * root.vertical
left: root.vertical ? parent.left : undefined
right: root.vertical ? parent.right : undefined
leftMargin: root.spacing * !root.vertical
rightMargin: root.spacing * !root.vertical
}
vertical: C.Config.options.bar.vertical
spacing: root.spacing
}
}
@@ -1,76 +0,0 @@
import QtQuick
import QtQuick.Layouts
import qs.modules.common as C
import qs.modules.common.widgets as W
Item {
id: root
property bool startSide: false
property bool endSide: false
property alias color: bg.color
property real margins: 4
property real padding: 4
default property alias groupData: layout.data
readonly property bool vertical: C.Config.options.bar.vertical
readonly property bool m3eRadius: C.Config.options.hefty.bar.m3ExpressiveGrouping
readonly property real barUndirectionalWidth: C.Config.options.bar.vertical ? C.Appearance.sizes.baseVerticalBarWidth : C.Appearance.sizes.baseBarHeight
readonly property real backgroundUndirectionalWidth: barUndirectionalWidth - margins * 2
implicitWidth: vertical ? barUndirectionalWidth : layout.implicitWidth
implicitHeight: vertical ? layout.implicitHeight : barUndirectionalWidth
property alias startRadius: bg.startRadius
property alias endRadius: bg.endRadius
property alias topLeftRadius: bg.topLeftRadius
property alias topRightRadius: bg.topRightRadius
property alias bottomLeftRadius: bg.bottomLeftRadius
property alias bottomRightRadius: bg.bottomRightRadius
property real backgroundWidth: root.vertical ? root.backgroundUndirectionalWidth : (root.width - margins * (startSide + endSide))
property real backgroundHeight: !root.vertical ? root.backgroundUndirectionalWidth : (root.height - margins * (startSide + endSide))
property real backgroundTopMargin: root.margins * (!root.vertical || root.startSide)
property real backgroundLeftMargin: root.margins * (root.vertical || root.startSide)
property real fullBackgroundRadius: Math.min(backgroundWidth, backgroundHeight) / 2
function getBackgroundRadius(atSide) {
if (root.m3eRadius) {
if (atSide) return fullBackgroundRadius;
else return C.Appearance.rounding.unsharpenmore;
} else {
return 12;
}
}
property Item background: W.AxisRectangle {
id: bg
anchors {
top: parent?.top
left: parent?.left
topMargin: root.backgroundTopMargin
leftMargin: root.backgroundLeftMargin
}
contentLayer: W.StyledRectangle.ContentLayer.Group
width: root.backgroundWidth
height: root.backgroundHeight
vertical: root.vertical
startRadius: root.getBackgroundRadius(root.startSide)
endRadius: root.getBackgroundRadius(root.endSide)
}
property Item contentItem: GridLayout {
id: layout
columns: C.Config.options.bar.vertical ? 1 : -1
anchors.fill: parent
property real spacing: 4
columnSpacing: spacing
rowSpacing: spacing
}
children: [
background,
contentItem
]
}
@@ -1,42 +0,0 @@
pragma ComponentBehavior: Bound
import QtQuick
import qs.modules.common as C
import qs.modules.common.widgets as W
W.StyledRectangle {
id: root
contentLayer: W.StyledRectangle.ContentLayer.Pane
color: C.Appearance.colors.colLayer2Base
transitions: Transition {
AnchorAnimation {
duration: C.Appearance.animation.elementMove.duration
easing.type: C.Appearance.animation.elementMove.type
easing.bezierCurve: C.Appearance.animation.elementMove.bezierCurve
}
ColorAnimation {
duration: C.Appearance.animation.elementMoveFast.duration
easing.type: C.Appearance.animation.elementMoveFast.type
easing.bezierCurve: C.Appearance.animation.elementMoveFast.bezierCurve
}
PropertyAnimation {
properties: "topLeftRadius,topRightRadius,bottomLeftRadius,bottomRightRadius,intendedWidth,intendedHeight"
duration: C.Appearance.animation.elementMove.duration
easing.type: C.Appearance.animation.elementMove.type
easing.bezierCurve: C.Appearance.animation.elementMove.bezierCurve
}
PropertyAnimation {
properties: "opacity"
duration: C.Appearance.animation.elementMoveFast.duration
easing.type: C.Appearance.animation.elementMoveFast.type
easing.bezierCurve: C.Appearance.animation.elementMoveFast.bezierCurve
}
}
W.StyledRectangularShadow {
target: root
z: -1
radius: root.topLeftRadius
}
}
@@ -1,83 +0,0 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import Quickshell
import qs.modules.common as C
import qs.modules.common.widgets as W
Repeater {
id: root
readonly property bool vertical: C.Config.options.bar.vertical
readonly property string invisibleItem: "_invisible"
required property list<var> componentNames
property string context: Quickshell.shellPath("modules/hefty/topLayer/bar/widgets")
model: {
const m = componentNames.map(item => {
if (item instanceof Array)
return ({"type": "container", "value": item});
else if (item === root.invisibleItem)
return ({"type": "invisible", "value": item});
else
return ({"type": "component", "value": item});
});
for (var i = 0;i < m.length; i++) {
const item = m[i];
if (item.type === "container" || item.type === "component") {
item.startSide = (i === 0) || (m[i - 1].type !== m[i].type);
item.endSide = (i + 1 >= m.length) || (m[i + 1].type !== m[i].type);
}
}
// print(JSON.stringify(m, null, 2));
return m;
}
delegate: DelegateChooser {
role: "type"
DelegateChoice {
roleValue: root.invisibleItem
delegate: Item { visible: false }
}
DelegateChoice {
roleValue: "component"
delegate: W.UserFallbackLoader {
required property var modelData
required property int index
componentName: modelData.value
context: root.context
property bool startSide: index === 0
property bool endSide: index === root.model.length - 1
Layout.fillWidth: root.vertical ? true : item.Layout.fillWidth
Layout.fillHeight: !root.vertical ? true : item.Layout.fillHeight
Layout.maximumWidth: item.Layout.maximumWidth
Layout.maximumHeight: item.Layout.maximumHeight
}
}
DelegateChoice {
roleValue: "container"
delegate: HBarGroupContainer {
id: group
required property var modelData
startSide: modelData.startSide
endSide: modelData.endSide
Repeater {
id: containerRepeater
model: group.modelData.value
delegate: W.UserFallbackLoader {
required property var modelData
required property int index
componentName: modelData
context: root.context
property bool startSide: index === 0
property bool endSide: index === group.modelData.value.length - 1
}
}
}
}
}
}
@@ -1,9 +0,0 @@
import QtQuick
import QtQuick.Layouts
import qs.modules.common as C
import qs.modules.common.widgets as W
HBarGroupContainer {
startSide: parent.startSide ?? true
endSide: parent.endSide ?? true
}
@@ -1,73 +0,0 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import qs.modules.common as C
import qs.modules.common.functions as F
import qs.modules.common.widgets as W
import ".."
W.ButtonMouseArea {
id: root
required property bool vertical
required property bool atBottom
required property bool showPopup
readonly property var layoutParent: F.ObjectUtils.findParentWithProperty(root, "startSide")
readonly property real layoutParentTopLeftRadius: layoutParent.topLeftRadius
readonly property real layoutParentTopRightRadius: layoutParent.topRightRadius
readonly property real layoutParentBottomLeftRadius: layoutParent.bottomLeftRadius
readonly property real layoutParentBottomRightRadius: layoutParent.bottomRightRadius
readonly property real barThickness: vertical ? C.Appearance.sizes.verticalBarWidth : C.Appearance.sizes.barHeight
readonly property real barVisualThickness: vertical ? C.Appearance.sizes.baseVerticalBarWidth : C.Appearance.sizes.baseBarHeight
readonly property real barGap: (barThickness - barVisualThickness) / 2
required property real contentImplicitWidth
required property real contentImplicitHeight
property real parentRadiusToPaddingRatio: 0.3
implicitWidth: {
if (vertical) {
return barThickness;
} else {
const roundingPadding = (layoutParentTopLeftRadius + layoutParentBottomRightRadius) * parentRadiusToPaddingRatio;
return (contentImplicitWidth + roundingPadding + 4 * 2);
}
}
implicitHeight: {
if (!vertical) {
return barThickness;
} else {
const roundingPadding = (layoutParentTopLeftRadius + layoutParentBottomRightRadius) * parentRadiusToPaddingRatio;
return (contentImplicitHeight + roundingPadding + 4 * 2);
}
}
Layout.alignment: vertical ? Qt.AlignHCenter : Qt.AlignVCenter
Layout.fillWidth: vertical
Layout.fillHeight: !vertical
property alias hover: hoverOverlay.hover
property alias press: hoverOverlay.press
W.StateOverlay {
id: hoverOverlay
anchors.fill: parent
property real parentMargins: 4 + root.barGap
property real ownMargins: 2
property real edgeMargins: parentMargins + ownMargins
property real sideMargins: 2
anchors {
leftMargin: (root.vertical ? edgeMargins : sideMargins) + parentMargins * (!root.vertical && root.layoutParent.startSide)
rightMargin: (root.vertical ? edgeMargins : sideMargins) + parentMargins * (!root.vertical && root.layoutParent.endSide)
topMargin: (root.vertical ? sideMargins : edgeMargins) + parentMargins * (root.vertical && root.layoutParent.startSide)
bottomMargin: (root.vertical ? sideMargins : edgeMargins) + parentMargins * (root.vertical && root.layoutParent.endSide)
}
topLeftRadius: root.layoutParentTopLeftRadius - ownMargins
topRightRadius: root.layoutParentTopRightRadius - ownMargins
bottomLeftRadius: root.layoutParentBottomLeftRadius - ownMargins
bottomRightRadius: root.layoutParentBottomRightRadius - ownMargins
hover: root.containsMouse
press: root.containsPress
}
}
@@ -1,260 +0,0 @@
pragma ComponentBehavior: Bound
import QtQuick
import Quickshell
import qs.modules.common as C
import qs.modules.common.widgets as W
import qs.modules.common.widgets.shapes as Shapes
import "../../../common/widgets/shapes/material-shapes.js" as MaterialShapes
// Notes
// vertical + atBottom = right side
// start radius = top or left, end radius = bottom or right
Shapes.ShapeCanvas {
id: bgShape
// Stuff fed from outside
required property bool vertical
required property bool atBottom
required property bool showPopup
required property real backgroundWidth
required property real backgroundHeight
property real popupContentWidth: 400
property real popupContentHeight: 500
property real popupPadding: 10
property real popupWidth: popupContentWidth + popupPadding * 2
property real popupHeight: popupContentHeight + popupPadding * 2
required property real startRadius
required property real endRadius
property real baseMargin: {
if (!vertical)
return parent.anchors.topMargin
else
return parent.anchors.leftMargin
}
property alias containerShape: containerShape
property alias popupShape: popupShape
// Popup constraints
// mapToGlobal is not reactive so we gotta hook manually
property real xInGlobal
property real yInGlobal
function updateXInGlobal() {
xInGlobal = mapToGlobal(0, 0).x + xOffset;
}
function updateYInGlobal() {
yInGlobal = mapToGlobal(0, 0).y + yOffset;
}
function updatePosInGlobal() {
updateXInGlobal()
updateYInGlobal()
}
Component.onCompleted: updatePosInGlobal()
onXChanged: updatePosInGlobal()
onYChanged: updatePosInGlobal()
readonly property real minPopupXOffset: -xInGlobal
readonly property real minPopupYOffset: -yInGlobal
readonly property real maxPopupXOffset: {
const maxPopupX = QsWindow.window.screen.width - popupWidth;
const maxOffset = maxPopupX - xInGlobal;
return maxOffset;
}
readonly property real maxPopupYOffset: {
const maxPopupY = QsWindow.window.screen.height - popupHeight;
const maxOffset = maxPopupY - yInGlobal;
return maxOffset;
}
property bool lockPopupX: false
property bool lockPopupY: false
readonly property real popupXOffset: {
// print("popupXOffset", popupXOffset, "lock", lockPopupX)
if (bgShape.lockPopupX) return;
if (!vertical) return Math.min(Math.max(-(popupWidth - containerShape.width) / 2, minPopupXOffset), maxPopupXOffset);
else return atBottom ? -(popupShape.width + spacing) : (containerShape.width + spacing);
if (bgShape.showPopup) lockPopupX = true;
}
onPopupXOffsetChanged: if (bgShape.showPopup) lockPopupX = true;
readonly property real popupYOffset: {
if (bgShape.lockPopupY) return;
if (!vertical) return atBottom ? -(popupShape.height + spacing) : (containerShape.height + spacing);
else return Math.min(Math.max(-(popupHeight - containerShape.height) / 2, minPopupYOffset), maxPopupYOffset)
if (bgShape.showPopup) lockPopupY = true;
}
onPopupYOffsetChanged: if (bgShape.showPopup) lockPopupY = true;
// Positioning
readonly property real popupContentOffsetBase: popupPadding
readonly property real popupContentOffsetBaseX: popupContentOffsetBase + parent.anchors.leftMargin
readonly property real popupContentOffsetBaseY: popupContentOffsetBase + parent.anchors.topMargin
readonly property real paddedContainerHeight: containerShape.height
readonly property real paddedContainerWidth: containerShape.width
readonly property real popupContentOffsetX: {
if (!vertical) return popupXOffset + popupContentOffsetBaseX;
else return paddedContainerWidth + spacing + popupContentOffsetBaseX + (atBottom * -(popupWidth + backgroundWidth + spacing * 2));
}
readonly property real popupContentOffsetY: {
if (!vertical) return paddedContainerHeight + spacing + popupContentOffsetBaseY + (atBottom * -(popupHeight + backgroundHeight + spacing * 2))
else return popupYOffset + popupContentOffsetBaseY;
}
anchors {
left: parent.left
leftMargin: {
if (!vertical) return -xOffset;
if (!bgShape.atBottom || !bgShape.showPopup) return 0;
return -popupShape.width - bgShape.spacing;
}
top: parent.top
topMargin: {
if (vertical) return -yOffset;
if (!bgShape.atBottom || !bgShape.showPopup) return 0;
return -popupShape.height - bgShape.spacing;
}
}
width: {
if (!vertical) return bgShape.showPopup ? Math.max(backgroundWidth, popupWidth) : backgroundWidth;
else return bgShape.showPopup ? (containerShape.width + popupShape.width + bgShape.spacing) : containerShape.width;
}
height: {
if (!vertical) return bgShape.showPopup ? (containerShape.height + popupShape.height + bgShape.spacing) : containerShape.height;
else return bgShape.showPopup ? Math.max(backgroundHeight, popupHeight) : backgroundHeight;
}
property bool popupHiding: false
onShowPopupChanged: popupHiding = true
onProgressChanged: {
if (progress == 1) {
popupHiding = false
if (!showPopup) {
lockPopupX = false;
lockPopupY = false;
}
}
}
color: showPopup || popupHiding ? C.Appearance.colors.colLayer3Base : C.Appearance.colors.colLayer1
xOffset: {
if (!vertical) return showPopup ? Math.max(-popupXOffset, 0) : 0;
else return bgShape.atBottom ? (width - containerShape.width) : 0;
}
yOffset: {
if (!vertical) return bgShape.atBottom ? (height - containerShape.height) : 0;
else return showPopup ? Math.max(-popupYOffset, 0) : 0;
}
animation: Anim {}
Behavior on width {
Anim {}
}
Behavior on height {
Anim {}
}
Behavior on anchors.topMargin {
animation: !bgShape.vertical ? animComp.createObject(this) : undefined
}
Behavior on anchors.leftMargin {
animation: bgShape.vertical ? animComp.createObject(this) : undefined
}
Behavior on xOffset {
animation: !bgShape.vertical ? animComp.createObject(this) : undefined
}
Behavior on yOffset {
animation: bgShape.vertical ? animComp.createObject(this) : undefined
}
polygonIsNormalized: false
property real spacing: baseMargin * 2
W.AxisRectangularContainerShape {
id: containerShape
width: bgShape.backgroundWidth
height: bgShape.backgroundHeight
startRadius: bgShape.startRadius
endRadius: bgShape.endRadius
vertical: bgShape.vertical
}
W.RectangularContainerShape {
id: popupShape
width: bgShape.popupWidth
height: bgShape.popupHeight
radius: C.Appearance.rounding.large
xOffset: bgShape.popupXOffset
yOffset: bgShape.popupYOffset
}
roundedPolygon: {
var points = [];
if (!bgShape.showPopup) return containerShape.getFullShape();
const joinRadiusOverride = containerShape.radiusLimit;
if (!bgShape.vertical) {
const popupBigger = bgShape.popupWidth > bgShape.backgroundWidth;
// Inline comment spam to mitigate qmlls' sabotaging of the (code) layout
points = [
...(bgShape.atBottom ? containerShape.getFirstBottomPoints() : [ //
...popupShape.getFirstBottomPoints(), popupShape.getBottomLeftPoint(), //
...popupShape.leftPoints, //
popupShape.getTopLeftPoint(0, -bgShape.spacing * (!popupBigger), joinRadiusOverride), //
]), //
containerShape.getBottomLeftPoint(0, bgShape.spacing * (!bgShape.atBottom && popupBigger), joinRadiusOverride), //
containerShape.getTopLeftPoint(0, -bgShape.spacing * (bgShape.atBottom && popupBigger), joinRadiusOverride), //
...(!bgShape.atBottom ? containerShape.topPoints : [ //
popupShape.getBottomLeftPoint(0, bgShape.spacing * (!popupBigger), joinRadiusOverride), //
...popupShape.leftPoints, //
popupShape.getTopLeftPoint(), //
...popupShape.topPoints, //
popupShape.getTopRightPoint(), //
...popupShape.rightPoints, //
popupShape.getBottomRightPoint(0, bgShape.spacing * (!popupBigger), joinRadiusOverride), //
]), //
containerShape.getTopRightPoint(0, -bgShape.spacing * (bgShape.atBottom && popupBigger), joinRadiusOverride), //
containerShape.getBottomRightPoint(0, bgShape.spacing * (!bgShape.atBottom && popupBigger), joinRadiusOverride), //
...(bgShape.atBottom ? containerShape.getLastBottomPoints() : [ //
popupShape.getTopRightPoint(0, -bgShape.spacing * (!popupBigger), joinRadiusOverride), //
...popupShape.rightPoints, //
popupShape.getBottomRightPoint(), //
...popupShape.getLastBottomPoints(), //
]),
];
} else {
const popupBigger = bgShape.popupHeight > bgShape.backgroundHeight;
points = [ //
...containerShape.getFirstBottomPoints(), //
containerShape.getBottomLeftPoint(-bgShape.spacing * (popupBigger && bgShape.atBottom), 0, joinRadiusOverride), //
...(!bgShape.atBottom ? containerShape.leftPoints : [ //
popupShape.getBottomRightPoint(bgShape.spacing * (!popupBigger)), //
...popupShape.bottomPoints, //
popupShape.getBottomLeftPoint(), //
...popupShape.leftPoints, //
popupShape.getTopLeftPoint(), //
...popupShape.topPoints, //
popupShape.getTopRightPoint(bgShape.spacing * (!popupBigger)), //
]), //
containerShape.getTopLeftPoint(-bgShape.spacing * (popupBigger && bgShape.atBottom), 0, joinRadiusOverride), //
...containerShape.topPoints, //
containerShape.getTopRightPoint(bgShape.spacing * (popupBigger && !bgShape.atBottom), 0, joinRadiusOverride), //
...(bgShape.atBottom ? containerShape.rightPoints : [ //
popupShape.getTopLeftPoint(-bgShape.spacing * (!popupBigger), 0, joinRadiusOverride), //
...popupShape.topPoints, //
popupShape.getTopRightPoint(), //
...popupShape.rightPoints, //
popupShape.getBottomRightPoint(), //
...popupShape.bottomPoints, //
popupShape.getBottomLeftPoint(-bgShape.spacing * (!popupBigger), 0, joinRadiusOverride), //
]), //
containerShape.getBottomRightPoint(bgShape.spacing * (popupBigger && !bgShape.atBottom), 0, joinRadiusOverride), //
...containerShape.getLastBottomPoints(), //
];
}
return MaterialShapes.customPolygon(points);
}
component Anim: NumberAnimation {
duration: C.Appearance.animation.elementMove.duration
easing.type: C.Appearance.animation.elementMove.type
easing.bezierCurve: C.Appearance.animation.elementMove.bezierCurve
}
Component {
id: animComp
Anim {}
}
}
@@ -1,65 +0,0 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import qs.modules.common as C
import qs.modules.common.functions as F
import qs.services as S
import qs.modules.common.widgets as W
import ".."
HBarWidgetContainer {
id: root
property bool showPopup: false
readonly property bool vertical: C.Config.options.bar.vertical
readonly property bool atBottom: C.Config.options.bar.bottom
// Interactions
property var morphedPanelParent: F.ObjectUtils.findParentWithProperty(root, "maskItems")
onShowPopupChanged: {
if (root.showPopup) {
root.morphedPanelParent.addAttachedMaskItem(bgShape);
} else {
root.morphedPanelParent.removeAttachedMaskItem(bgShape);
}
}
Connections {
target: root.morphedPanelParent
function onFocusGrabDismissed() {
root.showPopup = false;
}
}
// Background container shape
property alias backgroundShape: bgShape
property alias popupContentWidth: bgShape.popupContentWidth
property alias popupContentHeight: bgShape.popupContentHeight
property alias popupContentOffsetX: bgShape.popupContentOffsetX
property alias popupContentOffsetY: bgShape.popupContentOffsetY
background: Item {
anchors {
top: parent.top
left: parent.left
topMargin: root.backgroundTopMargin
leftMargin: root.backgroundLeftMargin
}
implicitWidth: root.backgroundWidth
implicitHeight: root.backgroundHeight
HBarWidgetShapeBackground {
id: bgShape
vertical: root.vertical
atBottom: root.atBottom
showPopup: root.showPopup
backgroundWidth: root.backgroundWidth
backgroundHeight: root.backgroundHeight
startRadius: root.getBackgroundRadius(root.startSide)
endRadius: root.getBackgroundRadius(root.endSide)
}
}
}
@@ -1,79 +0,0 @@
pragma ComponentBehavior: Bound
import QtQuick
import qs
import qs.services
import qs.modules.common
import qs.modules.common.widgets
import ".."
HBarWidgetWithPopout {
id: root
property bool showPing: false
Connections {
target: Ai
function onResponseFinished() {
if (GlobalStates.sidebarLeftOpen) return;
root.showPing = true;
}
}
Connections {
target: Booru
function onResponseFinished() {
if (GlobalStates.sidebarLeftOpen) return;
root.showPing = true;
}
}
Connections {
target: GlobalStates
function onSidebarLeftOpenChanged() {
root.showPing = false;
}
}
HBarWidgetContent {
id: contentRoot
parentRadiusToPaddingRatio: 0
vertical: root.vertical
atBottom: root.atBottom
contentImplicitWidth: 14
contentImplicitHeight: 14
implicitWidth: 40
implicitHeight: 46
showPopup: false
onClicked: GlobalStates.sidebarLeftOpen = !GlobalStates.sidebarLeftOpen;
CustomIcon {
id: distroIcon
anchors.centerIn: parent
width: 20
height: 20
source: Config.options.bar.topLeftIcon == 'distro' ? SystemInfo.distroIcon : `${Config.options.bar.topLeftIcon}-symbolic`
colorize: true
color: Appearance.colors.colOnLayer0
Rectangle {
opacity: root.showPing ? 1 : 0
visible: opacity > 0
anchors {
bottom: parent.bottom
right: parent.right
bottomMargin: -2
rightMargin: -2
}
implicitWidth: 8
implicitHeight: 8
radius: Appearance.rounding.full
color: Appearance.colors.colTertiary
Behavior on opacity {
animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
}
}
}
}
}
@@ -1,27 +0,0 @@
pragma ComponentBehavior: Bound
import QtQuick
import qs
import qs.modules.common.widgets
import ".."
HBarWidgetWithPopout {
id: root
HBarWidgetContent {
id: contentRoot
vertical: root.vertical
atBottom: root.atBottom
contentImplicitWidth: symbol.implicitWidth
contentImplicitHeight: symbol.implicitHeight
showPopup: false
onClicked: GlobalStates.sessionOpen = true;
MaterialSymbol {
id: symbol
anchors.centerIn: parent
iconSize: 20
text: "power_settings_new"
}
}
}
@@ -1,598 +0,0 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Quickshell.Services.UPower
import qs.services as S
import qs.modules.common as C
import qs.modules.common.functions as F
import qs.modules.common.widgets as W
import ".."
HBarWidgetWithPopout {
id: root
property bool chargingAndNotFull: S.Battery.isCharging && S.Battery.percentage < 1
property bool powerSaving: PowerProfiles.profile == PowerProfile.PowerSaver
popupContentWidth: popupContent.implicitWidth
popupContentHeight: popupContent.implicitHeight
HBarWidgetContent {
id: contentRoot
vertical: root.vertical
atBottom: root.atBottom
showPopup: root.showPopup
onClicked: root.showPopup = !root.showPopup
contentImplicitWidth: sysInfoContent.implicitWidth
contentImplicitHeight: sysInfoContent.implicitHeight
parentRadiusToPaddingRatio: 0
SysInfoContent {
id: sysInfoContent
anchors.fill: parent
}
SysInfoPopupContent {
id: popupContent
anchors {
top: parent.top
left: parent.left
topMargin: root.popupContentOffsetY
leftMargin: root.popupContentOffsetX
}
shown: root.showPopup
}
}
component SysInfoContent: Item {
implicitWidth: contentGrid.implicitWidth + contentGrid.anchors.leftMargin + contentGrid.anchors.rightMargin
implicitHeight: contentGrid.implicitHeight + contentGrid.anchors.topMargin + contentGrid.anchors.bottomMargin
W.BoxLayout {
id: contentGrid
vertical: root.vertical
property int visibleChildren: children.filter(child => child.shown).length
property bool hasResourceIndicators: visibleChildren > 1 || (visibleChildren > 0 && !S.Battery.available)
anchors {
fill: parent
leftMargin: !root.vertical ? (hasResourceIndicators ? 1 : 4) : 0
topMargin: root.vertical ? 2 : 0
rightMargin: !root.vertical ? (root.endSide ? 1 : (battLoader.visible ? 0 : -3)) : 0
bottomMargin: root.vertical ? (root.endSide ? 4 : 2) : 0
}
spacing: 4
Item {} // Trick for extra spacing
Item {
visible: root.startSide
}
AlignedFadeLoader {
shown: C.Config.options.hefty.bar.resources.showMemory || (
!battLoader.visible
&& !C.Config.options.hefty.bar.resources.showRam
&& !C.Config.options.hefty.bar.resources.showSwap
&& !C.Config.options.hefty.bar.resources.showCpu
)
sourceComponent: Memory {}
}
AlignedFadeLoader {
shown: C.Config.options.hefty.bar.resources.showRam
sourceComponent: RamOnly {}
}
AlignedFadeLoader {
shown: C.Config.options.hefty.bar.resources.showSwap
sourceComponent: SwapOnly {}
}
AlignedFadeLoader {
shown: C.Config.options.hefty.bar.resources.showCpu
sourceComponent: Cpu {}
}
AlignedFadeLoader {
id: battLoader
shown: S.Battery.available
Layout.fillWidth: root.vertical
Layout.fillHeight: !root.vertical
sourceComponent: Battery {}
}
Item {}
}
}
component AlignedFadeLoader: W.FadeLoader {
Layout.alignment: root.vertical ? Qt.AlignHCenter : Qt.AlignVCenter
}
component Memory: SysResourceProgress {
valueWeights: [S.ResourceUsage.memoryTotal, S.ResourceUsage.swapTotal]
values: [S.ResourceUsage.memoryUsedPercentage, S.ResourceUsage.swapUsedPercentage]
centerChar: S.Translation.tr("Memory")[0]
}
component RamOnly: SysResourceProgress {
values: [S.ResourceUsage.memoryUsedPercentage]
centerChar: S.Translation.tr("RAM")[0]
}
component SwapOnly: SysResourceProgress {
values: [S.ResourceUsage.swapUsedPercentage]
centerChar: S.Translation.tr("Swap")[0]
}
component Cpu: SysResourceProgress {
values: [S.ResourceUsage.cpuUsage]
centerChar: S.Translation.tr("CPU")[0]
}
component SysResourceProgress: W.CombinedCircularProgress {
id: sysResProg
implicitSize: 22
valueHighlights: [C.Appearance.colors.colPrimary]
valueTroughs: [C.Appearance.colors.colSecondaryContainer]
property string centerChar: ""
W.StyledText {
renderType: Text.QtRendering
anchors.centerIn: parent
text: sysResProg.centerChar
font.pixelSize: 9
}
}
component Battery: Item {
implicitWidth: !root.vertical ? battShape.implicitWidth : battShape.implicitHeight
implicitHeight: !root.vertical ? battShape.implicitHeight : battShape.implicitWidth
BatteryShape {
id: battShape
anchors.centerIn: parent
}
}
component BatteryShape: Row {
Layout.fillHeight: true
spacing: 1.5
rotation: -90 * root.vertical
W.ClippedProgressBar {
id: batteryProgress
anchors.verticalCenter: parent.verticalCenter
valueBarWidth: 28
valueBarHeight: 16
radius: 4
progressRadius: 0
value: S.Battery.percentage
highlightColor: (S.Battery.isLow && !S.Battery.isCharging) ? C.Appearance.m3colors.m3error : C.Appearance.colors.colOnSecondaryContainer
font.pixelSize: boltIcon.visible ? 13 : 14
Item {
layer.enabled: true
width: batteryProgress.valueBarWidth
height: batteryProgress.valueBarHeight
RowLayout {
anchors {
horizontalCenter: parent.horizontalCenter
bottom: parent.bottom
bottomMargin: (parent.height - height) / 2
}
rotation: 180 * root.vertical
spacing: 0
W.MaterialSymbol {
id: boltIcon
Layout.alignment: Qt.AlignVCenter
Layout.leftMargin: -2
Layout.rightMargin: -2
rotation: -90 * root.vertical
fill: 1 * (text == "bolt")
fillAnimation: null
text: {
if (batteryProgress.value == 1)
return "check";
if (root.chargingAndNotFull)
return "bolt";
if (root.powerSaving)
return "nest_eco_leaf";
return "";
}
iconSize: C.Appearance.font.pixelSize.small
font.weight: Font.DemiBold
visible: text != ""
}
W.VisuallyCenteredStyledText {
visible: batteryProgress.value < 1
Layout.fillHeight: true
rotation: -90 * root.vertical
font: batteryProgress.font
text: batteryProgress.text
}
}
}
}
Rectangle {
anchors.verticalCenter: parent.verticalCenter
color: batteryProgress.trackColor
topRightRadius: width
bottomRightRadius: width
implicitWidth: 2.5
implicitHeight: 8
}
}
component SysInfoPopupContent: W.ChoreographerLoader {
id: sysInfoPopupContent
sourceComponent: W.ChoreographerGridLayout {
id: popupRoot
rowSpacing: 10
property bool showSettings: false
onShownChanged: {
if (shown) {
powerProfileSelection.focusSelectedChild();
}
}
W.FlyFadeEnterChoreographable {
z: 1
Layout.fillWidth: true
Item {
anchors {
left: parent.left
right: parent.right
}
implicitHeight: popupHeaderLayout.implicitHeight
ColumnLayout {
anchors {
top: parent.top
left: parent.left
right: parent.right
}
spacing: 10
RowLayout {
id: popupHeaderLayout
Layout.fillWidth: true
W.StyledText {
Layout.fillWidth: true
text: S.Translation.tr("Resources")
elide: Text.ElideRight
font.pixelSize: C.Appearance.font.pixelSize.title
}
W.StyledIconButton {
implicitSize: C.Appearance.rounding.normal * 2
text: "settings"
iconSize: 20
onClicked: popupRoot.showSettings = !popupRoot.showSettings;
checked: popupRoot.showSettings
}
}
W.FadeLoader {
shown: popupRoot.showSettings
Layout.fillWidth: true
sourceComponent: SysInfoPopupConfig {}
}
}
}
}
W.FlyFadeEnterChoreographable {
Layout.fillWidth: true
Column {
anchors {
left: parent.left
right: parent.right
}
spacing: 2
Item {
anchors {
left: parent.left
right: parent.right
}
implicitHeight: memUsed.implicitHeight
BigSmallTextPair {
id: memUsed
materialSymbol: "memory"
bigText: S.ResourceUsage.kbToGbString(S.ResourceUsage.memoryUsed, false)
smallText: {
const total = S.ResourceUsage.kbToGbString(S.ResourceUsage.memoryTotal, false);
return S.Translation.tr("%1").arg(`/ ${total}`)
}
W.StyledText {
Layout.alignment: Qt.AlignBaseline
text: S.Translation.tr("Memory")
color: C.Appearance.colors.colOutline
}
}
BigSmallTextPair {
id: swapUsed
TextMetrics {
id: plusTextMetric
font: swapUsed.bigFont
text: "+"
}
property real halfWidthOfAPlus: plusTextMetric.width / 2
x: Math.min(memProg.availableWidth * memProg.visualEnds[0] - halfWidthOfAPlus, parent.width - width)
bigText: "+ " + S.ResourceUsage.kbToGbString(S.ResourceUsage.swapUsed, false)
smallText: {
const total = S.ResourceUsage.kbToGbString(S.ResourceUsage.swapTotal, false);
return `/ ${total} GB`
}
}
}
W.StyledCombinedProgressBar {
id: memProg
anchors {
left: parent.left
right: parent.right
}
valueWeights: [S.ResourceUsage.memoryTotal, S.ResourceUsage.swapTotal]
values: [S.ResourceUsage.memoryUsedPercentage, S.ResourceUsage.swapUsedPercentage]
}
}
}
W.FlyFadeEnterChoreographable {
Layout.fillWidth: true
Column {
anchors {
left: parent.left
right: parent.right
}
spacing: 2
BigSmallTextPair {
spacing: 0
materialSymbol: "developer_board"
bigText: Math.round(S.ResourceUsage.cpuUsage * 100)
smallText: "%"
W.StyledText {
Layout.alignment: Qt.AlignBaseline
text: " " + S.Translation.tr("CPU")
color: C.Appearance.colors.colOutline
}
}
W.StyledCombinedProgressBar {
anchors {
left: parent.left
right: parent.right
}
property bool useSingleAggregate: S.ResourceUsage.cpuCoreUsages.length > 8
valueWeights: useSingleAggregate ? [1] : S.ResourceUsage.cpuCoreFreqCaps
values: useSingleAggregate ? [S.ResourceUsage.cpuUsage] : S.ResourceUsage.cpuCoreUsages
}
}
}
W.FlyFadeEnterChoreographable {
Layout.topMargin: 8
Layout.fillWidth: true
RowLayout {
spacing: 10
width: parent.width
W.CircularProgress {
id: battCircProg
implicitSize: 44
lineWidth: 3
value: S.Battery.percentage
W.MaterialSymbol {
anchors.centerIn: parent
iconSize: 22
fill: 1
animateChange: true
text: {
if (battCircProg.value == 1)
return "check";
if (root.chargingAndNotFull)
return "bolt";
if (root.powerSaving)
return "energy_savings_leaf";
if (PowerProfiles.profile == PowerProfile.Performance)
return "local_fire_department";
return C.Icons.getBatteryIcon(battCircProg.value * 100);
}
}
}
RowLayout {
Layout.fillWidth: false
spacing: 4
W.StyledText {
Layout.alignment: Qt.AlignBaseline
visible: S.Battery.knownEnergyRate
text: F.DateUtils.formatDuration(S.Battery.isCharging ? S.Battery.timeToFull : S.Battery.timeToEmpty)
font.pixelSize: C.Appearance.font.pixelSize.title
}
W.StyledText {
Layout.alignment: Qt.AlignBaseline
text: {
if (!S.Battery.knownEnergyRate)
return S.Battery.isCharging ? S.Translation.tr("Charging") : S.Translation.tr("Discharging");
return S.Battery.isCharging ? S.Translation.tr("to full") : S.Translation.tr("remaining");
}
}
}
Item {
Layout.fillWidth: true
}
ColumnLayout {
id: notSoImportantBatteryStats
Layout.fillWidth: false
spacing: 4
StatWithIcon {
visible: S.Battery.knownEnergyRate
Layout.alignment: Qt.AlignLeft
icon: "bolt"
value: `${S.Battery.energyRate.toFixed(1)}W`
longestValueString: "69.0W"
}
StatWithIcon {
Layout.alignment: Qt.AlignLeft
icon: "favorite"
value: `${(S.Battery.health).toFixed(1 * (S.Battery.health < 100))}%`
longestValueString: "69.0%"
}
}
}
}
W.FlyFadeEnterChoreographable {
Layout.fillWidth: true
W.ConfigSelectionArray {
id: powerProfileSelection
currentValue: PowerProfiles.profile
onSelected: newValue => {
PowerProfiles.profile = newValue;
}
options: [
{
displayName: S.Translation.tr("Power saver"),
value: PowerProfile.PowerSaver
},
{
displayName: S.Translation.tr("Balanced"),
value: PowerProfile.Balanced
},
{
displayName: S.Translation.tr("Performance"),
value: PowerProfile.Performance
}
]
}
}
}
}
component SysInfoPopupConfig: Rectangle {
implicitWidth: sysConfLayout.implicitWidth
implicitHeight: sysConfLayout.implicitHeight
radius: C.Appearance.rounding.normal
color: C.Appearance.colors.colLayer4Base
W.StyledRectangularShadow {
target: parent
z: -1
}
ColumnLayout {
id: sysConfLayout
anchors.fill: parent
W.ConfigSwitch {
buttonIcon: "pie_chart"
text: S.Translation.tr("Show memory usage")
checked: C.Config.options.hefty.bar.resources.showMemory
onCheckedChanged: {
C.Config.options.hefty.bar.resources.showMemory = checked;
}
}
W.ConfigSwitch {
buttonIcon: "memory"
text: S.Translation.tr("Show RAM usage")
checked: C.Config.options.hefty.bar.resources.showRam
onCheckedChanged: {
C.Config.options.hefty.bar.resources.showRam = checked;
}
}
W.ConfigSwitch {
buttonIcon: "swap_horiz"
text: S.Translation.tr("Show swap")
checked: C.Config.options.hefty.bar.resources.showSwap
onCheckedChanged: {
C.Config.options.hefty.bar.resources.showSwap = checked;
}
}
W.ConfigSwitch {
buttonIcon: "planner_review"
text: S.Translation.tr("Show CPU usage")
checked: C.Config.options.hefty.bar.resources.showCpu
onCheckedChanged: {
C.Config.options.hefty.bar.resources.showCpu = checked;
}
}
}
}
component BigSmallTextPair: RowLayout {
id: txtPair
property string materialSymbol: ""
property string bigText: ""
property string smallText: ""
property alias bigFont: bigTxt.font
property alias smallFont: smallTxt.font
spacing: 6
W.MaterialSymbol {
Layout.rightMargin: 6 - spacing
visible: text.length > 0
Layout.alignment: Qt.AlignVCenter
text: txtPair.materialSymbol
fill: 1
iconSize: 24
}
W.StyledText {
id: bigTxt
Layout.alignment: Qt.AlignBaseline
font.pixelSize: C.Appearance.font.pixelSize.title
text: txtPair.bigText
}
W.StyledText {
id: smallTxt
Layout.alignment: Qt.AlignBaseline
text: txtPair.smallText
}
}
component StatWithIcon: Item {
id: statItem
required property string icon
required property string value
property string longestValueString
implicitWidth: statRow.implicitWidth
implicitHeight: statRow.implicitHeight
RowLayout {
id: statRow
anchors.fill: parent
spacing: 4
W.MaterialSymbol {
Layout.fillWidth: false
Layout.alignment: Qt.AlignVCenter
text: statItem.icon
fill: 1
iconSize: 16
}
W.FixedWidthTextContainer {
longestText: statItem.longestValueString
W.VisuallyCenteredStyledText {
anchors.fill: parent
horizontalAlignment: Text.AlignLeft
text: statItem.value
}
}
Item {
Layout.fillWidth: true
}
}
}
}
@@ -1,134 +0,0 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Quickshell.Services.UPower
import qs
import qs.services as S
import qs.modules.common as C
import qs.modules.common.functions as F
import qs.modules.common.widgets as W
import ".."
HBarWidgetWithPopout {
id: root
property bool chargingAndNotFull: S.Battery.isCharging && S.Battery.percentage < 1
property bool powerSaving: PowerProfiles.profile == PowerProfile.PowerSaver
popupContentWidth: popupContent.implicitWidth
popupContentHeight: popupContent.implicitHeight
HBarWidgetContent {
id: contentRoot
vertical: root.vertical
atBottom: root.atBottom
showPopup: root.showPopup
contentImplicitWidth: activeItem.implicitWidth
contentImplicitHeight: activeItem.implicitHeight
onClicked: GlobalStates.sidebarRightOpen = !GlobalStates.sidebarRightOpen; // TODO: use own popup
property var activeItem: content
SystemIndicatorsContent {
id: content
anchors {
top: !root.vertical ? parent.top : undefined
bottom: !root.vertical ? parent.bottom : undefined
left: root.vertical ? parent.left : undefined
right: root.vertical ? parent.right : undefined
horizontalCenter: !root.vertical ? parent.horizontalCenter : undefined
verticalCenter: root.vertical ? parent.verticalCenter : undefined
}
}
SystemPanel {
id: popupContent
anchors {
top: root.vertical ? contentRoot.activeItem.top : contentRoot.activeItem.top
topMargin: root.popupContentOffsetY
left: root.vertical ? contentRoot.activeItem.left : contentRoot.activeItem.left
leftMargin: root.popupContentOffsetX
}
shown: root.showPopup
}
}
component SystemIndicatorsContent: W.BoxLayout {
vertical: root.vertical
rowSpacing: 2
columnSpacing: 8
VolMuteIcon {}
MicMuteIcon {}
WifiIcon {}
BluetoothIcon {}
}
component VolMuteIcon: IconIndicator {
visible: S.Audio.sink?.audio?.muted ?? false
W.MaterialSymbol {
anchors.centerIn: parent
text: "volume_off"
iconSize: 20
}
}
component MicMuteIcon: IconIndicator {
visible: S.Audio.source?.audio?.muted ?? false
W.MaterialSymbol {
anchors.centerIn: parent
text: "mic_off"
iconSize: 20
}
}
component BluetoothIcon: IconIndicator {
visible: S.BluetoothStatus.available
child: W.MaterialSymbol {
anchors.centerIn: parent
iconSize: 20
text: S.BluetoothStatus.connected ? "bluetooth_connected" : S.BluetoothStatus.enabled ? "bluetooth" : "bluetooth_disabled"
}
}
component WifiIcon: IconIndicator {
child: W.MaterialSymbol {
anchors.centerIn: parent
text: C.Icons.getNetworkMaterialSymbol()
iconSize: 20
}
}
component IconIndicator: Item {
Layout.fillWidth: root.vertical
Layout.fillHeight: !root.vertical
default property Item child
implicitWidth: child.implicitWidth
implicitHeight: child.implicitHeight
children: [child]
}
component SystemPanel: W.ChoreographerLoader {
sourceComponent: W.ChoreographerGridLayout {
id: panelRoot
W.FlyFadeEnterChoreographable {
W.StyledText {
text: "five little chuddies jumping on da bed"
}
}
W.FlyFadeEnterChoreographable {
W.StyledText {
text: "one fell off and bumped his head"
}
}
W.FlyFadeEnterChoreographable {
W.StyledText {
text: "momma call the doctor and the doctor sez"
}
}
}
}
}
@@ -1,140 +0,0 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import Quickshell
import Quickshell.Wayland
import Quickshell.Hyprland
import qs.services
import qs.modules.common
import qs.modules.common.widgets
import qs.modules.ii.bar
import ".."
HBarWidgetWithPopout {
id: root
property list<var> pinnedItems: TrayService.pinnedItems
property list<var> unpinnedItems: TrayService.unpinnedItems
popupContentWidth: popupContent.implicitWidth
popupContentHeight: popupContent.implicitHeight
Layout.maximumWidth: vertical ? -1 : implicitWidth
Layout.maximumHeight: vertical ? implicitHeight : -1
Layout.fillWidth: true
Layout.fillHeight: true
HBarWidgetContent {
id: contentRoot
vertical: root.vertical
atBottom: root.atBottom
showPopup: root.showPopup
contentImplicitWidth: trayContent.implicitWidth
contentImplicitHeight: trayContent.implicitHeight
hoverEnabled: false
parentRadiusToPaddingRatio: 0.45
hover: trayContent.moreHovered
press: trayContent.morePressed
TrayContent {
id: trayContent
anchors.fill: parent
vertical: root.vertical
}
UnpinnedItemsPopup {
id: popupContent
anchors {
top: parent.top
topMargin: root.popupContentOffsetY
left: parent.left
leftMargin: root.popupContentOffsetX
}
shown: root.showPopup
}
}
component TrayContent: BoxLayout {
spacing: 4
property alias moreHovered: moreBtn.containsMouse
property alias morePressed: moreBtn.containsPress
ButtonMouseArea {
id: moreBtn
visible: TrayService.unpinnedItems.length > 0
Layout.fillWidth: true
Layout.fillHeight: true
Layout.topMargin: 4 * root.vertical
Layout.leftMargin: 4 * !root.vertical
hoverEnabled: true
acceptedButtons: Qt.AllButtons
implicitWidth: 20 - parent.spacing
implicitHeight: 20 - parent.spacing
onClicked: root.showPopup = !root.showPopup
MaterialSymbol {
anchors.centerIn: parent
iconSize: 20
text: "expand_more"
horizontalAlignment: Text.AlignHCenter
color: root.showPopup ? Appearance.colors.colOnSecondaryContainer : Appearance.colors.colOnLayer2
rotation: (root.showPopup ? 180 : 0) - (90 * root.vertical) + (180 * root.atBottom)
Behavior on rotation {
animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
}
}
}
Repeater {
model: root.pinnedItems
delegate: StyledTrayItem {
Layout.fillWidth: true
Layout.fillHeight: true
required property var modelData
item: modelData
}
}
}
component UnpinnedItemsPopup: ChoreographerLoader {
sourceComponent: ChoreographerGridLayout {
id: popupRoot
totalDuration: 70
columns: root.vertical ? 1 : -1
columnSpacing: 8
rowSpacing: 8
Repeater {
model: root.unpinnedItems
delegate: FlyFadeEnterChoreographable {
id: unpinnedTrayItem
required property var modelData
StyledTrayItem {
item: unpinnedTrayItem.modelData
}
}
}
}
}
component StyledTrayItem: SysTrayItem {
iconSize: 18
propagateComposedEvents: false
property var menuWindow
onMenuClosed: {
if (menuWindow)
GlobalFocusGrab.removeDismissable(menuWindow);
}
onMenuOpened: (qsWindow) => {
menuWindow = qsWindow;
GlobalFocusGrab.addDismissable(qsWindow)
}
}
}
@@ -1,283 +0,0 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import qs.modules.common as C
import qs.services as S
import qs.modules.common.widgets as W
import ".."
HBarWidgetWithPopout {
id: root
readonly property string timeFormatString: C.Config.options.time.format
readonly property bool is12h: timeFormatString.startsWith("h:")
readonly property bool hasAmPm: timeFormatString.toLowerCase().includes("ap") || timeFormatString.toLowerCase().endsWith("a")
readonly property bool capitalizedAmPm: timeFormatString.includes("AP") || timeFormatString.endsWith("A")
popupContentWidth: popupContent.implicitWidth
popupContentHeight: popupContent.implicitHeight
// The button on the bar
HBarWidgetContent {
id: contentRoot
vertical: root.vertical
atBottom: root.atBottom
showPopup: root.showPopup
onClicked: root.showPopup = !showPopup
contentImplicitWidth: activeItem.implicitWidth
contentImplicitHeight: activeItem.implicitHeight
property Item activeItem: vertical ? verticalContent : horizontalContent
// When horizontal
Loader {
id: horizontalContent
anchors.fill: parent
active: !contentRoot.vertical
sourceComponent: HorizontalClock {}
}
// When vertical
Loader {
id: verticalContent
anchors.fill: parent
active: contentRoot.vertical
sourceComponent: VerticalClock {}
}
// Popup content
PopupContent {
id: popupContent
anchors {
top: parent.top
left: parent.left
topMargin: root.popupContentOffsetY
leftMargin: root.popupContentOffsetX
}
shown: root.showPopup
}
}
component HorizontalClock: Item {
implicitWidth: contentLayout.implicitWidth
implicitHeight: contentLayout.implicitHeight
RowLayout {
id: contentLayout
anchors.fill: parent
W.FixedWidthTextContainer {
Layout.leftMargin: contentRoot.layoutParentTopLeftRadius * contentRoot.parentRadiusToPaddingRatio
Layout.alignment: Qt.AlignVCenter | Qt.AlignHCenter
Layout.fillHeight: true
longestText: Qt.locale().toString(new Date(1984, 6, 9, 00, 00, 00), root.timeFormatString)
font: clockText.font
W.VisuallyCenteredStyledText {
id: clockText
anchors.fill: parent
font.pixelSize: C.Appearance.font.pixelSize.large
color: C.Appearance.colors.colOnLayer1
text: S.DateTime.time
}
}
W.VisuallyCenteredStyledText {
Layout.alignment: Qt.AlignVCenter
font.pixelSize: C.Appearance.font.pixelSize.small
color: C.Appearance.colors.colOnLayer1
text: "•"
}
W.VisuallyCenteredStyledText {
Layout.alignment: Qt.AlignVCenter
font.pixelSize: C.Appearance.font.pixelSize.small
color: C.Appearance.colors.colOnLayer1
text: S.DateTime.longDate
}
}
}
component VerticalClock: Item {
implicitWidth: contentLayoutVertical.implicitWidth
implicitHeight: contentLayoutVertical.implicitHeight
ColumnLayout {
id: contentLayoutVertical
anchors.fill: parent
spacing: amPmText.visible ? -2 : -4
ColumnLayout {
id: verticalTime
Layout.alignment: Qt.AlignHCenter
spacing: -4
W.StyledText {
Layout.alignment: Qt.AlignHCenter
font.pixelSize: C.Appearance.font.pixelSize.large
color: C.Appearance.colors.colOnLayer1
text: {
var hrs = S.DateTime.clock.hours;
if (root.is12h && hrs != 12)
hrs %= 12;
return hrs.toString().padStart(2, '0');
}
}
W.StyledText {
Layout.alignment: Qt.AlignHCenter
font.pixelSize: C.Appearance.font.pixelSize.large
color: C.Appearance.colors.colOnLayer1
text: S.DateTime.clock.minutes.toString().padStart(2, '0')
}
W.StyledText {
id: amPmText
visible: root.hasAmPm
Layout.topMargin: -2
Layout.alignment: Qt.AlignHCenter
font.pixelSize: C.Appearance.font.pixelSize.smaller
color: C.Appearance.colors.colOnLayer1
text: Qt.locale().toString(S.DateTime.clock.date, root.capitalizedAmPm ? "AP" : "ap")
}
}
W.StyledText {
Layout.alignment: Qt.AlignHCenter
font.pixelSize: C.Appearance.font.pixelSize.smallest
color: C.Appearance.colors.colOnLayer1
text: S.DateTime.shortDate
}
}
}
component PopupContent: W.ChoreographerLoader {
sourceComponent: W.ChoreographerGridLayout {
id: popupRoot
property real buttonSize: C.Appearance.rounding.normal * 2
property real buttonSpacing: 4
rowSpacing: 2
W.FlyFadeEnterChoreographable {
Layout.fillWidth: true
Layout.bottomMargin: 6
RowLayout {
width: parent.width
spacing: 0
W.StyledText {
Layout.leftMargin: 6
Layout.alignment: Qt.AlignVCenter
Layout.fillWidth: true
text: calendarView.title
font.pixelSize: C.Appearance.font.pixelSize.larger
elide: Text.ElideRight
color: C.Appearance.colors.colSecondary
}
W.StyledIconButton {
implicitSize: 30
text: "chevron_left"
iconSize: 20
onClicked: calendarView.scrollMonthsAndSnap(-1)
colForeground: C.Appearance.colors.colPrimary
}
W.StyledIconButton {
implicitSize: 30
text: "chevron_right"
iconSize: 20
onClicked: calendarView.scrollMonthsAndSnap(1)
colForeground: C.Appearance.colors.colPrimary
}
W.StyledIconButton {
implicitSize: 30
text: "rotate_left"
iconSize: 20
onClicked: calendarView.scrollToToday()
colForeground: C.Appearance.colors.colPrimary
enabled: calendarView.targetWeekDiff != 0
}
}
}
W.FlyFadeEnterChoreographable {
Layout.alignment: Qt.AlignHCenter
W.CalendarDaysOfWeek {
locale: calendarView.locale
spacing: popupRoot.buttonSpacing
delegate: Item {
id: dowItem
required property var model
implicitWidth: popupRoot.buttonSize
implicitHeight: dowText.implicitHeight
W.VisuallyCenteredStyledText {
id: dowText
anchors.centerIn: parent
font.pixelSize: C.Appearance.font.pixelSize.smaller
color: C.Appearance.colors.colOutline
text: {
var result = dowItem.model.shortName;
if (C.Config.options.calendar.force2CharDayOfWeek)
result = result.substring(0, 2);
return result;
}
}
}
}
}
W.FlyFadeEnterChoreographable {
Item {
implicitWidth: calendarView.implicitWidth - calendarView.horizontalPadding * 2
implicitHeight: calendarView.implicitHeight - calendarView.verticalPadding * 2
W.CalendarView {
id: calendarView
anchors.centerIn: parent
locale: Qt.locale(C.Config.options.calendar.locale)
verticalPadding: 4
horizontalPadding: 4
buttonSize: popupRoot.buttonSize
buttonSpacing: popupRoot.buttonSpacing
buttonVerticalSpacing: popupRoot.buttonSpacing
Layout.fillWidth: true
delegate: W.StyledButton {
id: dayButton
required property var model
focus: model.today
checked: model.today
enabled: model.month === calendarView.focusedMonth
implicitWidth: popupRoot.buttonSize
implicitHeight: popupRoot.buttonSize
width: implicitWidth
height: implicitHeight
text: model.day
Connections {
target: popupRoot
enabled: dayButton.model.today
function onShownChanged() {
if (popupRoot.shown)
dayButton.forceActiveFocus();
}
}
contentItem: Item {
W.VisuallyCenteredStyledText {
anchors.centerIn: parent
text: dayButton.text
color: dayButton.colForeground
}
}
}
}
}
}
}
}
}
@@ -1,282 +0,0 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import Quickshell
import Quickshell.Wayland
import Quickshell.Hyprland
import qs.services
import qs.modules.common
import qs.modules.common.widgets
import ".."
HBarWidgetWithPopout {
id: root
readonly property HyprlandMonitor monitor: Hyprland.monitorFor(root.QsWindow.window?.screen)
readonly property Toplevel activeWindow: ToplevelManager.activeToplevel
readonly property var activeHyprlandClient: HyprlandData.clientForToplevel(activeWindow)
readonly property bool focusingThisMonitor: HyprlandData.activeWorkspace?.monitor == monitor?.name
readonly property var biggestWindow: HyprlandData.biggestWindowForWorkspace(HyprlandData.monitors[root.monitor?.id]?.activeWorkspace.id)
readonly property bool hasFocusedWindow: (focusingThisMonitor && activeWindow?.activated && biggestWindow) ?? false
readonly property string primaryText: {
if (root.hasFocusedWindow)
return root.activeWindow?.title;
return (root.biggestWindow?.title) ?? `${Translation.tr("Workspace")} ${root.monitor?.activeWorkspace?.id ?? 1}`;
}
readonly property string secondaryText: {
if (root.hasFocusedWindow && root.activeWindow?.appId != "" && root.activeWindow?.appId != primaryText)
return root.activeWindow?.appId;
return Translation.tr("Options")
}
onPrimaryTextChanged: showPopup = false;
property real fontPixelSize: Appearance.font.pixelSize.smaller
Layout.maximumWidth: vertical ? -1 : implicitWidth
Layout.fillWidth: true
popupContentWidth: popupContent.implicitWidth
popupContentHeight: popupContent.implicitHeight
HBarWidgetContent {
id: contentRoot
Layout.fillWidth: true
Layout.fillHeight: true
vertical: root.vertical
atBottom: root.atBottom
contentImplicitWidth: winTitleContent.implicitWidth
contentImplicitHeight: winTitleContent.implicitHeight
showPopup: false
onClicked: root.showPopup = !root.showPopup;
WinTitleContent {
id: winTitleContent
}
WinOptionsPopup {
id: popupContent
anchors {
top: parent.top
topMargin: root.popupContentOffsetY
left: parent.left
leftMargin: root.popupContentOffsetX
}
shown: root.showPopup
}
}
component WinTitleContent: BoxLayout {
anchors.fill: parent
vertical: root.vertical
spacing: 4
Item {
Layout.leftMargin: 4 * !root.vertical
Layout.topMargin: 3 * root.vertical
Layout.bottomMargin: 4 * root.vertical
Layout.alignment: Qt.AlignCenter
implicitWidth: appIcon.implicitWidth
implicitHeight: appIcon.implicitHeight
AppIcon {
id: appIcon
anchors.centerIn: parent
opacity: 0
source: Quickshell.iconPath(AppSearch.guessIcon(root.activeWindow?.appId), "image-missing")
implicitSize: 16
animated: false
}
Circle {
id: iconMask
visible: false
layer.enabled: true
diameter: appIcon.implicitSize
}
Loader {
id: renderedIconLoader
anchors.fill: appIcon
visible: root.activeWindow
sourceComponent: Colorizer {
implicitWidth: appIcon.implicitWidth
implicitHeight: appIcon.implicitHeight
colorizationColor: Appearance.m3colors.darkmode ? Appearance.colors.colOnSecondaryContainer : Appearance.colors.colOnPrimary
colorization: Config.options.bar.workspaces.monochromeIcons ? 0.8 : 0.5
brightness: 0
source: appIcon
maskEnabled: true
maskSource: iconMask
maskThresholdMin: 0.5
maskSpreadAtMin: 1
}
}
MaterialSymbol {
anchors.centerIn: parent
visible: !renderedIconLoader.visible
text: "overview_key"
iconSize: 16
}
}
Item {
visible: !root.vertical
Layout.rightMargin: 4
Layout.alignment: Qt.AlignCenter
Layout.fillHeight: true
// No overflow
Layout.maximumWidth: implicitWidth
Layout.fillWidth: true
// Size
implicitWidth: winText.implicitWidth
implicitHeight: winText.implicitHeight
FlyFadeEnterChoreographable {
anchors.fill: parent
progress: contentRoot.containsMouse ? 0 : 1
reverseDirection: true
animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
VisuallyCenteredStyledText {
id: winText
height: parent.height
width: parent.width
elide: Text.ElideRight
// Styles & text
font.pixelSize: root.fontPixelSize
text: root.primaryText
}
}
FlyFadeEnterChoreographable {
anchors.fill: parent
progress: contentRoot.containsMouse ? 1 : 0
animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
VisuallyCenteredStyledText {
height: parent.height
width: parent.width
elide: Text.ElideRight
horizontalAlignment: Text.AlignLeft
// Styles & text
font.pixelSize: root.fontPixelSize
text: root.secondaryText
}
}
}
}
component WinOptionsPopup: ChoreographerLoader {
sourceComponent: ChoreographerGridLayout {
id: popupRoot
columns: 3
rowSpacing: 8
columnSpacing: 6
FlyFadeEnterChoreographable {
Layout.fillWidth: true
Layout.columnSpan: 3
StyledText {
width: parent.width
horizontalAlignment: Text.AlignHCenter
wrapMode: Text.Wrap
text: root.hasFocusedWindow ? Translation.tr("Window options") : Translation.tr("Launch")
// font.pixelSize: Appearance.font.pixelSize.title
}
}
FlyFadeEnterChoreographable {
visible: !root.hasFocusedWindow
PopupLabeledIconButton {
materialSymbol: "terminal"
text: Translation.tr("Terminal")
onClicked: Quickshell.execDetached(["bash", "-c", Config.options.apps.terminal]);
}
}
FlyFadeEnterChoreographable {
visible: !root.hasFocusedWindow
PopupLabeledIconButton {
materialSymbol: "files"
text: Translation.tr("Files")
onClicked: Qt.openUrlExternally(Directories.home);
}
}
FlyFadeEnterChoreographable {
visible: !root.hasFocusedWindow
PopupLabeledIconButton {
materialSymbol: "language"
text: Translation.tr("Browser")
// Kinda hacky. Works with Google and DDG at least
onClicked: Qt.openUrlExternally(Config.options.search.engineBaseUrl);
}
}
FlyFadeEnterChoreographable {
visible: root.hasFocusedWindow
PopupLabeledIconButton {
materialSymbol: "content_copy"
text: Translation.tr("Address")
onClicked: Quickshell.clipboardText = root.activeHyprlandClient.address
}
}
FlyFadeEnterChoreographable {
visible: root.hasFocusedWindow
PopupLabeledIconButton {
property bool toFloat: !(root.activeHyprlandClient?.floating ?? false)
materialSymbol: toFloat ? "picture_in_picture_center" : "side_navigation"
text: toFloat ? Translation.tr("Float") : Translation.tr("Tile")
onClicked: {
Hyprland.dispatch(`hl.dsp.window.float({action = "toggle", window = {address=${root.activeHyprlandClient.address}}})`)
HyprlandData.updateWindowList()
}
}
}
FlyFadeEnterChoreographable {
visible: root.hasFocusedWindow
PopupLabeledIconButton {
materialSymbol: "warning"
text: Translation.tr("Kill")
colBackground: Appearance.colors.colError
colForeground: Appearance.colors.colOnError
onClicked: {
Hyprland.dispatch(`hl.dsp.window.kill({window = {address=${root.activeHyprlandClient.address}}})`)
HyprlandData.updateWindowList()
}
}
}
}
}
component PopupLabeledIconButton: Column {
id: licobtn
property string materialSymbol: "circle"
property string text: "Label"
property alias colBackground: btn.colBackground
property alias colForeground: btn.colForeground
spacing: 4
signal clicked()
onClicked: root.showPopup = false
StyledIconButton {
id: btn
implicitWidth: 70
implicitHeight: 50
text: licobtn.materialSymbol
iconSize: 24
colBackground: Appearance.colors.colLayer4
colForeground: Appearance.colors.colOnLayer4
onClicked: licobtn.clicked()
}
StyledText {
anchors.horizontalCenter: parent.horizontalCenter
text: licobtn.text
}
}
}
@@ -1,441 +0,0 @@
pragma ComponentBehavior: Bound
import qs
import qs.modules.common
import qs.modules.common.models
import qs.modules.common.widgets
import qs.modules.common.functions
import qs.services
import QtQuick
import QtQuick.Effects
import QtQuick.Layouts
import Quickshell
import Quickshell.Hyprland
import ".."
HBarWidgetContainer {
id: containerRoot
ButtonMouseArea {
id: root
Layout.topMargin: (5 + 5 * containerRoot.startSide) * containerRoot.vertical
Layout.bottomMargin: (5 + 5 * containerRoot.endSide) * containerRoot.vertical
Layout.leftMargin: (3 + 4 * containerRoot.startSide) * !containerRoot.vertical
Layout.rightMargin: (3 + 4 * containerRoot.endSide) * !containerRoot.vertical
readonly property HyprlandMonitor monitor: Hyprland.monitorFor(root.QsWindow.window?.screen)
WorkspaceModel {
id: wsModel
monitor: root.monitor
}
property bool vertical: Config.options.bar.vertical
property bool superPressAndHeld: false // Relevant modifications at bottom of file
property real workspaceButtonWidth: 26
property real activeWorkspaceMargin: 2
property real activeWorkspaceSize: workspaceButtonWidth - activeWorkspaceMargin * 2
property real workspaceIconSize: workspaceButtonWidth * 0.69
property real workspaceIconSizeShrinked: workspaceButtonWidth * 0.55
property real workspaceIconOpacityShrinked: 1
property real workspaceIconMarginShrinked: -4
property int workspaceIndexInGroup: (monitor?.activeWorkspace?.id - 1) % wsModel.shownCount
property real specialTextSize: workspaceButtonWidth * 0.5
Layout.alignment: vertical ? Qt.AlignHCenter : Qt.AlignVCenter
Layout.fillWidth: vertical
Layout.fillHeight: !vertical
readonly property real barThickness: vertical ? Appearance.sizes.verticalBarWidth : Appearance.sizes.barHeight
implicitWidth: vertical ? barThickness : occupiedIndicators.implicitWidth
implicitHeight: vertical ? occupiedIndicators.implicitHeight : barThickness
property real specialBlur: (wsModel.specialWorkspaceActive && !containsMouse) ? 1 : 0
Behavior on specialBlur {
animation: Appearance.animation.elementMoveSmall.numberAnimation.createObject(this)
}
// Interactions
acceptedButtons: Qt.LeftButton | Qt.RightButton
hoverEnabled: true
property int hoverIndex: {
const position = root.vertical ? mouseY : mouseX;
return Math.floor(position / root.workspaceButtonWidth);
}
function switchWorkspaceToHovered() {
Hyprland.dispatch(`hl.dsp.focus({workspace = ${wsModel.getWorkspaceIdAt(hoverIndex)}})`)
}
onPressed: (mouse) => {
if (mouse.button == Qt.LeftButton)
switchWorkspaceToHovered()
else if (mouse.button == Qt.RightButton)
GlobalStates.overviewOpen = !GlobalStates.overviewOpen;
}
onWheel: (event) => {
if (event.angleDelta.y < 0)
Hyprland.dispatch(`hl.dsp.focus({workspace = "r+1"})`);
else if (event.angleDelta.y > 0)
Hyprland.dispatch(`hl.dsp.focus({workspace = "r-1"})`);
}
// Indications
Item {
id: regularWorkspaces
anchors.fill: parent
scale: 1 - 0.08 * root.specialBlur
layer.smooth: true
layer.enabled: root.specialBlur > 0
layer.effect: MultiEffect {
brightness: -0.1 * root.specialBlur
blurEnabled: true
blur: root.specialBlur
blurMax: 32
}
/////////////////// Occupied indicators ///////////////////
StyledRectangle {
id: occupiedIndicatorsBg
anchors.fill: parent
contentLayer: StyledRectangle.ContentLayer.Group
color: ColorUtils.transparentize(Appearance.m3colors.m3secondaryContainer, 0.4)
visible: false
}
WorkspaceLayout {
id: occupiedIndicators
anchors.centerIn: parent
layer.enabled: true
visible: false
Repeater {
model: wsModel.shownCount
delegate: Item {
id: wsBg
required property int index
readonly property int wsId: wsModel.getWorkspaceIdAt(index)
property bool currentOccupied: wsModel.occupied[index] && wsId != wsModel.fakeWorkspace
property bool previousOccupied: index > 0 && wsModel.occupied[index - 1] && (wsId - 1) != wsModel.fakeWorkspace
property bool nextOccupied: index < wsModel.shownCount - 1 && wsModel.occupied[index + 1] && (wsId + 1) != wsModel.fakeWorkspace
implicitWidth: root.workspaceButtonWidth
implicitHeight: root.workspaceButtonWidth
// The idea: over-stretch to occupied sides, animate this for a smooth transition.
// masking already prevents weird overlaps
Pill {
property real undirectionalWidth: root.workspaceButtonWidth * wsBg.currentOccupied
property real undirectionalLength: root.workspaceButtonWidth * (1 + 0.5 * wsBg.previousOccupied + 0.5 * wsBg.nextOccupied) * currentOccupied
property real undirectionalOffset: (!wsBg.currentOccupied ? 0.5 : -0.5 * wsBg.previousOccupied) * root.workspaceButtonWidth
anchors.verticalCenter: root.vertical ? undefined : parent.verticalCenter
anchors.horizontalCenter: root.vertical ? parent.horizontalCenter : undefined
x: root.vertical ? 0 : undirectionalOffset
y: root.vertical ? undirectionalOffset : 0
implicitWidth: root.vertical ? undirectionalWidth : undirectionalLength
implicitHeight: root.vertical ? undirectionalLength : undirectionalWidth
Behavior on undirectionalWidth {
animation: Appearance.animation.elementMoveSmall.numberAnimation.createObject(this)
}
Behavior on undirectionalLength {
animation: Appearance.animation.elementMoveSmall.numberAnimation.createObject(this)
}
Behavior on undirectionalOffset {
animation: Appearance.animation.elementMoveSmall.numberAnimation.createObject(this)
}
}
}
}
}
MaskMultiEffect {
id: occupiedIndicatorsMultiEffect
z: 1
anchors.centerIn: parent
implicitWidth: occupiedIndicators.implicitWidth
implicitHeight: occupiedIndicators.implicitHeight
source: occupiedIndicatorsBg
maskSource: occupiedIndicators
}
/////////////////// Active indicator ///////////////////
TrailingIndicator {
id: activeIndicator
anchors.fill: parent
z: 2
index: root.workspaceIndexInGroup
}
/////////////////// Hover ///////////////////
TrailingIndicator {
id: interactionIndicator
z: 3
index: root.containsMouse ? root.hoverIndex : root.workspaceIndexInGroup
color: "transparent"
StateOverlay {
id: hoverOverlay
anchors.fill: interactionIndicator.indicatorRectangle
radius: root.activeWorkspaceSize / 2
hover: root.containsMouse
press: root.containsPress
drag: true // There are too many layers so we need to force this to be a lil more opaque
contentColor: Appearance.colors.colPrimary
}
}
/////////////////// Numbers ///////////////////
WorkspaceLayout {
id: numbersGrid
z: 4
layer.enabled: true // For the masking
Repeater {
model: wsModel.shownCount
delegate: NumberWorkspaceItem {}
}
}
Colorizer {
z: 5
anchors.fill: numbersGrid
colorizationColor: Appearance.colors.colOnPrimary
sourceColor: Appearance.colors.colOnSecondaryContainer
source: activeIndicator
maskEnabled: true
maskSource: numbersGrid
maskThresholdMin: 0.5
maskSpreadAtMin: 1
}
/////////////////// App icons ///////////////////
WorkspaceLayout {
id: appsGrid
z: 6
Repeater {
model: wsModel.shownCount
delegate: WorkspaceItem {
id: wsApp
property var biggestWindow: wsModel.biggestWindow[index]
property var mainAppIconSource: Quickshell.iconPath(AppSearch.guessIcon(biggestWindow?.class), "image-missing")
AppIcon {
id: appIcon
property real cornerMargin: (!root.superPressAndHeld && Config.options?.bar.workspaces.showAppIcons && wsApp.biggestWindow) ? (root.workspaceButtonWidth - root.workspaceIconSize) / 2 : root.workspaceIconMarginShrinked
anchors {
bottom: parent.bottom
right: parent.right
bottomMargin: (parent.implicitHeight - root.workspaceButtonWidth) / 2 + cornerMargin
rightMargin: (parent.implicitWidth - root.workspaceButtonWidth) / 2 + cornerMargin
}
animated: !wsApp.biggestWindow // Prevent the "image-missing" icon
visible: false // Prevent dupe: the colorizer already copies the icon
source: wsApp.mainAppIconSource
implicitSize: NumberUtils.roundToEven(root.workspaceIconSize)
Behavior on opacity {
animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
}
Behavior on cornerMargin {
animation: Appearance.animation.elementMoveSmall.numberAnimation.createObject(this)
}
}
Circle {
id: iconMask
visible: false
layer.enabled: true
diameter: appIcon.implicitSize
}
Loader { // Somehow putting this multieffect in a loader prevents it from not showing up
id: colorizer
anchors.fill: appIcon
sourceComponent: Colorizer {
implicitWidth: appIcon.implicitWidth
implicitHeight: appIcon.implicitHeight
colorizationColor: Appearance.m3colors.darkmode ? Appearance.colors.colOnSecondaryContainer : Appearance.colors.colOnPrimary
colorization: Config.options.bar.workspaces.monochromeIcons ? 0.8 : 0.5
brightness: 0
source: appIcon
opacity: !Config.options?.bar.workspaces.showAppIcons ? 0 : (wsApp.biggestWindow && !root.superPressAndHeld && Config.options?.bar.workspaces.showAppIcons) ? 1 : wsApp.biggestWindow ? root.workspaceIconOpacityShrinked : 0
visible: opacity > 0
scale: ((!root.superPressAndHeld && Config.options?.bar.workspaces.showAppIcons) ? root.workspaceIconSize : root.workspaceIconSizeShrinked) / root.workspaceIconSize
Behavior on opacity {
animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
}
Behavior on scale {
animation: Appearance.animation.elementMoveSmall.numberAnimation.createObject(this)
}
maskEnabled: true
maskSource: iconMask
maskThresholdMin: 0.5
maskSpreadAtMin: 1
}
}
}
}
}
}
FadeLoader {
anchors.centerIn: parent
shown: wsModel.specialWorkspaceActive
scale: 0.8 + 0.2 * root.specialBlur
opacity: root.specialBlur
Behavior on opacity {} // Don't animate, as specialBlur is already animated
sourceComponent: Pill {
anchors.centerIn: parent
property real undirectionalWidth: root.activeWorkspaceSize
property real undirectionalLength: {
const base = root.workspaceButtonWidth * Math.min(1.35, wsModel.shownCount); // Who tf only configures only 2 workspaces shown anyway?
if (root.vertical)
return base;
return specialWsText.implicitWidth + undirectionalWidth;
}
color: Appearance.colors.colPrimary
implicitWidth: root.vertical ? undirectionalWidth : undirectionalLength
implicitHeight: root.vertical ? undirectionalLength : undirectionalWidth
StyledText {
id: specialWsText
anchors.centerIn: parent
text: (!root.vertical ? wsModel.specialWorkspaceName : "S")
color: Appearance.colors.colOnPrimary
font.pixelSize: root.specialTextSize
}
Behavior on undirectionalLength {
animation: Appearance.animation.elementMoveEnter.numberAnimation.createObject(this)
}
}
}
/////////////////// Super key press handling ///////////////////
Timer {
id: superPressAndHeldTimer
interval: (Config?.options.bar.autoHide.showWhenPressingSuper.delay ?? 100)
repeat: false
onTriggered: {
root.superPressAndHeld = true;
}
}
Connections {
target: GlobalStates
function onSuperDownChanged() {
if (!Config?.options.bar.autoHide.showWhenPressingSuper.enable)
return;
if (GlobalStates.superDown)
superPressAndHeldTimer.restart();
else {
superPressAndHeldTimer.stop();
root.superPressAndHeld = false;
}
}
function onSuperReleaseMightTriggerChanged() {
superPressAndHeldTimer.stop();
}
}
}
/////////////////// Components ///////////////////
component WorkspaceLayout: Box {
anchors {
top: !root.vertical ? parent.top : undefined
bottom: !root.vertical ? parent.bottom : undefined
left: root.vertical ? parent.left : undefined
right: root.vertical ? parent.right : undefined
}
rowSpacing: 0
columnSpacing: 0
vertical: root.vertical
}
component WorkspaceItem: Item {
required property int index
readonly property int wsId: wsModel.getWorkspaceIdAt(index)
implicitWidth: root.vertical ? root.barThickness : root.workspaceButtonWidth
implicitHeight: root.vertical ? root.workspaceButtonWidth : root.barThickness
}
component NumberWorkspaceItem: WorkspaceItem {
id: wsNum
property bool hasBiggestWindow: !!wsModel.biggestWindow[index]
property int wsId: wsModel.getWorkspaceIdAt(index)
property color contentColor: (wsModel.occupied[wsNum.index] && wsId !== wsModel.fakeWorkspace) ? Appearance.colors.colOnSecondaryContainer : Appearance.colors.colOnLayer1Inactive
property bool showingNumbers: {
if (root.superPressAndHeld) return true;
if (GlobalStates.screenLocked) return false;
if (Config.options?.bar.workspaces.alwaysShowNumbers && (!Config.options?.bar.workspaces.showAppIcons || !wsNum.hasBiggestWindow)) return true;
return false;
}
FadeLoader {
shown: !wsNum.showingNumbers
anchors.centerIn: parent
Circle {
anchors.centerIn: parent
diameter: root.workspaceButtonWidth * 0.18
color: wsNum.contentColor
}
}
FadeLoader {
shown: wsNum.showingNumbers
anchors.centerIn: parent
StyledText {
anchors.centerIn: parent
font {
pixelSize: Appearance.font.pixelSize.small - ((text.length - 1) * (text !== "10") * 2)
family: Config.options?.bar.workspaces.useNerdFont ? Appearance.font.family.iconNerd : defaultFont
}
color: wsNum.contentColor
text: Config.options?.bar.workspaces.numberMap[wsNum.wsId - 1] || wsNum.wsId
}
}
}
component TrailingIndicator: Item {
id: trailingIndicator
anchors.fill: parent
required property int index
property alias indicatorRectangle: indicatorRect
property alias color: indicatorRect.color
property var indexPair: AnimatedTabIndexPair {
id: idxPair
index: trailingIndicator.index
}
StyledRectangle {
id: indicatorRect
anchors {
verticalCenter: root.vertical ? undefined : parent.verticalCenter
horizontalCenter: root.vertical ? parent.horizontalCenter : undefined
}
property real indicatorPosition: Math.min(idxPair.idx1, idxPair.idx2) * root.workspaceButtonWidth + root.activeWorkspaceMargin
property real indicatorLength: Math.abs(idxPair.idx1 - idxPair.idx2) * root.workspaceButtonWidth + root.activeWorkspaceSize
property real indicatorThickness: root.activeWorkspaceSize
contentLayer: StyledRectangle.ContentLayer.Group
radius: indicatorThickness / 2
color: Appearance.colors.colPrimary
x: root.vertical ? null : indicatorPosition
y: root.vertical ? indicatorPosition : null
implicitWidth: root.vertical ? indicatorThickness : indicatorLength
implicitHeight: root.vertical ? indicatorLength : indicatorThickness
}
}
}
@@ -1,12 +0,0 @@
import QtQuick
import QtQuick.Layouts
import qs.modules.ii.bar as IIBar
import qs.modules.common as C
IIBar.Workspaces {
id: root
vertical: C.Config.options.bar.vertical
Layout.alignment: vertical ? Qt.AlignHCenter : Qt.AlignVCenter
Layout.fillWidth: vertical
Layout.fillHeight: !vertical
}
@@ -1,9 +0,0 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
Item {
visible: false
Layout.fillWidth: false
Layout.fillHeight: false
}
@@ -1,9 +0,0 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
Item {
visible: false
Layout.fillHeight: true
Layout.fillWidth: true
}
@@ -12,8 +12,9 @@ Item {
readonly property HyprlandMonitor monitor: Hyprland.monitorFor(root.QsWindow.window?.screen) readonly property HyprlandMonitor monitor: Hyprland.monitorFor(root.QsWindow.window?.screen)
readonly property Toplevel activeWindow: ToplevelManager.activeToplevel readonly property Toplevel activeWindow: ToplevelManager.activeToplevel
readonly property bool focusingThisMonitor: HyprlandData.activeWorkspace?.monitor == monitor?.name property string activeWindowAddress: `0x${activeWindow?.HyprlandToplevel?.address}`
readonly property var biggestWindow: HyprlandData.biggestWindowForWorkspace(HyprlandData.monitors[root.monitor?.id]?.activeWorkspace.id) property bool focusingThisMonitor: HyprlandData.activeWorkspace?.monitor == monitor?.name
property var biggestWindow: HyprlandData.biggestWindowForWorkspace(HyprlandData.monitors[root.monitor?.id]?.activeWorkspace.id)
implicitWidth: colLayout.implicitWidth implicitWidth: colLayout.implicitWidth
@@ -303,7 +303,7 @@ Item { // Bar content region
} }
} }
MaterialSymbol { MaterialSymbol {
text: Icons.getNetworkMaterialSymbol() text: Network.materialSymbol
iconSize: Appearance.font.pixelSize.larger iconSize: Appearance.font.pixelSize.larger
color: rightSidebarButton.colText color: rightSidebarButton.colText
} }
@@ -1,5 +1,4 @@
import qs.modules.common import qs.modules.common
import qs.modules.common.widgets
import QtQuick import QtQuick
import QtQuick.Layouts import QtQuick.Layouts
@@ -24,9 +23,9 @@ Item {
radius: Appearance.rounding.small radius: Appearance.rounding.small
} }
BoxLayout { GridLayout {
id: gridLayout id: gridLayout
vertical: root.vertical columns: root.vertical ? 1 : -1
anchors { anchors {
verticalCenter: root.vertical ? undefined : parent.verticalCenter verticalCenter: root.vertical ? undefined : parent.verticalCenter
horizontalCenter: root.vertical ? parent.horizontalCenter : undefined horizontalCenter: root.vertical ? parent.horizontalCenter : undefined
@@ -1,5 +1,4 @@
import qs.modules.common import qs.modules.common
import qs.modules.common.functions
import qs.modules.common.widgets import qs.modules.common.widgets
import qs.services import qs.services
import QtQuick import QtQuick
@@ -15,7 +14,7 @@ StyledPopup {
// Header // Header
StyledPopupHeaderRow { StyledPopupHeaderRow {
icon: Icons.getBatteryIcon(Battery.percentage * 100) icon: "battery_android_full"
label: Translation.tr("Battery") label: Translation.tr("Battery")
} }
@@ -28,10 +27,18 @@ StyledPopup {
icon: "schedule" icon: "schedule"
label: Battery.isCharging ? Translation.tr("Time to full:") : Translation.tr("Time to empty:") label: Battery.isCharging ? Translation.tr("Time to full:") : Translation.tr("Time to empty:")
value: { value: {
function formatTime(seconds) {
var h = Math.floor(seconds / 3600);
var m = Math.floor((seconds % 3600) / 60);
if (h > 0)
return `${h}h, ${m}m`;
else
return `${m}m`;
}
if (Battery.isCharging) if (Battery.isCharging)
return DateUtils.formatDuration(Battery.timeToFull); return formatTime(Battery.timeToFull);
else else
return DateUtils.formatDuration(Battery.timeToEmpty); return formatTime(Battery.timeToEmpty);
} }
} }
@@ -47,7 +54,13 @@ StyledPopup {
return Translation.tr("Discharging:"); return Translation.tr("Discharging:");
} }
} }
value: `${Battery.energyRate.toFixed(2)}W` value: {
if (Battery.chargeState == 4) {
return "";
} else {
return `${Battery.energyRate.toFixed(2)}W`;
}
}
} }
StyledPopupValueRow { StyledPopupValueRow {
@@ -6,6 +6,7 @@ import QtQuick.Layouts
Item { Item {
id: root id: root
property bool borderless: Config.options.bar.borderless
property bool showDate: Config.options.bar.verbose property bool showDate: Config.options.bar.verbose
implicitWidth: rowLayout.implicitWidth implicitWidth: rowLayout.implicitWidth
implicitHeight: Appearance.sizes.barHeight implicitHeight: Appearance.sizes.barHeight
@@ -55,8 +55,8 @@ RippleButton {
CustomIcon { CustomIcon {
id: distroIcon id: distroIcon
anchors.centerIn: parent anchors.centerIn: parent
width: 20 width: 19.5
height: 20 height: 19.5
source: Config.options.bar.topLeftIcon == 'distro' ? SystemInfo.distroIcon : `${Config.options.bar.topLeftIcon}-symbolic` source: Config.options.bar.topLeftIcon == 'distro' ? SystemInfo.distroIcon : `${Config.options.bar.topLeftIcon}-symbolic`
colorize: true colorize: true
color: Appearance.colors.colOnLayer0 color: Appearance.colors.colOnLayer0
@@ -7,6 +7,11 @@ import QtQuick.Layouts
StyledPopup { StyledPopup {
id: root id: root
// Helper function to format KB to GB
function formatKB(kb) {
return (kb / (1024 * 1024)).toFixed(1) + " GB";
}
Row { Row {
anchors.centerIn: parent anchors.centerIn: parent
spacing: 12 spacing: 12
@@ -24,17 +29,17 @@ StyledPopup {
StyledPopupValueRow { StyledPopupValueRow {
icon: "clock_loader_60" icon: "clock_loader_60"
label: Translation.tr("Used:") label: Translation.tr("Used:")
value: ResourceUsage.kbToGbString(ResourceUsage.memoryUsed) value: root.formatKB(ResourceUsage.memoryUsed)
} }
StyledPopupValueRow { StyledPopupValueRow {
icon: "check_circle" icon: "check_circle"
label: Translation.tr("Free:") label: Translation.tr("Free:")
value: ResourceUsage.kbToGbString(ResourceUsage.memoryFree) value: root.formatKB(ResourceUsage.memoryFree)
} }
StyledPopupValueRow { StyledPopupValueRow {
icon: "empty_dashboard" icon: "empty_dashboard"
label: Translation.tr("Total:") label: Translation.tr("Total:")
value: ResourceUsage.kbToGbString(ResourceUsage.memoryTotal) value: root.formatKB(ResourceUsage.memoryTotal)
} }
} }
} }
@@ -53,17 +58,17 @@ StyledPopup {
StyledPopupValueRow { StyledPopupValueRow {
icon: "clock_loader_60" icon: "clock_loader_60"
label: Translation.tr("Used:") label: Translation.tr("Used:")
value: ResourceUsage.kbToGbString(ResourceUsage.swapUsed) value: root.formatKB(ResourceUsage.swapUsed)
} }
StyledPopupValueRow { StyledPopupValueRow {
icon: "check_circle" icon: "check_circle"
label: Translation.tr("Free:") label: Translation.tr("Free:")
value: ResourceUsage.kbToGbString(ResourceUsage.swapFree) value: root.formatKB(ResourceUsage.swapFree)
} }
StyledPopupValueRow { StyledPopupValueRow {
icon: "empty_dashboard" icon: "empty_dashboard"
label: Translation.tr("Total:") label: Translation.tr("Total:")
value: ResourceUsage.kbToGbString(ResourceUsage.swapTotal) value: root.formatKB(ResourceUsage.swapTotal)
} }
} }
} }
@@ -65,9 +65,9 @@ Item {
} }
} }
BoxLayout { GridLayout {
id: gridLayout id: gridLayout
vertical: root.vertical columns: root.vertical ? 1 : -1
anchors.fill: parent anchors.fill: parent
rowSpacing: 8 rowSpacing: 8
columnSpacing: 15 columnSpacing: 15
@@ -9,7 +9,7 @@ import qs.modules.common
import qs.modules.common.widgets import qs.modules.common.widgets
import qs.modules.common.functions import qs.modules.common.functions
ButtonMouseArea { MouseArea {
id: root id: root
required property SystemTrayItem item required property SystemTrayItem item
property bool targetMenuOpen: false property bool targetMenuOpen: false
@@ -19,10 +19,8 @@ ButtonMouseArea {
hoverEnabled: true hoverEnabled: true
acceptedButtons: Qt.LeftButton | Qt.RightButton acceptedButtons: Qt.LeftButton | Qt.RightButton
property real iconSize: 20 implicitWidth: 20
property real backgroundSize: 26 implicitHeight: 20
implicitWidth: iconSize
implicitHeight: iconSize
onPressed: (event) => { onPressed: (event) => {
switch (event.button) { switch (event.button) {
case Qt.LeftButton: case Qt.LeftButton:
@@ -42,16 +40,6 @@ ButtonMouseArea {
tooltip.text = TrayService.getTooltipForItem(root.item); tooltip.text = TrayService.getTooltipForItem(root.item);
} }
StateOverlay {
id: hoverOverlay
anchors.centerIn: parent
width: root.backgroundSize
height: root.backgroundSize
radius: root.backgroundSize / 2
hover: root.containsMouse
press: root.containsPress
}
Loader { Loader {
id: menu id: menu
function open() { function open() {
@@ -85,8 +73,8 @@ ButtonMouseArea {
visible: !Config.options.tray.monochromeIcons visible: !Config.options.tray.monochromeIcons
source: root.item.icon source: root.item.icon
anchors.centerIn: parent anchors.centerIn: parent
width: root.iconSize width: parent.width
height: root.iconSize height: parent.height
} }
Loader { Loader {
@@ -187,10 +187,7 @@ PopupWindow {
Layout.fillWidth: true Layout.fillWidth: true
visible: root.trayItemId !== undefined && root.trayItemId.length > 0 && stackView.depth === 1 visible: root.trayItemId !== undefined && root.trayItemId.length > 0 && stackView.depth === 1
releaseAction: () => { releaseAction: () => TrayService.togglePin(root.trayItemId);
GlobalFocusGrab.dismiss();
TrayService.togglePin(root.trayItemId);
}
contentItem: RowLayout { contentItem: RowLayout {
anchors { anchors {
@@ -1,4 +1,3 @@
pragma ComponentBehavior: Bound
import qs import qs
import qs.services import qs.services
import qs.modules.common import qs.modules.common
@@ -16,22 +15,22 @@ import Qt5Compat.GraphicalEffects
Item { Item {
id: root id: root
property bool vertical: false property bool vertical: false
property bool borderless: Config.options.bar.borderless
readonly property HyprlandMonitor monitor: Hyprland.monitorFor(root.QsWindow.window?.screen) readonly property HyprlandMonitor monitor: Hyprland.monitorFor(root.QsWindow.window?.screen)
readonly property Toplevel activeWindow: ToplevelManager.activeToplevel readonly property Toplevel activeWindow: ToplevelManager.activeToplevel
readonly property bool activeActuallyFocused: activeWindow?.activated ?? false readonly property int effectiveActiveWorkspaceId: monitor?.activeWorkspace?.id ?? 1
WorkspaceModel {
id: wsModel
monitor: root.monitor
}
readonly property int workspacesShown: Config.options.bar.workspaces.shown
readonly property int workspaceGroup: Math.floor((effectiveActiveWorkspaceId - 1) / root.workspacesShown)
property list<bool> workspaceOccupied: []
property int widgetPadding: 4
property int workspaceButtonWidth: 26 property int workspaceButtonWidth: 26
property real activeWorkspaceMargin: 2 property real activeWorkspaceMargin: 2
property real workspaceIconSize: workspaceButtonWidth * 0.69 property real workspaceIconSize: workspaceButtonWidth * 0.69
property real workspaceIconSizeShrinked: workspaceButtonWidth * 0.55 property real workspaceIconSizeShrinked: workspaceButtonWidth * 0.55
property real workspaceIconOpacityShrinked: 1 property real workspaceIconOpacityShrinked: 1
property real workspaceIconMarginShrinked: -4 property real workspaceIconMarginShrinked: -4
property int workspaceIndexInGroup: (monitor?.activeWorkspace?.id - 1) % wsModel.shownCount property int workspaceIndexInGroup: (effectiveActiveWorkspaceId - 1) % root.workspacesShown
property bool showNumbers: false property bool showNumbers: false
Timer { Timer {
@@ -57,8 +56,33 @@ Item {
} }
} }
implicitWidth: root.vertical ? Appearance.sizes.verticalBarWidth : (root.workspaceButtonWidth * wsModel.shownCount) // Function to update workspaceOccupied
implicitHeight: root.vertical ? (root.workspaceButtonWidth * wsModel.shownCount) : Appearance.sizes.barHeight function updateWorkspaceOccupied() {
workspaceOccupied = Array.from({ length: root.workspacesShown }, (_, i) => {
return Hyprland.workspaces.values.some(ws => ws.id === workspaceGroup * root.workspacesShown + i + 1);
})
}
// Occupied workspace updates
Component.onCompleted: updateWorkspaceOccupied()
Connections {
target: Hyprland.workspaces
function onValuesChanged() {
updateWorkspaceOccupied();
}
}
Connections {
target: Hyprland
function onFocusedWorkspaceChanged() {
updateWorkspaceOccupied();
}
}
onWorkspaceGroupChanged: {
updateWorkspaceOccupied();
}
implicitWidth: root.vertical ? Appearance.sizes.verticalBarWidth : (root.workspaceButtonWidth * root.workspacesShown)
implicitHeight: root.vertical ? (root.workspaceButtonWidth * root.workspacesShown) : Appearance.sizes.barHeight
// Scroll to switch workspaces // Scroll to switch workspaces
WheelHandler { WheelHandler {
@@ -82,27 +106,25 @@ Item {
} }
// Workspaces - background // Workspaces - background
Box { Grid {
z: 1 z: 1
anchors.centerIn: parent anchors.centerIn: parent
rowSpacing: 0 rowSpacing: 0
columnSpacing: 0 columnSpacing: 0
vertical: root.vertical columns: root.vertical ? 1 : root.workspacesShown
rows: root.vertical ? root.workspacesShown : 1
Repeater { Repeater {
model: wsModel.shownCount model: root.workspacesShown
delegate: Rectangle {
required property int index
Rectangle {
z: 1 z: 1
implicitWidth: root.workspaceButtonWidth implicitWidth: workspaceButtonWidth
implicitHeight: root.workspaceButtonWidth implicitHeight: workspaceButtonWidth
radius: (width / 2) radius: (width / 2)
property bool thisOccupied: (wsModel.occupied[index] && !(!wsModel.currentWorkspaceNotFake && monitor?.activeWorkspace?.id === index+1)) property var previousOccupied: (workspaceOccupied[index-1] && !(!activeWindow?.activated && root.effectiveActiveWorkspaceId === index))
property var previousOccupied: (wsModel.occupied[index-1] && !(!wsModel.currentWorkspaceNotFake && monitor?.activeWorkspace?.id === index)) property var rightOccupied: (workspaceOccupied[index+1] && !(!activeWindow?.activated && root.effectiveActiveWorkspaceId === index+2))
property var rightOccupied: (wsModel.occupied[index+1] && !(!wsModel.currentWorkspaceNotFake && monitor?.activeWorkspace?.id === index+2))
property var radiusPrev: previousOccupied ? 0 : (width / 2) property var radiusPrev: previousOccupied ? 0 : (width / 2)
property var radiusNext: rightOccupied ? 0 : (width / 2) property var radiusNext: rightOccupied ? 0 : (width / 2)
@@ -112,7 +134,7 @@ Item {
bottomRightRadius: radiusNext bottomRightRadius: radiusNext
color: ColorUtils.transparentize(Appearance.m3colors.m3secondaryContainer, 0.4) color: ColorUtils.transparentize(Appearance.m3colors.m3secondaryContainer, 0.4)
opacity: thisOccupied ? 1 : 0 opacity: (workspaceOccupied[index] && !(!activeWindow?.activated && root.effectiveActiveWorkspaceId === index+1)) ? 1 : 0
Behavior on opacity { Behavior on opacity {
animation: Appearance.animation.elementMove.numberAnimation.createObject(this) animation: Appearance.animation.elementMove.numberAnimation.createObject(this)
@@ -147,9 +169,9 @@ Item {
id: idxPair id: idxPair
index: root.workspaceIndexInGroup index: root.workspaceIndexInGroup
} }
property real indicatorPosition: Math.min(idxPair.idx1, idxPair.idx2) * root.workspaceButtonWidth + root.activeWorkspaceMargin property real indicatorPosition: Math.min(idxPair.idx1, idxPair.idx2) * workspaceButtonWidth + root.activeWorkspaceMargin
property real indicatorLength: Math.abs(idxPair.idx1 - idxPair.idx2) * root.workspaceButtonWidth + root.workspaceButtonWidth - root.activeWorkspaceMargin * 2 property real indicatorLength: Math.abs(idxPair.idx1 - idxPair.idx2) * workspaceButtonWidth + workspaceButtonWidth - root.activeWorkspaceMargin * 2
property real indicatorThickness: root.workspaceButtonWidth - root.activeWorkspaceMargin * 2 property real indicatorThickness: workspaceButtonWidth - root.activeWorkspaceMargin * 2
x: root.vertical ? null : indicatorPosition x: root.vertical ? null : indicatorPosition
implicitWidth: root.vertical ? indicatorThickness : indicatorLength implicitWidth: root.vertical ? indicatorThickness : indicatorLength
@@ -159,21 +181,22 @@ Item {
} }
// Workspaces - numbers // Workspaces - numbers
Box { Grid {
id: wsNumbers
z: 3 z: 3
anchors.fill: parent
vertical: root.vertical columns: root.vertical ? 1 : root.workspacesShown
rows: root.vertical ? root.workspacesShown : 1
columnSpacing: 0 columnSpacing: 0
rowSpacing: 0 rowSpacing: 0
anchors.fill: parent
Repeater { Repeater {
model: wsModel.shownCount model: root.workspacesShown
delegate: Button {
Button {
id: button id: button
required property int index property int workspaceValue: workspaceGroup * root.workspacesShown + index + 1
property int workspaceValue: wsModel.getWorkspaceIdAt(index)
implicitHeight: vertical ? Appearance.sizes.verticalBarWidth : Appearance.sizes.barHeight implicitHeight: vertical ? Appearance.sizes.verticalBarWidth : Appearance.sizes.barHeight
implicitWidth: vertical ? Appearance.sizes.verticalBarWidth : Appearance.sizes.verticalBarWidth implicitWidth: vertical ? Appearance.sizes.verticalBarWidth : Appearance.sizes.verticalBarWidth
onPressed: Hyprland.dispatch(`hl.dsp.focus({ workspace = ${workspaceValue}})`) onPressed: Hyprland.dispatch(`hl.dsp.focus({ workspace = ${workspaceValue}})`)
@@ -182,16 +205,11 @@ Item {
background: Item { background: Item {
id: workspaceButtonBackground id: workspaceButtonBackground
implicitWidth: root.workspaceButtonWidth implicitWidth: workspaceButtonWidth
implicitHeight: root.workspaceButtonWidth implicitHeight: workspaceButtonWidth
property var biggestWindow: HyprlandData.biggestWindowForWorkspace(button.workspaceValue) property var biggestWindow: HyprlandData.biggestWindowForWorkspace(button.workspaceValue)
property var mainAppIconSource: Quickshell.iconPath(AppSearch.guessIcon(biggestWindow?.class), "image-missing") property var mainAppIconSource: Quickshell.iconPath(AppSearch.guessIcon(biggestWindow?.class), "image-missing")
property color numberColor: (monitor?.activeWorkspace?.id == button.workspaceValue) ?
Appearance.m3colors.m3onPrimary :
(wsModel.occupied[index] ? Appearance.m3colors.m3onSecondaryContainer :
Appearance.colors.colOnLayer1Inactive)
StyledText { // Workspace number text StyledText { // Workspace number text
opacity: root.showNumbers opacity: root.showNumbers
|| ((Config.options?.bar.workspaces.alwaysShowNumbers && (!Config.options?.bar.workspaces.showAppIcons || !workspaceButtonBackground.biggestWindow || root.showNumbers)) || ((Config.options?.bar.workspaces.alwaysShowNumbers && (!Config.options?.bar.workspaces.showAppIcons || !workspaceButtonBackground.biggestWindow || root.showNumbers))
@@ -208,7 +226,10 @@ Item {
} }
text: Config.options?.bar.workspaces.numberMap[button.workspaceValue - 1] || button.workspaceValue text: Config.options?.bar.workspaces.numberMap[button.workspaceValue - 1] || button.workspaceValue
elide: Text.ElideRight elide: Text.ElideRight
color: workspaceButtonBackground.numberColor color: (root.effectiveActiveWorkspaceId == button.workspaceValue) ?
Appearance.m3colors.m3onPrimary :
(workspaceOccupied[index] ? Appearance.m3colors.m3onSecondaryContainer :
Appearance.colors.colOnLayer1Inactive)
Behavior on opacity { Behavior on opacity {
animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this) animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
@@ -222,10 +243,13 @@ Item {
) ? 0 : 1 ) ? 0 : 1
visible: opacity > 0 visible: opacity > 0
anchors.centerIn: parent anchors.centerIn: parent
width: root.workspaceButtonWidth * 0.18 width: workspaceButtonWidth * 0.18
height: width height: width
radius: width / 2 radius: width / 2
color: workspaceButtonBackground.numberColor color: (root.effectiveActiveWorkspaceId == button.workspaceValue) ?
Appearance.m3colors.m3onPrimary :
(workspaceOccupied[index] ? Appearance.m3colors.m3onSecondaryContainer :
Appearance.colors.colOnLayer1Inactive)
Behavior on opacity { Behavior on opacity {
animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this) animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
@@ -233,8 +257,8 @@ Item {
} }
Item { // Main app icon Item { // Main app icon
anchors.centerIn: parent anchors.centerIn: parent
width: root.workspaceButtonWidth width: workspaceButtonWidth
height: root.workspaceButtonWidth height: workspaceButtonWidth
opacity: !Config.options?.bar.workspaces.showAppIcons ? 0 : opacity: !Config.options?.bar.workspaces.showAppIcons ? 0 :
(workspaceButtonBackground.biggestWindow && !root.showNumbers && Config.options?.bar.workspaces.showAppIcons) ? (workspaceButtonBackground.biggestWindow && !root.showNumbers && Config.options?.bar.workspaces.showAppIcons) ?
1 : workspaceButtonBackground.biggestWindow ? workspaceIconOpacityShrinked : 0 1 : workspaceButtonBackground.biggestWindow ? workspaceIconOpacityShrinked : 0
@@ -244,9 +268,9 @@ Item {
anchors.bottom: parent.bottom anchors.bottom: parent.bottom
anchors.right: parent.right anchors.right: parent.right
anchors.bottomMargin: (!root.showNumbers && Config.options?.bar.workspaces.showAppIcons) ? anchors.bottomMargin: (!root.showNumbers && Config.options?.bar.workspaces.showAppIcons) ?
(root.workspaceButtonWidth - workspaceIconSize) / 2 : workspaceIconMarginShrinked (workspaceButtonWidth - workspaceIconSize) / 2 : workspaceIconMarginShrinked
anchors.rightMargin: (!root.showNumbers && Config.options?.bar.workspaces.showAppIcons) ? anchors.rightMargin: (!root.showNumbers && Config.options?.bar.workspaces.showAppIcons) ?
(root.workspaceButtonWidth - workspaceIconSize) / 2 : workspaceIconMarginShrinked (workspaceButtonWidth - workspaceIconSize) / 2 : workspaceIconMarginShrinked
source: workspaceButtonBackground.mainAppIconSource source: workspaceButtonBackground.mainAppIconSource
implicitSize: (!root.showNumbers && Config.options?.bar.workspaces.showAppIcons) ? workspaceIconSize : workspaceIconSizeShrinked implicitSize: (!root.showNumbers && Config.options?.bar.workspaces.showAppIcons) ? workspaceIconSize : workspaceIconSizeShrinked
@@ -285,8 +309,12 @@ Item {
} }
} }
} }
} }
} }
} }
} }
@@ -303,7 +303,7 @@ MouseArea {
IconAndTextPair { IconAndTextPair {
visible: Battery.available visible: Battery.available
icon: Battery.isCharging ? "bolt" : Icons.getBatteryIcon(Battery.percentage * 100) icon: Battery.isCharging ? "bolt" : "battery_android_full"
text: Math.round(Battery.percentage * 100) text: Math.round(Battery.percentage * 100)
color: (Battery.isLow && !Battery.isCharging) ? Appearance.colors.colError : Appearance.colors.colOnSurfaceVariant color: (Battery.isLow && !Battery.isCharging) ? Appearance.colors.colError : Appearance.colors.colOnSurfaceVariant
} }
@@ -92,7 +92,7 @@ Rectangle {
id: boltIcon id: boltIcon
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
fill: 1 fill: 1
text: Battery.isCharging ? "bolt" : Icons.getBatteryIcon(Battery.percentage * 100) text: Battery.isCharging ? "bolt" : "battery_android_full"
color: batteryWidget.colText color: batteryWidget.colText
iconSize: 24 iconSize: 24
animateChange: true animateChange: true
@@ -95,25 +95,6 @@ Item { // Wrapper
root.focusFirstItem(); root.focusFirstItem();
} }
} }
// Ctrl+N / Ctrl+P navigation
if (event.modifiers & Qt.ControlModifier) {
if (event.key === Qt.Key_N) {
if (appResults.visible && appResults.count > 0) {
// Wrap around the list rather than the default arrow key behaviour
appResults.currentIndex = (appResults.currentIndex + 1) % appResults.count;
event.accepted = true;
return;
}
} else if (event.key === Qt.Key_P) {
if (appResults.visible && appResults.count > 0) {
// Wrap around too
appResults.currentIndex = (appResults.count + appResults.currentIndex - 1) % appResults.count;
event.accepted = true;
return;
}
}
}
} }
StyledRectangularShadow { StyledRectangularShadow {
@@ -53,7 +53,6 @@ Scope {
margins: Appearance.sizes.hyprlandGapsOut margins: Appearance.sizes.hyprlandGapsOut
leftMargin: Appearance.sizes.elevationMargin leftMargin: Appearance.sizes.elevationMargin
} }
asynchronous: true
width: sidebarWidth - Appearance.sizes.hyprlandGapsOut - Appearance.sizes.elevationMargin width: sidebarWidth - Appearance.sizes.hyprlandGapsOut - Appearance.sizes.elevationMargin
height: parent.height - Appearance.sizes.hyprlandGapsOut * 2 height: parent.height - Appearance.sizes.hyprlandGapsOut * 2
@@ -40,7 +40,6 @@ Item {
return `${minutes}:${seconds}`; return `${minutes}:${seconds}`;
} }
font.pixelSize: 40 font.pixelSize: 40
font.features: { "tnum": 1 }
color: Appearance.m3colors.m3onSurface color: Appearance.m3colors.m3onSurface
} }
StyledText { StyledText {
@@ -53,7 +53,6 @@ Item {
StyledText { StyledText {
// Layout.preferredWidth: elapsedIndicator.width * 0.6 // Prevent shakiness // Layout.preferredWidth: elapsedIndicator.width * 0.6 // Prevent shakiness
font.pixelSize: 40 font.pixelSize: 40
font.features: { "tnum": 1 }
color: Appearance.m3colors.m3onSurface color: Appearance.m3colors.m3onSurface
text: { text: {
let totalSeconds = Math.floor(TimerService.stopwatchTime) / 100 let totalSeconds = Math.floor(TimerService.stopwatchTime) / 100
@@ -65,10 +64,9 @@ Item {
StyledText { StyledText {
Layout.fillWidth: true Layout.fillWidth: true
font.pixelSize: 40 font.pixelSize: 40
font.features: { "tnum": 1 }
color: Appearance.colors.colSubtext color: Appearance.colors.colSubtext
text: { text: {
return `:${(Math.floor(TimerService.stopwatchTime) % 100).toString().padStart(2, '0')}` return `:<sub>${(Math.floor(TimerService.stopwatchTime) % 100).toString().padStart(2, '0')}</sub>`
} }
} }
} }
@@ -11,7 +11,7 @@ import Quickshell.Hyprland
QuickToggleButton { QuickToggleButton {
toggled: Network.wifiStatus !== "disabled" toggled: Network.wifiStatus !== "disabled"
buttonIcon: Icons.getNetworkMaterialSymbol() buttonIcon: Network.materialSymbol
onClicked: Network.toggleWifi() onClicked: Network.toggleWifi()
altAction: () => { altAction: () => {
Quickshell.execDetached(["bash", "-c", `${Network.ethernet ? Config.options.apps.networkEthernet : Config.options.apps.network}`]) Quickshell.execDetached(["bash", "-c", `${Network.ethernet ? Config.options.apps.networkEthernet : Config.options.apps.network}`])
@@ -273,7 +273,7 @@ Item { // Bar content region
} }
} }
MaterialSymbol { MaterialSymbol {
text: Icons.getNetworkMaterialSymbol() text: Network.materialSymbol
iconSize: Appearance.font.pixelSize.larger iconSize: Appearance.font.pixelSize.larger
color: rightSidebarButton.colText color: rightSidebarButton.colText
} }
@@ -1,14 +1,19 @@
import QtQuick import QtQuick
import org.kde.kirigami as Kirigami
import qs.services import qs.services
import qs.modules.common import qs.modules.common
import qs.modules.common.widgets as W
W.AppIcon { Kirigami.Icon {
id: root id: root
required property string iconName required property string iconName
property bool separateLightDark: false property bool separateLightDark: false
property bool tryCustomIcon: true property bool tryCustomIcon: true
property real implicitSize: 26
implicitWidth: implicitSize
implicitHeight: implicitSize
animated: true
roundToIconSize: false roundToIconSize: false
fallback: root.iconName fallback: root.iconName
source: tryCustomIcon ? `${Looks.iconsPath}/${root.iconName}${!root.separateLightDark ? "" : Looks.dark ? "-dark" : "-light"}.svg` : fallback source: tryCustomIcon ? `${Looks.iconsPath}/${root.iconName}${!root.separateLightDark ? "" : Looks.dark ? "-dark" : "-light"}.svg` : fallback
@@ -24,9 +24,9 @@ Singleton {
property string internetIcon: { property string internetIcon: {
if (Network.ethernet) if (Network.ethernet)
return "ethernet"; return "ethernet";
if (Network.wifiEnabled && Network.wifiStatus === "connected") { if (Network.wifiEnabled) {
const strength = Network.networkStrength; const strength = Network.networkStrength;
return root.wifiIconForStrength(strength); return wifiIconForStrength(strength);
} }
if (Network.wifiStatus === "connecting") if (Network.wifiStatus === "connecting")
return "wifi-4"; return "wifi-4";
@@ -11,9 +11,10 @@ ScrollBar {
active: hovered || pressed active: hovered || pressed
property color color: Looks.colors.controlBg property color color: Looks.colors.controlBg
contentItem: Pill { contentItem: Rectangle {
implicitWidth: root.active ? 4 : 2 implicitWidth: root.active ? 4 : 2
implicitHeight: root.visualSize implicitHeight: root.visualSize
radius: 9999
color: root.color color: root.color
opacity: root.policy === ScrollBar.AlwaysOn || (root.active && root.size < 1.0) ? 0.5 : 0 opacity: root.policy === ScrollBar.AlwaysOn || (root.active && root.size < 1.0) ? 0.5 : 0
@@ -1,23 +1,31 @@
import QtQuick import QtQuick
import qs.modules.common.widgets as W
W.FixedWidthTextContainer { Item {
id: root id: root
property string longestText
property alias text: textItem.text property alias text: textItem.text
property alias font: textItem.font
property alias horizontalAlignment: textItem.horizontalAlignment property alias horizontalAlignment: textItem.horizontalAlignment
property alias verticalAlignment: textItem.verticalAlignment property alias verticalAlignment: textItem.verticalAlignment
property alias color: textItem.color property alias color: textItem.color
font { implicitWidth: longestTextMetrics.width
family: Looks.font.family.ui implicitHeight: longestTextMetrics.height
pixelSize: Looks.font.pixelSize.large
weight: Looks.font.weight.regular TextMetrics {
id: longestTextMetrics
text: root.longestText
font {
family: Looks.font.family.ui
pixelSize: Looks.font.pixelSize.large
weight: Looks.font.weight.regular
}
} }
WText { WText {
id: textItem id: textItem
anchors.fill: parent anchors.fill: parent
font: root.font font.pixelSize: Looks.font.pixelSize.large
} }
} }

Some files were not shown because too many files have changed in this diff Show More