diff --git a/.config/hypr/hypridle.conf b/.config/hypr/hypridle.conf index bb6ecccd2..a77bbb242 100644 --- a/.config/hypr/hypridle.conf +++ b/.config/hypr/hypridle.conf @@ -1,5 +1,5 @@ -# $lock_cmd = hyprctl dispatch global quickshell:lock & pidof qs quickshell hyprlock || hyprlock -$lock_cmd = pidof hyprlock || hyprlock +$lock_cmd = hyprctl dispatch global quickshell:lock & pidof qs quickshell hyprlock || hyprlock +# $lock_cmd = pidof hyprlock || hyprlock $suspend_cmd = systemctl suspend || loginctl suspend general { diff --git a/.config/hypr/hyprland/env.conf b/.config/hypr/hyprland/env.conf index 6f1316304..62e931fd7 100644 --- a/.config/hypr/hyprland/env.conf +++ b/.config/hypr/hyprland/env.conf @@ -22,3 +22,6 @@ env = XDG_MENU_PREFIX, plasma- # ######## Virtual envrionment ######### env = ILLOGICAL_IMPULSE_VIRTUAL_ENV, ~/.local/state/quickshell/.venv + +# ######## Terminal application ######### +env = TERMINAL,kitty -1 diff --git a/.config/hypr/hyprland/keybinds.conf b/.config/hypr/hyprland/keybinds.conf index 5a3140069..c7e953d94 100644 --- a/.config/hypr/hyprland/keybinds.conf +++ b/.config/hypr/hyprland/keybinds.conf @@ -5,9 +5,12 @@ ##! Shell # These absolutely need to be on top, or they won't work consistently bindid = Super, Super_L, Toggle overview, global, quickshell:overviewToggleRelease # Toggle overview/launcher +bindid = Super, Super_R, Toggle overview, global, quickshell:overviewToggleRelease # [hidden] Toggle overview/launcher bind = Super, Super_L, exec, qs -c $qsConfig ipc call TEST_ALIVE || pkill fuzzel || fuzzel # [hidden] Launcher (fallback) +bind = Super, Super_R, exec, qs -c $qsConfig ipc call TEST_ALIVE || pkill fuzzel || fuzzel # [hidden] Launcher (fallback) binditn = Super, catchall, global, quickshell:overviewToggleReleaseInterrupt # [hidden] bind = Ctrl, Super_L, global, quickshell:overviewToggleReleaseInterrupt # [hidden] +bind = Ctrl, Super_R, global, quickshell:overviewToggleReleaseInterrupt # [hidden] bind = Super, mouse:272, global, quickshell:overviewToggleReleaseInterrupt # [hidden] bind = Super, mouse:273, global, quickshell:overviewToggleReleaseInterrupt # [hidden] bind = Super, mouse:274, global, quickshell:overviewToggleReleaseInterrupt # [hidden] @@ -18,6 +21,7 @@ bind = Super, mouse_up, global, quickshell:overviewToggleReleaseInterrupt # [hi bind = Super, mouse_down,global, quickshell:overviewToggleReleaseInterrupt # [hidden] bindit = ,Super_L, global, quickshell:workspaceNumber # [hidden] +bindit = ,Super_R, global, quickshell:workspaceNumber # [hidden] bindd = Super, V, Clipboard history >> clipboard, global, quickshell:overviewClipboardToggle # Clipboard history >> clipboard bindd = Super, Period, Emoji >> clipboard, global, quickshell:overviewEmojiToggle # Emoji >> clipboard bindd = Super, Tab, Toggle overview, global, quickshell:overviewToggle # [hidden] Toggle overview/launcher (alt) @@ -201,10 +205,10 @@ bindl= ,XF86AudioPlay, exec, playerctl play-pause # [hidden] bindl= ,XF86AudioPause, exec, playerctl play-pause # [hidden] ##! Apps -bind = Super, Return, exec, ~/.config/hypr/hyprland/scripts/launch_first_available.sh "kitty -1" "foot" "alacritty" "wezterm" "konsole" "kgx" "uxterm" "xterm" # Terminal -bind = Super, T, exec, ~/.config/hypr/hyprland/scripts/launch_first_available.sh "kitty -1" "foot" "alacritty" "wezterm" "konsole" "kgx" "uxterm" "xterm" # [hidden] Kitty (terminal) (alt) -bind = Ctrl+Alt, T, exec, ~/.config/hypr/hyprland/scripts/launch_first_available.sh "kitty -1" "foot" "alacritty" "wezterm" "konsole" "kgx" "uxterm" "xterm" # [hidden] Kitty (for Ubuntu people) -bind = Super, E, exec, ~/.config/hypr/hyprland/scripts/launch_first_available.sh "dolphin" "nautilus" "nemo" "thunar" "kitty -1 fish -c yazi" # File manager +bind = Super, Return, exec, ~/.config/hypr/hyprland/scripts/launch_first_available.sh "$TERMINAL" "kitty -1" "foot" "alacritty" "wezterm" "konsole" "kgx" "uxterm" "xterm" # Terminal +bind = Super, T, exec, ~/.config/hypr/hyprland/scripts/launch_first_available.sh "$TERMINAL" "kitty -1" "foot" "alacritty" "wezterm" "konsole" "kgx" "uxterm" "xterm" # [hidden] (terminal) (alt) +bind = Ctrl+Alt, T, exec, ~/.config/hypr/hyprland/scripts/launch_first_available.sh "$TERMINAL" "kitty -1" "foot" "alacritty" "wezterm" "konsole" "kgx" "uxterm" "xterm" # [hidden] (terminal) (for Ubuntu people) +bind = Super, E, exec, ~/.config/hypr/hyprland/scripts/launch_first_available.sh "dolphin" "nautilus" "nemo" "thunar" "$TERMINAL" "kitty -1 fish -c yazi" # File manager bind = Super, W, exec, ~/.config/hypr/hyprland/scripts/launch_first_available.sh "google-chrome-stable" "zen-browser" "firefox" "brave" "chromium" "microsoft-edge-stable" "opera" "librewolf" # Browser bind = Super, C, exec, ~/.config/hypr/hyprland/scripts/launch_first_available.sh "code" "codium" "cursor" "zed" "zedit" "zeditor" "kate" "gnome-text-editor" "emacs" "command -v nvim && kitty -1 nvim" # Code editor bind = Super+Shift, W, exec, ~/.config/hypr/hyprland/scripts/launch_first_available.sh "wps" "onlyoffice-desktopeditors" # Office software diff --git a/.config/hypr/hyprland/scripts/launch_first_available.sh b/.config/hypr/hyprland/scripts/launch_first_available.sh index 499947a9d..31dd23a55 100755 --- a/.config/hypr/hyprland/scripts/launch_first_available.sh +++ b/.config/hypr/hyprland/scripts/launch_first_available.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash for cmd in "$@"; do + [[ -z "$cmd" ]] && continue eval "command -v ${cmd%% *}" >/dev/null 2>&1 || continue eval "$cmd" & exit done -exit 1 diff --git a/.config/quickshell/ii/GlobalStates.qml b/.config/quickshell/ii/GlobalStates.qml index b3a124eb6..3f7468f4c 100644 --- a/.config/quickshell/ii/GlobalStates.qml +++ b/.config/quickshell/ii/GlobalStates.qml @@ -17,11 +17,13 @@ Singleton { property bool osdVolumeOpen: false property bool oskOpen: false property bool overviewOpen: false - property bool sessionOpen: false - property bool workspaceShowNumbers: false - property bool superReleaseMightTrigger: true property bool screenLocked: false property bool screenLockContainsCharacters: false + property bool screenUnlockFailed: false + property bool sessionOpen: false + property bool superDown: false + property bool superReleaseMightTrigger: true + property bool workspaceShowNumbers: false property real screenZoom: 1 onScreenZoomChanged: { @@ -31,31 +33,15 @@ Singleton { animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this) } - // When user is not reluctant while pressing super, they probably don't need to see workspace numbers - onSuperReleaseMightTriggerChanged: { - workspaceShowNumbersTimer.stop() - } - - Timer { - id: workspaceShowNumbersTimer - interval: Config.options.bar.workspaces.showNumberDelay - // interval: 0 - repeat: false - onTriggered: { - workspaceShowNumbers = true - } - } - GlobalShortcut { name: "workspaceNumber" description: "Hold to show workspace numbers, release to show icons" onPressed: { - workspaceShowNumbersTimer.start() + root.superDown = true } onReleased: { - workspaceShowNumbersTimer.stop() - workspaceShowNumbers = false + root.superDown = false } } diff --git a/.config/quickshell/ii/modules/background/Background.qml b/.config/quickshell/ii/modules/background/Background.qml index f960adf8c..b06b6294f 100644 --- a/.config/quickshell/ii/modules/background/Background.qml +++ b/.config/quickshell/ii/modules/background/Background.qml @@ -26,13 +26,13 @@ Variants { required property var modelData // Hide when fullscreen - readonly property Toplevel activeWindow: ToplevelManager.activeToplevel - property bool focusingThisMonitor: HyprlandData.activeWorkspace?.monitor == monitor.name - visible: !(activeWindow?.fullscreen && activeWindow?.activated && focusingThisMonitor) + property list workspacesForMonitor: Hyprland.workspaces.values.filter(workspace=>workspace.monitor && workspace.monitor.name == monitor.name) + property var activeWorkspaceWithFullscreen: workspacesForMonitor.filter(workspace=>((workspace.toplevels.values.filter(window=>window.wayland.fullscreen)[0] != undefined) && workspace.active))[0] + visible: !(activeWorkspaceWithFullscreen != undefined) // Workspaces property HyprlandMonitor monitor: Hyprland.monitorFor(modelData) - property list relevantWindows: HyprlandData.windowList.filter(win => win.monitor == monitor.id && win.workspace.id >= 0).sort((a, b) => a.workspace.id - b.workspace.id) + property list relevantWindows: HyprlandData.windowList.filter(win => win.monitor == monitor?.id && win.workspace.id >= 0).sort((a, b) => a.workspace.id - b.workspace.id) property int firstWorkspaceId: relevantWindows[0]?.workspace.id || 1 property int lastWorkspaceId: relevantWindows[relevantWindows.length - 1]?.workspace.id || 10 // Wallpaper @@ -148,8 +148,13 @@ Variants { // Wallpaper Image { id: wallpaper - visible: !bgRoot.wallpaperIsVideo + visible: opacity > 0 + opacity: (status === Image.Ready && !bgRoot.wallpaperIsVideo) ? 1 : 0 + Behavior on opacity { + animation: Appearance.animation.elementMoveEnter.numberAnimation.createObject(this) + } property real value // 0 to 1, for offset + asynchronous: true value: { // Range = groups that workspaces span on const chunkSize = Config?.options.bar.workspaces.shown ?? 10; @@ -278,8 +283,8 @@ Variants { Behavior on opacity { animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this) } - text: "Enter password" - color: CF.ColorUtils.transparentize(bgRoot.colText, 0.3) + text: GlobalStates.screenUnlockFailed ? Translation.tr("Incorrect password") : Translation.tr("Enter password") + color: GlobalStates.screenUnlockFailed ? Appearance.colors.colError : bgRoot.colText font { pixelSize: Appearance.font.pixelSize.normal } diff --git a/.config/quickshell/ii/modules/bar/ActiveWindow.qml b/.config/quickshell/ii/modules/bar/ActiveWindow.qml index c571b1d02..9e7eeba1f 100644 --- a/.config/quickshell/ii/modules/bar/ActiveWindow.qml +++ b/.config/quickshell/ii/modules/bar/ActiveWindow.qml @@ -45,7 +45,7 @@ Item { elide: Text.ElideRight text: root.focusingThisMonitor && root.activeWindow?.activated && root.biggestWindow ? root.activeWindow?.title : - (root.biggestWindow?.title) ?? `${Translation.tr("Workspace")} ${monitor?.activeWorkspace?.id}` + (root.biggestWindow?.title) ?? `${Translation.tr("Workspace")} ${monitor?.activeWorkspace?.id ?? 1}` } } diff --git a/.config/quickshell/ii/modules/bar/Bar.qml b/.config/quickshell/ii/modules/bar/Bar.qml index c08133ad6..c19fbd311 100644 --- a/.config/quickshell/ii/modules/bar/Bar.qml +++ b/.config/quickshell/ii/modules/bar/Bar.qml @@ -39,8 +39,30 @@ Scope { property real useShortenedForm: (Appearance.sizes.barHellaShortenScreenWidthThreshold >= screen.width) ? 2 : (Appearance.sizes.barShortenScreenWidthThreshold >= screen.width) ? 1 : 0 readonly property int centerSideModuleWidth: (useShortenedForm == 2) ? Appearance.sizes.barCenterSideModuleWidthHellaShortened : (useShortenedForm == 1) ? Appearance.sizes.barCenterSideModuleWidthShortened : Appearance.sizes.barCenterSideModuleWidth + Timer { + id: showBarTimer + interval: (Config?.options.bar.autoHide.showWhenPressingSuper.delay ?? 100) + repeat: false + onTriggered: { + barRoot.superShow = true + } + } + Connections { + target: GlobalStates + function onSuperDownChanged() { + if (!Config?.options.bar.autoHide.showWhenPressingSuper.enable) return; + if (GlobalStates.superDown) showBarTimer.restart(); + else { + showBarTimer.stop(); + barRoot.superShow = false; + } + } + } + property bool superShow: false + property bool mustShow: hoverRegion.containsMouse || superShow exclusionMode: ExclusionMode.Ignore - exclusiveZone: Appearance.sizes.baseBarHeight + (Config.options.bar.cornerStyle === 1 ? Appearance.sizes.hyprlandGapsOut : 0) + exclusiveZone: (Config?.options.bar.autoHide.enable && (!mustShow || !Config?.options.bar.autoHide.pushWindows)) ? 0 : + Appearance.sizes.baseBarHeight + (Config.options.bar.cornerStyle === 1 ? Appearance.sizes.hyprlandGapsOut : 0) WlrLayershell.namespace: "quickshell:bar" implicitHeight: Appearance.sizes.barHeight + Appearance.rounding.screenRounding mask: Region { @@ -55,90 +77,116 @@ Scope { right: true } - BarContent { - id: barContent - - anchors { - right: parent.right - left: parent.left - top: parent.top - bottom: undefined - } - implicitHeight: Appearance.sizes.barHeight + MouseArea { + id: hoverRegion + hoverEnabled: true + anchors.fill: parent - states: State { - name: "bottom" - when: Config.options.bar.bottom - AnchorChanges { - target: barContent - anchors { - right: parent.right - left: parent.left - top: undefined - bottom: parent.bottom + BarContent { + id: barContent + + implicitHeight: Appearance.sizes.barHeight + anchors { + right: parent.right + left: parent.left + top: parent.top + bottom: undefined + topMargin: (Config?.options.bar.autoHide.enable && !mustShow) ? -Appearance.sizes.barHeight + 1 : 0 + bottomMargin: 0 + } + Behavior on anchors.topMargin { + animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this) + } + Behavior on anchors.bottomMargin { + animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this) + } + + states: State { + name: "bottom" + when: Config.options.bar.bottom + AnchorChanges { + target: barContent + anchors { + right: parent.right + left: parent.left + top: undefined + bottom: parent.bottom + } + } + PropertyChanges { + target: barContent + anchors.topMargin: 0 + anchors.bottomMargin: (Config?.options.bar.autoHide.enable && !mustShow) ? -Appearance.sizes.barHeight + 1 : 0 } } } - } - // Round decorators - Loader { - id: roundDecorators - anchors { - left: parent.left - right: parent.right - } - y: Appearance.sizes.barHeight - width: parent.width - height: Appearance.rounding.screenRounding - active: showBarBackground && Config.options.bar.cornerStyle === 0 // Hug - - states: State { - name: "bottom" - when: Config.options.bar.bottom - PropertyChanges { - roundDecorators.y: 0 + // Round decorators + Loader { + id: roundDecorators + anchors { + left: parent.left + right: parent.right + top: barContent.bottom + bottom: undefined } - } + width: parent.width + height: Appearance.rounding.screenRounding + active: showBarBackground && Config.options.bar.cornerStyle === 0 // Hug - sourceComponent: Item { - implicitHeight: Appearance.rounding.screenRounding - RoundCorner { - id: leftCorner - anchors { - top: parent.top - bottom: parent.bottom - left: parent.left - } - - implicitSize: Appearance.rounding.screenRounding - color: showBarBackground ? Appearance.colors.colLayer0 : "transparent" - - corner: RoundCorner.CornerEnum.TopLeft - states: State { - name: "bottom" - when: Config.options.bar.bottom - PropertyChanges { - leftCorner.corner: RoundCorner.CornerEnum.BottomLeft + states: State { + name: "bottom" + when: Config.options.bar.bottom + AnchorChanges { + target: roundDecorators + anchors { + right: parent.right + left: parent.left + top: undefined + bottom: barContent.top } } } - RoundCorner { - id: rightCorner - anchors { - right: parent.right - top: !Config.options.bar.bottom ? parent.top : undefined - bottom: Config.options.bar.bottom ? parent.bottom : undefined - } - implicitSize: Appearance.rounding.screenRounding - color: showBarBackground ? Appearance.colors.colLayer0 : "transparent" - corner: RoundCorner.CornerEnum.TopRight - states: State { - name: "bottom" - when: Config.options.bar.bottom - PropertyChanges { - rightCorner.corner: RoundCorner.CornerEnum.BottomRight + sourceComponent: Item { + implicitHeight: Appearance.rounding.screenRounding + RoundCorner { + id: leftCorner + anchors { + top: parent.top + bottom: parent.bottom + left: parent.left + } + + implicitSize: Appearance.rounding.screenRounding + color: showBarBackground ? Appearance.colors.colLayer0 : "transparent" + + corner: RoundCorner.CornerEnum.TopLeft + states: State { + name: "bottom" + when: Config.options.bar.bottom + PropertyChanges { + leftCorner.corner: RoundCorner.CornerEnum.BottomLeft + } + } + } + RoundCorner { + id: rightCorner + anchors { + right: parent.right + top: !Config.options.bar.bottom ? parent.top : undefined + bottom: Config.options.bar.bottom ? parent.bottom : undefined + } + implicitSize: Appearance.rounding.screenRounding + color: showBarBackground ? Appearance.colors.colLayer0 : "transparent" + + corner: RoundCorner.CornerEnum.TopRight + states: State { + name: "bottom" + when: Config.options.bar.bottom + PropertyChanges { + rightCorner.corner: RoundCorner.CornerEnum.BottomRight + } } } } diff --git a/.config/quickshell/ii/modules/bar/UtilButtons.qml b/.config/quickshell/ii/modules/bar/UtilButtons.qml index a9ecbb5a4..98ee4f6cd 100644 --- a/.config/quickshell/ii/modules/bar/UtilButtons.qml +++ b/.config/quickshell/ii/modules/bar/UtilButtons.qml @@ -129,9 +129,9 @@ Item { horizontalAlignment: Qt.AlignHCenter fill: 0 text: switch(PowerProfiles.profile) { - case PowerProfile.PowerSaver: return "battery_saver" - case PowerProfile.Balanced: return "dynamic_form" - case PowerProfile.Performance: return "speed" + case PowerProfile.PowerSaver: return "energy_savings_leaf" + case PowerProfile.Balanced: return "settings_slow_motion" + case PowerProfile.Performance: return "local_fire_department" } iconSize: Appearance.font.pixelSize.large color: Appearance.colors.colOnLayer2 diff --git a/.config/quickshell/ii/modules/bar/Workspaces.qml b/.config/quickshell/ii/modules/bar/Workspaces.qml index b07dc9d18..b97727a3c 100644 --- a/.config/quickshell/ii/modules/bar/Workspaces.qml +++ b/.config/quickshell/ii/modules/bar/Workspaces.qml @@ -28,6 +28,30 @@ Item { property real workspaceIconMarginShrinked: -4 property int workspaceIndexInGroup: (monitor?.activeWorkspace?.id - 1) % Config.options.bar.workspaces.shown + property bool showNumbers: false + Timer { + id: showNumbersTimer + interval: (Config?.options.bar.autoHide.showWhenPressingSuper.delay ?? 100) + repeat: false + onTriggered: { + root.showNumbers = true + } + } + Connections { + target: GlobalStates + function onSuperDownChanged() { + if (!Config?.options.bar.autoHide.showWhenPressingSuper.enable) return; + if (GlobalStates.superDown) showNumbersTimer.restart(); + else { + showNumbersTimer.stop(); + root.showNumbers = false; + } + } + function onSuperReleaseMightTriggerChanged() { + showNumbersTimer.stop() + } + } + // Function to update workspaceOccupied function updateWorkspaceOccupied() { workspaceOccupied = Array.from({ length: Config.options.bar.workspaces.shown }, (_, i) => { @@ -176,9 +200,9 @@ Item { property var mainAppIconSource: Quickshell.iconPath(AppSearch.guessIcon(biggestWindow?.class), "image-missing") StyledText { // Workspace number text - opacity: GlobalStates.workspaceShowNumbers - || ((Config.options?.bar.workspaces.alwaysShowNumbers && (!Config.options?.bar.workspaces.showAppIcons || !workspaceButtonBackground.biggestWindow || GlobalStates.workspaceShowNumbers)) - || (GlobalStates.workspaceShowNumbers && !Config.options?.bar.workspaces.showAppIcons) + opacity: root.showNumbers + || ((Config.options?.bar.workspaces.alwaysShowNumbers && (!Config.options?.bar.workspaces.showAppIcons || !workspaceButtonBackground.biggestWindow || root.showNumbers)) + || (root.showNumbers && !Config.options?.bar.workspaces.showAppIcons) ) ? 1 : 0 z: 3 @@ -200,7 +224,7 @@ Item { Rectangle { // Dot instead of ws number id: wsDot opacity: (Config.options?.bar.workspaces.alwaysShowNumbers - || GlobalStates.workspaceShowNumbers + || root.showNumbers || (Config.options?.bar.workspaces.showAppIcons && workspaceButtonBackground.biggestWindow) ) ? 0 : 1 visible: opacity > 0 @@ -222,20 +246,20 @@ Item { width: workspaceButtonWidth height: workspaceButtonWidth opacity: !Config.options?.bar.workspaces.showAppIcons ? 0 : - (workspaceButtonBackground.biggestWindow && !GlobalStates.workspaceShowNumbers && Config.options?.bar.workspaces.showAppIcons) ? + (workspaceButtonBackground.biggestWindow && !root.showNumbers && Config.options?.bar.workspaces.showAppIcons) ? 1 : workspaceButtonBackground.biggestWindow ? workspaceIconOpacityShrinked : 0 visible: opacity > 0 IconImage { id: mainAppIcon anchors.bottom: parent.bottom anchors.right: parent.right - anchors.bottomMargin: (!GlobalStates.workspaceShowNumbers && Config.options?.bar.workspaces.showAppIcons) ? + anchors.bottomMargin: (!root.showNumbers && Config.options?.bar.workspaces.showAppIcons) ? (workspaceButtonWidth - workspaceIconSize) / 2 : workspaceIconMarginShrinked - anchors.rightMargin: (!GlobalStates.workspaceShowNumbers && Config.options?.bar.workspaces.showAppIcons) ? + anchors.rightMargin: (!root.showNumbers && Config.options?.bar.workspaces.showAppIcons) ? (workspaceButtonWidth - workspaceIconSize) / 2 : workspaceIconMarginShrinked source: workspaceButtonBackground.mainAppIconSource - implicitSize: (!GlobalStates.workspaceShowNumbers && Config.options?.bar.workspaces.showAppIcons) ? workspaceIconSize : workspaceIconSizeShrinked + implicitSize: (!root.showNumbers && Config.options?.bar.workspaces.showAppIcons) ? workspaceIconSize : workspaceIconSizeShrinked Behavior on opacity { animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this) diff --git a/.config/quickshell/ii/modules/bar/weather/WeatherBar.qml b/.config/quickshell/ii/modules/bar/weather/WeatherBar.qml index c49a1b7d1..63fd1f5d4 100644 --- a/.config/quickshell/ii/modules/bar/weather/WeatherBar.qml +++ b/.config/quickshell/ii/modules/bar/weather/WeatherBar.qml @@ -31,7 +31,7 @@ MouseArea { visible: true font.pixelSize: Appearance.font.pixelSize.small color: Appearance.colors.colOnLayer1 - text: Weather.data.temp + text: Weather.data?.temp ?? "--°" Layout.alignment: Qt.AlignVCenter } } diff --git a/.config/quickshell/ii/modules/common/Appearance.qml b/.config/quickshell/ii/modules/common/Appearance.qml index e71eb4274..f7e3a9eb2 100644 --- a/.config/quickshell/ii/modules/common/Appearance.qml +++ b/.config/quickshell/ii/modules/common/Appearance.qml @@ -15,9 +15,26 @@ Singleton { property QtObject sizes property string syntaxHighlightingTheme - // Extremely conservative transparency values for consistency and readability - property real transparency: Config.options?.appearance.transparency ? (m3colors.darkmode ? 0.1 : 0.07) : 0 - property real contentTransparency: Config.options?.appearance.transparency ? (m3colors.darkmode ? 0.55 : 0.55) : 0 + // Transparency. The quadratic functions were derived from analysis of hand-picked transparency values. + ColorQuantizer { + id: wallColorQuant + source: Qt.resolvedUrl(Config.options.background.wallpaperPath) + depth: 0 // 2^0 = 1 color + rescaleSize: 10 + } + property real wallpaperVibrancy: (wallColorQuant.colors[0]?.hslSaturation + wallColorQuant.colors[0]?.hslLightness) / 2 + property real autoBackgroundTransparency: { // y = 0.5768x^2 - 0.759x + 0.2896 + let x = wallpaperVibrancy + let y = 0.5768 * (x * x) - 0.759 * (x) + 0.2896 + return Math.max(0, Math.min(0.22, y)) + } + property real autoContentTransparency: { // y = -10.1734x^2 + 3.4457x + 0.1872 + let x = autoBackgroundTransparency + let y = -10.1734 * (x * x) + 3.4457 * (x) + 0.1872 + return Math.max(0, Math.min(0.6, y)) + } + property real backgroundTransparency: Config?.options.appearance.transparency.enable ? Config?.options.appearance.transparency.automatic ? autoBackgroundTransparency : Config?.options.appearance.transparency.backgroundTransparency : 0 + property real contentTransparency: Config?.options.appearance.transparency.enable ? Config?.options.appearance.transparency.automatic ? autoContentTransparency : Config?.options.appearance.transparency.contentTransparency : 0 m3colors: QtObject { property bool darkmode: false @@ -100,26 +117,30 @@ Singleton { colors: QtObject { property color colSubtext: m3colors.m3outline - property color colLayer0: ColorUtils.mix(ColorUtils.transparentize(m3colors.m3background, root.transparency), m3colors.m3primary, Config.options.appearance.extraBackgroundTint ? 0.99 : 1) + property color colLayer0: ColorUtils.mix(ColorUtils.transparentize(m3colors.m3background, root.backgroundTransparency), m3colors.m3primary, Config.options.appearance.extraBackgroundTint ? 0.99 : 1) property color colOnLayer0: m3colors.m3onBackground property color colLayer0Hover: ColorUtils.transparentize(ColorUtils.mix(colLayer0, colOnLayer0, 0.9, root.contentTransparency)) property color colLayer0Active: ColorUtils.transparentize(ColorUtils.mix(colLayer0, colOnLayer0, 0.8, root.contentTransparency)) property color colLayer0Border: ColorUtils.mix(root.m3colors.m3outlineVariant, colLayer0, 0.4) - property color colLayer1: ColorUtils.transparentize(ColorUtils.mix(m3colors.m3surfaceContainerLow, m3colors.m3background, 0.8), root.contentTransparency); + property color colLayer1: ColorUtils.transparentize(m3colors.m3surfaceContainerLow, root.contentTransparency); property color colOnLayer1: m3colors.m3onSurfaceVariant; property color colOnLayer1Inactive: ColorUtils.mix(colOnLayer1, colLayer1, 0.45); - property color colLayer2: ColorUtils.transparentize(ColorUtils.mix(m3colors.m3surfaceContainer, m3colors.m3surfaceContainerHigh, 0.1), root.contentTransparency) + property color colLayer2: ColorUtils.transparentize(m3colors.m3surfaceContainer, root.contentTransparency) property color colOnLayer2: m3colors.m3onSurface; property color colOnLayer2Disabled: ColorUtils.mix(colOnLayer2, m3colors.m3background, 0.4); - property color colLayer3: ColorUtils.transparentize(ColorUtils.mix(m3colors.m3surfaceContainerHigh, m3colors.m3onSurface, 0.96), root.contentTransparency) - property color colOnLayer3: m3colors.m3onSurface; property color colLayer1Hover: ColorUtils.transparentize(ColorUtils.mix(colLayer1, colOnLayer1, 0.92), root.contentTransparency) property color colLayer1Active: ColorUtils.transparentize(ColorUtils.mix(colLayer1, colOnLayer1, 0.85), root.contentTransparency); property color colLayer2Hover: ColorUtils.transparentize(ColorUtils.mix(colLayer2, colOnLayer2, 0.90), root.contentTransparency) property color colLayer2Active: ColorUtils.transparentize(ColorUtils.mix(colLayer2, colOnLayer2, 0.80), root.contentTransparency); property color colLayer2Disabled: ColorUtils.transparentize(ColorUtils.mix(colLayer2, m3colors.m3background, 0.8), root.contentTransparency); + property color colLayer3: ColorUtils.transparentize(m3colors.m3surfaceContainerHigh, root.contentTransparency) + property color colOnLayer3: m3colors.m3onSurface; property color colLayer3Hover: ColorUtils.transparentize(ColorUtils.mix(colLayer3, colOnLayer3, 0.90), root.contentTransparency) property color colLayer3Active: ColorUtils.transparentize(ColorUtils.mix(colLayer3, colOnLayer3, 0.80), root.contentTransparency); + property color colLayer4: ColorUtils.transparentize(m3colors.m3surfaceContainerHighest, root.contentTransparency) + property color colOnLayer4: m3colors.m3onSurface; + property color colLayer4Hover: ColorUtils.transparentize(ColorUtils.mix(colLayer4, colOnLayer4, 0.90), root.contentTransparency) + property color colLayer4Active: ColorUtils.transparentize(ColorUtils.mix(colLayer4, colOnLayer4, 0.80), root.contentTransparency); property color colPrimary: m3colors.m3primary property color colOnPrimary: m3colors.m3onPrimary property color colPrimaryHover: ColorUtils.mix(colors.colPrimary, colLayer1Hover, 0.87) @@ -148,6 +169,14 @@ Singleton { property color colScrim: ColorUtils.transparentize(m3colors.m3scrim, 0.5) property color colShadow: ColorUtils.transparentize(m3colors.m3shadow, 0.7) property color colOutlineVariant: m3colors.m3outlineVariant + property color colError: m3colors.m3error + property color colErrorHover: ColorUtils.mix(m3colors.m3error, colLayer1Hover, 0.85) + property color colErrorActive: ColorUtils.mix(m3colors.m3error, colLayer1Active, 0.7) + property color colOnError: m3colors.m3onError + property color colErrorContainer: m3colors.m3errorContainer + property color colErrorContainerHover: ColorUtils.mix(m3colors.m3errorContainer, m3colors.m3onErrorContainer, 0.90) + property color colErrorContainerActive: ColorUtils.mix(m3colors.m3errorContainer, m3colors.m3onErrorContainer, 0.70) + property color colOnErrorContainer: m3colors.m3onErrorContainer } rounding: QtObject { @@ -268,7 +297,6 @@ Singleton { easing.bezierCurve: root.animation.elementMoveFast.bezierCurve }} } - property QtObject clickBounce: QtObject { property int duration: 200 property int type: Easing.BezierSpline @@ -281,7 +309,7 @@ Singleton { }} } property QtObject scroll: QtObject { - property int duration: 400 + property int duration: 200 property int type: Easing.BezierSpline property list bezierCurve: animationCurves.standardDecel } diff --git a/.config/quickshell/ii/modules/common/Config.qml b/.config/quickshell/ii/modules/common/Config.qml index bcbbd1e33..ad2d614af 100644 --- a/.config/quickshell/ii/modules/common/Config.qml +++ b/.config/quickshell/ii/modules/common/Config.qml @@ -81,7 +81,12 @@ Singleton { property JsonObject appearance: JsonObject { property bool extraBackgroundTint: true property int fakeScreenRounding: 2 // 0: None | 1: Always | 2: When not fullscreen - property bool transparency: false + property JsonObject transparency: JsonObject { + property bool enable: true + property bool automatic: true + property real backgroundTransparency: 0.11 + property real contentTransparency: 0.57 + } property JsonObject wallpaperTheming: JsonObject { property bool enableAppsAndShell: true property bool enableQtApps: true @@ -124,6 +129,14 @@ Singleton { } property JsonObject bar: JsonObject { + property JsonObject autoHide: JsonObject { + property bool enable: false + property bool pushWindows: false + property JsonObject showWhenPressingSuper: JsonObject { + property bool enable: true + property int delay: 140 + } + } property bool bottom: false // Instead of top property int cornerStyle: 0 // 0: Hug | 1: Float | 2: Plain rectangle property bool borderless: false // true for no grouping of items @@ -181,6 +194,15 @@ Singleton { property list ignoredAppRegexes: [] } + property JsonObject interactions: JsonObject { + property JsonObject scrolling: JsonObject { + property bool fasterTouchpadScroll: true // Enable faster scrolling with touchpad + property int mouseScrollDeltaThreshold: 120 // delta >= this then it gets detected as mouse scroll rather than touchpad + property int mouseScrollFactor: 120 + property int touchpadScrollFactor: 450 + } + } + property JsonObject language: JsonObject { property JsonObject translator: JsonObject { property string engine: "auto" // Run `trans -list-engines` for available engines. auto should use google @@ -198,6 +220,11 @@ Singleton { } } + property JsonObject media: JsonObject { + // Attempt to remove dupes (the aggregator playerctl one and browsers' native ones when there's plasma browser integration) + property bool filterDuplicatePlayers: true + } + property JsonObject networking: JsonObject { property string userAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36" } @@ -253,6 +280,13 @@ Singleton { // https://doc.qt.io/qt-6/qtime.html#toString property string format: "hh:mm" property string dateFormat: "ddd, dd/MM" + property JsonObject pomodoro: JsonObject { + property string alertSound: "" + property int breakTime: 300 + property int cyclesBeforeLongBreak: 4 + property int focus: 1500 + property int longBreak: 900 + } } property JsonObject windows: JsonObject { diff --git a/.config/quickshell/ii/modules/common/Persistent.qml b/.config/quickshell/ii/modules/common/Persistent.qml index abd062d73..29d41e18d 100644 --- a/.config/quickshell/ii/modules/common/Persistent.qml +++ b/.config/quickshell/ii/modules/common/Persistent.qml @@ -11,18 +11,35 @@ Singleton { property string fileName: "states.json" property string filePath: `${root.fileDir}/${root.fileName}` + Timer { + id: fileReloadTimer + interval: 100 + repeat: false + onTriggered: { + persistentStatesFileView.reload() + } + } + + Timer { + id: fileWriteTimer + interval: 100 + repeat: false + onTriggered: { + persistentStatesFileView.writeAdapter() + } + } + FileView { + id: persistentStatesFileView path: root.filePath watchChanges: true - onFileChanged: reload() - onAdapterUpdated: { - writeAdapter() - } + onFileChanged: fileReloadTimer.restart() + onAdapterUpdated: fileWriteTimer.restart() onLoadFailed: error => { console.log("Failed to load persistent states file:", error); if (error == FileViewError.FileNotFound) { - writeAdapter(); + fileWriteTimer.restart(); } } @@ -44,6 +61,20 @@ Singleton { property bool allowNsfw: false property string provider: "yandere" } + + property JsonObject timer: JsonObject { + property JsonObject pomodoro: JsonObject { + property bool running: false + property int start: 0 + property bool isBreak: false + property int cycle: 0 + } + property JsonObject stopwatch: JsonObject { + property bool running: false + property int start: 0 + property list laps: [] + } + } } } } diff --git a/.config/quickshell/ii/modules/common/widgets/ButtonGroup.qml b/.config/quickshell/ii/modules/common/widgets/ButtonGroup.qml index 7dc7a5913..e58354d44 100644 --- a/.config/quickshell/ii/modules/common/widgets/ButtonGroup.qml +++ b/.config/quickshell/ii/modules/common/widgets/ButtonGroup.qml @@ -10,6 +10,7 @@ import QtQuick.Layouts Rectangle { id: root default property alias data: rowLayout.data + property alias uniformCellSizes: rowLayout.uniformCellSizes property real spacing: 5 property real padding: 0 property int clickIndex: rowLayout.clickIndex diff --git a/.config/quickshell/ii/modules/common/widgets/NotificationActionButton.qml b/.config/quickshell/ii/modules/common/widgets/NotificationActionButton.qml index 2a737255e..4d0829620 100644 --- a/.config/quickshell/ii/modules/common/widgets/NotificationActionButton.qml +++ b/.config/quickshell/ii/modules/common/widgets/NotificationActionButton.qml @@ -12,9 +12,9 @@ RippleButton { leftPadding: 15 rightPadding: 15 buttonRadius: Appearance.rounding.small - colBackground: (urgency == NotificationUrgency.Critical) ? Appearance.colors.colSecondaryContainer : Appearance.colors.colSurfaceContainerHighest - colBackgroundHover: (urgency == NotificationUrgency.Critical) ? Appearance.colors.colSecondaryContainerHover : Appearance.colors.colSurfaceContainerHighestHover - colRipple: (urgency == NotificationUrgency.Critical) ? Appearance.colors.colSecondaryContainerActive : Appearance.colors.colSurfaceContainerHighestActive + colBackground: (urgency == NotificationUrgency.Critical) ? Appearance.colors.colSecondaryContainer : Appearance.colors.colLayer4 + colBackgroundHover: (urgency == NotificationUrgency.Critical) ? Appearance.colors.colSecondaryContainerHover : Appearance.colors.colLayer4Hover + colRipple: (urgency == NotificationUrgency.Critical) ? Appearance.colors.colSecondaryContainerActive : Appearance.colors.colLayer4Active contentItem: StyledText { horizontalAlignment: Text.AlignHCenter diff --git a/.config/quickshell/ii/modules/common/widgets/NotificationAppIcon.qml b/.config/quickshell/ii/modules/common/widgets/NotificationAppIcon.qml index 5158d6438..278691b8b 100644 --- a/.config/quickshell/ii/modules/common/widgets/NotificationAppIcon.qml +++ b/.config/quickshell/ii/modules/common/widgets/NotificationAppIcon.qml @@ -13,9 +13,9 @@ Rectangle { // App icon property var urgency: NotificationUrgency.Normal property var image: "" property real scale: 1 - property real size: 45 * scale + property real size: 38 * scale property real materialIconScale: 0.57 - property real appIconScale: 0.7 + property real appIconScale: 0.8 property real smallAppIconScale: 0.49 property real materialIconSize: size * materialIconScale property real appIconSize: size * appIconScale diff --git a/.config/quickshell/ii/modules/common/widgets/NotificationGroup.qml b/.config/quickshell/ii/modules/common/widgets/NotificationGroup.qml index d63bbb310..fbdc44daf 100644 --- a/.config/quickshell/ii/modules/common/widgets/NotificationGroup.qml +++ b/.config/quickshell/ii/modules/common/widgets/NotificationGroup.qml @@ -105,7 +105,7 @@ Item { // Notification group area id: background anchors.left: parent.left width: parent.width - color: Appearance.colors.colSurfaceContainer + color: Appearance.colors.colLayer2 radius: Appearance.rounding.normal anchors.leftMargin: root.xOffset diff --git a/.config/quickshell/ii/modules/common/widgets/NotificationItem.qml b/.config/quickshell/ii/modules/common/widgets/NotificationItem.qml index d5e9c4fa6..82367db17 100644 --- a/.config/quickshell/ii/modules/common/widgets/NotificationItem.qml +++ b/.config/quickshell/ii/modules/common/widgets/NotificationItem.qml @@ -140,8 +140,8 @@ Item { // Notification item area color: (expanded && !onlyNotification) ? (notificationObject.urgency == NotificationUrgency.Critical) ? ColorUtils.mix(Appearance.colors.colSecondaryContainer, Appearance.colors.colLayer2, 0.35) : - (Appearance.colors.colSurfaceContainerHigh) : - ColorUtils.transparentize(Appearance.colors.colSurfaceContainerHighest) + (Appearance.colors.colLayer3) : + ColorUtils.transparentize(Appearance.colors.colLayer3) implicitHeight: expanded ? (contentColumn.implicitHeight + padding * 2) : summaryRow.implicitHeight Behavior on implicitHeight { @@ -168,7 +168,7 @@ Item { // Notification item area id: summaryText visible: !root.onlyNotification font.pixelSize: root.fontSize - color: Appearance.colors.colOnLayer2 + color: Appearance.colors.colOnLayer3 elide: Text.ElideRight text: root.notificationObject.summary || "" } diff --git a/.config/quickshell/ii/modules/common/widgets/PrimaryTabButton.qml b/.config/quickshell/ii/modules/common/widgets/PrimaryTabButton.qml index 0b4b6f8fb..6c687321c 100644 --- a/.config/quickshell/ii/modules/common/widgets/PrimaryTabButton.qml +++ b/.config/quickshell/ii/modules/common/widgets/PrimaryTabButton.qml @@ -53,6 +53,7 @@ TabButton { RippleAnim { id: rippleFadeAnim + duration: rippleDuration * 2 target: ripple property: "opacity" to: 0 diff --git a/.config/quickshell/ii/modules/common/widgets/RippleButton.qml b/.config/quickshell/ii/modules/common/widgets/RippleButton.qml index 7487203ae..759bc043c 100644 --- a/.config/quickshell/ii/modules/common/widgets/RippleButton.qml +++ b/.config/quickshell/ii/modules/common/widgets/RippleButton.qml @@ -29,6 +29,7 @@ Button { property color colRipple: Appearance?.colors.colLayer1Active ?? "#D6CEE2" property color colRippleToggled: Appearance?.colors.colPrimaryActive ?? "#D6CEE2" + opacity: root.enabled ? 1 : 0.4 property color buttonColor: root.enabled ? (root.toggled ? (root.hovered ? colBackgroundToggledHover : colBackgroundToggled) : @@ -91,6 +92,7 @@ Button { RippleAnim { id: rippleFadeAnim + duration: rippleDuration * 2 target: ripple property: "opacity" to: 0 diff --git a/.config/quickshell/ii/modules/common/widgets/SecondaryTabButton.qml b/.config/quickshell/ii/modules/common/widgets/SecondaryTabButton.qml index 983dd02bc..774da7d51 100644 --- a/.config/quickshell/ii/modules/common/widgets/SecondaryTabButton.qml +++ b/.config/quickshell/ii/modules/common/widgets/SecondaryTabButton.qml @@ -51,6 +51,7 @@ TabButton { RippleAnim { id: rippleFadeAnim + duration: rippleDuration * 2 target: ripple property: "opacity" to: 0 @@ -106,13 +107,29 @@ TabButton { animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this) } - Rectangle { + Item { id: ripple - - radius: Appearance.rounding.full - color: root.colRipple + width: ripple.implicitWidth + height: ripple.implicitHeight opacity: 0 + property real implicitWidth: 0 + property real implicitHeight: 0 + visible: width > 0 && height > 0 + + Behavior on opacity { + animation: Appearance?.animation.elementMoveFast.colorAnimation.createObject(this) + } + + RadialGradient { + anchors.fill: parent + gradient: Gradient { + GradientStop { position: 0.0; color: root.colRipple } + GradientStop { position: 0.3; color: root.colRipple } + GradientStop { position: 0.5 ; color: Qt.rgba(root.colRipple.r, root.colRipple.g, root.colRipple.b, 0) } + } + } + transform: Translate { x: -ripple.width / 2 y: -ripple.height / 2 diff --git a/.config/quickshell/ii/modules/common/widgets/SelectionDialog.qml b/.config/quickshell/ii/modules/common/widgets/SelectionDialog.qml index 72da7ec5e..6044cbfea 100644 --- a/.config/quickshell/ii/modules/common/widgets/SelectionDialog.qml +++ b/.config/quickshell/ii/modules/common/widgets/SelectionDialog.qml @@ -63,7 +63,7 @@ Item { Layout.rightMargin: dialogPadding } - ListView { + StyledListView { id: choiceListView Layout.fillWidth: true Layout.fillHeight: true @@ -71,9 +71,6 @@ Item { currentIndex: root.defaultChoice !== undefined ? root.items.indexOf(root.defaultChoice) : -1 spacing: 6 - maximumFlickVelocity: 3500 - boundsBehavior: Flickable.DragOverBounds - model: ScriptModel { id: choiceModel } diff --git a/.config/quickshell/ii/modules/common/widgets/StyledFlickable.qml b/.config/quickshell/ii/modules/common/widgets/StyledFlickable.qml index 14b3af03c..c765a3fce 100644 --- a/.config/quickshell/ii/modules/common/widgets/StyledFlickable.qml +++ b/.config/quickshell/ii/modules/common/widgets/StyledFlickable.qml @@ -1,6 +1,50 @@ import QtQuick +import qs.modules.common Flickable { + id: root maximumFlickVelocity: 3500 boundsBehavior: Flickable.DragOverBounds + + property real touchpadScrollFactor: Config?.options.interactions.scrolling.touchpadScrollFactor ?? 100 + property real mouseScrollFactor: Config?.options.interactions.scrolling.mouseScrollFactor ?? 50 + property real mouseScrollDeltaThreshold: Config?.options.interactions.scrolling.mouseScrollDeltaThreshold ?? 120 + // Accumulated scroll destination so wheel deltas stack while animating + property real scrollTargetY: 0 + + MouseArea { + visible: Config?.options.interactions.scrolling.fasterTouchpadScroll + anchors.fill: parent + acceptedButtons: Qt.NoButton + onWheel: function(wheelEvent) { + const delta = wheelEvent.angleDelta.y / root.mouseScrollDeltaThreshold; + // The angleDelta.y of a touchpad is usually small and continuous, + // while that of a mouse wheel is typically in multiples of ±120. + var scrollFactor = Math.abs(wheelEvent.angleDelta.y) >= root.mouseScrollDeltaThreshold ? root.mouseScrollFactor : root.touchpadScrollFactor; + + const maxY = Math.max(0, root.contentHeight - root.height); + const base = scrollAnim.running ? root.scrollTargetY : root.contentY; + var targetY = Math.max(0, Math.min(base - delta * scrollFactor, maxY)); + + root.scrollTargetY = targetY; + root.contentY = targetY; + wheelEvent.accepted = true; + } + } + + Behavior on contentY { + NumberAnimation { + id: scrollAnim + duration: Appearance.animation.scroll.duration + easing.type: Appearance.animation.scroll.type + easing.bezierCurve: Appearance.animation.scroll.bezierCurve + } + } + + // Keep target synced when not animating (e.g., drag/flick or programmatic changes) + onContentYChanged: { + if (!scrollAnim.running) { + root.scrollTargetY = root.contentY; + } + } } diff --git a/.config/quickshell/ii/modules/common/widgets/StyledListView.qml b/.config/quickshell/ii/modules/common/widgets/StyledListView.qml index 7021f24a4..aebf35d77 100644 --- a/.config/quickshell/ii/modules/common/widgets/StyledListView.qml +++ b/.config/quickshell/ii/modules/common/widgets/StyledListView.qml @@ -14,6 +14,12 @@ ListView { property int dragIndex: -1 property real dragDistance: 0 property bool popin: true + // Accumulated scroll destination so wheel deltas stack while animating + property real scrollTargetY: 0 + + property real touchpadScrollFactor: Config?.options.interactions.scrolling.touchpadScrollFactor ?? 100 + property real mouseScrollFactor: Config?.options.interactions.scrolling.mouseScrollFactor ?? 50 + property real mouseScrollDeltaThreshold: Config?.options.interactions.scrolling.mouseScrollDeltaThreshold ?? 120 function resetDrag() { root.dragIndex = -1 @@ -23,6 +29,42 @@ ListView { maximumFlickVelocity: 3500 boundsBehavior: Flickable.DragOverBounds + MouseArea { + visible: Config?.options.interactions.scrolling.fasterTouchpadScroll + anchors.fill: parent + acceptedButtons: Qt.NoButton + onWheel: function(wheelEvent) { + const delta = wheelEvent.angleDelta.y / root.mouseScrollDeltaThreshold; + // The angleDelta.y of a touchpad is usually small and continuous, + // while that of a mouse wheel is typically in multiples of ±120. + var scrollFactor = Math.abs(wheelEvent.angleDelta.y) >= root.mouseScrollDeltaThreshold ? root.mouseScrollFactor : root.touchpadScrollFactor; + + const maxY = Math.max(0, root.contentHeight - root.height); + const base = scrollAnim.running ? root.scrollTargetY : root.contentY; + var targetY = Math.max(0, Math.min(base - delta * scrollFactor, maxY)); + + root.scrollTargetY = targetY; + root.contentY = targetY; + wheelEvent.accepted = true; + } + } + + Behavior on contentY { + NumberAnimation { + id: scrollAnim + duration: Appearance.animation.scroll.duration + easing.type: Appearance.animation.scroll.type + easing.bezierCurve: Appearance.animation.scroll.bezierCurve + } + } + + // Keep target synced when not animating (e.g., drag/flick or programmatic changes) + onContentYChanged: { + if (!scrollAnim.running) { + root.scrollTargetY = root.contentY; + } + } + add: Transition { animations: [ Appearance?.animation.elementMove.numberAnimation.createObject(this, { diff --git a/.config/quickshell/ii/modules/lock/Lock.qml b/.config/quickshell/ii/modules/lock/Lock.qml index 89d39778a..2024c19b2 100644 --- a/.config/quickshell/ii/modules/lock/Lock.qml +++ b/.config/quickshell/ii/modules/lock/Lock.qml @@ -19,6 +19,9 @@ Scope { // Unlock the screen before exiting, or the compositor will display a // fallback lock you can't interact with. GlobalStates.screenLocked = false; + + // Refocus last focused window on unlock (hack) + Quickshell.execDetached(["bash", "-c", `sleep 0.2; hyprctl --batch "dispatch togglespecialworkspace; dispatch togglespecialworkspace"`]) } } diff --git a/.config/quickshell/ii/modules/lock/LockContext.qml b/.config/quickshell/ii/modules/lock/LockContext.qml index ede61eed8..18728136d 100644 --- a/.config/quickshell/ii/modules/lock/LockContext.qml +++ b/.config/quickshell/ii/modules/lock/LockContext.qml @@ -24,7 +24,10 @@ Scope { } onCurrentTextChanged: { - showFailure = false; // Clear the failure text once the user starts typing. + if (currentText.length > 0) { + showFailure = false; + GlobalStates.screenUnlockFailed = false; + } GlobalStates.screenLockContainsCharacters = currentText.length > 0; passwordClearTimer.restart(); } @@ -58,6 +61,7 @@ Scope { root.unlocked(); } else { root.showFailure = true; + GlobalStates.screenUnlockFailed = true; } root.currentText = ""; diff --git a/.config/quickshell/ii/modules/lock/LockSurface.qml b/.config/quickshell/ii/modules/lock/LockSurface.qml index 98d1f57b5..a0b28445d 100644 --- a/.config/quickshell/ii/modules/lock/LockSurface.qml +++ b/.config/quickshell/ii/modules/lock/LockSurface.qml @@ -75,17 +75,10 @@ MouseArea { animation: Appearance.animation.elementMove.numberAnimation.createObject(this) } radius: Appearance.rounding.full - color: Appearance.colors.colLayer2 + color: Appearance.m3colors.m3surfaceContainer implicitWidth: 160 implicitHeight: 44 - StyledText { - visible: root.context.showFailure && passwordBox.text.length == 0 - anchors.centerIn: parent - text: "Incorrect" - color: Appearance.m3colors.m3error - } - StyledTextInput { id: passwordBox diff --git a/.config/quickshell/ii/modules/mediaControls/MediaControls.qml b/.config/quickshell/ii/modules/mediaControls/MediaControls.qml index 06d1a3818..2d880d010 100644 --- a/.config/quickshell/ii/modules/mediaControls/MediaControls.qml +++ b/.config/quickshell/ii/modules/mediaControls/MediaControls.qml @@ -25,7 +25,7 @@ Scope { property real artRounding: Appearance.rounding.verysmall property list visualizerPoints: [] - property bool hasPlasmaIntegration: true + property bool hasPlasmaIntegration: false Process { id: plasmaIntegrationAvailabilityCheckProc running: true @@ -35,7 +35,9 @@ Scope { } } function isRealPlayer(player) { - // return true + if (!Config.options.media.filterDuplicatePlayers) { + return true; + } return ( // Remove unecessary native buses from browsers if there's plasma integration !(hasPlasmaIntegration && player.dbusName.startsWith('org.mpris.MediaPlayer2.firefox')) && diff --git a/.config/quickshell/ii/modules/overview/Overview.qml b/.config/quickshell/ii/modules/overview/Overview.qml index 80c692b6b..89845bc6f 100644 --- a/.config/quickshell/ii/modules/overview/Overview.qml +++ b/.config/quickshell/ii/modules/overview/Overview.qml @@ -21,7 +21,7 @@ Scope { required property var modelData property string searchingText: "" readonly property HyprlandMonitor monitor: Hyprland.monitorFor(root.screen) - property bool monitorIsFocused: (Hyprland.focusedMonitor?.id == monitor.id) + property bool monitorIsFocused: (Hyprland.focusedMonitor?.id == monitor?.id) screen: modelData visible: GlobalStates.overviewOpen diff --git a/.config/quickshell/ii/modules/overview/OverviewWidget.qml b/.config/quickshell/ii/modules/overview/OverviewWidget.qml index 550d72c1a..2510a5642 100644 --- a/.config/quickshell/ii/modules/overview/OverviewWidget.qml +++ b/.config/quickshell/ii/modules/overview/OverviewWidget.qml @@ -20,7 +20,7 @@ Item { property var windows: HyprlandData.windowList property var windowByAddress: HyprlandData.windowByAddress property var windowAddresses: HyprlandData.addresses - property var monitorData: HyprlandData.monitors.find(m => m.id === root.monitor.id) + property var monitorData: HyprlandData.monitors.find(m => m.id === root.monitor?.id) property real scale: Config.options.overview.scale property color activeBorderColor: Appearance.colors.colSecondary @@ -149,14 +149,15 @@ Item { const address = `0x${toplevel.HyprlandToplevel.address}` var win = windowByAddress[address] const inWorkspaceGroup = (root.workspaceGroup * root.workspacesShown < win?.workspace?.id && win?.workspace?.id <= (root.workspaceGroup + 1) * root.workspacesShown) - const inMonitor = root.monitor.id === win.monitor - return inWorkspaceGroup && inMonitor; + return inWorkspaceGroup; }) } } delegate: OverviewWindow { id: window required property var modelData + property int monitorId: windowData?.monitor + property var monitor: HyprlandData.monitors[monitorId] property var address: `0x${modelData.HyprlandToplevel.address}` windowData: windowByAddress[address] toplevel: modelData @@ -164,9 +165,7 @@ Item { scale: root.scale availableWorkspaceWidth: root.workspaceImplicitWidth availableWorkspaceHeight: root.workspaceImplicitHeight - - property int monitorId: windowData?.monitor - property var monitor: HyprlandData.monitors[monitorId] + widgetMonitorId: root.monitor.id property bool atInitPosition: (initX == x && initY == y) diff --git a/.config/quickshell/ii/modules/overview/OverviewWindow.qml b/.config/quickshell/ii/modules/overview/OverviewWindow.qml index 8029ec4c0..856096f38 100644 --- a/.config/quickshell/ii/modules/overview/OverviewWindow.qml +++ b/.config/quickshell/ii/modules/overview/OverviewWindow.qml @@ -1,7 +1,6 @@ import qs import qs.services import qs.modules.common -import qs.modules.common.widgets import qs.modules.common.functions import Qt5Compat.GraphicalEffects import QtQuick @@ -22,6 +21,7 @@ Item { // Window property real initY: Math.max((windowData?.at[1] - (monitorData?.y ?? 0) - monitorData?.reserved[1]) * root.scale, 0) + yOffset property real xOffset: 0 property real yOffset: 0 + property int widgetMonitorId: 0 property var targetWindowWidth: windowData?.size[0] * scale property var targetWindowHeight: windowData?.size[1] * scale @@ -40,6 +40,7 @@ Item { // Window y: initY width: windowData?.size[0] * root.scale height: windowData?.size[1] * root.scale + opacity: windowData.monitor == widgetMonitorId ? 1 : 0.4 layer.enabled: true layer.effect: OpacityMask { @@ -69,6 +70,7 @@ Item { // Window captureSource: GlobalStates.overviewOpen ? root.toplevel : null live: true + // Color overlay for interactions Rectangle { anchors.fill: parent radius: Appearance.rounding.windowRounding * root.scale diff --git a/.config/quickshell/ii/modules/screenCorners/ScreenCorners.qml b/.config/quickshell/ii/modules/screenCorners/ScreenCorners.qml index 99dab8292..768032383 100644 --- a/.config/quickshell/ii/modules/screenCorners/ScreenCorners.qml +++ b/.config/quickshell/ii/modules/screenCorners/ScreenCorners.qml @@ -13,7 +13,8 @@ Scope { component CornerPanelWindow: PanelWindow { id: cornerPanelWindow - visible: (Config.options.appearance.fakeScreenRounding === 1 || (Config.options.appearance.fakeScreenRounding === 2 && !activeWindow?.fullscreen)) + property bool fullscreen + visible: (Config.options.appearance.fakeScreenRounding === 1 || (Config.options.appearance.fakeScreenRounding === 2 && !fullscreen)) property var corner exclusionMode: ExclusionMode.Ignore @@ -44,22 +45,34 @@ Scope { model: Quickshell.screens Scope { + id: monitorScope required property var modelData + property HyprlandMonitor monitor: Hyprland.monitorFor(modelData) + + // Hide when fullscreen + property list workspacesForMonitor: Hyprland.workspaces.values.filter(workspace=>workspace.monitor && workspace.monitor.name == monitor.name) + property var activeWorkspaceWithFullscreen: workspacesForMonitor.filter(workspace=>((workspace.toplevels.values.filter(window=>window.wayland.fullscreen)[0] != undefined) && workspace.active))[0] + property bool fullscreen: activeWorkspaceWithFullscreen != undefined + CornerPanelWindow { screen: modelData corner: RoundCorner.CornerEnum.TopLeft + fullscreen: monitorScope.fullscreen } CornerPanelWindow { screen: modelData corner: RoundCorner.CornerEnum.TopRight + fullscreen: monitorScope.fullscreen } CornerPanelWindow { screen: modelData corner: RoundCorner.CornerEnum.BottomLeft + fullscreen: monitorScope.fullscreen } CornerPanelWindow { screen: modelData corner: RoundCorner.CornerEnum.BottomRight + fullscreen: monitorScope.fullscreen } } } diff --git a/.config/quickshell/ii/modules/settings/InterfaceConfig.qml b/.config/quickshell/ii/modules/settings/InterfaceConfig.qml index e4b712012..88cbc6234 100644 --- a/.config/quickshell/ii/modules/settings/InterfaceConfig.qml +++ b/.config/quickshell/ii/modules/settings/InterfaceConfig.qml @@ -96,6 +96,23 @@ ContentPage { ContentSubsection { title: Translation.tr("Overall appearance") + ConfigRow { + uniform: true + ConfigSwitch { + text: Translation.tr("Automatically hide") + checked: Config.options.bar.autoHide.enable + onCheckedChanged: { + Config.options.bar.autoHide.enable = checked; + } + } + ConfigSwitch { + text: Translation.tr("Place at the bottom") + checked: Config.options.bar.bottom + onCheckedChanged: { + Config.options.bar.bottom = checked; + } + } + } ConfigRow { uniform: true ConfigSwitch { diff --git a/.config/quickshell/ii/modules/settings/StyleConfig.qml b/.config/quickshell/ii/modules/settings/StyleConfig.qml index 6eabfabdc..2c312460d 100644 --- a/.config/quickshell/ii/modules/settings/StyleConfig.qml +++ b/.config/quickshell/ii/modules/settings/StyleConfig.qml @@ -140,9 +140,9 @@ ContentPage { ConfigRow { ConfigSwitch { text: Translation.tr("Enable") - checked: Config.options.appearance.transparency + checked: Config.options.appearance.transparency.enable onCheckedChanged: { - Config.options.appearance.transparency = checked; + Config.options.appearance.transparency.enable = checked; } StyledToolTip { content: Translation.tr("Might look ass. Unsupported.") diff --git a/.config/quickshell/ii/modules/sidebarLeft/AiChat.qml b/.config/quickshell/ii/modules/sidebarLeft/AiChat.qml index dcbff893e..3817afe50 100644 --- a/.config/quickshell/ii/modules/sidebarLeft/AiChat.qml +++ b/.config/quickshell/ii/modules/sidebarLeft/AiChat.qml @@ -282,6 +282,9 @@ Inline w/ backslash and round brackets \\(e^{i\\pi} + 1 = 0\\) spacing: 10 popin: false + touchpadScrollFactor: Config.options.interactions.scrolling.touchpadScrollFactor * 1.4 + mouseScrollFactor: Config.options.interactions.scrolling.mouseScrollFactor * 1.4 + property int lastResponseLength: 0 clip: true @@ -296,15 +299,6 @@ Inline w/ backslash and round brackets \\(e^{i\\pi} + 1 = 0\\) add: null // Prevent function calls from being janky - Behavior on contentY { - NumberAnimation { - id: scrollAnim - duration: Appearance.animation.scroll.duration - easing.type: Appearance.animation.scroll.type - easing.bezierCurve: Appearance.animation.scroll.bezierCurve - } - } - model: ScriptModel { values: Ai.messageIDs.filter(id => { const message = Ai.messageByID[id]; diff --git a/.config/quickshell/ii/modules/sidebarLeft/Anime.qml b/.config/quickshell/ii/modules/sidebarLeft/Anime.qml index a72837725..97dabd942 100644 --- a/.config/quickshell/ii/modules/sidebarLeft/Anime.qml +++ b/.config/quickshell/ii/modules/sidebarLeft/Anime.qml @@ -137,6 +137,9 @@ Item { anchors.fill: parent spacing: 10 + touchpadScrollFactor: Config.options.interactions.scrolling.touchpadScrollFactor * 1.4 + mouseScrollFactor: Config.options.interactions.scrolling.mouseScrollFactor * 1.4 + property int lastResponseLength: 0 clip: true @@ -149,15 +152,6 @@ Item { } } - Behavior on contentY { - NumberAnimation { - id: scrollAnim - duration: Appearance.animation.scroll.duration - easing.type: Appearance.animation.scroll.type - easing.bezierCurve: Appearance.animation.scroll.bezierCurve - } - } - model: ScriptModel { values: { if(root.responses.length > booruResponseListView.lastResponseLength) { diff --git a/.config/quickshell/ii/modules/sidebarLeft/anime/BooruImage.qml b/.config/quickshell/ii/modules/sidebarLeft/anime/BooruImage.qml index abb74612d..b2eef2939 100644 --- a/.config/quickshell/ii/modules/sidebarLeft/anime/BooruImage.qml +++ b/.config/quickshell/ii/modules/sidebarLeft/anime/BooruImage.qml @@ -15,7 +15,7 @@ Button { id: root property var imageData property var rowHeight - property bool manualDownload: true + property bool manualDownload: false property string previewDownloadPath property string downloadPath property string nsfwPath @@ -63,13 +63,17 @@ Button { anchors.fill: parent width: root.rowHeight * modelData.aspect_ratio height: root.rowHeight - visible: opacity > 0 - opacity: status === Image.Ready ? 1 : 0 fillMode: Image.PreserveAspectFit source: modelData.preview_url sourceSize.width: root.rowHeight * modelData.aspect_ratio sourceSize.height: root.rowHeight + visible: opacity > 0 + opacity: status === Image.Ready ? 1 : 0 + Behavior on opacity { + animation: Appearance.animation.elementMoveEnter.numberAnimation.createObject(this) + } + layer.enabled: true layer.effect: OpacityMask { maskSource: Rectangle { @@ -78,10 +82,6 @@ Button { radius: imageRadius } } - - Behavior on opacity { - animation: Appearance.animation.elementMoveEnter.numberAnimation.createObject(this) - } } RippleButton { diff --git a/.config/quickshell/ii/modules/sidebarLeft/anime/BooruResponse.qml b/.config/quickshell/ii/modules/sidebarLeft/anime/BooruResponse.qml index baf47710e..9e021e584 100644 --- a/.config/quickshell/ii/modules/sidebarLeft/anime/BooruResponse.qml +++ b/.config/quickshell/ii/modules/sidebarLeft/anime/BooruResponse.qml @@ -237,7 +237,7 @@ Rectangle { rowHeight: imageRow.rowHeight imageRadius: imageRow.modelData.images.length == 1 ? 50 : Appearance.rounding.normal // Download manually to reduce redundant requests or make sure downloading works - // manualDownload: ["danbooru", "waifu.im", "t.alcy.cc"].includes(root.responseData.provider) + manualDownload: ["danbooru", "waifu.im", "t.alcy.cc"].includes(root.responseData.provider) previewDownloadPath: root.previewDownloadPath downloadPath: root.downloadPath nsfwPath: root.nsfwPath diff --git a/.config/quickshell/ii/modules/sidebarRight/BottomWidgetGroup.qml b/.config/quickshell/ii/modules/sidebarRight/BottomWidgetGroup.qml index 384011634..cd5385c75 100644 --- a/.config/quickshell/ii/modules/sidebarRight/BottomWidgetGroup.qml +++ b/.config/quickshell/ii/modules/sidebarRight/BottomWidgetGroup.qml @@ -4,6 +4,7 @@ import qs import qs.services import "./calendar" import "./todo" +import "./pomodoro" import QtQuick import QtQuick.Layouts @@ -17,7 +18,8 @@ Rectangle { property bool collapsed: Persistent.states.sidebar.bottomGroup.collapsed property var tabs: [ {"type": "calendar", "name": Translation.tr("Calendar"), "icon": "calendar_month", "widget": calendarWidget}, - {"type": "todo", "name": Translation.tr("To Do"), "icon": "done_outline", "widget": todoWidget} + {"type": "todo", "name": Translation.tr("To Do"), "icon": "done_outline", "widget": todoWidget}, + {"type": "timer", "name": Translation.tr("Timer"), "icon": "schedule", "widget": pomodoroWidget}, ] Behavior on implicitHeight { @@ -238,4 +240,13 @@ Rectangle { anchors.margins: 5 } } + + // Pomodoro component + Component { + id: pomodoroWidget + PomodoroWidget { + anchors.fill: parent + anchors.margins: 5 + } + } } \ No newline at end of file diff --git a/.config/quickshell/ii/modules/sidebarRight/CenterWidgetGroup.qml b/.config/quickshell/ii/modules/sidebarRight/CenterWidgetGroup.qml index 4aeef71c2..65b80d886 100644 --- a/.config/quickshell/ii/modules/sidebarRight/CenterWidgetGroup.qml +++ b/.config/quickshell/ii/modules/sidebarRight/CenterWidgetGroup.qml @@ -15,7 +15,7 @@ Rectangle { color: Appearance.colors.colLayer1 property int selectedTab: 0 - property var tabButtonList: [{"icon": "notifications", "name": Translation.tr("Notifications")}, {"icon": "volume_up", "name": Translation.tr("Volume mixer")}] + property var tabButtonList: [{"icon": "notifications", "name": Translation.tr("Notifications")}, {"icon": "volume_up", "name": Translation.tr("Audio")}] Keys.onPressed: (event) => { if (event.key === Qt.Key_PageDown || event.key === Qt.Key_PageUp) { diff --git a/.config/quickshell/ii/modules/sidebarRight/pomodoro/PomodoroTimer.qml b/.config/quickshell/ii/modules/sidebarRight/pomodoro/PomodoroTimer.qml new file mode 100644 index 000000000..a7a5cc48f --- /dev/null +++ b/.config/quickshell/ii/modules/sidebarRight/pomodoro/PomodoroTimer.qml @@ -0,0 +1,115 @@ +import qs +import qs.services +import qs.modules.common +import qs.modules.common.widgets +import Qt5Compat.GraphicalEffects +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell + +Item { + id: root + + implicitHeight: contentColumn.implicitHeight + implicitWidth: contentColumn.implicitWidth + + ColumnLayout { + id: contentColumn + anchors.fill: parent + spacing: 0 + + // The Pomodoro timer circle + CircularProgress { + Layout.alignment: Qt.AlignHCenter + lineWidth: 8 + value: { + return TimerService.pomodoroSecondsLeft / TimerService.pomodoroLapDuration; + } + implicitSize: 200 + enableAnimation: true + + ColumnLayout { + anchors.centerIn: parent + spacing: 0 + + StyledText { + Layout.alignment: Qt.AlignHCenter + text: { + let minutes = Math.floor(TimerService.pomodoroSecondsLeft / 60).toString().padStart(2, '0'); + let seconds = Math.floor(TimerService.pomodoroSecondsLeft % 60).toString().padStart(2, '0'); + return `${minutes}:${seconds}`; + } + font.pixelSize: 40 + color: Appearance.m3colors.m3onSurface + } + StyledText { + Layout.alignment: Qt.AlignHCenter + text: TimerService.pomodoroLongBreak ? Translation.tr("Long break") : TimerService.pomodoroBreak ? Translation.tr("Break") : Translation.tr("Focus") + font.pixelSize: Appearance.font.pixelSize.normal + color: Appearance.colors.colSubtext + } + } + + Rectangle { + radius: Appearance.rounding.full + color: Appearance.colors.colLayer2 + + anchors { + right: parent.right + bottom: parent.bottom + } + implicitWidth: 36 + implicitHeight: implicitWidth + + StyledText { + id: cycleText + anchors.centerIn: parent + color: Appearance.colors.colOnLayer2 + text: TimerService.pomodoroCycle + 1 + } + } + } + + // The Start/Stop and Reset buttons + RowLayout { + Layout.alignment: Qt.AlignHCenter + spacing: 10 + + RippleButton { + contentItem: StyledText { + anchors.centerIn: parent + horizontalAlignment: Text.AlignHCenter + text: TimerService.pomodoroRunning ? Translation.tr("Pause") : (TimerService.pomodoroSecondsLeft === TimerService.focusTime) ? Translation.tr("Start") : Translation.tr("Resume") + color: TimerService.pomodoroRunning ? Appearance.colors.colOnSecondaryContainer : Appearance.colors.colOnPrimary + } + implicitHeight: 35 + implicitWidth: 90 + font.pixelSize: Appearance.font.pixelSize.larger + onClicked: TimerService.togglePomodoro() + colBackground: TimerService.pomodoroRunning ? Appearance.colors.colSecondaryContainer : Appearance.colors.colPrimary + colBackgroundHover: TimerService.pomodoroRunning ? Appearance.colors.colSecondaryContainer : Appearance.colors.colPrimary + } + + RippleButton { + implicitHeight: 35 + implicitWidth: 90 + + onClicked: TimerService.resetPomodoro() + enabled: (TimerService.pomodoroSecondsLeft < TimerService.pomodoroLapDuration) || TimerService.pomodoroCycle > 0 || TimerService.pomodoroBreak + + font.pixelSize: Appearance.font.pixelSize.larger + colBackground: Appearance.colors.colErrorContainer + colBackgroundHover: Appearance.colors.colErrorContainerHover + colRipple: Appearance.colors.colErrorContainerActive + + contentItem: StyledText { + anchors.centerIn: parent + horizontalAlignment: Text.AlignHCenter + text: Translation.tr("Reset") + color: Appearance.colors.colOnErrorContainer + } + } + } + } +} diff --git a/.config/quickshell/ii/modules/sidebarRight/pomodoro/PomodoroWidget.qml b/.config/quickshell/ii/modules/sidebarRight/pomodoro/PomodoroWidget.qml new file mode 100644 index 000000000..729d45809 --- /dev/null +++ b/.config/quickshell/ii/modules/sidebarRight/pomodoro/PomodoroWidget.qml @@ -0,0 +1,144 @@ +import qs +import qs.services +import qs.modules.common +import qs.modules.common.widgets +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +Item { + id: root + property int currentTab: 0 + property var tabButtonList: [ + {"name": Translation.tr("Pomodoro"), "icon": "search_activity"}, + {"name": Translation.tr("Stopwatch"), "icon": "timer"} + ] + + // These are keybinds for stopwatch and pomodoro + Keys.onPressed: (event) => { + if ((event.key === Qt.Key_PageDown || event.key === Qt.Key_PageUp) && event.modifiers === Qt.NoModifier) { // Switch tabs + if (event.key === Qt.Key_PageDown) { + currentTab = Math.min(currentTab + 1, root.tabButtonList.length - 1) + } else if (event.key === Qt.Key_PageUp) { + currentTab = Math.max(currentTab - 1, 0) + } + event.accepted = true + } else if (event.key === Qt.Key_Space || event.key === Qt.Key_S) { // Pause/resume with Space or S + if (currentTab === 0) { + TimerService.togglePomodoro() + } else { + TimerService.toggleStopwatch() + } + event.accepted = true + } else if (event.key === Qt.Key_R) { // Reset with R + if (currentTab === 0) { + TimerService.resetPomodoro() + } else { + TimerService.stopwatchReset() + } + event.accepted = true + } else if (event.key === Qt.Key_L) { // Record lap with L + TimerService.stopwatchRecordLap() + event.accepted = true + } + } + + ColumnLayout { + anchors.fill: parent + spacing: 0 + + TabBar { + id: tabBar + Layout.fillWidth: true + currentIndex: currentTab + onCurrentIndexChanged: currentTab = currentIndex + + background: Item { + WheelHandler { + onWheel: (event) => { + if (event.angleDelta.y < 0) + tabBar.currentIndex = Math.min(tabBar.currentIndex + 1, root.tabButtonList.length - 1) + else if (event.angleDelta.y > 0) + tabBar.currentIndex = Math.max(tabBar.currentIndex - 1, 0) + } + acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad + } + } + + Repeater { + model: root.tabButtonList + delegate: SecondaryTabButton { + selected: (index == currentTab) + buttonText: modelData.name + buttonIcon: modelData.icon + } + } + } + + Item { // Tab indicator + id: tabIndicator + Layout.fillWidth: true + height: 3 + property bool enableIndicatorAnimation: false + Connections { + target: root + function onCurrentTabChanged() { + 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.colors.colPrimary + radius: Appearance.rounding.full + + Behavior on x { + enabled: tabIndicator.enableIndicatorAnimation + animation: Appearance.animation.elementMove.numberAnimation.createObject(this) + } + + Behavior on implicitWidth { + enabled: tabIndicator.enableIndicatorAnimation + animation: Appearance.animation.elementMove.numberAnimation.createObject(this) + } + } + } + + Rectangle { // Tabbar bottom border + id: tabBarBottomBorder + Layout.fillWidth: true + height: 1 + color: Appearance.colors.colOutlineVariant + } + + SwipeView { + id: swipeView + Layout.topMargin: 10 + Layout.fillWidth: true + Layout.fillHeight: true + spacing: 10 + clip: true + currentIndex: currentTab + onCurrentIndexChanged: { + tabIndicator.enableIndicatorAnimation = true + currentTab = currentIndex + } + + // Tabs + PomodoroTimer {} + Stopwatch {} + } + } +} diff --git a/.config/quickshell/ii/modules/sidebarRight/pomodoro/Stopwatch.qml b/.config/quickshell/ii/modules/sidebarRight/pomodoro/Stopwatch.qml new file mode 100644 index 000000000..ffc706568 --- /dev/null +++ b/.config/quickshell/ii/modules/sidebarRight/pomodoro/Stopwatch.qml @@ -0,0 +1,208 @@ +import qs +import qs.services +import qs.modules.common +import qs.modules.common.widgets +import Qt5Compat.GraphicalEffects +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell + +Item { + id: stopwatchTab + Layout.fillWidth: true + Layout.fillHeight: true + + Item { + anchors { + fill: parent + topMargin: 8 + leftMargin: 16 + rightMargin: 16 + } + + RowLayout { // Elapsed + id: elapsedIndicator + + anchors { + top: undefined + verticalCenter: parent.verticalCenter + left: controlButtons.left + leftMargin: 6 + } + + states: State { + name: "hasLaps" + when: TimerService.stopwatchLaps.length > 0 + AnchorChanges { + target: elapsedIndicator + anchors.top: parent.top + anchors.verticalCenter: undefined + anchors.left: controlButtons.left + } + } + + transitions: Transition { + AnchorAnimation { + duration: Appearance.animation.elementMoveFast.duration + easing.type: Appearance.animation.elementMoveFast.type + easing.bezierCurve: Appearance.animation.elementMoveFast.bezierCurve + } + } + + spacing: 0 + StyledText { + // Layout.preferredWidth: elapsedIndicator.width * 0.6 // Prevent shakiness + font.pixelSize: 40 + color: Appearance.m3colors.m3onSurface + text: { + let totalSeconds = Math.floor(TimerService.stopwatchTime) / 100 + let minutes = Math.floor(totalSeconds / 60).toString().padStart(2, '0') + let seconds = Math.floor(totalSeconds % 60).toString().padStart(2, '0') + return `${minutes}:${seconds}` + } + } + StyledText { + Layout.fillWidth: true + font.pixelSize: 40 + color: Appearance.colors.colSubtext + text: { + return `:${(Math.floor(TimerService.stopwatchTime) % 100).toString().padStart(2, '0')}` + } + } + } + + // Laps + StyledListView { + id: lapsList + anchors { + top: elapsedIndicator.bottom + bottom: controlButtons.top + left: parent.left + right: parent.right + topMargin: 16 + bottomMargin: 16 + } + spacing: 4 + clip: true + popin: true + + model: ScriptModel { + values: TimerService.stopwatchLaps.map((v, i, arr) => arr[arr.length - 1 - i]) + } + + delegate: Rectangle { + id: lapItem + required property int index + required property var modelData + property var horizontalPadding: 10 + property var verticalPadding: 6 + width: lapsList.width + implicitHeight: lapRow.implicitHeight + verticalPadding * 2 + implicitWidth: lapRow.implicitWidth + horizontalPadding * 2 + color: Appearance.colors.colLayer2 + radius: Appearance.rounding.small + + RowLayout { + id: lapRow + anchors { + fill: parent + leftMargin: lapItem.horizontalPadding + rightMargin: lapItem.horizontalPadding + topMargin: lapItem.verticalPadding + bottomMargin: lapItem.verticalPadding + } + + StyledText { + font.pixelSize: Appearance.font.pixelSize.small + color: Appearance.colors.colSubtext + text: `${TimerService.stopwatchLaps.length - lapItem.index}.` + } + + StyledText { + font.pixelSize: Appearance.font.pixelSize.small + text: { + const lapTime = lapItem.modelData + const _10ms = (Math.floor(lapTime) % 100).toString().padStart(2, '0') + const totalSeconds = Math.floor(lapTime) / 100 + const minutes = Math.floor(totalSeconds / 60).toString().padStart(2, '0') + const seconds = Math.floor(totalSeconds % 60).toString().padStart(2, '0') + return `${minutes}:${seconds}.${_10ms}` + } + } + + Item { Layout.fillWidth: true } + + StyledText { + font.pixelSize: Appearance.font.pixelSize.smaller + color: Appearance.colors.colPrimary + text: { + const originalIndex = TimerService.stopwatchLaps.length - lapItem.index - 1 + const lastTime = originalIndex > 0 ? TimerService.stopwatchLaps[originalIndex - 1] : 0 + const lapTime = lapItem.modelData - lastTime + const _10ms = (Math.floor(lapTime) % 100).toString().padStart(2, '0') + const totalSeconds = Math.floor(lapTime) / 100 + const minutes = Math.floor(totalSeconds / 60).toString().padStart(2, '0') + const seconds = Math.floor(totalSeconds % 60).toString().padStart(2, '0') + return `+${minutes == "00" ? "" : minutes + ":"}${seconds}.${_10ms}` + } + } + } + } + } + + RowLayout { + id: controlButtons + anchors { + horizontalCenter: parent.horizontalCenter + bottom: parent.bottom + bottomMargin: 6 + } + spacing: 4 + + RippleButton { + Layout.preferredHeight: 35 + Layout.preferredWidth: 90 + font.pixelSize: Appearance.font.pixelSize.larger + + onClicked: { + TimerService.toggleStopwatch() + } + + colBackground: TimerService.stopwatchRunning ? Appearance.colors.colSecondaryContainer : Appearance.colors.colPrimary + colBackgroundHover: TimerService.stopwatchRunning ? Appearance.colors.colSecondaryContainerHover : Appearance.colors.colPrimaryHover + colRipple: TimerService.stopwatchRunning ? Appearance.colors.colSecondaryContainerActive : Appearance.colors.colPrimaryActive + + contentItem: StyledText { + horizontalAlignment: Text.AlignHCenter + color: TimerService.stopwatchRunning ? Appearance.colors.colOnSecondaryContainer : Appearance.colors.colOnPrimary + text: TimerService.stopwatchRunning ? Translation.tr("Pause") : TimerService.stopwatchTime === 0 ? Translation.tr("Start") : Translation.tr("Resume") + } + } + + RippleButton { + implicitHeight: 35 + implicitWidth: 90 + font.pixelSize: Appearance.font.pixelSize.larger + + onClicked: { + if (TimerService.stopwatchRunning) + TimerService.stopwatchRecordLap() + else + TimerService.stopwatchReset() + } + enabled: TimerService.stopwatchTime > 0 || Persistent.states.timer.stopwatch.laps.length > 0 + + colBackground: TimerService.stopwatchRunning ? Appearance.colors.colLayer2 : Appearance.colors.colErrorContainer + colBackgroundHover: TimerService.stopwatchRunning ? Appearance.colors.colLayer2Hover : Appearance.colors.colErrorContainerHover + colRipple: TimerService.stopwatchRunning ? Appearance.colors.colLayer2Active : Appearance.colors.colErrorContainerActive + + contentItem: StyledText { + horizontalAlignment: Text.AlignHCenter + text: TimerService.stopwatchRunning ? Translation.tr("Lap") : Translation.tr("Reset") + color: TimerService.stopwatchRunning ? Appearance.colors.colOnLayer2 : Appearance.colors.colOnErrorContainer + } + } + } + } +} \ No newline at end of file diff --git a/.config/quickshell/ii/modules/sidebarRight/quickToggles/EasyEffectsToggle.qml b/.config/quickshell/ii/modules/sidebarRight/quickToggles/EasyEffectsToggle.qml index 5fbef04b5..10b866007 100644 --- a/.config/quickshell/ii/modules/sidebarRight/quickToggles/EasyEffectsToggle.qml +++ b/.config/quickshell/ii/modules/sidebarRight/quickToggles/EasyEffectsToggle.qml @@ -1,23 +1,23 @@ import qs.modules.common.widgets import qs +import qs.services +import QtQuick import Quickshell.Io import Quickshell import Quickshell.Hyprland QuickToggleButton { id: root - toggled: false - visible: false + toggled: EasyEffects.active + visible: EasyEffects.available buttonIcon: "instant_mix" + Component.onCompleted: { + EasyEffects.fetchActiveState() + } + onClicked: { - if (toggled) { - root.toggled = false - Quickshell.execDetached(["pkill", "easyeffects"]) - } else { - root.toggled = true - Quickshell.execDetached(["easyeffects", "--gapplication-service"]) - } + EasyEffects.toggle() } altAction: () => { @@ -25,24 +25,6 @@ QuickToggleButton { GlobalStates.sidebarRightOpen = false } - Process { - id: fetchAvailability - running: true - command: ["bash", "-c", "command -v easyeffects"] - onExited: (exitCode, exitStatus) => { - root.visible = exitCode === 0 - } - } - - Process { - id: fetchActiveState - running: true - command: ["pidof", "easyeffects"] - onExited: (exitCode, exitStatus) => { - root.toggled = exitCode === 0 - } - } - StyledToolTip { content: Translation.tr("EasyEffects | Right-click to configure") } diff --git a/.config/quickshell/ii/modules/sidebarRight/volumeMixer/AudioDeviceSelectorButton.qml b/.config/quickshell/ii/modules/sidebarRight/volumeMixer/AudioDeviceSelectorButton.qml index a1e589de7..fba430528 100644 --- a/.config/quickshell/ii/modules/sidebarRight/volumeMixer/AudioDeviceSelectorButton.qml +++ b/.config/quickshell/ii/modules/sidebarRight/volumeMixer/AudioDeviceSelectorButton.qml @@ -6,15 +6,17 @@ import QtQuick import QtQuick.Layouts import Quickshell.Services.Pipewire -GroupButton { +RippleButton { id: button required property bool input buttonRadius: Appearance.rounding.small colBackground: Appearance.colors.colLayer2 colBackgroundHover: Appearance.colors.colLayer2Hover - colBackgroundActive: Appearance.colors.colLayer2Active - clickedWidth: baseWidth + 30 + colRipple: Appearance.colors.colLayer2Active + + implicitHeight: contentItem.implicitHeight + 6 * 2 + implicitWidth: contentItem.implicitWidth + 6 * 2 contentItem: RowLayout { anchors.fill: parent diff --git a/.config/quickshell/ii/modules/sidebarRight/volumeMixer/VolumeMixer.qml b/.config/quickshell/ii/modules/sidebarRight/volumeMixer/VolumeMixer.qml index f9e0118bb..13bebf94b 100644 --- a/.config/quickshell/ii/modules/sidebarRight/volumeMixer/VolumeMixer.qml +++ b/.config/quickshell/ii/modules/sidebarRight/volumeMixer/VolumeMixer.qml @@ -99,19 +99,13 @@ Item { } } - // Separator - Rectangle { - color: Appearance.m3colors.m3outlineVariant - implicitHeight: 1 - Layout.fillWidth: true - } - - // Device selector - ButtonGroup { + RowLayout { id: deviceSelectorRowLayout Layout.fillWidth: true Layout.fillHeight: false + uniformCellSizes: true + AudioDeviceSelectorButton { Layout.fillWidth: true input: false diff --git a/.config/quickshell/ii/services/EasyEffects.qml b/.config/quickshell/ii/services/EasyEffects.qml new file mode 100644 index 000000000..8767a9d4e --- /dev/null +++ b/.config/quickshell/ii/services/EasyEffects.qml @@ -0,0 +1,61 @@ +import qs.modules.common +import QtQuick +import Quickshell +import Quickshell.Io +import Quickshell.Services.Pipewire +pragma Singleton +pragma ComponentBehavior: Bound + +/** + * Handles EasyEffects active state and presets. + */ +Singleton { + id: root + + property bool available: false + property bool active: false + + function fetchAvailability() { + fetchAvailabilityProc.running = true + } + + function fetchActiveState() { + fetchActiveStateProc.running = true + } + + function disable() { + root.active = false + Quickshell.execDetached(["pkill", "easyeffects"]) + } + + function enable() { + root.active = true + Quickshell.execDetached(["easyeffects", "--gapplication-service"]) + } + + function toggle() { + if (root.active) { + root.disable() + } else { + root.enable() + } + } + + Process { + id: fetchAvailabilityProc + running: true + command: ["bash", "-c", "command -v easyeffects"] + onExited: (exitCode, exitStatus) => { + root.available = exitCode === 0 + } + } + + Process { + id: fetchActiveStateProc + running: true + command: ["pidof", "easyeffects"] + onExited: (exitCode, exitStatus) => { + root.active = exitCode === 0 + } + } +} diff --git a/.config/quickshell/ii/services/HyprlandData.qml b/.config/quickshell/ii/services/HyprlandData.qml index 07c2d89a2..abbaaf577 100644 --- a/.config/quickshell/ii/services/HyprlandData.qml +++ b/.config/quickshell/ii/services/HyprlandData.qml @@ -69,7 +69,7 @@ Singleton { Process { id: getClients - command: ["bash", "-c", "hyprctl clients -j"] + command: ["hyprctl", "clients", "-j"] stdout: StdioCollector { id: clientsCollector onStreamFinished: { @@ -87,7 +87,7 @@ Singleton { Process { id: getMonitors - command: ["bash", "-c", "hyprctl monitors -j"] + command: ["hyprctl", "monitors", "-j"] stdout: StdioCollector { id: monitorsCollector onStreamFinished: { @@ -98,7 +98,7 @@ Singleton { Process { id: getLayers - command: ["bash", "-c", "hyprctl layers -j"] + command: ["hyprctl", "layers", "-j"] stdout: StdioCollector { id: layersCollector onStreamFinished: { @@ -109,7 +109,7 @@ Singleton { Process { id: getWorkspaces - command: ["bash", "-c", "hyprctl workspaces -j"] + command: ["hyprctl", "workspaces", "-j"] stdout: StdioCollector { id: workspacesCollector onStreamFinished: { @@ -127,7 +127,7 @@ Singleton { Process { id: getActiveWorkspace - command: ["bash", "-c", "hyprctl activeworkspace -j"] + command: ["hyprctl", "activeworkspace", "-j"] stdout: StdioCollector { id: activeWorkspaceCollector onStreamFinished: { diff --git a/.config/quickshell/ii/services/TimerService.qml b/.config/quickshell/ii/services/TimerService.qml new file mode 100644 index 000000000..e6c8906bc --- /dev/null +++ b/.config/quickshell/ii/services/TimerService.qml @@ -0,0 +1,142 @@ +pragma Singleton +pragma ComponentBehavior: Bound + +import qs +import qs.modules.common + +import Quickshell +import Quickshell.Io +import QtQuick + +/** + * Simple Pomodoro time manager. + */ +Singleton { + id: root + + property int focusTime: Config.options.time.pomodoro.focus + property int breakTime: Config.options.time.pomodoro.breakTime + property int longBreakTime: Config.options.time.pomodoro.longBreak + property int cyclesBeforeLongBreak: Config.options.time.pomodoro.cyclesBeforeLongBreak + property string alertSound: Config.options.time.pomodoro.alertSound + + property bool pomodoroRunning: Persistent.states.timer.pomodoro.running + property bool pomodoroBreak: Persistent.states.timer.pomodoro.isBreak + property bool pomodoroLongBreak: Persistent.states.timer.pomodoro.isBreak && (pomodoroCycle + 1 == cyclesBeforeLongBreak); + property int pomodoroLapDuration: pomodoroLongBreak ? longBreakTime : pomodoroBreak ? breakTime : focusTime // This is a binding that's to be kept + property int pomodoroSecondsLeft: pomodoroLapDuration // Reasonable init value, to be changed + property int pomodoroCycle: Persistent.states.timer.pomodoro.cycle + + property bool stopwatchRunning: Persistent.states.timer.stopwatch.running + property int stopwatchTime: 0 + property int stopwatchStart: Persistent.states.timer.stopwatch.start + property var stopwatchLaps: Persistent.states.timer.stopwatch.laps + + // General + Component.onCompleted: { + if (!stopwatchRunning) + stopwatchReset(); + } + + function getCurrentTimeInSeconds() { // Pomodoro uses Seconds + return Math.floor(Date.now() / 1000); + } + + function getCurrentTimeIn10ms() { // Stopwatch uses 10ms + return Math.floor(Date.now() / 10); + } + + // Pomodoro + function refreshPomodoro() { + // Work <-> break ? + if (getCurrentTimeInSeconds() >= Persistent.states.timer.pomodoro.start + pomodoroLapDuration) { + // Reset counts + Persistent.states.timer.pomodoro.isBreak = !Persistent.states.timer.pomodoro.isBreak; + Persistent.states.timer.pomodoro.start = getCurrentTimeInSeconds(); + + // Send notification + let notificationMessage; + if (Persistent.states.timer.pomodoro.isBreak && (pomodoroCycle + 1 == cyclesBeforeLongBreak)) { + notificationMessage = Translation.tr(`🌿 Long break: %1 minutes`).arg(Math.floor(longBreakTime / 60)); + } else if (Persistent.states.timer.pomodoro.isBreak) { + notificationMessage = Translation.tr(`☕ Break: %1 minutes`).arg(Math.floor(breakTime / 60)); + } else { + notificationMessage = Translation.tr(`🔴 Focus: %1 minutes`).arg(Math.floor(focusTime / 60)); + } + + Quickshell.execDetached(["notify-send", "Pomodoro", notificationMessage, "-a", "Shell"]); + if (alertSound) + Quickshell.execDetached(["ffplay", "-nodisp", "-autoexit", alertSound]); + + if (!pomodoroBreak) { + Persistent.states.timer.pomodoro.cycle = (Persistent.states.timer.pomodoro.cycle + 1) % root.cyclesBeforeLongBreak; + } + } + + pomodoroSecondsLeft = pomodoroLapDuration - (getCurrentTimeInSeconds() - Persistent.states.timer.pomodoro.start); + } + + Timer { + id: pomodoroTimer + interval: 200 + running: root.pomodoroRunning + repeat: true + onTriggered: refreshPomodoro() + } + + function togglePomodoro() { + Persistent.states.timer.pomodoro.running = !pomodoroRunning; + if (Persistent.states.timer.pomodoro.running) { + // Start/Resume + Persistent.states.timer.pomodoro.start = getCurrentTimeInSeconds() + pomodoroSecondsLeft - pomodoroLapDuration; + } + } + + function resetPomodoro() { + Persistent.states.timer.pomodoro.running = false; + Persistent.states.timer.pomodoro.isBreak = false; + Persistent.states.timer.pomodoro.start = getCurrentTimeInSeconds(); + Persistent.states.timer.pomodoro.cycle = 0; + refreshPomodoro(); + } + + // Stopwatch + function refreshStopwatch() { // Stopwatch stores time in 10ms + stopwatchTime = getCurrentTimeIn10ms() - stopwatchStart; + } + + Timer { + id: stopwatchTimer + interval: 10 + running: root.stopwatchRunning + repeat: true + onTriggered: refreshStopwatch() + } + + function toggleStopwatch() { + if (root.stopwatchRunning) + stopwatchPause(); + else + stopwatchResume(); + } + + function stopwatchPause() { + Persistent.states.timer.stopwatch.running = false; + } + + function stopwatchResume() { + if (stopwatchTime === 0) Persistent.states.timer.stopwatch.laps = []; + Persistent.states.timer.stopwatch.running = true; + Persistent.states.timer.stopwatch.start = getCurrentTimeIn10ms() - stopwatchTime; + } + + function stopwatchReset() { + stopwatchTime = 0; + Persistent.states.timer.stopwatch.laps = []; + Persistent.states.timer.stopwatch.running = false; + } + + function stopwatchRecordLap() { + Persistent.states.timer.stopwatch.laps.push(stopwatchTime); + } +} diff --git a/.config/quickshell/ii/services/ai/MistralApiStrategy.qml b/.config/quickshell/ii/services/ai/MistralApiStrategy.qml index dfcb950eb..1ae7fc13f 100644 --- a/.config/quickshell/ii/services/ai/MistralApiStrategy.qml +++ b/.config/quickshell/ii/services/ai/MistralApiStrategy.qml @@ -100,6 +100,17 @@ ApiStrategy { message.content += newContent; message.rawContent += newContent; + // Usage metadata + if (dataJson.usage) { + return { + tokenUsage: { + input: dataJson.usage.prompt_tokens ?? -1, + output: dataJson.usage.completion_tokens ?? -1, + total: dataJson.usage.total_tokens ?? -1 + } + }; + } + if (`dataJson`.done) { return { finished: true }; } diff --git a/.config/quickshell/ii/services/ai/OpenAiApiStrategy.qml b/.config/quickshell/ii/services/ai/OpenAiApiStrategy.qml index a5792ace7..24a0892fb 100644 --- a/.config/quickshell/ii/services/ai/OpenAiApiStrategy.qml +++ b/.config/quickshell/ii/services/ai/OpenAiApiStrategy.qml @@ -72,6 +72,17 @@ ApiStrategy { message.content += newContent; message.rawContent += newContent; + // Usage metadata + if (dataJson.usage) { + return { + tokenUsage: { + input: dataJson.usage.prompt_tokens ?? -1, + output: dataJson.usage.completion_tokens ?? -1, + total: dataJson.usage.total_tokens ?? -1 + } + }; + } + if (dataJson.done) { return { finished: true }; } diff --git a/.config/quickshell/ii/welcome.qml b/.config/quickshell/ii/welcome.qml index df9b0d447..1406ff0de 100644 --- a/.config/quickshell/ii/welcome.qml +++ b/.config/quickshell/ii/welcome.qml @@ -26,16 +26,24 @@ ApplicationWindow { property real contentPadding: 8 property bool showNextTime: false visible: true - onClosing: Qt.quit() + onClosing: { + Quickshell.execDetached([ + "notify-send", + Translation.tr("Welcome app"), + Translation.tr("Enjoy! You can reopen the welcome app any time with Super+Shift+Alt+/. To open the settings app, hit Super+I"), + "-a", "Shell" + ]) + Qt.quit() + } title: Translation.tr("illogical-impulse Welcome") Component.onCompleted: { - MaterialThemeLoader.reapplyTheme() + MaterialThemeLoader.reapplyTheme(); } minimumWidth: 600 minimumHeight: 400 - width: 800 + width: 900 height: 650 color: Appearance.m3colors.m3background @@ -57,7 +65,8 @@ ApplicationWindow { margins: contentPadding } - Item { // Titlebar + Item { + // Titlebar visible: Config.options?.windows.showTitlebar Layout.fillWidth: true implicitHeight: Math.max(welcomeText.implicitHeight, windowControlsRow.implicitHeight) @@ -70,7 +79,7 @@ ApplicationWindow { leftMargin: 12 } color: Appearance.colors.colOnLayer0 - text: Translation.tr("Yooooo hi there") + text: Translation.tr("Hi there! First things first...") font.pixelSize: Appearance.font.pixelSize.title font.family: Appearance.font.family.title } @@ -89,9 +98,9 @@ ApplicationWindow { Layout.alignment: Qt.AlignVCenter onCheckedChanged: { if (checked) { - Quickshell.execDetached(["rm", root.firstRunFilePath]) + Quickshell.execDetached(["rm", root.firstRunFilePath]); } else { - Quickshell.execDetached(["bash", "-c", `echo '${StringUtils.shellSingleQuoteEscape(root.firstRunFileContent)}' > '${StringUtils.shellSingleQuoteEscape(root.firstRunFilePath)}'`]) + Quickshell.execDetached(["bash", "-c", `echo '${StringUtils.shellSingleQuoteEscape(root.firstRunFileContent)}' > '${StringUtils.shellSingleQuoteEscape(root.firstRunFilePath)}'`]); } } } @@ -109,33 +118,63 @@ ApplicationWindow { } } } - Rectangle { // Content container + Rectangle { + // Content container color: Appearance.m3colors.m3surfaceContainerLow radius: Appearance.rounding.windowRounding - root.contentPadding implicitHeight: contentColumn.implicitHeight implicitWidth: contentColumn.implicitWidth Layout.fillWidth: true Layout.fillHeight: true - ContentPage { id: contentColumn anchors.fill: parent ContentSection { - title: Translation.tr("Bar style") + title: Translation.tr("Bar") - ConfigSelectionArray { - currentValue: Config.options.bar.cornerStyle - configOptionName: "bar.cornerStyle" - onSelected: (newValue) => { - Config.options.bar.cornerStyle = newValue; // Update local copy + ContentSubsection { + title: "Corner style" + + ConfigSelectionArray { + currentValue: Config.options.bar.cornerStyle + configOptionName: "bar.cornerStyle" + onSelected: newValue => { + Config.options.bar.cornerStyle = newValue; // Update local copy + } + options: [ + { + displayName: Translation.tr("Hug"), + value: 0 + }, + { + displayName: Translation.tr("Float"), + value: 1 + }, + { + displayName: Translation.tr("Plain rectangle"), + value: 2 + } + ] + } + } + + ConfigRow { + ConfigSwitch { + text: Translation.tr("Automatically hide") + checked: Config.options.bar.autoHide.enable + onCheckedChanged: { + Config.options.bar.autoHide.enable = checked; + } + } + ConfigSwitch { + text: Translation.tr("Place at the bottom") + checked: Config.options.bar.bottom + onCheckedChanged: { + Config.options.bar.bottom = checked; + } } - options: [ - { displayName: Translation.tr("Hug"), value: 0 }, - { displayName: Translation.tr("Float"), value: 1 }, - { displayName: Translation.tr("Plain rectangle"), value: 2 } - ] } } @@ -161,7 +200,7 @@ ApplicationWindow { materialIcon: "wallpaper" mainText: konachanWallProc.running ? Translation.tr("Be patient...") : Translation.tr("Random: Konachan") onClicked: { - console.log(konachanWallProc.command.join(" ")) + console.log(konachanWallProc.command.join(" ")); konachanWallProc.running = true; } StyledToolTip { @@ -174,7 +213,7 @@ ApplicationWindow { content: Translation.tr("Pick wallpaper image on your system") } onClicked: { - Quickshell.execDetached([`${Directories.wallpaperSwitchScriptPath}`]) + Quickshell.execDetached([`${Directories.wallpaperSwitchScriptPath}`]); } mainContentComponent: Component { RowLayout { @@ -217,38 +256,56 @@ ApplicationWindow { title: Translation.tr("Policies") ConfigRow { - ColumnLayout { // Weeb policy - ContentSubsectionLabel { - text: Translation.tr("Weeb") - } + Layout.fillWidth: true + + ContentSubsection { + title: "Weeb" + ConfigSelectionArray { currentValue: Config.options.policies.weeb configOptionName: "policies.weeb" - onSelected: (newValue) => { + onSelected: newValue => { Config.options.policies.weeb = newValue; } options: [ - { displayName: Translation.tr("No"), value: 0 }, - { displayName: Translation.tr("Yes"), value: 1 }, - { displayName: Translation.tr("Closet"), value: 2 } + { + displayName: Translation.tr("No"), + value: 0 + }, + { + displayName: Translation.tr("Yes"), + value: 1 + }, + { + displayName: Translation.tr("Closet"), + value: 2 + } ] } } - ColumnLayout { // AI policy - ContentSubsectionLabel { - text: Translation.tr("AI") - } + ContentSubsection { + title: "AI" + ConfigSelectionArray { currentValue: Config.options.policies.ai configOptionName: "policies.ai" - onSelected: (newValue) => { + onSelected: newValue => { Config.options.policies.ai = newValue; } options: [ - { displayName: Translation.tr("No"), value: 0 }, - { displayName: Translation.tr("Yes"), value: 1 }, - { displayName: Translation.tr("Local only"), value: 2 } + { + displayName: Translation.tr("No"), + value: 0 + }, + { + displayName: Translation.tr("Yes"), + value: 1 + }, + { + displayName: Translation.tr("Local only"), + value: 2 + } ] } } @@ -265,7 +322,7 @@ ApplicationWindow { RippleButtonWithIcon { materialIcon: "keyboard_alt" onClicked: { - Quickshell.execDetached(["qs", "-p", Quickshell.shellPath(""), "ipc", "call", "cheatsheet", "toggle"]) + Quickshell.execDetached(["qs", "-p", Quickshell.shellPath(""), "ipc", "call", "cheatsheet", "toggle"]); } mainContentComponent: Component { RowLayout { @@ -296,14 +353,14 @@ ApplicationWindow { materialIcon: "help" mainText: Translation.tr("Usage") onClicked: { - Qt.openUrlExternally("https://end-4.github.io/dots-hyprland-wiki/en/ii-qs/02usage/") + Qt.openUrlExternally("https://end-4.github.io/dots-hyprland-wiki/en/ii-qs/02usage/"); } } RippleButtonWithIcon { materialIcon: "construction" mainText: Translation.tr("Configuration") onClicked: { - Qt.openUrlExternally("https://end-4.github.io/dots-hyprland-wiki/en/ii-qs/03config/") + Qt.openUrlExternally("https://end-4.github.io/dots-hyprland-wiki/en/ii-qs/03config/"); } } } @@ -320,14 +377,14 @@ ApplicationWindow { nerdIcon: "󰊤" mainText: Translation.tr("GitHub") onClicked: { - Qt.openUrlExternally("https://github.com/end-4/dots-hyprland") + Qt.openUrlExternally("https://github.com/end-4/dots-hyprland"); } } RippleButtonWithIcon { materialIcon: "favorite" mainText: "Funny number" onClicked: { - Qt.openUrlExternally("https://github.com/sponsors/end-4") + Qt.openUrlExternally("https://github.com/sponsors/end-4"); } } } diff --git a/.config/quickshell/translations/zh_CN.json b/.config/quickshell/translations/zh_CN.json index 11d29dccd..4d80b222c 100644 --- a/.config/quickshell/translations/zh_CN.json +++ b/.config/quickshell/translations/zh_CN.json @@ -26,7 +26,6 @@ "Bluetooth": "蓝牙", "Brightness": "亮度", "Cancel": "取消", - "Chain of Thought": "思维链", "Cheat sheet": "快捷键表", "Choose model": "选择模型", "Clean stuff | Excellent quality, no NSFW": "清洁内容 | 优秀质量,无 NSFW", @@ -65,7 +64,6 @@ "Logout": "注销", "Markdown test": "Markdown 测试", "Math result": "数学结果", - "Night Light": "护眼模式", "No API key set for %1": "未为 %1 设置 API 密钥", "No audio source": "无音频源", "No media": "无媒体", @@ -112,7 +110,6 @@ "Unknown Artist": "未知艺术家", "Unknown Title": "未知标题", "Unknown function call: %1": "未知函数调用:%1", - "Uptime: %1": "运行时间:%1", "View Markdown source": "查看 Markdown 源码", "Volume": "音量", "Volume mixer": "音量混合器", @@ -123,20 +120,15 @@ "%1 does not require an API key": "%1 不需要 API 密钥", "%1 queries pending": "%1 个查询等待中", "%1 | Right-click to configure": "%1 | 右键点击进行配置", - "Set with /mode PROVIDER": "使用 /mode PROVIDER 设置", "Invalid API provider. Supported: \n-": "无效的 API 提供商。支持的:\n-", "Unknown command:": "未知命令:", "Type /key to get started with online models\nCtrl+O to expand the sidebar\nCtrl+P to detach sidebar into a window": "输入 /key 开始使用在线模型\nCtrl+O 展开侧边栏\nCtrl+P 将侧边栏分离为窗口", - "The current API used. Endpoint:": "当前使用的 API。端点:", "Provider set to": "提供商设置为", "Invalid model. Supported: \n```": "无效模型。支持的:\n```", "Switched to search mode. Continue with the user's request.": "已切换到搜索模式。继续处理用户请求。", - "Experimental | Online | Google's model\nCan do a little more but doesn't search quickly": "实验性 | 在线 | Google 模型\n功能更多但搜索速度较慢", - "To set an API key, pass it with the command\n\nTo view the key, pass \"get\" with the command
\n\n### For %1:\n\n**Link**: %2\n\n%3": "要设置 API 密钥,请将其与命令一起传递\n\n要查看密钥,请将 \"get\" 与命令一起传递
\n\n### 对于 %1:\n\n**链接**:%2\n\n%3", "Enter tags, or \"%1\" for commands": "输入标签,或 \"%1\" 查看命令", "Online via %1 | %2's model": "通过 %1 在线 | %2 的模型", "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": "没有找到结果。提示:\n- 检查您的标签和 NSFW 设置\n- 如果没有想到标签,请输入页码", - "Online | Google's model\nGives up-to-date information with search.": "在线 | Google 模型\n通过搜索提供最新信息。", "Settings": "设置", "Save chat": "保存对话", "Load chat": "加载对话", @@ -236,7 +228,6 @@ "Weather": "天气", "Pinned on startup": "启动时固定", "Tip: Hide icons and always show numbers for\nthe classic illogical-impulse experience": "提示:隐藏图标并始终显示数字以获得经典体验", - "Appearance": "外观", "Always show numbers": "总是显示数字", "Buttons": "按钮", "Keyboard toggle": "键盘切换", @@ -334,5 +325,48 @@ "High": "高", "Medium": "中", "Low": "低", - "System Resource": "系统资源" + "System Resource": "系统资源", + "Tint icons": "图标着色", + "Performance Profile toggle": "性能配置文件切换", + "**Instructions**: Log into Mistral account, go to Keys on the sidebar, click Create new key": "**说明**:登录 Mistral 账户,在侧边栏中选择 Keys,点击创建新密钥", + "Invalid arguments. Must provide `command`.": "参数无效。必须提供 `command`。", + "Thought": "思考", + "Online | Google's model\nA Gemini 2.5 Flash model optimized for cost-efficiency and high throughput.": "在线 | Google 模型\n针对成本效益和高吞吐量优化的 Gemini 2.5 Flash 模型。", + "Online | Google's model\nFast, can perform searches for up-to-date information": "在线 | Google 模型\n速度快,可搜索最新信息", + "Your package manager is running": "您的包管理器正在运行", + "Gives the model search capabilities (immediately)": "为模型提供搜索功能(即时)", + "Set the tool to use for the model.": "设置模型使用的工具。", + "Night Light | Right-click to toggle Auto mode": "夜间模式 | 右键切换自动模式", + "Online | %1's model | Delivers fast, responsive and well-formatted answers. Disadvantages: not very eager to do stuff; might make up unknown function calls": "在线 | %1 的模型 | 提供快速、响应迅速且格式良好的答案。缺点:不太积极主动;可能编造未知的函数调用", + "Depends on workspace": "取决于工作区", + "Usage: %1tool TOOL_NAME": "用法:%1tool 工具名称", + "Tray": "托盘", + "Usage: %1save CHAT_NAME": "用法:%1save 聊天名称", + "Approve": "批准", + "Depends on sidebars": "取决于侧边栏", + "Commands, edit configs, search.\nTakes an extra turn to switch to search mode if that's needed": "命令、编辑配置、搜索。\n如果需要,会额外执行一次切换到搜索模式", + "Overall appearance": "整体外观", + "Up %1": "运行 %1", + "Tool set to: %1": "工具设置为:%1", + "Wallpaper parallax": "壁纸视差", + "Online | Google's model\nGoogle's state-of-the-art multipurpose model that excels at coding and complex reasoning tasks.": "在线 | Google 模型\nGoogle 最先进的多用途模型,在编程和复杂推理任务方面表现卓越。", + "When enabled keeps the content of the right sidebar loaded to reduce the delay when opening,\nat the cost of around 15MB of consistent RAM usage. Delay significance depends on your system's performance.\nUsing a different kernel might help with this delay": "启用时会保持右侧边栏内容加载以减少打开延迟,\n代价是持续使用约 15MB 内存。延迟程度取决于您的系统性能。\n使用不同的内核可能有助于减少延迟", + "Tint app icons": "应用图标着色", + "Preferred wallpaper zoom (%)": "首选壁纸缩放比例 (%)", + "To set an API key, pass it with the %4 command\n\nTo view the key, pass \"get\" with the command
\n\n### For %1:\n\n**Link**: %2\n\n%3": "要设置 API 密钥,请使用 %4 命令传递\n\n要查看密钥,请在命令中传递 \"get\"
\n\n### 对于 %1:\n\n**链接**:%2\n\n%3", + "No API key\nSet it with /key YOUR_API_KEY": "无 API 密钥\n使用 /key YOUR_API_KEY 设置", + "Total token count\nInput: %1\nOutput: %2": "总令牌数\n输入:%1\n输出:%2", + "Disable tools": "禁用工具", + "API key is set\nChange with /key YOUR_API_KEY": "API 密钥已设置\n使用 /key YOUR_API_KEY 更改", + "Usage: %1load CHAT_NAME": "用法:%1load 聊天名称", + "Sidebars": "侧边栏", + "Temperature\nChange with /temp VALUE": "温度\n使用 /temp VALUE 更改", + "Current tool: %1\nSet it with %2tool TOOL": "当前工具:%1\n使用 %2tool TOOL 设置", + "There might be a download in progress": "可能有下载正在进行", + "Online | Google's model\nNewer model that's slower than its predecessor but should deliver higher quality answers": "在线 | Google 模型\n比前代模型更慢但应该提供更高质量答案的新模型", + "EasyEffects | Right-click to configure": "EasyEffects | 右键配置", + "Command rejected by user": "用户拒绝了命令", + "Invalid tool. Supported tools:\n- %1": "无效工具。支持的工具:\n- %1", + "Keep right sidebar loaded": "保持右侧边栏加载", + "Reject": "拒绝" }