Custom widgets

Custom widgets

Besides the standard widgets, Interact provides a framework to define custom GUIs. This is currently possible with two approaches, the full featured @widget macro and the simple to use but more basic @manipulate macro.

The Widget type

The Widget type can be used to create custom widgets. The types is parametric, with the parameter being the name of the widget and it takes as argument a OrderedDict of children.

For example:

d = OrderedDict(:label => "My label", :button => button("My button"))
w = Interact.Widget{:mywidget}(d)

Children can be accessed and modified using getindex and setindex! on the Widget object:

println(w[:label])
w[:label] = "A new label"

The @output! and @display! macros can be used to set the output of the widget and define how to display it.

@output! w $(:button) > 5 ? "You pressed me many times" : "You didn't press me enough"
@display! w dom"div"($(_.output), style = Dict("color" => "red"))

Finally the @layout! macro allows us to set the layout of the widget:

@layout! w hbox(vbox(:label, :button), _.display)

The recipe macro

To simplify adding children to a custom widget (as well as to register it as a "widget recipe"), a @widget macro is provided.

See Creating custom widgets for examples.

Widgets.@widgetMacro.

@widget(wdgname, func_call)

Special macro to create "recipes" for custom widgets. The @widget macro takes to argument, a variable name wdgname and a function call func_call. The function call is changed by the macro in several ways:

  • an extra line is added at the beginning to initiliaze a variable called wdgname::Widget that can be used to refer to the widget in the function body

  • all lines of the type sym::Symbol = expr are replaced with wdgname[sym] = @map(wdgname, expr), see Widgets.@map for more details

  • an extra line is added at the end to return wdgname

The macro then registers the function func_call and exports it. It also overloads the widget function with the following signature:

Widgets.widget(::Val{Symbol(func_name)}, args...; kwargs..) = func_name(args...; kwargs...)

source

Auxiliary functions

Widgets.@mapMacro.

@map(d, x)

Apply the expression x to the widget d, replacing e.g. symbol :s with the corresponding Observable observe(d[:s]). To use the value of some of d's components, use :s[]. Use $(:s) if you want the output to update automatically as soon as the value of observe(d[:s]) changes. In this context, _ refers to the whole widget. To use actual symbols, escape them with ^, as in ^(:a).

Examples

julia> using DataStructures, InteractBase, Observables

julia> t = Widgets.Widget{:test}(OrderedDict(:a => Observable(2), :b => slider(1:100), :c => button()));

This updates as soon as observe(t[:a]) or observe(t[:b]) change:

julia> Widgets.@map t $(:a) + $(:b)
Observables.Observable{Int64}("ob_31", 52, Any[])

whereas this only updates when button :c is pressed:

julia> Widgets.@map t ($(:c); :a[] + :b[])
Observables.Observable{Int64}("ob_33", 52, Any[])

@map(x)

Curried version of @map(d, x): anonymous function mapping d to @map(d, x).

source
Widgets.@map!Macro.

@map!(d, target, x)

In the expression x to the widget d, replace e.g. symbol :s with the corresponding Observable observe(d[:s]). To use the value of some of d's components, use :s[]. As soon as one of the symbols wrapped in a $ changes value, the observable target gets updated with the value of that expression. If no symbol is wrapped in a $, nothing happens. In this context, _ refers to the whole widget. To use actual symbols, escape them with ^, as in ^(:a).

Examples

julia> using DataStructures, InteractBase, Observables

julia> t = Widgets.Widget{:test}(OrderedDict(:a => Observable(2), :b => slider(1:100), :c => button()));

This updates t[:a] as soon as the user moves the slider:

julia> Widgets.@map! t :a $(:b);

@map!(target, x)

Curried version of @map!(d, target, x): anonymous function mapping d to @map(d, target, x).

source
Widgets.@onMacro.

@on(d, x)

