-
Notifications
You must be signed in to change notification settings - Fork 14
Golden Layout
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.
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.
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 }
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
)
Include the appropriate WebPart
definitions when starting the web server. For Golden Layout two definitions are required:
- An assembly
WebPart
so embedded resource files inAardvark.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
Layouts are made up of three different item types:
type Layout =
| Element of Element
| Stack of Stack
| RowOrColumn of RowOrColumn
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 totrue
if omitted. -
header (Header | Header option)
- Determines the position of the header or if one is shown at all (defaultHeader.Top
). If set toNone
, 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 ofHeaderButtons
in theLayoutConfig
is used (see Configuration). Note that hiding the close button with this option does not remove the close button from the tab itself. Useclosable 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)
- Iftrue
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 istrue
.
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 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 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 ofsize (V2i)
only setting the width. -
height (int)
- Variant ofsize (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.
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 toGoldenLayout.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 asonLayoutChanged
but the layout is passed in its serialized form as a string. The string can be deserialized withGoldenLayout.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 withMessage.SaveLayout
this event can be used to store the layout whenever it changed without having to pass it to the Media application first.
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 orTheme resourcePath
whereresourcePath
is a path to a CSS file. Default isTheme.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 iffalse
. Default istrue
. -
PopOutWholeStack: bool
- Determines whether the popout header button affects the whole stack or just the active tab. Default istrue
. -
DragBetweenWindows: bool
- Determines whether elements may be dragged from one window to another. Default istrue
. -
DragToNewWindow: bool
- Determines whether elements may be dragged and dropped outside the containing window creating a new popout window. Default istrue
. -
HeaderButtons: Buttons
- Default buttons to be displayed in the headers. Default isButtons.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 istrue
. -
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 ifPopInOnClose
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.
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
, andstack
are functions rather than computation expressions. Their first parameter is the size as weight.stack
also expects anactive
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.