1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2025-12-05 21:15:38 -05:00

Compare commits

...

12 Commits

Author SHA1 Message Date
bbedward
ad0f3fa33b dankbar: convert center section to use WidgetHost 2025-12-04 19:37:21 -05:00
bbedward
63d121b796 proc: ability to run command with noTimeout 2025-12-04 16:09:38 -05:00
bbedward
4291cfe82f settings: fix launcher tab sizing 2025-12-04 16:01:07 -05:00
bbedward
f312868154 lock: respect confirmation mode power actions 2025-12-04 14:58:36 -05:00
bbedward
5b42d34ac8 expose iconSize helpers to plugins 2025-12-04 14:40:38 -05:00
bbedward
397a8c275d settings: add IPCs to open specific settings tabs 2025-12-04 14:31:35 -05:00
purian23
2aabee453b Remove hyprpicker requirement for DMS Copr 2025-12-04 14:25:40 -05:00
bbedward
185333a615 brightness: default IPCs to pinned devices per-display
fixes #875
2025-12-04 13:55:14 -05:00
bbedward
7d177eb1d4 greeter: fix mango config override
fixes #904
2025-12-04 13:46:11 -05:00
Givani Boekestijn
705a84051d feat(dankdash): add vim keybindings (hjkl) to wallpaper picker navigation (#903) 2025-12-04 13:22:30 -05:00
bbedward
f6821f80e1 dankslideout: convert to Rectangle 2025-12-04 12:54:15 -05:00
bbedward
e7a6f5228d widgets: fix binding loop in button 2025-12-04 12:50:06 -05:00
25 changed files with 1302 additions and 857 deletions

View File

@@ -517,7 +517,6 @@ jobs:
Recommends: cava Recommends: cava
Recommends: cliphist Recommends: cliphist
Recommends: danksearch Recommends: danksearch
Recommends: hyprpicker
Recommends: matugen Recommends: matugen
Recommends: wl-clipboard Recommends: wl-clipboard
Recommends: NetworkManager Recommends: NetworkManager

View File

@@ -33,7 +33,6 @@ Requires: dgop
Recommends: cava Recommends: cava
Recommends: cliphist Recommends: cliphist
Recommends: danksearch Recommends: danksearch
Recommends: hyprpicker
Recommends: matugen Recommends: matugen
Recommends: quickshell-git Recommends: quickshell-git
Recommends: wl-clipboard Recommends: wl-clipboard

View File

@@ -3,122 +3,139 @@ pragma ComponentBehavior: Bound
import QtQuick import QtQuick
import Quickshell import Quickshell
import Quickshell.Io
Singleton { Singleton {
id: root id: root
readonly property int noTimeout: -1
property int defaultDebounceMs: 50 property int defaultDebounceMs: 50
property int defaultTimeoutMs: 10000 property int defaultTimeoutMs: 10000
property var _procDebouncers: ({}) property var _procDebouncers: ({})
function runCommand(id, command, callback, debounceMs, timeoutMs) { function runCommand(id, command, callback, debounceMs, timeoutMs) {
const wait = (typeof debounceMs === "number" && debounceMs >= 0) ? debounceMs : defaultDebounceMs const wait = (typeof debounceMs === "number" && debounceMs >= 0) ? debounceMs : defaultDebounceMs;
const timeout = (typeof timeoutMs === "number" && timeoutMs > 0) ? timeoutMs : defaultTimeoutMs const timeout = (typeof timeoutMs === "number") ? timeoutMs : defaultTimeoutMs;
let procId = id ? id : Math.random() let procId = id ? id : Math.random();
const isRandomId = !id const isRandomId = !id;
if (!_procDebouncers[procId]) { if (!_procDebouncers[procId]) {
const t = Qt.createQmlObject('import QtQuick; Timer { repeat: false }', root) const t = Qt.createQmlObject('import QtQuick; Timer { repeat: false }', root);
t.triggered.connect(function() { _launchProc(procId, isRandomId) }) t.triggered.connect(function () {
_procDebouncers[procId] = { timer: t, command: command, callback: callback, waitMs: wait, timeoutMs: timeout, isRandomId: isRandomId } _launchProc(procId, isRandomId);
});
_procDebouncers[procId] = {
timer: t,
command: command,
callback: callback,
waitMs: wait,
timeoutMs: timeout,
isRandomId: isRandomId
};
} else { } else {
_procDebouncers[procId].command = command _procDebouncers[procId].command = command;
_procDebouncers[procId].callback = callback _procDebouncers[procId].callback = callback;
_procDebouncers[procId].waitMs = wait _procDebouncers[procId].waitMs = wait;
_procDebouncers[procId].timeoutMs = timeout _procDebouncers[procId].timeoutMs = timeout;
} }
const entry = _procDebouncers[procId] const entry = _procDebouncers[procId];
entry.timer.interval = entry.waitMs entry.timer.interval = entry.waitMs;
entry.timer.restart() entry.timer.restart();
} }
function _launchProc(id, isRandomId) { function _launchProc(id, isRandomId) {
const entry = _procDebouncers[id] const entry = _procDebouncers[id];
if (!entry) return if (!entry)
return;
const proc = Qt.createQmlObject('import Quickshell.Io; Process { running: false }', root);
const out = Qt.createQmlObject('import Quickshell.Io; StdioCollector {}', proc);
const err = Qt.createQmlObject('import Quickshell.Io; StdioCollector {}', proc);
const timeoutTimer = Qt.createQmlObject('import QtQuick; Timer { repeat: false }', root);
const proc = Qt.createQmlObject('import Quickshell.Io; Process { running: false }', root) proc.stdout = out;
const out = Qt.createQmlObject('import Quickshell.Io; StdioCollector {}', proc) proc.stderr = err;
const err = Qt.createQmlObject('import Quickshell.Io; StdioCollector {}', proc) proc.command = entry.command;
const timeoutTimer = Qt.createQmlObject('import QtQuick; Timer { repeat: false }', root)
proc.stdout = out let capturedOut = "";
proc.stderr = err let capturedErr = "";
proc.command = entry.command let exitSeen = false;
let exitCodeValue = -1;
let outSeen = false;
let errSeen = false;
let timedOut = false;
let capturedOut = "" timeoutTimer.interval = entry.timeoutMs;
let capturedErr = "" timeoutTimer.triggered.connect(function () {
let exitSeen = false
let exitCodeValue = -1
let outSeen = false
let errSeen = false
let timedOut = false
timeoutTimer.interval = entry.timeoutMs
timeoutTimer.triggered.connect(function() {
if (!exitSeen) { if (!exitSeen) {
timedOut = true timedOut = true;
proc.running = false proc.running = false;
exitSeen = true exitSeen = true;
exitCodeValue = 124 exitCodeValue = 124;
maybeComplete() maybeComplete();
} }
}) });
out.streamFinished.connect(function() { out.streamFinished.connect(function () {
try { try {
capturedOut = out.text || "" capturedOut = out.text || "";
} catch (e) { } catch (e) {
capturedOut = "" capturedOut = "";
} }
outSeen = true outSeen = true;
maybeComplete() maybeComplete();
}) });
err.streamFinished.connect(function() { err.streamFinished.connect(function () {
try { try {
capturedErr = err.text || "" capturedErr = err.text || "";
} catch (e) { } catch (e) {
capturedErr = "" capturedErr = "";
} }
errSeen = true errSeen = true;
maybeComplete() maybeComplete();
}) });
proc.exited.connect(function(code) { proc.exited.connect(function (code) {
timeoutTimer.stop() timeoutTimer.stop();
exitSeen = true exitSeen = true;
exitCodeValue = code exitCodeValue = code;
maybeComplete() maybeComplete();
}) });
function maybeComplete() { function maybeComplete() {
if (!exitSeen || !outSeen || !errSeen) return if (!exitSeen || !outSeen || !errSeen)
timeoutTimer.stop() return;
timeoutTimer.stop();
if (entry && entry.callback && typeof entry.callback === "function") { if (entry && entry.callback && typeof entry.callback === "function") {
try { try {
const safeOutput = capturedOut !== null && capturedOut !== undefined ? capturedOut : "" const safeOutput = capturedOut !== null && capturedOut !== undefined ? capturedOut : "";
const safeExitCode = exitCodeValue !== null && exitCodeValue !== undefined ? exitCodeValue : -1 const safeExitCode = exitCodeValue !== null && exitCodeValue !== undefined ? exitCodeValue : -1;
entry.callback(safeOutput, safeExitCode) entry.callback(safeOutput, safeExitCode);
} catch (e) { } catch (e) {
console.warn("runCommand callback error for command:", entry.command, "Error:", e) console.warn("runCommand callback error for command:", entry.command, "Error:", e);
} }
} }
try { proc.destroy() } catch (_) {} try {
try { timeoutTimer.destroy() } catch (_) {} proc.destroy();
} catch (_) {}
try {
timeoutTimer.destroy();
} catch (_) {}
if (isRandomId || entry.isRandomId) { if (isRandomId || entry.isRandomId) {
Qt.callLater(function() { Qt.callLater(function () {
if (_procDebouncers[id]) { if (_procDebouncers[id]) {
try { _procDebouncers[id].timer.destroy() } catch (_) {} try {
delete _procDebouncers[id] _procDebouncers[id].timer.destroy();
} catch (_) {}
delete _procDebouncers[id];
} }
}) });
} }
} }
proc.running = true proc.running = true;
timeoutTimer.start() if (entry.timeoutMs !== noTimeout)
timeoutTimer.start();
} }
} }

View File

@@ -648,6 +648,13 @@ Item {
return "SETTINGS_OPEN_SUCCESS"; return "SETTINGS_OPEN_SUCCESS";
} }
function openWith(tab: string): string {
if (!tab)
return "SETTINGS_OPEN_FAILED: No tab specified";
PopoutService.openSettingsWithTab(tab);
return `SETTINGS_OPEN_SUCCESS: ${tab}`;
}
function close(): string { function close(): string {
PopoutService.closeSettings(); PopoutService.closeSettings();
return "SETTINGS_CLOSE_SUCCESS"; return "SETTINGS_CLOSE_SUCCESS";
@@ -658,11 +665,47 @@ Item {
return "SETTINGS_TOGGLE_SUCCESS"; return "SETTINGS_TOGGLE_SUCCESS";
} }
function toggleWith(tab: string): string {
if (!tab)
return "SETTINGS_TOGGLE_FAILED: No tab specified";
PopoutService.toggleSettingsWithTab(tab);
return `SETTINGS_TOGGLE_SUCCESS: ${tab}`;
}
function focusOrToggle(): string { function focusOrToggle(): string {
PopoutService.focusOrToggleSettings(); PopoutService.focusOrToggleSettings();
return "SETTINGS_FOCUS_OR_TOGGLE_SUCCESS"; return "SETTINGS_FOCUS_OR_TOGGLE_SUCCESS";
} }
function focusOrToggleWith(tab: string): string {
if (!tab)
return "SETTINGS_FOCUS_OR_TOGGLE_FAILED: No tab specified";
PopoutService.focusOrToggleSettingsWithTab(tab);
return `SETTINGS_FOCUS_OR_TOGGLE_SUCCESS: ${tab}`;
}
function tabs(): string {
if (!PopoutService.settingsModal)
return "wallpaper\ntheme\ntypography\ntime_weather\nsounds\ndankbar\ndankbar_settings\ndankbar_widgets\nworkspaces\nmedia_player\nnotifications\nosd\nrunning_apps\nupdater\ndock\nlauncher\nkeybinds\ndisplays\nnetwork\nprinters\nlock_screen\npower_sleep\nplugins\nabout";
var modal = PopoutService.settingsModal;
var ids = [];
var structure = modal.sidebar?.categoryStructure ?? [];
for (var i = 0; i < structure.length; i++) {
var cat = structure[i];
if (cat.separator)
continue;
if (cat.id)
ids.push(cat.id);
if (cat.children) {
for (var j = 0; j < cat.children.length; j++) {
if (cat.children[j].id)
ids.push(cat.children[j].id);
}
}
}
return ids.join("\n");
}
function get(key: string): string { function get(key: string): string {
return JSON.stringify(SettingsData?.[key]); return JSON.stringify(SettingsData?.[key]);
} }

View File

@@ -110,7 +110,7 @@ DankModal {
console.warn("Failed to parse dms color pick JSON:", e); console.warn("Failed to parse dms color pick JSON:", e);
root.show(); root.show();
} }
}); }, 0, Proc.noTimeout);
} }
modalWidth: 680 modalWidth: 680

View File

