This commit is contained in:
Zacharias-Brohn
2026-03-09 12:47:09 +01:00
parent 720bc2808e
commit 1ee345f946
11 changed files with 762 additions and 115 deletions
+170
View File
@@ -0,0 +1,170 @@
pragma ComponentBehavior: Bound
import QtQuick
Item {
id: root
readonly property real arcStartAngle: 0.75 * Math.PI
readonly property real arcSweep: 1.5 * Math.PI
property real currentHue: 0
required property var drawing
property real handleSize: 30
property real lastChromaticHue: 0
property real ringThickness: 12
readonly property int segmentCount: 180
function hueToAngle(hue) {
return arcStartAngle + arcSweep * hue;
}
function normalizeAngle(angle) {
const tau = Math.PI * 2;
let a = angle % tau;
if (a < 0)
a += tau;
return a;
}
function syncFromPenColor() {
if (!drawing)
return;
const c = drawing.penColor;
// QML color exposes HSL channels directly.
// If the current color is chromatic, move the handle to that hue.
// If it is achromatic (black/white/gray), keep the last useful hue.
if (c.hslSaturation > 0) {
currentHue = c.hslHue;
lastChromaticHue = c.hslHue;
} else {
currentHue = lastChromaticHue;
}
canvas.requestPaint();
}
function updateHueFromPoint(x, y) {
const cx = canvas.width / 2;
const cy = canvas.height / 2;
const dx = x - cx;
const dy = y - cy;
const distance = Math.sqrt(dx * dx + dy * dy);
const radius = (Math.min(canvas.width, canvas.height) - handleSize - 8) / 2;
if (distance < radius - 24 || distance > radius + 24)
return;
const angle = normalizeAngle(Math.atan2(dy, dx));
const start = normalizeAngle(arcStartAngle);
let relative = angle - start;
if (relative < 0)
relative += Math.PI * 2;
if (relative > arcSweep) {
const gap = Math.PI * 2 - arcSweep;
relative = relative < arcSweep + gap / 2 ? arcSweep : 0;
}
currentHue = relative / arcSweep;
lastChromaticHue = currentHue;
drawing.penColor = Qt.hsla(currentHue, 1.0, 0.5, 1.0);
}
implicitHeight: 180
implicitWidth: 220
Component.onCompleted: syncFromPenColor()
onCurrentHueChanged: canvas.requestPaint()
onDrawingChanged: syncFromPenColor()
Connections {
function onPenColorChanged() {
root.syncFromPenColor();
}
target: root.drawing
}
Canvas {
id: canvas
anchors.fill: parent
renderStrategy: Canvas.Threaded
renderTarget: Canvas.Image
Component.onCompleted: requestPaint()
onHeightChanged: requestPaint()
onPaint: {
const ctx = getContext("2d");
ctx.reset();
ctx.clearRect(0, 0, width, height);
const cx = width / 2;
const cy = height / 2;
const radius = (Math.min(width, height) - root.handleSize - 8) / 2;
ctx.beginPath();
ctx.arc(cx, cy, radius, root.arcStartAngle, root.arcStartAngle + root.arcSweep);
ctx.lineWidth = root.ringThickness + 4;
ctx.lineCap = "round";
ctx.strokeStyle = Qt.rgba(1, 1, 1, 0.12);
ctx.stroke();
for (let i = 0; i < root.segmentCount; ++i) {
const t1 = i / root.segmentCount;
const t2 = (i + 1) / root.segmentCount;
const a1 = root.arcStartAngle + root.arcSweep * t1;
const a2 = root.arcStartAngle + root.arcSweep * t2;
ctx.beginPath();
ctx.arc(cx, cy, radius, a1, a2);
ctx.lineWidth = root.ringThickness;
ctx.lineCap = "round";
ctx.strokeStyle = Qt.hsla(t1, 1.0, 0.5, 1.0);
ctx.stroke();
}
const handleAngle = root.hueToAngle(root.currentHue);
const hx = cx + radius * Math.cos(handleAngle);
const hy = cy + radius * Math.sin(handleAngle);
ctx.beginPath();
ctx.arc(hx, hy, root.handleSize / 2, 0, Math.PI * 2);
ctx.fillStyle = Qt.rgba(1, 1, 1, 0.95);
ctx.fill();
ctx.beginPath();
ctx.arc(hx, hy, root.handleSize / 2, 0, Math.PI * 2);
ctx.lineWidth = 1.5;
ctx.strokeStyle = Qt.rgba(0, 0, 0, 0.18);
ctx.stroke();
ctx.beginPath();
ctx.arc(hx, hy, root.handleSize / 2 - 6, 0, Math.PI * 2);
ctx.fillStyle = root.drawing.penColor;
ctx.fill();
ctx.beginPath();
ctx.arc(hx, hy, root.handleSize / 2 - 6, 0, Math.PI * 2);
ctx.lineWidth = 1;
ctx.strokeStyle = Qt.rgba(0, 0, 0, 0.20);
ctx.stroke();
}
onWidthChanged: requestPaint()
}
MouseArea {
acceptedButtons: Qt.LeftButton
anchors.fill: parent
onPositionChanged: mouse => {
if (mouse.buttons & Qt.LeftButton)
root.updateHueFromPoint(mouse.x, mouse.y);
}
onPressed: mouse => root.updateHueFromPoint(mouse.x, mouse.y)
}
}
+7 -1
View File
@@ -11,7 +11,7 @@ import qs.Modules.Dashboard as Dashboard
import qs.Modules.Osd as Osd import qs.Modules.Osd as Osd
import qs.Modules.Launcher as Launcher import qs.Modules.Launcher as Launcher
import qs.Modules.Resources as Resources import qs.Modules.Resources as Resources
import qs.Modules.Drawing as Drawing
import qs.Modules.Settings as Settings import qs.Modules.Settings as Settings
Shape { Shape {
@@ -31,6 +31,12 @@ Shape {
} }
} }
Drawing.Background {
startX: 0
startY: wrapper.y - rounding
wrapper: root.panels.drawing
}
Resources.Background { Resources.Background {
startX: 0 - rounding startX: 0 - rounding
startY: 0 startY: 0
+35 -11
View File
@@ -32,19 +32,9 @@ Variants {
WlrLayershell.namespace: "ZShell-Bar" WlrLayershell.namespace: "ZShell-Bar"
color: "transparent" color: "transparent"
contentItem.focus: true contentItem.focus: true
mask: visibilities.isDrawing ? null : region
screen: scope.modelData screen: scope.modelData
mask: Region {
id: region
height: bar.screen.height - backgroundRect.implicitHeight
intersection: Intersection.Xor
regions: popoutRegions.instances
width: bar.width
x: 0
y: Config.barConfig.autoHide && !visibilities.bar ? 4 : backgroundRect.height
}
contentItem.Keys.onEscapePressed: { contentItem.Keys.onEscapePressed: {
if (Config.barConfig.autoHide) if (Config.barConfig.autoHide)
visibilities.bar = false; visibilities.bar = false;
@@ -55,6 +45,17 @@ Variants {
visibilities.resources = false; visibilities.resources = false;
} }
Region {
id: region
height: bar.screen.height - backgroundRect.implicitHeight
intersection: Intersection.Xor
regions: popoutRegions.instances
width: bar.width
x: 0
y: Config.barConfig.autoHide && !visibilities.bar ? 4 : backgroundRect.height
}
PanelWindow { PanelWindow {
id: exclusionZone id: exclusionZone
@@ -117,6 +118,7 @@ Variants {
property bool bar property bool bar
property bool dashboard property bool dashboard
property bool isDrawing
property bool launcher property bool launcher
property bool notif: NotifServer.popups.length > 0 property bool notif: NotifServer.popups.length > 0
property bool osd property bool osd
@@ -157,20 +159,42 @@ Variants {
} }
} }
Drawing {
id: drawing
anchors.fill: parent
z: 2
}
DrawingInput {
id: input
bar: backgroundRect
drawing: drawing
panels: panels
popout: panels.drawing
visibilities: visibilities
z: 2
}
Interactions { Interactions {
id: mouseArea id: mouseArea
anchors.fill: parent anchors.fill: parent
bar: barLoader bar: barLoader
drawing: drawing
input: input
panels: panels panels: panels
popouts: panels.popouts popouts: panels.popouts
screen: scope.modelData screen: scope.modelData
visibilities: visibilities visibilities: visibilities
z: 1
Panels { Panels {
id: panels id: panels
bar: backgroundRect bar: backgroundRect
drawingItem: drawing
screen: scope.modelData screen: scope.modelData
visibilities: visibilities visibilities: visibilities
} }
+186
View File
@@ -0,0 +1,186 @@
import QtQuick
Canvas {
id: root
property rect dirtyRect: Qt.rect(0, 0, 0, 0)
property bool frameQueued: false
property bool fullRepaintPending: true
property point lastPoint: Qt.point(0, 0)
property real minPointDistance: 2.0
property color penColor: "white"
property real penWidth: 4
property var pendingSegments: []
property bool strokeActive: false
property var strokes: []
function appendPoint(x, y) {
if (!strokeActive || strokes.length === 0)
return;
const dx = x - lastPoint.x;
const dy = y - lastPoint.y;
if ((dx * dx + dy * dy) < (minPointDistance * minPointDistance))
return;
const x1 = lastPoint.x;
const y1 = lastPoint.y;
const x2 = x;
const y2 = y;
strokes[strokes.length - 1].push(Qt.point(x2, y2));
pendingSegments.push({
dot: false,
x1: x1,
y1: y1,
x2: x2,
y2: y2
});
lastPoint = Qt.point(x2, y2);
queueDirty(segmentDirtyRect(x1, y1, x2, y2));
}
function beginStroke(x, y) {
const p = Qt.point(x, y);
strokes.push([p]);
lastPoint = p;
strokeActive = true;
pendingSegments.push({
dot: true,
x: x,
y: y
});
queueDirty(pointDirtyRect(x, y));
}
function clear() {
strokes = [];
pendingSegments = [];
dirtyRect = Qt.rect(0, 0, 0, 0);
fullRepaintPending = true;
markDirty(Qt.rect(0, 0, width, height));
}
function drawDot(ctx, x, y) {
ctx.beginPath();
ctx.arc(x, y, penWidth / 2, 0, Math.PI * 2);
ctx.fill();
}
function drawSegment(ctx, x1, y1, x2, y2) {
ctx.beginPath();
ctx.moveTo(x1, y1);
ctx.lineTo(x2, y2);
ctx.stroke();
}
function endStroke() {
strokeActive = false;
}
function pointDirtyRect(x, y) {
const pad = penWidth + 2;
return Qt.rect(x - pad, y - pad, pad * 2, pad * 2);
}
function queueDirty(r) {
dirtyRect = unionRects(dirtyRect, r);
if (frameQueued)
return;
frameQueued = true;
requestAnimationFrame(function () {
frameQueued = false;
if (dirtyRect.width > 0 && dirtyRect.height > 0) {
markDirty(dirtyRect);
dirtyRect = Qt.rect(0, 0, 0, 0);
}
});
}
function replayAll(ctx) {
ctx.clearRect(0, 0, width, height);
for (const stroke of strokes) {
if (!stroke || stroke.length === 0)
continue;
if (stroke.length === 1) {
const p = stroke[0];
drawDot(ctx, p.x, p.y);
continue;
}
ctx.beginPath();
ctx.moveTo(stroke[0].x, stroke[0].y);
for (let i = 1; i < stroke.length; ++i)
ctx.lineTo(stroke[i].x, stroke[i].y);
ctx.stroke();
}
}
function requestFullRepaint() {
fullRepaintPending = true;
markDirty(Qt.rect(0, 0, width, height));
}
function segmentDirtyRect(x1, y1, x2, y2) {
const pad = penWidth + 2;
const left = Math.min(x1, x2) - pad;
const top = Math.min(y1, y2) - pad;
const right = Math.max(x1, x2) + pad;
const bottom = Math.max(y1, y2) + pad;
return Qt.rect(left, top, right - left, bottom - top);
}
function unionRects(a, b) {
if (a.width <= 0 || a.height <= 0)
return b;
if (b.width <= 0 || b.height <= 0)
return a;
const left = Math.min(a.x, b.x);
const top = Math.min(a.y, b.y);
const right = Math.max(a.x + a.width, b.x + b.width);
const bottom = Math.max(a.y + a.height, b.y + b.height);
return Qt.rect(left, top, right - left, bottom - top);
}
anchors.fill: parent
contextType: "2d"
renderStrategy: Canvas.Threaded
renderTarget: Canvas.Image
onHeightChanged: requestFullRepaint()
onPaint: region => {
const ctx = getContext("2d");
ctx.lineCap = "round";
ctx.lineJoin = "round";
ctx.lineWidth = penWidth;
ctx.strokeStyle = penColor;
ctx.fillStyle = penColor;
if (fullRepaintPending) {
fullRepaintPending = false;
replayAll(ctx);
pendingSegments = [];
return;
}
for (const seg of pendingSegments) {
if (seg.dot)
drawDot(ctx, seg.x, seg.y);
else
drawSegment(ctx, seg.x1, seg.y1, seg.x2, seg.y2);
}
pendingSegments = [];
}
onWidthChanged: requestFullRepaint()
}
+53
View File
@@ -0,0 +1,53 @@
import Quickshell
import QtQuick
import qs.Components
CustomMouseArea {
id: root
required property var bar
required property Drawing drawing
required property Panels panels
required property var popout
required property PersistentProperties visibilities
function inLeftPanel(panel: Item, x: real, y: real): bool {
return x < panel.x + panel.width && withinPanelHeight(panel, x, y);
}
function withinPanelHeight(panel: Item, x: real, y: real): bool {
const panelY = panel.y + bar.implicitHeight;
return y >= panelY && y <= panelY + panel.height;
}
anchors.fill: root.visibilities.isDrawing ? parent : undefined
hoverEnabled: true
visible: root.visibilities.isDrawing
onPositionChanged: event => {
const x = event.x;
const y = event.y;
if (event.buttons & Qt.LeftButton)
root.drawing.appendPoint(x, y);
if (root.inLeftPanel(root.popout, x, y)) {
root.z = -2;
root.panels.drawing.expanded = true;
}
}
onPressed: event => {
const x = event.x;
const y = event.y;
if (root.visibilities.isDrawing && (event.buttons & Qt.LeftButton)) {
root.panels.drawing.expanded = false;
root.drawing.beginStroke(x, y);
return;
}
}
onReleased: {
if (root.visibilities.isDrawing)
root.drawing.endStroke();
}
}
+14 -103
View File
@@ -10,6 +10,8 @@ CustomMouseArea {
required property Item bar required property Item bar
property bool dashboardShortcutActive property bool dashboardShortcutActive
property point dragStart property point dragStart
required property Drawing drawing
required property DrawingInput input
property bool osdShortcutActive property bool osdShortcutActive
required property Panels panels required property Panels panels
required property BarPopouts.Wrapper popouts required property BarPopouts.Wrapper popouts
@@ -51,20 +53,10 @@ CustomMouseArea {
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
propagateComposedEvents: true
// onPressed: event => {
// if ( root.popouts.hasCurrent && !inTopPanel( root.popouts, event.x, event.y )) {
// root.popouts.hasCurrent = false;
// } else if (root.visibilities.sidebar && !inRightPanel( panels.sidebar, event.x, event.y )) {
// root.visibilities.sidebar = false;
// } else if (root.visibilities.dashboard && !inTopPanel( panels.dashboard, event.x, event.y )) {
// root.visibilities.dashboard = false;
// }
// }
onContainsMouseChanged: { onContainsMouseChanged: {
if (!containsMouse) { if (!containsMouse) {
// Only hide if not activated by shortcut
if (!osdShortcutActive) { if (!osdShortcutActive) {
visibilities.osd = false; visibilities.osd = false;
root.panels.osd.hovered = false; root.panels.osd.hovered = false;
@@ -87,120 +79,42 @@ CustomMouseArea {
const dragX = x - dragStart.x; const dragX = x - dragStart.x;
const dragY = y - dragStart.y; const dragY = y - dragStart.y;
// Show bar in non-exclusive mode on hover if (root.visibilities.isDrawing && !root.inLeftPanel(root.panels.drawing, x, y)) {
root.input.z = 2;
root.panels.drawing.expanded = false;
}
if (!visibilities.bar && Config.barConfig.autoHide && y < bar.implicitHeight + bar.anchors.topMargin) if (!visibilities.bar && Config.barConfig.autoHide && y < bar.implicitHeight + bar.anchors.topMargin)
visibilities.bar = true; visibilities.bar = true;
if (panels.sidebar.width === 0) { if (panels.sidebar.width === 0) {
// Show osd on hover
const showOsd = inRightPanel(panels.osd, x, y); const showOsd = inRightPanel(panels.osd, x, y);
// // Always update visibility based on hover if not in shortcut mode if (showOsd) {
if (!osdShortcutActive) {
visibilities.osd = showOsd;
root.panels.osd.hovered = showOsd;
} else if (showOsd) {
// If hovering over OSD area while in shortcut mode, transition to hover control
osdShortcutActive = false; osdShortcutActive = false;
root.panels.osd.hovered = true; root.panels.osd.hovered = true;
} }
// const showSidebar = pressed && dragStart.x > bar.implicitWidth + panels.sidebar.x;
//
// // Show/hide session on drag
// if (pressed && inRightPanel(panels.session, dragStart.x, dragStart.y) && withinPanelHeight(panels.session, x, y)) {
// if (dragX < -Config.session.dragThreshold)
// visibilities.session = true;
// else if (dragX > Config.session.dragThreshold)
// visibilities.session = false;
//
// // Show sidebar on drag if in session area and session is nearly fully visible
// if (showSidebar && panels.session.width >= panels.session.nonAnimWidth && dragX < -Config.sidebar.dragThreshold)
// visibilities.sidebar = true;
// } else if (showSidebar && dragX < -Config.sidebar.dragThreshold) {
// // Show sidebar on drag if not in session area
// visibilities.sidebar = true;
// }
} else { } else {
const outOfSidebar = x < width - panels.sidebar.width; const outOfSidebar = x < width - panels.sidebar.width;
// Show osd on hover
const showOsd = outOfSidebar && inRightPanel(panels.osd, x, y); const showOsd = outOfSidebar && inRightPanel(panels.osd, x, y);
// Always update visibility based on hover if not in shortcut mode
if (!osdShortcutActive) { if (!osdShortcutActive) {
visibilities.osd = showOsd; visibilities.osd = showOsd;
root.panels.osd.hovered = showOsd; root.panels.osd.hovered = showOsd;
} else if (showOsd) { } else if (showOsd) {
// If hovering over OSD area while in shortcut mode, transition to hover control
osdShortcutActive = false; osdShortcutActive = false;
root.panels.osd.hovered = true; root.panels.osd.hovered = true;
} }
//
// // Show/hide session on drag
// if (pressed && outOfSidebar && inRightPanel(panels.session, dragStart.x, dragStart.y) && withinPanelHeight(panels.session, x, y)) {
// if (dragX < -Config.session.dragThreshold)
// visibilities.session = true;
// else if (dragX > Config.session.dragThreshold)
// visibilities.session = false;
// }
//
// // Hide sidebar on drag
// if (pressed && inRightPanel(panels.sidebar, dragStart.x, 0) && dragX > Config.sidebar.dragThreshold)
// visibilities.sidebar = false;
} }
// Show launcher on hover, or show/hide on drag if hover is disabled
// if (Config.launcher.showOnHover) {
// if (!visibilities.launcher && inBottomPanel(panels.launcher, x, y))
// visibilities.launcher = true;
// } else if (pressed && inBottomPanel(panels.launcher, dragStart.x, dragStart.y) && withinPanelWidth(panels.launcher, x, y)) {
// if (dragY < -Config.launcher.dragThreshold)
// visibilities.launcher = true;
// else if (dragY > Config.launcher.dragThreshold)
// visibilities.launcher = false;
// }
//
// // Show dashboard on hover
// const showDashboard = Config.dashboard.showOnHover && inTopPanel(panels.dashboard, x, y);
//
// // Always update visibility based on hover if not in shortcut mode
// if (!dashboardShortcutActive) {
// visibilities.dashboard = showDashboard;
// } else if (showDashboard) {
// // If hovering over dashboard area while in shortcut mode, transition to hover control
// dashboardShortcutActive = false;
// }
//
// // Show/hide dashboard on drag (for touchscreen devices)
// if (pressed && inTopPanel(panels.dashboard, dragStart.x, dragStart.y) && withinPanelWidth(panels.dashboard, x, y)) {
// if (dragY > Config.dashboard.dragThreshold)
// visibilities.dashboard = true;
// else if (dragY < -Config.dashboard.dragThreshold)
// visibilities.dashboard = false;
// }
//
// // Show utilities on hover
// const showUtilities = inBottomPanel(panels.utilities, x, y);
//
// // Always update visibility based on hover if not in shortcut mode
// if (!utilitiesShortcutActive) {
// visibilities.utilities = showUtilities;
// } else if (showUtilities) {
// // If hovering over utilities area while in shortcut mode, transition to hover control
// utilitiesShortcutActive = false;
// }
// Show popouts on hover
if (y < bar.implicitHeight) { if (y < bar.implicitHeight) {
bar.checkPopout(x); bar.checkPopout(x);
} }
} }
// Monitor individual visibility changes
Connections { Connections {
function onDashboardChanged() { function onDashboardChanged() {
if (root.visibilities.dashboard) { if (root.visibilities.dashboard) {
// Dashboard became visible, immediately check if this should be shortcut mode
const inDashboardArea = root.inTopPanel(root.panels.dashboard, root.mouseX, root.mouseY); const inDashboardArea = root.inTopPanel(root.panels.dashboard, root.mouseX, root.mouseY);
if (!inDashboardArea) { if (!inDashboardArea) {
root.dashboardShortcutActive = true; root.dashboardShortcutActive = true;
@@ -209,20 +123,21 @@ CustomMouseArea {
root.visibilities.sidebar = false; root.visibilities.sidebar = false;
root.popouts.hasCurrent = false; root.popouts.hasCurrent = false;
} else { } else {
// Dashboard hidden, clear shortcut flag
root.dashboardShortcutActive = false; root.dashboardShortcutActive = false;
// root.visibilities.bar = false;
} }
} }
function onIsDrawingChanged() {
if (!root.visibilities.isDrawing)
root.drawing.clear();
}
function onLauncherChanged() { function onLauncherChanged() {
// If launcher is hidden, clear shortcut flags for dashboard and OSD
if (!root.visibilities.launcher) { if (!root.visibilities.launcher) {
root.dashboardShortcutActive = false; root.dashboardShortcutActive = false;
root.osdShortcutActive = false; root.osdShortcutActive = false;
root.utilitiesShortcutActive = false; root.utilitiesShortcutActive = false;
// Also hide dashboard and OSD if they're not being hovered
const inOsdArea = root.inRightPanel(root.panels.osd, root.mouseX, root.mouseY); const inOsdArea = root.inRightPanel(root.panels.osd, root.mouseX, root.mouseY);
if (!inOsdArea) { if (!inOsdArea) {
@@ -234,13 +149,11 @@ CustomMouseArea {
function onOsdChanged() { function onOsdChanged() {
if (root.visibilities.osd) { if (root.visibilities.osd) {
// OSD became visible, immediately check if this should be shortcut mode
const inOsdArea = root.inRightPanel(root.panels.osd, root.mouseX, root.mouseY); const inOsdArea = root.inRightPanel(root.panels.osd, root.mouseX, root.mouseY);
if (!inOsdArea) { if (!inOsdArea) {
root.osdShortcutActive = true; root.osdShortcutActive = true;
} }
} else { } else {
// OSD hidden, clear shortcut flag
root.osdShortcutActive = false; root.osdShortcutActive = false;
} }
} }
@@ -260,13 +173,11 @@ CustomMouseArea {
function onUtilitiesChanged() { function onUtilitiesChanged() {
if (root.visibilities.utilities) { if (root.visibilities.utilities) {
// Utilities became visible, immediately check if this should be shortcut mode
const inUtilitiesArea = root.inBottomPanel(root.panels.utilities, root.mouseX, root.mouseY); const inUtilitiesArea = root.inBottomPanel(root.panels.utilities, root.mouseX, root.mouseY);
if (!inUtilitiesArea) { if (!inUtilitiesArea) {
root.utilitiesShortcutActive = true; root.utilitiesShortcutActive = true;
} }
} else { } else {
// Utilities hidden, clear shortcut flag
root.utilitiesShortcutActive = false; root.utilitiesShortcutActive = false;
} }
} }
+13
View File
@@ -11,6 +11,7 @@ import qs.Components.Toast as Toasts
import qs.Modules.Launcher as Launcher import qs.Modules.Launcher as Launcher
import qs.Modules.Resources as Resources import qs.Modules.Resources as Resources
import qs.Modules.Settings as Settings import qs.Modules.Settings as Settings
import qs.Modules.Drawing as Drawing
import qs.Config import qs.Config
Item { Item {
@@ -18,6 +19,8 @@ Item {
required property Item bar required property Item bar
readonly property alias dashboard: dashboard readonly property alias dashboard: dashboard
readonly property alias drawing: drawing
required property Canvas drawingItem
readonly property alias launcher: launcher readonly property alias launcher: launcher
readonly property alias notifications: notifications readonly property alias notifications: notifications
readonly property alias osd: osd readonly property alias osd: osd
@@ -47,6 +50,16 @@ Item {
visibilities: root.visibilities visibilities: root.visibilities
} }
Drawing.Wrapper {
id: drawing
anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter
drawing: root.drawingItem
screen: root.screen
visibilities: root.visibilities
}
Osd.Wrapper { Osd.Wrapper {
id: osd id: osd
+66
View File
@@ -0,0 +1,66 @@
import QtQuick
import QtQuick.Shapes
import qs.Components
import qs.Config
ShapePath {
id: root
readonly property bool flatten: wrapper.width < rounding * 2
readonly property real rounding: Appearance.rounding.normal
readonly property real roundingX: flatten ? wrapper.width / 2 : rounding
required property Wrapper wrapper
fillColor: DynamicColors.palette.m3surface
strokeWidth: -1
Behavior on fillColor {
CAnim {
}
}
PathArc {
direction: PathArc.Counterclockwise
radiusX: Math.min(root.rounding, root.wrapper.width)
radiusY: root.rounding
relativeX: root.roundingX
relativeY: root.rounding
}
PathLine {
relativeX: root.wrapper.width - root.roundingX * 2
relativeY: 0
}
PathArc {
radiusX: Math.min(root.rounding, root.wrapper.width)
radiusY: root.rounding
relativeX: root.roundingX
relativeY: root.rounding
}
PathLine {
relativeX: 0
relativeY: root.wrapper.height - root.rounding * 2
}
PathArc {
radiusX: Math.min(root.rounding, root.wrapper.width)
radiusY: root.rounding
relativeX: -root.roundingX
relativeY: root.rounding
}
PathLine {
relativeX: -(root.wrapper.width - root.roundingX * 2)
relativeY: 0
}
PathArc {
direction: PathArc.Counterclockwise
radiusX: Math.min(root.rounding, root.wrapper.width)
radiusY: root.rounding
relativeX: -root.roundingX
relativeY: root.rounding
}
}
+76
View File
@@ -0,0 +1,76 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import qs.Config
import qs.Components
Item {
id: root
readonly property var colors: ["#ef4444", "#f97316", "#eab308", "#22c55e", "#06b6d4", "#3b82f6", "#a855f7", "#ec4899", "#ffffff", "#000000"]
required property Canvas drawing
required property var visibilities
implicitHeight: huePicker.implicitHeight + 12 + palette.implicitHeight + Appearance.padding.normal * 2
implicitWidth: huePicker.implicitWidth + Appearance.padding.normal * 2
Column {
anchors.centerIn: parent
spacing: 12
ColorArcPicker {
id: huePicker
drawing: root.drawing
}
GridLayout {
id: palette
anchors.left: parent.left
anchors.right: parent.right
columns: 5
rowSpacing: 8
rows: 2
Repeater {
model: root.colors
delegate: Item {
required property color modelData
readonly property bool selected: Qt.colorEqual(root.drawing.penColor, modelData)
Layout.fillWidth: true
height: 28
CustomRect {
anchors.centerIn: parent
border.color: selected ? "#ffffff" : Qt.rgba(1, 1, 1, 0.28)
border.width: selected ? 3 : 1
color: "transparent"
height: parent.height
radius: width / 2
width: parent.height
}
CustomRect {
anchors.centerIn: parent
border.color: Qt.rgba(0, 0, 0, 0.25)
border.width: Qt.colorEqual(modelData, "#ffffff") ? 1 : 0
color: modelData
height: 20
radius: width / 2
width: 20
}
MouseArea {
anchors.fill: parent
onClicked: root.drawing.penColor = parent.modelData
}
}
}
}
}
}
+133
View File
@@ -0,0 +1,133 @@
pragma ComponentBehavior: Bound
import Quickshell
import QtQuick
import qs.Components
import qs.Helpers
import qs.Config
import qs.Daemons
Item {
id: root
required property Canvas drawing
property bool expanded: true
required property ShellScreen screen
readonly property bool shouldBeActive: visibilities.isDrawing
required property var visibilities
function show(): void {
visibilities.isDrawing = true;
timer.restart();
}
implicitHeight: content.implicitHeight
implicitWidth: 0
visible: width > 0
states: [
State {
name: "hidden"
when: !root.shouldBeActive
PropertyChanges {
root.implicitWidth: 0
}
PropertyChanges {
icon.opacity: 0
}
PropertyChanges {
content.opacity: 0
}
},
State {
name: "collapsed"
when: root.shouldBeActive && !root.expanded
PropertyChanges {
root.implicitWidth: icon.implicitWidth
}
PropertyChanges {
icon.opacity: 1
}
PropertyChanges {
content.opacity: 0
}
},
State {
name: "visible"
when: root.shouldBeActive && root.expanded
PropertyChanges {
root.implicitWidth: content.implicitWidth
}
PropertyChanges {
icon.opacity: 0
}
PropertyChanges {
content.opacity: 1
}
}
]
transitions: [
Transition {
from: "*"
to: "*"
ParallelAnimation {
Anim {
easing.bezierCurve: MaterialEasing.expressiveEffects
property: "implicitWidth"
target: root
}
Anim {
duration: Appearance.anim.durations.small
property: "opacity"
target: icon
}
Anim {
duration: Appearance.anim.durations.small
property: "opacity"
target: content
}
}
}
]
Loader {
id: icon
active: Qt.binding(() => root.shouldBeActive || root.visible)
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
height: content.contentItem.height
opacity: 1
sourceComponent: MaterialIcon {
font.pointSize: 14
text: "arrow_forward_ios"
}
}
Loader {
id: content
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
sourceComponent: Content {
drawing: root.drawing
visibilities: root.visibilities
}
Component.onCompleted: active = Qt.binding(() => root.shouldBeActive || root.visible)
}
}
+9
View File
@@ -24,6 +24,15 @@ Scope {
} }
} }
CustomShortcut {
name: "toggle-drawing"
onPressed: {
const visibilities = Visibilities.getForActive();
visibilities.isDrawing = !visibilities.isDrawing;
}
}
CustomShortcut { CustomShortcut {
name: "toggle-nc" name: "toggle-nc"