LUMIERA.clone/doc/technical/gui/CodePolicy.txt

94 lines
4.6 KiB
Text

Coding Policies within UI-Layer
===============================
:Toc:
NOTE: for the time being, this is a loose collection of
Conventions and Policies deemed adequate for work
on the Lumiera UI-Layer
Architecture
------------
The UI is loaded as Plug-In, which instantiates a `GtkLumiera` object.
All of the UI activity happens within the blocking function `GtkLumiera::run()`.
This UI activity boils down to run the GTK event loop; we use a dedicated thread,
and all of the GUI thus performs single-threaded and need not be thread-safe.
All UI activities thus need to be short running and with deterministic outcome.
It is strictly prohibited to spawn other threads from within the event loop.
We hold up a strict distinction between the _UI mechanics_ and the _core editing
concerns_. The latter _must not be implemented within the UI-Layer._ Rather, you need
to send an `act(CommandID)` message over the UI-Bus, which causes the corresponding
Steam-Layer command script to be dispatched within the Session thread. It is good
practice to give an immediate visual clue regarding the fact of sending such
a command (e.g. pressing a button). But be prepared that the actual feedback
of invoking a command happens asynchronously.
UI-Model
~~~~~~~~
In short: _there is no dedicated UI-Model._ Stateful working data goes directly
into the widgets. However, some widgets or controllers are special, insofar they
correspond to and reflect some entities within the _Session model._ These special
entities must be implemented as subclasses of `gui::model::Tangible`. These are
always connected to the UI-Bus, and they are populated and mutated by receiving
diff messages pushed up from the Steam-Layer.
There is an _inner circle_ of UI backbone services. These are all mutually dependent
on each other and their start-up sequence is intricate. Implement global and cross-cutting
concerns here. In case some widget or controller needs access to such a service, then you
should prefer using `lib::Depend<ServiceInterface>`, and install the corresponding service
via `lib::DependInject<ServiceInterface>::ServiceInstance<ServiceImpl>`.
Behaviour patterns
------------------
Parent Containers
~~~~~~~~~~~~~~~~~
A _Parent Container_ is an object managing a collection of children, with respect to
the structure of _tangible UI elements._ Irrespective if this container is also a
`Gtk::Container` widget, or ``just'' a controller. The key point to turn something
into a _Parent Container_ is the fact that this entity creates the children
in response to a (population) diff message.
.Responsibilities
- a _Parent Container_ has to wire each child properly, so to enable the adequate
use of the UI element protocol. This includes
** to ensure the child is attached to the UI-Bus (usually enforced by ctor call)
** to install a suitable `Expander` functor if the child supports expand/collapse
** to install a suitable `Revealer` when it is relevant for the child to be brought
into sight in response to some message (e.g. to indicate error state).
- whenever a child is detached from the container, you need to _ensure it is destroyed_
right away, before there is any chance of processing some other UI event.footnote:[
Invoking the destructor triggers a lot of magic here, especially it causes the child
to be properly detached from signals. We want to ensure this happens as soon as the
child is taken out of service. Do not use an ``can collect garbage later'' approach.]
Error handling
~~~~~~~~~~~~~~
Be aware that GTK is written in C. And while GTKmm has some safeguards in place,
better be sure no exception can emanate from event handling code.
WARNING: [red]#TODO#: probably we'll need a common wrapper to do so...
Code organisation
-----------------
GTK is massive and compilation times tend to be problematic in the UI-Layer.
Thus you should be diligent with the organisation of includes. Try to confine the
more heavyweight includes within the implementation translation units. Use forward
declarations and PImpl if possible. However, it is fine to write a mere implementation
widget in header-only fashion, in case this improves the locality and readability of code.
NOTE: `using namespace Gtk` and similar wildcard includes are prohibited.
We are forced to observe a tricky include sequence, due to NoBug's `ERROR` macro,
and also in order to get I18N right.
Thus any actual translation unit should ensure that effectively the first include
is that of 'gui/gtk-base.hpp'.footnote:[you need not include it literally. It is
perfectly fine if you can be sure the first included header somehow drags in
'gtk-base.hpp' before any other header.]