From 1193b7a80223b7a7e9822a4b10c82b4ea2afc197 Mon Sep 17 00:00:00 2001 From: EoinKanro <54404008+EoinKanro@users.noreply.github.com> Date: Thu, 4 Dec 2025 22:30:55 +0100 Subject: [PATCH 01/17] Rework parallax --- .../quickshell/ii/modules/common/Config.qml | 2 +- .../ii/modules/ii/background/Background.qml | 114 ++++++++++-------- .../ii/modules/settings/BackgroundConfig.qml | 4 +- 3 files changed, 68 insertions(+), 52 deletions(-) diff --git a/dots/.config/quickshell/ii/modules/common/Config.qml b/dots/.config/quickshell/ii/modules/common/Config.qml index b0b767a44..121f78741 100644 --- a/dots/.config/quickshell/ii/modules/common/Config.qml +++ b/dots/.config/quickshell/ii/modules/common/Config.qml @@ -208,7 +208,7 @@ Singleton { property bool vertical: false property bool autoVertical: false property bool enableWorkspace: true - property real workspaceZoom: 1.07 // Relative to your screen, not wallpaper size + property real workspaceZoom: 1.0 // Relative to wallpaper size property bool enableSidebar: true property real widgetsFactor: 1.2 } diff --git a/dots/.config/quickshell/ii/modules/ii/background/Background.qml b/dots/.config/quickshell/ii/modules/ii/background/Background.qml index 68d8b1698..f9ffe54d2 100644 --- a/dots/.config/quickshell/ii/modules/ii/background/Background.qml +++ b/dots/.config/quickshell/ii/modules/ii/background/Background.qml @@ -37,6 +37,7 @@ Variants { 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 + property int totalWorkspaces: Config?.options.bar.workspaces.shown ?? 10 // Wallpaper property bool wallpaperIsVideo: Config.options.background.wallpaperPath.endsWith(".mp4") || Config.options.background.wallpaperPath.endsWith(".webm") || Config.options.background.wallpaperPath.endsWith(".mkv") || Config.options.background.wallpaperPath.endsWith(".avi") || Config.options.background.wallpaperPath.endsWith(".mov") property string wallpaperPath: wallpaperIsVideo ? Config.options.background.thumbnailPath : Config.options.background.wallpaperPath @@ -46,13 +47,15 @@ Variants { const sensitiveNetwork = (CF.StringUtils.stringListContainsSubstring(Network.networkName.toLowerCase(), Config.options.workSafety.triggerCondition.networkNameKeywords)); return enabled && sensitiveWallpaper && sensitiveNetwork; } - property real wallpaperToScreenRatio: Math.min(wallpaperWidth / screen.width, wallpaperHeight / screen.height) - property real preferredWallpaperScale: Config.options.background.parallax.workspaceZoom + property real parallaxRation: 1.2 + property real additionalScaleFactor: Config.options.background.parallax.workspaceZoom property real effectiveWallpaperScale: 1 // Some reasonable init value, to be updated property int wallpaperWidth: modelData.width // Some reasonable init value, to be updated property int wallpaperHeight: modelData.height // Some reasonable init value, to be updated - property real movableXSpace: ((wallpaperWidth / wallpaperToScreenRatio * effectiveWallpaperScale) - screen.width) / 2 - property real movableYSpace: ((wallpaperHeight / wallpaperToScreenRatio * effectiveWallpaperScale) - screen.height) / 2 + property real scaledWallpaperWidth: wallpaperWidth * effectiveWallpaperScale + property real scaledWallpaperHeight: wallpaperHeight * effectiveWallpaperScale + property real parallaxTotalPixelsX: Math.max(screen.width - scaledWallpaperWidth, scaledWallpaperWidth - screen.width) + property real parallaxTotalPixelsY: Math.max(screen.height - scaledWallpaperHeight, scaledWallpaperHeight - screen.height) readonly property bool verticalParallax: (Config.options.background.parallax.autoVertical && wallpaperHeight > wallpaperWidth) || Config.options.background.parallax.vertical // Colors property bool shouldBlur: (GlobalStates.screenLocked && Config.options.lock.blur.enable) @@ -111,13 +114,13 @@ Variants { bgRoot.wallpaperWidth = width; bgRoot.wallpaperHeight = height; - if (width <= screenWidth || height <= screenHeight) { - // Undersized/perfectly sized wallpapers - bgRoot.effectiveWallpaperScale = Math.max(screenWidth / width, screenHeight / height); - } else { - // Oversized = can be zoomed for parallax, yay - bgRoot.effectiveWallpaperScale = Math.min(bgRoot.preferredWallpaperScale, width / screenWidth, height / screenHeight); - } + // Perfect image; scale = 1 + // Small picture; scale > 1; will zoom in the picture + // Big picture; scale < 1; will zoom out the picture + // Choose max number so every side will fit + const minSuitableScale = Math.max(screenWidth / width, screenHeight / height); + //todo switch off parallax + bgRoot.effectiveWallpaperScale = minSuitableScale * bgRoot.additionalScaleFactor * bgRoot.parallaxRation; } } } @@ -133,32 +136,49 @@ Variants { opacity: (status === Image.Ready && !bgRoot.wallpaperIsVideo) ? 1 : 0 cache: false smooth: false - // Range = groups that workspaces span on - property int chunkSize: Config?.options.bar.workspaces.shown ?? 10 - property int lower: Math.floor(bgRoot.firstWorkspaceId / chunkSize) * chunkSize - property int upper: Math.ceil(bgRoot.lastWorkspaceId / chunkSize) * chunkSize - property int range: upper - lower - property real valueX: { - let result = 0.5; + + property int workspaceIndex: (bgRoot.monitor.activeWorkspace?.id ?? 1) - 1 + property real middleFraction: 0.5 + property real fraction: { + // 0 - start of the picture + // 1 - end of the picture + if (bgRoot.totalWorkspaces <= 1) { + return middleFraction; + } + return Math.max(0, Math.min(1, workspaceIndex / (bgRoot.totalWorkspaces - 1))); + } + + x: { + if (bgRoot.screen.width > bgRoot.scaledWallpaperWidth) { + // Center the picture + return bgRoot.parallaxTotalPixelsX / 2; + } + + let usedFraction = middleFraction; if (Config.options.background.parallax.enableWorkspace && !bgRoot.verticalParallax) { - result = ((bgRoot.monitor.activeWorkspace?.id - lower) / range); + usedFraction = fraction; } if (Config.options.background.parallax.enableSidebar) { - result += (0.15 * GlobalStates.sidebarRightOpen - 0.15 * GlobalStates.sidebarLeftOpen); + let sidebarFraction = bgRoot.parallaxRation / 10; + usedFraction += (sidebarFraction * GlobalStates.sidebarRightOpen - sidebarFraction * GlobalStates.sidebarLeftOpen); } - return result; + usedFraction = Math.max(0, Math.min(1, usedFraction)); + return - bgRoot.parallaxTotalPixelsX * usedFraction; } - property real valueY: { - let result = 0.5; + y: { + if (bgRoot.screen.height > bgRoot.scaledWallpaperHeight) { + // Center the picture + return bgRoot.parallaxTotalPixelsY / 2; + } + + let usedFraction = middleFraction; if (Config.options.background.parallax.enableWorkspace && bgRoot.verticalParallax) { - result = ((bgRoot.monitor.activeWorkspace?.id - lower) / range); + usedFraction = fraction; } - return result; + usedFraction = Math.max(0, Math.min(1, usedFraction)); + return - bgRoot.parallaxTotalPixelsY * usedFraction; } - property real effectiveValueX: Math.max(0, Math.min(1, valueX)) - property real effectiveValueY: Math.max(0, Math.min(1, valueY)) - x: -(bgRoot.movableXSpace) - (effectiveValueX - 0.5) * 2 * bgRoot.movableXSpace - y: -(bgRoot.movableYSpace) - (effectiveValueY - 0.5) * 2 * bgRoot.movableYSpace + source: bgRoot.wallpaperSafetyTriggered ? "" : bgRoot.wallpaperPath fillMode: Image.PreserveAspectCrop Behavior on x { @@ -174,11 +194,11 @@ Variants { } } sourceSize { - width: bgRoot.screen.width * bgRoot.effectiveWallpaperScale * bgRoot.monitor.scale - height: bgRoot.screen.height * bgRoot.effectiveWallpaperScale * bgRoot.monitor.scale + width: bgRoot.scaledWallpaperWidth + height: bgRoot.scaledWallpaperHeight } - width: bgRoot.wallpaperWidth / bgRoot.wallpaperToScreenRatio * bgRoot.effectiveWallpaperScale - height: bgRoot.wallpaperHeight / bgRoot.wallpaperToScreenRatio * bgRoot.effectiveWallpaperScale + width: bgRoot.scaledWallpaperWidth + height: bgRoot.scaledWallpaperHeight } Loader { @@ -210,22 +230,18 @@ Variants { WidgetCanvas { id: widgetCanvas anchors { - left: wallpaper.left - right: wallpaper.right - top: wallpaper.top - bottom: wallpaper.bottom + left: bgRoot.screen.left + right: bgRoot.screen.right + top: bgRoot.screen.top + bottom: bgRoot.screen.bottom horizontalCenter: undefined verticalCenter: undefined readonly property real parallaxFactor: Config.options.background.parallax.widgetsFactor leftMargin: { - const xOnWallpaper = bgRoot.movableXSpace; - const extraMove = (wallpaper.effectiveValueX * 2 * bgRoot.movableXSpace) * (parallaxFactor - 1); - return xOnWallpaper - extraMove; + return bgRoot.screen.width * 0.2; } topMargin: { - const yOnWallpaper = bgRoot.movableYSpace; - const extraMove = (wallpaper.effectiveValueY * 2 * bgRoot.movableYSpace) * (parallaxFactor - 1); - return yOnWallpaper - extraMove; + return bgRoot.screen.height * 0.2; } Behavior on leftMargin { animation: Appearance.animation.elementMove.numberAnimation.createObject(this) @@ -275,9 +291,9 @@ Variants { sourceComponent: WeatherWidget { screenWidth: bgRoot.screen.width screenHeight: bgRoot.screen.height - scaledScreenWidth: bgRoot.screen.width / bgRoot.effectiveWallpaperScale - scaledScreenHeight: bgRoot.screen.height / bgRoot.effectiveWallpaperScale - wallpaperScale: bgRoot.effectiveWallpaperScale + scaledScreenWidth: bgRoot.screen.width + scaledScreenHeight: bgRoot.screen.height + wallpaperScale: 1 } } @@ -286,9 +302,9 @@ Variants { sourceComponent: ClockWidget { screenWidth: bgRoot.screen.width screenHeight: bgRoot.screen.height - scaledScreenWidth: bgRoot.screen.width / bgRoot.effectiveWallpaperScale - scaledScreenHeight: bgRoot.screen.height / bgRoot.effectiveWallpaperScale - wallpaperScale: bgRoot.effectiveWallpaperScale + scaledScreenWidth: bgRoot.screen.width + scaledScreenHeight: bgRoot.screen.height + wallpaperScale: 1 wallpaperSafetyTriggered: bgRoot.wallpaperSafetyTriggered } } diff --git a/dots/.config/quickshell/ii/modules/settings/BackgroundConfig.qml b/dots/.config/quickshell/ii/modules/settings/BackgroundConfig.qml index 66630cc24..86debf5d7 100644 --- a/dots/.config/quickshell/ii/modules/settings/BackgroundConfig.qml +++ b/dots/.config/quickshell/ii/modules/settings/BackgroundConfig.qml @@ -43,8 +43,8 @@ ContentPage { icon: "loupe" text: Translation.tr("Preferred wallpaper zoom (%)") value: Config.options.background.parallax.workspaceZoom * 100 - from: 100 - to: 150 + from: 10 + to: 200 stepSize: 1 onValueChanged: { Config.options.background.parallax.workspaceZoom = value / 100; From 1efd2dfa19b5fa84aaf871ec16d729ed23d40c76 Mon Sep 17 00:00:00 2001 From: EoinKanro <54404008+EoinKanro@users.noreply.github.com> Date: Thu, 4 Dec 2025 22:47:50 +0100 Subject: [PATCH 02/17] Remove todo and make readonly additionalScaleFactor --- .../.config/quickshell/ii/modules/ii/background/Background.qml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/dots/.config/quickshell/ii/modules/ii/background/Background.qml b/dots/.config/quickshell/ii/modules/ii/background/Background.qml index f9ffe54d2..06f40f26b 100644 --- a/dots/.config/quickshell/ii/modules/ii/background/Background.qml +++ b/dots/.config/quickshell/ii/modules/ii/background/Background.qml @@ -48,7 +48,7 @@ Variants { return enabled && sensitiveWallpaper && sensitiveNetwork; } property real parallaxRation: 1.2 - property real additionalScaleFactor: Config.options.background.parallax.workspaceZoom + readonly property real additionalScaleFactor: Config.options.background.parallax.workspaceZoom property real effectiveWallpaperScale: 1 // Some reasonable init value, to be updated property int wallpaperWidth: modelData.width // Some reasonable init value, to be updated property int wallpaperHeight: modelData.height // Some reasonable init value, to be updated @@ -119,7 +119,6 @@ Variants { // Big picture; scale < 1; will zoom out the picture // Choose max number so every side will fit const minSuitableScale = Math.max(screenWidth / width, screenHeight / height); - //todo switch off parallax bgRoot.effectiveWallpaperScale = minSuitableScale * bgRoot.additionalScaleFactor * bgRoot.parallaxRation; } } From 758c84feafd5b7bb53cd57bb087feb94565e58b0 Mon Sep 17 00:00:00 2001 From: EoinKanro <54404008+EoinKanro@users.noreply.github.com> Date: Thu, 4 Dec 2025 23:47:08 +0100 Subject: [PATCH 03/17] Make readonly parallaxRation --- dots/.config/quickshell/ii/modules/ii/background/Background.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dots/.config/quickshell/ii/modules/ii/background/Background.qml b/dots/.config/quickshell/ii/modules/ii/background/Background.qml index 06f40f26b..8ff970889 100644 --- a/dots/.config/quickshell/ii/modules/ii/background/Background.qml +++ b/dots/.config/quickshell/ii/modules/ii/background/Background.qml @@ -47,7 +47,7 @@ Variants { const sensitiveNetwork = (CF.StringUtils.stringListContainsSubstring(Network.networkName.toLowerCase(), Config.options.workSafety.triggerCondition.networkNameKeywords)); return enabled && sensitiveWallpaper && sensitiveNetwork; } - property real parallaxRation: 1.2 + readonly property real parallaxRation: 1.2 readonly property real additionalScaleFactor: Config.options.background.parallax.workspaceZoom property real effectiveWallpaperScale: 1 // Some reasonable init value, to be updated property int wallpaperWidth: modelData.width // Some reasonable init value, to be updated From ead056c20776882c074d005c3ef1ccfc6c41e46e Mon Sep 17 00:00:00 2001 From: Ivan Rosinskii <54404008+EoinKanro@users.noreply.github.com> Date: Fri, 5 Dec 2025 17:41:12 +0100 Subject: [PATCH 04/17] Fix widgets and decrease parallaxRation --- .../ii/modules/ii/background/Background.qml | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/dots/.config/quickshell/ii/modules/ii/background/Background.qml b/dots/.config/quickshell/ii/modules/ii/background/Background.qml index 8ff970889..07b53e4e9 100644 --- a/dots/.config/quickshell/ii/modules/ii/background/Background.qml +++ b/dots/.config/quickshell/ii/modules/ii/background/Background.qml @@ -47,7 +47,7 @@ Variants { const sensitiveNetwork = (CF.StringUtils.stringListContainsSubstring(Network.networkName.toLowerCase(), Config.options.workSafety.triggerCondition.networkNameKeywords)); return enabled && sensitiveWallpaper && sensitiveNetwork; } - readonly property real parallaxRation: 1.2 + readonly property real parallaxRation: 1.1 readonly property real additionalScaleFactor: Config.options.background.parallax.workspaceZoom property real effectiveWallpaperScale: 1 // Some reasonable init value, to be updated property int wallpaperWidth: modelData.width // Some reasonable init value, to be updated @@ -229,19 +229,13 @@ Variants { WidgetCanvas { id: widgetCanvas anchors { - left: bgRoot.screen.left - right: bgRoot.screen.right - top: bgRoot.screen.top - bottom: bgRoot.screen.bottom + left: wallpaper.left + right: wallpaper.right + top: wallpaper.top + bottom: wallpaper.bottom horizontalCenter: undefined verticalCenter: undefined readonly property real parallaxFactor: Config.options.background.parallax.widgetsFactor - leftMargin: { - return bgRoot.screen.width * 0.2; - } - topMargin: { - return bgRoot.screen.height * 0.2; - } Behavior on leftMargin { animation: Appearance.animation.elementMove.numberAnimation.createObject(this) } From 52d6e8a5d1bbb9caabdb8fb570a17aaaf1dfb37a Mon Sep 17 00:00:00 2001 From: Ivan Rosinskii <54404008+EoinKanro@users.noreply.github.com> Date: Fri, 5 Dec 2025 18:07:40 +0100 Subject: [PATCH 05/17] Fix sourceSize quality --- .../quickshell/ii/modules/ii/background/Background.qml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dots/.config/quickshell/ii/modules/ii/background/Background.qml b/dots/.config/quickshell/ii/modules/ii/background/Background.qml index 07b53e4e9..c5176343b 100644 --- a/dots/.config/quickshell/ii/modules/ii/background/Background.qml +++ b/dots/.config/quickshell/ii/modules/ii/background/Background.qml @@ -193,8 +193,8 @@ Variants { } } sourceSize { - width: bgRoot.scaledWallpaperWidth - height: bgRoot.scaledWallpaperHeight + width: Math.max(bgRoot.wallpaperWidth, bgRoot.wallpaperWidth / bgRoot.parallaxRation) + height: Math.max(bgRoot.wallpaperHeight, bgRoot.wallpaperHeight / bgRoot.parallaxRation) } width: bgRoot.scaledWallpaperWidth height: bgRoot.scaledWallpaperHeight From ddf1bc6a08db2ee10ecf323a0414ad2c26bc9453 Mon Sep 17 00:00:00 2001 From: EoinKanro <54404008+EoinKanro@users.noreply.github.com> Date: Sat, 6 Dec 2025 00:53:07 +0100 Subject: [PATCH 06/17] Remove tricky calculations and fix sourceSize --- .../ii/modules/ii/background/Background.qml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/dots/.config/quickshell/ii/modules/ii/background/Background.qml b/dots/.config/quickshell/ii/modules/ii/background/Background.qml index c5176343b..afa2731d2 100644 --- a/dots/.config/quickshell/ii/modules/ii/background/Background.qml +++ b/dots/.config/quickshell/ii/modules/ii/background/Background.qml @@ -54,8 +54,8 @@ Variants { property int wallpaperHeight: modelData.height // Some reasonable init value, to be updated property real scaledWallpaperWidth: wallpaperWidth * effectiveWallpaperScale property real scaledWallpaperHeight: wallpaperHeight * effectiveWallpaperScale - property real parallaxTotalPixelsX: Math.max(screen.width - scaledWallpaperWidth, scaledWallpaperWidth - screen.width) - property real parallaxTotalPixelsY: Math.max(screen.height - scaledWallpaperHeight, scaledWallpaperHeight - screen.height) + property real parallaxTotalPixelsX: Math.max(0, scaledWallpaperWidth - screen.width) + property real parallaxTotalPixelsY: Math.max(0, scaledWallpaperHeight - screen.height) readonly property bool verticalParallax: (Config.options.background.parallax.autoVertical && wallpaperHeight > wallpaperWidth) || Config.options.background.parallax.vertical // Colors property bool shouldBlur: (GlobalStates.screenLocked && Config.options.lock.blur.enable) @@ -150,7 +150,7 @@ Variants { x: { if (bgRoot.screen.width > bgRoot.scaledWallpaperWidth) { // Center the picture - return bgRoot.parallaxTotalPixelsX / 2; + return (bgRoot.screen.width - bgRoot.scaledWallpaperWidth) / 2; } let usedFraction = middleFraction; @@ -167,7 +167,7 @@ Variants { y: { if (bgRoot.screen.height > bgRoot.scaledWallpaperHeight) { // Center the picture - return bgRoot.parallaxTotalPixelsY / 2; + return (bgRoot.screen.height - bgRoot.scaledWallpaperHeight) / 2; } let usedFraction = middleFraction; @@ -193,8 +193,8 @@ Variants { } } sourceSize { - width: Math.max(bgRoot.wallpaperWidth, bgRoot.wallpaperWidth / bgRoot.parallaxRation) - height: Math.max(bgRoot.wallpaperHeight, bgRoot.wallpaperHeight / bgRoot.parallaxRation) + width: bgRoot.scaledWallpaperWidth + height: bgRoot.scaledWallpaperHeight } width: bgRoot.scaledWallpaperWidth height: bgRoot.scaledWallpaperHeight From 56ad8b80b24d34a71d25072d0e6d38a9cce19e26 Mon Sep 17 00:00:00 2001 From: clsty Date: Wed, 1 Apr 2026 08:43:06 +0800 Subject: [PATCH 07/17] Remove file check before bibata-modern-classic-bin --- sdata/dist-arch/install-deps.sh | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/sdata/dist-arch/install-deps.sh b/sdata/dist-arch/install-deps.sh index 864c9cd2f..4e28cf23b 100644 --- a/sdata/dist-arch/install-deps.sh +++ b/sdata/dist-arch/install-deps.sh @@ -94,8 +94,7 @@ metapkgs=(./sdata/dist-arch/illogical-impulse-{audio,backlight,basic,fonts-theme metapkgs+=(./sdata/dist-arch/illogical-impulse-hyprland) metapkgs+=(./sdata/dist-arch/illogical-impulse-microtex-git) metapkgs+=(./sdata/dist-arch/illogical-impulse-quickshell-git) -[[ -f /usr/share/icons/Bibata-Modern-Classic/index.theme ]] || \ - metapkgs+=(./sdata/dist-arch/illogical-impulse-bibata-modern-classic-bin) +metapkgs+=(./sdata/dist-arch/illogical-impulse-bibata-modern-classic-bin) for i in "${metapkgs[@]}"; do metainstallflags="--needed" From 7948d01d94706247542591e22277f68e5684ea57 Mon Sep 17 00:00:00 2001 From: jwihardi Date: Wed, 1 Apr 2026 18:45:54 -0400 Subject: [PATCH 08/17] gentoo: removed oneui --- ...cal-impulse-oneui4-icons-git-1.0-r1.ebuild | 26 ------------------- sdata/dist-gentoo/install-deps.sh | 2 +- sdata/dist-gentoo/keywords | 1 - sdata/dist-gentoo/uninstall-deps.sh | 2 +- 4 files changed, 2 insertions(+), 29 deletions(-) delete mode 100644 sdata/dist-gentoo/illogical-impulse-oneui4-icons-git/illogical-impulse-oneui4-icons-git-1.0-r1.ebuild diff --git a/sdata/dist-gentoo/illogical-impulse-oneui4-icons-git/illogical-impulse-oneui4-icons-git-1.0-r1.ebuild b/sdata/dist-gentoo/illogical-impulse-oneui4-icons-git/illogical-impulse-oneui4-icons-git-1.0-r1.ebuild deleted file mode 100644 index 4e38d2e8b..000000000 --- a/sdata/dist-gentoo/illogical-impulse-oneui4-icons-git/illogical-impulse-oneui4-icons-git-1.0-r1.ebuild +++ /dev/null @@ -1,26 +0,0 @@ -# Copyright 2025 Gentoo Authors -# Distributed under the terms of the GNU General Public License v2 - -EAPI=8 - -DESCRIPTION="A fork of mjkim0727/OneUI4-Icons for illogical-impulse dotfiles" -HOMEPAGE="" -SRC_URI="https://github.com/end-4/OneUI4-Icons/archive/main.tar.gz -> ${P}.tar.gz" - -LICENSE="GPL-3" -SLOT="0" -KEYWORDS="~amd64 ~arm64 ~x86" -RESTRICT="strip" - -DEPEND="" -RDEPEND="" - -S="${WORKDIR}/OneUI4-Icons-main" - -src_install() { - insinto /usr/share/icons - - for theme in "OneUI" "OneUI-dark" "OneUI-light"; do - doins -r ${S}/${theme} - done -} diff --git a/sdata/dist-gentoo/install-deps.sh b/sdata/dist-gentoo/install-deps.sh index 1cbbdb966..e27af481e 100644 --- a/sdata/dist-gentoo/install-deps.sh +++ b/sdata/dist-gentoo/install-deps.sh @@ -33,7 +33,7 @@ fi arch=$(portageq envvar ACCEPT_KEYWORDS) # Exclude hyprland, will deal with that separately -metapkgs=(illogical-impulse-{audio,backlight,basic,bibata-modern-classic-bin,fonts-themes,hyprland,kde,microtex-git,oneui4-icons-git,portal,python,quickshell-git,screencapture,toolkit,widgets}) +metapkgs=(illogical-impulse-{audio,backlight,basic,bibata-modern-classic-bin,fonts-themes,hyprland,kde,microtex-git,portal,python,quickshell-git,screencapture,toolkit,widgets}) ebuild_dir="/var/db/repos/ii-dots" diff --git a/sdata/dist-gentoo/keywords b/sdata/dist-gentoo/keywords index 7e6fe2ab2..b6417ab31 100644 --- a/sdata/dist-gentoo/keywords +++ b/sdata/dist-gentoo/keywords @@ -6,7 +6,6 @@ app-misc/illogical-impulse-fonts-themes app-misc/illogical-impulse-hyprland app-misc/illogical-impulse-kde app-misc/illogical-impulse-microtex-git -app-misc/illogical-impulse-oneui4-icons-git app-misc/illogical-impulse-portal app-misc/illogical-impulse-python app-misc/illogical-impulse-quickshell-git diff --git a/sdata/dist-gentoo/uninstall-deps.sh b/sdata/dist-gentoo/uninstall-deps.sh index 280433946..849880445 100644 --- a/sdata/dist-gentoo/uninstall-deps.sh +++ b/sdata/dist-gentoo/uninstall-deps.sh @@ -1,7 +1,7 @@ # This script is meant to be sourced. # It's not for directly running. -for i in illogical-impulse-{quickshell-git,audio,backlight,basic,bibata-modern-classic-bin,fonts-themes,hyprland,kde,microtex-git,oneui4-icons-git,portal,python,screencapture,toolkit,widgets}; do +for i in illogical-impulse-{quickshell-git,audio,backlight,basic,bibata-modern-classic-bin,fonts-themes,hyprland,kde,microtex-git,portal,python,screencapture,toolkit,widgets}; do v sudo emerge --unmerge $i done From 4408b5d9d328e645f92d0925ef290efe12cec948 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Thu, 2 Apr 2026 22:22:35 +0200 Subject: [PATCH 09/17] fix not working with more than 10 workspaces --- .../.config/quickshell/ii/modules/ii/background/Background.qml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/dots/.config/quickshell/ii/modules/ii/background/Background.qml b/dots/.config/quickshell/ii/modules/ii/background/Background.qml index afa2731d2..667735a22 100644 --- a/dots/.config/quickshell/ii/modules/ii/background/Background.qml +++ b/dots/.config/quickshell/ii/modules/ii/background/Background.qml @@ -37,7 +37,8 @@ Variants { 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 - property int totalWorkspaces: Config?.options.bar.workspaces.shown ?? 10 + property int workspaceChunkSize: Config?.options.bar.workspaces.shown ?? 10 + property int totalWorkspaces: Math.ceil(lastWorkspaceId / workspaceChunkSize) * workspaceChunkSize // Wallpaper property bool wallpaperIsVideo: Config.options.background.wallpaperPath.endsWith(".mp4") || Config.options.background.wallpaperPath.endsWith(".webm") || Config.options.background.wallpaperPath.endsWith(".mkv") || Config.options.background.wallpaperPath.endsWith(".avi") || Config.options.background.wallpaperPath.endsWith(".mov") property string wallpaperPath: wallpaperIsVideo ? Config.options.background.thumbnailPath : Config.options.background.wallpaperPath From dfe9c876d2bf9ab4f8e1f435f8ab630203242031 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Thu, 2 Apr 2026 22:46:52 +0200 Subject: [PATCH 10/17] fix widget parallax --- .../ii/modules/ii/background/Background.qml | 85 +++++++------------ 1 file changed, 32 insertions(+), 53 deletions(-) diff --git a/dots/.config/quickshell/ii/modules/ii/background/Background.qml b/dots/.config/quickshell/ii/modules/ii/background/Background.qml index 667735a22..b63ba8862 100644 --- a/dots/.config/quickshell/ii/modules/ii/background/Background.qml +++ b/dots/.config/quickshell/ii/modules/ii/background/Background.qml @@ -127,7 +127,6 @@ Variants { Item { anchors.fill: parent - clip: true // Wallpaper StyledImage { @@ -148,35 +147,38 @@ Variants { return Math.max(0, Math.min(1, workspaceIndex / (bgRoot.totalWorkspaces - 1))); } - x: { - if (bgRoot.screen.width > bgRoot.scaledWallpaperWidth) { - // Center the picture - return (bgRoot.screen.width - bgRoot.scaledWallpaperWidth) / 2; - } - + property real usedFractionX: { let usedFraction = middleFraction; if (Config.options.background.parallax.enableWorkspace && !bgRoot.verticalParallax) { usedFraction = fraction; } if (Config.options.background.parallax.enableSidebar) { - let sidebarFraction = bgRoot.parallaxRation / 10; + let sidebarFraction = bgRoot.parallaxRation / bgRoot.workspaceChunkSize / 2; usedFraction += (sidebarFraction * GlobalStates.sidebarRightOpen - sidebarFraction * GlobalStates.sidebarLeftOpen); } - usedFraction = Math.max(0, Math.min(1, usedFraction)); - return - bgRoot.parallaxTotalPixelsX * usedFraction; + return Math.max(0, Math.min(1, usedFraction)); + } + property real usedFractionY: { + let usedFraction = middleFraction; + if (Config.options.background.parallax.enableWorkspace && bgRoot.verticalParallax) { + usedFraction = fraction; + } + return Math.max(0, Math.min(1, usedFraction)); + } + + x: { + if (bgRoot.screen.width > bgRoot.scaledWallpaperWidth) { + // Center the picture + return (bgRoot.screen.width - bgRoot.scaledWallpaperWidth) / 2; + } + return - bgRoot.parallaxTotalPixelsX * usedFractionX; } y: { if (bgRoot.screen.height > bgRoot.scaledWallpaperHeight) { // Center the picture return (bgRoot.screen.height - bgRoot.scaledWallpaperHeight) / 2; } - - let usedFraction = middleFraction; - if (Config.options.background.parallax.enableWorkspace && bgRoot.verticalParallax) { - usedFraction = fraction; - } - usedFraction = Math.max(0, Math.min(1, usedFraction)); - return - bgRoot.parallaxTotalPixelsY * usedFraction; + return - bgRoot.parallaxTotalPixelsY * usedFractionY; } source: bgRoot.wallpaperSafetyTriggered ? "" : bgRoot.wallpaperPath @@ -229,43 +231,20 @@ Variants { WidgetCanvas { id: widgetCanvas - anchors { - left: wallpaper.left - right: wallpaper.right - top: wallpaper.top - bottom: wallpaper.bottom - horizontalCenter: undefined - verticalCenter: undefined - readonly property real parallaxFactor: Config.options.background.parallax.widgetsFactor - Behavior on leftMargin { - animation: Appearance.animation.elementMove.numberAnimation.createObject(this) - } - Behavior on topMargin { - animation: Appearance.animation.elementMove.numberAnimation.createObject(this) - } - } - width: wallpaper.width - height: wallpaper.height - states: State { - name: "centered" - when: GlobalStates.screenLocked || bgRoot.wallpaperSafetyTriggered - PropertyChanges { - target: widgetCanvas - width: parent.width - height: parent.height - } - AnchorChanges { - target: widgetCanvas - anchors { - left: undefined - right: undefined - top: undefined - bottom: undefined - horizontalCenter: parent.horizontalCenter - verticalCenter: parent.verticalCenter - } - } + width: parent.width + height: parent.height + readonly property real parallaxFactor: { + var f = Config.options.background.parallax.widgetsFactor; + return f / Config.options.background.parallax.workspaceZoom; } + readonly property real baseWallpaperOffsetX: (bgRoot.screen.width - bgRoot.scaledWallpaperWidth) / 2 + readonly property real baseWallpaperOffsetY: (bgRoot.screen.height - bgRoot.scaledWallpaperHeight) / 2 + readonly property real wallpaperTotalOffsetX: wallpaper.x - baseWallpaperOffsetX + readonly property real wallpaperTotalOffsetY: wallpaper.y - baseWallpaperOffsetY + readonly property bool locked: GlobalStates.screenLocked + x: wallpaperTotalOffsetX * parallaxFactor * !locked + y: wallpaperTotalOffsetY * parallaxFactor * !locked + transitions: Transition { PropertyAnimation { properties: "width,height" From a06764e111ed030646db131240f9164b8ab102cb Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Fri, 3 Apr 2026 18:50:44 +0200 Subject: [PATCH 11/17] revealer: more correct visible conditions --- dots/.config/quickshell/ii/modules/common/widgets/Revealer.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dots/.config/quickshell/ii/modules/common/widgets/Revealer.qml b/dots/.config/quickshell/ii/modules/common/widgets/Revealer.qml index bbbe2efae..ccd9e5094 100644 --- a/dots/.config/quickshell/ii/modules/common/widgets/Revealer.qml +++ b/dots/.config/quickshell/ii/modules/common/widgets/Revealer.qml @@ -12,7 +12,7 @@ Item { implicitWidth: (reveal || vertical) ? childrenRect.width : 0 implicitHeight: (reveal || !vertical) ? childrenRect.height : 0 - visible: reveal || (width > 0 && height > 0) + visible: reveal || (implicitWidth > 0 && !vertical) || (implicitHeight > 0 && vertical) Behavior on implicitWidth { enabled: !vertical From 347ada664d5a2c86ce295257a1920e5f9d8bfe7c Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Fri, 3 Apr 2026 18:51:06 +0200 Subject: [PATCH 12/17] region selector: repage image after crop --- .../quickshell/ii/modules/common/utils/ScreenshotAction.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dots/.config/quickshell/ii/modules/common/utils/ScreenshotAction.qml b/dots/.config/quickshell/ii/modules/common/utils/ScreenshotAction.qml index 831834bd5..c4eae679f 100644 --- a/dots/.config/quickshell/ii/modules/common/utils/ScreenshotAction.qml +++ b/dots/.config/quickshell/ii/modules/common/utils/ScreenshotAction.qml @@ -32,7 +32,7 @@ Singleton { const rw = Math.round(width); const rh = Math.round(height); const cropBase = `magick ${StringUtils.shellSingleQuoteEscape(screenshotPath)} ` - + `-crop ${rw}x${rh}+${rx}+${ry}` + + `-crop ${rw}x${rh}+${rx}+${ry} +repage` const cropToStdout = `${cropBase} -` const cropInPlace = `${cropBase} '${StringUtils.shellSingleQuoteEscape(screenshotPath)}'` const cleanup = `rm '${StringUtils.shellSingleQuoteEscape(screenshotPath)}'` From eef2b691e77e81fc92348f43e15612101b7a5bd8 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Fri, 3 Apr 2026 18:51:24 +0200 Subject: [PATCH 13/17] lock: refactor error shake anim --- .../common/widgets/ErrorShakeAnimation.qml | 16 ++++++++++++++++ .../ii/modules/ii/lock/LockSurface.qml | 8 ++------ 2 files changed, 18 insertions(+), 6 deletions(-) create mode 100644 dots/.config/quickshell/ii/modules/common/widgets/ErrorShakeAnimation.qml diff --git a/dots/.config/quickshell/ii/modules/common/widgets/ErrorShakeAnimation.qml b/dots/.config/quickshell/ii/modules/common/widgets/ErrorShakeAnimation.qml new file mode 100644 index 000000000..dadfe88c3 --- /dev/null +++ b/dots/.config/quickshell/ii/modules/common/widgets/ErrorShakeAnimation.qml @@ -0,0 +1,16 @@ +pragma ComponentBehavior: Bound +import QtQuick +import QtQuick.Layouts + +SequentialAnimation { + id: root + + required property Item target + property real distance: 30 + + NumberAnimation { target: root.target; property: "Layout.leftMargin"; to: -root.distance; duration: 50 } + NumberAnimation { target: root.target; property: "Layout.leftMargin"; to: root.distance; duration: 50 } + NumberAnimation { target: root.target; property: "Layout.leftMargin"; to: -root.distance / 2; duration: 40 } + NumberAnimation { target: root.target; property: "Layout.leftMargin"; to: root.distance / 2; duration: 40 } + NumberAnimation { target: root.target; property: "Layout.leftMargin"; to: 0; duration: 30 } +} diff --git a/dots/.config/quickshell/ii/modules/ii/lock/LockSurface.qml b/dots/.config/quickshell/ii/modules/ii/lock/LockSurface.qml index 82d10260a..4eb709c06 100644 --- a/dots/.config/quickshell/ii/modules/ii/lock/LockSurface.qml +++ b/dots/.config/quickshell/ii/modules/ii/lock/LockSurface.qml @@ -170,13 +170,9 @@ MouseArea { } // Shake when wrong password - SequentialAnimation { + ErrorShakeAnimation { id: wrongPasswordShakeAnim - NumberAnimation { target: passwordBox; property: "Layout.leftMargin"; to: -30; duration: 50 } - NumberAnimation { target: passwordBox; property: "Layout.leftMargin"; to: 30; duration: 50 } - NumberAnimation { target: passwordBox; property: "Layout.leftMargin"; to: -15; duration: 40 } - NumberAnimation { target: passwordBox; property: "Layout.leftMargin"; to: 15; duration: 40 } - NumberAnimation { target: passwordBox; property: "Layout.leftMargin"; to: 0; duration: 30 } + target: passwordBox } Connections { target: GlobalStates From 59398595a53aa28441820ff7ffc01436203f4afa Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Fri, 3 Apr 2026 18:53:33 +0200 Subject: [PATCH 14/17] refactor toolbar fab --- .../common/widgets/ToolbarPairedFab.qml | 35 +++ .../ii/regionSelector/RegionSelection.qml | 28 +-- .../ii/regionSelector/RegionSelector.qml | 8 - .../WallpaperSelectorContent.qml | 214 ++++++++++-------- 4 files changed, 164 insertions(+), 121 deletions(-) create mode 100644 dots/.config/quickshell/ii/modules/common/widgets/ToolbarPairedFab.qml diff --git a/dots/.config/quickshell/ii/modules/common/widgets/ToolbarPairedFab.qml b/dots/.config/quickshell/ii/modules/common/widgets/ToolbarPairedFab.qml new file mode 100644 index 000000000..fd82d4ea8 --- /dev/null +++ b/dots/.config/quickshell/ii/modules/common/widgets/ToolbarPairedFab.qml @@ -0,0 +1,35 @@ +pragma ComponentBehavior: Bound +import QtQuick +import qs.modules.common + +Item { + id: root + + signal clicked(event: var) + property alias iconText: fabWidget.iconText + default property alias fabData: fabWidget.data + property bool enableShadow: true + + anchors { + verticalCenter: parent.verticalCenter + } + implicitWidth: fabWidget.implicitWidth + implicitHeight: fabWidget.implicitHeight + Loader { + active: root.enableShadow + anchors.fill: parent + sourceComponent: StyledRectangularShadow { + target: fabWidget + radius: fabWidget.buttonRadius + } + } + FloatingActionButton { + id: fabWidget + onClicked: e => root.clicked(e) + baseSize: 48 + colBackground: Appearance.colors.colTertiaryContainer + colBackgroundHover: Appearance.colors.colTertiaryContainerHover + colRipple: Appearance.colors.colTertiaryContainerActive + colOnBackground: Appearance.colors.colOnTertiaryContainer + } +} \ No newline at end of file diff --git a/dots/.config/quickshell/ii/modules/ii/regionSelector/RegionSelection.qml b/dots/.config/quickshell/ii/modules/ii/regionSelector/RegionSelection.qml index 99dc88eda..19435a74c 100644 --- a/dots/.config/quickshell/ii/modules/ii/regionSelector/RegionSelection.qml +++ b/dots/.config/quickshell/ii/modules/ii/regionSelector/RegionSelection.qml @@ -528,28 +528,12 @@ PanelWindow { } onDismiss: root.dismiss(); } - Item { - anchors { - verticalCenter: parent.verticalCenter - } - implicitWidth: closeFab.implicitWidth - implicitHeight: closeFab.implicitHeight - StyledRectangularShadow { - target: closeFab - radius: closeFab.buttonRadius - } - FloatingActionButton { - id: closeFab - baseSize: 48 - iconText: "close" - onClicked: root.dismiss(); - StyledToolTip { - text: Translation.tr("Close") - } - colBackground: Appearance.colors.colTertiaryContainer - colBackgroundHover: Appearance.colors.colTertiaryContainerHover - colRipple: Appearance.colors.colTertiaryContainerActive - colOnBackground: Appearance.colors.colOnTertiaryContainer + ToolbarPairedFab { + anchors.verticalCenter: parent.verticalCenter + iconText: "close" + onClicked: root.dismiss(); + StyledToolTip { + text: Translation.tr("Close") } } } diff --git a/dots/.config/quickshell/ii/modules/ii/regionSelector/RegionSelector.qml b/dots/.config/quickshell/ii/modules/ii/regionSelector/RegionSelector.qml index 61525e527..63ca4ad27 100644 --- a/dots/.config/quickshell/ii/modules/ii/regionSelector/RegionSelector.qml +++ b/dots/.config/quickshell/ii/modules/ii/regionSelector/RegionSelector.qml @@ -1,17 +1,9 @@ pragma ComponentBehavior: Bound import qs import qs.modules.common -import qs.modules.common.functions -import qs.modules.common.widgets -import qs.services import QtQuick -import QtQuick.Controls -import QtQuick.Layouts -import Qt5Compat.GraphicalEffects import Quickshell import Quickshell.Io -import Quickshell.Wayland -import Quickshell.Widgets import Quickshell.Hyprland Scope { diff --git a/dots/.config/quickshell/ii/modules/ii/wallpaperSelector/WallpaperSelectorContent.qml b/dots/.config/quickshell/ii/modules/ii/wallpaperSelector/WallpaperSelectorContent.qml index c237a8854..baea12bdc 100644 --- a/dots/.config/quickshell/ii/modules/ii/wallpaperSelector/WallpaperSelectorContent.qml +++ b/dots/.config/quickshell/ii/modules/ii/wallpaperSelector/WallpaperSelectorContent.qml @@ -17,20 +17,20 @@ MouseArea { property bool useDarkMode: Appearance.m3colors.darkmode function updateThumbnails() { - const totalImageMargin = (Appearance.sizes.wallpaperSelectorItemMargins + Appearance.sizes.wallpaperSelectorItemPadding) * 2 - const thumbnailSizeName = Images.thumbnailSizeNameForDimensions(grid.cellWidth - totalImageMargin, grid.cellHeight - totalImageMargin) - Wallpapers.generateThumbnail(thumbnailSizeName) + const totalImageMargin = (Appearance.sizes.wallpaperSelectorItemMargins + Appearance.sizes.wallpaperSelectorItemPadding) * 2; + const thumbnailSizeName = Images.thumbnailSizeNameForDimensions(grid.cellWidth - totalImageMargin, grid.cellHeight - totalImageMargin); + Wallpapers.generateThumbnail(thumbnailSizeName); } Connections { target: Wallpapers function onDirectoryChanged() { - root.updateThumbnails() + root.updateThumbnails(); } } function handleFilePasting(event) { - const currentClipboardEntry = Cliphist.entries[0] + const currentClipboardEntry = Cliphist.entries[0]; if (/^\d+\tfile:\/\/\S+/.test(currentClipboardEntry)) { const url = StringUtils.cleanCliphistEntry(currentClipboardEntry); Wallpapers.setDirectory(FileUtils.trimFileProtocol(decodeURIComponent(url))); @@ -164,15 +164,48 @@ MouseArea { implicitWidth: 140 clip: true model: [ - { icon: "home", name: "Home", path: Directories.home }, - { icon: "docs", name: "Documents", path: Directories.documents }, - { icon: "download", name: "Downloads", path: Directories.downloads }, - { icon: "image", name: "Pictures", path: Directories.pictures }, - { icon: "movie", name: "Videos", path: Directories.videos }, - { icon: "", name: "---", path: "INTENTIONALLY_INVALID_DIR" }, - { icon: "wallpaper", name: "Wallpapers", path: `${Directories.pictures}/Wallpapers` }, - ...(Config.options.policies.weeb === 1 ? [{ icon: "favorite", name: "Homework", path: `${Directories.pictures}/homework` }] : []), - ] + { + icon: "home", + name: "Home", + path: Directories.home + }, + { + icon: "docs", + name: "Documents", + path: Directories.documents + }, + { + icon: "download", + name: "Downloads", + path: Directories.downloads + }, + { + icon: "image", + name: "Pictures", + path: Directories.pictures + }, + { + icon: "movie", + name: "Videos", + path: Directories.videos + }, + { + icon: "", + name: "---", + path: "INTENTIONALLY_INVALID_DIR" + }, + { + icon: "wallpaper", + name: "Wallpapers", + path: `${Directories.pictures}/Wallpapers` + }, + ...(Config.options.policies.weeb === 1 ? [ + { + icon: "favorite", + name: "Homework", + path: `${Directories.pictures}/homework` + } + ] : []),] delegate: RippleButton { id: quickDirButton required property var modelData @@ -267,7 +300,7 @@ MouseArea { ScrollBar.vertical: StyledScrollBar {} Component.onCompleted: { - root.updateThumbnails() + root.updateThumbnails(); } function moveSelection(delta) { @@ -276,7 +309,7 @@ MouseArea { } function activateCurrent() { - const filePath = grid.model.get(currentIndex, "filePath") + const filePath = grid.model.get(currentIndex, "filePath"); root.selectWallpaperPath(filePath); } @@ -294,7 +327,7 @@ MouseArea { onEntered: { grid.currentIndex = index; } - + onActivated: { root.selectWallpaperPath(fileModelData.filePath); } @@ -310,92 +343,91 @@ MouseArea { } } - Toolbar { + Row { id: extraOptions anchors { bottom: parent.bottom horizontalCenter: parent.horizontalCenter bottomMargin: 8 } + spacing: 6 + Toolbar { - IconToolbarButton { - implicitWidth: height - onClicked: { - Wallpapers.openFallbackPicker(root.useDarkMode); - GlobalStates.wallpaperSelectorOpen = false; - } - altAction: () => { - Wallpapers.openFallbackPicker(root.useDarkMode); - GlobalStates.wallpaperSelectorOpen = false; - Config.options.wallpaperSelector.useSystemFileDialog = true - } - text: "open_in_new" - StyledToolTip { - text: Translation.tr("Use the system file picker instead\nRight-click to make this the default behavior") - } - } - - IconToolbarButton { - implicitWidth: height - onClicked: { - Wallpapers.randomFromCurrentFolder(); - } - text: "ifl" - StyledToolTip { - text: Translation.tr("Pick random from this folder") - } - } - - IconToolbarButton { - implicitWidth: height - onClicked: root.useDarkMode = !root.useDarkMode - text: root.useDarkMode ? "dark_mode" : "light_mode" - StyledToolTip { - text: Translation.tr("Click to toggle light/dark mode\n(applied when wallpaper is chosen)") - } - } - - ToolbarTextField { - id: filterField - placeholderText: focus ? Translation.tr("Search wallpapers") : Translation.tr("Hit \"/\" to search") - - // Style - clip: true - font.pixelSize: Appearance.font.pixelSize.small - - // Search - onTextChanged: { - Wallpapers.searchQuery = text; - } - - Keys.onPressed: event => { - if ((event.modifiers & Qt.ControlModifier) && event.key === Qt.Key_V) { // Intercept Ctrl+V to handle "paste to go to" in pickers - root.handleFilePasting(event); - return; + IconToolbarButton { + implicitWidth: height + onClicked: { + Wallpapers.openFallbackPicker(root.useDarkMode); + GlobalStates.wallpaperSelectorOpen = false; } - else if (text.length !== 0) { - // No filtering, just navigate grid - if (event.key === Qt.Key_Down) { - grid.moveSelection(grid.columns); - event.accepted = true; - return; - } - if (event.key === Qt.Key_Up) { - grid.moveSelection(-grid.columns); - event.accepted = true; - return; - } + altAction: () => { + Wallpapers.openFallbackPicker(root.useDarkMode); + GlobalStates.wallpaperSelectorOpen = false; + Config.options.wallpaperSelector.useSystemFileDialog = true; + } + text: "open_in_new" + StyledToolTip { + text: Translation.tr("Use the system file picker instead\nRight-click to make this the default behavior") + } + } + + IconToolbarButton { + implicitWidth: height + onClicked: { + Wallpapers.randomFromCurrentFolder(); + } + text: "ifl" + StyledToolTip { + text: Translation.tr("Pick random from this folder") + } + } + + IconToolbarButton { + implicitWidth: height + onClicked: root.useDarkMode = !root.useDarkMode + text: root.useDarkMode ? "dark_mode" : "light_mode" + StyledToolTip { + text: Translation.tr("Click to toggle light/dark mode\n(applied when wallpaper is chosen)") + } + } + + ToolbarTextField { + id: filterField + placeholderText: focus ? Translation.tr("Search wallpapers") : Translation.tr("Hit \"/\" to search") + + // Style + clip: true + font.pixelSize: Appearance.font.pixelSize.small + + // Search + onTextChanged: { + Wallpapers.searchQuery = text; + } + + Keys.onPressed: event => { + if ((event.modifiers & Qt.ControlModifier) && event.key === Qt.Key_V) { // Intercept Ctrl+V to handle "paste to go to" in pickers + root.handleFilePasting(event); + return; + } else if (text.length !== 0) { + // No filtering, just navigate grid + if (event.key === Qt.Key_Down) { + grid.moveSelection(grid.columns); + event.accepted = true; + return; + } + if (event.key === Qt.Key_Up) { + grid.moveSelection(-grid.columns); + event.accepted = true; + return; + } + } + event.accepted = false; } - event.accepted = false; } } - IconToolbarButton { - implicitWidth: height - onClicked: { - GlobalStates.wallpaperSelectorOpen = false; - } - text: "close" + ToolbarPairedFab { + iconText: "close" + onClicked: GlobalStates.wallpaperSelectorOpen = false; StyledToolTip { text: Translation.tr("Cancel wallpaper selection") } From ec3b92a9388bde84f485cd4b523bb229fea23410 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Fri, 3 Apr 2026 18:53:50 +0200 Subject: [PATCH 15/17] add MultiTurnProcess --- .../modules/common/utils/MultiTurnProcess.qml | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 dots/.config/quickshell/ii/modules/common/utils/MultiTurnProcess.qml diff --git a/dots/.config/quickshell/ii/modules/common/utils/MultiTurnProcess.qml b/dots/.config/quickshell/ii/modules/common/utils/MultiTurnProcess.qml new file mode 100644 index 000000000..9999b93ad --- /dev/null +++ b/dots/.config/quickshell/ii/modules/common/utils/MultiTurnProcess.qml @@ -0,0 +1,46 @@ +pragma ComponentBehavior: Bound +import QtQuick +import Quickshell.Io + +Process { + id: proc + + signal finished(string output) + + property list sequence: [] + property int index: 0 + + // Runs a sequence of command and functions + function runSequence(seq) { + if (seq.length == 0) + return; + sequence = seq; + running = false; + index = -1; + step(); + } + + function step(output) { + index++; + if (index >= sequence.length) { + finished(output); + return; + } + const nextItem = sequence[index]; + if (typeof nextItem === "function") { + const nextOutput = nextItem(output); + step(nextOutput); + } else { + // If empty command then not set; this allows setting it with previous function + if (nextItem.length > 0) + command = nextItem; + running = true; + } + } + + stdout: StdioCollector { + onStreamFinished: { + proc.step(text); + } + } +} From 64b52f6a908138e91b1a800aed68993db69a7ff0 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Fri, 3 Apr 2026 19:31:24 +0200 Subject: [PATCH 16/17] add screen translation --- dots/.config/quickshell/ii/GlobalStates.qml | 1 + .../common/models/gCloud/GCloudTranslate.qml | 69 ++++ .../common/models/gCloud/GCloudVision.qml | 93 ++++++ .../models/gCloud/GCloudVisionResult.qml | 90 +++++ .../common/widgets/MaskMultiEffect.qml | 9 + .../widgets/SqueezedAnnotationStyledText.qml | 74 +++++ .../ii/screenTranslator/ScreenTextOverlay.qml | 313 ++++++++++++++++++ .../ii/screenTranslator/ScreenTranslator.qml | 41 +++ .../ScreenTranslatorPanel.qml | 225 +++++++++++++ .../panelFamilies/IllogicalImpulseFamily.qml | 2 + .../ii/panelFamilies/WaffleFamily.qml | 2 + .../ii/scripts/images/text-color-venv.sh | 6 + .../ii/scripts/images/text_color.py | 60 ++++ .../quickshell/ii/services/GoogleCloud.qml | 93 ++++++ .../quickshell/ii/services/KeyringStorage.qml | 3 + .../ii/services/gCloud/token-from-key-venv.sh | 6 + .../ii/services/gCloud/token_from_key.py | 30 ++ sdata/uv/requirements.in | 2 + sdata/uv/requirements.txt | 60 ++-- 19 files changed, 1159 insertions(+), 20 deletions(-) create mode 100644 dots/.config/quickshell/ii/modules/common/models/gCloud/GCloudTranslate.qml create mode 100644 dots/.config/quickshell/ii/modules/common/models/gCloud/GCloudVision.qml create mode 100644 dots/.config/quickshell/ii/modules/common/models/gCloud/GCloudVisionResult.qml create mode 100644 dots/.config/quickshell/ii/modules/common/widgets/MaskMultiEffect.qml create mode 100644 dots/.config/quickshell/ii/modules/common/widgets/SqueezedAnnotationStyledText.qml create mode 100644 dots/.config/quickshell/ii/modules/ii/screenTranslator/ScreenTextOverlay.qml create mode 100644 dots/.config/quickshell/ii/modules/ii/screenTranslator/ScreenTranslator.qml create mode 100644 dots/.config/quickshell/ii/modules/ii/screenTranslator/ScreenTranslatorPanel.qml create mode 100755 dots/.config/quickshell/ii/scripts/images/text-color-venv.sh create mode 100755 dots/.config/quickshell/ii/scripts/images/text_color.py create mode 100644 dots/.config/quickshell/ii/services/GoogleCloud.qml create mode 100755 dots/.config/quickshell/ii/services/gCloud/token-from-key-venv.sh create mode 100755 dots/.config/quickshell/ii/services/gCloud/token_from_key.py diff --git a/dots/.config/quickshell/ii/GlobalStates.qml b/dots/.config/quickshell/ii/GlobalStates.qml index ba680220b..bfb531e6e 100644 --- a/dots/.config/quickshell/ii/GlobalStates.qml +++ b/dots/.config/quickshell/ii/GlobalStates.qml @@ -24,6 +24,7 @@ Singleton { property bool screenLocked: false property bool screenLockContainsCharacters: false property bool screenUnlockFailed: false + property bool screenTranslatorOpen: false property bool sessionOpen: false property bool superDown: false property bool superReleaseMightTrigger: true diff --git a/dots/.config/quickshell/ii/modules/common/models/gCloud/GCloudTranslate.qml b/dots/.config/quickshell/ii/modules/common/models/gCloud/GCloudTranslate.qml new file mode 100644 index 000000000..d117e8d3a --- /dev/null +++ b/dots/.config/quickshell/ii/modules/common/models/gCloud/GCloudTranslate.qml @@ -0,0 +1,69 @@ +pragma ComponentBehavior: Bound +import QtQuick +import qs.modules.common.functions +import qs.modules.common.utils +import qs.services +import ".." + +NestableObject { + id: root + + enum State { + Done, Preparing, Processing + } + + signal finished() + property var outputData + property var state: GCloudTranslate.State.Done + + property list pendingStrings + property bool setupReady: false + readonly property bool preparationReady: GoogleCloud.tokenReady && setupReady + + function translateStrings(strings: list) { + GoogleCloud.load(); + root.setupReady = false; + root.pendingStrings = strings; + root.state = GCloudTranslate.State.Preparing; + root.setupReady = true; + } + + onPreparationReadyChanged: { + if (!preparationReady) return; + root.state = GCloudTranslate.State.Processing; + + const targetLang = Translation.languageCode; + const payload = { + "targetLanguageCode": targetLang, + "contents": root.pendingStrings, + "mimeType": "text/plain" + }; + + // print("PENDING STRINGS:", root.pendingStrings) + + var seq = []; + seq.push([ // + "bash", "-c", // + `curl -sL -X POST \ +-H "Authorization: Bearer ${GoogleCloud.token}" \ +-H "x-goog-user-project: ${GoogleCloud.projectId}" \ +-H "Content-Type: application/json" \ +-d '${StringUtils.shellSingleQuoteEscape(JSON.stringify(payload))}' \ +"https://translation.googleapis.com/v3/projects/${GoogleCloud.projectId}:translateText"` + ]); + + seq.push(((out) => { + // print(out) + root.outputData = JSON.parse(out); + root.pendingStrings = []; + root.finished(); + root.state = GCloudTranslate.State.Done; + })); + + multiproc.runSequence(seq); + } + + MultiTurnProcess { + id: multiproc + } +} diff --git a/dots/.config/quickshell/ii/modules/common/models/gCloud/GCloudVision.qml b/dots/.config/quickshell/ii/modules/common/models/gCloud/GCloudVision.qml new file mode 100644 index 000000000..3e02fd561 --- /dev/null +++ b/dots/.config/quickshell/ii/modules/common/models/gCloud/GCloudVision.qml @@ -0,0 +1,93 @@ +pragma ComponentBehavior: Bound +import QtQuick +import Quickshell +import qs.modules.common.functions +import qs.modules.common.utils +import qs.services +import qs.modules.common +import ".." + +NestableObject { + id: root + + enum State { + Done, Uploading, Processing, Error + } + + signal finished() + signal error() + property var outputData + property var state: GCloudVision.State.Done + + readonly property string imageBase64FilePath: `${Directories.screenshotTemp}/vision_base64.txt` + readonly property string payloadFilePath: `${Directories.screenshotTemp}/vision_payload.json` + property string uploadEndpoint: "https://uguu.se/upload" + + property bool tokenReady: GoogleCloud.tokenReady + property bool onlineImageReady: false + readonly property bool preparationReady: tokenReady && onlineImageReady + + function annotateImage(imageUri: string) { + root.state = GCloudVision.State.Uploading; + root.onlineImageReady = false + GoogleCloud.load(); + + var seq = []; // command sequence + + const niceFilePath = StringUtils.shellSingleQuoteEscape(FileUtils.trimFileProtocol(imageUri)) + seq = [ // + ["bash", "-c", `mkdir -p '${Directories.screenshotTemp}'; base64 '${niceFilePath}' -w 0 > '${imageBase64FilePath}'`], // + (out) => { // + root.onlineImageReady = true; // + } + ] + + // Execute the base64 conversion & load the token + prepMultiproc.runSequence(seq); + } + + onPreparationReadyChanged: { + if (!preparationReady) return; + if (GoogleCloud.tokenError || GoogleCloud.keyError) { + root.state = GCloudVision.State.Error; + root.error(); + return; + } + root.state = GCloudVision.State.Processing; + var seq = []; // command sequence + + // Construct the JSON payload using jq to read from the base64 file + seq.push([ + "bash", "-c", + `jq -n --rawfile content '${imageBase64FilePath}' \ +'{"requests": [{"image": {"content": $content}, "features": [{"type": "DOCUMENT_TEXT_DETECTION"}]}]}' \ +> '${payloadFilePath}'` + ]); + + seq.push([ + "bash", "-c", + `curl -s -X POST \ +-H "Authorization: Bearer ${GoogleCloud.token}" \ +-H "x-goog-user-project: ${GoogleCloud.projectId}" \ +-H "Content-Type: application/json" \ +https://vision.googleapis.com/v1/images:annotate \ +-d @'${payloadFilePath}'` + ]); + + seq.push((out) => { + root.outputData = JSON.parse(out); + root.finished(); + root.state = GCloudVision.State.Done; + }); + + lookMultiproc.runSequence(seq); + } + + MultiTurnProcess { + id: prepMultiproc + } + + MultiTurnProcess { + id: lookMultiproc + } +} diff --git a/dots/.config/quickshell/ii/modules/common/models/gCloud/GCloudVisionResult.qml b/dots/.config/quickshell/ii/modules/common/models/gCloud/GCloudVisionResult.qml new file mode 100644 index 000000000..d0794b37a --- /dev/null +++ b/dots/.config/quickshell/ii/modules/common/models/gCloud/GCloudVisionResult.qml @@ -0,0 +1,90 @@ +pragma ComponentBehavior: Bound +import QtQuick +import ".." + +NestableObject { + id: root + + property real confidenceThreshold: 0.5 // TODO tune this + + property var rawData + property var rawBlocks + property var rawParagraphs + property var coherentParagraphs + + function initializeWithData(apiOutputData: var): void { + // Null check + if (!apiOutputData) { + print("[GCloudVisionResult] Data is null/undefined") + return; + } + + // Raw data + root.rawData = apiOutputData + + // Raw blocks + var pages = apiOutputData.responses[0].fullTextAnnotation.pages + var blocks = []; + for (var i = 0; i < pages.length; i++) { + // print("this page", JSON.stringify(pages[i])) + var blocksThisPage = pages[i].blocks; + for (var j = 0; j < blocksThisPage.length; j++) { + const block = blocksThisPage[j]; + // print("new block with confidence", block.confidence, ":", JSON.stringify(block, null, 2)) + if (block.confidence > root.confidenceThreshold) { + blocks.push(block); + } + } + } + + root.rawBlocks = blocks + // print("RAW BLOCKS:", blocks) + + // Raw paragraphs + var paragraphs = [] + for (var i = 0; i < blocks.length; i++) { + var blockParagraphs = blocks[i].paragraphs; + for (var j = 0; j < blockParagraphs.length; j++) { + const para = blockParagraphs[j]; + // print("new paragraph", JSON.stringify(para)) + paragraphs.push(para); + } + } + root.rawParagraphs = [...paragraphs]; + + // print("RAW PARAGRAPHS", paragraphs) + + // Coherent paragraphs + // (raw data can be as granular as symbols) + // We're interested in paragraph level of granularity as it's good for translations + for (var i = 0; i < paragraphs.length; i++) { + const paragraph = paragraphs[i]; + const words = paragraph.words; + var strList = [] + for (var j = 0; j < words.length; j++) { + const symbols = words[j].symbols; + for (var k = 0; k < symbols.length; k++) { + const sym = symbols[k]; + strList.push(sym.text); + // print("CHAR:", JSON.stringify(sym, null, 2)); + // Breaks + // Reference: https://docs.cloud.google.com/vision/docs/reference/rpc/google.cloud.vision.v1#breaktype + if (sym.property?.detectedBreak.type == "SPACE" || sym.property?.detectedBreak.type == "UNKNOWN") { + strList.push(" "); + } else if (sym.property?.detectedBreak.type == "SURE_SPACE") { + strList.push(" "); + } else if (sym.property?.detectedBreak.type == "EOL_SURE_SPACE" || sym.property?.detectedBreak.type == "LINE_BREAK") { + strList.push("\n"); + } else if (sym.property?.detectedBreak.type == "HYPHEN") { + strList.push("-\n"); + } + } + } + // print("STR LIST:", strList) + paragraphs[i].text = strList.join("").trim(); + // print("PARA TEXT:", paragraphs[i].text) + } + root.coherentParagraphs = paragraphs + // print("COHERENT PARAGRAPHS", JSON.stringify(paragraphs)) + } +} diff --git a/dots/.config/quickshell/ii/modules/common/widgets/MaskMultiEffect.qml b/dots/.config/quickshell/ii/modules/common/widgets/MaskMultiEffect.qml new file mode 100644 index 000000000..18e2177d4 --- /dev/null +++ b/dots/.config/quickshell/ii/modules/common/widgets/MaskMultiEffect.qml @@ -0,0 +1,9 @@ +import QtQuick +import QtQuick.Effects + +// Note: You still have to set sizes yourself +MultiEffect { + maskEnabled: true + maskThresholdMin: 0.5 + maskSpreadAtMin: 1 +} diff --git a/dots/.config/quickshell/ii/modules/common/widgets/SqueezedAnnotationStyledText.qml b/dots/.config/quickshell/ii/modules/common/widgets/SqueezedAnnotationStyledText.qml new file mode 100644 index 000000000..25990cd42 --- /dev/null +++ b/dots/.config/quickshell/ii/modules/common/widgets/SqueezedAnnotationStyledText.qml @@ -0,0 +1,74 @@ +pragma ComponentBehavior: Bound +import QtQuick +import qs.modules.common + +// Annotation similar to how Google Lens does it. +Item { + id: root + + property real scaleFactor: 1.0 + property alias font: textWidget.font + property alias color: textWidget.color + property string text: "" + + property bool rotate90: false + property real maxFontPixelSize: 100 + visible: false + + Component.onCompleted: updateText() + onTextChanged: updateText() + + property bool searching: false + property real searchPixelSize: Appearance.font.pixelSize.small + property real renderPixelSize: Appearance.font.pixelSize.small + font.pixelSize: searching ? searchPixelSize : (renderPixelSize * scaleFactor) + + function updateText() { + // Do we rotate? + + root.rotate90 = false; + const textAspectRatio = textMetrics.width / textMetrics.height + const areaAspectRatio = root.width / root.height + if ((textAspectRatio > 1 && areaAspectRatio < 1) || (textAspectRatio < 1 && areaAspectRatio > 1)) { + root.rotate90 = true; + } + const targetWidth = (root.rotate90 ? root.height : root.width) / root.scaleFactor; + const targetHeight = (root.rotate90 ? root.width : root.height) / root.scaleFactor; + + // Binary search to find the correct font size + var lower = 0 + var upper = maxFontPixelSize + root.searching = true; + while (upper - lower > 0.00001) { + var mid = (lower + upper) / 2; + // print("bin searching", mid, "target", targetWidth, targetHeight, "actual", textWidget.contentWidth, textWidget.contentHeight); + root.searchPixelSize = mid + if (textWidget.contentHeight > targetHeight) { + upper = mid + } else { + lower = mid + } + } + root.renderPixelSize = lower + root.searching = false; + root.visible = true + } + + TextMetrics { + id: textMetrics + text: root.text + font: root.font + } + + StyledText { + id: textWidget + + anchors.centerIn: parent + width: root.rotate90 ? parent.height : parent.width + text: root.text + rotation: root.rotate90 ? 90 : 0 + + renderType: Text.QtRendering + wrapMode: Text.Wrap + } +} diff --git a/dots/.config/quickshell/ii/modules/ii/screenTranslator/ScreenTextOverlay.qml b/dots/.config/quickshell/ii/modules/ii/screenTranslator/ScreenTextOverlay.qml new file mode 100644 index 000000000..cd1c6a202 --- /dev/null +++ b/dots/.config/quickshell/ii/modules/ii/screenTranslator/ScreenTextOverlay.qml @@ -0,0 +1,313 @@ +pragma ComponentBehavior: Bound + +import QtQuick +import QtQuick.Effects +import Qt5Compat.GraphicalEffects +import Quickshell + +import qs +import qs.modules.common +import qs.modules.common.functions +import qs.modules.common.models.gCloud +import qs.modules.common.utils +import qs.modules.common.widgets +import qs.services + +Item { + id: root + + property double scaleFactor: 1 + property color overlayColor: "#BB000000" + property color textColor: "white" + required property string screenshotPath + + readonly property string wikiLink: "https://ii.clsty.link/en/ii-qs/02usage/#setting-it-up" // TODO: write a page for this + readonly property string textColorDetectionScriptPath: Quickshell.shellPath("scripts/images/text-color-venv.sh") + + property bool loading: true + property var visionParagraphs: [] + property list translationKeys: [] + property var translation: ({}) + + function translate(s: string): string { + return translation[s] ?? s; + } + + property bool error: false + function showError() { + error = true; + } + + Component.onCompleted: { + if (GoogleCloud.tokenReady && GoogleCloud.tokenError) { + root.showError(); + } + cloudVision.annotateImage(screenshotPath); + } + + Connections { + target: GoogleCloud + function onTokenChanged() { + if (GoogleCloud.tokenReady && !GoogleCloud.tokenError) { + root.error = false; + cloudVision.annotateImage(root.screenshotPath); + } + } + } + + Rectangle { + id: loadingOverlay + anchors.fill: parent + opacity: root.loading ? 1 : 0 + Behavior on opacity { + animation: Appearance.animation.elementMoveSmall.numberAnimation.createObject(this) + } + color: root.overlayColor + + Column { + visible: !root.error + anchors.centerIn: parent + spacing: 10 * root.scaleFactor + MaterialLoadingIndicator { + anchors.horizontalCenter: parent.horizontalCenter + implicitSize: 100 * root.scaleFactor + scale: 1 + ((1 - loadingOverlay.opacity) * 0.5) * root.scaleFactor + } + StyledText { + anchors.horizontalCenter: parent.horizontalCenter + text: { + if (cloudVision.state == GCloudVision.State.Uploading) + return Translation.tr("Uploading image"); + else if (cloudVision.state == GCloudVision.State.Processing) + return Translation.tr("Reading image"); + else if (cloudVision.state == GCloudVision.State.Error) + return Translation.tr("Error"); + else if (cloudTrans.state == GCloudTranslate.State.Preparing) + return Translation.tr("Getting ready to translate"); + else if (cloudTrans.state == GCloudTranslate.State.Processing) + return Translation.tr("Translating"); + else + return " "; + } + font.pixelSize: Appearance.font.pixelSize.small * root.scaleFactor + animateChange: true + color: root.textColor + } + } + + Column { + visible: root.error + anchors.centerIn: parent + spacing: 10 * root.scaleFactor + + MaterialShapeWrappedMaterialSymbol { + anchors.horizontalCenter: parent.horizontalCenter + text: "exclamation" + iconSize: 80 * root.scaleFactor + padding: 6 * root.scaleFactor + color: Appearance.colors.colError + colSymbol: Appearance.colors.colOnError + shape: MaterialShape.Shape.Sunny + } + StyledText { + anchors.horizontalCenter: parent.horizontalCenter + horizontalAlignment: Text.AlignHCenter + textFormat: Text.MarkdownText + text: `**${Translation.tr("Screen Translator")}**\n\n${Translation.tr("Set your Google Cloud service account key")}\n\n__[${Translation.tr("See how on the wiki")}](${root.wikiLink})__` + font.pixelSize: Appearance.font.pixelSize.small * root.scaleFactor + color: root.textColor + MouseArea { + anchors.fill: parent + cursorShape: Qt.PointingHandCursor + onClicked: { + Qt.openUrlExternally(root.wikiLink) + GlobalStates.screenTranslatorOpen = false + } + } + } + } + } + + GCloudVisionResult { + id: gcr + } + + GCloudVision { + id: cloudVision + onError: { + root.showError(); + } + onFinished: { + gcr.initializeWithData(outputData); + root.visionParagraphs = gcr.coherentParagraphs; + // print(gcr.coherentParagraphs) + root.translationKeys = gcr.coherentParagraphs.map(p => p.text); + // print("TRANSLATION KEYS:", JSON.stringify(root.translationKeys)); + cloudTrans.translateStrings(root.translationKeys); + } + } + + GCloudTranslate { + id: cloudTrans + onFinished: { + var values = outputData.translations.map(translation => translation.translatedText); + const keys = root.translationKeys; + root.translation = ({}); + for (var i = 0; i < keys.length; i++) { + Object.assign(root.translation, { + [keys[i]]: values[i] + }); + } + // print("TRANSLATION:", JSON.stringify(root.translation)); + root.loading = false; + } + } + + property real windowWidth: QsWindow.window.screen.width + property real windowHeight: QsWindow.window.screen.height + + StyledImage { + id: screenshotImage + z: 1 + asynchronous: false + width: root.windowWidth + height: root.windowHeight + sourceSize: Qt.size(root.windowWidth, root.windowHeight) + source: Qt.resolvedUrl(root.screenshotPath) + visible: false + } + + Item { + id: blurMaskItem + z: 2 + width: root.windowWidth + height: root.windowHeight + layer.enabled: true + visible: false + Repeater { + model: root.loading ? [] : root.visionParagraphs + delegate: VisionBoundingBoxRect { + readonly property string text: modelData.text + readonly property string translatedText: root.translate(text) + visible: translatedText != text + scaleFactor: 1 + } + } + } + + // I no longer need these but they were a fucking pain in the ass to figure out so they're staying + // GaussianBlur { + // id: blurredImage + // z: 3 + // width: root.windowWidth + // height: root.windowHeight + // transformOrigin: Item.TopLeft + // scale: root.scaleFactor + // source: screenshotImage + // radius: 10 + // samples: radius * 2 + 1 + // visible: false + // } + // MultiEffect { + // id: blurredImage + // z: 3 + // source: screenshotImage + // width: root.windowWidth + // height: root.windowHeight + // transformOrigin: Item.TopLeft + // scale: root.scaleFactor + + // blurEnabled: true + // blur: 1 + // blurMax: 64 + // visible: false + // } + + MaskMultiEffect { + z: 4 + implicitWidth: parent.width + implicitHeight: parent.height + width: parent.width + height: parent.height + + // Mask + source: screenshotImage + maskSource: blurMaskItem + + // Blur + blurEnabled: true + blur: 1 + blurMax: 50 + blurMultiplier: root.scaleFactor + autoPaddingEnabled: false + } + + Item { + id: textItems + z: 999 + Repeater { + model: root.loading ? [] : root.visionParagraphs + // An entry looks like this: + delegate: TextItem {} + } + } + + component VisionBoundingBoxRect: Rectangle { + required property var modelData + property real scaleFactor: root.scaleFactor + property list boundingVertices: modelData.boundingBox.vertices + property real unscaledX: boundingVertices[0].x + property real unscaledY: boundingVertices[0].y + property real unscaledWidth: boundingVertices[1].x - boundingVertices[0].x + property real unscaledHeight: boundingVertices[3].y - boundingVertices[0].y + x: unscaledX * scaleFactor + y: unscaledY * scaleFactor + width: unscaledWidth * scaleFactor + height: unscaledHeight * scaleFactor + radius: 4 + } + + component TextItem: VisionBoundingBoxRect { + id: ti + // {"boundingPoly": {"vertices": [{"x": 536,"y": 236},{"x": 583,"y": 236},{"x": 583,"y": 262},{"x": 536,"y": 262}]},"description": "宮坂"} + readonly property string text: modelData.text + readonly property string translatedText: root.translate(text) + visible: translatedText != text + + color: ColorUtils.transparentize(Appearance.colors.colSecondaryContainer, 0.4) + Behavior on color { + animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this) + } + + Loader { + active: ti.visible + sourceComponent: MultiTurnProcess { + Component.onCompleted: { + runSequence([ // + [ // + "bash", "-c", // + `magick ${StringUtils.shellSingleQuoteEscape(root.screenshotPath)} +repage -crop ${StringUtils.shellSingleQuoteEscape(ti.unscaledWidth)}x${StringUtils.shellSingleQuoteEscape(ti.unscaledHeight)}+${StringUtils.shellSingleQuoteEscape(ti.unscaledX)}+${StringUtils.shellSingleQuoteEscape(ti.unscaledY)} png:- | ${root.textColorDetectionScriptPath}` + ], + (out => { + var colorData = JSON.parse(out); + ti.color = ColorUtils.transparentize(colorData.background, 0.4); + tiText.color = colorData.text; + }) + ]); + } + } + } + + SqueezedAnnotationStyledText { + id: tiText + width: parent.width + height: parent.height + text: ti.translatedText + scaleFactor: root.scaleFactor + + Behavior on color { + animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this) + } + } + } +} diff --git a/dots/.config/quickshell/ii/modules/ii/screenTranslator/ScreenTranslator.qml b/dots/.config/quickshell/ii/modules/ii/screenTranslator/ScreenTranslator.qml new file mode 100644 index 000000000..39db82909 --- /dev/null +++ b/dots/.config/quickshell/ii/modules/ii/screenTranslator/ScreenTranslator.qml @@ -0,0 +1,41 @@ +pragma ComponentBehavior: Bound +import qs +import QtQuick +import Quickshell +import Quickshell.Io +import Quickshell.Hyprland + +Scope { + id: root + + function dismiss() { + GlobalStates.screenTranslatorOpen = false + } + + Loader { + id: translatorLoader + active: GlobalStates.screenTranslatorOpen + + sourceComponent: ScreenTranslatorPanel { + onDismiss: root.dismiss() + } + } + + function translate() { + GlobalStates.screenTranslatorOpen = true + } + + IpcHandler { + target: "screenTranslator" + + function translate() { + root.translate() + } + } + + GlobalShortcut { + name: "screenTranslate" + description: "Translates screen content" + onPressed: root.translate() + } +} diff --git a/dots/.config/quickshell/ii/modules/ii/screenTranslator/ScreenTranslatorPanel.qml b/dots/.config/quickshell/ii/modules/ii/screenTranslator/ScreenTranslatorPanel.qml new file mode 100644 index 000000000..ac4070ede --- /dev/null +++ b/dots/.config/quickshell/ii/modules/ii/screenTranslator/ScreenTranslatorPanel.qml @@ -0,0 +1,225 @@ +pragma ComponentBehavior: Bound + +import QtQuick +import QtQuick.Layouts +import Quickshell +import Quickshell.Wayland + +import qs.modules.common +import qs.modules.common.utils +import qs.modules.common.widgets +import qs.services + +PanelWindow { + id: root + + // Interface + signal dismiss + + // Window props + visible: false + // color: Appearance.colors.colLayer0 + color: "black" + WlrLayershell.namespace: "quickshell:regionSelector" + WlrLayershell.layer: WlrLayer.Overlay + WlrLayershell.keyboardFocus: WlrKeyboardFocus.OnDemand + exclusionMode: ExclusionMode.Ignore + anchors { + left: true + right: true + top: true + bottom: true + } + + // Config + readonly property string screenshotDir: Directories.screenshotTemp + readonly property string screenshotPath: `${root.screenshotDir}/image-${screen.name}` + + // Preparation + property bool screenshotReady: false + + function performTranslation() { + screenshotReady = true; + } + + TempScreenshotProcess { + id: screenshotProc + running: true + screen: root.screen + screenshotDir: root.screenshotDir + screenshotPath: root.screenshotPath + onExited: (_, __) => { + root.visible = true; + root.performTranslation(); + } + } + + // Actual content + property real scale: 1.0 + property real contentX: 0 + property real contentY: 0 + + MouseArea { + anchors.fill: parent + clip: true + + property real lastX: 0 + property real lastY: 0 + + cursorShape: Qt.SizeAllCursor + + onPressed: mouse => { + lastX = mouse.x; + lastY = mouse.y; + } + + onPositionChanged: mouse => { + if (pressed) { + root.contentX += (mouse.x - lastX); + root.contentY += (mouse.y - lastY); + lastX = mouse.x; + lastY = mouse.y; + } + } + + onWheel: event => { + const zoomFactor = event.angleDelta.y > 0 ? 1.1 : 0.9; + const oldScale = root.scale; + const newScale = Math.min(Math.max(0.1, oldScale * zoomFactor), 5); + + if (newScale !== oldScale) { + // Determine mouse position relative to the content's unscaled origin + const localX = (event.x - root.contentX) / oldScale; + const localY = (event.y - root.contentY) / oldScale; + + // Apply zoom + root.scale = newScale; + + // Shift offsets to keep the same local point under the cursor + root.contentX = event.x - (localX * newScale); + root.contentY = event.y - (localY * newScale); + } + } + + ScreencopyView { // Freeze screen + id: screencopy + width: parent.width + height: parent.height + + x: root.contentX + y: root.contentY + scale: root.scale + transformOrigin: Item.TopLeft + + live: false + captureSource: root.screen + } + + Loader { + width: parent.width * root.scale + height: parent.height * root.scale + + x: root.contentX + y: root.contentY + + active: root.screenshotReady + sourceComponent: ScreenTextOverlay { + screenshotPath: root.screenshotPath + scaleFactor: root.scale + } + } + } + + Row { + anchors { + horizontalCenter: parent.horizontalCenter + bottom: parent.bottom + bottomMargin: -height + } + Behavior on anchors.bottomMargin { + animation: Appearance.animation.elementMove.numberAnimation.createObject(this) + } + Component.onCompleted: { + anchors.bottomMargin = 8; + } + + spacing: 6 + + Toolbar { + id: toolbar + focus: root.visible + Keys.onPressed: event => { // Esc to close + if (event.key === Qt.Key_Escape) { + root.dismiss(); + } + } + spacing: 0 + + IconToolbarButton { + id: sleepButton + onClicked: { + toggled = !toggled + if (toggled) keyInput.forceActiveFocus() + } + text: "key" + + StyledToolTip { + z: 9999 + text: Translation.tr("Key input") + } + } + + Revealer { + reveal: sleepButton.toggled + Layout.fillHeight: true + + RowLayout { + anchors.left: parent.left + spacing: 6 + Item {} // extra padding + ToolbarTextField { + id: keyInput + implicitWidth: 400 + placeholderText: Translation.tr("Paste service account key JSON here") + inputMethodHints: Qt.ImhSensitiveData + onAccepted: submit() + + function submit() { + const success = GoogleCloud.setKeyJson(text); + if (!success) { + invalidJsonAnimation.restart(); + } else { + text = ""; + sleepButton.toggled = false; + } + } + + ErrorShakeAnimation { + id: invalidJsonAnimation + target: keyInput + } + } + IconToolbarButton { + id: submitButton + onClicked: keyInput.submit() + text: "check" + toggled: keyInput.text.length > 0 + + StyledToolTip { + z: 9999 + text: Translation.tr("Confirm") + } + } + } + } + } + + ToolbarPairedFab { + iconText: "close" + onClicked: root.dismiss() + StyledToolTip { + text: Translation.tr("Close") + } + } + } +} diff --git a/dots/.config/quickshell/ii/panelFamilies/IllogicalImpulseFamily.qml b/dots/.config/quickshell/ii/panelFamilies/IllogicalImpulseFamily.qml index f4ffda651..61a901fa2 100644 --- a/dots/.config/quickshell/ii/panelFamilies/IllogicalImpulseFamily.qml +++ b/dots/.config/quickshell/ii/panelFamilies/IllogicalImpulseFamily.qml @@ -15,6 +15,7 @@ import qs.modules.ii.overview import qs.modules.ii.polkit import qs.modules.ii.regionSelector import qs.modules.ii.screenCorners +import qs.modules.ii.screenTranslator import qs.modules.ii.sessionScreen import qs.modules.ii.sidebarLeft import qs.modules.ii.sidebarRight @@ -37,6 +38,7 @@ Scope { PanelLoader { component: Polkit {} } PanelLoader { component: RegionSelector {} } PanelLoader { component: ScreenCorners {} } + PanelLoader { component: ScreenTranslator {} } PanelLoader { component: SessionScreen {} } PanelLoader { component: SidebarLeft {} } PanelLoader { component: SidebarRight {} } diff --git a/dots/.config/quickshell/ii/panelFamilies/WaffleFamily.qml b/dots/.config/quickshell/ii/panelFamilies/WaffleFamily.qml index 67d35de55..1953703b4 100644 --- a/dots/.config/quickshell/ii/panelFamilies/WaffleFamily.qml +++ b/dots/.config/quickshell/ii/panelFamilies/WaffleFamily.qml @@ -20,6 +20,7 @@ import qs.modules.waffle.taskView import qs.modules.ii.cheatsheet import qs.modules.ii.onScreenKeyboard import qs.modules.ii.overlay +import qs.modules.ii.screenTranslator import qs.modules.ii.wallpaperSelector Scope { @@ -40,5 +41,6 @@ Scope { PanelLoader { component: Cheatsheet {} } PanelLoader { component: OnScreenKeyboard {} } PanelLoader { component: Overlay {} } + PanelLoader { component: ScreenTranslator {} } PanelLoader { component: WallpaperSelector {} } } diff --git a/dots/.config/quickshell/ii/scripts/images/text-color-venv.sh b/dots/.config/quickshell/ii/scripts/images/text-color-venv.sh new file mode 100755 index 000000000..f673bd687 --- /dev/null +++ b/dots/.config/quickshell/ii/scripts/images/text-color-venv.sh @@ -0,0 +1,6 @@ +#!/usr/bin/env bash +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +source $(eval echo $ILLOGICAL_IMPULSE_VIRTUAL_ENV)/bin/activate +"$SCRIPT_DIR/text_color.py" "$@" +deactivate diff --git a/dots/.config/quickshell/ii/scripts/images/text_color.py b/dots/.config/quickshell/ii/scripts/images/text_color.py new file mode 100755 index 000000000..3fcff8d10 --- /dev/null +++ b/dots/.config/quickshell/ii/scripts/images/text_color.py @@ -0,0 +1,60 @@ +#!/usr/bin/env python3 +# Disclaimer: This script was ai-generated and went through minimal revision. + +import cv2 +import numpy as np +import json +import sys + +def to_hex(color): + return "#{:02x}{:02x}{:02x}".format(int(color[0]), int(color[1]), int(color[2])) + +def get_color_from_stdin(): + # Read raw bytes from stdin + input_data = sys.stdin.buffer.read() + if not input_data: + return {"error": "No data received via stdin"} + + # Convert bytes to numpy array and decode to image + nparr = np.frombuffer(input_data, np.uint8) + img = cv2.imdecode(nparr, cv2.IMREAD_COLOR) + + if img is None: + return {"error": "Could not decode image data"} + + img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) + h, w, _ = img_rgb.shape + + # 1. Sample corner pixels (The background anchors) + corners = np.array([ + img_rgb[0, 0], + img_rgb[0, w-1], + img_rgb[h-1, 0], + img_rgb[h-1, w-1] + ]) + + # 2. Determine single dominant background + # Using median handles noise/gradients better than a simple average + bg_color = np.median(corners, axis=0).astype(int) + + # 3. Find the Text Color + pixels = img_rgb.reshape(-1, 3).astype(int) + distances = np.linalg.norm(pixels - bg_color, axis=1) + + # Take the 95th percentile of pixels furthest from background + threshold = np.percentile(distances, 95) + text_pixels = pixels[distances >= threshold] + + if len(text_pixels) == 0: + text_color = [255, 255, 255] # Fallback + else: + text_color = np.median(text_pixels, axis=0).astype(int) + + return { + "background": to_hex(bg_color), + "text": to_hex(text_color) + } + +if __name__ == "__main__": + result = get_color_from_stdin() + print(json.dumps(result)) \ No newline at end of file diff --git a/dots/.config/quickshell/ii/services/GoogleCloud.qml b/dots/.config/quickshell/ii/services/GoogleCloud.qml new file mode 100644 index 000000000..e90ae5dfd --- /dev/null +++ b/dots/.config/quickshell/ii/services/GoogleCloud.qml @@ -0,0 +1,93 @@ +pragma Singleton +pragma ComponentBehavior: Bound +import QtQuick +import Quickshell +import qs.modules.common.utils + +Singleton { + id: root + + property var keyContent: ({}) + property string keyProjectId: keyContent.project_id + property bool keyError: false + property bool keyReady: false + property string token: "" + property bool tokenError: false + property bool tokenReady: false + readonly property string projectId: keyProjectId + + readonly property bool loaded: keyReady && tokenReady + + readonly property string tokenForKeyScriptPath: Quickshell.shellPath("services/gCloud/token-from-key-venv.sh") + + function load() { + // Dummy for init + } + + function setKeyJson(str: string): bool { + try { + var keyData = JSON.parse(str) + KeyringStorage.setNestedField(["googleCloud", "serviceAccountKey"], keyData); + return true; + } catch(e) { + return false; + } + } + + function getToken() { + if (root.keyError) { + root.tokenError = true; + root.tokenReady = true; + return; + } + tokenProc.runSequence([(() => { // prep token fetcher + tokenProc.environment.SERVICE_KEY_CONTENT = JSON.stringify(root.keyContent); + tokenProc.command = [ // + "bash", "-c" // + , `${tokenForKeyScriptPath} "$SERVICE_KEY_CONTENT"`]; + }), [] // run token fetcher + , (out => { + if (out.startsWith("Error")) { + root.tokenError = true; + } else { + root.tokenError = false; + root.token = out.trim(); + } + root.tokenReady = true; + })]); + } + + function loadKeyIfPossible() { + if (KeyringStorage.loaded) { + root.keyContent = KeyringStorage.keyringData?.googleCloud?.serviceAccountKey; + if (!root.keyContent?.project_id) { + root.keyError = true; + } else { + root.keyError = false; + root.keyProjectId = root.keyContent.project_id; + } + root.keyReady = true; + root.getToken(); + } else { + KeyringStorage.fetchKeyringData(); + } + } + + Component.onCompleted: { + loadKeyIfPossible(); + } + + Connections { + target: KeyringStorage + function onLoadedChanged() { + root.loadKeyIfPossible(); + } + function onDataChanged() { + root.loadKeyIfPossible(); + } + } + + MultiTurnProcess { + id: tokenProc + } +} diff --git a/dots/.config/quickshell/ii/services/KeyringStorage.qml b/dots/.config/quickshell/ii/services/KeyringStorage.qml index ae49496d2..7e716d459 100644 --- a/dots/.config/quickshell/ii/services/KeyringStorage.qml +++ b/dots/.config/quickshell/ii/services/KeyringStorage.qml @@ -15,6 +15,8 @@ import QtQuick; Singleton { id: root + signal dataChanged() + property bool loaded: false property var keyringData: ({}) @@ -82,6 +84,7 @@ Singleton { if (saveData.running) { // console.log("[KeyringStorage] Saving with command: '" + saveData.command.join("' '") + "'"); saveData.write(JSON.stringify(root.keyringData)); + root.dataChanged() stdinEnabled = false // End input stream } } diff --git a/dots/.config/quickshell/ii/services/gCloud/token-from-key-venv.sh b/dots/.config/quickshell/ii/services/gCloud/token-from-key-venv.sh new file mode 100755 index 000000000..fd9e255e8 --- /dev/null +++ b/dots/.config/quickshell/ii/services/gCloud/token-from-key-venv.sh @@ -0,0 +1,6 @@ +#!/usr/bin/env bash +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +source $(eval echo $ILLOGICAL_IMPULSE_VIRTUAL_ENV)/bin/activate +"$SCRIPT_DIR/token_from_key.py" "$@" +deactivate diff --git a/dots/.config/quickshell/ii/services/gCloud/token_from_key.py b/dots/.config/quickshell/ii/services/gCloud/token_from_key.py new file mode 100755 index 000000000..f40a50661 --- /dev/null +++ b/dots/.config/quickshell/ii/services/gCloud/token_from_key.py @@ -0,0 +1,30 @@ +#!/usr/bin/env python3 +import sys +import json +import google.auth.transport.requests +import google.oauth2.service_account + +def get_token(json_str): + try: + # Load the string into a dictionary + info = json.loads(json_str) + + # Initialize credentials + creds = google.oauth2.service_account.Credentials.from_service_account_info(info) + scoped_creds = creds.with_scopes(['https://www.googleapis.com/auth/cloud-platform']) + + # Refresh to get the access token + request = google.auth.transport.requests.Request() + scoped_creds.refresh(request) + + print(scoped_creds.token) + except Exception as e: + sys.stderr.write(f"Error: {str(e)}\n") + sys.exit(1) + +if __name__ == "__main__": + if len(sys.argv) < 2: + sys.stderr.write("Usage: python3 get_token.py ''\n") + sys.exit(1) + + get_token(sys.argv[1]) diff --git a/sdata/uv/requirements.in b/sdata/uv/requirements.in index 277a461cb..58023b915 100644 --- a/sdata/uv/requirements.in +++ b/sdata/uv/requirements.in @@ -16,3 +16,5 @@ pygobject tqdm numpy opencv-contrib-python +google-auth +requests diff --git a/sdata/uv/requirements.txt b/sdata/uv/requirements.txt index f58e87d3d..cdfd65503 100644 --- a/sdata/uv/requirements.txt +++ b/sdata/uv/requirements.txt @@ -1,63 +1,83 @@ # This file was autogenerated by uv via the following command: -# uv pip compile sdata/uv/requirements.in -o sdata/uv/requirements.txt +# uv pip compile requirements.in -o requirements.txt build==1.2.2.post1 - # via -r sdata/uv/requirements.in + # via -r requirements.in +certifi==2026.2.25 + # via requests cffi==1.17.1 - # via pywayland + # via + # cryptography + # pywayland +charset-normalizer==3.4.7 + # via requests click==8.2.1 - # via -r sdata/uv/requirements.in + # via -r requirements.in +cryptography==46.0.0 + # via google-auth dbus-python==1.4.0 # via kde-material-you-colors +google-auth==2.49.1 + # via -r requirements.in +idna==3.11 + # via requests kde-material-you-colors==1.10.1 - # via -r sdata/uv/requirements.in + # via -r requirements.in libsass==0.23.0 - # via -r sdata/uv/requirements.in + # via -r requirements.in loguru==0.7.3 - # via -r sdata/uv/requirements.in + # via -r requirements.in material-color-utilities==0.2.1 - # via -r sdata/uv/requirements.in + # via -r requirements.in materialyoucolor==2.0.10 # via - # -r sdata/uv/requirements.in + # -r requirements.in # kde-material-you-colors numpy==2.2.2 # via - # -r sdata/uv/requirements.in + # -r requirements.in # kde-material-you-colors # material-color-utilities # opencv-contrib-python opencv-contrib-python==4.12.0.88 - # via -r sdata/uv/requirements.in + # via -r requirements.in packaging==24.2 # via # build # setuptools-scm pillow==11.1.0 # via - # -r sdata/uv/requirements.in + # -r requirements.in # kde-material-you-colors # material-color-utilities psutil==6.1.1 - # via -r sdata/uv/requirements.in + # via -r requirements.in +pyasn1==0.6.3 + # via pyasn1-modules +pyasn1-modules==0.4.2 + # via google-auth pycairo==1.28.0 # via - # -r sdata/uv/requirements.in + # -r requirements.in # pygobject pycparser==2.22 # via cffi pygobject==3.52.3 - # via -r sdata/uv/requirements.in + # via -r requirements.in pyproject-hooks==1.2.0 # via build pywayland==0.4.18 - # via -r sdata/uv/requirements.in + # via -r requirements.in +requests==2.33.1 + # via -r requirements.in setproctitle==1.3.4 - # via -r sdata/uv/requirements.in + # via -r requirements.in setuptools==80.9.0 # via setuptools-scm setuptools-scm==8.1.0 - # via -r sdata/uv/requirements.in + # via -r requirements.in tqdm==4.67.1 - # via -r sdata/uv/requirements.in + # via -r requirements.in +urllib3==2.6.3 + # via requests wheel==0.45.1 - # via -r sdata/uv/requirements.in + # via -r requirements.in From f88b21e681d907cdaf36c5e516247d1c075e267b Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Fri, 3 Apr 2026 19:33:28 +0200 Subject: [PATCH 17/17] add missing bezier curve for screen translator --- .../quickshell/ii/modules/common/Appearance.qml | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/dots/.config/quickshell/ii/modules/common/Appearance.qml b/dots/.config/quickshell/ii/modules/common/Appearance.qml index d64253476..bedf01ebc 100644 --- a/dots/.config/quickshell/ii/modules/common/Appearance.qml +++ b/dots/.config/quickshell/ii/modules/common/Appearance.qml @@ -282,6 +282,20 @@ Singleton { } } + property QtObject elementMoveSmall: QtObject { + property int duration: animationCurves.expressiveFastSpatialDuration + property int type: Easing.BezierSpline + property list bezierCurve: animationCurves.expressiveFastSpatial + property int velocity: 650 + property Component numberAnimation: Component { + NumberAnimation { + duration: root.animation.elementMoveSmall.duration + easing.type: root.animation.elementMoveSmall.type + easing.bezierCurve: root.animation.elementMoveSmall.bezierCurve + } + } + } + property QtObject elementMoveEnter: QtObject { property int duration: 400 property int type: Easing.BezierSpline @@ -361,7 +375,7 @@ Singleton { property QtObject scroll: QtObject { property int duration: 200 property int type: Easing.BezierSpline - property list bezierCurve: animationCurves.standardDecel + property list bezierCurve: root.animationCurves.standardDecel } property QtObject menuDecel: QtObject {