1 Commits

Author SHA1 Message Date
zach 76e55b01e4 update blobs 2026-05-19 04:32:52 +02:00
178 changed files with 4149 additions and 12285 deletions
+26
View File
@@ -0,0 +1,26 @@
name: Format (JS/TS)
on:
pull_request:
jobs:
format:
runs-on: alpine
container: node:20-alpine
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install tools
run: |
apk add --no-cache \
git
- name: Prettier
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 .gitignore
else
echo "No JS/TS files found"
fi
+26 -35
View File
@@ -1,42 +1,33 @@
name: Lint & Format (JS/TS) name: Lint (JS/TS)
on: on:
pull_request: pull_request:
jobs: jobs:
lint-format: lint:
runs-on: alpine runs-on: alpine
container: node:26-alpine container: node:20-alpine
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: Install tools - name: Install tools
run: | run: |
apk add --no-cache \ apk add --no-cache \
git git
- name: Prettier - name: ESLint
continue-on-error: true run: |
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 [ -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
npx --yes prettier --check "**/*.{js,jsx,ts,tsx,mjs,cjs}" --ignore-path .prettierignore npm install --no-audit --no-fund
else fi
echo "No JS/TS files found" 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
fi npx --yes eslint .
else
- name: ESLint echo "No eslint config found"
run: | fi
if [ -n "$(find . \( -iname "*.js" -o -iname "*.jsx" -o -iname "*.ts" -o -iname "*.tsx" -o -iname "*.mjs" -o -iname "*.cjs" \) -print -quit)" ]; then else
if [ -f package.json ]; then echo "No JS/TS files found"
npm install --no-audit --no-fund fi
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
+20 -58
View File
@@ -1,65 +1,27 @@
name: Python name: Lint (Python)
on: on:
pull_request: pull_request:
jobs: jobs:
lint-format: lint:
runs-on: alpine runs-on: alpine
container: node:26-alpine container: node:20-alpine
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: Install tools - name: Install tools
run: | run: |
apk add --no-cache \ apk add --no-cache \
git \ git \
python3 \ python3 \
py3-pip py3-pip
python3 -m venv .venv
. .venv/bin/activate
pip install --no-cache-dir ruff
- name: Format check - name: Ruff
continue-on-error: true run: |
run: | python3 -m venv .venv
. .venv/bin/activate . .venv/bin/activate
ruff format --check . pip install --no-cache-dir ruff
ruff check .
- name: Lint
run: |
. .venv/bin/activate
ruff check .
test:
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 \
py3-pillow \
build-base
python3 -m venv .venv
. .venv/bin/activate
pip install --no-cache-dir \
typer \
pillow \
materialyoucolor \
jinja2 \
pytest
- name: Test
run: |
. .venv/bin/activate
cd cli
python -m pytest tests/ -v
+27 -65
View File
@@ -1,72 +1,34 @@
name: Lint & Format (Rust) name: Lint (Rust)
on: on:
pull_request: pull_request:
jobs: jobs:
lint-format: lint:
runs-on: alpine runs-on: alpine
container: node:26-alpine container: node:20-alpine
env:
CARGO_HOME: ${{ github.workspace }}/.cargo
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: Cache cargo packages - name: Install tools
uses: actions/cache@v4 run: |
env: apk add --no-cache \
cache-name: cache-cargo-packages git \
with: cargo \
path: | rust \
.cargo/registry rust-clippy
.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 - name: Clippy
run: | run: |
apk add --no-cache \ if [ -n "$(find . -name "Cargo.toml" -print -quit)" ]; then
git \ find . -name "Cargo.toml" -print0 | while IFS= read -r -d '' manifest; do
cargo \ cargo clippy --manifest-path "$manifest" -- -D warnings
rust \ done
rustfmt \ elif [ -n "$(find . -name "*.rs" -print -quit)" ]; then
rust-clippy echo "Rust files found but no Cargo.toml"
exit 1
- name: Format check else
continue-on-error: true echo "No Rust project found"
run: | fi
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
-3
View File
@@ -1,3 +0,0 @@
.venv/
scripts/fzf.js
scripts/fuzzysort.js
+1 -8
View File
@@ -31,13 +31,6 @@ if("shell" IN_LIST ENABLE_MODULES)
foreach(dir assets scripts Components Config Modules Daemons Drawers Effects Helpers Paths) foreach(dir assets scripts Components Config Modules Daemons Drawers Effects Helpers Paths)
install(DIRECTORY ${dir} DESTINATION "${INSTALL_QSCONFDIR}") install(DIRECTORY ${dir} DESTINATION "${INSTALL_QSCONFDIR}")
endforeach() endforeach()
install(FILES shell.qml DESTINATION "${INSTALL_QSCONFDIR}")
# Disable watching for changes
file(READ shell.qml SHELL_QML)
string(REPLACE "settings.watchFiles: true" "settings.watchFiles: false" SHELL_QML "${SHELL_QML}")
file(WRITE "${CMAKE_BINARY_DIR}/qml/shell.qml" "${SHELL_QML}")
install(FILES "${CMAKE_BINARY_DIR}/qml/shell.qml" DESTINATION "${INSTALL_QSCONFDIR}")
# Greeter
install(DIRECTORY Greeter/ DESTINATION "${INSTALL_GREETERCONFDIR}") install(DIRECTORY Greeter/ DESTINATION "${INSTALL_GREETERCONFDIR}")
endif() endif()
+2 -16
View File
@@ -8,34 +8,20 @@ Item {
id: root id: root
property alias active: splitButton.active property alias active: splitButton.active
property alias buttonAlias: splitButton
property bool enabled: true property bool enabled: true
property alias expanded: splitButton.expanded property alias expanded: splitButton.expanded
property int expandedZ: 100 property int expandedZ: 100
required property string label required property string label
property alias menuItems: splitButton.menuItems property alias menuItems: splitButton.menuItems
property bool shouldBeActive: true
property alias type: splitButton.type property alias type: splitButton.type
signal selected(item: MenuItem) signal selected(item: MenuItem)
anchors.left: parent.left Layout.fillWidth: true
anchors.right: parent.right Layout.preferredHeight: row.implicitHeight + Appearance.padding.smaller * 2
clip: false clip: false
implicitHeight: row.implicitHeight + Appearance.padding.smaller * 2
opacity: shouldBeActive ? 1 : 0
scale: shouldBeActive ? 1 : 0.8
z: root.expanded ? expandedZ : -1 z: root.expanded ? expandedZ : -1
Behavior on opacity {
Anim {
}
}
Behavior on scale {
Anim {
}
}
RowLayout { RowLayout {
id: row id: row
-1
View File
@@ -15,7 +15,6 @@ Text {
color: DynamicColors.palette.m3onSurface color: DynamicColors.palette.m3onSurface
font.family: Appearance.font.family.sans font.family: Appearance.font.family.sans
font.pointSize: Appearance.font.size.normal font.pointSize: Appearance.font.size.normal
linkColor: DynamicColors.palette.m3onPrimaryFixedVariant
renderType: Text.NativeRendering renderType: Text.NativeRendering
textFormat: Text.PlainText textFormat: Text.PlainText
-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
} }
-1
View File
@@ -58,7 +58,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
-8
View File
@@ -1,13 +1,5 @@
import Quickshell.Io import Quickshell.Io
JsonObject { JsonObject {
property Presets presets: Presets {
}
property string schemeType: "vibrant" property string schemeType: "vibrant"
component Presets: JsonObject {
property string accent: ""
property string name: ""
property string variant: ""
}
} }
+2 -25
View File
@@ -22,7 +22,6 @@ Singleton {
property alias notifs: adapter.notifs property alias notifs: adapter.notifs
property alias osd: adapter.osd property alias osd: adapter.osd
property alias overview: adapter.overview property alias overview: adapter.overview
property alias plugins: adapter.plugins
property bool recentlySaved: false property bool recentlySaved: false
property alias screenshot: adapter.screenshot property alias screenshot: adapter.screenshot
property alias services: adapter.services property alias services: adapter.services
@@ -84,10 +83,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
}; };
@@ -99,7 +94,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,
@@ -116,12 +110,7 @@ Singleton {
function serializeColors(): var { function serializeColors(): var {
return { return {
schemeType: colors.schemeType, schemeType: colors.schemeType
presets: {
name: colors.presets.name,
variant: colors.presets.variant,
accent: colors.presets.accent
}
}; };
} }
@@ -141,8 +130,7 @@ Singleton {
launcher: serializeLauncher(), launcher: serializeLauncher(),
colors: serializeColors(), colors: serializeColors(),
dock: serializeDock(), dock: serializeDock(),
screenshot: serializeScreenshot(), screenshot: serializeScreenshot()
plugins: serializePlugins()
}; };
} }
@@ -248,8 +236,6 @@ Singleton {
return { return {
recolorLogo: lock.recolorLogo, recolorLogo: lock.recolorLogo,
enableFprint: lock.enableFprint, enableFprint: lock.enableFprint,
showNotifContent: lock.showNotifContent,
showNotifIcon: lock.showNotifIcon,
maxFprintTries: lock.maxFprintTries, maxFprintTries: lock.maxFprintTries,
blurAmount: lock.blurAmount, blurAmount: lock.blurAmount,
sizes: { sizes: {
@@ -291,13 +277,6 @@ Singleton {
}; };
} }
function serializePlugins(): var {
return {
enabled: plugins.enabled,
entries: plugins.entries
};
}
function serializeScreenshot(): var { function serializeScreenshot(): var {
return { return {
enable_pp: screenshot.enable_pp, enable_pp: screenshot.enable_pp,
@@ -467,8 +446,6 @@ Singleton {
} }
property Overview overview: Overview { property Overview overview: Overview {
} }
property PluginConfig plugins: PluginConfig {
}
property Screenshot screenshot: Screenshot { property Screenshot screenshot: Screenshot {
} }
property Services services: Services { property Services services: Services {
+6 -38
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
@@ -30,10 +29,9 @@ Singleton {
readonly property alias wallLuminance: analyser.luminance readonly property alias wallLuminance: analyser.luminance
function alterColor(c: color, a: real, layer: int): color { function alterColor(c: color, a: real, layer: int): color {
const initLuminance = getLuminance(c); const luminance = getLuminance(c);
const luminance = Math.max(initLuminance, 0.001);
const offset = (!light || layer == 1 ? 1 : -layer / 2) * (light ? 0.2 : 0.3) * (0.2 + 0.3 * (1 - transparency.base)) * (1 + wallLuminance * (light ? (layer == 1 ? 3 : 1) : 2.5)); const offset = (!light || layer == 1 ? 1 : -layer / 2) * (light ? 0.2 : 0.3) * (1 - transparency.base) * (1 + wallLuminance * (light ? (layer == 1 ? 3 : 1) : 2.5));
const scale = (luminance + offset) / luminance; const scale = (luminance + offset) / luminance;
const r = Math.max(0, Math.min(1, c.r * scale)); const r = Math.max(0, Math.min(1, c.r * scale));
const g = Math.max(0, Math.min(1, c.g * scale)); const g = Math.max(0, Math.min(1, c.g * scale));
@@ -81,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 {
@@ -117,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();
-2
View File
@@ -5,8 +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 bool showNotifIcon: true
property Sizes sizes: Sizes { property Sizes sizes: Sizes {
} }
-11
View File
@@ -1,11 +0,0 @@
import Quickshell.Io
JsonObject {
property bool enabled: false
property list<var> entries: [
{
id: "Plugin",
enabled: false
},
]
}
+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();
} }
} }
+17 -17
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;
} }
@@ -96,25 +96,25 @@ CustomMouseArea {
if (dragY < -10) if (dragY < -10)
visibilities.dock = true; visibilities.dock = true;
if (panels.sidebar.width === 0) { if (panels.sidebar.width === 0) {
const showOsd = inRightPanel(panels.osdWrapper, x, y); const showOsd = inRightPanel(panels.osdWrapper, x, y);
if (showOsd) { if (showOsd) {
osdShortcutActive = false; osdShortcutActive = false;
root.panels.osd.hovered = true; root.panels.osd.hovered = true;
} }
} else { } else {
const outOfSidebar = x < width - panels.sidebar.width; const outOfSidebar = x < width - panels.sidebar.width;
const showOsd = outOfSidebar && inRightPanel(panels.osdWrapper, x, y); const showOsd = outOfSidebar && inRightPanel(panels.osdWrapper, x, y);
if (!osdShortcutActive) { if (!osdShortcutActive) {
visibilities.osd = showOsd; visibilities.osd = showOsd;
root.panels.osd.hovered = showOsd; root.panels.osd.hovered = showOsd;
} else if (showOsd) { } else if (showOsd) {
osdShortcutActive = false; osdShortcutActive = false;
root.panels.osd.hovered = true; root.panels.osd.hovered = true;
}
} }
}
if (Config.dock.enable && !Config.dock.hoverToReveal && !visibilities.dock && !visibilities.launcher && inBottomPanel(panels.dock, x, y)) if (Config.dock.enable && !Config.dock.hoverToReveal && !visibilities.dock && !visibilities.launcher && inBottomPanel(panels.dock, x, y))
visibilities.dock = true; visibilities.dock = true;
+6 -17
View File
@@ -34,7 +34,6 @@ 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 utilities: utilities readonly property alias utilities: utilities
@@ -177,25 +176,15 @@ Item {
visibilities: root.visibilities visibilities: root.visibilities
} }
Item { Settings.Wrapper {
id: settingsWrapper id: settings
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top anchors.top: parent.top
clip: true // anchors.centerIn: parent
implicitHeight: settings.implicitHeight * (1 - settings.offsetScale) panels: root
implicitWidth: settings.implicitWidth screen: root.screen
visibilities: root.visibilities
Settings.Wrapper {
id: settings
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top
// anchors.centerIn: parent
panels: root
screen: root.screen
visibilities: root.visibilities
}
} }
Dock.Wrapper { Dock.Wrapper {
+21 -30
View File
@@ -158,7 +158,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 {
@@ -180,34 +179,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
@@ -216,10 +209,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
@@ -234,7 +227,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
@@ -245,10 +238,12 @@ 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
targetHeight: panels.popouts.height * (1 + extraHeight)
targetWidth: panels.popouts.width
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 : 20 * Appearance.rounding.scale
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
@@ -262,10 +257,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
@@ -274,23 +269,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
} }
@@ -298,7 +287,7 @@ 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
} }
@@ -393,7 +382,9 @@ 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 targetHeight: panel.height
targetWidth: panel.width
deformScale: deformAmount / 10000
group: blobGroup group: blobGroup
implicitHeight: panel.height implicitHeight: panel.height
implicitWidth: panel.width implicitWidth: panel.width
-18
View File
@@ -1,18 +0,0 @@
pragma Singleton
import Quickshell
import ZShell.Models
Singleton {
id: root
property alias plugins: plugins.entries
FileSystemModel {
id: plugins
nameFilters: ["*.qml"]
path: Quickshell.env("HOME") + "/.config/zshell"
recursive: false
}
}
-17
View File
@@ -1,17 +0,0 @@
import Quickshell
import QtQuick
import ZShell.Models
import qs.Config
Repeater {
model: FetchPlugins.plugins
LazyLoader {
required property FileSystemEntry modelData
activeAsync: Config.plugins.entries.some(p => {
return p.id === modelData.baseName && p.enabled;
})
source: modelData.path
}
}
+20 -20
View File
@@ -16,14 +16,27 @@ Scope {
property bool launching: false property bool launching: false
property string promptMessage: "" property string promptMessage: ""
readonly property var selectedSession: sessionIndex >= 0 ? sessions[sessionIndex] : null readonly property var selectedSession: sessionIndex >= 0 ? sessions[sessionIndex] : null
readonly property var selectedUser: Users.selectedUser
property int sessionIndex: sessions.length > 0 ? 0 : -1 property int sessionIndex: sessions.length > 0 ? 0 : -1
property var sessions: [] property var sessions: []
readonly property string userFace: selectedUser ? selectedUser.face : ""
readonly property string username: Users.selectedUsername
// User handling - now uses the Users singleton // User handling - now uses the Users singleton
readonly property var users: Users.users readonly property var users: Users.users
readonly property var selectedUser: Users.selectedUser
readonly property string username: Users.selectedUsername
readonly property string userFace: selectedUser ? selectedUser.face : ""
// User selection functions (delegate to Users singleton)
function selectUser(username: string): bool {
return Users.selectUser(username);
}
function selectNextUser(): void {
Users.selectNext();
}
function selectPreviousUser(): void {
Users.selectPrevious();
}
signal flashMsg signal flashMsg
@@ -45,11 +58,11 @@ Scope {
event.accepted = true; event.accepted = true;
return; return;
} else if (event.key === Qt.Key_Escape) { }
buffer = "";
} else if (" abcdefghijklmnopqrstuvwxyz1234567890`~!@#$%^&*()-_=+[{]}\\|;:'\",<.>/?".includes(event.text.toLowerCase())) { if (event.text && !/[\r\n]/.test(event.text)) {
// No illegal characters (you are insane if you use unicode in your password)
buffer += event.text; buffer += event.text;
event.accepted = true;
} }
} }
@@ -68,19 +81,6 @@ Scope {
Greetd.launch(selectedSession.command, [], true); Greetd.launch(selectedSession.command, [], true);
} }
function selectNextUser(): void {
Users.selectNext();
}
function selectPreviousUser(): void {
Users.selectPrevious();
}
// User selection functions (delegate to Users singleton)
function selectUser(username: string): bool {
return Users.selectUser(username);
}
function submit(): void { function submit(): void {
errorMessage = ""; errorMessage = "";
+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");
}
} }
} }
-63
View File
@@ -1,63 +0,0 @@
// FetchPresets.qml
pragma Singleton
import QtQuick
import Quickshell
import Quickshell.Io
Singleton {
id: root
property var parsedPresets: ({})
readonly property var presets: parsedPresets
property bool ready: false
function accents(presetName, variantName) {
const variant = parsedPresets[presetName]?.variants?.[variantName];
return variant?.accents ?? [];
}
function defaultAccent(presetName, variantName) {
const variant = parsedPresets[presetName]?.variants?.[variantName];
return variant?.default_accent ?? "";
}
function modes(presetName, variantName) {
const variant = parsedPresets[presetName]?.variants?.[variantName];
return variant?.modes ?? [];
}
function presetNames() {
return Object.keys(parsedPresets);
}
function variantNames(presetName) {
const preset = parsedPresets[presetName];
if (!preset || !preset.variants)
return [];
return Object.keys(preset.variants);
}
Process {
command: ["zshell-cli", "scheme", "list-presets", "--json"]
running: true
stdout: StdioCollector {
onStreamFinished: {
try {
const parsed = JSON.parse(text);
root.parsedPresets = parsed.presets ?? {};
root.ready = true;
} catch (e) {
console.error("Failed to parse presets JSON:", e);
}
}
}
}
}
+5 -5
View File
@@ -13,11 +13,11 @@ Singleton {
function setHyprConf(): void { function setHyprConf(): void {
Hypr.extras.applyOptions({ Hypr.extras.applyOptions({
"animations.enabled": 0, "animations:enabled": 0,
"decoration.shadow.enabled": 0, "decoration:shadow:enabled": 0,
"decoration.blur.enabled": 0, "decoration:blur:enabled": 0,
"general.border_size": 0, "general:border_size": 0,
"decoration.rounding": 0 "decoration:rounding": 0
}); });
} }
+1 -1
View File
@@ -12,7 +12,7 @@ Singleton {
readonly property int darkEnd: Config.general.color.scheduleDarkEnd readonly property int darkEnd: Config.general.color.scheduleDarkEnd
readonly property int darkStart: Config.general.color.scheduleDarkStart readonly property int darkStart: Config.general.color.scheduleDarkStart
readonly property bool enabled: Config.general.color.scheduleDark && Config.general.color.schemeGeneration readonly property bool enabled: Config.general.color.scheduleDark
function applyDarkMode() { function applyDarkMode() {
Quickshell.execDetached(["zshell-cli", "scheme", "generate", "--mode", "dark"]); Quickshell.execDetached(["zshell-cli", "scheme", "generate", "--mode", "dark"]);
+41 -82
View File
@@ -1,82 +1,41 @@
pragma Singleton // pragma Singleton
//
import Quickshell // import Quickshell
import Quickshell.Io // import QtQuick
import QtQuick //
// Singleton {
Singleton { // id: root
id: root //
// function start(extraArgs = []): void {
readonly property alias elapsed: props.elapsed // needsStart = true;
property bool needsPause // startArgs = extraArgs;
property bool needsStart // checkProc.running = true;
property bool needsStop // }
readonly property alias paused: props.paused //
readonly property alias running: props.running // PersistentProperties {
property list<string> startArgs // id: props
//
function start(extraArgs = []): void { // property real elapsed: 0
needsStart = true; // property bool paused: false
startArgs = extraArgs; // property bool running: false
checkProc.running = true; //
} // reloadableId: "recorder"
// }
function stop(): void { //
needsStop = true; // Process {
checkProc.running = true; // id: checkProc
} //
// command: ["pidof", "gpu-screen-recorder"]
function togglePause(): void { // running: true
needsPause = true; //
checkProc.running = true; // onExited: code => {
} // props.running = code === 0;
//
PersistentProperties { // if (code === 0) {
id: props // if (root.needsStop) {
// Quickshell.execDetached(["zshell-cli"]);
property real elapsed: 0 // }
property bool paused: false // }
property bool running: false // }
// }
reloadableId: "recorder" // }
}
Process {
id: checkProc
command: ["pidof", "gpu-screen-recorder"]
running: true
onExited: code => {
props.running = code === 0;
if (code === 0) {
if (root.needsStop) {
Quickshell.execDetached(["zshell-cli", "record", "record"]);
props.running = false;
props.paused = false;
} else if (root.needsPause) {
Quickshell.execDetached(["zshell-cli", "record", "record", "-p"]);
props.paused = !props.paused;
}
} else if (root.needsStart) {
Quickshell.execDetached(["zshell-cli", "record", "record", ...root.startArgs]);
props.running = true;
props.paused = false;
props.elapsed = 0;
}
root.needsStart = false;
root.needsStop = false;
root.needsPause = false;
}
}
Connections {
function onSecondsChanged(): void {
props.elapsed++;
}
target: Time // qmllint disable incompatible-type
}
}
+1 -1
View File
@@ -1,7 +1,7 @@
import Quickshell
import "../scripts/fzf.js" as Fzf import "../scripts/fzf.js" as Fzf
import "../scripts/fuzzysort.js" as Fuzzy import "../scripts/fuzzysort.js" as Fuzzy
import QtQuick import QtQuick
import Quickshell
Singleton { Singleton {
property var extraOpts: ({}) property var extraOpts: ({})
-14
View File
@@ -6,21 +6,7 @@ import Quickshell.Services.UPower
Singleton { Singleton {
id: root id: root
readonly property real batteryPercent: UPower.displayDevice.percentage
readonly property list<UPowerDevice> devices: UPower.devices.values readonly property list<UPowerDevice> devices: UPower.devices.values
readonly property UPowerDevice displayDevice: UPower.displayDevice readonly property UPowerDevice displayDevice: UPower.displayDevice
readonly property bool onBattery: UPower.onBattery readonly property bool onBattery: UPower.onBattery
// property bool toastShown
//
// Connections {
// target: UPower
//
// function onPercentageChanged(): {
// if (root.batteryPercent >= 0.2 && toastShown)
// return;
//
// root.toastShown = true;
// Toaster.toast(qsTr("Battery "))
// }
// }
} }
-51
View File
@@ -1,9 +1,7 @@
pragma Singleton pragma Singleton
pragma ComponentBehavior: Bound
import Quickshell import Quickshell
import Quickshell.Io import Quickshell.Io
import QtQuick
import ZShell.Models import ZShell.Models
import qs.Config import qs.Config
import qs.Modules import qs.Modules
@@ -14,17 +12,10 @@ Searcher {
id: root id: root
property string actualCurrent: WallpaperPath.currentWallpaperPath property string actualCurrent: WallpaperPath.currentWallpaperPath
property alias crops: adapter.monitorCrops
readonly property string current: showPreview ? previewPath : actualCurrent readonly property string current: showPreview ? previewPath : actualCurrent
property alias monitorCrops: monitorCrops
property string previewPath property string previewPath
property bool recentlyChanged
property bool showPreview: false property bool showPreview: false
function getCrop(screen: string): var {
return root.crops[screen];
}
function preview(path: string): void { function preview(path: string): void {
previewPath = path; previewPath = path;
if (Config.general.color.schemeGeneration) if (Config.general.color.schemeGeneration)
@@ -32,35 +23,9 @@ Searcher {
showPreview = true; showPreview = true;
} }
function setCrop(screen: string, rect: rect, scaledRect: rect, zoom: real): void {
let updated = Object.assign({}, root.crops);
if (zoom <= 0)
zoom = 1.0;
else if (zoom > 5.0)
zoom = 5.0;
updated[screen] = {
x: rect.x,
y: rect.y,
width: rect.width,
height: rect.height,
scaledX: scaledRect.x,
scaledY: scaledRect.y,
scaledWidth: scaledRect.width,
scaledHeight: scaledRect.height,
zoom: zoom
};
root.crops = updated;
monitorCrops.writeAdapter();
monitorCrops.reload();
}
function setWallpaper(path: string): void { function setWallpaper(path: string): void {
actualCurrent = path; actualCurrent = path;
WallpaperPath.currentWallpaperPath = path; WallpaperPath.currentWallpaperPath = path;
Quickshell.screens.forEach(n => setCrop(n.name, Qt.rect(0, 0, 0, 0), Qt.rect(0, 0, 0, 0), 1.0));
Quickshell.execDetached(["zshell-cli", "wallpaper", "lockscreen", "--input-image", `${root.actualCurrent}`, "--output-path", `${Paths.state}/lockscreen_bg.png`, "--blur-amount", `${Config.lock.blurAmount}`]); Quickshell.execDetached(["zshell-cli", "wallpaper", "lockscreen", "--input-image", `${root.actualCurrent}`, "--output-path", `${Paths.state}/lockscreen_bg.png`, "--blur-amount", `${Config.lock.blurAmount}`]);
if (Config.general.color.schemeGeneration) if (Config.general.color.schemeGeneration)
Quickshell.execDetached(["zshell-cli", "scheme", "generate", "--image-path", `${root.actualCurrent}`, "--scheme", `${Config.colors.schemeType}`, "--mode", `${Config.general.color.mode}`]); Quickshell.execDetached(["zshell-cli", "scheme", "generate", "--image-path", `${root.actualCurrent}`, "--scheme", `${Config.colors.schemeType}`, "--mode", `${Config.general.color.mode}`]);
@@ -87,22 +52,6 @@ Searcher {
target: "wallpaper" target: "wallpaper"
} }
FileView {
id: monitorCrops
path: `${Paths.state}/wallpaper-crops.json`
watchChanges: true
onAdapterUpdated: writeAdapter()
onFileChanged: reload()
JsonAdapter {
id: adapter
property var monitorCrops: ({})
}
}
FileSystemModel { FileSystemModel {
id: wallpapers id: wallpapers
+77 -25
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: [
Anim { State {
duration: Appearance.anim.durations.expressiveDefaultSpatial name: "hidden"
easing.bezierCurve: Appearance.anim.curves.expressiveDefaultSpatial when: !root.shouldBeActive
PropertyChanges {
root.implicitWidth: 0
}
PropertyChanges {
icon.opacity: 0
}
PropertyChanges {
content.opacity: 0
}
},
State {
name: "collapsed"
when: root.shouldBeActive && !root.expanded
PropertyChanges {
root.implicitWidth: icon.implicitWidth
}
PropertyChanges {
icon.opacity: 1
}
PropertyChanges {
content.opacity: 0
}
},
State {
name: "visible"
when: root.shouldBeActive && root.expanded
PropertyChanges {
root.implicitWidth: content.implicitWidth
}
PropertyChanges {
icon.opacity: 0
}
PropertyChanges {
content.opacity: 1
}
} }
} ]
Behavior on offsetScale { transitions: [
Anim { Transition {
duration: Appearance.anim.durations.expressiveDefaultSpatial from: "*"
easing.bezierCurve: Appearance.anim.curves.expressiveDefaultSpatial to: "*"
ParallelAnimation {
Anim {
easing.bezierCurve: MaterialEasing.expressiveEffects
property: "implicitWidth"
target: root
}
Anim {
duration: Appearance.anim.durations.small
property: "opacity"
target: icon
}
Anim {
duration: Appearance.anim.durations.small
property: "opacity"
target: content
}
}
} }
} ]
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({
-3
View File
@@ -58,7 +58,6 @@ CustomRect {
fillMode: Image.PreserveAspectCrop fillMode: Image.PreserveAspectCrop
height: Config.notifs.sizes.image height: Config.notifs.sizes.image
source: Qt.resolvedUrl(root.image) source: Qt.resolvedUrl(root.image)
visible: Config.lock.showNotifIcon
width: Config.notifs.sizes.image width: Config.notifs.sizes.image
} }
} }
@@ -285,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;
-2
View File
@@ -30,8 +30,6 @@ Scope {
} else { } else {
buffer = buffer.slice(0, -1); buffer = buffer.slice(0, -1);
} }
} else if (event.key === Qt.Key_Escape) {
buffer = "";
} else if (" abcdefghijklmnopqrstuvwxyz1234567890`~!@#$%^&*()-_=+[{]}\\|;:'\",<.>/?".includes(event.text.toLowerCase())) { } else if (" abcdefghijklmnopqrstuvwxyz1234567890`~!@#$%^&*()-_=+[{]}\\|;:'\",<.>/?".includes(event.text.toLowerCase())) {
// No illegal characters (you are insane if you use unicode in your password) // No illegal characters (you are insane if you use unicode in your password)
buffer += event.text; buffer += event.text;
+1 -4
View File
@@ -136,10 +136,7 @@ CustomRect {
wrapMode: Text.WordWrap wrapMode: Text.WordWrap
onLinkActivated: link => { onLinkActivated: link => {
if (Config.launcher.uwsm) Quickshell.execDetached(["app2unit", "-O", "--", link]);
Quickshell.execDetached(["app2unit", "-O", "--", link]);
else
Quickshell.execDetached(["xdg-open", link]);
root.visibilities.sidebar = false; root.visibilities.sidebar = false;
} }
} }
@@ -1,290 +0,0 @@
pragma ComponentBehavior: Bound
import Quickshell
import QtQuick
import QtQuick.Layouts
import qs.Components
import qs.Config
import qs.Helpers
CustomRect {
id: root
required property var props
required property PersistentProperties visibilities
Layout.fillWidth: true
color: DynamicColors.tPalette.m3surfaceContainer
implicitHeight: layout.implicitHeight + layout.anchors.margins * 2
radius: Appearance.rounding.smallest
ColumnLayout {
id: layout
anchors.fill: parent
anchors.margins: Appearance.padding.large
spacing: Appearance.spacing.normal
RowLayout {
spacing: Appearance.spacing.normal
z: 1
CustomRect {
color: Recorder.running ? DynamicColors.palette.m3secondary : DynamicColors.palette.m3secondaryContainer
implicitHeight: {
const h = icon.implicitHeight + Appearance.padding.smaller * 2;
return h - (h % 2);
}
implicitWidth: implicitHeight
radius: Appearance.rounding.full
MaterialIcon {
id: icon
anchors.centerIn: parent
anchors.horizontalCenterOffset: -0.5
anchors.verticalCenterOffset: 1.5
color: Recorder.running ? DynamicColors.palette.m3onSecondary : DynamicColors.palette.m3onSecondaryContainer
font.pointSize: Appearance.font.size.large
text: "screen_record"
}
}
ColumnLayout {
Layout.fillWidth: true
spacing: 0
CustomText {
Layout.fillWidth: true
elide: Text.ElideRight
font.pointSize: Appearance.font.size.normal
text: qsTr("Screen Recorder")
}
CustomText {
Layout.fillWidth: true
color: DynamicColors.palette.m3onSurfaceVariant
elide: Text.ElideRight
font.pointSize: Appearance.font.size.small
text: Recorder.paused ? qsTr("Recording paused") : Recorder.running ? qsTr("Recording running") : qsTr("Recording off")
}
}
CustomSplitButton {
active: menuItems.find(m => root.props.recordingMode === m.icon + m.text) ?? menuItems[0]
disabled: Recorder.running
menuItems: [
MenuItem {
activeText: qsTr("Fullscreen")
icon: "fullscreen"
text: qsTr("Record fullscreen")
onClicked: Recorder.start()
},
MenuItem {
activeText: qsTr("Region")
icon: "screenshot_region"
text: qsTr("Record region")
onClicked: Recorder.start(["-r"])
},
MenuItem {
activeText: qsTr("Fullscreen")
icon: "select_to_speak"
text: qsTr("Record fullscreen with sound")
onClicked: Recorder.start(["-s"])
},
MenuItem {
activeText: qsTr("Region")
icon: "volume_up"
text: qsTr("Record region with sound")
onClicked: Recorder.start(["-s", "-r"])
}
]
menu.onItemSelected: item => root.props.recordingMode = item.icon + item.text
}
}
Loader {
id: listOrControls
property bool running: Recorder.running
Layout.fillWidth: true
Layout.preferredHeight: implicitHeight
asynchronous: true
sourceComponent: running ? recordingControls : recordingList
Behavior on Layout.preferredHeight {
id: locHeightAnim
enabled: false
Anim {
}
}
Behavior on running {
SequentialAnimation {
ParallelAnimation {
Anim {
duration: Appearance.anim.durations.small
easing: Appearance.anim.curves.standardAccel
property: "scale"
target: listOrControls
to: 0.7
}
Anim {
duration: Appearance.anim.durations.small
easing: Appearance.anim.curves.standardAccel
property: "opacity"
target: listOrControls
to: 0
}
}
PropertyAction {
property: "enabled"
target: locHeightAnim
value: true
}
PropertyAction {
}
PropertyAction {
property: "enabled"
target: locHeightAnim
value: false
}
ParallelAnimation {
Anim {
duration: Appearance.anim.durations.small
easing: Appearance.anim.curves.standardDecel
property: "scale"
target: listOrControls
to: 1
}
Anim {
duration: Appearance.anim.durations.small
easing: Appearance.anim.curves.standardDecel
property: "opacity"
target: listOrControls
to: 1
}
}
}
}
}
}
Component {
id: recordingList
RecordingList {
props: root.props
visibilities: root.visibilities
}
}
Component {
id: recordingControls
RowLayout {
spacing: Appearance.spacing.normal
CustomRect {
color: Recorder.paused ? DynamicColors.palette.m3tertiary : DynamicColors.palette.m3error
implicitHeight: recText.implicitHeight + Appearance.padding.smaller * 2
implicitWidth: recText.implicitWidth + Appearance.padding.normal * 2
radius: Appearance.rounding.full
Behavior on implicitWidth {
Anim {
}
}
SequentialAnimation on opacity {
alwaysRunToEnd: true
loops: Animation.Infinite
running: !Recorder.paused
Anim {
duration: Appearance.anim.durations.large
easing: Appearance.anim.curves.emphasizedAccel
from: 1
to: 0
}
Anim {
duration: Appearance.anim.durations.extraLarge
easing: Appearance.anim.curves.emphasizedDecel
from: 0
to: 1
}
}
CustomText {
id: recText
anchors.centerIn: parent
animate: true
color: Recorder.paused ? DynamicColors.palette.m3onTertiary : DynamicColors.palette.m3onError
font.family: Appearance.font.family.mono
text: Recorder.paused ? "PAUSED" : "REC"
}
}
CustomText {
font.pointSize: Appearance.font.size.normal
text: {
const elapsed = Recorder.elapsed;
const hours = Math.floor(elapsed / 3600);
const mins = Math.floor((elapsed % 3600) / 60);
const secs = Math.floor(elapsed % 60).toString().padStart(2, "0");
let time;
if (hours > 0)
time = `${hours}:${mins.toString().padStart(2, "0")}:${secs}`;
else
time = `${mins}:${secs}`;
return qsTr("Recording for %1").arg(time);
}
}
Item {
Layout.fillWidth: true
}
IconButton {
checked: Recorder.paused
font.pointSize: Appearance.font.size.large
icon: Recorder.paused ? "play_arrow" : "pause"
label.animate: true
toggle: true
type: IconButton.Tonal
onClicked: {
Recorder.togglePause();
internalChecked = Recorder.paused;
}
}
IconButton {
font.pointSize: Appearance.font.size.large
icon: "stop"
inactiveColour: DynamicColors.palette.m3error
inactiveOnColour: DynamicColors.palette.m3onError
onClicked: Recorder.stop()
}
}
}
}
@@ -1,226 +0,0 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import Quickshell
import Quickshell.Widgets
import ZShell.Models
import qs.Components
import qs.Helpers
import qs.Paths
import qs.Config
ColumnLayout {
id: root
required property var props
required property PersistentProperties visibilities
spacing: 0
WrapperMouseArea {
Layout.fillWidth: true
cursorShape: Qt.PointingHandCursor
onClicked: root.props.recordingListExpanded = !root.props.recordingListExpanded
RowLayout {
spacing: Appearance.spacing.smaller
MaterialIcon {
Layout.alignment: Qt.AlignVCenter
font.pointSize: Appearance.font.size.large
text: "list"
}
CustomText {
Layout.alignment: Qt.AlignVCenter
Layout.fillWidth: true
font.pointSize: Appearance.font.size.normal
text: qsTr("Recordings")
}
IconButton {
icon: root.props.recordingListExpanded ? "unfold_less" : "unfold_more"
label.animate: true
type: IconButton.Text
onClicked: root.props.recordingListExpanded = !root.props.recordingListExpanded
}
}
}
CustomListView {
id: list
Layout.fillWidth: true
Layout.rightMargin: -Appearance.spacing.small
clip: true
implicitHeight: (Appearance.font.size.larger + Appearance.padding.small) * (root.props.recordingListExpanded ? 10 : 3)
CustomScrollBar.vertical: CustomScrollBar {
flickable: list
}
add: Transition {
Anim {
from: 0
property: "opacity"
to: 1
}
Anim {
from: 0.5
property: "scale"
to: 1
}
}
delegate: RowLayout {
id: recording
property string baseName
required property FileSystemEntry modelData
anchors.left: list.contentItem.left
anchors.right: list.contentItem.right
anchors.rightMargin: Appearance.spacing.small
spacing: Appearance.spacing.small / 2
Component.onCompleted: baseName = modelData.baseName
CustomText {
Layout.fillWidth: true
Layout.rightMargin: Appearance.spacing.small / 2
color: DynamicColors.palette.m3onSurfaceVariant
elide: Text.ElideRight
text: {
const time = recording.baseName;
const matches = time.match(/^recording_(\d{4})(\d{2})(\d{2})_(\d{2})-(\d{2})-(\d{2})/);
if (!matches)
return time;
const date = new Date(...matches.slice(1));
date.setMonth(date.getMonth() - 1);
return qsTr("Recording at %1").arg(Qt.formatDateTime(date, Qt.locale()));
}
}
IconButton {
icon: "play_arrow"
type: IconButton.Text
onClicked: {
root.visibilities.sidebar = false;
Quickshell.execDetached(["app2unit", "--", ...Config.general.apps.playback, recording.modelData.path]);
}
}
IconButton {
icon: "folder"
type: IconButton.Text
onClicked: {
root.visibilities.sidebar = false;
Quickshell.execDetached(["app2unit", "--", ...Config.general.apps.explorer, recording.modelData.path]);
}
}
}
displaced: Transition {
Anim {
properties: "opacity,scale"
to: 1
}
Anim {
property: "y"
}
}
Behavior on implicitHeight {
Anim {
}
}
model: FileSystemModel {
nameFilters: ["recording_*.mp4"]
path: Paths.recsdir
sortReverse: true
}
remove: Transition {
Anim {
property: "opacity"
to: 0
}
Anim {
property: "scale"
to: 0.5
}
}
Loader {
active: opacity > 0
anchors.centerIn: parent
asynchronous: true
opacity: list.count === 0 ? 1 : 0
Behavior on opacity {
Anim {
}
}
sourceComponent: ColumnLayout {
spacing: Appearance.spacing.small
MaterialIcon {
Layout.alignment: Qt.AlignHCenter
Layout.preferredHeight: root.props.recordingListExpanded ? implicitHeight : 0
color: DynamicColors.palette.m3outline
font.pointSize: Appearance.font.size.extraLarge
opacity: root.props.recordingListExpanded ? 1 : 0
scale: root.props.recordingListExpanded ? 1 : 0
text: "scan_delete"
Behavior on Layout.preferredHeight {
Anim {
}
}
Behavior on opacity {
Anim {
}
}
Behavior on scale {
Anim {
}
}
}
RowLayout {
spacing: Appearance.spacing.smaller
MaterialIcon {
Layout.alignment: Qt.AlignHCenter
Layout.preferredWidth: !root.props.recordingListExpanded ? implicitWidth : 0
color: DynamicColors.palette.m3outline
opacity: !root.props.recordingListExpanded ? 1 : 0
scale: !root.props.recordingListExpanded ? 1 : 0
text: "scan_delete"
Behavior on Layout.preferredWidth {
Anim {
}
}
Behavior on opacity {
Anim {
}
}
Behavior on scale {
Anim {
}
}
}
CustomText {
color: DynamicColors.palette.m3outline
text: qsTr("No recordings found")
}
}
}
}
}
}
@@ -1,14 +1,13 @@
import Quickshell
import QtQuick
import QtQuick.Layouts
import qs.Modules.Notifications.Sidebar.Utils.Cards import qs.Modules.Notifications.Sidebar.Utils.Cards
import qs.Config import qs.Config
import QtQuick
import QtQuick.Layouts
Item { Item {
id: root id: root
required property Item popouts required property Item popouts
required property PersistentProperties props required property var props
required property var visibilities required property var visibilities
implicitHeight: layout.implicitHeight implicitHeight: layout.implicitHeight
@@ -23,12 +22,6 @@ Item {
IdleInhibit { IdleInhibit {
} }
Record {
props: root.props
visibilities: root.visibilities
z: 1
}
Toggles { Toggles {
popouts: root.popouts popouts: root.popouts
visibilities: root.visibilities visibilities: root.visibilities
+6 -8
View File
@@ -100,14 +100,12 @@ Item {
icon: `brightness_${(Math.round(value * 6) + 1)}` icon: `brightness_${(Math.round(value * 6) + 1)}`
value: root.brightness value: root.brightness
onPressedChanged: { onMoved: {
if (!pressed) { if (Config.osd.allMonBrightness) {
if (Config.osd.allMonBrightness) { root.monitor?.setBrightness(value);
for (const mon of Brightness.monitors) { } else {
mon.setBrightness(value); for (const mon of Brightness.monitors) {
} mon.setBrightness(value);
} else {
root.monitor?.setBrightness(value);
} }
} }
} }
-6
View File
@@ -116,12 +116,6 @@ Item {
key: "updates" key: "updates"
name: "Updates" name: "Updates"
} }
ListElement {
icon: "extension"
key: "plugins"
name: "Extensions"
}
} }
CustomClippingRect { CustomClippingRect {
@@ -1,4 +1,3 @@
import QtQuick
import qs.Modules.Settings.Controls import qs.Modules.Settings.Controls
import qs.Config import qs.Config
@@ -81,7 +80,6 @@ SettingsPage {
name: "Sans family" name: "Sans family"
object: Config.appearance.font.family object: Config.appearance.font.family
setting: "sans" setting: "sans"
stringList: Qt.fontFamilies()
} }
Separator { Separator {
@@ -91,7 +89,6 @@ SettingsPage {
name: "Monospace family" name: "Monospace family"
object: Config.appearance.font.family object: Config.appearance.font.family
setting: "mono" setting: "mono"
stringList: Qt.fontFamilies()
} }
} }
+8 -8
View File
@@ -1,4 +1,3 @@
import Quickshell
import QtQuick.Layouts import QtQuick.Layouts
import qs.Modules.Settings.Controls import qs.Modules.Settings.Controls
import qs.Config import qs.Config
@@ -6,8 +5,6 @@ import qs.Config
SettingsPage { SettingsPage {
id: root id: root
required property ShellScreen screen
SettingsSection { SettingsSection {
sectionId: "Wallpaper" sectionId: "Wallpaper"
@@ -32,17 +29,20 @@ SettingsPage {
step: 50 step: 50
} }
Separator { // Separator {
} // }
//
WallpaperCropper { // WallpaperCropper {
} // Layout.fillWidth: true
// Layout.preferredHeight: 300
// }
} }
SettingsSection { SettingsSection {
sectionId: "Wallpapers" sectionId: "Wallpapers"
WallpaperGrid { WallpaperGrid {
Layout.fillWidth: true
} }
} }
} }
+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"
} }
+111 -111
View File
@@ -19,8 +19,8 @@ SettingsPage {
} }
SettingSpinBox { SettingSpinBox {
min: 0
name: "Media update interval" name: "Media update interval"
min: 0
object: Config.dashboard object: Config.dashboard
setting: "mediaUpdateInterval" setting: "mediaUpdateInterval"
step: 50 step: 50
@@ -30,8 +30,8 @@ SettingsPage {
} }
SettingSpinBox { SettingSpinBox {
min: 0
name: "Resource update interval" name: "Resource update interval"
min: 0
object: Config.dashboard object: Config.dashboard
setting: "resourceUpdateInterval" setting: "resourceUpdateInterval"
step: 50 step: 50
@@ -41,8 +41,8 @@ SettingsPage {
} }
SettingSpinBox { SettingSpinBox {
min: 0
name: "Drag threshold" name: "Drag threshold"
min: 0
object: Config.dashboard object: Config.dashboard
setting: "dragThreshold" setting: "dragThreshold"
} }
@@ -107,112 +107,112 @@ SettingsPage {
} }
} }
// SettingsSection { SettingsSection {
// sectionId: "Layout Sizes" sectionId: "Layout Sizes"
//
// SettingsHeader { SettingsHeader {
// name: "Layout Sizes" name: "Layout Sizes"
// } }
//
// SettingReadOnly { SettingReadOnly {
// name: "Tab indicator height" name: "Tab indicator height"
// value: String(Config.dashboard.sizes.tabIndicatorHeight) value: String(Config.dashboard.sizes.tabIndicatorHeight)
// } }
//
// Separator { Separator {
// } }
//
// SettingReadOnly { SettingReadOnly {
// name: "Tab indicator spacing" name: "Tab indicator spacing"
// value: String(Config.dashboard.sizes.tabIndicatorSpacing) value: String(Config.dashboard.sizes.tabIndicatorSpacing)
// } }
//
// Separator { Separator {
// } }
//
// SettingReadOnly { SettingReadOnly {
// name: "Info width" name: "Info width"
// value: String(Config.dashboard.sizes.infoWidth) value: String(Config.dashboard.sizes.infoWidth)
// } }
//
// Separator { Separator {
// } }
//
// SettingReadOnly { SettingReadOnly {
// name: "Info icon size" name: "Info icon size"
// value: String(Config.dashboard.sizes.infoIconSize) value: String(Config.dashboard.sizes.infoIconSize)
// } }
//
// Separator { Separator {
// } }
//
// SettingReadOnly { SettingReadOnly {
// name: "Date time width" name: "Date time width"
// value: String(Config.dashboard.sizes.dateTimeWidth) value: String(Config.dashboard.sizes.dateTimeWidth)
// } }
//
// Separator { Separator {
// } }
//
// SettingReadOnly { SettingReadOnly {
// name: "Media width" name: "Media width"
// value: String(Config.dashboard.sizes.mediaWidth) value: String(Config.dashboard.sizes.mediaWidth)
// } }
//
// Separator { Separator {
// } }
//
// SettingReadOnly { SettingReadOnly {
// name: "Media progress sweep" name: "Media progress sweep"
// value: String(Config.dashboard.sizes.mediaProgressSweep) value: String(Config.dashboard.sizes.mediaProgressSweep)
// } }
//
// Separator { Separator {
// } }
//
// SettingReadOnly { SettingReadOnly {
// name: "Media progress thickness" name: "Media progress thickness"
// value: String(Config.dashboard.sizes.mediaProgressThickness) value: String(Config.dashboard.sizes.mediaProgressThickness)
// } }
//
// Separator { Separator {
// } }
//
// SettingReadOnly { SettingReadOnly {
// name: "Resource progress thickness" name: "Resource progress thickness"
// value: String(Config.dashboard.sizes.resourceProgessThickness) value: String(Config.dashboard.sizes.resourceProgessThickness)
// } }
//
// Separator { Separator {
// } }
//
// SettingReadOnly { SettingReadOnly {
// name: "Weather width" name: "Weather width"
// value: String(Config.dashboard.sizes.weatherWidth) value: String(Config.dashboard.sizes.weatherWidth)
// } }
//
// Separator { Separator {
// } }
//
// SettingReadOnly { SettingReadOnly {
// name: "Media cover art size" name: "Media cover art size"
// value: String(Config.dashboard.sizes.mediaCoverArtSize) value: String(Config.dashboard.sizes.mediaCoverArtSize)
// } }
//
// Separator { Separator {
// } }
//
// SettingReadOnly { SettingReadOnly {
// name: "Media visualiser size" name: "Media visualiser size"
// value: String(Config.dashboard.sizes.mediaVisualiserSize) value: String(Config.dashboard.sizes.mediaVisualiserSize)
// } }
//
// Separator { Separator {
// } }
//
// SettingReadOnly { SettingReadOnly {
// name: "Resource size" name: "Resource size"
// value: String(Config.dashboard.sizes.resourceSize) value: String(Config.dashboard.sizes.resourceSize)
// } }
// } }
} }
-52
View File
@@ -1,6 +1,4 @@
import Quickshell import Quickshell
import QtQuick
import QtQuick.Layouts
import qs.Modules.Settings.Controls import qs.Modules.Settings.Controls
import qs.Config import qs.Config
import qs.Components import qs.Components
@@ -69,7 +67,6 @@ SettingsPage {
CustomSplitButtonRow { CustomSplitButtonRow {
active: Config.general.color.mode === "light" ? menuItems[0] : menuItems[1] active: Config.general.color.mode === "light" ? menuItems[0] : menuItems[1]
buttonAlias.disabled: !Config.general.color.schemeGeneration
label: qsTr("Scheme mode") label: qsTr("Scheme mode")
menuItems: [ menuItems: [
@@ -103,7 +100,6 @@ SettingsPage {
id: schemeType id: schemeType
active: root.schemeTypeItem(menuItems, Config.colors.schemeType) active: root.schemeTypeItem(menuItems, Config.colors.schemeType)
buttonAlias.disabled: !Config.general.color.schemeGeneration
label: qsTr("Scheme type") label: qsTr("Scheme type")
z: 2 z: 2
@@ -173,69 +169,21 @@ SettingsPage {
} }
Separator { Separator {
shouldBeActive: Config.general.color.schemeGeneration ? 0 : 1
}
SchemesListView {
name: "Color scheme presets"
object: Config.colors.presets
setting: "name"
shouldBeActive: Config.general.color.schemeGeneration ? 0 : 1
stringList: FetchPresets.presetNames()
}
Separator {
shouldBeActive: Config.colors.presets.name !== "" && !Config.general.color.schemeGeneration
}
SchemesListView {
name: "Preset variant"
object: Config.colors.presets
setting: "variant"
shouldBeActive: Config.colors.presets.name !== "" && !Config.general.color.schemeGeneration
stringList: FetchPresets.variantNames(Config.colors.presets.name)
onOptionSet: item => {
Quickshell.execDetached(["zshell-cli", "scheme", "generate", "--preset", `${Config.colors.presets.name.toLowerCase()}:${item}`]);
}
}
Separator {
shouldBeActive: Config.colors.presets.variant !== "" && FetchPresets.accents(Config.colors.presets.name, Config.colors.presets.variant).length > 0 && !Config.general.color.schemeGeneration
}
SchemesListView {
name: "Preset accent"
object: Config.colors.presets
setting: "accent"
shouldBeActive: Config.colors.presets.variant !== "" && FetchPresets.accents(Config.colors.presets.name, Config.colors.presets.variant).length > 0 && !Config.general.color.schemeGeneration
stringList: FetchPresets.accents(Config.colors.presets.name, Config.colors.presets.variant)
onOptionSet: item => {
Quickshell.execDetached(["zshell-cli", "scheme", "generate", "--preset", `${Config.colors.presets.name.toLowerCase()}:${Config.colors.presets.variant}`, "--accent", `${item}`]);
}
}
Separator {
shouldBeActive: Config.general.color.schemeGeneration ? 1 : 0
} }
SettingSwitch { SettingSwitch {
name: "Smart color scheme" name: "Smart color scheme"
object: Config.general.color object: Config.general.color
setting: "smart" setting: "smart"
shouldBeActive: Config.general.color.schemeGeneration ? 1 : 0
} }
Separator { Separator {
shouldBeActive: Config.general.color.schemeGeneration ? 1 : 0
} }
SettingSpinner { SettingSpinner {
name: "Schedule dark mode" name: "Schedule dark mode"
object: Config.general.color object: Config.general.color
settings: ["scheduleDarkStart", "scheduleDarkEnd", "scheduleDark"] settings: ["scheduleDarkStart", "scheduleDarkEnd", "scheduleDark"]
shouldBeActive: Config.general.color.schemeGeneration ? 1 : 0
} }
Separator { Separator {
+5 -35
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,27 +41,9 @@ SettingsPage {
Separator { Separator {
} }
SettingSwitch {
name: "Show notification details"
object: Config.lock
setting: "showNotifContent"
}
Separator {
}
SettingSwitch {
name: "Show notification icon"
object: Config.lock
setting: "showNotifIcon"
}
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
@@ -71,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
@@ -83,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
@@ -95,26 +77,14 @@ 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
} }
} }
SettingsSection {
sectionId: "Greeter"
SettingsHeader {
name: "Greeter"
}
SettingsIconButton {
name: "Install wallpaper and color scheme to greeter"
}
}
SettingsSection { SettingsSection {
sectionId: "Idle" sectionId: "Idle"
@@ -9,8 +9,6 @@ import qs.Modules.Settings.Controls
ColumnLayout { ColumnLayout {
id: root id: root
property bool shouldBeActive: true
function addTimeoutEntry() { function addTimeoutEntry() {
let list = [...Config.general.idle.timeouts]; let list = [...Config.general.idle.timeouts];
@@ -42,26 +40,8 @@ ColumnLayout {
Config.save(); Config.save();
} }
anchors.left: parent.left Layout.fillWidth: true
anchors.right: parent.right
height: shouldBeActive ? implicitHeight : 0
opacity: shouldBeActive ? 1 : 0
scale: shouldBeActive ? 1 : 0.8
spacing: Appearance.spacing.smaller spacing: Appearance.spacing.smaller
visible: opacity > 0
Behavior on opacity {
Anim {
}
}
Behavior on scale {
Anim {
}
}
Behavior on y {
Anim {
}
}
Settings { Settings {
name: "Idle Monitors" name: "Idle Monitors"
@@ -72,8 +52,6 @@ ColumnLayout {
SettingList { SettingList {
Layout.fillWidth: true Layout.fillWidth: true
anchors.left: undefined
anchors.right: undefined
onAddActiveActionRequested: { onAddActiveActionRequested: {
root.updateTimeoutEntry(index, "activeAction", ""); root.updateTimeoutEntry(index, "activeAction", "");
-18
View File
@@ -1,18 +0,0 @@
import qs.Modules.Settings.Controls
import qs.Config
SettingsPage {
SettingsSection {
sectionId: "Plugins"
SettingsHeader {
name: "Plugins"
}
SettingBarEntryList {
name: "Enable or disable plugins"
object: Config.plugins
setting: "entries"
}
}
}
+97 -97
View File
@@ -19,8 +19,8 @@ SettingsPage {
} }
SettingSpinBox { SettingSpinBox {
min: 1
name: "Max toasts" name: "Max toasts"
min: 1
object: Config.utilities object: Config.utilities
setting: "maxToasts" setting: "maxToasts"
} }
@@ -29,8 +29,8 @@ SettingsPage {
} }
SettingSpinBox { SettingSpinBox {
min: 1
name: "Panel width" name: "Panel width"
min: 1
object: Config.utilities.sizes object: Config.utilities.sizes
setting: "width" setting: "width"
} }
@@ -39,8 +39,8 @@ SettingsPage {
} }
SettingSpinBox { SettingSpinBox {
min: 1
name: "Toast width" name: "Toast width"
min: 1
object: Config.utilities.sizes object: Config.utilities.sizes
setting: "toastWidth" setting: "toastWidth"
} }
@@ -77,100 +77,100 @@ SettingsPage {
setting: "gameModeChanged" setting: "gameModeChanged"
} }
// Separator { Separator {
// } }
//
// SettingSwitch { SettingSwitch {
// name: "Do not disturb changed" name: "Do not disturb changed"
// object: Config.utilities.toasts object: Config.utilities.toasts
// setting: "dndChanged" setting: "dndChanged"
// } }
//
// Separator { Separator {
// } }
//
// SettingSwitch { SettingSwitch {
// name: "Audio output changed" name: "Audio output changed"
// object: Config.utilities.toasts object: Config.utilities.toasts
// setting: "audioOutputChanged" setting: "audioOutputChanged"
// } }
//
// Separator { Separator {
// } }
//
// SettingSwitch { SettingSwitch {
// name: "Audio input changed" name: "Audio input changed"
// object: Config.utilities.toasts object: Config.utilities.toasts
// setting: "audioInputChanged" setting: "audioInputChanged"
// } }
//
// Separator { Separator {
// } }
//
// SettingSwitch { SettingSwitch {
// name: "Caps lock changed" name: "Caps lock changed"
// object: Config.utilities.toasts object: Config.utilities.toasts
// setting: "capsLockChanged" setting: "capsLockChanged"
// } }
//
// Separator { Separator {
// } }
//
// SettingSwitch { SettingSwitch {
// name: "Num lock changed" name: "Num lock changed"
// object: Config.utilities.toasts object: Config.utilities.toasts
// setting: "numLockChanged" setting: "numLockChanged"
// } }
//
// Separator { Separator {
// } }
//
// SettingSwitch { SettingSwitch {
// name: "Keyboard layout changed" name: "Keyboard layout changed"
// object: Config.utilities.toasts object: Config.utilities.toasts
// setting: "kbLayoutChanged" setting: "kbLayoutChanged"
// } }
//
// Separator { Separator {
// } }
//
// SettingSwitch { SettingSwitch {
// name: "VPN changed" name: "VPN changed"
// object: Config.utilities.toasts object: Config.utilities.toasts
// setting: "vpnChanged" setting: "vpnChanged"
// } }
//
// Separator { Separator {
// } }
//
// SettingSwitch { SettingSwitch {
// name: "Now playing" name: "Now playing"
// object: Config.utilities.toasts object: Config.utilities.toasts
// setting: "nowPlaying" setting: "nowPlaying"
// } }
} }
// SettingsSection { SettingsSection {
// sectionId: "VPN" sectionId: "VPN"
//
// SettingsHeader { SettingsHeader {
// name: "VPN" name: "VPN"
// } }
//
// SettingSwitch { SettingSwitch {
// name: "Enable VPN integration" name: "Enable VPN integration"
// object: Config.utilities.vpn object: Config.utilities.vpn
// setting: "enabled" setting: "enabled"
// } }
//
// Separator { Separator {
// } }
//
// SettingStringList { SettingStringList {
// name: "Provider" name: "Provider"
// addLabel: qsTr("Add VPN provider") addLabel: qsTr("Add VPN provider")
// object: Config.utilities.vpn object: Config.utilities.vpn
// setting: "provider" setting: "provider"
// } }
// } }
} }
+2 -13
View File
@@ -1,5 +1,3 @@
pragma ComponentBehavior: Bound
import Quickshell import Quickshell
import Quickshell.Widgets import Quickshell.Widgets
import QtQuick import QtQuick
@@ -22,6 +20,7 @@ Item {
required property PersistentProperties visibilities required property PersistentProperties visibilities
function scrollToSetting(section: string, settingName: string) { function scrollToSetting(section: string, settingName: string) {
// Wait for the StackView transition to complete, then scroll
root.pendingSection = section; root.pendingSection = section;
root.pendingSetting = settingName; root.pendingSetting = settingName;
scrollTimer.restart(); scrollTimer.restart();
@@ -79,8 +78,6 @@ Item {
stack.push(screenshot); stack.push(screenshot);
else if (currentCategory === "updates") else if (currentCategory === "updates")
stack.push(updates); stack.push(updates);
else if (currentCategory === "plugins")
stack.push(plugins);
} }
target: root target: root
@@ -136,7 +133,7 @@ Item {
anchors.right: parent.right anchors.right: parent.right
anchors.top: searchBar.bottom anchors.top: searchBar.bottom
anchors.topMargin: Appearance.spacing.smaller anchors.topMargin: Appearance.spacing.smaller
color: DynamicColors.tPalette.m3surfaceContainerLowest color: DynamicColors.tPalette.m3surfaceContainer
radius: Appearance.rounding.normal radius: Appearance.rounding.normal
StackView { StackView {
@@ -160,7 +157,6 @@ Item {
id: background id: background
Cat.Background { Cat.Background {
screen: root.screen
} }
} }
@@ -247,11 +243,4 @@ Item {
Cat.SystemUpdates { Cat.SystemUpdates {
} }
} }
Component {
id: plugins
Cat.Plugins {
}
}
} }
@@ -1,161 +0,0 @@
pragma ComponentBehavior: Bound
import Quickshell
import QtQuick
import QtQuick.Layouts
import qs.Config
import qs.Components
Item {
id: root
required property string name
required property var object
property alias row: row
required property string setting
property bool shouldBeActive: true
required property list<var> stringList
signal optionSet(option: string)
anchors.left: parent.left
anchors.right: parent.right
implicitHeight: shouldBeActive ? row.height : 0
opacity: shouldBeActive ? 1 : 0
scale: shouldBeActive ? 1 : 0.8
visible: opacity > 0
Behavior on opacity {
Anim {
}
}
Behavior on scale {
Anim {
}
}
Behavior on y {
Anim {
}
}
RowLayout {
id: row
anchors.left: parent.left
anchors.margins: Appearance.padding.small
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
CustomText {
id: text
Layout.fillWidth: true
font.pointSize: Appearance.font.size.larger
text: root.name
}
CustomClippingRect {
Layout.preferredHeight: 42 * 6 + Appearance.padding.normal * 2 + Appearance.spacing.small * 5
Layout.preferredWidth: 500
color: DynamicColors.tPalette.m3surfaceContainer
radius: (21 + Appearance.padding.normal) * Appearance.rounding.scale
CustomRect {
id: searchBox
anchors.left: parent.left
anchors.margins: Appearance.padding.normal
anchors.right: parent.right
anchors.top: parent.top
color: DynamicColors.tPalette.m3surfaceContainer
implicitHeight: 42
radius: Appearance.rounding.full
MaterialIcon {
id: searchIcon
anchors.bottom: parent.bottom
anchors.left: parent.left
anchors.leftMargin: Appearance.padding.large
anchors.top: parent.top
font.pointSize: Appearance.font.size.large
text: "search"
verticalAlignment: Text.AlignVCenter
}
CustomTextField {
id: textSearch
anchors.left: searchIcon.right
anchors.leftMargin: Appearance.spacing.small
anchors.right: parent.right
anchors.rightMargin: Appearance.spacing.normal
anchors.verticalCenter: parent.verticalCenter
placeholderText: "Search..."
}
}
CustomClippingRect {
anchors.bottom: parent.bottom
anchors.left: parent.left
anchors.margins: Appearance.padding.normal
anchors.right: parent.right
anchors.top: searchBox.bottom
bottomLeftRadius: 21
bottomRightRadius: 21
CustomListView {
anchors.fill: parent
clip: true
spacing: Appearance.spacing.small
delegate: CustomRect {
id: delegate
required property string modelData
anchors.left: parent.left
anchors.right: parent.right
implicitHeight: 42
radius: Appearance.rounding.smallest
CustomText {
anchors.fill: parent
anchors.leftMargin: Appearance.padding.normal
text: modelData
verticalAlignment: Text.AlignVCenter
}
MaterialIcon {
anchors.fill: parent
anchors.rightMargin: Appearance.padding.normal
color: DynamicColors.palette.m3primary
font.pointSize: Appearance.font.size.large
horizontalAlignment: Text.AlignRight
text: "check_circle"
verticalAlignment: Text.AlignVCenter
visible: root.object[root.setting] === delegate.modelData
}
StateLayer {
onClicked: {
root.object[root.setting] = delegate.modelData;
root.optionSet(delegate.modelData);
Config.save();
}
}
}
model: ScriptModel {
values: {
const values = root.stringList;
const search = textSearch.text;
var regex = new RegExp(search, "i");
return values.filter(n => regex.test(n));
}
}
}
}
}
}
}
+2 -21
View File
@@ -6,26 +6,7 @@ import qs.Config
CustomRect { CustomRect {
id: root id: root
property bool shouldBeActive: true Layout.fillWidth: true
Layout.preferredHeight: 1
anchors.left: parent.left
anchors.right: parent.right
color: DynamicColors.tPalette.m3outlineVariant color: DynamicColors.tPalette.m3outlineVariant
implicitHeight: shouldBeActive ? 1 : 0
opacity: shouldBeActive ? 1 : 0
scale: shouldBeActive ? 1 : 0.8
visible: opacity > 0
Behavior on opacity {
Anim {
}
}
Behavior on scale {
Anim {
}
}
Behavior on y {
Anim {
}
}
} }
@@ -13,7 +13,6 @@ ColumnLayout {
required property string name required property string name
required property var object required property var object
required property string setting required property string setting
property bool shouldBeActive: true
function addAction() { function addAction() {
const list = [...root.object[root.setting]]; const list = [...root.object[root.setting]];
@@ -45,26 +44,8 @@ ColumnLayout {
Config.save(); Config.save();
} }
anchors.left: parent.left Layout.fillWidth: true
anchors.right: parent.right
height: shouldBeActive ? implicitHeight : 0
opacity: shouldBeActive ? 1 : 0
scale: shouldBeActive ? 1 : 0.8
spacing: Appearance.spacing.smaller spacing: Appearance.spacing.smaller
visible: opacity > 0
Behavior on opacity {
Anim {
}
}
Behavior on scale {
Anim {
}
}
Behavior on y {
Anim {
}
}
Rectangle { Rectangle {
anchors.fill: parent anchors.fill: parent
@@ -127,9 +108,6 @@ ColumnLayout {
} }
Separator { Separator {
Layout.fillWidth: true
anchors.left: undefined
anchors.right: undefined
} }
RowLayout { RowLayout {
@@ -210,8 +188,6 @@ ColumnLayout {
StringListEditor { StringListEditor {
Layout.fillWidth: true Layout.fillWidth: true
addLabel: qsTr("Add command argument") addLabel: qsTr("Add command argument")
anchors.left: undefined
anchors.right: undefined
values: [...(modelData.command ?? [])] values: [...(modelData.command ?? [])]
onListEdited: function (values) { onListEdited: function (values) {
@@ -220,9 +196,6 @@ ColumnLayout {
} }
Separator { Separator {
Layout.fillWidth: true
anchors.left: undefined
anchors.right: undefined
} }
RowLayout { RowLayout {
@@ -241,9 +214,6 @@ ColumnLayout {
} }
Separator { Separator {
Layout.fillWidth: true
anchors.left: undefined
anchors.right: undefined
} }
RowLayout { RowLayout {
+89 -120
View File
@@ -6,14 +6,13 @@ import qs.Components
import qs.Config import qs.Config
import qs.Helpers import qs.Helpers
CustomRect { ColumnLayout {
id: root id: root
readonly property bool highlighted: SettingsHighlight.highlightedSetting === name readonly property bool highlighted: SettingsHighlight.highlightedSetting === name
required property string name required property string name
required property var object required property var object
required property string setting required property string setting
property bool shouldBeActive: true
function addAlias() { function addAlias() {
const list = [...root.object[root.setting]]; const list = [...root.object[root.setting]];
@@ -41,25 +40,8 @@ CustomRect {
Config.save(); Config.save();
} }
anchors.left: parent.left Layout.fillWidth: true
anchors.right: parent.right spacing: Appearance.spacing.smaller
height: shouldBeActive ? layout.implicitHeight : 0
opacity: shouldBeActive ? 1 : 0
scale: shouldBeActive ? 1 : 0.8
visible: opacity > 0
Behavior on opacity {
Anim {
}
}
Behavior on scale {
Anim {
}
}
Behavior on y {
Anim {
}
}
Rectangle { Rectangle {
anchors.fill: parent anchors.fill: parent
@@ -76,128 +58,115 @@ CustomRect {
} }
} }
ColumnLayout { CustomText {
id: layout Layout.fillWidth: true
font.pointSize: Appearance.font.size.larger
text: root.name
}
anchors.left: parent.left Repeater {
anchors.right: parent.right model: [...root.object[root.setting]]
anchors.verticalCenter: parent.verticalCenter
spacing: Appearance.spacing.smaller Item {
required property int index
required property var modelData
CustomText {
Layout.fillWidth: true Layout.fillWidth: true
font.pointSize: Appearance.font.size.larger Layout.preferredHeight: layout.implicitHeight + Appearance.padding.smaller * 2
text: root.name
}
Repeater { CustomRect {
model: [...root.object[root.setting]] anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
anchors.topMargin: -(Appearance.spacing.smaller / 2)
color: DynamicColors.tPalette.m3outlineVariant
implicitHeight: 1
visible: index !== 0
}
Item { ColumnLayout {
required property int index id: layout
required property var modelData
Layout.fillWidth: true anchors.left: parent.left
Layout.preferredHeight: layout.implicitHeight + Appearance.padding.smaller * 2 anchors.margins: Appearance.padding.small
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
spacing: Appearance.spacing.small
CustomRect { RowLayout {
anchors.left: parent.left Layout.fillWidth: true
anchors.right: parent.right
anchors.top: parent.top
anchors.topMargin: -(Appearance.spacing.smaller / 2)
color: DynamicColors.tPalette.m3outlineVariant
implicitHeight: 1
visible: index !== 0
}
ColumnLayout { CustomText {
id: layout
anchors.left: parent.left
anchors.margins: Appearance.padding.small
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
spacing: Appearance.spacing.small
RowLayout {
Layout.fillWidth: true Layout.fillWidth: true
text: qsTr("From")
}
CustomText { CustomRect {
Layout.fillWidth: true Layout.fillWidth: true
text: qsTr("From") Layout.preferredHeight: 33
} color: DynamicColors.tPalette.m3surfaceContainerHigh
radius: Appearance.rounding.full
CustomRect { CustomTextField {
Layout.preferredHeight: 33 anchors.fill: parent
Layout.preferredWidth: Math.max(Math.min(fromTextField.contentWidth + Appearance.padding.large * 2, 550), 50) anchors.leftMargin: Appearance.padding.normal
color: DynamicColors.tPalette.m3surfaceContainerHigh anchors.rightMargin: Appearance.padding.normal
radius: Appearance.rounding.full text: modelData.from ?? ""
CustomTextField { onEditingFinished: root.updateAlias(index, "from", text)
id: fromTextField
anchors.centerIn: parent
horizontalAlignment: Text.AlignHCenter
implicitWidth: Math.min(contentWidth + Appearance.padding.normal * 2, 550)
text: modelData.from ?? ""
onEditingFinished: root.updateAlias(index, "from", text)
}
}
IconButton {
font.pointSize: Appearance.font.size.large
icon: "delete"
type: IconButton.Tonal
onClicked: root.removeAlias(index)
} }
} }
RowLayout { IconButton {
font.pointSize: Appearance.font.size.large
icon: "delete"
type: IconButton.Tonal
onClicked: root.removeAlias(index)
}
}
RowLayout {
Layout.fillWidth: true
CustomText {
Layout.fillWidth: true Layout.fillWidth: true
text: qsTr("To")
}
CustomText { CustomRect {
Layout.fillWidth: true Layout.fillWidth: true
text: qsTr("To") Layout.preferredHeight: 33
} color: DynamicColors.tPalette.m3surface
radius: Appearance.rounding.small
CustomRect { CustomTextField {
Layout.preferredHeight: 33 anchors.fill: parent
Layout.preferredWidth: Math.max(Math.min(toTextField.contentWidth + Appearance.padding.large * 2, 550), 50) anchors.leftMargin: Appearance.padding.normal
color: DynamicColors.tPalette.m3surfaceContainerHigh anchors.rightMargin: Appearance.padding.normal
radius: Appearance.rounding.full text: modelData.to ?? ""
CustomTextField { onEditingFinished: root.updateAlias(index, "to", text)
id: toTextField
anchors.centerIn: parent
horizontalAlignment: Text.AlignHCenter
implicitWidth: Math.min(contentWidth + Appearance.padding.normal * 2, 550)
text: modelData.to ?? ""
onEditingFinished: root.updateAlias(index, "to", text)
}
} }
} }
} }
} }
} }
}
RowLayout {
Layout.fillWidth: true RowLayout {
Layout.fillWidth: true
IconButton {
font.pointSize: Appearance.font.size.large IconButton {
icon: "add" font.pointSize: Appearance.font.size.large
icon: "add"
onClicked: root.addAlias()
} onClicked: root.addAlias()
}
CustomText {
Layout.fillWidth: true CustomText {
text: qsTr("Add alias") Layout.fillWidth: true
} text: qsTr("Add alias")
} }
} }
} }
@@ -25,7 +25,6 @@ Item {
required property var object required property var object
property var pendingCommitEntries: [] property var pendingCommitEntries: []
required property string setting required property string setting
property bool shouldBeActive: true
property int uidCounter: 0 property int uidCounter: 0
property var visualEntries: [] property var visualEntries: []
@@ -147,25 +146,8 @@ Item {
Config.save(); Config.save();
} }
anchors.left: parent.left Layout.fillWidth: true
anchors.right: parent.right implicitHeight: layout.implicitHeight
implicitHeight: shouldBeActive ? layout.implicitHeight : 0
opacity: shouldBeActive ? 1 : 0
scale: shouldBeActive ? 1 : 0.8
visible: opacity > 0
Behavior on opacity {
Anim {
}
}
Behavior on scale {
Anim {
}
}
Behavior on y {
Anim {
}
}
Component.onCompleted: root.rebuildVisualEntries() Component.onCompleted: root.rebuildVisualEntries()
@@ -11,7 +11,6 @@ Item {
required property string name required property string name
required property var object required property var object
required property list<string> settings required property list<string> settings
property bool shouldBeActive: true
function commitChoice(choice: int, setting: string): void { function commitChoice(choice: int, setting: string): void {
root.object[setting] = choice; root.object[setting] = choice;
@@ -33,25 +32,8 @@ Item {
return Qt.formatTime(d, "h AP"); return Qt.formatTime(d, "h AP");
} }
anchors.left: parent.left Layout.fillWidth: true
anchors.right: parent.right Layout.preferredHeight: row.implicitHeight + Appearance.padding.smaller * 2
implicitHeight: shouldBeActive ? row.implicitHeight + Appearance.padding.smaller * 2 : 0
opacity: shouldBeActive ? 1 : 0
scale: shouldBeActive ? 1 : 0.8
visible: opacity > 0
Behavior on opacity {
Anim {
}
}
Behavior on scale {
Anim {
}
}
Behavior on y {
Anim {
}
}
Rectangle { Rectangle {
anchors.fill: parent anchors.fill: parent
+2 -20
View File
@@ -11,7 +11,6 @@ Item {
required property string name required property string name
required property var object required property var object
required property string setting required property string setting
property bool shouldBeActive: true
function formattedValue(): string { function formattedValue(): string {
const value = root.object[root.setting]; const value = root.object[root.setting];
@@ -22,25 +21,8 @@ Item {
return String(value); return String(value);
} }
anchors.left: parent.left Layout.fillWidth: true
anchors.right: parent.right Layout.preferredHeight: row.implicitHeight + Appearance.padding.smaller * 2
implicitHeight: shouldBeActive ? row.implicitHeight + Appearance.padding.smaller * 2 : 0
opacity: shouldBeActive ? 1 : 0
scale: shouldBeActive ? 1 : 0.8
visible: opacity > 0
Behavior on opacity {
Anim {
}
}
Behavior on scale {
Anim {
}
}
Behavior on y {
Anim {
}
}
Rectangle { Rectangle {
anchors.fill: parent anchors.fill: parent
+2 -26
View File
@@ -8,31 +8,13 @@ Item {
required property int index required property int index
required property var modelData required property var modelData
property bool shouldBeActive: true
signal addActiveActionRequested signal addActiveActionRequested
signal deleteRequested(int index) signal deleteRequested(int index)
signal fieldEdited(string key, var value) signal fieldEdited(string key, var value)
anchors.left: parent.left Layout.fillWidth: true
anchors.right: parent.right Layout.preferredHeight: row.implicitHeight + Appearance.padding.smaller * 2
implicitHeight: shouldBeActive ? row.implicitHeight + Appearance.padding.smaller * 2 : 0
opacity: shouldBeActive ? 1 : 0
scale: shouldBeActive ? 1 : 0.8
visible: opacity > 0
Behavior on opacity {
Anim {
}
}
Behavior on scale {
Anim {
}
}
Behavior on y {
Anim {
}
}
CustomRect { CustomRect {
anchors.left: parent.left anchors.left: parent.left
@@ -194,9 +176,6 @@ Item {
} }
Separator { Separator {
Layout.fillWidth: true
anchors.left: undefined
anchors.right: undefined
} }
RowLayout { RowLayout {
@@ -228,9 +207,6 @@ Item {
} }
Separator { Separator {
Layout.fillWidth: true
anchors.left: undefined
anchors.right: undefined
} }
Item { Item {
+4 -27
View File
@@ -11,32 +11,10 @@ Item {
required property string name required property string name
required property var object required property var object
property alias row: row
required property string setting required property string setting
property bool shouldBeActive: true
required property list<var> stringList
signal optionSet Layout.fillWidth: true
Layout.preferredHeight: row.height
anchors.left: parent.left
anchors.right: parent.right
implicitHeight: shouldBeActive ? row.height : 0
opacity: shouldBeActive ? 1 : 0
scale: shouldBeActive ? 1 : 0.8
visible: opacity > 0
Behavior on opacity {
Anim {
}
}
Behavior on scale {
Anim {
}
}
Behavior on y {
Anim {
}
}
RowLayout { RowLayout {
id: row id: row
@@ -142,18 +120,17 @@ Item {
StateLayer { StateLayer {
onClicked: { onClicked: {
root.object[root.setting] = fontDelegate.modelData; root.object[root.setting] = fontDelegate.modelData;
root.optionSet();
Config.save(); Config.save();
} }
} }
} }
model: ScriptModel { model: ScriptModel {
values: { values: {
const values = root.stringList; const fonts = Qt.fontFamilies();
const search = fontSearch.text; const search = fontSearch.text;
var regex = new RegExp(search, "i"); var regex = new RegExp(search, "i");
return values.filter(n => regex.test(n)); return fonts.filter(n => regex.test(n));
} }
} }
} }
+2 -20
View File
@@ -9,28 +9,10 @@ Item {
readonly property bool highlighted: SettingsHighlight.highlightedSetting === name readonly property bool highlighted: SettingsHighlight.highlightedSetting === name
required property string name required property string name
property bool shouldBeActive: true
required property string value required property string value
anchors.left: parent.left Layout.fillWidth: true
anchors.right: parent.right Layout.preferredHeight: row.implicitHeight + Appearance.padding.smaller * 2
implicitHeight: shouldBeActive ? row.implicitHeight + Appearance.padding.smaller * 2 : 0
opacity: shouldBeActive ? 1 : 0
scale: shouldBeActive ? 1 : 0.8
visible: opacity > 0
Behavior on opacity {
Anim {
}
}
Behavior on scale {
Anim {
}
}
Behavior on y {
Anim {
}
}
Rectangle { Rectangle {
anchors.fill: parent anchors.fill: parent
+2 -20
View File
@@ -13,28 +13,10 @@ Item {
required property string name required property string name
required property var object required property var object
required property string setting required property string setting
property bool shouldBeActive: true
property real step: 1 property real step: 1
anchors.left: parent.left Layout.fillWidth: true
anchors.right: parent.right Layout.preferredHeight: row.implicitHeight + Appearance.padding.smaller * 2
implicitHeight: shouldBeActive ? row.implicitHeight + Appearance.padding.smaller * 2 : 0
opacity: shouldBeActive ? 1 : 0
scale: shouldBeActive ? 1 : 0.8
visible: opacity > 0
Behavior on opacity {
Anim {
}
}
Behavior on scale {
Anim {
}
}
Behavior on y {
Anim {
}
}
Rectangle { Rectangle {
anchors.fill: parent anchors.fill: parent
+2 -20
View File
@@ -11,7 +11,6 @@ Item {
required property string name required property string name
required property var object required property var object
required property list<string> settings required property list<string> settings
property bool shouldBeActive: true
function commitChoice(choice: int, setting: string): void { function commitChoice(choice: int, setting: string): void {
root.object[setting] = choice; root.object[setting] = choice;
@@ -33,25 +32,8 @@ Item {
return Qt.formatTime(d, "h AP"); return Qt.formatTime(d, "h AP");
} }
anchors.left: parent.left Layout.fillWidth: true
anchors.right: parent.right Layout.preferredHeight: row.implicitHeight + Appearance.padding.smaller * 2
implicitHeight: shouldBeActive ? row.implicitHeight + Appearance.padding.smaller * 2 : 0
opacity: shouldBeActive ? 1 : 0
scale: shouldBeActive ? 1 : 0.8
visible: opacity > 0
Behavior on opacity {
Anim {
}
}
Behavior on scale {
Anim {
}
}
Behavior on y {
Anim {
}
}
Rectangle { Rectangle {
anchors.fill: parent anchors.fill: parent
@@ -12,27 +12,9 @@ Item {
required property string name required property string name
required property var object required property var object
required property string setting required property string setting
property bool shouldBeActive: true
anchors.left: parent.left Layout.fillWidth: true
anchors.right: parent.right Layout.preferredHeight: layout.implicitHeight
implicitHeight: shouldBeActive ? layout.implicitHeight : 0
opacity: shouldBeActive ? 1 : 0
scale: shouldBeActive ? 1 : 0.8
visible: opacity > 0
Behavior on opacity {
Anim {
}
}
Behavior on scale {
Anim {
}
}
Behavior on y {
Anim {
}
}
Rectangle { Rectangle {
anchors.fill: parent anchors.fill: parent
@@ -64,9 +46,6 @@ Item {
StringListEditor { StringListEditor {
Layout.fillWidth: true Layout.fillWidth: true
addLabel: root.addLabel addLabel: root.addLabel
anchors.left: undefined
anchors.right: undefined
anchors.verticalCenter: undefined
values: [...(root.object[root.setting] ?? [])] values: [...(root.object[root.setting] ?? [])]
onListEdited: function (values) { onListEdited: function (values) {
+2 -20
View File
@@ -11,27 +11,9 @@ Item {
required property string name required property string name
required property var object required property var object
required property string setting required property string setting
property bool shouldBeActive: true
anchors.left: parent.left Layout.fillWidth: true
anchors.right: parent.right Layout.preferredHeight: row.implicitHeight + Appearance.padding.smaller * 2
implicitHeight: shouldBeActive ? row.implicitHeight + Appearance.padding.smaller * 2 : 0
opacity: shouldBeActive ? 1 : 0
scale: shouldBeActive ? 1 : 0.8
visible: opacity > 0
Behavior on opacity {
Anim {
}
}
Behavior on scale {
Anim {
}
}
Behavior on y {
Anim {
}
}
Rectangle { Rectangle {
anchors.fill: parent anchors.fill: parent
+2 -6
View File
@@ -7,13 +7,9 @@ CustomRect {
id: root id: root
required property string name required property string name
property bool shouldBeActive: true
implicitHeight: 60 Layout.preferredHeight: 60
implicitWidth: 200 Layout.preferredWidth: 200
opacity: shouldBeActive ? 1 : 0
scale: shouldBeActive ? 1 : 0.8
visible: opacity > 0
CustomText { CustomText {
anchors.fill: parent anchors.fill: parent
@@ -1,83 +0,0 @@
import Quickshell
import QtQuick
import QtQuick.Layouts
import qs.Paths
import qs.Components
import qs.Config
import qs.Helpers
Item {
id: root
property alias button: iButton
readonly property bool highlighted: SettingsHighlight.highlightedSetting === name
required property string name
property bool shouldBeActive: true
anchors.left: parent.left
anchors.right: parent.right
implicitHeight: shouldBeActive ? row.implicitHeight + Appearance.padding.smaller * 2 : 0
opacity: shouldBeActive ? 1 : 0
scale: shouldBeActive ? 1 : 0.8
visible: opacity > 0
Behavior on opacity {
Anim {
}
}
Behavior on scale {
Anim {
}
}
Behavior on y {
Anim {
}
}
Rectangle {
anchors.fill: parent
anchors.margins: -Appearance.padding.smaller
color: DynamicColors.palette.m3primaryContainer
opacity: root.highlighted ? 0.5 : 0
radius: Appearance.rounding.small
Behavior on opacity {
Anim {
duration: Appearance.anim.durations.normal
}
}
}
RowLayout {
id: row
anchors.left: parent.left
anchors.margins: Appearance.padding.small
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
CustomText {
id: text
Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter
Layout.fillWidth: true
font.pointSize: Appearance.font.size.larger
text: root.name
}
IconButton {
id: iButton
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
icon: "download"
onClicked: {
const lockBg = `${Paths.state}/lockscreen_bg.png`;
const scheme = `${Paths.state}/scheme.json`;
const face = `${Paths.home}/.face`;
const destination = "/etc/zshell-greeter/images";
Quickshell.execDetached(["pkexec", "sh", "-c", `mkdir -p ${destination}; cp ${lockBg} ${destination}; cp ${scheme} /etc/zshell-greeter; cp ${face} ${destination}`]);
}
}
}
}
+1 -7
View File
@@ -60,18 +60,12 @@ CustomClippingRect {
} }
} }
Column { ColumnLayout {
id: clayout id: clayout
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right
spacing: Appearance.spacing.small spacing: Appearance.spacing.small
// move: Transition {
// Anim {
// properties: "y"
// }
// }
} }
} }
} }
+4 -25
View File
@@ -10,39 +10,18 @@ CustomRect {
property real contentPadding: Appearance.padding.large property real contentPadding: Appearance.padding.large
property string sectionId: "" property string sectionId: ""
anchors.left: parent.left Layout.fillWidth: true
anchors.right: parent.right Layout.preferredHeight: layout.implicitHeight + contentPadding * 2
color: DynamicColors.tPalette.m3surfaceContainer color: DynamicColors.tPalette.m3surfaceContainer
implicitHeight: layout.height + contentPadding * 2
radius: Appearance.rounding.normal - Appearance.padding.smaller radius: Appearance.rounding.normal - Appearance.padding.smaller
Behavior on implicitHeight { ColumnLayout {
Anim {
}
}
Behavior on y {
Anim {
}
}
Column {
id: layout id: layout
anchors.left: parent.left anchors.left: parent.left
anchors.margins: root.contentPadding anchors.margins: root.contentPadding
anchors.right: parent.right anchors.right: parent.right
anchors.top: parent.top anchors.verticalCenter: parent.verticalCenter
// anchors.verticalCenter: parent.verticalCenter
spacing: Appearance.spacing.normal spacing: Appearance.spacing.normal
Behavior on height {
Anim {
}
}
move: Transition {
Anim {
properties: "y"
}
}
} }
} }
@@ -12,29 +12,12 @@ CustomRect {
property alias expanded: menu.expanded property alias expanded: menu.expanded
property alias label: label property alias label: label
property alias menu: menu property alias menu: menu
property bool shouldBeActive: true
property alias text: label.text property alias text: label.text
color: enabled ? DynamicColors.palette.m3primary : DynamicColors.layer(DynamicColors.palette.m3surfaceContainerHigh, 2) color: enabled ? DynamicColors.palette.m3primary : DynamicColors.layer(DynamicColors.palette.m3surfaceContainerHigh, 2)
opacity: shouldBeActive ? 1 : 0
radius: Appearance.rounding.full radius: Appearance.rounding.full
scale: shouldBeActive ? 1 : 0.8
visible: opacity > 0
z: expanded ? 100 : 0 z: expanded ? 100 : 0
Behavior on opacity {
Anim {
}
}
Behavior on scale {
Anim {
}
}
Behavior on y {
Anim {
}
}
CustomText { CustomText {
id: label id: label
+1 -20
View File
@@ -9,7 +9,6 @@ ColumnLayout {
id: root id: root
property string addLabel: qsTr("Add entry") property string addLabel: qsTr("Add entry")
property bool shouldBeActive: true
property var values: [] property var values: []
signal listEdited(var values) signal listEdited(var values)
@@ -32,26 +31,8 @@ ColumnLayout {
root.listEdited(list); root.listEdited(list);
} }
anchors.left: parent.left Layout.fillWidth: true
anchors.right: parent.right
height: shouldBeActive ? implicitHeight : 0
opacity: shouldBeActive ? 1 : 0
scale: shouldBeActive ? 1 : 0.8
spacing: Appearance.spacing.smaller spacing: Appearance.spacing.smaller
visible: opacity > 0
Behavior on opacity {
Anim {
}
}
Behavior on scale {
Anim {
}
}
Behavior on y {
Anim {
}
}
Repeater { Repeater {
model: [...root.values] model: [...root.values]
+70 -247
View File
@@ -1,279 +1,102 @@
pragma ComponentBehavior: Bound
import QtQuick import QtQuick
import QtQuick.Layouts import QtQuick.Layouts
import QtQuick.Controls
import Quickshell import Quickshell
import Quickshell.Hyprland
import ZShell.Internal
import qs.Config import qs.Config
import qs.Components import qs.Components
import qs.Helpers import qs.Helpers
Item { ColumnLayout {
id: wrapper id: root
property bool changesMade: false spacing: 15
property bool shouldBeActive: true width: Math.min(parent ? parent.width : 600, 600)
signal requestCrop Rectangle {
id: previewContainer
anchors.left: parent.left Layout.fillHeight: true
anchors.right: parent.right Layout.preferredWidth: height * (Quickshell.screens.length > 0 ? (Quickshell.screens[0].height / Math.max(1, Quickshell.screens[0].width)) : 16 / 9)
implicitHeight: shouldBeActive ? 400 : 0 clip: true
opacity: shouldBeActive ? 1 : 0 color: DynamicColors.palette.m3surfaceContainer
scale: shouldBeActive ? 1 : 0.8 radius: Config.appearance.rounding.scale * 10
visible: opacity > 0
Behavior on opacity { Image {
Anim { id: img
}
}
Behavior on scale {
Anim {
}
}
Behavior on y {
Anim {
}
}
IconButton { anchors.fill: parent
anchors.margins: Appearance.padding.normal asynchronous: true
anchors.right: parent.right fillMode: Image.PreserveAspectFit
anchors.top: parent.top source: Wallpapers.current
icon: "check"
opacity: wrapper.changesMade ? 1 : 0
scale: wrapper.changesMade ? 1 : 0
z: 2
Behavior on opacity { Rectangle {
Anim { id: cropRect
}
}
Behavior on scale {
Anim {
}
}
onClicked: { property real cropHeight: (imageAspect > screenAspect ? paintedHeight : paintedWidth / screenAspect) / Config.background.zoom
wrapper.requestCrop(); property real cropWidth: (imageAspect > screenAspect ? paintedHeight * screenAspect : paintedWidth) / Config.background.zoom
wrapper.changesMade = false; property real imageAspect: Math.max(1, paintedWidth) / Math.max(1, paintedHeight)
} property real paintedHeight: img.paintedHeight > 0 ? img.paintedHeight : img.height
} property real paintedWidth: img.paintedWidth > 0 ? img.paintedWidth : img.width
property real paintedX: (img.width - paintedWidth) / 2
property real paintedY: (img.height - paintedHeight) / 2
property real screenAspect: Quickshell.screens.length > 0 ? (Quickshell.screens[0].width / Math.max(1, Quickshell.screens[0].height)) : 16 / 9
RowLayout { border.color: DynamicColors.palette.m3primary
id: root border.width: 2
color: Qt.alpha(DynamicColors.palette.m3primaryContainer, 0.3)
height: cropHeight
width: cropWidth
x: paintedX + (paintedWidth - width) * Config.background.alignX
y: paintedY + (paintedHeight - height) * Config.background.alignY
anchors.fill: parent DragHandler {
spacing: Appearance.spacing.normal target: null
Repeater { onActiveTranslationChanged: {
model: ScriptModel { if (active) {
values: [...Quickshell.screens].sort((a, b) => { let newX = cropRect.x - cropRect.paintedX + translation.x;
return a.x - b.x; let newY = cropRect.y - cropRect.paintedY + translation.y;
})
}
Item { let rangeX = cropRect.paintedWidth - cropRect.width;
id: delegate let rangeY = cropRect.paintedHeight - cropRect.height;
required property ShellScreen modelData if (rangeX > 0) {
let valX = newX / rangeX;
Config.background.alignX = Math.max(0.0, Math.min(1.0, valX));
Config.save();
}
function applyCrop(): void { if (rangeY > 0) {
const croprect = cropRect.mapToItem(scaledImg, 0, 0, cropRect.width, cropRect.height); let valY = newY / rangeY;
const upscaledRect = Qt.rect((croprect.x - cropRect.imageX) / scaledImg.paintedWidth, (croprect.y - cropRect.imageY) / scaledImg.paintedHeight, croprect.width / scaledImg.paintedWidth, croprect.height / scaledImg.paintedHeight); Config.background.alignY = Math.max(0.0, Math.min(1.0, valY));
Wallpapers.setCrop(delegate.modelData.name, upscaledRect, croprect, cropRect.zoom); Config.save();
} }
function zoomClipRect(zoom: real): void {
let oldCenterX = cropRect.x + cropRect.width * 0.5;
let oldCenterY = cropRect.y + cropRect.height * 0.5;
cropRect.zoom = zoom;
cropRect.x = oldCenterX - cropRect.width * 0.5;
cropRect.y = oldCenterY - cropRect.height * 0.5;
cropRect.clampToBounds();
}
Layout.fillHeight: true
Layout.fillWidth: true
Connections {
function onRequestCrop(): void {
delegate.applyCrop();
}
target: wrapper
}
RowLayout {
id: sliderLayout
anchors.bottom: parent.bottom
anchors.left: parent.left
anchors.right: parent.right
implicitHeight: 30
spacing: Appearance.spacing.large
CustomText {
text: qsTr("Crop scale")
}
CustomSlider {
id: zoomSlider
Layout.fillWidth: true
Layout.preferredHeight: 10
from: 1.0
to: 5.0
value: cropRect.zoom
onMoved: {
delegate.zoomClipRect(value);
wrapper.changesMade = true;
} }
} }
} }
CachingImage { PinchHandler {
id: scaledImg maximumScale: 5.0
minimumScale: 1.0
target: null
property var displayData onActiveScaleChanged: {
property real monitorScale: 1.0 if (active) {
let newZoom = Config.background.zoom * (1 / (1 + (activeScale - 1) * 0.1));
anchors.bottom: sliderLayout.top Config.background.zoom = Math.max(1.0, Math.min(newZoom, 5.0));
anchors.bottomMargin: Appearance.spacing.normal
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
asynchronous: true
fillMode: Image.PreserveAspectFit
// retainWhileLoading: true
source: Wallpapers.current
sourceSize.height: parent.height
sourceSize.width: parent.width
onPaintedWidthChanged: {
if (paintedWidth > 0) {
scaledImg.displayData = Wallpapers.getCrop(delegate.modelData.name);
cropRect.zoom = Wallpapers.getCrop(delegate.modelData.name).zoom;
cropRect.restoreFromData();
}
}
onSourceChanged: cropRect.clampToBounds()
onStatusChanged: if (scaledImg.status == Image.Ready)
cropRect.clampToBounds()
CustomText {
id: monitorId
anchors.centerIn: parent
color: Qt.alpha(DynamicColors.palette.m3surface, 0.85)
font.pointSize: Appearance.font.size.large * 4
style: Text.Outline
styleColor: DynamicColors.palette.m3onSurface
text: delegate.modelData.name
}
CustomRect {
id: cropRect
property real aspectRatio: delegate.modelData.width / delegate.modelData.height
readonly property real baseHeight: baseWidth / aspectRatio
readonly property real baseWidth: {
let fittedHeight = scaledImg.paintedHeight;
let fittedWidth = fittedHeight * aspectRatio;
if (fittedWidth > scaledImg.paintedWidth) {
fittedWidth = scaledImg.paintedWidth;
fittedHeight = fittedWidth / aspectRatio;
}
return fittedWidth;
}
readonly property real imageX: (scaledImg.width - scaledImg.paintedWidth) / 2
readonly property real imageY: (scaledImg.height - scaledImg.paintedHeight) / 2
property real imgAspectRatio: scaledImg.paintedWidth / scaledImg.paintedHeight
property real zoom: scaledImg.displayData.zoom
function centerInImage() {
x = imageX + (scaledImg.paintedWidth - width) / 2;
y = imageY + (scaledImg.paintedHeight - height) / 2;
}
function clampToBounds() {
x = Math.max(imageX, Math.min(x, imageX + scaledImg.paintedWidth - width));
y = Math.max(imageY, Math.min(y, imageY + scaledImg.paintedHeight - height));
}
function restoreFromData() {
let data = scaledImg.displayData;
if (data && data.scaledX !== 0 || data.scaledY !== 0 || data.scaledWidth !== 0 || data.scaledHeight !== 0) {
x = data.scaledX;
y = data.scaledY;
clampToBounds();
} else {
zoom = 1.0;
centerInImage();
}
}
border.color: DynamicColors.palette.m3primary
border.width: 2
height: baseHeight / zoom
opacity: 1
width: baseWidth / zoom
Behavior on opacity {
Anim {
}
}
Component.onCompleted: clampToBounds()
onHeightChanged: clampToBounds()
onWidthChanged: clampToBounds()
}
MouseArea {
id: mouse
function updateCrop(mouseX, mouseY) {
let nx = mouseX - cropRect.width * 0.5;
let ny = mouseY - cropRect.height * 0.5;
nx = Math.max(cropRect.imageX, Math.min(nx, cropRect.imageX + scaledImg.paintedWidth - cropRect.width));
ny = Math.max(cropRect.imageY, Math.min(ny, cropRect.imageY + scaledImg.paintedHeight - cropRect.height));
cropRect.x = nx;
cropRect.y = ny;
}
anchors.fill: parent
hoverEnabled: true
preventStealing: true
onPositionChanged: mouse => {
if (pressed) {
updateCrop(mouse.x, mouse.y);
wrapper.changesMade = true;
}
}
onPressed: mouse => {
updateCrop(mouse.x, mouse.y);
wrapper.changesMade = true;
}
onReleased: {
wrapper.changesMade = true;
} }
} }
} }
} }
} }
} }
SettingSpinBox {
max: 5.0
min: 1.0
name: "Zoom"
object: Config.background
setting: "zoom"
step: 0.1
}
} }
+42 -54
View File
@@ -12,30 +12,37 @@ GridView {
readonly property int columnsCount: Math.max(1, Math.floor(width / minCellWidth)) readonly property int columnsCount: Math.max(1, Math.floor(width / minCellWidth))
readonly property int minCellWidth: 200 + Appearance.spacing.normal readonly property int minCellWidth: 200 + Appearance.spacing.normal
property bool shouldBeActive: true
anchors.left: parent.left Layout.preferredHeight: contentHeight
anchors.right: parent.right
cellHeight: 140 + Appearance.spacing.normal cellHeight: 140 + Appearance.spacing.normal
cellWidth: width / columnsCount cellWidth: width / columnsCount
clip: true clip: true
implicitHeight: shouldBeActive ? contentHeight : 0
interactive: false interactive: false
model: Wallpapers.list model: Wallpapers.list
opacity: shouldBeActive ? 1 : 0
scale: shouldBeActive ? 1 : 0.8
visible: opacity > 0
delegate: Item { delegate: Item {
required property int index required property int index
readonly property bool isCurrent: modelData && modelData.path === Wallpapers.actualCurrent readonly property bool isCurrent: modelData && modelData.path === Wallpapers.actualCurrent
readonly property real itemMargin: Appearance.spacing.normal readonly property real itemMargin: Appearance.spacing.normal / 2
readonly property real itemRadius: Appearance.rounding.small readonly property real itemRadius: Appearance.rounding.normal
required property var modelData required property var modelData
height: root.cellHeight height: root.cellHeight
width: root.cellWidth width: root.cellWidth
StateLayer {
function onClicked(): void {
Wallpapers.setWallpaper(modelData.path);
}
anchors.bottomMargin: itemMargin
anchors.fill: parent
anchors.leftMargin: itemMargin
anchors.rightMargin: itemMargin
anchors.topMargin: itemMargin
radius: itemRadius
}
CustomClippingRect { CustomClippingRect {
id: image id: image
@@ -46,6 +53,8 @@ GridView {
anchors.topMargin: itemMargin anchors.topMargin: itemMargin
antialiasing: true antialiasing: true
color: DynamicColors.tPalette.m3surfaceContainer color: DynamicColors.tPalette.m3surfaceContainer
layer.enabled: true
layer.smooth: true
radius: itemRadius radius: itemRadius
CachingImage { CachingImage {
@@ -91,33 +100,6 @@ GridView {
} }
} }
Rectangle {
anchors.fill: parent
antialiasing: true
border.color: DynamicColors.palette.m3primary
border.width: isCurrent ? 2 : 0
color: "transparent"
radius: itemRadius + 2
smooth: true
Behavior on border.width {
NumberAnimation {
duration: 150
easing.type: Easing.OutQuad
}
}
MaterialIcon {
anchors.margins: Appearance.padding.small
anchors.right: parent.right
anchors.top: parent.top
color: DynamicColors.palette.m3primary
font.pointSize: Appearance.font.size.large
text: "check_circle"
visible: isCurrent
}
}
Timer { Timer {
id: fallbackTimer id: fallbackTimer
@@ -130,29 +112,35 @@ GridView {
} }
} }
StateLayer { Rectangle {
function onClicked(): void {
Wallpapers.setWallpaper(modelData.path);
}
anchors.bottomMargin: itemMargin anchors.bottomMargin: itemMargin
anchors.fill: parent anchors.fill: parent
anchors.leftMargin: itemMargin anchors.leftMargin: itemMargin
anchors.rightMargin: itemMargin anchors.rightMargin: itemMargin
anchors.topMargin: itemMargin anchors.topMargin: itemMargin
radius: itemRadius antialiasing: true
} border.color: DynamicColors.palette.m3primary
} border.width: isCurrent ? 2 : 0
Behavior on opacity { color: "transparent"
Anim { radius: itemRadius - border.width
} smooth: true
}
Behavior on scale { Behavior on border.width {
Anim { NumberAnimation {
} duration: 150
} easing.type: Easing.OutQuad
Behavior on y { }
Anim { }
MaterialIcon {
anchors.margins: Appearance.padding.small
anchors.right: parent.right
anchors.top: parent.top
color: DynamicColors.palette.m3primary
font.pointSize: Appearance.font.size.large
text: "check_circle"
visible: isCurrent
}
} }
} }
} }
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
} }
+31 -10
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
Behavior on offsetScale { states: State {
Anim { name: "visible"
duration: Appearance.anim.durations.expressiveDefaultSpatial when: root.visibilities.settings
easing.bezierCurve: Appearance.anim.curves.expressiveDefaultSpatial
PropertyChanges {
root.implicitHeight: content.implicitHeight
} }
} }
transitions: [
Transition {
from: ""
to: "visible"
Anim {
duration: MaterialEasing.expressiveEffectsTime
easing.bezierCurve: MaterialEasing.expressiveEffects
property: "implicitHeight"
target: root
}
},
Transition {
from: "visible"
to: ""
Anim {
easing.bezierCurve: MaterialEasing.expressiveEffects
property: "implicitHeight"
target: root
}
}
]
CustomClippingRect { CustomClippingRect {
anchors.fill: parent anchors.fill: parent
+1
View File
@@ -19,6 +19,7 @@ Scope {
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;
console.log(root.launcherInterrupted);
} }
root.launcherInterrupted = false; root.launcherInterrupted = false;
} }
-1
View File
@@ -1,6 +1,5 @@
import QtQuick.Layouts import QtQuick.Layouts
import QtQuick import QtQuick
import QtQuick.VectorImage
import Quickshell import Quickshell
import Quickshell.Services.SystemTray import Quickshell.Services.SystemTray
import qs.Modules import qs.Modules
-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;
}
}
}
}
+55 -54
View File
@@ -1,7 +1,5 @@
pragma ComponentBehavior: Bound pragma ComponentBehavior: Bound
import Quickshell
import Quickshell.Hyprland
import QtQuick import QtQuick
import qs.Components import qs.Components
import qs.Helpers import qs.Helpers
@@ -10,76 +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
function refreshData(): void {
Hyprland.refreshMonitors();
const scale = Hyprland.monitorFor(root.screen).scale;
if (scale > 0 && img.resScale !== scale) {
img.resScale = scale;
img.sourceSize.width = root.screen.width * scale;
}
const displayData = Wallpapers.getCrop(root.screen.name);
const displayRect = Qt.rect(img.sourceSize.width * displayData.x, img.implicitHeight * displayData.y, img.sourceSize.width * displayData.width, img.implicitHeight * displayData.height);
img.anchors.fill = null;
img.zoom = displayData.zoom;
img.x = -(displayRect.x * displayData.zoom / img.resScale);
img.y = -(displayRect.y * displayData.zoom / img.resScale);
}
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
property int displayH property real imageRatio: Math.max(1, sourceSize.width) / Math.max(1, sourceSize.height)
property int displayW property bool isValid: sourceSize.width > 0 && sourceSize.height > 0 && root.width > 0 && root.height > 0
property real resScale property real windowRatio: root.width / Math.max(1, root.height)
property real zoom: 1.0
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
height: implicitHeight * zoom / resScale height: isValid ? (imageRatio > windowRatio ? root.height : root.width / imageRatio) * Config.background.zoom : root.height
opacity: 1 opacity: 0
retainWhileLoading: true scale: Wallpapers.showPreview ? 1 : 0.8
source: root.source width: isValid ? (imageRatio > windowRatio ? root.height * imageRatio : root.width) * Config.background.zoom : root.width
sourceSize.width: root.screen.width * resScale x: isValid ? (root.width - width) * Config.background.alignX : 0
width: implicitWidth * zoom / resScale y: isValid ? (root.height - height) * Config.background.alignY : 0
Behavior on height { states: State {
Anim { name: "visible"
when: root.current === img
PropertyChanges {
img.opacity: 1
img.scale: 1
} }
} }
Behavior on width { transitions: Transition {
Anim {
}
}
Behavior on x {
Anim {
}
}
Behavior on y {
Anim { Anim {
duration: Config.background.wallFadeDuration
properties: "opacity,scale"
target: img
} }
} }
onStatusChanged: { onStatusChanged: {
if (img.status == Image.Ready) { if (status === Image.Ready) {
root.refreshData(); root.current = this;
} }
} }
Connections {
function onAdapterUpdated(): void {
root.refreshData();
}
function onLoaded(): void {
root.refreshData();
}
target: Wallpapers.monitorCrops
}
} }
} }
-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}`);
} }
} }
} }
+28 -9
View File
@@ -59,6 +59,20 @@ void BlobShape::setRadius(qreal r) {
m_group->markDirty(); m_group->markDirty();
} }
void BlobShape::setTargetWidth(qreal w) {
if (qFuzzyCompare(m_targetWidth, w))
return;
m_targetWidth = w;
emit targetWidthChanged();
}
void BlobShape::setTargetHeight(qreal h) {
if (qFuzzyCompare(m_targetHeight, h))
return;
m_targetHeight = h;
emit targetHeightChanged();
}
void BlobShape::componentComplete() { void BlobShape::componentComplete() {
QQuickItem::componentComplete(); QQuickItem::componentComplete();
if (m_group) if (m_group)
@@ -72,17 +86,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);
} }
} }
@@ -124,6 +132,17 @@ void BlobShape::updatePolish() {
if (!m_group) if (!m_group)
return; return;
// Check if target dimensions (panel size) have changed since last polish.
// This catches cases where the panel's implicit size changes but the
// BlobRect's geometry doesn't update (e.g. parent Rectangle doesn't
// respect implicit sizes), which would otherwise skip dirty marking.
if (!qFuzzyCompare(m_targetWidth, m_lastPolishTargetWidth) ||
!qFuzzyCompare(m_targetHeight, m_lastPolishTargetHeight)) {
m_lastPolishTargetWidth = m_targetWidth;
m_lastPolishTargetHeight = m_targetHeight;
m_group->markDirty();
}
// Ensure all shapes have up-to-date physics (only once per frame) // Ensure all shapes have up-to-date physics (only once per frame)
m_group->ensurePhysicsUpdated(); m_group->ensurePhysicsUpdated();
+23 -6
View File
@@ -14,6 +14,8 @@ Q_PROPERTY(BlobGroup* group READ group WRITE setGroup NOTIFY groupChanged)
Q_PROPERTY(qreal radius READ radius WRITE setRadius NOTIFY radiusChanged) Q_PROPERTY(qreal radius READ radius WRITE setRadius NOTIFY radiusChanged)
Q_PROPERTY(QMatrix4x4 deformMatrix READ deformMatrix NOTIFY deformMatrixChanged) Q_PROPERTY(QMatrix4x4 deformMatrix READ deformMatrix NOTIFY deformMatrixChanged)
Q_PROPERTY(QMatrix4x4 rawDeformMatrix READ rawDeformMatrix NOTIFY rawDeformMatrixChanged) Q_PROPERTY(QMatrix4x4 rawDeformMatrix READ rawDeformMatrix NOTIFY rawDeformMatrixChanged)
Q_PROPERTY(qreal targetWidth READ targetWidth WRITE setTargetWidth NOTIFY targetWidthChanged)
Q_PROPERTY(qreal targetHeight READ targetHeight WRITE setTargetHeight NOTIFY targetHeightChanged)
friend class BlobGroup; friend class BlobGroup;
@@ -42,10 +44,23 @@ QMatrix4x4 rawDeformMatrix() const {
} }
signals: signals:
void groupChanged(); void groupChanged();
void radiusChanged(); void radiusChanged();
void deformMatrixChanged(); void deformMatrixChanged();
void rawDeformMatrixChanged(); void rawDeformMatrixChanged();
void targetWidthChanged();
void targetHeightChanged();
public:
qreal targetWidth() const {
return m_targetWidth;
}
void setTargetWidth(qreal w);
qreal targetHeight() const {
return m_targetHeight;
}
void setTargetHeight(qreal h);
protected: protected:
void componentComplete() override; void componentComplete() override;
@@ -85,8 +100,10 @@ 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; qreal m_targetWidth = 0;
float m_pendingDh = 0; qreal m_targetHeight = 0;
qreal m_lastPolishTargetWidth = -1;
qreal m_lastPolishTargetHeight = -1;
bool m_cachedHasInverted = false; bool m_cachedHasInverted = false;
float m_cachedInvertedRadius = 0; float m_cachedInvertedRadius = 0;
float m_cachedInvertedOuter[4] = {}; float m_cachedInvertedOuter[4] = {};
+142 -309
View File
@@ -1,384 +1,217 @@
#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("")
, m_eventSocket("") , m_eventSocket("")
, m_socket(nullptr) , m_socket(nullptr)
, m_socketValid(false) , m_socketValid(false)
, 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()
return; << "HyprExtras::HyprExtras: $HYPRLAND_INSTANCE_SIGNATURE is unset. Unable to connect to Hyprland socket.";
} 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 "
return; "Hyprland socket.";
} 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();
m_socket = new QLocalSocket(this); m_socket = new QLocalSocket(this);
QObject::connect(m_socket, &QLocalSocket::errorOccurred, this, &HyprExtras::socketError); QObject::connect(m_socket, &QLocalSocket::errorOccurred, this, &HyprExtras::socketError);
QObject::connect(m_socket, &QLocalSocket::stateChanged, this, &HyprExtras::socketStateChanged); QObject::connect(m_socket, &QLocalSocket::stateChanged, this, &HyprExtras::socketStateChanged);
QObject::connect(m_socket, &QLocalSocket::readyRead, this, &HyprExtras::readEvent); QObject::connect(m_socket, &QLocalSocket::readyRead, this, &HyprExtras::readEvent);
m_socket->connectToServer(m_eventSocket, QLocalSocket::ReadOnly); m_socket->connectToServer(m_eventSocket, QLocalSocket::ReadOnly);
} }
QVariantHash HyprExtras::options() const { QVariantHash HyprExtras::options() const {
return m_options; return m_options;
} }
HyprDevices* HyprExtras::devices() const { HyprDevices* HyprExtras::devices() const {
return m_devices; return m_devices;
} }
void HyprExtras::message(const QString& message) { void HyprExtras::message(const QString& message) {
if (message.isEmpty()) { if (message.isEmpty()) {
return; return;
} }
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);
} }
}); });
} }
void HyprExtras::batchMessage(const QStringList& messages) { void HyprExtras::batchMessage(const QStringList& messages) {
if (messages.isEmpty()) { if (messages.isEmpty()) {
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) { qWarning() << "HyprExtras::batchMessage: request error:" << QString::fromUtf8(res);
qCWarning(lcHypr) << "batchMessage: request error:" << QString::fromUtf8(res); }
} });
});
} }
void HyprExtras::applyOptions(const QVariantHash& options) { void HyprExtras::applyOptions(const QVariantHash& options) {
if (options.isEmpty()) { if (options.isEmpty()) {
return; return;
} }
QStringList calls; QString request = "[[BATCH]]";
calls.reserve(options.size()); for (auto it = options.constBegin(); it != options.constEnd(); ++it) {
request += QString("keyword %1 %2;").arg(it.key(), it.value().toString());
}
for (auto it = options.constBegin(); it != options.constEnd(); ++it) { makeRequest(request, [this](bool success, const QByteArray& res) {
const auto call = buildHlConfigCall(it.key(), it.value()); if (success) {
if (!call.isEmpty()) { refreshOptions();
calls << call; } else {
} qWarning() << "HyprExtras::applyOptions: request error" << QString::fromUtf8(res);
} }
});
if (calls.isEmpty()) {
return;
}
makeRequest(QStringLiteral("eval ") + calls.join(QLatin1String("; ")), [this](bool success, const QByteArray& res) {
if (success) {
refreshOptions();
} else {
qCWarning(lcHypr) << "applyOptions: request error" << QString::fromUtf8(res);
}
});
} }
void HyprExtras::refreshOptions() { void HyprExtras::refreshOptions() {
if (!m_optionsRefresh.isNull()) { if (!m_optionsRefresh.isNull()) {
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;
} }
const auto options = response.array(); const auto options = response.array();
bool dirty = false; bool dirty = false;
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) {
dirty = true;
m_options.insert(key, value);
}
}
if (m_options.value(key) != value) { if (dirty) {
dirty = true; emit optionsChanged();
m_options.insert(key, value); }
} });
}
if (dirty) {
emit optionsChanged();
}
});
} }
void HyprExtras::refreshDevices() { void HyprExtras::refreshDevices() {
if (!m_devicesRefresh.isNull()) { if (!m_devicesRefresh.isNull()) {
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());
} }
}); });
} }
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;
} }
void HyprExtras::readEvent() { void HyprExtras::readEvent() {
while (true) { while (true) {
auto rawEvent = m_socket->readLine(); auto rawEvent = m_socket->readLine();
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));
}); });
} }
HyprExtras::SocketPtr HyprExtras::makeRequest( HyprExtras::SocketPtr HyprExtras::makeRequest(
const QString& request, const std::function<void(bool, QByteArray)>& callback) { const QString& request, const std::function<void(bool, QByteArray)>& callback) {
if (m_requestSocket.isEmpty()) { if (m_requestSocket.isEmpty()) {
return SocketPtr(); return SocketPtr();
} }
auto socket = SocketPtr::create(this); auto socket = SocketPtr::create(this);
QObject::connect(socket.data(), &QLocalSocket::connected, this, [=, this]() { QObject::connect(socket.data(), &QLocalSocket::connected, this, [=, this]() {
QObject::connect(socket.data(), &QLocalSocket::readyRead, this, [socket, callback]() { QObject::connect(socket.data(), &QLocalSocket::readyRead, this, [socket, callback]() {
const auto response = socket->readAll(); const auto response = socket->readAll();
callback(true, std::move(response)); callback(true, std::move(response));
socket->close(); socket->close();
}); });
socket->write(request.toUtf8()); socket->write(request.toUtf8());
socket->flush(); socket->flush();
}); });
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();
}); });
socket->connectToServer(m_requestSocket); socket->connectToServer(m_requestSocket);
return socket; return socket;
} }
} // namespace ZShell::internal::hypr } // namespace ZShell::internal::hypr
+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
+311 -302
View File
@@ -7,464 +7,473 @@
namespace ZShell::models { namespace ZShell::models {
FileSystemEntry::FileSystemEntry(const QString& path, const QString& relativePath, QObject* parent) FileSystemEntry::FileSystemEntry(const QString& path, const QString& relativePath, QObject* parent)
: QObject(parent) : QObject(parent)
, m_fileInfo(path) , m_fileInfo(path)
, m_path(path) , m_path(path)
, m_relativePath(relativePath) , m_relativePath(relativePath)
, m_isImageInitialised(false) , m_isImageInitialised(false)
, m_mimeTypeInitialised(false) { , m_mimeTypeInitialised(false) {}
}
QString FileSystemEntry::path() const { QString FileSystemEntry::path() const {
return m_path; return m_path;
}; };
QString FileSystemEntry::relativePath() const { QString FileSystemEntry::relativePath() const {
return m_relativePath; return m_relativePath;
}; };
QString FileSystemEntry::name() const { QString FileSystemEntry::name() const {
return m_fileInfo.fileName(); return m_fileInfo.fileName();
}; };
QString FileSystemEntry::baseName() const { QString FileSystemEntry::baseName() const {
return m_fileInfo.baseName(); return m_fileInfo.baseName();
}; };
QString FileSystemEntry::parentDir() const { QString FileSystemEntry::parentDir() const {
return m_fileInfo.absolutePath(); return m_fileInfo.absolutePath();
}; };
QString FileSystemEntry::suffix() const { QString FileSystemEntry::suffix() const {
return m_fileInfo.completeSuffix(); return m_fileInfo.completeSuffix();
}; };
qint64 FileSystemEntry::size() const { qint64 FileSystemEntry::size() const {
return m_fileInfo.size(); return m_fileInfo.size();
}; };
bool FileSystemEntry::isDir() const { bool FileSystemEntry::isDir() const {
return m_fileInfo.isDir(); return m_fileInfo.isDir();
}; };
bool FileSystemEntry::isImage() const { bool FileSystemEntry::isImage() const {
if (!m_isImageInitialised) { if (!m_isImageInitialised) {
QImageReader reader(m_path); QImageReader reader(m_path);
m_isImage = reader.canRead(); m_isImage = reader.canRead();
m_isImageInitialised = true; m_isImageInitialised = true;
} }
return m_isImage; return m_isImage;
} }
QString FileSystemEntry::mimeType() const { QString FileSystemEntry::mimeType() const {
if (!m_mimeTypeInitialised) { if (!m_mimeTypeInitialised) {
static const QMimeDatabase s_db; const QMimeDatabase db;
m_mimeType = s_db.mimeTypeForFile(m_path).name(); m_mimeType = db.mimeTypeForFile(m_path).name();
m_mimeTypeInitialised = true; m_mimeTypeInitialised = true;
} }
return m_mimeType; return m_mimeType;
} }
void FileSystemEntry::updateRelativePath(const QDir& dir) { void FileSystemEntry::updateRelativePath(const QDir& dir) {
const auto relPath = dir.relativeFilePath(m_path); const auto relPath = dir.relativeFilePath(m_path);
if (m_relativePath != relPath) { if (m_relativePath != relPath) {
m_relativePath = relPath; m_relativePath = relPath;
emit relativePathChanged(); emit relativePathChanged();
} }
} }
FileSystemModel::FileSystemModel(QObject* parent) FileSystemModel::FileSystemModel(QObject* parent)
: QAbstractListModel(parent) : QAbstractListModel(parent)
, m_recursive(false) , m_recursive(false)
, m_watchChanges(true) , m_watchChanges(true)
, m_showHidden(false) , m_showHidden(false)
, m_filter(NoFilter) { , m_filter(NoFilter) {
connect(&m_watcher, &QFileSystemWatcher::directoryChanged, this, &FileSystemModel::watchDirIfRecursive); connect(&m_watcher, &QFileSystemWatcher::directoryChanged, this, &FileSystemModel::watchDirIfRecursive);
connect(&m_watcher, &QFileSystemWatcher::directoryChanged, this, &FileSystemModel::updateEntriesForDir); connect(&m_watcher, &QFileSystemWatcher::directoryChanged, this, &FileSystemModel::updateEntriesForDir);
} }
int FileSystemModel::rowCount(const QModelIndex& parent) const { int FileSystemModel::rowCount(const QModelIndex& parent) const {
if (parent != QModelIndex()) { if (parent != QModelIndex()) {
return 0; return 0;
} }
return static_cast<int>(m_entries.size()); return static_cast<int>(m_entries.size());
} }
QVariant FileSystemModel::data(const QModelIndex& index, int role) const { QVariant FileSystemModel::data(const QModelIndex& index, int role) const {
if (role != Qt::UserRole || !index.isValid() || index.row() >= m_entries.size()) { if (role != Qt::UserRole || !index.isValid() || index.row() >= m_entries.size()) {
return QVariant(); return QVariant();
} }
return QVariant::fromValue(m_entries.at(index.row())); return QVariant::fromValue(m_entries.at(index.row()));
} }
QHash<int, QByteArray> FileSystemModel::roleNames() const { QHash<int, QByteArray> FileSystemModel::roleNames() const {
return { { Qt::UserRole, "modelData" } }; return { { Qt::UserRole, "modelData" } };
} }
QString FileSystemModel::path() const { QString FileSystemModel::path() const {
return m_path; return m_path;
} }
void FileSystemModel::setPath(const QString& path) { void FileSystemModel::setPath(const QString& path) {
if (m_path == path) { if (m_path == path) {
return; return;
} }
m_path = path; m_path = path;
emit pathChanged(); emit pathChanged();
m_dir.setPath(m_path); m_dir.setPath(m_path);
for (const auto& entry : std::as_const(m_entries)) { for (const auto& entry : std::as_const(m_entries)) {
entry->updateRelativePath(m_dir); entry->updateRelativePath(m_dir);
} }
update(); update();
} }
bool FileSystemModel::recursive() const { bool FileSystemModel::recursive() const {
return m_recursive; return m_recursive;
} }
void FileSystemModel::setRecursive(bool recursive) { void FileSystemModel::setRecursive(bool recursive) {
if (m_recursive == recursive) { if (m_recursive == recursive) {
return; return;
} }
m_recursive = recursive; m_recursive = recursive;
emit recursiveChanged(); emit recursiveChanged();
update(); update();
} }
bool FileSystemModel::watchChanges() const { bool FileSystemModel::watchChanges() const {
return m_watchChanges; return m_watchChanges;
} }
void FileSystemModel::setWatchChanges(bool watchChanges) { void FileSystemModel::setWatchChanges(bool watchChanges) {
if (m_watchChanges == watchChanges) { if (m_watchChanges == watchChanges) {
return; return;
} }
m_watchChanges = watchChanges; m_watchChanges = watchChanges;
emit watchChangesChanged(); emit watchChangesChanged();
update(); update();
} }
bool FileSystemModel::showHidden() const { bool FileSystemModel::showHidden() const {
return m_showHidden; return m_showHidden;
} }
void FileSystemModel::setShowHidden(bool showHidden) { void FileSystemModel::setShowHidden(bool showHidden) {
if (m_showHidden == showHidden) { if (m_showHidden == showHidden) {
return; return;
} }
m_showHidden = showHidden; m_showHidden = showHidden;
emit showHiddenChanged(); emit showHiddenChanged();
update(); update();
} }
bool FileSystemModel::sortReverse() const { bool FileSystemModel::sortReverse() const {
return m_sortReverse; return m_sortReverse;
} }
void FileSystemModel::setSortReverse(bool sortReverse) { void FileSystemModel::setSortReverse(bool sortReverse) {
if (m_sortReverse == sortReverse) { if (m_sortReverse == sortReverse) {
return; return;
} }
m_sortReverse = sortReverse; m_sortReverse = sortReverse;
emit sortReverseChanged(); emit sortReverseChanged();
update(); update();
} }
FileSystemModel::Filter FileSystemModel::filter() const { FileSystemModel::Filter FileSystemModel::filter() const {
return m_filter; return m_filter;
} }
void FileSystemModel::setFilter(Filter filter) { void FileSystemModel::setFilter(Filter filter) {
if (m_filter == filter) { if (m_filter == filter) {
return; return;
} }
m_filter = filter; m_filter = filter;
emit filterChanged(); emit filterChanged();
update(); update();
} }
QStringList FileSystemModel::nameFilters() const { QStringList FileSystemModel::nameFilters() const {
return m_nameFilters; return m_nameFilters;
} }
void FileSystemModel::setNameFilters(const QStringList& nameFilters) { void FileSystemModel::setNameFilters(const QStringList& nameFilters) {
if (m_nameFilters == nameFilters) { if (m_nameFilters == nameFilters) {
return; return;
} }
m_nameFilters = nameFilters; m_nameFilters = nameFilters;
emit nameFiltersChanged(); emit nameFiltersChanged();
update(); update();
} }
QQmlListProperty<FileSystemEntry> FileSystemModel::entries() { QQmlListProperty<FileSystemEntry> FileSystemModel::entries() {
return QQmlListProperty<FileSystemEntry>(this, &m_entries); return QQmlListProperty<FileSystemEntry>(this, &m_entries);
} }
void FileSystemModel::watchDirIfRecursive(const QString& path) { void FileSystemModel::watchDirIfRecursive(const QString& path) {
if (m_recursive && m_watchChanges) { if (m_recursive && m_watchChanges) {
const auto currentDir = m_dir; const auto currentDir = m_dir;
const bool showHidden = m_showHidden; const bool showHidden = m_showHidden;
auto future = QtConcurrent::run([showHidden, path]() { const auto future = QtConcurrent::run([showHidden, path]() {
QDir::Filters filters = QDir::Dirs | QDir::NoDotAndDotDot; QDir::Filters filters = QDir::Dirs | QDir::NoDotAndDotDot;
if (showHidden) { if (showHidden) {
filters |= QDir::Hidden; filters |= QDir::Hidden;
} }
QDirIterator iter(path, filters, QDirIterator::Subdirectories); QDirIterator iter(path, filters, QDirIterator::Subdirectories);
QStringList dirs; QStringList dirs;
while (iter.hasNext()) { while (iter.hasNext()) {
dirs << iter.next(); dirs << iter.next();
} }
return dirs; return dirs;
}); });
future.then(this, [currentDir, showHidden, this](const QStringList& paths) { const auto watcher = new QFutureWatcher<QStringList>(this);
if (currentDir == m_dir && showHidden == m_showHidden && !paths.isEmpty()) { connect(watcher, &QFutureWatcher<QStringList>::finished, this, [currentDir, showHidden, watcher, this]() {
// Ignore if dir or showHidden has changed const auto paths = watcher->result();
m_watcher.addPaths(paths); if (currentDir == m_dir && showHidden == m_showHidden && !paths.isEmpty()) {
} // Ignore if dir or showHidden has changed
}); m_watcher.addPaths(paths);
} }
watcher->deleteLater();
});
watcher->setFuture(future);
}
} }
void FileSystemModel::update() { void FileSystemModel::update() {
updateWatcher(); updateWatcher();
updateEntries(); updateEntries();
} }
void FileSystemModel::updateWatcher() { void FileSystemModel::updateWatcher() {
if (!m_watcher.directories().isEmpty()) { if (!m_watcher.directories().isEmpty()) {
m_watcher.removePaths(m_watcher.directories()); m_watcher.removePaths(m_watcher.directories());
} }
if (!m_watchChanges || m_path.isEmpty()) { if (!m_watchChanges || m_path.isEmpty()) {
return; return;
} }
m_watcher.addPath(m_path); m_watcher.addPath(m_path);
watchDirIfRecursive(m_path); watchDirIfRecursive(m_path);
} }
void FileSystemModel::updateEntries() { void FileSystemModel::updateEntries() {
if (m_path.isEmpty()) { if (m_path.isEmpty()) {
if (!m_entries.isEmpty()) { if (!m_entries.isEmpty()) {
beginResetModel(); beginResetModel();
qDeleteAll(m_entries); qDeleteAll(m_entries);
m_entries.clear(); m_entries.clear();
endResetModel(); endResetModel();
emit entriesChanged(); emit entriesChanged();
} }
return; return;
} }
for (auto& future : m_futures) { for (auto& future : m_futures) {
future.cancel(); future.cancel();
} }
m_futures.clear(); m_futures.clear();
updateEntriesForDir(m_path); updateEntriesForDir(m_path);
} }
void FileSystemModel::updateEntriesForDir(const QString& dir) { void FileSystemModel::updateEntriesForDir(const QString& dir) {
const auto recursive = m_recursive; const auto recursive = m_recursive;
const auto showHidden = m_showHidden; const auto showHidden = m_showHidden;
const auto filter = m_filter; const auto filter = m_filter;
const auto nameFilters = m_nameFilters; const auto nameFilters = m_nameFilters;
QSet<QString> oldPaths; QSet<QString> oldPaths;
for (const auto& entry : std::as_const(m_entries)) { for (const auto& entry : std::as_const(m_entries)) {
oldPaths << entry->path(); oldPaths << entry->path();
} }
auto future = QtConcurrent::run([=](QPromise<QPair<QSet<QString>, QSet<QString> > >& promise) { const auto future = QtConcurrent::run([=](QPromise<QPair<QSet<QString>, QSet<QString>>>& promise) {
const auto flags = recursive ? QDirIterator::Subdirectories : QDirIterator::NoIteratorFlags; const auto flags = recursive ? QDirIterator::Subdirectories : QDirIterator::NoIteratorFlags;
std::optional<QDirIterator> iter; std::optional<QDirIterator> iter;
if (filter == Images) { if (filter == Images) {
QStringList extraNameFilters = nameFilters; QStringList extraNameFilters = nameFilters;
const auto formats = QImageReader::supportedImageFormats(); const auto formats = QImageReader::supportedImageFormats();
for (const auto& format : formats) { for (const auto& format : formats) {
extraNameFilters << "*." + format; extraNameFilters << "*." + format;
} }
QDir::Filters filters = QDir::Files; QDir::Filters filters = QDir::Files;
if (showHidden) { if (showHidden) {
filters |= QDir::Hidden; filters |= QDir::Hidden;
} }
iter.emplace(dir, extraNameFilters, filters, flags); iter.emplace(dir, extraNameFilters, filters, flags);
} else { } else {
QDir::Filters filters; QDir::Filters filters;
if (filter == Files) { if (filter == Files) {
filters = QDir::Files; filters = QDir::Files;
} else if (filter == Dirs) { } else if (filter == Dirs) {
filters = QDir::Dirs | QDir::NoDotAndDotDot; filters = QDir::Dirs | QDir::NoDotAndDotDot;
} else { } else {
filters = QDir::Dirs | QDir::Files | QDir::NoDotAndDotDot; filters = QDir::Dirs | QDir::Files | QDir::NoDotAndDotDot;
} }
if (showHidden) { if (showHidden) {
filters |= QDir::Hidden; filters |= QDir::Hidden;
} }
if (nameFilters.isEmpty()) { if (nameFilters.isEmpty()) {
iter.emplace(dir, filters, flags); iter.emplace(dir, filters, flags);
} else { } else {
iter.emplace(dir, nameFilters, filters, flags); iter.emplace(dir, nameFilters, filters, flags);
} }
} }
QSet<QString> newPaths; QSet<QString> newPaths;
while (iter->hasNext()) { while (iter->hasNext()) {
if (promise.isCanceled()) { if (promise.isCanceled()) {
return; return;
} }
QString path = iter->next(); QString path = iter->next();
if (filter == Images) { if (filter == Images) {
QImageReader reader(path); QImageReader reader(path);
if (!reader.canRead()) { if (!reader.canRead()) {
continue; continue;
} }
} }
newPaths.insert(path); newPaths.insert(path);
} }
if (promise.isCanceled()) { if (promise.isCanceled() || newPaths == oldPaths) {
return; return;
} }
promise.addResult(qMakePair(oldPaths - newPaths, newPaths - oldPaths)); promise.addResult(qMakePair(oldPaths - newPaths, newPaths - oldPaths));
}); });
if (m_futures.contains(dir)) { if (m_futures.contains(dir)) {
m_futures[dir].cancel(); m_futures[dir].cancel();
} }
m_futures.insert(dir, future); m_futures.insert(dir, future);
future const auto watcher = new QFutureWatcher<QPair<QSet<QString>, QSet<QString>>>(this);
.then(this,
[dir, this](QPair<QSet<QString>, QSet<QString> > result) { connect(watcher, &QFutureWatcher<QPair<QSet<QString>, QSet<QString>>>::finished, this, [dir, watcher, this]() {
m_futures.remove(dir); m_futures.remove(dir);
if (!result.first.isEmpty() || !result.second.isEmpty()) {
applyChanges(result.first, result.second); if (!watcher->future().isResultReadyAt(0)) {
} watcher->deleteLater();
}) return;
.onCanceled(this, [dir, this]() { }
m_futures.remove(dir);
}); const auto result = watcher->result();
applyChanges(result.first, result.second);
watcher->deleteLater();
});
watcher->setFuture(future);
} }
void FileSystemModel::applyChanges(const QSet<QString>& removedPaths, const QSet<QString>& addedPaths) { void FileSystemModel::applyChanges(const QSet<QString>& removedPaths, const QSet<QString>& addedPaths) {
QList<int> removedIndices; QList<int> removedIndices;
for (int i = 0; i < m_entries.size(); ++i) { for (int i = 0; i < m_entries.size(); ++i) {
if (removedPaths.contains(m_entries[i]->path())) { if (removedPaths.contains(m_entries[i]->path())) {
removedIndices << i; removedIndices << i;
} }
} }
std::sort(removedIndices.begin(), removedIndices.end(), std::greater<int>()); std::sort(removedIndices.begin(), removedIndices.end(), std::greater<int>());
// Batch remove old entries // Batch remove old entries
int start = -1; int start = -1;
int end = -1; int end = -1;
for (int idx : std::as_const(removedIndices)) { for (int idx : std::as_const(removedIndices)) {
if (start == -1) { if (start == -1) {
start = idx; start = idx;
end = idx; end = idx;
} else if (idx == end - 1) { } else if (idx == end - 1) {
end = idx; end = idx;
} else { } else {
beginRemoveRows(QModelIndex(), end, start); beginRemoveRows(QModelIndex(), end, start);
for (int i = start; i >= end; --i) { for (int i = start; i >= end; --i) {
m_entries.takeAt(i)->deleteLater(); m_entries.takeAt(i)->deleteLater();
} }
endRemoveRows(); endRemoveRows();
start = idx; start = idx;
end = idx; end = idx;
} }
} }
if (start != -1) { if (start != -1) {
beginRemoveRows(QModelIndex(), end, start); beginRemoveRows(QModelIndex(), end, start);
for (int i = start; i >= end; --i) { for (int i = start; i >= end; --i) {
m_entries.takeAt(i)->deleteLater(); m_entries.takeAt(i)->deleteLater();
} }
endRemoveRows(); endRemoveRows();
} }
// Create new entries // Create new entries
QList<FileSystemEntry*> newEntries; QList<FileSystemEntry*> newEntries;
for (const auto& path : addedPaths) { for (const auto& path : addedPaths) {
newEntries << new FileSystemEntry(path, m_dir.relativeFilePath(path), this); newEntries << new FileSystemEntry(path, m_dir.relativeFilePath(path), this);
} }
std::sort(newEntries.begin(), newEntries.end(), [this](const FileSystemEntry* a, const FileSystemEntry* b) { std::sort(newEntries.begin(), newEntries.end(), [this](const FileSystemEntry* a, const FileSystemEntry* b) {
return compareEntries(a, b); return compareEntries(a, b);
}); });
// Batch insert new entries // Batch insert new entries
int insertStart = -1; int insertStart = -1;
QList<FileSystemEntry*> batchItems; QList<FileSystemEntry*> batchItems;
for (const auto& entry : std::as_const(newEntries)) { for (const auto& entry : std::as_const(newEntries)) {
const auto it = std::lower_bound( const auto it = std::lower_bound(
m_entries.begin(), m_entries.end(), entry, [this](const FileSystemEntry* a, const FileSystemEntry* b) { m_entries.begin(), m_entries.end(), entry, [this](const FileSystemEntry* a, const FileSystemEntry* b) {
return compareEntries(a, b); return compareEntries(a, b);
}); });
const auto row = static_cast<int>(it - m_entries.begin()); const auto row = static_cast<int>(it - m_entries.begin());
if (insertStart == -1) { if (insertStart == -1) {
insertStart = row; insertStart = row;
batchItems << entry; batchItems << entry;
} else if (row == insertStart + batchItems.size()) { } else if (row == insertStart + batchItems.size()) {
batchItems << entry; batchItems << entry;
} else { } else {
beginInsertRows(QModelIndex(), insertStart, insertStart + static_cast<int>(batchItems.size()) - 1); beginInsertRows(QModelIndex(), insertStart, insertStart + static_cast<int>(batchItems.size()) - 1);
for (int i = 0; i < batchItems.size(); ++i) { for (int i = 0; i < batchItems.size(); ++i) {
m_entries.insert(insertStart + i, batchItems[i]); m_entries.insert(insertStart + i, batchItems[i]);
} }
endInsertRows(); endInsertRows();
insertStart = row; insertStart = row;
batchItems.clear(); batchItems.clear();
batchItems << entry; batchItems << entry;
} }
} }
if (!batchItems.isEmpty()) { if (!batchItems.isEmpty()) {
beginInsertRows(QModelIndex(), insertStart, insertStart + static_cast<int>(batchItems.size()) - 1); beginInsertRows(QModelIndex(), insertStart, insertStart + static_cast<int>(batchItems.size()) - 1);
for (int i = 0; i < batchItems.size(); ++i) { for (int i = 0; i < batchItems.size(); ++i) {
m_entries.insert(insertStart + i, batchItems[i]); m_entries.insert(insertStart + i, batchItems[i]);
} }
endInsertRows(); endInsertRows();
} }
emit entriesChanged(); emit entriesChanged();
} }
bool FileSystemModel::compareEntries(const FileSystemEntry* a, const FileSystemEntry* b) const { bool FileSystemModel::compareEntries(const FileSystemEntry* a, const FileSystemEntry* b) const {
if (a->isDir() != b->isDir()) { if (a->isDir() != b->isDir()) {
return m_sortReverse ^ a->isDir(); return m_sortReverse ^ a->isDir();
} }
const auto cmp = a->relativePath().localeAwareCompare(b->relativePath()); const auto cmp = a->relativePath().localeAwareCompare(b->relativePath());
return m_sortReverse ? cmp > 0 : cmp < 0; return m_sortReverse ? cmp > 0 : cmp < 0;
} }
} // namespace ZShell::models } // namespace ZShell::models
+95 -95
View File
@@ -13,136 +13,136 @@
namespace ZShell::models { namespace ZShell::models {
class FileSystemEntry : public QObject { class FileSystemEntry : public QObject {
Q_OBJECT Q_OBJECT
QML_ELEMENT QML_ELEMENT
QML_UNCREATABLE("FileSystemEntry instances can only be retrieved from a FileSystemModel") QML_UNCREATABLE("FileSystemEntry instances can only be retrieved from a FileSystemModel")
Q_PROPERTY(QString path READ path CONSTANT) Q_PROPERTY(QString path READ path CONSTANT)
Q_PROPERTY(QString relativePath READ relativePath NOTIFY relativePathChanged) Q_PROPERTY(QString relativePath READ relativePath NOTIFY relativePathChanged)
Q_PROPERTY(QString name READ name CONSTANT) Q_PROPERTY(QString name READ name CONSTANT)
Q_PROPERTY(QString baseName READ baseName CONSTANT) Q_PROPERTY(QString baseName READ baseName CONSTANT)
Q_PROPERTY(QString parentDir READ parentDir CONSTANT) Q_PROPERTY(QString parentDir READ parentDir CONSTANT)
Q_PROPERTY(QString suffix READ suffix CONSTANT) Q_PROPERTY(QString suffix READ suffix CONSTANT)
Q_PROPERTY(qint64 size READ size CONSTANT) Q_PROPERTY(qint64 size READ size CONSTANT)
Q_PROPERTY(bool isDir READ isDir CONSTANT) Q_PROPERTY(bool isDir READ isDir CONSTANT)
Q_PROPERTY(bool isImage READ isImage CONSTANT) Q_PROPERTY(bool isImage READ isImage CONSTANT)
Q_PROPERTY(QString mimeType READ mimeType CONSTANT) Q_PROPERTY(QString mimeType READ mimeType CONSTANT)
public: public:
explicit FileSystemEntry(const QString& path, const QString& relativePath, QObject* parent = nullptr); explicit FileSystemEntry(const QString& path, const QString& relativePath, QObject* parent = nullptr);
[[nodiscard]] QString path() const; [[nodiscard]] QString path() const;
[[nodiscard]] QString relativePath() const; [[nodiscard]] QString relativePath() const;
[[nodiscard]] QString name() const; [[nodiscard]] QString name() const;
[[nodiscard]] QString baseName() const; [[nodiscard]] QString baseName() const;
[[nodiscard]] QString parentDir() const; [[nodiscard]] QString parentDir() const;
[[nodiscard]] QString suffix() const; [[nodiscard]] QString suffix() const;
[[nodiscard]] qint64 size() const; [[nodiscard]] qint64 size() const;
[[nodiscard]] bool isDir() const; [[nodiscard]] bool isDir() const;
[[nodiscard]] bool isImage() const; [[nodiscard]] bool isImage() const;
[[nodiscard]] QString mimeType() const; [[nodiscard]] QString mimeType() const;
void updateRelativePath(const QDir& dir); void updateRelativePath(const QDir& dir);
signals: signals:
void relativePathChanged(); void relativePathChanged();
private: private:
const QFileInfo m_fileInfo; const QFileInfo m_fileInfo;
const QString m_path; const QString m_path;
QString m_relativePath; QString m_relativePath;
mutable bool m_isImage; mutable bool m_isImage;
mutable bool m_isImageInitialised; mutable bool m_isImageInitialised;
mutable QString m_mimeType; mutable QString m_mimeType;
mutable bool m_mimeTypeInitialised; mutable bool m_mimeTypeInitialised;
}; };
class FileSystemModel : public QAbstractListModel { class FileSystemModel : public QAbstractListModel {
Q_OBJECT Q_OBJECT
QML_ELEMENT QML_ELEMENT
Q_PROPERTY(QString path READ path WRITE setPath NOTIFY pathChanged) Q_PROPERTY(QString path READ path WRITE setPath NOTIFY pathChanged)
Q_PROPERTY(bool recursive READ recursive WRITE setRecursive NOTIFY recursiveChanged) Q_PROPERTY(bool recursive READ recursive WRITE setRecursive NOTIFY recursiveChanged)
Q_PROPERTY(bool watchChanges READ watchChanges WRITE setWatchChanges NOTIFY watchChangesChanged) Q_PROPERTY(bool watchChanges READ watchChanges WRITE setWatchChanges NOTIFY watchChangesChanged)
Q_PROPERTY(bool showHidden READ showHidden WRITE setShowHidden NOTIFY showHiddenChanged) Q_PROPERTY(bool showHidden READ showHidden WRITE setShowHidden NOTIFY showHiddenChanged)
Q_PROPERTY(bool sortReverse READ sortReverse WRITE setSortReverse NOTIFY sortReverseChanged) Q_PROPERTY(bool sortReverse READ sortReverse WRITE setSortReverse NOTIFY sortReverseChanged)
Q_PROPERTY(Filter filter READ filter WRITE setFilter NOTIFY filterChanged) Q_PROPERTY(Filter filter READ filter WRITE setFilter NOTIFY filterChanged)
Q_PROPERTY(QStringList nameFilters READ nameFilters WRITE setNameFilters NOTIFY nameFiltersChanged) Q_PROPERTY(QStringList nameFilters READ nameFilters WRITE setNameFilters NOTIFY nameFiltersChanged)
Q_PROPERTY(QQmlListProperty<ZShell::models::FileSystemEntry> entries READ entries NOTIFY entriesChanged) Q_PROPERTY(QQmlListProperty<ZShell::models::FileSystemEntry> entries READ entries NOTIFY entriesChanged)
public: public:
enum Filter { enum Filter {
NoFilter, NoFilter,
Images, Images,
Files, Files,
Dirs Dirs
}; };
Q_ENUM(Filter) Q_ENUM(Filter)
explicit FileSystemModel(QObject* parent = nullptr); explicit FileSystemModel(QObject* parent = nullptr);
int rowCount(const QModelIndex& parent = QModelIndex()) const override; int rowCount(const QModelIndex& parent = QModelIndex()) const override;
QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override;
QHash<int, QByteArray> roleNames() const override; QHash<int, QByteArray> roleNames() const override;
[[nodiscard]] QString path() const; [[nodiscard]] QString path() const;
void setPath(const QString& path); void setPath(const QString& path);
[[nodiscard]] bool recursive() const; [[nodiscard]] bool recursive() const;
void setRecursive(bool recursive); void setRecursive(bool recursive);
[[nodiscard]] bool watchChanges() const; [[nodiscard]] bool watchChanges() const;
void setWatchChanges(bool watchChanges); void setWatchChanges(bool watchChanges);
[[nodiscard]] bool showHidden() const; [[nodiscard]] bool showHidden() const;
void setShowHidden(bool showHidden); void setShowHidden(bool showHidden);
[[nodiscard]] bool sortReverse() const; [[nodiscard]] bool sortReverse() const;
void setSortReverse(bool sortReverse); void setSortReverse(bool sortReverse);
[[nodiscard]] Filter filter() const; [[nodiscard]] Filter filter() const;
void setFilter(Filter filter); void setFilter(Filter filter);
[[nodiscard]] QStringList nameFilters() const; [[nodiscard]] QStringList nameFilters() const;
void setNameFilters(const QStringList& nameFilters); void setNameFilters(const QStringList& nameFilters);
[[nodiscard]] QQmlListProperty<FileSystemEntry> entries(); [[nodiscard]] QQmlListProperty<FileSystemEntry> entries();
signals: signals:
void pathChanged(); void pathChanged();
void recursiveChanged(); void recursiveChanged();
void watchChangesChanged(); void watchChangesChanged();
void showHiddenChanged(); void showHiddenChanged();
void sortReverseChanged(); void sortReverseChanged();
void filterChanged(); void filterChanged();
void nameFiltersChanged(); void nameFiltersChanged();
void entriesChanged(); void entriesChanged();
private: private:
QDir m_dir; QDir m_dir;
QFileSystemWatcher m_watcher; QFileSystemWatcher m_watcher;
QList<FileSystemEntry*> m_entries; QList<FileSystemEntry*> m_entries;
QHash<QString, QFuture<QPair<QSet<QString>, QSet<QString> > > > m_futures; QHash<QString, QFuture<QPair<QSet<QString>, QSet<QString>>>> m_futures;
QString m_path; QString m_path;
bool m_recursive; bool m_recursive;
bool m_watchChanges; bool m_watchChanges;
bool m_showHidden; bool m_showHidden;
bool m_sortReverse = false; bool m_sortReverse;
Filter m_filter; Filter m_filter;
QStringList m_nameFilters; QStringList m_nameFilters;
void watchDirIfRecursive(const QString& path); void watchDirIfRecursive(const QString& path);
void update(); void update();
void updateWatcher(); void updateWatcher();
void updateEntries(); void updateEntries();
void updateEntriesForDir(const QString& dir); void updateEntriesForDir(const QString& dir);
void applyChanges(const QSet<QString>& removedPaths, const QSet<QString>& addedPaths); void applyChanges(const QSet<QString>& removedPaths, const QSet<QString>& addedPaths);
[[nodiscard]] bool compareEntries(const FileSystemEntry* a, const FileSystemEntry* b) const; [[nodiscard]] bool compareEntries(const FileSystemEntry* a, const FileSystemEntry* b) const;
}; };
} // namespace ZShell::models } // namespace ZShell::models
+5 -49
View File
@@ -216,56 +216,12 @@ Action-driven flows (`>` prefix by default) include calculator, wallpaper picker
`zshell-cli` provides these subcommands: `zshell-cli` provides these subcommands:
### `shell` — daemon management - `shell` - start/kill/log/IPC calls
- `screenshot` - open area picker (`start`, `start-freeze`)
- `wallpaper` - set wallpaper + generate lockscreen blur image
- `scheme` - generate and apply dynamic/preset color schemes
| Command | Description | Note: `cli/src/zshell/subcommands/scheme.py` uses Jinja2 templating for `~/.config/zshell/templates` rendering.
|---------|-------------|
| `start` | Start the shell daemon (pass `--no-daemon` to run in foreground) |
| `kill` | Kill the running shell daemon |
| `restart` | Kill then restart the daemon |
| `lock` | Lock the session via IPC |
| `show` | Show the shell window via IPC |
| `log` | Print daemon logs |
### `scheme` — color scheme generation
```
Usage: zshell-cli scheme generate [--preset <scheme>:<variant>] [--accent <accent>]
[--mode <dark|light>] [--image-path <path>]
Generate a color scheme from a wallpaper image (Material You) or from
a built-in preset.
Preset selection:
--preset <scheme>:<variant> Pick a built-in scheme (e.g. catppuccin:mocha)
--accent <name> Accent color for schemes that support it
(catppuccin accepts: blue, green, mauve,
peach, pink, red, rosewater, etc.)
--mode <dark|light> Override variant mode
If variant has both dark and light modes, the mode is auto-detected from
the current system or config preference.
List all available presets:
zshell-cli scheme list-presets # human-readable
zshell-cli scheme list-presets --json # machine-readable (QML UI)
Examples:
zshell-cli scheme generate --preset gruvbox:medium
zshell-cli scheme generate --preset catppuccin:mocha --accent green
zshell-cli scheme generate --preset everforest:medium --mode light
```
Note: Template rendering (Jinja2) applies generated colors to `~/.config/zshell/templates/*`.
### `screenshot` — area picker
- `start` — open interactive area picker
- `start-freeze` — freeze screen then pick
### `wallpaper` — wallpaper management
- Set wallpaper and generate lockscreen blur background
## Greeter ## Greeter
-5
View File
@@ -9,7 +9,6 @@ version = "0.1.0"
dependencies = [ dependencies = [
"typer", "typer",
"pillow", "pillow",
"jinja2",
"materialyoucolor" "materialyoucolor"
] ]
@@ -26,7 +25,3 @@ only-include = [
[tool.ruff] [tool.ruff]
line-length = 120 line-length = 120
[tool.pytest.ini_options]
testpaths = ["tests"]
pythonpath = ["src"]
+1 -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, record from zshell.subcommands import shell, scheme, screenshot, wallpaper
app = typer.Typer() app = typer.Typer()
@@ -8,7 +8,6 @@ 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(record.app, name="record")
# app.add_typer(preset.app, name="preset") # app.add_typer(preset.app, name="preset")
@@ -0,0 +1,550 @@
_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"
}
}
]
}
}
@@ -1,110 +0,0 @@
primary_paletteKeyColor ca9ee6
secondary_paletteKeyColor 7d6799
tertiary_paletteKeyColor f4b8e4
neutral_paletteKeyColor 414559
neutral_variant_paletteKeyColor 303446
background 303446
onBackground c6d0f5
surface 414559
surfaceDim 414559
surfaceBright 55596f
surfaceContainerLowest 292c3c
surfaceContainerLow 363a50
surfaceContainer 303446
surfaceContainerHigh 292c3c
surfaceContainerHighest 232634
onSurface c6d0f5
surfaceVariant 303446
onSurfaceVariant a5adce
inverseSurface c6d0f5
inverseOnSurface 414559
outline 737994
outlineVariant 51576d
shadow 000000
scrim 000000
surfaceTint 7086bc
primary 8caaee
onPrimary 303446
primaryContainer 7086bc
onPrimaryContainer ffffff
inversePrimary 6c4f94
secondary 637195
onSecondary ffffff
secondaryContainer 544874
onSecondaryContainer cbbae8
tertiary f4b8e4
onTertiary 4e1e44
tertiaryContainer bb7da9
onTertiaryContainer 000000
error e78284
onError 4a0019
errorContainer 8c2643
onErrorContainer ffb3c6
primaryFixed e8d4ff
primaryFixedDim ca9ee6
onPrimaryFixed 2a1040
onPrimaryFixedVariant 544874
secondaryFixed e2d4ff
secondaryFixedDim 7d6799
onSecondaryFixed 1a0a28
onSecondaryFixedVariant 3a2850
tertiaryFixed ffd7f5
tertiaryFixedDim f4b8e4
onTertiaryFixed 330832
onTertiaryFixedVariant 653661
term0 414559
term1 e78284
term2 a6d189
term3 e5c890
term4 8caaee
term5 f4b8e4
term6 81c8be
term7 c6d0f5
term8 51576d
term9 e78284
term10 a6d189
term11 e5c890
term12 8caaee
term13 f4b8e4
term14 81c8be
term15 a5adce
rosewater f2d5cf
flamingo eebebe
pink f4b8e4
mauve ca9ee6
red e78284
maroon ea999c
peach ef9f76
yellow e5c890
green a6d189
teal 81c8be
sky 99d1db
sapphire 85c1dc
blue 8caaee
lavender babbf1
klink 6685d1
klinkSelection 6585d1
kvisited 7276dd
kvisitedSelection 7276dd
knegative 8e70ff
knegativeSelection 8e70ff
kneutral c794ff
kneutralSelection c794ff
kpositive 54afff
kpositiveSelection 54afff
text c6d0f5
subtext1 a5adce
subtext0 7a7f9e
overlay2 737994
overlay1 585b70
overlay0 45475a
surface2 414559
surface1 363a50
surface0 303446
base 303446
mantle 292c3c
crust 232634
success a6d189
onSuccess 303446
successContainer 3b5e3b
onSuccessContainer b6f0b1
@@ -1,87 +1,87 @@
primary_paletteKeyColor ca9ee6 primary_paletteKeyColor 6674ac
secondary_paletteKeyColor 7d6799 secondary_paletteKeyColor 71768e
tertiary_paletteKeyColor f4b8e4 tertiary_paletteKeyColor 9a6593
neutral_paletteKeyColor 414559 neutral_paletteKeyColor 77767b
neutral_variant_paletteKeyColor 303446 neutral_variant_paletteKeyColor 757680
background 303446 background 131317
onBackground c6d0f5 onBackground e4e1e7
surface 414559 surface 131317
surfaceDim 414559 surfaceDim 131317
surfaceBright 55596f surfaceBright 39393d
surfaceContainerLowest 292c3c surfaceContainerLowest 0d0e12
surfaceContainerLow 363a50 surfaceContainerLow 1b1b1f
surfaceContainer 303446 surfaceContainer 1f1f23
surfaceContainerHigh 292c3c surfaceContainerHigh 292a2e
surfaceContainerHighest 232634 surfaceContainerHighest 343438
onSurface c6d0f5 onSurface e4e1e7
surfaceVariant 303446 surfaceVariant 45464f
onSurfaceVariant a5adce onSurfaceVariant c6c5d1
inverseSurface c6d0f5 inverseSurface e4e1e7
inverseOnSurface 414559 inverseOnSurface 303034
outline 737994 outline 8f909a
outlineVariant 51576d outlineVariant 45464f
shadow 000000 shadow 000000
scrim 000000 scrim 000000
surfaceTint 9c7eb6 surfaceTint b7c4ff
primary ca9ee6 primary b7c4ff
onPrimary 303446 onPrimary 1e2d60
primaryContainer 9c7eb6 primaryContainer 6674ac
onPrimaryContainer ffffff onPrimaryContainer ffffff
inversePrimary 6c4f94 inversePrimary 4e5c92
secondary 7d6799 secondary c1c5e0
onSecondary ffffff onSecondary 2a2f44
secondaryContainer 544874 secondaryContainer 41465c
onSecondaryContainer cbbae8 onSecondaryContainer afb4ce
tertiary f4b8e4 tertiary f1b3e6
onTertiary 4e1e44 onTertiary 4c1f49
tertiaryContainer bb7da9 tertiaryContainer b67fae
onTertiaryContainer 000000 onTertiaryContainer 000000
error e78284 error ffb4ab
onError 4a0019 onError 690005
errorContainer 8c2643 errorContainer 93000a
onErrorContainer ffb3c6 onErrorContainer ffdad6
primaryFixed e8d4ff primaryFixed dce1ff
primaryFixedDim ca9ee6 primaryFixedDim b7c4ff
onPrimaryFixed 2a1040 onPrimaryFixed 05164b
onPrimaryFixedVariant 544874 onPrimaryFixedVariant 364478
secondaryFixed e2d4ff secondaryFixed dde1fd
secondaryFixedDim 7d6799 secondaryFixedDim c1c5e0
onSecondaryFixed 1a0a28 onSecondaryFixed 151b2e
onSecondaryFixedVariant 3a2850 onSecondaryFixedVariant 41465c
tertiaryFixed ffd7f5 tertiaryFixed ffd7f5
tertiaryFixedDim f4b8e4 tertiaryFixedDim f1b3e6
onTertiaryFixed 330832 onTertiaryFixed 330832
onTertiaryFixedVariant 653661 onTertiaryFixedVariant 653661
term0 414559 term0 353434
term1 e78284 term1 9a7bff
term2 a6d189 term2 44def5
term3 e5c890 term3 ffdcf2
term4 8caaee term4 92acd6
term5 f4b8e4 term5 a9a2ed
term6 81c8be term6 9dceff
term7 c6d0f5 term7 e8d3de
term8 51576d term8 ac9fa9
term9 e78284 term9 b299ff
term10 a6d189 term10 89ecff
term11 e5c890 term11 fff0f6
term12 8caaee term12 b1c2db
term13 f4b8e4 term13 c1b7f7
term14 81c8be term14 bae0ff
term15 a5adce term15 ffffff
rosewater f2d5cf rosewater f5eff9
flamingo eebebe flamingo e5def4
pink f4b8e4 pink dcd9ff
mauve ca9ee6 mauve b5bbff
red e78284 red b5a9ff
maroon ea999c maroon c1b7ef
peach ef9f76 peach e0c2f9
yellow e5c890 yellow ffecf3
green a6d189 green c8e3ff
teal 81c8be teal cee1ff
sky 99d1db sky cadcff
sapphire 85c1dc sapphire aec7ff
blue 8caaee blue a6baff
lavender babbf1 lavender bfcaff
klink 6685d1 klink 6685d1
klinkSelection 6585d1 klinkSelection 6585d1
kvisited 7276dd kvisited 7276dd
@@ -92,19 +92,19 @@ kneutral c794ff
kneutralSelection c794ff kneutralSelection c794ff
kpositive 54afff kpositive 54afff
kpositiveSelection 54afff kpositiveSelection 54afff
text c6d0f5 text e4e1e7
subtext1 a5adce subtext1 c6c5d1
subtext0 7a7f9e subtext0 8f909a
overlay2 737994 overlay2 7d7d86
overlay1 585b70 overlay1 6a6a72
overlay0 45475a overlay0 585960
surface2 414559 surface2 48484e
surface1 363a50 surface1 37373d
surface0 303446 surface0 25252a
base 303446 base 131317
mantle 292c3c mantle 131317
crust 232634 crust 121216
success a6d189 success B5CCBA
onSuccess 303446 onSuccess 213528
successContainer 3b5e3b successContainer 374B3E
onSuccessContainer b6f0b1 onSuccessContainer D1E9D6
@@ -1,110 +0,0 @@
primary_paletteKeyColor ca9ee6
secondary_paletteKeyColor 7d6799
tertiary_paletteKeyColor f4b8e4
neutral_paletteKeyColor 414559
neutral_variant_paletteKeyColor 303446
background 303446
onBackground c6d0f5
surface 414559
surfaceDim 414559
surfaceBright 55596f
surfaceContainerLowest 292c3c
surfaceContainerLow 363a50
surfaceContainer 303446
surfaceContainerHigh 292c3c
surfaceContainerHighest 232634
onSurface c6d0f5
surfaceVariant 303446
onSurfaceVariant a5adce
inverseSurface c6d0f5
inverseOnSurface 414559
outline 737994
outlineVariant 51576d
shadow 000000
scrim 000000
surfaceTint b5949a
primary eebebe
onPrimary 303446
primaryContainer b5949a
onPrimaryContainer ffffff
inversePrimary 6c4f94
secondary 9d6b80
onSecondary ffffff
secondaryContainer 544874
onSecondaryContainer cbbae8
tertiary f4b8e4
onTertiary 4e1e44
tertiaryContainer bb7da9
onTertiaryContainer 000000
error e78284
onError 4a0019
errorContainer 8c2643
onErrorContainer ffb3c6
primaryFixed e8d4ff
primaryFixedDim ca9ee6
onPrimaryFixed 2a1040
onPrimaryFixedVariant 544874
secondaryFixed e2d4ff
secondaryFixedDim 7d6799
onSecondaryFixed 1a0a28
onSecondaryFixedVariant 3a2850
tertiaryFixed ffd7f5
tertiaryFixedDim f4b8e4
onTertiaryFixed 330832
onTertiaryFixedVariant 653661
term0 414559
term1 e78284
term2 a6d189
term3 e5c890
term4 8caaee
term5 f4b8e4
term6 81c8be
term7 c6d0f5
term8 51576d
term9 e78284
term10 a6d189
term11 e5c890
term12 8caaee
term13 f4b8e4
term14 81c8be
term15 a5adce
rosewater f2d5cf
flamingo eebebe
pink f4b8e4
mauve ca9ee6
red e78284
maroon ea999c
peach ef9f76
yellow e5c890
green a6d189
teal 81c8be
sky 99d1db
sapphire 85c1dc
blue 8caaee
lavender babbf1
klink 6685d1
klinkSelection 6585d1
kvisited 7276dd
kvisitedSelection 7276dd
knegative 8e70ff
knegativeSelection 8e70ff
kneutral c794ff
kneutralSelection c794ff
kpositive 54afff
kpositiveSelection 54afff
text c6d0f5
subtext1 a5adce
subtext0 7a7f9e
overlay2 737994
overlay1 585b70
overlay0 45475a
surface2 414559
surface1 363a50
surface0 303446
base 303446
mantle 292c3c
crust 232634
success a6d189
onSuccess 303446
successContainer 3b5e3b
onSuccessContainer b6f0b1
@@ -1,110 +0,0 @@
primary_paletteKeyColor ca9ee6
secondary_paletteKeyColor 7d6799
tertiary_paletteKeyColor f4b8e4
neutral_paletteKeyColor 414559
neutral_variant_paletteKeyColor 303446
background 303446
onBackground c6d0f5
surface 414559
surfaceDim 414559
surfaceBright 55596f
surfaceContainerLowest 292c3c
surfaceContainerLow 363a50
surfaceContainer 303446
surfaceContainerHigh 292c3c
surfaceContainerHighest 232634
onSurface c6d0f5
surfaceVariant 303446
onSurfaceVariant a5adce
inverseSurface c6d0f5
inverseOnSurface 414559
outline 737994
outlineVariant 51576d
shadow 000000
scrim 000000
surfaceTint 83a275
primary a6d189
onPrimary 303446
primaryContainer 83a275
onPrimaryContainer ffffff
inversePrimary 6c4f94
secondary 648e5e
onSecondary ffffff
secondaryContainer 544874
onSecondaryContainer cbbae8
tertiary f4b8e4
onTertiary 4e1e44
tertiaryContainer bb7da9
onTertiaryContainer 000000
error e78284
onError 4a0019
errorContainer 8c2643
onErrorContainer ffb3c6
primaryFixed e8d4ff
primaryFixedDim ca9ee6
onPrimaryFixed 2a1040
onPrimaryFixedVariant 544874
secondaryFixed e2d4ff
secondaryFixedDim 7d6799
onSecondaryFixed 1a0a28
onSecondaryFixedVariant 3a2850
tertiaryFixed ffd7f5
tertiaryFixedDim f4b8e4
onTertiaryFixed 330832
onTertiaryFixedVariant 653661
term0 414559
term1 e78284
term2 a6d189
term3 e5c890
term4 8caaee
term5 f4b8e4
term6 81c8be
term7 c6d0f5
term8 51576d
term9 e78284
term10 a6d189
term11 e5c890
term12 8caaee
term13 f4b8e4
term14 81c8be
term15 a5adce
rosewater f2d5cf
flamingo eebebe
pink f4b8e4
mauve ca9ee6
red e78284
maroon ea999c
peach ef9f76
yellow e5c890
green a6d189
teal 81c8be
sky 99d1db
sapphire 85c1dc
blue 8caaee
lavender babbf1
klink 6685d1
klinkSelection 6585d1
kvisited 7276dd
kvisitedSelection 7276dd
knegative 8e70ff
knegativeSelection 8e70ff
kneutral c794ff
kneutralSelection c794ff
kpositive 54afff
kpositiveSelection 54afff
text c6d0f5
subtext1 a5adce
subtext0 7a7f9e
overlay2 737994
overlay1 585b70
overlay0 45475a
surface2 414559
surface1 363a50
surface0 303446
base 303446
mantle 292c3c
crust 232634
success a6d189
onSuccess 303446
successContainer 3b5e3b
onSuccessContainer b6f0b1
@@ -1,110 +0,0 @@
primary_paletteKeyColor ca9ee6
secondary_paletteKeyColor 7d6799
tertiary_paletteKeyColor f4b8e4
neutral_paletteKeyColor 414559
neutral_variant_paletteKeyColor 303446
background 303446
onBackground c6d0f5
surface 414559
surfaceDim 414559
surfaceBright 55596f
surfaceContainerLowest 292c3c
surfaceContainerLow 363a50
surfaceContainer 303446
surfaceContainerHigh 292c3c
surfaceContainerHighest 232634
onSurface c6d0f5
surfaceVariant 303446
onSurfaceVariant a5adce
inverseSurface c6d0f5
inverseOnSurface 414559
outline 737994
outlineVariant 51576d
shadow 000000
scrim 000000
surfaceTint 9192be
primary babbf1
onPrimary 303446
primaryContainer 9192be
onPrimaryContainer ffffff
inversePrimary 6c4f94
secondary 7175a1
onSecondary ffffff
secondaryContainer 544874
onSecondaryContainer cbbae8
tertiary f4b8e4
onTertiary 4e1e44
tertiaryContainer bb7da9
onTertiaryContainer 000000
error e78284
onError 4a0019
errorContainer 8c2643
onErrorContainer ffb3c6
primaryFixed e8d4ff
primaryFixedDim ca9ee6
onPrimaryFixed 2a1040
onPrimaryFixedVariant 544874
secondaryFixed e2d4ff
secondaryFixedDim 7d6799
onSecondaryFixed 1a0a28
onSecondaryFixedVariant 3a2850
tertiaryFixed ffd7f5
tertiaryFixedDim f4b8e4
onTertiaryFixed 330832
onTertiaryFixedVariant 653661
term0 414559
term1 e78284
term2 a6d189
term3 e5c890
term4 8caaee
term5 f4b8e4
term6 81c8be
term7 c6d0f5
term8 51576d
term9 e78284
term10 a6d189
term11 e5c890
term12 8caaee
term13 f4b8e4
term14 81c8be
term15 a5adce
rosewater f2d5cf
flamingo eebebe
pink f4b8e4
mauve ca9ee6
red e78284
maroon ea999c
peach ef9f76
yellow e5c890
green a6d189
teal 81c8be
sky 99d1db
sapphire 85c1dc
blue 8caaee
lavender babbf1
klink 6685d1
klinkSelection 6585d1
kvisited 7276dd
kvisitedSelection 7276dd
knegative 8e70ff
knegativeSelection 8e70ff
kneutral c794ff
kneutralSelection c794ff
kpositive 54afff
kpositiveSelection 54afff
text c6d0f5
subtext1 a5adce
subtext0 7a7f9e
overlay2 737994
overlay1 585b70
overlay0 45475a
surface2 414559
surface1 363a50
surface0 303446
base 303446
mantle 292c3c
crust 232634
success a6d189
onSuccess 303446
successContainer 3b5e3b
onSuccessContainer b6f0b1
@@ -1,110 +0,0 @@
primary_paletteKeyColor ca9ee6
secondary_paletteKeyColor 7d6799
tertiary_paletteKeyColor f4b8e4
neutral_paletteKeyColor 414559
neutral_variant_paletteKeyColor 303446
background 303446
onBackground c6d0f5
surface 414559
surfaceDim 414559
surfaceBright 55596f
surfaceContainerLowest 292c3c
surfaceContainerLow 363a50
surfaceContainer 303446
surfaceContainerHigh 292c3c
surfaceContainerHighest 232634
onSurface c6d0f5
surfaceVariant 303446
onSurfaceVariant a5adce
inverseSurface c6d0f5
inverseOnSurface 414559
outline 737994
outlineVariant 51576d
shadow 000000
scrim 000000
surfaceTint b27a83
primary ea999c
onPrimary 303446
primaryContainer b27a83
onPrimaryContainer ffffff
inversePrimary 6c4f94
secondary 92626f
onSecondary ffffff
secondaryContainer 544874
onSecondaryContainer cbbae8
tertiary f4b8e4
onTertiary 4e1e44
tertiaryContainer bb7da9
onTertiaryContainer 000000
error e78284
onError 4a0019
errorContainer 8c2643
onErrorContainer ffb3c6
primaryFixed e8d4ff
primaryFixedDim ca9ee6
onPrimaryFixed 2a1040
onPrimaryFixedVariant 544874
secondaryFixed e2d4ff
secondaryFixedDim 7d6799
onSecondaryFixed 1a0a28
onSecondaryFixedVariant 3a2850
tertiaryFixed ffd7f5
tertiaryFixedDim f4b8e4
onTertiaryFixed 330832
onTertiaryFixedVariant 653661
term0 414559
term1 e78284
term2 a6d189
term3 e5c890
term4 8caaee
term5 f4b8e4
term6 81c8be
term7 c6d0f5
term8 51576d
term9 e78284
term10 a6d189
term11 e5c890
term12 8caaee
term13 f4b8e4
term14 81c8be
term15 a5adce
rosewater f2d5cf
flamingo eebebe
pink f4b8e4
mauve ca9ee6
red e78284
maroon ea999c
peach ef9f76
yellow e5c890
green a6d189
teal 81c8be
sky 99d1db
sapphire 85c1dc
blue 8caaee
lavender babbf1
klink 6685d1
klinkSelection 6585d1
kvisited 7276dd
kvisitedSelection 7276dd
knegative 8e70ff
knegativeSelection 8e70ff
kneutral c794ff
kneutralSelection c794ff
kpositive 54afff
kpositiveSelection 54afff
text c6d0f5
subtext1 a5adce
subtext0 7a7f9e
overlay2 737994
overlay1 585b70
overlay0 45475a
surface2 414559
surface1 363a50
surface0 303446
base 303446
mantle 292c3c
crust 232634
success a6d189
onSuccess 303446
successContainer 3b5e3b
onSuccessContainer b6f0b1
@@ -1,110 +0,0 @@
primary_paletteKeyColor ca9ee6
secondary_paletteKeyColor 7d6799
tertiary_paletteKeyColor f4b8e4
neutral_paletteKeyColor 414559
neutral_variant_paletteKeyColor 303446
background 303446
onBackground c6d0f5
surface 414559
surfaceDim 414559
surfaceBright 55596f
surfaceContainerLowest 292c3c
surfaceContainerLow 363a50
surfaceContainer 303446
surfaceContainerHigh 292c3c
surfaceContainerHighest 232634
onSurface c6d0f5
surfaceVariant 303446
onSurfaceVariant a5adce
inverseSurface c6d0f5
inverseOnSurface 414559
outline 737994
outlineVariant 51576d
shadow 000000
scrim 000000
surfaceTint 9c7eb6
primary ca9ee6
onPrimary 303446
primaryContainer 9c7eb6
onPrimaryContainer ffffff
inversePrimary 6c4f94
secondary 7d6799
onSecondary ffffff
secondaryContainer 544874
onSecondaryContainer cbbae8
tertiary f4b8e4
onTertiary 4e1e44
tertiaryContainer bb7da9
onTertiaryContainer 000000
error e78284
onError 4a0019
errorContainer 8c2643
onErrorContainer ffb3c6
primaryFixed e8d4ff
primaryFixedDim ca9ee6
onPrimaryFixed 2a1040
onPrimaryFixedVariant 544874
secondaryFixed e2d4ff
secondaryFixedDim 7d6799
onSecondaryFixed 1a0a28
onSecondaryFixedVariant 3a2850
tertiaryFixed ffd7f5
tertiaryFixedDim f4b8e4
onTertiaryFixed 330832
onTertiaryFixedVariant 653661
term0 414559
term1 e78284
term2 a6d189
term3 e5c890
term4 8caaee
term5 f4b8e4
term6 81c8be
term7 c6d0f5
term8 51576d
term9 e78284
term10 a6d189
term11 e5c890
term12 8caaee
term13 f4b8e4
term14 81c8be
term15 a5adce
rosewater f2d5cf
flamingo eebebe
pink f4b8e4
mauve ca9ee6
red e78284
maroon ea999c
peach ef9f76
yellow e5c890
green a6d189
teal 81c8be
sky 99d1db
sapphire 85c1dc
blue 8caaee
lavender babbf1
klink 6685d1
klinkSelection 6585d1
kvisited 7276dd
kvisitedSelection 7276dd
knegative 8e70ff
knegativeSelection 8e70ff
kneutral c794ff
kneutralSelection c794ff
kpositive 54afff
kpositiveSelection 54afff
text c6d0f5
subtext1 a5adce
subtext0 7a7f9e
overlay2 737994
overlay1 585b70
overlay0 45475a
surface2 414559
surface1 363a50
surface0 303446
base 303446
mantle 292c3c
crust 232634
success a6d189
onSuccess 303446
successContainer 3b5e3b
onSuccessContainer b6f0b1

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