multiple windows
tiny.win.open — settings panels, palettes, document windows
An app starts with one window, 'main'. tiny.win.open() opens more: each is a separate native window with its own page, named by an id you choose. Every window gets the full tiny API, and tiny.win in a window's page controls that window. The backend opens the same windows with app.win.open() and reaches each one through app.window(id).
await tiny.win.open('settings', { page: 'settings.html', title: 'Settings', size: '420x300' });opening a window
tiny.win.open(id, opts?) opens a window under id. If a window with that id is already open, it's brought to the front and focused instead, and opts are ignored — so a Settings menu item can call open('settings', …) every time.
| option | default | |
|---|---|---|
page | an HTML file in your frontend folder, like 'settings.html', or an http(s) URL | 'index.html' |
title | the window title | the id |
size | 'WxH': the page's box, without the title bar or menu bar — the same units as setSize | '600x400' |
minSize | 'WxH': a floor under the user's resizes | none |
x, y | the top-left, in global points, set before the window first shows | |
screen | a screens() entry or id; x, y are then relative to that screen | |
units | 'pt' or 'px' for x, y with screen | 'pt' |
chrome | the same fields as setChrome, applied before the first paint | |
parent | true (the main window) or a window id — see child windows |
chrome and the position apply before the window is drawn, so a frameless panel never flashes a title bar and a positioned window doesn't jump from the centre:
tiny.win.open('hud', {
page: 'hud.html', size: '320x120', x: 40, y: 40,
chrome: { frame: false, windowControls: false, transparent: true, vibrancy: 'hud' },
});A page can only open files inside the app's frontend folder, or http(s) URLs; an absolute path or a ../ that leads out of the frontend rejects. The backend's app.win.open() also takes absolute paths.
New windows show the app menu on Windows and Linux; give one its own with tiny.win.menu.set() or none with chrome: { menu: false }. With rememberWindows, a window opened without x and y goes back to where it was last time.
the window ids
tiny.win.id; // the window this page is in: 'main' or its open() id
await tiny.win.windows(); // ['main', 'settings', 'hud']windows() lists the open windows; 'main' is in it when there is a main window. win.open('main') shows and focuses the main window.
closing
tiny.win.close(); // this window
tiny.win.close('settings'); // another oneclose(id?) closes the window with that id, or the calling page's own window with no id. Closing 'main' quits the app. The close button on a secondary window closes just that window.
When a window closes, every remaining page gets a 'window-closed' event and the backend's onWindowClosed(id, app) hook runs:
tiny.api.on('window-closed', ({ id }) => {
if (id === 'settings') reloadPreferences();
});child windows
tiny.win.open('about', { page: 'about.html', size: '360x420', parent: true });parent keeps the window above another window of yours — true for the main window, or a window id — without floating over other apps the way setLevel('floating') does. The child minimizes and hides with its parent and closes when the parent closes. On Windows and Linux it gets no taskbar button of its own; on macOS it also moves when the parent moves. It can only be set when opening the window.
without a main window
With "mainWindow": false the app starts with no window at all — a tray app or a background agent — and the backend opens windows by name when it needs them. There's no 'main': win.open('main') rejects, so use another id. Closing the last window leaves the backend and tray running.
// backend
export function onTray(id, app) {
if (id === 'prefs') app.win.open('prefs', { page: 'prefs.html', size: '480x360' });
}events between windows
Each window's page is separate, with its own JavaScript and DOM, so windows talk through the backend or through events:
app.push(name, data)in the backend reaches every window;app.window(id).push(name, data)reaches one, andapp.win.push()only the main window. Pages listen withtiny.api.on(name, fn). See events to the page.- Your
apifunctions get the caller's window id asmeta.window, so the backend knows which window asked:
// backend
export const api = {
pick: async ({ color }, app, { window }) => {
app.window('main').push('color-picked', { color, from: window });
},
};tiny.win.onState, file drops (onDrop) and'window-closed'reach every window, so check the window id in the event where it matters.
from the backend
app.win.open('settings', { page: 'settings.html' });
const settings = app.window('settings');
settings.setTitle('Settings — Sync');
settings.setPosition(100, 100);
await settings.getState();
settings.close();app.window(id) returns a handle with the same window calls as tiny.win — setSize, setChrome, show, getState() and the rest — plus push(name, data) and eval(js). Calls on a handle whose window has closed do nothing. Compared with app.win (the main window), a handle has no open, windows or setHideOnClose, and its close() takes no id. A few calls are page-only: onState, onDrop, find, stopFind, and dragging files out.
platform differences
- Full screen on Windows applies to the main window only; other windows are maximized instead.
- Click-through (
setClickThrough) on Windows applies to the main window only. setHideOnCloseacts on the main window's close button, whichever page calls it. A secondary window's close button always closes it.- On macOS,
hide()from the main window hides the whole app; from another window it hides just that window. See showing and hiding.