island: general optimizations and cleanups

This commit is contained in:
bbedward
2026-08-23 11:46:02 -04:00
parent 5c9dd50c2e
commit 2c44b794f1
39 changed files with 763 additions and 2096 deletions
+1
View File
@@ -18,6 +18,7 @@ const DMS_ACTIONS = [
{ id: "spawn dms ipc call island open controlcenter", label: "DankIsland: Open Control Center" },
{ id: "spawn dms ipc call island open wallpaper", label: "DankIsland: Open Wallpapers" },
{ id: "spawn dms ipc call island open weather", label: "DankIsland: Open Weather" },
{ id: "spawn dms ipc call island open notificationcenter", label: "DankIsland: Open Notification Center" },
{ id: "spawn dms ipc call island cycle", label: "DankIsland: Cycle Activity" },
{ id: "spawn dms ipc call island close", label: "DankIsland: Close" },
{ id: "spawn dms ipc call defaultApp browser", label: "Default Web Browser: Open" },
@@ -520,6 +520,7 @@ var SPEC = {
notificationRules: { def: [] },
notificationDndAllowCritical: { def: true },
notificationFocusedMonitor: { def: false },
dankIslandBarId: { def: "", onChange: "updateBarConfigs" },
dankIslandFloating: { def: false },
dankIslandUseOverlayLayer: { def: false },
+13 -13
View File
@@ -217,7 +217,7 @@ Item {
IpcHandler {
function open(): string {
if (PopoutService.routeControlCenterToIsland(null, "", false))
if (PopoutService.routeToIsland("controlcenter", null, false, ""))
return "CONTROL_CENTER_OPEN_SUCCESS";
const bar = root.getPreferredBar("controlCenterButtonRef");
if (bar) {
@@ -228,7 +228,7 @@ Item {
}
function hide(): string {
if (PopoutService.dankIslandRouter?.closeControlCenter?.() === true)
if (PopoutService.closeIslandActivity("controlcenter"))
return "CONTROL_CENTER_HIDE_SUCCESS";
if (root.controlCenterLoader.item && root.controlCenterLoader.item.shouldBeVisible) {
root.controlCenterLoader.item.close();
@@ -242,7 +242,7 @@ Item {
root.controlCenterLoader.item.close();
return "CONTROL_CENTER_TOGGLE_SUCCESS";
}
if (PopoutService.routeControlCenterToIsland(null, "", true))
if (PopoutService.routeToIsland("controlcenter", null, true, ""))
return "CONTROL_CENTER_TOGGLE_SUCCESS";
const bar = root.getPreferredBar("controlCenterButtonRef");
@@ -313,13 +313,13 @@ Item {
function _openDash(tab, position) {
const tabId = _resolveTabId(tab);
if (!position) {
if (tabId === "overview" && PopoutService.routeOverviewToIsland(null, false))
if (tabId === "overview" && PopoutService.routeToIsland("home", null, false))
return true;
if (tabId === "media" && PopoutService.routeMediaToIsland(null, false))
if (tabId === "media" && PopoutService.routeToIsland("media", null, false))
return true;
if (tabId === "wallpaper" && PopoutService.routeWallpaperToIsland(null, false))
if (tabId === "wallpaper" && PopoutService.routeToIsland("wallpaper", null, false))
return true;
if (tabId === "weather" && PopoutService.routeWeatherToIsland(null, false))
if (tabId === "weather" && PopoutService.routeToIsland("weather", null, false))
return true;
}
@@ -348,13 +348,13 @@ Item {
const tabId = _resolveTabId(tab);
if (!position) {
if (tabId === "overview" && PopoutService.routeOverviewToIsland(null, true))
if (tabId === "overview" && PopoutService.routeToIsland("home", null, true))
return true;
if (tabId === "media" && PopoutService.routeMediaToIsland(null, true))
if (tabId === "media" && PopoutService.routeToIsland("media", null, true))
return true;
if (tabId === "wallpaper" && PopoutService.routeWallpaperToIsland(null, true))
if (tabId === "wallpaper" && PopoutService.routeToIsland("wallpaper", null, true))
return true;
if (tabId === "weather" && PopoutService.routeWeatherToIsland(null, true))
if (tabId === "weather" && PopoutService.routeToIsland("weather", null, true))
return true;
}
@@ -377,7 +377,7 @@ Item {
}
function close(): string {
if (PopoutService.closeIslandHome() || PopoutService.closeIslandMedia() || PopoutService.closeIslandWallpaper() || PopoutService.closeIslandWeather())
if (PopoutService.closeIslandActivity("home") || PopoutService.closeIslandActivity("media") || PopoutService.closeIslandActivity("wallpaper") || PopoutService.closeIslandActivity("weather"))
return "DASH_CLOSE_SUCCESS";
if (root.dankDashPopoutLoader.item) {
root.dankDashPopoutLoader.item.dashVisible = false;
@@ -785,7 +785,7 @@ Item {
// ! TODO - remove for v1.6
IpcHandler {
function wallpaper(): string {
if (PopoutService.routeWallpaperToIsland(null, true))
if (PopoutService.routeToIsland("wallpaper", null, true))
return "WARN; deprecated, use dms ipc call dash toggle wallpaper instead";
const bar = root.getPreferredBar("clockButtonRef") || root.getPreferredBar();
if (bar) {
@@ -1,3 +1,5 @@
pragma ComponentBehavior: Bound
import QtQuick
import qs.Services
@@ -98,10 +100,12 @@ Item {
return;
if (spotlightOpen) {
_islandWasOpen = true;
} else if (_islandWasOpen) {
_islandWasOpen = false;
dialogClosed();
return;
}
if (!_islandWasOpen)
return;
_islandWasOpen = false;
dialogClosed();
}
DankLauncherV2ModalSpotlight {
+3 -3
View File
@@ -123,21 +123,21 @@ DankModal {
IpcHandler {
// DankIsland owns this trigger whenever it is the sole bar for the screen.
function open(): string {
if (PopoutService.routeNotificationCenterToIsland(null, false))
if (PopoutService.routeToIsland("notificationcenter", null, false))
return "NOTIFICATION_ISLAND_OPEN_SUCCESS";
notificationModal.show();
return "NOTIFICATION_MODAL_OPEN_SUCCESS";
}
function close(): string {
if (PopoutService.closeIslandNotificationCenter())
if (PopoutService.closeIslandActivity("notificationcenter"))
return "NOTIFICATION_ISLAND_CLOSE_SUCCESS";
notificationModal.hide();
return "NOTIFICATION_MODAL_CLOSE_SUCCESS";
}
function toggle(): string {
if (PopoutService.routeNotificationCenterToIsland(null, true))
if (PopoutService.routeToIsland("notificationcenter", null, true))
return "NOTIFICATION_ISLAND_TOGGLE_SUCCESS";
notificationModal.toggle();
return "NOTIFICATION_MODAL_TOGGLE_SUCCESS";
@@ -7,7 +7,6 @@ import qs.Modules.ControlCenter.Models
import qs.Modules.ControlCenter.Details
import qs.Widgets
// Host-agnostic Control Center body; popout and island both host this.
Rectangle {
id: root
@@ -4,7 +4,6 @@ import QtQuick
import Quickshell.Services.Mpris
import Quickshell.Widgets
import qs.Common
import qs.Modules.DankIsland.Activities
import qs.Services
import qs.Widgets
@@ -50,7 +50,6 @@ Item {
id: numberGlyph
anchors.verticalCenter: parent.verticalCenter
// Digits have no descenders; sink by half the descent to optically center.
anchors.verticalCenterOffset: Math.round(digitMetrics.descent / 2)
text: root.numberText
color: glyphs.glyphColor
@@ -72,7 +71,11 @@ Item {
width: root.bodyWidth
height: root.thickness
radius: Math.round(Math.min(width, height) * 0.34)
color: root.outlined ? (root.hovered ? Theme.withAlpha(Theme.surfaceVariant, 0.45) : "transparent") : (root.hovered ? Theme.surfaceVariant : Theme.withAlpha(Theme.surfaceVariant, 0.9))
color: {
if (root.outlined)
return root.hovered ? Theme.withAlpha(Theme.surfaceVariant, 0.45) : "transparent";
return root.hovered ? Theme.surfaceVariant : Theme.withAlpha(Theme.surfaceVariant, 0.9);
}
border.width: root.outlined ? 1.2 : 0
border.color: root.fillColor
@@ -5,7 +5,6 @@ import qs.Common
import qs.Modules.ControlCenter
import qs.Services
// Shared Control Center body hosted in the island silhouette.
FocusScope {
id: root
@@ -17,7 +16,6 @@ FocusScope {
property real alignedY: 0
property real alignedWidth: 0
property real alignedHeight: 0
// Report content height once per turn; the controller clamps it before retargeting the spring.
property real bottomInset: Theme.spacingM
property bool _heightReportPending: false
@@ -46,7 +44,7 @@ FocusScope {
root._heightReportPending = true;
Qt.callLater(() => {
root._heightReportPending = false;
root.islandController.setControlCenterContentHeight(content.targetImplicitHeight + Theme.spacingXS + root.bottomInset);
root.islandController.setDestinationContentHeight("controlcenter", content.targetImplicitHeight + Theme.spacingXS + root.bottomInset);
});
}
@@ -59,7 +57,6 @@ FocusScope {
property var expandedWidgetData: null
readonly property bool shouldBeVisible: root.islandController.activeActivity === "controlcenter" && root.islandController.expanded
// The island has no outside chrome to click, so the header row closes it.
readonly property bool headerTogglesClose: true
readonly property bool powerMenuOpen: root.powerMenuModalLoader?.item?.shouldBeVisible ?? false
readonly property var screen: root.effectiveScreen
@@ -111,7 +108,6 @@ FocusScope {
root.releaseScanState();
}
// Hot-unplug skips the visibility path; release scans here too.
Component.onDestruction: {
if (hostContract.shouldBeVisible)
root.releaseScanState();
@@ -156,8 +152,9 @@ FocusScope {
Connections {
target: root.islandController
function onControlCenterSessionSerialChanged() {
Qt.callLater(root.beginSession);
function onSessionStarted(activityId) {
if (activityId === "controlcenter")
Qt.callLater(root.beginSession);
}
function onExpandedChanged() {
@@ -171,5 +168,5 @@ FocusScope {
}
}
Component.onCompleted: root.islandController.markControlCenterVisualsReady()
Component.onCompleted: root.islandController.markVisualsReady("controlcenter")
}
@@ -139,7 +139,7 @@ Item {
id: weatherRow
anchors.centerIn: parent
spacing: 2
spacing: Theme.spacingXXS
visible: slot.isWeather
DankIcon {
@@ -213,11 +213,10 @@ Item {
anchors.verticalCenter: parent.verticalCenter
spacing: root.tight ? Theme.spacingXS : Theme.spacingS
Text {
StyledText {
anchors.verticalCenter: parent.verticalCenter
text: root.timeText
color: Theme.surfaceText
font.family: Theme.fontFamily
font.pixelSize: root.tight ? Theme.fontSizeSmall : Theme.fontSizeMedium
font.weight: Font.DemiBold
}
@@ -230,13 +229,12 @@ Item {
color: Theme.primary
}
Text {
StyledText {
id: dateLabel
anchors.verticalCenter: parent.verticalCenter
text: root.dateText
color: Theme.surfaceTextMedium
font.family: Theme.fontFamily
font.pixelSize: Theme.fontSizeSmall
font.weight: Font.Medium
}
@@ -2,7 +2,6 @@ pragma ComponentBehavior: Bound
import QtQuick
// Compact-face slot click target; hover here suppresses island hover-expand.
MouseArea {
id: root
@@ -86,11 +86,12 @@ FocusScope {
Connections {
target: root.islandController
function onLauncherSessionSerialChanged() {
Qt.callLater(root.initializeSession);
function onSessionStarted(activityId) {
if (activityId === "launcher")
Qt.callLater(root.initializeSession);
}
}
Component.onCompleted: root.islandController.markLauncherVisualsReady()
Component.onCompleted: root.islandController.markVisualsReady("launcher")
Component.onDestruction: root.islandController.launcherInputFocused = false
}
@@ -43,28 +43,20 @@ Item {
bandsB = nextB;
}
onLiveChanged: {
if (live)
updateBands();
else
function syncBands() {
if (!live) {
applyIdleBands();
}
onAvailableChanged: {
if (!available)
return;
if (live)
updateBands();
else
applyIdleBands();
}
updateBands();
}
Component.onCompleted: {
if (root.live)
updateBands();
else
applyIdleBands();
onLiveChanged: syncBands()
onAvailableChanged: {
if (available)
syncBands();
}
Component.onCompleted: syncBands()
Loader {
active: root.live && root.available
@@ -4,6 +4,7 @@ import QtQuick
import Quickshell
import qs.Common
import qs.Services
import qs.Widgets
Item {
id: root
@@ -35,8 +36,6 @@ Item {
TextMetrics {
id: titleMetrics
font.family: Theme.fontFamily
font.pixelSize: Theme.fontSizeSmall
font.weight: Font.DemiBold
text: root.mediaModel.title
@@ -44,8 +43,6 @@ Item {
TextMetrics {
id: artistMetrics
font.family: Theme.fontFamily
font.pixelSize: Math.max(9, Theme.fontSizeSmall - 2)
text: root.dense ? "" : root.mediaModel.artist
}
@@ -104,7 +101,7 @@ Item {
height: titleText.implicitHeight
clip: true
Text {
StyledText {
id: titleText
readonly property bool onScreen: Window.window?.visible ?? false
@@ -153,7 +150,6 @@ Item {
x: needsScrolling ? -scrollOffset : 0
text: root.mediaModel.title
color: Theme.surfaceText
font.family: Theme.fontFamily
font.pixelSize: Theme.fontSizeSmall
font.weight: Font.DemiBold
wrapMode: Text.NoWrap
@@ -186,13 +182,12 @@ Item {
}
}
Text {
StyledText {
x: root.textGutter
width: parent.width - root.textGutter * 2
visible: !root.dense
text: root.mediaModel.artist
color: Theme.surfaceTextSecondary
font.family: Theme.fontFamily
font.pixelSize: Math.max(9, Theme.fontSizeSmall - 2)
elide: Text.ElideRight
}
@@ -218,13 +213,12 @@ Item {
radius: height / 2
color: clockArea.containsMouse ? Theme.surfaceTextHover : "transparent"
Text {
StyledText {
id: clockText
anchors.centerIn: parent
text: root.timeText
color: Theme.surfaceText
font.family: Theme.fontFamily
font.pixelSize: Theme.fontSizeSmall
font.weight: Font.DemiBold
}
@@ -5,7 +5,6 @@ import qs.Common
import qs.Modules.Notifications.Center
import qs.Services
// Shared notification center body hosted in the island silhouette.
FocusScope {
id: root
@@ -28,7 +27,6 @@ FocusScope {
if (!hostContract.shouldBeVisible)
return;
root._sessionOpen = true;
// Suppresses transient island cards for as long as the list owns them.
NotificationService.onOverlayOpen();
keyboardController.reset();
keyboardController.listView = content.notificationList;
@@ -56,14 +54,14 @@ FocusScope {
Qt.callLater(() => {
root._heightReportPending = false;
content.notificationList.syncSessionContentHeight();
root.islandController.setNotificationCenterContentHeight(content.targetImplicitHeight + root.bottomInset);
root.islandController.setDestinationContentHeight("notificationcenter", content.targetImplicitHeight + root.bottomInset);
});
}
function prepareVisuals() {
content.notificationList.syncSessionContentHeight();
root.islandController.setNotificationCenterContentHeight(content.targetImplicitHeight + root.bottomInset);
root.islandController.markNotificationCenterVisualsReady();
root.islandController.setDestinationContentHeight("notificationcenter", content.targetImplicitHeight + root.bottomInset);
root.islandController.markVisualsReady("notificationcenter");
}
QtObject {
@@ -116,8 +114,9 @@ FocusScope {
Connections {
target: root.islandController
function onNotificationCenterSessionSerialChanged() {
Qt.callLater(root.beginSession);
function onSessionStarted(activityId) {
if (activityId === "notificationcenter")
Qt.callLater(root.beginSession);
}
function onExpandedChanged() {
@@ -43,23 +43,20 @@ Item {
width: parent.width - root.iconSize - parent.spacing - (root.notificationModel.critical ? 3 + parent.spacing : 0)
spacing: 1
// A dense pill keeps one line, so the app name folds into the summary.
Text {
StyledText {
width: parent.width
visible: !root.dense
text: root.notificationModel.appName + (root.notificationModel.timeText ? " · " + root.notificationModel.timeText : "")
color: Theme.surfaceTextSecondary
font.family: Theme.fontFamily
font.pixelSize: Theme.fontSizeSmall
font.weight: Font.Medium
elide: Text.ElideRight
}
Text {
StyledText {
width: parent.width
text: root.dense && root.notificationModel.appName ? root.notificationModel.appName + " · " + root.notificationModel.summary : root.notificationModel.summary
color: Theme.surfaceText
font.family: Theme.fontFamily
font.pixelSize: root.dense ? Theme.fontSizeSmall : Theme.fontSizeMedium
font.weight: Font.DemiBold
elide: Text.ElideRight
@@ -39,52 +39,34 @@ Item {
Column {
anchors.verticalCenter: parent.verticalCenter
width: parent.width - 46 - closeButton.width - parent.spacing * 2
spacing: 2
spacing: Theme.spacingXXS
Text {
StyledText {
width: parent.width
text: root.notificationModel.appName
color: Theme.surfaceText
font.family: Theme.fontFamily
font.pixelSize: Theme.fontSizeMedium
font.weight: Font.DemiBold
elide: Text.ElideRight
}
Text {
StyledText {
width: parent.width
text: root.notificationModel.timeText
color: Theme.surfaceTextSecondary
font.family: Theme.fontFamily
font.pixelSize: Theme.fontSizeSmall
elide: Text.ElideRight
}
}
Rectangle {
DankActionButton {
id: closeButton
anchors.verticalCenter: parent.verticalCenter
width: 38
height: 38
radius: 19
color: closeArea.containsMouse ? Theme.surfaceTextHover : "transparent"
DankIcon {
anchors.centerIn: parent
name: "close"
size: 21
color: Theme.surfaceText
}
MouseArea {
id: closeArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: root.notificationModel.dismiss()
}
iconName: "close"
iconSize: 21
buttonSize: 38
onClicked: root.notificationModel.dismiss()
}
}
@@ -99,11 +81,10 @@ Item {
}
spacing: Theme.spacingS
Text {
StyledText {
width: parent.width
text: root.notificationModel.summary
color: Theme.surfaceText
font.family: Theme.fontFamily
font.pixelSize: Theme.fontSizeLarge
font.weight: Font.DemiBold
wrapMode: Text.WrapAtWordBoundaryOrAnywhere
@@ -111,11 +92,10 @@ Item {
elide: Text.ElideRight
}
Text {
StyledText {
width: parent.width
text: root.notificationModel.body
color: Theme.surfaceTextMedium
font.family: Theme.fontFamily
font.pixelSize: Theme.fontSizeSmall
wrapMode: Text.WrapAtWordBoundaryOrAnywhere
maximumLineCount: 3
@@ -134,61 +114,29 @@ Item {
height: 34
spacing: Theme.spacingS
Rectangle {
width: dismissLabel.implicitWidth + Theme.spacingL * 2
DankButton {
height: parent.height
radius: height / 2
color: dismissArea.containsMouse ? Theme.surfaceTextHover : "transparent"
text: I18n.tr("Dismiss")
backgroundColor: "transparent"
textColor: Theme.surfaceText
buttonHeight: parent.height
horizontalPadding: Theme.spacingL
border.width: 1
border.color: Theme.withAlpha(Theme.outline, 0.5)
Text {
id: dismissLabel
anchors.centerIn: parent
text: "Dismiss"
color: Theme.surfaceText
font.family: Theme.fontFamily
font.pixelSize: Theme.fontSizeSmall
font.weight: Font.Medium
}
MouseArea {
id: dismissArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: root.notificationModel.dismiss()
}
onClicked: root.notificationModel.dismiss()
}
Rectangle {
DankButton {
visible: root.notificationModel.hasAction
width: actionLabel.implicitWidth + Theme.spacingL * 2
height: parent.height
radius: height / 2
color: actionArea.containsMouse ? Theme.primaryContainer : Theme.primary
Text {
id: actionLabel
anchors.centerIn: parent
text: root.notificationModel.actionLabel
color: actionArea.containsMouse ? Theme.primary : Theme.onPrimary
font.family: Theme.fontFamily
font.pixelSize: Theme.fontSizeSmall
font.weight: Font.DemiBold
}
MouseArea {
id: actionArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: root.notificationModel.activate()
}
text: root.notificationModel.actionLabel
backgroundColor: Theme.primary
textColor: Theme.onPrimary
buttonHeight: parent.height
horizontalPadding: Theme.spacingL
onClicked: root.notificationModel.activate()
}
}
}
@@ -12,7 +12,7 @@ Item {
Column {
anchors {
fill: parent
margins: 24
margins: Theme.spacingXL
}
spacing: Theme.spacingL
@@ -44,85 +44,50 @@ Item {
Column {
anchors.verticalCenter: parent.verticalCenter
width: parent.width - 48 - valueText.width - parent.spacing * 2
spacing: 2
spacing: Theme.spacingXXS
Text {
StyledText {
text: root.systemModel.title
color: Theme.surfaceText
font.family: Theme.fontFamily
font.pixelSize: Theme.fontSizeLarge
font.weight: Font.DemiBold
}
Text {
text: root.systemModel.volumeActivity ? "Click the icon to mute" : "Display level"
StyledText {
text: root.systemModel.volumeActivity ? I18n.tr("Click the icon to mute") : I18n.tr("Display level")
color: Theme.surfaceTextSecondary
font.family: Theme.fontFamily
font.pixelSize: Theme.fontSizeSmall
}
}
Text {
StyledText {
id: valueText
anchors.verticalCenter: parent.verticalCenter
text: root.systemModel.displayValue
color: Theme.primary
font.family: Theme.fontFamily
font.pixelSize: 28
font.weight: Font.DemiBold
}
}
Item {
id: slider
DankSlider {
id: levelSlider
width: parent.width
height: 40
minimum: root.systemModel.minimum
maximum: Math.max(root.systemModel.minimum + 1, Math.round(root.systemModel.maximum))
enabled: root.systemModel.available
showValue: false
unit: root.systemModel.unit
thumbOutlineColor: Theme.surfaceContainerHigh
valueOverride: Math.round(root.systemModel.value)
onSliderValueChanged: newValue => root.systemModel.setRatio(newValue / maximum)
Rectangle {
id: track
anchors.verticalCenter: parent.verticalCenter
width: parent.width
height: 12
radius: 6
color: Theme.withAlpha(Theme.surfaceText, 0.14)
Rectangle {
width: parent.width * root.systemModel.ratio
height: parent.height
radius: parent.radius
color: Theme.primary
}
Rectangle {
x: Math.max(0, Math.min(parent.width - width, parent.width * root.systemModel.ratio - width / 2))
anchors.verticalCenter: parent.verticalCenter
width: 22
height: 22
radius: 11
color: Theme.primary
border.width: 3
border.color: Theme.surfaceContainerHigh
}
}
MouseArea {
anchors.fill: parent
enabled: root.systemModel.available
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
function updateValue(mouse) {
root.systemModel.setRatio(mouse.x / width);
}
onPressed: mouse => updateValue(mouse)
onPositionChanged: mouse => {
if (pressed)
updateValue(mouse);
}
Binding on value {
value: Math.round(root.systemModel.value)
when: !levelSlider.isDragging
}
}
}
@@ -81,18 +81,19 @@ FocusScope {
Connections {
target: root.islandController
function onWallpaperSessionSerialChanged() {
Qt.callLater(root.beginSession);
function onSessionStarted(activityId) {
if (activityId === "wallpaper")
Qt.callLater(root.beginSession);
}
}
Component.onCompleted: {
root.contentStaged = true;
root.islandController.markWallpaperVisualsReady();
root.islandController.markVisualsReady("wallpaper");
}
Component.onDestruction: {
root.islandController.keyboardYielded = false;
root.islandController.wallpaperVisualsReady = false;
root.islandController.setVisualsReady("wallpaper", false);
}
}
@@ -60,10 +60,10 @@ FocusScope {
Component.onCompleted: {
root.contentStaged = true;
root.islandController.markWeatherVisualsReady();
root.islandController.markVisualsReady("weather");
}
Component.onDestruction: {
root.islandController.weatherVisualsReady = false;
root.islandController.setVisualsReady("weather", false);
}
}
+150 -339
View File
@@ -16,79 +16,95 @@ Item {
readonly property bool launcherOpen: root.activityOpen("launcher")
readonly property bool controlCenterOpen: root.activityOpen("controlcenter")
readonly property bool wallpaperOpen: root.activityOpen("wallpaper")
readonly property bool weatherOpen: root.activityOpen("weather")
readonly property bool mediaOpen: root.activityOpen("media")
readonly property bool homeOpen: root.activityOpen("home")
readonly property bool notificationCenterOpen: root.activityOpen("notificationcenter")
function activityOpen(activityId) {
const hosts = islandVariants.instances || [];
for (let i = 0; i < hosts.length; i++) {
if (hosts[i]?.islandController?.activeActivity === activityId && hosts[i].islandController.expanded)
return true;
}
return false;
function hosts() {
return islandVariants.instances || [];
}
function hostWithActivity(activityId) {
const hosts = islandVariants.instances || [];
for (let i = 0; i < hosts.length; i++) {
if (hosts[i]?.islandController?.activeActivity === activityId && hosts[i].islandController.expanded)
return hosts[i];
for (const host of hosts()) {
if (host?.islandController?.activeActivity === activityId && host.islandController.expanded)
return host;
}
return null;
}
function activityOpen(activityId) {
return root.hostWithActivity(activityId) !== null;
}
function hostForExactScreen(screen) {
if (!screen)
return null;
for (const host of hosts()) {
if (host?.screen === screen || host?.screen?.name === screen.name)
return host;
}
return null;
}
function hostForScreenName(screenName) {
for (const host of hosts()) {
if (host?.screen?.name === screenName)
return host;
}
return null;
}
function focusedHost() {
const focusedName = CompositorService.getFocusedScreen()?.name ?? "";
return focusedName ? root.hostForScreenName(focusedName) : null;
}
function focusedIslandScreen() {
return root.focusedHost()?.screen ?? null;
}
function hasHostForScreen(screen) {
if (!screen)
return (islandVariants.instances || []).length > 0;
const hosts = islandVariants.instances || [];
for (let i = 0; i < hosts.length; i++) {
if (hosts[i]?.screen === screen || hosts[i]?.screen?.name === screen.name)
return true;
}
return false;
return hosts().length > 0;
return root.hostForExactScreen(screen) !== null;
}
function hostForScreenOrFocused(screen) {
return root.hostForExactScreen(screen) ?? root.focusedHost();
}
function hostForScreen(screenName) {
const requestedName = (screenName || "").trim();
const hosts = islandVariants.instances || [];
// An explicitly named screen with no island is an error, not a redirect to another display.
if (requestedName) {
for (let i = 0; i < hosts.length; i++) {
if (hosts[i]?.screen?.name === requestedName)
return hosts[i];
}
return null;
}
const focusedName = CompositorService.getFocusedScreen()?.name ?? "";
if (focusedName) {
for (let i = 0; i < hosts.length; i++) {
if (hosts[i]?.screen?.name === focusedName)
return hosts[i];
}
}
return hosts.length > 0 ? hosts[0] : null;
if (requestedName)
return root.hostForScreenName(requestedName);
return root.focusedHost() ?? (hosts()[0] ?? null);
}
function activityName(activity) {
const requested = (activity || "home").trim().toLowerCase();
if (requested === "media" || requested === "launcher" || requested === "controlcenter" || requested === "wallpaper" || requested === "weather" || requested === "notificationcenter")
switch (requested) {
case "media":
case "launcher":
case "controlcenter":
case "wallpaper":
case "weather":
case "notificationcenter":
return requested;
if (requested === "control-center" || requested === "cc")
case "control-center":
case "cc":
return "controlcenter";
if (requested === "notifications" || requested === "notification-center" || requested === "notification" || requested === "nc")
case "notifications":
case "notification-center":
case "notification":
case "nc":
return "notificationcenter";
}
return "home";
}
function openActivityOn(host, activityId) {
function openActivityOn(host, activityId, section) {
switch (activityId) {
case "launcher":
return host.islandController.requestLauncher("", "", false);
case "controlcenter":
return host.islandController.requestControlCenter("", false);
return host.islandController.requestControlCenter(section || "", false);
case "wallpaper":
return host.islandController.requestWallpaper(false);
case "weather":
@@ -99,24 +115,26 @@ Item {
return host.islandController.requestActivity(activityId, true, true);
}
function focusedHost() {
const focusedName = CompositorService.getFocusedScreen()?.name ?? "";
if (!focusedName)
return null;
const hosts = islandVariants.instances || [];
for (let i = 0; i < hosts.length; i++) {
if (hosts[i]?.screen?.name === focusedName)
return hosts[i];
function openActivity(activityId, screen, section): bool {
const host = root.hostForScreenOrFocused(screen);
return host ? root.openActivityOn(host, activityId, section) === true : false;
}
function toggleActivity(activityId, screen, section): bool {
const openHost = root.hostWithActivity(activityId);
if (openHost) {
openHost.islandController.requestCollapse();
return true;
}
return null;
return root.openActivity(activityId, screen, section);
}
function focusedIslandScreen() {
return root.focusedHost()?.screen ?? null;
}
function activeLauncherHost() {
return root.hostWithActivity("launcher");
function closeActivity(activityId): bool {
const host = root.hostWithActivity(activityId);
if (!host)
return false;
host.islandController.requestCollapse();
return true;
}
function openLauncher(query, mode): bool {
@@ -125,219 +143,86 @@ Item {
}
function toggleLauncher(query, mode): bool {
const openHost = root.activeLauncherHost();
if (openHost) {
openHost.islandController.requestCollapse();
if (root.closeActivity("launcher"))
return true;
}
const host = root.focusedHost();
return host ? host.islandController.requestLauncher(query || "", mode || "", false) : false;
return root.openLauncher(query, mode);
}
function closeLauncher(): bool {
const host = root.activeLauncherHost();
return root.closeActivity("launcher");
}
function ipcOpen(activity, screen) {
const host = root.hostForScreen(screen);
if (!host)
return false;
host.islandController.requestCollapse();
return true;
return "DANK_ISLAND_UNAVAILABLE";
const requested = root.activityName(activity);
if (!root.openActivityOn(host, requested, ""))
return `DANK_ISLAND_ACTIVITY_UNAVAILABLE: ${requested}`;
return `DANK_ISLAND_OPEN: ${requested}\t${host.screen?.name ?? ""}`;
}
function controlCenterHostForScreen(screen) {
if (screen) {
const hosts = islandVariants.instances || [];
for (let i = 0; i < hosts.length; i++) {
if (hosts[i]?.screen === screen || hosts[i]?.screen?.name === screen.name)
return hosts[i];
}
}
return root.focusedHost();
}
function openControlCenter(section, screen): bool {
const host = root.controlCenterHostForScreen(screen);
return host ? host.islandController.requestControlCenter(section || "", false) : false;
}
function toggleControlCenter(section, screen): bool {
const openHost = root.hostWithActivity("controlcenter");
if (openHost) {
openHost.islandController.requestCollapse();
return true;
}
return root.openControlCenter(section, screen);
}
function closeControlCenter(): bool {
const host = root.hostWithActivity("controlcenter");
function ipcToggle(activity, screen) {
const host = root.hostForScreen(screen);
if (!host)
return false;
host.islandController.requestCollapse();
return true;
}
function wallpaperHostForScreen(screen) {
if (screen) {
const hosts = islandVariants.instances || [];
for (let i = 0; i < hosts.length; i++) {
if (hosts[i]?.screen === screen || hosts[i]?.screen?.name === screen.name)
return hosts[i];
}
return "DANK_ISLAND_UNAVAILABLE";
if (host.islandController.expanded && !host.islandController.notificationActive) {
host.islandController.requestCollapse();
return `DANK_ISLAND_CLOSED: ${host.screen?.name ?? ""}`;
}
return root.focusedHost();
return ipcOpen(activity, screen);
}
function openWallpaper(screen): bool {
const host = root.wallpaperHostForScreen(screen);
return host ? host.islandController.requestWallpaper(false) : false;
}
function toggleWallpaper(screen): bool {
const openHost = root.hostWithActivity("wallpaper");
if (openHost) {
openHost.islandController.requestCollapse();
return true;
}
return root.openWallpaper(screen);
}
function closeWallpaper(): bool {
const host = root.hostWithActivity("wallpaper");
function ipcShow(activity, screen) {
const host = root.hostForScreen(screen);
if (!host)
return false;
host.islandController.requestCollapse();
return true;
return "DANK_ISLAND_UNAVAILABLE";
const requested = root.activityName(activity);
if (!host.islandController.requestActivity(requested, false, false))
return `DANK_ISLAND_ACTIVITY_UNAVAILABLE: ${requested}`;
return `DANK_ISLAND_SHOW: ${requested}\t${host.screen?.name ?? ""}`;
}
function weatherHostForScreen(screen) {
if (screen) {
const hosts = islandVariants.instances || [];
for (let i = 0; i < hosts.length; i++) {
if (hosts[i]?.screen === screen || hosts[i]?.screen?.name === screen.name)
return hosts[i];
}
}
return root.focusedHost();
}
function openWeather(screen): bool {
const host = root.weatherHostForScreen(screen);
return host ? host.islandController.requestWeather(false) : false;
}
function toggleWeather(screen): bool {
const openHost = root.hostWithActivity("weather");
if (openHost) {
openHost.islandController.requestCollapse();
return true;
}
return root.openWeather(screen);
}
function closeWeather(): bool {
const host = root.hostWithActivity("weather");
function ipcClose(screen) {
const host = root.hostForScreen(screen);
if (!host)
return false;
return "DANK_ISLAND_UNAVAILABLE";
host.islandController.requestCollapse();
return true;
return `DANK_ISLAND_CLOSED: ${host.screen?.name ?? ""}`;
}
function notificationHostForScreen(screen) {
if (screen) {
const hosts = islandVariants.instances || [];
for (let i = 0; i < hosts.length; i++) {
if (hosts[i]?.screen === screen || hosts[i]?.screen?.name === screen.name)
return hosts[i];
}
}
return root.focusedHost();
}
function openNotifications(screen): bool {
const host = root.notificationHostForScreen(screen);
return host ? host.islandController.requestNotificationCenter(false) : false;
}
function toggleNotifications(screen): bool {
const openHost = root.hostWithActivity("notificationcenter");
if (openHost) {
openHost.islandController.requestCollapse();
return true;
}
return root.openNotifications(screen);
}
function closeNotifications(): bool {
const host = root.hostWithActivity("notificationcenter");
function ipcCycle(screen) {
const host = root.hostForScreen(screen);
if (!host)
return false;
host.islandController.requestCollapse();
return true;
return "DANK_ISLAND_UNAVAILABLE";
host.islandController.cycleActivity(1, host.islandController.expanded);
return `DANK_ISLAND_ACTIVITY: ${host.islandController.activeActivity}\t${host.screen?.name ?? ""}`;
}
function mediaHostForScreen(screen) {
if (screen) {
const hosts = islandVariants.instances || [];
for (let i = 0; i < hosts.length; i++) {
if (hosts[i]?.screen === screen || hosts[i]?.screen?.name === screen.name)
return hosts[i];
}
}
return root.focusedHost();
}
function openMedia(screen): bool {
const host = root.mediaHostForScreen(screen);
return host ? host.islandController.requestActivity("media", true, true) : false;
}
function toggleMedia(screen): bool {
const openHost = root.hostWithActivity("media");
if (openHost) {
openHost.islandController.requestCollapse();
return true;
}
return root.openMedia(screen);
}
function closeMedia(): bool {
const host = root.hostWithActivity("media");
function ipcStatus(screen) {
const host = root.hostForScreen(screen);
if (!host)
return false;
host.islandController.requestCollapse();
return true;
}
function homeHostForScreen(screen) {
if (screen) {
const hosts = islandVariants.instances || [];
for (let i = 0; i < hosts.length; i++) {
if (hosts[i]?.screen === screen || hosts[i]?.screen?.name === screen.name)
return hosts[i];
}
}
return root.focusedHost();
}
function openHome(screen): bool {
const host = root.homeHostForScreen(screen);
return host ? host.islandController.requestActivity("home", true, true) : false;
}
function toggleHome(screen): bool {
const openHost = root.hostWithActivity("home");
if (openHost) {
openHost.islandController.requestCollapse();
return true;
}
return root.openHome(screen);
}
function closeHome(): bool {
const host = root.hostWithActivity("home");
if (!host)
return false;
host.islandController.requestCollapse();
return true;
return JSON.stringify({
"available": false,
"enabled": true,
"launcherAvailable": false
});
return JSON.stringify({
"available": true,
"enabled": true,
"screen": host.screen?.name ?? "",
"activity": host.islandController.activeActivity,
"expanded": host.islandController.expanded,
"mediaAvailable": host.islandController.mediaAvailable,
"launcherAvailable": true,
"controlCenterAvailable": true,
"wallpaperAvailable": true,
"weatherAvailable": true,
"notificationCenterAvailable": true,
"launcherInputFocused": host.islandController.launcherInputFocused,
"launcherResultCount": host.launcherResultCount,
"compactHeight": host.islandController.compactHeight
});
}
Component.onCompleted: PopoutService.dankIslandRouter = root
@@ -364,134 +249,60 @@ Item {
IpcHandler {
target: "island"
function _runOpen(activity, screen) {
const host = root.hostForScreen(screen);
if (!host)
return "DANK_ISLAND_UNAVAILABLE";
const requested = root.activityName(activity);
const accepted = root.openActivityOn(host, requested);
if (!accepted)
return `DANK_ISLAND_ACTIVITY_UNAVAILABLE: ${requested}`;
return `DANK_ISLAND_OPEN: ${requested}\t${host.screen?.name ?? ""}`;
}
function _runToggle(activity, screen) {
const host = root.hostForScreen(screen);
if (!host)
return "DANK_ISLAND_UNAVAILABLE";
if (host.islandController.expanded && !host.islandController.notificationActive) {
host.islandController.requestCollapse();
return `DANK_ISLAND_CLOSED: ${host.screen?.name ?? ""}`;
}
return _runOpen(activity, screen);
}
function _runShow(activity, screen) {
const host = root.hostForScreen(screen);
if (!host)
return "DANK_ISLAND_UNAVAILABLE";
const requested = root.activityName(activity);
if (!host.islandController.requestActivity(requested, false, false))
return `DANK_ISLAND_ACTIVITY_UNAVAILABLE: ${requested}`;
return `DANK_ISLAND_SHOW: ${requested}\t${host.screen?.name ?? ""}`;
}
function _runClose(screen) {
const host = root.hostForScreen(screen);
if (!host)
return "DANK_ISLAND_UNAVAILABLE";
host.islandController.requestCollapse();
return `DANK_ISLAND_CLOSED: ${host.screen?.name ?? ""}`;
}
function _runCycle(screen) {
const host = root.hostForScreen(screen);
if (!host)
return "DANK_ISLAND_UNAVAILABLE";
host.islandController.cycleActivity(1, host.islandController.expanded);
return `DANK_ISLAND_ACTIVITY: ${host.islandController.activeActivity}\t${host.screen?.name ?? ""}`;
}
function _runStatus(screen) {
const host = root.hostForScreen(screen);
if (!host)
return JSON.stringify({
"available": false,
"enabled": true,
"launcherAvailable": false
});
return JSON.stringify({
"available": true,
"enabled": true,
"screen": host.screen?.name ?? "",
"activity": host.islandController.activeActivity,
"expanded": host.islandController.expanded,
"mediaAvailable": host.islandController.mediaAvailable,
"launcherAvailable": true,
"controlCenterAvailable": true,
"wallpaperAvailable": true,
"weatherAvailable": true,
"notificationCenterAvailable": true,
"launcherInputFocused": host.islandController.launcherInputFocused,
"launcherResultCount": host.launcherResultCount,
"compactHeight": host.islandController.compactHeight
});
}
function open(activity: string): string {
return _runOpen(activity, "");
return root.ipcOpen(activity, "");
}
function toggle(activity: string): string {
return _runToggle(activity, "");
return root.ipcToggle(activity, "");
}
function show(activity: string): string {
return _runShow(activity, "");
return root.ipcShow(activity, "");
}
function close(): string {
return _runClose("");
return root.ipcClose("");
}
function cycle(): string {
return _runCycle("");
return root.ipcCycle("");
}
function status(): string {
return _runStatus("");
return root.ipcStatus("");
}
function openOn(activity: string, screen: string): string {
return _runOpen(activity, screen);
return root.ipcOpen(activity, screen);
}
function toggleOn(activity: string, screen: string): string {
return _runToggle(activity, screen);
return root.ipcToggle(activity, screen);
}
function showOn(activity: string, screen: string): string {
return _runShow(activity, screen);
return root.ipcShow(activity, screen);
}
function closeOn(screen: string): string {
return _runClose(screen);
return root.ipcClose(screen);
}
function cycleOn(screen: string): string {
return _runCycle(screen);
return root.ipcCycle(screen);
}
function statusOn(screen: string): string {
return _runStatus(screen);
return root.ipcStatus(screen);
}
function notifications(): string {
return _runToggle("notificationcenter", "");
return root.ipcToggle("notificationcenter", "");
}
function notificationsOn(screen: string): string {
return _runToggle("notificationcenter", screen);
return root.ipcToggle("notificationcenter", screen);
}
}
}
@@ -48,14 +48,13 @@ PanelWindow {
readonly property var islandLayer: LayerShell.fromEnv("DMS_DANKISLAND_LAYER", usesOverlayLayer ? WlrLayer.Overlay : WlrLayer.Top)
readonly property bool bottomEdge: SettingsData.dankIslandEdge === "bottom"
readonly property int reservedStripHeight: Math.max(reserveHeight, outerGap + compactHeight)
// Input bands hug the anchored edge, which is the far side of the host when anchored bottom.
readonly property int stripY: root.bottomEdge ? root.height - root.reservedStripHeight : 0
// Screen-space Y of this strip's local origin. Zero on top, where the two spaces coincide.
readonly property int hostOriginY: root.bottomEdge ? Math.max(0, (root.screen?.height ?? 0) - root.height) : 0
readonly property int outerGap: Math.max(0, Math.min(48, SettingsData.dankIslandOuterGap))
// The host is larger than the visible island to cover every clamped activity target.
readonly property int maxActivityHeight: Math.max(560, Math.min(680, (screen?.height ?? 1080) - 200))
readonly property int hostHeight: outerGap + maxActivityHeight + 8
readonly property int collapsedHostHeight: Math.max(reservedStripHeight, outerGap + Math.ceil(Math.max(controller.compactFaceHeight, satelliteHost.barThickness)) + 4)
readonly property bool hostTall: controller.expanded || surface.targetEdgeExtent > collapsedHostHeight || (surface.motionRunning && surface.motionStartEdgeExtent > collapsedHostHeight)
readonly property int maxActivityWidth: 736
readonly property real maximumHorizontalOffset: Math.max(0, (width - maxActivityWidth) / 2 - 8)
property var colorPickerModal: null
@@ -65,7 +64,7 @@ PanelWindow {
signal lockRequested
color: "transparent"
implicitHeight: hostHeight
implicitHeight: hostTall ? hostHeight : collapsedHostHeight
exclusiveZone: floating ? 0 : reservedStripHeight
readonly property alias islandController: controller
readonly property int launcherResultCount: launcherController.flatModel?.length ?? 0
@@ -156,14 +155,8 @@ PanelWindow {
IslandController {
id: controller
readonly property string interactionOverride: Quickshell.env("DMS_DANKISLAND_INTERACTION") ?? ""
interactionMode: {
const raw = interactionOverride || SettingsData.dankIslandInteractionMode;
return raw === "click" ? "click" : "hybrid";
}
interactionMode: SettingsData.dankIslandInteractionMode === "click" ? "click" : "hybrid"
inputSuspended: PopoutManager.screenshotActive
autoDemo: Quickshell.env("DMS_DANKISLAND_DEMO") === "1"
horizontalOffset: Math.max(-root.maximumHorizontalOffset, Math.min(root.maximumHorizontalOffset, SettingsData.dankIslandHorizontalOffset))
outerGap: root.outerGap
compactHeight: root.compactHeight
@@ -201,26 +194,25 @@ PanelWindow {
}
}
DmsMediaActivitySource {
IslandMediaSource {
id: mediaSource
controller: controller
}
DmsSystemActivitySource {
IslandSystemSource {
id: systemSource
controller: controller
}
DmsNotificationActivitySource {
IslandNotificationSource {
id: notificationSource
controller: controller
targetScreen: root.screen
}
// Empty strip dismisses satellite popouts while something is open; click-through otherwise.
// PopoutManager mutates the map in place, so this tracks popoutChanged instead.
property int popoutRevision: 0
readonly property bool satelliteSurfacesOpen: {
@@ -1,14 +0,0 @@
pragma ComponentBehavior: Bound
import QtQuick
import Quickshell
Variants {
model: Quickshell.screens
delegate: DankIslandLabWindow {
required property var modelData
screen: modelData
}
}
@@ -1,37 +0,0 @@
pragma ComponentBehavior: Bound
import QtQuick
import Quickshell
import Quickshell.Wayland
PanelWindow {
id: root
property int reserveHeight: 58
property int hostHeight: 300
color: "transparent"
implicitHeight: hostHeight
exclusiveZone: reserveHeight
anchors {
top: true
left: true
right: true
}
WlrLayershell.namespace: "dms:dankisland-lab"
WlrLayershell.layer: WlrLayer.Overlay
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
mask: Region {
item: lab.inputMaskItem
}
DankIslandMotionLab {
id: lab
anchors.fill: parent
reserveHeight: root.reserveHeight
}
}
@@ -1,299 +0,0 @@
pragma ComponentBehavior: Bound
import QtQuick
import Quickshell
import qs.Common
import qs.Services
Item {
id: root
required property int reserveHeight
readonly property alias inputMaskItem: inputEnvelope
readonly property var activeMotion: engineMode === "native" ? nativeMotion : vectorMotion
readonly property list<string> targetStates: ["home", "compact", "expanded", "offset-test"]
readonly property var log: Log.scoped("DankIslandMotionLab")
readonly property bool hudEnabled: Quickshell.env("DMS_DANKISLAND_LAB_HUD") !== "0"
readonly property bool metricsLogEnabled: Quickshell.env("DMS_DANKISLAND_LAB_LOG") === "1"
property string engineMode: Quickshell.env("DMS_DANKISLAND_LAB_ENGINE") === "native" ? "native" : "vector"
property string targetState: "home"
property bool stressRunning: Quickshell.env("DMS_DANKISLAND_LAB_STRESS") === "1"
property real lastFrameMs: 0
property real smoothFrameMs: 0
property real peakFrameMs: 0
property real currentPeakFrameMs: 0
property int metricFrameCount: 0
property string statusText: ""
function targetForState(state) {
switch (state) {
case "compact":
return {
"width": 252,
"height": 48,
"offsetX": 0,
"offsetY": 8,
"topLeftRadius": 24,
"topRightRadius": 24,
"bottomLeftRadius": 24,
"bottomRightRadius": 24
};
case "expanded":
return {
"width": 448,
"height": 176,
"offsetX": 0,
"offsetY": 8,
"topLeftRadius": 26,
"topRightRadius": 26,
"bottomLeftRadius": 32,
"bottomRightRadius": 32
};
case "offset-test":
return {
"width": 520,
"height": 132,
"offsetX": -72,
"offsetY": 14,
"topLeftRadius": 22,
"topRightRadius": 30,
"bottomLeftRadius": 44,
"bottomRightRadius": 18
};
default:
return {
"width": 176,
"height": 42,
"offsetX": 0,
"offsetY": 8,
"topLeftRadius": 21,
"topRightRadius": 21,
"bottomLeftRadius": 21,
"bottomRightRadius": 21
};
}
}
function motionSnapshot(motion) {
return {
"width": motion.currentWidth,
"height": motion.currentHeight,
"offsetX": motion.currentOffsetX,
"offsetY": motion.currentOffsetY,
"topLeftRadius": motion.currentTopLeftRadius,
"topRightRadius": motion.currentTopRightRadius,
"bottomLeftRadius": motion.currentBottomLeftRadius,
"bottomRightRadius": motion.currentBottomRightRadius
};
}
function applyState(state) {
targetState = state;
activeMotion.setTarget(targetForState(state));
}
function cycleState(direction) {
const currentIndex = targetStates.indexOf(targetState);
const nextIndex = (currentIndex + direction + targetStates.length) % targetStates.length;
applyState(targetStates[nextIndex]);
}
function toggleEngine() {
const snapshot = motionSnapshot(activeMotion);
const target = targetForState(targetState);
if (engineMode === "vector") {
nativeMotion.snapTo(snapshot);
engineMode = "native";
nativeMotion.setTarget(target);
} else {
vectorMotion.snapTo(snapshot);
engineMode = "vector";
vectorMotion.setTarget(target);
}
peakFrameMs = 0;
currentPeakFrameMs = 0;
metricFrameCount = 0;
}
function updateStatusText() {
if (!hudEnabled)
return;
const engineLabel = engineMode === "vector" ? "VECTOR" : "NATIVE";
const motionLabel = activeMotion.running ? "MOVING" : "SETTLED";
const stressLabel = stressRunning ? " · STRESS" : "";
statusText = engineLabel + " · " + targetState.toUpperCase() + " · " + motionLabel + stressLabel + "\n" + Math.round(activeMotion.currentWidth) + "×"
+ Math.round(activeMotion.currentHeight) + " · " + smoothFrameMs.toFixed(2) + " ms smooth · " + peakFrameMs.toFixed(2) + " ms peak";
}
Component.onCompleted: {
const home = targetForState("home");
nativeMotion.snapTo(home);
vectorMotion.snapTo(home);
updateStatusText();
}
NativeSpringMotion {
id: nativeMotion
enabled: root.engineMode === "native"
}
VectorSpringMotion {
id: vectorMotion
enabled: root.engineMode === "vector"
reducedMotion: SettingsData.dankIslandReducedMotion || SettingsData.animationSpeed === SettingsData.AnimationSpeed.None
stiffness: Math.max(100, Math.min(1200, SettingsData.dankIslandSpringStiffness))
damping: Math.max(10, Math.min(100, SettingsData.dankIslandSpringDamping))
mass: Math.max(0.25, Math.min(3, SettingsData.dankIslandSpringMass))
}
Timer {
interval: 110
repeat: true
running: root.stressRunning
onTriggered: root.cycleState(1)
}
Timer {
interval: 200
repeat: true
running: root.hudEnabled
triggeredOnStart: true
onTriggered: root.updateStatusText()
}
FrameAnimation {
running: root.activeMotion.running || root.stressRunning
onRunningChanged: {
if (running) {
root.peakFrameMs = 0;
root.currentPeakFrameMs = 0;
root.metricFrameCount = 0;
}
}
onTriggered: {
root.lastFrameMs = frameTime * 1000;
root.smoothFrameMs = smoothFrameTime * 1000;
root.metricFrameCount++;
if (root.metricFrameCount > 8)
root.currentPeakFrameMs = Math.max(root.currentPeakFrameMs, root.lastFrameMs);
}
}
Timer {
interval: 1000
repeat: true
running: root.hudEnabled || root.metricsLogEnabled
onTriggered: {
root.peakFrameMs = root.currentPeakFrameMs;
root.currentPeakFrameMs = 0;
if (root.metricsLogEnabled)
root.log.info("DankIslandLabMetrics engine=" + root.engineMode + " state=" + root.targetState + " moving=" + root.activeMotion.running + " frameMs=" + root.lastFrameMs.toFixed(3) + " smoothMs=" + root.smoothFrameMs.toFixed(3) + " peakMs=" + root.peakFrameMs.toFixed(3));
}
}
readonly property real currentVisualX: (width - activeMotion.currentWidth) / 2 + activeMotion.currentOffsetX
readonly property real currentVisualY: activeMotion.currentOffsetY
readonly property real targetVisualX: (width - activeMotion.targetWidth) / 2 + activeMotion.targetOffsetX
readonly property real targetVisualY: activeMotion.targetOffsetY
Item {
id: inputEnvelope
x: Math.min(root.currentVisualX, root.targetVisualX)
y: Math.min(root.currentVisualY, root.targetVisualY)
width: Math.max(root.currentVisualX + root.activeMotion.currentWidth, root.targetVisualX + root.activeMotion.targetWidth) - x
height: Math.max(root.currentVisualY + root.activeMotion.currentHeight, root.targetVisualY + root.activeMotion.targetHeight) - y
}
Rectangle {
x: 0
y: root.reserveHeight - 1
width: root.width
height: 1
color: Theme.outline
opacity: 0.22
visible: root.hudEnabled
}
Rectangle {
id: island
x: root.currentVisualX
y: root.currentVisualY
width: root.activeMotion.currentWidth
height: root.activeMotion.currentHeight
topLeftRadius: Math.max(0, root.activeMotion.currentTopLeftRadius)
topRightRadius: Math.max(0, root.activeMotion.currentTopRightRadius)
bottomLeftRadius: Math.max(0, root.activeMotion.currentBottomLeftRadius)
bottomRightRadius: Math.max(0, root.activeMotion.currentBottomRightRadius)
color: Theme.surfaceContainerHigh
border.width: 1
border.color: Theme.withAlpha(Theme.outline, 0.28)
Rectangle {
anchors {
left: parent.left
right: parent.right
top: parent.top
}
height: 2
color: root.engineMode === "vector" ? Theme.primary : Theme.tertiary
opacity: 0.85
}
Column {
anchors.centerIn: parent
spacing: 2
visible: root.hudEnabled
Text {
anchors.horizontalCenter: parent.horizontalCenter
text: root.statusText.split("\n")[0] ?? ""
color: Theme.surfaceText
font.pixelSize: Theme.fontSizeSmall
font.weight: Font.DemiBold
}
Text {
anchors.horizontalCenter: parent.horizontalCenter
text: root.statusText.split("\n")[1] ?? ""
color: Theme.withAlpha(Theme.surfaceText, 0.72)
font.pixelSize: Math.max(9, Theme.fontSizeSmall - 2)
visible: island.width >= 230
}
Text {
anchors.horizontalCenter: parent.horizontalCenter
text: "left: next · right: engine · middle: stress · wheel: retarget"
color: Theme.withAlpha(Theme.surfaceText, 0.62)
font.pixelSize: Math.max(9, Theme.fontSizeSmall - 2)
visible: island.width >= 400 && island.height >= 100
}
}
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton
onClicked: mouse => {
if (mouse.button === Qt.RightButton) {
root.toggleEngine();
} else if (mouse.button === Qt.MiddleButton) {
root.stressRunning = !root.stressRunning;
} else {
root.cycleState(1);
}
}
onWheel: wheel => {
root.cycleState(wheel.angleDelta.y >= 0 ? 1 : -1);
wheel.accepted = true;
}
}
}
}
@@ -23,7 +23,6 @@ Item {
property real springStiffness: 560
property real springDamping: 37
property real springMass: 1
// Screen-space Y of the host strip's local origin; non-zero only on the bottom edge.
property real hostOriginY: 0
property string palette: "default"
property bool highContrast: false
@@ -43,6 +42,10 @@ Item {
readonly property alias inputMaskItem: inputEnvelope
readonly property bool motionRunning: motion.running
readonly property real targetEdgeExtent: motion.targetOffsetY + motion.targetHeight
readonly property real currentEdgeExtent: motion.currentOffsetY + motion.currentHeight
property real motionStartEdgeExtent: 0
property real trackedHeight: 0
readonly property Item mediaDropdownMaskItem: mediaDropdowns.__activePanel
property real fadeCompactHeight: 48
property real fadeExpandedHeight: 352
@@ -56,7 +59,6 @@ Item {
readonly property real currentVisualHeight: motion.currentHeight
readonly property real targetVisualX: (width - motion.targetWidth) / 2 + motion.targetOffsetX
readonly property real targetVisualY: bottomEdge ? height - motion.targetOffsetY - motion.targetHeight : motion.targetOffsetY
// Activity hosts hand alignedY to full-screen windows, which need screen space, not surface space.
readonly property real targetScreenY: targetVisualY + root.hostOriginY
readonly property real targetVisualWidth: motion.targetWidth
readonly property real targetVisualHeight: motion.targetHeight
@@ -72,15 +74,15 @@ Item {
fadeExpandedHeight = controller.expandedTarget.height;
else
fadeCompactHeight = controller.compactTarget.height;
// A retarget leaves the frozen start stale, so widen it to cover where the island is now.
if (motion.running)
root.unionMotionStartBounds();
motion.setTarget(controller.targetDescriptor);
if (!motion.running)
root.releaseIdleVisuals();
root.controller.releaseIdleVisuals();
}
function unionMotionStartBounds() {
root.motionStartEdgeExtent = Math.max(root.motionStartEdgeExtent, root.currentEdgeExtent);
const b = root.motionStartBounds;
const left = Math.min(b.x, root.currentVisualX);
const top = Math.min(b.y, root.currentVisualY);
@@ -89,14 +91,6 @@ Item {
root.motionStartBounds = Qt.rect(left, top, right - left, bottom - top);
}
function releaseIdleVisuals() {
root.controller.releaseWallpaperVisuals();
root.controller.releaseWeatherVisuals();
root.controller.releaseNotificationCenterVisuals();
root.controller.releaseLauncherVisuals();
root.controller.releaseControlCenterVisuals();
}
function openMediaDropdown(type, pos) {
stopMediaDropdownCloseTimer();
mediaDropdownAnchor = pos;
@@ -123,11 +117,21 @@ Item {
}
Component.onCompleted: {
trackedHeight = height;
fadeCompactHeight = controller.compactTarget.height;
fadeExpandedHeight = controller.expandedTarget.height;
motion.snapTo(controller.targetDescriptor);
}
onHeightChanged: {
const delta = height - trackedHeight;
trackedHeight = height;
if (!bottomEdge || !motion.running || delta === 0)
return;
const b = motionStartBounds;
motionStartBounds = Qt.rect(b.x, b.y + delta, b.width, b.height);
}
Connections {
target: root.controller
@@ -152,11 +156,12 @@ Item {
function onRunningChanged() {
if (motion.running) {
root.motionStartBounds = Qt.rect(root.currentVisualX, root.currentVisualY, motion.currentWidth, motion.currentHeight);
root.motionStartEdgeExtent = root.currentEdgeExtent;
if (root.controller.activeActivity === "media")
root.hideMediaDropdowns();
return;
}
root.releaseIdleVisuals();
root.controller.releaseIdleVisuals();
}
}
@@ -180,7 +185,6 @@ Item {
Item {
id: inputEnvelope
// Cover underdamped overshoot for the frozen flight, scaled to travel.
readonly property real overshootBudget: {
if (!motion.running)
return 0;
@@ -216,7 +220,6 @@ Item {
border.width: root.highContrast ? 2 : 0
border.color: root.highContrast ? Theme.outlineStrong : "transparent"
// Full destinations keep blank clicks for their own content instead of collapsing.
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.LeftButton
@@ -236,6 +239,7 @@ Item {
id: contentHost
anchors.fill: parent
controller: root.controller
morphProgress: root.morphProgress
expanded: root.controller.expanded
pointerInside: root.controller.pointerInside
@@ -246,32 +250,22 @@ Item {
mediaExpandedComponent: expandedMediaComponent
launcherCompactComponent: compactLauncherComponent
launcherExpandedComponent: expandedLauncherComponent
launcherVisualsRequested: root.controller.launcherVisualsRequested
controlCenterCompactComponent: compactControlCenterComponent
controlCenterExpandedComponent: expandedControlCenterComponent
controlCenterVisualsRequested: root.controller.controlCenterVisualsRequested
wallpaperCompactComponent: compactWallpaperComponent
wallpaperExpandedComponent: expandedWallpaperComponent
wallpaperVisualsRequested: root.controller.wallpaperVisualsRequested
weatherCompactComponent: compactWeatherComponent
weatherExpandedComponent: expandedWeatherComponent
weatherVisualsRequested: root.controller.weatherVisualsRequested
systemCompactComponent: compactSystemComponent
systemExpandedComponent: expandedSystemComponent
notificationCompactComponent: compactNotificationComponent
notificationExpandedComponent: expandedNotificationComponent
notificationCenterCompactComponent: compactNotificationCenterComponent
notificationCenterExpandedComponent: expandedNotificationCenterComponent
notificationCenterVisualsRequested: root.controller.notificationCenterVisualsRequested
}
HoverHandler {
onHoveredChanged: {
if (hovered)
root.controller.updatePointerInside(true);
else
root.controller.updatePointerInside(false);
}
onHoveredChanged: root.controller.updatePointerInside(hovered)
}
}
@@ -335,7 +329,6 @@ Item {
geometrySettled: !motion.running
effectiveScreen: root.effectiveScreen
alignedX: root.targetVisualX
// Surface-local on purpose: the media dropdowns it anchors live inside this surface.
alignedY: root.targetVisualY
alignedWidth: root.targetVisualWidth
alignedHeight: root.targetVisualHeight
@@ -363,7 +356,6 @@ Item {
launcherController: root.launcherController
transientSurfaceTracker: root.launcherTransientSurfaceTracker
effectiveScreen: root.effectiveScreen
// Settled bounds; animated values rebind the tree every frame.
alignedX: root.targetVisualX
alignedY: root.targetScreenY
}
@@ -385,7 +377,6 @@ Item {
effectiveScreen: root.effectiveScreen
colorPickerModal: root.colorPickerModal
powerMenuModalLoader: root.powerMenuModalLoader
// Settled bounds; animated values rebind the tree every frame.
alignedX: root.targetVisualX
alignedY: root.targetScreenY
alignedWidth: root.targetVisualWidth
@@ -1,115 +0,0 @@
pragma ComponentBehavior: Bound
import QtQuick
import Quickshell
import Quickshell.Services.Mpris
import qs.Services
QtObject {
id: root
required property IslandController controller
readonly property bool demoEnabled: Quickshell.env("DMS_DANKISLAND_MEDIA_DEMO") === "1"
readonly property var demoTracks: [
{
"title": "DankIsland Motion Study",
"artist": "DankMaterialShell",
"album": "Physical Springs",
"icon": "applications-multimedia"
},
{
"title": "Event Horizon",
"artist": "Quickshell Ensemble",
"album": "Signal Driven",
"icon": "audio-x-generic"
},
{
"title": "Warm Surface",
"artist": "Scene Graph",
"album": "Sixty Frames",
"icon": "multimedia-player"
}
]
readonly property var demoTrack: demoTracks[demoTrackIndex]
readonly property var player: MprisController.activePlayer
readonly property bool realMediaAvailable: !!player && player.playbackState !== MprisPlaybackState.Stopped
readonly property bool available: demoEnabled || realMediaAvailable
readonly property string title: demoEnabled ? demoTrack.title : (MprisController.stableTitle || player?.trackTitle || "Unknown track")
readonly property string artist: demoEnabled ? demoTrack.artist : (MprisController.stableArtist || player?.trackArtist || player?.identity || "Unknown artist")
readonly property string album: demoEnabled ? demoTrack.album : (MprisController.stableAlbum || player?.trackAlbum || "")
readonly property string artUrl: demoEnabled ? Quickshell.iconPath(demoTrack.icon, true) : TrackArtService.resolvedArtUrl
readonly property bool playing: demoEnabled ? demoPlaying : !!player?.isPlaying
readonly property bool canToggle: demoEnabled || !!player?.canTogglePlaying
readonly property bool canGoPrevious: demoEnabled || !!player?.canGoPrevious
readonly property bool canGoNext: demoEnabled || !!player?.canGoNext
readonly property bool canSeek: demoEnabled || (!!player?.canSeek && length > 0)
readonly property real position: demoEnabled ? demoPosition : Math.max(0, player?.position || 0)
readonly property real length: demoEnabled ? 243 : Math.max(0, MprisController.activePlayerStableLength)
readonly property real progress: length > 0 ? Math.max(0, Math.min(1, position / length)) : 0
property int demoTrackIndex: 0
property bool demoPlaying: true
property real demoPosition: 74
function advanceDemoTrack(delta) {
demoTrackIndex = (demoTrackIndex + delta + demoTracks.length) % demoTracks.length;
demoPosition = 0;
}
function syncAvailability() {
controller.updateMediaAvailability(available);
}
function togglePlaying() {
if (demoEnabled) {
demoPlaying = !demoPlaying;
return;
}
if (player?.canTogglePlaying)
player.togglePlaying();
}
function previous() {
if (demoEnabled) {
advanceDemoTrack(-1);
return;
}
MprisController.previousOrRewind();
}
function next() {
if (demoEnabled) {
advanceDemoTrack(1);
return;
}
MprisController.next();
}
function seekRatio(ratio) {
if (!canSeek || length <= 0)
return;
const nextPosition = Math.max(0.1, Math.min(length - 0.1, length * Math.max(0, Math.min(1, ratio))));
if (demoEnabled)
demoPosition = nextPosition;
else
player.position = nextPosition;
}
Component.onCompleted: syncAvailability()
onAvailableChanged: syncAvailability()
property Timer demoTimer: Timer {
interval: 1000
repeat: true
running: root.demoEnabled && root.demoPlaying
onTriggered: root.demoPosition = (root.demoPosition + 1) % root.length
}
property Timer demoTrackTimer: Timer {
interval: 4200
repeat: true
running: root.demoEnabled
onTriggered: root.advanceDemoTrack(1)
}
}
@@ -1,10 +1,12 @@
pragma ComponentBehavior: Bound
import QtQuick
import qs.Common
Item {
id: root
required property var controller
required property real morphProgress
required property bool expanded
required property bool pointerInside
@@ -15,27 +17,21 @@ Item {
required property Component mediaExpandedComponent
required property Component launcherCompactComponent
required property Component launcherExpandedComponent
required property bool launcherVisualsRequested
required property Component controlCenterCompactComponent
required property Component controlCenterExpandedComponent
required property bool controlCenterVisualsRequested
required property Component wallpaperCompactComponent
required property Component wallpaperExpandedComponent
required property bool wallpaperVisualsRequested
required property Component weatherCompactComponent
required property Component weatherExpandedComponent
required property bool weatherVisualsRequested
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 bool notificationCenterVisualsRequested
readonly property real compactFade: root.fadeCompact(root.morphProgress)
readonly property real expandedFade: root.fadeExpanded(root.morphProgress)
// Outgoing faces freeze at the morph they were drawn at so they do not flash expanded.
readonly property real outgoingCompactFade: root.fadeCompact(root.outgoingMorph)
readonly property real outgoingExpandedFade: root.fadeExpanded(root.outgoingMorph)
readonly property bool mediaSurfaceActive: root.activityId === "media" || root.outgoingActivity === "media"
@@ -103,7 +99,6 @@ Item {
homeExpandedTouched = true;
}
// Overview is latched on first pointer intent, not at startup.
onPointerInsideChanged: {
if (root.pointerInside)
root.homeExpandedTouched = true;
@@ -126,14 +121,14 @@ Item {
id: activityTransition
PauseAnimation {
duration: 45
duration: Theme.shorterDuration
}
NumberAnimation {
target: root
property: "activityFade"
to: 1
duration: 180
duration: Theme.mediumDuration
easing.type: Easing.OutCubic
}
@@ -156,12 +151,11 @@ Item {
id: homeExpandedLoader
anchors.fill: parent
// Latched: built on first expansion, then kept so later morphs never hitch.
active: root.homeExpandedTouched
asynchronous: true
sourceComponent: root.homeExpandedComponent
opacity: root.expandedOpacity("home")
visible: active || opacity > 0.001
visible: opacity > 0.001
enabled: opacity >= 0.5
}
@@ -201,7 +195,7 @@ Item {
id: launcherExpandedLoader
anchors.fill: parent
active: root.launcherVisualsRequested
active: root.controller.visualsRequested("launcher")
asynchronous: true
sourceComponent: root.launcherExpandedComponent
opacity: root.expandedOpacity("launcher")
@@ -223,7 +217,7 @@ Item {
id: controlCenterExpandedLoader
anchors.fill: parent
active: root.controlCenterVisualsRequested
active: root.controller.visualsRequested("controlcenter")
asynchronous: false
sourceComponent: root.controlCenterExpandedComponent
opacity: root.expandedOpacity("controlcenter")
@@ -245,11 +239,11 @@ Item {
id: wallpaperExpandedLoader
anchors.fill: parent
active: root.wallpaperVisualsRequested
active: root.controller.visualsRequested("wallpaper")
asynchronous: true
sourceComponent: root.wallpaperExpandedComponent
opacity: root.expandedOpacity("wallpaper")
visible: active || opacity > 0.001
visible: opacity > 0.001
enabled: opacity >= 0.5
}
@@ -267,11 +261,11 @@ Item {
id: weatherExpandedLoader
anchors.fill: parent
active: root.weatherVisualsRequested
active: root.controller.visualsRequested("weather")
asynchronous: true
sourceComponent: root.weatherExpandedComponent
opacity: root.expandedOpacity("weather")
visible: active || opacity > 0.001
visible: opacity > 0.001
enabled: opacity >= 0.5
}
@@ -289,7 +283,7 @@ Item {
id: notificationCenterExpandedLoader
anchors.fill: parent
active: root.notificationCenterVisualsRequested
active: root.controller.visualsRequested("notificationcenter")
asynchronous: true
sourceComponent: root.notificationCenterExpandedComponent
opacity: root.expandedOpacity("notificationcenter")
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,49 @@
pragma ComponentBehavior: Bound
import QtQuick
import Quickshell.Services.Mpris
import qs.Common
import qs.Services
QtObject {
id: root
required property IslandController controller
readonly property var player: MprisController.activePlayer
readonly property bool available: !!player && player.playbackState !== MprisPlaybackState.Stopped
readonly property string title: MprisController.stableTitle || player?.trackTitle || I18n.tr("Unknown Track")
readonly property string artist: MprisController.stableArtist || player?.trackArtist || player?.identity || I18n.tr("Unknown Artist")
readonly property string album: MprisController.stableAlbum || player?.trackAlbum || ""
readonly property string artUrl: TrackArtService.resolvedArtUrl
readonly property bool playing: !!player?.isPlaying
readonly property bool canToggle: !!player?.canTogglePlaying
readonly property bool canGoPrevious: !!player?.canGoPrevious
readonly property bool canGoNext: !!player?.canGoNext
readonly property bool canSeek: !!player?.canSeek && length > 0
readonly property real position: Math.max(0, player?.position || 0)
readonly property real length: Math.max(0, MprisController.activePlayerStableLength)
readonly property real progress: length > 0 ? Math.max(0, Math.min(1, position / length)) : 0
function togglePlaying() {
if (player?.canTogglePlaying)
player.togglePlaying();
}
function previous() {
MprisController.previousOrRewind();
}
function next() {
MprisController.next();
}
function seekRatio(ratio) {
if (!canSeek || length <= 0)
return;
player.position = Math.max(0.1, Math.min(length - 0.1, length * Math.max(0, Math.min(1, ratio))));
}
Component.onCompleted: controller.updateMediaAvailability(available)
onAvailableChanged: controller.updateMediaAvailability(available)
}
@@ -1,7 +1,6 @@
pragma ComponentBehavior: Bound
import QtQuick
import Quickshell
import Quickshell.Services.Notifications
import qs.Common
import qs.Services
@@ -22,11 +21,8 @@ QtObject {
property string fallbackText: ""
property string actionLabel: ""
property bool critical: false
property int demoIndex: 0
readonly property bool demoEnabled: Quickshell.env("DMS_DANKISLAND_NOTIFICATION_DEMO") === "1"
readonly property bool hasAction: actionLabel.length > 0
// Keep the wrapper alive while the island is hovered or expanded, matching NotificationPopup.
readonly property bool wrapperTimeoutHeld: (controller.notificationActive && controller.timeoutSuspended) || controller.notificationHeldForSystem
function isFocusedScreen() {
@@ -36,7 +32,6 @@ QtObject {
return !!focused && !!root.targetScreen && focused.name === root.targetScreen.name;
}
// Hold display fields through the outgoing crossfade.
function scheduleDisplayFieldClear() {
displayFieldClearTimer.restart();
}
@@ -111,16 +106,16 @@ QtObject {
releaseWrapperTimeoutHold();
displayFieldClearTimer.stop();
currentWrapper = wrapper;
appName = wrapper.appName || "Notification";
appName = wrapper.appName || I18n.tr("Notification");
summary = plainText(wrapper.summary) || appName;
body = plainText(wrapper.body);
timeText = wrapper.timeStr || "Now";
timeText = wrapper.timeStr || "";
imageSource = resolveImage(wrapper);
fallbackIcon = resolveFallbackIcon(wrapper);
fallbackText = appName.charAt(0).toUpperCase();
critical = isCritical;
const actions = wrapper.actions || [];
actionLabel = actions.length > 0 ? (actions[0].text || "Open") : "";
actionLabel = actions.length > 0 ? (actions[0].text || I18n.tr("Open")) : "";
controller.notificationTimeout = wrapperTimeout(wrapper);
const accepted = controller.requestNotification(isCritical);
applyWrapperTimeoutHold();
@@ -137,9 +132,6 @@ QtObject {
onWrapperTimeoutHeldChanged: applyWrapperTimeoutHold()
function syncVisibleNotifications() {
if (demoEnabled)
return;
const visible = NotificationService.visibleNotifications || [];
if (visible.length > 0) {
const latest = visible[visible.length - 1];
@@ -147,18 +139,14 @@ QtObject {
return;
}
if (currentWrapper && visible.indexOf(currentWrapper) === -1) {
currentWrapper = null;
controller.completeNotification();
scheduleDisplayFieldClear();
}
if (!currentWrapper || visible.indexOf(currentWrapper) !== -1)
return;
currentWrapper = null;
controller.completeNotification();
scheduleDisplayFieldClear();
}
function activate() {
if (demoEnabled) {
controller.completeNotification();
return;
}
const actions = currentWrapper?.actions || [];
if (actions.length > 0)
actions[0].invoke();
@@ -170,47 +158,10 @@ QtObject {
currentWrapper = null;
controller.completeNotification();
scheduleDisplayFieldClear();
if (!demoEnabled && wrapper)
if (wrapper)
NotificationService.dismissNotification(wrapper);
}
function showDemo() {
const variants = [
{
"appName": "Messages",
"summary": "New message from Alex",
"body": "The new activity transition looks great. Are we still on for tomorrow?",
"icon": "material:mark_chat_unread",
"action": "Reply"
},
{
"appName": "Calendar",
"summary": "Design review in 10 minutes",
"body": "DankIsland activity protocol · Studio call",
"icon": "material:event_upcoming",
"action": "Open"
}
];
const demo = variants[demoIndex % variants.length];
demoIndex += 1;
if (!controller.canRequestNotification(false))
return;
releaseWrapperTimeoutHold();
displayFieldClearTimer.stop();
currentWrapper = null;
appName = demo.appName;
summary = demo.summary;
body = demo.body;
timeText = "Now";
imageSource = "";
fallbackIcon = demo.icon;
fallbackText = demo.appName.charAt(0);
actionLabel = demo.action;
critical = false;
controller.notificationTimeout = 5000;
controller.requestNotification(false);
}
property Connections notificationConnections: Connections {
target: NotificationService
@@ -228,17 +179,5 @@ QtObject {
onTriggered: root.clearDisplayFields()
}
property Timer demoTimer: Timer {
interval: 7000
repeat: true
running: root.demoEnabled
onTriggered: root.showDemo()
}
Component.onCompleted: {
if (demoEnabled)
Qt.callLater(showDemo);
else
syncVisibleNotifications();
}
Component.onCompleted: syncVisibleNotifications()
}
@@ -1,7 +1,6 @@
pragma ComponentBehavior: Bound
import QtQuick
import Quickshell
import qs.Common
import qs.Services
@@ -11,17 +10,12 @@ QtObject {
required property IslandController controller
property string kind: "volume"
property real demoRatio: 0.68
readonly property bool demoEnabled: Quickshell.env("DMS_DANKISLAND_SYSTEM_DEMO") === "1"
readonly property bool volumeActivity: kind === "volume"
readonly property bool available: demoEnabled || (volumeActivity ? !!AudioService.sink?.audio : DisplayService.brightnessAvailable)
readonly property bool muted: !demoEnabled && volumeActivity && (AudioService.sink?.audio?.muted ?? false)
readonly property real value: demoEnabled ? demoRatio * 100 : volumeActivity ? Math.min(AudioService.sinkMaxVolume, Math.round((AudioService.sink?.audio?.volume ?? 0) * 100)) : DisplayService.brightnessLevel
readonly property bool available: volumeActivity ? !!AudioService.sink?.audio : DisplayService.brightnessAvailable
readonly property bool muted: volumeActivity && (AudioService.sink?.audio?.muted ?? false)
readonly property real value: volumeActivity ? Math.min(AudioService.sinkMaxVolume, Math.round((AudioService.sink?.audio?.volume ?? 0) * 100)) : DisplayService.brightnessLevel
readonly property real maximum: {
if (demoEnabled)
return 100;
if (volumeActivity)
return AudioService.sinkMaxVolume;
const deviceInfo = DisplayService.getCurrentDeviceInfo();
@@ -32,7 +26,7 @@ QtObject {
return deviceInfo.displayMax || 100;
}
readonly property int minimum: {
if (volumeActivity || demoEnabled)
if (volumeActivity)
return 0;
const deviceInfo = DisplayService.getCurrentDeviceInfo();
if (!deviceInfo)
@@ -42,11 +36,11 @@ QtObject {
return (deviceInfo.class === "backlight" || deviceInfo.class === "ddc") ? 1 : 0;
}
readonly property real ratio: maximum > 0 ? Math.max(0, Math.min(1, value / maximum)) : 0
readonly property string title: volumeActivity ? "Volume" : "Brightness"
readonly property string title: volumeActivity ? I18n.tr("Volume") : I18n.tr("Brightness")
readonly property string unit: {
if (volumeActivity)
return "%";
const deviceInfo = demoEnabled ? null : DisplayService.getCurrentDeviceInfo();
const deviceInfo = DisplayService.getCurrentDeviceInfo();
if (!deviceInfo)
return "%";
if (SessionData.getBrightnessExponential(deviceInfo.id))
@@ -54,28 +48,31 @@ QtObject {
return deviceInfo.class === "ddc" ? "" : "%";
}
readonly property string displayValue: muted ? I18n.tr("Muted") : Math.round(value) + unit
readonly property string iconName: {
if (!volumeActivity) {
const deviceInfo = demoEnabled ? null : DisplayService.getCurrentDeviceInfo();
if (!demoEnabled && (!available || !deviceInfo))
return "brightness_low";
if (demoEnabled || deviceInfo.class === "backlight" || deviceInfo.class === "ddc") {
if (ratio <= 0.33)
return "brightness_low";
return ratio <= 0.66 ? "brightness_medium" : "brightness_high";
}
if (String(deviceInfo.name ?? "").includes("kbd"))
return "keyboard";
return "lightbulb";
}
readonly property string iconName: volumeActivity ? volumeIconName() : brightnessIconName()
function volumeIconName() {
if (!available || muted)
return "volume_off";
const volume = demoEnabled ? ratio : (AudioService.sink?.audio?.volume ?? 0);
const volume = AudioService.sink?.audio?.volume ?? 0;
if (volume === 0)
return "volume_mute";
return volume <= 0.33 ? "volume_down" : "volume_up";
}
function brightnessIconName() {
const deviceInfo = DisplayService.getCurrentDeviceInfo();
if (!available || !deviceInfo)
return "brightness_low";
if (deviceInfo.class === "backlight" || deviceInfo.class === "ddc") {
if (ratio <= 0.33)
return "brightness_low";
return ratio <= 0.66 ? "brightness_medium" : "brightness_high";
}
if (String(deviceInfo.name ?? "").includes("kbd"))
return "keyboard";
return "lightbulb";
}
function show(activityKind) {
if (controller.requestSystemActivity(activityKind))
kind = activityKind;
@@ -83,22 +80,17 @@ QtObject {
function setRatio(nextRatio) {
const clampedRatio = Math.max(0, Math.min(1, nextRatio));
if (demoEnabled) {
demoRatio = clampedRatio;
SessionData.suppressOSDTemporarily();
if (volumeActivity) {
AudioService.setVolume(Math.round(clampedRatio * maximum));
return;
}
SessionData.suppressOSDTemporarily();
if (volumeActivity)
AudioService.setVolume(Math.round(clampedRatio * maximum));
else
DisplayService.setBrightness(Math.round(clampedRatio * maximum), DisplayService.lastIpcDevice, true);
DisplayService.setBrightness(Math.round(clampedRatio * maximum), DisplayService.lastIpcDevice, true);
}
function toggleMute() {
if (!volumeActivity || !available)
return;
if (demoEnabled)
return;
SessionData.suppressOSDTemporarily();
AudioService.toggleMute();
}
@@ -125,21 +117,4 @@ QtObject {
root.show("brightness");
}
}
property Timer demoTimer: Timer {
interval: 3200
repeat: true
running: root.demoEnabled
onTriggered: {
const nextKind = root.kind === "volume" ? "brightness" : "volume";
root.demoRatio = nextKind === "volume" ? 0.68 : 0.82;
root.show(nextKind);
}
}
Component.onCompleted: {
if (demoEnabled)
show("volume");
}
}
@@ -1,176 +0,0 @@
pragma ComponentBehavior: Bound
import QtQuick
QtObject {
id: root
property bool enabled: true
property bool animationsEnabled: true
property real spring: 5
property real damping: 0.32
property real mass: 1
property real epsilon: 0.04
property real currentWidth: 176
property real currentHeight: 42
property real currentOffsetX: 0
property real currentOffsetY: 8
property real currentTopLeftRadius: 21
property real currentTopRightRadius: 21
property real currentBottomLeftRadius: 21
property real currentBottomRightRadius: 21
property real targetWidth: currentWidth
property real targetHeight: currentHeight
property real targetOffsetX: currentOffsetX
property real targetOffsetY: currentOffsetY
property real targetTopLeftRadius: currentTopLeftRadius
property real targetTopRightRadius: currentTopRightRadius
property real targetBottomLeftRadius: currentBottomLeftRadius
property real targetBottomRightRadius: currentBottomRightRadius
readonly property bool running: widthSpring.running || heightSpring.running || offsetXSpring.running || offsetYSpring.running || topLeftRadiusSpring.running || topRightRadiusSpring.running
|| bottomLeftRadiusSpring.running || bottomRightRadiusSpring.running
function setTarget(target) {
targetWidth = target.width;
targetHeight = target.height;
targetOffsetX = target.offsetX;
targetOffsetY = target.offsetY;
targetTopLeftRadius = target.topLeftRadius;
targetTopRightRadius = target.topRightRadius;
targetBottomLeftRadius = target.bottomLeftRadius;
targetBottomRightRadius = target.bottomRightRadius;
currentWidth = targetWidth;
currentHeight = targetHeight;
currentOffsetX = targetOffsetX;
currentOffsetY = targetOffsetY;
currentTopLeftRadius = targetTopLeftRadius;
currentTopRightRadius = targetTopRightRadius;
currentBottomLeftRadius = targetBottomLeftRadius;
currentBottomRightRadius = targetBottomRightRadius;
}
function snapTo(target) {
animationsEnabled = false;
targetWidth = target.width;
targetHeight = target.height;
targetOffsetX = target.offsetX;
targetOffsetY = target.offsetY;
targetTopLeftRadius = target.topLeftRadius;
targetTopRightRadius = target.topRightRadius;
targetBottomLeftRadius = target.bottomLeftRadius;
targetBottomRightRadius = target.bottomRightRadius;
currentWidth = target.width;
currentHeight = target.height;
currentOffsetX = target.offsetX;
currentOffsetY = target.offsetY;
currentTopLeftRadius = target.topLeftRadius;
currentTopRightRadius = target.topRightRadius;
currentBottomLeftRadius = target.bottomLeftRadius;
currentBottomRightRadius = target.bottomRightRadius;
animationsEnabled = true;
}
Behavior on currentWidth {
enabled: root.enabled && root.animationsEnabled
SpringAnimation {
id: widthSpring
spring: root.spring
damping: root.damping
mass: root.mass
epsilon: root.epsilon
}
}
Behavior on currentHeight {
enabled: root.enabled && root.animationsEnabled
SpringAnimation {
id: heightSpring
spring: root.spring
damping: root.damping
mass: root.mass
epsilon: root.epsilon
}
}
Behavior on currentOffsetX {
enabled: root.enabled && root.animationsEnabled
SpringAnimation {
id: offsetXSpring
spring: root.spring
damping: root.damping
mass: root.mass
epsilon: root.epsilon
}
}
Behavior on currentOffsetY {
enabled: root.enabled && root.animationsEnabled
SpringAnimation {
id: offsetYSpring
spring: root.spring
damping: root.damping
mass: root.mass
epsilon: root.epsilon
}
}
Behavior on currentTopLeftRadius {
enabled: root.enabled && root.animationsEnabled
SpringAnimation {
id: topLeftRadiusSpring
spring: root.spring
damping: root.damping
mass: root.mass
epsilon: root.epsilon
}
}
Behavior on currentTopRightRadius {
enabled: root.enabled && root.animationsEnabled
SpringAnimation {
id: topRightRadiusSpring
spring: root.spring
damping: root.damping
mass: root.mass
epsilon: root.epsilon
}
}
Behavior on currentBottomLeftRadius {
enabled: root.enabled && root.animationsEnabled
SpringAnimation {
id: bottomLeftRadiusSpring
spring: root.spring
damping: root.damping
mass: root.mass
epsilon: root.epsilon
}
}
Behavior on currentBottomRightRadius {
enabled: root.enabled && root.animationsEnabled
SpringAnimation {
id: bottomRightRadiusSpring
spring: root.spring
damping: root.damping
mass: root.mass
epsilon: root.epsilon
}
}
}
@@ -44,7 +44,13 @@ QtObject {
property real velocityBottomLeftRadius: 0
property real velocityBottomRightRadius: 0
function matchesTarget(target) {
return targetWidth === target.width && targetHeight === target.height && targetOffsetX === target.offsetX && targetOffsetY === target.offsetY && targetTopLeftRadius === target.topLeftRadius && targetTopRightRadius === target.topRightRadius && targetBottomLeftRadius === target.bottomLeftRadius && targetBottomRightRadius === target.bottomRightRadius;
}
function setTarget(target) {
if (matchesTarget(target))
return;
targetWidth = target.width;
targetHeight = target.height;
targetOffsetX = target.offsetX;
@@ -94,13 +100,8 @@ QtObject {
}
function isSettled() {
const positionSettled = Math.abs(targetWidth - currentWidth) <= positionEpsilon && Math.abs(targetHeight - currentHeight) <= positionEpsilon
&& Math.abs(targetOffsetX - currentOffsetX) <= positionEpsilon && Math.abs(targetOffsetY - currentOffsetY) <= positionEpsilon
&& Math.abs(targetTopLeftRadius - currentTopLeftRadius) <= positionEpsilon && Math.abs(targetTopRightRadius - currentTopRightRadius) <= positionEpsilon
&& Math.abs(targetBottomLeftRadius - currentBottomLeftRadius) <= positionEpsilon && Math.abs(targetBottomRightRadius - currentBottomRightRadius) <= positionEpsilon;
const velocitySettled = Math.abs(velocityWidth) <= velocityEpsilon && Math.abs(velocityHeight) <= velocityEpsilon && Math.abs(velocityOffsetX) <= velocityEpsilon
&& Math.abs(velocityOffsetY) <= velocityEpsilon && Math.abs(velocityTopLeftRadius) <= velocityEpsilon && Math.abs(velocityTopRightRadius) <= velocityEpsilon
&& Math.abs(velocityBottomLeftRadius) <= velocityEpsilon && Math.abs(velocityBottomRightRadius) <= velocityEpsilon;
const positionSettled = Math.abs(targetWidth - currentWidth) <= positionEpsilon && Math.abs(targetHeight - currentHeight) <= positionEpsilon && Math.abs(targetOffsetX - currentOffsetX) <= positionEpsilon && Math.abs(targetOffsetY - currentOffsetY) <= positionEpsilon && Math.abs(targetTopLeftRadius - currentTopLeftRadius) <= positionEpsilon && Math.abs(targetTopRightRadius - currentTopRightRadius) <= positionEpsilon && Math.abs(targetBottomLeftRadius - currentBottomLeftRadius) <= positionEpsilon && Math.abs(targetBottomRightRadius - currentBottomRightRadius) <= positionEpsilon;
const velocitySettled = Math.abs(velocityWidth) <= velocityEpsilon && Math.abs(velocityHeight) <= velocityEpsilon && Math.abs(velocityOffsetX) <= velocityEpsilon && Math.abs(velocityOffsetY) <= velocityEpsilon && Math.abs(velocityTopLeftRadius) <= velocityEpsilon && Math.abs(velocityTopRightRadius) <= velocityEpsilon && Math.abs(velocityBottomLeftRadius) <= velocityEpsilon && Math.abs(velocityBottomRightRadius) <= velocityEpsilon;
return positionSettled && velocitySettled;
}
@@ -4,7 +4,6 @@ import QtQuick
import qs.Common
import qs.Services
// Host-agnostic notification center body; popout and island both host this.
Item {
id: root
+12 -6
View File
@@ -48,7 +48,7 @@ Item {
width: parent.width
iconName: "view_in_ar"
title: I18n.tr("Dank Island - Beta")
settingKey: "dankIslandBarId"
settingKey: "dankIslandInstance"
SettingsButtonGroupRow {
settingKey: "dankIslandBarId"
@@ -260,11 +260,13 @@ Item {
tags: ["island", "appearance", "palette", "surface", "bright", "dim"]
text: I18n.tr("Palette")
model: [I18n.tr("Default"), I18n.tr("Bright"), I18n.tr("Dim")]
currentIndex: SettingsData.dankIslandPalette === "bright" ? 1 : SettingsData.dankIslandPalette === "dim" ? 2 : 0
readonly property var _paletteValues: ["default", "bright", "dim"]
currentIndex: Math.max(0, _paletteValues.indexOf(SettingsData.dankIslandPalette))
onSelectionChanged: (index, selected) => {
if (!selected)
return;
SettingsData.set("dankIslandPalette", index === 1 ? "bright" : index === 2 ? "dim" : "default");
SettingsData.set("dankIslandPalette", _paletteValues[index] ?? "default");
}
}
@@ -369,7 +371,7 @@ Item {
SettingsCard {
width: parent.width
iconName: "touch_app"
title: I18n.tr("Interaction")
title: I18n.tr("Behavior")
settingKey: "dankIslandInteraction"
enabled: SettingsData.dankIslandEnabled
@@ -399,7 +401,7 @@ Item {
SettingsSliderRow {
settingKey: "dankIslandHoverOpenDelay"
tags: ["island", "interaction", "hover", "open", "delay"]
text: I18n.tr("Hover Open Delay")
text: I18n.tr("Open Delay")
unit: "ms"
minimum: 0
maximum: 1000
@@ -413,7 +415,7 @@ Item {
SettingsSliderRow {
settingKey: "dankIslandHoverCloseDelay"
tags: ["island", "interaction", "hover", "close", "delay"]
text: I18n.tr("Hover Close Delay")
text: I18n.tr("Hide Delay")
unit: "ms"
minimum: 0
maximum: 1000
@@ -435,6 +437,7 @@ Item {
SettingsToggleRow {
settingKey: "dankIslandReducedMotion"
tags: ["island", "motion", "animation", "reduce", "accessibility", "spring"]
text: I18n.tr("Reduce Motion")
description: I18n.tr("Apply island geometry changes immediately without spring overshoot")
checked: SettingsData.dankIslandReducedMotion
@@ -443,6 +446,7 @@ Item {
SettingsSliderRow {
settingKey: "dankIslandSpringStiffness"
tags: ["island", "motion", "spring", "stiffness", "animation"]
text: I18n.tr("Spring Stiffness")
description: I18n.tr("Higher values pull island toward its target more strongly")
minimum: 100
@@ -456,6 +460,7 @@ Item {
SettingsSliderRow {
settingKey: "dankIslandSpringDamping"
tags: ["island", "motion", "spring", "damping", "bounce", "animation"]
text: I18n.tr("Spring Damping")
description: I18n.tr("Higher values settle island with less bounce")
minimum: 10
@@ -469,6 +474,7 @@ Item {
SettingsSliderRow {
settingKey: "dankIslandSpringMass"
tags: ["island", "motion", "spring", "mass", "inertia", "animation"]
text: I18n.tr("Spring Mass")
description: I18n.tr("Higher percentages give island more inertia")
minimum: 25
+11 -74
View File
@@ -141,84 +141,21 @@ Singleton {
}
readonly property bool islandControlCenterOpen: dankIslandRouter?.controlCenterOpen ?? false
readonly property bool islandMediaOpen: dankIslandRouter?.mediaOpen ?? false
readonly property bool islandWallpaperOpen: dankIslandRouter?.wallpaperOpen ?? false
readonly property bool islandWeatherOpen: dankIslandRouter?.weatherOpen ?? false
readonly property bool islandHomeOpen: dankIslandRouter?.homeOpen ?? false
readonly property bool islandNotificationCenterOpen: dankIslandRouter?.notificationCenterOpen ?? false
// Shared triggers use the island only when it owns that screen; false lets callers keep the popout path.
function routeControlCenterToIsland(screen, section, shouldToggle) {
function routeToIsland(activityId, screen, shouldToggle, section) {
if (!_islandOwnsSharedTrigger(screen))
return false;
if (shouldToggle === true)
return dankIslandRouter.toggleControlCenter(section || "", screen ?? null);
return dankIslandRouter.openControlCenter(section || "", screen ?? null);
return dankIslandRouter.toggleActivity(activityId, screen ?? null, section || "") === true;
return dankIslandRouter.openActivity(activityId, screen ?? null, section || "") === true;
}
// A missing media session is a deliberate fallback signal, not a handled request.
function routeMediaToIsland(screen, shouldToggle) {
if (!_islandOwnsSharedTrigger(screen))
return false;
if (shouldToggle === true)
return dankIslandRouter.toggleMedia(screen ?? null) === true;
return dankIslandRouter.openMedia(screen ?? null) === true;
}
function routeWallpaperToIsland(screen, shouldToggle) {
if (!_islandOwnsSharedTrigger(screen))
return false;
if (shouldToggle === true)
return dankIslandRouter.toggleWallpaper(screen ?? null) === true;
return dankIslandRouter.openWallpaper(screen ?? null) === true;
}
function routeWeatherToIsland(screen, shouldToggle) {
if (!_islandOwnsSharedTrigger(screen))
return false;
if (shouldToggle === true)
return dankIslandRouter.toggleWeather(screen ?? null) === true;
return dankIslandRouter.openWeather(screen ?? null) === true;
}
function routeOverviewToIsland(screen, shouldToggle) {
if (!_islandOwnsSharedTrigger(screen))
return false;
if (shouldToggle === true)
return dankIslandRouter.toggleHome(screen ?? null) === true;
return dankIslandRouter.openHome(screen ?? null) === true;
}
function routeNotificationCenterToIsland(screen, shouldToggle) {
if (!_islandOwnsSharedTrigger(screen))
return false;
if (shouldToggle === true)
return dankIslandRouter.toggleNotifications(screen ?? null) === true;
return dankIslandRouter.openNotifications(screen ?? null) === true;
}
function closeIslandNotificationCenter() {
return dankIslandRouter?.closeNotifications?.() === true;
}
function closeIslandMedia() {
return dankIslandRouter?.closeMedia?.() === true;
}
function closeIslandWallpaper() {
return dankIslandRouter?.closeWallpaper?.() === true;
}
function closeIslandWeather() {
return dankIslandRouter?.closeWeather?.() === true;
}
function closeIslandHome() {
return dankIslandRouter?.closeHome?.() === true;
function closeIslandActivity(activityId) {
return dankIslandRouter?.closeActivity?.(activityId) === true;
}
function openControlCenter(x, y, width, section, screen) {
if (_islandOwnsSharedTrigger(screen) && dankIslandRouter.openControlCenter(section || "", screen ?? null))
if (routeToIsland("controlcenter", screen, false, section))
return;
if (controlCenterPopout) {
setPosition(controlCenterPopout, x, y, width, section, screen);
@@ -227,7 +164,7 @@ Singleton {
}
function closeControlCenter() {
if (dankIslandRouter?.closeControlCenter?.() === true)
if (closeIslandActivity("controlcenter"))
return;
controlCenterPopout?.close();
}
@@ -237,7 +174,7 @@ Singleton {
}
function toggleControlCenter(x, y, width, section, screen) {
if (_islandOwnsSharedTrigger(screen) && dankIslandRouter.toggleControlCenter(section || "", screen ?? null))
if (routeToIsland("controlcenter", screen, true, section))
return;
if (controlCenterPopout) {
setPosition(controlCenterPopout, x, y, width, section, screen);
@@ -246,7 +183,7 @@ Singleton {
}
function openNotificationCenter(x, y, width, section, screen) {
if (routeNotificationCenterToIsland(screen, false))
if (routeToIsland("notificationcenter", screen, false))
return;
if (notificationCenterPopout) {
setPosition(notificationCenterPopout, x, y, width, section, screen);
@@ -255,7 +192,7 @@ Singleton {
}
function closeNotificationCenter() {
if (closeIslandNotificationCenter())
if (closeIslandActivity("notificationcenter"))
return;
notificationCenterPopout?.close();
}
@@ -265,7 +202,7 @@ Singleton {
}
function toggleNotificationCenter(x, y, width, section, screen) {
if (routeNotificationCenterToIsland(screen, true))
if (routeToIsland("notificationcenter", screen, true))
return;
if (notificationCenterPopout) {
setPosition(notificationCenterPopout, x, y, width, section, screen);
@@ -110,7 +110,7 @@ Item {
Connections {
target: CavaService
enabled: blobEffect.visible && root.available
enabled: root.blobActive
function onValuesChanged() {
root.updateBands();
@@ -147,7 +147,7 @@ Item {
Behavior on activation {
NumberAnimation {
duration: 550
duration: Theme.extraLongDuration
easing.type: Easing.InOutQuad
}
}
@@ -11987,6 +11987,33 @@
],
"description": "Solid material type or Outline"
},
{
"section": "dankIslandInteraction",
"label": "Behavior",
"tabIndex": 46,
"category": "Dank Island",
"keywords": [
"activities",
"behavior",
"click",
"compact",
"current",
"dank",
"dynamic island",
"expand",
"expands",
"face",
"hover",
"hybrid",
"intentional",
"interaction",
"island",
"peeks",
"press"
],
"icon": "touch_app",
"description": "Click expands only on an intentional press. Hybrid peeks the current compact face on hover"
},
{
"section": "dankIslandHomeCompactTight",
"label": "Compact Clock Pill",
@@ -12036,7 +12063,7 @@
"description": "Resize the resting island to align with nearby widgets"
},
{
"section": "dankIslandBarId",
"section": "dankIslandInstance",
"label": "Dank Island - Beta",
"tabIndex": 46,
"category": "Dank Island",
@@ -12126,6 +12153,23 @@
],
"description": "Floats above windows and other content"
},
{
"section": "dankIslandHoverCloseDelay",
"label": "Hide Delay",
"tabIndex": 46,
"category": "Dank Island",
"keywords": [
"activities",
"close",
"dank",
"delay",
"dynamic island",
"hide",
"hover",
"interaction",
"island"
]
},
{
"section": "dankIslandHighContrast",
"label": "High Contrast",
@@ -12200,64 +12244,6 @@
],
"description": "Move island and satellites left or right from display center"
},
{
"section": "dankIslandHoverCloseDelay",
"label": "Hover Close Delay",
"tabIndex": 46,
"category": "Dank Island",
"keywords": [
"activities",
"close",
"dank",
"delay",
"dynamic island",
"hover",
"interaction",
"island"
]
},
{
"section": "dankIslandHoverOpenDelay",
"label": "Hover Open Delay",
"tabIndex": 46,
"category": "Dank Island",
"keywords": [
"activities",
"dank",
"delay",
"dynamic island",
"hover",
"interaction",
"island",
"open"
]
},
{
"section": "dankIslandInteraction",
"label": "Interaction",
"tabIndex": 46,
"category": "Dank Island",
"keywords": [
"activities",
"click",
"compact",
"current",
"dank",
"dynamic island",
"expand",
"expands",
"face",
"hover",
"hybrid",
"intentional",
"interaction",
"island",
"peeks",
"press"
],
"icon": "touch_app",
"description": "Click expands only on an intentional press. Hybrid peeks the current compact face on hover"
},
{
"section": "_tab_46",
"label": "Island",
@@ -12287,6 +12273,23 @@
"widgets"
]
},
{
"section": "dankIslandBarId",
"label": "Island Instance",
"tabIndex": 46,
"category": "Dank Island",
"keywords": [
"activities",
"bar",
"dank",
"dynamic island",
"instance",
"island",
"media",
"notifications",
"osd"
]
},
{
"section": "dankIslandMediaClockVisible",
"label": "Keep Clock with Media",
@@ -12348,6 +12351,7 @@
"keywords": [
"accessibility",
"activities",
"animation",
"apply",
"changes",
"dank",
@@ -12357,12 +12361,29 @@
"island",
"motion",
"overshoot",
"reduce",
"spring",
"without"
],
"icon": "animation",
"description": "Apply island geometry changes immediately without spring overshoot"
},
{
"section": "dankIslandHoverOpenDelay",
"label": "Open Delay",
"tabIndex": 46,
"category": "Dank Island",
"keywords": [
"activities",
"dank",
"delay",
"dynamic island",
"hover",
"interaction",
"island",
"open"
]
},
{
"section": "dankIslandOuterGap",
"label": "Outer Gap",
@@ -12463,7 +12484,9 @@
"tabIndex": 46,
"category": "Dank Island",
"keywords": [
"accessibility",
"activities",
"animation",
"apply",
"changes",
"dank",
@@ -12553,6 +12576,7 @@
"category": "Dank Island",
"keywords": [
"activities",
"animation",
"bounce",
"damping",
"dank",
@@ -12560,6 +12584,7 @@
"higher",
"island",
"less",
"motion",
"settle",
"spring",
"values"
@@ -12573,6 +12598,7 @@
"category": "Dank Island",
"keywords": [
"activities",
"animation",
"dank",
"dynamic island",
"give",
@@ -12581,6 +12607,7 @@
"island",
"mass",
"more",
"motion",
"percentages",
"spring"
],
@@ -12593,11 +12620,13 @@
"category": "Dank Island",
"keywords": [
"activities",
"animation",
"dank",
"dynamic island",
"higher",
"island",
"more",
"motion",
"pull",
"spring",
"stiffness",