Skip to content

Golden Layout

Martin edited this page Mar 20, 2024 · 4 revisions

Golden Layout is a TypeScript layout manager enabling users to build web pages with beautiful dynamic layouts. As of Aardvark.Media 5.4.3 applications may take full advantage of Golden Layout. Our fork extends the library with proper support for multiwindow layouts, allowing users to move elements seamlessly between multiple windows. Combined with Aardium you can build Desktop applications with rich user interfaces that take advantage of multiple monitors.

Setup

The 27 - GoldenLayout example demonstrates how to integrate Golden Layout in a Media application. In the following the required steps are explained in more detail.

Model & update

The implementation is located in the Aardvark.UI.Primitives.Golden namespace. Extend the model and message types of your application to handle your GoldenLayout instance:

[<ModelType>]
type Model = {
    . . .
    Layout: GoldenLayout
}

type Message =
    . . .
    | GoldenLayout of GoldenLayout.Message

Use GoldenLayout.create to initialize your model. The first parameter defines global configuration settings (see Configuration), the second parameter defines the initial layout (see Defining layouts):

let initialModel: Model = {
    . . .
    Layout = GoldenLayout.create layoutConfig defaultLayout
}

Pass messages to GoldenLayout.update from your update function:

let update (model: Model) (msg: Message) =
    match msg with
    . . .
    | GoldenLayout msg ->
        { model with Layout = model.Layout |> GoldenLayout.update msg }

View

Adapt your view function so that your root is pages(). Each element in your layout is displayed as an <iframe> element containing its ID as a query parameter in the target URL. The pages() function detects this query parameter in the incoming request and invokes the callback with Pages.Page. If the request does not originate from an element the callback is invoked with Pages.Body. In this case, call GoldenLayout.view to define the container element of the layout:

let view (model: AdaptiveModel) =
    pages (function
        | Pages.Page "firstElementId" -> . . .
        | Pages.Page "secondElementId" -> . . .
        . . .
        | Pages.Body ->
            let attributes = [
                style "width: 100%; height: 100%; overflow: hidden"
            ]

            GoldenLayout.view attributes model.Layout
    )

Web server

Include the appropriate WebPart definitions when starting the web server. For Golden Layout two definitions are required:

  • An assembly WebPart so embedded resource files in Aardvark.UI.Primitives are served (as required for any application using functionality from that namespace)
  • A special route handler for popout windows

If you are using Suave, your web server starting routine should look as follows:

Suave.WebPart.startServerLocalhost 4321 [
    . . .
    Reflection.assemblyWebPart typeof<Aardvark.UI.Primitives.EmbeddedResources>.Assembly
    GoldenLayout.WebPart.suave
]

For Giraffe there is no predefined route handler for popout windows. Use WebPart.ofRouteHtml instead:

open Aardvark.UI.Giraffe
open Aardvark.Service.Giraffe
open Aardvark.UI.Primitives.Golden

Server.startServerLocalhost 4321 Threading.CancellationToken.None [
    . . .
    Reflection.assemblyWebPart typeof<Aardvark.UI.Primitives.EmbeddedResources>.Assembly
    WebPart.ofRouteHtml GoldenLayout.WebPart.route GoldenLayout.WebPart.handler
] |> ignore

Defining layouts

Layouts are made up of three different item types:

type Layout =
    | Element     of Element
    | Stack       of Stack
    | RowOrColumn of RowOrColumn

Elements

Elements or components are the actual content of your interface. An element can be defined with the element computation expression:

element {
    id "render"
    title "3D View"
    closable true
    header Header.Top
    buttons Buttons.All
    minSize 20
    weight 1
    size 50
    keepAlive true
}

