4 Commits

118 changed files with 3432 additions and 5728 deletions
-42
View File
@@ -1,42 +0,0 @@
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
-34
View File
@@ -1,34 +0,0 @@
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 .
-72
View File
@@ -1,72 +0,0 @@
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
+1 -2
View File
@@ -1,4 +1,4 @@
**/__pycache__/ ./__pycache__/
./result/ ./result/
.pyre/ .pyre/
.cache/ .cache/
@@ -12,4 +12,3 @@ pkg/
uv.lock uv.lock
.qtcreator/ .qtcreator/
dist/ dist/
**/target/
-3
View File
@@ -1,3 +0,0 @@
.venv/
scripts/fzf.js
scripts/fuzzysort.js
-13
View File
@@ -1,13 +0,0 @@
{
"semi": true,
"singleQuote": false,
"jsxSingleQuote": false,
"tabWidth": 4,
"printWidth": 80,
"trailingComma": "es5",
"bracketSpacing": true,
"bracketSameLine": false,
"arrowParens": "always",
"endOfLine": "lf",
"proseWrap": "preserve"
}
+2 -2
View File
@@ -2,7 +2,7 @@ import QtQuick
import qs.Config import qs.Config
NumberAnimation { NumberAnimation {
duration: Appearance.anim.durations.normal duration: MaterialEasing.standardTime
easing.bezierCurve: Appearance.anim.curves.standard easing.bezierCurve: MaterialEasing.standard
easing.type: Easing.BezierSpline easing.type: Easing.BezierSpline
} }
-33
View File
@@ -1,33 +0,0 @@
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;
}
}
}
+2 -1
View File
@@ -41,11 +41,12 @@ CustomRect {
color: type === IconButton.Text ? "transparent" : disabled ? disabledColour : internalChecked ? activeColour : inactiveColour color: type === IconButton.Text ? "transparent" : disabled ? disabledColour : internalChecked ? activeColour : inactiveColour
implicitHeight: label.implicitHeight + padding * 2 implicitHeight: label.implicitHeight + padding * 2
implicitWidth: implicitHeight implicitWidth: implicitHeight
radius: internalChecked ? 6 : (implicitHeight / 2 * Math.min(1, 1)) * Appearance.rounding.scale radius: internalChecked ? 6 : implicitHeight / 2 * Math.min(1, 1)
Behavior on radius { Behavior on radius {
Anim { Anim {
id: radiusAnim id: radiusAnim
} }
} }
-4
View File
@@ -7,8 +7,4 @@ JsonObject {
property real alignX: 0.5 property real alignX: 0.5
property real alignY: 0.5 property real alignY: 0.5
property real zoom: 1.0 property real zoom: 1.0
property real sourceClipX: 0
property real sourceClipY: 0
property real sourceClipW: 0
property real sourceClipH: 0
} }
+12 -1
View File
@@ -8,6 +8,10 @@ JsonObject {
id: "workspaces", id: "workspaces",
enabled: true enabled: true
}, },
{
id: "audio",
enabled: true
},
{ {
id: "media", id: "media",
enabled: true enabled: true
@@ -20,6 +24,10 @@ JsonObject {
id: "updates", id: "updates",
enabled: true enabled: true
}, },
{
id: "dash",
enabled: true
},
{ {
id: "spacer", id: "spacer",
enabled: true enabled: true
@@ -40,6 +48,10 @@ JsonObject {
id: "tray", id: "tray",
enabled: true enabled: true
}, },
{
id: "upower",
enabled: false
},
{ {
id: "network", id: "network",
enabled: false enabled: false
@@ -58,7 +70,6 @@ JsonObject {
property Popouts popouts: Popouts { property Popouts popouts: Popouts {
} }
property int rounding: 8 property int rounding: 8
property int smoothing: 32
component Popouts: JsonObject { component Popouts: JsonObject {
property bool activeWindow: true property bool activeWindow: true
+1 -26
View File
@@ -23,7 +23,6 @@ Singleton {
property alias osd: adapter.osd property alias osd: adapter.osd
property alias overview: adapter.overview property alias overview: adapter.overview
property bool recentlySaved: false property bool recentlySaved: false
property alias screenshot: adapter.screenshot
property alias services: adapter.services property alias services: adapter.services
property alias sidebar: adapter.sidebar property alias sidebar: adapter.sidebar
property alias utilities: adapter.utilities property alias utilities: adapter.utilities
@@ -83,10 +82,6 @@ Singleton {
wallFadeDuration: background.wallFadeDuration, wallFadeDuration: background.wallFadeDuration,
enabled: background.enabled, enabled: background.enabled,
alignX: background.alignX, alignX: background.alignX,
sourceClipX: background.sourceClipX,
sourceClipY: background.sourceClipY,
sourceClipW: background.sourceClipW,
sourceClipH: background.sourceClipH,
alignY: background.alignY, alignY: background.alignY,
zoom: background.zoom zoom: background.zoom
}; };
@@ -98,7 +93,6 @@ Singleton {
hideWhenNotif: barConfig.hideWhenNotif, hideWhenNotif: barConfig.hideWhenNotif,
rounding: barConfig.rounding, rounding: barConfig.rounding,
border: barConfig.border, border: barConfig.border,
smoothing: barConfig.smoothing,
height: barConfig.height, height: barConfig.height,
popouts: { popouts: {
tray: barConfig.popouts.tray, tray: barConfig.popouts.tray,
@@ -134,8 +128,7 @@ Singleton {
background: serializeBackground(), background: serializeBackground(),
launcher: serializeLauncher(), launcher: serializeLauncher(),
colors: serializeColors(), colors: serializeColors(),
dock: serializeDock(), dock: serializeDock()
screenshot: serializeScreenshot()
}; };
} }
@@ -187,7 +180,6 @@ Singleton {
logo: general.logo, logo: general.logo,
wallpaperPath: general.wallpaperPath, wallpaperPath: general.wallpaperPath,
desktopIcons: general.desktopIcons, desktopIcons: general.desktopIcons,
dateFormat: general.dateFormat,
color: { color: {
mode: general.color.mode, mode: general.color.mode,
smart: general.color.smart, smart: general.color.smart,
@@ -241,7 +233,6 @@ Singleton {
return { return {
recolorLogo: lock.recolorLogo, recolorLogo: lock.recolorLogo,
enableFprint: lock.enableFprint, enableFprint: lock.enableFprint,
showNotifContent: lock.showNotifContent,
maxFprintTries: lock.maxFprintTries, maxFprintTries: lock.maxFprintTries,
blurAmount: lock.blurAmount, blurAmount: lock.blurAmount,
sizes: { sizes: {
@@ -283,20 +274,6 @@ 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 { function serializeServices(): var {
return { return {
weatherLocation: services.weatherLocation, weatherLocation: services.weatherLocation,
@@ -452,8 +429,6 @@ Singleton {
} }
property Overview overview: Overview { property Overview overview: Overview {
} }
property Screenshot screenshot: Screenshot {
}
property Services services: Services { property Services services: Services {
} }
property SidebarConfig sidebar: SidebarConfig { property SidebarConfig sidebar: SidebarConfig {
+4 -35
View File
@@ -3,7 +3,6 @@ pragma ComponentBehavior: Bound
import Quickshell import Quickshell
import Quickshell.Io import Quickshell.Io
import Quickshell.Hyprland
import QtQuick import QtQuick
import ZShell import ZShell
import qs.Helpers import qs.Helpers
@@ -80,32 +79,10 @@ Singleton {
} }
function reloadHyprRules(): void { function reloadHyprRules(): void {
const blur = transparency.enabled ? 1 : 0; const barStr = "keyword layerrule %1 %2, match:namespace ZShell-Bar";
const alpha = transparency.base - 0.03; 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)]);
const rules = ` Hypr.extras.batchMessage([authStr.arg("blur").arg(transparency.enabled ? 1 : 0), authStr.arg("ignore_alpha").arg(transparency.base - 0.03)]);
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 { function setMode(mode: string): void {
@@ -116,14 +93,6 @@ Singleton {
Component.onCompleted: debounceTimer.triggered() Component.onCompleted: debounceTimer.triggered()
Connections {
function onUsingLuaChanged(): void {
root.reloadHyprRules();
}
target: Hyprland
}
Connections { Connections {
function onConfigReloaded(): void { function onConfigReloaded(): void {
root.reloadHyprRules(); root.reloadHyprRules();
-1
View File
@@ -6,7 +6,6 @@ JsonObject {
} }
property Color color: Color { property Color color: Color {
} }
property string dateFormat: "ddd d MMM - hh:mm:ss"
property bool desktopIcons: false property bool desktopIcons: false
property Idle idle: Idle { property Idle idle: Idle {
} }
-1
View File
@@ -5,7 +5,6 @@ JsonObject {
property bool enableFprint: true property bool enableFprint: true
property int maxFprintTries: 3 property int maxFprintTries: 3
property bool recolorLogo: false property bool recolorLogo: false
property bool showNotifContent: false
property Sizes sizes: Sizes { property Sizes sizes: Sizes {
} }
-13
View File
@@ -1,13 +0,0 @@
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
}
+175 -24
View File
@@ -3,33 +3,184 @@ import QtQuick
Canvas { Canvas {
id: root 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 color penColor: "white"
property real penWidth: 4 property real penWidth: 4
property var points: [] property var pendingSegments: []
property bool strokeActive: false
property var strokes: []
function clear(): void { function appendPoint(x, y) {
var ctx = getContext('2d'); if (!strokeActive || strokes.length === 0)
root.points = [];
ctx.reset();
root.requestPaint();
}
renderStrategy: Canvas.Cooperative
onPaint: {
if (points.length < 2)
return; return;
var ctx = root.getContext('2d'); const dx = x - lastPoint.x;
ctx.save(); const dy = y - lastPoint.y;
ctx.lineWidth = root.penWidth;
ctx.strokeStyle = root.penColor; if ((dx * dx + dy * dy) < (minPointDistance * minPointDistance))
ctx.lineCap = "round"; return;
ctx.beginPath(); const x1 = lastPoint.x;
ctx.moveTo(points[0].x, points[0].y); const y1 = lastPoint.y;
for (var i = 1; i < points.length; i++) const x2 = x;
ctx.lineTo(points[i].x, points[i].y); const y2 = y;
ctx.stroke();
points = points.slice(points.length - 2); strokes[strokes.length - 1].push(Qt.point(x2, y2));
ctx.restore();
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");
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 = [];
}
onWidthChanged: requestFullRepaint()
} }
+5 -7
View File
@@ -30,10 +30,8 @@ CustomMouseArea {
const x = event.x; const x = event.x;
const y = event.y; const y = event.y;
if (root.visibilities.isDrawing && (event.buttons & Qt.LeftButton)) { if (event.buttons & Qt.LeftButton)
root.drawing.points.push(Qt.point(x, y)); root.drawing.appendPoint(x, y);
root.drawing.requestPaint();
}
if (root.inLeftPanel(root.popout, x, y)) { if (root.inLeftPanel(root.popout, x, y)) {
root.z = -2; root.z = -2;
@@ -46,8 +44,7 @@ CustomMouseArea {
if (root.visibilities.isDrawing && (event.buttons & Qt.LeftButton)) { if (root.visibilities.isDrawing && (event.buttons & Qt.LeftButton)) {
root.panels.drawing.expanded = false; root.panels.drawing.expanded = false;
root.drawing.points.push(Qt.point(x, y)); root.drawing.beginStroke(x, y);
root.drawing.requestPaint();
return; return;
} }
@@ -55,6 +52,7 @@ CustomMouseArea {
root.drawing.clear(); root.drawing.clear();
} }
onReleased: { onReleased: {
root.drawing.points = []; if (root.visibilities.isDrawing)
root.drawing.endStroke();
} }
} }
-15
View File
@@ -12,37 +12,22 @@ Scope {
required property ShellScreen screen required property ShellScreen screen
ExclusionZone { ExclusionZone {
id: top
anchors.top: true anchors.top: true
exclusiveZone: root.bar.exclusiveZone exclusiveZone: root.bar.exclusiveZone
} }
ExclusionZone { ExclusionZone {
id: left
anchors.left: true anchors.left: true
} }
ExclusionZone { ExclusionZone {
id: right
anchors.right: true anchors.right: true
} }
ExclusionZone { ExclusionZone {
id: bottom
anchors.bottom: true 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 { component ExclusionZone: CustomWindow {
exclusiveZone: Config.barConfig.border exclusiveZone: Config.barConfig.border
implicitHeight: 1 implicitHeight: 1
+1 -1
View File
@@ -78,7 +78,7 @@ CustomMouseArea {
const dragY = y - dragStart.y; const dragY = y - dragStart.y;
if (root.visibilities.isDrawing && !root.inLeftPanel(root.panels.drawing, x, 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; root.panels.drawing.expanded = false;
} }
-86
View File
@@ -13,7 +13,6 @@ import qs.Modules.Resources as Resources
import qs.Modules.Settings as Settings import qs.Modules.Settings as Settings
import qs.Modules.Drawing as Drawing import qs.Modules.Drawing as Drawing
import qs.Modules.Dock as Dock import qs.Modules.Dock as Dock
import qs.Modules.SysTray.Popouts as SysPopouts
import qs.Config import qs.Config
Item { Item {
@@ -35,10 +34,8 @@ Item {
readonly property alias resourcesWrapper: resourcesWrapper readonly property alias resourcesWrapper: resourcesWrapper
required property ShellScreen screen required property ShellScreen screen
readonly property alias settings: settings readonly property alias settings: settings
readonly property alias settingsWrapper: settingsWrapper
readonly property alias sidebar: sidebar readonly property alias sidebar: sidebar
readonly property alias toasts: toasts readonly property alias toasts: toasts
readonly property alias traySubmenus: traySubmenus
readonly property alias utilities: utilities readonly property alias utilities: utilities
required property PersistentProperties visibilities required property PersistentProperties visibilities
@@ -95,79 +92,6 @@ Item {
visibilities: root.visibilities 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 { Modules.ClipWrapper {
id: popouts id: popouts
@@ -252,15 +176,6 @@ Item {
visibilities: root.visibilities visibilities: root.visibilities
} }
Item {
id: settingsWrapper
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top
clip: true
implicitHeight: settings.implicitHeight * (1 - settings.offsetScale)
implicitWidth: settings.implicitWidth
Settings.Wrapper { Settings.Wrapper {
id: settings id: settings
@@ -271,7 +186,6 @@ Item {
screen: root.screen screen: root.screen
visibilities: root.visibilities visibilities: root.visibilities
} }
}
Dock.Wrapper { Dock.Wrapper {
id: dock id: dock
+19 -69
View File
@@ -3,7 +3,6 @@ pragma ComponentBehavior: Bound
import QtQuick import QtQuick
import QtQuick.Effects import QtQuick.Effects
import Quickshell import Quickshell
import Quickshell.Io
import Quickshell.Wayland import Quickshell.Wayland
import Quickshell.Hyprland import Quickshell.Hyprland
import ZShell.Blobs import ZShell.Blobs
@@ -64,7 +63,7 @@ Variants {
height: win.height - bar.implicitHeight - Config.barConfig.border height: win.height - bar.implicitHeight - Config.barConfig.border
intersection: Intersection.Xor intersection: Intersection.Xor
regions: [...popoutRegions.instances, ...subMenuRegions.instances] regions: popoutRegions.instances
width: win.width - Config.barConfig.border * 2 width: win.width - Config.barConfig.border * 2
x: Config.barConfig.border x: Config.barConfig.border
y: bar.implicitHeight y: bar.implicitHeight
@@ -93,22 +92,6 @@ 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 { HyprlandFocusGrab {
id: focusGrab id: focusGrab
@@ -144,14 +127,6 @@ Variants {
Component.onCompleted: Visibilities.load(scope.modelData, this) Component.onCompleted: Visibilities.load(scope.modelData, this)
} }
IpcHandler {
function toggleLauncher(fix: string): void {
visibilities.launcher = !visibilities.launcher;
}
target: "visibilities"
}
Binding { Binding {
property: "bar" property: "bar"
target: visibilities target: visibilities
@@ -174,7 +149,6 @@ Variants {
id: blobGroup id: blobGroup
color: DynamicColors.palette.m3surface color: DynamicColors.palette.m3surface
smoothing: Config.barConfig.smoothing
Behavior on color { Behavior on color {
CAnim { CAnim {
@@ -196,34 +170,28 @@ Variants {
PanelBg { PanelBg {
id: dashBg id: dashBg
property real extraHeight: 0.2 deformAmount: 0.08 * Config.appearance.deform.scale
implicitHeight: panels.dashboard.height
deformAmount: 0.06
implicitHeight: panels.dashboard.height * (1 + extraHeight)
implicitWidth: panels.dashboard.width implicitWidth: panels.dashboard.width
panel: panels.dashboardWrapper panel: panels.dashboard
radius: Appearance.rounding.normal radius: Appearance.rounding.normal
x: panels.dashboardWrapper.x + panels.dashboard.x + Config.barConfig.border x: panels.dashboardWrapper.x + panels.dashboard.x + Config.barConfig.border
y: panels.dashboardWrapper.y + panels.dashboard.y + bar.implicitHeight - panels.dashboard.height * extraHeight y: panels.dashboardWrapper.y + panels.dashboard.y + bar.implicitHeight
} }
PanelBg { PanelBg {
id: launcherBg id: launcherBg
property real extraHeight: 0.2 deformAmount: 0.08 * Config.appearance.deform.scale
deformAmount: 0.06
implicitHeight: panels.launcher.height * (1 + extraHeight)
panel: panels.launcher panel: panels.launcher
radius: Appearance.rounding.smallest + 5 radius: Appearance.rounding.smallest + 5
y: panels.launcher.y + bar.implicitHeight
} }
PanelBg { PanelBg {
id: sidebarBg id: sidebarBg
bottomLeftRadius: 0 bottomLeftRadius: 0
deformAmount: 0.04 deformAmount: 0.08 * Config.appearance.deform.scale
exclude: panels.sidebar.offsetScale > 0.08 ? [] : [utilsBg] exclude: panels.sidebar.offsetScale > 0.08 ? [] : [utilsBg]
implicitHeight: panel.height * (1 / rawDeformMatrix.m22) + 2 implicitHeight: panel.height * (1 / rawDeformMatrix.m22) + 2
panel: panels.sidebar panel: panels.sidebar
@@ -232,10 +200,10 @@ Variants {
PanelBg { PanelBg {
id: osdBg id: osdBg
deformAmount: 0.1 deformAmount: 0.1 * Config.appearance.deform.scale
implicitHeight: panels.osd.height implicitHeight: panels.osd.height
implicitWidth: panels.osd.width implicitWidth: panels.osd.width
panel: panels.osdWrapper panel: panels.osd
radius: 20 radius: 20
x: panels.osdWrapper.x + panels.osd.x + Config.barConfig.border x: panels.osdWrapper.x + panels.osd.x + Config.barConfig.border
y: panels.osdWrapper.y + panels.osd.y + bar.implicitHeight y: panels.osdWrapper.y + panels.osd.y + bar.implicitHeight
@@ -250,7 +218,7 @@ Variants {
PanelBg { PanelBg {
id: utilsBg id: utilsBg
deformAmount: panels.sidebar.visible ? (0.1) : (0.1) deformAmount: panels.sidebar.visible ? (0.1 * Config.appearance.deform.scale) : (0.1 * Config.appearance.deform.scale)
exclude: panels.sidebar.offsetScale > 0.08 ? [] : [sidebarBg] exclude: panels.sidebar.offsetScale > 0.08 ? [] : [sidebarBg]
panel: panels.utilities panel: panels.utilities
topLeftRadius: 0 topLeftRadius: 0
@@ -261,11 +229,11 @@ Variants {
property real extraHeight: panels.popouts.isDetached ? 0 : 0.2 property real extraHeight: panels.popouts.isDetached ? 0 : 0.2
deformAmount: panels.popouts.isDetached ? 0.05 : panels.popouts.hasCurrent ? 0.15 : 0.1 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
implicitHeight: panels.popouts.height * (1 + extraHeight) implicitHeight: panels.popouts.height * (1 + extraHeight)
implicitWidth: panels.popouts.width implicitWidth: panels.popouts.width
panel: panels.popoutsWrapper panel: panels.popouts
radius: (panels.popouts.currentName.startsWith("audio") || panels.popouts.currentName.startsWith("updates")) ? Appearance.rounding.normal : 20 * Appearance.rounding.scale radius: (panels.popouts.currentName.startsWith("audio") || panels.popouts.currentName.startsWith("updates")) ? Appearance.rounding.normal : Appearance.rounding.smallest
x: panels.popoutsWrapper.x + panels.popouts.x + Config.barConfig.border x: panels.popoutsWrapper.x + panels.popouts.x + Config.barConfig.border
y: panels.popoutsWrapper.y + panels.popouts.y + bar.implicitHeight - panels.popouts.height * extraHeight y: panels.popoutsWrapper.y + panels.popouts.y + bar.implicitHeight - panels.popouts.height * extraHeight
@@ -278,10 +246,10 @@ Variants {
PanelBg { PanelBg {
id: resourcesBg id: resourcesBg
deformAmount: 0.05 deformAmount: 0.08 * Config.appearance.deform.scale
implicitHeight: panels.resources.height implicitHeight: panels.resources.height
implicitWidth: panels.resources.width implicitWidth: panels.resources.width
panel: panels.resourcesWrapper panel: panels.resources
radius: Appearance.rounding.normal radius: Appearance.rounding.normal
x: panels.resourcesWrapper.x + panels.resources.x + Config.barConfig.border x: panels.resourcesWrapper.x + panels.resources.x + Config.barConfig.border
y: panels.resourcesWrapper.y + panels.resources.y + bar.implicitHeight y: panels.resourcesWrapper.y + panels.resources.y + bar.implicitHeight
@@ -290,23 +258,17 @@ Variants {
PanelBg { PanelBg {
id: settingsBg id: settingsBg
property real extraHeight: 0.2 deformAmount: 0.08 * Config.appearance.deform.scale
deformAmount: 0.03
implicitHeight: panels.settings.height * (1 + extraHeight)
implicitWidth: panels.settings.width
panel: panels.settings panel: panels.settings
radius: Appearance.rounding.large radius: Appearance.rounding.large
topLeftRadius: Appearance.rounding.large + Appearance.padding.smaller topLeftRadius: Appearance.rounding.large + Appearance.padding.smaller
topRightRadius: 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 { PanelBg {
id: dockBg id: dockBg
deformAmount: 0.08 deformAmount: 0.08 * Config.appearance.deform.scale
panel: panels.dock panel: panels.dock
radius: Appearance.rounding.normal radius: Appearance.rounding.normal
} }
@@ -314,22 +276,10 @@ Variants {
PanelBg { PanelBg {
id: drawingBg id: drawingBg
deformAmount: 0.08 deformAmount: 0.08 * Config.appearance.deform.scale
panel: panels.drawing panel: panels.drawing
radius: Appearance.rounding.normal 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 { Drawing {
@@ -421,7 +371,7 @@ Variants {
property real deformAmount: 0.15 property real deformAmount: 0.15
required property Item panel required property Item panel
deformScale: (deformAmount * Config.appearance.deform.scale) / 10000 deformScale: deformAmount / 10000
group: blobGroup group: blobGroup
implicitHeight: panel.height implicitHeight: panel.height
implicitWidth: panel.width implicitWidth: panel.width
+1
View File
@@ -346,6 +346,7 @@ Singleton {
stdout: StdioCollector { stdout: StdioCollector {
onStreamFinished: { onStreamFinished: {
console.log("this is running");
if (root.gpuType === "GENERIC") { if (root.gpuType === "GENERIC") {
const percs = text.trim().split("\n"); const percs = text.trim().split("\n");
const sum = percs.reduce((acc, d) => acc + parseInt(d, 10), 0); const sum = percs.reduce((acc, d) => acc + parseInt(d, 10), 0);
+44 -20
View File
@@ -7,30 +7,23 @@ import QtQuick
Singleton { Singleton {
id: root id: root
readonly property string defaultUserFile: "/etc/zshell-greeter/default-user" // The list of users that can log in graphically
property int selectedIndex: 0 // Each user object has: username, uid, home, shell, gecos (full name), face (avatar path)
readonly property var selectedUser: selectedIndex >= 0 && selectedIndex < users.length ? users[selectedIndex] : null
readonly property string selectedUsername: selectedUser ? selectedUser.username : ""
property var users: [] property var users: []
function saveDefaultUser(): void { // The currently selected user index
if (selectedUser) { property int selectedIndex: 0
defaultUserStorage.setText(selectedUser.username);
}
}
function selectNext(): void { // The currently selected user object (or null if none)
if (users.length === 0) readonly property var selectedUser: selectedIndex >= 0 && selectedIndex < users.length ? users[selectedIndex] : null
return;
selectedIndex = (selectedIndex + 1) % users.length;
}
function selectPrevious(): void { // Convenience property for the selected username
if (users.length === 0) readonly property string selectedUsername: selectedUser ? selectedUser.username : ""
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 { function selectUser(username: string): bool {
for (let i = 0; i < users.length; i++) { for (let i = 0; i < users.length; i++) {
if (users[i].username === username) { if (users[i].username === username) {
@@ -41,6 +34,28 @@ Singleton {
return false; 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 { Process {
id: userLister id: userLister
@@ -52,10 +67,13 @@ Singleton {
try { try {
root.users = JSON.parse(text); root.users = JSON.parse(text);
// If we have users and no selection yet, try to select the default user
if (root.users.length > 0) { if (root.users.length > 0) {
// Try to load the default user
if (defaultUserStorage.loaded) { if (defaultUserStorage.loaded) {
const defaultUsername = defaultUserStorage.text().trim(); const defaultUsername = defaultUserStorage.text().trim();
if (defaultUsername && !root.selectUser(defaultUsername)) { if (defaultUsername && !root.selectUser(defaultUsername)) {
// Default user not found, select first user
root.selectedIndex = 0; root.selectedIndex = 0;
} }
} else { } else {
@@ -69,14 +87,15 @@ Singleton {
} }
} }
// FileView for persisting the default user
FileView { FileView {
id: defaultUserStorage id: defaultUserStorage
path: root.defaultUserFile path: root.defaultUserFile
preload: true preload: true
onLoadFailed: {}
onLoaded: { onLoaded: {
// If users are already loaded, try to select the default user
if (root.users.length > 0) { if (root.users.length > 0) {
const defaultUsername = text().trim(); const defaultUsername = text().trim();
if (defaultUsername) { if (defaultUsername) {
@@ -84,5 +103,10 @@ 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");
}
} }
} }
-72
View File
@@ -1,72 +0,0 @@
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"
}
}
+11
View File
@@ -0,0 +1,11 @@
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
}
+1 -2
View File
@@ -66,8 +66,7 @@ MouseArea {
function save(): void { function save(): void {
const tmpfile = Qt.resolvedUrl(`/tmp/zshell-picker-${Quickshell.processId}-${Date.now()}.png`); const tmpfile = Qt.resolvedUrl(`/tmp/zshell-picker-${Quickshell.processId}-${Date.now()}.png`);
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(["swappy", "-f", path]));
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(); closeAnim.start();
} }
+2 -7
View File
@@ -1,23 +1,18 @@
pragma Singleton pragma Singleton
import Quickshell import Quickshell
import QtQuick
import qs.Config
Singleton { Singleton {
id: root readonly property string amPmStr: timeComponents[2] ?? ""
readonly property date date: clock.date readonly property date date: clock.date
readonly property string dateStr: format(Config.general.dateFormat)
property alias enabled: clock.enabled property alias enabled: clock.enabled
readonly property string hourStr: timeComponents[0] ?? "" readonly property string hourStr: timeComponents[0] ?? ""
readonly property int hours: clock.hours readonly property int hours: clock.hours
readonly property string minuteStr: timeComponents[1] ?? "" readonly property string minuteStr: timeComponents[1] ?? ""
readonly property int minutes: clock.minutes readonly property int minutes: clock.minutes
readonly property string secondStr: timeComponents[2] ?? ""
readonly property int seconds: clock.seconds readonly property int seconds: clock.seconds
readonly property list<string> timeComponents: timeStr.split(":") readonly property list<string> timeComponents: timeStr.split(":")
readonly property string timeStr: format("hh:mm:ss") readonly property string timeStr: format("hh:mm")
function format(fmt: string): string { function format(fmt: string): string {
return Qt.formatDateTime(clock.date, fmt); return Qt.formatDateTime(clock.date, fmt);
-79
View File
@@ -11,13 +11,10 @@ Singleton {
id: root id: root
property int availableUpdates: 0 property int availableUpdates: 0
property string cmd: ""
property bool commandReady property bool commandReady
property bool loaded property bool loaded
property double now: Date.now() property double now: Date.now()
property var updates: ({}) property var updates: ({})
property bool updating
property string updatingPackage: ""
function formatUpdateTime(timestamp) { function formatUpdateTime(timestamp) {
const diffMs = root.now - timestamp; const diffMs = root.now - timestamp;
@@ -37,22 +34,6 @@ Singleton {
return Qt.formatDateTime(new Date(timestamp), "dd hh:mm"); 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: { onUpdatesChanged: {
if (!root.loaded) if (!root.loaded)
return; return;
@@ -111,28 +92,6 @@ 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 { Process {
id: updatesProc id: updatesProc
@@ -156,44 +115,6 @@ 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 { Timer {
id: saveTimer id: saveTimer
-1
View File
@@ -14,7 +14,6 @@ Searcher {
property string actualCurrent: WallpaperPath.currentWallpaperPath property string actualCurrent: WallpaperPath.currentWallpaperPath
readonly property string current: showPreview ? previewPath : actualCurrent readonly property string current: showPreview ? previewPath : actualCurrent
property string previewPath property string previewPath
property bool recentlyChanged
property bool showPreview: false property bool showPreview: false
function preview(path: string): void { function preview(path: string): void {
+1 -1
View File
@@ -3,7 +3,7 @@ import QtQuick
import QtQuick.Layouts import QtQuick.Layouts
import qs.Config import qs.Config
import qs.Modules import qs.Modules
import qs.Helpers import qs.Helpers as Helpers
import qs.Components import qs.Components
CustomRect { CustomRect {
-2
View File
@@ -16,7 +16,6 @@ Item {
readonly property Item current: currentPopout?.item ?? null readonly property Item current: currentPopout?.item ?? null
readonly property Popout currentPopout: content.children.find(c => c.shouldBeActive) ?? null readonly property Popout currentPopout: content.children.find(c => c.shouldBeActive) ?? null
required property PopoutState popouts required property PopoutState popouts
required property ShellScreen screen
implicitHeight: (currentPopout?.implicitHeight ?? 0) + 5 * 2 implicitHeight: (currentPopout?.implicitHeight ?? 0) + 5 * 2
implicitWidth: (currentPopout?.implicitWidth ?? 0) + 5 * 2 implicitWidth: (currentPopout?.implicitWidth ?? 0) + 5 * 2
@@ -64,7 +63,6 @@ Item {
TrayMenuPopout { TrayMenuPopout {
popouts: root.popouts popouts: root.popouts
screen: root.screen
trayItem: trayMenu.modelData.menu trayItem: trayMenu.modelData.menu
} }
} }
+31
View File
@@ -0,0 +1,31 @@
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"
}
}
+74 -22
View File
@@ -12,29 +12,90 @@ Item {
required property Canvas drawing required property Canvas drawing
property bool expanded: true property bool expanded: true
property real offsetScale: shouldBeActive ? 0 : 1
required property ShellScreen screen required property ShellScreen screen
readonly property bool shouldBeActive: visibilities.isDrawing readonly property bool shouldBeActive: visibilities.isDrawing
required property var visibilities required property var visibilities
anchors.leftMargin: (-implicitWidth - 5) * offsetScale
implicitHeight: content.implicitHeight implicitHeight: content.implicitHeight
implicitWidth: root.expanded ? content.implicitWidth : icon.implicitWidth implicitWidth: 0
opacity: 1 - offsetScale visible: width > 0
visible: offsetScale < 1
Behavior on implicitWidth { 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
}
}
]
transitions: [
Transition {
from: "*"
to: "*"
ParallelAnimation {
Anim { Anim {
duration: Appearance.anim.durations.expressiveDefaultSpatial easing.bezierCurve: MaterialEasing.expressiveEffects
easing.bezierCurve: Appearance.anim.curves.expressiveDefaultSpatial property: "implicitWidth"
target: root
} }
}
Behavior on offsetScale {
Anim { Anim {
duration: Appearance.anim.durations.expressiveDefaultSpatial duration: Appearance.anim.durations.small
easing.bezierCurve: Appearance.anim.curves.expressiveDefaultSpatial property: "opacity"
target: icon
}
Anim {
duration: Appearance.anim.durations.small
property: "opacity"
target: content
} }
} }
}
]
onVisibleChanged: { onVisibleChanged: {
if (!visible) if (!visible)
@@ -48,12 +109,8 @@ Item {
anchors.right: parent.right anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
height: content.contentItem.height height: content.contentItem.height
opacity: root.expanded ? 0 : 1 opacity: 1
Behavior on opacity {
Anim {
}
}
sourceComponent: MaterialIcon { sourceComponent: MaterialIcon {
font.pointSize: Appearance.font.size.larger font.pointSize: Appearance.font.size.larger
text: "arrow_forward_ios" text: "arrow_forward_ios"
@@ -65,12 +122,7 @@ Item {
anchors.right: parent.right anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
opacity: root.expanded ? 1 : 0
Behavior on opacity {
Anim {
}
}
sourceComponent: Content { sourceComponent: Content {
drawing: root.drawing drawing: root.drawing
visibilities: root.visibilities visibilities: root.visibilities
+1
View File
@@ -13,6 +13,7 @@ Searcher {
function launch(entry: DesktopEntry): void { function launch(entry: DesktopEntry): void {
appDb.incrementFrequency(entry.id); appDb.incrementFrequency(entry.id);
console.log(root.command);
if (entry.runInTerminal) if (entry.runInTerminal)
Quickshell.execDetached({ Quickshell.execDetached({
+9 -14
View File
@@ -8,25 +8,21 @@ import qs.Helpers
Scope { Scope {
id: root id: root
readonly property bool enabled: !Players.list.some(p => p.isPlaying)
required property Lock lock required property Lock lock
readonly property bool enabled: !Players.list.some( p => p.isPlaying )
function handleIdleAction(action: var): void { function handleIdleAction( action: var ): void {
if (!action) if ( !action )
return; return;
if (action === "lock") if ( action === "lock" )
lock.lock.locked = true; lock.lock.locked = true;
else if (action === "unlock") else if ( action === "unlock" )
lock.lock.locked = false; lock.lock.locked = false;
else if (action === "dpms on") else if ( typeof action === "string" )
Hypr.dispatch('hl.dsp.dpms({ action = "enable" })'); Hypr.dispatch( action );
else if (action === "dpms off")
Hypr.dispatch('hl.dsp.dpms({ action = "disable" })');
else if (typeof action === "string")
Hypr.dispatch(action);
else else
Quickshell.execDetached(action); Quickshell.execDetached( action );
} }
Variants { Variants {
@@ -37,8 +33,7 @@ Scope {
enabled: root.enabled && modelData.timeout > 0 ? true : false enabled: root.enabled && modelData.timeout > 0 ? true : false
timeout: modelData.timeout timeout: modelData.timeout
onIsIdleChanged: root.handleIdleAction( isIdle ? modelData.idleAction : modelData.activeAction )
onIsIdleChanged: root.handleIdleAction(isIdle ? modelData.idleAction : modelData.activeAction)
} }
} }
} }
-2
View File
@@ -284,8 +284,6 @@ CustomRect {
Layout.fillWidth: true Layout.fillWidth: true
color: root.urgency === "critical" ? DynamicColors.palette.m3onSecondaryContainer : DynamicColors.palette.m3onSurface color: root.urgency === "critical" ? DynamicColors.palette.m3onSecondaryContainer : DynamicColors.palette.m3onSurface
text: { text: {
if (!Config.lock.showNotifContent)
return "Unlock to view";
const summary = modelData.summary.replace(/\n/g, " "); const summary = modelData.summary.replace(/\n/g, " ");
const body = modelData.body.replace(/\n/g, " "); const body = modelData.body.replace(/\n/g, " ");
const color = root.urgency === "critical" ? DynamicColors.palette.m3secondary : DynamicColors.palette.m3outline; const color = root.urgency === "critical" ? DynamicColors.palette.m3secondary : DynamicColors.palette.m3outline;
+9 -3
View File
@@ -10,6 +10,7 @@ Item {
readonly property int padding: 6 readonly property int padding: 6
required property Item panels required property Item panels
required property Item sidebarPanel
required property PersistentProperties visibilities required property PersistentProperties visibilities
anchors.bottom: parent.bottom anchors.bottom: parent.bottom
@@ -43,7 +44,7 @@ Item {
return Math.min((QsWindow.window?.screen?.height ?? 0) - 1 * 2, height + padding * 2); return Math.min((QsWindow.window?.screen?.height ?? 0) - 1 * 2, height + padding * 2);
} }
implicitWidth: Config.notifs.sizes.width + padding * 2 implicitWidth: Math.max(sidebarPanel.width * (1 - sidebarPanel.offsetScale), Config.notifs.sizes.width + padding * 2)
Behavior on implicitHeight { Behavior on implicitHeight {
Anim { Anim {
@@ -72,8 +73,9 @@ Item {
required property NotifServer.Notif modelData required property NotifServer.Notif modelData
readonly property alias nonAnimHeight: notif.nonAnimHeight readonly property alias nonAnimHeight: notif.nonAnimHeight
anchors.left: parent.left
anchors.right: parent.right
implicitHeight: notif.implicitHeight + (idx === 0 ? 0 : 8) implicitHeight: notif.implicitHeight + (idx === 0 ? 0 : 8)
implicitWidth: notif.implicitWidth
ListView.onRemove: removeAnim.start() ListView.onRemove: removeAnim.start()
onIndexChanged: { onIndexChanged: {
@@ -124,16 +126,20 @@ Item {
} }
ClippingRectangle { ClippingRectangle {
// implicitWidth: notif.implicitWidth
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top anchors.top: parent.top
anchors.topMargin: wrapper.idx === 0 ? 0 : 8 anchors.topMargin: wrapper.idx === 0 ? 0 : 8
color: "transparent" color: "transparent"
implicitHeight: notif.implicitHeight implicitHeight: notif.implicitHeight
implicitWidth: notif.implicitWidth
radius: Appearance.rounding.smallest / 2 radius: Appearance.rounding.smallest / 2
Notification { Notification {
id: notif id: notif
anchors.left: parent.left
anchors.right: parent.right
modelData: wrapper.modelData modelData: wrapper.modelData
} }
} }
+1 -1
View File
@@ -20,9 +20,9 @@ CustomRect {
required property NotifServer.Notif modelData 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 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 color: root.modelData.urgency === NotificationUrgency.Critical ? DynamicColors.palette.m3secondaryContainer : DynamicColors.tPalette.m3surfaceContainer
implicitHeight: inner.implicitHeight implicitHeight: inner.implicitHeight
implicitWidth: Config.notifs.sizes.width
radius: 6 radius: 6
x: Config.notifs.sizes.width x: Config.notifs.sizes.width
@@ -78,10 +78,6 @@ LazyListView {
} }
} }
onDoubleClicked: event => {
if (event.button === Qt.LeftButton)
notifInner.toggleExpand(!notifInner.expanded);
}
onPositionChanged: event => { onPositionChanged: event => {
if (pressed) { if (pressed) {
const diffY = event.y - startY; const diffY = event.y - startY;
@@ -79,10 +79,6 @@ LazyListView {
Component.onCompleted: modelData?.lock(this) Component.onCompleted: modelData?.lock(this)
Component.onDestruction: modelData?.unlock(this) Component.onDestruction: modelData?.unlock(this)
onDoubleClicked: event => {
if (event.button === Qt.LeftButton)
root.requestToggleExpand(!root.expanded);
}
onPositionChanged: event => { onPositionChanged: event => {
if (pressed && !root.expanded) { if (pressed && !root.expanded) {
const diffY = event.y - startY; const diffY = event.y - startY;
@@ -5,7 +5,6 @@ import QtQuick.Layouts
import qs.Components import qs.Components
import qs.Config import qs.Config
import qs.Modules import qs.Modules
import qs.Helpers
import qs.Daemons import qs.Daemons
CustomRect { CustomRect {
@@ -33,7 +32,7 @@ CustomRect {
Toggle { Toggle {
checked: Network.wifiEnabled checked: Network.wifiEnabled
icon: Network.wifiEnabled ? "wifi" : "wifi_off" icon: Network.wifiEnabled ? "wifi" : "wifi_off"
visible: QSNetwork.Networking.devices.values.some(n => n.type === QSNetwork.DeviceType.Wifi) visible: QSNetwork.Networking.devices.values.length > 0
onClicked: Network.toggleWifi() onClicked: Network.toggleWifi()
} }
@@ -80,13 +79,6 @@ CustomRect {
adapter.enabled = !adapter.enabled; adapter.enabled = !adapter.enabled;
} }
} }
Toggle {
checked: GameMode.enabled
icon: GameMode.enabled ? "videogame_asset" : "videogame_asset_off"
onClicked: GameMode.enabled = !GameMode.enabled
}
} }
} }
+1
View File
@@ -17,6 +17,7 @@ Item {
id: content id: content
panels: root.panels panels: root.panels
sidebarPanel: root.sidebarPanel
visibilities: root.visibilities visibilities: root.visibilities
} }
} }
-28
View File
@@ -1,36 +1,8 @@
import QtQuick import QtQuick
QtObject { QtObject {
id: root
property string currentName property string currentName
property bool hasCurrent property bool hasCurrent
property var submenus: []
signal detachRequested(mode: string) 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();
}
} }
+1 -1
View File
@@ -17,7 +17,7 @@ CustomRect {
color: visibilities.resources ? DynamicColors.palette.m3primary : DynamicColors.tPalette.m3surfaceContainer color: visibilities.resources ? DynamicColors.palette.m3primary : DynamicColors.tPalette.m3surfaceContainer
implicitHeight: Config.barConfig.height + Appearance.padding.smallest * 2 implicitHeight: Config.barConfig.height + Appearance.padding.smallest * 2
implicitWidth: rowLayout.implicitWidth + Appearance.padding.normal * 2 implicitWidth: rowLayout.implicitWidth + Appearance.padding.normal * 2
radius: Appearance.rounding.full radius: height / 2
StateLayer { StateLayer {
onClicked: root.visibilities.resources = !root.visibilities.resources onClicked: root.visibilities.resources = !root.visibilities.resources
-18
View File
@@ -104,18 +104,6 @@ Item {
key: "launcher" key: "launcher"
name: "Launcher" name: "Launcher"
} }
ListElement {
icon: "screenshot_region"
key: "screenshot"
name: "Screenshot"
}
ListElement {
icon: "cached"
key: "updates"
name: "Updates"
}
} }
CustomClippingRect { CustomClippingRect {
@@ -183,15 +171,9 @@ Item {
Layout.fillHeight: true Layout.fillHeight: true
Layout.preferredWidth: icon.contentWidth Layout.preferredWidth: icon.contentWidth
color: categoryItem.index === clayout.currentIndex ? DynamicColors.palette.m3onPrimary : DynamicColors.palette.m3onSurface 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 font.pointSize: Appearance.font.size.small * 2
text: categoryItem.icon text: categoryItem.icon
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
Behavior on fill {
Anim {
}
}
} }
CustomText { CustomText {
+7 -7
View File
@@ -29,13 +29,13 @@ SettingsPage {
step: 50 step: 50
} }
Separator { // Separator {
} // }
//
WallpaperCropper { // WallpaperCropper {
Layout.fillWidth: true // Layout.fillWidth: true
Layout.preferredHeight: 600 // Layout.preferredHeight: 300
} // }
} }
SettingsSection { SettingsSection {
+6 -16
View File
@@ -19,8 +19,8 @@ SettingsPage {
} }
SettingSpinBox { SettingSpinBox {
min: 1
name: "Height" name: "Height"
min: 1
object: Config.barConfig object: Config.barConfig
setting: "height" setting: "height"
} }
@@ -29,8 +29,8 @@ SettingsPage {
} }
SettingSpinBox { SettingSpinBox {
min: 0
name: "Rounding" name: "Rounding"
min: 0
object: Config.barConfig object: Config.barConfig
setting: "rounding" setting: "rounding"
} }
@@ -39,21 +39,11 @@ SettingsPage {
} }
SettingSpinBox { SettingSpinBox {
min: 0
name: "Border" name: "Border"
min: 0
object: Config.barConfig object: Config.barConfig
setting: "border" setting: "border"
} }
Separator {
}
SettingSpinBox {
min: 0
name: "Smoothing"
object: Config.barConfig
setting: "smoothing"
}
} }
SettingsSection { SettingsSection {
@@ -155,8 +145,8 @@ SettingsPage {
} }
SettingSpinBox { SettingSpinBox {
min: 1
name: "Dock height" name: "Dock height"
min: 1
object: Config.dock object: Config.dock
setting: "height" setting: "height"
} }
@@ -183,8 +173,8 @@ SettingsPage {
} }
SettingStringList { SettingStringList {
addLabel: qsTr("Add pinned app")
name: "Pinned apps" name: "Pinned apps"
addLabel: qsTr("Add pinned app")
object: Config.dock object: Config.dock
setting: "pinnedApps" setting: "pinnedApps"
} }
@@ -193,8 +183,8 @@ SettingsPage {
} }
SettingStringList { SettingStringList {
addLabel: qsTr("Add ignored regex")
name: "Ignored app regexes" name: "Ignored app regexes"
addLabel: qsTr("Add ignored regex")
object: Config.dock object: Config.dock
setting: "ignoredAppRegexes" setting: "ignoredAppRegexes"
} }
-9
View File
@@ -47,15 +47,6 @@ SettingsPage {
object: Config.general object: Config.general
setting: "desktopIcons" setting: "desktopIcons"
} }
Separator {
}
SettingInput {
name: "Date format"
object: Config.general
setting: "dateFormat"
}
} }
SettingsSection { SettingsSection {
+5 -14
View File
@@ -31,8 +31,8 @@ SettingsPage {
} }
SettingSpinBox { SettingSpinBox {
min: 1
name: "Max fingerprint tries" name: "Max fingerprint tries"
min: 1
object: Config.lock object: Config.lock
setting: "maxFprintTries" setting: "maxFprintTries"
step: 1 step: 1
@@ -41,18 +41,9 @@ SettingsPage {
Separator { Separator {
} }
SettingSwitch {
name: "Show notification details"
object: Config.lock
setting: "showNotifContent"
}
Separator {
}
SettingSpinBox { SettingSpinBox {
min: 0
name: "Blur amount" name: "Blur amount"
min: 0
object: Config.lock object: Config.lock
setting: "blurAmount" setting: "blurAmount"
step: 1 step: 1
@@ -62,9 +53,9 @@ SettingsPage {
} }
SettingSpinBox { SettingSpinBox {
name: "Height multiplier"
max: 2 max: 2
min: 0.1 min: 0.1
name: "Height multiplier"
object: Config.lock.sizes object: Config.lock.sizes
setting: "heightMult" setting: "heightMult"
step: 0.05 step: 0.05
@@ -74,9 +65,9 @@ SettingsPage {
} }
SettingSpinBox { SettingSpinBox {
name: "Aspect ratio"
max: 4 max: 4
min: 0.5 min: 0.5
name: "Aspect ratio"
object: Config.lock.sizes object: Config.lock.sizes
setting: "ratio" setting: "ratio"
step: 0.05 step: 0.05
@@ -86,8 +77,8 @@ SettingsPage {
} }
SettingSpinBox { SettingSpinBox {
min: 100
name: "Center width" name: "Center width"
min: 100
object: Config.lock.sizes object: Config.lock.sizes
setting: "centerWidth" setting: "centerWidth"
step: 10 step: 10
@@ -22,13 +22,6 @@ ColumnLayout {
Config.save(); 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) { function updateTimeoutEntry(i, key, value) {
const list = [...Config.general.idle.timeouts]; const list = [...Config.general.idle.timeouts];
let entry = list[i]; let entry = list[i];
@@ -56,9 +49,6 @@ ColumnLayout {
onAddActiveActionRequested: { onAddActiveActionRequested: {
root.updateTimeoutEntry(index, "activeAction", ""); root.updateTimeoutEntry(index, "activeAction", "");
} }
onDeleteRequested: function (index) {
root.deleteTimeoutEntry(index);
}
onFieldEdited: function (key, value) { onFieldEdited: function (key, value) {
root.updateTimeoutEntry(index, key, value); root.updateTimeoutEntry(index, key, value);
} }
-130
View File
@@ -1,130 +0,0 @@
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"
}
}
}
@@ -1,197 +0,0 @@
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
}))
}
}
}
}
-18
View File
@@ -74,10 +74,6 @@ Item {
stack.push(osd); stack.push(osd);
else if (currentCategory === "launcher") else if (currentCategory === "launcher")
stack.push(launcher); stack.push(launcher);
else if (currentCategory === "screenshot")
stack.push(screenshot);
else if (currentCategory === "updates")
stack.push(updates);
} }
target: root target: root
@@ -229,18 +225,4 @@ Item {
Cat.Launcher { Cat.Launcher {
} }
} }
Component {
id: screenshot
Cat.Screenshot {
}
}
Component {
id: updates
Cat.SystemUpdates {
}
}
} }
+344 -27
View File
@@ -28,11 +28,72 @@ Item {
property int uidCounter: 0 property int uidCounter: 0
property var visualEntries: [] 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() { function ensureVisualEntries() {
if (!root.dragActive && !root.dropAnimating) if (!root.dragActive && !root.dropAnimating)
root.rebuildVisualEntries(); 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) { function iconForId(id) {
switch (id) { switch (id) {
case "workspaces": case "workspaces":
@@ -92,7 +153,7 @@ Item {
case "spacer": case "spacer":
return qsTr("Spacer"); return qsTr("Spacer");
case "activeWindow": case "activeWindow":
return qsTr("Title"); return qsTr("Active window");
case "tray": case "tray":
return qsTr("Tray"); return qsTr("Tray");
case "upower": case "upower":
@@ -102,14 +163,34 @@ Item {
case "clock": case "clock":
return qsTr("Clock"); return qsTr("Clock");
case "notifBell": case "notifBell":
return qsTr("Notifs"); return qsTr("Notification bell");
case "hyprsunset":
return qsTr("Night light");
default: default:
return id; 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() { function rebuildVisualEntries() {
const entries = root.object[root.setting] ?? []; const entries = root.object[root.setting] ?? [];
const next = []; const next = [];
@@ -144,6 +225,7 @@ Item {
list[index] = entry; list[index] = entry;
root.object[root.setting] = list; root.object[root.setting] = list;
Config.save(); Config.save();
root.ensureVisualEntries();
} }
Layout.fillWidth: true Layout.fillWidth: true
@@ -151,7 +233,7 @@ Item {
Component.onCompleted: root.rebuildVisualEntries() Component.onCompleted: root.rebuildVisualEntries()
CustomRect { Rectangle {
anchors.fill: parent anchors.fill: parent
anchors.margins: -Appearance.padding.smaller anchors.margins: -Appearance.padding.smaller
color: DynamicColors.palette.m3primaryContainer color: DynamicColors.palette.m3primaryContainer
@@ -166,12 +248,39 @@ Item {
} }
} }
RowLayout { 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 {
id: layout id: layout
anchors.fill: parent anchors.fill: parent
spacing: Appearance.spacing.smaller
// spacing: Appearance.spacing.smaller CustomText {
Layout.fillWidth: true
font.pointSize: Appearance.font.size.larger
text: root.name
}
DelegateModel { DelegateModel {
id: visualModel id: visualModel
@@ -184,40 +293,248 @@ Item {
} }
} }
Repeater { ListView {
delegate: entryDelegate id: listView
Layout.fillWidth: true
Layout.preferredHeight: contentHeight
boundsBehavior: Flickable.StopAtBounds
clip: false
implicitHeight: contentHeight
implicitWidth: width
interactive: !(root.dragActive || root.dropAnimating)
model: visualModel 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 { Component {
id: entryDelegate id: entryDelegate
IconButton { DropArea {
required property int index id: slot
readonly property var entryData: modelData.entry
required property var modelData required property var modelData
readonly property string uid: modelData.uid
Layout.fillWidth: true function previewReorder(drag) {
Layout.preferredWidth: implicitWidth + (stateLayer.pressed ? 18 : internalChecked ? 7 : 0) const source = drag.source;
checked: modelData.entry.enabled ?? true if (!source || !source.uid || source.uid === slot.uid)
font: Appearance.font.family.sans return;
// 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))
Behavior on Layout.preferredWidth { 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 { Anim {
duration: MaterialEasing.expressiveEffectsTime
easing.bezierCurve: MaterialEasing.expressiveEffects
} }
} }
onClicked: root.updateEntry(index, internalChecked) 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)
}
}
}
} }
} }
} }
+8 -31
View File
@@ -10,7 +10,6 @@ Item {
required property var modelData required property var modelData
signal addActiveActionRequested signal addActiveActionRequested
signal deleteRequested(int index)
signal fieldEdited(string key, var value) signal fieldEdited(string key, var value)
Layout.fillWidth: true Layout.fillWidth: true
@@ -66,64 +65,42 @@ Item {
HoverHandler { HoverHandler {
id: nameHover 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 { CustomText {
anchors.left: parent.left anchors.left: parent.left
anchors.leftMargin: nameHover.hovered ? editButton.width + Appearance.spacing.smaller * 2 : 0 anchors.right: editButton.left
anchors.right: deleteButton.left
anchors.rightMargin: Appearance.spacing.small anchors.rightMargin: Appearance.spacing.small
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
elide: Text.ElideRight // enable if CustomText supports it elide: Text.ElideRight // enable if CustomText supports it
font.pointSize: Appearance.font.size.larger font.pointSize: Appearance.font.size.larger
text: root.modelData.name text: root.modelData.name
visible: !nameCell.editing visible: !nameCell.editing
Behavior on anchors.leftMargin {
Anim {
}
}
} }
HoverIconButton { IconButton {
id: deleteButton id: editButton
anchors.right: parent.right anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
font.pointSize: Appearance.font.size.large font.pointSize: Appearance.font.size.large
icon: "delete" icon: "edit"
shouldBeVisible: nameHover.hovered && !nameCell.editing visible: nameHover.hovered && !nameCell.editing
onClicked: root.deleteRequested(root.index) onClicked: nameCell.beginEdit()
} }
CustomRect { CustomRect {
anchors.left: parent.left anchors.fill: parent
anchors.verticalCenter: parent.verticalCenter
color: DynamicColors.tPalette.m3surface 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 radius: Appearance.rounding.small
visible: nameCell.editing visible: nameCell.editing
CustomTextField { CustomTextField {
id: nameEditor id: nameEditor
anchors.centerIn: parent anchors.fill: parent
horizontalAlignment: Text.AlignHCenter
implicitWidth: Math.min(contentWidth + Appearance.padding.normal * 2, nameCell.width - Appearance.padding.normal)
text: nameCell.draftName text: nameCell.draftName
Keys.onEscapePressed: { Keys.onEscapePressed: {
@@ -38,7 +38,7 @@ Item {
Layout.preferredHeight: 42 * 6 + Appearance.padding.normal * 2 + Appearance.spacing.small * 5 Layout.preferredHeight: 42 * 6 + Appearance.padding.normal * 2 + Appearance.spacing.small * 5
Layout.preferredWidth: 500 Layout.preferredWidth: 500
color: DynamicColors.tPalette.m3surfaceContainer color: DynamicColors.tPalette.m3surfaceContainer
radius: (21 + Appearance.padding.normal) * Appearance.rounding.scale radius: 21 + Appearance.padding.normal
CustomRect { CustomRect {
id: searchBox id: searchBox
+70 -82
View File
@@ -6,109 +6,97 @@ import qs.Config
import qs.Components import qs.Components
import qs.Helpers import qs.Helpers
Item { ColumnLayout {
id: root id: root
Image { spacing: 15
id: imageView width: Math.min(parent ? parent.width : 600, 600)
property real displayH: paintedHeight Rectangle {
property real displayW: paintedWidth id: previewContainer
property real displayX: (width - paintedWidth) * 0.5
property real displayY: (height - paintedHeight) * 0.5 Layout.fillHeight: true
property real scaleX: sourceW / displayW Layout.preferredWidth: height * (Quickshell.screens.length > 0 ? (Quickshell.screens[0].height / Math.max(1, Quickshell.screens[0].width)) : 16 / 9)
property real scaleY: sourceH / displayH clip: true
property real sourceH: Quickshell.screens[0].height color: DynamicColors.palette.m3surfaceContainer
property real sourceW: Quickshell.screens[0].width radius: Config.appearance.rounding.scale * 10
Image {
id: img
anchors.fill: parent anchors.fill: parent
asynchronous: true
fillMode: Image.PreserveAspectFit fillMode: Image.PreserveAspectFit
smooth: true
source: Wallpapers.current source: Wallpapers.current
}
Item { Rectangle {
id: overlay
clip: true
height: imageView.displayH
width: imageView.displayW
x: imageView.displayX
y: imageView.displayY
CustomRect {
id: cropRect id: cropRect
property real aspectRatio: Quickshell.screens[0].width / Quickshell.screens[0].height property real cropHeight: (imageAspect > screenAspect ? paintedHeight : paintedWidth / screenAspect) / Config.background.zoom
readonly property rect sourceRect: Qt.rect(x * imageView.scaleX, y * imageView.scaleY, width * imageView.scaleX, height * imageView.scaleY) property real cropWidth: (imageAspect > screenAspect ? paintedHeight * screenAspect : paintedWidth) / Config.background.zoom
property real zoom: Config.background.zoom property real imageAspect: Math.max(1, paintedWidth) / Math.max(1, paintedHeight)
property real paintedHeight: img.paintedHeight > 0 ? img.paintedHeight : img.height
function clampToBounds() { property real paintedWidth: img.paintedWidth > 0 ? img.paintedWidth : img.width
x = Math.max(0, Math.min(x, overlay.width - width)); property real paintedX: (img.width - paintedWidth) / 2
property real paintedY: (img.height - paintedHeight) / 2
y = Math.max(0, Math.min(y, overlay.height - height)); property real screenAspect: Quickshell.screens.length > 0 ? (Quickshell.screens[0].width / Math.max(1, Quickshell.screens[0].height)) : 16 / 9
}
border.color: DynamicColors.palette.m3primary border.color: DynamicColors.palette.m3primary
border.width: 2 border.width: 2
color: DynamicColors.tPalette.m3primary color: Qt.alpha(DynamicColors.palette.m3primaryContainer, 0.3)
height: width / aspectRatio height: cropHeight
radius: Appearance.rounding.small width: cropWidth
visible: imageView.status === Image.Ready x: paintedX + (paintedWidth - width) * Config.background.alignX
width: Math.min(overlay.width / zoom, overlay.height * aspectRatio / zoom) y: paintedY + (paintedHeight - height) * Config.background.alignY
x: Config.background.sourceClipX / imageView.scaleX
y: Config.background.sourceClipY / imageView.scaleY
}
MouseArea { DragHandler {
function updateCrop(mouseX, mouseY) { target: null
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)); onActiveTranslationChanged: {
if (active) {
let newX = cropRect.x - cropRect.paintedX + translation.x;
let newY = cropRect.y - cropRect.paintedY + translation.y;
ny = Math.max(0, Math.min(ny, overlay.height - cropRect.height)); let rangeX = cropRect.paintedWidth - cropRect.width;
let rangeY = cropRect.paintedHeight - cropRect.height;
cropRect.x = nx; if (rangeX > 0) {
cropRect.y = ny; let valX = newX / rangeX;
} Config.background.alignX = Math.max(0.0, Math.min(1.0, valX));
anchors.fill: parent
hoverEnabled: true
preventStealing: true
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(); Config.save();
} }
onWheel: wheel => {
let oldCenterX = cropRect.x + cropRect.width * 0.5;
let oldCenterY = cropRect.y + cropRect.height * 0.5;
if (wheel.angleDelta.y > 0) if (rangeY > 0) {
cropRect.zoom *= 1.1; let valY = newY / rangeY;
else Config.background.alignY = Math.max(0.0, Math.min(1.0, valY));
cropRect.zoom /= 1.1; Config.save();
cropRect.zoom = Math.max(1.0, Math.min(cropRect.zoom, 10.0));
Config.background.zoom = cropRect.zoom;
cropRect.x = oldCenterX - cropRect.width * 0.5;
cropRect.y = oldCenterY - cropRect.height * 0.5;
cropRect.clampToBounds();
} }
} }
} }
}
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));
}
}
}
}
}
}
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
+3 -2
View File
@@ -6,7 +6,7 @@ import QtQuick.Controls
import qs.Components import qs.Components
import qs.Config import qs.Config
import "../../scripts/fuzzysort.js" as Fuzzy import "../../scripts/fuzzysort.js" as Fuzzy
import "../../scripts/SettingsIndex.mjs" as SettingsIndex import "./SettingsIndex.mjs" as SettingsIndex
Item { Item {
id: root id: root
@@ -53,10 +53,11 @@ Item {
Shortcut { Shortcut {
sequence: "/" sequence: "/"
onActivated: searchField.forceActiveFocus() onActivated: searchField.forceActiveFocus()
} }
Component.onCompleted: console.log(root.height)
ListModel { ListModel {
id: resultsModel id: resultsModel
} }
+30 -9
View File
@@ -7,24 +7,45 @@ import qs.Helpers
Item { Item {
id: root id: root
property real offsetScale: shouldBeActive ? 0 : 1
required property var panels required property var panels
required property ShellScreen screen required property ShellScreen screen
readonly property bool shouldBeActive: visibilities.settings
required property PersistentProperties visibilities required property PersistentProperties visibilities
anchors.topMargin: (-implicitHeight - 5) * offsetScale implicitHeight: 0
implicitHeight: content.implicitHeight
implicitWidth: content.implicitWidth implicitWidth: content.implicitWidth
opacity: 1 - offsetScale visible: height > 0
visible: offsetScale < 1
states: State {
name: "visible"
when: root.visibilities.settings
PropertyChanges {
root.implicitHeight: content.implicitHeight
}
}
transitions: [
Transition {
from: ""
to: "visible"
Behavior on offsetScale {
Anim { Anim {
duration: Appearance.anim.durations.expressiveDefaultSpatial duration: MaterialEasing.expressiveEffectsTime
easing.bezierCurve: Appearance.anim.curves.expressiveDefaultSpatial easing.bezierCurve: MaterialEasing.expressiveEffects
property: "implicitHeight"
target: root
}
},
Transition {
from: "visible"
to: ""
Anim {
easing.bezierCurve: MaterialEasing.expressiveEffects
property: "implicitHeight"
target: root
} }
} }
]
CustomClippingRect { CustomClippingRect {
anchors.fill: parent anchors.fill: parent
+2 -2
View File
@@ -14,8 +14,8 @@ Scope {
description: "Toggle launcher" description: "Toggle launcher"
name: "toggle-launcher" name: "toggle-launcher"
onPressed: { onPressed: root.launcherInterrupted = false
root.launcherInterrupted = false; onReleased: {
if (!root.launcherInterrupted && !root.hasFullscreen) { if (!root.launcherInterrupted && !root.hasFullscreen) {
const visibilities = Visibilities.getForActive(); const visibilities = Visibilities.getForActive();
visibilities.launcher = !visibilities.launcher; visibilities.launcher = !visibilities.launcher;
-158
View File
@@ -1,158 +0,0 @@
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"
}
}
}
}
}
}
}
+237 -4
View File
@@ -1,16 +1,249 @@
pragma ComponentBehavior: Bound pragma ComponentBehavior: Bound
import Quickshell import Quickshell
import Quickshell.Widgets
import QtQuick import QtQuick
import QtQuick.Controls
import QtQuick.Effects
import qs.Components import qs.Components
import qs.Modules import qs.Modules
import qs.Config import qs.Config
SubMenu { StackView {
id: root id: root
handle: trayItem property int biggestWidth: 0
level: 0 required property PopoutState popouts
property int rootWidth: 0
required property QsMenuHandle trayItem 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")
}
}
}
}
}
}
} }
+1 -1
View File
@@ -31,7 +31,7 @@ Item {
implicitHeight: Math.max(saver.implicitHeight, balance.implicitHeight, perf.implicitHeight) + 5 * 2 + saverLabel.contentHeight implicitHeight: Math.max(saver.implicitHeight, balance.implicitHeight, perf.implicitHeight) + 5 * 2 + saverLabel.contentHeight
implicitWidth: saver.implicitHeight + balance.implicitHeight + perf.implicitHeight + 8 * 2 + saverLabel.contentWidth implicitWidth: saver.implicitHeight + balance.implicitHeight + perf.implicitHeight + 8 * 2 + saverLabel.contentWidth
// color: "transparent" // color: "transparent"
radius: (20 - Appearance.padding.small) * Appearance.rounding.scale radius: 6
CustomRect { CustomRect {
id: indicator id: indicator
+30
View File
@@ -0,0 +1,30 @@
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
}
}
+2 -5
View File
@@ -54,8 +54,6 @@ CustomClippingRect {
anchors.centerIn: parent anchors.centerIn: parent
contentHeight: childrenRect.height contentHeight: childrenRect.height
contentWidth: 600 contentWidth: 600
displayMarginBeginning: root.itemHeight
displayMarginEnd: root.itemHeight
implicitHeight: Math.min(contentHeight, (root.itemHeight + spacing) * 5 - spacing) implicitHeight: Math.min(contentHeight, (root.itemHeight + spacing) * 5 - spacing)
implicitWidth: contentWidth implicitWidth: contentWidth
spacing: Appearance.spacing.normal spacing: Appearance.spacing.normal
@@ -67,11 +65,10 @@ CustomClippingRect {
required property var modelData required property var modelData
readonly property list<string> sections: modelData.update.split(" ") readonly property list<string> sections: modelData.update.split(" ")
// anchors.left: parent.left anchors.left: parent.left
// anchors.right: parent.right anchors.right: parent.right
color: DynamicColors.tPalette.m3surfaceContainer color: DynamicColors.tPalette.m3surfaceContainer
implicitHeight: root.itemHeight implicitHeight: root.itemHeight
implicitWidth: 600
radius: Appearance.rounding.small - Appearance.padding.small radius: Appearance.rounding.small - Appearance.padding.small
RowLayout { RowLayout {
+1 -1
View File
@@ -14,7 +14,7 @@ CustomRect {
color: DynamicColors.tPalette.m3surfaceContainer color: DynamicColors.tPalette.m3surfaceContainer
implicitHeight: Config.barConfig.height + Appearance.padding.smallest * 2 implicitHeight: Config.barConfig.height + Appearance.padding.smallest * 2
implicitWidth: contentRow.implicitWidth + Appearance.spacing.small * 2 implicitWidth: contentRow.implicitWidth + Appearance.spacing.small * 2
radius: Appearance.rounding.full radius: height / 2
RowLayout { RowLayout {
id: contentRow id: contentRow
-80
View File
@@ -1,80 +0,0 @@
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;
}
}
}
}
+63 -19
View File
@@ -1,6 +1,5 @@
pragma ComponentBehavior: Bound pragma ComponentBehavior: Bound
import Quickshell
import QtQuick import QtQuick
import qs.Components import qs.Components
import qs.Helpers import qs.Helpers
@@ -9,34 +8,79 @@ import qs.Config
Item { Item {
id: root id: root
required property ShellScreen screen property Image current: one
property string source: Wallpapers.current property string source: Wallpapers.current
anchors.fill: parent anchors.fill: parent
Image { 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 {
id: img id: img
anchors.fill: parent 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
asynchronous: true asynchronous: true
fillMode: Image.PreserveAspectCrop fillMode: Image.PreserveAspectCrop
opacity: 1 height: isValid ? (imageRatio > windowRatio ? root.height : root.width / imageRatio) * Config.background.zoom : root.height
retainWhileLoading: true opacity: 0
source: root.source scale: Wallpapers.showPreview ? 1 : 0.8
sourceClipRect: Wallpapers.recentlyChanged ? null : Qt.rect(Config.background.sourceClipX, Config.background.sourceClipY, Config.background.sourceClipW, Config.background.sourceClipH) width: isValid ? (imageRatio > windowRatio ? root.height * imageRatio : root.width) * Config.background.zoom : root.width
sourceSize.height: root.screen.height x: isValid ? (root.width - width) * Config.background.alignX : 0
sourceSize.width: root.screen.width y: isValid ? (root.height - height) * Config.background.alignY : 0
onSourceChanged: { states: State {
if (Wallpapers.recentlyChanged) { name: "visible"
Config.background.sourceClipH = 0; when: root.current === img
Config.background.sourceClipW = 0;
Config.background.sourceClipY = 0; PropertyChanges {
Config.background.sourceClipX = 0; img.opacity: 1
Config.background.zoom = 1.0; img.scale: 1
Config.save(); }
}
transitions: Transition {
Anim {
duration: Config.background.wallFadeDuration
properties: "opacity,scale"
target: img
}
}
onStatusChanged: {
if (status === Image.Ready) {
root.current = this;
} }
Wallpapers.recentlyChanged = true;
} }
} }
} }
-1
View File
@@ -30,7 +30,6 @@ Loader {
} }
WallBackground { WallBackground {
screen: root.screen
} }
Loader { Loader {
+1 -2
View File
@@ -79,8 +79,7 @@ Item {
} }
onPressed: { onPressed: {
const ws = button.modelData.name; Hyprland.dispatch(`workspace ${button.modelData.name}`);
Hyprland.dispatch(Hyprland.usingLua ? `hl.dsp.focus({ workspace= "${ws}"})` : `workspace ${ws}`);
} }
} }
} }
+1 -3
View File
@@ -15,14 +15,13 @@ Item {
property real currentCenter property real currentCenter
property alias currentName: popoutState.currentName property alias currentName: popoutState.currentName
property string detachedMode property string detachedMode
property alias hasCurrent: popoutState.hasCurrent
readonly property bool isDetached: detachedMode.length > 0 readonly property bool isDetached: detachedMode.length > 0
property alias hasCurrent: popoutState.hasCurrent
readonly property real nonAnimHeight: children.find(c => c.shouldBeActive)?.implicitHeight ?? content.implicitHeight readonly property real nonAnimHeight: children.find(c => c.shouldBeActive)?.implicitHeight ?? content.implicitHeight
readonly property real nonAnimWidth: children.find(c => c.shouldBeActive)?.implicitWidth ?? content.implicitWidth readonly property real nonAnimWidth: children.find(c => c.shouldBeActive)?.implicitWidth ?? content.implicitWidth
required property real offsetScale required property real offsetScale
property string queuedMode property string queuedMode
required property ShellScreen screen required property ShellScreen screen
property alias state: popoutState
function close(): void { function close(): void {
hasCurrent = false; hasCurrent = false;
@@ -80,7 +79,6 @@ Item {
sourceComponent: Content { sourceComponent: Content {
popouts: popoutState popouts: popoutState
screen: root.screen
} }
} }
-1
View File
@@ -12,7 +12,6 @@ qml_module(ZShell-blobs
qt_add_shaders(ZShell-blobs "blob_shaders" qt_add_shaders(ZShell-blobs "blob_shaders"
BATCHABLE OPTIMIZED NOHLSL NOMSL BATCHABLE OPTIMIZED NOHLSL NOMSL
GLSL "300es,330"
PREFIX "/" PREFIX "/"
FILES FILES
shaders/blob.frag shaders/blob.frag
+1 -7
View File
@@ -2,9 +2,6 @@
#include <cstring> #include <cstring>
static_assert(sizeof(decltype(BlobRectData::excludeMask)) == sizeof(float),
"BlobMaterial packs excludeMask into a float slot via memcpy");
QSGMaterialType* BlobMaterial::type() const { QSGMaterialType* BlobMaterial::type() const {
static QSGMaterialType s_type; static QSGMaterialType s_type;
return &s_type; return &s_type;
@@ -85,11 +82,8 @@ bool BlobMaterialShader::updateUniformData(RenderState& state, QSGMaterial* newM
for (int i = 0; i < count; ++i) { for (int i = 0; i < count; ++i) {
const auto& r = mat->m_rects[i]; const auto& r = mat->m_rects[i];
const int base = 160 + i * 80; 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 d0[4] = { r.cx, r.cy, r.hw, r.hh };
const float d1[4] = { maskAsFloat, r.offsetX, r.offsetY, r.minEig }; const float d1[4] = { 0.0f, r.offsetX, r.offsetY, r.minEig };
const float d3[4] = { r.screenHalfX, r.screenHalfY, 0.0f, 0.0f }; const float d3[4] = { r.screenHalfX, r.screenHalfY, 0.0f, 0.0f };
memcpy(buf->data() + base, d0, 16); memcpy(buf->data() + base, d0, 16);
memcpy(buf->data() + base + 16, d1, 16); memcpy(buf->data() + base + 16, d1, 16);
-3
View File
@@ -14,9 +14,6 @@ struct BlobRectData {
float screenHalfX = 0, screenHalfY = 0; float screenHalfX = 0, screenHalfY = 0;
// Effective per-corner radii (tr, br, bl, tl), pre-computed on CPU // Effective per-corner radii (tr, br, bl, tl), pre-computed on CPU
float radius[4] = { 0, 0, 0, 0 }; 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 { class BlobMaterial : public QSGMaterial {
+3 -33
View File
@@ -72,17 +72,11 @@ void BlobShape::geometryChange(const QRectF& newGeometry, const QRectF& oldGeome
// Accumulate sub-pixel drift so slow movements don't desync the shader // Accumulate sub-pixel drift so slow movements don't desync the shader
m_pendingDx += static_cast<float>(newGeometry.x() - oldGeometry.x()); m_pendingDx += static_cast<float>(newGeometry.x() - oldGeometry.x());
m_pendingDy += static_cast<float>(newGeometry.y() - oldGeometry.y()); m_pendingDy += static_cast<float>(newGeometry.y() - oldGeometry.y());
// Accumulate size delta across multiple frames so incremental size const auto dw = std::abs(newGeometry.width() - oldGeometry.width());
// changes that are each below the threshold still trigger a dirty const auto dh = std::abs(newGeometry.height() - oldGeometry.height());
// mark once their accumulated delta exceeds it. if (std::abs(m_pendingDx) > 0.5f || std::abs(m_pendingDy) > 0.5f || dw > 0.5 || dh > 0.5) {
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_pendingDx = 0;
m_pendingDy = 0; m_pendingDy = 0;
m_pendingDw = 0;
m_pendingDh = 0;
m_group->markShapeDirty(this); m_group->markShapeDirty(this);
} }
} }
@@ -155,10 +149,6 @@ void BlobShape::updatePolish() {
const QRectF myPadded(static_cast<double>(m_cachedPaddedX), static_cast<double>(m_cachedPaddedY), const QRectF myPadded(static_cast<double>(m_cachedPaddedX), static_cast<double>(m_cachedPaddedY),
static_cast<double>(m_cachedPaddedW), static_cast<double>(m_cachedPaddedH)); 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()) { for (BlobShape* other : m_group->shapes()) {
if (other->isInvertedRect()) if (other->isInvertedRect())
continue; continue;
@@ -220,29 +210,12 @@ void BlobShape::updatePolish() {
r.screenHalfY = std::abs(b) * r.hw + std::abs(d) * r.hh; r.screenHalfY = std::abs(b) * r.hw + std::abs(d) * r.hh;
m_cachedRects.append(r); m_cachedRects.append(r);
rectShapes.append(other);
} }
} }
if (isInvertedRect()) if (isInvertedRect())
m_cachedMyIndex = -1; 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 // Cache inverted rect data
m_cachedHasInverted = false; m_cachedHasInverted = false;
m_cachedInvertedRadius = 0; m_cachedInvertedRadius = 0;
@@ -297,7 +270,6 @@ void BlobShape::updatePolish() {
const auto rectCount = m_cachedRects.size(); const auto rectCount = m_cachedRects.size();
for (qsizetype i = 0; i < rectCount; ++i) { for (qsizetype i = 0; i < rectCount; ++i) {
auto& ri = m_cachedRects[i]; auto& ri = m_cachedRects[i];
const int riExcludeMask = ri.excludeMask;
float fTr = 1.0f, fBr = 1.0f, fBl = 1.0f, fTl = 1.0f; 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; const float cTrX = ri.cx + ri.hw, cTrY = ri.cy - ri.hh;
@@ -308,8 +280,6 @@ void BlobShape::updatePolish() {
for (qsizetype j = 0; j < rectCount; ++j) { for (qsizetype j = 0; j < rectCount; ++j) {
if (j == i) if (j == i)
continue; continue;
if (riExcludeMask & (1 << j))
continue;
const auto& rj = m_cachedRects[j]; 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))); 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))); fBr = std::min(fBr, cpuSmoothstep(0.0f, smoothFactor, cpuSdBox(cBrX, cBrY, rj.cx, rj.cy, rj.hw, rj.hh)));
+2 -4
View File
@@ -84,10 +84,8 @@ QRectF m_localPaddedRect;
QVector<BlobRectData> m_cachedRects; QVector<BlobRectData> m_cachedRects;
int m_cachedMyIndex = -2; int m_cachedMyIndex = -2;
float m_pendingDx = 0; float m_pendingDx = 0;
float m_pendingDy = 0; float m_pendingDy = 0;
float m_pendingDw = 0; bool m_cachedHasInverted = false;
float m_pendingDh = 0;
bool m_cachedHasInverted = false;
float m_cachedInvertedRadius = 0; float m_cachedInvertedRadius = 0;
float m_cachedInvertedOuter[4] = {}; float m_cachedInvertedOuter[4] = {};
float m_cachedInvertedInner[4] = {}; float m_cachedInvertedInner[4] = {};
+4 -35
View File
@@ -63,17 +63,13 @@ float smaxSharpA(float a, float b, float k) {
void main() { void main() {
vec2 pixel = vec2(paddedX, paddedY) + qt_TexCoord0 * vec2(paddedW, paddedH); vec2 pixel = vec2(paddedX, paddedY) + qt_TexCoord0 * vec2(paddedW, paddedH);
// Phase 1: compute per-rect SDF, track owner. We can't smin yet because float mergedSdf = 1e10;
// excluded pairs need to skip the smooth blend, which requires pairwise pass
// below.
float dArr[16];
int owner = -2; int owner = -2;
float minDist = 1e10; float minDist = 1e10;
for (int i = 0; i < rectCount; i++) { for (int i = 0; i < rectCount; i++) {
vec4 rect = rectData[i * 5]; // cx, cy, hw, hh vec4 rect = rectData[i * 5]; // cx, cy, hw, hh
vec4 props = vec4 props = rectData[i * 5 + 1]; // radius, offsetX, offsetY, minEig
rectData[i * 5 + 1]; // excludeMask(int bits), offsetX, offsetY, minEig
vec4 invDm = rectData[i * 5 + 2]; // inverse deform matrix vec4 invDm = rectData[i * 5 + 2]; // inverse deform matrix
vec4 sh = rectData[i * 5 + 3]; // screenHalfX, screenHalfY, 0, 0 vec4 sh = rectData[i * 5 + 3]; // screenHalfX, screenHalfY, 0, 0
vec4 radii = vec4 radii =
@@ -85,10 +81,8 @@ void main() {
// AABB early-out: skip rects far from this pixel // AABB early-out: skip rects far from this pixel
vec2 extent = sh.xy + vec2(smoothFactor * 1.5); vec2 extent = sh.xy + vec2(smoothFactor * 1.5);
if (abs(pixel.x - center.x) > extent.x || 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; continue;
}
// Apply pre-computed inverse deformation to the evaluation point // Apply pre-computed inverse deformation to the evaluation point
mat2 invDeform = mat2(invDm.xy, invDm.zw); mat2 invDeform = mat2(invDm.xy, invDm.zw);
@@ -144,38 +138,13 @@ void main() {
d *= scale; d *= scale;
} }
dArr[i] = d; mergedSdf = smin(mergedSdf, d, smoothFactor);
if (d < smoothFactor && d < minDist) { if (d < smoothFactor && d < minDist) {
minDist = d; minDist = d;
owner = i; 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) { if (hasInverted != 0) {
float dOuter = sdBox(pixel, invertedOuter.xy, invertedOuter.zw) - 1.0; float dOuter = sdBox(pixel, invertedOuter.xy, invertedOuter.zw) - 1.0;
float dInner = float dInner =
+26 -193
View File
@@ -1,170 +1,12 @@
#include "hyprextras.hpp" #include "hyprextras.hpp"
#include "hyprdevices.hpp"
#include <qdir.h> #include <qdir.h>
#include <qjsonarray.h> #include <qjsonarray.h>
#include <qlocalsocket.h> #include <qlocalsocket.h>
#include <qloggingcategory.h>
#include <qmetatype.h>
#include <qregularexpression.h>
#include <qvariant.h> #include <qvariant.h>
Q_LOGGING_CATEGORY(lcHypr, "ZShell.internal.hypr", QtInfoMsg)
namespace ZShell::internal::hypr { 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) HyprExtras::HyprExtras(QObject* parent)
: QObject(parent) : QObject(parent)
, m_requestSocket("") , m_requestSocket("")
@@ -174,22 +16,24 @@ HyprExtras::HyprExtras(QObject* parent)
, m_devices(new HyprDevices(this)) { , m_devices(new HyprDevices(this)) {
const auto his = qEnvironmentVariable("HYPRLAND_INSTANCE_SIGNATURE"); const auto his = qEnvironmentVariable("HYPRLAND_INSTANCE_SIGNATURE");
if (his.isEmpty()) { if (his.isEmpty()) {
qCWarning(lcHypr) << "$HYPRLAND_INSTANCE_SIGNATURE is unset. Unable to connect to Hyprland socket."; qWarning()
<< "HyprExtras::HyprExtras: $HYPRLAND_INSTANCE_SIGNATURE is unset. Unable to connect to Hyprland socket.";
return; return;
} }
auto hyprDir = QString("%1/hypr/%2").arg(qEnvironmentVariable("XDG_RUNTIME_DIR"), his); auto hyprDir = QString("%1/hypr/%2").arg(qEnvironmentVariable("XDG_RUNTIME_DIR"), his);
if (!QDir(hyprDir).exists()) { if (!QDir(hyprDir).exists()) {
hyprDir = QStringLiteral("/tmp/hypr/") + his; hyprDir = "/tmp/hypr/" + his;
if (!QDir(hyprDir).exists()) { if (!QDir(hyprDir).exists()) {
qCWarning(lcHypr) << "Hyprland socket directory does not exist. Unable to connect to Hyprland socket."; qWarning() << "HyprExtras::HyprExtras: Hyprland socket directory does not exist. Unable to connect to "
"Hyprland socket.";
return; return;
} }
} }
m_requestSocket = hyprDir + QStringLiteral("/.socket.sock"); m_requestSocket = hyprDir + "/.socket.sock";
m_eventSocket = hyprDir + QStringLiteral("/.socket2.sock"); m_eventSocket = hyprDir + "/.socket2.sock";
refreshOptions(); refreshOptions();
refreshDevices(); refreshDevices();
@@ -218,7 +62,7 @@ void HyprExtras::message(const QString& message) {
makeRequest(message, [](bool success, const QByteArray& res) { makeRequest(message, [](bool success, const QByteArray& res) {
if (!success) { if (!success) {
qCWarning(lcHypr) << "message: request error:" << QString::fromUtf8(res); qWarning() << "HyprExtras::message: request error:" << QString::fromUtf8(res);
} }
}); });
} }
@@ -228,10 +72,9 @@ void HyprExtras::batchMessage(const QStringList& messages) {
return; return;
} }
makeRequest(QStringLiteral("[[BATCH]]") + messages.join(QLatin1Char(';')), makeRequest("[[BATCH]]" + messages.join(";"), [](bool success, const QByteArray& res) {
[](bool success, const QByteArray& res) {
if (!success) { if (!success) {
qCWarning(lcHypr) << "batchMessage: request error:" << QString::fromUtf8(res); qWarning() << "HyprExtras::batchMessage: request error:" << QString::fromUtf8(res);
} }
}); });
} }
@@ -241,25 +84,16 @@ void HyprExtras::applyOptions(const QVariantHash& options) {
return; return;
} }
QStringList calls; QString request = "[[BATCH]]";
calls.reserve(options.size());
for (auto it = options.constBegin(); it != options.constEnd(); ++it) { for (auto it = options.constBegin(); it != options.constEnd(); ++it) {
const auto call = buildHlConfigCall(it.key(), it.value()); request += QString("keyword %1 %2;").arg(it.key(), it.value().toString());
if (!call.isEmpty()) {
calls << call;
}
} }
if (calls.isEmpty()) { makeRequest(request, [this](bool success, const QByteArray& res) {
return;
}
makeRequest(QStringLiteral("eval ") + calls.join(QLatin1String("; ")), [this](bool success, const QByteArray& res) {
if (success) { if (success) {
refreshOptions(); refreshOptions();
} else { } else {
qCWarning(lcHypr) << "applyOptions: request error" << QString::fromUtf8(res); qWarning() << "HyprExtras::applyOptions: request error" << QString::fromUtf8(res);
} }
}); });
} }
@@ -269,7 +103,7 @@ void HyprExtras::refreshOptions() {
m_optionsRefresh->close(); m_optionsRefresh->close();
} }
m_optionsRefresh = makeRequestJson(QStringLiteral("descriptions"), [this](bool success, const QJsonDocument& response) { m_optionsRefresh = makeRequestJson("descriptions", [this](bool success, const QJsonDocument& response) {
m_optionsRefresh.reset(); m_optionsRefresh.reset();
if (!success) { if (!success) {
return; return;
@@ -280,9 +114,8 @@ void HyprExtras::refreshOptions() {
for (const auto& o : std::as_const(options)) { for (const auto& o : std::as_const(options)) {
const auto obj = o.toObject(); const auto obj = o.toObject();
const auto key = obj.value(QStringLiteral("value")).toString(); const auto key = obj.value("value").toString();
const auto value = obj.value(QStringLiteral("data")).toObject().value(QStringLiteral("current")).toVariant(); const auto value = obj.value("data").toObject().value("current").toVariant();
if (m_options.value(key) != value) { if (m_options.value(key) != value) {
dirty = true; dirty = true;
m_options.insert(key, value); m_options.insert(key, value);
@@ -300,7 +133,7 @@ void HyprExtras::refreshDevices() {
m_devicesRefresh->close(); m_devicesRefresh->close();
} }
m_devicesRefresh = makeRequestJson(QStringLiteral("devices"), [this](bool success, const QJsonDocument& response) { m_devicesRefresh = makeRequestJson("devices", [this](bool success, const QJsonDocument& response) {
m_devicesRefresh.reset(); m_devicesRefresh.reset();
if (success) { if (success) {
m_devices->updateLastIpcObject(response.object()); m_devices->updateLastIpcObject(response.object());
@@ -310,15 +143,15 @@ void HyprExtras::refreshDevices() {
void HyprExtras::socketError(QLocalSocket::LocalSocketError error) const { void HyprExtras::socketError(QLocalSocket::LocalSocketError error) const {
if (!m_socketValid) { if (!m_socketValid) {
qCWarning(lcHypr) << "socketError: unable to connect to Hyprland event socket:" << error; qWarning() << "HyprExtras::socketError: unable to connect to Hyprland event socket:" << error;
} else { } else {
qCWarning(lcHypr) << "socketError: Hyprland event socket error:" << error; qWarning() << "HyprExtras::socketError: Hyprland event socket error:" << error;
} }
} }
void HyprExtras::socketStateChanged(QLocalSocket::LocalSocketState state) { void HyprExtras::socketStateChanged(QLocalSocket::LocalSocketState state) {
if (state == QLocalSocket::UnconnectedState && m_socketValid) { if (state == QLocalSocket::UnconnectedState && m_socketValid) {
qCWarning(lcHypr) << "socketStateChanged: Hyprland event socket disconnected."; qWarning() << "HyprExtras::socketStateChanged: Hyprland event socket disconnected.";
} }
m_socketValid = state == QLocalSocket::ConnectedState; m_socketValid = state == QLocalSocket::ConnectedState;
@@ -330,23 +163,23 @@ void HyprExtras::readEvent() {
if (rawEvent.isEmpty()) { if (rawEvent.isEmpty()) {
break; break;
} }
rawEvent.truncate(rawEvent.length() - 1); rawEvent.truncate(rawEvent.length() - 1); // Remove trailing \n
const auto event = QByteArrayView(rawEvent.data(), rawEvent.indexOf(">>")); const auto event = QByteArrayView(rawEvent.data(), rawEvent.indexOf(">>"));
handleEvent(QString::fromUtf8(event)); handleEvent(QString::fromUtf8(event));
} }
} }
void HyprExtras::handleEvent(const QString& event) { void HyprExtras::handleEvent(const QString& event) {
if (event == QStringLiteral("configreloaded")) { if (event == "configreloaded") {
refreshOptions(); refreshOptions();
} else if (event == QStringLiteral("activelayout")) { } else if (event == "activelayout") {
refreshDevices(); refreshDevices();
} }
} }
HyprExtras::SocketPtr HyprExtras::makeRequestJson( HyprExtras::SocketPtr HyprExtras::makeRequestJson(
const QString& request, const std::function<void(bool, QJsonDocument)>& callback) { const QString& request, const std::function<void(bool, QJsonDocument)>& callback) {
return makeRequest(QStringLiteral("j/") + request, [callback](bool success, const QByteArray& response) { return makeRequest("j/" + request, [callback](bool success, const QByteArray& response) {
callback(success, QJsonDocument::fromJson(response)); callback(success, QJsonDocument::fromJson(response));
}); });
} }
@@ -371,7 +204,7 @@ HyprExtras::SocketPtr HyprExtras::makeRequest(
}); });
QObject::connect(socket.data(), &QLocalSocket::errorOccurred, this, [=](QLocalSocket::LocalSocketError err) { QObject::connect(socket.data(), &QLocalSocket::errorOccurred, this, [=](QLocalSocket::LocalSocketError err) {
qCWarning(lcHypr) << "makeRequest: error making request:" << err << "| request:" << request; qWarning() << "HyprExtras::makeRequest: error making request:" << err << "| request:" << request;
callback(false, {}); callback(false, {});
socket->close(); socket->close();
}); });
+29 -33
View File
@@ -1,60 +1,56 @@
#pragma once #pragma once
#include "hyprdevices.hpp"
#include <qlocalsocket.h> #include <qlocalsocket.h>
#include <qobject.h> #include <qobject.h>
#include <qqmlintegration.h> #include <qqmlintegration.h>
#include <qsharedpointer.h>
#include <qvariant.h>
namespace ZShell::internal::hypr { namespace ZShell::internal::hypr {
class HyprDevices;
class HyprExtras : public QObject { class HyprExtras : public QObject {
Q_OBJECT Q_OBJECT
QML_ELEMENT QML_ELEMENT
Q_MOC_INCLUDE("hyprdevices.hpp")
Q_PROPERTY(QVariantHash options READ options NOTIFY optionsChanged) Q_PROPERTY(QVariantHash options READ options NOTIFY optionsChanged)
Q_PROPERTY(ZShell::internal::hypr::HyprDevices* devices READ devices CONSTANT) Q_PROPERTY(ZShell::internal::hypr::HyprDevices* devices READ devices CONSTANT)
public: public:
explicit HyprExtras(QObject* parent = nullptr); explicit HyprExtras(QObject* parent = nullptr);
[[nodiscard]] QVariantHash options() const; [[nodiscard]] QVariantHash options() const;
[[nodiscard]] HyprDevices* devices() const; [[nodiscard]] HyprDevices* devices() const;
Q_INVOKABLE void message(const QString& message); Q_INVOKABLE void message(const QString& message);
Q_INVOKABLE void batchMessage(const QStringList& messages); Q_INVOKABLE void batchMessage(const QStringList& messages);
Q_INVOKABLE void applyOptions(const QVariantHash& options); Q_INVOKABLE void applyOptions(const QVariantHash& options);
Q_INVOKABLE void refreshOptions(); Q_INVOKABLE void refreshOptions();
Q_INVOKABLE void refreshDevices(); Q_INVOKABLE void refreshDevices();
signals: signals:
void optionsChanged(); void optionsChanged();
private: private:
using SocketPtr = QSharedPointer<QLocalSocket>; using SocketPtr = QSharedPointer<QLocalSocket>;
QString m_requestSocket; QString m_requestSocket;
QString m_eventSocket; QString m_eventSocket;
QLocalSocket* m_socket; QLocalSocket* m_socket;
bool m_socketValid; bool m_socketValid;
QVariantHash m_options; QVariantHash m_options;
HyprDevices* const m_devices; HyprDevices* const m_devices;
SocketPtr m_optionsRefresh; SocketPtr m_optionsRefresh;
SocketPtr m_devicesRefresh; SocketPtr m_devicesRefresh;
void socketError(QLocalSocket::LocalSocketError error) const; void socketError(QLocalSocket::LocalSocketError error) const;
void socketStateChanged(QLocalSocket::LocalSocketState state); void socketStateChanged(QLocalSocket::LocalSocketState state);
void readEvent(); void readEvent();
void handleEvent(const QString& event); void handleEvent(const QString& event);
SocketPtr makeRequestJson(const QString& request, const std::function<void(bool, QJsonDocument)>& 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); SocketPtr makeRequest(const QString& request, const std::function<void(bool, QByteArray)>& callback);
}; };
} // namespace ZShell::internal::hypr } // namespace ZShell::internal::hypr
-2
View File
@@ -74,8 +74,6 @@ Defaults from `CMakeLists.txt`:
### Nix Flake ### 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: The flake exposes:
- `packages.<system>.zshell` - `packages.<system>.zshell`
+1
View File
@@ -9,6 +9,7 @@ version = "0.1.0"
dependencies = [ dependencies = [
"typer", "typer",
"pillow", "pillow",
"jinja2",
"materialyoucolor" "materialyoucolor"
] ]
+2 -2
View File
@@ -1,6 +1,6 @@
from __future__ import annotations from __future__ import annotations
import typer import typer
from zshell.subcommands import shell, scheme, screenshot, wallpaper from zshell.subcommands import shell, scheme, screenshot, wallpaper, preset
app = typer.Typer() app = typer.Typer()
@@ -8,7 +8,7 @@ app.add_typer(shell.app, name="shell")
app.add_typer(scheme.app, name="scheme") app.add_typer(scheme.app, name="scheme")
app.add_typer(screenshot.app, name="screenshot") app.add_typer(screenshot.app, name="screenshot")
app.add_typer(wallpaper.app, name="wallpaper") app.add_typer(wallpaper.app, name="wallpaper")
# app.add_typer(preset.app, name="preset") app.add_typer(preset.app, name="preset")
def main() -> None: def main() -> None:
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,544 @@
{
"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"
}
}
]
}
}
@@ -1,544 +0,0 @@
_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",
},
},
],
},
}
+61 -12
View File
@@ -1,13 +1,62 @@
# import json from __future__ import annotations
# import typer
# from zshell.assets.schemes.catppuccin import catppuccin
#
# app = typer.Typer()
#
# SCHEMES = catppuccin.variants.flavors
#
#
# @app.command()
# def set():
# TODO: Currently unsused 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}")

Some files were not shown because too many files have changed in this diff Show More