diff --git a/mango_layouts/README.md b/mango_layouts/README.md new file mode 100644 index 0000000..cd7e562 --- /dev/null +++ b/mango_layouts/README.md @@ -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. diff --git a/mango_layouts/panel.luau b/mango_layouts/panel.luau new file mode 100644 index 0000000..073beb5 --- /dev/null +++ b/mango_layouts/panel.luau @@ -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 diff --git a/mango_layouts/plugin.toml b/mango_layouts/plugin.toml new file mode 100644 index 0000000..abef2da --- /dev/null +++ b/mango_layouts/plugin.toml @@ -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 diff --git a/mango_layouts/thumbnail.webp b/mango_layouts/thumbnail.webp new file mode 100644 index 0000000..cf7c889 Binary files /dev/null and b/mango_layouts/thumbnail.webp differ diff --git a/mango_layouts/translations/en.json b/mango_layouts/translations/en.json new file mode 100644 index 0000000..4338bcc --- /dev/null +++ b/mango_layouts/translations/en.json @@ -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" +} diff --git a/mango_layouts/widget.luau b/mango_layouts/widget.luau new file mode 100644 index 0000000..b130821 --- /dev/null +++ b/mango_layouts/widget.luau @@ -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