Timeline: further steps towards attaching the widget structure

This commit is contained in:
Fischlurch 2018-10-28 18:56:04 +01:00
parent 3dd3fc7810
commit 8803af1a0a
12 changed files with 163 additions and 118 deletions

View file

@ -351,9 +351,9 @@ namespace dialog {
// construct and wire the pages...
notebook_.buildPage<Page1> (_("#1099"), uiBus_);
notebook_.buildPage<Page2> (_("Populate"), uiBus_);
notebook_.set_current_page(-1);
show_all();
notebook_.set_current_page(-1);
}
};

View file

@ -31,6 +31,7 @@
#include "gui/gtk-base.hpp"
#include "gui/timeline/body-canvas-widget.hpp"
#include "gui/timeline/track-body.hpp"
//#include "gui/ui-bus.hpp"
//#include "lib/format-string.hpp"
@ -77,6 +78,21 @@ namespace timeline {
/**
* The Lumiera Timeline model does not rely on a list of tracks, as most conventional video editing software does --
* rather, each sequence holds a _fork of nested scopes._ This recursively nested structure is reflected in the way
* we organise and draw the timeline representation onto the TimelineCanvas: we use an intermediary entity, the TrackBody
* as an organisational grouping device, even while we draw _all of the timeline representation_ onto a single global
* ::canvas_ within the (scrollable) BodyCanvasWidget. Thus, adding the first TrackBody to represent the root track
* of a Timeline, will also prepare the grounding for any other nested entities to be drawn on top.
*/
void
BodyCanvasWidget::installForkRoot (TrackBody& rootTrackHead)
{
UNIMPLEMENTED ("how actually to represent the track bode on the canvas");
}
}}// namespace gui::timeline

View file

