island: optional dash tab bar on the island faces

- bump qml-common
Port 2.0

(cherry picked from commit e777288904)
This commit is contained in:
purian23 authored and dux-ci[bot] committed 2026-10-30 03:24:40 +00:00
1 parent 03f4dc0aaf
commit e7707fb5e4
9 files changed
+168 -44

No files matched your search

@@ -36,6 +36,7 @@ var ISLAND_DEFAULTS = {
islandHoverCloseDelay: 150,
islandPalette: "default",
islandHighContrast: false,
islandDashTabs: false,
islandMediaClockVisible: true,
islandMediaLyrics: false,
islandMediaAutoSwitch: false,
+4 -1
View File
@@ -26,7 +26,6 @@ Singleton {
// Card pills overhang their card by half their height and must stay inside the pages clip.
readonly property real islandPillOverhang: PopoutMetrics.chromeButtonSize / 2
readonly property real islandEditChromeHeight: islandEditHeaderInset + editHeaderHeight + contentPadding + islandPillOverhang + islandEditBottomInset
readonly property real islandEditRoom: islandEditChromeHeight - islandHandleChromeHeight
readonly property real spinnerSize: Theme.iconButtonSize
readonly property real triggerWidth: CcMetrics.triggerWidth
readonly property int transitionDuration: CcMetrics.transitionDuration
@@ -36,6 +35,10 @@ Singleton {
readonly property int overlayZ: CcMetrics.overlayZ
readonly property bool animationsEnabled: CcMetrics.animationsEnabled
function islandChromeHeight(tabBarHeight) {
return tabBarHeight > 0 ? contentPadding * 2 + tabBarHeight + contentGap : islandHandleChromeHeight;
}
function widthFor(weekNumbers, screenWidth, columns = gridColumns) {
// The floor is sized at comfortable spacing, so it carries the density offsets.
const floorWidth = minimumPopoutWidth + (gridGap - Theme.spacingS) * (columns - 1) + (contentPadding - Theme.spacingM) * 2;
@@ -22,12 +22,16 @@ FocusScope {
readonly property var tab: tabLoader.item
readonly property real tabHeight: tab?.implicitHeight ?? 0
readonly property real contentHeight: DashMetrics.panelHeightFor(entryId, tabHeight)
readonly property bool handleHeader: !root.editMode
readonly property bool tabsShown: root.controller.dashTabsShownFor(root.activityId)
readonly property real chromeHeight: DashMetrics.islandChromeHeight(tabsShown ? (tabBarLoader.item?.implicitHeight ?? Theme.navigationHeight) : 0)
readonly property bool handleHeader: !root.editMode && !root.tabsShown
readonly property real pillOverhang: editMode ? DashMetrics.islandPillOverhang : 0
readonly property int panelColumns: DashMetrics.panelColumnsFor(entryId)
readonly property int contentRows: DashMetrics.rowsForHeight(tabHeight)
readonly property int panelRows: Math.max(DashMetrics.panelFloorRowsFor(entryId), contentRows)
signal windowRequested(string windowName)
readonly property QtObject resizeHost: QtObject {
readonly property real renderedAlignedX: root.resizeGeometry?.renderedX ?? 0
readonly property real renderedAlignedY: root.resizeGeometry?.renderedY ?? 0
@@ -72,7 +76,13 @@ FocusScope {
function focusFace() {
pageActions.clearFocus();
sheetHandle.focusTarget.focus = false;
if (tabBarLoader.item)
tabBarLoader.item.focus = false;
tabLoader.focus = false;
if (root.controller.dashNavigationFocused && tabBarLoader.item && !root.editMode) {
tabBarLoader.item.forceActiveFocus(Qt.OtherFocusReason);
return true;
}
root.forceActiveFocus(Qt.OtherFocusReason);
if (!root.editMode)
root.tab?.restoreFocus?.();
@@ -80,10 +90,11 @@ FocusScope {
}
function reportHeight() {
root.controller.setDashboardContentHeight(root.activityId, root.contentHeight + DashMetrics.islandHandleChromeHeight);
root.controller.setDashboardContentHeight(root.activityId, root.contentHeight + root.chromeHeight);
}
onContentHeightChanged: reportHeight()
onChromeHeightChanged: reportHeight()
onLiveChanged: {
if (live)
return;
@@ -97,7 +108,7 @@ FocusScope {
focusFace();
}
onEditModeChanged: {
root.controller.setEditing(root.activityId, editMode, DashMetrics.islandEditRoom);
root.controller.setEditing(root.activityId, editMode, Math.max(0, DashMetrics.islandEditChromeHeight - root.chromeHeight));
if (!editMode)
panelResizer.cancel();
Qt.callLater(() => {
@@ -119,8 +130,8 @@ FocusScope {
tabItem: root.tab
contentScope: tabLoader
contentTarget: root.tab?.focusTarget ?? null
headerTargets: root.editMode ? pageActions.focusTargets : [sheetHandle.focusTarget]
onTabCycleRequested: direction => root.controller.cycleDashTab(direction)
headerTargets: root.editMode ? pageActions.focusTargets : [tabBarLoader.item, sheetHandle.focusTarget]
onTabCycleRequested: direction => root.controller.cycleDashTab(direction, tabBarLoader.item?.activeFocus ?? false)
onEditModeRequested: on => root.editMode = on
onOptionsDismissRequested: tabOptions.dismiss()
onCloseRequested: root.controller.requestCollapse()
@@ -133,11 +144,41 @@ FocusScope {
top: parent.top
left: parent.left
right: parent.right
topMargin: root.editMode ? DashMetrics.islandEditHeaderInset : 0
topMargin: root.editMode ? DashMetrics.islandEditHeaderInset : root.tabsShown ? DashMetrics.contentPadding : 0
leftMargin: DashMetrics.contentPadding
rightMargin: DashMetrics.contentPadding
}
height: root.handleHeader ? DashMetrics.islandHandleHeight : DashMetrics.editHeaderHeight
height: root.editMode ? DashMetrics.editHeaderHeight : root.tabsShown ? (tabBarLoader.item?.implicitHeight ?? Theme.navigationHeight) : DashMetrics.islandHandleHeight
Loader {
id: tabBarLoader
anchors.fill: parent
active: root.tabsShown
visible: !root.editMode
sourceComponent: DNavigationBar {
id: tabBar
editable: true
tabHighlights: true
evenlySpaced: SettingsData.dashTabsEvenlySpaced
currentIndex: root.controller.dashTabIds.indexOf(root.entryId)
nextFocusTarget: root.tab?.focusTarget ?? null
previousFocusTarget: root.tab?.focusTarget ?? null
model: root.controller.dashTabBarModel
onEditRequested: root.editMode = true
onActivated: index => {
const id = root.controller.dashTabIds[index];
if (id === undefined || id === root.entryId)
return;
if (DashRegistry.isAction(id)) {
root.windowRequested(id);
return;
}
root.controller.requestDashTab(id, tabBar.activeFocus);
}
}
}
DashSheetHandle {
id: sheetHandle
@@ -170,7 +211,7 @@ FocusScope {
showScrollBar: false
anchors {
top: header.bottom
topMargin: root.editMode ? DashMetrics.contentPadding : 0
topMargin: root.editMode ? DashMetrics.contentPadding : root.tabsShown ? DashMetrics.contentGap : 0
left: parent.left
right: parent.right
bottom: parent.bottom
@@ -190,6 +231,10 @@ FocusScope {
asynchronous: true
visible: status === Loader.Ready
sourceComponent: root.tabComponent
onActiveFocusChanged: {
if (activeFocus)
root.controller.dashNavigationFocused = false;
}
onLoaded: {
root.reportHeight();
if (root.live)
@@ -15,23 +15,7 @@ DashTabFace {
editMode: root.editMode
rowBudget: root.controller.dashboardRowBudget
transientSurfaceTracker: root.controller.transientSurfaces
onTabRequested: id => {
switch (id) {
case "media":
root.controller.requestActivity("media", true, true);
break;
case "weather":
if (SettingsData.weatherEnabled)
root.controller.requestWeather(false);
break;
case "wellbeing":
root.controller.requestActivity("wellbeing", true, true);
break;
case "notifications":
root.controller.requestNotificationCenter(false);
break;
}
}
onTabRequested: id => root.controller.requestDashTab(id)
}
}
}
@@ -627,6 +627,7 @@ Item {
HomeExpanded {
controller: root.controller
onWindowRequested: windowName => root.openAfterCollapse(windowName)
resizeGeometry: root.resizeGeometry
}
}
@@ -645,6 +646,7 @@ Item {
MediaExpanded {
controller: root.controller
onWindowRequested: windowName => root.openAfterCollapse(windowName)
resizeGeometry: root.resizeGeometry
}
}
@@ -734,6 +736,7 @@ Item {
WallpaperExpanded {
controller: root.controller
onWindowRequested: windowName => root.openAfterCollapse(windowName)
resizeGeometry: root.resizeGeometry
effectiveScreen: root.effectiveScreen
}
@@ -755,6 +758,7 @@ Item {
WeatherExpanded {
controller: root.controller
onWindowRequested: windowName => root.openAfterCollapse(windowName)
resizeGeometry: root.resizeGeometry
}
}
@@ -775,6 +779,7 @@ Item {
WellbeingExpanded {
controller: root.controller
onWindowRequested: windowName => root.openAfterCollapse(windowName)
resizeGeometry: root.resizeGeometry
}
}
@@ -845,6 +850,7 @@ Item {
NotificationCenterExpanded {
controller: root.controller
onWindowRequested: windowName => root.openAfterCollapse(windowName)
resizeGeometry: root.resizeGeometry
}
}
@@ -436,6 +436,7 @@ Item {
batteryStyle: root.setting("islandBatteryStyle")
mediaClockVisible: root.setting("islandMediaClockVisible")
mediaLyrics: root.setting("islandMediaLyrics")
dashTabs: root.setting("islandDashTabs")
// Home and media share the dot face; see mediaFaceToggleAvailable.
mediaAutoSwitch: !root.dotMode && root.setting("islandMediaAutoSwitch")
launcherCycleEnabled: SettingsData.launcherStyle === "island"
@@ -68,6 +68,14 @@ QtObject {
property string batteryStyle: "duo"
property bool mediaClockVisible: true
property bool mediaLyrics: false
property bool dashTabs: false
// Carries tab-bar focus across a face switch.
property bool dashNavigationFocused: false
onExpandedChanged: {
if (!expanded)
dashNavigationFocused = false;
}
property int transientTimeout: 2200
property int notificationTimeout: 5000
property string transientReturnActivity: ""
@@ -106,7 +114,10 @@ QtObject {
readonly property int dashboardColumnCap: DashMetrics.columnCapFor(dashboardAvailableWidth - PopoutMetrics.editOverflow * 2, SettingsData.showWeekNumber)
readonly property real dashboardMaxWidth: Math.min(dashboardAvailableWidth, DashMetrics.widthFor(SettingsData.showWeekNumber, undefined, editingActivity !== "" ? dashboardColumnCap : DashRegistry.widestPanelColumns))
property var dashboardContentHeights: ({})
readonly property real dashChromeHeight: DashMetrics.islandHandleChromeHeight
// Plugin tabs have no island face, and the media face needs a player.
readonly property var dashTabIds: dashTabs ? DashRegistry.navigationIds.filter(id => DashRegistry.isAction(id) || dashActivityFor(id) !== "") : []
readonly property var dashTabBarModel: dashTabIds.length === 0 ? [] : DashRegistry.tabBarModel.filter((_, index) => dashTabIds.indexOf(DashRegistry.navigationIds[index]) >= 0)
readonly property real dashChromeHeight: DashMetrics.islandChromeHeight(dashTabIds.length > 1 ? Theme.navigationHeight : 0)
readonly property real editSurfaceHeight: editRoom <= 0 ? 0 : editingActivity === "controlcenter" ? controlCenterHeightFor(true) : dashboardHeightFor(editingActivity, true)
readonly property real dashboardHeight: Math.min(dashboardAvailableHeight, Math.max(DashMetrics.tabDefaultHeight + root.dashChromeHeight, ...Object.values(dashboardContentHeights)))
readonly property int dashboardRowBudget: Math.max(DashMetrics.minimumTabRows, Math.floor((dashboardAvailableHeight - DashMetrics.islandEditChromeHeight + DashMetrics.gridGap) / (DashMetrics.gridRowUnit + DashMetrics.gridGap)))
@@ -166,20 +177,25 @@ QtObject {
return "";
}
function requestDashTab(entryId) {
function dashTabsShownFor(activityId) {
return dashTabIds.length > 1 && dashTabIds.indexOf(dashEntryIdFor(activityId)) >= 0;
}
function requestDashTab(entryId, fromNavigation) {
dashNavigationFocused = fromNavigation === true;
if (entryId === "notifications")
return requestNotificationCenter(false);
const activityId = dashActivityFor(entryId);
return activityId !== "" && requestActivity(activityId, true, true);
}
function cycleDashTab(direction) {
function cycleDashTab(direction, fromNavigation) {
const ids = DashRegistry.visibleTabIds.filter(id => dashActivityFor(id) !== "");
if (ids.length === 0)
return false;
const pos = ids.indexOf(dashEntryIdFor(activeActivity));
const next = ids[pos < 0 ? (direction > 0 ? 0 : ids.length - 1) : (pos + direction + ids.length) % ids.length];
return next === dashEntryIdFor(activeActivity) || requestDashTab(next);
return next === dashEntryIdFor(activeActivity) || requestDashTab(next, fromNavigation);
}
function dashboardWidthFor(activityId) {
@@ -188,7 +204,8 @@ QtObject {
function dashboardHeightFor(activityId, withEditRoom) {
const minimum = DashMetrics.panelHeightFor(dashEntryIdFor(activityId));
const height = Math.max(minimum + root.dashChromeHeight, dashboardContentHeights[activityId] ?? 0);
const chrome = DashMetrics.islandChromeHeight(dashTabsShownFor(activityId) ? Theme.navigationHeight : 0);
const height = Math.max(minimum + chrome, dashboardContentHeights[activityId] ?? 0);
return Math.min(dashboardAvailableHeight, height + (withEditRoom ? root.editRoom : 0));
}
@@ -174,6 +174,15 @@ SettingsCard {
onSliderDragFinished: value => root.store.apply("islandHoverCloseDelay", value)
}
SettingsToggleRow {
settingKey: root.keyPrefix + "DashTabs"
tags: ["island", "dashboard", "dash", "tabs", "navigation"]
text: I18n.tr("Dashboard tabs", "island settings: dashboard tab bar toggle")
description: I18n.tr("Shows the dashboard tab bar instead of the sheet handle", "island settings: dashboard tab bar toggle description")
checked: root.store.setting("islandDashTabs")
onToggled: checked => root.store.apply("islandDashTabs", checked)
}
SettingsToggleRow {
settingKey: root.keyPrefix + "MediaClockVisible"
tags: ["island", "media", "clock", "compact", "time"]
@@ -3572,6 +3572,31 @@
"parentLabel": "Bar",
"conditionKey": "islandEnabled"
},
{
"section": "islandDashTabs",
"label": "Dashboard tabs",
"tabIndex": 3,
"category": "General",
"keywords": [
"bar",
"dash",
"dashboard",
"general",
"handle",
"island",
"navigation",
"panel",
"sheet",
"shows",
"statusbar",
"tabs",
"taskbar",
"topbar"
],
"parentLabel": "Bar",
"description": "Shows the dashboard tab bar instead of the sheet handle",
"conditionKey": "islandEnabled"
},
{
"section": "barDisplay",
"label": "Displays",
@@ -10108,7 +10133,7 @@
"optionFile": "PrivacyOptions.qml"
},
{
"section": "barWidgetOption:IslandOptions.qml:28",
"section": "barWidgetOption:IslandOptions.qml:29",
"label": "Charging",
"tabIndex": 22,
"keywords": [],
@@ -10116,7 +10141,7 @@
"optionFile": "IslandOptions.qml"
},
{
"section": "barWidgetOption:IslandOptions.qml:30",
"section": "barWidgetOption:IslandOptions.qml:31",
"label": "Clear badge on open",
"tabIndex": 22,
"keywords": [],
@@ -10307,6 +10332,14 @@
"runtimeType": "barWidgetOption",
"optionFile": "IslandOptions.qml"
},
{
"section": "barWidgetOption:IslandOptions.qml:22",
"label": "Dashboard tabs",
"tabIndex": 22,
"keywords": [],
"runtimeType": "barWidgetOption",
"optionFile": "IslandOptions.qml"
},
{
"section": "barWidgetOption:SystemMonitorOptions.qml:7",
"label": "Disk usage format",
@@ -10332,7 +10365,7 @@
"optionFile": "WorkspaceSwitcherOptions.qml"
},
{
"section": "barWidgetOption:IslandOptions.qml:29",
"section": "barWidgetOption:IslandOptions.qml:30",
"label": "Expand by default",
"tabIndex": 22,
"keywords": [],
@@ -10524,7 +10557,7 @@
"optionFile": "ControlCenterOptions.qml"
},
{
"section": "barWidgetOption:IslandOptions.qml:22",
"section": "barWidgetOption:IslandOptions.qml:23",
"label": "Keep clock with media",
"tabIndex": 22,
"keywords": [],
@@ -10556,7 +10589,7 @@
"optionFile": "AppsDockOptions.qml"
},
{
"section": "barWidgetOption:IslandOptions.qml:23",
"section": "barWidgetOption:IslandOptions.qml:24",
"label": "Lyrics",
"tabIndex": 22,
"keywords": [],
@@ -10644,7 +10677,7 @@
"optionFile": "WorkspaceSwitcherOptions.qml"
},
{
"section": "barWidgetOption:IslandOptions.qml:31",
"section": "barWidgetOption:IslandOptions.qml:32",
"label": "Motion",
"tabIndex": 22,
"keywords": [],
@@ -10692,7 +10725,7 @@
"optionFile": "ClockOptions.qml"
},
{
"section": "barWidgetOption:IslandOptions.qml:25",
"section": "barWidgetOption:IslandOptions.qml:26",
"label": "Notifications",
"tabIndex": 22,
"keywords": [],
@@ -10972,7 +11005,7 @@
"optionFile": "AppsDockOptions.qml"
},
{
"section": "barWidgetOption:IslandOptions.qml:33",
"section": "barWidgetOption:IslandOptions.qml:34",
"label": "Spring damping",
"tabIndex": 22,
"keywords": [],
@@ -10980,7 +11013,7 @@
"optionFile": "IslandOptions.qml"
},
{
"section": "barWidgetOption:IslandOptions.qml:34",
"section": "barWidgetOption:IslandOptions.qml:35",
"label": "Spring mass",
"tabIndex": 22,
"keywords": [],
@@ -10988,7 +11021,7 @@
"optionFile": "IslandOptions.qml"
},
{
"section": "barWidgetOption:IslandOptions.qml:32",
"section": "barWidgetOption:IslandOptions.qml:33",
"label": "Spring stiffness",
"tabIndex": 22,
"keywords": [],
@@ -11020,7 +11053,7 @@
"optionFile": "WorkspaceSwitcherOptions.qml"
},
{
"section": "barWidgetOption:IslandOptions.qml:24",
"section": "barWidgetOption:IslandOptions.qml:25",
"label": "Switch to media when playback starts",
"tabIndex": 22,
"keywords": [],
@@ -11076,7 +11109,7 @@
"optionFile": "SystemTrayOptions.qml"
},
{
"section": "barWidgetOption:IslandOptions.qml:27",
"section": "barWidgetOption:IslandOptions.qml:28",
"label": "Use standard OSDs",
"tabIndex": 22,
"keywords": [],
@@ -11084,7 +11117,7 @@
"optionFile": "IslandOptions.qml"
},
{
"section": "barWidgetOption:IslandOptions.qml:26",
"section": "barWidgetOption:IslandOptions.qml:27",
"label": "Use standard popups",
"tabIndex": 22,
"keywords": [],
@@ -17960,6 +17993,31 @@
"parentLabel": "Bar",
"conditionKey": "dotEnabled"
},
{
"section": "dotDashTabs",
"label": "Dashboard tabs",
"tabIndex": 65,
"category": "Dot",
"keywords": [
"bar",
"dash",
"dashboard",
"dot",
"handle",
"island",
"navigation",
"panel",
"sheet",
"shows",
"statusbar",
"tabs",
"taskbar",
"topbar"
],
"parentLabel": "Bar",
"description": "Shows the dashboard tab bar instead of the sheet handle",
"conditionKey": "dotEnabled"
},
{
"section": "dotDisplays",
"label": "Displays",