From c6ec92d33c1de26910f0cadbaecb1b12d13851be Mon Sep 17 00:00:00 2001 From: Lucas Date: Tue, 11 Aug 2026 21:14:42 +0200 Subject: [PATCH 1/6] feat!: add all option for apply, select, and cycle --- w-engine/plugin.toml | 2 +- w-engine/start.luau | 93 +++++++++++++++++++++++++++++++++++- w-engine/w-engine-panel.luau | 30 +++++++++++- 3 files changed, 121 insertions(+), 4 deletions(-) diff --git a/w-engine/plugin.toml b/w-engine/plugin.toml index b47c3586..627c014d 100644 --- a/w-engine/plugin.toml +++ b/w-engine/plugin.toml @@ -1,6 +1,6 @@ id = "tadomika_ari/w-engine" name = "W Engine" -version = "1.2.0" +version = "1.3.0" # Tile and control callbacks are Luau closures rather than named globals, which # the host resolves from plugin API 9 onwards. plugin_api = 9 diff --git a/w-engine/start.luau b/w-engine/start.luau index 32eccac2..9efdd5ea 100644 --- a/w-engine/start.luau +++ b/w-engine/start.luau @@ -61,6 +61,21 @@ local lastNonce = nil local workshopRoot = nil local rngState = 0 +-- Get All output for all fonctionnality -- + +local allOutputs = nil +local isAllOutputs = false + +local function getAllOutput() + local outputs = {} + for index, output in ipairs(noctalia.outputs()) do + table.insert(outputs, output.name) + end + return outputs +end + +allOutputs = getAllOutput() + local function tr(key, subst) if subst then return noctalia.tr(key, subst) @@ -593,7 +608,7 @@ local function handleRequest(request) end lastNonce = request.nonce - local output = request.output + local output = request.output -- here if type(output) ~= "string" or output == "" then output = noctalia.focusedOutputName() end @@ -601,12 +616,46 @@ local function handleRequest(request) return end + if output == "All" then + isAllOutputs = true + end + if output ~= "All" then + isAllOutputs = false + end + local action = request.action if action == "apply" then + if isAllOutputs then + for _, outputForAll in ipairs(allOutputs) do + setCycle(outputForAll, false) + apply(outputForAll, request.id) + end + return + end -- A one-shot pick takes over from any cycle on that output. setCycle(output, false) apply(output, request.id) + elseif action == "select" then + + if isAllOutputs then + for _, outputForAll in ipairs(allOutputs) do + local ids = {} + if type(request.ids) == "table" then + for _, id in ipairs(request.ids) do + if type(id) == "string" and id ~= "" then + table.insert(ids, id) + end + end + end + data.selection[outputForAll] = ids + shuffled[outputForAll] = nil + saveData() + publishStatus() + end + return + end + local ids = {} if type(request.ids) == "table" then for _, id in ipairs(request.ids) do @@ -619,7 +668,34 @@ local function handleRequest(request) shuffled[output] = nil saveData() publishStatus() + elseif action == "cycle" then + + if isAllOutputs then + for _, outputForAll in ipairs(allOutputs) do + setCycle(outputForAll, request.enabled, request.minutes, request.order) + local cycle = cycleFor(outputForAll) + if cycle.enabled then + local ids = selectionFor(outputForAll) + if #ids == 0 then + setCycle(outputForAll, false) + noctalia.notify(tr("panel.title"), tr("panel.cycle_needs_selection")) + else + -- Start on the first pick straight away rather than leaving the + -- previous wallpaper up for a whole interval. + local first = cycle.order == "random" and nextRandom(outputForAll, ids) or ids[1] + apply(outputForAll, first) + noctalia.notify( + tr("panel.title"), + tr("panel.cycle_started", { count = #ids, minutes = cycle.minutes }) + ) + end + end + publishStatus() + end + return + end + setCycle(output, request.enabled, request.minutes, request.order) local cycle = cycleFor(output) if cycle.enabled then @@ -640,6 +716,21 @@ local function handleRequest(request) end publishStatus() elseif action == "stop" then + + if isAllOutputs then + for _, outputForAll in ipairs(allOutputs) do + + setCycle(outputForAll, false) + data.current[outputForAll] = nil + saveData() + stopProcess(outputForAll, nil) + restoreShellWallpaper(outputForAll) + publishStatus() + + end + return + end + setCycle(output, false) data.current[output] = nil saveData() diff --git a/w-engine/w-engine-panel.luau b/w-engine/w-engine-panel.luau index 529b6c4b..91513376 100644 --- a/w-engine/w-engine-panel.luau +++ b/w-engine/w-engine-panel.luau @@ -78,6 +78,9 @@ local function tr(key, subst) return noctalia.tr(key) end +-- Get All output for all fonctionnality -- + + -- ── Workshop discovery ─────────────────────────────────────────────────────── local function candidateRoots() @@ -216,6 +219,26 @@ local function outputStatus() return type(entry) == "table" and entry or {} end + +local function hasStopState() -- check for render stop button + if outputName ~= "All" then + local state = outputStatus() + return state.current ~= nil or state.restorable == true + end + + local outputs = type(status) == "table" and status.outputs or nil + if type(outputs) ~= "table" then + return false + end + + for _, entry in pairs(outputs) do + if type(entry) == "table" and (entry.current ~= nil or entry.restorable == true) then + return true + end + end + return false +end + local function storedDefaults() return asTable(asTable(status.defaults).engine) end @@ -553,7 +576,6 @@ local ENGINE_TOGGLES = { -- ── Rendering ──────────────────────────────────────────────────────────────── local function header() - local state = outputStatus() local children = { ui.label({ text = tr("panel.title"), @@ -575,7 +597,7 @@ local function header() }), } - if state.current or state.restorable then + if hasStopState() then table.insert( children, ui.button({ @@ -597,6 +619,10 @@ local function header() selectedIndex = index - 1 end end + table.insert(outputs, "All") + if outputName == "All" then + selectedIndex = #outputs - 1 + end table.insert(children, ui.select({ options = outputs, selectedIndex = selectedIndex, onChange = "outputSelected" })) table.insert( children, From 988eb7dfedff245a219ac67abb89ad1f175d532c Mon Sep 17 00:00:00 2001 From: Lucas Date: Tue, 11 Aug 2026 21:23:20 +0200 Subject: [PATCH 2/6] docs: add comment --- w-engine/start.luau | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/w-engine/start.luau b/w-engine/start.luau index 9efdd5ea..ffb70af4 100644 --- a/w-engine/start.luau +++ b/w-engine/start.luau @@ -625,7 +625,7 @@ local function handleRequest(request) local action = request.action if action == "apply" then - if isAllOutputs then + if isAllOutputs then -- loop for All Output for _, outputForAll in ipairs(allOutputs) do setCycle(outputForAll, false) apply(outputForAll, request.id) @@ -638,7 +638,7 @@ local function handleRequest(request) elseif action == "select" then - if isAllOutputs then + if isAllOutputs then -- loop for All Output for _, outputForAll in ipairs(allOutputs) do local ids = {} if type(request.ids) == "table" then @@ -671,7 +671,7 @@ local function handleRequest(request) elseif action == "cycle" then - if isAllOutputs then + if isAllOutputs then -- loop for All Output for _, outputForAll in ipairs(allOutputs) do setCycle(outputForAll, request.enabled, request.minutes, request.order) local cycle = cycleFor(outputForAll) From cb7c54b0b589b590b7b7c2cd36e0730f349035f2 Mon Sep 17 00:00:00 2001 From: Lucas Date: Thu, 20 Aug 2026 00:39:41 +0200 Subject: [PATCH 3/6] feat!: add page gestion --- w-engine/w-engine-panel.luau | 135 ++++++++++++++++++++--------------- 1 file changed, 79 insertions(+), 56 deletions(-) diff --git a/w-engine/w-engine-panel.luau b/w-engine/w-engine-panel.luau index 91513376..ae66da63 100644 --- a/w-engine/w-engine-panel.luau +++ b/w-engine/w-engine-panel.luau @@ -33,37 +33,6 @@ local previewHeight = 93 local workshopRoot = nil local catalog = nil -- cached: the grid re-renders far more often than the disk changes local catalogStamp = nil -- mtime of the Workshop dir the cache was built from - -local isOpen = false -local outputName = nil -local multiSelect = false -local selected = {} -- ids, in the order they were picked -local minutesText = "15" -local order = "sequential" -local status = {} - --- Per-wallpaper configuration view. -local view = "grid" -- "grid" | "config" | "defaults" -local configId = nil -local configName = "" -local configProps = {} -local engineDraft = {} -- working copy of options[id].engine -local propsDraft = {} -- working copy of options[id].properties -local showUnlabeled = false - --- Global defaults view. -local defaultsDraft = {} -- working copy of defaults.engine -local clearOverrides = false - -type Wallpaper = { - nb: string, - name: string, - path: string, -} - -local thumbsRequested = false - --- Forward declaration: callbacks defined above the rendering section redraw -- through this. local render @@ -139,11 +108,20 @@ local function requestThumbnails(thumbs) end, 60000) end -local function loadCatalog(): { Wallpaper } +PAGE_SIZE = 48 + +local function isInTranch(index, actualPage) + if actualPage == 0 then + return false + end + local checkMax = PAGE_SIZE * actualPage + local checkMin = checkMax - PAGE_SIZE + return index > checkMin and index <= checkMax +end + +local function loadCatalog(actualPage): { Wallpaper } local items = {} workshopRoot = nil - -- Several stock locations can exist at once, and last match wins, so a - -- personnalPath entry takes precedence. for _, path in ipairs(candidateRoots()) do if noctalia.listDir(path) then workshopRoot = path @@ -163,30 +141,35 @@ local function loadCatalog(): { Wallpaper } local thumbs = thumbDir() local missing = false + local numericIndex = 0 + for _, entryName in entries do - if tonumber(entryName) then -- Workshop items are numeric ids - local content = noctalia.readFile(workshopRoot .. entryName .. "/project.json") - if content then - local value = noctalia.json.decode(content) - if type(value) == "table" then - -- Prefer the cached still: Workshop previews are often animated - -- GIFs, which the grid would decode and animate for as long as - -- the panel is open. - local path = workshopRoot .. entryName .. "/" .. (value.preview or "preview.jpg") - local thumb = thumbs and (thumbs .. "/" .. entryName .. ".jpg") or nil - if thumb and noctalia.fileExists(thumb) then - path = thumb - else - missing = true + if tonumber(entryName) then -- check if is workshop id + numericIndex = numericIndex + 1 + if isInTranch(numericIndex, actualPage) then + local content = noctalia.readFile(workshopRoot .. entryName .. "/project.json") + if content then + local value = noctalia.json.decode(content) + if type(value) == "table" then + -- Prefer the cached still: Workshop previews are often animated + -- GIFs, which the grid would decode and animate for as long as + -- the panel is open. + local path = workshopRoot .. entryName .. "/" .. (value.preview or "preview.jpg") + local thumb = thumbs and (thumbs .. "/" .. entryName .. ".jpg") or nil + if thumb and noctalia.fileExists(thumb) then + path = thumb + else + missing = true + end + table.insert(items, { + nb = entryName, + name = value.title or entryName, + path = path, + }) end - table.insert(items, { - nb = entryName, - name = value.title or entryName, - path = path, - }) + else + noctalia.notify(tr("panel.title"), tr("panel.missing_project_json", { name = entryName })) end - else - noctalia.notify(tr("panel.title"), tr("panel.missing_project_json", { name = entryName })) end end end @@ -1304,11 +1287,13 @@ local function tile(item: Wallpaper, state) }) end +local actualPage = 1 + render = function() if not isOpen then return end - catalog = catalog or loadCatalog() + catalog = catalog or loadCatalog(actualPage) if view == "defaults" then panel.render(defaultsView()) return @@ -1349,6 +1334,8 @@ render = function() -- Single scroll for all list (Wallpaper and Favorite Wallpaper) local scrollContent = {} + + -- Favorite list part table.insert(scrollContent, ui.label({ text = "Favorites", fontSize = 13, fontWeight = "bold", color = "on_surface" })) if #FavoriteList == 0 then table.insert(scrollContent, ui.label({ text = "No favorite", fontSize = 12, color = "on_surface_variant" })) @@ -1362,6 +1349,7 @@ render = function() ui.separator({ spacing = 12 } )) + -- All list part table.insert(scrollContent, ui.label({ text = "All wallpapers", fontSize = 13, fontWeight = "bold", color = "on_surface" } )) @@ -1373,6 +1361,41 @@ render = function() table.insert(body, ui.scroll({ key = "grid-scroll", gap = 12, paddingRight = 8, align = "stretch", flexGrow = 1 }, scrollContent)) + + table.insert(body, ui.row({ align = "center", justify = "space_between", gap = 8 }, { + ui.button({ + text = "previous page", + variant = "ghost", + controlSize = "sm", + tooltip = tr("panel.defaults_tooltip"), + onClick = function() + if actualPage > 1 then + actualPage = actualPage - 1 + catalog = nil + render() + end + end, + }), + ui.button({ + text = tostring(actualPage), + variant = "ghost", + controlSize = "sm", + tooltip = tr("panel.defaults_tooltip"), + onClick = "", + }), + ui.button({ + text = "next page", + variant = "ghost", + controlSize = "sm", + tooltip = tr("panel.defaults_tooltip"), + onClick = function() + actualPage = actualPage + 1 + catalog = nil + render() + end, + }), + })) + panel.render(ui.column({ flexGrow = 1, gap = 12 }, body)) end -- ── Handlers ───────────────────────────────────────────────────────────────── From ab8e49e2b9e8c0c5c68ad69903b5c157225c103b Mon Sep 17 00:00:00 2001 From: Lucas Date: Thu, 20 Aug 2026 00:45:36 +0200 Subject: [PATCH 4/6] feat!: up to 1.4.0 --- w-engine/plugin.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/w-engine/plugin.toml b/w-engine/plugin.toml index 627c014d..1b20b878 100644 --- a/w-engine/plugin.toml +++ b/w-engine/plugin.toml @@ -1,6 +1,6 @@ id = "tadomika_ari/w-engine" name = "W Engine" -version = "1.3.0" +version = "1.4.0" # Tile and control callbacks are Luau closures rather than named globals, which # the host resolves from plugin API 9 onwards. plugin_api = 9 From 7fa3b60902ef9d927ff19dbad2071d5b5cfd9ec0 Mon Sep 17 00:00:00 2001 From: Lucas Date: Thu, 20 Aug 2026 23:32:27 +0200 Subject: [PATCH 5/6] feat!: rm false tooltips --- w-engine/w-engine-panel.luau | 3 --- 1 file changed, 3 deletions(-) diff --git a/w-engine/w-engine-panel.luau b/w-engine/w-engine-panel.luau index ae66da63..60a1da45 100644 --- a/w-engine/w-engine-panel.luau +++ b/w-engine/w-engine-panel.luau @@ -1367,7 +1367,6 @@ render = function() text = "previous page", variant = "ghost", controlSize = "sm", - tooltip = tr("panel.defaults_tooltip"), onClick = function() if actualPage > 1 then actualPage = actualPage - 1 @@ -1380,14 +1379,12 @@ render = function() text = tostring(actualPage), variant = "ghost", controlSize = "sm", - tooltip = tr("panel.defaults_tooltip"), onClick = "", }), ui.button({ text = "next page", variant = "ghost", controlSize = "sm", - tooltip = tr("panel.defaults_tooltip"), onClick = function() actualPage = actualPage + 1 catalog = nil From 973eacc4b9c4d7559278e790638c17180b162404 Mon Sep 17 00:00:00 2001 From: Lucas Date: Thu, 20 Aug 2026 23:46:57 +0200 Subject: [PATCH 6/6] feat!: add limite warning page if no more page --- w-engine/translations/en.json | 1 + w-engine/w-engine-panel.luau | 13 +++++++++---- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/w-engine/translations/en.json b/w-engine/translations/en.json index 9792c64b..1876ae8d 100644 --- a/w-engine/translations/en.json +++ b/w-engine/translations/en.json @@ -61,6 +61,7 @@ "label": "Sync shell colors with the wallpaper" } }, + "no_more_page": "No more page available", "widget": { "cycling": "W Engine — cycling wallpapers", "idle": "W Engine", diff --git a/w-engine/w-engine-panel.luau b/w-engine/w-engine-panel.luau index 60a1da45..8b5a29ee 100644 --- a/w-engine/w-engine-panel.luau +++ b/w-engine/w-engine-panel.luau @@ -30,6 +30,9 @@ local columns = 4 local previewWidth = 168 local previewHeight = 93 +local PAGE_SIZE = 48 +local actualPage = 1 + local workshopRoot = nil local catalog = nil -- cached: the grid re-renders far more often than the disk changes local catalogStamp = nil -- mtime of the Workshop dir the cache was built from @@ -108,8 +111,6 @@ local function requestThumbnails(thumbs) end, 60000) end -PAGE_SIZE = 48 - local function isInTranch(index, actualPage) if actualPage == 0 then return false @@ -177,6 +178,12 @@ local function loadCatalog(actualPage): { Wallpaper } if missing then requestThumbnails(thumbs) end + + if #items == 0 then + actualPage = actualPage - 1 + noctalia.notify(tr("plugin_name") ,tr("no_more_page")) + render() + end return items end @@ -1287,8 +1294,6 @@ local function tile(item: Wallpaper, state) }) end -local actualPage = 1 - render = function() if not isOpen then return