Select Git revision

Albin Heimerson authored
gui.jl 6.02 KiB
export Widget_Container, GUI, set!, add!, animate
"""
Widget_Container contains an array of widgets and a dict, so that a widget can be accessed through getindex with its associated symbol:
wc[:foo]
"""
mutable struct Widget_Container
#Widgets with symbols
#widgets::Array{WebIO.Node{WebIO.DOM},1}
widgets
vals::Dict
end
function Widget_Container()
Widget_Container([], Dict())
end
"""
getindex_(widget::Widget_Container, s::Symbol)
returns the index of the widget associated with symbol
"""
function getindex_(widget::Widget_Container, s::Symbol)
widget.vals[s]
end
"""
Base.getindex(widget::Widget_Container, n::Integer)
returns the widget at the given index or the one associated with given symbol
"""
function Base.getindex(widget::Widget_Container, n::Integer)
widget.widgets[n]
end
function Base.getindex(widget::Widget_Container, s::Symbol)
widget.widgets[getindex_(widget, s)]
end
"""
GUI(widgets::Widget_Container, dom::WebIO.Node)
A GUI contains widgets in a widget container as well as the DOM of the UI.
Accessing a widget: gui.widgets[:foo], where foo is the symbol associated with the widget.
Accessing an observable value: gui[:foo].
Accessing the DOM object: gui(), which adds non-displayed dom nodes to ensure the widgets don't time out.
"""
mutable struct GUI
widgets::Widget_Container
dom::WebIO.Node
data::Array{Array}
values::Array
#values contains the values of the widget observables, but isn't automatically updated
end
function GUI(widgets::Widget_Container, dom::WebIO.Node)
GUI(widgets, dom, [[]], [])
end
function GUI(n::Integer)
GUI(Widget_Container(), Node(:div), [Float64[] for i in 1:n], [])
end
function GUI()
GUI(10)
end
function (gui::GUI)()
# This ensures that observables don't time out, removing this will
# break everything and it will be impossible to track down.
watched = Node(:div, style=Dict(:display=>"none"), gui.widgets.widgets...)
Node(:div, gui.dom, watched)
end
"""
set!(gui::GUI, widgets::Widget_Container)
Used to set the widgets, the dom or the data array of the GUI. This replaces any existing widget or dom in the GUI.
"""
function set!(gui::GUI, widgets::Widget_Container)
gui.widgets = widgets
gui.values = [observe(widget).val for widget in gui.widgets.widgets]
end
function set!(gui::GUI, dom::WebIO.Node)
gui.dom = dom
end
function set!(gui::GUI, dom::WebIO.Scope)
gui.dom = Node(dom)
end
function set!(gui::GUI, data::Array)
gui.data = data
end
"""
Base.push!(gui::GUI, value, index::Integer)
Pushes data to the data array(s) to the provided index/indices.
"""
function Base.push!(gui::GUI, value, index::Integer)
Base.push!(gui.data[index], value)
end
function Base.push!(gui::GUI, values::Tuple, indices::Tuple)
foreach(zip(values, indices)) do t
Base.push!(gui.data[t[2]], t[1])
end
end
function Base.getindex(gui::GUI, s::Symbol)
observe(gui.widgets[s])[]
end
function Base.setindex!(gui::GUI, newval, s::Symbol)
observe(gui.widgets[s])[] = newval
end
"""
add!(gui::GUI, widgets::Widget_Container)
Add a Widget_Container to the GUI Widget_Container. This makes it possible to access widgets or observables using their associated symbols and ensures the widgets don't time out.
"""
function add!(gui::GUI, widgets::Widget_Container)
newdict = copy(widgets.vals)
for key in keys(newdict)
newdict[key] += length(gui.widgets.widgets)
end
gui.widgets.widgets = vcat(gui.widgets.widgets, widgets.widgets)
gui.widgets.vals = merge(gui.widgets.vals, newdict)
gui.values = vcat(gui.values, [observe(w)[] for w in widgets.widgets])
end
function heartbeat(f, t, n=0)
if n!=0
@async for i in 1:n
t0 = time()
f()
tdif = time()-t0
sleep(max(0, t-tdif))
end
else
@async while true
t0 = time()
f()
tdif = time()-t0
sleep(max(0,t-tdif))
end
end
end
function has_changed(gui::GUI, s::Symbol)
if gui[s] == gui.values[getindex_(gui.widgets, s)]
return false
else
gui.values[getindex_(gui.widgets, s)] = gui[s]
return true
end
end
function has_changed(gui::GUI, s::Array{Symbol, 1})
!all(.!(has_changed.(gui, s)))
end
"""
animate(gui::GUI, t=0.5)
Usage: animate(gui, t) will periodically update all widgets, that is, call the
listeners of all observables associated with widgets in the gui
animate(gui, s::symbol(s)) will periodically update the widget(s) associated with s
animate(gui, s::symbol(s), w::symbol(s)) will periodically update the widget(s) associated
with s only if the widget(s) associated with w has/have changed
Note: Widgets will stop working after a while if their associated graphic isn't
in the responder. This seems to be due to WebIO.
"""
function animate(gui::GUI, t=0.5)
ks = [k for k in keys(gui.widgets.vals)]
f = updater_function(gui, ks)
heartbeat(f, t)
end
function animate(gui::GUI, s::Union{Symbol, Array}, t=0.5)
# println("animation station", s)
f = updater_function(gui, s)
heartbeat(f, t)
end
function animate(gui::GUI, s::Union{Symbol, Array}, w::Union{Symbol, Array}, t=0.1)
f = updater_function(gui, s, w)
heartbeat(f, t)
end
function update(gui::GUI, s::Symbol, watchsymbol=:0)
# s is the symbol associated with widget we wish to update
# if given a watchsymbol, it will only update the widget associated with s
# if the value of the widget associated with watchsymbol has changed
w = gui.widgets[s]
if watchsymbol==:0||has_changed(gui, watchsymbol)
#observe(w).listeners[2](observe(w)[])
# This takes too long, can run 1 fps but not even 10 fps
observe(w)[] = observe(w)[]
end
end
function update(gui::GUI, a::Array, watchsymbol=:0)
W = [gui.widgets[s] for s in a]
if watchsymbol==:0||has_changed(gui, watchsymbol)
[observe(w).listeners[2](observe(w)[]) for w in W]
end
end
function updater_function(gui::GUI, s::Union{Symbol, Array}, w=:0)
()->update(gui::GUI, s, w)
end