diff --git a/.config/ags/modules/.configuration/default_options.jsonc b/.config/ags/modules/.configuration/default_options.jsonc index e20975e1b..eaddb9eb1 100644 --- a/.config/ags/modules/.configuration/default_options.jsonc +++ b/.config/ags/modules/.configuration/default_options.jsonc @@ -46,10 +46,10 @@ "fakeScreenRounding": 2 // 0: None | 1: Always | 2: When not fullscreen }, "apps": { - "bluetooth": "blueberry", + "bluetooth": "better-control --bluetooth", "imageViewer": "loupe", - "network": "XDG_CURRENT_DESKTOP=\"gnome\" gnome-control-center wifi", - "settings": "XDG_CURRENT_DESKTOP=\"gnome\" gnome-control-center", + "network": "better-control --wifi", + "settings": "better-control", "taskManager": "gnome-usage", "terminal": "foot" // This is only for shell actions }, diff --git a/.config/ags/modules/indicators/musiccontrols.js b/.config/ags/modules/indicators/musiccontrols.js index 6f07c7f50..0613ada1b 100644 --- a/.config/ags/modules/indicators/musiccontrols.js +++ b/.config/ags/modules/indicators/musiccontrols.js @@ -22,8 +22,8 @@ var lastCoverPath = ''; function isRealPlayer(player) { return ( // 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.chromium')) && + // !(hasPlasmaIntegration && player.busName.startsWith('org.mpris.MediaPlayer2.firefox')) && + // !(hasPlasmaIntegration && player.busName.startsWith('org.mpris.MediaPlayer2.chromium')) && // playerctld just copies other buses and we don't need duplicates !player.busName.startsWith('org.mpris.MediaPlayer2.playerctld') && // Non-instance mpd bus @@ -209,8 +209,15 @@ const CoverArt = ({ player, ...rest }) => { 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`]) .then(() => { - 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`); + 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"`)}` + 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}"`); Utils.timeout(200, () => { // self.attribute.showImage(self, coverPath) diff --git a/.config/ags/scripts/templates/wal/_musicwal.scss b/.config/ags/scripts/templates/wal/_musicwal.scss new file mode 100644 index 000000000..cad4df3f5 --- /dev/null +++ b/.config/ags/scripts/templates/wal/_musicwal.scss @@ -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}}; \ No newline at end of file diff --git a/.config/ags/scss/_music.scss b/.config/ags/scss/_music.scss index 473a855e8..7450c5041 100644 --- a/.config/ags/scss/_music.scss +++ b/.config/ags/scss/_music.scss @@ -20,8 +20,11 @@ $secondaryContainer: transparentize(mix(mix($background, $color2, 50%), $color6, $onSecondaryContainer: mix($color7, $color2, 90%); @if $darkmode == False { $onSecondaryContainer: mix($onSecondaryContainer, black, 50%); +} @else { + $onSecondaryContainer: mix($onSecondaryContainer, white, 50%); } + .osd-music { @include menu_decel; @include elevation2; diff --git a/.config/hypr/custom/keybinds.conf b/.config/hypr/custom/keybinds.conf index d3079441d..798742ee8 100644 --- a/.config/hypr/custom/keybinds.conf +++ b/.config/hypr/custom/keybinds.conf @@ -1,8 +1,10 @@ # See https://wiki.hyprland.org/Configuring/Binds/ #! -##! User keybinds -bind = Ctrl+Super+Alt, Slash, exec, xdg-open ~/.config/hypr/custom/keybinds.conf # Edit custom keybinds +##! Extra keybinds +bind = Ctrl+Super+Alt, Slash, exec, xdg-open ~/.config/hypr/custom/keybinds.conf # Edit extra keybinds # Add stuff here # Use #! to add an extra column on the cheatsheet # Use ##! to add a section in that column +# Add a comment after a bind to add a description, like above + diff --git a/.config/hypr/hyprland.conf b/.config/hypr/hyprland.conf index 1255db74a..eed207898 100644 --- a/.config/hypr/hyprland.conf +++ b/.config/hypr/hyprland.conf @@ -1,5 +1,5 @@ # 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 submap = global # This is required for catchall to work diff --git a/.config/hypr/hyprland/keybinds.conf b/.config/hypr/hyprland/keybinds.conf index a2a485c79..18a0ab955 100644 --- a/.config/hypr/hyprland/keybinds.conf +++ b/.config/hypr/hyprland/keybinds.conf @@ -23,6 +23,7 @@ bindd = Super, A, Toggle left sidebar, global, quickshell:sidebarLeftToggle # To bind = Super, O, global, quickshell:sidebarLeftToggle # [hidden] bindd = Super, N, Toggle right sidebar, global, quickshell:sidebarRightToggle # Toggle right sidebar 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 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 # 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, Period, Copy an emoji, exec, pkill fuzzel || ~/.local/bin/fuzzel-emoji # Pick emoji >> 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 # Emoji 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 # OCR @@ -198,8 +199,8 @@ bind = Super+Alt, E, exec, thunar # [hidden] bind = Super, W, exec, zen-browser # [hidden] bind = Super+Shift, W, exec, wps # WPS Office 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, I, exec, XDG_CURRENT_DESKTOP="gnome" gnome-control-center # GNOME Settings bind = Ctrl+Shift, Escape, exec, gnome-system-monitor # GNOME System monitor # Cursed stuff diff --git a/.config/hypr/hyprland/rules.conf b/.config/hypr/hyprland/rules.conf index 876f45b53..75adefcd4 100644 --- a/.config/hypr/hyprland/rules.conf +++ b/.config/hypr/hyprland/rules.conf @@ -105,15 +105,14 @@ layerrule = ignorealpha 0.6, osk[0-9]* # Quickshell ## My stuff +layerrule = animation slide, quickshell:bar layerrule = animation fade, quickshell:screenCorners layerrule = animation slide right, quickshell:sidebarRight layerrule = animation slide left, quickshell:sidebarLeft -layerrule = animation slide top, quickshell:onScreenDisplay layerrule = blur, quickshell:session layerrule = noanim, quickshell:session # Launchers need to be FAST layerrule = noanim, quickshell:overview -layerrule = noanim, launcher layerrule = noanim, gtk4-layer-shell ## outfoxxed's stuff layerrule = blur, shell:bar diff --git a/.config/quickshell/GlobalStates.qml b/.config/quickshell/GlobalStates.qml index 470dfc4f8..1b879421b 100644 --- a/.config/quickshell/GlobalStates.qml +++ b/.config/quickshell/GlobalStates.qml @@ -8,8 +8,8 @@ pragma ComponentBehavior: Bound Singleton { id: root - property int sidebarLeftOpenCount: 0 - property int sidebarRightOpenCount: 0 + property bool sidebarLeftOpen: false + property bool sidebarRightOpen: false property bool overviewOpen: false property bool workspaceShowNumbers: false property bool superReleaseMightTrigger: true @@ -31,7 +31,7 @@ Singleton { GlobalShortcut { name: "workspaceNumber" - description: "Hold to show workspace numbers, release to show icons" + description: qsTr("Hold to show workspace numbers, release to show icons") onPressed: { workspaceShowNumbersTimer.start() diff --git a/.config/quickshell/modules/bar/Bar.qml b/.config/quickshell/modules/bar/Bar.qml index c3365dd0f..ea5aa58ad 100644 --- a/.config/quickshell/modules/bar/Bar.qml +++ b/.config/quickshell/modules/bar/Bar.qml @@ -108,7 +108,7 @@ Scope { ScrollHint { reveal: barLeftSideMouseArea.hovered icon: "light_mode" - tooltipText: "Scroll to change brightness" + tooltipText: qsTr("Scroll to change brightness") side: "left" anchors.left: parent.left anchors.verticalCenter: parent.verticalCenter @@ -127,7 +127,7 @@ Scope { // Layout.fillHeight: true 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 implicitHeight: distroIcon.height + 5*2 @@ -283,7 +283,7 @@ Scope { ScrollHint { reveal: barRightSideMouseArea.hovered icon: "volume_up" - tooltipText: "Scroll to change volume" + tooltipText: qsTr("Scroll to change volume") side: "right" anchors.right: parent.right anchors.verticalCenter: parent.verticalCenter @@ -301,7 +301,7 @@ Scope { Layout.fillHeight: true implicitWidth: indicatorsRowLayout.implicitWidth + 10*2 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 { id: indicatorsRowLayout anchors.centerIn: parent diff --git a/.config/quickshell/modules/bar/Battery.qml b/.config/quickshell/modules/bar/Battery.qml index 5267ceb16..5def8560e 100644 --- a/.config/quickshell/modules/bar/Battery.qml +++ b/.config/quickshell/modules/bar/Battery.qml @@ -8,6 +8,7 @@ import Quickshell.Services.UPower Rectangle { id: root + property bool borderless: ConfigOptions.bar.borderless readonly property var chargeState: UPower.displayDevice.state readonly property bool isCharging: chargeState == UPowerDeviceState.Charging readonly property bool isPluggedIn: isCharging || chargeState == UPowerDeviceState.PendingCharge @@ -18,7 +19,7 @@ Rectangle { implicitWidth: rowLayout.implicitWidth + rowLayout.spacing * 2 implicitHeight: 32 - color: Appearance.colors.colLayer1 + color: borderless ? "transparent" : Appearance.colors.colLayer1 radius: Appearance.rounding.small RowLayout { @@ -31,18 +32,14 @@ Rectangle { implicitWidth: (isCharging ? (boltIconLoader?.item?.width ?? 0) : 0) Behavior on implicitWidth { - NumberAnimation { - duration: Appearance.animation.elementMove.duration - easing.type: Appearance.animation.elementMove.type - easing.bezierCurve: Appearance.animation.elementMove.bezierCurve - } + animation: Appearance.animation.elementMove.numberAnimation.createObject(this) } } StyledText { Layout.alignment: Qt.AlignVCenter color: Appearance.colors.colOnLayer1 - text: `${Math.round(percentage * 100)}%` + text: `${Math.round(percentage * 100)}` } CircularProgress { @@ -56,6 +53,7 @@ Rectangle { MaterialSymbol { anchors.centerIn: parent + fill: 1 text: "battery_full" iconSize: Appearance.font.pixelSize.normal color: (isLow && !isCharging) ? batteryLowOnBackground : Appearance.m3colors.m3onSecondaryContainer @@ -91,12 +89,7 @@ Rectangle { } Behavior on opacity { - NumberAnimation { - duration: Appearance.animation.elementMove.duration - easing.type: Appearance.animation.elementMove.type - easing.bezierCurve: Appearance.animation.elementMove.bezierCurve - } - + animation: Appearance.animation.elementMove.numberAnimation.createObject(this) } } diff --git a/.config/quickshell/modules/bar/CircleUtilButton.qml b/.config/quickshell/modules/bar/CircleUtilButton.qml index 0651211c3..c4673e743 100644 --- a/.config/quickshell/modules/bar/CircleUtilButton.qml +++ b/.config/quickshell/modules/bar/CircleUtilButton.qml @@ -1,5 +1,6 @@ import "root:/modules/common" import "root:/modules/common/widgets/" +import "root:/modules/common/functions/color_utils.js" as ColorUtils import QtQuick import QtQuick.Controls import QtQuick.Layouts @@ -21,7 +22,9 @@ Button { background: Rectangle { anchors.fill: parent 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)) } diff --git a/.config/quickshell/modules/bar/ClockWidget.qml b/.config/quickshell/modules/bar/ClockWidget.qml index e44ac7f5a..080dfb9b8 100644 --- a/.config/quickshell/modules/bar/ClockWidget.qml +++ b/.config/quickshell/modules/bar/ClockWidget.qml @@ -5,9 +5,10 @@ import QtQuick import QtQuick.Layouts Rectangle { + property bool borderless: ConfigOptions.bar.borderless implicitWidth: rowLayout.implicitWidth + rowLayout.spacing * 6 // idk, text seems nicer w/ more padding implicitHeight: 32 - color: Appearance.colors.colLayer1 + color: borderless ? "transparent" : Appearance.colors.colLayer1 radius: Appearance.rounding.small RowLayout { diff --git a/.config/quickshell/modules/bar/Media.qml b/.config/quickshell/modules/bar/Media.qml index ad6722806..f4b8606b9 100644 --- a/.config/quickshell/modules/bar/Media.qml +++ b/.config/quickshell/modules/bar/Media.qml @@ -1,29 +1,24 @@ import "root:/modules/common" import "root:/modules/common/widgets" import "root:/services" +import "root:/modules/common/functions/string_utils.js" as StringUtils import QtQuick import QtQuick.Layouts import Quickshell import Quickshell.Io import Quickshell.Services.Mpris +import Quickshell.Hyprland Item { + id: root + property bool borderless: ConfigOptions.bar.borderless 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 implicitWidth: rowLayout.implicitWidth + rowLayout.spacing * 2 implicitHeight: 40 - // Background - Rectangle { - anchors.centerIn: parent - width: parent.width - implicitHeight: 32 - color: Appearance.colors.colLayer1 - radius: Appearance.rounding.small - } - Timer { running: activePlayer?.playbackState == MprisPlaybackState.Playing interval: 1000 @@ -33,7 +28,7 @@ Item { MouseArea { 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) => { if (event.button === Qt.MiddleButton) { activePlayer.togglePlaying(); @@ -41,11 +36,21 @@ Item { activePlayer.previous(); } else if (event.button === Qt.ForwardButton || event.button === Qt.RightButton) { 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 spacing: 4 @@ -62,6 +67,7 @@ Item { MaterialSymbol { anchors.centerIn: parent + fill: 1 text: activePlayer?.isPlaying ? "pause" : "play_arrow" iconSize: Appearance.font.pixelSize.normal color: Appearance.m3colors.m3onSecondaryContainer diff --git a/.config/quickshell/modules/bar/Resource.qml b/.config/quickshell/modules/bar/Resource.qml index c13a3e0df..18fdcba42 100644 --- a/.config/quickshell/modules/bar/Resource.qml +++ b/.config/quickshell/modules/bar/Resource.qml @@ -28,6 +28,7 @@ Item { MaterialSymbol { anchors.centerIn: parent + fill: 1 text: iconName iconSize: Appearance.font.pixelSize.normal color: Appearance.m3colors.m3onSecondaryContainer @@ -38,15 +39,11 @@ Item { StyledText { Layout.alignment: Qt.AlignVCenter color: Appearance.colors.colOnLayer1 - text: `${Math.round(percentage * 100)}%` + text: `${Math.round(percentage * 100)}` } Behavior on x { - NumberAnimation { - duration: Appearance.animation.elementMove.duration - easing.type: Appearance.animation.elementMove.type - easing.bezierCurve: Appearance.animation.elementMove.bezierCurve - } + animation: Appearance.animation.elementMove.numberAnimation.createObject(this) } } diff --git a/.config/quickshell/modules/bar/Resources.qml b/.config/quickshell/modules/bar/Resources.qml index db7741b2c..3f82c9950 100644 --- a/.config/quickshell/modules/bar/Resources.qml +++ b/.config/quickshell/modules/bar/Resources.qml @@ -8,9 +8,10 @@ import Quickshell.Io import Quickshell.Services.Mpris Rectangle { + property bool borderless: ConfigOptions.bar.borderless implicitWidth: rowLayout.implicitWidth + rowLayout.anchors.leftMargin + rowLayout.anchors.rightMargin implicitHeight: 32 - color: Appearance.colors.colLayer1 + color: borderless ? "transparent" : Appearance.colors.colLayer1 radius: Appearance.rounding.small RowLayout { diff --git a/.config/quickshell/modules/bar/SysTrayItem.qml b/.config/quickshell/modules/bar/SysTrayItem.qml index 6bef27ad3..900ef68f4 100644 --- a/.config/quickshell/modules/bar/SysTrayItem.qml +++ b/.config/quickshell/modules/bar/SysTrayItem.qml @@ -1,4 +1,5 @@ import "root:/modules/common/" +import "root:/modules/common/functions/color_utils.js" as ColorUtils import QtQuick import QtQuick.Layouts import Quickshell @@ -57,7 +58,7 @@ MouseArea { ColorOverlay { anchors.fill: desaturatedIcon source: desaturatedIcon - color: Appearance.transparentize(Appearance.colors.colOnLayer0, 0.6) + color: ColorUtils.transparentize(Appearance.colors.colOnLayer0, 0.6) } } diff --git a/.config/quickshell/modules/bar/UtilButtons.qml b/.config/quickshell/modules/bar/UtilButtons.qml index 93b34d8d9..1f0a09f91 100644 --- a/.config/quickshell/modules/bar/UtilButtons.qml +++ b/.config/quickshell/modules/bar/UtilButtons.qml @@ -7,10 +7,12 @@ import Quickshell.Io import Quickshell.Hyprland Rectangle { + id: root + property bool borderless: ConfigOptions.bar.borderless Layout.alignment: Qt.AlignVCenter implicitWidth: rowLayout.implicitWidth + rowLayout.spacing * 2 implicitHeight: 32 - color: Appearance.colors.colLayer1 + color: borderless ? "transparent" : Appearance.colors.colLayer1 radius: Appearance.rounding.small RowLayout { @@ -24,7 +26,8 @@ Rectangle { onClicked: Hyprland.dispatch("exec grimblast copy area") MaterialSymbol { - anchors.centerIn: parent + horizontalAlignment: Qt.AlignHCenter + fill: 1 text: "screenshot_region" iconSize: Appearance.font.pixelSize.normal color: Appearance.colors.colOnLayer2 @@ -37,7 +40,8 @@ Rectangle { onClicked: Hyprland.dispatch("exec hyprpicker -a") MaterialSymbol { - anchors.centerIn: parent + horizontalAlignment: Qt.AlignHCenter + fill: 1 text: "colorize" iconSize: Appearance.font.pixelSize.normal color: Appearance.colors.colOnLayer2 diff --git a/.config/quickshell/modules/bar/Workspaces.qml b/.config/quickshell/modules/bar/Workspaces.qml index fc6b7dc45..b00738f61 100644 --- a/.config/quickshell/modules/bar/Workspaces.qml +++ b/.config/quickshell/modules/bar/Workspaces.qml @@ -15,6 +15,7 @@ import Qt5Compat.GraphicalEffects Item { required property var bar + property bool borderless: ConfigOptions.bar.borderless readonly property HyprlandMonitor monitor: Hyprland.monitorFor(bar.screen) readonly property Toplevel activeWindow: ToplevelManager.activeToplevel @@ -66,7 +67,7 @@ Item { implicitHeight: 32 implicitWidth: rowLayout.implicitWidth + widgetPadding * 2 radius: Appearance.rounding.small - color: Appearance.colors.colLayer1 + color: borderless ? "transparent" : Appearance.colors.colLayer1 } // Scroll to switch workspaces @@ -119,26 +120,14 @@ Item { opacity: (workspaceOccupied[index] && !(!activeWindow?.activated && monitor.activeWorkspace?.id === index+1)) ? 1 : 0 Behavior on opacity { - NumberAnimation { - duration: Appearance.animation.elementMove.duration - easing.type: Appearance.animation.elementMove.type - easing.bezierCurve: Appearance.animation.elementMove.bezierCurve - } + animation: Appearance.animation.elementMove.numberAnimation.createObject(this) } Behavior on radiusLeft { - NumberAnimation { - duration: Appearance.animation.elementMove.duration - easing.type: Appearance.animation.elementMove.type - easing.bezierCurve: Appearance.animation.elementMove.bezierCurve - } + animation: Appearance.animation.elementMove.numberAnimation.createObject(this) } Behavior on radiusRight { - NumberAnimation { - duration: Appearance.animation.elementMove.duration - easing.type: Appearance.animation.elementMove.type - easing.bezierCurve: Appearance.animation.elementMove.bezierCurve - } + animation: Appearance.animation.elementMove.numberAnimation.createObject(this) } } @@ -206,15 +195,6 @@ Item { (workspaceOccupied[index] ? Appearance.colors.colOnLayer1 : 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 { NumberAnimation { duration: Appearance.animation.elementMoveFast.duration diff --git a/.config/quickshell/modules/cheatsheet/Cheatsheet.qml b/.config/quickshell/modules/cheatsheet/Cheatsheet.qml index 03e91f1e6..587e47150 100644 --- a/.config/quickshell/modules/cheatsheet/Cheatsheet.qml +++ b/.config/quickshell/modules/cheatsheet/Cheatsheet.qml @@ -2,10 +2,11 @@ import "root:/" import "root:/services" import "root:/modules/common" import "root:/modules/common/widgets" +import "root:/modules/common/functions/color_utils.js" as ColorUtils import QtQuick import QtQuick.Controls +import QtQuick.Effects import QtQuick.Layouts -import Qt5Compat.GraphicalEffects import Quickshell.Io import Quickshell import Quickshell.Widgets @@ -15,18 +16,24 @@ import Quickshell.Hyprland Scope { // Scope id: root - Variants { // Window repeater - id: cheatsheetVariants - model: Quickshell.screens - - PanelWindow { // Window + Loader { + id: cheatsheetLoader + active: false + + sourceComponent: PanelWindow { // Window id: cheatsheetRoot - visible: false - focusable: true + visible: cheatsheetLoader.active - property var modelData + anchors { + top: true + bottom: true + left: true + right: true + } - screen: modelData + function hide() { + cheatsheetLoader.active = false + } exclusiveZone: 0 implicitWidth: cheatsheetBackground.width + Appearance.sizes.elevationMargin * 2 implicitHeight: cheatsheetBackground.height + Appearance.sizes.elevationMargin * 2 @@ -42,25 +49,9 @@ Scope { // Scope HyprlandFocusGrab { // Click outside to close id: grab windows: [ cheatsheetRoot ] - active: false + active: cheatsheetRoot.visible onCleared: () => { - if (!active) cheatsheetRoot.visible = false - } - } - - Connections { - target: cheatsheetRoot - function onVisibleChanged() { - delayedGrabTimer.start() - } - } - - Timer { - id: delayedGrabTimer - interval: ConfigOptions.hacks.arbitraryRaceConditionDelay - repeat: false - onTriggered: { - grab.active = cheatsheetRoot.visible + if (!active) cheatsheetRoot.hide() } } @@ -74,9 +65,19 @@ Scope { // Scope implicitWidth: cheatsheetColumnLayout.implicitWidth + 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 if (event.key === Qt.Key_Escape) { - cheatsheetRoot.visible = false + cheatsheetRoot.hide() } } @@ -94,23 +95,19 @@ Scope { // Scope PointingHandInteraction {} onClicked: { - cheatsheetRoot.visible = false + cheatsheetRoot.hide() } - background: Item {} + background: null contentItem: Rectangle { anchors.fill: parent radius: Appearance.rounding.full color: closeButton.pressed ? Appearance.colors.colLayer0Active : closeButton.hovered ? Appearance.colors.colLayer0Hover : - Appearance.transparentize(Appearance.colors.colLayer0, 1) + ColorUtils.transparentize(Appearance.colors.colLayer0, 1) Behavior on color { - ColorAnimation { - duration: Appearance.animation.elementMoveFast.duration - easing.type: Appearance.animation.elementMoveFast.type - easing.bezierCurve: Appearance.animation.elementMoveFast.bezierCurve - } + animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this) } 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 { target: "cheatsheet" function toggle(): void { - for (let i = 0; i < cheatsheetVariants.instances.length; i++) { - let panelWindow = cheatsheetVariants.instances[i]; - if (panelWindow.modelData.name == Hyprland.focusedMonitor.name) { - panelWindow.visible = !panelWindow.visible; - if(panelWindow.visible) Notifications.timeoutAll(); - } - } + cheatsheetLoader.active = !cheatsheetLoader.active } function close(): void { - for (let i = 0; i < cheatsheetVariants.instances.length; i++) { - let panelWindow = cheatsheetVariants.instances[i]; - if (panelWindow.modelData.name == Hyprland.focusedMonitor.name) { - panelWindow.visible = false; - } - } + cheatsheetLoader.active = false } function open(): void { - for (let i = 0; i < cheatsheetVariants.instances.length; i++) { - let panelWindow = cheatsheetVariants.instances[i]; - if (panelWindow.modelData.name == Hyprland.focusedMonitor.name) { - panelWindow.visible = true; - if(panelWindow.visible) Notifications.timeoutAll(); - } - } + cheatsheetLoader.active = true } } GlobalShortcut { name: "cheatsheetToggle" - description: "Toggles cheatsheet on press" + description: qsTr("Toggles cheatsheet on press") onPressed: { - for (let i = 0; i < cheatsheetVariants.instances.length; i++) { - let panelWindow = cheatsheetVariants.instances[i]; - if (panelWindow.modelData.name == Hyprland.focusedMonitor.name) { - panelWindow.visible = !panelWindow.visible; - if(panelWindow.visible) Notifications.timeoutAll(); - } - } + cheatsheetLoader.active = !cheatsheetLoader.active; } } GlobalShortcut { name: "cheatsheetOpen" - description: "Opens cheatsheet on press" + description: qsTr("Opens cheatsheet on press") onPressed: { - for (let i = 0; i < cheatsheetVariants.instances.length; i++) { - let panelWindow = cheatsheetVariants.instances[i]; - if (panelWindow.modelData.name == Hyprland.focusedMonitor.name) { - panelWindow.visible = true; - if(panelWindow.visible) Notifications.timeoutAll(); - } - } + cheatsheetLoader.active = true; } } GlobalShortcut { name: "cheatsheetClose" - description: "Closes cheatsheet on press" + description: qsTr("Closes cheatsheet on press") onPressed: { - for (let i = 0; i < cheatsheetVariants.instances.length; i++) { - let panelWindow = cheatsheetVariants.instances[i]; - if (panelWindow.modelData.name == Hyprland.focusedMonitor.name) { - panelWindow.visible = false; - } - } + cheatsheetLoader.active = false; } } diff --git a/.config/quickshell/modules/common/Appearance.qml b/.config/quickshell/modules/common/Appearance.qml index b7d4ec608..381bfcab7 100644 --- a/.config/quickshell/modules/common/Appearance.qml +++ b/.config/quickshell/modules/common/Appearance.qml @@ -1,9 +1,11 @@ import QtQuick import Quickshell +import "root:/modules/common/functions/color_utils.js" as ColorUtils pragma Singleton pragma ComponentBehavior: Bound Singleton { + id: root property QtObject m3colors property QtObject animation property QtObject animationCurves @@ -13,18 +15,6 @@ Singleton { property QtObject sizes 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 { property bool darkmode: false property bool transparent: false @@ -108,37 +98,37 @@ Singleton { property color colSubtext: m3colors.m3outline property color colLayer0: m3colors.m3background property color colOnLayer0: m3colors.m3onBackground - property color colLayer0Hover: mix(colLayer0, colOnLayer0, 0.9) - property color colLayer0Active: mix(colLayer0, colOnLayer0, 0.8) + property color colLayer0Hover: ColorUtils.mix(colLayer0, colOnLayer0, 0.9) + property color colLayer0Active: ColorUtils.mix(colLayer0, colOnLayer0, 0.8) property color colLayer1: m3colors.m3surfaceContainerLow; property color colOnLayer1: m3colors.m3onSurfaceVariant; - property color colOnLayer1Inactive: mix(colOnLayer1, colLayer1, 0.45); - property color colLayer2: mix(m3colors.m3surfaceContainer, m3colors.m3surfaceContainerHigh, 0.55); + property color colOnLayer1Inactive: ColorUtils.mix(colOnLayer1, colLayer1, 0.45); + property color colLayer2: ColorUtils.mix(m3colors.m3surfaceContainer, m3colors.m3surfaceContainerHigh, 0.55); property color colOnLayer2: m3colors.m3onSurface; - property color colOnLayer2Disabled: mix(colOnLayer2, m3colors.m3background, 0.4); - property color colLayer3: mix(m3colors.m3surfaceContainerHigh, m3colors.m3onSurface, 0.96); + property color colOnLayer2Disabled: ColorUtils.mix(colOnLayer2, m3colors.m3background, 0.4); + property color colLayer3: ColorUtils.mix(m3colors.m3surfaceContainerHigh, m3colors.m3onSurface, 0.96); property color colOnLayer3: m3colors.m3onSurface; - property color colLayer1Hover: mix(colLayer1, colOnLayer1, 0.88); - property color colLayer1Active: mix(colLayer1, colOnLayer1, 0.77); - property color colLayer2Hover: mix(colLayer2, colOnLayer2, 0.90); - property color colLayer2Active: mix(colLayer2, colOnLayer2, 0.80); - property color colLayer2Disabled: mix(colLayer2, m3colors.m3background, 0.8); - property color colLayer3Hover: mix(colLayer3, colOnLayer3, 0.90); - property color colLayer3Active: mix(colLayer3, colOnLayer3, 0.80); - property color colPrimaryHover: mix(m3colors.m3primary, colLayer1Hover, 0.85) - property color colPrimaryActive: mix(m3colors.m3primary, colLayer1Active, 0.7) - property color colPrimaryContainerHover: mix(m3colors.m3primaryContainer, colLayer1Hover, 0.7) - property color colPrimaryContainerActive: mix(m3colors.m3primaryContainer, colLayer1Active, 0.6) - property color colSecondaryHover: mix(m3colors.m3secondary, colLayer1Hover, 0.85) - property color colSecondaryActive: mix(m3colors.m3secondary, colLayer1Active, 0.4) - property color colSecondaryContainerHover: mix(m3colors.m3secondaryContainer, colLayer1Hover, 0.6) - property color colSecondaryContainerActive: mix(m3colors.m3secondaryContainer, colLayer1Active, 0.54) - property color colSurfaceContainerHighestHover: mix(m3colors.m3surfaceContainerHighest, m3colors.m3onSurface, 0.95) - property color colSurfaceContainerHighestActive: mix(m3colors.m3surfaceContainerHighest, m3colors.m3onSurface, 0.85) + property color colLayer1Hover: ColorUtils.mix(colLayer1, colOnLayer1, 0.92); + property color colLayer1Active: ColorUtils.mix(colLayer1, colOnLayer1, 0.85); + property color colLayer2Hover: ColorUtils.mix(colLayer2, colOnLayer2, 0.90); + property color colLayer2Active: ColorUtils.mix(colLayer2, colOnLayer2, 0.80); + property color colLayer2Disabled: ColorUtils.mix(colLayer2, m3colors.m3background, 0.8); + property color colLayer3Hover: ColorUtils.mix(colLayer3, colOnLayer3, 0.90); + property color colLayer3Active: ColorUtils.mix(colLayer3, colOnLayer3, 0.80); + property color colPrimaryHover: ColorUtils.mix(m3colors.m3primary, colLayer1Hover, 0.85) + property color colPrimaryActive: ColorUtils.mix(m3colors.m3primary, colLayer1Active, 0.7) + property color colPrimaryContainerHover: ColorUtils.mix(m3colors.m3primaryContainer, colLayer1Hover, 0.7) + property color colPrimaryContainerActive: ColorUtils.mix(m3colors.m3primaryContainer, colLayer1Active, 0.6) + property color colSecondaryHover: ColorUtils.mix(m3colors.m3secondary, colLayer1Hover, 0.85) + property color colSecondaryActive: ColorUtils.mix(m3colors.m3secondary, colLayer1Active, 0.4) + property color colSecondaryContainerHover: ColorUtils.mix(m3colors.m3secondaryContainer, colLayer1Hover, 0.6) + property color colSecondaryContainerActive: ColorUtils.mix(m3colors.m3secondaryContainer, colLayer1Active, 0.54) + property color colSurfaceContainerHighestHover: ColorUtils.mix(m3colors.m3surfaceContainerHighest, m3colors.m3onSurface, 0.95) + 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 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 colShadow: transparentize(m3colors.m3shadow, 0.75) + property color colScrim: ColorUtils.transparentize(m3colors.m3scrim, 0.5) + property color colShadow: ColorUtils.transparentize(m3colors.m3shadow, 0.75) } rounding: QtObject { @@ -189,24 +179,57 @@ Singleton { property int type: Easing.BezierSpline property list bezierCurve: animationCurves.emphasized 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 int duration: 400 property int type: Easing.BezierSpline property list bezierCurve: animationCurves.emphasizedDecel 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 int duration: 200 property int type: Easing.BezierSpline property list bezierCurve: animationCurves.emphasizedAccel 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 int duration: 200 property int type: Easing.BezierSpline property list bezierCurve: animationCurves.standardDecel 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 int duration: 400 @@ -226,18 +249,21 @@ Singleton { } sizes: QtObject { - property int barHeight: 40 - property int barCenterSideModuleWidth: 360 - property int barPreferredSideSectionWidth: 400 - property int sidebarWidth: 450 - property int sidebarWidthExtended: 750 - property int notificationPopupWidth: 410 - property int searchWidthCollapsed: 260 - property int searchWidth: 450 - property int hyprlandGapsOut: 5 - property int elevationMargin: 7 - property int fabShadowRadius: 5 - property int fabHoveredShadowRadius: 7 + property real barHeight: 40 + property real barCenterSideModuleWidth: 360 + property real barPreferredSideSectionWidth: 400 + property real sidebarWidth: 460 + property real sidebarWidthExtended: 750 + property real osdWidth: 200 + property real mediaControlsWidth: 440 + property real mediaControlsHeight: 160 + property real notificationPopupWidth: 410 + property real searchWidthCollapsed: 260 + property real searchWidth: 450 + property real hyprlandGapsOut: 5 + property real elevationMargin: 8 + property real fabShadowRadius: 5 + property real fabHoveredShadowRadius: 7 } syntaxHighlightingTheme: Appearance.m3colors.darkmode ? "Monokai" : "ayu Light" diff --git a/.config/quickshell/modules/common/ConfigOptions.qml b/.config/quickshell/modules/common/ConfigOptions.qml index 57d6e5aa5..ce4ddc0f9 100644 --- a/.config/quickshell/modules/common/ConfigOptions.qml +++ b/.config/quickshell/modules/common/ConfigOptions.qml @@ -5,7 +5,7 @@ pragma ComponentBehavior: Bound Singleton { 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 { @@ -13,10 +13,10 @@ Singleton { } property QtObject apps: QtObject { - property string bluetooth: "blueberry" + property string bluetooth: "better-control --bluetooth" property string imageViewer: "loupe" - property string network: "XDG_CURRENT_DESKTOP=\"gnome\" gnome-control-center wifi" - property string settings: "XDG_CURRENT_DESKTOP=\"gnome\" gnome-control-center" + property string network: "better-control --wifi" + property string settings: "better-control" property string taskManager: "gnome-usage" property string terminal: "foot" // This is only for shell actions } @@ -25,6 +25,7 @@ Singleton { property int batteryLowThreshold: 20 property string topLeftIcon: "spark" // Options: distro, spark property bool showBackground: true + property bool borderless: false property QtObject resources: QtObject { property bool alwaysShowSwap: true property bool alwaysShowCpu: false @@ -70,7 +71,7 @@ Singleton { property string defaultProvider: "yandere" property int limit: 20 property QtObject zerochan: QtObject { - property string username: "" + property string username: "[unset]" } } } diff --git a/.config/quickshell/modules/common/PersistentStates.qml b/.config/quickshell/modules/common/PersistentStates.qml index 4c9a7608c..cbf52e8e4 100644 --- a/.config/quickshell/modules/common/PersistentStates.qml +++ b/.config/quickshell/modules/common/PersistentStates.qml @@ -9,12 +9,6 @@ Singleton { } 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 bool collapsed: false property int selectedTab: 0 diff --git a/.config/quickshell/modules/common/functions/color_utils.js b/.config/quickshell/modules/common/functions/color_utils.js new file mode 100644 index 000000000..652b09ebb --- /dev/null +++ b/.config/quickshell/modules/common/functions/color_utils.js @@ -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)); +} diff --git a/.config/quickshell/modules/common/functions/string_utils.js b/.config/quickshell/modules/common/functions/string_utils.js index 105a3fef9..d3784637c 100644 --- a/.config/quickshell/modules/common/functions/string_utils.js +++ b/.config/quickshell/modules/common/functions/string_utils.js @@ -147,4 +147,32 @@ function wordWrap(str, maxLen) { } if (current.length > 0) lines.push(current); 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')}`; + } } \ No newline at end of file diff --git a/.config/quickshell/modules/common/widgets/DialogButton.qml b/.config/quickshell/modules/common/widgets/DialogButton.qml index d4310dbdf..c3ae39397 100644 --- a/.config/quickshell/modules/common/widgets/DialogButton.qml +++ b/.config/quickshell/modules/common/widgets/DialogButton.qml @@ -1,4 +1,5 @@ import "root:/modules/common" +import "root:/modules/common/functions/color_utils.js" as ColorUtils import QtQuick import QtQuick.Controls import QtQuick.Layouts @@ -17,14 +18,12 @@ Button { background: Rectangle { anchors.fill: parent 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 { - ColorAnimation { - duration: Appearance.animation.elementMove.duration - easing.type: Appearance.animation.elementMove.type - easing.bezierCurve: Appearance.animation.elementMove.bezierCurve - } + animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this) } @@ -41,11 +40,7 @@ Button { color: button.enabled ? Appearance.m3colors.m3primary : Appearance.m3colors.m3outline Behavior on color { - ColorAnimation { - duration: Appearance.animation.elementMove.duration - easing.type: Appearance.animation.elementMove.type - easing.bezierCurve: Appearance.animation.elementMove.bezierCurve - } + animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this) } } diff --git a/.config/quickshell/modules/common/widgets/MenuButton.qml b/.config/quickshell/modules/common/widgets/MenuButton.qml index f5de52c5e..f44c4c099 100644 --- a/.config/quickshell/modules/common/widgets/MenuButton.qml +++ b/.config/quickshell/modules/common/widgets/MenuButton.qml @@ -1,4 +1,5 @@ import "root:/modules/common" +import "root:/modules/common/functions/color_utils.js" as ColorUtils import QtQuick import QtQuick.Controls import QtQuick.Layouts @@ -16,17 +17,12 @@ Button { background: Rectangle { anchors.fill: parent - color: (button.down && button.enabled) ? Appearance.transparentize(Appearance.m3colors.m3onSurface, 0.84) : - ((button.hovered && button.enabled) ? Appearance.transparentize(Appearance.m3colors.m3onSurface, 0.92) : - Appearance.transparentize(Appearance.m3colors.m3onSurface, 1)) + color: (button.down && button.enabled) ? ColorUtils.transparentize(Appearance.m3colors.m3onSurface, 0.84) : + ((button.hovered && button.enabled) ? ColorUtils.transparentize(Appearance.m3colors.m3onSurface, 0.92) : + ColorUtils.transparentize(Appearance.m3colors.m3onSurface, 1)) Behavior on color { - ColorAnimation { - duration: Appearance.animation.elementMove.duration - easing.type: Appearance.animation.elementMove.type - easing.bezierCurve: Appearance.animation.elementMove.bezierCurve - } - + animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this) } } @@ -42,11 +38,7 @@ Button { color: button.enabled ? Appearance.m3colors.m3onSurface : Appearance.m3colors.m3outline Behavior on color { - ColorAnimation { - duration: Appearance.animation.elementMove.duration - easing.type: Appearance.animation.elementMove.type - easing.bezierCurve: Appearance.animation.elementMove.bezierCurve - } + animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this) } } diff --git a/.config/quickshell/modules/common/widgets/NavRailButton.qml b/.config/quickshell/modules/common/widgets/NavRailButton.qml index 13bf2f949..1f55ad282 100644 --- a/.config/quickshell/modules/common/widgets/NavRailButton.qml +++ b/.config/quickshell/modules/common/widgets/NavRailButton.qml @@ -1,5 +1,6 @@ import "root:/modules/common" import "root:/modules/common/widgets" +import "root:/modules/common/functions/color_utils.js" as ColorUtils import QtQuick import QtQuick.Controls import QtQuick.Layouts @@ -16,7 +17,7 @@ Button { implicitHeight: columnLayout.implicitHeight implicitWidth: columnLayout.implicitWidth - background: Item {} + background: null PointingHandInteraction {} // Real stuff @@ -30,14 +31,10 @@ Button { radius: Appearance.rounding.full color: toggled ? (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 { - ColorAnimation { - duration: Appearance.animation.elementMove.duration - easing.type: Appearance.animation.elementMove.type - easing.bezierCurve: Appearance.animation.elementMove.bezierCurve - } + animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this) } MaterialSymbol { id: navRailButtonIcon @@ -48,11 +45,7 @@ Button { color: toggled ? Appearance.m3colors.m3onSecondaryContainer : Appearance.colors.colOnLayer1 Behavior on color { - ColorAnimation { - duration: Appearance.animation.elementMove.duration - easing.type: Appearance.animation.elementMove.type - easing.bezierCurve: Appearance.animation.elementMove.bezierCurve - } + animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this) } } } diff --git a/.config/quickshell/modules/common/widgets/NotificationWidget.qml b/.config/quickshell/modules/common/widgets/NotificationWidget.qml index 3f4acd275..cb94c95cd 100644 --- a/.config/quickshell/modules/common/widgets/NotificationWidget.qml +++ b/.config/quickshell/modules/common/widgets/NotificationWidget.qml @@ -1,9 +1,11 @@ import "root:/modules/common" 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.Controls +import QtQuick.Effects import QtQuick.Layouts import Quickshell import Quickshell.Io @@ -126,7 +128,7 @@ Item { onPressAndHold: (mouse) => { if (mouse.button === Qt.LeftButton) { Hyprland.dispatch(`exec wl-copy '${StringUtils.shellSingleQuoteEscape(notificationObject.body)}'`) - notificationSummaryText.text = `${notificationObject.summary} (copied)` + notificationSummaryText.text = String.format(qsTr("{0} (copied)"), notificationObject.summary) } } onDragStartedChanged: () => { @@ -187,9 +189,19 @@ Item { height: notificationColumnLayout.implicitHeight 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 + 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 { enabled: enableAnimation 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 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 iconSize: 27 horizontalAlignment: Text.AlignHCenter @@ -422,17 +420,11 @@ Item { background: Rectangle { anchors.fill: parent 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 { - ColorAnimation { - duration: Appearance.animation.elementMoveFast.duration - easing.type: Appearance.animation.elementMoveFast.type - easing.bezierCurve: Appearance.animation.elementMoveFast.bezierCurve - } - + animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this) } - } contentItem: MaterialSymbol { diff --git a/.config/quickshell/modules/common/widgets/PrimaryTabBar.qml b/.config/quickshell/modules/common/widgets/PrimaryTabBar.qml index e0fbd6d90..de80d185b 100644 --- a/.config/quickshell/modules/common/widgets/PrimaryTabBar.qml +++ b/.config/quickshell/modules/common/widgets/PrimaryTabBar.qml @@ -52,38 +52,31 @@ ColumnLayout { root.enableIndicatorAnimation = true } } + 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 radius: Appearance.rounding.full - z: 2 - anchors.fill: parent - // TODO: make the end point in the moving direction go first - 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; + Behavior on x { + animation: Appearance.animation.elementMove.numberAnimation.createObject(this) } - anchors.rightMargin: { - const tabCount = root.tabButtonList.length - const targetWidth = tabBar.contentItem.children[0].children[tabBar.currentIndex].tabContentWidth - const fullTabSize = root.width / tabCount; - return fullTabSize * (tabCount - root.externalTrackedTab - 1) + (fullTabSize - targetWidth) / 2; + + Behavior on implicitWidth { + animation: Appearance.animation.elementMove.numberAnimation.createObject(this) } - 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 - } - } - } } diff --git a/.config/quickshell/modules/common/widgets/PrimaryTabButton.qml b/.config/quickshell/modules/common/widgets/PrimaryTabButton.qml index e5993d8e0..58424032c 100644 --- a/.config/quickshell/modules/common/widgets/PrimaryTabButton.qml +++ b/.config/quickshell/modules/common/widgets/PrimaryTabButton.qml @@ -1,5 +1,7 @@ import "root:/modules/common" import "root:/modules/common/widgets" +import "root:/modules/common/functions/color_utils.js" as ColorUtils +import Qt5Compat.GraphicalEffects import QtQuick import QtQuick.Controls import QtQuick.Layouts @@ -12,24 +14,108 @@ TabButton { property string buttonIcon property bool selected: false property int tabContentWidth: contentItem.children[0].implicitWidth + property int rippleDuration: 1200 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 { id: buttonBackground radius: Appearance.rounding.small 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 { - ColorAnimation { - duration: Appearance.animation.elementMove.duration - easing.type: Appearance.animation.elementMove.type - easing.bezierCurve: Appearance.animation.elementMove.bezierCurve + animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this) + } + + 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 { anchors.centerIn: buttonBackground ColumnLayout { @@ -44,11 +130,7 @@ TabButton { fill: selected ? 1 : 0 color: selected ? Appearance.m3colors.m3primary : Appearance.colors.colOnLayer1 Behavior on color { - ColorAnimation { - duration: Appearance.animation.elementMove.duration - easing.type: Appearance.animation.elementMove.type - easing.bezierCurve: Appearance.animation.elementMove.bezierCurve - } + animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this) } } StyledText { @@ -59,11 +141,7 @@ TabButton { color: selected ? Appearance.m3colors.m3primary : Appearance.colors.colOnLayer1 text: buttonText Behavior on color { - ColorAnimation { - duration: Appearance.animation.elementMove.duration - easing.type: Appearance.animation.elementMove.type - easing.bezierCurve: Appearance.animation.elementMove.bezierCurve - } + animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this) } } } diff --git a/.config/quickshell/modules/common/widgets/Revealer.qml b/.config/quickshell/modules/common/widgets/Revealer.qml index e3afa680b..327d4aaa1 100644 --- a/.config/quickshell/modules/common/widgets/Revealer.qml +++ b/.config/quickshell/modules/common/widgets/Revealer.qml @@ -16,18 +16,10 @@ Item { Behavior on implicitWidth { enabled: !vertical - NumberAnimation { - duration: Appearance.animation.elementMoveEnter.duration - easing.type: Appearance.animation.elementMoveEnter.type - easing.bezierCurve: Appearance.animation.elementMoveEnter.bezierCurve - } + animation: Appearance.animation.elementMoveEnter.numberAnimation.createObject(this) } Behavior on implicitHeight { enabled: vertical - NumberAnimation { - duration: Appearance.animation.elementMoveEnter.duration - easing.type: Appearance.animation.elementMoveEnter.type - easing.bezierCurve: Appearance.animation.elementMoveEnter.bezierCurve - } + animation: Appearance.animation.elementMoveEnter.numberAnimation.createObject(this) } } diff --git a/.config/quickshell/modules/common/widgets/SecondaryTabButton.qml b/.config/quickshell/modules/common/widgets/SecondaryTabButton.qml index 050cec6c3..6f87e719d 100644 --- a/.config/quickshell/modules/common/widgets/SecondaryTabButton.qml +++ b/.config/quickshell/modules/common/widgets/SecondaryTabButton.qml @@ -1,5 +1,7 @@ import "root:/modules/common" import "root:/modules/common/widgets" +import "root:/modules/common/functions/color_utils.js" as ColorUtils +import Qt5Compat.GraphicalEffects import QtQuick import QtQuick.Controls import QtQuick.Layouts @@ -11,25 +13,111 @@ TabButton { property string buttonText property string buttonIcon property bool selected: false + property int rippleDuration: 1200 height: buttonBackground.height property int tabContentWidth: buttonBackground.width - buttonBackground.radius*2 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 { id: buttonBackground radius: Appearance.rounding.small 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 { - ColorAnimation { - duration: Appearance.animation.elementMove.duration - easing.type: Appearance.animation.elementMove.type - easing.bezierCurve: Appearance.animation.elementMove.bezierCurve + animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this) + } + + 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 { anchors.centerIn: buttonBackground RowLayout { @@ -52,11 +140,7 @@ TabButton { fill: selected ? 1 : 0 color: selected ? Appearance.m3colors.m3primary : Appearance.colors.colOnLayer1 Behavior on color { - ColorAnimation { - duration: Appearance.animation.elementMove.duration - easing.type: Appearance.animation.elementMove.type - easing.bezierCurve: Appearance.animation.elementMove.bezierCurve - } + animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this) } } } @@ -67,11 +151,7 @@ TabButton { color: selected ? Appearance.m3colors.m3primary : Appearance.colors.colOnLayer1 text: buttonText Behavior on color { - ColorAnimation { - duration: Appearance.animation.elementMove.duration - easing.type: Appearance.animation.elementMove.type - easing.bezierCurve: Appearance.animation.elementMove.bezierCurve - } + animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this) } } } diff --git a/.config/quickshell/modules/common/widgets/StyledProgressBar.qml b/.config/quickshell/modules/common/widgets/StyledProgressBar.qml index 765f38d10..7173af88b 100644 --- a/.config/quickshell/modules/common/widgets/StyledProgressBar.qml +++ b/.config/quickshell/modules/common/widgets/StyledProgressBar.qml @@ -13,13 +13,11 @@ ProgressBar { property real valueBarWidth: 120 property real valueBarHeight: 4 property real valueBarGap: 4 + property color highlightColor: Appearance.m3colors.m3primary + property color trackColor: Appearance.m3colors.m3secondaryContainer Behavior on value { - NumberAnimation { - duration: Appearance.animation.elementMoveEnter.duration - easing.type: Appearance.animation.elementMoveEnter.type - easing.bezierCurve: Appearance.animation.elementMoveEnter.bezierCurve - } + animation: Appearance.animation.elementMoveEnter.numberAnimation.createObject(this) } background: Rectangle { @@ -38,21 +36,21 @@ ProgressBar { width: root.visualPosition * parent.width height: parent.height radius: Appearance.rounding.full - color: Appearance.m3colors.m3primary + color: root.highlightColor } Rectangle { // Right remaining part fill anchors.right: parent.right width: (1 - root.visualPosition) * parent.width - valueBarGap height: parent.height radius: Appearance.rounding.full - color: Appearance.m3colors.m3secondaryContainer + color: root.trackColor } Rectangle { // Stop point anchors.right: parent.right width: valueBarGap height: valueBarGap radius: Appearance.rounding.full - color: Appearance.m3colors.m3primary + color: root.highlightColor } } } \ No newline at end of file diff --git a/.config/quickshell/modules/common/widgets/StyledSlider.qml b/.config/quickshell/modules/common/widgets/StyledSlider.qml index b39a9a419..23a6179f4 100644 --- a/.config/quickshell/modules/common/widgets/StyledSlider.qml +++ b/.config/quickshell/modules/common/widgets/StyledSlider.qml @@ -16,8 +16,13 @@ Slider { property real handleWidth: (slider.pressed ? 3 : 5) * scale property real handleHeight: 44 * 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 string tooltipContent: `${Math.round(value * 100)}%` Layout.fillWidth: true from: 0 to: 1 @@ -44,14 +49,15 @@ Slider { background: Item { 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 Rectangle { + anchors.verticalCenter: parent.verticalCenter anchors.left: parent.left width: slider.handleLimit + slider.visualPosition * slider.limitedHandleRangeWidth - (slider.handleMargins + slider.handleWidth / 2) - height: parent.height - color: Appearance.m3colors.m3primary + height: trackHeight + color: slider.highlightColor topLeftRadius: Appearance.rounding.full bottomLeftRadius: Appearance.rounding.full topRightRadius: Appearance.rounding.unsharpen @@ -60,10 +66,11 @@ Slider { // Fill right Rectangle { + anchors.verticalCenter: parent.verticalCenter anchors.right: parent.right width: slider.handleLimit + (1 - slider.visualPosition) * slider.limitedHandleRangeWidth - (slider.handleMargins + slider.handleWidth / 2) - height: parent.height - color: Appearance.m3colors.m3secondaryContainer + height: trackHeight + color: slider.trackColor topLeftRadius: Appearance.rounding.unsharpen bottomLeftRadius: Appearance.rounding.unsharpen topRightRadius: Appearance.rounding.full @@ -78,7 +85,7 @@ Slider { width: slider.backgroundDotSize height: slider.backgroundDotSize radius: Appearance.rounding.full - color: Appearance.m3colors.m3onSecondaryContainer + color: slider.handleColor } } @@ -89,7 +96,7 @@ Slider { implicitWidth: slider.handleWidth implicitHeight: slider.handleHeight radius: Appearance.rounding.full - color: Appearance.m3colors.m3onSecondaryContainer + color: slider.handleColor Behavior on implicitWidth { NumberAnimation { @@ -101,7 +108,7 @@ Slider { StyledToolTip { extraVisibleCondition: slider.pressed - content: `${Math.round(slider.value * 100)}%` + content: slider.tooltipContent } } } \ No newline at end of file diff --git a/.config/quickshell/modules/common/widgets/StyledSwitch.qml b/.config/quickshell/modules/common/widgets/StyledSwitch.qml index 8406a47d1..e12583b90 100644 --- a/.config/quickshell/modules/common/widgets/StyledSwitch.qml +++ b/.config/quickshell/modules/common/widgets/StyledSwitch.qml @@ -22,18 +22,10 @@ Switch { border.color: root.checked ? Appearance.m3colors.m3primary : Appearance.m3colors.m3outline Behavior on color { - ColorAnimation { - duration: Appearance.animation.elementMove.duration - easing.type: Appearance.animation.elementMove.type - easing.bezierCurve: Appearance.animation.elementMove.bezierCurve - } + animation: Appearance.animation.elementMove.colorAnimation.createObject(this) } Behavior on border.color { - ColorAnimation { - duration: Appearance.animation.elementMove.duration - easing.type: Appearance.animation.elementMove.type - easing.bezierCurve: Appearance.animation.elementMove.bezierCurve - } + animation: Appearance.animation.elementMove.colorAnimation.createObject(this) } } @@ -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) Behavior on anchors.leftMargin { - NumberAnimation { - duration: Appearance.animation.elementMove.duration - easing.type: Appearance.animation.elementMove.type - easing.bezierCurve: Appearance.animation.elementMove.bezierCurve - } + animation: Appearance.animation.elementMove.numberAnimation.createObject(this) } Behavior on width { - NumberAnimation { - duration: Appearance.animation.elementMove.duration - easing.type: Appearance.animation.elementMove.type - easing.bezierCurve: Appearance.animation.elementMove.bezierCurve - } + animation: Appearance.animation.elementMove.numberAnimation.createObject(this) } Behavior on height { - NumberAnimation { - duration: Appearance.animation.elementMove.duration - easing.type: Appearance.animation.elementMove.type - easing.bezierCurve: Appearance.animation.elementMove.bezierCurve - } + animation: Appearance.animation.elementMove.numberAnimation.createObject(this) } Behavior on color { - ColorAnimation { - duration: Appearance.animation.elementMove.duration - easing.type: Appearance.animation.elementMove.type - easing.bezierCurve: Appearance.animation.elementMove.bezierCurve - } + animation: Appearance.animation.elementMove.colorAnimation.createObject(this) } } } diff --git a/.config/quickshell/modules/common/widgets/StyledToolTip.qml b/.config/quickshell/modules/common/widgets/StyledToolTip.qml index 838129351..627dfb965 100644 --- a/.config/quickshell/modules/common/widgets/StyledToolTip.qml +++ b/.config/quickshell/modules/common/widgets/StyledToolTip.qml @@ -26,7 +26,7 @@ ToolTip { } } - background: Item {} + background: null contentItem: Item { id: contentItemBackground diff --git a/.config/quickshell/modules/mediaControls/MediaControls.qml b/.config/quickshell/modules/mediaControls/MediaControls.qml new file mode 100644 index 000000000..345bd3bb0 --- /dev/null +++ b/.config/quickshell/modules/mediaControls/MediaControls.qml @@ -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; + } + } + +} \ No newline at end of file diff --git a/.config/quickshell/modules/mediaControls/PlayerControl.qml b/.config/quickshell/modules/mediaControls/PlayerControl.qml new file mode 100644 index 000000000..7217e0b9f --- /dev/null +++ b/.config/quickshell/modules/mediaControls/PlayerControl.qml @@ -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) + } + } + } + } + } + } + } +} \ No newline at end of file diff --git a/.config/quickshell/modules/notificationPopup/NotificationPopup.qml b/.config/quickshell/modules/notificationPopup/NotificationPopup.qml index 8d5b0549c..f4373f14d 100644 --- a/.config/quickshell/modules/notificationPopup/NotificationPopup.qml +++ b/.config/quickshell/modules/notificationPopup/NotificationPopup.qml @@ -7,111 +7,105 @@ import QtQuick.Controls import QtQuick.Layouts import Quickshell import Quickshell.Wayland +import Quickshell.Hyprland Scope { - id: screenCorners - readonly property Toplevel activeWindow: ToplevelManager.activeToplevel + id: notificationPopup - Variants { - model: Quickshell.screens + LazyLoader { + 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 var modelData - loading: true - PanelWindow { - id: root - visible: (columnLayout.children.length > 0 || notificationWidgetList.length > 0) + property Component notifComponent: NotificationWidget {} + property list notificationWidgetList: [] - property Component notifComponent: NotificationWidget {} - property list notificationWidgetList: [] - - 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 - } + 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.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 + } + + } } } diff --git a/.config/quickshell/modules/onScreenDisplay/OnScreenDisplayBrightness.qml b/.config/quickshell/modules/onScreenDisplay/OnScreenDisplayBrightness.qml index 4e3bca7c1..0bcc3615b 100644 --- a/.config/quickshell/modules/onScreenDisplay/OnScreenDisplayBrightness.qml +++ b/.config/quickshell/modules/onScreenDisplay/OnScreenDisplayBrightness.qml @@ -12,6 +12,8 @@ import Quickshell.Wayland Scope { id: root property bool showOsdValues: false + property var focusedScreen: Quickshell.screens.find(s => s.name === Hyprland.focusedMonitor?.name) + property var brightnessMonitor: Brightness.getMonitorForScreen(focusedScreen) function triggerOsd() { showOsdValues = true @@ -36,81 +38,79 @@ Scope { } } - Variants { - model: Quickshell.screens + Connections { + target: Brightness + function onBrightnessChanged() { + if (!root.brightnessMonitor.ready) return + root.triggerOsd() + } + } - Loader { - id: osdLoader - property var modelData - active: showOsdValues - property var brightnessMonitor: Brightness.getMonitorForScreen(modelData) + Loader { + id: osdLoader + active: showOsdValues + + PanelWindow { + id: osdRoot Connections { - target: brightnessMonitor - function onBrightnessChanged() { - if (!brightnessMonitor.ready) return - root.triggerOsd() + target: root + function onFocusedScreenChanged() { + osdRoot.screen = root.focusedScreen } } - PanelWindow { - screen: modelData - exclusionMode: ExclusionMode.Normal - WlrLayershell.namespace: "quickshell:onScreenDisplay" - 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") - } - } - } + exclusionMode: ExclusionMode.Normal + WlrLayershell.namespace: "quickshell:onScreenDisplay" + WlrLayershell.layer: WlrLayer.Overlay + color: "transparent" + 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 { @@ -131,7 +131,7 @@ Scope { GlobalShortcut { name: "osdBrightnessTrigger" - description: "Triggers brightness OSD on press" + description: qsTr("Triggers brightness OSD on press") onPressed: { root.triggerOsd() @@ -139,7 +139,7 @@ Scope { } GlobalShortcut { name: "osdBrightnessHide" - description: "Hides brightness OSD on press" + description: qsTr("Hides brightness OSD on press") onPressed: { root.showOsdValues = false diff --git a/.config/quickshell/modules/onScreenDisplay/OnScreenDisplayVolume.qml b/.config/quickshell/modules/onScreenDisplay/OnScreenDisplayVolume.qml index 6bd83fa86..9a6c43272 100644 --- a/.config/quickshell/modules/onScreenDisplay/OnScreenDisplayVolume.qml +++ b/.config/quickshell/modules/onScreenDisplay/OnScreenDisplayVolume.qml @@ -12,6 +12,7 @@ import Quickshell.Hyprland Scope { id: root property bool showOsdValues: false + property var focusedScreen: Quickshell.screens.find(s => s.name === Hyprland.focusedMonitor?.name) function triggerOsd() { showOsdValues = true @@ -47,70 +48,68 @@ Scope { } } - Variants { - model: Quickshell.screens + Loader { + id: osdLoader + active: showOsdValues - Loader { - id: osdLoader - property var modelData - active: showOsdValues - PanelWindow { - screen: modelData - exclusionMode: ExclusionMode.Normal - WlrLayershell.namespace: "quickshell:onScreenDisplay" - WlrLayershell.layer: WlrLayer.Overlay - color: "transparent" + PanelWindow { + id: osdRoot - anchors { - top: true - } - mask: Region { - item: osdValuesWrapper + Connections { + target: root + function onFocusedScreenChanged() { + osdRoot.screen = root.focusedScreen } + } - implicitWidth: columnLayout.implicitWidth - implicitHeight: columnLayout.implicitHeight - visible: osdLoader.active + exclusionMode: ExclusionMode.Normal + WlrLayershell.namespace: "quickshell:onScreenDisplay" + WlrLayershell.layer: WlrLayer.Overlay + color: "transparent" - ColumnLayout { - id: columnLayout - anchors.horizontalCenter: parent.horizontalCenter - Item { - height: 1 // Prevent Wayland protocol error + 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 } - 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: Audio.sink?.audio.volume ?? 0 - icon: Audio.sink?.audio.muted ? "volume_off" : "volume_up" - name: qsTr("Volume") + 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: Audio.sink?.audio.volume ?? 0 + icon: Audio.sink?.audio.muted ? "volume_off" : "volume_up" + name: qsTr("Volume") + } + } } } - } IpcHandler { @@ -130,7 +129,7 @@ Scope { } GlobalShortcut { name: "osdVolumeTrigger" - description: "Triggers volume OSD on press" + description: qsTr("Triggers volume OSD on press") onPressed: { root.triggerOsd() @@ -138,7 +137,7 @@ Scope { } GlobalShortcut { name: "osdVolumeHide" - description: "Hides volume OSD on press" + description: qsTr("Hides volume OSD on press") onPressed: { root.showOsdValues = false diff --git a/.config/quickshell/modules/onScreenDisplay/OsdValueIndicator.qml b/.config/quickshell/modules/onScreenDisplay/OsdValueIndicator.qml index f13cd7fc3..58d4ac5ca 100644 --- a/.config/quickshell/modules/onScreenDisplay/OsdValueIndicator.qml +++ b/.config/quickshell/modules/onScreenDisplay/OsdValueIndicator.qml @@ -3,10 +3,11 @@ import "root:/modules/common" import "root:/modules/common/widgets" import QtQuick import QtQuick.Controls +import QtQuick.Effects import QtQuick.Layouts import Quickshell import Quickshell.Widgets -import Qt5Compat.GraphicalEffects +// import Qt5Compat.GraphicalEffects Item { id: root @@ -14,27 +15,40 @@ Item { required property string icon required property string name property bool rotateIcon: false + property bool scaleIcon: false - property real valueIndicatorVerticalPadding: 5 + property real valueIndicatorVerticalPadding: 9 property real valueIndicatorLeftPadding: 10 property real valueIndicatorRightPadding: 20 // An icon is circle ish, a column isn't, hence the extra padding Layout.margins: Appearance.sizes.elevationMargin - implicitWidth: valueIndicator.implicitWidth + implicitWidth: Appearance.sizes.osdWidth implicitHeight: valueIndicator.implicitHeight WrapperRectangle { id: valueIndicator + anchors.fill: parent radius: Appearance.rounding.full color: Appearance.colors.colLayer0 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 id: valueRow Layout.margins: 10 + anchors.fill: parent spacing: 10 - Item { + Item { implicitWidth: 30 implicitHeight: 30 Layout.alignment: Qt.AlignVCenter @@ -47,22 +61,14 @@ Item { renderType: Text.QtRendering text: root.icon - iconSize: 20 + 10 * (root.rotateIcon ? value : 1) + iconSize: 20 + 10 * (root.scaleIcon ? value : 1) rotation: 180 * (root.rotateIcon ? value : 0) Behavior on iconSize { - NumberAnimation { - duration: Appearance.animation.elementMoveEnter.duration - easing.type: Appearance.animation.elementMoveEnter.type - easing.bezierCurve: Appearance.animation.elementMoveEnter.bezierCurve - } + animation: Appearance.animation.elementMoveEnter.numberAnimation.createObject(this) } Behavior on rotation { - NumberAnimation { - duration: Appearance.animation.elementMoveEnter.duration - easing.type: Appearance.animation.elementMoveEnter.type - easing.bezierCurve: Appearance.animation.elementMoveEnter.bezierCurve - } + animation: Appearance.animation.elementMoveEnter.numberAnimation.createObject(this) } } @@ -93,20 +99,10 @@ Item { StyledProgressBar { id: valueProgressBar + Layout.fillWidth: true 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 - } } \ No newline at end of file diff --git a/.config/quickshell/modules/overview/Overview.qml b/.config/quickshell/modules/overview/Overview.qml index 9aa5da314..0b267699c 100644 --- a/.config/quickshell/modules/overview/Overview.qml +++ b/.config/quickshell/modules/overview/Overview.qml @@ -128,7 +128,7 @@ Scope { GlobalShortcut { name: "overviewToggle" - description: "Toggles overview on press" + description: qsTr("Toggles overview on press") onPressed: { GlobalStates.overviewOpen = !GlobalStates.overviewOpen @@ -136,7 +136,7 @@ Scope { } GlobalShortcut { name: "overviewClose" - description: "Closes overview" + description: qsTr("Closes overview") onPressed: { GlobalStates.overviewOpen = false @@ -144,7 +144,7 @@ Scope { } GlobalShortcut { name: "overviewToggleRelease" - description: "Toggles overview on release" + description: qsTr("Toggles overview on release") onPressed: { GlobalStates.superReleaseMightTrigger = true @@ -160,9 +160,9 @@ Scope { } GlobalShortcut { name: "overviewToggleReleaseInterrupt" - description: "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. " + - "To make sure this works consistently, use binditn = MODKEYS, catchall in an automatically triggered submap that includes everything." + description: qsTr("Interrupts possibility of overview being toggled on release. ") + + qsTr("This is necessary because GlobalShortcut.onReleased in quickshell triggers whether or not you press something else while holding the key. ") + + qsTr("To make sure this works consistently, use binditn = MODKEYS, catchall in an automatically triggered submap that includes everything.") onPressed: { GlobalStates.superReleaseMightTrigger = false diff --git a/.config/quickshell/modules/overview/OverviewWidget.qml b/.config/quickshell/modules/overview/OverviewWidget.qml index 0b00fcb20..f4498408e 100644 --- a/.config/quickshell/modules/overview/OverviewWidget.qml +++ b/.config/quickshell/modules/overview/OverviewWidget.qml @@ -2,8 +2,9 @@ import "root:/" import "root:/services/" import "root:/modules/common" import "root:/modules/common/widgets" -import Qt5Compat.GraphicalEffects +import "root:/modules/common/functions/color_utils.js" as ColorUtils import QtQuick +import QtQuick.Effects import QtQuick.Layouts import Quickshell import Quickshell.Io @@ -58,6 +59,16 @@ Item { color: Appearance.colors.colLayer0 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 { id: workspaceColumnLayout @@ -78,7 +89,7 @@ Item { property int colIndex: index 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 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 activeBorderColor: Appearance.m3colors.m3secondary 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 - } } diff --git a/.config/quickshell/modules/overview/OverviewWindow.qml b/.config/quickshell/modules/overview/OverviewWindow.qml index d6526904e..67faa9628 100644 --- a/.config/quickshell/modules/overview/OverviewWindow.qml +++ b/.config/quickshell/modules/overview/OverviewWindow.qml @@ -2,6 +2,7 @@ import "root:/services/" import "root:/modules/common" import "root:/modules/common/widgets" import "root:/modules/common/functions/icons.js" as Icons +import "root:/modules/common/functions/color_utils.js" as ColorUtils import Qt5Compat.GraphicalEffects import QtQuick import QtQuick.Layouts @@ -42,37 +43,21 @@ Rectangle { // Window radius: Appearance.rounding.windowRounding * root.scale 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.width : 1 Behavior on x { - NumberAnimation { - duration: Appearance.animation.elementMoveEnter.duration - easing.type: Appearance.animation.elementMoveEnter.type - easing.bezierCurve: Appearance.animation.elementMoveEnter.bezierCurve - } + animation: Appearance.animation.elementMoveEnter.numberAnimation.createObject(this) } Behavior on y { - NumberAnimation { - duration: Appearance.animation.elementMoveEnter.duration - easing.type: Appearance.animation.elementMoveEnter.type - easing.bezierCurve: Appearance.animation.elementMoveEnter.bezierCurve - } + animation: Appearance.animation.elementMoveEnter.numberAnimation.createObject(this) } Behavior on width { - NumberAnimation { - duration: Appearance.animation.elementMoveEnter.duration - easing.type: Appearance.animation.elementMoveEnter.type - easing.bezierCurve: Appearance.animation.elementMoveEnter.bezierCurve - } + animation: Appearance.animation.elementMoveEnter.numberAnimation.createObject(this) } Behavior on height { - NumberAnimation { - duration: Appearance.animation.elementMoveEnter.duration - easing.type: Appearance.animation.elementMoveEnter.type - easing.bezierCurve: Appearance.animation.elementMoveEnter.bezierCurve - } + animation: Appearance.animation.elementMoveEnter.numberAnimation.createObject(this) } ColumnLayout { @@ -88,11 +73,7 @@ Rectangle { // Window implicitSize: Math.min(targetWindowWidth, targetWindowHeight) * (root.compactMode ? root.iconToWindowRatioCompact : root.iconToWindowRatio) Behavior on implicitSize { - NumberAnimation { - duration: Appearance.animation.elementMoveEnter.duration - easing.type: Appearance.animation.elementMoveEnter.type - easing.bezierCurve: Appearance.animation.elementMoveEnter.bezierCurve - } + animation: Appearance.animation.elementMoveEnter.numberAnimation.createObject(this) } IconImage { diff --git a/.config/quickshell/modules/overview/SearchItem.qml b/.config/quickshell/modules/overview/SearchItem.qml index ab6aaf8c5..7643af80c 100644 --- a/.config/quickshell/modules/overview/SearchItem.qml +++ b/.config/quickshell/modules/overview/SearchItem.qml @@ -2,6 +2,7 @@ import "root:/" import "root:/modules/common" import "root:/modules/common/widgets" +import "root:/modules/common/functions/color_utils.js" as ColorUtils import QtQuick import QtQuick.Controls import QtQuick.Layouts @@ -57,7 +58,9 @@ Button { anchors.leftMargin: root.horizontalMargin anchors.rightMargin: root.horizontalMargin 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 { @@ -100,7 +103,7 @@ Button { StyledText { font.pixelSize: Appearance.font.pixelSize.small color: Appearance.colors.colSubtext - visible: root.itemType && root.itemType != "App" + visible: root.itemType && root.itemType != qsTr("App") text: root.itemType } StyledText { diff --git a/.config/quickshell/modules/overview/SearchWidget.qml b/.config/quickshell/modules/overview/SearchWidget.qml index 3be7b915f..4320a96a1 100644 --- a/.config/quickshell/modules/overview/SearchWidget.qml +++ b/.config/quickshell/modules/overview/SearchWidget.qml @@ -7,6 +7,7 @@ import Qt5Compat.GraphicalEffects import Qt.labs.platform import QtQuick import QtQuick.Controls +import QtQuick.Effects import QtQuick.Layouts import Quickshell import Quickshell.Io @@ -160,6 +161,15 @@ Item { // Wrapper implicitHeight: columnLayout.implicitHeight radius: Appearance.rounding.large 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 { id: columnLayout @@ -227,7 +237,7 @@ Item { // Wrapper } } - background: Item {} + background: null cursorDelegate: Rectangle { width: 1 @@ -276,7 +286,7 @@ Item { // Wrapper nonAppResultsTimer.restart(); const mathResultObject = { name: root.mathResult, - clickActionName: "Copy", + clickActionName: qsTr("Copy"), type: qsTr("Math result"), fontType: "monospace", materialSymbol: 'calculate', @@ -286,7 +296,7 @@ Item { // Wrapper } const commandResultObject = { name: searchingText, - clickActionName: "Run", + clickActionName: qsTr("Run"), type: qsTr("Run command"), fontType: "monospace", materialSymbol: 'terminal', @@ -300,8 +310,8 @@ Item { // Wrapper if (actionString.startsWith(root.searchingText) || root.searchingText.startsWith(actionString)) { return { name: root.searchingText.startsWith(actionString) ? root.searchingText : actionString, - clickActionName: "Run", - type: "Action", + clickActionName: qsTr("Run"), + type: qsTr("Action"), materialSymbol: 'settings_suggest', execute: () => { action.execute(root.searchingText.split(" ").slice(1).join(" ")) @@ -319,8 +329,8 @@ Item { // Wrapper result = result.concat( AppSearch.fuzzyQuery(root.searchingText) .map((entry) => { - entry.clickActionName = "Launch"; - entry.type = "App" + entry.clickActionName = qsTr("Launch"); + entry.type = qsTr("App"); return entry; }) ); @@ -344,8 +354,8 @@ Item { // Wrapper // Web search result.push({ name: root.searchingText, - clickActionName: "Search", - type: "Search the web", + clickActionName: qsTr("Search"), + type: qsTr("Search the web"), materialSymbol: 'travel_explore', execute: () => { let url = ConfigOptions.search.engineBaseUrl + root.searchingText @@ -361,22 +371,9 @@ Item { // Wrapper } delegate: SearchItem { 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 - } } \ No newline at end of file diff --git a/.config/quickshell/modules/session/Session.qml b/.config/quickshell/modules/session/Session.qml index 00c1d4a26..190666bd0 100644 --- a/.config/quickshell/modules/session/Session.qml +++ b/.config/quickshell/modules/session/Session.qml @@ -1,201 +1,193 @@ import "root:/modules/common" import "root:/modules/common/widgets" +import "root:/modules/common/functions/color_utils.js" as ColorUtils import QtQuick import QtQuick.Controls import QtQuick.Layouts import Quickshell -import Quickshell.Hyprland import Quickshell.Io -import Quickshell.Wayland import Quickshell.Widgets +import Quickshell.Wayland +import Quickshell.Hyprland Scope { id: root - readonly property Toplevel activeWindow: ToplevelManager.activeToplevel + property var focusedScreen: Quickshell.screens.find(s => s.name === Hyprland.focusedMonitor?.name) - Variants { - id: sessionVariants - model: Quickshell.screens - - 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 - } - } - } + Loader { + id: sessionLoader + active: false + 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" function toggle(): void { - for (let i = 0; i < sessionVariants.instances.length; i++) { - let loader = sessionVariants.instances[i]; - loader.active = !loader.active; - } + sessionLoader.active = !sessionLoader.active; } function close(): void { - for (let i = 0; i < sessionVariants.instances.length; i++) { - let loader = sessionVariants.instances[i]; - loader.active = false; - } + sessionLoader.active = false; } function open(): void { - for (let i = 0; i < sessionVariants.instances.length; i++) { - let loader = sessionVariants.instances[i]; - loader.active = true; - } + sessionLoader.active = true; } } GlobalShortcut { name: "sessionToggle" - description: "Toggles session screen on press" + description: qsTr("Toggles session screen on press") onPressed: { - for (let i = 0; i < sessionVariants.instances.length; i++) { - let loader = sessionVariants.instances[i]; - loader.active = !loader.active; - } + sessionLoader.active = !sessionLoader.active; } } GlobalShortcut { name: "sessionOpen" - description: "Opens session screen on press" + description: qsTr("Opens session screen on press") onPressed: { - for (let i = 0; i < sessionVariants.instances.length; i++) { - let loader = sessionVariants.instances[i]; - loader.active = !loader.active; - } + sessionLoader.active = true; } } diff --git a/.config/quickshell/modules/session/SessionActionButton.qml b/.config/quickshell/modules/session/SessionActionButton.qml index b55c096b0..1d32018a0 100644 --- a/.config/quickshell/modules/session/SessionActionButton.qml +++ b/.config/quickshell/modules/session/SessionActionButton.qml @@ -37,12 +37,7 @@ Button { color: (button.down || button.keyboardDown) ? Appearance.colors.colLayer2Active : ((button.hovered || button.focus) ? Appearance.colors.colLayer2Hover : Appearance.colors.colLayer2) Behavior on color { - ColorAnimation { - duration: Appearance.animation.elementMove.duration - easing.type: Appearance.animation.elementMove.type - easing.bezierCurve: Appearance.animation.elementMove.bezierCurve - } - + animation: Appearance.animation.elementMove.colorAnimation.createObject(this) } } diff --git a/.config/quickshell/modules/sidebarLeft/AiChat.qml b/.config/quickshell/modules/sidebarLeft/AiChat.qml index dba4fefa3..37fa876ed 100644 --- a/.config/quickshell/modules/sidebarLeft/AiChat.qml +++ b/.config/quickshell/modules/sidebarLeft/AiChat.qml @@ -18,7 +18,6 @@ Item { id: root property var panelWindow property var inputField: messageInputField - readonly property var messages: Ai.messages property string commandPrefix: "/" property string faviconDownloadPath: FileUtils.trimFileProtocol(`${XdgDirectories.cache}/media/favicons`) @@ -31,7 +30,7 @@ Item { onFocusChanged: (focus) => { if (focus) { - messageInputField.forceActiveFocus() + root.inputField.forceActiveFocus() } } @@ -179,46 +178,42 @@ int main(int argc, char* argv[]) { } add: Transition { - NumberAnimation { - property: "opacity" - from: 0; to: 1 - duration: Appearance.animation.elementMoveEnter.duration - easing.type: Appearance.animation.elementMoveEnter.type - easing.bezierCurve: Appearance.animation.elementMoveEnter.bezierCurve - } + animations: [Appearance.animation.elementMoveEnter.numberAnimation.createObject(this, { + property: "opacity", + from: 0, + to: 1 + })] } remove: Transition { - NumberAnimation { - property: "opacity" - from: 1; to: 0 - duration: Appearance.animation.elementMoveEnter.duration - easing.type: Appearance.animation.elementMoveEnter.type - easing.bezierCurve: Appearance.animation.elementMoveEnter.bezierCurve - } + animations: [Appearance.animation.elementMoveEnter.numberAnimation.createObject(this, { + property: "opacity", + from: 1, + to: 0 + })] } model: ScriptModel { - values: root.messages + values: Ai.messageIDs } delegate: AiMessage { + required property var modelData + required property int index messageIndex: index - messageData: modelData + messageData: { + Ai.messageByID[modelData] + } messageInputField: root.inputField faviconDownloadPath: root.faviconDownloadPath } } Item { // Placeholder when list is empty - opacity: root.messages.length === 0 ? 1 : 0 + opacity: Ai.messageIDs.length === 0 ? 1 : 0 visible: opacity > 0 anchors.fill: parent Behavior on opacity { - NumberAnimation { - duration: Appearance.animation.elementMove.duration - easing.type: Appearance.animation.elementMove.type - easing.bezierCurve: Appearance.animation.elementMove.bezierCurve - } + animation: Appearance.animation.elementMoveEnter.numberAnimation.createObject(this) } ColumnLayout { @@ -356,11 +351,7 @@ int main(int argc, char* argv[]) { border.width: 1 Behavior on implicitHeight { - NumberAnimation { - duration: Appearance.animation.elementMove.duration - easing.type: Appearance.animation.elementMove.type - easing.bezierCurve: Appearance.animation.elementMove.bezierCurve - } + animation: Appearance.animation.elementMove.numberAnimation.createObject(this) } 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) placeholderTextColor: Appearance.m3colors.m3outline - background: Item {} + background: null onTextChanged: { // Handle suggestions if(messageInputField.text.length === 0) { @@ -474,11 +465,7 @@ int main(int argc, char* argv[]) { Appearance.m3colors.m3primary) : Appearance.colors.colLayer2Disabled Behavior on color { - ColorAnimation { - duration: Appearance.animation.elementMove.duration - easing.type: Appearance.animation.elementMove.type - easing.bezierCurve: Appearance.animation.elementMove.bezierCurve - } + animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this) } } @@ -566,11 +553,7 @@ int main(int argc, char* argv[]) { Appearance.colors.colLayer2 Behavior on color { - ColorAnimation { - duration: Appearance.animation.elementMove.duration - easing.type: Appearance.animation.elementMove.type - easing.bezierCurve: Appearance.animation.elementMove.bezierCurve - } + animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this) } } onClicked: { diff --git a/.config/quickshell/modules/sidebarLeft/Anime.qml b/.config/quickshell/modules/sidebarLeft/Anime.qml index 29b571b43..fb1182f80 100644 --- a/.config/quickshell/modules/sidebarLeft/Anime.qml +++ b/.config/quickshell/modules/sidebarLeft/Anime.qml @@ -4,6 +4,7 @@ import "root:/modules/common" import "root:/modules/common/widgets" import "root:/modules/common/functions/fuzzysort.js" as Fuzzy import "root:/modules/common/functions/string_utils.js" as StringUtils +import "root:/modules/common/functions/file_utils.js" as FileUtils import "./anime/" import Qt.labs.platform import QtQuick @@ -19,9 +20,9 @@ Item { property var panelWindow property var inputField: tagInputField readonly property var responses: Booru.responses - property string previewDownloadPath: `${XdgDirectories.cache}/media/waifus`.replace("file://", "") - property string downloadPath: (XdgDirectories.pictures + "/homework").replace("file://", "") - property string nsfwPath: (XdgDirectories.pictures + "/homework/🌶️").replace("file://", "") + property string previewDownloadPath: FileUtils.trimFileProtocol(`${XdgDirectories.cache}/media/waifus`) + property string downloadPath: FileUtils.trimFileProtocol(XdgDirectories.pictures + "/homework") + property string nsfwPath: FileUtils.trimFileProtocol(XdgDirectories.pictures + "/homework/🌶️") property string commandPrefix: "/" property real scrollOnNewResponse: 100 property int tagSuggestionDelay: 210 @@ -37,7 +38,8 @@ Item { } 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: [ @@ -114,12 +116,6 @@ Item { } } - Connections { - target: panelWindow - function onVisibleChanged(visible) { - tagInputField.forceActiveFocus() - } - } onFocusChanged: (focus) => { if (focus) { tagInputField.forceActiveFocus() @@ -174,13 +170,11 @@ Item { } add: Transition { - NumberAnimation { - property: "opacity" - from: 0; to: 1 - duration: Appearance.animation.elementMoveEnter.duration - easing.type: Appearance.animation.elementMoveEnter.type - easing.bezierCurve: Appearance.animation.elementMoveEnter.bezierCurve - } + animations: [Appearance.animation.elementMoveEnter.numberAnimation.createObject(this, { + property: "opacity", + from: 0, + to: 1 + })] } model: ScriptModel { @@ -208,11 +202,7 @@ Item { anchors.fill: parent Behavior on opacity { - NumberAnimation { - duration: Appearance.animation.elementMove.duration - easing.type: Appearance.animation.elementMove.type - easing.bezierCurve: Appearance.animation.elementMove.bezierCurve - } + animation: Appearance.animation.elementMoveEnter.numberAnimation.createObject(this) } ColumnLayout { @@ -246,11 +236,7 @@ Item { visible: opacity > 0 Behavior on opacity { - NumberAnimation { - duration: Appearance.animation.elementMove.duration - easing.type: Appearance.animation.elementMove.type - easing.bezierCurve: Appearance.animation.elementMove.bezierCurve - } + animation: Appearance.animation.elementMoveEnter.numberAnimation.createObject(this) } Rectangle { @@ -392,11 +378,7 @@ Item { border.width: 1 Behavior on implicitHeight { - NumberAnimation { - duration: Appearance.animation.elementMove.duration - easing.type: Appearance.animation.elementMove.type - easing.bezierCurve: Appearance.animation.elementMove.bezierCurve - } + animation: Appearance.animation.elementMove.numberAnimation.createObject(this) } RowLayout { // Input field and send button @@ -419,7 +401,7 @@ Item { placeholderText: StringUtils.format(qsTr('Enter tags, or "{0}" for commands'), root.commandPrefix) placeholderTextColor: Appearance.m3colors.m3outline - background: Item {} + background: null property Timer searchTimer: Timer { // Timer for tag suggestions interval: root.tagSuggestionDelay @@ -530,11 +512,7 @@ Item { Appearance.m3colors.m3primary) : Appearance.colors.colLayer2Disabled Behavior on color { - ColorAnimation { - duration: Appearance.animation.elementMove.duration - easing.type: Appearance.animation.elementMove.type - easing.bezierCurve: Appearance.animation.elementMove.bezierCurve - } + animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this) } } @@ -666,11 +644,7 @@ Item { Appearance.colors.colLayer2 Behavior on color { - ColorAnimation { - duration: Appearance.animation.elementMove.duration - easing.type: Appearance.animation.elementMove.type - easing.bezierCurve: Appearance.animation.elementMove.bezierCurve - } + animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this) } } onClicked: { diff --git a/.config/quickshell/modules/sidebarLeft/SidebarLeft.qml b/.config/quickshell/modules/sidebarLeft/SidebarLeft.qml index b6eb13bea..a17e7f582 100644 --- a/.config/quickshell/modules/sidebarLeft/SidebarLeft.qml +++ b/.config/quickshell/modules/sidebarLeft/SidebarLeft.qml @@ -5,6 +5,7 @@ import "root:/modules/common/widgets" import QtQuick import QtQuick.Controls import QtQuick.Layouts +import QtQuick.Effects import Qt5Compat.GraphicalEffects import Quickshell.Io import Quickshell @@ -17,30 +18,31 @@ Scope { // Scope property int sidebarPadding: 15 property var tabButtonList: [{"icon": "neurology", "name": qsTr("Intelligence")}, {"icon": "bookmark_heart", "name": qsTr("Anime")}] - Variants { // Window repeater - id: sidebarVariants - model: Quickshell.screens - + Loader { + id: sidebarLoader + active: false + onActiveChanged: { + GlobalStates.sidebarLeftOpen = sidebarLoader.active + } + PanelWindow { // Window id: sidebarRoot - visible: false - focusable: true - property int selectedTab: PersistentStates.sidebar.leftSide.selectedTab + visible: sidebarLoader.active + + property int selectedTab: 0 property bool extend: false + property bool pin: false property real sidebarWidth: sidebarRoot.extend ? Appearance.sizes.sidebarWidthExtended : Appearance.sizes.sidebarWidth - onVisibleChanged: { - GlobalStates.sidebarLeftOpenCount += visible ? 1 : -1 + function hide() { + sidebarLoader.active = false } - property var modelData - - screen: modelData - exclusiveZone: 0 + exclusiveZone: sidebarRoot.pin ? sidebarWidth : 0 implicitWidth: Appearance.sizes.sidebarWidthExtended WlrLayershell.namespace: "quickshell:sidebarLeft" - // Hyprland 0.49: Focus is always exclusive and setting this breaks mouse focus grab - // WlrLayershell.keyboardFocus: WlrKeyboardFocus.Exclusive + // Hyprland 0.49: OnDemand is Exclusive, Exclusive just breaks click-outside-to-close + WlrLayershell.keyboardFocus: WlrKeyboardFocus.OnDemand color: "transparent" anchors { @@ -56,26 +58,12 @@ Scope { // Scope HyprlandFocusGrab { // Click outside to close id: grab windows: [ sidebarRoot ] - active: false + active: sidebarRoot.visible && !sidebarRoot.pin + onActiveChanged: { // Focus the selected tab + if (active) swipeView.currentItem.forceActiveFocus() + } onCleared: () => { - if (!active) sidebarRoot.visible = false - } - } - - 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 + if (!active) sidebarRoot.hide() } } @@ -87,41 +75,50 @@ Scope { // Scope anchors.left: parent.left anchors.topMargin: 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 color: Appearance.colors.colLayer0 radius: Appearance.rounding.screenRounding - Appearance.sizes.elevationMargin + 1 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 { - NumberAnimation { - duration: Appearance.animation.elementMove.duration - easing.type: Appearance.animation.elementMove.type - easing.bezierCurve: Appearance.animation.elementMove.bezierCurve - } + animation: Appearance.animation.elementMove.numberAnimation.createObject(this) } Keys.onPressed: (event) => { // console.log("Key pressed: " + event.key) if (event.key === Qt.Key_Escape) { - sidebarRoot.visible = false; + sidebarRoot.hide(); } if (event.modifiers === Qt.ControlModifier) { 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) { - 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) { - 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) { - 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) { sidebarRoot.extend = !sidebarRoot.extend; } + else if (event.key === Qt.Key_P) { + sidebarRoot.pin = !sidebarRoot.pin; + } event.accepted = true; } } @@ -137,7 +134,7 @@ Scope { // Scope tabButtonList: root.tabButtonList externalTrackedTab: sidebarRoot.selectedTab function onCurrentIndexChanged(currentIndex) { - PersistentStateManager.setState("sidebar.leftSide.selectedTab", currentIndex) + sidebarRoot.selectedTab = currentIndex } } @@ -146,10 +143,12 @@ Scope { // Scope Layout.topMargin: 5 Layout.fillWidth: true Layout.fillHeight: true - currentIndex: sidebarRoot.selectedTab + spacing: 10 + + currentIndex: tabBar.externalTrackedTab onCurrentIndexChanged: { tabBar.enableIndicatorAnimation = true - PersistentStateManager.setState("sidebar.leftSide.selectedTab", currentIndex) + sidebarRoot.selectedTab = currentIndex } 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 { target: "sidebarLeft" function toggle(): void { - for (let i = 0; i < sidebarVariants.instances.length; i++) { - let panelWindow = sidebarVariants.instances[i]; - if (panelWindow.modelData.name == Hyprland.focusedMonitor.name) { - panelWindow.visible = !panelWindow.visible; - if(panelWindow.visible) Notifications.timeoutAll(); - } - } + sidebarLoader.active = !sidebarLoader.active + if(sidebarLoader.active) Notifications.timeoutAll(); } function close(): void { - for (let i = 0; i < sidebarVariants.instances.length; i++) { - let panelWindow = sidebarVariants.instances[i]; - if (panelWindow.modelData.name == Hyprland.focusedMonitor.name) { - panelWindow.visible = false; - } - } + sidebarLoader.active = false } function open(): void { - for (let i = 0; i < sidebarVariants.instances.length; i++) { - let panelWindow = sidebarVariants.instances[i]; - if (panelWindow.modelData.name == Hyprland.focusedMonitor.name) { - panelWindow.visible = true; - if(panelWindow.visible) Notifications.timeoutAll(); - } - } + sidebarLoader.active = true + if(sidebarLoader.active) Notifications.timeoutAll(); } } GlobalShortcut { name: "sidebarLeftToggle" - description: "Toggles left sidebar on press" + description: qsTr("Toggles left sidebar on press") onPressed: { - for (let i = 0; i < sidebarVariants.instances.length; i++) { - let panelWindow = sidebarVariants.instances[i]; - if (panelWindow.modelData.name == Hyprland.focusedMonitor.name) { - panelWindow.visible = !panelWindow.visible; - if(panelWindow.visible) Notifications.timeoutAll(); - } - } + sidebarLoader.active = !sidebarLoader.active; + if(sidebarLoader.active) Notifications.timeoutAll(); } } GlobalShortcut { name: "sidebarLeftOpen" - description: "Opens left sidebar on press" + description: qsTr("Opens left sidebar on press") onPressed: { - for (let i = 0; i < sidebarVariants.instances.length; i++) { - let panelWindow = sidebarVariants.instances[i]; - if (panelWindow.modelData.name == Hyprland.focusedMonitor.name) { - panelWindow.visible = true; - if(panelWindow.visible) Notifications.timeoutAll(); - } - } + sidebarLoader.active = true; + if(sidebarLoader.active) Notifications.timeoutAll(); } } GlobalShortcut { name: "sidebarLeftClose" - description: "Closes left sidebar on press" + description: qsTr("Closes left sidebar on press") onPressed: { - for (let i = 0; i < sidebarVariants.instances.length; i++) { - let panelWindow = sidebarVariants.instances[i]; - if (panelWindow.modelData.name == Hyprland.focusedMonitor.name) { - panelWindow.visible = false; - } - } + sidebarLoader.active = false; } } diff --git a/.config/quickshell/modules/sidebarLeft/aiChat/AiMessage.qml b/.config/quickshell/modules/sidebarLeft/aiChat/AiMessage.qml index 7418e31ab..4c412719a 100644 --- a/.config/quickshell/modules/sidebarLeft/aiChat/AiMessage.qml +++ b/.config/quickshell/modules/sidebarLeft/aiChat/AiMessage.qml @@ -151,7 +151,7 @@ Rectangle { color: Appearance.m3colors.m3onSecondaryContainer text: messageData.role == 'assistant' ? Ai.models[messageData.model].name : (messageData.role == 'user' && SystemInfo.username) ? SystemInfo.username : - (messageData.role == 'interface') ? qsTr("Interface") : qsTr("Unknown") + qsTr("Interface") } } } diff --git a/.config/quickshell/modules/sidebarLeft/aiChat/AiMessageControlButton.qml b/.config/quickshell/modules/sidebarLeft/aiChat/AiMessageControlButton.qml index 56de474dd..96b967538 100644 --- a/.config/quickshell/modules/sidebarLeft/aiChat/AiMessageControlButton.qml +++ b/.config/quickshell/modules/sidebarLeft/aiChat/AiMessageControlButton.qml @@ -1,6 +1,7 @@ import "root:/modules/common" import "root:/modules/common/widgets" import "root:/services" +import "root:/modules/common/functions/color_utils.js" as ColorUtils import QtQuick import QtQuick.Controls import QtQuick.Layouts @@ -18,20 +19,16 @@ Button { background: Rectangle { 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.hovered ? Appearance.colors.colPrimaryHover : Appearance.m3colors.m3primary) : (button.down ? Appearance.colors.colSurfaceContainerHighestActive : button.hovered ? Appearance.colors.colSurfaceContainerHighestHover : - Appearance.transparentize(Appearance.m3colors.m3surfaceContainerHighest, 1)) + ColorUtils.transparentize(Appearance.m3colors.m3surfaceContainerHighest, 1)) Behavior on color { - ColorAnimation { - duration: Appearance.animation.elementMoveFast.duration - easing.type: Appearance.animation.elementMoveFast.type - easing.bezierCurve: Appearance.animation.elementMoveFast.bezierCurve - } + animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this) } } @@ -44,11 +41,7 @@ Button { Appearance.colors.colOnLayer1Inactive Behavior on color { - ColorAnimation { - duration: Appearance.animation.elementMoveFast.duration - easing.type: Appearance.animation.elementMoveFast.type - easing.bezierCurve: Appearance.animation.elementMoveFast.bezierCurve - } + animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this) } } } diff --git a/.config/quickshell/modules/sidebarLeft/aiChat/MessageThinkBlock.qml b/.config/quickshell/modules/sidebarLeft/aiChat/MessageThinkBlock.qml index f2f6b35d3..e00615b06 100644 --- a/.config/quickshell/modules/sidebarLeft/aiChat/MessageThinkBlock.qml +++ b/.config/quickshell/modules/sidebarLeft/aiChat/MessageThinkBlock.qml @@ -6,6 +6,7 @@ import "root:/modules/common/" import "root:/modules/common/widgets" import "../" import "root:/modules/common/functions/string_utils.js" as StringUtils +import "root:/modules/common/functions/color_utils.js" as ColorUtils import QtQuick import QtQuick.Controls import QtQuick.Layouts @@ -98,7 +99,7 @@ Item { id: thinkBlockLanguage Layout.fillWidth: false 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 } Button { // Expand button @@ -117,7 +118,7 @@ Item { radius: Appearance.rounding.full color: (headerMouseArea.pressed) ? Appearance.colors.colLayer2Active : (headerMouseArea.containsMouse ? Appearance.colors.colLayer2Hover - : Appearance.transparentize(Appearance.colors.colLayer2, 1)) + : ColorUtils.transparentize(Appearance.colors.colLayer2, 1)) Behavior on color { ColorAnimation { diff --git a/.config/quickshell/modules/sidebarLeft/anime/BooruImage.qml b/.config/quickshell/modules/sidebarLeft/anime/BooruImage.qml index ca180271f..4a754f813 100644 --- a/.config/quickshell/modules/sidebarLeft/anime/BooruImage.qml +++ b/.config/quickshell/modules/sidebarLeft/anime/BooruImage.qml @@ -2,15 +2,17 @@ import "root:/" import "root:/modules/common" import "root:/modules/common/widgets" import "root:/modules/common/functions/string_utils.js" as StringUtils +import "root:/modules/common/functions/color_utils.js" as ColorUtils import QtQml import Qt.labs.platform import QtQuick import QtQuick.Controls import QtQuick.Layouts +import QtQuick.Effects +import Qt5Compat.GraphicalEffects import Quickshell import Quickshell.Io import Quickshell.Hyprland -import Qt5Compat.GraphicalEffects Button { id: root @@ -78,11 +80,7 @@ Button { } Behavior on opacity { - NumberAnimation { - duration: Appearance.animation.elementMoveEnter.duration - easing.type: Appearance.animation.elementMoveEnter.type - easing.bezierCurve: Appearance.animation.elementMoveEnter.bezierCurve - } + animation: Appearance.animation.elementMoveEnter.numberAnimation.createObject(this) } } @@ -97,13 +95,13 @@ Button { PointingHandInteraction {} 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 { - color: menuButton.down ? Appearance.transparentize(Appearance.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) : - Appearance.transparentize(Appearance.m3colors.m3surface, 0.3) + color: menuButton.down ? ColorUtils.transparentize(ColorUtils.mix(Appearance.m3colors.m3surface, Appearance.m3colors.m3onSurface, 0.6), 0.1) : + menuButton.hovered ? ColorUtils.transparentize(ColorUtils.mix(Appearance.m3colors.m3surface, Appearance.m3colors.m3onSurface, 0.8), 0.2) : + ColorUtils.transparentize(Appearance.m3colors.m3surface, 0.3) radius: Appearance.rounding.full } @@ -140,6 +138,16 @@ Button { implicitHeight: contextMenuColumnLayout.implicitHeight + radius * 2 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 { NumberAnimation { duration: Appearance.animation.elementMoveFast.duration @@ -180,7 +188,7 @@ Button { MenuButton { id: downloadButton Layout.fillWidth: true - buttonText: "Download" + buttonText: qsTr("Download") onClicked: { 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}'`) @@ -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 - } - } - } } } } diff --git a/.config/quickshell/modules/sidebarLeft/anime/BooruResponse.qml b/.config/quickshell/modules/sidebarLeft/anime/BooruResponse.qml index e9fe244ab..4822a2224 100644 --- a/.config/quickshell/modules/sidebarLeft/anime/BooruResponse.qml +++ b/.config/quickshell/modules/sidebarLeft/anime/BooruResponse.qml @@ -2,6 +2,7 @@ import "root:/" import "root:/services" import "root:/modules/common" import "root:/modules/common/widgets" +import "root:/modules/common/functions/string_utils.js" as StringUtils import "../" import QtQuick import QtQuick.Controls @@ -93,7 +94,8 @@ Rectangle { anchors.centerIn: parent font.pixelSize: Appearance.font.pixelSize.smaller 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 { - NumberAnimation { - duration: Appearance.animation.elementMove.duration - easing.type: Appearance.animation.elementMove.type - easing.bezierCurve: Appearance.animation.elementMove.bezierCurve - } + animation: Appearance.animation.elementMove.numberAnimation.createObject(this) } Behavior on implicitHeight { - NumberAnimation { - duration: Appearance.animation.elementMove.duration - easing.type: Appearance.animation.elementMove.type - easing.bezierCurve: Appearance.animation.elementMove.bezierCurve - } + animation: Appearance.animation.elementMove.numberAnimation.createObject(this) } RowLayout { diff --git a/.config/quickshell/modules/sidebarRight/CenterWidgetGroup.qml b/.config/quickshell/modules/sidebarRight/CenterWidgetGroup.qml index ede9e7219..1b426da43 100644 --- a/.config/quickshell/modules/sidebarRight/CenterWidgetGroup.qml +++ b/.config/quickshell/modules/sidebarRight/CenterWidgetGroup.qml @@ -16,27 +16,23 @@ Rectangle { radius: Appearance.rounding.normal 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")}] - onSelectedTabChanged: { - PersistentStateManager.setState("sidebar.centerGroup.selectedTab", selectedTab) - } - Keys.onPressed: (event) => { if (event.key === Qt.Key_PageDown || event.key === Qt.Key_PageUp) { 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) { - PersistentStateManager.setState("sidebar.centerGroup.selectedTab", Math.max(root.selectedTab - 1, 0)) + root.selectedTab = Math.max(root.selectedTab - 1, 0) } event.accepted = true; } if (event.modifiers === Qt.ControlModifier) { 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) { - 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; } @@ -53,7 +49,7 @@ Rectangle { externalTrackedTab: root.selectedTab function onCurrentIndexChanged(currentIndex) { - PersistentStateManager.setState("sidebar.centerGroup.selectedTab", currentIndex) + root.selectedTab = currentIndex } } @@ -62,10 +58,11 @@ Rectangle { Layout.topMargin: 5 Layout.fillWidth: true Layout.fillHeight: true + spacing: 10 currentIndex: root.selectedTab onCurrentIndexChanged: { tabBar.enableIndicatorAnimation = true - PersistentStateManager.setState("sidebar.centerGroup.selectedTab", currentIndex) + root.selectedTab = currentIndex } clip: true diff --git a/.config/quickshell/modules/sidebarRight/SidebarRight.qml b/.config/quickshell/modules/sidebarRight/SidebarRight.qml index e9ef5ffdc..a8013e87b 100644 --- a/.config/quickshell/modules/sidebarRight/SidebarRight.qml +++ b/.config/quickshell/modules/sidebarRight/SidebarRight.qml @@ -2,10 +2,12 @@ import "root:/" import "root:/services" import "root:/modules/common" import "root:/modules/common/widgets" +import "root:/modules/common/functions/string_utils.js" as StringUtils import "./quickToggles/" import QtQuick import QtQuick.Controls import QtQuick.Layouts +import QtQuick.Effects import Qt5Compat.GraphicalEffects import Quickshell.Io import Quickshell @@ -17,251 +19,207 @@ Scope { property int sidebarWidth: Appearance.sizes.sidebarWidth property int sidebarPadding: 15 - Variants { - id: sidebarVariants - model: Quickshell.screens - - Loader { - 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 - } - - } + Loader { + id: sidebarLoader + active: false + onActiveChanged: { + GlobalStates.sidebarRightOpen = sidebarLoader.active } + 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 { target: "sidebarRight" function toggle(): void { - for (let i = 0; i < sidebarVariants.instances.length; i++) { - let loader = sidebarVariants.instances[i]; - loader.active = !loader.active; - } + sidebarLoader.active = !sidebarLoader.active; + if(sidebarLoader.active) Notifications.timeoutAll(); } function close(): void { - for (let i = 0; i < sidebarVariants.instances.length; i++) { - let loader = sidebarVariants.instances[i]; - loader.active = false; - } + sidebarLoader.active = false; } function open(): void { - for (let i = 0; i < sidebarVariants.instances.length; i++) { - let loader = sidebarVariants.instances[i]; - loader.active = true; - } + sidebarLoader.active = true; + Notifications.timeoutAll(); } } GlobalShortcut { name: "sidebarRightToggle" - description: "Toggles right sidebar on press" + description: qsTr("Toggles right sidebar on press") onPressed: { - for (let i = 0; i < sidebarVariants.instances.length; i++) { - let loader = sidebarVariants.instances[i]; - loader.active = !loader.active; - } + sidebarLoader.active = !sidebarLoader.active; + if(sidebarLoader.active) Notifications.timeoutAll(); } } GlobalShortcut { name: "sidebarRightOpen" - description: "Opens right sidebar on press" + description: qsTr("Opens right sidebar on press") onPressed: { - for (let i = 0; i < sidebarVariants.instances.length; i++) { - let loader = sidebarVariants.instances[i]; - loader.active = true; - } + sidebarLoader.active = true; + Notifications.timeoutAll(); } } GlobalShortcut { name: "sidebarRightClose" - description: "Closes right sidebar on press" + description: qsTr("Closes right sidebar on press") onPressed: { - for (let i = 0; i < sidebarVariants.instances.length; i++) { - let loader = sidebarVariants.instances[i]; - loader.active = false; - } + sidebarLoader.active = false; } } diff --git a/.config/quickshell/modules/sidebarRight/calendar/CalendarDayButton.qml b/.config/quickshell/modules/sidebarRight/calendar/CalendarDayButton.qml index 6977c6bc4..041c64a7a 100644 --- a/.config/quickshell/modules/sidebarRight/calendar/CalendarDayButton.qml +++ b/.config/quickshell/modules/sidebarRight/calendar/CalendarDayButton.qml @@ -1,5 +1,6 @@ import "root:/modules/common" import "root:/modules/common/widgets" +import "root:/modules/common/functions/color_utils.js" as ColorUtils import QtQuick import QtQuick.Controls import QtQuick.Layouts @@ -24,15 +25,10 @@ Button { Appearance.m3colors.m3primary) : (interactable && button.down) ? Appearance.colors.colLayer1Active : (interactable && button.hovered) ? Appearance.colors.colLayer1Hover : - Appearance.transparentize(Appearance.colors.colLayer1, 1) + ColorUtils.transparentize(Appearance.colors.colLayer1, 1) Behavior on color { - ColorAnimation { - duration: Appearance.animation.elementMove.duration - easing.type: Appearance.animation.elementMove.type - easing.bezierCurve: Appearance.animation.elementMove.bezierCurve - } - + animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this) } } @@ -46,12 +42,7 @@ Button { Appearance.m3colors.m3outlineVariant Behavior on color { - ColorAnimation { - duration: Appearance.animation.elementMove.duration - easing.type: Appearance.animation.elementMove.type - easing.bezierCurve: Appearance.animation.elementMove.bezierCurve - } - + animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this) } } } diff --git a/.config/quickshell/modules/sidebarRight/calendar/CalendarHeaderButton.qml b/.config/quickshell/modules/sidebarRight/calendar/CalendarHeaderButton.qml index b0c394641..16d916b6c 100644 --- a/.config/quickshell/modules/sidebarRight/calendar/CalendarHeaderButton.qml +++ b/.config/quickshell/modules/sidebarRight/calendar/CalendarHeaderButton.qml @@ -26,12 +26,7 @@ Button { color: (button.down) ? Appearance.colors.colLayer2Active : (button.hovered ? Appearance.colors.colLayer2Hover : Appearance.colors.colLayer2) Behavior on color { - ColorAnimation { - duration: Appearance.animation.elementMove.duration - easing.type: Appearance.animation.elementMove.type - easing.bezierCurve: Appearance.animation.elementMove.bezierCurve - } - + animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this) } } diff --git a/.config/quickshell/modules/sidebarRight/notifications/NotificationList.qml b/.config/quickshell/modules/sidebarRight/notifications/NotificationList.qml index ff42a186e..085d7c5bf 100644 --- a/.config/quickshell/modules/sidebarRight/notifications/NotificationList.qml +++ b/.config/quickshell/modules/sidebarRight/notifications/NotificationList.qml @@ -142,11 +142,7 @@ Item { opacity: notificationWidgetList.length > 0 ? 1 : 0 visible: opacity > 0 Behavior on opacity { - NumberAnimation { - duration: Appearance.animation.elementMove.duration - easing.type: Appearance.animation.elementMove.type - easing.bezierCurve: Appearance.animation.elementMove.bezierCurve - } + animation: Appearance.animation.elementMove.numberAnimation.createObject(this) } } diff --git a/.config/quickshell/modules/sidebarRight/notifications/NotificationStatusButton.qml b/.config/quickshell/modules/sidebarRight/notifications/NotificationStatusButton.qml index 0a1db2c43..568d7ad11 100644 --- a/.config/quickshell/modules/sidebarRight/notifications/NotificationStatusButton.qml +++ b/.config/quickshell/modules/sidebarRight/notifications/NotificationStatusButton.qml @@ -9,7 +9,7 @@ Button { property string buttonText: "" property string buttonIcon: "" - implicitHeight: 30 + // implicitHeight: 30 implicitWidth: contentRowLayout.implicitWidth + 10 * 2 Behavior on implicitWidth { SmoothedAnimation { @@ -25,19 +25,13 @@ Button { color: (button.down) ? Appearance.colors.colLayer2Active : (button.hovered ? Appearance.colors.colLayer2Hover : Appearance.colors.colLayer2) Behavior on color { - ColorAnimation { - duration: Appearance.animation.elementMove.duration - easing.type: Appearance.animation.elementMove.type - easing.bezierCurve: Appearance.animation.elementMove.bezierCurve - } - + animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this) } } contentItem: RowLayout { id: contentRowLayout - // anchors.centerIn: parent - anchors.right: parent.right + anchors.centerIn: parent spacing: 0 MaterialSymbol { text: buttonIcon diff --git a/.config/quickshell/modules/sidebarRight/quickToggles/BluetoothToggle.qml b/.config/quickshell/modules/sidebarRight/quickToggles/BluetoothToggle.qml index d73a66076..fc432c090 100644 --- a/.config/quickshell/modules/sidebarRight/quickToggles/BluetoothToggle.qml +++ b/.config/quickshell/modules/sidebarRight/quickToggles/BluetoothToggle.qml @@ -2,6 +2,7 @@ import "../" import "root:/services" import "root:/modules/common" import "root:/modules/common/widgets" +import "root:/modules/common/functions/string_utils.js" as StringUtils import QtQuick import Quickshell import Quickshell.Io @@ -37,8 +38,9 @@ QuickToggleButton { } } StyledToolTip { - content: `${(Bluetooth.bluetoothEnabled && Bluetooth.bluetoothDeviceName.length > 0) ? - Bluetooth.bluetoothDeviceName : "Bluetooth"} | ${qsTr("Right-click to configure")}` + content: StringUtils.format(qsTr("{0} | Right-click to configure"), + (Bluetooth.bluetoothEnabled && Bluetooth.bluetoothDeviceName.length > 0) ? + Bluetooth.bluetoothDeviceName : qsTr("Bluetooth")) } } diff --git a/.config/quickshell/modules/sidebarRight/quickToggles/NetworkToggle.qml b/.config/quickshell/modules/sidebarRight/quickToggles/NetworkToggle.qml index 742a2f480..93a4aec99 100644 --- a/.config/quickshell/modules/sidebarRight/quickToggles/NetworkToggle.qml +++ b/.config/quickshell/modules/sidebarRight/quickToggles/NetworkToggle.qml @@ -1,6 +1,7 @@ +import "root:/services" import "root:/modules/common" import "root:/modules/common/widgets" -import "root:/services" +import "root:/modules/common/functions/string_utils.js" as StringUtils import "../" import QtQuick import Quickshell @@ -42,6 +43,6 @@ QuickToggleButton { } } StyledToolTip { - content: `${Network.networkName} | Right-click to configure` + content: StringUtils.format(qsTr("{0} | Right-click to configure"), Network.networkName) } } diff --git a/.config/quickshell/modules/sidebarRight/quickToggles/QuickToggleButton.qml b/.config/quickshell/modules/sidebarRight/quickToggles/QuickToggleButton.qml index ac17f34b1..0bb94cffb 100644 --- a/.config/quickshell/modules/sidebarRight/quickToggles/QuickToggleButton.qml +++ b/.config/quickshell/modules/sidebarRight/quickToggles/QuickToggleButton.qml @@ -1,5 +1,6 @@ import "root:/modules/common" import "root:/modules/common/widgets" +import "root:/modules/common/functions/color_utils.js" as ColorUtils import QtQuick import QtQuick.Controls import Quickshell.Io @@ -20,14 +21,10 @@ Button { radius: Appearance.rounding.full color: toggled ? (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 { - ColorAnimation { - duration: Appearance.animation.elementMove.duration - easing.type: Appearance.animation.elementMove.type - easing.bezierCurve: Appearance.animation.elementMove.bezierCurve - } + animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this) } @@ -39,11 +36,7 @@ Button { color: toggled ? Appearance.m3colors.m3onPrimary : Appearance.colors.colOnLayer1 Behavior on color { - ColorAnimation { - duration: Appearance.animation.elementMove.duration - easing.type: Appearance.animation.elementMove.type - easing.bezierCurve: Appearance.animation.elementMove.bezierCurve - } + animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this) } } diff --git a/.config/quickshell/modules/sidebarRight/todo/TaskList.qml b/.config/quickshell/modules/sidebarRight/todo/TaskList.qml index 5d688fae4..b727ab1cd 100644 --- a/.config/quickshell/modules/sidebarRight/todo/TaskList.qml +++ b/.config/quickshell/modules/sidebarRight/todo/TaskList.qml @@ -155,11 +155,7 @@ Item { anchors.fill: parent Behavior on opacity { - NumberAnimation { - duration: Appearance.animation.elementMove.duration - easing.type: Appearance.animation.elementMove.type - easing.bezierCurve: Appearance.animation.elementMove.bezierCurve - } + animation: Appearance.animation.elementMove.numberAnimation.createObject(this) } ColumnLayout { diff --git a/.config/quickshell/modules/sidebarRight/todo/TodoItemActionButton.qml b/.config/quickshell/modules/sidebarRight/todo/TodoItemActionButton.qml index d6ca97855..1b1881776 100644 --- a/.config/quickshell/modules/sidebarRight/todo/TodoItemActionButton.qml +++ b/.config/quickshell/modules/sidebarRight/todo/TodoItemActionButton.qml @@ -1,5 +1,6 @@ import "root:/modules/common" import "root:/modules/common/widgets" +import "root:/modules/common/functions/color_utils.js" as ColorUtils import QtQuick import QtQuick.Controls import QtQuick.Layouts @@ -23,15 +24,10 @@ Button { background: Rectangle { anchors.fill: parent 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 { - ColorAnimation { - duration: Appearance.animation.elementMove.duration - easing.type: Appearance.animation.elementMove.type - easing.bezierCurve: Appearance.animation.elementMove.bezierCurve - } - + animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this) } } diff --git a/.config/quickshell/modules/sidebarRight/todo/TodoWidget.qml b/.config/quickshell/modules/sidebarRight/todo/TodoWidget.qml index b545eba3b..8b32fd855 100644 --- a/.config/quickshell/modules/sidebarRight/todo/TodoWidget.qml +++ b/.config/quickshell/modules/sidebarRight/todo/TodoWidget.qml @@ -1,10 +1,11 @@ import "root:/modules/common" import "root:/modules/common/widgets" import "root:/services" +import "root:/modules/common/functions/color_utils.js" as ColorUtils import QtQuick import QtQuick.Controls +import QtQuick.Effects import QtQuick.Layouts -import Qt5Compat.GraphicalEffects Item { id: root @@ -79,37 +80,33 @@ Item { tabIndicator.enableIndicatorAnimation = true } } + 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 radius: Appearance.rounding.full - z: 2 - anchors.fill: parent - 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 { + Behavior on x { enabled: tabIndicator.enableIndicatorAnimation - SmoothedAnimation { - velocity: Appearance.animation.positionShift.velocity - } + animation: Appearance.animation.elementMove.numberAnimation.createObject(this) } - Behavior on anchors.rightMargin { + + Behavior on implicitWidth { enabled: tabIndicator.enableIndicatorAnimation - SmoothedAnimation { - velocity: Appearance.animation.positionShift.velocity - } + animation: Appearance.animation.elementMove.numberAnimation.createObject(this) } - } } @@ -125,6 +122,7 @@ Item { Layout.topMargin: 10 Layout.fillWidth: true Layout.fillHeight: true + spacing: 10 clip: true currentIndex: currentTab onCurrentIndexChanged: { @@ -173,31 +171,17 @@ Item { color: (fabButton.down) ? Appearance.colors.colPrimaryContainerActive : (fabButton.hovered ? Appearance.colors.colPrimaryContainerHover : Appearance.m3colors.m3primaryContainer) Behavior on color { - ColorAnimation { - duration: Appearance.animation.elementMove.duration - easing.type: Appearance.animation.elementMove.type - easing.bezierCurve: Appearance.animation.elementMove.bezierCurve - } + animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this) } - } - DropShadow { - id: fabShadow - anchors.fill: fabBackground - source: fabBackground - horizontalOffset: 0 - verticalOffset: fabButton.hovered ? 4 : 2 - radius: fabButton.hovered ? Appearance.sizes.fabHoveredShadowRadius : Appearance.sizes.fabShadowRadius - samples: fabShadow.radius * 2 + 1 - 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 - } + layer.enabled: true + layer.effect: MultiEffect { + source: fabBackground + anchors.fill: fabBackground + shadowEnabled: true + shadowColor: Appearance.colors.colShadow + shadowBlur: 0.6 + shadowVerticalOffset: fabButton.hovered ? 4 : 2 } } diff --git a/.config/quickshell/modules/sidebarRight/volumeMixer/AudioDeviceSelectorButton.qml b/.config/quickshell/modules/sidebarRight/volumeMixer/AudioDeviceSelectorButton.qml index 14c4c3a37..643889048 100644 --- a/.config/quickshell/modules/sidebarRight/volumeMixer/AudioDeviceSelectorButton.qml +++ b/.config/quickshell/modules/sidebarRight/volumeMixer/AudioDeviceSelectorButton.qml @@ -18,12 +18,7 @@ Button { color: (button.down) ? Appearance.colors.colLayer2Active : (button.hovered ? Appearance.colors.colLayer2Hover : Appearance.colors.colLayer2) Behavior on color { - ColorAnimation { - duration: Appearance.animation.elementMove.duration - easing.type: Appearance.animation.elementMove.type - easing.bezierCurve: Appearance.animation.elementMove.bezierCurve - } - + animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this) } } diff --git a/.config/quickshell/scripts/switchwall.sh b/.config/quickshell/scripts/switchwall.sh index 32fb7c8a2..4547be416 100755 --- a/.config/quickshell/scripts/switchwall.sh +++ b/.config/quickshell/scripts/switchwall.sh @@ -27,6 +27,82 @@ post_process() { 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() { imgpath="$1" mode_flag="$2" @@ -48,8 +124,67 @@ switch() { echo 'Aborted' exit 0 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 # Determine mode if not set @@ -62,18 +197,14 @@ switch() { fi fi - # Dark/light mode, material scheme [[ -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") - # Terminal scheme generate_colors_material_args+=(--termscheme "$terminalscheme" --blend_bg_fg) generate_colors_material_args+=(--cache "$STATE_DIR/user/color.txt") - + pre_process - # Generate with matugen matugen "${matugen_args[@]}" - # Use custom script for mixing (matugen can't D:) source "$(eval echo $ILLOGICAL_IMPULSE_VIRTUAL_ENV)/bin/activate" python "$SCRIPT_DIR/generate_colors_material.py" "${generate_colors_material_args[@]}" \ > "$STATE_DIR"/user/generated/material_colors.scss diff --git a/.config/quickshell/services/Ai.qml b/.config/quickshell/services/Ai.qml index 495f15efc..7c33cd249 100644 --- a/.config/quickshell/services/Ai.qml +++ b/.config/quickshell/services/Ai.qml @@ -14,11 +14,22 @@ Singleton { readonly property string interfaceRole: "interface" readonly property string apiKeyEnvVarName: "API_KEY" property Component aiMessageComponent: AiMessageData {} - property string systemPrompt: ConfigOptions.ai.systemPrompt ?? "" + property string systemPrompt: ConfigOptions?.ai?.systemPrompt ?? "" property var messages: [] + property var messageIDs: [] + property var messageByID: ({}) readonly property var apiKeys: KeyringStorage.keyringData?.apiKeys ?? {} 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: // - name: Name of the model // - icon: Icon name of the model @@ -36,14 +47,14 @@ Singleton { "gemini-2.0-flash-search": { "name": "Gemini 2.0 Flash", "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", "endpoint": "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:streamGenerateContent", "model": "gemini-2.0-flash", "requires_key": true, "key_id": "gemini", "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", "tools": [ { @@ -54,25 +65,26 @@ Singleton { "openrouter-llama4-maverick": { "name": "Llama 4 Maverick", "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", "endpoint": "https://openrouter.ai/api/v1/chat/completions", "model": "meta-llama/llama-4-maverick:free", "requires_key": true, "key_id": "openrouter", "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": { "name": "DeepSeek R1", "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", "endpoint": "https://openrouter.ai/api/v1/chat/completions", "model": "deepseek/deepseek-r1:free", "requires_key": true, "key_id": "openrouter", "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) @@ -114,10 +126,11 @@ Singleton { const dataJson = JSON.parse(data); root.modelList = [...root.modelList, ...dataJson]; dataJson.forEach(model => { - root.models[model] = { + const safeModelName = root.safeModelName(model); + root.models[safeModelName] = { "name": guessModelName(model), "icon": guessModelLogo(model), - "description": `Local Ollama model: ${model}`, + "description": StringUtils.format(qsTr("Local Ollama model | {0}"), model), "homepage": `https://ollama.com/library/${model}`, "endpoint": "http://localhost:11434/v1/chat/completions", "model": model, @@ -141,13 +154,17 @@ Singleton { "thinking": false, "done": true, }); - root.messages = [...root.messages, aiMessage]; + const id = idForMessage(aiMessage); + root.messageIDs = [...root.messageIDs, id]; + root.messageByID[id] = aiMessage; } function removeMessage(index) { - if (index < 0 || index >= messages.length) return; - root.messages.splice(index, 1); - root.messages = [...root.messages]; + if (index < 0 || index >= messageIDs.length) return; + const id = root.messageIDs[index]; + root.messageIDs.splice(index, 1); + root.messageIDs = [...root.messageIDs]; + delete root.messageByID[id]; } function addApiKeyAdvice(model) { @@ -167,7 +184,7 @@ Singleton { modelId = modelId.toLowerCase() if (modelList.indexOf(modelId) !== -1) { 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 key not there show advice 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) { const model = models[currentModelId]; 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; } if (!key || key.length === 0) { @@ -194,7 +211,7 @@ Singleton { return; } 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() { @@ -207,12 +224,13 @@ Singleton { root.addMessage(StringUtils.format(qsTr("No API key set for {0}"), model.name), Ai.interfaceRole); } } 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() { - messages = []; + root.messageIDs = []; + root.messageByID = ({}); } Process { @@ -274,7 +292,8 @@ Singleton { /* Build endpoint, request data */ 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 = { "Content-Type": "application/json", @@ -288,7 +307,9 @@ Singleton { "thinking": true, "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 */ let headerString = Object.entries(requestHeaders) @@ -318,14 +339,14 @@ Singleton { const dataJson = JSON.parse(requester.geminiBuffer); const responseContent = dataJson.candidates[0]?.content?.parts[0]?.text requester.message.content += responseContent; - const annotationSources = dataJson.candidates[0]?.groundingMetadata.groundingChunks?.map(chunk => { + const annotationSources = dataJson.candidates[0]?.groundingMetadata?.groundingChunks?.map(chunk => { return { "type": "url_citation", "text": chunk?.web?.title, "url": chunk?.web?.uri, } }); - const annotations = dataJson.candidates[0]?.groundingMetadata.groundingSupports?.map(citation => { + const annotations = dataJson.candidates[0]?.groundingMetadata?.groundingSupports?.map(citation => { return { "type": "url_citation", "start_index": citation.segment?.startIndex, @@ -446,15 +467,7 @@ Singleton { function sendUserMessage(message) { if (message.length === 0) return; - - const userMessage = aiMessageComponent.createObject(root, { - "role": "user", - "content": message, - "thinking": false, - "done": true, - }); - root.messages = [...root.messages, userMessage]; - + root.addMessage(message, "user"); requester.makeRequest(); } diff --git a/.config/quickshell/services/Booru.qml b/.config/quickshell/services/Booru.qml index d0c5dceb2..216fc1f5e 100644 --- a/.config/quickshell/services/Booru.qml +++ b/.config/quickshell/services/Booru.qml @@ -13,20 +13,13 @@ Singleton { 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 var responses: [] 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 providers: { - "system": { "name": "System" }, + "system": { "name": qsTr("System") }, "yandere": { "name": "yande.re", "url": "https://yande.re", @@ -347,7 +340,7 @@ Singleton { xhr.setRequestHeader("User-Agent", defaultUserAgent) } 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) } root.runningRequests++; diff --git a/.config/quickshell/services/Brightness.qml b/.config/quickshell/services/Brightness.qml index 7fc2a2b24..ddf25f8ec 100644 --- a/.config/quickshell/services/Brightness.qml +++ b/.config/quickshell/services/Brightness.qml @@ -134,13 +134,13 @@ Singleton { GlobalShortcut { name: "brightnessIncrease" - description: "Increase brightness" + description: qsTr("Increase brightness") onPressed: root.increaseBrightness() } GlobalShortcut { name: "brightnessDecrease" - description: "Decrease brightness" + description: qsTr("Decrease brightness") onPressed: root.decreaseBrightness() } } diff --git a/.config/quickshell/services/ConfigLoader.qml b/.config/quickshell/services/ConfigLoader.qml index c05b17341..6c330c68e 100644 --- a/.config/quickshell/services/ConfigLoader.qml +++ b/.config/quickshell/services/ConfigLoader.qml @@ -29,11 +29,11 @@ Singleton { if (root.firstLoad) { root.firstLoad = false; } else { - Hyprland.dispatch(`exec notify-send "Shell configuration reloaded" "${root.filePath}"`) + Hyprland.dispatch(`exec notify-send "${qsTr("Shell configuration reloaded")}" "${root.filePath}"`) } } catch (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; } } @@ -66,9 +66,9 @@ Singleton { console.log("[ConfigLoader] File not found, creating new file.") const plainConfig = ObjectUtils.toPlainObject(ConfigOptions) 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 { - 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}"`) } } } diff --git a/.config/quickshell/services/DateTime.qml b/.config/quickshell/services/DateTime.qml index 32b89ff9c..ee5cf4b41 100644 --- a/.config/quickshell/services/DateTime.qml +++ b/.config/quickshell/services/DateTime.qml @@ -38,7 +38,7 @@ Singleton { if (hours > 0) formatted += `${formatted ? ", " : ""}${hours}h` if (minutes > 0 || !formatted) formatted += `${formatted ? ", " : ""}${minutes}m` uptime = formatted - interval = ConfigOptions.resources.updateInterval; + interval = ConfigOptions?.resources?.updateInterval ?? 3000 } } diff --git a/.config/quickshell/services/HyprlandKeybinds.qml b/.config/quickshell/services/HyprlandKeybinds.qml index 81c63a08e..1b2972fe9 100644 --- a/.config/quickshell/services/HyprlandKeybinds.qml +++ b/.config/quickshell/services/HyprlandKeybinds.qml @@ -11,19 +11,15 @@ import Quickshell.Hyprland Singleton { id: root - property var defaultKeybinds: [] - property var userKeybinds: [] + property var defaultKeybinds: {"children": []} + property var userKeybinds: {"children": []} property var keybinds: ({ children: [ - ...defaultKeybinds.children, - ...userKeybinds.children, + ...(defaultKeybinds.children ?? []), + ...(userKeybinds.children ?? []), ] }) - // onKeybindsChanged: { - // console.log("[CheatsheetKeybinds] Keybinds changed:", JSON.stringify(keybinds, null, 2)) - // } - Connections { target: Hyprland diff --git a/.config/quickshell/services/KeyringStorage.qml b/.config/quickshell/services/KeyringStorage.qml index 39632c6ca..0de619100 100644 --- a/.config/quickshell/services/KeyringStorage.qml +++ b/.config/quickshell/services/KeyringStorage.qml @@ -2,6 +2,7 @@ pragma Singleton pragma ComponentBehavior: Bound import "root:/modules/common" +import "root:/modules/common/functions/string_utils.js" as StringUtils import Quickshell; import Quickshell.Io; import Qt.labs.platform @@ -18,14 +19,14 @@ Singleton { property var properties: { "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( function(arr, 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) { if (!root.keyringData) root.keyringData = {}; diff --git a/.config/quickshell/services/MaterialThemeLoader.qml b/.config/quickshell/services/MaterialThemeLoader.qml index 7eb974b1e..339a0d772 100644 --- a/.config/quickshell/services/MaterialThemeLoader.qml +++ b/.config/quickshell/services/MaterialThemeLoader.qml @@ -30,7 +30,7 @@ Singleton { Timer { id: delayedFileRead - interval: ConfigOptions.hacks.arbitraryRaceConditionDelay + interval: ConfigOptions?.hacks?.arbitraryRaceConditionDelay ?? 100 repeat: false running: false onTriggered: { diff --git a/.config/quickshell/services/PersistentStateManager.qml b/.config/quickshell/services/PersistentStateManager.qml index d961b41b6..7c3fec739 100644 --- a/.config/quickshell/services/PersistentStateManager.qml +++ b/.config/quickshell/services/PersistentStateManager.qml @@ -72,7 +72,7 @@ Singleton { Timer { id: delayedFileRead - interval: ConfigOptions.hacks.arbitraryRaceConditionDelay + interval: ConfigOptions?.hacks?.arbitraryRaceConditionDelay ?? 100 repeat: false running: false onTriggered: { diff --git a/.config/quickshell/services/ResourceUsage.qml b/.config/quickshell/services/ResourceUsage.qml index e30b36c84..39078fbde 100644 --- a/.config/quickshell/services/ResourceUsage.qml +++ b/.config/quickshell/services/ResourceUsage.qml @@ -50,7 +50,7 @@ Singleton { previousCpuStats = { total, idle } } - interval = ConfigOptions.resources.updateInterval + interval = ConfigOptions?.resources?.updateInterval ?? 3000 } } diff --git a/.config/quickshell/shell.qml b/.config/quickshell/shell.qml index fae85be7d..af14d6b74 100644 --- a/.config/quickshell/shell.qml +++ b/.config/quickshell/shell.qml @@ -1,7 +1,10 @@ //@ pragma UseQApplication +//@ pragma Env QT_QUICK_CONTROLS_STYLE=Basic +//@ pragma Env QS_NO_RELOAD_POPUP=1 import "./modules/bar/" import "./modules/cheatsheet/" +import "./modules/mediaControls/" import "./modules/notificationPopup/" import "./modules/onScreenDisplay/" import "./modules/overview/" @@ -25,6 +28,7 @@ ShellRoot { Bar {} Cheatsheet {} + MediaControls {} NotificationPopup {} OnScreenDisplayBrightness {} OnScreenDisplayVolume {} diff --git a/.local/bin/fuzzel-emoji b/.local/bin/fuzzel-emoji index a7d9291a1..a526a4646 100755 --- a/.local/bin/fuzzel-emoji +++ b/.local/bin/fuzzel-emoji @@ -1,10 +1,26 @@ #!/bin/bash -if [ $? -eq 0 ] -then - sed '1,/^### DATA ###$/d' $0 | fuzzel --match-mode fzf --dmenu | cut -d ' ' -f 1 | tr -d '\n' | wl-copy -else - sed '1,/^### DATA ###$/d' $0 | fuzzel --match-mode fzf --dmenu | cut -d ' ' -f 1 | tr -d '\n' | wl-copy -fi +set -euo pipefail + +MODE="${1:-type}" + +emoji="$(sed '1,/^### DATA ###$/d' "$0" | fuzzel --match-mode fzf --dmenu | cut -d ' ' -f 1 | tr -d '\n')" + +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 ### DATA ### 😀 grinning face face smile happy joy :D grin diff --git a/.local/bin/rubyshot b/.local/bin/rubyshot deleted file mode 100755 index 8431bd693..000000000 --- a/.local/bin/rubyshot +++ /dev/null @@ -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 ${#:+"$@"} \ No newline at end of file diff --git a/README.md b/README.md index a6fc98402..11f7e7675 100644 --- a/README.md +++ b/README.md @@ -2,8 +2,8 @@ ## Not ready, but feel free to try it. It's simple: - **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 quickshell and more stuff**: `yay -S quickshell matugen-bin grimblast` +- **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 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) - **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 diff --git a/arch-packages/illogical-impulse-gnome/PKGBUILD b/arch-packages/illogical-impulse-gnome/PKGBUILD index 33af4b8ba..3e773bdaf 100644 --- a/arch-packages/illogical-impulse-gnome/PKGBUILD +++ b/arch-packages/illogical-impulse-gnome/PKGBUILD @@ -7,6 +7,6 @@ license=(None) depends=( polkit-gnome gnome-keyring - gnome-control-center - blueberry networkmanager + networkmanager + better-control-git )