mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-10-21 18:54:01 -04:00
395 lines
14 KiB
QML
395 lines
14 KiB
QML
pragma ComponentBehavior: Bound
|
|
|
|
import QtQuick
|
|
import qs.Common
|
|
|
|
Item {
|
|
id: root
|
|
|
|
x: Math.round(parent.x) - parent.x
|
|
y: Math.round(parent.y) - parent.y
|
|
width: Math.round(parent.x + parent.width) - Math.round(parent.x)
|
|
height: Math.round(parent.y + parent.height) - Math.round(parent.y)
|
|
clip: true
|
|
|
|
required property var controller
|
|
required property real islandX
|
|
required property real islandY
|
|
required property real hostWidth
|
|
required property real hostHeight
|
|
required property real springTimeConstantMs
|
|
required property real morphProgress
|
|
required property bool expanded
|
|
required property bool pointerInside
|
|
required property string activityId
|
|
// The colour painted behind the faces, so edge fades blend into it.
|
|
required property color surfaceColor
|
|
property bool freeMode: false
|
|
property Component compactFaceOverride: null
|
|
// Hosted islands fold the slot anchor into every target; the face must pin to the same resolved slot.
|
|
property var resolveTarget: target => target
|
|
// Embedded sheets grow by this on the near edge; the face keeps its content size below the fold.
|
|
property real expandedInset: 0
|
|
required property Component homeCompactComponent
|
|
required property Component homeExpandedComponent
|
|
required property Component mediaCompactComponent
|
|
required property Component mediaExpandedComponent
|
|
required property Component launcherCompactComponent
|
|
required property Component launcherExpandedComponent
|
|
required property Component controlCenterCompactComponent
|
|
required property Component controlCenterExpandedComponent
|
|
required property Component wallpaperCompactComponent
|
|
required property Component wallpaperExpandedComponent
|
|
required property Component weatherCompactComponent
|
|
required property Component weatherExpandedComponent
|
|
required property Component systemCompactComponent
|
|
required property Component systemExpandedComponent
|
|
required property Component notificationCompactComponent
|
|
required property Component notificationExpandedComponent
|
|
required property Component notificationCenterCompactComponent
|
|
required property Component notificationCenterExpandedComponent
|
|
required property Component clipboardCompactComponent
|
|
required property Component clipboardExpandedComponent
|
|
required property Component powerMenuCompactComponent
|
|
required property Component powerMenuExpandedComponent
|
|
|
|
readonly property real compactFade: root.fadeCompact(root.morphProgress)
|
|
readonly property real expandedFade: root.fadeExpanded(root.morphProgress)
|
|
readonly property real outgoingCompactFade: root.fadeCompact(root.outgoingMorph)
|
|
readonly property real outgoingExpandedFade: root.fadeExpanded(root.outgoingMorph)
|
|
readonly property bool mediaSurfaceActive: root.surfaceActive("media")
|
|
readonly property bool systemSurfaceActive: root.controller.systemActivities.some(id => root.surfaceActive(id))
|
|
|
|
function systemOpacity(opacityFor) {
|
|
const incoming = root.controller.isSystemActivity(root.renderedActivity) ? opacityFor(root.renderedActivity) : 0;
|
|
const outgoing = root.controller.isSystemActivity(root.outgoingActivity) ? opacityFor(root.outgoingActivity) : 0;
|
|
return Math.max(incoming, outgoing);
|
|
}
|
|
|
|
property string renderedActivity: "home"
|
|
property string outgoingActivity: ""
|
|
property real activityFade: 1
|
|
property real outgoingMorph: 0
|
|
property bool homeExpandedTouched: false
|
|
|
|
Keys.onEscapePressed: root.controller.requestCollapse()
|
|
|
|
function fadeCompact(morph) {
|
|
return 1 - Math.max(0, Math.min(1, morph / 0.34));
|
|
}
|
|
|
|
function fadeExpanded(morph) {
|
|
return Math.max(0, Math.min(1, (morph - 0.22) / 0.42));
|
|
}
|
|
|
|
function surfaceActive(activity) {
|
|
return root.renderedActivity === activity || root.outgoingActivity === activity;
|
|
}
|
|
|
|
function compactOpacity(activity) {
|
|
const incoming = root.renderedActivity === activity ? root.activityFade * root.compactFade : 0;
|
|
const outgoing = root.outgoingActivity === activity ? (1 - root.activityFade) * root.outgoingCompactFade : 0;
|
|
return Math.max(incoming, outgoing);
|
|
}
|
|
|
|
function expandedOpacity(activity) {
|
|
const incoming = root.renderedActivity === activity ? root.activityFade * root.expandedFade : 0;
|
|
const outgoing = root.outgoingActivity === activity ? (1 - root.activityFade) * root.outgoingExpandedFade : 0;
|
|
return Math.max(incoming, outgoing);
|
|
}
|
|
|
|
readonly property var focusableFaces: ({
|
|
"home": homeExpandedLoader,
|
|
"media": mediaExpandedLoader,
|
|
"launcher": launcherExpandedLoader,
|
|
"wallpaper": wallpaperExpandedLoader,
|
|
"weather": weatherExpandedLoader,
|
|
"notificationcenter": notificationCenterExpandedLoader,
|
|
"clipboard": clipboardExpandedLoader,
|
|
"powermenu": powerMenuExpandedLoader,
|
|
"controlcenter": controlCenterExpandedLoader
|
|
})
|
|
|
|
function requestActivityFocus() {
|
|
if (root.activityId === "launcher" && launcherExpandedLoader.active) {
|
|
// Reserve focus until the launcher is ready.
|
|
root.refocusLauncher();
|
|
return true;
|
|
}
|
|
const face = root.focusableFaces[root.activityId]?.item;
|
|
if (!face || typeof face.focusFace !== "function")
|
|
return false;
|
|
return face.focusFace() === true;
|
|
}
|
|
|
|
function refocusLauncher() {
|
|
if (root.activityId !== "launcher" || !root.controller.keyboardDismissRequested || !launcherExpandedLoader.enabled)
|
|
return;
|
|
const face = launcherExpandedLoader.item;
|
|
if (!face || face.activeFocus)
|
|
return;
|
|
face.focusFace();
|
|
}
|
|
|
|
function latchHomeExpanded() {
|
|
if (!homeExpandedTouched && expanded && activityId === "home")
|
|
homeExpandedTouched = true;
|
|
}
|
|
|
|
onPointerInsideChanged: {
|
|
if (root.pointerInside)
|
|
root.homeExpandedTouched = true;
|
|
}
|
|
|
|
onExpandedChanged: latchHomeExpanded()
|
|
|
|
onActivityIdChanged: {
|
|
latchHomeExpanded();
|
|
if (activityId === renderedActivity)
|
|
return;
|
|
outgoingMorph = morphProgress;
|
|
outgoingActivity = renderedActivity;
|
|
renderedActivity = activityId;
|
|
activityFade = 0;
|
|
activityTransition.restart();
|
|
}
|
|
|
|
SequentialAnimation {
|
|
id: activityTransition
|
|
|
|
PauseAnimation {
|
|
duration: Math.round(root.springTimeConstantMs)
|
|
}
|
|
|
|
NumberAnimation {
|
|
target: root
|
|
property: "activityFade"
|
|
to: 1
|
|
duration: Math.round(root.springTimeConstantMs * 3)
|
|
easing.type: Easing.OutCubic
|
|
}
|
|
|
|
ScriptAction {
|
|
script: root.outgoingActivity = ""
|
|
}
|
|
}
|
|
|
|
// Where a resolved target sits at rest, in host coordinates.
|
|
function slotAlong(target) {
|
|
return root.controller.isVertical ? (root.hostHeight - target.height) / 2 + target.offsetAlong : (root.hostWidth - target.width) / 2 + target.offsetAlong;
|
|
}
|
|
|
|
function slotCross(target) {
|
|
if (!root.controller.farEdge)
|
|
return target.offsetCross;
|
|
return root.controller.isVertical ? root.hostWidth - target.offsetCross - target.width : root.hostHeight - target.offsetCross - target.height;
|
|
}
|
|
|
|
// Pinned to the compact target's own screen slot so the face holds still while the island morphs.
|
|
component CompactFace: Loader {
|
|
required property string activity
|
|
required property Component face
|
|
readonly property var target: root.resolveTarget(root.controller.compactTargetFor(activity))
|
|
readonly property bool isVertical: root.controller.isVertical
|
|
readonly property real alongPos: !active ? 0 : Math.round(root.slotAlong(target)) - Math.round(isVertical ? root.islandY : root.islandX)
|
|
readonly property real crossPos: !active ? 0 : Math.round(root.slotCross(target)) - Math.round(isVertical ? root.islandX : root.islandY)
|
|
|
|
x: root.freeMode ? Math.round((parent.width - width) / 2) : (isVertical ? crossPos : alongPos)
|
|
y: root.freeMode ? Math.round((parent.height - height) / 2) : (isVertical ? alongPos : crossPos)
|
|
width: target.width
|
|
height: target.height
|
|
asynchronous: false
|
|
sourceComponent: root.compactFaceOverride && root.controller.usesDotFace(activity) ? root.compactFaceOverride : face
|
|
opacity: !active ? 0 : root.controller.isSystemActivity(activity) ? root.systemOpacity(root.compactOpacity) : root.compactOpacity(activity)
|
|
visible: opacity > 0.001
|
|
enabled: opacity >= 0.5
|
|
}
|
|
|
|
// Pinned to the sheet's settled slot so the content holds still while the island morphs.
|
|
component ExpandedFace: Loader {
|
|
required property string activity
|
|
readonly property var target: root.controller.expandedTargetFor(activity)
|
|
readonly property var slot: root.resolveTarget(target)
|
|
readonly property bool isVertical: root.controller.isVertical
|
|
readonly property real inset: root.controller.farEdge ? 0 : root.expandedInset
|
|
readonly property real alongPos: !active ? 0 : Math.round(root.slotAlong(slot)) - Math.round(isVertical ? root.islandY : root.islandX)
|
|
readonly property real crossPos: !active ? 0 : Math.round(root.slotCross(slot)) - Math.round(isVertical ? root.islandX : root.islandY) + inset
|
|
|
|
x: isVertical ? crossPos : alongPos
|
|
y: isVertical ? alongPos : crossPos
|
|
width: target.width
|
|
height: target.height
|
|
opacity: !active ? 0 : root.controller.isSystemActivity(activity) ? root.systemOpacity(root.expandedOpacity) : root.expandedOpacity(activity)
|
|
visible: opacity > 0.001
|
|
enabled: opacity >= 0.5
|
|
}
|
|
|
|
CompactFace {
|
|
active: true
|
|
activity: "home"
|
|
face: root.homeCompactComponent
|
|
}
|
|
|
|
ExpandedFace {
|
|
id: homeExpandedLoader
|
|
|
|
activity: "home"
|
|
active: root.homeExpandedTouched
|
|
asynchronous: true
|
|
sourceComponent: root.homeExpandedComponent
|
|
}
|
|
|
|
CompactFace {
|
|
active: root.mediaSurfaceActive
|
|
activity: "media"
|
|
face: root.mediaCompactComponent
|
|
}
|
|
|
|
ExpandedFace {
|
|
id: mediaExpandedLoader
|
|
|
|
activity: "media"
|
|
active: root.mediaSurfaceActive && (root.expanded || root.expandedFade > 0)
|
|
asynchronous: false
|
|
sourceComponent: root.mediaExpandedComponent
|
|
}
|
|
|
|
CompactFace {
|
|
active: root.surfaceActive("launcher")
|
|
activity: "launcher"
|
|
face: root.launcherCompactComponent
|
|
}
|
|
|
|
ExpandedFace {
|
|
id: launcherExpandedLoader
|
|
|
|
activity: "launcher"
|
|
active: root.controller.visualsRequested("launcher")
|
|
asynchronous: true
|
|
sourceComponent: root.launcherExpandedComponent
|
|
onEnabledChanged: {
|
|
if (enabled)
|
|
root.refocusLauncher();
|
|
}
|
|
onItemChanged: {
|
|
if (item)
|
|
root.refocusLauncher();
|
|
}
|
|
}
|
|
|
|
CompactFace {
|
|
active: root.surfaceActive("controlcenter")
|
|
activity: "controlcenter"
|
|
face: root.controlCenterCompactComponent
|
|
}
|
|
|
|
ExpandedFace {
|
|
id: controlCenterExpandedLoader
|
|
activity: "controlcenter"
|
|
active: root.controller.visualsRequested("controlcenter")
|
|
asynchronous: false
|
|
sourceComponent: root.controlCenterExpandedComponent
|
|
}
|
|
|
|
CompactFace {
|
|
active: root.surfaceActive("wallpaper")
|
|
activity: "wallpaper"
|
|
face: root.wallpaperCompactComponent
|
|
}
|
|
|
|
ExpandedFace {
|
|
id: wallpaperExpandedLoader
|
|
|
|
activity: "wallpaper"
|
|
active: root.controller.visualsRequested("wallpaper")
|
|
asynchronous: true
|
|
sourceComponent: root.wallpaperExpandedComponent
|
|
}
|
|
|
|
CompactFace {
|
|
active: root.surfaceActive("weather")
|
|
activity: "weather"
|
|
face: root.weatherCompactComponent
|
|
}
|
|
|
|
ExpandedFace {
|
|
id: weatherExpandedLoader
|
|
|
|
activity: "weather"
|
|
active: root.controller.visualsRequested("weather")
|
|
asynchronous: true
|
|
sourceComponent: root.weatherExpandedComponent
|
|
}
|
|
|
|
CompactFace {
|
|
active: root.surfaceActive("notificationcenter")
|
|
activity: "notificationcenter"
|
|
face: root.notificationCenterCompactComponent
|
|
}
|
|
|
|
ExpandedFace {
|
|
id: notificationCenterExpandedLoader
|
|
|
|
activity: "notificationcenter"
|
|
active: root.controller.visualsRequested("notificationcenter")
|
|
asynchronous: true
|
|
sourceComponent: root.notificationCenterExpandedComponent
|
|
}
|
|
|
|
CompactFace {
|
|
active: root.surfaceActive("clipboard")
|
|
activity: "clipboard"
|
|
face: root.clipboardCompactComponent
|
|
}
|
|
|
|
ExpandedFace {
|
|
id: clipboardExpandedLoader
|
|
|
|
activity: "clipboard"
|
|
active: root.controller.visualsRequested("clipboard")
|
|
asynchronous: true
|
|
sourceComponent: root.clipboardExpandedComponent
|
|
}
|
|
|
|
CompactFace {
|
|
active: root.surfaceActive("powermenu")
|
|
activity: "powermenu"
|
|
face: root.powerMenuCompactComponent
|
|
}
|
|
|
|
ExpandedFace {
|
|
id: powerMenuExpandedLoader
|
|
|
|
activity: "powermenu"
|
|
active: root.controller.visualsRequested("powermenu")
|
|
asynchronous: true
|
|
sourceComponent: root.powerMenuExpandedComponent
|
|
}
|
|
|
|
CompactFace {
|
|
active: root.systemSurfaceActive
|
|
activity: "volume"
|
|
face: root.systemCompactComponent
|
|
}
|
|
|
|
ExpandedFace {
|
|
activity: "volume"
|
|
active: root.systemSurfaceActive && (root.expanded || root.expandedFade > 0)
|
|
asynchronous: false
|
|
sourceComponent: root.systemExpandedComponent
|
|
}
|
|
|
|
CompactFace {
|
|
active: root.surfaceActive("notification")
|
|
activity: "notification"
|
|
face: root.notificationCompactComponent
|
|
}
|
|
|
|
ExpandedFace {
|
|
activity: "notification"
|
|
active: root.surfaceActive("notification") && (root.expanded || root.expandedFade > 0)
|
|
asynchronous: false
|
|
sourceComponent: root.notificationExpandedComponent
|
|
}
|
|
}
|