JuliaGizmos / InteractBase.jl

Build interactive HTML5 widgets in Julia
Other
27 stars 23 forks source link

Heavily WIP: adjust to the recipe framework in UIRecipesBase #46

Closed piever closed 6 years ago

shashi commented 6 years ago

Can you say a bit about what's the interface of AbstractUI?

piever commented 6 years ago

It's still WIP, here I'm just setting it up so that things will work in UIRecipesBase, where all the interesting code will be. The idea is to provide a macro (say @ui) that will transform symbols to the respective widgets or observables and x::Symbol = ... expression in ui[x] = map(...) expressions. To clarify with an example, let's say we want to create two dropdowns (the second one being a function of the first) and when the second is selected, plot something. It would be:

my_ui = @ui begin
  :s1 = dropdown(["x", "y", "z"])
  :s2 = dropdown(f(:s1))
  :plt = plot(g(:s1[], :s2))

To break down what happens, the macro expansion does:

  1. Initialize a ui: s = UI()
  2. The first line is :s1 = dropdown(["x", "y", "z"]): the RHS has no symbols, so we simply set s[:s1] = dropdown(["x", "y", "z"])
  3. The second line is :s2 = dropdown(f(:s1)) which has a symbol on the RHS, so it becomes s[:s2] = map(x -> dropdown(f(x)), s[:s2])
  4. The third line :plt = plot(g(:s1[], :s2)) has two symbols, but one of them is :s1[] which means take the value, so it translates to s[:plt] = map(x -> plot(g(d[s1][], x)), s[s2]) (this is necessary if we want the widget to only update as a function of :s2 but not :s1 (meaning, only plot when the second dropdown is set)

This is similar to manipulate (and I can make extra tricks, for example, always widgetify the RHS, meaning if the function returns a number I'd use a slider. The advantage is that it doesn't special case input and output, but everything can be a function of what was defined already.

Finally I want to add a @layout! macro to set the layout. By default things are stacked vertically, but one can set a custom layout with:

@layout! my_ui vbox(hbox(:s1, :s2), :plt)

Once again symbols correspond to elements of the UI.

After the UI is created, I plan to have also the various @on, @map! macros and so on to define behaviors in the UI using symbols to refer to widgets (pretty much like JuliaDBMeta).

As a final note, I want to have a ui function that allows to create any UI defined with this macro and ui to be defined in UIRecipesBase and a div function defined in UIRecipesBase and extended in Interact to allow users (if they wish to do so) to define recipes without taking dependencies on the WebIO stack. Of course it's easiest to define the layout in users depend on CSSUtil, but if they don't wish to do so they can define flexboxes starting from div, which should allow for a fully customizable layout.

shashi commented 6 years ago

I quite like this syntax!

The second line is :s2 = dropdown(f(:s1)) which has a symbol on the RHS, so it becomes s[:s2] = map(x -> dropdown(f(x)), s[:s2])

So I assume you're planning to take the outermost call Expr and rewrite it as a map? What if you want to pass an observable to a function (this is common in the way we structure WebIO code).

always widgetify the RHS

Does this mean any assignment in the block will be treated as a new widget being created?

@layout! my_ui vbox(hbox(:s1, :s2), :plt)

This is nice!

It would also help right now to think about composability of multiple UIs...

Specifically

  1. Naming the UI while registering it... Giving a name to every UI definition will allow e.g. Sputnik to show a list of available UIs for every state it's in. (see 3)
  2. Separate the compute from output -- so in this case, there would be a place which creates the output data, and a different place which defines how to plot the data.
  3. If a UI outputs an object of type T, Sputnik should be able to show all the UIs that take T as an argument.
piever commented 6 years ago

So I assume you're planning to take the outermost call Expr and rewrite it as a map? What if you want to pass an observable to a function (this is common in the way we structure WebIO code).

I may need to think more carefully about this.The syntax could be:

always widgetify the RHS

Does this mean any assignment in the block will be treated as a new widget being created?

My idea is that I can distinguish between normal assignment and UI widget assignment by checking whether the LHS is a symbol, so the rule could be that if the LHS is a symbol, the RHS gets widgetified.