1 Commits

123 changed files with 3467 additions and 3248 deletions
-58
View File
@@ -1,58 +0,0 @@
---
BasedOnStyle: LLVM
AccessModifierOffset: 0
AlignAfterOpenBracket: Align
AlignConsecutiveAssignments: false
AlignConsecutiveDeclarations: false
AlignOperands: Align
AllowAllParametersOfDeclarationOnNextLine: true
AllowShortFunctionsOnASingleLine: Inline
AllowShortIfStatementsOnASingleLine: Never
AlwaysBreakBeforeMultilineStrings: false
AlwaysBreakTemplateDeclarations: Yes
BinPackArguments: false
BinPackParameters: false
BreakBeforeBraces: Attach
BreakBeforeTernaryOperators: true
BreakConstructorInitializers: BeforeComma
ColumnLimit: 80
ConstructorInitializerAllOnOneLineOrOnePerLine: true
ConstructorInitializerIndentWidth: 4
ContinuationIndentWidth: 4
Cpp11BracedListStyle: true
DerivePointerAlignment: false
FixNamespaceComments: true
IndentCaseLabels: false
IndentWidth: 4
KeepEmptyLinesAtTheStartOfBlocks: false
Language: Cpp
MaxEmptyLinesToKeep: 2
NamespaceIndentation: None
PenaltyBreakAssignment: 10
PenaltyBreakBeforeFirstCallParameter: 100
PenaltyBreakComment: 300
PenaltyBreakFirstLessLess: 120
PenaltyBreakString: 1000
PenaltyExcessCharacter: 100
PenaltyReturnTypeOnItsOwnLine: 200
PointerAlignment: Left
ReflowComments: false
SortIncludes: false
SpaceAfterCStyleCast: false
SpaceAfterTemplateKeyword: true
SpaceBeforeAssignmentOperators: true
SpaceBeforeCpp11BracedList: false
SpaceBeforeCtorInitializerColon: true
SpaceBeforeInheritanceColon: true
SpaceBeforeParens: ControlStatements
SpaceBeforeRangeBasedForLoopColon: true
SpaceInEmptyParentheses: false
SpacesBeforeTrailingComments: 1
SpacesInAngles: false
SpacesInContainerLiterals: false
SpacesInCStyleCastParentheses: false
SpacesInParentheses: false
SpacesInSquareBrackets: false
Standard: c++20
TabWidth: 4
UseTab: Always
-31
View File
@@ -1,31 +0,0 @@
---
Checks: >
-*,
bugprone-*,
clang-analyzer-*,
modernize-*,
-modernize-use-trailing-return-type,
performance-*,
readability-braces-around-statements,
readability-else-after-return,
readability-identifier-naming,
readability-redundant-*,
readability-simplify-*,
CheckOptions:
readability-identifier-naming.ClassCase: CamelCase
readability-identifier-naming.EnumCase: CamelCase
readability-identifier-naming.FunctionCase: camelBack
readability-identifier-naming.MemberCase: camelBack
readability-identifier-naming.MemberPrefix: m_
readability-identifier-naming.MethodCase: camelBack
readability-identifier-naming.NamespaceCase: CamelCase
readability-identifier-naming.ParameterCase: camelBack
readability-identifier-naming.PrivateMemberPrefix: m_
readability-identifier-naming.StaticConstantCase: UPPER_CASE
readability-identifier-naming.StaticConstantPrefix: k
readability-identifier-naming.VariableCase: camelBack
WarningsAsErrors: "*"
HeaderFilterRegex: ".*"
FormatStyle: file
...
+3 -16
View File
@@ -38,18 +38,15 @@ jobs:
rustfmt \ rustfmt \
rust-clippy rust-clippy
- id: format-check - name: Format check
name: Format check
continue-on-error: true continue-on-error: true
run: | run: |
if [ -n "$(find . -name "Cargo.toml" -print -quit)" ]; then if [ -n "$(find . -name "Cargo.toml" -print -quit)" ]; then
status=0
for manifest in $(find . -name "Cargo.toml"); do for manifest in $(find . -name "Cargo.toml"); do
cargo fmt --manifest-path "$manifest" --check && \ cargo fmt --manifest-path "$manifest" --check && \
echo "$manifest: formatting OK" || \ echo "$manifest: formatting OK" || \
{ echo "$manifest: needs formatting"; status=1; } echo "$manifest: needs formatting"
done done
exit $status
elif [ -n "$(find . -name "*.rs" -print -quit)" ]; then elif [ -n "$(find . -name "*.rs" -print -quit)" ]; then
echo "Rust files found but no Cargo.toml" echo "Rust files found but no Cargo.toml"
exit 1 exit 1
@@ -57,8 +54,7 @@ jobs:
echo "No Rust project found" echo "No Rust project found"
fi fi
- id: clippy - name: Clippy
name: Clippy
run: | run: |
if [ -n "$(find . -name "Cargo.toml" -print -quit)" ]; then if [ -n "$(find . -name "Cargo.toml" -print -quit)" ]; then
status=0 status=0
@@ -74,12 +70,3 @@ jobs:
else else
echo "No Rust project found" echo "No Rust project found"
fi fi
- name: Check results
if: always()
run: |
if [ "${{ steps.format-check.outcome }}" = "failure" ] || [ "${{ steps.clippy.outcome }}" = "failure" ]; then
echo "One or more checks failed"
exit 1
fi
echo "All checks passed"
-2
View File
@@ -13,5 +13,3 @@ uv.lock
.qtcreator/ .qtcreator/
dist/ dist/
**/target/ **/target/
**/test-plugins/
**/Charts/
-1
View File
@@ -25,7 +25,6 @@ add_compile_options(
if("plugin" IN_LIST ENABLE_MODULES) if("plugin" IN_LIST ENABLE_MODULES)
add_subdirectory(Plugins) add_subdirectory(Plugins)
endif() endif()
if("shell" IN_LIST ENABLE_MODULES) if("shell" IN_LIST ENABLE_MODULES)
+7 -11
View File
@@ -10,35 +10,31 @@ Slider {
background: Item { background: Item {
CustomRect { CustomRect {
anchors.bottom: parent.bottom anchors.bottom: parent.bottom
anchors.bottomMargin: root.implicitHeight / 6
anchors.left: parent.left anchors.left: parent.left
anchors.top: parent.top anchors.top: parent.top
anchors.topMargin: root.implicitHeight / 6
bottomRightRadius: root.implicitHeight / 6 bottomRightRadius: root.implicitHeight / 6
color: root.color color: root.color
implicitWidth: root.handle.x - root.implicitHeight / 6 implicitWidth: root.handle.x - root.implicitHeight / 2
radius: root.implicitHeight / 6 radius: Appearance.rounding.full
topRightRadius: root.implicitHeight / 6 topRightRadius: root.implicitHeight / 6
} }
CustomRect { CustomRect {
anchors.bottom: parent.bottom anchors.bottom: parent.bottom
anchors.bottomMargin: root.implicitHeight / 6
anchors.right: parent.right anchors.right: parent.right
anchors.top: parent.top anchors.top: parent.top
anchors.topMargin: root.implicitHeight / 6
bottomLeftRadius: root.implicitHeight / 6 bottomLeftRadius: root.implicitHeight / 6
color: DynamicColors.tPalette.m3surfaceContainerHighest color: DynamicColors.tPalette.m3surfaceContainer
implicitWidth: parent.width - root.handle.x - root.handle.implicitWidth - root.implicitHeight / 6 implicitWidth: parent.width - root.handle.x - root.handle.implicitWidth - root.implicitHeight / 2
radius: root.implicitHeight / 6 radius: Appearance.rounding.full
topLeftRadius: root.implicitHeight / 6 topLeftRadius: root.implicitHeight / 6
} }
} }
handle: CustomRect { handle: CustomRect {
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
color: root.color color: root.color
implicitHeight: root.implicitHeight implicitHeight: 15
implicitWidth: root.implicitHeight / 4.5 implicitWidth: 5
radius: Appearance.rounding.full radius: Appearance.rounding.full
x: root.visualPosition * root.availableWidth - implicitWidth / 2 x: root.visualPosition * root.availableWidth - implicitWidth / 2
+5 -6
View File
@@ -28,7 +28,6 @@ RowLayout {
CustomTextField { CustomTextField {
id: textField id: textField
color: root.enabled ? DynamicColors.palette.m3onSurface : Qt.alpha(DynamicColors.palette.m3onSurface, 0.5)
implicitHeight: upButton.implicitHeight implicitHeight: upButton.implicitHeight
inputMethodHints: Qt.ImhFormattedNumbersOnly inputMethodHints: Qt.ImhFormattedNumbersOnly
leftPadding: Appearance.padding.normal leftPadding: Appearance.padding.normal
@@ -37,7 +36,7 @@ RowLayout {
text: root.isEditing ? text : root.displayText text: root.isEditing ? text : root.displayText
background: CustomRect { background: CustomRect {
color: root.enabled ? DynamicColors.tPalette.m3surfaceContainerHigh : DynamicColors.tPalette.m3surfaceContainerLow color: DynamicColors.tPalette.m3surfaceContainerHigh
implicitWidth: 100 implicitWidth: 100
radius: Appearance.rounding.full radius: Appearance.rounding.full
} }
@@ -86,7 +85,7 @@ RowLayout {
CustomRect { CustomRect {
id: upButton id: upButton
color: root.enabled ? DynamicColors.palette.m3primary : DynamicColors.layer(DynamicColors.palette.m3surfaceContainerHighest, 1) color: DynamicColors.palette.m3primary
implicitHeight: upIcon.implicitHeight + Appearance.padding.small * 2 implicitHeight: upIcon.implicitHeight + Appearance.padding.small * 2
implicitWidth: implicitHeight implicitWidth: implicitHeight
radius: Appearance.rounding.full radius: Appearance.rounding.full
@@ -114,13 +113,13 @@ RowLayout {
id: upIcon id: upIcon
anchors.centerIn: parent anchors.centerIn: parent
color: root.enabled ? DynamicColors.palette.m3onPrimary : Qt.alpha(DynamicColors.palette.m3onSurface, 0.5) color: DynamicColors.palette.m3onPrimary
text: "keyboard_arrow_up" text: "keyboard_arrow_up"
} }
} }
CustomRect { CustomRect {
color: root.enabled ? DynamicColors.palette.m3primary : DynamicColors.layer(DynamicColors.palette.m3surfaceContainerHighest, 1) color: DynamicColors.palette.m3primary
implicitHeight: downIcon.implicitHeight + Appearance.padding.small * 2 implicitHeight: downIcon.implicitHeight + Appearance.padding.small * 2
implicitWidth: implicitHeight implicitWidth: implicitHeight
radius: Appearance.rounding.full radius: Appearance.rounding.full
@@ -148,7 +147,7 @@ RowLayout {
id: downIcon id: downIcon
anchors.centerIn: parent anchors.centerIn: parent
color: root.enabled ? DynamicColors.palette.m3onPrimary : Qt.alpha(DynamicColors.palette.m3onSurface, 0.5) color: DynamicColors.palette.m3onPrimary
text: "keyboard_arrow_down" text: "keyboard_arrow_down"
} }
} }
+11 -7
View File
@@ -35,10 +35,14 @@ Row {
} }
function openDropdown(): void { function openDropdown(): void {
if (root.disabled)
return;
SettingsDropdowns.open(menu, root); SettingsDropdowns.open(menu, root);
} }
function toggleDropdown(): void { function toggleDropdown(): void {
if (root.disabled)
return;
SettingsDropdowns.toggle(menu, root); SettingsDropdowns.toggle(menu, root);
} }
@@ -51,7 +55,7 @@ Row {
CustomRect { CustomRect {
bottomRightRadius: Appearance.rounding.small / 2 bottomRightRadius: Appearance.rounding.small / 2
color: !root.enabled ? root.disabledColor : root.color color: root.disabled ? root.disabledColor : root.color
implicitHeight: expandBtn.implicitHeight implicitHeight: expandBtn.implicitHeight
implicitWidth: textRow.implicitWidth + root.horizontalPadding * 2 implicitWidth: textRow.implicitWidth + root.horizontalPadding * 2
radius: implicitHeight / 2 * Math.min(1, Appearance.rounding.scale) radius: implicitHeight / 2 * Math.min(1, Appearance.rounding.scale)
@@ -65,7 +69,7 @@ Row {
} }
color: root.textColor color: root.textColor
disabled: !root.enabled disabled: root.disabled
rect.bottomRightRadius: parent.bottomRightRadius rect.bottomRightRadius: parent.bottomRightRadius
rect.topRightRadius: parent.topRightRadius rect.topRightRadius: parent.topRightRadius
} }
@@ -82,7 +86,7 @@ Row {
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
animate: true animate: true
color: !root.enabled ? root.disabledTextColor : root.textColor color: root.disabled ? root.disabledTextColor : root.textColor
fill: 1 fill: 1
text: root.active?.activeIcon ?? root.fallbackIcon text: root.active?.activeIcon ?? root.fallbackIcon
} }
@@ -94,7 +98,7 @@ Row {
Layout.preferredWidth: implicitWidth Layout.preferredWidth: implicitWidth
animate: true animate: true
clip: true clip: true
color: !root.enabled ? root.disabledTextColor : root.textColor color: root.disabled ? root.disabledTextColor : root.textColor
text: root.active?.activeText ?? root.fallbackText text: root.active?.activeText ?? root.fallbackText
Behavior on Layout.preferredWidth { Behavior on Layout.preferredWidth {
@@ -112,7 +116,7 @@ Row {
property real rad: root.expanded ? implicitHeight / 2 * Math.min(1, Appearance.rounding.scale) : Appearance.rounding.small / 2 property real rad: root.expanded ? implicitHeight / 2 * Math.min(1, Appearance.rounding.scale) : Appearance.rounding.small / 2
bottomLeftRadius: rad bottomLeftRadius: rad
color: !root.enabled ? root.disabledColor : root.color color: root.disabled ? root.disabledColor : root.color
implicitHeight: expandIcon.implicitHeight + root.verticalPadding * 2 implicitHeight: expandIcon.implicitHeight + root.verticalPadding * 2
implicitWidth: implicitHeight implicitWidth: implicitHeight
radius: implicitHeight / 2 * Math.min(1, Appearance.rounding.scale) radius: implicitHeight / 2 * Math.min(1, Appearance.rounding.scale)
@@ -131,7 +135,7 @@ Row {
} }
color: root.textColor color: root.textColor
disabled: !root.enabled disabled: root.disabled
rect.bottomLeftRadius: parent.bottomLeftRadius rect.bottomLeftRadius: parent.bottomLeftRadius
rect.topLeftRadius: parent.topLeftRadius rect.topLeftRadius: parent.topLeftRadius
} }
@@ -141,7 +145,7 @@ Row {
anchors.centerIn: parent anchors.centerIn: parent
anchors.horizontalCenterOffset: root.expanded ? 0 : -Math.floor(root.verticalPadding / 4) anchors.horizontalCenterOffset: root.expanded ? 0 : -Math.floor(root.verticalPadding / 4)
color: !root.enabled ? root.disabledTextColor : root.textColor color: root.disabled ? root.disabledTextColor : root.textColor
rotation: root.expanded ? 180 : 0 rotation: root.expanded ? 180 : 0
text: "expand_more" text: "expand_more"
+4 -4
View File
@@ -12,7 +12,7 @@ Switch {
implicitWidth: implicitIndicatorWidth implicitWidth: implicitIndicatorWidth
indicator: CustomRect { indicator: CustomRect {
color: root.checked && root.enabled ? DynamicColors.palette.m3primary : DynamicColors.layer(DynamicColors.palette.m3surfaceContainerHighest, root.cLayer) color: root.checked ? DynamicColors.palette.m3primary : DynamicColors.layer(DynamicColors.palette.m3surfaceContainerHighest, root.cLayer)
implicitHeight: 13 + 7 * 2 implicitHeight: 13 + 7 * 2
implicitWidth: implicitHeight * 1.7 implicitWidth: implicitHeight * 1.7
radius: Appearance.rounding.full radius: Appearance.rounding.full
@@ -21,7 +21,7 @@ Switch {
readonly property real nonAnimWidth: root.pressed ? implicitHeight * 1.3 : implicitHeight readonly property real nonAnimWidth: root.pressed ? implicitHeight * 1.3 : implicitHeight
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
color: root.checked && root.enabled ? DynamicColors.palette.m3onPrimary : DynamicColors.layer(DynamicColors.palette.m3outline, root.cLayer + 1) color: root.checked ? DynamicColors.palette.m3onPrimary : DynamicColors.layer(DynamicColors.palette.m3outline, root.cLayer + 1)
implicitHeight: parent.implicitHeight - 10 implicitHeight: parent.implicitHeight - 10
implicitWidth: nonAnimWidth implicitWidth: nonAnimWidth
radius: Appearance.rounding.full radius: Appearance.rounding.full
@@ -38,7 +38,7 @@ Switch {
CustomRect { CustomRect {
anchors.fill: parent anchors.fill: parent
color: root.checked && root.enabled ? DynamicColors.palette.m3primary : DynamicColors.palette.m3onSurface color: root.checked ? DynamicColors.palette.m3primary : DynamicColors.palette.m3onSurface
opacity: root.pressed ? 0.1 : root.hovered ? 0.08 : 0 opacity: root.pressed ? 0.1 : root.hovered ? 0.08 : 0
radius: parent.radius radius: parent.radius
@@ -114,7 +114,7 @@ Switch {
fillColor: "transparent" fillColor: "transparent"
startX: icon.start1.x startX: icon.start1.x
startY: icon.start1.y startY: icon.start1.y
strokeColor: root.checked && root.enabled ? DynamicColors.palette.m3primary : DynamicColors.palette.m3surfaceContainerHighest strokeColor: root.checked ? DynamicColors.palette.m3primary : DynamicColors.palette.m3surfaceContainerHighest
strokeWidth: Appearance.font.size.larger * 0.15 strokeWidth: Appearance.font.size.larger * 0.15
Behavior on strokeColor { Behavior on strokeColor {
-5
View File
@@ -59,8 +59,6 @@ JsonObject {
} }
property int rounding: 8 property int rounding: 8
property int smoothing: 32 property int smoothing: 32
property Tray tray: Tray {
}
component Popouts: JsonObject { component Popouts: JsonObject {
property bool activeWindow: true property bool activeWindow: true
@@ -71,7 +69,4 @@ JsonObject {
property bool tray: true property bool tray: true
property bool upower: true property bool upower: true
} }
component Tray: JsonObject {
property int trayIconSize: 24
}
} }
+16 -12
View File
@@ -22,6 +22,7 @@ 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
@@ -100,9 +101,6 @@ Singleton {
border: barConfig.border, border: barConfig.border,
smoothing: barConfig.smoothing, smoothing: barConfig.smoothing,
height: barConfig.height, height: barConfig.height,
tray: {
trayIconSize: barConfig.tray.trayIconSize
},
popouts: { popouts: {
tray: barConfig.popouts.tray, tray: barConfig.popouts.tray,
audio: barConfig.popouts.audio, audio: barConfig.popouts.audio,
@@ -143,7 +141,8 @@ Singleton {
launcher: serializeLauncher(), launcher: serializeLauncher(),
colors: serializeColors(), colors: serializeColors(),
dock: serializeDock(), dock: serializeDock(),
screenshot: serializeScreenshot() screenshot: serializeScreenshot(),
plugins: serializePlugins()
}; };
} }
@@ -217,10 +216,6 @@ Singleton {
}, },
idle: { idle: {
timeouts: general.idle.timeouts timeouts: general.idle.timeouts
},
battery: {
popupThresholds: general.battery.popupThresholds,
critPerc: general.battery.critPerc
} }
}; };
} }
@@ -296,14 +291,21 @@ 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,
mode: screenshot.mode, mode: screenshot.mode,
radius: screenshot.radius, corner_radius: screenshot.corner_radius,
shadow: screenshot.shadow, drop_shadow: screenshot.drop_shadow,
rounding: screenshot.rounding, rounded_corners: screenshot.rounded_corners,
shadow_blur: screenshot.shadow_blur, shadow_blur_radius: screenshot.shadow_blur_radius,
shadow_color: screenshot.shadow_color, shadow_color: screenshot.shadow_color,
shadow_offset_x: screenshot.shadow_offset_x, shadow_offset_x: screenshot.shadow_offset_x,
shadow_offset_y: screenshot.shadow_offset_y shadow_offset_y: screenshot.shadow_offset_y
@@ -465,6 +467,8 @@ 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 {
-4
View File
@@ -115,10 +115,6 @@ Singleton {
Config.save(); Config.save();
} }
function swapRG(c: color): color {
return Qt.rgba(c.g, c.r, c.b, c.a);
}
Component.onCompleted: debounceTimer.triggered() Component.onCompleted: debounceTimer.triggered()
Connections { Connections {
-13
View File
@@ -4,8 +4,6 @@ import Quickshell
JsonObject { JsonObject {
property Apps apps: Apps { property Apps apps: Apps {
} }
property Battery battery: Battery {
}
property Color color: Color { property Color color: Color {
} }
property string dateFormat: "ddd d MMM - hh:mm:ss" property string dateFormat: "ddd d MMM - hh:mm:ss"
@@ -21,17 +19,6 @@ JsonObject {
property list<string> playback: ["mpv"] property list<string> playback: ["mpv"]
property list<string> terminal: ["kitty"] property list<string> terminal: ["kitty"]
} }
component Battery: JsonObject {
property int critPerc: 5
property list<var> popupThresholds: [
{
perc: 20,
name: qsTr("Low battery"),
message: qsTr("Battery is low"),
icon: "battery_android_frame_2"
},
]
}
component Color: JsonObject { component Color: JsonObject {
property int hyprsunsetTemp: 5000 property int hyprsunsetTemp: 5000
property string mode: "dark" property string mode: "dark"
+11
View File
@@ -0,0 +1,11 @@
import Quickshell.Io
JsonObject {
property bool enabled: false
property list<var> entries: [
{
id: "Plugin",
enabled: false
},
]
}
+4 -4
View File
@@ -1,12 +1,12 @@
import Quickshell.Io import Quickshell.Io
JsonObject { JsonObject {
property real corner_radius: 12.0
property bool drop_shadow: true
property bool enable_pp: true property bool enable_pp: true
property string mode: "manual" property string mode: "manual"
property real radius: 12.0 property bool rounded_corners: false
property bool rounding: false property real shadow_blur_radius: 22.0
property bool shadow: true
property real shadow_blur: 22.0
property list<int> shadow_color: [0, 0, 0, 160] property list<int> shadow_color: [0, 0, 0, 160]
property real shadow_offset_x: 5.0 property real shadow_offset_x: 5.0
property real shadow_offset_y: 5.0 property real shadow_offset_y: 5.0
-70
View File
@@ -1,70 +0,0 @@
import Quickshell
import Quickshell.Services.UPower
import QtQuick
import ZShell
import qs.Config
import qs.Components.Toast
import qs.Helpers
Scope {
id: root
readonly property list<var> popupThresholds: [...Config.general.battery.popupThresholds].sort((a, b) => b.perc - a.perc)
function nearestThresholdAbove(p: real): var {
const thresholds = [...root.popupThresholds];
for (const perc of thresholds) {
if (p < perc.perc)
return perc;
}
return null;
}
Connections {
function onOnBatteryChanged(): void {
if (!Battery.ready)
return;
if (Battery.onBattery) {
if (Config.utilities.toasts.chargingChanged)
Toaster.toast(qsTr("Charger unplugged"), qsTr("Battery is discharging"), "power_off");
const p = Battery.currentPerc * 100;
const perc = root.nearestThresholdAbove(p);
if (perc)
Toaster.toast(perc.title ?? qsTr("Battery warning"), perc.message ?? qsTr("Battery is low"), perc.icon ?? "battery_android_alert", perc.critical ? Toast.Error : Toast.Warning);
} else {
if (Config.utilities.toasts.chargingChanged)
Toaster.toast(qsTr("Charger plugged in"), qsTr("Battery is charging"), "power");
}
}
target: Battery
}
Connections {
function onCurrentPercChanged(): void {
if (!Battery.onBattery)
return;
const p = Battery.currentPerc * 100;
for (const perc of root.popupThresholds) {
if (p == perc.perc) {
Toaster.toast(perc.title ?? qsTr("Battery warning"), perc.message ?? qsTr("Battery perc is low"), perc.icon ?? "battery_android_alert", perc.critical ? Toast.Error : Toast.Warning);
}
}
}
function onReadyChanged(): void {
if (!Battery.ready)
return;
const p = Battery.currentPerc * 100;
const perc = root.nearestThresholdAbove(p);
if (perc)
Toaster.toast(perc.title ?? qsTr("Battery warning"), perc.message ?? qsTr("Battery is low"), perc.icon ?? "battery_android_alert", perc.critical ? Toast.Error : Toast.Warning);
}
target: Battery
}
}
+57 -32
View File
@@ -179,8 +179,6 @@ Singleton {
property string appIcon property string appIcon
property string appName property string appName
property string body property string body
property string cachedImageSource: ""
property bool cachingImage: false
property bool closed property bool closed
readonly property Connections conn: Connections { readonly property Connections conn: Connections {
function onActionsChanged(): void { function onActionsChanged(): void {
@@ -216,9 +214,9 @@ Singleton {
} }
function onImageChanged(): void { function onImageChanged(): void {
notif.imageSource = notif.notification.image || ""; notif.image = notif.notification.image;
notif.image = notif.imageSource; if (notif.notification?.image)
notif.cacheImageIfNeeded(); notif.dummyImageLoader.active = true;
} }
function onResidentChanged(): void { function onResidentChanged(): void {
@@ -235,12 +233,60 @@ Singleton {
target: notif.notification target: notif.notification
} }
readonly property LazyLoader dummyImageLoader: LazyLoader {
active: false
PanelWindow {
color: "transparent"
implicitHeight: Config.notifs.sizes.image
implicitWidth: Config.notifs.sizes.image
mask: Region {
}
Image {
function tryCache(): void {
if (status !== Image.Ready || width != Config.notifs.sizes.image || height != Config.notifs.sizes.image)
return;
const cacheKey = notif.appName + notif.summary + notif.id;
let h1 = 0xdeadbeef, h2 = 0x41c6ce57, ch;
for (let i = 0; i < cacheKey.length; i++) {
ch = cacheKey.charCodeAt(i);
h1 = Math.imul(h1 ^ ch, 2654435761);
h2 = Math.imul(h2 ^ ch, 1597334677);
}
h1 = Math.imul(h1 ^ (h1 >>> 16), 2246822507);
h1 ^= Math.imul(h2 ^ (h2 >>> 13), 3266489909);
h2 = Math.imul(h2 ^ (h2 >>> 16), 2246822507);
h2 ^= Math.imul(h1 ^ (h1 >>> 13), 3266489909);
const hash = (h2 >>> 0).toString(16).padStart(8, 0) + (h1 >>> 0).toString(16).padStart(8, 0);
const cache = `${Paths.notifimagecache}/${hash}.png`;
ZShellIo.saveItem(this, Qt.resolvedUrl(cache), () => {
notif.image = cache;
notif.dummyImageLoader.active = false;
});
}
anchors.fill: parent
asynchronous: true
cache: false
fillMode: Image.PreserveAspectCrop
opacity: 0
source: Qt.resolvedUrl(notif.image)
onHeightChanged: tryCache()
onStatusChanged: tryCache()
onWidthChanged: tryCache()
}
}
}
property real expireTimeout: 5 property real expireTimeout: 5
property bool hasActionIcons property bool hasActionIcons
property string id
property string image property string image
property string imageSource
property var locks: new Set() property var locks: new Set()
property string notifId
property Notification notification property Notification notification
property bool popup property bool popup
property bool resident property bool resident
@@ -283,26 +329,6 @@ Singleton {
} }
property int urgency: NotificationUrgency.Normal property int urgency: NotificationUrgency.Normal
function cacheImageIfNeeded(): void {
const source = imageSource;
if (!source || cachingImage)
return;
if (cachedImageSource === source)
return;
cachingImage = true;
ZShellIo.cacheImage(Qt.resolvedUrl(source), Paths.notifimagecache, (path, url) => {
cachedImageSource = source;
image = path;
cachingImage = false;
}, () => {
cachingImage = false;
});
}
function close(): void { function close(): void {
closed = true; closed = true;
if (locks.size === 0 && root.list.includes(this)) { if (locks.size === 0 && root.list.includes(this)) {
@@ -326,13 +352,14 @@ Singleton {
if (!notification) if (!notification)
return; return;
notifId = notification.id; id = notification.id;
summary = notification.summary; summary = notification.summary;
body = notification.body; body = notification.body;
appIcon = notification.appIcon; appIcon = notification.appIcon;
appName = notification.appName; appName = notification.appName;
imageSource = notification.image || ""; image = notification.image;
image = imageSource; if (notification?.image)
dummyImageLoader.active = true;
expireTimeout = notification.expireTimeout; expireTimeout = notification.expireTimeout;
urgency = notification.urgency; urgency = notification.urgency;
resident = notification.resident; resident = notification.resident;
@@ -342,8 +369,6 @@ Singleton {
text: a.text, text: a.text,
invoke: () => a.invoke() invoke: () => a.invoke()
})); }));
cacheImageIfNeeded();
} }
} }
} }
+107
View File
@@ -0,0 +1,107 @@
import Quickshell
import QtQuick
import QtQuick.Shapes
import qs.Components
import qs.Config
import qs.Modules as Modules
import qs.Modules.Notifications as Notifications
import qs.Modules.Notifications.Sidebar as Sidebar
import qs.Modules.Notifications.Sidebar.Utils as Utils
import qs.Modules.Dashboard as Dashboard
import qs.Modules.Osd as Osd
import qs.Modules.Launcher as Launcher
import qs.Modules.Resources as Resources
import qs.Modules.Drawing as Drawing
import qs.Modules.Settings as Settings
import qs.Modules.Dock as Dock
Shape {
id: root
required property Item bar
required property Panels panels
required property PersistentProperties visibilities
anchors.fill: parent
anchors.margins: Config.barConfig.border
anchors.topMargin: bar.implicitHeight
asynchronous: true
preferredRendererType: Shape.CurveRenderer
Drawing.Background {
startX: 0
startY: wrapper.y - rounding
wrapper: root.panels.drawing
}
Resources.Background {
startX: 0 - rounding
startY: 0
wrapper: root.panels.resources
}
Osd.Background {
startX: root.width - root.panels.sidebar.width
startY: (root.height - wrapper.height) / 2 - rounding
wrapper: root.panels.osd
}
Modules.Background {
invertBottomRounding: wrapper.x <= 0
rounding: root.panels.popouts.currentName.startsWith("updates") || root.panels.popouts.currentName.startsWith("audio") ? Appearance.rounding.normal : Appearance.rounding.smallest
startX: wrapper.x - rounding
startY: wrapper.y
wrapper: root.panels.popouts
}
Notifications.Background {
sidebar: sidebar
startX: root.width
startY: 0
wrapper: root.panels.notifications
}
Launcher.Background {
startX: (root.width - wrapper.width) / 2 - rounding
startY: root.height
wrapper: root.panels.launcher
}
Dashboard.Background {
startX: root.width - root.panels.dashboard.width - rounding
startY: 0
wrapper: root.panels.dashboard
}
Utils.Background {
sidebar: sidebar
startX: root.width
startY: root.height
wrapper: root.panels.utilities
}
Sidebar.Background {
id: sidebar
panels: root.panels
startX: root.width
startY: root.panels.notifications.height
wrapper: root.panels.sidebar
}
Settings.Background {
id: settings
startX: (root.width - wrapper.width) / 2 - rounding
startY: 0
wrapper: root.panels.settings
}
Dock.Background {
id: dock
startX: (root.width - wrapper.width) / 2 - rounding
startY: root.height
wrapper: root.panels.dock
}
}
-1
View File
@@ -23,7 +23,6 @@ Canvas {
ctx.save(); ctx.save();
ctx.lineWidth = root.penWidth; ctx.lineWidth = root.penWidth;
ctx.strokeStyle = root.penColor; ctx.strokeStyle = root.penColor;
ctx.lineJoin = "round";
ctx.lineCap = "round"; ctx.lineCap = "round";
ctx.beginPath(); ctx.beginPath();
ctx.moveTo(points[0].x, points[0].y); ctx.moveTo(points[0].x, points[0].y);
+3 -3
View File
@@ -22,20 +22,20 @@ CustomMouseArea {
} }
acceptedButtons: Qt.LeftButton | Qt.RightButton acceptedButtons: Qt.LeftButton | Qt.RightButton
enabled: z > 0 anchors.fill: root.visibilities.isDrawing ? parent : undefined
hoverEnabled: true hoverEnabled: true
visible: root.visibilities.isDrawing visible: root.visibilities.isDrawing
onPositionChanged: event => { onPositionChanged: event => {
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 (root.visibilities.isDrawing && (event.buttons & Qt.LeftButton)) {
root.drawing.points.push(Qt.point(x, y)); root.drawing.points.push(Qt.point(x, y));
root.drawing.requestPaint(); root.drawing.requestPaint();
return;
} }
if (!(event.buttons & Qt.LeftButton) && root.inLeftPanel(root.popout, x, y)) { if (root.inLeftPanel(root.popout, x, y)) {
root.z = -2; root.z = -2;
root.panels.drawing.expanded = true; root.panels.drawing.expanded = true;
} }
+57 -109
View File
@@ -2,21 +2,20 @@ import Quickshell
import QtQuick import QtQuick
import qs.Components import qs.Components
import qs.Config import qs.Config
import qs.Helpers
import qs.Modules as BarPopouts import qs.Modules as BarPopouts
Item { CustomMouseArea {
id: root id: root
required property Item bar required property Item bar
property bool dashboardShortcutActive property bool dashboardShortcutActive
property point dragStart
required property Drawing drawing required property Drawing drawing
required property DrawingInput input required property DrawingInput input
property bool osdShortcutActive property bool osdShortcutActive
required property Panels panels required property Panels panels
required property BarPopouts.Wrapper popouts required property BarPopouts.Wrapper popouts
required property ShellScreen screen required property ShellScreen screen
property bool singleGestureTriggered: false
property bool utilitiesShortcutActive property bool utilitiesShortcutActive
required property PersistentProperties visibilities required property PersistentProperties visibilities
@@ -53,123 +52,78 @@ Item {
} }
anchors.fill: parent anchors.fill: parent
cursorShape: (pressed && dragStart.y < bar.implicitHeight) ? Qt.ClosedHandCursor : undefined
hoverEnabled: true
propagateComposedEvents: false
DragHandler { onContainsMouseChanged: {
id: singleHandler if (!containsMouse) {
if (!osdShortcutActive) {
cursorShape: (active && centroid.pressPosition.y < root.bar.implicitHeight) ? Qt.ClosedHandCursor : undefined visibilities.osd = false;
dragThreshold: 0 root.panels.osd.hovered = false;
grabPermissions: PointerHandler.CanTakeOverFromHandlersOfDifferentType | PointerHandler.ApprovesTakeOverByAnything
maximumPointCount: 1
minimumPointCount: 1
target: null
onActiveChanged: {
if (!active)
root.singleGestureTriggered = false;
}
onCentroidChanged: {
const x = centroid.position.x;
const y = centroid.position.y;
const dragX = x - centroid.pressPosition.x;
const dragY = y - centroid.pressPosition.y;
if (centroid.pressPosition.y >= root.screen.height - Config.barConfig.border && dragY < -200)
root.visibilities.launcher = true;
if (root.singleGestureTriggered)
return;
if (centroid.pressPosition.y < root.bar.implicitHeight) {
if (dragY > 20) {
root.visibilities.settings = true;
root.singleGestureTriggered = true;
} else if (dragY < -20) {
root.visibilities.settings = false;
root.singleGestureTriggered = true;
}
} }
if (!Config.dock.hoverToReveal && centroid.pressPosition.y > root.screen.height - root.bar.implicitHeight) if (!popouts.currentName.startsWith("traymenu")) {
if (dragY < -10) { popouts.hasCurrent = false;
root.visibilities.dock = true;
root.singleGestureTriggered = true;
}
if (centroid.pressPosition.x > root.screen.width - Config.barConfig.border && centroid.pressPosition.y < (root.screen.height / 2) && dragX < -20) {
root.visibilities.sidebar = true;
root.singleGestureTriggered = true;
} }
if (centroid.pressPosition.x >= root.screen.width - Config.barConfig.border && centroid.pressPosition.y > (root.screen.height / 2) && dragX < -20) { if (Config.barConfig.autoHide)
Hypr.dispatch(`hl.dsp.focus({ workspace = 'r+1', on_current_monitor = true })`); bar.isHovered = false;
root.singleGestureTriggered = true;
}
if (centroid.pressPosition.x <= Config.barConfig.border && dragX > 20) {
Hypr.dispatch(`hl.dsp.focus({ workspace = 'r-1', on_current_monitor = true })`);
root.singleGestureTriggered = true;
}
} }
} }
onPositionChanged: event => {
const x = event.x;
const y = event.y;
const dragX = x - dragStart.x;
const dragY = y - dragStart.y;
HoverHandler { if (root.visibilities.isDrawing && !root.inLeftPanel(root.panels.drawing, x, y)) {
id: hoverHandler // root.input.z = 2;
root.panels.drawing.expanded = false;
}
onHoveredChanged: { if (!visibilities.bar && Config.barConfig.autoHide && y < bar.implicitHeight)
if (!hovered) { bar.isHovered = true;
if (!root.osdShortcutActive) {
root.visibilities.osd = false;
root.panels.osd.hovered = false;
}
if (!root.popouts.currentName.startsWith("traymenu")) { if (pressed && dragStart.y < bar.implicitHeight) {
root.popouts.hasCurrent = false; if (dragY > 20)
} visibilities.settings = true;
else if (dragY < -20)
visibilities.settings = false;
}
if (Config.barConfig.autoHide) if (Config.dock.hoverToReveal && pressed && dragStart.y > root.screen.height - root.bar.implicitHeight)
root.bar.isHovered = false; if (dragY < -10)
visibilities.dock = true;
if (panels.sidebar.width === 0) {
const showOsd = inRightPanel(panels.osdWrapper, x, y);
if (showOsd) {
osdShortcutActive = false;
root.panels.osd.hovered = true;
}
} else {
const outOfSidebar = x < width - panels.sidebar.width;
const showOsd = outOfSidebar && inRightPanel(panels.osdWrapper, x, y);
if (!osdShortcutActive) {
visibilities.osd = showOsd;
root.panels.osd.hovered = showOsd;
} else if (showOsd) {
osdShortcutActive = false;
root.panels.osd.hovered = true;
} }
} }
onPointChanged: {
const x = point.position.x;
const y = point.position.y;
if (root.visibilities.isDrawing && !root.inLeftPanel(root.panels.drawing, x, y)) { if (Config.dock.enable && !Config.dock.hoverToReveal && !visibilities.dock && !visibilities.launcher && inBottomPanel(panels.dock, x, y))
root.input.z = 2; visibilities.dock = true;
root.panels.drawing.expanded = false;
}
if (!root.visibilities.bar && Config.barConfig.autoHide && y < root.bar.implicitHeight) if (y < root.bar.implicitHeight) {
root.bar.isHovered = true; root.bar.checkPopout(x);
if (root.panels.sidebar.width === 0) {
const showOsd = root.inRightPanel(root.panels.osdWrapper, x, y);
if (showOsd) {
root.osdShortcutActive = false;
root.panels.osd.hovered = true;
}
} else {
const outOfSidebar = x < root.width - root.panels.sidebar.width;
const showOsd = outOfSidebar && root.inRightPanel(root.panels.osdWrapper, x, y);
if (!root.osdShortcutActive) {
root.visibilities.osd = showOsd;
root.panels.osd.hovered = showOsd;
} else if (showOsd) {
root.osdShortcutActive = false;
root.panels.osd.hovered = true;
}
}
if (Config.dock.enable && Config.dock.hoverToReveal && !root.visibilities.dock && !root.visibilities.launcher && root.inBottomPanel(root.panels.dock, x, y))
root.visibilities.dock = true;
if (y < root.bar.implicitHeight)
root.bar.checkPopout(x);
} }
} }
onPressed: event => dragStart = Qt.point(event.x, event.y)
Connections { Connections {
function onDashboardChanged() { function onDashboardChanged() {
@@ -178,8 +132,6 @@ Item {
if (!inDashboardArea) { if (!inDashboardArea) {
root.dashboardShortcutActive = true; root.dashboardShortcutActive = true;
} }
if (root.panels.launcher.x + root.panels.launcher.width > root.panels.dashboardWrapper.x)
root.visibilities.launcher = false;
root.visibilities.settings = false; root.visibilities.settings = false;
root.visibilities.sidebar = false; root.visibilities.sidebar = false;
@@ -209,10 +161,6 @@ Item {
} }
if (root.visibilities.launcher) { if (root.visibilities.launcher) {
if (root.panels.dashboardWrapper.x < root.panels.launcher.x + root.panels.launcher.width) {
root.visibilities.dashboard = false;
}
root.visibilities.dock = false; root.visibilities.dock = false;
root.visibilities.settings = false; root.visibilities.settings = false;
} }
+13 -27
View File
@@ -35,7 +35,7 @@ Variants {
property var root: Quickshell.shellDir property var root: Quickshell.shellDir
WlrLayershell.exclusionMode: ExclusionMode.Ignore WlrLayershell.exclusionMode: ExclusionMode.Ignore
// WlrLayershell.keyboardFocus: visibilities.dock || visibilities.launcher || visibilities.sidebar || visibilities.dashboard || visibilities.settings || visibilities.resources ? WlrKeyboardFocus.OnDemand : WlrKeyboardFocus.None WlrLayershell.keyboardFocus: visibilities.dock || visibilities.launcher || visibilities.sidebar || visibilities.dashboard || visibilities.settings || visibilities.resources ? WlrKeyboardFocus.OnDemand : WlrKeyboardFocus.None
color: "transparent" color: "transparent"
contentItem.focus: true contentItem.focus: true
mask: visibilities.isDrawing ? null : region mask: visibilities.isDrawing ? null : region
@@ -229,7 +229,6 @@ Variants {
id: notifsBg id: notifsBg
panel: panels.notifications panel: panels.notifications
radius: Appearance.rounding.normal
} }
PanelBg { PanelBg {
@@ -305,34 +304,22 @@ Variants {
} }
} }
Loader { Drawing {
id: drawingLoader id: drawing
active: visibilities.isDrawing
anchors.fill: parent anchors.fill: parent
z: 2 z: 2
sourceComponent: Drawing {
id: drawing
}
} }
Loader { DrawingInput {
id: inputLoader id: input
active: visibilities.isDrawing bar: bar
anchors.fill: parent drawing: drawing
panels: panels
popout: panels.drawing
visibilities: visibilities
z: 2 z: 2
sourceComponent: DrawingInput {
id: input
bar: bar
drawing: drawingLoader.item
panels: panels
popout: panels.drawing
visibilities: visibilities
}
} }
Interactions { Interactions {
@@ -340,9 +327,8 @@ Variants {
anchors.fill: parent anchors.fill: parent
bar: bar bar: bar
drawing: drawingLoader.item drawing: drawing
enabled: true input: input
input: inputLoader.item
panels: panels panels: panels
popouts: panels.popouts popouts: panels.popouts
screen: scope.modelData screen: scope.modelData
@@ -353,7 +339,7 @@ Variants {
id: panels id: panels
bar: bar bar: bar
drawingItem: drawingLoader.item drawingItem: drawing
screen: scope.modelData screen: scope.modelData
visibilities: visibilities visibilities: visibilities
+18
View File
@@ -0,0 +1,18 @@
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
@@ -0,0 +1,17 @@
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
}
}
-32
View File
@@ -1,32 +0,0 @@
pragma Singleton
import Quickshell
import Quickshell.Services.UPower
import qs.Config
Singleton {
id: root
readonly property var colors: {
if (deviceState === UPowerDeviceState.Charging || deviceState === UPowerDeviceState.FullyCharged)
return {
fg: DynamicColors.swapRG(DynamicColors.palette.m3error),
bg: DynamicColors.swapRG(DynamicColors.palette.m3onError)
};
else if (currentPerc <= 0.2)
return {
fg: DynamicColors.palette.m3error,
bg: DynamicColors.palette.m3onError
};
else
return {
fg: DynamicColors.palette.m3onSurface,
bg: DynamicColors.palette.m3surface
};
}
readonly property real currentPerc: UPower.displayDevice.percentage
readonly property var deviceState: UPower.displayDevice.state
readonly property bool isLaptop: UPower.displayDevice.isLaptopBattery
readonly property bool onBattery: UPower.onBattery
readonly property bool ready: UPower.displayDevice.ready
}
+1 -1
View File
@@ -36,7 +36,7 @@ Singleton {
PersistentProperties { PersistentProperties {
id: props id: props
property bool enabled: Hypr.options.animations.enabled === 0 property bool enabled: Hypr.options["animations:enabled"] === 0
reloadableId: "gamemode" reloadableId: "gamemode"
} }
+1
View File
@@ -158,5 +158,6 @@ Singleton {
HyprExtras { HyprExtras {
id: extras id: extras
} }
} }
+59 -11
View File
@@ -2,13 +2,12 @@ pragma Singleton
import Quickshell import Quickshell
import QtQuick import QtQuick
import ZShell.Services
import qs.Config import qs.Config
Singleton { Singleton {
id: root id: root
readonly property bool enabled: service.enabled property bool enabled
readonly property int end: Config.general.color.scheduleHyprsunsetEnd readonly property int end: Config.general.color.scheduleHyprsunsetEnd
property bool manualToggle: false property bool manualToggle: false
readonly property int start: Config.general.color.scheduleHyprsunsetStart readonly property int start: Config.general.color.scheduleHyprsunsetStart
@@ -18,20 +17,69 @@ Singleton {
if (!Config.general.color.scheduleHyprsunset) if (!Config.general.color.scheduleHyprsunset)
return; return;
service.apply(); var now = new Date();
if (now.getHours() >= root.start || now.getHours() < root.end) {
root.startNightLight(root.temp);
} else {
root.stopNightLight();
}
}
function startNightLight(temp: int): void {
Quickshell.execDetached(["hyprctl", "hyprsunset", "temperature", `${temp}`]);
root.enabled = true;
}
function stopNightLight(): void {
Quickshell.execDetached(["hyprctl", "hyprsunset", "identity"]);
root.enabled = false;
} }
function toggleNightLight(): void { function toggleNightLight(): void {
service.manualToggle = true; if (enabled)
service.toggle(); stopNightLight();
else
startNightLight(temp);
} }
HyprsunsetManager { onManualToggleChanged: {
id: service if (root.manualToggle)
manualTimer.start();
}
activeAuto: Config.general.color.scheduleHyprsunset Timer {
endTime: root.end id: manualTimer
startTime: root.start
temp: root.temp interval: 60000 * 60
repeat: false
running: false
onTriggered: {
root.manualToggle = false;
}
}
Timer {
interval: 5000
repeat: true
running: true
triggeredOnStart: true
onTriggered: {
if (!Config.general.color.scheduleHyprsunset)
return;
if (root.manualToggle)
return;
var now = new Date();
if (now.getHours() >= root.start || now.getHours() < root.end) {
if (!root.enabled)
root.startNightLight(root.temp);
} else {
if (root.enabled)
root.stopNightLight();
}
}
} }
} }
+3 -17
View File
@@ -26,23 +26,16 @@ MouseArea {
return (bc.pinned - ac.pinned) || ((bc.fullscreen !== 0) - (ac.fullscreen !== 0)) || (bc.floating - ac.floating); return (bc.pinned - ac.pinned) || ((bc.fullscreen !== 0) - (ac.fullscreen !== 0)) || (bc.floating - ac.floating);
}); });
} }
readonly property int cornerRadius: Hypr.options.decoration.rounding
property real ex: screen.width property real ex: screen.width
property real ey: screen.height property real ey: screen.height
required property LazyLoader loader required property LazyLoader loader
property bool onClient property bool onClient
property real realBorderWidth: onClient ? (Hypr.options.general.border_size ?? 1) : 2 property real realBorderWidth: onClient ? (Hypr.options["general:border_size"] ?? 1) : 2
property real realRounding: onClient ? (Hypr.options.decoration.rounding ?? 0) : 0 property real realRounding: onClient ? (Hypr.options["decoration:rounding"] ?? 0) : 0
property real rsx: Math.min(sx, ex) property real rsx: Math.min(sx, ex)
property real rsy: Math.min(sy, ey) property real rsy: Math.min(sy, ey)
readonly property real scaleRatio: Hypr.monitorFor(screen).scale
required property ShellScreen screen required property ShellScreen screen
property real sh: Math.abs(sy - ey) property real sh: Math.abs(sy - ey)
readonly property color shadowColor: Hypr.options.decoration.shadow.color
readonly property bool shadowEnabled: Hypr.options.decoration.shadow.enabled
readonly property var shadowOffset: Hypr.options.decoration.shadow.offset
readonly property int shadowRange: Hypr.options.decoration.shadow.range
readonly property int shadowRenderPower: Hypr.options.decoration.shadow.render_power
property real ssx property real ssx
property real ssy property real ssy
property real sw: Math.abs(sx - ex) property real sw: Math.abs(sx - ex)
@@ -73,14 +66,7 @@ MouseArea {
function save(): void { function save(): void {
const tmpfile = Qt.resolvedUrl(`/tmp/zshell-picker-${Quickshell.processId}-${Date.now()}.png`); const tmpfile = Qt.resolvedUrl(`/tmp/zshell-picker-${Quickshell.processId}-${Date.now()}.png`);
const rounding = root.cornerRadius > 0; const cmd = Config.screenshot.enable_pp ? ["zshell-img-tools", "--image"] : ["swappy", "-f"];
const shadow_blur = root.shadowRange / root.shadowRenderPower;
const r = Math.floor(root.shadowColor.r * 256);
const g = Math.floor(root.shadowColor.g * 256);
const b = Math.floor(root.shadowColor.b * 256);
const a = Math.floor(root.shadowColor.a * 256);
const args = Config.screenshot.mode === "auto" ? ["--rounding", `${rounding}`, "--radius", root.cornerRadius, "--shadow", root.shadowEnabled, "--shadow-blur", `${shadow_blur}`, "--shadow-color", `${r},${g},${b},${a}`, "--shadow-offset-x", root.shadowOffset[0], "--shadow-offset-y", root.shadowOffset[1]] : [];
const cmd = Config.screenshot.enable_pp ? ["zshell-img-tools", "--scale", root.scaleRatio, ...args, "--image"] : ["swappy", "-f"];
ZShellIo.saveItem(screencopy, tmpfile, Qt.rect(Math.ceil(rsx), Math.ceil(rsy), Math.floor(sw), Math.floor(sh)), path => Quickshell.execDetached([...cmd, path])); ZShellIo.saveItem(screencopy, tmpfile, Qt.rect(Math.ceil(rsx), Math.ceil(rsy), Math.floor(sw), Math.floor(sh)), path => Quickshell.execDetached([...cmd, path]));
closeAnim.start(); closeAnim.start();
} }
-1
View File
@@ -17,7 +17,6 @@ Singleton {
property var disks: [] property var disks: []
property real gpuMemTotal: 0 property real gpuMemTotal: 0
property real gpuMemUsed property real gpuMemUsed
property string gpuName
property real gpuPerc property real gpuPerc
property real gpuTemp property real gpuTemp
readonly property string gpuType: Config.services.gpuType.toUpperCase() || autoGpuType readonly property string gpuType: Config.services.gpuType.toUpperCase() || autoGpuType
+26
View File
@@ -0,0 +1,26 @@
pragma Singleton
import Quickshell
import Quickshell.Services.UPower
Singleton {
id: root
readonly property real batteryPercent: UPower.displayDevice.percentage
readonly property list<UPowerDevice> devices: UPower.devices.values
readonly property UPowerDevice displayDevice: UPower.displayDevice
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 "))
// }
// }
}
+3 -1
View File
@@ -53,12 +53,14 @@ Searcher {
}; };
root.crops = updated; 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, 1, 1), Qt.rect(0, 0, 0, 0), 1.0)); 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}`]);
+68
View File
@@ -0,0 +1,68 @@
import QtQuick
import QtQuick.Shapes
import qs.Components
import qs.Config
ShapePath {
id: root
readonly property bool flatten: wrapper.height < rounding * 2
property real ibr: invertBottomRounding ? -1 : 1
required property bool invertBottomRounding
property real rounding: Appearance.rounding.smallest
readonly property real roundingY: flatten ? wrapper.height / 2 : rounding
required property Wrapper wrapper
fillColor: DynamicColors.palette.m3surface
strokeWidth: -1
Behavior on fillColor {
CAnim {
}
}
PathArc {
radiusX: root.rounding
radiusY: Math.min(root.rounding, root.wrapper.height)
relativeX: root.rounding
relativeY: root.roundingY
}
PathLine {
relativeX: 0
relativeY: root.wrapper.height - root.roundingY - root.roundingY * root.ibr
}
PathArc {
direction: root.invertBottomRounding ? PathArc.Clockwise : PathArc.Counterclockwise
radiusX: root.rounding
radiusY: Math.min(root.rounding, root.wrapper.height)
relativeX: root.rounding
relativeY: root.roundingY * root.ibr
}
PathLine {
relativeX: root.wrapper.width - root.rounding * 2
relativeY: 0
}
PathArc {
direction: PathArc.Counterclockwise
radiusX: root.rounding
radiusY: Math.min(root.rounding, root.wrapper.height)
relativeX: root.rounding
relativeY: -root.roundingY
}
PathLine {
relativeX: 0
relativeY: -(root.wrapper.height - root.roundingY * 2)
}
PathArc {
radiusX: root.rounding
radiusY: Math.min(root.rounding, root.wrapper.height)
relativeX: root.rounding
relativeY: -root.roundingY
}
}
+1 -1
View File
@@ -23,7 +23,7 @@ CustomRect {
anchors.centerIn: parent anchors.centerIn: parent
color: root.visibilities.dashboard ? DynamicColors.palette.m3onPrimary : DynamicColors.palette.m3onSurface color: root.visibilities.dashboard ? DynamicColors.palette.m3onPrimary : DynamicColors.palette.m3onSurface
font: Appearance.font.family.mono // qmllint disable incompatible-type font: Appearance.font.family.mono
text: Time.dateStr text: Time.dateStr
Behavior on color { Behavior on color {
+65
View File
@@ -0,0 +1,65 @@
import qs.Components
import qs.Config
import QtQuick
import QtQuick.Shapes
ShapePath {
id: root
readonly property bool flatten: wrapper.height < rounding * 2
readonly property real rounding: Appearance.rounding.normal
readonly property real roundingY: flatten ? wrapper.height / 2 : rounding
required property Wrapper wrapper
fillColor: DynamicColors.palette.m3surface
strokeWidth: -1
Behavior on fillColor {
CAnim {
}
}
PathArc {
radiusX: root.rounding
radiusY: Math.min(root.rounding, root.wrapper.height)
relativeX: root.rounding
relativeY: root.roundingY
}
PathLine {
relativeX: 0
relativeY: root.wrapper.height - root.roundingY * 2
}
PathArc {
direction: PathArc.Counterclockwise
radiusX: root.rounding
radiusY: Math.min(root.rounding, root.wrapper.height)
relativeX: root.rounding
relativeY: root.roundingY
}
PathLine {
relativeX: root.wrapper.width - root.rounding * 2
relativeY: 0
}
PathArc {
radiusX: root.rounding
radiusY: Math.min(root.rounding, root.wrapper.height)
relativeX: root.rounding
relativeY: root.roundingY
}
PathLine {
relativeX: 0
relativeY: -(root.wrapper.height)
}
PathArc {
radiusX: root.rounding
radiusY: Math.min(root.rounding, root.wrapper.height)
relativeX: root.rounding
relativeY: -root.roundingY
}
}
+1 -1
View File
@@ -20,7 +20,7 @@ Item {
required property PersistentProperties visibilities required property PersistentProperties visibilities
implicitHeight: content.implicitHeight implicitHeight: content.implicitHeight
implicitWidth: content.implicitWidth || 854 implicitWidth: content.implicitWidth || 854 // Hard coded fallback for first open
opacity: 1 - offsetScale opacity: 1 - offsetScale
visible: offsetScale < 1 visible: offsetScale < 1
+66
View File
@@ -0,0 +1,66 @@
import QtQuick
import QtQuick.Shapes
import qs.Components
import qs.Config
ShapePath {
id: root
readonly property bool flatten: wrapper.height < rounding * 2
readonly property real rounding: Appearance.rounding.normal
readonly property real roundingY: flatten ? wrapper.height / 2 : rounding
required property Wrapper wrapper
fillColor: DynamicColors.palette.m3surface
strokeWidth: -1
Behavior on fillColor {
CAnim {
}
}
PathArc {
direction: PathArc.Counterclockwise
radiusX: root.rounding
radiusY: Math.min(root.rounding, root.wrapper.height)
relativeX: root.rounding
relativeY: -root.roundingY
}
PathLine {
relativeX: 0
relativeY: -(root.wrapper.height - root.roundingY * 2)
}
PathArc {
radiusX: root.rounding
radiusY: Math.min(root.rounding, root.wrapper.height)
relativeX: root.rounding
relativeY: -root.roundingY
}
PathLine {
relativeX: root.wrapper.width - root.rounding * 2
relativeY: 0
}
PathArc {
radiusX: root.rounding
radiusY: Math.min(root.rounding, root.wrapper.height)
relativeX: root.rounding
relativeY: root.roundingY
}
PathLine {
relativeX: 0
relativeY: root.wrapper.height - root.roundingY * 2
}
PathArc {
direction: PathArc.Counterclockwise
radiusX: root.rounding
radiusY: Math.min(root.rounding, root.wrapper.height)
relativeX: root.rounding
relativeY: root.roundingY
}
}
+6 -5
View File
@@ -9,17 +9,18 @@ Item {
id: root id: root
property int contentHeight property int contentHeight
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.dock && Config.dock.enable
required property PersistentProperties visibilities required property PersistentProperties visibilities
readonly property bool shouldBeActive: visibilities.dock
property real offsetScale: shouldBeActive ? 0 : 1
visible: offsetScale < 1
anchors.bottomMargin: (-implicitHeight - 5) * offsetScale anchors.bottomMargin: (-implicitHeight - 5) * offsetScale
implicitHeight: content.implicitHeight implicitHeight: content.implicitHeight
implicitWidth: content.implicitWidth || 400 implicitWidth: content.implicitWidth || 400
opacity: 1 - offsetScale opacity: 1 - offsetScale
visible: offsetScale < 1
Behavior on offsetScale { Behavior on offsetScale {
Anim { Anim {
@@ -31,10 +32,10 @@ Item {
Loader { Loader {
id: content id: content
active: root.shouldBeActive || root.visible
anchors.left: parent.left anchors.left: parent.left
anchors.top: parent.top anchors.top: parent.top
asynchronous: true
active: root.shouldBeActive || root.visible
sourceComponent: Content { sourceComponent: Content {
panels: root.panels panels: root.panels
+66
View File
@@ -0,0 +1,66 @@
import QtQuick
import QtQuick.Shapes
import qs.Components
import qs.Config
ShapePath {
id: root
readonly property bool flatten: wrapper.width < rounding * 2
readonly property real rounding: Appearance.rounding.normal
readonly property real roundingX: flatten ? wrapper.width / 2 : rounding
required property Wrapper wrapper
fillColor: DynamicColors.palette.m3surface
strokeWidth: -1
Behavior on fillColor {
CAnim {
}
}
PathArc {
direction: PathArc.Counterclockwise
radiusX: Math.min(root.rounding, root.wrapper.width)
radiusY: root.rounding
relativeX: root.roundingX
relativeY: root.rounding
}
PathLine {
relativeX: root.wrapper.width - root.roundingX * 2
relativeY: 0
}
PathArc {
radiusX: Math.min(root.rounding, root.wrapper.width)
radiusY: root.rounding
relativeX: root.roundingX
relativeY: root.rounding
}
PathLine {
relativeX: 0
relativeY: root.wrapper.height - root.rounding * 2
}
PathArc {
radiusX: Math.min(root.rounding, root.wrapper.width)
radiusY: root.rounding
relativeX: -root.roundingX
relativeY: root.rounding
}
PathLine {
relativeX: -(root.wrapper.width - root.roundingX * 2)
relativeY: 0
}
PathArc {
direction: PathArc.Counterclockwise
radiusX: Math.min(root.rounding, root.wrapper.width)
radiusY: root.rounding
relativeX: -root.roundingX
relativeY: root.rounding
}
}
+4 -4
View File
@@ -44,10 +44,10 @@ Item {
Loader { Loader {
id: icon id: icon
active: root.shouldBeActive || root.visible active: Qt.binding(() => root.shouldBeActive || root.visible)
anchors.right: parent.right anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
asynchronous: true height: content.contentItem.height
opacity: root.expanded ? 0 : 1 opacity: root.expanded ? 0 : 1
Behavior on opacity { Behavior on opacity {
@@ -63,10 +63,8 @@ Item {
Loader { Loader {
id: content id: content
active: root.shouldBeActive || root.visible
anchors.right: parent.right anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
asynchronous: true
opacity: root.expanded ? 1 : 0 opacity: root.expanded ? 1 : 0
Behavior on opacity { Behavior on opacity {
@@ -77,5 +75,7 @@ Item {
drawing: root.drawing drawing: root.drawing
visibilities: root.visibilities visibilities: root.visibilities
} }
Component.onCompleted: active = Qt.binding(() => root.shouldBeActive || root.visible)
} }
} }
+66
View File
@@ -0,0 +1,66 @@
import QtQuick
import QtQuick.Shapes
import qs.Components
import qs.Config
ShapePath {
id: root
readonly property bool flatten: wrapper.height < rounding * 2
readonly property real rounding: Appearance.rounding.smallest + 5
readonly property real roundingY: flatten ? wrapper.height / 2 : rounding
required property Wrapper wrapper
fillColor: DynamicColors.palette.m3surface
strokeWidth: -1
Behavior on fillColor {
CAnim {
}
}
PathArc {
direction: PathArc.Counterclockwise
radiusX: root.rounding
radiusY: Math.min(root.rounding, root.wrapper.height)
relativeX: root.rounding
relativeY: -root.roundingY
}
PathLine {
relativeX: 0
relativeY: -(root.wrapper.height - root.roundingY * 2)
}
PathArc {
radiusX: root.rounding
radiusY: Math.min(root.rounding, root.wrapper.height)
relativeX: root.rounding
relativeY: -root.roundingY
}
PathLine {
relativeX: root.wrapper.width - root.rounding * 2
relativeY: 0
}
PathArc {
radiusX: root.rounding
radiusY: Math.min(root.rounding, root.wrapper.height)
relativeX: root.rounding
relativeY: root.roundingY
}
PathLine {
relativeX: 0
relativeY: root.wrapper.height - root.roundingY * 2
}
PathArc {
direction: PathArc.Counterclockwise
radiusX: root.rounding
radiusY: Math.min(root.rounding, root.wrapper.height)
relativeX: root.rounding
relativeY: root.roundingY
}
}
+60 -16
View File
@@ -4,7 +4,6 @@ import Quickshell
import QtQuick import QtQuick
import qs.Components import qs.Components
import qs.Config import qs.Config
import qs.Modules.Launcher.Services
Item { Item {
id: root id: root
@@ -12,24 +11,34 @@ Item {
property int contentHeight property int contentHeight
readonly property real maxHeight: { readonly property real maxHeight: {
let max = screen.height - Appearance.spacing.large * 2; let max = screen.height - Appearance.spacing.large * 2;
if (visibilities.resources && panels.resourcesWrapper.x + panels.resourcesWrapper.width > root.x) if (visibilities.resources)
max -= panels.resources.nonAnimHeight; max -= panels.resources.nonAnimHeight;
if (panels.popouts.hasCurrent) if (visibilities.dashboard && panels.dashboard.x < root.x + root.implicitWidth)
if (panels.popouts.current.x + panels.popouts.current.width > root.x && panels.popouts.current.x < root.x + root.width) max -= panels.dashboard.nonAnimHeight;
max -= panels.popouts.nonAnimHeight; if (panels.popouts.currentName.startsWith("updates"))
max -= panels.popouts.nonAnimHeight;
return max; return max;
} }
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.launcher
required property PersistentProperties visibilities required property PersistentProperties visibilities
readonly property bool shouldBeActive: visibilities.launcher
property real offsetScale: shouldBeActive ? 0 : 1
onShouldBeActiveChanged: {
if (shouldBeActive) {
implicitHeight = Qt.binding(() => content.implicitHeight);
timer.stop();
} else {
implicitHeight = implicitHeight;
}
}
visible: offsetScale < 1
anchors.bottomMargin: (-implicitHeight - 5) * offsetScale anchors.bottomMargin: (-implicitHeight - 5) * offsetScale
implicitHeight: content.implicitHeight implicitHeight: content.implicitHeight
implicitWidth: content.implicitWidth || 400 implicitWidth: content.implicitWidth || 400
opacity: 1 - offsetScale opacity: 1 - offsetScale
visible: offsetScale < 1
Behavior on offsetScale { Behavior on offsetScale {
Anim { Anim {
@@ -38,26 +47,61 @@ Item {
} }
} }
Component.onCompleted: Qt.callLater(() => Apps) onMaxHeightChanged: timer.start()
onShouldBeActiveChanged: {
if (shouldBeActive) Connections {
implicitHeight = Qt.binding(() => content.implicitHeight); function onEnabledChanged(): void {
else timer.start();
implicitHeight = implicitHeight; }
function onMaxShownChanged(): void {
timer.start();
}
target: Config.launcher
}
Connections {
function onValuesChanged(): void {
if (DesktopEntries.applications.values.length < Config.launcher.maxAppsShown)
timer.start();
}
target: DesktopEntries.applications
}
Timer {
id: timer
interval: Appearance.anim.durations.small
onRunningChanged: {
if (running && !root.shouldBeActive) {
content.visible = false;
content.active = true;
} else {
root.contentHeight = Math.min(root.maxHeight, content.implicitHeight);
content.active = Qt.binding(() => root.shouldBeActive || root.visible);
content.visible = true;
}
}
} }
Loader { Loader {
id: content id: content
active: root.shouldBeActive || root.visible active: false
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top anchors.top: parent.top
asynchronous: true
sourceComponent: Content { sourceComponent: Content {
maxHeight: root.maxHeight maxHeight: root.maxHeight
panels: root.panels panels: root.panels
visibilities: root.visibilities visibilities: root.visibilities
Component.onCompleted: root.contentHeight = implicitHeight
} }
Component.onCompleted: timer.start()
} }
} }
-5
View File
@@ -2,7 +2,6 @@ pragma ComponentBehavior: Bound
import Quickshell import Quickshell
import Quickshell.Wayland import Quickshell.Wayland
import ZShell.Internal
import qs.Config import qs.Config
import qs.Helpers import qs.Helpers
@@ -30,10 +29,6 @@ Scope {
Quickshell.execDetached(action); Quickshell.execDetached(action);
} }
LidWatcher {
onAboutToSleep: root.lock.lock.locked = true
}
Variants { Variants {
model: Config.general.idle.timeouts model: Config.general.idle.timeouts
+59
View File
@@ -0,0 +1,59 @@
import qs.Components
import qs.Config
import QtQuick
import QtQuick.Shapes
ShapePath {
id: root
readonly property bool flatten: wrapper.height < rounding * 2
readonly property real rounding: 8
readonly property real roundingY: flatten ? wrapper.height / 2 : rounding
required property var sidebar
required property Wrapper wrapper
fillColor: DynamicColors.palette.m3surface
strokeWidth: -1
Behavior on fillColor {
CAnim {
}
}
PathLine {
relativeX: -(root.wrapper.width + root.rounding)
relativeY: 0
}
PathArc {
radiusX: root.rounding
radiusY: Math.min(root.rounding, root.wrapper.height)
relativeX: root.rounding
relativeY: root.roundingY
}
PathLine {
relativeX: 0
relativeY: root.wrapper.height - root.roundingY * 2
}
PathArc {
direction: PathArc.Counterclockwise
radiusX: root.sidebar.notifsRoundingX
radiusY: Math.min(root.rounding, root.wrapper.height)
relativeX: root.sidebar.notifsRoundingX
relativeY: root.roundingY
}
PathLine {
relativeX: root.wrapper.height > 0 ? root.wrapper.width - root.rounding - root.sidebar.notifsRoundingX : root.wrapper.width
relativeY: 0
}
PathArc {
radiusX: root.rounding
radiusY: root.rounding
relativeX: root.rounding
relativeY: root.rounding
}
}
+45 -3
View File
@@ -8,7 +8,7 @@ import QtQuick
Item { Item {
id: root id: root
readonly property int padding: Appearance.padding.smaller readonly property int padding: 6
required property Item panels required property Item panels
required property PersistentProperties visibilities required property PersistentProperties visibilities
@@ -54,7 +54,7 @@ Item {
anchors.fill: parent anchors.fill: parent
anchors.margins: root.padding anchors.margins: root.padding
color: "transparent" color: "transparent"
radius: Appearance.rounding.normal - root.padding radius: Appearance.rounding.smallest / 2
CustomListView { CustomListView {
id: list id: list
@@ -72,7 +72,7 @@ Item {
required property NotifServer.Notif modelData required property NotifServer.Notif modelData
readonly property alias nonAnimHeight: notif.nonAnimHeight readonly property alias nonAnimHeight: notif.nonAnimHeight
implicitHeight: notif.implicitHeight + (idx === 0 ? 0 : Appearance.spacing.small) implicitHeight: notif.implicitHeight + (idx === 0 ? 0 : 8)
implicitWidth: notif.implicitWidth implicitWidth: notif.implicitWidth
ListView.onRemove: removeAnim.start() ListView.onRemove: removeAnim.start()
@@ -151,6 +151,48 @@ Item {
property: "y" property: "y"
} }
} }
ExtraIndicator {
anchors.top: parent.top
extra: {
const count = list.count;
if (count === 0)
return 0;
const scrollY = list.contentY;
let height = 0;
for (let i = 0; i < count; i++) {
height += (list.itemAtIndex(i)?.nonAnimHeight ?? 0) + 8;
if (height - 8 >= scrollY)
return i;
}
return count;
}
}
ExtraIndicator {
anchors.bottom: parent.bottom
extra: {
const count = list.count;
if (count === 0)
return 0;
const scrollY = list.contentHeight - (list.contentY + list.height);
let height = 0;
for (let i = count - 1; i >= 0; i--) {
height += (list.itemAtIndex(i)?.nonAnimHeight ?? 0) + 8;
if (height - 8 >= scrollY)
return count - i - 1;
}
return 0;
}
}
} }
} }
@@ -0,0 +1,54 @@
import QtQuick
import QtQuick.Shapes
import qs.Components
import qs.Config
ShapePath {
id: root
readonly property bool flatten: wrapper.width < rounding * 2
readonly property real notifsRoundingX: panels.notifications.height > 0 && notifsWidthDiff < rounding * 2 ? notifsWidthDiff / 2 : rounding
readonly property real notifsWidthDiff: panels.notifications.width - wrapper.width
required property var panels
readonly property real rounding: Config.barConfig.rounding
readonly property real utilsRoundingX: utilsWidthDiff < rounding * 2 ? utilsWidthDiff / 2 : rounding
readonly property real utilsWidthDiff: panels.utilities.width - wrapper.width
required property Wrapper wrapper
fillColor: DynamicColors.palette.m3surface
strokeWidth: -1
Behavior on fillColor {
CAnim {
}
}
PathLine {
relativeX: -root.wrapper.width - root.notifsRoundingX
relativeY: 0
}
PathArc {
radiusX: root.notifsRoundingX
radiusY: root.rounding
relativeX: root.notifsRoundingX
relativeY: root.rounding
}
PathLine {
relativeX: 0
relativeY: root.wrapper.height - root.rounding * 2
}
PathArc {
radiusX: root.utilsRoundingX
radiusY: root.rounding
relativeX: -root.utilsRoundingX
relativeY: root.rounding
}
PathLine {
relativeX: root.wrapper.width + root.utilsRoundingX
relativeY: 0
}
}
+66
View File
@@ -0,0 +1,66 @@
import QtQuick
import QtQuick.Shapes
import qs.Components
import qs.Config
ShapePath {
id: root
readonly property bool flatten: wrapper.width < rounding * 2
readonly property real rounding: 10
readonly property real roundingX: flatten ? wrapper.width / 2 : rounding
required property Wrapper wrapper
fillColor: DynamicColors.palette.m3surface
strokeWidth: -1
Behavior on fillColor {
CAnim {
}
}
PathArc {
radiusX: Math.min(root.rounding, root.wrapper.width)
radiusY: root.rounding
relativeX: -root.roundingX
relativeY: root.rounding
}
PathLine {
relativeX: -(root.wrapper.width - root.roundingX * 3)
relativeY: 0
}
PathArc {
direction: PathArc.Counterclockwise
radiusX: Math.min(root.rounding * 2, root.wrapper.width)
radiusY: root.rounding * 2
relativeX: -root.roundingX * 2
relativeY: root.rounding * 2
}
PathLine {
relativeX: 0
relativeY: root.wrapper.height - root.rounding * 4
}
PathArc {
direction: PathArc.Counterclockwise
radiusX: Math.min(root.rounding * 2, root.wrapper.width)
radiusY: root.rounding * 2
relativeX: root.roundingX * 2
relativeY: root.rounding * 2
}
PathLine {
relativeX: root.wrapper.width - root.roundingX * 3
relativeY: 0
}
PathArc {
radiusX: Math.min(root.rounding, root.wrapper.width)
radiusY: root.rounding
relativeX: root.roundingX
relativeY: root.rounding
}
}
+65
View File
@@ -0,0 +1,65 @@
import qs.Components
import qs.Config
import QtQuick
import QtQuick.Shapes
ShapePath {
id: root
readonly property bool flatten: wrapper.height < rounding * 2
readonly property real rounding: Appearance.rounding.normal
readonly property real roundingY: flatten ? wrapper.height / 2 : rounding
required property Wrapper wrapper
fillColor: DynamicColors.palette.m3surface
strokeWidth: -1
Behavior on fillColor {
CAnim {
}
}
PathArc {
radiusX: root.rounding
radiusY: Math.min(root.rounding, root.wrapper.height)
relativeX: root.rounding
relativeY: root.roundingY
}
PathLine {
relativeX: 0
relativeY: root.wrapper.height
}
PathArc {
radiusX: root.rounding
radiusY: Math.min(root.rounding, root.wrapper.height)
relativeX: root.rounding
relativeY: -root.roundingY
}
PathLine {
relativeX: root.wrapper.width - root.rounding * 2
relativeY: 0
}
PathArc {
direction: PathArc.Counterclockwise
radiusX: root.rounding
radiusY: Math.min(root.rounding, root.wrapper.height)
relativeX: root.rounding
relativeY: -root.roundingY
}
PathLine {
relativeX: 0
relativeY: -(root.wrapper.height - root.roundingY * 2)
}
PathArc {
radiusX: root.rounding
radiusY: Math.min(root.rounding, root.wrapper.height)
relativeX: root.rounding
relativeY: -root.roundingY
}
}
+3 -2
View File
@@ -8,7 +8,7 @@ import qs.Config
Item { Item {
id: root id: root
readonly property real nonAnimHeight: content.item?.nonAnimHeight ?? 0 readonly property real nonAnimHeight: state === "visible" ? (content.item?.nonAnimHeight ?? 0) : 0
property real offsetScale: shouldBeActive ? 0 : 1 property real offsetScale: shouldBeActive ? 0 : 1
readonly property bool shouldBeActive: root.visibilities.resources readonly property bool shouldBeActive: root.visibilities.resources
required property PersistentProperties visibilities required property PersistentProperties visibilities
@@ -31,7 +31,8 @@ Item {
id: content id: content
active: root.shouldBeActive || root.visible active: root.shouldBeActive || root.visible
anchors.centerIn: parent anchors.bottom: parent.bottom
anchors.horizontalCenter: parent.horizontalCenter
sourceComponent: Content { sourceComponent: Content {
padding: Appearance.padding.normal padding: Appearance.padding.normal
+66
View File
@@ -0,0 +1,66 @@
import QtQuick
import QtQuick.Shapes
import qs.Components
import qs.Config
ShapePath {
id: root
readonly property bool flatten: wrapper.height < rounding * 2
readonly property real rounding: Appearance.rounding.large
readonly property real roundingY: flatten ? wrapper.height / 2 : rounding
required property Wrapper wrapper
fillColor: DynamicColors.palette.m3surface
strokeWidth: -1
Behavior on fillColor {
CAnim {
}
}
PathArc {
radiusX: root.rounding
radiusY: Math.min(root.roundingY, root.wrapper.height)
relativeX: root.rounding
relativeY: root.roundingY
}
PathLine {
relativeX: 0
relativeY: root.wrapper.height - root.roundingY * 2
}
PathArc {
direction: PathArc.Counterclockwise
radiusX: root.rounding
radiusY: Math.min(root.rounding, root.wrapper.height)
relativeX: root.rounding
relativeY: root.roundingY
}
PathLine {
relativeX: root.wrapper.width - root.rounding * 2
relativeY: 0
}
PathArc {
direction: PathArc.Counterclockwise
radiusX: root.rounding
radiusY: Math.min(root.rounding, root.wrapper.height)
relativeX: root.rounding
relativeY: -root.roundingY
}
PathLine {
relativeX: 0
relativeY: -(root.wrapper.height - root.roundingY * 2)
}
PathArc {
radiusX: root.rounding
radiusY: Math.min(root.rounding, root.wrapper.height)
relativeX: root.rounding
relativeY: -root.roundingY
}
}
+6
View File
@@ -116,6 +116,12 @@ Item {
key: "updates" key: "updates"
name: "Updates" name: "Updates"
} }
ListElement {
icon: "extension"
key: "plugins"
name: "Extensions"
}
} }
CustomClippingRect { CustomClippingRect {
-15
View File
@@ -56,21 +56,6 @@ SettingsPage {
} }
} }
SettingsSection {
sectionId: "Tray"
SettingsHeader {
name: "System tray"
}
SettingSpinBox {
min: 16
name: "Tray icon size"
object: Config.barConfig.tray
setting: "trayIconSize"
}
}
SettingsSection { SettingsSection {
sectionId: "Popouts" sectionId: "Popouts"
+2 -2
View File
@@ -69,7 +69,7 @@ SettingsPage {
CustomSplitButtonRow { CustomSplitButtonRow {
active: Config.general.color.mode === "light" ? menuItems[0] : menuItems[1] active: Config.general.color.mode === "light" ? menuItems[0] : menuItems[1]
enabled: Config.general.color.schemeGeneration buttonAlias.disabled: !Config.general.color.schemeGeneration
label: qsTr("Scheme mode") label: qsTr("Scheme mode")
menuItems: [ menuItems: [
@@ -103,7 +103,7 @@ SettingsPage {
id: schemeType id: schemeType
active: root.schemeTypeItem(menuItems, Config.colors.schemeType) active: root.schemeTypeItem(menuItems, Config.colors.schemeType)
enabled: Config.general.color.schemeGeneration buttonAlias.disabled: !Config.general.color.schemeGeneration
label: qsTr("Scheme type") label: qsTr("Scheme type")
z: 2 z: 2
@@ -1,6 +1,5 @@
import qs.Modules.Settings.Categories.Lockscreen import qs.Modules.Settings.Categories.Lockscreen
import qs.Modules.Settings.Controls import qs.Modules.Settings.Controls
import qs.Helpers
import qs.Config import qs.Config
SettingsPage { SettingsPage {
+18
View File
@@ -0,0 +1,18 @@
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"
}
}
}
+36 -56
View File
@@ -20,8 +20,7 @@ SettingsPage {
} }
CustomSplitButtonRow { CustomSplitButtonRow {
active: Config.screenshot.mode === "manual" ? menuItems[0] : menuItems[1] // active: true
enabled: Config.screenshot.enable_pp
label: qsTr("Effects mode") label: qsTr("Effects mode")
menuItems: [ menuItems: [
@@ -44,107 +43,88 @@ SettingsPage {
} }
Separator { Separator {
shouldBeActive: Config.screenshot.mode === "manual" visible: Config.screenshot.mode === "manual"
}
SettingSwitch {
enabled: Config.screenshot.enable_pp
name: "Enable rounded corners"
object: Config.screenshot
setting: "rounding"
shouldBeActive: Config.screenshot.mode === "manual"
}
Separator {
shouldBeActive: Config.screenshot.mode === "manual" && Config.screenshot.rounding
} }
SettingSpinBox { SettingSpinBox {
enabled: Config.screenshot.enable_pp
min: 0 min: 0
name: "Corner radius" name: "Corner radius"
object: Config.screenshot object: Config.screenshot
setting: "radius" setting: "corner_radius"
shouldBeActive: Config.screenshot.mode === "manual" && Config.screenshot.rounding
step: 1 step: 1
visible: Config.screenshot.mode === "manual"
} }
Separator { Separator {
shouldBeActive: Config.screenshot.mode === "manual" visible: Config.screenshot.mode === "manual"
} }
SettingSwitch { SettingSwitch {
enabled: Config.screenshot.enable_pp name: "Enable drop shadow"
name: "Enable shadow"
object: Config.screenshot object: Config.screenshot
setting: "shadow" setting: "drop_shadow"
shouldBeActive: Config.screenshot.mode === "manual" visible: Config.screenshot.mode === "manual"
} }
Separator { Separator {
shouldBeActive: Config.screenshot.mode === "manual" && Config.screenshot.shadow visible: Config.screenshot.mode === "manual"
}
SettingSwitch {
name: "Enable rounded corners"
object: Config.screenshot
setting: "rounded_corners"
visible: Config.screenshot.mode === "manual"
}
Separator {
visible: Config.screenshot.mode === "manual"
} }
SettingSpinBox { SettingSpinBox {
enabled: Config.screenshot.enable_pp
min: 0 min: 0
name: "Shadow blur amount" name: "Shadow blur radius"
object: Config.screenshot object: Config.screenshot
setting: "shadow_blur" setting: "shadow_blur_radius"
shouldBeActive: Config.screenshot.mode === "manual" && Config.screenshot.shadow
step: 1 step: 1
visible: Config.screenshot.mode === "manual"
} }
Separator { Separator {
shouldBeActive: Config.screenshot.mode === "manual" && Config.screenshot.shadow visible: Config.screenshot.mode === "manual"
} }
// SettingSwitch { SettingSwitch {
// name: "Shadow color broken atm" name: "Shadow color broken atm"
// object: Config.Screenshot object: Config.Screenshot
// setting: "shadow_color" setting: "shadow_color"
// shouldBeActive: Config.screenshot.mode === "manual" visible: Config.screenshot.mode === "manual"
// } }
//
// Separator {
// shouldBeActive: Config.screenshot.mode === "manual"
// }
// SettingSpinBox { Separator {
// min: 1 visible: Config.screenshot.mode === "manual"
// name: "Shadow passes" }
// object: Config.screenshot
// setting: "shadow_blur_passes"
// shouldBeActive: Config.screenshot.mode === "manual"
// step: 1
// }
//
// Separator {
// shouldBeActive: Config.screenshot.mode === "manual"
// }
SettingSpinBox { SettingSpinBox {
enabled: Config.screenshot.enable_pp
min: 0 min: 0
name: "Shadow offset X" name: "Shadow offset X"
object: Config.screenshot object: Config.screenshot
setting: "shadow_offset_x" setting: "shadow_offset_x"
shouldBeActive: Config.screenshot.mode === "manual" && Config.screenshot.shadow
step: 1 step: 1
visible: Config.screenshot.mode === "manual"
} }
Separator { Separator {
shouldBeActive: Config.screenshot.mode === "manual" && Config.screenshot.shadow visible: Config.screenshot.mode === "manual"
} }
SettingSpinBox { SettingSpinBox {
enabled: Config.screenshot.enable_pp
min: 0 min: 0
name: "Shadow offset Y" name: "Shadow offset Y"
object: Config.screenshot object: Config.screenshot
setting: "shadow_offset_y" setting: "shadow_offset_y"
shouldBeActive: Config.screenshot.mode === "manual" && Config.screenshot.shadow
step: 1 step: 1
visible: Config.screenshot.mode === "manual"
} }
} }
} }
+10 -1
View File
@@ -79,6 +79,8 @@ 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
@@ -134,7 +136,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.m3surfaceContainer color: DynamicColors.tPalette.m3surfaceContainerLowest
radius: Appearance.rounding.normal radius: Appearance.rounding.normal
StackView { StackView {
@@ -245,4 +247,11 @@ Item {
Cat.SystemUpdates { Cat.SystemUpdates {
} }
} }
Component {
id: plugins
Cat.Plugins {
}
}
} }
+61 -98
View File
@@ -80,28 +80,12 @@ Item {
required property ShellScreen modelData required property ShellScreen modelData
function applyCrop(): void { function applyCrop(): void {
if (!cropRectLoader.item) const croprect = cropRect.mapToItem(scaledImg, 0, 0, cropRect.width, cropRect.height);
return; const upscaledRect = Qt.rect((croprect.x - cropRect.imageX) / scaledImg.paintedWidth, (croprect.y - cropRect.imageY) / scaledImg.paintedHeight, croprect.width / scaledImg.paintedWidth, croprect.height / scaledImg.paintedHeight);
const cropRect = cropRectLoader.item; Wallpapers.setCrop(delegate.modelData.name, upscaledRect, croprect, cropRect.zoom);
// We need to calculate the exact percentage coordinates that map perfectly
// to our C++ backend, regardless of current display scaling
const cropXPercent = (cropRect.x - cropRect.imageX) / scaledImg.paintedWidth;
const cropYPercent = (cropRect.y - cropRect.imageY) / scaledImg.paintedHeight;
const cropWidthPercent = cropRect.width / scaledImg.paintedWidth;
const cropHeightPercent = cropRect.height / scaledImg.paintedHeight;
const finalRect = Qt.rect(cropXPercent, cropYPercent, cropWidthPercent, cropHeightPercent);
// We just pass the percentages directly to the backend
Wallpapers.setCrop(delegate.modelData.name, finalRect, finalRect, cropRect.zoom);
} }
function zoomClipRect(zoom: real): void { function zoomClipRect(zoom: real): void {
if (!cropRectLoader.item)
return;
const cropRect = cropRectLoader.item;
let oldCenterX = cropRect.x + cropRect.width * 0.5; let oldCenterX = cropRect.x + cropRect.width * 0.5;
let oldCenterY = cropRect.y + cropRect.height * 0.5; let oldCenterY = cropRect.y + cropRect.height * 0.5;
@@ -141,11 +125,10 @@ Item {
id: zoomSlider id: zoomSlider
Layout.fillWidth: true Layout.fillWidth: true
Layout.preferredHeight: 30 Layout.preferredHeight: 10
from: 1.0 from: 1.0
implicitHeight: 30
to: 5.0 to: 5.0
value: cropRectLoader.item ? cropRectLoader.item.zoom : 1.0 value: cropRect.zoom
onMoved: { onMoved: {
delegate.zoomClipRect(value); delegate.zoomClipRect(value);
@@ -173,20 +156,15 @@ Item {
sourceSize.width: parent.width sourceSize.width: parent.width
onPaintedWidthChanged: { onPaintedWidthChanged: {
if (paintedWidth > 0 && cropRectLoader.item) { if (paintedWidth > 0) {
cropRectLoader.item.restoreFromData(); scaledImg.displayData = Wallpapers.getCrop(delegate.modelData.name);
} cropRect.zoom = Wallpapers.getCrop(delegate.modelData.name).zoom;
} cropRect.restoreFromData();
onSourceChanged: {
if (cropRectLoader.item) {
cropRectLoader.item.restoreFromData();
}
}
onStatusChanged: {
if (scaledImg.status == Image.Ready && cropRectLoader.item) {
cropRectLoader.item.restoreFromData();
} }
} }
onSourceChanged: cropRect.clampToBounds()
onStatusChanged: if (scaledImg.status == Image.Ready)
cropRect.clampToBounds()
CustomText { CustomText {
id: monitorId id: monitorId
@@ -199,87 +177,72 @@ Item {
text: delegate.modelData.name text: delegate.modelData.name
} }
Loader { CustomRect {
id: cropRectLoader id: cropRect
active: scaledImg.paintedWidth > 0 && scaledImg.status == Image.Ready 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;
sourceComponent: Component { if (fittedWidth > scaledImg.paintedWidth) {
CustomRect { fittedWidth = scaledImg.paintedWidth;
id: cropRect fittedHeight = fittedWidth / aspectRatio;
}
property real aspectRatio: delegate.modelData.width / delegate.modelData.height return fittedWidth;
readonly property real baseHeight: baseWidth / aspectRatio }
readonly property real baseWidth: { readonly property real imageX: (scaledImg.width - scaledImg.paintedWidth) / 2
let fittedHeight = scaledImg.paintedHeight; readonly property real imageY: (scaledImg.height - scaledImg.paintedHeight) / 2
let fittedWidth = fittedHeight * aspectRatio; property real imgAspectRatio: scaledImg.paintedWidth / scaledImg.paintedHeight
property real zoom: scaledImg.displayData.zoom
if (fittedWidth > scaledImg.paintedWidth) { function centerInImage() {
fittedWidth = scaledImg.paintedWidth; x = imageX + (scaledImg.paintedWidth - width) / 2;
fittedHeight = fittedWidth / aspectRatio; y = imageY + (scaledImg.paintedHeight - height) / 2;
} }
return fittedWidth; function clampToBounds() {
} x = Math.max(imageX, Math.min(x, imageX + scaledImg.paintedWidth - width));
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() { y = Math.max(imageY, Math.min(y, imageY + scaledImg.paintedHeight - height));
x = imageX + (scaledImg.paintedWidth - width) / 2; }
y = imageY + (scaledImg.paintedHeight - height) / 2;
}
function clampToBounds() { function restoreFromData() {
x = Math.max(imageX, Math.min(x, imageX + scaledImg.paintedWidth - width)); let data = scaledImg.displayData;
y = Math.max(imageY, Math.min(y, imageY + scaledImg.paintedHeight - height)); if (data && data.scaledX !== 0 || data.scaledY !== 0 || data.scaledWidth !== 0 || data.scaledHeight !== 0) {
} x = data.scaledX;
y = data.scaledY;
function restoreFromData() { clampToBounds();
let data = Wallpapers.getCrop(delegate.modelData.name); } else {
zoom = 1.0;
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)) { centerInImage();
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: DynamicColors.palette.m3primary
border.width: 2
height: baseHeight / zoom
opacity: 1
width: baseWidth / zoom
Behavior on opacity {
Anim {
}
}
Component.onCompleted: {
restoreFromData();
}
onHeightChanged: clampToBounds()
onWidthChanged: clampToBounds()
} }
} }
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 { MouseArea {
id: mouse id: mouse
function updateCrop(mouseX, mouseY) { function updateCrop(mouseX, mouseY) {
if (!cropRectLoader.item)
return;
const cropRect = cropRectLoader.item;
let nx = mouseX - cropRect.width * 0.5; let nx = mouseX - cropRect.width * 0.5;
let ny = mouseY - cropRect.height * 0.5; let ny = mouseY - cropRect.height * 0.5;
+2 -1
View File
@@ -32,9 +32,10 @@ Item {
Loader { Loader {
id: content id: content
active: root.shouldBeActive || root.visible active: true
anchors.bottom: parent.bottom anchors.bottom: parent.bottom
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
visible: true
sourceComponent: Content { sourceComponent: Content {
screen: root.screen screen: root.screen
+133 -153
View File
@@ -52,25 +52,25 @@ Item {
CustomRect { CustomRect {
Layout.fillWidth: true Layout.fillWidth: true
Layout.preferredHeight: 50 + Appearance.spacing.smaller * 2 Layout.preferredHeight: 42 + Appearance.spacing.smaller * 2
Layout.topMargin: root.topMargin Layout.topMargin: root.topMargin
color: DynamicColors.tPalette.m3surfaceContainer color: DynamicColors.tPalette.m3surfaceContainer
radius: root.rounding radius: root.rounding
Item { RowLayout {
id: sinkIcon id: outputVolume
anchors.bottom: parent.bottom
anchors.left: parent.left anchors.left: parent.left
anchors.leftMargin: Appearance.padding.normal anchors.margins: Appearance.spacing.smaller
anchors.top: parent.top anchors.right: parent.right
implicitWidth: childrenRect.width anchors.verticalCenter: parent.verticalCenter
spacing: 15
CustomRect { CustomRect {
anchors.centerIn: parent Layout.alignment: Qt.AlignVCenter
Layout.preferredHeight: 40
Layout.preferredWidth: 40
color: Audio.muted ? DynamicColors.palette.m3error : DynamicColors.palette.m3primary color: Audio.muted ? DynamicColors.palette.m3error : DynamicColors.palette.m3primary
implicitHeight: 40
implicitWidth: 40
radius: Appearance.rounding.full radius: Appearance.rounding.full
MaterialIcon { MaterialIcon {
@@ -92,53 +92,45 @@ Item {
} }
} }
} }
}
ColumnLayout { ColumnLayout {
anchors.bottom: parent.bottom
anchors.bottomMargin: Appearance.padding.smallest
anchors.left: sinkIcon.right
anchors.leftMargin: Appearance.spacing.normal
anchors.right: parent.right
anchors.rightMargin: Appearance.padding.large
anchors.top: parent.top
anchors.topMargin: Appearance.padding.smaller
RowLayout {
Layout.fillHeight: true
Layout.fillWidth: true Layout.fillWidth: true
CustomText { RowLayout {
Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft
Layout.fillWidth: true Layout.fillWidth: true
text: "Output Volume"
}
CustomText { CustomText {
Layout.alignment: Qt.AlignVCenter | Qt.AlignRight Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft
font.bold: true Layout.fillWidth: true
text: qsTr("%1").arg(Audio.muted ? qsTr("Muted") : `${Math.round(Audio.volume * 100)}%`) text: "Output Volume"
}
}
CustomMouseArea {
Layout.bottomMargin: 5
Layout.fillHeight: true
Layout.fillWidth: true
CustomSlider {
anchors.left: parent.left
anchors.right: parent.right
color: Audio.muted ? DynamicColors.palette.m3error : DynamicColors.palette.m3primary
implicitHeight: parent.height
value: Audio.volume
Behavior on value {
Anim {
}
} }
onMoved: Audio.setVolume(value) CustomText {
Layout.alignment: Qt.AlignVCenter | Qt.AlignRight
font.bold: true
text: qsTr("%1").arg(Audio.muted ? qsTr("Muted") : `${Math.round(Audio.volume * 100)}%`)
}
}
CustomMouseArea {
Layout.bottomMargin: 5
Layout.fillWidth: true
Layout.preferredHeight: 10
CustomSlider {
anchors.left: parent.left
anchors.right: parent.right
color: Audio.muted ? DynamicColors.palette.m3error : DynamicColors.palette.m3primary
implicitHeight: 10
value: Audio.volume
Behavior on value {
Anim {
}
}
onMoved: Audio.setVolume(value)
}
} }
} }
} }
@@ -146,7 +138,7 @@ Item {
CustomClippingRect { CustomClippingRect {
Layout.fillWidth: true Layout.fillWidth: true
Layout.preferredHeight: 50 + Appearance.spacing.smaller * 2 Layout.preferredHeight: 42 + Appearance.spacing.smaller * 2
Layout.topMargin: root.topMargin Layout.topMargin: root.topMargin
color: DynamicColors.tPalette.m3surfaceContainer color: DynamicColors.tPalette.m3surfaceContainer
radius: root.rounding radius: root.rounding
@@ -173,20 +165,20 @@ Item {
} }
} }
Item { RowLayout {
id: sourceIcon id: inputVolume
anchors.bottom: parent.bottom
anchors.left: parent.left anchors.left: parent.left
anchors.leftMargin: Appearance.padding.normal anchors.margins: Appearance.spacing.smaller
anchors.top: parent.top anchors.right: parent.right
implicitWidth: childrenRect.width anchors.verticalCenter: parent.verticalCenter
spacing: 15
CustomRect { CustomRect {
anchors.centerIn: parent Layout.alignment: Qt.AlignVCenter
Layout.preferredHeight: 40
Layout.preferredWidth: 40
color: Audio.sourceMuted ? DynamicColors.palette.m3error : DynamicColors.palette.m3primary color: Audio.sourceMuted ? DynamicColors.palette.m3error : DynamicColors.palette.m3primary
implicitHeight: 40
implicitWidth: 40
radius: Appearance.rounding.full radius: Appearance.rounding.full
MaterialIcon { MaterialIcon {
@@ -208,53 +200,46 @@ Item {
} }
} }
} }
}
ColumnLayout { ColumnLayout {
anchors.bottom: parent.bottom
anchors.bottomMargin: Appearance.padding.smallest
anchors.left: sourceIcon.right
anchors.leftMargin: Appearance.spacing.normal
anchors.right: parent.right
anchors.rightMargin: Appearance.padding.large
anchors.top: parent.top
anchors.topMargin: Appearance.padding.smaller
RowLayout {
Layout.fillHeight: true
Layout.fillWidth: true Layout.fillWidth: true
CustomText { RowLayout {
Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft Layout.fillHeight: true
Layout.fillWidth: true Layout.fillWidth: true
text: "Input Volume"
}
CustomText { CustomText {
Layout.alignment: Qt.AlignVCenter | Qt.AlignRight Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft
font.bold: true Layout.fillWidth: true
text: qsTr("%1").arg(Audio.sourceMuted ? qsTr("Muted") : `${Math.round(Audio.sourceVolume * 100)}%`) text: "Input Volume"
}
}
CustomMouseArea {
Layout.bottomMargin: 5
Layout.fillHeight: true
Layout.fillWidth: true
CustomSlider {
anchors.left: parent.left
anchors.right: parent.right
color: Audio.sourceMuted ? DynamicColors.palette.m3error : DynamicColors.palette.m3primary
implicitHeight: parent.height
value: Audio.sourceVolume
Behavior on value {
Anim {
}
} }
onMoved: Audio.setSourceVolume(value) CustomText {
Layout.alignment: Qt.AlignVCenter | Qt.AlignRight
font.bold: true
text: qsTr("%1").arg(Audio.sourceMuted ? qsTr("Muted") : `${Math.round(Audio.sourceVolume * 100)}%`)
}
}
CustomMouseArea {
Layout.bottomMargin: 5
Layout.fillWidth: true
implicitHeight: 10
CustomSlider {
anchors.left: parent.left
anchors.right: parent.right
color: Audio.sourceMuted ? DynamicColors.palette.m3error : DynamicColors.palette.m3primary
implicitHeight: 10
value: Audio.sourceVolume
Behavior on value {
Anim {
}
}
onMoved: Audio.setSourceVolume(value)
}
} }
} }
} }
@@ -280,7 +265,7 @@ Item {
required property var modelData required property var modelData
Layout.fillWidth: true Layout.fillWidth: true
Layout.preferredHeight: 50 + Appearance.spacing.smaller * 2 Layout.preferredHeight: 42 + Appearance.spacing.smaller * 2
Layout.topMargin: root.topMargin Layout.topMargin: root.topMargin
color: DynamicColors.tPalette.m3surfaceContainer color: DynamicColors.tPalette.m3surfaceContainer
radius: root.rounding radius: root.rounding
@@ -307,20 +292,18 @@ Item {
} }
} }
Item { RowLayout {
id: appBoxIcon id: layoutVolume
anchors.bottom: parent.bottom anchors.fill: parent
anchors.left: parent.left anchors.margins: Appearance.spacing.smaller
anchors.leftMargin: Appearance.padding.normal spacing: 15
anchors.top: parent.top
implicitWidth: childrenRect.width
CustomRect { CustomRect {
anchors.centerIn: parent Layout.alignment: Qt.AlignVCenter
Layout.preferredHeight: 40
Layout.preferredWidth: 40
color: appBox.modelData.audio.muted ? DynamicColors.palette.m3error : DynamicColors.palette.m3primary color: appBox.modelData.audio.muted ? DynamicColors.palette.m3error : DynamicColors.palette.m3primary
implicitHeight: 40
implicitWidth: 40
radius: Appearance.rounding.full radius: Appearance.rounding.full
MaterialIcon { MaterialIcon {
@@ -342,58 +325,55 @@ Item {
} }
} }
} }
}
TextMetrics { ColumnLayout {
id: metrics Layout.alignment: Qt.AlignLeft | Qt.AlignTop
elide: Text.ElideRight
elideWidth: root.width - 50
text: Audio.getStreamName(appBox.modelData)
}
ColumnLayout {
anchors.bottom: parent.bottom
anchors.bottomMargin: Appearance.padding.smallest
anchors.left: appBoxIcon.right
anchors.leftMargin: Appearance.spacing.normal
anchors.right: parent.right
anchors.rightMargin: Appearance.padding.large
anchors.top: parent.top
anchors.topMargin: Appearance.padding.smaller
RowLayout {
Layout.fillHeight: true Layout.fillHeight: true
Layout.fillWidth: true
CustomText { TextMetrics {
Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft id: metrics
Layout.fillWidth: true
elide: Text.ElideRight elide: Text.ElideRight
text: metrics.elidedText elideWidth: root.width - 50
text: Audio.getStreamName(appBox.modelData)
} }
CustomText { RowLayout {
Layout.alignment: Qt.AlignVCenter | Qt.AlignRight Layout.fillHeight: true
font.bold: true Layout.fillWidth: true
text: qsTr("%1").arg(appBox.modelData.audio.muted ? qsTr("Muted") : `${Math.round(appBox.modelData.audio.volume * 100)}%`)
CustomText {
Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft
Layout.fillHeight: true
Layout.fillWidth: true
elide: Text.ElideRight
text: metrics.elidedText
}
CustomText {
Layout.alignment: Qt.AlignVCenter | Qt.AlignRight
Layout.fillHeight: true
font.bold: true
text: qsTr("%1").arg(appBox.modelData.audio.muted ? qsTr("Muted") : `${Math.round(appBox.modelData.audio.volume * 100)}%`)
}
} }
}
CustomMouseArea { CustomMouseArea {
Layout.bottomMargin: 5 Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft
Layout.fillHeight: true Layout.fillHeight: true
Layout.fillWidth: true Layout.fillWidth: true
implicitHeight: 10
CustomSlider { CustomSlider {
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right
color: appBox.modelData.audio.muted ? DynamicColors.palette.m3error : DynamicColors.palette.m3primary color: appBox.modelData.audio.muted ? DynamicColors.palette.m3error : DynamicColors.palette.m3primary
implicitHeight: parent.height implicitHeight: 10
value: appBox.modelData.audio.volume value: appBox.modelData.audio.volume
onMoved: { onMoved: {
Audio.setStreamVolume(appBox.modelData, value); Audio.setStreamVolume(appBox.modelData, value);
}
} }
} }
} }
+4 -32
View File
@@ -3,7 +3,6 @@ import QtQuick
import QtQuick.VectorImage import QtQuick.VectorImage
import Quickshell import Quickshell
import Quickshell.Services.SystemTray import Quickshell.Services.SystemTray
import qs.Helpers
import qs.Modules import qs.Modules
import qs.Components import qs.Components
import qs.Config import qs.Config
@@ -12,36 +11,12 @@ Item {
id: root id: root
property bool current: popouts.currentName.startsWith(`traymenu${ind}`) && popouts.hasCurrent property bool current: popouts.currentName.startsWith(`traymenu${ind}`) && popouts.hasCurrent
readonly property real dpr: Hypr.monitorFor(loader.screen).scale property bool hasLoaded: false
required property int ind required property int ind
required property SystemTrayItem item required property SystemTrayItem item
required property RowLayout loader required property RowLayout loader
required property Wrapper popouts required property Wrapper popouts
function resolveIcon(app: string, icon: string): string {
if (app === "chrome_status_icon_1") {
return Quickshell.iconPath("discord-tray");
} else if (app === "AyuGramDesktop") {
if (icon === Quickshell.iconPath("com.ayugram.desktop-attention-symbolic"))
return Quickshell.iconPath("telegram-attention-panel");
else if (icon === Quickshell.iconPath("com.ayugram.desktop-mute-symbolic"))
return Quickshell.iconPath("telegram-mute-panel");
else if (icon === Quickshell.iconPath("com.ayugram.desktop-symbolic"))
return Quickshell.iconPath("telegram-panel");
} else if (app === "TelegramDesktop") {
if (icon === Quickshell.iconPath("org.telegram.desktop-symbolic"))
return Quickshell.iconPath("telegram-panel");
else if (icon === Quickshell.iconPath("org.telegram.desktop-attention-symbolic"))
return Quickshell.iconPath("telegram-attention-panel");
else if (icon === Quickshell.iconPath("org.telegram.desktop-mute-symbolic"))
return Quickshell.iconPath("telegram-mute-panel");
} else if (app === "steam") {
return Quickshell.iconPath("steam_tray_mono");
}
return root.item.icon;
}
CustomRect { CustomRect {
anchors.fill: parent anchors.fill: parent
anchors.margins: 3 anchors.margins: 3
@@ -55,8 +30,7 @@ Item {
onClicked: { onClicked: {
if (mouse.button === Qt.LeftButton) { if (mouse.button === Qt.LeftButton) {
root.item.activate(); root.item.activate();
console.log(icon.source + "\n" + root.item.id); } else if (mouse.button === Qt.RightButton) {
} else if (mouse.button === Qt.RightButton && Config.barConfig.popouts.tray) {
root.popouts.currentName = `traymenu${root.ind}`; root.popouts.currentName = `traymenu${root.ind}`;
root.popouts.currentCenter = Qt.binding(() => root.mapToItem(root.loader, root.implicitWidth / 2, 0).x); root.popouts.currentCenter = Qt.binding(() => root.mapToItem(root.loader, root.implicitWidth / 2, 0).x);
root.popouts.hasCurrent = true; root.popouts.hasCurrent = true;
@@ -74,11 +48,9 @@ Item {
id: icon id: icon
anchors.centerIn: parent anchors.centerIn: parent
antialiasing: true
color: root.current ? DynamicColors.palette.m3onPrimary : DynamicColors.palette.m3onSurface color: root.current ? DynamicColors.palette.m3onPrimary : DynamicColors.palette.m3onSurface
implicitSize: Config.barConfig.tray.trayIconSize * root.dpr implicitSize: 22
layer.enabled: Config.general.color.smart || Config.general.color.scheduleDark layer.enabled: Config.general.color.smart || Config.general.color.scheduleDark
scale: 1 / root.dpr source: root.item.icon
source: root.resolveIcon(root.item.id, root.item.icon)
} }
} }
+2 -2
View File
@@ -23,12 +23,12 @@ RowLayout {
let modRowPos = sysTrayMod.mapToItem(sysModRow, modPos.x, modPos.y); let modRowPos = sysTrayMod.mapToItem(sysModRow, modPos.x, modPos.y);
let child = sysModRow.childAt(modRowPos.x, modRowPos.y); let child = sysModRow.childAt(modRowPos.x, modRowPos.y);
if (child) { if (child) {
if (child.objectName === "audioWidget" && Config.barConfig.popouts.audio) if (child.objectName === "audioWidget")
return { return {
id: "audio", id: "audio",
item: child item: child
}; };
if (child.objectName === "upowerWidget" && Config.barConfig.popouts.upower) if (child.objectName === "upowerWidget")
return { return {
id: "upower", id: "upower",
item: child item: child
+22 -109
View File
@@ -3,120 +3,33 @@ import QtQuick
import QtQuick.Layouts import QtQuick.Layouts
import qs.Components import qs.Components
import qs.Config import qs.Config
import qs.Helpers import qs.Helpers as Helpers
Item { RowLayout {
id: root id: root
implicitHeight: Battery.isLaptop ? batteryIconLoader.item.implicitHeight : upowerIconLoader.item.implicitHeight MaterialIcon {
implicitWidth: Battery.isLaptop ? batteryIconLoader.item.implicitWidth : upowerIconLoader.item.implicitWidth Layout.alignment: Qt.AlignVCenter
animate: true
Loader { color: !Helpers.UPower.onBattery || UPower.displayDevice.percentage > 0.2 ? DynamicColors.palette.m3onSurface : DynamicColors.palette.m3error
id: batteryIconLoader fill: 1
text: {
active: Battery.isLaptop if (!Helpers.UPower.displayDevice.isLaptopBattery) {
anchors.centerIn: parent if (PowerProfiles.profile === PowerProfile.PowerSaver)
return "nest_eco_leaf";
sourceComponent: Row { if (PowerProfiles.profile === PowerProfile.Performance)
id: batteryIcon return "bolt";
return "power_settings_new";
property real batHeight: 16
property real batWidth: 30
property real nubHeight: 6
property real nubWidth: 2
property real radius: Appearance.rounding.smallest / 2
spacing: 1
CustomRect {
id: track
anchors.verticalCenter: parent.verticalCenter
color: Battery.colors.bg
height: batteryIcon.batHeight
radius: batteryIcon.radius
width: batteryIcon.batWidth
CustomText {
color: Battery.colors.fg
font.pointSize: Appearance.font.size.larger / 1.5
font.weight: 800
height: track.height
horizontalAlignment: Text.AlignHCenter
text: Math.round(Battery.currentPerc * 100)
verticalAlignment: Text.AlignVCenter
width: track.width
}
Item {
clip: true
width: parent.width * Battery.currentPerc
anchors {
bottom: parent.bottom
left: parent.left
top: parent.top
}
CustomRect {
id: fill
color: Battery.colors.fg
height: track.height
radius: track.radius
width: track.width
CustomText {
id: batteryLabel
clip: true
color: Battery.colors.bg
font.pointSize: 7.5
font.weight: 800
height: track.height
horizontalAlignment: Text.AlignHCenter
text: Math.round(Battery.currentPerc * 100)
verticalAlignment: Text.AlignVCenter
width: track.width
}
}
}
} }
CustomRect { const perc = Helpers.UPower.displayDevice.percentage;
id: nub const charging = [UPowerDeviceState.Charging, UPowerDeviceState.FullyCharged, UPowerDeviceState.PendingCharge].includes(Helpers.UPower.displayDevice.state);
if (perc === 1)
anchors.verticalCenter: parent.verticalCenter return charging ? "battery_charging_full" : "battery_full";
bottomRightRadius: 20 let level = Math.floor(perc * 7);
color: Battery.currentPerc < 0.99 ? track.color : fill.color if (charging && (level === 4 || level === 1))
height: batteryIcon.nubHeight level--;
topRightRadius: 20 return charging ? `battery_charging_${(level + 3) * 10}` : `battery_${level}_bar`;
width: batteryIcon.nubWidth
}
}
}
Loader {
id: upowerIconLoader
active: !Battery.isLaptop
anchors.centerIn: parent
sourceComponent: RowLayout {
id: upowerIcon
MaterialIcon {
Layout.alignment: Qt.AlignVCenter
animate: true
fill: 1
text: {
if (PowerProfiles.profile === PowerProfile.PowerSaver)
return "nest_eco_leaf";
if (PowerProfiles.profile === PowerProfile.Performance)
return "bolt";
return "power_settings_new";
}
}
} }
} }
} }
+112 -123
View File
@@ -11,161 +11,150 @@ import qs.Helpers
CustomClippingRect { CustomClippingRect {
id: root id: root
readonly property bool hasUpdates: Object.keys(Updates.updates)?.length > 0
readonly property int itemHeight: 50 + Appearance.padding.smaller * 2 readonly property int itemHeight: 50 + Appearance.padding.smaller * 2
required property var wrapper required property var wrapper
color: DynamicColors.tPalette.m3surfaceContainer color: DynamicColors.tPalette.m3surfaceContainer
implicitHeight: hasUpdates ? updatesListLoader.item?.implicitHeight + Appearance.padding.small * 2 : noUpdatesLoader.item.height implicitHeight: updatesList.visible ? updatesList.implicitHeight + Appearance.padding.small * 2 : noUpdates.height
implicitWidth: hasUpdates ? updatesListLoader.item?.contentWidth + Appearance.padding.small * 2 : noUpdatesLoader.item.width implicitWidth: updatesList.visible ? updatesList.contentWidth + Appearance.padding.small * 2 : noUpdates.width
radius: Appearance.rounding.small radius: Appearance.rounding.small
Loader { Item {
id: noUpdatesLoader id: noUpdates
active: !root.hasUpdates
anchors.centerIn: parent anchors.centerIn: parent
height: 200
visible: script.values.length === 0
width: 600
sourceComponent: Item { MaterialIcon {
id: noUpdates id: noUpdatesIcon
height: 200 anchors.horizontalCenter: parent.horizontalCenter
width: 300 anchors.top: parent.top
color: DynamicColors.tPalette.m3onSurfaceVariant
font.pointSize: Appearance.font.size.extraLarge * 3
horizontalAlignment: Text.AlignHCenter
text: "check"
}
MaterialIcon { CustomText {
id: noUpdatesIcon anchors.horizontalCenter: parent.horizontalCenter
anchors.top: noUpdatesIcon.bottom
anchors.horizontalCenter: parent.horizontalCenter color: DynamicColors.tPalette.m3onSurfaceVariant
anchors.top: parent.top horizontalAlignment: Text.AlignHCenter
color: DynamicColors.tPalette.m3onSurfaceVariant text: qsTr("No updates available")
font.pointSize: Appearance.font.size.extraLarge * 3 verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
text: "check"
}
CustomText {
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: noUpdatesIcon.bottom
color: DynamicColors.tPalette.m3onSurfaceVariant
horizontalAlignment: Text.AlignHCenter
text: qsTr("No updates available")
verticalAlignment: Text.AlignVCenter
}
} }
} }
Loader { CustomListView {
id: updatesListLoader id: updatesList
active: root.hasUpdates
anchors.centerIn: parent anchors.centerIn: parent
contentHeight: childrenRect.height
contentWidth: 600
displayMarginBeginning: root.itemHeight
displayMarginEnd: root.itemHeight
implicitHeight: Math.min(contentHeight, (root.itemHeight + spacing) * 5 - spacing)
implicitWidth: contentWidth
spacing: Appearance.spacing.normal
visible: script.values.length > 0
sourceComponent: CustomListView { delegate: CustomRect {
id: updatesList id: update
contentHeight: childrenRect.height required property var modelData
contentWidth: 600 readonly property list<string> sections: modelData.update.split(" ")
displayMarginBeginning: root.itemHeight
displayMarginEnd: root.itemHeight
implicitHeight: Math.min(contentHeight, (root.itemHeight + spacing) * 5 - spacing)
implicitWidth: contentWidth
spacing: Appearance.spacing.normal
delegate: CustomRect { // anchors.left: parent.left
id: update // anchors.right: parent.right
color: DynamicColors.tPalette.m3surfaceContainer
implicitHeight: root.itemHeight
implicitWidth: 600
radius: Appearance.rounding.small - Appearance.padding.small
required property var modelData RowLayout {
readonly property list<string> sections: modelData.update.split(" ") anchors.fill: parent
anchors.leftMargin: Appearance.padding.smaller
anchors.rightMargin: Appearance.padding.smaller
// anchors.left: parent.left MaterialIcon {
// anchors.right: parent.right font.pointSize: Appearance.font.size.large * 2
color: DynamicColors.tPalette.m3surfaceContainer text: "package_2"
implicitHeight: root.itemHeight }
implicitWidth: 600
radius: Appearance.rounding.small - Appearance.padding.small ColumnLayout {
Layout.fillWidth: true
CustomText {
Layout.fillWidth: true
Layout.preferredHeight: 25
elide: Text.ElideRight
font.pointSize: Appearance.font.size.large
text: update.sections[0]
}
CustomText {
Layout.fillWidth: true
color: DynamicColors.palette.m3onSurfaceVariant
text: Updates.formatUpdateTime(update.modelData.timestamp)
}
}
RowLayout { RowLayout {
anchors.fill: parent Layout.fillHeight: true
anchors.leftMargin: Appearance.padding.smaller Layout.preferredWidth: 300
anchors.rightMargin: Appearance.padding.smaller
MarqueeText {
id: versionFrom
Layout.fillHeight: true
Layout.preferredWidth: 125
animate: true
color: DynamicColors.palette.m3tertiary
font.pointSize: Appearance.font.size.large
horizontalAlignment: Text.AlignHCenter
marqueeEnabled: true
pauseMs: 4000
text: update.sections[1]
width: 125
}
MaterialIcon { MaterialIcon {
font.pointSize: Appearance.font.size.large * 2
text: "package_2"
}
ColumnLayout {
Layout.fillWidth: true
CustomText {
Layout.fillWidth: true
Layout.preferredHeight: 25
elide: Text.ElideRight
font.pointSize: Appearance.font.size.large
text: update.sections[0]
}
CustomText {
Layout.fillWidth: true
color: DynamicColors.palette.m3onSurfaceVariant
text: Updates.formatUpdateTime(update.modelData.timestamp)
}
}
RowLayout {
Layout.fillHeight: true Layout.fillHeight: true
Layout.preferredWidth: 300 color: DynamicColors.palette.m3secondary
font.pointSize: Appearance.font.size.extraLarge
horizontalAlignment: Text.AlignHCenter
text: "arrow_right_alt"
verticalAlignment: Text.AlignVCenter
}
MarqueeText { MarqueeText {
id: versionFrom id: versionTo
Layout.fillHeight: true Layout.fillHeight: true
Layout.preferredWidth: 125 Layout.preferredWidth: 120
animate: true animate: true
color: DynamicColors.palette.m3tertiary color: DynamicColors.palette.m3primary
font.pointSize: Appearance.font.size.large font.pointSize: Appearance.font.size.large
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
marqueeEnabled: true marqueeEnabled: true
pauseMs: 4000 pauseMs: 4000
text: update.sections[1] text: update.sections[3]
width: 125 width: 125
}
MaterialIcon {
Layout.fillHeight: true
color: DynamicColors.palette.m3secondary
font.pointSize: Appearance.font.size.extraLarge
horizontalAlignment: Text.AlignHCenter
text: "arrow_right_alt"
verticalAlignment: Text.AlignVCenter
}
MarqueeText {
id: versionTo
Layout.fillHeight: true
Layout.preferredWidth: 120
animate: true
color: DynamicColors.palette.m3primary
font.pointSize: Appearance.font.size.large
horizontalAlignment: Text.AlignHCenter
marqueeEnabled: true
pauseMs: 4000
text: update.sections[3]
width: 125
}
} }
} }
} }
model: ScriptModel { }
id: script model: ScriptModel {
id: script
objectProp: "update" objectProp: "update"
values: Object.entries(Updates.updates).sort((a, b) => b[1] - a[1]).map(([update, timestamp]) => ({ values: Object.entries(Updates.updates).sort((a, b) => b[1] - a[1]).map(([update, timestamp]) => ({
update, update,
timestamp timestamp
})) }))
}
} }
} }
} }
+80
View File
@@ -0,0 +1,80 @@
pragma ComponentBehavior: Bound
import QtQuick
import qs.Components
import qs.Helpers
import qs.Config
Item {
id: root
property Image current: one
property string source: Wallpapers.current
anchors.fill: parent
Component.onCompleted: {
if (source)
Qt.callLater(() => one.update());
}
onSourceChanged: {
if (!source) {
current = null;
} else if (current === one) {
two.update();
} else {
one.update();
}
}
Img {
id: one
}
Img {
id: two
}
component Img: Image {
id: img
function update(): void {
if (source === root.source) {
root.current = this;
} else {
source = root.source;
}
}
anchors.fill: parent
asynchronous: true
fillMode: Image.PreserveAspectCrop
opacity: 0
retainWhileLoading: true
scale: Wallpapers.showPreview ? 1 : 0.8
sourceClipRect: Qt.rect(Config.background.sourceClipX, Config.background.sourceClipY, Config.background.sourceClipW, Config.background.sourceClipH)
states: State {
name: "visible"
when: root.current === img
PropertyChanges {
img.opacity: 1
img.scale: 1
}
}
transitions: Transition {
Anim {
duration: Config.background.wallFadeDuration
properties: "opacity,scale"
target: img
}
}
onStatusChanged: {
if (status === Image.Ready) {
root.current = this;
}
}
}
}
+31 -38
View File
@@ -6,7 +6,6 @@ import QtQuick
import qs.Components import qs.Components
import qs.Helpers import qs.Helpers
import qs.Config import qs.Config
import ZShell.Internal
Item { Item {
id: root id: root
@@ -16,64 +15,58 @@ Item {
function refreshData(): void { function refreshData(): void {
Hyprland.refreshMonitors(); Hyprland.refreshMonitors();
let scale = Hyprland.monitorFor(root.screen).scale; const scale = Hyprland.monitorFor(root.screen).scale;
if (scale <= 0) if (scale > 0 && img.resScale !== scale) {
scale = 1.0; // Fallback to avoid zeroes on initialization img.resScale = scale;
img.sourceSize.width = root.screen.width * scale;
if (root.screen.width > 0 && root.screen.height > 0) {
img.screenResolution = Qt.size(root.screen.width * scale, root.screen.height * scale);
} }
const displayData = Wallpapers.getCrop(root.screen.name); 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);
if (displayData) { img.anchors.fill = null;
img.cropX = displayData.x !== undefined ? displayData.x : 0.0; img.zoom = displayData.zoom;
img.cropY = displayData.y !== undefined ? displayData.y : 0.0; img.x = -(displayRect.x * displayData.zoom / img.resScale);
img.cropWidth = (displayData.width !== undefined && displayData.width > 0) ? displayData.width : 1.0; img.y = -(displayRect.y * displayData.zoom / img.resScale);
img.cropHeight = (displayData.height !== undefined && displayData.height > 0) ? displayData.height : 1.0;
}
} }
anchors.fill: parent anchors.fill: parent
Component.onCompleted: root.refreshData() Image {
Connections {
function onHeightChanged() {
root.refreshData();
}
function onWidthChanged() {
root.refreshData();
}
target: root.screen
}
WallpaperImage {
id: img id: img
anchors.fill: parent property int displayH
source: root.source property int displayW
property real resScale
property real zoom: 1.0
Behavior on cropHeight { asynchronous: true
fillMode: Image.PreserveAspectCrop
height: implicitHeight * zoom / resScale
opacity: 1
retainWhileLoading: true
source: root.source
sourceSize.width: root.screen.width * resScale
width: implicitWidth * zoom / resScale
Behavior on height {
Anim { Anim {
} }
} }
Behavior on cropWidth { Behavior on width {
Anim { Anim {
} }
} }
Behavior on cropX { Behavior on x {
Anim { Anim {
} }
} }
Behavior on cropY { Behavior on y {
Anim { Anim {
} }
} }
Behavior on zoom {
Anim { onStatusChanged: {
if (img.status == Image.Ready) {
root.refreshData();
} }
} }
+1 -1
View File
@@ -6,7 +6,7 @@ import qs.Modules.DesktopIcons
Loader { Loader {
active: Config.background.enabled active: Config.background.enabled
asynchronous: false asynchronous: true
sourceComponent: Variants { sourceComponent: Variants {
model: Quickshell.screens model: Quickshell.screens
+49 -4
View File
@@ -15,8 +15,9 @@ Item {
property real currentCenter property real currentCenter
property alias currentName: popoutState.currentName property alias currentName: popoutState.currentName
property string detachedMode property string detachedMode
readonly property bool isDetached: detachedMode.length > 0
property alias hasCurrent: popoutState.hasCurrent property alias hasCurrent: popoutState.hasCurrent
readonly property real nonAnimHeight: content.implicitHeight || 150 readonly property real nonAnimHeight: children.find(c => c.shouldBeActive)?.implicitHeight ?? content.implicitHeight
readonly property real nonAnimWidth: children.find(c => c.shouldBeActive)?.implicitWidth ?? content.implicitWidth readonly property real nonAnimWidth: children.find(c => c.shouldBeActive)?.implicitWidth ?? content.implicitWidth
required property real offsetScale required property real offsetScale
property string queuedMode property string queuedMode
@@ -27,13 +28,29 @@ Item {
detachedMode = ""; detachedMode = "";
} }
function detach(mode: string): void {
setAnims(true);
if (mode === "winfo") {
detachedMode = mode;
} else {
queuedMode = mode;
detachedMode = "any";
}
setAnims(false);
focus = true;
}
function setAnims(detach: bool): void {
const type = `expressive${detach ? "Slow" : "Default"}Spatial`;
animLength = Appearance.anim.durations[type];
animCurve = Appearance.anim.curves[type];
}
focus: hasCurrent focus: hasCurrent
implicitHeight: nonAnimHeight implicitHeight: nonAnimHeight
implicitWidth: nonAnimWidth implicitWidth: nonAnimWidth
Behavior on implicitHeight { Behavior on implicitHeight {
enabled: root.offsetScale < 1
Anim { Anim {
duration: root.animLength duration: root.animLength
easing.bezierCurve: root.animCurve easing.bezierCurve: root.animCurve
@@ -55,14 +72,42 @@ Item {
Comp { Comp {
id: content id: content
// anchors.horizontalCenter: parent.horizontalCenter
// anchors.top: parent.top
anchors.centerIn: parent anchors.centerIn: parent
shouldBeActive: root.hasCurrent shouldBeActive: root.hasCurrent && !root.detachedMode
sourceComponent: Content { sourceComponent: Content {
popouts: popoutState popouts: popoutState
} }
} }
// Comp {
// id: winfo
//
// anchors.centerIn: parent
// shouldBeActive: root.detachedMode === "winfo"
//
// sourceComponent: WindowInfo {
// client: Hypr.activeToplevel
// screen: root.screen
// }
// }
//
// Comp {
// id: controlCenter
//
// anchors.centerIn: parent
// shouldBeActive: root.detachedMode === "any"
//
// sourceComponent: ControlCenter {
// active: root.queuedMode
// screen: root.screen
//
// onClose: root.close()
// }
// }
component Comp: Loader { component Comp: Loader {
id: comp id: comp
+4 -4
View File
@@ -18,13 +18,13 @@ public:
explicit BlobGroup(QObject* parent = nullptr); explicit BlobGroup(QObject* parent = nullptr);
~BlobGroup() override; ~BlobGroup() override;
[[nodiscard]] qreal smoothing() const { qreal smoothing() const {
return m_smoothing; return m_smoothing;
} }
void setSmoothing(qreal s); void setSmoothing(qreal s);
[[nodiscard]] QColor color() const { QColor color() const {
return m_color; return m_color;
} }
@@ -36,11 +36,11 @@ void removeShape(BlobShape* shape);
void setInvertedRect(BlobInvertedRect* rect); void setInvertedRect(BlobInvertedRect* rect);
void clearInvertedRect(BlobInvertedRect* rect); void clearInvertedRect(BlobInvertedRect* rect);
[[nodiscard]] const QList<BlobShape*>& shapes() const { const QList<BlobShape*>& shapes() const {
return m_shapes; return m_shapes;
} }
[[nodiscard]] BlobInvertedRect* invertedRect() const { BlobInvertedRect* invertedRect() const {
return m_invertedRect; return m_invertedRect;
} }
+5 -5
View File
@@ -16,25 +16,25 @@ public:
explicit BlobInvertedRect(QQuickItem* parent = nullptr); explicit BlobInvertedRect(QQuickItem* parent = nullptr);
~BlobInvertedRect() override; ~BlobInvertedRect() override;
[[nodiscard]] qreal borderLeft() const { qreal borderLeft() const {
return m_borderLeft; return m_borderLeft;
} }
void setBorderLeft(qreal v); void setBorderLeft(qreal v);
[[nodiscard]] qreal borderRight() const { qreal borderRight() const {
return m_borderRight; return m_borderRight;
} }
void setBorderRight(qreal v); void setBorderRight(qreal v);
[[nodiscard]] qreal borderTop() const { qreal borderTop() const {
return m_borderTop; return m_borderTop;
} }
void setBorderTop(qreal v); void setBorderTop(qreal v);
[[nodiscard]] qreal borderBottom() const { qreal borderBottom() const {
return m_borderBottom; return m_borderBottom;
} }
@@ -47,7 +47,7 @@ void borderTopChanged();
void borderBottomChanged(); void borderBottomChanged();
protected: protected:
[[nodiscard]] bool isInvertedRect() const override { bool isInvertedRect() const override {
return true; return true;
} }
+2 -2
View File
@@ -21,8 +21,8 @@ struct BlobRectData {
class BlobMaterial : public QSGMaterial { class BlobMaterial : public QSGMaterial {
public: public:
[[nodiscard]] QSGMaterialType* type() const override; QSGMaterialType* type() const override;
[[nodiscard]] QSGMaterialShader* createShader(QSGRendererInterface::RenderMode) const override; QSGMaterialShader* createShader(QSGRendererInterface::RenderMode) const override;
int compare(const QSGMaterial* other) const override; int compare(const QSGMaterial* other) const override;
float m_paddedX = 0; float m_paddedX = 0;
+9 -23
View File
@@ -14,11 +14,15 @@ BlobRect::~BlobRect() {
} }
void BlobRect::updatePolish() { void BlobRect::updatePolish() {
BlobShape::updatePolish();
if (m_physicsActive) { if (m_physicsActive) {
// Check if deformation is visually imperceptible
float totalDelta = std::abs(m_dm00 - 1.0f) + std::abs(m_dm01) + std::abs(m_dm11 - 1.0f); float totalDelta = std::abs(m_dm00 - 1.0f) + std::abs(m_dm01) + std::abs(m_dm11 - 1.0f);
float totalVel = std::abs(m_dmVel00) + std::abs(m_dmVel01) + std::abs(m_dmVel11); float totalVel = std::abs(m_dmVel00) + std::abs(m_dmVel01) + std::abs(m_dmVel11);
if (totalDelta < 0.004f && totalVel < 0.05f) { if (totalDelta < 0.004f && totalVel < 0.05f) {
// Snap to rest, no visible deformation
m_dm00 = 1.0f; m_dm00 = 1.0f;
m_dm01 = 0.0f; m_dm01 = 0.0f;
m_dm11 = 1.0f; m_dm11 = 1.0f;
@@ -27,16 +31,6 @@ void BlobRect::updatePolish() {
emit rawDeformMatrixChanged(); emit rawDeformMatrixChanged();
updateCenteredDeformMatrix(); updateCenteredDeformMatrix();
m_physicsActive = false; m_physicsActive = false;
if (m_group) {
QMetaObject::invokeMethod(
this,
[this]() {
if (m_group)
m_group->markDirty();
},
Qt::QueuedConnection);
}
} else { } else {
QMetaObject::invokeMethod( QMetaObject::invokeMethod(
this, this,
@@ -47,8 +41,6 @@ void BlobRect::updatePolish() {
Qt::QueuedConnection); Qt::QueuedConnection);
} }
} }
BlobShape::updatePolish();
} }
void BlobRect::updatePhysics() { void BlobRect::updatePhysics() {
@@ -64,6 +56,7 @@ void BlobRect::updatePhysics() {
const float dt = static_cast<float>(m_elapsed.restart()) / 1000.0f; const float dt = static_cast<float>(m_elapsed.restart()) / 1000.0f;
if (dt > 0.1f || dt < 0.001f) { if (dt > 0.1f || dt < 0.001f) {
m_prevScenePos = scenePos; m_prevScenePos = scenePos;
// Still check atRest on skipped frames to avoid getting stuck
if (m_physicsActive) if (m_physicsActive)
checkAtRest(0.0f); checkAtRest(0.0f);
return; return;
@@ -81,6 +74,8 @@ void BlobRect::updatePhysics() {
m_physicsActive = true; m_physicsActive = true;
} }
// Compute target deformation matrix from velocity
// R(θ) * diag(stretch, compress) * R(θ)^T
const float kStretchFactor = static_cast<float>(m_deformScale); const float kStretchFactor = static_cast<float>(m_deformScale);
constexpr float kMaxStretch = 0.35f; constexpr float kMaxStretch = 0.35f;
@@ -103,6 +98,7 @@ void BlobRect::updatePhysics() {
target11 = targetStretch * sin2 + targetCompress * cos2; target11 = targetStretch * sin2 + targetCompress * cos2;
} }
// Underdamped spring on each matrix component
const float kStiffness = static_cast<float>(m_stiffness); const float kStiffness = static_cast<float>(m_stiffness);
const float kDamping = static_cast<float>(m_damping); const float kDamping = static_cast<float>(m_damping);
@@ -242,19 +238,9 @@ void BlobRect::checkAtRest(float speed) {
m_dmVel00 = 0.0f; m_dmVel00 = 0.0f;
m_dmVel01 = 0.0f; m_dmVel01 = 0.0f;
m_dmVel11 = 0.0f; m_dmVel11 = 0.0f;
m_deformMatrix = QMatrix4x4(); m_deformMatrix = QMatrix4x4(); // identity
emit rawDeformMatrixChanged(); emit rawDeformMatrixChanged();
updateCenteredDeformMatrix(); updateCenteredDeformMatrix();
m_physicsActive = false; m_physicsActive = false;
if (m_group) {
QMetaObject::invokeMethod(
this,
[this]() {
if (m_group)
m_group->markDirty();
},
Qt::QueuedConnection);
}
} }
} }
+7 -7
View File
@@ -24,7 +24,7 @@ public:
explicit BlobRect(QQuickItem* parent = nullptr); explicit BlobRect(QQuickItem* parent = nullptr);
~BlobRect() override; ~BlobRect() override;
[[nodiscard]] qreal stiffness() const { qreal stiffness() const {
return m_stiffness; return m_stiffness;
} }
@@ -35,7 +35,7 @@ void setStiffness(qreal s) {
} }
} }
[[nodiscard]] qreal damping() const { qreal damping() const {
return m_damping; return m_damping;
} }
@@ -46,7 +46,7 @@ void setDamping(qreal d) {
} }
} }
[[nodiscard]] qreal deformScale() const { qreal deformScale() const {
return m_deformScale; return m_deformScale;
} }
@@ -62,25 +62,25 @@ QQmlListProperty<BlobRect> exclude();
bool isExcluded(const BlobShape* other) const override; bool isExcluded(const BlobShape* other) const override;
void cornerRadii(float out[4]) const override; void cornerRadii(float out[4]) const override;
[[nodiscard]] qreal topLeftRadius() const { qreal topLeftRadius() const {
return m_topLeftRadius; return m_topLeftRadius;
} }
void setTopLeftRadius(qreal r); void setTopLeftRadius(qreal r);
[[nodiscard]] qreal topRightRadius() const { qreal topRightRadius() const {
return m_topRightRadius; return m_topRightRadius;
} }
void setTopRightRadius(qreal r); void setTopRightRadius(qreal r);
[[nodiscard]] qreal bottomLeftRadius() const { qreal bottomLeftRadius() const {
return m_bottomLeftRadius; return m_bottomLeftRadius;
} }
void setBottomLeftRadius(qreal r); void setBottomLeftRadius(qreal r);
[[nodiscard]] qreal bottomRightRadius() const { qreal bottomRightRadius() const {
return m_bottomRightRadius; return m_bottomRightRadius;
} }
+23 -7
View File
@@ -9,6 +9,7 @@
#include <cmath> #include <cmath>
static float deformPadding(const QMatrix4x4& dm, float hw, float hh) { static float deformPadding(const QMatrix4x4& dm, float hw, float hh) {
// Bounding box of the deformed shape: |M * corners|
const float dm00 = dm(0, 0), dm01 = dm(0, 1); const float dm00 = dm(0, 0), dm01 = dm(0, 1);
const float dm10 = dm(1, 0), dm11 = dm(1, 1); const float dm10 = dm(1, 0), dm11 = dm(1, 1);
const float boundX = std::abs(dm00) * hw + std::abs(dm01) * hh; const float boundX = std::abs(dm00) * hw + std::abs(dm01) * hh;
@@ -68,17 +69,16 @@ void BlobShape::geometryChange(const QRectF& newGeometry, const QRectF& oldGeome
QQuickItem::geometryChange(newGeometry, oldGeometry); QQuickItem::geometryChange(newGeometry, oldGeometry);
updateCenteredDeformMatrix(); updateCenteredDeformMatrix();
if (m_group) { if (m_group) {
// 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
// changes that are each below the threshold still trigger a dirty
// mark once their accumulated delta exceeds it.
m_pendingDw += static_cast<float>(newGeometry.width() - oldGeometry.width()); m_pendingDw += static_cast<float>(newGeometry.width() - oldGeometry.width());
m_pendingDh += static_cast<float>(newGeometry.height() - oldGeometry.height()); m_pendingDh += static_cast<float>(newGeometry.height() - oldGeometry.height());
if (std::abs(m_pendingDx) > 0.5f || std::abs(m_pendingDy) > 0.5f ||
const float deformMag = std::abs(m_deformMatrix(0, 0) - 1.0f) + std::abs(m_deformMatrix(0, 1)) + std::abs(m_pendingDw) > 0.5f || std::abs(m_pendingDh) > 0.5f) {
std::abs(m_deformMatrix(1, 0)) + std::abs(m_deformMatrix(1, 1) - 1.0f);
const float syncThreshold = deformMag > 0.001f ? 0.05f : 0.5f;
if (std::abs(m_pendingDx) > syncThreshold || std::abs(m_pendingDy) > syncThreshold ||
std::abs(m_pendingDw) > syncThreshold || std::abs(m_pendingDh) > syncThreshold) {
m_pendingDx = 0; m_pendingDx = 0;
m_pendingDy = 0; m_pendingDy = 0;
m_pendingDw = 0; m_pendingDw = 0;
@@ -124,6 +124,7 @@ void BlobShape::updatePolish() {
if (!m_group) if (!m_group)
return; return;
// Ensure all shapes have up-to-date physics (only once per frame)
m_group->ensurePhysicsUpdated(); m_group->ensurePhysicsUpdated();
const QPointF scenePos = mapToScene(QPointF(0, 0)); const QPointF scenePos = mapToScene(QPointF(0, 0));
@@ -148,11 +149,13 @@ void BlobShape::updatePolish() {
width() + 2.0 * static_cast<double>(totalPad), height() + 2.0 * static_cast<double>(totalPad)); width() + 2.0 * static_cast<double>(totalPad), height() + 2.0 * static_cast<double>(totalPad));
} }
// Filter nearby normal rects
m_cachedRects.clear(); m_cachedRects.clear();
m_cachedMyIndex = -2; m_cachedMyIndex = -2;
const QRectF myPadded(static_cast<double>(m_cachedPaddedX), static_cast<double>(m_cachedPaddedY), const QRectF myPadded(static_cast<double>(m_cachedPaddedX), static_cast<double>(m_cachedPaddedY),
static_cast<double>(m_cachedPaddedW), static_cast<double>(m_cachedPaddedH)); static_cast<double>(m_cachedPaddedW), static_cast<double>(m_cachedPaddedH));
// Track shape pointers parallel to m_cachedRects for pairwise exclusion lookups
QVector<BlobShape*> rectShapes; QVector<BlobShape*> rectShapes;
rectShapes.reserve(m_group->shapes().size()); rectShapes.reserve(m_group->shapes().size());
@@ -160,6 +163,7 @@ void BlobShape::updatePolish() {
if (other->isInvertedRect()) if (other->isInvertedRect())
continue; continue;
// Skip zero-size rects
if (other->width() <= 0 || other->height() <= 0) if (other->width() <= 0 || other->height() <= 0)
continue; continue;
@@ -198,6 +202,7 @@ void BlobShape::updatePolish() {
r.offsetX = dm(0, 3); r.offsetX = dm(0, 3);
r.offsetY = dm(1, 3); r.offsetY = dm(1, 3);
// Pre-compute inverse deformation matrix
const float det = a * d - c * b; const float det = a * d - c * b;
const float invDet = std::abs(det) > 1e-6f ? 1.0f / det : 1.0f; const float invDet = std::abs(det) > 1e-6f ? 1.0f / det : 1.0f;
r.invDeform[0] = d * invDet; r.invDeform[0] = d * invDet;
@@ -205,10 +210,12 @@ void BlobShape::updatePolish() {
r.invDeform[2] = -c * invDet; r.invDeform[2] = -c * invDet;
r.invDeform[3] = a * invDet; r.invDeform[3] = a * invDet;
// Pre-compute minimum eigenvalue (avoids per-pixel sqrt)
const float halfTr = 0.5f * (a + d); const float halfTr = 0.5f * (a + d);
const float halfDiff = 0.5f * (a - d); const float halfDiff = 0.5f * (a - d);
r.minEig = halfTr - std::sqrt(halfDiff * halfDiff + c * c); r.minEig = halfTr - std::sqrt(halfDiff * halfDiff + c * c);
// Pre-compute screen-space AABB half-extents
r.screenHalfX = std::abs(a) * r.hw + std::abs(c) * r.hh; r.screenHalfX = std::abs(a) * r.hw + std::abs(c) * r.hh;
r.screenHalfY = std::abs(b) * r.hw + std::abs(d) * r.hh; r.screenHalfY = std::abs(b) * r.hw + std::abs(d) * r.hh;
@@ -220,6 +227,8 @@ void BlobShape::updatePolish() {
if (isInvertedRect()) if (isInvertedRect())
m_cachedMyIndex = -1; m_cachedMyIndex = -1;
// Compute pairwise exclude masks. Bit j in entry i is set iff rect i excludes rect j
// or rect j excludes rect i. The shader uses this to avoid smin between excluded pairs.
const auto cachedCount = m_cachedRects.size(); const auto cachedCount = m_cachedRects.size();
for (qsizetype i = 0; i < cachedCount; ++i) { for (qsizetype i = 0; i < cachedCount; ++i) {
int mask = 0; int mask = 0;
@@ -234,6 +243,7 @@ void BlobShape::updatePolish() {
m_cachedRects[i].excludeMask = mask; m_cachedRects[i].excludeMask = mask;
} }
// Cache inverted rect data
m_cachedHasInverted = false; m_cachedHasInverted = false;
m_cachedInvertedRadius = 0; m_cachedInvertedRadius = 0;
memset(m_cachedInvertedOuter, 0, sizeof(m_cachedInvertedOuter)); memset(m_cachedInvertedOuter, 0, sizeof(m_cachedInvertedOuter));
@@ -252,6 +262,7 @@ void BlobShape::updatePolish() {
const float innerHW = outerHW - static_cast<float>((inv->borderLeft() + inv->borderRight()) / 2.0); const float innerHW = outerHW - static_cast<float>((inv->borderLeft() + inv->borderRight()) / 2.0);
const float innerHH = outerHH - static_cast<float>((inv->borderTop() + inv->borderBottom()) / 2.0); const float innerHH = outerHH - static_cast<float>((inv->borderTop() + inv->borderBottom()) / 2.0);
// Check if this rect is near the border (within 2x smoothing of inner edge)
bool nearBorder = isInvertedRect(); bool nearBorder = isInvertedRect();
if (!nearBorder) { if (!nearBorder) {
const float margin = pad * 2.0f; const float margin = pad * 2.0f;
@@ -259,6 +270,7 @@ void BlobShape::updatePolish() {
const float myCY = m_cachedPaddedY + m_cachedPaddedH * 0.5f; const float myCY = m_cachedPaddedY + m_cachedPaddedH * 0.5f;
const float myHW = m_cachedPaddedW * 0.5f; const float myHW = m_cachedPaddedW * 0.5f;
const float myHH = m_cachedPaddedH * 0.5f; const float myHH = m_cachedPaddedH * 0.5f;
// Near border if any edge of padded rect is within margin of inner edge
nearBorder = (myCX - myHW < innerCX - innerHW + margin) || (myCX + myHW > innerCX + innerHW - margin) || nearBorder = (myCX - myHW < innerCX - innerHW + margin) || (myCX + myHW > innerCX + innerHW - margin) ||
(myCY - myHH < innerCY - innerHH + margin) || (myCY + myHH > innerCY + innerHH - margin); (myCY - myHH < innerCY - innerHH + margin) || (myCY + myHH > innerCY + innerHH - margin);
} }
@@ -279,6 +291,7 @@ void BlobShape::updatePolish() {
} }
} }
// Pre-compute effective per-corner radii (moves O(N²) work from GPU to CPU)
const float smoothFactor = pad; const float smoothFactor = pad;
constexpr float minR = 2.0f; constexpr float minR = 2.0f;
const auto rectCount = m_cachedRects.size(); const auto rectCount = m_cachedRects.size();
@@ -315,6 +328,7 @@ void BlobShape::updatePolish() {
fTl = std::min(fTl, cpuSmoothstep(0.0f, smoothFactor, -cpuSdBox(cTlX, cTlY, icx, icy, ihw, ihh))); fTl = std::min(fTl, cpuSmoothstep(0.0f, smoothFactor, -cpuSdBox(cTlX, cTlY, icx, icy, ihw, ihh)));
} }
// Combine base radii with fill factors into effective per-corner radii
ri.radius[0] = std::max(ri.radius[0] * fTr, minR); ri.radius[0] = std::max(ri.radius[0] * fTr, minR);
ri.radius[1] = std::max(ri.radius[1] * fBr, minR); ri.radius[1] = std::max(ri.radius[1] * fBr, minR);
ri.radius[2] = std::max(ri.radius[2] * fBl, minR); ri.radius[2] = std::max(ri.radius[2] * fBl, minR);
@@ -343,6 +357,7 @@ QSGNode* BlobShape::updatePaintNode(QSGNode* oldNode, UpdatePaintNodeData*) {
node->setFlag(QSGNode::OwnsMaterial); node->setFlag(QSGNode::OwnsMaterial);
} }
// Update geometry
auto* geometry = node->geometry(); auto* geometry = node->geometry();
auto* v = geometry->vertexDataAsTexturedPoint2D(); auto* v = geometry->vertexDataAsTexturedPoint2D();
@@ -358,6 +373,7 @@ QSGNode* BlobShape::updatePaintNode(QSGNode* oldNode, UpdatePaintNodeData*) {
node->markDirty(QSGNode::DirtyGeometry); node->markDirty(QSGNode::DirtyGeometry);
// Update material
auto* material = static_cast<BlobMaterial*>(node->material()); auto* material = static_cast<BlobMaterial*>(node->material());
material->m_paddedX = m_cachedPaddedX; material->m_paddedX = m_cachedPaddedX;
material->m_paddedY = m_cachedPaddedY; material->m_paddedY = m_cachedPaddedY;
+11 -10
View File
@@ -21,23 +21,23 @@ public:
explicit BlobShape(QQuickItem* parent = nullptr); explicit BlobShape(QQuickItem* parent = nullptr);
~BlobShape() override = default; ~BlobShape() override = default;
[[nodiscard]] BlobGroup* group() const { BlobGroup* group() const {
return m_group; return m_group;
} }
void setGroup(BlobGroup* g); void setGroup(BlobGroup* g);
[[nodiscard]] qreal radius() const { qreal radius() const {
return m_radius; return m_radius;
} }
void setRadius(qreal r); void setRadius(qreal r);
[[nodiscard]] QMatrix4x4 deformMatrix() const { QMatrix4x4 deformMatrix() const {
return m_centeredDeformMatrix; return m_centeredDeformMatrix;
} }
[[nodiscard]] QMatrix4x4 rawDeformMatrix() const { QMatrix4x4 rawDeformMatrix() const {
return m_deformMatrix; return m_deformMatrix;
} }
@@ -53,7 +53,7 @@ void geometryChange(const QRectF& newGeometry, const QRectF& oldGeometry) overri
void updatePolish() override; void updatePolish() override;
QSGNode* updatePaintNode(QSGNode* oldNode, UpdatePaintNodeData*) override; QSGNode* updatePaintNode(QSGNode* oldNode, UpdatePaintNodeData*) override;
[[nodiscard]] virtual bool isInvertedRect() const { virtual bool isInvertedRect() const {
return false; return false;
} }
@@ -72,9 +72,10 @@ void updateCenteredDeformMatrix();
BlobGroup* m_group = nullptr; BlobGroup* m_group = nullptr;
qreal m_radius = 0; qreal m_radius = 0;
QMatrix4x4 m_deformMatrix; QMatrix4x4 m_deformMatrix; // identity by default
QMatrix4x4 m_centeredDeformMatrix; QMatrix4x4 m_centeredDeformMatrix;
// Cached data from updatePolish
float m_cachedPaddedX = 0; float m_cachedPaddedX = 0;
float m_cachedPaddedY = 0; float m_cachedPaddedY = 0;
float m_cachedPaddedW = 0; float m_cachedPaddedW = 0;
@@ -83,10 +84,10 @@ QRectF m_localPaddedRect;
QVector<BlobRectData> m_cachedRects; QVector<BlobRectData> m_cachedRects;
int m_cachedMyIndex = -2; int m_cachedMyIndex = -2;
float m_pendingDx = 0; float m_pendingDx = 0;
float m_pendingDy = 0; float m_pendingDy = 0;
float m_pendingDw = 0; float m_pendingDw = 0;
float m_pendingDh = 0; float m_pendingDh = 0;
bool m_cachedHasInverted = false; bool m_cachedHasInverted = false;
float m_cachedInvertedRadius = 0; float m_cachedInvertedRadius = 0;
float m_cachedInvertedOuter[4] = {}; float m_cachedInvertedOuter[4] = {};
float m_cachedInvertedInner[4] = {}; float m_cachedInvertedInner[4] = {};
-1
View File
@@ -50,7 +50,6 @@ qml_module(ZShell
Qt::Quick Qt::Quick
Qt::Concurrent Qt::Concurrent
Qt::Sql Qt::Sql
Qt::DBus
PkgConfig::Qalculate PkgConfig::Qalculate
) )
-2
View File
@@ -8,8 +8,6 @@ qml_module(ZShell-internal
circularbuffer.hpp circularbuffer.cpp circularbuffer.hpp circularbuffer.cpp
sparklineitem.hpp sparklineitem.cpp sparklineitem.hpp sparklineitem.cpp
arcgauge.hpp arcgauge.cpp arcgauge.hpp arcgauge.cpp
wallpaperimage.hpp wallpaperimage.cpp
lidwatcher.hpp lidwatcher.cpp
LIBRARIES LIBRARIES
Qt::Gui Qt::Gui
Qt::Quick Qt::Quick
+2 -2
View File
@@ -4,7 +4,7 @@
#include <qpainter.h> #include <qpainter.h>
#include <qpen.h> #include <qpen.h>
namespace ZShell::internal { namespace caelestia::internal {
ArcGauge::ArcGauge(QQuickItem* parent) ArcGauge::ArcGauge(QQuickItem* parent)
: QQuickPaintedItem(parent) { : QQuickPaintedItem(parent) {
@@ -116,4 +116,4 @@ void ArcGauge::setLineWidth(qreal width) {
update(); update();
} }
} // namespace ZShell::internal } // namespace caelestia::internal
+2 -2
View File
@@ -5,7 +5,7 @@
#include <qqmlintegration.h> #include <qqmlintegration.h>
#include <qquickpainteditem.h> #include <qquickpainteditem.h>
namespace ZShell::internal { namespace caelestia::internal {
class ArcGauge : public QQuickPaintedItem { class ArcGauge : public QQuickPaintedItem {
Q_OBJECT Q_OBJECT
@@ -58,4 +58,4 @@ qreal m_sweepAngle = 1.5 * M_PI;
qreal m_lineWidth = 10.0; qreal m_lineWidth = 10.0;
}; };
} // namespace ZShell::Internal } // namespace caelestia::internal
+34 -35
View File
@@ -7,60 +7,59 @@
namespace ZShell::internal { namespace ZShell::internal {
class CachingImageManager : public QObject { class CachingImageManager : public QObject {
Q_OBJECT Q_OBJECT
QML_ELEMENT QML_ELEMENT
Q_PROPERTY(QQuickItem* item READ item WRITE setItem NOTIFY itemChanged REQUIRED) Q_PROPERTY(QQuickItem* item READ item WRITE setItem NOTIFY itemChanged REQUIRED)
Q_PROPERTY(QUrl cacheDir READ cacheDir WRITE setCacheDir NOTIFY cacheDirChanged REQUIRED) Q_PROPERTY(QUrl cacheDir READ cacheDir WRITE setCacheDir NOTIFY cacheDirChanged REQUIRED)
Q_PROPERTY(QString path READ path WRITE setPath NOTIFY pathChanged) Q_PROPERTY(QString path READ path WRITE setPath NOTIFY pathChanged)
Q_PROPERTY(QUrl cachePath READ cachePath NOTIFY cachePathChanged) Q_PROPERTY(QUrl cachePath READ cachePath NOTIFY cachePathChanged)
public: public:
explicit CachingImageManager(QObject* parent = nullptr) explicit CachingImageManager(QObject* parent = nullptr)
: QObject(parent) : QObject(parent)
, m_item(nullptr) { , m_item(nullptr) {}
}
[[nodiscard]] QQuickItem* item() const; [[nodiscard]] QQuickItem* item() const;
void setItem(QQuickItem* item); void setItem(QQuickItem* item);
[[nodiscard]] QUrl cacheDir() const; [[nodiscard]] QUrl cacheDir() const;
void setCacheDir(const QUrl& cacheDir); void setCacheDir(const QUrl& cacheDir);
[[nodiscard]] QString path() const; [[nodiscard]] QString path() const;
void setPath(const QString& path); void setPath(const QString& path);
[[nodiscard]] QUrl cachePath() const; [[nodiscard]] QUrl cachePath() const;
Q_INVOKABLE void updateSource(); Q_INVOKABLE void updateSource();
Q_INVOKABLE void updateSource(const QString& path); Q_INVOKABLE void updateSource(const QString& path);
signals: signals:
void itemChanged(); void itemChanged();
void cacheDirChanged(); void cacheDirChanged();
void pathChanged(); void pathChanged();
void cachePathChanged(); void cachePathChanged();
void usingCacheChanged(); void usingCacheChanged();
private: private:
QString m_shaPath; QString m_shaPath;
QQuickItem* m_item; QQuickItem* m_item;
QUrl m_cacheDir; QUrl m_cacheDir;
QString m_path; QString m_path;
QUrl m_cachePath; QUrl m_cachePath;
QMetaObject::Connection m_widthConn; QMetaObject::Connection m_widthConn;
QMetaObject::Connection m_heightConn; QMetaObject::Connection m_heightConn;
[[nodiscard]] qreal effectiveScale() const; [[nodiscard]] qreal effectiveScale() const;
[[nodiscard]] QSize effectiveSize() const; [[nodiscard]] QSize effectiveSize() const;
void createCache(const QString& path, const QString& cache, const QString& fillMode, const QSize& size) const; void createCache(const QString& path, const QString& cache, const QString& fillMode, const QSize& size) const;
[[nodiscard]] static QString sha256sum(const QString& path); [[nodiscard]] static QString sha256sum(const QString& path);
}; };
} // namespace ZShell::internal } // namespace ZShell::internal
+2 -2
View File
@@ -2,7 +2,7 @@
#include <algorithm> #include <algorithm>
namespace ZShell::internal { namespace caelestia::internal {
CircularBuffer::CircularBuffer(QObject* parent) CircularBuffer::CircularBuffer(QObject* parent)
: QObject(parent) { : QObject(parent) {
@@ -92,4 +92,4 @@ qreal CircularBuffer::at(int index) const {
return m_data[actualIndex]; return m_data[actualIndex];
} }
} // namespace ZShell::internal } // namespace caelestia::internal
+2 -2
View File
@@ -4,7 +4,7 @@
#include <qqmlintegration.h> #include <qqmlintegration.h>
#include <qvector.h> #include <qvector.h>
namespace ZShell::internal { namespace caelestia::internal {
class CircularBuffer : public QObject { class CircularBuffer : public QObject {
Q_OBJECT Q_OBJECT
@@ -41,4 +41,4 @@ int m_count = 0;
int m_capacity = 0; int m_capacity = 0;
}; };
} // namespace ZShell::internal } // namespace caelestia::internal
@@ -1,6 +1,5 @@
#pragma once #pragma once
#include <cstdint>
#include <qeasingcurve.h> #include <qeasingcurve.h>
#include <qobject.h> #include <qobject.h>
#include <qqmlintegration.h> #include <qqmlintegration.h>
+40 -40
View File
@@ -8,67 +8,67 @@
namespace ZShell::internal::hypr { namespace ZShell::internal::hypr {
class HyprKeyboard : public QObject { class HyprKeyboard : public QObject {
Q_OBJECT Q_OBJECT
QML_ELEMENT QML_ELEMENT
QML_UNCREATABLE("HyprKeyboard instances can only be retrieved from a HyprDevices") QML_UNCREATABLE("HyprKeyboard instances can only be retrieved from a HyprDevices")
Q_PROPERTY(QVariantHash lastIpcObject READ lastIpcObject NOTIFY lastIpcObjectChanged) Q_PROPERTY(QVariantHash lastIpcObject READ lastIpcObject NOTIFY lastIpcObjectChanged)
Q_PROPERTY(QString address READ address NOTIFY addressChanged) Q_PROPERTY(QString address READ address NOTIFY addressChanged)
Q_PROPERTY(QString name READ name NOTIFY nameChanged) Q_PROPERTY(QString name READ name NOTIFY nameChanged)
Q_PROPERTY(QString layout READ layout NOTIFY layoutChanged) Q_PROPERTY(QString layout READ layout NOTIFY layoutChanged)
Q_PROPERTY(QString activeKeymap READ activeKeymap NOTIFY activeKeymapChanged) Q_PROPERTY(QString activeKeymap READ activeKeymap NOTIFY activeKeymapChanged)
Q_PROPERTY(bool capsLock READ capsLock NOTIFY capsLockChanged) Q_PROPERTY(bool capsLock READ capsLock NOTIFY capsLockChanged)
Q_PROPERTY(bool numLock READ numLock NOTIFY numLockChanged) Q_PROPERTY(bool numLock READ numLock NOTIFY numLockChanged)
Q_PROPERTY(bool main READ main NOTIFY mainChanged) Q_PROPERTY(bool main READ main NOTIFY mainChanged)
public: public:
explicit HyprKeyboard(QJsonObject ipcObject, QObject* parent = nullptr); explicit HyprKeyboard(QJsonObject ipcObject, QObject* parent = nullptr);
[[nodiscard]] QVariantHash lastIpcObject() const; [[nodiscard]] QVariantHash lastIpcObject() const;
[[nodiscard]] QString address() const; [[nodiscard]] QString address() const;
[[nodiscard]] QString name() const; [[nodiscard]] QString name() const;
[[nodiscard]] QString layout() const; [[nodiscard]] QString layout() const;
[[nodiscard]] QString activeKeymap() const; [[nodiscard]] QString activeKeymap() const;
[[nodiscard]] bool capsLock() const; [[nodiscard]] bool capsLock() const;
[[nodiscard]] bool numLock() const; [[nodiscard]] bool numLock() const;
[[nodiscard]] bool main() const; [[nodiscard]] bool main() const;
bool updateLastIpcObject(QJsonObject object); bool updateLastIpcObject(QJsonObject object);
signals: signals:
void lastIpcObjectChanged(); void lastIpcObjectChanged();
void addressChanged(); void addressChanged();
void nameChanged(); void nameChanged();
void layoutChanged(); void layoutChanged();
void activeKeymapChanged(); void activeKeymapChanged();
void capsLockChanged(); void capsLockChanged();
void numLockChanged(); void numLockChanged();
void mainChanged(); void mainChanged();
private: private:
QJsonObject m_lastIpcObject; QJsonObject m_lastIpcObject;
}; };
class HyprDevices : public QObject { class HyprDevices : public QObject {
Q_OBJECT Q_OBJECT
QML_ELEMENT QML_ELEMENT
QML_UNCREATABLE("HyprDevices instances can only be retrieved from a HyprExtras") QML_UNCREATABLE("HyprDevices instances can only be retrieved from a HyprExtras")
Q_PROPERTY( Q_PROPERTY(
QQmlListProperty<ZShell::internal::hypr::HyprKeyboard> keyboards READ keyboards NOTIFY keyboardsChanged) QQmlListProperty<ZShell::internal::hypr::HyprKeyboard> keyboards READ keyboards NOTIFY keyboardsChanged)
public: public:
explicit HyprDevices(QObject* parent = nullptr); explicit HyprDevices(QObject* parent = nullptr);
[[nodiscard]] QQmlListProperty<HyprKeyboard> keyboards(); [[nodiscard]] QQmlListProperty<HyprKeyboard> keyboards();
bool updateLastIpcObject(QJsonObject object); bool updateLastIpcObject(QJsonObject object);
signals: signals:
void keyboardsChanged(); void keyboardsChanged();
private: private:
QList<HyprKeyboard*> m_keyboards; QList<HyprKeyboard*> m_keyboards;
}; };
} // namespace ZShell::internal::hypr } // namespace ZShell::internal::hypr
+19 -140
View File
@@ -1,18 +1,11 @@
#include "hyprextras.hpp" #include "hyprextras.hpp"
#include "hyprdevices.hpp" #include "hyprdevices.hpp"
#include <functional>
#include <memory>
#include <qdir.h> #include <qdir.h>
#include <qcolor.h>
#include <qjsonarray.h> #include <qjsonarray.h>
#include <qjsondocument.h>
#include <qjsonobject.h>
#include <qlocalsocket.h> #include <qlocalsocket.h>
#include <qloggingcategory.h> #include <qloggingcategory.h>
#include <qmetatype.h> #include <qmetatype.h>
#include <qobject.h>
#include <qregularexpression.h> #include <qregularexpression.h>
#include <qvariant.h> #include <qvariant.h>
@@ -170,86 +163,6 @@ static QString buildHlConfigCall(const QString& key, const QVariant& value) {
return out; return out;
} }
static QColor colorFromInt(quint32 value) {
const int a = (value >> 24) & 0xFF;
const int r = (value >> 16) & 0xFF;
const int g = (value >> 8) & 0xFF;
const int b = value & 0xFF;
return QColor(r, g, b, a);
}
static QVariant parseGetOptionValue(const QJsonObject& obj) {
if (obj.contains(QStringLiteral("bool"))) {
return obj.value(QStringLiteral("bool")).toBool();
}
if (obj.contains(QStringLiteral("int"))) {
const auto value = obj.value(QStringLiteral("int")).toInt();
const auto option = obj.value(QStringLiteral("option")).toString();
if (option.contains(QStringLiteral("color")) || option.contains(QStringLiteral("col."))) {
return colorFromInt(static_cast<quint32>(value));
}
return value;
}
if (obj.contains(QStringLiteral("float"))) {
return obj.value(QStringLiteral("float")).toDouble();
}
if (obj.contains(QStringLiteral("str"))) {
return obj.value(QStringLiteral("str")).toString();
}
if (obj.contains(QStringLiteral("current"))) {
return obj.value(QStringLiteral("current")).toVariant();
}
if (obj.contains(QStringLiteral("value"))) {
return obj.value(QStringLiteral("value")).toVariant();
}
if (obj.contains(QStringLiteral("vec2"))) {
return obj.value(QStringLiteral("vec2")).toVariant();
}
if (obj.contains(QStringLiteral("data"))) {
const auto data = obj.value(QStringLiteral("data"));
if (data.isObject()) {
const auto d = data.toObject();
if (d.contains(QStringLiteral("current"))) {
return d.value(QStringLiteral("current")).toVariant();
}
if (d.contains(QStringLiteral("value"))) {
return d.value(QStringLiteral("value")).toVariant();
}
} else {
return data.toVariant();
}
}
return {};
}
static void insertNestedValue(QVariantMap& root, const QStringList& path, const QVariant& value) {
if (path.isEmpty()) {
return;
}
if (path.size() == 1) {
root.insert(path.first(), value);
return;
}
const QString head = path.first();
QVariantMap child = root.value(head).toMap();
insertNestedValue(child, path.mid(1), value);
root.insert(head, child);
}
} // namespace } // namespace
HyprExtras::HyprExtras(QObject* parent) HyprExtras::HyprExtras(QObject* parent)
@@ -290,7 +203,7 @@ HyprExtras::HyprExtras(QObject* parent)
m_socket->connectToServer(m_eventSocket, QLocalSocket::ReadOnly); m_socket->connectToServer(m_eventSocket, QLocalSocket::ReadOnly);
} }
QVariantMap HyprExtras::options() const { QVariantHash HyprExtras::options() const {
return m_options; return m_options;
} }
@@ -356,64 +269,30 @@ void HyprExtras::refreshOptions() {
m_optionsRefresh->close(); m_optionsRefresh->close();
} }
++m_optionsRefreshGeneration; m_optionsRefresh = makeRequestJson(QStringLiteral("descriptions"), [this](bool success, const QJsonDocument& response) {
const quint64 generation = m_optionsRefreshGeneration; m_optionsRefresh.reset();
if (!success) {
static const QStringList optionKeys = {
QStringLiteral("general:border_size"),
QStringLiteral("decoration:rounding"),
QStringLiteral("animations:enabled"),
QStringLiteral("decoration:shadow:enabled"),
QStringLiteral("decoration:shadow:offset"),
QStringLiteral("decoration:shadow:color"),
QStringLiteral("decoration:shadow:range"),
QStringLiteral("decoration:shadow:render_power"),
};
auto nextOptions = std::make_shared<QVariantMap>();
auto step = std::make_shared<std::function<void(int)> >();
*step = [this, generation, nextOptions, step](int index) {
if (generation != m_optionsRefreshGeneration) {
return; return;
} }
if (index >= optionKeys.size()) { const auto options = response.array();
if (m_options != *nextOptions) { bool dirty = false;
m_options = *nextOptions;
emit optionsChanged(); for (const auto& o : std::as_const(options)) {
const auto obj = o.toObject();
const auto key = obj.value(QStringLiteral("value")).toString();
const auto value = obj.value(QStringLiteral("data")).toObject().value(QStringLiteral("current")).toVariant();
if (m_options.value(key) != value) {
dirty = true;
m_options.insert(key, value);
} }
return;
} }
const QString key = optionKeys.at(index); if (dirty) {
emit optionsChanged();
m_optionsRefresh = makeRequestJson( }
QStringLiteral("getoption ") + key, });
[this, generation, nextOptions, step, index, key](bool success, const QJsonDocument& response)
{
m_optionsRefresh.reset();
if (generation != m_optionsRefreshGeneration) {
return;
}
if (success && response.isObject()) {
const QVariant value = parseGetOptionValue(response.object());
if (value.isValid()) {
insertNestedValue(*nextOptions, key.split(QLatin1Char(':'), Qt::SkipEmptyParts), value);
} else {
qCWarning(lcHypr) << "refreshOptions: getoption returned no usable value for" << key;
}
} else if (!success) {
qCWarning(lcHypr) << "refreshOptions: getoption request error for" << key;
}
(*step)(index + 1);
});
};
(*step)(0);
} }
void HyprExtras::refreshDevices() { void HyprExtras::refreshDevices() {
+3 -8
View File
@@ -1,13 +1,9 @@
#pragma once #pragma once
#include <functional>
#include <qjsondocument.h>
#include <qlocalsocket.h> #include <qlocalsocket.h>
#include <qobject.h> #include <qobject.h>
#include <qqmlintegration.h> #include <qqmlintegration.h>
#include <qsharedpointer.h> #include <qsharedpointer.h>
#include <qstringlist.h>
#include <qvariant.h> #include <qvariant.h>
namespace ZShell::internal::hypr { namespace ZShell::internal::hypr {
@@ -19,13 +15,13 @@ Q_OBJECT
QML_ELEMENT QML_ELEMENT
Q_MOC_INCLUDE("hyprdevices.hpp") Q_MOC_INCLUDE("hyprdevices.hpp")
Q_PROPERTY(QVariantMap 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]] QVariantMap 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);
@@ -46,12 +42,11 @@ QString m_eventSocket;
QLocalSocket* m_socket; QLocalSocket* m_socket;
bool m_socketValid; bool m_socketValid;
QVariantMap 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;
quint64 m_optionsRefreshGeneration = 0;
void socketError(QLocalSocket::LocalSocketError error) const; void socketError(QLocalSocket::LocalSocketError error) const;
void socketStateChanged(QLocalSocket::LocalSocketState state); void socketStateChanged(QLocalSocket::LocalSocketState state);
-86
View File
@@ -1,86 +0,0 @@
#include "lidwatcher.hpp"
#include <QtDBus/qdbusconnection.h>
#include <QtDBus/qdbuserror.h>
#include <QtDBus/qdbusinterface.h>
#include <QtDBus/qdbusreply.h>
#include <qloggingcategory.h>
Q_LOGGING_CATEGORY(lcLidWatcher, "ZShell.internal.logindmanager", QtInfoMsg)
namespace ZShell::internal {
LidWatcher::LidWatcher(QObject* parent) : QObject(parent) {
auto bus = QDBusConnection::systemBus();
if (!bus.isConnected()) {
qCWarning(lcLidWatcher)
<< "Failed to connect to system bus:" << bus.lastError().message();
return;
}
bool ok = bus.connect("org.freedesktop.login1",
"/org/freedesktop/login1",
"org.freedesktop.login1.Manager",
"PrepareForSleep",
this,
SLOT(handlePrepareForSleep(bool)));
if (!ok) {
qCWarning(lcLidWatcher)
<< "Failed to connect to PrepareForSleep signal:"
<< bus.lastError().message();
}
QDBusInterface login1("org.freedesktop.login1",
"/org/freedesktop/login1",
"org.freedesktop.login1.Manager",
bus);
const QDBusReply<QDBusObjectPath> reply = login1.call("GetSession", "auto");
if (!reply.isValid()) {
qCWarning(lcLidWatcher) << "Failed to get session path";
return;
}
const auto sessionPath = reply.value().path();
ok = bus.connect("org.freedesktop.login1",
sessionPath,
"org.freedesktop.login1.Session",
"Lock",
this,
SLOT(handleLockRequested()));
if (!ok) {
qCWarning(lcLidWatcher)
<< "Failed to connect to Lock signal:" << bus.lastError().message();
}
ok = bus.connect("org.freedesktop.login1",
sessionPath,
"org.freedesktop.login1.Session",
"Unlock",
this,
SLOT(handleUnlockRequested()));
if (!ok) {
qCWarning(lcLidWatcher) << "Failed to connect to Unlock signal:"
<< bus.lastError().message();
}
}
void LidWatcher::handlePrepareForSleep(bool sleep) {
if (sleep) {
emit aboutToSleep();
} else {
emit resumed();
}
}
void LidWatcher::handleLockRequested() {
emit lockRequested();
}
void LidWatcher::handleUnlockRequested() {
emit unlockRequested();
}
} // namespace ZShell::internal
-27
View File
@@ -1,27 +0,0 @@
#pragma once
#include <qobject.h>
#include <qqmlintegration.h>
namespace ZShell::internal {
class LidWatcher : public QObject {
Q_OBJECT
QML_ELEMENT
public:
explicit LidWatcher(QObject* parent = nullptr);
signals:
void aboutToSleep();
void resumed();
void lockRequested();
void unlockRequested();
private slots:
void handlePrepareForSleep(bool sleep);
void handleLockRequested();
void handleUnlockRequested();
};
} // namespace ZShell::internal
+2 -2
View File
@@ -4,7 +4,7 @@
#include <qpainterpath.h> #include <qpainterpath.h>
#include <qpen.h> #include <qpen.h>
namespace ZShell::internal { namespace caelestia::internal {
SparklineItem::SparklineItem(QQuickItem* parent) SparklineItem::SparklineItem(QQuickItem* parent)
: QQuickPaintedItem(parent) { : QQuickPaintedItem(parent) {
@@ -212,4 +212,4 @@ void SparklineItem::setLineWidth(qreal width) {
update(); update();
} }
} // namespace ZShell::internal } // namespace caelestia::internal
+2 -2
View File
@@ -7,7 +7,7 @@
#include "circularbuffer.hpp" #include "circularbuffer.hpp"
namespace ZShell::internal { namespace caelestia::internal {
class SparklineItem : public QQuickPaintedItem { class SparklineItem : public QQuickPaintedItem {
Q_OBJECT Q_OBJECT
@@ -87,4 +87,4 @@ int m_historyLength = 30;
qreal m_lineWidth = 2.0; qreal m_lineWidth = 2.0;
}; };
} // namespace ZShell::internal } // namespace caelestia::internal
-199
View File
@@ -1,199 +0,0 @@
#include "wallpaperimage.hpp"
#include <QStandardPaths>
#include <QCryptographicHash>
#include <QDir>
#include <QFileInfo>
#include <QtConcurrent>
#include <QSGImageNode>
#include <QQuickWindow>
namespace ZShell::internal {
WallpaperImage::WallpaperImage(QQuickItem *parent)
: QQuickItem(parent)
{
setFlag(ItemHasContents, true);
connect(&m_imageWatcher, &QFutureWatcher<QImage>::finished, this, &WallpaperImage::handleImageLoaded);
}
WallpaperImage::~WallpaperImage() {
if (m_texture) delete m_texture;
}
void WallpaperImage::setSource(const QUrl &source) {
if (m_source == source) return;
m_source = source;
emit sourceChanged();
loadImage();
}
void WallpaperImage::setScreenResolution(const QSize &screenResolution) {
if (m_screenResolution == screenResolution) return;
m_screenResolution = screenResolution;
emit screenResolutionChanged();
loadImage();
}
void WallpaperImage::setZoom(qreal zoom) {
if (qFuzzyCompare(m_zoom, zoom)) return;
m_zoom = zoom;
emit zoomChanged();
update();
}
void WallpaperImage::setCropX(qreal x) {
if (qFuzzyCompare(m_cropX, x)) return;
m_cropX = x;
emit cropXChanged();
update();
}
void WallpaperImage::setCropY(qreal y) {
if (qFuzzyCompare(m_cropY, y)) return;
m_cropY = y;
emit cropYChanged();
update();
}
void WallpaperImage::setCropWidth(qreal w) {
if (w <= 0.0) w = 1.0;
if (qFuzzyCompare(m_cropWidth, w)) return;
m_cropWidth = w;
emit cropWidthChanged();
update();
}
void WallpaperImage::setCropHeight(qreal h) {
if (h <= 0.0) h = 1.0;
if (qFuzzyCompare(m_cropHeight, h)) return;
m_cropHeight = h;
emit cropHeightChanged();
update();
}
QString WallpaperImage::getCacheFilePath() const {
if (m_source.isEmpty() || m_screenResolution.isEmpty()) return QString();
QString cachePath = QStandardPaths::writableLocation(QStandardPaths::GenericCacheLocation) + "/zshell/imagecache";
QDir().mkpath(cachePath);
// Hash the source URL + resolution
QString id = m_source.toString() + "_" + QString::number(m_screenResolution.width()) + "x" + QString::number(m_screenResolution.height());
QByteArray hash = QCryptographicHash::hash(id.toUtf8(), QCryptographicHash::Md5).toHex();
return cachePath + "/" + hash + ".png";
}
void WallpaperImage::loadImage() {
if (m_source.isEmpty()) return;
QString cacheFile = getCacheFilePath();
QString sourceFile = m_source.isLocalFile() ? m_source.toLocalFile() : m_source.toString();
// Qt resource path correction if passed as a standard URL string
if (sourceFile.startsWith("qrc:/")) {
sourceFile = sourceFile.mid(3); // Converts "qrc:/" to ":/"
}
QSize targetRes = m_screenResolution;
// Run off the main thread to avoid blocking the UI
QFuture<QImage> future = QtConcurrent::run([sourceFile, cacheFile, targetRes]() -> QImage {
if (!targetRes.isEmpty() && !cacheFile.isEmpty() && QFileInfo::exists(cacheFile)) {
QImage cached(cacheFile);
if (!cached.isNull()) return cached;
}
QImage original(sourceFile);
if (original.isNull()) return QImage();
if (targetRes.isEmpty()) {
// Screen resolution not set yet by QML, return the unscaled original for now to prevent a black screen
return original;
}
// Check if original is strictly larger than screen resolution
if (original.width() > targetRes.width() || original.height() > targetRes.height()) {
QImage scaled = original.scaled(targetRes, Qt::KeepAspectRatioByExpanding, Qt::SmoothTransformation);
if (!cacheFile.isEmpty()) scaled.save(cacheFile, "PNG");
return scaled;
}
// Otherwise just cache and return the original
if (!cacheFile.isEmpty()) original.save(cacheFile, "PNG");
return original;
});
m_imageWatcher.setFuture(future);
}
void WallpaperImage::handleImageLoaded() {
m_image = m_imageWatcher.result();
m_textureDirty = true;
update(); // Request redraw
}
QSGNode *WallpaperImage::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *) {
auto *node = static_cast<QSGImageNode *>(oldNode);
if (m_image.isNull()) {
delete node;
return nullptr;
}
if (!node) {
node = window()->createImageNode();
}
if (m_textureDirty) {
if (m_texture) delete m_texture;
m_texture = window()->createTextureFromImage(m_image, QQuickWindow::TextureHasAlphaChannel);
m_textureDirty = false;
}
if (m_texture) {
node->setTexture(m_texture);
node->setRect(boundingRect());
node->setFiltering(QSGTexture::Linear);
qreal cW = m_cropWidth / m_zoom;
qreal cH = m_cropHeight / m_zoom;
QRectF reqRect(
m_cropX * m_texture->textureSize().width(),
m_cropY * m_texture->textureSize().height(),
cW * m_texture->textureSize().width(),
cH * m_texture->textureSize().height()
);
QRectF bounds = boundingRect();
if (bounds.isEmpty() || reqRect.isEmpty()) return node;
qreal targetRatio = bounds.width() / bounds.height();
qreal reqRatio = reqRect.width() / reqRect.height();
QRectF sourceRect = reqRect;
// Force 'PreserveAspectCrop' behavior on the requested region
if (reqRatio > targetRatio) {
// Requested region is too wide, center-crop the sides
qreal newWidth = reqRect.height() * targetRatio;
qreal xOffset = (reqRect.width() - newWidth) / 2.0;
sourceRect.setX(reqRect.x() + xOffset);
sourceRect.setWidth(newWidth);
} else if (reqRatio < targetRatio) {
// Requested region is too tall, center-crop the top/bottom
qreal newHeight = reqRect.width() / targetRatio;
qreal yOffset = (reqRect.height() - newHeight) / 2.0;
sourceRect.setY(reqRect.y() + yOffset);
sourceRect.setHeight(newHeight);
}
node->setSourceRect(sourceRect);
}
return node;
}
} // namespace ZShell::internal
@@ -1,95 +0,0 @@
#pragma once
#include <QQuickItem>
#include <QImage>
#include <QUrl>
#include <QSGTexture>
#include <QFutureWatcher>
#include <QtQml/qqml.h>
namespace ZShell::internal {
class WallpaperImage : public QQuickItem {
Q_OBJECT
QML_NAMED_ELEMENT(WallpaperImage)
Q_PROPERTY(QUrl source READ source WRITE setSource NOTIFY sourceChanged)
Q_PROPERTY(QSize screenResolution READ screenResolution WRITE setScreenResolution NOTIFY screenResolutionChanged)
Q_PROPERTY(qreal zoom READ zoom WRITE setZoom NOTIFY zoomChanged)
Q_PROPERTY(qreal cropX READ cropX WRITE setCropX NOTIFY cropXChanged)
Q_PROPERTY(qreal cropY READ cropY WRITE setCropY NOTIFY cropYChanged)
Q_PROPERTY(qreal cropWidth READ cropWidth WRITE setCropWidth NOTIFY cropWidthChanged)
Q_PROPERTY(qreal cropHeight READ cropHeight WRITE setCropHeight NOTIFY cropHeightChanged)
public:
explicit WallpaperImage(QQuickItem *parent = nullptr);
~WallpaperImage() override;
QUrl source() const {
return m_source;
}
void setSource(const QUrl &source);
QSize screenResolution() const {
return m_screenResolution;
}
void setScreenResolution(const QSize &screenResolution);
qreal zoom() const {
return m_zoom;
}
void setZoom(qreal zoom);
qreal cropX() const {
return m_cropX;
}
void setCropX(qreal x);
qreal cropY() const {
return m_cropY;
}
void setCropY(qreal y);
qreal cropWidth() const {
return m_cropWidth;
}
void setCropWidth(qreal w);
qreal cropHeight() const {
return m_cropHeight;
}
void setCropHeight(qreal h);
protected:
QSGNode *updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *updatePaintNodeData) override;
signals:
void sourceChanged();
void screenResolutionChanged();
void zoomChanged();
void cropXChanged();
void cropYChanged();
void cropWidthChanged();
void cropHeightChanged();
private:
void loadImage();
void handleImageLoaded();
QString getCacheFilePath() const;
QUrl m_source;
QSize m_screenResolution;
qreal m_zoom = 1.0;
qreal m_cropX = 0.0;
qreal m_cropY = 0.0;
qreal m_cropWidth = 1.0;
qreal m_cropHeight = 1.0;
QImage m_image;
QSGTexture *m_texture = nullptr;
bool m_textureDirty = false;
QFutureWatcher<QImage> m_imageWatcher;
};
} // namespace ZShell::internal
+302 -311
View File
@@ -7,473 +7,464 @@
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) {
const QMimeDatabase db; static const QMimeDatabase s_db;
m_mimeType = db.mimeTypeForFile(m_path).name(); m_mimeType = s_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;
const auto future = QtConcurrent::run([showHidden, path]() { 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;
}); });
const auto watcher = new QFutureWatcher<QStringList>(this); future.then(this, [currentDir, showHidden, this](const QStringList& paths) {
connect(watcher, &QFutureWatcher<QStringList>::finished, this, [currentDir, showHidden, watcher, this]() { if (currentDir == m_dir && showHidden == m_showHidden && !paths.isEmpty()) {
const auto paths = watcher->result(); // Ignore if dir or showHidden has changed
if (currentDir == m_dir && showHidden == m_showHidden && !paths.isEmpty()) { m_watcher.addPaths(paths);
// 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();
} }
const auto future = QtConcurrent::run([=](QPromise<QPair<QSet<QString>, QSet<QString>>>& promise) { 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() || newPaths == oldPaths) { if (promise.isCanceled()) {
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);
const auto watcher = new QFutureWatcher<QPair<QSet<QString>, QSet<QString>>>(this); future
.then(this,
connect(watcher, &QFutureWatcher<QPair<QSet<QString>, QSet<QString>>>::finished, this, [dir, watcher, this]() { [dir, this](QPair<QSet<QString>, QSet<QString> > result) {
m_futures.remove(dir); m_futures.remove(dir);
if (!result.first.isEmpty() || !result.second.isEmpty()) {
if (!watcher->future().isResultReadyAt(0)) { applyChanges(result.first, result.second);
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
+4 -5
View File
@@ -1,6 +1,5 @@
#pragma once #pragma once
#include <cstdint>
#include <qabstractitemmodel.h> #include <qabstractitemmodel.h>
#include <qdir.h> #include <qdir.h>
#include <qfilesystemwatcher.h> #include <qfilesystemwatcher.h>
@@ -86,9 +85,9 @@ Q_ENUM(Filter)
explicit FileSystemModel(QObject* parent = nullptr); explicit FileSystemModel(QObject* parent = nullptr);
[[nodiscard]] int rowCount(const QModelIndex& parent = QModelIndex()) const override; int rowCount(const QModelIndex& parent = QModelIndex()) const override;
[[nodiscard]] QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override;
[[nodiscard]] 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);
@@ -133,7 +132,7 @@ QString m_path;
bool m_recursive; bool m_recursive;
bool m_watchChanges; bool m_watchChanges;
bool m_showHidden; bool m_showHidden;
bool m_sortReverse; bool m_sortReverse = false;
Filter m_filter; Filter m_filter;
QStringList m_nameFilters; QStringList m_nameFilters;
-1
View File
@@ -9,7 +9,6 @@ qml_module(ZShell-services
cavaprovider.hpp cavaprovider.cpp cavaprovider.hpp cavaprovider.cpp
desktopmodel.hpp desktopmodel.cpp desktopmodel.hpp desktopmodel.cpp
desktopstatemanager.hpp desktopstatemanager.cpp desktopstatemanager.hpp desktopstatemanager.cpp
hyprsunsetmanager.hpp hyprsunsetmanager.cpp
LIBRARIES LIBRARIES
Qt6::Core Qt6::Core
Qt6::Qml Qt6::Qml
+1 -1
View File
@@ -59,7 +59,7 @@ quint32 readChunk(double* out, quint32 count = 0);
private: private:
explicit AudioCollector(QObject* parent = nullptr); explicit AudioCollector(QObject* parent = nullptr);
~AudioCollector() override; ~AudioCollector();
std::jthread m_thread; std::jthread m_thread;
std::vector<float> m_buffer1; std::vector<float> m_buffer1;
+3 -2
View File
@@ -11,10 +11,11 @@ Q_OBJECT
public: public:
explicit AudioProcessor(QObject* parent = nullptr); explicit AudioProcessor(QObject* parent = nullptr);
~AudioProcessor() override; ~AudioProcessor();
void init(); void init();
public slots:
void start(); void start();
void stop(); void stop();
@@ -30,7 +31,7 @@ Q_OBJECT
public: public:
explicit AudioProvider(QObject* parent = nullptr); explicit AudioProvider(QObject* parent = nullptr);
~AudioProvider() override; ~AudioProvider();
protected: protected:
AudioProcessor* m_processor; AudioProcessor* m_processor;
+1 -1
View File
@@ -11,7 +11,7 @@ Q_OBJECT
public: public:
explicit BeatProcessor(QObject* parent = nullptr); explicit BeatProcessor(QObject* parent = nullptr);
~BeatProcessor() override; ~BeatProcessor();
signals: signals:
void beat(smpl_t bpm); void beat(smpl_t bpm);
+1 -1
View File
@@ -11,7 +11,7 @@ Q_OBJECT
public: public:
explicit CavaProcessor(QObject* parent = nullptr); explicit CavaProcessor(QObject* parent = nullptr);
~CavaProcessor() override; ~CavaProcessor();
void setBars(int bars); void setBars(int bars);
+4 -5
View File
@@ -4,7 +4,6 @@
#include <QList> #include <QList>
#include <QString> #include <QString>
#include <QQmlEngine> #include <QQmlEngine>
#include <cstdint>
namespace ZShell::services { namespace ZShell::services {
@@ -31,9 +30,9 @@ enum DesktopRoles {
explicit DesktopModel(QObject *parent = nullptr); explicit DesktopModel(QObject *parent = nullptr);
[[nodiscard]] int rowCount(const QModelIndex &parent = QModelIndex()) const override; int rowCount(const QModelIndex &parent = QModelIndex()) const override;
[[nodiscard]] QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
[[nodiscard]] QHash<int, QByteArray> roleNames() const override; QHash<int, QByteArray> roleNames() const override;
Q_INVOKABLE void loadDirectory(const QString &path); Q_INVOKABLE void loadDirectory(const QString &path);
Q_INVOKABLE void moveIcon(int index, int newX, int newY); Q_INVOKABLE void moveIcon(int index, int newX, int newY);
@@ -44,4 +43,4 @@ QList<DesktopItem> m_items;
void saveCurrentLayout(); void saveCurrentLayout();
}; };
} // namespace ZShell::Services } // namespace ZShell::services

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