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::Widget
that can be used to refer to the widget in the function bodyall lines of the type
sym::Symbol = expr
are replaced withwdgname[sym] = @map(wdgname, expr)
, seeWidgets.@map
for 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)
end
A 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