In the expression x to the widget d, replace e.g. symbol :s with the corresponding Observable observe(d[:s]). To use the value of some of d's components, use :s[]. As soon as one of the symbols wrapped in a $ changes value, the expression x gets executed with the updated value. If no symbol is wrapped in a $, nothing happens. In this context, _ refers to the whole widget. To use actual symbols, escape them with ^, as in ^(:a).

Examples

julia> using DataStructures, InteractBase, Observables

julia> t = Widgets.Widget{:test}(OrderedDict(:a => Observable(2), :b => slider(1:100), :c => button()));

This prints the value of the slider as soon as the user moves it:

julia> Widgets.@on t println($(:b));

@on(x)

Curried version of @on(d, x): anonymous function mapping d to @on(d, x).

source

Customizing output, display and layout

Widgets.@output!Macro.

@output!(d, x)

Computes Widgets.@map(d, x) and sets d.output to be the result (see Widgets.@map for more details). d.display is also set by default to match d.output. To have a custom display use @display!(d, expr) _after_ @output!(d, x)

source

@display!(d, x)

Computes Widgets.@map(d, x) and sets d.display to be the result (see Widgets.@map for more details).

source
Widgets.@layoutMacro.

@layout(d, x)

Apply the expression x to the widget d, replacing e.g. symbol :s with the corresponding subwidget d[:s] To create a layout that updates automatically as some Widget or Observable updates, use $(:s). In this context, _ refers to the whole widget. To use actual symbols, escape them with ^, as in ^(:a).

Examples

julia> using DataStructures, InteractBase, CSSUtil

julia> t = Widgets.Widget{:test}(OrderedDict(:vertical => Observable(true), :b => slider(1:100), :c => button()));

julia> Widgets.@layout t vertical ? vbox(:b, CSSUtil.vskip(1em), :c) : hbox(:b, CSSUtil.hskip(1em), :c);

@layout(x)

Curried version of @layout(d, x): anonymous function mapping d to @layout(d, x).

source
Widgets.@layout!Macro.

@layout!(d, x)

Set d.layout to match the result of Widgets.@layout(x). See Widgets.@layout for more information.

Examples

julia> using DataStructures, InteractBase, CSSUtil

julia> t = Widgets.Widget{:test}(OrderedDict(:b => slider(1:100), :c => button()));

julia> @layout! t hbox(:b, CSSUtil.hskip(1em), :c);
source

Defining custom widgets without depending on Interact

Widgets.@nodepsMacro.

@nodeps(expr)

Macro to remove need to depend on package X that defines a recipe to use it in one's own recipe. For example, InteractBase defines dropwdown recipe. To use dropdown in a recipe in a package, without depending on InteractBase, wrap the dropdown call in the @nodeps macro:

@widget wdg function myrecipe(i)
    :label = "My recipe"
    :dropdown = Widgets.@nodeps dropdown(i)
end
source

A simpler approach: the manipulate macro

@manipulate expr

The @manipulate macro lets you play with any expression using widgets. expr needs to be a for loop. The for loop variable are converted to widgets using the widget function (ranges become slider, lists of options become togglebuttons, etc...). The for loop body is displayed beneath the widgets and automatically updated as soon as the widgets change value.

Use throttle = df to only update the output after a small time interval dt (useful if the update is costly as it prevents multiple updates when moving for example a slider).

Examples

using Colors

@manipulate for r = 0:.05:1, g = 0:.05:1, b = 0:.05:1
    HTML(string("<div style='color:#", hex(RGB(r,g,b)), "'>Color me</div>"))
end

@manipulate throttle = 0.1 for r = 0:.05:1, g = 0:.05:1, b = 0:.05:1
    HTML(string("<div style='color:#", hex(RGB(r,g,b)), "'>Color me</div>"))
end

@layout! can be used to adjust the layout of a manipulate block:

using Widgets, CSSUtil, WebIO

ui = @manipulate throttle = 0.1 for r = 0:.05:1, g = 0:.05:1, b = 0:.05:1
    HTML(string("<div style='color:#", hex(RGB(r,g,b)), "'>Color me</div>"))
end
@layout! ui dom"div"(_.display, vskip(2em), :r, :g, :b)
ui
source