feat: add mango_layouts plugin (#192)
* feat: add mango_layouts plugin * fix: address CI validation errors * refactor: inline apply_layout script for cross-device portability * fix: commit inlined script changes in panel.luau * fix: address official PR review feedback * fix: revert noctalia.translate which caused runtime crash * fix: remove setGlyph empty string to prevent fallback skull icon * feat: restore translations using correct noctalia.tr API * refactor: remove unused label fields from layouts table
This commit is contained in:
@@ -0,0 +1,56 @@
|
|||||||
|
# Mango Layouts
|
||||||
|
|
||||||
|
A clean, minimalist layout switcher plugin for MangoWC. It provides a bar widget and a fast popup panel to switch workspace layouts on the fly, directly from your desktop.
|
||||||
|
|
||||||
|
## Plugin
|
||||||
|
|
||||||
|
| Field | Value |
|
||||||
|
| --- | --- |
|
||||||
|
| ID | `ezequiel/mango_layouts` |
|
||||||
|
| Entries | Bar widget: `btn`; panel: `panel` |
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
Install `jq` and `mmsg` (MangoWC CLI) on `PATH`.
|
||||||
|
Requires MangoWC as the compositor.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
Add the widget to your Noctalia bar in Settings. Click the widget to open the layout switcher panel and change the current workspace layout.
|
||||||
|
|
||||||
|
Alternatively, toggle the panel directly via command line or keybinding:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
noctalia msg panel-toggle ezequiel/mango_layouts:panel
|
||||||
|
```
|
||||||
|
|
||||||
|
## Settings
|
||||||
|
|
||||||
|
| Setting | Type | Default | Description |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `list_mode` | `bool` | `false` | Display layouts in a vertical list instead of a grid. |
|
||||||
|
| `show_tile` | `bool` | `true` | Show Tile layout. |
|
||||||
|
| `show_scroller` | `bool` | `true` | Show Scroller layout. |
|
||||||
|
| `show_monocle` | `bool` | `true` | Show Monocle layout. |
|
||||||
|
| `show_grid` | `bool` | `true` | Show Grid layout. |
|
||||||
|
| `show_fair` | `bool` | `true` | Show Fair layout. |
|
||||||
|
| `show_deck` | `bool` | `true` | Show Deck layout. |
|
||||||
|
| `show_dwindle` | `bool` | `true` | Show Dwindle layout. |
|
||||||
|
| `show_center_tile` | `bool` | `true` | Show Center Tile layout. |
|
||||||
|
| `show_vertical_tile` | `bool` | `true` | Show Vertical Tile layout. |
|
||||||
|
| `show_right_tile` | `bool` | `true` | Show Right Tile layout. |
|
||||||
|
| `show_vertical_scroller` | `bool` | `true` | Show Vertical Scroller layout. |
|
||||||
|
| `show_vertical_grid` | `bool` | `true` | Show Vertical Grid layout. |
|
||||||
|
| `show_vertical_deck` | `bool` | `true` | Show Vertical Deck layout. |
|
||||||
|
| `show_vertical_fair` | `bool` | `true` | Show Vertical Fair layout. |
|
||||||
|
|
||||||
|
Widget Specific Settings:
|
||||||
|
| Setting | Type | Default | Description |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `show_glyph` | `bool` | `true` | Show the widget icon. |
|
||||||
|
| `show_text` | `bool` | `false` | Show the active layout name as text. |
|
||||||
|
| `custom_color` | `string` | `""` | Custom icon/text color (e.g. primary, on_surface). |
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
The plugin uses `mmsg` to communicate with MangoWC for reading the active monitor state and applying the selected layout. Make sure you don't use this plugin on compositors other than MangoWC.
|
||||||
@@ -0,0 +1,174 @@
|
|||||||
|
local layouts = {
|
||||||
|
{ sym = "T", name = "tile", glyph = "layout-sidebar" },
|
||||||
|
{ sym = "S", name = "scroller", glyph = "carousel-horizontal" },
|
||||||
|
{ sym = "M", name = "monocle", glyph = "square" },
|
||||||
|
{ sym = "G", name = "grid", glyph = "layout-grid" },
|
||||||
|
{ sym = "F", name = "fair", glyph = "layout-board-split" },
|
||||||
|
{ sym = "D", name = "deck", glyph = "layers-difference" },
|
||||||
|
{ sym = "DW", name = "dwindle", glyph = "layout-2" },
|
||||||
|
{ sym = "CT", name = "center_tile", glyph = "layout-distribute-vertical" },
|
||||||
|
{ sym = "VT", name = "vertical_tile", glyph = "layout-rows" },
|
||||||
|
{ sym = "RT", name = "right_tile", glyph = "layout-sidebar-right" },
|
||||||
|
{ sym = "VS", name = "vertical_scroller",glyph = "carousel-vertical" },
|
||||||
|
{ sym = "VG", name = "vertical_grid", glyph = "grid-dots" },
|
||||||
|
{ sym = "VD", name = "vertical_deck", glyph = "chart-funnel" },
|
||||||
|
{ sym = "VF", name = "vertical_fair", glyph = "layout-board" },
|
||||||
|
}
|
||||||
|
|
||||||
|
local COLUMNS = 4
|
||||||
|
local COLUMN_GAP = 2
|
||||||
|
local selectedLayout = ""
|
||||||
|
local pickSlots = {}
|
||||||
|
|
||||||
|
local render
|
||||||
|
|
||||||
|
local function onPickAt(index)
|
||||||
|
local slot = pickSlots[index]
|
||||||
|
if slot == nil then return end
|
||||||
|
selectedLayout = slot.sym
|
||||||
|
noctalia.runAsync("mmsg dispatch setlayout," .. slot.entry.name)
|
||||||
|
panel.close()
|
||||||
|
end
|
||||||
|
|
||||||
|
local function makeGrid(children)
|
||||||
|
local rows = {}
|
||||||
|
local row = {}
|
||||||
|
for i, child in ipairs(children) do
|
||||||
|
table.insert(row, child)
|
||||||
|
if #row >= COLUMNS or i == #children then
|
||||||
|
while #row < COLUMNS do
|
||||||
|
table.insert(row, ui.spacer({ flexGrow = 1 }))
|
||||||
|
end
|
||||||
|
table.insert(rows, ui.row({ gap = COLUMN_GAP, align = "stretch" }, row))
|
||||||
|
row = {}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return ui.column({ gap = COLUMN_GAP }, rows)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function getShowConfig(name)
|
||||||
|
if name == "tile" then return noctalia.getConfig("show_tile") end
|
||||||
|
if name == "scroller" then return noctalia.getConfig("show_scroller") end
|
||||||
|
if name == "monocle" then return noctalia.getConfig("show_monocle") end
|
||||||
|
if name == "grid" then return noctalia.getConfig("show_grid") end
|
||||||
|
if name == "fair" then return noctalia.getConfig("show_fair") end
|
||||||
|
if name == "deck" then return noctalia.getConfig("show_deck") end
|
||||||
|
if name == "dwindle" then return noctalia.getConfig("show_dwindle") end
|
||||||
|
if name == "center_tile" then return noctalia.getConfig("show_center_tile") end
|
||||||
|
if name == "vertical_tile" then return noctalia.getConfig("show_vertical_tile") end
|
||||||
|
if name == "right_tile" then return noctalia.getConfig("show_right_tile") end
|
||||||
|
if name == "vertical_scroller" then return noctalia.getConfig("show_vertical_scroller") end
|
||||||
|
if name == "vertical_grid" then return noctalia.getConfig("show_vertical_grid") end
|
||||||
|
if name == "vertical_deck" then return noctalia.getConfig("show_vertical_deck") end
|
||||||
|
if name == "vertical_fair" then return noctalia.getConfig("show_vertical_fair") end
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
local function renderButtons(is_list_mode)
|
||||||
|
local buttons = {}
|
||||||
|
local i = 0
|
||||||
|
for _, entry in ipairs(layouts) do
|
||||||
|
local raw_val = getShowConfig(entry.name)
|
||||||
|
local enabled = (raw_val == true) or (raw_val == "true") or (raw_val == nil)
|
||||||
|
|
||||||
|
if enabled then
|
||||||
|
local slotIndex = i
|
||||||
|
local isSelected = selectedLayout == entry.sym
|
||||||
|
pickSlots[slotIndex] = { sym = entry.sym, entry = entry }
|
||||||
|
|
||||||
|
if is_list_mode then
|
||||||
|
table.insert(buttons, ui.button({
|
||||||
|
key = entry.sym,
|
||||||
|
glyph = entry.glyph,
|
||||||
|
text = noctalia.tr("name_" .. entry.name),
|
||||||
|
variant = if isSelected then "primary" else "ghost",
|
||||||
|
onClick = `onPick{slotIndex}`,
|
||||||
|
-- If noctalia supports button alignment, this will align it to the left:
|
||||||
|
-- (If not, it will just remain centered like an elegant menu)
|
||||||
|
}))
|
||||||
|
else
|
||||||
|
-- Grid Mode
|
||||||
|
table.insert(buttons, ui.column({
|
||||||
|
key = entry.sym,
|
||||||
|
gap = 2,
|
||||||
|
align = "center",
|
||||||
|
flexGrow = 1,
|
||||||
|
}, {
|
||||||
|
ui.button({
|
||||||
|
glyph = entry.glyph,
|
||||||
|
glyphSize = 18,
|
||||||
|
variant = if isSelected then "primary" else "ghost",
|
||||||
|
onClick = `onPick{slotIndex}`,
|
||||||
|
}),
|
||||||
|
ui.label({
|
||||||
|
text = noctalia.tr("name_" .. entry.name),
|
||||||
|
fontSize = 9,
|
||||||
|
fontWeight = "bold",
|
||||||
|
color = if isSelected then "primary" else "on_surface_variant",
|
||||||
|
textAlign = "center",
|
||||||
|
maxLines = 1,
|
||||||
|
}),
|
||||||
|
}))
|
||||||
|
end
|
||||||
|
i += 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return buttons
|
||||||
|
end
|
||||||
|
|
||||||
|
render = function()
|
||||||
|
pickSlots = {}
|
||||||
|
|
||||||
|
local list_mode_raw = noctalia.getConfig("list_mode")
|
||||||
|
local is_list_mode = (list_mode_raw == true) or (list_mode_raw == "true")
|
||||||
|
|
||||||
|
local content
|
||||||
|
if is_list_mode then
|
||||||
|
content = ui.column({ padding = 8, gap = 4 }, renderButtons(true))
|
||||||
|
else
|
||||||
|
content = ui.column({ padding = 12, gap = COLUMN_GAP }, {
|
||||||
|
makeGrid(renderButtons(false))
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
panel.render(ui.column({ flexGrow = 1 }, {
|
||||||
|
ui.scroll({ flexGrow = 1 }, {
|
||||||
|
content
|
||||||
|
})
|
||||||
|
}))
|
||||||
|
end
|
||||||
|
|
||||||
|
function onOpen(context)
|
||||||
|
selectedLayout = ""
|
||||||
|
render()
|
||||||
|
noctalia.runAsync(
|
||||||
|
"mmsg get all-monitors | jq -r '.monitors[] | select(.active == true and .last_open_surface != null) | .layout_symbol'",
|
||||||
|
function(result)
|
||||||
|
if result == nil or result.exitCode ~= 0 then return end
|
||||||
|
local sym = result.stdout:gsub("%s+", "")
|
||||||
|
|
||||||
|
-- Check if sym exists in our ordered list
|
||||||
|
local exists = false
|
||||||
|
for _, l in ipairs(layouts) do
|
||||||
|
if l.sym == sym then exists = true end
|
||||||
|
end
|
||||||
|
selectedLayout = if exists then sym else ""
|
||||||
|
render()
|
||||||
|
end
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
function onPick0() onPickAt(0) end
|
||||||
|
function onPick1() onPickAt(1) end
|
||||||
|
function onPick2() onPickAt(2) end
|
||||||
|
function onPick3() onPickAt(3) end
|
||||||
|
function onPick4() onPickAt(4) end
|
||||||
|
function onPick5() onPickAt(5) end
|
||||||
|
function onPick6() onPickAt(6) end
|
||||||
|
function onPick7() onPickAt(7) end
|
||||||
|
function onPick8() onPickAt(8) end
|
||||||
|
function onPick9() onPickAt(9) end
|
||||||
|
function onPick10() onPickAt(10) end
|
||||||
|
function onPick11() onPickAt(11) end
|
||||||
|
function onPick12() onPickAt(12) end
|
||||||
|
function onPick13() onPickAt(13) end
|
||||||
@@ -0,0 +1,134 @@
|
|||||||
|
id = "ezequiel/mango_layouts"
|
||||||
|
name = "Mango Layouts"
|
||||||
|
version = "1.0.0"
|
||||||
|
plugin_api = 14
|
||||||
|
author = "ezequiel"
|
||||||
|
license = "MIT"
|
||||||
|
icon = "layout-filled"
|
||||||
|
description = "Layout switcher for MangoWC"
|
||||||
|
tags = ["bar", "panel", "mangowc", "utility"]
|
||||||
|
dependencies = ["jq", "mmsg"]
|
||||||
|
|
||||||
|
[[widget]]
|
||||||
|
id = "btn"
|
||||||
|
entry = "widget.luau"
|
||||||
|
actions = {}
|
||||||
|
|
||||||
|
# -- WIDGET SETTINGS --
|
||||||
|
[[widget.setting]]
|
||||||
|
key = "show_glyph"
|
||||||
|
type = "bool"
|
||||||
|
label_key = "show_icon_label"
|
||||||
|
default = true
|
||||||
|
|
||||||
|
[[widget.setting]]
|
||||||
|
key = "show_text"
|
||||||
|
type = "bool"
|
||||||
|
label_key = "show_text_label"
|
||||||
|
default = false
|
||||||
|
|
||||||
|
[[widget.setting]]
|
||||||
|
key = "custom_color"
|
||||||
|
type = "string"
|
||||||
|
label_key = "custom_color_label"
|
||||||
|
description_key = "custom_color_desc"
|
||||||
|
default = ""
|
||||||
|
|
||||||
|
[[panel]]
|
||||||
|
id = "panel"
|
||||||
|
entry = "panel.luau"
|
||||||
|
width = 240
|
||||||
|
height = 250
|
||||||
|
placement = "floating"
|
||||||
|
position = "bottom_right"
|
||||||
|
|
||||||
|
# -- GLOBAL SETTINGS --
|
||||||
|
[[setting]]
|
||||||
|
key = "list_mode"
|
||||||
|
type = "bool"
|
||||||
|
label_key = "list_mode_label"
|
||||||
|
default = false
|
||||||
|
|
||||||
|
[[setting]]
|
||||||
|
key = "show_tile"
|
||||||
|
type = "bool"
|
||||||
|
label_key = "layout_tile_label"
|
||||||
|
default = true
|
||||||
|
|
||||||
|
[[setting]]
|
||||||
|
key = "show_scroller"
|
||||||
|
type = "bool"
|
||||||
|
label_key = "layout_scroller_label"
|
||||||
|
default = true
|
||||||
|
|
||||||
|
[[setting]]
|
||||||
|
key = "show_monocle"
|
||||||
|
type = "bool"
|
||||||
|
label_key = "layout_monocle_label"
|
||||||
|
default = true
|
||||||
|
|
||||||
|
[[setting]]
|
||||||
|
key = "show_grid"
|
||||||
|
type = "bool"
|
||||||
|
label_key = "layout_grid_label"
|
||||||
|
default = true
|
||||||
|
|
||||||
|
[[setting]]
|
||||||
|
key = "show_fair"
|
||||||
|
type = "bool"
|
||||||
|
label_key = "layout_fair_label"
|
||||||
|
default = true
|
||||||
|
|
||||||
|
[[setting]]
|
||||||
|
key = "show_deck"
|
||||||
|
type = "bool"
|
||||||
|
label_key = "layout_deck_label"
|
||||||
|
default = true
|
||||||
|
|
||||||
|
[[setting]]
|
||||||
|
key = "show_dwindle"
|
||||||
|
type = "bool"
|
||||||
|
label_key = "layout_dwindle_label"
|
||||||
|
default = true
|
||||||
|
|
||||||
|
[[setting]]
|
||||||
|
key = "show_center_tile"
|
||||||
|
type = "bool"
|
||||||
|
label_key = "layout_center_tile_label"
|
||||||
|
default = true
|
||||||
|
|
||||||
|
[[setting]]
|
||||||
|
key = "show_vertical_tile"
|
||||||
|
type = "bool"
|
||||||
|
label_key = "layout_vertical_tile_label"
|
||||||
|
default = true
|
||||||
|
|
||||||
|
[[setting]]
|
||||||
|
key = "show_right_tile"
|
||||||
|
type = "bool"
|
||||||
|
label_key = "layout_right_tile_label"
|
||||||
|
default = true
|
||||||
|
|
||||||
|
[[setting]]
|
||||||
|
key = "show_vertical_scroller"
|
||||||
|
type = "bool"
|
||||||
|
label_key = "layout_vertical_scroller_label"
|
||||||
|
default = true
|
||||||
|
|
||||||
|
[[setting]]
|
||||||
|
key = "show_vertical_grid"
|
||||||
|
type = "bool"
|
||||||
|
label_key = "layout_vertical_grid_label"
|
||||||
|
default = true
|
||||||
|
|
||||||
|
[[setting]]
|
||||||
|
key = "show_vertical_deck"
|
||||||
|
type = "bool"
|
||||||
|
label_key = "layout_vertical_deck_label"
|
||||||
|
default = true
|
||||||
|
|
||||||
|
[[setting]]
|
||||||
|
key = "show_vertical_fair"
|
||||||
|
type = "bool"
|
||||||
|
label_key = "layout_vertical_fair_label"
|
||||||
|
default = true
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 37 KiB |
@@ -0,0 +1,35 @@
|
|||||||
|
{
|
||||||
|
"show_icon_label": "Show Icon",
|
||||||
|
"show_text_label": "Show Text",
|
||||||
|
"custom_color_label": "Icon/Text Color",
|
||||||
|
"custom_color_desc": "E.g. primary, on_surface, etc.",
|
||||||
|
"list_mode_label": "Vertical List Mode",
|
||||||
|
"layout_tile_label": "Layout: Tile",
|
||||||
|
"layout_scroller_label": "Layout: Scroller",
|
||||||
|
"layout_monocle_label": "Layout: Monocle",
|
||||||
|
"layout_grid_label": "Layout: Grid",
|
||||||
|
"layout_fair_label": "Layout: Fair",
|
||||||
|
"layout_deck_label": "Layout: Deck",
|
||||||
|
"layout_dwindle_label": "Layout: Dwindle",
|
||||||
|
"layout_center_tile_label": "Layout: Center Tile",
|
||||||
|
"layout_vertical_tile_label": "Layout: Vertical Tile",
|
||||||
|
"layout_right_tile_label": "Layout: Right Tile",
|
||||||
|
"layout_vertical_scroller_label": "Layout: Vertical Scroller",
|
||||||
|
"layout_vertical_grid_label": "Layout: Vertical Grid",
|
||||||
|
"layout_vertical_deck_label": "Layout: Vertical Deck",
|
||||||
|
"layout_vertical_fair_label": "Layout: Vertical Fair",
|
||||||
|
"name_tile": "Tile",
|
||||||
|
"name_scroller": "Scroller",
|
||||||
|
"name_monocle": "Monocle",
|
||||||
|
"name_grid": "Grid",
|
||||||
|
"name_fair": "Fair",
|
||||||
|
"name_deck": "Deck",
|
||||||
|
"name_dwindle": "Dwindle",
|
||||||
|
"name_center_tile": "Center Tile",
|
||||||
|
"name_vertical_tile": "Vertical Tile",
|
||||||
|
"name_right_tile": "Right Tile",
|
||||||
|
"name_vertical_scroller": "Vertical Scroller",
|
||||||
|
"name_vertical_grid": "Vertical Grid",
|
||||||
|
"name_vertical_deck": "Vertical Deck",
|
||||||
|
"name_vertical_fair": "Vertical Fair"
|
||||||
|
}
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
local layouts = {
|
||||||
|
T = { name = "tile", glyph = "layout-sidebar" },
|
||||||
|
S = { name = "scroller", glyph = "carousel-horizontal" },
|
||||||
|
M = { name = "monocle", glyph = "square" },
|
||||||
|
G = { name = "grid", glyph = "layout-grid" },
|
||||||
|
F = { name = "fair", glyph = "layout-board-split" },
|
||||||
|
D = { name = "deck", glyph = "layers-difference" },
|
||||||
|
DW = { name = "dwindle", glyph = "layout-2" },
|
||||||
|
CT = { name = "center_tile", glyph = "layout-distribute-vertical" },
|
||||||
|
VT = { name = "vertical_tile", glyph = "layout-rows" },
|
||||||
|
RT = { name = "right_tile", glyph = "layout-sidebar-right" },
|
||||||
|
VS = { name = "vertical_scroller",glyph = "carousel-vertical" },
|
||||||
|
VG = { name = "vertical_grid", glyph = "grid-dots" },
|
||||||
|
VD = { name = "vertical_deck", glyph = "chart-funnel" },
|
||||||
|
VF = { name = "vertical_fair", glyph = "layout-board" },
|
||||||
|
}
|
||||||
|
|
||||||
|
local show_glyph_raw = noctalia.getConfig("show_glyph")
|
||||||
|
local show_glyph = (show_glyph_raw == true) or (show_glyph_raw == "true") or (show_glyph_raw == nil)
|
||||||
|
|
||||||
|
local show_text_raw = noctalia.getConfig("show_text")
|
||||||
|
local show_text = (show_text_raw == true) or (show_text_raw == "true")
|
||||||
|
|
||||||
|
local custom_color = noctalia.getConfig("custom_color")
|
||||||
|
|
||||||
|
function update()
|
||||||
|
noctalia.setUpdateInterval(1000)
|
||||||
|
noctalia.runAsync(
|
||||||
|
"mmsg get all-monitors | jq -r '.monitors[] | select(.active == true) | .layout_symbol'",
|
||||||
|
function(result)
|
||||||
|
if result and result.exitCode == 0 then
|
||||||
|
local sym = result.stdout:gsub("%s+", "")
|
||||||
|
local current = layouts[sym]
|
||||||
|
|
||||||
|
if current then
|
||||||
|
if show_glyph then
|
||||||
|
barWidget.setGlyph(current.glyph)
|
||||||
|
end
|
||||||
|
|
||||||
|
if show_text then
|
||||||
|
barWidget.setText(noctalia.tr("name_" .. current.name))
|
||||||
|
else
|
||||||
|
barWidget.setText("")
|
||||||
|
end
|
||||||
|
|
||||||
|
if custom_color and custom_color ~= "" then
|
||||||
|
barWidget.setColor(custom_color)
|
||||||
|
barWidget.setGlyphColor(custom_color)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
if show_glyph then
|
||||||
|
barWidget.setGlyph("layout")
|
||||||
|
end
|
||||||
|
|
||||||
|
if show_text then
|
||||||
|
barWidget.setText("Layout")
|
||||||
|
else
|
||||||
|
barWidget.setText("")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
function onClick()
|
||||||
|
noctalia.togglePanel("ezequiel/mango_layouts:panel")
|
||||||
|
end
|
||||||
Reference in New Issue
Block a user