The Loup Native Book — contents
  1. Chapter 1: Getting started
  2. Chapter 2: Native mode overview
  3. Chapter 3: Variables and numerics
  4. Chapter 4: Functions
  5. Chapter 5: Control flow
  6. Chapter 6: Collections
  7. Chapter 7: Structs
  8. Chapter 8: Enums and pattern matching
  9. Chapter 9: Classes and ARC
  10. Chapter 10: Interfaces
  11. Chapter 11: Generics
  12. Chapter 12: Error handling
  13. Chapter 13: Modules and packages
  14. Chapter 14: Template fences
  15. Chapter 15: Ownership verbs
  16. Chapter 16: borrow, excl borrow and noescape
  17. Chapter 17: unique ownership
  18. Chapter 18: Lifetimes and regions
  19. Chapter 19: @unsafe and calling C
  20. Chapter 20: Concurrency with OS threads
  21. Chapter 21: Async on native
  22. Chapter 22: Desktop UI
  23. Chapter 23: SIMD and GPU
  24. Chapter 24: Building and distributing
  25. Chapter 25: Memory safety patterns

Chapter 22: Desktop UI

⚠ Planned — not in Loup 1.25. This chapter describes the design in the Language Reference (§2-4, "Reactive UI", and §2-4.8, "Desktop Backend"). The toolchain does not implement it yet, so its listings are design sketches and are not verified.

Loup's specification includes a reactive UI library that is shared between the browser and the desktop: the same signals, components and ui fences render as HTML in a browser and as native windows on the desktop. None of it ships in 1.25.1. There is no ui or ui.desktop module ('ui.desktop' is not a standard-library module), no signals, and no windowing.

Two things with similar names do exist and are easy to confuse with it:

  • loup-bootstrap build --target desktop packages a program as a self-contained application directory (a launcher executable plus the program compiled to WebAssembly, chapter 24). It produces a console program, not a windowed one.
  • The native.window module (Packages Reference §21.35), a bare OS window with a GPU surface for games and renderers, is also absent.

The rest of this chapter is a short tour of the planned design, so that you can recognize it and so that code written now in other modes moves over easily when it lands.

Signals, components and a window

State lives in signals. signal(v) makes a writable signal; get() reads it and records the dependency, and set(v) or update(f) writes it and re-renders whatever depends on it (§2-4.1). A component is a function that returns a View, usually built with a ui fence, whose layout primitives (Row, Column, Text, Button, TextField, …) are the same on every backend (§2-4.6). On the desktop, createWindow opens a window and mount attaches a component to it (§2-4.8):

Fragment — not a complete program
@mode(host.native)

from ui import (signal, mount);
from ui.desktop import (createWindow, WindowConfig);

func TodoApp(): View {
    let items = signal(list[string]());
    let input = signal("");

    return ```(ui)
      <Column spacing={$ 12 $}>
        <Row spacing={$ 8 $}>
          <TextField value={$ input $} placeholder="Add item..." />
          <Button onclick={$ func(): void {
              items.update(func(xs: list[string]): list[string] => xs + [input.get()]);
              input.set("");
          } $}>
            <Text>Add</Text>
          </Button>
        </Row>
        {% for (item in items.get()) %}
          <Text>{$ item $}</Text>
        {% end %}
      </Column>
    ```;
}

func main(): void {
    let win = createWindow(WindowConfig(title = "Todo", width = 640, height = 480));
    mount(TodoApp(), win);
}

Listing 22-1: A to-do window (design sketch)

The specified Window has title, width, height, resizable, fullscreen and visible properties and close(), minimize(), maximize(), focus() and setIcon(path) methods. Its properties behave like signals: reading win.width inside an effect re-runs the effect when the window is resized.

Under ARC, a closure that an object keeps (an event handler, an effect) and that refers back to that object forms a reference cycle. The specification breaks it with [weak self] capture lists (§1-7.3), which 1.25.1 does not accept yet (chapter 4); expect UI code to need them.

The desktop backend adds the parts of an application that a browser does not have (§2-4.8):

Fragment — not a complete program
@mode(host.native)

from ui.desktop import (Menu, MenuBar, MenuItem, setMenuBar, openFileDialog, FileDialogOptions, FileFilter);

func installMenus(win: Window): void {
    let file = Menu(title = "File");
    file.addItem(MenuItem(label = "Open...", shortcut = "Cmd+O", onclick = func(): void { openDocument(); }));
    file.addSeparator();
    file.addItem(MenuItem(label = "Quit", shortcut = "Cmd+Q", onclick = func(): void { exit(0); }));
    let bar = MenuBar();
    bar.addMenu(file);
    setMenuBar(win, bar);
}

async func chooseFile(): Task[Option[string]] {
    return await openFileDialog(FileDialogOptions(
        title = "Open a document",
        filters = [FileFilter(name = "Text", extensions = ["txt", "md"])],
    ));
}

Listing 22-2: A menu bar and a file dialog (design sketch)

The dialogs (openFileDialog, openFilesDialog, saveFileDialog, openDirectoryDialog, alertDialog, confirmDialog) are async functions, so they also depend on the native async executor of chapter 21. createTray() returns a Tray with setIcon, setTooltip, setMenu, show, hide and destroy.

One entry point for browser and desktop

A program that uses only ui (and none of ui.web or ui.desktop) is meant to run unchanged in a browser and on the desktop. runtime.ui.rootMount() returns the right mount target for the backend: the page's root element in a browser, the primary window on the desktop (§2-4.9). The browser half of this design is the subject of The Loup Web Book, where it is planned as well.

Summary

  • The reactive UI library (ui, signals, ui fences) and its desktop backend (ui.desktop: windows, menus, tray icons, dialogs) are specified but not implemented in Loup 1.25.
  • --target desktop packages a console program; it does not provide a GUI.
  • The design shares components between browser and desktop and mounts them with mount(view, window).
  • Chapter 23 previews SIMD and GPU compute, which are also planned.