From 7a8b582a4a8ca48127d733f0c8de31d05d67de29 Mon Sep 17 00:00:00 2001
From: jwikmark <jacob@wikmark.se>
Date: Mon, 26 Feb 2018 17:19:29 +0100
Subject: [PATCH] Added files

---
 src/LabGUI.jl     |  34 +++++++++-
 src/construct.jl  |  52 +++++++++++++++
 src/gridmaker.jl  |  50 ++++++++++++++
 src/gui.jl        | 165 ++++++++++++++++++++++++++++++++++++++++++++++
 src/svg_helper.jl |  66 +++++++++++++++++++
 5 files changed, 365 insertions(+), 2 deletions(-)
 create mode 100644 src/construct.jl
 create mode 100644 src/gridmaker.jl
 create mode 100644 src/gui.jl
 create mode 100644 src/svg_helper.jl

diff --git a/src/LabGUI.jl b/src/LabGUI.jl
index f9ab804..ffb52ed 100644
--- a/src/LabGUI.jl
+++ b/src/LabGUI.jl
@@ -1,5 +1,35 @@
 module LabGUI
+using WebIO, InteractNext
+include("gridmaker.jl")
+include("gui.jl")
+include("construct.jl")
+include("svg_helper.jl")
 
-# package code goes here
 
