# window

> Open native windows that show std:ui interfaces, with menus, shortcuts, dialogs, the clipboard and screenshots.

This is the Markdown version of https://osl-lang.org/packages/window. Every page of the OSL docs has one: add .md to its path. https://osl-lang.org/llms.txt lists them all.

```osl
import "std:window"
```

`std:window` opens a native window and draws a [ui](/packages/ui.md) 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](/packages/serve.md).

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:

```osl
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.node` is 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.

```osl
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.

**Quiz.** You pass `def() -> Counter()` to `show` for two windows. What do they share?

- [ ] The same `Counter`, so the count is shared.
- [x] Nothing: each window gets its own `Counter`.
- [ ] The count, but not the labels.

Answer: A function that returns a component is a factory, called once per window. Pass the object itself to 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.

```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:

```osl
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:

```osl
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`:

```osl
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:

```osl
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:

```osl
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()
```

### Exercise: 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`.

Starting code:

```osl
import "std:ui"

class Greeter (
  string name = ""

  def view() ui.node (
    return <column pad=12 gap=8>
      <input bind=self.name/>
      <text>Hello, ${self.name}</text>
    </column>
  )
)

Greeter g = Greeter()
log "Hello, " ++ g.name
```

It should print:

```text
Hello, Ada
```

Hint 1: A class component keeps its state in fields. Set `g.name` before you print.

Hint 2: In a real program, add `import "std:window"`, call `window.create(...)`, then `show(g)` and `window.run()`.

A solution:

```osl
import "std:ui"

class Greeter (
  string name = ""

  def view() ui.node (
    return <column pad=12 gap=8>
      <input bind=self.name/>
      <text>Hello, ${self.name}</text>
    </column>
  )
)

Greeter g = Greeter()
g.name = "Ada"
log "Hello, " ++ g.name
```

### Exercise: 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.

Starting code:

```osl
def titleFor(string file) string -> "?"

log titleFor("notes.txt")
log titleFor("")
```

It should print:

```text
Editor - notes.txt
Editor
```

Hint 1: `if file == "" return "Editor"` then return the longer text.

A solution:

```osl
def titleFor(string file) string (
  if file == "" return "Editor"
  return "Editor - " ++ file
)

log titleFor("notes.txt")
log titleFor("")
```

## API reference

## API reference

Every member of `std:window`, generated from the source of OSL.

### Functions

#### `window.all() *window.Window[]`

all lists the open windows, in the order they were opened.

#### `window.clipboard() string`

clipboard 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.Window`

focused 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() number`

update 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() any`

content 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) boolean`

held 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() boolean`

isFocused reports whether the window has the keyboard.

#### `window.isOpen() boolean`

isOpen reports whether the window is still open.

#### `window.isVisible() boolean`

isVisible 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.menu(array items)`

menu sets the window's menu bar: an array of menus, each {label, items},
where an item is {label, action, keys} or {separator: true}. keys is a
shortcut, as key takes, that runs the item's action and shows beside it.
SDL2 has no native menus, so the bar is drawn at the top of the window
on every platform. An empty array removes it. Throws TypeError for a
menu or item of the wrong shape.

#### `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() number`

scale 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() boolean`

shouldClose 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() string`

title is the window's title.

#### `window.unhide()`

unhide shows a hidden window again.
