mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-01-24 21:42:51 -05:00
displays: break monolith config down and allow floats/fix integer
writing (niri)
This commit is contained in:
1247
quickshell/Modules/Settings/DisplayConfig/DisplayConfigState.qml
Normal file
1247
quickshell/Modules/Settings/DisplayConfig/DisplayConfigState.qml
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,88 @@
|
|||||||
|
import QtQuick
|
||||||
|
import qs.Common
|
||||||
|
import qs.Widgets
|
||||||
|
|
||||||
|
StyledRect {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
width: parent.width
|
||||||
|
height: warningContent.implicitHeight + Theme.spacingL * 2
|
||||||
|
radius: Theme.cornerRadius
|
||||||
|
|
||||||
|
readonly property bool showError: DisplayConfigState.includeStatus.exists && !DisplayConfigState.includeStatus.included
|
||||||
|
readonly property bool showSetup: !DisplayConfigState.includeStatus.exists && !DisplayConfigState.includeStatus.included
|
||||||
|
|
||||||
|
color: (showError || showSetup) ? Theme.withAlpha(Theme.primary, 0.15) : "transparent"
|
||||||
|
border.color: (showError || showSetup) ? Theme.withAlpha(Theme.primary, 0.3) : "transparent"
|
||||||
|
border.width: 1
|
||||||
|
visible: (showError || showSetup) && DisplayConfigState.hasOutputBackend && !DisplayConfigState.checkingInclude
|
||||||
|
|
||||||
|
Column {
|
||||||
|
id: warningContent
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: Theme.spacingL
|
||||||
|
spacing: Theme.spacingM
|
||||||
|
|
||||||
|
Row {
|
||||||
|
width: parent.width
|
||||||
|
spacing: Theme.spacingM
|
||||||
|
|
||||||
|
DankIcon {
|
||||||
|
name: "warning"
|
||||||
|
size: Theme.iconSize
|
||||||
|
color: Theme.primary
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
width: parent.width - Theme.iconSize - (fixButton.visible ? fixButton.width + Theme.spacingM : 0) - Theme.spacingM
|
||||||
|
spacing: Theme.spacingXS
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: {
|
||||||
|
if (root.showSetup)
|
||||||
|
return I18n.tr("First Time Setup");
|
||||||
|
if (root.showError)
|
||||||
|
return I18n.tr("Outputs Include Missing");
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
|
font.weight: Font.Medium
|
||||||
|
color: Theme.primary
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: {
|
||||||
|
if (root.showSetup)
|
||||||
|
return I18n.tr("Click 'Setup' to create the outputs config and add include to your compositor config.");
|
||||||
|
if (root.showError)
|
||||||
|
return I18n.tr("dms/outputs config exists but is not included in your compositor config. Display changes won't persist.");
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: Theme.surfaceVariantText
|
||||||
|
wrapMode: Text.WordWrap
|
||||||
|
width: parent.width
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DankButton {
|
||||||
|
id: fixButton
|
||||||
|
visible: root.showError || root.showSetup
|
||||||
|
text: {
|
||||||
|
if (DisplayConfigState.fixingInclude)
|
||||||
|
return I18n.tr("Fixing...");
|
||||||
|
if (root.showSetup)
|
||||||
|
return I18n.tr("Setup");
|
||||||
|
return I18n.tr("Fix Now");
|
||||||
|
}
|
||||||
|
backgroundColor: Theme.primary
|
||||||
|
textColor: Theme.primaryText
|
||||||
|
enabled: !DisplayConfigState.fixingInclude
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
onClicked: DisplayConfigState.fixOutputsInclude()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
49
quickshell/Modules/Settings/DisplayConfig/MonitorCanvas.qml
Normal file
49
quickshell/Modules/Settings/DisplayConfig/MonitorCanvas.qml
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
import QtQuick
|
||||||
|
import qs.Common
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
width: parent.width
|
||||||
|
height: 280
|
||||||
|
radius: Theme.cornerRadius
|
||||||
|
color: Theme.surfaceContainerHighest
|
||||||
|
border.color: Theme.outline
|
||||||
|
border.width: 1
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: canvas
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: Theme.spacingL
|
||||||
|
|
||||||
|
property var bounds: DisplayConfigState.getOutputBounds()
|
||||||
|
property real scaleFactor: {
|
||||||
|
if (bounds.width === 0 || bounds.height === 0)
|
||||||
|
return 0.1;
|
||||||
|
const padding = Theme.spacingL * 2;
|
||||||
|
const scaleX = (width - padding) / bounds.width;
|
||||||
|
const scaleY = (height - padding) / bounds.height;
|
||||||
|
return Math.min(scaleX, scaleY);
|
||||||
|
}
|
||||||
|
property point offset: Qt.point((width - bounds.width * scaleFactor) / 2 - bounds.minX * scaleFactor, (height - bounds.height * scaleFactor) / 2 - bounds.minY * scaleFactor)
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: DisplayConfigState
|
||||||
|
function onAllOutputsChanged() {
|
||||||
|
canvas.bounds = DisplayConfigState.getOutputBounds();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Repeater {
|
||||||
|
model: DisplayConfigState.allOutputs ? Object.keys(DisplayConfigState.allOutputs) : []
|
||||||
|
|
||||||
|
delegate: MonitorRect {
|
||||||
|
required property string modelData
|
||||||
|
outputName: modelData
|
||||||
|
outputData: DisplayConfigState.allOutputs[modelData]
|
||||||
|
canvasScaleFactor: canvas.scaleFactor
|
||||||
|
canvasOffset: canvas.offset
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
158
quickshell/Modules/Settings/DisplayConfig/MonitorRect.qml
Normal file
158
quickshell/Modules/Settings/DisplayConfig/MonitorRect.qml
Normal file
@@ -0,0 +1,158 @@
|
|||||||
|
import QtQuick
|
||||||
|
import qs.Common
|
||||||
|
import qs.Services
|
||||||
|
import qs.Widgets
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
required property string outputName
|
||||||
|
required property var outputData
|
||||||
|
required property real canvasScaleFactor
|
||||||
|
required property point canvasOffset
|
||||||
|
|
||||||
|
property bool isConnected: outputData?.connected ?? false
|
||||||
|
property bool isDragging: false
|
||||||
|
property point originalLogical: Qt.point(0, 0)
|
||||||
|
property point snappedLogical: Qt.point(0, 0)
|
||||||
|
property bool isValidPosition: true
|
||||||
|
|
||||||
|
property var physSize: DisplayConfigState.getPhysicalSize(outputData)
|
||||||
|
property var logicalSize: DisplayConfigState.getLogicalSize(outputData)
|
||||||
|
|
||||||
|
x: isDragging ? x : (outputData?.logical?.x ?? 0) * canvasScaleFactor + canvasOffset.x
|
||||||
|
y: isDragging ? y : (outputData?.logical?.y ?? 0) * canvasScaleFactor + canvasOffset.y
|
||||||
|
width: logicalSize.w * canvasScaleFactor
|
||||||
|
height: logicalSize.h * canvasScaleFactor
|
||||||
|
radius: Theme.cornerRadius
|
||||||
|
opacity: isConnected ? 1.0 : 0.5
|
||||||
|
|
||||||
|
color: {
|
||||||
|
if (!isConnected)
|
||||||
|
return Theme.surfaceContainerHighest;
|
||||||
|
if (!isValidPosition)
|
||||||
|
return Theme.withAlpha(Theme.error, 0.3);
|
||||||
|
if (isDragging)
|
||||||
|
return Theme.withAlpha(Theme.primary, 0.4);
|
||||||
|
if (dragArea.containsMouse)
|
||||||
|
return Theme.withAlpha(Theme.primary, 0.2);
|
||||||
|
return Theme.surfaceContainerHigh;
|
||||||
|
}
|
||||||
|
|
||||||
|
border.color: {
|
||||||
|
if (!isConnected)
|
||||||
|
return Theme.outline;
|
||||||
|
if (!isValidPosition)
|
||||||
|
return Theme.error;
|
||||||
|
if (isDragging)
|
||||||
|
return Theme.primary;
|
||||||
|
if (CompositorService.getFocusedScreen()?.name === outputName)
|
||||||
|
return Theme.primary;
|
||||||
|
return Theme.outline;
|
||||||
|
}
|
||||||
|
border.width: isDragging ? 3 : 2
|
||||||
|
z: isDragging ? 100 : (isConnected ? 1 : 0)
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: snapPreview
|
||||||
|
visible: root.isDragging && root.isValidPosition
|
||||||
|
x: root.snappedLogical.x * root.canvasScaleFactor + root.canvasOffset.x - root.x
|
||||||
|
y: root.snappedLogical.y * root.canvasScaleFactor + root.canvasOffset.y - root.y
|
||||||
|
width: parent.width
|
||||||
|
height: parent.height
|
||||||
|
radius: Theme.cornerRadius
|
||||||
|
color: "transparent"
|
||||||
|
border.color: Theme.primary
|
||||||
|
border.width: 2
|
||||||
|
opacity: 0.6
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
spacing: 2
|
||||||
|
|
||||||
|
DankIcon {
|
||||||
|
name: root.isConnected ? "desktop_windows" : "desktop_access_disabled"
|
||||||
|
size: Math.min(24, Math.min(root.width * 0.3, root.height * 0.25))
|
||||||
|
color: root.isConnected ? (root.isValidPosition ? Theme.primary : Theme.error) : Theme.surfaceVariantText
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: DisplayConfigState.getOutputDisplayName(root.outputData, root.outputName)
|
||||||
|
font.pixelSize: Math.max(10, Math.min(14, root.width * 0.12))
|
||||||
|
font.weight: Font.Medium
|
||||||
|
color: root.isConnected ? Theme.surfaceText : Theme.surfaceVariantText
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
elide: Text.ElideMiddle
|
||||||
|
width: Math.min(implicitWidth, root.width - 8)
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: root.isConnected ? (root.physSize.w + "x" + root.physSize.h) : I18n.tr("Disconnected")
|
||||||
|
font.pixelSize: Math.max(8, Math.min(11, root.width * 0.09))
|
||||||
|
color: Theme.surfaceVariantText
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: dragArea
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
enabled: root.isConnected
|
||||||
|
cursorShape: !root.isConnected ? Qt.ArrowCursor : (root.isDragging ? Qt.ClosedHandCursor : Qt.OpenHandCursor)
|
||||||
|
drag.target: root.isConnected ? root : null
|
||||||
|
drag.axis: Drag.XAndYAxis
|
||||||
|
drag.threshold: 0
|
||||||
|
|
||||||
|
onPressed: mouse => {
|
||||||
|
if (!root.isConnected)
|
||||||
|
return;
|
||||||
|
root.isDragging = true;
|
||||||
|
root.originalLogical = Qt.point(root.outputData?.logical?.x ?? 0, root.outputData?.logical?.y ?? 0);
|
||||||
|
root.snappedLogical = root.originalLogical;
|
||||||
|
root.isValidPosition = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
onPositionChanged: mouse => {
|
||||||
|
if (!root.isDragging || !root.isConnected)
|
||||||
|
return;
|
||||||
|
let posX = Math.round((root.x - root.canvasOffset.x) / root.canvasScaleFactor);
|
||||||
|
let posY = Math.round((root.y - root.canvasOffset.y) / root.canvasScaleFactor);
|
||||||
|
|
||||||
|
const size = DisplayConfigState.getLogicalSize(root.outputData);
|
||||||
|
|
||||||
|
const snapped = DisplayConfigState.snapToEdges(root.outputName, posX, posY, size.w, size.h);
|
||||||
|
root.snappedLogical = snapped;
|
||||||
|
root.isValidPosition = !DisplayConfigState.checkOverlap(root.outputName, snapped.x, snapped.y, size.w, size.h);
|
||||||
|
}
|
||||||
|
|
||||||
|
onReleased: {
|
||||||
|
if (!root.isDragging || !root.isConnected)
|
||||||
|
return;
|
||||||
|
root.isDragging = false;
|
||||||
|
|
||||||
|
const size = DisplayConfigState.getLogicalSize(root.outputData);
|
||||||
|
const finalX = root.snappedLogical.x;
|
||||||
|
const finalY = root.snappedLogical.y;
|
||||||
|
|
||||||
|
if (DisplayConfigState.checkOverlap(root.outputName, finalX, finalY, size.w, size.h)) {
|
||||||
|
root.isValidPosition = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (finalX === root.originalLogical.x && finalY === root.originalLogical.y)
|
||||||
|
return;
|
||||||
|
|
||||||
|
DisplayConfigState.initOriginalOutputs();
|
||||||
|
DisplayConfigState.backendUpdateOutputPosition(root.outputName, finalX, finalY);
|
||||||
|
DisplayConfigState.setPendingChange(root.outputName, "position", {
|
||||||
|
"x": finalX,
|
||||||
|
"y": finalY
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Drag.active: dragArea.drag.active && root.isConnected
|
||||||
|
}
|
||||||
368
quickshell/Modules/Settings/DisplayConfig/NiriOutputSettings.qml
Normal file
368
quickshell/Modules/Settings/DisplayConfig/NiriOutputSettings.qml
Normal file
@@ -0,0 +1,368 @@
|
|||||||
|
import QtQuick
|
||||||
|
import qs.Common
|
||||||
|
import qs.Widgets
|
||||||
|
|
||||||
|
Column {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
property string outputName: ""
|
||||||
|
property var outputData: null
|
||||||
|
property bool expanded: false
|
||||||
|
|
||||||
|
width: parent.width
|
||||||
|
spacing: 0
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
width: parent.width
|
||||||
|
height: headerRow.implicitHeight + Theme.spacingS * 2
|
||||||
|
color: headerMouse.containsMouse ? Theme.withAlpha(Theme.primary, 0.1) : "transparent"
|
||||||
|
radius: Theme.cornerRadius / 2
|
||||||
|
|
||||||
|
Row {
|
||||||
|
id: headerRow
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
anchors.leftMargin: Theme.spacingS
|
||||||
|
anchors.rightMargin: Theme.spacingS
|
||||||
|
spacing: Theme.spacingS
|
||||||
|
|
||||||
|
DankIcon {
|
||||||
|
name: root.expanded ? "expand_more" : "chevron_right"
|
||||||
|
size: Theme.iconSize
|
||||||
|
color: Theme.primary
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: I18n.tr("Compositor Settings")
|
||||||
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
|
font.weight: Font.Medium
|
||||||
|
color: Theme.primary
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: headerMouse
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onClicked: root.expanded = !root.expanded
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
width: parent.width
|
||||||
|
spacing: Theme.spacingS
|
||||||
|
visible: root.expanded
|
||||||
|
topPadding: Theme.spacingS
|
||||||
|
|
||||||
|
DankToggle {
|
||||||
|
width: parent.width
|
||||||
|
text: I18n.tr("Disable Output")
|
||||||
|
checked: DisplayConfigState.getNiriSetting(root.outputData, root.outputName, "disabled", false)
|
||||||
|
onToggled: checked => DisplayConfigState.setNiriSetting(root.outputData, root.outputName, "disabled", checked)
|
||||||
|
}
|
||||||
|
|
||||||
|
DankToggle {
|
||||||
|
width: parent.width
|
||||||
|
text: I18n.tr("Focus at Startup")
|
||||||
|
checked: DisplayConfigState.getNiriSetting(root.outputData, root.outputName, "focusAtStartup", false)
|
||||||
|
onToggled: checked => DisplayConfigState.setNiriSetting(root.outputData, root.outputName, "focusAtStartup", checked)
|
||||||
|
}
|
||||||
|
|
||||||
|
DankDropdown {
|
||||||
|
width: parent.width
|
||||||
|
text: I18n.tr("Hot Corners")
|
||||||
|
addHorizontalPadding: true
|
||||||
|
|
||||||
|
property var hotCornersData: {
|
||||||
|
void (DisplayConfigState.pendingNiriChanges);
|
||||||
|
return DisplayConfigState.getNiriSetting(root.outputData, root.outputName, "hotCorners", null);
|
||||||
|
}
|
||||||
|
|
||||||
|
currentValue: {
|
||||||
|
if (!hotCornersData)
|
||||||
|
return I18n.tr("Inherit");
|
||||||
|
if (hotCornersData.off)
|
||||||
|
return I18n.tr("Off");
|
||||||
|
const corners = hotCornersData.corners || [];
|
||||||
|
if (corners.length === 0)
|
||||||
|
return I18n.tr("Inherit");
|
||||||
|
if (corners.length === 4)
|
||||||
|
return I18n.tr("All");
|
||||||
|
return I18n.tr("Select...");
|
||||||
|
}
|
||||||
|
options: [I18n.tr("Inherit"), I18n.tr("Off"), I18n.tr("All"), I18n.tr("Select...")]
|
||||||
|
|
||||||
|
onValueChanged: value => {
|
||||||
|
switch (value) {
|
||||||
|
case I18n.tr("Inherit"):
|
||||||
|
DisplayConfigState.setNiriSetting(root.outputData, root.outputName, "hotCorners", null);
|
||||||
|
break;
|
||||||
|
case I18n.tr("Off"):
|
||||||
|
DisplayConfigState.setNiriSetting(root.outputData, root.outputName, "hotCorners", {
|
||||||
|
"off": true
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case I18n.tr("All"):
|
||||||
|
DisplayConfigState.setNiriSetting(root.outputData, root.outputName, "hotCorners", {
|
||||||
|
"corners": ["top-left", "top-right", "bottom-left", "bottom-right"]
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case I18n.tr("Select..."):
|
||||||
|
DisplayConfigState.setNiriSetting(root.outputData, root.outputName, "hotCorners", {
|
||||||
|
"corners": []
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
width: parent.width
|
||||||
|
height: hotCornersGroup.implicitHeight
|
||||||
|
clip: true
|
||||||
|
|
||||||
|
property var hotCornersData: {
|
||||||
|
void (DisplayConfigState.pendingNiriChanges);
|
||||||
|
return DisplayConfigState.getNiriSetting(root.outputData, root.outputName, "hotCorners", null);
|
||||||
|
}
|
||||||
|
|
||||||
|
visible: hotCornersData && !hotCornersData.off && hotCornersData.corners !== undefined
|
||||||
|
|
||||||
|
DankButtonGroup {
|
||||||
|
id: hotCornersGroup
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
selectionMode: "multi"
|
||||||
|
checkEnabled: false
|
||||||
|
buttonHeight: 32
|
||||||
|
buttonPadding: parent.width < 400 ? Theme.spacingXS : Theme.spacingM
|
||||||
|
minButtonWidth: parent.width < 400 ? 28 : 56
|
||||||
|
textSize: parent.width < 400 ? 11 : Theme.fontSizeMedium
|
||||||
|
model: [I18n.tr("Top Left"), I18n.tr("Top Right"), I18n.tr("Bottom Left"), I18n.tr("Bottom Right")]
|
||||||
|
|
||||||
|
property var cornerKeys: ["top-left", "top-right", "bottom-left", "bottom-right"]
|
||||||
|
|
||||||
|
currentSelection: {
|
||||||
|
const hcData = parent.hotCornersData;
|
||||||
|
if (!hcData?.corners)
|
||||||
|
return [];
|
||||||
|
return hcData.corners.map(key => {
|
||||||
|
const idx = cornerKeys.indexOf(key);
|
||||||
|
return idx >= 0 ? model[idx] : null;
|
||||||
|
}).filter(v => v !== null);
|
||||||
|
}
|
||||||
|
|
||||||
|
onSelectionChanged: (index, selected) => {
|
||||||
|
const corners = currentSelection.map(label => {
|
||||||
|
const idx = model.indexOf(label);
|
||||||
|
return idx >= 0 ? cornerKeys[idx] : null;
|
||||||
|
}).filter(v => v !== null);
|
||||||
|
DisplayConfigState.setNiriSetting(root.outputData, root.outputName, "hotCorners", {
|
||||||
|
"corners": corners
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
width: parent.width
|
||||||
|
height: 1
|
||||||
|
color: Theme.withAlpha(Theme.outline, 0.15)
|
||||||
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
width: parent.width
|
||||||
|
height: layoutColumn.implicitHeight
|
||||||
|
|
||||||
|
Column {
|
||||||
|
id: layoutColumn
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.leftMargin: Theme.spacingM
|
||||||
|
anchors.rightMargin: Theme.spacingM
|
||||||
|
spacing: Theme.spacingS
|
||||||
|
|
||||||
|
Column {
|
||||||
|
width: parent.width
|
||||||
|
spacing: Theme.spacingXS
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: I18n.tr("Layout Overrides")
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
font.weight: Font.Medium
|
||||||
|
color: Theme.surfaceVariantText
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: I18n.tr("Override global layout settings for this output")
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: Theme.withAlpha(Theme.surfaceVariantText, 0.7)
|
||||||
|
wrapMode: Text.WordWrap
|
||||||
|
width: parent.width
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Row {
|
||||||
|
width: parent.width
|
||||||
|
spacing: Theme.spacingM
|
||||||
|
|
||||||
|
Column {
|
||||||
|
width: (parent.width - Theme.spacingM) / 2
|
||||||
|
spacing: Theme.spacingXS
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: I18n.tr("Window Gaps (px)")
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: Theme.surfaceVariantText
|
||||||
|
}
|
||||||
|
|
||||||
|
DankTextField {
|
||||||
|
width: parent.width
|
||||||
|
height: 40
|
||||||
|
placeholderText: I18n.tr("Inherit")
|
||||||
|
text: {
|
||||||
|
const layout = DisplayConfigState.getNiriSetting(root.outputData, root.outputName, "layout", null);
|
||||||
|
if (layout?.gaps === undefined)
|
||||||
|
return "";
|
||||||
|
return layout.gaps.toString();
|
||||||
|
}
|
||||||
|
onEditingFinished: {
|
||||||
|
const layout = DisplayConfigState.getNiriSetting(root.outputData, root.outputName, "layout", {}) || {};
|
||||||
|
const trimmed = text.trim();
|
||||||
|
if (!trimmed) {
|
||||||
|
delete layout.gaps;
|
||||||
|
DisplayConfigState.setNiriSetting(root.outputData, root.outputName, "layout", Object.keys(layout).length > 0 ? layout : null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const val = parseInt(trimmed);
|
||||||
|
if (isNaN(val) || val < 0)
|
||||||
|
return;
|
||||||
|
layout.gaps = val;
|
||||||
|
DisplayConfigState.setNiriSetting(root.outputData, root.outputName, "layout", layout);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
width: (parent.width - Theme.spacingM) / 2
|
||||||
|
spacing: Theme.spacingXS
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: I18n.tr("Default Width (%)")
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: Theme.surfaceVariantText
|
||||||
|
}
|
||||||
|
|
||||||
|
DankTextField {
|
||||||
|
width: parent.width
|
||||||
|
height: 40
|
||||||
|
placeholderText: I18n.tr("Inherit")
|
||||||
|
text: {
|
||||||
|
const layout = DisplayConfigState.getNiriSetting(root.outputData, root.outputName, "layout", null);
|
||||||
|
if (!layout?.defaultColumnWidth)
|
||||||
|
return "";
|
||||||
|
if (layout.defaultColumnWidth.type !== "proportion")
|
||||||
|
return "";
|
||||||
|
const percent = layout.defaultColumnWidth.value * 100;
|
||||||
|
return parseFloat(percent.toFixed(4)).toString();
|
||||||
|
}
|
||||||
|
onEditingFinished: {
|
||||||
|
const layout = DisplayConfigState.getNiriSetting(root.outputData, root.outputName, "layout", {}) || {};
|
||||||
|
const trimmed = text.trim().replace("%", "");
|
||||||
|
if (!trimmed) {
|
||||||
|
delete layout.defaultColumnWidth;
|
||||||
|
DisplayConfigState.setNiriSetting(root.outputData, root.outputName, "layout", Object.keys(layout).length > 0 ? layout : null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const val = parseFloat(trimmed);
|
||||||
|
if (isNaN(val) || val <= 0 || val > 100)
|
||||||
|
return;
|
||||||
|
layout.defaultColumnWidth = {
|
||||||
|
"type": "proportion",
|
||||||
|
"value": parseFloat((val / 100).toFixed(6))
|
||||||
|
};
|
||||||
|
DisplayConfigState.setNiriSetting(root.outputData, root.outputName, "layout", layout);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
width: parent.width
|
||||||
|
spacing: Theme.spacingXS
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: I18n.tr("Preset Widths (%)")
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: Theme.surfaceVariantText
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: "e.g. 33.33, 50, 66.67"
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: Theme.withAlpha(Theme.surfaceVariantText, 0.7)
|
||||||
|
}
|
||||||
|
|
||||||
|
DankTextField {
|
||||||
|
width: parent.width
|
||||||
|
height: 40
|
||||||
|
placeholderText: I18n.tr("Inherit")
|
||||||
|
text: {
|
||||||
|
const layout = DisplayConfigState.getNiriSetting(root.outputData, root.outputName, "layout", null);
|
||||||
|
const presets = layout?.presetColumnWidths || [];
|
||||||
|
if (presets.length === 0)
|
||||||
|
return "";
|
||||||
|
return presets.filter(p => p.type === "proportion").map(p => parseFloat((p.value * 100).toFixed(4))).join(", ");
|
||||||
|
}
|
||||||
|
onEditingFinished: {
|
||||||
|
const layout = DisplayConfigState.getNiriSetting(root.outputData, root.outputName, "layout", {}) || {};
|
||||||
|
const trimmed = text.trim();
|
||||||
|
if (!trimmed) {
|
||||||
|
delete layout.presetColumnWidths;
|
||||||
|
DisplayConfigState.setNiriSetting(root.outputData, root.outputName, "layout", Object.keys(layout).length > 0 ? layout : null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const parts = trimmed.split(/[,\s]+/).filter(s => s);
|
||||||
|
const presets = [];
|
||||||
|
for (const part of parts) {
|
||||||
|
const val = parseFloat(part.replace("%", ""));
|
||||||
|
if (!isNaN(val) && val > 0 && val <= 100)
|
||||||
|
presets.push({
|
||||||
|
"type": "proportion",
|
||||||
|
"value": parseFloat((val / 100).toFixed(6))
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (presets.length === 0) {
|
||||||
|
delete layout.presetColumnWidths;
|
||||||
|
DisplayConfigState.setNiriSetting(root.outputData, root.outputName, "layout", Object.keys(layout).length > 0 ? layout : null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
presets.sort((a, b) => a.value - b.value);
|
||||||
|
layout.presetColumnWidths = presets;
|
||||||
|
DisplayConfigState.setNiriSetting(root.outputData, root.outputName, "layout", layout);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DankToggle {
|
||||||
|
width: parent.width
|
||||||
|
text: I18n.tr("Center Single Column")
|
||||||
|
property var layoutData: DisplayConfigState.getNiriSetting(root.outputData, root.outputName, "layout", null)
|
||||||
|
checked: layoutData?.alwaysCenterSingleColumn ?? false
|
||||||
|
onToggled: checked => {
|
||||||
|
const layout = DisplayConfigState.getNiriSetting(root.outputData, root.outputName, "layout", {}) || {};
|
||||||
|
if (checked) {
|
||||||
|
layout.alwaysCenterSingleColumn = true;
|
||||||
|
} else {
|
||||||
|
delete layout.alwaysCenterSingleColumn;
|
||||||
|
}
|
||||||
|
DisplayConfigState.setNiriSetting(root.outputData, root.outputName, "layout", Object.keys(layout).length > 0 ? layout : null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
import QtQuick
|
||||||
|
import qs.Common
|
||||||
|
import qs.Widgets
|
||||||
|
|
||||||
|
StyledRect {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
width: parent.width
|
||||||
|
height: messageContent.implicitHeight + Theme.spacingL * 2
|
||||||
|
radius: Theme.cornerRadius
|
||||||
|
color: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
|
||||||
|
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
|
||||||
|
border.width: 0
|
||||||
|
|
||||||
|
Column {
|
||||||
|
id: messageContent
|
||||||
|
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.surfaceVariantText
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
width: parent.width - Theme.iconSize - Theme.spacingM
|
||||||
|
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("Display configuration is not available. WLR output management protocol not supported.")
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: Theme.surfaceVariantText
|
||||||
|
wrapMode: Text.WordWrap
|
||||||
|
width: parent.width
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
278
quickshell/Modules/Settings/DisplayConfig/OutputCard.qml
Normal file
278
quickshell/Modules/Settings/DisplayConfig/OutputCard.qml
Normal file
@@ -0,0 +1,278 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -234,23 +234,82 @@ PanelWindow {
|
|||||||
anchors.margins: Theme.spacingS
|
anchors.margins: Theme.spacingS
|
||||||
spacing: Theme.spacingS
|
spacing: Theme.spacingS
|
||||||
|
|
||||||
StyledText {
|
Item {
|
||||||
id: detailsText
|
width: parent.width - Theme.spacingS * 2
|
||||||
text: ToastService.currentDetails
|
height: detailsText.implicitHeight
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
color: {
|
visible: ToastService.currentDetails.length > 0
|
||||||
switch (ToastService.currentLevel) {
|
|
||||||
case ToastService.levelError:
|
StyledText {
|
||||||
case ToastService.levelWarn:
|
id: detailsText
|
||||||
return SessionData.isLightMode ? Theme.surfaceText : Theme.background;
|
text: ToastService.currentDetails
|
||||||
default:
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
return Theme.surfaceText;
|
color: {
|
||||||
|
switch (ToastService.currentLevel) {
|
||||||
|
case ToastService.levelError:
|
||||||
|
case ToastService.levelWarn:
|
||||||
|
return SessionData.isLightMode ? Theme.surfaceText : Theme.background;
|
||||||
|
default:
|
||||||
|
return Theme.surfaceText;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.right: copyDetailsButton.left
|
||||||
|
anchors.rightMargin: Theme.spacingS
|
||||||
|
wrapMode: Text.Wrap
|
||||||
|
}
|
||||||
|
|
||||||
|
DankActionButton {
|
||||||
|
id: copyDetailsButton
|
||||||
|
iconName: "content_copy"
|
||||||
|
iconSize: Theme.iconSizeSmall
|
||||||
|
iconColor: {
|
||||||
|
switch (ToastService.currentLevel) {
|
||||||
|
case ToastService.levelError:
|
||||||
|
case ToastService.levelWarn:
|
||||||
|
return SessionData.isLightMode ? Theme.surfaceText : Theme.background;
|
||||||
|
default:
|
||||||
|
return Theme.surfaceText;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
buttonSize: Theme.iconSizeSmall + 8
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.top: parent.top
|
||||||
|
|
||||||
|
property bool showTooltip: false
|
||||||
|
|
||||||
|
onClicked: {
|
||||||
|
Quickshell.execDetached(["dms", "cl", "copy", ToastService.currentDetails]);
|
||||||
|
showTooltip = true;
|
||||||
|
detailsTooltipTimer.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
Timer {
|
||||||
|
id: detailsTooltipTimer
|
||||||
|
interval: 1500
|
||||||
|
onTriggered: copyDetailsButton.showTooltip = false
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
visible: copyDetailsButton.showTooltip
|
||||||
|
width: detailsTooltipLabel.implicitWidth + 16
|
||||||
|
height: detailsTooltipLabel.implicitHeight + 8
|
||||||
|
color: Theme.surfaceContainer
|
||||||
|
radius: Theme.cornerRadius
|
||||||
|
border.width: 1
|
||||||
|
border.color: Theme.outlineMedium
|
||||||
|
y: -height - 4
|
||||||
|
x: -width / 2 + copyDetailsButton.width / 2
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
id: detailsTooltipLabel
|
||||||
|
anchors.centerIn: parent
|
||||||
|
text: root.copiedText
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: Theme.surfaceText
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
visible: ToastService.currentDetails.length > 0
|
|
||||||
width: parent.width - Theme.spacingS * 2
|
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
|
||||||
wrapMode: Text.Wrap
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
|
|||||||
@@ -1236,13 +1236,19 @@ Singleton {
|
|||||||
let block = ` layout {\n`;
|
let block = ` layout {\n`;
|
||||||
if (layout.gaps !== undefined)
|
if (layout.gaps !== undefined)
|
||||||
block += ` gaps ${layout.gaps}\n`;
|
block += ` gaps ${layout.gaps}\n`;
|
||||||
if (layout.defaultColumnWidth?.type === "proportion")
|
if (layout.defaultColumnWidth?.type === "proportion") {
|
||||||
block += ` default-column-width { proportion ${layout.defaultColumnWidth.value}; }\n`;
|
const val = layout.defaultColumnWidth.value;
|
||||||
|
const formatted = Number.isInteger(val) ? val.toFixed(1) : val.toString();
|
||||||
|
block += ` default-column-width { proportion ${formatted}; }\n`;
|
||||||
|
}
|
||||||
if (layout.presetColumnWidths && layout.presetColumnWidths.length > 0) {
|
if (layout.presetColumnWidths && layout.presetColumnWidths.length > 0) {
|
||||||
block += ` preset-column-widths {\n`;
|
block += ` preset-column-widths {\n`;
|
||||||
for (const preset of layout.presetColumnWidths) {
|
for (const preset of layout.presetColumnWidths) {
|
||||||
if (preset.type === "proportion")
|
if (preset.type === "proportion") {
|
||||||
block += ` proportion ${preset.value}\n`;
|
const val = preset.value;
|
||||||
|
const formatted = Number.isInteger(val) ? val.toFixed(1) : val.toString();
|
||||||
|
block += ` proportion ${formatted}\n`;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
block += ` }\n`;
|
block += ` }\n`;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user