10 Commits
Author SHA1 Message Date
zach 7cc673430c separate dashboard and settings changes to new branch
C++ / fmt (pull_request) Failing after 5s
C++ / build (pull_request) Failing after 9s
C++ / clang-tidy (pull_request) Failing after 10s
JS/TS / fmt (pull_request) Successful in 5s
JS/TS / lint (pull_request) Successful in 7s
Python / static (pull_request) Successful in 26s
Rust / build (pull_request) Successful in 46s
Python / verify (pull_request) Successful in 1m38s
Rust / fmt (pull_request) Successful in 28s
Rust / clippy (pull_request) Successful in 49s
2026-09-30 14:48:38 +02:00
zach 08bb221699 feat: config object migration path and add new options to settings 2026-09-30 14:40:22 +02:00
zach 60293abe61 fix: define declared method + insert new bar entries to config file list + rename Hyprsunset -> Nightlight 2026-09-30 14:33:58 +02:00
zach a22022e969 fix: changesMade bool now binding for robustness 2026-09-30 14:26:59 +02:00
zach 62e19b5261 fix: show bar on hover 2026-09-30 14:26:59 +02:00
zach d76aaf093b fix: normalize zoom slider to linear scale 2026-09-30 14:26:59 +02:00
zach 4ebb4ce859 feat: wallpaper cropper sdf shader rectangle, better confirmation controls + hide passive tray icons 2026-09-30 14:26:59 +02:00
zach be3c938c39 feat: progress on changing interactions to mousearea again 2026-09-30 14:26:59 +02:00
zach f6fa2f0230 fix: remove clippingrectangle in favor of passing radius to sub-items 2026-09-30 14:26:59 +02:00
zach 4a021d81ac chore: formatter 2026-09-30 14:26:59 +02:00
35 changed files with 950 additions and 582 deletions
+2 -5
View File
@@ -17,12 +17,9 @@ ScrollBar {
property bool shouldBeActive property bool shouldBeActive
readonly property real travelScale: root.rawTravel > 0 ? root.effectiveTravel / root.rawTravel : 0 readonly property real travelScale: root.rawTravel > 0 ? root.effectiveTravel / root.rawTravel : 0
property bool shouldBeActive
property bool inverted
enabled: !ShellState.forActive().isDrawing enabled: !ShellState.forActive().isDrawing
implicitWidth: size === 1 ? 0 : isHorizontal ? 0 : Tokens.padding.extraSmall * 2 implicitHeight: Tokens.padding.extraSmall * 2
implicitHeight: size === 1 ? 0 : isHorizontal ? Tokens.padding.extraSmall * 2 : 0 implicitWidth: Tokens.padding.extraSmall * 2
contentItem: Item { contentItem: Item {
} }
+1 -1
View File
@@ -1,7 +1,7 @@
import Quickshell import Quickshell
import QtQuick import QtQuick
import qs.Components
import ZShell.Config import ZShell.Config
import qs.Components
import qs.Helpers import qs.Helpers
import qs.Modules.Bar.Popouts import qs.Modules.Bar.Popouts
+213 -207
View File
@@ -1,309 +1,315 @@
import QtQuick
import QtQuick.Controls
import Quickshell import Quickshell
import QtQuick
import ZShell.Config import ZShell.Config
import ZShell.Internal
import qs.Components import qs.Components
import qs.Modules.Bar as Bar import qs.Helpers
import qs.Modules.Bar.Popouts as BarPopouts import qs.Modules.Bar.Popouts
CustomMouseArea { CustomMouseArea {
id: root id: root
required property ShellScreen screen required property Item bar
required property BarPopouts.Wrapper popouts
required property ScreenState screenState
required property Panels panels
required property Bar.BarLoader bar
required property real borderThickness required property real borderThickness
required property bool fullscreen required property ScreenState screenState
required property Drawing drawing
required property EdgeGeometry geometry
required property Panels panels
required property Wrapper popouts
required property ShellScreen screen
property point dragStart
property bool dashboardShortcutActive property bool dashboardShortcutActive
property bool osdShortcutActive property bool osdShortcutActive
property bool singleGestureTriggered: false
property bool utilitiesShortcutActive property bool utilitiesShortcutActive
property bool setInitialPoint: false
property point dragStart
function withinPanelHeight(panel: Item, x: real, y: real): bool { function inBottomPanel(panel: Item, x: real, y: real): bool {
const panelY = root.borderThickness + panel.y; const panelHeight = panel.height * (1 - (panel.offsetScale ?? 0));
return y >= panelY - Config.border.rounding && y <= panelY + panel.height + Config.border.rounding; return y > root.height - (panelHeight + geometry.insetBottom(borderThickness)) && withinPanelWidth(panel, x, y);
}
function withinPanelWidth(panel: Item, x: real, y: real): bool {
const panelX = bar.implicitWidth + panel.x;
return x >= panelX - Config.border.rounding && x <= panelX + panel.width + Config.border.rounding;
} }
function inLeftPanel(panel: Item, x: real, y: real): bool { function inLeftPanel(panel: Item, x: real, y: real): bool {
return x < bar.implicitWidth + panel.x + panel.width && withinPanelHeight(panel, x, y); return x < panel.x + panel.width + geometry.insetLeft(borderThickness) && withinPanelHeight(panel, x, y);
}
function inPopoutArea(x: real, y: real): bool {
const panel = panels.popoutsWrapper;
if (!geometry.horizontal)
return inLeftPanel(panel, x, y);
return withinPanelWidth(panel, x, y) && withinPanelHeight(panel, x, y);
} }
function inRightPanel(panel: Item, x: real, y: real): bool { function inRightPanel(panel: Item, x: real, y: real): bool {
return x > Math.min(width - Config.border.minThickness, bar.implicitWidth + panel.x) && withinPanelHeight(panel, x, y); return x > panel.x + geometry.insetLeft(borderThickness) && withinPanelHeight(panel, x, y);
} }
function inTopPanel(panel: Item, x: real, y: real): bool { function inTopPanel(panel: Item, x: real, y: real): bool {
const panelHeight = panel.height * (1 - (panel.offsetScale ?? 0)); // qmllint disable missing-property const panelHeight = panel.height * (1 - (panel.offsetScale ?? 0));
return y < Math.max(Config.border.minThickness, Config.border.thickness + panelHeight) && withinPanelWidth(panel, x, y); return y < geometry.insetTop(borderThickness) + panelHeight && withinPanelWidth(panel, x, y);
} }
function inBottomPanel(panel: Item, x: real, y: real, isCorner = false): bool { function withinPanelHeight(panel: Item, x: real, y: real): bool {
const panelHeight = panel.height * (1 - (panel.offsetScale ?? 0)); // qmllint disable missing-property const panelY = panel.y + geometry.insetTop(borderThickness);
return y > height - Math.max(Config.border.minThickness, Config.border.thickness + panelHeight) - (isCorner ? Config.border.rounding : 0) && withinPanelWidth(panel, x, y); return y >= panelY && y <= panelY + panel.height;
} }
function onWheel(event: WheelEvent): void { function withinPanelWidth(panel: Item, x: real, y: real): bool {
if (fullscreen) const panelX = panel.x + geometry.insetLeft(borderThickness);
return; return x >= panelX && x <= panelX + panel.width;
if (event.x < bar.implicitWidth) {
bar.handleWheel(event.y, event.angleDelta);
}
} }
anchors.fill: parent anchors.fill: parent
acceptedButtons: fullscreen ? Qt.NoButton : Qt.AllButtons acceptedButtons: bar.fullscreen ? Qt.NoButton : Qt.AllButtons
hoverEnabled: true hoverEnabled: true
onPressed: event => dragStart = Qt.point(event.x, event.y) onPressed: event => {
dragStart = Qt.point(event.x, event.y);
if (screenState.isDrawing) {
if (event.button & Qt.RightButton) {
drawing.content.clear();
return;
}
if (!inLeftPanel(panels.drawing, mouseX, mouseY))
drawing.content.beginStroke(event.x, event.y);
}
}
onPressedChanged: {
if (!pressed) {
if (screenState.isDrawing) {
setInitialPoint = false;
drawing.content.endStroke();
if (!inLeftPanel(panels.drawing, mouseX, mouseY))
drawing.content.showHover(mouseX, mouseY);
}
} else {
if (screenState.isDrawing) {
panels.drawing.collapse();
}
}
}
onContainsMouseChanged: { onContainsMouseChanged: {
if (!containsMouse) { if (!containsMouse) {
// Only hide if not activated by shortcut
if (!osdShortcutActive) { if (!osdShortcutActive) {
screenState.osd = false; screenState.osd = false;
root.panels.osd.hovered = false; panels.osd.hovered = false;
} }
if (!dashboardShortcutActive) if (!popouts.currentName.startsWith("traymenu") || Config.bar.tray.showOnHover) {
screenState.dashboard = false;
if (!utilitiesShortcutActive)
screenState.utilities = false;
if (!popouts.currentName.startsWith("traymenu") || ((popouts.current as StackView)?.depth ?? 0) <= 1) {
popouts.hasCurrent = false; popouts.hasCurrent = false;
bar.closeTray();
} }
if (Config.bar.showOnHover) if (Config.bar.autoHide || bar.fullscreen)
bar.isHovered = false; bar.isHovered = false;
if (Config.sidebar.showOnHover) if (screenState.dashboard && Config.dashboard.showOnHover)
screenState.sidebar = false; screenState.dashboard = false;
} }
} }
onPositionChanged: event => { onPositionChanged: event => {
if (popouts.isDetached)
return;
const x = event.x; const x = event.x;
const y = event.y; const y = event.y;
const dragX = x - dragStart.x; const dragX = x - dragStart.x;
const dragY = y - dragStart.y; const dragY = y - dragStart.y;
if (fullscreen) { if (screenState.isDrawing) {
root.panels.osd.hovered = inRightPanel(panels.osdWrapper, x, y); if (pressed) {
drawing.content.appendPoint(x, y);
} else {
if (inLeftPanel(panels.drawing, x, y)) {
if (!(pressedButtons & Qt.LeftButton)) {
panels.drawing.expand();
drawing.content.hideHover();
Cursor.clearOverride();
}
} else {
Cursor.setOverride(Qt.BlankCursor);
drawing.content.showHover(x, y);
}
}
if (drawing.content.hoverVisible)
drawing.content.showHover(x, y);
return; return;
} }
// Show bar in non-exclusive mode on hover if (root.bar.fullscreen && !root.bar.isHovered)
if (!screenState.bar && Config.bar.showOnHover && x < bar.clampedWidth) unhideTimer.start();
bar.isHovered = true;
// Show/hide bar on drag if (pressed && dragStart.y >= screen.height - Config.bar.border && dragStart.x > screen.width / 5 && dragY < -Config.launcher.dragThreshold)
if (pressed && dragStart.x < bar.clampedWidth) { screenState.launcher = true;
if (dragX > Config.bar.dragThreshold)
screenState.bar = true;
else if (dragX < -Config.bar.dragThreshold)
screenState.bar = false;
}
if (panels.sidebar.offsetScale === 1) { const showDashboard = Config.dashboard.showOnHover && inTopPanel(panels.dashboardWrapper, x, y);
// Show osd on hover
const showOsd = inRightPanel(panels.osdWrapper, x, y);
// Always update visibility based on hover if not in shortcut mode
if (!osdShortcutActive) {
screenState.osd = showOsd;
root.panels.osd.hovered = showOsd;
} else if (showOsd) {
// If hovering over OSD area while in shortcut mode, transition to hover control
osdShortcutActive = false;
root.panels.osd.hovered = true;
}
const showSidebar = pressed && dragStart.x > Math.min(width - Config.border.minThickness, bar.implicitWidth + panels.sidebar.x);
// Show sidebar on hover (top-right corner, bounded by notification panel height)
if (Config.sidebar.showOnHover) {
const sidebarTriggerY = Math.max(Config.sidebar.minHoverThreshold, panels.notifications.y + panels.notifications.height + borderThickness);
const showSidebarHover = x > Math.min(width - Config.border.minThickness, bar.implicitWidth + panels.sidebar.x) && y <= sidebarTriggerY;
if (showSidebarHover && !screenState.sidebar)
screenState.sidebar = true;
}
// Show/hide session on drag
if (pressed && inRightPanel(panels.sessionWrapper, dragStart.x, dragStart.y) && withinPanelHeight(panels.sessionWrapper, x, y)) {
if (dragX < -Config.session.dragThreshold)
screenState.session = true;
else if (dragX > Config.session.dragThreshold)
screenState.session = false;
// Show sidebar on drag if in session area and session is nearly fully visible
if (showSidebar && panels.session.offsetScale <= 0 && dragX < -Config.sidebar.dragThreshold)
screenState.sidebar = true;
} else if (showSidebar && dragX < -Config.sidebar.dragThreshold) {
// Show sidebar on drag if not in session area
screenState.sidebar = true;
}
} else {
const outOfSidebar = x < width - panels.sidebar.width * (1 - panels.sidebar.offsetScale);
// Show osd on hover
const showOsd = outOfSidebar && inRightPanel(panels.osdWrapper, x, y);
// Always update visibility based on hover if not in shortcut mode
if (!osdShortcutActive) {
screenState.osd = showOsd;
root.panels.osd.hovered = showOsd;
} else if (showOsd) {
// If hovering over OSD area while in shortcut mode, transition to hover control
osdShortcutActive = false;
root.panels.osd.hovered = true;
}
// Show/hide session on drag
if (pressed && outOfSidebar && inRightPanel(panels.sessionWrapper, dragStart.x, dragStart.y) && withinPanelHeight(panels.sessionWrapper, x, y)) {
if (dragX < -Config.session.dragThreshold)
screenState.session = true;
else if (dragX > Config.session.dragThreshold)
screenState.session = false;
}
// Show/hide sidebar on hover
if (Config.sidebar.showOnHover && !pressed) {
const sidebarTriggerY = Math.max(Config.sidebar.minHoverThreshold, panels.notifications.y + panels.notifications.height + borderThickness);
const showSidebarHover = x > Math.min(width - Config.border.minThickness, bar.implicitWidth + panels.sidebar.x) && y <= sidebarTriggerY;
if (showSidebarHover && !screenState.sidebar) {
screenState.sidebar = true;
} else {
const inSidebarArea = inRightPanel(panels.sidebar, x, y) || inRightPanel(panels.sessionWrapper, x, y);
if (!inSidebarArea)
screenState.sidebar = false;
}
}
// Hide sidebar on drag
if (pressed && inRightPanel(panels.sidebar, dragStart.x, 0) && dragX > Config.sidebar.dragThreshold)
screenState.sidebar = false;
}
// Show launcher on hover, or show/hide on drag if hover is disabled
if (Config.launcher.showOnHover) {
if (!screenState.launcher && inBottomPanel(panels.launcher, x, y))
screenState.launcher = true;
} else if (pressed && inBottomPanel(panels.launcher, dragStart.x, dragStart.y) && withinPanelWidth(panels.launcher, x, y)) {
if (dragY < -Config.launcher.dragThreshold)
screenState.launcher = true;
else if (dragY > Config.launcher.dragThreshold)
screenState.launcher = false;
}
// Show dashboard on hover
const showDashboard = Config.dashboard.showOnHover && inTopPanel(panels.dashboard, x, y);
// Always update visibility based on hover if not in shortcut mode
if (!dashboardShortcutActive) { if (!dashboardShortcutActive) {
screenState.dashboard = showDashboard; screenState.dashboard = showDashboard;
} else if (showDashboard) { } else if (showDashboard) {
// If hovering over dashboard area while in shortcut mode, transition to hover control
dashboardShortcutActive = false; dashboardShortcutActive = false;
} }
// Show/hide dashboard on drag (for touchscreen devices) if (pressed && geometry.insetTop(borderThickness) > dragStart.y && withinPanelWidth(panels.dashboardWrapper, x, y)) {
if (pressed && inTopPanel(panels.dashboard, dragStart.x, dragStart.y) && withinPanelWidth(panels.dashboard, x, y)) {
if (dragY > Config.dashboard.dragThreshold) if (dragY > Config.dashboard.dragThreshold)
screenState.dashboard = true; screenState.dashboard = true;
else if (dragY < -Config.dashboard.dragThreshold) else if (dragY < -Config.dashboard.dragThreshold)
screenState.dashboard = false; screenState.dashboard = false;
} }
// Show utilities on hover if (pressed && geometry.insetTop(borderThickness) > dragStart.y && (Config.dashboard.showOnHover || !inTopPanel(panels.dashboardWrapper, dragStart.x, dragStart.y))) {
const showUtilities = inBottomPanel(panels.utilities, x, y, true); if (dragY > 20) {
screenState.settings = true;
// Always update visibility based on hover if not in shortcut mode } else if (dragY < -20) {
if (!utilitiesShortcutActive) { screenState.settings = false;
screenState.utilities = showUtilities; }
} else if (showUtilities) {
// If hovering over utilities area while in shortcut mode, transition to hover control
utilitiesShortcutActive = false;
} }
// Show popouts on hover if (pressed && dragStart.y > screen.height - geometry.insetBottom(borderThickness) && dragStart.x < screen.width / 5 && dragY < -50)
if (x < bar.implicitWidth) { screenState.clipboard = true;
bar.checkPopout(y);
} else if ((!popouts.currentName.startsWith("traymenu") || ((popouts.current as StackView)?.depth ?? 0) <= 1) && !inLeftPanel(panels.popoutsWrapper, x, y)) { if (pressed && !Config.dock.hoverToReveal && dragStart.y > screen.height - geometry.insetTop(borderThickness) && dragStart.x > screen.width / 5 && !screenState.launcher)
popouts.hasCurrent = false; if (dragY < -10)
bar.closeTray(); screenState.dock = true;
if (pressed && dragStart.x > screen.width - Config.bar.border && dragStart.y < (screen.height / 2) && dragX < -Config.sidebar.dragThreshold)
screenState.sidebar = true;
if (pressed && dragStart.x >= screen.width - Config.bar.border && dragStart.y > (screen.height / 2) && dragX < -20)
Hypr.dispatch(`hl.dsp.focus({ workspace = 'r+1', on_current_monitor = true })`);
if (pressed && dragStart.x <= Config.bar.border && dragX > 20)
Hypr.dispatch(`hl.dsp.focus({ workspace = 'r-1', on_current_monitor = true })`);
if (!screenState.bar && Config.bar.autoHide && geometry.barContains(x, y, true))
bar.isHovered = true;
if (panels.sidebar.offsetScale === 1) {
const showOsd = inRightPanel(panels.osdWrapper, x, y);
if (showOsd) {
osdShortcutActive = false;
panels.osd.hovered = true;
}
} else {
const outOfSidebar = x < width - panels.sidebar.width * (1 - panels.sidebar.offsetScale);
const showOsd = outOfSidebar && inRightPanel(panels.osdWrapper, x, y);
if (!osdShortcutActive) {
screenState.osd = showOsd;
panels.osd.hovered = showOsd;
} else if (showOsd) {
osdShortcutActive = false;
panels.osd.hovered = true;
}
}
if (Config.dock.enable && Config.dock.hoverToReveal && !screenState.dock && !screenState.launcher && inBottomPanel(panels.dock, x, y))
screenState.dock = true;
if (geometry.barContains(x, y))
bar.checkPopout(geometry.axisPos(x, y));
}
Timer {
id: unhideTimer
interval: Config.bar.revealDelay
repeat: false
running: false
onTriggered: {
if (root.containsMouse)
root.bar.isHovered = true;
} }
} }
// Monitor individual visibility changes
Connections { Connections {
function onLauncherChanged() { function onDashboardChanged(): void {
// If launcher is hidden, clear shortcut flags for dashboard and OSD if (root.screenState.dashboard) {
const inDashboardArea = root.inTopPanel(root.panels.dashboardWrapper, root.mouseX, root.mouseY);
if (!inDashboardArea) {
root.dashboardShortcutActive = true;
}
root.screenState.launcher = false;
root.screenState.settings = false;
root.screenState.sidebar = false;
root.screenState.resources = false;
root.popouts.hasCurrent = false;
} else {
root.dashboardShortcutActive = false;
}
}
function onIsDrawingChanged(): void {
if (root.screenState.isDrawing) {
Cursor.setOverride(Qt.BlankCursor);
} else {
Cursor.clearOverride();
}
}
function onLauncherChanged(): void {
if (!root.screenState.launcher) { if (!root.screenState.launcher) {
root.dashboardShortcutActive = false; root.dashboardShortcutActive = false;
root.osdShortcutActive = false; root.osdShortcutActive = false;
root.utilitiesShortcutActive = false; root.utilitiesShortcutActive = false;
// Also hide dashboard and OSD if they're not being hovered const inOsdArea = root.inRightPanel(root.panels.osd, root.mouseX, root.mouseY);
const inDashboardArea = root.inTopPanel(root.panels.dashboard, root.mouseX, root.mouseY);
const inOsdArea = root.inRightPanel(root.panels.osdWrapper, root.mouseX, root.mouseY);
if (!inDashboardArea) {
root.screenState.dashboard = false;
}
if (!inOsdArea) { if (!inOsdArea) {
root.screenState.osd = false; root.screenState.osd = false;
root.panels.osd.hovered = false; root.panels.osd.hovered = false;
} }
} }
}
function onDashboardChanged() { if (root.screenState.launcher) {
if (root.screenState.dashboard) { root.screenState.dashboard = false;
// Dashboard became visible, immediately check if this should be shortcut mode root.screenState.dock = false;
const inDashboardArea = root.inTopPanel(root.panels.dashboard, root.mouseX, root.mouseY); root.screenState.settings = false;
if (!inDashboardArea) {
root.dashboardShortcutActive = true;
}
} else {
// Dashboard hidden, clear shortcut flag
root.dashboardShortcutActive = false;
} }
} }
function onOsdChanged() { function onOsdChanged(): void {
if (root.screenState.osd) { if (root.screenState.osd) {
// OSD became visible, immediately check if this should be shortcut mode const inOsdArea = root.inRightPanel(root.panels.osd, root.mouseX, root.mouseY);
const inOsdArea = root.inRightPanel(root.panels.osdWrapper, root.mouseX, root.mouseY);
if (!inOsdArea) { if (!inOsdArea) {
root.osdShortcutActive = true; root.osdShortcutActive = true;
} }
} else { } else {
// OSD hidden, clear shortcut flag
root.osdShortcutActive = false; root.osdShortcutActive = false;
} }
} }
function onUtilitiesChanged() { function onResourcesChanged(): void {
if (root.screenState.utilities) { if (root.screenState.resources) {
// Utilities became visible, immediately check if this should be shortcut mode if (root.popouts.currentName.startsWith("audio") || root.popouts.currentName.startsWith("updates"))
const inUtilitiesArea = root.inBottomPanel(root.panels.utilities, root.mouseX, root.mouseY); root.popouts.hasCurrent = false;
if (!inUtilitiesArea) {
root.utilitiesShortcutActive = true; root.screenState.settings = false;
} root.screenState.dashboard = false;
} else { }
// Utilities hidden, clear shortcut flag }
root.utilitiesShortcutActive = false;
function onSettingsChanged(): void {
if (root.screenState.settings) {
root.screenState.resources = false;
root.screenState.dashboard = false;
root.screenState.sidebar = false;
root.panels.popouts.hasCurrent = false;
root.screenState.launcher = false;
}
}
function onSidebarChanged(): void {
if (root.screenState.sidebar) {
if (root.panels.dashboardWrapper.x + root.panels.dashboardWrapper.width > root.panels.sidebar.x)
root.screenState.dashboard = false;
root.screenState.settings = false;
root.popouts.hasCurrent = false;
} }
} }
+5 -6
View File
@@ -37,12 +37,12 @@ Item {
readonly property alias resources: resources readonly property alias resources: resources
readonly property alias resourcesWrapper: resourcesWrapper readonly property alias resourcesWrapper: resourcesWrapper
required property ShellScreen screen required property ShellScreen screen
required property ScreenState screenState
readonly property alias settings: settings readonly property alias settings: settings
readonly property alias settingsWrapper: settingsWrapper 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
required property ScreenState screenState
anchors.bottomMargin: geometry.insetBottom(borderThickness) anchors.bottomMargin: geometry.insetBottom(borderThickness)
anchors.fill: parent anchors.fill: parent
@@ -84,8 +84,8 @@ Item {
anchors.right: parent.right anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
screen: root.screen screen: root.screen
sidebarOrSessionVisible: sidebar.visible
screenState: root.screenState screenState: root.screenState
sidebarOrSessionVisible: sidebar.visible
} }
} }
@@ -121,8 +121,8 @@ Item {
anchors.right: parent.right anchors.right: parent.right
anchors.top: parent.top anchors.top: parent.top
panels: root panels: root
sidebarPanel: sidebar
screenState: root.screenState screenState: root.screenState
sidebarPanel: sidebar
} }
Launcher.Wrapper { Launcher.Wrapper {
@@ -140,9 +140,8 @@ Item {
anchors.bottom: parent.bottom anchors.bottom: parent.bottom
anchors.right: parent.right anchors.right: parent.right
popouts: popouts
sidebar: sidebar
screenState: root.screenState screenState: root.screenState
sidebar: sidebar
} }
Item { Item {
@@ -150,8 +149,8 @@ Item {
property real offsetScale: dashboard.shouldBeActive ? 0 : 1 property real offsetScale: dashboard.shouldBeActive ? 0 : 1
anchors.top: parent.top
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top
clip: true clip: true
implicitHeight: dashboard.implicitHeight * (1 - offsetScale) implicitHeight: dashboard.implicitHeight * (1 - offsetScale)
implicitWidth: dashboard.implicitWidth implicitWidth: dashboard.implicitWidth
+1 -1
View File
@@ -375,7 +375,7 @@ CustomWindow {
} }
} }
Interactions { Interactions2 {
id: interactions id: interactions
anchors.fill: parent anchors.fill: parent
+5 -10
View File
@@ -13,21 +13,17 @@ import qs.Paths
Singleton { Singleton {
id: root id: root
readonly property M3Palette current: M3Palette { readonly property M3Palette current: M3Palette {}
}
property bool currentLight property bool currentLight
property string flavour property string flavour
readonly property bool light: showPreview ? previewLight : currentLight readonly property bool light: showPreview ? previewLight : currentLight
readonly property M3Palette palette: showPreview ? preview : current readonly property M3Palette palette: showPreview ? preview : current
readonly property M3Palette preview: M3Palette { readonly property M3Palette preview: M3Palette {}
}
property bool previewLight property bool previewLight
property string scheme property string scheme
property bool showPreview property bool showPreview
readonly property M3TPalette tPalette: M3TPalette { readonly property M3TPalette tPalette: M3TPalette {}
} readonly property Transparency transparency: Transparency {}
readonly property Transparency transparency: Transparency {
}
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 {
@@ -112,8 +108,7 @@ Singleton {
function setMode(mode: string): void { function setMode(mode: string): void {
Quickshell.execDetached(["zshell-cli", "scheme", "generate", "--mode", mode]); Quickshell.execDetached(["zshell-cli", "scheme", "generate", "--mode", mode]);
Config.general.color.mode = mode; Config.colors.mode = mode;
Config.save();
} }
function swapRG(c: color): color { function swapRG(c: color): color {
+6 -6
View File
@@ -11,14 +11,14 @@ import qs.Services
Singleton { Singleton {
id: root id: root
readonly property int darkEnd: Config.general.color.scheduleDarkEnd readonly property int darkEnd: Config.colors.scheduleDarkEnd
readonly property int darkStart: Config.general.color.scheduleDarkStart readonly property int darkStart: Config.colors.scheduleDarkStart
readonly property bool enabled: Config.general.color.scheduleDark && Config.general.color.schemeGeneration readonly property bool enabled: Config.colors.scheduleDark && Config.colors.schemeGen
function applyDarkMode() { function applyDarkMode() {
Quickshell.execDetached(["zshell-cli", "scheme", "generate", "--mode", "dark"]); Quickshell.execDetached(["zshell-cli", "scheme", "generate", "--mode", "dark"]);
Config.general.color.mode = "dark"; Config.colors.mode = "dark";
Quickshell.execDetached(["gsettings", "set", "org.gnome.desktop.interface", "color-scheme", "'prefer-dark'"]); Quickshell.execDetached(["gsettings", "set", "org.gnome.desktop.interface", "color-scheme", "'prefer-dark'"]);
@@ -30,13 +30,13 @@ Singleton {
function applyLightMode() { function applyLightMode() {
Quickshell.execDetached(["zshell-cli", "scheme", "generate", "--mode", "light"]); Quickshell.execDetached(["zshell-cli", "scheme", "generate", "--mode", "light"]);
Config.general.color.mode = "light"; Config.colors.mode = "light";
Quickshell.execDetached(["gsettings", "set", "org.gnome.desktop.interface", "color-scheme", "'prefer-light'"]); Quickshell.execDetached(["gsettings", "set", "org.gnome.desktop.interface", "color-scheme", "'prefer-light'"]);
Quickshell.execDetached(["sh", "-c", `sed -i 's/color_scheme_path=\\(.*\\)Dark.colors/color_scheme_path=\\1Light.colors/' ${Paths.home}/.config/qt6ct/qt6ct.conf`]); Quickshell.execDetached(["sh", "-c", `sed -i 's/color_scheme_path=\\(.*\\)Dark.colors/color_scheme_path=\\1Light.colors/' ${Paths.home}/.config/qt6ct/qt6ct.conf`]);
if (Config.general.color.neovimColors) if (Config.colors.neovimColors)
Quickshell.execDetached(["sed", "-i", "'s/\\(vim.cmd.colorscheme \\).*/\\1\"onelight\"/'", "~/.config/nvim/lua/config/load-colorscheme.lua"]); Quickshell.execDetached(["sed", "-i", "'s/\\(vim.cmd.colorscheme \\).*/\\1\"onelight\"/'", "~/.config/nvim/lua/config/load-colorscheme.lua"]);
} }
+7 -7
View File
@@ -9,13 +9,13 @@ Singleton {
id: root id: root
readonly property bool enabled: service.enabled readonly property bool enabled: service.enabled
readonly property int end: Config.general.color.scheduleHyprsunsetEnd readonly property int end: Config.display.nightlight.scheduleEnd
property bool manualToggle: false property bool manualToggle: false
readonly property int start: Config.general.color.scheduleHyprsunsetStart readonly property int start: Config.display.nightlight.scheduleStart
readonly property int temp: Config.general.color.hyprsunsetTemp readonly property int temp: Config.display.nightlight.temp
function checkStartup(): void { function checkStartup(): void {
if (!Config.general.color.scheduleHyprsunset) if (!Config.display.nightlight.schedule)
return; return;
service.apply(); service.apply();
@@ -29,11 +29,11 @@ Singleton {
NightlightManager { NightlightManager {
id: service id: service
activeAuto: Config.general.color.scheduleHyprsunset activeAuto: Config.display.nightlight.schedule
endTime: root.end endTime: root.end
startTime: root.start startTime: root.start
temp: root.temp temp: root.temp
fadeDuration: Config.general.color.nativeFadeDuration / 1000 fadeDuration: Config.display.nightlight.fadeDuration / 1000
useNativeNightlight: Config.general.color.useNativeNightlight useNativeNightlight: Config.display.nightlight.useNative
} }
} }
+3 -3
View File
@@ -38,7 +38,7 @@ Searcher {
previewPath = path; previewPath = path;
showPreview = true; showPreview = true;
if (Config.general.color.schemeGeneration) if (Config.colors.schemeGen)
previewColorsProc.running = true; previewColorsProc.running = true;
} }
@@ -62,8 +62,8 @@ Searcher {
WallpaperPath.currentWallpaperPath = path; WallpaperPath.currentWallpaperPath = path;
Quickshell.screens.forEach(n => setCrop(n.name, Qt.rect(0, 0, 1, 1), 1.0)); Quickshell.screens.forEach(n => setCrop(n.name, Qt.rect(0, 0, 1, 1), 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.colors.schemeGen)
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.colors.mode}`]);
} }
function stopPreview(): void { function stopPreview(): void {
+1 -1
View File
@@ -42,7 +42,7 @@ WidgetBase {
Repeater { Repeater {
id: repeater id: repeater
model: SystemTray.items model: SystemTray.items.values.filter(i => i.status !== Status.Passive && i.hasMenu)
TrayItem { TrayItem {
id: trayItem id: trayItem
+77 -74
View File
@@ -11,8 +11,8 @@ import qs.Components
Item { Item {
id: root id: root
readonly property real nonAnimWidth: view.implicitWidth + clipWrapper.anchors.margins * 2 readonly property real nonAnimWidth: view.implicitWidth + viewWrapper.anchors.margins * 2
readonly property real nonAnimHeight: tabs.implicitHeight + tabs.anchors.topMargin + view.implicitHeight + clipWrapper.anchors.margins + clipWrapper.anchors.topMargin readonly property real nonAnimHeight: tabs.implicitHeight + tabs.anchors.topMargin + view.implicitHeight + viewWrapper.anchors.margins + viewWrapper.anchors.topMargin
required property ScreenState screenState required property ScreenState screenState
@@ -74,115 +74,118 @@ Item {
anchors.margins: Tokens.padding.small anchors.margins: Tokens.padding.small
} }
CustomClippingRect { Item {
id: clipWrapper id: viewWrapper
property int radius: Tokens.rounding.extraLarge - anchors.margins
anchors.top: tabs.bottom anchors.top: tabs.bottom
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right
anchors.bottom: parent.bottom anchors.bottom: parent.bottom
anchors.topMargin: Tokens.spacing.small anchors.topMargin: Tokens.spacing.small
anchors.margins: Tokens.padding.small anchors.margins: Tokens.padding.small
radius: Tokens.rounding.extraLarge - anchors.margins clip: true
Item { Flickable {
id: viewWrapper id: view
readonly property int currentIndex: root.screenState.dashboardTab
readonly property Item currentItem: {
repeater.count;
return repeater.itemAt(currentIndex);
}
anchors.fill: parent anchors.fill: parent
Flickable { implicitWidth: currentItem?.implicitWidth ?? 0
id: view implicitHeight: currentItem?.implicitHeight ?? 0
readonly property int currentIndex: root.screenState.dashboardTab contentX: currentItem?.x ?? 0
readonly property Item currentItem: { contentWidth: row.implicitWidth
repeater.count; contentHeight: row.implicitHeight
return repeater.itemAt(currentIndex);
}
anchors.fill: parent interactive: false
implicitWidth: currentItem?.implicitWidth ?? 0 onContentXChanged: {
implicitHeight: currentItem?.implicitHeight ?? 0 if (!moving || !currentItem)
return;
contentX: currentItem?.x ?? 0 const x = contentX - currentItem.x;
contentWidth: row.implicitWidth if (x > currentItem.implicitWidth / 2)
contentHeight: row.implicitHeight root.screenState.dashboardTab = Math.min(root.screenState.dashboardTab + 1, tabs.count - 1);
else if (x < -currentItem.implicitWidth / 2)
root.screenState.dashboardTab = Math.max(root.screenState.dashboardTab - 1, 0);
}
interactive: false RowLayout {
id: row
onContentXChanged: { Repeater {
if (!moving || !currentItem) id: repeater
return;
const x = contentX - currentItem.x; model: ScriptModel {
if (x > currentItem.implicitWidth / 2) values: root.dashboardTabs
root.screenState.dashboardTab = Math.min(root.screenState.dashboardTab + 1, tabs.count - 1); }
else if (x < -currentItem.implicitWidth / 2)
root.screenState.dashboardTab = Math.max(root.screenState.dashboardTab - 1, 0);
}
RowLayout { delegate: Loader {
id: row id: paneLoader
Repeater { required property int index
id: repeater required property var modelData
model: ScriptModel { Layout.alignment: Qt.AlignTop
values: root.dashboardTabs
}
delegate: Loader { sourceComponent: modelData.component
id: paneLoader
required property int index Component.onCompleted: active = Qt.binding(() => {
required property var modelData if (index === view.currentIndex)
return true;
Layout.alignment: Qt.AlignTop const vx = Math.floor(view.visibleArea.xPosition * view.contentWidth);
const vex = Math.floor(vx + view.visibleArea.widthRatio * view.contentWidth);
sourceComponent: modelData.component return (vx >= x && vx <= x + implicitWidth) || (vex >= x && vex <= x + implicitWidth);
})
Component.onCompleted: active = Qt.binding(() => {
if (index === view.currentIndex)
return true;
const vx = Math.floor(view.visibleArea.xPosition * view.contentWidth);
const vex = Math.floor(vx + view.visibleArea.widthRatio * view.contentWidth);
return (vx >= x && vx <= x + implicitWidth) || (vex >= x && vex <= x + implicitWidth);
})
}
} }
} }
}
Component { Component {
id: dashComponent id: dashComponent
Dash { Dash {
screenState: root.screenState screenState: root.screenState
} radius: viewWrapper.radius
} }
}
Component { Component {
id: weatherComponent id: weatherComponent
WeatherTab {} WeatherTab {
radius: viewWrapper.radius
} }
}
Component { Component {
id: mediaComponent id: mediaComponent
Media { Media {
screenState: root.screenState screenState: root.screenState
} radius: viewWrapper.radius
} }
}
Component { Component {
id: procsComponent id: procsComponent
ProcessesTab {} ProcessesTab {
radius: viewWrapper.radius
} }
}
Behavior on contentX { Behavior on contentX {
Anim { Anim {
type: Anim.DefaultEffects type: Anim.DefaultEffects
}
} }
} }
} }
+1 -1
View File
@@ -12,7 +12,7 @@ GridLayout {
id: root id: root
readonly property bool dashboardVisible: screenState.dashboard readonly property bool dashboardVisible: screenState.dashboard
property int radius: Tokens.rounding.medium required property int radius
required property ScreenState screenState required property ScreenState screenState
columnSpacing: Tokens.spacing.small columnSpacing: Tokens.spacing.small
+7 -1
View File
@@ -11,12 +11,18 @@ Item {
id: root id: root
property ScreenState screenState property ScreenState screenState
required property int radius
implicitWidth: 1000 implicitWidth: 1000
implicitHeight: 320 implicitHeight: 320
BackgroundShapes { CustomClippingRect {
anchors.fill: parent anchors.fill: parent
radius: root.radius
BackgroundShapes {
anchors.fill: parent
}
} }
RowLayout { RowLayout {
+3 -1
View File
@@ -11,6 +11,8 @@ import qs.Services
Item { Item {
id: root id: root
required property int radius
implicitHeight: layout.implicitHeight implicitHeight: layout.implicitHeight
ServiceRef { ServiceRef {
@@ -53,7 +55,7 @@ Item {
property list<var> values property list<var> values
color: Colors.tPalette.m3surfaceContainer color: Colors.tPalette.m3surfaceContainer
radius: Tokens.rounding.largeIncreased radius: root.radius
Layout.fillWidth: true Layout.fillWidth: true
Layout.fillHeight: true Layout.fillHeight: true
implicitHeight: mainLayout.implicitHeight + mainLayout.anchors.margins * 2 implicitHeight: mainLayout.implicitHeight + mainLayout.anchors.margins * 2
+3
View File
@@ -9,6 +9,8 @@ import qs.Services
Item { Item {
id: root id: root
required property int radius
implicitWidth: Tokens.sizes.dashboard.processesWidth implicitWidth: Tokens.sizes.dashboard.processesWidth
implicitHeight: Tokens.sizes.dashboard.processesHeight implicitHeight: Tokens.sizes.dashboard.processesHeight
@@ -47,5 +49,6 @@ Item {
anchors.bottom: parent.bottom anchors.bottom: parent.bottom
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right
radius: root.radius
} }
} }
+4 -1
View File
@@ -1,3 +1,5 @@
pragma ComponentBehavior: Bound
import QtQuick import QtQuick
import QtQuick.Layouts import QtQuick.Layouts
import ZShell.Config import ZShell.Config
@@ -9,6 +11,7 @@ import qs.Helpers
Item { Item {
id: root id: root
required property int radius
readonly property var today: Weather.forecast && Weather.forecast.length > 0 ? Weather.forecast[0] : null readonly property var today: Weather.forecast && Weather.forecast.length > 0 ? Weather.forecast[0] : null
implicitWidth: layout.implicitWidth > 800 ? layout.implicitWidth : 840 implicitWidth: layout.implicitWidth > 800 ? layout.implicitWidth : 840
@@ -136,7 +139,7 @@ Item {
Layout.fillWidth: true Layout.fillWidth: true
implicitHeight: forecastItemColumn.implicitHeight + Tokens.padding.medium * 2 implicitHeight: forecastItemColumn.implicitHeight + Tokens.padding.medium * 2
radius: Tokens.rounding.large radius: root.radius
color: Colors.tPalette.m3surfaceContainer color: Colors.tPalette.m3surfaceContainer
ColumnLayout { ColumnLayout {
+3 -5
View File
@@ -27,7 +27,7 @@ Item {
for (let i = 0; i < count; i++) for (let i = 0; i < count; i++)
height += (list.itemAtIndex(i) as NotifWrapper)?.nonAnimHeight ?? 0; height += (list.itemAtIndex(i) as NotifWrapper)?.nonAnimHeight ?? 0;
if (panels.popouts.hasCurrent && (panels.popouts.currentCenter + (panels.popouts.current?.width / 2)) > panels.notifications.x || screenState.dashboard) if (panels.popouts.hasCurrent && (panels.popouts.currentCenter + (panels.popouts.current?.width / 2)) > panels.notifications.x)
return 0; return 0;
if (screenState.osd) { if (screenState.osd) {
@@ -47,8 +47,7 @@ Item {
implicitWidth: Config.notifs.sizes.width + padding * 2 implicitWidth: Config.notifs.sizes.width + padding * 2
Behavior on implicitHeight { Behavior on implicitHeight {
Anim { Anim {}
}
} }
ClippingWrapperRectangle { ClippingWrapperRectangle {
@@ -65,8 +64,7 @@ Item {
orientation: Qt.Vertical orientation: Qt.Vertical
spacing: 0 spacing: 0
delegate: NotifWrapper { delegate: NotifWrapper {}
}
displaced: Transition { displaced: Transition {
Anim { Anim {
property: "y" property: "y"
@@ -8,6 +8,7 @@ import qs.Services
DialogRowButton { DialogRowButton {
id: root id: root
property var initialSelect
required property var model required property var model
property var selectedItem property var selectedItem
+282 -164
View File
@@ -13,25 +13,48 @@ import qs.Services
Item { Item {
id: wrapper id: wrapper
property int buttonRowHeight: Tokens.padding.large * 3
property bool changesMade: false
readonly property var currentScreen: screens.length > selectedScreenIndex ? screens[selectedScreenIndex] : null readonly property var currentScreen: screens.length > selectedScreenIndex ? screens[selectedScreenIndex] : null
readonly property real maxZoom: 5.0
readonly property bool changesMade: {
cropRevision;
const r = cropRectLoader.item;
if (!r || !currentScreen || scaledImg.status !== Image.Ready)
return false;
const t = r.savedTarget();
return Math.abs(r.x - t.x) > 0.5 || Math.abs(r.y - t.y) > 0.5 || Math.abs(r.zoom - t.zoom) > 0.001;
}
property int cropRevision: 0
property int buttonRowHeight: Tokens.padding.large * 3
property var screens: [] property var screens: []
property int selectedScreenIndex: 0 property int selectedScreenIndex: 0
property bool shouldBeActive: true property bool shouldBeActive: true
function zoomFromSlider(t: real): real {
return 1 / (1 - t * (1 - 1 / maxZoom));
}
function sliderFromZoom(zoom: real): real {
return (1 - 1 / zoom) / (1 - 1 / maxZoom);
}
function applyCrop(): void { function applyCrop(): void {
if (!cropRectLoader.item || !currentScreen) if (!cropRectLoader.item || !currentScreen)
return; return;
const cropRect = cropRectLoader.item; const cropRect = cropRectLoader.item;
const cropXPercent = (cropRect.x - cropRect.imageX) / scaledImg.paintedWidth; const cropXPercent = (cropRect.x - cropRect.imageX) / cropRect.imageW;
const cropYPercent = (cropRect.y - cropRect.imageY) / scaledImg.paintedHeight; const cropYPercent = (cropRect.y - cropRect.imageY) / cropRect.imageH;
const cropWidthPercent = cropRect.width / scaledImg.paintedWidth; const cropWidthPercent = cropRect.width / cropRect.imageW;
const cropHeightPercent = cropRect.height / scaledImg.paintedHeight; const cropHeightPercent = cropRect.height / cropRect.imageH;
Wallpapers.setCrop(currentScreen.name, Qt.rect(cropXPercent, cropYPercent, cropWidthPercent, cropHeightPercent), cropRect.zoom); Wallpapers.setCrop(currentScreen.name, Qt.rect(cropXPercent, cropYPercent, cropWidthPercent, cropHeightPercent), cropRect.zoom);
cropRevision++;
} }
function refreshScreens(): void { function refreshScreens(): void {
@@ -79,16 +102,13 @@ Item {
visible: opacity > 0 visible: opacity > 0
Behavior on opacity { Behavior on opacity {
Anim { Anim {}
}
} }
Behavior on scale { Behavior on scale {
Anim { Anim {}
}
} }
Behavior on y { Behavior on y {
Anim { Anim {}
}
} }
Component.onCompleted: { Component.onCompleted: {
@@ -99,34 +119,10 @@ Item {
Qt.callLater(syncCropToScreen); Qt.callLater(syncCropToScreen);
} }
IconButton {
anchors.margins: Tokens.padding.small
anchors.right: parent.right
anchors.top: parent.top
icon: "check"
opacity: wrapper.changesMade ? 1 : 0
scale: wrapper.changesMade ? 1 : 0
z: 2
Behavior on opacity {
Anim {
}
}
Behavior on scale {
Anim {
}
}
onClicked: {
wrapper.applyCrop();
wrapper.changesMade = false;
}
}
ButtonRow { ButtonRow {
id: screenSelector id: screenSelector
anchors.bottom: scaledImg.top anchors.bottom: imageSlot.top
anchors.bottomMargin: Tokens.spacing.medium anchors.bottomMargin: Tokens.spacing.medium
anchors.left: parent.left anchors.left: parent.left
anchors.leftMargin: Tokens.padding.extraLarge * 2 anchors.leftMargin: Tokens.padding.extraLarge * 2
@@ -149,6 +145,7 @@ Item {
isRound: true isRound: true
shapeMorph: true shapeMorph: true
text: modelData.name text: modelData.name
font: Tokens.font.body.builders.large.weight(Font.DemiBold).build()
onClicked: wrapper.selectScreen(index) onClicked: wrapper.selectScreen(index)
} }
@@ -170,24 +167,54 @@ Item {
Layout.leftMargin: Tokens.padding.small Layout.leftMargin: Tokens.padding.small
Layout.preferredHeight: Tokens.padding.medium * 3 Layout.preferredHeight: Tokens.padding.medium * 3
Layout.rightMargin: Tokens.padding.small Layout.rightMargin: Tokens.padding.small
from: 1.0
implicitHeight: Tokens.padding.small * 3 implicitHeight: Tokens.padding.small * 3
insetIcon: "crop" insetIcon: "crop"
to: 5.0 value: cropRectLoader.item ? wrapper.sliderFromZoom(cropRectLoader.item.zoom) : 0
value: cropRectLoader.item ? cropRectLoader.item.zoom : 1.0
onInteraction: value => { onInteraction: value => {
wrapper.zoomClipRect(1 + (value * 4)); wrapper.zoomClipRect(wrapper.zoomFromSlider(value));
wrapper.changesMade = true; }
}
ButtonRow {
spacing: Tokens.spacing.medium
IconTextButton {
isRound: true
icon: "close"
text: qsTr("Cancel")
font: Tokens.font.body.large
shapeMorph: true
horizontalPadding: Tokens.padding.large
inactiveColor: Colors.layer(Colors.palette.m3surfaceContainerHighest, 2)
inactiveOnColor: Colors.palette.m3onSurfaceVariant
enabled: wrapper.changesMade
onClicked: {
wrapper.syncCropToScreen();
}
}
IconTextButton {
isRound: true
icon: "check"
text: qsTr("Apply")
font: Tokens.font.body.large
shapeMorph: true
horizontalPadding: Tokens.padding.large
inactiveColor: Colors.palette.m3secondary
inactiveOnColor: Colors.palette.m3onSecondary
enabled: wrapper.changesMade
onClicked: {
wrapper.applyCrop();
}
} }
} }
} }
Image { Item {
id: scaledImg id: imageSlot
property var displayData
property real monitorScale: 1.0
anchors.bottom: sliderLayout.top anchors.bottom: sliderLayout.top
anchors.bottomMargin: Tokens.spacing.medium anchors.bottomMargin: Tokens.spacing.medium
@@ -195,147 +222,238 @@ Item {
anchors.right: parent.right anchors.right: parent.right
anchors.top: parent.top anchors.top: parent.top
anchors.topMargin: wrapper.buttonRowHeight + Tokens.spacing.medium anchors.topMargin: wrapper.buttonRowHeight + Tokens.spacing.medium
// anchors.top: screenSelector.bottom
asynchronous: true
fillMode: Image.PreserveAspectFit
retainWhileLoading: true
source: Wallpapers.current
sourceSize.height: parent.height
sourceSize.width: parent.width
onPaintedWidthChanged: { CustomClippingRect {
if (paintedWidth > 0 && cropRectLoader.item) { id: fitBox
if (!wrapper.screens.length)
wrapper.refreshScreens();
wrapper.syncCropToScreen(); readonly property real aspect: scaledImg.implicitHeight > 0 ? scaledImg.implicitWidth / scaledImg.implicitHeight : 1
} readonly property real fitW: Math.min(imageSlot.width, imageSlot.height * aspect)
}
onSourceChanged: {
if (cropRectLoader.item) {
if (!wrapper.screens.length)
wrapper.refreshScreens();
wrapper.syncCropToScreen(); implicitHeight: Math.round(fitW / aspect)
} implicitWidth: Math.round(fitW)
} x: Math.round((imageSlot.width - width) / 2)
onStatusChanged: { y: Math.round((imageSlot.height - height) / 2)
if (scaledImg.status == Image.Ready && cropRectLoader.item) { radius: Tokens.rounding.largeIncreased
if (!wrapper.screens.length)
wrapper.refreshScreens();
wrapper.syncCropToScreen(); Loader {
} anchors.centerIn: parent
}
Loader { opacity: scaledImg.status === Image.Ready ? 0 : 1
id: cropRectLoader active: opacity > 0
active: scaledImg.status === Image.Ready sourceComponent: CustomRect {
implicitWidth: loadingIndicator.implicitSize + Tokens.padding.large * 2
implicitHeight: loadingIndicator.implicitSize + Tokens.padding.large * 2
sourceComponent: Component { color: Colors.palette.m3primaryContainer
CustomRect { radius: Tokens.rounding.full
id: cropRect
property real aspectRatio: wrapper.currentScreen ? wrapper.currentScreen.width / wrapper.currentScreen.height : 1 LoadingIndicator {
readonly property real baseHeight: baseWidth / aspectRatio id: loadingIndicator
readonly property real baseWidth: {
let fittedHeight = scaledImg.paintedHeight;
let fittedWidth = fittedHeight * aspectRatio;
if (fittedWidth > scaledImg.paintedWidth) { anchors.centerIn: parent
fittedWidth = scaledImg.paintedWidth;
fittedHeight = fittedWidth / aspectRatio;
}
return fittedWidth; containsIcon: true
implicitSize: Math.min(fitBox.width, fitBox.height) * 0.3
} }
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: 1.0
function centerInImage() { Behavior on opacity {
x = imageX + (scaledImg.paintedWidth - width) / 2; Anim {
y = imageY + (scaledImg.paintedHeight - height) / 2; type: Anim.DefaultEffects
} }
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 = Wallpapers.getCrop(wrapper.currentScreen.name);
if (data && (Math.abs(data.x) > 0.001 || Math.abs(data.y) > 0.001 || Math.abs(data.width - 1.0) > 0.001 || Math.abs(data.height - 1.0) > 0.001)) {
zoom = data.zoom > 0 ? data.zoom : 1.0;
x = imageX + (data.x * scaledImg.paintedWidth);
y = imageY + (data.y * scaledImg.paintedHeight);
clampToBounds();
} else {
zoom = 1.0;
centerInImage();
}
}
border.color: Colors.palette.m3primary
border.width: 2
height: baseHeight / zoom
opacity: 1
width: baseWidth / zoom
Behavior on opacity {
Anim {
}
}
Component.onCompleted: {
restoreFromData();
}
onHeightChanged: clampToBounds()
onWidthChanged: clampToBounds()
} }
} }
}
MouseArea { Image {
id: mouse id: scaledImg
function updateCrop(mouseX, mouseY) { anchors.fill: parent
if (!cropRectLoader.item) asynchronous: true
return; fillMode: Image.Stretch
source: Wallpapers.current
sourceSize: {
const dpr = (QsWindow.window as QsWindow)?.devicePixelRatio ?? 1;
return Qt.size(width * dpr, height * dpr);
}
retainWhileLoading: true
opacity: status === Image.Ready ? 1 : 0
const cropRect = cropRectLoader.item; Behavior on opacity {
Anim {
let nx = mouseX - cropRect.width * 0.5; type: Anim.SlowEffects
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 Loader {
hoverEnabled: true id: cropRectLoader
preventStealing: true
onPositionChanged: mouse => { active: scaledImg.status === Image.Ready
if (pressed) {
sourceComponent: Component {
CustomRect {
id: cropRect
property real aspectRatio: wrapper.currentScreen ? wrapper.currentScreen.width / wrapper.currentScreen.height : 1
readonly property real baseHeight: baseWidth / aspectRatio
readonly property real baseWidth: {
let fittedHeight = imageH;
let fittedWidth = fittedHeight * aspectRatio;
if (fittedWidth > imageW) {
fittedWidth = imageW;
fittedHeight = fittedWidth / aspectRatio;
}
return fittedWidth;
}
readonly property real imageH: Math.round(fitBox.height)
readonly property real imageW: Math.round(fitBox.width)
readonly property real imageX: 0
readonly property real imageY: 0
property real imgAspectRatio: imageH > 0 ? imageW / imageH : 1
property real zoom: 1.0
function centerInImage() {
x = Math.round(imageX + (imageW - width) / 2);
y = Math.round(imageY + (imageH - height) / 2);
}
function clampToBounds() {
x = Math.max(imageX, Math.min(x, imageX + imageW - width));
y = Math.max(imageY, Math.min(y, imageY + imageH - height));
}
// Where the rect should be according to saved data (same rules as a restore).
function savedTarget() {
const data = Wallpapers.getCrop(wrapper.currentScreen.name);
const hasCrop = data && (Math.abs(data.x) > 0.001 || Math.abs(data.y) > 0.001 || Math.abs(data.width - 1.0) > 0.001 || Math.abs(data.height - 1.0) > 0.001);
const z = hasCrop && data.zoom > 0 ? data.zoom : 1.0;
const w = Math.round(baseWidth / z);
const h = Math.round(baseHeight / z);
let tx = hasCrop ? Math.round(imageX + data.x * imageW) : Math.round(imageX + (imageW - w) / 2);
let ty = hasCrop ? Math.round(imageY + data.y * imageH) : Math.round(imageY + (imageH - h) / 2);
tx = Math.max(imageX, Math.min(tx, imageX + imageW - w));
ty = Math.max(imageY, Math.min(ty, imageY + imageH - h));
return {
x: tx,
y: ty,
zoom: z
};
}
function restoreFromData() {
const t = savedTarget();
zoom = t.zoom;
x = t.x;
y = t.y;
}
height: Math.round(baseHeight / zoom)
width: Math.round(baseWidth / zoom)
color: zoomSlider.dragging || mouse.pressed ? Qt.alpha(Colors.palette.m3tertiary, 0.3) : "transparent"
Behavior on color {
CAnim {}
}
Component.onCompleted: {
restoreFromData();
}
onHeightChanged: clampToBounds()
onWidthChanged: clampToBounds()
}
}
}
ShaderEffect {
anchors.fill: parent
readonly property real dpr: (QsWindow.window as QsWindow)?.devicePixelRatio ?? 1
function snap(v: real): real {
return Math.round(v * dpr) / dpr;
}
property color borderColor: Colors.palette.m3tertiary
property vector4d cropBox: cropRectLoader.item ? Qt.vector4d(snap(cropRectLoader.item.x), snap(cropRectLoader.item.y), snap(cropRectLoader.item.width), snap(cropRectLoader.item.height)) : Qt.vector4d(0, 0, 0, 0)
property real borderWidth: 2
property real cornerRadius: fitBox.radius
property vector2d itemSize: Qt.vector2d(width, height)
property real pixelScale: dpr
fragmentShader: Qt.resolvedUrl(`${Quickshell.shellDir}/assets/shaders/crop_sdf.frag.qsb`)
}
MouseArea {
id: mouse
property real grabX: 0
property real grabY: 0
function insideCropRect(mx, my): bool {
const r = cropRectLoader.item;
if (!r)
return false;
return mx >= r.x && mx <= r.x + r.width && my >= r.y && my <= r.y + r.height;
}
function beginGrab(mx, my): void {
const r = cropRectLoader.item;
if (!r)
return;
const inside = insideCropRect(mx, my);
if (inside) {
grabX = mx - r.x;
grabY = my - r.y;
} else {
grabX = r.width * 0.5;
grabY = r.height * 0.5;
}
}
function updateCrop(mx, my): void {
if (!cropRectLoader.item)
return;
const cropRect = cropRectLoader.item;
let nx = mx - grabX;
let ny = my - grabY;
nx = Math.max(cropRect.imageX, Math.min(nx, cropRect.imageX + cropRect.imageW - cropRect.width));
ny = Math.max(cropRect.imageY, Math.min(ny, cropRect.imageY + cropRect.imageH - cropRect.height));
cropRect.x = nx;
cropRect.y = ny;
}
anchors.fill: parent
hoverEnabled: true
preventStealing: true
cursorShape: pressed ? Qt.ClosedHandCursor : insideCropRect(mouseX, mouseY) ? Qt.OpenHandCursor : Qt.PointingHandCursor
onPositionChanged: mouse => {
if (pressed) {
updateCrop(mouse.x, mouse.y);
}
}
onPressed: mouse => {
beginGrab(mouse.x, mouse.y);
updateCrop(mouse.x, mouse.y); updateCrop(mouse.x, mouse.y);
wrapper.changesMade = true;
} }
} }
onPressed: mouse => {
updateCrop(mouse.x, mouse.y);
wrapper.changesMade = true;
}
onReleased: {
wrapper.changesMade = true;
}
} }
} }
} }
+11 -11
View File
@@ -43,17 +43,17 @@ Item {
Component.onCompleted: root.loadPage(root.sState.currentPageIdx) Component.onCompleted: root.loadPage(root.sState.currentPageIdx)
CustomRect { // CustomRect {
anchors.fill: parent // anchors.fill: parent
color: Qt.alpha(Colors.palette.m3shadow, 0.3) // color: Qt.alpha(Colors.palette.m3shadow, 0.3)
opacity: root.sState.dimmed ? 1 : 0 // opacity: root.sState.dimmed ? 1 : 0
visible: opacity > 0 // visible: opacity > 0
z: 0 // z: 0
//
Behavior on opacity { // Behavior on opacity {
Anim {} // Anim {}
} // }
} // }
} }
Connections { Connections {
+2 -2
View File
@@ -202,12 +202,12 @@ PageBase {
} }
ToggleRow { ToggleRow {
checked: Config.general.color.smart checked: Config.colors.smart
settingAnchor: "services-smart-color-scheme" settingAnchor: "services-smart-color-scheme"
subtext: qsTr("Derive theme mode from the wallpaper") subtext: qsTr("Derive theme mode from the wallpaper")
text: qsTr("Smart color scheme") text: qsTr("Smart color scheme")
onToggled: Config.general.color.smart = checked onToggled: Config.colors.smart = checked
} }
SelectRow { SelectRow {
@@ -73,15 +73,15 @@ PageBase {
ToggleRow { ToggleRow {
text: qsTr("Dynamic colors") text: qsTr("Dynamic colors")
first: true first: true
subtext: qsTr("Automatic color scheme based on wallpaper is %1").arg(Config.general.color.schemeGeneration ? "enabled" : "disabled") subtext: qsTr("Automatic color scheme based on wallpaper is %1").arg(Config.colors.schemeGen ? "enabled" : "disabled")
checked: Config.general.color.schemeGeneration checked: Config.colors.schemeGen
settingAnchor: "colors-fonts-dynamic-colors" settingAnchor: "colors-fonts-dynamic-colors"
onToggled: { onToggled: {
Config.general.color.schemeGeneration = checked; Config.colors.schemeGen = checked;
if (checked) if (checked)
Colors.setMode(Config.general.color.mode); Colors.setMode(Config.colors.mode);
} }
} }
@@ -89,7 +89,7 @@ PageBase {
settingAnchor: "colors-fonts-preset-scheme" settingAnchor: "colors-fonts-preset-scheme"
model: root.schemes model: root.schemes
acceptLabel: qsTr("Confirm") acceptLabel: qsTr("Confirm")
enabled: !Config.general.color.schemeGeneration enabled: !Config.colors.schemeGen
header: qsTr("Select scheme") header: qsTr("Select scheme")
rowButton.activeItem: root.schemes.find(s => { rowButton.activeItem: root.schemes.find(s => {
var regex = new RegExp(Config.colors.presets.name, "i"); var regex = new RegExp(Config.colors.presets.name, "i");
@@ -157,7 +157,7 @@ PageBase {
} }
// qmlformat on // qmlformat on
acceptLabel: qsTr("Confirm") acceptLabel: qsTr("Confirm")
enabled: !Config.general.color.schemeGeneration && model.length > 1 enabled: !Config.colors.schemeGen && model.length > 1
header: qsTr("Select variant") header: qsTr("Select variant")
rowButton.activeItem: { rowButton.activeItem: {
const v = Config.colors.presets.variant ?? ""; const v = Config.colors.presets.variant ?? "";
@@ -210,7 +210,7 @@ PageBase {
} }
// qmlformat on // qmlformat on
acceptLabel: qsTr("Confirm") acceptLabel: qsTr("Confirm")
enabled: !Config.general.color.schemeGeneration && model.length > 1 enabled: !Config.colors.schemeGen && model.length > 1
header: qsTr("Select accent") header: qsTr("Select accent")
rowButton.activeItem: { rowButton.activeItem: {
const a = Config.colors.presets.accent ?? ""; const a = Config.colors.presets.accent ?? "";
+60 -19
View File
@@ -96,20 +96,21 @@ PageBase {
TimeDialogSelect { TimeDialogSelect {
readonly property string endTime: { readonly property string endTime: {
var d = new Date(0, 0, 0, 0, 0, 0, 0); var d = new Date(0, 0, 0, 0, 0, 0, 0);
d.setMinutes(Config.general.color.scheduleDarkEnd); d.setMinutes(Config.colors.scheduleDarkEnd);
return Qt.formatTime(d, "hh:mm AP"); return Qt.formatTime(d, "hh:mm AP");
} }
readonly property string startTime: { readonly property string startTime: {
var d = new Date(0, 0, 0, 0, 0, 0, 0); var d = new Date(0, 0, 0, 0, 0, 0, 0);
d.setMinutes(Config.general.color.scheduleDarkStart); d.setMinutes(Config.colors.scheduleDarkStart);
return Qt.formatTime(d, "hh:mm AP"); return Qt.formatTime(d, "hh:mm AP");
} }
rowButton.checked: Config.general.color.scheduleDark rowButton.checked: Config.colors.scheduleDark
rowButton.onToggled: Config.general.color.scheduleDark = rowButton.checked rowButton.onToggled: Config.colors.scheduleDark = rowButton.checked
start: Config.general.color.scheduleDarkStart start: Config.colors.scheduleDarkStart
end: Config.general.color.scheduleDarkEnd end: Config.colors.scheduleDarkEnd
first: true first: true
last: true
settingAnchor: "style-schedule-dark-mode" settingAnchor: "style-schedule-dark-mode"
acceptLabel: qsTr("Apply") acceptLabel: qsTr("Apply")
rootParent: root.flickable rootParent: root.flickable
@@ -122,8 +123,8 @@ PageBase {
} }
onAccepted: { onAccepted: {
Config.general.color.scheduleDarkStart = startCurrent; Config.colors.scheduleDarkStart = startCurrent;
Config.general.color.scheduleDarkEnd = endCurrent; Config.colors.scheduleDarkEnd = endCurrent;
ModeScheduler.checkStartup(); ModeScheduler.checkStartup();
} }
} }
@@ -131,27 +132,26 @@ PageBase {
TimeDialogSelect { TimeDialogSelect {
readonly property string endTime: { readonly property string endTime: {
var d = new Date(0, 0, 0, 0, 0, 0, 0); var d = new Date(0, 0, 0, 0, 0, 0, 0);
d.setMinutes(Config.general.color.scheduleHyprsunsetEnd); d.setMinutes(Config.display.nightlight.scheduleEnd);
return Qt.formatTime(d, "hh:mm AP"); return Qt.formatTime(d, "hh:mm AP");
} }
readonly property string startTime: { readonly property string startTime: {
var d = new Date(0, 0, 0, 0, 0, 0, 0); var d = new Date(0, 0, 0, 0, 0, 0, 0);
d.setMinutes(Config.general.color.scheduleHyprsunsetStart); d.setMinutes(Config.display.nightlight.scheduleStart);
return Qt.formatTime(d, "hh:mm AP"); return Qt.formatTime(d, "hh:mm AP");
} }
Layout.topMargin: Tokens.spacing.extraSmall / 2 - parent.spacing rowButton.checked: Config.display.nightlight.schedule
rowButton.checked: Config.general.color.scheduleHyprsunset rowButton.onToggled: Config.display.nightlight.schedule = rowButton.checked
rowButton.onToggled: Config.general.color.scheduleHyprsunset = rowButton.checked start: Config.display.nightlight.scheduleStart
start: Config.general.color.scheduleHyprsunsetStart end: Config.display.nightlight.scheduleEnd
end: Config.general.color.scheduleHyprsunsetEnd first: true
last: true settingAnchor: "style-schedule-nightlight"
settingAnchor: "style-schedule-hyprsunset"
acceptLabel: qsTr("Apply") acceptLabel: qsTr("Apply")
rootParent: root.flickable rootParent: root.flickable
header: qsTr("Select time") header: qsTr("Select time")
subtext: qsTr("Hyprsunset will turn on at %1, and turn off at %2.").arg(startTime).arg(endTime) subtext: qsTr("Nightlight will turn on at %1, and turn off at %2.").arg(startTime).arg(endTime)
label: qsTr("Schedule hyprsunset") label: qsTr("Schedule nightlight")
onOpenChanged: { onOpenChanged: {
root.sState.dimmed = open; root.sState.dimmed = open;
@@ -163,5 +163,46 @@ PageBase {
Nightlight.checkStartup(); Nightlight.checkStartup();
} }
} }
ToggleRow {
Layout.topMargin: Tokens.spacing.extraSmall / 2 - parent.spacing
checked: Config.display.nightlight.useNative
settingAnchor: "display-nightlight-useNative"
text: qsTr("Use native")
subtext: qsTr("Use native shader nightlight for more features")
onToggled: Config.display.nightlight.useNative = checked
}
SpinRow {
Layout.topMargin: Tokens.spacing.extraSmall / 2 - parent.spacing
from: 2000
settingAnchor: "display-nightlight-temp"
stepSize: 100
subtext: qsTr("How warm/orange the screen will be. Lower is warmer (Kelvin)")
text: qsTr("Temperature")
to: 6500
value: Config.display.nightlight.temp
onMoved: v => Config.display.nightlight.temp = v
}
SpinRow {
Layout.topMargin: Tokens.spacing.extraSmall / 2 - parent.spacing
from: 0
settingAnchor: "display-nightlight-fade-duration"
enabled: Config.display.nightlight.useNative
last: true
stepSize: 1
subtext: qsTr("How long it takes for the gradual nightlight to finish (s)")
text: qsTr("Fade duration")
to: 60 * 5
value: Config.display.nightlight.fadeDuration / 1000
onMoved: v => Config.display.nightlight.fadeDuration = v * 1000
}
} }
} }
+5 -2
View File
@@ -4,8 +4,10 @@ qml_module(ZShell-config
DEPENDENCIES DEPENDENCIES
ZShell.Settings ZShell.Settings
SOURCES SOURCES
common.hpp common.cpp confignode.hpp confignode.cpp
rootnodes.hpp rootnodes.cpp configobject.hpp configobject.cpp
configlist.hpp configlist.cpp
config.hpp config.cpp
migration.hpp migration.cpp migration.hpp migration.cpp
anim.hpp anim.cpp anim.hpp anim.cpp
fontbuilder.hpp fontbuilder.cpp fontbuilder.hpp fontbuilder.cpp
@@ -17,6 +19,7 @@ qml_module(ZShell-config
clipboard.hpp clipboard.hpp
colors.hpp colors.hpp
dashboard.hpp dashboard.hpp
display.hpp
dock.hpp dock.hpp
enums.hpp enums.hpp
general.hpp general.hpp
+123
View File
@@ -0,0 +1,123 @@
#include "migration.hpp"
#include <QDebug>
namespace ZShell::config {
namespace {
// A stored value is "missing" if the path does not resolve to an entry.
// Note: default-constructed QJsonValue is Null, not Undefined.
bool isMissing(const QJsonValue& v) {
return v.isUndefined() || v.isNull();
}
QJsonValue valueAt(const QJsonObject& obj, const QStringList& path) {
QJsonObject cur = obj;
for (int i = 0; i < path.size() - 1; ++i) {
if (!cur.value(path.at(i)).isObject()) return QJsonValue::Undefined;
cur = cur.value(path.at(i)).toObject();
}
const QString key = path.last();
if (!cur.contains(key)) return QJsonValue::Undefined;
return cur.value(key);
}
QJsonObject placeAt(QJsonObject obj, QStringList path, const QJsonValue& value) {
const QString head = path.takeFirst();
if (path.isEmpty()) {
obj[head] = value;
return obj;
}
QJsonObject child = obj.value(head).isObject() ? obj.value(head).toObject()
: QJsonObject();
obj[head] = placeAt(child, path, value);
return obj;
}
// Removes `path` from `obj`. Returns true if a value was removed.
// Parent objects left empty are removed as well.
bool removeAt(QJsonObject& obj, const QStringList& path) {
const QString head = path.first();
if (path.size() == 1) {
if (!obj.contains(head)) return false;
obj.remove(head);
return true;
}
if (!obj.value(head).isObject()) return false;
QJsonObject child = obj.value(head).toObject();
const bool removed = removeAt(child, path.mid(1));
if (child.isEmpty())
obj.remove(head);
else
obj[head] = child;
return removed;
}
} // namespace
const QList<ConfigMigrationRule>& ConfigMigrations::rules() {
static const QList<ConfigMigrationRule> s_rules = {
migrate(
"general.color.scheduleHyprsunset", "display.nightlight.schedule"),
migrate(
"general.color.scheduleHyprsunsetStart",
"display.nightlight.scheduleStart"),
migrate(
"general.color.scheduleHyprsunsetEnd",
"display.nightlight.scheduleEnd"),
migrate("general.color.scheduleDark", "colors.scheduleDark"),
migrate("general.color.scheduleDarkStart", "colors.scheduleDarkStart"),
migrate("general.color.scheduleDarkEnd", "colors.scheduleDarkEnd"),
migrate("general.color.schemeGeneration", "colors.schemeGen"),
migrate(
"general.color.useNativeNightlight",
"display.nightlight.useNative"),
migrate(
"general.color.nativeFadeDuration",
"display.nightlight.fadeDuration"),
migrate("general.color.hyprsunsetTemp", "display.nightlight.temp"),
migrate("general.color.neovimColors", "colors.neovimColors"),
migrate("general.color.mode", "colors.mode"),
migrate("general.color.smart", "colors.smart"),
};
return s_rules;
}
QJsonObject ConfigMigrations::apply(const QJsonObject& json) {
QJsonObject obj = json;
for (const auto& rule : rules()) {
const QStringList from =
rule.from.split(QLatin1Char('.'), Qt::SkipEmptyParts);
const QStringList to =
rule.to.split(QLatin1Char('.'), Qt::SkipEmptyParts);
if (from.isEmpty() || to.isEmpty()) continue;
const QJsonValue oldValue = valueAt(obj, from);
if (isMissing(oldValue)) continue;
const bool targetExists = !isMissing(valueAt(obj, to));
if (!targetExists) obj = placeAt(obj, to, oldValue);
if (removeAt(obj, from)) {
qInfo() << "Config migration:" << rule.from << "->" << rule.to
<< (targetExists ? "(target already set, old value dropped)"
: "(value moved)");
}
}
return obj;
}
} // namespace ZShell::config
+38
View File
@@ -0,0 +1,38 @@
#pragma once
#include <QJsonObject>
#include <QStringList>
namespace ZShell::config {
// A single migration rule: move the value stored at the dotted `from` path
// to the dotted `to` path, then delete the old key (and any parent objects
// left empty).
//
// Rules are applied to the raw JSON file on every load, before the schema
// loads it. They are idempotent: once the old key is gone the rule never
// fires again, so no version stamping is needed. Old rules can stay in the
// table forever.
//
// WARNING: the target path must exist in the schema (i.e. a ConfigObject
// property somewhere in the tree). If it does not, the migrated value is
// loaded as an unknown key and silently dropped on the next save.
struct ConfigMigrationRule {
QString from;
QString to;
};
// Helper for writing rules: migrate("general.color.foo", "display.bar.foo")
inline ConfigMigrationRule migrate(const QString& from, const QString& to) {
return {from, to};
}
class ConfigMigrations {
public:
[[nodiscard]] static const QList<ConfigMigrationRule>& rules();
// Applies all rules to `json`, returning the migrated object.
[[nodiscard]] static QJsonObject apply(const QJsonObject& json);
};
} // namespace ZShell::config
+9 -5
View File
@@ -2,6 +2,8 @@
#include <qcursor.h> #include <qcursor.h>
#include <qguiapplication.h> #include <qguiapplication.h>
#include <qlogging.h>
#include <qnamespace.h>
#include <qobject.h> #include <qobject.h>
#include <qqmlintegration.h> #include <qqmlintegration.h>
@@ -14,23 +16,25 @@ class Cursor : public QObject {
public: public:
Q_INVOKABLE void setOverride(Qt::CursorShape shape) { Q_INVOKABLE void setOverride(Qt::CursorShape shape) {
if (m_override) if (m_shape == shape) return;
if (m_shape)
QGuiApplication::changeOverrideCursor(QCursor(shape)); QGuiApplication::changeOverrideCursor(QCursor(shape));
else { else {
QGuiApplication::setOverrideCursor(QCursor(shape)); QGuiApplication::setOverrideCursor(QCursor(shape));
m_override = true;
} }
m_shape = shape;
} }
Q_INVOKABLE void clearOverride() { Q_INVOKABLE void clearOverride() {
if (!m_override) return; if (!m_shape) return;
QGuiApplication::restoreOverrideCursor(); QGuiApplication::restoreOverrideCursor();
m_override = false; m_shape.reset();
} }
private: private:
bool m_override = false; std::optional<Qt::CursorShape> m_shape;
}; };
} // namespace ZShell::internal } // namespace ZShell::internal
+7 -8
View File
@@ -45,8 +45,7 @@ void Memory::tick() {
QStringLiteral("MemTotal: *(\\d+)")); QStringLiteral("MemTotal: *(\\d+)"));
static const QRegularExpression reAvail( static const QRegularExpression reAvail(
QStringLiteral("MemAvailable: *(\\d+)")); QStringLiteral("MemAvailable: *(\\d+)"));
static const QRegularExpression reCached(QStringLiteral( static const QRegularExpression reCached(QStringLiteral("Cached: *(\\d+)"));
"Cached: *(\\d+)"));
const QString text = QString::fromLatin1(data); const QString text = QString::fromLatin1(data);
const auto totalMatch = reTotal.match(text); const auto totalMatch = reTotal.match(text);
@@ -78,14 +77,14 @@ void Memory::tick() {
const auto pgin = rePgin.match(vtext); const auto pgin = rePgin.match(vtext);
const auto pgout = rePgout.match(vtext); const auto pgout = rePgout.match(vtext);
if (pgin.hasMatch()) { if (pgin.hasMatch()) {
readKib = static_cast<quint64>(static_cast<double>( readKib = static_cast<quint64>(
pgin.captured(1).toULongLong()) * static_cast<double>(pgin.captured(1).toULongLong()) *
pageKib); pageKib);
} }
if (pgout.hasMatch()) { if (pgout.hasMatch()) {
writeKib = static_cast<quint64>(static_cast<double>( writeKib = static_cast<quint64>(
pgout.captured(1).toULongLong()) * static_cast<double>(pgout.captured(1).toULongLong()) *
pageKib); pageKib);
} }
} }
} }
@@ -10,7 +10,6 @@ namespace ZShell::services {
namespace { namespace {
constexpr const char* NL_EVENT = "zshell-nightlight"; constexpr const char* NL_EVENT = "zshell-nightlight";
constexpr int NEUTRAL_KELVIN = 6500;
constexpr int RECONNECT_MS = 2000; constexpr int RECONNECT_MS = 2000;
} // namespace } // namespace
@@ -404,7 +403,6 @@ void NightlightManager::handleEventLine(const QByteArray& line) {
if (line.left(sep) != NL_EVENT) return; if (line.left(sep) != NL_EVENT) return;
// data = enabled,animating,last,current
const auto fields = line.mid(sep + 2).split(','); const auto fields = line.mid(sep + 2).split(',');
if (fields.size() < 4) return; if (fields.size() < 4) return;
+10 -9
View File
@@ -793,8 +793,7 @@ void ProcessModel::scanChunk(
nowTicks > prevTicks ? nowTicks - prevTicks : 0; nowTicks > prevTicks ? nowTicks - prevTicks : 0;
entry.cpuPercent = entry.cpuPercent =
(static_cast<double>(deltaTicks) / (static_cast<double>(deltaTicks) /
(ctx.deltaSecs * (ctx.deltaSecs * static_cast<double>(ctx.clockTicksPerSec))) *
static_cast<double>(ctx.clockTicksPerSec))) *
100.0; 100.0;
} else { } else {
entry.cpuPercent = 0.0; entry.cpuPercent = 0.0;
@@ -839,7 +838,8 @@ ProcessModel::ScanResult ProcessModel::scanProcesses(
// syscall-bound (open/read/close per file per process) and serializes // syscall-bound (open/read/close per file per process) and serializes
// badly on large systems. Keep one pool thread free for this function, // badly on large systems. Keep one pool thread free for this function,
// which itself runs inside the global pool. // which itself runs inside the global pool.
int nThreads = qMin(16, QThreadPool::globalInstance()->maxThreadCount() - 1); int nThreads =
qMin(16, QThreadPool::globalInstance()->maxThreadCount() - 1);
if (pids.size() < 128 || nThreads < 2) { if (pids.size() < 128 || nThreads < 2) {
nThreads = 1; nThreads = 1;
} }
@@ -860,8 +860,8 @@ ProcessModel::ScanResult ProcessModel::scanProcesses(
continue; continue;
} }
chunks[t] = pids.mid(a, b - a); chunks[t] = pids.mid(a, b - a);
futures.append(QtConcurrent::run( futures.append(
[&ctx, &chunks, &outs, &uidParts, t]() { QtConcurrent::run([&ctx, &chunks, &outs, &uidParts, t]() {
scanChunk(ctx, chunks[t], outs[t], uidParts[t]); scanChunk(ctx, chunks[t], outs[t], uidParts[t]);
})); }));
} }
@@ -880,8 +880,9 @@ ProcessModel::ScanResult ProcessModel::scanProcesses(
} }
} }
std::sort( std::sort(
fresh.begin(), fresh.end(), [](const ProcessEntry& a, fresh.begin(),
const ProcessEntry& b) { fresh.end(),
[](const ProcessEntry& a, const ProcessEntry& b) {
return a.pid < b.pid; return a.pid < b.pid;
}); });
} }
@@ -1012,8 +1013,8 @@ void ProcessModel::applyView() {
const FuzzyText prep = fuzzyPrepare(src.command); const FuzzyText prep = fuzzyPrepare(src.command);
fe.commandChars = std::move(prep.chars); fe.commandChars = std::move(prep.chars);
fe.commandClasses = std::move(prep.classes); fe.commandClasses = std::move(prep.classes);
fe.hasCommandLine = fe.hasCommandLine = !src.commandLine.isEmpty() &&
!src.commandLine.isEmpty() && src.commandLine != src.command; src.commandLine != src.command;
if (fe.hasCommandLine) { if (fe.hasCommandLine) {
const FuzzyText prepCl = fuzzyPrepare(src.commandLine); const FuzzyText prepCl = fuzzyPrepare(src.commandLine);
fe.commandLineChars = std::move(prepCl.chars); fe.commandLineChars = std::move(prepCl.chars);
+5 -9
View File
@@ -13,21 +13,17 @@ import qs.Paths
Singleton { Singleton {
id: root id: root
readonly property M3Palette current: M3Palette { readonly property M3Palette current: M3Palette {}
}
property bool currentLight property bool currentLight
property string flavour property string flavour
readonly property bool light: showPreview ? previewLight : currentLight readonly property bool light: showPreview ? previewLight : currentLight
readonly property M3Palette palette: showPreview ? preview : current readonly property M3Palette palette: showPreview ? preview : current
readonly property M3Palette preview: M3Palette { readonly property M3Palette preview: M3Palette {}
}
property bool previewLight property bool previewLight
property string scheme property string scheme
property bool showPreview property bool showPreview
readonly property M3TPalette tPalette: M3TPalette { readonly property M3TPalette tPalette: M3TPalette {}
} readonly property Transparency transparency: Transparency {}
readonly property Transparency transparency: Transparency {
}
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 {
@@ -112,7 +108,7 @@ Singleton {
function setMode(mode: string): void { function setMode(mode: string): void {
Quickshell.execDetached(["zshell-cli", "scheme", "generate", "--mode", mode]); Quickshell.execDetached(["zshell-cli", "scheme", "generate", "--mode", mode]);
Config.general.color.mode = mode; Config.colors.mode = mode;
} }
function swapRG(c: color): color { function swapRG(c: color): color {
+40
View File
@@ -0,0 +1,40 @@
#version 440
layout(location = 0) in vec2 qt_TexCoord0;
layout(location = 0) out vec4 fragColor;
layout(std140, binding = 0) uniform buf {
mat4 qt_Matrix;
float qt_Opacity;
vec2 itemSize;
vec4 cropBox;
vec4 borderColor;
float cornerRadius;
float borderWidth;
float pixelScale;
};
float sdRoundBox(vec2 p, vec2 halfSize, float r) {
vec2 q = abs(p) - halfSize + r;
return length(max(q, 0.0)) + min(max(q.x, q.y), 0.0) - r;
}
void main() {
vec2 sz = itemSize * pixelScale;
vec2 p = qt_TexCoord0 * sz;
vec4 box = cropBox * pixelScale;
float r = cornerRadius * pixelScale;
float bw = borderWidth * pixelScale;
float dImage = sdRoundBox(p - sz * 0.5, sz * 0.5, r);
float dCrop = sdRoundBox(p - (box.xy + box.zw * 0.5), box.zw * 0.5, 0.0);
float d = max(dImage, dCrop);
// ramp is exactly 1 physical pixel wide
float outer = clamp(0.5 - d, 0.0, 1.0);
float inner = clamp(0.5 - (d + bw), 0.0, 1.0);
float a = outer - inner;
fragColor = vec4(borderColor.rgb, 1.0) * (borderColor.a * a * qt_Opacity);
}
Binary file not shown.
+2 -2
View File
@@ -625,8 +625,8 @@ def generate(
scheme_type = config["colors"].get("schemeType", "fruit-salad") scheme_type = config["colors"].get("schemeType", "fruit-salad")
scheme = scheme or scheme_type scheme = scheme or scheme_type
assert isinstance(scheme, str) assert isinstance(scheme, str)
config_mode = config["general"]["color"]["mode"] config_mode = config["colors"]["mode"]
smart = bool(config["general"]["color"].get("smart", False)) smart = bool(config["colors"].get("smart", False))
scheme_class = get_scheme_class(scheme) scheme_class = get_scheme_class(scheme)
p_variant = "default" p_variant = "default"
+6 -12
View File
@@ -23,33 +23,27 @@ ShellRoot {
settings.watchFiles: true settings.watchFiles: true
Drawers { Drawers {}
}
Wallpaper { Wallpaper {}
}
AreaPicker { AreaPicker {}
}
Lock { Lock {
id: lock id: lock
} }
Shortcuts { Shortcuts {}
}
IdleMonitors { IdleMonitors {
lock: lock lock: lock
} }
Polkit { Polkit {}
}
LazyLoader { LazyLoader {
activeAsync: root.laptop activeAsync: root.laptop
component: BatteryService { component: BatteryService {}
}
} }
} }