mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-10-14 23:34:01 -04:00
1204 lines
41 KiB
QML
1204 lines
41 KiB
QML
pragma ComponentBehavior: Bound
|
|
|
|
import QtQuick
|
|
import qs.Common
|
|
import qs.Services
|
|
import qs.Widgets
|
|
import qs.Modules.Settings.Widgets
|
|
|
|
DankDialog {
|
|
id: root
|
|
|
|
property var editingRule: null
|
|
property var targetWindow: null
|
|
property bool submitting: false
|
|
readonly property bool isEditMode: editingRule !== null
|
|
readonly property bool isNiri: CompositorService.isNiri
|
|
readonly property bool isHyprland: CompositorService.isHyprland
|
|
readonly property bool isMango: CompositorService.isMango
|
|
readonly property bool fieldsEnabled: !submitting
|
|
|
|
property int floatingTri: 0
|
|
property var openingFlags: []
|
|
property var dynamicFlags: []
|
|
property var hyprFlags: []
|
|
property var mangoFlags: []
|
|
property bool opacityOn: false
|
|
property int opacityValue: 100
|
|
property bool scrollFactorOn: false
|
|
property int scrollFactorValue: 100
|
|
property bool cornerRadiusOn: false
|
|
property int cornerRadiusValue: 12
|
|
property bool noiseOn: false
|
|
property int noiseValue: 5
|
|
property bool saturationOn: false
|
|
property int saturationValue: 100
|
|
property string blockOutValue: ""
|
|
property string columnDisplayValue: ""
|
|
property string floatingRelativeValue: "top-left"
|
|
|
|
readonly property var triLabels: [I18n.tr("Default"), I18n.tr("On"), I18n.tr("Off")]
|
|
readonly property var blockOutOptions: ["", "screencast", "screen-capture"]
|
|
readonly property var columnDisplayOptions: ["", "tabbed"]
|
|
readonly property var anchorOptions: ["top-left", "top-right", "bottom-left", "bottom-right", "top", "bottom", "left", "right"]
|
|
readonly property var openingOptions: {
|
|
const options = [];
|
|
if (!isNiri)
|
|
options.push({
|
|
"label": I18n.tr("Float"),
|
|
"value": "float"
|
|
});
|
|
if (!isMango)
|
|
options.push({
|
|
"label": I18n.tr("Maximize", "verb, window rule action to open the window maximized"),
|
|
"value": "maximize"
|
|
});
|
|
options.push({
|
|
"label": I18n.tr("Fullscreen", "window rule action, open the window fullscreen"),
|
|
"value": "fullscreen"
|
|
});
|
|
if (isNiri) {
|
|
options.push({
|
|
"label": I18n.tr("Max edges", "window rule action, open maximized to screen edges"),
|
|
"value": "maxEdges"
|
|
});
|
|
options.push({
|
|
"label": I18n.tr("Focus", "verb, window rule action to focus the window when it opens"),
|
|
"value": "focus"
|
|
});
|
|
}
|
|
return options;
|
|
}
|
|
readonly property var dynamicOptions: [
|
|
{
|
|
"label": I18n.tr("VRR On-Demand"),
|
|
"value": "vrr"
|
|
},
|
|
{
|
|
"label": I18n.tr("Clip to Geometry"),
|
|
"value": "clip"
|
|
},
|
|
{
|
|
"label": I18n.tr("Tiled State"),
|
|
"value": "tiled"
|
|
},
|
|
{
|
|
"label": I18n.tr("Border with Background"),
|
|
"value": "borderBg"
|
|
}
|
|
]
|
|
readonly property var hyprOptions: [
|
|
{
|
|
"label": I18n.tr("Tile"),
|
|
"value": "tile"
|
|
},
|
|
{
|
|
"label": I18n.tr("No focus"),
|
|
"value": "nofocus"
|
|
},
|
|
{
|
|
"label": I18n.tr("No border"),
|
|
"value": "noborder"
|
|
},
|
|
{
|
|
"label": I18n.tr("No shadow"),
|
|
"value": "noshadow"
|
|
},
|
|
{
|
|
"label": I18n.tr("No dim"),
|
|
"value": "nodim"
|
|
},
|
|
{
|
|
"label": I18n.tr("No blur"),
|
|
"value": "noblur"
|
|
},
|
|
{
|
|
"label": I18n.tr("No anim"),
|
|
"value": "noanim"
|
|
},
|
|
{
|
|
"label": I18n.tr("No Rounding"),
|
|
"value": "norounding"
|
|
},
|
|
{
|
|
"label": I18n.tr("Pin", "verb, keep an item pinned in place"),
|
|
"value": "pin"
|
|
},
|
|
{
|
|
"label": I18n.tr("Opaque", "adjective, window rule checkbox forcing an opaque window"),
|
|
"value": "opaque"
|
|
}
|
|
]
|
|
readonly property var mangoOptions: [
|
|
{
|
|
"label": I18n.tr("No blur"),
|
|
"value": "noblur"
|
|
},
|
|
{
|
|
"label": I18n.tr("No border"),
|
|
"value": "noborder"
|
|
},
|
|
{
|
|
"label": I18n.tr("No shadow"),
|
|
"value": "noshadow"
|
|
},
|
|
{
|
|
"label": I18n.tr("No Rounding"),
|
|
"value": "norounding"
|
|
},
|
|
{
|
|
"label": I18n.tr("No anim"),
|
|
"value": "noanim"
|
|
}
|
|
]
|
|
|
|
signal ruleSubmitted
|
|
|
|
embedded: nativeWindow
|
|
opened: nativeWindow
|
|
maximumWidth: SettingsMetrics.formDialogWidth
|
|
surfaceColor: Theme.hostSurface
|
|
title: isEditMode ? I18n.tr("Edit Window Rule") : I18n.tr("New Window Rule")
|
|
closeEnabled: !submitting
|
|
acceptEnabled: !submitting
|
|
onAccepted: submit()
|
|
|
|
ListModel {
|
|
id: extraMatchModel
|
|
}
|
|
|
|
function show(window) {
|
|
editingRule = null;
|
|
targetWindow = window || null;
|
|
resetForm();
|
|
if (targetWindow?.appId) {
|
|
nameInput.text = targetWindow.appId;
|
|
appIdInput.text = isMango ? targetWindow.appId : "^" + targetWindow.appId + "$";
|
|
}
|
|
present();
|
|
}
|
|
|
|
function showEdit(rule) {
|
|
if (!rule) {
|
|
show();
|
|
return;
|
|
}
|
|
editingRule = rule;
|
|
targetWindow = null;
|
|
resetForm();
|
|
populateForm(rule);
|
|
present();
|
|
}
|
|
|
|
function showCopy(rule) {
|
|
if (!rule) {
|
|
show();
|
|
return;
|
|
}
|
|
editingRule = null;
|
|
targetWindow = null;
|
|
resetForm();
|
|
populateForm(rule);
|
|
present();
|
|
}
|
|
|
|
function present() {
|
|
opened = true;
|
|
forceActiveFocus();
|
|
nameInput.forceActiveFocus();
|
|
}
|
|
|
|
function flagSet(flags, value, checked) {
|
|
const next = flags.filter(flag => flag !== value);
|
|
if (checked)
|
|
next.push(value);
|
|
return next;
|
|
}
|
|
|
|
function triFromBool(value) {
|
|
if (value === true)
|
|
return 1;
|
|
if (value === false)
|
|
return 2;
|
|
return 0;
|
|
}
|
|
|
|
function applyCond(target, key, triState) {
|
|
if (triState === 1)
|
|
target[key] = true;
|
|
else if (triState === 2)
|
|
target[key] = false;
|
|
}
|
|
|
|
function blockOutLabel(value) {
|
|
return value === "" ? I18n.tr("None") : value;
|
|
}
|
|
|
|
function columnDisplayLabel(value) {
|
|
return value === "" ? I18n.tr("Normal") : value;
|
|
}
|
|
|
|
function resetForm() {
|
|
nameInput.text = "";
|
|
appIdInput.text = "";
|
|
titleInput.text = "";
|
|
extraMatchModel.clear();
|
|
for (const cond of matchConditions)
|
|
cond.triState = 0;
|
|
floatingTri = 0;
|
|
openingFlags = [];
|
|
dynamicFlags = [];
|
|
hyprFlags = [];
|
|
mangoFlags = [];
|
|
opacityOn = false;
|
|
opacityValue = 100;
|
|
scrollFactorOn = false;
|
|
scrollFactorValue = 100;
|
|
cornerRadiusOn = false;
|
|
cornerRadiusValue = 12;
|
|
noiseOn = false;
|
|
noiseValue = 5;
|
|
saturationOn = false;
|
|
saturationValue = 100;
|
|
blockOutValue = "";
|
|
columnDisplayValue = "";
|
|
floatingRelativeValue = "top-left";
|
|
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])
|
|
field.text = "";
|
|
}
|
|
|
|
function populateForm(rule) {
|
|
nameInput.text = rule.name || "";
|
|
const matchList = (rule.matches && rule.matches.length > 0) ? rule.matches : [rule.matchCriteria || {}];
|
|
const match = matchList[0] || {};
|
|
appIdInput.text = match.appId || "";
|
|
titleInput.text = match.title || "";
|
|
for (let i = 1; i < matchList.length; i++)
|
|
extraMatchModel.append({
|
|
"rowAppId": matchList[i].appId || "",
|
|
"rowTitle": matchList[i].title || ""
|
|
});
|
|
for (const cond of matchConditions)
|
|
cond.triState = triFromBool(match[cond.key]);
|
|
|
|
const actions = rule.actions || {};
|
|
const flags = [];
|
|
if (!isNiri && actions.openFloating)
|
|
flags.push("float");
|
|
if (actions.openMaximized)
|
|
flags.push("maximize");
|
|
if (actions.openFullscreen)
|
|
flags.push("fullscreen");
|
|
if (actions.openMaximizedToEdges)
|
|
flags.push("maxEdges");
|
|
if (actions.openFocused)
|
|
flags.push("focus");
|
|
openingFlags = flags;
|
|
floatingTri = triFromBool(actions.openFloating);
|
|
|
|
const dynamic = [];
|
|
if (actions.variableRefreshRate)
|
|
dynamic.push("vrr");
|
|
if (actions.clipToGeometry)
|
|
dynamic.push("clip");
|
|
if (actions.tiledState)
|
|
dynamic.push("tiled");
|
|
if (actions.drawBorderWithBackground)
|
|
dynamic.push("borderBg");
|
|
dynamicFlags = dynamic;
|
|
|
|
opacityOn = actions.opacity !== undefined && actions.opacity !== null;
|
|
opacityValue = opacityOn ? Math.round(actions.opacity * 100) : 100;
|
|
scrollFactorOn = actions.scrollFactor !== undefined && actions.scrollFactor !== null;
|
|
scrollFactorValue = scrollFactorOn ? Math.round(actions.scrollFactor * 100) : 100;
|
|
cornerRadiusOn = actions.cornerRadius !== undefined && actions.cornerRadius !== null;
|
|
cornerRadiusValue = cornerRadiusOn ? actions.cornerRadius : 12;
|
|
noiseOn = actions.backgroundNoise !== undefined && actions.backgroundNoise !== null;
|
|
noiseValue = noiseOn ? Math.round(actions.backgroundNoise * 100) : 5;
|
|
saturationOn = actions.backgroundSaturation !== undefined && actions.backgroundSaturation !== null;
|
|
saturationValue = saturationOn ? Math.round(actions.backgroundSaturation * 100) : 100;
|
|
blockOutValue = actions.blockOutFrom || "";
|
|
columnDisplayValue = actions.defaultColumnDisplay || "";
|
|
blurCond.triState = triFromBool(actions.backgroundBlur);
|
|
xrayCond.triState = triFromBool(actions.backgroundXray);
|
|
|
|
outputInput.text = actions.openOnOutput || "";
|
|
workspaceInput.text = actions.openOnWorkspace || "";
|
|
columnWidthInput.text = actions.defaultColumnWidth || "";
|
|
windowHeightInput.text = actions.defaultWindowHeight || "";
|
|
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";
|
|
minWidthInput.text = actions.minWidth !== undefined ? String(actions.minWidth) : "";
|
|
maxWidthInput.text = actions.maxWidth !== undefined ? String(actions.maxWidth) : "";
|
|
minHeightInput.text = actions.minHeight !== undefined ? String(actions.minHeight) : "";
|
|
maxHeightInput.text = actions.maxHeight !== undefined ? String(actions.maxHeight) : "";
|
|
|
|
hyprFlags = hyprOptions.map(option => option.value).filter(value => actions[value]);
|
|
moveXInput.text = actions.moveX || "";
|
|
moveYInput.text = actions.moveY || "";
|
|
sizeWInput.text = actions.sizeWidth || "";
|
|
sizeHInput.text = actions.sizeHeight || "";
|
|
monitorInput.text = actions.monitor || "";
|
|
hyprWorkspaceInput.text = actions.workspace || "";
|
|
|
|
mangoFlags = mangoOptions.map(option => option.value).filter(value => actions[value]);
|
|
mangoTagsInput.text = actions.workspace || "";
|
|
mangoMonitorInput.text = actions.monitor || "";
|
|
mangoSizeInput.text = (actions.sizeWidth && actions.sizeHeight) ? actions.sizeWidth + "x" + actions.sizeHeight : "";
|
|
}
|
|
|
|
function collectMatches() {
|
|
const matchCriteria = {};
|
|
if (appIdInput.text.trim())
|
|
matchCriteria.appId = appIdInput.text.trim();
|
|
if (titleInput.text.trim())
|
|
matchCriteria.title = titleInput.text.trim();
|
|
for (const cond of matchConditions) {
|
|
if (cond.visible)
|
|
applyCond(matchCriteria, cond.key, cond.triState);
|
|
}
|
|
const matches = [];
|
|
if (Object.keys(matchCriteria).length > 0)
|
|
matches.push(matchCriteria);
|
|
if (!isNiri)
|
|
return {
|
|
matchCriteria,
|
|
matches
|
|
};
|
|
for (let i = 0; i < extraMatchModel.count; i++) {
|
|
const row = extraMatchModel.get(i);
|
|
const extra = {};
|
|
if ((row.rowAppId || "").trim())
|
|
extra.appId = row.rowAppId.trim();
|
|
if ((row.rowTitle || "").trim())
|
|
extra.title = row.rowTitle.trim();
|
|
if (Object.keys(extra).length > 0)
|
|
matches.push(extra);
|
|
}
|
|
return {
|
|
matchCriteria,
|
|
matches
|
|
};
|
|
}
|
|
|
|
function collectActions() {
|
|
const actions = {};
|
|
const has = value => openingFlags.includes(value);
|
|
if (opacityOn)
|
|
actions.opacity = opacityValue / 100;
|
|
if (isNiri)
|
|
applyCond(actions, "openFloating", floatingTri);
|
|
else if (has("float"))
|
|
actions.openFloating = true;
|
|
if (has("maximize"))
|
|
actions.openMaximized = true;
|
|
if (has("maxEdges") && isNiri)
|
|
actions.openMaximizedToEdges = true;
|
|
if (has("fullscreen"))
|
|
actions.openFullscreen = true;
|
|
if (has("focus") && isNiri)
|
|
actions.openFocused = true;
|
|
if (outputInput.text.trim())
|
|
actions.openOnOutput = outputInput.text.trim();
|
|
if (workspaceInput.text.trim())
|
|
actions.openOnWorkspace = workspaceInput.text.trim();
|
|
if (cornerRadiusOn)
|
|
actions.cornerRadius = cornerRadiusValue;
|
|
|
|
const minW = parseInt(minWidthInput.text);
|
|
const maxW = parseInt(maxWidthInput.text);
|
|
const minH = parseInt(minHeightInput.text);
|
|
const maxH = parseInt(maxHeightInput.text);
|
|
if (!isNaN(minW))
|
|
actions.minWidth = minW;
|
|
if (!isNaN(maxW))
|
|
actions.maxWidth = maxW;
|
|
if (!isNaN(minH))
|
|
actions.minHeight = minH;
|
|
if (!isNaN(maxH))
|
|
actions.maxHeight = maxH;
|
|
|
|
if (isNiri) {
|
|
if (columnWidthInput.text.trim())
|
|
actions.defaultColumnWidth = columnWidthInput.text.trim();
|
|
if (windowHeightInput.text.trim())
|
|
actions.defaultWindowHeight = windowHeightInput.text.trim();
|
|
if (dynamicFlags.includes("vrr"))
|
|
actions.variableRefreshRate = true;
|
|
if (dynamicFlags.includes("clip"))
|
|
actions.clipToGeometry = true;
|
|
if (dynamicFlags.includes("tiled"))
|
|
actions.tiledState = true;
|
|
if (dynamicFlags.includes("borderBg"))
|
|
actions.drawBorderWithBackground = true;
|
|
if (blockOutValue)
|
|
actions.blockOutFrom = blockOutValue;
|
|
if (columnDisplayValue)
|
|
actions.defaultColumnDisplay = columnDisplayValue;
|
|
if (scrollFactorOn)
|
|
actions.scrollFactor = scrollFactorValue / 100;
|
|
applyCond(actions, "backgroundBlur", blurCond.triState);
|
|
applyCond(actions, "backgroundXray", xrayCond.triState);
|
|
if (noiseOn)
|
|
actions.backgroundNoise = noiseValue / 100;
|
|
if (saturationOn)
|
|
actions.backgroundSaturation = saturationValue / 100;
|
|
const floatX = parseInt(floatingXInput.text);
|
|
const floatY = parseInt(floatingYInput.text);
|
|
if (!isNaN(floatX) && !isNaN(floatY)) {
|
|
actions.defaultFloatingX = floatX;
|
|
actions.defaultFloatingY = floatY;
|
|
if (floatingRelativeValue !== "top-left")
|
|
actions.defaultFloatingRelativeTo = floatingRelativeValue;
|
|
}
|
|
}
|
|
|
|
if (isHyprland) {
|
|
for (const flag of hyprFlags)
|
|
actions[flag] = true;
|
|
if (sizeWInput.text.trim())
|
|
actions.sizeWidth = sizeWInput.text.trim();
|
|
if (sizeHInput.text.trim())
|
|
actions.sizeHeight = sizeHInput.text.trim();
|
|
if (moveXInput.text.trim())
|
|
actions.moveX = moveXInput.text.trim();
|
|
if (moveYInput.text.trim())
|
|
actions.moveY = moveYInput.text.trim();
|
|
if (monitorInput.text.trim())
|
|
actions.monitor = monitorInput.text.trim();
|
|
if (hyprWorkspaceInput.text.trim())
|
|
actions.workspace = hyprWorkspaceInput.text.trim();
|
|
}
|
|
|
|
if (isMango) {
|
|
for (const flag of mangoFlags)
|
|
actions[flag] = true;
|
|
if (mangoTagsInput.text.trim())
|
|
actions.workspace = mangoTagsInput.text.trim();
|
|
if (mangoMonitorInput.text.trim())
|
|
actions.monitor = mangoMonitorInput.text.trim();
|
|
const parts = mangoSizeInput.text.trim().split(/x/i);
|
|
if (mangoSizeInput.text.trim() && parts.length === 2) {
|
|
actions.sizeWidth = parts[0].trim();
|
|
actions.sizeHeight = parts[1].trim();
|
|
}
|
|
}
|
|
return actions;
|
|
}
|
|
|
|
function submit() {
|
|
if (submitting)
|
|
return;
|
|
const {
|
|
matchCriteria,
|
|
matches
|
|
} = collectMatches();
|
|
const ruleData = {
|
|
name: nameInput.text.trim() || matchCriteria.appId || I18n.tr("Rule", "noun, fallback name for an unnamed window rule"),
|
|
matchCriteria: matchCriteria,
|
|
actions: collectActions(),
|
|
enabled: true
|
|
};
|
|
if (isNiri && extraMatchModel.count > 0)
|
|
ruleData.matches = matches;
|
|
// No exclude editor yet; carry existing excludes through so an edit doesn't delete them (#2996)
|
|
if (isEditMode && (editingRule.excludes?.length ?? 0) > 0)
|
|
ruleData.excludes = editingRule.excludes;
|
|
|
|
const compositor = CompositorService.compositor;
|
|
const ruleJson = JSON.stringify(ruleData);
|
|
const args = isEditMode ? [Proc.dmsBin, "config", "windowrules", "update", compositor, editingRule.id, ruleJson] : [Proc.dmsBin, "config", "windowrules", "add", compositor, ruleJson];
|
|
submitting = true;
|
|
Proc.runCommand(isEditMode ? "update-windowrule" : "add-windowrule", args, (output, exitCode) => {
|
|
root.submitting = false;
|
|
if (exitCode !== 0)
|
|
return;
|
|
if (CompositorService.isNiri)
|
|
NiriService.validate();
|
|
if (CompositorService.isMango)
|
|
MangoService.reloadConfig();
|
|
root.ruleSubmitted();
|
|
});
|
|
}
|
|
|
|
component MatchCond: StyledButton {
|
|
id: mc
|
|
|
|
property string label: ""
|
|
property string key: ""
|
|
property int triState: 0
|
|
property string unsetLabel: I18n.tr("Default")
|
|
readonly property var stateText: [mc.unsetLabel, "true", "false"]
|
|
readonly property var stateColor: [Theme.surfaceVariantText, Theme.primary, Theme.error]
|
|
|
|
width: condRow.implicitWidth + Theme.spacingM * 2
|
|
height: Theme.buttonHeightXS
|
|
radius: Theme.cornerRadiusS
|
|
color: enabled ? Theme.chipSurface : Theme.onSurface_12
|
|
border.width: Theme.outlineWidth
|
|
border.color: !enabled ? "transparent" : mc.triState === 0 ? Theme.outlineVariant : mc.stateColor[mc.triState]
|
|
enabled: root.fieldsEnabled
|
|
Accessible.role: Accessible.Button
|
|
Accessible.name: label + ": " + stateText[triState]
|
|
onClicked: triState = (triState + 1) % 3
|
|
|
|
Row {
|
|
id: condRow
|
|
anchors.centerIn: parent
|
|
spacing: Theme.spacingXS
|
|
|
|
StyledText {
|
|
text: mc.label
|
|
font.pixelSize: Theme.fontSizeSmall
|
|
color: mc.enabled ? Theme.onSurface : Theme.onSurface_38
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
}
|
|
|
|
Rectangle {
|
|
width: stateBadge.implicitWidth + Theme.spacingS * 2
|
|
height: stateBadge.implicitHeight + Theme.spacingXXS * 2
|
|
radius: Theme.fullRadius(width, height)
|
|
color: mc.enabled ? Theme.withAlpha(mc.stateColor[mc.triState], Theme.stateLayerPressed) : "transparent"
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
|
|
StyledText {
|
|
id: stateBadge
|
|
anchors.centerIn: parent
|
|
text: mc.stateText[mc.triState]
|
|
font.pixelSize: Theme.fontSizeSmall
|
|
font.weight: Theme.fontWeightMedium
|
|
color: mc.enabled ? mc.stateColor[mc.triState] : Theme.onSurface_38
|
|
}
|
|
}
|
|
}
|
|
|
|
StateLayer {
|
|
control: mc
|
|
disabled: !mc.enabled
|
|
stateColor: Theme.primary
|
|
}
|
|
|
|
FocusRing {
|
|
visible: mc.visualFocus
|
|
}
|
|
}
|
|
|
|
component FieldRow: SettingsRow {
|
|
default property alias fields: fieldRow.data
|
|
|
|
body: Row {
|
|
id: fieldRow
|
|
width: parent.width
|
|
spacing: Theme.spacingS
|
|
}
|
|
}
|
|
|
|
component Field: DankTextField {
|
|
property int share: 1
|
|
property int shares: 2
|
|
|
|
width: Math.round((parent.width - parent.spacing * (shares - 1)) * share / shares)
|
|
outlined: true
|
|
enabled: root.fieldsEnabled
|
|
onAccepted: root.submit()
|
|
}
|
|
|
|
readonly property var matchConditions: [condFloating, condActive, condFocused, condActiveInColumn, condCastTarget, condUrgent, condAtStartup, condXwayland, condFullscreen, condPinned, condInitialised]
|
|
|
|
actions: [
|
|
DankButton {
|
|
text: I18n.tr("Cancel")
|
|
enabled: root.closeEnabled
|
|
backgroundColor: "transparent"
|
|
textColor: Theme.primary
|
|
onClicked: root.rejected()
|
|
},
|
|
DankButton {
|
|
text: root.isEditMode ? I18n.tr("Save") : I18n.tr("Add")
|
|
enabled: root.acceptEnabled
|
|
busy: root.submitting
|
|
onClicked: root.submit()
|
|
}
|
|
]
|
|
|
|
SettingsGroup {
|
|
SettingsRow {
|
|
body: DankTextField {
|
|
id: nameInput
|
|
width: parent.width
|
|
outlined: true
|
|
leftIconName: "edit"
|
|
labelText: I18n.tr("Rule Name")
|
|
enabled: root.fieldsEnabled
|
|
onAccepted: root.submit()
|
|
}
|
|
}
|
|
}
|
|
|
|
SettingsCard {
|
|
title: I18n.tr("Match Criteria")
|
|
headerActions: DankButton {
|
|
visible: root.isNiri
|
|
text: I18n.tr("Add match")
|
|
iconName: "add"
|
|
buttonHeight: Theme.buttonHeightXS
|
|
backgroundColor: "transparent"
|
|
textColor: Theme.primary
|
|
enabled: root.fieldsEnabled
|
|
onClicked: extraMatchModel.append({
|
|
"rowAppId": "",
|
|
"rowTitle": ""
|
|
})
|
|
}
|
|
|
|
SettingsRow {
|
|
visible: root.isNiri
|
|
subtitle: I18n.tr("The rule applies to any window matching one of these.")
|
|
}
|
|
|
|
SettingsRow {
|
|
body: DankTextField {
|
|
id: appIdInput
|
|
width: parent.width
|
|
outlined: true
|
|
leftIconName: "apps"
|
|
labelText: root.isMango ? I18n.tr("App ID (e.g. firefox)") : root.isHyprland ? I18n.tr("Class regex (e.g. ^firefox$)") : I18n.tr("App ID regex (e.g. ^firefox$)")
|
|
enabled: root.fieldsEnabled
|
|
onAccepted: root.submit()
|
|
}
|
|
}
|
|
|
|
FieldRow {
|
|
Field {
|
|
id: titleInput
|
|
width: parent.width - (addTitle.visible ? addTitle.width + parent.spacing : 0)
|
|
leftIconName: "title"
|
|
labelText: root.isMango ? I18n.tr("Title (optional)") : I18n.tr("Title regex (optional)")
|
|
}
|
|
|
|
DankActionButton {
|
|
id: addTitle
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
iconName: "add"
|
|
visible: !root.isEditMode && !!root.targetWindow?.title
|
|
tooltipText: I18n.tr("Add Title")
|
|
onClicked: titleInput.text = root.isMango ? root.targetWindow.title : "^" + root.targetWindow.title + "$"
|
|
}
|
|
}
|
|
|
|
Repeater {
|
|
model: extraMatchModel
|
|
|
|
delegate: FieldRow {
|
|
id: extraRow
|
|
|
|
required property int index
|
|
required property string rowAppId
|
|
required property string rowTitle
|
|
|
|
Field {
|
|
width: Math.round((parent.width - parent.spacing * 2 - removeMatch.width) / 2)
|
|
leftIconName: "apps"
|
|
labelText: I18n.tr("App ID regex")
|
|
text: extraRow.rowAppId
|
|
onTextEdited: extraMatchModel.setProperty(extraRow.index, "rowAppId", text)
|
|
}
|
|
|
|
Field {
|
|
width: Math.round((parent.width - parent.spacing * 2 - removeMatch.width) / 2)
|
|
leftIconName: "title"
|
|
labelText: I18n.tr("Title regex (optional)")
|
|
text: extraRow.rowTitle
|
|
onTextEdited: extraMatchModel.setProperty(extraRow.index, "rowTitle", text)
|
|
}
|
|
|
|
DankActionButton {
|
|
id: removeMatch
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
iconName: "close"
|
|
Accessible.name: I18n.tr("Remove match")
|
|
onClicked: extraMatchModel.remove(extraRow.index)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
SettingsCard {
|
|
title: I18n.tr("Match Conditions")
|
|
visible: root.isNiri || root.isHyprland
|
|
|
|
SettingsRow {
|
|
subtitle: I18n.tr("Optional state-based conditions applied to the first match.")
|
|
}
|
|
|
|
SettingsRow {
|
|
body: Flow {
|
|
width: parent.width
|
|
spacing: Theme.spacingS
|
|
|
|
MatchCond {
|
|
id: condFloating
|
|
key: "isFloating"
|
|
label: I18n.tr("Floating", "adjective, window rule match condition for floating windows")
|
|
}
|
|
MatchCond {
|
|
id: condActive
|
|
key: "isActive"
|
|
label: I18n.tr("Active")
|
|
visible: root.isNiri
|
|
}
|
|
MatchCond {
|
|
id: condFocused
|
|
key: "isFocused"
|
|
label: I18n.tr("Focused", "adjective, window rule match condition for the focused window")
|
|
visible: root.isNiri
|
|
}
|
|
MatchCond {
|
|
id: condActiveInColumn
|
|
key: "isActiveInColumn"
|
|
label: I18n.tr("Active in column")
|
|
visible: root.isNiri
|
|
}
|
|
MatchCond {
|
|
id: condCastTarget
|
|
key: "isWindowCastTarget"
|
|
label: I18n.tr("Cast target")
|
|
visible: root.isNiri
|
|
}
|
|
MatchCond {
|
|
id: condUrgent
|
|
key: "isUrgent"
|
|
label: I18n.tr("Urgent", "adjective, window rule match condition for windows requesting attention")
|
|
visible: root.isNiri
|
|
}
|
|
MatchCond {
|
|
id: condAtStartup
|
|
key: "atStartup"
|
|
label: I18n.tr("At startup")
|
|
visible: root.isNiri
|
|
}
|
|
MatchCond {
|
|
id: condXwayland
|
|
key: "xwayland"
|
|
label: "XWayland"
|
|
visible: root.isHyprland
|
|
}
|
|
MatchCond {
|
|
id: condFullscreen
|
|
key: "fullscreen"
|
|
label: I18n.tr("Fullscreen", "adjective, window rule match condition for fullscreen windows")
|
|
visible: root.isHyprland
|
|
}
|
|
MatchCond {
|
|
id: condPinned
|
|
key: "pinned"
|
|
label: I18n.tr("Pinned", "adjective, state of a pinned window, clipboard entry or item")
|
|
visible: root.isHyprland
|
|
}
|
|
MatchCond {
|
|
id: condInitialised
|
|
key: "initialised"
|
|
label: I18n.tr("Initialised", "adjective, hyprland window rule match condition")
|
|
visible: root.isHyprland
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
SettingsCard {
|
|
title: I18n.tr("Window Opening")
|
|
|
|
SettingsButtonGroupRow {
|
|
visible: root.isNiri
|
|
text: I18n.tr("Float")
|
|
model: root.triLabels
|
|
currentIndex: root.floatingTri
|
|
enabled: root.fieldsEnabled
|
|
onSelectionChanged: (index, selected) => {
|
|
if (selected)
|
|
root.floatingTri = index;
|
|
}
|
|
}
|
|
|
|
SettingsRow {
|
|
body: DankFilterChips {
|
|
width: parent.width
|
|
multiSelect: true
|
|
showCounts: false
|
|
enabled: root.fieldsEnabled
|
|
model: root.openingOptions
|
|
selectedValues: root.openingFlags
|
|
onSelectionToggled: (index, selected) => root.openingFlags = root.flagSet(root.openingFlags, root.openingOptions[index].value, selected)
|
|
}
|
|
}
|
|
|
|
FieldRow {
|
|
visible: root.isNiri || root.isHyprland
|
|
|
|
Field {
|
|
id: outputInput
|
|
leftIconName: "monitor"
|
|
labelText: I18n.tr("Output", "noun, display output a window opens on, window rule field")
|
|
placeholderText: "HDMI-A-1"
|
|
}
|
|
|
|
Field {
|
|
id: workspaceInput
|
|
leftIconName: "view_module"
|
|
labelText: I18n.tr("Workspace")
|
|
placeholderText: "chat"
|
|
}
|
|
}
|
|
|
|
FieldRow {
|
|
visible: root.isNiri
|
|
|
|
Field {
|
|
id: columnWidthInput
|
|
leftIconName: "width"
|
|
labelText: I18n.tr("Column Width")
|
|
placeholderText: "800"
|
|
}
|
|
|
|
Field {
|
|
id: windowHeightInput
|
|
leftIconName: "height"
|
|
labelText: I18n.tr("Window Height")
|
|
placeholderText: "600"
|
|
}
|
|
}
|
|
}
|
|
|
|
SettingsCard {
|
|
title: I18n.tr("Dynamic Properties")
|
|
visible: root.isNiri || root.isHyprland
|
|
|
|
SettingsToggleRow {
|
|
text: I18n.tr("Opacity")
|
|
checked: root.opacityOn
|
|
enabled: root.fieldsEnabled
|
|
onToggled: checked => root.opacityOn = checked
|
|
}
|
|
|
|
SettingsSliderRow {
|
|
visible: root.opacityOn
|
|
text: I18n.tr("Opacity")
|
|
value: root.opacityValue
|
|
minimum: 10
|
|
unit: "%"
|
|
enabled: root.fieldsEnabled
|
|
onSliderValueChanged: newValue => root.opacityValue = newValue
|
|
}
|
|
|
|
SettingsToggleRow {
|
|
text: I18n.tr("Corner radius")
|
|
checked: root.cornerRadiusOn
|
|
enabled: root.fieldsEnabled
|
|
onToggled: checked => root.cornerRadiusOn = checked
|
|
}
|
|
|
|
SettingsSliderRow {
|
|
visible: root.cornerRadiusOn
|
|
text: I18n.tr("Corner radius")
|
|
value: root.cornerRadiusValue
|
|
maximum: 24
|
|
unit: "px"
|
|
enabled: root.fieldsEnabled
|
|
onSliderValueChanged: newValue => root.cornerRadiusValue = newValue
|
|
}
|
|
|
|
SettingsToggleRow {
|
|
visible: root.isNiri
|
|
text: I18n.tr("Scroll Factor")
|
|
checked: root.scrollFactorOn
|
|
enabled: root.fieldsEnabled
|
|
onToggled: checked => root.scrollFactorOn = checked
|
|
}
|
|
|
|
SettingsSliderRow {
|
|
visible: root.isNiri && root.scrollFactorOn
|
|
text: I18n.tr("Scroll Factor")
|
|
value: root.scrollFactorValue
|
|
minimum: 10
|
|
maximum: 200
|
|
unit: "%"
|
|
enabled: root.fieldsEnabled
|
|
onSliderValueChanged: newValue => root.scrollFactorValue = newValue
|
|
}
|
|
|
|
SettingsRow {
|
|
visible: root.isNiri
|
|
|
|
body: DankFilterChips {
|
|
width: parent.width
|
|
multiSelect: true
|
|
showCounts: false
|
|
enabled: root.fieldsEnabled
|
|
model: root.dynamicOptions
|
|
selectedValues: root.dynamicFlags
|
|
onSelectionToggled: (index, selected) => root.dynamicFlags = root.flagSet(root.dynamicFlags, root.dynamicOptions[index].value, selected)
|
|
}
|
|
}
|
|
|
|
SettingsDropdownRow {
|
|
visible: root.isNiri
|
|
text: I18n.tr("Block Out From")
|
|
options: root.blockOutOptions.map(root.blockOutLabel)
|
|
currentValue: root.blockOutLabel(root.blockOutValue)
|
|
enabled: root.fieldsEnabled
|
|
onValueChanged: value => root.blockOutValue = root.blockOutOptions.find(option => root.blockOutLabel(option) === value) ?? ""
|
|
}
|
|
|
|
SettingsDropdownRow {
|
|
visible: root.isNiri
|
|
text: I18n.tr("Column Display")
|
|
options: root.columnDisplayOptions.map(root.columnDisplayLabel)
|
|
currentValue: root.columnDisplayLabel(root.columnDisplayValue)
|
|
enabled: root.fieldsEnabled
|
|
onValueChanged: value => root.columnDisplayValue = root.columnDisplayOptions.find(option => root.columnDisplayLabel(option) === value) ?? ""
|
|
}
|
|
}
|
|
|
|
SettingsCard {
|
|
title: I18n.tr("Background Effect")
|
|
visible: root.isNiri
|
|
|
|
SettingsRow {
|
|
subtitle: I18n.tr("Xray blurs only the wallpaper (efficient) and is the default when Blur is on. Set Xray to Off for regular full blur of everything beneath the window (more expensive).")
|
|
}
|
|
|
|
SettingsRow {
|
|
body: Flow {
|
|
width: parent.width
|
|
spacing: Theme.spacingS
|
|
|
|
MatchCond {
|
|
id: blurCond
|
|
label: I18n.tr("Blur", "noun, background blur effect option")
|
|
unsetLabel: I18n.tr("Inherit")
|
|
}
|
|
|
|
MatchCond {
|
|
id: xrayCond
|
|
label: I18n.tr("X-Ray", "window rule background xray effect option")
|
|
unsetLabel: I18n.tr("Inherit")
|
|
}
|
|
}
|
|
}
|
|
|
|
SettingsToggleRow {
|
|
text: I18n.tr("Noise", "window rule background noise effect checkbox")
|
|
checked: root.noiseOn
|
|
enabled: root.fieldsEnabled
|
|
onToggled: checked => root.noiseOn = checked
|
|
}
|
|
|
|
SettingsSliderRow {
|
|
visible: root.noiseOn
|
|
text: I18n.tr("Noise", "window rule background noise effect checkbox")
|
|
value: root.noiseValue
|
|
unit: "%"
|
|
enabled: root.fieldsEnabled
|
|
onSliderValueChanged: newValue => root.noiseValue = newValue
|
|
}
|
|
|
|
SettingsToggleRow {
|
|
text: I18n.tr("Saturation", "window rule background color saturation checkbox")
|
|
checked: root.saturationOn
|
|
enabled: root.fieldsEnabled
|
|
onToggled: checked => root.saturationOn = checked
|
|
}
|
|
|
|
SettingsSliderRow {
|
|
visible: root.saturationOn
|
|
text: I18n.tr("Saturation", "window rule background color saturation checkbox")
|
|
value: root.saturationValue
|
|
maximum: 200
|
|
unit: "%"
|
|
enabled: root.fieldsEnabled
|
|
onSliderValueChanged: newValue => root.saturationValue = newValue
|
|
}
|
|
}
|
|
|
|
SettingsCard {
|
|
title: I18n.tr("Floating Position")
|
|
visible: root.isNiri
|
|
|
|
SettingsRow {
|
|
subtitle: I18n.tr("Initial position for floating windows. Set both X and Y; anchor controls which corner/edge they're relative to.")
|
|
}
|
|
|
|
FieldRow {
|
|
Field {
|
|
id: floatingXInput
|
|
leftIconName: "open_with"
|
|
labelText: "X"
|
|
placeholderText: "px"
|
|
}
|
|
|
|
Field {
|
|
id: floatingYInput
|
|
leftIconName: "open_with"
|
|
labelText: "Y"
|
|
placeholderText: "px"
|
|
}
|
|
}
|
|
|
|
SettingsDropdownRow {
|
|
text: I18n.tr("Anchor", "noun, screen corner or edge a floating window position is relative to")
|
|
options: root.anchorOptions
|
|
currentValue: root.floatingRelativeValue
|
|
enabled: root.fieldsEnabled
|
|
onValueChanged: value => root.floatingRelativeValue = value
|
|
}
|
|
}
|
|
|
|
SettingsCard {
|
|
title: I18n.tr("Size Constraints")
|
|
visible: root.isNiri || root.isHyprland
|
|
|
|
FieldRow {
|
|
Field {
|
|
id: minWidthInput
|
|
leftIconName: "width"
|
|
labelText: I18n.tr("Min W")
|
|
placeholderText: "px"
|
|
}
|
|
|
|
Field {
|
|
id: maxWidthInput
|
|
leftIconName: "width"
|
|
labelText: I18n.tr("Max W")
|
|
placeholderText: "px"
|
|
}
|
|
}
|
|
|
|
FieldRow {
|
|
Field {
|
|
id: minHeightInput
|
|
leftIconName: "height"
|
|
labelText: I18n.tr("Min H")
|
|
placeholderText: "px"
|
|
}
|
|
|
|
Field {
|
|
id: maxHeightInput
|
|
leftIconName: "height"
|
|
labelText: I18n.tr("Max H")
|
|
placeholderText: "px"
|
|
}
|
|
}
|
|
}
|
|
|
|
SettingsCard {
|
|
title: I18n.tr("Hyprland Options")
|
|
visible: root.isHyprland
|
|
|
|
SettingsRow {
|
|
body: DankFilterChips {
|
|
width: parent.width
|
|
multiSelect: true
|
|
showCounts: false
|
|
enabled: root.fieldsEnabled
|
|
model: root.hyprOptions
|
|
selectedValues: root.hyprFlags
|
|
onSelectionToggled: (index, selected) => root.hyprFlags = root.flagSet(root.hyprFlags, root.hyprOptions[index].value, selected)
|
|
}
|
|
}
|
|
|
|
FieldRow {
|
|
Field {
|
|
id: moveXInput
|
|
leftIconName: "open_with"
|
|
labelText: "X"
|
|
placeholderText: "0"
|
|
}
|
|
|
|
Field {
|
|
id: moveYInput
|
|
leftIconName: "open_with"
|
|
labelText: "Y"
|
|
placeholderText: "0"
|
|
}
|
|
}
|
|
|
|
FieldRow {
|
|
Field {
|
|
id: sizeWInput
|
|
leftIconName: "width"
|
|
labelText: I18n.tr("W")
|
|
placeholderText: "800"
|
|
}
|
|
|
|
Field {
|
|
id: sizeHInput
|
|
leftIconName: "height"
|
|
labelText: I18n.tr("H", "abbreviation of height, window rule size field label")
|
|
placeholderText: "600"
|
|
}
|
|
}
|
|
|
|
FieldRow {
|
|
Field {
|
|
id: monitorInput
|
|
leftIconName: "monitor"
|
|
labelText: I18n.tr("Monitor", "noun, display output field in window rule editor")
|
|
placeholderText: "DP-1"
|
|
}
|
|
|
|
Field {
|
|
id: hyprWorkspaceInput
|
|
leftIconName: "view_module"
|
|
labelText: I18n.tr("Workspace")
|
|
placeholderText: "1"
|
|
}
|
|
}
|
|
}
|
|
|
|
SettingsCard {
|
|
title: I18n.tr("Mango Options")
|
|
visible: root.isMango
|
|
|
|
SettingsRow {
|
|
body: DankFilterChips {
|
|
width: parent.width
|
|
multiSelect: true
|
|
showCounts: false
|
|
enabled: root.fieldsEnabled
|
|
model: root.mangoOptions
|
|
selectedValues: root.mangoFlags
|
|
onSelectionToggled: (index, selected) => root.mangoFlags = root.flagSet(root.mangoFlags, root.mangoOptions[index].value, selected)
|
|
}
|
|
}
|
|
|
|
FieldRow {
|
|
Field {
|
|
id: mangoTagsInput
|
|
leftIconName: "view_module"
|
|
labelText: I18n.tr("Tags", "noun, mango compositor workspace tags field in window rule editor")
|
|
placeholderText: "1"
|
|
}
|
|
|
|
Field {
|
|
id: mangoMonitorInput
|
|
leftIconName: "monitor"
|
|
labelText: I18n.tr("Monitor")
|
|
placeholderText: "HDMI-A-1"
|
|
}
|
|
}
|
|
|
|
FieldRow {
|
|
Field {
|
|
id: mangoSizeInput
|
|
shares: 1
|
|
leftIconName: "aspect_ratio"
|
|
labelText: I18n.tr("Size")
|
|
placeholderText: "800x600"
|
|
}
|
|
}
|
|
}
|
|
}
|