The ID is mandatory and used to identify the element in the view function (see View). The rest of the computation operations are optional configuration settings:

  • title (string) - Title shown in the header tab. Defaults to "Untitled" if omitted.
  • closable (bool) - Determines if the element can be closed via buttons in the header and tab. Defaults to true if omitted.
  • header (Header | Header option) - Determines the position of the header or if one is shown at all (default Header.Top). If set to None, the header will be hidden preventing the element from being dragged around or closed.
  • buttons (Buttons) - A bitmask determining the buttons that are displayed in the header. If omitted the value of HeaderButtons in the LayoutConfig is used (see Configuration). Note that hiding the close button with this option does not remove the close button from the tab itself. Use closable false if you want to prevent an element from being closed instead.
  • minSize (int) - The minimum size in pixels in either dimension the element should maintain.
  • weight (int) - Initial size as weight relative to siblings in case the parent is a row or column container.
  • size (int) - Initial size of the element (in percent) in case the parent is a row or column container.
  • keepAlive (bool) - If true the DOM element is hidden rather than destroyed if it is removed from the layout. This allows for faster restoring of the element but may come with a performance penalty. Default is true.

Stacks

Stacks are containers that can hold multiple elements. Their tabs are displayed side-by-side in the header. If there is not enough space to display all tabs, a dropdown menu is shown in the header. A stack can be defined with the stack computation expression:

stack {
    header Header.Top
    weight 1
    size 50
    
    element { id "foo" }
    element { id "bar" }
    
    // Alternatively use content to define children
    content sequenceOfElements
}

Content elements can be specified either directly inline or by using the content (Element seq) operation. The header (Header), weight (int), and size (int) operations are the same as for element expressions except that the header cannot be hidden.

Rows and columns

Rows are containers that display content items side by side. A vertical splitter element is placed in between each child allowing the user to adjust the width of the contents. Column containers are the same as row containers except that the content items are displayed on top of each other and their height can be manipulated. Row and column containers cannot hold just elements but also other row, column, and stack containers (although it does not make sense to put a row container within a row container, or a column container within a column container). They are defined with the row and column computation expressions respectively:

row {
    weight 1
    size 50

    column {
        stack {
            element { id "bar"}
            element { id "baz"}
        }
        
        element { id "hugo" } 
    } 
    
    element { id "foo" }
    
    // Alternatively use content to define children
    content sequenceOfItems
}

Content elements can be specified either directly inline or by using the content (Layout seq | Element seq | Stack seq | RowOrColumn seq) operation. The weight (int) and size (int) operations are the same as for element expressions.

Multi-window layouts

Multi-window layouts are defined and represented by the WindowLayout type. It holds the root layout of the main window and a list of popout windows. A popout window is represented by the PopoutWindow type. It contains the layout and optionally the screen position and size of the window. The popout computation expression is used to define a popout window:

popout {
    element { id "foo" }
    
    // Alternatively use root to define the root of the layout
    root someLayoutRoot
    
    position (V2i(12, 25))
    size (V2i(300, 300))
    width 300
    height 300
}

The root layout can be specified either directly inline or by using the root (^LayoutRoot) operation. ^LayoutRoot can be any of Layout, Element, Stack, or RowAndColumn. The rest of the computation operations are optional configuration settings:

  • position (V2i) - Determines the position of the window on the screen. If omitted a default position is used.
  • size (V2i) - Determines the size of the window. If omitted a default size is used.
  • width (int) - Variant of size (V2i) only setting the width.
  • height (int) - Variant of size (V2i) only setting the height.

The actual multi-window layout is defined with the layout computation expression:

layout {
    element { id "foo" }
    
    // Alternatively use root to define the root of the layout
    root someLayoutRoot
    
    popout {
        element { id "bla" }
        width 300
        height 400
    }
    
    popout {
        element { id "bla2" }
    }
    
    // Alternatively use popouts to define popout windows
    popouts sequenceOfPopoutWindows 
}

Both the root layout and the popout windows can be either specified inline or by using the root (^LayoutRoot) and popouts (PopoutWindow seq) operations respectively.

Saving and restoring layouts

The current layout can be changed programmatically by passing GoldenLayout.Message messages to the GoldenLayout.update function:

  • Message.ResetLayout - Restores the layout that was initially supplied to GoldenLayout.create.
  • Message.SetLayout (layout: Layout) - Sets the given layout.
  • Message.SetWindowLayout (layout: WindowLayout) - Sets the given multi-window layout.
  • Message.SaveLayout (key: string) - Saves the current layout in local storage with the given key. This includes adjustments made by the user, especially opened popout windows.
  • Message.LoadLayout (key: string) - Loads the layout from local storage with the given key. Has no effect if there is no layout stored in local storage for the given key.

