diff --git a/quickshell/Common/SettingsData.qml b/quickshell/Common/SettingsData.qml index 565b33ab9..bd8236379 100644 --- a/quickshell/Common/SettingsData.qml +++ b/quickshell/Common/SettingsData.qml @@ -192,6 +192,18 @@ Singleton { property int hyprlandLayoutRadiusOverride: Spec.SPEC.hyprlandLayoutRadiusOverride.def property int hyprlandLayoutBorderSize: Spec.SPEC.hyprlandLayoutBorderSize.def property bool hyprlandResizeOnBorder: Spec.SPEC.hyprlandResizeOnBorder.def + property string hyprlandTilingLayout: Spec.SPEC.hyprlandTilingLayout.def + property bool hyprlandDwindlePreserveSplit: Spec.SPEC.hyprlandDwindlePreserveSplit.def + property bool hyprlandDwindleSmartSplit: Spec.SPEC.hyprlandDwindleSmartSplit.def + property int hyprlandDwindleForceSplit: Spec.SPEC.hyprlandDwindleForceSplit.def + property string hyprlandMasterOrientation: Spec.SPEC.hyprlandMasterOrientation.def + property string hyprlandMasterNewStatus: Spec.SPEC.hyprlandMasterNewStatus.def + property bool hyprlandMasterNewOnTop: Spec.SPEC.hyprlandMasterNewOnTop.def + property int hyprlandMasterSize: Spec.SPEC.hyprlandMasterSize.def + property string hyprlandScrollingDirection: Spec.SPEC.hyprlandScrollingDirection.def + property int hyprlandScrollingColumnWidth: Spec.SPEC.hyprlandScrollingColumnWidth.def + property bool hyprlandScrollingFullscreenOneColumn: Spec.SPEC.hyprlandScrollingFullscreenOneColumn.def + property bool hyprlandScrollingFollowFocus: Spec.SPEC.hyprlandScrollingFollowFocus.def property int mangoLayoutGapsOverride: Spec.SPEC.mangoLayoutGapsOverride.def property int mangoLayoutGapsOutOverride: Spec.SPEC.mangoLayoutGapsOutOverride.def property int mangoLayoutRadiusOverride: Spec.SPEC.mangoLayoutRadiusOverride.def diff --git a/quickshell/Common/settings/SettingsSpec.js b/quickshell/Common/settings/SettingsSpec.js index a6dc65446..924fcaf99 100644 --- a/quickshell/Common/settings/SettingsSpec.js +++ b/quickshell/Common/settings/SettingsSpec.js @@ -105,6 +105,54 @@ var LOCAL_SPEC = { def: false, onChange: "updateCompositorLayout" }, + hyprlandTilingLayout: { + def: "", + onChange: "updateCompositorLayout" + }, + hyprlandDwindlePreserveSplit: { + def: false, + onChange: "updateCompositorLayout" + }, + hyprlandDwindleSmartSplit: { + def: false, + onChange: "updateCompositorLayout" + }, + hyprlandDwindleForceSplit: { + def: 0, + onChange: "updateCompositorLayout" + }, + hyprlandMasterOrientation: { + def: "left", + onChange: "updateCompositorLayout" + }, + hyprlandMasterNewStatus: { + def: "slave", + onChange: "updateCompositorLayout" + }, + hyprlandMasterNewOnTop: { + def: false, + onChange: "updateCompositorLayout" + }, + hyprlandMasterSize: { + def: 55, + onChange: "updateCompositorLayout" + }, + hyprlandScrollingDirection: { + def: "right", + onChange: "updateCompositorLayout" + }, + hyprlandScrollingColumnWidth: { + def: 50, + onChange: "updateCompositorLayout" + }, + hyprlandScrollingFullscreenOneColumn: { + def: true, + onChange: "updateCompositorLayout" + }, + hyprlandScrollingFollowFocus: { + def: true, + onChange: "updateCompositorLayout" + }, mangoLayoutGapsOverride: { def: -1, onChange: "updateCompositorLayout" diff --git a/quickshell/Modules/Settings/CompositorLayoutTab.qml b/quickshell/Modules/Settings/CompositorLayoutTab.qml index 0d42bcb61..7f7ffbd0d 100644 --- a/quickshell/Modules/Settings/CompositorLayoutTab.qml +++ b/quickshell/Modules/Settings/CompositorLayoutTab.qml @@ -221,6 +221,157 @@ awk '$1 == "xray" { print FILENAME ":" FNR; exit }' $files 2>/dev/null`; } } + SettingsCard { + id: hyprTilingCard + width: parent.width + tags: ["hyprland", "layout", "tiling", "dwindle", "master", "scrolling", "general:layout"] + title: I18n.tr("Tiling layout") + settingKey: "hyprlandTilingLayout" + iconName: "view_quilt" + visible: CompositorService.isHyprland + + readonly property var layoutIds: ["", "dwindle", "master", "scrolling"] + + SettingsButtonGroupRow { + tags: ["hyprland", "layout", "tiling", "dwindle", "master", "scrolling"] + settingKey: "hyprlandTilingLayout" + text: I18n.tr("Layout") + model: [I18n.tr("Off"), I18n.tr("Dwindle", "Hyprland tiling layout name"), I18n.tr("Master", "Hyprland tiling layout name"), I18n.tr("Scrolling", "Hyprland tiling layout name")] + currentIndex: Math.max(0, hyprTilingCard.layoutIds.indexOf(SettingsData.hyprlandTilingLayout)) + onSelectionChanged: (index, selected) => { + if (!selected) + return; + SettingsData.set("hyprlandTilingLayout", hyprTilingCard.layoutIds[index]); + } + } + + SettingsToggleRow { + tags: ["hyprland", "dwindle", "preserve", "split"] + settingKey: "hyprlandDwindlePreserveSplit" + visible: SettingsData.hyprlandTilingLayout === "dwindle" + text: I18n.tr("Preserve split") + description: I18n.tr("Split direction stays fixed when the container resizes") + checked: SettingsData.hyprlandDwindlePreserveSplit + onToggled: checked => SettingsData.set("hyprlandDwindlePreserveSplit", checked) + } + + SettingsToggleRow { + tags: ["hyprland", "dwindle", "smart", "split", "cursor"] + settingKey: "hyprlandDwindleSmartSplit" + visible: SettingsData.hyprlandTilingLayout === "dwindle" + text: I18n.tr("Smart split") + description: I18n.tr("Split direction follows the cursor position in the window") + checked: SettingsData.hyprlandDwindleSmartSplit + onToggled: checked => SettingsData.set("hyprlandDwindleSmartSplit", checked) + } + + SettingsButtonGroupRow { + tags: ["hyprland", "dwindle", "force", "split", "direction"] + settingKey: "hyprlandDwindleForceSplit" + visible: SettingsData.hyprlandTilingLayout === "dwindle" + text: I18n.tr("Force split") + model: [I18n.tr("Follow mouse"), I18n.tr("Left"), I18n.tr("Right")] + currentIndex: SettingsData.hyprlandDwindleForceSplit + onSelectionChanged: (index, selected) => { + if (!selected) + return; + SettingsData.set("hyprlandDwindleForceSplit", index); + } + } + + SettingsDropdownRow { + readonly property var ids: ["left", "right", "top", "bottom", "center"] + tags: ["hyprland", "master", "orientation", "position"] + settingKey: "hyprlandMasterOrientation" + visible: SettingsData.hyprlandTilingLayout === "master" + text: I18n.tr("Master position") + options: [I18n.tr("Left"), I18n.tr("Right"), I18n.tr("Top"), I18n.tr("Bottom"), I18n.tr("Center")] + currentValue: options[Math.max(0, ids.indexOf(SettingsData.hyprlandMasterOrientation))] + onValueChanged: value => SettingsData.set("hyprlandMasterOrientation", ids[Math.max(0, options.indexOf(value))]) + } + + SettingsButtonGroupRow { + readonly property var ids: ["slave", "master", "inherit"] + tags: ["hyprland", "master", "new", "window", "status", "slave"] + settingKey: "hyprlandMasterNewStatus" + visible: SettingsData.hyprlandTilingLayout === "master" + text: I18n.tr("New windows") + model: [I18n.tr("Stack"), I18n.tr("Master", "Hyprland tiling layout name"), I18n.tr("Inherit")] + currentIndex: Math.max(0, ids.indexOf(SettingsData.hyprlandMasterNewStatus)) + onSelectionChanged: (index, selected) => { + if (!selected) + return; + SettingsData.set("hyprlandMasterNewStatus", ids[index]); + } + } + + SettingsToggleRow { + tags: ["hyprland", "master", "new", "top", "stack"] + settingKey: "hyprlandMasterNewOnTop" + visible: SettingsData.hyprlandTilingLayout === "master" + text: I18n.tr("New windows on top of the stack") + checked: SettingsData.hyprlandMasterNewOnTop + onToggled: checked => SettingsData.set("hyprlandMasterNewOnTop", checked) + } + + SettingsSliderRow { + tags: ["hyprland", "master", "size", "mfact", "ratio"] + settingKey: "hyprlandMasterSize" + visible: SettingsData.hyprlandTilingLayout === "master" + text: I18n.tr("Master size") + value: SettingsData.hyprlandMasterSize + minimum: 10 + maximum: 90 + unit: "%" + onSliderValueChanged: newValue => SettingsData.set("hyprlandMasterSize", newValue) + } + + SettingsButtonGroupRow { + readonly property var ids: ["right", "left", "down", "up"] + tags: ["hyprland", "scrolling", "direction"] + settingKey: "hyprlandScrollingDirection" + visible: SettingsData.hyprlandTilingLayout === "scrolling" + text: I18n.tr("Direction") + model: [I18n.tr("Right"), I18n.tr("Left"), I18n.tr("Down"), I18n.tr("Up")] + currentIndex: Math.max(0, ids.indexOf(SettingsData.hyprlandScrollingDirection)) + onSelectionChanged: (index, selected) => { + if (!selected) + return; + SettingsData.set("hyprlandScrollingDirection", ids[index]); + } + } + + SettingsSliderRow { + tags: ["hyprland", "scrolling", "column", "width"] + settingKey: "hyprlandScrollingColumnWidth" + visible: SettingsData.hyprlandTilingLayout === "scrolling" + text: I18n.tr("Column Width") + value: SettingsData.hyprlandScrollingColumnWidth + minimum: 10 + maximum: 100 + unit: "%" + onSliderValueChanged: newValue => SettingsData.set("hyprlandScrollingColumnWidth", newValue) + } + + SettingsToggleRow { + tags: ["hyprland", "scrolling", "fullscreen", "single", "column"] + settingKey: "hyprlandScrollingFullscreenOneColumn" + visible: SettingsData.hyprlandTilingLayout === "scrolling" + text: I18n.tr("Fullscreen single column") + checked: SettingsData.hyprlandScrollingFullscreenOneColumn + onToggled: checked => SettingsData.set("hyprlandScrollingFullscreenOneColumn", checked) + } + + SettingsToggleRow { + tags: ["hyprland", "scrolling", "follow", "focus", "scroll"] + settingKey: "hyprlandScrollingFollowFocus" + visible: SettingsData.hyprlandTilingLayout === "scrolling" + text: I18n.tr("Follow focus") + checked: SettingsData.hyprlandScrollingFollowFocus + onToggled: checked => SettingsData.set("hyprlandScrollingFollowFocus", checked) + } + } + SettingsCard { width: parent.width tags: ["hyprland", "layout", "gaps", "radius", "window", "border", "rounding"] @@ -234,7 +385,6 @@ awk '$1 == "xray" { print FILENAME ":" FNR; exit }' $files 2>/dev/null`; settingKey: "hyprlandLayoutGapsMode" resetKeys: ["hyprlandLayoutGapsOverride"] text: I18n.tr("Gaps") - description: I18n.tr("Auto follows bar spacing, Off keeps your %1 config").arg("Hyprland") model: [I18n.tr("Auto"), I18n.tr("Custom"), I18n.tr("Off")] currentIndex: { if (SettingsData.hyprlandLayoutGapsOverride === -2) diff --git a/quickshell/Services/HyprlandService.qml b/quickshell/Services/HyprlandService.qml index fc44865ab..57a4648d5 100644 --- a/quickshell/Services/HyprlandService.qml +++ b/quickshell/Services/HyprlandService.qml @@ -406,20 +406,25 @@ Singleton { if (frameEnabled && SettingsData.frameMode !== "connected") xrayNamespaces.push("dms:frame"); + const tilingLayout = typeof SettingsData !== "undefined" ? SettingsData.hyprlandTilingLayout : ""; + const generalLines = []; if (manageGaps) generalLines.push(`gaps_in = ${gapsIn},`, `gaps_out = ${gapsOut},`); generalLines.push(`border_size = ${borderSize},`, `resize_on_border = ${resizeOnBorder},`); + if (tilingLayout) + generalLines.push(`layout = ${luaString(tilingLayout)},`); + + const sections = [`\tgeneral = {\n${generalLines.map(l => "\t\t" + l).join("\n")}\n\t},`]; + const tilingLines = tilingLayoutLines(tilingLayout); + if (tilingLines.length) + sections.push(`\t${tilingLayout} = {\n${tilingLines.map(l => "\t\t" + l).join("\n")}\n\t},`); + sections.push(`\tdecoration = {\n\t\trounding = ${cornerRadius},\n\t},`); let content = `-- Auto-generated by DMS — do not edit manually hl.config({ - general = { -${generalLines.map(l => "\t\t" + l).join("\n")} - }, - decoration = { - rounding = ${cornerRadius}, - }, +${sections.join("\n")} }) `; @@ -469,6 +474,19 @@ hl.layer_rule({ }); } + function tilingLayoutLines(layout) { + switch (layout) { + case "dwindle": + return [`preserve_split = ${SettingsData.hyprlandDwindlePreserveSplit},`, `smart_split = ${SettingsData.hyprlandDwindleSmartSplit},`, `force_split = ${SettingsData.hyprlandDwindleForceSplit},`]; + case "master": + return [`orientation = ${luaString(SettingsData.hyprlandMasterOrientation)},`, `new_status = ${luaString(SettingsData.hyprlandMasterNewStatus)},`, `new_on_top = ${SettingsData.hyprlandMasterNewOnTop},`, `mfact = ${SettingsData.hyprlandMasterSize / 100},`]; + case "scrolling": + return [`direction = ${luaString(SettingsData.hyprlandScrollingDirection)},`, `column_width = ${SettingsData.hyprlandScrollingColumnWidth / 100},`, `fullscreen_on_one_column = ${SettingsData.hyprlandScrollingFullscreenOneColumn},`, `follow_focus = ${SettingsData.hyprlandScrollingFollowFocus},`]; + default: + return []; + } + } + function generateCursorConfig() { if (!CompositorService.isHyprland) return; diff --git a/quickshell/translations/check_term_variants.py b/quickshell/translations/check_term_variants.py index 00abe204d..09cc2ef23 100644 --- a/quickshell/translations/check_term_variants.py +++ b/quickshell/translations/check_term_variants.py @@ -25,6 +25,7 @@ ALLOWED = [ {"PIN", "Pin"}, # WPS PIN acronym vs the verb "pin" {"Device", "device"}, # label vs inline generic-noun fallback {"Until %1", "until %1"}, # sentence-initial vs mid-sentence position + {"Up", "up"}, # scroll direction vs uptime prefix {"Custom Color", "Custom color"}, # dms-plugins checkout, not this repo {"Media Player", "Media player"}, # dms-plugins checkout, not this repo ] diff --git a/quickshell/translations/settings_search_index.json b/quickshell/translations/settings_search_index.json index 80be0c6a7..d84c6a5a5 100644 --- a/quickshell/translations/settings_search_index.json +++ b/quickshell/translations/settings_search_index.json @@ -10599,6 +10599,67 @@ ], "conditionKey": "layoutCapable" }, + { + "section": "hyprlandScrollingColumnWidth", + "label": "Column Width", + "tabIndex": 37, + "category": "System & integrations", + "keywords": [ + "column", + "hyprland", + "integrations", + "scrolling", + "system", + "width" + ], + "conditionKey": "layoutCapable" + }, + { + "section": "hyprlandScrollingDirection", + "label": "Direction", + "tabIndex": 37, + "category": "System & integrations", + "keywords": [ + "direction", + "hyprland", + "integrations", + "scrolling", + "system" + ], + "conditionKey": "layoutCapable" + }, + { + "section": "hyprlandScrollingFollowFocus", + "label": "Follow focus", + "tabIndex": 37, + "category": "System & integrations", + "keywords": [ + "focus", + "follow", + "hyprland", + "integrations", + "scroll", + "scrolling", + "system" + ], + "conditionKey": "layoutCapable" + }, + { + "section": "hyprlandDwindleForceSplit", + "label": "Force split", + "tabIndex": 37, + "category": "System & integrations", + "keywords": [ + "direction", + "dwindle", + "force", + "hyprland", + "integrations", + "split", + "system" + ], + "conditionKey": "layoutCapable" + }, { "section": "hyprlandLayoutBarXrayEnabled", "label": "Frame xray", @@ -10651,35 +10712,37 @@ "description": "Blur against the wallpaper even with xray off", "conditionKey": "isNiri" }, + { + "section": "hyprlandScrollingFullscreenOneColumn", + "label": "Fullscreen single column", + "tabIndex": 37, + "category": "System & integrations", + "keywords": [ + "column", + "fullscreen", + "hyprland", + "integrations", + "scrolling", + "single", + "system" + ], + "conditionKey": "layoutCapable" + }, { "section": "hyprlandLayoutGapsMode", "label": "Gaps", "tabIndex": 37, "category": "System & integrations", "keywords": [ - "auto", - "config", - "follows", - "gap", "gaps", "hyprland", "inner", "integrations", - "keeps", - "margin", - "margins", "outer", "override", - "padding", - "panel", - "spacing", - "statusbar", "system", - "taskbar", - "topbar", "unmanaged" ], - "description": "Auto follows bar spacing, Off keeps your %1 config", "conditionKey": "layoutCapable" }, { @@ -10783,32 +10846,30 @@ "tabIndex": 37, "category": "System & integrations", "keywords": [ - "auto", + "background", + "beneath", + "bg", + "blurred", "border", - "config", - "follows", - "gap", + "desktop", "gaps", "hyprland", + "image", "integrations", - "keeps", "layout", - "margin", - "margins", "overrides", - "padding", - "panel", + "picture", "radius", "rounding", - "spacing", - "statusbar", + "show", + "surfaces", "system", - "taskbar", - "topbar", - "window" + "wallpaper", + "window", + "windows" ], "icon": "crop_square", - "description": "Auto follows bar spacing, Off keeps your %1 config", + "description": "Blurred surfaces show the wallpaper, not the windows beneath", "conditionKey": "isHyprland" }, { @@ -10879,6 +10940,72 @@ "description": "Auto follows bar spacing, Off keeps your %1 config", "conditionKey": "isNiri" }, + { + "section": "hyprlandMasterOrientation", + "label": "Master position", + "tabIndex": 37, + "category": "System & integrations", + "keywords": [ + "hyprland", + "integrations", + "master", + "orientation", + "position", + "system" + ], + "conditionKey": "layoutCapable" + }, + { + "section": "hyprlandMasterSize", + "label": "Master size", + "tabIndex": 37, + "category": "System & integrations", + "keywords": [ + "hyprland", + "integrations", + "master", + "mfact", + "ratio", + "size", + "system" + ], + "conditionKey": "layoutCapable" + }, + { + "section": "hyprlandMasterNewStatus", + "label": "New windows", + "tabIndex": 37, + "category": "System & integrations", + "keywords": [ + "hyprland", + "integrations", + "master", + "new", + "slave", + "status", + "system", + "window", + "windows" + ], + "conditionKey": "layoutCapable" + }, + { + "section": "hyprlandMasterNewOnTop", + "label": "New windows on top of the stack", + "tabIndex": 37, + "category": "System & integrations", + "keywords": [ + "hyprland", + "integrations", + "master", + "new", + "stack", + "system", + "top", + "windows" + ], + "conditionKey": "layoutCapable" + }, { "section": "compositorNotifications", "label": "Notifications", @@ -11001,6 +11128,27 @@ "icon": "overview", "conditionKey": "isNiri" }, + { + "section": "hyprlandDwindlePreserveSplit", + "label": "Preserve split", + "tabIndex": 37, + "category": "System & integrations", + "keywords": [ + "container", + "direction", + "dwindle", + "fixed", + "hyprland", + "integrations", + "preserve", + "resizes", + "split", + "stays", + "system" + ], + "description": "Split direction stays fixed when the container resizes", + "conditionKey": "layoutCapable" + }, { "section": "hyprlandResizeOnBorder", "label": "Resize on border", @@ -11032,6 +11180,53 @@ ], "conditionKey": "layoutCapable" }, + { + "section": "hyprlandDwindleSmartSplit", + "label": "Smart split", + "tabIndex": 37, + "category": "System & integrations", + "keywords": [ + "cursor", + "direction", + "dwindle", + "follows", + "hyprland", + "integrations", + "position", + "smart", + "split", + "system", + "window" + ], + "description": "Split direction follows the cursor position in the window", + "conditionKey": "layoutCapable" + }, + { + "section": "hyprlandTilingLayout", + "label": "Tiling layout", + "tabIndex": 37, + "category": "System & integrations", + "keywords": [ + "container", + "direction", + "dwindle", + "fixed", + "general:layout", + "hyprland", + "integrations", + "layout", + "master", + "resizes", + "scrolling", + "split", + "stays", + "system", + "tiling" + ], + "icon": "view_quilt", + "description": "Split direction stays fixed when the container resizes", + "conditionKey": "isHyprland" + }, { "section": "niriLayoutGapsOverride", "label": "Window gaps",