mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-10-24 12:07:47 -04:00
feat(island): state pulses, mic, privacy & duo status glyph activities
- caps lock, power profile, idle inhibitor and charging pulse in the island - microphone mute and volume as a level transient - privacy slot while mic, camera or screen share is in use - duo battery style: arc around the network glyph with signal dots
This commit is contained in:
1 parent
51f115f11e
commit
19abc7dc96
28 files changed
+776
-78
No files matched your search
@@ -973,7 +973,7 @@ Singleton {
|
||||
function islandStripThickness(bc) {
|
||||
return LayoutResolver.islandThickness(islandSettings(bc), islandDefaultsFor(bc));
|
||||
}
|
||||
readonly property var _islandHomeGroupIds: ["media", "clock", "weather", "status", "volume", "brightness", "notifications"]
|
||||
readonly property var _islandHomeGroupIds: ["media", "clock", "weather", "status", "volume", "brightness", "notifications", "privacy"]
|
||||
readonly property var _islandHomeLayoutDefault: [
|
||||
{
|
||||
"id": "media",
|
||||
@@ -1002,6 +1002,10 @@ Singleton {
|
||||
{
|
||||
"id": "notifications",
|
||||
"enabled": true
|
||||
},
|
||||
{
|
||||
"id": "privacy",
|
||||
"enabled": true
|
||||
}
|
||||
]
|
||||
function getIslandHomeLayout(bc) {
|
||||
|
||||
@@ -36,6 +36,7 @@ var ISLAND_DEFAULTS = {
|
||||
islandNotificationExpand: false,
|
||||
islandNotificationPopups: false,
|
||||
islandSystemOsd: true,
|
||||
islandChargingPulse: true,
|
||||
islandHomeCompactTight: false,
|
||||
islandHomeClockDisplay: "both",
|
||||
islandHomeVolumeDisplay: "both",
|
||||
|
||||
@@ -1280,7 +1280,7 @@ Item {
|
||||
}
|
||||
|
||||
Variants {
|
||||
model: SettingsData.getFilteredScreens("osd")
|
||||
model: root.legacySystemLevelOsdScreens
|
||||
|
||||
delegate: MicVolumeOSD {}
|
||||
}
|
||||
@@ -1292,19 +1292,19 @@ Item {
|
||||
}
|
||||
|
||||
Variants {
|
||||
model: SettingsData.getFilteredScreens("osd")
|
||||
model: root.legacySystemLevelOsdScreens
|
||||
|
||||
delegate: IdleInhibitorOSD {}
|
||||
}
|
||||
|
||||
Variants {
|
||||
model: SettingsData.osdPowerProfileEnabled ? SettingsData.getFilteredScreens("osd") : []
|
||||
model: SettingsData.osdPowerProfileEnabled ? root.legacySystemLevelOsdScreens : []
|
||||
|
||||
delegate: PowerProfileOSD {}
|
||||
}
|
||||
|
||||
Variants {
|
||||
model: SettingsData.getFilteredScreens("osd")
|
||||
model: root.legacySystemLevelOsdScreens
|
||||
|
||||
delegate: CapsLockOSD {}
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ CcTile {
|
||||
}
|
||||
active: !!audio && !audio.muted
|
||||
showExpand: true
|
||||
enabled: widgetDef?.enabled ?? true
|
||||
available: widgetDef?.enabled ?? true
|
||||
tallContent: Component {
|
||||
Item {
|
||||
DankRingGauge {
|
||||
|
||||
@@ -49,6 +49,8 @@ Item {
|
||||
property bool opensPage: false
|
||||
property color restIconColor: CcMetrics.tileInactiveIcon
|
||||
property Component tallContent: null
|
||||
// The tile's own state. The island disables a face while it fades in, and that must not restyle the tile.
|
||||
property bool available: true
|
||||
property bool interactive: true
|
||||
property bool iconBlinking: false
|
||||
property real iconRotation: 0
|
||||
@@ -68,29 +70,29 @@ Item {
|
||||
property bool acceptsInput: interactive && enabled
|
||||
readonly property bool bodyActive: showsActive && !hasIconBox
|
||||
readonly property color bodyColor: {
|
||||
if (!enabled)
|
||||
if (!available)
|
||||
return Theme.onSurface_12;
|
||||
return bodyActive ? CcMetrics.tileActiveColor : CcMetrics.tileInactiveColor;
|
||||
}
|
||||
readonly property color contentColor: {
|
||||
if (!enabled)
|
||||
if (!available)
|
||||
return Theme.onSurface_38;
|
||||
return bodyActive ? CcMetrics.tileActiveContent : CcMetrics.tileInactiveContent;
|
||||
}
|
||||
readonly property color subtitleColor: {
|
||||
if (!enabled)
|
||||
if (!available)
|
||||
return Theme.onSurface_38;
|
||||
return bodyActive ? CcMetrics.tileActiveContent : CcMetrics.tileInactiveSubtitle;
|
||||
}
|
||||
readonly property color iconColor: {
|
||||
if (!enabled)
|
||||
if (!available)
|
||||
return Theme.onSurface_38;
|
||||
if (hasIconBox)
|
||||
return showsActive ? CcMetrics.tileActiveContent : CcMetrics.tileInactiveContent;
|
||||
return bodyActive ? CcMetrics.tileActiveContent : root.restIconColor;
|
||||
}
|
||||
readonly property color iconBoxColor: {
|
||||
if (!enabled)
|
||||
if (!available)
|
||||
return Theme.onSurface_12;
|
||||
return showsActive ? CcMetrics.tileActiveColor : CcMetrics.iconBoxInactiveColor;
|
||||
}
|
||||
@@ -106,6 +108,7 @@ Item {
|
||||
|
||||
width: parent?.width ?? 0
|
||||
height: CcMetrics.tileHeight
|
||||
enabled: available
|
||||
activeFocusOnTab: acceptsInput
|
||||
Accessible.role: toggle && (!showExpand || compact) && !opensPage ? Accessible.CheckBox : Accessible.Button
|
||||
Accessible.checkable: toggle && (!showExpand || compact) && !opensPage
|
||||
|
||||
@@ -39,7 +39,7 @@ CcTile {
|
||||
}
|
||||
active: false
|
||||
opensPage: true
|
||||
enabled: DgopService.dgopAvailable
|
||||
available: DgopService.dgopAvailable
|
||||
tallContent: Component {
|
||||
Item {
|
||||
DankRingGauge {
|
||||
|
||||
@@ -95,7 +95,7 @@ CcTile {
|
||||
}
|
||||
}
|
||||
showExpand: true
|
||||
enabled: widgetDef?.enabled ?? true
|
||||
available: widgetDef?.enabled ?? true
|
||||
|
||||
onClicked: {
|
||||
if (status === "ethernet" || status === "cellular" || NetworkService.wifiToggling)
|
||||
|
||||
@@ -9,7 +9,7 @@ CcTile {
|
||||
iconName: NightModeService.nightModeEnabled ? "nightlight" : "dark_mode"
|
||||
title: I18n.tr("Night mode")
|
||||
active: NightModeService.nightModeEnabled || false
|
||||
enabled: NightModeService.automationAvailable
|
||||
available: NightModeService.automationAvailable
|
||||
|
||||
onClicked: NightModeService.toggleNightMode()
|
||||
expandedContent: Component {
|
||||
|
||||
@@ -21,7 +21,7 @@ CcTile {
|
||||
expandedContent: instance?.ccExpandedContent ?? null
|
||||
bodyContent: docked ? (instance?.ccFooterContent ?? null) : null
|
||||
expandedMinimumHeight: instance?.ccExpandedMinimumHeight ?? Theme.listItemHeight
|
||||
enabled: instance !== null
|
||||
available: instance !== null
|
||||
|
||||
onClicked: {
|
||||
if ((compact || !toggle) && hasDetail) {
|
||||
|
||||
@@ -109,12 +109,15 @@ BasePill {
|
||||
return parts.join(" ");
|
||||
}
|
||||
|
||||
// Percent always stays inside the pill; time and wattage show beside it.
|
||||
// Percent stays inside the pill unless the duo glyph occupies it; time and wattage show beside it.
|
||||
readonly property string horizontalSideText: {
|
||||
if (!pillStyle) {
|
||||
return horizontalDisplayText;
|
||||
}
|
||||
const parts = [];
|
||||
if (showPercent && batteryStyle === "duo") {
|
||||
parts.push(`${BatteryService.batteryLevel}%`);
|
||||
}
|
||||
if (showTime && batteryTimeText) {
|
||||
parts.push(batteryTimeText);
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ Item {
|
||||
required property var controller
|
||||
required property string activityId
|
||||
required property string label
|
||||
property string value: ""
|
||||
property string iconName: ""
|
||||
property color iconColor: Theme.primary
|
||||
property Component leading: null
|
||||
@@ -54,6 +55,14 @@ Item {
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
font.weight: Theme.fontWeightMedium
|
||||
}
|
||||
|
||||
StyledText {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
visible: root.value !== ""
|
||||
text: root.value
|
||||
color: Theme.surfaceTextSecondary
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
}
|
||||
}
|
||||
|
||||
// The pill is only as wide as the strip on a side edge, so the label is dropped.
|
||||
@@ -80,5 +89,13 @@ Item {
|
||||
size: Theme.iconSizeSmall
|
||||
color: root.iconColor
|
||||
}
|
||||
|
||||
StyledText {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
visible: root.value !== ""
|
||||
text: root.value
|
||||
color: Theme.surfaceTextSecondary
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -46,6 +46,8 @@ Item {
|
||||
return !!AudioService.sink?.audio;
|
||||
case "brightness":
|
||||
return BrightnessService.brightnessAvailable && !!root.brightnessDevice;
|
||||
case "privacy":
|
||||
return PrivacyService.anyPrivacyActive;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -81,6 +83,9 @@ Item {
|
||||
case "brightness":
|
||||
root.systemModel.open(groupId);
|
||||
return;
|
||||
case "privacy":
|
||||
root.controller.requestControlCenter("audioInput", false);
|
||||
return;
|
||||
}
|
||||
root.controller.requestControlCenter("", false);
|
||||
}
|
||||
@@ -226,6 +231,15 @@ Item {
|
||||
}
|
||||
}
|
||||
|
||||
component PrivacyIcon: DankIcon {
|
||||
required property bool active
|
||||
|
||||
visible: active
|
||||
size: root.statusIconSize
|
||||
color: Theme.error
|
||||
filled: true
|
||||
}
|
||||
|
||||
component GroupHoverArea: IslandSlotHoverArea {
|
||||
required property var group
|
||||
|
||||
@@ -243,6 +257,10 @@ Item {
|
||||
if (event.button === Qt.MiddleButton) {
|
||||
if (group.usesConnectivity)
|
||||
root.toggleConnectivity(connectivityTypeAt(event));
|
||||
if (group.isPrivacy && PrivacyService.microphoneActive && AudioService.source?.audio) {
|
||||
SessionData.suppressOSDTemporarily();
|
||||
AudioService.toggleMicMute();
|
||||
}
|
||||
if (group.isMedia && root.controller.mediaAvailable && MprisController.activePlayer?.canTogglePlaying)
|
||||
MprisController.activePlayer.togglePlaying();
|
||||
if (group.isVolume && AudioService.sink?.audio) {
|
||||
@@ -277,6 +295,7 @@ Item {
|
||||
readonly property bool isNotifications: group.groupId === "notifications"
|
||||
readonly property bool isVolume: group.groupId === "volume"
|
||||
readonly property bool isBrightness: group.groupId === "brightness"
|
||||
readonly property bool isPrivacy: group.groupId === "privacy"
|
||||
readonly property bool isSystemLevel: group.isVolume || group.isBrightness
|
||||
readonly property bool usesConnectivity: group.isStatus && root.controller.homeStatusContent === "connectivity"
|
||||
readonly property bool usesBattery: group.isStatus && !group.usesConnectivity && BatteryService.batteryAvailable
|
||||
@@ -394,6 +413,27 @@ Item {
|
||||
}
|
||||
}
|
||||
|
||||
Grid {
|
||||
visible: group.isPrivacy
|
||||
columns: group.vertical ? 1 : 3
|
||||
spacing: Theme.spacingXXS
|
||||
|
||||
PrivacyIcon {
|
||||
name: "mic"
|
||||
active: PrivacyService.microphoneActive
|
||||
}
|
||||
|
||||
PrivacyIcon {
|
||||
name: "camera_video"
|
||||
active: PrivacyService.cameraActive
|
||||
}
|
||||
|
||||
PrivacyIcon {
|
||||
name: "screen_share"
|
||||
active: PrivacyService.screensharingActive
|
||||
}
|
||||
}
|
||||
|
||||
DankIcon {
|
||||
visible: group.isStatus && !group.usesBattery && !group.usesConnectivity
|
||||
name: "tune"
|
||||
|
||||
@@ -37,7 +37,7 @@ Item {
|
||||
visible: !root.isVertical
|
||||
iconName: root.systemModel.iconName
|
||||
tonalIcon: false
|
||||
iconInteractive: root.systemModel.volumeActivity
|
||||
iconInteractive: root.systemModel.mutable
|
||||
iconLabel: root.systemModel.muted ? I18n.tr("Unmute") : I18n.tr("Mute")
|
||||
value: Math.round(root.systemModel.value)
|
||||
minimum: root.systemModel.minimum
|
||||
@@ -65,11 +65,11 @@ Item {
|
||||
anchors.top: parent.top
|
||||
name: root.systemModel.iconName
|
||||
size: Math.min(Theme.iconSize, root.iconSize)
|
||||
color: root.systemModel.volumeActivity ? Theme.surfaceText : Theme.primary
|
||||
color: root.systemModel.mutable ? Theme.surfaceText : Theme.primary
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
enabled: root.systemModel.volumeActivity
|
||||
enabled: root.systemModel.mutable
|
||||
cursorShape: enabled ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||
onClicked: root.systemModel.toggleMute()
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ Item {
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: root.systemModel.volumeActivity ? I18n.tr("Click the icon to mute", "island volume face: hint under the title") : I18n.tr("Display brightness control", "island brightness face: hint under the title")
|
||||
text: root.systemModel.mutable ? I18n.tr("Click the icon to mute", "island volume face: hint under the title") : I18n.tr("Display brightness control", "island brightness face: hint under the title")
|
||||
color: Theme.surfaceTextSecondary
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
}
|
||||
@@ -56,12 +56,13 @@ Item {
|
||||
width: parent.width
|
||||
size: "m"
|
||||
insetIcon: root.systemModel.iconName
|
||||
insetIconClickable: root.systemModel.volumeActivity
|
||||
insetIconClickable: root.systemModel.mutable
|
||||
insetIconLabel: root.systemModel.muted ? I18n.tr("Unmute") : I18n.tr("Mute")
|
||||
Accessible.name: root.systemModel.title
|
||||
onInsetIconClicked: root.systemModel.toggleMute()
|
||||
minimum: root.systemModel.minimum
|
||||
maximum: Math.max(root.systemModel.minimum + 1, Math.round(root.systemModel.maximum))
|
||||
visible: root.systemModel.levelActivity
|
||||
enabled: root.systemModel.available
|
||||
showValue: false
|
||||
unit: root.systemModel.unit
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
import qs.Common
|
||||
|
||||
DestinationCompact {
|
||||
id: root
|
||||
|
||||
required property var systemModel
|
||||
|
||||
readonly property bool linksControlCenter: root.systemModel.controlCenterSection !== ""
|
||||
|
||||
activityId: root.systemModel.kind
|
||||
iconName: root.systemModel.iconName
|
||||
label: root.systemModel.title
|
||||
value: root.systemModel.displayValue
|
||||
|
||||
IslandSlotHoverArea {
|
||||
anchors.fill: parent
|
||||
controller: root.controller
|
||||
cursorShape: root.linksControlCenter ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||
onClicked: {
|
||||
if (root.linksControlCenter) {
|
||||
root.controller.requestControlCenter(root.systemModel.controlCenterSection, false);
|
||||
return;
|
||||
}
|
||||
root.controller.requestCollapse();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -680,6 +680,14 @@ Item {
|
||||
Component {
|
||||
id: compactSystemComponent
|
||||
|
||||
Loader {
|
||||
sourceComponent: root.systemModel.levelActivity ? compactSystemLevelComponent : compactSystemStateComponent
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: compactSystemLevelComponent
|
||||
|
||||
SystemLevelCompact {
|
||||
systemModel: root.systemModel
|
||||
isVertical: root.isVertical
|
||||
@@ -687,6 +695,15 @@ Item {
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: compactSystemStateComponent
|
||||
|
||||
SystemStateCompact {
|
||||
systemModel: root.systemModel
|
||||
controller: root.controller
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: expandedSystemComponent
|
||||
|
||||
|
||||
@@ -424,6 +424,7 @@ Item {
|
||||
|
||||
controller: controller
|
||||
enabled: root.setting("islandSystemOsd") && root.barConfig?.visible !== false
|
||||
chargingPulseEnabled: root.setting("islandChargingPulse")
|
||||
}
|
||||
|
||||
IslandNotificationSource {
|
||||
|
||||
@@ -54,7 +54,15 @@ Item {
|
||||
readonly property real outgoingCompactFade: root.fadeCompact(root.outgoingMorph)
|
||||
readonly property real outgoingExpandedFade: root.fadeExpanded(root.outgoingMorph)
|
||||
readonly property bool mediaSurfaceActive: root.surfaceActive("media")
|
||||
readonly property bool systemSurfaceActive: root.surfaceActive("volume") || root.surfaceActive("brightness")
|
||||
readonly property bool systemSurfaceActive: root.controller.systemActivities.some(id => root.surfaceActive(id))
|
||||
|
||||
function systemCompactOpacity() {
|
||||
return Math.max(0, ...root.controller.systemActivities.map(id => root.compactOpacity(id)));
|
||||
}
|
||||
|
||||
function systemExpandedOpacity() {
|
||||
return Math.max(0, ...root.controller.systemActivities.map(id => root.expandedOpacity(id)));
|
||||
}
|
||||
|
||||
property string renderedActivity: "home"
|
||||
property string outgoingActivity: ""
|
||||
@@ -342,7 +350,7 @@ Item {
|
||||
active: root.systemSurfaceActive
|
||||
activity: "volume"
|
||||
face: root.systemCompactComponent
|
||||
opacity: Math.max(root.compactOpacity("volume"), root.compactOpacity("brightness"))
|
||||
opacity: root.systemCompactOpacity()
|
||||
}
|
||||
|
||||
ExpandedFace {
|
||||
@@ -350,7 +358,7 @@ Item {
|
||||
active: root.systemSurfaceActive && (root.expanded || root.expandedFade > 0)
|
||||
asynchronous: false
|
||||
sourceComponent: root.systemExpandedComponent
|
||||
opacity: Math.max(root.expandedOpacity("volume"), root.expandedOpacity("brightness"))
|
||||
opacity: root.systemExpandedOpacity()
|
||||
}
|
||||
|
||||
CompactFace {
|
||||
|
||||
@@ -236,6 +236,10 @@ QtObject {
|
||||
}
|
||||
|
||||
function setDestinationContentLength(activityId, length) {
|
||||
if (isSystemActivity(activityId)) {
|
||||
setSystemStateContentLength(length);
|
||||
return;
|
||||
}
|
||||
const entry = destinationState[activityId];
|
||||
const next = Math.ceil(length);
|
||||
if (!entry || !isFinite(next) || next <= 0 || Math.abs(next - entry.contentLength) < 2)
|
||||
@@ -429,7 +433,13 @@ QtObject {
|
||||
"bottomLeftRadius": root.dotSize / 2,
|
||||
"bottomRightRadius": root.dotSize / 2
|
||||
})
|
||||
readonly property var dotTransientActivities: ["notification", "volume", "brightness"]
|
||||
readonly property var systemLevelActivities: ["volume", "brightness", "mic"]
|
||||
readonly property var systemStateActivities: ["capslock", "powerprofile", "idleinhibitor", "charging"]
|
||||
readonly property var systemActivities: systemLevelActivities.concat(systemStateActivities)
|
||||
readonly property var dotTransientActivities: ["notification"].concat(systemActivities)
|
||||
function isSystemActivity(activityId) {
|
||||
return root.systemActivities.indexOf(activityId) !== -1;
|
||||
}
|
||||
// Transients keep their island pill so the dot can still show a notification or a level.
|
||||
function usesDotFace(activityId) {
|
||||
return root.dotMode && root.dotTransientActivities.indexOf(activityId) === -1;
|
||||
@@ -441,13 +451,15 @@ QtObject {
|
||||
readonly property var launcherExpandedTarget: sheetTarget(Math.min(dashboardAvailableWidth, LauncherMetrics.sizeWidth(SettingsData.dankLauncherV2Size)), Math.min(dashboardAvailableHeight, LauncherMetrics.sizeHeight(SettingsData.dankLauncherV2Size)))
|
||||
readonly property var controlCenterExpandedTarget: sheetTarget(controlCenterSheetWidth + controlCenterSheetInset, controlCenterHeight)
|
||||
readonly property var systemCompactTarget: pillTarget(root.isVertical ? 240 : (SettingsData.osdAlwaysShowValue ? 330 : 282), compactFaceThickness)
|
||||
property real systemStateContentLength: 0
|
||||
readonly property var systemStateCompactTarget: pillTarget(Math.ceil(Math.max(compactFaceThickness, systemStateContentLength + destinationCompactEndPad * 2)), compactFaceThickness)
|
||||
readonly property var systemExpandedTarget: sheetTarget(460, 176)
|
||||
readonly property var notificationCompactTarget: pillTarget(Math.ceil(Math.max(notificationCompactMinLength, Math.min(notificationCompactMaxLength, notificationContentLength))), compactFaceThickness)
|
||||
readonly property var notificationExpandedTarget: sheetTarget(520, 220)
|
||||
readonly property var notificationCenterExpandedTarget: dashboardTargetFor("notificationcenter")
|
||||
readonly property var clipboardExpandedTarget: sheetTarget(Math.min(dashboardAvailableWidth, ClipboardConstants.sizeWidth(SettingsData.clipboardSize)), Math.min(dashboardAvailableHeight, ClipboardConstants.sizeHeight(SettingsData.clipboardSize)))
|
||||
|
||||
readonly property bool systemActivityActive: activeActivity === "volume" || activeActivity === "brightness"
|
||||
readonly property bool systemActivityActive: isSystemActivity(activeActivity)
|
||||
readonly property bool notificationActive: activeActivity === "notification"
|
||||
readonly property bool notificationHeldForSystem: root.systemActivityActive && root.transientReturnActivity === "notification"
|
||||
readonly property bool transientActive: systemActivityActive || notificationActive
|
||||
@@ -460,14 +472,15 @@ QtObject {
|
||||
switch (activityId) {
|
||||
case "notification":
|
||||
return notificationCompactTarget;
|
||||
case "volume":
|
||||
case "brightness":
|
||||
return systemCompactTarget;
|
||||
case "media":
|
||||
return mediaCompactTarget;
|
||||
case "home":
|
||||
return homeCompactTarget;
|
||||
}
|
||||
if (systemLevelActivities.indexOf(activityId) !== -1)
|
||||
return systemCompactTarget;
|
||||
if (systemStateActivities.indexOf(activityId) !== -1)
|
||||
return systemStateCompactTarget;
|
||||
return isDestination(activityId) ? pillTarget(destinationCompactLength(activityId), compactFaceThickness) : homeCompactTarget;
|
||||
}
|
||||
|
||||
@@ -478,9 +491,6 @@ QtObject {
|
||||
return homeExpandedTarget;
|
||||
case "notification":
|
||||
return notificationExpandedTarget;
|
||||
case "volume":
|
||||
case "brightness":
|
||||
return systemExpandedTarget;
|
||||
case "launcher":
|
||||
return launcherExpandedTarget;
|
||||
case "controlcenter":
|
||||
@@ -492,7 +502,7 @@ QtObject {
|
||||
case "media":
|
||||
return mediaExpandedTarget;
|
||||
}
|
||||
return dashboardTargetFor(activityId);
|
||||
return isSystemActivity(activityId) ? systemExpandedTarget : dashboardTargetFor(activityId);
|
||||
}
|
||||
|
||||
readonly property var expandedTarget: expandedTargetFor(activeActivity)
|
||||
@@ -758,8 +768,15 @@ QtObject {
|
||||
activeActivity = returnActivity;
|
||||
}
|
||||
|
||||
function setSystemStateContentLength(length) {
|
||||
const next = Math.ceil(length);
|
||||
if (!isFinite(next) || next <= 0 || Math.abs(next - systemStateContentLength) < 2)
|
||||
return;
|
||||
systemStateContentLength = next;
|
||||
}
|
||||
|
||||
function requestSystemActivity(activityId) {
|
||||
if (activityId !== "volume" && activityId !== "brightness")
|
||||
if (!isSystemActivity(activityId))
|
||||
return false;
|
||||
|
||||
launcherSessionActive = false;
|
||||
|
||||
@@ -10,21 +10,114 @@ QtObject {
|
||||
required property IslandController controller
|
||||
|
||||
property string kind: "volume"
|
||||
// Off: volume and brightness keep their OSDs and only the face's own controls open this activity.
|
||||
// Off: system levels and state changes keep their OSDs and only the face's own controls open this activity.
|
||||
property bool enabled: true
|
||||
property bool chargingPulseEnabled: true
|
||||
|
||||
readonly property bool volumeActivity: kind === "volume"
|
||||
readonly property bool available: volumeActivity ? !!AudioService.sink?.audio : BrightnessService.brightnessAvailable
|
||||
readonly property bool muted: volumeActivity && (AudioService.sink?.audio?.muted ?? false)
|
||||
readonly property real value: volumeActivity ? AudioService.sinkVolumePercent : BrightnessService.brightnessLevel
|
||||
readonly property bool micActivity: kind === "mic"
|
||||
readonly property bool brightnessActivity: kind === "brightness"
|
||||
readonly property bool levelActivity: volumeActivity || micActivity || brightnessActivity
|
||||
readonly property var audio: volumeActivity ? AudioService.sink?.audio : (micActivity ? AudioService.source?.audio : null)
|
||||
readonly property bool available: levelActivity ? (brightnessActivity ? BrightnessService.brightnessAvailable : !!audio) : true
|
||||
readonly property bool muted: !!audio && (audio.muted ?? false)
|
||||
readonly property bool mutable: volumeActivity || micActivity
|
||||
readonly property real value: {
|
||||
switch (kind) {
|
||||
case "volume":
|
||||
return AudioService.sinkVolumePercent;
|
||||
case "mic":
|
||||
return AudioService.sourceVolumePercent;
|
||||
case "brightness":
|
||||
return BrightnessService.brightnessLevel;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
readonly property var brightnessDevice: BrightnessService.getCurrentDeviceInfo()
|
||||
readonly property real maximum: volumeActivity ? AudioService.sinkMaxVolume : BrightnessService.brightnessMaximum(brightnessDevice)
|
||||
readonly property int minimum: volumeActivity ? 0 : BrightnessService.brightnessMinimum(brightnessDevice)
|
||||
readonly property real maximum: {
|
||||
switch (kind) {
|
||||
case "volume":
|
||||
return AudioService.sinkMaxVolume;
|
||||
case "brightness":
|
||||
return BrightnessService.brightnessMaximum(brightnessDevice);
|
||||
}
|
||||
return 100;
|
||||
}
|
||||
readonly property int minimum: brightnessActivity ? BrightnessService.brightnessMinimum(brightnessDevice) : 0
|
||||
readonly property real ratio: maximum > 0 ? Math.max(0, Math.min(1, value / maximum)) : 0
|
||||
readonly property string title: volumeActivity ? I18n.tr("Volume", "island system face: volume title") : I18n.tr("Brightness", "island system face: brightness title")
|
||||
readonly property string unit: volumeActivity ? "%" : BrightnessService.brightnessUnit(brightnessDevice)
|
||||
readonly property string displayValue: muted ? I18n.tr("Muted", "island system face: muted value label") : Math.round(value) + unit
|
||||
readonly property string iconName: volumeActivity ? AudioService.sinkVolumeIconName : BrightnessService.brightnessIconName(brightnessDevice, value)
|
||||
readonly property string title: {
|
||||
switch (kind) {
|
||||
case "volume":
|
||||
return I18n.tr("Volume", "island system face: volume title");
|
||||
case "mic":
|
||||
return I18n.tr("Microphone", "island system face: microphone title");
|
||||
case "brightness":
|
||||
return I18n.tr("Brightness", "island system face: brightness title");
|
||||
case "capslock":
|
||||
return I18n.tr("Caps lock", "island system face: caps lock title");
|
||||
case "powerprofile":
|
||||
return I18n.tr("Power profile", "island system face: power profile title");
|
||||
case "idleinhibitor":
|
||||
return I18n.tr("Idle inhibitor", "island system face: idle inhibitor title");
|
||||
case "charging":
|
||||
return BatteryService.isPluggedIn ? I18n.tr("Plugged in", "island system face: power connected title") : I18n.tr("Discharging", "island system face: power disconnected title");
|
||||
}
|
||||
return "";
|
||||
}
|
||||
readonly property string unit: brightnessActivity ? BrightnessService.brightnessUnit(brightnessDevice) : "%"
|
||||
readonly property string displayValue: {
|
||||
switch (kind) {
|
||||
case "capslock":
|
||||
return DMSService.capsLockState ? I18n.tr("On") : I18n.tr("Off");
|
||||
case "powerprofile":
|
||||
return Theme.getPowerProfileLabel(PowerProfileWatcher.currentProfile);
|
||||
case "idleinhibitor":
|
||||
return SessionService.idleInhibited ? I18n.tr("On") : I18n.tr("Off");
|
||||
case "charging":
|
||||
return root.chargingValue();
|
||||
}
|
||||
return muted ? I18n.tr("Muted", "island system face: muted value label") : Math.round(value) + unit;
|
||||
}
|
||||
readonly property string iconName: {
|
||||
switch (kind) {
|
||||
case "volume":
|
||||
return AudioService.sinkVolumeIconName;
|
||||
case "mic":
|
||||
return muted ? "mic_off" : "mic";
|
||||
case "brightness":
|
||||
return BrightnessService.brightnessIconName(brightnessDevice, value);
|
||||
case "capslock":
|
||||
return DMSService.capsLockState ? "shift_lock" : "shift_lock_off";
|
||||
case "powerprofile":
|
||||
return Theme.getPowerProfileIcon(PowerProfileWatcher.currentProfile);
|
||||
case "idleinhibitor":
|
||||
return SessionService.idleInhibited ? "motion_sensor_active" : "motion_sensor_idle";
|
||||
case "charging":
|
||||
return BatteryService.getBatteryIcon();
|
||||
}
|
||||
return "";
|
||||
}
|
||||
// Level pulses carry their own controls, so only state pulses link out.
|
||||
readonly property string controlCenterSection: {
|
||||
switch (kind) {
|
||||
case "mic":
|
||||
return "audioInput";
|
||||
case "powerprofile":
|
||||
case "charging":
|
||||
return "battery";
|
||||
case "idleinhibitor":
|
||||
return "idleInhibitor";
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
function chargingValue() {
|
||||
const level = Math.round(BatteryService.batteryLevel) + "%";
|
||||
const seconds = BatteryService.estimatedSeconds();
|
||||
if (!seconds)
|
||||
return level;
|
||||
return level + " · " + BatteryService.formatTimeRemaining();
|
||||
}
|
||||
|
||||
function show(activityKind) {
|
||||
if (!enabled || SessionData.suppressOSD)
|
||||
@@ -40,17 +133,27 @@ QtObject {
|
||||
function setRatio(nextRatio) {
|
||||
const clampedRatio = Math.max(0, Math.min(1, nextRatio));
|
||||
SessionData.suppressOSDTemporarily();
|
||||
if (volumeActivity) {
|
||||
switch (kind) {
|
||||
case "volume":
|
||||
AudioService.setVolume(Math.round(clampedRatio * maximum));
|
||||
return;
|
||||
case "mic":
|
||||
AudioService.setMicVolume(Math.round(clampedRatio * maximum));
|
||||
return;
|
||||
case "brightness":
|
||||
BrightnessService.setBrightness(Math.round(clampedRatio * maximum), BrightnessService.lastIpcDevice, true);
|
||||
return;
|
||||
}
|
||||
BrightnessService.setBrightness(Math.round(clampedRatio * maximum), BrightnessService.lastIpcDevice, true);
|
||||
}
|
||||
|
||||
function toggleMute() {
|
||||
if (!volumeActivity || !available)
|
||||
if (!mutable || !available)
|
||||
return;
|
||||
SessionData.suppressOSDTemporarily();
|
||||
if (micActivity) {
|
||||
AudioService.toggleMicMute();
|
||||
return;
|
||||
}
|
||||
AudioService.toggleMute();
|
||||
}
|
||||
|
||||
@@ -68,6 +171,24 @@ QtObject {
|
||||
}
|
||||
}
|
||||
|
||||
property Connections micConnection: Connections {
|
||||
target: AudioService.source?.audio ?? null
|
||||
|
||||
function onMutedChanged() {
|
||||
if (SettingsData.osdMicMuteEnabled)
|
||||
root.show("mic");
|
||||
}
|
||||
}
|
||||
|
||||
property Connections micServiceConnection: Connections {
|
||||
target: AudioService
|
||||
|
||||
function onMicVolumeChanged() {
|
||||
if (SettingsData.osdMicVolumeEnabled)
|
||||
root.show("mic");
|
||||
}
|
||||
}
|
||||
|
||||
property Connections brightnessConnection: Connections {
|
||||
target: BrightnessService
|
||||
|
||||
@@ -76,4 +197,40 @@ QtObject {
|
||||
root.show("brightness");
|
||||
}
|
||||
}
|
||||
|
||||
property Connections capsLockConnection: Connections {
|
||||
target: DMSService
|
||||
|
||||
function onCapsLockStateChanged() {
|
||||
if (SettingsData.osdCapsLockEnabled)
|
||||
root.show("capslock");
|
||||
}
|
||||
}
|
||||
|
||||
property Connections powerProfileConnection: Connections {
|
||||
target: PowerProfileWatcher
|
||||
|
||||
function onProfileChanged() {
|
||||
if (SettingsData.osdPowerProfileEnabled)
|
||||
root.show("powerprofile");
|
||||
}
|
||||
}
|
||||
|
||||
property Connections idleInhibitorConnection: Connections {
|
||||
target: SessionService
|
||||
|
||||
function onInhibitorChanged() {
|
||||
if (SettingsData.osdIdleInhibitorEnabled)
|
||||
root.show("idleinhibitor");
|
||||
}
|
||||
}
|
||||
|
||||
property Connections batteryConnection: Connections {
|
||||
target: BatteryService
|
||||
|
||||
function onIsPluggedInChanged() {
|
||||
if (root.chargingPulseEnabled && BatteryService.batteryAvailable)
|
||||
root.show("charging");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,7 @@ Column {
|
||||
|
||||
property var page: null
|
||||
|
||||
readonly property var styleValues: ["icon", "solid", "outline", "ring"]
|
||||
readonly property var styleValues: ["icon", "solid", "outline", "ring", "duo"]
|
||||
readonly property var barConfig: {
|
||||
SettingsData.barConfigs;
|
||||
return SettingsData.getBarConfig(page?.barId ?? "");
|
||||
@@ -84,7 +84,7 @@ Column {
|
||||
resetStore: root.page
|
||||
resetKeys: ["batteryStyle"]
|
||||
text: I18n.tr("Battery style")
|
||||
model: [I18n.tr("Icon", "battery widget: system battery glyph"), I18n.tr("Solid", "island settings: filled battery meter style"), I18n.tr("Outline", "island settings: outlined battery meter style"), I18n.tr("Circle", "island settings: circular battery meter style")]
|
||||
model: [I18n.tr("Icon", "battery widget: system battery glyph"), I18n.tr("Solid", "island settings: filled battery meter style"), I18n.tr("Outline", "island settings: outlined battery meter style"), I18n.tr("Circle", "island settings: circular battery meter style"), I18n.tr("Duo", "battery meter style: open battery arc around the network glyph")]
|
||||
currentIndex: Math.max(0, root.styleValues.indexOf(root.page.value("batteryStyle")))
|
||||
onSelectionChanged: (index, selected) => {
|
||||
if (selected)
|
||||
|
||||
@@ -14,7 +14,7 @@ SettingsCard {
|
||||
readonly property var clockDisplayValues: ["time", "date", "both"]
|
||||
readonly property var systemLevelDisplayValues: ["icon", "percentage", "both"]
|
||||
readonly property var statusContentValues: ["battery", "connectivity"]
|
||||
readonly property var batteryStyleValues: ["solid", "outline", "ring"]
|
||||
readonly property var batteryStyleValues: ["solid", "outline", "ring", "duo"]
|
||||
readonly property bool batteryShown: SettingsData.islandHomeGroupEnabled(root.store.config, "status") && BatteryService.batteryAvailable && SettingsData.islandHomeStatusContent(root.store.config) === "battery"
|
||||
|
||||
|
||||
@@ -110,12 +110,12 @@ SettingsCard {
|
||||
|
||||
SettingsButtonGroupRow {
|
||||
settingKey: root.keyPrefix + "BatteryStyle"
|
||||
tags: ["island", "battery", "gauge", "solid", "outline", "ring", "circle", "appearance"]
|
||||
tags: ["island", "battery", "gauge", "solid", "outline", "ring", "circle", "duo", "wifi", "appearance"]
|
||||
resetStore: root.store
|
||||
resetKeys: ["islandBatteryStyle"]
|
||||
text: I18n.tr("Battery style", "island settings: battery meter style row")
|
||||
visible: root.batteryShown
|
||||
model: [I18n.tr("Solid", "island settings: filled battery meter style"), I18n.tr("Outline", "island settings: outlined battery meter style"), I18n.tr("Circle", "island settings: circular battery meter style")]
|
||||
model: [I18n.tr("Solid", "island settings: filled battery meter style"), I18n.tr("Outline", "island settings: outlined battery meter style"), I18n.tr("Circle", "island settings: circular battery meter style"), I18n.tr("Duo", "battery meter style: open battery arc around the network glyph")]
|
||||
values: root.batteryStyleValues
|
||||
value: root.store.setting("islandBatteryStyle")
|
||||
fallbackValue: "solid"
|
||||
|
||||
@@ -55,6 +55,11 @@ Column {
|
||||
"icon": "notifications",
|
||||
"text": I18n.tr("Notifications", "island settings: notification badge slot row"),
|
||||
"description": ""
|
||||
},
|
||||
"privacy": {
|
||||
"icon": "privacy_tip",
|
||||
"text": I18n.tr("Privacy", "island settings: microphone, camera and screen share indicator slot row"),
|
||||
"description": I18n.tr("Shown only while the microphone, camera or screen sharing is in use", "island settings: privacy slot hint")
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import QtQuick
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
|
||||
SettingsCard {
|
||||
id: root
|
||||
@@ -27,7 +28,7 @@ SettingsCard {
|
||||
|
||||
SettingsToggleRow {
|
||||
settingKey: root.keyPrefix + "SystemOsd"
|
||||
tags: ["island", "volume", "brightness", "osd", "level", "standard"]
|
||||
tags: ["island", "volume", "brightness", "microphone", "caps lock", "power profile", "idle inhibitor", "osd", "level", "standard"]
|
||||
resetStore: root.store
|
||||
resetKeys: ["islandSystemOsd"]
|
||||
text: I18n.tr("Use standard OSDs", "island settings: show volume and brightness changes as the regular on-screen displays instead of in the island")
|
||||
@@ -35,6 +36,18 @@ SettingsCard {
|
||||
onToggled: checked => root.store.apply("islandSystemOsd", !checked)
|
||||
}
|
||||
|
||||
SettingsToggleRow {
|
||||
settingKey: root.keyPrefix + "ChargingPulse"
|
||||
tags: ["island", "battery", "charging", "plugged", "power", "pulse"]
|
||||
resetStore: root.store
|
||||
resetKeys: ["islandChargingPulse"]
|
||||
text: I18n.tr("Charging", "island settings: pulse the island when power is connected or removed")
|
||||
description: I18n.tr("Show the battery briefly when power is connected or removed", "island settings: charging pulse toggle description")
|
||||
visible: root.store.setting("islandSystemOsd") && BatteryService.batteryAvailable
|
||||
checked: root.store.setting("islandChargingPulse")
|
||||
onToggled: checked => root.store.apply("islandChargingPulse", checked)
|
||||
}
|
||||
|
||||
SettingsToggleRow {
|
||||
settingKey: root.keyPrefix + "NotificationExpand"
|
||||
tags: ["island", "notifications", "expand", "arrival", "size"]
|
||||
|
||||
@@ -22,6 +22,12 @@ Item {
|
||||
|
||||
readonly property bool outlined: root.meterStyle === "outline"
|
||||
readonly property bool ring: root.meterStyle === "ring"
|
||||
// Duo: an open arc around the network glyph, signal dots in the gap.
|
||||
readonly property bool duo: root.meterStyle === "duo"
|
||||
readonly property bool circular: root.ring || root.duo
|
||||
readonly property real arcStart: root.duo ? 150 : -90
|
||||
readonly property real arcSpan: root.duo ? 240 : 360
|
||||
readonly property real chargeTarget: Math.max(root.level, Math.min(100, SettingsData.batteryChargeLimit))
|
||||
readonly property real unit: root.thickness / 14
|
||||
readonly property real level: Math.max(0, Math.min(100, BatteryService.batteryLevel))
|
||||
readonly property bool charging: BatteryService.batteryAvailable && BatteryService.isCharging
|
||||
@@ -39,6 +45,8 @@ Item {
|
||||
}
|
||||
readonly property color dimColor: Theme.withAlpha(root.fillColor, root.hovered ? 0.6 : 0.48)
|
||||
readonly property color trackColor: {
|
||||
if (root.duo)
|
||||
return Theme.withAlpha(root.fillColor, root.hovered ? 0.3 : 0.16);
|
||||
if (root.ring)
|
||||
return Theme.withAlpha(root.fillColor, root.hovered ? 0.4 : 0.26);
|
||||
if (root.outlined)
|
||||
@@ -52,7 +60,7 @@ Item {
|
||||
}
|
||||
readonly property int glyphWeight: Theme.fontWeight
|
||||
readonly property string numberText: Math.round(root.level).toString()
|
||||
readonly property bool boltVisible: root.charging && root.showBolt
|
||||
readonly property bool boltVisible: root.charging && root.showBolt && !root.duo
|
||||
readonly property bool numberInside: !root.vertical && root.showNumber && BatteryService.batteryAvailable
|
||||
readonly property bool ringNumberVisible: root.ring && root.showNumber && !root.boltVisible && BatteryService.batteryAvailable
|
||||
readonly property real strokeWidth: root.outlined ? 1.5 * root.unit : 0
|
||||
@@ -72,9 +80,9 @@ Item {
|
||||
readonly property real ringInnerRadius: Math.max(1, (root.ringDiameter - 2 * root.ringStroke) / 2)
|
||||
readonly property real ringChordWidth: 2 * Math.sqrt(Math.max(1, Math.pow(root.ringInnerRadius, 2) - Math.pow(fitMetrics.tightBoundingRect.height / 2, 2)))
|
||||
readonly property real ringTextSize: root.textNeed > 0 ? Math.min(root.baseTextSize, root.baseTextSize * root.ringChordWidth / root.textNeed) : root.baseTextSize
|
||||
readonly property real textSize: root.ring ? root.ringTextSize : root.baseTextSize
|
||||
readonly property real textSize: root.circular ? root.ringTextSize : root.baseTextSize
|
||||
readonly property real textBaseline: root.height / 2 - digitInk.tightBoundingRect.y - digitInk.tightBoundingRect.height / 2
|
||||
readonly property real boltBadgeSize: Math.round((root.ring ? 12 : 9) * root.unit)
|
||||
readonly property real boltBadgeSize: Math.round((root.circular ? 12 : 9) * root.unit)
|
||||
readonly property real boltBadgeWidth: Math.round(root.boltBadgeSize * (8 / 13))
|
||||
readonly property real bodyLength: Math.max(Math.round(25 * root.unit), Math.ceil(root.numberInside ? root.textNeed + root.textCanvasLeft + 1.5 * root.unit : 0))
|
||||
readonly property real capGap: Math.max(1, Math.round(root.unit))
|
||||
@@ -82,8 +90,8 @@ Item {
|
||||
readonly property real capBreadth: Math.max(1, Math.round(1.25 * root.unit))
|
||||
readonly property real capSpan: Math.round(6 * root.unit)
|
||||
|
||||
implicitWidth: root.ring ? root.ringDiameter : root.vertical ? Math.round(14 * root.unit) : root.capOffset + (root.boltVisible ? root.boltBadgeWidth : root.capBreadth)
|
||||
implicitHeight: root.ring ? root.ringDiameter : root.vertical ? root.capOffset + root.capBreadth : Math.round(14 * root.unit)
|
||||
implicitWidth: root.circular ? root.ringDiameter : root.vertical ? Math.round(14 * root.unit) : root.capOffset + (root.boltVisible ? root.boltBadgeWidth : root.capBreadth)
|
||||
implicitHeight: root.circular ? root.ringDiameter : root.vertical ? root.capOffset + root.capBreadth : Math.round(14 * root.unit)
|
||||
|
||||
StyledTextMetrics {
|
||||
id: fitMetrics
|
||||
@@ -151,7 +159,7 @@ Item {
|
||||
Shape {
|
||||
id: gauge
|
||||
|
||||
property real sweep: root.level * 3.6
|
||||
property real sweep: root.level / 100 * root.arcSpan
|
||||
|
||||
anchors.fill: parent
|
||||
preferredRendererType: Shape.CurveRenderer
|
||||
@@ -174,8 +182,24 @@ Item {
|
||||
centerY: root.ringDiameter / 2
|
||||
radiusX: root.ringRadius
|
||||
radiusY: root.ringRadius
|
||||
startAngle: -90
|
||||
sweepAngle: 360
|
||||
startAngle: root.arcStart
|
||||
sweepAngle: root.arcSpan
|
||||
}
|
||||
}
|
||||
|
||||
ShapePath {
|
||||
fillColor: "transparent"
|
||||
strokeColor: root.duo && root.charging ? Theme.withAlpha(root.fillColor, 0.45) : "transparent"
|
||||
strokeWidth: root.ringStroke
|
||||
capStyle: ShapePath.RoundCap
|
||||
|
||||
PathAngleArc {
|
||||
centerX: root.ringDiameter / 2
|
||||
centerY: root.ringDiameter / 2
|
||||
radiusX: root.ringRadius
|
||||
radiusY: root.ringRadius
|
||||
startAngle: root.arcStart + gauge.sweep
|
||||
sweepAngle: Math.max(0, (root.chargeTarget - root.level) / 100 * root.arcSpan)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -190,7 +214,7 @@ Item {
|
||||
centerY: root.ringDiameter / 2
|
||||
radiusX: root.ringRadius
|
||||
radiusY: root.ringRadius
|
||||
startAngle: -90
|
||||
startAngle: root.arcStart
|
||||
sweepAngle: gauge.sweep
|
||||
}
|
||||
}
|
||||
@@ -198,13 +222,81 @@ Item {
|
||||
}
|
||||
|
||||
Loader {
|
||||
active: root.ring
|
||||
active: root.circular
|
||||
anchors.centerIn: parent
|
||||
width: root.ringDiameter
|
||||
height: root.ringDiameter
|
||||
sourceComponent: ringGauge
|
||||
}
|
||||
|
||||
Loader {
|
||||
active: root.duo
|
||||
anchors.centerIn: parent
|
||||
width: root.ringDiameter
|
||||
height: root.ringDiameter
|
||||
sourceComponent: duoFace
|
||||
}
|
||||
|
||||
Component {
|
||||
id: duoFace
|
||||
|
||||
Item {
|
||||
id: face
|
||||
|
||||
readonly property string networkIcon: {
|
||||
if (NetworkService.wifiToggling)
|
||||
return "sync";
|
||||
switch (NetworkService.networkStatus) {
|
||||
case "ethernet":
|
||||
return "lan";
|
||||
case "cellular":
|
||||
return "network_cell";
|
||||
case "vpn":
|
||||
return NetworkService.ethernetConnected ? "lan" : (NetworkService.cellularConnected ? "network_cell" : NetworkService.wifiSignalIcon);
|
||||
}
|
||||
return NetworkService.wifiEnabled && NetworkService.wifiAvailable ? NetworkService.wifiSignalIcon : "wifi_off";
|
||||
}
|
||||
// Inner pair first so a partial signal stays symmetric.
|
||||
readonly property int litDots: NetworkService.wifiConnected ? (NetworkService.wifiSignalStrength > 50 ? 4 : 2) : 0
|
||||
readonly property real dotSize: Math.max(2, Math.round(root.ringStroke * 1.25))
|
||||
readonly property int glyphInset: Math.round(root.ringDiameter * 0.2)
|
||||
|
||||
DankIcon {
|
||||
id: glyph
|
||||
|
||||
x: face.glyphInset
|
||||
y: face.glyphInset
|
||||
name: face.networkIcon
|
||||
size: root.ringDiameter - 2 * face.glyphInset
|
||||
color: NetworkService.networkStatus !== "disconnected" ? Theme.surfaceText : Theme.surfaceTextMedium
|
||||
|
||||
DankBlink {
|
||||
target: glyph
|
||||
running: face.visible && (NetworkService.wifiToggling || NetworkService.isWifiConnecting)
|
||||
}
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: 4
|
||||
|
||||
Rectangle {
|
||||
id: dot
|
||||
|
||||
required property int index
|
||||
readonly property real angle: (54 + dot.index * 24) * Math.PI / 180
|
||||
readonly property bool lit: dot.index === 1 || dot.index === 2 ? face.litDots >= 2 : face.litDots >= 4
|
||||
|
||||
x: root.ringDiameter / 2 + Math.cos(dot.angle) * root.ringRadius - width / 2
|
||||
y: root.ringDiameter / 2 + Math.sin(dot.angle) * root.ringRadius - height / 2
|
||||
width: face.dotSize
|
||||
height: face.dotSize
|
||||
radius: width / 2
|
||||
color: dot.lit ? Theme.surfaceText : Theme.withAlpha(Theme.surfaceText, 0.16)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NumericText {
|
||||
isMonospace: false
|
||||
visible: root.ringNumberVisible
|
||||
@@ -224,7 +316,7 @@ Item {
|
||||
width: root.vertical ? root.capSpan : root.capBreadth
|
||||
height: root.vertical ? root.capBreadth : root.capSpan
|
||||
radius: root.capBreadth / 2
|
||||
visible: !root.ring && (root.vertical || !root.boltVisible)
|
||||
visible: !root.circular && (root.vertical || !root.boltVisible)
|
||||
color: root.outlined ? root.fillColor : root.dimColor
|
||||
}
|
||||
|
||||
@@ -236,7 +328,7 @@ Item {
|
||||
width: root.vertical ? root.width : root.bodyLength
|
||||
height: root.vertical ? root.bodyLength : root.height
|
||||
radius: 4 * root.unit
|
||||
visible: !root.ring
|
||||
visible: !root.circular
|
||||
color: root.trackColor
|
||||
border.width: root.strokeWidth
|
||||
border.color: root.fillColor
|
||||
@@ -250,7 +342,7 @@ Item {
|
||||
width: frame.width - root.strokeWidth * 2
|
||||
height: frame.height - root.strokeWidth * 2
|
||||
radius: Math.max(0, frame.radius - root.strokeWidth)
|
||||
visible: !root.ring
|
||||
visible: !root.circular
|
||||
color: "transparent"
|
||||
|
||||
Rectangle {
|
||||
@@ -301,7 +393,7 @@ Item {
|
||||
}
|
||||
|
||||
Glyphs {
|
||||
visible: root.numberInside && !root.ring
|
||||
visible: root.numberInside && !root.circular
|
||||
ink: Theme.surfaceText
|
||||
}
|
||||
|
||||
@@ -311,7 +403,7 @@ Item {
|
||||
width: fill.width
|
||||
height: fill.height
|
||||
clip: true
|
||||
visible: root.numberInside && !root.outlined && !root.ring
|
||||
visible: root.numberInside && !root.outlined && !root.circular
|
||||
|
||||
Glyphs {
|
||||
x: -parent.x
|
||||
@@ -322,8 +414,8 @@ Item {
|
||||
|
||||
Bolt {
|
||||
visible: root.boltVisible
|
||||
x: root.ring || root.vertical ? (root.width - width) / 2 : root.capOffset
|
||||
y: !root.ring && root.vertical ? frame.y + (frame.height - height) / 2 : (root.height - height) / 2
|
||||
x: root.circular || root.vertical ? (root.width - width) / 2 : root.capOffset
|
||||
y: !root.circular && root.vertical ? frame.y + (frame.height - height) / 2 : (root.height - height) / 2
|
||||
fillColor: Theme.surfaceText
|
||||
}
|
||||
}
|
||||
@@ -593,3 +593,57 @@ test("an expanding hosted island closes its own bar's popout only for deliberate
|
||||
host.closeSameEdgeSurfaces();
|
||||
assert.deepEqual(closed.splice(0), ["modal:launcher"], "connected chrome closes only the same-edge slot, and only this screen's modal");
|
||||
});
|
||||
|
||||
const controllerSource = readFileSync(new URL("../Modules/DankIsland/IslandController.qml", import.meta.url), "utf8");
|
||||
|
||||
function controllerContext() {
|
||||
const context = vm.createContext({
|
||||
systemLevelActivities: ["volume", "brightness", "mic"],
|
||||
systemStateActivities: ["capslock", "powerprofile", "idleinhibitor", "charging"],
|
||||
activeActivity: "home",
|
||||
expanded: false,
|
||||
notificationActive: false,
|
||||
launcherSessionActive: false,
|
||||
transientReturnActivity: "",
|
||||
hoverExpanded: false,
|
||||
keyboardDismissRequested: false,
|
||||
transientTimer: { stop() {}, restart() {} },
|
||||
notificationTimer: { stop() {} },
|
||||
hoverOpenTimer: { stop() {} },
|
||||
hoverCloseTimer: { stop() {} },
|
||||
destinations: [],
|
||||
destinationState: {},
|
||||
destinationRevision: 0,
|
||||
timeoutSuspended: false
|
||||
});
|
||||
context.root = context;
|
||||
context.systemActivities = context.systemLevelActivities.concat(context.systemStateActivities);
|
||||
Object.defineProperty(context, "systemActivityActive", { get: () => context.isSystemActivity(context.activeActivity) });
|
||||
return functions(controllerSource, " ", context);
|
||||
}
|
||||
|
||||
test("state pulses are system transients that return to the interrupted activity", () => {
|
||||
const controller = controllerContext();
|
||||
controller.activeActivity = "media";
|
||||
assert.equal(controller.requestSystemActivity("capslock"), true);
|
||||
assert.equal(controller.activeActivity, "capslock");
|
||||
assert.equal(controller.transientReturnActivity, "media");
|
||||
assert.equal(controller.requestSystemActivity("charging"), true, "a second pulse replaces the first");
|
||||
assert.equal(controller.transientReturnActivity, "media", "the return point is kept across chained pulses");
|
||||
assert.equal(controller.requestSystemActivity("bogus"), false);
|
||||
assert.equal(controller.activeActivity, "charging");
|
||||
controller.expanded = true;
|
||||
assert.equal(controller.requestSystemActivity("mic"), false, "an open sheet is not interrupted");
|
||||
});
|
||||
|
||||
test("the privacy slot is a known home layout entry appended after stored ones", () => {
|
||||
const groupIds = JSON.parse(settingsSource.match(/_islandHomeGroupIds: (\[.*\])/)[1]);
|
||||
const layoutDefault = JSON.parse(settingsSource.match(/_islandHomeLayoutDefault: (\[[\s\S]*?\n \])/)[1]);
|
||||
const settings = functions(settingsSource, " ", vm.createContext({ _islandHomeGroupIds: groupIds, _islandHomeLayoutDefault: layoutDefault }));
|
||||
settings.islandSetting = (config, key) => config[key];
|
||||
settings.islandSettings = config => config;
|
||||
const legacy = { islandHomeLayout: [{ id: "clock" }, { id: "media", enabled: false }] };
|
||||
const ids = settings.getIslandHomeLayout(legacy).map(entry => entry.id);
|
||||
assert.ok(ids.includes("privacy"), "a stored layout gains the privacy slot");
|
||||
assert.equal(ids.indexOf("privacy"), ids.length - 1, "new slots append after the stored order");
|
||||
});
|
||||
@@ -0,0 +1,149 @@
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
import qs.Modules.DankBar
|
||||
import qs.Modules.DankIsland
|
||||
import qs.Modules.Frame
|
||||
import qs.DankCommon.Common as DC
|
||||
|
||||
// State pulses (caps lock, charging) and the mic level share the system transient with volume, and the duo status glyph loads in the home face.
|
||||
ShellRoot {
|
||||
id: root
|
||||
|
||||
readonly property var screen: Quickshell.screens[0]
|
||||
readonly property string barId: "main"
|
||||
|
||||
Item {
|
||||
Repeater {
|
||||
model: ScriptModel {
|
||||
values: SettingsData.barConfigs
|
||||
objectProp: "id"
|
||||
}
|
||||
delegate: DankBar {
|
||||
required property var modelData
|
||||
barConfig: modelData
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Frame {}
|
||||
|
||||
DankIsland {}
|
||||
|
||||
Component.onCompleted: {
|
||||
Quickshell.watchFiles = false;
|
||||
DC.Style.theme = Theme;
|
||||
DC.Style.settings = SettingsData;
|
||||
DC.I18n.backend = I18n;
|
||||
}
|
||||
|
||||
Timer {
|
||||
interval: 0
|
||||
running: SettingsData._hasLoaded && SessionData._hasLoaded
|
||||
onTriggered: {
|
||||
SettingsData.reduceMotion = true;
|
||||
SettingsData.frameMode = "connected";
|
||||
SettingsData.frameEnabled = true;
|
||||
FrameTransitionState.acknowledge(FrameTransitionState.revision);
|
||||
SettingsData.barConfigs = [
|
||||
{
|
||||
id: root.barId,
|
||||
enabled: true,
|
||||
visible: true,
|
||||
position: 0,
|
||||
screenPreferences: ["all"],
|
||||
leftWidgets: ["clock"],
|
||||
centerWidgets: [
|
||||
{
|
||||
id: "island",
|
||||
enabled: true,
|
||||
islandBatteryStyle: "duo",
|
||||
islandHomeLayout: [
|
||||
{
|
||||
id: "status",
|
||||
enabled: true
|
||||
},
|
||||
{
|
||||
id: "clock",
|
||||
enabled: true
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
rightWidgets: ["clock"]
|
||||
}
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: steps
|
||||
|
||||
property int step: 0
|
||||
property int waited: 0
|
||||
readonly property var body: IslandHostRegistry.hostFor(root.screen.name, root.barId)
|
||||
|
||||
interval: 25
|
||||
repeat: true
|
||||
running: !!body && !!body.islandController && SettingsData.frameEnabled
|
||||
|
||||
function finish(message) {
|
||||
if (message)
|
||||
console.error("FIXTURE_FAIL", message);
|
||||
else
|
||||
console.log("FIXTURE_PASS");
|
||||
stop();
|
||||
Qt.quit();
|
||||
}
|
||||
|
||||
function expectActivity(activityId) {
|
||||
if (body.islandController.activeActivity === activityId)
|
||||
return true;
|
||||
finish("expected " + activityId + " but the island shows " + body.islandController.activeActivity);
|
||||
return false;
|
||||
}
|
||||
|
||||
onTriggered: {
|
||||
if (++waited > 800) {
|
||||
finish("timed out at step " + step);
|
||||
return;
|
||||
}
|
||||
if (body.motionRunning || body.bandThickness <= 0)
|
||||
return;
|
||||
const controller = body.islandController;
|
||||
waited = 0;
|
||||
switch (step++) {
|
||||
case 0:
|
||||
if (!controller.requestSystemActivity("capslock")) {
|
||||
finish("caps lock is not a system transient");
|
||||
return;
|
||||
}
|
||||
expectActivity("capslock");
|
||||
return;
|
||||
case 1:
|
||||
if (!controller.requestSystemActivity("charging"))
|
||||
finish("charging is not a system transient");
|
||||
return;
|
||||
case 2:
|
||||
if (!expectActivity("charging"))
|
||||
return;
|
||||
if (controller.compactTargetFor("charging").width === controller.compactTargetFor("volume").width)
|
||||
finish("a state pulse borrows the level pill width");
|
||||
return;
|
||||
case 3:
|
||||
if (!controller.requestSystemActivity("mic"))
|
||||
finish("mic is not a system level");
|
||||
return;
|
||||
case 4:
|
||||
if (!expectActivity("mic"))
|
||||
return;
|
||||
controller.requestCollapse();
|
||||
return;
|
||||
default:
|
||||
if (expectActivity("home"))
|
||||
finish("");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3181,6 +3181,40 @@
|
||||
"description": "Routes launcher, dash, control center and notification shortcuts",
|
||||
"conditionKey": "islandEnabled"
|
||||
},
|
||||
{
|
||||
"section": "islandChargingPulse",
|
||||
"label": "Charging",
|
||||
"tabIndex": 3,
|
||||
"category": "General",
|
||||
"keywords": [
|
||||
"bar",
|
||||
"battery",
|
||||
"briefly",
|
||||
"charge",
|
||||
"charging",
|
||||
"connected",
|
||||
"general",
|
||||
"hibernate",
|
||||
"island",
|
||||
"panel",
|
||||
"plugged",
|
||||
"power",
|
||||
"pulse",
|
||||
"reboot",
|
||||
"removed",
|
||||
"restart",
|
||||
"show",
|
||||
"shutdown",
|
||||
"sleep",
|
||||
"statusbar",
|
||||
"suspend",
|
||||
"taskbar",
|
||||
"topbar"
|
||||
],
|
||||
"parentLabel": "Bar",
|
||||
"description": "Show the battery briefly when power is connected or removed",
|
||||
"conditionKey": "islandEnabled"
|
||||
},
|
||||
{
|
||||
"section": "islandNotificationBadgeClearOnOpen",
|
||||
"label": "Clear badge on open",
|
||||
@@ -4257,12 +4291,16 @@
|
||||
"keywords": [
|
||||
"bar",
|
||||
"brightness",
|
||||
"caps lock",
|
||||
"general",
|
||||
"idle inhibitor",
|
||||
"island",
|
||||
"level",
|
||||
"microphone",
|
||||
"osd",
|
||||
"osds",
|
||||
"panel",
|
||||
"power profile",
|
||||
"standard",
|
||||
"statusbar",
|
||||
"taskbar",
|
||||
@@ -4802,6 +4840,7 @@
|
||||
"charge",
|
||||
"charging",
|
||||
"circle",
|
||||
"duo",
|
||||
"gauge",
|
||||
"island",
|
||||
"outline",
|
||||
@@ -4812,7 +4851,8 @@
|
||||
"statusbar",
|
||||
"style",
|
||||
"taskbar",
|
||||
"topbar"
|
||||
"topbar",
|
||||
"wifi"
|
||||
],
|
||||
"parentLabel": "Bar",
|
||||
"conditionKey": "islandEnabled"
|
||||
@@ -9814,7 +9854,15 @@
|
||||
"optionFile": "PrivacyOptions.qml"
|
||||
},
|
||||
{
|
||||
"section": "barWidgetOption:IslandOptions.qml:26",
|
||||
"section": "barWidgetOption:IslandOptions.qml:25",
|
||||
"label": "Charging",
|
||||
"tabIndex": 22,
|
||||
"keywords": [],
|
||||
"runtimeType": "barWidgetOption",
|
||||
"optionFile": "IslandOptions.qml"
|
||||
},
|
||||
{
|
||||
"section": "barWidgetOption:IslandOptions.qml:27",
|
||||
"label": "Clear badge on open",
|
||||
"tabIndex": 22,
|
||||
"keywords": [],
|
||||
@@ -10030,7 +10078,7 @@
|
||||
"optionFile": "AppsDockOptions.qml"
|
||||
},
|
||||
{
|
||||
"section": "barWidgetOption:IslandOptions.qml:25",
|
||||
"section": "barWidgetOption:IslandOptions.qml:26",
|
||||
"label": "Expand by default",
|
||||
"tabIndex": 22,
|
||||
"keywords": [],
|
||||
@@ -10326,7 +10374,7 @@
|
||||
"optionFile": "WorkspaceSwitcherOptions.qml"
|
||||
},
|
||||
{
|
||||
"section": "barWidgetOption:IslandOptions.qml:27",
|
||||
"section": "barWidgetOption:IslandOptions.qml:28",
|
||||
"label": "Motion",
|
||||
"tabIndex": 22,
|
||||
"keywords": [],
|
||||
@@ -10422,7 +10470,7 @@
|
||||
"optionFile": "AppsDockOptions.qml"
|
||||
},
|
||||
{
|
||||
"section": "barWidgetOption:IslandOptions.qml:28",
|
||||
"section": "barWidgetOption:IslandOptions.qml:29",
|
||||
"label": "Reduce motion",
|
||||
"tabIndex": 22,
|
||||
"keywords": [],
|
||||
@@ -10638,7 +10686,7 @@
|
||||
"optionFile": "AppsDockOptions.qml"
|
||||
},
|
||||
{
|
||||
"section": "barWidgetOption:IslandOptions.qml:30",
|
||||
"section": "barWidgetOption:IslandOptions.qml:31",
|
||||
"label": "Spring damping",
|
||||
"tabIndex": 22,
|
||||
"keywords": [],
|
||||
@@ -10646,7 +10694,7 @@
|
||||
"optionFile": "IslandOptions.qml"
|
||||
},
|
||||
{
|
||||
"section": "barWidgetOption:IslandOptions.qml:31",
|
||||
"section": "barWidgetOption:IslandOptions.qml:32",
|
||||
"label": "Spring mass",
|
||||
"tabIndex": 22,
|
||||
"keywords": [],
|
||||
@@ -10654,7 +10702,7 @@
|
||||
"optionFile": "IslandOptions.qml"
|
||||
},
|
||||
{
|
||||
"section": "barWidgetOption:IslandOptions.qml:29",
|
||||
"section": "barWidgetOption:IslandOptions.qml:30",
|
||||
"label": "Spring stiffness",
|
||||
"tabIndex": 22,
|
||||
"keywords": [],
|
||||
@@ -16854,6 +16902,40 @@
|
||||
"description": "Routes launcher, dash, control center and notification shortcuts",
|
||||
"conditionKey": "dotEnabled"
|
||||
},
|
||||
{
|
||||
"section": "dotChargingPulse",
|
||||
"label": "Charging",
|
||||
"tabIndex": 65,
|
||||
"category": "Dot",
|
||||
"keywords": [
|
||||
"bar",
|
||||
"battery",
|
||||
"briefly",
|
||||
"charge",
|
||||
"charging",
|
||||
"connected",
|
||||
"dot",
|
||||
"hibernate",
|
||||
"island",
|
||||
"panel",
|
||||
"plugged",
|
||||
"power",
|
||||
"pulse",
|
||||
"reboot",
|
||||
"removed",
|
||||
"restart",
|
||||
"show",
|
||||
"shutdown",
|
||||
"sleep",
|
||||
"statusbar",
|
||||
"suspend",
|
||||
"taskbar",
|
||||
"topbar"
|
||||
],
|
||||
"parentLabel": "Bar",
|
||||
"description": "Show the battery briefly when power is connected or removed",
|
||||
"conditionKey": "dotEnabled"
|
||||
},
|
||||
{
|
||||
"section": "dotNotificationBadgeClearOnOpen",
|
||||
"label": "Clear badge on open",
|
||||
@@ -17491,12 +17573,16 @@
|
||||
"keywords": [
|
||||
"bar",
|
||||
"brightness",
|
||||
"caps lock",
|
||||
"dot",
|
||||
"idle inhibitor",
|
||||
"island",
|
||||
"level",
|
||||
"microphone",
|
||||
"osd",
|
||||
"osds",
|
||||
"panel",
|
||||
"power profile",
|
||||
"standard",
|
||||
"statusbar",
|
||||
"taskbar",
|
||||
|
||||
Reference in new issue
Block a user