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