clarify the principles of UI - Core collaboration
it occurred to me that effectively we abandoned the use of a business facade and proxy model in the UI. The connection becomes entirely message based now. To put that into context, the originally intended architecture never came to life. The UI development stalled before this could happen; possibly it was also hampered by the "impedance mismatch" between our intentions in the core and such a classical, model centric architecture. Joel several times complained that he felt blocked; but I did not really understand this issue. Only recently, when I came to adapting the timeline display to GTK-3, I realised the model centric approach can not possibly work with such an open model as intended in our case. It would lead to endless cascades of introspection.
This commit is contained in:
parent
3ffd511a76
commit
14588dbc19
6 changed files with 119 additions and 43 deletions
|
|
@ -21,16 +21,17 @@
|
|||
* *****************************************************/
|
||||
|
||||
|
||||
/** @file timeline-widget.cpp
|
||||
** Implementation details of Lumiera's timeline display widget.
|
||||
/** @file timeline-controller.cpp
|
||||
** Implementation details of timeline operation management and control.
|
||||
**
|
||||
** @todo as of 12/2016 a complete rework of the timeline display is underway
|
||||
** @see TimelineWidget
|
||||
**
|
||||
*/
|
||||
|
||||
|
||||
#include "gui/gtk-lumiera.hpp"
|
||||
#include "gui/timeline/timeline-widget.hpp"
|
||||
#include "gui/timeline/timeline-controller.hpp"
|
||||
|
||||
//#include "gui/workspace/workspace-window.hpp"
|
||||
//#include "gui/ui-bus.hpp"
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
TIMELINE-WIDGET.hpp - custom widget for timeline display of the project
|
||||
TIMELINE-CONTROLLER.hpp - coordinate operation of timeline display
|
||||
|
||||
Copyright (C) Lumiera.org
|
||||
2016, Hermann Vosseler <Ichthyostega@web.de>
|
||||
|
|
@ -21,20 +21,37 @@
|
|||
*/
|
||||
|
||||
|
||||
/** @file timeline-widget.hpp
|
||||
** This file defines the core component of the Lumiera GUI.
|
||||
** Timelines are the top level elements within Lumiera's high-level-model ("the session").
|
||||
** In the UI workspace, there is a timeline pane with several tabs, each of which holds an
|
||||
** instance of the TimelineWidget. Each of these tabs either represents one of the top-level
|
||||
** timelines in the model, or it represents a (focussed / slave) view into some timeline.
|
||||
/** @file timeline-controller.hpp
|
||||
** Controller to supervise operation of timeline display in the UI.
|
||||
** While the TimelineWidget is the top level entry point to any facility dealing with
|
||||
** timeline display and editing operations, the widget in turn creates a TimelineController
|
||||
** right away, which then takes initiative to populate the display with the corresponding
|
||||
** session::Timeline contents. The controller thus serves as the model connection through
|
||||
** the UI-Bus and becomes the active part of running the timeline display, delegating
|
||||
** display activities to the widget, which in turn uses the timeline::LayoutManager to
|
||||
** work out the details of presentation in collaboration with the local UI model elements.
|
||||
** Incidentally, those UI model elements, which are actually Presenters, are managed as
|
||||
** children of the TimelineController; this makes sense from an architectural point of view:
|
||||
** In Lumiera, we separate between the core concerns and the questions of UI mechanics. For
|
||||
** the former, the global angle of view, the roles of _controller_ and _model_ are mediated
|
||||
** by the control::UiBus, while the [tangible elements](\ref model::Tangible), play the
|
||||
** role of the _view._ But at the same time, following the local UI centric angle, the latter
|
||||
** entities act more like presenters, forming a dedicated _view model,_ while controlling
|
||||
** mostly passive view components (widgets).
|
||||
**
|
||||
** To sum up
|
||||
** - TimelineController is a Tangible, connected to the UiBus, representing "the timeline"
|
||||
** - it directly manages a set of TrackPresenter entities, to correspond to the session::Fork
|
||||
** - these in turn manage a set of ClipPresenter entities
|
||||
** - and those presenters care for injecting suitable widgets into the TimelineWidget's parts.
|
||||
**
|
||||
** @todo as of 12/2016 a complete rework of the timeline display is underway
|
||||
**
|
||||
*/
|
||||
|
||||
|
||||
#ifndef GUI_TIMELINE_TIMELINE_WIDGET_H
|
||||
#define GUI_TIMELINE_TIMELINE_WIDGET_H
|
||||
#ifndef GUI_TIMELINE_TIMELINE_CONTROLLER_H
|
||||
#define GUI_TIMELINE_TIMELINE_CONTROLLER_H
|
||||
|
||||
#include "gui/gtk-base.hpp"
|
||||
|
||||
|
|
@ -62,7 +79,7 @@ namespace timeline {
|
|||
* @param source_state state to be used used as the
|
||||
* data source (model) for this timeline widget.
|
||||
*/
|
||||
TimelineController ();
|
||||
TimelineController();
|
||||
~TimelineController();
|
||||
|
||||
|
||||
|
|
@ -79,4 +96,4 @@ namespace timeline {
|
|||
|
||||
|
||||
}}// namespace gui::timeline
|
||||
#endif /*GUI_TIMELINE_TIMELINE_WIDGET_H*/
|
||||
#endif /*GUI_TIMELINE_TIMELINE_CONTROLLER_H*/
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
TimelineWidgetEmpty - coordinate operation of timeline display
|
||||
TimelineWidgetEmpty - placeholder widget for a missing timeline
|
||||
|
||||
Copyright (C) Lumiera.org
|
||||
2016, Hermann Vosseler <Ichthyostega@web.de>
|
||||
|
|
@ -21,8 +21,8 @@
|
|||
* *****************************************************/
|
||||
|
||||
|
||||
/** @file timeline-widget.cpp
|
||||
** Implementation details of Lumiera's timeline display widget.
|
||||
/** @file timeline-widget-empty.cpp
|
||||
** Implementation of an empty timeline placeholder.
|
||||
**
|
||||
** @todo as of 12/2016 a complete rework of the timeline display is underway
|
||||
**
|
||||
|
|
@ -30,7 +30,7 @@
|
|||
|
||||
|
||||
#include "gui/gtk-lumiera.hpp"
|
||||
#include "gui/timeline/timeline-widget.hpp"
|
||||
#include "gui/timeline/timeline-widget-empty.hpp"
|
||||
|
||||
//#include "gui/workspace/workspace-window.hpp"
|
||||
//#include "gui/ui-bus.hpp"
|
||||
|
|
@ -64,12 +64,12 @@ namespace timeline {
|
|||
|
||||
|
||||
|
||||
TimelineController::TimelineController ()
|
||||
TimelineWidgetEmpty::TimelineWidgetEmpty ()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
TimelineController::~TimelineController()
|
||||
TimelineWidgetEmpty::~TimelineWidgetEmpty()
|
||||
{
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
TIMELINE-WIDGET.hpp - custom widget for timeline display of the project
|
||||
TIMELINE-WIDGET-EMPTY.hpp - placeholder widget for a missing timeline
|
||||
|
||||
Copyright (C) Lumiera.org
|
||||
2016, Hermann Vosseler <Ichthyostega@web.de>
|
||||
|
|
@ -21,20 +21,20 @@
|
|||
*/
|
||||
|
||||
|
||||
/** @file timeline-widget.hpp
|
||||
** This file defines the core component of the Lumiera GUI.
|
||||
** Timelines are the top level elements within Lumiera's high-level-model ("the session").
|
||||
** In the UI workspace, there is a timeline pane with several tabs, each of which holds an
|
||||
** instance of the TimelineWidget. Each of these tabs either represents one of the top-level
|
||||
** timelines in the model, or it represents a (focussed / slave) view into some timeline.
|
||||
/** @file timeline-widget-empty.hpp
|
||||
** Empty placeholder to be rendered when the UI starts without session.
|
||||
** The TimelinePannel represents the [model root](\ref mobject::session::Root) and thus
|
||||
** has the responsibility to manage the individual TimelineWidget instances. In case the
|
||||
** UI is started with no usable session loaded in Proc-Layer, this empty placeholder widget
|
||||
** can be inserted.
|
||||
**
|
||||
** @todo as of 12/2016 a complete rework of the timeline display is underway
|
||||
**
|
||||
*/
|
||||
|
||||
|
||||
#ifndef GUI_TIMELINE_TIMELINE_WIDGET_H
|
||||
#define GUI_TIMELINE_TIMELINE_WIDGET_H
|
||||
#ifndef GUI_TIMELINE_TIMELINE_WIDGET_EMPTY_H
|
||||
#define GUI_TIMELINE_TIMELINE_WIDGET_EMPTY_H
|
||||
|
||||
#include "gui/gtk-base.hpp"
|
||||
|
||||
|
|
@ -55,15 +55,15 @@ namespace timeline {
|
|||
* @remarks At top level, this widget is split into a header pane (left)
|
||||
* and a scrollable timeline body (right). The layout of both parts is aligned.
|
||||
*/
|
||||
class TimelineController
|
||||
class TimelineWidgetEmpty
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @param source_state state to be used used as the
|
||||
* data source (model) for this timeline widget.
|
||||
*/
|
||||
TimelineController ();
|
||||
~TimelineController();
|
||||
TimelineWidgetEmpty();
|
||||
~TimelineWidgetEmpty();
|
||||
|
||||
|
||||
|
||||
|
|
@ -79,4 +79,4 @@ namespace timeline {
|
|||
|
||||
|
||||
}}// namespace gui::timeline
|
||||
#endif /*GUI_TIMELINE_TIMELINE_WIDGET_H*/
|
||||
#endif /*GUI_TIMELINE_TIMELINE_WIDGET_EMPTY_H*/
|
||||
|
|
|
|||
|
|
@ -2536,15 +2536,20 @@ This contrastive approach attempts to keep knowledge and definition clustered in
|
|||
''Lumera decides to take the latter apptoch'' -- resulting in a separation between immediate low-level UI element reactions, and anything of relevance for the behaviour of the UI. The widget code embodies the low-level UI element reactions and as such becomes more or less meaningless beyond local concerns of layout and presentation. If you want to find out about the behaviour of the UI, you need to know where to look, and you need to know how to read and understand those enablement rules. Another consequence is the build-up of dedicated yet rather abstract state tracking facilities, hooking like an octopus into various widgets and controllers, which might work counter to the intentions behind the design of common UI toolkit sets.
|
||||
</pre>
|
||||
</div>
|
||||
<div title="GuiConnection" modifier="Ichthyostega" created="200812050543" modified="201501052157" tags="GuiIntegration overview" changecount="1">
|
||||
<pre>All communication between Proc-Layer and GUI has to be routed through the respective LayerSeparationInterfaces. Following a fundamental design decision within Lumiera, these interface are //intended to be language agnostic// &mdash; forcing them to stick to the least common denominator. Which creates the additional problem of how to create a smooth integration without forcing the architecture into functional decomposition style. To solve this problem, we rely on the well known solution of using a __business facade__ and delegation proxies.
|
||||
Thus, the Proc-Layer exposes (one or several) facade interfaces for the GUI to use it's functionality, and similarily the GUI provides a [[notification facade|GuiNotificationFacade]] for pushing back status information created as result of the edit operations, the build process and the render tasks.
|
||||
<div title="GuiConnection" modifier="Ichthyostega" created="200812050543" modified="201612021714" tags="GuiIntegration overview" changecount="4">
|
||||
<pre>All communication between Proc-Layer and GUI has to be routed through the respective LayerSeparationInterfaces. Following a fundamental design decision within Lumiera, these interface are //intended to be language agnostic// &mdash; forcing them to stick to the least common denominator. Which creates the additional problem of how to create a smooth integration without forcing the architecture into functional decomposition style. To solve this problem, we rely on ''messaging'' rather than on a //business facade// -- our facade interfaces are rather narrow and limited to lifecycle management. In addition, the UI exposes a [[notification facade|GuiNotificationFacade]] for pushing back status information created as result of the edit operations, the build process and the render tasks.
|
||||
|
||||
!anatomy of the Proc/GUI interface
|
||||
* the GuiFacade is used as a general lifecycle facade to start up the GUI and to set up the LayerSeparationInterfaces.
|
||||
* GuiFacade is implemented by a class //in core// and exposes a notification proxy implementing GuiNotificationFacade, which actually is wired through the InterfaceSystem to forward to the corresponding implementation facade object within the GUI
|
||||
* similarly, starting the fundamental subsystems within proc installs Interfaces into the InterfaceSystem and exposes them through proxy objects
|
||||
* similarly, starting the fundamental subsystems within proc installs Interfaces into the InterfaceSystem and exposes them through proxy objects.<br/>{{red{TODO 12/2016}}} there shall be an interface to [[invoke commands|CommandHandling]]...
|
||||
|
||||
!principles of UI / Proc interaction
|
||||
By all means, we want to avoid a common shared data structure as foundation for any interaction. For a prominent example, have a look at [[Blender|https://developer.blender.org]] to see where this leads; //such is not bad,// but it limits to a very specific kind of evolution. //We are aiming for less and for more.// Fuelled by our command and UNDO system, and our rules based [[Builder]] with its asynchronous responses, we came to rely on a messaging system, known as the [[UI-Bus]].
|
||||
|
||||
The consequence is that both sides, "the core" and "the UI" remain autonomous within their realm. For some concerns, namely //the core concerns,// that is editing, arranging, processing, the core is in charge and has absolute authority. On the other hand, when it comes to user interaction, especially the //mechanics and materiality of interaction,// the UI is the authority; it is free to decide about what is exposed and in which way. The collaboration between both sides is based on a ''common structural understanding'', which is never fully, totally formed in concrete data structures.
|
||||
|
||||
Rather, the core sends ''diff messages'' up to the UI, indicating how it sees this virtual structure to be changing. The UI reflects these changes into //its own understanding and representation,// that is here a structure of display widgets. When the user interacts with these structures of the presentation layer, ''command messages'' are generated, using the element ~IDs to designate the arguments of the intended operation. This again causes reaction and change in the core, which is reflected back in the form of further diff messages.
|
||||
</pre>
|
||||
</div>
|
||||
<div title="GuiCustomWidget" creator="Ichthyostega" modifier="Ichthyostega" created="201410250002" modified="201611182349" tags="GuiPattern discuss decision draft" changecount="64">
|
||||
|
|
@ -2661,10 +2666,13 @@ In order to build a sensible plan for our timeline structure, we need to investi
|
|||
<pre>special LayerSeparationInterface which serves the main purpose to load the GuiStarterPlugin, thus bringing up the Lumiera GTK UI at application start.
|
||||
</pre>
|
||||
</div>
|
||||
<div title="GuiIntegration" modifier="Ichthyostega" created="200812050532" modified="201410170111" tags="overview" changecount="2">
|
||||
<pre>Considering how to interface to and integrate with the GUI Layer. Running the GUI is //optional,// but it requires to be [[started up|GuiStart]], installing the necessary LayerSeparationInterfaces. As an example how to integrate the GUI with the lower layers, in 1/2009 we created an PlayerDummy, which "pulls" dummy frames from the (not yet existing) engine and displays them within an XV viewer widget.
|
||||
<div title="GuiIntegration" modifier="Ichthyostega" created="200812050532" modified="201612021642" tags="overview" changecount="5">
|
||||
<pre>Considering how to interface to and integrate with the GUI Layer. Running the GUI is //optional,// but it requires to be [[started up|GuiStart]], installing the necessary LayerSeparationInterfaces. Probably the most important aspect regarding the GUI integration is how to get [[access to and operate|GuiConnection]] on the [[Session|SessionInterface]].
|
||||
|
||||
Probably the most important aspect regarding the GUI integration is how to get [[access to and operate on the Session|SessionInterface]]. More specifically, this includes [[referring to individual objects|MObjectRef]]. On top of these generic access mechanisms, we create a [[proxy GUI model|GuiModel]] for binding. The interface of this GUI model is tailored for display and translation into UI entities.</pre>
|
||||
More specifically, the integration is based on ''messaging''. To start with, the UI needs to be [[populated with updates|GuiModelUpdate]], and while in operation, it will send command messages over the [[UI-Bus]]. Effectively, the UI maintains its own [[model|GuiModel]], specifically tailored for display and translation into tangible UI entities.
|
||||
|
||||
----
|
||||
In a preliminary attempt to establish an integration between the GUI and the lower layers, in 1/2009 we created an PlayerDummy, which "pulls" dummy frames from the (not yet existing) engine and displays them within an XV viewer widget. This highlighted the problems we're about to encounter and made us think about the more radically decoupled approach we followed thereafter...</pre>
|
||||
</div>
|
||||
<div title="GuiModel" creator="Ichthyostega" modifier="Ichthyostega" created="201410170142" modified="201511202206" tags="GuiIntegration design draft" changecount="11">
|
||||
<pre>Building a layered architecture is a challenge, since the lower layer //really// needs to be self-contained, while prepared for usage by the higher layer.
|
||||
|
|
@ -2767,10 +2775,15 @@ This treatment ensures each nested diff is consumed within a properly typed cont
|
|||
Access point for the lower layers to push information and state changes (aynchronously) to the GUI. Actually, most operations within Lumiera are initiated by the user through the GUI. In the course of such actions, the GUI uses the services of the lower layer and typically recieves an synchronous response. In some exceptional cases, these operations may cause additional changes to happen asynchronously from the GUI's perspective. For example, an edit operation might trigger a re-build of the low-level model, which then detects an error.
|
||||
</pre>
|
||||
</div>
|
||||
<div title="GuiPattern" creator="Ichthyostega" modifier="Ichthyostega" created="201410160054" modified="201410250026" tags="overview" changecount="2">
|
||||
<div title="GuiPattern" creator="Ichthyostega" modifier="Ichthyostega" created="201410160054" modified="201612021723" tags="overview" changecount="4">
|
||||
<pre>While the HighLevelModel is self-contained and forms an autonomous »Universe«, the Lumiera GUI uses a well defined set of Metaphors, structural patterns and conventions to represent the user's view upon the model within the session.
|
||||
|
||||
Based on these foundations, we have to take some design decisions regarding the [[organisation of our custom GUI|GuiTimelineWidgetStructure]]</pre>
|
||||
The most fundamental principle is that of ''subsidiarity'': we understand both "the core" and "the UI" to be autonomous and responsible for their own concerns. The core has to deal with editing, arranging and processing, while the UI forms the materiality and mechanics of interaction. The [[link between both sides|GuiConnection]] is established through a communication system, the UI-Bus.
|
||||
&rarr; the UI view [[is updated|GuiModelUpdate]] by ''diff messages''
|
||||
&rarr; and in turn, commands are [[prepared and issued|GuiCommandBinding]] in the UI and sent as ''command messages''
|
||||
|
||||
Based on these foundations, we shape and form the core part of the interface, which is the [[timeline display|GuiTimelineView]]
|
||||
</pre>
|
||||
</div>
|
||||
<div title="GuiStart" modifier="Ichthyostega" created="200812050525" modified="200902080720" tags="GuiIntegration">
|
||||
<pre>Starting up the GUI is optional and is considered part of the Application start/stop and lifecycle.
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@
|
|||
</node>
|
||||
<node CREATED="1480639327169" HGAP="36" ID="ID_700485676" MODIFIED="1480639350283" TEXT="Assets" VSHIFT="-2"/>
|
||||
<node CREATED="1476376913589" HGAP="42" ID="ID_1887326939" MODIFIED="1480639340628" TEXT="Timeline" VSHIFT="-17">
|
||||
<node CREATED="1477599995452" ID="ID_97568136" MODIFIED="1477599999383" TEXT="Bestandteile">
|
||||
<node CREATED="1477599995452" HGAP="10" ID="ID_97568136" MODIFIED="1480694723679" TEXT="Bestandteile" VSHIFT="-4">
|
||||
<node CREATED="1476377043180" ID="ID_1179709828" MODIFIED="1476377047495" TEXT="Head">
|
||||
<node CREATED="1476377067729" ID="ID_896843893" MODIFIED="1476377070268" TEXT="Patchbay"/>
|
||||
<node CREATED="1476377071072" ID="ID_745594295" MODIFIED="1476377074604" TEXT="in-sync with body"/>
|
||||
|
|
@ -44,6 +44,47 @@
|
|||
</node>
|
||||
<node CREATED="1476377059386" ID="ID_363001687" MODIFIED="1476377064293" TEXT="Control">
|
||||
<node CREATED="1476377117290" ID="ID_179386424" MODIFIED="1476377119406" TEXT="scolling"/>
|
||||
<node CREATED="1480694668090" ID="ID_792729651" MODIFIED="1480694674901" TEXT="TimelineWidget">
|
||||
<node CREATED="1480694675624" ID="ID_1512443419" MODIFIED="1480694678756" TEXT="Einstiegspunkt"/>
|
||||
<node CREATED="1480694679232" ID="ID_1344733448" MODIFIED="1480694705355">
|
||||
<richcontent TYPE="NODE"><html>
|
||||
<head>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<p>
|
||||
sets für <b>eine feste</b> session::Timeline
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
</richcontent>
|
||||
<icon BUILTIN="yes"/>
|
||||
</node>
|
||||
</node>
|
||||
<node CREATED="1480694854057" HGAP="32" ID="ID_577309407" MODIFIED="1480694877355" TEXT="TimelinePane" VSHIFT="7">
|
||||
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1480694860696" ID="ID_1847844548" MODIFIED="1480694886787" TEXT="entspricht dem Model-Root">
|
||||
<icon BUILTIN="yes"/>
|
||||
</node>
|
||||
<node CREATED="1480694897443" ID="ID_402490324" MODIFIED="1480694916396" TEXT="verwaltet die Timelines"/>
|
||||
<node CREATED="1480694918056" ID="ID_789731200" MODIFIED="1480694948954">
|
||||
<richcontent TYPE="NODE"><html>
|
||||
<head>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<p>
|
||||
es gibt eine <i>EmptyTimeline</i>
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
</richcontent>
|
||||
<node CREATED="1480694930990" ID="ID_1289595423" MODIFIED="1480694936274" TEXT="interface == Widget"/>
|
||||
</node>
|
||||
</node>
|
||||
<node CREATED="1480694624879" HGAP="37" ID="ID_1678741068" MODIFIED="1480694715769" TEXT="TimelineController" VSHIFT="21">
|
||||
<node CREATED="1480694630302" ID="ID_10965216" MODIFIED="1480694651464" TEXT="hat das Sagen"/>
|
||||
<node CREATED="1480694652219" ID="ID_1232910852" MODIFIED="1480694656791" TEXT="Widget wird passiv"/>
|
||||
</node>
|
||||
</node>
|
||||
</node>
|
||||
<node CREATED="1477600006370" ID="ID_1328755612" MODIFIED="1477600008246" TEXT="Ansatz">
|
||||
|
|
@ -391,6 +432,10 @@
|
|||
</node>
|
||||
</node>
|
||||
<node CREATED="1480606954115" ID="ID_1178000371" MODIFIED="1480606960326" TEXT="Struktur-Modell"/>
|
||||
<node CREATED="1480694550601" ID="ID_391329400" MODIFIED="1480694554372" TEXT="TimelineController">
|
||||
<node CREATED="1480694557112" ID="ID_786517324" MODIFIED="1480694570434" TEXT="Widget ist Startpunkt"/>
|
||||
<node CREATED="1480694571310" ID="ID_1188786444" MODIFIED="1480694576714" TEXT="aber Controller wird Chef"/>
|
||||
</node>
|
||||
<node CREATED="1480606985087" ID="ID_885244508" MODIFIED="1480639465600" TEXT="Layout-Manager">
|
||||
<node CREATED="1480639469981" ID="ID_983391388" MODIFIED="1480639472833" TEXT="Abstraktionen"/>
|
||||
<node CREATED="1480639473324" ID="ID_191170582" MODIFIED="1480639510996" TEXT="eval pass"/>
|
||||
|
|
|
|||
Loading…
Reference in a new issue