Windows, Docks and Embedded Views¶
An app's whole frame can be written in the same YAML as any other view: the window, its title bar, a dock of panels, and the
screens inside it, each a view of its own. An app that still has a *_Shell.yaml can move over with
Migrating.
| You want | Write |
|---|---|
| The OS window, its title and its size | kind: Window at the root of Window_View.yaml |
| A title bar of your own | title_bar: on the window, or kind: TitleBar anywhere |
| Another view shown inside this one | view: |
| Screens you navigate between | view: with route: |
| Panels around the content, in tabs, that the user can drag | kind: Dock with kind: DockPanel |
tesserae new notes --window makes a project with a window, a rail and routed screens, and examples/window_dock/ in the repository
is a studio with all of it: a title bar, a rail, docked panels, routed screens and a status bar, in one Window_View.yaml.
The window¶
kind: Window is the root of a Window_View.yaml. There is one per app, it can't be nested or embedded, and it needs no
ViewModel (an empty one is made, so its title bar's buttons still work).
id: root
kind: Window
title: Notes # the OS window's title
borderless: true # no OS title bar or borders: the title bar below is the window's
min_width: 480
min_height: 320
style: {width: 960, height: 600, background: surface} # the window's size, and what it is painted with
title_bar: {title: Notes, icon: home, buttons: [minimize, maximize, close]}
children:
- id: content
kind: Text
text: {content: Hello, typography_role: headline_small}
app = App(title="Notes", root=HERE, theme_seed=(0x67, 0x50, 0xA4, 0xFF))
app.load("Window") # Views/Window_View.yaml
app.run()
style:'swidthandheightare the window's size, andbackgroundits fill. The rest ofstyle:lays out the content (aflex_direction: horizontalputs a rail beside the screens).borderless: trueis the same asApp(borderless=True): the OS window has no title bar or borders, and the title bar below is the window's.- A window with a
title_bar:that isn'tborderlesshas a plain header: the OS draws the window's own buttons, so the bar has none of its own. app.load("Window")is what an app loads;App(window_view="Name")loadsName_View.yamlas the window when the app is created.- Hot reload (
app.run(hot_reload=True)) applies edits to the window view live.
The title bar¶
title_bar: on a window, and kind: TitleBar anywhere, make the same bar: it drags the window, maximizes on a
double-click, and holds the icon and title, your own children, and the buttons. Its buttons need no ViewModel method:
window.minimize, window.maximize, window.restore, window.toggle_maximized and window.close are handlers anyone
can use, on any node, in any view (handlers: {on_click: window.close}).
A dialog or a sheet takes one too, with a close button that dismisses it:
- id: bar
kind: TitleBar
title: Rename
buttons: [dismiss]
surface.dismiss is the handler behind it. Custom Title Bars has the rest.
Embedded views¶
view: shows another *_View.yaml where the node is, as a view of its own: its own ids (two views can both have
id: root), and its own ViewModel when it has one.
- id: left
view: Left_View.yaml # a file, or a name found in the project: `view: Left`
with: {size: 3} # optional: what its ViewModel is given, by keyword
style: {width: 220} # where it sits and how big, in its parent
It takes id, view, with, route, style, classes, a11y and group, and nothing else. A view with no ViewModel is
fine: it is a fragment of layout with its own ids. view.embedded("left") is the embedded view from Python, and
view.viewmodel is a view's own ViewModel. Editing the embedded file reloads it; editing the host keeps an embedded view that
didn't change, with its state.
This is not a component: a component is a fragment with params: expanded into the host, sharing its ids;
an embedded view is a whole view.
Routed views¶
Give a view: node a route: in the window view and it is a screen of the app:
children:
- id: nav
component: NavigationRailScreens
with:
items:
- {label: Notes, icon: home, screen: Main}
- {label: Settings, icon: settings, screen: Settings}
- id: screens
kind: Container
style: {flex: fill}
children:
- {id: main, view: Main_View.yaml, route: ""}
- {id: settings, view: Settings_View.yaml, route: settings}
- The screen's name is the file's name without
_View.yaml;app.navigate("Settings"),app.navigate_to("settings"),app.show("Settings")andapp.screen("Settings")all work. - Only the current screen shows; the others are hidden, out of the layout, with their state and ViewModels kept.
handlers: {on_click: navigate.Settings}goes to a screen from any node,navigate.backandnavigate.forwardmove in the history, andapp.current_screenis a value a binding can read.NavigationRailScreensis a rail whose destinations navigate and fill while their screen is current.- A
route:is an error outside the window view, and a view can be routed once. - In the current language a view call with a
route:is the screen (named for the view it calls), the screen showing reads the params it was reached with asapp.params, andnavigate_to('Note', {'id': 3})ornavigate_route('notes/' + str(id))go there from a handler.
Call app.navigate_to("") after loading the window; nothing shows until a screen is current.
Several windows¶
app.window is the main window. app.open_window("Prefs") builds Prefs_View.yaml in a new OS window and returns its AppWindow (also app.windows, app.window_of("prefs"),
app.close_window("prefs")). The view's widget: Window root gives that window its title, size, minimum size, borderless and flags (without one it is 480 by 320). The view has the ViewModel that
serves its name, and the app's theme, stylesheet and app. From a handler: open_window('Prefs'), and open_window('Prefs', True) for a modal one.
prefs = app.open_window("Prefs", modal=True) # blocks the window it was opened from until it closes
prefs.on_close(save)
prefs.center() # where the system lets an app place a window
- Before
run()the window opens with the main one; while it runs, on the loop's next turn. window.close,window.minimizeand the other window handlers act on the window their view is in.modal=Trueputs a dimming layer over the parent (the main window, orparent=anotherAppWindow) that takes its clicks and keys until the child closes. The operating system is not told the child belongs to the parent (tre has no owner window), so the two can still be raised and minimized separately.- Closing a second window leaves the app running; closing the main window closes every window.
- A second window has no screens or routes (those belong to the main window's frame), and its title bar's maximize button reads
app.maximized, which is the main window's. - Positions are the system's to give: Wayland does not let an app place or centre a window.
Docks¶
kind: Dock holds kind: DockPanels around the content. Each panel says where it docks with zone:, a style field:
- id: dock
kind: Dock
style: {flex: fill}
children:
- id: files
kind: DockPanel
title: Files # the panel's tab, when its zone has several
style: {zone: left, width: 220}
children:
- {id: tree, view: Files_View.yaml}
- id: editor
kind: DockPanel
title: Editor
style: {zone: center}
children: [...]
- id: output
kind: DockPanel
title: Output
style: {zone: bottom, height: 160}
children: [...]
- The zones are
left,right,top,bottomandcenter. A left or right zone's size is its panel'swidth; a top or bottom one's is itsheight. - Panels in one zone are its tabs. A panel alone has no tab strip. The user drags a tab to another zone, and drags the handles between zones to resize them.
- A
DockPanelinside aDockPanelis a split of it: side by side (the parent'sflex_direction: horizontal, the default) or top and bottom (vertical), with a handle between the halves. A half with awidth(orheight) of its own keeps it, and the others share what is left. A panel has either splits or content, not both. zone:is an error on anything but aDockPanelin aDock; a window has oneDock.- Editing the YAML while the app runs keeps the layout the user made: which zone each panel is in, which tab shows, the sizes.
dock = view.dock_host("dock")
dock.set_size("left", 280)
saved = dock.layout() # plain data: where every panel is, which one shows, the sizes
dock.restore(saved) # ...put back later, e.g. when the app starts
Dragging a panel onto one half of a split isn't supported: a dragged panel docks in the five zones.