window rules: percent sizing for niri column width and height, explain float default

fixes #3687
This commit is contained in:
bbedward
2026-10-05 09:56:35 -04:00
parent d8113d2a02
commit 7eee3dcbe7
6 changed files with 106 additions and 13 deletions
+32
View File
@@ -0,0 +1,32 @@
.pragma library
const PERCENT = "%";
const PIXELS = "px";
function parse(value) {
const parts = String(value || "").trim().split(/\s+/);
if (parts[0] !== "proportion")
return {
unit: PIXELS,
amount: parts[parts.length - 1] || ""
};
const fraction = parseFloat(parts[1]);
return {
unit: PERCENT,
amount: isNaN(fraction) ? "" : String(Math.round(fraction * 10000) / 100)
};
}
function format(unit, amount) {
const number = parseFloat(amount);
if (isNaN(number) || number <= 0)
return "";
if (unit === PERCENT)
return "proportion " + Math.round(number * 100) / 10000;
return "fixed " + Math.round(number);
}
function label(value) {
const size = parse(value);
return size.amount + size.unit;
}
@@ -298,11 +298,13 @@ FocusScope {
width: transcript.width
height: vocals.implicitHeight
// Positioners relayout on polish, so the list would measure reused and rescaled rows at their old height.
Column {
id: vocals
width: parent.width
Repeater {
onItemAdded: vocals.forceLayout()
model: root.controller.synced ? lyric.modelData.parts : [
{
x: lyric.modelData,
@@ -326,6 +328,7 @@ FocusScope {
distance: lyric ? Math.abs(lyric.index - root.activeIndex) : 0
animationsEnabled: root.animationsEnabled
smoothHighlight: root.smoothHighlight
onImplicitHeightChanged: vocals.forceLayout()
inViewport: {
if (!lyric)
return false;
@@ -352,6 +352,7 @@ Column {
tags: ["floating", "window", "tile", "tiling", "compositor", "rule", "niri", "hyprland", "mango"]
settingKey: "dmsWindowsFloating"
text: I18n.tr("Open floating")
description: I18n.tr("Adds a compositor window rule so DMS windows such as Settings open floating", "theme floating windows section, open floating toggle description")
visible: windowRulesInclude.compositorSupported
checked: CompositorService.dmsWindowFloatingActive
modified: !checked
@@ -2,6 +2,7 @@ pragma ComponentBehavior: Bound
import QtQuick
import qs.Common
import "../../Common/WindowRuleSize.js" as WindowRuleSize
import qs.Services
import qs.Widgets
import qs.Modules.Settings.Widgets
@@ -36,6 +37,8 @@ DankDialog {
property string blockOutValue: ""
property string columnDisplayValue: ""
property string floatingRelativeValue: "top-left"
property string columnWidthUnit: WindowRuleSize.PIXELS
property string windowHeightUnit: WindowRuleSize.PIXELS
readonly property var triLabels: [I18n.tr("Default"), I18n.tr("On"), I18n.tr("Off")]
readonly property var blockOutOptions: ["", "screencast", "screen-capture"]
@@ -263,6 +266,8 @@ DankDialog {
blockOutValue = "";
columnDisplayValue = "";
floatingRelativeValue = "top-left";
columnWidthUnit = WindowRuleSize.PIXELS;
windowHeightUnit = WindowRuleSize.PIXELS;
blurCond.triState = 0;
xrayCond.triState = 0;
for (const field of [outputInput, workspaceInput, columnWidthInput, windowHeightInput, floatingXInput, floatingYInput, minWidthInput, maxWidthInput, minHeightInput, maxHeightInput, moveXInput, moveYInput, sizeWInput, sizeHInput, monitorInput, hyprWorkspaceInput, mangoTagsInput, mangoMonitorInput, mangoSizeInput])
@@ -326,8 +331,12 @@ DankDialog {
outputInput.text = actions.openOnOutput || "";
workspaceInput.text = actions.openOnWorkspace || "";
columnWidthInput.text = actions.defaultColumnWidth || "";
windowHeightInput.text = actions.defaultWindowHeight || "";
const columnWidth = WindowRuleSize.parse(actions.defaultColumnWidth);
const windowHeight = WindowRuleSize.parse(actions.defaultWindowHeight);
columnWidthUnit = columnWidth.unit;
windowHeightUnit = windowHeight.unit;
columnWidthInput.text = columnWidth.amount;
windowHeightInput.text = windowHeight.amount;
floatingXInput.text = (actions.defaultFloatingX !== undefined && actions.defaultFloatingX !== null) ? String(actions.defaultFloatingX) : "";
floatingYInput.text = (actions.defaultFloatingY !== undefined && actions.defaultFloatingY !== null) ? String(actions.defaultFloatingY) : "";
floatingRelativeValue = actions.defaultFloatingRelativeTo || "top-left";
@@ -422,10 +431,12 @@ DankDialog {
actions.maxHeight = maxH;
if (isNiri) {
if (columnWidthInput.text.trim())
actions.defaultColumnWidth = columnWidthInput.text.trim();
if (windowHeightInput.text.trim())
actions.defaultWindowHeight = windowHeightInput.text.trim();
const columnWidth = WindowRuleSize.format(columnWidthUnit, columnWidthInput.text);
const windowHeight = WindowRuleSize.format(windowHeightUnit, windowHeightInput.text);
if (columnWidth)
actions.defaultColumnWidth = columnWidth;
if (windowHeight)
actions.defaultWindowHeight = windowHeight;
if (dynamicFlags.includes("vrr"))
actions.variableRefreshRate = true;
if (dynamicFlags.includes("clip"))
@@ -606,6 +617,35 @@ DankDialog {
onAccepted: root.submit()
}
component SizeField: Field {
id: sizeField
property string unit: WindowRuleSize.PIXELS
property string pixelsPlaceholder: ""
signal unitSelected(string unit)
placeholderText: unit === WindowRuleSize.PERCENT ? "50" : pixelsPlaceholder
rightAccessoryWidth: unitPicker.width + Theme.spacingS
DankButtonGroup {
id: unitPicker
anchors.right: parent.right
anchors.rightMargin: Theme.spacingS
y: sizeField.containerTop + (sizeField.containerHeight - height) / 2
size: "small"
checkEnabled: false
arrowKeysSelect: false
enabled: sizeField.enabled
model: [I18n.tr("px"), WindowRuleSize.PERCENT]
currentIndex: sizeField.unit === WindowRuleSize.PERCENT ? 1 : 0
onSelectionChanged: (index, selected) => {
if (selected)
sizeField.unitSelected(index === 1 ? WindowRuleSize.PERCENT : WindowRuleSize.PIXELS);
}
}
}
readonly property var matchConditions: [condFloating, condActive, condFocused, condActiveInColumn, condCastTarget, condUrgent, condAtStartup, condXwayland, condFullscreen, condPinned, condInitialised]
actions: [
@@ -814,6 +854,7 @@ DankDialog {
SettingsButtonGroupRow {
visible: root.isNiri
text: I18n.tr("Float")
description: I18n.tr("Default leaves it to niri, which floats dialogs and fixed-size windows and tiles the rest", "window rule float option, explains the Default choice")
model: root.triLabels
currentIndex: root.floatingTri
enabled: root.fieldsEnabled
@@ -856,18 +897,22 @@ DankDialog {
FieldRow {
visible: root.isNiri
Field {
SizeField {
id: columnWidthInput
leftIconName: "width"
labelText: I18n.tr("Column Width")
placeholderText: "800"
pixelsPlaceholder: "800"
unit: root.columnWidthUnit
onUnitSelected: unit => root.columnWidthUnit = unit
}
Field {
SizeField {
id: windowHeightInput
leftIconName: "height"
labelText: I18n.tr("Window Height")
placeholderText: "600"
pixelsPlaceholder: "600"
unit: root.windowHeightUnit
onUnitSelected: unit => root.windowHeightUnit = unit
}
}
}
@@ -5,6 +5,7 @@ import Quickshell
import Quickshell.Wayland
import qs.Common
import "../../Common/ConfigIncludeResolve.js" as ConfigIncludeResolve
import "../../Common/WindowRuleSize.js" as WindowRuleSize
import qs.Services
import qs.Widgets
import qs.Modules.Settings.Widgets
@@ -73,6 +74,8 @@ Item {
const label = root.actionLabels[k] || k;
if (typeof a[k] === "boolean")
return a[k] ? label : label + ": " + I18n.tr("Off");
if (k === "defaultColumnWidth" || k === "defaultWindowHeight")
return label + ": " + WindowRuleSize.label(a[k]);
return label + ": " + a[k];
});
}
@@ -1042,14 +1042,18 @@
"tabIndex": -1,
"category": "Interface style",
"keywords": [
"adds",
"authentication",
"compositor",
"floating",
"foreground",
"interface",
"notepad",
"opacity",
"open",
"override",
"polkit",
"rule",
"settings",
"style",
"tile",
@@ -1058,7 +1062,8 @@
"window",
"windows"
],
"page": "theme_surfaces"
"page": "theme_surfaces",
"description": "Adds a compositor window rule so DMS windows such as Settings open floating"
},
{
"section": "focusRingEnabled",
@@ -1818,6 +1823,7 @@
"tabIndex": -1,
"category": "Interface style",
"keywords": [
"adds",
"compositor",
"floating",
"hyprland",
@@ -1826,12 +1832,15 @@
"niri",
"open",
"rule",
"settings",
"style",
"tile",
"tiling",
"window"
"window",
"windows"
],
"page": "theme_surfaces"
"page": "theme_surfaces",
"description": "Adds a compositor window rule so DMS windows such as Settings open floating"
},
{
"section": "notificationOverlayEnabled",