Skip to content
Snippets Groups Projects
Select Git revision
  • d756f32389e88bba9ef4a8dec95698fbbea3ba94
  • master default protected
  • tanklabsim
  • julia1
4 results

gui.jl

Blame
  • 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