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.Launcher as Launcher
import qs.Modules.Resources as Resources
import qs.Modules.Drawing as Drawing
import qs.Modules.Settings as Settings
Shape {
@@ -31,6 +31,12 @@ Shape {
}
}
Drawing.Background {
startX: 0
startY: wrapper.y - rounding
wrapper: root.panels.drawing
}
Resources.Background {
startX: 0 - rounding
startY: 0
+35 -11
View File
@@ -32,19 +32,9 @@ Variants {
WlrLayershell.namespace: "ZShell-Bar"
color: "transparent"
contentItem.focus: true
mask: visibilities.isDrawing ? null : region
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: {
if (Config.barConfig.autoHide)
visibilities.bar = false;
@@ -55,6 +45,17 @@ Variants {
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 {
id: exclusionZone
@@ -117,6 +118,7 @@ Variants {
property bool bar
property bool dashboard
property bool isDrawing
property bool launcher
property bool notif: NotifServer.popups.length > 0
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 {
id: mouseArea
anchors.fill: parent
bar: barLoader
drawing: drawing
input: input
panels: panels
popouts: panels.popouts
screen: scope.modelData
visibilities: visibilities
z: 1
Panels {
id: panels
bar: backgroundRect
drawingItem: drawing
screen: scope.modelData
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
property bool dashboardShortcutActive
property point dragStart
required property Drawing drawing
required property DrawingInput input
property bool osdShortcutActive
required property Panels panels
required property BarPopouts.Wrapper popouts
@@ -51,20 +53,10 @@ CustomMouseArea {
anchors.fill: parent
hoverEnabled: 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;
// }
// }
propagateComposedEvents: true
onContainsMouseChanged: {
if (!containsMouse) {
// Only hide if not activated by shortcut
if (!osdShortcutActive) {
visibilities.osd = false;
root.panels.osd.hovered = false;
@@ -87,120 +79,42 @@ CustomMouseArea {
const dragX = x - dragStart.x;
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)
visibilities.bar = true;
if (panels.sidebar.width === 0) {
// Show osd on hover
const showOsd = inRightPanel(panels.osd, x, y);
// // Always update visibility based on hover if not in shortcut mode
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
if (showOsd) {
osdShortcutActive = false;
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 {
const outOfSidebar = x < width - panels.sidebar.width;
// Show osd on hover
const showOsd = outOfSidebar && inRightPanel(panels.osd, x, y);
// Always update visibility based on hover if not in shortcut mode
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;
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) {
bar.checkPopout(x);
}
}
// Monitor individual visibility changes
Connections {
function onDashboardChanged() {
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);
if (!inDashboardArea) {
root.dashboardShortcutActive = true;
@@ -209,20 +123,21 @@ CustomMouseArea {
root.visibilities.sidebar = false;
root.popouts.hasCurrent = false;
} else {
// Dashboard hidden, clear shortcut flag
root.dashboardShortcutActive = false;
// root.visibilities.bar = false;
}
}
function onIsDrawingChanged() {
if (!root.visibilities.isDrawing)
root.drawing.clear();
}
function onLauncherChanged() {
// If launcher is hidden, clear shortcut flags for dashboard and OSD
if (!root.visibilities.launcher) {
root.dashboardShortcutActive = false;
root.osdShortcutActive = 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);
if (!inOsdArea) {
@@ -234,13 +149,11 @@ CustomMouseArea {
function onOsdChanged() {
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);
if (!inOsdArea) {
root.osdShortcutActive = true;
}
} else {
// OSD hidden, clear shortcut flag
root.osdShortcutActive = false;
}
}
@@ -260,13 +173,11 @@ CustomMouseArea {
function onUtilitiesChanged() {
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);
if (!inUtilitiesArea) {
root.utilitiesShortcutActive = true;
}
} else {
// Utilities hidden, clear shortcut flag
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.Resources as Resources
import qs.Modules.Settings as Settings
import qs.Modules.Drawing as Drawing
import qs.Config
Item {
@@ -18,6 +19,8 @@ Item {
required property Item bar
readonly property alias dashboard: dashboard
readonly property alias drawing: drawing
required property Canvas drawingItem
readonly property alias launcher: launcher
readonly property alias notifications: notifications
readonly property alias osd: osd
@@ -47,6 +50,16 @@ Item {
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 {
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 {
name: "toggle-nc"