mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-10-18 09:14:01 -04:00
293 lines
11 KiB
QML
293 lines
11 KiB
QML
import QtQuick
|
|
import qs.Common
|
|
import qs.DCommon.Widgets
|
|
import qs.Widgets
|
|
|
|
Item {
|
|
id: root
|
|
|
|
LayoutMirroring.enabled: I18n.isRtl
|
|
LayoutMirroring.childrenInherit: true
|
|
|
|
property string sizeText: ""
|
|
property bool dragging: false
|
|
property bool resizing: false
|
|
property bool hasOptions: false
|
|
property bool atDefault: false
|
|
property bool snapped: false
|
|
property bool removable: true
|
|
property string removeIcon: "close"
|
|
property bool horizontalResize: false
|
|
property bool edgeResize: false
|
|
property bool cornerResize: true
|
|
property real resizeEdgeWidth: -1
|
|
property real hitOverflow: -1
|
|
// A child of the tile that keeps its own input in edit mode; resize bands never claim points over it.
|
|
property Item passthrough: null
|
|
property real cornerRadius: Theme.cornerRadiusXL
|
|
property vector4d cornerRadii: Qt.vector4d(cornerRadius, cornerRadius, cornerRadius, cornerRadius)
|
|
property real buttonSize: Theme.iconSizeLarge
|
|
property real iconSize: Theme.iconSizeSmall
|
|
readonly property real handleThickness: Theme.spacingM
|
|
readonly property real touchTargetSize: Math.max(Theme.minimumTouchTargetSize, buttonSize)
|
|
readonly property real contentInset: touchTargetSize / 2
|
|
readonly property bool showOptionsButton: hasOptions
|
|
// Too narrow to sit beside the remove button: both move onto the top corners instead.
|
|
readonly property bool cornerButtons: hasOptions && width - contentInset * 2 < touchTargetSize * (1 + (removable ? 1 : 0) + (horizontalResize ? 1 : 0))
|
|
readonly property int chromeButtons: (removable ? 1 : 0) + (showOptionsButton && !cornerButtons ? 1 : 0)
|
|
readonly property rect hitBounds: Qt.rect(contentInset - hitOverflow, contentInset - hitOverflow, width - (contentInset - hitOverflow) * 2, height - (contentInset - hitOverflow) * 2)
|
|
|
|
signal removeRequested
|
|
signal optionsRequested(var anchor)
|
|
signal resizeStarted(real px, real py, int signX)
|
|
signal resizeCanceled
|
|
signal resizeMoved(real px, real py)
|
|
signal resizeEnded
|
|
|
|
component HitMask: Item {
|
|
required property Item target
|
|
readonly property real insetX: Math.max(0, root.hitBounds.x - target.x)
|
|
readonly property real insetY: Math.max(0, root.hitBounds.y - target.y)
|
|
|
|
x: insetX
|
|
y: insetY
|
|
width: Math.max(0, Math.min(target.width, root.hitBounds.x + root.hitBounds.width - target.x) - insetX)
|
|
height: Math.max(0, Math.min(target.height, root.hitBounds.y + root.hitBounds.height - target.y) - insetY)
|
|
}
|
|
|
|
Rectangle {
|
|
anchors.fill: parent
|
|
anchors.margins: root.contentInset
|
|
topLeftRadius: root.cornerRadii.x
|
|
topRightRadius: root.cornerRadii.y
|
|
bottomRightRadius: root.cornerRadii.z
|
|
bottomLeftRadius: root.cornerRadii.w
|
|
color: root.dragging || root.resizing ? Theme.withAlpha(Theme.primary, Theme.stateLayerDrag) : "transparent"
|
|
border.color: Theme.primary
|
|
border.width: Theme.outlineWidthFocused
|
|
}
|
|
|
|
component ButtonChip: Rectangle {
|
|
height: root.buttonSize
|
|
radius: Theme.fullRadius(width, height)
|
|
color: Theme.chipSurface
|
|
border.color: Theme.primary
|
|
border.width: Theme.outlineWidth
|
|
}
|
|
|
|
ButtonChip {
|
|
x: root.cornerButtons ? removeButton.x + (root.touchTargetSize - root.buttonSize) / 2 : I18n.isRtl ? root.width - root.contentInset - width - (root.touchTargetSize - root.buttonSize) / 2 : root.contentInset + (root.touchTargetSize - root.buttonSize) / 2
|
|
y: root.contentInset - height / 2
|
|
width: root.buttonSize + root.touchTargetSize * Math.max(0, root.chromeButtons - 1)
|
|
visible: root.chromeButtons > 0
|
|
}
|
|
|
|
ButtonChip {
|
|
x: optionsButton.x + (root.touchTargetSize - root.buttonSize) / 2
|
|
y: root.contentInset - height / 2
|
|
width: root.buttonSize
|
|
visible: root.cornerButtons
|
|
}
|
|
|
|
DActionButton {
|
|
id: removeButton
|
|
|
|
x: {
|
|
if (root.cornerButtons)
|
|
return I18n.isRtl ? root.width - width : 0;
|
|
return I18n.isRtl ? root.width - root.contentInset - width : root.contentInset;
|
|
}
|
|
y: 0
|
|
width: root.touchTargetSize
|
|
height: root.touchTargetSize
|
|
buttonSize: root.buttonSize
|
|
iconSize: root.iconSize
|
|
iconName: root.removeIcon
|
|
iconColor: Theme.error
|
|
tooltipText: I18n.tr("Remove")
|
|
visible: root.removable
|
|
enabled: !root.dragging && !root.resizing
|
|
containmentMask: root.hitOverflow < 0 ? null : removeMask
|
|
onClicked: root.removeRequested()
|
|
|
|
HitMask {
|
|
id: removeMask
|
|
target: removeButton
|
|
}
|
|
onPressAndHold: {
|
|
if (root.hasOptions)
|
|
root.optionsRequested(removeButton);
|
|
}
|
|
Shortcut {
|
|
sequences: ["Menu", "Shift+F10"]
|
|
enabled: root.hasOptions && removeButton.activeFocus && removeButton.enabled
|
|
onActivated: root.optionsRequested(removeButton)
|
|
}
|
|
}
|
|
|
|
DActionButton {
|
|
id: optionsButton
|
|
|
|
readonly property real slotOffset: root.removable ? root.touchTargetSize : 0
|
|
|
|
x: {
|
|
if (root.cornerButtons)
|
|
return I18n.isRtl ? 0 : root.width - width;
|
|
return I18n.isRtl ? root.width - root.contentInset - slotOffset - width : root.contentInset + slotOffset;
|
|
}
|
|
y: 0
|
|
width: root.touchTargetSize
|
|
height: root.touchTargetSize
|
|
buttonSize: root.buttonSize
|
|
iconSize: root.iconSize
|
|
iconName: "tune"
|
|
iconColor: Theme.primary
|
|
tooltipText: I18n.tr("Options")
|
|
visible: root.showOptionsButton
|
|
enabled: !root.dragging && !root.resizing
|
|
containmentMask: root.hitOverflow < 0 ? null : optionsMask
|
|
onClicked: root.optionsRequested(optionsButton)
|
|
|
|
HitMask {
|
|
id: optionsMask
|
|
target: optionsButton
|
|
}
|
|
}
|
|
|
|
Rectangle {
|
|
anchors.centerIn: parent
|
|
width: sizeLabel.implicitWidth + Theme.spacingM * 2
|
|
height: root.buttonSize
|
|
radius: Theme.fullRadius(width, height)
|
|
color: root.atDefault || root.snapped ? Theme.primary : Theme.chipSurface
|
|
visible: root.resizing && root.sizeText.length > 0
|
|
|
|
StyledText {
|
|
id: sizeLabel
|
|
anchors.centerIn: parent
|
|
text: root.sizeText
|
|
font.pixelSize: Theme.fontSizeMedium
|
|
font.weight: Theme.fontWeightMedium
|
|
color: root.atDefault || root.snapped ? Theme.onPrimary : Theme.surfaceText
|
|
}
|
|
}
|
|
|
|
readonly property real gripRadius: Math.max(0, Math.min(cornerRadius, width / 2 - contentInset, height / 2 - contentInset) - Theme.outlineWidthFocused / 2)
|
|
property real handleOverhang: edgeResize || Math.min(width, height) - contentInset * 2 < touchTargetSize * 2 ? contentInset : Theme.spacingS / 2
|
|
readonly property real gripInset: handleOverhang - Theme.spacingS / 2
|
|
|
|
component ResizeBand: MouseArea {
|
|
property int signX: 1
|
|
property real startX: 0
|
|
property real startY: 0
|
|
|
|
enabled: visible && !root.dragging
|
|
hoverEnabled: true
|
|
preventStealing: true
|
|
|
|
onPressed: mouse => {
|
|
const p = mapToItem(root, mouse.x, mouse.y);
|
|
startX = p.x - root.contentInset;
|
|
startY = p.y - root.contentInset;
|
|
root.resizeStarted(startX, startY, signX);
|
|
}
|
|
onPositionChanged: mouse => {
|
|
if (!pressed)
|
|
return;
|
|
const p = mapToItem(root, mouse.x, mouse.y);
|
|
root.resizeMoved(p.x - root.contentInset, p.y - root.contentInset);
|
|
}
|
|
onReleased: root.resizeEnded()
|
|
onCanceled: root.resizeCanceled()
|
|
}
|
|
|
|
component Handle: Item {
|
|
id: handleItem
|
|
|
|
property int signX: 1
|
|
readonly property bool diagonalFlipped: (signX > 0) === I18n.isRtl
|
|
|
|
y: root.horizontalResize ? (root.height - height) / 2 : root.height - root.contentInset - height + root.handleOverhang
|
|
width: root.touchTargetSize
|
|
height: root.touchTargetSize
|
|
|
|
Rectangle {
|
|
anchors.centerIn: parent
|
|
width: root.handleThickness
|
|
height: root.buttonSize
|
|
radius: Theme.fullRadius(width, height)
|
|
color: Theme.primary
|
|
visible: root.horizontalResize
|
|
}
|
|
|
|
DResizeGrip {
|
|
anchors.fill: parent
|
|
anchors.leftMargin: handleItem.signX < 0 ? root.gripInset : 0
|
|
anchors.rightMargin: handleItem.signX > 0 ? root.gripInset : 0
|
|
anchors.bottomMargin: root.gripInset
|
|
visible: !root.horizontalResize
|
|
gripRadius: root.gripRadius
|
|
mirrored: handleItem.diagonalFlipped
|
|
}
|
|
|
|
ResizeBand {
|
|
id: band
|
|
|
|
anchors.fill: parent
|
|
signX: handleItem.signX
|
|
cursorShape: root.horizontalResize ? Qt.SizeHorCursor : (handleItem.diagonalFlipped ? Qt.SizeBDiagCursor : Qt.SizeFDiagCursor)
|
|
containmentMask: root.hitOverflow < 0 && !root.passthrough ? null : bandMask
|
|
|
|
HitMask {
|
|
id: handleMask
|
|
target: handleItem
|
|
}
|
|
|
|
QtObject {
|
|
id: bandMask
|
|
|
|
function overButton(button: Item, point: point): bool {
|
|
return button.visible && button.contains(band.mapToItem(button, point.x, point.y));
|
|
}
|
|
|
|
function contains(point: point): bool {
|
|
if (root.hitOverflow >= 0 && (point.x < handleMask.x || point.y < handleMask.y || point.x >= handleMask.x + handleMask.width || point.y >= handleMask.y + handleMask.height))
|
|
return false;
|
|
if (overButton(removeButton, point) || overButton(optionsButton, point))
|
|
return false;
|
|
if (root.resizeEdgeWidth >= 0 && !root.horizontalResize) {
|
|
const local = band.mapToItem(root, point.x, point.y);
|
|
const fromEdge = handleItem.diagonalFlipped ? local.x - root.contentInset : root.width - root.contentInset - local.x;
|
|
if (fromEdge > root.resizeEdgeWidth && root.height - root.contentInset - local.y > root.resizeEdgeWidth)
|
|
return false;
|
|
}
|
|
const target = root.passthrough;
|
|
if (!target?.visible)
|
|
return true;
|
|
return !target.contains(band.mapToItem(target, point.x, point.y));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Handle {
|
|
anchors.right: parent.right
|
|
anchors.rightMargin: root.contentInset - (root.horizontalResize ? width / 2 : root.handleOverhang)
|
|
z: 1
|
|
visible: root.cornerResize
|
|
}
|
|
|
|
Loader {
|
|
anchors.fill: parent
|
|
active: root.edgeResize && root.visible
|
|
|
|
sourceComponent: Item {
|
|
Handle {
|
|
anchors.left: parent.left
|
|
anchors.leftMargin: root.contentInset - (root.horizontalResize ? width / 2 : root.handleOverhang)
|
|
signX: -1
|
|
}
|
|
}
|
|
}
|
|
}
|