Building UI
Changing screens
Section titled “Changing screens”The page event fires every time a screen opens. page.top is the top
part (the whole screen on single-screen devices) and page.bottom the bottom screen on dual-screen handhelds
(nil otherwise).
nari.on("page", function(name, page) if name ~= "NowPlaying" then return end local button = NariButton.new() button.text = "Hi!" button.custom_minimum_size = Vector2(240, 96) button.pressed:connect(function() local dialog = Dialog.menu("Hello from a plugin", "It works") dialog:add_option("Close", function() end) dialog:popup() end) page.top:add_child(button)end)- Find nodes inside a screen with Godot’s own methods:
page.top:find_child("Header", true, false),page.top:get_node("%List"),get_children()… - Use the theme’s colors with
ThemeEngine:get_color(ThemeEngine.Colors.AccentBg), and repaint when the theme changes withThemeEngine:follow(node, handler). - Nodes the plugin creates are freed when it’s turned off, so a button you added goes away with it.
Nari’s UI pieces
Section titled “Nari’s UI pieces”| Class | Notes |
|---|---|
NariButton |
The app’s button. text, icon, font_size, bg_color / fg_color / gradient_color (a ThemeEngine.Colors value), pressed signal. |
NariLabel |
A label that follows the theme. text, font_size, weight, marquee. |
Snackbar |
Snackbar.post(title, message, action) shows a message at the bottom for a few seconds. message and action are optional; action runs when it’s tapped. |
Dialog |
Dialog.menu(title, subtitle), then :add_option(text, action), :add_toggle(text, on, toggled), :add_button(text, action, style, closes) and :popup(). style is Dialog.Style.NORMAL, PRIMARY or DANGER. Dialog.confirm(message, accept_text, on_accept, danger) shows right away. |
GlyphIcon |
Shows the controller or keyboard glyph for an InputGlyphs.Glyph. |
Icon |
A themed SVG icon. |
ScrollList, SettingItem |
The same pieces Nari’s screens use. See ui/components and ui/shared in Nari’s code. |
WebView |
A web page over Nari. See Web views. |
Home entries
Section titled “Home entries”nari.home_entry({ title = "Random picks", subtitle = "A new mix every time you open it", icon = "res://assets/icons/shuffle.svg", color = "Purple", action = function() nari.log("opened!") end,})Entries show up before Settings, on single and dual screen Home. color is a theme color family: Blue, Purple,
Warning, Green, Red, Yellow, Pink, Orange, Teal, Cyan, Indigo, Lime, Success, Error,
Accent or Gray (the default). icon is a path in the plugin folder, a res:// path or a texture.
Lists of your own songs
Section titled “Lists of your own songs”The Tracks screen can show any list of songs:
Main:change_page(Main.Pages.Tracks, Dictionary({ tracks = songs, views = Dictionary({ single = "stair", top = "circle", bottom = "stair" }),}))tracksis a list of songs, the same asMediaPlayer:get_tracks()gives, shown in that order.viewsis optional and picks the layout:singlefor one screen (circle,ipod,vertical,stair,grid,compact),topandbottomfor dual screens. Without it, the user’s Tracks layout is used.- Picking a song plays the list from it. The hints add Shuffle and Play all.
Now Playing tabs
Section titled “Now Playing tabs”nari.now_playing_tab({ id = "hello", title = "Hello", icon = "res://assets/icons/notifications.svg", build = function(area) local label = NariLabel.new() label.text = "Hello World!" area:add_child(label) end,})The tab goes into the Now Playing tab bar after the built-in ones. build runs every time Now Playing opens, with
the tab’s page (a MarginContainer) to fill. It shows and hides like the Queue and Effects pages.
Discover sections
Section titled “Discover sections”Discover, the first entry of Home, is a page of sections built from what the user plays and likes. Add your own:
nari.discover_section({ id = "trending", title = "Trending now", kind = "tracks", order = 15, items = function(done) nari.http({ url = "https://example.com/trending" }, function(response) done(response and nari.json_decode(response.body) or {}) end) end,})| Key | |
|---|---|
title |
Shown above the section. |
kind |
quick_picks (a grid of songs), tracks (song cards), track_list (up to 5 song rows), albums, artists or playlists. |
items |
A function that returns the list, or calls done(list) later, with the same keys as the library lists. |
order |
Where the section goes. Built-in ones use 0 (Quick picks) to 60, lower comes first. 50 by default. |
id |
Optional name for the section. |
Sections without items are hidden. Songs play the section from the one picked, and albums, artists and playlists open their screens.
Item actions
Section titled “Item actions”nari.item_action({ title = "Share link", kinds = { "track", "album" }, visible = function(kind, item) return item.id ~= nil end, action = function(kind, item) nari.open_url("https://example.com/" .. kind .. "/" .. item.id) end,})The option shows up in the menu of every item of those kinds (track, album, artist, playlist; all of them
when left out), after Nari’s own options. visible is optional and hides it for some items.
Web views
Section titled “Web views”nari.webview(spec) opens a web page as a full Nari screen and returns a handle, or nil when the device can’t
show web pages. Going back closes it. It’s the easy way to sign in to a service:
local web = nari.webview({ title = "Sign in", url = "https://example.com/login", incognito = true, on_load = function(web, url) if url:find("/home", 1, true) then local cookies = web:cookies("https://example.com") web:close() end end,})| Spec field | |
|---|---|
title, url |
Header title and first page. |
service |
The name in “Enter your … account” on the top screen of dual-screen devices. The plugin’s name by default. |
screen |
0 follows the screen the page is on, 1 always uses the main window, 2 the second screen. |
incognito |
Nothing is kept: cookies and storage are gone when it closes. |
desktop_mode |
true by default: pages load their desktop version, also on phones. |
user_agent |
Replaces the browser’s user agent. |
scripts |
JavaScript run at the start of every page, before the page’s own scripts. |
transparent, devtools, autoplay, zoom |
Page background, the inspector, media autoplay and zoom. |
on_start, on_load, on_title, on_message, on_close |
A page started or finished loading, its title changed, it called window.ipc.postMessage(text), or it closed. |
The handle has web:load(url), web:eval(script, done), web:cookies(url) (a Cookie header with every cookie
for that URL, HttpOnly ones too), web:close() and web.view, the WebView node.
WebView is a Control that draws a native browser where it’s placed, so nothing Nari draws can cover it. On
Linux it needs X11 and WebKitGTK 4.1 (the Flatpak has it), and on Windows WebView2 (part of Windows 10 and 11).