Layout changes can be detected by passing one of the following event handlers as an attribute to the GoldenLayout.view function (see View):

  • onLayoutChanged (callback: WindowLayout -> 'msg) - The callback is invoked whenever the layout changes (e.g. the user resizes the window or moves an element around). The first argument contains the updated layout.
  • onLayoutChangedRaw (callback: string -> 'msg) - Same as onLayoutChanged but the layout is passed in its serialized form as a string. The string can be deserialized with GoldenLayout.Json.deserialize: string -> WindowLayout. This can be useful if you want to avoid unnecessary deserialization.
  • onLayoutChanged' (callback: unit -> 'msg) - This variant does not receive the current layout as an argument and avoids serialization and deserialization altogether. Combined with Message.SaveLayout this event can be used to store the layout whenever it changed without having to pass it to the Media application first.

Configuration

The LayoutConfig record holds configuration properties independent of the current layout:

  • Theme: Theme - The color theme to be applied. Can be either one of the predefined themes or Theme resourcePath where resourcePath is a path to a CSS file. Default is Theme.BorderlessDark.
  • PopInOnClose: bool - Determines whether items in popout windows are automatically docked when the window is closed. Shows a small dock button in popout windows if false. Default is true.
  • PopOutWholeStack: bool - Determines whether the popout header button affects the whole stack or just the active tab. Default is true.
  • DragBetweenWindows: bool - Determines whether elements may be dragged from one window to another. Default is true.
  • DragToNewWindow: bool - Determines whether elements may be dragged and dropped outside the containing window creating a new popout window. Default is true.
  • HeaderButtons: Buttons - Default buttons to be displayed in the headers. Default is Buttons.All = Buttons.Close ||| Buttons.Popout ||| Buttons.Maximize.
  • SetPopoutTitle: bool - Determines whether the document.title of popout windows is set and updated automatically to the document.title of the main window. Default is true.
  • MinItemWidth: int - Default minimum width (in pixels) of any item. Default is 20.
  • MinItemHeight: int - Default minimum height (in pixels) of any item. Default is 20.
  • DragProxyWidth: int - Width (in pixels) of drag proxy / preview elements. Default is 300.
  • DragProxyHeight: int - Height (in pixels) of drag proxy / preview elements. Default is 200.
  • LabelMinimize: string - Tooltip label of minimize button.
  • LabelMaximize: string - Tooltip label of maximize button.
  • LabelPopOut: string - Tooltip label of pop-out button.
  • LabelPopIn: string - Tooltip label of pop-in / dock button. Only visible if PopInOnClose is false.
  • LabelClose: string - Tooltip label of close button.
  • LabelTabDropdown: string - Tooltip label of stack tab dropdown. The dropdown is only visible when a stack has too many tabs to display at once.

Porting from the legacy Aardvark docking manager

Migrating from the old Aardvark docking manager to Golden Layout is pretty straightforward. Generally replace the DockConfig type with GoldenLayout and follow the steps in Setup. The view function probably already uses page() which is just a more general variant of pages(). Here you have to replace docking with GoldenLayout.view.

The old docking manager supports the same item types as Golden Layout, however there are some minor differences:

  • Rows are called horizontal
  • Columns are called vertical
  • horizontal, vertical, and stack are functions rather than computation expressions. Their first parameter is the size as weight. stack also expects an active parameter setting the active element. In Golden Layout the first element of a stack is active initially.
  • Elements are not closable by default. This is reversed in Golden Layout.

For example, the old docking layout

horizontal 10.0 [
    element { id "render"; title "Render View"; weight 20; isCloseable true }
    vertical 5.0 [
        element { id "controls"; title "Controls" }
        element { id "meta"; title "Layout Controls" }
    ]
]

is equivalent to the Golden Layout

row {
    element { id "render"; title "Render View"; weight 20 }
    column {
        weight 5
        element { id "controls"; title "Controls"; closable false }
        element { id "meta"; title "Layout Controls"; closable false }
    }
}

The old docking manager has a useCachedConfig (bool) option that causes layouts to be automatically saved and loaded when set to true. You can emulate this behavior with the messages and events described in Saving and restoring layouts.