Compare commits
110 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 33746fca04 | |||
| 853b683962 | |||
| b1bfcb3ed0 | |||
| 68662120ba | |||
| b8af60008d | |||
| b8524ff621 | |||
| ffde4063a0 | |||
| 96bf5f3365 | |||
| 053efb4aaf | |||
| c88aef2164 | |||
| 01b54ec5e1 | |||
| 7276ee28dc | |||
| a14ebe2016 | |||
| d3f6765819 | |||
| a3d0ee18cb | |||
| c9d6b95ca5 | |||
| 794a26a3fe | |||
| ca3a288eab | |||
| 902863e5ba | |||
| dd49198cf7 | |||
| ceca949535 | |||
| 24d5584b98 | |||
| 62ec1b9f33 | |||
| 9c6a1ce1a4 | |||
| b6ad180b6a | |||
| b20767c702 | |||
| 362b7bb8c2 | |||
| 405825518a | |||
| db7a822caf | |||
| 3bd9444e2f | |||
| d0e696c681 | |||
| 550630feaa | |||
| 8c22855dd8 | |||
| 015ee61885 | |||
| 8fba953f52 | |||
| 3d2fc0a3b1 | |||
| c060be79e8 | |||
| cb1df5078b | |||
| 5eb32fc30c | |||
| 0a84c822d5 | |||
| f89236f51e | |||
| 4b1316e887 | |||
| 63f4694322 | |||
| 51a8f1d5e1 | |||
| d57010a501 | |||
| d506d5ad27 | |||
| f6119072f7 | |||
| 00063309cd | |||
| c30891de83 | |||
| 55e9c0f267 | |||
| 3424df53e2 | |||
| 583c50f994 | |||
| ca53152630 | |||
| 0cd7df243b | |||
| 6a8ad4dbf2 | |||
| f57577fefd | |||
| 3a05cd339d | |||
| c67a498f8d | |||
| e7e772ebc6 | |||
| fb2c9c6a21 | |||
| 383671344f | |||
| beb1d96750 | |||
| 6f8af9028b | |||
| e874c19ee2 | |||
| af04e5d227 | |||
| 4ab19a8e37 | |||
| 783d05f815 | |||
| 17fb9c0fef | |||
| 39cbfa2c93 | |||
| 5c5018033d | |||
| d9afc6c7c7 | |||
| 17fef78672 | |||
| c120dcae41 | |||
| 64e65ca9df | |||
| 22a7993c07 | |||
| 24526ca2d1 | |||
| c5ee27bf62 | |||
| 7cec08d262 | |||
| 97b657ce9a | |||
| 33f6706658 | |||
| f6c4dc8ee1 | |||
| a53a4b32eb | |||
| e80ac202d0 | |||
| 6e6f6c28f6 | |||
| 26bfa952d7 | |||
| 611abdf028 | |||
| 37a112a04b | |||
| bfc09c71a8 | |||
| c7fabf9fc5 | |||
| 0bae21c891 | |||
| 53733c7fe0 | |||
| 836b92cc5f | |||
| bcc75abc54 | |||
| 11c185baa6 | |||
| 7cc056c327 | |||
| f657741551 | |||
| 073e1dd8b1 | |||
| 8fa447c63d | |||
| e4113994dc | |||
| 8a2eeb6c31 | |||
| 10340a83dd | |||
| c2bd45db4a | |||
| d5256a3952 | |||
| abd85388f6 | |||
| 2bad732592 | |||
| 47120db391 | |||
| d568cd34ab | |||
| a3a55ba8d1 | |||
| 8db28ec3a0 | |||
| 9688e99a8e |
@@ -0,0 +1,42 @@
|
||||
name: Lint & Format (JS/TS)
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
lint-format:
|
||||
runs-on: alpine
|
||||
container: node:26-alpine
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install tools
|
||||
run: |
|
||||
apk add --no-cache \
|
||||
git
|
||||
|
||||
- name: Prettier
|
||||
continue-on-error: true
|
||||
run: |
|
||||
if [ -n "$(find . \( -iname "*.js" -o -iname "*.jsx" -o -iname "*.ts" -o -iname "*.tsx" -o -iname "*.mjs" -o -iname "*.cjs" \) -print -quit)" ]; then
|
||||
npx --yes prettier --check "**/*.{js,jsx,ts,tsx,mjs,cjs}" --ignore-path .prettierignore
|
||||
else
|
||||
echo "No JS/TS files found"
|
||||
fi
|
||||
|
||||
- name: ESLint
|
||||
run: |
|
||||
if [ -n "$(find . \( -iname "*.js" -o -iname "*.jsx" -o -iname "*.ts" -o -iname "*.tsx" -o -iname "*.mjs" -o -iname "*.cjs" \) -print -quit)" ]; then
|
||||
if [ -f package.json ]; then
|
||||
npm install --no-audit --no-fund
|
||||
fi
|
||||
if [ -f eslint.config.js ] || [ -f eslint.config.mjs ] || [ -f eslint.config.cjs ] || [ -f .eslintrc ] || [ -f .eslintrc.js ] || [ -f .eslintrc.cjs ] || [ -f .eslintrc.json ] || [ -f .eslintrc.yaml ] || [ -f .eslintrc.yml ]; then
|
||||
npx --yes eslint . && echo "ESLint passed" || echo "ESLint failed"
|
||||
else
|
||||
echo "No eslint config found"
|
||||
fi
|
||||
else
|
||||
echo "No JS/TS files found"
|
||||
fi
|
||||
@@ -0,0 +1,34 @@
|
||||
name: Lint & Format (Python)
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
lint-format:
|
||||
runs-on: alpine
|
||||
container: node:26-alpine
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install tools
|
||||
run: |
|
||||
apk add --no-cache \
|
||||
git \
|
||||
python3 \
|
||||
py3-pip
|
||||
python3 -m venv .venv
|
||||
. .venv/bin/activate
|
||||
pip install --no-cache-dir ruff
|
||||
|
||||
- name: Format check
|
||||
continue-on-error: true
|
||||
run: |
|
||||
. .venv/bin/activate
|
||||
ruff format --check .
|
||||
|
||||
- name: Lint
|
||||
run: |
|
||||
. .venv/bin/activate
|
||||
ruff check .
|
||||
@@ -0,0 +1,72 @@
|
||||
name: Lint & Format (Rust)
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
lint-format:
|
||||
runs-on: alpine
|
||||
container: node:26-alpine
|
||||
env:
|
||||
CARGO_HOME: ${{ github.workspace }}/.cargo
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Cache cargo packages
|
||||
uses: actions/cache@v4
|
||||
env:
|
||||
cache-name: cache-cargo-packages
|
||||
with:
|
||||
path: |
|
||||
.cargo/registry
|
||||
.cargo/git
|
||||
target
|
||||
key: rust-${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/Cargo.lock') }}
|
||||
restore-keys: |
|
||||
rust-${{ runner.os }}-build-${{ env.cache-name }}-
|
||||
rust-${{ runner.os }}-build-
|
||||
rust-
|
||||
|
||||
- name: Install tools
|
||||
run: |
|
||||
apk add --no-cache \
|
||||
git \
|
||||
cargo \
|
||||
rust \
|
||||
rustfmt \
|
||||
rust-clippy
|
||||
|
||||
- name: Format check
|
||||
continue-on-error: true
|
||||
run: |
|
||||
if [ -n "$(find . -name "Cargo.toml" -print -quit)" ]; then
|
||||
for manifest in $(find . -name "Cargo.toml"); do
|
||||
cargo fmt --manifest-path "$manifest" --check && \
|
||||
echo "$manifest: formatting OK" || \
|
||||
echo "$manifest: needs formatting"
|
||||
done
|
||||
elif [ -n "$(find . -name "*.rs" -print -quit)" ]; then
|
||||
echo "Rust files found but no Cargo.toml"
|
||||
exit 1
|
||||
else
|
||||
echo "No Rust project found"
|
||||
fi
|
||||
|
||||
- name: Clippy
|
||||
run: |
|
||||
if [ -n "$(find . -name "Cargo.toml" -print -quit)" ]; then
|
||||
status=0
|
||||
for manifest in $(find . -name "Cargo.toml"); do
|
||||
cargo clippy --manifest-path "$manifest" --all-targets --all-features -- -D warnings && \
|
||||
echo "$manifest: Clippy passed" || \
|
||||
{ echo "$manifest: Clippy failed"; status=1; }
|
||||
done
|
||||
exit $status
|
||||
elif [ -n "$(find . -name "*.rs" -print -quit)" ]; then
|
||||
echo "Rust files found but no Cargo.toml"
|
||||
exit 1
|
||||
else
|
||||
echo "No Rust project found"
|
||||
fi
|
||||
+2
-1
@@ -1,4 +1,4 @@
|
||||
./__pycache__/
|
||||
**/__pycache__/
|
||||
./result/
|
||||
.pyre/
|
||||
.cache/
|
||||
@@ -12,3 +12,4 @@ pkg/
|
||||
uv.lock
|
||||
.qtcreator/
|
||||
dist/
|
||||
**/target/
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
.venv/
|
||||
scripts/fzf.js
|
||||
scripts/fuzzysort.js
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"semi": true,
|
||||
"singleQuote": false,
|
||||
"jsxSingleQuote": false,
|
||||
"tabWidth": 4,
|
||||
"printWidth": 80,
|
||||
"trailingComma": "es5",
|
||||
"bracketSpacing": true,
|
||||
"bracketSameLine": false,
|
||||
"arrowParens": "always",
|
||||
"endOfLine": "lf",
|
||||
"proseWrap": "preserve"
|
||||
}
|
||||
+2
-2
@@ -2,7 +2,7 @@ import QtQuick
|
||||
import qs.Config
|
||||
|
||||
NumberAnimation {
|
||||
duration: MaterialEasing.standardTime
|
||||
easing.bezierCurve: MaterialEasing.standard
|
||||
duration: Appearance.anim.durations.normal
|
||||
easing.bezierCurve: Appearance.anim.curves.standard
|
||||
easing.type: Easing.BezierSpline
|
||||
}
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import qs.Config
|
||||
|
||||
IconButton {
|
||||
id: root
|
||||
|
||||
required property bool shouldBeVisible
|
||||
|
||||
opacity: 0
|
||||
scale: 0
|
||||
visible: root.scale > 0
|
||||
|
||||
Behavior on opacity {
|
||||
Anim {
|
||||
duration: Appearance.anim.durations.small
|
||||
}
|
||||
}
|
||||
Behavior on scale {
|
||||
Anim {
|
||||
}
|
||||
}
|
||||
|
||||
onShouldBeVisibleChanged: {
|
||||
if (root.shouldBeVisible) {
|
||||
root.opacity = 1;
|
||||
root.scale = 1;
|
||||
} else {
|
||||
root.opacity = 0;
|
||||
root.scale = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -41,12 +41,11 @@ CustomRect {
|
||||
color: type === IconButton.Text ? "transparent" : disabled ? disabledColour : internalChecked ? activeColour : inactiveColour
|
||||
implicitHeight: label.implicitHeight + padding * 2
|
||||
implicitWidth: implicitHeight
|
||||
radius: internalChecked ? 6 : implicitHeight / 2 * Math.min(1, 1)
|
||||
radius: internalChecked ? 6 : (implicitHeight / 2 * Math.min(1, 1)) * Appearance.rounding.scale
|
||||
|
||||
Behavior on radius {
|
||||
Anim {
|
||||
id: radiusAnim
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,4 +7,8 @@ JsonObject {
|
||||
property real alignX: 0.5
|
||||
property real alignY: 0.5
|
||||
property real zoom: 1.0
|
||||
property real sourceClipX: 0
|
||||
property real sourceClipY: 0
|
||||
property real sourceClipW: 0
|
||||
property real sourceClipH: 0
|
||||
}
|
||||
|
||||
+1
-12
@@ -8,10 +8,6 @@ JsonObject {
|
||||
id: "workspaces",
|
||||
enabled: true
|
||||
},
|
||||
{
|
||||
id: "audio",
|
||||
enabled: true
|
||||
},
|
||||
{
|
||||
id: "media",
|
||||
enabled: true
|
||||
@@ -24,10 +20,6 @@ JsonObject {
|
||||
id: "updates",
|
||||
enabled: true
|
||||
},
|
||||
{
|
||||
id: "dash",
|
||||
enabled: true
|
||||
},
|
||||
{
|
||||
id: "spacer",
|
||||
enabled: true
|
||||
@@ -48,10 +40,6 @@ JsonObject {
|
||||
id: "tray",
|
||||
enabled: true
|
||||
},
|
||||
{
|
||||
id: "upower",
|
||||
enabled: false
|
||||
},
|
||||
{
|
||||
id: "network",
|
||||
enabled: false
|
||||
@@ -70,6 +58,7 @@ JsonObject {
|
||||
property Popouts popouts: Popouts {
|
||||
}
|
||||
property int rounding: 8
|
||||
property int smoothing: 32
|
||||
|
||||
component Popouts: JsonObject {
|
||||
property bool activeWindow: true
|
||||
|
||||
+26
-1
@@ -23,6 +23,7 @@ Singleton {
|
||||
property alias osd: adapter.osd
|
||||
property alias overview: adapter.overview
|
||||
property bool recentlySaved: false
|
||||
property alias screenshot: adapter.screenshot
|
||||
property alias services: adapter.services
|
||||
property alias sidebar: adapter.sidebar
|
||||
property alias utilities: adapter.utilities
|
||||
@@ -82,6 +83,10 @@ Singleton {
|
||||
wallFadeDuration: background.wallFadeDuration,
|
||||
enabled: background.enabled,
|
||||
alignX: background.alignX,
|
||||
sourceClipX: background.sourceClipX,
|
||||
sourceClipY: background.sourceClipY,
|
||||
sourceClipW: background.sourceClipW,
|
||||
sourceClipH: background.sourceClipH,
|
||||
alignY: background.alignY,
|
||||
zoom: background.zoom
|
||||
};
|
||||
@@ -93,6 +98,7 @@ Singleton {
|
||||
hideWhenNotif: barConfig.hideWhenNotif,
|
||||
rounding: barConfig.rounding,
|
||||
border: barConfig.border,
|
||||
smoothing: barConfig.smoothing,
|
||||
height: barConfig.height,
|
||||
popouts: {
|
||||
tray: barConfig.popouts.tray,
|
||||
@@ -128,7 +134,8 @@ Singleton {
|
||||
background: serializeBackground(),
|
||||
launcher: serializeLauncher(),
|
||||
colors: serializeColors(),
|
||||
dock: serializeDock()
|
||||
dock: serializeDock(),
|
||||
screenshot: serializeScreenshot()
|
||||
};
|
||||
}
|
||||
|
||||
@@ -180,6 +187,7 @@ Singleton {
|
||||
logo: general.logo,
|
||||
wallpaperPath: general.wallpaperPath,
|
||||
desktopIcons: general.desktopIcons,
|
||||
dateFormat: general.dateFormat,
|
||||
color: {
|
||||
mode: general.color.mode,
|
||||
smart: general.color.smart,
|
||||
@@ -233,6 +241,7 @@ Singleton {
|
||||
return {
|
||||
recolorLogo: lock.recolorLogo,
|
||||
enableFprint: lock.enableFprint,
|
||||
showNotifContent: lock.showNotifContent,
|
||||
maxFprintTries: lock.maxFprintTries,
|
||||
blurAmount: lock.blurAmount,
|
||||
sizes: {
|
||||
@@ -274,6 +283,20 @@ Singleton {
|
||||
};
|
||||
}
|
||||
|
||||
function serializeScreenshot(): var {
|
||||
return {
|
||||
enable_pp: screenshot.enable_pp,
|
||||
mode: screenshot.mode,
|
||||
corner_radius: screenshot.corner_radius,
|
||||
drop_shadow: screenshot.drop_shadow,
|
||||
rounded_corners: screenshot.rounded_corners,
|
||||
shadow_blur_radius: screenshot.shadow_blur_radius,
|
||||
shadow_color: screenshot.shadow_color,
|
||||
shadow_offset_x: screenshot.shadow_offset_x,
|
||||
shadow_offset_y: screenshot.shadow_offset_y
|
||||
};
|
||||
}
|
||||
|
||||
function serializeServices(): var {
|
||||
return {
|
||||
weatherLocation: services.weatherLocation,
|
||||
@@ -429,6 +452,8 @@ Singleton {
|
||||
}
|
||||
property Overview overview: Overview {
|
||||
}
|
||||
property Screenshot screenshot: Screenshot {
|
||||
}
|
||||
property Services services: Services {
|
||||
}
|
||||
property SidebarConfig sidebar: SidebarConfig {
|
||||
|
||||
@@ -3,6 +3,7 @@ pragma ComponentBehavior: Bound
|
||||
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import Quickshell.Hyprland
|
||||
import QtQuick
|
||||
import ZShell
|
||||
import qs.Helpers
|
||||
@@ -79,10 +80,32 @@ Singleton {
|
||||
}
|
||||
|
||||
function reloadHyprRules(): void {
|
||||
const barStr = "keyword layerrule %1 %2, match:namespace ZShell-Bar";
|
||||
const authStr = "keyword layerrule %1 %2, match:namespace ZShell-Auth";
|
||||
Hypr.extras.batchMessage([barStr.arg("blur").arg(transparency.enabled ? 1 : 0), barStr.arg("ignore_alpha").arg(transparency.base - 0.03)]);
|
||||
Hypr.extras.batchMessage([authStr.arg("blur").arg(transparency.enabled ? 1 : 0), authStr.arg("ignore_alpha").arg(transparency.base - 0.03)]);
|
||||
const blur = transparency.enabled ? 1 : 0;
|
||||
const alpha = transparency.base - 0.03;
|
||||
|
||||
const rules = `
|
||||
hl.layer_rule({
|
||||
match = { namespace = "ZShell-Bar" },
|
||||
blur = ${blur}
|
||||
})
|
||||
|
||||
hl.layer_rule({
|
||||
match = { namespace = "ZShell-Bar" },
|
||||
ignore_alpha = ${alpha}
|
||||
})
|
||||
|
||||
hl.layer_rule({
|
||||
match = { namespace = "ZShell-Auth" },
|
||||
blur = ${blur}
|
||||
})
|
||||
|
||||
hl.layer_rule({
|
||||
match = { namespace = "ZShell-Auth" },
|
||||
ignore_alpha = ${alpha}
|
||||
})
|
||||
`;
|
||||
|
||||
Hypr.extras.message(`eval ${rules}`);
|
||||
}
|
||||
|
||||
function setMode(mode: string): void {
|
||||
@@ -93,6 +116,14 @@ Singleton {
|
||||
|
||||
Component.onCompleted: debounceTimer.triggered()
|
||||
|
||||
Connections {
|
||||
function onUsingLuaChanged(): void {
|
||||
root.reloadHyprRules();
|
||||
}
|
||||
|
||||
target: Hyprland
|
||||
}
|
||||
|
||||
Connections {
|
||||
function onConfigReloaded(): void {
|
||||
root.reloadHyprRules();
|
||||
|
||||
@@ -6,6 +6,7 @@ JsonObject {
|
||||
}
|
||||
property Color color: Color {
|
||||
}
|
||||
property string dateFormat: "ddd d MMM - hh:mm:ss"
|
||||
property bool desktopIcons: false
|
||||
property Idle idle: Idle {
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ JsonObject {
|
||||
property bool enableFprint: true
|
||||
property int maxFprintTries: 3
|
||||
property bool recolorLogo: false
|
||||
property bool showNotifContent: false
|
||||
property Sizes sizes: Sizes {
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
import Quickshell.Io
|
||||
|
||||
JsonObject {
|
||||
property real corner_radius: 12.0
|
||||
property bool drop_shadow: true
|
||||
property bool enable_pp: true
|
||||
property string mode: "manual"
|
||||
property bool rounded_corners: false
|
||||
property real shadow_blur_radius: 22.0
|
||||
property list<int> shadow_color: [0, 0, 0, 160]
|
||||
property real shadow_offset_x: 5.0
|
||||
property real shadow_offset_y: 5.0
|
||||
}
|
||||
+23
-174
@@ -3,184 +3,33 @@ import QtQuick
|
||||
Canvas {
|
||||
id: root
|
||||
|
||||
property rect dirtyRect: Qt.rect(0, 0, 0, 0)
|
||||
property bool frameQueued: false
|
||||
property bool fullRepaintPending: true
|
||||
property point lastPoint: Qt.point(0, 0)
|
||||
property real minPointDistance: 2.0
|
||||
property color penColor: "white"
|
||||
property real penWidth: 4
|
||||
property var pendingSegments: []
|
||||
property bool strokeActive: false
|
||||
property var strokes: []
|
||||
property var points: []
|
||||
|
||||
function appendPoint(x, y) {
|
||||
if (!strokeActive || strokes.length === 0)
|
||||
function clear(): void {
|
||||
var ctx = getContext('2d');
|
||||
root.points = [];
|
||||
ctx.reset();
|
||||
root.requestPaint();
|
||||
}
|
||||
|
||||
renderStrategy: Canvas.Cooperative
|
||||
|
||||
onPaint: {
|
||||
if (points.length < 2)
|
||||
return;
|
||||
const dx = x - lastPoint.x;
|
||||
const dy = y - lastPoint.y;
|
||||
|
||||
if ((dx * dx + dy * dy) < (minPointDistance * minPointDistance))
|
||||
return;
|
||||
const x1 = lastPoint.x;
|
||||
const y1 = lastPoint.y;
|
||||
const x2 = x;
|
||||
const y2 = y;
|
||||
|
||||
strokes[strokes.length - 1].push(Qt.point(x2, y2));
|
||||
|
||||
pendingSegments.push({
|
||||
dot: false,
|
||||
x1: x1,
|
||||
y1: y1,
|
||||
x2: x2,
|
||||
y2: y2
|
||||
});
|
||||
|
||||
lastPoint = Qt.point(x2, y2);
|
||||
queueDirty(segmentDirtyRect(x1, y1, x2, y2));
|
||||
}
|
||||
|
||||
function beginStroke(x, y) {
|
||||
const p = Qt.point(x, y);
|
||||
strokes.push([p]);
|
||||
lastPoint = p;
|
||||
strokeActive = true;
|
||||
|
||||
pendingSegments.push({
|
||||
dot: true,
|
||||
x: x,
|
||||
y: y
|
||||
});
|
||||
|
||||
queueDirty(pointDirtyRect(x, y));
|
||||
}
|
||||
|
||||
function clear() {
|
||||
strokes = [];
|
||||
pendingSegments = [];
|
||||
dirtyRect = Qt.rect(0, 0, 0, 0);
|
||||
fullRepaintPending = true;
|
||||
markDirty(Qt.rect(0, 0, width, height));
|
||||
}
|
||||
|
||||
function drawDot(ctx, x, y) {
|
||||
ctx.beginPath();
|
||||
ctx.arc(x, y, penWidth / 2, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
}
|
||||
|
||||
function drawSegment(ctx, x1, y1, x2, y2) {
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(x1, y1);
|
||||
ctx.lineTo(x2, y2);
|
||||
ctx.stroke();
|
||||
}
|
||||
|
||||
function endStroke() {
|
||||
strokeActive = false;
|
||||
}
|
||||
|
||||
function pointDirtyRect(x, y) {
|
||||
const pad = penWidth + 2;
|
||||
return Qt.rect(x - pad, y - pad, pad * 2, pad * 2);
|
||||
}
|
||||
|
||||
function queueDirty(r) {
|
||||
dirtyRect = unionRects(dirtyRect, r);
|
||||
|
||||
if (frameQueued)
|
||||
return;
|
||||
frameQueued = true;
|
||||
|
||||
requestAnimationFrame(function () {
|
||||
frameQueued = false;
|
||||
|
||||
if (dirtyRect.width > 0 && dirtyRect.height > 0) {
|
||||
markDirty(dirtyRect);
|
||||
dirtyRect = Qt.rect(0, 0, 0, 0);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function replayAll(ctx) {
|
||||
ctx.clearRect(0, 0, width, height);
|
||||
|
||||
for (const stroke of strokes) {
|
||||
if (!stroke || stroke.length === 0)
|
||||
continue;
|
||||
if (stroke.length === 1) {
|
||||
const p = stroke[0];
|
||||
drawDot(ctx, p.x, p.y);
|
||||
continue;
|
||||
}
|
||||
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(stroke[0].x, stroke[0].y);
|
||||
for (let i = 1; i < stroke.length; ++i)
|
||||
ctx.lineTo(stroke[i].x, stroke[i].y);
|
||||
ctx.stroke();
|
||||
}
|
||||
}
|
||||
|
||||
function requestFullRepaint() {
|
||||
fullRepaintPending = true;
|
||||
markDirty(Qt.rect(0, 0, width, height));
|
||||
}
|
||||
|
||||
function segmentDirtyRect(x1, y1, x2, y2) {
|
||||
const pad = penWidth + 2;
|
||||
const left = Math.min(x1, x2) - pad;
|
||||
const top = Math.min(y1, y2) - pad;
|
||||
const right = Math.max(x1, x2) + pad;
|
||||
const bottom = Math.max(y1, y2) + pad;
|
||||
return Qt.rect(left, top, right - left, bottom - top);
|
||||
}
|
||||
|
||||
function unionRects(a, b) {
|
||||
if (a.width <= 0 || a.height <= 0)
|
||||
return b;
|
||||
if (b.width <= 0 || b.height <= 0)
|
||||
return a;
|
||||
|
||||
const left = Math.min(a.x, b.x);
|
||||
const top = Math.min(a.y, b.y);
|
||||
const right = Math.max(a.x + a.width, b.x + b.width);
|
||||
const bottom = Math.max(a.y + a.height, b.y + b.height);
|
||||
|
||||
return Qt.rect(left, top, right - left, bottom - top);
|
||||
}
|
||||
|
||||
anchors.fill: parent
|
||||
contextType: "2d"
|
||||
renderStrategy: Canvas.Threaded
|
||||
renderTarget: Canvas.Image
|
||||
|
||||
onHeightChanged: requestFullRepaint()
|
||||
onPaint: region => {
|
||||
const ctx = getContext("2d");
|
||||
|
||||
var ctx = root.getContext('2d');
|
||||
ctx.save();
|
||||
ctx.lineWidth = root.penWidth;
|
||||
ctx.strokeStyle = root.penColor;
|
||||
ctx.lineCap = "round";
|
||||
ctx.lineJoin = "round";
|
||||
ctx.lineWidth = penWidth;
|
||||
ctx.strokeStyle = penColor;
|
||||
ctx.fillStyle = penColor;
|
||||
|
||||
if (fullRepaintPending) {
|
||||
fullRepaintPending = false;
|
||||
replayAll(ctx);
|
||||
pendingSegments = [];
|
||||
return;
|
||||
}
|
||||
|
||||
for (const seg of pendingSegments) {
|
||||
if (seg.dot)
|
||||
drawDot(ctx, seg.x, seg.y);
|
||||
else
|
||||
drawSegment(ctx, seg.x1, seg.y1, seg.x2, seg.y2);
|
||||
}
|
||||
|
||||
pendingSegments = [];
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(points[0].x, points[0].y);
|
||||
for (var i = 1; i < points.length; i++)
|
||||
ctx.lineTo(points[i].x, points[i].y);
|
||||
ctx.stroke();
|
||||
points = points.slice(points.length - 2);
|
||||
ctx.restore();
|
||||
}
|
||||
onWidthChanged: requestFullRepaint()
|
||||
}
|
||||
|
||||
@@ -30,8 +30,10 @@ CustomMouseArea {
|
||||
const x = event.x;
|
||||
const y = event.y;
|
||||
|
||||
if (event.buttons & Qt.LeftButton)
|
||||
root.drawing.appendPoint(x, y);
|
||||
if (root.visibilities.isDrawing && (event.buttons & Qt.LeftButton)) {
|
||||
root.drawing.points.push(Qt.point(x, y));
|
||||
root.drawing.requestPaint();
|
||||
}
|
||||
|
||||
if (root.inLeftPanel(root.popout, x, y)) {
|
||||
root.z = -2;
|
||||
@@ -44,7 +46,8 @@ CustomMouseArea {
|
||||
|
||||
if (root.visibilities.isDrawing && (event.buttons & Qt.LeftButton)) {
|
||||
root.panels.drawing.expanded = false;
|
||||
root.drawing.beginStroke(x, y);
|
||||
root.drawing.points.push(Qt.point(x, y));
|
||||
root.drawing.requestPaint();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -52,7 +55,6 @@ CustomMouseArea {
|
||||
root.drawing.clear();
|
||||
}
|
||||
onReleased: {
|
||||
if (root.visibilities.isDrawing)
|
||||
root.drawing.endStroke();
|
||||
root.drawing.points = [];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,22 +12,37 @@ Scope {
|
||||
required property ShellScreen screen
|
||||
|
||||
ExclusionZone {
|
||||
id: top
|
||||
|
||||
anchors.top: true
|
||||
exclusiveZone: root.bar.exclusiveZone
|
||||
}
|
||||
|
||||
ExclusionZone {
|
||||
id: left
|
||||
|
||||
anchors.left: true
|
||||
}
|
||||
|
||||
ExclusionZone {
|
||||
id: right
|
||||
|
||||
anchors.right: true
|
||||
}
|
||||
|
||||
ExclusionZone {
|
||||
id: bottom
|
||||
|
||||
anchors.bottom: true
|
||||
}
|
||||
|
||||
Timer {
|
||||
interval: 5000
|
||||
running: true
|
||||
|
||||
onTriggered: console.log("top height:", top.exclusiveZone, "left width:", left.exclusiveZone, "right width:", right.exclusiveZone, "bottom height:", bottom.exclusiveZone)
|
||||
}
|
||||
|
||||
component ExclusionZone: CustomWindow {
|
||||
exclusiveZone: Config.barConfig.border
|
||||
implicitHeight: 1
|
||||
|
||||
+18
-18
@@ -78,7 +78,7 @@ CustomMouseArea {
|
||||
const dragY = y - dragStart.y;
|
||||
|
||||
if (root.visibilities.isDrawing && !root.inLeftPanel(root.panels.drawing, x, y)) {
|
||||
root.input.z = 2;
|
||||
// root.input.z = 2;
|
||||
root.panels.drawing.expanded = false;
|
||||
}
|
||||
|
||||
@@ -96,25 +96,25 @@ CustomMouseArea {
|
||||
if (dragY < -10)
|
||||
visibilities.dock = true;
|
||||
|
||||
if (panels.sidebar.width === 0) {
|
||||
const showOsd = inRightPanel(panels.osdWrapper, x, y);
|
||||
if (panels.sidebar.width === 0) {
|
||||
const showOsd = inRightPanel(panels.osdWrapper, x, y);
|
||||
|
||||
if (showOsd) {
|
||||
osdShortcutActive = false;
|
||||
root.panels.osd.hovered = true;
|
||||
}
|
||||
} else {
|
||||
const outOfSidebar = x < width - panels.sidebar.width;
|
||||
const showOsd = outOfSidebar && inRightPanel(panels.osdWrapper, x, y);
|
||||
|
||||
if (!osdShortcutActive) {
|
||||
visibilities.osd = showOsd;
|
||||
root.panels.osd.hovered = showOsd;
|
||||
} else if (showOsd) {
|
||||
osdShortcutActive = false;
|
||||
root.panels.osd.hovered = true;
|
||||
}
|
||||
if (showOsd) {
|
||||
osdShortcutActive = false;
|
||||
root.panels.osd.hovered = true;
|
||||
}
|
||||
} else {
|
||||
const outOfSidebar = x < width - panels.sidebar.width;
|
||||
const showOsd = outOfSidebar && inRightPanel(panels.osdWrapper, x, y);
|
||||
|
||||
if (!osdShortcutActive) {
|
||||
visibilities.osd = showOsd;
|
||||
root.panels.osd.hovered = showOsd;
|
||||
} else if (showOsd) {
|
||||
osdShortcutActive = false;
|
||||
root.panels.osd.hovered = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (Config.dock.enable && !Config.dock.hoverToReveal && !visibilities.dock && !visibilities.launcher && inBottomPanel(panels.dock, x, y))
|
||||
visibilities.dock = true;
|
||||
|
||||
+92
-6
@@ -13,6 +13,7 @@ import qs.Modules.Resources as Resources
|
||||
import qs.Modules.Settings as Settings
|
||||
import qs.Modules.Drawing as Drawing
|
||||
import qs.Modules.Dock as Dock
|
||||
import qs.Modules.SysTray.Popouts as SysPopouts
|
||||
import qs.Config
|
||||
|
||||
Item {
|
||||
@@ -34,8 +35,10 @@ Item {
|
||||
readonly property alias resourcesWrapper: resourcesWrapper
|
||||
required property ShellScreen screen
|
||||
readonly property alias settings: settings
|
||||
readonly property alias settingsWrapper: settingsWrapper
|
||||
readonly property alias sidebar: sidebar
|
||||
readonly property alias toasts: toasts
|
||||
readonly property alias traySubmenus: traySubmenus
|
||||
readonly property alias utilities: utilities
|
||||
required property PersistentProperties visibilities
|
||||
|
||||
@@ -92,6 +95,79 @@ Item {
|
||||
visibilities: root.visibilities
|
||||
}
|
||||
|
||||
Item {
|
||||
id: traySubmenus
|
||||
|
||||
Repeater {
|
||||
model: popouts.content.state.submenus
|
||||
|
||||
CustomClippingRect {
|
||||
id: subMenuWrapper
|
||||
|
||||
required property int index
|
||||
required property var modelData
|
||||
property real targetX: 0
|
||||
property real targetY: 0
|
||||
|
||||
function updatePosition() {
|
||||
let sourceItem = modelData.sourceItem;
|
||||
if (!sourceItem || !sourceItem.parent)
|
||||
return;
|
||||
|
||||
let mapped = sourceItem.mapToItem(root, 0, -Appearance.padding.small);
|
||||
|
||||
let rightX = mapped.x + modelData.sourceWidth + Config.barConfig.border;
|
||||
let leftX = mapped.x - implicitWidth - Config.barConfig.border;
|
||||
|
||||
if (rightX + implicitWidth > root.width) {
|
||||
targetX = leftX;
|
||||
} else {
|
||||
targetX = rightX;
|
||||
}
|
||||
|
||||
targetY = mapped.y;
|
||||
|
||||
if (targetY + implicitHeight > root.height) {
|
||||
targetY = root.height - implicitHeight;
|
||||
}
|
||||
if (targetY < 0)
|
||||
targetY = 0;
|
||||
}
|
||||
|
||||
implicitHeight: subMenuContent.implicitHeight + Appearance.padding.small * 2
|
||||
implicitWidth: subMenuContent.implicitWidth + Appearance.padding.small * 2
|
||||
radius: Appearance.rounding.normal
|
||||
x: targetX
|
||||
y: targetY
|
||||
|
||||
Behavior on implicitHeight {
|
||||
Anim {
|
||||
}
|
||||
}
|
||||
Behavior on implicitWidth {
|
||||
Anim {
|
||||
}
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
updatePosition();
|
||||
}
|
||||
onImplicitHeightChanged: updatePosition()
|
||||
onImplicitWidthChanged: updatePosition()
|
||||
|
||||
SysPopouts.SubMenu {
|
||||
id: subMenuContent
|
||||
|
||||
anchors.centerIn: parent
|
||||
handle: subMenuWrapper.modelData.handle
|
||||
level: subMenuWrapper.index + 1
|
||||
popouts: root.popouts.state
|
||||
screen: root.screen
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Modules.ClipWrapper {
|
||||
id: popouts
|
||||
|
||||
@@ -176,15 +252,25 @@ Item {
|
||||
visibilities: root.visibilities
|
||||
}
|
||||
|
||||
Settings.Wrapper {
|
||||
id: settings
|
||||
Item {
|
||||
id: settingsWrapper
|
||||
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.top: parent.top
|
||||
// anchors.centerIn: parent
|
||||
panels: root
|
||||
screen: root.screen
|
||||
visibilities: root.visibilities
|
||||
clip: true
|
||||
implicitHeight: settings.implicitHeight * (1 - settings.offsetScale)
|
||||
implicitWidth: settings.implicitWidth
|
||||
|
||||
Settings.Wrapper {
|
||||
id: settings
|
||||
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.top: parent.top
|
||||
// anchors.centerIn: parent
|
||||
panels: root
|
||||
screen: root.screen
|
||||
visibilities: root.visibilities
|
||||
}
|
||||
}
|
||||
|
||||
Dock.Wrapper {
|
||||
|
||||
+69
-19
@@ -3,6 +3,7 @@ pragma ComponentBehavior: Bound
|
||||
import QtQuick
|
||||
import QtQuick.Effects
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import Quickshell.Wayland
|
||||
import Quickshell.Hyprland
|
||||
import ZShell.Blobs
|
||||
@@ -63,7 +64,7 @@ Variants {
|
||||
|
||||
height: win.height - bar.implicitHeight - Config.barConfig.border
|
||||
intersection: Intersection.Xor
|
||||
regions: popoutRegions.instances
|
||||
regions: [...popoutRegions.instances, ...subMenuRegions.instances]
|
||||
width: win.width - Config.barConfig.border * 2
|
||||
x: Config.barConfig.border
|
||||
y: bar.implicitHeight
|
||||
@@ -92,6 +93,22 @@ Variants {
|
||||
}
|
||||
}
|
||||
|
||||
Variants {
|
||||
id: subMenuRegions
|
||||
|
||||
model: panels.traySubmenus.children
|
||||
|
||||
Region {
|
||||
required property Item modelData
|
||||
|
||||
height: modelData.height
|
||||
intersection: Intersection.Subtract
|
||||
width: modelData.width
|
||||
x: modelData.x + panels.traySubmenus.x + Config.barConfig.border
|
||||
y: modelData.y + panels.traySubmenus.y + bar.implicitHeight
|
||||
}
|
||||
}
|
||||
|
||||
HyprlandFocusGrab {
|
||||
id: focusGrab
|
||||
|
||||
@@ -127,6 +144,14 @@ Variants {
|
||||
Component.onCompleted: Visibilities.load(scope.modelData, this)
|
||||
}
|
||||
|
||||
IpcHandler {
|
||||
function toggleLauncher(fix: string): void {
|
||||
visibilities.launcher = !visibilities.launcher;
|
||||
}
|
||||
|
||||
target: "visibilities"
|
||||
}
|
||||
|
||||
Binding {
|
||||
property: "bar"
|
||||
target: visibilities
|
||||
@@ -149,6 +174,7 @@ Variants {
|
||||
id: blobGroup
|
||||
|
||||
color: DynamicColors.palette.m3surface
|
||||
smoothing: Config.barConfig.smoothing
|
||||
|
||||
Behavior on color {
|
||||
CAnim {
|
||||
@@ -170,28 +196,34 @@ Variants {
|
||||
PanelBg {
|
||||
id: dashBg
|
||||
|
||||
deformAmount: 0.08 * Config.appearance.deform.scale
|
||||
implicitHeight: panels.dashboard.height
|
||||
property real extraHeight: 0.2
|
||||
|
||||
deformAmount: 0.06
|
||||
implicitHeight: panels.dashboard.height * (1 + extraHeight)
|
||||
implicitWidth: panels.dashboard.width
|
||||
panel: panels.dashboard
|
||||
panel: panels.dashboardWrapper
|
||||
radius: Appearance.rounding.normal
|
||||
x: panels.dashboardWrapper.x + panels.dashboard.x + Config.barConfig.border
|
||||
y: panels.dashboardWrapper.y + panels.dashboard.y + bar.implicitHeight
|
||||
y: panels.dashboardWrapper.y + panels.dashboard.y + bar.implicitHeight - panels.dashboard.height * extraHeight
|
||||
}
|
||||
|
||||
PanelBg {
|
||||
id: launcherBg
|
||||
|
||||
deformAmount: 0.08 * Config.appearance.deform.scale
|
||||
property real extraHeight: 0.2
|
||||
|
||||
deformAmount: 0.06
|
||||
implicitHeight: panels.launcher.height * (1 + extraHeight)
|
||||
panel: panels.launcher
|
||||
radius: Appearance.rounding.smallest + 5
|
||||
y: panels.launcher.y + bar.implicitHeight
|
||||
}
|
||||
|
||||
PanelBg {
|
||||
id: sidebarBg
|
||||
|
||||
bottomLeftRadius: 0
|
||||
deformAmount: 0.08 * Config.appearance.deform.scale
|
||||
deformAmount: 0.04
|
||||
exclude: panels.sidebar.offsetScale > 0.08 ? [] : [utilsBg]
|
||||
implicitHeight: panel.height * (1 / rawDeformMatrix.m22) + 2
|
||||
panel: panels.sidebar
|
||||
@@ -200,10 +232,10 @@ Variants {
|
||||
PanelBg {
|
||||
id: osdBg
|
||||
|
||||
deformAmount: 0.1 * Config.appearance.deform.scale
|
||||
deformAmount: 0.1
|
||||
implicitHeight: panels.osd.height
|
||||
implicitWidth: panels.osd.width
|
||||
panel: panels.osd
|
||||
panel: panels.osdWrapper
|
||||
radius: 20
|
||||
x: panels.osdWrapper.x + panels.osd.x + Config.barConfig.border
|
||||
y: panels.osdWrapper.y + panels.osd.y + bar.implicitHeight
|
||||
@@ -218,7 +250,7 @@ Variants {
|
||||
PanelBg {
|
||||
id: utilsBg
|
||||
|
||||
deformAmount: panels.sidebar.visible ? (0.1 * Config.appearance.deform.scale) : (0.1 * Config.appearance.deform.scale)
|
||||
deformAmount: panels.sidebar.visible ? (0.1) : (0.1)
|
||||
exclude: panels.sidebar.offsetScale > 0.08 ? [] : [sidebarBg]
|
||||
panel: panels.utilities
|
||||
topLeftRadius: 0
|
||||
@@ -229,11 +261,11 @@ Variants {
|
||||
|
||||
property real extraHeight: panels.popouts.isDetached ? 0 : 0.2
|
||||
|
||||
deformAmount: panels.popouts.isDetached ? 0.05 * Config.appearance.deform.scale : panels.popouts.hasCurrent ? 0.15 * Config.appearance.deform.scale : 0.1 * Config.appearance.deform.scale
|
||||
deformAmount: panels.popouts.isDetached ? 0.05 : panels.popouts.hasCurrent ? 0.15 : 0.1
|
||||
implicitHeight: panels.popouts.height * (1 + extraHeight)
|
||||
implicitWidth: panels.popouts.width
|
||||
panel: panels.popouts
|
||||
radius: (panels.popouts.currentName.startsWith("audio") || panels.popouts.currentName.startsWith("updates")) ? Appearance.rounding.normal : Appearance.rounding.smallest
|
||||
panel: panels.popoutsWrapper
|
||||
radius: (panels.popouts.currentName.startsWith("audio") || panels.popouts.currentName.startsWith("updates")) ? Appearance.rounding.normal : 20 * Appearance.rounding.scale
|
||||
x: panels.popoutsWrapper.x + panels.popouts.x + Config.barConfig.border
|
||||
y: panels.popoutsWrapper.y + panels.popouts.y + bar.implicitHeight - panels.popouts.height * extraHeight
|
||||
|
||||
@@ -246,10 +278,10 @@ Variants {
|
||||
PanelBg {
|
||||
id: resourcesBg
|
||||
|
||||
deformAmount: 0.08 * Config.appearance.deform.scale
|
||||
deformAmount: 0.05
|
||||
implicitHeight: panels.resources.height
|
||||
implicitWidth: panels.resources.width
|
||||
panel: panels.resources
|
||||
panel: panels.resourcesWrapper
|
||||
radius: Appearance.rounding.normal
|
||||
x: panels.resourcesWrapper.x + panels.resources.x + Config.barConfig.border
|
||||
y: panels.resourcesWrapper.y + panels.resources.y + bar.implicitHeight
|
||||
@@ -258,17 +290,23 @@ Variants {
|
||||
PanelBg {
|
||||
id: settingsBg
|
||||
|
||||
deformAmount: 0.08 * Config.appearance.deform.scale
|
||||
property real extraHeight: 0.2
|
||||
|
||||
deformAmount: 0.03
|
||||
implicitHeight: panels.settings.height * (1 + extraHeight)
|
||||
implicitWidth: panels.settings.width
|
||||
panel: panels.settings
|
||||
radius: Appearance.rounding.large
|
||||
topLeftRadius: Appearance.rounding.large + Appearance.padding.smaller
|
||||
topRightRadius: Appearance.rounding.large + Appearance.padding.smaller
|
||||
x: panels.settingsWrapper.x + panels.settings.x + Config.barConfig.border
|
||||
y: panels.settingsWrapper.y + panels.settings.y + bar.implicitHeight - panels.settings.height * extraHeight
|
||||
}
|
||||
|
||||
PanelBg {
|
||||
id: dockBg
|
||||
|
||||
deformAmount: 0.08 * Config.appearance.deform.scale
|
||||
deformAmount: 0.08
|
||||
panel: panels.dock
|
||||
radius: Appearance.rounding.normal
|
||||
}
|
||||
@@ -276,10 +314,22 @@ Variants {
|
||||
PanelBg {
|
||||
id: drawingBg
|
||||
|
||||
deformAmount: 0.08 * Config.appearance.deform.scale
|
||||
deformAmount: 0.08
|
||||
panel: panels.drawing
|
||||
radius: Appearance.rounding.normal
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: panels.traySubmenus.children
|
||||
|
||||
PanelBg {
|
||||
required property Item modelData
|
||||
|
||||
deformAmount: 0.1
|
||||
panel: modelData
|
||||
radius: 20 * Appearance.rounding.scale
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Drawing {
|
||||
@@ -371,7 +421,7 @@ Variants {
|
||||
property real deformAmount: 0.15
|
||||
required property Item panel
|
||||
|
||||
deformScale: deformAmount / 10000
|
||||
deformScale: (deformAmount * Config.appearance.deform.scale) / 10000
|
||||
group: blobGroup
|
||||
implicitHeight: panel.height
|
||||
implicitWidth: panel.width
|
||||
|
||||
@@ -346,7 +346,6 @@ Singleton {
|
||||
|
||||
stdout: StdioCollector {
|
||||
onStreamFinished: {
|
||||
console.log("this is running");
|
||||
if (root.gpuType === "GENERIC") {
|
||||
const percs = text.trim().split("\n");
|
||||
const sum = percs.reduce((acc, d) => acc + parseInt(d, 10), 0);
|
||||
|
||||
+20
-44
@@ -7,23 +7,30 @@ import QtQuick
|
||||
Singleton {
|
||||
id: root
|
||||
|
||||
// The list of users that can log in graphically
|
||||
// Each user object has: username, uid, home, shell, gecos (full name), face (avatar path)
|
||||
readonly property string defaultUserFile: "/etc/zshell-greeter/default-user"
|
||||
property int selectedIndex: 0
|
||||
readonly property var selectedUser: selectedIndex >= 0 && selectedIndex < users.length ? users[selectedIndex] : null
|
||||
readonly property string selectedUsername: selectedUser ? selectedUser.username : ""
|
||||
property var users: []
|
||||
|
||||
// The currently selected user index
|
||||
property int selectedIndex: 0
|
||||
function saveDefaultUser(): void {
|
||||
if (selectedUser) {
|
||||
defaultUserStorage.setText(selectedUser.username);
|
||||
}
|
||||
}
|
||||
|
||||
// The currently selected user object (or null if none)
|
||||
readonly property var selectedUser: selectedIndex >= 0 && selectedIndex < users.length ? users[selectedIndex] : null
|
||||
function selectNext(): void {
|
||||
if (users.length === 0)
|
||||
return;
|
||||
selectedIndex = (selectedIndex + 1) % users.length;
|
||||
}
|
||||
|
||||
// Convenience property for the selected username
|
||||
readonly property string selectedUsername: selectedUser ? selectedUser.username : ""
|
||||
function selectPrevious(): void {
|
||||
if (users.length === 0)
|
||||
return;
|
||||
selectedIndex = (selectedIndex - 1 + users.length) % users.length;
|
||||
}
|
||||
|
||||
// Path to store the default user preference
|
||||
readonly property string defaultUserFile: "/etc/zshell-greeter/default-user"
|
||||
|
||||
// Select a user by username
|
||||
function selectUser(username: string): bool {
|
||||
for (let i = 0; i < users.length; i++) {
|
||||
if (users[i].username === username) {
|
||||
@@ -34,28 +41,6 @@ Singleton {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Select the next user in the list (wraps around)
|
||||
function selectNext(): void {
|
||||
if (users.length === 0)
|
||||
return;
|
||||
selectedIndex = (selectedIndex + 1) % users.length;
|
||||
}
|
||||
|
||||
// Select the previous user in the list (wraps around)
|
||||
function selectPrevious(): void {
|
||||
if (users.length === 0)
|
||||
return;
|
||||
selectedIndex = (selectedIndex - 1 + users.length) % users.length;
|
||||
}
|
||||
|
||||
// Save the current user as the default for next login
|
||||
function saveDefaultUser(): void {
|
||||
if (selectedUser) {
|
||||
defaultUserStorage.setText(selectedUser.username);
|
||||
}
|
||||
}
|
||||
|
||||
// Process to fetch the list of graphical users
|
||||
Process {
|
||||
id: userLister
|
||||
|
||||
@@ -67,13 +52,10 @@ Singleton {
|
||||
try {
|
||||
root.users = JSON.parse(text);
|
||||
|
||||
// If we have users and no selection yet, try to select the default user
|
||||
if (root.users.length > 0) {
|
||||
// Try to load the default user
|
||||
if (defaultUserStorage.loaded) {
|
||||
const defaultUsername = defaultUserStorage.text().trim();
|
||||
if (defaultUsername && !root.selectUser(defaultUsername)) {
|
||||
// Default user not found, select first user
|
||||
root.selectedIndex = 0;
|
||||
}
|
||||
} else {
|
||||
@@ -87,15 +69,14 @@ Singleton {
|
||||
}
|
||||
}
|
||||
|
||||
// FileView for persisting the default user
|
||||
FileView {
|
||||
id: defaultUserStorage
|
||||
|
||||
path: root.defaultUserFile
|
||||
preload: true
|
||||
|
||||
onLoadFailed: {}
|
||||
onLoaded: {
|
||||
// If users are already loaded, try to select the default user
|
||||
if (root.users.length > 0) {
|
||||
const defaultUsername = text().trim();
|
||||
if (defaultUsername) {
|
||||
@@ -103,10 +84,5 @@ Singleton {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onLoadFailed: {
|
||||
// File doesn't exist yet, that's fine - we'll create it on first save
|
||||
console.log("No default user file found, will use first user");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
pragma Singleton
|
||||
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import ZShell
|
||||
import qs.Config
|
||||
|
||||
Singleton {
|
||||
id: root
|
||||
|
||||
property alias enabled: props.enabled
|
||||
|
||||
function setHyprConf(): void {
|
||||
Hypr.extras.applyOptions({
|
||||
"animations.enabled": 0,
|
||||
"decoration.shadow.enabled": 0,
|
||||
"decoration.blur.enabled": 0,
|
||||
"general.border_size": 0,
|
||||
"decoration.rounding": 0
|
||||
});
|
||||
}
|
||||
|
||||
onEnabledChanged: {
|
||||
if (enabled) {
|
||||
setHyprConf();
|
||||
if (Config.utilities.toasts.gameModeChanged)
|
||||
Toaster.toast(qsTr("Game mode enabled"), qsTr("Disabled Hyprland animations, blur, shadows and corner radius"), "gamepad");
|
||||
} else {
|
||||
Hypr.extras.message("reload");
|
||||
if (Config.utilities.toasts.gameModeChanged)
|
||||
Toaster.toast(qsTr("Game mode disabled"), qsTr("Hyprland settings restored"), "gamepad");
|
||||
}
|
||||
}
|
||||
|
||||
PersistentProperties {
|
||||
id: props
|
||||
|
||||
property bool enabled: Hypr.options["animations:enabled"] === 0
|
||||
|
||||
reloadableId: "gamemode"
|
||||
}
|
||||
|
||||
Connections {
|
||||
function onConfigReloaded(): void {
|
||||
if (props.enabled)
|
||||
root.setHyprConf();
|
||||
}
|
||||
|
||||
target: Hypr
|
||||
}
|
||||
|
||||
IpcHandler {
|
||||
function disable(): void {
|
||||
props.enabled = false;
|
||||
}
|
||||
|
||||
function enable(): void {
|
||||
props.enabled = true;
|
||||
}
|
||||
|
||||
function isEnabled(): bool {
|
||||
return props.enabled;
|
||||
}
|
||||
|
||||
function toggle(): void {
|
||||
props.enabled = !props.enabled;
|
||||
}
|
||||
|
||||
target: "gameMode"
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
pragma Singleton
|
||||
|
||||
import Quickshell
|
||||
import Quickshell.Networking
|
||||
|
||||
Singleton {
|
||||
id: root
|
||||
|
||||
property NetworkDevice activeDevice: devices.find(d => d.connected)
|
||||
property list<NetworkDevice> devices: Networking.devices.values
|
||||
}
|
||||
+2
-1
@@ -66,7 +66,8 @@ MouseArea {
|
||||
|
||||
function save(): void {
|
||||
const tmpfile = Qt.resolvedUrl(`/tmp/zshell-picker-${Quickshell.processId}-${Date.now()}.png`);
|
||||
ZShellIo.saveItem(screencopy, tmpfile, Qt.rect(Math.ceil(rsx), Math.ceil(rsy), Math.floor(sw), Math.floor(sh)), path => Quickshell.execDetached(["swappy", "-f", path]));
|
||||
const cmd = Config.screenshot.enable_pp ? ["zshell-img-tools", "--image"] : ["swappy", "-f"];
|
||||
ZShellIo.saveItem(screencopy, tmpfile, Qt.rect(Math.ceil(rsx), Math.ceil(rsy), Math.floor(sw), Math.floor(sh)), path => Quickshell.execDetached([...cmd, path]));
|
||||
closeAnim.start();
|
||||
}
|
||||
|
||||
|
||||
+7
-2
@@ -1,18 +1,23 @@
|
||||
pragma Singleton
|
||||
|
||||
import Quickshell
|
||||
import QtQuick
|
||||
import qs.Config
|
||||
|
||||
Singleton {
|
||||
readonly property string amPmStr: timeComponents[2] ?? ""
|
||||
id: root
|
||||
|
||||
readonly property date date: clock.date
|
||||
readonly property string dateStr: format(Config.general.dateFormat)
|
||||
property alias enabled: clock.enabled
|
||||
readonly property string hourStr: timeComponents[0] ?? ""
|
||||
readonly property int hours: clock.hours
|
||||
readonly property string minuteStr: timeComponents[1] ?? ""
|
||||
readonly property int minutes: clock.minutes
|
||||
readonly property string secondStr: timeComponents[2] ?? ""
|
||||
readonly property int seconds: clock.seconds
|
||||
readonly property list<string> timeComponents: timeStr.split(":")
|
||||
readonly property string timeStr: format("hh:mm")
|
||||
readonly property string timeStr: format("hh:mm:ss")
|
||||
|
||||
function format(fmt: string): string {
|
||||
return Qt.formatDateTime(clock.date, fmt);
|
||||
|
||||
@@ -11,10 +11,13 @@ Singleton {
|
||||
id: root
|
||||
|
||||
property int availableUpdates: 0
|
||||
property string cmd: ""
|
||||
property bool commandReady
|
||||
property bool loaded
|
||||
property double now: Date.now()
|
||||
property var updates: ({})
|
||||
property bool updating
|
||||
property string updatingPackage: ""
|
||||
|
||||
function formatUpdateTime(timestamp) {
|
||||
const diffMs = root.now - timestamp;
|
||||
@@ -34,6 +37,22 @@ Singleton {
|
||||
return Qt.formatDateTime(new Date(timestamp), "dd hh:mm");
|
||||
}
|
||||
|
||||
function performPackageUpdate(pkg: string): void {
|
||||
if (root.cmd === "pacman")
|
||||
pkgUpdateProc.command = ["pkexec", root.cmd, "--noconfirm", "-Sy", pkg];
|
||||
else
|
||||
pkgUpdateProc.command = [root.cmd, "--noconfirm", "--sudo", "pkexec", "-Sy", pkg];
|
||||
pkgUpdateProc.running = true;
|
||||
}
|
||||
|
||||
function performSystemUpdate(): void {
|
||||
if (root.cmd === "pacman")
|
||||
sysUpdateProc.command = ["pkexec", root.cmd, "--noconfirm", "-Syu"];
|
||||
else
|
||||
sysUpdateProc.command = [root.cmd, "--noconfirm", "--sudo", "pkexec", "-Syu"];
|
||||
sysUpdateProc.running = true;
|
||||
}
|
||||
|
||||
onUpdatesChanged: {
|
||||
if (!root.loaded)
|
||||
return;
|
||||
@@ -92,6 +111,28 @@ Singleton {
|
||||
}
|
||||
}
|
||||
|
||||
Process {
|
||||
id: updateCmdDetect
|
||||
|
||||
command: ["sh", "-c", "command -v yay || command -v paru"]
|
||||
running: true
|
||||
|
||||
stdout: StdioCollector {
|
||||
onStreamFinished: {
|
||||
const cmd = this.text.trim();
|
||||
let helper;
|
||||
|
||||
if (cmd.length > 0) {
|
||||
helper = cmd.split("/").pop();
|
||||
} else {
|
||||
helper = "pacman";
|
||||
}
|
||||
|
||||
root.cmd = helper;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Process {
|
||||
id: updatesProc
|
||||
|
||||
@@ -115,6 +156,44 @@ Singleton {
|
||||
}
|
||||
}
|
||||
|
||||
Process {
|
||||
id: sysUpdateProc
|
||||
|
||||
command: []
|
||||
running: false
|
||||
|
||||
stdout: StdioCollector {
|
||||
onStreamFinished: {
|
||||
root.updating = false;
|
||||
}
|
||||
}
|
||||
|
||||
onRunningChanged: {
|
||||
if (running)
|
||||
root.updating = true;
|
||||
}
|
||||
}
|
||||
|
||||
Process {
|
||||
id: pkgUpdateProc
|
||||
|
||||
command: []
|
||||
running: false
|
||||
|
||||
stdout: StdioCollector {
|
||||
onStreamFinished: {
|
||||
root.updating = false;
|
||||
}
|
||||
}
|
||||
|
||||
onRunningChanged: {
|
||||
if (running) {
|
||||
root.updatingPackage = command[command.length - 1];
|
||||
root.updating = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: saveTimer
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ Searcher {
|
||||
property string actualCurrent: WallpaperPath.currentWallpaperPath
|
||||
readonly property string current: showPreview ? previewPath : actualCurrent
|
||||
property string previewPath
|
||||
property bool recentlyChanged
|
||||
property bool showPreview: false
|
||||
|
||||
function preview(path: string): void {
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@ import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import qs.Config
|
||||
import qs.Modules
|
||||
import qs.Helpers as Helpers
|
||||
import qs.Helpers
|
||||
import qs.Components
|
||||
|
||||
CustomRect {
|
||||
|
||||
@@ -16,6 +16,7 @@ Item {
|
||||
readonly property Item current: currentPopout?.item ?? null
|
||||
readonly property Popout currentPopout: content.children.find(c => c.shouldBeActive) ?? null
|
||||
required property PopoutState popouts
|
||||
required property ShellScreen screen
|
||||
|
||||
implicitHeight: (currentPopout?.implicitHeight ?? 0) + 5 * 2
|
||||
implicitWidth: (currentPopout?.implicitWidth ?? 0) + 5 * 2
|
||||
@@ -63,6 +64,7 @@ Item {
|
||||
|
||||
TrayMenuPopout {
|
||||
popouts: root.popouts
|
||||
screen: root.screen
|
||||
trayItem: trayMenu.modelData.menu
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
import Quickshell
|
||||
import QtQuick
|
||||
import qs.Config
|
||||
import qs.Helpers
|
||||
import qs.Components
|
||||
|
||||
CustomRect {
|
||||
id: root
|
||||
|
||||
required property PersistentProperties visibilities
|
||||
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.bottomMargin: 6
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: 6
|
||||
color: DynamicColors.tPalette.m3surfaceContainer
|
||||
implicitWidth: 40
|
||||
radius: Appearance.rounding.full
|
||||
|
||||
StateLayer {
|
||||
onClicked: {
|
||||
root.visibilities.dashboard = !root.visibilities.dashboard;
|
||||
}
|
||||
}
|
||||
|
||||
MaterialIcon {
|
||||
anchors.centerIn: parent
|
||||
color: DynamicColors.palette.m3onSurface
|
||||
text: "widgets"
|
||||
}
|
||||
}
|
||||
+25
-77
@@ -12,90 +12,29 @@ Item {
|
||||
|
||||
required property Canvas drawing
|
||||
property bool expanded: true
|
||||
property real offsetScale: shouldBeActive ? 0 : 1
|
||||
required property ShellScreen screen
|
||||
readonly property bool shouldBeActive: visibilities.isDrawing
|
||||
required property var visibilities
|
||||
|
||||
anchors.leftMargin: (-implicitWidth - 5) * offsetScale
|
||||
implicitHeight: content.implicitHeight
|
||||
implicitWidth: 0
|
||||
visible: width > 0
|
||||
implicitWidth: root.expanded ? content.implicitWidth : icon.implicitWidth
|
||||
opacity: 1 - offsetScale
|
||||
visible: offsetScale < 1
|
||||
|
||||
states: [
|
||||
State {
|
||||
name: "hidden"
|
||||
when: !root.shouldBeActive
|
||||
|
||||
PropertyChanges {
|
||||
root.implicitWidth: 0
|
||||
}
|
||||
|
||||
PropertyChanges {
|
||||
icon.opacity: 0
|
||||
}
|
||||
|
||||
PropertyChanges {
|
||||
content.opacity: 0
|
||||
}
|
||||
},
|
||||
State {
|
||||
name: "collapsed"
|
||||
when: root.shouldBeActive && !root.expanded
|
||||
|
||||
PropertyChanges {
|
||||
root.implicitWidth: icon.implicitWidth
|
||||
}
|
||||
|
||||
PropertyChanges {
|
||||
icon.opacity: 1
|
||||
}
|
||||
|
||||
PropertyChanges {
|
||||
content.opacity: 0
|
||||
}
|
||||
},
|
||||
State {
|
||||
name: "visible"
|
||||
when: root.shouldBeActive && root.expanded
|
||||
|
||||
PropertyChanges {
|
||||
root.implicitWidth: content.implicitWidth
|
||||
}
|
||||
|
||||
PropertyChanges {
|
||||
icon.opacity: 0
|
||||
}
|
||||
|
||||
PropertyChanges {
|
||||
content.opacity: 1
|
||||
}
|
||||
Behavior on implicitWidth {
|
||||
Anim {
|
||||
duration: Appearance.anim.durations.expressiveDefaultSpatial
|
||||
easing.bezierCurve: Appearance.anim.curves.expressiveDefaultSpatial
|
||||
}
|
||||
]
|
||||
transitions: [
|
||||
Transition {
|
||||
from: "*"
|
||||
to: "*"
|
||||
|
||||
ParallelAnimation {
|
||||
Anim {
|
||||
easing.bezierCurve: MaterialEasing.expressiveEffects
|
||||
property: "implicitWidth"
|
||||
target: root
|
||||
}
|
||||
|
||||
Anim {
|
||||
duration: Appearance.anim.durations.small
|
||||
property: "opacity"
|
||||
target: icon
|
||||
}
|
||||
|
||||
Anim {
|
||||
duration: Appearance.anim.durations.small
|
||||
property: "opacity"
|
||||
target: content
|
||||
}
|
||||
}
|
||||
}
|
||||
Behavior on offsetScale {
|
||||
Anim {
|
||||
duration: Appearance.anim.durations.expressiveDefaultSpatial
|
||||
easing.bezierCurve: Appearance.anim.curves.expressiveDefaultSpatial
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
onVisibleChanged: {
|
||||
if (!visible)
|
||||
@@ -109,8 +48,12 @@ Item {
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
height: content.contentItem.height
|
||||
opacity: 1
|
||||
opacity: root.expanded ? 0 : 1
|
||||
|
||||
Behavior on opacity {
|
||||
Anim {
|
||||
}
|
||||
}
|
||||
sourceComponent: MaterialIcon {
|
||||
font.pointSize: Appearance.font.size.larger
|
||||
text: "arrow_forward_ios"
|
||||
@@ -122,7 +65,12 @@ Item {
|
||||
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
opacity: root.expanded ? 1 : 0
|
||||
|
||||
Behavior on opacity {
|
||||
Anim {
|
||||
}
|
||||
}
|
||||
sourceComponent: Content {
|
||||
drawing: root.drawing
|
||||
visibilities: root.visibilities
|
||||
|
||||
@@ -13,7 +13,6 @@ Searcher {
|
||||
|
||||
function launch(entry: DesktopEntry): void {
|
||||
appDb.incrementFrequency(entry.id);
|
||||
console.log(root.command);
|
||||
|
||||
if (entry.runInTerminal)
|
||||
Quickshell.execDetached({
|
||||
|
||||
@@ -8,21 +8,25 @@ import qs.Helpers
|
||||
Scope {
|
||||
id: root
|
||||
|
||||
readonly property bool enabled: !Players.list.some(p => p.isPlaying)
|
||||
required property Lock lock
|
||||
readonly property bool enabled: !Players.list.some( p => p.isPlaying )
|
||||
|
||||
function handleIdleAction( action: var ): void {
|
||||
if ( !action )
|
||||
function handleIdleAction(action: var): void {
|
||||
if (!action)
|
||||
return;
|
||||
|
||||
if ( action === "lock" )
|
||||
if (action === "lock")
|
||||
lock.lock.locked = true;
|
||||
else if ( action === "unlock" )
|
||||
else if (action === "unlock")
|
||||
lock.lock.locked = false;
|
||||
else if ( typeof action === "string" )
|
||||
Hypr.dispatch( action );
|
||||
else if (action === "dpms on")
|
||||
Hypr.dispatch('hl.dsp.dpms({ action = "enable" })');
|
||||
else if (action === "dpms off")
|
||||
Hypr.dispatch('hl.dsp.dpms({ action = "disable" })');
|
||||
else if (typeof action === "string")
|
||||
Hypr.dispatch(action);
|
||||
else
|
||||
Quickshell.execDetached( action );
|
||||
Quickshell.execDetached(action);
|
||||
}
|
||||
|
||||
Variants {
|
||||
@@ -33,7 +37,8 @@ Scope {
|
||||
|
||||
enabled: root.enabled && modelData.timeout > 0 ? true : false
|
||||
timeout: modelData.timeout
|
||||
onIsIdleChanged: root.handleIdleAction( isIdle ? modelData.idleAction : modelData.activeAction )
|
||||
|
||||
onIsIdleChanged: root.handleIdleAction(isIdle ? modelData.idleAction : modelData.activeAction)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -284,6 +284,8 @@ CustomRect {
|
||||
Layout.fillWidth: true
|
||||
color: root.urgency === "critical" ? DynamicColors.palette.m3onSecondaryContainer : DynamicColors.palette.m3onSurface
|
||||
text: {
|
||||
if (!Config.lock.showNotifContent)
|
||||
return "Unlock to view";
|
||||
const summary = modelData.summary.replace(/\n/g, " ");
|
||||
const body = modelData.body.replace(/\n/g, " ");
|
||||
const color = root.urgency === "critical" ? DynamicColors.palette.m3secondary : DynamicColors.palette.m3outline;
|
||||
|
||||
@@ -10,7 +10,6 @@ Item {
|
||||
|
||||
readonly property int padding: 6
|
||||
required property Item panels
|
||||
required property Item sidebarPanel
|
||||
required property PersistentProperties visibilities
|
||||
|
||||
anchors.bottom: parent.bottom
|
||||
@@ -44,7 +43,7 @@ Item {
|
||||
|
||||
return Math.min((QsWindow.window?.screen?.height ?? 0) - 1 * 2, height + padding * 2);
|
||||
}
|
||||
implicitWidth: Math.max(sidebarPanel.width * (1 - sidebarPanel.offsetScale), Config.notifs.sizes.width + padding * 2)
|
||||
implicitWidth: Config.notifs.sizes.width + padding * 2
|
||||
|
||||
Behavior on implicitHeight {
|
||||
Anim {
|
||||
@@ -73,9 +72,8 @@ Item {
|
||||
required property NotifServer.Notif modelData
|
||||
readonly property alias nonAnimHeight: notif.nonAnimHeight
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
implicitHeight: notif.implicitHeight + (idx === 0 ? 0 : 8)
|
||||
implicitWidth: notif.implicitWidth
|
||||
|
||||
ListView.onRemove: removeAnim.start()
|
||||
onIndexChanged: {
|
||||
@@ -126,20 +124,16 @@ Item {
|
||||
}
|
||||
|
||||
ClippingRectangle {
|
||||
// implicitWidth: notif.implicitWidth
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: wrapper.idx === 0 ? 0 : 8
|
||||
color: "transparent"
|
||||
implicitHeight: notif.implicitHeight
|
||||
implicitWidth: notif.implicitWidth
|
||||
radius: Appearance.rounding.smallest / 2
|
||||
|
||||
Notification {
|
||||
id: notif
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
modelData: wrapper.modelData
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,9 +20,9 @@ CustomRect {
|
||||
required property NotifServer.Notif modelData
|
||||
readonly property int nonAnimHeight: summary.implicitHeight + (root.expanded ? appName.height + body.height + actions.height + actions.anchors.topMargin : bodyPreview.height) + inner.anchors.margins * 2
|
||||
|
||||
// implicitWidth: Config.notifs.sizes.width
|
||||
color: root.modelData.urgency === NotificationUrgency.Critical ? DynamicColors.palette.m3secondaryContainer : DynamicColors.tPalette.m3surfaceContainer
|
||||
implicitHeight: inner.implicitHeight
|
||||
implicitWidth: Config.notifs.sizes.width
|
||||
radius: 6
|
||||
x: Config.notifs.sizes.width
|
||||
|
||||
|
||||
@@ -78,6 +78,10 @@ LazyListView {
|
||||
}
|
||||
}
|
||||
|
||||
onDoubleClicked: event => {
|
||||
if (event.button === Qt.LeftButton)
|
||||
notifInner.toggleExpand(!notifInner.expanded);
|
||||
}
|
||||
onPositionChanged: event => {
|
||||
if (pressed) {
|
||||
const diffY = event.y - startY;
|
||||
|
||||
@@ -79,6 +79,10 @@ LazyListView {
|
||||
|
||||
Component.onCompleted: modelData?.lock(this)
|
||||
Component.onDestruction: modelData?.unlock(this)
|
||||
onDoubleClicked: event => {
|
||||
if (event.button === Qt.LeftButton)
|
||||
root.requestToggleExpand(!root.expanded);
|
||||
}
|
||||
onPositionChanged: event => {
|
||||
if (pressed && !root.expanded) {
|
||||
const diffY = event.y - startY;
|
||||
|
||||
@@ -5,6 +5,7 @@ import QtQuick.Layouts
|
||||
import qs.Components
|
||||
import qs.Config
|
||||
import qs.Modules
|
||||
import qs.Helpers
|
||||
import qs.Daemons
|
||||
|
||||
CustomRect {
|
||||
@@ -32,7 +33,7 @@ CustomRect {
|
||||
Toggle {
|
||||
checked: Network.wifiEnabled
|
||||
icon: Network.wifiEnabled ? "wifi" : "wifi_off"
|
||||
visible: QSNetwork.Networking.devices.values.length > 0
|
||||
visible: QSNetwork.Networking.devices.values.some(n => n.type === QSNetwork.DeviceType.Wifi)
|
||||
|
||||
onClicked: Network.toggleWifi()
|
||||
}
|
||||
@@ -79,6 +80,13 @@ CustomRect {
|
||||
adapter.enabled = !adapter.enabled;
|
||||
}
|
||||
}
|
||||
|
||||
Toggle {
|
||||
checked: GameMode.enabled
|
||||
icon: GameMode.enabled ? "videogame_asset" : "videogame_asset_off"
|
||||
|
||||
onClicked: GameMode.enabled = !GameMode.enabled
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -17,7 +17,6 @@ Item {
|
||||
id: content
|
||||
|
||||
panels: root.panels
|
||||
sidebarPanel: root.sidebarPanel
|
||||
visibilities: root.visibilities
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,36 @@
|
||||
import QtQuick
|
||||
|
||||
QtObject {
|
||||
id: root
|
||||
|
||||
property string currentName
|
||||
property bool hasCurrent
|
||||
property var submenus: []
|
||||
|
||||
signal detachRequested(mode: string)
|
||||
|
||||
function clearSubmenus(): void {
|
||||
submenus = [];
|
||||
}
|
||||
|
||||
function closeSubmenus(level: int): void {
|
||||
submenus = submenus.slice(0, level);
|
||||
}
|
||||
|
||||
function pushSubmenu(level: int, handle: var, sourceItem: var, sourceWidth: int): void {
|
||||
let newSubmenus = submenus.slice(0, level);
|
||||
newSubmenus.push({
|
||||
"handle": handle,
|
||||
"sourceItem": sourceItem,
|
||||
"sourceWidth": sourceWidth
|
||||
});
|
||||
submenus = newSubmenus;
|
||||
}
|
||||
|
||||
onCurrentNameChanged: {
|
||||
root.clearSubmenus();
|
||||
}
|
||||
onHasCurrentChanged: {
|
||||
root.clearSubmenus();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ CustomRect {
|
||||
color: visibilities.resources ? DynamicColors.palette.m3primary : DynamicColors.tPalette.m3surfaceContainer
|
||||
implicitHeight: Config.barConfig.height + Appearance.padding.smallest * 2
|
||||
implicitWidth: rowLayout.implicitWidth + Appearance.padding.normal * 2
|
||||
radius: height / 2
|
||||
radius: Appearance.rounding.full
|
||||
|
||||
StateLayer {
|
||||
onClicked: root.visibilities.resources = !root.visibilities.resources
|
||||
|
||||
@@ -104,6 +104,18 @@ Item {
|
||||
key: "launcher"
|
||||
name: "Launcher"
|
||||
}
|
||||
|
||||
ListElement {
|
||||
icon: "screenshot_region"
|
||||
key: "screenshot"
|
||||
name: "Screenshot"
|
||||
}
|
||||
|
||||
ListElement {
|
||||
icon: "cached"
|
||||
key: "updates"
|
||||
name: "Updates"
|
||||
}
|
||||
}
|
||||
|
||||
CustomClippingRect {
|
||||
@@ -171,9 +183,15 @@ Item {
|
||||
Layout.fillHeight: true
|
||||
Layout.preferredWidth: icon.contentWidth
|
||||
color: categoryItem.index === clayout.currentIndex ? DynamicColors.palette.m3onPrimary : DynamicColors.palette.m3onSurface
|
||||
fill: categoryItem.index === clayout.currentIndex ? 1 : 0
|
||||
font.pointSize: Appearance.font.size.small * 2
|
||||
text: categoryItem.icon
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
|
||||
Behavior on fill {
|
||||
Anim {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CustomText {
|
||||
|
||||
@@ -29,13 +29,13 @@ SettingsPage {
|
||||
step: 50
|
||||
}
|
||||
|
||||
// Separator {
|
||||
// }
|
||||
//
|
||||
// WallpaperCropper {
|
||||
// Layout.fillWidth: true
|
||||
// Layout.preferredHeight: 300
|
||||
// }
|
||||
Separator {
|
||||
}
|
||||
|
||||
WallpaperCropper {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 600
|
||||
}
|
||||
}
|
||||
|
||||
SettingsSection {
|
||||
|
||||
@@ -19,8 +19,8 @@ SettingsPage {
|
||||
}
|
||||
|
||||
SettingSpinBox {
|
||||
name: "Height"
|
||||
min: 1
|
||||
name: "Height"
|
||||
object: Config.barConfig
|
||||
setting: "height"
|
||||
}
|
||||
@@ -29,8 +29,8 @@ SettingsPage {
|
||||
}
|
||||
|
||||
SettingSpinBox {
|
||||
name: "Rounding"
|
||||
min: 0
|
||||
name: "Rounding"
|
||||
object: Config.barConfig
|
||||
setting: "rounding"
|
||||
}
|
||||
@@ -39,11 +39,21 @@ SettingsPage {
|
||||
}
|
||||
|
||||
SettingSpinBox {
|
||||
name: "Border"
|
||||
min: 0
|
||||
name: "Border"
|
||||
object: Config.barConfig
|
||||
setting: "border"
|
||||
}
|
||||
|
||||
Separator {
|
||||
}
|
||||
|
||||
SettingSpinBox {
|
||||
min: 0
|
||||
name: "Smoothing"
|
||||
object: Config.barConfig
|
||||
setting: "smoothing"
|
||||
}
|
||||
}
|
||||
|
||||
SettingsSection {
|
||||
@@ -145,8 +155,8 @@ SettingsPage {
|
||||
}
|
||||
|
||||
SettingSpinBox {
|
||||
name: "Dock height"
|
||||
min: 1
|
||||
name: "Dock height"
|
||||
object: Config.dock
|
||||
setting: "height"
|
||||
}
|
||||
@@ -173,8 +183,8 @@ SettingsPage {
|
||||
}
|
||||
|
||||
SettingStringList {
|
||||
name: "Pinned apps"
|
||||
addLabel: qsTr("Add pinned app")
|
||||
name: "Pinned apps"
|
||||
object: Config.dock
|
||||
setting: "pinnedApps"
|
||||
}
|
||||
@@ -183,8 +193,8 @@ SettingsPage {
|
||||
}
|
||||
|
||||
SettingStringList {
|
||||
name: "Ignored app regexes"
|
||||
addLabel: qsTr("Add ignored regex")
|
||||
name: "Ignored app regexes"
|
||||
object: Config.dock
|
||||
setting: "ignoredAppRegexes"
|
||||
}
|
||||
|
||||
@@ -47,6 +47,15 @@ SettingsPage {
|
||||
object: Config.general
|
||||
setting: "desktopIcons"
|
||||
}
|
||||
|
||||
Separator {
|
||||
}
|
||||
|
||||
SettingInput {
|
||||
name: "Date format"
|
||||
object: Config.general
|
||||
setting: "dateFormat"
|
||||
}
|
||||
}
|
||||
|
||||
SettingsSection {
|
||||
|
||||
@@ -31,8 +31,8 @@ SettingsPage {
|
||||
}
|
||||
|
||||
SettingSpinBox {
|
||||
name: "Max fingerprint tries"
|
||||
min: 1
|
||||
name: "Max fingerprint tries"
|
||||
object: Config.lock
|
||||
setting: "maxFprintTries"
|
||||
step: 1
|
||||
@@ -41,9 +41,18 @@ SettingsPage {
|
||||
Separator {
|
||||
}
|
||||
|
||||
SettingSwitch {
|
||||
name: "Show notification details"
|
||||
object: Config.lock
|
||||
setting: "showNotifContent"
|
||||
}
|
||||
|
||||
Separator {
|
||||
}
|
||||
|
||||
SettingSpinBox {
|
||||
name: "Blur amount"
|
||||
min: 0
|
||||
name: "Blur amount"
|
||||
object: Config.lock
|
||||
setting: "blurAmount"
|
||||
step: 1
|
||||
@@ -53,9 +62,9 @@ SettingsPage {
|
||||
}
|
||||
|
||||
SettingSpinBox {
|
||||
name: "Height multiplier"
|
||||
max: 2
|
||||
min: 0.1
|
||||
name: "Height multiplier"
|
||||
object: Config.lock.sizes
|
||||
setting: "heightMult"
|
||||
step: 0.05
|
||||
@@ -65,9 +74,9 @@ SettingsPage {
|
||||
}
|
||||
|
||||
SettingSpinBox {
|
||||
name: "Aspect ratio"
|
||||
max: 4
|
||||
min: 0.5
|
||||
name: "Aspect ratio"
|
||||
object: Config.lock.sizes
|
||||
setting: "ratio"
|
||||
step: 0.05
|
||||
@@ -77,8 +86,8 @@ SettingsPage {
|
||||
}
|
||||
|
||||
SettingSpinBox {
|
||||
name: "Center width"
|
||||
min: 100
|
||||
name: "Center width"
|
||||
object: Config.lock.sizes
|
||||
setting: "centerWidth"
|
||||
step: 10
|
||||
|
||||
@@ -22,6 +22,13 @@ ColumnLayout {
|
||||
Config.save();
|
||||
}
|
||||
|
||||
function deleteTimeoutEntry(index) {
|
||||
let list = [...Config.general.idle.timeouts];
|
||||
list.splice(index, 1);
|
||||
Config.general.idle.timeouts = list;
|
||||
Config.save();
|
||||
}
|
||||
|
||||
function updateTimeoutEntry(i, key, value) {
|
||||
const list = [...Config.general.idle.timeouts];
|
||||
let entry = list[i];
|
||||
@@ -49,6 +56,9 @@ ColumnLayout {
|
||||
onAddActiveActionRequested: {
|
||||
root.updateTimeoutEntry(index, "activeAction", "");
|
||||
}
|
||||
onDeleteRequested: function (index) {
|
||||
root.deleteTimeoutEntry(index);
|
||||
}
|
||||
onFieldEdited: function (key, value) {
|
||||
root.updateTimeoutEntry(index, key, value);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,130 @@
|
||||
import qs.Modules.Settings.Controls
|
||||
import qs.Config
|
||||
import qs.Components
|
||||
|
||||
SettingsPage {
|
||||
SettingsSection {
|
||||
sectionId: "Screenshot"
|
||||
|
||||
SettingsHeader {
|
||||
name: "Screenshot"
|
||||
}
|
||||
|
||||
SettingSwitch {
|
||||
name: "Enable effects"
|
||||
object: Config.screenshot
|
||||
setting: "enable_pp"
|
||||
}
|
||||
|
||||
Separator {
|
||||
}
|
||||
|
||||
CustomSplitButtonRow {
|
||||
// active: true
|
||||
label: qsTr("Effects mode")
|
||||
|
||||
menuItems: [
|
||||
MenuItem {
|
||||
icon: "build"
|
||||
text: qsTr("Manual")
|
||||
value: "manual"
|
||||
},
|
||||
MenuItem {
|
||||
icon: "rotate_auto"
|
||||
text: qsTr("Auto")
|
||||
value: "auto"
|
||||
}
|
||||
]
|
||||
|
||||
onSelected: item => {
|
||||
Config.screenshot.mode = item.value;
|
||||
Config.save();
|
||||
}
|
||||
}
|
||||
|
||||
Separator {
|
||||
visible: Config.screenshot.mode === "manual"
|
||||
}
|
||||
|
||||
SettingSpinBox {
|
||||
min: 0
|
||||
name: "Corner radius"
|
||||
object: Config.screenshot
|
||||
setting: "corner_radius"
|
||||
step: 1
|
||||
visible: Config.screenshot.mode === "manual"
|
||||
}
|
||||
|
||||
Separator {
|
||||
visible: Config.screenshot.mode === "manual"
|
||||
}
|
||||
|
||||
SettingSwitch {
|
||||
name: "Enable drop shadow"
|
||||
object: Config.screenshot
|
||||
setting: "drop_shadow"
|
||||
visible: Config.screenshot.mode === "manual"
|
||||
}
|
||||
|
||||
Separator {
|
||||
visible: Config.screenshot.mode === "manual"
|
||||
}
|
||||
|
||||
SettingSwitch {
|
||||
name: "Enable rounded corners"
|
||||
object: Config.screenshot
|
||||
setting: "rounded_corners"
|
||||
visible: Config.screenshot.mode === "manual"
|
||||
}
|
||||
|
||||
Separator {
|
||||
visible: Config.screenshot.mode === "manual"
|
||||
}
|
||||
|
||||
SettingSpinBox {
|
||||
min: 0
|
||||
name: "Shadow blur radius"
|
||||
object: Config.screenshot
|
||||
setting: "shadow_blur_radius"
|
||||
step: 1
|
||||
visible: Config.screenshot.mode === "manual"
|
||||
}
|
||||
|
||||
Separator {
|
||||
visible: Config.screenshot.mode === "manual"
|
||||
}
|
||||
|
||||
SettingSwitch {
|
||||
name: "Shadow color broken atm"
|
||||
object: Config.Screenshot
|
||||
setting: "shadow_color"
|
||||
visible: Config.screenshot.mode === "manual"
|
||||
}
|
||||
|
||||
Separator {
|
||||
visible: Config.screenshot.mode === "manual"
|
||||
}
|
||||
|
||||
SettingSpinBox {
|
||||
min: 0
|
||||
name: "Shadow offset X"
|
||||
object: Config.screenshot
|
||||
setting: "shadow_offset_x"
|
||||
step: 1
|
||||
visible: Config.screenshot.mode === "manual"
|
||||
}
|
||||
|
||||
Separator {
|
||||
visible: Config.screenshot.mode === "manual"
|
||||
}
|
||||
|
||||
SettingSpinBox {
|
||||
min: 0
|
||||
name: "Shadow offset Y"
|
||||
object: Config.screenshot
|
||||
setting: "shadow_offset_y"
|
||||
step: 1
|
||||
visible: Config.screenshot.mode === "manual"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,197 @@
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import Quickshell
|
||||
import QtQuick.Layouts
|
||||
import QtQuick
|
||||
import qs.Config
|
||||
import qs.Helpers
|
||||
import qs.Components
|
||||
import qs.Modules.Settings.Controls
|
||||
|
||||
CustomClippingRect {
|
||||
id: root
|
||||
|
||||
radius: Appearance.rounding.normal - Appearance.padding.smaller
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
Layout.margins: Appearance.padding.large
|
||||
spacing: Appearance.spacing.large
|
||||
|
||||
MaterialIcon {
|
||||
font.pointSize: Appearance.font.size.larger * 4
|
||||
text: "update"
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
CustomText {
|
||||
font.pointSize: Appearance.font.size.large * 2
|
||||
text: "System updates"
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
id: row
|
||||
|
||||
Layout.fillWidth: true
|
||||
|
||||
CustomText {
|
||||
id: text
|
||||
|
||||
Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter
|
||||
Layout.fillWidth: true
|
||||
font.pointSize: Appearance.font.size.larger
|
||||
text: `${Updates.availableUpdates} available updates`
|
||||
}
|
||||
|
||||
CustomRect {
|
||||
Layout.preferredHeight: 40
|
||||
Layout.preferredWidth: 150
|
||||
color: Updates.updating ? DynamicColors.layer(DynamicColors.palette.m3outline, 2) : DynamicColors.palette.m3primary
|
||||
radius: Appearance.rounding.full
|
||||
|
||||
RowLayout {
|
||||
anchors.centerIn: parent
|
||||
|
||||
MaterialIcon {
|
||||
animate: true
|
||||
color: DynamicColors.palette.m3onPrimary
|
||||
font.pointSize: Appearance.font.size.large
|
||||
text: Updates.updating ? "update" : "download"
|
||||
}
|
||||
|
||||
CustomText {
|
||||
color: Updates.updating ? DynamicColors.palette.m3onSurface : DynamicColors.palette.m3onPrimary
|
||||
text: "Update all"
|
||||
}
|
||||
}
|
||||
|
||||
StateLayer {
|
||||
color: DynamicColors.palette.m3onPrimary
|
||||
disabled: Updates.updating
|
||||
|
||||
onClicked: Updates.performSystemUpdate()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CustomListView {
|
||||
id: view
|
||||
|
||||
readonly property int itemHeight: 50 + Appearance.padding.smaller * 2
|
||||
|
||||
Layout.fillHeight: true
|
||||
Layout.fillWidth: true
|
||||
clip: true
|
||||
contentHeight: height
|
||||
spacing: Appearance.spacing.normal
|
||||
|
||||
delegate: CustomRect {
|
||||
id: update
|
||||
|
||||
required property var modelData
|
||||
readonly property list<string> sections: modelData.update.split(" ")
|
||||
|
||||
color: DynamicColors.tPalette.m3surfaceContainer
|
||||
implicitHeight: view.itemHeight
|
||||
implicitWidth: parent.width
|
||||
radius: Appearance.rounding.small - Appearance.padding.small
|
||||
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
anchors.leftMargin: Appearance.padding.smaller
|
||||
anchors.rightMargin: Appearance.padding.smaller
|
||||
|
||||
MaterialIcon {
|
||||
font.pointSize: Appearance.font.size.large * 2
|
||||
text: "package_2"
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
|
||||
CustomText {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 25
|
||||
elide: Text.ElideRight
|
||||
font.pointSize: Appearance.font.size.large
|
||||
text: update.sections[0]
|
||||
}
|
||||
|
||||
CustomText {
|
||||
Layout.fillWidth: true
|
||||
color: DynamicColors.palette.m3onSurfaceVariant
|
||||
text: Updates.formatUpdateTime(update.modelData.timestamp)
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.fillHeight: true
|
||||
Layout.preferredWidth: 500
|
||||
|
||||
MarqueeText {
|
||||
id: versionFrom
|
||||
|
||||
Layout.fillHeight: true
|
||||
Layout.preferredWidth: 225
|
||||
animate: true
|
||||
color: DynamicColors.palette.m3tertiary
|
||||
font.pointSize: Appearance.font.size.large
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
marqueeEnabled: true
|
||||
pauseMs: 4000
|
||||
text: update.sections[1]
|
||||
width: 225
|
||||
}
|
||||
|
||||
MaterialIcon {
|
||||
Layout.fillHeight: true
|
||||
color: DynamicColors.palette.m3secondary
|
||||
font.pointSize: Appearance.font.size.extraLarge
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
text: "arrow_right_alt"
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
}
|
||||
|
||||
MarqueeText {
|
||||
id: versionTo
|
||||
|
||||
Layout.fillHeight: true
|
||||
Layout.preferredWidth: 225
|
||||
animate: true
|
||||
color: DynamicColors.palette.m3primary
|
||||
font.pointSize: Appearance.font.size.large
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
marqueeEnabled: true
|
||||
pauseMs: 4000
|
||||
text: update.sections[3]
|
||||
width: 225
|
||||
}
|
||||
}
|
||||
|
||||
IconButton {
|
||||
Layout.preferredHeight: width
|
||||
icon: "download"
|
||||
|
||||
onClicked: {
|
||||
Updates.performPackageUpdate(update.sections[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
model: ScriptModel {
|
||||
id: script
|
||||
|
||||
objectProp: "update"
|
||||
values: Object.entries(Updates.updates).sort((a, b) => b[1] - a[1]).map(([update, timestamp]) => ({
|
||||
update,
|
||||
timestamp
|
||||
}))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -74,6 +74,10 @@ Item {
|
||||
stack.push(osd);
|
||||
else if (currentCategory === "launcher")
|
||||
stack.push(launcher);
|
||||
else if (currentCategory === "screenshot")
|
||||
stack.push(screenshot);
|
||||
else if (currentCategory === "updates")
|
||||
stack.push(updates);
|
||||
}
|
||||
|
||||
target: root
|
||||
@@ -225,4 +229,18 @@ Item {
|
||||
Cat.Launcher {
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: screenshot
|
||||
|
||||
Cat.Screenshot {
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: updates
|
||||
|
||||
Cat.SystemUpdates {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,72 +28,11 @@ Item {
|
||||
property int uidCounter: 0
|
||||
property var visualEntries: []
|
||||
|
||||
function beginVisualDrag(uid, modelData, item) {
|
||||
const pos = item.mapToItem(root, 0, 0);
|
||||
|
||||
root.draggedUid = uid;
|
||||
root.draggedModelData = modelData;
|
||||
root.dragHeight = item.height;
|
||||
root.dragStartX = pos.x;
|
||||
root.dragStartY = pos.y;
|
||||
root.dragX = pos.x;
|
||||
root.dragY = pos.y;
|
||||
root.dragActive = true;
|
||||
root.dropAnimating = false;
|
||||
root.pendingCommitEntries = [];
|
||||
}
|
||||
|
||||
function commitVisualOrder(entries) {
|
||||
const list = [];
|
||||
|
||||
for (let i = 0; i < entries.length; i++)
|
||||
list.push(entries[i].entry);
|
||||
|
||||
root.object[root.setting] = list;
|
||||
Config.save();
|
||||
root.rebuildVisualEntries();
|
||||
}
|
||||
|
||||
function endVisualDrag() {
|
||||
const entries = root.visualEntries.slice();
|
||||
const finalIndex = root.indexForUid(root.draggedUid);
|
||||
const finalItem = listView.itemAtIndex(finalIndex);
|
||||
|
||||
root.dragActive = false;
|
||||
|
||||
if (!finalItem) {
|
||||
root.pendingCommitEntries = entries;
|
||||
root.finishVisualDrag();
|
||||
return;
|
||||
}
|
||||
|
||||
const pos = finalItem.mapToItem(root, 0, 0);
|
||||
|
||||
root.pendingCommitEntries = entries;
|
||||
root.dropAnimating = true;
|
||||
settleX.to = pos.x;
|
||||
settleY.to = pos.y;
|
||||
settleAnim.start();
|
||||
}
|
||||
|
||||
function ensureVisualEntries() {
|
||||
if (!root.dragActive && !root.dropAnimating)
|
||||
root.rebuildVisualEntries();
|
||||
}
|
||||
|
||||
function finishVisualDrag() {
|
||||
const entries = root.pendingCommitEntries.slice();
|
||||
|
||||
root.dragActive = false;
|
||||
root.dropAnimating = false;
|
||||
root.draggedUid = "";
|
||||
root.draggedModelData = null;
|
||||
root.pendingCommitEntries = [];
|
||||
root.dragHeight = 0;
|
||||
|
||||
root.commitVisualOrder(entries);
|
||||
}
|
||||
|
||||
function iconForId(id) {
|
||||
switch (id) {
|
||||
case "workspaces":
|
||||
@@ -153,7 +92,7 @@ Item {
|
||||
case "spacer":
|
||||
return qsTr("Spacer");
|
||||
case "activeWindow":
|
||||
return qsTr("Active window");
|
||||
return qsTr("Title");
|
||||
case "tray":
|
||||
return qsTr("Tray");
|
||||
case "upower":
|
||||
@@ -163,34 +102,14 @@ Item {
|
||||
case "clock":
|
||||
return qsTr("Clock");
|
||||
case "notifBell":
|
||||
return qsTr("Notification bell");
|
||||
return qsTr("Notifs");
|
||||
case "hyprsunset":
|
||||
return qsTr("Night light");
|
||||
default:
|
||||
return id;
|
||||
}
|
||||
}
|
||||
|
||||
function moveArrayItem(list, from, to) {
|
||||
const next = list.slice();
|
||||
const [item] = next.splice(from, 1);
|
||||
next.splice(to, 0, item);
|
||||
return next;
|
||||
}
|
||||
|
||||
function previewVisualMove(from, hovered, before) {
|
||||
let to = hovered + (before ? 0 : 1);
|
||||
|
||||
if (to > from)
|
||||
to -= 1;
|
||||
|
||||
to = Math.max(0, Math.min(visualModel.items.count - 1, to));
|
||||
|
||||
if (from === to)
|
||||
return;
|
||||
|
||||
visualModel.items.move(from, to);
|
||||
root.visualEntries = root.moveArrayItem(root.visualEntries, from, to);
|
||||
}
|
||||
|
||||
function rebuildVisualEntries() {
|
||||
const entries = root.object[root.setting] ?? [];
|
||||
const next = [];
|
||||
@@ -225,7 +144,6 @@ Item {
|
||||
list[index] = entry;
|
||||
root.object[root.setting] = list;
|
||||
Config.save();
|
||||
root.ensureVisualEntries();
|
||||
}
|
||||
|
||||
Layout.fillWidth: true
|
||||
@@ -233,7 +151,7 @@ Item {
|
||||
|
||||
Component.onCompleted: root.rebuildVisualEntries()
|
||||
|
||||
Rectangle {
|
||||
CustomRect {
|
||||
anchors.fill: parent
|
||||
anchors.margins: -Appearance.padding.smaller
|
||||
color: DynamicColors.palette.m3primaryContainer
|
||||
@@ -248,39 +166,12 @@ Item {
|
||||
}
|
||||
}
|
||||
|
||||
ParallelAnimation {
|
||||
id: settleAnim
|
||||
|
||||
onFinished: root.finishVisualDrag()
|
||||
|
||||
Anim {
|
||||
id: settleX
|
||||
|
||||
duration: Appearance.anim.durations.normal
|
||||
property: "dragX"
|
||||
target: root
|
||||
}
|
||||
|
||||
Anim {
|
||||
id: settleY
|
||||
|
||||
duration: Appearance.anim.durations.normal
|
||||
property: "dragY"
|
||||
target: root
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
RowLayout {
|
||||
id: layout
|
||||
|
||||
anchors.fill: parent
|
||||
spacing: Appearance.spacing.smaller
|
||||
|
||||
CustomText {
|
||||
Layout.fillWidth: true
|
||||
font.pointSize: Appearance.font.size.larger
|
||||
text: root.name
|
||||
}
|
||||
// spacing: Appearance.spacing.smaller
|
||||
|
||||
DelegateModel {
|
||||
id: visualModel
|
||||
@@ -293,248 +184,40 @@ Item {
|
||||
}
|
||||
}
|
||||
|
||||
ListView {
|
||||
id: listView
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: contentHeight
|
||||
boundsBehavior: Flickable.StopAtBounds
|
||||
clip: false
|
||||
implicitHeight: contentHeight
|
||||
implicitWidth: width
|
||||
interactive: !(root.dragActive || root.dropAnimating)
|
||||
Repeater {
|
||||
delegate: entryDelegate
|
||||
model: visualModel
|
||||
spacing: Appearance.spacing.small
|
||||
|
||||
add: Transition {
|
||||
Anim {
|
||||
properties: "opacity,scale"
|
||||
to: 1
|
||||
}
|
||||
}
|
||||
addDisplaced: Transition {
|
||||
Anim {
|
||||
duration: Appearance.anim.durations.normal
|
||||
property: "y"
|
||||
}
|
||||
}
|
||||
displaced: Transition {
|
||||
Anim {
|
||||
duration: Appearance.anim.durations.normal
|
||||
property: "y"
|
||||
}
|
||||
}
|
||||
move: Transition {
|
||||
Anim {
|
||||
duration: Appearance.anim.durations.normal
|
||||
property: "y"
|
||||
}
|
||||
}
|
||||
removeDisplaced: Transition {
|
||||
Anim {
|
||||
duration: Appearance.anim.durations.normal
|
||||
property: "y"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
active: root.dragActive || root.dropAnimating
|
||||
asynchronous: false
|
||||
|
||||
sourceComponent: Item {
|
||||
Drag.active: root.dragActive
|
||||
Drag.hotSpot.x: width / 2
|
||||
Drag.hotSpot.y: height / 2
|
||||
height: proxyRect.implicitHeight
|
||||
implicitHeight: proxyRect.implicitHeight
|
||||
implicitWidth: listView.width
|
||||
visible: root.draggedModelData !== null
|
||||
width: listView.width
|
||||
x: root.dragX
|
||||
y: root.dragY
|
||||
z: 100
|
||||
|
||||
Drag.source: QtObject {
|
||||
property string uid: root.draggedUid
|
||||
property int visualIndex: root.indexForUid(root.draggedUid)
|
||||
}
|
||||
|
||||
CustomRect {
|
||||
id: proxyRect
|
||||
|
||||
color: DynamicColors.tPalette.m3surface
|
||||
implicitHeight: proxyRow.implicitHeight + Appearance.padding.small * 2
|
||||
implicitWidth: parent.width
|
||||
opacity: 0.95
|
||||
radius: Appearance.rounding.normal
|
||||
width: parent.width
|
||||
|
||||
RowLayout {
|
||||
id: proxyRow
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.margins: Appearance.padding.small
|
||||
spacing: Appearance.spacing.normal
|
||||
|
||||
CustomRect {
|
||||
color: Qt.alpha(DynamicColors.palette.m3onSurface, 0.12)
|
||||
implicitHeight: 32
|
||||
implicitWidth: implicitHeight
|
||||
radius: Appearance.rounding.small
|
||||
|
||||
MaterialIcon {
|
||||
anchors.centerIn: parent
|
||||
color: DynamicColors.palette.m3onSurfaceVariant
|
||||
text: "drag_indicator"
|
||||
}
|
||||
}
|
||||
|
||||
MaterialIcon {
|
||||
color: DynamicColors.palette.m3onSurfaceVariant
|
||||
text: root.iconForId(root.draggedModelData?.entry?.id ?? "")
|
||||
}
|
||||
|
||||
CustomText {
|
||||
Layout.fillWidth: true
|
||||
font.pointSize: Appearance.font.size.larger
|
||||
text: root.labelForId(root.draggedModelData?.entry?.id ?? "")
|
||||
}
|
||||
|
||||
CustomSwitch {
|
||||
checked: root.draggedModelData?.entry?.enabled ?? true
|
||||
enabled: false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: entryDelegate
|
||||
|
||||
DropArea {
|
||||
id: slot
|
||||
|
||||
readonly property var entryData: modelData.entry
|
||||
IconButton {
|
||||
required property int index
|
||||
required property var modelData
|
||||
readonly property string uid: modelData.uid
|
||||
|
||||
function previewReorder(drag) {
|
||||
const source = drag.source;
|
||||
if (!source || !source.uid || source.uid === slot.uid)
|
||||
return;
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredWidth: implicitWidth + (stateLayer.pressed ? 18 : internalChecked ? 7 : 0)
|
||||
checked: modelData.entry.enabled ?? true
|
||||
font: Appearance.font.family.sans
|
||||
// icon: root.iconForId(modelData.entry.id)
|
||||
icon: root.labelForId(modelData.entry.id)
|
||||
inactiveColour: DynamicColors.layer(DynamicColors.palette.m3surfaceContainerHighest, 2)
|
||||
radius: stateLayer.pressed ? 6 / 2 : internalChecked ? 6 : 8
|
||||
radiusAnim.duration: MaterialEasing.expressiveEffectsTime
|
||||
radiusAnim.easing.bezierCurve: MaterialEasing.expressiveEffects
|
||||
toggle: true
|
||||
visible: !["spacer", "upower", "dash", "audio"].some(prefix => modelData.entry.id.startsWith(prefix))
|
||||
|
||||
const from = source.visualIndex;
|
||||
const hovered = slot.DelegateModel.itemsIndex;
|
||||
|
||||
if (from < 0 || hovered < 0)
|
||||
return;
|
||||
|
||||
root.previewVisualMove(from, hovered, drag.y < height / 2);
|
||||
}
|
||||
|
||||
height: entryRow.implicitHeight
|
||||
implicitHeight: entryRow.implicitHeight
|
||||
implicitWidth: listView.width
|
||||
width: ListView.view ? ListView.view.width : listView.width
|
||||
|
||||
onEntered: drag => previewReorder(drag)
|
||||
onPositionChanged: drag => previewReorder(drag)
|
||||
|
||||
CustomRect {
|
||||
id: entryRow
|
||||
|
||||
anchors.fill: parent
|
||||
color: DynamicColors.tPalette.m3surface
|
||||
implicitHeight: entryLayout.implicitHeight + Appearance.padding.small * 2
|
||||
implicitWidth: parent.width
|
||||
opacity: root.draggedUid === slot.uid ? 0 : 1
|
||||
radius: Appearance.rounding.full
|
||||
|
||||
Behavior on opacity {
|
||||
Anim {
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
id: entryLayout
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.margins: Appearance.padding.small
|
||||
spacing: Appearance.spacing.normal
|
||||
|
||||
CustomRect {
|
||||
id: handle
|
||||
|
||||
color: Qt.alpha(DynamicColors.palette.m3onSurface, handleDrag.active ? 0.12 : handleHover.hovered ? 0.09 : 0.06)
|
||||
implicitHeight: 32
|
||||
implicitWidth: implicitHeight
|
||||
radius: Appearance.rounding.full
|
||||
|
||||
Behavior on color {
|
||||
CAnim {
|
||||
}
|
||||
}
|
||||
|
||||
MaterialIcon {
|
||||
anchors.centerIn: parent
|
||||
color: DynamicColors.palette.m3onSurfaceVariant
|
||||
text: "drag_indicator"
|
||||
}
|
||||
|
||||
HoverHandler {
|
||||
id: handleHover
|
||||
|
||||
cursorShape: handleDrag.active ? Qt.ClosedHandCursor : Qt.OpenHandCursor
|
||||
}
|
||||
|
||||
DragHandler {
|
||||
id: handleDrag
|
||||
|
||||
enabled: true
|
||||
grabPermissions: PointerHandler.CanTakeOverFromAnything
|
||||
target: null
|
||||
xAxis.enabled: false
|
||||
yAxis.enabled: true
|
||||
|
||||
onActiveChanged: {
|
||||
if (active) {
|
||||
root.beginVisualDrag(slot.uid, slot.modelData, entryRow);
|
||||
} else if (root.draggedUid === slot.uid) {
|
||||
root.endVisualDrag();
|
||||
}
|
||||
}
|
||||
onActiveTranslationChanged: {
|
||||
if (!active || root.draggedUid !== slot.uid)
|
||||
return;
|
||||
|
||||
root.dragX = root.dragStartX;
|
||||
root.dragY = root.dragStartY + activeTranslation.y;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MaterialIcon {
|
||||
color: DynamicColors.palette.m3onSurfaceVariant
|
||||
text: root.iconForId(slot.entryData.id)
|
||||
}
|
||||
|
||||
CustomText {
|
||||
Layout.fillWidth: true
|
||||
font.pointSize: Appearance.font.size.larger
|
||||
text: root.labelForId(slot.entryData.id)
|
||||
}
|
||||
|
||||
CustomSwitch {
|
||||
Layout.rightMargin: Appearance.padding.small
|
||||
checked: slot.entryData.enabled ?? true
|
||||
|
||||
onToggled: root.updateEntry(slot.DelegateModel.itemsIndex, checked)
|
||||
}
|
||||
Behavior on Layout.preferredWidth {
|
||||
Anim {
|
||||
duration: MaterialEasing.expressiveEffectsTime
|
||||
easing.bezierCurve: MaterialEasing.expressiveEffects
|
||||
}
|
||||
}
|
||||
|
||||
onClicked: root.updateEntry(index, internalChecked)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ Item {
|
||||
required property var modelData
|
||||
|
||||
signal addActiveActionRequested
|
||||
signal deleteRequested(int index)
|
||||
signal fieldEdited(string key, var value)
|
||||
|
||||
Layout.fillWidth: true
|
||||
@@ -65,42 +66,64 @@ Item {
|
||||
|
||||
HoverHandler {
|
||||
id: nameHover
|
||||
}
|
||||
|
||||
HoverIconButton {
|
||||
id: editButton
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
font.pointSize: Appearance.font.size.large
|
||||
icon: "edit"
|
||||
shouldBeVisible: nameHover.hovered && !nameCell.editing
|
||||
|
||||
onClicked: nameCell.beginEdit()
|
||||
}
|
||||
|
||||
CustomText {
|
||||
anchors.left: parent.left
|
||||
anchors.right: editButton.left
|
||||
anchors.leftMargin: nameHover.hovered ? editButton.width + Appearance.spacing.smaller * 2 : 0
|
||||
anchors.right: deleteButton.left
|
||||
anchors.rightMargin: Appearance.spacing.small
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
elide: Text.ElideRight // enable if CustomText supports it
|
||||
font.pointSize: Appearance.font.size.larger
|
||||
text: root.modelData.name
|
||||
visible: !nameCell.editing
|
||||
|
||||
Behavior on anchors.leftMargin {
|
||||
Anim {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
IconButton {
|
||||
id: editButton
|
||||
HoverIconButton {
|
||||
id: deleteButton
|
||||
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
font.pointSize: Appearance.font.size.large
|
||||
icon: "edit"
|
||||
visible: nameHover.hovered && !nameCell.editing
|
||||
icon: "delete"
|
||||
shouldBeVisible: nameHover.hovered && !nameCell.editing
|
||||
|
||||
onClicked: nameCell.beginEdit()
|
||||
onClicked: root.deleteRequested(root.index)
|
||||
}
|
||||
|
||||
CustomRect {
|
||||
anchors.fill: parent
|
||||
anchors.left: parent.left
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
color: DynamicColors.tPalette.m3surface
|
||||
implicitHeight: nameEditor.implicitHeight + (Appearance.padding.normal * 2)
|
||||
implicitWidth: Math.min(nameEditor.contentWidth + (Appearance.padding.normal * 2), parent.width - Appearance.padding.normal)
|
||||
radius: Appearance.rounding.small
|
||||
visible: nameCell.editing
|
||||
|
||||
CustomTextField {
|
||||
id: nameEditor
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.centerIn: parent
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
implicitWidth: Math.min(contentWidth + Appearance.padding.normal * 2, nameCell.width - Appearance.padding.normal)
|
||||
text: nameCell.draftName
|
||||
|
||||
Keys.onEscapePressed: {
|
||||
|
||||
@@ -38,7 +38,7 @@ Item {
|
||||
Layout.preferredHeight: 42 * 6 + Appearance.padding.normal * 2 + Appearance.spacing.small * 5
|
||||
Layout.preferredWidth: 500
|
||||
color: DynamicColors.tPalette.m3surfaceContainer
|
||||
radius: 21 + Appearance.padding.normal
|
||||
radius: (21 + Appearance.padding.normal) * Appearance.rounding.scale
|
||||
|
||||
CustomRect {
|
||||
id: searchBox
|
||||
|
||||
@@ -6,97 +6,109 @@ import qs.Config
|
||||
import qs.Components
|
||||
import qs.Helpers
|
||||
|
||||
ColumnLayout {
|
||||
Item {
|
||||
id: root
|
||||
|
||||
spacing: 15
|
||||
width: Math.min(parent ? parent.width : 600, 600)
|
||||
Image {
|
||||
id: imageView
|
||||
|
||||
Rectangle {
|
||||
id: previewContainer
|
||||
property real displayH: paintedHeight
|
||||
property real displayW: paintedWidth
|
||||
property real displayX: (width - paintedWidth) * 0.5
|
||||
property real displayY: (height - paintedHeight) * 0.5
|
||||
property real scaleX: sourceW / displayW
|
||||
property real scaleY: sourceH / displayH
|
||||
property real sourceH: Quickshell.screens[0].height
|
||||
property real sourceW: Quickshell.screens[0].width
|
||||
|
||||
anchors.fill: parent
|
||||
fillMode: Image.PreserveAspectFit
|
||||
smooth: true
|
||||
source: Wallpapers.current
|
||||
}
|
||||
|
||||
Item {
|
||||
id: overlay
|
||||
|
||||
Layout.fillHeight: true
|
||||
Layout.preferredWidth: height * (Quickshell.screens.length > 0 ? (Quickshell.screens[0].height / Math.max(1, Quickshell.screens[0].width)) : 16 / 9)
|
||||
clip: true
|
||||
color: DynamicColors.palette.m3surfaceContainer
|
||||
radius: Config.appearance.rounding.scale * 10
|
||||
height: imageView.displayH
|
||||
width: imageView.displayW
|
||||
x: imageView.displayX
|
||||
y: imageView.displayY
|
||||
|
||||
Image {
|
||||
id: img
|
||||
CustomRect {
|
||||
id: cropRect
|
||||
|
||||
property real aspectRatio: Quickshell.screens[0].width / Quickshell.screens[0].height
|
||||
readonly property rect sourceRect: Qt.rect(x * imageView.scaleX, y * imageView.scaleY, width * imageView.scaleX, height * imageView.scaleY)
|
||||
property real zoom: Config.background.zoom
|
||||
|
||||
function clampToBounds() {
|
||||
x = Math.max(0, Math.min(x, overlay.width - width));
|
||||
|
||||
y = Math.max(0, Math.min(y, overlay.height - height));
|
||||
}
|
||||
|
||||
border.color: DynamicColors.palette.m3primary
|
||||
border.width: 2
|
||||
color: DynamicColors.tPalette.m3primary
|
||||
height: width / aspectRatio
|
||||
radius: Appearance.rounding.small
|
||||
visible: imageView.status === Image.Ready
|
||||
width: Math.min(overlay.width / zoom, overlay.height * aspectRatio / zoom)
|
||||
x: Config.background.sourceClipX / imageView.scaleX
|
||||
y: Config.background.sourceClipY / imageView.scaleY
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
function updateCrop(mouseX, mouseY) {
|
||||
let nx = mouseX - cropRect.width * 0.5;
|
||||
let ny = mouseY - cropRect.height * 0.5;
|
||||
|
||||
nx = Math.max(0, Math.min(nx, overlay.width - cropRect.width));
|
||||
|
||||
ny = Math.max(0, Math.min(ny, overlay.height - cropRect.height));
|
||||
|
||||
cropRect.x = nx;
|
||||
cropRect.y = ny;
|
||||
}
|
||||
|
||||
anchors.fill: parent
|
||||
asynchronous: true
|
||||
fillMode: Image.PreserveAspectFit
|
||||
source: Wallpapers.current
|
||||
hoverEnabled: true
|
||||
preventStealing: true
|
||||
|
||||
Rectangle {
|
||||
id: cropRect
|
||||
onPositionChanged: mouse => {
|
||||
if (pressed)
|
||||
updateCrop(mouse.x, mouse.y);
|
||||
}
|
||||
onPressed: mouse => {
|
||||
updateCrop(mouse.x, mouse.y);
|
||||
}
|
||||
onReleased: {
|
||||
Wallpapers.recentlyChanged = false;
|
||||
Config.background.sourceClipX = cropRect.sourceRect.x;
|
||||
Config.background.sourceClipY = cropRect.sourceRect.y;
|
||||
Config.background.sourceClipW = cropRect.sourceRect.width;
|
||||
Config.background.sourceClipH = cropRect.sourceRect.height;
|
||||
Config.save();
|
||||
}
|
||||
onWheel: wheel => {
|
||||
let oldCenterX = cropRect.x + cropRect.width * 0.5;
|
||||
let oldCenterY = cropRect.y + cropRect.height * 0.5;
|
||||
|
||||
property real cropHeight: (imageAspect > screenAspect ? paintedHeight : paintedWidth / screenAspect) / Config.background.zoom
|
||||
property real cropWidth: (imageAspect > screenAspect ? paintedHeight * screenAspect : paintedWidth) / Config.background.zoom
|
||||
property real imageAspect: Math.max(1, paintedWidth) / Math.max(1, paintedHeight)
|
||||
property real paintedHeight: img.paintedHeight > 0 ? img.paintedHeight : img.height
|
||||
property real paintedWidth: img.paintedWidth > 0 ? img.paintedWidth : img.width
|
||||
property real paintedX: (img.width - paintedWidth) / 2
|
||||
property real paintedY: (img.height - paintedHeight) / 2
|
||||
property real screenAspect: Quickshell.screens.length > 0 ? (Quickshell.screens[0].width / Math.max(1, Quickshell.screens[0].height)) : 16 / 9
|
||||
if (wheel.angleDelta.y > 0)
|
||||
cropRect.zoom *= 1.1;
|
||||
else
|
||||
cropRect.zoom /= 1.1;
|
||||
|
||||
border.color: DynamicColors.palette.m3primary
|
||||
border.width: 2
|
||||
color: Qt.alpha(DynamicColors.palette.m3primaryContainer, 0.3)
|
||||
height: cropHeight
|
||||
width: cropWidth
|
||||
x: paintedX + (paintedWidth - width) * Config.background.alignX
|
||||
y: paintedY + (paintedHeight - height) * Config.background.alignY
|
||||
cropRect.zoom = Math.max(1.0, Math.min(cropRect.zoom, 10.0));
|
||||
Config.background.zoom = cropRect.zoom;
|
||||
|
||||
DragHandler {
|
||||
target: null
|
||||
cropRect.x = oldCenterX - cropRect.width * 0.5;
|
||||
cropRect.y = oldCenterY - cropRect.height * 0.5;
|
||||
|
||||
onActiveTranslationChanged: {
|
||||
if (active) {
|
||||
let newX = cropRect.x - cropRect.paintedX + translation.x;
|
||||
let newY = cropRect.y - cropRect.paintedY + translation.y;
|
||||
|
||||
let rangeX = cropRect.paintedWidth - cropRect.width;
|
||||
let rangeY = cropRect.paintedHeight - cropRect.height;
|
||||
|
||||
if (rangeX > 0) {
|
||||
let valX = newX / rangeX;
|
||||
Config.background.alignX = Math.max(0.0, Math.min(1.0, valX));
|
||||
Config.save();
|
||||
}
|
||||
|
||||
if (rangeY > 0) {
|
||||
let valY = newY / rangeY;
|
||||
Config.background.alignY = Math.max(0.0, Math.min(1.0, valY));
|
||||
Config.save();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PinchHandler {
|
||||
maximumScale: 5.0
|
||||
minimumScale: 1.0
|
||||
target: null
|
||||
|
||||
onActiveScaleChanged: {
|
||||
if (active) {
|
||||
let newZoom = Config.background.zoom * (1 / (1 + (activeScale - 1) * 0.1));
|
||||
Config.background.zoom = Math.max(1.0, Math.min(newZoom, 5.0));
|
||||
}
|
||||
}
|
||||
}
|
||||
cropRect.clampToBounds();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SettingSpinBox {
|
||||
max: 5.0
|
||||
min: 1.0
|
||||
name: "Zoom"
|
||||
object: Config.background
|
||||
setting: "zoom"
|
||||
step: 0.1
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -6,7 +6,7 @@ import QtQuick.Controls
|
||||
import qs.Components
|
||||
import qs.Config
|
||||
import "../../scripts/fuzzysort.js" as Fuzzy
|
||||
import "./SettingsIndex.mjs" as SettingsIndex
|
||||
import "../../scripts/SettingsIndex.mjs" as SettingsIndex
|
||||
|
||||
Item {
|
||||
id: root
|
||||
@@ -53,11 +53,10 @@ Item {
|
||||
|
||||
Shortcut {
|
||||
sequence: "/"
|
||||
|
||||
onActivated: searchField.forceActiveFocus()
|
||||
}
|
||||
|
||||
Component.onCompleted: console.log(root.height)
|
||||
|
||||
ListModel {
|
||||
id: resultsModel
|
||||
}
|
||||
|
||||
@@ -7,45 +7,24 @@ import qs.Helpers
|
||||
Item {
|
||||
id: root
|
||||
|
||||
property real offsetScale: shouldBeActive ? 0 : 1
|
||||
required property var panels
|
||||
required property ShellScreen screen
|
||||
readonly property bool shouldBeActive: visibilities.settings
|
||||
required property PersistentProperties visibilities
|
||||
|
||||
implicitHeight: 0
|
||||
anchors.topMargin: (-implicitHeight - 5) * offsetScale
|
||||
implicitHeight: content.implicitHeight
|
||||
implicitWidth: content.implicitWidth
|
||||
visible: height > 0
|
||||
opacity: 1 - offsetScale
|
||||
visible: offsetScale < 1
|
||||
|
||||
states: State {
|
||||
name: "visible"
|
||||
when: root.visibilities.settings
|
||||
|
||||
PropertyChanges {
|
||||
root.implicitHeight: content.implicitHeight
|
||||
Behavior on offsetScale {
|
||||
Anim {
|
||||
duration: Appearance.anim.durations.expressiveDefaultSpatial
|
||||
easing.bezierCurve: Appearance.anim.curves.expressiveDefaultSpatial
|
||||
}
|
||||
}
|
||||
transitions: [
|
||||
Transition {
|
||||
from: ""
|
||||
to: "visible"
|
||||
|
||||
Anim {
|
||||
duration: MaterialEasing.expressiveEffectsTime
|
||||
easing.bezierCurve: MaterialEasing.expressiveEffects
|
||||
property: "implicitHeight"
|
||||
target: root
|
||||
}
|
||||
},
|
||||
Transition {
|
||||
from: "visible"
|
||||
to: ""
|
||||
|
||||
Anim {
|
||||
easing.bezierCurve: MaterialEasing.expressiveEffects
|
||||
property: "implicitHeight"
|
||||
target: root
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
CustomClippingRect {
|
||||
anchors.fill: parent
|
||||
|
||||
@@ -14,8 +14,8 @@ Scope {
|
||||
description: "Toggle launcher"
|
||||
name: "toggle-launcher"
|
||||
|
||||
onPressed: root.launcherInterrupted = false
|
||||
onReleased: {
|
||||
onPressed: {
|
||||
root.launcherInterrupted = false;
|
||||
if (!root.launcherInterrupted && !root.hasFullscreen) {
|
||||
const visibilities = Visibilities.getForActive();
|
||||
visibilities.launcher = !visibilities.launcher;
|
||||
|
||||
@@ -0,0 +1,158 @@
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import Quickshell
|
||||
import Quickshell.Widgets
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Effects
|
||||
import qs.Components
|
||||
import qs.Modules
|
||||
import qs.Config
|
||||
|
||||
Column {
|
||||
id: menu
|
||||
|
||||
property int biggestWidth: 0
|
||||
required property QsMenuHandle handle
|
||||
required property int level
|
||||
required property PopoutState popouts
|
||||
required property ShellScreen screen
|
||||
property bool shown: true
|
||||
|
||||
height: childrenRect.height
|
||||
opacity: shown ? 1 : 0
|
||||
padding: 0
|
||||
scale: shown ? 1 : 0.8
|
||||
spacing: 4
|
||||
width: biggestWidth
|
||||
|
||||
Behavior on opacity {
|
||||
Anim {
|
||||
}
|
||||
}
|
||||
Behavior on scale {
|
||||
Anim {
|
||||
}
|
||||
}
|
||||
|
||||
QsMenuOpener {
|
||||
id: menuOpener
|
||||
|
||||
menu: menu.handle
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: menuOpener.children
|
||||
|
||||
CustomRect {
|
||||
id: item
|
||||
|
||||
required property int index
|
||||
required property QsMenuEntry modelData
|
||||
|
||||
color: modelData.isSeparator ? DynamicColors.palette.m3outlineVariant : "transparent"
|
||||
implicitHeight: modelData.isSeparator ? 1 : children.implicitHeight
|
||||
implicitWidth: menu.biggestWidth
|
||||
radius: Appearance.rounding.full
|
||||
visible: index !== (menuOpener.children.values.length - 1) ? true : (modelData.isSeparator ? false : true)
|
||||
|
||||
Loader {
|
||||
id: children
|
||||
|
||||
active: !item.modelData.isSeparator
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
asynchronous: true
|
||||
|
||||
sourceComponent: Item {
|
||||
implicitHeight: 30
|
||||
|
||||
StateLayer {
|
||||
function onClicked(): void {
|
||||
const entry = item.modelData;
|
||||
if (entry.hasChildren) {
|
||||
menu.popouts.pushSubmenu(menu.level, entry, item, menu.biggestWidth);
|
||||
} else {
|
||||
entry.triggered();
|
||||
menu.popouts.hasCurrent = false;
|
||||
}
|
||||
}
|
||||
|
||||
disabled: !item.modelData.enabled
|
||||
radius: item.radius
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: icon
|
||||
|
||||
active: item.modelData.icon !== ""
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 10
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
asynchronous: true
|
||||
|
||||
sourceComponent: Item {
|
||||
implicitHeight: label.implicitHeight
|
||||
implicitWidth: label.implicitHeight
|
||||
|
||||
IconImage {
|
||||
id: iconImage
|
||||
|
||||
implicitSize: parent.implicitHeight
|
||||
source: item.modelData.icon
|
||||
visible: false
|
||||
}
|
||||
|
||||
MultiEffect {
|
||||
anchors.fill: iconImage
|
||||
colorization: 1.0
|
||||
colorizationColor: item.modelData.enabled ? DynamicColors.palette.m3onSurface : DynamicColors.palette.m3outline
|
||||
source: iconImage
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CustomText {
|
||||
id: label
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 10
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
color: item.modelData.enabled ? DynamicColors.palette.m3onSurface : DynamicColors.palette.m3outline
|
||||
text: labelMetrics.elidedText
|
||||
}
|
||||
|
||||
TextMetrics {
|
||||
id: labelMetrics
|
||||
|
||||
font.family: label.font.family
|
||||
font.pointSize: label.font.pointSize
|
||||
text: item.modelData.text
|
||||
|
||||
Component.onCompleted: {
|
||||
var biggestWidth = menu.biggestWidth;
|
||||
var currentWidth = labelMetrics.width + (item.modelData.icon ?? "" ? 30 : 0) + (item.modelData.hasChildren ? 30 : 0) + 20;
|
||||
if (currentWidth > biggestWidth) {
|
||||
menu.biggestWidth = currentWidth;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: expand
|
||||
|
||||
active: item.modelData.hasChildren
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
asynchronous: true
|
||||
|
||||
sourceComponent: MaterialIcon {
|
||||
color: item.modelData.enabled ? DynamicColors.palette.m3onSurface : DynamicColors.palette.m3outline
|
||||
text: "chevron_right"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,249 +1,16 @@
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import Quickshell
|
||||
import Quickshell.Widgets
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Effects
|
||||
import qs.Components
|
||||
import qs.Modules
|
||||
import qs.Config
|
||||
|
||||
StackView {
|
||||
SubMenu {
|
||||
id: root
|
||||
|
||||
property int biggestWidth: 0
|
||||
required property PopoutState popouts
|
||||
property int rootWidth: 0
|
||||
handle: trayItem
|
||||
level: 0
|
||||
|
||||
required property QsMenuHandle trayItem
|
||||
|
||||
implicitHeight: currentItem.implicitHeight
|
||||
implicitWidth: currentItem.implicitWidth
|
||||
|
||||
initialItem: SubMenu {
|
||||
handle: root.trayItem
|
||||
}
|
||||
popEnter: NoAnim {
|
||||
}
|
||||
popExit: NoAnim {
|
||||
}
|
||||
pushEnter: NoAnim {
|
||||
}
|
||||
pushExit: NoAnim {
|
||||
}
|
||||
|
||||
Component {
|
||||
id: subMenuComp
|
||||
|
||||
SubMenu {
|
||||
}
|
||||
}
|
||||
|
||||
component NoAnim: Transition {
|
||||
NumberAnimation {
|
||||
duration: 0
|
||||
}
|
||||
}
|
||||
component SubMenu: Column {
|
||||
id: menu
|
||||
|
||||
required property QsMenuHandle handle
|
||||
property bool isSubMenu
|
||||
property bool shown
|
||||
|
||||
opacity: shown ? 1 : 0
|
||||
padding: 0
|
||||
scale: shown ? 1 : 0.8
|
||||
spacing: 4
|
||||
|
||||
Behavior on opacity {
|
||||
Anim {
|
||||
}
|
||||
}
|
||||
Behavior on scale {
|
||||
Anim {
|
||||
}
|
||||
}
|
||||
|
||||
Component.onCompleted: shown = true
|
||||
StackView.onActivating: shown = true
|
||||
StackView.onDeactivating: shown = false
|
||||
StackView.onRemoved: destroy()
|
||||
|
||||
QsMenuOpener {
|
||||
id: menuOpener
|
||||
|
||||
menu: menu.handle
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: menuOpener.children
|
||||
|
||||
CustomRect {
|
||||
id: item
|
||||
|
||||
required property QsMenuEntry modelData
|
||||
|
||||
color: modelData.isSeparator ? DynamicColors.palette.m3outlineVariant : "transparent"
|
||||
implicitHeight: modelData.isSeparator ? 1 : children.implicitHeight
|
||||
implicitWidth: root.biggestWidth
|
||||
radius: Appearance.rounding.smallest / 2
|
||||
|
||||
Loader {
|
||||
id: children
|
||||
|
||||
active: !item.modelData.isSeparator
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
asynchronous: true
|
||||
|
||||
sourceComponent: Item {
|
||||
implicitHeight: 30
|
||||
|
||||
StateLayer {
|
||||
function onClicked(): void {
|
||||
const entry = item.modelData;
|
||||
if (entry.hasChildren) {
|
||||
root.rootWidth = root.biggestWidth;
|
||||
root.biggestWidth = 0;
|
||||
root.push(subMenuComp.createObject(null, {
|
||||
handle: entry,
|
||||
isSubMenu: true
|
||||
}));
|
||||
} else {
|
||||
item.modelData.triggered();
|
||||
root.popouts.hasCurrent = false;
|
||||
}
|
||||
}
|
||||
|
||||
disabled: !item.modelData.enabled
|
||||
radius: item.radius
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: icon
|
||||
|
||||
active: item.modelData.icon !== ""
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 10
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
asynchronous: true
|
||||
|
||||
sourceComponent: Item {
|
||||
implicitHeight: label.implicitHeight
|
||||
implicitWidth: label.implicitHeight
|
||||
|
||||
IconImage {
|
||||
id: iconImage
|
||||
|
||||
implicitSize: parent.implicitHeight
|
||||
source: item.modelData.icon
|
||||
visible: false
|
||||
}
|
||||
|
||||
MultiEffect {
|
||||
anchors.fill: iconImage
|
||||
colorization: 1.0
|
||||
colorizationColor: item.modelData.enabled ? DynamicColors.palette.m3onSurface : DynamicColors.palette.m3outline
|
||||
source: iconImage
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CustomText {
|
||||
id: label
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 10
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
color: item.modelData.enabled ? DynamicColors.palette.m3onSurface : DynamicColors.palette.m3outline
|
||||
text: labelMetrics.elidedText
|
||||
}
|
||||
|
||||
TextMetrics {
|
||||
id: labelMetrics
|
||||
|
||||
font.family: label.font.family
|
||||
font.pointSize: label.font.pointSize
|
||||
text: item.modelData.text
|
||||
|
||||
Component.onCompleted: {
|
||||
var biggestWidth = root.biggestWidth;
|
||||
var currentWidth = labelMetrics.width + (item.modelData.icon ?? "" ? 30 : 0) + (item.modelData.hasChildren ? 30 : 0) + 20;
|
||||
if (currentWidth > biggestWidth) {
|
||||
root.biggestWidth = currentWidth;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: expand
|
||||
|
||||
active: item.modelData.hasChildren
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
asynchronous: true
|
||||
|
||||
sourceComponent: MaterialIcon {
|
||||
color: item.modelData.enabled ? DynamicColors.palette.m3onSurface : DynamicColors.palette.m3outline
|
||||
text: "chevron_right"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: loader
|
||||
|
||||
active: menu.isSubMenu
|
||||
asynchronous: true
|
||||
|
||||
sourceComponent: Item {
|
||||
implicitHeight: back.implicitHeight + 2 / 2
|
||||
implicitWidth: back.implicitWidth
|
||||
|
||||
Item {
|
||||
anchors.bottom: parent.bottom
|
||||
implicitHeight: back.implicitHeight
|
||||
implicitWidth: back.implicitWidth + 10
|
||||
|
||||
CustomRect {
|
||||
anchors.fill: parent
|
||||
color: DynamicColors.palette.m3secondaryContainer
|
||||
radius: Appearance.rounding.smallest / 2
|
||||
|
||||
StateLayer {
|
||||
function onClicked(): void {
|
||||
root.pop();
|
||||
root.biggestWidth = root.rootWidth;
|
||||
}
|
||||
|
||||
color: DynamicColors.palette.m3onSecondaryContainer
|
||||
radius: parent.radius
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
id: back
|
||||
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
MaterialIcon {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
color: DynamicColors.palette.m3onSecondaryContainer
|
||||
text: "chevron_left"
|
||||
}
|
||||
|
||||
CustomText {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
color: DynamicColors.palette.m3onSecondaryContainer
|
||||
text: qsTr("Back")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -31,7 +31,7 @@ Item {
|
||||
implicitHeight: Math.max(saver.implicitHeight, balance.implicitHeight, perf.implicitHeight) + 5 * 2 + saverLabel.contentHeight
|
||||
implicitWidth: saver.implicitHeight + balance.implicitHeight + perf.implicitHeight + 8 * 2 + saverLabel.contentWidth
|
||||
// color: "transparent"
|
||||
radius: 6
|
||||
radius: (20 - Appearance.padding.small) * Appearance.rounding.scale
|
||||
|
||||
CustomRect {
|
||||
id: indicator
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
pragma Singleton
|
||||
|
||||
import Quickshell
|
||||
import QtQuick
|
||||
|
||||
Singleton {
|
||||
id: root
|
||||
|
||||
readonly property date date: clock.date
|
||||
readonly property string dateStr: format("ddd d MMM - hh:mm:ss")
|
||||
property alias enabled: clock.enabled
|
||||
readonly property string hourStr: timeComponents[0] ?? ""
|
||||
readonly property int hours: clock.hours
|
||||
readonly property string minuteStr: timeComponents[1] ?? ""
|
||||
readonly property int minutes: clock.minutes
|
||||
readonly property string secondStr: timeComponents[2] ?? ""
|
||||
readonly property int seconds: clock.seconds
|
||||
readonly property list<string> timeComponents: timeStr.split(":")
|
||||
readonly property string timeStr: format("hh:mm:ss")
|
||||
|
||||
function format(fmt: string): string {
|
||||
return Qt.formatDateTime(clock.date, fmt);
|
||||
}
|
||||
|
||||
SystemClock {
|
||||
id: clock
|
||||
|
||||
precision: SystemClock.Seconds
|
||||
}
|
||||
}
|
||||
@@ -54,6 +54,8 @@ CustomClippingRect {
|
||||
anchors.centerIn: parent
|
||||
contentHeight: childrenRect.height
|
||||
contentWidth: 600
|
||||
displayMarginBeginning: root.itemHeight
|
||||
displayMarginEnd: root.itemHeight
|
||||
implicitHeight: Math.min(contentHeight, (root.itemHeight + spacing) * 5 - spacing)
|
||||
implicitWidth: contentWidth
|
||||
spacing: Appearance.spacing.normal
|
||||
@@ -65,10 +67,11 @@ CustomClippingRect {
|
||||
required property var modelData
|
||||
readonly property list<string> sections: modelData.update.split(" ")
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
// anchors.left: parent.left
|
||||
// anchors.right: parent.right
|
||||
color: DynamicColors.tPalette.m3surfaceContainer
|
||||
implicitHeight: root.itemHeight
|
||||
implicitWidth: 600
|
||||
radius: Appearance.rounding.small - Appearance.padding.small
|
||||
|
||||
RowLayout {
|
||||
|
||||
@@ -14,7 +14,7 @@ CustomRect {
|
||||
color: DynamicColors.tPalette.m3surfaceContainer
|
||||
implicitHeight: Config.barConfig.height + Appearance.padding.smallest * 2
|
||||
implicitWidth: contentRow.implicitWidth + Appearance.spacing.small * 2
|
||||
radius: height / 2
|
||||
radius: Appearance.rounding.full
|
||||
|
||||
RowLayout {
|
||||
id: contentRow
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
import qs.Components
|
||||
import qs.Helpers
|
||||
import qs.Config
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
property Image current: one
|
||||
property string source: Wallpapers.current
|
||||
|
||||
anchors.fill: parent
|
||||
|
||||
Component.onCompleted: {
|
||||
if (source)
|
||||
Qt.callLater(() => one.update());
|
||||
}
|
||||
onSourceChanged: {
|
||||
if (!source) {
|
||||
current = null;
|
||||
} else if (current === one) {
|
||||
two.update();
|
||||
} else {
|
||||
one.update();
|
||||
}
|
||||
}
|
||||
|
||||
Img {
|
||||
id: one
|
||||
}
|
||||
|
||||
Img {
|
||||
id: two
|
||||
}
|
||||
|
||||
component Img: Image {
|
||||
id: img
|
||||
|
||||
function update(): void {
|
||||
if (source === root.source) {
|
||||
root.current = this;
|
||||
} else {
|
||||
source = root.source;
|
||||
}
|
||||
}
|
||||
|
||||
anchors.fill: parent
|
||||
asynchronous: true
|
||||
fillMode: Image.PreserveAspectCrop
|
||||
opacity: 0
|
||||
retainWhileLoading: true
|
||||
scale: Wallpapers.showPreview ? 1 : 0.8
|
||||
sourceClipRect: Qt.rect(Config.background.sourceClipX, Config.background.sourceClipY, Config.background.sourceClipW, Config.background.sourceClipH)
|
||||
|
||||
states: State {
|
||||
name: "visible"
|
||||
when: root.current === img
|
||||
|
||||
PropertyChanges {
|
||||
img.opacity: 1
|
||||
img.scale: 1
|
||||
}
|
||||
}
|
||||
transitions: Transition {
|
||||
Anim {
|
||||
duration: Config.background.wallFadeDuration
|
||||
properties: "opacity,scale"
|
||||
target: img
|
||||
}
|
||||
}
|
||||
|
||||
onStatusChanged: {
|
||||
if (status === Image.Ready) {
|
||||
root.current = this;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import Quickshell
|
||||
import QtQuick
|
||||
import qs.Components
|
||||
import qs.Helpers
|
||||
@@ -8,79 +9,34 @@ import qs.Config
|
||||
Item {
|
||||
id: root
|
||||
|
||||
property Image current: one
|
||||
required property ShellScreen screen
|
||||
property string source: Wallpapers.current
|
||||
|
||||
anchors.fill: parent
|
||||
|
||||
Component.onCompleted: {
|
||||
if (source)
|
||||
Qt.callLater(() => one.update());
|
||||
}
|
||||
onSourceChanged: {
|
||||
if (!source) {
|
||||
current = null;
|
||||
} else if (current === one) {
|
||||
two.update();
|
||||
} else {
|
||||
one.update();
|
||||
}
|
||||
}
|
||||
|
||||
Img {
|
||||
id: one
|
||||
}
|
||||
|
||||
Img {
|
||||
id: two
|
||||
}
|
||||
|
||||
component Img: CachingImage {
|
||||
Image {
|
||||
id: img
|
||||
|
||||
property real imageRatio: Math.max(1, sourceSize.width) / Math.max(1, sourceSize.height)
|
||||
property bool isValid: sourceSize.width > 0 && sourceSize.height > 0 && root.width > 0 && root.height > 0
|
||||
property real windowRatio: root.width / Math.max(1, root.height)
|
||||
|
||||
function update(): void {
|
||||
if (path === root.source) {
|
||||
root.current = this;
|
||||
} else {
|
||||
path = root.source;
|
||||
}
|
||||
}
|
||||
|
||||
anchors.fill: undefined
|
||||
anchors.fill: parent
|
||||
asynchronous: true
|
||||
fillMode: Image.PreserveAspectCrop
|
||||
height: isValid ? (imageRatio > windowRatio ? root.height : root.width / imageRatio) * Config.background.zoom : root.height
|
||||
opacity: 0
|
||||
scale: Wallpapers.showPreview ? 1 : 0.8
|
||||
width: isValid ? (imageRatio > windowRatio ? root.height * imageRatio : root.width) * Config.background.zoom : root.width
|
||||
x: isValid ? (root.width - width) * Config.background.alignX : 0
|
||||
y: isValid ? (root.height - height) * Config.background.alignY : 0
|
||||
opacity: 1
|
||||
retainWhileLoading: true
|
||||
source: root.source
|
||||
sourceClipRect: Wallpapers.recentlyChanged ? null : Qt.rect(Config.background.sourceClipX, Config.background.sourceClipY, Config.background.sourceClipW, Config.background.sourceClipH)
|
||||
sourceSize.height: root.screen.height
|
||||
sourceSize.width: root.screen.width
|
||||
|
||||
states: State {
|
||||
name: "visible"
|
||||
when: root.current === img
|
||||
|
||||
PropertyChanges {
|
||||
img.opacity: 1
|
||||
img.scale: 1
|
||||
}
|
||||
}
|
||||
transitions: Transition {
|
||||
Anim {
|
||||
duration: Config.background.wallFadeDuration
|
||||
properties: "opacity,scale"
|
||||
target: img
|
||||
}
|
||||
}
|
||||
|
||||
onStatusChanged: {
|
||||
if (status === Image.Ready) {
|
||||
root.current = this;
|
||||
onSourceChanged: {
|
||||
if (Wallpapers.recentlyChanged) {
|
||||
Config.background.sourceClipH = 0;
|
||||
Config.background.sourceClipW = 0;
|
||||
Config.background.sourceClipY = 0;
|
||||
Config.background.sourceClipX = 0;
|
||||
Config.background.zoom = 1.0;
|
||||
Config.save();
|
||||
}
|
||||
Wallpapers.recentlyChanged = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@ Loader {
|
||||
}
|
||||
|
||||
WallBackground {
|
||||
screen: root.screen
|
||||
}
|
||||
|
||||
Loader {
|
||||
|
||||
@@ -79,7 +79,8 @@ Item {
|
||||
}
|
||||
|
||||
onPressed: {
|
||||
Hyprland.dispatch(`workspace ${button.modelData.name}`);
|
||||
const ws = button.modelData.name;
|
||||
Hyprland.dispatch(Hyprland.usingLua ? `hl.dsp.focus({ workspace= "${ws}"})` : `workspace ${ws}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+3
-1
@@ -15,13 +15,14 @@ Item {
|
||||
property real currentCenter
|
||||
property alias currentName: popoutState.currentName
|
||||
property string detachedMode
|
||||
readonly property bool isDetached: detachedMode.length > 0
|
||||
property alias hasCurrent: popoutState.hasCurrent
|
||||
readonly property bool isDetached: detachedMode.length > 0
|
||||
readonly property real nonAnimHeight: children.find(c => c.shouldBeActive)?.implicitHeight ?? content.implicitHeight
|
||||
readonly property real nonAnimWidth: children.find(c => c.shouldBeActive)?.implicitWidth ?? content.implicitWidth
|
||||
required property real offsetScale
|
||||
property string queuedMode
|
||||
required property ShellScreen screen
|
||||
property alias state: popoutState
|
||||
|
||||
function close(): void {
|
||||
hasCurrent = false;
|
||||
@@ -79,6 +80,7 @@ Item {
|
||||
|
||||
sourceComponent: Content {
|
||||
popouts: popoutState
|
||||
screen: root.screen
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ qml_module(ZShell-blobs
|
||||
|
||||
qt_add_shaders(ZShell-blobs "blob_shaders"
|
||||
BATCHABLE OPTIMIZED NOHLSL NOMSL
|
||||
GLSL "300es,330"
|
||||
PREFIX "/"
|
||||
FILES
|
||||
shaders/blob.frag
|
||||
|
||||
@@ -2,6 +2,9 @@
|
||||
|
||||
#include <cstring>
|
||||
|
||||
static_assert(sizeof(decltype(BlobRectData::excludeMask)) == sizeof(float),
|
||||
"BlobMaterial packs excludeMask into a float slot via memcpy");
|
||||
|
||||
QSGMaterialType* BlobMaterial::type() const {
|
||||
static QSGMaterialType s_type;
|
||||
return &s_type;
|
||||
@@ -82,8 +85,11 @@ bool BlobMaterialShader::updateUniformData(RenderState& state, QSGMaterial* newM
|
||||
for (int i = 0; i < count; ++i) {
|
||||
const auto& r = mat->m_rects[i];
|
||||
const int base = 160 + i * 80;
|
||||
// Pack excludeMask into props.x via bit-cast (read in shader with floatBitsToInt)
|
||||
float maskAsFloat;
|
||||
memcpy(&maskAsFloat, &r.excludeMask, sizeof(float));
|
||||
const float d0[4] = { r.cx, r.cy, r.hw, r.hh };
|
||||
const float d1[4] = { 0.0f, r.offsetX, r.offsetY, r.minEig };
|
||||
const float d1[4] = { maskAsFloat, r.offsetX, r.offsetY, r.minEig };
|
||||
const float d3[4] = { r.screenHalfX, r.screenHalfY, 0.0f, 0.0f };
|
||||
memcpy(buf->data() + base, d0, 16);
|
||||
memcpy(buf->data() + base + 16, d1, 16);
|
||||
|
||||
@@ -14,6 +14,9 @@ struct BlobRectData {
|
||||
float screenHalfX = 0, screenHalfY = 0;
|
||||
// Effective per-corner radii (tr, br, bl, tl), pre-computed on CPU
|
||||
float radius[4] = { 0, 0, 0, 0 };
|
||||
// Bitmask of indices in this rect's m_cachedRects that mutually exclude (or are excluded by) this rect.
|
||||
// Used by the shader to skip smin between excluded pairs.
|
||||
int excludeMask = 0;
|
||||
};
|
||||
|
||||
class BlobMaterial : public QSGMaterial {
|
||||
|
||||
@@ -72,11 +72,17 @@ void BlobShape::geometryChange(const QRectF& newGeometry, const QRectF& oldGeome
|
||||
// Accumulate sub-pixel drift so slow movements don't desync the shader
|
||||
m_pendingDx += static_cast<float>(newGeometry.x() - oldGeometry.x());
|
||||
m_pendingDy += static_cast<float>(newGeometry.y() - oldGeometry.y());
|
||||
const auto dw = std::abs(newGeometry.width() - oldGeometry.width());
|
||||
const auto dh = std::abs(newGeometry.height() - oldGeometry.height());
|
||||
if (std::abs(m_pendingDx) > 0.5f || std::abs(m_pendingDy) > 0.5f || dw > 0.5 || dh > 0.5) {
|
||||
// Accumulate size delta across multiple frames so incremental size
|
||||
// changes that are each below the threshold still trigger a dirty
|
||||
// mark once their accumulated delta exceeds it.
|
||||
m_pendingDw += static_cast<float>(newGeometry.width() - oldGeometry.width());
|
||||
m_pendingDh += static_cast<float>(newGeometry.height() - oldGeometry.height());
|
||||
if (std::abs(m_pendingDx) > 0.5f || std::abs(m_pendingDy) > 0.5f ||
|
||||
std::abs(m_pendingDw) > 0.5f || std::abs(m_pendingDh) > 0.5f) {
|
||||
m_pendingDx = 0;
|
||||
m_pendingDy = 0;
|
||||
m_pendingDw = 0;
|
||||
m_pendingDh = 0;
|
||||
m_group->markShapeDirty(this);
|
||||
}
|
||||
}
|
||||
@@ -149,6 +155,10 @@ void BlobShape::updatePolish() {
|
||||
const QRectF myPadded(static_cast<double>(m_cachedPaddedX), static_cast<double>(m_cachedPaddedY),
|
||||
static_cast<double>(m_cachedPaddedW), static_cast<double>(m_cachedPaddedH));
|
||||
|
||||
// Track shape pointers parallel to m_cachedRects for pairwise exclusion lookups
|
||||
QVector<BlobShape*> rectShapes;
|
||||
rectShapes.reserve(m_group->shapes().size());
|
||||
|
||||
for (BlobShape* other : m_group->shapes()) {
|
||||
if (other->isInvertedRect())
|
||||
continue;
|
||||
@@ -210,12 +220,29 @@ void BlobShape::updatePolish() {
|
||||
r.screenHalfY = std::abs(b) * r.hw + std::abs(d) * r.hh;
|
||||
|
||||
m_cachedRects.append(r);
|
||||
rectShapes.append(other);
|
||||
}
|
||||
}
|
||||
|
||||
if (isInvertedRect())
|
||||
m_cachedMyIndex = -1;
|
||||
|
||||
// Compute pairwise exclude masks. Bit j in entry i is set iff rect i excludes rect j
|
||||
// or rect j excludes rect i. The shader uses this to avoid smin between excluded pairs.
|
||||
const auto cachedCount = m_cachedRects.size();
|
||||
for (qsizetype i = 0; i < cachedCount; ++i) {
|
||||
int mask = 0;
|
||||
BlobShape* si = rectShapes[i];
|
||||
for (qsizetype j = 0; j < cachedCount; ++j) {
|
||||
if (j == i)
|
||||
continue;
|
||||
BlobShape* sj = rectShapes[j];
|
||||
if (si->isExcluded(sj) || sj->isExcluded(si))
|
||||
mask |= (1 << j);
|
||||
}
|
||||
m_cachedRects[i].excludeMask = mask;
|
||||
}
|
||||
|
||||
// Cache inverted rect data
|
||||
m_cachedHasInverted = false;
|
||||
m_cachedInvertedRadius = 0;
|
||||
@@ -270,6 +297,7 @@ void BlobShape::updatePolish() {
|
||||
const auto rectCount = m_cachedRects.size();
|
||||
for (qsizetype i = 0; i < rectCount; ++i) {
|
||||
auto& ri = m_cachedRects[i];
|
||||
const int riExcludeMask = ri.excludeMask;
|
||||
float fTr = 1.0f, fBr = 1.0f, fBl = 1.0f, fTl = 1.0f;
|
||||
|
||||
const float cTrX = ri.cx + ri.hw, cTrY = ri.cy - ri.hh;
|
||||
@@ -280,6 +308,8 @@ void BlobShape::updatePolish() {
|
||||
for (qsizetype j = 0; j < rectCount; ++j) {
|
||||
if (j == i)
|
||||
continue;
|
||||
if (riExcludeMask & (1 << j))
|
||||
continue;
|
||||
const auto& rj = m_cachedRects[j];
|
||||
fTr = std::min(fTr, cpuSmoothstep(0.0f, smoothFactor, cpuSdBox(cTrX, cTrY, rj.cx, rj.cy, rj.hw, rj.hh)));
|
||||
fBr = std::min(fBr, cpuSmoothstep(0.0f, smoothFactor, cpuSdBox(cBrX, cBrY, rj.cx, rj.cy, rj.hw, rj.hh)));
|
||||
|
||||
@@ -84,8 +84,10 @@ QRectF m_localPaddedRect;
|
||||
QVector<BlobRectData> m_cachedRects;
|
||||
int m_cachedMyIndex = -2;
|
||||
float m_pendingDx = 0;
|
||||
float m_pendingDy = 0;
|
||||
bool m_cachedHasInverted = false;
|
||||
float m_pendingDy = 0;
|
||||
float m_pendingDw = 0;
|
||||
float m_pendingDh = 0;
|
||||
bool m_cachedHasInverted = false;
|
||||
float m_cachedInvertedRadius = 0;
|
||||
float m_cachedInvertedOuter[4] = {};
|
||||
float m_cachedInvertedInner[4] = {};
|
||||
|
||||
@@ -63,13 +63,17 @@ float smaxSharpA(float a, float b, float k) {
|
||||
void main() {
|
||||
vec2 pixel = vec2(paddedX, paddedY) + qt_TexCoord0 * vec2(paddedW, paddedH);
|
||||
|
||||
float mergedSdf = 1e10;
|
||||
// Phase 1: compute per-rect SDF, track owner. We can't smin yet because
|
||||
// excluded pairs need to skip the smooth blend, which requires pairwise pass
|
||||
// below.
|
||||
float dArr[16];
|
||||
int owner = -2;
|
||||
float minDist = 1e10;
|
||||
|
||||
for (int i = 0; i < rectCount; i++) {
|
||||
vec4 rect = rectData[i * 5]; // cx, cy, hw, hh
|
||||
vec4 props = rectData[i * 5 + 1]; // radius, offsetX, offsetY, minEig
|
||||
vec4 rect = rectData[i * 5]; // cx, cy, hw, hh
|
||||
vec4 props =
|
||||
rectData[i * 5 + 1]; // excludeMask(int bits), offsetX, offsetY, minEig
|
||||
vec4 invDm = rectData[i * 5 + 2]; // inverse deform matrix
|
||||
vec4 sh = rectData[i * 5 + 3]; // screenHalfX, screenHalfY, 0, 0
|
||||
vec4 radii =
|
||||
@@ -81,8 +85,10 @@ void main() {
|
||||
// AABB early-out: skip rects far from this pixel
|
||||
vec2 extent = sh.xy + vec2(smoothFactor * 1.5);
|
||||
if (abs(pixel.x - center.x) > extent.x ||
|
||||
abs(pixel.y - center.y) > extent.y)
|
||||
abs(pixel.y - center.y) > extent.y) {
|
||||
dArr[i] = 1e10;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Apply pre-computed inverse deformation to the evaluation point
|
||||
mat2 invDeform = mat2(invDm.xy, invDm.zw);
|
||||
@@ -138,13 +144,38 @@ void main() {
|
||||
d *= scale;
|
||||
}
|
||||
|
||||
mergedSdf = smin(mergedSdf, d, smoothFactor);
|
||||
dArr[i] = d;
|
||||
if (d < smoothFactor && d < minDist) {
|
||||
minDist = d;
|
||||
owner = i;
|
||||
}
|
||||
}
|
||||
|
||||
// Phase 2: hard-min baseline over all rects.
|
||||
float mergedSdf = 1e10;
|
||||
for (int i = 0; i < rectCount; i++) {
|
||||
mergedSdf = min(mergedSdf, dArr[i]);
|
||||
}
|
||||
|
||||
// Phase 3: pair-wise smin contributions, skipping excluded pairs. Pair smin
|
||||
// <= min, so taking the min over all non-excluded pair smins gives the
|
||||
// smoothly-merged SDF.
|
||||
for (int i = 0; i < rectCount; i++) {
|
||||
if (dArr[i] >= 1e9)
|
||||
continue;
|
||||
int excludeMask = floatBitsToInt(rectData[i * 5 + 1].x);
|
||||
for (int j = i + 1; j < rectCount; j++) {
|
||||
if (dArr[j] >= 1e9)
|
||||
continue;
|
||||
if ((excludeMask & (1 << j)) != 0)
|
||||
continue;
|
||||
// smin only deviates from min within smoothFactor
|
||||
if (abs(dArr[i] - dArr[j]) >= smoothFactor)
|
||||
continue;
|
||||
mergedSdf = min(mergedSdf, smin(dArr[i], dArr[j], smoothFactor));
|
||||
}
|
||||
}
|
||||
|
||||
if (hasInverted != 0) {
|
||||
float dOuter = sdBox(pixel, invertedOuter.xy, invertedOuter.zw) - 1.0;
|
||||
float dInner =
|
||||
|
||||
@@ -1,217 +1,384 @@
|
||||
#include "hyprextras.hpp"
|
||||
#include "hyprdevices.hpp"
|
||||
|
||||
#include <qdir.h>
|
||||
#include <qjsonarray.h>
|
||||
#include <qlocalsocket.h>
|
||||
#include <qloggingcategory.h>
|
||||
#include <qmetatype.h>
|
||||
#include <qregularexpression.h>
|
||||
#include <qvariant.h>
|
||||
|
||||
Q_LOGGING_CATEGORY(lcHypr, "ZShell.internal.hypr", QtInfoMsg)
|
||||
|
||||
namespace ZShell::internal::hypr {
|
||||
|
||||
namespace {
|
||||
|
||||
static QString luaEscapeString(const QString& s) {
|
||||
QString out;
|
||||
out.reserve(s.size() + 2);
|
||||
out += QLatin1Char('"');
|
||||
|
||||
for (const QChar c : s) {
|
||||
switch (c.unicode()) {
|
||||
case '\\':
|
||||
out += QLatin1String(R"(\\)");
|
||||
break;
|
||||
case '"':
|
||||
out += QLatin1String(R"(\")");
|
||||
break;
|
||||
case '\n':
|
||||
out += QLatin1String(R"(\n)");
|
||||
break;
|
||||
case '\r':
|
||||
out += QLatin1String(R"(\r)");
|
||||
break;
|
||||
case '\t':
|
||||
out += QLatin1String(R"(\t)");
|
||||
break;
|
||||
default:
|
||||
out += c;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
out += QLatin1Char('"');
|
||||
return out;
|
||||
}
|
||||
|
||||
static QString luaValue(const QVariant& v);
|
||||
|
||||
static QString luaArray(const QVariantList& list) {
|
||||
QStringList parts;
|
||||
parts.reserve(list.size());
|
||||
|
||||
for (const auto& item : list) {
|
||||
parts << luaValue(item);
|
||||
}
|
||||
|
||||
return QLatin1String("{ ") + parts.join(QLatin1String(", ")) + QLatin1String(" }");
|
||||
}
|
||||
|
||||
static QString luaArray(const QStringList& list) {
|
||||
QStringList parts;
|
||||
parts.reserve(list.size());
|
||||
|
||||
for (const auto& item : list) {
|
||||
parts << luaEscapeString(item);
|
||||
}
|
||||
|
||||
return QLatin1String("{ ") + parts.join(QLatin1String(", ")) + QLatin1String(" }");
|
||||
}
|
||||
|
||||
static QString luaMapFromHash(const QVariantHash& hash) {
|
||||
QStringList parts;
|
||||
parts.reserve(hash.size());
|
||||
|
||||
for (auto it = hash.cbegin(); it != hash.cend(); ++it) {
|
||||
parts << luaEscapeString(it.key()) + QLatin1String(" = ") + luaValue(it.value());
|
||||
}
|
||||
|
||||
return QLatin1String("{ ") + parts.join(QLatin1String(", ")) + QLatin1String(" }");
|
||||
}
|
||||
|
||||
static QString luaMap(const QVariantMap& map) {
|
||||
QStringList parts;
|
||||
parts.reserve(map.size());
|
||||
|
||||
for (auto it = map.cbegin(); it != map.cend(); ++it) {
|
||||
parts << luaEscapeString(it.key()) + QLatin1String(" = ") + luaValue(it.value());
|
||||
}
|
||||
|
||||
return QLatin1String("{ ") + parts.join(QLatin1String(", ")) + QLatin1String(" }");
|
||||
}
|
||||
|
||||
static QString luaValue(const QVariant& v) {
|
||||
if (!v.isValid() || v.isNull()) {
|
||||
return QLatin1String("nil");
|
||||
}
|
||||
|
||||
switch (v.metaType().id()) {
|
||||
case QMetaType::Bool:
|
||||
return v.toBool() ? QLatin1String("true") : QLatin1String("false");
|
||||
|
||||
case QMetaType::Int:
|
||||
case QMetaType::UInt:
|
||||
case QMetaType::LongLong:
|
||||
case QMetaType::ULongLong:
|
||||
case QMetaType::Double:
|
||||
return v.toString();
|
||||
|
||||
case QMetaType::QString:
|
||||
return luaEscapeString(v.toString());
|
||||
|
||||
case QMetaType::QStringList:
|
||||
return luaArray(v.toStringList());
|
||||
|
||||
case QMetaType::QVariantList:
|
||||
return luaArray(v.toList());
|
||||
|
||||
case QMetaType::QVariantMap:
|
||||
return luaMap(v.toMap());
|
||||
|
||||
case QMetaType::QVariantHash:
|
||||
return luaMapFromHash(v.toHash());
|
||||
|
||||
default:
|
||||
return luaEscapeString(v.toString());
|
||||
}
|
||||
}
|
||||
|
||||
static QString normalizeOptionPath(QString key) {
|
||||
key = key.trimmed();
|
||||
key.replace(QLatin1Char(':'), QLatin1Char('.'));
|
||||
return key;
|
||||
}
|
||||
|
||||
static QString buildHlConfigCall(const QString& key, const QVariant& value) {
|
||||
const auto parts = normalizeOptionPath(key).split(QLatin1Char('.'), Qt::SkipEmptyParts);
|
||||
if (parts.isEmpty()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
QString out;
|
||||
out.reserve(32 + key.size() + value.toString().size());
|
||||
out += QLatin1String("hl.config({ ");
|
||||
|
||||
for (int i = 0; i < parts.size(); ++i) {
|
||||
out += parts.at(i);
|
||||
out += QLatin1String(" = ");
|
||||
if (i + 1 < parts.size()) {
|
||||
out += QLatin1String("{ ");
|
||||
}
|
||||
}
|
||||
|
||||
out += luaValue(value);
|
||||
|
||||
for (int i = 0; i + 1 < parts.size(); ++i) {
|
||||
out += QLatin1String(" }");
|
||||
}
|
||||
|
||||
out += QLatin1String(" })");
|
||||
return out;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
HyprExtras::HyprExtras(QObject* parent)
|
||||
: QObject(parent)
|
||||
, m_requestSocket("")
|
||||
, m_eventSocket("")
|
||||
, m_socket(nullptr)
|
||||
, m_socketValid(false)
|
||||
, m_devices(new HyprDevices(this)) {
|
||||
const auto his = qEnvironmentVariable("HYPRLAND_INSTANCE_SIGNATURE");
|
||||
if (his.isEmpty()) {
|
||||
qWarning()
|
||||
<< "HyprExtras::HyprExtras: $HYPRLAND_INSTANCE_SIGNATURE is unset. Unable to connect to Hyprland socket.";
|
||||
return;
|
||||
}
|
||||
: QObject(parent)
|
||||
, m_requestSocket("")
|
||||
, m_eventSocket("")
|
||||
, m_socket(nullptr)
|
||||
, m_socketValid(false)
|
||||
, m_devices(new HyprDevices(this)) {
|
||||
const auto his = qEnvironmentVariable("HYPRLAND_INSTANCE_SIGNATURE");
|
||||
if (his.isEmpty()) {
|
||||
qCWarning(lcHypr) << "$HYPRLAND_INSTANCE_SIGNATURE is unset. Unable to connect to Hyprland socket.";
|
||||
return;
|
||||
}
|
||||
|
||||
auto hyprDir = QString("%1/hypr/%2").arg(qEnvironmentVariable("XDG_RUNTIME_DIR"), his);
|
||||
if (!QDir(hyprDir).exists()) {
|
||||
hyprDir = "/tmp/hypr/" + his;
|
||||
auto hyprDir = QString("%1/hypr/%2").arg(qEnvironmentVariable("XDG_RUNTIME_DIR"), his);
|
||||
if (!QDir(hyprDir).exists()) {
|
||||
hyprDir = QStringLiteral("/tmp/hypr/") + his;
|
||||
|
||||
if (!QDir(hyprDir).exists()) {
|
||||
qWarning() << "HyprExtras::HyprExtras: Hyprland socket directory does not exist. Unable to connect to "
|
||||
"Hyprland socket.";
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (!QDir(hyprDir).exists()) {
|
||||
qCWarning(lcHypr) << "Hyprland socket directory does not exist. Unable to connect to Hyprland socket.";
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
m_requestSocket = hyprDir + "/.socket.sock";
|
||||
m_eventSocket = hyprDir + "/.socket2.sock";
|
||||
m_requestSocket = hyprDir + QStringLiteral("/.socket.sock");
|
||||
m_eventSocket = hyprDir + QStringLiteral("/.socket2.sock");
|
||||
|
||||
refreshOptions();
|
||||
refreshDevices();
|
||||
refreshOptions();
|
||||
refreshDevices();
|
||||
|
||||
m_socket = new QLocalSocket(this);
|
||||
m_socket = new QLocalSocket(this);
|
||||
|
||||
QObject::connect(m_socket, &QLocalSocket::errorOccurred, this, &HyprExtras::socketError);
|
||||
QObject::connect(m_socket, &QLocalSocket::stateChanged, this, &HyprExtras::socketStateChanged);
|
||||
QObject::connect(m_socket, &QLocalSocket::readyRead, this, &HyprExtras::readEvent);
|
||||
QObject::connect(m_socket, &QLocalSocket::errorOccurred, this, &HyprExtras::socketError);
|
||||
QObject::connect(m_socket, &QLocalSocket::stateChanged, this, &HyprExtras::socketStateChanged);
|
||||
QObject::connect(m_socket, &QLocalSocket::readyRead, this, &HyprExtras::readEvent);
|
||||
|
||||
m_socket->connectToServer(m_eventSocket, QLocalSocket::ReadOnly);
|
||||
m_socket->connectToServer(m_eventSocket, QLocalSocket::ReadOnly);
|
||||
}
|
||||
|
||||
QVariantHash HyprExtras::options() const {
|
||||
return m_options;
|
||||
return m_options;
|
||||
}
|
||||
|
||||
HyprDevices* HyprExtras::devices() const {
|
||||
return m_devices;
|
||||
return m_devices;
|
||||
}
|
||||
|
||||
void HyprExtras::message(const QString& message) {
|
||||
if (message.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
if (message.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
makeRequest(message, [](bool success, const QByteArray& res) {
|
||||
if (!success) {
|
||||
qWarning() << "HyprExtras::message: request error:" << QString::fromUtf8(res);
|
||||
}
|
||||
});
|
||||
makeRequest(message, [](bool success, const QByteArray& res) {
|
||||
if (!success) {
|
||||
qCWarning(lcHypr) << "message: request error:" << QString::fromUtf8(res);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void HyprExtras::batchMessage(const QStringList& messages) {
|
||||
if (messages.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
if (messages.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
makeRequest("[[BATCH]]" + messages.join(";"), [](bool success, const QByteArray& res) {
|
||||
if (!success) {
|
||||
qWarning() << "HyprExtras::batchMessage: request error:" << QString::fromUtf8(res);
|
||||
}
|
||||
});
|
||||
makeRequest(QStringLiteral("[[BATCH]]") + messages.join(QLatin1Char(';')),
|
||||
[](bool success, const QByteArray& res) {
|
||||
if (!success) {
|
||||
qCWarning(lcHypr) << "batchMessage: request error:" << QString::fromUtf8(res);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void HyprExtras::applyOptions(const QVariantHash& options) {
|
||||
if (options.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
if (options.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
QString request = "[[BATCH]]";
|
||||
for (auto it = options.constBegin(); it != options.constEnd(); ++it) {
|
||||
request += QString("keyword %1 %2;").arg(it.key(), it.value().toString());
|
||||
}
|
||||
QStringList calls;
|
||||
calls.reserve(options.size());
|
||||
|
||||
makeRequest(request, [this](bool success, const QByteArray& res) {
|
||||
if (success) {
|
||||
refreshOptions();
|
||||
} else {
|
||||
qWarning() << "HyprExtras::applyOptions: request error" << QString::fromUtf8(res);
|
||||
}
|
||||
});
|
||||
for (auto it = options.constBegin(); it != options.constEnd(); ++it) {
|
||||
const auto call = buildHlConfigCall(it.key(), it.value());
|
||||
if (!call.isEmpty()) {
|
||||
calls << call;
|
||||
}
|
||||
}
|
||||
|
||||
if (calls.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
makeRequest(QStringLiteral("eval ") + calls.join(QLatin1String("; ")), [this](bool success, const QByteArray& res) {
|
||||
if (success) {
|
||||
refreshOptions();
|
||||
} else {
|
||||
qCWarning(lcHypr) << "applyOptions: request error" << QString::fromUtf8(res);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void HyprExtras::refreshOptions() {
|
||||
if (!m_optionsRefresh.isNull()) {
|
||||
m_optionsRefresh->close();
|
||||
}
|
||||
if (!m_optionsRefresh.isNull()) {
|
||||
m_optionsRefresh->close();
|
||||
}
|
||||
|
||||
m_optionsRefresh = makeRequestJson("descriptions", [this](bool success, const QJsonDocument& response) {
|
||||
m_optionsRefresh.reset();
|
||||
if (!success) {
|
||||
return;
|
||||
}
|
||||
m_optionsRefresh = makeRequestJson(QStringLiteral("descriptions"), [this](bool success, const QJsonDocument& response) {
|
||||
m_optionsRefresh.reset();
|
||||
if (!success) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto options = response.array();
|
||||
bool dirty = false;
|
||||
const auto options = response.array();
|
||||
bool dirty = false;
|
||||
|
||||
for (const auto& o : std::as_const(options)) {
|
||||
const auto obj = o.toObject();
|
||||
const auto key = obj.value("value").toString();
|
||||
const auto value = obj.value("data").toObject().value("current").toVariant();
|
||||
if (m_options.value(key) != value) {
|
||||
dirty = true;
|
||||
m_options.insert(key, value);
|
||||
}
|
||||
}
|
||||
for (const auto& o : std::as_const(options)) {
|
||||
const auto obj = o.toObject();
|
||||
const auto key = obj.value(QStringLiteral("value")).toString();
|
||||
const auto value = obj.value(QStringLiteral("data")).toObject().value(QStringLiteral("current")).toVariant();
|
||||
|
||||
if (dirty) {
|
||||
emit optionsChanged();
|
||||
}
|
||||
});
|
||||
if (m_options.value(key) != value) {
|
||||
dirty = true;
|
||||
m_options.insert(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
if (dirty) {
|
||||
emit optionsChanged();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void HyprExtras::refreshDevices() {
|
||||
if (!m_devicesRefresh.isNull()) {
|
||||
m_devicesRefresh->close();
|
||||
}
|
||||
if (!m_devicesRefresh.isNull()) {
|
||||
m_devicesRefresh->close();
|
||||
}
|
||||
|
||||
m_devicesRefresh = makeRequestJson("devices", [this](bool success, const QJsonDocument& response) {
|
||||
m_devicesRefresh.reset();
|
||||
if (success) {
|
||||
m_devices->updateLastIpcObject(response.object());
|
||||
}
|
||||
});
|
||||
m_devicesRefresh = makeRequestJson(QStringLiteral("devices"), [this](bool success, const QJsonDocument& response) {
|
||||
m_devicesRefresh.reset();
|
||||
if (success) {
|
||||
m_devices->updateLastIpcObject(response.object());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void HyprExtras::socketError(QLocalSocket::LocalSocketError error) const {
|
||||
if (!m_socketValid) {
|
||||
qWarning() << "HyprExtras::socketError: unable to connect to Hyprland event socket:" << error;
|
||||
} else {
|
||||
qWarning() << "HyprExtras::socketError: Hyprland event socket error:" << error;
|
||||
}
|
||||
if (!m_socketValid) {
|
||||
qCWarning(lcHypr) << "socketError: unable to connect to Hyprland event socket:" << error;
|
||||
} else {
|
||||
qCWarning(lcHypr) << "socketError: Hyprland event socket error:" << error;
|
||||
}
|
||||
}
|
||||
|
||||
void HyprExtras::socketStateChanged(QLocalSocket::LocalSocketState state) {
|
||||
if (state == QLocalSocket::UnconnectedState && m_socketValid) {
|
||||
qWarning() << "HyprExtras::socketStateChanged: Hyprland event socket disconnected.";
|
||||
}
|
||||
if (state == QLocalSocket::UnconnectedState && m_socketValid) {
|
||||
qCWarning(lcHypr) << "socketStateChanged: Hyprland event socket disconnected.";
|
||||
}
|
||||
|
||||
m_socketValid = state == QLocalSocket::ConnectedState;
|
||||
m_socketValid = state == QLocalSocket::ConnectedState;
|
||||
}
|
||||
|
||||
void HyprExtras::readEvent() {
|
||||
while (true) {
|
||||
auto rawEvent = m_socket->readLine();
|
||||
if (rawEvent.isEmpty()) {
|
||||
break;
|
||||
}
|
||||
rawEvent.truncate(rawEvent.length() - 1); // Remove trailing \n
|
||||
const auto event = QByteArrayView(rawEvent.data(), rawEvent.indexOf(">>"));
|
||||
handleEvent(QString::fromUtf8(event));
|
||||
}
|
||||
while (true) {
|
||||
auto rawEvent = m_socket->readLine();
|
||||
if (rawEvent.isEmpty()) {
|
||||
break;
|
||||
}
|
||||
rawEvent.truncate(rawEvent.length() - 1);
|
||||
const auto event = QByteArrayView(rawEvent.data(), rawEvent.indexOf(">>"));
|
||||
handleEvent(QString::fromUtf8(event));
|
||||
}
|
||||
}
|
||||
|
||||
void HyprExtras::handleEvent(const QString& event) {
|
||||
if (event == "configreloaded") {
|
||||
refreshOptions();
|
||||
} else if (event == "activelayout") {
|
||||
refreshDevices();
|
||||
}
|
||||
if (event == QStringLiteral("configreloaded")) {
|
||||
refreshOptions();
|
||||
} else if (event == QStringLiteral("activelayout")) {
|
||||
refreshDevices();
|
||||
}
|
||||
}
|
||||
|
||||
HyprExtras::SocketPtr HyprExtras::makeRequestJson(
|
||||
const QString& request, const std::function<void(bool, QJsonDocument)>& callback) {
|
||||
return makeRequest("j/" + request, [callback](bool success, const QByteArray& response) {
|
||||
callback(success, QJsonDocument::fromJson(response));
|
||||
});
|
||||
const QString& request, const std::function<void(bool, QJsonDocument)>& callback) {
|
||||
return makeRequest(QStringLiteral("j/") + request, [callback](bool success, const QByteArray& response) {
|
||||
callback(success, QJsonDocument::fromJson(response));
|
||||
});
|
||||
}
|
||||
|
||||
HyprExtras::SocketPtr HyprExtras::makeRequest(
|
||||
const QString& request, const std::function<void(bool, QByteArray)>& callback) {
|
||||
if (m_requestSocket.isEmpty()) {
|
||||
return SocketPtr();
|
||||
}
|
||||
const QString& request, const std::function<void(bool, QByteArray)>& callback) {
|
||||
if (m_requestSocket.isEmpty()) {
|
||||
return SocketPtr();
|
||||
}
|
||||
|
||||
auto socket = SocketPtr::create(this);
|
||||
auto socket = SocketPtr::create(this);
|
||||
|
||||
QObject::connect(socket.data(), &QLocalSocket::connected, this, [=, this]() {
|
||||
QObject::connect(socket.data(), &QLocalSocket::readyRead, this, [socket, callback]() {
|
||||
const auto response = socket->readAll();
|
||||
callback(true, std::move(response));
|
||||
socket->close();
|
||||
});
|
||||
QObject::connect(socket.data(), &QLocalSocket::connected, this, [=, this]() {
|
||||
QObject::connect(socket.data(), &QLocalSocket::readyRead, this, [socket, callback]() {
|
||||
const auto response = socket->readAll();
|
||||
callback(true, std::move(response));
|
||||
socket->close();
|
||||
});
|
||||
|
||||
socket->write(request.toUtf8());
|
||||
socket->flush();
|
||||
});
|
||||
socket->write(request.toUtf8());
|
||||
socket->flush();
|
||||
});
|
||||
|
||||
QObject::connect(socket.data(), &QLocalSocket::errorOccurred, this, [=](QLocalSocket::LocalSocketError err) {
|
||||
qWarning() << "HyprExtras::makeRequest: error making request:" << err << "| request:" << request;
|
||||
callback(false, {});
|
||||
socket->close();
|
||||
});
|
||||
QObject::connect(socket.data(), &QLocalSocket::errorOccurred, this, [=](QLocalSocket::LocalSocketError err) {
|
||||
qCWarning(lcHypr) << "makeRequest: error making request:" << err << "| request:" << request;
|
||||
callback(false, {});
|
||||
socket->close();
|
||||
});
|
||||
|
||||
socket->connectToServer(m_requestSocket);
|
||||
socket->connectToServer(m_requestSocket);
|
||||
|
||||
return socket;
|
||||
return socket;
|
||||
}
|
||||
|
||||
} // namespace ZShell::internal::hypr
|
||||
|
||||
@@ -1,56 +1,60 @@
|
||||
#pragma once
|
||||
|
||||
#include "hyprdevices.hpp"
|
||||
#include <qlocalsocket.h>
|
||||
#include <qobject.h>
|
||||
#include <qqmlintegration.h>
|
||||
#include <qsharedpointer.h>
|
||||
#include <qvariant.h>
|
||||
|
||||
namespace ZShell::internal::hypr {
|
||||
|
||||
class HyprExtras : public QObject {
|
||||
Q_OBJECT
|
||||
QML_ELEMENT
|
||||
class HyprDevices;
|
||||
|
||||
Q_PROPERTY(QVariantHash options READ options NOTIFY optionsChanged)
|
||||
Q_PROPERTY(ZShell::internal::hypr::HyprDevices* devices READ devices CONSTANT)
|
||||
class HyprExtras : public QObject {
|
||||
Q_OBJECT
|
||||
QML_ELEMENT
|
||||
Q_MOC_INCLUDE("hyprdevices.hpp")
|
||||
|
||||
Q_PROPERTY(QVariantHash options READ options NOTIFY optionsChanged)
|
||||
Q_PROPERTY(ZShell::internal::hypr::HyprDevices* devices READ devices CONSTANT)
|
||||
|
||||
public:
|
||||
explicit HyprExtras(QObject* parent = nullptr);
|
||||
explicit HyprExtras(QObject* parent = nullptr);
|
||||
|
||||
[[nodiscard]] QVariantHash options() const;
|
||||
[[nodiscard]] HyprDevices* devices() const;
|
||||
[[nodiscard]] QVariantHash options() const;
|
||||
[[nodiscard]] HyprDevices* devices() const;
|
||||
|
||||
Q_INVOKABLE void message(const QString& message);
|
||||
Q_INVOKABLE void batchMessage(const QStringList& messages);
|
||||
Q_INVOKABLE void applyOptions(const QVariantHash& options);
|
||||
Q_INVOKABLE void message(const QString& message);
|
||||
Q_INVOKABLE void batchMessage(const QStringList& messages);
|
||||
Q_INVOKABLE void applyOptions(const QVariantHash& options);
|
||||
|
||||
Q_INVOKABLE void refreshOptions();
|
||||
Q_INVOKABLE void refreshDevices();
|
||||
Q_INVOKABLE void refreshOptions();
|
||||
Q_INVOKABLE void refreshDevices();
|
||||
|
||||
signals:
|
||||
void optionsChanged();
|
||||
void optionsChanged();
|
||||
|
||||
private:
|
||||
using SocketPtr = QSharedPointer<QLocalSocket>;
|
||||
using SocketPtr = QSharedPointer<QLocalSocket>;
|
||||
|
||||
QString m_requestSocket;
|
||||
QString m_eventSocket;
|
||||
QLocalSocket* m_socket;
|
||||
bool m_socketValid;
|
||||
QString m_requestSocket;
|
||||
QString m_eventSocket;
|
||||
QLocalSocket* m_socket;
|
||||
bool m_socketValid;
|
||||
|
||||
QVariantHash m_options;
|
||||
HyprDevices* const m_devices;
|
||||
QVariantHash m_options;
|
||||
HyprDevices* const m_devices;
|
||||
|
||||
SocketPtr m_optionsRefresh;
|
||||
SocketPtr m_devicesRefresh;
|
||||
SocketPtr m_optionsRefresh;
|
||||
SocketPtr m_devicesRefresh;
|
||||
|
||||
void socketError(QLocalSocket::LocalSocketError error) const;
|
||||
void socketStateChanged(QLocalSocket::LocalSocketState state);
|
||||
void readEvent();
|
||||
void handleEvent(const QString& event);
|
||||
void socketError(QLocalSocket::LocalSocketError error) const;
|
||||
void socketStateChanged(QLocalSocket::LocalSocketState state);
|
||||
void readEvent();
|
||||
void handleEvent(const QString& event);
|
||||
|
||||
SocketPtr makeRequestJson(const QString& request, const std::function<void(bool, QJsonDocument)>& callback);
|
||||
SocketPtr makeRequest(const QString& request, const std::function<void(bool, QByteArray)>& callback);
|
||||
SocketPtr makeRequestJson(const QString& request, const std::function<void(bool, QJsonDocument)>& callback);
|
||||
SocketPtr makeRequest(const QString& request, const std::function<void(bool, QByteArray)>& callback);
|
||||
};
|
||||
|
||||
} // namespace ZShell::internal::hypr
|
||||
|
||||
@@ -74,6 +74,8 @@ Defaults from `CMakeLists.txt`:
|
||||
|
||||
### Nix Flake
|
||||
|
||||
_Note that Nix is not actively developed at this point. Things may be broken. Feel free to suggest fixes in a PR_
|
||||
|
||||
The flake exposes:
|
||||
|
||||
- `packages.<system>.zshell`
|
||||
|
||||
@@ -9,7 +9,6 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"typer",
|
||||
"pillow",
|
||||
"jinja2",
|
||||
"materialyoucolor"
|
||||
]
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
from __future__ import annotations
|
||||
import typer
|
||||
from zshell.subcommands import shell, scheme, screenshot, wallpaper, preset
|
||||
from zshell.subcommands import shell, scheme, screenshot, wallpaper
|
||||
|
||||
app = typer.Typer()
|
||||
|
||||
@@ -8,7 +8,7 @@ app.add_typer(shell.app, name="shell")
|
||||
app.add_typer(scheme.app, name="scheme")
|
||||
app.add_typer(screenshot.app, name="screenshot")
|
||||
app.add_typer(wallpaper.app, name="wallpaper")
|
||||
app.add_typer(preset.app, name="preset")
|
||||
# app.add_typer(preset.app, name="preset")
|
||||
|
||||
|
||||
def main() -> None:
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,544 +0,0 @@
|
||||
{
|
||||
"id": "catppuccin",
|
||||
"name": "Catppuccin",
|
||||
"version": "1.0.0",
|
||||
"author": "Catppuccin Org",
|
||||
"description": "Soothing pastel theme for the high-spirited!",
|
||||
"dark": {},
|
||||
"light": {},
|
||||
"variants": {
|
||||
"type": "multi",
|
||||
"defaults": {
|
||||
"dark": { "flavor": "mocha", "accent": "mauve" },
|
||||
"light": { "flavor": "latte", "accent": "mauve" }
|
||||
},
|
||||
"flavors": [
|
||||
{
|
||||
"id": "latte",
|
||||
"name": "Latte",
|
||||
"light": {
|
||||
"surface": "#ccd0da",
|
||||
"surfaceText": "#4c4f69",
|
||||
"surfaceVariant": "#eff1f5",
|
||||
"surfaceVariantText": "#6c6f85",
|
||||
"background": "#eff1f5",
|
||||
"backgroundText": "#4c4f69",
|
||||
"outline": "#9ca0b0",
|
||||
"surfaceContainer": "#eff1f5",
|
||||
"surfaceContainerHigh": "#e6e9ef",
|
||||
"surfaceContainerHighest": "#dce0e8",
|
||||
"error": "#d20f39",
|
||||
"warning": "#fe640b",
|
||||
"info": "#1e66f5"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "frappe",
|
||||
"name": "Frappé",
|
||||
"dark": {
|
||||
"surface": "#414559",
|
||||
"surfaceText": "#c6d0f5",
|
||||
"surfaceVariant": "#303446",
|
||||
"surfaceVariantText": "#a5adce",
|
||||
"background": "#303446",
|
||||
"backgroundText": "#c6d0f5",
|
||||
"outline": "#737994",
|
||||
"surfaceContainer": "#303446",
|
||||
"surfaceContainerHigh": "#292c3c",
|
||||
"surfaceContainerHighest": "#232634",
|
||||
"error": "#e78284",
|
||||
"warning": "#ef9f76",
|
||||
"info": "#8caaee"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "macchiato",
|
||||
"name": "Macchiato",
|
||||
"dark": {
|
||||
"surface": "#363a4f",
|
||||
"surfaceText": "#cad3f5",
|
||||
"surfaceVariant": "#24273a",
|
||||
"surfaceVariantText": "#a5adcb",
|
||||
"background": "#24273a",
|
||||
"backgroundText": "#cad3f5",
|
||||
"outline": "#6e738d",
|
||||
"surfaceContainer": "#24273a",
|
||||
"surfaceContainerHigh": "#1e2030",
|
||||
"surfaceContainerHighest": "#181926",
|
||||
"error": "#ed8796",
|
||||
"warning": "#f5a97f",
|
||||
"info": "#8aadf4"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "mocha",
|
||||
"name": "Mocha",
|
||||
"dark": {
|
||||
"surface": "#313244",
|
||||
"surfaceText": "#cdd6f4",
|
||||
"surfaceVariant": "#1e1e2e",
|
||||
"surfaceVariantText": "#a6adc8",
|
||||
"background": "#1e1e2e",
|
||||
"backgroundText": "#cdd6f4",
|
||||
"outline": "#6c7086",
|
||||
"surfaceContainer": "#1e1e2e",
|
||||
"surfaceContainerHigh": "#181825",
|
||||
"surfaceContainerHighest": "#11111b",
|
||||
"error": "#f38ba8",
|
||||
"warning": "#fab387",
|
||||
"info": "#89b4fa"
|
||||
}
|
||||
}
|
||||
],
|
||||
"accents": [
|
||||
{
|
||||
"id": "rosewater",
|
||||
"name": "Rosewater",
|
||||
"latte": {
|
||||
"primary": "#dc8a78",
|
||||
"primaryText": "#eff1f5",
|
||||
"primaryContainer": "#e1a99d",
|
||||
"secondary": "#d8c7c4",
|
||||
"surfaceTint": "#e1a99d"
|
||||
},
|
||||
"frappe": {
|
||||
"primary": "#f2d5cf",
|
||||
"primaryText": "#303446",
|
||||
"primaryContainer": "#b8a5a6",
|
||||
"secondary": "#a2748b",
|
||||
"surfaceTint": "#b8a5a6"
|
||||
},
|
||||
"macchiato": {
|
||||
"primary": "#f4dbd6",
|
||||
"primaryText": "#24273a",
|
||||
"primaryContainer": "#b6a6a7",
|
||||
"secondary": "#9f6f8d",
|
||||
"surfaceTint": "#b6a6a7"
|
||||
},
|
||||
"mocha": {
|
||||
"primary": "#f5e0dc",
|
||||
"primaryText": "#1e1e2e",
|
||||
"primaryContainer": "#b5a6a8",
|
||||
"secondary": "#9d6d87",
|
||||
"surfaceTint": "#b5a6a8"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "flamingo",
|
||||
"name": "Flamingo",
|
||||
"latte": {
|
||||
"primary": "#dd7878",
|
||||
"primaryText": "#eff1f5",
|
||||
"primaryContainer": "#e29c9d",
|
||||
"secondary": "#d7c3c4",
|
||||
"surfaceTint": "#e29c9d"
|
||||
},
|
||||
"frappe": {
|
||||
"primary": "#eebebe",
|
||||
"primaryText": "#303446",
|
||||
"primaryContainer": "#b5949a",
|
||||
"secondary": "#9d6b80",
|
||||
"surfaceTint": "#b5949a"
|
||||
},
|
||||
"macchiato": {
|
||||
"primary": "#f0c6c6",
|
||||
"primaryText": "#24273a",
|
||||
"primaryContainer": "#b3979c",
|
||||
"secondary": "#996780",
|
||||
"surfaceTint": "#b3979c"
|
||||
},
|
||||
"mocha": {
|
||||
"primary": "#f2cdcd",
|
||||
"primaryText": "#1e1e2e",
|
||||
"primaryContainer": "#b3999e",
|
||||
"secondary": "#98667c",
|
||||
"surfaceTint": "#b3999e"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "pink",
|
||||
"name": "Pink",
|
||||
"latte": {
|
||||
"primary": "#ea76cb",
|
||||
"primaryText": "#eff1f5",
|
||||
"primaryContainer": "#eb9bd7",
|
||||
"secondary": "#d9c7d5",
|
||||
"surfaceTint": "#eb9bd7"
|
||||
},
|
||||
"frappe": {
|
||||
"primary": "#f4b8e4",
|
||||
"primaryText": "#303446",
|
||||
"primaryContainer": "#b990b5",
|
||||
"secondary": "#996e9e",
|
||||
"surfaceTint": "#b990b5"
|
||||
},
|
||||
"macchiato": {
|
||||
"primary": "#f5bde6",
|
||||
"primaryText": "#24273a",
|
||||
"primaryContainer": "#b791b2",
|
||||
"secondary": "#95689a",
|
||||
"surfaceTint": "#b791b2"
|
||||
},
|
||||
"mocha": {
|
||||
"primary": "#f5c2e7",
|
||||
"primaryText": "#1e1e2e",
|
||||
"primaryContainer": "#b591b0",
|
||||
"secondary": "#966597",
|
||||
"surfaceTint": "#b591b0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "mauve",
|
||||
"name": "Mauve",
|
||||
"latte": {
|
||||
"primary": "#8839ef",
|
||||
"primaryText": "#eff1f5",
|
||||
"primaryContainer": "#a670f1",
|
||||
"secondary": "#c2b8d0",
|
||||
"surfaceTint": "#a670f1"
|
||||
},
|
||||
"frappe": {
|
||||
"primary": "#ca9ee6",
|
||||
"primaryText": "#303446",
|
||||
"primaryContainer": "#9c7eb6",
|
||||
"secondary": "#7d6799",
|
||||
"surfaceTint": "#9c7eb6"
|
||||
},
|
||||
"macchiato": {
|
||||
"primary": "#c6a0f6",
|
||||
"primaryText": "#24273a",
|
||||
"primaryContainer": "#967cbe",
|
||||
"secondary": "#766597",
|
||||
"surfaceTint": "#967cbe"
|
||||
},
|
||||
"mocha": {
|
||||
"primary": "#cba6f7",
|
||||
"primaryText": "#1e1e2e",
|
||||
"primaryContainer": "#977ebb",
|
||||
"secondary": "#756294",
|
||||
"surfaceTint": "#977ebb"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "red",
|
||||
"name": "Red",
|
||||
"latte": {
|
||||
"primary": "#d20f39",
|
||||
"primaryText": "#eff1f5",
|
||||
"primaryContainer": "#da5371",
|
||||
"secondary": "#c0a0a8",
|
||||
"surfaceTint": "#da5371"
|
||||
},
|
||||
"frappe": {
|
||||
"primary": "#e78284",
|
||||
"primaryText": "#303446",
|
||||
"primaryContainer": "#b06a72",
|
||||
"secondary": "#8b5d66",
|
||||
"surfaceTint": "#b06a72"
|
||||
},
|
||||
"macchiato": {
|
||||
"primary": "#ed8796",
|
||||
"primaryText": "#24273a",
|
||||
"primaryContainer": "#b16b7a",
|
||||
"secondary": "#865a69",
|
||||
"surfaceTint": "#b16b7a"
|
||||
},
|
||||
"mocha": {
|
||||
"primary": "#f38ba8",
|
||||
"primaryText": "#1e1e2e",
|
||||
"primaryContainer": "#b46b84",
|
||||
"secondary": "#85596b",
|
||||
"surfaceTint": "#b46b84"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "maroon",
|
||||
"name": "Maroon",
|
||||
"latte": {
|
||||
"primary": "#e64553",
|
||||
"primaryText": "#eff1f5",
|
||||
"primaryContainer": "#e87883",
|
||||
"secondary": "#cfb7ba",
|
||||
"surfaceTint": "#e87883"
|
||||
},
|
||||
"frappe": {
|
||||
"primary": "#ea999c",
|
||||
"primaryText": "#303446",
|
||||
"primaryContainer": "#b27a83",
|
||||
"secondary": "#92626f",
|
||||
"surfaceTint": "#b27a83"
|
||||
},
|
||||
"macchiato": {
|
||||
"primary": "#ee99a0",
|
||||
"primaryText": "#24273a",
|
||||
"primaryContainer": "#b27781",
|
||||
"secondary": "#8c5e6c",
|
||||
"surfaceTint": "#b27781"
|
||||
},
|
||||
"mocha": {
|
||||
"primary": "#eba0ac",
|
||||
"primaryText": "#1e1e2e",
|
||||
"primaryContainer": "#ae7987",
|
||||
"secondary": "#895b6c",
|
||||
"surfaceTint": "#ae7987"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "peach",
|
||||
"name": "Peach",
|
||||
"latte": {
|
||||
"primary": "#fe640b",
|
||||
"primaryText": "#eff1f5",
|
||||
"primaryContainer": "#f98e51",
|
||||
"secondary": "#c9b7ad",
|
||||
"surfaceTint": "#f98e51"
|
||||
},
|
||||
"frappe": {
|
||||
"primary": "#ef9f76",
|
||||
"primaryText": "#303446",
|
||||
"primaryContainer": "#b67f68",
|
||||
"secondary": "#8f6a5f",
|
||||
"surfaceTint": "#b67f68"
|
||||
},
|
||||
"macchiato": {
|
||||
"primary": "#f5a97f",
|
||||
"primaryText": "#24273a",
|
||||
"primaryContainer": "#b7836a",
|
||||
"secondary": "#8c695e",
|
||||
"surfaceTint": "#b7836a"
|
||||
},
|
||||
"mocha": {
|
||||
"primary": "#fab387",
|
||||
"primaryText": "#1e1e2e",
|
||||
"primaryContainer": "#b8876d",
|
||||
"secondary": "#8b6a5d",
|
||||
"surfaceTint": "#b8876d"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "yellow",
|
||||
"name": "Yellow",
|
||||
"latte": {
|
||||
"primary": "#df8e1d",
|
||||
"primaryText": "#eff1f5",
|
||||
"primaryContainer": "#e4ac5d",
|
||||
"secondary": "#c6baaa",
|
||||
"surfaceTint": "#e4ac5d"
|
||||
},
|
||||
"frappe": {
|
||||
"primary": "#e5c890",
|
||||
"primaryText": "#303446",
|
||||
"primaryContainer": "#af9b7a",
|
||||
"secondary": "#948062",
|
||||
"surfaceTint": "#af9b7a"
|
||||
},
|
||||
"macchiato": {
|
||||
"primary": "#eed49f",
|
||||
"primaryText": "#24273a",
|
||||
"primaryContainer": "#b2a181",
|
||||
"secondary": "#947e62",
|
||||
"surfaceTint": "#b2a181"
|
||||
},
|
||||
"mocha": {
|
||||
"primary": "#f9e2af",
|
||||
"primaryText": "#1e1e2e",
|
||||
"primaryContainer": "#b8a889",
|
||||
"secondary": "#978265",
|
||||
"surfaceTint": "#b8a889"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "green",
|
||||
"name": "Green",
|
||||
"latte": {
|
||||
"primary": "#40a02b",
|
||||
"primaryText": "#eff1f5",
|
||||
"primaryContainer": "#74b867",
|
||||
"secondary": "#9fbd9b",
|
||||
"surfaceTint": "#74b867"
|
||||
},
|
||||
"frappe": {
|
||||
"primary": "#a6d189",
|
||||
"primaryText": "#303446",
|
||||
"primaryContainer": "#83a275",
|
||||
"secondary": "#648e5e",
|
||||
"surfaceTint": "#83a275"
|
||||
},
|
||||
"macchiato": {
|
||||
"primary": "#a6da95",
|
||||
"primaryText": "#24273a",
|
||||
"primaryContainer": "#80a57a",
|
||||
"secondary": "#5c8a61",
|
||||
"surfaceTint": "#80a57a"
|
||||
},
|
||||
"mocha": {
|
||||
"primary": "#a6e3a1",
|
||||
"primaryText": "#1e1e2e",
|
||||
"primaryContainer": "#7ea87f",
|
||||
"secondary": "#5b8964",
|
||||
"surfaceTint": "#7ea87f"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "teal",
|
||||
"name": "Teal",
|
||||
"latte": {
|
||||
"primary": "#179299",
|
||||
"primaryText": "#eff1f5",
|
||||
"primaryContainer": "#57aeb4",
|
||||
"secondary": "#93b4b7",
|
||||
"surfaceTint": "#57aeb4"
|
||||
},
|
||||
"frappe": {
|
||||
"primary": "#81c8be",
|
||||
"primaryText": "#303446",
|
||||
"primaryContainer": "#699b9a",
|
||||
"secondary": "#588084",
|
||||
"surfaceTint": "#699b9a"
|
||||
},
|
||||
"macchiato": {
|
||||
"primary": "#8bd5ca",
|
||||
"primaryText": "#24273a",
|
||||
"primaryContainer": "#6da29f",
|
||||
"secondary": "#577e83",
|
||||
"surfaceTint": "#6da29f"
|
||||
},
|
||||
"mocha": {
|
||||
"primary": "#94e2d5",
|
||||
"primaryText": "#1e1e2e",
|
||||
"primaryContainer": "#71a8a4",
|
||||
"secondary": "#588284",
|
||||
"surfaceTint": "#71a8a4"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "sky",
|
||||
"name": "Sky",
|
||||
"latte": {
|
||||
"primary": "#04a5e5",
|
||||
"primaryText": "#eff1f5",
|
||||
"primaryContainer": "#4abcea",
|
||||
"secondary": "#a4b9c2",
|
||||
"surfaceTint": "#4abcea"
|
||||
},
|
||||
"frappe": {
|
||||
"primary": "#99d1db",
|
||||
"primaryText": "#303446",
|
||||
"primaryContainer": "#79a2af",
|
||||
"secondary": "#628494",
|
||||
"surfaceTint": "#79a2af"
|
||||
},
|
||||
"macchiato": {
|
||||
"primary": "#91d7e3",
|
||||
"primaryText": "#24273a",
|
||||
"primaryContainer": "#71a3b0",
|
||||
"secondary": "#5e7e8c",
|
||||
"surfaceTint": "#71a3b0"
|
||||
},
|
||||
"mocha": {
|
||||
"primary": "#89dceb",
|
||||
"primaryText": "#1e1e2e",
|
||||
"primaryContainer": "#69a3b3",
|
||||
"secondary": "#5a7b88",
|
||||
"surfaceTint": "#69a3b3"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "sapphire",
|
||||
"name": "Sapphire",
|
||||
"latte": {
|
||||
"primary": "#209fb5",
|
||||
"primaryText": "#eff1f5",
|
||||
"primaryContainer": "#5db8c8",
|
||||
"secondary": "#9eb9be",
|
||||
"surfaceTint": "#5db8c8"
|
||||
},
|
||||
"frappe": {
|
||||
"primary": "#85c1dc",
|
||||
"primaryText": "#303446",
|
||||
"primaryContainer": "#6b96af",
|
||||
"secondary": "#5e7b8e",
|
||||
"surfaceTint": "#6b96af"
|
||||
},
|
||||
"macchiato": {
|
||||
"primary": "#7dc4e4",
|
||||
"primaryText": "#24273a",
|
||||
"primaryContainer": "#6396b1",
|
||||
"secondary": "#5a7486",
|
||||
"surfaceTint": "#6396b1"
|
||||
},
|
||||
"mocha": {
|
||||
"primary": "#74c7ec",
|
||||
"primaryText": "#1e1e2e",
|
||||
"primaryContainer": "#5a95b4",
|
||||
"secondary": "#567080",
|
||||
"surfaceTint": "#5a95b4"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "blue",
|
||||
"name": "Blue",
|
||||
"latte": {
|
||||
"primary": "#1e66f5",
|
||||
"primaryText": "#eff1f5",
|
||||
"primaryContainer": "#5c90f5",
|
||||
"secondary": "#b1bacb",
|
||||
"surfaceTint": "#5c90f5"
|
||||
},
|
||||
"frappe": {
|
||||
"primary": "#8caaee",
|
||||
"primaryText": "#303446",
|
||||
"primaryContainer": "#7086bc",
|
||||
"secondary": "#637195",
|
||||
"surfaceTint": "#7086bc"
|
||||
},
|
||||
"macchiato": {
|
||||
"primary": "#8aadf4",
|
||||
"primaryText": "#24273a",
|
||||
"primaryContainer": "#6c85bc",
|
||||
"secondary": "#5f6d8f",
|
||||
"surfaceTint": "#6c85bc"
|
||||
},
|
||||
"mocha": {
|
||||
"primary": "#89b4fa",
|
||||
"primaryText": "#1e1e2e",
|
||||
"primaryContainer": "#6987bd",
|
||||
"secondary": "#5d6c8b",
|
||||
"surfaceTint": "#6987bd"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "lavender",
|
||||
"name": "Lavender",
|
||||
"latte": {
|
||||
"primary": "#7287fd",
|
||||
"primaryText": "#eff1f5",
|
||||
"primaryContainer": "#97a7fb",
|
||||
"secondary": "#cdcfdd",
|
||||
"surfaceTint": "#97a7fb"
|
||||
},
|
||||
"frappe": {
|
||||
"primary": "#babbf1",
|
||||
"primaryText": "#303446",
|
||||
"primaryContainer": "#9192be",
|
||||
"secondary": "#7175a1",
|
||||
"surfaceTint": "#9192be"
|
||||
},
|
||||
"macchiato": {
|
||||
"primary": "#b7bdf8",
|
||||
"primaryText": "#24273a",
|
||||
"primaryContainer": "#8b91bf",
|
||||
"secondary": "#6b709d",
|
||||
"surfaceTint": "#8b91bf"
|
||||
},
|
||||
"mocha": {
|
||||
"primary": "#b4befe",
|
||||
"primaryText": "#1e1e2e",
|
||||
"primaryContainer": "#878ec0",
|
||||
"secondary": "#676d99",
|
||||
"surfaceTint": "#878ec0"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,544 @@
|
||||
_data = {
|
||||
"id": "catppuccin",
|
||||
"name": "Catppuccin",
|
||||
"version": "1.0.0",
|
||||
"author": "Catppuccin Org",
|
||||
"description": "Soothing pastel theme for the high-spirited!",
|
||||
"dark": {},
|
||||
"light": {},
|
||||
"variants": {
|
||||
"type": "multi",
|
||||
"defaults": {
|
||||
"dark": {"m3flavor": "mocha", "m3accent": "mauve"},
|
||||
"light": {"m3flavor": "latte", "m3accent": "mauve"},
|
||||
},
|
||||
"flavors": [
|
||||
{
|
||||
"id": "latte",
|
||||
"name": "Latte",
|
||||
"light": {
|
||||
"m3surface": "#ccd0da",
|
||||
"m3surfaceText": "#4c4f69",
|
||||
"m3surfaceVariant": "#eff1f5",
|
||||
"m3surfaceVariantText": "#6c6f85",
|
||||
"m3background": "#eff1f5",
|
||||
"m3backgroundText": "#4c4f69",
|
||||
"m3outline": "#9ca0b0",
|
||||
"m3surfaceContainer": "#eff1f5",
|
||||
"m3surfaceContainerHigh": "#e6e9ef",
|
||||
"m3surfaceContainerHighest": "#dce0e8",
|
||||
"m3error": "#d20f39",
|
||||
"m3warning": "#fe640b",
|
||||
"m3info": "#1e66f5",
|
||||
},
|
||||
},
|
||||
{
|
||||
"id": "frappe",
|
||||
"name": "Frappé",
|
||||
"dark": {
|
||||
"m3surface": "#414559",
|
||||
"m3surfaceText": "#c6d0f5",
|
||||
"m3surfaceVariant": "#303446",
|
||||
"m3surfaceVariantText": "#a5adce",
|
||||
"m3background": "#303446",
|
||||
"m3backgroundText": "#c6d0f5",
|
||||
"m3outline": "#737994",
|
||||
"m3surfaceContainer": "#303446",
|
||||
"m3surfaceContainerHigh": "#292c3c",
|
||||
"m3surfaceContainerHighest": "#232634",
|
||||
"m3error": "#e78284",
|
||||
"m3warning": "#ef9f76",
|
||||
"m3info": "#8caaee",
|
||||
},
|
||||
},
|
||||
{
|
||||
"id": "macchiato",
|
||||
"name": "Macchiato",
|
||||
"dark": {
|
||||
"m3surface": "#363a4f",
|
||||
"m3surfaceText": "#cad3f5",
|
||||
"m3surfaceVariant": "#24273a",
|
||||
"m3surfaceVariantText": "#a5adcb",
|
||||
"m3background": "#24273a",
|
||||
"m3backgroundText": "#cad3f5",
|
||||
"m3outline": "#6e738d",
|
||||
"m3surfaceContainer": "#24273a",
|
||||
"m3surfaceContainerHigh": "#1e2030",
|
||||
"m3surfaceContainerHighest": "#181926",
|
||||
"m3error": "#ed8796",
|
||||
"m3warning": "#f5a97f",
|
||||
"m3info": "#8aadf4",
|
||||
},
|
||||
},
|
||||
{
|
||||
"id": "mocha",
|
||||
"name": "Mocha",
|
||||
"dark": {
|
||||
"m3surface": "#313244",
|
||||
"m3surfaceText": "#cdd6f4",
|
||||
"m3surfaceVariant": "#1e1e2e",
|
||||
"m3surfaceVariantText": "#a6adc8",
|
||||
"m3background": "#1e1e2e",
|
||||
"m3backgroundText": "#cdd6f4",
|
||||
"m3outline": "#6c7086",
|
||||
"m3surfaceContainer": "#1e1e2e",
|
||||
"m3surfaceContainerHigh": "#181825",
|
||||
"m3surfaceContainerHighest": "#11111b",
|
||||
"m3error": "#f38ba8",
|
||||
"m3warning": "#fab387",
|
||||
"m3info": "#89b4fa",
|
||||
},
|
||||
},
|
||||
],
|
||||
"accents": [
|
||||
{
|
||||
"id": "rosewater",
|
||||
"name": "Rosewater",
|
||||
"latte": {
|
||||
"m3primary": "#dc8a78",
|
||||
"m3primaryText": "#eff1f5",
|
||||
"m3primaryContainer": "#e1a99d",
|
||||
"m3secondary": "#d8c7c4",
|
||||
"m3surfaceTint": "#e1a99d",
|
||||
},
|
||||
"frappe": {
|
||||
"m3primary": "#f2d5cf",
|
||||
"m3primaryText": "#303446",
|
||||
"m3primaryContainer": "#b8a5a6",
|
||||
"m3secondary": "#a2748b",
|
||||
"m3surfaceTint": "#b8a5a6",
|
||||
},
|
||||
"macchiato": {
|
||||
"m3primary": "#f4dbd6",
|
||||
"m3primaryText": "#24273a",
|
||||
"m3primaryContainer": "#b6a6a7",
|
||||
"m3secondary": "#9f6f8d",
|
||||
"m3surfaceTint": "#b6a6a7",
|
||||
},
|
||||
"mocha": {
|
||||
"m3primary": "#f5e0dc",
|
||||
"m3primaryText": "#1e1e2e",
|
||||
"m3primaryContainer": "#b5a6a8",
|
||||
"m3secondary": "#9d6d87",
|
||||
"m3surfaceTint": "#b5a6a8",
|
||||
},
|
||||
},
|
||||
{
|
||||
"id": "flamingo",
|
||||
"name": "Flamingo",
|
||||
"latte": {
|
||||
"m3primary": "#dd7878",
|
||||
"m3primaryText": "#eff1f5",
|
||||
"m3primaryContainer": "#e29c9d",
|
||||
"m3secondary": "#d7c3c4",
|
||||
"m3surfaceTint": "#e29c9d",
|
||||
},
|
||||
"frappe": {
|
||||
"m3primary": "#eebebe",
|
||||
"m3primaryText": "#303446",
|
||||
"m3primaryContainer": "#b5949a",
|
||||
"m3secondary": "#9d6b80",
|
||||
"m3surfaceTint": "#b5949a",
|
||||
},
|
||||
"macchiato": {
|
||||
"m3primary": "#f0c6c6",
|
||||
"m3primaryText": "#24273a",
|
||||
"m3primaryContainer": "#b3979c",
|
||||
"m3secondary": "#996780",
|
||||
"m3surfaceTint": "#b3979c",
|
||||
},
|
||||
"mocha": {
|
||||
"m3primary": "#f2cdcd",
|
||||
"m3primaryText": "#1e1e2e",
|
||||
"m3primaryContainer": "#b3999e",
|
||||
"m3secondary": "#98667c",
|
||||
"m3surfaceTint": "#b3999e",
|
||||
},
|
||||
},
|
||||
{
|
||||
"id": "pink",
|
||||
"name": "Pink",
|
||||
"latte": {
|
||||
"m3primary": "#ea76cb",
|
||||
"m3primaryText": "#eff1f5",
|
||||
"m3primaryContainer": "#eb9bd7",
|
||||
"m3secondary": "#d9c7d5",
|
||||
"m3surfaceTint": "#eb9bd7",
|
||||
},
|
||||
"frappe": {
|
||||
"m3primary": "#f4b8e4",
|
||||
"m3primaryText": "#303446",
|
||||
"m3primaryContainer": "#b990b5",
|
||||
"m3secondary": "#996e9e",
|
||||
"m3surfaceTint": "#b990b5",
|
||||
},
|
||||
"macchiato": {
|
||||
"m3primary": "#f5bde6",
|
||||
"m3primaryText": "#24273a",
|
||||
"m3primaryContainer": "#b791b2",
|
||||
"m3secondary": "#95689a",
|
||||
"m3surfaceTint": "#b791b2",
|
||||
},
|
||||
"mocha": {
|
||||
"m3primary": "#f5c2e7",
|
||||
"m3primaryText": "#1e1e2e",
|
||||
"m3primaryContainer": "#b591b0",
|
||||
"m3secondary": "#966597",
|
||||
"m3surfaceTint": "#b591b0",
|
||||
},
|
||||
},
|
||||
{
|
||||
"id": "mauve",
|
||||
"name": "Mauve",
|
||||
"latte": {
|
||||
"m3primary": "#8839ef",
|
||||
"m3primaryText": "#eff1f5",
|
||||
"m3primaryContainer": "#a670f1",
|
||||
"m3secondary": "#c2b8d0",
|
||||
"m3surfaceTint": "#a670f1",
|
||||
},
|
||||
"frappe": {
|
||||
"m3primary": "#ca9ee6",
|
||||
"m3primaryText": "#303446",
|
||||
"m3primaryContainer": "#9c7eb6",
|
||||
"m3secondary": "#7d6799",
|
||||
"m3surfaceTint": "#9c7eb6",
|
||||
},
|
||||
"macchiato": {
|
||||
"m3primary": "#c6a0f6",
|
||||
"m3primaryText": "#24273a",
|
||||
"m3primaryContainer": "#967cbe",
|
||||
"m3secondary": "#766597",
|
||||
"m3surfaceTint": "#967cbe",
|
||||
},
|
||||
"mocha": {
|
||||
"m3primary": "#cba6f7",
|
||||
"m3primaryText": "#1e1e2e",
|
||||
"m3primaryContainer": "#977ebb",
|
||||
"m3secondary": "#756294",
|
||||
"m3surfaceTint": "#977ebb",
|
||||
},
|
||||
},
|
||||
{
|
||||
"id": "red",
|
||||
"name": "Red",
|
||||
"latte": {
|
||||
"m3primary": "#d20f39",
|
||||
"m3primaryText": "#eff1f5",
|
||||
"m3primaryContainer": "#da5371",
|
||||
"m3secondary": "#c0a0a8",
|
||||
"m3surfaceTint": "#da5371",
|
||||
},
|
||||
"frappe": {
|
||||
"m3primary": "#e78284",
|
||||
"m3primaryText": "#303446",
|
||||
"m3primaryContainer": "#b06a72",
|
||||
"m3secondary": "#8b5d66",
|
||||
"m3surfaceTint": "#b06a72",
|
||||
},
|
||||
"macchiato": {
|
||||
"m3primary": "#ed8796",
|
||||
"m3primaryText": "#24273a",
|
||||
"m3primaryContainer": "#b16b7a",
|
||||
"m3secondary": "#865a69",
|
||||
"m3surfaceTint": "#b16b7a",
|
||||
},
|
||||
"mocha": {
|
||||
"m3primary": "#f38ba8",
|
||||
"m3primaryText": "#1e1e2e",
|
||||
"m3primaryContainer": "#b46b84",
|
||||
"m3secondary": "#85596b",
|
||||
"m3surfaceTint": "#b46b84",
|
||||
},
|
||||
},
|
||||
{
|
||||
"id": "maroon",
|
||||
"name": "Maroon",
|
||||
"latte": {
|
||||
"m3primary": "#e64553",
|
||||
"m3primaryText": "#eff1f5",
|
||||
"m3primaryContainer": "#e87883",
|
||||
"m3secondary": "#cfb7ba",
|
||||
"m3surfaceTint": "#e87883",
|
||||
},
|
||||
"frappe": {
|
||||
"m3primary": "#ea999c",
|
||||
"m3primaryText": "#303446",
|
||||
"m3primaryContainer": "#b27a83",
|
||||
"m3secondary": "#92626f",
|
||||
"m3surfaceTint": "#b27a83",
|
||||
},
|
||||
"macchiato": {
|
||||
"m3primary": "#ee99a0",
|
||||
"m3primaryText": "#24273a",
|
||||
"m3primaryContainer": "#b27781",
|
||||
"m3secondary": "#8c5e6c",
|
||||
"m3surfaceTint": "#b27781",
|
||||
},
|
||||
"mocha": {
|
||||
"m3primary": "#eba0ac",
|
||||
"m3primaryText": "#1e1e2e",
|
||||
"m3primaryContainer": "#ae7987",
|
||||
"m3secondary": "#895b6c",
|
||||
"m3surfaceTint": "#ae7987",
|
||||
},
|
||||
},
|
||||
{
|
||||
"id": "peach",
|
||||
"name": "Peach",
|
||||
"latte": {
|
||||
"m3primary": "#fe640b",
|
||||
"m3primaryText": "#eff1f5",
|
||||
"m3primaryContainer": "#f98e51",
|
||||
"m3secondary": "#c9b7ad",
|
||||
"m3surfaceTint": "#f98e51",
|
||||
},
|
||||
"frappe": {
|
||||
"m3primary": "#ef9f76",
|
||||
"m3primaryText": "#303446",
|
||||
"m3primaryContainer": "#b67f68",
|
||||
"m3secondary": "#8f6a5f",
|
||||
"m3surfaceTint": "#b67f68",
|
||||
},
|
||||
"macchiato": {
|
||||
"m3primary": "#f5a97f",
|
||||
"m3primaryText": "#24273a",
|
||||
"m3primaryContainer": "#b7836a",
|
||||
"m3secondary": "#8c695e",
|
||||
"m3surfaceTint": "#b7836a",
|
||||
},
|
||||
"mocha": {
|
||||
"m3primary": "#fab387",
|
||||
"m3primaryText": "#1e1e2e",
|
||||
"m3primaryContainer": "#b8876d",
|
||||
"m3secondary": "#8b6a5d",
|
||||
"m3surfaceTint": "#b8876d",
|
||||
},
|
||||
},
|
||||
{
|
||||
"id": "yellow",
|
||||
"name": "Yellow",
|
||||
"latte": {
|
||||
"m3primary": "#df8e1d",
|
||||
"m3primaryText": "#eff1f5",
|
||||
"m3primaryContainer": "#e4ac5d",
|
||||
"m3secondary": "#c6baaa",
|
||||
"m3surfaceTint": "#e4ac5d",
|
||||
},
|
||||
"frappe": {
|
||||
"m3primary": "#e5c890",
|
||||
"m3primaryText": "#303446",
|
||||
"m3primaryContainer": "#af9b7a",
|
||||
"m3secondary": "#948062",
|
||||
"m3surfaceTint": "#af9b7a",
|
||||
},
|
||||
"macchiato": {
|
||||
"m3primary": "#eed49f",
|
||||
"m3primaryText": "#24273a",
|
||||
"m3primaryContainer": "#b2a181",
|
||||
"m3secondary": "#947e62",
|
||||
"m3surfaceTint": "#b2a181",
|
||||
},
|
||||
"mocha": {
|
||||
"m3primary": "#f9e2af",
|
||||
"m3primaryText": "#1e1e2e",
|
||||
"m3primaryContainer": "#b8a889",
|
||||
"m3secondary": "#978265",
|
||||
"m3surfaceTint": "#b8a889",
|
||||
},
|
||||
},
|
||||
{
|
||||
"id": "green",
|
||||
"name": "Green",
|
||||
"latte": {
|
||||
"m3primary": "#40a02b",
|
||||
"m3primaryText": "#eff1f5",
|
||||
"m3primaryContainer": "#74b867",
|
||||
"m3secondary": "#9fbd9b",
|
||||
"m3surfaceTint": "#74b867",
|
||||
},
|
||||
"frappe": {
|
||||
"m3primary": "#a6d189",
|
||||
"m3primaryText": "#303446",
|
||||
"m3primaryContainer": "#83a275",
|
||||
"m3secondary": "#648e5e",
|
||||
"m3surfaceTint": "#83a275",
|
||||
},
|
||||
"macchiato": {
|
||||
"m3primary": "#a6da95",
|
||||
"m3primaryText": "#24273a",
|
||||
"m3primaryContainer": "#80a57a",
|
||||
"m3secondary": "#5c8a61",
|
||||
"m3surfaceTint": "#80a57a",
|
||||
},
|
||||
"mocha": {
|
||||
"m3primary": "#a6e3a1",
|
||||
"m3primaryText": "#1e1e2e",
|
||||
"m3primaryContainer": "#7ea87f",
|
||||
"m3secondary": "#5b8964",
|
||||
"m3surfaceTint": "#7ea87f",
|
||||
},
|
||||
},
|
||||
{
|
||||
"id": "teal",
|
||||
"name": "Teal",
|
||||
"latte": {
|
||||
"m3primary": "#179299",
|
||||
"m3primaryText": "#eff1f5",
|
||||
"m3primaryContainer": "#57aeb4",
|
||||
"m3secondary": "#93b4b7",
|
||||
"m3surfaceTint": "#57aeb4",
|
||||
},
|
||||
"frappe": {
|
||||
"m3primary": "#81c8be",
|
||||
"m3primaryText": "#303446",
|
||||
"m3primaryContainer": "#699b9a",
|
||||
"m3secondary": "#588084",
|
||||
"m3surfaceTint": "#699b9a",
|
||||
},
|
||||
"macchiato": {
|
||||
"m3primary": "#8bd5ca",
|
||||
"m3primaryText": "#24273a",
|
||||
"m3primaryContainer": "#6da29f",
|
||||
"m3secondary": "#577e83",
|
||||
"m3surfaceTint": "#6da29f",
|
||||
},
|
||||
"mocha": {
|
||||
"m3primary": "#94e2d5",
|
||||
"m3primaryText": "#1e1e2e",
|
||||
"m3primaryContainer": "#71a8a4",
|
||||
"m3secondary": "#588284",
|
||||
"m3surfaceTint": "#71a8a4",
|
||||
},
|
||||
},
|
||||
{
|
||||
"id": "sky",
|
||||
"name": "Sky",
|
||||
"latte": {
|
||||
"m3primary": "#04a5e5",
|
||||
"m3primaryText": "#eff1f5",
|
||||
"m3primaryContainer": "#4abcea",
|
||||
"m3secondary": "#a4b9c2",
|
||||
"m3surfaceTint": "#4abcea",
|
||||
},
|
||||
"frappe": {
|
||||
"m3primary": "#99d1db",
|
||||
"m3primaryText": "#303446",
|
||||
"m3primaryContainer": "#79a2af",
|
||||
"m3secondary": "#628494",
|
||||
"m3surfaceTint": "#79a2af",
|
||||
},
|
||||
"macchiato": {
|
||||
"m3primary": "#91d7e3",
|
||||
"m3primaryText": "#24273a",
|
||||
"m3primaryContainer": "#71a3b0",
|
||||
"m3secondary": "#5e7e8c",
|
||||
"m3surfaceTint": "#71a3b0",
|
||||
},
|
||||
"mocha": {
|
||||
"m3primary": "#89dceb",
|
||||
"m3primaryText": "#1e1e2e",
|
||||
"m3primaryContainer": "#69a3b3",
|
||||
"m3secondary": "#5a7b88",
|
||||
"m3surfaceTint": "#69a3b3",
|
||||
},
|
||||
},
|
||||
{
|
||||
"id": "sapphire",
|
||||
"name": "Sapphire",
|
||||
"latte": {
|
||||
"m3primary": "#209fb5",
|
||||
"m3primaryText": "#eff1f5",
|
||||
"m3primaryContainer": "#5db8c8",
|
||||
"m3secondary": "#9eb9be",
|
||||
"m3surfaceTint": "#5db8c8",
|
||||
},
|
||||
"frappe": {
|
||||
"m3primary": "#85c1dc",
|
||||
"m3primaryText": "#303446",
|
||||
"m3primaryContainer": "#6b96af",
|
||||
"m3secondary": "#5e7b8e",
|
||||
"m3surfaceTint": "#6b96af",
|
||||
},
|
||||
"macchiato": {
|
||||
"m3primary": "#7dc4e4",
|
||||
"m3primaryText": "#24273a",
|
||||
"m3primaryContainer": "#6396b1",
|
||||
"m3secondary": "#5a7486",
|
||||
"m3surfaceTint": "#6396b1",
|
||||
},
|
||||
"mocha": {
|
||||
"m3primary": "#74c7ec",
|
||||
"m3primaryText": "#1e1e2e",
|
||||
"m3primaryContainer": "#5a95b4",
|
||||
"m3secondary": "#567080",
|
||||
"m3surfaceTint": "#5a95b4",
|
||||
},
|
||||
},
|
||||
{
|
||||
"id": "blue",
|
||||
"name": "Blue",
|
||||
"latte": {
|
||||
"m3primary": "#1e66f5",
|
||||
"m3primaryText": "#eff1f5",
|
||||
"m3primaryContainer": "#5c90f5",
|
||||
"m3secondary": "#b1bacb",
|
||||
"m3surfaceTint": "#5c90f5",
|
||||
},
|
||||
"frappe": {
|
||||
"m3primary": "#8caaee",
|
||||
"m3primaryText": "#303446",
|
||||
"m3primaryContainer": "#7086bc",
|
||||
"m3secondary": "#637195",
|
||||
"m3surfaceTint": "#7086bc",
|
||||
},
|
||||
"macchiato": {
|
||||
"m3primary": "#8aadf4",
|
||||
"m3primaryText": "#24273a",
|
||||
"m3primaryContainer": "#6c85bc",
|
||||
"m3secondary": "#5f6d8f",
|
||||
"m3surfaceTint": "#6c85bc",
|
||||
},
|
||||
"mocha": {
|
||||
"m3primary": "#89b4fa",
|
||||
"m3primaryText": "#1e1e2e",
|
||||
"m3primaryContainer": "#6987bd",
|
||||
"m3secondary": "#5d6c8b",
|
||||
"m3surfaceTint": "#6987bd",
|
||||
},
|
||||
},
|
||||
{
|
||||
"id": "lavender",
|
||||
"name": "Lavender",
|
||||
"latte": {
|
||||
"m3primary": "#7287fd",
|
||||
"m3primaryText": "#eff1f5",
|
||||
"m3primaryContainer": "#97a7fb",
|
||||
"m3secondary": "#cdcfdd",
|
||||
"m3surfaceTint": "#97a7fb",
|
||||
},
|
||||
"frappe": {
|
||||
"m3primary": "#babbf1",
|
||||
"m3primaryText": "#303446",
|
||||
"m3primaryContainer": "#9192be",
|
||||
"m3secondary": "#7175a1",
|
||||
"m3surfaceTint": "#9192be",
|
||||
},
|
||||
"macchiato": {
|
||||
"m3primary": "#b7bdf8",
|
||||
"m3primaryText": "#24273a",
|
||||
"m3primaryContainer": "#8b91bf",
|
||||
"m3secondary": "#6b709d",
|
||||
"m3surfaceTint": "#8b91bf",
|
||||
},
|
||||
"mocha": {
|
||||
"m3primary": "#b4befe",
|
||||
"m3primaryText": "#1e1e2e",
|
||||
"m3primaryContainer": "#878ec0",
|
||||
"m3secondary": "#676d99",
|
||||
"m3surfaceTint": "#878ec0",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,62 +1,13 @@
|
||||
from __future__ import annotations
|
||||
# import json
|
||||
# import typer
|
||||
# from zshell.assets.schemes.catppuccin import catppuccin
|
||||
#
|
||||
# app = typer.Typer()
|
||||
#
|
||||
# SCHEMES = catppuccin.variants.flavors
|
||||
#
|
||||
#
|
||||
# @app.command()
|
||||
# def set():
|
||||
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
import typer
|
||||
|
||||
from zshell.utils.presets import build_palette, list_presets, load_preset_file
|
||||
|
||||
app = typer.Typer()
|
||||
OUTPUT = Path(os.getenv("HOME", "")) / ".local/state/zshell/scheme.json"
|
||||
|
||||
|
||||
def _split_preset(value: str) -> tuple[str, str | None, str | None]:
|
||||
parts = value.split(":")
|
||||
if len(parts) == 1:
|
||||
return parts[0], None, None
|
||||
if len(parts) == 3:
|
||||
return parts[0], parts[1], parts[2]
|
||||
raise typer.BadParameter("Preset must be <name> or <name>:<flavor>:<accent>")
|
||||
|
||||
|
||||
@app.command()
|
||||
def list():
|
||||
for preset_name in list_presets():
|
||||
data = load_preset_file(preset_name)
|
||||
flavors = [item["id"] for item in data["variants"]["flavors"]]
|
||||
accents = [item["id"] for item in data["variants"]["accents"]]
|
||||
for flavor in flavors:
|
||||
for accent in accents:
|
||||
typer.echo(f"{preset_name}:{flavor}:{accent}")
|
||||
|
||||
|
||||
@app.command()
|
||||
def set(
|
||||
preset: str = typer.Argument(
|
||||
..., help="Preset in form <name> or <name>:<flavor>:<accent>"
|
||||
),
|
||||
mode: str = typer.Option("dark", help="Mode for preset colors."),
|
||||
):
|
||||
name, flavor, accent = _split_preset(preset)
|
||||
data = load_preset_file(name)
|
||||
|
||||
if flavor is None or accent is None:
|
||||
defaults = data["variants"]["defaults"][mode]
|
||||
flavor = defaults["flavor"]
|
||||
accent = defaults["accent"]
|
||||
|
||||
palette = build_palette(data, flavor, accent, mode)
|
||||
out = {
|
||||
"name": palette.name,
|
||||
"flavor": f"{palette.flavor}:{palette.accent}",
|
||||
"mode": palette.mode,
|
||||
"variant": name,
|
||||
"colors": dict(palette.colors),
|
||||
"seed": int(palette.colors["primary"].lstrip("#"), 16),
|
||||
}
|
||||
|
||||
OUTPUT.parent.mkdir(parents=True, exist_ok=True)
|
||||
OUTPUT.write_text(json.dumps(out, indent=4), encoding="utf-8")
|
||||
typer.echo(f"applied: {name}:{flavor}:{accent}")
|
||||
# TODO: Currently unsused
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user