Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ad23da4eda |
@@ -13,5 +13,3 @@ uv.lock
|
|||||||
.qtcreator/
|
.qtcreator/
|
||||||
dist/
|
dist/
|
||||||
**/target/
|
**/target/
|
||||||
**/test-plugins/
|
|
||||||
**/Charts/
|
|
||||||
|
|||||||
@@ -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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
+12
-9
@@ -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,6 +291,13 @@ 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,
|
||||||
@@ -304,7 +306,6 @@ Singleton {
|
|||||||
drop_shadow: screenshot.drop_shadow,
|
drop_shadow: screenshot.drop_shadow,
|
||||||
rounded_corners: screenshot.rounded_corners,
|
rounded_corners: screenshot.rounded_corners,
|
||||||
shadow_blur_radius: screenshot.shadow_blur_radius,
|
shadow_blur_radius: screenshot.shadow_blur_radius,
|
||||||
shadow_blur_passes: screenshot.shadow_blur_passes,
|
|
||||||
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
|
||||||
@@ -466,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,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"
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
import Quickshell.Io
|
||||||
|
|
||||||
|
JsonObject {
|
||||||
|
property bool enabled: false
|
||||||
|
property list<var> entries: [
|
||||||
|
{
|
||||||
|
id: "Plugin",
|
||||||
|
enabled: false
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -6,7 +6,6 @@ JsonObject {
|
|||||||
property bool enable_pp: true
|
property bool enable_pp: true
|
||||||
property string mode: "manual"
|
property string mode: "manual"
|
||||||
property bool rounded_corners: false
|
property bool rounded_corners: false
|
||||||
property int shadow_blur_passes: 1
|
|
||||||
property real shadow_blur_radius: 22.0
|
property real shadow_blur_radius: 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
|
||||||
|
|||||||
@@ -1,47 +0,0 @@
|
|||||||
import Quickshell
|
|
||||||
import Quickshell.Services.UPower
|
|
||||||
import QtQuick
|
|
||||||
import ZShell
|
|
||||||
import qs.Config
|
|
||||||
import qs.Components.Toast
|
|
||||||
|
|
||||||
Scope {
|
|
||||||
id: root
|
|
||||||
|
|
||||||
readonly property real currentPerc: UPower.displayDevice.percentage
|
|
||||||
readonly property list<var> popupThresholds: [...Config.general.battery.popupThresholds].sort((a, b) => b.perc - a.perc)
|
|
||||||
|
|
||||||
Connections {
|
|
||||||
function onOnBatteryChanged(): void {
|
|
||||||
if (UPower.onBattery) {
|
|
||||||
if (Config.utilities.toasts.chargingChanged)
|
|
||||||
Toaster.toast(qsTr("Charger unplugged"), qsTr("Battery is discharging"), "power_off");
|
|
||||||
} else {
|
|
||||||
if (Config.utilities.toasts.chargingChanged)
|
|
||||||
Toaster.toast(qsTr("Charger plugged in"), qsTr("Battery is charging"), "power");
|
|
||||||
for (const level of root.popupThresholds)
|
|
||||||
level.warned = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
target: UPower
|
|
||||||
}
|
|
||||||
|
|
||||||
Connections {
|
|
||||||
function onPercentageChanged(): void {
|
|
||||||
if (!UPower.onBattery)
|
|
||||||
return;
|
|
||||||
|
|
||||||
const p = UPower.displayDevice.percentage * 100;
|
|
||||||
for (const perc of root.popupThresholds) {
|
|
||||||
if (p <= perc.perc && !perc.warned) {
|
|
||||||
perc.warned = true;
|
|
||||||
console.log(perc.warned + "\n" + [...Config.general.battery.popupThresholds][0].warned);
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
target: UPower.displayDevice
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+57
-32
@@ -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();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -78,7 +78,7 @@ CustomMouseArea {
|
|||||||
const dragY = y - dragStart.y;
|
const dragY = y - dragStart.y;
|
||||||
|
|
||||||
if (root.visibilities.isDrawing && !root.inLeftPanel(root.panels.drawing, x, y)) {
|
if (root.visibilities.isDrawing && !root.inLeftPanel(root.panels.drawing, x, y)) {
|
||||||
root.input.z = 2;
|
// root.input.z = 2;
|
||||||
root.panels.drawing.expanded = false;
|
root.panels.drawing.expanded = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -132,8 +132,6 @@ CustomMouseArea {
|
|||||||
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;
|
||||||
@@ -163,11 +161,6 @@ CustomMouseArea {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (root.visibilities.launcher) {
|
if (root.visibilities.launcher) {
|
||||||
if (root.panels.dashboardWrapper.x < root.panels.launcher.x + root.panels.launcher.width) {
|
|
||||||
console.log("true");
|
|
||||||
root.visibilities.dashboard = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
root.visibilities.dock = false;
|
root.visibilities.dock = false;
|
||||||
root.visibilities.settings = false;
|
root.visibilities.settings = false;
|
||||||
}
|
}
|
||||||
|
|||||||
+13
-26
@@ -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,8 +327,8 @@ Variants {
|
|||||||
|
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
bar: bar
|
bar: bar
|
||||||
drawing: drawingLoader.item
|
drawing: drawing
|
||||||
input: inputLoader.item
|
input: input
|
||||||
panels: panels
|
panels: panels
|
||||||
popouts: panels.popouts
|
popouts: panels.popouts
|
||||||
screen: scope.modelData
|
screen: scope.modelData
|
||||||
@@ -352,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
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -158,5 +158,6 @@ Singleton {
|
|||||||
|
|
||||||
HyprExtras {
|
HyprExtras {
|
||||||
id: extras
|
id: extras
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-8
@@ -30,17 +30,12 @@ MouseArea {
|
|||||||
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 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)
|
||||||
@@ -71,7 +66,7 @@ MouseArea {
|
|||||||
|
|
||||||
function save(): void {
|
function save(): void {
|
||||||
const tmpfile = Qt.resolvedUrl(`/tmp/zshell-picker-${Quickshell.processId}-${Date.now()}.png`);
|
const tmpfile = Qt.resolvedUrl(`/tmp/zshell-picker-${Quickshell.processId}-${Date.now()}.png`);
|
||||||
const cmd = Config.screenshot.enable_pp ? ["zshell-img-tools", "--scale", root.scaleRatio, "--shadow-blur-radius", root.shadowRange, "--image"] : ["swappy", "-f"];
|
const cmd = Config.screenshot.enable_pp ? ["zshell-img-tools", "--image"] : ["swappy", "-f"];
|
||||||
ZShellIo.saveItem(screencopy, tmpfile, Qt.rect(Math.ceil(rsx), Math.ceil(rsy), Math.floor(sw), Math.floor(sh)), path => Quickshell.execDetached([...cmd, path]));
|
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();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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}`]);
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -116,6 +116,12 @@ Item {
|
|||||||
key: "updates"
|
key: "updates"
|
||||||
name: "Updates"
|
name: "Updates"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ListElement {
|
||||||
|
icon: "extension"
|
||||||
|
key: "plugins"
|
||||||
|
name: "Extensions"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
CustomClippingRect {
|
CustomClippingRect {
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|
||||||
|
|||||||
@@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -43,7 +43,7 @@ SettingsPage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Separator {
|
Separator {
|
||||||
shouldBeActive: Config.screenshot.mode === "manual"
|
visible: Config.screenshot.mode === "manual"
|
||||||
}
|
}
|
||||||
|
|
||||||
SettingSpinBox {
|
SettingSpinBox {
|
||||||
@@ -51,34 +51,34 @@ SettingsPage {
|
|||||||
name: "Corner radius"
|
name: "Corner radius"
|
||||||
object: Config.screenshot
|
object: Config.screenshot
|
||||||
setting: "corner_radius"
|
setting: "corner_radius"
|
||||||
shouldBeActive: Config.screenshot.mode === "manual"
|
|
||||||
step: 1
|
step: 1
|
||||||
|
visible: Config.screenshot.mode === "manual"
|
||||||
}
|
}
|
||||||
|
|
||||||
Separator {
|
Separator {
|
||||||
shouldBeActive: Config.screenshot.mode === "manual"
|
visible: Config.screenshot.mode === "manual"
|
||||||
}
|
}
|
||||||
|
|
||||||
SettingSwitch {
|
SettingSwitch {
|
||||||
name: "Enable drop shadow"
|
name: "Enable drop shadow"
|
||||||
object: Config.screenshot
|
object: Config.screenshot
|
||||||
setting: "drop_shadow"
|
setting: "drop_shadow"
|
||||||
shouldBeActive: Config.screenshot.mode === "manual"
|
visible: Config.screenshot.mode === "manual"
|
||||||
}
|
}
|
||||||
|
|
||||||
Separator {
|
Separator {
|
||||||
shouldBeActive: Config.screenshot.mode === "manual"
|
visible: Config.screenshot.mode === "manual"
|
||||||
}
|
}
|
||||||
|
|
||||||
SettingSwitch {
|
SettingSwitch {
|
||||||
name: "Enable rounded corners"
|
name: "Enable rounded corners"
|
||||||
object: Config.screenshot
|
object: Config.screenshot
|
||||||
setting: "rounded_corners"
|
setting: "rounded_corners"
|
||||||
shouldBeActive: Config.screenshot.mode === "manual"
|
visible: Config.screenshot.mode === "manual"
|
||||||
}
|
}
|
||||||
|
|
||||||
Separator {
|
Separator {
|
||||||
shouldBeActive: Config.screenshot.mode === "manual"
|
visible: Config.screenshot.mode === "manual"
|
||||||
}
|
}
|
||||||
|
|
||||||
SettingSpinBox {
|
SettingSpinBox {
|
||||||
@@ -86,36 +86,23 @@ SettingsPage {
|
|||||||
name: "Shadow blur radius"
|
name: "Shadow blur radius"
|
||||||
object: Config.screenshot
|
object: Config.screenshot
|
||||||
setting: "shadow_blur_radius"
|
setting: "shadow_blur_radius"
|
||||||
shouldBeActive: Config.screenshot.mode === "manual"
|
|
||||||
step: 1
|
step: 1
|
||||||
|
visible: Config.screenshot.mode === "manual"
|
||||||
}
|
}
|
||||||
|
|
||||||
Separator {
|
Separator {
|
||||||
shouldBeActive: Config.screenshot.mode === "manual"
|
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 {
|
Separator {
|
||||||
shouldBeActive: Config.screenshot.mode === "manual"
|
visible: Config.screenshot.mode === "manual"
|
||||||
}
|
|
||||||
|
|
||||||
SettingSpinBox {
|
|
||||||
min: 1
|
|
||||||
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 {
|
||||||
@@ -123,12 +110,12 @@ SettingsPage {
|
|||||||
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"
|
|
||||||
step: 1
|
step: 1
|
||||||
|
visible: Config.screenshot.mode === "manual"
|
||||||
}
|
}
|
||||||
|
|
||||||
Separator {
|
Separator {
|
||||||
shouldBeActive: Config.screenshot.mode === "manual"
|
visible: Config.screenshot.mode === "manual"
|
||||||
}
|
}
|
||||||
|
|
||||||
SettingSpinBox {
|
SettingSpinBox {
|
||||||
@@ -136,8 +123,8 @@ SettingsPage {
|
|||||||
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"
|
|
||||||
step: 1
|
step: 1
|
||||||
|
visible: Config.screenshot.mode === "manual"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -80,26 +80,12 @@ Item {
|
|||||||
required property ShellScreen modelData
|
required property ShellScreen modelData
|
||||||
|
|
||||||
function applyCrop(): void {
|
function applyCrop(): void {
|
||||||
if (!cropRectLoader.item) return;
|
const croprect = cropRect.mapToItem(scaledImg, 0, 0, cropRect.width, cropRect.height);
|
||||||
const cropRect = cropRectLoader.item;
|
const upscaledRect = Qt.rect((croprect.x - cropRect.imageX) / scaledImg.paintedWidth, (croprect.y - cropRect.imageY) / scaledImg.paintedHeight, croprect.width / scaledImg.paintedWidth, croprect.height / scaledImg.paintedHeight);
|
||||||
|
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;
|
||||||
|
|
||||||
@@ -142,7 +128,7 @@ Item {
|
|||||||
Layout.preferredHeight: 10
|
Layout.preferredHeight: 10
|
||||||
from: 1.0
|
from: 1.0
|
||||||
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);
|
||||||
@@ -170,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
|
||||||
@@ -196,85 +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
|
|
||||||
|
|
||||||
sourceComponent: Component {
|
|
||||||
CustomRect {
|
|
||||||
id: cropRect
|
|
||||||
|
|
||||||
property real aspectRatio: delegate.modelData.width / delegate.modelData.height
|
property real aspectRatio: delegate.modelData.width / delegate.modelData.height
|
||||||
readonly property real baseHeight: baseWidth / aspectRatio
|
readonly property real baseHeight: baseWidth / aspectRatio
|
||||||
readonly property real baseWidth: {
|
readonly property real baseWidth: {
|
||||||
let fittedHeight = scaledImg.paintedHeight;
|
let fittedHeight = scaledImg.paintedHeight;
|
||||||
let fittedWidth = fittedHeight * aspectRatio;
|
let fittedWidth = fittedHeight * aspectRatio;
|
||||||
|
|
||||||
if (fittedWidth > scaledImg.paintedWidth) {
|
if (fittedWidth > scaledImg.paintedWidth) {
|
||||||
fittedWidth = scaledImg.paintedWidth;
|
fittedWidth = scaledImg.paintedWidth;
|
||||||
fittedHeight = fittedWidth / aspectRatio;
|
fittedHeight = fittedWidth / aspectRatio;
|
||||||
}
|
}
|
||||||
|
|
||||||
return fittedWidth;
|
return fittedWidth;
|
||||||
}
|
}
|
||||||
readonly property real imageX: (scaledImg.width - scaledImg.paintedWidth) / 2
|
readonly property real imageX: (scaledImg.width - scaledImg.paintedWidth) / 2
|
||||||
readonly property real imageY: (scaledImg.height - scaledImg.paintedHeight) / 2
|
readonly property real imageY: (scaledImg.height - scaledImg.paintedHeight) / 2
|
||||||
property real imgAspectRatio: scaledImg.paintedWidth / scaledImg.paintedHeight
|
property real imgAspectRatio: scaledImg.paintedWidth / scaledImg.paintedHeight
|
||||||
property real zoom: 1.0
|
property real zoom: scaledImg.displayData.zoom
|
||||||
|
|
||||||
function centerInImage() {
|
function centerInImage() {
|
||||||
x = imageX + (scaledImg.paintedWidth - width) / 2;
|
x = imageX + (scaledImg.paintedWidth - width) / 2;
|
||||||
y = imageY + (scaledImg.paintedHeight - height) / 2;
|
y = imageY + (scaledImg.paintedHeight - height) / 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
function clampToBounds() {
|
function clampToBounds() {
|
||||||
x = Math.max(imageX, Math.min(x, imageX + scaledImg.paintedWidth - width));
|
x = Math.max(imageX, Math.min(x, imageX + scaledImg.paintedWidth - width));
|
||||||
|
|
||||||
y = Math.max(imageY, Math.min(y, imageY + scaledImg.paintedHeight - height));
|
y = Math.max(imageY, Math.min(y, imageY + scaledImg.paintedHeight - height));
|
||||||
}
|
}
|
||||||
|
|
||||||
function restoreFromData() {
|
function restoreFromData() {
|
||||||
let data = Wallpapers.getCrop(delegate.modelData.name);
|
let data = scaledImg.displayData;
|
||||||
|
|
||||||
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)) {
|
if (data && data.scaledX !== 0 || data.scaledY !== 0 || data.scaledWidth !== 0 || data.scaledHeight !== 0) {
|
||||||
zoom = data.zoom > 0 ? data.zoom : 1.0;
|
x = data.scaledX;
|
||||||
x = imageX + (data.x * scaledImg.paintedWidth);
|
y = data.scaledY;
|
||||||
y = imageY + (data.y * scaledImg.paintedHeight);
|
|
||||||
|
|
||||||
clampToBounds();
|
|
||||||
} else {
|
|
||||||
zoom = 1.0;
|
|
||||||
centerInImage();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
border.color: DynamicColors.palette.m3primary
|
clampToBounds();
|
||||||
border.width: 2
|
} else {
|
||||||
height: baseHeight / zoom
|
zoom = 1.0;
|
||||||
opacity: 1
|
centerInImage();
|
||||||
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;
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
+112
-123
@@ -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
|
||||||
}))
|
}))
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +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
|
|
||||||
LIBRARIES
|
LIBRARIES
|
||||||
Qt::Gui
|
Qt::Gui
|
||||||
Qt::Quick
|
Qt::Quick
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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() {
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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
|
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -13,136 +13,136 @@
|
|||||||
namespace ZShell::models {
|
namespace ZShell::models {
|
||||||
|
|
||||||
class FileSystemEntry : public QObject {
|
class FileSystemEntry : public QObject {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
QML_ELEMENT
|
QML_ELEMENT
|
||||||
QML_UNCREATABLE("FileSystemEntry instances can only be retrieved from a FileSystemModel")
|
QML_UNCREATABLE("FileSystemEntry instances can only be retrieved from a FileSystemModel")
|
||||||
|
|
||||||
Q_PROPERTY(QString path READ path CONSTANT)
|
Q_PROPERTY(QString path READ path CONSTANT)
|
||||||
Q_PROPERTY(QString relativePath READ relativePath NOTIFY relativePathChanged)
|
Q_PROPERTY(QString relativePath READ relativePath NOTIFY relativePathChanged)
|
||||||
Q_PROPERTY(QString name READ name CONSTANT)
|
Q_PROPERTY(QString name READ name CONSTANT)
|
||||||
Q_PROPERTY(QString baseName READ baseName CONSTANT)
|
Q_PROPERTY(QString baseName READ baseName CONSTANT)
|
||||||
Q_PROPERTY(QString parentDir READ parentDir CONSTANT)
|
Q_PROPERTY(QString parentDir READ parentDir CONSTANT)
|
||||||
Q_PROPERTY(QString suffix READ suffix CONSTANT)
|
Q_PROPERTY(QString suffix READ suffix CONSTANT)
|
||||||
Q_PROPERTY(qint64 size READ size CONSTANT)
|
Q_PROPERTY(qint64 size READ size CONSTANT)
|
||||||
Q_PROPERTY(bool isDir READ isDir CONSTANT)
|
Q_PROPERTY(bool isDir READ isDir CONSTANT)
|
||||||
Q_PROPERTY(bool isImage READ isImage CONSTANT)
|
Q_PROPERTY(bool isImage READ isImage CONSTANT)
|
||||||
Q_PROPERTY(QString mimeType READ mimeType CONSTANT)
|
Q_PROPERTY(QString mimeType READ mimeType CONSTANT)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit FileSystemEntry(const QString& path, const QString& relativePath, QObject* parent = nullptr);
|
explicit FileSystemEntry(const QString& path, const QString& relativePath, QObject* parent = nullptr);
|
||||||
|
|
||||||
[[nodiscard]] QString path() const;
|
[[nodiscard]] QString path() const;
|
||||||
[[nodiscard]] QString relativePath() const;
|
[[nodiscard]] QString relativePath() const;
|
||||||
[[nodiscard]] QString name() const;
|
[[nodiscard]] QString name() const;
|
||||||
[[nodiscard]] QString baseName() const;
|
[[nodiscard]] QString baseName() const;
|
||||||
[[nodiscard]] QString parentDir() const;
|
[[nodiscard]] QString parentDir() const;
|
||||||
[[nodiscard]] QString suffix() const;
|
[[nodiscard]] QString suffix() const;
|
||||||
[[nodiscard]] qint64 size() const;
|
[[nodiscard]] qint64 size() const;
|
||||||
[[nodiscard]] bool isDir() const;
|
[[nodiscard]] bool isDir() const;
|
||||||
[[nodiscard]] bool isImage() const;
|
[[nodiscard]] bool isImage() const;
|
||||||
[[nodiscard]] QString mimeType() const;
|
[[nodiscard]] QString mimeType() const;
|
||||||
|
|
||||||
void updateRelativePath(const QDir& dir);
|
void updateRelativePath(const QDir& dir);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void relativePathChanged();
|
void relativePathChanged();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
const QFileInfo m_fileInfo;
|
const QFileInfo m_fileInfo;
|
||||||
|
|
||||||
const QString m_path;
|
const QString m_path;
|
||||||
QString m_relativePath;
|
QString m_relativePath;
|
||||||
|
|
||||||
mutable bool m_isImage;
|
mutable bool m_isImage;
|
||||||
mutable bool m_isImageInitialised;
|
mutable bool m_isImageInitialised;
|
||||||
|
|
||||||
mutable QString m_mimeType;
|
mutable QString m_mimeType;
|
||||||
mutable bool m_mimeTypeInitialised;
|
mutable bool m_mimeTypeInitialised;
|
||||||
};
|
};
|
||||||
|
|
||||||
class FileSystemModel : public QAbstractListModel {
|
class FileSystemModel : public QAbstractListModel {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
QML_ELEMENT
|
QML_ELEMENT
|
||||||
|
|
||||||
Q_PROPERTY(QString path READ path WRITE setPath NOTIFY pathChanged)
|
Q_PROPERTY(QString path READ path WRITE setPath NOTIFY pathChanged)
|
||||||
Q_PROPERTY(bool recursive READ recursive WRITE setRecursive NOTIFY recursiveChanged)
|
Q_PROPERTY(bool recursive READ recursive WRITE setRecursive NOTIFY recursiveChanged)
|
||||||
Q_PROPERTY(bool watchChanges READ watchChanges WRITE setWatchChanges NOTIFY watchChangesChanged)
|
Q_PROPERTY(bool watchChanges READ watchChanges WRITE setWatchChanges NOTIFY watchChangesChanged)
|
||||||
Q_PROPERTY(bool showHidden READ showHidden WRITE setShowHidden NOTIFY showHiddenChanged)
|
Q_PROPERTY(bool showHidden READ showHidden WRITE setShowHidden NOTIFY showHiddenChanged)
|
||||||
Q_PROPERTY(bool sortReverse READ sortReverse WRITE setSortReverse NOTIFY sortReverseChanged)
|
Q_PROPERTY(bool sortReverse READ sortReverse WRITE setSortReverse NOTIFY sortReverseChanged)
|
||||||
Q_PROPERTY(Filter filter READ filter WRITE setFilter NOTIFY filterChanged)
|
Q_PROPERTY(Filter filter READ filter WRITE setFilter NOTIFY filterChanged)
|
||||||
Q_PROPERTY(QStringList nameFilters READ nameFilters WRITE setNameFilters NOTIFY nameFiltersChanged)
|
Q_PROPERTY(QStringList nameFilters READ nameFilters WRITE setNameFilters NOTIFY nameFiltersChanged)
|
||||||
|
|
||||||
Q_PROPERTY(QQmlListProperty<ZShell::models::FileSystemEntry> entries READ entries NOTIFY entriesChanged)
|
Q_PROPERTY(QQmlListProperty<ZShell::models::FileSystemEntry> entries READ entries NOTIFY entriesChanged)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
enum Filter {
|
enum Filter {
|
||||||
NoFilter,
|
NoFilter,
|
||||||
Images,
|
Images,
|
||||||
Files,
|
Files,
|
||||||
Dirs
|
Dirs
|
||||||
};
|
};
|
||||||
Q_ENUM(Filter)
|
Q_ENUM(Filter)
|
||||||
|
|
||||||
explicit FileSystemModel(QObject* parent = nullptr);
|
explicit FileSystemModel(QObject* parent = nullptr);
|
||||||
|
|
||||||
int rowCount(const QModelIndex& parent = QModelIndex()) const override;
|
int rowCount(const QModelIndex& parent = QModelIndex()) const override;
|
||||||
QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override;
|
QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override;
|
||||||
QHash<int, QByteArray> roleNames() const override;
|
QHash<int, QByteArray> roleNames() const override;
|
||||||
|
|
||||||
[[nodiscard]] QString path() const;
|
[[nodiscard]] QString path() const;
|
||||||
void setPath(const QString& path);
|
void setPath(const QString& path);
|
||||||
|
|
||||||
[[nodiscard]] bool recursive() const;
|
[[nodiscard]] bool recursive() const;
|
||||||
void setRecursive(bool recursive);
|
void setRecursive(bool recursive);
|
||||||
|
|
||||||
[[nodiscard]] bool watchChanges() const;
|
[[nodiscard]] bool watchChanges() const;
|
||||||
void setWatchChanges(bool watchChanges);
|
void setWatchChanges(bool watchChanges);
|
||||||
|
|
||||||
[[nodiscard]] bool showHidden() const;
|
[[nodiscard]] bool showHidden() const;
|
||||||
void setShowHidden(bool showHidden);
|
void setShowHidden(bool showHidden);
|
||||||
|
|
||||||
[[nodiscard]] bool sortReverse() const;
|
[[nodiscard]] bool sortReverse() const;
|
||||||
void setSortReverse(bool sortReverse);
|
void setSortReverse(bool sortReverse);
|
||||||
|
|
||||||
[[nodiscard]] Filter filter() const;
|
[[nodiscard]] Filter filter() const;
|
||||||
void setFilter(Filter filter);
|
void setFilter(Filter filter);
|
||||||
|
|
||||||
[[nodiscard]] QStringList nameFilters() const;
|
[[nodiscard]] QStringList nameFilters() const;
|
||||||
void setNameFilters(const QStringList& nameFilters);
|
void setNameFilters(const QStringList& nameFilters);
|
||||||
|
|
||||||
[[nodiscard]] QQmlListProperty<FileSystemEntry> entries();
|
[[nodiscard]] QQmlListProperty<FileSystemEntry> entries();
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void pathChanged();
|
void pathChanged();
|
||||||
void recursiveChanged();
|
void recursiveChanged();
|
||||||
void watchChangesChanged();
|
void watchChangesChanged();
|
||||||
void showHiddenChanged();
|
void showHiddenChanged();
|
||||||
void sortReverseChanged();
|
void sortReverseChanged();
|
||||||
void filterChanged();
|
void filterChanged();
|
||||||
void nameFiltersChanged();
|
void nameFiltersChanged();
|
||||||
void entriesChanged();
|
void entriesChanged();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QDir m_dir;
|
QDir m_dir;
|
||||||
QFileSystemWatcher m_watcher;
|
QFileSystemWatcher m_watcher;
|
||||||
QList<FileSystemEntry*> m_entries;
|
QList<FileSystemEntry*> m_entries;
|
||||||
QHash<QString, QFuture<QPair<QSet<QString>, QSet<QString>>>> m_futures;
|
QHash<QString, QFuture<QPair<QSet<QString>, QSet<QString> > > > m_futures;
|
||||||
|
|
||||||
QString m_path;
|
QString m_path;
|
||||||
bool m_recursive;
|
bool m_recursive;
|
||||||
bool m_watchChanges;
|
bool m_watchChanges;
|
||||||
bool m_showHidden;
|
bool m_showHidden;
|
||||||
bool m_sortReverse;
|
bool m_sortReverse = false;
|
||||||
Filter m_filter;
|
Filter m_filter;
|
||||||
QStringList m_nameFilters;
|
QStringList m_nameFilters;
|
||||||
|
|
||||||
void watchDirIfRecursive(const QString& path);
|
void watchDirIfRecursive(const QString& path);
|
||||||
void update();
|
void update();
|
||||||
void updateWatcher();
|
void updateWatcher();
|
||||||
void updateEntries();
|
void updateEntries();
|
||||||
void updateEntriesForDir(const QString& dir);
|
void updateEntriesForDir(const QString& dir);
|
||||||
void applyChanges(const QSet<QString>& removedPaths, const QSet<QString>& addedPaths);
|
void applyChanges(const QSet<QString>& removedPaths, const QSet<QString>& addedPaths);
|
||||||
[[nodiscard]] bool compareEntries(const FileSystemEntry* a, const FileSystemEntry* b) const;
|
[[nodiscard]] bool compareEntries(const FileSystemEntry* a, const FileSystemEntry* b) const;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace ZShell::models
|
} // namespace ZShell::models
|
||||||
|
|||||||
+83
-307
@@ -1,355 +1,131 @@
|
|||||||
#include "writefile.hpp"
|
#include "writefile.hpp"
|
||||||
|
|
||||||
#include <QtConcurrent/qtconcurrentrun.h>
|
#include <QtConcurrent/qtconcurrentrun.h>
|
||||||
#include <QtCore/QCryptographicHash>
|
|
||||||
#include <QtCore/QSaveFile>
|
|
||||||
#include <QtGui/QImageReader>
|
|
||||||
#include <QtQuick/qquickimageprovider.h>
|
|
||||||
#include <QtQuick/qquickitemgrabresult.h>
|
#include <QtQuick/qquickitemgrabresult.h>
|
||||||
#include <QtQuick/qquickwindow.h>
|
#include <QtQuick/qquickwindow.h>
|
||||||
|
#include <qdir.h>
|
||||||
#include <QDir>
|
#include <qfileinfo.h>
|
||||||
#include <QFile>
|
#include <qfuturewatcher.h>
|
||||||
#include <QFileInfo>
|
#include <qqmlengine.h>
|
||||||
#include <QFutureWatcher>
|
|
||||||
#include <QImage>
|
|
||||||
#include <QJSValue>
|
|
||||||
#include <QQmlEngine>
|
|
||||||
#include <QSize>
|
|
||||||
#include <QVariant>
|
|
||||||
|
|
||||||
namespace ZShell {
|
namespace ZShell {
|
||||||
|
|
||||||
// ============================================================
|
|
||||||
// saveItem
|
|
||||||
// ============================================================
|
|
||||||
|
|
||||||
void ZShellIo::saveItem(QQuickItem* target, const QUrl& path) {
|
void ZShellIo::saveItem(QQuickItem* target, const QUrl& path) {
|
||||||
this->saveItem(target, path, QRect(), QJSValue(), QJSValue());
|
this->saveItem(target, path, QRect(), QJSValue(), QJSValue());
|
||||||
}
|
}
|
||||||
|
|
||||||
void ZShellIo::saveItem(QQuickItem* target, const QUrl& path, const QRect& rect) {
|
void ZShellIo::saveItem(QQuickItem* target, const QUrl& path, const QRect& rect) {
|
||||||
this->saveItem(target, path, rect, QJSValue(), QJSValue());
|
this->saveItem(target, path, rect, QJSValue(), QJSValue());
|
||||||
}
|
}
|
||||||
|
|
||||||
void ZShellIo::saveItem(QQuickItem* target, const QUrl& path, QJSValue onSaved) {
|
void ZShellIo::saveItem(QQuickItem* target, const QUrl& path, QJSValue onSaved) {
|
||||||
this->saveItem(target, path, QRect(), onSaved, QJSValue());
|
this->saveItem(target, path, QRect(), onSaved, QJSValue());
|
||||||
}
|
}
|
||||||
|
|
||||||
void ZShellIo::saveItem(QQuickItem* target, const QUrl& path, QJSValue onSaved, QJSValue onFailed) {
|
void ZShellIo::saveItem(QQuickItem* target, const QUrl& path, QJSValue onSaved, QJSValue onFailed) {
|
||||||
this->saveItem(target, path, QRect(), onSaved, onFailed);
|
this->saveItem(target, path, QRect(), onSaved, onFailed);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ZShellIo::saveItem(QQuickItem* target, const QUrl& path, const QRect& rect, QJSValue onSaved) {
|
void ZShellIo::saveItem(QQuickItem* target, const QUrl& path, const QRect& rect, QJSValue onSaved) {
|
||||||
this->saveItem(target, path, rect, onSaved, QJSValue());
|
this->saveItem(target, path, rect, onSaved, QJSValue());
|
||||||
}
|
}
|
||||||
|
|
||||||
void ZShellIo::saveItem(
|
void ZShellIo::saveItem(QQuickItem* target, const QUrl& path, const QRect& rect, QJSValue onSaved, QJSValue onFailed) {
|
||||||
QQuickItem* target,
|
if (!target) {
|
||||||
const QUrl& path,
|
qWarning() << "ZShellIo::saveItem: a target is required";
|
||||||
const QRect& rect,
|
return;
|
||||||
QJSValue onSaved,
|
}
|
||||||
QJSValue onFailed
|
|
||||||
) {
|
|
||||||
if (!target) {
|
|
||||||
qWarning() << "ZShellIo::saveItem: a target is required";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!path.isLocalFile()) {
|
if (!path.isLocalFile()) {
|
||||||
qWarning() << "ZShellIo::saveItem:" << path << "is not a local file";
|
qWarning() << "ZShellIo::saveItem:" << path << "is not a local file";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!target->window()) {
|
if (!target->window()) {
|
||||||
qWarning() << "ZShellIo::saveItem: unable to save target"
|
qWarning() << "ZShellIo::saveItem: unable to save target" << target << "without a window";
|
||||||
<< target
|
return;
|
||||||
<< "without a window";
|
}
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto scaledRect = rect;
|
auto scaledRect = rect;
|
||||||
|
const qreal scale = target->window()->devicePixelRatio();
|
||||||
|
if (rect.isValid() && !qFuzzyCompare(scale + 1.0, 2.0)) {
|
||||||
|
scaledRect =
|
||||||
|
QRectF(rect.left() * scale, rect.top() * scale, rect.width() * scale, rect.height() * scale).toRect();
|
||||||
|
}
|
||||||
|
|
||||||
const qreal scale = target->window()->devicePixelRatio();
|
const QSharedPointer<const QQuickItemGrabResult> grabResult = target->grabToImage();
|
||||||
|
|
||||||
if (rect.isValid() && !qFuzzyCompare(scale + 1.0, 2.0)) {
|
QObject::connect(grabResult.data(), &QQuickItemGrabResult::ready, this,
|
||||||
scaledRect = QRectF(
|
[grabResult, scaledRect, path, onSaved, onFailed, this]() {
|
||||||
rect.left() * scale,
|
const auto future = QtConcurrent::run([=]() {
|
||||||
rect.top() * scale,
|
QImage image = grabResult->image();
|
||||||
rect.width() * scale,
|
|
||||||
rect.height() * scale
|
|
||||||
).toRect();
|
|
||||||
}
|
|
||||||
|
|
||||||
const QSharedPointer<const QQuickItemGrabResult> grabResult =
|
if (scaledRect.isValid()) {
|
||||||
target->grabToImage();
|
image = image.copy(scaledRect);
|
||||||
|
}
|
||||||
|
|
||||||
QObject::connect(
|
const QString file = path.toLocalFile();
|
||||||
grabResult.data(),
|
const QString parent = QFileInfo(file).absolutePath();
|
||||||
&QQuickItemGrabResult::ready,
|
return QDir().mkpath(parent) && image.save(file);
|
||||||
this,
|
});
|
||||||
[grabResult, scaledRect, path, onSaved, onFailed, this]() {
|
|
||||||
const auto future = QtConcurrent::run([grabResult, scaledRect, path]() {
|
|
||||||
QImage image = grabResult->image();
|
|
||||||
|
|
||||||
if (scaledRect.isValid()) {
|
auto* watcher = new QFutureWatcher<bool>(this);
|
||||||
image = image.copy(scaledRect);
|
auto* engine = qmlEngine(this);
|
||||||
}
|
|
||||||
|
|
||||||
const QString file = path.toLocalFile();
|
QObject::connect(watcher, &QFutureWatcher<bool>::finished, this, [=]() {
|
||||||
const QString parent = QFileInfo(file).absolutePath();
|
if (watcher->result()) {
|
||||||
|
if (onSaved.isCallable()) {
|
||||||
QDir().mkpath(parent);
|
onSaved.call(
|
||||||
|
{ QJSValue(path.toLocalFile()), engine->toScriptValue(QVariant::fromValue(path)) });
|
||||||
QSaveFile out(file);
|
}
|
||||||
if (!out.open(QIODevice::WriteOnly)) {
|
} else {
|
||||||
return false;
|
qWarning() << "ZShellIo::saveItem: failed to save" << path;
|
||||||
}
|
if (onFailed.isCallable()) {
|
||||||
|
onFailed.call({ engine->toScriptValue(QVariant::fromValue(path)) });
|
||||||
if (!image.save(&out, "PNG")) {
|
}
|
||||||
return false;
|
}
|
||||||
}
|
watcher->deleteLater();
|
||||||
|
});
|
||||||
return out.commit();
|
watcher->setFuture(future);
|
||||||
});
|
});
|
||||||
|
|
||||||
auto* watcher = new QFutureWatcher<bool>(this);
|
|
||||||
auto* engine = qmlEngine(this);
|
|
||||||
|
|
||||||
QObject::connect(watcher, &QFutureWatcher<bool>::finished, this, [=]() {
|
|
||||||
if (watcher->result()) {
|
|
||||||
if (onSaved.isCallable() && engine) {
|
|
||||||
onSaved.call({
|
|
||||||
engine->toScriptValue(path.toLocalFile()),
|
|
||||||
engine->toScriptValue(path)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
qWarning() << "ZShellIo::saveItem: failed to save" << path;
|
|
||||||
if (onFailed.isCallable() && engine) {
|
|
||||||
onFailed.call({
|
|
||||||
engine->toScriptValue(path)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
watcher->deleteLater();
|
|
||||||
});
|
|
||||||
|
|
||||||
watcher->setFuture(future);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============================================================
|
|
||||||
// cacheImage
|
|
||||||
// ============================================================
|
|
||||||
|
|
||||||
void ZShellIo::cacheImage(const QUrl& source, const QString& cacheDir) {
|
|
||||||
this->cacheImage(source, cacheDir, QJSValue(), QJSValue());
|
|
||||||
}
|
|
||||||
|
|
||||||
void ZShellIo::cacheImage(const QUrl& source, const QString& cacheDir, QJSValue onSaved) {
|
|
||||||
this->cacheImage(source, cacheDir, onSaved, QJSValue());
|
|
||||||
}
|
|
||||||
|
|
||||||
void ZShellIo::cacheImage(
|
|
||||||
const QUrl& source,
|
|
||||||
const QString& cacheDir,
|
|
||||||
QJSValue onSaved,
|
|
||||||
QJSValue onFailed
|
|
||||||
) {
|
|
||||||
if (cacheDir.isEmpty()) {
|
|
||||||
qWarning() << "ZShellIo::cacheImage: cacheDir is empty";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
QImage image;
|
|
||||||
if (!loadSourceImage(source, image)) {
|
|
||||||
qWarning() << "ZShellIo::cacheImage: failed to load source image" << source;
|
|
||||||
auto* engine = qmlEngine(this);
|
|
||||||
if (onFailed.isCallable() && engine) {
|
|
||||||
onFailed.call({
|
|
||||||
engine->toScriptValue(source),
|
|
||||||
engine->toScriptValue(cacheDir)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto future = QtConcurrent::run([image, cacheDir]() -> QString {
|
|
||||||
if (image.isNull()) {
|
|
||||||
return QString();
|
|
||||||
}
|
|
||||||
|
|
||||||
const QImage normalized = image.convertToFormat(QImage::Format_RGBA8888);
|
|
||||||
|
|
||||||
const QByteArray bytes(
|
|
||||||
reinterpret_cast<const char*>(normalized.constBits()),
|
|
||||||
qsizetype(normalized.sizeInBytes())
|
|
||||||
);
|
|
||||||
|
|
||||||
const QByteArray digest =
|
|
||||||
QCryptographicHash::hash(bytes, QCryptographicHash::Sha256).toHex();
|
|
||||||
|
|
||||||
QDir dir(cacheDir);
|
|
||||||
if (!dir.exists() && !QDir().mkpath(cacheDir)) {
|
|
||||||
return QString();
|
|
||||||
}
|
|
||||||
|
|
||||||
const QString finalPath = dir.filePath(QString::fromLatin1(digest) + ".png");
|
|
||||||
|
|
||||||
if (QFile::exists(finalPath)) {
|
|
||||||
return finalPath;
|
|
||||||
}
|
|
||||||
|
|
||||||
QSaveFile out(finalPath);
|
|
||||||
if (!out.open(QIODevice::WriteOnly)) {
|
|
||||||
return QString();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!normalized.save(&out, "PNG")) {
|
|
||||||
return QString();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!out.commit()) {
|
|
||||||
return QString();
|
|
||||||
}
|
|
||||||
|
|
||||||
return finalPath;
|
|
||||||
});
|
|
||||||
|
|
||||||
auto* watcher = new QFutureWatcher<QString>(this);
|
|
||||||
auto* engine = qmlEngine(this);
|
|
||||||
|
|
||||||
QObject::connect(watcher, &QFutureWatcher<QString>::finished, this, [=]() {
|
|
||||||
const QString finalPath = watcher->result();
|
|
||||||
|
|
||||||
if (!finalPath.isEmpty()) {
|
|
||||||
if (onSaved.isCallable() && engine) {
|
|
||||||
onSaved.call({
|
|
||||||
engine->toScriptValue(finalPath),
|
|
||||||
engine->toScriptValue(QUrl::fromLocalFile(finalPath))
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
qWarning() << "ZShellIo::cacheImage: failed to cache" << source;
|
|
||||||
if (onFailed.isCallable() && engine) {
|
|
||||||
onFailed.call({
|
|
||||||
engine->toScriptValue(source),
|
|
||||||
engine->toScriptValue(cacheDir)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
watcher->deleteLater();
|
|
||||||
});
|
|
||||||
|
|
||||||
watcher->setFuture(future);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ============================================================
|
|
||||||
// loadSourceImage
|
|
||||||
// ============================================================
|
|
||||||
|
|
||||||
bool ZShellIo::loadSourceImage(const QUrl& source, QImage& image) const {
|
|
||||||
image = QImage();
|
|
||||||
|
|
||||||
if (source.isLocalFile()) {
|
|
||||||
QImageReader reader(source.toLocalFile());
|
|
||||||
reader.setAutoTransform(true);
|
|
||||||
image = reader.read();
|
|
||||||
return !image.isNull();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (source.scheme() == "image") {
|
|
||||||
auto* engine = qmlEngine(const_cast<ZShellIo*>(this));
|
|
||||||
if (!engine) {
|
|
||||||
qWarning() << "ZShellIo::loadSourceImage: no QQmlEngine";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const QString providerId = source.host();
|
|
||||||
|
|
||||||
const QString imageId =
|
|
||||||
source.path().startsWith('/')
|
|
||||||
? source.path().mid(1)
|
|
||||||
: source.path();
|
|
||||||
|
|
||||||
auto* providerBase = engine->imageProvider(providerId);
|
|
||||||
if (!providerBase) {
|
|
||||||
qWarning() << "ZShellIo::loadSourceImage: provider not found"
|
|
||||||
<< providerId;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto* provider = dynamic_cast<QQuickImageProvider*>(providerBase);
|
|
||||||
if (!provider) {
|
|
||||||
qWarning() << "ZShellIo::loadSourceImage: provider is not a QQuickImageProvider"
|
|
||||||
<< providerId;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
QSize size;
|
|
||||||
|
|
||||||
switch (provider->imageType()) {
|
|
||||||
case QQuickImageProvider::Image:
|
|
||||||
image = provider->requestImage(imageId, &size, QSize());
|
|
||||||
break;
|
|
||||||
|
|
||||||
case QQuickImageProvider::Pixmap:
|
|
||||||
image = provider->requestPixmap(imageId, &size, QSize()).toImage();
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
qWarning() << "ZShellIo::loadSourceImage: unsupported provider type"
|
|
||||||
<< providerId;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return !image.isNull();
|
|
||||||
}
|
|
||||||
|
|
||||||
qWarning() << "ZShellIo::loadSourceImage: unsupported source" << source;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ============================================================
|
|
||||||
// File ops
|
|
||||||
// ============================================================
|
|
||||||
|
|
||||||
bool ZShellIo::copyFile(const QUrl& source, const QUrl& target, bool overwrite) const {
|
bool ZShellIo::copyFile(const QUrl& source, const QUrl& target, bool overwrite) const {
|
||||||
if (!source.isLocalFile()) {
|
if (!source.isLocalFile()) {
|
||||||
qWarning() << "ZShellIo::copyFile: source" << source << "is not a local file";
|
qWarning() << "ZShellIo::copyFile: source" << source << "is not a local file";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (!target.isLocalFile()) {
|
if (!target.isLocalFile()) {
|
||||||
qWarning() << "ZShellIo::copyFile: target" << target << "is not a local file";
|
qWarning() << "ZShellIo::copyFile: target" << target << "is not a local file";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (overwrite) {
|
if (overwrite) {
|
||||||
QFile::remove(target.toLocalFile());
|
if (!QFile::remove(target.toLocalFile())) {
|
||||||
}
|
qWarning() << "ZShellIo::copyFile: overwrite was specified but failed to remove" << target.toLocalFile();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return QFile::copy(source.toLocalFile(), target.toLocalFile());
|
return QFile::copy(source.toLocalFile(), target.toLocalFile());
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ZShellIo::deleteFile(const QUrl& path) const {
|
bool ZShellIo::deleteFile(const QUrl& path) const {
|
||||||
if (!path.isLocalFile()) {
|
if (!path.isLocalFile()) {
|
||||||
qWarning() << "ZShellIo::deleteFile: path" << path << "is not a local file";
|
qWarning() << "ZShellIo::deleteFile: path" << path << "is not a local file";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return QFile::remove(path.toLocalFile());
|
return QFile::remove(path.toLocalFile());
|
||||||
}
|
}
|
||||||
|
|
||||||
QString ZShellIo::toLocalFile(const QUrl& url) const {
|
QString ZShellIo::toLocalFile(const QUrl& url) const {
|
||||||
if (!url.isLocalFile()) {
|
if (!url.isLocalFile()) {
|
||||||
qWarning() << "ZShellIo::toLocalFile: given url is not a local file" << url;
|
qWarning() << "ZShellIo::toLocalFile: given url is not a local file" << url;
|
||||||
return QString();
|
return QString();
|
||||||
}
|
}
|
||||||
|
|
||||||
return url.toLocalFile();
|
return url.toLocalFile();
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace ZShell
|
} // namespace ZShell
|
||||||
|
|||||||
@@ -1,40 +1,31 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <QtQuick/qquickitem.h>
|
#include <QtQuick/qquickitem.h>
|
||||||
#include <QImage>
|
#include <qobject.h>
|
||||||
#include <QJSValue>
|
|
||||||
#include <QObject>
|
|
||||||
#include <qqmlintegration.h>
|
#include <qqmlintegration.h>
|
||||||
#include <QUrl>
|
|
||||||
|
|
||||||
namespace ZShell {
|
namespace ZShell {
|
||||||
|
|
||||||
class ZShellIo : public QObject {
|
class ZShellIo : public QObject {
|
||||||
|
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
QML_ELEMENT
|
QML_ELEMENT
|
||||||
QML_SINGLETON
|
QML_SINGLETON
|
||||||
|
|
||||||
public:
|
public:
|
||||||
// clang-format off
|
// clang-format off
|
||||||
Q_INVOKABLE void saveItem(QQuickItem* target, const QUrl& path);
|
Q_INVOKABLE void saveItem(QQuickItem* target, const QUrl& path);
|
||||||
Q_INVOKABLE void saveItem(QQuickItem* target, const QUrl& path, const QRect& rect);
|
Q_INVOKABLE void saveItem(QQuickItem* target, const QUrl& path, const QRect& rect);
|
||||||
Q_INVOKABLE void saveItem(QQuickItem* target, const QUrl& path, QJSValue onSaved);
|
Q_INVOKABLE void saveItem(QQuickItem* target, const QUrl& path, QJSValue onSaved);
|
||||||
Q_INVOKABLE void saveItem(QQuickItem* target, const QUrl& path, QJSValue onSaved, QJSValue onFailed);
|
Q_INVOKABLE void saveItem(QQuickItem* target, const QUrl& path, QJSValue onSaved, QJSValue onFailed);
|
||||||
Q_INVOKABLE void saveItem(QQuickItem* target, const QUrl& path, const QRect& rect, QJSValue onSaved);
|
Q_INVOKABLE void saveItem(QQuickItem* target, const QUrl& path, const QRect& rect, QJSValue onSaved);
|
||||||
Q_INVOKABLE void saveItem(QQuickItem* target, const QUrl& path, const QRect& rect, QJSValue onSaved, QJSValue onFailed);
|
Q_INVOKABLE void saveItem(QQuickItem* target, const QUrl& path, const QRect& rect, QJSValue onSaved, QJSValue onFailed);
|
||||||
|
// clang-format on
|
||||||
|
|
||||||
Q_INVOKABLE void cacheImage(const QUrl& source, const QString& cacheDir);
|
Q_INVOKABLE bool copyFile(const QUrl& source, const QUrl& target, bool overwrite = true) const;
|
||||||
Q_INVOKABLE void cacheImage(const QUrl& source, const QString& cacheDir, QJSValue onSaved);
|
Q_INVOKABLE bool deleteFile(const QUrl& path) const;
|
||||||
Q_INVOKABLE void cacheImage(const QUrl& source, const QString& cacheDir, QJSValue onSaved, QJSValue onFailed);
|
Q_INVOKABLE QString toLocalFile(const QUrl& url) const;
|
||||||
// clang-format on
|
|
||||||
|
|
||||||
Q_INVOKABLE bool copyFile(const QUrl& source, const QUrl& target, bool overwrite = true) const;
|
|
||||||
Q_INVOKABLE bool deleteFile(const QUrl& path) const;
|
|
||||||
Q_INVOKABLE QString toLocalFile(const QUrl& url) const;
|
|
||||||
|
|
||||||
private:
|
|
||||||
bool loadSourceImage(const QUrl& source, QImage& image) const;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
} // namespace ZShell
|
} // namespace ZShell
|
||||||
|
|||||||
@@ -1,52 +1,16 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
import sys
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
import typer
|
import typer
|
||||||
from typer._completion_shared import install, _get_shell_name
|
|
||||||
from zshell.subcommands import shell, scheme, screenshot, wallpaper, record
|
from zshell.subcommands import shell, scheme, screenshot, wallpaper, record
|
||||||
|
|
||||||
app = typer.Typer(name="zshell-cli", add_completion=False)
|
app = typer.Typer()
|
||||||
|
|
||||||
app.add_typer(shell.app, name="shell")
|
app.add_typer(shell.app, name="shell")
|
||||||
app.add_typer(scheme.app, name="scheme")
|
app.add_typer(scheme.app, name="scheme")
|
||||||
app.add_typer(screenshot.app, name="screenshot")
|
app.add_typer(screenshot.app, name="screenshot")
|
||||||
app.add_typer(wallpaper.app, name="wallpaper")
|
app.add_typer(wallpaper.app, name="wallpaper")
|
||||||
app.add_typer(record.app, name="record")
|
app.add_typer(record.app, name="record")
|
||||||
|
# app.add_typer(preset.app, name="preset")
|
||||||
|
|
||||||
def _completion_installed() -> bool:
|
|
||||||
shell = _get_shell_name()
|
|
||||||
match shell:
|
|
||||||
case "zsh":
|
|
||||||
return (Path.home() / ".zfunc" / "_zshell-cli").exists()
|
|
||||||
case "bash":
|
|
||||||
return (Path.home() / ".bash_completions" / "zshell-cli.sh").exists()
|
|
||||||
case "fish":
|
|
||||||
return (Path.home() / ".config" / "fish" / "completions" / "zshell-cli.fish").exists()
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
def _install_completion() -> None:
|
|
||||||
if _completion_installed():
|
|
||||||
print("zshell-cli: Shell completion already installed.")
|
|
||||||
raise typer.Exit()
|
|
||||||
shell = _get_shell_name()
|
|
||||||
if shell is None:
|
|
||||||
print("zshell-cli: Unable to detect shell type.", file=sys.stderr)
|
|
||||||
raise typer.Exit(code=1)
|
|
||||||
try:
|
|
||||||
_, path = install(prog_name="zshell-cli")
|
|
||||||
print(f"zshell-cli: Shell completion installed ({shell}: {path})")
|
|
||||||
print("zshell-cli: Restart your shell or source the file to enable tab-completion.")
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def main() -> None:
|
def main() -> None:
|
||||||
if "--install-autocomplete" in sys.argv:
|
|
||||||
_install_completion()
|
|
||||||
return
|
|
||||||
if sys.stdout.isatty() and not _completion_installed():
|
|
||||||
print("zshell-cli: Tip: run with --install-autocomplete for tab completion.", file=sys.stderr)
|
|
||||||
app()
|
app()
|
||||||
|
|||||||
@@ -18,7 +18,8 @@ TEMP_RECORDING = STATE_DIR / "recording.mp4"
|
|||||||
REPLAY_RECORDING = STATE_DIR / "replay.mp4"
|
REPLAY_RECORDING = STATE_DIR / "replay.mp4"
|
||||||
NOTIF_ID_FILE = STATE_DIR / "notifid.txt"
|
NOTIF_ID_FILE = STATE_DIR / "notifid.txt"
|
||||||
|
|
||||||
RECORDINGS_DIR = os.getenv("ZSHELL_RECORDINGS_DIR", str(Path(HOME) / "Videos/Recordings"))
|
RECORDINGS_DIR = os.getenv("ZSHELL_RECORDINGS_DIR",
|
||||||
|
str(Path(HOME) / "Videos/Recordings"))
|
||||||
|
|
||||||
|
|
||||||
def _read_extra_args() -> list[str]:
|
def _read_extra_args() -> list[str]:
|
||||||
@@ -35,7 +36,7 @@ def _is_recording() -> bool:
|
|||||||
return subprocess.run(["pidof", RECORDER], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL).returncode == 0
|
return subprocess.run(["pidof", RECORDER], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL).returncode == 0
|
||||||
|
|
||||||
|
|
||||||
def _notify(summary: str, body: str = "", actions: list | None = None, timeout: int = 5000) -> Optional[int]:
|
def _notify(summary: str, body: str = "", actions: list = None, timeout: int = 5000) -> Optional[int]:
|
||||||
args = ["notify-send", summary, body, "-t", str(timeout), "-p"]
|
args = ["notify-send", summary, body, "-t", str(timeout), "-p"]
|
||||||
if actions:
|
if actions:
|
||||||
for action in actions:
|
for action in actions:
|
||||||
@@ -48,12 +49,14 @@ def _notify(summary: str, body: str = "", actions: list | None = None, timeout:
|
|||||||
|
|
||||||
|
|
||||||
def _close_notification(notif_id: int):
|
def _close_notification(notif_id: int):
|
||||||
subprocess.run(["notify-send", "--close", str(notif_id)], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
subprocess.run(["notify-send", "--close", str(notif_id)],
|
||||||
|
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
||||||
|
|
||||||
|
|
||||||
def _get_monitors() -> list[dict]:
|
def _get_monitors() -> list[dict]:
|
||||||
try:
|
try:
|
||||||
res = subprocess.run(["hyprctl", "monitors", "-j"], capture_output=True, text=True)
|
res = subprocess.run(["hyprctl", "monitors", "-j"],
|
||||||
|
capture_output=True, text=True)
|
||||||
return json.loads(res.stdout)
|
return json.loads(res.stdout)
|
||||||
except Exception:
|
except Exception:
|
||||||
return []
|
return []
|
||||||
@@ -89,7 +92,6 @@ def _slurp_region() -> Optional[str]:
|
|||||||
|
|
||||||
def _parse_geometry(geometry: str) -> Optional[tuple[int, int, int, int]]:
|
def _parse_geometry(geometry: str) -> Optional[tuple[int, int, int, int]]:
|
||||||
import re
|
import re
|
||||||
|
|
||||||
match = re.match(r"(\d+)x(\d+)\+(\d+)\+(\d+)", geometry)
|
match = re.match(r"(\d+)x(\d+)\+(\d+)\+(\d+)", geometry)
|
||||||
if match:
|
if match:
|
||||||
return int(match.group(3)), int(match.group(4)), int(match.group(1)), int(match.group(2))
|
return int(match.group(3)), int(match.group(4)), int(match.group(1)), int(match.group(2))
|
||||||
@@ -137,7 +139,8 @@ def start_recording(region: Optional[str], sound: bool):
|
|||||||
cmd.extend(extra_args)
|
cmd.extend(extra_args)
|
||||||
cmd.extend(["-o", str(TEMP_RECORDING)])
|
cmd.extend(["-o", str(TEMP_RECORDING)])
|
||||||
|
|
||||||
subprocess.Popen(cmd, start_new_session=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
subprocess.Popen(cmd, start_new_session=True,
|
||||||
|
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
||||||
|
|
||||||
notif_id = _notify("Recording started", f"Saving to {TEMP_RECORDING}")
|
notif_id = _notify("Recording started", f"Saving to {TEMP_RECORDING}")
|
||||||
if notif_id is not None:
|
if notif_id is not None:
|
||||||
@@ -145,12 +148,14 @@ def start_recording(region: Optional[str], sound: bool):
|
|||||||
|
|
||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
if not _is_recording():
|
if not _is_recording():
|
||||||
_notify("Recording failed", "Check gpu-screen-recorder output.", timeout=5000)
|
_notify("Recording failed",
|
||||||
|
"Check gpu-screen-recorder output.", timeout=5000)
|
||||||
raise typer.Exit(code=1)
|
raise typer.Exit(code=1)
|
||||||
|
|
||||||
|
|
||||||
def stop_recording(clipboard: bool):
|
def stop_recording(clipboard: bool):
|
||||||
subprocess.run(["pkill", "-f", RECORDER], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
subprocess.run(["pkill", "-f", RECORDER],
|
||||||
|
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
||||||
|
|
||||||
for _ in range(50):
|
for _ in range(50):
|
||||||
if not _is_recording():
|
if not _is_recording():
|
||||||
@@ -173,31 +178,30 @@ def stop_recording(clipboard: bool):
|
|||||||
NOTIF_ID_FILE.unlink()
|
NOTIF_ID_FILE.unlink()
|
||||||
|
|
||||||
if clipboard:
|
if clipboard:
|
||||||
subprocess.run(
|
subprocess.run(["wl-copy", "--type", "text/uri-list", f"file://{final_path}"],
|
||||||
["wl-copy", "--type", "text/uri-list", f"file://{final_path}"],
|
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
||||||
stdout=subprocess.DEVNULL,
|
|
||||||
stderr=subprocess.DEVNULL,
|
|
||||||
)
|
|
||||||
|
|
||||||
_notify("Recording stopped", f"Saved to {final_path}", timeout=5000)
|
_notify("Recording stopped", f"Saved to {final_path}", timeout=5000)
|
||||||
|
|
||||||
|
|
||||||
def toggle_pause():
|
def toggle_pause():
|
||||||
subprocess.run(["pkill", "-USR2", "-f", RECORDER], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
subprocess.run(["pkill", "-USR2", "-f", RECORDER],
|
||||||
|
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
||||||
typer.echo("Toggled pause.")
|
typer.echo("Toggled pause.")
|
||||||
|
|
||||||
|
|
||||||
@app.command()
|
@app.command()
|
||||||
def record(
|
def record(
|
||||||
region: Optional[str] = typer.Option(
|
region: Optional[str] = typer.Option(
|
||||||
None,
|
None, "--region", "-r",
|
||||||
"--region",
|
|
||||||
"-r",
|
|
||||||
help="Record a region. Use 'slurp' (or omit value) to select interactively, or give 'WxH+X+Y'.",
|
help="Record a region. Use 'slurp' (or omit value) to select interactively, or give 'WxH+X+Y'.",
|
||||||
),
|
),
|
||||||
sound: bool = typer.Option(False, "--sound", "-s", help="Record audio from default output."),
|
sound: bool = typer.Option(
|
||||||
pause: bool = typer.Option(False, "--pause", "-p", help="Toggle pause/resume."),
|
False, "--sound", "-s", help="Record audio from default output."),
|
||||||
clipboard: bool = typer.Option(False, "--clipboard", "-c", help="Copy the final recording path to clipboard."),
|
pause: bool = typer.Option(
|
||||||
|
False, "--pause", "-p", help="Toggle pause/resume."),
|
||||||
|
clipboard: bool = typer.Option(
|
||||||
|
False, "--clipboard", "-c", help="Copy the final recording path to clipboard."),
|
||||||
):
|
):
|
||||||
"""Start or stop a screen recording with gpu-screen-recorder."""
|
"""Start or stop a screen recording with gpu-screen-recorder."""
|
||||||
if pause:
|
if pause:
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import typer
|
|||||||
import json
|
import json
|
||||||
import shutil
|
import shutil
|
||||||
import os
|
import os
|
||||||
import sys
|
|
||||||
import re
|
import re
|
||||||
import subprocess
|
import subprocess
|
||||||
|
|
||||||
@@ -16,61 +15,11 @@ from materialyoucolor.score.score import Score
|
|||||||
from materialyoucolor.dynamiccolor.material_dynamic_colors import MaterialDynamicColors
|
from materialyoucolor.dynamiccolor.material_dynamic_colors import MaterialDynamicColors
|
||||||
from materialyoucolor.hct.hct import Hct
|
from materialyoucolor.hct.hct import Hct
|
||||||
from materialyoucolor.utils.color_utils import argb_from_rgb
|
from materialyoucolor.utils.color_utils import argb_from_rgb
|
||||||
from materialyoucolor.utils.math_utils import (
|
from materialyoucolor.utils.math_utils import difference_degrees, rotation_direction, sanitize_degrees_double
|
||||||
difference_degrees,
|
|
||||||
rotation_direction,
|
|
||||||
sanitize_degrees_double,
|
|
||||||
)
|
|
||||||
|
|
||||||
app = typer.Typer()
|
app = typer.Typer()
|
||||||
|
|
||||||
|
|
||||||
def _complete_scheme_name(incomplete):
|
|
||||||
schemes = [
|
|
||||||
"fruit-salad",
|
|
||||||
"expressive",
|
|
||||||
"monochrome",
|
|
||||||
"rainbow",
|
|
||||||
"tonal-spot",
|
|
||||||
"neutral",
|
|
||||||
"fidelity",
|
|
||||||
"content",
|
|
||||||
"vibrant",
|
|
||||||
]
|
|
||||||
return [s for s in schemes if incomplete in s]
|
|
||||||
|
|
||||||
|
|
||||||
def _complete_preset(incomplete):
|
|
||||||
results = []
|
|
||||||
for sid, meta in list_schemes().items():
|
|
||||||
for v in meta.variants:
|
|
||||||
preset = f"{sid}:{v.id}"
|
|
||||||
if incomplete in preset:
|
|
||||||
results.append((preset, f"{meta.name} - {v.name}"))
|
|
||||||
return results
|
|
||||||
|
|
||||||
|
|
||||||
def _complete_mode(incomplete):
|
|
||||||
return [m for m in ("dark", "light") if incomplete in m]
|
|
||||||
|
|
||||||
|
|
||||||
def _complete_accent(ctx, incomplete):
|
|
||||||
preset_val = ctx.params.get("preset")
|
|
||||||
if preset_val:
|
|
||||||
try:
|
|
||||||
p_scheme, p_variant = resolve_preset(preset_val)
|
|
||||||
for v in list_schemes()[p_scheme].variants:
|
|
||||||
if v.id == p_variant:
|
|
||||||
return [a for a in v.accents if incomplete in a]
|
|
||||||
except (ValueError, KeyError):
|
|
||||||
pass
|
|
||||||
all_accents = set()
|
|
||||||
for meta in list_schemes().values():
|
|
||||||
for v in meta.variants:
|
|
||||||
all_accents.update(v.accents)
|
|
||||||
return [a for a in sorted(all_accents) if incomplete in a]
|
|
||||||
|
|
||||||
|
|
||||||
@app.command()
|
@app.command()
|
||||||
def list_presets(
|
def list_presets(
|
||||||
json_format: bool = typer.Option(False, "--json", help="Output in JSON format"),
|
json_format: bool = typer.Option(False, "--json", help="Output in JSON format"),
|
||||||
@@ -81,7 +30,7 @@ def list_presets(
|
|||||||
for sid, meta in sorted(schemes.items()):
|
for sid, meta in sorted(schemes.items()):
|
||||||
variants = {}
|
variants = {}
|
||||||
for v in meta.variants:
|
for v in meta.variants:
|
||||||
entry: dict[str, Any] = {"modes": sorted(v.modes)}
|
entry = {"modes": sorted(v.modes)}
|
||||||
if v.accents:
|
if v.accents:
|
||||||
entry["accents"] = sorted(v.accents)
|
entry["accents"] = sorted(v.accents)
|
||||||
entry["default_accent"] = sorted(v.accents)[0]
|
entry["default_accent"] = sorted(v.accents)[0]
|
||||||
@@ -106,35 +55,14 @@ def list_presets(
|
|||||||
|
|
||||||
@app.command()
|
@app.command()
|
||||||
def generate(
|
def generate(
|
||||||
image_path: Optional[Path] = typer.Option(
|
image_path: Optional[Path] = typer.Option(None, help="Path to source image. Required for image mode."),
|
||||||
None, help="Path to source image. Required for image mode."
|
|
||||||
),
|
|
||||||
scheme: Optional[str] = typer.Option(
|
scheme: Optional[str] = typer.Option(
|
||||||
None,
|
None, help="Color scheme algorithm to use for image mode. Ignored in preset mode."
|
||||||
help="Color scheme algorithm to use for image mode. Ignored in preset mode.",
|
|
||||||
autocompletion=_complete_scheme_name,
|
|
||||||
),
|
|
||||||
preset: Optional[str] = typer.Option(
|
|
||||||
None,
|
|
||||||
help="Name of a premade scheme in this format: <scheme>:<variant>",
|
|
||||||
autocompletion=_complete_preset,
|
|
||||||
),
|
|
||||||
mode: Optional[str] = typer.Option(
|
|
||||||
None,
|
|
||||||
help="Mode of the preset scheme (dark or light).",
|
|
||||||
autocompletion=_complete_mode,
|
|
||||||
),
|
|
||||||
accent: Optional[str] = typer.Option(
|
|
||||||
None,
|
|
||||||
help="Accent for schemes that support it (e.g. mauve).",
|
|
||||||
autocompletion=_complete_accent,
|
|
||||||
),
|
),
|
||||||
|
preset: Optional[str] = typer.Option(None, help="Name of a premade scheme in this format: <scheme>:<variant>"),
|
||||||
|
mode: Optional[str] = typer.Option(None, help="Mode of the preset scheme (dark or light)."),
|
||||||
|
accent: Optional[str] = typer.Option(None, help="Accent for schemes that support it (e.g. mauve)."),
|
||||||
):
|
):
|
||||||
if not any([image_path, scheme, preset, mode, accent]):
|
|
||||||
print(
|
|
||||||
"Hint: use --preset <scheme>:<variant> or --image-path <path>",
|
|
||||||
file=sys.stderr,
|
|
||||||
)
|
|
||||||
|
|
||||||
HOME = str(os.getenv("HOME"))
|
HOME = str(os.getenv("HOME"))
|
||||||
OUTPUT = Path(HOME + "/.local/state/zshell/scheme.json")
|
OUTPUT = Path(HOME + "/.local/state/zshell/scheme.json")
|
||||||
@@ -272,15 +200,11 @@ def generate(
|
|||||||
def harmonize(from_hct: Hct, to_hct: Hct, tone_boost: float) -> Hct:
|
def harmonize(from_hct: Hct, to_hct: Hct, tone_boost: float) -> Hct:
|
||||||
diff = difference_degrees(from_hct.hue, to_hct.hue)
|
diff = difference_degrees(from_hct.hue, to_hct.hue)
|
||||||
rotation = min(diff * 0.8, 100)
|
rotation = min(diff * 0.8, 100)
|
||||||
output_hue = sanitize_degrees_double(
|
output_hue = sanitize_degrees_double(from_hct.hue + rotation * rotation_direction(from_hct.hue, to_hct.hue))
|
||||||
from_hct.hue + rotation * rotation_direction(from_hct.hue, to_hct.hue)
|
|
||||||
)
|
|
||||||
tone = max(0.0, min(100.0, from_hct.tone * (1 + tone_boost)))
|
tone = max(0.0, min(100.0, from_hct.tone * (1 + tone_boost)))
|
||||||
return Hct.from_hct(output_hue, from_hct.chroma, tone)
|
return Hct.from_hct(output_hue, from_hct.chroma, tone)
|
||||||
|
|
||||||
def terminal_palette(
|
def terminal_palette(colors: dict[str, str], mode: str, variant: str) -> dict[str, str]:
|
||||||
colors: dict[str, str], mode: str, variant: str
|
|
||||||
) -> dict[str, str]:
|
|
||||||
light = mode.lower() == "light"
|
light = mode.lower() == "light"
|
||||||
|
|
||||||
key_hex = (
|
key_hex = (
|
||||||
@@ -312,7 +236,7 @@ def generate(
|
|||||||
|
|
||||||
image = Image.open(image_path)
|
image = Image.open(image_path)
|
||||||
image = image.convert("RGB")
|
image = image.convert("RGB")
|
||||||
image.thumbnail(size, Image.Resampling.NEAREST)
|
image.thumbnail(size, Image.NEAREST)
|
||||||
|
|
||||||
thumbnail_file.parent.mkdir(parents=True, exist_ok=True)
|
thumbnail_file.parent.mkdir(parents=True, exist_ok=True)
|
||||||
image.save(thumbnail_path, "JPEG")
|
image.save(thumbnail_path, "JPEG")
|
||||||
@@ -344,15 +268,8 @@ def generate(
|
|||||||
is_dark = ""
|
is_dark = ""
|
||||||
|
|
||||||
with Image.open(image_path) as img:
|
with Image.open(image_path) as img:
|
||||||
img.thumbnail((1, 1), Image.Resampling.LANCZOS)
|
img.thumbnail((1, 1), Image.LANCZOS)
|
||||||
px = img.getpixel((0, 0))
|
hct = Hct.from_int(argb_from_rgb(*img.getpixel((0, 0))))
|
||||||
if isinstance(px, (int, float)):
|
|
||||||
r = g = b = int(px)
|
|
||||||
elif px is not None:
|
|
||||||
r, g, b = int(px[0]), int(px[1]), int(px[2])
|
|
||||||
else:
|
|
||||||
r = g = b = 0
|
|
||||||
hct = Hct.from_int(argb_from_rgb(r, g, b))
|
|
||||||
is_dark = "light" if hct.tone > 50 else "dark"
|
is_dark = "light" if hct.tone > 50 else "dark"
|
||||||
|
|
||||||
return is_dark
|
return is_dark
|
||||||
@@ -514,8 +431,6 @@ def generate(
|
|||||||
|
|
||||||
raw = tpl_path.read_text(encoding="utf-8")
|
raw = tpl_path.read_text(encoding="utf-8")
|
||||||
out_path, body = split_directive_and_body(raw)
|
out_path, body = split_directive_and_body(raw)
|
||||||
if out_path is None:
|
|
||||||
continue
|
|
||||||
|
|
||||||
out_path.parent.mkdir(parents=True, exist_ok=True)
|
out_path.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
@@ -569,30 +484,23 @@ def generate(
|
|||||||
with CONFIG.open() as f:
|
with CONFIG.open() as f:
|
||||||
config = json.load(f)
|
config = json.load(f)
|
||||||
|
|
||||||
scheme_type = config["colors"].get("schemeType", "fruit-salad")
|
scheme = scheme or config["colors"]["schemeType"]
|
||||||
scheme = scheme or scheme_type
|
|
||||||
assert isinstance(scheme, str)
|
|
||||||
config_mode = config["general"]["color"]["mode"]
|
config_mode = config["general"]["color"]["mode"]
|
||||||
smart = bool(config["general"]["color"].get("smart", False))
|
smart = bool(config["general"]["color"].get("smart", False))
|
||||||
scheme_class = get_scheme_class(scheme)
|
scheme_class = get_scheme_class(scheme)
|
||||||
|
|
||||||
p_variant = "default"
|
|
||||||
if preset:
|
if preset:
|
||||||
p_scheme, p_variant = resolve_preset(preset)
|
p_scheme, p_variant = resolve_preset(preset)
|
||||||
schemes = list_schemes()
|
schemes = list_schemes()
|
||||||
if accent and p_scheme in schemes:
|
if accent and p_scheme in schemes:
|
||||||
meta = schemes[p_scheme]
|
meta = schemes[p_scheme]
|
||||||
var_accents = next(
|
var_accents = next((v.accents for v in meta.variants if v.id == p_variant), ())
|
||||||
(v.accents for v in meta.variants if v.id == p_variant), ()
|
|
||||||
)
|
|
||||||
if accent not in var_accents:
|
if accent not in var_accents:
|
||||||
available = ", ".join(var_accents) if var_accents else "none"
|
available = ", ".join(var_accents) if var_accents else "none"
|
||||||
raise typer.BadParameter(
|
raise typer.BadParameter(
|
||||||
f"Accent '{accent}' not available for '{p_scheme}:{p_variant}'. Available accents: {available}"
|
f"Accent '{accent}' not available for '{p_scheme}:{p_variant}'. Available accents: {available}"
|
||||||
)
|
)
|
||||||
palette_obj = get_palette(
|
palette_obj = get_palette(p_scheme, p_variant, mode or config_mode, accent=accent)
|
||||||
p_scheme, p_variant, mode or config_mode, accent=accent
|
|
||||||
)
|
|
||||||
colors = palette_obj.colors
|
colors = palette_obj.colors
|
||||||
effective_mode = palette_obj.mode
|
effective_mode = palette_obj.mode
|
||||||
name = palette_obj.scheme
|
name = palette_obj.scheme
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import subprocess
|
|||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
|
|
||||||
|
import click
|
||||||
import typer
|
import typer
|
||||||
|
|
||||||
args = ["qs", "-c", "zshell"]
|
args = ["qs", "-c", "zshell"]
|
||||||
@@ -13,8 +14,7 @@ app = typer.Typer()
|
|||||||
def kill():
|
def kill():
|
||||||
result = subprocess.run(args + ["kill"], capture_output=True)
|
result = subprocess.run(args + ["kill"], capture_output=True)
|
||||||
if result.returncode != 0:
|
if result.returncode != 0:
|
||||||
sys.stderr.write("No running instance to kill.\n")
|
raise click.ClickException("No running instance to kill.")
|
||||||
sys.exit(1)
|
|
||||||
sys.stderr.write(result.stderr.decode())
|
sys.stderr.write(result.stderr.decode())
|
||||||
|
|
||||||
|
|
||||||
@@ -23,12 +23,10 @@ def start_instance(no_daemon: bool = False) -> None:
|
|||||||
stdout = result.stdout.decode().strip()
|
stdout = result.stdout.decode().strip()
|
||||||
if stdout:
|
if stdout:
|
||||||
if "already running" in stdout.lower():
|
if "already running" in stdout.lower():
|
||||||
sys.stderr.write(stdout + "\n")
|
raise click.ClickException(stdout)
|
||||||
sys.exit(1)
|
|
||||||
if result.returncode != 0:
|
if result.returncode != 0:
|
||||||
stderr = result.stderr.decode().strip()
|
stderr = result.stderr.decode().strip()
|
||||||
sys.stderr.write(stderr + "\n")
|
raise click.ClickException(stderr)
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
|
|
||||||
@app.command()
|
@app.command()
|
||||||
@@ -52,9 +50,7 @@ def restart(no_daemon: bool = False):
|
|||||||
def show():
|
def show():
|
||||||
result = subprocess.run(args + ["ipc"] + ["show"], capture_output=True)
|
result = subprocess.run(args + ["ipc"] + ["show"], capture_output=True)
|
||||||
if result.returncode != 0:
|
if result.returncode != 0:
|
||||||
sys.stderr.write(result.stderr.decode())
|
raise click.ClickException(result.stderr.decode().strip())
|
||||||
sys.exit(1)
|
|
||||||
sys.stdout.write(result.stdout.decode())
|
|
||||||
sys.stderr.write(result.stderr.decode())
|
sys.stderr.write(result.stderr.decode())
|
||||||
|
|
||||||
|
|
||||||
@@ -62,8 +58,7 @@ def show():
|
|||||||
def log():
|
def log():
|
||||||
result = subprocess.run(args + ["log"], capture_output=True)
|
result = subprocess.run(args + ["log"], capture_output=True)
|
||||||
if result.returncode != 0:
|
if result.returncode != 0:
|
||||||
sys.stderr.write(result.stderr.decode())
|
raise click.ClickException(result.stderr.decode().strip())
|
||||||
sys.exit(1)
|
|
||||||
sys.stdout.write(result.stdout.decode())
|
sys.stdout.write(result.stdout.decode())
|
||||||
sys.stderr.write(result.stderr.decode())
|
sys.stderr.write(result.stderr.decode())
|
||||||
|
|
||||||
@@ -72,8 +67,7 @@ def log():
|
|||||||
def lock():
|
def lock():
|
||||||
result = subprocess.run(args + ["ipc"] + ["call"] + ["lock"] + ["lock"], capture_output=True)
|
result = subprocess.run(args + ["ipc"] + ["call"] + ["lock"] + ["lock"], capture_output=True)
|
||||||
if result.returncode != 0:
|
if result.returncode != 0:
|
||||||
sys.stderr.write(result.stderr.decode())
|
raise click.ClickException(result.stderr.decode().strip())
|
||||||
sys.exit(1)
|
|
||||||
sys.stderr.write(result.stderr.decode())
|
sys.stderr.write(result.stderr.decode())
|
||||||
|
|
||||||
|
|
||||||
@@ -81,6 +75,5 @@ def lock():
|
|||||||
def call(target: str, method: str, method_args: list[str] = typer.Argument(None)):
|
def call(target: str, method: str, method_args: list[str] = typer.Argument(None)):
|
||||||
result = subprocess.run(args + ["ipc"] + ["call"] + [target] + [method] + (method_args or []), capture_output=True)
|
result = subprocess.run(args + ["ipc"] + ["call"] + [target] + [method] + (method_args or []), capture_output=True)
|
||||||
if result.returncode != 0:
|
if result.returncode != 0:
|
||||||
sys.stderr.write(result.stderr.decode())
|
raise click.ClickException(result.stderr.decode().strip())
|
||||||
sys.exit(1)
|
|
||||||
sys.stderr.write(result.stderr.decode())
|
sys.stderr.write(result.stderr.decode())
|
||||||
|
|||||||
@@ -34,9 +34,9 @@ def lockscreen(
|
|||||||
return
|
return
|
||||||
|
|
||||||
if size[0] < 3840 or size[1] < 2160:
|
if size[0] < 3840 or size[1] < 2160:
|
||||||
img = img.resize((size[0] // 2, size[1] // 2), Image.Resampling.NEAREST)
|
img = img.resize((size[0] // 2, size[1] // 2), Image.NEAREST)
|
||||||
else:
|
else:
|
||||||
img = img.resize((size[0] // 4, size[1] // 4), Image.Resampling.NEAREST)
|
img = img.resize((size[0] // 4, size[1] // 4), Image.NEAREST)
|
||||||
|
|
||||||
img = img.filter(ImageFilter.GaussianBlur(blur_amount))
|
img = img.filter(ImageFilter.GaussianBlur(blur_amount))
|
||||||
|
|
||||||
|
|||||||
@@ -62,9 +62,8 @@ class TestStart:
|
|||||||
class TestShow:
|
class TestShow:
|
||||||
@patch("zshell.subcommands.shell.subprocess.run")
|
@patch("zshell.subcommands.shell.subprocess.run")
|
||||||
def test_show_runs_ipc_show(self, mock_run):
|
def test_show_runs_ipc_show(self, mock_run):
|
||||||
mock_run.return_value = CompletedProcess([], 0, b"target visibilities\n", b"")
|
mock_run.return_value = CompletedProcess([], 0, b"", b"target visibilities\n")
|
||||||
result = invoke("show")
|
invoke("show")
|
||||||
assert "target visibilities" in result.output
|
|
||||||
mock_run.assert_called_once_with(["qs", "-c", "zshell", "ipc", "show"], capture_output=True)
|
mock_run.assert_called_once_with(["qs", "-c", "zshell", "ipc", "show"], capture_output=True)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -180,14 +180,6 @@ export const settingsIndex = [
|
|||||||
section: "Bar",
|
section: "Bar",
|
||||||
keywords: ["smoothing", "rounding"],
|
keywords: ["smoothing", "rounding"],
|
||||||
},
|
},
|
||||||
// System tray section
|
|
||||||
{
|
|
||||||
name: "Tray icon size",
|
|
||||||
category: "bar",
|
|
||||||
categoryName: "Bar",
|
|
||||||
section: "Tray",
|
|
||||||
keywords: ["tray", "icon", "size"],
|
|
||||||
},
|
|
||||||
// Popouts section
|
// Popouts section
|
||||||
{
|
{
|
||||||
name: "Tray",
|
name: "Tray",
|
||||||
|
|||||||
@@ -1,25 +1,20 @@
|
|||||||
//@ pragma UseQApplication
|
//@ pragma UseQApplication
|
||||||
//@ pragma Env QSG_RENDER_LOOP=threaded
|
//@ pragma Env QSG_RENDER_LOOP=threaded
|
||||||
// @ pragma Env QSG_RHI_BACKEND=vulkan
|
//@ pragma Env QSG_RHI_BACKEND=vulkan
|
||||||
//@ pragma Env QSG_NO_VSYNC=1
|
//@ pragma Env QSG_NO_VSYNC=1
|
||||||
//@ pragma Env QS_NO_RELOAD_POPUP=1
|
//@ pragma Env QS_NO_RELOAD_POPUP=1
|
||||||
//@ pragma Env QT_SCALE_FACTOR_ROUNDING_POLICY=Round
|
//@ pragma Env QT_SCALE_FACTOR_ROUNDING_POLICY=Round
|
||||||
//@ pragma DropExpensiveFonts
|
//@ pragma DropExpensiveFonts
|
||||||
import Quickshell
|
import Quickshell
|
||||||
import Quickshell.Services.UPower
|
import qs.Extensions
|
||||||
import qs.Modules
|
import qs.Modules
|
||||||
import qs.Modules.Wallpaper
|
import qs.Modules.Wallpaper
|
||||||
import qs.Modules.Lock
|
import qs.Modules.Lock
|
||||||
import qs.Drawers
|
import qs.Drawers
|
||||||
import qs.Helpers
|
import qs.Helpers
|
||||||
import qs.Modules.Polkit
|
import qs.Modules.Polkit
|
||||||
import qs.Daemons
|
|
||||||
|
|
||||||
ShellRoot {
|
ShellRoot {
|
||||||
id: root
|
|
||||||
|
|
||||||
readonly property bool laptop: UPower.displayDevice.isLaptopBattery
|
|
||||||
|
|
||||||
settings.watchFiles: true
|
settings.watchFiles: true
|
||||||
|
|
||||||
Windows {
|
Windows {
|
||||||
@@ -45,10 +40,6 @@ ShellRoot {
|
|||||||
Polkit {
|
Polkit {
|
||||||
}
|
}
|
||||||
|
|
||||||
LazyLoader {
|
LoadExtensions {
|
||||||
activeAsync: root.laptop
|
|
||||||
|
|
||||||
component: Battery {
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Generated
+807
-3
@@ -8,12 +8,47 @@ version = "2.0.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
|
checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "aligned"
|
||||||
|
version = "0.4.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ee4508988c62edf04abd8d92897fca0c2995d907ce1dfeaf369dac3716a40685"
|
||||||
|
dependencies = [
|
||||||
|
"as-slice",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "aligned-vec"
|
||||||
|
version = "0.6.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "dc890384c8602f339876ded803c97ad529f3842aba97f6392b3dba0dd171769b"
|
||||||
|
dependencies = [
|
||||||
|
"equator",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "anyhow"
|
name = "anyhow"
|
||||||
version = "1.0.102"
|
version = "1.0.102"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c"
|
checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "arbitrary"
|
||||||
|
version = "1.4.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "arg_enum_proc_macro"
|
||||||
|
version = "0.3.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0ae92a5119aa49cdbcf6b9f893fe4e1d98b04ccbf82ee0584ad948a44a734dea"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "arrayref"
|
name = "arrayref"
|
||||||
version = "0.3.9"
|
version = "0.3.9"
|
||||||
@@ -26,18 +61,103 @@ version = "0.7.6"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50"
|
checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "as-slice"
|
||||||
|
version = "0.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "516b6b4f0e40d50dcda9365d53964ec74560ad4284da2e7fc97122cd83174516"
|
||||||
|
dependencies = [
|
||||||
|
"stable_deref_trait",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "autocfg"
|
name = "autocfg"
|
||||||
version = "1.5.0"
|
version = "1.5.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
|
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "av-scenechange"
|
||||||
|
version = "0.14.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0f321d77c20e19b92c39e7471cf986812cbb46659d2af674adc4331ef3f18394"
|
||||||
|
dependencies = [
|
||||||
|
"aligned",
|
||||||
|
"anyhow",
|
||||||
|
"arg_enum_proc_macro",
|
||||||
|
"arrayvec",
|
||||||
|
"log",
|
||||||
|
"num-rational",
|
||||||
|
"num-traits",
|
||||||
|
"pastey",
|
||||||
|
"rayon",
|
||||||
|
"thiserror",
|
||||||
|
"v_frame",
|
||||||
|
"y4m",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "av1-grain"
|
||||||
|
version = "0.2.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8cfddb07216410377231960af4fcab838eaa12e013417781b78bd95ee22077f8"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"arrayvec",
|
||||||
|
"log",
|
||||||
|
"nom",
|
||||||
|
"num-rational",
|
||||||
|
"v_frame",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "avif-serialize"
|
||||||
|
version = "0.8.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "375082f007bd67184fb9c0374614b29f9aaa604ec301635f72338bb65386a53d"
|
||||||
|
dependencies = [
|
||||||
|
"arrayvec",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bit_field"
|
||||||
|
version = "0.10.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1e4b40c7323adcfc0a41c4b88143ed58346ff65a288fc144329c5c45e05d70c6"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bitflags"
|
||||||
|
version = "1.3.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bitflags"
|
name = "bitflags"
|
||||||
version = "2.11.1"
|
version = "2.11.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3"
|
checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bitstream-io"
|
||||||
|
version = "4.10.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7eff00be299a18769011411c9def0d827e8f2d7bf0c3dbf53633147a8867fd1f"
|
||||||
|
dependencies = [
|
||||||
|
"no_std_io2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "built"
|
||||||
|
version = "0.8.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f4ad8f11f288f48ca24471bbd51ac257aaeaaa07adae295591266b792902ae64"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bumpalo"
|
||||||
|
version = "3.20.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bytemuck"
|
name = "bytemuck"
|
||||||
version = "1.25.0"
|
version = "1.25.0"
|
||||||
@@ -50,12 +170,30 @@ version = "0.1.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495"
|
checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cc"
|
||||||
|
version = "1.2.61"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d16d90359e986641506914ba71350897565610e87ce0ad9e6f28569db3dd5c6d"
|
||||||
|
dependencies = [
|
||||||
|
"find-msvc-tools",
|
||||||
|
"jobserver",
|
||||||
|
"libc",
|
||||||
|
"shlex",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cfg-if"
|
name = "cfg-if"
|
||||||
version = "1.0.4"
|
version = "1.0.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
|
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "color_quant"
|
||||||
|
version = "1.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "crc32fast"
|
name = "crc32fast"
|
||||||
version = "1.5.0"
|
version = "1.5.0"
|
||||||
@@ -65,6 +203,84 @@ dependencies = [
|
|||||||
"cfg-if",
|
"cfg-if",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "crossbeam-deque"
|
||||||
|
version = "0.8.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51"
|
||||||
|
dependencies = [
|
||||||
|
"crossbeam-epoch",
|
||||||
|
"crossbeam-utils",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "crossbeam-epoch"
|
||||||
|
version = "0.9.18"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e"
|
||||||
|
dependencies = [
|
||||||
|
"crossbeam-utils",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "crossbeam-utils"
|
||||||
|
version = "0.8.21"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "crunchy"
|
||||||
|
version = "0.2.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "either"
|
||||||
|
version = "1.15.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "equator"
|
||||||
|
version = "0.4.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4711b213838dfee0117e3be6ac926007d7f433d7bbe33595975d4190cb07e6fc"
|
||||||
|
dependencies = [
|
||||||
|
"equator-macro",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "equator-macro"
|
||||||
|
version = "0.4.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "44f23cf4b44bfce11a86ace86f8a73ffdec849c9fd00a386a53d278bd9e81fb3"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "exr"
|
||||||
|
version = "1.74.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4300e043a56aa2cb633c01af81ca8f699a321879a7854d3896a0ba89056363be"
|
||||||
|
dependencies = [
|
||||||
|
"bit_field",
|
||||||
|
"half",
|
||||||
|
"lebe",
|
||||||
|
"miniz_oxide",
|
||||||
|
"rayon-core",
|
||||||
|
"smallvec",
|
||||||
|
"zune-inflate",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fax"
|
||||||
|
version = "0.2.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "caf1079563223d5d59d83c85886a56e586cfd5c1a26292e971a0fa266531ac5a"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fdeflate"
|
name = "fdeflate"
|
||||||
version = "0.3.7"
|
version = "0.3.7"
|
||||||
@@ -74,6 +290,12 @@ dependencies = [
|
|||||||
"simd-adler32",
|
"simd-adler32",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "find-msvc-tools"
|
||||||
|
version = "0.1.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "flate2"
|
name = "flate2"
|
||||||
version = "1.1.9"
|
version = "1.1.9"
|
||||||
@@ -84,6 +306,39 @@ dependencies = [
|
|||||||
"miniz_oxide",
|
"miniz_oxide",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "getrandom"
|
||||||
|
version = "0.3.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"libc",
|
||||||
|
"r-efi",
|
||||||
|
"wasip2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "gif"
|
||||||
|
version = "0.14.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ee8cfcc411d9adbbaba82fb72661cc1bcca13e8bba98b364e62b2dba8f960159"
|
||||||
|
dependencies = [
|
||||||
|
"color_quant",
|
||||||
|
"weezl",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "half"
|
||||||
|
version = "2.7.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"crunchy",
|
||||||
|
"zerocopy",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "image"
|
name = "image"
|
||||||
version = "0.25.10"
|
version = "0.25.10"
|
||||||
@@ -92,9 +347,56 @@ checksum = "85ab80394333c02fe689eaf900ab500fbd0c2213da414687ebf995a65d5a6104"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"bytemuck",
|
"bytemuck",
|
||||||
"byteorder-lite",
|
"byteorder-lite",
|
||||||
|
"color_quant",
|
||||||
|
"exr",
|
||||||
|
"gif",
|
||||||
|
"image-webp",
|
||||||
"moxcms",
|
"moxcms",
|
||||||
"num-traits",
|
"num-traits",
|
||||||
"png",
|
"png 0.18.1",
|
||||||
|
"qoi",
|
||||||
|
"ravif",
|
||||||
|
"rayon",
|
||||||
|
"rgb",
|
||||||
|
"tiff",
|
||||||
|
"zune-core",
|
||||||
|
"zune-jpeg",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "image-webp"
|
||||||
|
version = "0.2.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "525e9ff3e1a4be2fbea1fdf0e98686a6d98b4d8f937e1bf7402245af1909e8c3"
|
||||||
|
dependencies = [
|
||||||
|
"byteorder-lite",
|
||||||
|
"quick-error",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "imgref"
|
||||||
|
version = "1.12.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "40fac9d56ed6437b198fddba683305e8e2d651aa42647f00f5ae542e7f5c94a2"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "interpolate_name"
|
||||||
|
version = "0.2.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c34819042dc3d3971c46c2190835914dfbe0c3c13f61449b2997f4e9722dfa60"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "itertools"
|
||||||
|
version = "0.14.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285"
|
||||||
|
dependencies = [
|
||||||
|
"either",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -103,12 +405,63 @@ version = "1.0.18"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
|
checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "jobserver"
|
||||||
|
version = "0.1.34"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33"
|
||||||
|
dependencies = [
|
||||||
|
"getrandom",
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "lebe"
|
||||||
|
version = "0.5.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7a79a3332a6609480d7d0c9eab957bca6b455b91bb84e66d19f5ff66294b85b8"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "libc"
|
||||||
|
version = "0.2.186"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "libfuzzer-sys"
|
||||||
|
version = "0.4.12"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f12a681b7dd8ce12bff52488013ba614b869148d54dd79836ab85aafdd53f08d"
|
||||||
|
dependencies = [
|
||||||
|
"arbitrary",
|
||||||
|
"cc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "log"
|
name = "log"
|
||||||
version = "0.4.29"
|
version = "0.4.29"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
|
checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "loop9"
|
||||||
|
version = "0.1.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0fae87c125b03c1d2c0150c90365d7d6bcc53fb73a9acaef207d2d065860f062"
|
||||||
|
dependencies = [
|
||||||
|
"imgref",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "maybe-rayon"
|
||||||
|
version = "0.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8ea1f30cedd69f0a2954655f7188c6a834246d2bcf1e315e2ac40c4b24dc9519"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"rayon",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "memchr"
|
name = "memchr"
|
||||||
version = "2.8.0"
|
version = "2.8.0"
|
||||||
@@ -135,6 +488,77 @@ dependencies = [
|
|||||||
"pxfm",
|
"pxfm",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "new_debug_unreachable"
|
||||||
|
version = "1.0.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "no_std_io2"
|
||||||
|
version = "0.9.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b51ed7824b6e07d354605f4abb3d9d300350701299da96642ee084f5ce631550"
|
||||||
|
dependencies = [
|
||||||
|
"memchr",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "nom"
|
||||||
|
version = "8.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "df9761775871bdef83bee530e60050f7e54b1105350d6884eb0fb4f46c2f9405"
|
||||||
|
dependencies = [
|
||||||
|
"memchr",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "noop_proc_macro"
|
||||||
|
version = "0.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0676bb32a98c1a483ce53e500a81ad9c3d5b3f7c920c28c24e9cb0980d0b5bc8"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num-bigint"
|
||||||
|
version = "0.4.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9"
|
||||||
|
dependencies = [
|
||||||
|
"num-integer",
|
||||||
|
"num-traits",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num-derive"
|
||||||
|
version = "0.4.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num-integer"
|
||||||
|
version = "0.1.46"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f"
|
||||||
|
dependencies = [
|
||||||
|
"num-traits",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num-rational"
|
||||||
|
version = "0.4.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824"
|
||||||
|
dependencies = [
|
||||||
|
"num-bigint",
|
||||||
|
"num-integer",
|
||||||
|
"num-traits",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "num-traits"
|
name = "num-traits"
|
||||||
version = "0.2.19"
|
version = "0.2.19"
|
||||||
@@ -144,19 +568,59 @@ dependencies = [
|
|||||||
"autocfg",
|
"autocfg",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "once_cell"
|
||||||
|
version = "1.21.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "paste"
|
||||||
|
version = "1.0.15"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pastey"
|
||||||
|
version = "0.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "35fb2e5f958ec131621fdd531e9fc186ed768cbe395337403ae56c17a74c68ec"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "png"
|
||||||
|
version = "0.17.16"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "82151a2fc869e011c153adc57cf2789ccb8d9906ce52c0b39a6b5697749d7526"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 1.3.2",
|
||||||
|
"crc32fast",
|
||||||
|
"fdeflate",
|
||||||
|
"flate2",
|
||||||
|
"miniz_oxide",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "png"
|
name = "png"
|
||||||
version = "0.18.1"
|
version = "0.18.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "60769b8b31b2a9f263dae2776c37b1b28ae246943cf719eb6946a1db05128a61"
|
checksum = "60769b8b31b2a9f263dae2776c37b1b28ae246943cf719eb6946a1db05128a61"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags",
|
"bitflags 2.11.1",
|
||||||
"crc32fast",
|
"crc32fast",
|
||||||
"fdeflate",
|
"fdeflate",
|
||||||
"flate2",
|
"flate2",
|
||||||
"miniz_oxide",
|
"miniz_oxide",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ppv-lite86"
|
||||||
|
version = "0.2.21"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9"
|
||||||
|
dependencies = [
|
||||||
|
"zerocopy",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proc-macro2"
|
name = "proc-macro2"
|
||||||
version = "1.0.106"
|
version = "1.0.106"
|
||||||
@@ -166,12 +630,46 @@ dependencies = [
|
|||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "profiling"
|
||||||
|
version = "1.0.17"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3eb8486b569e12e2c32ad3e204dbaba5e4b5b216e9367044f25f1dba42341773"
|
||||||
|
dependencies = [
|
||||||
|
"profiling-procmacros",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "profiling-procmacros"
|
||||||
|
version = "1.0.17"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "52717f9a02b6965224f95ca2a81e2e0c5c43baacd28ca057577988930b6c3d5b"
|
||||||
|
dependencies = [
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pxfm"
|
name = "pxfm"
|
||||||
version = "0.1.29"
|
version = "0.1.29"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e0c5ccf5294c6ccd63a74f1565028353830a9c2f5eb0c682c355c471726a6e3f"
|
checksum = "e0c5ccf5294c6ccd63a74f1565028353830a9c2f5eb0c682c355c471726a6e3f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "qoi"
|
||||||
|
version = "0.4.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7f6d64c71eb498fe9eae14ce4ec935c555749aef511cca85b5568910d6e48001"
|
||||||
|
dependencies = [
|
||||||
|
"bytemuck",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "quick-error"
|
||||||
|
version = "2.0.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "quote"
|
name = "quote"
|
||||||
version = "1.0.45"
|
version = "1.0.45"
|
||||||
@@ -181,6 +679,123 @@ dependencies = [
|
|||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "r-efi"
|
||||||
|
version = "5.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand"
|
||||||
|
version = "0.9.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea"
|
||||||
|
dependencies = [
|
||||||
|
"rand_chacha",
|
||||||
|
"rand_core",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand_chacha"
|
||||||
|
version = "0.9.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"
|
||||||
|
dependencies = [
|
||||||
|
"ppv-lite86",
|
||||||
|
"rand_core",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand_core"
|
||||||
|
version = "0.9.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c"
|
||||||
|
dependencies = [
|
||||||
|
"getrandom",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rav1e"
|
||||||
|
version = "0.8.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "43b6dd56e85d9483277cde964fd1bdb0428de4fec5ebba7540995639a21cb32b"
|
||||||
|
dependencies = [
|
||||||
|
"aligned-vec",
|
||||||
|
"arbitrary",
|
||||||
|
"arg_enum_proc_macro",
|
||||||
|
"arrayvec",
|
||||||
|
"av-scenechange",
|
||||||
|
"av1-grain",
|
||||||
|
"bitstream-io",
|
||||||
|
"built",
|
||||||
|
"cfg-if",
|
||||||
|
"interpolate_name",
|
||||||
|
"itertools",
|
||||||
|
"libc",
|
||||||
|
"libfuzzer-sys",
|
||||||
|
"log",
|
||||||
|
"maybe-rayon",
|
||||||
|
"new_debug_unreachable",
|
||||||
|
"noop_proc_macro",
|
||||||
|
"num-derive",
|
||||||
|
"num-traits",
|
||||||
|
"paste",
|
||||||
|
"profiling",
|
||||||
|
"rand",
|
||||||
|
"rand_chacha",
|
||||||
|
"simd_helpers",
|
||||||
|
"thiserror",
|
||||||
|
"v_frame",
|
||||||
|
"wasm-bindgen",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ravif"
|
||||||
|
version = "0.13.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e52310197d971b0f5be7fe6b57530dcd27beb35c1b013f29d66c1ad73fbbcc45"
|
||||||
|
dependencies = [
|
||||||
|
"avif-serialize",
|
||||||
|
"imgref",
|
||||||
|
"loop9",
|
||||||
|
"quick-error",
|
||||||
|
"rav1e",
|
||||||
|
"rayon",
|
||||||
|
"rgb",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rayon"
|
||||||
|
version = "1.12.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "fb39b166781f92d482534ef4b4b1b2568f42613b53e5b6c160e24cfbfa30926d"
|
||||||
|
dependencies = [
|
||||||
|
"either",
|
||||||
|
"rayon-core",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rayon-core"
|
||||||
|
version = "1.13.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91"
|
||||||
|
dependencies = [
|
||||||
|
"crossbeam-deque",
|
||||||
|
"crossbeam-utils",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rgb"
|
||||||
|
version = "0.8.53"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "47b34b781b31e5d73e9fbc8689c70551fd1ade9a19e3e28cfec8580a79290cc4"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustversion"
|
||||||
|
version = "1.0.22"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde"
|
name = "serde"
|
||||||
version = "1.0.228"
|
version = "1.0.228"
|
||||||
@@ -224,12 +839,39 @@ dependencies = [
|
|||||||
"zmij",
|
"zmij",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "shlex"
|
||||||
|
version = "1.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "simd-adler32"
|
name = "simd-adler32"
|
||||||
version = "0.3.9"
|
version = "0.3.9"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214"
|
checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "simd_helpers"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "95890f873bec569a0362c235787f3aca6e1e887302ba4840839bcc6459c42da6"
|
||||||
|
dependencies = [
|
||||||
|
"quote",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "smallvec"
|
||||||
|
version = "1.15.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "stable_deref_trait"
|
||||||
|
version = "1.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "strict-num"
|
name = "strict-num"
|
||||||
version = "0.1.1"
|
version = "0.1.1"
|
||||||
@@ -247,6 +889,40 @@ dependencies = [
|
|||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "thiserror"
|
||||||
|
version = "2.0.18"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4"
|
||||||
|
dependencies = [
|
||||||
|
"thiserror-impl",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "thiserror-impl"
|
||||||
|
version = "2.0.18"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tiff"
|
||||||
|
version = "0.11.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b63feaf3343d35b6ca4d50483f94843803b0f51634937cc2ec519fc32232bc52"
|
||||||
|
dependencies = [
|
||||||
|
"fax",
|
||||||
|
"flate2",
|
||||||
|
"half",
|
||||||
|
"quick-error",
|
||||||
|
"weezl",
|
||||||
|
"zune-jpeg",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tiny-skia"
|
name = "tiny-skia"
|
||||||
version = "0.11.4"
|
version = "0.11.4"
|
||||||
@@ -258,6 +934,7 @@ dependencies = [
|
|||||||
"bytemuck",
|
"bytemuck",
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"log",
|
"log",
|
||||||
|
"png 0.17.16",
|
||||||
"tiny-skia-path",
|
"tiny-skia-path",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -278,6 +955,109 @@ version = "1.0.24"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
|
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "v_frame"
|
||||||
|
version = "0.3.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "666b7727c8875d6ab5db9533418d7c764233ac9c0cff1d469aec8fa127597be2"
|
||||||
|
dependencies = [
|
||||||
|
"aligned-vec",
|
||||||
|
"num-traits",
|
||||||
|
"wasm-bindgen",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasip2"
|
||||||
|
version = "1.0.3+wasi-0.2.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6"
|
||||||
|
dependencies = [
|
||||||
|
"wit-bindgen",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-bindgen"
|
||||||
|
version = "0.2.120"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "df52b6d9b87e0c74c9edfa1eb2d9bf85e5d63515474513aa50fa181b3c4f5db1"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"once_cell",
|
||||||
|
"rustversion",
|
||||||
|
"wasm-bindgen-macro",
|
||||||
|
"wasm-bindgen-shared",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-bindgen-macro"
|
||||||
|
version = "0.2.120"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "78b1041f495fb322e64aca85f5756b2172e35cd459376e67f2a6c9dffcedb103"
|
||||||
|
dependencies = [
|
||||||
|
"quote",
|
||||||
|
"wasm-bindgen-macro-support",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-bindgen-macro-support"
|
||||||
|
version = "0.2.120"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9dcd0ff20416988a18ac686d4d4d0f6aae9ebf08a389ff5d29012b05af2a1b41"
|
||||||
|
dependencies = [
|
||||||
|
"bumpalo",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
"wasm-bindgen-shared",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-bindgen-shared"
|
||||||
|
version = "0.2.120"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "49757b3c82ebf16c57d69365a142940b384176c24df52a087fb748e2085359ea"
|
||||||
|
dependencies = [
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "weezl"
|
||||||
|
version = "0.1.12"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a28ac98ddc8b9274cb41bb4d9d4d5c425b6020c50c46f25559911905610b4a88"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wit-bindgen"
|
||||||
|
version = "0.57.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "y4m"
|
||||||
|
version = "0.8.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7a5a4b21e1a62b67a2970e6831bc091d7b87e119e7f9791aef9702e3bef04448"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zerocopy"
|
||||||
|
version = "0.8.48"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9"
|
||||||
|
dependencies = [
|
||||||
|
"zerocopy-derive",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zerocopy-derive"
|
||||||
|
version = "0.8.48"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zmij"
|
name = "zmij"
|
||||||
version = "1.0.21"
|
version = "1.0.21"
|
||||||
@@ -286,7 +1066,7 @@ checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zshell-img-tools"
|
name = "zshell-img-tools"
|
||||||
version = "0.2.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"image",
|
"image",
|
||||||
@@ -294,3 +1074,27 @@ dependencies = [
|
|||||||
"serde_json",
|
"serde_json",
|
||||||
"tiny-skia",
|
"tiny-skia",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zune-core"
|
||||||
|
version = "0.5.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "cb8a0807f7c01457d0379ba880ba6322660448ddebc890ce29bb64da71fb40f9"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zune-inflate"
|
||||||
|
version = "0.2.54"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "73ab332fe2f6680068f3582b16a24f90ad7096d5d39b974d1c0aff0125116f02"
|
||||||
|
dependencies = [
|
||||||
|
"simd-adler32",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zune-jpeg"
|
||||||
|
version = "0.5.15"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "27bc9d5b815bc103f142aa054f561d9187d191692ec7c2d1e2b4737f8dbd7296"
|
||||||
|
dependencies = [
|
||||||
|
"zune-core",
|
||||||
|
]
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "zshell-img-tools"
|
name = "zshell-img-tools"
|
||||||
version = "0.2.0"
|
version = "0.1.0"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
@@ -8,10 +8,10 @@ name = "zshell-img-tools"
|
|||||||
path = "src/main.rs"
|
path = "src/main.rs"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
image = { version = "0.25", default-features = false, features = ["png"] }
|
image = { version = "0.25", features = ["png"] }
|
||||||
tiny-skia = { version = "0.11", default-features = false, features = ["std", "simd"] }
|
tiny-skia = "0.11"
|
||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
anyhow = "1.0"
|
anyhow = "1"
|
||||||
serde_json = "1.0.149"
|
serde_json = "1.0.149"
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
# What_That_Claude_DO?
|
||||||
|
|
||||||
|
What That Claude Do? (WTCD)
|
||||||
|
A repository of random things I ask Claude to do for me.
|
||||||
|
|
||||||
|
In this case it is creating a screenshot tool
|
||||||
@@ -11,14 +11,13 @@ pub struct Config {
|
|||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
pub struct EffectsConfig {
|
pub struct EffectsConfig {
|
||||||
pub mode: String,
|
pub mode: String,
|
||||||
|
pub rounded_corners: bool,
|
||||||
pub corner_radius: f32,
|
pub corner_radius: f32,
|
||||||
pub drop_shadow: bool,
|
pub drop_shadow: bool,
|
||||||
pub rounded_corners: bool,
|
|
||||||
pub shadow_blur_radius: f32,
|
pub shadow_blur_radius: f32,
|
||||||
pub shadow_blur_passes: u32,
|
|
||||||
pub shadow_color: [u8; 4],
|
|
||||||
pub shadow_offset_x: f32,
|
pub shadow_offset_x: f32,
|
||||||
pub shadow_offset_y: f32,
|
pub shadow_offset_y: f32,
|
||||||
|
pub shadow_color: [u8; 4],
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Config {
|
impl Config {
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ pub fn apply_effects(img: RgbaImage, cfg: &EffectsConfig) -> RgbaImage {
|
|||||||
cfg.shadow_blur_radius,
|
cfg.shadow_blur_radius,
|
||||||
cfg.shadow_offset_x,
|
cfg.shadow_offset_x,
|
||||||
cfg.shadow_offset_y,
|
cfg.shadow_offset_y,
|
||||||
cfg.shadow_blur_passes,
|
|
||||||
cfg.shadow_color,
|
cfg.shadow_color,
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
@@ -53,16 +52,11 @@ pub fn apply_drop_shadow(
|
|||||||
blur_radius: f32,
|
blur_radius: f32,
|
||||||
offset_x: f32,
|
offset_x: f32,
|
||||||
offset_y: f32,
|
offset_y: f32,
|
||||||
blur_passes: u32,
|
|
||||||
shadow_color: [u8; 4],
|
shadow_color: [u8; 4],
|
||||||
) -> RgbaImage {
|
) -> RgbaImage {
|
||||||
let (iw, ih) = img.dimensions();
|
let (iw, ih) = img.dimensions();
|
||||||
let br = blur_radius.ceil() as u32;
|
let br = blur_radius.ceil() as u32;
|
||||||
let bp = blur_passes;
|
let spread = br * 2;
|
||||||
// Original idea
|
|
||||||
// let spread = br * bp;
|
|
||||||
// Claude is hallucinating but let's try it **Worked btw**
|
|
||||||
let spread = (br as f32 * (bp as f32).sqrt() * 2.0).ceil() as u32;
|
|
||||||
|
|
||||||
let extra_left = spread + (-offset_x).max(0.0).ceil() as u32;
|
let extra_left = spread + (-offset_x).max(0.0).ceil() as u32;
|
||||||
let extra_top = spread + (-offset_y).max(0.0).ceil() as u32;
|
let extra_top = spread + (-offset_y).max(0.0).ceil() as u32;
|
||||||
@@ -92,11 +86,8 @@ pub fn apply_drop_shadow(
|
|||||||
|
|
||||||
tint_pixmap_as_shadow(&mut shadow_pixmap, shadow_color);
|
tint_pixmap_as_shadow(&mut shadow_pixmap, shadow_color);
|
||||||
|
|
||||||
// Shadow
|
|
||||||
let shadow_img = pixmap_to_rgba_image(shadow_pixmap);
|
let shadow_img = pixmap_to_rgba_image(shadow_pixmap);
|
||||||
// Shadow blur
|
let blurred = box_blur_rgba(&shadow_img, br);
|
||||||
let blurred = box_blur_rgba(&shadow_img, br, bp);
|
|
||||||
// Shadow pos
|
|
||||||
let blurred_pixmap = rgba_image_to_pixmap(&blurred);
|
let blurred_pixmap = rgba_image_to_pixmap(&blurred);
|
||||||
|
|
||||||
let mut canvas = Pixmap::new(canvas_w, canvas_h).expect("canvas pixmap");
|
let mut canvas = Pixmap::new(canvas_w, canvas_h).expect("canvas pixmap");
|
||||||
@@ -145,7 +136,6 @@ fn rounded_rect_path(x: f32, y: f32, w: f32, h: f32, r: f32) -> Path {
|
|||||||
pb.finish().expect("rounded rect path")
|
pb.finish().expect("rounded rect path")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Shadow pos
|
|
||||||
fn rgba_image_to_pixmap(img: &RgbaImage) -> Pixmap {
|
fn rgba_image_to_pixmap(img: &RgbaImage) -> Pixmap {
|
||||||
let (w, h) = img.dimensions();
|
let (w, h) = img.dimensions();
|
||||||
let mut pixmap = Pixmap::new(w, h).expect("pixmap alloc");
|
let mut pixmap = Pixmap::new(w, h).expect("pixmap alloc");
|
||||||
@@ -164,7 +154,6 @@ fn rgba_image_to_pixmap(img: &RgbaImage) -> Pixmap {
|
|||||||
pixmap
|
pixmap
|
||||||
}
|
}
|
||||||
|
|
||||||
// Shadow
|
|
||||||
fn pixmap_to_rgba_image(pixmap: Pixmap) -> RgbaImage {
|
fn pixmap_to_rgba_image(pixmap: Pixmap) -> RgbaImage {
|
||||||
let (w, h) = (pixmap.width(), pixmap.height());
|
let (w, h) = (pixmap.width(), pixmap.height());
|
||||||
let mut out = RgbaImage::new(w, h);
|
let mut out = RgbaImage::new(w, h);
|
||||||
@@ -187,16 +176,31 @@ fn pixmap_to_rgba_image(pixmap: Pixmap) -> RgbaImage {
|
|||||||
out
|
out
|
||||||
}
|
}
|
||||||
|
|
||||||
// Shadow blur
|
fn tint_pixmap_as_shadow(pixmap: &mut Pixmap, color: [u8; 4]) {
|
||||||
fn box_blur_rgba(img: &RgbaImage, radius: u32, bp: u32) -> RgbaImage {
|
let [sr, sg, sb, _] = color;
|
||||||
|
for px in pixmap.pixels_mut() {
|
||||||
|
let a = px.alpha();
|
||||||
|
if a > 0 {
|
||||||
|
let af = a as f32 / 255.0;
|
||||||
|
*px = tiny_skia::PremultipliedColorU8::from_rgba(
|
||||||
|
(sr as f32 * af) as u8,
|
||||||
|
(sg as f32 * af) as u8,
|
||||||
|
(sb as f32 * af) as u8,
|
||||||
|
a,
|
||||||
|
)
|
||||||
|
.unwrap_or(tiny_skia::PremultipliedColorU8::TRANSPARENT);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn box_blur_rgba(img: &RgbaImage, radius: u32) -> RgbaImage {
|
||||||
if radius == 0 {
|
if radius == 0 {
|
||||||
return img.clone();
|
return img.clone();
|
||||||
}
|
}
|
||||||
let mut buf = img.clone();
|
let mut buf = sliding_horizontal(img, radius);
|
||||||
for _ in 0..bp {
|
buf = sliding_vertical(&buf, radius);
|
||||||
buf = sliding_horizontal(&buf, radius);
|
buf = sliding_horizontal(&buf, radius);
|
||||||
buf = sliding_vertical(&buf, radius);
|
buf = sliding_vertical(&buf, radius);
|
||||||
}
|
|
||||||
buf
|
buf
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -246,23 +250,6 @@ fn sliding_horizontal(img: &RgbaImage, radius: u32) -> RgbaImage {
|
|||||||
out
|
out
|
||||||
}
|
}
|
||||||
|
|
||||||
fn tint_pixmap_as_shadow(pixmap: &mut Pixmap, color: [u8; 4]) {
|
|
||||||
let [sr, sg, sb, _] = color;
|
|
||||||
for px in pixmap.pixels_mut() {
|
|
||||||
let a = px.alpha();
|
|
||||||
if a > 0 {
|
|
||||||
let af = a as f32 / 255.0;
|
|
||||||
*px = tiny_skia::PremultipliedColorU8::from_rgba(
|
|
||||||
(sr as f32 * af) as u8,
|
|
||||||
(sg as f32 * af) as u8,
|
|
||||||
(sb as f32 * af) as u8,
|
|
||||||
a,
|
|
||||||
)
|
|
||||||
.unwrap_or(tiny_skia::PremultipliedColorU8::TRANSPARENT);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn sliding_vertical(img: &RgbaImage, radius: u32) -> RgbaImage {
|
fn sliding_vertical(img: &RgbaImage, radius: u32) -> RgbaImage {
|
||||||
let (w, h) = img.dimensions();
|
let (w, h) = img.dimensions();
|
||||||
let r = radius as i32;
|
let r = radius as i32;
|
||||||
|
|||||||
@@ -1,20 +1,21 @@
|
|||||||
mod config;
|
mod config;
|
||||||
mod effects;
|
mod effects;
|
||||||
|
|
||||||
use anyhow::{bail, Context, Result};
|
use anyhow::{Context, Result, bail};
|
||||||
use std::io::Write as _;
|
use std::io::Write as _;
|
||||||
use std::process::{Command, Stdio};
|
use std::process::{Command, Stdio};
|
||||||
|
|
||||||
|
/// CLI overrides that map 1:1 to `EffectsConfig` fields.
|
||||||
|
/// All fields are `Option<T>` so we can tell "not supplied" from any concrete value.
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
struct CliOverrides {
|
struct CliOverrides {
|
||||||
rounded_corners: Option<bool>,
|
rounded_corners: Option<bool>,
|
||||||
corner_radius: Option<f32>,
|
corner_radius: Option<f32>,
|
||||||
drop_shadow: Option<bool>,
|
drop_shadow: Option<bool>,
|
||||||
shadow_blur_radius: Option<f32>,
|
shadow_blur_radius: Option<f32>,
|
||||||
shadow_blur_passes: Option<u32>,
|
|
||||||
shadow_offset_x: Option<f32>,
|
shadow_offset_x: Option<f32>,
|
||||||
shadow_offset_y: Option<f32>,
|
shadow_offset_y: Option<f32>,
|
||||||
// Accepted as four comma-separated u8 values, e.g. `255,0,0,200`
|
/// Accepted as four comma-separated u8 values, e.g. `255,0,0,200`
|
||||||
shadow_color: Option<[u8; 4]>,
|
shadow_color: Option<[u8; 4]>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -29,24 +30,24 @@ fn parse_bool(s: &str) -> Result<bool> {
|
|||||||
fn parse_shadow_color(s: &str) -> Result<[u8; 4]> {
|
fn parse_shadow_color(s: &str) -> Result<[u8; 4]> {
|
||||||
let parts: Vec<&str> = s.split(',').collect();
|
let parts: Vec<&str> = s.split(',').collect();
|
||||||
if parts.len() != 4 {
|
if parts.len() != 4 {
|
||||||
bail!("--shadow-color expects four comma-separated u8 values, e.g. 255,0,0,200");
|
bail!("--shadow_color expects four comma-separated u8 values, e.g. 255,0,0,200");
|
||||||
}
|
}
|
||||||
let r = parts[0]
|
let r = parts[0]
|
||||||
.trim()
|
.trim()
|
||||||
.parse::<u8>()
|
.parse::<u8>()
|
||||||
.context("shadow-color red channel")?;
|
.context("shadow_color red channel")?;
|
||||||
let g = parts[1]
|
let g = parts[1]
|
||||||
.trim()
|
.trim()
|
||||||
.parse::<u8>()
|
.parse::<u8>()
|
||||||
.context("shadow-color green channel")?;
|
.context("shadow_color green channel")?;
|
||||||
let b = parts[2]
|
let b = parts[2]
|
||||||
.trim()
|
.trim()
|
||||||
.parse::<u8>()
|
.parse::<u8>()
|
||||||
.context("shadow-color blue channel")?;
|
.context("shadow_color blue channel")?;
|
||||||
let a = parts[3]
|
let a = parts[3]
|
||||||
.trim()
|
.trim()
|
||||||
.parse::<u8>()
|
.parse::<u8>()
|
||||||
.context("shadow-color alpha channel")?;
|
.context("shadow_color alpha channel")?;
|
||||||
Ok([r, g, b, a])
|
Ok([r, g, b, a])
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -55,7 +56,6 @@ fn main() -> Result<()> {
|
|||||||
|
|
||||||
let mut image_path: Option<String> = None;
|
let mut image_path: Option<String> = None;
|
||||||
let mut overrides = CliOverrides::default();
|
let mut overrides = CliOverrides::default();
|
||||||
let mut scale: Option<f32> = None;
|
|
||||||
|
|
||||||
let mut i = 0;
|
let mut i = 0;
|
||||||
while i < args.len() {
|
while i < args.len() {
|
||||||
@@ -68,82 +68,67 @@ fn main() -> Result<()> {
|
|||||||
.context("Expected a path after --image")?,
|
.context("Expected a path after --image")?,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
"--rounded-corners" => {
|
"--rounded_corners" => {
|
||||||
i += 1;
|
i += 1;
|
||||||
let val = args
|
let val = args
|
||||||
.get(i)
|
.get(i)
|
||||||
.context("Expected true/false after --rounded-corners")?;
|
.context("Expected true/false after --rounded_corners")?;
|
||||||
overrides.rounded_corners = Some(parse_bool(val)?);
|
overrides.rounded_corners = Some(parse_bool(val)?);
|
||||||
}
|
}
|
||||||
"--corner-radius" => {
|
"--corner_radius" => {
|
||||||
i += 1;
|
i += 1;
|
||||||
let val = args
|
let val = args
|
||||||
.get(i)
|
.get(i)
|
||||||
.context("Expected a number after --corner-radius")?;
|
.context("Expected a number after --corner_radius")?;
|
||||||
overrides.corner_radius = Some(
|
overrides.corner_radius = Some(
|
||||||
val.parse::<f32>()
|
val.parse::<f32>()
|
||||||
.context("--corner-radius must be a number")?,
|
.context("--corner_radius must be a number")?,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
"--drop-shadow" => {
|
"--drop_shadow" => {
|
||||||
i += 1;
|
i += 1;
|
||||||
let val = args
|
let val = args
|
||||||
.get(i)
|
.get(i)
|
||||||
.context("Expected true/false after --drop-shadow")?;
|
.context("Expected true/false after --drop_shadow")?;
|
||||||
overrides.drop_shadow = Some(parse_bool(val)?);
|
overrides.drop_shadow = Some(parse_bool(val)?);
|
||||||
}
|
}
|
||||||
"--shadow-blur-radius" => {
|
"--shadow_blur_radius" => {
|
||||||
i += 1;
|
i += 1;
|
||||||
let val = args
|
let val = args
|
||||||
.get(i)
|
.get(i)
|
||||||
.context("Expected a number after --shadow-blur-radius")?;
|
.context("Expected a number after --shadow_blur_radius")?;
|
||||||
overrides.shadow_blur_radius = Some(
|
overrides.shadow_blur_radius = Some(
|
||||||
val.parse::<f32>()
|
val.parse::<f32>()
|
||||||
.context("--shadow-blur-radius must be a number")?,
|
.context("--shadow_blur_radius must be a number")?,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
"--shadow-offset-x" => {
|
"--shadow_offset_x" => {
|
||||||
i += 1;
|
i += 1;
|
||||||
let val = args
|
let val = args
|
||||||
.get(i)
|
.get(i)
|
||||||
.context("Expected a number after --shadow-offset-x")?;
|
.context("Expected a number after --shadow_offset_x")?;
|
||||||
overrides.shadow_offset_x = Some(
|
overrides.shadow_offset_x = Some(
|
||||||
val.parse::<f32>()
|
val.parse::<f32>()
|
||||||
.context("--shadow-offset-x must be a number")?,
|
.context("--shadow_offset_x must be a number")?,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
"--shadow-offset-y" => {
|
"--shadow_offset_y" => {
|
||||||
i += 1;
|
i += 1;
|
||||||
let val = args
|
let val = args
|
||||||
.get(i)
|
.get(i)
|
||||||
.context("Expected a number after --shadow-offset-y")?;
|
.context("Expected a number after --shadow_offset_y")?;
|
||||||
overrides.shadow_offset_y = Some(
|
overrides.shadow_offset_y = Some(
|
||||||
val.parse::<f32>()
|
val.parse::<f32>()
|
||||||
.context("--shadow-offset-y must be a number")?,
|
.context("--shadow_offset_y must be a number")?,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
"--shadow-blur-passes" => {
|
"--shadow_color" => {
|
||||||
i += 1;
|
i += 1;
|
||||||
let val = args
|
let val = args
|
||||||
.get(i)
|
.get(i)
|
||||||
.context("Expected a number after --shadow-blur-passes")?;
|
.context("Expected r,g,b,a after --shadow_color")?;
|
||||||
overrides.shadow_blur_passes = Some(
|
|
||||||
val.parse::<u32>()
|
|
||||||
.context("--shadow-blur-passes must be a number")?,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
"--shadow-color" => {
|
|
||||||
i += 1;
|
|
||||||
let val = args
|
|
||||||
.get(i)
|
|
||||||
.context("Expected r,g,b,a after --shadow-color")?;
|
|
||||||
overrides.shadow_color = Some(parse_shadow_color(val)?);
|
overrides.shadow_color = Some(parse_shadow_color(val)?);
|
||||||
}
|
}
|
||||||
"--scale" => {
|
|
||||||
i += 1;
|
|
||||||
let val = args.get(i).context("Expected a number after --scale")?;
|
|
||||||
scale = Some(val.parse::<f32>().context("--scale must be a number")?);
|
|
||||||
}
|
|
||||||
unknown => bail!("Unknown argument: {unknown}"),
|
unknown => bail!("Unknown argument: {unknown}"),
|
||||||
}
|
}
|
||||||
i += 1;
|
i += 1;
|
||||||
@@ -173,22 +158,11 @@ fn main() -> Result<()> {
|
|||||||
if let Some(v) = overrides.shadow_offset_y {
|
if let Some(v) = overrides.shadow_offset_y {
|
||||||
effects.shadow_offset_y = v;
|
effects.shadow_offset_y = v;
|
||||||
}
|
}
|
||||||
if let Some(v) = overrides.shadow_blur_passes {
|
|
||||||
effects.shadow_blur_passes = v;
|
|
||||||
}
|
|
||||||
if let Some(v) = overrides.shadow_color {
|
if let Some(v) = overrides.shadow_color {
|
||||||
effects.shadow_color = v;
|
effects.shadow_color = v;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// if scale is set do
|
|
||||||
if let Some(scale) = scale.filter(|&s| s != 1.0) {
|
|
||||||
effects.corner_radius *= scale;
|
|
||||||
effects.shadow_blur_radius *= scale;
|
|
||||||
effects.shadow_offset_x *= scale;
|
|
||||||
effects.shadow_offset_y *= scale;
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Err(e) = process_image(&image_path, &effects) {
|
if let Err(e) = process_image(&image_path, &effects) {
|
||||||
eprintln!("Error processing '{}': {e:#}", image_path);
|
eprintln!("Error processing '{}': {e:#}", image_path);
|
||||||
}
|
}
|
||||||
@@ -217,11 +191,20 @@ fn process_image(path: &str, effects: &config::EffectsConfig) -> Result<()> {
|
|||||||
.spawn()
|
.spawn()
|
||||||
.context("Failed to spawn swappy. Is it installed and in PATH?")?;
|
.context("Failed to spawn swappy. Is it installed and in PATH?")?;
|
||||||
|
|
||||||
// Writes the PNG bytes to swappy's stdin and then closes
|
child
|
||||||
if let Some(mut stdin) = child.stdin.take() {
|
.stdin
|
||||||
stdin
|
.take()
|
||||||
.write_all(&png_bytes)
|
.context("Failed to get swappy stdin")?
|
||||||
.context("Failed to write image data to swappy")?;
|
.write_all(&png_bytes)
|
||||||
|
.context("Failed to write image data to swappy")?;
|
||||||
|
|
||||||
|
let status = child.wait().context("Failed to wait for swappy")?;
|
||||||
|
|
||||||
|
if !status.success() {
|
||||||
|
eprintln!(
|
||||||
|
"swappy exited with non-zero status for '{}': {}",
|
||||||
|
path, status
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
Reference in New Issue
Block a user