merge upstream

This commit is contained in:
Greyfeather
2025-05-21 00:22:49 -06:00
parent 08b9014ee2
commit 65b5ec93c7
87 changed files with 2119 additions and 1602 deletions
@@ -46,10 +46,10 @@
"fakeScreenRounding": 2 // 0: None | 1: Always | 2: When not fullscreen
},
"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
},
@@ -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)
@@ -0,0 +1,22 @@
// Special
$background: {{backgroundColor}};
$foreground: {{foregroundColor}};
$cursor: {{foregroundColor}};
// Colors
$color0: {{dominantColor}};
$color1: {{dominantColor}};
$color2: {{dominantColor}};
$color3: {{dominantColor}};
$color4: {{dominantColor}};
$color5: {{dominantColor}};
$color6: {{dominantColor}};
$color7: {{dominantColor}};
$color8: {{dominantColor}};
$color9: {{dominantColor}};
$color10: {{dominantColor}};
$color11: {{dominantColor}};
$color12: {{dominantColor}};
$color13: {{dominantColor}};
$color14: {{dominantColor}};
$color15: {{dominantColor}};
+3
View File
@@ -20,8 +20,11 @@ $secondaryContainer: transparentize(mix(mix($background, $color2, 50%), $color6,
$onSecondaryContainer: mix($color7, $color2, 90%);
@if $darkmode == False {
$onSecondaryContainer: mix($onSecondaryContainer, black, 50%);
} @else {
$onSecondaryContainer: mix($onSecondaryContainer, white, 50%);
}
.osd-music {
@include menu_decel;
@include elevation2;
+4 -2
View File
@@ -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
+1 -1
View File
@@ -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
+4 -3
View File
@@ -23,6 +23,7 @@ bindd = Super, A, Toggle left sidebar, global, quickshell:sidebarLeftToggle # To
bind = Super, O, global, quickshell:sidebarLeftToggle # [hidden]
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
+1 -2
View File
@@ -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
+3 -3
View File
@@ -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()
+4 -4
View File
@@ -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
+6 -13
View File
@@ -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)
}
}
@@ -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))
}
@@ -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 {
+19 -13
View File
@@ -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
+3 -6
View File
@@ -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)
}
}
+2 -1
View File
@@ -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 {
@@ -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)
}
}
@@ -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
+5 -25
View File
@@ -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
@@ -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;
}
}
@@ -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<real> 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<real> 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<real> 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<real> 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"
@@ -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]"
}
}
}
@@ -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
@@ -0,0 +1,85 @@
// This module provides high level utility functions for color manipulation.
/**
* Returns a color with the hue of color2 and the saturation, value, and alpha of color1.
*
* @param {string} color1 - The base color (any Qt.color-compatible string).
* @param {string} color2 - The color to take hue from.
* @returns {Qt.rgba} The resulting color.
*/
function colorWithHueOf(color1, color2) {
var c1 = Qt.color(color1);
var c2 = Qt.color(color2);
// Qt.color hsvHue/hsvSaturation/hsvValue/alpha return 0-1
var hue = c2.hsvHue;
var sat = c1.hsvSaturation;
var val = c1.hsvValue;
var alpha = c1.a;
return Qt.hsva(hue, sat, val, alpha);
}
/**
* Returns a color with the saturation of color2 and the hue/value/alpha of color1.
*
* @param {string} color1 - The base color (any Qt.color-compatible string).
* @param {string} color2 - The color to take saturation from.
* @returns {Qt.rgba} The resulting color.
*/
function colorWithSaturationOf(color1, color2) {
var c1 = Qt.color(color1);
var c2 = Qt.color(color2);
var hue = c1.hsvHue;
var sat = c2.hsvSaturation;
var val = c1.hsvValue;
var alpha = c1.a;
return Qt.hsva(hue, sat, val, alpha);
}
/**
* Adapts color1 to the accent (hue and saturation) of color2 using HSL, keeping lightness and alpha from color1.
*
* @param {string} color1 - The base color (any Qt.color-compatible string).
* @param {string} color2 - The accent color.
* @returns {Qt.rgba} The resulting color.
*/
function adaptToAccent(color1, color2) {
var c1 = Qt.color(color1);
var c2 = Qt.color(color2);
var hue = c2.hslHue;
var sat = c2.hslSaturation;
var light = c1.hslLightness;
var alpha = c1.a;
return Qt.hsla(hue, sat, light, alpha);
}
/**
* Mixes two colors by a given percentage.
*
* @param {string} color1 - The first color (any Qt.color-compatible string).
* @param {string} color2 - The second color.
* @param {number} percentage - The mix ratio (0-1). 1 = all color1, 0 = all color2.
* @returns {Qt.rgba} The resulting mixed color.
*/
function mix(color1, color2, percentage) {
var c1 = Qt.color(color1);
var c2 = Qt.color(color2);
return Qt.rgba(percentage * c1.r + (1 - percentage) * c2.r, percentage * c1.g + (1 - percentage) * c2.g, percentage * c1.b + (1 - percentage) * c2.b, percentage * c1.a + (1 - percentage) * c2.a);
}
/**
* Transparentizes a color by a given percentage.
*
* @param {string} color - The color (any Qt.color-compatible string).
* @param {number} percentage - The amount to transparentize (0-1).
* @returns {Qt.rgba} The resulting color.
*/
function transparentize(color, percentage) {
var c = Qt.color(color);
return Qt.rgba(c.r, c.g, c.b, c.a * (1 - percentage));
}
@@ -147,4 +147,32 @@ function wordWrap(str, maxLen) {
}
if (current.length > 0) lines.push(current);
return lines.join("\n");
}
function cleanMusicTitle(title) {
if (!title) return "";
// Brackets
title = title.replace(/^ *\([^)]*\) */g, " "); // Round brackets
title = title.replace(/^ *\[[^\]]*\] */g, " "); // Square brackets
title = title.replace(/^ *\{[^\}]*\} */g, " "); // Curly brackets
// Japenis brackets
title = title.replace(/^ *【[^】]*】/, "") // Touhou
title = title.replace(/^ *《[^》]*》/, "") // ??
title = title.replace(/^ *「[^」]*」/, "") // OP/ED
title = title.replace(/^ *『[^』]*』/, "") // OP/ED
return title;
}
function friendlyTimeForSeconds(seconds) {
if (isNaN(seconds) || seconds < 0) return "0:00";
seconds = Math.floor(seconds);
const h = Math.floor(seconds / 3600);
const m = Math.floor((seconds % 3600) / 60);
const s = seconds % 60;
if (h > 0) {
return `${h}:${m.toString().padStart(2, '0')}:${s.toString().padStart(2, '0')}`;
} else {
return `${m}:${s.toString().padStart(2, '0')}`;
}
}
@@ -1,4 +1,5 @@
import "root:/modules/common"
import "root:/modules/common/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)
}
}
@@ -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)
}
}
@@ -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)
}
}
}
@@ -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 {
@@ -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
}
}
}
}
@@ -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)
}
}
}
@@ -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)
}
}
@@ -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)
}
}
}
@@ -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
}
}
}
@@ -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
}
}
}
@@ -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)
}
}
}
@@ -26,7 +26,7 @@ ToolTip {
}
}
background: Item {}
background: null
contentItem: Item {
id: contentItemBackground
@@ -0,0 +1,146 @@
import "root:/modules/common"
import "root:/modules/common/widgets"
import "root:/services"
import "root:/modules/common/functions/string_utils.js" as StringUtils
import "root:/modules/common/functions/file_utils.js" as FileUtils
import Qt5Compat.GraphicalEffects
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls
import Quickshell
import Quickshell.Io
import Quickshell.Services.Mpris
import Quickshell.Widgets
import Quickshell.Wayland
import Quickshell.Hyprland
Scope {
id: root
required property var bar
property bool visible: false
readonly property MprisPlayer activePlayer: MprisController.activePlayer
readonly property var realPlayers: Mpris.players.values.filter(player => isRealPlayer(player))
readonly property real osdWidth: Appearance.sizes.osdWidth
readonly property real widgetWidth: Appearance.sizes.mediaControlsWidth
readonly property real widgetHeight: Appearance.sizes.mediaControlsHeight
property real contentPadding: 13
property real popupRounding: Appearance.rounding.screenRounding - Appearance.sizes.elevationMargin + 1
property real artRounding: Appearance.rounding.verysmall
property string baseCoverArtDir: FileUtils.trimFileProtocol(`${XdgDirectories.cache}/media/coverart`)
property bool hasPlasmaIntegration: false
function isRealPlayer(player) {
// return true
return (
// Remove unecessary native buses from browsers if there's plasma integration
!(hasPlasmaIntegration && player.dbusName.startsWith('org.mpris.MediaPlayer2.firefox')) &&
!(hasPlasmaIntegration && player.dbusName.startsWith('org.mpris.MediaPlayer2.chromium')) &&
// playerctld just copies other buses and we don't need duplicates
!player.dbusName?.startsWith('org.mpris.MediaPlayer2.playerctld') &&
// Non-instance mpd bus
!(player.dbusName?.endsWith('.mpd') && !player.dbusName.endsWith('MediaPlayer2.mpd'))
);
}
Component.onCompleted: {
Hyprland.dispatch(`exec rm -rf ${baseCoverArtDir} && mkdir -p ${baseCoverArtDir}`)
}
Loader {
id: mediaControlsLoader
active: false
PanelWindow {
id: mediaControlsRoot
visible: mediaControlsLoader.active
exclusiveZone: 0
implicitWidth: (
(mediaControlsRoot.screen.width / 2) // Middle of screen
- (osdWidth / 2) // Dodge OSD
- (widgetWidth / 2) // Account for widget width
) * 2
implicitHeight: playerColumnLayout.implicitHeight
color: "transparent"
WlrLayershell.namespace: "quickshell:mediaControls"
anchors {
top: true
left: true
}
mask: Region {
item: playerColumnLayout
}
ColumnLayout {
id: playerColumnLayout
anchors.top: parent.top
anchors.bottom: parent.bottom
x: (mediaControlsRoot.screen.width / 2) // Middle of screen
- (osdWidth / 2) // Dodge OSD
- (widgetWidth) // Account for widget width
+ (Appearance.sizes.elevationMargin) // It's fine for shadows to overlap
spacing: -Appearance.sizes.elevationMargin // Shadow overlap okay
Repeater {
model: ScriptModel {
values: root.realPlayers
}
delegate: PlayerControl {
required property MprisPlayer modelData
player: modelData
}
}
}
}
}
IpcHandler {
target: "mediaControls"
function toggle(): void {
mediaControlsLoader.active = !mediaControlsLoader.active;
if(mediaControlsLoader.active) Notifications.timeoutAll();
}
function close(): void {
mediaControlsLoader.active = false;
}
function open(): void {
mediaControlsLoader.active = true;
Notifications.timeoutAll();
}
}
GlobalShortcut {
name: "mediaControlsToggle"
description: qsTr("Toggles media controls on press")
onPressed: {
if (!mediaControlsLoader.active && Mpris.players.values.filter(player => isRealPlayer(player)).length === 0) {
return;
}
mediaControlsLoader.active = !mediaControlsLoader.active;
if(mediaControlsLoader.active) Notifications.timeoutAll();
}
}
GlobalShortcut {
name: "mediaControlsOpen"
description: qsTr("Opens media controls on press")
onPressed: {
mediaControlsLoader.active = true;
Notifications.timeoutAll();
}
}
GlobalShortcut {
name: "mediaControlsClose"
description: qsTr("Closes media controls on press")
onPressed: {
mediaControlsLoader.active = false;
}
}
}
@@ -0,0 +1,292 @@
import "root:/modules/common"
import "root:/modules/common/widgets"
import "root:/services"
import "root:/modules/common/functions/string_utils.js" as StringUtils
import "root:/modules/common/functions/color_utils.js" as ColorUtils
import Qt5Compat.GraphicalEffects
import QtQuick
import QtQuick.Effects
import QtQuick.Layouts
import QtQuick.Controls
import Quickshell
import Quickshell.Io
import Quickshell.Services.Mpris
import Quickshell.Widgets
import Quickshell.Wayland
import Quickshell.Hyprland
Item { // Player instance
id: playerController
required property MprisPlayer player
// property var artUrl: player?.metadata["xesam:url"] || player?.metadata["mpris:artUrl"] || player?.trackArtUrl
property var artUrl: player?.trackArtUrl
property color artDominantColor: Appearance.m3colors.m3secondaryContainer
implicitWidth: widgetWidth
implicitHeight: widgetHeight
component TrackChangeButton: Button {
id: playPauseButton
implicitWidth: 24
implicitHeight: 24
property var iconName
PointingHandInteraction {}
background: Rectangle {
color: playPauseButton.pressed ? blendedColors.colSecondaryContainerActive :
playPauseButton.hovered ? blendedColors.colSecondaryContainerHover :
ColorUtils.transparentize(blendedColors.colSecondaryContainer, 1)
radius: Appearance.rounding.full
Behavior on color {
animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this)
}
}
contentItem: MaterialSymbol {
iconSize: Appearance.font.pixelSize.huge
fill: 1
horizontalAlignment: Text.AlignHCenter
color: blendedColors.colOnSecondaryContainer
text: iconName
Behavior on color {
animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this)
}
}
}
Timer { // Force update for prevision
running: playerController.player?.playbackState == MprisPlaybackState.Playing
interval: 1000
repeat: true
onTriggered: {
playerController.player.positionChanged()
}
}
onArtUrlChanged: {
if (playerController.artUrl.length == 0) {
playerController.artDominantColor = Appearance.m3colors.m3secondaryContainer
return;
}
colorQuantizer.targetFile = playerController.artUrl // Yes this binding break is intentional
colorQuantizer.running = true
}
Process { // Average Color Runner
id: colorQuantizer
property string targetFile: playerController.artUrl
command: [ "sh", "-c", `magick '${targetFile}' -scale 1x1\\! -format '%[fx:int(255*r+.5)],%[fx:int(255*g+.5)],%[fx:int(255*b+.5)]' info: | sed 's/,/\\n/g' | xargs -L 1 printf '%02x' ; echo` ]
stdout: SplitParser {
onRead: data => {
// console.log("Color quantizer output:", data)
playerController.artDominantColor = "#" + data
}
}
}
property QtObject blendedColors: QtObject {
property color colLayer0: ColorUtils.mix(Appearance.colors.colLayer0, artDominantColor, 0.5)
property color colLayer1: ColorUtils.mix(Appearance.colors.colLayer1, artDominantColor, 0.5)
property color colOnLayer0: ColorUtils.mix(Appearance.colors.colOnLayer0, artDominantColor, 0.5)
property color colOnLayer1: ColorUtils.mix(Appearance.colors.colOnLayer1, artDominantColor, 0.5)
property color colSubtext: ColorUtils.mix(Appearance.colors.colOnLayer1, artDominantColor, 0.5)
property color colPrimary: ColorUtils.mix(ColorUtils.adaptToAccent(Appearance.m3colors.m3primary, artDominantColor), artDominantColor, 0.5)
property color colPrimaryHover: ColorUtils.mix(ColorUtils.adaptToAccent(Appearance.colors.colPrimaryHover, artDominantColor), artDominantColor, 0.3)
property color colPrimaryActive: ColorUtils.mix(ColorUtils.adaptToAccent(Appearance.colors.colPrimaryActive, artDominantColor), artDominantColor, 0.3)
property color colSecondaryContainer: ColorUtils.mix(Appearance.m3colors.m3secondaryContainer, artDominantColor, 0.3)
property color colSecondaryContainerHover: ColorUtils.mix(Appearance.colors.colSecondaryContainerHover, artDominantColor, 0.3)
property color colSecondaryContainerActive: ColorUtils.mix(Appearance.colors.colSecondaryContainerActive, artDominantColor, 0.3)
property color colOnPrimary: ColorUtils.mix(ColorUtils.adaptToAccent(Appearance.m3colors.m3onPrimary, artDominantColor), artDominantColor, 0.5)
property color colOnSecondaryContainer: ColorUtils.mix(Appearance.m3colors.m3onSecondaryContainer, artDominantColor, 0.2)
}
Rectangle { // Background
id: background
anchors.fill: parent
anchors.margins: Appearance.sizes.elevationMargin
color: blendedColors.colLayer0
radius: root.popupRounding
layer.enabled: true
layer.effect: OpacityMask {
maskSource: Rectangle {
width: background.width
height: background.height
radius: background.radius
}
}
Image {
id: blurredArt
anchors.fill: parent
visible: true
source: playerController.artUrl
sourceSize.width: background.width
sourceSize.height: background.height
fillMode: Image.PreserveAspectCrop
cache: false
antialiasing: true
asynchronous: true
layer.enabled: true
layer.effect: MultiEffect {
source: blurredArt
anchors.fill: blurredArt
saturation: 0.2
blurEnabled: true
blurMax: 100
blur: 1
}
Rectangle {
anchors.fill: parent
color: ColorUtils.transparentize(blendedColors.colLayer0, 0.25)
radius: root.popupRounding
}
}
RowLayout {
anchors.fill: parent
anchors.margins: root.contentPadding
spacing: 15
Rectangle { // Art background
id: artBackground
Layout.fillHeight: true
implicitWidth: height
radius: root.artRounding
color: blendedColors.colLayer1
layer.enabled: true
layer.effect: OpacityMask {
maskSource: Rectangle {
width: artBackground.width
height: artBackground.height
radius: artBackground.radius
}
}
Image { // Art image
id: mediaArt
property int size: parent.height
anchors.fill: parent
source: playerController.artUrl
fillMode: Image.PreserveAspectCrop
cache: false
antialiasing: true
asynchronous: true
width: size
height: size
sourceSize.width: size
sourceSize.height: size
}
}
ColumnLayout { // Info & controls
Layout.fillHeight: true
spacing: 2
StyledText {
id: trackTitle
Layout.fillWidth: true
font.pixelSize: Appearance.font.pixelSize.large
color: blendedColors.colOnLayer0
elide: Text.ElideRight
text: StringUtils.cleanMusicTitle(playerController.player?.trackTitle) || "Untitled"
}
StyledText {
id: trackArtist
Layout.fillWidth: true
font.pixelSize: Appearance.font.pixelSize.smaller
color: blendedColors.colSubtext
elide: Text.ElideRight
text: playerController.player?.trackArtist
}
Item { Layout.fillHeight: true }
Item {
Layout.fillWidth: true
implicitHeight: trackTime.implicitHeight + sliderRow.implicitHeight
StyledText {
id: trackTime
anchors.bottom: sliderRow.top
anchors.bottomMargin: 5
anchors.left: parent.left
font.pixelSize: Appearance.font.pixelSize.small
color: blendedColors.colSubtext
elide: Text.ElideRight
text: `${StringUtils.friendlyTimeForSeconds(playerController.player?.position)} / ${StringUtils.friendlyTimeForSeconds(playerController.player?.length)}`
}
RowLayout {
id: sliderRow
anchors {
bottom: parent.bottom
left: parent.left
right: parent.right
}
TrackChangeButton {
iconName: "skip_previous"
onClicked: playerController.player?.previous()
}
StyledProgressBar {
id: slider
Layout.fillWidth: true
highlightColor: blendedColors.colPrimary
trackColor: blendedColors.colSecondaryContainer
value: playerController.player?.position / playerController.player?.length
}
TrackChangeButton {
iconName: "skip_next"
onClicked: playerController.player?.next()
}
}
Button {
id: playPauseButton
anchors.right: parent.right
anchors.bottom: sliderRow.top
anchors.bottomMargin: 5
implicitWidth: 44
implicitHeight: 44
onClicked: playerController.player.togglePlaying();
PointingHandInteraction {}
background: Rectangle {
color: playerController.player?.isPlaying ?
(playPauseButton.pressed ? blendedColors.colPrimaryActive :
playPauseButton.hovered ? blendedColors.colPrimaryHover :
blendedColors.colPrimary) :
(playPauseButton.pressed ? blendedColors.colSecondaryContainerActive :
playPauseButton.hovered ? blendedColors.colSecondaryContainerHover :
blendedColors.colSecondaryContainer)
radius: Appearance.rounding.full
Behavior on color {
animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this)
}
}
contentItem: MaterialSymbol {
iconSize: Appearance.font.pixelSize.huge
fill: 1
horizontalAlignment: Text.AlignHCenter
color: playerController.player?.isPlaying ? blendedColors.colOnPrimary : blendedColors.colOnSecondaryContainer
text: playerController.player?.isPlaying ? "pause" : "play_arrow"
Behavior on color {
animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this)
}
}
}
}
}
}
}
}
@@ -7,111 +7,105 @@ import QtQuick.Controls
import QtQuick.Layouts
import 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<NotificationWidget> notificationWidgetList: []
property Component notifComponent: NotificationWidget {}
property list<NotificationWidget> 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
}
}
}
}
@@ -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
@@ -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
@@ -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
}
}
@@ -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
@@ -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
}
}
@@ -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 {
@@ -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 {
@@ -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
}
}
+183 -206
View File
@@ -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;
}
}
@@ -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)
}
}
@@ -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: {
@@ -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: {
@@ -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;
}
}
@@ -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")
}
}
}
@@ -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)
}
}
}
@@ -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 {
@@ -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
}
}
}
}
}
}
@@ -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 {
@@ -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
@@ -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;
}
}
@@ -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)
}
}
}
@@ -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)
}
}
@@ -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)
}
}
@@ -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
@@ -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"))
}
}
@@ -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)
}
}
@@ -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)
}
}
@@ -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 {
@@ -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)
}
}
@@ -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
}
}
@@ -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)
}
}
+138 -7
View File
@@ -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
+43 -30
View File
@@ -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();
}
+3 -10
View File
@@ -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++;
+2 -2
View File
@@ -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()
}
}
+4 -4
View File
@@ -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}"`)
}
}
}
+1 -1
View File
@@ -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
}
}
@@ -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
@@ -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 = {};
@@ -30,7 +30,7 @@ Singleton {
Timer {
id: delayedFileRead
interval: ConfigOptions.hacks.arbitraryRaceConditionDelay
interval: ConfigOptions?.hacks?.arbitraryRaceConditionDelay ?? 100
repeat: false
running: false
onTriggered: {
@@ -72,7 +72,7 @@ Singleton {
Timer {
id: delayedFileRead
interval: ConfigOptions.hacks.arbitraryRaceConditionDelay
interval: ConfigOptions?.hacks?.arbitraryRaceConditionDelay ?? 100
repeat: false
running: false
onTriggered: {
@@ -50,7 +50,7 @@ Singleton {
previousCpuStats = { total, idle }
}
interval = ConfigOptions.resources.updateInterval
interval = ConfigOptions?.resources?.updateInterval ?? 3000
}
}
+4
View File
@@ -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 {}
+22 -6
View File
@@ -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
-6
View File
@@ -1,6 +0,0 @@
#!/usr/bin/bash
WORKSPACES="$(hyprctl monitors -j | jq -r 'map(.activeWorkspace.id)')"
WINDOWS="$(hyprctl clients -j | jq -r --argjson workspaces "$WORKSPACES" 'map(select([.workspace.id] | inside($workspaces)))' )"
GEOM=$(echo "$WINDOWS" | jq -r '.[] | "\(.at[0]),\(.at[1]) \(.size[0])x\(.size[1])"' | slurp -f '%x %y %w %h')
wayshot -s "$GEOM" --stdout ${#:+"$@"}
+2 -2
View File
@@ -2,8 +2,8 @@
## Not ready, but feel free to try it. It's simple:
- **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
@@ -7,6 +7,6 @@ license=(None)
depends=(
polkit-gnome
gnome-keyring
gnome-control-center
blueberry networkmanager
networkmanager
better-control-git
)