@@ -10,6 +10,7 @@ FloatingWindow {
property alias profileBrowser: profileBrowser property alias profileBrowser: profileBrowser
property alias wallpaperBrowser: wallpaperBrowser property alias wallpaperBrowser: wallpaperBrowser
property alias sidebar: sidebar
property int currentTabIndex: 0 property int currentTabIndex: 0
property bool shouldHaveFocus: visible property bool shouldHaveFocus: visible
property bool allowFocusOverride: false property bool allowFocusOverride: false
@@ -32,6 +33,23 @@ FloatingWindow {
visible = !visible; visible = !visible;
} }
function showWithTab(tabIndex: int) {
if (tabIndex >= 0)
currentTabIndex = tabIndex;
visible = true;
}
function showWithTabName(tabName: string) {
var idx = sidebar.resolveTabIndex(tabName);
if (idx >= 0)
currentTabIndex = idx;
visible = true;
}
function resolveTabIndex(tabName: string): int {
return sidebar.resolveTabIndex(tabName);
}
function toggleMenu() { function toggleMenu() {
enableAnimations = true; enableAnimations = true;
menuVisible = !menuVisible; menuVisible = !menuVisible;

View File

@@ -21,26 +21,31 @@ Rectangle {
"icon": "palette", "icon": "palette",
"children": [ "children": [
{ {
"id": "wallpaper",
"text": I18n.tr("Wallpaper"), "text": I18n.tr("Wallpaper"),
"icon": "wallpaper", "icon": "wallpaper",
"tabIndex": 0 "tabIndex": 0
}, },
{ {
"id": "theme",
"text": I18n.tr("Theme & Colors"), "text": I18n.tr("Theme & Colors"),
"icon": "format_paint", "icon": "format_paint",
"tabIndex": 10 "tabIndex": 10
}, },
{ {
"id": "typography",
"text": I18n.tr("Typography & Motion"), "text": I18n.tr("Typography & Motion"),
"icon": "text_fields", "icon": "text_fields",
"tabIndex": 14 "tabIndex": 14
}, },
{ {
"id": "time_weather",
"text": I18n.tr("Time & Weather"), "text": I18n.tr("Time & Weather"),
"icon": "schedule", "icon": "schedule",
"tabIndex": 1 "tabIndex": 1
}, },
{ {
"id": "sounds",
"text": I18n.tr("Sounds"), "text": I18n.tr("Sounds"),
"icon": "volume_up", "icon": "volume_up",
"tabIndex": 15, "tabIndex": 15,
@@ -54,11 +59,13 @@ Rectangle {
"icon": "toolbar", "icon": "toolbar",
"children": [ "children": [
{ {
"id": "dankbar_settings",
"text": I18n.tr("Settings"), "text": I18n.tr("Settings"),
"icon": "tune", "icon": "tune",
"tabIndex": 3 "tabIndex": 3
}, },
{ {
"id": "dankbar_widgets",
"text": I18n.tr("Widgets"), "text": I18n.tr("Widgets"),
"icon": "widgets", "icon": "widgets",
"tabIndex": 22 "tabIndex": 22
@@ -72,32 +79,38 @@ Rectangle {
"collapsedByDefault": true, "collapsedByDefault": true,
"children": [ "children": [
{ {
"id": "workspaces",
"text": I18n.tr("Workspaces"), "text": I18n.tr("Workspaces"),
"icon": "view_module", "icon": "view_module",
"tabIndex": 4 "tabIndex": 4
}, },
{ {
"id": "media_player",
"text": I18n.tr("Media Player"), "text": I18n.tr("Media Player"),
"icon": "music_note", "icon": "music_note",
"tabIndex": 16 "tabIndex": 16
}, },
{ {
"id": "notifications",
"text": I18n.tr("Notifications"), "text": I18n.tr("Notifications"),
"icon": "notifications", "icon": "notifications",
"tabIndex": 17 "tabIndex": 17
}, },
{ {
"id": "osd",
"text": I18n.tr("On-screen Displays"), "text": I18n.tr("On-screen Displays"),
"icon": "tune", "icon": "tune",
"tabIndex": 18 "tabIndex": 18
}, },
{ {
"id": "running_apps",
"text": I18n.tr("Running Apps"), "text": I18n.tr("Running Apps"),
"icon": "apps", "icon": "apps",
"tabIndex": 19, "tabIndex": 19,
"hyprlandNiriOnly": true "hyprlandNiriOnly": true
}, },
{ {
"id": "updater",
"text": I18n.tr("System Updater"), "text": I18n.tr("System Updater"),
"icon": "refresh", "icon": "refresh",
"tabIndex": 20 "tabIndex": 20
@@ -111,11 +124,13 @@ Rectangle {
"collapsedByDefault": true, "collapsedByDefault": true,
"children": [ "children": [
{ {
"id": "dock",
"text": I18n.tr("Dock"), "text": I18n.tr("Dock"),
"icon": "dock_to_bottom", "icon": "dock_to_bottom",
"tabIndex": 5 "tabIndex": 5
}, },
{ {
"id": "launcher",
"text": I18n.tr("Launcher"), "text": I18n.tr("Launcher"),
"icon": "grid_view", "icon": "grid_view",
"tabIndex": 9 "tabIndex": 9
@@ -123,7 +138,7 @@ Rectangle {
] ]
}, },
{ {
"id": "input", "id": "keybinds",
"text": I18n.tr("Keyboard Shortcuts"), "text": I18n.tr("Keyboard Shortcuts"),
"icon": "keyboard", "icon": "keyboard",
"tabIndex": 2, "tabIndex": 2,
@@ -156,11 +171,13 @@ Rectangle {
"collapsedByDefault": true, "collapsedByDefault": true,
"children": [ "children": [
{ {
"id": "lock_screen",
"text": I18n.tr("Lock Screen"), "text": I18n.tr("Lock Screen"),
"icon": "lock", "icon": "lock",
"tabIndex": 11 "tabIndex": 11
}, },
{ {
"id": "power_sleep",
"text": I18n.tr("Power & Sleep"), "text": I18n.tr("Power & Sleep"),
"icon": "power_settings_new", "icon": "power_settings_new",
"tabIndex": 21 "tabIndex": 21
@@ -338,6 +355,37 @@ Rectangle {
return items; return items;
} }
function resolveTabIndex(name: string): int {
if (!name)
return -1;
var normalized = name.toLowerCase().replace(/[_\-\s]/g, "");
for (var i = 0; i < categoryStructure.length; i++) {
var cat = categoryStructure[i];
if (cat.separator)
continue;
var catId = (cat.id || "").toLowerCase().replace(/[_\-\s]/g, "");
if (catId === normalized) {
if (cat.tabIndex !== undefined)
return cat.tabIndex;
if (cat.children && cat.children.length > 0)
return cat.children[0].tabIndex;
}
if (cat.children) {
for (var j = 0; j < cat.children.length; j++) {
var child = cat.children[j];
var childId = (child.id || "").toLowerCase().replace(/[_\-\s]/g, "");
if (childId === normalized)
return child.tabIndex;
}
}
}
return -1;
}
width: 270 width: 270
height: parent.height height: parent.height
color: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency) color: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)

View File

@@ -312,7 +312,7 @@ Item {
Row { Row {
id: viewModeButtons id: viewModeButtons
spacing: Theme.spacingXS spacing: Theme.spacingXS
visible: searchMode === "apps" && appLauncher.model.count > 0 visible: searchMode === "apps"
anchors.right: parent.right anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter

View File

@@ -1,6 +1,5 @@
import QtQuick import QtQuick
import QtQuick.Layouts import QtQuick.Layouts
import Quickshell
import qs.Common import qs.Common
import qs.Services import qs.Services
import qs.Widgets import qs.Widgets
@@ -17,20 +16,18 @@ PluginComponent {
ccWidgetPrimaryText: I18n.tr("Printers") ccWidgetPrimaryText: I18n.tr("Printers")
ccWidgetSecondaryText: { ccWidgetSecondaryText: {
if (CupsService.cupsAvailable && CupsService.getPrintersNum() > 0) { if (CupsService.cupsAvailable && CupsService.getPrintersNum() > 0) {
return I18n.tr("Printers: ") + CupsService.getPrintersNum() + " - " + I18n.tr("Jobs: ") + CupsService.getTotalJobsNum() return I18n.tr("Printers: ") + CupsService.getPrintersNum() + " - " + I18n.tr("Jobs: ") + CupsService.getTotalJobsNum();
} else { } else {
if (!CupsService.cupsAvailable) { if (!CupsService.cupsAvailable) {
return I18n.tr("Print Server not available") return I18n.tr("Print Server not available");
} else { } else {
return I18n.tr("No printer found") return I18n.tr("No printer found");
} }
} }
} }
ccWidgetIsActive: CupsService.cupsAvailable && CupsService.getTotalJobsNum() > 0 ccWidgetIsActive: CupsService.cupsAvailable && CupsService.getTotalJobsNum() > 0
onCcWidgetToggled: { onCcWidgetToggled: {}
}
ccDetailContent: Component { ccDetailContent: Component {
Rectangle { Rectangle {
@@ -39,6 +36,21 @@ PluginComponent {
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: Theme.surfaceContainerHigh color: Theme.surfaceContainerHigh
DankActionButton {
anchors.top: parent.top
anchors.right: parent.right
anchors.topMargin: Theme.spacingS
anchors.rightMargin: Theme.spacingS
iconName: "settings"
buttonSize: 24
iconSize: 14
iconColor: Theme.surfaceVariantText
onClicked: {
PopoutService.closeControlCenter();
PopoutService.openSettingsWithTab("printers");
}
}
Column { Column {
visible: !CupsService.cupsAvailable || CupsService.getPrintersNum() == 0 visible: !CupsService.cupsAvailable || CupsService.getPrintersNum() == 0
anchors.centerIn: parent anchors.centerIn: parent
@@ -58,7 +70,7 @@ PluginComponent {
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
} }
} }
Column { Column {
id: detailColumn id: detailColumn
anchors.fill: parent anchors.fill: parent
@@ -78,12 +90,12 @@ PluginComponent {
Layout.maximumWidth: parent.width - 180 Layout.maximumWidth: parent.width - 180
description: "" description: ""
currentValue: { currentValue: {
CupsService.getSelectedPrinter() CupsService.getSelectedPrinter();
} }
options: CupsService.getPrintersNames() options: CupsService.getPrintersNames()
onValueChanged: value => { onValueChanged: value => {
CupsService.setSelectedPrinter(value) CupsService.setSelectedPrinter(value);
} }
} }
Column { Column {
@@ -135,11 +147,11 @@ PluginComponent {
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
enabled: true enabled: true
onClicked: { onClicked: {
const selected = CupsService.getSelectedPrinter() const selected = CupsService.getSelectedPrinter();
if (CupsService.getCurrentPrinterState() === "stopped") { if (CupsService.getCurrentPrinterState() === "stopped") {
CupsService.resumePrinter(selected) CupsService.resumePrinter(selected);
} else { } else {
CupsService.pausePrinter(selected) CupsService.pausePrinter(selected);
} }
} }
} }
@@ -180,8 +192,8 @@ PluginComponent {
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
enabled: true enabled: true
onClicked: { onClicked: {
const selected = CupsService.getSelectedPrinter() const selected = CupsService.getSelectedPrinter();
CupsService.purgeJobs(selected) CupsService.purgeJobs(selected);
} }
} }
} }
@@ -275,8 +287,8 @@ PluginComponent {
StyledText { StyledText {
text: { text: {
var date = new Date(modelData.timeCreated) var date = new Date(modelData.timeCreated);
return date.toLocaleString(Qt.locale(), Locale.ShortFormat) return date.toLocaleString(Qt.locale(), Locale.ShortFormat);
} }
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceTextMedium color: Theme.surfaceTextMedium
@@ -296,7 +308,7 @@ PluginComponent {
iconName: "delete" iconName: "delete"
buttonSize: 36 buttonSize: 36
onClicked: { onClicked: {
CupsService.cancelJob(CupsService.getSelectedPrinter(), modelData.id) CupsService.cancelJob(CupsService.getSelectedPrinter(), modelData.id);
} }
} }
} }

View File

@@ -69,8 +69,8 @@ Rectangle {
height: 40 height: 40
StyledText { StyledText {
id: headerText id: headerLeft
text: I18n.tr("Network Settings") text: I18n.tr("Network")
font.pixelSize: Theme.fontSizeLarge font.pixelSize: Theme.fontSizeLarge
color: Theme.surfaceText color: Theme.surfaceText
font.weight: Font.Medium font.weight: Font.Medium
@@ -79,7 +79,7 @@ Rectangle {
Item { Item {
height: 1 height: 1
width: parent.width - headerText.width - rightControls.width width: parent.width - headerLeft.width - rightControls.width
} }
Row { Row {
@@ -115,6 +115,8 @@ Rectangle {
id: preferenceControls id: preferenceControls
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
visible: NetworkService.backend === "networkmanager" && DMSService.apiVersion > 10 visible: NetworkService.backend === "networkmanager" && DMSService.apiVersion > 10
buttonHeight: 28
textSize: Theme.fontSizeSmall
model: ["Ethernet", "WiFi"] model: ["Ethernet", "WiFi"]
currentIndex: currentPreferenceIndex currentIndex: currentPreferenceIndex
@@ -125,6 +127,18 @@ Rectangle {
NetworkService.setNetworkPreference(index === 0 ? "ethernet" : "wifi"); NetworkService.setNetworkPreference(index === 0 ? "ethernet" : "wifi");
} }
} }
DankActionButton {
anchors.verticalCenter: parent.verticalCenter
iconName: "settings"
buttonSize: 28
iconSize: 16
iconColor: Theme.surfaceVariantText
onClicked: {
PopoutService.closeControlCenter();
PopoutService.openSettingsWithTab("network");
}
}
} }
} }

View File

@@ -1,6 +1,5 @@
import QtQuick import QtQuick
import qs.Common import qs.Common
import qs.Services
Item { Item {
id: root id: root
@@ -19,7 +18,7 @@ Item {
property bool forceVerticalLayout: false property bool forceVerticalLayout: false
readonly property bool isVertical: overrideAxisLayout ? forceVerticalLayout : (axis?.isVertical ?? false) readonly property bool isVertical: overrideAxisLayout ? forceVerticalLayout : (axis?.isVertical ?? false)
readonly property real spacing: { readonly property real widgetSpacing: {
const baseSpacing = noBackground ? 2 : Theme.spacingXS; const baseSpacing = noBackground ? 2 : Theme.spacingXS;
const outlineThickness = (barConfig?.widgetOutlineEnabled ?? false) ? (barConfig?.widgetOutlineThickness ?? 1) : 0; const outlineThickness = (barConfig?.widgetOutlineEnabled ?? false) ? (barConfig?.widgetOutlineThickness ?? 1) : 0;
return baseSpacing + (outlineThickness * 2); return baseSpacing + (outlineThickness * 2);
@@ -33,36 +32,32 @@ Item {
if (SettingsData.centeringMode === "geometric") { if (SettingsData.centeringMode === "geometric") {
applyGeometricLayout(); applyGeometricLayout();
} else { } else {
// Default to index layout or if value is not 'geometric'
applyIndexLayout(); applyIndexLayout();
} }
} }
function applyGeometricLayout() { function applyGeometricLayout() {
if ((isVertical ? height : width) <= 0 || !visible) { if ((isVertical ? height : width) <= 0 || !visible)
return; return;
}
centerWidgets = []; centerWidgets = [];
totalWidgets = 0; totalWidgets = 0;
totalSize = 0; totalSize = 0;
for (var i = 0; i < centerRepeater.count; i++) { for (var i = 0; i < centerRepeater.count; i++) {
const item = centerRepeater.itemAt(i); const loader = centerRepeater.itemAt(i);
if (item && item.active && item.item && getWidgetVisible(item.widgetId)) { if (loader && loader.active && loader.item) {
centerWidgets.push(item.item); centerWidgets.push(loader.item);
totalWidgets++; totalWidgets++;
totalSize += isVertical ? item.item.height : item.item.width; totalSize += isVertical ? loader.item.height : loader.item.width;
} }
} }
if (totalWidgets === 0) { if (totalWidgets === 0)
return; return;
}
if (totalWidgets > 1) { if (totalWidgets > 1)
totalSize += spacing * (totalWidgets - 1); totalSize += widgetSpacing * (totalWidgets - 1);
}
positionWidgetsGeometric(); positionWidgetsGeometric();
} }
@@ -70,7 +65,6 @@ Item {
function positionWidgetsGeometric() { function positionWidgetsGeometric() {
const parentLength = isVertical ? height : width; const parentLength = isVertical ? height : width;
const parentCenter = parentLength / 2; const parentCenter = parentLength / 2;
let currentPos = parentCenter - (totalSize / 2); let currentPos = parentCenter - (totalSize / 2);
centerWidgets.forEach(widget => { centerWidgets.forEach(widget => {
@@ -81,67 +75,53 @@ Item {
widget.anchors.horizontalCenter = undefined; widget.anchors.horizontalCenter = undefined;
widget.x = currentPos; widget.x = currentPos;
} }
const widgetSize = isVertical ? widget.height : widget.width; const widgetSize = isVertical ? widget.height : widget.width;
currentPos += widgetSize + spacing; currentPos += widgetSize + widgetSpacing;
}); });
} }
function applyIndexLayout() { function applyIndexLayout() {
if ((isVertical ? height : width) <= 0 || !visible) { if ((isVertical ? height : width) <= 0 || !visible)
return; return;
}
centerWidgets = []; centerWidgets = [];
totalWidgets = 0; totalWidgets = 0;
totalSize = 0; totalSize = 0;
let configuredWidgets = 0;
let configuredMiddleWidget = null; let configuredMiddleWidget = null;
let configuredLeftWidget = null; let configuredLeftWidget = null;
let configuredRightWidget = null; let configuredRightWidget = null;
for (var i = 0; i < centerRepeater.count; i++) { const configuredWidgets = centerRepeater.count;
const item = centerRepeater.itemAt(i);
if (item && getWidgetVisible(item.widgetId)) {
configuredWidgets++;
}
}
const isOddConfigured = configuredWidgets % 2 === 1; const isOddConfigured = configuredWidgets % 2 === 1;
const configuredMiddlePos = Math.floor(configuredWidgets / 2); const configuredMiddlePos = Math.floor(configuredWidgets / 2);
const configuredLeftPos = isOddConfigured ? -1 : ((configuredWidgets / 2) - 1); const configuredLeftPos = isOddConfigured ? -1 : ((configuredWidgets / 2) - 1);
const configuredRightPos = isOddConfigured ? -1 : (configuredWidgets / 2); const configuredRightPos = isOddConfigured ? -1 : (configuredWidgets / 2);
let currentConfigIndex = 0;
for (var i = 0; i < centerRepeater.count; i++) { for (var i = 0; i < centerRepeater.count; i++) {
const item = centerRepeater.itemAt(i); const wrapper = centerRepeater.itemAt(i);
if (item && getWidgetVisible(item.widgetId)) { if (!wrapper)
if (isOddConfigured && currentConfigIndex === configuredMiddlePos && item.active && item.item) { continue;
configuredMiddleWidget = item.item;
} if (isOddConfigured && i === configuredMiddlePos && wrapper.active && wrapper.item)
if (!isOddConfigured && currentConfigIndex === configuredLeftPos && item.active && item.item) { configuredMiddleWidget = wrapper.item;
configuredLeftWidget = item.item; if (!isOddConfigured && i === configuredLeftPos && wrapper.active && wrapper.item)
} configuredLeftWidget = wrapper.item;
if (!isOddConfigured && currentConfigIndex === configuredRightPos && item.active && item.item) { if (!isOddConfigured && i === configuredRightPos && wrapper.active && wrapper.item)
configuredRightWidget = item.item; configuredRightWidget = wrapper.item;
}
if (item.active && item.item) { if (wrapper.active && wrapper.item) {
centerWidgets.push(item.item); centerWidgets.push(wrapper.item);
totalWidgets++; totalWidgets++;
totalSize += isVertical ? item.item.height : item.item.width; totalSize += isVertical ? wrapper.item.height : wrapper.item.width;
}
currentConfigIndex++;
} }
} }
if (totalWidgets === 0) { if (totalWidgets === 0)
return; return;
}
if (totalWidgets > 1) { if (totalWidgets > 1)
totalSize += spacing * (totalWidgets - 1); totalSize += widgetSpacing * (totalWidgets - 1);
}
positionWidgetsByIndex(configuredWidgets, configuredMiddleWidget, configuredLeftWidget, configuredRightWidget); positionWidgetsByIndex(configuredWidgets, configuredMiddleWidget, configuredLeftWidget, configuredRightWidget);
} }
@@ -151,11 +131,10 @@ Item {
const isOddConfigured = configuredWidgets % 2 === 1; const isOddConfigured = configuredWidgets % 2 === 1;
centerWidgets.forEach(widget => { centerWidgets.forEach(widget => {
if (isVertical) { if (isVertical)
widget.anchors.verticalCenter = undefined; widget.anchors.verticalCenter = undefined;
} else { else
widget.anchors.horizontalCenter = undefined; widget.anchors.horizontalCenter = undefined;
}
}); });
if (isOddConfigured && configuredMiddleWidget) { if (isOddConfigured && configuredMiddleWidget) {
@@ -163,222 +142,154 @@ Item {
const middleIndex = centerWidgets.indexOf(middleWidget); const middleIndex = centerWidgets.indexOf(middleWidget);
const middleSize = isVertical ? middleWidget.height : middleWidget.width; const middleSize = isVertical ? middleWidget.height : middleWidget.width;
if (isVertical) { if (isVertical)
middleWidget.y = parentCenter - (middleSize / 2); middleWidget.y = parentCenter - (middleSize / 2);
} else { else
middleWidget.x = parentCenter - (middleSize / 2); middleWidget.x = parentCenter - (middleSize / 2);
}
let currentPos = isVertical ? middleWidget.y : middleWidget.x; let currentPos = isVertical ? middleWidget.y : middleWidget.x;
for (var i = middleIndex - 1; i >= 0; i--) { for (var i = middleIndex - 1; i >= 0; i--) {
const size = isVertical ? centerWidgets[i].height : centerWidgets[i].width; const size = isVertical ? centerWidgets[i].height : centerWidgets[i].width;
currentPos -= (spacing + size); currentPos -= (widgetSpacing + size);
if (isVertical) { if (isVertical)
centerWidgets[i].y = currentPos; centerWidgets[i].y = currentPos;
} else { else
centerWidgets[i].x = currentPos; centerWidgets[i].x = currentPos;
}
} }
currentPos = (isVertical ? middleWidget.y : middleWidget.x) + middleSize; currentPos = (isVertical ? middleWidget.y : middleWidget.x) + middleSize;
for (var i = middleIndex + 1; i < totalWidgets; i++) { for (var i = middleIndex + 1; i < totalWidgets; i++) {
currentPos += spacing; currentPos += widgetSpacing;
if (isVertical) { if (isVertical)
centerWidgets[i].y = currentPos; centerWidgets[i].y = currentPos;
} else { else
centerWidgets[i].x = currentPos; centerWidgets[i].x = currentPos;
}
currentPos += isVertical ? centerWidgets[i].height : centerWidgets[i].width; currentPos += isVertical ? centerWidgets[i].height : centerWidgets[i].width;
} }
} else { return;
if (totalWidgets === 1) { }
const widget = centerWidgets[0];
const size = isVertical ? widget.height : widget.width; if (totalWidgets === 1) {
if (isVertical) { const widget = centerWidgets[0];
widget.y = parentCenter - (size / 2); const size = isVertical ? widget.height : widget.width;
} else { if (isVertical)
widget.x = parentCenter - (size / 2); widget.y = parentCenter - (size / 2);
else
widget.x = parentCenter - (size / 2);
return;
}
if (!configuredLeftWidget || !configuredRightWidget) {
if (totalWidgets % 2 === 1) {
const middleIndex = Math.floor(totalWidgets / 2);
const middleWidget = centerWidgets[middleIndex];
if (!middleWidget)
return;
const middleSize = isVertical ? middleWidget.height : middleWidget.width;
if (isVertical)
middleWidget.y = parentCenter - (middleSize / 2);
else
middleWidget.x = parentCenter - (middleSize / 2);
let currentPos = isVertical ? middleWidget.y : middleWidget.x;
for (var i = middleIndex - 1; i >= 0; i--) {
const size = isVertical ? centerWidgets[i].height : centerWidgets[i].width;
currentPos -= (widgetSpacing + size);
if (isVertical)
centerWidgets[i].y = currentPos;
else
centerWidgets[i].x = currentPos;
} }
return;
}
if (!configuredLeftWidget || !configuredRightWidget) { currentPos = (isVertical ? middleWidget.y : middleWidget.x) + middleSize;
if (totalWidgets % 2 === 1) { for (var i = middleIndex + 1; i < totalWidgets; i++) {
const middleIndex = Math.floor(totalWidgets / 2); currentPos += widgetSpacing;
const middleWidget = centerWidgets[middleIndex]; if (isVertical)
centerWidgets[i].y = currentPos;
if (!middleWidget) { else
return; centerWidgets[i].x = currentPos;
} currentPos += isVertical ? centerWidgets[i].height : centerWidgets[i].width;
const middleSize = isVertical ? middleWidget.height : middleWidget.width;
if (isVertical) {
middleWidget.y = parentCenter - (middleSize / 2);
} else {
middleWidget.x = parentCenter - (middleSize / 2);
}
let currentPos = isVertical ? middleWidget.y : middleWidget.x;
for (var i = middleIndex - 1; i >= 0; i--) {
const size = isVertical ? centerWidgets[i].height : centerWidgets[i].width;
currentPos -= (spacing + size);
if (isVertical) {
centerWidgets[i].y = currentPos;
} else {
centerWidgets[i].x = currentPos;
}
}
currentPos = (isVertical ? middleWidget.y : middleWidget.x) + middleSize;
for (var i = middleIndex + 1; i < totalWidgets; i++) {
currentPos += spacing;
if (isVertical) {
centerWidgets[i].y = currentPos;
} else {
centerWidgets[i].x = currentPos;
}
currentPos += isVertical ? centerWidgets[i].height : centerWidgets[i].width;
}
} else {
const leftIndex = (totalWidgets / 2) - 1;
const rightIndex = totalWidgets / 2;
const fallbackLeft = centerWidgets[leftIndex];
const fallbackRight = centerWidgets[rightIndex];
if (!fallbackLeft || !fallbackRight) {
return;
}
const halfSpacing = spacing / 2;
const leftSize = isVertical ? fallbackLeft.height : fallbackLeft.width;
if (isVertical) {
fallbackLeft.y = parentCenter - halfSpacing - leftSize;
fallbackRight.y = parentCenter + halfSpacing;
} else {
fallbackLeft.x = parentCenter - halfSpacing - leftSize;
fallbackRight.x = parentCenter + halfSpacing;
}
let currentPos = isVertical ? fallbackLeft.y : fallbackLeft.x;
for (var i = leftIndex - 1; i >= 0; i--) {
const size = isVertical ? centerWidgets[i].height : centerWidgets[i].width;
currentPos -= (spacing + size);
if (isVertical) {
centerWidgets[i].y = currentPos;
} else {
centerWidgets[i].x = currentPos;
}
}
currentPos = (isVertical ? fallbackRight.y + fallbackRight.height : fallbackRight.x + fallbackRight.width);
for (var i = rightIndex + 1; i < totalWidgets; i++) {
currentPos += spacing;
if (isVertical) {
centerWidgets[i].y = currentPos;
} else {
centerWidgets[i].x = currentPos;
}
currentPos += isVertical ? centerWidgets[i].height : centerWidgets[i].width;
}
} }
return;
}
const leftWidget = configuredLeftWidget;
const rightWidget = configuredRightWidget;
const leftIndex = centerWidgets.indexOf(leftWidget);
const rightIndex = centerWidgets.indexOf(rightWidget);
const halfSpacing = spacing / 2;
const leftSize = isVertical ? leftWidget.height : leftWidget.width;
if (isVertical) {
leftWidget.y = parentCenter - halfSpacing - leftSize;
rightWidget.y = parentCenter + halfSpacing;
} else { } else {
leftWidget.x = parentCenter - halfSpacing - leftSize; const leftIndex = (totalWidgets / 2) - 1;
rightWidget.x = parentCenter + halfSpacing; const rightIndex = totalWidgets / 2;
} const fallbackLeft = centerWidgets[leftIndex];
const fallbackRight = centerWidgets[rightIndex];
if (!fallbackLeft || !fallbackRight)
return;
const halfSpacing = widgetSpacing / 2;
const leftSize = isVertical ? fallbackLeft.height : fallbackLeft.width;
let currentPos = isVertical ? leftWidget.y : leftWidget.x;
for (var i = leftIndex - 1; i >= 0; i--) {
const size = isVertical ? centerWidgets[i].height : centerWidgets[i].width;
currentPos -= (spacing + size);
if (isVertical) { if (isVertical) {
centerWidgets[i].y = currentPos; fallbackLeft.y = parentCenter - halfSpacing - leftSize;
fallbackRight.y = parentCenter + halfSpacing;
} else { } else {
centerWidgets[i].x = currentPos; fallbackLeft.x = parentCenter - halfSpacing - leftSize;
fallbackRight.x = parentCenter + halfSpacing;
}
let currentPos = isVertical ? fallbackLeft.y : fallbackLeft.x;
for (var i = leftIndex - 1; i >= 0; i--) {
const size = isVertical ? centerWidgets[i].height : centerWidgets[i].width;
currentPos -= (widgetSpacing + size);
if (isVertical)
centerWidgets[i].y = currentPos;
else
centerWidgets[i].x = currentPos;
}
currentPos = (isVertical ? fallbackRight.y + fallbackRight.height : fallbackRight.x + fallbackRight.width);
for (var i = rightIndex + 1; i < totalWidgets; i++) {
currentPos += widgetSpacing;
if (isVertical)
centerWidgets[i].y = currentPos;
else
centerWidgets[i].x = currentPos;
currentPos += isVertical ? centerWidgets[i].height : centerWidgets[i].width;
} }
} }
return;
currentPos = (isVertical ? rightWidget.y + rightWidget.height : rightWidget.x + rightWidget.width);
for (var i = rightIndex + 1; i < totalWidgets; i++) {
currentPos += spacing;
if (isVertical) {
centerWidgets[i].y = currentPos;
} else {
centerWidgets[i].x = currentPos;
}
currentPos += isVertical ? centerWidgets[i].height : centerWidgets[i].width;
}
}
}
function getWidgetVisible(widgetId) {
const widgetVisibility = {
"cpuUsage": DgopService.dgopAvailable,
"memUsage": DgopService.dgopAvailable,
"cpuTemp": DgopService.dgopAvailable,
"gpuTemp": DgopService.dgopAvailable,
"network_speed_monitor": DgopService.dgopAvailable
};
return widgetVisibility[widgetId] ?? true;
}
function getWidgetComponent(widgetId) {
// Build dynamic component map including plugins
let baseMap = {
"launcherButton": "launcherButtonComponent",
"workspaceSwitcher": "workspaceSwitcherComponent",
"focusedWindow": "focusedWindowComponent",
"runningApps": "runningAppsComponent",
"clock": "clockComponent",
"music": "mediaComponent",
"weather": "weatherComponent",
"systemTray": "systemTrayComponent",
"privacyIndicator": "privacyIndicatorComponent",
"clipboard": "clipboardComponent",
"cpuUsage": "cpuUsageComponent",
"memUsage": "memUsageComponent",
"diskUsage": "diskUsageComponent",
"cpuTemp": "cpuTempComponent",
"gpuTemp": "gpuTempComponent",
"notificationButton": "notificationButtonComponent",
"battery": "batteryComponent",
"controlCenterButton": "controlCenterButtonComponent",
"idleInhibitor": "idleInhibitorComponent",
"spacer": "spacerComponent",
"separator": "separatorComponent",
"network_speed_monitor": "networkComponent",
"keyboard_layout_name": "keyboardLayoutNameComponent",
"vpn": "vpnComponent",
"notepadButton": "notepadButtonComponent",
"colorPicker": "colorPickerComponent",
"systemUpdate": "systemUpdateComponent"
};
// For built-in components, get from components property
const componentKey = baseMap[widgetId];
if (componentKey && root.components[componentKey]) {
return root.components[componentKey];
} }
// For plugin components, get from PluginService const leftWidget = configuredLeftWidget;
var parts = widgetId.split(":"); const rightWidget = configuredRightWidget;
var pluginId = parts[0]; const leftIndex = centerWidgets.indexOf(leftWidget);
let pluginComponents = PluginService.getWidgetComponents(); const rightIndex = centerWidgets.indexOf(rightWidget);
return pluginComponents[pluginId] || null; const halfSpacing = widgetSpacing / 2;
const leftSize = isVertical ? leftWidget.height : leftWidget.width;
if (isVertical) {
leftWidget.y = parentCenter - halfSpacing - leftSize;
rightWidget.y = parentCenter + halfSpacing;
} else {
leftWidget.x = parentCenter - halfSpacing - leftSize;
rightWidget.x = parentCenter + halfSpacing;
}
let currentPos = isVertical ? leftWidget.y : leftWidget.x;
for (var i = leftIndex - 1; i >= 0; i--) {
const size = isVertical ? centerWidgets[i].height : centerWidgets[i].width;
currentPos -= (widgetSpacing + size);
if (isVertical)
centerWidgets[i].y = currentPos;
else
centerWidgets[i].x = currentPos;
}
currentPos = (isVertical ? rightWidget.y + rightWidget.height : rightWidget.x + rightWidget.width);
for (var i = rightIndex + 1; i < totalWidgets; i++) {
currentPos += widgetSpacing;
if (isVertical)
centerWidgets[i].y = currentPos;
else
centerWidgets[i].x = currentPos;
currentPos += isVertical ? centerWidgets[i].height : centerWidgets[i].width;
}
} }
height: parent.height height: parent.height
@@ -392,177 +303,71 @@ Item {
onTriggered: root.updateLayout() onTriggered: root.updateLayout()
} }
Component.onCompleted: { Component.onCompleted: layoutTimer.restart()
layoutTimer.restart();
}
onWidthChanged: { onWidthChanged: {
if (width > 0) { if (width > 0)
layoutTimer.restart(); layoutTimer.restart();
}
} }
onHeightChanged: { onHeightChanged: {
if (height > 0) { if (height > 0)
layoutTimer.restart(); layoutTimer.restart();
}
} }
onVisibleChanged: { onVisibleChanged: {
if (visible && (isVertical ? height : width) > 0) { if (visible && (isVertical ? height : width) > 0)
layoutTimer.restart(); layoutTimer.restart();
}
} }
Repeater { Repeater {
id: centerRepeater id: centerRepeater
model: root.widgetsModel model: root.widgetsModel
Loader { onCountChanged: layoutTimer.restart()
Item {
property var itemData: modelData property var itemData: modelData
property string widgetId: itemData.widgetId readonly property real itemSpacing: root.widgetSpacing
property var widgetData: itemData
property int spacerSize: itemData.size || 20
anchors.verticalCenter: !root.isVertical ? parent.verticalCenter : undefined width: widgetLoader.item ? widgetLoader.item.width : 0
anchors.horizontalCenter: root.isVertical ? parent.horizontalCenter : undefined height: widgetLoader.item ? widgetLoader.item.height : 0
active: root.getWidgetVisible(itemData.widgetId) && (itemData.widgetId !== "music" || MprisController.activePlayer !== null)
sourceComponent: root.getWidgetComponent(itemData.widgetId)
opacity: (itemData.enabled !== false) ? 1 : 0
asynchronous: false
onLoaded: { readonly property bool active: widgetLoader.active
if (!item) { readonly property var item: widgetLoader.item
return;
} WidgetHost {
item.widthChanged.connect(() => { id: widgetLoader
if (layoutTimer)
layoutTimer.restart(); anchors.verticalCenter: !root.isVertical ? parent.verticalCenter : undefined
}); anchors.horizontalCenter: root.isVertical ? parent.horizontalCenter : undefined
item.heightChanged.connect(() => {
if (layoutTimer) widgetId: itemData.widgetId
layoutTimer.restart(); widgetData: itemData
}); spacerSize: itemData.size || 20
if (root.axis && "axis" in item) { components: root.components
item.axis = Qt.binding(() => root.axis); isInColumn: root.isVertical
} axis: root.axis
if (root.axis && "isVertical" in item) { section: "center"
try { parentScreen: root.parentScreen
item.isVertical = Qt.binding(() => root.axis.isVertical); widgetThickness: root.widgetThickness
} catch (e) {} barThickness: root.barThickness
barSpacing: root.barSpacing
barConfig: root.barConfig
isFirst: index === 0
isLast: index === centerRepeater.count - 1
sectionSpacing: parent.itemSpacing
isLeftBarEdge: false
isRightBarEdge: false
isTopBarEdge: false
isBottomBarEdge: false
onContentItemReady: contentItem => {
contentItem.widthChanged.connect(() => layoutTimer.restart());
contentItem.heightChanged.connect(() => layoutTimer.restart());
} }
// Inject properties for plugin widgets onActiveChanged: layoutTimer.restart()
if ("section" in item) {
item.section = root.section;
}
if ("parentScreen" in item) {
item.parentScreen = Qt.binding(() => root.parentScreen);
}
if ("widgetThickness" in item) {
item.widgetThickness = Qt.binding(() => root.widgetThickness);
}
if ("barThickness" in item) {
item.barThickness = Qt.binding(() => root.barThickness);
}
if ("barSpacing" in item) {
item.barSpacing = Qt.binding(() => root.barSpacing);
}
if ("barConfig" in item) {
item.barConfig = Qt.binding(() => root.barConfig);
}
if ("sectionSpacing" in item) {
item.sectionSpacing = Qt.binding(() => root.spacing);
}
if ("widgetData" in item) {
item.widgetData = Qt.binding(() => widgetData);
}
if ("isFirst" in item) {
item.isFirst = Qt.binding(() => {
for (var i = 0; i < centerRepeater.count; i++) {
const checkItem = centerRepeater.itemAt(i);
if (checkItem && checkItem.active && checkItem.item) {
return checkItem.item === item;
}
}
return false;
});
}
if ("isLast" in item) {
item.isLast = Qt.binding(() => {
for (var i = centerRepeater.count - 1; i >= 0; i--) {
const checkItem = centerRepeater.itemAt(i);
if (checkItem && checkItem.active && checkItem.item) {
return checkItem.item === item;
}
}
return false;
});
}
if ("isLeftBarEdge" in item) {
item.isLeftBarEdge = false;
}
if ("isRightBarEdge" in item) {
item.isRightBarEdge = false;
}
if ("isTopBarEdge" in item) {
item.isTopBarEdge = false;
}
if ("isBottomBarEdge" in item) {
item.isBottomBarEdge = false;
}
if (item.pluginService !== undefined) {
var parts = model.widgetId.split(":");
var pluginId = parts[0];
var variantId = parts.length > 1 ? parts[1] : null;
if (item.pluginId !== undefined) {
item.pluginId = pluginId;
}
if (item.variantId !== undefined) {
item.variantId = variantId;
}
if (item.variantData !== undefined && variantId) {
item.variantData = PluginService.getPluginVariantData(pluginId, variantId);
}
item.pluginService = PluginService;
}
if (item.popoutService !== undefined) {
item.popoutService = PopoutService;
}
layoutTimer.restart();
}
onActiveChanged: {
layoutTimer.restart();
}
}
}
Connections {
target: PluginService
function onPluginLoaded(pluginId) {
// Force refresh of component lookups
for (var i = 0; i < centerRepeater.count; i++) {
var item = centerRepeater.itemAt(i);
if (item && item.widgetId.startsWith(pluginId)) {
item.sourceComponent = root.getWidgetComponent(item.widgetId);
}
}
}
function onPluginUnloaded(pluginId) {
// Force refresh of component lookups
for (var i = 0; i < centerRepeater.count; i++) {
var item = centerRepeater.itemAt(i);
if (item && item.widgetId.startsWith(pluginId)) {
item.sourceComponent = root.getWidgetComponent(item.widgetId);
}
} }
} }
} }

View File

@@ -94,7 +94,7 @@ Item {
return true; return true;
} }
if (event.key === Qt.Key_Right) { if (event.key === Qt.Key_Right || event.key === Qt.Key_L) {
if (gridIndex + 1 < visibleCount) { if (gridIndex + 1 < visibleCount) {
gridIndex++; gridIndex++;
} else if (currentPage < totalPages - 1) { } else if (currentPage < totalPages - 1) {
@@ -104,7 +104,7 @@ Item {
return true; return true;
} }
if (event.key === Qt.Key_Left) { if (event.key === Qt.Key_Left || event.key === Qt.Key_H) {
if (gridIndex > 0) { if (gridIndex > 0) {
gridIndex--; gridIndex--;
} else if (currentPage > 0) { } else if (currentPage > 0) {
@@ -115,7 +115,7 @@ Item {
return true; return true;
} }
if (event.key === Qt.Key_Down) { if (event.key === Qt.Key_Down || event.key === Qt.Key_J) {
if (gridIndex + columns < visibleCount) { if (gridIndex + columns < visibleCount) {
gridIndex += columns; gridIndex += columns;
} else if (currentPage < totalPages - 1) { } else if (currentPage < totalPages - 1) {
@@ -125,7 +125,7 @@ Item {
return true; return true;
} }
if (event.key === Qt.Key_Up) { if (event.key === Qt.Key_Up || event.key === Qt.Key_K) {
if (gridIndex >= columns) { if (gridIndex >= columns) {
gridIndex -= columns; gridIndex -= columns;
} else if (currentPage > 0) { } else if (currentPage > 0) {

View File

@@ -208,14 +208,11 @@ SWAY_EOF
;; ;;
mangowc) mangowc)
TEMP_DIR=$(mktemp -d)
if [[ -n "$COMPOSITOR_CONFIG" ]]; then if [[ -n "$COMPOSITOR_CONFIG" ]]; then
cp "$COMPOSITOR_CONFIG" "$TEMP_DIR/config.conf" exec mango -c "$COMPOSITOR_CONFIG" -s "$QS_CMD && mmsg -d quit"
else else
touch "$TEMP_DIR/config.conf" exec mango -s "$QS_CMD && mmsg -d quit"
fi fi
export MANGOCONFIG="$TEMP_DIR"
exec mango -s "$QS_CMD && mmsg -d quit"
;; ;;
*) *)

View File

@@ -1,6 +1,7 @@
pragma ComponentBehavior: Bound pragma ComponentBehavior: Bound
import QtQuick import QtQuick
import QtQuick.Effects
import qs.Common import qs.Common
import qs.Services import qs.Services
import qs.Widgets import qs.Widgets
@@ -18,262 +19,365 @@ Rectangle {
property int gridRows: 2 property int gridRows: 2
property bool useGridLayout: false property bool useGridLayout: false
signal closed() property string holdAction: ""
property int holdActionIndex: -1
property real holdProgress: 0
property bool showHoldHint: false
readonly property bool needsConfirmation: SettingsData.powerActionConfirm
readonly property int holdDurationMs: SettingsData.powerActionHoldDuration * 1000
signal closed
function updateVisibleActions() { function updateVisibleActions() {
const allActions = (typeof SettingsData !== "undefined" && SettingsData.powerMenuActions) const allActions = (typeof SettingsData !== "undefined" && SettingsData.powerMenuActions) ? SettingsData.powerMenuActions : ["logout", "suspend", "hibernate", "reboot", "poweroff"];
? SettingsData.powerMenuActions const hibernateSupported = (typeof SessionService !== "undefined" && SessionService.hibernateSupported) || false;
: ["logout", "suspend", "hibernate", "reboot", "poweroff"]
const hibernateSupported = (typeof SessionService !== "undefined" && SessionService.hibernateSupported) || false
let filtered = allActions.filter(action => { let filtered = allActions.filter(action => {
if (action === "hibernate" && !hibernateSupported) return false if (action === "hibernate" && !hibernateSupported)
if (action === "lock") return false return false;
if (action === "restart") return false if (action === "lock")
if (action === "logout" && !showLogout) return false return false;
return true if (action === "restart")
}) return false;
if (action === "logout" && !showLogout)
return false;
return true;
});
visibleActions = filtered visibleActions = filtered;
useGridLayout = (typeof SettingsData !== "undefined" && SettingsData.powerMenuGridLayout !== undefined) useGridLayout = (typeof SettingsData !== "undefined" && SettingsData.powerMenuGridLayout !== undefined) ? SettingsData.powerMenuGridLayout : false;
? SettingsData.powerMenuGridLayout if (!useGridLayout)
: false return;
if (!useGridLayout) return const count = visibleActions.length;
const count = visibleActions.length
if (count === 0) { if (count === 0) {
gridColumns = 1 gridColumns = 1;
gridRows = 1 gridRows = 1;
return return;
} }
if (count <= 3) { if (count <= 3) {
gridColumns = 1 gridColumns = 1;
gridRows = count gridRows = count;
return return;
} }
if (count === 4) { if (count === 4) {
gridColumns = 2 gridColumns = 2;
gridRows = 2 gridRows = 2;
return return;
} }
gridColumns = 3 gridColumns = 3;
gridRows = Math.ceil(count / 3) gridRows = Math.ceil(count / 3);
} }
function getDefaultActionIndex() { function getDefaultActionIndex() {
const defaultAction = (typeof SettingsData !== "undefined" && SettingsData.powerMenuDefaultAction) const defaultAction = (typeof SettingsData !== "undefined" && SettingsData.powerMenuDefaultAction) ? SettingsData.powerMenuDefaultAction : "suspend";
? SettingsData.powerMenuDefaultAction const index = visibleActions.indexOf(defaultAction);
: "suspend" return index >= 0 ? index : 0;
const index = visibleActions.indexOf(defaultAction)
return index >= 0 ? index : 0
} }
function getActionAtIndex(index) { function getActionAtIndex(index) {
if (index < 0 || index >= visibleActions.length) return "" if (index < 0 || index >= visibleActions.length)
return visibleActions[index] return "";
return visibleActions[index];
} }
function getActionData(action) { function getActionData(action) {
switch (action) { switch (action) {
case "reboot": case "reboot":
return { "icon": "restart_alt", "label": I18n.tr("Reboot"), "key": "R" } return {
"icon": "restart_alt",
"label": I18n.tr("Reboot"),
"key": "R"
};
case "logout": case "logout":
return { "icon": "logout", "label": I18n.tr("Log Out"), "key": "X" } return {
"icon": "logout",
"label": I18n.tr("Log Out"),
"key": "X"
};
case "poweroff": case "poweroff":
return { "icon": "power_settings_new", "label": I18n.tr("Power Off"), "key": "P" } return {
"icon": "power_settings_new",
"label": I18n.tr("Power Off"),
"key": "P"
};
case "suspend": case "suspend":
return { "icon": "bedtime", "label": I18n.tr("Suspend"), "key": "S" } return {
"icon": "bedtime",
"label": I18n.tr("Suspend"),
"key": "S"
};
case "hibernate": case "hibernate":
return { "icon": "ac_unit", "label": I18n.tr("Hibernate"), "key": "H" } return {
"icon": "ac_unit",
"label": I18n.tr("Hibernate"),
"key": "H"
};
default: default:
return { "icon": "help", "label": action, "key": "?" } return {
"icon": "help",
"label": action,
"key": "?"
};
} }
} }
function selectOption(action) { function actionNeedsConfirm(action) {
if (!action) return return action !== "lock" && action !== "restart";
if (typeof SessionService === "undefined") return }
hide()
function startHold(action, actionIndex) {
if (!needsConfirmation || !actionNeedsConfirm(action)) {
executeAction(action);
return;
}
holdAction = action;
holdActionIndex = actionIndex;
holdProgress = 0;
showHoldHint = false;
holdTimer.start();
}
function cancelHold() {
if (holdAction === "")
return;
const wasHolding = holdProgress > 0;
holdTimer.stop();
if (wasHolding && holdProgress < 1) {
showHoldHint = true;
hintTimer.restart();
}
holdAction = "";
holdActionIndex = -1;
holdProgress = 0;
}
function completeHold() {
if (holdProgress < 1) {
cancelHold();
return;
}
const action = holdAction;
holdTimer.stop();
holdAction = "";
holdActionIndex = -1;
holdProgress = 0;
executeAction(action);
}
function executeAction(action) {
if (!action)
return;
if (typeof SessionService === "undefined")
return;
hide();
switch (action) { switch (action) {
case "logout": case "logout":
SessionService.logout() SessionService.logout();
break break;
case "suspend": case "suspend":
SessionService.suspend() SessionService.suspend();
break break;
case "hibernate": case "hibernate":
SessionService.hibernate() SessionService.hibernate();
break break;
case "reboot": case "reboot":
SessionService.reboot() SessionService.reboot();
break break;
case "poweroff": case "poweroff":
SessionService.poweroff() SessionService.poweroff();
break break;
} }
} }
function selectOption(action, actionIndex) {
startHold(action, actionIndex !== undefined ? actionIndex : -1);
}
function show() { function show() {
updateVisibleActions() holdAction = "";
const defaultIndex = getDefaultActionIndex() holdActionIndex = -1;
holdProgress = 0;
showHoldHint = false;
updateVisibleActions();
const defaultIndex = getDefaultActionIndex();
if (useGridLayout) { if (useGridLayout) {
selectedRow = Math.floor(defaultIndex / gridColumns) selectedRow = Math.floor(defaultIndex / gridColumns);
selectedCol = defaultIndex % gridColumns selectedCol = defaultIndex % gridColumns;
selectedIndex = defaultIndex selectedIndex = defaultIndex;
} else { } else {
selectedIndex = defaultIndex selectedIndex = defaultIndex;
} }
isVisible = true isVisible = true;
Qt.callLater(() => powerMenuFocusScope.forceActiveFocus()) Qt.callLater(() => powerMenuFocusScope.forceActiveFocus());
} }
function hide() { function hide() {
isVisible = false cancelHold();
closed() isVisible = false;
closed();
} }
function handleListNavigation(event) { function handleListNavigation(event, isPressed) {
if (!isPressed) {
if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter || event.key === Qt.Key_R || event.key === Qt.Key_X || event.key === Qt.Key_S || event.key === Qt.Key_H || (event.key === Qt.Key_P && !(event.modifiers & Qt.ControlModifier))) {
cancelHold();
event.accepted = true;
}
return;
}
switch (event.key) { switch (event.key) {
case Qt.Key_Up: case Qt.Key_Up:
case Qt.Key_Backtab: case Qt.Key_Backtab:
selectedIndex = (selectedIndex - 1 + visibleActions.length) % visibleActions.length selectedIndex = (selectedIndex - 1 + visibleActions.length) % visibleActions.length;
event.accepted = true event.accepted = true;
break break;
case Qt.Key_Down: case Qt.Key_Down:
case Qt.Key_Tab: case Qt.Key_Tab:
selectedIndex = (selectedIndex + 1) % visibleActions.length selectedIndex = (selectedIndex + 1) % visibleActions.length;
event.accepted = true event.accepted = true;
break break;
case Qt.Key_Return: case Qt.Key_Return:
case Qt.Key_Enter: case Qt.Key_Enter:
selectOption(getActionAtIndex(selectedIndex)) startHold(getActionAtIndex(selectedIndex), selectedIndex);
event.accepted = true event.accepted = true;
break break;
case Qt.Key_N: case Qt.Key_N:
if (event.modifiers & Qt.ControlModifier) { if (event.modifiers & Qt.ControlModifier) {
selectedIndex = (selectedIndex + 1) % visibleActions.length selectedIndex = (selectedIndex + 1) % visibleActions.length;
event.accepted = true event.accepted = true;
} }
break break;
case Qt.Key_P: case Qt.Key_P:
if (!(event.modifiers & Qt.ControlModifier)) { if (!(event.modifiers & Qt.ControlModifier)) {
selectOption("poweroff") const idx = visibleActions.indexOf("poweroff");
event.accepted = true startHold("poweroff", idx);
event.accepted = true;
} else { } else {
selectedIndex = (selectedIndex - 1 + visibleActions.length) % visibleActions.length selectedIndex = (selectedIndex - 1 + visibleActions.length) % visibleActions.length;
event.accepted = true event.accepted = true;
} }
break break;
case Qt.Key_J: case Qt.Key_J:
if (event.modifiers & Qt.ControlModifier) { if (event.modifiers & Qt.ControlModifier) {
selectedIndex = (selectedIndex + 1) % visibleActions.length selectedIndex = (selectedIndex + 1) % visibleActions.length;
event.accepted = true event.accepted = true;
} }
break break;
case Qt.Key_K: case Qt.Key_K:
if (event.modifiers & Qt.ControlModifier) { if (event.modifiers & Qt.ControlModifier) {
selectedIndex = (selectedIndex - 1 + visibleActions.length) % visibleActions.length selectedIndex = (selectedIndex - 1 + visibleActions.length) % visibleActions.length;
event.accepted = true event.accepted = true;
} }
break break;
case Qt.Key_R: case Qt.Key_R:
selectOption("reboot") startHold("reboot", visibleActions.indexOf("reboot"));
event.accepted = true event.accepted = true;
break break;
case Qt.Key_X: case Qt.Key_X:
selectOption("logout") startHold("logout", visibleActions.indexOf("logout"));
event.accepted = true event.accepted = true;
break break;
case Qt.Key_S: case Qt.Key_S:
selectOption("suspend") startHold("suspend", visibleActions.indexOf("suspend"));
event.accepted = true event.accepted = true;
break break;
case Qt.Key_H: case Qt.Key_H:
selectOption("hibernate") startHold("hibernate", visibleActions.indexOf("hibernate"));
event.accepted = true event.accepted = true;
break break;
} }
} }
function handleGridNavigation(event) { function handleGridNavigation(event, isPressed) {
if (!isPressed) {
if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter || event.key === Qt.Key_R || event.key === Qt.Key_X || event.key === Qt.Key_S || event.key === Qt.Key_H || (event.key === Qt.Key_P && !(event.modifiers & Qt.ControlModifier))) {
cancelHold();
event.accepted = true;
}
return;
}
switch (event.key) { switch (event.key) {
case Qt.Key_Left: case Qt.Key_Left:
selectedCol = (selectedCol - 1 + gridColumns) % gridColumns selectedCol = (selectedCol - 1 + gridColumns) % gridColumns;
selectedIndex = selectedRow * gridColumns + selectedCol selectedIndex = selectedRow * gridColumns + selectedCol;
event.accepted = true event.accepted = true;
break break;
case Qt.Key_Right: case Qt.Key_Right:
selectedCol = (selectedCol + 1) % gridColumns selectedCol = (selectedCol + 1) % gridColumns;
selectedIndex = selectedRow * gridColumns + selectedCol selectedIndex = selectedRow * gridColumns + selectedCol;
event.accepted = true event.accepted = true;
break break;
case Qt.Key_Up: case Qt.Key_Up:
case Qt.Key_Backtab: case Qt.Key_Backtab:
selectedRow = (selectedRow - 1 + gridRows) % gridRows selectedRow = (selectedRow - 1 + gridRows) % gridRows;
selectedIndex = selectedRow * gridColumns + selectedCol selectedIndex = selectedRow * gridColumns + selectedCol;
event.accepted = true event.accepted = true;
break break;
case Qt.Key_Down: case Qt.Key_Down:
case Qt.Key_Tab: case Qt.Key_Tab:
selectedRow = (selectedRow + 1) % gridRows selectedRow = (selectedRow + 1) % gridRows;
selectedIndex = selectedRow * gridColumns + selectedCol selectedIndex = selectedRow * gridColumns + selectedCol;
event.accepted = true event.accepted = true;
break break;
case Qt.Key_Return: case Qt.Key_Return:
case Qt.Key_Enter: case Qt.Key_Enter:
selectOption(getActionAtIndex(selectedIndex)) startHold(getActionAtIndex(selectedIndex), selectedIndex);
event.accepted = true event.accepted = true;
break break;
case Qt.Key_N: case Qt.Key_N:
if (event.modifiers & Qt.ControlModifier) { if (event.modifiers & Qt.ControlModifier) {
selectedCol = (selectedCol + 1) % gridColumns selectedCol = (selectedCol + 1) % gridColumns;
selectedIndex = selectedRow * gridColumns + selectedCol selectedIndex = selectedRow * gridColumns + selectedCol;
event.accepted = true event.accepted = true;
} }
break break;
case Qt.Key_P: case Qt.Key_P:
if (!(event.modifiers & Qt.ControlModifier)) { if (!(event.modifiers & Qt.ControlModifier)) {
selectOption("poweroff") const idx = visibleActions.indexOf("poweroff");
event.accepted = true startHold("poweroff", idx);
event.accepted = true;
} else { } else {
selectedCol = (selectedCol - 1 + gridColumns) % gridColumns selectedCol = (selectedCol - 1 + gridColumns) % gridColumns;
selectedIndex = selectedRow * gridColumns + selectedCol selectedIndex = selectedRow * gridColumns + selectedCol;
event.accepted = true event.accepted = true;
} }
break break;
case Qt.Key_J: case Qt.Key_J:
if (event.modifiers & Qt.ControlModifier) { if (event.modifiers & Qt.ControlModifier) {
selectedRow = (selectedRow + 1) % gridRows selectedRow = (selectedRow + 1) % gridRows;
selectedIndex = selectedRow * gridColumns + selectedCol selectedIndex = selectedRow * gridColumns + selectedCol;
event.accepted = true event.accepted = true;
} }
break break;
case Qt.Key_K: case Qt.Key_K:
if (event.modifiers & Qt.ControlModifier) { if (event.modifiers & Qt.ControlModifier) {
selectedRow = (selectedRow - 1 + gridRows) % gridRows selectedRow = (selectedRow - 1 + gridRows) % gridRows;
selectedIndex = selectedRow * gridColumns + selectedCol selectedIndex = selectedRow * gridColumns + selectedCol;
event.accepted = true event.accepted = true;
} }
break break;
case Qt.Key_R: case Qt.Key_R:
selectOption("reboot") startHold("reboot", visibleActions.indexOf("reboot"));
event.accepted = true event.accepted = true;
break break;
case Qt.Key_X: case Qt.Key_X:
selectOption("logout") startHold("logout", visibleActions.indexOf("logout"));
event.accepted = true event.accepted = true;
break break;
case Qt.Key_S: case Qt.Key_S:
selectOption("suspend") startHold("suspend", visibleActions.indexOf("suspend"));
event.accepted = true event.accepted = true;
break break;
case Qt.Key_H: case Qt.Key_H:
selectOption("hibernate") startHold("hibernate", visibleActions.indexOf("hibernate"));
event.accepted = true event.accepted = true;
break break;
} }
} }
@@ -287,29 +391,62 @@ Rectangle {
onClicked: root.hide() onClicked: root.hide()
} }
Timer {
id: holdTimer
interval: 16
repeat: true
onTriggered: {
root.holdProgress = Math.min(1, root.holdProgress + (interval / root.holdDurationMs));
if (root.holdProgress >= 1) {
stop();
root.completeHold();
}
}
}
Timer {
id: hintTimer
interval: 2000
onTriggered: root.showHoldHint = false
}
FocusScope { FocusScope {
id: powerMenuFocusScope id: powerMenuFocusScope
anchors.fill: parent anchors.fill: parent
focus: root.isVisible focus: root.isVisible
onVisibleChanged: { onVisibleChanged: {
if (visible) Qt.callLater(() => forceActiveFocus()) if (visible)
Qt.callLater(() => forceActiveFocus());
} }
Keys.onEscapePressed: root.hide() Keys.onEscapePressed: root.hide()
Keys.onPressed: event => { Keys.onPressed: event => {
if (event.isAutoRepeat) {
event.accepted = true;
return;
}
if (useGridLayout) { if (useGridLayout) {
handleGridNavigation(event) handleGridNavigation(event, true);
} else { } else {
handleListNavigation(event) handleListNavigation(event, true);
}
}
Keys.onReleased: event => {
if (event.isAutoRepeat) {
event.accepted = true;
return;
}
if (useGridLayout) {
handleGridNavigation(event, false);
} else {
handleListNavigation(event, false);
} }
} }
Rectangle { Rectangle {
anchors.centerIn: parent anchors.centerIn: parent
width: useGridLayout width: useGridLayout ? Math.min(550, gridColumns * 180 + Theme.spacingS * (gridColumns - 1) + Theme.spacingL * 2) : 320
? Math.min(550, gridColumns * 180 + Theme.spacingS * (gridColumns - 1) + Theme.spacingL * 2)
: 320
height: contentItem.implicitHeight + Theme.spacingL * 2 height: contentItem.implicitHeight + Theme.spacingL * 2
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: Theme.surfaceContainer color: Theme.surfaceContainer
@@ -320,7 +457,7 @@ Rectangle {
id: contentItem id: contentItem
anchors.fill: parent anchors.fill: parent
anchors.margins: Theme.spacingL anchors.margins: Theme.spacingL
implicitHeight: headerRow.height + Theme.spacingM + (useGridLayout ? buttonGrid.implicitHeight : buttonColumn.implicitHeight) implicitHeight: headerRow.height + Theme.spacingM + (useGridLayout ? buttonGrid.implicitHeight : buttonColumn.implicitHeight) + (root.needsConfirmation ? hintRow.height + Theme.spacingM : 0)
Row { Row {
id: headerRow id: headerRow
@@ -363,48 +500,86 @@ Rectangle {
model: root.visibleActions model: root.visibleActions
Rectangle { Rectangle {
id: gridButtonRect
required property int index required property int index
required property string modelData required property string modelData
readonly property var actionData: root.getActionData(modelData) readonly property var actionData: root.getActionData(modelData)
readonly property bool isSelected: root.selectedIndex === index readonly property bool isSelected: root.selectedIndex === index
readonly property bool showWarning: modelData === "reboot" || modelData === "poweroff" readonly property bool showWarning: modelData === "reboot" || modelData === "poweroff"
readonly property bool isHolding: root.holdActionIndex === index && root.holdProgress > 0
width: (contentItem.width - Theme.spacingS * (root.gridColumns - 1)) / root.gridColumns width: (contentItem.width - Theme.spacingS * (root.gridColumns - 1)) / root.gridColumns
height: 100 height: 100
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: { color: {
if (isSelected) return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) if (isSelected)
if (mouseArea.containsMouse) return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12);
return Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.08) if (mouseArea.containsMouse)
return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08);
return Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.08);
} }
border.color: isSelected ? Theme.primary : "transparent" border.color: isSelected ? Theme.primary : "transparent"
border.width: isSelected ? 2 : 0 border.width: isSelected ? 2 : 0
Rectangle {
id: gridProgressMask
anchors.fill: parent
radius: parent.radius
visible: false
layer.enabled: true
}
Item {
anchors.fill: parent
visible: gridButtonRect.isHolding
layer.enabled: gridButtonRect.isHolding
layer.effect: MultiEffect {
maskEnabled: true
maskSource: gridProgressMask
maskSpreadAtMin: 1
maskThresholdMin: 0.5
}
Rectangle {
anchors.left: parent.left
anchors.top: parent.top
anchors.bottom: parent.bottom
width: parent.width * root.holdProgress
color: {
if (gridButtonRect.modelData === "poweroff")
return Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.3);
if (gridButtonRect.modelData === "reboot")
return Qt.rgba(Theme.warning.r, Theme.warning.g, Theme.warning.b, 0.3);
return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.3);
}
}
}
Column { Column {
anchors.centerIn: parent anchors.centerIn: parent
spacing: Theme.spacingS spacing: Theme.spacingS
DankIcon { DankIcon {
name: parent.parent.actionData.icon name: gridButtonRect.actionData.icon
size: Theme.iconSize + 8 size: Theme.iconSize + 8
color: { color: {
if (parent.parent.showWarning && mouseArea.containsMouse) { if (gridButtonRect.showWarning && (mouseArea.containsMouse || gridButtonRect.isHolding)) {
return parent.parent.modelData === "poweroff" ? Theme.error : Theme.warning return gridButtonRect.modelData === "poweroff" ? Theme.error : Theme.warning;
} }
return Theme.surfaceText return Theme.surfaceText;
} }
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
} }
StyledText { StyledText {
text: parent.parent.actionData.label text: gridButtonRect.actionData.label
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
color: { color: {
if (parent.parent.showWarning && mouseArea.containsMouse) { if (gridButtonRect.showWarning && (mouseArea.containsMouse || gridButtonRect.isHolding)) {
return parent.parent.modelData === "poweroff" ? Theme.error : Theme.warning return gridButtonRect.modelData === "poweroff" ? Theme.error : Theme.warning;
} }
return Theme.surfaceText return Theme.surfaceText;
} }
font.weight: Font.Medium font.weight: Font.Medium
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
@@ -418,7 +593,7 @@ Rectangle {
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
StyledText { StyledText {
text: parent.parent.parent.actionData.key text: gridButtonRect.actionData.key
font.pixelSize: Theme.fontSizeSmall - 1 font.pixelSize: Theme.fontSizeSmall - 1
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.6) color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.6)
font.weight: Font.Medium font.weight: Font.Medium
@@ -432,11 +607,14 @@ Rectangle {
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onClicked: { onPressed: {
root.selectedRow = Math.floor(index / root.gridColumns) root.selectedRow = Math.floor(index / root.gridColumns);
root.selectedCol = index % root.gridColumns root.selectedCol = index % root.gridColumns;
root.selectOption(modelData) root.selectedIndex = index;
root.startHold(modelData, index);
} }
onReleased: root.cancelHold()
onCanceled: root.cancelHold()
} }
} }
} }
@@ -455,24 +633,62 @@ Rectangle {
model: root.visibleActions model: root.visibleActions
Rectangle { Rectangle {
id: listButtonRect
required property int index required property int index
required property string modelData required property string modelData
readonly property var actionData: root.getActionData(modelData) readonly property var actionData: root.getActionData(modelData)
readonly property bool isSelected: root.selectedIndex === index readonly property bool isSelected: root.selectedIndex === index
readonly property bool showWarning: modelData === "reboot" || modelData === "poweroff" readonly property bool showWarning: modelData === "reboot" || modelData === "poweroff"
readonly property bool isHolding: root.holdActionIndex === index && root.holdProgress > 0
width: parent.width width: parent.width
height: 50 height: 50
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: { color: {
if (isSelected) return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) if (isSelected)
if (listMouseArea.containsMouse) return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12);
return Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.08) if (listMouseArea.containsMouse)
return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08);
return Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.08);
} }
border.color: isSelected ? Theme.primary : "transparent" border.color: isSelected ? Theme.primary : "transparent"
border.width: isSelected ? 2 : 0 border.width: isSelected ? 2 : 0
Rectangle {
id: listProgressMask
anchors.fill: parent
radius: parent.radius
visible: false
layer.enabled: true
}
Item {
anchors.fill: parent
visible: listButtonRect.isHolding
layer.enabled: listButtonRect.isHolding
layer.effect: MultiEffect {
maskEnabled: true
maskSource: listProgressMask
maskSpreadAtMin: 1
maskThresholdMin: 0.5
}
Rectangle {
anchors.left: parent.left
anchors.top: parent.top
anchors.bottom: parent.bottom
width: parent.width * root.holdProgress
color: {
if (listButtonRect.modelData === "poweroff")
return Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.3);
if (listButtonRect.modelData === "reboot")
return Qt.rgba(Theme.warning.r, Theme.warning.g, Theme.warning.b, 0.3);
return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.3);
}
}
}
Row { Row {
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right
@@ -482,25 +698,25 @@ Rectangle {
spacing: Theme.spacingM spacing: Theme.spacingM
DankIcon { DankIcon {
name: parent.parent.actionData.icon name: listButtonRect.actionData.icon
size: Theme.iconSize + 4 size: Theme.iconSize + 4
color: { color: {
if (parent.parent.showWarning && listMouseArea.containsMouse) { if (listButtonRect.showWarning && (listMouseArea.containsMouse || listButtonRect.isHolding)) {
return parent.parent.modelData === "poweroff" ? Theme.error : Theme.warning return listButtonRect.modelData === "poweroff" ? Theme.error : Theme.warning;
} }
return Theme.surfaceText return Theme.surfaceText;
} }
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
} }
StyledText { StyledText {
text: parent.parent.actionData.label text: listButtonRect.actionData.label
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
color: { color: {
if (parent.parent.showWarning && listMouseArea.containsMouse) { if (listButtonRect.showWarning && (listMouseArea.containsMouse || listButtonRect.isHolding)) {
return parent.parent.modelData === "poweroff" ? Theme.error : Theme.warning return listButtonRect.modelData === "poweroff" ? Theme.error : Theme.warning;
} }
return Theme.surfaceText return Theme.surfaceText;
} }
font.weight: Font.Medium font.weight: Font.Medium
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
@@ -517,7 +733,7 @@ Rectangle {
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
StyledText { StyledText {
text: parent.parent.actionData.key text: listButtonRect.actionData.key
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.6) color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.6)
font.weight: Font.Medium font.weight: Font.Medium
@@ -530,14 +746,53 @@ Rectangle {
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onClicked: { onPressed: {
root.selectedIndex = index root.selectedIndex = index;
root.selectOption(modelData) root.startHold(modelData, index);
} }
onReleased: root.cancelHold()
onCanceled: root.cancelHold()
} }
} }
} }
} }
Row {
id: hintRow
anchors.horizontalCenter: parent.horizontalCenter
anchors.bottom: parent.bottom
anchors.bottomMargin: Theme.spacingS
spacing: Theme.spacingXS
visible: root.needsConfirmation
opacity: root.showHoldHint ? 1 : 0.5
Behavior on opacity {
NumberAnimation {
duration: 150
}
}
DankIcon {
name: root.showHoldHint ? "warning" : "touch_app"
size: Theme.fontSizeSmall
color: root.showHoldHint ? Theme.warning : Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.6)
anchors.verticalCenter: parent.verticalCenter
}
StyledText {
readonly property int remainingSeconds: Math.ceil(SettingsData.powerActionHoldDuration * (1 - root.holdProgress))
text: {
if (root.showHoldHint)
return I18n.tr("Hold longer to confirm");
if (root.holdProgress > 0)
return I18n.tr("Hold to confirm (%1s)").arg(remainingSeconds);
return I18n.tr("Hold to confirm (%1s)").arg(SettingsData.powerActionHoldDuration);
}
font.pixelSize: Theme.fontSizeSmall
color: root.showHoldHint ? Theme.warning : Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.6)
anchors.verticalCenter: parent.verticalCenter
}
}
} }
} }
} }

View File

@@ -44,6 +44,9 @@ Item {
readonly property bool hasVerticalPill: verticalBarPill !== null readonly property bool hasVerticalPill: verticalBarPill !== null
readonly property bool hasPopout: popoutContent !== null readonly property bool hasPopout: popoutContent !== null
readonly property int iconSize: Theme.barIconSize(barThickness, -4)
readonly property int iconSizeLarge: Theme.barIconSize(barThickness)
Component.onCompleted: { Component.onCompleted: {
loadPluginData(); loadPluginData();
} }

View File

@@ -45,10 +45,14 @@ Item {
Item { Item {
width: parent.width width: parent.width
height: logoModeGroup.implicitHeight height: logoModeGroup.implicitHeight
clip: true
DankButtonGroup { DankButtonGroup {
id: logoModeGroup id: logoModeGroup
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
buttonPadding: parent.width < 480 ? Theme.spacingS : Theme.spacingL
minButtonWidth: parent.width < 480 ? 44 : 64
textSize: parent.width < 480 ? Theme.fontSizeSmall : Theme.fontSizeMedium
model: { model: {
const modes = [I18n.tr("Apps Icon"), I18n.tr("OS Logo"), I18n.tr("Dank")]; const modes = [I18n.tr("Apps Icon"), I18n.tr("OS Logo"), I18n.tr("Dank")];
if (CompositorService.isNiri) { if (CompositorService.isNiri) {
@@ -153,78 +157,88 @@ Item {
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
} }
Row { Item {
anchors.horizontalCenter: parent.horizontalCenter width: parent.width
spacing: Theme.spacingM height: colorOverrideRow.implicitHeight
clip: true
DankButtonGroup { Row {
id: colorModeGroup id: colorOverrideRow
model: [I18n.tr("Default"), I18n.tr("Primary"), I18n.tr("Surface"), I18n.tr("Custom")] anchors.horizontalCenter: parent.horizontalCenter
currentIndex: { spacing: Theme.spacingM
const override = SettingsData.launcherLogoColorOverride;
if (override === "") DankButtonGroup {
return 0; id: colorModeGroup
if (override === "primary") buttonPadding: parent.parent.width < 480 ? Theme.spacingS : Theme.spacingL
return 1; minButtonWidth: parent.parent.width < 480 ? 44 : 64
if (override === "surface") textSize: parent.parent.width < 480 ? Theme.fontSizeSmall : Theme.fontSizeMedium
return 2; model: [I18n.tr("Default"), I18n.tr("Primary"), I18n.tr("Surface"), I18n.tr("Custom")]
return 3; currentIndex: {
} const override = SettingsData.launcherLogoColorOverride;
onSelectionChanged: (index, selected) => { if (override === "")
if (!selected) return 0;
return; if (override === "primary")
switch (index) { return 1;
case 0: if (override === "surface")
SettingsData.set("launcherLogoColorOverride", ""); return 2;
break; return 3;
case 1:
SettingsData.set("launcherLogoColorOverride", "primary");
break;
case 2:
SettingsData.set("launcherLogoColorOverride", "surface");
break;
case 3:
const currentOverride = SettingsData.launcherLogoColorOverride;
const isPreset = currentOverride === "" || currentOverride === "primary" || currentOverride === "surface";
if (isPreset) {
SettingsData.set("launcherLogoColorOverride", "#ffffff");
}
break;
} }
} onSelectionChanged: (index, selected) => {
} if (!selected)
Rectangle {
visible: {
const override = SettingsData.launcherLogoColorOverride;
return override !== "" && override !== "primary" && override !== "surface";
}
width: 36
height: 36
radius: 18
color: {
const override = SettingsData.launcherLogoColorOverride;
if (override !== "" && override !== "primary" && override !== "surface") {
return override;
}
return "#ffffff";
}
border.color: Theme.outline
border.width: 1
anchors.verticalCenter: parent.verticalCenter
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: {
if (!PopoutService.colorPickerModal)
return; return;
PopoutService.colorPickerModal.selectedColor = SettingsData.launcherLogoColorOverride; switch (index) {
PopoutService.colorPickerModal.pickerTitle = I18n.tr("Choose Launcher Logo Color"); case 0:
PopoutService.colorPickerModal.onColorSelectedCallback = function (selectedColor) { SettingsData.set("launcherLogoColorOverride", "");
SettingsData.set("launcherLogoColorOverride", selectedColor); break;
}; case 1:
PopoutService.colorPickerModal.show(); SettingsData.set("launcherLogoColorOverride", "primary");
break;
case 2:
SettingsData.set("launcherLogoColorOverride", "surface");
break;
case 3:
const currentOverride = SettingsData.launcherLogoColorOverride;
const isPreset = currentOverride === "" || currentOverride === "primary" || currentOverride === "surface";
if (isPreset) {
SettingsData.set("launcherLogoColorOverride", "#ffffff");
}
break;
}
}
}
Rectangle {
id: colorPickerCircle
visible: {
const override = SettingsData.launcherLogoColorOverride;
return override !== "" && override !== "primary" && override !== "surface";
}
width: 36
height: 36
radius: 18
color: {
const override = SettingsData.launcherLogoColorOverride;
if (override !== "" && override !== "primary" && override !== "surface")
return override;
return "#ffffff";
}
border.color: Theme.outline
border.width: 1
anchors.verticalCenter: parent.verticalCenter
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: {
if (!PopoutService.colorPickerModal)
return;
PopoutService.colorPickerModal.selectedColor = SettingsData.launcherLogoColorOverride;
PopoutService.colorPickerModal.pickerTitle = I18n.tr("Choose Launcher Logo Color");
PopoutService.colorPickerModal.onColorSelectedCallback = function (selectedColor) {
SettingsData.set("launcherLogoColorOverride", selectedColor);
};
PopoutService.colorPickerModal.show();
}
} }
} }
} }
@@ -329,28 +343,32 @@ Item {
SettingsToggleRow { SettingsToggleRow {
text: I18n.tr("Close Overview on Launch") text: I18n.tr("Close Overview on Launch")
description: I18n.tr("When enabled, launching an app from the launcher will automatically close the Niri overview if it's open.") description: I18n.tr("Auto-close Niri overview when launching apps.")
checked: SettingsData.spotlightCloseNiriOverview checked: SettingsData.spotlightCloseNiriOverview
onToggled: checked => SettingsData.set("spotlightCloseNiriOverview", checked) onToggled: checked => SettingsData.set("spotlightCloseNiriOverview", checked)
} }
SettingsToggleRow { SettingsToggleRow {
text: I18n.tr("Enable Overview Overlay") text: I18n.tr("Enable Overview Overlay")
description: I18n.tr("When enabled, shows the launcher overlay when typing in Niri overview mode. Disable this if you prefer to not have the launcher when typing on Niri overview or want to use other launcher in the overview.") description: I18n.tr("Show launcher overlay when typing in Niri overview. Disable to use another launcher.")
checked: SettingsData.niriOverviewOverlayEnabled checked: SettingsData.niriOverviewOverlayEnabled
onToggled: checked => SettingsData.set("niriOverviewOverlayEnabled", checked) onToggled: checked => SettingsData.set("niriOverviewOverlayEnabled", checked)
} }
} }
SettingsCard { SettingsCard {
id: recentAppsCard
width: parent.width width: parent.width
iconName: "history" iconName: "history"
title: I18n.tr("Recently Used Apps") title: I18n.tr("Recently Used Apps")
property var rankedAppsModel: { property var rankedAppsModel: {
var ranking = AppUsageHistoryData.appUsageRanking;
if (!ranking)
return [];
var apps = []; var apps = [];
for (var appId in (AppUsageHistoryData.appUsageRanking || {})) { for (var appId in ranking) {
var appData = (AppUsageHistoryData.appUsageRanking || {})[appId]; var appData = ranking[appId];
apps.push({ apps.push({
"id": appId, "id": appId,
"name": appData.name, "name": appData.name,
@@ -401,7 +419,7 @@ Item {
spacing: Theme.spacingS spacing: Theme.spacingS
Repeater { Repeater {
model: parent.parent.rankedAppsModel model: recentAppsCard.rankedAppsModel
delegate: Rectangle { delegate: Rectangle {
width: rankedAppsList.width width: rankedAppsList.width
@@ -496,11 +514,11 @@ Item {
StyledText { StyledText {
width: parent.width width: parent.width
text: parent.parent.rankedAppsModel.length === 0 ? "No apps have been launched yet." : "" text: "No apps have been launched yet."
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceVariantText color: Theme.surfaceVariantText
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
visible: parent.parent.rankedAppsModel.length === 0 visible: recentAppsCard.rankedAppsModel.length === 0
} }
} }
} }

View File

@@ -130,7 +130,7 @@ Item {
{ {
"id": "gpuTemp", "id": "gpuTemp",
"text": I18n.tr("GPU Temperature"), "text": I18n.tr("GPU Temperature"),
"description": I18n.tr("GPU temperature display"), "description": "",
"icon": "auto_awesome_mosaic", "icon": "auto_awesome_mosaic",
"warning": !DgopService.dgopAvailable ? I18n.tr("Requires 'dgop' tool") : I18n.tr("This widget prevents GPU power off states, which can significantly impact battery life on laptops. It is not recommended to use this on laptops with hybrid graphics."), "warning": !DgopService.dgopAvailable ? I18n.tr("Requires 'dgop' tool") : I18n.tr("This widget prevents GPU power off states, which can significantly impact battery life on laptops. It is not recommended to use this on laptops with hybrid graphics."),
"enabled": DgopService.dgopAvailable "enabled": DgopService.dgopAvailable

View File

@@ -168,7 +168,17 @@ Column {
} }
StyledText { StyledText {
text: modelData.description text: {
if (modelData.id === "gpuTemp") {
var selectedIdx = modelData.selectedGpuIndex !== undefined ? modelData.selectedGpuIndex : 0;
if (DgopService.availableGpus && DgopService.availableGpus.length > selectedIdx) {
var gpu = DgopService.availableGpus[selectedIdx];
return gpu.driver ? gpu.driver.toUpperCase() : "";
}
return I18n.tr("No GPU detected");
}
return modelData.description;
}
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
color: modelData.enabled ? Theme.outline : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.6) color: modelData.enabled ? Theme.outline : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.6)
elide: Text.ElideRight elide: Text.ElideRight
@@ -185,39 +195,37 @@ Column {
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
spacing: Theme.spacingXS spacing: Theme.spacingXS
Item { DankActionButton {
width: 60 id: gpuMenuButton
height: 32
visible: modelData.id === "gpuTemp" visible: modelData.id === "gpuTemp"
buttonSize: 32
iconName: "more_vert"
iconSize: 18
iconColor: Theme.outline
onClicked: {
gpuContextMenu.widgetData = modelData;
gpuContextMenu.sectionId = root.sectionId;
gpuContextMenu.widgetIndex = index;
DankDropdown { var buttonPos = gpuMenuButton.mapToItem(root, 0, 0);
id: gpuDropdown var popupWidth = gpuContextMenu.width;
anchors.fill: parent var popupHeight = gpuContextMenu.height;
popupWidth: -1
currentValue: { var xPos = buttonPos.x - popupWidth - Theme.spacingS;
var selectedIndex = modelData.selectedGpuIndex !== undefined ? modelData.selectedGpuIndex : 0; if (xPos < 0) {
if (DgopService.availableGpus && DgopService.availableGpus.length > selectedIndex && selectedIndex >= 0) { xPos = buttonPos.x + gpuMenuButton.width + Theme.spacingS;
var gpu = DgopService.availableGpus[selectedIndex];
return gpu.driver.toUpperCase();
}
return DgopService.availableGpus && DgopService.availableGpus.length > 0 ? DgopService.availableGpus[0].driver.toUpperCase() : "";
} }
options: {
var gpuOptions = []; var yPos = buttonPos.y - popupHeight / 2 + gpuMenuButton.height / 2;
if (DgopService.availableGpus && DgopService.availableGpus.length > 0) { if (yPos < 0) {
for (var i = 0; i < DgopService.availableGpus.length; i++) { yPos = Theme.spacingS;
var gpu = DgopService.availableGpus[i]; } else if (yPos + popupHeight > root.height) {
gpuOptions.push(gpu.driver.toUpperCase()); yPos = root.height - popupHeight - Theme.spacingS;
}
}
return gpuOptions;
}
onValueChanged: value => {
var gpuIndex = options.indexOf(value);
if (gpuIndex >= 0) {
root.gpuSelectionChanged(root.sectionId, index, gpuIndex);
}
} }
gpuContextMenu.x = xPos;
gpuContextMenu.y = yPos;
gpuContextMenu.open();
} }
} }
@@ -1123,4 +1131,112 @@ Column {
} }
} }
} }
Popup {
id: gpuContextMenu
property var widgetData: null
property string sectionId: ""
property int widgetIndex: -1
width: 250
height: gpuMenuColumn.implicitHeight + Theme.spacingS * 2
padding: 0
modal: true
focus: true
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside
background: Rectangle {
color: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
radius: Theme.cornerRadius
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
border.width: 0
}
contentItem: Item {
Column {
id: gpuMenuColumn
anchors.fill: parent
anchors.margins: Theme.spacingS
spacing: 2
Repeater {
model: DgopService.availableGpus || []
delegate: Rectangle {
required property var modelData
required property int index
width: gpuMenuColumn.width
height: 40
radius: Theme.cornerRadius
color: gpuOptionArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
property bool isSelected: {
var selectedIdx = gpuContextMenu.widgetData ? (gpuContextMenu.widgetData.selectedGpuIndex !== undefined ? gpuContextMenu.widgetData.selectedGpuIndex : 0) : 0;
return index === selectedIdx;
}
Row {
anchors.left: parent.left
anchors.leftMargin: Theme.spacingS
anchors.right: checkIcon.left
anchors.rightMargin: Theme.spacingS
anchors.verticalCenter: parent.verticalCenter
spacing: Theme.spacingS
DankIcon {
name: "memory"
size: 18
color: isSelected ? Theme.primary : Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
}
Column {
anchors.verticalCenter: parent.verticalCenter
spacing: 2
StyledText {
text: modelData.driver ? modelData.driver.toUpperCase() : ""
font.pixelSize: Theme.fontSizeSmall
font.weight: Font.Medium
color: isSelected ? Theme.primary : Theme.surfaceText
}
StyledText {
text: modelData.displayName || ""
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
elide: Text.ElideRight
width: 180
}
}
}
DankIcon {
id: checkIcon
anchors.right: parent.right
anchors.rightMargin: Theme.spacingS
anchors.verticalCenter: parent.verticalCenter
name: "check"
size: 18
color: Theme.primary
visible: isSelected
}
MouseArea {
id: gpuOptionArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
root.gpuSelectionChanged(gpuContextMenu.sectionId, gpuContextMenu.widgetIndex, index);
gpuContextMenu.close();
}
}
}
}
}
}
}
} }

View File

@@ -357,6 +357,35 @@ Singleton {
return devices.length > 0 ? devices[0].id : ""; return devices.length > 0 ? devices[0].id : "";
} }
function getPinnedDeviceForFocusedScreen() {
const focusedScreen = CompositorService.getFocusedScreen();
if (!focusedScreen)
return "";
const pins = SettingsData.brightnessDevicePins || {};
const screenKey = SettingsData.getScreenDisplayName(focusedScreen);
if (!screenKey)
return "";
const pinnedDevice = pins[screenKey];
if (!pinnedDevice)
return "";
const deviceExists = devices.some(d => d.id === pinnedDevice);
if (!deviceExists)
return "";
return pinnedDevice;
}
function getPreferredDevice() {
const pinned = getPinnedDeviceForFocusedScreen();
if (pinned)
return pinned;
return getDefaultDevice();
}
function getCurrentDeviceInfo() { function getCurrentDeviceInfo() {
const deviceToUse = lastIpcDevice === "" ? getDefaultDevice() : (lastIpcDevice || currentDevice); const deviceToUse = lastIpcDevice === "" ? getDefaultDevice() : (lastIpcDevice || currentDevice);
if (!deviceToUse) { if (!deviceToUse) {
@@ -809,110 +838,95 @@ Singleton {
// IPC Handler for external control // IPC Handler for external control
IpcHandler { IpcHandler {
function set(percentage: string, device: string): string { function set(percentage: string, device: string): string {
if (!root.brightnessAvailable) { if (!root.brightnessAvailable)
return "Brightness control not available"; return "Brightness control not available";
}
const value = parseInt(percentage); const value = parseInt(percentage);
if (isNaN(value)) { if (isNaN(value))
return "Invalid brightness value: " + percentage; return "Invalid brightness value: " + percentage;
}
const targetDevice = device || ""; const actualDevice = device || root.getPreferredDevice();
if (targetDevice && !root.devices.some(d => d.id === targetDevice)) { if (actualDevice && !root.devices.some(d => d.id === actualDevice))
return "Device not found: " + targetDevice; return "Device not found: " + actualDevice;
}
const deviceInfo = targetDevice ? root.getCurrentDeviceInfoByName(targetDevice) : null; const deviceInfo = actualDevice ? root.getCurrentDeviceInfoByName(actualDevice) : null;
const minValue = (deviceInfo && (deviceInfo.class === "backlight" || deviceInfo.class === "ddc")) ? 1 : 0; const minValue = (deviceInfo && (deviceInfo.class === "backlight" || deviceInfo.class === "ddc")) ? 1 : 0;
const clampedValue = Math.max(minValue, Math.min(100, value)); const clampedValue = Math.max(minValue, Math.min(100, value));
root.lastIpcDevice = targetDevice; root.lastIpcDevice = actualDevice;
if (targetDevice && targetDevice !== root.currentDevice) { if (actualDevice && actualDevice !== root.currentDevice)
root.setCurrentDevice(targetDevice, false); root.setCurrentDevice(actualDevice, false);
}
root.setBrightness(clampedValue, targetDevice);
if (targetDevice) { root.setBrightness(clampedValue, actualDevice);
return "Brightness set to " + clampedValue + "% on " + targetDevice;
} else { return actualDevice ? "Brightness set to " + clampedValue + "% on " + actualDevice : "Brightness set to " + clampedValue + "%";
return "Brightness set to " + clampedValue + "%";
}
} }
function increment(step: string, device: string): string { function increment(step: string, device: string): string {
if (!root.brightnessAvailable) { if (!root.brightnessAvailable)
return "Brightness control not available"; return "Brightness control not available";
}
const targetDevice = device || ""; const actualDevice = device || root.getPreferredDevice();
const actualDevice = targetDevice === "" ? root.getDefaultDevice() : targetDevice;
if (actualDevice && !root.devices.some(d => d.id === actualDevice)) { if (actualDevice && !root.devices.some(d => d.id === actualDevice))
return "Device not found: " + actualDevice; return "Device not found: " + actualDevice;
}
const stepValue = parseInt(step || "5"); const stepValue = parseInt(step || "5");
root.lastIpcDevice = actualDevice; root.lastIpcDevice = actualDevice;
if (actualDevice && actualDevice !== root.currentDevice) { if (actualDevice && actualDevice !== root.currentDevice)
root.setCurrentDevice(actualDevice, false); root.setCurrentDevice(actualDevice, false);
}
const isExponential = SessionData.getBrightnessExponential(actualDevice); const isExponential = SessionData.getBrightnessExponential(actualDevice);
const currentBrightness = root.getDeviceBrightness(actualDevice); const currentBrightness = root.getDeviceBrightness(actualDevice);
const deviceInfo = root.getCurrentDeviceInfoByName(actualDevice); const deviceInfo = root.getCurrentDeviceInfoByName(actualDevice);
let maxValue = 100; const maxValue = isExponential ? 100 : (deviceInfo?.displayMax || 100);
if (isExponential) {
maxValue = 100;
} else {
maxValue = deviceInfo?.displayMax || 100;
}
const newBrightness = Math.min(maxValue, currentBrightness + stepValue); const newBrightness = Math.min(maxValue, currentBrightness + stepValue);
root.setBrightness(newBrightness, actualDevice); root.setBrightness(newBrightness, actualDevice);
return "Brightness increased by " + stepValue + "%" + (targetDevice ? " on " + targetDevice : ""); return "Brightness increased by " + stepValue + "%" + (device ? " on " + actualDevice : "");
} }
function decrement(step: string, device: string): string { function decrement(step: string, device: string): string {
if (!root.brightnessAvailable) { if (!root.brightnessAvailable)
return "Brightness control not available"; return "Brightness control not available";
}
const targetDevice = device || ""; const actualDevice = device || root.getPreferredDevice();
const actualDevice = targetDevice === "" ? root.getDefaultDevice() : targetDevice;
if (actualDevice && !root.devices.some(d => d.id === actualDevice)) { if (actualDevice && !root.devices.some(d => d.id === actualDevice))
return "Device not found: " + actualDevice; return "Device not found: " + actualDevice;
}
const stepValue = parseInt(step || "5"); const stepValue = parseInt(step || "5");
root.lastIpcDevice = actualDevice; root.lastIpcDevice = actualDevice;
if (actualDevice && actualDevice !== root.currentDevice) { if (actualDevice && actualDevice !== root.currentDevice)
root.setCurrentDevice(actualDevice, false); root.setCurrentDevice(actualDevice, false);
}
const isExponential = SessionData.getBrightnessExponential(actualDevice); const isExponential = SessionData.getBrightnessExponential(actualDevice);
const currentBrightness = root.getDeviceBrightness(actualDevice); const currentBrightness = root.getDeviceBrightness(actualDevice);
const deviceInfo = root.getCurrentDeviceInfoByName(actualDevice); const deviceInfo = root.getCurrentDeviceInfoByName(actualDevice);
let minValue = 0; let minValue = 0;
if (isExponential) { switch (true) {
case isExponential:
minValue = 1; minValue = 1;
} else { break;
minValue = (deviceInfo && (deviceInfo.class === "backlight" || deviceInfo.class === "ddc")) ? 1 : 0; case deviceInfo && (deviceInfo.class === "backlight" || deviceInfo.class === "ddc"):
minValue = 1;
break;
default:
minValue = 0;
break;
} }
const newBrightness = Math.max(minValue, currentBrightness - stepValue); const newBrightness = Math.max(minValue, currentBrightness - stepValue);
root.setBrightness(newBrightness, actualDevice); root.setBrightness(newBrightness, actualDevice);
return "Brightness decreased by " + stepValue + "%" + (targetDevice ? " on " + targetDevice : ""); return "Brightness decreased by " + stepValue + "%" + (device ? " on " + actualDevice : "");
} }
function status(): string { function status(): string {

View File

@@ -197,6 +197,8 @@ Singleton {
property bool _settingsWantsOpen: false property bool _settingsWantsOpen: false
property bool _settingsWantsToggle: false property bool _settingsWantsToggle: false
property string _settingsPendingTab: ""
function openSettings() { function openSettings() {
if (settingsModal) { if (settingsModal) {
settingsModal.show(); settingsModal.show();
@@ -207,6 +209,19 @@ Singleton {
} }
} }
function openSettingsWithTab(tabName: string) {
if (settingsModal) {
settingsModal.showWithTabName(tabName);
return;
}
if (settingsModalLoader) {
_settingsPendingTab = tabName;
_settingsWantsOpen = true;
_settingsWantsToggle = false;
settingsModalLoader.activeAsync = true;
}
}
function closeSettings() { function closeSettings() {
settingsModal?.close(); settingsModal?.close();
} }
@@ -221,6 +236,22 @@ Singleton {
} }
} }
function toggleSettingsWithTab(tabName: string) {
if (settingsModal) {
var idx = settingsModal.resolveTabIndex(tabName);
if (idx >= 0)
settingsModal.currentTabIndex = idx;
settingsModal.toggle();
return;
}
if (settingsModalLoader) {
_settingsPendingTab = tabName;
_settingsWantsToggle = true;
_settingsWantsOpen = false;
settingsModalLoader.activeAsync = true;
}
}
function focusOrToggleSettings() { function focusOrToggleSettings() {
if (settingsModal?.visible) { if (settingsModal?.visible) {
const settingsTitle = I18n.tr("Settings", "settings window title"); const settingsTitle = I18n.tr("Settings", "settings window title");
@@ -238,6 +269,26 @@ Singleton {
openSettings(); openSettings();
} }
function focusOrToggleSettingsWithTab(tabName: string) {
if (settingsModal?.visible) {
const settingsTitle = I18n.tr("Settings", "settings window title");
for (const toplevel of ToplevelManager.toplevels.values) {
if (toplevel.title !== "Settings" && toplevel.title !== settingsTitle)
continue;
if (toplevel.activated) {
settingsModal.hide();
return;
}
var idx = settingsModal.resolveTabIndex(tabName);
if (idx >= 0)
settingsModal.currentTabIndex = idx;
toplevel.activate();
return;
}
}
openSettingsWithTab(tabName);
}
function unloadSettings() { function unloadSettings() {
if (settingsModalLoader) { if (settingsModalLoader) {
settingsModal = null; settingsModal = null;
@@ -248,9 +299,22 @@ Singleton {
function _onSettingsModalLoaded() { function _onSettingsModalLoaded() {
if (_settingsWantsOpen) { if (_settingsWantsOpen) {
_settingsWantsOpen = false; _settingsWantsOpen = false;
settingsModal?.show(); if (_settingsPendingTab) {
} else if (_settingsWantsToggle) { settingsModal?.showWithTabName(_settingsPendingTab);
_settingsPendingTab = "";
} else {
settingsModal?.show();
}
return;
}
if (_settingsWantsToggle) {
_settingsWantsToggle = false; _settingsWantsToggle = false;
if (_settingsPendingTab) {
var idx = settingsModal?.resolveTabIndex(_settingsPendingTab) ?? -1;
if (idx >= 0)
settingsModal.currentTabIndex = idx;
_settingsPendingTab = "";
}
settingsModal?.toggle(); settingsModal?.toggle();
} }
} }

View File

@@ -37,6 +37,6 @@ StyledRect {
onClicked: root.clicked() onClicked: root.clicked()
onEntered: root.entered() onEntered: root.entered()
onExited: root.exited() onExited: root.exited()
tooltipText: tooltipText tooltipText: root.tooltipText
} }
} }

View File

@@ -122,9 +122,10 @@ PanelWindow {
width: parent.width width: parent.width
x: Theme.snap(slideContainer.slideOffset, root.dpr) x: Theme.snap(slideContainer.slideOffset, root.dpr)
DankRectangle { Rectangle {
anchors.fill: parent anchors.fill: parent
color: Theme.surfaceContainer color: Theme.surfaceContainer
radius: Theme.cornerRadius
} }
Column { Column {

View File

@@ -11,7 +11,17 @@ Item {
if (!item) if (!item)
return; return;
const windowContentItem = item.Window.window?.contentItem; let windowContentItem = item.Window?.window?.contentItem;
if (!windowContentItem) {
let current = item;
while (current) {
if (current.Window?.window?.contentItem) {
windowContentItem = current.Window.window.contentItem;
break;
}
current = current.parent;
}
}
if (!windowContentItem) if (!windowContentItem)
return; return;

View File

@@ -21,29 +21,29 @@ MouseArea {
color: Qt.rgba(stateColor.r, stateColor.g, stateColor.b, stateOpacity) color: Qt.rgba(stateColor.r, stateColor.g, stateColor.b, stateOpacity)
} }
Timer { Timer {
id: hoverDelay id: hoverDelay
interval: 1000 interval: 1000
repeat: false repeat: false
onTriggered: { onTriggered: {
const p = root.mapToItem(null, parent.width / 2, parent.height + Theme.spacingXS) tooltip.show(root.tooltipText, root, 0, 0, "bottom");
tooltip.show(I18n.tr(""), p.x, p.y, null)
} }
} }
onEntered: { onEntered: {
if (!tooltipText) { return } if (!tooltipText)
hoverDelay.restart() return;
hoverDelay.restart();
} }
onExited: { onExited: {
if (!tooltipText) { return } if (!tooltipText)
hoverDelay.stop() return;
tooltip.hide() hoverDelay.stop();
tooltip.hide();
} }
DankTooltip { DankTooltipV2 {
id: tooltip id: tooltip
} }
} }

View File

@@ -133,6 +133,18 @@ Rectangle {
onClicked: DMSNetworkService.disconnectAllActive() onClicked: DMSNetworkService.disconnectAllActive()
} }
} }
DankActionButton {
Layout.alignment: Qt.AlignVCenter
iconName: "settings"
buttonSize: 28
iconSize: 16
iconColor: Theme.surfaceVariantText
onClicked: {
PopoutService.closeControlCenter();
PopoutService.openSettingsWithTab("network");
}
}
} }
Rectangle { Rectangle {