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,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
|
||||
Reference in New Issue
Block a user