From 6ae03b545cf3665abcf20ec33c0fa834d41e7b4a Mon Sep 17 00:00:00 2001 From: sansmoraxz Date: Fri, 1 Aug 2025 22:22:01 +0530 Subject: [PATCH 01/70] terminal env var --- .config/hypr/hyprland/keybinds.conf | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.config/hypr/hyprland/keybinds.conf b/.config/hypr/hyprland/keybinds.conf index 5a3140069..0cc42bcd5 100644 --- a/.config/hypr/hyprland/keybinds.conf +++ b/.config/hypr/hyprland/keybinds.conf @@ -201,10 +201,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 From dc2777703d74a8a119246284dae4938567c59897 Mon Sep 17 00:00:00 2001 From: sansmoraxz Date: Fri, 1 Aug 2025 22:47:01 +0530 Subject: [PATCH 02/70] update default terminal value --- .config/hypr/hyprland/env.conf | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.config/hypr/hyprland/env.conf b/.config/hypr/hyprland/env.conf index 6f1316304..c58b67a26 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" From 2b554cf2863e79f990e2da79b6c34fbd9f20cdcd Mon Sep 17 00:00:00 2001 From: Souyama Date: Sat, 2 Aug 2025 00:28:47 +0530 Subject: [PATCH 03/70] Update env.conf remote direct quotes --- .config/hypr/hyprland/env.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.config/hypr/hyprland/env.conf b/.config/hypr/hyprland/env.conf index c58b67a26..62e931fd7 100644 --- a/.config/hypr/hyprland/env.conf +++ b/.config/hypr/hyprland/env.conf @@ -24,4 +24,4 @@ env = XDG_MENU_PREFIX, plasma- env = ILLOGICAL_IMPULSE_VIRTUAL_ENV, ~/.local/state/quickshell/.venv # ######## Terminal application ######### -env = TERMINAL, "kitty -1" +env = TERMINAL,kitty -1 From 0b9717c2a5f3fd044bfa38f8ae2b99c7c4773b51 Mon Sep 17 00:00:00 2001 From: Nyx <189459385+nyx-4@users.noreply.github.com> Date: Sat, 2 Aug 2025 22:17:56 +0500 Subject: [PATCH 04/70] Added pomodoro timer in sidebarRight, closes #1477 Signed-off-by: Nyx <189459385+nyx-4@users.noreply.github.com> --- .../sidebarRight/BottomWidgetGroup.qml | 13 +- .../sidebarRight/pomodoro/PomodoroWidget.qml | 356 ++++++++++++++++++ .config/quickshell/ii/services/Pomodoro.qml | 69 ++++ 3 files changed, 437 insertions(+), 1 deletion(-) create mode 100644 .config/quickshell/ii/modules/sidebarRight/pomodoro/PomodoroWidget.qml create mode 100644 .config/quickshell/ii/services/Pomodoro.qml 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/pomodoro/PomodoroWidget.qml b/.config/quickshell/ii/modules/sidebarRight/pomodoro/PomodoroWidget.qml new file mode 100644 index 000000000..b39be28ce --- /dev/null +++ b/.config/quickshell/ii/modules/sidebarRight/pomodoro/PomodoroWidget.qml @@ -0,0 +1,356 @@ +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": "timer_play"}, + {"name": Translation.tr("Stopwatch"), "icon": "timer"} + ] + property bool showDialog: false + property int dialogMargins: 20 + property int fabSize: 48 + property int fabMargins: 14 + + + // These are keybinds, make sure to change them. + Keys.onPressed: (event) => { + if ((event.key === Qt.Key_PageDown || event.key === Qt.Key_PageUp) && event.modifiers === Qt.NoModifier) { + 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 && !showDialog) { + // Toggle start/pause with Space key + if (currentTab === 0) { + Pomodoro.togglePomodoro() + } else { + Pomodoro.toggleStopwatch() + } + event.accepted = true + } else if (event.key === Qt.Key_R && !showDialog) { + // Reset with R key + if (currentTab === 0) { + Pomodoro.pomodoroReset() + } else { + Pomodoro.stopwatchReset() + } + event.accepted = true + } else if (event.key === Qt.Key_Escape && showDialog) { + showDialog = false + event.accepted = true + } + } + + Timer { + id: pomodoroTimer + interval: 1000 + running: Pomodoro.isPomodoroRunning + repeat: true + onTriggered: Pomodoro.tickSecond() + } + + Timer { + id: stopwatchTimer + interval: 1000 + running: Pomodoro.isStopwatchRunning + repeat: true + onTriggered: { + Pomodoro.stopwatchTime += 1 + } + } + + + 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 + } + + // Pomodoro Timer Tab + Item { + ColumnLayout { + anchors.horizontalCenter: parent.horizontalCenter + spacing: 18 + + StyledText { + Layout.alignment: Qt.AlignHCenter + text: Pomodoro.timeFormattedPomodoro() + font.pixelSize: 50 + color: Appearance.m3colors.m3onSurface + } + + RowLayout { + Layout.alignment: Qt.AlignHCenter + spacing: 20 + + DialogButton { + buttonText: Pomodoro.isPomodoroRunning ? Translation.tr("Pause") : Translation.tr("Start") + Layout.preferredWidth: 90 + Layout.preferredHeight: 35 + font.pixelSize: Appearance.font.pixelSize.larger + onClicked: Pomodoro.togglePomodoro() + background: Rectangle { + color: Appearance.m3colors.m3onSecondary + radius: Appearance.rounding.normal + border.color: Appearance.m3colors.m3outline + border.width: 1 + } + } + + StyledText { + Layout.alignment: Qt.AlignHCenter + text: Pomodoro.isPomodoroBreak ? Translation.tr("Break time") : Translation.tr("Focus time") + font.pixelSize: Appearance.font.pixelSize.largest + color: Appearance.m3colors.m3onSurface + } + + DialogButton { + buttonText: Translation.tr("Reset") + Layout.preferredWidth: 90 + Layout.preferredHeight: 35 + font.pixelSize: Appearance.font.pixelSize.larger + onClicked: Pomodoro.pomodoroReset() + background: Rectangle { + color: Appearance.m3colors.m3onError + radius: Appearance.rounding.normal + border.color: Appearance.m3colors.m3outline + border.width: 1 + } + } + } + + ColumnLayout { + Layout.alignment: Qt.AlignHCenter + spacing: 0 + + StyledText { + text: Translation.tr("Focus Duration: %1 min").arg(Pomodoro.pomodoroWorkTime / 60) + color: Appearance.m3colors.m3onSurface + } + Slider { + id: workTimeSlider + Layout.fillWidth: true + from: 5 + to: 120 + stepSize: 1 + value: Pomodoro.pomodoroWorkTime / 60 + onValueChanged: Pomodoro.pomodoroWorkTime = value * 60 + handle: Rectangle { + x: workTimeSlider.leftPadding + workTimeSlider.visualPosition * (workTimeSlider.availableWidth - width) + y: workTimeSlider.topPadding + (workTimeSlider.availableHeight - height) / 2 + implicitWidth: 20 + implicitHeight: 20 + radius: 10 + color: Appearance.m3colors.m3onSecondary + border.color: Appearance.m3colors.m3outline + } + } + + StyledText { + text: Translation.tr("Break Duration: %1 min").arg(Pomodoro.pomodoroBreakTime / 60) + color: Appearance.m3colors.m3onSurface + } + Slider { + id: breakTimeSlider + Layout.fillWidth: true + from: 1 + to: 60 + stepSize: 1 + value: Pomodoro.pomodoroBreakTime / 60 + onValueChanged: Pomodoro.pomodoroBreakTime = value * 60 + handle: Rectangle { + x: breakTimeSlider.leftPadding + breakTimeSlider.visualPosition * (breakTimeSlider.availableWidth - width) + y: breakTimeSlider.topPadding + (breakTimeSlider.availableHeight - height) / 2 + implicitWidth: 20 + implicitHeight: 20 + radius: 10 + color: Appearance.m3colors.m3onSecondary + border.color: Appearance.m3colors.m3outline + } + } + } + } + } + + // Stopwatch Tab + Item { + ColumnLayout { + anchors.horizontalCenter: parent.horizontalCenter + spacing: 18 + + StyledText { + Layout.alignment: Qt.AlignHCenter + text: Pomodoro.timeFormattedStopwatch() + font.pixelSize: 50 + color: Appearance.m3colors.m3onSurface + } + + RowLayout { + Layout.alignment: Qt.AlignHCenter + spacing: 20 + + DialogButton { + buttonText: Pomodoro.isStopwatchRunning ? Translation.tr("Pause") : Translation.tr("Start") + Layout.preferredWidth: 90 + Layout.preferredHeight: 35 + font.pixelSize: Appearance.font.pixelSize.larger + onClicked: Pomodoro.toggleStopwatch() + background: Rectangle { + color: Appearance.m3colors.m3onSecondary + radius: Appearance.rounding.normal + border.color: Appearance.m3colors.m3outline + border.width: 1 + } + } + + StyledText { + Layout.alignment: Qt.AlignHCenter + text: Translation.tr("Stopwatch") + font.pixelSize: Appearance.font.pixelSize.largest + color: Appearance.m3colors.m3onSurface + } + + DialogButton { + buttonText: Translation.tr("Reset") + Layout.preferredWidth: 90 + Layout.preferredHeight: 35 + font.pixelSize: Appearance.font.pixelSize.larger + onClicked: Pomodoro.stopwatchReset() + background: Rectangle { + color: Appearance.m3colors.m3onError + radius: Appearance.rounding.normal + border.color: Appearance.m3colors.m3outline + border.width: 1 + } + } + } + } + } + } + } + + // + FAB + StyledRectangularShadow { + target: fabButton + radius: fabButton.buttonRadius + blur: 0.6 * Appearance.sizes.elevationMargin + } + FloatingActionButton { + id: fabButton + anchors.right: parent.right + anchors.bottom: parent.bottom + anchors.rightMargin: root.fabMargins + anchors.bottomMargin: root.fabMargins + + onClicked: { + if (currentTab === 0) { + Pomodoro.togglePomodoro() + } else { + Pomodoro.toggleStopwatch() + } + } + + contentItem: MaterialSymbol { + text: (currentTab === 0 && Pomodoro.isPomodoroRunning) || (currentTab === 1 && Pomodoro.isStopwatchRunning) ? "pause" : "play_arrow" + horizontalAlignment: Text.AlignHCenter + iconSize: Appearance.font.pixelSize.huge + color: Appearance.m3colors.m3onPrimaryContainer + } + } +} \ No newline at end of file diff --git a/.config/quickshell/ii/services/Pomodoro.qml b/.config/quickshell/ii/services/Pomodoro.qml new file mode 100644 index 000000000..e29ef25ab --- /dev/null +++ b/.config/quickshell/ii/services/Pomodoro.qml @@ -0,0 +1,69 @@ +pragma Singleton +pragma ComponentBehavior: Bound + +import qs.modules.common +import Quickshell +import Quickshell.Io +import QtQuick + +/** + * Simple Pomodoro time manager. + */ +Singleton { + id: root + + property int pomodoroWorkTime: 25 * 60 // 25 minutes in seconds + property int pomodoroBreakTime: 5 * 60 // 5 minutes in seconds + property int pomodoroTime: pomodoroWorkTime + property bool isPomodoroRunning: false + property bool isPomodoroBreak: false + property int stopwatchTime: 0 + property bool isStopwatchRunning: false + + function togglePomodoro() { + isPomodoroRunning = !isPomodoroRunning; + } + + function toggleStopwatch() { + isStopwatchRunning = !isStopwatchRunning; + } + + function pomodoroReset() { + pomodoroTime = pomodoroWorkTime; + isPomodoroRunning = false; + isPomodoroBreak = false; + } + + function stopwatchReset() { + stopwatchTime = 0; + isStopwatchRunning = false; + } + + function tickSecond() { + if (pomodoroTime > 0) { + pomodoroTime--; + } else { + isPomodoroBreak = !isPomodoroBreak; + pomodoroTime = isPomodoroBreak ? pomodoroBreakTime : pomodoroWorkTime; + if (isPomodoroBreak) { + Quickshell.execDetached(["bash", "-c", `notify-send "☕ Short Break!" "Relax for ${Math.floor(pomodoroBreakTime / 60)} minutes."`]); + } else { + Quickshell.execDetached(["bash", "-c", `notify-send "🔴 Pomodoro started!" "Focus for ${Math.floor(pomodoroWorkTime / 60)} minutes."`]); + } + } + } + + function timeFormattedPomodoro() { + let minutes = Math.floor(pomodoroTime / 60); + let seconds = Math.floor(pomodoroTime % 60); + return `${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`; + } + + function timeFormattedStopwatch() { + let totalSeconds = Math.floor(stopwatchTime); + let hours = Math.floor(totalSeconds / 3600); + let minutes = Math.floor((totalSeconds % 3600) / 60); + let seconds = Math.floor(totalSeconds % 60); + return `${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`; + } +} From 9df7129c6c15e6e8509b3d19a42ac583c3428bc9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=88=E6=9C=88?= <3600911665@qq.com> Date: Sun, 3 Aug 2025 09:42:43 +0800 Subject: [PATCH 05/70] translation: update zh_CN.json --- .config/quickshell/translations/zh_CN.json | 56 +++++++++++++++++----- 1 file changed, 45 insertions(+), 11 deletions(-) diff --git a/.config/quickshell/translations/zh_CN.json b/.config/quickshell/translations/zh_CN.json index 34dc54157..cfaf50e9b 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": "键盘切换", @@ -310,5 +301,48 @@ "Sunset": "日落", "Humidity": "湿度", "Wind": "风", - "Precipitation": "降水量" -} + "Precipitation": "降水量", + "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": "拒绝" +} \ No newline at end of file From e4b761917aec42dee92028d9cd295adbf61868b7 Mon Sep 17 00:00:00 2001 From: Nyx <189459385+nyx-4@users.noreply.github.com> Date: Sun, 3 Aug 2025 20:36:29 +0500 Subject: [PATCH 06/70] Polished the UI and added Persistent Config option. Signed-off-by: Nyx <189459385+nyx-4@users.noreply.github.com> Changelog: Added Config.options.time.pomodoro options in Config.qml Restructered the Pomodoro logic and added Long break Used timestamp instead of naively counting down. Major UI tweaks. --- .../quickshell/ii/modules/common/Config.qml | 6 + .../sidebarRight/pomodoro/PomodoroWidget.qml | 195 ++++++++---------- .config/quickshell/ii/services/Pomodoro.qml | 118 ++++++----- 3 files changed, 164 insertions(+), 155 deletions(-) diff --git a/.config/quickshell/ii/modules/common/Config.qml b/.config/quickshell/ii/modules/common/Config.qml index bcbbd1e33..3ee804fda 100644 --- a/.config/quickshell/ii/modules/common/Config.qml +++ b/.config/quickshell/ii/modules/common/Config.qml @@ -253,6 +253,12 @@ 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 int breaktime: 300 + property int cycle: 4 + property int focus: 1500 + property int longbreak: 1200 + } } property JsonObject windows: JsonObject { diff --git a/.config/quickshell/ii/modules/sidebarRight/pomodoro/PomodoroWidget.qml b/.config/quickshell/ii/modules/sidebarRight/pomodoro/PomodoroWidget.qml index b39be28ce..ff39f1b6b 100644 --- a/.config/quickshell/ii/modules/sidebarRight/pomodoro/PomodoroWidget.qml +++ b/.config/quickshell/ii/modules/sidebarRight/pomodoro/PomodoroWidget.qml @@ -166,102 +166,102 @@ Item { Item { ColumnLayout { anchors.horizontalCenter: parent.horizontalCenter - spacing: 18 - - StyledText { - Layout.alignment: Qt.AlignHCenter - text: Pomodoro.timeFormattedPomodoro() - font.pixelSize: 50 - color: Appearance.m3colors.m3onSurface - } + spacing: 20 RowLayout { - Layout.alignment: Qt.AlignHCenter - spacing: 20 + spacing: 40 + // The Pomodoro timer circle + CircularProgress { + Layout.alignment: Qt.AlignHCenter + lineWidth: 7 + value: { + let pomodoroTotalTime = Pomodoro.isPomodoroBreak ? Pomodoro.pomodoroBreakTime : Pomodoro.pomodoroFocusTime + return Pomodoro.getPomodoroSecondsLeft / pomodoroTotalTime + } + size: 125 + secondaryColor: Appearance.colors.colSecondaryContainer + primaryColor: Appearance.m3colors.m3onSecondaryContainer + enableAnimation: true - DialogButton { - buttonText: Pomodoro.isPomodoroRunning ? Translation.tr("Pause") : Translation.tr("Start") - Layout.preferredWidth: 90 - Layout.preferredHeight: 35 - font.pixelSize: Appearance.font.pixelSize.larger - onClicked: Pomodoro.togglePomodoro() - background: Rectangle { - color: Appearance.m3colors.m3onSecondary - radius: Appearance.rounding.normal - border.color: Appearance.m3colors.m3outline - border.width: 1 + ColumnLayout { + anchors.centerIn: parent + + StyledText { + Layout.alignment: Qt.AlignHCenter + text: { + let minutes = Math.floor(Pomodoro.getPomodoroSecondsLeft / 60).toString().padStart(2, '0') + let seconds = Math.floor(Pomodoro.getPomodoroSecondsLeft % 60).toString().padStart(2, '0') + return `${minutes}:${seconds}` + } + font.pixelSize: Appearance.font.pixelSize.hugeass + 4 + color: Appearance.m3colors.m3onSurface + } + StyledText { + Layout.alignment: Qt.AlignHCenter + text: Pomodoro.isPomodoroBreak ? Translation.tr("Break") : Translation.tr("Focus") + font.pixelSize: Appearance.font.pixelSize.normal + color: Appearance.m3colors.m3onSurface + } } } - StyledText { + // The Start/Stop and Reset buttons + ColumnLayout { Layout.alignment: Qt.AlignHCenter - text: Pomodoro.isPomodoroBreak ? Translation.tr("Break time") : Translation.tr("Focus time") - font.pixelSize: Appearance.font.pixelSize.largest - color: Appearance.m3colors.m3onSurface - } + spacing: 20 - DialogButton { - buttonText: Translation.tr("Reset") - Layout.preferredWidth: 90 - Layout.preferredHeight: 35 - font.pixelSize: Appearance.font.pixelSize.larger - onClicked: Pomodoro.pomodoroReset() - background: Rectangle { - color: Appearance.m3colors.m3onError - radius: Appearance.rounding.normal - border.color: Appearance.m3colors.m3outline - border.width: 1 + RippleButton { + buttonText: Pomodoro.isPomodoroRunning ? Translation.tr("Pause") : Translation.tr("Start") + Layout.preferredHeight: 35 + Layout.preferredWidth: 90 + font.pixelSize: Appearance.font.pixelSize.larger + onClicked: Pomodoro.togglePomodoro() + colBackground: Appearance.m3colors.m3onSecondary + colBackgroundHover: Appearance.m3colors.m3onSecondary + } + + RippleButton { + buttonText: Translation.tr("Reset") + Layout.preferredHeight: 35 + Layout.preferredWidth: 90 + font.pixelSize: Appearance.font.pixelSize.larger + onClicked: Pomodoro.pomodoroReset() + colBackground: Appearance.m3colors.m3onError + colBackgroundHover: Appearance.m3colors.m3onError } } } + // The sliders for adjusting duration ColumnLayout { Layout.alignment: Qt.AlignHCenter - spacing: 0 + spacing: 10 - StyledText { - text: Translation.tr("Focus Duration: %1 min").arg(Pomodoro.pomodoroWorkTime / 60) - color: Appearance.m3colors.m3onSurface + ConfigSpinBox { + text: Translation.tr("Focus Duration: ") + value: Pomodoro.pomodoroFocusTime / 60 + onValueChanged: { + Pomodoro.pomodoroFocusTime = value * 60 + Config.options.time.pomodoro.focus = value * 60 + } + Layout.alignment: Qt.AlignCenter } - Slider { - id: workTimeSlider - Layout.fillWidth: true - from: 5 - to: 120 - stepSize: 1 - value: Pomodoro.pomodoroWorkTime / 60 - onValueChanged: Pomodoro.pomodoroWorkTime = value * 60 - handle: Rectangle { - x: workTimeSlider.leftPadding + workTimeSlider.visualPosition * (workTimeSlider.availableWidth - width) - y: workTimeSlider.topPadding + (workTimeSlider.availableHeight - height) / 2 - implicitWidth: 20 - implicitHeight: 20 - radius: 10 - color: Appearance.m3colors.m3onSecondary - border.color: Appearance.m3colors.m3outline + + ConfigSpinBox { + text: Translation.tr("Break Duration:") + value: Pomodoro.pomodoroBreakTime / 60 + onValueChanged: { + Config.options.time.pomodoro.breaktime = value * 60 + Pomodoro.pomodoroBreakTime = value * 60 } } - StyledText { - text: Translation.tr("Break Duration: %1 min").arg(Pomodoro.pomodoroBreakTime / 60) - color: Appearance.m3colors.m3onSurface - } - Slider { - id: breakTimeSlider - Layout.fillWidth: true - from: 1 - to: 60 - stepSize: 1 - value: Pomodoro.pomodoroBreakTime / 60 - onValueChanged: Pomodoro.pomodoroBreakTime = value * 60 - handle: Rectangle { - x: breakTimeSlider.leftPadding + breakTimeSlider.visualPosition * (breakTimeSlider.availableWidth - width) - y: breakTimeSlider.topPadding + (breakTimeSlider.availableHeight - height) / 2 - implicitWidth: 20 - implicitHeight: 20 - radius: 10 - color: Appearance.m3colors.m3onSecondary - border.color: Appearance.m3colors.m3outline + ConfigSpinBox { + text: Translation.tr("Long Break Duration:") + value: Pomodoro.pomodoroLongBreakTime / 60 + onValueChanged:{ + Pomodoro.pomodoroLongBreakTime = value * 60 + Config.options.time.pomodoro.longbreak = value * 60 } } } @@ -276,7 +276,13 @@ Item { StyledText { Layout.alignment: Qt.AlignHCenter - text: Pomodoro.timeFormattedStopwatch() + text: { + let totalSeconds = Math.floor(Pomodoro.stopwatchTime) + let hours = Math.floor(totalSeconds / 3600).toString().padStart(2, '0') + let minutes = Math.floor((totalSeconds % 3600) / 60).toString().padStart(2, '0') + let seconds = Math.floor(totalSeconds % 60).toString().padStart(2, '0') + return `${hours}:${minutes}:${seconds}` + } font.pixelSize: 50 color: Appearance.m3colors.m3onSurface } @@ -302,7 +308,7 @@ Item { StyledText { Layout.alignment: Qt.AlignHCenter text: Translation.tr("Stopwatch") - font.pixelSize: Appearance.font.pixelSize.largest + font.pixelSize: Appearance.font.pixelSize.large color: Appearance.m3colors.m3onSurface } @@ -324,33 +330,4 @@ Item { } } } - - // + FAB - StyledRectangularShadow { - target: fabButton - radius: fabButton.buttonRadius - blur: 0.6 * Appearance.sizes.elevationMargin - } - FloatingActionButton { - id: fabButton - anchors.right: parent.right - anchors.bottom: parent.bottom - anchors.rightMargin: root.fabMargins - anchors.bottomMargin: root.fabMargins - - onClicked: { - if (currentTab === 0) { - Pomodoro.togglePomodoro() - } else { - Pomodoro.toggleStopwatch() - } - } - - contentItem: MaterialSymbol { - text: (currentTab === 0 && Pomodoro.isPomodoroRunning) || (currentTab === 1 && Pomodoro.isStopwatchRunning) ? "pause" : "play_arrow" - horizontalAlignment: Text.AlignHCenter - iconSize: Appearance.font.pixelSize.huge - color: Appearance.m3colors.m3onPrimaryContainer - } - } -} \ No newline at end of file +} diff --git a/.config/quickshell/ii/services/Pomodoro.qml b/.config/quickshell/ii/services/Pomodoro.qml index e29ef25ab..5244e5382 100644 --- a/.config/quickshell/ii/services/Pomodoro.qml +++ b/.config/quickshell/ii/services/Pomodoro.qml @@ -1,10 +1,12 @@ pragma Singleton pragma ComponentBehavior: Bound +import qs import qs.modules.common -import Quickshell -import Quickshell.Io -import QtQuick + +import Quickshell; +import Quickshell.Io; +import QtQuick; /** * Simple Pomodoro time manager. @@ -12,58 +14,82 @@ import QtQuick Singleton { id: root - property int pomodoroWorkTime: 25 * 60 // 25 minutes in seconds - property int pomodoroBreakTime: 5 * 60 // 5 minutes in seconds - property int pomodoroTime: pomodoroWorkTime - property bool isPomodoroRunning: false + // TODO: read these values from a config file. + property int pomodoroFocusTime: Config.options.time.pomodoro.focus + property int pomodoroBreakTime: Config.options.time.pomodoro.breaktime + property int pomodoroLongBreakTime: Config.options.time.pomodoro.longbreak + property int pomodoroLongBreakCycle: Config.options.time.pomodoro.cycle + + property int pomodoroTimeLeft: pomodoroFocusTime + property int getPomodoroSecondsLeft: pomodoroFocusTime + property int pomodoroTimeStarted: getCurrentTime() // The time pomodoro was last Resumed property bool isPomodoroBreak: false + property bool isPomodoroRunning: false + property int pomodoroCycle: 1 + property int stopwatchTime: 0 property bool isStopwatchRunning: false + // Pause and Resume button function togglePomodoro() { - isPomodoroRunning = !isPomodoroRunning; - } - - function toggleStopwatch() { - isStopwatchRunning = !isStopwatchRunning; - } - - function pomodoroReset() { - pomodoroTime = pomodoroWorkTime; - isPomodoroRunning = false; - isPomodoroBreak = false; - } - - function stopwatchReset() { - stopwatchTime = 0; - isStopwatchRunning = false; - } - - function tickSecond() { - if (pomodoroTime > 0) { - pomodoroTime--; - } else { - isPomodoroBreak = !isPomodoroBreak; - pomodoroTime = isPomodoroBreak ? pomodoroBreakTime : pomodoroWorkTime; - if (isPomodoroBreak) { - Quickshell.execDetached(["bash", "-c", `notify-send "☕ Short Break!" "Relax for ${Math.floor(pomodoroBreakTime / 60)} minutes."`]); - } else { - Quickshell.execDetached(["bash", "-c", `notify-send "🔴 Pomodoro started!" "Focus for ${Math.floor(pomodoroWorkTime / 60)} minutes."`]); - } + isPomodoroRunning = !isPomodoroRunning + if (isPomodoroRunning) { // Pressed Start button + pomodoroTimeStarted = getCurrentTime() + } else { // Pressed Pause button + pomodoroTimeLeft -= (getCurrentTime() - pomodoroTimeStarted) } } - function timeFormattedPomodoro() { - let minutes = Math.floor(pomodoroTime / 60); - let seconds = Math.floor(pomodoroTime % 60); - return `${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`; + // Reset button + function pomodoroReset() { + pomodoroTimeLeft = pomodoroFocusTime + getPomodoroSecondsLeft = pomodoroFocusTime + isPomodoroBreak = false + isPomodoroRunning = false } - function timeFormattedStopwatch() { - let totalSeconds = Math.floor(stopwatchTime); - let hours = Math.floor(totalSeconds / 3600); - let minutes = Math.floor((totalSeconds % 3600) / 60); - let seconds = Math.floor(totalSeconds % 60); - return `${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`; + function tickSecond() { + if (getCurrentTime() >= pomodoroTimeStarted + pomodoroTimeLeft) { + isPomodoroBreak = !isPomodoroBreak + pomodoroTimeStarted += pomodoroTimeLeft + pomodoroTimeLeft = isPomodoroBreak ? pomodoroBreakTime : pomodoroFocusTime + + if (isPomodoroBreak && pomodoroCycle % pomodoroLongBreakCycle == 0) { // isPomodoroLongBreak + Quickshell.execDetached([ + "notify-send", + Translation.tr("🌿 Long Break!"), + Translation.tr(`Relax for %1 minutes.`).arg(Math.floor(pomodoroLongBreakTime / 60)) + ]) + } else if(isPomodoroBreak){ + Quickshell.execDetached([ + "notify-send", + Translation.tr("☕ Short Break!"), + Translation.tr(`Relax for %1 minutes.`).arg(Math.floor(pomodoroBreakTime / 60)) + ]) + } else { + Quickshell.execDetached([ + "notify-send", + Translation.tr("🔴 Pomodoro started!"), + Translation.tr(`Focus for %1 minutes.`).arg(Math.floor(pomodoroFocusTime / 60)) + ]) + pomodoroCycle += 1 + } + } + + getPomodoroSecondsLeft = (pomodoroTimeStarted + pomodoroTimeLeft) - getCurrentTime() + } + + function getCurrentTime() { + return Math.floor(Date.now() / 1000) + } + + // Stopwatch functions + function toggleStopwatch() { + isStopwatchRunning = !isStopwatchRunning + } + + function stopwatchReset() { + stopwatchTime = 0 + isStopwatchRunning = false } } From d632111cf97d21be2e116b1ededcfd4d6a6add57 Mon Sep 17 00:00:00 2001 From: finjener Date: Sun, 3 Aug 2025 22:42:42 +0100 Subject: [PATCH 07/70] quickshell: fix qml null safety and monitor property errors --- .config/quickshell/ii/modules/background/Background.qml | 8 ++++---- .config/quickshell/ii/modules/bar/ActiveWindow.qml | 6 +++--- .config/quickshell/ii/modules/bar/weather/WeatherBar.qml | 4 ++-- .config/quickshell/ii/modules/overview/Overview.qml | 2 +- .config/quickshell/ii/modules/overview/OverviewWidget.qml | 4 ++-- .local/state/quickshell/user/ai/chats/chat.json | 7 +++++++ 6 files changed, 19 insertions(+), 12 deletions(-) create mode 100644 .local/state/quickshell/user/ai/chats/chat.json diff --git a/.config/quickshell/ii/modules/background/Background.qml b/.config/quickshell/ii/modules/background/Background.qml index dc6bdece1..5cffaa9f3 100644 --- a/.config/quickshell/ii/modules/background/Background.qml +++ b/.config/quickshell/ii/modules/background/Background.qml @@ -28,12 +28,12 @@ Scope { // Hide when fullscreen readonly property Toplevel activeWindow: ToplevelManager.activeToplevel - property bool focusingThisMonitor: HyprlandData.activeWorkspace.monitor == monitor.name + property bool focusingThisMonitor: HyprlandData.activeWorkspace?.monitor == monitor?.name visible: !(activeWindow?.fullscreen && activeWindow?.activated && focusingThisMonitor) // 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 @@ -157,7 +157,7 @@ Scope { const lower = Math.floor(bgRoot.firstWorkspaceId / chunkSize) * chunkSize; const upper = Math.ceil(bgRoot.lastWorkspaceId / chunkSize) * chunkSize; const range = upper - lower; - return (Config.options.background.parallax.enableWorkspace ? ((bgRoot.monitor.activeWorkspace.id - lower) / range) : 0.5) + return (Config.options.background.parallax.enableWorkspace ? ((bgRoot.monitor?.activeWorkspace?.id ?? 1 - lower) / range) : 0.5) + (0.15 * GlobalStates.sidebarRightOpen * Config.options.background.parallax.enableSidebar) - (0.15 * GlobalStates.sidebarLeftOpen * Config.options.background.parallax.enableSidebar) } @@ -199,7 +199,7 @@ Scope { ColumnLayout { id: clockColumn - anchors.centerIn: wallpaper + anchors.centerIn: parent spacing: 0 StyledText { diff --git a/.config/quickshell/ii/modules/bar/ActiveWindow.qml b/.config/quickshell/ii/modules/bar/ActiveWindow.qml index 636b23136..bb2b59e5f 100644 --- a/.config/quickshell/ii/modules/bar/ActiveWindow.qml +++ b/.config/quickshell/ii/modules/bar/ActiveWindow.qml @@ -14,8 +14,8 @@ Item { readonly property Toplevel activeWindow: ToplevelManager.activeToplevel property string activeWindowAddress: `0x${activeWindow?.HyprlandToplevel?.address}` - property bool focusingThisMonitor: HyprlandData.activeWorkspace.monitor == monitor.name - property var biggestWindow: HyprlandData.biggestWindowForWorkspace(HyprlandData.monitors[root.monitor.id]?.activeWorkspace.id) + property bool focusingThisMonitor: HyprlandData.activeWorkspace?.monitor == monitor?.name + property var biggestWindow: HyprlandData.biggestWindowForWorkspace(HyprlandData.monitors[root.monitor?.id]?.activeWorkspace.id) implicitWidth: colLayout.implicitWidth @@ -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/weather/WeatherBar.qml b/.config/quickshell/ii/modules/bar/weather/WeatherBar.qml index 363d9ba53..933750f9a 100644 --- a/.config/quickshell/ii/modules/bar/weather/WeatherBar.qml +++ b/.config/quickshell/ii/modules/bar/weather/WeatherBar.qml @@ -21,7 +21,7 @@ MouseArea { MaterialSymbol { fill: 0 - text: WeatherIcons.codeToName[Weather.data.wCode] + text: WeatherIcons.codeToName[Weather.data?.wCode] ?? "question_mark" iconSize: Appearance.font.pixelSize.large color: Appearance.colors.colOnLayer1 Layout.alignment: Qt.AlignVCenter @@ -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/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..9cfe9147e 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,7 +149,7 @@ 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 + const inMonitor = root.monitor?.id === win.monitor return inWorkspaceGroup && inMonitor; }) } diff --git a/.local/state/quickshell/user/ai/chats/chat.json b/.local/state/quickshell/user/ai/chats/chat.json new file mode 100644 index 000000000..20540c1d0 --- /dev/null +++ b/.local/state/quickshell/user/ai/chats/chat.json @@ -0,0 +1,7 @@ +{ + "messages": [], + "created": "2024-01-01T00:00:00Z", + "updated": "2024-01-01T00:00:00Z", + "title": "Default Chat", + "id": "default" +} \ No newline at end of file From bfb7ccffb5e13b941510d058ae5ae0a89eac95b6 Mon Sep 17 00:00:00 2001 From: Nyx <189459385+nyx-4@users.noreply.github.com> Date: Tue, 5 Aug 2025 16:06:22 +0500 Subject: [PATCH 08/70] Reworked on Pomodoro's UI and added Stopwatch Signed-off-by: Nyx <189459385+nyx-4@users.noreply.github.com> --- .../quickshell/ii/modules/common/Config.qml | 5 +- .../sidebarRight/pomodoro/PomodoroWidget.qml | 329 +++++++++++++----- .config/quickshell/ii/services/Pomodoro.qml | 89 +++-- 3 files changed, 300 insertions(+), 123 deletions(-) diff --git a/.config/quickshell/ii/modules/common/Config.qml b/.config/quickshell/ii/modules/common/Config.qml index 3ee804fda..032fd441f 100644 --- a/.config/quickshell/ii/modules/common/Config.qml +++ b/.config/quickshell/ii/modules/common/Config.qml @@ -254,10 +254,11 @@ Singleton { property string format: "hh:mm" property string dateFormat: "ddd, dd/MM" property JsonObject pomodoro: JsonObject { - property int breaktime: 300 + property int breakTime: 300 property int cycle: 4 property int focus: 1500 - property int longbreak: 1200 + property int longBreak: 1200 + property bool running: false } } diff --git a/.config/quickshell/ii/modules/sidebarRight/pomodoro/PomodoroWidget.qml b/.config/quickshell/ii/modules/sidebarRight/pomodoro/PomodoroWidget.qml index ff39f1b6b..b3c360665 100644 --- a/.config/quickshell/ii/modules/sidebarRight/pomodoro/PomodoroWidget.qml +++ b/.config/quickshell/ii/modules/sidebarRight/pomodoro/PomodoroWidget.qml @@ -2,9 +2,13 @@ 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 @@ -13,10 +17,8 @@ Item { {"name": Translation.tr("Pomodoro"), "icon": "timer_play"}, {"name": Translation.tr("Stopwatch"), "icon": "timer"} ] - property bool showDialog: false - property int dialogMargins: 20 - property int fabSize: 48 - property int fabMargins: 14 + property int lapsListItemPadding: 8 + property int lapsListItemSpacing: 5 // These are keybinds, make sure to change them. @@ -29,7 +31,7 @@ Item { } event.accepted = true } else if (event.key === Qt.Key_Space && !showDialog) { - // Toggle start/pause with Space key + // Toggle start/stop with Space key if (currentTab === 0) { Pomodoro.togglePomodoro() } else { @@ -52,20 +54,18 @@ Item { Timer { id: pomodoroTimer - interval: 1000 - running: Pomodoro.isPomodoroRunning + interval: 200 + running: Config.options.time.pomodoro.running repeat: true onTriggered: Pomodoro.tickSecond() } Timer { id: stopwatchTimer - interval: 1000 + interval: 10 running: Pomodoro.isStopwatchRunning repeat: true - onTriggered: { - Pomodoro.stopwatchTime += 1 - } + onTriggered: Pomodoro.tick10ms() } @@ -174,17 +174,19 @@ Item { CircularProgress { Layout.alignment: Qt.AlignHCenter lineWidth: 7 + gapAngle: Math.PI / 14 value: { let pomodoroTotalTime = Pomodoro.isPomodoroBreak ? Pomodoro.pomodoroBreakTime : Pomodoro.pomodoroFocusTime return Pomodoro.getPomodoroSecondsLeft / pomodoroTotalTime } size: 125 - secondaryColor: Appearance.colors.colSecondaryContainer primaryColor: Appearance.m3colors.m3onSecondaryContainer + secondaryColor: Appearance.colors.colSecondaryContainer enableAnimation: true ColumnLayout { anchors.centerIn: parent + spacing: 0 StyledText { Layout.alignment: Qt.AlignHCenter @@ -208,20 +210,30 @@ Item { // The Start/Stop and Reset buttons ColumnLayout { Layout.alignment: Qt.AlignHCenter - spacing: 20 + spacing: 10 RippleButton { - buttonText: Pomodoro.isPomodoroRunning ? Translation.tr("Pause") : Translation.tr("Start") + contentItem: StyledText { + anchors.centerIn: parent + horizontalAlignment: Text.AlignHCenter + text: Pomodoro.isPomodoroRunning ? Translation.tr("Stop") : Translation.tr("Start") + color: Appearance.colors.colSecondary + } Layout.preferredHeight: 35 Layout.preferredWidth: 90 font.pixelSize: Appearance.font.pixelSize.larger onClicked: Pomodoro.togglePomodoro() - colBackground: Appearance.m3colors.m3onSecondary - colBackgroundHover: Appearance.m3colors.m3onSecondary + colBackground: Appearance.colors.colSecondaryContainer + colBackgroundHover: Appearance.colors.colSecondaryContainer } RippleButton { - buttonText: Translation.tr("Reset") + contentItem: StyledText { + anchors.centerIn: parent + horizontalAlignment: Text.AlignHCenter + text: Translation.tr("Reset") + color: Appearance.colors.colSecondary + } Layout.preferredHeight: 35 Layout.preferredWidth: 90 font.pixelSize: Appearance.font.pixelSize.larger @@ -232,36 +244,89 @@ Item { } } - // The sliders for adjusting duration + // The SpinBoxes for adjusting duration ColumnLayout { - Layout.alignment: Qt.AlignHCenter - spacing: 10 + RowLayout { + Layout.fillWidth: true + spacing: 20 - ConfigSpinBox { - text: Translation.tr("Focus Duration: ") - value: Pomodoro.pomodoroFocusTime / 60 - onValueChanged: { - Pomodoro.pomodoroFocusTime = value * 60 - Config.options.time.pomodoro.focus = value * 60 + StyledText { + id: focusTextBox + Layout.leftMargin: focusSpinBox.implicitWidth / 2 - 7 + text: Translation.tr("Focus") } - Layout.alignment: Qt.AlignCenter - } - - ConfigSpinBox { - text: Translation.tr("Break Duration:") - value: Pomodoro.pomodoroBreakTime / 60 - onValueChanged: { - Config.options.time.pomodoro.breaktime = value * 60 - Pomodoro.pomodoroBreakTime = value * 60 + StyledText { + Layout.leftMargin: breakSpinBox.implicitWidth / 2 + 10 + text: Translation.tr("Break") } } - ConfigSpinBox { - text: Translation.tr("Long Break Duration:") - value: Pomodoro.pomodoroLongBreakTime / 60 - onValueChanged:{ - Pomodoro.pomodoroLongBreakTime = value * 60 - Config.options.time.pomodoro.longbreak = value * 60 + RowLayout { + Layout.alignment: Qt.AlignHCenter + spacing: 0 + + ConfigSpinBox { + id: focusSpinBox + spacing: 0 + Layout.leftMargin: 0 + Layout.rightMargin: 0 + value: Config.options.time.pomodoro.focus / 60 + onValueChanged: { + Config.options.time.pomodoro.focus = value * 60 + } + } + + ConfigSpinBox { + id: breakSpinBox + spacing: 0 + Layout.leftMargin: 0 + Layout.rightMargin: 0 + value: Config.options.time.pomodoro.breakTime / 60 + onValueChanged: { + Config.options.time.pomodoro.breakTime = value * 60 + } + } + } + + RowLayout { + Layout.fillWidth: true + spacing: 20 + + StyledText { + Layout.leftMargin: focusSpinBox.implicitWidth / 2 - 6 + text: Translation.tr("Cycle") + } + StyledText { + Layout.leftMargin: breakSpinBox.implicitWidth / 2 + text: Translation.tr("Long break") + } + } + + RowLayout { + Layout.alignment: Qt.AlignHCenter + spacing: 0 + + ConfigSpinBox { + id: cycleSpinBox + spacing: 0 + from: 1 + Layout.leftMargin: 0 + Layout.rightMargin: 0 + value: Config.options.time.pomodoro.cycle + onValueChanged: { + Config.options.time.pomodoro.cycle = value + } + } + + ConfigSpinBox { + id: longBreakSpinBox + spacing: 0 + Layout.leftMargin: 0 + Layout.rightMargin: 0 + value: Config.options.time.pomodoro.longBreak / 60 + onValueChanged: { + Config.options.time.pomodoro.longBreak = value * 60 + } } } } @@ -270,59 +335,155 @@ Item { // Stopwatch Tab Item { + Layout.fillWidth: true + ColumnLayout { anchors.horizontalCenter: parent.horizontalCenter - spacing: 18 - - StyledText { - Layout.alignment: Qt.AlignHCenter - text: { - let totalSeconds = Math.floor(Pomodoro.stopwatchTime) - let hours = Math.floor(totalSeconds / 3600).toString().padStart(2, '0') - let minutes = Math.floor((totalSeconds % 3600) / 60).toString().padStart(2, '0') - let seconds = Math.floor(totalSeconds % 60).toString().padStart(2, '0') - return `${hours}:${minutes}:${seconds}` - } - font.pixelSize: 50 - color: Appearance.m3colors.m3onSurface - } + spacing: 20 + Layout.fillWidth: true RowLayout { - Layout.alignment: Qt.AlignHCenter - spacing: 20 + spacing: 40 + // The Stopwatch circle + CircularProgress { + Layout.alignment: Qt.AlignHCenter + lineWidth: 7 + gapAngle: Math.PI / 18 + value: { + return Pomodoro.stopwatchTime % 6000 / 6000 // The seconds in percent + } + size: 125 + primaryColor: Math.floor(Pomodoro.stopwatchTime / 6000) % 2 ? Appearance.colors.colSecondaryContainer : Appearance.m3colors.m3onSecondaryContainer + secondaryColor: Math.floor(Pomodoro.stopwatchTime / 6000) % 2 ? Appearance.m3colors.m3onSecondaryContainer : Appearance.colors.colSecondaryContainer + enableAnimation: false // The animation seems weird after each cycle - DialogButton { - buttonText: Pomodoro.isStopwatchRunning ? Translation.tr("Pause") : Translation.tr("Start") - Layout.preferredWidth: 90 - Layout.preferredHeight: 35 - font.pixelSize: Appearance.font.pixelSize.larger - onClicked: Pomodoro.toggleStopwatch() - background: Rectangle { - color: Appearance.m3colors.m3onSecondary - radius: Appearance.rounding.normal - border.color: Appearance.m3colors.m3outline - border.width: 1 + ColumnLayout { + anchors.centerIn: parent + spacing: 0 + + StyledText { + Layout.alignment: Qt.AlignHCenter + text: { + let totalSeconds = Math.floor(Pomodoro.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}` + } + font.pixelSize: Appearance.font.pixelSize.hugeass + 4 + color: Appearance.m3colors.m3onSurface + } + StyledText { + Layout.alignment: Qt.AlignHCenter + text: { + return (Math.floor(Pomodoro.stopwatchTime) % 100).toString().padStart(2, '0') + } + font.pixelSize: Appearance.font.pixelSize.normal + color: Appearance.m3colors.m3onSurface + } } } - StyledText { + // The Start/Stop and Reset buttons + ColumnLayout { Layout.alignment: Qt.AlignHCenter - text: Translation.tr("Stopwatch") - font.pixelSize: Appearance.font.pixelSize.large - color: Appearance.m3colors.m3onSurface - } + spacing: 10 - DialogButton { - buttonText: Translation.tr("Reset") - Layout.preferredWidth: 90 - Layout.preferredHeight: 35 - font.pixelSize: Appearance.font.pixelSize.larger - onClicked: Pomodoro.stopwatchReset() - background: Rectangle { - color: Appearance.m3colors.m3onError - radius: Appearance.rounding.normal - border.color: Appearance.m3colors.m3outline - border.width: 1 + RippleButton { + contentItem: StyledText { + anchors.centerIn: parent + horizontalAlignment: Text.AlignHCenter + text: Pomodoro.isStopwatchRunning ? Translation.tr("Stop") : Translation.tr("Start") + color: Appearance.colors.colSecondary + } + Layout.preferredHeight: 35 + Layout.preferredWidth: 90 + font.pixelSize: Appearance.font.pixelSize.larger + onClicked: Pomodoro.toggleStopwatch() + colBackground: Appearance.colors.colSecondaryContainer + colBackgroundHover: Appearance.colors.colSecondaryContainer + } + + RippleButton { + contentItem: StyledText { + anchors.centerIn: parent + horizontalAlignment: Text.AlignHCenter + text: Pomodoro.isStopwatchRunning ? Translation.tr("Lap") : Translation.tr("Reset") + color: Appearance.colors.colSecondary + } + Layout.preferredHeight: 35 + Layout.preferredWidth: 90 + font.pixelSize: Appearance.font.pixelSize.larger + onClicked: Pomodoro.stopwatchReset() + colBackground: Appearance.m3colors.m3onError + colBackgroundHover: Appearance.m3colors.m3onError + } + } + } + + StyledListView { + id: lapsList + Layout.fillWidth: true + Layout.preferredHeight: contentHeight + spacing: lapsListItemSpacing + clip: true + model: Pomodoro.stopwatchLaps + + delegate: Rectangle { + width: lapsList.width + implicitHeight: lapsContentText.implicitHeight + lapsListItemPadding + color: Appearance.colors.colLayer2 + radius: Appearance.rounding.small + + StyledText { + id: lapsContentText + anchors.left: parent.left + anchors.top: parent.top + anchors.bottom: parent.bottom + leftPadding: lapsListItemPadding + rightPadding: lapsListItemPadding + topPadding: lapsListItemPadding / 2 + bottomPadding: lapsListItemPadding / 2 + font.pixelSize: Appearance.font.pixelSize.normal + + text: { + let lapIndex = index + 1 + let lapTime = modelData + // if (index > 0) { + // lapTime = modelData - Pomodoro.stopwatchLaps[index - 1] + // } + let _10ms = (Math.floor(lapTime) % 100).toString().padStart(2, '0') + let totalSeconds = Math.floor(lapTime) / 100 + let minutes = Math.floor(totalSeconds / 60).toString().padStart(2, '0') + let seconds = Math.floor(totalSeconds % 60).toString().padStart(2, '0') + return `${minutes}:${seconds}.${_10ms}` + } + } + + StyledText { + id: lapsDiffText + anchors.right: parent.right + anchors.top: parent.top + anchors.bottom: parent.bottom + leftPadding: lapsListItemPadding + rightPadding: lapsListItemPadding * 2 + topPadding: lapsListItemPadding / 2 + bottomPadding: lapsListItemPadding / 2 + font.pixelSize: Appearance.font.pixelSize.normal + color: Appearance.colors.colPrimary + + text: { + let lapTime = modelData + if (index != Pomodoro.stopwatchLaps.length - 1) { // except first lap + lapTime = modelData - Pomodoro.stopwatchLaps[index + 1] + let _10ms = (Math.floor(lapTime) % 100).toString().padStart(2, '0') + let totalSeconds = Math.floor(lapTime) / 100 + let minutes = Math.floor(totalSeconds / 60).toString().padStart(2, '0') + let seconds = Math.floor(totalSeconds % 60).toString().padStart(2, '0') + return `+${minutes}:${seconds}.${_10ms}` + } else { + return `` // Nothing for first lap + } + } } } } diff --git a/.config/quickshell/ii/services/Pomodoro.qml b/.config/quickshell/ii/services/Pomodoro.qml index 5244e5382..3387081d5 100644 --- a/.config/quickshell/ii/services/Pomodoro.qml +++ b/.config/quickshell/ii/services/Pomodoro.qml @@ -4,9 +4,9 @@ pragma ComponentBehavior: Bound import qs import qs.modules.common -import Quickshell; -import Quickshell.Io; -import QtQuick; +import Quickshell +import Quickshell.Io +import QtQuick /** * Simple Pomodoro time manager. @@ -14,29 +14,30 @@ import QtQuick; Singleton { id: root - // TODO: read these values from a config file. property int pomodoroFocusTime: Config.options.time.pomodoro.focus - property int pomodoroBreakTime: Config.options.time.pomodoro.breaktime - property int pomodoroLongBreakTime: Config.options.time.pomodoro.longbreak + property int pomodoroBreakTime: Config.options.time.pomodoro.breakTime + property int pomodoroLongBreakTime: Config.options.time.pomodoro.longBreak property int pomodoroLongBreakCycle: Config.options.time.pomodoro.cycle + property bool isPomodoroRunning: Config.options.time.pomodoro.running property int pomodoroTimeLeft: pomodoroFocusTime property int getPomodoroSecondsLeft: pomodoroFocusTime - property int pomodoroTimeStarted: getCurrentTime() // The time pomodoro was last Resumed + property int pomodoroTimeStarted: getCurrentTimeInSeconds() // The time pomodoro was last Resumed property bool isPomodoroBreak: false - property bool isPomodoroRunning: false property int pomodoroCycle: 1 property int stopwatchTime: 0 property bool isStopwatchRunning: false + property int stopwatchStartTime: 0 + property var stopwatchLaps: [] - // Pause and Resume button + // Start and Stop button function togglePomodoro() { - isPomodoroRunning = !isPomodoroRunning + Config.options.time.pomodoro.running = !isPomodoroRunning if (isPomodoroRunning) { // Pressed Start button - pomodoroTimeStarted = getCurrentTime() - } else { // Pressed Pause button - pomodoroTimeLeft -= (getCurrentTime() - pomodoroTimeStarted) + pomodoroTimeStarted = getCurrentTimeInSeconds() + } else { // Pressed Stop button + pomodoroTimeLeft -= (getCurrentTimeInSeconds() - pomodoroTimeStarted) } } @@ -45,51 +46,65 @@ Singleton { pomodoroTimeLeft = pomodoroFocusTime getPomodoroSecondsLeft = pomodoroFocusTime isPomodoroBreak = false - isPomodoroRunning = false + Config.options.time.pomodoro.running = false + pomodoroCycle = 1 } function tickSecond() { - if (getCurrentTime() >= pomodoroTimeStarted + pomodoroTimeLeft) { + if (getCurrentTimeInSeconds() >= pomodoroTimeStarted + pomodoroTimeLeft) { isPomodoroBreak = !isPomodoroBreak pomodoroTimeStarted += pomodoroTimeLeft - pomodoroTimeLeft = isPomodoroBreak ? pomodoroBreakTime : pomodoroFocusTime + pomodoroTimeLeft = isPomodoroBreak ? pomodoroBreakTime : pomodoroFocusTime - if (isPomodoroBreak && pomodoroCycle % pomodoroLongBreakCycle == 0) { // isPomodoroLongBreak - Quickshell.execDetached([ - "notify-send", - Translation.tr("🌿 Long Break!"), - Translation.tr(`Relax for %1 minutes.`).arg(Math.floor(pomodoroLongBreakTime / 60)) - ]) - } else if(isPomodoroBreak){ - Quickshell.execDetached([ - "notify-send", - Translation.tr("☕ Short Break!"), - Translation.tr(`Relax for %1 minutes.`).arg(Math.floor(pomodoroBreakTime / 60)) - ]) + let notificationTitle, notificationMessage + + if (isPomodoroBreak && pomodoroCycle % pomodoroLongBreakCycle === 0) { // isPomodoroLongBreak + notificationMessage = Translation.tr(`Relax for %1 minutes`).arg(Math.floor(pomodoroLongBreakTime / 60)) + } else if (isPomodoroBreak) { + notificationMessage = Translation.tr(`Relax for %1 minutes`).arg(Math.floor(pomodoroBreakTime / 60)) } else { - Quickshell.execDetached([ - "notify-send", - Translation.tr("🔴 Pomodoro started!"), - Translation.tr(`Focus for %1 minutes.`).arg(Math.floor(pomodoroFocusTime / 60)) - ]) + notificationMessage = Translation.tr(`Focus for %1 minutes`).arg(Math.floor(pomodoroFocusTime / 60)) pomodoroCycle += 1 } + + Quickshell.execDetached(["notify-send", "Pomodoro", notificationMessage, "-a", "Shell"]) } - getPomodoroSecondsLeft = (pomodoroTimeStarted + pomodoroTimeLeft) - getCurrentTime() + // A nice abstraction for resume logic by updating the TimeStarted + getPomodoroSecondsLeft = (pomodoroTimeStarted + pomodoroTimeLeft) - getCurrentTimeInSeconds() } - function getCurrentTime() { + function getCurrentTimeInSeconds() { // Pomodoro uses Seconds return Math.floor(Date.now() / 1000) } + function getCurrentTimeIn10ms() { // Stopwatch uses 10ms + return Math.floor(Date.now() / 10) + } + + function tick10ms() { // stopwatch stores time in 10ms + stopwatchTime = getCurrentTimeIn10ms() - stopwatchStartTime + } + // Stopwatch functions function toggleStopwatch() { isStopwatchRunning = !isStopwatchRunning + if (isStopwatchRunning) { + // Resume from paused time by adjusting start time + stopwatchStartTime = getCurrentTimeIn10ms() - stopwatchTime + } } function stopwatchReset() { - stopwatchTime = 0 - isStopwatchRunning = false + if (isStopwatchRunning) { // Clicked on Lap + stopwatchLaps.unshift(stopwatchTime) // Last lap goes first on list + // Reassign to trigger onListChanged, idk copied from Todo.qml + root.stopwatchLaps = stopwatchLaps.slice(0) + } else { // Clicked on Reset + isStopwatchRunning = false + stopwatchTime = 0 + stopwatchStartTime = 0 + stopwatchLaps = [] + } } } From 35e1dc95a52e0ebe09a4323420373b32e06de79a Mon Sep 17 00:00:00 2001 From: lunstia <136163666+lunstia@users.noreply.github.com> Date: Wed, 6 Aug 2025 05:27:29 -0400 Subject: [PATCH 09/70] Fix background hiding in fullscreen --- .config/quickshell/ii/modules/background/Background.qml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.config/quickshell/ii/modules/background/Background.qml b/.config/quickshell/ii/modules/background/Background.qml index f960adf8c..2553a8772 100644 --- a/.config/quickshell/ii/modules/background/Background.qml +++ b/.config/quickshell/ii/modules/background/Background.qml @@ -26,9 +26,9 @@ 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) + readonly property var monitorHasFullscreen: ToplevelManager.toplevels.values.filter(window=>(window.screens[0].name == monitor.name) && window.fullscreen)[0] + + visible: !(monitorHasFullscreen != undefined) // Workspaces property HyprlandMonitor monitor: Hyprland.monitorFor(modelData) From 6c3451b912dfdcd63b6514cff49924a866cc1cd2 Mon Sep 17 00:00:00 2001 From: lunstia <136163666+lunstia@users.noreply.github.com> Date: Thu, 7 Aug 2025 00:16:26 -0400 Subject: [PATCH 10/70] Fix background not always hiding in fullscreen and other monitors hiding background when they're not supposed to --- .config/quickshell/ii/modules/background/Background.qml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.config/quickshell/ii/modules/background/Background.qml b/.config/quickshell/ii/modules/background/Background.qml index 2553a8772..cc9b28a1f 100644 --- a/.config/quickshell/ii/modules/background/Background.qml +++ b/.config/quickshell/ii/modules/background/Background.qml @@ -26,9 +26,10 @@ Variants { required property var modelData // Hide when fullscreen - readonly property var monitorHasFullscreen: ToplevelManager.toplevels.values.filter(window=>(window.screens[0].name == monitor.name) && window.fullscreen)[0] - - visible: !(monitorHasFullscreen != undefined) + 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) From 86ddb61a3ff4da07f434f8441cf21e87fce0d6fc Mon Sep 17 00:00:00 2001 From: Runze Date: Thu, 7 Aug 2025 22:26:26 +0800 Subject: [PATCH 11/70] fix(touchpad): differentiate scroll speed between touchpad and mouse wheel --- .../modules/common/widgets/StyledFlickable.qml | 18 ++++++++++++++++++ .../modules/common/widgets/StyledListView.qml | 17 +++++++++++++++++ .../ii/modules/sidebarLeft/AiChat.qml | 3 +++ .../ii/modules/sidebarLeft/Anime.qml | 3 +++ 4 files changed, 41 insertions(+) diff --git a/.config/quickshell/ii/modules/common/widgets/StyledFlickable.qml b/.config/quickshell/ii/modules/common/widgets/StyledFlickable.qml index 14b3af03c..b077e141d 100644 --- a/.config/quickshell/ii/modules/common/widgets/StyledFlickable.qml +++ b/.config/quickshell/ii/modules/common/widgets/StyledFlickable.qml @@ -1,6 +1,24 @@ import QtQuick Flickable { + id: root maximumFlickVelocity: 3500 boundsBehavior: Flickable.DragOverBounds + + property real touchpadScrollFactor: 100 + property real mouseScrollFactor: 50 + + MouseArea { + anchors.fill: parent + acceptedButtons: Qt.NoButton + onWheel: function(wheelEvent) { + var delta = wheelEvent.angleDelta.y / 120; + // 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) >= 120 ? root.mouseScrollFactor : root.touchpadScrollFactor; + var targetY = root.contentY - delta * scrollFactor; + targetY = Math.max(0, Math.min(targetY, root.contentHeight - root.height)); + root.contentY = targetY; + } + } } diff --git a/.config/quickshell/ii/modules/common/widgets/StyledListView.qml b/.config/quickshell/ii/modules/common/widgets/StyledListView.qml index 7021f24a4..c6119fcc0 100644 --- a/.config/quickshell/ii/modules/common/widgets/StyledListView.qml +++ b/.config/quickshell/ii/modules/common/widgets/StyledListView.qml @@ -15,6 +15,9 @@ ListView { property real dragDistance: 0 property bool popin: true + property real touchpadScrollFactor: 100 + property real mouseScrollFactor: 50 + function resetDrag() { root.dragIndex = -1 root.dragDistance = 0 @@ -23,6 +26,20 @@ ListView { maximumFlickVelocity: 3500 boundsBehavior: Flickable.DragOverBounds + MouseArea { + anchors.fill: parent + acceptedButtons: Qt.NoButton + onWheel: function(wheelEvent) { + var delta = wheelEvent.angleDelta.y / 120; + // 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) >= 120 ? root.mouseScrollFactor : root.touchpadScrollFactor; + var targetY = root.contentY - delta * scrollFactor; + targetY = Math.max(0, Math.min(targetY, root.contentHeight - root.height)); + root.contentY = targetY; + } + } + add: Transition { animations: [ Appearance?.animation.elementMove.numberAnimation.createObject(this, { diff --git a/.config/quickshell/ii/modules/sidebarLeft/AiChat.qml b/.config/quickshell/ii/modules/sidebarLeft/AiChat.qml index dcbff893e..9120a8f72 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: 600 + mouseScrollFactor: 200 + property int lastResponseLength: 0 clip: true diff --git a/.config/quickshell/ii/modules/sidebarLeft/Anime.qml b/.config/quickshell/ii/modules/sidebarLeft/Anime.qml index a72837725..07575ca36 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: 600 + mouseScrollFactor: 200 + property int lastResponseLength: 0 clip: true From f581fd4821ac1a19931a38ae4de0300af86d45d5 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Thu, 7 Aug 2025 21:39:48 +0700 Subject: [PATCH 12/70] config option to (not) filter duplicate media controls --- .config/quickshell/ii/modules/common/Config.qml | 5 +++++ .../quickshell/ii/modules/mediaControls/MediaControls.qml | 6 ++++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/.config/quickshell/ii/modules/common/Config.qml b/.config/quickshell/ii/modules/common/Config.qml index bcbbd1e33..503b23b73 100644 --- a/.config/quickshell/ii/modules/common/Config.qml +++ b/.config/quickshell/ii/modules/common/Config.qml @@ -198,6 +198,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" } 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')) && From 733a79261064754f3f9e67708757c8064a9e009c Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Thu, 7 Aug 2025 21:53:37 +0700 Subject: [PATCH 13/70] ai: add usage metadata for openai and mistral --- .../quickshell/ii/services/ai/MistralApiStrategy.qml | 11 +++++++++++ .../quickshell/ii/services/ai/OpenAiApiStrategy.qml | 12 ++++++++++++ 2 files changed, 23 insertions(+) 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..85d5e0463 100644 --- a/.config/quickshell/ii/services/ai/OpenAiApiStrategy.qml +++ b/.config/quickshell/ii/services/ai/OpenAiApiStrategy.qml @@ -46,6 +46,7 @@ ApiStrategy { // Real stuff try { const dataJson = JSON.parse(cleanData); + console.log(JSON.stringify(dataJson, null, 2)); let newContent = ""; const responseContent = dataJson.choices[0]?.delta?.content || dataJson.message?.content; @@ -72,6 +73,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 }; } From 4f40ba8e6e910a5c556384f33d5bba07b9901f0c Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Thu, 7 Aug 2025 22:01:05 +0700 Subject: [PATCH 14/70] more intuitive power profiles icons --- .config/quickshell/ii/modules/bar/UtilButtons.qml | 6 +++--- .config/quickshell/ii/services/ai/OpenAiApiStrategy.qml | 1 - 2 files changed, 3 insertions(+), 4 deletions(-) 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/services/ai/OpenAiApiStrategy.qml b/.config/quickshell/ii/services/ai/OpenAiApiStrategy.qml index 85d5e0463..24a0892fb 100644 --- a/.config/quickshell/ii/services/ai/OpenAiApiStrategy.qml +++ b/.config/quickshell/ii/services/ai/OpenAiApiStrategy.qml @@ -46,7 +46,6 @@ ApiStrategy { // Real stuff try { const dataJson = JSON.parse(cleanData); - console.log(JSON.stringify(dataJson, null, 2)); let newContent = ""; const responseContent = dataJson.choices[0]?.delta?.content || dataJson.message?.content; From 0506917b87c2edc1689793bf31b4af41e8cb5415 Mon Sep 17 00:00:00 2001 From: Souyama Date: Thu, 7 Aug 2025 20:48:11 +0530 Subject: [PATCH 15/70] launch_first_available.sh should skip empty cmds --- .config/hypr/hyprland/scripts/launch_first_available.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 From f1c1ed833c28a455bfc5056b0fd9fcd830a57bc5 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Thu, 7 Aug 2025 22:31:19 +0700 Subject: [PATCH 16/70] use StyledListView for SelectionDialog --- .../quickshell/ii/modules/common/widgets/SelectionDialog.qml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) 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 } From 199b23d14a929125a40c9e6ddfda8184f000e912 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Thu, 7 Aug 2025 22:32:02 +0700 Subject: [PATCH 17/70] add config options for scroll factors and threshold --- .config/quickshell/ii/modules/common/Config.qml | 8 ++++++++ .../ii/modules/common/widgets/StyledFlickable.qml | 10 ++++++---- .../ii/modules/common/widgets/StyledListView.qml | 9 +++++---- 3 files changed, 19 insertions(+), 8 deletions(-) diff --git a/.config/quickshell/ii/modules/common/Config.qml b/.config/quickshell/ii/modules/common/Config.qml index bcbbd1e33..d2b979c6a 100644 --- a/.config/quickshell/ii/modules/common/Config.qml +++ b/.config/quickshell/ii/modules/common/Config.qml @@ -181,6 +181,14 @@ Singleton { property list ignoredAppRegexes: [] } + property JsonObject interactions: JsonObject { + property JsonObject scrolling: JsonObject { + property int mouseScrollDeltaThreshold: 120 // delta >= this then it gets detected as mouse scroll rather than touchpad + property int mouseScrollFactor: 50 + property int touchpadScrollFactor: 100 + } + } + property JsonObject language: JsonObject { property JsonObject translator: JsonObject { property string engine: "auto" // Run `trans -list-engines` for available engines. auto should use google diff --git a/.config/quickshell/ii/modules/common/widgets/StyledFlickable.qml b/.config/quickshell/ii/modules/common/widgets/StyledFlickable.qml index b077e141d..bde7bff9b 100644 --- a/.config/quickshell/ii/modules/common/widgets/StyledFlickable.qml +++ b/.config/quickshell/ii/modules/common/widgets/StyledFlickable.qml @@ -1,21 +1,23 @@ import QtQuick +import qs.modules.common Flickable { id: root maximumFlickVelocity: 3500 boundsBehavior: Flickable.DragOverBounds - property real touchpadScrollFactor: 100 - property real mouseScrollFactor: 50 + 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 MouseArea { anchors.fill: parent acceptedButtons: Qt.NoButton onWheel: function(wheelEvent) { - var delta = wheelEvent.angleDelta.y / 120; + 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) >= 120 ? root.mouseScrollFactor : root.touchpadScrollFactor; + var scrollFactor = Math.abs(wheelEvent.angleDelta.y) >= root.mouseScrollDeltaThreshold ? root.mouseScrollFactor : root.touchpadScrollFactor; var targetY = root.contentY - delta * scrollFactor; targetY = Math.max(0, Math.min(targetY, root.contentHeight - root.height)); root.contentY = targetY; diff --git a/.config/quickshell/ii/modules/common/widgets/StyledListView.qml b/.config/quickshell/ii/modules/common/widgets/StyledListView.qml index c6119fcc0..61e6740fc 100644 --- a/.config/quickshell/ii/modules/common/widgets/StyledListView.qml +++ b/.config/quickshell/ii/modules/common/widgets/StyledListView.qml @@ -15,8 +15,9 @@ ListView { property real dragDistance: 0 property bool popin: true - property real touchpadScrollFactor: 100 - property real mouseScrollFactor: 50 + 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 @@ -30,10 +31,10 @@ ListView { anchors.fill: parent acceptedButtons: Qt.NoButton onWheel: function(wheelEvent) { - var delta = wheelEvent.angleDelta.y / 120; + 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) >= 120 ? root.mouseScrollFactor : root.touchpadScrollFactor; + var scrollFactor = Math.abs(wheelEvent.angleDelta.y) >= root.mouseScrollDeltaThreshold ? root.mouseScrollFactor : root.touchpadScrollFactor; var targetY = root.contentY - delta * scrollFactor; targetY = Math.max(0, Math.min(targetY, root.contentHeight - root.height)); root.contentY = targetY; From a31733e2db5651e6d14743160b2534031f7c49b0 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Thu, 7 Aug 2025 22:39:30 +0700 Subject: [PATCH 18/70] move scrolling animation to styled components --- .../ii/modules/common/widgets/StyledFlickable.qml | 9 +++++++++ .../ii/modules/common/widgets/StyledListView.qml | 9 +++++++++ .config/quickshell/ii/modules/sidebarLeft/AiChat.qml | 9 --------- .config/quickshell/ii/modules/sidebarLeft/Anime.qml | 9 --------- 4 files changed, 18 insertions(+), 18 deletions(-) diff --git a/.config/quickshell/ii/modules/common/widgets/StyledFlickable.qml b/.config/quickshell/ii/modules/common/widgets/StyledFlickable.qml index bde7bff9b..7fcf7be88 100644 --- a/.config/quickshell/ii/modules/common/widgets/StyledFlickable.qml +++ b/.config/quickshell/ii/modules/common/widgets/StyledFlickable.qml @@ -23,4 +23,13 @@ Flickable { root.contentY = targetY; } } + + Behavior on contentY { + NumberAnimation { + id: scrollAnim + duration: Appearance.animation.scroll.duration + easing.type: Appearance.animation.scroll.type + easing.bezierCurve: Appearance.animation.scroll.bezierCurve + } + } } diff --git a/.config/quickshell/ii/modules/common/widgets/StyledListView.qml b/.config/quickshell/ii/modules/common/widgets/StyledListView.qml index 61e6740fc..6a9cf258f 100644 --- a/.config/quickshell/ii/modules/common/widgets/StyledListView.qml +++ b/.config/quickshell/ii/modules/common/widgets/StyledListView.qml @@ -41,6 +41,15 @@ ListView { } } + Behavior on contentY { + NumberAnimation { + id: scrollAnim + duration: Appearance.animation.scroll.duration + easing.type: Appearance.animation.scroll.type + easing.bezierCurve: Appearance.animation.scroll.bezierCurve + } + } + add: Transition { animations: [ Appearance?.animation.elementMove.numberAnimation.createObject(this, { diff --git a/.config/quickshell/ii/modules/sidebarLeft/AiChat.qml b/.config/quickshell/ii/modules/sidebarLeft/AiChat.qml index 9120a8f72..f3a1dae8f 100644 --- a/.config/quickshell/ii/modules/sidebarLeft/AiChat.qml +++ b/.config/quickshell/ii/modules/sidebarLeft/AiChat.qml @@ -299,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 07575ca36..68d4947b8 100644 --- a/.config/quickshell/ii/modules/sidebarLeft/Anime.qml +++ b/.config/quickshell/ii/modules/sidebarLeft/Anime.qml @@ -152,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) { From 7013b459a3884376e35272ded87c73ee654e2251 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Thu, 7 Aug 2025 23:13:07 +0700 Subject: [PATCH 19/70] adjust scrolling speed --- .config/quickshell/ii/modules/common/Appearance.qml | 3 +-- .config/quickshell/ii/modules/common/Config.qml | 4 ++-- .config/quickshell/ii/modules/sidebarLeft/AiChat.qml | 4 ++-- .config/quickshell/ii/modules/sidebarLeft/Anime.qml | 4 ++-- 4 files changed, 7 insertions(+), 8 deletions(-) diff --git a/.config/quickshell/ii/modules/common/Appearance.qml b/.config/quickshell/ii/modules/common/Appearance.qml index 9c9374860..cfc9e1cae 100644 --- a/.config/quickshell/ii/modules/common/Appearance.qml +++ b/.config/quickshell/ii/modules/common/Appearance.qml @@ -266,7 +266,6 @@ Singleton { easing.bezierCurve: root.animation.elementMoveFast.bezierCurve }} } - property QtObject clickBounce: QtObject { property int duration: 200 property int type: Easing.BezierSpline @@ -279,7 +278,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 d2b979c6a..ed10b805d 100644 --- a/.config/quickshell/ii/modules/common/Config.qml +++ b/.config/quickshell/ii/modules/common/Config.qml @@ -184,8 +184,8 @@ Singleton { property JsonObject interactions: JsonObject { property JsonObject scrolling: JsonObject { property int mouseScrollDeltaThreshold: 120 // delta >= this then it gets detected as mouse scroll rather than touchpad - property int mouseScrollFactor: 50 - property int touchpadScrollFactor: 100 + property int mouseScrollFactor: 120 + property int touchpadScrollFactor: 450 } } diff --git a/.config/quickshell/ii/modules/sidebarLeft/AiChat.qml b/.config/quickshell/ii/modules/sidebarLeft/AiChat.qml index f3a1dae8f..3817afe50 100644 --- a/.config/quickshell/ii/modules/sidebarLeft/AiChat.qml +++ b/.config/quickshell/ii/modules/sidebarLeft/AiChat.qml @@ -282,8 +282,8 @@ Inline w/ backslash and round brackets \\(e^{i\\pi} + 1 = 0\\) spacing: 10 popin: false - touchpadScrollFactor: 600 - mouseScrollFactor: 200 + touchpadScrollFactor: Config.options.interactions.scrolling.touchpadScrollFactor * 1.4 + mouseScrollFactor: Config.options.interactions.scrolling.mouseScrollFactor * 1.4 property int lastResponseLength: 0 diff --git a/.config/quickshell/ii/modules/sidebarLeft/Anime.qml b/.config/quickshell/ii/modules/sidebarLeft/Anime.qml index 68d4947b8..97dabd942 100644 --- a/.config/quickshell/ii/modules/sidebarLeft/Anime.qml +++ b/.config/quickshell/ii/modules/sidebarLeft/Anime.qml @@ -137,8 +137,8 @@ Item { anchors.fill: parent spacing: 10 - touchpadScrollFactor: 600 - mouseScrollFactor: 200 + touchpadScrollFactor: Config.options.interactions.scrolling.touchpadScrollFactor * 1.4 + mouseScrollFactor: Config.options.interactions.scrolling.mouseScrollFactor * 1.4 property int lastResponseLength: 0 From 772df06fa51547687337c0b6296449445c964fc7 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Fri, 8 Aug 2025 17:50:20 +0700 Subject: [PATCH 20/70] booru: fix inconsistent download --- .config/quickshell/ii/modules/sidebarLeft/anime/BooruImage.qml | 2 +- .../quickshell/ii/modules/sidebarLeft/anime/BooruResponse.qml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.config/quickshell/ii/modules/sidebarLeft/anime/BooruImage.qml b/.config/quickshell/ii/modules/sidebarLeft/anime/BooruImage.qml index abb74612d..cf412f274 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 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 From 4df22c96d0ce56a3c93272b01fc1e521e0cda1d4 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Fri, 8 Aug 2025 17:52:19 +0700 Subject: [PATCH 21/70] screen corners: fix visibility for multimonitor with varying fullscreen state --- .../ii/modules/screenCorners/ScreenCorners.qml | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) 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 } } } From a15f3b8c65055c3feddc673d69706c434a138aa7 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Fri, 8 Aug 2025 17:55:52 +0700 Subject: [PATCH 22/70] overview: show windows on other monitors too --- .../quickshell/ii/modules/overview/OverviewWidget.qml | 9 ++++----- .../quickshell/ii/modules/overview/OverviewWindow.qml | 3 +++ 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/.config/quickshell/ii/modules/overview/OverviewWidget.qml b/.config/quickshell/ii/modules/overview/OverviewWidget.qml index 9cfe9147e..2510a5642 100644 --- a/.config/quickshell/ii/modules/overview/OverviewWidget.qml +++ b/.config/quickshell/ii/modules/overview/OverviewWidget.qml @@ -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..ba5bba100 100644 --- a/.config/quickshell/ii/modules/overview/OverviewWindow.qml +++ b/.config/quickshell/ii/modules/overview/OverviewWindow.qml @@ -22,6 +22,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 +41,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 +71,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 From 8aa776ae62678f64976d105d940bb17dbdca3fa0 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Fri, 8 Aug 2025 18:02:10 +0700 Subject: [PATCH 23/70] make bg image loading async --- .config/quickshell/ii/modules/background/Background.qml | 1 + .config/quickshell/ii/modules/overview/OverviewWindow.qml | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/.config/quickshell/ii/modules/background/Background.qml b/.config/quickshell/ii/modules/background/Background.qml index 47c16f21a..84f99fe41 100644 --- a/.config/quickshell/ii/modules/background/Background.qml +++ b/.config/quickshell/ii/modules/background/Background.qml @@ -150,6 +150,7 @@ Variants { id: wallpaper visible: !bgRoot.wallpaperIsVideo 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; diff --git a/.config/quickshell/ii/modules/overview/OverviewWindow.qml b/.config/quickshell/ii/modules/overview/OverviewWindow.qml index ba5bba100..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 From 3d408b18f7c54572540bd00a1b03061a8a4ec928 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Fri, 8 Aug 2025 18:31:52 +0700 Subject: [PATCH 24/70] background: add fade when switching --- .../quickshell/ii/modules/background/Background.qml | 6 +++++- .../ii/modules/sidebarLeft/anime/BooruImage.qml | 12 ++++++------ 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/.config/quickshell/ii/modules/background/Background.qml b/.config/quickshell/ii/modules/background/Background.qml index 84f99fe41..2eb5c3ec7 100644 --- a/.config/quickshell/ii/modules/background/Background.qml +++ b/.config/quickshell/ii/modules/background/Background.qml @@ -148,7 +148,11 @@ 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: { diff --git a/.config/quickshell/ii/modules/sidebarLeft/anime/BooruImage.qml b/.config/quickshell/ii/modules/sidebarLeft/anime/BooruImage.qml index cf412f274..b2eef2939 100644 --- a/.config/quickshell/ii/modules/sidebarLeft/anime/BooruImage.qml +++ b/.config/quickshell/ii/modules/sidebarLeft/anime/BooruImage.qml @@ -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 { From f806e2c22cef5cd4e1798ae86ae66b59f9c1edf9 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Fri, 8 Aug 2025 19:54:10 +0700 Subject: [PATCH 25/70] bar: add auto hide --- .config/quickshell/ii/GlobalStates.qml | 9 +- .config/quickshell/ii/modules/bar/Bar.qml | 176 ++++++++++-------- .../quickshell/ii/modules/common/Config.qml | 5 + 3 files changed, 113 insertions(+), 77 deletions(-) diff --git a/.config/quickshell/ii/GlobalStates.qml b/.config/quickshell/ii/GlobalStates.qml index b3a124eb6..aa4db9b0a 100644 --- a/.config/quickshell/ii/GlobalStates.qml +++ b/.config/quickshell/ii/GlobalStates.qml @@ -17,11 +17,12 @@ 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 sessionOpen: false + property bool superDown: false + property bool superReleaseMightTrigger: true + property bool workspaceShowNumbers: false property real screenZoom: 1 onScreenZoomChanged: { @@ -51,9 +52,11 @@ Singleton { description: "Hold to show workspace numbers, release to show icons" onPressed: { + root.superDown = true workspaceShowNumbersTimer.start() } onReleased: { + root.superDown = false workspaceShowNumbersTimer.stop() workspaceShowNumbers = false } diff --git a/.config/quickshell/ii/modules/bar/Bar.qml b/.config/quickshell/ii/modules/bar/Bar.qml index c08133ad6..1c2d66a47 100644 --- a/.config/quickshell/ii/modules/bar/Bar.qml +++ b/.config/quickshell/ii/modules/bar/Bar.qml @@ -39,8 +39,10 @@ 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 + property bool mustShow: hoverRegion.containsMouse || (Config?.options.bar.autoHide.showWhenPressingSuper && GlobalStates.superDown) exclusionMode: ExclusionMode.Ignore - exclusiveZone: Appearance.sizes.baseBarHeight + (Config.options.bar.cornerStyle === 1 ? Appearance.sizes.hyprlandGapsOut : 0) + exclusiveZone: (Config?.options.bar.autoHide.enabled && (!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 +57,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.enabled && !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.enabled && !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/common/Config.qml b/.config/quickshell/ii/modules/common/Config.qml index ac8c4da41..3ca1daa6f 100644 --- a/.config/quickshell/ii/modules/common/Config.qml +++ b/.config/quickshell/ii/modules/common/Config.qml @@ -124,6 +124,11 @@ Singleton { } property JsonObject bar: JsonObject { + property JsonObject autoHide: JsonObject { + property bool enabled: false + property bool pushWindows: false + property bool showWhenPressingSuper: true + } 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 From 9824bb9c631db77bdd49a02ddd2769b4dccd962e Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Fri, 8 Aug 2025 20:06:53 +0700 Subject: [PATCH 26/70] bar: add delay for autohide --- .config/quickshell/ii/GlobalStates.qml | 1 - .config/quickshell/ii/modules/bar/Bar.qml | 22 ++++++++++++++++++- .../quickshell/ii/modules/common/Config.qml | 5 ++++- 3 files changed, 25 insertions(+), 3 deletions(-) diff --git a/.config/quickshell/ii/GlobalStates.qml b/.config/quickshell/ii/GlobalStates.qml index aa4db9b0a..55d0b67cc 100644 --- a/.config/quickshell/ii/GlobalStates.qml +++ b/.config/quickshell/ii/GlobalStates.qml @@ -40,7 +40,6 @@ Singleton { Timer { id: workspaceShowNumbersTimer interval: Config.options.bar.workspaces.showNumberDelay - // interval: 0 repeat: false onTriggered: { workspaceShowNumbers = true diff --git a/.config/quickshell/ii/modules/bar/Bar.qml b/.config/quickshell/ii/modules/bar/Bar.qml index 1c2d66a47..a611e28c3 100644 --- a/.config/quickshell/ii/modules/bar/Bar.qml +++ b/.config/quickshell/ii/modules/bar/Bar.qml @@ -39,7 +39,27 @@ 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 - property bool mustShow: hoverRegion.containsMouse || (Config?.options.bar.autoHide.showWhenPressingSuper && GlobalStates.superDown) + 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.enabled) 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: (Config?.options.bar.autoHide.enabled && (!mustShow || !Config?.options.bar.autoHide.pushWindows)) ? 0 : Appearance.sizes.baseBarHeight + (Config.options.bar.cornerStyle === 1 ? Appearance.sizes.hyprlandGapsOut : 0) diff --git a/.config/quickshell/ii/modules/common/Config.qml b/.config/quickshell/ii/modules/common/Config.qml index 3ca1daa6f..00be02b26 100644 --- a/.config/quickshell/ii/modules/common/Config.qml +++ b/.config/quickshell/ii/modules/common/Config.qml @@ -127,7 +127,10 @@ Singleton { property JsonObject autoHide: JsonObject { property bool enabled: false property bool pushWindows: false - property bool showWhenPressingSuper: true + property JsonObject showWhenPressingSuper: JsonObject { + property bool enabled: true + property int delay: 100 + } } property bool bottom: false // Instead of top property int cornerStyle: 0 // 0: Hug | 1: Float | 2: Plain rectangle From 66c810ead2329eeb7466224424b8073b4df89dd7 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Fri, 8 Aug 2025 20:12:50 +0700 Subject: [PATCH 27/70] bar autohide: rename enabled -> enable for consistency --- .config/quickshell/ii/modules/bar/Bar.qml | 8 ++++---- .config/quickshell/ii/modules/common/Config.qml | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.config/quickshell/ii/modules/bar/Bar.qml b/.config/quickshell/ii/modules/bar/Bar.qml index a611e28c3..c19fbd311 100644 --- a/.config/quickshell/ii/modules/bar/Bar.qml +++ b/.config/quickshell/ii/modules/bar/Bar.qml @@ -50,7 +50,7 @@ Scope { Connections { target: GlobalStates function onSuperDownChanged() { - if (!Config?.options.bar.autoHide.showWhenPressingSuper.enabled) return; + if (!Config?.options.bar.autoHide.showWhenPressingSuper.enable) return; if (GlobalStates.superDown) showBarTimer.restart(); else { showBarTimer.stop(); @@ -61,7 +61,7 @@ Scope { property bool superShow: false property bool mustShow: hoverRegion.containsMouse || superShow exclusionMode: ExclusionMode.Ignore - exclusiveZone: (Config?.options.bar.autoHide.enabled && (!mustShow || !Config?.options.bar.autoHide.pushWindows)) ? 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 @@ -91,7 +91,7 @@ Scope { left: parent.left top: parent.top bottom: undefined - topMargin: (Config?.options.bar.autoHide.enabled && !mustShow) ? -Appearance.sizes.barHeight + 1 : 0 + topMargin: (Config?.options.bar.autoHide.enable && !mustShow) ? -Appearance.sizes.barHeight + 1 : 0 bottomMargin: 0 } Behavior on anchors.topMargin { @@ -116,7 +116,7 @@ Scope { PropertyChanges { target: barContent anchors.topMargin: 0 - anchors.bottomMargin: (Config?.options.bar.autoHide.enabled && !mustShow) ? -Appearance.sizes.barHeight + 1 : 0 + anchors.bottomMargin: (Config?.options.bar.autoHide.enable && !mustShow) ? -Appearance.sizes.barHeight + 1 : 0 } } } diff --git a/.config/quickshell/ii/modules/common/Config.qml b/.config/quickshell/ii/modules/common/Config.qml index 00be02b26..9180000a5 100644 --- a/.config/quickshell/ii/modules/common/Config.qml +++ b/.config/quickshell/ii/modules/common/Config.qml @@ -125,10 +125,10 @@ Singleton { property JsonObject bar: JsonObject { property JsonObject autoHide: JsonObject { - property bool enabled: false + property bool enable: false property bool pushWindows: false property JsonObject showWhenPressingSuper: JsonObject { - property bool enabled: true + property bool enable: true property int delay: 100 } } From db66b85e61997e7ec292d2967dccf9a23daaaaf3 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Fri, 8 Aug 2025 20:24:37 +0700 Subject: [PATCH 28/70] bar: move number showing logic from GlobalStates to Workspaces --- .config/quickshell/ii/GlobalStates.qml | 17 -------- .../quickshell/ii/modules/bar/Workspaces.qml | 40 +++++++++++++++---- 2 files changed, 32 insertions(+), 25 deletions(-) diff --git a/.config/quickshell/ii/GlobalStates.qml b/.config/quickshell/ii/GlobalStates.qml index 55d0b67cc..163507177 100644 --- a/.config/quickshell/ii/GlobalStates.qml +++ b/.config/quickshell/ii/GlobalStates.qml @@ -32,32 +32,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 - repeat: false - onTriggered: { - workspaceShowNumbers = true - } - } - GlobalShortcut { name: "workspaceNumber" description: "Hold to show workspace numbers, release to show icons" onPressed: { root.superDown = true - workspaceShowNumbersTimer.start() } onReleased: { root.superDown = false - workspaceShowNumbersTimer.stop() - workspaceShowNumbers = false } } 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) From 61e4f59aa02d3da1e2a8f4faf61dbd4ffeea71f7 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Fri, 8 Aug 2025 20:47:20 +0700 Subject: [PATCH 29/70] welcome app: more bar options, extra info on closing --- .config/quickshell/ii/welcome.qml | 143 +++++++++++++++++++++--------- 1 file changed, 100 insertions(+), 43 deletions(-) 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"); } } } From d0ad416e2895cd20740952c7164b4145300726a6 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Fri, 8 Aug 2025 20:53:47 +0700 Subject: [PATCH 30/70] bar: adjust autohide super press show delay --- .config/quickshell/ii/modules/common/Config.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.config/quickshell/ii/modules/common/Config.qml b/.config/quickshell/ii/modules/common/Config.qml index 9180000a5..10dc5a379 100644 --- a/.config/quickshell/ii/modules/common/Config.qml +++ b/.config/quickshell/ii/modules/common/Config.qml @@ -129,7 +129,7 @@ Singleton { property bool pushWindows: false property JsonObject showWhenPressingSuper: JsonObject { property bool enable: true - property int delay: 100 + property int delay: 140 } } property bool bottom: false // Instead of top From 39a88a6cd3d1742646d774b798b7b79bd033176e Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Fri, 8 Aug 2025 21:02:45 +0700 Subject: [PATCH 31/70] ButtonGroup: add uniformCellSizes --- .config/quickshell/ii/modules/common/widgets/ButtonGroup.qml | 1 + 1 file changed, 1 insertion(+) 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 From 2b87d939bb20efe2b424e74bdaac26160139bfdb Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Fri, 8 Aug 2025 21:03:11 +0700 Subject: [PATCH 32/70] make fade of ripple effect longer --- .../quickshell/ii/modules/common/widgets/PrimaryTabButton.qml | 1 + .config/quickshell/ii/modules/common/widgets/RippleButton.qml | 1 + .../quickshell/ii/modules/common/widgets/SecondaryTabButton.qml | 1 + 3 files changed, 3 insertions(+) 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..955d0bb69 100644 --- a/.config/quickshell/ii/modules/common/widgets/RippleButton.qml +++ b/.config/quickshell/ii/modules/common/widgets/RippleButton.qml @@ -91,6 +91,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..568634bbe 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 From 4328746df0ed8bc545e9fd73a99b62d52bdcc75a Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Fri, 8 Aug 2025 21:36:45 +0700 Subject: [PATCH 33/70] SecondaryTabButton: make ripple radial gradient --- .../common/widgets/SecondaryTabButton.qml | 24 +++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/.config/quickshell/ii/modules/common/widgets/SecondaryTabButton.qml b/.config/quickshell/ii/modules/common/widgets/SecondaryTabButton.qml index 568634bbe..774da7d51 100644 --- a/.config/quickshell/ii/modules/common/widgets/SecondaryTabButton.qml +++ b/.config/quickshell/ii/modules/common/widgets/SecondaryTabButton.qml @@ -107,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 From 521061fc645f957d67adbe8aa1c03f4c8496aa73 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Fri, 8 Aug 2025 21:37:32 +0700 Subject: [PATCH 34/70] sidebar: volume mixer: adjust style --- .../ii/modules/sidebarRight/CenterWidgetGroup.qml | 2 +- .../volumeMixer/AudioDeviceSelectorButton.qml | 8 +++++--- .../modules/sidebarRight/volumeMixer/VolumeMixer.qml | 12 +++--------- 3 files changed, 9 insertions(+), 13 deletions(-) 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/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 From 91fee0d6e993b3bbe12396c9c75b50c04d7b4a1c Mon Sep 17 00:00:00 2001 From: Nyx <189459385+nyx-4@users.noreply.github.com> Date: Fri, 8 Aug 2025 19:51:03 +0500 Subject: [PATCH 35/70] Minor tweaks and better variables names Signed-off-by: Nyx <189459385+nyx-4@users.noreply.github.com> --- .../quickshell/ii/modules/common/Config.qml | 3 +- .../sidebarRight/pomodoro/PomodoroWidget.qml | 46 ++++----- .config/quickshell/ii/services/Pomodoro.qml | 94 +++++++++++-------- 3 files changed, 82 insertions(+), 61 deletions(-) diff --git a/.config/quickshell/ii/modules/common/Config.qml b/.config/quickshell/ii/modules/common/Config.qml index 032fd441f..73930f938 100644 --- a/.config/quickshell/ii/modules/common/Config.qml +++ b/.config/quickshell/ii/modules/common/Config.qml @@ -254,11 +254,12 @@ Singleton { property string format: "hh:mm" property string dateFormat: "ddd, dd/MM" property JsonObject pomodoro: JsonObject { + property string alertSound: "" + property bool autoRun: false property int breakTime: 300 property int cycle: 4 property int focus: 1500 property int longBreak: 1200 - property bool running: false } } diff --git a/.config/quickshell/ii/modules/sidebarRight/pomodoro/PomodoroWidget.qml b/.config/quickshell/ii/modules/sidebarRight/pomodoro/PomodoroWidget.qml index b3c360665..6ab70cc7d 100644 --- a/.config/quickshell/ii/modules/sidebarRight/pomodoro/PomodoroWidget.qml +++ b/.config/quickshell/ii/modules/sidebarRight/pomodoro/PomodoroWidget.qml @@ -21,7 +21,7 @@ Item { property int lapsListItemSpacing: 5 - // These are keybinds, make sure to change them. + // 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) { if (event.key === Qt.Key_PageDown) { @@ -30,15 +30,15 @@ Item { currentTab = Math.max(currentTab - 1, 0) } event.accepted = true - } else if (event.key === Qt.Key_Space && !showDialog) { - // Toggle start/stop with Space key + } else if (event.key === Qt.Key_Space || event.key === Qt.Key_S) { + // Toggle start/stop with Space or S key if (currentTab === 0) { Pomodoro.togglePomodoro() } else { Pomodoro.toggleStopwatch() } event.accepted = true - } else if (event.key === Qt.Key_R && !showDialog) { + } else if (event.key === Qt.Key_R) { // Reset with R key if (currentTab === 0) { Pomodoro.pomodoroReset() @@ -46,18 +46,18 @@ Item { Pomodoro.stopwatchReset() } event.accepted = true - } else if (event.key === Qt.Key_Escape && showDialog) { - showDialog = false - event.accepted = true + } else if (event.key === Qt.Key_L) { + // record Stopwatch lap with L key, regardless of current Tab + Pomodoro.recordLaps() } } Timer { id: pomodoroTimer interval: 200 - running: Config.options.time.pomodoro.running + running: Pomodoro.isPomodoroRunning repeat: true - onTriggered: Pomodoro.tickSecond() + onTriggered: Pomodoro.refreshPomodoro() } Timer { @@ -65,7 +65,7 @@ Item { interval: 10 running: Pomodoro.isStopwatchRunning repeat: true - onTriggered: Pomodoro.tick10ms() + onTriggered: Pomodoro.refreshStopwatch() } @@ -116,7 +116,7 @@ Item { Rectangle { id: indicator property int tabCount: root.tabButtonList.length - property real fullTabSize: root.width / tabCount + property real fullTabSize: root.width / tabCount; property real targetWidth: tabBar.contentItem.children[0].children[tabBar.currentIndex].tabContentWidth implicitWidth: targetWidth @@ -176,7 +176,7 @@ Item { lineWidth: 7 gapAngle: Math.PI / 14 value: { - let pomodoroTotalTime = Pomodoro.isPomodoroBreak ? Pomodoro.pomodoroBreakTime : Pomodoro.pomodoroFocusTime + let pomodoroTotalTime = Pomodoro.isBreak ? Pomodoro.breakTime : Pomodoro.focusTime return Pomodoro.getPomodoroSecondsLeft / pomodoroTotalTime } size: 125 @@ -200,7 +200,7 @@ Item { } StyledText { Layout.alignment: Qt.AlignHCenter - text: Pomodoro.isPomodoroBreak ? Translation.tr("Break") : Translation.tr("Focus") + text: Pomodoro.isBreak ? Translation.tr("Break") : Translation.tr("Focus") font.pixelSize: Appearance.font.pixelSize.normal color: Appearance.m3colors.m3onSurface } @@ -273,6 +273,10 @@ Item { value: Config.options.time.pomodoro.focus / 60 onValueChanged: { Config.options.time.pomodoro.focus = value * 60 + if (Pomodoro.isPomodoroReset) { // Special case for Pomodoro in Reset state + Pomodoro.getPomodoroSecondsLeft = Pomodoro.focusTime + Pomodoro.timeLeft = Pomodoro.focusTime + } } } @@ -335,7 +339,9 @@ Item { // Stopwatch Tab Item { + id: stopwatchTab Layout.fillWidth: true + Layout.fillHeight: true ColumnLayout { anchors.horizontalCenter: parent.horizontalCenter @@ -346,6 +352,7 @@ Item { spacing: 40 // The Stopwatch circle CircularProgress { + id: stopwatchClock Layout.alignment: Qt.AlignHCenter lineWidth: 7 gapAngle: Math.PI / 18 @@ -413,7 +420,7 @@ Item { Layout.preferredHeight: 35 Layout.preferredWidth: 90 font.pixelSize: Appearance.font.pixelSize.larger - onClicked: Pomodoro.stopwatchReset() + onClicked: Pomodoro.stopwatchResetOrLaps() colBackground: Appearance.m3colors.m3onError colBackgroundHover: Appearance.m3colors.m3onError } @@ -423,9 +430,10 @@ Item { StyledListView { id: lapsList Layout.fillWidth: true - Layout.preferredHeight: contentHeight + Layout.preferredHeight: stopwatchTab.height - stopwatchClock.height - 20 spacing: lapsListItemSpacing clip: true + popin: true model: Pomodoro.stopwatchLaps delegate: Rectangle { @@ -446,11 +454,8 @@ Item { font.pixelSize: Appearance.font.pixelSize.normal text: { - let lapIndex = index + 1 let lapTime = modelData - // if (index > 0) { - // lapTime = modelData - Pomodoro.stopwatchLaps[index - 1] - // } + let _10ms = (Math.floor(lapTime) % 100).toString().padStart(2, '0') let totalSeconds = Math.floor(lapTime) / 100 let minutes = Math.floor(totalSeconds / 60).toString().padStart(2, '0') @@ -472,9 +477,8 @@ Item { color: Appearance.colors.colPrimary text: { - let lapTime = modelData if (index != Pomodoro.stopwatchLaps.length - 1) { // except first lap - lapTime = modelData - Pomodoro.stopwatchLaps[index + 1] + let lapTime = modelData - Pomodoro.stopwatchLaps[index + 1] let _10ms = (Math.floor(lapTime) % 100).toString().padStart(2, '0') let totalSeconds = Math.floor(lapTime) / 100 let minutes = Math.floor(totalSeconds / 60).toString().padStart(2, '0') diff --git a/.config/quickshell/ii/services/Pomodoro.qml b/.config/quickshell/ii/services/Pomodoro.qml index 3387081d5..9b7b68da4 100644 --- a/.config/quickshell/ii/services/Pomodoro.qml +++ b/.config/quickshell/ii/services/Pomodoro.qml @@ -14,64 +14,72 @@ import QtQuick Singleton { id: root - property int pomodoroFocusTime: Config.options.time.pomodoro.focus - property int pomodoroBreakTime: Config.options.time.pomodoro.breakTime - property int pomodoroLongBreakTime: Config.options.time.pomodoro.longBreak - property int pomodoroLongBreakCycle: Config.options.time.pomodoro.cycle - property bool isPomodoroRunning: Config.options.time.pomodoro.running + 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 longBreakCycle: Config.options.time.pomodoro.cycle + property string alertSound: Config.options.time.pomodoro.alertSound - property int pomodoroTimeLeft: pomodoroFocusTime - property int getPomodoroSecondsLeft: pomodoroFocusTime - property int pomodoroTimeStarted: getCurrentTimeInSeconds() // The time pomodoro was last Resumed - property bool isPomodoroBreak: false + property bool isPomodoroRunning: Config.options.time.pomodoro.autoRun + property bool isBreak: false + property bool isPomodoroReset: !isPomodoroRunning + property int timeLeft: focusTime + property int getPomodoroSecondsLeft: focusTime + property int pomodoroStartTime: getCurrentTimeInSeconds() // The time pomodoro was last Resumed property int pomodoroCycle: 1 - property int stopwatchTime: 0 property bool isStopwatchRunning: false - property int stopwatchStartTime: 0 + property int stopwatchTime: 0 + property int stopwatchStart: 0 property var stopwatchLaps: [] // Start and Stop button function togglePomodoro() { - Config.options.time.pomodoro.running = !isPomodoroRunning + isPomodoroReset = false + isPomodoroRunning = !isPomodoroRunning if (isPomodoroRunning) { // Pressed Start button - pomodoroTimeStarted = getCurrentTimeInSeconds() + pomodoroStartTime = getCurrentTimeInSeconds() } else { // Pressed Stop button - pomodoroTimeLeft -= (getCurrentTimeInSeconds() - pomodoroTimeStarted) + timeLeft -= (getCurrentTimeInSeconds() - pomodoroStartTime) } } // Reset button function pomodoroReset() { - pomodoroTimeLeft = pomodoroFocusTime - getPomodoroSecondsLeft = pomodoroFocusTime - isPomodoroBreak = false - Config.options.time.pomodoro.running = false + isPomodoroRunning = false + isBreak = false + isPomodoroReset = true + timeLeft = focusTime + pomodoroStartTime = getCurrentTimeInSeconds() pomodoroCycle = 1 + refreshPomodoro() } - function tickSecond() { - if (getCurrentTimeInSeconds() >= pomodoroTimeStarted + pomodoroTimeLeft) { - isPomodoroBreak = !isPomodoroBreak - pomodoroTimeStarted += pomodoroTimeLeft - pomodoroTimeLeft = isPomodoroBreak ? pomodoroBreakTime : pomodoroFocusTime + function refreshPomodoro() { + if (getCurrentTimeInSeconds() >= pomodoroStartTime + timeLeft) { + isBreak = !isBreak + pomodoroStartTime += timeLeft + timeLeft = isBreak ? breakTime : focusTime let notificationTitle, notificationMessage - if (isPomodoroBreak && pomodoroCycle % pomodoroLongBreakCycle === 0) { // isPomodoroLongBreak - notificationMessage = Translation.tr(`Relax for %1 minutes`).arg(Math.floor(pomodoroLongBreakTime / 60)) - } else if (isPomodoroBreak) { - notificationMessage = Translation.tr(`Relax for %1 minutes`).arg(Math.floor(pomodoroBreakTime / 60)) + if (isBreak && pomodoroCycle % longBreakCycle === 0) { // isPomodoroLongBreak + notificationMessage = Translation.tr(`Relax for %1 minutes`).arg(Math.floor(longBreakTime / 60)) + } else if (isBreak) { + notificationMessage = Translation.tr(`Relax for %1 minutes`).arg(Math.floor(breakTime / 60)) } else { - notificationMessage = Translation.tr(`Focus for %1 minutes`).arg(Math.floor(pomodoroFocusTime / 60)) + notificationMessage = Translation.tr(`Focus for %1 minutes`).arg(Math.floor(focusTime / 60)) pomodoroCycle += 1 } Quickshell.execDetached(["notify-send", "Pomodoro", notificationMessage, "-a", "Shell"]) + if (alertSound) { // Play sound only if alertSound is explicitly specified + Quickshell.execDetached(["bash", "-c", `ffplay -nodisp -autoexit ${alertSound}`]) + } } // A nice abstraction for resume logic by updating the TimeStarted - getPomodoroSecondsLeft = (pomodoroTimeStarted + pomodoroTimeLeft) - getCurrentTimeInSeconds() + getPomodoroSecondsLeft = (pomodoroStartTime + timeLeft) - getCurrentTimeInSeconds() } function getCurrentTimeInSeconds() { // Pomodoro uses Seconds @@ -82,8 +90,8 @@ Singleton { return Math.floor(Date.now() / 10) } - function tick10ms() { // stopwatch stores time in 10ms - stopwatchTime = getCurrentTimeIn10ms() - stopwatchStartTime + function refreshStopwatch() { // stopwatch stores time in 10ms + stopwatchTime = getCurrentTimeIn10ms() - stopwatchStart } // Stopwatch functions @@ -91,20 +99,28 @@ Singleton { isStopwatchRunning = !isStopwatchRunning if (isStopwatchRunning) { // Resume from paused time by adjusting start time - stopwatchStartTime = getCurrentTimeIn10ms() - stopwatchTime + stopwatchStart = getCurrentTimeIn10ms() - stopwatchTime + } + } + + function stopwatchResetOrLaps() { + if (isStopwatchRunning) { // Clicked on Lap + recordLaps() + } else { // Clicked on Reset + stopwatchReset() } } function stopwatchReset() { - if (isStopwatchRunning) { // Clicked on Lap + isStopwatchRunning = false + stopwatchTime = 0 + stopwatchStart = 0 + stopwatchLaps = [] + } + + function recordLaps() { stopwatchLaps.unshift(stopwatchTime) // Last lap goes first on list // Reassign to trigger onListChanged, idk copied from Todo.qml root.stopwatchLaps = stopwatchLaps.slice(0) - } else { // Clicked on Reset - isStopwatchRunning = false - stopwatchTime = 0 - stopwatchStartTime = 0 - stopwatchLaps = [] - } } } From 3157e99e04e963c8fb05441cf69ea5ef11a524aa Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Fri, 8 Aug 2025 21:51:30 +0700 Subject: [PATCH 36/70] keybinds: make super key binds work also with right super --- .config/hypr/hyprland/keybinds.conf | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.config/hypr/hyprland/keybinds.conf b/.config/hypr/hyprland/keybinds.conf index 0cc42bcd5..e6e117960 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 # 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) From d256d8fc3539fea3c9639d4f8fc1ec94d9844f2a Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Fri, 8 Aug 2025 22:08:50 +0700 Subject: [PATCH 37/70] add config option to use "normal" scroll behavior (#1782) --- .config/quickshell/ii/modules/common/Config.qml | 1 + .config/quickshell/ii/modules/common/widgets/StyledFlickable.qml | 1 + .config/quickshell/ii/modules/common/widgets/StyledListView.qml | 1 + 3 files changed, 3 insertions(+) diff --git a/.config/quickshell/ii/modules/common/Config.qml b/.config/quickshell/ii/modules/common/Config.qml index 10dc5a379..d115062d0 100644 --- a/.config/quickshell/ii/modules/common/Config.qml +++ b/.config/quickshell/ii/modules/common/Config.qml @@ -191,6 +191,7 @@ Singleton { property JsonObject interactions: JsonObject { property JsonObject scrolling: JsonObject { + property bool fasterTouchpadScroll: false // 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 diff --git a/.config/quickshell/ii/modules/common/widgets/StyledFlickable.qml b/.config/quickshell/ii/modules/common/widgets/StyledFlickable.qml index 7fcf7be88..715d09a62 100644 --- a/.config/quickshell/ii/modules/common/widgets/StyledFlickable.qml +++ b/.config/quickshell/ii/modules/common/widgets/StyledFlickable.qml @@ -11,6 +11,7 @@ Flickable { property real mouseScrollDeltaThreshold: Config?.options.interactions.scrolling.mouseScrollDeltaThreshold ?? 120 MouseArea { + visible: Config?.options.interactions.scrolling.fasterTouchpadScroll anchors.fill: parent acceptedButtons: Qt.NoButton onWheel: function(wheelEvent) { diff --git a/.config/quickshell/ii/modules/common/widgets/StyledListView.qml b/.config/quickshell/ii/modules/common/widgets/StyledListView.qml index 6a9cf258f..7783d6fc0 100644 --- a/.config/quickshell/ii/modules/common/widgets/StyledListView.qml +++ b/.config/quickshell/ii/modules/common/widgets/StyledListView.qml @@ -28,6 +28,7 @@ ListView { boundsBehavior: Flickable.DragOverBounds MouseArea { + visible: Config?.options.interactions.scrolling.fasterTouchpadScroll anchors.fill: parent acceptedButtons: Qt.NoButton onWheel: function(wheelEvent) { From eac4ab3e3c249008d9596023f79dbc2d31012600 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Fri, 8 Aug 2025 22:09:45 +0700 Subject: [PATCH 38/70] enable faster touchpad scroll --- .config/quickshell/ii/modules/common/Config.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.config/quickshell/ii/modules/common/Config.qml b/.config/quickshell/ii/modules/common/Config.qml index d115062d0..d3a9d2f0f 100644 --- a/.config/quickshell/ii/modules/common/Config.qml +++ b/.config/quickshell/ii/modules/common/Config.qml @@ -191,7 +191,7 @@ Singleton { property JsonObject interactions: JsonObject { property JsonObject scrolling: JsonObject { - property bool fasterTouchpadScroll: false // Enable faster scrolling with touchpad + 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 From e43dcf5d4c3eaf0f17b378fcc7a8c657d79973ad Mon Sep 17 00:00:00 2001 From: Alvin <153151121+Alvin-HZ@users.noreply.github.com> Date: Fri, 8 Aug 2025 14:11:06 -0400 Subject: [PATCH 39/70] run hyprctl directly --- .config/quickshell/ii/services/HyprlandData.qml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.config/quickshell/ii/services/HyprlandData.qml b/.config/quickshell/ii/services/HyprlandData.qml index 07c2d89a2..d275d9c67 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: { From 9d98396e33f4561d525c471e8e5416543e568d85 Mon Sep 17 00:00:00 2001 From: Alvin <153151121+Alvin-HZ@users.noreply.github.com> Date: Fri, 8 Aug 2025 14:15:07 -0400 Subject: [PATCH 40/70] Add missing commas --- .config/quickshell/ii/services/HyprlandData.qml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.config/quickshell/ii/services/HyprlandData.qml b/.config/quickshell/ii/services/HyprlandData.qml index d275d9c67..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: ["hyprctl" "clients" "-j"] + command: ["hyprctl", "clients", "-j"] stdout: StdioCollector { id: clientsCollector onStreamFinished: { @@ -87,7 +87,7 @@ Singleton { Process { id: getMonitors - command: ["hyprctl" "monitors" "-j"] + command: ["hyprctl", "monitors", "-j"] stdout: StdioCollector { id: monitorsCollector onStreamFinished: { @@ -98,7 +98,7 @@ Singleton { Process { id: getLayers - command: ["hyprctl" "layers" "-j"] + command: ["hyprctl", "layers", "-j"] stdout: StdioCollector { id: layersCollector onStreamFinished: { @@ -109,7 +109,7 @@ Singleton { Process { id: getWorkspaces - command: ["hyprctl" "workspaces" "-j"] + command: ["hyprctl", "workspaces", "-j"] stdout: StdioCollector { id: workspacesCollector onStreamFinished: { @@ -127,7 +127,7 @@ Singleton { Process { id: getActiveWorkspace - command: ["hyprctl" "activeworkspace" "-j"] + command: ["hyprctl", "activeworkspace", "-j"] stdout: StdioCollector { id: activeWorkspaceCollector onStreamFinished: { From e15d2fe82c4c21278ec56f4caf48a12f29953bbb Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sat, 9 Aug 2025 08:55:29 +0700 Subject: [PATCH 41/70] easyeffects toggle: move logic to a service --- .../quickToggles/EasyEffectsToggle.qml | 36 +++-------- .../quickshell/ii/services/EasyEffects.qml | 61 +++++++++++++++++++ 2 files changed, 70 insertions(+), 27 deletions(-) create mode 100644 .config/quickshell/ii/services/EasyEffects.qml 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/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 + } + } +} From fb4f8a86f438dfca0acb8cbf19b97e9ce42ce07a Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sat, 9 Aug 2025 08:56:12 +0700 Subject: [PATCH 42/70] lock screen: use quickshell, add hack for window refocus --- .config/hypr/hypridle.conf | 4 ++-- .config/quickshell/ii/modules/lock/Lock.qml | 3 +++ 2 files changed, 5 insertions(+), 2 deletions(-) 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/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"`]) } } From 91dae8ad85c816a0b3eebe14e01d2c2a67b56ef9 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sat, 9 Aug 2025 10:42:50 +0700 Subject: [PATCH 43/70] pomodoro widget: use grid instead of row/col spam --- .../sidebarRight/pomodoro/PomodoroWidget.qml | 144 ++++++++---------- 1 file changed, 65 insertions(+), 79 deletions(-) diff --git a/.config/quickshell/ii/modules/sidebarRight/pomodoro/PomodoroWidget.qml b/.config/quickshell/ii/modules/sidebarRight/pomodoro/PomodoroWidget.qml index 6ab70cc7d..446a32977 100644 --- a/.config/quickshell/ii/modules/sidebarRight/pomodoro/PomodoroWidget.qml +++ b/.config/quickshell/ii/modules/sidebarRight/pomodoro/PomodoroWidget.qml @@ -8,8 +8,6 @@ import QtQuick.Controls import QtQuick.Layouts import Quickshell - - Item { id: root property int currentTab: 0 @@ -245,92 +243,80 @@ Item { } // The SpinBoxes for adjusting duration - ColumnLayout { - RowLayout { - Layout.fillWidth: true - spacing: 20 + GridLayout { + Layout.alignment: Qt.AlignHCenter + columns: 2 + uniformCellWidths: true + columnSpacing: 20 + rowSpacing: 6 - StyledText { - id: focusTextBox - Layout.leftMargin: focusSpinBox.implicitWidth / 2 - 7 - text: Translation.tr("Focus") - } - StyledText { - Layout.leftMargin: breakSpinBox.implicitWidth / 2 + 10 - text: Translation.tr("Break") - } - } - - RowLayout { + StyledText { Layout.alignment: Qt.AlignHCenter - spacing: 0 - - ConfigSpinBox { - id: focusSpinBox - spacing: 0 - Layout.leftMargin: 0 - Layout.rightMargin: 0 - value: Config.options.time.pomodoro.focus / 60 - onValueChanged: { - Config.options.time.pomodoro.focus = value * 60 - if (Pomodoro.isPomodoroReset) { // Special case for Pomodoro in Reset state - Pomodoro.getPomodoroSecondsLeft = Pomodoro.focusTime - Pomodoro.timeLeft = Pomodoro.focusTime - } - } - } - - ConfigSpinBox { - id: breakSpinBox - spacing: 0 - Layout.leftMargin: 0 - Layout.rightMargin: 0 - value: Config.options.time.pomodoro.breakTime / 60 - onValueChanged: { - Config.options.time.pomodoro.breakTime = value * 60 - } - } + text: Translation.tr("Focus") } - RowLayout { - Layout.fillWidth: true - spacing: 20 - - StyledText { - Layout.leftMargin: focusSpinBox.implicitWidth / 2 - 6 - text: Translation.tr("Cycle") - } - StyledText { - Layout.leftMargin: breakSpinBox.implicitWidth / 2 - text: Translation.tr("Long break") - } - } - - RowLayout { + StyledText { Layout.alignment: Qt.AlignHCenter - spacing: 0 + text: Translation.tr("Break") + } - ConfigSpinBox { - id: cycleSpinBox - spacing: 0 - from: 1 - Layout.leftMargin: 0 - Layout.rightMargin: 0 - value: Config.options.time.pomodoro.cycle - onValueChanged: { - Config.options.time.pomodoro.cycle = value + ConfigSpinBox { + id: focusSpinBox + spacing: 0 + Layout.leftMargin: 0 + Layout.rightMargin: 0 + value: Config.options.time.pomodoro.focus / 60 + onValueChanged: { + Config.options.time.pomodoro.focus = value * 60 + if (Pomodoro.isPomodoroReset) { // Special case for Pomodoro in Reset state + Pomodoro.getPomodoroSecondsLeft = Pomodoro.focusTime + Pomodoro.timeLeft = Pomodoro.focusTime } } + } - ConfigSpinBox { - id: longBreakSpinBox - spacing: 0 - Layout.leftMargin: 0 - Layout.rightMargin: 0 - value: Config.options.time.pomodoro.longBreak / 60 - onValueChanged: { - Config.options.time.pomodoro.longBreak = value * 60 - } + ConfigSpinBox { + id: breakSpinBox + spacing: 0 + Layout.leftMargin: 0 + Layout.rightMargin: 0 + value: Config.options.time.pomodoro.breakTime / 60 + onValueChanged: { + Config.options.time.pomodoro.breakTime = value * 60 + } + } + + StyledText { + Layout.topMargin: 6 + Layout.alignment: Qt.AlignHCenter + text: Translation.tr("Cycle") + } + StyledText { + Layout.topMargin: 6 + Layout.alignment: Qt.AlignHCenter + text: Translation.tr("Long break") + } + + ConfigSpinBox { + id: cycleSpinBox + spacing: 0 + from: 1 + Layout.leftMargin: 0 + Layout.rightMargin: 0 + value: Config.options.time.pomodoro.cycle + onValueChanged: { + Config.options.time.pomodoro.cycle = value + } + } + + ConfigSpinBox { + id: longBreakSpinBox + spacing: 0 + Layout.leftMargin: 0 + Layout.rightMargin: 0 + value: Config.options.time.pomodoro.longBreak / 60 + onValueChanged: { + Config.options.time.pomodoro.longBreak = value * 60 } } } From 814f6ced5fd277d0a212e42f33f1e01bc8ef266e Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sat, 9 Aug 2025 11:08:05 +0700 Subject: [PATCH 44/70] hide dupe keybind for one thing --- .config/hypr/hyprland/keybinds.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.config/hypr/hyprland/keybinds.conf b/.config/hypr/hyprland/keybinds.conf index e6e117960..c7e953d94 100644 --- a/.config/hypr/hyprland/keybinds.conf +++ b/.config/hypr/hyprland/keybinds.conf @@ -5,7 +5,7 @@ ##! 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 # 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] From 9bc4ff16a7b3aa502776658fc403deecdc531c9e Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sat, 9 Aug 2025 12:54:37 +0700 Subject: [PATCH 45/70] make pomodoro timer persistent --- .config/quickshell/ii/modules/common/Config.qml | 1 - .../quickshell/ii/modules/common/Persistent.qml | 7 +++++++ .config/quickshell/ii/services/Pomodoro.qml | 14 +++++++------- 3 files changed, 14 insertions(+), 8 deletions(-) diff --git a/.config/quickshell/ii/modules/common/Config.qml b/.config/quickshell/ii/modules/common/Config.qml index 73930f938..9580c3bc0 100644 --- a/.config/quickshell/ii/modules/common/Config.qml +++ b/.config/quickshell/ii/modules/common/Config.qml @@ -255,7 +255,6 @@ Singleton { property string dateFormat: "ddd, dd/MM" property JsonObject pomodoro: JsonObject { property string alertSound: "" - property bool autoRun: false property int breakTime: 300 property int cycle: 4 property int focus: 1500 diff --git a/.config/quickshell/ii/modules/common/Persistent.qml b/.config/quickshell/ii/modules/common/Persistent.qml index abd062d73..a650dc0bb 100644 --- a/.config/quickshell/ii/modules/common/Persistent.qml +++ b/.config/quickshell/ii/modules/common/Persistent.qml @@ -44,6 +44,13 @@ 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 + } + } } } } diff --git a/.config/quickshell/ii/services/Pomodoro.qml b/.config/quickshell/ii/services/Pomodoro.qml index 9b7b68da4..073280d65 100644 --- a/.config/quickshell/ii/services/Pomodoro.qml +++ b/.config/quickshell/ii/services/Pomodoro.qml @@ -20,12 +20,12 @@ Singleton { property int longBreakCycle: Config.options.time.pomodoro.cycle property string alertSound: Config.options.time.pomodoro.alertSound - property bool isPomodoroRunning: Config.options.time.pomodoro.autoRun + property bool isPomodoroRunning: Persistent.states.timer.pomodoro.running property bool isBreak: false property bool isPomodoroReset: !isPomodoroRunning property int timeLeft: focusTime property int getPomodoroSecondsLeft: focusTime - property int pomodoroStartTime: getCurrentTimeInSeconds() // The time pomodoro was last Resumed + property int pomodoroStartTime: Persistent.states.timer.pomodoro.start property int pomodoroCycle: 1 property bool isStopwatchRunning: false @@ -36,9 +36,9 @@ Singleton { // Start and Stop button function togglePomodoro() { isPomodoroReset = false - isPomodoroRunning = !isPomodoroRunning + Persistent.states.timer.pomodoro.running = !isPomodoroRunning if (isPomodoroRunning) { // Pressed Start button - pomodoroStartTime = getCurrentTimeInSeconds() + Persistent.states.timer.pomodoro.start = getCurrentTimeInSeconds() } else { // Pressed Stop button timeLeft -= (getCurrentTimeInSeconds() - pomodoroStartTime) } @@ -46,11 +46,11 @@ Singleton { // Reset button function pomodoroReset() { - isPomodoroRunning = false + Persistent.states.timer.pomodoro.running = false isBreak = false isPomodoroReset = true timeLeft = focusTime - pomodoroStartTime = getCurrentTimeInSeconds() + Persistent.states.timer.pomodoro.start = getCurrentTimeInSeconds() pomodoroCycle = 1 refreshPomodoro() } @@ -58,7 +58,7 @@ Singleton { function refreshPomodoro() { if (getCurrentTimeInSeconds() >= pomodoroStartTime + timeLeft) { isBreak = !isBreak - pomodoroStartTime += timeLeft + Persistent.states.timer.pomodoro.start += timeLeft timeLeft = isBreak ? breakTime : focusTime let notificationTitle, notificationMessage From de759b5120004806323955493e163ce97e604750 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sat, 9 Aug 2025 12:54:53 +0700 Subject: [PATCH 46/70] redesign stopwatch --- .../sidebarRight/pomodoro/PomodoroWidget.qml | 168 +++++++++--------- 1 file changed, 83 insertions(+), 85 deletions(-) diff --git a/.config/quickshell/ii/modules/sidebarRight/pomodoro/PomodoroWidget.qml b/.config/quickshell/ii/modules/sidebarRight/pomodoro/PomodoroWidget.qml index 446a32977..8e0d65f74 100644 --- a/.config/quickshell/ii/modules/sidebarRight/pomodoro/PomodoroWidget.qml +++ b/.config/quickshell/ii/modules/sidebarRight/pomodoro/PomodoroWidget.qml @@ -15,9 +15,6 @@ Item { {"name": Translation.tr("Pomodoro"), "icon": "timer_play"}, {"name": Translation.tr("Stopwatch"), "icon": "timer"} ] - property int lapsListItemPadding: 8 - property int lapsListItemSpacing: 5 - // These are keybinds for stopwatch and pomodoro Keys.onPressed: (event) => { @@ -214,7 +211,7 @@ Item { contentItem: StyledText { anchors.centerIn: parent horizontalAlignment: Text.AlignHCenter - text: Pomodoro.isPomodoroRunning ? Translation.tr("Stop") : Translation.tr("Start") + text: Pomodoro.isPomodoroRunning ? Translation.tr("Pause") : Translation.tr("Start") color: Appearance.colors.colSecondary } Layout.preferredHeight: 35 @@ -330,62 +327,53 @@ Item { Layout.fillHeight: true ColumnLayout { - anchors.horizontalCenter: parent.horizontalCenter + anchors { + fill: parent + leftMargin: 20 + rightMargin: 20 + } spacing: 20 - Layout.fillWidth: true - RowLayout { - spacing: 40 - // The Stopwatch circle - CircularProgress { - id: stopwatchClock + ColumnLayout { + spacing: 8 + Layout.alignment: Qt.AlignHCenter + Layout.fillWidth: false + + RowLayout { // Elapsed + id: elapsedIndicator Layout.alignment: Qt.AlignHCenter - lineWidth: 7 - gapAngle: Math.PI / 18 - value: { - return Pomodoro.stopwatchTime % 6000 / 6000 // The seconds in percent - } - size: 125 - primaryColor: Math.floor(Pomodoro.stopwatchTime / 6000) % 2 ? Appearance.colors.colSecondaryContainer : Appearance.m3colors.m3onSecondaryContainer - secondaryColor: Math.floor(Pomodoro.stopwatchTime / 6000) % 2 ? Appearance.m3colors.m3onSecondaryContainer : Appearance.colors.colSecondaryContainer - enableAnimation: false // The animation seems weird after each cycle - - ColumnLayout { - anchors.centerIn: parent - spacing: 0 - - StyledText { - Layout.alignment: Qt.AlignHCenter - text: { - let totalSeconds = Math.floor(Pomodoro.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}` - } - font.pixelSize: Appearance.font.pixelSize.hugeass + 4 - color: Appearance.m3colors.m3onSurface + spacing: 0 + StyledText { + Layout.preferredWidth: elapsedIndicator.width * 0.6 // Prevent shakiness + font.pixelSize: 40 + color: Appearance.m3colors.m3onSurface + text: { + let totalSeconds = Math.floor(Pomodoro.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.alignment: Qt.AlignHCenter - text: { - return (Math.floor(Pomodoro.stopwatchTime) % 100).toString().padStart(2, '0') - } - font.pixelSize: Appearance.font.pixelSize.normal - color: Appearance.m3colors.m3onSurface + } + StyledText { + Layout.fillWidth: true + font.pixelSize: 40 + color: Appearance.colors.colSubtext + text: { + return `:${(Math.floor(Pomodoro.stopwatchTime) % 100).toString().padStart(2, '0')}` } } } // The Start/Stop and Reset buttons - ColumnLayout { + RowLayout { Layout.alignment: Qt.AlignHCenter - spacing: 10 + spacing: 4 RippleButton { contentItem: StyledText { anchors.centerIn: parent horizontalAlignment: Text.AlignHCenter - text: Pomodoro.isStopwatchRunning ? Translation.tr("Stop") : Translation.tr("Start") + text: Pomodoro.isStopwatchRunning ? Translation.tr("Pause") : Pomodoro.stopwatchTime === 0 ? Translation.tr("Start") : Translation.tr("Resume") color: Appearance.colors.colSecondary } Layout.preferredHeight: 35 @@ -413,65 +401,75 @@ Item { } } + // Laps StyledListView { id: lapsList Layout.fillWidth: true - Layout.preferredHeight: stopwatchTab.height - stopwatchClock.height - 20 + Layout.fillHeight: true spacing: lapsListItemSpacing clip: true popin: true - model: Pomodoro.stopwatchLaps + + model: ScriptModel { + values: Pomodoro.stopwatchLaps + } delegate: Rectangle { + id: lapItem + required property int index + required property var modelData + property var horizontalPadding: 10 + property var verticalPadding: 6 width: lapsList.width - implicitHeight: lapsContentText.implicitHeight + lapsListItemPadding + implicitHeight: lapRow.implicitHeight + verticalPadding * 2 + implicitWidth: lapRow.implicitWidth + horizontalPadding * 2 color: Appearance.colors.colLayer2 radius: Appearance.rounding.small - StyledText { - id: lapsContentText - anchors.left: parent.left - anchors.top: parent.top - anchors.bottom: parent.bottom - leftPadding: lapsListItemPadding - rightPadding: lapsListItemPadding - topPadding: lapsListItemPadding / 2 - bottomPadding: lapsListItemPadding / 2 - font.pixelSize: Appearance.font.pixelSize.normal - - text: { - let lapTime = modelData - - let _10ms = (Math.floor(lapTime) % 100).toString().padStart(2, '0') - let totalSeconds = Math.floor(lapTime) / 100 - let minutes = Math.floor(totalSeconds / 60).toString().padStart(2, '0') - let seconds = Math.floor(totalSeconds % 60).toString().padStart(2, '0') - return `${minutes}:${seconds}.${_10ms}` + RowLayout { + id: lapRow + anchors { + fill: parent + leftMargin: lapItem.horizontalPadding + rightMargin: lapItem.horizontalPadding + topMargin: lapItem.verticalPadding + bottomMargin: lapItem.verticalPadding } - } - StyledText { - id: lapsDiffText - anchors.right: parent.right - anchors.top: parent.top - anchors.bottom: parent.bottom - leftPadding: lapsListItemPadding - rightPadding: lapsListItemPadding * 2 - topPadding: lapsListItemPadding / 2 - bottomPadding: lapsListItemPadding / 2 - font.pixelSize: Appearance.font.pixelSize.normal - color: Appearance.colors.colPrimary + StyledText { + font.pixelSize: Appearance.font.pixelSize.small + color: Appearance.colors.colSubtext + text: `${Pomodoro.stopwatchLaps.length - lapItem.index}.` + } - text: { - if (index != Pomodoro.stopwatchLaps.length - 1) { // except first lap - let lapTime = modelData - Pomodoro.stopwatchLaps[index + 1] + StyledText { + font.pixelSize: Appearance.font.pixelSize.small + text: { + let lapTime = lapItem.modelData let _10ms = (Math.floor(lapTime) % 100).toString().padStart(2, '0') let totalSeconds = Math.floor(lapTime) / 100 let minutes = Math.floor(totalSeconds / 60).toString().padStart(2, '0') let seconds = Math.floor(totalSeconds % 60).toString().padStart(2, '0') - return `+${minutes}:${seconds}.${_10ms}` - } else { - return `` // Nothing for first lap + return `${minutes}:${seconds}.${_10ms}` + } + } + + Item { Layout.fillWidth: true } + + StyledText { + font.pixelSize: Appearance.font.pixelSize.smaller + color: Appearance.colors.colPrimary + text: { + if (lapItem.index != Pomodoro.stopwatchLaps.length - 1) { // except first lap + let lapTime = lapItem.modelData - Pomodoro.stopwatchLaps[lapItem.index + 1] + let _10ms = (Math.floor(lapTime) % 100).toString().padStart(2, '0') + let totalSeconds = Math.floor(lapTime) / 100 + let minutes = Math.floor(totalSeconds / 60).toString().padStart(2, '0') + let seconds = Math.floor(totalSeconds % 60).toString().padStart(2, '0') + return `+${minutes == "00" ? "" : minutes + ":"}${seconds}.${_10ms}` + } else { + return `` // Nothing for first lap + } } } } From 0ee9afba4f3f3efa4d93bc57570ac6eba8108741 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sat, 9 Aug 2025 16:28:17 +0700 Subject: [PATCH 47/70] pomodoro: move tabs to their own files, adjust colors --- .../ii/modules/common/Appearance.qml | 8 + .../modules/common/widgets/RippleButton.qml | 1 + .../sidebarRight/pomodoro/PomodoroTimer.qml | 176 ++++++++++ .../sidebarRight/pomodoro/PomodoroWidget.qml | 327 +----------------- .../sidebarRight/pomodoro/Stopwatch.qml | 170 +++++++++ 5 files changed, 359 insertions(+), 323 deletions(-) create mode 100644 .config/quickshell/ii/modules/sidebarRight/pomodoro/PomodoroTimer.qml create mode 100644 .config/quickshell/ii/modules/sidebarRight/pomodoro/Stopwatch.qml diff --git a/.config/quickshell/ii/modules/common/Appearance.qml b/.config/quickshell/ii/modules/common/Appearance.qml index 9c9374860..c16574e0f 100644 --- a/.config/quickshell/ii/modules/common/Appearance.qml +++ b/.config/quickshell/ii/modules/common/Appearance.qml @@ -146,6 +146,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 { diff --git a/.config/quickshell/ii/modules/common/widgets/RippleButton.qml b/.config/quickshell/ii/modules/common/widgets/RippleButton.qml index 7487203ae..750e9a318 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) : 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..57bb4a9dc --- /dev/null +++ b/.config/quickshell/ii/modules/sidebarRight/pomodoro/PomodoroTimer.qml @@ -0,0 +1,176 @@ +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 { + ColumnLayout { + anchors.horizontalCenter: parent.horizontalCenter + spacing: 20 + + RowLayout { + spacing: 40 + // The Pomodoro timer circle + CircularProgress { + Layout.alignment: Qt.AlignHCenter + lineWidth: 7 + gapAngle: Math.PI / 14 + value: { + let pomodoroTotalTime = Pomodoro.isBreak ? Pomodoro.breakTime : Pomodoro.focusTime + return Pomodoro.getPomodoroSecondsLeft / pomodoroTotalTime + } + size: 125 + primaryColor: Appearance.m3colors.m3onSecondaryContainer + secondaryColor: Appearance.colors.colSecondaryContainer + enableAnimation: true + + ColumnLayout { + anchors.centerIn: parent + spacing: 0 + + StyledText { + Layout.alignment: Qt.AlignHCenter + text: { + let minutes = Math.floor(Pomodoro.getPomodoroSecondsLeft / 60).toString().padStart(2, '0') + let seconds = Math.floor(Pomodoro.getPomodoroSecondsLeft % 60).toString().padStart(2, '0') + return `${minutes}:${seconds}` + } + font.pixelSize: Appearance.font.pixelSize.hugeass + 4 + color: Appearance.m3colors.m3onSurface + } + StyledText { + Layout.alignment: Qt.AlignHCenter + text: Pomodoro.isBreak ? Translation.tr("Break") : Translation.tr("Focus") + font.pixelSize: Appearance.font.pixelSize.normal + color: Appearance.m3colors.m3onSurface + } + } + } + + // The Start/Stop and Reset buttons + ColumnLayout { + Layout.alignment: Qt.AlignHCenter + spacing: 10 + + RippleButton { + contentItem: StyledText { + anchors.centerIn: parent + horizontalAlignment: Text.AlignHCenter + text: Pomodoro.isPomodoroRunning ? Translation.tr("Pause") : (Pomodoro.getPomodoroSecondsLeft === Pomodoro.focusTime) ? Translation.tr("Start") : Translation.tr("Resume") + color: Pomodoro.isPomodoroRunning ? Appearance.colors.colOnSecondaryContainer : Appearance.colors.colOnPrimary + } + implicitHeight: 35 + implicitWidth: 90 + font.pixelSize: Appearance.font.pixelSize.larger + onClicked: Pomodoro.togglePomodoro() + colBackground: Pomodoro.isPomodoroRunning ? Appearance.colors.colSecondaryContainer : Appearance.colors.colPrimary + colBackgroundHover: Pomodoro.isPomodoroRunning ? Appearance.colors.colSecondaryContainer : Appearance.colors.colPrimary + } + + RippleButton { + implicitHeight: 35 + implicitWidth: 90 + + onClicked: Pomodoro.pomodoroReset() + enabled: (Pomodoro.getPomodoroSecondsLeft < Pomodoro.focusTime) + + 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 + } + } + } + } + + // The SpinBoxes for adjusting duration + GridLayout { + Layout.alignment: Qt.AlignHCenter + columns: 2 + uniformCellWidths: true + columnSpacing: 20 + rowSpacing: 4 + + StyledText { + Layout.alignment: Qt.AlignHCenter + text: Translation.tr("Focus") + } + + StyledText { + Layout.alignment: Qt.AlignHCenter + text: Translation.tr("Break") + } + + ConfigSpinBox { + id: focusSpinBox + spacing: 0 + Layout.leftMargin: 0 + Layout.rightMargin: 0 + value: Config.options.time.pomodoro.focus / 60 + onValueChanged: { + Config.options.time.pomodoro.focus = value * 60 + if (Pomodoro.isPomodoroReset) { // Special case for Pomodoro in Reset state + Pomodoro.getPomodoroSecondsLeft = Pomodoro.focusTime + Pomodoro.timeLeft = Pomodoro.focusTime + } + } + } + + ConfigSpinBox { + id: breakSpinBox + spacing: 0 + Layout.leftMargin: 0 + Layout.rightMargin: 0 + value: Config.options.time.pomodoro.breakTime / 60 + onValueChanged: { + Config.options.time.pomodoro.breakTime = value * 60 + } + } + + StyledText { + Layout.topMargin: 6 + Layout.alignment: Qt.AlignHCenter + text: Translation.tr("Cycle") + } + StyledText { + Layout.topMargin: 6 + Layout.alignment: Qt.AlignHCenter + text: Translation.tr("Long break") + } + + ConfigSpinBox { + id: cycleSpinBox + spacing: 0 + from: 1 + Layout.leftMargin: 0 + Layout.rightMargin: 0 + value: Config.options.time.pomodoro.cycle + onValueChanged: { + Config.options.time.pomodoro.cycle = value + } + } + + ConfigSpinBox { + id: longBreakSpinBox + spacing: 0 + Layout.leftMargin: 0 + Layout.rightMargin: 0 + value: Config.options.time.pomodoro.longBreak / 60 + onValueChanged: { + Config.options.time.pomodoro.longBreak = value * 60 + } + } + } + } +} \ No newline at end of file diff --git a/.config/quickshell/ii/modules/sidebarRight/pomodoro/PomodoroWidget.qml b/.config/quickshell/ii/modules/sidebarRight/pomodoro/PomodoroWidget.qml index 8e0d65f74..594533f72 100644 --- a/.config/quickshell/ii/modules/sidebarRight/pomodoro/PomodoroWidget.qml +++ b/.config/quickshell/ii/modules/sidebarRight/pomodoro/PomodoroWidget.qml @@ -2,17 +2,15 @@ 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 property int currentTab: 0 property var tabButtonList: [ - {"name": Translation.tr("Pomodoro"), "icon": "timer_play"}, + {"name": Translation.tr("Pomodoro"), "icon": "search_activity"}, {"name": Translation.tr("Stopwatch"), "icon": "timer"} ] @@ -157,326 +155,9 @@ Item { currentTab = currentIndex } - // Pomodoro Timer Tab - Item { - ColumnLayout { - anchors.horizontalCenter: parent.horizontalCenter - spacing: 20 - - RowLayout { - spacing: 40 - // The Pomodoro timer circle - CircularProgress { - Layout.alignment: Qt.AlignHCenter - lineWidth: 7 - gapAngle: Math.PI / 14 - value: { - let pomodoroTotalTime = Pomodoro.isBreak ? Pomodoro.breakTime : Pomodoro.focusTime - return Pomodoro.getPomodoroSecondsLeft / pomodoroTotalTime - } - size: 125 - primaryColor: Appearance.m3colors.m3onSecondaryContainer - secondaryColor: Appearance.colors.colSecondaryContainer - enableAnimation: true - - ColumnLayout { - anchors.centerIn: parent - spacing: 0 - - StyledText { - Layout.alignment: Qt.AlignHCenter - text: { - let minutes = Math.floor(Pomodoro.getPomodoroSecondsLeft / 60).toString().padStart(2, '0') - let seconds = Math.floor(Pomodoro.getPomodoroSecondsLeft % 60).toString().padStart(2, '0') - return `${minutes}:${seconds}` - } - font.pixelSize: Appearance.font.pixelSize.hugeass + 4 - color: Appearance.m3colors.m3onSurface - } - StyledText { - Layout.alignment: Qt.AlignHCenter - text: Pomodoro.isBreak ? Translation.tr("Break") : Translation.tr("Focus") - font.pixelSize: Appearance.font.pixelSize.normal - color: Appearance.m3colors.m3onSurface - } - } - } - - // The Start/Stop and Reset buttons - ColumnLayout { - Layout.alignment: Qt.AlignHCenter - spacing: 10 - - RippleButton { - contentItem: StyledText { - anchors.centerIn: parent - horizontalAlignment: Text.AlignHCenter - text: Pomodoro.isPomodoroRunning ? Translation.tr("Pause") : Translation.tr("Start") - color: Appearance.colors.colSecondary - } - Layout.preferredHeight: 35 - Layout.preferredWidth: 90 - font.pixelSize: Appearance.font.pixelSize.larger - onClicked: Pomodoro.togglePomodoro() - colBackground: Appearance.colors.colSecondaryContainer - colBackgroundHover: Appearance.colors.colSecondaryContainer - } - - RippleButton { - contentItem: StyledText { - anchors.centerIn: parent - horizontalAlignment: Text.AlignHCenter - text: Translation.tr("Reset") - color: Appearance.colors.colSecondary - } - Layout.preferredHeight: 35 - Layout.preferredWidth: 90 - font.pixelSize: Appearance.font.pixelSize.larger - onClicked: Pomodoro.pomodoroReset() - colBackground: Appearance.m3colors.m3onError - colBackgroundHover: Appearance.m3colors.m3onError - } - } - } - - // The SpinBoxes for adjusting duration - GridLayout { - Layout.alignment: Qt.AlignHCenter - columns: 2 - uniformCellWidths: true - columnSpacing: 20 - rowSpacing: 6 - - StyledText { - Layout.alignment: Qt.AlignHCenter - text: Translation.tr("Focus") - } - - StyledText { - Layout.alignment: Qt.AlignHCenter - text: Translation.tr("Break") - } - - ConfigSpinBox { - id: focusSpinBox - spacing: 0 - Layout.leftMargin: 0 - Layout.rightMargin: 0 - value: Config.options.time.pomodoro.focus / 60 - onValueChanged: { - Config.options.time.pomodoro.focus = value * 60 - if (Pomodoro.isPomodoroReset) { // Special case for Pomodoro in Reset state - Pomodoro.getPomodoroSecondsLeft = Pomodoro.focusTime - Pomodoro.timeLeft = Pomodoro.focusTime - } - } - } - - ConfigSpinBox { - id: breakSpinBox - spacing: 0 - Layout.leftMargin: 0 - Layout.rightMargin: 0 - value: Config.options.time.pomodoro.breakTime / 60 - onValueChanged: { - Config.options.time.pomodoro.breakTime = value * 60 - } - } - - StyledText { - Layout.topMargin: 6 - Layout.alignment: Qt.AlignHCenter - text: Translation.tr("Cycle") - } - StyledText { - Layout.topMargin: 6 - Layout.alignment: Qt.AlignHCenter - text: Translation.tr("Long break") - } - - ConfigSpinBox { - id: cycleSpinBox - spacing: 0 - from: 1 - Layout.leftMargin: 0 - Layout.rightMargin: 0 - value: Config.options.time.pomodoro.cycle - onValueChanged: { - Config.options.time.pomodoro.cycle = value - } - } - - ConfigSpinBox { - id: longBreakSpinBox - spacing: 0 - Layout.leftMargin: 0 - Layout.rightMargin: 0 - value: Config.options.time.pomodoro.longBreak / 60 - onValueChanged: { - Config.options.time.pomodoro.longBreak = value * 60 - } - } - } - } - } - - // Stopwatch Tab - Item { - id: stopwatchTab - Layout.fillWidth: true - Layout.fillHeight: true - - ColumnLayout { - anchors { - fill: parent - leftMargin: 20 - rightMargin: 20 - } - spacing: 20 - - ColumnLayout { - spacing: 8 - Layout.alignment: Qt.AlignHCenter - Layout.fillWidth: false - - RowLayout { // Elapsed - id: elapsedIndicator - Layout.alignment: Qt.AlignHCenter - spacing: 0 - StyledText { - Layout.preferredWidth: elapsedIndicator.width * 0.6 // Prevent shakiness - font.pixelSize: 40 - color: Appearance.m3colors.m3onSurface - text: { - let totalSeconds = Math.floor(Pomodoro.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(Pomodoro.stopwatchTime) % 100).toString().padStart(2, '0')}` - } - } - } - - // The Start/Stop and Reset buttons - RowLayout { - Layout.alignment: Qt.AlignHCenter - spacing: 4 - - RippleButton { - contentItem: StyledText { - anchors.centerIn: parent - horizontalAlignment: Text.AlignHCenter - text: Pomodoro.isStopwatchRunning ? Translation.tr("Pause") : Pomodoro.stopwatchTime === 0 ? Translation.tr("Start") : Translation.tr("Resume") - color: Appearance.colors.colSecondary - } - Layout.preferredHeight: 35 - Layout.preferredWidth: 90 - font.pixelSize: Appearance.font.pixelSize.larger - onClicked: Pomodoro.toggleStopwatch() - colBackground: Appearance.colors.colSecondaryContainer - colBackgroundHover: Appearance.colors.colSecondaryContainer - } - - RippleButton { - contentItem: StyledText { - anchors.centerIn: parent - horizontalAlignment: Text.AlignHCenter - text: Pomodoro.isStopwatchRunning ? Translation.tr("Lap") : Translation.tr("Reset") - color: Appearance.colors.colSecondary - } - Layout.preferredHeight: 35 - Layout.preferredWidth: 90 - font.pixelSize: Appearance.font.pixelSize.larger - onClicked: Pomodoro.stopwatchResetOrLaps() - colBackground: Appearance.m3colors.m3onError - colBackgroundHover: Appearance.m3colors.m3onError - } - } - } - - // Laps - StyledListView { - id: lapsList - Layout.fillWidth: true - Layout.fillHeight: true - spacing: lapsListItemSpacing - clip: true - popin: true - - model: ScriptModel { - values: Pomodoro.stopwatchLaps - } - - 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: `${Pomodoro.stopwatchLaps.length - lapItem.index}.` - } - - StyledText { - font.pixelSize: Appearance.font.pixelSize.small - text: { - let lapTime = lapItem.modelData - let _10ms = (Math.floor(lapTime) % 100).toString().padStart(2, '0') - let totalSeconds = Math.floor(lapTime) / 100 - let minutes = Math.floor(totalSeconds / 60).toString().padStart(2, '0') - let 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: { - if (lapItem.index != Pomodoro.stopwatchLaps.length - 1) { // except first lap - let lapTime = lapItem.modelData - Pomodoro.stopwatchLaps[lapItem.index + 1] - let _10ms = (Math.floor(lapTime) % 100).toString().padStart(2, '0') - let totalSeconds = Math.floor(lapTime) / 100 - let minutes = Math.floor(totalSeconds / 60).toString().padStart(2, '0') - let seconds = Math.floor(totalSeconds % 60).toString().padStart(2, '0') - return `+${minutes == "00" ? "" : minutes + ":"}${seconds}.${_10ms}` - } else { - return `` // Nothing for first lap - } - } - } - } - } - } - } - } + // 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..90ea1ef78 --- /dev/null +++ b/.config/quickshell/ii/modules/sidebarRight/pomodoro/Stopwatch.qml @@ -0,0 +1,170 @@ +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 + + ColumnLayout { + anchors { + fill: parent + leftMargin: 20 + rightMargin: 20 + } + spacing: 20 + + ColumnLayout { + spacing: 8 + Layout.alignment: Qt.AlignHCenter + Layout.fillWidth: false + + RowLayout { // Elapsed + id: elapsedIndicator + Layout.alignment: Qt.AlignHCenter + spacing: 0 + StyledText { + Layout.preferredWidth: elapsedIndicator.width * 0.6 // Prevent shakiness + font.pixelSize: 40 + color: Appearance.m3colors.m3onSurface + text: { + let totalSeconds = Math.floor(Pomodoro.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(Pomodoro.stopwatchTime) % 100).toString().padStart(2, '0')}` + } + } + } + + // The Start/Stop and Reset buttons + RowLayout { + Layout.alignment: Qt.AlignHCenter + spacing: 4 + + RippleButton { + Layout.preferredHeight: 35 + Layout.preferredWidth: 90 + font.pixelSize: Appearance.font.pixelSize.larger + + onClicked: Pomodoro.toggleStopwatch() + + colBackground: Pomodoro.isStopwatchRunning ? Appearance.colors.colSecondaryContainer : Appearance.colors.colPrimary + colBackgroundHover: Pomodoro.isStopwatchRunning ? Appearance.colors.colSecondaryContainerHover : Appearance.colors.colPrimaryHover + colRipple: Pomodoro.isStopwatchRunning ? Appearance.colors.colSecondaryContainerActive : Appearance.colors.colPrimaryActive + + contentItem: StyledText { + horizontalAlignment: Text.AlignHCenter + color: Pomodoro.isStopwatchRunning ? Appearance.colors.colOnSecondaryContainer : Appearance.colors.colOnPrimary + text: Pomodoro.isStopwatchRunning ? Translation.tr("Pause") : Pomodoro.stopwatchTime === 0 ? Translation.tr("Start") : Translation.tr("Resume") + } + } + + RippleButton { + implicitHeight: 35 + implicitWidth: 90 + font.pixelSize: Appearance.font.pixelSize.larger + + onClicked: Pomodoro.stopwatchResetOrLaps() + enabled: Pomodoro.stopwatchTime !== 0 + + colBackground: Pomodoro.isStopwatchRunning ? Appearance.colors.colLayer2 : Appearance.colors.colErrorContainer + colBackgroundHover: Pomodoro.isStopwatchRunning ? Appearance.colors.colLayer2Hover : Appearance.colors.colErrorContainerHover + colRipple: Pomodoro.isStopwatchRunning ? Appearance.colors.colLayer2Active : Appearance.colors.colErrorContainerActive + + contentItem: StyledText { + horizontalAlignment: Text.AlignHCenter + text: Pomodoro.isStopwatchRunning ? Translation.tr("Lap") : Translation.tr("Reset") + color: Pomodoro.isStopwatchRunning ? Appearance.colors.colOnLayer2 : Appearance.colors.colOnErrorContainer + } + } + } + } + + // Laps + StyledListView { + id: lapsList + Layout.fillWidth: true + Layout.fillHeight: true + spacing: lapsListItemSpacing + clip: true + popin: true + + model: ScriptModel { + values: Pomodoro.stopwatchLaps + } + + 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: `${Pomodoro.stopwatchLaps.length - lapItem.index}.` + } + + StyledText { + font.pixelSize: Appearance.font.pixelSize.small + text: { + let lapTime = lapItem.modelData + let _10ms = (Math.floor(lapTime) % 100).toString().padStart(2, '0') + let totalSeconds = Math.floor(lapTime) / 100 + let minutes = Math.floor(totalSeconds / 60).toString().padStart(2, '0') + let 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: { + let lastTime = (lapItem.index != Pomodoro.stopwatchLaps.length - 1) ? Pomodoro.stopwatchLaps[lapItem.index + 1] : 0 + let lapTime = lapItem.modelData - lastTime + let _10ms = (Math.floor(lapTime) % 100).toString().padStart(2, '0') + let totalSeconds = Math.floor(lapTime) / 100 + let minutes = Math.floor(totalSeconds / 60).toString().padStart(2, '0') + let seconds = Math.floor(totalSeconds % 60).toString().padStart(2, '0') + return `+${minutes == "00" ? "" : minutes + ":"}${seconds}.${_10ms}` + } + } + } + } + } + } +} \ No newline at end of file From 903a9750339d3b7afcc022c9894b490cf2158f97 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sat, 9 Aug 2025 16:31:03 +0700 Subject: [PATCH 48/70] rename getPomodoroSecondsLeft -> pomodoroSecondsLeft --- .../modules/sidebarRight/pomodoro/PomodoroTimer.qml | 12 ++++++------ .config/quickshell/ii/services/Pomodoro.qml | 4 ++-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.config/quickshell/ii/modules/sidebarRight/pomodoro/PomodoroTimer.qml b/.config/quickshell/ii/modules/sidebarRight/pomodoro/PomodoroTimer.qml index 57bb4a9dc..60a0cefbd 100644 --- a/.config/quickshell/ii/modules/sidebarRight/pomodoro/PomodoroTimer.qml +++ b/.config/quickshell/ii/modules/sidebarRight/pomodoro/PomodoroTimer.qml @@ -22,7 +22,7 @@ Item { gapAngle: Math.PI / 14 value: { let pomodoroTotalTime = Pomodoro.isBreak ? Pomodoro.breakTime : Pomodoro.focusTime - return Pomodoro.getPomodoroSecondsLeft / pomodoroTotalTime + return Pomodoro.pomodoroSecondsLeft / pomodoroTotalTime } size: 125 primaryColor: Appearance.m3colors.m3onSecondaryContainer @@ -36,8 +36,8 @@ Item { StyledText { Layout.alignment: Qt.AlignHCenter text: { - let minutes = Math.floor(Pomodoro.getPomodoroSecondsLeft / 60).toString().padStart(2, '0') - let seconds = Math.floor(Pomodoro.getPomodoroSecondsLeft % 60).toString().padStart(2, '0') + let minutes = Math.floor(Pomodoro.pomodoroSecondsLeft / 60).toString().padStart(2, '0') + let seconds = Math.floor(Pomodoro.pomodoroSecondsLeft % 60).toString().padStart(2, '0') return `${minutes}:${seconds}` } font.pixelSize: Appearance.font.pixelSize.hugeass + 4 @@ -61,7 +61,7 @@ Item { contentItem: StyledText { anchors.centerIn: parent horizontalAlignment: Text.AlignHCenter - text: Pomodoro.isPomodoroRunning ? Translation.tr("Pause") : (Pomodoro.getPomodoroSecondsLeft === Pomodoro.focusTime) ? Translation.tr("Start") : Translation.tr("Resume") + text: Pomodoro.isPomodoroRunning ? Translation.tr("Pause") : (Pomodoro.pomodoroSecondsLeft === Pomodoro.focusTime) ? Translation.tr("Start") : Translation.tr("Resume") color: Pomodoro.isPomodoroRunning ? Appearance.colors.colOnSecondaryContainer : Appearance.colors.colOnPrimary } implicitHeight: 35 @@ -77,7 +77,7 @@ Item { implicitWidth: 90 onClicked: Pomodoro.pomodoroReset() - enabled: (Pomodoro.getPomodoroSecondsLeft < Pomodoro.focusTime) + enabled: (Pomodoro.pomodoroSecondsLeft < Pomodoro.focusTime) font.pixelSize: Appearance.font.pixelSize.larger colBackground: Appearance.colors.colErrorContainer @@ -121,7 +121,7 @@ Item { onValueChanged: { Config.options.time.pomodoro.focus = value * 60 if (Pomodoro.isPomodoroReset) { // Special case for Pomodoro in Reset state - Pomodoro.getPomodoroSecondsLeft = Pomodoro.focusTime + Pomodoro.pomodoroSecondsLeft = Pomodoro.focusTime Pomodoro.timeLeft = Pomodoro.focusTime } } diff --git a/.config/quickshell/ii/services/Pomodoro.qml b/.config/quickshell/ii/services/Pomodoro.qml index 073280d65..5a08709b3 100644 --- a/.config/quickshell/ii/services/Pomodoro.qml +++ b/.config/quickshell/ii/services/Pomodoro.qml @@ -24,7 +24,7 @@ Singleton { property bool isBreak: false property bool isPomodoroReset: !isPomodoroRunning property int timeLeft: focusTime - property int getPomodoroSecondsLeft: focusTime + property int pomodoroSecondsLeft: focusTime property int pomodoroStartTime: Persistent.states.timer.pomodoro.start property int pomodoroCycle: 1 @@ -79,7 +79,7 @@ Singleton { } // A nice abstraction for resume logic by updating the TimeStarted - getPomodoroSecondsLeft = (pomodoroStartTime + timeLeft) - getCurrentTimeInSeconds() + pomodoroSecondsLeft = (pomodoroStartTime + timeLeft) - getCurrentTimeInSeconds() } function getCurrentTimeInSeconds() { // Pomodoro uses Seconds From 1f4568d22f2e388c06b2b45756f6e1a60a7a45e3 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sat, 9 Aug 2025 18:20:14 +0700 Subject: [PATCH 49/70] pomodoro: rename more for consistency --- .../ii/modules/common/Persistent.qml | 5 + .../sidebarRight/pomodoro/PomodoroTimer.qml | 161 ++++++++++-------- .../sidebarRight/pomodoro/PomodoroWidget.qml | 2 +- .../sidebarRight/pomodoro/Stopwatch.qml | 29 ++-- .config/quickshell/ii/services/Pomodoro.qml | 44 ++--- 5 files changed, 135 insertions(+), 106 deletions(-) diff --git a/.config/quickshell/ii/modules/common/Persistent.qml b/.config/quickshell/ii/modules/common/Persistent.qml index a650dc0bb..f0cbcafab 100644 --- a/.config/quickshell/ii/modules/common/Persistent.qml +++ b/.config/quickshell/ii/modules/common/Persistent.qml @@ -50,6 +50,11 @@ Singleton { property bool running: false property int start: 0 } + property JsonObject stopwatch: JsonObject { + property bool running: false + property int start: 0 + property list laps: [] + } } } } diff --git a/.config/quickshell/ii/modules/sidebarRight/pomodoro/PomodoroTimer.qml b/.config/quickshell/ii/modules/sidebarRight/pomodoro/PomodoroTimer.qml index 60a0cefbd..1534adb1f 100644 --- a/.config/quickshell/ii/modules/sidebarRight/pomodoro/PomodoroTimer.qml +++ b/.config/quickshell/ii/modules/sidebarRight/pomodoro/PomodoroTimer.qml @@ -76,7 +76,7 @@ Item { implicitHeight: 35 implicitWidth: 90 - onClicked: Pomodoro.pomodoroReset() + onClicked: Pomodoro.resetPomodoro() enabled: (Pomodoro.pomodoroSecondsLeft < Pomodoro.focusTime) font.pixelSize: Appearance.font.pixelSize.larger @@ -95,82 +95,101 @@ Item { } // The SpinBoxes for adjusting duration - GridLayout { - Layout.alignment: Qt.AlignHCenter - columns: 2 - uniformCellWidths: true - columnSpacing: 20 - rowSpacing: 4 + // GridLayout { + // Layout.alignment: Qt.AlignHCenter + // columns: 2 + // uniformCellWidths: true + // columnSpacing: 20 + // rowSpacing: 4 - StyledText { - Layout.alignment: Qt.AlignHCenter - text: Translation.tr("Focus") - } + // StyledText { + // Layout.alignment: Qt.AlignHCenter + // text: Translation.tr("Focus") + // } - StyledText { - Layout.alignment: Qt.AlignHCenter - text: Translation.tr("Break") - } + // StyledText { + // Layout.alignment: Qt.AlignHCenter + // text: Translation.tr("Break") + // } - ConfigSpinBox { - id: focusSpinBox - spacing: 0 - Layout.leftMargin: 0 - Layout.rightMargin: 0 - value: Config.options.time.pomodoro.focus / 60 - onValueChanged: { - Config.options.time.pomodoro.focus = value * 60 - if (Pomodoro.isPomodoroReset) { // Special case for Pomodoro in Reset state - Pomodoro.pomodoroSecondsLeft = Pomodoro.focusTime - Pomodoro.timeLeft = Pomodoro.focusTime - } - } - } + // ConfigSpinBox { + // id: focusSpinBox + // spacing: 0 + // Layout.leftMargin: 0 + // Layout.rightMargin: 0 + // from: 0 + // to: 120 + // Connections { + // target: Config + // function onReadyChanged() { + // focusSpinBox.valueChanged() + // } + // } + // value: { + // console.log("New focus time: " + (Config.options.time.pomodoro.focus / 60)) + // return Config.options.time.pomodoro.focus / 60 + // } + // onValueChanged: { + // console.log("New focus time is " + value + " minutes, Config is ready:", Config.ready) + // if (!Config.ready) return; + // console.log("Setting focus time to " + value + " minutes") + // Config.options.time.pomodoro.focus = value * 60 + // if (Pomodoro.isPomodoroReset) { // Special case for Pomodoro in Reset state + // Pomodoro.pomodoroSecondsLeft = Pomodoro.focusTime + // Pomodoro.timeLeft = Pomodoro.focusTime + // } + // } + // } - ConfigSpinBox { - id: breakSpinBox - spacing: 0 - Layout.leftMargin: 0 - Layout.rightMargin: 0 - value: Config.options.time.pomodoro.breakTime / 60 - onValueChanged: { - Config.options.time.pomodoro.breakTime = value * 60 - } - } + // ConfigSpinBox { + // id: breakSpinBox + // value: Config.options.time.pomodoro.breakTime / 60 + // spacing: 0 + // from: 0 + // to: 120 + // Layout.leftMargin: 0 + // Layout.rightMargin: 0 + // onValueChanged: { + // Config.options.time.pomodoro.breakTime = value * 60 + // } + // } - StyledText { - Layout.topMargin: 6 - Layout.alignment: Qt.AlignHCenter - text: Translation.tr("Cycle") - } - StyledText { - Layout.topMargin: 6 - Layout.alignment: Qt.AlignHCenter - text: Translation.tr("Long break") - } + // StyledText { + // Layout.topMargin: 6 + // Layout.alignment: Qt.AlignHCenter + // text: Translation.tr("Cycle") + // } + // StyledText { + // Layout.topMargin: 6 + // Layout.alignment: Qt.AlignHCenter + // text: Translation.tr("Long break") + // } - ConfigSpinBox { - id: cycleSpinBox - spacing: 0 - from: 1 - Layout.leftMargin: 0 - Layout.rightMargin: 0 - value: Config.options.time.pomodoro.cycle - onValueChanged: { - Config.options.time.pomodoro.cycle = value - } - } + // ConfigSpinBox { + // id: cycleSpinBox + // value: Config.options.time.pomodoro.cycle + // spacing: 0 + // from: 1 + // to: 20 + // Layout.leftMargin: 0 + // Layout.rightMargin: 0 + // onValueChanged: { + // Config.options.time.pomodoro.cycle = value + // } + // } - ConfigSpinBox { - id: longBreakSpinBox - spacing: 0 - Layout.leftMargin: 0 - Layout.rightMargin: 0 - value: Config.options.time.pomodoro.longBreak / 60 - onValueChanged: { - Config.options.time.pomodoro.longBreak = value * 60 - } - } - } + // ConfigSpinBox { + // id: longBreakSpinBox + // spacing: 0 + // Layout.leftMargin: 0 + // Layout.rightMargin: 0 + // value: Config.options.time.pomodoro.longBreak / 60 + // from: 0 + // to: 120 + // onValueChanged: { + // Config.options.time.pomodoro.longBreak = value * 60 + // } + // } + // } } } \ No newline at end of file diff --git a/.config/quickshell/ii/modules/sidebarRight/pomodoro/PomodoroWidget.qml b/.config/quickshell/ii/modules/sidebarRight/pomodoro/PomodoroWidget.qml index 594533f72..242b1ceb8 100644 --- a/.config/quickshell/ii/modules/sidebarRight/pomodoro/PomodoroWidget.qml +++ b/.config/quickshell/ii/modules/sidebarRight/pomodoro/PomodoroWidget.qml @@ -34,7 +34,7 @@ Item { } else if (event.key === Qt.Key_R) { // Reset with R key if (currentTab === 0) { - Pomodoro.pomodoroReset() + Pomodoro.resetPomodoro() } else { Pomodoro.stopwatchReset() } diff --git a/.config/quickshell/ii/modules/sidebarRight/pomodoro/Stopwatch.qml b/.config/quickshell/ii/modules/sidebarRight/pomodoro/Stopwatch.qml index 90ea1ef78..8f5d65295 100644 --- a/.config/quickshell/ii/modules/sidebarRight/pomodoro/Stopwatch.qml +++ b/.config/quickshell/ii/modules/sidebarRight/pomodoro/Stopwatch.qml @@ -31,7 +31,7 @@ Item { Layout.alignment: Qt.AlignHCenter spacing: 0 StyledText { - Layout.preferredWidth: elapsedIndicator.width * 0.6 // Prevent shakiness + // Layout.preferredWidth: elapsedIndicator.width * 0.6 // Prevent shakiness font.pixelSize: 40 color: Appearance.m3colors.m3onSurface text: { @@ -100,12 +100,12 @@ Item { id: lapsList Layout.fillWidth: true Layout.fillHeight: true - spacing: lapsListItemSpacing + spacing: 4 clip: true popin: true model: ScriptModel { - values: Pomodoro.stopwatchLaps + values: Pomodoro.stopwatchLaps.map((v, i, arr) => arr[arr.length - 1 - i]) } delegate: Rectangle { @@ -139,11 +139,11 @@ Item { StyledText { font.pixelSize: Appearance.font.pixelSize.small text: { - let lapTime = lapItem.modelData - let _10ms = (Math.floor(lapTime) % 100).toString().padStart(2, '0') - let totalSeconds = Math.floor(lapTime) / 100 - let minutes = Math.floor(totalSeconds / 60).toString().padStart(2, '0') - let seconds = Math.floor(totalSeconds % 60).toString().padStart(2, '0') + 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}` } } @@ -154,12 +154,13 @@ Item { font.pixelSize: Appearance.font.pixelSize.smaller color: Appearance.colors.colPrimary text: { - let lastTime = (lapItem.index != Pomodoro.stopwatchLaps.length - 1) ? Pomodoro.stopwatchLaps[lapItem.index + 1] : 0 - let lapTime = lapItem.modelData - lastTime - let _10ms = (Math.floor(lapTime) % 100).toString().padStart(2, '0') - let totalSeconds = Math.floor(lapTime) / 100 - let minutes = Math.floor(totalSeconds / 60).toString().padStart(2, '0') - let seconds = Math.floor(totalSeconds % 60).toString().padStart(2, '0') + const originalIndex = Pomodoro.stopwatchLaps.length - lapItem.index - 1 + const lastTime = originalIndex > 0 ? Pomodoro.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}` } } diff --git a/.config/quickshell/ii/services/Pomodoro.qml b/.config/quickshell/ii/services/Pomodoro.qml index 5a08709b3..87fe046be 100644 --- a/.config/quickshell/ii/services/Pomodoro.qml +++ b/.config/quickshell/ii/services/Pomodoro.qml @@ -25,13 +25,17 @@ Singleton { property bool isPomodoroReset: !isPomodoroRunning property int timeLeft: focusTime property int pomodoroSecondsLeft: focusTime - property int pomodoroStartTime: Persistent.states.timer.pomodoro.start + property int pomodoroStart: Persistent.states.timer.pomodoro.start property int pomodoroCycle: 1 - property bool isStopwatchRunning: false + property bool isStopwatchRunning: Persistent.states.timer.stopwatch.running property int stopwatchTime: 0 - property int stopwatchStart: 0 - property var stopwatchLaps: [] + property int stopwatchStart: Persistent.states.timer.stopwatch.start + property var stopwatchLaps: Persistent.states.timer.stopwatch.laps + + Component.onCompleted: { + if (!isStopwatchRunning) stopwatchReset() + } // Start and Stop button function togglePomodoro() { @@ -40,12 +44,12 @@ Singleton { if (isPomodoroRunning) { // Pressed Start button Persistent.states.timer.pomodoro.start = getCurrentTimeInSeconds() } else { // Pressed Stop button - timeLeft -= (getCurrentTimeInSeconds() - pomodoroStartTime) + timeLeft -= (getCurrentTimeInSeconds() - pomodoroStart) } } // Reset button - function pomodoroReset() { + function resetPomodoro() { Persistent.states.timer.pomodoro.running = false isBreak = false isPomodoroReset = true @@ -56,7 +60,7 @@ Singleton { } function refreshPomodoro() { - if (getCurrentTimeInSeconds() >= pomodoroStartTime + timeLeft) { + if (getCurrentTimeInSeconds() >= pomodoroStart + timeLeft) { isBreak = !isBreak Persistent.states.timer.pomodoro.start += timeLeft timeLeft = isBreak ? breakTime : focusTime @@ -74,12 +78,12 @@ Singleton { Quickshell.execDetached(["notify-send", "Pomodoro", notificationMessage, "-a", "Shell"]) if (alertSound) { // Play sound only if alertSound is explicitly specified - Quickshell.execDetached(["bash", "-c", `ffplay -nodisp -autoexit ${alertSound}`]) + Quickshell.execDetached(["ffplay", "-nodisp", "-autoexit", alertSound]) } } // A nice abstraction for resume logic by updating the TimeStarted - pomodoroSecondsLeft = (pomodoroStartTime + timeLeft) - getCurrentTimeInSeconds() + pomodoroSecondsLeft = (pomodoroStart + timeLeft) - getCurrentTimeInSeconds() } function getCurrentTimeInSeconds() { // Pomodoro uses Seconds @@ -96,31 +100,31 @@ Singleton { // Stopwatch functions function toggleStopwatch() { - isStopwatchRunning = !isStopwatchRunning + Persistent.states.timer.stopwatch.running = !isStopwatchRunning if (isStopwatchRunning) { // Resume from paused time by adjusting start time - stopwatchStart = getCurrentTimeIn10ms() - stopwatchTime + Persistent.states.timer.stopwatch.start = getCurrentTimeIn10ms() - stopwatchTime } } function stopwatchResetOrLaps() { - if (isStopwatchRunning) { // Clicked on Lap + if (isStopwatchRunning) { recordLaps() - } else { // Clicked on Reset + } else { stopwatchReset() } } function stopwatchReset() { - isStopwatchRunning = false - stopwatchTime = 0 - stopwatchStart = 0 - stopwatchLaps = [] + Persistent.states.timer.stopwatch.running = false + stopwatchTime = 0 + stopwatchStart = getCurrentTimeIn10ms() + Persistent.states.timer.stopwatch.laps = [] } function recordLaps() { - stopwatchLaps.unshift(stopwatchTime) // Last lap goes first on list - // Reassign to trigger onListChanged, idk copied from Todo.qml - root.stopwatchLaps = stopwatchLaps.slice(0) + Persistent.states.timer.stopwatch.laps.push(stopwatchTime) + // Reassign to trigger change + // Persistent.states.timer.stopwatch.laps = Persistent.states.timer.stopwatch.laps.slice(0) } } From 5bf80dae4ed5844f54d8ecfc175e18c7e9c323c8 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sat, 9 Aug 2025 19:45:26 +0700 Subject: [PATCH 50/70] pomodoro: move timers to service, specific button logic to widget --- .../ii/modules/common/Persistent.qml | 1 + .../sidebarRight/pomodoro/PomodoroWidget.qml | 17 ---- .../sidebarRight/pomodoro/Stopwatch.qml | 11 ++- .config/quickshell/ii/services/Pomodoro.qml | 99 +++++++++++-------- 4 files changed, 67 insertions(+), 61 deletions(-) diff --git a/.config/quickshell/ii/modules/common/Persistent.qml b/.config/quickshell/ii/modules/common/Persistent.qml index f0cbcafab..876b2ef6a 100644 --- a/.config/quickshell/ii/modules/common/Persistent.qml +++ b/.config/quickshell/ii/modules/common/Persistent.qml @@ -49,6 +49,7 @@ Singleton { property JsonObject pomodoro: JsonObject { property bool running: false property int start: 0 + property bool isBreak: false } property JsonObject stopwatch: JsonObject { property bool running: false diff --git a/.config/quickshell/ii/modules/sidebarRight/pomodoro/PomodoroWidget.qml b/.config/quickshell/ii/modules/sidebarRight/pomodoro/PomodoroWidget.qml index 242b1ceb8..64b16a9df 100644 --- a/.config/quickshell/ii/modules/sidebarRight/pomodoro/PomodoroWidget.qml +++ b/.config/quickshell/ii/modules/sidebarRight/pomodoro/PomodoroWidget.qml @@ -45,23 +45,6 @@ Item { } } - Timer { - id: pomodoroTimer - interval: 200 - running: Pomodoro.isPomodoroRunning - repeat: true - onTriggered: Pomodoro.refreshPomodoro() - } - - Timer { - id: stopwatchTimer - interval: 10 - running: Pomodoro.isStopwatchRunning - repeat: true - onTriggered: Pomodoro.refreshStopwatch() - } - - ColumnLayout { anchors.fill: parent spacing: 0 diff --git a/.config/quickshell/ii/modules/sidebarRight/pomodoro/Stopwatch.qml b/.config/quickshell/ii/modules/sidebarRight/pomodoro/Stopwatch.qml index 8f5d65295..f1c428ddf 100644 --- a/.config/quickshell/ii/modules/sidebarRight/pomodoro/Stopwatch.qml +++ b/.config/quickshell/ii/modules/sidebarRight/pomodoro/Stopwatch.qml @@ -61,7 +61,9 @@ Item { Layout.preferredWidth: 90 font.pixelSize: Appearance.font.pixelSize.larger - onClicked: Pomodoro.toggleStopwatch() + onClicked: { + Pomodoro.toggleStopwatch() + } colBackground: Pomodoro.isStopwatchRunning ? Appearance.colors.colSecondaryContainer : Appearance.colors.colPrimary colBackgroundHover: Pomodoro.isStopwatchRunning ? Appearance.colors.colSecondaryContainerHover : Appearance.colors.colPrimaryHover @@ -79,7 +81,12 @@ Item { implicitWidth: 90 font.pixelSize: Appearance.font.pixelSize.larger - onClicked: Pomodoro.stopwatchResetOrLaps() + onClicked: { + if (Pomodoro.isStopwatchRunning) + Pomodoro.stopwatchRecordLap() + else + Pomodoro.stopwatchReset() + } enabled: Pomodoro.stopwatchTime !== 0 colBackground: Pomodoro.isStopwatchRunning ? Appearance.colors.colLayer2 : Appearance.colors.colErrorContainer diff --git a/.config/quickshell/ii/services/Pomodoro.qml b/.config/quickshell/ii/services/Pomodoro.qml index 87fe046be..dd60a9f4a 100644 --- a/.config/quickshell/ii/services/Pomodoro.qml +++ b/.config/quickshell/ii/services/Pomodoro.qml @@ -21,7 +21,7 @@ Singleton { property string alertSound: Config.options.time.pomodoro.alertSound property bool isPomodoroRunning: Persistent.states.timer.pomodoro.running - property bool isBreak: false + property bool isBreak: Persistent.states.timer.pomodoro.isBreak property bool isPomodoroReset: !isPomodoroRunning property int timeLeft: focusTime property int pomodoroSecondsLeft: focusTime @@ -33,35 +33,24 @@ Singleton { property int stopwatchStart: Persistent.states.timer.stopwatch.start property var stopwatchLaps: Persistent.states.timer.stopwatch.laps + // General Component.onCompleted: { if (!isStopwatchRunning) stopwatchReset() } - // Start and Stop button - function togglePomodoro() { - isPomodoroReset = false - Persistent.states.timer.pomodoro.running = !isPomodoroRunning - if (isPomodoroRunning) { // Pressed Start button - Persistent.states.timer.pomodoro.start = getCurrentTimeInSeconds() - } else { // Pressed Stop button - timeLeft -= (getCurrentTimeInSeconds() - pomodoroStart) - } + function getCurrentTimeInSeconds() { // Pomodoro uses Seconds + return Math.floor(Date.now() / 1000) } - // Reset button - function resetPomodoro() { - Persistent.states.timer.pomodoro.running = false - isBreak = false - isPomodoroReset = true - timeLeft = focusTime - Persistent.states.timer.pomodoro.start = getCurrentTimeInSeconds() - pomodoroCycle = 1 - refreshPomodoro() + function getCurrentTimeIn10ms() { // Stopwatch uses 10ms + return Math.floor(Date.now() / 10) } + // Pomodoro function refreshPomodoro() { + // Work <-> break ? if (getCurrentTimeInSeconds() >= pomodoroStart + timeLeft) { - isBreak = !isBreak + Persistent.states.timer.pomodoro.isBreak = !isBreak Persistent.states.timer.pomodoro.start += timeLeft timeLeft = isBreak ? breakTime : focusTime @@ -86,45 +75,71 @@ Singleton { pomodoroSecondsLeft = (pomodoroStart + timeLeft) - getCurrentTimeInSeconds() } - function getCurrentTimeInSeconds() { // Pomodoro uses Seconds - return Math.floor(Date.now() / 1000) + Timer { + id: pomodoroTimer + interval: 200 + running: root.isPomodoroRunning + repeat: true + onTriggered: Pomodoro.refreshPomodoro() } - function getCurrentTimeIn10ms() { // Stopwatch uses 10ms - return Math.floor(Date.now() / 10) + function togglePomodoro() { + isPomodoroReset = false + Persistent.states.timer.pomodoro.running = !isPomodoroRunning + if (isPomodoroRunning) { // Pressed Start button + Persistent.states.timer.pomodoro.start = getCurrentTimeInSeconds() + } else { // Pressed Stop button + timeLeft -= (getCurrentTimeInSeconds() - pomodoroStart) + } } - function refreshStopwatch() { // stopwatch stores time in 10ms + function resetPomodoro() { + Persistent.states.timer.pomodoro.running = false + Persistent.states.timer.pomodoro.isBreak = false + isPomodoroReset = true + timeLeft = focusTime + Persistent.states.timer.pomodoro.start = getCurrentTimeInSeconds() + pomodoroCycle = 1 + refreshPomodoro() + } + + // Stopwatch + function refreshStopwatch() { // Stopwatch stores time in 10ms stopwatchTime = getCurrentTimeIn10ms() - stopwatchStart } - // Stopwatch functions - function toggleStopwatch() { - Persistent.states.timer.stopwatch.running = !isStopwatchRunning - if (isStopwatchRunning) { - // Resume from paused time by adjusting start time - Persistent.states.timer.stopwatch.start = getCurrentTimeIn10ms() - stopwatchTime - } + Timer { + id: stopwatchTimer + interval: 10 + running: root.isStopwatchRunning + repeat: true + onTriggered: root.refreshStopwatch() } - function stopwatchResetOrLaps() { - if (isStopwatchRunning) { - recordLaps() - } else { - stopwatchReset() - } + function toggleStopwatch() { + if (root.isStopwatchRunning) + root.stopwatchPause() + else + root.stopwatchResume() + } + + function stopwatchPause() { + Persistent.states.timer.stopwatch.running = false + } + + function stopwatchResume() { + Persistent.states.timer.stopwatch.running = true + Persistent.states.timer.stopwatch.start = getCurrentTimeIn10ms() - stopwatchTime } function stopwatchReset() { Persistent.states.timer.stopwatch.running = false stopwatchTime = 0 - stopwatchStart = getCurrentTimeIn10ms() + Persistent.states.timer.stopwatch.start = getCurrentTimeIn10ms() Persistent.states.timer.stopwatch.laps = [] } - function recordLaps() { + function stopwatchRecordLap() { Persistent.states.timer.stopwatch.laps.push(stopwatchTime) - // Reassign to trigger change - // Persistent.states.timer.stopwatch.laps = Persistent.states.timer.stopwatch.laps.slice(0) } } From 4ca15a1fc361c9f1b10ed6b22431f16ab0f66a4d Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sat, 9 Aug 2025 21:36:28 +0700 Subject: [PATCH 51/70] pomodoro: make pause/resume less weird --- .../quickshell/ii/modules/common/Config.qml | 4 +- .../ii/modules/common/Persistent.qml | 1 + .../sidebarRight/pomodoro/PomodoroTimer.qml | 237 ++++++------------ .config/quickshell/ii/services/Pomodoro.qml | 13 +- 4 files changed, 80 insertions(+), 175 deletions(-) diff --git a/.config/quickshell/ii/modules/common/Config.qml b/.config/quickshell/ii/modules/common/Config.qml index 9580c3bc0..32cc214dd 100644 --- a/.config/quickshell/ii/modules/common/Config.qml +++ b/.config/quickshell/ii/modules/common/Config.qml @@ -256,9 +256,9 @@ Singleton { property JsonObject pomodoro: JsonObject { property string alertSound: "" property int breakTime: 300 - property int cycle: 4 + property int cyclesBeforeLongBreak: 4 property int focus: 1500 - property int longBreak: 1200 + property int longBreak: 900 } } diff --git a/.config/quickshell/ii/modules/common/Persistent.qml b/.config/quickshell/ii/modules/common/Persistent.qml index 876b2ef6a..a2c8f4391 100644 --- a/.config/quickshell/ii/modules/common/Persistent.qml +++ b/.config/quickshell/ii/modules/common/Persistent.qml @@ -50,6 +50,7 @@ Singleton { property bool running: false property int start: 0 property bool isBreak: false + property int cycle: 0 } property JsonObject stopwatch: JsonObject { property bool running: false diff --git a/.config/quickshell/ii/modules/sidebarRight/pomodoro/PomodoroTimer.qml b/.config/quickshell/ii/modules/sidebarRight/pomodoro/PomodoroTimer.qml index 1534adb1f..fd971b512 100644 --- a/.config/quickshell/ii/modules/sidebarRight/pomodoro/PomodoroTimer.qml +++ b/.config/quickshell/ii/modules/sidebarRight/pomodoro/PomodoroTimer.qml @@ -9,187 +9,92 @@ import QtQuick.Layouts import Quickshell Item { + id: root + + implicitHeight: contentColumn.implicitHeight + implicitWidth: contentColumn.implicitWidth + ColumnLayout { - anchors.horizontalCenter: parent.horizontalCenter - spacing: 20 + id: contentColumn + anchors.fill: parent + spacing: 0 - RowLayout { - spacing: 40 - // The Pomodoro timer circle - CircularProgress { - Layout.alignment: Qt.AlignHCenter - lineWidth: 7 - gapAngle: Math.PI / 14 - value: { - let pomodoroTotalTime = Pomodoro.isBreak ? Pomodoro.breakTime : Pomodoro.focusTime - return Pomodoro.pomodoroSecondsLeft / pomodoroTotalTime - } - size: 125 - primaryColor: Appearance.m3colors.m3onSecondaryContainer - secondaryColor: Appearance.colors.colSecondaryContainer - enableAnimation: true - - ColumnLayout { - anchors.centerIn: parent - spacing: 0 - - StyledText { - Layout.alignment: Qt.AlignHCenter - text: { - let minutes = Math.floor(Pomodoro.pomodoroSecondsLeft / 60).toString().padStart(2, '0') - let seconds = Math.floor(Pomodoro.pomodoroSecondsLeft % 60).toString().padStart(2, '0') - return `${minutes}:${seconds}` - } - font.pixelSize: Appearance.font.pixelSize.hugeass + 4 - color: Appearance.m3colors.m3onSurface - } - StyledText { - Layout.alignment: Qt.AlignHCenter - text: Pomodoro.isBreak ? Translation.tr("Break") : Translation.tr("Focus") - font.pixelSize: Appearance.font.pixelSize.normal - color: Appearance.m3colors.m3onSurface - } - } + // The Pomodoro timer circle + CircularProgress { + Layout.alignment: Qt.AlignHCenter + lineWidth: 8 + gapAngle: Math.PI / 14 + value: { + let pomodoroTotalTime = Pomodoro.isBreak ? Pomodoro.breakTime : Pomodoro.focusTime; + return Pomodoro.pomodoroSecondsLeft / pomodoroTotalTime; } + size: 200 + primaryColor: Appearance.m3colors.m3onSecondaryContainer + secondaryColor: Appearance.colors.colSecondaryContainer + enableAnimation: true - // The Start/Stop and Reset buttons ColumnLayout { - Layout.alignment: Qt.AlignHCenter - spacing: 10 + anchors.centerIn: parent + spacing: 0 - RippleButton { - contentItem: StyledText { - anchors.centerIn: parent - horizontalAlignment: Text.AlignHCenter - text: Pomodoro.isPomodoroRunning ? Translation.tr("Pause") : (Pomodoro.pomodoroSecondsLeft === Pomodoro.focusTime) ? Translation.tr("Start") : Translation.tr("Resume") - color: Pomodoro.isPomodoroRunning ? Appearance.colors.colOnSecondaryContainer : Appearance.colors.colOnPrimary + StyledText { + Layout.alignment: Qt.AlignHCenter + text: { + let minutes = Math.floor(Pomodoro.pomodoroSecondsLeft / 60).toString().padStart(2, '0'); + let seconds = Math.floor(Pomodoro.pomodoroSecondsLeft % 60).toString().padStart(2, '0'); + return `${minutes}:${seconds}`; } - implicitHeight: 35 - implicitWidth: 90 - font.pixelSize: Appearance.font.pixelSize.larger - onClicked: Pomodoro.togglePomodoro() - colBackground: Pomodoro.isPomodoroRunning ? Appearance.colors.colSecondaryContainer : Appearance.colors.colPrimary - colBackgroundHover: Pomodoro.isPomodoroRunning ? Appearance.colors.colSecondaryContainer : Appearance.colors.colPrimary + font.pixelSize: 40 + color: Appearance.m3colors.m3onSurface } - - RippleButton { - implicitHeight: 35 - implicitWidth: 90 - - onClicked: Pomodoro.resetPomodoro() - enabled: (Pomodoro.pomodoroSecondsLeft < Pomodoro.focusTime) - - 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 - } + StyledText { + Layout.alignment: Qt.AlignHCenter + text: Pomodoro.isBreak ? Translation.tr("Break") : Translation.tr("Focus") + font.pixelSize: Appearance.font.pixelSize.normal + color: Appearance.colors.colSubtext } } } - // The SpinBoxes for adjusting duration - // GridLayout { - // Layout.alignment: Qt.AlignHCenter - // columns: 2 - // uniformCellWidths: true - // columnSpacing: 20 - // rowSpacing: 4 + // The Start/Stop and Reset buttons + RowLayout { + Layout.alignment: Qt.AlignHCenter + spacing: 10 - // StyledText { - // Layout.alignment: Qt.AlignHCenter - // text: Translation.tr("Focus") - // } + RippleButton { + contentItem: StyledText { + anchors.centerIn: parent + horizontalAlignment: Text.AlignHCenter + text: Pomodoro.isPomodoroRunning ? Translation.tr("Pause") : (Pomodoro.pomodoroSecondsLeft === Pomodoro.focusTime) ? Translation.tr("Start") : Translation.tr("Resume") + color: Pomodoro.isPomodoroRunning ? Appearance.colors.colOnSecondaryContainer : Appearance.colors.colOnPrimary + } + implicitHeight: 35 + implicitWidth: 90 + font.pixelSize: Appearance.font.pixelSize.larger + onClicked: Pomodoro.togglePomodoro() + colBackground: Pomodoro.isPomodoroRunning ? Appearance.colors.colSecondaryContainer : Appearance.colors.colPrimary + colBackgroundHover: Pomodoro.isPomodoroRunning ? Appearance.colors.colSecondaryContainer : Appearance.colors.colPrimary + } - // StyledText { - // Layout.alignment: Qt.AlignHCenter - // text: Translation.tr("Break") - // } + RippleButton { + implicitHeight: 35 + implicitWidth: 90 - // ConfigSpinBox { - // id: focusSpinBox - // spacing: 0 - // Layout.leftMargin: 0 - // Layout.rightMargin: 0 - // from: 0 - // to: 120 - // Connections { - // target: Config - // function onReadyChanged() { - // focusSpinBox.valueChanged() - // } - // } - // value: { - // console.log("New focus time: " + (Config.options.time.pomodoro.focus / 60)) - // return Config.options.time.pomodoro.focus / 60 - // } - // onValueChanged: { - // console.log("New focus time is " + value + " minutes, Config is ready:", Config.ready) - // if (!Config.ready) return; - // console.log("Setting focus time to " + value + " minutes") - // Config.options.time.pomodoro.focus = value * 60 - // if (Pomodoro.isPomodoroReset) { // Special case for Pomodoro in Reset state - // Pomodoro.pomodoroSecondsLeft = Pomodoro.focusTime - // Pomodoro.timeLeft = Pomodoro.focusTime - // } - // } - // } + onClicked: Pomodoro.resetPomodoro() + enabled: (Pomodoro.pomodoroSecondsLeft < (Pomodoro.isBreak ? Pomodoro.breakTime : Pomodoro.focusTime)) - // ConfigSpinBox { - // id: breakSpinBox - // value: Config.options.time.pomodoro.breakTime / 60 - // spacing: 0 - // from: 0 - // to: 120 - // Layout.leftMargin: 0 - // Layout.rightMargin: 0 - // onValueChanged: { - // Config.options.time.pomodoro.breakTime = value * 60 - // } - // } + font.pixelSize: Appearance.font.pixelSize.larger + colBackground: Appearance.colors.colErrorContainer + colBackgroundHover: Appearance.colors.colErrorContainerHover + colRipple: Appearance.colors.colErrorContainerActive - // StyledText { - // Layout.topMargin: 6 - // Layout.alignment: Qt.AlignHCenter - // text: Translation.tr("Cycle") - // } - // StyledText { - // Layout.topMargin: 6 - // Layout.alignment: Qt.AlignHCenter - // text: Translation.tr("Long break") - // } - - // ConfigSpinBox { - // id: cycleSpinBox - // value: Config.options.time.pomodoro.cycle - // spacing: 0 - // from: 1 - // to: 20 - // Layout.leftMargin: 0 - // Layout.rightMargin: 0 - // onValueChanged: { - // Config.options.time.pomodoro.cycle = value - // } - // } - - // ConfigSpinBox { - // id: longBreakSpinBox - // spacing: 0 - // Layout.leftMargin: 0 - // Layout.rightMargin: 0 - // value: Config.options.time.pomodoro.longBreak / 60 - // from: 0 - // to: 120 - // onValueChanged: { - // Config.options.time.pomodoro.longBreak = value * 60 - // } - // } - // } + contentItem: StyledText { + anchors.centerIn: parent + horizontalAlignment: Text.AlignHCenter + text: Translation.tr("Reset") + color: Appearance.colors.colOnErrorContainer + } + } + } } -} \ No newline at end of file +} diff --git a/.config/quickshell/ii/services/Pomodoro.qml b/.config/quickshell/ii/services/Pomodoro.qml index dd60a9f4a..a537fd957 100644 --- a/.config/quickshell/ii/services/Pomodoro.qml +++ b/.config/quickshell/ii/services/Pomodoro.qml @@ -17,7 +17,7 @@ Singleton { 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 longBreakCycle: Config.options.time.pomodoro.cycle + property int cyclesBeforeLongBreak: Config.options.time.pomodoro.cyclesBeforeLongBreak property string alertSound: Config.options.time.pomodoro.alertSound property bool isPomodoroRunning: Persistent.states.timer.pomodoro.running @@ -56,7 +56,7 @@ Singleton { let notificationTitle, notificationMessage - if (isBreak && pomodoroCycle % longBreakCycle === 0) { // isPomodoroLongBreak + if (isBreak && pomodoroCycle % cyclesBeforeLongBreak === 0) { // isPomodoroLongBreak notificationMessage = Translation.tr(`Relax for %1 minutes`).arg(Math.floor(longBreakTime / 60)) } else if (isBreak) { notificationMessage = Translation.tr(`Relax for %1 minutes`).arg(Math.floor(breakTime / 60)) @@ -86,10 +86,8 @@ Singleton { function togglePomodoro() { isPomodoroReset = false Persistent.states.timer.pomodoro.running = !isPomodoroRunning - if (isPomodoroRunning) { // Pressed Start button - Persistent.states.timer.pomodoro.start = getCurrentTimeInSeconds() - } else { // Pressed Stop button - timeLeft -= (getCurrentTimeInSeconds() - pomodoroStart) + if (Persistent.states.timer.pomodoro.running) { // Start/Resume + Persistent.states.timer.pomodoro.start = getCurrentTimeInSeconds() + pomodoroSecondsLeft - (isBreak ? breakTime : focusTime) } } @@ -99,7 +97,8 @@ Singleton { isPomodoroReset = true timeLeft = focusTime Persistent.states.timer.pomodoro.start = getCurrentTimeInSeconds() - pomodoroCycle = 1 + pomodoroSecondsLeft = 0 + Persistent.states.timer.pomodoro.cycle = 1 refreshPomodoro() } From 9e1b55a749126c45dbdf07e1228b41ffd0a2f54b Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sat, 9 Aug 2025 22:06:05 +0700 Subject: [PATCH 52/70] pomodoro: add cycle indicator, make long break work, fix reset button --- .../ii/modules/common/Persistent.qml | 28 ++++++++++--- .../sidebarRight/pomodoro/PomodoroTimer.qml | 29 ++++++++++---- .config/quickshell/ii/services/Pomodoro.qml | 40 +++++++++---------- 3 files changed, 65 insertions(+), 32 deletions(-) diff --git a/.config/quickshell/ii/modules/common/Persistent.qml b/.config/quickshell/ii/modules/common/Persistent.qml index a2c8f4391..e639f54f1 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(); } } @@ -50,6 +67,7 @@ Singleton { property bool running: false property int start: 0 property bool isBreak: false + property bool isLongBreak: false property int cycle: 0 } property JsonObject stopwatch: JsonObject { diff --git a/.config/quickshell/ii/modules/sidebarRight/pomodoro/PomodoroTimer.qml b/.config/quickshell/ii/modules/sidebarRight/pomodoro/PomodoroTimer.qml index fd971b512..b086a68ab 100644 --- a/.config/quickshell/ii/modules/sidebarRight/pomodoro/PomodoroTimer.qml +++ b/.config/quickshell/ii/modules/sidebarRight/pomodoro/PomodoroTimer.qml @@ -23,14 +23,10 @@ Item { CircularProgress { Layout.alignment: Qt.AlignHCenter lineWidth: 8 - gapAngle: Math.PI / 14 value: { - let pomodoroTotalTime = Pomodoro.isBreak ? Pomodoro.breakTime : Pomodoro.focusTime; - return Pomodoro.pomodoroSecondsLeft / pomodoroTotalTime; + return Pomodoro.pomodoroSecondsLeft / Pomodoro.pomodoroLapDuration; } size: 200 - primaryColor: Appearance.m3colors.m3onSecondaryContainer - secondaryColor: Appearance.colors.colSecondaryContainer enableAnimation: true ColumnLayout { @@ -49,11 +45,30 @@ Item { } StyledText { Layout.alignment: Qt.AlignHCenter - text: Pomodoro.isBreak ? Translation.tr("Break") : Translation.tr("Focus") + text: Pomodoro.isLongBreak ? Translation.tr("Long break") : Pomodoro.isBreak ? 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: Pomodoro.pomodoroCycle + 1 + } + } } // The Start/Stop and Reset buttons @@ -81,7 +96,7 @@ Item { implicitWidth: 90 onClicked: Pomodoro.resetPomodoro() - enabled: (Pomodoro.pomodoroSecondsLeft < (Pomodoro.isBreak ? Pomodoro.breakTime : Pomodoro.focusTime)) + enabled: (Pomodoro.pomodoroSecondsLeft < Pomodoro.pomodoroLapDuration) || Pomodoro.pomodoroCycle > 0 || Pomodoro.isBreak font.pixelSize: Appearance.font.pixelSize.larger colBackground: Appearance.colors.colErrorContainer diff --git a/.config/quickshell/ii/services/Pomodoro.qml b/.config/quickshell/ii/services/Pomodoro.qml index a537fd957..8bb198076 100644 --- a/.config/quickshell/ii/services/Pomodoro.qml +++ b/.config/quickshell/ii/services/Pomodoro.qml @@ -22,11 +22,11 @@ Singleton { property bool isPomodoroRunning: Persistent.states.timer.pomodoro.running property bool isBreak: Persistent.states.timer.pomodoro.isBreak - property bool isPomodoroReset: !isPomodoroRunning - property int timeLeft: focusTime + property bool isLongBreak: Persistent.states.timer.pomodoro.isLongBreak + property bool isPomodoroLongBreak: Persistent.states.timer.pomodoro.isLongBreak + property int pomodoroLapDuration: isBreak ? (isLongBreak ? longBreakTime : breakTime) : focusTime property int pomodoroSecondsLeft: focusTime - property int pomodoroStart: Persistent.states.timer.pomodoro.start - property int pomodoroCycle: 1 + property int pomodoroCycle: Persistent.states.timer.pomodoro.cycle property bool isStopwatchRunning: Persistent.states.timer.stopwatch.running property int stopwatchTime: 0 @@ -49,30 +49,34 @@ Singleton { // Pomodoro function refreshPomodoro() { // Work <-> break ? - if (getCurrentTimeInSeconds() >= pomodoroStart + timeLeft) { - Persistent.states.timer.pomodoro.isBreak = !isBreak - Persistent.states.timer.pomodoro.start += timeLeft - timeLeft = isBreak ? breakTime : focusTime + if (getCurrentTimeInSeconds() >= Persistent.states.timer.pomodoro.start + pomodoroLapDuration) { + // Reset counts + const currentTimeInSeconds = getCurrentTimeInSeconds() + Persistent.states.timer.pomodoro.isBreak = !Persistent.states.timer.pomodoro.isBreak + Persistent.states.timer.pomodoro.isLongBreak = Persistent.states.timer.pomodoro.isBreak && (pomodoroCycle + 1 == cyclesBeforeLongBreak) + Persistent.states.timer.pomodoro.start = currentTimeInSeconds + // Send notification let notificationTitle, notificationMessage - - if (isBreak && pomodoroCycle % cyclesBeforeLongBreak === 0) { // isPomodoroLongBreak + if (Persistent.states.timer.pomodoro.isBreak && pomodoroCycle % cyclesBeforeLongBreak === 0) { // isPomodoroLongBreak notificationMessage = Translation.tr(`Relax for %1 minutes`).arg(Math.floor(longBreakTime / 60)) - } else if (isBreak) { + } else if (Persistent.states.timer.pomodoro.isBreak) { notificationMessage = Translation.tr(`Relax for %1 minutes`).arg(Math.floor(breakTime / 60)) } else { notificationMessage = Translation.tr(`Focus for %1 minutes`).arg(Math.floor(focusTime / 60)) - pomodoroCycle += 1 } Quickshell.execDetached(["notify-send", "Pomodoro", notificationMessage, "-a", "Shell"]) if (alertSound) { // Play sound only if alertSound is explicitly specified Quickshell.execDetached(["ffplay", "-nodisp", "-autoexit", alertSound]) } + + if (!isBreak) { + Persistent.states.timer.pomodoro.cycle = (Persistent.states.timer.pomodoro.cycle + 1) % root.cyclesBeforeLongBreak; + } } - // A nice abstraction for resume logic by updating the TimeStarted - pomodoroSecondsLeft = (pomodoroStart + timeLeft) - getCurrentTimeInSeconds() + pomodoroSecondsLeft = pomodoroLapDuration - (getCurrentTimeInSeconds() - Persistent.states.timer.pomodoro.start) } Timer { @@ -84,21 +88,17 @@ Singleton { } function togglePomodoro() { - isPomodoroReset = false Persistent.states.timer.pomodoro.running = !isPomodoroRunning if (Persistent.states.timer.pomodoro.running) { // Start/Resume - Persistent.states.timer.pomodoro.start = getCurrentTimeInSeconds() + pomodoroSecondsLeft - (isBreak ? breakTime : focusTime) + Persistent.states.timer.pomodoro.start = getCurrentTimeInSeconds() + pomodoroSecondsLeft - pomodoroLapDuration } } function resetPomodoro() { Persistent.states.timer.pomodoro.running = false Persistent.states.timer.pomodoro.isBreak = false - isPomodoroReset = true - timeLeft = focusTime Persistent.states.timer.pomodoro.start = getCurrentTimeInSeconds() - pomodoroSecondsLeft = 0 - Persistent.states.timer.pomodoro.cycle = 1 + Persistent.states.timer.pomodoro.cycle = 0 refreshPomodoro() } From 90fafa219a54f5ee2e81d2fbb8d7827cb93235c6 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sat, 9 Aug 2025 22:09:07 +0700 Subject: [PATCH 53/70] remove unnecessary qualified access --- .config/quickshell/ii/services/Pomodoro.qml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.config/quickshell/ii/services/Pomodoro.qml b/.config/quickshell/ii/services/Pomodoro.qml index 8bb198076..8376499d9 100644 --- a/.config/quickshell/ii/services/Pomodoro.qml +++ b/.config/quickshell/ii/services/Pomodoro.qml @@ -84,7 +84,7 @@ Singleton { interval: 200 running: root.isPomodoroRunning repeat: true - onTriggered: Pomodoro.refreshPomodoro() + onTriggered: refreshPomodoro() } function togglePomodoro() { @@ -112,14 +112,14 @@ Singleton { interval: 10 running: root.isStopwatchRunning repeat: true - onTriggered: root.refreshStopwatch() + onTriggered: refreshStopwatch() } function toggleStopwatch() { if (root.isStopwatchRunning) - root.stopwatchPause() + stopwatchPause() else - root.stopwatchResume() + stopwatchResume() } function stopwatchPause() { From df9a5e398e9ad838a71509e0ca299d9a7e3202cc Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sat, 9 Aug 2025 22:11:06 +0700 Subject: [PATCH 54/70] pomodoro: remove unused notificationTitle var, format --- .config/quickshell/ii/services/Pomodoro.qml | 73 +++++++++++---------- 1 file changed, 37 insertions(+), 36 deletions(-) diff --git a/.config/quickshell/ii/services/Pomodoro.qml b/.config/quickshell/ii/services/Pomodoro.qml index 8376499d9..297a1c204 100644 --- a/.config/quickshell/ii/services/Pomodoro.qml +++ b/.config/quickshell/ii/services/Pomodoro.qml @@ -35,15 +35,16 @@ Singleton { // General Component.onCompleted: { - if (!isStopwatchRunning) stopwatchReset() + if (!isStopwatchRunning) + stopwatchReset(); } function getCurrentTimeInSeconds() { // Pomodoro uses Seconds - return Math.floor(Date.now() / 1000) + return Math.floor(Date.now() / 1000); } function getCurrentTimeIn10ms() { // Stopwatch uses 10ms - return Math.floor(Date.now() / 10) + return Math.floor(Date.now() / 10); } // Pomodoro @@ -51,32 +52,31 @@ Singleton { // Work <-> break ? if (getCurrentTimeInSeconds() >= Persistent.states.timer.pomodoro.start + pomodoroLapDuration) { // Reset counts - const currentTimeInSeconds = getCurrentTimeInSeconds() - Persistent.states.timer.pomodoro.isBreak = !Persistent.states.timer.pomodoro.isBreak - Persistent.states.timer.pomodoro.isLongBreak = Persistent.states.timer.pomodoro.isBreak && (pomodoroCycle + 1 == cyclesBeforeLongBreak) - Persistent.states.timer.pomodoro.start = currentTimeInSeconds + const currentTimeInSeconds = getCurrentTimeInSeconds(); + Persistent.states.timer.pomodoro.isBreak = !Persistent.states.timer.pomodoro.isBreak; + Persistent.states.timer.pomodoro.isLongBreak = Persistent.states.timer.pomodoro.isBreak && (pomodoroCycle + 1 == cyclesBeforeLongBreak); + Persistent.states.timer.pomodoro.start = currentTimeInSeconds; // Send notification - let notificationTitle, notificationMessage - if (Persistent.states.timer.pomodoro.isBreak && pomodoroCycle % cyclesBeforeLongBreak === 0) { // isPomodoroLongBreak - notificationMessage = Translation.tr(`Relax for %1 minutes`).arg(Math.floor(longBreakTime / 60)) + let notificationMessage; + if (Persistent.states.timer.pomodoro.isLongBreak) { + notificationMessage = Translation.tr(`Relax for %1 minutes`).arg(Math.floor(longBreakTime / 60)); } else if (Persistent.states.timer.pomodoro.isBreak) { - notificationMessage = Translation.tr(`Relax for %1 minutes`).arg(Math.floor(breakTime / 60)) + notificationMessage = Translation.tr(`Relax for %1 minutes`).arg(Math.floor(breakTime / 60)); } else { - notificationMessage = Translation.tr(`Focus for %1 minutes`).arg(Math.floor(focusTime / 60)) + notificationMessage = Translation.tr(`Focus for %1 minutes`).arg(Math.floor(focusTime / 60)); } - Quickshell.execDetached(["notify-send", "Pomodoro", notificationMessage, "-a", "Shell"]) - if (alertSound) { // Play sound only if alertSound is explicitly specified - Quickshell.execDetached(["ffplay", "-nodisp", "-autoexit", alertSound]) - } + Quickshell.execDetached(["notify-send", "Pomodoro", notificationMessage, "-a", "Shell"]); + if (alertSound) + Quickshell.execDetached(["ffplay", "-nodisp", "-autoexit", alertSound]); if (!isBreak) { Persistent.states.timer.pomodoro.cycle = (Persistent.states.timer.pomodoro.cycle + 1) % root.cyclesBeforeLongBreak; } } - pomodoroSecondsLeft = pomodoroLapDuration - (getCurrentTimeInSeconds() - Persistent.states.timer.pomodoro.start) + pomodoroSecondsLeft = pomodoroLapDuration - (getCurrentTimeInSeconds() - Persistent.states.timer.pomodoro.start); } Timer { @@ -88,23 +88,24 @@ Singleton { } function togglePomodoro() { - Persistent.states.timer.pomodoro.running = !isPomodoroRunning - if (Persistent.states.timer.pomodoro.running) { // Start/Resume - Persistent.states.timer.pomodoro.start = getCurrentTimeInSeconds() + pomodoroSecondsLeft - pomodoroLapDuration + Persistent.states.timer.pomodoro.running = !isPomodoroRunning; + 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() + 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 + stopwatchTime = getCurrentTimeIn10ms() - stopwatchStart; } Timer { @@ -117,28 +118,28 @@ Singleton { function toggleStopwatch() { if (root.isStopwatchRunning) - stopwatchPause() + stopwatchPause(); else - stopwatchResume() + stopwatchResume(); } function stopwatchPause() { - Persistent.states.timer.stopwatch.running = false + Persistent.states.timer.stopwatch.running = false; } function stopwatchResume() { - Persistent.states.timer.stopwatch.running = true - Persistent.states.timer.stopwatch.start = getCurrentTimeIn10ms() - stopwatchTime + Persistent.states.timer.stopwatch.running = true; + Persistent.states.timer.stopwatch.start = getCurrentTimeIn10ms() - stopwatchTime; } function stopwatchReset() { - Persistent.states.timer.stopwatch.running = false - stopwatchTime = 0 - Persistent.states.timer.stopwatch.start = getCurrentTimeIn10ms() - Persistent.states.timer.stopwatch.laps = [] + Persistent.states.timer.stopwatch.running = false; + stopwatchTime = 0; + Persistent.states.timer.stopwatch.start = getCurrentTimeIn10ms(); + Persistent.states.timer.stopwatch.laps = []; } function stopwatchRecordLap() { - Persistent.states.timer.stopwatch.laps.push(stopwatchTime) + Persistent.states.timer.stopwatch.laps.push(stopwatchTime); } } From b102e5c1a5c840dc4dc860b9ea297dcbf4f7e469 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sat, 9 Aug 2025 22:15:07 +0700 Subject: [PATCH 55/70] pomodoro: remove unnecessary event propagation prevention --- .../sidebarRight/pomodoro/PomodoroWidget.qml | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/.config/quickshell/ii/modules/sidebarRight/pomodoro/PomodoroWidget.qml b/.config/quickshell/ii/modules/sidebarRight/pomodoro/PomodoroWidget.qml index 64b16a9df..b5ee7a597 100644 --- a/.config/quickshell/ii/modules/sidebarRight/pomodoro/PomodoroWidget.qml +++ b/.config/quickshell/ii/modules/sidebarRight/pomodoro/PomodoroWidget.qml @@ -16,32 +16,26 @@ Item { // 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) { + 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) { - // Toggle start/stop with Space or S key + } else if (event.key === Qt.Key_Space || event.key === Qt.Key_S) { // Pause/resume with Space or S if (currentTab === 0) { Pomodoro.togglePomodoro() } else { Pomodoro.toggleStopwatch() } - event.accepted = true - } else if (event.key === Qt.Key_R) { - // Reset with R key + } else if (event.key === Qt.Key_R) { // Reset with R if (currentTab === 0) { Pomodoro.resetPomodoro() } else { Pomodoro.stopwatchReset() } - event.accepted = true - } else if (event.key === Qt.Key_L) { - // record Stopwatch lap with L key, regardless of current Tab - Pomodoro.recordLaps() + } else if (event.key === Qt.Key_L) { // Record lap with L + Pomodoro.stopwatchRecordLap() } } From fbe17dc3e3119d5ff15dd8418e1f9fb7c9461d15 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sat, 9 Aug 2025 22:19:45 +0700 Subject: [PATCH 56/70] pomodoro: accept event on keybind cuz recommended by qt docs --- .../ii/modules/sidebarRight/pomodoro/PomodoroWidget.qml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.config/quickshell/ii/modules/sidebarRight/pomodoro/PomodoroWidget.qml b/.config/quickshell/ii/modules/sidebarRight/pomodoro/PomodoroWidget.qml index b5ee7a597..3aaa868a4 100644 --- a/.config/quickshell/ii/modules/sidebarRight/pomodoro/PomodoroWidget.qml +++ b/.config/quickshell/ii/modules/sidebarRight/pomodoro/PomodoroWidget.qml @@ -22,20 +22,24 @@ Item { } 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) { Pomodoro.togglePomodoro() } else { Pomodoro.toggleStopwatch() } + event.accepted = true } else if (event.key === Qt.Key_R) { // Reset with R if (currentTab === 0) { Pomodoro.resetPomodoro() } else { Pomodoro.stopwatchReset() } + event.accepted = true } else if (event.key === Qt.Key_L) { // Record lap with L Pomodoro.stopwatchRecordLap() + event.accepted = true } } From bcd1167d3989bbbdbf4af1eeec2c4e8b1cb70f17 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sat, 9 Aug 2025 22:48:40 +0700 Subject: [PATCH 57/70] pomodoro: rename service to TimerService, better stopwatch layout --- .../sidebarRight/pomodoro/PomodoroTimer.qml | 24 +-- .../sidebarRight/pomodoro/PomodoroWidget.qml | 10 +- .../sidebarRight/pomodoro/Stopwatch.qml | 192 ++++++++++-------- .../{Pomodoro.qml => TimerService.qml} | 0 4 files changed, 128 insertions(+), 98 deletions(-) rename .config/quickshell/ii/services/{Pomodoro.qml => TimerService.qml} (100%) diff --git a/.config/quickshell/ii/modules/sidebarRight/pomodoro/PomodoroTimer.qml b/.config/quickshell/ii/modules/sidebarRight/pomodoro/PomodoroTimer.qml index b086a68ab..e50f92d83 100644 --- a/.config/quickshell/ii/modules/sidebarRight/pomodoro/PomodoroTimer.qml +++ b/.config/quickshell/ii/modules/sidebarRight/pomodoro/PomodoroTimer.qml @@ -24,7 +24,7 @@ Item { Layout.alignment: Qt.AlignHCenter lineWidth: 8 value: { - return Pomodoro.pomodoroSecondsLeft / Pomodoro.pomodoroLapDuration; + return TimerService.pomodoroSecondsLeft / TimerService.pomodoroLapDuration; } size: 200 enableAnimation: true @@ -36,8 +36,8 @@ Item { StyledText { Layout.alignment: Qt.AlignHCenter text: { - let minutes = Math.floor(Pomodoro.pomodoroSecondsLeft / 60).toString().padStart(2, '0'); - let seconds = Math.floor(Pomodoro.pomodoroSecondsLeft % 60).toString().padStart(2, '0'); + 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 @@ -45,7 +45,7 @@ Item { } StyledText { Layout.alignment: Qt.AlignHCenter - text: Pomodoro.isLongBreak ? Translation.tr("Long break") : Pomodoro.isBreak ? Translation.tr("Break") : Translation.tr("Focus") + text: TimerService.isLongBreak ? Translation.tr("Long break") : TimerService.isBreak ? Translation.tr("Break") : Translation.tr("Focus") font.pixelSize: Appearance.font.pixelSize.normal color: Appearance.colors.colSubtext } @@ -66,7 +66,7 @@ Item { id: cycleText anchors.centerIn: parent color: Appearance.colors.colOnLayer2 - text: Pomodoro.pomodoroCycle + 1 + text: TimerService.pomodoroCycle + 1 } } } @@ -80,23 +80,23 @@ Item { contentItem: StyledText { anchors.centerIn: parent horizontalAlignment: Text.AlignHCenter - text: Pomodoro.isPomodoroRunning ? Translation.tr("Pause") : (Pomodoro.pomodoroSecondsLeft === Pomodoro.focusTime) ? Translation.tr("Start") : Translation.tr("Resume") - color: Pomodoro.isPomodoroRunning ? Appearance.colors.colOnSecondaryContainer : Appearance.colors.colOnPrimary + text: TimerService.isPomodoroRunning ? Translation.tr("Pause") : (TimerService.pomodoroSecondsLeft === TimerService.focusTime) ? Translation.tr("Start") : Translation.tr("Resume") + color: TimerService.isPomodoroRunning ? Appearance.colors.colOnSecondaryContainer : Appearance.colors.colOnPrimary } implicitHeight: 35 implicitWidth: 90 font.pixelSize: Appearance.font.pixelSize.larger - onClicked: Pomodoro.togglePomodoro() - colBackground: Pomodoro.isPomodoroRunning ? Appearance.colors.colSecondaryContainer : Appearance.colors.colPrimary - colBackgroundHover: Pomodoro.isPomodoroRunning ? Appearance.colors.colSecondaryContainer : Appearance.colors.colPrimary + onClicked: TimerService.togglePomodoro() + colBackground: TimerService.isPomodoroRunning ? Appearance.colors.colSecondaryContainer : Appearance.colors.colPrimary + colBackgroundHover: TimerService.isPomodoroRunning ? Appearance.colors.colSecondaryContainer : Appearance.colors.colPrimary } RippleButton { implicitHeight: 35 implicitWidth: 90 - onClicked: Pomodoro.resetPomodoro() - enabled: (Pomodoro.pomodoroSecondsLeft < Pomodoro.pomodoroLapDuration) || Pomodoro.pomodoroCycle > 0 || Pomodoro.isBreak + onClicked: TimerService.resetPomodoro() + enabled: (TimerService.pomodoroSecondsLeft < TimerService.pomodoroLapDuration) || TimerService.pomodoroCycle > 0 || TimerService.isBreak font.pixelSize: Appearance.font.pixelSize.larger colBackground: Appearance.colors.colErrorContainer diff --git a/.config/quickshell/ii/modules/sidebarRight/pomodoro/PomodoroWidget.qml b/.config/quickshell/ii/modules/sidebarRight/pomodoro/PomodoroWidget.qml index 3aaa868a4..729d45809 100644 --- a/.config/quickshell/ii/modules/sidebarRight/pomodoro/PomodoroWidget.qml +++ b/.config/quickshell/ii/modules/sidebarRight/pomodoro/PomodoroWidget.qml @@ -25,20 +25,20 @@ Item { event.accepted = true } else if (event.key === Qt.Key_Space || event.key === Qt.Key_S) { // Pause/resume with Space or S if (currentTab === 0) { - Pomodoro.togglePomodoro() + TimerService.togglePomodoro() } else { - Pomodoro.toggleStopwatch() + TimerService.toggleStopwatch() } event.accepted = true } else if (event.key === Qt.Key_R) { // Reset with R if (currentTab === 0) { - Pomodoro.resetPomodoro() + TimerService.resetPomodoro() } else { - Pomodoro.stopwatchReset() + TimerService.stopwatchReset() } event.accepted = true } else if (event.key === Qt.Key_L) { // Record lap with L - Pomodoro.stopwatchRecordLap() + TimerService.stopwatchRecordLap() event.accepted = true } } diff --git a/.config/quickshell/ii/modules/sidebarRight/pomodoro/Stopwatch.qml b/.config/quickshell/ii/modules/sidebarRight/pomodoro/Stopwatch.qml index f1c428ddf..cbf588194 100644 --- a/.config/quickshell/ii/modules/sidebarRight/pomodoro/Stopwatch.qml +++ b/.config/quickshell/ii/modules/sidebarRight/pomodoro/Stopwatch.qml @@ -13,91 +13,61 @@ Item { Layout.fillWidth: true Layout.fillHeight: true - ColumnLayout { + Item { anchors { fill: parent - leftMargin: 20 - rightMargin: 20 + topMargin: 8 + leftMargin: 16 + rightMargin: 16 } - spacing: 20 - ColumnLayout { - spacing: 8 - Layout.alignment: Qt.AlignHCenter - Layout.fillWidth: false + RowLayout { // Elapsed + id: elapsedIndicator + + anchors { + top: undefined + verticalCenter: parent.verticalCenter + left: controlButtons.left + leftMargin: 6 + } - RowLayout { // Elapsed - id: elapsedIndicator - Layout.alignment: Qt.AlignHCenter - spacing: 0 - StyledText { - // Layout.preferredWidth: elapsedIndicator.width * 0.6 // Prevent shakiness - font.pixelSize: 40 - color: Appearance.m3colors.m3onSurface - text: { - let totalSeconds = Math.floor(Pomodoro.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(Pomodoro.stopwatchTime) % 100).toString().padStart(2, '0')}` - } + states: State { + name: "hasLaps" + when: TimerService.stopwatchLaps.length > 0 + AnchorChanges { + target: elapsedIndicator + anchors.top: parent.top + anchors.verticalCenter: undefined + anchors.left: controlButtons.left } } - // The Start/Stop and Reset buttons - RowLayout { - Layout.alignment: Qt.AlignHCenter - spacing: 4 - - RippleButton { - Layout.preferredHeight: 35 - Layout.preferredWidth: 90 - font.pixelSize: Appearance.font.pixelSize.larger - - onClicked: { - Pomodoro.toggleStopwatch() - } - - colBackground: Pomodoro.isStopwatchRunning ? Appearance.colors.colSecondaryContainer : Appearance.colors.colPrimary - colBackgroundHover: Pomodoro.isStopwatchRunning ? Appearance.colors.colSecondaryContainerHover : Appearance.colors.colPrimaryHover - colRipple: Pomodoro.isStopwatchRunning ? Appearance.colors.colSecondaryContainerActive : Appearance.colors.colPrimaryActive - - contentItem: StyledText { - horizontalAlignment: Text.AlignHCenter - color: Pomodoro.isStopwatchRunning ? Appearance.colors.colOnSecondaryContainer : Appearance.colors.colOnPrimary - text: Pomodoro.isStopwatchRunning ? Translation.tr("Pause") : Pomodoro.stopwatchTime === 0 ? Translation.tr("Start") : Translation.tr("Resume") - } + transitions: Transition { + AnchorAnimation { + duration: Appearance.animation.elementMoveFast.duration + easing.type: Appearance.animation.elementMoveFast.type + easing.bezierCurve: Appearance.animation.elementMoveFast.bezierCurve } + } - RippleButton { - implicitHeight: 35 - implicitWidth: 90 - font.pixelSize: Appearance.font.pixelSize.larger - - onClicked: { - if (Pomodoro.isStopwatchRunning) - Pomodoro.stopwatchRecordLap() - else - Pomodoro.stopwatchReset() - } - enabled: Pomodoro.stopwatchTime !== 0 - - colBackground: Pomodoro.isStopwatchRunning ? Appearance.colors.colLayer2 : Appearance.colors.colErrorContainer - colBackgroundHover: Pomodoro.isStopwatchRunning ? Appearance.colors.colLayer2Hover : Appearance.colors.colErrorContainerHover - colRipple: Pomodoro.isStopwatchRunning ? Appearance.colors.colLayer2Active : Appearance.colors.colErrorContainerActive - - contentItem: StyledText { - horizontalAlignment: Text.AlignHCenter - text: Pomodoro.isStopwatchRunning ? Translation.tr("Lap") : Translation.tr("Reset") - color: Pomodoro.isStopwatchRunning ? Appearance.colors.colOnLayer2 : Appearance.colors.colOnErrorContainer - } + 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')}` } } } @@ -105,14 +75,20 @@ Item { // Laps StyledListView { id: lapsList - Layout.fillWidth: true - Layout.fillHeight: true + 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: Pomodoro.stopwatchLaps.map((v, i, arr) => arr[arr.length - 1 - i]) + values: TimerService.stopwatchLaps.map((v, i, arr) => arr[arr.length - 1 - i]) } delegate: Rectangle { @@ -140,7 +116,7 @@ Item { StyledText { font.pixelSize: Appearance.font.pixelSize.small color: Appearance.colors.colSubtext - text: `${Pomodoro.stopwatchLaps.length - lapItem.index}.` + text: `${TimerService.stopwatchLaps.length - lapItem.index}.` } StyledText { @@ -161,8 +137,8 @@ Item { font.pixelSize: Appearance.font.pixelSize.smaller color: Appearance.colors.colPrimary text: { - const originalIndex = Pomodoro.stopwatchLaps.length - lapItem.index - 1 - const lastTime = originalIndex > 0 ? Pomodoro.stopwatchLaps[originalIndex - 1] : 0 + 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 @@ -174,5 +150,59 @@ Item { } } } + + 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.isStopwatchRunning ? Appearance.colors.colSecondaryContainer : Appearance.colors.colPrimary + colBackgroundHover: TimerService.isStopwatchRunning ? Appearance.colors.colSecondaryContainerHover : Appearance.colors.colPrimaryHover + colRipple: TimerService.isStopwatchRunning ? Appearance.colors.colSecondaryContainerActive : Appearance.colors.colPrimaryActive + + contentItem: StyledText { + horizontalAlignment: Text.AlignHCenter + color: TimerService.isStopwatchRunning ? Appearance.colors.colOnSecondaryContainer : Appearance.colors.colOnPrimary + text: TimerService.isStopwatchRunning ? 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.isStopwatchRunning) + TimerService.stopwatchRecordLap() + else + TimerService.stopwatchReset() + } + enabled: TimerService.stopwatchTime !== 0 + + colBackground: TimerService.isStopwatchRunning ? Appearance.colors.colLayer2 : Appearance.colors.colErrorContainer + colBackgroundHover: TimerService.isStopwatchRunning ? Appearance.colors.colLayer2Hover : Appearance.colors.colErrorContainerHover + colRipple: TimerService.isStopwatchRunning ? Appearance.colors.colLayer2Active : Appearance.colors.colErrorContainerActive + + contentItem: StyledText { + horizontalAlignment: Text.AlignHCenter + text: TimerService.isStopwatchRunning ? Translation.tr("Lap") : Translation.tr("Reset") + color: TimerService.isStopwatchRunning ? Appearance.colors.colOnLayer2 : Appearance.colors.colOnErrorContainer + } + } + } } } \ No newline at end of file diff --git a/.config/quickshell/ii/services/Pomodoro.qml b/.config/quickshell/ii/services/TimerService.qml similarity index 100% rename from .config/quickshell/ii/services/Pomodoro.qml rename to .config/quickshell/ii/services/TimerService.qml From 0e17b7a981d2e6450434db5d0aaebd43c379f58a Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sat, 9 Aug 2025 22:55:40 +0700 Subject: [PATCH 58/70] TimerService: add back emojis, make reset also reset isLongBreak --- .config/quickshell/ii/services/TimerService.qml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.config/quickshell/ii/services/TimerService.qml b/.config/quickshell/ii/services/TimerService.qml index 297a1c204..651af1b05 100644 --- a/.config/quickshell/ii/services/TimerService.qml +++ b/.config/quickshell/ii/services/TimerService.qml @@ -60,11 +60,11 @@ Singleton { // Send notification let notificationMessage; if (Persistent.states.timer.pomodoro.isLongBreak) { - notificationMessage = Translation.tr(`Relax for %1 minutes`).arg(Math.floor(longBreakTime / 60)); + notificationMessage = Translation.tr(`🌿 Long break: %1 minutes`).arg(Math.floor(longBreakTime / 60)); } else if (Persistent.states.timer.pomodoro.isBreak) { - notificationMessage = Translation.tr(`Relax for %1 minutes`).arg(Math.floor(breakTime / 60)); + notificationMessage = Translation.tr(`☕ Break: %1 minutes`).arg(Math.floor(breakTime / 60)); } else { - notificationMessage = Translation.tr(`Focus for %1 minutes`).arg(Math.floor(focusTime / 60)); + notificationMessage = Translation.tr(`🔴 Focus: %1 minutes`).arg(Math.floor(focusTime / 60)); } Quickshell.execDetached(["notify-send", "Pomodoro", notificationMessage, "-a", "Shell"]); @@ -98,6 +98,7 @@ Singleton { function resetPomodoro() { Persistent.states.timer.pomodoro.running = false; Persistent.states.timer.pomodoro.isBreak = false; + Persistent.states.timer.pomodoro.isLongBreak = false; Persistent.states.timer.pomodoro.start = getCurrentTimeInSeconds(); Persistent.states.timer.pomodoro.cycle = 0; refreshPomodoro(); From ca6463cae8f828a032a4136c489f8347196f256f Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sat, 9 Aug 2025 22:58:55 +0700 Subject: [PATCH 59/70] TimerService: not have stupid extra declaration --- .config/quickshell/ii/services/TimerService.qml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.config/quickshell/ii/services/TimerService.qml b/.config/quickshell/ii/services/TimerService.qml index 651af1b05..f4f9bde95 100644 --- a/.config/quickshell/ii/services/TimerService.qml +++ b/.config/quickshell/ii/services/TimerService.qml @@ -52,10 +52,9 @@ Singleton { // Work <-> break ? if (getCurrentTimeInSeconds() >= Persistent.states.timer.pomodoro.start + pomodoroLapDuration) { // Reset counts - const currentTimeInSeconds = getCurrentTimeInSeconds(); Persistent.states.timer.pomodoro.isBreak = !Persistent.states.timer.pomodoro.isBreak; Persistent.states.timer.pomodoro.isLongBreak = Persistent.states.timer.pomodoro.isBreak && (pomodoroCycle + 1 == cyclesBeforeLongBreak); - Persistent.states.timer.pomodoro.start = currentTimeInSeconds; + Persistent.states.timer.pomodoro.start = getCurrentTimeInSeconds(); // Send notification let notificationMessage; From c2d5d2b61ae4d4905ecc7f61e7dbf90e46557071 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sun, 10 Aug 2025 15:40:42 +0700 Subject: [PATCH 60/70] pomodoro: remove unnecessary isLongBreak persistent state --- .config/quickshell/ii/modules/common/Persistent.qml | 1 - .config/quickshell/ii/services/TimerService.qml | 10 +++------- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/.config/quickshell/ii/modules/common/Persistent.qml b/.config/quickshell/ii/modules/common/Persistent.qml index e639f54f1..29d41e18d 100644 --- a/.config/quickshell/ii/modules/common/Persistent.qml +++ b/.config/quickshell/ii/modules/common/Persistent.qml @@ -67,7 +67,6 @@ Singleton { property bool running: false property int start: 0 property bool isBreak: false - property bool isLongBreak: false property int cycle: 0 } property JsonObject stopwatch: JsonObject { diff --git a/.config/quickshell/ii/services/TimerService.qml b/.config/quickshell/ii/services/TimerService.qml index f4f9bde95..baf13c796 100644 --- a/.config/quickshell/ii/services/TimerService.qml +++ b/.config/quickshell/ii/services/TimerService.qml @@ -22,10 +22,9 @@ Singleton { property bool isPomodoroRunning: Persistent.states.timer.pomodoro.running property bool isBreak: Persistent.states.timer.pomodoro.isBreak - property bool isLongBreak: Persistent.states.timer.pomodoro.isLongBreak - property bool isPomodoroLongBreak: Persistent.states.timer.pomodoro.isLongBreak + property bool isLongBreak: Persistent.states.timer.pomodoro.isBreak && (pomodoroCycle + 1 == cyclesBeforeLongBreak); property int pomodoroLapDuration: isBreak ? (isLongBreak ? longBreakTime : breakTime) : focusTime - property int pomodoroSecondsLeft: focusTime + property int pomodoroSecondsLeft: isLongBreak ? longBreakTime : (isBreak ? breakTime : focusTime) property int pomodoroCycle: Persistent.states.timer.pomodoro.cycle property bool isStopwatchRunning: Persistent.states.timer.stopwatch.running @@ -53,7 +52,6 @@ Singleton { if (getCurrentTimeInSeconds() >= Persistent.states.timer.pomodoro.start + pomodoroLapDuration) { // Reset counts Persistent.states.timer.pomodoro.isBreak = !Persistent.states.timer.pomodoro.isBreak; - Persistent.states.timer.pomodoro.isLongBreak = Persistent.states.timer.pomodoro.isBreak && (pomodoroCycle + 1 == cyclesBeforeLongBreak); Persistent.states.timer.pomodoro.start = getCurrentTimeInSeconds(); // Send notification @@ -97,7 +95,6 @@ Singleton { function resetPomodoro() { Persistent.states.timer.pomodoro.running = false; Persistent.states.timer.pomodoro.isBreak = false; - Persistent.states.timer.pomodoro.isLongBreak = false; Persistent.states.timer.pomodoro.start = getCurrentTimeInSeconds(); Persistent.states.timer.pomodoro.cycle = 0; refreshPomodoro(); @@ -133,10 +130,9 @@ Singleton { } function stopwatchReset() { - Persistent.states.timer.stopwatch.running = false; stopwatchTime = 0; - Persistent.states.timer.stopwatch.start = getCurrentTimeIn10ms(); Persistent.states.timer.stopwatch.laps = []; + Persistent.states.timer.stopwatch.running = false; } function stopwatchRecordLap() { From 709415a6b4e54edc07d55d87511013e1cf8c90f8 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sun, 10 Aug 2025 16:02:00 +0700 Subject: [PATCH 61/70] rename stuff --- .../sidebarRight/pomodoro/PomodoroTimer.qml | 12 ++++----- .../sidebarRight/pomodoro/Stopwatch.qml | 22 ++++++++-------- .../quickshell/ii/services/TimerService.qml | 26 +++++++++---------- 3 files changed, 30 insertions(+), 30 deletions(-) diff --git a/.config/quickshell/ii/modules/sidebarRight/pomodoro/PomodoroTimer.qml b/.config/quickshell/ii/modules/sidebarRight/pomodoro/PomodoroTimer.qml index e50f92d83..439c06ee4 100644 --- a/.config/quickshell/ii/modules/sidebarRight/pomodoro/PomodoroTimer.qml +++ b/.config/quickshell/ii/modules/sidebarRight/pomodoro/PomodoroTimer.qml @@ -45,7 +45,7 @@ Item { } StyledText { Layout.alignment: Qt.AlignHCenter - text: TimerService.isLongBreak ? Translation.tr("Long break") : TimerService.isBreak ? Translation.tr("Break") : Translation.tr("Focus") + 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 } @@ -80,15 +80,15 @@ Item { contentItem: StyledText { anchors.centerIn: parent horizontalAlignment: Text.AlignHCenter - text: TimerService.isPomodoroRunning ? Translation.tr("Pause") : (TimerService.pomodoroSecondsLeft === TimerService.focusTime) ? Translation.tr("Start") : Translation.tr("Resume") - color: TimerService.isPomodoroRunning ? Appearance.colors.colOnSecondaryContainer : Appearance.colors.colOnPrimary + 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.isPomodoroRunning ? Appearance.colors.colSecondaryContainer : Appearance.colors.colPrimary - colBackgroundHover: TimerService.isPomodoroRunning ? Appearance.colors.colSecondaryContainer : Appearance.colors.colPrimary + colBackground: TimerService.pomodoroRunning ? Appearance.colors.colSecondaryContainer : Appearance.colors.colPrimary + colBackgroundHover: TimerService.pomodoroRunning ? Appearance.colors.colSecondaryContainer : Appearance.colors.colPrimary } RippleButton { @@ -96,7 +96,7 @@ Item { implicitWidth: 90 onClicked: TimerService.resetPomodoro() - enabled: (TimerService.pomodoroSecondsLeft < TimerService.pomodoroLapDuration) || TimerService.pomodoroCycle > 0 || TimerService.isBreak + enabled: (TimerService.pomodoroSecondsLeft < TimerService.pomodoroLapDuration) || TimerService.pomodoroCycle > 0 || TimerService.pomodoroBreak font.pixelSize: Appearance.font.pixelSize.larger colBackground: Appearance.colors.colErrorContainer diff --git a/.config/quickshell/ii/modules/sidebarRight/pomodoro/Stopwatch.qml b/.config/quickshell/ii/modules/sidebarRight/pomodoro/Stopwatch.qml index cbf588194..2f1483932 100644 --- a/.config/quickshell/ii/modules/sidebarRight/pomodoro/Stopwatch.qml +++ b/.config/quickshell/ii/modules/sidebarRight/pomodoro/Stopwatch.qml @@ -169,14 +169,14 @@ Item { TimerService.toggleStopwatch() } - colBackground: TimerService.isStopwatchRunning ? Appearance.colors.colSecondaryContainer : Appearance.colors.colPrimary - colBackgroundHover: TimerService.isStopwatchRunning ? Appearance.colors.colSecondaryContainerHover : Appearance.colors.colPrimaryHover - colRipple: TimerService.isStopwatchRunning ? Appearance.colors.colSecondaryContainerActive : Appearance.colors.colPrimaryActive + 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.isStopwatchRunning ? Appearance.colors.colOnSecondaryContainer : Appearance.colors.colOnPrimary - text: TimerService.isStopwatchRunning ? Translation.tr("Pause") : TimerService.stopwatchTime === 0 ? Translation.tr("Start") : Translation.tr("Resume") + color: TimerService.stopwatchRunning ? Appearance.colors.colOnSecondaryContainer : Appearance.colors.colOnPrimary + text: TimerService.stopwatchRunning ? Translation.tr("Pause") : TimerService.stopwatchTime === 0 ? Translation.tr("Start") : Translation.tr("Resume") } } @@ -186,21 +186,21 @@ Item { font.pixelSize: Appearance.font.pixelSize.larger onClicked: { - if (TimerService.isStopwatchRunning) + if (TimerService.stopwatchRunning) TimerService.stopwatchRecordLap() else TimerService.stopwatchReset() } enabled: TimerService.stopwatchTime !== 0 - colBackground: TimerService.isStopwatchRunning ? Appearance.colors.colLayer2 : Appearance.colors.colErrorContainer - colBackgroundHover: TimerService.isStopwatchRunning ? Appearance.colors.colLayer2Hover : Appearance.colors.colErrorContainerHover - colRipple: TimerService.isStopwatchRunning ? Appearance.colors.colLayer2Active : Appearance.colors.colErrorContainerActive + 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.isStopwatchRunning ? Translation.tr("Lap") : Translation.tr("Reset") - color: TimerService.isStopwatchRunning ? Appearance.colors.colOnLayer2 : Appearance.colors.colOnErrorContainer + text: TimerService.stopwatchRunning ? Translation.tr("Lap") : Translation.tr("Reset") + color: TimerService.stopwatchRunning ? Appearance.colors.colOnLayer2 : Appearance.colors.colOnErrorContainer } } } diff --git a/.config/quickshell/ii/services/TimerService.qml b/.config/quickshell/ii/services/TimerService.qml index baf13c796..5824c01ab 100644 --- a/.config/quickshell/ii/services/TimerService.qml +++ b/.config/quickshell/ii/services/TimerService.qml @@ -20,21 +20,21 @@ Singleton { property int cyclesBeforeLongBreak: Config.options.time.pomodoro.cyclesBeforeLongBreak property string alertSound: Config.options.time.pomodoro.alertSound - property bool isPomodoroRunning: Persistent.states.timer.pomodoro.running - property bool isBreak: Persistent.states.timer.pomodoro.isBreak - property bool isLongBreak: Persistent.states.timer.pomodoro.isBreak && (pomodoroCycle + 1 == cyclesBeforeLongBreak); - property int pomodoroLapDuration: isBreak ? (isLongBreak ? longBreakTime : breakTime) : focusTime - property int pomodoroSecondsLeft: isLongBreak ? longBreakTime : (isBreak ? breakTime : focusTime) + 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 isStopwatchRunning: Persistent.states.timer.stopwatch.running + 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 (!isStopwatchRunning) + if (!stopwatchRunning) stopwatchReset(); } @@ -56,7 +56,7 @@ Singleton { // Send notification let notificationMessage; - if (Persistent.states.timer.pomodoro.isLongBreak) { + 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)); @@ -68,7 +68,7 @@ Singleton { if (alertSound) Quickshell.execDetached(["ffplay", "-nodisp", "-autoexit", alertSound]); - if (!isBreak) { + if (!pomodoroBreak) { Persistent.states.timer.pomodoro.cycle = (Persistent.states.timer.pomodoro.cycle + 1) % root.cyclesBeforeLongBreak; } } @@ -79,13 +79,13 @@ Singleton { Timer { id: pomodoroTimer interval: 200 - running: root.isPomodoroRunning + running: root.pomodoroRunning repeat: true onTriggered: refreshPomodoro() } function togglePomodoro() { - Persistent.states.timer.pomodoro.running = !isPomodoroRunning; + Persistent.states.timer.pomodoro.running = !pomodoroRunning; if (Persistent.states.timer.pomodoro.running) { // Start/Resume Persistent.states.timer.pomodoro.start = getCurrentTimeInSeconds() + pomodoroSecondsLeft - pomodoroLapDuration; @@ -108,13 +108,13 @@ Singleton { Timer { id: stopwatchTimer interval: 10 - running: root.isStopwatchRunning + running: root.stopwatchRunning repeat: true onTriggered: refreshStopwatch() } function toggleStopwatch() { - if (root.isStopwatchRunning) + if (root.stopwatchRunning) stopwatchPause(); else stopwatchResume(); From 86f1e63ed2e56013b77a5d254326a69e454f692d Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sun, 10 Aug 2025 16:03:37 +0700 Subject: [PATCH 62/70] stopwatch: clear laps on Start --- .../quickshell/ii/modules/sidebarRight/pomodoro/Stopwatch.qml | 2 +- .config/quickshell/ii/services/TimerService.qml | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.config/quickshell/ii/modules/sidebarRight/pomodoro/Stopwatch.qml b/.config/quickshell/ii/modules/sidebarRight/pomodoro/Stopwatch.qml index 2f1483932..ffc706568 100644 --- a/.config/quickshell/ii/modules/sidebarRight/pomodoro/Stopwatch.qml +++ b/.config/quickshell/ii/modules/sidebarRight/pomodoro/Stopwatch.qml @@ -191,7 +191,7 @@ Item { else TimerService.stopwatchReset() } - enabled: TimerService.stopwatchTime !== 0 + 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 diff --git a/.config/quickshell/ii/services/TimerService.qml b/.config/quickshell/ii/services/TimerService.qml index 5824c01ab..e6c8906bc 100644 --- a/.config/quickshell/ii/services/TimerService.qml +++ b/.config/quickshell/ii/services/TimerService.qml @@ -125,6 +125,7 @@ Singleton { } function stopwatchResume() { + if (stopwatchTime === 0) Persistent.states.timer.stopwatch.laps = []; Persistent.states.timer.stopwatch.running = true; Persistent.states.timer.stopwatch.start = getCurrentTimeIn10ms() - stopwatchTime; } From 077922c6672ce1707e77db3f02ee33468a20fc99 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sun, 10 Aug 2025 16:15:29 +0700 Subject: [PATCH 63/70] fix wrong circular progress prop name --- .../ii/modules/sidebarRight/pomodoro/PomodoroTimer.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.config/quickshell/ii/modules/sidebarRight/pomodoro/PomodoroTimer.qml b/.config/quickshell/ii/modules/sidebarRight/pomodoro/PomodoroTimer.qml index 439c06ee4..a7a5cc48f 100644 --- a/.config/quickshell/ii/modules/sidebarRight/pomodoro/PomodoroTimer.qml +++ b/.config/quickshell/ii/modules/sidebarRight/pomodoro/PomodoroTimer.qml @@ -26,7 +26,7 @@ Item { value: { return TimerService.pomodoroSecondsLeft / TimerService.pomodoroLapDuration; } - size: 200 + implicitSize: 200 enableAnimation: true ColumnLayout { From 5873995e42beb0ece11b2334bb4b15c1f726a032 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sun, 10 Aug 2025 16:44:21 +0700 Subject: [PATCH 64/70] settings: add hiding and bottom position options --- .../ii/modules/settings/InterfaceConfig.qml | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) 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 { From 84c45f20683728fa252696c140640a75ca2111af Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sun, 10 Aug 2025 16:44:29 +0700 Subject: [PATCH 65/70] make scrolling accumulate properly --- .../common/widgets/StyledFlickable.qml | 20 ++++++++++++++++--- .../modules/common/widgets/StyledListView.qml | 20 ++++++++++++++++--- 2 files changed, 34 insertions(+), 6 deletions(-) diff --git a/.config/quickshell/ii/modules/common/widgets/StyledFlickable.qml b/.config/quickshell/ii/modules/common/widgets/StyledFlickable.qml index 715d09a62..c765a3fce 100644 --- a/.config/quickshell/ii/modules/common/widgets/StyledFlickable.qml +++ b/.config/quickshell/ii/modules/common/widgets/StyledFlickable.qml @@ -9,6 +9,8 @@ Flickable { 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 @@ -16,12 +18,17 @@ Flickable { acceptedButtons: Qt.NoButton onWheel: function(wheelEvent) { const delta = wheelEvent.angleDelta.y / root.mouseScrollDeltaThreshold; - // The angleDelta.y of a touchpad is usually small and continuous, + // 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; - var targetY = root.contentY - delta * scrollFactor; - targetY = Math.max(0, Math.min(targetY, root.contentHeight - root.height)); + + 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; } } @@ -33,4 +40,11 @@ Flickable { 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 7783d6fc0..aebf35d77 100644 --- a/.config/quickshell/ii/modules/common/widgets/StyledListView.qml +++ b/.config/quickshell/ii/modules/common/widgets/StyledListView.qml @@ -14,6 +14,8 @@ 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 @@ -33,12 +35,17 @@ ListView { acceptedButtons: Qt.NoButton onWheel: function(wheelEvent) { const delta = wheelEvent.angleDelta.y / root.mouseScrollDeltaThreshold; - // The angleDelta.y of a touchpad is usually small and continuous, + // 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; - var targetY = root.contentY - delta * scrollFactor; - targetY = Math.max(0, Math.min(targetY, root.contentHeight - root.height)); + + 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; } } @@ -51,6 +58,13 @@ ListView { } } + // 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, { From 252675d3ffe65ca41e70f88288718002e19d9562 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sun, 10 Aug 2025 17:24:52 +0700 Subject: [PATCH 66/70] notifications: smaller icon --- .../ii/modules/common/widgets/NotificationAppIcon.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.config/quickshell/ii/modules/common/widgets/NotificationAppIcon.qml b/.config/quickshell/ii/modules/common/widgets/NotificationAppIcon.qml index 5158d6438..fb05a76ac 100644 --- a/.config/quickshell/ii/modules/common/widgets/NotificationAppIcon.qml +++ b/.config/quickshell/ii/modules/common/widgets/NotificationAppIcon.qml @@ -13,7 +13,7 @@ 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 smallAppIconScale: 0.49 From 7f1699663d16b612cb232fd230c06492021fa692 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Sun, 10 Aug 2025 17:40:23 +0700 Subject: [PATCH 67/70] use wl-clip-persist so clipboard content won't go away when the content source app is closed --- .config/hypr/hyprland/execs.conf | 1 + arch-packages/illogical-impulse-basic/PKGBUILD | 1 + 2 files changed, 2 insertions(+) diff --git a/.config/hypr/hyprland/execs.conf b/.config/hypr/hyprland/execs.conf index 6d92de1c8..659f0ea92 100644 --- a/.config/hypr/hyprland/execs.conf +++ b/.config/hypr/hyprland/execs.conf @@ -20,6 +20,7 @@ exec-once = easyeffects --gapplication-service # exec-once = wl-paste --watch cliphist store & exec-once = wl-paste --type text --watch cliphist store exec-once = wl-paste --type image --watch cliphist store +exec-once = wl-clip-persist --clipboard regular # Cursor exec-once = hyprctl setcursor Bibata-Modern-Classic 24 diff --git a/arch-packages/illogical-impulse-basic/PKGBUILD b/arch-packages/illogical-impulse-basic/PKGBUILD index c338727f9..d4e74276a 100644 --- a/arch-packages/illogical-impulse-basic/PKGBUILD +++ b/arch-packages/illogical-impulse-basic/PKGBUILD @@ -16,5 +16,6 @@ depends=( ripgrep jq meson + wl-clip-persist xdg-user-dirs ) From ea70c354ada5eb3a1feb3c1fdf677811d8310a58 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Mon, 11 Aug 2025 15:07:47 +0700 Subject: [PATCH 68/70] lock screen: fix incorrect password text --- .config/quickshell/ii/GlobalStates.qml | 1 + .config/quickshell/ii/modules/background/Background.qml | 4 ++-- .config/quickshell/ii/modules/lock/LockContext.qml | 6 +++++- .config/quickshell/ii/modules/lock/LockSurface.qml | 9 +-------- 4 files changed, 9 insertions(+), 11 deletions(-) diff --git a/.config/quickshell/ii/GlobalStates.qml b/.config/quickshell/ii/GlobalStates.qml index 163507177..3f7468f4c 100644 --- a/.config/quickshell/ii/GlobalStates.qml +++ b/.config/quickshell/ii/GlobalStates.qml @@ -19,6 +19,7 @@ Singleton { property bool overviewOpen: false property bool screenLocked: false property bool screenLockContainsCharacters: false + property bool screenUnlockFailed: false property bool sessionOpen: false property bool superDown: false property bool superReleaseMightTrigger: true diff --git a/.config/quickshell/ii/modules/background/Background.qml b/.config/quickshell/ii/modules/background/Background.qml index 2eb5c3ec7..b06b6294f 100644 --- a/.config/quickshell/ii/modules/background/Background.qml +++ b/.config/quickshell/ii/modules/background/Background.qml @@ -283,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/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 From 5b24ac037ff9599ee5446d692b78c78d09a65b25 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Mon, 11 Aug 2025 15:09:39 +0700 Subject: [PATCH 69/70] automatic transparency --- .../ii/modules/common/Appearance.qml | 37 +++++++++++++++---- .../quickshell/ii/modules/common/Config.qml | 7 +++- .../widgets/NotificationActionButton.qml | 6 +-- .../common/widgets/NotificationAppIcon.qml | 2 +- .../common/widgets/NotificationGroup.qml | 2 +- .../common/widgets/NotificationItem.qml | 6 +-- .../ii/modules/settings/StyleConfig.qml | 4 +- 7 files changed, 45 insertions(+), 19 deletions(-) diff --git a/.config/quickshell/ii/modules/common/Appearance.qml b/.config/quickshell/ii/modules/common/Appearance.qml index 2c00e48e5..64f76b96b 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) diff --git a/.config/quickshell/ii/modules/common/Config.qml b/.config/quickshell/ii/modules/common/Config.qml index 8337be313..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 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 fb05a76ac..278691b8b 100644 --- a/.config/quickshell/ii/modules/common/widgets/NotificationAppIcon.qml +++ b/.config/quickshell/ii/modules/common/widgets/NotificationAppIcon.qml @@ -15,7 +15,7 @@ Rectangle { // App icon property real scale: 1 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/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.") From 9ba251ff30d10ecc1cbe42ae938b40231b80ffc7 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Mon, 11 Aug 2025 15:31:59 +0700 Subject: [PATCH 70/70] Revert "use wl-clip-persist" This reverts commit 7f1699663d16b612cb232fd230c06492021fa692. --- .config/hypr/hyprland/execs.conf | 1 - arch-packages/illogical-impulse-basic/PKGBUILD | 1 - 2 files changed, 2 deletions(-) diff --git a/.config/hypr/hyprland/execs.conf b/.config/hypr/hyprland/execs.conf index 659f0ea92..6d92de1c8 100644 --- a/.config/hypr/hyprland/execs.conf +++ b/.config/hypr/hyprland/execs.conf @@ -20,7 +20,6 @@ exec-once = easyeffects --gapplication-service # exec-once = wl-paste --watch cliphist store & exec-once = wl-paste --type text --watch cliphist store exec-once = wl-paste --type image --watch cliphist store -exec-once = wl-clip-persist --clipboard regular # Cursor exec-once = hyprctl setcursor Bibata-Modern-Classic 24 diff --git a/arch-packages/illogical-impulse-basic/PKGBUILD b/arch-packages/illogical-impulse-basic/PKGBUILD index d4e74276a..c338727f9 100644 --- a/arch-packages/illogical-impulse-basic/PKGBUILD +++ b/arch-packages/illogical-impulse-basic/PKGBUILD @@ -16,6 +16,5 @@ depends=( ripgrep jq meson - wl-clip-persist xdg-user-dirs )