@ -69,6 +69,8 @@
namespace gui {
namespace timeline {
class TrackBody;
class TimelineCanvas
: public Gtk::Layout
@ -89,6 +91,9 @@ namespace timeline {
BodyCanvasWidget();
~BodyCanvasWidget();
/** @internal Initially install the contents corresponding to the root track fork */
void installForkRoot (TrackBody& rootTrackBody);
private:/* ===== Internals ===== */
};

View file

@ -1,77 +0,0 @@
/*
HeaderPaneWidget - display pane for track headers within the timeline
Copyright (C) Lumiera.org
2016, Hermann Vosseler <Ichthyostega@web.de>
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of
the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
* *****************************************************/
/** @file header-pane-widget.cpp
** Implementation of the track header pane within the timeline UI.
**
** @todo WIP-WIP-WIP as of 12/2016
**
*/
#include "gui/gtk-base.hpp"
#include "gui/timeline/header-pane-widget.hpp"
//#include "gui/ui-bus.hpp"
//#include "lib/format-string.hpp"
//#include "lib/format-cout.hpp"
//#include "lib/util.hpp"
//#include <algorithm>
//#include <vector>
//using util::_Fmt;
//using util::contains;
//using Gtk::Widget;
//using sigc::mem_fun;
//using sigc::ptr_fun;
//using std::cout;
//using std::endl;
namespace gui {
namespace timeline {
HeaderPaneWidget::~HeaderPaneWidget() { }
HeaderPaneWidget::HeaderPaneWidget()
: Gtk::Box{Gtk::ORIENTATION_VERTICAL}
, navigator_{}
, patchbay_{}
{
this->pack_start (navigator_, Gtk::PACK_SHRINK);
this->pack_start (patchbay_, Gtk::PACK_EXPAND_WIDGET);
}
}}// namespace gui::timeline

View file

@ -63,6 +63,8 @@
namespace gui {
namespace timeline {
class TrackHeadWidget;
/**
* @todo WIP-WIP as of 12/2016
@ -74,9 +76,28 @@ namespace timeline {
PatchbayWidget patchbay_;
public:
HeaderPaneWidget();
~HeaderPaneWidget();
~HeaderPaneWidget() { }
HeaderPaneWidget()
: Gtk::Box{Gtk::ORIENTATION_VERTICAL}
, navigator_{}
, patchbay_{}
{
this->pack_start (navigator_, Gtk::PACK_SHRINK);
this->pack_start (patchbay_, Gtk::PACK_EXPAND_WIDGET);
}
/**
* Initially install the root node of the track fork,
* which later can be extended recursively by adding nested
* sub-forks ("Sub-Tracks").
*/
void
installForkRoot (TrackHeadWidget& rootTrackHead)
{
patchbay_.installFork (rootTrackHead);
}
private:/* ===== Internals ===== */
};

View file

@ -68,6 +68,22 @@ namespace timeline {
{ }
/**
* The Lumiera Timeline model does not rely on a list of tracks, as most conventional
* video editing software does -- rather, each sequence holds a _fork of nested scopes._
* This recursively nested structure is reflected in the patchbay area corresponding to
* each track in the _header pane_ of the timeline display, located to the left. The
* patchbay for each track is a grid with four quadrants, and the 4th quadrant is the
* _content area,_ which is recursively extended to hold nested PatchbayWidget elements,
* corresponding to the child tracks of this track. To _fund_ this recursively extensible
* structure, we need to set up the first four quadrants
*/
void
PatchbayWidget::installFork (TrackHeadWidget& rootTrackHead)
{
UNIMPLEMENTED ("how actually to represent the track in the patchbay");
}
}}// namespace gui::timeline

View file

@ -56,7 +56,9 @@
namespace gui {
namespace timeline {
class TrackHeadWidget;
/**
* Header pane control area corresponding to a Track with nested child Tracks.
@ -69,9 +71,12 @@ namespace timeline {
: public Gtk::Grid
{
public:
PatchbayWidget ();
PatchbayWidget();
~PatchbayWidget();
/** @internal Initially install the contents corresponding to this track fork */
void installFork (TrackHeadWidget& rootTrackHead);
private:/* ===== Internals ===== */
};

View file

@ -71,10 +71,19 @@ namespace timeline {
}
/**
* This function is invoked once for each new TimelineWidget, in order to build the
* starting point for the track widget structure, which then can be extended recursively
* to add further nested tracks. The central problem for this widget hierarchy is that
* we have to build two matching structures in parallel...
* - the track header area ("patchbay")
* - the corresponding track body with actual content (clips)
*/
void
TimelineLayout::installRootTrack(TrackHeadWidget& head, TrackBody& body)
{
UNIMPLEMENTED ("attach the widgets for the root track display");
headerPane_.installForkRoot (head);
bodyCanvas_.installForkRoot (body);
}

View file

@ -101,7 +101,10 @@ namespace timeline {
/**
* @todo WIP-WIP as of 12/2016
* Top-level anchor point for the timeline display (widgets).
* The central entity to organise concerns relevant for the presentation of the
* Timeline as a whole, as opposed to rendering individual tracks as part of the Timeline.
* @todo WIP-WIP as of 10/2018
*/
class TimelineLayout
{
@ -115,6 +118,7 @@ namespace timeline {
TimelineLayout (Gtk::Paned&);
~TimelineLayout();
/** @internal anchor the display of the root track into the two display panes */
void installRootTrack (TrackHeadWidget&,TrackBody&);
private:/* ===== Internals ===== */

View file

@ -33,13 +33,13 @@
** to a single session::Timeline, known by its ID. The widget creates a TimelineController
** right away, which takes initiative to populate the display with that Timeline's contents.
**
** #Lifecycle
** # Lifecycle
** The assumption is that any element creation and deletion is triggered through messages over
** the [UI-Bus](\ref ui-bus.hpp). So there will be a _parent element,_ corresponding to the
** ["model root"](\ref session::Root), and this parent, in response to some mutation message,
** will create a TimelineWidget, add it into the appropriate GTK display setup and manage it
** as child element; the [construction parameters](\ref TimelineWidget::TimelineWidget] ensure
** it gets connected to the bus as well. Incidentally, this assumption also implies that
** [model root](\ref proc::mobject::session::Root), and this parent, in response to some
** mutation message, will create a TimelineWidget, add it into the appropriate GTK display setup
** and manage it as child element; the [construction parameters](\ref TimelineWidget::TimelineWidget)
** ensure it gets connected to the bus as well. Incidentally, this assumption also implies that
** this parent element has set up a _binding for diff mutation,_ typically by implementing
** model::Tangible::buildMutator. And further on this means that the parent will also
** destroy the TimelineWidget, prompted by a message to that end. All deregistration

View file

@ -2691,12 +2691,12 @@ As starting point, {{red{in winter 2016/17}}} the old (broken) timeline panel wa
After that initial experiments, my focus shifted to the still unsatisfactory top-level UI structure, and I am working towards an initial integration with Proc-Layer since then.
</pre>
</div>
<div title="GuiClipWidget" creator="Ichthyostega" modifier="Ichthyostega" created="201611180114" modified="201810071842" tags="GuiPattern design draft" changecount="27">
<div title="GuiClipWidget" creator="Ichthyostega" modifier="Ichthyostega" created="201611180114" modified="201810281742" tags="GuiPattern design draft" changecount="29">
<pre>//The representation of a [[media clip|Clip]] for manipulation by the user within the UI.//
Within Lumiera, a clip is conceived as a //chunk of media,// which can be handled in compound. Clip as such is an abstract concept, which is treated with minimal assumptions...
* we know that a clip has //media content,// which need not be uniform and can be inherently structured (e.g. several media, several channels)
* we know that a clip has //media content,// which need not be uniform and can be inherently structured (e.g. several media, several channels, a processing function)
* like anything within a timeline, a clip has a temporal extension (but not necessarily finite; it can //not be zero,// but it can be infinite)
* by virtue of [[Placement]], a clip acquires a time position.
* by virtue of [[Placement]], a clip acquires a time position. (&amp;rarr; GuiPlacementDisplay)
Due to this unspecific nature, a clip might take on various //appearances// within the UI.
!clip appearances
@ -3393,6 +3393,15 @@ The most fundamental principle is that of ''subsidiarity'': we understand both &
Based on these foundations, we shape and form the core part of the interface, which is the [[timeline display|GuiTimelineView]]
</pre>
</div>
<div title="GuiPlacementDisplay" creator="Ichthyostega" modifier="Ichthyostega" created="201810281744" modified="201810281751" tags="spec GuiPattern draft" changecount="2">
<pre>A cross-cutting and somewhat tricky concern is how to represent and expose the [[MObject Placements|Placement]] within the UI.
For one, a Placement is a set of rules picked up from enclosing scopes -- and it is one of the most fundamental traits of Lumiera that the user is able to edit those placement rules. Yet the combination and final application of those rules also materialises itself into actual properties of the objects of the edit session -- most notably the time position of any element. Consequently, parts of the Placement are unfolded to appear as properties of the placed object, as far as the UI representation is concerned. However, Placement as a generic building block is prominently exposed, insofar pretty much every entity you'll see in the UI has the ability to &quot;edit its placement&quot;. This is indicated by a characteristic Icon decoration, leading to a common placement editor, where the user can
* see all the explicitly given locating pins
* see the effective, resulting ExplicitPlacement
* add and manipulate existing rules
To support this generic setup, pretty much every UI element needs to be outfitted with a &quot;placement&quot; attribute, to reflect those distinct information to be exposed in aforementioned placement edit UI.
</pre>
</div>
<div title="GuiStart" modifier="Ichthyostega" created="200812050525" modified="201808070201" tags="GuiIntegration GuiPattern" changecount="9">
<pre>Starting up the GUI is optional and is considered part of the Application start/stop and lifecycle.
* main and {{{lumiera::AppState}}} activate the lifecyle methods on the ~GuiSubsysDescriptor, accessible via the GuiFacade
@ -3448,7 +3457,7 @@ Now, since we build our UI on the notion of mapping session contents via a messa
In any case, this is an advanced topic, and nowhere near trivial. It seems reasonable to reject opening duplicate timeline presentations as a first step, and then address this topic way later, when we've gained sufficient knowledge regarding all the subtleties of timeline presentation and editing.</pre>
</div>
<div title="GuiTimelineView" creator="Ichthyostega" modifier="Ichthyostega" created="201410160100" modified="201810111203" tags="GuiPattern design decision draft img" changecount="55">
<div title="GuiTimelineView" creator="Ichthyostega" modifier="Ichthyostega" created="201410160100" modified="201810281754" tags="GuiPattern design decision draft img" changecount="58">
<pre>Within the Lumieara GUI, the [[Timeline]] structure(s) from the HighLevelModel are arranged and presented according to the following principles and conventions.
Several timeline views may be present at the same time -- and there is not necessarily a relation between them, since »a Timeline« is the top-level concept within the [[Session]]. Obviously, there can also be several //views// based on the same »Timeline« model element, and in this latter case, these //coupled views// behave according to a linked common state. An entity »Timeline« as represented through the GUI, emerges from the combination of several model elements
* a root level [[Binding|BindingMO]] acts as framework
@ -3482,10 +3491,13 @@ This collapsed, expanded and possibly nested workspace structure is always exact
!!!lifecycle and instances
A given instance of the {{{TimelineWidget}}} is always dedicated to render the contents of //one specific timeline.// We never switch the data model while retaining the UI entities. This also means, a given instance is tied to one conversation with the core; it is created when the core tells us about this timeline with an initial population diff, and it lives until either this timeline is discarded in the core model, or the whole session is shut down.
The dockable ''timeline pannel'' holds onto the existing {{{TimelineWidget}}} instances, allowing to make one of them visible for interaction. Yet the timeline itself is represented by the {{{TimelineControler}}}, which lives //within this widget,// and is attached to and managed by the InteractionDirector, who incorporates the role of representing the [[model root|ModelRootMO]]. Aside of the timeline display, there is also an ''asset pannel''; display of and interaction with those asset views is handled by dedicated widgets, backed by the {{{AssetControler}}} -- which is also a child of and managed by the InteractionDirector, since assets are modelled as global part of the [[Session]].
The dockable ''timeline pannel'' holds onto the existing {{{TimelineWidget}}} instances, allowing to make one of them visible for interaction. Yet the timeline itself is represented by the {{{TimelineControler}}}, which lives //within this widget,// and is attached to and managed by the InteractionDirector, who incorporates the role of representing the [[model root|ModelRootMO]]. To bridge this conflict of control, we introduce a {{{TimelineGui}}} proxy for each timeline. Which allows to close a timeline on widget level, thereby marking it as detached from model updates; and it enables child manipulation by the {{{InteractionDirector}}}, automatically forwarding to the respective {{{TimelineController}}} if applicable. Aside of the timeline display, there is also an ''asset pannel''; display of and interaction with those asset views is handled by dedicated widgets, backed by the {{{AssetControler}}} -- which is also a child of and managed by the InteractionDirector, since assets are modelled as global part of the [[Session]].
In case the UI starts with no session present in the core, an //empty timeline placeholder// will be displayed, which provides UI for creating a new session...
!!!Placements
As indicated in the drawing above, pretty much every UI element exposes a button to //edit its placement.// &amp;rarr; GuiPlacementDisplay
!!!slave Timelines
It is reasonable to expect the ability to have multiple [[slave timeline presentations|GuiTimelineSlave]] to access the same underlying timeline structure.
@ -3498,16 +3510,16 @@ In the most general case, there can be per-track content and nested content at t
&amp;rarr; important question: how to [[organise the widgets|GuiTimelineWidgetStructure]]
</pre>
</div>
<div title="GuiTimelineWidgetStructure" creator="Ichthyostega" modifier="Ichthyostega" created="201410250002" modified="201810262342" tags="GuiPattern discuss decision impl" changecount="93">
<div title="GuiTimelineWidgetStructure" creator="Ichthyostega" modifier="Ichthyostega" created="201410250002" modified="201810281438" tags="GuiPattern discuss decision impl" changecount="105">
<pre>The Timeline is probably the most prominent place in the GUI where we need to come up with a custom UI design.
Instead of combining standard components in one of the well-known ways, here we need to come up with our own handling solution -- which also means to write one or several custom GTK widgets. Thus the question of layout and screen space division and organisation becomes a crucial design decision. The ~GTK-2 Gui, as implemented currently, did already take some steps along this route, yet this kind of decision should be cast and documented explicitly (be it after the fact).
Instead of combining standard components in one of the well-known ways, here we need to come up with our own handling solution -- which also involves to build several custom GTK widgets. Thus the question of layout and screen space division and organisation becomes a crucial design decision. The ~GTK-2 UI, as implemented during the initial years of the Lumiera project, did already take some steps along this route, which was was valuable as foundation for assessment and further planning.
Right away, this topic touches a tricky design and architectural challenge: the → question [[how to organise custom widgets|GuiCustomWidget]].
As it stands, this topic touches a tricky design and architectural challenge: the → question [[how to organise custom widgets|GuiCustomWidget]].
In a nutshell, ~GTKmm offers several degrees of customisation, namely to build a custom widget class, to build a custom container widget, and to use the [[Gtk::Layout &quot;canvas widget&quot;|GtkLayoutWidget]], possibly combined with //custom drawing.// In addition to assembling a timeline widget class by combining several nested panes, the timeline display needs to rely on the latter approach to allow for the necessary flexible arrangement of [[clip widgets|GuiClipWidget]] within the [[track fork|Fork]].
!the header pane problem
From general considerations we draw the conclusion to have a track header pane area always visible to the left. For one this means that our top level widget organisation in the timeline will be a horizontal split. And then this means that we get two distinct sub widgets, whose vertical layout needs to be kept in sync. And even more so, presumably the most adequate implementation technique is different in both parts: the header pane looks like a classical fit for the paradigm of nested boxes and grid layout within those boxes, while the right part -- the actual track contents -- impose very specific layout constraints not served by any of the pre-existing layout containers, which means we have to resort to custom drawing on a canvas widget. Following this line of thought, we need an overarching layout manager to coordinate these two disjoint technologies. Any viable alternatives?
Based on principles of //conventional UI design,// we derive the necessity to have a track header pane area, always visible to the left, and scrolling vertically in sync with the actual track display to the right of the timeline area. This insight brings about several consequences. For one this means that our top level widget organisation in the timeline will be a horizontal split. And furthermore this means that we get two distinct sub widgets, whose vertical layout needs to be kept in sync. And even more so, presumably the most adequate implementation technique is different in both parts: the header pane looks like a classical fit for the paradigm of nested boxes and grid layout within those boxes, while the right part -- the actual track contents -- impose very specific layout constraints, not served by any of the pre-existing layout containers -- which means we have to resort to custom drawing on a canvas widget. Following this line of thought, we need an overarching layout manager to coordinate these two disjoint technologies. Any viable alternatives?
!!!considering a table grid layout
The layout mechanics we try to establish here by explicit implementation would be more or less a given, if instead we'd build the whole timeline display from one base widget, which needs to be a table layout, i.e. {{{Gtk::Grid}}}. We'd use two columns, one for the header pane area, one for the timeline display, and we'd use N+1 rows, with the head row holding the time ruler and the additional rows holding individual tracks. But to get the specific UI mechanics desirable for a timeline display, we had to introduce some twists:
@ -3522,13 +3534,13 @@ On the other hand, what would be the //obvious benefits...?//
While the special setup for scrolling doesn't really count (since it is necessary anyway), after this initial investigation it seems clear that a global grid layout doesn't yield enough benefit to justify all the quirks and limitations its use would impose.
!!!follow-up to the obvious choices
We came to this point of re-considering the overall organisation of widgets, after having to re-write the initial version of our timeline widget. This initial version was developed by Joel Holdsworth, and it followed a similar reasoning, involving a global timeline layout manager. The distinction between the two panes was not so clear though, and the access to the model code was awkward at places, so the necessity to re-write the timeline widget due to the transition to ~GTK-3 looks like a good opportunity to re-do the same basic reasoning a second time, verify decisions taken and improve matters turning out as difficult on first attempt.
We came to this point of re-considering the overall organisation of widgets, after having to re-write the initial version of our timeline widget. This initial version was developed until 2011 by Joel Holdsworth, and it followed a similar reasoning, involving a global timeline layout manager. The distinction between the two panes was not so clear though, and the access to the model code was awkward at places, so the necessity to re-write the timeline widget due to the transition to ~GTK-3 looks like a good opportunity to re-do the same basic reasoning a second time, verify decisions taken and improve matters turning out as difficult on first attempt.
So we get a timeline custom widget, which at top level establishes this two-part layout, provides the global scrollbars and integrates custom widget components for both parts. And this top-level timeline widget owns a layout manager, plus it exposes a common view management interface, to be used both from internal components (e.g. zoom widgets within the UI) and from external actors controlling the timeline display from a global level. Also at this global level, we get to define a layout control interface, the TimelineDisplayManager, which has to be implemented within the recursively structured parts of the timeline display, and which is used by the global layout manager to exert control over the layout as a whole. {{red{Note (11/2016)}}}: if this layout control interface works push or pull style is a decision yet to be worked out during the course of the implementation.
So we get a timeline custom widget, which at top level establishes this two-part layout, provides the global scrollbars and integrates custom widget components for both parts. And this top-level timeline widget owns a layout manager, plus it exposes a common view management interface, to be used both from internal components (e.g. zoom widgets within the UI) and from external actors controlling the timeline display from a global level. Also at this global level, we get to define a layout control interface, the TimelineDisplayManager, which has to be implemented within the recursively structured parts of the timeline display, and which is used by the top-level structure to exert control over the layout as a whole. {{red{Note (11/2016)}}}: if this layout control interface works push or pull style is a decision yet to be worked out during the course of the implementation.
!dealing with nested structures
The handling of objects structured into nested scopes is a hallmark of the very specific approach taken by Lumiera when it comes to attaching, arranging and relating media objects. But here in the UI display of the timeline, this approach creates a special architectural challenge: the only sane way to deal with nested structures without exploding complexity is to find some way to exploit the ''recursive self similarity'' inherent in any tree structure. But the problematic consequence of this assessment is the tension, even contradiction it creates to the necessities of GUI programming, which forces us to come up with one single, definitive widget representation of what is going on eventually. The conclusion is that we need to come up with an interface such as to allow building and remoulding of the UI display through incremental steps -- where each of this incremental steps relies solely on relative, context based information. Because this is the only way we can deal with building a tree structure by recursive programming. We must not demand the individual step to know its arrangement within the tree, other than indicating a &quot;current&quot; or a &quot;parent&quot; reference point.
The handling of objects structured into nested scopes is a hallmark of the very specific approach taken by Lumiera when it comes to attaching, arranging and relating media objects. But here in the UI display of the timeline, this approach creates a special architectural challenge: the only sane way to deal with nested structures without exploding complexity is to find some way to exploit the ''recursive self similarity'' inherent in any tree structure. But the problematic consequence of this assessment is the tension, even contradiction it creates to the necessities of GUI programming, which forces us to come up with one single, definitive widget representation of what is going on eventually. The conclusion is that we need to come up with an interface such as to allow building and remoulding of the UI display through incremental steps -- where each of this incremental steps relies solely on relative, context based information. Because this is the only way we can deal with building a tree structure by recursive programming. We must not allow the individual step to know its arrangement within the tree, other than indicating a &quot;current&quot; or a &quot;parent&quot; reference point.
The structure of the display is extended or altered under two circumstances:
# some component receives a [[diff mutation message|MutationMessage]], prompting to add or remove a //child component.//
@ -3538,12 +3550,12 @@ Here, the &quot;component&quot; relevant for such structural changes is always t
From these observations we can draw the conclusion, that we'll build a ''local structural model'', to reflect the logical relations between the parts comprising the timeline display. More precisely, these structuring components are not mere model objects, rather they are mediating entities used to guide and organise the actual view entities, which in turn are passive. They are more like a view model, while also bearing some local controller responsibilities. For this reason, we prefer to term these as ''presenters'' -- i.e. TrackPresenter and ClipPresenter. And each of these local representation components holds onto a ''display context'', which generally links it //into two different display widget stacks// within the two parts of the actual timeline display. Adding a child component thus becomes a rather tricky operation, involving to link possibly two child widgets into two disjoint parent widgets, thereby forming a similar display context for the child presenter. Overall, the guiding idea is that of self similarity: on each level, we have to reproduce the same relations and collaborations as present in the parent level.
!!!building the timeline representation structure
It is a fundamental decision within the Lumiera UI that any structure as to be injected as diff by the core. We do not hold a structure or data model within some UI entity and then &quot;interpret&quot; that model into a widget structure -- rather we build the widget structure itself in response to a diff message describing the structure. Especially in the case of timelines, the receiver of those messages is the InteractionDirector, which corresponds to the model root. On being prompted to set up a timeline, it will allocate a {{{TimelineWidget}}} within a suitable timeline docking panel and then attach to the {{{TimelineController}}} embedded within the widget.
It is a fundamental decision within the Lumiera UI that any structure has to be injected as diff by the core. We do not hold a structure or data model within some UI entity and then &quot;interpret&quot; that model into a widget structure -- rather we build the widget structure itself in response to a diff message describing the structure. Especially in the case of timelines, the receiver of those messages is the InteractionDirector, which corresponds to the model root. On being prompted to set up a timeline, it will allocate a {{{TimelineWidget}}} within a suitable timeline docking panel and then attach to the {{{TimelineController}}} embedded within the widget.
{{red{Problem 10/2018}}} how can the InteractionDirector //manage// a timeline while the timeline widget physically resides within the panel? Can we exploit a simliar structure as we did for the error log?
* in part yes -- however we introduce an mediating entity, the {{{TimelineGui}}} proxy
* it uses a {{{WLink}}} to refer to the actual {{{TimelineWidget}}} -- meaning the widget need not exist, and will detach automatically when destroyed by its holder, which is the {{{TimelinePanel}}}
* {{red{TODO 11/2018}}} still need to care for removing a {{{TimelineWidget}}} when the logically managing parent, i.e. the InteractionDirector deletes the {{{TimelineGui}}} proxy
* in part yes -- however we introduce a mediating entity, the {{{TimelineGui}}} proxy
* it uses a {{{WLink}}} to refer to the actual {{{TimelineWidget}}} -- meaning the widget need not exist, and will detach automatically when destroyed by its holder, which is the {{{TimelinePanel}}}
* {{red{TODO 11/2018}}} still need to care for removing a {{{TimelineWidget}}} when the logically managing parent, i.e. the InteractionDirector deletes the {{{TimelineGui}}} proxy
The diff describing and thus assembling the UI representation of a timeline is typically a ''population diff'' -- which means, it is a consolidated complete description of the whole sub-structure rooted below that timeline. Such a population diff is generated as emanation from the respective DiffConstituent.
@ -3553,7 +3565,7 @@ Applying a diff changes the structure, that is, the structure of the local model
* when a model element happens to be destructed, the corresponding display element has to be removed.
* such might be triggered indirectly, by clean-up of leftovers, since the {{{DiffApplicator}}} re-orders and deletes by leaving some data behind
* the diff also re-orders model elements, which does not have an immediate effect on the display, but needs to be interpreted separately.
Wrapping this together we get a fix up stage after model changes, where the display is re-adjusted to fit the new situation. This works in concert with the [[display manager|TimelineDisplayManager]] representing only those elements as actual widgets, which get a real chance to become visible. This way we can build on the assumption that the actual number of widgets to be managed any time remains so small as to get away with simple linear list processing. It remains to be seen how far this assumption can be pushed -- the problem is that the GTK container components don't support anything beyond such simple linear list processing; there isn't even a call to remove all child widgets of a container in a single pass.
Wrapping this together we get a //fix up stage// after any model changes, where the display is re-adjusted to fit the new situation. This works in concert with the [[display manager|TimelineDisplayManager]] representing only those elements as actual widgets, which get a real chance to become visible. This way we can build on the assumption that the actual number of widgets to be managed any time remains so small as to get away with simple linear list processing. It remains to be seen how far this assumption can be pushed -- the problem is that the GTK container components don't support anything beyond such simple linear list processing; there isn't even a call to remove all child widgets of a container in a single pass.
</pre>
</div>
<div title="GuiTopLevel" creator="Ichthyostega" modifier="Ichthyostega" created="201701261944" modified="201708311555" tags="GuiPattern spec draft" changecount="12">
@ -9016,13 +9028,13 @@ Besides building on the asset management, implementing Timeline (and Sequence) a
This topic is {{red{postponed as of 10/2018}}} &amp;rarr; [[#1083|http://issues.lumiera.org/ticket/1083]]
</pre>
</div>
<div title="TimelineDisplayManager" creator="Ichthyostega" modifier="Ichthyostega" created="201611280235" modified="201612011740" tags="spec GuiPattern img" changecount="4">
<div title="TimelineDisplayManager" creator="Ichthyostega" modifier="Ichthyostega" created="201611280235" modified="201810281443" tags="spec GuiPattern img" changecount="5">
<pre>//guide and control the concrete display properties of the various sub components (tracks, clips) comprising a timeline display.//
The TimelineDisplayManager actually is an abstraction, a control interface, revolving around the guidance individual components need in order to settle on a proper display. Those components are represented as mediating entities, the TrackPresenter and the ClipPresenter, each of which controls and manages a mostly passive GTK widget. To this end, the presenters need to know at which virtual coordinates their corresponding widgets would show up, and they need to know if these widgets need to be actually present at the moment. Also, especially the ClipPresenter needs to know which ''clip appearance style'' to choose for the corresponding slave widget.
!display evaluation pass
[&gt;img[Clip presentation control|uml/Timeline-display-evaluation.png]]Since the timeline display is formed by several nested collections of elements, a proper display layout must incorporate information from all those parts. A naive approach likely would just iterate over them and reach in to extract data -- or even worse, build a separate display data store, which then needs to be kept in sync with the component hierarchy. Any of this would lead to high coupling and turns any necessary adjustment and changes due to evolution of requirements into a liability. Such a dangerous situation can be avoided altogether by admitting the collaborative nature of the task at hand. To get there, we need to distil the abstraction shared between the global and the local concerns, and we need to shape it such as to build onto some kind of invariant, allowing that abstraction to re-emerge at several levels of locality.
[&gt;img[Clip presentation control|uml/Timeline-display-evaluation.png]]Since the timeline display is formed by several nested collections of elements, a proper display layout must incorporate information from all those parts. A naive approach likely would just iterate over them and reach in to extract data -- or even worse, build a separate display data store, which then needs to be kept in sync with the component hierarchy. Any of these naive working styles would lead to high coupling and it turns the necessary adjustment and changes into a liability, which raises due to evolution of requirements. Over time, such a dangerous situation can only be avoided altogether by admitting the collaborative nature of the task at hand. To get there, we need to distil the abstraction shared between the global and the local concerns, and we need to shape it such as to build onto some kind of invariant, allowing that abstraction to re-emerge at several levels of locality.
</pre>
</div>
<div title="TimelineSequences" modifier="Ichthyostega" created="200811011836" modified="201505310138" tags="design decision img" changecount="4">
@ -9126,10 +9138,10 @@ Placements are __resolved__ resulting in an ExplicitPlacement. In most cases thi
&amp;rarr; [[Definition|Pipe]] and [[handling of Pipes|PipeHandling]]
</pre>
</div>
<div title="TrackPresenter" creator="Ichthyostega" modifier="Ichthyostega" created="201611280207" modified="201810262349" tags="def GuiPattern" changecount="6">
<div title="TrackPresenter" creator="Ichthyostega" modifier="Ichthyostega" created="201611280207" modified="201810281454" tags="def GuiPattern" changecount="11">
<pre>//mediating entity used to guide and control the track-like nested working space in the timeline display of the UI.//
Similar to the ClipPresenter, from a global angle this element fulfils a model like role, while also guiding and controlling a mostly passive view component implemented as GTK widget. Here, the authority of the presenter over the widget must be total, since display management //needs to work automatically,// due to model updates and mutations arriving as [[diff messages|MutationMessage]]. In addition, this structure is prerequisite for (possibly) implementing UI rendering optimisations, since it allows us to leave out widgets entirely, when it is clear they won't become visible: A ''display evaluation pass'', which is effectively a //tree walk,// consecutively visits each part of the timeline structure, to negotiate its concrete display properties in collaboration with a global TimelineDisplayManager. As a result, the presenter knows where to show its corresponding view, and it knows if to show it at all, allowing to either adjust, create or destroy actual GTK widgets within its local reference frame.
Similar to the ClipPresenter, judged from a global angle, this element fulfils a model-like role, while at the same time guiding and controlling a mostly passive view component, implemented as GTK widget. Here, the authority of the presenter over the widget must be total, since display management //needs to work automatically,// due to model updates and mutations arriving as [[diff messages|MutationMessage]]. In addition, this structure is prerequisite for (possibly) implementing UI rendering optimisations, since it allows us to leave out widgets entirely, when it is clear they won't become visible: A ''display evaluation pass'', which is effectively a //tree walk,// consecutively visits each part of the timeline structure, to negotiate its concrete display properties in collaboration with a global TimelineDisplayManager. As a result, the presenter knows where to show its corresponding view, and it knows if to show it at all, allowing to either adjust, create or destroy actual GTK widgets within its local reference frame.
A special twist arises from the fact that track display has to happen aligned and in sync within the two display panes of the timeline at the same time. This means that each TrackPresenter has to hold and manage //two slave display elements,// each of which is inserted within a disjoint hierarchy of display elements. For one, there is the {{{timeline::TrackHeadWidget}}} which in turn renders a {{{PatchbayWidget}}}, and on the other side there is a {{{TrackBody}}} element, which is not strictly a widget of itself, but inserted into our custom drawing {{{timeline::BodyCanvasWidget}}} to manage the custom drawing of the respective track working area.
@ -9137,7 +9149,7 @@ To deal with this typical problem of recursive programming, we introduce a bindi
* the TrackPresenter holds a {{{DisplayFrame}}} member, which in turn houses the two head and body widgets. So basically the widgets are allocated within the presenter.
* however, after constructing the {{{DisplayFrame}}}, it invokes an anchor functor, which is passed in from the recursive outer scope, and which does the actual work of installing those widgets into the apropriate parent widgets
* but the {{{DisplayFrame}}} itself also exposes such an anchor functor, which can be passed down when (recursively) creating a TrackPresenter for a sub-Track
* obviously, at top-level, we need to digress from that general pattern -- here it is the {{{timeline::LayoutManager}}} responsible for the display structure of the whole timeline which exposes the necessary anchor function to attach the root track for a timeline.
* obviously, at top-level, we need to digress from that general pattern -- here it is the {{{timeline::TimelineLayout}}} responsible for the display structure of the whole timeline which exposes the necessary anchor function to attach the root track for a timeline.
</pre>
</div>
<div title="TransitionsHandling" modifier="Ichthyostega" created="200712080417" modified="200805300100" tags="def design">

View file

@ -18338,7 +18338,7 @@
<node CREATED="1480807594822" ID="ID_333819085" MODIFIED="1518487921081" TEXT="f&#xfc;hre mal einen Marker-Typ ein"/>
</node>
</node>
<node COLOR="#435e98" CREATED="1538957611472" ID="ID_1996849782" MODIFIED="1538957931290" TEXT="erwartet gem&#xe4;&#xdf; UI Modell-Schema">
<node COLOR="#435e98" CREATED="1538957611472" ID="ID_1996849782" MODIFIED="1540748032493" TEXT="erwartet gem&#xe4;&#xdf; UI Modell-Schema">
<arrowlink COLOR="#919fc6" DESTINATION="ID_165150753" ENDARROW="Default" ENDINCLINATION="-902;329;" ID="Arrow_ID_651613049" STARTARROW="None" STARTINCLINATION="823;93;"/>
<icon BUILTIN="info"/>
<node COLOR="#435e98" CREATED="1540639547294" ID="ID_133813709" MODIFIED="1540639561155" TEXT="TimelineID"/>
@ -19065,8 +19065,9 @@
</node>
<node CREATED="1540641305092" HGAP="50" ID="ID_18552766" MODIFIED="1540684353686" TEXT="PatchbayWidget" VSHIFT="-2">
<arrowlink DESTINATION="ID_151954769" ENDARROW="Default" ENDINCLINATION="68;9;" ID="Arrow_ID_1923805019" STARTARROW="None" STARTINCLINATION="110;25;"/>
<node CREATED="1540682893097" HGAP="65" ID="ID_419298158" MODIFIED="1540684338784" TEXT="interne Struktur?" VSHIFT="11">
<icon BUILTIN="help"/>
<node BACKGROUND_COLOR="#ccb59b" COLOR="#6e2a38" CREATED="1540682893097" HGAP="65" ID="ID_419298158" MODIFIED="1540747497085" TEXT="interne Struktur" VSHIFT="11">
<font ITALIC="true" NAME="SansSerif" SIZE="14"/>
<icon BUILTIN="yes"/>
<node CREATED="1540682901320" ID="ID_1054686680" MODIFIED="1540684245713" TEXT="stacked boxes?">
<icon BUILTIN="button_cancel"/>
</node>
@ -19087,6 +19088,27 @@
</node>
</node>
</node>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1540747470806" HGAP="97" ID="ID_831752558" MODIFIED="1540747508242" TEXT="Frage: wieviel Indirektion?" VSHIFT="5">
<icon BUILTIN="help"/>
<node CREATED="1540747516509" ID="ID_1965076800" MODIFIED="1540747526726" TEXT="ist TimelineHeaderWidget &#xfc;berfl&#xfc;ssig?"/>
<node CREATED="1540747533463" ID="ID_1845120165" MODIFIED="1540747546555" TEXT="dann w&#xe4;re TimelineHeader == PatchbayWidget"/>
<node CREATED="1540747550734" ID="ID_843399235" MODIFIED="1540748388723" TEXT="macht Sinn...">
<richcontent TYPE="NOTE"><html>
<head>
</head>
<body>
<p>
...denn das Patchbay-Widget mu&#223; diverse Interna des Tracks beachten,
</p>
<p>
so z.B. sein Placement, welches <i>teilweise als Properties</i>&#160;des Track abgebildet wird.
</p>
</body>
</html>
</richcontent>
</node>
</node>
</node>
</node>
</node>
@ -20806,7 +20828,7 @@
</html></richcontent>
</node>
</node>
<node CREATED="1538956606744" ID="ID_165150753" MODIFIED="1538957931290" TEXT="erwartete Modell-Struktur">
<node CREATED="1538956606744" ID="ID_165150753" MODIFIED="1540748009672" TEXT="erwartete Modell-Struktur">
<linktarget COLOR="#919fc6" DESTINATION="ID_165150753" ENDARROW="Default" ENDINCLINATION="-902;329;" ID="Arrow_ID_651613049" SOURCE="ID_1996849782" STARTARROW="None" STARTINCLINATION="823;93;"/>
<linktarget COLOR="#919fc6" DESTINATION="ID_165150753" ENDARROW="Default" ENDINCLINATION="-641;482;" ID="Arrow_ID_593956858" SOURCE="ID_580392349" STARTARROW="None" STARTINCLINATION="1108;116;"/>
<node CREATED="1538956633404" ID="ID_266268175" MODIFIED="1538956634704" TEXT="Root">
@ -20844,6 +20866,9 @@
<node CREATED="1538956945754" ID="ID_30162898" MODIFIED="1538956953906" TEXT="&quot;name&quot;"/>
<node CREATED="1538956945754" ID="ID_39934842" MODIFIED="1538957154865" TEXT="&quot;kind&quot; = LOOP|MARK"/>
</node>
<node CREATED="1538956606744" ID="ID_388585037" MODIFIED="1540748246007" TEXT="Placement">
<linktarget COLOR="#919fc6" DESTINATION="ID_388585037" ENDARROW="None" ENDINCLINATION="-682;462;" ID="Arrow_ID_726807054" SOURCE="ID_1483911882" STARTARROW="Default" STARTINCLINATION="638;-81;"/>
</node>
</node>
</node>
<node CREATED="1523019349923" ID="ID_353542665" MODIFIED="1523019373947" TEXT="UI-Koordinaten (UICoord)">
@ -30620,10 +30645,19 @@
<node CREATED="1448063874479" HGAP="43" ID="ID_739054690" MODIFIED="1538957545166" TEXT="UI-Binding" VSHIFT="1">
<icon BUILTIN="yes"/>
<node CREATED="1538957594154" ID="ID_2785316" MODIFIED="1538957602189" TEXT="erwartete Modell-Struktur">
<node COLOR="#435e98" CREATED="1538957611472" ID="ID_580392349" MODIFIED="1538957734503" TEXT="gem&#xe4;&#xdf; UI Modell-Schema">
<node COLOR="#435e98" CREATED="1538957611472" ID="ID_580392349" MODIFIED="1540748016895" TEXT="gem&#xe4;&#xdf; UI Modell-Schema">
<arrowlink COLOR="#919fc6" DESTINATION="ID_165150753" ENDARROW="Default" ENDINCLINATION="-641;482;" ID="Arrow_ID_593956858" STARTARROW="None" STARTINCLINATION="1108;116;"/>
<icon BUILTIN="info"/>
</node>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1538957611472" ID="ID_1483911882" MODIFIED="1540748246007" TEXT="Aufgabe: Placement GUI-Repr&#xe4;sentation">
<arrowlink COLOR="#919fc6" DESTINATION="ID_388585037" ENDARROW="None" ENDINCLINATION="-682;462;" ID="Arrow_ID_726807054" STARTARROW="Default" STARTINCLINATION="638;-81;"/>
<icon BUILTIN="flag-yellow"/>
<node CREATED="1540748268638" ID="ID_59379201" MODIFIED="1540748330629" TEXT="Grundkonzept">
<icon BUILTIN="yes"/>
<node CREATED="1540748279917" ID="ID_85147625" MODIFIED="1540748291791" TEXT="materialisierte Eigenschaften erscheinen auf dem Target"/>
<node CREATED="1540748293067" ID="ID_1744964690" MODIFIED="1540748324524" TEXT="(meta)-Einstellungen des Placements erscheinen in einem geschachtelten Objekt"/>
</node>
</node>
</node>
</node>
<node CREATED="1434128074725" FOLDED="true" HGAP="28" ID="ID_933994138" MODIFIED="1538870799628" TEXT="Diff-System" VSHIFT="1">