window
Open native windows that show std:ui interfaces, with menus, shortcuts, dialogs, the clipboard and screenshots.
import "std:window"std:window opens a native window and draws a ui view in it. You write the interface once, with tags and a class for its state, and the same code can run in a window or as a web page with serve.
Windows are drawn with SDL2, which has to be installed where the program is built and run (libSDL2 on Linux, sdl2 from Homebrew on macOS, SDL2.dll beside the program on Windows). A window needs a display, so the examples on this page compile but don't run in the browser. Run them on your own machine.
Your first window#
A counter. The class holds the state, view() returns the interface, and show gives it to a window. window.run() keeps the window drawn until it is closed:
import "std:ui"
import "std:window"
class Counter (
int count = 0
def add() (
self.count += 1
)
def view() ui.node (
return <column pad=24 gap=12 align=center>
<text size=28 weight=bold>${self.count}</text>
<button on:click=self.add>Add one</button>
</column>
)
)
*window.Window main = window.create({title: "Counter", width: 300, height: 200}).unwrap()
main.show(def() -> Counter())
window.run()Click the button, and the number changes. Make the window 600 by 400, change the title, then run it again.
What you can show#
show takes the same targets as app.UI in serve, and the type decides how it behaves:
- A function returning a
ui.nodeis a view, drawn again after every action. - A function returning an object with
view()is a factory. It is called once for this window, so every window gets its own state. - An object is shared with everything else it is given to, other windows and web pages included.
import "std:ui"
import "std:window"
class Note (
string text = ""
def view() ui.node (
return <input bind=self.text placeholder="Write here"/>
)
)
Note shared = Note()
*window.Window a = window.create({title: "A", width: 300, height: 120}).unwrap()
*window.Window b = window.create({title: "B", width: 300, height: 120}).unwrap()
a.show(shared)
b.show(shared)
window.run()Type in one window and the other follows. Swap shared for def() -> Note() in both calls and they stop sharing.
You pass def() -> Counter() to show for two windows. What do they share?
Window options#
window.create takes title, width and height (800 by 600), x and y (centred if left out), resizable, borderless, fullscreen, hidden, alwaysOnTop, minWidth and minHeight. It returns a result, because there may be no display. Positions count from 1, like everything in OSL.
import "std:window"
auto made = window.create({title: "Tool", width: 400, height: 300, alwaysOnTop: true})
if made.isErr() (
log "no window: " ++ made.unwrapErr().message
exit 1
)
*window.Window tool = made.unwrap()
tool.move(1, 1)
log tool.size() tool.bounds() tool.scale()size() is in units and scale() says how many pixels a unit is (2 on a high-density screen). title(), setTitle, resize, center, maximize, minimize, restore, setFullscreen, hide, unhide and close do what they say.
Keys and menus#
w.key("mod+s", fn) runs a function when the combination is pressed, mod being Cmd on macOS and Ctrl elsewhere. w.menu(items) draws a menu bar at the top of the window. Items can name the same shortcut:
import "std:ui"
import "std:window"
class Editor (
string text = ""
string status = "Ready"
def save() ( self.status = "Saved " ++ self.text.len ++ " characters" )
def clear() ( self.text = "" )
def view() ui.node (
return <column pad=12 gap=8 width=fill>
<input bind=self.text width=fill/>
<text>${self.status}</text>
</column>
)
)
Editor editor = Editor()
*window.Window w = window.create({title: "Editor"}).unwrap()
w.show(editor)
w.key("mod+s", editor.save)
w.menu([
{label: "File", items: [
{label: "Save", action: editor.save, keys: "mod+s"},
{separator: true},
{label: "Clear", action: editor.clear}
]}
])
window.run()An action runs like a button's on:click, so the window updates afterwards. SDL2 has no native menu bars, so this one is drawn inside the window and looks the same on every system.
Asking the user#
w.ask(message, buttons) shows a native dialog and returns the label of the button pressed (or "" if it was closed). openFile, saveFile and pickFolder return a path, or null when the user cancels. All of them return a result, because a machine may have no dialog program:
import "std:window"
*window.Window w = window.create({title: "Dialogs", width: 300, height: 100}).unwrap()
string answer = w.ask("Discard your changes?", ["Discard", "Keep"]).unwrap()
log "you chose " ++ answer
string? path = w.openFile({types: ["png", "jpg"]}).unwrap()
if path != null (
log "opening " ++ path
)Closing#
window.run() returns when the last window closes. To stop a window closing, give it an onClose handler that returns false:
import "std:window"
*window.Window w = window.create({title: "Unsaved"}).unwrap()
bool dirty = true
w.onClose(def() boolean (
if dirty (
return w.ask("Quit without saving?", ["Quit", "Stay"]).unwrap() == "Quit"
)
return true
))
window.run()Your own loop#
A game or a visualisation wants a frame loop of its own. Call window.update() once per frame instead of run(). It draws the windows, handles the events, waits for the next frame, and returns the milliseconds that passed:
import "std:ui"
import "std:window"
class Clock (
number seconds = 0
def view() ui.node -> <text size=40>${round(self.seconds, 1)}</text>
)
Clock clock = Clock()
*window.Window w = window.create({title: "Clock", width: 200, height: 100}).unwrap()
w.show(clock)
while w.isOpen() (
clock.seconds += window.update() / 1000
if w.held("space") ( clock.seconds = 0 )
)w.held(name) says whether a key or "mouseLeft" is down now.
Screenshots and the clipboard#
w.screenshot(path) saves what the window shows as a PNG, at its full pixel size, which is how to take pictures for documentation. window.clipboard() and window.setClipboard(text) read and write the system clipboard:
import "std:window"
*window.Window w = window.create({title: "Shot", width: 200, height: 100}).unwrap()
window.update()
w.screenshot("shot.png").unwrap()
window.setClipboard("copied").unwrap()
log window.clipboard()A greeting window
Make a window titled "Greeter" with a text input and a line that says Hello, followed by what was typed. This page can't open a window, so the starter prints the greeting the component would show. Make it print Hello, Ada.
Hello, Ada
A window title helper
Windows are named with title. Write def titleFor(string file) string that returns "Editor - " ++ file, or "Editor" when the file name is empty. Print both.
Editor - notes.txt Editor
API reference#
43 members, generated from the std:window source of OSL 9.3.0.
Functions
window.all() *window.Window[]all lists the open windows, in the order they were opened.
window.clipboard() stringclipboard is the text on the system clipboard, or "" when there is none or no display.
window.create(object? options) result<*window.Window, error>create opens a window. Options: title ("OSL"), width and height (800 by 600), x and y (centred when left out), resizable (true), borderless, fullscreen, hidden, alwaysOnTop (all false), minWidth and minHeight, parent (a *window.Window the new one is centred over and closes with) and closesWith (a *window.Window whose closing closes this one). Fails with RuntimeError when no window can be opened, such as on a machine without a display. Throws StateError off the main thread.
window.displays() {height: int, name: string, scale: number, width: int, x: int, y: int}[]displays lists the screens: each one's name, its area, counted from 1, and its scale. It is empty when there is no display.
window.focused() *window.Windowfocused is the open window that has the keyboard, or null.
window.run()run keeps every window drawn and answering until none is left open. Throws StateError off the main thread.
window.setClipboard(string text) result<unit, error>setClipboard puts text on the system clipboard. Fails with RuntimeError when there is no display to hold it.
window.update() numberupdate draws every open window and answers the clicks, typing and other events that arrived since the last call, then returns how many milliseconds passed since it last returned. It waits for the next frame when nothing is happening, so a loop calling it runs at about 60 frames a second without using the processor while idle. Throws StateError off the main thread.
*window.Window
window.ask(string message, array buttons) result<string, error>ask shows message in a dialog with buttons, such as ["Save", "Cancel"], and returns the label of the one pressed, or "" when the dialog was closed another way. An empty array shows one OK button. It waits for the answer. The dialog is SDL's native message box on every platform. Fails with RuntimeError when no dialog can open.
While the dialog waits, other actions can run, such as a web page's click or a thread's then, as other code runs during a JavaScript await. Read the state you need again after it returns: in seen = self.count; w.ask(...); self.count = seen + 1, a click made while the dialog was open is lost.
window.bounds() {height: int, width: int, x: int, y: int}bounds is the window's position, counted from 1, and size together.
window.center()center puts the window in the middle of its screen.
window.close()close closes the window straight away, and every window that closes with it. Closing a closed window does nothing.
window.content() anycontent is what the window shows: the target given to show, or null.
window.duplicate() result<*window.Window, error>duplicate opens another window with this one's options, a little below and to the right, showing the same target: a shared object stays shared and a factory makes the new window its own. Fails like window.create.
window.focus()focus brings the window to the front and gives it the keyboard.
window.held(string name) booleanheld reports whether a key, by the names key takes, or a mouse button, "mouseLeft", "mouseRight" or "mouseMiddle", is held down now.
window.hide()hide hides the window without closing it.
window.isFocused() booleanisFocused reports whether the window has the keyboard.
window.isOpen() booleanisOpen reports whether the window is still open.
window.isVisible() booleanisVisible reports whether the window is open and not hidden.
window.key(string keys, any fn)key runs fn when the key combination keys is pressed while the window has the keyboard, such as "mod+s", "ctrl+shift+z" or "f5". mod is Cmd on macOS and Ctrl elsewhere. fn runs like an action, so the window updates after it. Throws ValueError for a combination it doesn't know.
window.maximize()maximize makes the window as big as its screen allows.
window.minimize()minimize shrinks the window to the taskbar or dock.
window.move(int x, int y)move puts the window's top-left corner at x, y on the screen, counted from 1.
window.onClose(any fn)onClose adds fn, which runs when the window is asked to close, by its close button or requestClose. The window closes unless a handler returns false.
window.openFile(object? options) result<string?, error>openFile asks the user to pick a file to open and returns its path, or null when they cancel. Option: types, the extensions to offer, such as ["png", "jpg"]. The dialog is zenity or kdialog on Linux, AppleScript on macOS and PowerShell on Windows; fails with NotFoundError when none of them is there.
While the dialog waits, other actions can run, such as a web page's click or a thread's then, as other code runs during a JavaScript await. Read the state you need again after it returns: in seen = self.count; w.openFile(); self.count = seen + 1, a click made while the dialog was open is lost.
window.pickFolder() result<string?, error>pickFolder asks the user to pick a folder and returns its path, or null when they cancel. Dialogs are as for openFile.
While the dialog waits, other actions can run, such as a web page's click or a thread's then, as other code runs during a JavaScript await. Read the state you need again after it returns: in seen = self.count; w.pickFolder(); self.count = seen + 1, a click made while the dialog was open is lost.
window.position() {x: int, y: int}position is where the window's top-left corner is on the screen, counted from 1.
window.requestClose()requestClose asks the window to close, as its close button does: it closes unless an onClose handler returns false.
window.resize(int width, int height)resize sets the window's size. Throws ValueError below 1 by 1.
window.restore()restore undoes minimize and maximize.
window.saveFile(object? options) result<string?, error>saveFile asks the user where to save a file and returns the path, or null when they cancel. Options: types, as openFile takes, and name, the file name to suggest. Dialogs are as for openFile.
While the dialog waits, other actions can run, such as a web page's click or a thread's then, as other code runs during a JavaScript await. Read the state you need again after it returns: in seen = self.count; w.saveFile(); self.count = seen + 1, a click made while the dialog was open is lost.
window.scale() numberscale is how many pixels the window draws per unit: 2 on a typical high-density screen, 1 elsewhere.
window.screenshot(string path) result<unit, error>screenshot saves what the window shows as a PNG file at path, at its full pixel size. Fails with IOError when the file can't be written.
window.setFullscreen(boolean on)setFullscreen makes the window fill its screen, or not.
window.setMinSize(int width, int height)setMinSize sets the smallest size the user can resize the window to.
window.setTitle(string title)setTitle sets the window's title.
window.shouldClose() booleanshouldClose reports whether the window has been asked to close, by its close button or requestClose, whatever its onClose handlers decided.
window.show(any target)show makes the window show target, by the same rule as app.UI: a function returning ui.node is a view, a function returning an object with view() is a factory called once for this window, and an object is shared; the compiler checks target's type. Throws TypeError for a factory that takes a request, which a window doesn't have.
window.size() {height: int, width: int}size is the window's size, in units, not pixels; see scale.
window.title() stringtitle is the window's title.
window.unhide()unhide shows a hidden window again.