module Viewer using Blink using Colors using Printf export ConstructionViewer, display!, opentools!, closetools! # === Blink utilities === append_to_head!(w, type, content) = @js w begin @var element = document.createElement($type) element.appendChild(document.createTextNode($content)) document.head.appendChild(element) end style!(w, stylesheet) = append_to_head!(w, "style", stylesheet) script!(w, code) = append_to_head!(w, "script", code) # === construction viewer === mutable struct ConstructionViewer win::Window function ConstructionViewer() # create window and open developer console win = Window(Blink.Dict(:width => 620, :height => 830)) # set stylesheet style!(win, """ body { background-color: #ccc; } /* the maximum dimensions keep Ganja from blowing up the canvas */ #view { display: block; width: 600px; height: 600px; margin-top: 10px; margin-left: 10px; border-radius: 10px; background-color: #f0f0f0; } #control-panel { width: 600px; height: 200px; box-sizing: border-box; padding: 5px 10px 5px 10px; margin-top: 10px; margin-left: 10px; border-radius: 10px; background-color: #f0f0f0; } #control-panel > div { margin-top: 5px; padding: 2px; border-radius: 5px; font-family: monospace; } """) # load Ganja.js loadjs!(win, "https://unpkg.com/ganja.js") # create global functions and variables script!(win, """ // create algebra var CGA3 = Algebra(4, 1); // initialize element list and palette var elements = []; var palette = []; // declare handles for the view and its options var view; var viewOpt; // declare handles for the controls var controlPanel; var visControls; // create scene function function scene() { commands = []; for (let n = 0; n < elements.length; ++n) { if (visControls[n].checked) { commands.push(palette[n], elements[n]); } } return commands; } function updateView() { requestAnimationFrame(view.update.bind(view, scene)); } """) @js win begin # create view viewOpt = Dict( :conformal => true, :gl => true, :devicePixelRatio => window.devicePixelRatio ) view = CGA3.graph(scene, viewOpt) view.setAttribute(:id, "view") view.removeAttribute(:style) document.body.replaceChildren(view) # create control panel controlPanel = document.createElement(:div) controlPanel.setAttribute(:id, "control-panel") document.body.appendChild(controlPanel) end new(win) end end function display!(viewer::ConstructionViewer, elements::Matrix) # load elements elements_full = [ [0; elt; fill(0, 26)] for elt in eachcol(elements) ] @js viewer.win elements = $elements_full.map((elt) -> @new CGA3(elt)) # generate palette. this is Gadfly's `default_discrete_colors` palette, # available under the MIT license palette = distinguishable_colors( length(elements_full), [LCHab(70, 60, 240)], transform = c -> deuteranopic(c, 0.5), lchoices = Float64[65, 70, 75, 80], cchoices = Float64[0, 50, 60, 70], hchoices = range(0, stop=330, length=24) ) palette_packed = [RGB24(c).color for c in palette] @js viewer.win palette = $palette_packed # generate visibility controls @js viewer.win begin controlPanel.replaceChildren() visControls = [] end for n in 1:size(elements, 2) index_str = string(n) vec_str = join(map(t -> @sprintf("%.3f", t), elements[:, n]), ", ") style_str = "background-color: #$(hex(palette[n]));" println(style_str) @js viewer.win begin # create container @var container = document.createElement(:div) container.setAttribute(:style, $style_str) # create checkbox @var checkbox = document.createElement(:input) checkbox.setAttribute(:type, "checkbox") checkbox.setAttribute(:id, $index_str) checkbox.setAttribute(:checked, "true") checkbox.addEventListener(:input, updateView) visControls.push(checkbox) container.appendChild(checkbox) # create label @var label = document.createElement(:label); label.setAttribute(:for, $index_str) label.appendChild(document.createTextNode($vec_str)) container.appendChild(label) # add the control to the control panel controlPanel.appendChild(container) end end # update view @js viewer.win updateView() end function opentools!(viewer::ConstructionViewer) size(viewer.win, 1240, 830) opentools(viewer.win) end function closetools!(viewer::ConstructionViewer) closetools(viewer.win) size(viewer.win, 620, 830) end end # ~~~ sandbox setup ~~~ # in the default view, e4 + e5 is the point at infinity elements = sqrt(0.5) * BigFloat[ 1 1 -1 -1 0; 1 -1 1 -1 0; 1 -1 -1 1 0; 0 0 0 0 -sqrt(6); 1 1 1 1 2 ] # show construction viewer = Viewer.ConstructionViewer() Viewer.display!(viewer, elements)