mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-04-04 12:52:06 -04:00
527 lines
23 KiB
QML
527 lines
23 KiB
QML
import QtQuick
|
|
import qs.Common
|
|
import qs.Modals
|
|
import qs.Services
|
|
import qs.Widgets
|
|
import qs.Modules.Settings.DisplayConfig
|
|
|
|
Item {
|
|
id: root
|
|
|
|
property string selectedProfileId: SettingsData.getActiveDisplayProfile(CompositorService.compositor)
|
|
property bool showNewProfileDialog: false
|
|
property bool showDeleteConfirmDialog: false
|
|
property bool showRenameDialog: false
|
|
property string newProfileName: ""
|
|
property string renameProfileName: ""
|
|
|
|
function getProfileOptions() {
|
|
const profiles = DisplayConfigState.validatedProfiles;
|
|
const options = [];
|
|
for (const id in profiles)
|
|
options.push(profiles[id].name);
|
|
return options;
|
|
}
|
|
|
|
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 || "";
|
|
}
|
|
|
|
Connections {
|
|
target: DisplayConfigState
|
|
function onChangesApplied(changeDescriptions) {
|
|
confirmationModal.changes = changeDescriptions;
|
|
confirmationModal.open();
|
|
}
|
|
function onChangesConfirmed() {
|
|
}
|
|
function onChangesReverted() {
|
|
}
|
|
function onProfileActivated(profileId, profileName) {
|
|
root.selectedProfileId = profileId;
|
|
ToastService.showInfo(I18n.tr("Profile activated: %1").arg(profileName));
|
|
}
|
|
function onProfileSaved(profileId, profileName) {
|
|
root.selectedProfileId = profileId;
|
|
ToastService.showInfo(I18n.tr("Profile saved: %1").arg(profileName));
|
|
}
|
|
function onProfileDeleted(profileId) {
|
|
root.selectedProfileId = SettingsData.getActiveDisplayProfile(CompositorService.compositor);
|
|
ToastService.showInfo(I18n.tr("Profile deleted"));
|
|
}
|
|
function onProfileError(message) {
|
|
ToastService.showError(I18n.tr("Profile error"), message);
|
|
}
|
|
}
|
|
|
|
DankFlickable {
|
|
anchors.fill: parent
|
|
clip: true
|
|
contentHeight: mainColumn.height + Theme.spacingXL
|
|
contentWidth: width
|
|
|
|
Column {
|
|
id: mainColumn
|
|
topPadding: 4
|
|
|
|
width: Math.min(550, parent.width - Theme.spacingL * 2)
|
|
anchors.horizontalCenter: parent.horizontalCenter
|
|
spacing: Theme.spacingXL
|
|
|
|
IncludeWarningBox {
|
|
width: parent.width
|
|
}
|
|
|
|
StyledRect {
|
|
width: parent.width
|
|
height: profileSection.implicitHeight + Theme.spacingL * 2
|
|
radius: Theme.cornerRadius
|
|
color: Theme.surfaceContainerHigh
|
|
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
|
|
border.width: 0
|
|
visible: DisplayConfigState.hasOutputBackend
|
|
|
|
Column {
|
|
id: profileSection
|
|
anchors.fill: parent
|
|
anchors.margins: Theme.spacingL
|
|
spacing: Theme.spacingM
|
|
|
|
Row {
|
|
width: parent.width
|
|
spacing: Theme.spacingM
|
|
|
|
DankIcon {
|
|
name: "tune"
|
|
size: Theme.iconSize
|
|
color: Theme.primary
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
}
|
|
|
|
Column {
|
|
width: parent.width - Theme.iconSize - Theme.spacingM - autoSelectColumn.width - Theme.spacingM
|
|
spacing: Theme.spacingXS
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
|
|
StyledText {
|
|
text: I18n.tr("Display Profiles")
|
|
font.pixelSize: Theme.fontSizeLarge
|
|
font.weight: Font.Medium
|
|
color: Theme.surfaceText
|
|
}
|
|
|
|
StyledText {
|
|
text: I18n.tr("Save and switch between display configurations")
|
|
font.pixelSize: Theme.fontSizeSmall
|
|
color: Theme.surfaceVariantText
|
|
wrapMode: Text.WordWrap
|
|
width: parent.width
|
|
}
|
|
}
|
|
|
|
// ! TODO - auto profile switching is buggy on niri and other compositors
|
|
Column {
|
|
id: autoSelectColumn
|
|
visible: false // disabled for now
|
|
spacing: Theme.spacingXS
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
|
|
StyledText {
|
|
text: I18n.tr("Auto")
|
|
font.pixelSize: Theme.fontSizeSmall
|
|
color: Theme.surfaceVariantText
|
|
anchors.horizontalCenter: parent.horizontalCenter
|
|
}
|
|
|
|
DankToggle {
|
|
id: autoSelectToggle
|
|
checked: false // disabled for now
|
|
enabled: false
|
|
onToggled: checked => {
|
|
// disabled for now
|
|
// SettingsData.displayProfileAutoSelect = checked;
|
|
// SettingsData.saveSettings();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Row {
|
|
width: parent.width
|
|
spacing: Theme.spacingS
|
|
visible: !root.showNewProfileDialog && !root.showDeleteConfirmDialog && !root.showRenameDialog
|
|
|
|
DankDropdown {
|
|
id: profileDropdown
|
|
width: parent.width - newButton.width - deleteButton.width - Theme.spacingS * 2
|
|
compactMode: true
|
|
dropdownWidth: width
|
|
options: root.getProfileOptions()
|
|
currentValue: root.getProfileNameById(root.selectedProfileId)
|
|
emptyText: I18n.tr("No profiles")
|
|
onValueChanged: value => {
|
|
const profileId = root.getProfileIdByName(value);
|
|
if (profileId && profileId !== root.selectedProfileId)
|
|
DisplayConfigState.activateProfile(profileId);
|
|
}
|
|
}
|
|
|
|
DankButton {
|
|
id: newButton
|
|
iconName: "add"
|
|
text: ""
|
|
buttonHeight: 40
|
|
horizontalPadding: Theme.spacingM
|
|
backgroundColor: Theme.surfaceContainer
|
|
textColor: Theme.surfaceText
|
|
onClicked: {
|
|
root.newProfileName = "";
|
|
root.showNewProfileDialog = true;
|
|
}
|
|
}
|
|
|
|
DankButton {
|
|
id: deleteButton
|
|
iconName: "delete"
|
|
text: ""
|
|
buttonHeight: 40
|
|
horizontalPadding: Theme.spacingM
|
|
backgroundColor: Theme.surfaceContainer
|
|
textColor: Theme.error
|
|
enabled: root.selectedProfileId !== ""
|
|
onClicked: root.showDeleteConfirmDialog = true
|
|
}
|
|
}
|
|
|
|
Rectangle {
|
|
width: parent.width
|
|
height: newProfileRow.height + Theme.spacingM * 2
|
|
radius: Theme.cornerRadius
|
|
color: Theme.surfaceContainer
|
|
visible: root.showNewProfileDialog
|
|
|
|
Row {
|
|
id: newProfileRow
|
|
anchors.centerIn: parent
|
|
width: parent.width - Theme.spacingM * 2
|
|
spacing: Theme.spacingS
|
|
|
|
DankTextField {
|
|
id: newProfileField
|
|
width: parent.width - createButton.width - cancelNewButton.width - Theme.spacingS * 2
|
|
placeholderText: I18n.tr("Profile name")
|
|
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
|
|
}
|
|
}
|
|
}
|
|
|
|
Rectangle {
|
|
width: parent.width
|
|
height: deleteConfirmColumn.height + Theme.spacingM * 2
|
|
radius: Theme.cornerRadius
|
|
color: Theme.surfaceContainer
|
|
visible: root.showDeleteConfirmDialog
|
|
|
|
Column {
|
|
id: deleteConfirmColumn
|
|
anchors.centerIn: parent
|
|
width: parent.width - Theme.spacingM * 2
|
|
spacing: Theme.spacingS
|
|
|
|
StyledText {
|
|
text: I18n.tr("Delete profile \"%1\"?").arg(root.getProfileNameById(root.selectedProfileId))
|
|
font.pixelSize: Theme.fontSizeMedium
|
|
color: Theme.surfaceText
|
|
width: parent.width
|
|
wrapMode: Text.WordWrap
|
|
}
|
|
|
|
Row {
|
|
spacing: Theme.spacingS
|
|
anchors.right: parent.right
|
|
|
|
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
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Row {
|
|
width: parent.width
|
|
spacing: Theme.spacingS
|
|
visible: DisplayConfigState.matchedProfile !== ""
|
|
|
|
DankIcon {
|
|
name: "check_circle"
|
|
size: 16
|
|
color: Theme.success
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
}
|
|
|
|
StyledText {
|
|
text: I18n.tr("Matches profile: %1").arg(root.getProfileNameById(DisplayConfigState.matchedProfile))
|
|
font.pixelSize: Theme.fontSizeSmall
|
|
color: Theme.success
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
StyledRect {
|
|
width: parent.width
|
|
height: monitorConfigSection.implicitHeight + Theme.spacingL * 2
|
|
radius: Theme.cornerRadius
|
|
color: Theme.surfaceContainerHigh
|
|
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
|
|
border.width: 0
|
|
visible: DisplayConfigState.hasOutputBackend
|
|
|
|
Column {
|
|
id: monitorConfigSection
|
|
anchors.fill: parent
|
|
anchors.margins: Theme.spacingL
|
|
spacing: Theme.spacingM
|
|
|
|
Row {
|
|
width: parent.width
|
|
spacing: Theme.spacingM
|
|
|
|
DankIcon {
|
|
name: "monitor"
|
|
size: Theme.iconSize
|
|
color: Theme.primary
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
}
|
|
|
|
Column {
|
|
width: parent.width - Theme.iconSize - Theme.spacingM - (displayFormatColumn.visible ? displayFormatColumn.width + Theme.spacingM : 0) - (snapColumn.visible ? snapColumn.width + Theme.spacingM : 0)
|
|
spacing: Theme.spacingXS
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
|
|
StyledText {
|
|
text: I18n.tr("Monitor Configuration")
|
|
font.pixelSize: Theme.fontSizeLarge
|
|
font.weight: Font.Medium
|
|
color: Theme.surfaceText
|
|
}
|
|
|
|
StyledText {
|
|
text: I18n.tr("Arrange displays and configure resolution, refresh rate, and VRR")
|
|
font.pixelSize: Theme.fontSizeSmall
|
|
color: Theme.surfaceVariantText
|
|
wrapMode: Text.WordWrap
|
|
width: parent.width
|
|
}
|
|
}
|
|
|
|
Column {
|
|
id: snapColumn
|
|
visible: true
|
|
spacing: Theme.spacingXS
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
|
|
StyledText {
|
|
text: I18n.tr("Snap")
|
|
font.pixelSize: Theme.fontSizeSmall
|
|
color: Theme.surfaceVariantText
|
|
anchors.horizontalCenter: parent.horizontalCenter
|
|
}
|
|
|
|
DankToggle {
|
|
id: snapToggle
|
|
checked: SettingsData.displaySnapToEdge
|
|
onToggled: checked => {
|
|
SettingsData.displaySnapToEdge = checked;
|
|
SettingsData.saveSettings();
|
|
}
|
|
}
|
|
}
|
|
|
|
Column {
|
|
id: displayFormatColumn
|
|
visible: !CompositorService.isDwl
|
|
spacing: Theme.spacingXS
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
|
|
StyledText {
|
|
text: I18n.tr("Config Format")
|
|
font.pixelSize: Theme.fontSizeSmall
|
|
color: Theme.surfaceVariantText
|
|
anchors.horizontalCenter: parent.horizontalCenter
|
|
}
|
|
|
|
DankButtonGroup {
|
|
id: displayFormatGroup
|
|
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 {
|
|
width: parent.width
|
|
}
|
|
|
|
Column {
|
|
width: parent.width
|
|
spacing: Theme.spacingS
|
|
|
|
Row {
|
|
width: parent.width
|
|
spacing: Theme.spacingS
|
|
visible: {
|
|
const all = DisplayConfigState.allOutputs || {};
|
|
const disconnected = Object.keys(all).filter(k => !all[k]?.connected);
|
|
return disconnected.length > 0;
|
|
}
|
|
|
|
StyledText {
|
|
text: {
|
|
const all = DisplayConfigState.allOutputs || {};
|
|
const disconnected = Object.keys(all).filter(k => !all[k]?.connected);
|
|
if (SettingsData.displayShowDisconnected)
|
|
return I18n.tr("%1 disconnected").arg(disconnected.length);
|
|
return I18n.tr("%1 disconnected (hidden)").arg(disconnected.length);
|
|
}
|
|
font.pixelSize: Theme.fontSizeSmall
|
|
color: Theme.surfaceVariantText
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
}
|
|
|
|
StyledText {
|
|
text: SettingsData.displayShowDisconnected ? I18n.tr("Hide") : I18n.tr("Show")
|
|
font.pixelSize: Theme.fontSizeSmall
|
|
color: Theme.primary
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
|
|
MouseArea {
|
|
anchors.fill: parent
|
|
cursorShape: Qt.PointingHandCursor
|
|
onClicked: {
|
|
SettingsData.displayShowDisconnected = !SettingsData.displayShowDisconnected;
|
|
SettingsData.saveSettings();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Repeater {
|
|
model: {
|
|
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]
|
|
}
|
|
}
|
|
}
|
|
|
|
Row {
|
|
width: parent.width
|
|
spacing: Theme.spacingS
|
|
visible: DisplayConfigState.hasPendingChanges
|
|
layoutDirection: Qt.RightToLeft
|
|
|
|
DankButton {
|
|
text: I18n.tr("Apply Changes")
|
|
iconName: "check"
|
|
onClicked: DisplayConfigState.applyChanges()
|
|
}
|
|
|
|
DankButton {
|
|
text: I18n.tr("Discard")
|
|
backgroundColor: "transparent"
|
|
textColor: Theme.surfaceText
|
|
onClicked: DisplayConfigState.discardChanges()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
NoBackendMessage {
|
|
width: parent.width
|
|
visible: !DisplayConfigState.hasOutputBackend
|
|
}
|
|
}
|
|
}
|
|
|
|
DisplayConfirmationModal {
|
|
id: confirmationModal
|
|
onConfirmed: DisplayConfigState.confirmChanges()
|
|
onReverted: DisplayConfigState.revertChanges()
|
|
}
|
|
}
|