mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-10-14 23:34:01 -04:00
299 lines
9.8 KiB
QML
299 lines
9.8 KiB
QML
pragma ComponentBehavior: Bound
|
|
|
|
import QtQuick
|
|
import qs.Common
|
|
import qs.Widgets
|
|
import qs.Modules.DDash
|
|
import "../../../Common/GridLayout.js" as GridUtils
|
|
import "../utils/cards.js" as CardUtils
|
|
import "../../../DCommon/Common/FocusNavigation.js" as FocusNavigation
|
|
|
|
DEditableGrid {
|
|
id: root
|
|
|
|
property bool live: true
|
|
property int rowBudget: DashMetrics.minimumTabRows
|
|
property int columnCap: DashMetrics.maximumGridColumns
|
|
property bool panelPreviewing: false
|
|
property string preferredFocusId: "calendar"
|
|
property var transientSurfaceTracker: null
|
|
property string pendingFocusId: ""
|
|
property int pendingFocusReason: Qt.OtherFocusReason
|
|
property Item flashSlot: null
|
|
|
|
signal cardFocusChanged(string id)
|
|
|
|
signal cardClicked(string cardId)
|
|
signal optionsRequested(string cardId)
|
|
signal navFocusRequested(bool backwards)
|
|
signal detailRequested(var eventData)
|
|
signal editorRequested(var eventData, var initialDate)
|
|
|
|
readonly property real columnWidth: (width - DashMetrics.gridGap * (DashMetrics.gridColumns - 1)) / DashMetrics.gridColumns
|
|
readonly property int usedColumns: slotLayout.slots.reduce((used, slot) => slot ? Math.max(used, slot.col + slot.cols) : used, 0)
|
|
readonly property var addableEntries: DashRegistry.unplaced.map(entry => ({
|
|
"entry": entry,
|
|
"size": GridUtils.fitNewCard(sourceItems, DashMetrics.gridColumns, rowBudget, entry.id, DashRegistry.defaultSize(entry.id), DashRegistry.minSize(entry.id), DashRegistry.isCardAvailable)
|
|
})).filter(candidate => candidate.size !== null)
|
|
|
|
sourceItems: DashRegistry.placed
|
|
slotLayout: GridUtils.packCards(layoutItems, placementOrder, DashMetrics.gridColumns, width, DashMetrics.gridGap, DashMetrics.gridRowUnit, I18n.isRtl, DashRegistry.isCardAvailable)
|
|
minimumHeight: DashMetrics.tabMinHeight
|
|
placeholderRadius: DashMetrics.cardRadius
|
|
|
|
commitHandler: items => {
|
|
commitPanelPreview();
|
|
CardUtils.setLayout(items);
|
|
}
|
|
onSizePreviewChanged: {
|
|
if (!sizePreview)
|
|
clearPanelPreview();
|
|
}
|
|
|
|
function previewPanelColumns(startCol, size) {
|
|
const base = DashMetrics.storedPanelColumns(DashMetrics.overviewId) || DashMetrics.defaultGridColumns;
|
|
const wanted = Math.max(base, Math.min(columnCap, startCol + size.w));
|
|
if (wanted === base) {
|
|
clearPanelPreview();
|
|
return;
|
|
}
|
|
if (panelPreviewing && DashMetrics.panelPreview?.columns === wanted)
|
|
return;
|
|
DashMetrics.panelPreview = {
|
|
"id": DashMetrics.overviewId,
|
|
"columns": wanted,
|
|
"rows": DashMetrics.panelFloorRowsFor(DashMetrics.overviewId)
|
|
};
|
|
panelPreviewing = true;
|
|
}
|
|
|
|
function clearPanelPreview() {
|
|
if (!panelPreviewing)
|
|
return;
|
|
panelPreviewing = false;
|
|
DashMetrics.panelPreview = null;
|
|
}
|
|
|
|
function commitPanelPreview() {
|
|
if (!panelPreviewing)
|
|
return;
|
|
DashRegistry.setOptions(DashMetrics.overviewId, {
|
|
"panelColumns": DashMetrics.panelPreview.columns
|
|
});
|
|
clearPanelPreview();
|
|
}
|
|
|
|
function fittedSize(index, w, h) {
|
|
const card = sourceItems[index];
|
|
const want = DashRegistry.clampSize(card.id, w, h);
|
|
const fitted = GridUtils.fitResize(pinnedItems, placementOrder, DashMetrics.gridColumns, rowBudget, index, want, DashRegistry.minSize(card.id), DashRegistry.isCardAvailable);
|
|
return fitted ?? {
|
|
"w": card.w,
|
|
"h": card.h
|
|
};
|
|
}
|
|
|
|
function cardItemFor(id) {
|
|
for (let i = 0; i < slotRepeater.count; i++) {
|
|
const slot = slotRepeater.itemAt(i);
|
|
if (slot && slot.cardId === id)
|
|
return slot.cardItem;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
readonly property Item lastFocusTarget: lastFocusEntry
|
|
readonly property bool blocksTabNavigation: {
|
|
for (let i = 0; i < slotRepeater.count; i++) {
|
|
const slot = slotRepeater.itemAt(i);
|
|
if (slot?.keyboardFocused)
|
|
return slot.cardItem?.blocksTabNavigation ?? false;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function keyboardSlots() {
|
|
const slots = [];
|
|
for (let i = 0; i < slotRepeater.count; i++) {
|
|
const slot = slotRepeater.itemAt(i);
|
|
if (!slot?.visible || !slot.enabled || !slot.focusTarget?.enabled || !slot.focusTarget.visible)
|
|
continue;
|
|
slots.push(slot);
|
|
}
|
|
return slots.sort((a, b) => a.y - b.y || (I18n.isRtl ? b.x - a.x : a.x - b.x));
|
|
}
|
|
|
|
function slotFor(id) {
|
|
for (let i = 0; i < slotRepeater.count; i++) {
|
|
const slot = slotRepeater.itemAt(i);
|
|
if (slot?.cardId === id)
|
|
return slot;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
function requestFocus(backwards) {
|
|
if (editMode) {
|
|
navFocusRequested(backwards);
|
|
return;
|
|
}
|
|
if (backwards) {
|
|
const slots = keyboardSlots();
|
|
const last = slots[slots.length - 1];
|
|
last ? focusSlot(last, Qt.BacktabFocusReason) : navFocusRequested(true);
|
|
return;
|
|
}
|
|
if (!focusPreferred(Qt.TabFocusReason))
|
|
navFocusRequested(false);
|
|
}
|
|
|
|
function restoreFocus() {
|
|
if (editMode)
|
|
return false;
|
|
ringHold.stop();
|
|
return focusPreferred(Qt.OtherFocusReason);
|
|
}
|
|
|
|
function focusPreferred(reason) {
|
|
const wanted = slotFor(preferredFocusId) ?? slotFor("calendar");
|
|
if (wanted?.loading) {
|
|
pendingFocusId = wanted.cardId;
|
|
pendingFocusReason = reason;
|
|
return true;
|
|
}
|
|
const slot = preferredSlot(keyboardSlots());
|
|
if (!slot)
|
|
return false;
|
|
flashSlot = slot;
|
|
focusSlot(slot, reason);
|
|
return true;
|
|
}
|
|
|
|
function slotLoaded(slot) {
|
|
if (slot.cardId !== pendingFocusId)
|
|
return;
|
|
pendingFocusId = "";
|
|
if (editMode)
|
|
return;
|
|
flashSlot = slot;
|
|
focusSlot(slot, pendingFocusReason);
|
|
}
|
|
|
|
property int _focusReason: Qt.OtherFocusReason
|
|
|
|
function focusSlot(slot, reason) {
|
|
_focusReason = reason;
|
|
slot.focusContent(reason);
|
|
_focusReason = Qt.OtherFocusReason;
|
|
}
|
|
|
|
function noteSlotFocus(slot) {
|
|
cardFocusChanged(slot.cardId);
|
|
const changed = flashSlot !== null && flashSlot !== slot;
|
|
const keyboard = _focusReason === Qt.TabFocusReason || _focusReason === Qt.BacktabFocusReason;
|
|
flashSlot = slot;
|
|
if (changed && keyboard)
|
|
ringHold.restart();
|
|
}
|
|
|
|
function handleKeyEvent(event) {
|
|
if (editMode)
|
|
return false;
|
|
const slots = keyboardSlots();
|
|
const index = slots.findIndex(slot => slot.keyboardFocused);
|
|
if (index < 0)
|
|
return false;
|
|
const card = slots[index].cardItem;
|
|
if (typeof card.handleKeyEvent === "function" && card.handleKeyEvent(event) === true)
|
|
return true;
|
|
if (event.key !== Qt.Key_Tab && event.key !== Qt.Key_Backtab)
|
|
return false;
|
|
if (card.blocksTabNavigation)
|
|
return false;
|
|
const backwards = event.key === Qt.Key_Backtab || !!(event.modifiers & Qt.ShiftModifier);
|
|
if (!cycleFocus(backwards))
|
|
navFocusRequested(backwards);
|
|
return true;
|
|
}
|
|
|
|
function preferredSlot(slots) {
|
|
const wanted = slotFor(preferredFocusId) ?? slotFor("calendar");
|
|
return (wanted && slots.includes(wanted) ? wanted : null) ?? slots[0] ?? null;
|
|
}
|
|
|
|
function moveCardFocus(direction) {
|
|
if (editMode)
|
|
return false;
|
|
const slots = keyboardSlots();
|
|
const index = slots.findIndex(slot => slot.keyboardFocused);
|
|
if (index < 0)
|
|
return focusPreferred(Qt.TabFocusReason);
|
|
const nextIndex = GridUtils.neighborInDirection(slots.map(slot => slot.slot), index, direction);
|
|
if (nextIndex < 0)
|
|
return false;
|
|
focusSlot(slots[nextIndex], Qt.TabFocusReason);
|
|
return true;
|
|
}
|
|
|
|
function cycleFocus(backwards) {
|
|
return FocusNavigation.moveFocus(keyboardSlots().map(slot => slot.focusTarget), backwards);
|
|
}
|
|
|
|
function cycleCardFocus(backwards) {
|
|
if (editMode)
|
|
return false;
|
|
const slots = keyboardSlots();
|
|
if (!slots.length)
|
|
return false;
|
|
const index = slots.findIndex(slot => slot.keyboardFocused);
|
|
const step = backwards ? -1 : 1;
|
|
const next = index < 0 ? (backwards ? slots.length - 1 : 0) : (index + step + slots.length) % slots.length;
|
|
focusSlot(slots[next], backwards ? Qt.BacktabFocusReason : Qt.TabFocusReason);
|
|
return true;
|
|
}
|
|
|
|
Item {
|
|
id: lastFocusEntry
|
|
function requestFocus() {
|
|
root.requestFocus(true);
|
|
}
|
|
}
|
|
|
|
Repeater {
|
|
id: slotRepeater
|
|
model: root.tileModel
|
|
|
|
DashCardSlot {
|
|
grid: root
|
|
}
|
|
}
|
|
|
|
Timer {
|
|
id: ringHold
|
|
interval: DashMetrics.focusFlashHold
|
|
}
|
|
|
|
Rectangle {
|
|
x: root.flashSlot?.x ?? 0
|
|
y: root.flashSlot?.y ?? 0
|
|
width: root.flashSlot?.width ?? 0
|
|
height: root.flashSlot?.height ?? 0
|
|
z: 3
|
|
radius: DashMetrics.cardRadius
|
|
color: "transparent"
|
|
border.width: Theme.focusRingWidth
|
|
border.color: Theme.focusRingColor
|
|
visible: root.flashSlot !== null && opacity > 0
|
|
opacity: ringHold.running ? 1 : 0
|
|
|
|
Behavior on opacity {
|
|
enabled: DashMetrics.animationsEnabled
|
|
NumberAnimation {
|
|
duration: DashMetrics.fadeDuration
|
|
easing.type: Easing.BezierSpline
|
|
easing.bezierCurve: Theme.expressiveCurves.expressiveEffects
|
|
}
|
|
}
|
|
}
|
|
}
|