merge upstream

This commit is contained in:
Greyfeather
2025-05-21 00:22:49 -06:00
parent 08b9014ee2
commit 65b5ec93c7
87 changed files with 2119 additions and 1602 deletions
@@ -46,10 +46,10 @@
"fakeScreenRounding": 2 // 0: None | 1: Always | 2: When not fullscreen "fakeScreenRounding": 2 // 0: None | 1: Always | 2: When not fullscreen
}, },
"apps": { "apps": {
"bluetooth": "blueberry", "bluetooth": "better-control --bluetooth",
"imageViewer": "loupe", "imageViewer": "loupe",
"network": "XDG_CURRENT_DESKTOP=\"gnome\" gnome-control-center wifi", "network": "better-control --wifi",
"settings": "XDG_CURRENT_DESKTOP=\"gnome\" gnome-control-center", "settings": "better-control",
"taskManager": "gnome-usage", "taskManager": "gnome-usage",
"terminal": "foot" // This is only for shell actions "terminal": "foot" // This is only for shell actions
}, },
@@ -22,8 +22,8 @@ var lastCoverPath = '';
function isRealPlayer(player) { function isRealPlayer(player) {
return ( return (
// Remove unecessary native buses from browsers if there's plasma integration // Remove unecessary native buses from browsers if there's plasma integration
!(hasPlasmaIntegration && player.busName.startsWith('org.mpris.MediaPlayer2.firefox')) && // !(hasPlasmaIntegration && player.busName.startsWith('org.mpris.MediaPlayer2.firefox')) &&
!(hasPlasmaIntegration && player.busName.startsWith('org.mpris.MediaPlayer2.chromium')) && // !(hasPlasmaIntegration && player.busName.startsWith('org.mpris.MediaPlayer2.chromium')) &&
// playerctld just copies other buses and we don't need duplicates // playerctld just copies other buses and we don't need duplicates
!player.busName.startsWith('org.mpris.MediaPlayer2.playerctld') && !player.busName.startsWith('org.mpris.MediaPlayer2.playerctld') &&
// Non-instance mpd bus // Non-instance mpd bus
@@ -209,8 +209,15 @@ const CoverArt = ({ player, ...rest }) => {
execAsync(['bash', '-c', execAsync(['bash', '-c',
`${App.configDir}/scripts/color_generation/generate_colors_material.py --path '${coverPath}' --mode ${darkMode.value ? 'dark' : 'light'} > ${GLib.get_user_state_dir()}/ags/scss/_musicmaterial.scss`]) `${App.configDir}/scripts/color_generation/generate_colors_material.py --path '${coverPath}' --mode ${darkMode.value ? 'dark' : 'light'} > ${GLib.get_user_state_dir()}/ags/scss/_musicmaterial.scss`])
.then(() => { .then(() => {
exec(`${App.configDir}/scripts/color_generation/pywal.sh -i "${player.coverPath}" -n -t -s -e -q ${darkMode.value ? '' : '-l'}`) const dominantColor = `#${Utils.exec(`sh -c "magick '${coverPath}' -scale 1x1\\! -format '%[fx:int(255*r+.5)],%[fx:int(255*g+.5)],%[fx:int(255*b+.5)]' info: | sed 's/,/\\n/g' | xargs -L 1 printf '%02x' ; echo"`)}`
exec(`cp ${GLib.get_user_cache_dir()}/wal/colors.scss ${GLib.get_user_state_dir()}/ags/scss/_musicwal.scss`); console.log(dominantColor);
// exec(`${App.configDir}/scripts/color_generation/pywal.sh -i "${player.coverPath}" -n -t -s -e -q ${darkMode.value ? '' : '-l'}`)
// exec(`cp ${GLib.get_user_cache_dir()}/wal/colors.scss ${GLib.get_user_state_dir()}/ags/scss/_musicwal.scss`);
exec(`cp '${App.configDir}/scripts/templates/wal/_musicwal.scss' '${GLib.get_user_state_dir()}/ags/scss/_musicwal.scss'`);
exec(`sed -i 's/{{dominantColor}}/${dominantColor}/g' '${GLib.get_user_state_dir()}/ags/scss/_musicwal.scss'`)
exec(`sed -i 's/{{backgroundColor}}/${darkMode.value ? "#0E1415" : "#EEF4F4"}/g' '${GLib.get_user_state_dir()}/ags/scss/_musicwal.scss'`)
exec(`sed -i 's/{{foregroundColor}}/${darkMode.value ? "#EEF4F4" : "#0E1415"}/g' '${GLib.get_user_state_dir()}/ags/scss/_musicwal.scss'`)
exec(`sass -I "${GLib.get_user_state_dir()}/ags/scss" -I "${App.configDir}/scss/fallback" "${App.configDir}/scss/_music.scss" "${stylePath}"`); exec(`sass -I "${GLib.get_user_state_dir()}/ags/scss" -I "${App.configDir}/scss/fallback" "${App.configDir}/scss/_music.scss" "${stylePath}"`);
Utils.timeout(200, () => { Utils.timeout(200, () => {
// self.attribute.showImage(self, coverPath) // self.attribute.showImage(self, coverPath)
@@ -0,0 +1,22 @@
// Special
$background: {{backgroundColor}};
$foreground: {{foregroundColor}};
$cursor: {{foregroundColor}};
// Colors
$color0: {{dominantColor}};
$color1: {{dominantColor}};
$color2: {{dominantColor}};
$color3: {{dominantColor}};
$color4: {{dominantColor}};
$color5: {{dominantColor}};
$color6: {{dominantColor}};
$color7: {{dominantColor}};
$color8: {{dominantColor}};
$color9: {{dominantColor}};
$color10: {{dominantColor}};
$color11: {{dominantColor}};
$color12: {{dominantColor}};
$color13: {{dominantColor}};
$color14: {{dominantColor}};
$color15: {{dominantColor}};
+3
View File
@@ -20,8 +20,11 @@ $secondaryContainer: transparentize(mix(mix($background, $color2, 50%), $color6,
$onSecondaryContainer: mix($color7, $color2, 90%); $onSecondaryContainer: mix($color7, $color2, 90%);
@if $darkmode == False { @if $darkmode == False {
$onSecondaryContainer: mix($onSecondaryContainer, black, 50%); $onSecondaryContainer: mix($onSecondaryContainer, black, 50%);
} @else {
$onSecondaryContainer: mix($onSecondaryContainer, white, 50%);
} }
.osd-music { .osd-music {
@include menu_decel; @include menu_decel;
@include elevation2; @include elevation2;
+4 -2
View File
@@ -1,8 +1,10 @@
# See https://wiki.hyprland.org/Configuring/Binds/ # See https://wiki.hyprland.org/Configuring/Binds/
#! #!
##! User keybinds ##! Extra keybinds
bind = Ctrl+Super+Alt, Slash, exec, xdg-open ~/.config/hypr/custom/keybinds.conf # Edit custom keybinds bind = Ctrl+Super+Alt, Slash, exec, xdg-open ~/.config/hypr/custom/keybinds.conf # Edit extra keybinds
# Add stuff here # Add stuff here
# Use #! to add an extra column on the cheatsheet # Use #! to add an extra column on the cheatsheet
# Use ##! to add a section in that column # Use ##! to add a section in that column
# Add a comment after a bind to add a description, like above
+1 -1
View File
@@ -1,5 +1,5 @@
# This file sources other files in `hyprland` and `custom` folders # This file sources other files in `hyprland` and `custom` folders
# You wanna add your stuff in file in `custom` # You wanna add your stuff in files in `custom`
exec = hyprctl dispatch submap global # DO NOT REMOVE THIS OR YOU WON'T BE ABLE TO USE ANY KEYBIND exec = hyprctl dispatch submap global # DO NOT REMOVE THIS OR YOU WON'T BE ABLE TO USE ANY KEYBIND
submap = global # This is required for catchall to work submap = global # This is required for catchall to work
+4 -3
View File
@@ -23,6 +23,7 @@ bindd = Super, A, Toggle left sidebar, global, quickshell:sidebarLeftToggle # To
bind = Super, O, global, quickshell:sidebarLeftToggle # [hidden] bind = Super, O, global, quickshell:sidebarLeftToggle # [hidden]
bindd = Super, N, Toggle right sidebar, global, quickshell:sidebarRightToggle # Toggle right sidebar bindd = Super, N, Toggle right sidebar, global, quickshell:sidebarRightToggle # Toggle right sidebar
bindd = Super, Slash, Toggle cheatsheet, global, quickshell:cheatsheetToggle # Toggle cheatsheet bindd = Super, Slash, Toggle cheatsheet, global, quickshell:cheatsheetToggle # Toggle cheatsheet
bindd = Super, M, Toggle media controls, global, quickshell:mediaControlsToggle # Toggle media controls
bindd = Ctrl+Alt, Delete, Toggle session menu, global, quickshell:sessionToggle # Toggle session menu bindd = Ctrl+Alt, Delete, Toggle session menu, global, quickshell:sessionToggle # Toggle session menu
bind = Ctrl+Alt, Delete, exec, qs ipc call TEST_ALIVE || pkill wlogout || wlogout -p layer-shell # [hidden] bind = Ctrl+Alt, Delete, exec, qs ipc call TEST_ALIVE || pkill wlogout || wlogout -p layer-shell # [hidden]
@@ -46,8 +47,8 @@ bindd = Ctrl+Shift+Alt+Super, Delete, Shutdown, exec, systemctl poweroff || logi
##! Utilities ##! Utilities
# Screenshot, Record, OCR, Color picker, Clipboard history # Screenshot, Record, OCR, Color picker, Clipboard history
bindd = Super, V, Copy clipboard history entry, exec, pkill fuzzel || cliphist list | fuzzel --match-mode fzf --dmenu | cliphist decode | wl-copy # Clipboard history >> clipboard bindd = Super, V, Copy clipboard history entry, exec, pkill fuzzel || cliphist list | fuzzel --match-mode fzf --dmenu | cliphist decode | wl-copy # Clipboard history >> clipboard
bindd = Super, Period, Copy an emoji, exec, pkill fuzzel || ~/.local/bin/fuzzel-emoji # Pick emoji >> clipboard bindd = Super, Period, Copy an emoji, exec, pkill fuzzel || ~/.local/bin/fuzzel-emoji # Emoji
bindd = Super+Shift, S, Screen snip, exec, grimblast --freeze copy area # Screen snip >> clipboard bindd = Super+Shift, S, Screen snip, exec, grimblast --freeze copy area # Screen snip >> clipboard
bindd = Super+Shift+Alt, S, Screen snip and annotate, exec, grim -g "$(slurp)" - | swappy -f - # Screen snip and annotate bindd = Super+Shift+Alt, S, Screen snip and annotate, exec, grim -g "$(slurp)" - | swappy -f - # Screen snip and annotate
# OCR # OCR
@@ -198,8 +199,8 @@ bind = Super+Alt, E, exec, thunar # [hidden]
bind = Super, W, exec, zen-browser # [hidden] bind = Super, W, exec, zen-browser # [hidden]
bind = Super+Shift, W, exec, wps # WPS Office bind = Super+Shift, W, exec, wps # WPS Office
bind = Ctrl+Super, V, exec, pavucontrol # Pavucontrol (volume mixer) bind = Ctrl+Super, V, exec, pavucontrol # Pavucontrol (volume mixer)
bind = Super, I, exec, better-control # Better Control (settings app)
bind = Super, X, exec, gnome-text-editor --new-window # GNOME Text Editor bind = Super, X, exec, gnome-text-editor --new-window # GNOME Text Editor
bind = Super, I, exec, XDG_CURRENT_DESKTOP="gnome" gnome-control-center # GNOME Settings
bind = Ctrl+Shift, Escape, exec, gnome-system-monitor # GNOME System monitor bind = Ctrl+Shift, Escape, exec, gnome-system-monitor # GNOME System monitor
# Cursed stuff # Cursed stuff
+1 -2
View File
@@ -105,15 +105,14 @@ layerrule = ignorealpha 0.6, osk[0-9]*
# Quickshell # Quickshell
## My stuff ## My stuff
layerrule = animation slide, quickshell:bar
layerrule = animation fade, quickshell:screenCorners layerrule = animation fade, quickshell:screenCorners
layerrule = animation slide right, quickshell:sidebarRight layerrule = animation slide right, quickshell:sidebarRight
layerrule = animation slide left, quickshell:sidebarLeft layerrule = animation slide left, quickshell:sidebarLeft
layerrule = animation slide top, quickshell:onScreenDisplay
layerrule = blur, quickshell:session layerrule = blur, quickshell:session
layerrule = noanim, quickshell:session layerrule = noanim, quickshell:session
# Launchers need to be FAST # Launchers need to be FAST
layerrule = noanim, quickshell:overview layerrule = noanim, quickshell:overview
layerrule = noanim, launcher
layerrule = noanim, gtk4-layer-shell layerrule = noanim, gtk4-layer-shell
## outfoxxed's stuff ## outfoxxed's stuff
layerrule = blur, shell:bar layerrule = blur, shell:bar
+3 -3
View File
@@ -8,8 +8,8 @@ pragma ComponentBehavior: Bound
Singleton { Singleton {
id: root id: root
property int sidebarLeftOpenCount: 0 property bool sidebarLeftOpen: false
property int sidebarRightOpenCount: 0 property bool sidebarRightOpen: false
property bool overviewOpen: false property bool overviewOpen: false
property bool workspaceShowNumbers: false property bool workspaceShowNumbers: false
property bool superReleaseMightTrigger: true property bool superReleaseMightTrigger: true
@@ -31,7 +31,7 @@ Singleton {
GlobalShortcut { GlobalShortcut {
name: "workspaceNumber" name: "workspaceNumber"
description: "Hold to show workspace numbers, release to show icons" description: qsTr("Hold to show workspace numbers, release to show icons")
onPressed: { onPressed: {
workspaceShowNumbersTimer.start() workspaceShowNumbersTimer.start()
+4 -4
View File
@@ -108,7 +108,7 @@ Scope {
ScrollHint { ScrollHint {
reveal: barLeftSideMouseArea.hovered reveal: barLeftSideMouseArea.hovered
icon: "light_mode" icon: "light_mode"
tooltipText: "Scroll to change brightness" tooltipText: qsTr("Scroll to change brightness")
side: "left" side: "left"
anchors.left: parent.left anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
@@ -127,7 +127,7 @@ Scope {
// Layout.fillHeight: true // Layout.fillHeight: true
radius: Appearance.rounding.full radius: Appearance.rounding.full
color: (barLeftSideMouseArea.pressed || GlobalStates.sidebarLeftOpenCount > 0) ? Appearance.colors.colLayer1Active : barLeftSideMouseArea.hovered ? Appearance.colors.colLayer1Hover : "transparent" color: (barLeftSideMouseArea.pressed || GlobalStates.sidebarLeftOpen) ? Appearance.colors.colLayer1Active : barLeftSideMouseArea.hovered ? Appearance.colors.colLayer1Hover : "transparent"
implicitWidth: distroIcon.width + 5*2 implicitWidth: distroIcon.width + 5*2
implicitHeight: distroIcon.height + 5*2 implicitHeight: distroIcon.height + 5*2
@@ -283,7 +283,7 @@ Scope {
ScrollHint { ScrollHint {
reveal: barRightSideMouseArea.hovered reveal: barRightSideMouseArea.hovered
icon: "volume_up" icon: "volume_up"
tooltipText: "Scroll to change volume" tooltipText: qsTr("Scroll to change volume")
side: "right" side: "right"
anchors.right: parent.right anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
@@ -301,7 +301,7 @@ Scope {
Layout.fillHeight: true Layout.fillHeight: true
implicitWidth: indicatorsRowLayout.implicitWidth + 10*2 implicitWidth: indicatorsRowLayout.implicitWidth + 10*2
radius: Appearance.rounding.full radius: Appearance.rounding.full
color: (barRightSideMouseArea.pressed || GlobalStates.sidebarRightOpenCount > 0) ? Appearance.colors.colLayer1Active : barRightSideMouseArea.hovered ? Appearance.colors.colLayer1Hover : "transparent" color: (barRightSideMouseArea.pressed || GlobalStates.sidebarRightOpen) ? Appearance.colors.colLayer1Active : barRightSideMouseArea.hovered ? Appearance.colors.colLayer1Hover : "transparent"
RowLayout { RowLayout {
id: indicatorsRowLayout id: indicatorsRowLayout
anchors.centerIn: parent anchors.centerIn: parent
+6 -13
View File
@@ -8,6 +8,7 @@ import Quickshell.Services.UPower
Rectangle { Rectangle {
id: root id: root
property bool borderless: ConfigOptions.bar.borderless
readonly property var chargeState: UPower.displayDevice.state readonly property var chargeState: UPower.displayDevice.state
readonly property bool isCharging: chargeState == UPowerDeviceState.Charging readonly property bool isCharging: chargeState == UPowerDeviceState.Charging
readonly property bool isPluggedIn: isCharging || chargeState == UPowerDeviceState.PendingCharge readonly property bool isPluggedIn: isCharging || chargeState == UPowerDeviceState.PendingCharge
@@ -18,7 +19,7 @@ Rectangle {
implicitWidth: rowLayout.implicitWidth + rowLayout.spacing * 2 implicitWidth: rowLayout.implicitWidth + rowLayout.spacing * 2
implicitHeight: 32 implicitHeight: 32
color: Appearance.colors.colLayer1 color: borderless ? "transparent" : Appearance.colors.colLayer1
radius: Appearance.rounding.small radius: Appearance.rounding.small
RowLayout { RowLayout {
@@ -31,18 +32,14 @@ Rectangle {
implicitWidth: (isCharging ? (boltIconLoader?.item?.width ?? 0) : 0) implicitWidth: (isCharging ? (boltIconLoader?.item?.width ?? 0) : 0)
Behavior on implicitWidth { Behavior on implicitWidth {
NumberAnimation { animation: Appearance.animation.elementMove.numberAnimation.createObject(this)
duration: Appearance.animation.elementMove.duration
easing.type: Appearance.animation.elementMove.type
easing.bezierCurve: Appearance.animation.elementMove.bezierCurve
}
} }
} }
StyledText { StyledText {
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
color: Appearance.colors.colOnLayer1 color: Appearance.colors.colOnLayer1
text: `${Math.round(percentage * 100)}%` text: `${Math.round(percentage * 100)}`
} }
CircularProgress { CircularProgress {
@@ -56,6 +53,7 @@ Rectangle {
MaterialSymbol { MaterialSymbol {
anchors.centerIn: parent anchors.centerIn: parent
fill: 1
text: "battery_full" text: "battery_full"
iconSize: Appearance.font.pixelSize.normal iconSize: Appearance.font.pixelSize.normal
color: (isLow && !isCharging) ? batteryLowOnBackground : Appearance.m3colors.m3onSecondaryContainer color: (isLow && !isCharging) ? batteryLowOnBackground : Appearance.m3colors.m3onSecondaryContainer
@@ -91,12 +89,7 @@ Rectangle {
} }
Behavior on opacity { Behavior on opacity {
NumberAnimation { animation: Appearance.animation.elementMove.numberAnimation.createObject(this)
duration: Appearance.animation.elementMove.duration
easing.type: Appearance.animation.elementMove.type
easing.bezierCurve: Appearance.animation.elementMove.bezierCurve
}
} }
} }
@@ -1,5 +1,6 @@
import "root:/modules/common" import "root:/modules/common"
import "root:/modules/common/widgets/" import "root:/modules/common/widgets/"
import "root:/modules/common/functions/color_utils.js" as ColorUtils
import QtQuick import QtQuick
import QtQuick.Controls import QtQuick.Controls
import QtQuick.Layouts import QtQuick.Layouts
@@ -21,7 +22,9 @@ Button {
background: Rectangle { background: Rectangle {
anchors.fill: parent anchors.fill: parent
radius: Appearance.rounding.full radius: Appearance.rounding.full
color: (button.down || extraActiveCondition) ? Appearance.colors.colLayer2Active : (button.hovered ? Appearance.colors.colLayer2Hover : Appearance.colors.colLayer2) color: (button.down || extraActiveCondition) ? Appearance.colors.colLayer1Active :
(button.hovered ? Appearance.colors.colLayer1Hover :
ColorUtils.transparentize(Appearance.colors.colLayer1, 1))
} }
@@ -5,9 +5,10 @@ import QtQuick
import QtQuick.Layouts import QtQuick.Layouts
Rectangle { Rectangle {
property bool borderless: ConfigOptions.bar.borderless
implicitWidth: rowLayout.implicitWidth + rowLayout.spacing * 6 // idk, text seems nicer w/ more padding implicitWidth: rowLayout.implicitWidth + rowLayout.spacing * 6 // idk, text seems nicer w/ more padding
implicitHeight: 32 implicitHeight: 32
color: Appearance.colors.colLayer1 color: borderless ? "transparent" : Appearance.colors.colLayer1
radius: Appearance.rounding.small radius: Appearance.rounding.small
RowLayout { RowLayout {
+19 -13
View File
@@ -1,29 +1,24 @@
import "root:/modules/common" import "root:/modules/common"
import "root:/modules/common/widgets" import "root:/modules/common/widgets"
import "root:/services" import "root:/services"
import "root:/modules/common/functions/string_utils.js" as StringUtils
import QtQuick import QtQuick
import QtQuick.Layouts import QtQuick.Layouts
import Quickshell import Quickshell
import Quickshell.Io import Quickshell.Io
import Quickshell.Services.Mpris import Quickshell.Services.Mpris
import Quickshell.Hyprland
Item { Item {
id: root
property bool borderless: ConfigOptions.bar.borderless
readonly property MprisPlayer activePlayer: MprisController.activePlayer readonly property MprisPlayer activePlayer: MprisController.activePlayer
readonly property string cleanedTitle: activePlayer?.trackTitle.replace(/【[^】]*】/, "") || qsTr("No media") readonly property string cleanedTitle: StringUtils.cleanMusicTitle(activePlayer?.trackTitle) || qsTr("No media")
Layout.fillHeight: true Layout.fillHeight: true
implicitWidth: rowLayout.implicitWidth + rowLayout.spacing * 2 implicitWidth: rowLayout.implicitWidth + rowLayout.spacing * 2
implicitHeight: 40 implicitHeight: 40
// Background
Rectangle {
anchors.centerIn: parent
width: parent.width
implicitHeight: 32
color: Appearance.colors.colLayer1
radius: Appearance.rounding.small
}
Timer { Timer {
running: activePlayer?.playbackState == MprisPlaybackState.Playing running: activePlayer?.playbackState == MprisPlaybackState.Playing
interval: 1000 interval: 1000
@@ -33,7 +28,7 @@ Item {
MouseArea { MouseArea {
anchors.fill: parent anchors.fill: parent
acceptedButtons: Qt.MiddleButton | Qt.BackButton | Qt.ForwardButton | Qt.RightButton acceptedButtons: Qt.MiddleButton | Qt.BackButton | Qt.ForwardButton | Qt.RightButton | Qt.LeftButton
onPressed: (event) => { onPressed: (event) => {
if (event.button === Qt.MiddleButton) { if (event.button === Qt.MiddleButton) {
activePlayer.togglePlaying(); activePlayer.togglePlaying();
@@ -41,11 +36,21 @@ Item {
activePlayer.previous(); activePlayer.previous();
} else if (event.button === Qt.ForwardButton || event.button === Qt.RightButton) { } else if (event.button === Qt.ForwardButton || event.button === Qt.RightButton) {
activePlayer.next(); activePlayer.next();
} } else if (event.button === Qt.LeftButton) {
Hyprland.dispatch("global quickshell:mediaControlsToggle")
}
} }
} }
RowLayout { Rectangle { // Background
anchors.centerIn: parent
width: parent.width
implicitHeight: 32
color: borderless ? "transparent" : Appearance.colors.colLayer1
radius: Appearance.rounding.small
}
RowLayout { // Real content
id: rowLayout id: rowLayout
spacing: 4 spacing: 4
@@ -62,6 +67,7 @@ Item {
MaterialSymbol { MaterialSymbol {
anchors.centerIn: parent anchors.centerIn: parent
fill: 1
text: activePlayer?.isPlaying ? "pause" : "play_arrow" text: activePlayer?.isPlaying ? "pause" : "play_arrow"
iconSize: Appearance.font.pixelSize.normal iconSize: Appearance.font.pixelSize.normal
color: Appearance.m3colors.m3onSecondaryContainer color: Appearance.m3colors.m3onSecondaryContainer
+3 -6
View File
@@ -28,6 +28,7 @@ Item {
MaterialSymbol { MaterialSymbol {
anchors.centerIn: parent anchors.centerIn: parent
fill: 1
text: iconName text: iconName
iconSize: Appearance.font.pixelSize.normal iconSize: Appearance.font.pixelSize.normal
color: Appearance.m3colors.m3onSecondaryContainer color: Appearance.m3colors.m3onSecondaryContainer
@@ -38,15 +39,11 @@ Item {
StyledText { StyledText {
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
color: Appearance.colors.colOnLayer1 color: Appearance.colors.colOnLayer1
text: `${Math.round(percentage * 100)}%` text: `${Math.round(percentage * 100)}`
} }
Behavior on x { Behavior on x {
NumberAnimation { animation: Appearance.animation.elementMove.numberAnimation.createObject(this)
duration: Appearance.animation.elementMove.duration
easing.type: Appearance.animation.elementMove.type
easing.bezierCurve: Appearance.animation.elementMove.bezierCurve
}
} }
} }
+2 -1
View File
@@ -8,9 +8,10 @@ import Quickshell.Io
import Quickshell.Services.Mpris import Quickshell.Services.Mpris
Rectangle { Rectangle {
property bool borderless: ConfigOptions.bar.borderless
implicitWidth: rowLayout.implicitWidth + rowLayout.anchors.leftMargin + rowLayout.anchors.rightMargin implicitWidth: rowLayout.implicitWidth + rowLayout.anchors.leftMargin + rowLayout.anchors.rightMargin
implicitHeight: 32 implicitHeight: 32
color: Appearance.colors.colLayer1 color: borderless ? "transparent" : Appearance.colors.colLayer1
radius: Appearance.rounding.small radius: Appearance.rounding.small
RowLayout { RowLayout {
@@ -1,4 +1,5 @@
import "root:/modules/common/" import "root:/modules/common/"
import "root:/modules/common/functions/color_utils.js" as ColorUtils
import QtQuick import QtQuick
import QtQuick.Layouts import QtQuick.Layouts
import Quickshell import Quickshell
@@ -57,7 +58,7 @@ MouseArea {
ColorOverlay { ColorOverlay {
anchors.fill: desaturatedIcon anchors.fill: desaturatedIcon
source: desaturatedIcon source: desaturatedIcon
color: Appearance.transparentize(Appearance.colors.colOnLayer0, 0.6) color: ColorUtils.transparentize(Appearance.colors.colOnLayer0, 0.6)
} }
} }
@@ -7,10 +7,12 @@ import Quickshell.Io
import Quickshell.Hyprland import Quickshell.Hyprland
Rectangle { Rectangle {
id: root
property bool borderless: ConfigOptions.bar.borderless
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
implicitWidth: rowLayout.implicitWidth + rowLayout.spacing * 2 implicitWidth: rowLayout.implicitWidth + rowLayout.spacing * 2
implicitHeight: 32 implicitHeight: 32
color: Appearance.colors.colLayer1 color: borderless ? "transparent" : Appearance.colors.colLayer1
radius: Appearance.rounding.small radius: Appearance.rounding.small
RowLayout { RowLayout {
@@ -24,7 +26,8 @@ Rectangle {
onClicked: Hyprland.dispatch("exec grimblast copy area") onClicked: Hyprland.dispatch("exec grimblast copy area")
MaterialSymbol { MaterialSymbol {
anchors.centerIn: parent horizontalAlignment: Qt.AlignHCenter
fill: 1
text: "screenshot_region" text: "screenshot_region"
iconSize: Appearance.font.pixelSize.normal iconSize: Appearance.font.pixelSize.normal
color: Appearance.colors.colOnLayer2 color: Appearance.colors.colOnLayer2
@@ -37,7 +40,8 @@ Rectangle {
onClicked: Hyprland.dispatch("exec hyprpicker -a") onClicked: Hyprland.dispatch("exec hyprpicker -a")
MaterialSymbol { MaterialSymbol {
anchors.centerIn: parent horizontalAlignment: Qt.AlignHCenter
fill: 1
text: "colorize" text: "colorize"
iconSize: Appearance.font.pixelSize.normal iconSize: Appearance.font.pixelSize.normal
color: Appearance.colors.colOnLayer2 color: Appearance.colors.colOnLayer2
+5 -25
View File
@@ -15,6 +15,7 @@ import Qt5Compat.GraphicalEffects
Item { Item {
required property var bar required property var bar
property bool borderless: ConfigOptions.bar.borderless
readonly property HyprlandMonitor monitor: Hyprland.monitorFor(bar.screen) readonly property HyprlandMonitor monitor: Hyprland.monitorFor(bar.screen)
readonly property Toplevel activeWindow: ToplevelManager.activeToplevel readonly property Toplevel activeWindow: ToplevelManager.activeToplevel
@@ -66,7 +67,7 @@ Item {
implicitHeight: 32 implicitHeight: 32
implicitWidth: rowLayout.implicitWidth + widgetPadding * 2 implicitWidth: rowLayout.implicitWidth + widgetPadding * 2
radius: Appearance.rounding.small radius: Appearance.rounding.small
color: Appearance.colors.colLayer1 color: borderless ? "transparent" : Appearance.colors.colLayer1
} }
// Scroll to switch workspaces // Scroll to switch workspaces
@@ -119,26 +120,14 @@ Item {
opacity: (workspaceOccupied[index] && !(!activeWindow?.activated && monitor.activeWorkspace?.id === index+1)) ? 1 : 0 opacity: (workspaceOccupied[index] && !(!activeWindow?.activated && monitor.activeWorkspace?.id === index+1)) ? 1 : 0
Behavior on opacity { Behavior on opacity {
NumberAnimation { animation: Appearance.animation.elementMove.numberAnimation.createObject(this)
duration: Appearance.animation.elementMove.duration
easing.type: Appearance.animation.elementMove.type
easing.bezierCurve: Appearance.animation.elementMove.bezierCurve
}
} }
Behavior on radiusLeft { Behavior on radiusLeft {
NumberAnimation { animation: Appearance.animation.elementMove.numberAnimation.createObject(this)
duration: Appearance.animation.elementMove.duration
easing.type: Appearance.animation.elementMove.type
easing.bezierCurve: Appearance.animation.elementMove.bezierCurve
}
} }
Behavior on radiusRight { Behavior on radiusRight {
NumberAnimation { animation: Appearance.animation.elementMove.numberAnimation.createObject(this)
duration: Appearance.animation.elementMove.duration
easing.type: Appearance.animation.elementMove.type
easing.bezierCurve: Appearance.animation.elementMove.bezierCurve
}
} }
} }
@@ -206,15 +195,6 @@ Item {
(workspaceOccupied[index] ? Appearance.colors.colOnLayer1 : (workspaceOccupied[index] ? Appearance.colors.colOnLayer1 :
Appearance.colors.colOnLayer1Inactive) Appearance.colors.colOnLayer1Inactive)
Behavior on color {
ColorAnimation {
duration: Appearance.animation.elementMove.duration
easing.type: Appearance.animation.elementMove.type
easing.bezierCurve: Appearance.animation.elementMove.bezierCurve
}
}
Behavior on opacity { Behavior on opacity {
NumberAnimation { NumberAnimation {
duration: Appearance.animation.elementMoveFast.duration duration: Appearance.animation.elementMoveFast.duration
@@ -2,10 +2,11 @@ import "root:/"
import "root:/services" import "root:/services"
import "root:/modules/common" import "root:/modules/common"
import "root:/modules/common/widgets" import "root:/modules/common/widgets"
import "root:/modules/common/functions/color_utils.js" as ColorUtils
import QtQuick import QtQuick
import QtQuick.Controls import QtQuick.Controls
import QtQuick.Effects
import QtQuick.Layouts import QtQuick.Layouts
import Qt5Compat.GraphicalEffects
import Quickshell.Io import Quickshell.Io
import Quickshell import Quickshell
import Quickshell.Widgets import Quickshell.Widgets
@@ -15,18 +16,24 @@ import Quickshell.Hyprland
Scope { // Scope Scope { // Scope
id: root id: root
Variants { // Window repeater Loader {
id: cheatsheetVariants id: cheatsheetLoader
model: Quickshell.screens active: false
PanelWindow { // Window sourceComponent: PanelWindow { // Window
id: cheatsheetRoot id: cheatsheetRoot
visible: false visible: cheatsheetLoader.active
focusable: true
property var modelData anchors {
top: true
bottom: true
left: true
right: true
}
screen: modelData function hide() {
cheatsheetLoader.active = false
}
exclusiveZone: 0 exclusiveZone: 0
implicitWidth: cheatsheetBackground.width + Appearance.sizes.elevationMargin * 2 implicitWidth: cheatsheetBackground.width + Appearance.sizes.elevationMargin * 2
implicitHeight: cheatsheetBackground.height + Appearance.sizes.elevationMargin * 2 implicitHeight: cheatsheetBackground.height + Appearance.sizes.elevationMargin * 2
@@ -42,25 +49,9 @@ Scope { // Scope
HyprlandFocusGrab { // Click outside to close HyprlandFocusGrab { // Click outside to close
id: grab id: grab
windows: [ cheatsheetRoot ] windows: [ cheatsheetRoot ]
active: false active: cheatsheetRoot.visible
onCleared: () => { onCleared: () => {
if (!active) cheatsheetRoot.visible = false if (!active) cheatsheetRoot.hide()
}
}
Connections {
target: cheatsheetRoot
function onVisibleChanged() {
delayedGrabTimer.start()
}
}
Timer {
id: delayedGrabTimer
interval: ConfigOptions.hacks.arbitraryRaceConditionDelay
repeat: false
onTriggered: {
grab.active = cheatsheetRoot.visible
} }
} }
@@ -74,9 +65,19 @@ Scope { // Scope
implicitWidth: cheatsheetColumnLayout.implicitWidth + padding * 2 implicitWidth: cheatsheetColumnLayout.implicitWidth + padding * 2
implicitHeight: cheatsheetColumnLayout.implicitHeight + padding * 2 implicitHeight: cheatsheetColumnLayout.implicitHeight + padding * 2
layer.enabled: true
layer.effect: MultiEffect {
source: cheatsheetBackground
anchors.fill: cheatsheetBackground
shadowEnabled: true
shadowVerticalOffset: 1
shadowColor: Appearance.colors.colShadow
shadowBlur: 0.5
}
Keys.onPressed: (event) => { // Esc to close Keys.onPressed: (event) => { // Esc to close
if (event.key === Qt.Key_Escape) { if (event.key === Qt.Key_Escape) {
cheatsheetRoot.visible = false cheatsheetRoot.hide()
} }
} }
@@ -94,23 +95,19 @@ Scope { // Scope
PointingHandInteraction {} PointingHandInteraction {}
onClicked: { onClicked: {
cheatsheetRoot.visible = false cheatsheetRoot.hide()
} }
background: Item {} background: null
contentItem: Rectangle { contentItem: Rectangle {
anchors.fill: parent anchors.fill: parent
radius: Appearance.rounding.full radius: Appearance.rounding.full
color: closeButton.pressed ? Appearance.colors.colLayer0Active : color: closeButton.pressed ? Appearance.colors.colLayer0Active :
closeButton.hovered ? Appearance.colors.colLayer0Hover : closeButton.hovered ? Appearance.colors.colLayer0Hover :
Appearance.transparentize(Appearance.colors.colLayer0, 1) ColorUtils.transparentize(Appearance.colors.colLayer0, 1)
Behavior on color { Behavior on color {
ColorAnimation { animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this)
duration: Appearance.animation.elementMoveFast.duration
easing.type: Appearance.animation.elementMoveFast.type
easing.bezierCurve: Appearance.animation.elementMoveFast.bezierCurve
}
} }
MaterialSymbol { MaterialSymbol {
@@ -137,95 +134,49 @@ Scope { // Scope
} }
} }
// Shadow
DropShadow {
anchors.fill: cheatsheetBackground
horizontalOffset: 0
verticalOffset: 2
radius: Appearance.sizes.elevationMargin
samples: Appearance.sizes.elevationMargin * 2 + 1 // Ideally should be 2 * radius + 1, see qt docs
color: Appearance.colors.colShadow
source: cheatsheetBackground
}
} }
} }
IpcHandler { IpcHandler {
target: "cheatsheet" target: "cheatsheet"
function toggle(): void { function toggle(): void {
for (let i = 0; i < cheatsheetVariants.instances.length; i++) { cheatsheetLoader.active = !cheatsheetLoader.active
let panelWindow = cheatsheetVariants.instances[i];
if (panelWindow.modelData.name == Hyprland.focusedMonitor.name) {
panelWindow.visible = !panelWindow.visible;
if(panelWindow.visible) Notifications.timeoutAll();
}
}
} }
function close(): void { function close(): void {
for (let i = 0; i < cheatsheetVariants.instances.length; i++) { cheatsheetLoader.active = false
let panelWindow = cheatsheetVariants.instances[i];
if (panelWindow.modelData.name == Hyprland.focusedMonitor.name) {
panelWindow.visible = false;
}
}
} }
function open(): void { function open(): void {
for (let i = 0; i < cheatsheetVariants.instances.length; i++) { cheatsheetLoader.active = true
let panelWindow = cheatsheetVariants.instances[i];
if (panelWindow.modelData.name == Hyprland.focusedMonitor.name) {
panelWindow.visible = true;
if(panelWindow.visible) Notifications.timeoutAll();
}
}
} }
} }
GlobalShortcut { GlobalShortcut {
name: "cheatsheetToggle" name: "cheatsheetToggle"
description: "Toggles cheatsheet on press" description: qsTr("Toggles cheatsheet on press")
onPressed: { onPressed: {
for (let i = 0; i < cheatsheetVariants.instances.length; i++) { cheatsheetLoader.active = !cheatsheetLoader.active;
let panelWindow = cheatsheetVariants.instances[i];
if (panelWindow.modelData.name == Hyprland.focusedMonitor.name) {
panelWindow.visible = !panelWindow.visible;
if(panelWindow.visible) Notifications.timeoutAll();
}
}
} }
} }
GlobalShortcut { GlobalShortcut {
name: "cheatsheetOpen" name: "cheatsheetOpen"
description: "Opens cheatsheet on press" description: qsTr("Opens cheatsheet on press")
onPressed: { onPressed: {
for (let i = 0; i < cheatsheetVariants.instances.length; i++) { cheatsheetLoader.active = true;
let panelWindow = cheatsheetVariants.instances[i];
if (panelWindow.modelData.name == Hyprland.focusedMonitor.name) {
panelWindow.visible = true;
if(panelWindow.visible) Notifications.timeoutAll();
}
}
} }
} }
GlobalShortcut { GlobalShortcut {
name: "cheatsheetClose" name: "cheatsheetClose"
description: "Closes cheatsheet on press" description: qsTr("Closes cheatsheet on press")
onPressed: { onPressed: {
for (let i = 0; i < cheatsheetVariants.instances.length; i++) { cheatsheetLoader.active = false;
let panelWindow = cheatsheetVariants.instances[i];
if (panelWindow.modelData.name == Hyprland.focusedMonitor.name) {
panelWindow.visible = false;
}
}
} }
} }
@@ -1,9 +1,11 @@
import QtQuick import QtQuick
import Quickshell import Quickshell
import "root:/modules/common/functions/color_utils.js" as ColorUtils
pragma Singleton pragma Singleton
pragma ComponentBehavior: Bound pragma ComponentBehavior: Bound
Singleton { Singleton {
id: root
property QtObject m3colors property QtObject m3colors
property QtObject animation property QtObject animation
property QtObject animationCurves property QtObject animationCurves
@@ -13,18 +15,6 @@ Singleton {
property QtObject sizes property QtObject sizes
property string syntaxHighlightingTheme property string syntaxHighlightingTheme
function mix(color1, color2, percentage) {
var c1 = Qt.color(color1);
var c2 = Qt.color(color2);
return Qt.rgba(percentage * c1.r + (1 - percentage) * c2.r, percentage * c1.g + (1 - percentage) * c2.g, percentage * c1.b + (1 - percentage) * c2.b, percentage * c1.a + (1 - percentage) * c2.a);
}
// Transparentize
function transparentize(color, percentage) {
var c = Qt.color(color);
return Qt.rgba(c.r, c.g, c.b, c.a * (1 - percentage));
}
m3colors: QtObject { m3colors: QtObject {
property bool darkmode: false property bool darkmode: false
property bool transparent: false property bool transparent: false
@@ -108,37 +98,37 @@ Singleton {
property color colSubtext: m3colors.m3outline property color colSubtext: m3colors.m3outline
property color colLayer0: m3colors.m3background property color colLayer0: m3colors.m3background
property color colOnLayer0: m3colors.m3onBackground property color colOnLayer0: m3colors.m3onBackground
property color colLayer0Hover: mix(colLayer0, colOnLayer0, 0.9) property color colLayer0Hover: ColorUtils.mix(colLayer0, colOnLayer0, 0.9)
property color colLayer0Active: mix(colLayer0, colOnLayer0, 0.8) property color colLayer0Active: ColorUtils.mix(colLayer0, colOnLayer0, 0.8)
property color colLayer1: m3colors.m3surfaceContainerLow; property color colLayer1: m3colors.m3surfaceContainerLow;
property color colOnLayer1: m3colors.m3onSurfaceVariant; property color colOnLayer1: m3colors.m3onSurfaceVariant;
property color colOnLayer1Inactive: mix(colOnLayer1, colLayer1, 0.45); property color colOnLayer1Inactive: ColorUtils.mix(colOnLayer1, colLayer1, 0.45);
property color colLayer2: mix(m3colors.m3surfaceContainer, m3colors.m3surfaceContainerHigh, 0.55); property color colLayer2: ColorUtils.mix(m3colors.m3surfaceContainer, m3colors.m3surfaceContainerHigh, 0.55);
property color colOnLayer2: m3colors.m3onSurface; property color colOnLayer2: m3colors.m3onSurface;
property color colOnLayer2Disabled: mix(colOnLayer2, m3colors.m3background, 0.4); property color colOnLayer2Disabled: ColorUtils.mix(colOnLayer2, m3colors.m3background, 0.4);
property color colLayer3: mix(m3colors.m3surfaceContainerHigh, m3colors.m3onSurface, 0.96); property color colLayer3: ColorUtils.mix(m3colors.m3surfaceContainerHigh, m3colors.m3onSurface, 0.96);
property color colOnLayer3: m3colors.m3onSurface; property color colOnLayer3: m3colors.m3onSurface;
property color colLayer1Hover: mix(colLayer1, colOnLayer1, 0.88); property color colLayer1Hover: ColorUtils.mix(colLayer1, colOnLayer1, 0.92);
property color colLayer1Active: mix(colLayer1, colOnLayer1, 0.77); property color colLayer1Active: ColorUtils.mix(colLayer1, colOnLayer1, 0.85);
property color colLayer2Hover: mix(colLayer2, colOnLayer2, 0.90); property color colLayer2Hover: ColorUtils.mix(colLayer2, colOnLayer2, 0.90);
property color colLayer2Active: mix(colLayer2, colOnLayer2, 0.80); property color colLayer2Active: ColorUtils.mix(colLayer2, colOnLayer2, 0.80);
property color colLayer2Disabled: mix(colLayer2, m3colors.m3background, 0.8); property color colLayer2Disabled: ColorUtils.mix(colLayer2, m3colors.m3background, 0.8);
property color colLayer3Hover: mix(colLayer3, colOnLayer3, 0.90); property color colLayer3Hover: ColorUtils.mix(colLayer3, colOnLayer3, 0.90);
property color colLayer3Active: mix(colLayer3, colOnLayer3, 0.80); property color colLayer3Active: ColorUtils.mix(colLayer3, colOnLayer3, 0.80);
property color colPrimaryHover: mix(m3colors.m3primary, colLayer1Hover, 0.85) property color colPrimaryHover: ColorUtils.mix(m3colors.m3primary, colLayer1Hover, 0.85)
property color colPrimaryActive: mix(m3colors.m3primary, colLayer1Active, 0.7) property color colPrimaryActive: ColorUtils.mix(m3colors.m3primary, colLayer1Active, 0.7)
property color colPrimaryContainerHover: mix(m3colors.m3primaryContainer, colLayer1Hover, 0.7) property color colPrimaryContainerHover: ColorUtils.mix(m3colors.m3primaryContainer, colLayer1Hover, 0.7)
property color colPrimaryContainerActive: mix(m3colors.m3primaryContainer, colLayer1Active, 0.6) property color colPrimaryContainerActive: ColorUtils.mix(m3colors.m3primaryContainer, colLayer1Active, 0.6)
property color colSecondaryHover: mix(m3colors.m3secondary, colLayer1Hover, 0.85) property color colSecondaryHover: ColorUtils.mix(m3colors.m3secondary, colLayer1Hover, 0.85)
property color colSecondaryActive: mix(m3colors.m3secondary, colLayer1Active, 0.4) property color colSecondaryActive: ColorUtils.mix(m3colors.m3secondary, colLayer1Active, 0.4)
property color colSecondaryContainerHover: mix(m3colors.m3secondaryContainer, colLayer1Hover, 0.6) property color colSecondaryContainerHover: ColorUtils.mix(m3colors.m3secondaryContainer, colLayer1Hover, 0.6)
property color colSecondaryContainerActive: mix(m3colors.m3secondaryContainer, colLayer1Active, 0.54) property color colSecondaryContainerActive: ColorUtils.mix(m3colors.m3secondaryContainer, colLayer1Active, 0.54)
property color colSurfaceContainerHighestHover: mix(m3colors.m3surfaceContainerHighest, m3colors.m3onSurface, 0.95) property color colSurfaceContainerHighestHover: ColorUtils.mix(m3colors.m3surfaceContainerHighest, m3colors.m3onSurface, 0.95)
property color colSurfaceContainerHighestActive: mix(m3colors.m3surfaceContainerHighest, m3colors.m3onSurface, 0.85) property color colSurfaceContainerHighestActive: ColorUtils.mix(m3colors.m3surfaceContainerHighest, m3colors.m3onSurface, 0.85)
property color colTooltip: "#3C4043" // m3colors.m3inverseSurface in the specs, but the m3 website actually uses this color property color colTooltip: "#3C4043" // m3colors.m3inverseSurface in the specs, but the m3 website actually uses this color
property color colOnTooltip: "#F8F9FA" // m3colors.m3inverseOnSurface in the specs, but the m3 website actually uses this color property color colOnTooltip: "#F8F9FA" // m3colors.m3inverseOnSurface in the specs, but the m3 website actually uses this color
property color colScrim: transparentize(m3colors.m3scrim, 0.5) property color colScrim: ColorUtils.transparentize(m3colors.m3scrim, 0.5)
property color colShadow: transparentize(m3colors.m3shadow, 0.75) property color colShadow: ColorUtils.transparentize(m3colors.m3shadow, 0.75)
} }
rounding: QtObject { rounding: QtObject {
@@ -189,24 +179,57 @@ Singleton {
property int type: Easing.BezierSpline property int type: Easing.BezierSpline
property list<real> bezierCurve: animationCurves.emphasized property list<real> bezierCurve: animationCurves.emphasized
property int velocity: 650 property int velocity: 650
property Component numberAnimation: Component {
NumberAnimation {
duration: root.animation.elementMove.duration
easing.type: root.animation.elementMove.type
easing.bezierCurve: root.animation.elementMove.bezierCurve
}
}
property Component colorAnimation: Component {
ColorAnimation {
duration: root.animation.elementMove.duration
easing.type: root.animation.elementMove.type
easing.bezierCurve: root.animation.elementMove.bezierCurve
}
}
} }
property QtObject elementMoveEnter: QtObject { property QtObject elementMoveEnter: QtObject {
property int duration: 400 property int duration: 400
property int type: Easing.BezierSpline property int type: Easing.BezierSpline
property list<real> bezierCurve: animationCurves.emphasizedDecel property list<real> bezierCurve: animationCurves.emphasizedDecel
property int velocity: 650 property int velocity: 650
property Component numberAnimation: Component {
NumberAnimation {
duration: root.animation.elementMoveEnter.duration
easing.type: root.animation.elementMoveEnter.type
easing.bezierCurve: root.animation.elementMoveEnter.bezierCurve
}
}
} }
property QtObject elementMoveExit: QtObject { property QtObject elementMoveExit: QtObject {
property int duration: 200 property int duration: 200
property int type: Easing.BezierSpline property int type: Easing.BezierSpline
property list<real> bezierCurve: animationCurves.emphasizedAccel property list<real> bezierCurve: animationCurves.emphasizedAccel
property int velocity: 650 property int velocity: 650
property Component numberAnimation: Component {
NumberAnimation {
duration: root.animation.elementMoveExit.duration
easing.type: root.animation.elementMoveExit.type
easing.bezierCurve: root.animation.elementMoveExit.bezierCurve
}
}
} }
property QtObject elementMoveFast: QtObject { property QtObject elementMoveFast: QtObject {
property int duration: 200 property int duration: 200
property int type: Easing.BezierSpline property int type: Easing.BezierSpline
property list<real> bezierCurve: animationCurves.standardDecel property list<real> bezierCurve: animationCurves.standardDecel
property int velocity: 850 property int velocity: 850
property Component colorAnimation: Component {ColorAnimation {
duration: root.animation.elementMoveFast.duration
easing.type: root.animation.elementMoveFast.type
easing.bezierCurve: root.animation.elementMoveFast.bezierCurve
}}
} }
property QtObject scroll: QtObject { property QtObject scroll: QtObject {
property int duration: 400 property int duration: 400
@@ -226,18 +249,21 @@ Singleton {
} }
sizes: QtObject { sizes: QtObject {
property int barHeight: 40 property real barHeight: 40
property int barCenterSideModuleWidth: 360 property real barCenterSideModuleWidth: 360
property int barPreferredSideSectionWidth: 400 property real barPreferredSideSectionWidth: 400
property int sidebarWidth: 450 property real sidebarWidth: 460
property int sidebarWidthExtended: 750 property real sidebarWidthExtended: 750
property int notificationPopupWidth: 410 property real osdWidth: 200
property int searchWidthCollapsed: 260 property real mediaControlsWidth: 440
property int searchWidth: 450 property real mediaControlsHeight: 160
property int hyprlandGapsOut: 5 property real notificationPopupWidth: 410
property int elevationMargin: 7 property real searchWidthCollapsed: 260
property int fabShadowRadius: 5 property real searchWidth: 450
property int fabHoveredShadowRadius: 7 property real hyprlandGapsOut: 5
property real elevationMargin: 8
property real fabShadowRadius: 5
property real fabHoveredShadowRadius: 7
} }
syntaxHighlightingTheme: Appearance.m3colors.darkmode ? "Monokai" : "ayu Light" syntaxHighlightingTheme: Appearance.m3colors.darkmode ? "Monokai" : "ayu Light"
@@ -5,7 +5,7 @@ pragma ComponentBehavior: Bound
Singleton { Singleton {
property QtObject ai: QtObject { property QtObject ai: QtObject {
property string systemPrompt: "Use casual tone. No user knowledge is to be assumed except basic Linux literacy. Be brief and concise: When explaining concepts, use bullet points (prefer minus sign (-) over asterisk (*)) and highlight keywords in bold to pinpoint the main concepts instead of long paragraphs. You are also encouraged to split your response with h2 headers, each header title beginning with an emoji, like `## 🐧 Linux`." property string systemPrompt: qsTr("Use casual tone. No user knowledge is to be assumed except basic Linux literacy. Be brief and concise: When explaining concepts, use bullet points (prefer minus sign (-) over asterisk (*)) and highlight keywords in bold to pinpoint the main concepts instead of long paragraphs. You are also encouraged to split your response with h2 headers, each header title beginning with an emoji, like `## 🐧 Linux`.")
} }
property QtObject appearance: QtObject { property QtObject appearance: QtObject {
@@ -13,10 +13,10 @@ Singleton {
} }
property QtObject apps: QtObject { property QtObject apps: QtObject {
property string bluetooth: "blueberry" property string bluetooth: "better-control --bluetooth"
property string imageViewer: "loupe" property string imageViewer: "loupe"
property string network: "XDG_CURRENT_DESKTOP=\"gnome\" gnome-control-center wifi" property string network: "better-control --wifi"
property string settings: "XDG_CURRENT_DESKTOP=\"gnome\" gnome-control-center" property string settings: "better-control"
property string taskManager: "gnome-usage" property string taskManager: "gnome-usage"
property string terminal: "foot" // This is only for shell actions property string terminal: "foot" // This is only for shell actions
} }
@@ -25,6 +25,7 @@ Singleton {
property int batteryLowThreshold: 20 property int batteryLowThreshold: 20
property string topLeftIcon: "spark" // Options: distro, spark property string topLeftIcon: "spark" // Options: distro, spark
property bool showBackground: true property bool showBackground: true
property bool borderless: false
property QtObject resources: QtObject { property QtObject resources: QtObject {
property bool alwaysShowSwap: true property bool alwaysShowSwap: true
property bool alwaysShowCpu: false property bool alwaysShowCpu: false
@@ -70,7 +71,7 @@ Singleton {
property string defaultProvider: "yandere" property string defaultProvider: "yandere"
property int limit: 20 property int limit: 20
property QtObject zerochan: QtObject { property QtObject zerochan: QtObject {
property string username: "" property string username: "[unset]"
} }
} }
} }
@@ -9,12 +9,6 @@ Singleton {
} }
property QtObject sidebar: QtObject { property QtObject sidebar: QtObject {
property QtObject leftSide: QtObject {
property int selectedTab: 0
}
property QtObject centerGroup: QtObject {
property int selectedTab: 0
}
property QtObject bottomGroup: QtObject { property QtObject bottomGroup: QtObject {
property bool collapsed: false property bool collapsed: false
property int selectedTab: 0 property int selectedTab: 0
@@ -0,0 +1,85 @@
// This module provides high level utility functions for color manipulation.
/**
* Returns a color with the hue of color2 and the saturation, value, and alpha of color1.
*
* @param {string} color1 - The base color (any Qt.color-compatible string).
* @param {string} color2 - The color to take hue from.
* @returns {Qt.rgba} The resulting color.
*/
function colorWithHueOf(color1, color2) {
var c1 = Qt.color(color1);
var c2 = Qt.color(color2);
// Qt.color hsvHue/hsvSaturation/hsvValue/alpha return 0-1
var hue = c2.hsvHue;
var sat = c1.hsvSaturation;
var val = c1.hsvValue;
var alpha = c1.a;
return Qt.hsva(hue, sat, val, alpha);
}
/**
* Returns a color with the saturation of color2 and the hue/value/alpha of color1.
*
* @param {string} color1 - The base color (any Qt.color-compatible string).
* @param {string} color2 - The color to take saturation from.
* @returns {Qt.rgba} The resulting color.
*/
function colorWithSaturationOf(color1, color2) {
var c1 = Qt.color(color1);
var c2 = Qt.color(color2);
var hue = c1.hsvHue;
var sat = c2.hsvSaturation;
var val = c1.hsvValue;
var alpha = c1.a;
return Qt.hsva(hue, sat, val, alpha);
}
/**
* Adapts color1 to the accent (hue and saturation) of color2 using HSL, keeping lightness and alpha from color1.
*
* @param {string} color1 - The base color (any Qt.color-compatible string).
* @param {string} color2 - The accent color.
* @returns {Qt.rgba} The resulting color.
*/
function adaptToAccent(color1, color2) {
var c1 = Qt.color(color1);
var c2 = Qt.color(color2);
var hue = c2.hslHue;
var sat = c2.hslSaturation;
var light = c1.hslLightness;
var alpha = c1.a;
return Qt.hsla(hue, sat, light, alpha);
}
/**
* Mixes two colors by a given percentage.
*
* @param {string} color1 - The first color (any Qt.color-compatible string).
* @param {string} color2 - The second color.
* @param {number} percentage - The mix ratio (0-1). 1 = all color1, 0 = all color2.
* @returns {Qt.rgba} The resulting mixed color.
*/
function mix(color1, color2, percentage) {
var c1 = Qt.color(color1);
var c2 = Qt.color(color2);
return Qt.rgba(percentage * c1.r + (1 - percentage) * c2.r, percentage * c1.g + (1 - percentage) * c2.g, percentage * c1.b + (1 - percentage) * c2.b, percentage * c1.a + (1 - percentage) * c2.a);
}
/**
* Transparentizes a color by a given percentage.
*
* @param {string} color - The color (any Qt.color-compatible string).
* @param {number} percentage - The amount to transparentize (0-1).
* @returns {Qt.rgba} The resulting color.
*/
function transparentize(color, percentage) {
var c = Qt.color(color);
return Qt.rgba(c.r, c.g, c.b, c.a * (1 - percentage));
}
@@ -147,4 +147,32 @@ function wordWrap(str, maxLen) {
} }
if (current.length > 0) lines.push(current); if (current.length > 0) lines.push(current);
return lines.join("\n"); return lines.join("\n");
}
function cleanMusicTitle(title) {
if (!title) return "";
// Brackets
title = title.replace(/^ *\([^)]*\) */g, " "); // Round brackets
title = title.replace(/^ *\[[^\]]*\] */g, " "); // Square brackets
title = title.replace(/^ *\{[^\}]*\} */g, " "); // Curly brackets
// Japenis brackets
title = title.replace(/^ *【[^】]*】/, "") // Touhou
title = title.replace(/^ *《[^》]*》/, "") // ??
title = title.replace(/^ *「[^」]*」/, "") // OP/ED
title = title.replace(/^ *『[^』]*』/, "") // OP/ED
return title;
}
function friendlyTimeForSeconds(seconds) {
if (isNaN(seconds) || seconds < 0) return "0:00";
seconds = Math.floor(seconds);
const h = Math.floor(seconds / 3600);
const m = Math.floor((seconds % 3600) / 60);
const s = seconds % 60;
if (h > 0) {
return `${h}:${m.toString().padStart(2, '0')}:${s.toString().padStart(2, '0')}`;
} else {
return `${m}:${s.toString().padStart(2, '0')}`;
}
} }
@@ -1,4 +1,5 @@
import "root:/modules/common" import "root:/modules/common"
import "root:/modules/common/functions/color_utils.js" as ColorUtils
import QtQuick import QtQuick
import QtQuick.Controls import QtQuick.Controls
import QtQuick.Layouts import QtQuick.Layouts
@@ -17,14 +18,12 @@ Button {
background: Rectangle { background: Rectangle {
anchors.fill: parent anchors.fill: parent
radius: Appearance.rounding.full radius: Appearance.rounding.full
color: (button.down && button.enabled) ? Appearance.colors.colLayer1Active : ((button.hovered && button.enabled) ? Appearance.colors.colLayer1Hover : Appearance.transparentize(Appearance.m3colors.m3surfaceContainerHigh, 1)) color: (button.down && button.enabled) ? Appearance.colors.colLayer1Active :
((button.hovered && button.enabled) ? Appearance.colors.colLayer1Hover :
ColorUtils.transparentize(Appearance.m3colors.m3surfaceContainerHigh, 1))
Behavior on color { Behavior on color {
ColorAnimation { animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this)
duration: Appearance.animation.elementMove.duration
easing.type: Appearance.animation.elementMove.type
easing.bezierCurve: Appearance.animation.elementMove.bezierCurve
}
} }
@@ -41,11 +40,7 @@ Button {
color: button.enabled ? Appearance.m3colors.m3primary : Appearance.m3colors.m3outline color: button.enabled ? Appearance.m3colors.m3primary : Appearance.m3colors.m3outline
Behavior on color { Behavior on color {
ColorAnimation { animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this)
duration: Appearance.animation.elementMove.duration
easing.type: Appearance.animation.elementMove.type
easing.bezierCurve: Appearance.animation.elementMove.bezierCurve
}
} }
} }
@@ -1,4 +1,5 @@
import "root:/modules/common" import "root:/modules/common"
import "root:/modules/common/functions/color_utils.js" as ColorUtils
import QtQuick import QtQuick
import QtQuick.Controls import QtQuick.Controls
import QtQuick.Layouts import QtQuick.Layouts
@@ -16,17 +17,12 @@ Button {
background: Rectangle { background: Rectangle {
anchors.fill: parent anchors.fill: parent
color: (button.down && button.enabled) ? Appearance.transparentize(Appearance.m3colors.m3onSurface, 0.84) : color: (button.down && button.enabled) ? ColorUtils.transparentize(Appearance.m3colors.m3onSurface, 0.84) :
((button.hovered && button.enabled) ? Appearance.transparentize(Appearance.m3colors.m3onSurface, 0.92) : ((button.hovered && button.enabled) ? ColorUtils.transparentize(Appearance.m3colors.m3onSurface, 0.92) :
Appearance.transparentize(Appearance.m3colors.m3onSurface, 1)) ColorUtils.transparentize(Appearance.m3colors.m3onSurface, 1))
Behavior on color { Behavior on color {
ColorAnimation { animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this)
duration: Appearance.animation.elementMove.duration
easing.type: Appearance.animation.elementMove.type
easing.bezierCurve: Appearance.animation.elementMove.bezierCurve
}
} }
} }
@@ -42,11 +38,7 @@ Button {
color: button.enabled ? Appearance.m3colors.m3onSurface : Appearance.m3colors.m3outline color: button.enabled ? Appearance.m3colors.m3onSurface : Appearance.m3colors.m3outline
Behavior on color { Behavior on color {
ColorAnimation { animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this)
duration: Appearance.animation.elementMove.duration
easing.type: Appearance.animation.elementMove.type
easing.bezierCurve: Appearance.animation.elementMove.bezierCurve
}
} }
} }
@@ -1,5 +1,6 @@
import "root:/modules/common" import "root:/modules/common"
import "root:/modules/common/widgets" import "root:/modules/common/widgets"
import "root:/modules/common/functions/color_utils.js" as ColorUtils
import QtQuick import QtQuick
import QtQuick.Controls import QtQuick.Controls
import QtQuick.Layouts import QtQuick.Layouts
@@ -16,7 +17,7 @@ Button {
implicitHeight: columnLayout.implicitHeight implicitHeight: columnLayout.implicitHeight
implicitWidth: columnLayout.implicitWidth implicitWidth: columnLayout.implicitWidth
background: Item {} background: null
PointingHandInteraction {} PointingHandInteraction {}
// Real stuff // Real stuff
@@ -30,14 +31,10 @@ Button {
radius: Appearance.rounding.full radius: Appearance.rounding.full
color: toggled ? color: toggled ?
(button.down ? Appearance.colors.colSecondaryContainerActive : button.hovered ? Appearance.colors.colSecondaryContainerHover : Appearance.m3colors.m3secondaryContainer) : (button.down ? Appearance.colors.colSecondaryContainerActive : button.hovered ? Appearance.colors.colSecondaryContainerHover : Appearance.m3colors.m3secondaryContainer) :
(button.down ? Appearance.colors.colLayer1Active : button.hovered ? Appearance.colors.colLayer1Hover : Appearance.transparentize(Appearance.colors.colLayer1Hover, 1)) (button.down ? Appearance.colors.colLayer1Active : button.hovered ? Appearance.colors.colLayer1Hover : ColorUtils.transparentize(Appearance.colors.colLayer1Hover, 1))
Behavior on color { Behavior on color {
ColorAnimation { animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this)
duration: Appearance.animation.elementMove.duration
easing.type: Appearance.animation.elementMove.type
easing.bezierCurve: Appearance.animation.elementMove.bezierCurve
}
} }
MaterialSymbol { MaterialSymbol {
id: navRailButtonIcon id: navRailButtonIcon
@@ -48,11 +45,7 @@ Button {
color: toggled ? Appearance.m3colors.m3onSecondaryContainer : Appearance.colors.colOnLayer1 color: toggled ? Appearance.m3colors.m3onSecondaryContainer : Appearance.colors.colOnLayer1
Behavior on color { Behavior on color {
ColorAnimation { animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this)
duration: Appearance.animation.elementMove.duration
easing.type: Appearance.animation.elementMove.type
easing.bezierCurve: Appearance.animation.elementMove.bezierCurve
}
} }
} }
} }
@@ -1,9 +1,11 @@
import "root:/modules/common" import "root:/modules/common"
import "root:/services" import "root:/services"
import "root:/modules/common/functions/string_utils.js" as StringUtils import "root:/modules/common/functions/string_utils.js" as StringUtils
import "root:/modules/common/functions/color_utils.js" as ColorUtils
import Qt5Compat.GraphicalEffects import Qt5Compat.GraphicalEffects
import QtQuick import QtQuick
import QtQuick.Controls import QtQuick.Controls
import QtQuick.Effects
import QtQuick.Layouts import QtQuick.Layouts
import Quickshell import Quickshell
import Quickshell.Io import Quickshell.Io
@@ -126,7 +128,7 @@ Item {
onPressAndHold: (mouse) => { onPressAndHold: (mouse) => {
if (mouse.button === Qt.LeftButton) { if (mouse.button === Qt.LeftButton) {
Hyprland.dispatch(`exec wl-copy '${StringUtils.shellSingleQuoteEscape(notificationObject.body)}'`) Hyprland.dispatch(`exec wl-copy '${StringUtils.shellSingleQuoteEscape(notificationObject.body)}'`)
notificationSummaryText.text = `${notificationObject.summary} (copied)` notificationSummaryText.text = String.format(qsTr("{0} (copied)"), notificationObject.summary)
} }
} }
onDragStartedChanged: () => { onDragStartedChanged: () => {
@@ -187,9 +189,19 @@ Item {
height: notificationColumnLayout.implicitHeight height: notificationColumnLayout.implicitHeight
color: (notificationObject.urgency == NotificationUrgency.Critical) ? color: (notificationObject.urgency == NotificationUrgency.Critical) ?
Appearance.mix(Appearance.m3colors.m3secondaryContainer, Appearance.colors.colLayer2, 0.35) : Appearance.colors.colLayer2 ColorUtils.mix(Appearance.m3colors.m3secondaryContainer, Appearance.colors.colLayer2, 0.35) : Appearance.colors.colLayer2
radius: Appearance.rounding.normal radius: Appearance.rounding.normal
layer.enabled: true
layer.effect: MultiEffect {
source: notificationBackground
anchors.fill: notificationBackground
shadowEnabled: popup
shadowColor: Appearance.colors.colShadow
shadowVerticalOffset: 1
shadowBlur: 0.5
}
Behavior on x { Behavior on x {
enabled: enableAnimation enabled: enableAnimation
NumberAnimation { NumberAnimation {
@@ -207,20 +219,6 @@ Item {
} }
} }
} }
Loader {
active: popup
anchors.fill: notificationBackground
sourceComponent: DropShadow {
id: notificationShadow
source: notificationBackground
radius: 5
samples: radius * 2 + 1
color: Appearance.colors.colShadow
verticalOffset: 2
horizontalOffset: 0
}
}
} }
@@ -289,7 +287,7 @@ Item {
} }
anchors.fill: parent anchors.fill: parent
color: (notificationObject.urgency == NotificationUrgency.Critical) ? color: (notificationObject.urgency == NotificationUrgency.Critical) ?
Appearance.mix(Appearance.m3colors.m3onSecondary, Appearance.m3colors.m3onSecondaryContainer, 0.1) : ColorUtils.mix(Appearance.m3colors.m3onSecondary, Appearance.m3colors.m3onSecondaryContainer, 0.1) :
Appearance.m3colors.m3onSecondaryContainer Appearance.m3colors.m3onSecondaryContainer
iconSize: 27 iconSize: 27
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
@@ -422,17 +420,11 @@ Item {
background: Rectangle { background: Rectangle {
anchors.fill: parent anchors.fill: parent
radius: Appearance.rounding.full radius: Appearance.rounding.full
color: (expandButton.down) ? Appearance.colors.colLayer2Active : (expandButton.hovered ? Appearance.colors.colLayer2Hover : Appearance.transparentize(Appearance.colors.colLayer2, 1)) color: (expandButton.down) ? Appearance.colors.colLayer2Active : (expandButton.hovered ? Appearance.colors.colLayer2Hover : ColorUtils.transparentize(Appearance.colors.colLayer2, 1))
Behavior on color { Behavior on color {
ColorAnimation { animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this)
duration: Appearance.animation.elementMoveFast.duration
easing.type: Appearance.animation.elementMoveFast.type
easing.bezierCurve: Appearance.animation.elementMoveFast.bezierCurve
}
} }
} }
contentItem: MaterialSymbol { contentItem: MaterialSymbol {
@@ -52,38 +52,31 @@ ColumnLayout {
root.enableIndicatorAnimation = true root.enableIndicatorAnimation = true
} }
} }
Rectangle { Rectangle {
id: indicator
property int tabCount: root.tabButtonList.length
property real fullTabSize: root.width / tabCount;
property real targetWidth: tabBar.contentItem.children[0].children[tabBar.currentIndex].tabContentWidth
implicitWidth: targetWidth
anchors {
top: parent.top
bottom: parent.bottom
}
x: tabBar.currentIndex * fullTabSize + (fullTabSize - targetWidth) / 2
color: Appearance.m3colors.m3primary color: Appearance.m3colors.m3primary
radius: Appearance.rounding.full radius: Appearance.rounding.full
z: 2
anchors.fill: parent Behavior on x {
// TODO: make the end point in the moving direction go first animation: Appearance.animation.elementMove.numberAnimation.createObject(this)
anchors.leftMargin: {
const tabCount = root.tabButtonList.length
const targetWidth = tabBar.contentItem.children[0].children[tabBar.currentIndex].tabContentWidth
const fullTabSize = root.width / tabCount;
return fullTabSize * root.externalTrackedTab + (fullTabSize - targetWidth) / 2;
} }
anchors.rightMargin: {
const tabCount = root.tabButtonList.length Behavior on implicitWidth {
const targetWidth = tabBar.contentItem.children[0].children[tabBar.currentIndex].tabContentWidth animation: Appearance.animation.elementMove.numberAnimation.createObject(this)
const fullTabSize = root.width / tabCount;
return fullTabSize * (tabCount - root.externalTrackedTab - 1) + (fullTabSize - targetWidth) / 2;
} }
Behavior on anchors.leftMargin {
enabled: root.enableIndicatorAnimation
SmoothedAnimation {
velocity: Appearance.animation.positionShift.velocity
}
}
Behavior on anchors.rightMargin {
enabled: root.enableIndicatorAnimation
SmoothedAnimation {
velocity: Appearance.animation.positionShift.velocity
}
}
} }
} }
@@ -1,5 +1,7 @@
import "root:/modules/common" import "root:/modules/common"
import "root:/modules/common/widgets" import "root:/modules/common/widgets"
import "root:/modules/common/functions/color_utils.js" as ColorUtils
import Qt5Compat.GraphicalEffects
import QtQuick import QtQuick
import QtQuick.Controls import QtQuick.Controls
import QtQuick.Layouts import QtQuick.Layouts
@@ -12,24 +14,108 @@ TabButton {
property string buttonIcon property string buttonIcon
property bool selected: false property bool selected: false
property int tabContentWidth: contentItem.children[0].implicitWidth property int tabContentWidth: contentItem.children[0].implicitWidth
property int rippleDuration: 1200
height: buttonBackground.height height: buttonBackground.height
PointingHandInteraction {} component RippleAnim: NumberAnimation {
duration: rippleDuration
easing.type: Appearance.animation.elementMoveEnter.type
easing.bezierCurve: Appearance.animationCurves.standardDecel
}
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onPressed: (event) => {
const {x,y} = event
const stateY = buttonBackground.y;
rippleAnim.x = x;
rippleAnim.y = y - stateY;
const dist = (ox,oy) => ox*ox + oy*oy
const stateEndY = stateY + buttonBackground.height
rippleAnim.radius = Math.sqrt(Math.max(dist(0, stateY), dist(0, stateEndY), dist(width, stateY), dist(width, stateEndY)))
rippleFadeAnim.complete();
rippleAnim.restart();
}
onReleased: (event) => {
button.click() // Because the MouseArea already consumed the event
rippleFadeAnim.restart();
}
}
RippleAnim {
id: rippleFadeAnim
target: ripple
property: "opacity"
to: 0
}
SequentialAnimation {
id: rippleAnim
property real x
property real y
property real radius
PropertyAction {
target: ripple
property: "x"
value: rippleAnim.x
}
PropertyAction {
target: ripple
property: "y"
value: rippleAnim.y
}
PropertyAction {
target: ripple
property: "opacity"
value: 1
}
ParallelAnimation {
RippleAnim {
target: ripple
properties: "implicitWidth,implicitHeight"
from: 0
to: rippleAnim.radius * 2
}
}
}
background: Rectangle { background: Rectangle {
id: buttonBackground id: buttonBackground
radius: Appearance.rounding.small radius: Appearance.rounding.small
implicitHeight: 50 implicitHeight: 50
color: (button.down ? Appearance.colors.colLayer1Active : button.hovered ? Appearance.colors.colLayer1Hover : Appearance.transparentize(Appearance.colors.colLayer1Hover, 1)) color: (button.hovered ? Appearance.colors.colLayer1Hover : ColorUtils.transparentize(Appearance.colors.colLayer1Hover, 1))
layer.enabled: true
layer.effect: OpacityMask {
maskSource: Rectangle {
width: buttonBackground.width
height: buttonBackground.height
radius: buttonBackground.radius
}
}
Behavior on color { Behavior on color {
ColorAnimation { animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this)
duration: Appearance.animation.elementMove.duration }
easing.type: Appearance.animation.elementMove.type
easing.bezierCurve: Appearance.animation.elementMove.bezierCurve Rectangle {
id: ripple
radius: Appearance.rounding.full
color: Appearance.colors.colLayer1Active
opacity: 0
transform: Translate {
x: -ripple.width / 2
y: -ripple.height / 2
} }
} }
} }
contentItem: Item { contentItem: Item {
anchors.centerIn: buttonBackground anchors.centerIn: buttonBackground
ColumnLayout { ColumnLayout {
@@ -44,11 +130,7 @@ TabButton {
fill: selected ? 1 : 0 fill: selected ? 1 : 0
color: selected ? Appearance.m3colors.m3primary : Appearance.colors.colOnLayer1 color: selected ? Appearance.m3colors.m3primary : Appearance.colors.colOnLayer1
Behavior on color { Behavior on color {
ColorAnimation { animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this)
duration: Appearance.animation.elementMove.duration
easing.type: Appearance.animation.elementMove.type
easing.bezierCurve: Appearance.animation.elementMove.bezierCurve
}
} }
} }
StyledText { StyledText {
@@ -59,11 +141,7 @@ TabButton {
color: selected ? Appearance.m3colors.m3primary : Appearance.colors.colOnLayer1 color: selected ? Appearance.m3colors.m3primary : Appearance.colors.colOnLayer1
text: buttonText text: buttonText
Behavior on color { Behavior on color {
ColorAnimation { animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this)
duration: Appearance.animation.elementMove.duration
easing.type: Appearance.animation.elementMove.type
easing.bezierCurve: Appearance.animation.elementMove.bezierCurve
}
} }
} }
} }
@@ -16,18 +16,10 @@ Item {
Behavior on implicitWidth { Behavior on implicitWidth {
enabled: !vertical enabled: !vertical
NumberAnimation { animation: Appearance.animation.elementMoveEnter.numberAnimation.createObject(this)
duration: Appearance.animation.elementMoveEnter.duration
easing.type: Appearance.animation.elementMoveEnter.type
easing.bezierCurve: Appearance.animation.elementMoveEnter.bezierCurve
}
} }
Behavior on implicitHeight { Behavior on implicitHeight {
enabled: vertical enabled: vertical
NumberAnimation { animation: Appearance.animation.elementMoveEnter.numberAnimation.createObject(this)
duration: Appearance.animation.elementMoveEnter.duration
easing.type: Appearance.animation.elementMoveEnter.type
easing.bezierCurve: Appearance.animation.elementMoveEnter.bezierCurve
}
} }
} }
@@ -1,5 +1,7 @@
import "root:/modules/common" import "root:/modules/common"
import "root:/modules/common/widgets" import "root:/modules/common/widgets"
import "root:/modules/common/functions/color_utils.js" as ColorUtils
import Qt5Compat.GraphicalEffects
import QtQuick import QtQuick
import QtQuick.Controls import QtQuick.Controls
import QtQuick.Layouts import QtQuick.Layouts
@@ -11,25 +13,111 @@ TabButton {
property string buttonText property string buttonText
property string buttonIcon property string buttonIcon
property bool selected: false property bool selected: false
property int rippleDuration: 1200
height: buttonBackground.height height: buttonBackground.height
property int tabContentWidth: buttonBackground.width - buttonBackground.radius*2 property int tabContentWidth: buttonBackground.width - buttonBackground.radius*2
PointingHandInteraction {} PointingHandInteraction {}
component RippleAnim: NumberAnimation {
duration: rippleDuration
easing.type: Appearance.animation.elementMoveEnter.type
easing.bezierCurve: Appearance.animationCurves.standardDecel
}
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onPressed: (event) => {
const {x,y} = event
const stateY = buttonBackground.y;
rippleAnim.x = x;
rippleAnim.y = y - stateY;
const dist = (ox,oy) => ox*ox + oy*oy
const stateEndY = stateY + buttonBackground.height
rippleAnim.radius = Math.sqrt(Math.max(dist(0, stateY), dist(0, stateEndY), dist(width, stateY), dist(width, stateEndY)))
rippleFadeAnim.complete();
rippleAnim.restart();
}
onReleased: (event) => {
button.click() // Because the MouseArea already consumed the event
rippleFadeAnim.restart();
}
}
RippleAnim {
id: rippleFadeAnim
target: ripple
property: "opacity"
to: 0
}
SequentialAnimation {
id: rippleAnim
property real x
property real y
property real radius
PropertyAction {
target: ripple
property: "x"
value: rippleAnim.x
}
PropertyAction {
target: ripple
property: "y"
value: rippleAnim.y
}
PropertyAction {
target: ripple
property: "opacity"
value: 1
}
ParallelAnimation {
RippleAnim {
target: ripple
properties: "implicitWidth,implicitHeight"
from: 0
to: rippleAnim.radius * 2
}
}
}
background: Rectangle { background: Rectangle {
id: buttonBackground id: buttonBackground
radius: Appearance.rounding.small radius: Appearance.rounding.small
implicitHeight: 37 implicitHeight: 37
color: (button.down ? Appearance.colors.colLayer1Active : button.hovered ? Appearance.colors.colLayer1Hover : Appearance.transparentize(Appearance.colors.colLayer1Hover, 1)) color: (button.hovered ? Appearance.colors.colLayer1Hover : ColorUtils.transparentize(Appearance.colors.colLayer1Hover, 1))
layer.enabled: true
layer.effect: OpacityMask {
maskSource: Rectangle {
width: buttonBackground.width
height: buttonBackground.height
radius: buttonBackground.radius
}
}
Behavior on color { Behavior on color {
ColorAnimation { animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this)
duration: Appearance.animation.elementMove.duration }
easing.type: Appearance.animation.elementMove.type
easing.bezierCurve: Appearance.animation.elementMove.bezierCurve Rectangle {
id: ripple
radius: Appearance.rounding.full
color: Appearance.colors.colLayer1Active
opacity: 0
transform: Translate {
x: -ripple.width / 2
y: -ripple.height / 2
} }
} }
} }
contentItem: Item { contentItem: Item {
anchors.centerIn: buttonBackground anchors.centerIn: buttonBackground
RowLayout { RowLayout {
@@ -52,11 +140,7 @@ TabButton {
fill: selected ? 1 : 0 fill: selected ? 1 : 0
color: selected ? Appearance.m3colors.m3primary : Appearance.colors.colOnLayer1 color: selected ? Appearance.m3colors.m3primary : Appearance.colors.colOnLayer1
Behavior on color { Behavior on color {
ColorAnimation { animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this)
duration: Appearance.animation.elementMove.duration
easing.type: Appearance.animation.elementMove.type
easing.bezierCurve: Appearance.animation.elementMove.bezierCurve
}
} }
} }
} }
@@ -67,11 +151,7 @@ TabButton {
color: selected ? Appearance.m3colors.m3primary : Appearance.colors.colOnLayer1 color: selected ? Appearance.m3colors.m3primary : Appearance.colors.colOnLayer1
text: buttonText text: buttonText
Behavior on color { Behavior on color {
ColorAnimation { animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this)
duration: Appearance.animation.elementMove.duration
easing.type: Appearance.animation.elementMove.type
easing.bezierCurve: Appearance.animation.elementMove.bezierCurve
}
} }
} }
} }
@@ -13,13 +13,11 @@ ProgressBar {
property real valueBarWidth: 120 property real valueBarWidth: 120
property real valueBarHeight: 4 property real valueBarHeight: 4
property real valueBarGap: 4 property real valueBarGap: 4
property color highlightColor: Appearance.m3colors.m3primary
property color trackColor: Appearance.m3colors.m3secondaryContainer
Behavior on value { Behavior on value {
NumberAnimation { animation: Appearance.animation.elementMoveEnter.numberAnimation.createObject(this)
duration: Appearance.animation.elementMoveEnter.duration
easing.type: Appearance.animation.elementMoveEnter.type
easing.bezierCurve: Appearance.animation.elementMoveEnter.bezierCurve
}
} }
background: Rectangle { background: Rectangle {
@@ -38,21 +36,21 @@ ProgressBar {
width: root.visualPosition * parent.width width: root.visualPosition * parent.width
height: parent.height height: parent.height
radius: Appearance.rounding.full radius: Appearance.rounding.full
color: Appearance.m3colors.m3primary color: root.highlightColor
} }
Rectangle { // Right remaining part fill Rectangle { // Right remaining part fill
anchors.right: parent.right anchors.right: parent.right
width: (1 - root.visualPosition) * parent.width - valueBarGap width: (1 - root.visualPosition) * parent.width - valueBarGap
height: parent.height height: parent.height
radius: Appearance.rounding.full radius: Appearance.rounding.full
color: Appearance.m3colors.m3secondaryContainer color: root.trackColor
} }
Rectangle { // Stop point Rectangle { // Stop point
anchors.right: parent.right anchors.right: parent.right
width: valueBarGap width: valueBarGap
height: valueBarGap height: valueBarGap
radius: Appearance.rounding.full radius: Appearance.rounding.full
color: Appearance.m3colors.m3primary color: root.highlightColor
} }
} }
} }
@@ -16,8 +16,13 @@ Slider {
property real handleWidth: (slider.pressed ? 3 : 5) * scale property real handleWidth: (slider.pressed ? 3 : 5) * scale
property real handleHeight: 44 * scale property real handleHeight: 44 * scale
property real handleLimit: slider.backgroundDotMargins * scale property real handleLimit: slider.backgroundDotMargins * scale
property real trackHeight: 15 * scale
property color highlightColor: Appearance.m3colors.m3primary
property color trackColor: Appearance.m3colors.m3secondaryContainer
property color handleColor: Appearance.m3colors.m3onSecondaryContainer
property real limitedHandleRangeWidth: (slider.availableWidth - handleWidth - slider.handleLimit * 2) property real limitedHandleRangeWidth: (slider.availableWidth - handleWidth - slider.handleLimit * 2)
property string tooltipContent: `${Math.round(value * 100)}%`
Layout.fillWidth: true Layout.fillWidth: true
from: 0 from: 0
to: 1 to: 1
@@ -44,14 +49,15 @@ Slider {
background: Item { background: Item {
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
implicitHeight: 12 // Somehow binding this makes it fill height. Must be set with a constant like this implicitHeight: trackHeight
// Fill left // Fill left
Rectangle { Rectangle {
anchors.verticalCenter: parent.verticalCenter
anchors.left: parent.left anchors.left: parent.left
width: slider.handleLimit + slider.visualPosition * slider.limitedHandleRangeWidth - (slider.handleMargins + slider.handleWidth / 2) width: slider.handleLimit + slider.visualPosition * slider.limitedHandleRangeWidth - (slider.handleMargins + slider.handleWidth / 2)
height: parent.height height: trackHeight
color: Appearance.m3colors.m3primary color: slider.highlightColor
topLeftRadius: Appearance.rounding.full topLeftRadius: Appearance.rounding.full
bottomLeftRadius: Appearance.rounding.full bottomLeftRadius: Appearance.rounding.full
topRightRadius: Appearance.rounding.unsharpen topRightRadius: Appearance.rounding.unsharpen
@@ -60,10 +66,11 @@ Slider {
// Fill right // Fill right
Rectangle { Rectangle {
anchors.verticalCenter: parent.verticalCenter
anchors.right: parent.right anchors.right: parent.right
width: slider.handleLimit + (1 - slider.visualPosition) * slider.limitedHandleRangeWidth - (slider.handleMargins + slider.handleWidth / 2) width: slider.handleLimit + (1 - slider.visualPosition) * slider.limitedHandleRangeWidth - (slider.handleMargins + slider.handleWidth / 2)
height: parent.height height: trackHeight
color: Appearance.m3colors.m3secondaryContainer color: slider.trackColor
topLeftRadius: Appearance.rounding.unsharpen topLeftRadius: Appearance.rounding.unsharpen
bottomLeftRadius: Appearance.rounding.unsharpen bottomLeftRadius: Appearance.rounding.unsharpen
topRightRadius: Appearance.rounding.full topRightRadius: Appearance.rounding.full
@@ -78,7 +85,7 @@ Slider {
width: slider.backgroundDotSize width: slider.backgroundDotSize
height: slider.backgroundDotSize height: slider.backgroundDotSize
radius: Appearance.rounding.full radius: Appearance.rounding.full
color: Appearance.m3colors.m3onSecondaryContainer color: slider.handleColor
} }
} }
@@ -89,7 +96,7 @@ Slider {
implicitWidth: slider.handleWidth implicitWidth: slider.handleWidth
implicitHeight: slider.handleHeight implicitHeight: slider.handleHeight
radius: Appearance.rounding.full radius: Appearance.rounding.full
color: Appearance.m3colors.m3onSecondaryContainer color: slider.handleColor
Behavior on implicitWidth { Behavior on implicitWidth {
NumberAnimation { NumberAnimation {
@@ -101,7 +108,7 @@ Slider {
StyledToolTip { StyledToolTip {
extraVisibleCondition: slider.pressed extraVisibleCondition: slider.pressed
content: `${Math.round(slider.value * 100)}%` content: slider.tooltipContent
} }
} }
} }
@@ -22,18 +22,10 @@ Switch {
border.color: root.checked ? Appearance.m3colors.m3primary : Appearance.m3colors.m3outline border.color: root.checked ? Appearance.m3colors.m3primary : Appearance.m3colors.m3outline
Behavior on color { Behavior on color {
ColorAnimation { animation: Appearance.animation.elementMove.colorAnimation.createObject(this)
duration: Appearance.animation.elementMove.duration
easing.type: Appearance.animation.elementMove.type
easing.bezierCurve: Appearance.animation.elementMove.bezierCurve
}
} }
Behavior on border.color { Behavior on border.color {
ColorAnimation { animation: Appearance.animation.elementMove.colorAnimation.createObject(this)
duration: Appearance.animation.elementMove.duration
easing.type: Appearance.animation.elementMove.type
easing.bezierCurve: Appearance.animation.elementMove.bezierCurve
}
} }
} }
@@ -48,32 +40,16 @@ Switch {
anchors.leftMargin: root.checked ? (root.pressed ? (22 * root.scale) : 24 * root.scale) : (root.pressed ? (2 * root.scale) : 8 * root.scale) anchors.leftMargin: root.checked ? (root.pressed ? (22 * root.scale) : 24 * root.scale) : (root.pressed ? (2 * root.scale) : 8 * root.scale)
Behavior on anchors.leftMargin { Behavior on anchors.leftMargin {
NumberAnimation { animation: Appearance.animation.elementMove.numberAnimation.createObject(this)
duration: Appearance.animation.elementMove.duration
easing.type: Appearance.animation.elementMove.type
easing.bezierCurve: Appearance.animation.elementMove.bezierCurve
}
} }
Behavior on width { Behavior on width {
NumberAnimation { animation: Appearance.animation.elementMove.numberAnimation.createObject(this)
duration: Appearance.animation.elementMove.duration
easing.type: Appearance.animation.elementMove.type
easing.bezierCurve: Appearance.animation.elementMove.bezierCurve
}
} }
Behavior on height { Behavior on height {
NumberAnimation { animation: Appearance.animation.elementMove.numberAnimation.createObject(this)
duration: Appearance.animation.elementMove.duration
easing.type: Appearance.animation.elementMove.type
easing.bezierCurve: Appearance.animation.elementMove.bezierCurve
}
} }
Behavior on color { Behavior on color {
ColorAnimation { animation: Appearance.animation.elementMove.colorAnimation.createObject(this)
duration: Appearance.animation.elementMove.duration
easing.type: Appearance.animation.elementMove.type
easing.bezierCurve: Appearance.animation.elementMove.bezierCurve
}
} }
} }
} }
@@ -26,7 +26,7 @@ ToolTip {
} }
} }
background: Item {} background: null
contentItem: Item { contentItem: Item {
id: contentItemBackground id: contentItemBackground
@@ -0,0 +1,146 @@
import "root:/modules/common"
import "root:/modules/common/widgets"
import "root:/services"
import "root:/modules/common/functions/string_utils.js" as StringUtils
import "root:/modules/common/functions/file_utils.js" as FileUtils
import Qt5Compat.GraphicalEffects
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls
import Quickshell
import Quickshell.Io
import Quickshell.Services.Mpris
import Quickshell.Widgets
import Quickshell.Wayland
import Quickshell.Hyprland
Scope {
id: root
required property var bar
property bool visible: false
readonly property MprisPlayer activePlayer: MprisController.activePlayer
readonly property var realPlayers: Mpris.players.values.filter(player => isRealPlayer(player))
readonly property real osdWidth: Appearance.sizes.osdWidth
readonly property real widgetWidth: Appearance.sizes.mediaControlsWidth
readonly property real widgetHeight: Appearance.sizes.mediaControlsHeight
property real contentPadding: 13
property real popupRounding: Appearance.rounding.screenRounding - Appearance.sizes.elevationMargin + 1
property real artRounding: Appearance.rounding.verysmall
property string baseCoverArtDir: FileUtils.trimFileProtocol(`${XdgDirectories.cache}/media/coverart`)
property bool hasPlasmaIntegration: false
function isRealPlayer(player) {
// return true
return (
// Remove unecessary native buses from browsers if there's plasma integration
!(hasPlasmaIntegration && player.dbusName.startsWith('org.mpris.MediaPlayer2.firefox')) &&
!(hasPlasmaIntegration && player.dbusName.startsWith('org.mpris.MediaPlayer2.chromium')) &&
// playerctld just copies other buses and we don't need duplicates
!player.dbusName?.startsWith('org.mpris.MediaPlayer2.playerctld') &&
// Non-instance mpd bus
!(player.dbusName?.endsWith('.mpd') && !player.dbusName.endsWith('MediaPlayer2.mpd'))
);
}
Component.onCompleted: {
Hyprland.dispatch(`exec rm -rf ${baseCoverArtDir} && mkdir -p ${baseCoverArtDir}`)
}
Loader {
id: mediaControlsLoader
active: false
PanelWindow {
id: mediaControlsRoot
visible: mediaControlsLoader.active
exclusiveZone: 0
implicitWidth: (
(mediaControlsRoot.screen.width / 2) // Middle of screen
- (osdWidth / 2) // Dodge OSD
- (widgetWidth / 2) // Account for widget width
) * 2
implicitHeight: playerColumnLayout.implicitHeight
color: "transparent"
WlrLayershell.namespace: "quickshell:mediaControls"
anchors {
top: true
left: true
}
mask: Region {
item: playerColumnLayout
}
ColumnLayout {
id: playerColumnLayout
anchors.top: parent.top
anchors.bottom: parent.bottom
x: (mediaControlsRoot.screen.width / 2) // Middle of screen
- (osdWidth / 2) // Dodge OSD
- (widgetWidth) // Account for widget width
+ (Appearance.sizes.elevationMargin) // It's fine for shadows to overlap
spacing: -Appearance.sizes.elevationMargin // Shadow overlap okay
Repeater {
model: ScriptModel {
values: root.realPlayers
}
delegate: PlayerControl {
required property MprisPlayer modelData
player: modelData
}
}
}
}
}
IpcHandler {
target: "mediaControls"
function toggle(): void {
mediaControlsLoader.active = !mediaControlsLoader.active;
if(mediaControlsLoader.active) Notifications.timeoutAll();
}
function close(): void {
mediaControlsLoader.active = false;
}
function open(): void {
mediaControlsLoader.active = true;
Notifications.timeoutAll();
}
}
GlobalShortcut {
name: "mediaControlsToggle"
description: qsTr("Toggles media controls on press")
onPressed: {
if (!mediaControlsLoader.active && Mpris.players.values.filter(player => isRealPlayer(player)).length === 0) {
return;
}
mediaControlsLoader.active = !mediaControlsLoader.active;
if(mediaControlsLoader.active) Notifications.timeoutAll();
}
}
GlobalShortcut {
name: "mediaControlsOpen"
description: qsTr("Opens media controls on press")
onPressed: {
mediaControlsLoader.active = true;
Notifications.timeoutAll();
}
}
GlobalShortcut {
name: "mediaControlsClose"
description: qsTr("Closes media controls on press")
onPressed: {
mediaControlsLoader.active = false;
}
}
}
@@ -0,0 +1,292 @@
import "root:/modules/common"
import "root:/modules/common/widgets"
import "root:/services"
import "root:/modules/common/functions/string_utils.js" as StringUtils
import "root:/modules/common/functions/color_utils.js" as ColorUtils
import Qt5Compat.GraphicalEffects
import QtQuick
import QtQuick.Effects
import QtQuick.Layouts
import QtQuick.Controls
import Quickshell
import Quickshell.Io
import Quickshell.Services.Mpris
import Quickshell.Widgets
import Quickshell.Wayland
import Quickshell.Hyprland
Item { // Player instance
id: playerController
required property MprisPlayer player
// property var artUrl: player?.metadata["xesam:url"] || player?.metadata["mpris:artUrl"] || player?.trackArtUrl
property var artUrl: player?.trackArtUrl
property color artDominantColor: Appearance.m3colors.m3secondaryContainer
implicitWidth: widgetWidth
implicitHeight: widgetHeight
component TrackChangeButton: Button {
id: playPauseButton
implicitWidth: 24
implicitHeight: 24
property var iconName
PointingHandInteraction {}
background: Rectangle {
color: playPauseButton.pressed ? blendedColors.colSecondaryContainerActive :
playPauseButton.hovered ? blendedColors.colSecondaryContainerHover :
ColorUtils.transparentize(blendedColors.colSecondaryContainer, 1)
radius: Appearance.rounding.full
Behavior on color {
animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this)
}
}
contentItem: MaterialSymbol {
iconSize: Appearance.font.pixelSize.huge
fill: 1
horizontalAlignment: Text.AlignHCenter
color: blendedColors.colOnSecondaryContainer
text: iconName
Behavior on color {
animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this)
}
}
}
Timer { // Force update for prevision
running: playerController.player?.playbackState == MprisPlaybackState.Playing
interval: 1000
repeat: true
onTriggered: {
playerController.player.positionChanged()
}
}
onArtUrlChanged: {
if (playerController.artUrl.length == 0) {
playerController.artDominantColor = Appearance.m3colors.m3secondaryContainer
return;
}
colorQuantizer.targetFile = playerController.artUrl // Yes this binding break is intentional
colorQuantizer.running = true
}
Process { // Average Color Runner
id: colorQuantizer
property string targetFile: playerController.artUrl
command: [ "sh", "-c", `magick '${targetFile}' -scale 1x1\\! -format '%[fx:int(255*r+.5)],%[fx:int(255*g+.5)],%[fx:int(255*b+.5)]' info: | sed 's/,/\\n/g' | xargs -L 1 printf '%02x' ; echo` ]
stdout: SplitParser {
onRead: data => {
// console.log("Color quantizer output:", data)
playerController.artDominantColor = "#" + data
}
}
}
property QtObject blendedColors: QtObject {
property color colLayer0: ColorUtils.mix(Appearance.colors.colLayer0, artDominantColor, 0.5)
property color colLayer1: ColorUtils.mix(Appearance.colors.colLayer1, artDominantColor, 0.5)
property color colOnLayer0: ColorUtils.mix(Appearance.colors.colOnLayer0, artDominantColor, 0.5)
property color colOnLayer1: ColorUtils.mix(Appearance.colors.colOnLayer1, artDominantColor, 0.5)
property color colSubtext: ColorUtils.mix(Appearance.colors.colOnLayer1, artDominantColor, 0.5)
property color colPrimary: ColorUtils.mix(ColorUtils.adaptToAccent(Appearance.m3colors.m3primary, artDominantColor), artDominantColor, 0.5)
property color colPrimaryHover: ColorUtils.mix(ColorUtils.adaptToAccent(Appearance.colors.colPrimaryHover, artDominantColor), artDominantColor, 0.3)
property color colPrimaryActive: ColorUtils.mix(ColorUtils.adaptToAccent(Appearance.colors.colPrimaryActive, artDominantColor), artDominantColor, 0.3)
property color colSecondaryContainer: ColorUtils.mix(Appearance.m3colors.m3secondaryContainer, artDominantColor, 0.3)
property color colSecondaryContainerHover: ColorUtils.mix(Appearance.colors.colSecondaryContainerHover, artDominantColor, 0.3)
property color colSecondaryContainerActive: ColorUtils.mix(Appearance.colors.colSecondaryContainerActive, artDominantColor, 0.3)
property color colOnPrimary: ColorUtils.mix(ColorUtils.adaptToAccent(Appearance.m3colors.m3onPrimary, artDominantColor), artDominantColor, 0.5)
property color colOnSecondaryContainer: ColorUtils.mix(Appearance.m3colors.m3onSecondaryContainer, artDominantColor, 0.2)
}
Rectangle { // Background
id: background
anchors.fill: parent
anchors.margins: Appearance.sizes.elevationMargin
color: blendedColors.colLayer0
radius: root.popupRounding
layer.enabled: true
layer.effect: OpacityMask {
maskSource: Rectangle {
width: background.width
height: background.height
radius: background.radius
}
}
Image {
id: blurredArt
anchors.fill: parent
visible: true
source: playerController.artUrl
sourceSize.width: background.width
sourceSize.height: background.height
fillMode: Image.PreserveAspectCrop
cache: false
antialiasing: true
asynchronous: true
layer.enabled: true
layer.effect: MultiEffect {
source: blurredArt
anchors.fill: blurredArt
saturation: 0.2
blurEnabled: true
blurMax: 100
blur: 1
}
Rectangle {
anchors.fill: parent
color: ColorUtils.transparentize(blendedColors.colLayer0, 0.25)
radius: root.popupRounding
}
}
RowLayout {
anchors.fill: parent
anchors.margins: root.contentPadding
spacing: 15
Rectangle { // Art background
id: artBackground
Layout.fillHeight: true
implicitWidth: height
radius: root.artRounding
color: blendedColors.colLayer1
layer.enabled: true
layer.effect: OpacityMask {
maskSource: Rectangle {
width: artBackground.width
height: artBackground.height
radius: artBackground.radius
}
}
Image { // Art image
id: mediaArt
property int size: parent.height
anchors.fill: parent
source: playerController.artUrl
fillMode: Image.PreserveAspectCrop
cache: false
antialiasing: true
asynchronous: true
width: size
height: size
sourceSize.width: size
sourceSize.height: size
}
}
ColumnLayout { // Info & controls
Layout.fillHeight: true
spacing: 2
StyledText {
id: trackTitle
Layout.fillWidth: true
font.pixelSize: Appearance.font.pixelSize.large
color: blendedColors.colOnLayer0
elide: Text.ElideRight
text: StringUtils.cleanMusicTitle(playerController.player?.trackTitle) || "Untitled"
}
StyledText {
id: trackArtist
Layout.fillWidth: true
font.pixelSize: Appearance.font.pixelSize.smaller
color: blendedColors.colSubtext
elide: Text.ElideRight
text: playerController.player?.trackArtist
}
Item { Layout.fillHeight: true }
Item {
Layout.fillWidth: true
implicitHeight: trackTime.implicitHeight + sliderRow.implicitHeight
StyledText {
id: trackTime
anchors.bottom: sliderRow.top
anchors.bottomMargin: 5
anchors.left: parent.left
font.pixelSize: Appearance.font.pixelSize.small
color: blendedColors.colSubtext
elide: Text.ElideRight
text: `${StringUtils.friendlyTimeForSeconds(playerController.player?.position)} / ${StringUtils.friendlyTimeForSeconds(playerController.player?.length)}`
}
RowLayout {
id: sliderRow
anchors {
bottom: parent.bottom
left: parent.left
right: parent.right
}
TrackChangeButton {
iconName: "skip_previous"
onClicked: playerController.player?.previous()
}
StyledProgressBar {
id: slider
Layout.fillWidth: true
highlightColor: blendedColors.colPrimary
trackColor: blendedColors.colSecondaryContainer
value: playerController.player?.position / playerController.player?.length
}
TrackChangeButton {
iconName: "skip_next"
onClicked: playerController.player?.next()
}
}
Button {
id: playPauseButton
anchors.right: parent.right
anchors.bottom: sliderRow.top
anchors.bottomMargin: 5
implicitWidth: 44
implicitHeight: 44
onClicked: playerController.player.togglePlaying();
PointingHandInteraction {}
background: Rectangle {
color: playerController.player?.isPlaying ?
(playPauseButton.pressed ? blendedColors.colPrimaryActive :
playPauseButton.hovered ? blendedColors.colPrimaryHover :
blendedColors.colPrimary) :
(playPauseButton.pressed ? blendedColors.colSecondaryContainerActive :
playPauseButton.hovered ? blendedColors.colSecondaryContainerHover :
blendedColors.colSecondaryContainer)
radius: Appearance.rounding.full
Behavior on color {
animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this)
}
}
contentItem: MaterialSymbol {
iconSize: Appearance.font.pixelSize.huge
fill: 1
horizontalAlignment: Text.AlignHCenter
color: playerController.player?.isPlaying ? blendedColors.colOnPrimary : blendedColors.colOnSecondaryContainer
text: playerController.player?.isPlaying ? "pause" : "play_arrow"
Behavior on color {
animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this)
}
}
}
}
}
}
}
}
@@ -7,111 +7,105 @@ import QtQuick.Controls
import QtQuick.Layouts import QtQuick.Layouts
import Quickshell import Quickshell
import Quickshell.Wayland import Quickshell.Wayland
import Quickshell.Hyprland
Scope { Scope {
id: screenCorners id: notificationPopup
readonly property Toplevel activeWindow: ToplevelManager.activeToplevel
Variants { LazyLoader {
model: Quickshell.screens loading: true
PanelWindow {
id: root
visible: (columnLayout.children.length > 0 || notificationWidgetList.length > 0)
screen: Quickshell.screens.find(s => s.name === Hyprland.focusedMonitor?.name)
LazyLoader { property Component notifComponent: NotificationWidget {}
property var modelData property list<NotificationWidget> notificationWidgetList: []
loading: true
PanelWindow {
id: root
visible: (columnLayout.children.length > 0 || notificationWidgetList.length > 0)
property Component notifComponent: NotificationWidget {} WlrLayershell.namespace: "quickshell:notificationPopup"
property list<NotificationWidget> notificationWidgetList: [] WlrLayershell.layer: WlrLayer.Overlay
exclusiveZone: 0
screen: modelData
WlrLayershell.namespace: "quickshell:notificationPopup"
WlrLayershell.layer: WlrLayer.Overlay
exclusiveZone: 0
anchors {
top: true
right: true
bottom: true
}
mask: Region {
item: columnLayout
}
color: "transparent"
implicitWidth: Appearance.sizes.notificationPopupWidth
// Signal handlers to add/remove notifications
Connections {
target: Notifications
function onNotify(notification) {
if (GlobalStates.sidebarRightOpenCount > 0) {
return
}
// notificationRepeater.model = [notification, ...notificationRepeater.model]
const notif = root.notifComponent.createObject(columnLayout, {
notificationObject: notification,
popup: true
});
notificationWidgetList.unshift(notif)
// Remove stuff from t he column, add back
for (let i = 0; i < notificationWidgetList.length; i++) {
if (notificationWidgetList[i].parent === columnLayout) {
notificationWidgetList[i].parent = null;
}
}
// Add notification widgets to the column
for (let i = 0; i < notificationWidgetList.length; i++) {
if (notificationWidgetList[i].parent === null) {
notificationWidgetList[i].parent = columnLayout;
}
}
}
function onDiscard(id) {
for (let i = notificationWidgetList.length - 1; i >= 0; i--) {
const widget = notificationWidgetList[i];
if (widget && widget.notificationObject && widget.notificationObject.id === id) {
widget.destroyWithAnimation();
notificationWidgetList.splice(i, 1);
}
}
}
function onTimeout(id) {
for (let i = notificationWidgetList.length - 1; i >= 0; i--) {
const widget = notificationWidgetList[i];
if (widget && widget.notificationObject && widget.notificationObject.id === id) {
widget.destroyWithAnimation();
notificationWidgetList.splice(i, 1);
}
}
}
function onDiscardAll() {
for (let i = notificationWidgetList.length - 1; i >= 0; i--) {
const widget = notificationWidgetList[i];
if (widget && widget.notificationObject) {
widget.destroyWithAnimation();
}
}
notificationWidgetList = [];
}
}
ColumnLayout { // Scrollable window content
id: columnLayout
anchors.horizontalCenter: parent.horizontalCenter
width: parent.width - Appearance.sizes.hyprlandGapsOut * 2
spacing: 0 // The widgets themselves have margins for spacing
// Notifications are added by the above signal handlers
}
anchors {
top: true
right: true
bottom: true
} }
}
mask: Region {
item: columnLayout
}
color: "transparent"
implicitWidth: Appearance.sizes.notificationPopupWidth
// Signal handlers to add/remove notifications
Connections {
target: Notifications
function onNotify(notification) {
if (GlobalStates.sidebarRightOpen) {
return
}
// notificationRepeater.model = [notification, ...notificationRepeater.model]
const notif = root.notifComponent.createObject(columnLayout, {
notificationObject: notification,
popup: true
});
notificationWidgetList.unshift(notif)
// Remove stuff from t he column, add back
for (let i = 0; i < notificationWidgetList.length; i++) {
if (notificationWidgetList[i].parent === columnLayout) {
notificationWidgetList[i].parent = null;
}
}
// Add notification widgets to the column
for (let i = 0; i < notificationWidgetList.length; i++) {
if (notificationWidgetList[i].parent === null) {
notificationWidgetList[i].parent = columnLayout;
}
}
}
function onDiscard(id) {
for (let i = notificationWidgetList.length - 1; i >= 0; i--) {
const widget = notificationWidgetList[i];
if (widget && widget.notificationObject && widget.notificationObject.id === id) {
widget.destroyWithAnimation();
notificationWidgetList.splice(i, 1);
}
}
}
function onTimeout(id) {
for (let i = notificationWidgetList.length - 1; i >= 0; i--) {
const widget = notificationWidgetList[i];
if (widget && widget.notificationObject && widget.notificationObject.id === id) {
widget.destroyWithAnimation();
notificationWidgetList.splice(i, 1);
}
}
}
function onDiscardAll() {
for (let i = notificationWidgetList.length - 1; i >= 0; i--) {
const widget = notificationWidgetList[i];
if (widget && widget.notificationObject) {
widget.destroyWithAnimation();
}
}
notificationWidgetList = [];
}
}
ColumnLayout { // Scrollable window content
id: columnLayout
anchors.horizontalCenter: parent.horizontalCenter
width: parent.width - Appearance.sizes.elevationMargin * 2
spacing: 0 // The widgets themselves have margins for spacing
// Notifications are added by the above signal handlers
}
}
} }
} }
@@ -12,6 +12,8 @@ import Quickshell.Wayland
Scope { Scope {
id: root id: root
property bool showOsdValues: false property bool showOsdValues: false
property var focusedScreen: Quickshell.screens.find(s => s.name === Hyprland.focusedMonitor?.name)
property var brightnessMonitor: Brightness.getMonitorForScreen(focusedScreen)
function triggerOsd() { function triggerOsd() {
showOsdValues = true showOsdValues = true
@@ -36,81 +38,79 @@ Scope {
} }
} }
Variants { Connections {
model: Quickshell.screens target: Brightness
function onBrightnessChanged() {
if (!root.brightnessMonitor.ready) return
root.triggerOsd()
}
}
Loader { Loader {
id: osdLoader id: osdLoader
property var modelData active: showOsdValues
active: showOsdValues
property var brightnessMonitor: Brightness.getMonitorForScreen(modelData) PanelWindow {
id: osdRoot
Connections { Connections {
target: brightnessMonitor target: root
function onBrightnessChanged() { function onFocusedScreenChanged() {
if (!brightnessMonitor.ready) return osdRoot.screen = root.focusedScreen
root.triggerOsd()
} }
} }
PanelWindow { exclusionMode: ExclusionMode.Normal
screen: modelData WlrLayershell.namespace: "quickshell:onScreenDisplay"
exclusionMode: ExclusionMode.Normal WlrLayershell.layer: WlrLayer.Overlay
WlrLayershell.namespace: "quickshell:onScreenDisplay" color: "transparent"
WlrLayershell.layer: WlrLayer.Overlay
color: "transparent"
anchors {
top: true
}
mask: Region {
item: osdValuesWrapper
}
implicitWidth: columnLayout.implicitWidth
implicitHeight: columnLayout.implicitHeight
visible: osdLoader.active
ColumnLayout {
id: columnLayout
anchors.horizontalCenter: parent.horizontalCenter
Item {
height: 1 // Prevent Wayland protocol error
}
Item {
id: osdValuesWrapper
// Extra space for shadow
implicitHeight: true ? (osdValues.implicitHeight + Appearance.sizes.elevationMargin * 2) : 0
implicitWidth: osdValues.implicitWidth + Appearance.sizes.elevationMargin * 2
clip: true
MouseArea {
anchors.fill: parent
hoverEnabled: true
onEntered: root.showOsdValues = false
}
Behavior on implicitHeight {
NumberAnimation {
duration: Appearance.animation.menuDecel.duration
easing.type: Appearance.animation.menuDecel.type
}
}
OsdValueIndicator {
id: osdValues
anchors.centerIn: parent
value: brightnessMonitor.brightness
icon: "light_mode"
rotateIcon: true
name: qsTr("Brightness")
}
}
}
anchors.top: true
mask: Region {
item: osdValuesWrapper
} }
implicitWidth: Appearance.sizes.osdWidth
implicitHeight: columnLayout.implicitHeight
visible: osdLoader.active
ColumnLayout {
id: columnLayout
anchors.horizontalCenter: parent.horizontalCenter
Item {
id: osdValuesWrapper
// Extra space for shadow
implicitHeight: osdValues.implicitHeight + Appearance.sizes.elevationMargin * 2
implicitWidth: osdValues.implicitWidth
clip: true
MouseArea {
anchors.fill: parent
hoverEnabled: true
onEntered: root.showOsdValues = false
}
Behavior on implicitHeight {
NumberAnimation {
duration: Appearance.animation.menuDecel.duration
easing.type: Appearance.animation.menuDecel.type
}
}
OsdValueIndicator {
id: osdValues
anchors.fill: parent
anchors.margins: Appearance.sizes.elevationMargin
value: root.brightnessMonitor?.brightness ?? 50
icon: "light_mode"
rotateIcon: true
scaleIcon: true
name: qsTr("Brightness")
}
}
}
} }
} }
IpcHandler { IpcHandler {
@@ -131,7 +131,7 @@ Scope {
GlobalShortcut { GlobalShortcut {
name: "osdBrightnessTrigger" name: "osdBrightnessTrigger"
description: "Triggers brightness OSD on press" description: qsTr("Triggers brightness OSD on press")
onPressed: { onPressed: {
root.triggerOsd() root.triggerOsd()
@@ -139,7 +139,7 @@ Scope {
} }
GlobalShortcut { GlobalShortcut {
name: "osdBrightnessHide" name: "osdBrightnessHide"
description: "Hides brightness OSD on press" description: qsTr("Hides brightness OSD on press")
onPressed: { onPressed: {
root.showOsdValues = false root.showOsdValues = false
@@ -12,6 +12,7 @@ import Quickshell.Hyprland
Scope { Scope {
id: root id: root
property bool showOsdValues: false property bool showOsdValues: false
property var focusedScreen: Quickshell.screens.find(s => s.name === Hyprland.focusedMonitor?.name)
function triggerOsd() { function triggerOsd() {
showOsdValues = true showOsdValues = true
@@ -47,70 +48,68 @@ Scope {
} }
} }
Variants { Loader {
model: Quickshell.screens id: osdLoader
active: showOsdValues
Loader { PanelWindow {
id: osdLoader id: osdRoot
property var modelData
active: showOsdValues
PanelWindow {
screen: modelData
exclusionMode: ExclusionMode.Normal
WlrLayershell.namespace: "quickshell:onScreenDisplay"
WlrLayershell.layer: WlrLayer.Overlay
color: "transparent"
anchors { Connections {
top: true target: root
} function onFocusedScreenChanged() {
mask: Region { osdRoot.screen = root.focusedScreen
item: osdValuesWrapper
} }
}
implicitWidth: columnLayout.implicitWidth exclusionMode: ExclusionMode.Normal
implicitHeight: columnLayout.implicitHeight WlrLayershell.namespace: "quickshell:onScreenDisplay"
visible: osdLoader.active WlrLayershell.layer: WlrLayer.Overlay
color: "transparent"
ColumnLayout { anchors.top: true
id: columnLayout mask: Region {
anchors.horizontalCenter: parent.horizontalCenter item: osdValuesWrapper
Item { }
height: 1 // Prevent Wayland protocol error
implicitWidth: Appearance.sizes.osdWidth
implicitHeight: columnLayout.implicitHeight
visible: osdLoader.active
ColumnLayout {
id: columnLayout
anchors.horizontalCenter: parent.horizontalCenter
Item {
id: osdValuesWrapper
// Extra space for shadow
implicitHeight: osdValues.implicitHeight + Appearance.sizes.elevationMargin * 2
implicitWidth: osdValues.implicitWidth
clip: true
MouseArea {
anchors.fill: parent
hoverEnabled: true
onEntered: root.showOsdValues = false
} }
Item {
id: osdValuesWrapper
// Extra space for shadow
implicitHeight: true ? (osdValues.implicitHeight + Appearance.sizes.elevationMargin * 2) : 0
implicitWidth: osdValues.implicitWidth + Appearance.sizes.elevationMargin * 2
clip: true
MouseArea { Behavior on implicitHeight {
anchors.fill: parent NumberAnimation {
hoverEnabled: true duration: Appearance.animation.menuDecel.duration
onEntered: root.showOsdValues = false easing.type: Appearance.animation.menuDecel.type
}
Behavior on implicitHeight {
NumberAnimation {
duration: Appearance.animation.menuDecel.duration
easing.type: Appearance.animation.menuDecel.type
}
}
OsdValueIndicator {
id: osdValues
anchors.centerIn: parent
value: Audio.sink?.audio.volume ?? 0
icon: Audio.sink?.audio.muted ? "volume_off" : "volume_up"
name: qsTr("Volume")
} }
} }
}
OsdValueIndicator {
id: osdValues
anchors.fill: parent
anchors.margins: Appearance.sizes.elevationMargin
value: Audio.sink?.audio.volume ?? 0
icon: Audio.sink?.audio.muted ? "volume_off" : "volume_up"
name: qsTr("Volume")
}
}
} }
} }
} }
IpcHandler { IpcHandler {
@@ -130,7 +129,7 @@ Scope {
} }
GlobalShortcut { GlobalShortcut {
name: "osdVolumeTrigger" name: "osdVolumeTrigger"
description: "Triggers volume OSD on press" description: qsTr("Triggers volume OSD on press")
onPressed: { onPressed: {
root.triggerOsd() root.triggerOsd()
@@ -138,7 +137,7 @@ Scope {
} }
GlobalShortcut { GlobalShortcut {
name: "osdVolumeHide" name: "osdVolumeHide"
description: "Hides volume OSD on press" description: qsTr("Hides volume OSD on press")
onPressed: { onPressed: {
root.showOsdValues = false root.showOsdValues = false
@@ -3,10 +3,11 @@ import "root:/modules/common"
import "root:/modules/common/widgets" import "root:/modules/common/widgets"
import QtQuick import QtQuick
import QtQuick.Controls import QtQuick.Controls
import QtQuick.Effects
import QtQuick.Layouts import QtQuick.Layouts
import Quickshell import Quickshell
import Quickshell.Widgets import Quickshell.Widgets
import Qt5Compat.GraphicalEffects // import Qt5Compat.GraphicalEffects
Item { Item {
id: root id: root
@@ -14,27 +15,40 @@ Item {
required property string icon required property string icon
required property string name required property string name
property bool rotateIcon: false property bool rotateIcon: false
property bool scaleIcon: false
property real valueIndicatorVerticalPadding: 5 property real valueIndicatorVerticalPadding: 9
property real valueIndicatorLeftPadding: 10 property real valueIndicatorLeftPadding: 10
property real valueIndicatorRightPadding: 20 // An icon is circle ish, a column isn't, hence the extra padding property real valueIndicatorRightPadding: 20 // An icon is circle ish, a column isn't, hence the extra padding
Layout.margins: Appearance.sizes.elevationMargin Layout.margins: Appearance.sizes.elevationMargin
implicitWidth: valueIndicator.implicitWidth implicitWidth: Appearance.sizes.osdWidth
implicitHeight: valueIndicator.implicitHeight implicitHeight: valueIndicator.implicitHeight
WrapperRectangle { WrapperRectangle {
id: valueIndicator id: valueIndicator
anchors.fill: parent
radius: Appearance.rounding.full radius: Appearance.rounding.full
color: Appearance.colors.colLayer0 color: Appearance.colors.colLayer0
implicitWidth: valueRow.implicitWidth implicitWidth: valueRow.implicitWidth
layer.enabled: true
layer.effect: MultiEffect {
source: valueIndicator
anchors.fill: valueIndicator
shadowEnabled: true
shadowColor: Appearance.colors.colShadow
shadowVerticalOffset: 1
shadowBlur: 0.5
}
RowLayout { // Icon on the left, stuff on the right RowLayout { // Icon on the left, stuff on the right
id: valueRow id: valueRow
Layout.margins: 10 Layout.margins: 10
anchors.fill: parent
spacing: 10 spacing: 10
Item { Item {
implicitWidth: 30 implicitWidth: 30
implicitHeight: 30 implicitHeight: 30
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
@@ -47,22 +61,14 @@ Item {
renderType: Text.QtRendering renderType: Text.QtRendering
text: root.icon text: root.icon
iconSize: 20 + 10 * (root.rotateIcon ? value : 1) iconSize: 20 + 10 * (root.scaleIcon ? value : 1)
rotation: 180 * (root.rotateIcon ? value : 0) rotation: 180 * (root.rotateIcon ? value : 0)
Behavior on iconSize { Behavior on iconSize {
NumberAnimation { animation: Appearance.animation.elementMoveEnter.numberAnimation.createObject(this)
duration: Appearance.animation.elementMoveEnter.duration
easing.type: Appearance.animation.elementMoveEnter.type
easing.bezierCurve: Appearance.animation.elementMoveEnter.bezierCurve
}
} }
Behavior on rotation { Behavior on rotation {
NumberAnimation { animation: Appearance.animation.elementMoveEnter.numberAnimation.createObject(this)
duration: Appearance.animation.elementMoveEnter.duration
easing.type: Appearance.animation.elementMoveEnter.type
easing.bezierCurve: Appearance.animation.elementMoveEnter.bezierCurve
}
} }
} }
@@ -93,20 +99,10 @@ Item {
StyledProgressBar { StyledProgressBar {
id: valueProgressBar id: valueProgressBar
Layout.fillWidth: true
value: root.value value: root.value
} }
} }
} }
} }
DropShadow {
id: valueShadow
anchors.fill: valueIndicator
source: valueIndicator
radius: Appearance.sizes.elevationMargin
samples: radius * 2 + 1
color: Appearance.colors.colShadow
verticalOffset: 2
horizontalOffset: 0
}
} }
@@ -128,7 +128,7 @@ Scope {
GlobalShortcut { GlobalShortcut {
name: "overviewToggle" name: "overviewToggle"
description: "Toggles overview on press" description: qsTr("Toggles overview on press")
onPressed: { onPressed: {
GlobalStates.overviewOpen = !GlobalStates.overviewOpen GlobalStates.overviewOpen = !GlobalStates.overviewOpen
@@ -136,7 +136,7 @@ Scope {
} }
GlobalShortcut { GlobalShortcut {
name: "overviewClose" name: "overviewClose"
description: "Closes overview" description: qsTr("Closes overview")
onPressed: { onPressed: {
GlobalStates.overviewOpen = false GlobalStates.overviewOpen = false
@@ -144,7 +144,7 @@ Scope {
} }
GlobalShortcut { GlobalShortcut {
name: "overviewToggleRelease" name: "overviewToggleRelease"
description: "Toggles overview on release" description: qsTr("Toggles overview on release")
onPressed: { onPressed: {
GlobalStates.superReleaseMightTrigger = true GlobalStates.superReleaseMightTrigger = true
@@ -160,9 +160,9 @@ Scope {
} }
GlobalShortcut { GlobalShortcut {
name: "overviewToggleReleaseInterrupt" name: "overviewToggleReleaseInterrupt"
description: "Interrupts possibility of overview being toggled on release. " + description: qsTr("Interrupts possibility of overview being toggled on release. ") +
"This is necessary because GlobalShortcut.onReleased in quickshell triggers whether or not you press something else while holding the key. " + qsTr("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." qsTr("To make sure this works consistently, use binditn = MODKEYS, catchall in an automatically triggered submap that includes everything.")
onPressed: { onPressed: {
GlobalStates.superReleaseMightTrigger = false GlobalStates.superReleaseMightTrigger = false
@@ -2,8 +2,9 @@ import "root:/"
import "root:/services/" import "root:/services/"
import "root:/modules/common" import "root:/modules/common"
import "root:/modules/common/widgets" import "root:/modules/common/widgets"
import Qt5Compat.GraphicalEffects import "root:/modules/common/functions/color_utils.js" as ColorUtils
import QtQuick import QtQuick
import QtQuick.Effects
import QtQuick.Layouts import QtQuick.Layouts
import Quickshell import Quickshell
import Quickshell.Io import Quickshell.Io
@@ -58,6 +59,16 @@ Item {
color: Appearance.colors.colLayer0 color: Appearance.colors.colLayer0
radius: Appearance.rounding.screenRounding * root.scale + 5 * 2 radius: Appearance.rounding.screenRounding * root.scale + 5 * 2
layer.enabled: true
layer.effect: MultiEffect {
source: overviewBackground
anchors.fill: overviewBackground
shadowEnabled: true
shadowColor: Appearance.colors.colShadow
shadowVerticalOffset: 1
shadowBlur: 0.5
}
ColumnLayout { ColumnLayout {
id: workspaceColumnLayout id: workspaceColumnLayout
@@ -78,7 +89,7 @@ Item {
property int colIndex: index property int colIndex: index
property int workspaceValue: root.workspaceGroup * workspacesShown + rowIndex * ConfigOptions.overview.numOfCols + colIndex + 1 property int workspaceValue: root.workspaceGroup * workspacesShown + rowIndex * ConfigOptions.overview.numOfCols + colIndex + 1
property color defaultWorkspaceColor: Appearance.colors.colLayer1 // TODO: reconsider this color for a cleaner look property color defaultWorkspaceColor: Appearance.colors.colLayer1 // TODO: reconsider this color for a cleaner look
property color hoveredWorkspaceColor: Appearance.mix(defaultWorkspaceColor, Appearance.colors.colLayer1Hover, 0.1) property color hoveredWorkspaceColor: ColorUtils.mix(defaultWorkspaceColor, Appearance.colors.colLayer1Hover, 0.1)
property color hoveredBorderColor: Appearance.colors.colLayer2Hover property color hoveredBorderColor: Appearance.colors.colLayer2Hover
property color activeBorderColor: Appearance.m3colors.m3secondary property color activeBorderColor: Appearance.m3colors.m3secondary
property bool hoveredWhileDragging: false property bool hoveredWhileDragging: false
@@ -209,15 +220,4 @@ Item {
} }
} }
} }
DropShadow {
z: -9999
anchors.fill: overviewBackground
horizontalOffset: 0
verticalOffset: 2
radius: Appearance.sizes.elevationMargin
samples: radius * 2 + 1 // Ideally should be 2 * radius + 1, see qt docs
color: Appearance.colors.colShadow
source: overviewBackground
}
} }
@@ -2,6 +2,7 @@ import "root:/services/"
import "root:/modules/common" import "root:/modules/common"
import "root:/modules/common/widgets" import "root:/modules/common/widgets"
import "root:/modules/common/functions/icons.js" as Icons import "root:/modules/common/functions/icons.js" as Icons
import "root:/modules/common/functions/color_utils.js" as ColorUtils
import Qt5Compat.GraphicalEffects import Qt5Compat.GraphicalEffects
import QtQuick import QtQuick
import QtQuick.Layouts import QtQuick.Layouts
@@ -42,37 +43,21 @@ Rectangle { // Window
radius: Appearance.rounding.windowRounding * root.scale radius: Appearance.rounding.windowRounding * root.scale
color: pressed ? Appearance.colors.colLayer2Active : hovered ? Appearance.colors.colLayer2Hover : Appearance.colors.colLayer2 color: pressed ? Appearance.colors.colLayer2Active : hovered ? Appearance.colors.colLayer2Hover : Appearance.colors.colLayer2
border.color : Appearance.transparentize(Appearance.m3colors.m3outline, 0.9) border.color : ColorUtils.transparentize(Appearance.m3colors.m3outline, 0.9)
border.pixelAligned : false border.pixelAligned : false
border.width : 1 border.width : 1
Behavior on x { Behavior on x {
NumberAnimation { animation: Appearance.animation.elementMoveEnter.numberAnimation.createObject(this)
duration: Appearance.animation.elementMoveEnter.duration
easing.type: Appearance.animation.elementMoveEnter.type
easing.bezierCurve: Appearance.animation.elementMoveEnter.bezierCurve
}
} }
Behavior on y { Behavior on y {
NumberAnimation { animation: Appearance.animation.elementMoveEnter.numberAnimation.createObject(this)
duration: Appearance.animation.elementMoveEnter.duration
easing.type: Appearance.animation.elementMoveEnter.type
easing.bezierCurve: Appearance.animation.elementMoveEnter.bezierCurve
}
} }
Behavior on width { Behavior on width {
NumberAnimation { animation: Appearance.animation.elementMoveEnter.numberAnimation.createObject(this)
duration: Appearance.animation.elementMoveEnter.duration
easing.type: Appearance.animation.elementMoveEnter.type
easing.bezierCurve: Appearance.animation.elementMoveEnter.bezierCurve
}
} }
Behavior on height { Behavior on height {
NumberAnimation { animation: Appearance.animation.elementMoveEnter.numberAnimation.createObject(this)
duration: Appearance.animation.elementMoveEnter.duration
easing.type: Appearance.animation.elementMoveEnter.type
easing.bezierCurve: Appearance.animation.elementMoveEnter.bezierCurve
}
} }
ColumnLayout { ColumnLayout {
@@ -88,11 +73,7 @@ Rectangle { // Window
implicitSize: Math.min(targetWindowWidth, targetWindowHeight) * (root.compactMode ? root.iconToWindowRatioCompact : root.iconToWindowRatio) implicitSize: Math.min(targetWindowWidth, targetWindowHeight) * (root.compactMode ? root.iconToWindowRatioCompact : root.iconToWindowRatio)
Behavior on implicitSize { Behavior on implicitSize {
NumberAnimation { animation: Appearance.animation.elementMoveEnter.numberAnimation.createObject(this)
duration: Appearance.animation.elementMoveEnter.duration
easing.type: Appearance.animation.elementMoveEnter.type
easing.bezierCurve: Appearance.animation.elementMoveEnter.bezierCurve
}
} }
IconImage { IconImage {
@@ -2,6 +2,7 @@
import "root:/" import "root:/"
import "root:/modules/common" import "root:/modules/common"
import "root:/modules/common/widgets" import "root:/modules/common/widgets"
import "root:/modules/common/functions/color_utils.js" as ColorUtils
import QtQuick import QtQuick
import QtQuick.Controls import QtQuick.Controls
import QtQuick.Layouts import QtQuick.Layouts
@@ -57,7 +58,9 @@ Button {
anchors.leftMargin: root.horizontalMargin anchors.leftMargin: root.horizontalMargin
anchors.rightMargin: root.horizontalMargin anchors.rightMargin: root.horizontalMargin
radius: Appearance.rounding.normal radius: Appearance.rounding.normal
color: (root.down || root.keyboardDown) ? Appearance.colors.colLayer1Active : ((root.hovered || root.focus) ? Appearance.colors.colLayer1Hover : Appearance.transparentize(Appearance.m3colors.m3surfaceContainerHigh, 1)) color: (root.down || root.keyboardDown) ? Appearance.colors.colLayer1Active :
((root.hovered || root.focus) ? Appearance.colors.colLayer1Hover :
ColorUtils.transparentize(Appearance.m3colors.m3surfaceContainerHigh, 1))
} }
RowLayout { RowLayout {
@@ -100,7 +103,7 @@ Button {
StyledText { StyledText {
font.pixelSize: Appearance.font.pixelSize.small font.pixelSize: Appearance.font.pixelSize.small
color: Appearance.colors.colSubtext color: Appearance.colors.colSubtext
visible: root.itemType && root.itemType != "App" visible: root.itemType && root.itemType != qsTr("App")
text: root.itemType text: root.itemType
} }
StyledText { StyledText {
@@ -7,6 +7,7 @@ import Qt5Compat.GraphicalEffects
import Qt.labs.platform import Qt.labs.platform
import QtQuick import QtQuick
import QtQuick.Controls import QtQuick.Controls
import QtQuick.Effects
import QtQuick.Layouts import QtQuick.Layouts
import Quickshell import Quickshell
import Quickshell.Io import Quickshell.Io
@@ -160,6 +161,15 @@ Item { // Wrapper
implicitHeight: columnLayout.implicitHeight implicitHeight: columnLayout.implicitHeight
radius: Appearance.rounding.large radius: Appearance.rounding.large
color: Appearance.colors.colLayer0 color: Appearance.colors.colLayer0
layer.enabled: true
layer.effect: MultiEffect {
source: searchWidgetContent
anchors.fill: searchWidgetContent
shadowEnabled: true
shadowColor: Appearance.colors.colShadow
shadowVerticalOffset: 1
shadowBlur: 0.5
}
ColumnLayout { ColumnLayout {
id: columnLayout id: columnLayout
@@ -227,7 +237,7 @@ Item { // Wrapper
} }
} }
background: Item {} background: null
cursorDelegate: Rectangle { cursorDelegate: Rectangle {
width: 1 width: 1
@@ -276,7 +286,7 @@ Item { // Wrapper
nonAppResultsTimer.restart(); nonAppResultsTimer.restart();
const mathResultObject = { const mathResultObject = {
name: root.mathResult, name: root.mathResult,
clickActionName: "Copy", clickActionName: qsTr("Copy"),
type: qsTr("Math result"), type: qsTr("Math result"),
fontType: "monospace", fontType: "monospace",
materialSymbol: 'calculate', materialSymbol: 'calculate',
@@ -286,7 +296,7 @@ Item { // Wrapper
} }
const commandResultObject = { const commandResultObject = {
name: searchingText, name: searchingText,
clickActionName: "Run", clickActionName: qsTr("Run"),
type: qsTr("Run command"), type: qsTr("Run command"),
fontType: "monospace", fontType: "monospace",
materialSymbol: 'terminal', materialSymbol: 'terminal',
@@ -300,8 +310,8 @@ Item { // Wrapper
if (actionString.startsWith(root.searchingText) || root.searchingText.startsWith(actionString)) { if (actionString.startsWith(root.searchingText) || root.searchingText.startsWith(actionString)) {
return { return {
name: root.searchingText.startsWith(actionString) ? root.searchingText : actionString, name: root.searchingText.startsWith(actionString) ? root.searchingText : actionString,
clickActionName: "Run", clickActionName: qsTr("Run"),
type: "Action", type: qsTr("Action"),
materialSymbol: 'settings_suggest', materialSymbol: 'settings_suggest',
execute: () => { execute: () => {
action.execute(root.searchingText.split(" ").slice(1).join(" ")) action.execute(root.searchingText.split(" ").slice(1).join(" "))
@@ -319,8 +329,8 @@ Item { // Wrapper
result = result.concat( result = result.concat(
AppSearch.fuzzyQuery(root.searchingText) AppSearch.fuzzyQuery(root.searchingText)
.map((entry) => { .map((entry) => {
entry.clickActionName = "Launch"; entry.clickActionName = qsTr("Launch");
entry.type = "App" entry.type = qsTr("App");
return entry; return entry;
}) })
); );
@@ -344,8 +354,8 @@ Item { // Wrapper
// Web search // Web search
result.push({ result.push({
name: root.searchingText, name: root.searchingText,
clickActionName: "Search", clickActionName: qsTr("Search"),
type: "Search the web", type: qsTr("Search the web"),
materialSymbol: 'travel_explore', materialSymbol: 'travel_explore',
execute: () => { execute: () => {
let url = ConfigOptions.search.engineBaseUrl + root.searchingText let url = ConfigOptions.search.engineBaseUrl + root.searchingText
@@ -361,22 +371,9 @@ Item { // Wrapper
} }
delegate: SearchItem { delegate: SearchItem {
entry: modelData entry: modelData
// itemName: modelData.name
// itemIcon: modelData.icon
} }
} }
} }
} }
DropShadow {
id: searchWidgetShadow
anchors.fill: searchWidgetContent
source: searchWidgetContent
radius: Appearance.sizes.elevationMargin
samples: radius * 2 + 1
color: Appearance.colors.colShadow
verticalOffset: 2
horizontalOffset: 0
}
} }
+183 -206
View File
@@ -1,201 +1,193 @@
import "root:/modules/common" import "root:/modules/common"
import "root:/modules/common/widgets" import "root:/modules/common/widgets"
import "root:/modules/common/functions/color_utils.js" as ColorUtils
import QtQuick import QtQuick
import QtQuick.Controls import QtQuick.Controls
import QtQuick.Layouts import QtQuick.Layouts
import Quickshell import Quickshell
import Quickshell.Hyprland
import Quickshell.Io import Quickshell.Io
import Quickshell.Wayland
import Quickshell.Widgets import Quickshell.Widgets
import Quickshell.Wayland
import Quickshell.Hyprland
Scope { Scope {
id: root id: root
readonly property Toplevel activeWindow: ToplevelManager.activeToplevel property var focusedScreen: Quickshell.screens.find(s => s.name === Hyprland.focusedMonitor?.name)
Variants { Loader {
id: sessionVariants id: sessionLoader
model: Quickshell.screens active: false
Loader {
id: sessionLoader
property var modelData
active: false
PanelWindow { // Session menu
id: sessionRoot
visible: sessionLoader.active
function hide() {
sessionLoader.active = false
}
property string subtitle
screen: modelData
exclusionMode: ExclusionMode.Ignore
WlrLayershell.namespace: "quickshell:session"
WlrLayershell.layer: WlrLayer.Overlay
WlrLayershell.keyboardFocus: WlrKeyboardFocus.Exclusive
color: Appearance.transparentize(Appearance.m3colors.m3background, 0.3)
anchors {
top: true
left: true
right: true
}
implicitWidth: modelData.width
implicitHeight: modelData.height
MouseArea {
id: sessionMouseArea
anchors.fill: parent
onClicked: {
sessionRoot.hide()
}
}
ColumnLayout { // Content column
anchors.centerIn: parent
spacing: 15
Keys.onPressed: (event) => {
if (event.key === Qt.Key_Escape) {
sessionRoot.hide();
}
}
ColumnLayout {
Layout.alignment: Qt.AlignHCenter
spacing: 0
StyledText { // Title
Layout.alignment: Qt.AlignHCenter
horizontalAlignment: Text.AlignHCenter
font.family: Appearance.font.family.title
font.pixelSize: Appearance.font.pixelSize.title
font.weight: Font.DemiBold
text: qsTr("Session")
}
StyledText { // Small instruction
Layout.alignment: Qt.AlignHCenter
horizontalAlignment: Text.AlignHCenter
font.family: Appearance.font.family.title
font.pixelSize: Appearance.font.pixelSize.normal
text: qsTr("Arrow keys to navigate, Enter to select\nEsc or click anywhere to cancel")
}
}
RowLayout { // First row of buttons
spacing: 15
SessionActionButton {
id: sessionLock
focus: sessionRoot.visible
buttonIcon: "lock"
buttonText: qsTr("Lock")
onClicked: { Hyprland.dispatch("exec loginctl lock-session"); sessionRoot.hide() }
onFocusChanged: { if (focus) sessionRoot.subtitle = buttonText }
KeyNavigation.right: sessionSleep
KeyNavigation.down: sessionHibernate
}
SessionActionButton {
id: sessionSleep
buttonIcon: "dark_mode"
buttonText: qsTr("Sleep")
onClicked: { Hyprland.dispatch("exec systemctl suspend || loginctl suspend"); sessionRoot.hide() }
onFocusChanged: { if (focus) sessionRoot.subtitle = buttonText }
KeyNavigation.left: sessionLock
KeyNavigation.right: sessionLogout
KeyNavigation.down: sessionShutdown
}
SessionActionButton {
id: sessionLogout
buttonIcon: "logout"
buttonText: qsTr("Logout")
onClicked: { Hyprland.dispatch("exec pkill Hyprland"); sessionRoot.hide() }
onFocusChanged: { if (focus) sessionRoot.subtitle = buttonText }
KeyNavigation.left: sessionSleep
KeyNavigation.right: sessionTaskManager
KeyNavigation.down: sessionReboot
}
SessionActionButton {
id: sessionTaskManager
buttonIcon: "browse_activity"
buttonText: qsTr("Task Manager")
onClicked: { Hyprland.dispatch("exec gnome-system-monitor & disown"); sessionRoot.hide() }
onFocusChanged: { if (focus) sessionRoot.subtitle = buttonText }
KeyNavigation.left: sessionLogout
KeyNavigation.down: sessionFirmwareReboot
}
}
RowLayout { // Second row of buttons
spacing: 15
SessionActionButton {
id: sessionHibernate
buttonIcon: "downloading"
buttonText: qsTr("Hibernate")
onClicked: { Hyprland.dispatch("exec systemctl hibernate || loginctl hibernate"); sessionRoot.hide() }
onFocusChanged: { if (focus) sessionRoot.subtitle = buttonText }
KeyNavigation.up: sessionLock
KeyNavigation.right: sessionShutdown
}
SessionActionButton {
id: sessionShutdown
buttonIcon: "power_settings_new"
buttonText: qsTr("Shutdown")
onClicked: { Hyprland.dispatch("exec systemctl poweroff || loginctl poweroff"); sessionRoot.hide() }
onFocusChanged: { if (focus) sessionRoot.subtitle = buttonText }
KeyNavigation.left: sessionHibernate
KeyNavigation.right: sessionReboot
KeyNavigation.up: sessionSleep
}
SessionActionButton {
id: sessionReboot
buttonIcon: "restart_alt"
buttonText: qsTr("Reboot")
onClicked: { Hyprland.dispatch("exec reboot || loginctl reboot"); sessionRoot.hide() }
onFocusChanged: { if (focus) sessionRoot.subtitle = buttonText }
KeyNavigation.left: sessionShutdown
KeyNavigation.right: sessionFirmwareReboot
KeyNavigation.up: sessionLogout
}
SessionActionButton {
id: sessionFirmwareReboot
buttonIcon: "settings_applications"
buttonText: qsTr("Reboot to firmware settings")
onClicked: { Hyprland.dispatch("exec systemctl reboot --firmware-setup || loginctl reboot --firmware-setup"); sessionRoot.hide() }
onFocusChanged: { if (focus) sessionRoot.subtitle = buttonText }
KeyNavigation.up: sessionTaskManager
KeyNavigation.left: sessionReboot
}
}
Rectangle {
Layout.alignment: Qt.AlignHCenter
radius: Appearance.rounding.normal
implicitHeight: sessionSubtitle.implicitHeight + 10 * 2
implicitWidth: sessionSubtitle.implicitWidth + 15 * 2
color: Appearance.colors.colTooltip
clip: true
Behavior on implicitWidth {
SmoothedAnimation {
velocity: Appearance.animation.elementMoveFast.velocity
}
}
StyledText {
id: sessionSubtitle
anchors.centerIn: parent
color: Appearance.colors.colOnTooltip
text: sessionRoot.subtitle
}
}
}
PanelWindow { // Session menu
id: sessionRoot
visible: sessionLoader.active
property string subtitle
function hide() {
sessionLoader.active = false
} }
exclusionMode: ExclusionMode.Ignore
WlrLayershell.namespace: "quickshell:session"
WlrLayershell.layer: WlrLayer.Overlay
WlrLayershell.keyboardFocus: WlrKeyboardFocus.Exclusive
color: ColorUtils.transparentize(Appearance.m3colors.m3background, 0.3)
anchors {
top: true
left: true
right: true
}
implicitWidth: root.focusedScreen?.width ?? 0
implicitHeight: root.focusedScreen?.height ?? 0
MouseArea {
id: sessionMouseArea
anchors.fill: parent
onClicked: {
sessionRoot.hide()
}
}
ColumnLayout { // Content column
anchors.centerIn: parent
spacing: 15
Keys.onPressed: (event) => {
if (event.key === Qt.Key_Escape) {
sessionRoot.hide();
}
}
ColumnLayout {
Layout.alignment: Qt.AlignHCenter
spacing: 0
StyledText { // Title
Layout.alignment: Qt.AlignHCenter
horizontalAlignment: Text.AlignHCenter
font.family: Appearance.font.family.title
font.pixelSize: Appearance.font.pixelSize.title
font.weight: Font.DemiBold
text: qsTr("Session")
}
StyledText { // Small instruction
Layout.alignment: Qt.AlignHCenter
horizontalAlignment: Text.AlignHCenter
font.family: Appearance.font.family.title
font.pixelSize: Appearance.font.pixelSize.normal
text: qsTr("Arrow keys to navigate, Enter to select\nEsc or click anywhere to cancel")
}
}
RowLayout { // First row of buttons
spacing: 15
SessionActionButton {
id: sessionLock
focus: sessionRoot.visible
buttonIcon: "lock"
buttonText: qsTr("Lock")
onClicked: { Hyprland.dispatch("exec loginctl lock-session"); sessionRoot.hide() }
onFocusChanged: { if (focus) sessionRoot.subtitle = buttonText }
KeyNavigation.right: sessionSleep
KeyNavigation.down: sessionHibernate
}
SessionActionButton {
id: sessionSleep
buttonIcon: "dark_mode"
buttonText: qsTr("Sleep")
onClicked: { Hyprland.dispatch("exec systemctl suspend || loginctl suspend"); sessionRoot.hide() }
onFocusChanged: { if (focus) sessionRoot.subtitle = buttonText }
KeyNavigation.left: sessionLock
KeyNavigation.right: sessionLogout
KeyNavigation.down: sessionShutdown
}
SessionActionButton {
id: sessionLogout
buttonIcon: "logout"
buttonText: qsTr("Logout")
onClicked: { Hyprland.dispatch("exec pkill Hyprland"); sessionRoot.hide() }
onFocusChanged: { if (focus) sessionRoot.subtitle = buttonText }
KeyNavigation.left: sessionSleep
KeyNavigation.right: sessionTaskManager
KeyNavigation.down: sessionReboot
}
SessionActionButton {
id: sessionTaskManager
buttonIcon: "browse_activity"
buttonText: qsTr("Task Manager")
onClicked: { Hyprland.dispatch("exec gnome-system-monitor & disown"); sessionRoot.hide() }
onFocusChanged: { if (focus) sessionRoot.subtitle = buttonText }
KeyNavigation.left: sessionLogout
KeyNavigation.down: sessionFirmwareReboot
}
}
RowLayout { // Second row of buttons
spacing: 15
SessionActionButton {
id: sessionHibernate
buttonIcon: "downloading"
buttonText: qsTr("Hibernate")
onClicked: { Hyprland.dispatch("exec systemctl hibernate || loginctl hibernate"); sessionRoot.hide() }
onFocusChanged: { if (focus) sessionRoot.subtitle = buttonText }
KeyNavigation.up: sessionLock
KeyNavigation.right: sessionShutdown
}
SessionActionButton {
id: sessionShutdown
buttonIcon: "power_settings_new"
buttonText: qsTr("Shutdown")
onClicked: { Hyprland.dispatch("exec systemctl poweroff || loginctl poweroff"); sessionRoot.hide() }
onFocusChanged: { if (focus) sessionRoot.subtitle = buttonText }
KeyNavigation.left: sessionHibernate
KeyNavigation.right: sessionReboot
KeyNavigation.up: sessionSleep
}
SessionActionButton {
id: sessionReboot
buttonIcon: "restart_alt"
buttonText: qsTr("Reboot")
onClicked: { Hyprland.dispatch("exec reboot || loginctl reboot"); sessionRoot.hide() }
onFocusChanged: { if (focus) sessionRoot.subtitle = buttonText }
KeyNavigation.left: sessionShutdown
KeyNavigation.right: sessionFirmwareReboot
KeyNavigation.up: sessionLogout
}
SessionActionButton {
id: sessionFirmwareReboot
buttonIcon: "settings_applications"
buttonText: qsTr("Reboot to firmware settings")
onClicked: { Hyprland.dispatch("exec systemctl reboot --firmware-setup || loginctl reboot --firmware-setup"); sessionRoot.hide() }
onFocusChanged: { if (focus) sessionRoot.subtitle = buttonText }
KeyNavigation.up: sessionTaskManager
KeyNavigation.left: sessionReboot
}
}
Rectangle {
Layout.alignment: Qt.AlignHCenter
radius: Appearance.rounding.normal
implicitHeight: sessionSubtitle.implicitHeight + 10 * 2
implicitWidth: sessionSubtitle.implicitWidth + 15 * 2
color: Appearance.colors.colTooltip
clip: true
Behavior on implicitWidth {
animation: Appearance.animation.elementMove.numberAnimation.createObject(this)
}
StyledText {
id: sessionSubtitle
anchors.centerIn: parent
color: Appearance.colors.colOnTooltip
text: sessionRoot.subtitle
}
}
}
} }
} }
@@ -203,48 +195,33 @@ Scope {
target: "session" target: "session"
function toggle(): void { function toggle(): void {
for (let i = 0; i < sessionVariants.instances.length; i++) { sessionLoader.active = !sessionLoader.active;
let loader = sessionVariants.instances[i];
loader.active = !loader.active;
}
} }
function close(): void { function close(): void {
for (let i = 0; i < sessionVariants.instances.length; i++) { sessionLoader.active = false;
let loader = sessionVariants.instances[i];
loader.active = false;
}
} }
function open(): void { function open(): void {
for (let i = 0; i < sessionVariants.instances.length; i++) { sessionLoader.active = true;
let loader = sessionVariants.instances[i];
loader.active = true;
}
} }
} }
GlobalShortcut { GlobalShortcut {
name: "sessionToggle" name: "sessionToggle"
description: "Toggles session screen on press" description: qsTr("Toggles session screen on press")
onPressed: { onPressed: {
for (let i = 0; i < sessionVariants.instances.length; i++) { sessionLoader.active = !sessionLoader.active;
let loader = sessionVariants.instances[i];
loader.active = !loader.active;
}
} }
} }
GlobalShortcut { GlobalShortcut {
name: "sessionOpen" name: "sessionOpen"
description: "Opens session screen on press" description: qsTr("Opens session screen on press")
onPressed: { onPressed: {
for (let i = 0; i < sessionVariants.instances.length; i++) { sessionLoader.active = true;
let loader = sessionVariants.instances[i];
loader.active = !loader.active;
}
} }
} }
@@ -37,12 +37,7 @@ Button {
color: (button.down || button.keyboardDown) ? Appearance.colors.colLayer2Active : ((button.hovered || button.focus) ? Appearance.colors.colLayer2Hover : Appearance.colors.colLayer2) color: (button.down || button.keyboardDown) ? Appearance.colors.colLayer2Active : ((button.hovered || button.focus) ? Appearance.colors.colLayer2Hover : Appearance.colors.colLayer2)
Behavior on color { Behavior on color {
ColorAnimation { animation: Appearance.animation.elementMove.colorAnimation.createObject(this)
duration: Appearance.animation.elementMove.duration
easing.type: Appearance.animation.elementMove.type
easing.bezierCurve: Appearance.animation.elementMove.bezierCurve
}
} }
} }
@@ -18,7 +18,6 @@ Item {
id: root id: root
property var panelWindow property var panelWindow
property var inputField: messageInputField property var inputField: messageInputField
readonly property var messages: Ai.messages
property string commandPrefix: "/" property string commandPrefix: "/"
property string faviconDownloadPath: FileUtils.trimFileProtocol(`${XdgDirectories.cache}/media/favicons`) property string faviconDownloadPath: FileUtils.trimFileProtocol(`${XdgDirectories.cache}/media/favicons`)
@@ -31,7 +30,7 @@ Item {
onFocusChanged: (focus) => { onFocusChanged: (focus) => {
if (focus) { if (focus) {
messageInputField.forceActiveFocus() root.inputField.forceActiveFocus()
} }
} }
@@ -179,46 +178,42 @@ int main(int argc, char* argv[]) {
} }
add: Transition { add: Transition {
NumberAnimation { animations: [Appearance.animation.elementMoveEnter.numberAnimation.createObject(this, {
property: "opacity" property: "opacity",
from: 0; to: 1 from: 0,
duration: Appearance.animation.elementMoveEnter.duration to: 1
easing.type: Appearance.animation.elementMoveEnter.type })]
easing.bezierCurve: Appearance.animation.elementMoveEnter.bezierCurve
}
} }
remove: Transition { remove: Transition {
NumberAnimation { animations: [Appearance.animation.elementMoveEnter.numberAnimation.createObject(this, {
property: "opacity" property: "opacity",
from: 1; to: 0 from: 1,
duration: Appearance.animation.elementMoveEnter.duration to: 0
easing.type: Appearance.animation.elementMoveEnter.type })]
easing.bezierCurve: Appearance.animation.elementMoveEnter.bezierCurve
}
} }
model: ScriptModel { model: ScriptModel {
values: root.messages values: Ai.messageIDs
} }
delegate: AiMessage { delegate: AiMessage {
required property var modelData
required property int index
messageIndex: index messageIndex: index
messageData: modelData messageData: {
Ai.messageByID[modelData]
}
messageInputField: root.inputField messageInputField: root.inputField
faviconDownloadPath: root.faviconDownloadPath faviconDownloadPath: root.faviconDownloadPath
} }
} }
Item { // Placeholder when list is empty Item { // Placeholder when list is empty
opacity: root.messages.length === 0 ? 1 : 0 opacity: Ai.messageIDs.length === 0 ? 1 : 0
visible: opacity > 0 visible: opacity > 0
anchors.fill: parent anchors.fill: parent
Behavior on opacity { Behavior on opacity {
NumberAnimation { animation: Appearance.animation.elementMoveEnter.numberAnimation.createObject(this)
duration: Appearance.animation.elementMove.duration
easing.type: Appearance.animation.elementMove.type
easing.bezierCurve: Appearance.animation.elementMove.bezierCurve
}
} }
ColumnLayout { ColumnLayout {
@@ -356,11 +351,7 @@ int main(int argc, char* argv[]) {
border.width: 1 border.width: 1
Behavior on implicitHeight { Behavior on implicitHeight {
NumberAnimation { animation: Appearance.animation.elementMove.numberAnimation.createObject(this)
duration: Appearance.animation.elementMove.duration
easing.type: Appearance.animation.elementMove.type
easing.bezierCurve: Appearance.animation.elementMove.bezierCurve
}
} }
RowLayout { // Input field and send button RowLayout { // Input field and send button
@@ -383,7 +374,7 @@ int main(int argc, char* argv[]) {
placeholderText: StringUtils.format(qsTr('Message the model... "{0}" for commands'), root.commandPrefix) placeholderText: StringUtils.format(qsTr('Message the model... "{0}" for commands'), root.commandPrefix)
placeholderTextColor: Appearance.m3colors.m3outline placeholderTextColor: Appearance.m3colors.m3outline
background: Item {} background: null
onTextChanged: { // Handle suggestions onTextChanged: { // Handle suggestions
if(messageInputField.text.length === 0) { if(messageInputField.text.length === 0) {
@@ -474,11 +465,7 @@ int main(int argc, char* argv[]) {
Appearance.m3colors.m3primary) : Appearance.colors.colLayer2Disabled Appearance.m3colors.m3primary) : Appearance.colors.colLayer2Disabled
Behavior on color { Behavior on color {
ColorAnimation { animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this)
duration: Appearance.animation.elementMove.duration
easing.type: Appearance.animation.elementMove.type
easing.bezierCurve: Appearance.animation.elementMove.bezierCurve
}
} }
} }
@@ -566,11 +553,7 @@ int main(int argc, char* argv[]) {
Appearance.colors.colLayer2 Appearance.colors.colLayer2
Behavior on color { Behavior on color {
ColorAnimation { animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this)
duration: Appearance.animation.elementMove.duration
easing.type: Appearance.animation.elementMove.type
easing.bezierCurve: Appearance.animation.elementMove.bezierCurve
}
} }
} }
onClicked: { onClicked: {
@@ -4,6 +4,7 @@ import "root:/modules/common"
import "root:/modules/common/widgets" import "root:/modules/common/widgets"
import "root:/modules/common/functions/fuzzysort.js" as Fuzzy import "root:/modules/common/functions/fuzzysort.js" as Fuzzy
import "root:/modules/common/functions/string_utils.js" as StringUtils import "root:/modules/common/functions/string_utils.js" as StringUtils
import "root:/modules/common/functions/file_utils.js" as FileUtils
import "./anime/" import "./anime/"
import Qt.labs.platform import Qt.labs.platform
import QtQuick import QtQuick
@@ -19,9 +20,9 @@ Item {
property var panelWindow property var panelWindow
property var inputField: tagInputField property var inputField: tagInputField
readonly property var responses: Booru.responses readonly property var responses: Booru.responses
property string previewDownloadPath: `${XdgDirectories.cache}/media/waifus`.replace("file://", "") property string previewDownloadPath: FileUtils.trimFileProtocol(`${XdgDirectories.cache}/media/waifus`)
property string downloadPath: (XdgDirectories.pictures + "/homework").replace("file://", "") property string downloadPath: FileUtils.trimFileProtocol(XdgDirectories.pictures + "/homework")
property string nsfwPath: (XdgDirectories.pictures + "/homework/🌶️").replace("file://", "") property string nsfwPath: FileUtils.trimFileProtocol(XdgDirectories.pictures + "/homework/🌶️")
property string commandPrefix: "/" property string commandPrefix: "/"
property real scrollOnNewResponse: 100 property real scrollOnNewResponse: 100
property int tagSuggestionDelay: 210 property int tagSuggestionDelay: 210
@@ -37,7 +38,8 @@ Item {
} }
Component.onCompleted: { Component.onCompleted: {
Hyprland.dispatch(`exec rm -rf ${previewDownloadPath} && mkdir -p ${previewDownloadPath}`) Hyprland.dispatch(`exec rm -rf '${previewDownloadPath}' && mkdir -p '${previewDownloadPath}'`)
Hyprland.dispatch(`exec mkdir -p '${downloadPath}' && mkdir -p '${downloadPath}'`)
} }
property var allCommands: [ property var allCommands: [
@@ -114,12 +116,6 @@ Item {
} }
} }
Connections {
target: panelWindow
function onVisibleChanged(visible) {
tagInputField.forceActiveFocus()
}
}
onFocusChanged: (focus) => { onFocusChanged: (focus) => {
if (focus) { if (focus) {
tagInputField.forceActiveFocus() tagInputField.forceActiveFocus()
@@ -174,13 +170,11 @@ Item {
} }
add: Transition { add: Transition {
NumberAnimation { animations: [Appearance.animation.elementMoveEnter.numberAnimation.createObject(this, {
property: "opacity" property: "opacity",
from: 0; to: 1 from: 0,
duration: Appearance.animation.elementMoveEnter.duration to: 1
easing.type: Appearance.animation.elementMoveEnter.type })]
easing.bezierCurve: Appearance.animation.elementMoveEnter.bezierCurve
}
} }
model: ScriptModel { model: ScriptModel {
@@ -208,11 +202,7 @@ Item {
anchors.fill: parent anchors.fill: parent
Behavior on opacity { Behavior on opacity {
NumberAnimation { animation: Appearance.animation.elementMoveEnter.numberAnimation.createObject(this)
duration: Appearance.animation.elementMove.duration
easing.type: Appearance.animation.elementMove.type
easing.bezierCurve: Appearance.animation.elementMove.bezierCurve
}
} }
ColumnLayout { ColumnLayout {
@@ -246,11 +236,7 @@ Item {
visible: opacity > 0 visible: opacity > 0
Behavior on opacity { Behavior on opacity {
NumberAnimation { animation: Appearance.animation.elementMoveEnter.numberAnimation.createObject(this)
duration: Appearance.animation.elementMove.duration
easing.type: Appearance.animation.elementMove.type
easing.bezierCurve: Appearance.animation.elementMove.bezierCurve
}
} }
Rectangle { Rectangle {
@@ -392,11 +378,7 @@ Item {
border.width: 1 border.width: 1
Behavior on implicitHeight { Behavior on implicitHeight {
NumberAnimation { animation: Appearance.animation.elementMove.numberAnimation.createObject(this)
duration: Appearance.animation.elementMove.duration
easing.type: Appearance.animation.elementMove.type
easing.bezierCurve: Appearance.animation.elementMove.bezierCurve
}
} }
RowLayout { // Input field and send button RowLayout { // Input field and send button
@@ -419,7 +401,7 @@ Item {
placeholderText: StringUtils.format(qsTr('Enter tags, or "{0}" for commands'), root.commandPrefix) placeholderText: StringUtils.format(qsTr('Enter tags, or "{0}" for commands'), root.commandPrefix)
placeholderTextColor: Appearance.m3colors.m3outline placeholderTextColor: Appearance.m3colors.m3outline
background: Item {} background: null
property Timer searchTimer: Timer { // Timer for tag suggestions property Timer searchTimer: Timer { // Timer for tag suggestions
interval: root.tagSuggestionDelay interval: root.tagSuggestionDelay
@@ -530,11 +512,7 @@ Item {
Appearance.m3colors.m3primary) : Appearance.colors.colLayer2Disabled Appearance.m3colors.m3primary) : Appearance.colors.colLayer2Disabled
Behavior on color { Behavior on color {
ColorAnimation { animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this)
duration: Appearance.animation.elementMove.duration
easing.type: Appearance.animation.elementMove.type
easing.bezierCurve: Appearance.animation.elementMove.bezierCurve
}
} }
} }
@@ -666,11 +644,7 @@ Item {
Appearance.colors.colLayer2 Appearance.colors.colLayer2
Behavior on color { Behavior on color {
ColorAnimation { animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this)
duration: Appearance.animation.elementMove.duration
easing.type: Appearance.animation.elementMove.type
easing.bezierCurve: Appearance.animation.elementMove.bezierCurve
}
} }
} }
onClicked: { onClicked: {
@@ -5,6 +5,7 @@ import "root:/modules/common/widgets"
import QtQuick import QtQuick
import QtQuick.Controls import QtQuick.Controls
import QtQuick.Layouts import QtQuick.Layouts
import QtQuick.Effects
import Qt5Compat.GraphicalEffects import Qt5Compat.GraphicalEffects
import Quickshell.Io import Quickshell.Io
import Quickshell import Quickshell
@@ -17,30 +18,31 @@ Scope { // Scope
property int sidebarPadding: 15 property int sidebarPadding: 15
property var tabButtonList: [{"icon": "neurology", "name": qsTr("Intelligence")}, {"icon": "bookmark_heart", "name": qsTr("Anime")}] property var tabButtonList: [{"icon": "neurology", "name": qsTr("Intelligence")}, {"icon": "bookmark_heart", "name": qsTr("Anime")}]
Variants { // Window repeater Loader {
id: sidebarVariants id: sidebarLoader
model: Quickshell.screens active: false
onActiveChanged: {
GlobalStates.sidebarLeftOpen = sidebarLoader.active
}
PanelWindow { // Window PanelWindow { // Window
id: sidebarRoot id: sidebarRoot
visible: false visible: sidebarLoader.active
focusable: true
property int selectedTab: PersistentStates.sidebar.leftSide.selectedTab property int selectedTab: 0
property bool extend: false property bool extend: false
property bool pin: false
property real sidebarWidth: sidebarRoot.extend ? Appearance.sizes.sidebarWidthExtended : Appearance.sizes.sidebarWidth property real sidebarWidth: sidebarRoot.extend ? Appearance.sizes.sidebarWidthExtended : Appearance.sizes.sidebarWidth
onVisibleChanged: { function hide() {
GlobalStates.sidebarLeftOpenCount += visible ? 1 : -1 sidebarLoader.active = false
} }
property var modelData exclusiveZone: sidebarRoot.pin ? sidebarWidth : 0
screen: modelData
exclusiveZone: 0
implicitWidth: Appearance.sizes.sidebarWidthExtended implicitWidth: Appearance.sizes.sidebarWidthExtended
WlrLayershell.namespace: "quickshell:sidebarLeft" WlrLayershell.namespace: "quickshell:sidebarLeft"
// Hyprland 0.49: Focus is always exclusive and setting this breaks mouse focus grab // Hyprland 0.49: OnDemand is Exclusive, Exclusive just breaks click-outside-to-close
// WlrLayershell.keyboardFocus: WlrKeyboardFocus.Exclusive WlrLayershell.keyboardFocus: WlrKeyboardFocus.OnDemand
color: "transparent" color: "transparent"
anchors { anchors {
@@ -56,26 +58,12 @@ Scope { // Scope
HyprlandFocusGrab { // Click outside to close HyprlandFocusGrab { // Click outside to close
id: grab id: grab
windows: [ sidebarRoot ] windows: [ sidebarRoot ]
active: false active: sidebarRoot.visible && !sidebarRoot.pin
onActiveChanged: { // Focus the selected tab
if (active) swipeView.currentItem.forceActiveFocus()
}
onCleared: () => { onCleared: () => {
if (!active) sidebarRoot.visible = false if (!active) sidebarRoot.hide()
}
}
Connections {
target: sidebarRoot
function onVisibleChanged() {
delayedGrabTimer.start()
swipeView.children[0].children[0].children[sidebarRoot.selectedTab].forceActiveFocus()
}
}
Timer {
id: delayedGrabTimer
interval: ConfigOptions.hacks.arbitraryRaceConditionDelay
repeat: false
onTriggered: {
grab.active = sidebarRoot.visible
} }
} }
@@ -87,41 +75,50 @@ Scope { // Scope
anchors.left: parent.left anchors.left: parent.left
anchors.topMargin: Appearance.sizes.hyprlandGapsOut anchors.topMargin: Appearance.sizes.hyprlandGapsOut
anchors.leftMargin: Appearance.sizes.hyprlandGapsOut anchors.leftMargin: Appearance.sizes.hyprlandGapsOut
width: sidebarWidth - Appearance.sizes.hyprlandGapsOut * 2 width: sidebarRoot.sidebarWidth - Appearance.sizes.hyprlandGapsOut * 2
height: parent.height - Appearance.sizes.hyprlandGapsOut * 2 height: parent.height - Appearance.sizes.hyprlandGapsOut * 2
color: Appearance.colors.colLayer0 color: Appearance.colors.colLayer0
radius: Appearance.rounding.screenRounding - Appearance.sizes.elevationMargin + 1 radius: Appearance.rounding.screenRounding - Appearance.sizes.elevationMargin + 1
focus: sidebarRoot.visible focus: sidebarRoot.visible
layer.enabled: true
layer.effect: MultiEffect {
source: sidebarLeftBackground
anchors.fill: sidebarLeftBackground
shadowEnabled: true
shadowColor: Appearance.colors.colShadow
shadowVerticalOffset: 1
shadowBlur: 0.5
}
Behavior on width { Behavior on width {
NumberAnimation { animation: Appearance.animation.elementMove.numberAnimation.createObject(this)
duration: Appearance.animation.elementMove.duration
easing.type: Appearance.animation.elementMove.type
easing.bezierCurve: Appearance.animation.elementMove.bezierCurve
}
} }
Keys.onPressed: (event) => { Keys.onPressed: (event) => {
// console.log("Key pressed: " + event.key) // console.log("Key pressed: " + event.key)
if (event.key === Qt.Key_Escape) { if (event.key === Qt.Key_Escape) {
sidebarRoot.visible = false; sidebarRoot.hide();
} }
if (event.modifiers === Qt.ControlModifier) { if (event.modifiers === Qt.ControlModifier) {
if (event.key === Qt.Key_PageDown) { if (event.key === Qt.Key_PageDown) {
PersistentStateManager.setState("sidebar.leftSide.selectedTab", Math.min(sidebarRoot.selectedTab + 1, root.tabButtonList.length - 1)) sidebarRoot.selectedTab = Math.min(sidebarRoot.selectedTab + 1, root.tabButtonList.length - 1)
} }
else if (event.key === Qt.Key_PageUp) { else if (event.key === Qt.Key_PageUp) {
PersistentStateManager.setState("sidebar.leftSide.selectedTab", Math.max(sidebarRoot.selectedTab - 1, 0)) sidebarRoot.selectedTab = Math.max(sidebarRoot.selectedTab - 1, 0)
} }
else if (event.key === Qt.Key_Tab) { else if (event.key === Qt.Key_Tab) {
PersistentStateManager.setState("sidebar.leftSide.selectedTab", (sidebarRoot.selectedTab + 1) % root.tabButtonList.length); sidebarRoot.selectedTab = (sidebarRoot.selectedTab + 1) % root.tabButtonList.length;
} }
else if (event.key === Qt.Key_Backtab) { else if (event.key === Qt.Key_Backtab) {
PersistentStateManager.setState("sidebar.leftSide.selectedTab", (sidebarRoot.selectedTab - 1 + root.tabButtonList.length) % root.tabButtonList.length); sidebarRoot.selectedTab = (sidebarRoot.selectedTab - 1 + root.tabButtonList.length) % root.tabButtonList.length;
} }
else if (event.key === Qt.Key_O) { else if (event.key === Qt.Key_O) {
sidebarRoot.extend = !sidebarRoot.extend; sidebarRoot.extend = !sidebarRoot.extend;
} }
else if (event.key === Qt.Key_P) {
sidebarRoot.pin = !sidebarRoot.pin;
}
event.accepted = true; event.accepted = true;
} }
} }
@@ -137,7 +134,7 @@ Scope { // Scope
tabButtonList: root.tabButtonList tabButtonList: root.tabButtonList
externalTrackedTab: sidebarRoot.selectedTab externalTrackedTab: sidebarRoot.selectedTab
function onCurrentIndexChanged(currentIndex) { function onCurrentIndexChanged(currentIndex) {
PersistentStateManager.setState("sidebar.leftSide.selectedTab", currentIndex) sidebarRoot.selectedTab = currentIndex
} }
} }
@@ -146,10 +143,12 @@ Scope { // Scope
Layout.topMargin: 5 Layout.topMargin: 5
Layout.fillWidth: true Layout.fillWidth: true
Layout.fillHeight: true Layout.fillHeight: true
currentIndex: sidebarRoot.selectedTab spacing: 10
currentIndex: tabBar.externalTrackedTab
onCurrentIndexChanged: { onCurrentIndexChanged: {
tabBar.enableIndicatorAnimation = true tabBar.enableIndicatorAnimation = true
PersistentStateManager.setState("sidebar.leftSide.selectedTab", currentIndex) sidebarRoot.selectedTab = currentIndex
} }
clip: true clip: true
@@ -173,95 +172,53 @@ Scope { // Scope
} }
} }
// Shadow
DropShadow {
anchors.fill: sidebarLeftBackground
horizontalOffset: 0
verticalOffset: 2
radius: Appearance.sizes.elevationMargin
samples: Appearance.sizes.elevationMargin * 2 + 1 // Ideally should be 2 * radius + 1, see qt docs
color: Appearance.colors.colShadow
source: sidebarLeftBackground
}
} }
} }
IpcHandler { IpcHandler {
target: "sidebarLeft" target: "sidebarLeft"
function toggle(): void { function toggle(): void {
for (let i = 0; i < sidebarVariants.instances.length; i++) { sidebarLoader.active = !sidebarLoader.active
let panelWindow = sidebarVariants.instances[i]; if(sidebarLoader.active) Notifications.timeoutAll();
if (panelWindow.modelData.name == Hyprland.focusedMonitor.name) {
panelWindow.visible = !panelWindow.visible;
if(panelWindow.visible) Notifications.timeoutAll();
}
}
} }
function close(): void { function close(): void {
for (let i = 0; i < sidebarVariants.instances.length; i++) { sidebarLoader.active = false
let panelWindow = sidebarVariants.instances[i];
if (panelWindow.modelData.name == Hyprland.focusedMonitor.name) {
panelWindow.visible = false;
}
}
} }
function open(): void { function open(): void {
for (let i = 0; i < sidebarVariants.instances.length; i++) { sidebarLoader.active = true
let panelWindow = sidebarVariants.instances[i]; if(sidebarLoader.active) Notifications.timeoutAll();
if (panelWindow.modelData.name == Hyprland.focusedMonitor.name) {
panelWindow.visible = true;
if(panelWindow.visible) Notifications.timeoutAll();
}
}
} }
} }
GlobalShortcut { GlobalShortcut {
name: "sidebarLeftToggle" name: "sidebarLeftToggle"
description: "Toggles left sidebar on press" description: qsTr("Toggles left sidebar on press")
onPressed: { onPressed: {
for (let i = 0; i < sidebarVariants.instances.length; i++) { sidebarLoader.active = !sidebarLoader.active;
let panelWindow = sidebarVariants.instances[i]; if(sidebarLoader.active) Notifications.timeoutAll();
if (panelWindow.modelData.name == Hyprland.focusedMonitor.name) {
panelWindow.visible = !panelWindow.visible;
if(panelWindow.visible) Notifications.timeoutAll();
}
}
} }
} }
GlobalShortcut { GlobalShortcut {
name: "sidebarLeftOpen" name: "sidebarLeftOpen"
description: "Opens left sidebar on press" description: qsTr("Opens left sidebar on press")
onPressed: { onPressed: {
for (let i = 0; i < sidebarVariants.instances.length; i++) { sidebarLoader.active = true;
let panelWindow = sidebarVariants.instances[i]; if(sidebarLoader.active) Notifications.timeoutAll();
if (panelWindow.modelData.name == Hyprland.focusedMonitor.name) {
panelWindow.visible = true;
if(panelWindow.visible) Notifications.timeoutAll();
}
}
} }
} }
GlobalShortcut { GlobalShortcut {
name: "sidebarLeftClose" name: "sidebarLeftClose"
description: "Closes left sidebar on press" description: qsTr("Closes left sidebar on press")
onPressed: { onPressed: {
for (let i = 0; i < sidebarVariants.instances.length; i++) { sidebarLoader.active = false;
let panelWindow = sidebarVariants.instances[i];
if (panelWindow.modelData.name == Hyprland.focusedMonitor.name) {
panelWindow.visible = false;
}
}
} }
} }
@@ -151,7 +151,7 @@ Rectangle {
color: Appearance.m3colors.m3onSecondaryContainer color: Appearance.m3colors.m3onSecondaryContainer
text: messageData.role == 'assistant' ? Ai.models[messageData.model].name : text: messageData.role == 'assistant' ? Ai.models[messageData.model].name :
(messageData.role == 'user' && SystemInfo.username) ? SystemInfo.username : (messageData.role == 'user' && SystemInfo.username) ? SystemInfo.username :
(messageData.role == 'interface') ? qsTr("Interface") : qsTr("Unknown") qsTr("Interface")
} }
} }
} }
@@ -1,6 +1,7 @@
import "root:/modules/common" import "root:/modules/common"
import "root:/modules/common/widgets" import "root:/modules/common/widgets"
import "root:/services" import "root:/services"
import "root:/modules/common/functions/color_utils.js" as ColorUtils
import QtQuick import QtQuick
import QtQuick.Controls import QtQuick.Controls
import QtQuick.Layouts import QtQuick.Layouts
@@ -18,20 +19,16 @@ Button {
background: Rectangle { background: Rectangle {
radius: Appearance.rounding.small radius: Appearance.rounding.small
color: !button.enabled ? Appearance.transparentize(Appearance.m3colors.m3surfaceContainerHighest, 1) : color: !button.enabled ? ColorUtils.transparentize(Appearance.m3colors.m3surfaceContainerHighest, 1) :
button.activated ? (button.down ? Appearance.colors.colPrimaryActive : button.activated ? (button.down ? Appearance.colors.colPrimaryActive :
button.hovered ? Appearance.colors.colPrimaryHover : button.hovered ? Appearance.colors.colPrimaryHover :
Appearance.m3colors.m3primary) : Appearance.m3colors.m3primary) :
(button.down ? Appearance.colors.colSurfaceContainerHighestActive : (button.down ? Appearance.colors.colSurfaceContainerHighestActive :
button.hovered ? Appearance.colors.colSurfaceContainerHighestHover : button.hovered ? Appearance.colors.colSurfaceContainerHighestHover :
Appearance.transparentize(Appearance.m3colors.m3surfaceContainerHighest, 1)) ColorUtils.transparentize(Appearance.m3colors.m3surfaceContainerHighest, 1))
Behavior on color { Behavior on color {
ColorAnimation { animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this)
duration: Appearance.animation.elementMoveFast.duration
easing.type: Appearance.animation.elementMoveFast.type
easing.bezierCurve: Appearance.animation.elementMoveFast.bezierCurve
}
} }
} }
@@ -44,11 +41,7 @@ Button {
Appearance.colors.colOnLayer1Inactive Appearance.colors.colOnLayer1Inactive
Behavior on color { Behavior on color {
ColorAnimation { animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this)
duration: Appearance.animation.elementMoveFast.duration
easing.type: Appearance.animation.elementMoveFast.type
easing.bezierCurve: Appearance.animation.elementMoveFast.bezierCurve
}
} }
} }
} }
@@ -6,6 +6,7 @@ import "root:/modules/common/"
import "root:/modules/common/widgets" import "root:/modules/common/widgets"
import "../" import "../"
import "root:/modules/common/functions/string_utils.js" as StringUtils import "root:/modules/common/functions/string_utils.js" as StringUtils
import "root:/modules/common/functions/color_utils.js" as ColorUtils
import QtQuick import QtQuick
import QtQuick.Controls import QtQuick.Controls
import QtQuick.Layouts import QtQuick.Layouts
@@ -98,7 +99,7 @@ Item {
id: thinkBlockLanguage id: thinkBlockLanguage
Layout.fillWidth: false Layout.fillWidth: false
Layout.alignment: Qt.AlignLeft Layout.alignment: Qt.AlignLeft
text: root.completed ? "Chain of Thought" : ("Thinking" + ".".repeat(Math.random() * 4)) text: root.completed ? qsTr("Chain of Thought") : (qsTr("Thinking") + ".".repeat(Math.random() * 4))
} }
Item { Layout.fillWidth: true } Item { Layout.fillWidth: true }
Button { // Expand button Button { // Expand button
@@ -117,7 +118,7 @@ Item {
radius: Appearance.rounding.full radius: Appearance.rounding.full
color: (headerMouseArea.pressed) ? Appearance.colors.colLayer2Active color: (headerMouseArea.pressed) ? Appearance.colors.colLayer2Active
: (headerMouseArea.containsMouse ? Appearance.colors.colLayer2Hover : (headerMouseArea.containsMouse ? Appearance.colors.colLayer2Hover
: Appearance.transparentize(Appearance.colors.colLayer2, 1)) : ColorUtils.transparentize(Appearance.colors.colLayer2, 1))
Behavior on color { Behavior on color {
ColorAnimation { ColorAnimation {
@@ -2,15 +2,17 @@ import "root:/"
import "root:/modules/common" import "root:/modules/common"
import "root:/modules/common/widgets" import "root:/modules/common/widgets"
import "root:/modules/common/functions/string_utils.js" as StringUtils import "root:/modules/common/functions/string_utils.js" as StringUtils
import "root:/modules/common/functions/color_utils.js" as ColorUtils
import QtQml import QtQml
import Qt.labs.platform import Qt.labs.platform
import QtQuick import QtQuick
import QtQuick.Controls import QtQuick.Controls
import QtQuick.Layouts import QtQuick.Layouts
import QtQuick.Effects
import Qt5Compat.GraphicalEffects
import Quickshell import Quickshell
import Quickshell.Io import Quickshell.Io
import Quickshell.Hyprland import Quickshell.Hyprland
import Qt5Compat.GraphicalEffects
Button { Button {
id: root id: root
@@ -78,11 +80,7 @@ Button {
} }
Behavior on opacity { Behavior on opacity {
NumberAnimation { animation: Appearance.animation.elementMoveEnter.numberAnimation.createObject(this)
duration: Appearance.animation.elementMoveEnter.duration
easing.type: Appearance.animation.elementMoveEnter.type
easing.bezierCurve: Appearance.animation.elementMoveEnter.bezierCurve
}
} }
} }
@@ -97,13 +95,13 @@ Button {
PointingHandInteraction {} PointingHandInteraction {}
StyledToolTip { StyledToolTip {
content: `${StringUtils.wordWrap(root.imageData.tags, root.maxTagStringLineLength)}\nClick for options` content: `${StringUtils.wordWrap(root.imageData.tags, root.maxTagStringLineLength)}\n${qsTr("Click for options")}`
} }
background: Rectangle { background: Rectangle {
color: menuButton.down ? Appearance.transparentize(Appearance.mix(Appearance.m3colors.m3surface, Appearance.m3colors.m3onSurface, 0.6), 0.1) : color: menuButton.down ? ColorUtils.transparentize(ColorUtils.mix(Appearance.m3colors.m3surface, Appearance.m3colors.m3onSurface, 0.6), 0.1) :
menuButton.hovered ? Appearance.transparentize(Appearance.mix(Appearance.m3colors.m3surface, Appearance.m3colors.m3onSurface, 0.8), 0.2) : menuButton.hovered ? ColorUtils.transparentize(ColorUtils.mix(Appearance.m3colors.m3surface, Appearance.m3colors.m3onSurface, 0.8), 0.2) :
Appearance.transparentize(Appearance.m3colors.m3surface, 0.3) ColorUtils.transparentize(Appearance.m3colors.m3surface, 0.3)
radius: Appearance.rounding.full radius: Appearance.rounding.full
} }
@@ -140,6 +138,16 @@ Button {
implicitHeight: contextMenuColumnLayout.implicitHeight + radius * 2 implicitHeight: contextMenuColumnLayout.implicitHeight + radius * 2
implicitWidth: contextMenuColumnLayout.implicitWidth implicitWidth: contextMenuColumnLayout.implicitWidth
layer.enabled: true
layer.effect: MultiEffect {
source: contextMenu
anchors.fill: contextMenu
shadowEnabled: true
shadowColor: Appearance.colors.colShadow
shadowVerticalOffset: 1
shadowBlur: 0.5
}
Behavior on opacity { Behavior on opacity {
NumberAnimation { NumberAnimation {
duration: Appearance.animation.elementMoveFast.duration duration: Appearance.animation.elementMoveFast.duration
@@ -180,7 +188,7 @@ Button {
MenuButton { MenuButton {
id: downloadButton id: downloadButton
Layout.fillWidth: true Layout.fillWidth: true
buttonText: "Download" buttonText: qsTr("Download")
onClicked: { onClicked: {
root.showActions = false root.showActions = false
Hyprland.dispatch(`exec curl '${root.imageData.file_url}' -o '${root.imageData.is_nsfw ? root.nsfwPath : root.downloadPath}/${root.fileName}' && notify-send '${qsTr("Download complete")}' '${root.downloadPath}/${root.fileName}'`) Hyprland.dispatch(`exec curl '${root.imageData.file_url}' -o '${root.imageData.is_nsfw ? root.nsfwPath : root.downloadPath}/${root.fileName}' && notify-send '${qsTr("Download complete")}' '${root.downloadPath}/${root.fileName}'`)
@@ -188,26 +196,6 @@ Button {
} }
} }
} }
DropShadow {
opacity: root.showActions ? 1 : 0
visible: opacity > 0
anchors.fill: contextMenu
source: contextMenu
radius: Appearance.sizes.elevationMargin
samples: radius * 2 + 1
color: Appearance.colors.colShadow
verticalOffset: 2
horizontalOffset: 0
Behavior on opacity {
NumberAnimation {
duration: Appearance.animation.elementMoveFast.duration
easing.type: Appearance.animation.elementMoveFast.type
easing.bezierCurve: Appearance.animation.elementMoveFast.bezierCurve
}
}
}
} }
} }
} }
@@ -2,6 +2,7 @@ import "root:/"
import "root:/services" import "root:/services"
import "root:/modules/common" import "root:/modules/common"
import "root:/modules/common/widgets" import "root:/modules/common/widgets"
import "root:/modules/common/functions/string_utils.js" as StringUtils
import "../" import "../"
import QtQuick import QtQuick
import QtQuick.Controls import QtQuick.Controls
@@ -93,7 +94,8 @@ Rectangle {
anchors.centerIn: parent anchors.centerIn: parent
font.pixelSize: Appearance.font.pixelSize.smaller font.pixelSize: Appearance.font.pixelSize.smaller
color: Appearance.colors.colOnLayer2 color: Appearance.colors.colOnLayer2
text: `Page ${root.responseData.page}` // text: `Page ${root.responseData.page}`
text: StringUtils.format(qsTr("Page {0}"), root.responseData.page)
} }
} }
} }
@@ -120,18 +122,10 @@ Rectangle {
} }
Behavior on height { Behavior on height {
NumberAnimation { animation: Appearance.animation.elementMove.numberAnimation.createObject(this)
duration: Appearance.animation.elementMove.duration
easing.type: Appearance.animation.elementMove.type
easing.bezierCurve: Appearance.animation.elementMove.bezierCurve
}
} }
Behavior on implicitHeight { Behavior on implicitHeight {
NumberAnimation { animation: Appearance.animation.elementMove.numberAnimation.createObject(this)
duration: Appearance.animation.elementMove.duration
easing.type: Appearance.animation.elementMove.type
easing.bezierCurve: Appearance.animation.elementMove.bezierCurve
}
} }
RowLayout { RowLayout {
@@ -16,27 +16,23 @@ Rectangle {
radius: Appearance.rounding.normal radius: Appearance.rounding.normal
color: Appearance.colors.colLayer1 color: Appearance.colors.colLayer1
property int selectedTab: PersistentStates.sidebar.centerGroup.selectedTab property int selectedTab: 0
property var tabButtonList: [{"icon": "notifications", "name": qsTr("Notifications")}, {"icon": "volume_up", "name": qsTr("Volume mixer")}] property var tabButtonList: [{"icon": "notifications", "name": qsTr("Notifications")}, {"icon": "volume_up", "name": qsTr("Volume mixer")}]
onSelectedTabChanged: {
PersistentStateManager.setState("sidebar.centerGroup.selectedTab", selectedTab)
}
Keys.onPressed: (event) => { Keys.onPressed: (event) => {
if (event.key === Qt.Key_PageDown || event.key === Qt.Key_PageUp) { if (event.key === Qt.Key_PageDown || event.key === Qt.Key_PageUp) {
if (event.key === Qt.Key_PageDown) { if (event.key === Qt.Key_PageDown) {
PersistentStateManager.setState("sidebar.centerGroup.selectedTab", Math.min(root.selectedTab + 1, root.tabButtonList.length - 1)) root.selectedTab = Math.min(root.selectedTab + 1, root.tabButtonList.length - 1)
} else if (event.key === Qt.Key_PageUp) { } else if (event.key === Qt.Key_PageUp) {
PersistentStateManager.setState("sidebar.centerGroup.selectedTab", Math.max(root.selectedTab - 1, 0)) root.selectedTab = Math.max(root.selectedTab - 1, 0)
} }
event.accepted = true; event.accepted = true;
} }
if (event.modifiers === Qt.ControlModifier) { if (event.modifiers === Qt.ControlModifier) {
if (event.key === Qt.Key_Tab) { if (event.key === Qt.Key_Tab) {
PersistentStateManager.setState("sidebar.centerGroup.selectedTab", (root.selectedTab + 1) % root.tabButtonList.length); root.selectedTab = (root.selectedTab + 1) % root.tabButtonList.length
} else if (event.key === Qt.Key_Backtab) { } else if (event.key === Qt.Key_Backtab) {
PersistentStateManager.setState("sidebar.centerGroup.selectedTab", (root.selectedTab - 1 + root.tabButtonList.length) % root.tabButtonList.length); root.selectedTab = (root.selectedTab - 1 + root.tabButtonList.length) % root.tabButtonList.length
} }
event.accepted = true; event.accepted = true;
} }
@@ -53,7 +49,7 @@ Rectangle {
externalTrackedTab: root.selectedTab externalTrackedTab: root.selectedTab
function onCurrentIndexChanged(currentIndex) { function onCurrentIndexChanged(currentIndex) {
PersistentStateManager.setState("sidebar.centerGroup.selectedTab", currentIndex) root.selectedTab = currentIndex
} }
} }
@@ -62,10 +58,11 @@ Rectangle {
Layout.topMargin: 5 Layout.topMargin: 5
Layout.fillWidth: true Layout.fillWidth: true
Layout.fillHeight: true Layout.fillHeight: true
spacing: 10
currentIndex: root.selectedTab currentIndex: root.selectedTab
onCurrentIndexChanged: { onCurrentIndexChanged: {
tabBar.enableIndicatorAnimation = true tabBar.enableIndicatorAnimation = true
PersistentStateManager.setState("sidebar.centerGroup.selectedTab", currentIndex) root.selectedTab = currentIndex
} }
clip: true clip: true
@@ -2,10 +2,12 @@ import "root:/"
import "root:/services" import "root:/services"
import "root:/modules/common" import "root:/modules/common"
import "root:/modules/common/widgets" import "root:/modules/common/widgets"
import "root:/modules/common/functions/string_utils.js" as StringUtils
import "./quickToggles/" import "./quickToggles/"
import QtQuick import QtQuick
import QtQuick.Controls import QtQuick.Controls
import QtQuick.Layouts import QtQuick.Layouts
import QtQuick.Effects
import Qt5Compat.GraphicalEffects import Qt5Compat.GraphicalEffects
import Quickshell.Io import Quickshell.Io
import Quickshell import Quickshell
@@ -17,251 +19,207 @@ Scope {
property int sidebarWidth: Appearance.sizes.sidebarWidth property int sidebarWidth: Appearance.sizes.sidebarWidth
property int sidebarPadding: 15 property int sidebarPadding: 15
Variants { Loader {
id: sidebarVariants id: sidebarLoader
model: Quickshell.screens active: false
onActiveChanged: {
Loader { GlobalStates.sidebarRightOpen = sidebarLoader.active
id: sidebarLoader
active: false
property var modelData
onActiveChanged: {
GlobalStates.sidebarRightOpenCount += active ? 1 : -1
}
PanelWindow {
id: sidebarRoot
visible: sidebarLoader.active
focusable: true
onVisibleChanged: {
if (!visible) sidebarLoader.active = false
}
function hide() {
sidebarLoader.active = false
}
screen: modelData
exclusiveZone: 0
implicitWidth: sidebarWidth
WlrLayershell.namespace: "quickshell:sidebarRight"
// Hyprland 0.49: Focus is always exclusive and setting this breaks mouse focus grab
// WlrLayershell.keyboardFocus: WlrKeyboardFocus.Exclusive
color: "transparent"
anchors {
top: true
right: true
bottom: true
}
HyprlandFocusGrab {
id: grab
windows: [ sidebarRoot ]
active: false
onCleared: () => {
if (!active) sidebarRoot.hide()
}
}
Connections {
target: sidebarRoot
function onVisibleChanged() {
delayedGrabTimer.start()
}
}
Timer {
id: delayedGrabTimer
interval: ConfigOptions.hacks.arbitraryRaceConditionDelay
repeat: false
onTriggered: {
grab.active = sidebarRoot.visible
}
}
// Background
Rectangle {
id: sidebarRightBackground
anchors.centerIn: parent
width: parent.width - Appearance.sizes.hyprlandGapsOut * 2
height: parent.height - Appearance.sizes.hyprlandGapsOut * 2
color: Appearance.colors.colLayer0
radius: Appearance.rounding.screenRounding - Appearance.sizes.elevationMargin + 1
Keys.onPressed: (event) => {
if (event.key === Qt.Key_Escape) {
sidebarRoot.hide();
}
}
ColumnLayout {
anchors.fill: parent
anchors.margins: sidebarPadding
spacing: sidebarPadding
RowLayout {
Layout.fillHeight: false
spacing: 10
Layout.margins: 10
Layout.topMargin: 5
Layout.bottomMargin: 0
Item {
implicitWidth: distroIcon.width
implicitHeight: distroIcon.height
CustomIcon {
id: distroIcon
width: 25
height: 25
source: SystemInfo.distroIcon
}
ColorOverlay {
anchors.fill: distroIcon
source: distroIcon
color: Appearance.colors.colOnLayer0
}
}
StyledText {
font.pixelSize: Appearance.font.pixelSize.normal
color: Appearance.colors.colOnLayer0
text: `Uptime: ${DateTime.uptime}`
textFormat: Text.MarkdownText
}
Item {
Layout.fillWidth: true
}
QuickToggleButton {
toggled: false
buttonIcon: "power_settings_new"
onClicked: {
Hyprland.dispatch("global quickshell:sessionOpen")
}
StyledToolTip {
content: qsTr("Session")
}
}
}
Rectangle {
Layout.alignment: Qt.AlignHCenter
Layout.fillHeight: false
radius: Appearance.rounding.full
color: Appearance.colors.colLayer1
implicitWidth: sidebarQuickControlsRow.implicitWidth + 10
implicitHeight: sidebarQuickControlsRow.implicitHeight + 10
RowLayout {
id: sidebarQuickControlsRow
anchors.fill: parent
anchors.margins: 5
spacing: 5
NetworkToggle {}
BluetoothToggle {}
NightLight {}
GameMode {}
IdleInhibitor {}
}
}
// Center widget group
CenterWidgetGroup {
focus: sidebarRoot.visible
Layout.alignment: Qt.AlignHCenter
Layout.fillHeight: true
Layout.fillWidth: true
}
BottomWidgetGroup {
Layout.alignment: Qt.AlignHCenter
Layout.fillHeight: false
Layout.fillWidth: true
Layout.preferredHeight: implicitHeight
}
}
}
// Shadow
DropShadow {
anchors.fill: sidebarRightBackground
horizontalOffset: 0
verticalOffset: 2
radius: Appearance.sizes.elevationMargin
samples: Appearance.sizes.elevationMargin * 2 + 1 // Ideally should be 2 * radius + 1, see qt docs
color: Appearance.colors.colShadow
source: sidebarRightBackground
}
}
} }
PanelWindow {
id: sidebarRoot
visible: sidebarLoader.active
function hide() {
sidebarLoader.active = false
}
exclusiveZone: 0
implicitWidth: sidebarWidth
WlrLayershell.namespace: "quickshell:sidebarRight"
// Hyprland 0.49: Focus is always exclusive and setting this breaks mouse focus grab
// WlrLayershell.keyboardFocus: WlrKeyboardFocus.Exclusive
color: "transparent"
anchors {
top: true
right: true
bottom: true
}
HyprlandFocusGrab {
id: grab
windows: [ sidebarRoot ]
active: sidebarRoot.visible
onCleared: () => {
if (!active) sidebarRoot.hide()
}
}
// Background
Rectangle {
id: sidebarRightBackground
anchors.centerIn: parent
width: parent.width - Appearance.sizes.hyprlandGapsOut * 2
height: parent.height - Appearance.sizes.hyprlandGapsOut * 2
color: Appearance.colors.colLayer0
radius: Appearance.rounding.screenRounding - Appearance.sizes.elevationMargin + 1
layer.enabled: true
layer.effect: MultiEffect {
source: sidebarRightBackground
anchors.fill: sidebarRightBackground
shadowEnabled: true
shadowColor: Appearance.colors.colShadow
shadowVerticalOffset: 1
shadowBlur: 0.5
}
Keys.onPressed: (event) => {
if (event.key === Qt.Key_Escape) {
sidebarRoot.hide();
}
}
ColumnLayout {
anchors.fill: parent
anchors.margins: sidebarPadding
spacing: sidebarPadding
RowLayout {
Layout.fillHeight: false
spacing: 10
Layout.margins: 10
Layout.topMargin: 5
Layout.bottomMargin: 0
Item {
implicitWidth: distroIcon.width
implicitHeight: distroIcon.height
CustomIcon {
id: distroIcon
width: 25
height: 25
source: SystemInfo.distroIcon
}
ColorOverlay {
anchors.fill: distroIcon
source: distroIcon
color: Appearance.colors.colOnLayer0
}
}
StyledText {
font.pixelSize: Appearance.font.pixelSize.normal
color: Appearance.colors.colOnLayer0
text: StringUtils.format(qsTr("Uptime: {0}"), DateTime.uptime)
textFormat: Text.MarkdownText
}
Item {
Layout.fillWidth: true
}
QuickToggleButton {
toggled: false
buttonIcon: "power_settings_new"
onClicked: {
Hyprland.dispatch("global quickshell:sessionOpen")
}
StyledToolTip {
content: qsTr("Session")
}
}
}
Rectangle {
Layout.alignment: Qt.AlignHCenter
Layout.fillHeight: false
radius: Appearance.rounding.full
color: Appearance.colors.colLayer1
implicitWidth: sidebarQuickControlsRow.implicitWidth + 10
implicitHeight: sidebarQuickControlsRow.implicitHeight + 10
RowLayout {
id: sidebarQuickControlsRow
anchors.fill: parent
anchors.margins: 5
spacing: 5
NetworkToggle {}
BluetoothToggle {}
NightLight {}
GameMode {}
IdleInhibitor {}
}
}
// Center widget group
CenterWidgetGroup {
focus: sidebarRoot.visible
Layout.alignment: Qt.AlignHCenter
Layout.fillHeight: true
Layout.fillWidth: true
}
BottomWidgetGroup {
Layout.alignment: Qt.AlignHCenter
Layout.fillHeight: false
Layout.fillWidth: true
Layout.preferredHeight: implicitHeight
}
}
}
}
} }
IpcHandler { IpcHandler {
target: "sidebarRight" target: "sidebarRight"
function toggle(): void { function toggle(): void {
for (let i = 0; i < sidebarVariants.instances.length; i++) { sidebarLoader.active = !sidebarLoader.active;
let loader = sidebarVariants.instances[i]; if(sidebarLoader.active) Notifications.timeoutAll();
loader.active = !loader.active;
}
} }
function close(): void { function close(): void {
for (let i = 0; i < sidebarVariants.instances.length; i++) { sidebarLoader.active = false;
let loader = sidebarVariants.instances[i];
loader.active = false;
}
} }
function open(): void { function open(): void {
for (let i = 0; i < sidebarVariants.instances.length; i++) { sidebarLoader.active = true;
let loader = sidebarVariants.instances[i]; Notifications.timeoutAll();
loader.active = true;
}
} }
} }
GlobalShortcut { GlobalShortcut {
name: "sidebarRightToggle" name: "sidebarRightToggle"
description: "Toggles right sidebar on press" description: qsTr("Toggles right sidebar on press")
onPressed: { onPressed: {
for (let i = 0; i < sidebarVariants.instances.length; i++) { sidebarLoader.active = !sidebarLoader.active;
let loader = sidebarVariants.instances[i]; if(sidebarLoader.active) Notifications.timeoutAll();
loader.active = !loader.active;
}
} }
} }
GlobalShortcut { GlobalShortcut {
name: "sidebarRightOpen" name: "sidebarRightOpen"
description: "Opens right sidebar on press" description: qsTr("Opens right sidebar on press")
onPressed: { onPressed: {
for (let i = 0; i < sidebarVariants.instances.length; i++) { sidebarLoader.active = true;
let loader = sidebarVariants.instances[i]; Notifications.timeoutAll();
loader.active = true;
}
} }
} }
GlobalShortcut { GlobalShortcut {
name: "sidebarRightClose" name: "sidebarRightClose"
description: "Closes right sidebar on press" description: qsTr("Closes right sidebar on press")
onPressed: { onPressed: {
for (let i = 0; i < sidebarVariants.instances.length; i++) { sidebarLoader.active = false;
let loader = sidebarVariants.instances[i];
loader.active = false;
}
} }
} }
@@ -1,5 +1,6 @@
import "root:/modules/common" import "root:/modules/common"
import "root:/modules/common/widgets" import "root:/modules/common/widgets"
import "root:/modules/common/functions/color_utils.js" as ColorUtils
import QtQuick import QtQuick
import QtQuick.Controls import QtQuick.Controls
import QtQuick.Layouts import QtQuick.Layouts
@@ -24,15 +25,10 @@ Button {
Appearance.m3colors.m3primary) : Appearance.m3colors.m3primary) :
(interactable && button.down) ? Appearance.colors.colLayer1Active : (interactable && button.down) ? Appearance.colors.colLayer1Active :
(interactable && button.hovered) ? Appearance.colors.colLayer1Hover : (interactable && button.hovered) ? Appearance.colors.colLayer1Hover :
Appearance.transparentize(Appearance.colors.colLayer1, 1) ColorUtils.transparentize(Appearance.colors.colLayer1, 1)
Behavior on color { Behavior on color {
ColorAnimation { animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this)
duration: Appearance.animation.elementMove.duration
easing.type: Appearance.animation.elementMove.type
easing.bezierCurve: Appearance.animation.elementMove.bezierCurve
}
} }
} }
@@ -46,12 +42,7 @@ Button {
Appearance.m3colors.m3outlineVariant Appearance.m3colors.m3outlineVariant
Behavior on color { Behavior on color {
ColorAnimation { animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this)
duration: Appearance.animation.elementMove.duration
easing.type: Appearance.animation.elementMove.type
easing.bezierCurve: Appearance.animation.elementMove.bezierCurve
}
} }
} }
} }
@@ -26,12 +26,7 @@ Button {
color: (button.down) ? Appearance.colors.colLayer2Active : (button.hovered ? Appearance.colors.colLayer2Hover : Appearance.colors.colLayer2) color: (button.down) ? Appearance.colors.colLayer2Active : (button.hovered ? Appearance.colors.colLayer2Hover : Appearance.colors.colLayer2)
Behavior on color { Behavior on color {
ColorAnimation { animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this)
duration: Appearance.animation.elementMove.duration
easing.type: Appearance.animation.elementMove.type
easing.bezierCurve: Appearance.animation.elementMove.bezierCurve
}
} }
} }
@@ -142,11 +142,7 @@ Item {
opacity: notificationWidgetList.length > 0 ? 1 : 0 opacity: notificationWidgetList.length > 0 ? 1 : 0
visible: opacity > 0 visible: opacity > 0
Behavior on opacity { Behavior on opacity {
NumberAnimation { animation: Appearance.animation.elementMove.numberAnimation.createObject(this)
duration: Appearance.animation.elementMove.duration
easing.type: Appearance.animation.elementMove.type
easing.bezierCurve: Appearance.animation.elementMove.bezierCurve
}
} }
} }
@@ -9,7 +9,7 @@ Button {
property string buttonText: "" property string buttonText: ""
property string buttonIcon: "" property string buttonIcon: ""
implicitHeight: 30 // implicitHeight: 30
implicitWidth: contentRowLayout.implicitWidth + 10 * 2 implicitWidth: contentRowLayout.implicitWidth + 10 * 2
Behavior on implicitWidth { Behavior on implicitWidth {
SmoothedAnimation { SmoothedAnimation {
@@ -25,19 +25,13 @@ Button {
color: (button.down) ? Appearance.colors.colLayer2Active : (button.hovered ? Appearance.colors.colLayer2Hover : Appearance.colors.colLayer2) color: (button.down) ? Appearance.colors.colLayer2Active : (button.hovered ? Appearance.colors.colLayer2Hover : Appearance.colors.colLayer2)
Behavior on color { Behavior on color {
ColorAnimation { animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this)
duration: Appearance.animation.elementMove.duration
easing.type: Appearance.animation.elementMove.type
easing.bezierCurve: Appearance.animation.elementMove.bezierCurve
}
} }
} }
contentItem: RowLayout { contentItem: RowLayout {
id: contentRowLayout id: contentRowLayout
// anchors.centerIn: parent anchors.centerIn: parent
anchors.right: parent.right
spacing: 0 spacing: 0
MaterialSymbol { MaterialSymbol {
text: buttonIcon text: buttonIcon
@@ -2,6 +2,7 @@ import "../"
import "root:/services" import "root:/services"
import "root:/modules/common" import "root:/modules/common"
import "root:/modules/common/widgets" import "root:/modules/common/widgets"
import "root:/modules/common/functions/string_utils.js" as StringUtils
import QtQuick import QtQuick
import Quickshell import Quickshell
import Quickshell.Io import Quickshell.Io
@@ -37,8 +38,9 @@ QuickToggleButton {
} }
} }
StyledToolTip { StyledToolTip {
content: `${(Bluetooth.bluetoothEnabled && Bluetooth.bluetoothDeviceName.length > 0) ? content: StringUtils.format(qsTr("{0} | Right-click to configure"),
Bluetooth.bluetoothDeviceName : "Bluetooth"} | ${qsTr("Right-click to configure")}` (Bluetooth.bluetoothEnabled && Bluetooth.bluetoothDeviceName.length > 0) ?
Bluetooth.bluetoothDeviceName : qsTr("Bluetooth"))
} }
} }
@@ -1,6 +1,7 @@
import "root:/services"
import "root:/modules/common" import "root:/modules/common"
import "root:/modules/common/widgets" import "root:/modules/common/widgets"
import "root:/services" import "root:/modules/common/functions/string_utils.js" as StringUtils
import "../" import "../"
import QtQuick import QtQuick
import Quickshell import Quickshell
@@ -42,6 +43,6 @@ QuickToggleButton {
} }
} }
StyledToolTip { StyledToolTip {
content: `${Network.networkName} | Right-click to configure` content: StringUtils.format(qsTr("{0} | Right-click to configure"), Network.networkName)
} }
} }
@@ -1,5 +1,6 @@
import "root:/modules/common" import "root:/modules/common"
import "root:/modules/common/widgets" import "root:/modules/common/widgets"
import "root:/modules/common/functions/color_utils.js" as ColorUtils
import QtQuick import QtQuick
import QtQuick.Controls import QtQuick.Controls
import Quickshell.Io import Quickshell.Io
@@ -20,14 +21,10 @@ Button {
radius: Appearance.rounding.full radius: Appearance.rounding.full
color: toggled ? color: toggled ?
(button.down ? Appearance.colors.colPrimaryActive : button.hovered ? Appearance.colors.colPrimaryHover : Appearance.m3colors.m3primary) : (button.down ? Appearance.colors.colPrimaryActive : button.hovered ? Appearance.colors.colPrimaryHover : Appearance.m3colors.m3primary) :
(button.down ? Appearance.colors.colLayer1Active : button.hovered ? Appearance.colors.colLayer1Hover : Appearance.transparentize(Appearance.colors.colLayer1Hover, 1)) (button.down ? Appearance.colors.colLayer1Active : button.hovered ? Appearance.colors.colLayer1Hover : ColorUtils.transparentize(Appearance.colors.colLayer1Hover, 1))
Behavior on color { Behavior on color {
ColorAnimation { animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this)
duration: Appearance.animation.elementMove.duration
easing.type: Appearance.animation.elementMove.type
easing.bezierCurve: Appearance.animation.elementMove.bezierCurve
}
} }
@@ -39,11 +36,7 @@ Button {
color: toggled ? Appearance.m3colors.m3onPrimary : Appearance.colors.colOnLayer1 color: toggled ? Appearance.m3colors.m3onPrimary : Appearance.colors.colOnLayer1
Behavior on color { Behavior on color {
ColorAnimation { animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this)
duration: Appearance.animation.elementMove.duration
easing.type: Appearance.animation.elementMove.type
easing.bezierCurve: Appearance.animation.elementMove.bezierCurve
}
} }
} }
@@ -155,11 +155,7 @@ Item {
anchors.fill: parent anchors.fill: parent
Behavior on opacity { Behavior on opacity {
NumberAnimation { animation: Appearance.animation.elementMove.numberAnimation.createObject(this)
duration: Appearance.animation.elementMove.duration
easing.type: Appearance.animation.elementMove.type
easing.bezierCurve: Appearance.animation.elementMove.bezierCurve
}
} }
ColumnLayout { ColumnLayout {
@@ -1,5 +1,6 @@
import "root:/modules/common" import "root:/modules/common"
import "root:/modules/common/widgets" import "root:/modules/common/widgets"
import "root:/modules/common/functions/color_utils.js" as ColorUtils
import QtQuick import QtQuick
import QtQuick.Controls import QtQuick.Controls
import QtQuick.Layouts import QtQuick.Layouts
@@ -23,15 +24,10 @@ Button {
background: Rectangle { background: Rectangle {
anchors.fill: parent anchors.fill: parent
radius: Appearance.rounding.full radius: Appearance.rounding.full
color: (button.down) ? Appearance.colors.colLayer2Active : (button.hovered ? Appearance.colors.colLayer2Hover : Appearance.transparentize(Appearance.colors.colLayer2, 1)) color: (button.down) ? Appearance.colors.colLayer2Active : (button.hovered ? Appearance.colors.colLayer2Hover : ColorUtils.transparentize(Appearance.colors.colLayer2, 1))
Behavior on color { Behavior on color {
ColorAnimation { animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this)
duration: Appearance.animation.elementMove.duration
easing.type: Appearance.animation.elementMove.type
easing.bezierCurve: Appearance.animation.elementMove.bezierCurve
}
} }
} }
@@ -1,10 +1,11 @@
import "root:/modules/common" import "root:/modules/common"
import "root:/modules/common/widgets" import "root:/modules/common/widgets"
import "root:/services" import "root:/services"
import "root:/modules/common/functions/color_utils.js" as ColorUtils
import QtQuick import QtQuick
import QtQuick.Controls import QtQuick.Controls
import QtQuick.Effects
import QtQuick.Layouts import QtQuick.Layouts
import Qt5Compat.GraphicalEffects
Item { Item {
id: root id: root
@@ -79,37 +80,33 @@ Item {
tabIndicator.enableIndicatorAnimation = true tabIndicator.enableIndicatorAnimation = true
} }
} }
Rectangle { Rectangle {
id: indicator
property int tabCount: root.tabButtonList.length
property real fullTabSize: root.width / tabCount;
property real targetWidth: tabBar.contentItem.children[0].children[tabBar.currentIndex].tabContentWidth
implicitWidth: targetWidth
anchors {
top: parent.top
bottom: parent.bottom
}
x: tabBar.currentIndex * fullTabSize + (fullTabSize - targetWidth) / 2
color: Appearance.m3colors.m3primary color: Appearance.m3colors.m3primary
radius: Appearance.rounding.full radius: Appearance.rounding.full
z: 2
anchors.fill: parent Behavior on x {
anchors.leftMargin: {
const tabCount = root.tabButtonList.length
const targetWidth = tabBar.contentItem.children[0].children[tabBar.currentIndex].tabContentWidth
const fullTabSize = tabBar.width / tabCount;
return fullTabSize * currentTab + (fullTabSize - targetWidth) / 2;
}
anchors.rightMargin: {
const tabCount = root.tabButtonList.length
const targetWidth = tabBar.contentItem.children[0].children[tabBar.currentIndex].tabContentWidth
const fullTabSize = tabBar.width / tabCount;
return fullTabSize * (tabCount - currentTab - 1) + (fullTabSize - targetWidth) / 2;
}
Behavior on anchors.leftMargin {
enabled: tabIndicator.enableIndicatorAnimation enabled: tabIndicator.enableIndicatorAnimation
SmoothedAnimation { animation: Appearance.animation.elementMove.numberAnimation.createObject(this)
velocity: Appearance.animation.positionShift.velocity
}
} }
Behavior on anchors.rightMargin {
Behavior on implicitWidth {
enabled: tabIndicator.enableIndicatorAnimation enabled: tabIndicator.enableIndicatorAnimation
SmoothedAnimation { animation: Appearance.animation.elementMove.numberAnimation.createObject(this)
velocity: Appearance.animation.positionShift.velocity
}
} }
} }
} }
@@ -125,6 +122,7 @@ Item {
Layout.topMargin: 10 Layout.topMargin: 10
Layout.fillWidth: true Layout.fillWidth: true
Layout.fillHeight: true Layout.fillHeight: true
spacing: 10
clip: true clip: true
currentIndex: currentTab currentIndex: currentTab
onCurrentIndexChanged: { onCurrentIndexChanged: {
@@ -173,31 +171,17 @@ Item {
color: (fabButton.down) ? Appearance.colors.colPrimaryContainerActive : (fabButton.hovered ? Appearance.colors.colPrimaryContainerHover : Appearance.m3colors.m3primaryContainer) color: (fabButton.down) ? Appearance.colors.colPrimaryContainerActive : (fabButton.hovered ? Appearance.colors.colPrimaryContainerHover : Appearance.m3colors.m3primaryContainer)
Behavior on color { Behavior on color {
ColorAnimation { animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this)
duration: Appearance.animation.elementMove.duration
easing.type: Appearance.animation.elementMove.type
easing.bezierCurve: Appearance.animation.elementMove.bezierCurve
}
} }
}
DropShadow { layer.enabled: true
id: fabShadow layer.effect: MultiEffect {
anchors.fill: fabBackground source: fabBackground
source: fabBackground anchors.fill: fabBackground
horizontalOffset: 0 shadowEnabled: true
verticalOffset: fabButton.hovered ? 4 : 2 shadowColor: Appearance.colors.colShadow
radius: fabButton.hovered ? Appearance.sizes.fabHoveredShadowRadius : Appearance.sizes.fabShadowRadius shadowBlur: 0.6
samples: fabShadow.radius * 2 + 1 shadowVerticalOffset: fabButton.hovered ? 4 : 2
color: Appearance.transparentize(Appearance.m3colors.m3shadow, 0.55)
z: fabBackground.z - 1
Behavior on verticalOffset {
NumberAnimation {
duration: Appearance.animation.elementMoveFast.duration
easing.type: Appearance.animation.elementMoveFast.type
easing.bezierCurve: Appearance.animation.elementMoveFast.bezierCurve
}
} }
} }
@@ -18,12 +18,7 @@ Button {
color: (button.down) ? Appearance.colors.colLayer2Active : (button.hovered ? Appearance.colors.colLayer2Hover : Appearance.colors.colLayer2) color: (button.down) ? Appearance.colors.colLayer2Active : (button.hovered ? Appearance.colors.colLayer2Hover : Appearance.colors.colLayer2)
Behavior on color { Behavior on color {
ColorAnimation { animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this)
duration: Appearance.animation.elementMove.duration
easing.type: Appearance.animation.elementMove.type
easing.bezierCurve: Appearance.animation.elementMove.bezierCurve
}
} }
} }
+138 -7
View File
@@ -27,6 +27,82 @@ post_process() {
fi fi
} }
check_and_prompt_upscale() {
local img="$1"
min_width_desired="$(hyprctl monitors -j | jq '([.[].width] | max)' | xargs)" # max monitor width
min_height_desired="$(hyprctl monitors -j | jq '([.[].height] | max)' | xargs)" # max monitor height
if command -v identify &>/dev/null && [ -f "$img" ]; then
local img_width img_height
img_width=$(identify -format "%w" "$img" 2>/dev/null)
img_height=$(identify -format "%h" "$img" 2>/dev/null)
if [[ "$img_width" -lt "$min_width_desired" || "$img_height" -lt "$min_height_desired" ]]; then
action=$(notify-send "Upscale?" \
"Image resolution (${img_width}x${img_height}) is lower than screen resolution (${min_width_desired}x${min_height_desired})" \
-A "open_upscayl=Open Upscayl")
if [[ "$action" == "open_upscayl" ]]; then
if command -v upscayl &>/dev/null; then
nohup upscayl > /dev/null 2>&1 &
else
action2=$(notify-send \
-a "Wallpaper switcher" \
-c "im.error" \
-A "install_upscayl=Install Upscayl (Arch)" \
"Install Upscayl?" \
"yay -S upscayl-bin")
if [[ "$action2" == "install_upscayl" ]]; then
foot yay -S upscayl-bin
if command -v upscayl &>/dev/null; then
nohup upscayl > /dev/null 2>&1 &
fi
fi
fi
fi
fi
fi
}
THUMBNAIL_DIR="/tmp/mpvpaper_thumbnails"
CUSTOM_DIR="$XDG_CONFIG_HOME/hypr/custom"
RESTORE_SCRIPT_DIR="$CUSTOM_DIR/scripts"
RESTORE_SCRIPT="$RESTORE_SCRIPT_DIR/__restore_video_wallpaper.sh"
VIDEO_OPTS="no-audio loop hwdec=auto scale=bilinear interpolation=no video-sync=display-resample panscan=1.0 video-scale-x=1.0 video-scale-y=1.0 video-align-x=0.5 video-align-y=0.5"
is_video() {
local extension="${1##*.}"
[[ "$extension" == "mp4" || "$extension" == "mkv" || "$extension" == "webm" ]] && return 0 || return 1
}
kill_existing_mpvpaper() {
pkill -f -9 mpvpaper || true
}
create_restore_script() {
local video_path=$1
cat > "$RESTORE_SCRIPT.tmp" << EOF
#!/bin/bash
# Generated by switchwall.sh - Don't modify it by yourself.
# Time: $(date)
pkill -f -9 mpvpaper
for monitor in \$(hyprctl monitors -j | jq -r '.[] | .name'); do
mpvpaper -o "$VIDEO_OPTS" "\$monitor" "$video_path" &
sleep 0.1
done
EOF
mv "$RESTORE_SCRIPT.tmp" "$RESTORE_SCRIPT"
chmod +x "$RESTORE_SCRIPT"
}
remove_restore() {
cat > "$RESTORE_SCRIPT.tmp" << EOF
#!/bin/bash
# The content of this script will be generated by switchwall.sh - Don't modify it by yourself.
EOF
mv "$RESTORE_SCRIPT.tmp" "$RESTORE_SCRIPT"
}
switch() { switch() {
imgpath="$1" imgpath="$1"
mode_flag="$2" mode_flag="$2"
@@ -48,8 +124,67 @@ switch() {
echo 'Aborted' echo 'Aborted'
exit 0 exit 0
fi fi
matugen_args=(image "$imgpath")
generate_colors_material_args=(--path "$imgpath") check_and_prompt_upscale "$imgpath" &
kill_existing_mpvpaper
if is_video "$imgpath"; then
mkdir -p "$THUMBNAIL_DIR"
missing_deps=()
if ! command -v mpvpaper &> /dev/null; then
missing_deps+=("mpvpaper")
fi
if ! command -v ffmpeg &> /dev/null; then
missing_deps+=("ffmpeg")
fi
if [ ${#missing_deps[@]} -gt 0 ]; then
echo "Missing deps: ${missing_deps[*]}"
echo "Arch: sudo pacman -S ${missing_deps[*]}"
action=$(notify-send \
-a "Wallpaper switcher" \
-c "im.error" \
-A "install_arch=Install (Arch)" \
"Can't switch to video wallpaper" \
"Missing dependencies: ${missing_deps[*]}")
if [[ "$action" == "install_arch" ]]; then
foot sudo pacman -S "${missing_deps[*]}"
if command -v mpvpaper &>/dev/null && command -v ffmpeg &>/dev/null; then
notify-send 'Wallpaper switcher' 'Alright, try again!'
fi
fi
exit 0
fi
local video_path="$imgpath"
monitors=$(hyprctl monitors -j | jq -r '.[] | .name')
for monitor in $monitors; do
mpvpaper -o "$VIDEO_OPTS" "$monitor" "$video_path" &
sleep 0.1
done
# Extract first frame for color generation
thumbnail="$THUMBNAIL_DIR/$(basename "$imgpath").jpg"
ffmpeg -y -i "$imgpath" -vframes 1 "$thumbnail" 2>/dev/null
if [ -f "$thumbnail" ]; then
matugen_args=(image "$thumbnail")
generate_colors_material_args=(--path "$thumbnail")
create_restore_script "$video_path"
else
echo "Cannot create image to colorgen"
remove_restore
exit 1
fi
else
matugen_args=(image "$imgpath")
generate_colors_material_args=(--path "$imgpath")
# Set wallpaper with swww
swww img "$imgpath" --transition-step 100 --transition-fps 120 \
--transition-type grow --transition-angle 30 --transition-duration 1 \
--transition-pos "$cursorposx, $cursorposy_inverted"
remove_restore
fi
fi fi
# Determine mode if not set # Determine mode if not set
@@ -62,18 +197,14 @@ switch() {
fi fi
fi fi
# Dark/light mode, material scheme
[[ -n "$mode_flag" ]] && matugen_args+=(--mode "$mode_flag") && generate_colors_material_args+=(--mode "$mode_flag") [[ -n "$mode_flag" ]] && matugen_args+=(--mode "$mode_flag") && generate_colors_material_args+=(--mode "$mode_flag")
[[ -n "$type_flag" ]] && matugen_args+=(--type "$type_flag") && generate_colors_material_args+=(--scheme "$type_flag") [[ -n "$type_flag" ]] && matugen_args+=(--type "$type_flag") && generate_colors_material_args+=(--scheme "$type_flag")
# Terminal scheme
generate_colors_material_args+=(--termscheme "$terminalscheme" --blend_bg_fg) generate_colors_material_args+=(--termscheme "$terminalscheme" --blend_bg_fg)
generate_colors_material_args+=(--cache "$STATE_DIR/user/color.txt") generate_colors_material_args+=(--cache "$STATE_DIR/user/color.txt")
pre_process pre_process
# Generate with matugen
matugen "${matugen_args[@]}" matugen "${matugen_args[@]}"
# Use custom script for mixing (matugen can't D:)
source "$(eval echo $ILLOGICAL_IMPULSE_VIRTUAL_ENV)/bin/activate" source "$(eval echo $ILLOGICAL_IMPULSE_VIRTUAL_ENV)/bin/activate"
python "$SCRIPT_DIR/generate_colors_material.py" "${generate_colors_material_args[@]}" \ python "$SCRIPT_DIR/generate_colors_material.py" "${generate_colors_material_args[@]}" \
> "$STATE_DIR"/user/generated/material_colors.scss > "$STATE_DIR"/user/generated/material_colors.scss
+43 -30
View File
@@ -14,11 +14,22 @@ Singleton {
readonly property string interfaceRole: "interface" readonly property string interfaceRole: "interface"
readonly property string apiKeyEnvVarName: "API_KEY" readonly property string apiKeyEnvVarName: "API_KEY"
property Component aiMessageComponent: AiMessageData {} property Component aiMessageComponent: AiMessageData {}
property string systemPrompt: ConfigOptions.ai.systemPrompt ?? "" property string systemPrompt: ConfigOptions?.ai?.systemPrompt ?? ""
property var messages: [] property var messages: []
property var messageIDs: []
property var messageByID: ({})
readonly property var apiKeys: KeyringStorage.keyringData?.apiKeys ?? {} readonly property var apiKeys: KeyringStorage.keyringData?.apiKeys ?? {}
readonly property var apiKeysLoaded: KeyringStorage.loaded readonly property var apiKeysLoaded: KeyringStorage.loaded
function idForMessage(message) {
// Generate a unique ID using timestamp and random value
return Date.now().toString(36) + Math.random().toString(36).substr(2, 8);
}
function safeModelName(modelName) {
return modelName.replace(/:/g, "_").replace(/\./g, "_")
}
// Model properties: // Model properties:
// - name: Name of the model // - name: Name of the model
// - icon: Icon name of the model // - icon: Icon name of the model
@@ -36,14 +47,14 @@ Singleton {
"gemini-2.0-flash-search": { "gemini-2.0-flash-search": {
"name": "Gemini 2.0 Flash", "name": "Gemini 2.0 Flash",
"icon": "google-gemini-symbolic", "icon": "google-gemini-symbolic",
"description": "Online | Google's model\nGives up-to-date information with search.", "description": qsTr("Online | Google's model\nGives up-to-date information with search."),
"homepage": "https://aistudio.google.com", "homepage": "https://aistudio.google.com",
"endpoint": "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:streamGenerateContent", "endpoint": "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:streamGenerateContent",
"model": "gemini-2.0-flash", "model": "gemini-2.0-flash",
"requires_key": true, "requires_key": true,
"key_id": "gemini", "key_id": "gemini",
"key_get_link": "https://aistudio.google.com/app/apikey", "key_get_link": "https://aistudio.google.com/app/apikey",
"key_get_description": "**Pricing**: free. Data used for training.\n\n**Instructions**: Log into Google account, allow AI Studio to create Google Cloud project or whatever it asks, go back and click Get API key", "key_get_description": qsTr("**Pricing**: free. Data used for training.\n\n**Instructions**: Log into Google account, allow AI Studio to create Google Cloud project or whatever it asks, go back and click Get API key"),
"api_format": "gemini", "api_format": "gemini",
"tools": [ "tools": [
{ {
@@ -54,25 +65,26 @@ Singleton {
"openrouter-llama4-maverick": { "openrouter-llama4-maverick": {
"name": "Llama 4 Maverick", "name": "Llama 4 Maverick",
"icon": "ollama-symbolic", "icon": "ollama-symbolic",
"description": "Online via OpenRouter | Meta's model", "description": StringUtils.format(qsTr("Online via {0} | {1}'s model"), "OpenRouter", "Meta"),
"homepage": "https://openrouter.ai/meta-llama/llama-4-maverick:free", "homepage": "https://openrouter.ai/meta-llama/llama-4-maverick:free",
"endpoint": "https://openrouter.ai/api/v1/chat/completions", "endpoint": "https://openrouter.ai/api/v1/chat/completions",
"model": "meta-llama/llama-4-maverick:free", "model": "meta-llama/llama-4-maverick:free",
"requires_key": true, "requires_key": true,
"key_id": "openrouter", "key_id": "openrouter",
"key_get_link": "https://openrouter.ai/settings/keys", "key_get_link": "https://openrouter.ai/settings/keys",
"key_get_description": "**Pricing**: free. Data use policy varies depending on your OpenRouter account settings.\n\n**Instructions**: Log into OpenRouter account, go to Keys on the topright menu, click Create API Key", "key_get_description": qsTr("**Pricing**: free. Data use policy varies depending on your OpenRouter account settings.\n\n**Instructions**: Log into OpenRouter account, go to Keys on the topright menu, click Create API Key"),
}, },
"openrouter-deepseek-r1": { "openrouter-deepseek-r1": {
"name": "DeepSeek R1", "name": "DeepSeek R1",
"icon": "deepseek-symbolic", "icon": "deepseek-symbolic",
"description": "Online via OpenRouter | DeepSeek's reasoning model", "description": StringUtils.format(qsTr("Online via {0} | {1}'s model"), "OpenRouter", "DeepSeek"),
"homepage": "https://openrouter.ai/deepseek/deepseek-r1:free", "homepage": "https://openrouter.ai/deepseek/deepseek-r1:free",
"endpoint": "https://openrouter.ai/api/v1/chat/completions", "endpoint": "https://openrouter.ai/api/v1/chat/completions",
"model": "deepseek/deepseek-r1:free", "model": "deepseek/deepseek-r1:free",
"requires_key": true, "requires_key": true,
"key_id": "openrouter", "key_id": "openrouter",
"key_get_link": "https://openrouter.ai/settings/keys", "key_get_link": "https://openrouter.ai/settings/keys",
"key_get_description": qsTr("**Pricing**: free. Data use policy varies depending on your OpenRouter account settings.\n\n**Instructions**: Log into OpenRouter account, go to Keys on the topright menu, click Create API Key"),
}, },
} }
property var modelList: Object.keys(root.models) property var modelList: Object.keys(root.models)
@@ -114,10 +126,11 @@ Singleton {
const dataJson = JSON.parse(data); const dataJson = JSON.parse(data);
root.modelList = [...root.modelList, ...dataJson]; root.modelList = [...root.modelList, ...dataJson];
dataJson.forEach(model => { dataJson.forEach(model => {
root.models[model] = { const safeModelName = root.safeModelName(model);
root.models[safeModelName] = {
"name": guessModelName(model), "name": guessModelName(model),
"icon": guessModelLogo(model), "icon": guessModelLogo(model),
"description": `Local Ollama model: ${model}`, "description": StringUtils.format(qsTr("Local Ollama model | {0}"), model),
"homepage": `https://ollama.com/library/${model}`, "homepage": `https://ollama.com/library/${model}`,
"endpoint": "http://localhost:11434/v1/chat/completions", "endpoint": "http://localhost:11434/v1/chat/completions",
"model": model, "model": model,
@@ -141,13 +154,17 @@ Singleton {
"thinking": false, "thinking": false,
"done": true, "done": true,
}); });
root.messages = [...root.messages, aiMessage]; const id = idForMessage(aiMessage);
root.messageIDs = [...root.messageIDs, id];
root.messageByID[id] = aiMessage;
} }
function removeMessage(index) { function removeMessage(index) {
if (index < 0 || index >= messages.length) return; if (index < 0 || index >= messageIDs.length) return;
root.messages.splice(index, 1); const id = root.messageIDs[index];
root.messages = [...root.messages]; root.messageIDs.splice(index, 1);
root.messageIDs = [...root.messageIDs];
delete root.messageByID[id];
} }
function addApiKeyAdvice(model) { function addApiKeyAdvice(model) {
@@ -167,7 +184,7 @@ Singleton {
modelId = modelId.toLowerCase() modelId = modelId.toLowerCase()
if (modelList.indexOf(modelId) !== -1) { if (modelList.indexOf(modelId) !== -1) {
PersistentStateManager.setState("ai.model", modelId); PersistentStateManager.setState("ai.model", modelId);
if (feedback) root.addMessage("Model set to " + models[modelId].name, Ai.interfaceRole) if (feedback) root.addMessage(StringUtils.format(StringUtils.format("Model set to {0}"), models[modelId].name, Ai.interfaceRole))
if (models[modelId].requires_key) { if (models[modelId].requires_key) {
// If key not there show advice // If key not there show advice
if (root.apiKeysLoaded && (!root.apiKeys[models[modelId].key_id] || root.apiKeys[models[modelId].key_id].length === 0)) { if (root.apiKeysLoaded && (!root.apiKeys[models[modelId].key_id] || root.apiKeys[models[modelId].key_id].length === 0)) {
@@ -185,7 +202,7 @@ Singleton {
function setApiKey(key) { function setApiKey(key) {
const model = models[currentModelId]; const model = models[currentModelId];
if (!model.requires_key) { if (!model.requires_key) {
root.addMessage(`${model.name} does not require an API key`, Ai.interfaceRole); root.addMessage(StringUtils.format(qsTr("{0} does not require an API key"), model.name), Ai.interfaceRole);
return; return;
} }
if (!key || key.length === 0) { if (!key || key.length === 0) {
@@ -194,7 +211,7 @@ Singleton {
return; return;
} }
KeyringStorage.setNestedField(["apiKeys", model.key_id], key.trim()); KeyringStorage.setNestedField(["apiKeys", model.key_id], key.trim());
root.addMessage("API key set for " + model.name, Ai.interfaceRole); root.addMessage(StringUtils.format(qsTr("API key set for {0}"), model.name, Ai.interfaceRole));
} }
function printApiKey() { function printApiKey() {
@@ -207,12 +224,13 @@ Singleton {
root.addMessage(StringUtils.format(qsTr("No API key set for {0}"), model.name), Ai.interfaceRole); root.addMessage(StringUtils.format(qsTr("No API key set for {0}"), model.name), Ai.interfaceRole);
} }
} else { } else {
root.addMessage(`This model (${model.name}) does not require an API key`, Ai.interfaceRole); root.addMessage(StringUtils.format(qsTr("{0} does not require an API key"), model.name), Ai.interfaceRole);
} }
} }
function clearMessages() { function clearMessages() {
messages = []; root.messageIDs = [];
root.messageByID = ({});
} }
Process { Process {
@@ -274,7 +292,8 @@ Singleton {
/* Build endpoint, request data */ /* Build endpoint, request data */
const endpoint = (apiFormat === "gemini") ? buildGeminiEndpoint(model) : buildOpenAIEndpoint(model); const endpoint = (apiFormat === "gemini") ? buildGeminiEndpoint(model) : buildOpenAIEndpoint(model);
const data = (apiFormat === "gemini") ? buildGeminiRequestData(model, root.messages) : buildOpenAIRequestData(model, root.messages); const messageArray = root.messageIDs.map(id => root.messageByID[id]);
const data = (apiFormat === "gemini") ? buildGeminiRequestData(model, messageArray) : buildOpenAIRequestData(model, messageArray);
let requestHeaders = { let requestHeaders = {
"Content-Type": "application/json", "Content-Type": "application/json",
@@ -288,7 +307,9 @@ Singleton {
"thinking": true, "thinking": true,
"done": false, "done": false,
}); });
root.messages = [...root.messages, requester.message]; const id = idForMessage(requester.message);
root.messageIDs = [...root.messageIDs, id];
root.messageByID[id] = requester.message;
/* Build header string for curl */ /* Build header string for curl */
let headerString = Object.entries(requestHeaders) let headerString = Object.entries(requestHeaders)
@@ -318,14 +339,14 @@ Singleton {
const dataJson = JSON.parse(requester.geminiBuffer); const dataJson = JSON.parse(requester.geminiBuffer);
const responseContent = dataJson.candidates[0]?.content?.parts[0]?.text const responseContent = dataJson.candidates[0]?.content?.parts[0]?.text
requester.message.content += responseContent; requester.message.content += responseContent;
const annotationSources = dataJson.candidates[0]?.groundingMetadata.groundingChunks?.map(chunk => { const annotationSources = dataJson.candidates[0]?.groundingMetadata?.groundingChunks?.map(chunk => {
return { return {
"type": "url_citation", "type": "url_citation",
"text": chunk?.web?.title, "text": chunk?.web?.title,
"url": chunk?.web?.uri, "url": chunk?.web?.uri,
} }
}); });
const annotations = dataJson.candidates[0]?.groundingMetadata.groundingSupports?.map(citation => { const annotations = dataJson.candidates[0]?.groundingMetadata?.groundingSupports?.map(citation => {
return { return {
"type": "url_citation", "type": "url_citation",
"start_index": citation.segment?.startIndex, "start_index": citation.segment?.startIndex,
@@ -446,15 +467,7 @@ Singleton {
function sendUserMessage(message) { function sendUserMessage(message) {
if (message.length === 0) return; if (message.length === 0) return;
root.addMessage(message, "user");
const userMessage = aiMessageComponent.createObject(root, {
"role": "user",
"content": message,
"thinking": false,
"done": true,
});
root.messages = [...root.messages, userMessage];
requester.makeRequest(); requester.makeRequest();
} }
+3 -10
View File
@@ -13,20 +13,13 @@ Singleton {
signal tagSuggestion(string query, var suggestions) signal tagSuggestion(string query, var suggestions)
Connections {
target: ConfigOptions.sidebar.booru
function onAllowNsfwChanged() {
root.addSystemMessage(PersistentStates.booru.allowNsfw ? qsTr("Tiddies enabled") : qsTr("No horny"))
}
}
property string failMessage: qsTr("That didn't work. Tips:\n- Check your tags and NSFW settings\n- If you don't have a tag in mind, type a page number") property string failMessage: qsTr("That didn't work. Tips:\n- Check your tags and NSFW settings\n- If you don't have a tag in mind, type a page number")
property var responses: [] property var responses: []
property int runningRequests: 0 property int runningRequests: 0
property var defaultUserAgent: ConfigOptions.networking.userAgent property var defaultUserAgent: ConfigOptions?.networking?.userAgent || "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36"
property var providerList: ["yandere", "konachan", "zerochan", "danbooru", "gelbooru", "waifu.im"] property var providerList: ["yandere", "konachan", "zerochan", "danbooru", "gelbooru", "waifu.im"]
property var providers: { property var providers: {
"system": { "name": "System" }, "system": { "name": qsTr("System") },
"yandere": { "yandere": {
"name": "yande.re", "name": "yande.re",
"url": "https://yande.re", "url": "https://yande.re",
@@ -347,7 +340,7 @@ Singleton {
xhr.setRequestHeader("User-Agent", defaultUserAgent) xhr.setRequestHeader("User-Agent", defaultUserAgent)
} }
else if (currentProvider == "zerochan") { else if (currentProvider == "zerochan") {
const userAgent = ConfigOptions.sidebar.booru.zerochan.username ? `Desktop sidebar booru viewer - ${ConfigOptions.sidebar.booru.zerochan.username}` : defaultUserAgent const userAgent = ConfigOptions?.sidebar?.booru?.zerochan?.username ? `Desktop sidebar booru viewer - username: ${ConfigOptions.sidebar.booru.zerochan.username}` : defaultUserAgent
xhr.setRequestHeader("User-Agent", userAgent) xhr.setRequestHeader("User-Agent", userAgent)
} }
root.runningRequests++; root.runningRequests++;
+2 -2
View File
@@ -134,13 +134,13 @@ Singleton {
GlobalShortcut { GlobalShortcut {
name: "brightnessIncrease" name: "brightnessIncrease"
description: "Increase brightness" description: qsTr("Increase brightness")
onPressed: root.increaseBrightness() onPressed: root.increaseBrightness()
} }
GlobalShortcut { GlobalShortcut {
name: "brightnessDecrease" name: "brightnessDecrease"
description: "Decrease brightness" description: qsTr("Decrease brightness")
onPressed: root.decreaseBrightness() onPressed: root.decreaseBrightness()
} }
} }
+4 -4
View File
@@ -29,11 +29,11 @@ Singleton {
if (root.firstLoad) { if (root.firstLoad) {
root.firstLoad = false; root.firstLoad = false;
} else { } else {
Hyprland.dispatch(`exec notify-send "Shell configuration reloaded" "${root.filePath}"`) Hyprland.dispatch(`exec notify-send "${qsTr("Shell configuration reloaded")}" "${root.filePath}"`)
} }
} catch (e) { } catch (e) {
console.error("[ConfigLoader] Error reading file:", e); console.error("[ConfigLoader] Error reading file:", e);
Hyprland.dispatch(`exec notify-send "Shell configuration failed to load" "${root.filePath}"`) Hyprland.dispatch(`exec notify-send "${qsTr("Shell configuration failed to load")}" "${root.filePath}"`)
return; return;
} }
} }
@@ -66,9 +66,9 @@ Singleton {
console.log("[ConfigLoader] File not found, creating new file.") console.log("[ConfigLoader] File not found, creating new file.")
const plainConfig = ObjectUtils.toPlainObject(ConfigOptions) const plainConfig = ObjectUtils.toPlainObject(ConfigOptions)
configFileView.setText(JSON.stringify(plainConfig, null, 2)) configFileView.setText(JSON.stringify(plainConfig, null, 2))
Hyprland.dispatch(`exec notify-send "Shell configuration created" "${root.filePath}"`) Hyprland.dispatch(`exec notify-send "${qsTr("Shell configuration created")}" "${root.filePath}"`)
} else { } else {
Hyprland.dispatch(`exec notify-send "Shell configuration failed to load" "${root.filePath}"`) Hyprland.dispatch(`exec notify-send "${qsTr("Shell configuration failed to load")}" "${root.filePath}"`)
} }
} }
} }
+1 -1
View File
@@ -38,7 +38,7 @@ Singleton {
if (hours > 0) formatted += `${formatted ? ", " : ""}${hours}h` if (hours > 0) formatted += `${formatted ? ", " : ""}${hours}h`
if (minutes > 0 || !formatted) formatted += `${formatted ? ", " : ""}${minutes}m` if (minutes > 0 || !formatted) formatted += `${formatted ? ", " : ""}${minutes}m`
uptime = formatted uptime = formatted
interval = ConfigOptions.resources.updateInterval; interval = ConfigOptions?.resources?.updateInterval ?? 3000
} }
} }
@@ -11,19 +11,15 @@ import Quickshell.Hyprland
Singleton { Singleton {
id: root id: root
property var defaultKeybinds: [] property var defaultKeybinds: {"children": []}
property var userKeybinds: [] property var userKeybinds: {"children": []}
property var keybinds: ({ property var keybinds: ({
children: [ children: [
...defaultKeybinds.children, ...(defaultKeybinds.children ?? []),
...userKeybinds.children, ...(userKeybinds.children ?? []),
] ]
}) })
// onKeybindsChanged: {
// console.log("[CheatsheetKeybinds] Keybinds changed:", JSON.stringify(keybinds, null, 2))
// }
Connections { Connections {
target: Hyprland target: Hyprland
@@ -2,6 +2,7 @@ pragma Singleton
pragma ComponentBehavior: Bound pragma ComponentBehavior: Bound
import "root:/modules/common" import "root:/modules/common"
import "root:/modules/common/functions/string_utils.js" as StringUtils
import Quickshell; import Quickshell;
import Quickshell.Io; import Quickshell.Io;
import Qt.labs.platform import Qt.labs.platform
@@ -18,14 +19,14 @@ Singleton {
property var properties: { property var properties: {
"application": "illogical-impulse", "application": "illogical-impulse",
"explanation": "For storing API keys and other sensitive information", "explanation": qsTr("For storing API keys and other sensitive information"),
} }
property var propertiesAsArgs: Object.keys(root.properties).reduce( property var propertiesAsArgs: Object.keys(root.properties).reduce(
function(arr, key) { function(arr, key) {
return arr.concat([key, root.properties[key]]); return arr.concat([key, root.properties[key]]);
}, [] }, []
) )
property string keyringLabel: "illogical-impulse Safe Storage" property string keyringLabel: StringUtils.format(qsTr("{0} Safe Storage"), "illogical-impulse")
function setNestedField(path, value) { function setNestedField(path, value) {
if (!root.keyringData) root.keyringData = {}; if (!root.keyringData) root.keyringData = {};
@@ -30,7 +30,7 @@ Singleton {
Timer { Timer {
id: delayedFileRead id: delayedFileRead
interval: ConfigOptions.hacks.arbitraryRaceConditionDelay interval: ConfigOptions?.hacks?.arbitraryRaceConditionDelay ?? 100
repeat: false repeat: false
running: false running: false
onTriggered: { onTriggered: {
@@ -72,7 +72,7 @@ Singleton {
Timer { Timer {
id: delayedFileRead id: delayedFileRead
interval: ConfigOptions.hacks.arbitraryRaceConditionDelay interval: ConfigOptions?.hacks?.arbitraryRaceConditionDelay ?? 100
repeat: false repeat: false
running: false running: false
onTriggered: { onTriggered: {
@@ -50,7 +50,7 @@ Singleton {
previousCpuStats = { total, idle } previousCpuStats = { total, idle }
} }
interval = ConfigOptions.resources.updateInterval interval = ConfigOptions?.resources?.updateInterval ?? 3000
} }
} }
+4
View File
@@ -1,7 +1,10 @@
//@ pragma UseQApplication //@ pragma UseQApplication
//@ pragma Env QT_QUICK_CONTROLS_STYLE=Basic
//@ pragma Env QS_NO_RELOAD_POPUP=1
import "./modules/bar/" import "./modules/bar/"
import "./modules/cheatsheet/" import "./modules/cheatsheet/"
import "./modules/mediaControls/"
import "./modules/notificationPopup/" import "./modules/notificationPopup/"
import "./modules/onScreenDisplay/" import "./modules/onScreenDisplay/"
import "./modules/overview/" import "./modules/overview/"
@@ -25,6 +28,7 @@ ShellRoot {
Bar {} Bar {}
Cheatsheet {} Cheatsheet {}
MediaControls {}
NotificationPopup {} NotificationPopup {}
OnScreenDisplayBrightness {} OnScreenDisplayBrightness {}
OnScreenDisplayVolume {} OnScreenDisplayVolume {}
+22 -6
View File
@@ -1,10 +1,26 @@
#!/bin/bash #!/bin/bash
if [ $? -eq 0 ] set -euo pipefail
then
sed '1,/^### DATA ###$/d' $0 | fuzzel --match-mode fzf --dmenu | cut -d ' ' -f 1 | tr -d '\n' | wl-copy MODE="${1:-type}"
else
sed '1,/^### DATA ###$/d' $0 | fuzzel --match-mode fzf --dmenu | cut -d ' ' -f 1 | tr -d '\n' | wl-copy emoji="$(sed '1,/^### DATA ###$/d' "$0" | fuzzel --match-mode fzf --dmenu | cut -d ' ' -f 1 | tr -d '\n')"
fi
case "$MODE" in
type)
wtype "${emoji}" || wl-copy "${emoji}"
;;
copy)
wl-copy "${emoji}"
;;
both)
wtype "${emoji}" || true
wl-copy "${emoji}"
;;
*)
echo "Usage: $0 [type|copy|both]"
exit 1
;;
esac
exit exit
### DATA ### ### DATA ###
😀 grinning face face smile happy joy :D grin 😀 grinning face face smile happy joy :D grin
-6
View File
@@ -1,6 +0,0 @@
#!/usr/bin/bash
WORKSPACES="$(hyprctl monitors -j | jq -r 'map(.activeWorkspace.id)')"
WINDOWS="$(hyprctl clients -j | jq -r --argjson workspaces "$WORKSPACES" 'map(select([.workspace.id] | inside($workspaces)))' )"
GEOM=$(echo "$WINDOWS" | jq -r '.[] | "\(.at[0]),\(.at[1]) \(.size[0])x\(.size[1])"' | slurp -f '%x %y %w %h')
wayshot -s "$GEOM" --stdout ${#:+"$@"}
+2 -2
View File
@@ -2,8 +2,8 @@
## Not ready, but feel free to try it. It's simple: ## Not ready, but feel free to try it. It's simple:
- **Assumption**: You are already using the AGS illogical-impulse - **Assumption**: You are already using the AGS illogical-impulse
- **Install Qt packages** (idk which are actually needed so this is everything I have): `qt5-base qt5-declarative qt5-graphicaleffects qt5-imageformats qt5-quickcontrols qt5-quickcontrols2 qt5-svg qt5-translations qt5-wayland qt5-x11extras qt6-5compat qt6-base qt6-declarative qt6-imageformats qt6-multimedia qt6-positioning qt6-quicktimeline qt6-sensors qt6-svg qt6-tools qt6-translations qt6-virtualkeyboard qt6-wayland qt6-webchannel qt6-webengine qt6-websockets qt6-webview syntax-highlighting` - **Install Qt packages** (idk which are actually needed so this is everything I have): `qt5-base qt5-declarative qt5-graphicaleffects qt5-imageformats qt5-svg qt5-translations qt5-wayland qt6-5compat qt6-base qt6-declarative qt6-imageformats qt6-multimedia qt6-positioning qt6-quicktimeline qt6-sensors qt6-svg qt6-tools qt6-translations qt6-virtualkeyboard qt6-wayland syntax-highlighting`
- **Install quickshell and more stuff**: `yay -S quickshell matugen-bin grimblast` - **Install quickshell and more stuff**: `yay -S quickshell matugen-bin grimblast wtype`
- **Copy** `.config/quickshell` folder and hyprland config files in `.config/hypr/hyprland/` (backing up is your responsibility) (or you can create a new user) - **Copy** `.config/quickshell` folder and hyprland config files in `.config/hypr/hyprland/` (backing up is your responsibility) (or you can create a new user)
- **Run quickshell** with `qs` and see how things are - it's not finished, but **feedback is very welcome** - **Run quickshell** with `qs` and see how things are - it's not finished, but **feedback is very welcome**
- We currently have bar, right sidebar, search/overview - We currently have bar, right sidebar, search/overview
@@ -7,6 +7,6 @@ license=(None)
depends=( depends=(
polkit-gnome polkit-gnome
gnome-keyring gnome-keyring
gnome-control-center networkmanager
blueberry networkmanager better-control-git
) )