Clojure: how to architect desktop UI
Asked Answered
D

1

11

I'm trying to design a desktop UI for schematics, layout, drawing stuff. Just looking for high level advice from actual software designers.

Assuming an in-memory "database", (clojure map of arbitrary depth for all user data, and possibly another one for application preferences, etc.), I'm examining how to do the model-view-controller thing on these, where the data may be rendered and modified by any one or more of:

  1. A standalone text field that shows a single parameter, such as box width.
  2. An "inspector" type of view that shows multiple parameters of a selected object, such as box width, height, color, checkboxes, etc.
  3. A table/spreadsheet type of view that shows multiple parameters of multiple objects, potentially the whole database
  4. A graphical rendering of the whole thing, such as both schematic and layout view.

Modifying any one of these should show up immediately in every other active view, both text and graphical, not after clicking "ok"... so no modal boxes allowed. If for some reason the table view, an inspector view, and a graphical rendering are all in view, dragging the corner of the box graphically should immediately show up in the text, etc.

The platform in question is JavaFX, but I'd like a clean separation between UI and everything else, so I want to avoid binding in the JFX sense, as that ties my design data very tightly to JFX Properties, increases the graininess of the model, and forces me to work outside the standard clojure functions for dealing with data, and/or deal heavily with the whole getValue/setValue world.

I'm still assuming at least some statefulness/mutability, and the use of built-in Clojure functionality such as the ability to add-watch on an atom/var/ref and let the runtime signal dependent functions.

Platform-specific interaction will rest tightly with the actual UI, such as reifying ActionListeners, and dealing with ObservableValues etc., and will attempt to minimize the reliance on things like JavaFX Property for actual application data. I'm not entertaining FRP for this.

I don't mind extending JFX interfaces or making up my own protocols to use application-specific defrecords, but I'd prefer for the application data to remain as straight Clojure data, unsullied by the platform.

The question is how to set this all up, with closest adherence to the immutable model. I see a few options:

  1. Fine-grain: Each parameter value/primitive (ie Long, Double, Boolean, or String) is an atom, and each view which can modify the value "reaches in" as far as it needs to in the database to change the value. This could suck as there could potentially be thousands of individual values (for example points on a hand-drawn curve), and will require lots of (deref...) junk. I believe this is how JFX would want to do this, with giant arrays of Properties at the leaf nodes, etc., which feels bloated. With this approach it doesn't seem much better than just coding it up in Java/C++.
  2. Medium-grain: Each object/record in the database is an atom of a Clojure map. The entire map is replaced when any one of its values changes. Fewer total atoms to deal with, and allows for example long arrays of straight-up numbers for various things. But this gets complicated when some objects in the database require more nesting than others.
  3. Coarse-grain: There is just one atom: the database. Any time anything changes, the entire database is replaced, and every view needs to re-render its particular portion. This feels a bit like using a hammer to swat a fly, and a naive implementation would require everything to re-render all the time. But I still think this is the best trade off, as any primitive has a clear access path from the root node, whether it is accessed on a per-primitive level or per-record level.

I also need the ability for one data template to be instantiated many times. So for example if the user changes a symbol or shape which is used in multiple places, a single edit will apply everywhere. I believe this also requires some type of "pointer"-like behavior. I think I can store a atom to the model, then instantiate as needed, and it can work in any of the above grain models.

Any other approaches? Is trying to do a GUI editor-like tool in a functional language just stupid? Thanks

Doy answered 12/1, 2015 at 18:47 Comment(5)
As you are targeting JavaFX as the GUI toolkit, perhaps studying SceneBuilderKit (a library framework for graphically editing JavaFX scenes) and its source code might help you. Some of its capability is similar, though different, to what you describe. It's definitely not based on a clojure type functional model though.Radiotelegraphy
Yes, I'm targeting JavaFX. After months of occasionally poking at the keyboard between work, traffic, and screaming baby, I finally managed to get an anonymous/local ChangeListener to change an atom and trigger a Clojure watch. Neato. I have not seen SceneBuilderKit (although I have played around with their SceneBuilder desktop app), but I will take a look. Another consideration is whether to allow JFX to own the entire scene graph using its own primitives, or whether to draw my own shapes on the canvas in standard "OnPaint" style, but that is off-topic for this SO question.Doy
Yes, many options Sonic. It's a great question, but very hard to answer. My guess is it will be more work if you don't use a scene graph and some pre-built controls such as a [ControlsFX PropertySheet]. Maybe email the clojurefx developer and get his opinion on your question.Radiotelegraphy
what about forgetting MVC pattern and instead adopting Flux architecture with core.async channel as event bus? Just my 2¢.Okajima
Thanks, I'm trying my best to not rely on "patterns" necessarily, but I used that term so reader would know immediately what I'm trying to do. At the lowest level there is mutable state for a user-interactive program, and the UI platform has callbacks that need to be registered. How to use Flux, React, core.async, compojure, datomic, node.js... argh... it's too much stuff! I'm still not on the clojurescript train. I have only a few neurons left so I'm trying to minimize the overload.Doy
N
5

I don't think is stupid to use a functional language to do a GUI editor-like tool. But I can't claim to have an answer to your question. Here are some links that might help you in your journey:

  1. Stuart Sierra - Components Just Enough Structure
  2. Chris Granger - Light Table: Explains how Light Table (source) is structured.
  3. Chris Granger - The IDE as a Value: blog post related to the video above
  4. Conal Elliott - Tangible Functional Programming: Using Functional Reactive Programming to create a composable UI, but his code is in Haskell.
  5. Nathan Herzing & Chris Shea - Helping voters with Pedestal, Datomic, Om and core.async
  6. David Nolen - Comparative Literate Programming: Shows all to use core.async to simplify UI programming in ClojureScript. The ideas here can be used in a desktop UI.
  7. Rich Hickey - The Language of the System: Amazing talk about system programming by the creator of Clojure.

Erik Meijer has a good quote about functional vs imperative code:

...no matter whether it's Haskell, C# Java, F#, Scala, Python, PHP think about the idea of having a sea of imperative code that interacts with the outside world and in there have islands of pure code where you write your functions in a pure way. But you have to decide how big the islands are and how big the sea is. But the answer is never that there are only islands or only sea. A good programmer knows exactly the right balance.

Newark answered 1/2, 2015 at 23:10 Comment(2)
Thanks, those are good links. I've seen one or two of them before, but most are new to me. Just need to digest and find the proper abstraction I need... I'd like to understand when/how to do the core.async stuff, when to use promises, futures, agents, raw threads, etc., vs just leaving things singe-threaded, and how to weld the low level GUI calls to the functional/dataflow/async/frp/whatever abstraction. I suppose this will take several iterations and finding the blocking/slow sections where adding more threads will help.Doy
Added one more talk, I can't believe I forgot that amazing talk.Newark

© 2022 - 2024 — McMap. All rights reserved.