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.@widget — Macro.@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::Widgetthat can be used to refer to the widget in the function bodyall lines of the type
sym::Symbol = exprare replaced withwdgname[sym] = @map(wdgname, expr), seeWidgets.@mapfor more detailsan 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...)
Auxiliary functions
Widgets.@map — Macro.@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).
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).
Widgets.@on — Macro.@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).
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)
Widgets.@display! — Macro.@display!(d, x)
Computes Widgets.@map(d, x) and sets d.display to be the result (see Widgets.@map for more details).
Widgets.@layout — Macro.@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).
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);Defining custom widgets without depending on Interact
Widgets.@nodeps — Macro.@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)
endA simpler approach: the manipulate macro
InteractBase.@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