diff --git a/.config/quickshell/ii/GlobalStates.qml b/.config/quickshell/ii/GlobalStates.qml index f2836c99b..207b23fe3 100644 --- a/.config/quickshell/ii/GlobalStates.qml +++ b/.config/quickshell/ii/GlobalStates.qml @@ -10,6 +10,7 @@ pragma ComponentBehavior: Bound Singleton { id: root property bool barOpen: true + property bool crosshairOpen: false property bool sidebarLeftOpen: false property bool sidebarRightOpen: false property bool mediaControlsOpen: false diff --git a/.config/quickshell/ii/modules/common/Config.qml b/.config/quickshell/ii/modules/common/Config.qml index d5e0a440a..878fdcd02 100644 --- a/.config/quickshell/ii/modules/common/Config.qml +++ b/.config/quickshell/ii/modules/common/Config.qml @@ -215,6 +215,11 @@ Singleton { property bool autoKillTrays: false } + property JsonObject crosshair: JsonObject { + // Valorant crosshair format. Use https://www.vcrdb.net/builder + property string code: "0;P;d;1;0l;10;0o;2;1b;0" + } + property JsonObject dock: JsonObject { property bool enable: false property bool monochromeIcons: true diff --git a/.config/quickshell/ii/modules/crosshair/Crosshair.qml b/.config/quickshell/ii/modules/crosshair/Crosshair.qml new file mode 100644 index 000000000..31c25f0dc --- /dev/null +++ b/.config/quickshell/ii/modules/crosshair/Crosshair.qml @@ -0,0 +1,57 @@ +import qs +import qs.modules.common +import qs.modules.common.widgets +import qs.services +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell +import Quickshell.Io +import Quickshell.Wayland +import Quickshell.Hyprland + +Scope { + id: root + + Loader { + id: crosshairLoader + active: GlobalStates.crosshairOpen + sourceComponent: PanelWindow { + id: crosshairWindow + exclusionMode: ExclusionMode.Ignore + WlrLayershell.namespace: "quickshell:crosshair" + WlrLayershell.layer: WlrLayer.Overlay + visible: true + color: "transparent" + + mask: Region { // Crosshair should not block mouse input + item: null + } + + implicitWidth: crosshairContent.implicitWidth + implicitHeight: crosshairContent.implicitHeight + + CrosshairContent { + id: crosshairContent + anchors.centerIn: parent + } + } + } + + IpcHandler { + target: "sidebarRight" + + function toggle(): void { + GlobalStates.crosshairOpen = !GlobalStates.crosshairOpen; + } + } + + GlobalShortcut { + name: "crosshairToggle" + description: "Toggles crosshair on press" + + onPressed: { + GlobalStates.crosshairOpen = !GlobalStates.crosshairOpen; + } + } +} diff --git a/.config/quickshell/ii/modules/crosshair/CrosshairContent.qml b/.config/quickshell/ii/modules/crosshair/CrosshairContent.qml new file mode 100644 index 000000000..668a90867 --- /dev/null +++ b/.config/quickshell/ii/modules/crosshair/CrosshairContent.qml @@ -0,0 +1,197 @@ +pragma ComponentBehavior: Bound +import QtQuick +import qs.modules.common +import qs.modules.common.functions + +Item { + id: root + + // Keys to props + // f, 0f, 1f, m are irrelevant as they're firing error stuff + // 0 is irrelevant because it's some profile stuff + property var propertyMap: ({ + "c": "color", + "u": "colorCode", + "h": "outline", + "o": "outlineOpacity", + "t": "outlineThickness", + "d": "centerDot", + "a": "centerDotOpacity", + "z": "centerDotSize", + "0a": "innerLineOpacity", + "0l": "innerLineLength", + "0v": "innerLineVerticalLength", + "0g": "innerLineUnbindAxesLengths", + "0t": "innerLineThickness", + "0o": "innerLineOffset", + "1b": "outerLines", + "1a": "outerLineOpacity", + "1l": "outerLineLength", + "1v": "outerLineVerticalLength", + "1g": "outerLineUnbindAxesLengths", + "1t": "outerLineThickness", + "1o": "outerLineOffset", + }) + property var colorMap: ({ + 0: "#FFFFFF", + 1: "#00FF00", + 2: "#7FFF00", + 3: "#DFFF00", + 4: "#FFFF00", + 5: "#00FFFF", + 6: "#FF00FF", + 7: "#FF0000" + }) + + // Raw props + property int color: 0 + property string colorCode: "#FFFFFF" + property bool outline: true + property real outlineOpacity: 0.5 + property int outlineThickness: 1 + property bool centerDot: false + property real centerDotOpacity: 1 + property int centerDotSize: 2 + property bool innerLines: true + property real innerLineOpacity: 0.8 + property int innerLineLength: 6 + property int innerLineVerticalLength: innerLineLength + property bool innerLineUnbindAxesLengths: false + property int innerLineThickness: 2 + property int innerLineOffset: 3 + property bool outerLines: true + property real outerLineOpacity: 0.35 + property int outerLineLength: 2 + property int outerLineVerticalLength: outerLineLength + property bool outerLineUnbindAxesLengths: false + property int outerLineThickness: 2 + property int outerLineOffset: 10 + property string defaultCode: "c;0;u;FFFFFF;h;1;o;0.5;t;1;d;0;a;1;z;2;0a;0.8;0l;6;0v;6;0g;0;0t;2;0o;3;1b;1;1a;0.35;1l;2;1v;2;1g;0;1t;2;1o;10" + + function loadFromCode(code: string): void { + let args = code.split(";"); + for (let i = 0; i < args.length; i+= 2) { + let key = args[i]; + let value = args[i+1]; + let targetKey = root.propertyMap[key]; + let targetType = typeof root[targetKey]; + + if (targetKey === undefined) continue; + + if (targetType === "number") { + value = parseFloat(value); + } else if (targetType === "boolean") { + value = (value === "1"); + } + if (targetKey === "colorCode") { + value = "#" + value.slice(0, 6); + } + root[targetKey] = value; + } + + if (!root.innerLineUnbindAxesLengths) { + root.innerLineVerticalLength = root.innerLineLength; + } + if (!root.outerLineUnbindAxesLengths) { + root.outerLineVerticalLength = root.outerLineLength; + } + + } + + // Update values from code + property var code: Config.options.crosshair.code + Component.onCompleted: reloadFromCode(); + onCodeChanged: reloadFromCode(); + function reloadFromCode() { + root.loadFromCode(root.defaultCode); + root.loadFromCode(root.code); + } + + // Aggregated props + property color crosshairColor: { + if (colorMap[color] !== undefined) return root.colorMap[color]; + if (color === 8) return colorCode; + return "#FFFFFF"; + } + property int borderWidth: outline ? outlineThickness : 0 + property color borderColor: ColorUtils.transparentize("black", 1 - root.outlineOpacity) + property color innerLineColor: ColorUtils.transparentize(root.crosshairColor, 1 - root.innerLineOpacity) + property color outerLineColor: ColorUtils.transparentize(root.crosshairColor, 1 - root.outerLineOpacity) + property int innerLineTotalOffset: root.centerDotSize / 2 + 1 + root.innerLineOffset + property int outerLineTotalOffset: root.centerDotSize / 2 + 1 + root.outerLineOffset + property real centerDotTotalSize: root.centerDotSize + root.borderWidth * 2 + property real innerLineTotalSize: (innerLineTotalOffset + root.innerLineLength + root.borderWidth) * 2 + property real outerLineTotalSize: (outerLineTotalOffset + root.outerLineLength + root.borderWidth) * 2 + implicitWidth: Math.max(centerDotTotalSize, innerLineTotalSize, outerLineTotalSize) + 2 // 2 for pixel correction + implicitHeight: implicitWidth + // width: implicitWidth + // height: implicitHeight + + Rectangle { + id: centerDot + visible: root.centerDot + anchors.centerIn: parent + + color: root.crosshairColor + opacity: root.centerDotOpacity + width: centerDotTotalSize + height: width + + border.width: root.borderWidth + border.color: root.borderColor + } + + Repeater { + id: innerLines + model: 4 + Item { + id: innerHair + z: index % 2 // Vertical lines above horizontal lines + required property int index + property int pixelCorrection: (root.innerLineThickness % 2 === 1 && index > 1) ? 1 : 0 + property int hairLength: (innerHair.index % 2 === 0 ? root.innerLineLength : root.innerLineVerticalLength) + visible: root.innerLines && hairLength > 0 + anchors.fill: parent + rotation: index * 90 + Rectangle { + x: parent.width / 2 + root.innerLineTotalOffset - root.borderWidth + innerHair.pixelCorrection + y: parent.height / 2 - height / 2 + + color: root.innerLineColor + width: innerHair.hairLength + root.borderWidth * 2 + height: root.innerLineThickness + root.borderWidth * 2 + + border.width: root.borderWidth + border.color: root.borderColor + } + } + } + + Repeater { + id: outerLines + model: 4 + Item { + id: outerHair + z: index % 2 + 2 // Vertical lines above horizontal lines, above inner lines + required property int index + property int pixelCorrection: (root.outerLineThickness % 2 === 1 && index > 1) ? 1 : 0 + property int hairLength: (outerHair.index % 2 === 0 ? root.outerLineLength : root.outerLineVerticalLength) + visible: root.outerLines && hairLength > 0 + anchors.fill: parent + rotation: index * 90 + Rectangle { + x: parent.width / 2 + root.outerLineTotalOffset - root.borderWidth + outerHair.pixelCorrection + y: parent.height / 2 - height / 2 + + color: root.outerLineColor + width: hairLength + root.borderWidth * 2 + height: root.outerLineThickness + root.borderWidth * 2 + + border.width: root.borderWidth + border.color: root.borderColor + } + } + } + + +} diff --git a/.config/quickshell/ii/modules/settings/InterfaceConfig.qml b/.config/quickshell/ii/modules/settings/InterfaceConfig.qml index 2c14dc49f..de1680609 100644 --- a/.config/quickshell/ii/modules/settings/InterfaceConfig.qml +++ b/.config/quickshell/ii/modules/settings/InterfaceConfig.qml @@ -61,6 +61,37 @@ ContentPage { } } + ContentSection { + icon: "point_scan" + title: Translation.tr("Crosshair") + + MaterialTextArea { + Layout.fillWidth: true + placeholderText: Translation.tr("Valorant crosshair code") + text: Config.options.crosshair.code + wrapMode: TextEdit.Wrap + onTextChanged: { + Config.options.crosshair.code = text; + } + } + + RowLayout { + Item { Layout.fillWidth: true } + RippleButtonWithIcon { + id: editorButton + buttonRadius: Appearance.rounding.full + materialIcon: "open_in_new" + mainText: Translation.tr("Open editor") + onClicked: { + Qt.openUrlExternally(`https://www.vcrdb.net/builder?c=${Config.options.crosshair.code}`); + } + StyledToolTip { + text: "www.vcrdb.net" + } + } + } + } + ContentSection { icon: "call_to_action" title: Translation.tr("Dock") diff --git a/.config/quickshell/ii/shell.qml b/.config/quickshell/ii/shell.qml index f2062cc23..0abe0dca5 100644 --- a/.config/quickshell/ii/shell.qml +++ b/.config/quickshell/ii/shell.qml @@ -11,6 +11,7 @@ import "./modules/common/" import "./modules/background/" import "./modules/bar/" import "./modules/cheatsheet/" +import "./modules/crosshair/" import "./modules/dock/" import "./modules/lock/" import "./modules/mediaControls/" @@ -36,6 +37,7 @@ ShellRoot { property bool enableBar: true property bool enableBackground: true property bool enableCheatsheet: true + property bool enableCrosshair: true property bool enableDock: true property bool enableLock: true property bool enableMediaControls: true @@ -64,6 +66,7 @@ ShellRoot { LazyLoader { active: enableBar && Config.ready && !Config.options.bar.vertical; component: Bar {} } LazyLoader { active: enableBackground; component: Background {} } LazyLoader { active: enableCheatsheet; component: Cheatsheet {} } + LazyLoader { active: enableCrosshair; component: Crosshair {} } LazyLoader { active: enableDock && Config.options.dock.enable; component: Dock {} } LazyLoader { active: enableLock; component: Lock {} } LazyLoader { active: enableMediaControls; component: MediaControls {} }