From 952aa63147c9fb28f6ace6f0bc7ccf45ced1299a Mon Sep 17 00:00:00 2001 From: Kiran George Date: Mon, 9 Jun 2025 11:30:08 +0530 Subject: Overview v2 --- config/quickshell/modules/overview/Overview.qml | 279 ++++++++++++++ .../quickshell/modules/overview/OverviewWidget.qml | 341 +++++++++++++++++ .../quickshell/modules/overview/OverviewWindow.qml | 94 +++++ config/quickshell/modules/overview/SearchItem.qml | 220 +++++++++++ .../quickshell/modules/overview/SearchWidget.qml | 425 +++++++++++++++++++++ 5 files changed, 1359 insertions(+) create mode 100644 config/quickshell/modules/overview/Overview.qml create mode 100644 config/quickshell/modules/overview/OverviewWidget.qml create mode 100644 config/quickshell/modules/overview/OverviewWindow.qml create mode 100644 config/quickshell/modules/overview/SearchItem.qml create mode 100644 config/quickshell/modules/overview/SearchWidget.qml (limited to 'config/quickshell/modules/overview') diff --git a/config/quickshell/modules/overview/Overview.qml b/config/quickshell/modules/overview/Overview.qml new file mode 100644 index 00000000..ef5a49c3 --- /dev/null +++ b/config/quickshell/modules/overview/Overview.qml @@ -0,0 +1,279 @@ +import "root:/" +import "root:/services" +import "root:/modules/common" +import "root:/modules/common/widgets" +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell +import Quickshell.Io +import Quickshell.Wayland +import Quickshell.Hyprland + +Scope { + id: overviewScope + property bool dontAutoCancelSearch: false + property bool searchEnabled: ConfigOptions.search.searchEnabled + + Variants { + id: overviewVariants + model: Quickshell.screens + PanelWindow { + id: root + required property var modelData + property string searchingText: "" + readonly property HyprlandMonitor monitor: Hyprland.monitorFor(root.screen) + property bool monitorIsFocused: (Hyprland.focusedMonitor?.id == monitor.id) + screen: modelData + visible: GlobalStates.overviewOpen + + WlrLayershell.namespace: "quickshell:overview" + WlrLayershell.layer: WlrLayer.Overlay + color: "transparent" + + mask: Region { + item: GlobalStates.overviewOpen ? columnLayout : null + } + HyprlandWindow.visibleMask: Region { + item: GlobalStates.overviewOpen ? columnLayout : null + } + + anchors { + top: true + left: true + right: true + bottom: true + } + + HyprlandFocusGrab { + id: grab + windows: [ root ] + property bool canBeActive: root.monitorIsFocused + active: false + onCleared: () => { + if (!active) GlobalStates.overviewOpen = false + } + } + + Connections { + target: GlobalStates + function onOverviewOpenChanged() { + if (!GlobalStates.overviewOpen) { + if (overviewScope.searchEnabled && searchWidget) { + searchWidget.disableExpandAnimation() + } + overviewScope.dontAutoCancelSearch = false; + } else { + if (!overviewScope.dontAutoCancelSearch && overviewScope.searchEnabled && searchWidget) { + searchWidget.cancelSearch() + } + delayedGrabTimer.start() + } + } + } + + Timer { + id: delayedGrabTimer + interval: ConfigOptions.hacks.arbitraryRaceConditionDelay + repeat: false + onTriggered: { + if (!grab.canBeActive) return + grab.active = GlobalStates.overviewOpen + } + } + + implicitWidth: columnLayout.implicitWidth + implicitHeight: columnLayout.implicitHeight + + function setSearchingText(text) { + if (overviewScope.searchEnabled && searchWidget) { + searchWidget.setSearchingText(text); + } + } + + ColumnLayout { + id: columnLayout + visible: GlobalStates.overviewOpen + anchors { + horizontalCenter: parent.horizontalCenter + top: ConfigOptions.overview.position === 0 ? parent.top : undefined + verticalCenter: ConfigOptions.overview.position === 1 ? parent.verticalCenter : undefined + bottom: ConfigOptions.overview.position === 2 ? parent.bottom : undefined + } + + Keys.onPressed: (event) => { + if (event.key === Qt.Key_Escape) { + GlobalStates.overviewOpen = false; + } + } + + Item { + height: 1 + width: 1 + } + + // Conditionally render SearchWidget - only exists when searchEnabled is true + SearchWidget { + id: searchWidget + Layout.alignment: Qt.AlignHCenter + visible: overviewScope.searchEnabled + height: overviewScope.searchEnabled ? implicitHeight : 0 + Layout.preferredHeight: overviewScope.searchEnabled ? implicitHeight : 0 + onSearchingTextChanged: (text) => { + root.searchingText = searchingText + } + } + + Item { + Layout.preferredHeight: overviewScope.searchEnabled ? 0 : 20 + Layout.fillWidth: true + visible: !overviewScope.searchEnabled + } + + Loader { + id: overviewLoader + active: GlobalStates.overviewOpen + sourceComponent: OverviewWidget { + panelWindow: root + // Show OverviewWidget when search is disabled OR when search text is empty + visible: !overviewScope.searchEnabled || (root.searchingText == "") + } + } + } + } + } + + IpcHandler { + target: "overview" + + function toggle() { + GlobalStates.overviewOpen = !GlobalStates.overviewOpen + } + function close() { + GlobalStates.overviewOpen = false + } + function open() { + GlobalStates.overviewOpen = true + } + function toggleReleaseInterrupt() { + GlobalStates.superReleaseMightTrigger = false + } + // Add function to control search + function toggleSearch() { + overviewScope.searchEnabled = !overviewScope.searchEnabled + } + function enableSearch() { + overviewScope.searchEnabled = true + } + function disableSearch() { + overviewScope.searchEnabled = false + } + } + + GlobalShortcut { + name: "overviewToggle" + description: qsTr("Toggles overview on press") + + onPressed: { + GlobalStates.overviewOpen = !GlobalStates.overviewOpen + } + } + + GlobalShortcut { + name: "overviewClose" + description: qsTr("Closes overview") + + onPressed: { + GlobalStates.overviewOpen = false + } + } + + GlobalShortcut { + name: "overviewToggleRelease" + description: qsTr("Toggles overview on release") + + onPressed: { + GlobalStates.superReleaseMightTrigger = true + } + + onReleased: { + if (!GlobalStates.superReleaseMightTrigger) { + GlobalStates.superReleaseMightTrigger = true + return + } + GlobalStates.overviewOpen = !GlobalStates.overviewOpen + } + } + + GlobalShortcut { + name: "overviewToggleReleaseInterrupt" + description: qsTr("Interrupts possibility of overview being toggled on release. ") + + qsTr("This is necessary because GlobalShortcut.onReleased in quickshell triggers whether or not you press something else while holding the key. ") + + qsTr("To make sure this works consistently, use binditn = MODKEYS, catchall in an automatically triggered submap that includes everything.") + + onPressed: { + GlobalStates.superReleaseMightTrigger = false + } + } + + // Only enable clipboard/emoji shortcuts when search is enabled + GlobalShortcut { + name: "overviewClipboardToggle" + description: qsTr("Toggle clipboard query on overview widget") + + onPressed: { + if (!overviewScope.searchEnabled) return; // Skip if search disabled + + if (GlobalStates.overviewOpen && overviewScope.dontAutoCancelSearch) { + GlobalStates.overviewOpen = false; + return; + } + for (let i = 0; i < overviewVariants.instances.length; i++) { + let panelWindow = overviewVariants.instances[i]; + if (panelWindow.modelData.name == Hyprland.focusedMonitor.name) { + overviewScope.dontAutoCancelSearch = true; + panelWindow.setSearchingText( + ConfigOptions.search.prefix.clipboard + ); + GlobalStates.overviewOpen = true; + return + } + } + } + } + + GlobalShortcut { + name: "overviewEmojiToggle" + description: qsTr("Toggle emoji query on overview widget") + + onPressed: { + if (!overviewScope.searchEnabled) return; // Skip if search disabled + + if (GlobalStates.overviewOpen && overviewScope.dontAutoCancelSearch) { + GlobalStates.overviewOpen = false; + return; + } + for (let i = 0; i < overviewVariants.instances.length; i++) { + let panelWindow = overviewVariants.instances[i]; + if (panelWindow.modelData.name == Hyprland.focusedMonitor.name) { + overviewScope.dontAutoCancelSearch = true; + panelWindow.setSearchingText( + ConfigOptions.search.prefix.emojis + ); + GlobalStates.overviewOpen = true; + return + } + } + } + } + + // Optional: Add shortcut to toggle search functionality + GlobalShortcut { + name: "overviewToggleSearch" + description: qsTr("Toggle search functionality in overview") + + onPressed: { + overviewScope.searchEnabled = !overviewScope.searchEnabled + } + } +} \ No newline at end of file diff --git a/config/quickshell/modules/overview/OverviewWidget.qml b/config/quickshell/modules/overview/OverviewWidget.qml new file mode 100644 index 00000000..73dbbc26 --- /dev/null +++ b/config/quickshell/modules/overview/OverviewWidget.qml @@ -0,0 +1,341 @@ +import "root:/" +import "root:/services/" +import "root:/modules/common" +import "root:/modules/common/widgets" +import "root:/modules/common/functions/color_utils.js" as ColorUtils +import QtQuick +import QtQuick.Effects +import QtQuick.Layouts +import Quickshell +import Quickshell.Io +import Quickshell.Widgets +import Quickshell.Wayland +import Quickshell.Hyprland + +Item { + id: root + required property var panelWindow + readonly property HyprlandMonitor monitor: Hyprland.monitorFor(panelWindow.screen) + readonly property var toplevels: ToplevelManager.toplevels + readonly property int workspacesShown: ConfigOptions.overview.numOfRows * ConfigOptions.overview.numOfCols + readonly property int workspaceGroup: Math.floor((monitor.activeWorkspace?.id - 1) / workspacesShown) + property bool monitorIsFocused: (Hyprland.focusedMonitor?.id == monitor.id) + 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 real scale: ConfigOptions.overview.scale + property color activeBorderColor: Appearance.m3colors.m3secondary + + property real workspaceImplicitWidth: Math.max(100, (monitorData?.transform % 2 === 1) ? + ((monitor.height - monitorData?.reserved[0] - monitorData?.reserved[2]) * root.scale / monitor.scale) : + ((monitor.width - monitorData?.reserved[0] - monitorData?.reserved[2]) * root.scale / monitor.scale)) + property real workspaceImplicitHeight: Math.max(60, (monitorData?.transform % 2 === 1) ? + ((monitor.width - monitorData?.reserved[1] - monitorData?.reserved[3]) * root.scale / monitor.scale) : + ((monitor.height - monitorData?.reserved[1] - monitorData?.reserved[3]) * root.scale / monitor.scale)) + + property real workspaceNumberMargin: 80 + property real workspaceNumberSize: Math.min(workspaceImplicitHeight, workspaceImplicitWidth) * monitor.scale + property int workspaceZ: 0 + property int windowZ: 1 + property int windowDraggingZ: 99999 + property real workspaceSpacing: 5 + + property int draggingFromWorkspace: -1 + property int draggingTargetWorkspace: -1 + + implicitWidth: overviewBackground.implicitWidth + Appearance.sizes.elevationMargin * 2 + implicitHeight: overviewBackground.implicitHeight + Appearance.sizes.elevationMargin * 2 + + property Component windowComponent: OverviewWindow {} + property list windowWidgets: [] + + // Shared wallpaper image - loaded once and reused + Image { + id: sharedWallpaper + source: Appearance.background_image || "" + visible: false // Hidden as it's only used as a source + cache: true + asynchronous: true + smooth: true + opacity: Appearance.workpaceTransparency // Adds slight transparency (0.0 = fully transparent, 1.0 = fully opaque) + } + + StyledRectangularShadow { + target: overviewBackground + } + Rectangle { // Background + id: overviewBackground + property real padding: 10 + anchors.fill: parent + anchors.margins: Appearance.sizes.elevationMargin + border.color : ColorUtils.transparentize(Appearance.m3colors.m3outline, 0.2) + border.width : 2 + + implicitWidth: workspaceColumnLayout.implicitWidth + padding * 2 + implicitHeight: workspaceColumnLayout.implicitHeight + padding * 2 + radius: Appearance.rounding.screenRounding * root.scale + padding + color: Appearance.colors.colLayer0 + + + ColumnLayout { // Workspaces + id: workspaceColumnLayout + + z: root.workspaceZ + anchors.centerIn: parent + spacing: workspaceSpacing + Repeater { + model: ConfigOptions.overview.numOfRows + delegate: RowLayout { + id: row + property int rowIndex: index + spacing: workspaceSpacing + + Repeater { // Workspace repeater + model: ConfigOptions.overview.numOfCols + Rectangle { // Workspace + id: workspace + property int colIndex: index + property int workspaceValue: root.workspaceGroup * workspacesShown + rowIndex * ConfigOptions.overview.numOfCols + colIndex + 1 + property color defaultWorkspaceColor: Appearance.colors.colLayer1 + property color hoveredWorkspaceColor: ColorUtils.mix(defaultWorkspaceColor, Appearance.colors.colLayer1Hover, 0.1) + property color hoveredBorderColor: Appearance.colors.colLayer2Hover + property bool hoveredWhileDragging: false + readonly property int padding: ConfigOptions.overview.windowPadding + + Layout.preferredWidth: root.workspaceImplicitWidth + Layout.preferredHeight: root.workspaceImplicitHeight + Layout.minimumWidth: 100 + Layout.minimumHeight: 60 + + width: root.workspaceImplicitWidth + height: root.workspaceImplicitHeight + color: "transparent" + radius: Appearance.rounding.screenRounding * root.scale + clip: true + opacity: Appearance.workpaceTransparency // Adds slight transparency (0.0 = fully transparent, 1.0 = fully opaque) + + + // Efficient wallpaper using ShaderEffectSource + Rectangle { + id: wallpaperContainer + anchors.fill: parent + anchors.margins: 2 // Leave space for border + radius: workspace.radius - 2 + color: workspace.defaultWorkspaceColor // Fallback color + clip: true + + ShaderEffectSource { + id: wallpaperSource + anchors.fill: parent + sourceItem: sharedWallpaper + visible: sharedWallpaper.status === Image.Ready + smooth: true + + // Scale to fill while preserving aspect ratio + transform: Scale { + property real aspectRatio: sharedWallpaper.implicitWidth / Math.max(1, sharedWallpaper.implicitHeight) + property real containerRatio: wallpaperContainer.width / Math.max(1, wallpaperContainer.height) + + xScale: aspectRatio > containerRatio ? + wallpaperContainer.height * aspectRatio / wallpaperContainer.width : 1 + yScale: aspectRatio > containerRatio ? + 1 : wallpaperContainer.width / (wallpaperContainer.height * aspectRatio) + + origin.x: wallpaperContainer.width / 2 + origin.y: wallpaperContainer.height / 2 + } + } + + // Fallback when image fails to load or isn't ready + Rectangle { + anchors.fill: parent + color: workspace.defaultWorkspaceColor + visible: sharedWallpaper.status !== Image.Ready + } + + // Optional: Add overlay for better text readability and hover effects + Rectangle { + anchors.fill: parent + color: hoveredWhileDragging ? hoveredWorkspaceColor : "black" + opacity: hoveredWhileDragging ? 0.3 : 0.1 + } + } + + // Border overlay - on top of wallpaper + Rectangle { + anchors.fill: parent + color: "transparent" + radius: parent.radius + border.width: 1 + border.color: hoveredWhileDragging ? hoveredBorderColor : ColorUtils.transparentize(Appearance.m3colors.m3outline, 0.6) + z: 10 // Ensure it's on top + } + + StyledText { + // Position in top-left corner with padding + anchors.top: parent.top + anchors.left: parent.left + anchors.topMargin: 12 // Padding from top edge + anchors.leftMargin: 12 // Padding from left edge + + text: workspaceValue + font.pixelSize: root.workspaceNumberSize * root.scale + font.weight: Font.DemiBold + color: ColorUtils.transparentize(Appearance.colors.colOnLayer1, 0.8) + horizontalAlignment: Text.AlignLeft + verticalAlignment: Text.AlignTop + z: 15 // Above border + } + + MouseArea { + id: workspaceArea + anchors.fill: parent + acceptedButtons: Qt.LeftButton + z: 20 // Above all visual elements + onClicked: { + if (root.draggingTargetWorkspace === -1) { + GlobalStates.overviewOpen = false + Hyprland.dispatch(`workspace ${workspaceValue}`) + } + } + } + + DropArea { + anchors.fill: parent + z: 20 // Same level as MouseArea + onEntered: { + root.draggingTargetWorkspace = workspaceValue + if (root.draggingFromWorkspace == root.draggingTargetWorkspace) return; + hoveredWhileDragging = true + } + onExited: { + hoveredWhileDragging = false + if (root.draggingTargetWorkspace == workspaceValue) root.draggingTargetWorkspace = -1 + } + } + + } + } + } + } + } + + Item { // Windows & focused workspace indicator + id: windowSpace + anchors.centerIn: parent + implicitWidth: workspaceColumnLayout.implicitWidth + implicitHeight: workspaceColumnLayout.implicitHeight + + Repeater { // Window repeater + model: ScriptModel { + values: windowAddresses.filter((address) => { + var win = windowByAddress[address] + return (root.workspaceGroup * root.workspacesShown < win?.workspace?.id && win?.workspace?.id <= (root.workspaceGroup + 1) * root.workspacesShown) + }) + } + delegate: OverviewWindow { + id: window + windowData: windowByAddress[modelData] + monitorData: root.monitorData + scale: root.scale + availableWorkspaceWidth: root.workspaceImplicitWidth + availableWorkspaceHeight: root.workspaceImplicitHeight + + property bool atInitPosition: (initX == x && initY == y) + restrictToWorkspace: Drag.active || atInitPosition + + property int workspaceColIndex: (windowData?.workspace.id - 1) % ConfigOptions.overview.numOfCols + property int workspaceRowIndex: Math.floor((windowData?.workspace.id - 1) % root.workspacesShown / ConfigOptions.overview.numOfCols) + xOffset: (root.workspaceImplicitWidth + workspaceSpacing) * workspaceColIndex + yOffset: (root.workspaceImplicitHeight + workspaceSpacing) * workspaceRowIndex + + Timer { + id: updateWindowPosition + interval: ConfigOptions.hacks.arbitraryRaceConditionDelay + repeat: false + running: false + onTriggered: { + window.x = Math.max((windowData?.at[0] - monitorData?.reserved[0]) * root.scale, 0) + xOffset + window.y = Math.max((windowData?.at[1] - monitorData?.reserved[1]) * root.scale, 0) + yOffset + } + } + + z: atInitPosition ? root.windowZ : root.windowDraggingZ + Drag.hotSpot.x: targetWindowWidth / 2 + Drag.hotSpot.y: targetWindowHeight / 2 + MouseArea { + id: dragArea + anchors.fill: parent + hoverEnabled: true + onEntered: hovered = true + onExited: hovered = false + acceptedButtons: Qt.LeftButton | Qt.MiddleButton + drag.target: parent + onPressed: { + root.draggingFromWorkspace = windowData?.workspace.id + window.pressed = true + window.Drag.active = true + window.Drag.source = window + } + onReleased: { + const targetWorkspace = root.draggingTargetWorkspace + window.pressed = false + window.Drag.active = false + root.draggingFromWorkspace = -1 + if (targetWorkspace !== -1 && targetWorkspace !== windowData?.workspace.id) { + Hyprland.dispatch(`movetoworkspacesilent ${targetWorkspace}, address:${window.windowData?.address}`) + updateWindowPosition.restart() + } + else { + window.x = window.initX + window.y = window.initY + } + } + onClicked: (event) => { + if (!windowData) return; + + if (event.button === Qt.LeftButton) { + GlobalStates.overviewOpen = false + Hyprland.dispatch(`focuswindow address:${windowData.address}`) + event.accepted = true + } else if (event.button === Qt.MiddleButton) { + Hyprland.dispatch(`closewindow address:${windowData.address}`) + event.accepted = true + } + } + + StyledToolTip { + extraVisibleCondition: false + alternativeVisibleCondition: dragArea.containsMouse && !window.Drag.active + content: `${windowData.title}\n[${windowData.class}] ${windowData.xwayland ? "[XWayland] " : ""}\n` + } + } + } + } + + Rectangle { // Focused workspace indicator + id: focusedWorkspaceIndicator + property int activeWorkspaceInGroup: monitor.activeWorkspace?.id - (root.workspaceGroup * root.workspacesShown) + property int activeWorkspaceRowIndex: Math.floor((activeWorkspaceInGroup - 1) / ConfigOptions.overview.numOfCols) + property int activeWorkspaceColIndex: (activeWorkspaceInGroup - 1) % ConfigOptions.overview.numOfCols + x: (root.workspaceImplicitWidth + workspaceSpacing) * activeWorkspaceColIndex + y: (root.workspaceImplicitHeight + workspaceSpacing) * activeWorkspaceRowIndex + z: root.windowZ + width: Math.max(100, root.workspaceImplicitWidth) + height: Math.max(60, root.workspaceImplicitHeight) + color: "transparent" + radius: Appearance.rounding.screenRounding * root.scale + border.width: 2 + border.color: root.activeBorderColor + visible: width > 0 && height > 0 && activeWorkspaceInGroup > 0 + Behavior on x { + animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this) + } + Behavior on y { + animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this) + } + } + } + } +} \ No newline at end of file diff --git a/config/quickshell/modules/overview/OverviewWindow.qml b/config/quickshell/modules/overview/OverviewWindow.qml new file mode 100644 index 00000000..273eff7e --- /dev/null +++ b/config/quickshell/modules/overview/OverviewWindow.qml @@ -0,0 +1,94 @@ +import "root:/services/" +import "root:/modules/common" +import "root:/modules/common/widgets" +import "root:/modules/common/functions/color_utils.js" as ColorUtils +import Qt5Compat.GraphicalEffects +import QtQuick +import QtQuick.Layouts +import Quickshell +import Quickshell.Widgets +import Quickshell.Io +import Quickshell.Hyprland + +Rectangle { // Window + id: root + property var windowData + property var monitorData + property var scale + property var availableWorkspaceWidth + property var availableWorkspaceHeight + property bool restrictToWorkspace: true + property real initX: Math.max((windowData?.at[0] - monitorData?.reserved[0]) * root.scale, 0) + xOffset + property real initY: Math.max((windowData?.at[1] - monitorData?.reserved[1]) * root.scale, 0) + yOffset + property real xOffset: 0 + property real yOffset: 0 + + property var targetWindowWidth: windowData?.size[0] * scale + property var targetWindowHeight: windowData?.size[1] * scale + property bool hovered: false + property bool pressed: false + + property var iconToWindowRatio: 0.35 + property var xwaylandIndicatorToIconRatio: 0.35 + property var iconToWindowRatioCompact: 0.6 + property var iconPath: Quickshell.iconPath(AppSearch.guessIcon(windowData?.class), "image-missing") + property bool compactMode: Appearance.font.pixelSize.smaller * 4 > targetWindowHeight || Appearance.font.pixelSize.smaller * 4 > targetWindowWidth + + property bool indicateXWayland: (ConfigOptions.overview.showXwaylandIndicator && windowData?.xwayland) ?? false + + x: initX + y: initY + width: Math.min(windowData?.size[0] * root.scale, (restrictToWorkspace ? windowData?.size[0] : availableWorkspaceWidth - x + xOffset)) + height: Math.min(windowData?.size[1] * root.scale, (restrictToWorkspace ? windowData?.size[1] : availableWorkspaceHeight - y + yOffset)) + + radius: Appearance.rounding.windowRounding * root.scale + color: pressed ? Appearance.colors.colLayer2Active : hovered ? Appearance.colors.colLayer2Hover : Appearance.colors.colLayer2 + // border.color : ColorUtils.transparentize(Appearance.m3colors.m3outline, 0.9) + border.color : ColorUtils.transparentize(Appearance.m3colors.m3outline, 0.4) + border.pixelAligned : false + border.width : 2 + + Behavior on x { + animation: Appearance.animation.elementMoveEnter.numberAnimation.createObject(this) + } + Behavior on y { + animation: Appearance.animation.elementMoveEnter.numberAnimation.createObject(this) + } + Behavior on width { + animation: Appearance.animation.elementMoveEnter.numberAnimation.createObject(this) + } + Behavior on height { + animation: Appearance.animation.elementMoveEnter.numberAnimation.createObject(this) + } + + ColumnLayout { + anchors.verticalCenter: parent.verticalCenter + anchors.left: parent.left + anchors.right: parent.right + spacing: Appearance.font.pixelSize.smaller * 0.5 + + IconImage { + id: windowIcon + Layout.alignment: Qt.AlignHCenter + source: root.iconPath + implicitSize: Math.min(targetWindowWidth, targetWindowHeight) * (root.compactMode ? root.iconToWindowRatioCompact : root.iconToWindowRatio) + + Behavior on implicitSize { + animation: Appearance.animation.elementMoveEnter.numberAnimation.createObject(this) + } + } + + StyledText { + Layout.leftMargin: 10 + Layout.rightMargin: 10 + visible: !compactMode + Layout.fillWidth: true + Layout.fillHeight: true + horizontalAlignment: Text.AlignHCenter + font.pixelSize: Appearance.font.pixelSize.smaller + font.italic: indicateXWayland ? true : false + elide: Text.ElideRight + text: windowData?.title ?? "" + } + } +} \ No newline at end of file diff --git a/config/quickshell/modules/overview/SearchItem.qml b/config/quickshell/modules/overview/SearchItem.qml new file mode 100644 index 00000000..1363b88d --- /dev/null +++ b/config/quickshell/modules/overview/SearchItem.qml @@ -0,0 +1,220 @@ +// pragma NativeMethodBehavior: AcceptThisObject +import "root:/" +import "root:/modules/common" +import "root:/modules/common/widgets" +import "root:/modules/common/functions/color_utils.js" as ColorUtils +import "root:/modules/common/functions/string_utils.js" as StringUtils +import "root:/modules/common/functions/fuzzysort.js" as Fuzzy +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell +import Quickshell.Io +import Quickshell.Widgets +import Quickshell.Hyprland + +RippleButton { + id: root + property var entry + property string query + property bool entryShown: entry?.shown ?? true + property string itemType: entry?.type + property string itemName: entry?.name + property string itemIcon: entry?.icon ?? "" + property var itemExecute: entry?.execute + property string fontType: entry?.fontType ?? "main" + property string itemClickActionName: entry?.clickActionName + property string bigText: entry?.bigText ?? "" + property string materialSymbol: entry?.materialSymbol ?? "" + property string cliphistRawString: entry?.cliphistRawString ?? "" + + property string highlightPrefix: `` + property string highlightSuffix: `` + function highlightContent(content, query) { + if (!query || query.length === 0 || content == query || fontType === "monospace") + return StringUtils.escapeHtml(content); + + let contentLower = content.toLowerCase(); + let queryLower = query.toLowerCase(); + + let result = ""; + let lastIndex = 0; + let qIndex = 0; + + for (let i = 0; i < content.length && qIndex < query.length; i++) { + if (contentLower[i] === queryLower[qIndex]) { + // Add non-highlighted part (escaped) + if (i > lastIndex) + result += StringUtils.escapeHtml(content.slice(lastIndex, i)); + // Add highlighted character (escaped) + result += root.highlightPrefix + StringUtils.escapeHtml(content[i]) + root.highlightSuffix; + lastIndex = i + 1; + qIndex++; + } + } + // Add the rest of the string (escaped) + if (lastIndex < content.length) + result += StringUtils.escapeHtml(content.slice(lastIndex)); + + return result; + } + property string displayContent: highlightContent(root.itemName, root.query) + + property list urls: { + if (!root.itemName) return []; + // Regular expression to match URLs + const urlRegex = /https?:\/\/[^\s<>"{}|\\^`[\]]+/gi; + const matches = root.itemName?.match(urlRegex) + ?.filter(url => !url.includes("…")) // Elided = invalid + return matches ? matches : []; + } + + visible: root.entryShown + property int horizontalMargin: 10 + property int buttonHorizontalPadding: 10 + property int buttonVerticalPadding: 5 + property bool keyboardDown: false + + implicitHeight: rowLayout.implicitHeight + root.buttonVerticalPadding * 2 + implicitWidth: rowLayout.implicitWidth + root.buttonHorizontalPadding * 2 + buttonRadius: Appearance.rounding.normal + colBackground: (root.down || root.keyboardDown) ? Appearance.colors.colLayer1Active : + ((root.hovered || root.focus) ? Appearance.colors.colLayer1Hover : + ColorUtils.transparentize(Appearance.m3colors.m3surfaceContainerHigh, 1)) + colBackgroundHover: Appearance.colors.colLayer1Hover + colRipple: Appearance.colors.colLayer1Active + + background { + anchors.fill: root + anchors.leftMargin: root.horizontalMargin + anchors.rightMargin: root.horizontalMargin + } + + PointingHandInteraction {} + onClicked: { + root.itemExecute() + Hyprland.dispatch("global quickshell:overviewClose") + } + Keys.onPressed: (event) => { + if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) { + root.keyboardDown = true + root.clicked() + event.accepted = true; + } + } + Keys.onReleased: (event) => { + if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) { + root.keyboardDown = false + event.accepted = true; + } + } + + RowLayout { + id: rowLayout + spacing: iconLoader.sourceComponent === null ? 0 : 10 + anchors.fill: parent + anchors.leftMargin: root.horizontalMargin + root.buttonHorizontalPadding + anchors.rightMargin: root.horizontalMargin + root.buttonHorizontalPadding + + // Icon + Loader { + id: iconLoader + active: true + sourceComponent: root.materialSymbol !== "" ? materialSymbolComponent : + root.bigText ? bigTextComponent : + root.itemIcon !== "" ? iconImageComponent : + null + } + + Component { + id: iconImageComponent + IconImage { + source: Quickshell.iconPath(root.itemIcon, "image-missing") + width: 35 + height: 35 + } + } + + Component { + id: materialSymbolComponent + MaterialSymbol { + text: root.materialSymbol + iconSize: 30 + color: Appearance.m3colors.m3onSurface + } + } + + Component { + id: bigTextComponent + StyledText { + text: root.bigText + font.pixelSize: Appearance.font.pixelSize.larger + color: Appearance.m3colors.m3onSurface + } + } + + // Main text + ColumnLayout { + id: contentColumn + Layout.fillWidth: true + Layout.alignment: Qt.AlignVCenter + spacing: 0 + StyledText { + font.pixelSize: Appearance.font.pixelSize.smaller + color: Appearance.colors.colSubtext + visible: root.itemType && root.itemType != qsTr("App") + text: root.itemType + } + RowLayout { + Loader { // Checkmark for copied clipboard entry + visible: itemName == Quickshell.clipboardText && root.cliphistRawString + active: itemName == Quickshell.clipboardText && root.cliphistRawString + sourceComponent: Rectangle { + implicitWidth: activeText.implicitHeight + implicitHeight: activeText.implicitHeight + radius: Appearance.rounding.full + color: Appearance.colors.colPrimary + MaterialSymbol { + id: activeText + anchors.centerIn: parent + text: "check" + font.pixelSize: Appearance.font.pixelSize.normal + color: Appearance.m3colors.m3onPrimary + } + } + } + StyledText { // Item name/content + Layout.fillWidth: true + id: nameText + textFormat: Text.StyledText // RichText also works, but StyledText ensures elide work + font.pixelSize: Appearance.font.pixelSize.small + font.family: Appearance.font.family[root.fontType] + color: Appearance.m3colors.m3onSurface + horizontalAlignment: Text.AlignLeft + elide: Text.ElideRight + text: `${root.displayContent}` + } + } + Loader { // Clipboard image preview + active: root.cliphistRawString && /^\d+\t\[\[.*binary data.*\d+x\d+.*\]\]$/.test(root.cliphistRawString) + sourceComponent: CliphistImage { + Layout.fillWidth: true + entry: root.cliphistRawString + maxWidth: contentColumn.width + maxHeight: 140 + } + } + } + + // Action text + StyledText { + Layout.fillWidth: false + visible: (root.hovered || root.focus) + id: clickAction + font.pixelSize: Appearance.font.pixelSize.normal + color: Appearance.colors.colSubtext + horizontalAlignment: Text.AlignRight + text: root.itemClickActionName + } + } +} diff --git a/config/quickshell/modules/overview/SearchWidget.qml b/config/quickshell/modules/overview/SearchWidget.qml new file mode 100644 index 00000000..fed710ec --- /dev/null +++ b/config/quickshell/modules/overview/SearchWidget.qml @@ -0,0 +1,425 @@ +import "root:/" +import "root:/services/" +import "root:/modules/common" +import "root:/modules/common/widgets" +import "root:/modules/common/functions/string_utils.js" as StringUtils +import Qt5Compat.GraphicalEffects +import Qt.labs.platform +import QtQuick +import QtQuick.Controls +import QtQuick.Effects +import QtQuick.Layouts +import Quickshell +import Quickshell.Io +import Quickshell.Hyprland + +Item { // Wrapper + id: root + readonly property string xdgConfigHome: Directories.config + property string searchingText: "" + property bool showResults: searchingText != "" + property real searchBarHeight: searchBar.height + Appearance.sizes.elevationMargin * 2 + implicitWidth: searchWidgetContent.implicitWidth + Appearance.sizes.elevationMargin * 2 + implicitHeight: searchWidgetContent.implicitHeight + Appearance.sizes.elevationMargin * 2 + + property string mathResult: "" + + function disableExpandAnimation() { + searchWidthBehavior.enabled = false; + } + + function cancelSearch() { + searchInput.selectAll() + root.searchingText = "" + searchWidthBehavior.enabled = true; + } + + function setSearchingText(text) { + searchInput.text = text; + root.searchingText = text; + } + + property var searchActions: [ + { + action: "img", + execute: () => { + executor.executeCommand(Directories.wallpaperSwitchScriptPath) + } + }, + { + action: "dark", + execute: () => { + executor.executeCommand(`${Directories.wallpaperSwitchScriptPath} --mode dark --noswitch`) + } + }, + { + action: "light", + execute: () => { + executor.executeCommand(`${Directories.wallpaperSwitchScriptPath} --mode light --noswitch`) + } + }, + { + action: "accentcolor", + execute: (args) => { + executor.executeCommand( + `${Directories.wallpaperSwitchScriptPath} --noswitch --color ${args != '' ? ("'"+args+"'") : ""}` + ) + } + }, + { + action: "todo", + execute: (args) => { + Todo.addTask(args) + } + }, + ] + + function focusFirstItemIfNeeded() { + if (searchInput.focus) appResults.currentIndex = 0; // Focus the first item + } + + Timer { + id: nonAppResultsTimer + interval: ConfigOptions.search.nonAppResultDelay + onTriggered: { + mathProcess.calculateExpression(root.searchingText); + } + } + + Process { + id: mathProcess + property list baseCommand: ["qalc", "-t"] + function calculateExpression(expression) { + // mathProcess.running = false + mathProcess.command = baseCommand.concat(expression) + mathProcess.running = true + } + stdout: SplitParser { + onRead: data => { + root.mathResult = data + root.focusFirstItemIfNeeded() + } + } + } + + Process { + id: executor + property list baseCommand: ["bash", "-c"] + function executeCommand(command) { + executor.command = baseCommand.concat( + `${command} || ${ConfigOptions.apps.terminal} fish -C 'echo "${qsTr("Searching for package with that command")}..." && pacman -F ${command}'` + ) + executor.startDetached() + } + } + + Keys.onPressed: (event) => { + // Prevent Esc and Backspace from registering + if (event.key === Qt.Key_Escape) return; + + // Handle Backspace: focus and delete character if not focused + if (event.key === Qt.Key_Backspace) { + if (!searchInput.activeFocus) { + searchInput.forceActiveFocus(); + if (event.modifiers & Qt.ControlModifier) { + // Delete word before cursor + let text = searchInput.text; + let pos = searchInput.cursorPosition; + if (pos > 0) { + // Find the start of the previous word + let left = text.slice(0, pos); + let match = left.match(/(\s*\S+)\s*$/); + let deleteLen = match ? match[0].length : 1; + searchInput.text = text.slice(0, pos - deleteLen) + text.slice(pos); + searchInput.cursorPosition = pos - deleteLen; + } + } else { + // Delete character before cursor if any + if (searchInput.cursorPosition > 0) { + searchInput.text = searchInput.text.slice(0, searchInput.cursorPosition - 1) + + searchInput.text.slice(searchInput.cursorPosition); + searchInput.cursorPosition -= 1; + } + } + // Always move cursor to end after programmatic edit + searchInput.cursorPosition = searchInput.text.length; + event.accepted = true; + } + // If already focused, let TextField handle it + return; + } + + // Only handle visible printable characters (ignore control chars, arrows, etc.) + if ( + event.text && + event.text.length === 1 && + event.key !== Qt.Key_Enter && + event.key !== Qt.Key_Return && + event.text.charCodeAt(0) >= 0x20 // ignore control chars like Backspace, Tab, etc. + ) { + if (!searchInput.activeFocus) { + searchInput.forceActiveFocus(); + // Insert the character at the cursor position + searchInput.text = searchInput.text.slice(0, searchInput.cursorPosition) + + event.text + + searchInput.text.slice(searchInput.cursorPosition); + searchInput.cursorPosition += 1; + event.accepted = true; + } + } + } + + StyledRectangularShadow { + target: searchWidgetContent + } + Rectangle { // Background + id: searchWidgetContent + anchors.centerIn: parent + implicitWidth: columnLayout.implicitWidth + implicitHeight: columnLayout.implicitHeight + radius: Appearance.rounding.large + color: Appearance.colors.colLayer0 + + ColumnLayout { + id: columnLayout + anchors.centerIn: parent + spacing: 0 + + // clip: true + layer.enabled: true + layer.effect: OpacityMask { + maskSource: Rectangle { + width: searchWidgetContent.width + height: searchWidgetContent.width + radius: searchWidgetContent.radius + } + } + + RowLayout { + id: searchBar + spacing: 5 + MaterialSymbol { + id: searchIcon + Layout.leftMargin: 15 + iconSize: Appearance.font.pixelSize.huge + color: Appearance.m3colors.m3onSurface + text: root.searchingText.startsWith(ConfigOptions.search.prefix.clipboard) ? 'content_paste_search' : '' + } + TextField { // Search box + id: searchInput + + focus: GlobalStates.overviewOpen + Layout.rightMargin: 15 + padding: 15 + renderType: Text.NativeRendering + font { + family: Appearance?.font.family.main ?? "sans-serif" + pixelSize: Appearance?.font.pixelSize.small ?? 15 + hintingPreference: Font.PreferFullHinting + } + color: activeFocus ? Appearance.m3colors.m3onSurface : Appearance.m3colors.m3onSurfaceVariant + selectedTextColor: Appearance.m3colors.m3onSecondaryContainer + selectionColor: Appearance.m3colors.m3secondaryContainer + placeholderText: qsTr("Search, calculate or run") + placeholderTextColor: Appearance.m3colors.m3outline + implicitWidth: root.searchingText == "" ? Appearance.sizes.searchWidthCollapsed : Appearance.sizes.searchWidth + + Behavior on implicitWidth { + id: searchWidthBehavior + enabled: false + NumberAnimation { + duration: 300 + easing.type: Appearance.animation.elementMove.type + easing.bezierCurve: Appearance.animation.elementMove.bezierCurve + } + } + + onTextChanged: root.searchingText = text + + onAccepted: { + if (appResults.count > 0) { + // Get the first visible delegate and trigger its click + let firstItem = appResults.itemAtIndex(0); + if (firstItem && firstItem.clicked) { + firstItem.clicked(); + } + } + } + + background: null + + cursorDelegate: Rectangle { + width: 1 + color: searchInput.activeFocus ? Appearance.colors.colPrimary : "transparent" + radius: 1 + } + } + } + + Rectangle { // Separator + visible: root.showResults + Layout.fillWidth: true + height: 1 + color: Appearance.m3colors.m3outlineVariant + } + + ListView { // App results + id: appResults + visible: root.showResults + Layout.fillWidth: true + implicitHeight: Math.min(600, appResults.contentHeight + topMargin + bottomMargin) + clip: true + topMargin: 10 + bottomMargin: 10 + spacing: 2 + KeyNavigation.up: searchBar + highlightMoveDuration : 100 + + onFocusChanged: { + if(focus) appResults.currentIndex = 1; + } + + Connections { + target: root + function onSearchingTextChanged() { + if (appResults.count > 0) + appResults.currentIndex = 0; + } + } + + model: ScriptModel { + id: model + values: { // Search results are handled here + ////////////////// Skip? ////////////////// + if(root.searchingText == "") return []; + + ///////////// Special cases /////////////// + if (root.searchingText.startsWith(ConfigOptions.search.prefix.clipboard)) { // Clipboard + const searchString = root.searchingText.slice(ConfigOptions.search.prefix.clipboard.length); + return Cliphist.fuzzyQuery(searchString).map(entry => { + return { + cliphistRawString: entry, + name: entry.replace(/^\s*\S+\s+/, ""), + clickActionName: "", + type: `#${entry.match(/^\s*(\S+)/)?.[1] || ""}`, + execute: () => { + Hyprland.dispatch(`exec echo '${StringUtils.shellSingleQuoteEscape(entry)}' | cliphist decode | wl-copy`); + } + }; + }).filter(Boolean); + } + if (root.searchingText.startsWith(ConfigOptions.search.prefix.emojis)) { // Clipboard + const searchString = root.searchingText.slice(ConfigOptions.search.prefix.emojis.length); + return Emojis.fuzzyQuery(searchString).map(entry => { + return { + cliphistRawString: entry, + bigText: entry.match(/^\s*(\S+)/)?.[1] || "", + name: entry.replace(/^\s*\S+\s+/, ""), + clickActionName: "", + type: "Emoji", + execute: () => { + Hyprland.dispatch(`exec wl-copy '${StringUtils.shellSingleQuoteEscape(entry.match(/^\s*(\S+)/)?.[1])}'`); + } + }; + }).filter(Boolean); + } + + + ////////////////// Init /////////////////// + nonAppResultsTimer.restart(); + const mathResultObject = { + name: root.mathResult, + clickActionName: qsTr("Copy"), + type: qsTr("Math result"), + fontType: "monospace", + materialSymbol: 'calculate', + execute: () => { + Hyprland.dispatch(`exec wl-copy '${StringUtils.shellSingleQuoteEscape(root.mathResult)}'`) + } + } + const commandResultObject = { + name: searchingText.replace("file://", ""), + clickActionName: qsTr("Run"), + type: qsTr("Run command"), + fontType: "monospace", + materialSymbol: 'terminal', + execute: () => { + executor.executeCommand(searchingText.startsWith('sudo') ? `${ConfigOptions.apps.terminal} fish -C '${root.searchingText.replace("file://", "")}'` : root.searchingText.replace("file://", "")); + } + } + const launcherActionObjects = root.searchActions + .map(action => { + const actionString = `${ConfigOptions.search.prefix.action}${action.action}`; + if (actionString.startsWith(root.searchingText) || root.searchingText.startsWith(actionString)) { + return { + name: root.searchingText.startsWith(actionString) ? root.searchingText : actionString, + clickActionName: qsTr("Run"), + type: qsTr("Action"), + materialSymbol: 'settings_suggest', + execute: () => { + action.execute(root.searchingText.split(" ").slice(1).join(" ")) + }, + }; + } + return null; + }) + .filter(Boolean); + + let result = []; + + //////////////// Apps ////////////////// + result = result.concat( + AppSearch.fuzzyQuery(root.searchingText) + .map((entry) => { + entry.clickActionName = qsTr("Launch"); + entry.type = qsTr("App"); + return entry; + }) + ); + + ////////// Launcher actions //////////// + result = result.concat(launcherActionObjects); + + /////////// Math result & command ////////// + const startsWithNumber = /^\d/.test(root.searchingText); + if (startsWithNumber) { + result.push(mathResultObject); + result.push(commandResultObject); + } else { + result.push(commandResultObject); + result.push(mathResultObject); + } + + ///////////////// Web search //////////////// + result.push({ + name: root.searchingText, + clickActionName: qsTr("Search"), + type: qsTr("Search the web"), + materialSymbol: 'travel_explore', + execute: () => { + let url = ConfigOptions.search.engineBaseUrl + root.searchingText + for (let site of ConfigOptions.search.excludedSites) { + url += ` -site:${site}`; + } + Qt.openUrlExternally(url); + } + }); + + return result; + } + } + + delegate: SearchItem { // The selectable item for each search result + required property var modelData + anchors.left: parent?.left + anchors.right: parent?.right + entry: modelData + query: root.searchingText.startsWith(ConfigOptions.search.prefix.clipboard) ? + root.searchingText.slice(ConfigOptions.search.prefix.clipboard.length) : + root.searchingText; + } + } + + } + } +} \ No newline at end of file -- cgit v1.2.3 From 1462996cc5256e6337eafc226423de5559214c7f Mon Sep 17 00:00:00 2001 From: Kiran George Date: Mon, 16 Jun 2025 08:15:18 +0530 Subject: Expose font size and family in config --- config/quickshell/config.json | 24 ++++++++++++- config/quickshell/modules/common/ConfigOptions.qml | 1 + .../quickshell/modules/overview/OverviewWidget.qml | 4 ++- config/quickshell/services/ConfigLoader.qml | 39 +++++++++++++++++++--- 4 files changed, 62 insertions(+), 6 deletions(-) (limited to 'config/quickshell/modules/overview') diff --git a/config/quickshell/config.json b/config/quickshell/config.json index 33fca8e9..f6c9e843 100644 --- a/config/quickshell/config.json +++ b/config/quickshell/config.json @@ -15,7 +15,8 @@ "numOfCols": 5, "showXwaylandIndicator": true, "windowPadding": 6, - "position": 1 + "position": 1, + "workspaceNumberSize": 220 }, "resources": { "updateInterval": 3000 @@ -34,5 +35,26 @@ }, "bar": { "bottom": false + }, + "font": { + "family": { + "main": "Open Sans", + "title": "JetBrains Mono NF", + "iconMaterial": "FiraConde Nerd Font", + "iconNerd": "SpaceMono NF", + "monospace": "JetBrains Mono NF", + "reading": "Readex Pro" + }, + "pixelSize": { + "smallest": 10, + "smaller": 13, + "small": 15, + "normal": 16, + "large": 17, + "larger": 19, + "huge": 22, + "hugeass": 23, + "title": 28 + } } } \ No newline at end of file diff --git a/config/quickshell/modules/common/ConfigOptions.qml b/config/quickshell/modules/common/ConfigOptions.qml index 61e6ab8e..25f0de05 100644 --- a/config/quickshell/modules/common/ConfigOptions.qml +++ b/config/quickshell/modules/common/ConfigOptions.qml @@ -16,6 +16,7 @@ Singleton { property bool showXwaylandIndicator: true property real windowPadding: 6 property real position: 1 // 0: top | 1: middle | 2: bottom + property real workspaceNumberSize: 120 // Set 0, dynamic calculation based on monitor size } property QtObject resources: QtObject { diff --git a/config/quickshell/modules/overview/OverviewWidget.qml b/config/quickshell/modules/overview/OverviewWidget.qml index 73dbbc26..63633602 100644 --- a/config/quickshell/modules/overview/OverviewWidget.qml +++ b/config/quickshell/modules/overview/OverviewWidget.qml @@ -35,7 +35,9 @@ Item { ((monitor.height - monitorData?.reserved[1] - monitorData?.reserved[3]) * root.scale / monitor.scale)) property real workspaceNumberMargin: 80 - property real workspaceNumberSize: Math.min(workspaceImplicitHeight, workspaceImplicitWidth) * monitor.scale + property real workspaceNumberSize: (ConfigOptions.overview.workspaceNumberSize > 0) + ? ConfigOptions.overview.workspaceNumberSize + : Math.min(workspaceImplicitHeight, workspaceImplicitWidth) * monitor.scale property int workspaceZ: 0 property int windowZ: 1 property int windowDraggingZ: 99999 diff --git a/config/quickshell/services/ConfigLoader.qml b/config/quickshell/services/ConfigLoader.qml index 051162d1..5f16bf55 100644 --- a/config/quickshell/services/ConfigLoader.qml +++ b/config/quickshell/services/ConfigLoader.qml @@ -29,7 +29,32 @@ Singleton { try { const json = JSON.parse(fileContent); - ObjectUtils.applyToQtObject(ConfigOptions, json); + // Extract font configuration if it exists + let fontConfig = null; + let configForOptions = {}; + + // Copy all properties except font to configForOptions + for (let key in json) { + if (key !== "font") { + configForOptions[key] = json[key]; + } else { + fontConfig = json[key]; + } + } + + // Apply the non-font configuration to ConfigOptions + ObjectUtils.applyToQtObject(ConfigOptions, configForOptions); + + // Apply font configuration to Appearance if it exists + if (fontConfig && typeof Appearance !== 'undefined') { + if (fontConfig.family && Appearance.font && Appearance.font.family) { + ObjectUtils.applyToQtObject(Appearance.font.family, fontConfig.family); + } + if (fontConfig.pixelSize && Appearance.font && Appearance.font.pixelSize) { + ObjectUtils.applyToQtObject(Appearance.font.pixelSize, fontConfig.pixelSize); + } + } + if (root.firstLoad) { root.firstLoad = false; } else { @@ -39,13 +64,19 @@ Singleton { console.error("[ConfigLoader] Error reading file:", e); Hyprland.dispatch(`exec notify-send "${qsTr("Shell configuration failed to load")}" "${root.filePath}"`) return; - } } function setLiveConfigValue(nestedKey, value) { let keys = nestedKey.split("."); - let obj = ConfigOptions; + let targetObject = ConfigOptions; + + // Check if this is a font-related configuration + if (keys[0] === "font" && typeof Appearance !== 'undefined') { + targetObject = Appearance; + } + + let obj = targetObject; let parents = [obj]; // Traverse and collect parent objects @@ -113,4 +144,4 @@ Singleton { } } } -} +} \ No newline at end of file -- cgit v1.2.3 From 0cda8f13953d0f4cc6126d4810c04452cc3375b8 Mon Sep 17 00:00:00 2001 From: Kiran George Date: Sat, 21 Jun 2025 17:26:23 +0530 Subject: Refactored for better colour and font expose and cleaned up unused code --- config/quickshell/.claude/settings.local.json | 9 ++ config/quickshell/GlobalStates.qml | 2 - config/quickshell/config.json | 25 ++-- config/quickshell/modules/common/Appearance.qml | 75 +++++------- config/quickshell/modules/common/Directories.qml | 3 +- .../modules/common/functions/string_utils.js | 135 --------------------- .../modules/common/widgets/DialogButton.qml | 4 +- .../modules/common/widgets/MaterialSymbol.qml | 9 +- .../modules/common/widgets/StyledText.qml | 7 +- .../modules/common/widgets/StyledTextArea.qml | 10 +- .../modules/common/widgets/StyledToolTip.qml | 2 +- .../quickshell/modules/overview/OverviewWidget.qml | 6 +- .../quickshell/modules/overview/OverviewWindow.qml | 8 +- config/quickshell/modules/overview/SearchItem.qml | 24 ++-- .../quickshell/modules/overview/SearchWidget.qml | 18 +-- config/quickshell/qml_color.json | 17 +++ config/quickshell/services/ConfigLoader.qml | 5 +- config/quickshell/services/MaterialThemeLoader.qml | 2 +- config/quickshell/shell.qml | 1 - config/wallust/templates/qml_color.json | 34 +++--- 20 files changed, 128 insertions(+), 268 deletions(-) create mode 100644 config/quickshell/.claude/settings.local.json create mode 100644 config/quickshell/qml_color.json (limited to 'config/quickshell/modules/overview') diff --git a/config/quickshell/.claude/settings.local.json b/config/quickshell/.claude/settings.local.json new file mode 100644 index 00000000..d474da6e --- /dev/null +++ b/config/quickshell/.claude/settings.local.json @@ -0,0 +1,9 @@ +{ + "permissions": { + "allow": [ + "Bash(find:*)", + "Bash(grep:*)" + ], + "deny": [] + } +} \ No newline at end of file diff --git a/config/quickshell/GlobalStates.qml b/config/quickshell/GlobalStates.qml index 7875645c..84b53c03 100644 --- a/config/quickshell/GlobalStates.qml +++ b/config/quickshell/GlobalStates.qml @@ -1,8 +1,6 @@ import "root:/modules/common/" import QtQuick import Quickshell -import Quickshell.Hyprland -import Quickshell.Io pragma Singleton pragma ComponentBehavior: Bound diff --git a/config/quickshell/config.json b/config/quickshell/config.json index f6c9e843..cd9c2c4d 100644 --- a/config/quickshell/config.json +++ b/config/quickshell/config.json @@ -16,7 +16,7 @@ "showXwaylandIndicator": true, "windowPadding": 6, "position": 1, - "workspaceNumberSize": 220 + "workspaceNumberSize": 0 }, "resources": { "updateInterval": 3000 @@ -38,23 +38,16 @@ }, "font": { "family": { - "main": "Open Sans", - "title": "JetBrains Mono NF", - "iconMaterial": "FiraConde Nerd Font", - "iconNerd": "SpaceMono NF", - "monospace": "JetBrains Mono NF", - "reading": "Readex Pro" + "uiFont": "Open Sans", + "iconFont": "FiraConde Nerd Font", + "codeFont": "JetBrains Mono NF" }, "pixelSize": { - "smallest": 10, - "smaller": 13, - "small": 15, - "normal": 16, - "large": 17, - "larger": 19, - "huge": 22, - "hugeass": 23, - "title": 28 + "textSmall": 13, + "textBase": 15, + "textMedium": 16, + "textLarge": 19, + "iconLarge": 22 } } } \ No newline at end of file diff --git a/config/quickshell/modules/common/Appearance.qml b/config/quickshell/modules/common/Appearance.qml index 29eca00c..675f1d1e 100644 --- a/config/quickshell/modules/common/Appearance.qml +++ b/config/quickshell/modules/common/Appearance.qml @@ -18,53 +18,47 @@ Singleton { property real transparency: 0.5 property real contentTransparency: 0.1 property real workpaceTransparency: 0.8 - // property real transparency: 0.15 - // property real contentTransparency: 0.5 property string background_image: Directories.config + "/rofi/.current_wallpaper" m3colors: QtObject { property bool darkmode: true property bool transparent: true - property color m3background: "#161217" - property color m3onBackground: "#EAE0E7" - property color m3surfaceContainerLow: "#1F1A1F" - property color m3surfaceContainer: "#231E23" - property color m3surfaceContainerHigh: "#2D282E" - property color m3surfaceContainerHighest: "#383339" - property color m3onSurface: "#EAE0E7" - property color m3onSurfaceVariant: "#CFC3CD" - property color m3outline: "#cba6f7" - property color m3scrim: "#000000" - property color m3shadow: "#000000" - property color m3primary: "#E5B6F2" - property color m3primaryContainer: "#5D386A" - property color m3secondary: "#D5C0D7" - property color m3secondaryContainer: "#534457" - property color m3onPrimary: "#452152" - property color m3onPrimaryContainer: "#F9D8FF" - property color m3onSecondaryContainer: "#F2DCF3" - property color m3outlineVariant: "#4C444D" + property color m3windowBackground: "#161217" + property color m3primaryText: "#EAE0E7" + property color m3layerBackground1: "#1F1A1F" + property color m3layerBackground2: "#231E23" + property color m3layerBackground3: "#2D282E" + property color m3surfaceText: "#EAE0E7" + property color m3secondaryText: "#CFC3CD" + property color m3borderPrimary: "#cba6f7" + property color m3shadowColor: "#000000" + property color m3accentPrimary: "#E5B6F2" + property color m3accentSecondary: "#D5C0D7" + property color m3selectionBackground: "#534457" + property color m3accentPrimaryText: "#452152" + property color m3selectionText: "#F2DCF3" + property color m3borderSecondary: "#4C444D" property color colTooltip: "#1e1e2e" property color colOnTooltip: "#F8F9FA" } colors: QtObject { - property color colSubtext: m3colors.m3outline - property color colLayer0: ColorUtils.transparentize(m3colors.m3background, root.transparency) - property color colLayer1: ColorUtils.transparentize(ColorUtils.mix(m3colors.m3surfaceContainerLow, m3colors.m3background, 0.7), root.contentTransparency); - property color colOnLayer1: m3colors.m3onSurfaceVariant; - property color colLayer2: ColorUtils.transparentize(ColorUtils.mix(m3colors.m3surfaceContainer, m3colors.m3surfaceContainerHigh, 0.55), root.contentTransparency) - property color colOnLayer2: m3colors.m3onSurface; + property color colSubtext: m3colors.m3borderPrimary + property color colLayer0: ColorUtils.transparentize(m3colors.m3windowBackground, root.transparency) + property color colLayer1: ColorUtils.transparentize(ColorUtils.mix(m3colors.m3layerBackground1, m3colors.m3windowBackground, 0.7), root.contentTransparency); + property color colOnLayer1: m3colors.m3secondaryText; + property color colLayer2: ColorUtils.transparentize(ColorUtils.mix(m3colors.m3layerBackground2, m3colors.m3layerBackground3, 0.55), root.contentTransparency) + property color colOnLayer2: m3colors.m3surfaceText; 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 colPrimary: m3colors.m3primary + property color colPrimary: m3colors.m3accentPrimary property color colPrimaryHover: ColorUtils.mix(colors.colPrimary, colLayer1Hover, 0.87) property color colPrimaryActive: ColorUtils.mix(colors.colPrimary, colLayer1Active, 0.7) - property color colShadow: ColorUtils.transparentize(m3colors.m3shadow, 0.7) + property color colShadow: ColorUtils.transparentize(m3colors.m3shadowColor, 0.7) } rounding: QtObject { @@ -82,23 +76,16 @@ Singleton { font: QtObject { property QtObject family: QtObject { - property string main: "Open Sans" - property string title: "JetBrains Mono NF" - property string iconMaterial: "FiraConde Nerd Font" - property string iconNerd: "SpaceMono NF" - property string monospace: "JetBrains Mono NF" - property string reading: "Readex Pro" + property string uiFont: "Open Sans" + property string iconFont: "FiraConde Nerd Font" + property string codeFont: "JetBrains Mono NF" } property QtObject pixelSize: QtObject { - property int smallest: 10 - property int smaller: 13 - property int small: 15 - property int normal: 16 - property int large: 17 - property int larger: 19 - property int huge: 22 - property int hugeass: 23 - property int title: 28 + property int textSmall: 13 + property int textBase: 15 + property int textMedium: 16 + property int textLarge: 19 + property int iconLarge: 22 } } diff --git a/config/quickshell/modules/common/Directories.qml b/config/quickshell/modules/common/Directories.qml index 9ddf43bd..694c73df 100644 --- a/config/quickshell/modules/common/Directories.qml +++ b/config/quickshell/modules/common/Directories.qml @@ -11,9 +11,10 @@ Singleton { // XDG Dirs, with "file://" readonly property string config: StandardPaths.standardLocations(StandardPaths.ConfigLocation)[0] readonly property string state: StandardPaths.standardLocations(StandardPaths.StateLocation)[0] + readonly property string gen_cache: StandardPaths.standardLocations(StandardPaths.GenericCacheLocation)[0] // Other dirs used by the shell, without "file://" property string shellConfig: FileUtils.trimFileProtocol(`${Directories.config}/quickshell`) property string shellConfigPath: `${Directories.shellConfig}/config.json` - property string generatedMaterialThemePath: `${Directories.shellConfig}/qml_color.json` + property string generatedMaterialThemePath: `${Directories.gen_cache}/hellwal/qml_color.json` } diff --git a/config/quickshell/modules/common/functions/string_utils.js b/config/quickshell/modules/common/functions/string_utils.js index c22671eb..c31edf49 100644 --- a/config/quickshell/modules/common/functions/string_utils.js +++ b/config/quickshell/modules/common/functions/string_utils.js @@ -42,141 +42,6 @@ function shellSingleQuoteEscape(str) { .replace(/'/g, "'\\''"); } -/** - * Splits markdown blocks into three different types: text, think, and code. - * @param { string } markdown - */ -function splitMarkdownBlocks(markdown) { - const regex = /```(\w+)?\n([\s\S]*?)```|([\s\S]*?)<\/think>/g; - /** - * @type {{type: "text" | "think" | "code"; content: string; lang: string | undefined; completed: boolean | undefined}[]} - */ - let result = []; - let lastIndex = 0; - let match; - while ((match = regex.exec(markdown)) !== null) { - if (match.index > lastIndex) { - const text = markdown.slice(lastIndex, match.index); - if (text.trim()) { - result.push({ type: "text", content: text }); - } - } - if (match[0].startsWith('```')) { - if (match[2] && match[2].trim()) { - result.push({ type: "code", lang: match[1] || "", content: match[2], completed: true }); - } - } else if (match[0].startsWith('')) { - if (match[3] && match[3].trim()) { - result.push({ type: "think", content: match[3], completed: true }); - } - } - lastIndex = regex.lastIndex; - } - // Handle any remaining text after the last match - if (lastIndex < markdown.length) { - const text = markdown.slice(lastIndex); - // Check for unfinished block - const thinkStart = text.indexOf(''); - const codeStart = text.indexOf('```'); - if ( - thinkStart !== -1 && - (codeStart === -1 || thinkStart < codeStart) - ) { - const beforeThink = text.slice(0, thinkStart); - if (beforeThink.trim()) { - result.push({ type: "text", content: beforeThink }); - } - const thinkContent = text.slice(thinkStart + 7); - if (thinkContent.trim()) { - result.push({ type: "think", content: thinkContent, completed: false }); - } - } else if (codeStart !== -1) { - const beforeCode = text.slice(0, codeStart); - if (beforeCode.trim()) { - result.push({ type: "text", content: beforeCode }); - } - // Try to detect language after ``` - const codeLangMatch = text.slice(codeStart + 3).match(/^(\w+)?\n/); - let lang = ""; - let codeContentStart = codeStart + 3; - if (codeLangMatch) { - lang = codeLangMatch[1] || ""; - codeContentStart += codeLangMatch[0].length; - } else if (text[codeStart + 3] === '\n') { - codeContentStart += 1; - } - const codeContent = text.slice(codeContentStart); - if (codeContent.trim()) { - result.push({ type: "code", lang, content: codeContent, completed: false }); - } - } else if (text.trim()) { - result.push({ type: "text", content: text }); - } - } - // console.log(JSON.stringify(result, null, 2)); - return result; -} - -/** - * Returns the original string with backslashes escaped - * @param { string } str - * @returns { string } - */ -function escapeBackslashes(str) { - return str.replace(/\\/g, '\\\\'); -} - -/** - * Wraps words to supplied maximum length - * @param { string | null } str - * @param { number } maxLen - * @returns { string } - */ -function wordWrap(str, maxLen) { - if (!str) return ""; - let words = str.split(" "); - let lines = []; - let current = ""; - for (let i = 0; i < words.length; ++i) { - if ((current + (current.length > 0 ? " " : "") + words[i]).length > maxLen) { - if (current.length > 0) lines.push(current); - current = words[i]; - } else { - current += (current.length > 0 ? " " : "") + words[i]; - } - } - if (current.length > 0) lines.push(current); - return lines.join("\n"); -} - -function cleanMusicTitle(title) { - if (!title) return ""; - // Brackets - title = title.replace(/^ *\([^)]*\) */g, " "); // Round brackets - title = title.replace(/^ *\[[^\]]*\] */g, " "); // Square brackets - title = title.replace(/^ *\{[^\}]*\} */g, " "); // Curly brackets - // Japenis brackets - title = title.replace(/^ *【[^】]*】/, "") // Touhou - title = title.replace(/^ *《[^》]*》/, "") // ?? - title = title.replace(/^ *「[^」]*」/, "") // OP/ED - title = title.replace(/^ *『[^』]*』/, "") // OP/ED - - return title; -} - -function friendlyTimeForSeconds(seconds) { - if (isNaN(seconds) || seconds < 0) return "0:00"; - seconds = Math.floor(seconds); - const h = Math.floor(seconds / 3600); - const m = Math.floor((seconds % 3600) / 60); - const s = seconds % 60; - if (h > 0) { - return `${h}:${m.toString().padStart(2, '0')}:${s.toString().padStart(2, '0')}`; - } else { - return `${m}:${s.toString().padStart(2, '0')}`; - } -} - function escapeHtml(str) { if (typeof str !== 'string') return str; return str diff --git a/config/quickshell/modules/common/widgets/DialogButton.qml b/config/quickshell/modules/common/widgets/DialogButton.qml index 9e19a507..b799336a 100644 --- a/config/quickshell/modules/common/widgets/DialogButton.qml +++ b/config/quickshell/modules/common/widgets/DialogButton.qml @@ -18,7 +18,7 @@ RippleButton { buttonRadius: Appearance?.rounding.full ?? 9999 property color colEnabled: Appearance?.colors.colPrimary - property color colDisabled: Appearance?.m3colors.m3outline + property color colDisabled: Appearance?.m3colors.m3borderPrimary contentItem: StyledText { id: buttonTextWidget @@ -27,7 +27,7 @@ RippleButton { anchors.rightMargin: 15 text: buttonText horizontalAlignment: Text.AlignHCenter - font.pixelSize: Appearance?.font.pixelSize.small ?? 12 + font.pixelSize: Appearance?.font.pixelSize.textBase ?? 12 color: button.enabled ? button.colEnabled : button.colDisabled Behavior on color { diff --git a/config/quickshell/modules/common/widgets/MaterialSymbol.qml b/config/quickshell/modules/common/widgets/MaterialSymbol.qml index dbbfff00..214f838e 100644 --- a/config/quickshell/modules/common/widgets/MaterialSymbol.qml +++ b/config/quickshell/modules/common/widgets/MaterialSymbol.qml @@ -1,17 +1,16 @@ import "root:/modules/common/" import QtQuick -import QtQuick.Layouts Text { id: root - property real iconSize: Appearance?.font.pixelSize.small ?? 16 + property real iconSize: Appearance?.font.pixelSize.textBase ?? 16 property real fill: 0 renderType: Text.NativeRendering font.hintingPreference: Font.PreferFullHinting verticalAlignment: Text.AlignVCenter - font.family: Appearance?.font.family.iconMaterial ?? "Material Symbols Rounded" + font.family: Appearance?.font.family.iconFont ?? "Material Symbols Rounded" font.pixelSize: iconSize - color: Appearance.m3colors.m3onBackground + color: Appearance.m3colors.m3primaryText Behavior on fill { NumberAnimation { @@ -23,8 +22,6 @@ Text { font.variableAxes: { "FILL": fill, - // "wght": font.weight, - // "GRAD": 0, "opsz": iconSize, } } diff --git a/config/quickshell/modules/common/widgets/StyledText.qml b/config/quickshell/modules/common/widgets/StyledText.qml index 6eef5785..988c136d 100644 --- a/config/quickshell/modules/common/widgets/StyledText.qml +++ b/config/quickshell/modules/common/widgets/StyledText.qml @@ -1,14 +1,13 @@ import "root:/modules/common" import QtQuick -import QtQuick.Layouts Text { renderType: Text.NativeRendering verticalAlignment: Text.AlignVCenter font { hintingPreference: Font.PreferFullHinting - family: Appearance?.font.family.main ?? "sans-serif" - pixelSize: Appearance?.font.pixelSize.small ?? 15 + family: Appearance?.font.family.uiFont ?? "sans-serif" + pixelSize: Appearance?.font.pixelSize.textBase ?? 15 } - color: Appearance?.m3colors.m3onBackground ?? "black" + color: Appearance?.m3colors.m3primaryText ?? "black" } diff --git a/config/quickshell/modules/common/widgets/StyledTextArea.qml b/config/quickshell/modules/common/widgets/StyledTextArea.qml index 1ea9a349..af3cf34d 100644 --- a/config/quickshell/modules/common/widgets/StyledTextArea.qml +++ b/config/quickshell/modules/common/widgets/StyledTextArea.qml @@ -4,12 +4,12 @@ import QtQuick.Controls TextArea { renderType: Text.NativeRendering - selectedTextColor: Appearance.m3colors.m3onSecondaryContainer - selectionColor: Appearance.m3colors.m3secondaryContainer - placeholderTextColor: Appearance.m3colors.m3outline + selectedTextColor: Appearance.m3colors.m3selectionText + selectionColor: Appearance.m3colors.m3selectionBackground + placeholderTextColor: Appearance.m3colors.m3borderPrimary font { - family: Appearance?.font.family.main ?? "sans-serif" - pixelSize: Appearance?.font.pixelSize.small ?? 15 + family: Appearance?.font.family.uiFont ?? "sans-serif" + pixelSize: Appearance?.font.pixelSize.textBase ?? 15 hintingPreference: Font.PreferFullHinting } } diff --git a/config/quickshell/modules/common/widgets/StyledToolTip.qml b/config/quickshell/modules/common/widgets/StyledToolTip.qml index 2ca16df1..aaaad813 100644 --- a/config/quickshell/modules/common/widgets/StyledToolTip.qml +++ b/config/quickshell/modules/common/widgets/StyledToolTip.qml @@ -50,7 +50,7 @@ ToolTip { id: tooltipTextObject anchors.centerIn: parent text: content - font.pixelSize: Appearance?.font.pixelSize.smaller ?? 14 + font.pixelSize: Appearance?.font.pixelSize.textSmall ?? 14 font.hintingPreference: Font.PreferNoHinting // Prevent shaky text color: Appearance?.m3colors.colOnTooltip ?? "#FFFFFF" wrapMode: Text.Wrap diff --git a/config/quickshell/modules/overview/OverviewWidget.qml b/config/quickshell/modules/overview/OverviewWidget.qml index 63633602..2ea8d58a 100644 --- a/config/quickshell/modules/overview/OverviewWidget.qml +++ b/config/quickshell/modules/overview/OverviewWidget.qml @@ -25,7 +25,7 @@ Item { property var windowAddresses: HyprlandData.addresses property var monitorData: HyprlandData.monitors.find(m => m.id === root.monitor.id) property real scale: ConfigOptions.overview.scale - property color activeBorderColor: Appearance.m3colors.m3secondary + property color activeBorderColor: Appearance.m3colors.m3accentSecondary property real workspaceImplicitWidth: Math.max(100, (monitorData?.transform % 2 === 1) ? ((monitor.height - monitorData?.reserved[0] - monitorData?.reserved[2]) * root.scale / monitor.scale) : @@ -71,7 +71,7 @@ Item { property real padding: 10 anchors.fill: parent anchors.margins: Appearance.sizes.elevationMargin - border.color : ColorUtils.transparentize(Appearance.m3colors.m3outline, 0.2) + border.color : ColorUtils.transparentize(Appearance.m3colors.m3borderPrimary, 0.2) border.width : 2 implicitWidth: workspaceColumnLayout.implicitWidth + padding * 2 @@ -170,7 +170,7 @@ Item { color: "transparent" radius: parent.radius border.width: 1 - border.color: hoveredWhileDragging ? hoveredBorderColor : ColorUtils.transparentize(Appearance.m3colors.m3outline, 0.6) + border.color: hoveredWhileDragging ? hoveredBorderColor : ColorUtils.transparentize(Appearance.m3colors.m3borderPrimary, 0.6) z: 10 // Ensure it's on top } diff --git a/config/quickshell/modules/overview/OverviewWindow.qml b/config/quickshell/modules/overview/OverviewWindow.qml index 273eff7e..449a98c4 100644 --- a/config/quickshell/modules/overview/OverviewWindow.qml +++ b/config/quickshell/modules/overview/OverviewWindow.qml @@ -32,7 +32,7 @@ Rectangle { // Window property var xwaylandIndicatorToIconRatio: 0.35 property var iconToWindowRatioCompact: 0.6 property var iconPath: Quickshell.iconPath(AppSearch.guessIcon(windowData?.class), "image-missing") - property bool compactMode: Appearance.font.pixelSize.smaller * 4 > targetWindowHeight || Appearance.font.pixelSize.smaller * 4 > targetWindowWidth + property bool compactMode: Appearance.font.pixelSize.textSmall * 4 > targetWindowHeight || Appearance.font.pixelSize.textSmall * 4 > targetWindowWidth property bool indicateXWayland: (ConfigOptions.overview.showXwaylandIndicator && windowData?.xwayland) ?? false @@ -44,7 +44,7 @@ Rectangle { // Window radius: Appearance.rounding.windowRounding * root.scale color: pressed ? Appearance.colors.colLayer2Active : hovered ? Appearance.colors.colLayer2Hover : Appearance.colors.colLayer2 // border.color : ColorUtils.transparentize(Appearance.m3colors.m3outline, 0.9) - border.color : ColorUtils.transparentize(Appearance.m3colors.m3outline, 0.4) + border.color : ColorUtils.transparentize(Appearance.m3colors.m3borderPrimary, 0.4) border.pixelAligned : false border.width : 2 @@ -65,7 +65,7 @@ Rectangle { // Window anchors.verticalCenter: parent.verticalCenter anchors.left: parent.left anchors.right: parent.right - spacing: Appearance.font.pixelSize.smaller * 0.5 + spacing: Appearance.font.pixelSize.textSmall * 0.5 IconImage { id: windowIcon @@ -85,7 +85,7 @@ Rectangle { // Window Layout.fillWidth: true Layout.fillHeight: true horizontalAlignment: Text.AlignHCenter - font.pixelSize: Appearance.font.pixelSize.smaller + font.pixelSize: Appearance.font.pixelSize.textSmall font.italic: indicateXWayland ? true : false elide: Text.ElideRight text: windowData?.title ?? "" diff --git a/config/quickshell/modules/overview/SearchItem.qml b/config/quickshell/modules/overview/SearchItem.qml index 1363b88d..1357d03c 100644 --- a/config/quickshell/modules/overview/SearchItem.qml +++ b/config/quickshell/modules/overview/SearchItem.qml @@ -22,7 +22,7 @@ RippleButton { property string itemName: entry?.name property string itemIcon: entry?.icon ?? "" property var itemExecute: entry?.execute - property string fontType: entry?.fontType ?? "main" + property string fontType: entry?.fontType ?? "uiFont" property string itemClickActionName: entry?.clickActionName property string bigText: entry?.bigText ?? "" property string materialSymbol: entry?.materialSymbol ?? "" @@ -31,7 +31,7 @@ RippleButton { property string highlightPrefix: `` property string highlightSuffix: `` function highlightContent(content, query) { - if (!query || query.length === 0 || content == query || fontType === "monospace") + if (!query || query.length === 0 || content == query || fontType === "codeFont") return StringUtils.escapeHtml(content); let contentLower = content.toLowerCase(); @@ -80,7 +80,7 @@ RippleButton { buttonRadius: Appearance.rounding.normal colBackground: (root.down || root.keyboardDown) ? Appearance.colors.colLayer1Active : ((root.hovered || root.focus) ? Appearance.colors.colLayer1Hover : - ColorUtils.transparentize(Appearance.m3colors.m3surfaceContainerHigh, 1)) + ColorUtils.transparentize(Appearance.m3colors.m3layerBackground3, 1)) colBackgroundHover: Appearance.colors.colLayer1Hover colRipple: Appearance.colors.colLayer1Active @@ -140,7 +140,7 @@ RippleButton { MaterialSymbol { text: root.materialSymbol iconSize: 30 - color: Appearance.m3colors.m3onSurface + color: Appearance.m3colors.m3surfaceText } } @@ -148,8 +148,8 @@ RippleButton { id: bigTextComponent StyledText { text: root.bigText - font.pixelSize: Appearance.font.pixelSize.larger - color: Appearance.m3colors.m3onSurface + font.pixelSize: Appearance.font.pixelSize.textLarge + color: Appearance.m3colors.m3surfaceText } } @@ -160,7 +160,7 @@ RippleButton { Layout.alignment: Qt.AlignVCenter spacing: 0 StyledText { - font.pixelSize: Appearance.font.pixelSize.smaller + font.pixelSize: Appearance.font.pixelSize.textSmall color: Appearance.colors.colSubtext visible: root.itemType && root.itemType != qsTr("App") text: root.itemType @@ -178,8 +178,8 @@ RippleButton { id: activeText anchors.centerIn: parent text: "check" - font.pixelSize: Appearance.font.pixelSize.normal - color: Appearance.m3colors.m3onPrimary + font.pixelSize: Appearance.font.pixelSize.textMedium + color: Appearance.m3colors.m3accentPrimaryText } } } @@ -187,9 +187,9 @@ RippleButton { Layout.fillWidth: true id: nameText textFormat: Text.StyledText // RichText also works, but StyledText ensures elide work - font.pixelSize: Appearance.font.pixelSize.small + font.pixelSize: Appearance.font.pixelSize.textBase font.family: Appearance.font.family[root.fontType] - color: Appearance.m3colors.m3onSurface + color: Appearance.m3colors.m3surfaceText horizontalAlignment: Text.AlignLeft elide: Text.ElideRight text: `${root.displayContent}` @@ -211,7 +211,7 @@ RippleButton { Layout.fillWidth: false visible: (root.hovered || root.focus) id: clickAction - font.pixelSize: Appearance.font.pixelSize.normal + font.pixelSize: Appearance.font.pixelSize.textMedium color: Appearance.colors.colSubtext horizontalAlignment: Text.AlignRight text: root.itemClickActionName diff --git a/config/quickshell/modules/overview/SearchWidget.qml b/config/quickshell/modules/overview/SearchWidget.qml index fed710ec..f84aa558 100644 --- a/config/quickshell/modules/overview/SearchWidget.qml +++ b/config/quickshell/modules/overview/SearchWidget.qml @@ -201,8 +201,8 @@ Item { // Wrapper MaterialSymbol { id: searchIcon Layout.leftMargin: 15 - iconSize: Appearance.font.pixelSize.huge - color: Appearance.m3colors.m3onSurface + iconSize: Appearance.font.pixelSize.iconLarge + color: Appearance.m3colors.m3surfaceText text: root.searchingText.startsWith(ConfigOptions.search.prefix.clipboard) ? 'content_paste_search' : '' } TextField { // Search box @@ -213,15 +213,15 @@ Item { // Wrapper padding: 15 renderType: Text.NativeRendering font { - family: Appearance?.font.family.main ?? "sans-serif" - pixelSize: Appearance?.font.pixelSize.small ?? 15 + family: Appearance?.font.family.uiFont ?? "sans-serif" + pixelSize: Appearance?.font.pixelSize.textBase ?? 15 hintingPreference: Font.PreferFullHinting } - color: activeFocus ? Appearance.m3colors.m3onSurface : Appearance.m3colors.m3onSurfaceVariant - selectedTextColor: Appearance.m3colors.m3onSecondaryContainer - selectionColor: Appearance.m3colors.m3secondaryContainer + color: activeFocus ? Appearance.m3colors.m3surfaceText : Appearance.m3colors.m3secondaryText + selectedTextColor: Appearance.m3colors.m3selectionText + selectionColor: Appearance.m3colors.m3selectionBackground placeholderText: qsTr("Search, calculate or run") - placeholderTextColor: Appearance.m3colors.m3outline + placeholderTextColor: Appearance.m3colors.m3borderPrimary implicitWidth: root.searchingText == "" ? Appearance.sizes.searchWidthCollapsed : Appearance.sizes.searchWidth Behavior on implicitWidth { @@ -260,7 +260,7 @@ Item { // Wrapper visible: root.showResults Layout.fillWidth: true height: 1 - color: Appearance.m3colors.m3outlineVariant + color: Appearance.m3colors.m3borderSecondary } ListView { // App results diff --git a/config/quickshell/qml_color.json b/config/quickshell/qml_color.json new file mode 100644 index 00000000..888ba3e6 --- /dev/null +++ b/config/quickshell/qml_color.json @@ -0,0 +1,17 @@ +{ + "windowBackground": "#0f0f15", + "primaryText": "#bac2de", + "layerBackground1": "#1F1A1F", + "layerBackground2": "#231E23", + "layerBackground3": "#2D282E", + "surfaceText": "#EAE0E7", + "secondaryText": "#CFC3CD", + "borderPrimary": "#cba6f7", + "shadowColor": "#000000", + "accentPrimary": "#6750A4", + "accentSecondary": "#D5C0D7", + "selectionBackground": "#534457", + "accentPrimaryText": "#FFFFFF", + "selectionText": "#F2DCF3", + "borderSecondary": "#4C444D" +} \ No newline at end of file diff --git a/config/quickshell/services/ConfigLoader.qml b/config/quickshell/services/ConfigLoader.qml index 5f16bf55..d3fb4e26 100644 --- a/config/quickshell/services/ConfigLoader.qml +++ b/config/quickshell/services/ConfigLoader.qml @@ -72,7 +72,7 @@ Singleton { let targetObject = ConfigOptions; // Check if this is a font-related configuration - if (keys[0] === "font" && typeof Appearance !== 'undefined') { + if (keys[0] === "font") { targetObject = Appearance; } @@ -101,13 +101,12 @@ Singleton { } } - console.log(parents.join(".")); console.log(`[ConfigLoader] Setting live config value: ${nestedKey} = ${convertedValue}`); obj[keys[keys.length - 1]] = convertedValue; } function saveConfig() { - const plainConfig = ObjectUtils.toPlainObject(ConfigOptions) + const plainConfig = ObjectUtils.toPlainObject(ConfigOptions); Hyprland.dispatch(`exec echo '${StringUtils.shellSingleQuoteEscape(JSON.stringify(plainConfig, null, 2))}' > '${root.filePath}'`) } diff --git a/config/quickshell/services/MaterialThemeLoader.qml b/config/quickshell/services/MaterialThemeLoader.qml index cd4eb686..2d67ad5b 100644 --- a/config/quickshell/services/MaterialThemeLoader.qml +++ b/config/quickshell/services/MaterialThemeLoader.qml @@ -29,7 +29,7 @@ Singleton { } } - Appearance.m3colors.darkmode = (Appearance.m3colors.m3background.hslLightness < 0.5) + Appearance.m3colors.darkmode = (Appearance.m3colors.m3windowBackground.hslLightness < 0.5) } Timer { diff --git a/config/quickshell/shell.qml b/config/quickshell/shell.qml index 36842c2b..b14c8773 100644 --- a/config/quickshell/shell.qml +++ b/config/quickshell/shell.qml @@ -7,7 +7,6 @@ import "./modules/overview/" import QtQuick import QtQuick.Controls import QtQuick.Layouts -import QtQuick.Window import Quickshell import "./services/" diff --git a/config/wallust/templates/qml_color.json b/config/wallust/templates/qml_color.json index 70283080..03565181 100644 --- a/config/wallust/templates/qml_color.json +++ b/config/wallust/templates/qml_color.json @@ -1,21 +1,17 @@ { - "background": "#1e1e2e", - "onBackground": "#bac2de", - "surfaceContainerLow": "{{color4}}", - "surfaceContainer": "{{color6}}", - "surfaceContainerHigh": "{{color3}}", - "surfaceContainerHighest": "{{color2}}", - "onSurface": "#EAE0E7", - "onSurfaceVariant": "#CFC3CD", - "outline": "{{color7}}", - "scrim": "#000000", - "shadow": "#000000", - "primary": "{{color7}}", - "primaryContainer": "{{color7}}", - "secondary": "#D5C0D7", - "secondaryContainer": "{{color5}}", - "onPrimary": "#FFFFFF", - "onPrimaryContainer": "#21005D", - "onSecondaryContainer": "#F2DCF3", - "outlineVariant": "{{color5}}" + "windowBackground": "#0f0f15", + "primaryText": "#bac2de", + "layerBackground1": "{{color7}}", + "layerBackground2": "{{color6}}", + "layerBackground3": "#2D282E", + "surfaceText": "#EAE0E7", + "secondaryText": "#CFC3CD", + "borderPrimary": "{{color7}}", + "shadowColor": "#000000", + "accentPrimary": "{{color7}}", + "accentSecondary": "#{{color7}}", + "selectionBackground": "{{color7}}", + "accentPrimaryText": "#FFFFFF", + "selectionText": "#F2DCF3", + "borderSecondary": "#{{color5}}" } \ No newline at end of file -- cgit v1.2.3