-end # module
+#We redefine WebIO.newid to prevent nodes from getting infinitely high values,
+# and Base.show is redefined to prevent memory leaks when constructing SVGs
+const max_nodes = 100000
+let count=1
+		global newid
+		node_space=Dict()
+		function WebIO.newid(prefix)
+				if count == max_nodes
+						count = 0
+				end
+				string(prefix, "-", count+=1)
+		end
+end
+
+function Base.show(io::IO, m::MIME"text/html", x::Node)
+		if haskey(x.props, :id)
+				id = "$(x.props[:id])-parent"
+		else
+				id = WebIO.newid("node")
+		end
+		x in keys(WebIO.showcbs) && (x = WebIO.showcbs[x](id))
+		write(io, """<div id=$id></div>
+			  <script>WebIO.mount('$id', '#$id',""")
+		WebIO.jsexpr(io, x)
+		write(io, ")</script>")
+end
+end
diff --git a/src/construct.jl b/src/construct.jl
new file mode 100644
index 0000000..2e0315f
--- /dev/null
+++ b/src/construct.jl
@@ -0,0 +1,52 @@
+export @construct
+
+function make_widget(binding)
+    if binding.head != :(=)
+        error("@construct syntax error.")
+    end
+    sym, expr = binding.args
+    Expr(:(=), esc(sym),
+         Expr(:call, widget, esc(expr), string(sym)))
+end
+
+function display_widgets(widgetvars)
+    map(v -> Expr(:call, esc(:display), esc(v)), widgetvars)
+end
+
+function map_block(block, symbols)
+    lambda = Expr(:(->), Expr(:tuple, symbols...),
+                  block)
+    :(map($lambda, $(map(s->:(obs($s)), symbols)...)))
+end
+
+function symbols(bindings)
+    map(x->x.args[1], bindings)
+end
+
+macro construct(expr)
+    if expr.head != :for
+        error("@construct syntax is @construct for ",
+              " [<variable>=<domain>,]... <expression> end")
+    end
+    block = expr.args[2]
+    if expr.args[1].head == :block
+        bindings = expr.args[1].args
+    else
+        bindings = [expr.args[1]]
+    end
+    syms = symbols(bindings)
+
+    widgets = map(make_widget, bindings)
+	dict = Dict(zip(syms, 1:length(syms)))
+    quote
+			(Widget_Container([$(widgets...)], $(dict)), WebIO.render($(esc(map_block(block, syms)))))
+    end
+end
+
+widget(x::Range, label="") = slider(x; label=label)
+widget(x::Observable, label="") = x
+widget(x::WebIO.Node{<:Any}, label="") = x
+widget(x::AbstractVector, label="") = togglebuttons(x, label=label)
+widget(x::Associative, label="") = togglebuttons(x, label=label)
+widget(x::Bool, label="") = checkbox(x, label=label)
+widget(x::AbstractString, label="") = textbox(x, label=label, typ=AbstractString)
diff --git a/src/gridmaker.jl b/src/gridmaker.jl
new file mode 100644
index 0000000..7a6d24f
--- /dev/null
+++ b/src/gridmaker.jl
@@ -0,0 +1,50 @@
+export make_row, make_grid, setindex_
+
+function make_row(columns::Integer)
+		#Returns tuple, needs to be Node(:div, tuple..) before use
+		style = Dict(:display => "inline-table",
+					 :verticalAlign => "top",
+					 :width => "$(100/columns)%")
+					 #:transform => "scale($(1/columns))")
+		(Node(:div, style=style, "Lorem Ipsum") for i in 1:columns)
+end
+
+function make_grid(rows::Integer, columns::Integer, width = 800, height = 1000)
+		style_row  = Dict(:height => "$(100/rows)%", :overflow => "auto")
+		style_grid = Dict(:width  => width, :height => height)
+		Node(:div, style=style_grid, (Node(:div, style=style_row, make_row(columns)...)
+					for i in 1:rows)...)
+end
+
+#specify this later, should be some sort of typedef of WebIO.Node{WebIO.DOM}
+
+
+# We give the content of the container if given two arguments
+# If given one argument, we give the child
+# This means a[r,c] = a[r][c][1] and not a[r][c]
+# We to do this to keep the style (width and formatting of the columns)
+
+function Base.getindex(grid::WebIO.Node{WebIO.DOM}, ind::Integer)
+		grid.children[ind]
+end
+
+function Base.getindex(grid::WebIO.Node{WebIO.DOM}, row::Integer, column::Integer)
+		grid[row][column][1]
+end
+
+
+
+function setindex_(grid::WebIO.Node{WebIO.DOM},
+				   new_value,
+				   ind::Integer)
+		#Make this look nicer
+		setchild(grid, ind, setchild(grid[ind], 1, Node(:div, new_value)))
+end
+
+function setindex_(grid::WebIO.Node{WebIO.DOM},
+				   new_value,
+				   row::Integer,
+				   column::Integer)
+		#Make this look nicer
+		setchild(grid, row, setindex_(grid[row], new_value, column))
+end		
diff --git a/src/gui.jl b/src/gui.jl
new file mode 100644
index 0000000..123d9d3
--- /dev/null
+++ b/src/gui.jl
@@ -0,0 +1,165 @@
+export Widget_Container, GUI, set!, add!, animate
+
+mutable struct Widget_Container
+		#Widgets with symbols
+		widgets::Array{WebIO.Node{WebIO.DOM},1}
+		vals::Dict
+end
+
+function Widget_Container()
+		Widget_Container([], Dict())
+end
+
+function getindex_(widget::Widget_Container, s::Symbol)
+		#returns the index of the widget associated with symbol
+		widget.vals[s]
+end
+
+function Base.getindex(widget::Widget_Container, n::Integer)
+		widget.widgets[n]
+end
+
+function Base.getindex(widget::Widget_Container, s::Symbol)
+		#returns the widget associated with symbol
+		widget.widgets[getindex_(widget, s)]
+end
+
+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 set!(gui::GUI, widgets::Widget_Container)
+		gui.widgets = widgets
+		gui.values = [obs(widget).val for widget in gui.widgets.widgets]
+end
+
+function set!(gui::GUI, dom::WebIO.Node)
+		gui.dom = dom
+end
+
+function set!(gui::GUI, data::Array)
+		gui.data = data
+end
+
+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)
+		obs(gui.widgets[s])[]
+end
+
+function Base.setindex!(gui::GUI, newval, s::Symbol)
+		obs(gui.widgets[s])[] = newval
+end
+
+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, [obs(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
+
+# 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)
+		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)
+				obs(w).listeners[2](obs(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)
+				[obs(w).listeners[2](obs(w)[]) for w in W]
+		end
+end
+
+function updater_function(gui::GUI, s::Union{Symbol, Array}, w=:0)
+		()->update(gui::GUI, s, w)
+end
+
diff --git a/src/svg_helper.jl b/src/svg_helper.jl
new file mode 100644
index 0000000..60522e0
--- /dev/null
+++ b/src/svg_helper.jl
@@ -0,0 +1,66 @@
+#Provides some routines for easier svg construction, along with some 
+#(hopefully!) sane defaults
+
+export svg_rect, svg_circle, svg_line, svg_polyline, svg_polygon, default_height, default_width, default_posy, default_posx, s_black, s_red, s_green, s_blue, stdstyle, stdattr, svgsvg_symb
+global const default_height = 900
+global const default_width  = 600
+global const default_posy   = 0
+global const default_posx   = 0
+
+global const s_black = (0,0,0)
+global const s_red   = (255,0,0)
+global const s_green = (0,255,0)
+global const s_blue  = (0,0,255)
+
+global const stdattr = Dict("y" => "$default_posy", 
+					 "x" => "$default_posx", 
+					 "height" => "$default_height", 
+					 "width" => "$default_width")
+
+global const stdstyle = Dict(:fill => "rgb$s_black")
+
+global const svgsvg_symb = instanceof(dom"svg:svg"())
+
+####################
+
+function svg_rect(width, height, x=0, y=0, style = stdstyle)
+		attr  = Dict("x" => "$x",
+					 "y" => "$y",
+					 "width" => "$width",
+					 "height" => "$height") 
+		Node(instanceof(dom"svg:rect"()), style = style, attributes = attr)
+end
+
+function svg_circle(x, y, r, style=stdstyle)
+		attr  = Dict("cy" => "$y",
+					 "cx" => "$x",
+					 "r" => "$r")
+		Node(instanceof(dom"svg:circle"()), style = style, attributes = attr)
+end
+
+function svg_line(x1, y1, x2, y2, style=stdstyle)
+		attr  = Dict("x1" => "$x1",
+					 "y1" => "$y1",
+					 "x2" => "$x2",
+					 "y2" => "$y2")
+		Node(instanceof(dom"svg:line"()), style = style, attributes = attr)
+end
+
+function svg_poly(ptsvector, style)
+		#there must be a better way to do this
+		pts = join((join((ptsvector[i][1], ptsvector[i][2]), ",") for i in 1:length(ptsvector)), " ")
+		attr = Dict("points" => pts)
+		Node(instanceof(dom"svg:polyline"()), style = style, attributes = attr)
+end
+
+function svg_polyline(ptsvector, thickness, color)
+		style = Dict(:strokeWidth => "$thickness",
+					 :fill => "none",
+					 :stroke => "rgb$color")
+		svg_poly(ptsvector, style)
+end
+
+function svg_polygon(ptsvector, color)
+		style = Dict( :fill => "rgb$color")
+		svg_poly(ptsvector, style)
+end
-- 
GitLab