mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-10-16 00:04:01 -04:00
268 lines
11 KiB
QML
268 lines
11 KiB
QML
import QtQuick
|
||
import qs.Common
|
||
import qs.Widgets
|
||
|
||
Item {
|
||
id: root
|
||
|
||
required property var instanceData
|
||
required property var screen
|
||
required property var hostLayer
|
||
property var lockHost: null
|
||
property bool editMode: false
|
||
|
||
readonly property string instanceId: instanceData?.id ?? ""
|
||
readonly property string widgetType: instanceData?.widgetType ?? ""
|
||
readonly property bool lockScreen: hostLayer.lockScreen
|
||
readonly property bool removable: widgetType !== "lockAuth" && widgetType !== "greeterSession"
|
||
readonly property bool selected: hostLayer.selectedInstanceId === instanceId
|
||
readonly property bool interacting: editChrome.item?.interacting ?? false
|
||
readonly property var contrastColors: content.item?.contrastColors ?? []
|
||
readonly property real bottomOverflow: content.item?.bottomOverflow ?? 0
|
||
readonly property bool hasSavedPosition: geometry.hasSavedPosition
|
||
readonly property bool automaticPlacement: lockScreen && widgetType === "desktopClock" && (instanceData?.config?.autoPosition ?? true) && !hasSavedPosition
|
||
readonly property bool dragging: editChrome.item?.dragging ?? false
|
||
readonly property real chromeOutset: editChrome.item?.chromeOutset ?? 0
|
||
readonly property var stock: hostLayer.stockRect(widgetType, root)
|
||
// Stock-placed lock widgets grow around their stock anchor; everything else keeps its top-left corner.
|
||
readonly property bool keepsCornerOnResize: !lockScreen || hasSavedPosition
|
||
property bool snappedCenterX: false
|
||
property bool snappedCenterY: false
|
||
|
||
signal removeRequested
|
||
signal optionsRequested
|
||
|
||
readonly property bool loaded: content.item !== null
|
||
onLoadedChanged: {
|
||
if (loaded)
|
||
Qt.callLater(() => hostLayer.placeIfPending(root));
|
||
}
|
||
|
||
x: geometry.widgetX
|
||
y: geometry.widgetY
|
||
width: geometry.widgetWidth
|
||
height: geometry.widgetHeight
|
||
activeFocusOnTab: editMode
|
||
Accessible.name: instanceData?.name || I18n.tr("Widget", "fallback accessible name for an unnamed plugin widget")
|
||
Accessible.role: Accessible.Button
|
||
onActiveFocusChanged: {
|
||
if (editMode && activeFocus)
|
||
hostLayer.selectedInstanceId = instanceId;
|
||
}
|
||
Keys.onReturnPressed: {
|
||
if (editMode)
|
||
optionsRequested();
|
||
}
|
||
Keys.onSpacePressed: {
|
||
if (editMode)
|
||
optionsRequested();
|
||
}
|
||
|
||
function guideFor(value, guides) {
|
||
const threshold = geometry.gridEnabled ? geometry.gridSize : Theme.spacingXL;
|
||
let best;
|
||
let bestDistance = threshold;
|
||
for (const guide of guides) {
|
||
const distance = Math.abs(value - guide);
|
||
if (distance > bestDistance)
|
||
continue;
|
||
best = guide;
|
||
bestDistance = distance;
|
||
}
|
||
return best;
|
||
}
|
||
|
||
function guidedPosition(startX, startY, dx, dy) {
|
||
const rawX = Math.max(0, Math.min(startX + dx, geometry.screenWidth - width));
|
||
const rawY = Math.max(0, Math.min(startY + dy, geometry.screenHeight - height));
|
||
const snapped = geometry.dragMoveTo(startX, startY, dx, dy);
|
||
const centerX = (hostLayer.width - width) / 2;
|
||
const centerY = (hostLayer.height - height) / 2;
|
||
const guidesX = [centerX, Theme.spacingXL, hostLayer.width - Theme.spacingXL - width];
|
||
const guidesY = [centerY, Theme.spacingXL, hostLayer.height - Theme.spacingXL - height];
|
||
if (stock) {
|
||
guidesX.push(stock.x);
|
||
guidesY.push(stock.y);
|
||
}
|
||
const guideX = guideFor(rawX, guidesX);
|
||
const guideY = guideFor(rawY, guidesY);
|
||
snappedCenterX = guideX === centerX;
|
||
snappedCenterY = guideY === centerY;
|
||
return Qt.point(guideX ?? snapped.x, guideY ?? snapped.y);
|
||
}
|
||
|
||
function pinPosition() {
|
||
geometry.savePosition(geometry.widgetX, geometry.widgetY);
|
||
SettingsData.updateDesktopWidgetInstanceConfig(instanceId, {
|
||
autoPosition: false
|
||
});
|
||
}
|
||
|
||
function commitPosition(finalX, finalY) {
|
||
if (stock && finalX === stock.x && finalY === stock.y) {
|
||
SessionData.resetDesktopWidgetInstanceGeometry(instanceId, ["x", "y"]);
|
||
return;
|
||
}
|
||
geometry.savePosition(finalX, finalY);
|
||
if (lockScreen && widgetType === "desktopClock" && (instanceData?.config?.autoPosition ?? true))
|
||
SettingsData.updateDesktopWidgetInstanceConfig(instanceId, {
|
||
autoPosition: false
|
||
});
|
||
}
|
||
|
||
DesktopWidgetGeometry {
|
||
id: geometry
|
||
instanceId: root.instanceId
|
||
instanceData: root.instanceData
|
||
screen: root.screen
|
||
lockScreen: root.lockScreen
|
||
minWidth: content.contentMinWidth
|
||
minHeight: content.contentMinHeight
|
||
forceSquare: content.contentForceSquare
|
||
defaultWidth: content.contentDefaultWidth
|
||
defaultHeight: content.contentDefaultHeight
|
||
defaultX: root.stock ? root.stock.x : screenWidth / 2 - savedWidth / 2
|
||
defaultY: root.stock ? root.stock.y : screenHeight / 2 - savedHeight / 2
|
||
}
|
||
|
||
DesktopWidgetContent {
|
||
id: content
|
||
anchors.fill: parent
|
||
active: root.visible
|
||
focus: root.widgetType === "lockAuth"
|
||
pluginId: root.widgetType
|
||
instanceId: root.instanceId
|
||
instanceData: root.instanceData
|
||
screen: root.screen
|
||
geometry: geometry
|
||
lockScreen: root.lockScreen
|
||
lockHost: root.lockHost
|
||
persistDefaults: false
|
||
}
|
||
|
||
Loader {
|
||
id: editChrome
|
||
anchors.fill: parent
|
||
active: root.editMode
|
||
|
||
sourceComponent: Item {
|
||
readonly property bool interacting: dragArea.pressed || chrome.resizing
|
||
readonly property bool dragging: dragArea.pressed
|
||
readonly property real chromeOutset: chrome.contentInset + Theme.spacingL
|
||
|
||
TapHandler {
|
||
acceptedButtons: Qt.RightButton
|
||
gesturePolicy: TapHandler.WithinBounds
|
||
onTapped: {
|
||
root.forceActiveFocus();
|
||
root.optionsRequested();
|
||
}
|
||
}
|
||
|
||
MouseArea {
|
||
id: dragArea
|
||
anchors.fill: parent
|
||
acceptedButtons: Qt.LeftButton
|
||
cursorShape: pressed ? Qt.ClosedHandCursor : Qt.OpenHandCursor
|
||
|
||
property point startPos
|
||
property real startX
|
||
property real startY
|
||
property bool moved: false
|
||
|
||
onPressed: mouse => {
|
||
root.hostLayer.selectedInstanceId = root.instanceId;
|
||
root.forceActiveFocus();
|
||
moved = false;
|
||
startPos = mapToItem(root.parent, mouse.x, mouse.y);
|
||
startX = geometry.widgetX;
|
||
startY = geometry.widgetY;
|
||
geometry.dragOverrideX = startX;
|
||
geometry.dragOverrideY = startY;
|
||
}
|
||
|
||
onPositionChanged: mouse => {
|
||
if (!pressed)
|
||
return;
|
||
const current = mapToItem(root.parent, mouse.x, mouse.y);
|
||
if (!moved && Math.hypot(current.x - startPos.x, current.y - startPos.y) < Qt.styleHints.startDragDistance)
|
||
return;
|
||
moved = true;
|
||
const next = root.guidedPosition(startX, startY, current.x - startPos.x, current.y - startPos.y);
|
||
geometry.dragOverrideX = next.x;
|
||
geometry.dragOverrideY = next.y;
|
||
}
|
||
|
||
onReleased: {
|
||
const finalX = geometry.dragOverrideX;
|
||
const finalY = geometry.dragOverrideY;
|
||
geometry.clearDragOverrides();
|
||
root.snappedCenterX = false;
|
||
root.snappedCenterY = false;
|
||
if (moved)
|
||
root.commitPosition(finalX, finalY);
|
||
}
|
||
|
||
onCanceled: {
|
||
geometry.clearDragOverrides();
|
||
root.snappedCenterX = false;
|
||
root.snappedCenterY = false;
|
||
}
|
||
}
|
||
|
||
DGridEditChrome {
|
||
id: chrome
|
||
|
||
property real startWidth
|
||
property real startHeight
|
||
property point startPos
|
||
|
||
anchors.fill: parent
|
||
anchors.margins: -parent.chromeOutset
|
||
visible: root.selected
|
||
cornerRadius: Theme.cornerRadius
|
||
dragging: dragArea.pressed
|
||
removable: root.removable
|
||
hasOptions: true
|
||
cornerResize: content.contentResizable
|
||
sizeText: Math.round(root.width) + "×" + Math.round(root.height)
|
||
snapped: geometry.squareSnapped
|
||
onRemoveRequested: root.removeRequested()
|
||
onOptionsRequested: root.optionsRequested()
|
||
// The chrome moves with the widget, so pointer deltas are taken in the layer's frame.
|
||
onResizeStarted: (px, py) => {
|
||
startPos = chrome.mapToItem(root.hostLayer, px, py);
|
||
startWidth = geometry.widgetWidth;
|
||
startHeight = geometry.widgetHeight;
|
||
geometry.dragOverrideW = startWidth;
|
||
geometry.dragOverrideH = startHeight;
|
||
if (root.keepsCornerOnResize) {
|
||
geometry.dragOverrideX = geometry.widgetX;
|
||
geometry.dragOverrideY = geometry.widgetY;
|
||
}
|
||
resizing = true;
|
||
}
|
||
onResizeMoved: (px, py) => {
|
||
const current = chrome.mapToItem(root.hostLayer, px, py);
|
||
const next = geometry.dragResizeTo(startWidth, startHeight, current.x - startPos.x, current.y - startPos.y);
|
||
geometry.dragOverrideW = next.width;
|
||
geometry.dragOverrideH = next.height;
|
||
}
|
||
onResizeEnded: {
|
||
geometry.saveSize(geometry.dragOverrideW, geometry.dragOverrideH);
|
||
if (root.automaticPlacement)
|
||
root.pinPosition();
|
||
else if (root.keepsCornerOnResize)
|
||
geometry.savePosition(geometry.widgetX, geometry.widgetY);
|
||
resizing = false;
|
||
geometry.clearDragOverrides();
|
||
}
|
||
onResizeCanceled: {
|
||
resizing = false;
|
||
geometry.clearDragOverrides();
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|