These are notes for developers on the design principles of this project.

Decoupled Components

Components are carefully decoupled so that anyone may take just the parts that are useful to them and place them within existing programs or joined up with their own custom work. For example, there are separate models for SearchInput and SearchResults which can be used and remixed independently. These are composed to together in a Search model. Multiple Search models can be composed together in a SearchList model—for example, to back a tabbed view of multiple searches.

In this nested scheme, parents are allowed to know about their children, but children are not allowed to know about their parents. For example, a Search can react to and expose out things happening in SearchInput, but SearchInput will never reach up into Search or sideways into its sibling SearchResults.

What does this buy us?

  • Maximum reusability

  • Easy embedding into existing applications, validated by early examples (napari, pyFAI, Xi-CAM)

Models not tied to any GUI framework

All of the logic lives in the models. The models use an internal signaling system (vendored from napari, which in turn vendored and adapted theirs from vispy). Thus, they are not tied to any particular GUI framework’s signaling system, such as Qt signals and slots or ipywidgets’ traitlets, but they can be hooked up to any of them.

For user–developer assembling components into a custom application, connecting the models to a particular GUI looks like this, as illustrated in the examples.

thing_model = ThingModel()
qt_thing = QtThing(thing_model)


thing_model = ThingModel()  # the very same type of model
jupyter_thing = JupyterThing(thing_model)  # a different view

where, in the __init__ of QtThing and JupyterThing, connections are made between Qt signals and slots or ipywidgets traitlets and the model’s own signaling system. This is in addition to whatever model–view abstractions are happening within those frameworks; in some places one has effectively model–model–view.

What does this buy us?

  • If we launch the GUI from within IPython or Jupyter, we can access and alter the state of the model interactively. The model and the GUI remain synced, with updates propagating in both directions.

    # Update a search GUI to set the time range.
    search.input.since = "2020"
    search.input.until = "2021"
    # Access all the results.
    # Access all the selected results.
  • We can efficiently base a variety of GUI frameworks (starting with Qt and Jupyter) on this model because most of the work involved is simply hooking up the frameworks’ particular signaling system to the model’s.

  • The model may naturally be used “headless”.

  • The model can be unit tested separate from any GUI-based testing.