mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-10-15 15:54:00 -04:00
466 lines
17 KiB
QML
466 lines
17 KiB
QML
pragma ComponentBehavior: Bound
|
|
|
|
import QtQuick
|
|
import qs.Common
|
|
import qs.Services
|
|
import qs.Widgets
|
|
import "../../Common/QmlUtils.js" as QmlUtils
|
|
|
|
Item {
|
|
id: root
|
|
|
|
property string settingKey: "islandHomeLayout"
|
|
required property string barId
|
|
readonly property var groupIds: SettingsData._islandHomeGroupIds
|
|
readonly property var layout: {
|
|
SettingsData.barConfigs;
|
|
return SettingsData.getIslandHomeLayout(SettingsData.getBarConfig(root.barId));
|
|
}
|
|
readonly property bool isHighlighted: settingKey !== "" && SettingsSearchService.highlightSection === settingKey
|
|
|
|
readonly property var presentation: ({
|
|
"media": {
|
|
"icon": "music_note",
|
|
"text": I18n.tr("Media / Launcher", "island settings: media or launcher slot row"),
|
|
"description": I18n.tr("Search when idle, visualizer when media is playing", "island settings: media slot description")
|
|
},
|
|
"clock": {
|
|
"icon": "schedule",
|
|
"text": I18n.tr("Clock", "island settings: pinned clock row in the home layout"),
|
|
"description": I18n.tr("Current time and date display")
|
|
},
|
|
"weather": {
|
|
"icon": "wb_sunny",
|
|
"text": I18n.tr("Weather", "island settings: weather slot row"),
|
|
"description": SettingsData.weatherEnabled ? I18n.tr("Weather icon and temperature open the weather activity", "island settings: weather slot description") : I18n.tr("Enable weather in Time & Weather to show this shortcut", "island settings: weather slot disabled hint")
|
|
},
|
|
"status": {
|
|
"icon": BatteryService.batteryAvailable ? "battery_full" : "tune",
|
|
"text": I18n.tr("Battery / Control Center", "island settings: battery or control center slot row"),
|
|
"description": BatteryService.batteryAvailable ? I18n.tr("Battery gauge opens Control Center", "island settings: status slot description with battery") : I18n.tr("Tools icon opens Control Center", "island settings: status slot description without battery")
|
|
},
|
|
"volume": {
|
|
"icon": "volume_up",
|
|
"text": I18n.tr("Volume", "island settings: volume slot row"),
|
|
"description": I18n.tr("Current output volume on the home face", "island settings: volume slot description")
|
|
},
|
|
"brightness": {
|
|
"icon": "brightness_6",
|
|
"text": I18n.tr("Brightness", "island settings: brightness slot row"),
|
|
"description": I18n.tr("Current display brightness on the home face", "island settings: brightness slot description")
|
|
},
|
|
"notifications": {
|
|
"icon": "notifications",
|
|
"text": I18n.tr("Notifications", "island settings: notification badge slot row"),
|
|
"description": I18n.tr("Unread notification count beside the clock", "island settings: notification badge description")
|
|
}
|
|
})
|
|
|
|
readonly property real rowHeight: 72
|
|
readonly property real rowSpacing: Theme.spacingS
|
|
readonly property real dividerGap: 40
|
|
|
|
property var enabledOrder: []
|
|
property var disabledOrder: []
|
|
property string draggingId: ""
|
|
property var dragStartOrder: []
|
|
property string highlightedId: ""
|
|
property bool showHidden: false
|
|
|
|
readonly property bool hasHidden: disabledOrder.length > 0
|
|
readonly property real dividerY: enabledOrder.length * (rowHeight + rowSpacing)
|
|
readonly property real totalHeight: {
|
|
const base = enabledOrder.length * (rowHeight + rowSpacing);
|
|
if (!hasHidden)
|
|
return Math.max(0, base - rowSpacing);
|
|
if (!showHidden)
|
|
return base + dividerGap;
|
|
return base + dividerGap + disabledOrder.length * (rowHeight + rowSpacing) - rowSpacing;
|
|
}
|
|
|
|
width: parent?.width ?? 0
|
|
height: totalHeight
|
|
|
|
function isEnabled(id) {
|
|
const entry = layout.find(g => g.id === id);
|
|
return entry ? entry.enabled : false;
|
|
}
|
|
|
|
function rebuild() {
|
|
enabledOrder = layout.filter(g => g.enabled).map(g => g.id);
|
|
disabledOrder = layout.filter(g => !g.enabled).map(g => g.id);
|
|
}
|
|
|
|
function slotYForId(id) {
|
|
const enabledIndex = enabledOrder.indexOf(id);
|
|
if (enabledIndex >= 0)
|
|
return enabledIndex * (rowHeight + rowSpacing);
|
|
return dividerY + dividerGap + Math.max(0, disabledOrder.indexOf(id)) * (rowHeight + rowSpacing);
|
|
}
|
|
|
|
function beginDrag(id) {
|
|
draggingId = id;
|
|
dragStartOrder = enabledOrder.slice();
|
|
}
|
|
|
|
function updateDragTarget(centerY) {
|
|
if (draggingId === "")
|
|
return;
|
|
const pos = Math.max(0, Math.min(Math.floor(centerY / (rowHeight + rowSpacing)), enabledOrder.length - 1));
|
|
const arr = enabledOrder.slice();
|
|
const from = arr.indexOf(draggingId);
|
|
if (from < 0 || from === pos)
|
|
return;
|
|
arr.splice(from, 1);
|
|
arr.splice(pos, 0, draggingId);
|
|
enabledOrder = arr;
|
|
}
|
|
|
|
function commit() {
|
|
SettingsData.setIslandHomeLayoutOrder(root.barId, enabledOrder.concat(disabledOrder));
|
|
}
|
|
|
|
function endDrag() {
|
|
if (draggingId === "")
|
|
return;
|
|
const changed = JSON.stringify(enabledOrder) !== JSON.stringify(dragStartOrder);
|
|
draggingId = "";
|
|
if (changed)
|
|
commit();
|
|
}
|
|
|
|
function moveEnabled(id, delta) {
|
|
const pos = enabledOrder.indexOf(id);
|
|
const next = pos + delta;
|
|
if (pos < 0 || next < 0 || next >= enabledOrder.length)
|
|
return;
|
|
const arr = enabledOrder.slice();
|
|
arr.splice(pos, 1);
|
|
arr.splice(next, 0, id);
|
|
enabledOrder = arr;
|
|
commit();
|
|
}
|
|
|
|
onLayoutChanged: rebuild()
|
|
Component.onCompleted: {
|
|
rebuild();
|
|
Qt.callLater(() => {
|
|
if (!root.parent || !root.settingKey)
|
|
return;
|
|
const flickable = QmlUtils.findParentFlickable(root.parent);
|
|
if (flickable)
|
|
SettingsSearchService.registerCard(root.settingKey, root, flickable);
|
|
});
|
|
}
|
|
Component.onDestruction: {
|
|
if (settingKey)
|
|
SettingsSearchService.unregisterCard(settingKey);
|
|
}
|
|
|
|
function stepHighlight(dir, move) {
|
|
const order = enabledOrder.concat(disabledOrder);
|
|
if (order.length === 0)
|
|
return;
|
|
if (move && highlightedId !== "" && isEnabled(highlightedId)) {
|
|
moveEnabled(highlightedId, dir);
|
|
return;
|
|
}
|
|
if (highlightedId === "") {
|
|
highlightedId = dir > 0 ? order[0] : order[order.length - 1];
|
|
return;
|
|
}
|
|
const idx = Math.max(0, Math.min(order.length - 1, order.indexOf(highlightedId) + dir));
|
|
highlightedId = order[idx];
|
|
}
|
|
|
|
Keys.onPressed: event => {
|
|
const ctrl = (event.modifiers & Qt.ControlModifier) !== 0;
|
|
switch (event.key) {
|
|
case Qt.Key_Up:
|
|
stepHighlight(-1, ctrl);
|
|
event.accepted = true;
|
|
return;
|
|
case Qt.Key_Down:
|
|
stepHighlight(1, ctrl);
|
|
event.accepted = true;
|
|
return;
|
|
case Qt.Key_Space:
|
|
case Qt.Key_Return:
|
|
if (highlightedId === "")
|
|
return;
|
|
SettingsData.setIslandHomeGroupEnabled(root.barId, highlightedId, !isEnabled(highlightedId));
|
|
event.accepted = true;
|
|
return;
|
|
}
|
|
}
|
|
|
|
Rectangle {
|
|
anchors.fill: parent
|
|
radius: Theme.cornerRadius
|
|
color: Theme.withAlpha(Theme.primary, root.isHighlighted ? 0.2 : 0)
|
|
visible: root.isHighlighted
|
|
}
|
|
|
|
Behavior on height {
|
|
NumberAnimation {
|
|
duration: Theme.expressiveDurations.normal
|
|
easing.type: Easing.BezierSpline
|
|
easing.bezierCurve: Theme.expressiveCurves.expressiveDefaultSpatial
|
|
}
|
|
}
|
|
|
|
Item {
|
|
id: hiddenDivider
|
|
|
|
width: parent.width
|
|
height: root.dividerGap
|
|
y: root.dividerY
|
|
opacity: root.hasHidden ? 1 : 0
|
|
visible: opacity > 0.01
|
|
|
|
Behavior on y {
|
|
NumberAnimation {
|
|
duration: Theme.expressiveDurations.normal
|
|
easing.type: Easing.BezierSpline
|
|
easing.bezierCurve: Theme.expressiveCurves.expressiveDefaultSpatial
|
|
}
|
|
}
|
|
Behavior on opacity {
|
|
NumberAnimation {
|
|
duration: Theme.shortDuration
|
|
}
|
|
}
|
|
|
|
Row {
|
|
anchors.left: parent.left
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
spacing: Theme.spacingS
|
|
|
|
DankIcon {
|
|
name: "visibility_off"
|
|
size: 14
|
|
color: Theme.outline
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
}
|
|
|
|
StyledText {
|
|
text: I18n.tr("Hidden (%1)", "island settings: hidden groups divider, %1 is count").arg(root.disabledOrder.length)
|
|
font.pixelSize: Theme.fontSizeSmall
|
|
font.weight: Font.Medium
|
|
color: Theme.outline
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
}
|
|
}
|
|
|
|
DankIcon {
|
|
name: root.showHidden ? "expand_less" : "expand_more"
|
|
size: 14
|
|
color: Theme.outline
|
|
anchors.right: parent.right
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
}
|
|
|
|
MouseArea {
|
|
anchors.fill: parent
|
|
cursorShape: Qt.PointingHandCursor
|
|
onClicked: root.showHidden = !root.showHidden
|
|
}
|
|
}
|
|
|
|
Repeater {
|
|
model: root.groupIds
|
|
|
|
delegate: Item {
|
|
id: rowItem
|
|
|
|
required property string modelData
|
|
|
|
readonly property var present: root.presentation[modelData]
|
|
readonly property bool isClock: modelData === "clock"
|
|
readonly property bool isEnabled: root.isEnabled(modelData)
|
|
readonly property bool dragging: root.draggingId === modelData
|
|
readonly property bool highlighted: root.highlightedId === modelData
|
|
readonly property bool draggable: isEnabled
|
|
readonly property real surfaceAlphaScale: isClock ? 0.5 : (isEnabled ? 0.7 : 0.4)
|
|
|
|
width: root.width
|
|
height: root.rowHeight
|
|
z: dragging ? 100 : (highlighted ? 3 : 1)
|
|
visible: isEnabled || root.showHidden
|
|
|
|
Binding {
|
|
target: rowItem
|
|
property: "y"
|
|
value: root.slotYForId(rowItem.modelData)
|
|
when: !rowItem.dragging
|
|
restoreMode: Binding.RestoreNone
|
|
}
|
|
|
|
onYChanged: {
|
|
if (dragging)
|
|
root.updateDragTarget(y + height / 2);
|
|
}
|
|
|
|
Behavior on y {
|
|
enabled: !rowItem.dragging
|
|
NumberAnimation {
|
|
duration: Theme.expressiveDurations.expressiveDefaultSpatial
|
|
easing.type: Easing.BezierSpline
|
|
easing.bezierCurve: Theme.expressiveCurves.expressiveFastSpatial
|
|
}
|
|
}
|
|
|
|
Rectangle {
|
|
id: surface
|
|
|
|
anchors.fill: parent
|
|
radius: Theme.cornerRadius
|
|
color: surfaceColor.value
|
|
border.width: rowItem.dragging || rowItem.highlighted ? 2 : 1
|
|
border.color: rowItem.dragging || rowItem.highlighted ? Theme.primary : (rowItem.isClock ? Theme.outlineMedium : Theme.outlineHeavy)
|
|
|
|
DankColorAnimation {
|
|
id: surfaceColor
|
|
|
|
to: {
|
|
if (rowItem.dragging)
|
|
return Theme.secondaryContainer;
|
|
return Theme.withAlpha(Theme.surfaceContainerHigh, Theme.floatingWindowForegroundLayers ? Theme.floatingWindowForegroundTransparency * rowItem.surfaceAlphaScale : 0);
|
|
}
|
|
duration: Theme.shortDuration
|
|
}
|
|
|
|
Behavior on border.color {
|
|
ColorAnimation {
|
|
duration: Theme.shortDuration
|
|
}
|
|
}
|
|
|
|
Rectangle {
|
|
anchors.fill: parent
|
|
radius: parent.radius
|
|
color: Theme.primary
|
|
opacity: dragArea.containsMouse && !rowItem.dragging ? 0.06 : 0
|
|
|
|
Behavior on opacity {
|
|
NumberAnimation {
|
|
duration: Theme.shortDuration
|
|
}
|
|
}
|
|
}
|
|
|
|
DankIcon {
|
|
name: "drag_indicator"
|
|
size: Theme.iconSize - 4
|
|
color: rowItem.dragging ? Theme.primary : Theme.outline
|
|
anchors.left: parent.left
|
|
anchors.leftMargin: Theme.spacingM
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
opacity: !rowItem.draggable ? 0 : (dragArea.containsMouse || rowItem.dragging || rowItem.highlighted ? 1 : 0.45)
|
|
visible: opacity > 0.01
|
|
|
|
Behavior on opacity {
|
|
NumberAnimation {
|
|
duration: Theme.shortDuration
|
|
}
|
|
}
|
|
}
|
|
|
|
DankIcon {
|
|
id: groupIcon
|
|
|
|
name: rowItem.present.icon
|
|
size: Theme.iconSize
|
|
color: rowItem.isEnabled ? Theme.primary : Theme.outline
|
|
anchors.left: parent.left
|
|
anchors.leftMargin: Theme.spacingM * 2 + Theme.iconSize - 4
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
|
|
Behavior on color {
|
|
ColorAnimation {
|
|
duration: Theme.shortDuration
|
|
}
|
|
}
|
|
}
|
|
|
|
Column {
|
|
anchors.left: groupIcon.right
|
|
anchors.leftMargin: Theme.spacingM
|
|
anchors.right: visibilityButton.left
|
|
anchors.rightMargin: Theme.spacingM
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
spacing: Theme.spacingXXS
|
|
|
|
StyledText {
|
|
text: rowItem.present.text
|
|
font.pixelSize: Theme.fontSizeMedium
|
|
font.weight: Font.Medium
|
|
color: rowItem.isEnabled ? Theme.surfaceText : Theme.outline
|
|
elide: Text.ElideRight
|
|
width: parent.width
|
|
}
|
|
|
|
StyledText {
|
|
text: rowItem.present.description
|
|
font.pixelSize: Theme.fontSizeSmall
|
|
color: rowItem.isEnabled ? Theme.outline : Theme.outlineVariant
|
|
elide: Text.ElideRight
|
|
width: parent.width
|
|
visible: text.length > 0
|
|
}
|
|
}
|
|
|
|
DankIcon {
|
|
name: "lock"
|
|
size: 16
|
|
color: Theme.outline
|
|
opacity: 0.6
|
|
anchors.horizontalCenter: visibilityButton.horizontalCenter
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
visible: rowItem.isClock
|
|
}
|
|
|
|
DankActionButton {
|
|
id: visibilityButton
|
|
|
|
anchors.right: parent.right
|
|
anchors.rightMargin: Theme.spacingS
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
visible: !rowItem.isClock
|
|
buttonSize: 36
|
|
iconName: rowItem.isEnabled ? "visibility" : "visibility_off"
|
|
iconSize: 18
|
|
iconColor: rowItem.isEnabled ? Theme.primary : Theme.outline
|
|
tooltipText: rowItem.isEnabled ? I18n.tr("Hide", "island settings: hide home group") : I18n.tr("Show", "island settings: show home group")
|
|
onClicked: {
|
|
root.forceActiveFocus();
|
|
root.highlightedId = rowItem.modelData;
|
|
SettingsData.setIslandHomeGroupEnabled(root.barId, rowItem.modelData, !rowItem.isEnabled);
|
|
}
|
|
}
|
|
}
|
|
|
|
MouseArea {
|
|
id: dragArea
|
|
|
|
anchors.fill: parent
|
|
anchors.rightMargin: 48
|
|
hoverEnabled: true
|
|
enabled: rowItem.draggable
|
|
cursorShape: rowItem.dragging ? Qt.ClosedHandCursor : Qt.OpenHandCursor
|
|
drag.target: rowItem
|
|
drag.axis: Drag.YAxis
|
|
drag.minimumY: -rowItem.height
|
|
drag.maximumY: root.height
|
|
drag.smoothed: false
|
|
onPressed: {
|
|
root.forceActiveFocus();
|
|
root.highlightedId = rowItem.modelData;
|
|
root.beginDrag(rowItem.modelData);
|
|
}
|
|
onReleased: root.endDrag()
|
|
}
|
|
}
|
|
}
|
|
}
|