mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-06-08 04:09:15 -04:00
feat(Hyprland): add fractional scaling display presets
- Show Hyprland scale presets that fit the active mode - Preserve current dms setup values
This commit is contained in:
@@ -857,6 +857,8 @@ Singleton {
|
||||
Component.onCompleted: {
|
||||
outputs = buildOutputsMap();
|
||||
reloadSavedOutputs();
|
||||
if (CompositorService.isHyprland)
|
||||
checkIncludeStatus();
|
||||
}
|
||||
|
||||
function reloadSavedOutputs() {
|
||||
@@ -1424,6 +1426,11 @@ Singleton {
|
||||
showHyprlandReadOnlyWarning();
|
||||
return;
|
||||
}
|
||||
if (CompositorService.isHyprland && !HyprlandService.luaConfigActive) {
|
||||
showHyprlandReadOnlyWarning();
|
||||
checkIncludeStatus();
|
||||
return;
|
||||
}
|
||||
const paths = getConfigPaths();
|
||||
if (!paths)
|
||||
return;
|
||||
@@ -1440,9 +1447,25 @@ Singleton {
|
||||
});
|
||||
|
||||
Proc.runCommand("fix-outputs-include", ["sh", "-c", script], (output, exitCode) => {
|
||||
fixingInclude = false;
|
||||
if (exitCode !== 0)
|
||||
if (exitCode !== 0) {
|
||||
fixingInclude = false;
|
||||
return;
|
||||
}
|
||||
|
||||
const liveOutputs = buildOutputsMap();
|
||||
if (CompositorService.isHyprland && Object.keys(liveOutputs).length > 0) {
|
||||
outputs = liveOutputs;
|
||||
HyprlandService.generateOutputsConfig(liveOutputs, SettingsData.hyprlandOutputSettings, success => {
|
||||
fixingInclude = false;
|
||||
if (!success)
|
||||
ToastService.showError(I18n.tr("Display setup failed"), I18n.tr("Failed to write Hyprland outputs config."), "", "display-config");
|
||||
checkIncludeStatus();
|
||||
WlrOutputService.requestState();
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
fixingInclude = false;
|
||||
checkIncludeStatus();
|
||||
WlrOutputService.requestState();
|
||||
});
|
||||
@@ -2504,6 +2527,50 @@ Singleton {
|
||||
return mode.width + "x" + mode.height + "@" + (mode.refresh_rate / 1000).toFixed(3);
|
||||
}
|
||||
|
||||
function formatScaleLabel(scale) {
|
||||
const value = Number(scale);
|
||||
if (!isFinite(value))
|
||||
return "1";
|
||||
return parseFloat(value.toFixed(2)).toString();
|
||||
}
|
||||
|
||||
function getScalePresetValues(outputName, outputData) {
|
||||
if (!CompositorService.isHyprland)
|
||||
return [0.5, 0.75, 1, 1.25, 1.5, 1.75, 2, 2.5, 3];
|
||||
|
||||
const candidates = [0.5, 2 / 3, 0.75, 0.8, 1, 4 / 3, 1.6, 2, 2.5, 8 / 3, 3.2, 4];
|
||||
const mode = getModeForScalePresets(outputName, outputData);
|
||||
if (!mode)
|
||||
return candidates;
|
||||
|
||||
return candidates.filter(scale => scaleFitsMode(mode, scale));
|
||||
}
|
||||
|
||||
function getModeForScalePresets(outputName, outputData) {
|
||||
const pendingMode = getPendingValue(outputName, "mode");
|
||||
const modes = outputData?.modes || [];
|
||||
if (pendingMode) {
|
||||
for (const mode of modes) {
|
||||
if (formatMode(mode) === pendingMode)
|
||||
return mode;
|
||||
}
|
||||
}
|
||||
const currentMode = outputData?.current_mode;
|
||||
if (currentMode !== undefined && modes[currentMode])
|
||||
return modes[currentMode];
|
||||
return null;
|
||||
}
|
||||
|
||||
function scaleFitsMode(mode, scale) {
|
||||
const width = Number(mode?.width || 0);
|
||||
const height = Number(mode?.height || 0);
|
||||
if (width <= 0 || height <= 0 || scale <= 0)
|
||||
return false;
|
||||
const logicalWidth = width / scale;
|
||||
const logicalHeight = height / scale;
|
||||
return Math.abs(logicalWidth - Math.round(logicalWidth)) < 0.001 && Math.abs(logicalHeight - Math.round(logicalHeight)) < 0.001;
|
||||
}
|
||||
|
||||
function getTransformLabel(transform) {
|
||||
switch (transform) {
|
||||
case "Normal":
|
||||
|
||||
@@ -203,12 +203,40 @@ StyledRect {
|
||||
height: scaleDropdown.visible ? scaleDropdown.height : scaleInput.height
|
||||
|
||||
property bool customMode: false
|
||||
property string currentScale: {
|
||||
property real currentScaleValue: {
|
||||
const pendingScale = DisplayConfigState.getPendingValue(root.outputName, "scale");
|
||||
if (pendingScale !== undefined)
|
||||
return parseFloat(pendingScale.toFixed(2)).toString();
|
||||
const scale = root.outputData?.logical?.scale || 1.0;
|
||||
return parseFloat(scale.toFixed(2)).toString();
|
||||
return pendingScale;
|
||||
return root.outputData?.logical?.scale || 1.0;
|
||||
}
|
||||
property string currentScale: DisplayConfigState.formatScaleLabel(currentScaleValue)
|
||||
property var scaleOptionsData: {
|
||||
void (DisplayConfigState.pendingChanges);
|
||||
const customLabel = I18n.tr("Custom...");
|
||||
const values = DisplayConfigState.getScalePresetValues(root.outputName, root.outputData);
|
||||
const labels = [];
|
||||
const valueByLabel = {};
|
||||
|
||||
function addValue(value) {
|
||||
if (!isFinite(value) || value <= 0)
|
||||
return;
|
||||
const label = DisplayConfigState.formatScaleLabel(value);
|
||||
if (valueByLabel[label] !== undefined)
|
||||
return;
|
||||
valueByLabel[label] = value;
|
||||
labels.push(label);
|
||||
}
|
||||
|
||||
for (const value of values)
|
||||
addValue(value);
|
||||
addValue(scaleContainer.currentScaleValue);
|
||||
|
||||
labels.sort((a, b) => parseFloat(a) - parseFloat(b));
|
||||
labels.push(customLabel);
|
||||
return {
|
||||
"labels": labels,
|
||||
"valueByLabel": valueByLabel
|
||||
};
|
||||
}
|
||||
|
||||
DankDropdown {
|
||||
@@ -217,20 +245,7 @@ StyledRect {
|
||||
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);
|
||||
});
|
||||
}
|
||||
options: scaleContainer.scaleOptionsData.labels
|
||||
onValueChanged: value => {
|
||||
if (value === I18n.tr("Custom...")) {
|
||||
scaleContainer.customMode = true;
|
||||
@@ -239,7 +254,8 @@ StyledRect {
|
||||
scaleInput.selectAll();
|
||||
return;
|
||||
}
|
||||
DisplayConfigState.setPendingChange(root.outputName, "scale", parseFloat(value));
|
||||
const mapped = scaleContainer.scaleOptionsData.valueByLabel[value];
|
||||
DisplayConfigState.setPendingChange(root.outputName, "scale", mapped !== undefined ? mapped : parseFloat(value));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -248,7 +264,7 @@ StyledRect {
|
||||
width: parent.width
|
||||
height: 40
|
||||
visible: scaleContainer.customMode
|
||||
placeholderText: "0.5 - 4.0"
|
||||
placeholderText: "0.25 - 4.0"
|
||||
|
||||
function applyValue() {
|
||||
const val = parseFloat(text);
|
||||
@@ -257,7 +273,7 @@ StyledRect {
|
||||
scaleContainer.customMode = false;
|
||||
return;
|
||||
}
|
||||
DisplayConfigState.setPendingChange(root.outputName, "scale", parseFloat(val.toFixed(2)));
|
||||
DisplayConfigState.setPendingChange(root.outputName, "scale", parseFloat(val.toFixed(6)));
|
||||
scaleContainer.customMode = false;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user