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 {
timeout = 600 # 10mins
on-timeout = hyprctl dispatch 'hl.dsp.dpms(false)'
on-resume = hyprctl dispatch 'hl.dsp.dpms(true)'
on-timeout = hyprctl dispatch 'hl.dsp.dpms({ action = "disable" })'
on-resume = hyprctl dispatch 'hl.dsp.dpms({ action = "enable" })'
}
listener {
+6 -6
View File
@@ -202,12 +202,12 @@ for i = 1, 10 do
end, { description = "Window: Send to workspace " .. i })
end
--# 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
local numberkey = { 10, 11, 12, 13, 14, 15, 16, 17, 18, 19 }
hl.bind("SUPER + ALT + code:" .. numberkey[i], function()
hl.dispatch(hl.dsp.window.move({ workspace = workspace_in_group(i), follow = false }))
end)
end
-- for i = 1, 10 do
-- local numberkey = { 10, 11, 12, 13, 14, 15, 16, 17, 18, 19 }
-- hl.bind("SUPER + ALT + code:" .. numberkey[i], function()
-- hl.dispatch(hl.dsp.window.move({ workspace = workspace_in_group(i), follow = false }))
-- end)
-- end
--# keypad numbers
for i = 1, 10 do
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.Effects
import QtQuick.Layouts
import Qt5Compat.GraphicalEffects
import Quickshell
import Quickshell.Wayland
Scope {
id: root
property bool failed
property string errorString
property real progressHeight: 3
id: root
property bool failed;
property string errorString;
// Connect to the Quickshell global to listen for the reload signals.
Connections {
target: Quickshell
// Connect to the Quickshell global to listen for the reload signals.
Connections {
target: Quickshell
function onReloadCompleted() {
root.failed = false;
popupLoader.loading = true;
}
function onReloadCompleted() {
root.failed = false;
popupLoader.loading = true;
}
function onReloadFailed(error: string) {
// Close any existing popup before making a new one.
popupLoader.active = false;
function onReloadFailed(error: string) {
// Close any existing popup before making a new one.
popupLoader.active = false;
root.failed = true;
root.errorString = error;
popupLoader.loading = true;
}
}
root.failed = true;
root.errorString = error;
popupLoader.loading = true;
}
}
// Keep the popup in a loader because it isn't needed most of the time
LazyLoader {
id: popupLoader
// Keep the popup in a loader because it isn't needed most of the time
LazyLoader {
id: popupLoader
PanelWindow {
id: popup
PanelWindow {
id: popup
exclusiveZone: 0
anchors.top: true
margins.top: 0
exclusiveZone: 0
anchors.top: true
margins.top: 0
implicitWidth: rect.width + 8 * 2
implicitHeight: rect.height + 8 * 2
implicitWidth: rect.width + shadow.radius * 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: "transparent"
// color blending is a bit odd as detailed in the type reference.
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
radius: rect.radius
blur: 6.3
offset: Qt.vector2d(0.0, 1.0)
spread: 1
color: "#55000000"
horizontalOffset: 0
verticalOffset: 2
radius: 6
samples: radius * 2 + 1 // Ideally should be 2 * radius + 1, see qt docs
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 var main: ({
"wght": 500,
"wght": 450,
"wdth": 100,
})
property var numbers: ({
@@ -3,9 +3,7 @@ pragma ComponentBehavior: Bound
import QtQuick
import Quickshell
import Quickshell.Io
import "functions"
import "config"
import qs.modules.common.functions
Singleton {
id: root
@@ -15,8 +13,6 @@ Singleton {
property int readWriteDelay: 50 // milliseconds
property bool blockWrites: false
signal reloaded()
function setNestedValue(nestedKey, value) {
let keys = nestedKey.split(".");
let obj = root.options;
@@ -52,7 +48,7 @@ Singleton {
interval: root.readWriteDelay
repeat: false
onTriggered: {
configFileView.reload();
configFileView.reload()
}
}
@@ -61,7 +57,7 @@ Singleton {
interval: root.readWriteDelay
repeat: false
onTriggered: {
configFileView.writeAdapter();
configFileView.writeAdapter()
}
}
@@ -72,10 +68,7 @@ Singleton {
blockWrites: root.blockWrites
onFileChanged: fileReloadTimer.restart()
onAdapterUpdated: fileWriteTimer.restart()
onLoaded: {
if (!root.ready) root.reloaded()
root.ready = true
}
onLoaded: root.ready = true
onLoadFailed: error => {
if (error == FileViewError.FileNotFound) {
writeAdapter();
@@ -159,8 +152,8 @@ Singleton {
property JsonObject apps: JsonObject {
property string bluetooth: "kcmshell6 kcm_bluetooth"
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 manageUser: "kcmshell6 kcm_users"
property string networkEthernet: "kcmshell6 kcm_networkmanagement"
property string taskManager: "plasma-systemmonitor --page-name Processes"
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 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 list<string> screenList: [] // List of names, like "eDP-1", find out with 'hyprctl monitors' command
property bool showBackground: true
property bool verbose: true
property bool vertical: false
property JsonObject indicators: JsonObject {
property JsonObject notifications: JsonObject {
property bool showUnreadCount: false
}
}
property JsonObject resources: JsonObject {
property bool alwaysShowSwap: true
property bool alwaysShowCpu: true
@@ -261,9 +248,7 @@ Singleton {
property int swapWarningThreshold: 85
property int cpuWarningThreshold: 90
}
property JsonObject tooltips: JsonObject {
property bool clickToShow: false
}
property list<string> screenList: [] // List of names, like "eDP-1", find out with 'hyprctl monitors' command
property JsonObject utilButtons: JsonObject {
property bool showScreenSnip: true
property bool showColorPicker: false
@@ -273,13 +258,6 @@ Singleton {
property bool showPerformanceProfileToggle: 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 bool monochromeIcons: true
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 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 {
@@ -300,10 +293,7 @@ Singleton {
}
property JsonObject calendar: JsonObject {
property string locale: "C"
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 string locale: "en-GB"
}
property JsonObject cheatsheet: JsonObject {
@@ -351,8 +341,7 @@ Singleton {
property int mouseScrollFactor: 120
property int touchpadScrollFactor: 450
}
property JsonObject deadPixelWorkaround: JsonObject {
// Hyprland leaves out 1 pixel on the right for interactions
property JsonObject deadPixelWorkaround: JsonObject { // Hyprland leaves out 1 pixel on the right for interactions
property bool enable: false
}
}
@@ -367,7 +356,7 @@ Singleton {
}
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 {
@@ -410,7 +399,7 @@ Singleton {
property JsonObject notifications: JsonObject {
property int timeout: 7000
property JsonObject forceMonitor: JsonObject {
property JsonObject monitor: JsonObject {
property bool enable: false
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 showItemId: false
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
}
@@ -540,30 +529,12 @@ Singleton {
property JsonObject android: JsonObject {
property int columns: 5
property list<var> toggles: [
{
"size": 2,
"type": "network"
},
{
"size": 2,
"type": "bluetooth"
},
{
"size": 1,
"type": "idleInhibitor"
},
{
"size": 1,
"type": "mic"
},
{
"size": 2,
"type": "audio"
},
{
"size": 2,
"type": "nightLight"
}
{ "size": 2, "type": "network" },
{ "size": 2, "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 string savePath: Directories.videos.replace("file://", "") // strip "file://"
property string savePath: Directories.videos.replace("file://","") // strip "file://"
}
property JsonObject screenSnip: JsonObject {
@@ -611,11 +582,11 @@ Singleton {
property int adviseUpdateThreshold: 75 // packages
property int stronglyAdviseUpdateThreshold: 200 // packages
}
property JsonObject wallpaperSelector: JsonObject {
property bool useSystemFileDialog: false
}
property JsonObject windows: JsonObject {
property bool showTitlebar: true // Client-side decoration for shell apps
property bool centerTitle: true
@@ -637,8 +608,26 @@ Singleton {
}
}
property JsonObject hefty: HeftyConfig {}
property JsonObject waffles: WaffleConfig {}
property JsonObject waffles: 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" ]
}
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 videos: StandardPaths.standardLocations(StandardPaths.MoviesLocation)[0]
/////// Stuff below are without "file://" /////////
// General
// Other dirs used by the shell, without "file://"
property string assetsPath: Quickshell.shellPath("assets")
property string scriptPath: Quickshell.shellPath("scripts")
property string favicons: FileUtils.trimFileProtocol(`${Directories.cache}/media/favicons`)
@@ -31,6 +30,9 @@ Singleton {
property string booruDownloads: FileUtils.trimFileProtocol(Directories.pictures + "/homework")
property string booruDownloadsNsfw: FileUtils.trimFileProtocol(Directories.pictures + "/homework/🌶️")
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 notesPath: FileUtils.trimFileProtocol(`${Directories.state}/user/notes.txt`)
property string conflictCachePath: FileUtils.trimFileProtocol(`${Directories.cache}/conflict-killer`)
@@ -41,39 +43,24 @@ Singleton {
property string screenshotTemp: "/tmp/quickshell/media/screenshot"
property string wallpaperSwitchScriptPath: FileUtils.trimFileProtocol(`${Directories.scriptPath}/colors/switchwall.sh`)
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 aiTranslationScriptPath: FileUtils.trimFileProtocol(`${Directories.scriptPath}/ai/gemini-translate.sh`)
property string recordScriptPath: FileUtils.trimFileProtocol(`${Directories.scriptPath}/videos/record.sh`)
property string userAvatarPathAccountsService: FileUtils.trimFileProtocol(`/var/lib/AccountsService/icons/${SystemInfo.username}`)
property string userAvatarPathRicersAndWeirdSystems: FileUtils.trimFileProtocol(`${Directories.home}.face`)
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
Component.onCompleted: {
Quickshell.execDetached(["mkdir", "-p", `${aiChats}`])
Quickshell.execDetached(["mkdir", "-p", `${favicons}`])
Quickshell.execDetached(["mkdir", "-p", `${shellConfig}`])
Quickshell.execDetached(["rm", "-rf", `${tempImages}`])
Quickshell.execDetached(["mkdir", "-p", `${userComponents}`])
Quickshell.execDetached(["mkdir", "-p", `${userAiPrompts}`])
Quickshell.execDetached(["mkdir", "-p", `${favicons}`])
Quickshell.execDetached(["bash", "-c", `rm -rf '${coverArt}'; mkdir -p '${coverArt}'`])
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(["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}'`])
}
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}'`])
Quickshell.execDetached(["rm", "-rf", `${tempImages}`])
}
}
@@ -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) {
var c = Qt.color(color);
var a = c.a * (1 - clamp01(percentage));
return Qt.rgba(c.r, c.g, c.b, a);
return Qt.rgba(c.r, c.g, c.b, c.a * (1 - percentage));
}
/**
@@ -122,7 +121,7 @@ Singleton {
*/
function applyAlpha(color, alpha) {
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);
}
@@ -24,19 +24,4 @@ Singleton {
targetDate.setDate(firstDayDate.getDate() + i);
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
QtObject {
id: root
property int index
required property int index
property real idx1: 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")
statusText: Network.networkName
tooltipText: Translation.tr("%1 | Right-click to configure").arg(Network.networkName)
icon: Icons.getNetworkMaterialSymbol()
icon: Network.materialSymbol
toggled: Network.wifiStatus !== "disabled"
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
import QtQml
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Quickshell
import qs
import qs.services
import qs.modules.common
import qs.modules.common.widgets
import qs.modules.common.functions
import qs.modules.waffle.looks
Item {
id: root
@@ -31,23 +36,15 @@ Item {
const diffWeeks = Math.round(diffMillis / root.millisPerWeek);
root.targetWeekDiff += diffWeeks;
}
function scrollToToday() {
root.targetWeekDiff = 0;
}
property int weeksPerScroll: 1
property real targetWeekDiff: 0
property real weekDiff: targetWeekDiff
property int contentWeekDiff: weekDiff // whole part of weekDiff
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 {
id: weekScrollBehavior
animation: root.scrollAnimation
animation: Looks.transition.scroll.createObject(this)
}
Timer {
id: scrollAnimationCheckTimer
@@ -59,19 +56,11 @@ Item {
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 {
id: mouseArea
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
@@ -93,16 +82,14 @@ Item {
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 string title: locale.toString(focusedDate, "MMMM yyyy")
// Sizes
property real verticalPadding: 0
property real horizontalPadding: 0
property real buttonSize: 40
property real buttonSpacing: 2
property real buttonVerticalSpacing: buttonSpacing
implicitHeight: (6 * buttonSize) + (5 * buttonVerticalSpacing) + (2 * verticalPadding)
implicitWidth: weeksColumn.implicitWidth + (2 * horizontalPadding)
implicitWidth: weeksColumn.implicitWidth
clip: true
ColumnLayout {
@@ -110,8 +97,6 @@ Item {
anchors {
left: parent.left
right: parent.right
leftMargin: root.horizontalPadding
rightMargin: root.horizontalPadding
}
y: {
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
Rectangle {
property real diameter
property double diameter
implicitWidth: diameter
implicitHeight: diameter
@@ -42,7 +42,8 @@ Item {
active: root.fill
anchors.fill: parent
sourceComponent: Circle {
sourceComponent: Rectangle {
radius: 9999
color: root.colSecondary
}
}
@@ -3,7 +3,7 @@ import qs.modules.common.functions
import qs.modules.common.widgets
import QtQuick
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.
@@ -16,17 +16,15 @@ ProgressBar {
property color highlightColor: Appearance?.colors.colOnSecondaryContainer ?? "#685496"
property color trackColor: ColorUtils.transparentize(highlightColor, 0.5) ?? "#F1D3F9"
property alias radius: contentItem.radius
property alias progressRadius: progressFill.radius
property string text
default property Item textMask: Item {
width: root.valueBarWidth
height: root.valueBarHeight
VisuallyCenteredStyledText {
anchors.fill: parent
width: valueBarWidth
height: valueBarHeight
StyledText {
anchors.centerIn: parent
font: root.font
text: root.text
}
layer.enabled: true
}
text: Math.round(value * 100)
@@ -40,9 +38,10 @@ ProgressBar {
implicitWidth: valueBarWidth
}
contentItem: Pill {
contentItem: Rectangle {
id: contentItem
anchors.fill: parent
radius: 9999
color: root.trackColor
visible: false
@@ -81,40 +80,22 @@ ProgressBar {
}
}
Rectangle {
id: contentMaskRect
anchors.fill: contentItem
width: contentItem.width
height: contentItem.height
radius: contentItem.radius
layer.enabled: true
OpacityMask {
id: roundingMask
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
source: contentItem
maskSource: contentMaskRect
visible: false
maskSource: Rectangle {
width: contentItem.width
height: contentItem.height
radius: contentItem.radius
}
}
MaskMultiEffect {
id: textClip
OpacityMask {
anchors.fill: parent
implicitWidth: contentItem.implicitWidth
implicitHeight: contentItem.implicitHeight
source: boxClip
source: roundingMask
invert: true
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
function focusSelectedChild() {
children.find(c => c.value == currentValue).forceActiveFocus()
}
signal selected(var newValue)
Repeater {
@@ -35,7 +31,6 @@ Flow {
id: paletteButton
required property var modelData
required property int index
readonly property var value: modelData.value
onYChanged: {
if (index === 0) {
paletteButton.leftmost = true
@@ -33,7 +33,6 @@ RippleButton {
}
StyledSwitch {
id: switchWidget
focusPolicy: Qt.NoFocus
down: root.down
Layout.fillWidth: false
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 colBackgroundToggledHover: Appearance?.colors.colPrimaryHover ?? "#77699C"
property color colBackgroundToggledActive: Appearance?.colors.colPrimaryActive ?? "#D6CEE2"
property color colFocusRing: Appearance.colors.colOnSecondaryContainer
property real radius: 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 {
id: buttonBackground
topLeftRadius: root.leftRadius
@@ -130,25 +130,9 @@ Button {
Behavior on color {
animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this)
}
}
z: visualFocus ? 1 : 0
Rectangle {
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
}
border.width: root.tabbedTo ? 2 : 0
border.color: Appearance.colors.colSecondary
}
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
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
acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton
onPressed: (mouse) => {
onPressed: {
if (mouse.button === Qt.RightButton)
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 colRipple: Appearance?.colors.colLayer1Active ?? "#D6CEE2"
property color colRippleToggled: Appearance?.colors.colPrimaryActive ?? "#D6CEE2"
property color colFocusRing: Appearance.colors.colOnSecondaryContainer
opacity: root.enabled ? 1 : 0.4
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 {
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 {
target: root
duration: 130
duration: 300 / 2
easing.type: Easing.BezierSpline
easing.bezierCurve: Appearance.animation.elementMoveFast.bezierCurve
}
@@ -55,8 +55,7 @@ Text {
Anim {
property: "opacity"
to: 0
easing.type: Easing.BezierSpline
easing.bezierCurve: Appearance.animation.elementMoveFast.bezierCurve
easing.type: Easing.InSine
}
}
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 {
property: "opacity"
to: 1
easing.type: Easing.BezierSpline
easing.bezierCurve: Appearance.animation.elementMoveFast.bezierCurve
easing.type: Easing.OutSine
}
}
}
@@ -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 Toplevel activeWindow: ToplevelManager.activeToplevel
readonly property bool focusingThisMonitor: HyprlandData.activeWorkspace?.monitor == monitor?.name
readonly property var biggestWindow: HyprlandData.biggestWindowForWorkspace(HyprlandData.monitors[root.monitor?.id]?.activeWorkspace.id)
property string activeWindowAddress: `0x${activeWindow?.HyprlandToplevel?.address}`
property bool focusingThisMonitor: HyprlandData.activeWorkspace?.monitor == monitor?.name
property var biggestWindow: HyprlandData.biggestWindowForWorkspace(HyprlandData.monitors[root.monitor?.id]?.activeWorkspace.id)
implicitWidth: colLayout.implicitWidth
@@ -303,7 +303,7 @@ Item { // Bar content region
}
}
MaterialSymbol {
text: Icons.getNetworkMaterialSymbol()
text: Network.materialSymbol
iconSize: Appearance.font.pixelSize.larger
color: rightSidebarButton.colText
}
@@ -1,5 +1,4 @@
import qs.modules.common
import qs.modules.common.widgets
import QtQuick
import QtQuick.Layouts
@@ -24,9 +23,9 @@ Item {
radius: Appearance.rounding.small
}
BoxLayout {
GridLayout {
id: gridLayout
vertical: root.vertical
columns: root.vertical ? 1 : -1
anchors {
verticalCenter: root.vertical ? undefined : parent.verticalCenter
horizontalCenter: root.vertical ? parent.horizontalCenter : undefined
@@ -1,5 +1,4 @@
import qs.modules.common
import qs.modules.common.functions
import qs.modules.common.widgets
import qs.services
import QtQuick
@@ -15,7 +14,7 @@ StyledPopup {
// Header
StyledPopupHeaderRow {
icon: Icons.getBatteryIcon(Battery.percentage * 100)
icon: "battery_android_full"
label: Translation.tr("Battery")
}
@@ -28,10 +27,18 @@ StyledPopup {
icon: "schedule"
label: Battery.isCharging ? Translation.tr("Time to full:") : Translation.tr("Time to empty:")
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)
return DateUtils.formatDuration(Battery.timeToFull);
return formatTime(Battery.timeToFull);
else
return DateUtils.formatDuration(Battery.timeToEmpty);
return formatTime(Battery.timeToEmpty);
}
}
@@ -47,7 +54,13 @@ StyledPopup {
return Translation.tr("Discharging:");
}
}
value: `${Battery.energyRate.toFixed(2)}W`
value: {
if (Battery.chargeState == 4) {
return "";
} else {
return `${Battery.energyRate.toFixed(2)}W`;
}
}
}
StyledPopupValueRow {
@@ -6,6 +6,7 @@ import QtQuick.Layouts
Item {
id: root
property bool borderless: Config.options.bar.borderless
property bool showDate: Config.options.bar.verbose
implicitWidth: rowLayout.implicitWidth
implicitHeight: Appearance.sizes.barHeight
@@ -55,8 +55,8 @@ RippleButton {
CustomIcon {
id: distroIcon
anchors.centerIn: parent
width: 20
height: 20
width: 19.5
height: 19.5
source: Config.options.bar.topLeftIcon == 'distro' ? SystemInfo.distroIcon : `${Config.options.bar.topLeftIcon}-symbolic`
colorize: true
color: Appearance.colors.colOnLayer0
@@ -7,6 +7,11 @@ import QtQuick.Layouts
StyledPopup {
id: root
// Helper function to format KB to GB
function formatKB(kb) {
return (kb / (1024 * 1024)).toFixed(1) + " GB";
}
Row {
anchors.centerIn: parent
spacing: 12
@@ -24,17 +29,17 @@ StyledPopup {
StyledPopupValueRow {
icon: "clock_loader_60"
label: Translation.tr("Used:")
value: ResourceUsage.kbToGbString(ResourceUsage.memoryUsed)
value: root.formatKB(ResourceUsage.memoryUsed)
}
StyledPopupValueRow {
icon: "check_circle"
label: Translation.tr("Free:")
value: ResourceUsage.kbToGbString(ResourceUsage.memoryFree)
value: root.formatKB(ResourceUsage.memoryFree)
}
StyledPopupValueRow {
icon: "empty_dashboard"
label: Translation.tr("Total:")
value: ResourceUsage.kbToGbString(ResourceUsage.memoryTotal)
value: root.formatKB(ResourceUsage.memoryTotal)
}
}
}
@@ -53,17 +58,17 @@ StyledPopup {
StyledPopupValueRow {
icon: "clock_loader_60"
label: Translation.tr("Used:")
value: ResourceUsage.kbToGbString(ResourceUsage.swapUsed)
value: root.formatKB(ResourceUsage.swapUsed)
}
StyledPopupValueRow {
icon: "check_circle"
label: Translation.tr("Free:")
value: ResourceUsage.kbToGbString(ResourceUsage.swapFree)
value: root.formatKB(ResourceUsage.swapFree)
}
StyledPopupValueRow {
icon: "empty_dashboard"
label: Translation.tr("Total:")
value: ResourceUsage.kbToGbString(ResourceUsage.swapTotal)
value: root.formatKB(ResourceUsage.swapTotal)
}
}
}
@@ -65,9 +65,9 @@ Item {
}
}
BoxLayout {
GridLayout {
id: gridLayout
vertical: root.vertical
columns: root.vertical ? 1 : -1
anchors.fill: parent
rowSpacing: 8
columnSpacing: 15
@@ -9,7 +9,7 @@ import qs.modules.common
import qs.modules.common.widgets
import qs.modules.common.functions
ButtonMouseArea {
MouseArea {
id: root
required property SystemTrayItem item
property bool targetMenuOpen: false
@@ -19,10 +19,8 @@ ButtonMouseArea {
hoverEnabled: true
acceptedButtons: Qt.LeftButton | Qt.RightButton
property real iconSize: 20
property real backgroundSize: 26
implicitWidth: iconSize
implicitHeight: iconSize
implicitWidth: 20
implicitHeight: 20
onPressed: (event) => {
switch (event.button) {
case Qt.LeftButton:
@@ -42,16 +40,6 @@ ButtonMouseArea {
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 {
id: menu
function open() {
@@ -85,8 +73,8 @@ ButtonMouseArea {
visible: !Config.options.tray.monochromeIcons
source: root.item.icon
anchors.centerIn: parent
width: root.iconSize
height: root.iconSize
width: parent.width
height: parent.height
}
Loader {
@@ -187,10 +187,7 @@ PopupWindow {
Layout.fillWidth: true
visible: root.trayItemId !== undefined && root.trayItemId.length > 0 && stackView.depth === 1
releaseAction: () => {
GlobalFocusGrab.dismiss();
TrayService.togglePin(root.trayItemId);
}
releaseAction: () => TrayService.togglePin(root.trayItemId);
contentItem: RowLayout {
anchors {
@@ -1,4 +1,3 @@
pragma ComponentBehavior: Bound
import qs
import qs.services
import qs.modules.common
@@ -16,22 +15,22 @@ import Qt5Compat.GraphicalEffects
Item {
id: root
property bool vertical: false
property bool borderless: Config.options.bar.borderless
readonly property HyprlandMonitor monitor: Hyprland.monitorFor(root.QsWindow.window?.screen)
readonly property Toplevel activeWindow: ToplevelManager.activeToplevel
readonly property bool activeActuallyFocused: activeWindow?.activated ?? false
WorkspaceModel {
id: wsModel
monitor: root.monitor
}
readonly property int effectiveActiveWorkspaceId: monitor?.activeWorkspace?.id ?? 1
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 real 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 int workspaceIndexInGroup: (effectiveActiveWorkspaceId - 1) % root.workspacesShown
property bool showNumbers: false
Timer {
@@ -57,8 +56,33 @@ Item {
}
}
implicitWidth: root.vertical ? Appearance.sizes.verticalBarWidth : (root.workspaceButtonWidth * wsModel.shownCount)
implicitHeight: root.vertical ? (root.workspaceButtonWidth * wsModel.shownCount) : Appearance.sizes.barHeight
// Function to update workspaceOccupied
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
WheelHandler {
@@ -82,27 +106,25 @@ Item {
}
// Workspaces - background
Box {
Grid {
z: 1
anchors.centerIn: parent
rowSpacing: 0
columnSpacing: 0
vertical: root.vertical
columns: root.vertical ? 1 : root.workspacesShown
rows: root.vertical ? root.workspacesShown : 1
Repeater {
model: wsModel.shownCount
delegate: Rectangle {
required property int index
model: root.workspacesShown
Rectangle {
z: 1
implicitWidth: root.workspaceButtonWidth
implicitHeight: root.workspaceButtonWidth
implicitWidth: workspaceButtonWidth
implicitHeight: workspaceButtonWidth
radius: (width / 2)
property bool thisOccupied: (wsModel.occupied[index] && !(!wsModel.currentWorkspaceNotFake && monitor?.activeWorkspace?.id === index+1))
property var previousOccupied: (wsModel.occupied[index-1] && !(!wsModel.currentWorkspaceNotFake && monitor?.activeWorkspace?.id === index))
property var rightOccupied: (wsModel.occupied[index+1] && !(!wsModel.currentWorkspaceNotFake && monitor?.activeWorkspace?.id === index+2))
property var previousOccupied: (workspaceOccupied[index-1] && !(!activeWindow?.activated && root.effectiveActiveWorkspaceId === index))
property var rightOccupied: (workspaceOccupied[index+1] && !(!activeWindow?.activated && root.effectiveActiveWorkspaceId === index+2))
property var radiusPrev: previousOccupied ? 0 : (width / 2)
property var radiusNext: rightOccupied ? 0 : (width / 2)
@@ -112,7 +134,7 @@ Item {
bottomRightRadius: radiusNext
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 {
animation: Appearance.animation.elementMove.numberAnimation.createObject(this)
@@ -147,9 +169,9 @@ Item {
id: idxPair
index: root.workspaceIndexInGroup
}
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.workspaceButtonWidth - root.activeWorkspaceMargin * 2
property real indicatorThickness: root.workspaceButtonWidth - root.activeWorkspaceMargin * 2
property real indicatorPosition: Math.min(idxPair.idx1, idxPair.idx2) * workspaceButtonWidth + root.activeWorkspaceMargin
property real indicatorLength: Math.abs(idxPair.idx1 - idxPair.idx2) * workspaceButtonWidth + workspaceButtonWidth - root.activeWorkspaceMargin * 2
property real indicatorThickness: workspaceButtonWidth - root.activeWorkspaceMargin * 2
x: root.vertical ? null : indicatorPosition
implicitWidth: root.vertical ? indicatorThickness : indicatorLength
@@ -159,21 +181,22 @@ Item {
}
// Workspaces - numbers
Box {
id: wsNumbers
Grid {
z: 3
anchors.fill: parent
vertical: root.vertical
columns: root.vertical ? 1 : root.workspacesShown
rows: root.vertical ? root.workspacesShown : 1
columnSpacing: 0
rowSpacing: 0
anchors.fill: parent
Repeater {
model: wsModel.shownCount
delegate: Button {
model: root.workspacesShown
Button {
id: button
required property int index
property int workspaceValue: wsModel.getWorkspaceIdAt(index)
property int workspaceValue: workspaceGroup * root.workspacesShown + index + 1
implicitHeight: vertical ? Appearance.sizes.verticalBarWidth : Appearance.sizes.barHeight
implicitWidth: vertical ? Appearance.sizes.verticalBarWidth : Appearance.sizes.verticalBarWidth
onPressed: Hyprland.dispatch(`hl.dsp.focus({ workspace = ${workspaceValue}})`)
@@ -182,16 +205,11 @@ Item {
background: Item {
id: workspaceButtonBackground
implicitWidth: root.workspaceButtonWidth
implicitHeight: root.workspaceButtonWidth
implicitWidth: workspaceButtonWidth
implicitHeight: workspaceButtonWidth
property var biggestWindow: HyprlandData.biggestWindowForWorkspace(button.workspaceValue)
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
opacity: 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
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 {
animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
@@ -222,10 +243,13 @@ Item {
) ? 0 : 1
visible: opacity > 0
anchors.centerIn: parent
width: root.workspaceButtonWidth * 0.18
width: workspaceButtonWidth * 0.18
height: width
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 {
animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
@@ -233,8 +257,8 @@ Item {
}
Item { // Main app icon
anchors.centerIn: parent
width: root.workspaceButtonWidth
height: root.workspaceButtonWidth
width: workspaceButtonWidth
height: workspaceButtonWidth
opacity: !Config.options?.bar.workspaces.showAppIcons ? 0 :
(workspaceButtonBackground.biggestWindow && !root.showNumbers && Config.options?.bar.workspaces.showAppIcons) ?
1 : workspaceButtonBackground.biggestWindow ? workspaceIconOpacityShrinked : 0
@@ -244,9 +268,9 @@ Item {
anchors.bottom: parent.bottom
anchors.right: parent.right
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) ?
(root.workspaceButtonWidth - workspaceIconSize) / 2 : workspaceIconMarginShrinked
(workspaceButtonWidth - workspaceIconSize) / 2 : workspaceIconMarginShrinked
source: workspaceButtonBackground.mainAppIconSource
implicitSize: (!root.showNumbers && Config.options?.bar.workspaces.showAppIcons) ? workspaceIconSize : workspaceIconSizeShrinked
@@ -285,8 +309,12 @@ Item {
}
}
}
}
}
}
}
@@ -303,7 +303,7 @@ MouseArea {
IconAndTextPair {
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)
color: (Battery.isLow && !Battery.isCharging) ? Appearance.colors.colError : Appearance.colors.colOnSurfaceVariant
}
@@ -92,7 +92,7 @@ Rectangle {
id: boltIcon
anchors.verticalCenter: parent.verticalCenter
fill: 1
text: Battery.isCharging ? "bolt" : Icons.getBatteryIcon(Battery.percentage * 100)
text: Battery.isCharging ? "bolt" : "battery_android_full"
color: batteryWidget.colText
iconSize: 24
animateChange: true
@@ -95,25 +95,6 @@ Item { // Wrapper
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 {
@@ -53,7 +53,6 @@ Scope {
margins: Appearance.sizes.hyprlandGapsOut
leftMargin: Appearance.sizes.elevationMargin
}
asynchronous: true
width: sidebarWidth - Appearance.sizes.hyprlandGapsOut - Appearance.sizes.elevationMargin
height: parent.height - Appearance.sizes.hyprlandGapsOut * 2
@@ -40,7 +40,6 @@ Item {
return `${minutes}:${seconds}`;
}
font.pixelSize: 40
font.features: { "tnum": 1 }
color: Appearance.m3colors.m3onSurface
}
StyledText {
@@ -53,7 +53,6 @@ Item {
StyledText {
// Layout.preferredWidth: elapsedIndicator.width * 0.6 // Prevent shakiness
font.pixelSize: 40
font.features: { "tnum": 1 }
color: Appearance.m3colors.m3onSurface
text: {
let totalSeconds = Math.floor(TimerService.stopwatchTime) / 100
@@ -65,10 +64,9 @@ Item {
StyledText {
Layout.fillWidth: true
font.pixelSize: 40
font.features: { "tnum": 1 }
color: Appearance.colors.colSubtext
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 {
toggled: Network.wifiStatus !== "disabled"
buttonIcon: Icons.getNetworkMaterialSymbol()
buttonIcon: Network.materialSymbol
onClicked: Network.toggleWifi()
altAction: () => {
Quickshell.execDetached(["bash", "-c", `${Network.ethernet ? Config.options.apps.networkEthernet : Config.options.apps.network}`])
@@ -273,7 +273,7 @@ Item { // Bar content region
}
}
MaterialSymbol {
text: Icons.getNetworkMaterialSymbol()
text: Network.materialSymbol
iconSize: Appearance.font.pixelSize.larger
color: rightSidebarButton.colText
}
@@ -1,14 +1,19 @@
import QtQuick
import org.kde.kirigami as Kirigami
import qs.services
import qs.modules.common
import qs.modules.common.widgets as W
W.AppIcon {
Kirigami.Icon {
id: root
required property string iconName
property bool separateLightDark: false
property bool tryCustomIcon: true
property real implicitSize: 26
implicitWidth: implicitSize
implicitHeight: implicitSize
animated: true
roundToIconSize: false
fallback: root.iconName
source: tryCustomIcon ? `${Looks.iconsPath}/${root.iconName}${!root.separateLightDark ? "" : Looks.dark ? "-dark" : "-light"}.svg` : fallback
@@ -24,9 +24,9 @@ Singleton {
property string internetIcon: {
if (Network.ethernet)
return "ethernet";
if (Network.wifiEnabled && Network.wifiStatus === "connected") {
if (Network.wifiEnabled) {
const strength = Network.networkStrength;
return root.wifiIconForStrength(strength);
return wifiIconForStrength(strength);
}
if (Network.wifiStatus === "connecting")
return "wifi-4";
@@ -11,9 +11,10 @@ ScrollBar {
active: hovered || pressed
property color color: Looks.colors.controlBg
contentItem: Pill {
contentItem: Rectangle {
implicitWidth: root.active ? 4 : 2
implicitHeight: root.visualSize
radius: 9999
color: root.color
opacity: root.policy === ScrollBar.AlwaysOn || (root.active && root.size < 1.0) ? 0.5 : 0
@@ -1,23 +1,31 @@
import QtQuick
import qs.modules.common.widgets as W
W.FixedWidthTextContainer {
Item {
id: root
property string longestText
property alias text: textItem.text
property alias font: textItem.font
property alias horizontalAlignment: textItem.horizontalAlignment
property alias verticalAlignment: textItem.verticalAlignment
property alias color: textItem.color
font {
family: Looks.font.family.ui
pixelSize: Looks.font.pixelSize.large
weight: Looks.font.weight.regular
implicitWidth: longestTextMetrics.width
implicitHeight: longestTextMetrics.height
TextMetrics {
id: longestTextMetrics
text: root.longestText
font {
family: Looks.font.family.ui
pixelSize: Looks.font.pixelSize.large
weight: Looks.font.weight.regular
}
}
WText {
id: textItem
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