382 lines
13 KiB
QML
382 lines
13 KiB
QML
|
import QtQuick 2.12
|
||
|
import QtQuick.Controls 2.12
|
||
|
|
||
|
import MeuiKit 1.0 as Meui
|
||
|
import Cutefish.FileManager 1.0 as FM
|
||
|
|
||
|
GridView {
|
||
|
id: control
|
||
|
|
||
|
// XXXX
|
||
|
property var iconSize: 130 + Meui.Units.largeSpacing
|
||
|
|
||
|
cellWidth: {
|
||
|
var extraWidth = calcExtraSpacing(iconSize, control.width - leftMargin - rightMargin);
|
||
|
return iconSize + extraWidth;
|
||
|
}
|
||
|
|
||
|
cellHeight: {
|
||
|
var extraHeight = calcExtraSpacing(iconSize, control.height - topMargin - bottomMargin);
|
||
|
return iconSize + extraHeight;
|
||
|
}
|
||
|
|
||
|
ScrollBar.vertical: ScrollBar {}
|
||
|
clip: true
|
||
|
|
||
|
property Item rubberBand: null
|
||
|
|
||
|
property Item hoveredItem: null
|
||
|
property Item pressedItem: null
|
||
|
|
||
|
property int pressX: -1
|
||
|
property int pressY: -1
|
||
|
property int dragX: -1
|
||
|
property int dragY: -1
|
||
|
property variant cPress: null
|
||
|
property bool doubleClickInProgress: false
|
||
|
|
||
|
property int anchorIndex: 0
|
||
|
property bool ctrlPressed: false
|
||
|
property bool shiftPressed: false
|
||
|
|
||
|
property bool overflowing: (visibleArea.heightRatio < 1.0 || visibleArea.widthRatio < 1.0)
|
||
|
|
||
|
property bool scrollLeft: false
|
||
|
property bool scrollRight: false
|
||
|
property bool scrollUp: false
|
||
|
property bool scrollDown: false
|
||
|
|
||
|
property variant cachedRectangleSelection: null
|
||
|
property int previouslySelectedItemIndex: -1
|
||
|
property int verticalDropHitscanOffset: 0
|
||
|
|
||
|
flow: GridView.FlowLeftToRight
|
||
|
|
||
|
currentIndex: -1
|
||
|
|
||
|
onPressXChanged: {
|
||
|
cPress = mapToItem(control.contentItem, pressX, pressY);
|
||
|
}
|
||
|
|
||
|
onPressYChanged: {
|
||
|
cPress = mapToItem(control.contentItem, pressX, pressY);
|
||
|
}
|
||
|
|
||
|
MouseArea {
|
||
|
anchors.fill: parent
|
||
|
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
||
|
// propagateComposedEvents: true
|
||
|
hoverEnabled: true
|
||
|
z: -1
|
||
|
|
||
|
onPressed: {
|
||
|
control.forceActiveFocus()
|
||
|
|
||
|
if (mouse.source === Qt.MouseEventSynthesizedByQt) {
|
||
|
var index = control.indexAt(mouse.x, mouse.y)
|
||
|
var indexItem = control.itemAtIndex(index)
|
||
|
if (indexItem && indexItem.iconArea) {
|
||
|
control.currentIndex = index
|
||
|
hoveredItem = indexItem
|
||
|
} else {
|
||
|
hoveredItem = null
|
||
|
}
|
||
|
}
|
||
|
|
||
|
pressX = mouse.x
|
||
|
pressY = mouse.y
|
||
|
|
||
|
if (!hoveredItem || hoveredItem.blank) {
|
||
|
if (!control.ctrlPressed) {
|
||
|
control.currentIndex = -1;
|
||
|
previouslySelectedItemIndex = -1;
|
||
|
dirModel.clearSelection();
|
||
|
}
|
||
|
|
||
|
if (mouse.buttons & Qt.RightButton) {
|
||
|
clearPressState()
|
||
|
dirModel.openContextMenu(null, mouse.modifiers)
|
||
|
mouse.accepted = true
|
||
|
}
|
||
|
} else {
|
||
|
pressedItem = hoveredItem;
|
||
|
|
||
|
if (control.shiftPressed && control.currentIndex !== -1) {
|
||
|
positioner.setRangeSelected(control.anchorIndex, hoveredItem.index);
|
||
|
} else {
|
||
|
if (!control.ctrlPressed && !dirModel.isSelected(positioner.map(hoveredItem.index))) {
|
||
|
previouslySelectedItemIndex = -1;
|
||
|
dirModel.clearSelection();
|
||
|
}
|
||
|
|
||
|
if (control.ctrlPressed) {
|
||
|
dirModel.toggleSelected(positioner.map(hoveredItem.index));
|
||
|
} else {
|
||
|
dirModel.setSelected(positioner.map(hoveredItem.index));
|
||
|
}
|
||
|
|
||
|
control.currentIndex = hoveredItem.index;
|
||
|
|
||
|
if (mouse.buttons & Qt.RightButton) {
|
||
|
clearPressState();
|
||
|
|
||
|
dirModel.openContextMenu(null, mouse.modifiers);
|
||
|
mouse.accepted = true;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
onPositionChanged: {
|
||
|
control.ctrlPressed = (mouse.modifiers & Qt.ControlModifier);
|
||
|
control.shiftPressed = (mouse.modifiers & Qt.ShiftModifier);
|
||
|
|
||
|
var cPos = mapToItem(control.contentItem, mouse.x, mouse.y);
|
||
|
var item = control.itemAt(cPos.x, cPos.y);
|
||
|
var leftEdge = Math.min(control.contentX, control.originX);
|
||
|
|
||
|
if (!item || item.blank) {
|
||
|
if (control.hoveredItem && !root.containsDrag) {
|
||
|
control.hoveredItem = null;
|
||
|
}
|
||
|
} else {
|
||
|
var fPos = mapToItem(item, mouse.x, mouse.y);
|
||
|
|
||
|
if (fPos.x < 0 || fPos.y < 0 || fPos.x > item.width || fPos.y > item.height) {
|
||
|
control.hoveredItem = null;
|
||
|
} else {
|
||
|
control.hoveredItem = item
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Trigger autoscroll.
|
||
|
if (pressX != -1) {
|
||
|
control.scrollLeft = (mouse.x <= 0 && control.contentX > leftEdge);
|
||
|
control.scrollRight = (mouse.x >= control.width
|
||
|
&& control.contentX < control.contentItem.width - control.width);
|
||
|
control.scrollUp = (mouse.y <= 0 && control.contentY > 0);
|
||
|
control.scrollDown = (mouse.y >= control.height
|
||
|
&& control.contentY < control.contentItem.height - control.height);
|
||
|
}
|
||
|
|
||
|
// Update rubberband geometry.
|
||
|
if (control.rubberBand) {
|
||
|
var rB = control.rubberBand;
|
||
|
|
||
|
if (cPos.x < cPress.x) {
|
||
|
rB.x = Math.max(leftEdge, cPos.x);
|
||
|
rB.width = Math.abs(rB.x - cPress.x);
|
||
|
} else {
|
||
|
rB.x = cPress.x;
|
||
|
var ceil = Math.max(control.width, control.contentItem.width) + leftEdge;
|
||
|
rB.width = Math.min(ceil - rB.x, Math.abs(rB.x - cPos.x));
|
||
|
}
|
||
|
|
||
|
if (cPos.y < cPress.y) {
|
||
|
rB.y = Math.max(0, cPos.y);
|
||
|
rB.height = Math.abs(rB.y - cPress.y);
|
||
|
} else {
|
||
|
rB.y = cPress.y;
|
||
|
var ceil = Math.max(control.height, control.contentItem.height);
|
||
|
rB.height = Math.min(ceil - rB.y, Math.abs(rB.y - cPos.y));
|
||
|
}
|
||
|
|
||
|
// Ensure rubberband is at least 1px in size or else it will become
|
||
|
// invisible and not match any items.
|
||
|
rB.width = Math.max(1, rB.width);
|
||
|
rB.height = Math.max(1, rB.height);
|
||
|
|
||
|
control.rectangleSelect(rB.x, rB.y, rB.width, rB.height);
|
||
|
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// Drag initiation.
|
||
|
if (pressX != -1 /*&& root.isDrag(pressX, pressY, mouse.x, mouse.y)*/) {
|
||
|
if (pressedItem != null && dirModel.isSelected(positioner.map(pressedItem.index))) {
|
||
|
dragX = mouse.x;
|
||
|
dragY = mouse.y;
|
||
|
control.verticalDropHitscanOffset = pressedItem.iconArea.y + (pressedItem.iconArea.height / 2)
|
||
|
dirModel.dragSelected(mouse.x, mouse.y);
|
||
|
dragX = -1;
|
||
|
dragY = -1;
|
||
|
clearPressState();
|
||
|
} else {
|
||
|
dirModel.pinSelection();
|
||
|
control.rubberBand = rubberBandObject.createObject(control.contentItem, {x: cPress.x, y: cPress.y})
|
||
|
control.interactive = false;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
onContainsMouseChanged: {
|
||
|
if (!containsMouse && !control.rubberBand) {
|
||
|
clearPressState();
|
||
|
|
||
|
if (control.hoveredItem) {
|
||
|
control.hoveredItem = null;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
onCanceled: pressCanceled()
|
||
|
onReleased: pressCanceled()
|
||
|
}
|
||
|
|
||
|
function calcExtraSpacing(cellSize, containerSize) {
|
||
|
var availableColumns = Math.floor(containerSize / cellSize);
|
||
|
var extraSpacing = 0;
|
||
|
if (availableColumns > 0) {
|
||
|
var allColumnSize = availableColumns * cellSize;
|
||
|
var extraSpace = Math.max(containerSize - allColumnSize, 0);
|
||
|
extraSpacing = extraSpace / availableColumns;
|
||
|
}
|
||
|
return Math.floor(extraSpacing);
|
||
|
}
|
||
|
|
||
|
function clearPressState() {
|
||
|
pressedItem = null;
|
||
|
pressX = -1;
|
||
|
pressY = -1;
|
||
|
}
|
||
|
|
||
|
function rectangleSelect(x, y, width, height) {
|
||
|
var rows = (control.flow === GridView.FlowLeftToRight);
|
||
|
var axis = rows ? control.width : control.height;
|
||
|
var step = rows ? cellWidth : cellHeight;
|
||
|
var perStripe = Math.floor(axis / step);
|
||
|
var stripes = Math.ceil(control.count / perStripe);
|
||
|
var cWidth = control.cellWidth - (2 * Meui.Units.smallSpacing);
|
||
|
var cHeight = control.cellHeight - (2 * Meui.Units.smallSpacing);
|
||
|
var midWidth = control.cellWidth / 2;
|
||
|
var midHeight = control.cellHeight / 2;
|
||
|
var indices = [];
|
||
|
|
||
|
for (var s = 0; s < stripes; s++) {
|
||
|
for (var i = 0; i < perStripe; i++) {
|
||
|
var index = (s * perStripe) + i;
|
||
|
|
||
|
if (index >= control.count) {
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
if (positioner.isBlank(index)) {
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
var itemX = ((rows ? i : s) * control.cellWidth);
|
||
|
var itemY = ((rows ? s : i) * control.cellHeight);
|
||
|
|
||
|
if (control.effectiveLayoutDirection == Qt.RightToLeft) {
|
||
|
itemX -= (rows ? control.contentX : control.originX);
|
||
|
itemX += cWidth;
|
||
|
itemX = (rows ? control.width : control.contentItem.width) - itemX;
|
||
|
}
|
||
|
|
||
|
// Check if the rubberband intersects this cell first to avoid doing more
|
||
|
// expensive work.
|
||
|
if (control.rubberBand.intersects(Qt.rect(itemX + Meui.Units.smallSpacing, itemY + Meui.Units.smallSpacing,
|
||
|
cWidth, cHeight))) {
|
||
|
var item = control.contentItem.childAt(itemX + midWidth, itemY + midHeight);
|
||
|
|
||
|
// If this is a visible item, check for intersection with the actual
|
||
|
// icon or label rects for better feel.
|
||
|
if (item && item.iconArea) {
|
||
|
var iconRect = Qt.rect(itemX + item.iconArea.x, itemY + item.iconArea.y,
|
||
|
item.iconArea.width, item.iconArea.height);
|
||
|
|
||
|
if (control.rubberBand.intersects(iconRect)) {
|
||
|
indices.push(index);
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
var labelRect = Qt.rect(itemX + item.labelArea.x, itemY + item.labelArea.y,
|
||
|
item.labelArea.width, item.labelArea.height);
|
||
|
|
||
|
if (control.rubberBand.intersects(labelRect)) {
|
||
|
indices.push(index);
|
||
|
continue;
|
||
|
}
|
||
|
} else {
|
||
|
// Otherwise be content with the cell intersection.
|
||
|
indices.push(index);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
control.cachedRectangleSelection = indices;
|
||
|
}
|
||
|
|
||
|
onCachedRectangleSelectionChanged: {
|
||
|
if (cachedRectangleSelection == null) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (cachedRectangleSelection.length) {
|
||
|
// Set current index to start of selection.
|
||
|
// cachedRectangleSelection is pre-sorted.
|
||
|
currentIndex = cachedRectangleSelection[0];
|
||
|
}
|
||
|
|
||
|
dirModel.updateSelection(cachedRectangleSelection.map(positioner.map), control.ctrlPressed);
|
||
|
}
|
||
|
|
||
|
function pressCanceled() {
|
||
|
if (control.rubberBand) {
|
||
|
control.rubberBand.close()
|
||
|
control.rubberBand = null
|
||
|
|
||
|
control.interactive = true;
|
||
|
control.cachedRectangleSelection = null;
|
||
|
dirModel.unpinSelection();
|
||
|
}
|
||
|
|
||
|
clearPressState();
|
||
|
control.cancelAutoscroll();
|
||
|
}
|
||
|
|
||
|
function cancelAutoscroll() {
|
||
|
scrollLeft = false;
|
||
|
scrollRight = false;
|
||
|
scrollUp = false;
|
||
|
scrollDown = false;
|
||
|
}
|
||
|
|
||
|
Component {
|
||
|
id: rubberBandObject
|
||
|
|
||
|
FM.RubberBand {
|
||
|
id: rubberBand
|
||
|
|
||
|
width: 0
|
||
|
height: 0
|
||
|
z: 99999
|
||
|
color: Meui.Theme.highlightColor
|
||
|
|
||
|
function close() {
|
||
|
opacityAnimation.restart()
|
||
|
}
|
||
|
|
||
|
OpacityAnimator {
|
||
|
id: opacityAnimation
|
||
|
target: rubberBand
|
||
|
to: 0
|
||
|
from: 1
|
||
|
duration: 150
|
||
|
|
||
|
easing {
|
||
|
bezierCurve: [0.4, 0.0, 1, 1]
|
||
|
type: Easing.Bezier
|
||
|
}
|
||
|
|
||
|
onFinished: {
|
||
|
rubberBand.visible = false
|
||
|
rubberBand.enabled = false
|
||
|
rubberBand.destroy()
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|