1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2026-01-24 21:42:51 -05:00
Files
DankMaterialShell/quickshell/Modules/Settings/DisplayConfig/OutputCard.qml
2025-12-16 13:36:00 -05:00

279 lines
12 KiB
QML

import QtQuick
import qs.Common
import qs.Services
import qs.Widgets
StyledRect {
id: root
required property string outputName
required property var outputData
property bool isConnected: outputData?.connected ?? false
width: parent.width
height: settingsColumn.implicitHeight + Theme.spacingM * 2
radius: Theme.cornerRadius
color: Theme.withAlpha(Theme.surfaceContainerHigh, isConnected ? 0.5 : 0.3)
border.color: Theme.withAlpha(Theme.outline, 0.3)
border.width: 1
opacity: isConnected ? 1.0 : 0.7
Column {
id: settingsColumn
anchors.fill: parent
anchors.margins: Theme.spacingM
spacing: Theme.spacingS
Row {
width: parent.width
spacing: Theme.spacingM
DankIcon {
name: root.isConnected ? "desktop_windows" : "desktop_access_disabled"
size: Theme.iconSize - 4
color: root.isConnected ? Theme.primary : Theme.surfaceVariantText
anchors.verticalCenter: parent.verticalCenter
}
Column {
width: parent.width - Theme.iconSize - Theme.spacingM - (disconnectedBadge.visible ? disconnectedBadge.width + Theme.spacingS : 0)
spacing: 2
StyledText {
text: DisplayConfigState.getOutputDisplayName(root.outputData, root.outputName)
font.pixelSize: Theme.fontSizeMedium
font.weight: Font.Medium
color: root.isConnected ? Theme.surfaceText : Theme.surfaceVariantText
}
StyledText {
text: (root.outputData?.model ?? "") + (root.outputData?.make ? " - " + root.outputData.make : "")
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
}
}
Rectangle {
id: disconnectedBadge
visible: !root.isConnected
width: disconnectedText.implicitWidth + Theme.spacingM
height: disconnectedText.implicitHeight + Theme.spacingXS
radius: height / 2
color: Theme.withAlpha(Theme.outline, 0.3)
anchors.verticalCenter: parent.verticalCenter
StyledText {
id: disconnectedText
text: I18n.tr("Disconnected")
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
anchors.centerIn: parent
}
}
}
DankDropdown {
width: parent.width
text: I18n.tr("Resolution & Refresh")
visible: root.isConnected
currentValue: {
const pendingMode = DisplayConfigState.getPendingValue(root.outputName, "mode");
if (pendingMode)
return pendingMode;
const data = DisplayConfigState.outputs[root.outputName];
if (!data?.modes || data?.current_mode === undefined)
return "Auto";
const mode = data.modes[data.current_mode];
return mode ? DisplayConfigState.formatMode(mode) : "Auto";
}
options: {
const data = DisplayConfigState.outputs[root.outputName];
if (!data?.modes)
return ["Auto"];
const opts = [];
for (var i = 0; i < data.modes.length; i++) {
opts.push(DisplayConfigState.formatMode(data.modes[i]));
}
return opts;
}
onValueChanged: value => DisplayConfigState.setPendingChange(root.outputName, "mode", value)
}
StyledText {
visible: !root.isConnected
text: I18n.tr("Configuration will be preserved when this display reconnects")
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
wrapMode: Text.WordWrap
width: parent.width
}
Row {
width: parent.width
spacing: Theme.spacingM
visible: root.isConnected
Column {
width: (parent.width - Theme.spacingM) / 2
spacing: Theme.spacingXS
StyledText {
text: I18n.tr("Scale")
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
}
Item {
id: scaleContainer
width: parent.width
height: scaleDropdown.visible ? scaleDropdown.height : scaleInput.height
property bool customMode: false
property string currentScale: {
const pendingScale = DisplayConfigState.getPendingValue(root.outputName, "scale");
if (pendingScale !== undefined)
return parseFloat(pendingScale.toFixed(2)).toString();
const scale = DisplayConfigState.outputs[root.outputName]?.logical?.scale ?? 1.0;
return parseFloat(scale.toFixed(2)).toString();
}
DankDropdown {
id: scaleDropdown
width: parent.width
dropdownWidth: parent.width
visible: !scaleContainer.customMode
currentValue: scaleContainer.currentScale
options: {
const standard = ["0.5", "0.75", "1", "1.25", "1.5", "1.75", "2", "2.5", "3", I18n.tr("Custom...")];
const current = scaleContainer.currentScale;
if (standard.slice(0, -1).includes(current))
return standard;
const opts = [...standard.slice(0, -1), current, standard[standard.length - 1]];
return opts.sort((a, b) => {
if (a === I18n.tr("Custom..."))
return 1;
if (b === I18n.tr("Custom..."))
return -1;
return parseFloat(a) - parseFloat(b);
});
}
onValueChanged: value => {
if (value === I18n.tr("Custom...")) {
scaleContainer.customMode = true;
scaleInput.text = scaleContainer.currentScale;
scaleInput.forceActiveFocus();
scaleInput.selectAll();
return;
}
DisplayConfigState.setPendingChange(root.outputName, "scale", parseFloat(value));
}
}
DankTextField {
id: scaleInput
width: parent.width
height: 40
visible: scaleContainer.customMode
placeholderText: "0.5 - 4.0"
function applyValue() {
const val = parseFloat(text);
if (isNaN(val) || val < 0.25 || val > 4) {
text = scaleContainer.currentScale;
scaleContainer.customMode = false;
return;
}
DisplayConfigState.setPendingChange(root.outputName, "scale", parseFloat(val.toFixed(2)));
scaleContainer.customMode = false;
}
onAccepted: applyValue()
onEditingFinished: applyValue()
Keys.onEscapePressed: {
text = scaleContainer.currentScale;
scaleContainer.customMode = false;
}
}
}
}
Column {
width: (parent.width - Theme.spacingM) / 2
spacing: Theme.spacingXS
StyledText {
text: I18n.tr("Transform")
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
}
DankDropdown {
width: parent.width
dropdownWidth: parent.width
currentValue: {
const pendingTransform = DisplayConfigState.getPendingValue(root.outputName, "transform");
if (pendingTransform)
return DisplayConfigState.getTransformLabel(pendingTransform);
const data = DisplayConfigState.outputs[root.outputName];
return DisplayConfigState.getTransformLabel(data?.logical?.transform ?? "Normal");
}
options: [I18n.tr("Normal"), I18n.tr("90°"), I18n.tr("180°"), I18n.tr("270°"), I18n.tr("Flipped"), I18n.tr("Flipped 90°"), I18n.tr("Flipped 180°"), I18n.tr("Flipped 270°")]
onValueChanged: value => DisplayConfigState.setPendingChange(root.outputName, "transform", DisplayConfigState.getTransformValue(value))
}
}
}
DankToggle {
width: parent.width
text: I18n.tr("Variable Refresh Rate")
visible: root.isConnected && !CompositorService.isDwl && (DisplayConfigState.outputs[root.outputName]?.vrr_supported ?? false)
checked: {
const pendingVrr = DisplayConfigState.getPendingValue(root.outputName, "vrr");
if (pendingVrr !== undefined)
return pendingVrr;
return DisplayConfigState.outputs[root.outputName]?.vrr_enabled ?? false;
}
onToggled: checked => DisplayConfigState.setPendingChange(root.outputName, "vrr", checked)
}
DankToggle {
width: parent.width
text: I18n.tr("VRR On-Demand")
description: I18n.tr("VRR activates only when applications request it")
visible: root.isConnected && CompositorService.isNiri && (DisplayConfigState.outputs[root.outputName]?.vrr_supported ?? false)
checked: DisplayConfigState.getNiriSetting(root.outputData, root.outputName, "vrrOnDemand", false)
onToggled: checked => DisplayConfigState.setNiriSetting(root.outputData, root.outputName, "vrrOnDemand", checked)
}
Rectangle {
width: parent.width
height: 1
color: Theme.withAlpha(Theme.outline, 0.2)
visible: compositorSettingsLoader.active
}
Loader {
id: compositorSettingsLoader
width: parent.width
active: root.isConnected && compositorSettingsSource !== ""
source: compositorSettingsSource
property string compositorSettingsSource: {
switch (CompositorService.compositor) {
case "niri":
return "NiriOutputSettings.qml";
case "hyprland":
return "";
default:
return "";
}
}
onLoaded: {
item.outputName = root.outputName;
item.outputData = root.outputData;
}
}
}
}