mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-10-14 23:34:01 -04:00
settings: label some non-obvious thing swith more details fixes #3555 fixes #3553
468 lines
19 KiB
QML
468 lines
19 KiB
QML
import QtQuick
|
|
import qs.Common
|
|
import qs.Modals
|
|
import qs.Modals.FileBrowser
|
|
import qs.Services
|
|
import qs.Widgets
|
|
import qs.Modules.Settings.Widgets
|
|
import qs.Modules.Settings.DisplayConfig
|
|
|
|
Item {
|
|
id: root
|
|
|
|
LayoutMirroring.enabled: I18n.isRtl
|
|
LayoutMirroring.childrenInherit: true
|
|
|
|
property var parentModal: null
|
|
property string pendingICCOutput: ""
|
|
|
|
property string selectedProfileId: {
|
|
const id = SessionData.activeDisplayProfile[CompositorService.compositor] || "";
|
|
if (!SettingsData.displayProfileAutoSelect) {
|
|
const profile = DisplayConfigState.validatedProfiles[id];
|
|
if (profile && profile.name === "")
|
|
return "";
|
|
}
|
|
return id;
|
|
}
|
|
property bool showNewProfileDialog: false
|
|
property bool showDeleteConfirmDialog: false
|
|
property bool showRenameDialog: false
|
|
property bool showEditMonitorsDialog: false
|
|
property string newProfileName: ""
|
|
property string renameProfileName: ""
|
|
property var editMonitorSelection: ({})
|
|
|
|
function getProfileOptions() {
|
|
return Object.values(DisplayConfigState.validatedProfiles).filter(p => p.name !== "").map(p => p.name);
|
|
}
|
|
|
|
function getProfileIds() {
|
|
return Object.keys(DisplayConfigState.validatedProfiles);
|
|
}
|
|
|
|
function getProfileIdByName(name) {
|
|
const profiles = DisplayConfigState.validatedProfiles;
|
|
for (const id in profiles) {
|
|
if (profiles[id].name === name)
|
|
return id;
|
|
}
|
|
return "";
|
|
}
|
|
|
|
function getProfileNameById(id) {
|
|
const profiles = DisplayConfigState.validatedProfiles;
|
|
return profiles[id]?.name || "";
|
|
}
|
|
|
|
function openEditMonitorsDialog() {
|
|
if (!root.selectedProfileId)
|
|
return;
|
|
editMonitorSelection = DisplayConfigState.getProfileMonitorInclusion(root.selectedProfileId);
|
|
showEditMonitorsDialog = true;
|
|
}
|
|
|
|
Connections {
|
|
target: DisplayConfigState
|
|
function onChangesApplied(changeDescriptions) {
|
|
confirmationModal.changes = changeDescriptions;
|
|
confirmationModal.open();
|
|
}
|
|
function onChangesConfirmed() {
|
|
}
|
|
function onChangesReverted() {
|
|
}
|
|
function onProfileActivated(profileId, profileName) {
|
|
ToastService.showInfo(I18n.tr("Profile activated: %1", "toast after switching display profile, %1 is the profile name").arg(profileName));
|
|
}
|
|
function onProfileSaved(profileId, profileName) {
|
|
ToastService.showInfo(I18n.tr("Profile saved: %1", "toast after saving a display profile, %1 is the profile name").arg(profileName));
|
|
}
|
|
function onProfileDeleted(profileId) {
|
|
ToastService.showInfo(I18n.tr("Profile deleted"));
|
|
}
|
|
function onProfileError(message) {
|
|
ToastService.showError(I18n.tr("Profile error"), message);
|
|
}
|
|
}
|
|
|
|
SettingsPage {
|
|
id: mainColumn
|
|
|
|
IncludeSetupBanner {
|
|
include: DisplayConfigState.include
|
|
visibleCondition: DisplayConfigState.hasOutputBackend
|
|
}
|
|
|
|
SettingsCard {
|
|
width: parent.width
|
|
visible: CompositorService.isAqueous && DisplayConfigState.validationError !== ""
|
|
|
|
SettingsRow {
|
|
subtitle: DisplayConfigState.validationError
|
|
subtitleColor: Theme.error
|
|
|
|
DankButton {
|
|
text: I18n.tr("Discard draft and reload", "Discard unsaved Aqueous display settings and refresh the current display state")
|
|
enabled: !DisplayConfigState.validatingConfig
|
|
onClicked: DisplayConfigState.discardAqueousPreview()
|
|
}
|
|
}
|
|
}
|
|
|
|
SettingsCard {
|
|
width: parent.width
|
|
iconName: "tune"
|
|
title: I18n.tr("Profiles", "card title for display configuration profiles")
|
|
visible: DisplayConfigState.hasOutputBackend
|
|
|
|
SettingsToggleRow {
|
|
settingKey: "displayProfileAutoSelect"
|
|
text: I18n.tr("Auto")
|
|
description: I18n.tr("Applies the saved profile that matches the connected displays", "display profile auto select toggle description")
|
|
checked: SettingsData.displayProfileAutoSelect
|
|
onToggled: checked => {
|
|
SettingsData.displayProfileAutoSelect = checked;
|
|
if (!checked)
|
|
SessionData.setActiveDisplayProfile(CompositorService.compositor, "");
|
|
SettingsData.saveSettings();
|
|
if (checked)
|
|
DisplayConfigState.applyAutoConfig();
|
|
}
|
|
}
|
|
|
|
SettingsRow {
|
|
visible: !root.showNewProfileDialog && !root.showDeleteConfirmDialog && !root.showRenameDialog && !root.showEditMonitorsDialog
|
|
body: Row {
|
|
width: parent.width
|
|
spacing: Theme.spacingS
|
|
opacity: SettingsData.displayProfileAutoSelect ? 0.4 : 1.0
|
|
|
|
DankDropdown {
|
|
id: profileDropdown
|
|
width: parent.width - newButton.width - editMonitorsButton.width - deleteButton.width - Theme.spacingS * 3
|
|
compactMode: true
|
|
dropdownWidth: width
|
|
options: root.getProfileOptions()
|
|
emptyText: I18n.tr("No profiles")
|
|
enabled: !SettingsData.displayProfileAutoSelect
|
|
onValueChanged: value => {
|
|
const profileId = root.getProfileIdByName(value);
|
|
if (profileId && profileId !== root.selectedProfileId)
|
|
DisplayConfigState.activateProfile(profileId);
|
|
}
|
|
}
|
|
|
|
Binding {
|
|
target: profileDropdown
|
|
property: "currentValue"
|
|
value: SettingsData.displayProfileAutoSelect ? I18n.tr("Auto") : root.getProfileNameById(root.selectedProfileId)
|
|
}
|
|
|
|
DankButton {
|
|
id: newButton
|
|
tooltipText: I18n.tr("New profile")
|
|
iconName: "add"
|
|
text: ""
|
|
horizontalPadding: Theme.spacingM
|
|
backgroundColor: Theme.chipSurface
|
|
textColor: Theme.surfaceText
|
|
enabled: !SettingsData.displayProfileAutoSelect
|
|
onClicked: {
|
|
root.newProfileName = "";
|
|
root.showNewProfileDialog = true;
|
|
}
|
|
}
|
|
|
|
DankButton {
|
|
id: editMonitorsButton
|
|
tooltipText: I18n.tr("Edit monitors")
|
|
iconName: "edit"
|
|
text: ""
|
|
horizontalPadding: Theme.spacingM
|
|
backgroundColor: Theme.chipSurface
|
|
textColor: Theme.surfaceText
|
|
enabled: root.selectedProfileId !== "" && !SettingsData.displayProfileAutoSelect
|
|
onClicked: root.openEditMonitorsDialog()
|
|
}
|
|
|
|
DankButton {
|
|
id: deleteButton
|
|
Accessible.name: I18n.tr("Delete profile")
|
|
iconName: "delete"
|
|
text: ""
|
|
horizontalPadding: Theme.spacingM
|
|
backgroundColor: Theme.chipSurface
|
|
textColor: Theme.error
|
|
enabled: root.selectedProfileId !== "" && !SettingsData.displayProfileAutoSelect
|
|
onClicked: root.showDeleteConfirmDialog = true
|
|
}
|
|
}
|
|
}
|
|
|
|
SettingsRow {
|
|
visible: root.showNewProfileDialog
|
|
body: Row {
|
|
width: parent.width
|
|
spacing: Theme.spacingS
|
|
|
|
DankTextField {
|
|
id: newProfileField
|
|
outlined: true
|
|
leftIconName: "badge"
|
|
labelText: I18n.tr("Profile name")
|
|
width: parent.width - createButton.width - cancelNewButton.width - Theme.spacingS * 2
|
|
text: root.newProfileName
|
|
onTextChanged: root.newProfileName = text
|
|
onAccepted: {
|
|
if (text.trim())
|
|
DisplayConfigState.createProfile(text.trim());
|
|
root.showNewProfileDialog = false;
|
|
}
|
|
Component.onCompleted: forceActiveFocus()
|
|
}
|
|
|
|
DankButton {
|
|
id: createButton
|
|
text: I18n.tr("Create")
|
|
enabled: root.newProfileName.trim() !== ""
|
|
onClicked: {
|
|
DisplayConfigState.createProfile(root.newProfileName.trim());
|
|
root.showNewProfileDialog = false;
|
|
}
|
|
}
|
|
|
|
DankButton {
|
|
id: cancelNewButton
|
|
text: I18n.tr("Cancel")
|
|
backgroundColor: "transparent"
|
|
textColor: Theme.surfaceText
|
|
onClicked: root.showNewProfileDialog = false
|
|
}
|
|
}
|
|
}
|
|
|
|
SettingsRow {
|
|
visible: root.showDeleteConfirmDialog
|
|
title: I18n.tr("Delete profile \"%1\"?", "delete confirmation, %1 is the display profile name").arg(root.getProfileNameById(root.selectedProfileId))
|
|
|
|
DankButton {
|
|
text: I18n.tr("Delete")
|
|
backgroundColor: Theme.error
|
|
textColor: Theme.primaryText
|
|
onClicked: {
|
|
DisplayConfigState.deleteProfile(root.selectedProfileId);
|
|
root.showDeleteConfirmDialog = false;
|
|
}
|
|
}
|
|
|
|
DankButton {
|
|
text: I18n.tr("Cancel")
|
|
backgroundColor: "transparent"
|
|
textColor: Theme.surfaceText
|
|
onClicked: root.showDeleteConfirmDialog = false
|
|
}
|
|
}
|
|
|
|
SettingsRow {
|
|
visible: root.showEditMonitorsDialog
|
|
title: I18n.tr("Displays in \"%1\"", "monitor list heading, %1 is the display profile name").arg(root.getProfileNameById(root.selectedProfileId)) + ":"
|
|
}
|
|
|
|
Repeater {
|
|
model: Object.keys(DisplayConfigState.allOutputs || {})
|
|
|
|
delegate: SettingsToggleRow {
|
|
required property string modelData
|
|
|
|
visible: root.showEditMonitorsDialog
|
|
text: {
|
|
const od = DisplayConfigState.allOutputs[modelData];
|
|
return DisplayConfigState.getOutputDisplayName(od, modelData);
|
|
}
|
|
description: DisplayConfigState.allOutputs[modelData]?.connected ? I18n.tr("Connected") : I18n.tr("Disconnected")
|
|
descriptionColor: DisplayConfigState.allOutputs[modelData]?.connected ? Theme.success : Theme.surfaceVariantText
|
|
checked: root.editMonitorSelection[modelData] ?? false
|
|
onToggled: checked => {
|
|
const sel = Object.assign({}, root.editMonitorSelection);
|
|
sel[modelData] = checked;
|
|
root.editMonitorSelection = sel;
|
|
}
|
|
}
|
|
}
|
|
|
|
SettingsRow {
|
|
visible: root.showEditMonitorsDialog
|
|
|
|
DankButton {
|
|
text: I18n.tr("Save")
|
|
enabled: Object.values(root.editMonitorSelection).some(v => v)
|
|
onClicked: {
|
|
const enabled = Object.keys(root.editMonitorSelection).filter(k => root.editMonitorSelection[k]);
|
|
DisplayConfigState.updateProfileMonitors(root.selectedProfileId, enabled);
|
|
root.showEditMonitorsDialog = false;
|
|
}
|
|
}
|
|
|
|
DankButton {
|
|
text: I18n.tr("Cancel")
|
|
backgroundColor: "transparent"
|
|
textColor: Theme.surfaceText
|
|
onClicked: root.showEditMonitorsDialog = false
|
|
}
|
|
}
|
|
}
|
|
|
|
SettingsCard {
|
|
width: parent.width
|
|
iconName: "monitor"
|
|
title: I18n.tr("Arrangement", "card title for monitor arrangement")
|
|
visible: DisplayConfigState.hasOutputBackend
|
|
|
|
SettingsToggleRow {
|
|
settingKey: "displaySnapToEdge"
|
|
visible: monitorCanvas.filteredOutputs.length > 1
|
|
text: I18n.tr("Snap", "verb, toggle to snap monitors to edges when arranging")
|
|
description: I18n.tr("Dragged displays line up with neighboring edges without overlapping", "display arrangement snap toggle description")
|
|
checked: SettingsData.displaySnapToEdge
|
|
onToggled: checked => SettingsData.set("displaySnapToEdge", checked)
|
|
}
|
|
|
|
SettingsButtonGroupRow {
|
|
id: displayFormatGroup
|
|
settingKey: "displayNameMode"
|
|
resetKeys: []
|
|
visible: !CompositorService.isMango
|
|
text: I18n.tr("Name format")
|
|
model: [I18n.tr("Name"), I18n.tr("Model")]
|
|
currentIndex: SettingsData.displayNameMode === "model" ? 1 : 0
|
|
onSelectionChanged: (index, selected) => {
|
|
if (!selected)
|
|
return;
|
|
const newMode = index === 1 ? "model" : "system";
|
|
DisplayConfigState.setOriginalDisplayNameMode(SettingsData.displayNameMode);
|
|
SettingsData.displayNameMode = newMode;
|
|
}
|
|
|
|
Connections {
|
|
target: SettingsData
|
|
function onDisplayNameModeChanged() {
|
|
displayFormatGroup.currentIndex = SettingsData.displayNameMode === "model" ? 1 : 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
MonitorCanvas {
|
|
id: monitorCanvas
|
|
width: parent.width
|
|
}
|
|
|
|
SettingsRow {
|
|
visible: {
|
|
const all = DisplayConfigState.allOutputs || {};
|
|
const disconnected = Object.keys(all).filter(k => !all[k]?.connected);
|
|
return disconnected.length > 0;
|
|
}
|
|
title: {
|
|
const all = DisplayConfigState.allOutputs || {};
|
|
const disconnected = Object.keys(all).filter(k => !all[k]?.connected);
|
|
if (SettingsData.displayShowDisconnected)
|
|
return I18n.tr("%1 disconnected", "displays row title, %1 is a count of disconnected monitors").arg(disconnected.length);
|
|
return I18n.tr("%1 disconnected (hidden)", "displays row title, %1 is a count of disconnected monitors").arg(disconnected.length);
|
|
}
|
|
|
|
DankButton {
|
|
text: SettingsData.displayShowDisconnected ? I18n.tr("Hide") : I18n.tr("Show")
|
|
backgroundColor: "transparent"
|
|
textColor: Theme.primary
|
|
onClicked: {
|
|
SettingsData.displayShowDisconnected = !SettingsData.displayShowDisconnected;
|
|
SettingsData.saveSettings();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Repeater {
|
|
model: {
|
|
if (!DisplayConfigState.hasOutputBackend)
|
|
return [];
|
|
const keys = Object.keys(DisplayConfigState.allOutputs || {});
|
|
if (SettingsData.displayShowDisconnected)
|
|
return keys;
|
|
return keys.filter(k => DisplayConfigState.allOutputs[k]?.connected);
|
|
}
|
|
|
|
delegate: OutputCard {
|
|
required property string modelData
|
|
outputName: modelData
|
|
outputData: DisplayConfigState.allOutputs[modelData]
|
|
onRequestICCBrowse: name => {
|
|
root.pendingICCOutput = name;
|
|
iccFileBrowser.open();
|
|
}
|
|
onRequestICCInfo: name => iccInfoModal.showProfile(name)
|
|
}
|
|
}
|
|
|
|
NoBackendMessage {
|
|
width: parent.width
|
|
visible: !DisplayConfigState.hasOutputBackend
|
|
}
|
|
|
|
SettingsFabBar {
|
|
id: pendingChangesBar
|
|
shown: DisplayConfigState.hasOutputBackend && DisplayConfigState.hasPendingChanges
|
|
|
|
DankFab {
|
|
text: I18n.tr("Discard", "verb, button to discard pending changes")
|
|
iconName: "undo"
|
|
colorRole: "secondaryContainer"
|
|
onClicked: DisplayConfigState.discardChanges()
|
|
}
|
|
|
|
DankFab {
|
|
text: I18n.tr("Apply changes")
|
|
iconName: "check"
|
|
colorRole: "primary"
|
|
onClicked: DisplayConfigState.applyChanges()
|
|
}
|
|
}
|
|
}
|
|
|
|
ICCProfileInfoModal {
|
|
id: iccInfoModal
|
|
}
|
|
|
|
FileBrowserModal {
|
|
id: iccFileBrowser
|
|
parentModal: root.parentModal || null
|
|
browserTitle: I18n.tr("Select ICC Profile", "ICC profile file browser title")
|
|
bucket: "icc"
|
|
filters: ["*.icc", "*.icm"]
|
|
onAccepted: paths => {
|
|
if (pendingICCOutput)
|
|
ICCService.applyICC(pendingICCOutput, paths[0]);
|
|
}
|
|
}
|
|
|
|
DisplayConfirmationModal {
|
|
id: confirmationModal
|
|
onConfirmed: DisplayConfigState.confirmChanges(root.selectedProfileId)
|
|
onReverted: DisplayConfigState.revertChanges()
|
|
}
|
|
|
|
readonly property bool identifyConfigured: {
|
|
if (!DisplayConfigState.hasOutputBackend || DisplayConfigState.readOnly)
|
|
return false;
|
|
if (!["niri", "hyprland", "mango"].includes(CompositorService.compositor))
|
|
return true;
|
|
return DisplayConfigState.includeStatus.included;
|
|
}
|
|
|
|
Loader {
|
|
active: root.visible && root.identifyConfigured && monitorCanvas.identifyActive
|
|
sourceComponent: MonitorIdentifyOverlay {}
|
|
}
|
|
}
|