diff --git a/declarative.luau b/declarative.luau deleted file mode 100644 index b8890bd..0000000 --- a/declarative.luau +++ /dev/null @@ -1,43 +0,0 @@ ---!nonstrict --- Declarative bar widget: renders a ui.* tree with barWidget.render() instead --- of the imperative setText/setGlyph patches (hello.luau). The tree replaces --- the built-in glyph/text row. Pointer controls (ui.button, ui.toggle, …) work --- inline and consume their own clicks; the widget-level onClick still fires on --- the rest of the capsule. Keyboard controls (ui.input/ui.select/ui.scroll) --- are not available in the bar. The capsule clips to bar thickness, so keep --- the tree one control tall. - -local clicks = 0 -local tick = noctalia.state.get("tick") or 0 - -local function render() - -- Stack along the bar: a row in a horizontal bar, a column in a side bar. - local container = barWidget.isVertical() and ui.column or ui.row - barWidget.render(container({ gap = 6, align = "center" }, { - ui.glyph({ name = "sparkles", size = 14, color = "secondary" }), - -- A filled rounded badge — something the imperative setText/setGlyph API - -- cannot draw. "secondary/0.25" is the secondary role at 25% alpha, so the - -- pill stays theme-aware while the text on it remains fully opaque. - ui.row({ fill = "secondary/0.25", radius = 8, paddingH = 6, align = "center" }, { - ui.label({ text = tostring(tick), color = "secondary", fontWeight = "bold" }), - }), - ui.button({ glyph = "plus", glyphSize = 12, variant = "ghost", onClick = "onPlus" }), - })) -end - --- Live updates from the plugin's background service (ticker.luau). -noctalia.state.watch("tick", function(value) - tick = value - render() -end) - -function update() - noctalia.setUpdateInterval(1000) - render() -end - --- Inner-control callback: fires only when the ui.button is clicked. -function onPlus() - clicks += 1 - noctalia.notify(noctalia.tr("title"), noctalia.trp("clicks", clicks)) -end diff --git a/finder.luau b/finder.luau deleted file mode 100644 index a87b133..0000000 --- a/finder.luau +++ /dev/null @@ -1,77 +0,0 @@ ---!nonstrict --- Launcher provider for the example plugin. --- --- Type `/ex` in the launcher. The top level lists categories; pressing Enter on --- one calls launcher.setQuery() to rewrite the input (e.g. `/ex fruits `) and --- drill into it WITHOUT closing the launcher. Pressing Enter on a leaf item --- copies it and lets the launcher close normally. --- --- launcher.setQuery() takes effect when called from a handler while the launcher --- is open. Called synchronously from onActivate it keeps the panel open and --- rewrites the input instead of closing — the basis for autocomplete / drill-in. - -local PREFIX = "/ex" - -local CATEGORIES = { - fruits = { "Apple", "Apricot", "Banana", "Cherry" }, - animals = { "Aardvark", "Bear", "Cat", "Dolphin" }, - colors = { "Amber", "Azure", "Crimson", "Teal" }, -} - --- Top level: one row per category. Activating a row drills into it. -local function categoryRows() - local rows = {} - for name in CATEGORIES do - table.insert(rows, { - id = "category:" .. name, - title = name, - subtitle = "Browse " .. name .. " — press Enter to open", - glyph = "folder", - }) - end - return rows -end - --- Inside a category: the items matching the text typed after the category name. -local function itemRows(category, filter) - local rows = {} - for _, item in CATEGORIES[category] do - if filter == "" or item:lower():find(filter:lower(), 1, true) then - table.insert(rows, { - id = item, - title = item, - subtitle = category .. " — press Enter to copy", - glyph = "tag", - }) - end - end - if #rows == 0 then - rows[1] = { id = "", title = "No matches", subtitle = category, glyph = "mood-empty" } - end - return rows -end - --- `query` is the launcher text after the `/ex` prefix, leading space trimmed. -function onQuery(query) - local text = noctalia.string.trim(query) - local category, rest = text:match("^(%S+)%s*(.*)$") - if category and CATEGORIES[category] then - launcher.setResults(query, itemRows(category, noctalia.string.trim(rest))) - else - launcher.setResults(query, categoryRows()) - end -end - -function onActivate(id) - local category = id:match("^category:(.+)$") - if category then - -- Drill in: rewrite the launcher input (note the prefix and trailing space) - -- and stay open so the user can keep filtering within the category. - launcher.setQuery(PREFIX .. " " .. category .. " ") - return - end - if id ~= "" then - noctalia.copyToClipboard(id, "text/plain") - noctalia.notify("Example", id .. " copied to clipboard") - end -end diff --git a/hello.luau b/hello.luau deleted file mode 100644 index 78b5480..0000000 --- a/hello.luau +++ /dev/null @@ -1,67 +0,0 @@ ---!nonstrict --- Minimal reference plugin entry: a bar widget that shows a configurable label --- and glyph, counts clicks, and accepts IPC to change its text at runtime. --- --- Demonstrates the capability surface: --- barWidget.* presentation (text / glyph / tooltip) --- barWidget.getConfig settings (seeded from the manifest; single-arg, no fallback) --- noctalia.tr / trp translation against this plugin's translations/.json --- noctalia.readFile filesystem, resolved relative to the plugin's own dir --- noctalia.http async HTTP (off-thread; response delivered back as a callback) --- noctalia.state.* shared data published by this plugin's [[service]] (ticker.luau) --- noctalia.notify host notification --- onIpc shell-triggered events --- --- Drive it from the shell: --- noctalia msg plugin noctalia/example:hello focused set "Hi there" --- noctalia msg plugin noctalia/example:hello focused fetch "https://example.com" - -local label = barWidget.getConfig("label") -local glyph = barWidget.getConfig("glyph") -local showGlyph = barWidget.getConfig("show_glyph") - -local clicks = 0 -local dataNote = noctalia.readFile("data.txt") or "(no data file)" -local tick = noctalia.state.get("tick") or 0 - -local function render() - if showGlyph then - barWidget.setGlyph(glyph) - end - barWidget.setText(label) - barWidget.setTooltip(`{noctalia.trp("clicks", clicks)} — {dataNote} — service tick {tick}`) -end - --- Live updates from the plugin's background service (ticker.luau). -noctalia.state.watch("tick", function(value) - tick = value - render() -end) - --- Called on the widget's update interval. Static here, so slow the ticks right down. -function update() - noctalia.setUpdateInterval(1000) - render() -end - -function onClick() - clicks += 1 - noctalia.notify(noctalia.tr("title"), noctalia.trp("clicks", clicks)) - render() -end - -function onIpc(event, payload) - if event == "set" then - label = payload - render() - elseif event == "fetch" then - -- Async: the request runs off-thread and the callback fires when it lands. - noctalia.http({ url = payload }, function(res) - if res.ok then - noctalia.notify(noctalia.tr("title"), `HTTP {res.status} — {#res.body} bytes`) - else - noctalia.notify(noctalia.tr("title"), "HTTP request failed") - end - end) - end -end diff --git a/panel.luau b/panel.luau deleted file mode 100644 index df8973e..0000000 --- a/panel.luau +++ /dev/null @@ -1,102 +0,0 @@ ---!nonstrict --- Reference [[panel]] entry — a settings-style panel of interactive controls. --- --- Panels describe their UI declaratively, exactly like desktop widgets: build a --- tree with the ui.* constructors and hand it to panel.render(). The host diffs --- the tree against the previous one and updates retained native controls, so --- rendering an unchanged tree is free. --- --- panel.render(tree) replace the panel's UI tree --- panel.close() ask the host to close this panel --- panel.getConfig(key) settings (seeded from the manifest) --- button/toggle/slider/... onClick / onChange call the plugin global of --- that name; values arrive as string args --- --- Layout notes: --- * a Flex (column/row/scroll) centers its children on the cross axis by --- default — pass align = "stretch" so controls fill the panel width. --- * panel size is host-owned: declare width/height on the [[panel]] manifest --- entry, so the surface is the same size on every open. --- --- Stateful controls follow a controlled/uncontrolled split: --- * toggle / slider / select are value-driven — pass the current value each --- render; the callback reports the user's new value. --- * input is uncontrolled — `value` seeds the field once; the host owns the --- text buffer afterward, so re-rendering never clobbers what the user typed. --- Observe edits via onChange (live) / onSubmit (Enter). - -local enabled = true -local volume = 50 -local greeting = "" -local themeIndex = 0 -local themes = { "Dark", "Light", "System" } - --- A labelled section: a heading above a control. ui.column stretches children to --- the panel width by default, so the control fills the row. -local function section(title, control) - return ui.column({ gap = 8 }, { - ui.label({ text = title, fontWeight = "medium", color = "on_surface_variant" }), - control, - }) -end - -local function render() - -- The panel surface already insets its content (Style::panelPadding), so this - -- adds gaps between rows, not its own outer padding. - panel.render(ui.column({ flexGrow = 1, gap = 16 }, { - -- Header: title on the left, close button pinned to the top right — - -- matching the built-in panels (no divider rule). - ui.row({ align = "center", justify = "space_between", gap = 8 }, { - ui.label({ text = "Example Panel", fontSize = 16, fontWeight = "bold", color = "on_surface", flexGrow = 1 }), - ui.button({ glyph = "close", onClick = "onCloseClicked" }), - }), - - -- Body: a scrollable stack of settings. - ui.scroll({ flexGrow = 1, gap = 16 }, { - section("Notifications", ui.row({ gap = 12, align = "center" }, { - ui.toggle({ checked = enabled, onChange = "onToggle" }), - ui.label({ text = enabled and "Enabled" or "Disabled", flexGrow = 1 }), - })), - - section(`Volume — {volume}%`, ui.slider({ - min = 0, max = 100, step = 1, value = volume, onChange = "onVolume", - })), - - section("Your name (press Enter to greet)", ui.column({ gap = 8 }, { - ui.input({ key = "name", placeholder = "Type a name…", onSubmit = "onNameSubmit" }), - ui.label({ text = greeting, color = "primary" }), - })), - - section("Theme", ui.select({ options = themes, selectedIndex = themeIndex, onChange = "onTheme" })), - }), - })) -end - -function onOpen(_context) - render() -end - -function onToggle(value) - enabled = value == "true" - render() -end - -function onVolume(value) - volume = math.floor(tonumber(value) or 0) - render() -end - -function onNameSubmit(value) - greeting = value ~= "" and `Hello, {value}!` or "" - noctalia.notify("Example Panel", greeting) - render() -end - -function onTheme(index, _text) - themeIndex = tonumber(index) or 0 - render() -end - -function onCloseClicked() - panel.close() -end diff --git a/shortcut.luau b/shortcut.luau deleted file mode 100644 index a0edd89..0000000 --- a/shortcut.luau +++ /dev/null @@ -1,29 +0,0 @@ ---!nonstrict --- Control-center [[shortcut]] entry: a quick-toggle tile. --- --- shortcut.setLabel(text) --- shortcut.setIcon(on [, off]) -- one glyph, or distinct on/off glyphs --- shortcut.setActive(bool) -- toggle/highlight state --- shortcut.setEnabled(bool) --- onClick() / onRightClick() --- --- State is shared via noctalia.state, so the plugin's other entries (widget, --- service) could react to the toggle. - -local on = noctalia.state.get("toggle") == true - -local function render() - shortcut.setLabel(if on then "Example On" else "Example Off") - shortcut.setIcon("puzzle") - shortcut.setActive(on) - shortcut.setEnabled(true) -end - --- Initial state at load. -render() - -function onClick() - on = not on - noctalia.state.set("toggle", on) - render() -end diff --git a/ticker.luau b/ticker.luau deleted file mode 100644 index 051eabb..0000000 --- a/ticker.luau +++ /dev/null @@ -1,15 +0,0 @@ ---!nonstrict --- Headless [[service]] entry: runs in the background with no UI, ticking once a --- second and publishing an incrementing counter to the plugin's shared state. --- Any of this plugin's entries (the hello widget) can read it with --- noctalia.state.get / noctalia.state.watch. - -local count = 0 - --- Drive this service's own tick rate -noctalia.setUpdateInterval(1000) - -function update() - count += 1 - noctalia.state.set("tick", count) -end