Timeline: requirement analysis to define the ZoomWindow (see #1196)

This commit is contained in:
Fischlurch 2022-10-29 01:59:42 +02:00
parent 7eca11b332
commit b3fe6e16c6
5 changed files with 590 additions and 74 deletions

View file

@ -87,19 +87,19 @@ namespace model {
TimeSpan
coveredTime() const override
{
return zoomWindow_.overallSpan;
return zoomWindow_.overallSpan();
}
int
translateTimeToPixels (TimeValue startTimePoint) const override
{
return _raw(startTimePoint) * zoomWindow_.px_per_sec / Time::SCALE;
return _raw(startTimePoint) * zoomWindow_.px_per_sec() / Time::SCALE;
}
TimeValue
applyScreenDelta(Time anchor, double deltaPx) const override
{
return anchor + TimeValue{gavl_time_t(Time::SCALE * deltaPx / zoomWindow_.px_per_sec)};
return anchor + TimeValue{gavl_time_t(Time::SCALE * deltaPx / zoomWindow_.px_per_sec())};
}
};

View file

@ -26,19 +26,43 @@
** This is a generic component to represent and handle the zooming and positioning of
** views within an underlying model space. This model space is conceived to be two fold:
** - it is a place or excerpt within the model topology (e.g. the n-th track in the fork)
** - it has a temporal extension within a larger temporal frame (e.g. some seconds within the timeline)
** - it has a temporal extension within a larger temporal frame (e.g. some seconds within
** the timeline)
** This component is called »Zoom Window«, since it represents a window-like local visible
** interval, embedded into a larger time span covering the whole timeline.
**
** # rationale
** # Rationale
**
** TBW
** Working with and arranging media requires a lot of navigation and changes of zoom detail level.
** More specifically, the editor is required to repeatedly return to the same locations and show
** arrangements at the same alternating scale levels. Most existing editing applications approach
** this topic naively, by just responding to some coarse grained interaction controls thereby
** creating the need for a lot of superfluous and tedious search and navigation activities,
** causing constant grind for the user. And resolving these obnoxious shortcomings turns out
** as a never ending task, precisely due to the naive and ad-hoc approach initially taken.
** Based on these observations, the design of the Lumiera UI calls for centralisation of
** all zoom- and navigation handling into a single component, instantiated once for every
** visible context, outfitted with the ability to capture and maintain a history of zoom
** and navigation activities. The current zoom state is thus defined by
** - the overall TimeSpan of the timeline, defining a start and end time
** - the visible interval (window), likewise modelled as time::TimeSpan
** - the scale defined as pixels per second
**
** # Interactions
**
** - *bla*: connect to blubb
** see [sigc-track] for an explanation.
** The basic parameters can be changed and adjusted through various setters, dedicated
** to specific usage scenarios. After invoking any setter, one of the mutating functions
** is invoked to adjust the base parameters and then re-establish the *invariant*
** - visible window lies completely within the overall range
** - scale factor and visible window line up logically
** - scale factor produces precise reproducible values
**
** [MVC-Pattern]: http://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller
** [sigc-track]: http://issues.lumiera.org/ticket/940#comment:3 "Ticket #940"
** ## Change listener
**
** A single change listener lambda can be installed (as of 10/2022 this is considered sufficient,
** since only the TimelineLayout was identified as collaborator requiring push notification).
** This callback will be invoked after any effective change and serves as notification; the
** receiver is expected to read the current settings by invoking the getters.
**
** @todo WIP-WIP as of 10/2022 the usage is just emerging, while the actual meaning
** of this interface still remains somewhat nebulous...
@ -53,7 +77,7 @@
#include "lib/error.hpp"
#include "lib/time/timevalue.hpp"
//#include "lib/nocopy.hpp"
#include "lib/nocopy.hpp"
//#include "lib/idi/entry-id.hpp"
//#include "lib/symbol.hpp"
@ -68,28 +92,161 @@ namespace model {
// using lib::Symbol;
using lib::time::TimeValue;
using lib::time::TimeSpan;
using lib::time::Duration;
using lib::time::TimeVar;
using lib::time::Offset;
using lib::time::FSecs;
using lib::time::Time;
/**
* A component to ensure uniform handling of zoom scale
* and visible interval on the timeline. Changes through
* the mutator functions are validated and harmonised to
* meet the internal invariants; a change listener is
* possibly notified to pick up the new settings.
*/
struct ZoomWindow
class ZoomWindow
: util::NonCopyable
{
TimeSpan overallSpan{Time::ZERO, FSecs{23}}; ////////////////Lalala Lalü
TimeSpan visible = overallSpan;
uint px_per_sec = 25;
TimeVar startAll_, afterAll_,
startWin_, afterWin_;
uint px_per_sec_;
public:
ZoomWindow()
: startAll_{Time::ZERO}
, afterAll_{Time(0,23)}
, startWin_{startAll_}
, afterWin_{afterAll_}
, px_per_sec_{25}
{ }
void reset();
TimeSpan
overallSpan() const
{
return TimeSpan{startAll_, afterAll_};
}
protected:
TimeSpan
visible() const
{
return TimeSpan{startWin_, afterWin_};
}
uint
px_per_sec() const
{
return px_per_sec_;
}
/* === Mutators === */
void
setMetric (uint px_per_sec)
{
UNIMPLEMENTED ("setMetric");
}
void
nudgeMetric (int steps)
{
UNIMPLEMENTED ("nudgeMetric");
}
void
setRanges (TimeSpan overall, TimeSpan visible)
{
UNIMPLEMENTED ("setOverallRange");
}
void
setOverallRange (TimeSpan range)
{
UNIMPLEMENTED ("setOverallRange");
}
void
setOverallStart (TimeValue start)
{
UNIMPLEMENTED ("setOverallStart");
}
void
setOverallDuration (Duration duration)
{
UNIMPLEMENTED ("setOverallDuration");
}
void
setVisibleRange (TimeSpan newWindow)
{
UNIMPLEMENTED ("setVisibleRange");
}
void
expandVisibleRange (TimeSpan target)
{
UNIMPLEMENTED ("zoom out to bring the current window at the designated time span");
}
void
setVisibleDuration (Duration duration)
{
UNIMPLEMENTED ("setVisibleDuration");
}
void
setVisiblePos (TimeValue posToShow)
{
UNIMPLEMENTED ("setVisiblePos");
}
void
setVisiblePos (float percentage)
{
UNIMPLEMENTED ("setVisiblePos");
}
void
offsetVisiblePos (Offset offset)
{
UNIMPLEMENTED ("offsetVisiblePos");
}
void
nudgeVisiblePos (int steps)
{
UNIMPLEMENTED ("nudgeVisiblePos");
}
void
navHistory()
{
UNIMPLEMENTED ("navigate Zoom History");
}
private:
/* === adjust and coordinate === */
void
mutateWindow (TimeValue start, TimeValue after)
{
UNIMPLEMENTED ("change Window TimeSpan, validate and adjust all params");
}
void
mutateScale (uint px_per_sec)
{
UNIMPLEMENTED ("change scale factor, validate and adjust all params");
}
void
mutateDuration (Duration duration)
{
UNIMPLEMENTED ("change visible duration, validate and adjust all params");
}
};

View file

@ -58,10 +58,13 @@ namespace test {
/*************************************************************************************//**
* @test verify technicalities regarding the translation between domain model coordinates
* and screen layout coordinates.
* - bla
* - blubb
* @test verify consistent handling of scrolling and zoom settings for the timeline.
* - setting the overall range
* - setting the visible range
* - adjusting the scale factor
* - setting a visible position
* - nudging the position
* - nudging the scale factor
* @see zoom-window.hpp
*/
class ZoomWindow_test : public Test
@ -70,15 +73,24 @@ namespace test {
virtual void
run (Arg)
{
verify_standardUsage();
verify_simpleUsage();
}
/** @test the standard use case is to.... TBW
*/
void
verify_standardUsage()
verify_simpleUsage()
{
ZoomWindow zoomWin;
CHECK (zoomWin.overallSpan() == TimeSpan(Time::ZERO, Time(FSecs(23))));
CHECK (zoomWin.visible() == TimeSpan(Time::ZERO, Time(FSecs(23))));
CHECK (zoomWin.px_per_sec() == 25);
zoomWin.nudgeMetric(+1);
CHECK (zoomWin.px_per_sec() == 50);
CHECK (zoomWin.visible() == TimeSpan(Time(FSecs(23,4)), Time(FSecs(23,2))));
CHECK (zoomWin.overallSpan() == TimeSpan(Time::ZERO, Time(FSecs(23))));
}

View file

@ -2705,7 +2705,7 @@ Typically, when starting the draw operation, we retrieve our //allocation.// Thi
Thus, if we want to use absolute canvas coordinates for our drawing, we need to adjust the cairo context prior to any drawing operations: we translate it in the opposite direction of the values retrieved from the scrollbar {{{Gtk::Adjustment}}}s. This causes the //user coordinates// to coincede with the absolute canvas coordinates.
</pre>
</div>
<div title="GtkLayoutWidget" creator="Ichthyostega" modifier="Ichthyostega" created="201610141819" modified="201808301543" tags="GuiPattern impl" changecount="6">
<div title="GtkLayoutWidget" creator="Ichthyostega" modifier="Ichthyostega" created="201610141819" modified="202210281459" tags="GuiPattern impl" changecount="8">
<pre>//This is the canvas widget of ~GTK-3//
It allows not only custom drawing, but also to embed other widgets at defined coordinates.
@ -2722,12 +2722,31 @@ we need a test setup for this investigation.
* easy to launch
* don't waste much time, it is disposable
* realistic: shall reflect the situation in our actual UI
As starting point, {{red{in winter 2016/17}}} the old (broken) timeline panel was moved aside and a new panel was attached for GTK experiments. These basically confirmed the envisioned approach; it is possible to place widgets freely onto the canvas; they are drawn in insert order, which allows for overlapped widgets (and mouse events are dispatched properly, as you'd expect). Moreover, it is also possible to draw directly onto the canvas, by overriding the {{{on_draw()}}}-function. However, some (rather trivial) adjustments need to be done to get a virtual canvas, which moves along with the placed widgets. That is, GtkLayoutWidget handles scrolling of embedded widgets automatically, but you need to adjust the Cairo drawing context manually to move along. The aforementioned experimental code shows how.
As starting point, in winter 2016/17 the old (broken) timeline panel was moved aside and a new panel was attached for GTK experiments. These basically confirmed the envisioned approach; it is possible to place widgets freely onto the canvas; they are drawn in insert order, which allows for overlapped widgets (and mouse events are dispatched properly, as you'd expect). Moreover, it is also possible to draw directly onto the canvas, by overriding the {{{on_draw()}}}-function. However, some (rather trivial) adjustments need to be done to get a virtual canvas, which moves along with the placed widgets. That is, GtkLayoutWidget handles scrolling of embedded widgets automatically, but you need to adjust the Cairo drawing context manually to move along. The aforementioned experimental code shows how.
After that initial experiments, my focus shifted to the still unsatisfactory top-level UI structure, and I am working towards an initial integration with Steam-Layer since then.
After that initial experiments, the focus of development shifted to the top-level UI structure, still unsatisfactory at that time, and integration with the message based communication via UI-Bus and the propagation of changes in the form of [[Diff messages|MutationMessage]] has been achieved. Following this ground work, the [[timeline UI|GuiTimelineView]] has been successfully implemented, using [[custom drawing|GuiTimelineDraw]] on a {{{Gtk::Layout}}} based »timeline body canvas« — thereby fully validating the feasibility of this approach.
</pre>
</div>
<div title="GuiClipWidget" creator="Ichthyostega" modifier="Ichthyostega" created="201611180114" modified="202208282100" tags="GuiPattern design draft" changecount="31">
<div title="GuiCanvasInterface" creator="Ichthyostega" modifier="Ichthyostega" created="202210281527" modified="202210282005" tags="spec GuiPattern draft" changecount="3">
<pre>//Interface to handle widgets on a timeline canvas without tight coupling to a central [[Layout Manager|TimelineDisplayManager]].//
Instead of actively installing and handling widgets from a central controlling entity, rather we allow the widgets to //attach// to an existing UI context, which beyond that remains unspecified. Widgets attached this way are required to implement a handling interface. Based on this general scheme, two kinds of attachments are modelled, each through a distinct set of interfaces
;~ViewHook
:Widgets of type {{{ViewHooked}}} are placed into an existing layout framework, like e.g. a grid control;
:those widgets maintain an //order of attachment// (which also translates into an order of appearance in the GUI.
;~CanvasHook
:Widgets of type {{{CanvasHooked}}} are attached with relative coordinates onto a [[Canvas widget|GtkLayoutWidget]] combined with custom drawing
In both cases, the interface on the widget side is modelled as //smart-handle// -- the widget detaches automatically at end-of-life.
Similarly, widgets can be re-attached, retaining the order -- or (in case of ~CanvasHook) -- maintaining or possibly adjusting the relative coordinates of attachment. And, also in both cases, the attachment can be repeated recursively, allowing to build nested UI structures.
This interconnected structure allows //another entity// (not the hook or canvas) to control and rearrange the widgets, and also to some degree it allows the widgets to adjust themselves, in response to local interactions like dragging
!Time calibrated display
The arrangement of media elements onto a timeline display imposes another constraint: the position of elements directly relates to their properties, especially a start and end time. Such a requirement is in conflict with the usual arrangement of UI elements, which largely is governed by considerations of layout and style and UI space necessary for proper presentation. When attaching a widget to the Canvas, the //canvas coordinates// of the attachment point need to be specified -- yet the widget knows its own properties rather in terms of time and duration. A //calibration// thus needs to be maintained, in close relation to global layout properties like the zoom scale or the scrolling position of the data section currently visible.
To bridge this discrepancy, the ~CanvasHook interface exposes a ''~DisplayMetric'' component, with operations to convert time to pixel offsets and vice versa. Internally, this ~DisplayMetric is passed through and actually attached to the ZoomWindow component, which is maintained within the {{{TimelineLayout}}} to coordinate all scrolling and zooming activities
</pre>
</div>
<div title="GuiClipWidget" creator="Ichthyostega" modifier="Ichthyostega" created="201611180114" modified="202210281442" tags="GuiPattern design draft" changecount="32">
<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, a processing function)
@ -2742,7 +2761,7 @@ The next step in a series of progressively more detailed clip representations is
A yet more detailed display of the clip's internals is exposed in the ''expanded form.'' Here, the clip is displayed as a window pane holding nested clip displays, which in turn might again be abridged, compact or ({{red{maybe 11/16}}}) even expanded. This enclosing clip window pane should be rendered semi transparent, just to indicate the enclosing whole. The individual clip displays embedded therein serve to represent individual media parts or channels, or individual attached effects. Due to the recursive nature of Lumiera's HighLevelModel, each of these parts exposes essentially the same controls, allowing to control the respective aspects of the part in question. We may even consider ({{red{unclear as of 11/16}}}) to allow accessing the parts of a VirtualClip, this way turning the enclosing clip into a lightweight container ({{red{11/2016 give proper credit for this concept! Who proposed this initially in 2008? was this Thosten Wilms?}}}
Finally, there can be a situation where it is just not possible to render any of the aforementioned display styles properly, due to size constraints. Especially, this happens when zooming out such as to show a whole sequence or even timeline in overview. We need to come up with a scheme of ''graceful display degradation'' to deal with this situation -- just naively attempting to render any form might easily send our UI thread into a minute long blocking render state, for no good reason. Instead, in such cases display should fall back to a ''degraded form''
Finally, there can be a situation where it is just not possible to render any of the aforementioned display styles properly, due to size constraints. Especially, this happens when [[zooming|ZoomWindow]] out such as to show a whole sequence or even timeline in overview. We need to come up with a scheme of ''graceful display degradation'' to deal with this situation -- just naively attempting to render any form might easily send our UI thread into a minute long blocking render state, for no good reason. Instead, in such cases display should fall back to a ''degraded form''
* showing just a placeholder rectangle, when the clip (or any other media element) will cover a temporal span relating to at least 1 pixel width (configurable trigger condition)
* even further collapsing several entities into a strike of elements,to indicate at least that some content is present in this part of the timeline.
Together this shows we have to decide on a ''display strategy'' //before we even consider to add// a specific widget to the enclosing GTK container....
@ -3084,7 +3103,7 @@ Yet in the generic case, content is shown within a Box with well defined extensi
These disparate usage patterns impose the challenge to avoid architectural tangling: The [[Element Box Witget|GuiElementBoxWidget]] must be confined to be //merely a container,// and remain agnostic with respect to the inner structure of the content; it is instructed only insofar a specific //content type// is indicated, as foundation for picking a suitable //rendering strategy.//
</pre>
</div>
<div title="GuiCustomWidget" creator="Ichthyostega" modifier="Ichthyostega" created="201410250002" modified="201611182349" tags="GuiPattern discuss decision draft" changecount="64">
<div title="GuiCustomWidget" creator="Ichthyostega" modifier="Ichthyostega" created="201410250002" modified="202210281453" tags="GuiPattern discuss decision draft" changecount="65">
<pre>Inevitably, the UI of an advanced application like Lumiera needs some parts beyond the scope of what can be achieved by combining standard widgets. Typically, an UI toolkit (GTK is no exception here) offers some extension mechanism to integrate such more elaborate, application specific behaviour. Within Lumiera, the Timeline is probably the most prominent place where we need to come up with our own handling solution -- which also means to rely on such extension mechanisms and to integrate well with the more conventional parts of the UI. Since the core concept of typical UI toolkit sets is that of a //widget,// we end up with writing some kind of customised or completely custom defined widget.
!two fundamental models
@ -3115,15 +3134,15 @@ All of the above is a serious concern. There is no easy way out, since, for the
* in the past, this functionality was pioneered by several extension libraries, for example by the [[GooCanvas|https://developer.gnome.org/goocanvas/stable/GooCanvas.html]] library for ~GTK-2
* meanwhile, ~GTK-3 features several special layout managers, one of which is the [[GtkLayout|https://developer.gnome.org/gtk3/stable/GtkLayout.html]] widget, which incorporates this concept of //widgets placed on a canvas.//
!Investigation: ~GtkLayout {{red{WIP 10/2016}}}
In order to build a sensible plan for our timeline structure, we need to investigate and clarify some fundamental properties of the GtkLayoutWidget
!Investigation: ~GtkLayout
In order to build a sensible plan for our timeline structure, an investigation has been carried out in 2016 to clarify some fundamental properties of the GtkLayoutWidget
* how are overlapping widgets handled?
* how is resizing of widgets handled, esp. when they need to grow due to content changes?
* how does the event dispatching deal with partially covered widgets?
* how can embedded widgets be integrated into a tabbing / focus order?
* how is custom drawing and widget drawing interwoven?
!!!Plan of investigation
!!!Topics for investigation
# place some simple widgets (Buttons)
# learn how to draw
# place a huge number of widgets, to scrutinise scrolling and performance
@ -3682,7 +3701,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="202210012303" tags="GuiPattern design decision draft img" changecount="64">
<div title="GuiTimelineView" creator="Ichthyostega" modifier="Ichthyostega" created="201410160100" modified="202210281524" tags="GuiPattern design decision draft img" changecount="66">
<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
@ -3697,7 +3716,7 @@ Session, Binding and Sequence are the mandatory ingredients.
!Basic layout
[&gt;img[Clip presentation control|draw/UI-TimelineLayout-1.png]]The representation is split into a ''Header pane'' exposing structure and configuration, and a ''Content pane'' extending in time. The ''Time ruler'' ( &amp;rarr; [[Rulers|TrackRuler]]) running alongside the top of the content pane represents the //position in time.// Beyond this temporal dimension, the content area is conceived as a flexible working space. This working space //can// be structured hierarchically -- when interacting with the GUI, hierarchical nesting will be created and collapsed on demand. Contrast this with conventional editing applications which are built upon the rigid notion of &quot;Tracks&quot;: Lumiera is based on //Pipes// and //Scopes// rather than Tracks.
In the temporal dimension, there is the usual scrolling and zooming of content, and possibly a selected time range, and after establishing a ViewerPlayConnection, there is an effective playback location featured as a &quot;Playhead&quot;
In the temporal dimension, there is the usual [[scrolling and zooming|ZoomWindow]] of content, and possibly a selected time range, and after establishing a ViewerPlayConnection, there is an effective playback location featured as a &quot;Playhead&quot;
The workspace dimension (vertical layout) is more like a ''Fork'', which can be expanded recursively. More specifically, each strip or layer or &quot;track&quot; can be featured in //collapsed// or //expanded state.//
* the collapsed state features a condensed representation (&quot;the tip of the iceberg&quot;). It exposes just the topmost entity, and might show a rendered (pre)view. Elements might be stacked on top, but any element visible here //is still accessible.//
* when expanding, the content unfolds into...
@ -3721,10 +3740,12 @@ The dockable ''timeline pannel'' holds onto the existing {{{TimelineWidget}}} in
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...
!!!The Canvas
At least the track body area of the timeline display needs to be implemented in parts by //custom drawing// onto a [[»Canvas widget«|GtkLayoutWidget]] -- meaning that we have both to coordinate explicit graphic operations using the »Cairo« library, and child widgets attached at explicit positions on top of this canvas. These nested widgets in turn need to know about the calibration of the canvas in terms of time and pixels, to be able to adjust their display to fit into the established metric. As the first implementation attempt for the timeline highlighted, at this point there is a lurking danger to slide into the usage of one central „God class“ -- in this case a //Layout Manager// -- which has to actuate and coordinate any related interplay by „pulling the strings“ like a puppeteer. To forego that threat, we introduce yet another abstraction: a [[»Canvas Interface«|GuiCanvasInterface]] to support attachment of widgets and handle the translation of logical (time based) coordinates into pixel values.
!!!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.
Currently {{red{as of 10/2018}}} there is a preference to deal with that problem on session level -- but for now we decide to postpone this topic &amp;rarr; [[#1083|https://issues.lumiera.org/ticket/1083]]
@ -3739,7 +3760,7 @@ In the most general case, there can be per-track content and nested content at t
</pre>
</div>
<div title="GuiTimelineWidgetStructure" creator="Ichthyostega" modifier="Ichthyostega" created="201410250002" modified="201904052200" tags="GuiPattern discuss decision impl" changecount="106">
<div title="GuiTimelineWidgetStructure" creator="Ichthyostega" modifier="Ichthyostega" created="201410250002" modified="202210281525" tags="GuiPattern discuss decision impl" changecount="110">
<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 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.
@ -3765,7 +3786,10 @@ While the special setup for scrolling doesn't really count (since it is necessar
!!!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 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 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.
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 [[»Timeline Display Manager«|TimelineDisplayManager]] -- which is conceived as an interface and has to be implemented within the recursively structured parts of the timeline display. This layout control interface will be used by the top-level structure to exert control over the layout as a whole. Any change will work by...
# setting global parameters, like e.g. the ZoomWindow
# triggering a »''display evaluation pass''«
# have the individual widgets in turn //pull// the local relevant metric parameters from some abstracted [[»Canvas«|GuiCanvasInterface]] interface.
!dealing with nested structures
@ -9248,15 +9272,20 @@ 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="202003062036" tags="spec GuiPattern img" changecount="22">
<div title="TimelineDisplayManager" creator="Ichthyostega" modifier="Ichthyostega" created="201611280235" modified="202210281503" tags="spec GuiPattern img" changecount="24">
<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 themselves 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 display widgets would show up, and they should be aware if these corresponding widgets need actually to be visible at the moment. Moreover, especially the ClipPresenter has to choose the appropriate &amp;rarr;[[»appearance style«|GuiClipWidget]] for the corresponding slave widget.
The »Display Manager« of the timeline actually is an abstraction, a control interface, revolving around the guidance individual components need in order to settle on a proper display. Those components themselves 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 display widgets would show up, and they should be aware if these corresponding widgets need actually to be visible at the moment. Moreover, especially the ClipPresenter has to choose the appropriate &amp;rarr;[[»appearance style«|GuiClipWidget]] 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 into their innards in order to extract the necessary data -- or even worse, build a separate display data store, which then must be //kept in sync// with the component hierarchy proper. Any of these naive coding approaches would lead to high coupling and turns later adjustments and necessary changes into a liability, due to evolution of requirements. On the long run, such a dangerous situation can only be mitigated by a collaboration of self contained local structures. And, to get there, we need to distil the common ground shared between the global and the local concerns, and reshape it into some kind of invariant -- allowing a common pattern to re-emerge at several levels of locality.
The purpose of the display evaluation pass is to make the timeline display globally coherent. Parts of our display are implemented as GTK compound widgets -- and GTK ensures a proper layout within these parts. However, our layout creates interconnections and dependencies beyond what can be expressed with GTK building blocks. Most notably, the track space must be aligned horizontally, both in header and track body display, while each individual track has to accommodate to its various content's extension. And since clips might in turn contain nested structures, we can not hope to achieve a finished layout just in one pass, since some of our local adjustments in turn need to be re-evaluated by the GTK space allocation mechanism. So we end up with several sweeping passes over our timeline control structure, each collecting data and //gradually increasing the allocated space.// This kind of //monotonous adjustment// is key for avoiding oscillations and to guarantee a quick convergence of this iterative space allocation process. Each pass visits the timeline UI components in //strict layout order// (from top down and from left to right), causing a strike of size adjustments to embedded widgets, which, after being accommodated by GTK into appropriate local layout, will in turn trigger our custom drawing code eventually -- at which point the next display evaluation pass is hooked in, continuing this cycle until convergence is achieved.
!!!Trigger
The //Display Evaluation Pass// is triggered by various events from different sources
* after any //structural change// by MutationMessage
* when any Widget within the [[nested timeline display|GuiTimelineView]] detects the necessity of resizing
* after notification from the ZoomWindow control to indicate a change for any of the //display metric parameters//
!mapping service for the widgets
Another, closely related topic, handled within this context, is the mapping from model structures and units into display layout coordinates. Within the model, we can distinguish several dimensions (degrees of freedom). For one, there is the topology, the ''location or part'' of the model to expose through the UI. Moreover, there is the ''degree of detail'' for this UI representation. And then, there is the ''temporal position and extension'' to represent.
@ -10476,6 +10505,108 @@ Wiring requests are small stateful value objects. They will be collected, sorted
&amp;rarr; ConManager
</pre>
</div>
<div title="ZoomWindow" creator="Ichthyostega" modifier="Ichthyostega" created="202210281528" modified="202210282204" tags="spec GuiPattern draft" changecount="16">
<pre>//A component for uniform handling of zoom scale and visible interval on the timeline.//
Working with and arranging media requires a lot of //navigation// and changes of //zoom detail level.// More specifically, the editor is required to repeatedly return //to the same locations// and show arrangements at the same alternating scale levels. Most existing editing applications approach this topic naïvely, by just responding to some coarse grained interaction controls -- thereby creating the need for a lot of superfluous and tedious search and navigation activities, causing constant grind for the user. And resolving these obnoxious shortcomings turns out as a never ending task, precisely due to the naïve and ad hoc approach initially taken.
Based on these observations, the design of the Lumiera UI calls for centralisation of all zoom- and navigation handling into a single component, instantiated once for every visible context, outfitted with the ability to capture and maintain a //history of zoom and navigation activities.// This component is called »''Zoom Window''«, since it represents a window-like local visible interval, embedded into a larger time span covering the whole timeline. The //current zoom state// is thus defined by
* the overall {{{TimeSpan}}} of the timeline, including a start time (inclusive) and an end time (exclusive)
* the //visible interval// („window“), likewise modelled as {{{time::TimeSpan}}}
* the //scale// defined as pixels per second {{red{10/2022 -- imposing a hard limit at 1px / sec ≙ 20min per screen page}}}
!Requirement Analysis
The Timeline UI is {{red{as of 10/2022}}} specified sufficiently to serve as framework to determine the requirements of a zoom handling component
!!!User
;~CanvasHook + ~DisplayMetric
:info | pull
:*Pixel / sec
:*Time ⟼ Pixel
:*Pixel ⟼ ~Time-Offset
;~BodyCanvas
:info | pull
:*window offset
:*Time ⟼ Pixel
:*overall range
;~TimelineLayout
:info | push
:*change to scale factor ⟶ zoom slider
:*any change ⟹ trigger ~DisplayEvaluation
:**visible window
:**scale factor
:**overall range
:mutate
:*in response to scrollbar
:**nudge
:**move window
:*in response to zoom slider
:**change scale
;~TimelineController
:mutate
:*overall range
:*window parameters (persistent UI state)
;Menu + Actions + ~SpotLocator
:mutate
:*zoom factor explicit
:*zoom in / out
:*Zoom history
:*goto
;Gestures~~(Controller)~~
:mutate
:*move window
:*set window new
:*nudge / navigate scale
!!!~Use-Cases
;initial setup
:preparation
:*~UI-state ➩ set metric
:initial content population received...
:#~TimelineController ➩ set overall range
:#~DisplayEvaluation prompts timeline elements to reflow
:#~BodyCanvas ⟵ window params
:#~DisplayMetric ⟵ metric params
;reposition
:UI interaction received...
:*~TimelineLayout ⟵ scrollbar
:*~TimelineLayout ⟵ zoom slider
:*Menu/Locator ⟵ intentional settings
:*Gestures ⟵ move
:*Gestures ⟵ select
:*Gestures ⟵ nudge
:handling sequence...
:#push ⟶ ~TimelineLayout
:#~DisplayEvaluation ⟹ reflow
:#*~BodyCanvas ⟵ window params
:#*~DisplayMetric ⟵ metric params
;model content
:changes received per MutationMessage
:#~TimelineController ➩ set overall range
:#push ⟶ ~TimelineLayout
:#~DisplayEvaluation ⟹ reflow
:#*~BodyCanvas ⟵ window params
:#*~DisplayMetric ⟵ metric params
!!!Interaction Procedure
#mutators
#*set metric
#*nudge metric
#*set overall range
#*set position visible
#*offset position
#*nudge position
#*select/set window
#*nav History
#coordinated adjustment
#*any change ⟹ trigger ~TimelineLayout ⟶ ~DisplayEvaluation
#read state
#*metric
#*translations
#*window offset
#*overall range
!Implementation
The above requirement analysis reveals a common handling scheme, which is best served by a conventional design with an object, mutator and getter methods and encapsulated state. Moreover, a single client for push notification can be identified: the ~TimelineLayout (implementation of the [[DisplayManager interface|TimelineDisplayManager]]; it suffices to notify this collaboration partner; since at implementation level the ZoomWindow is itself embedded by mix-in into the ~TimelineLayout, the latter can easily read the resulting zoom parameters and react accordingly.
</pre>
</div>
<div title="automation" modifier="Ichthyostega" created="200805300057" modified="200805300125" tags="overview">
<pre>The purpose of automation is to vary a parameter of some data processing instance in the course of time while rendering. Thus, automation encompasses all the variability within the render network //which is not a structural change.//

View file

@ -38065,23 +38065,189 @@
</node>
<node CREATED="1541859255448" ID="ID_1251550945" MODIFIED="1557498707234" TEXT="hat seine eigene Historie"/>
</node>
<node CREATED="1541860963705" ID="ID_13414263" MODIFIED="1557498707234" TEXT="Design">
<node CREATED="1541860967281" ID="ID_1676646773" MODIFIED="1557498707234" TEXT="manipulativ - mutable">
<font ITALIC="true" NAME="SansSerif" SIZE="12"/>
</node>
<node CREATED="1541860985526" ID="ID_640984391" MODIFIED="1557498707234" TEXT="Struct mit Methoden"/>
<node CREATED="1541861314456" ID="ID_267107783" MODIFIED="1576282358022" TEXT="generisch">
<richcontent TYPE="NOTE"><html>
<node BACKGROUND_COLOR="#eef0c5" COLOR="#990000" CREATED="1666961652530" ID="ID_1551608483" MODIFIED="1666961657079" TEXT="Anforderungen">
<icon BUILTIN="pencil"/>
<node CREATED="1666961922242" ID="ID_1841861801" MODIFIED="1666961924990" TEXT="Nutzer">
<node CREATED="1666961939961" ID="ID_1450461751" MODIFIED="1666964219794">
<richcontent TYPE="NODE"><html>
<head>
</head>
<body>
<p>
nix GTK
CanvasHook /
</p>
<p>
DisplayMetric
</p>
</body>
</html></richcontent>
<node CREATED="1666964107682" ID="ID_987173896" MODIFIED="1666964109614" TEXT="info">
<node CREATED="1666964110858" ID="ID_1532390725" MODIFIED="1666964112213" TEXT="pull">
<node CREATED="1666962009910" ID="ID_529672706" MODIFIED="1666962010867" TEXT="Pixel / sec"/>
<node CREATED="1666962011710" ID="ID_414340764" MODIFIED="1666963008416" TEXT="Time &#x27fc; Pixel">
<linktarget COLOR="#a9b4c1" DESTINATION="ID_414340764" ENDARROW="Default" ENDINCLINATION="69;0;" ID="Arrow_ID_1434161517" SOURCE="ID_1881628552" STARTARROW="None" STARTINCLINATION="69;0;"/>
</node>
<node CREATED="1666962045507" ID="ID_1981297904" MODIFIED="1666964118381" TEXT="Pixel &#x27fc; Time-Offset"/>
</node>
</node>
</node>
<node CREATED="1666962948140" ID="ID_1637428541" MODIFIED="1666962951762" TEXT="BodyCanvas">
<node CREATED="1666962967612" ID="ID_449651932" MODIFIED="1666962970528" TEXT="info">
<node CREATED="1666962972110" ID="ID_394919753" MODIFIED="1666963848689" TEXT="pull">
<linktarget COLOR="#845fa8" DESTINATION="ID_394919753" ENDARROW="Default" ENDINCLINATION="-2;42;" ID="Arrow_ID_803019531" SOURCE="ID_647016441" STARTARROW="None" STARTINCLINATION="-87;9;"/>
<node CREATED="1666962983298" ID="ID_1421218984" MODIFIED="1666962989245" TEXT="window offset"/>
<node CREATED="1666962998128" ID="ID_1881628552" MODIFIED="1666963008416" TEXT="Time &#x27fc; Pixel">
<arrowlink COLOR="#a9b4c1" DESTINATION="ID_414340764" ENDARROW="Default" ENDINCLINATION="69;0;" ID="Arrow_ID_1434161517" STARTARROW="None" STARTINCLINATION="69;0;"/>
</node>
<node CREATED="1666963029028" ID="ID_1761637588" MODIFIED="1666963032537" TEXT="overall range"/>
</node>
</node>
</node>
<node CREATED="1666963161996" ID="ID_1959770633" MODIFIED="1666963165721" TEXT="TimelineLayout">
<node CREATED="1666963168179" ID="ID_1043980998" MODIFIED="1666963170055" TEXT="info">
<node CREATED="1666963053313" ID="ID_1467716107" MODIFIED="1666963055117" TEXT="push">
<node CREATED="1666963473113" ID="ID_1083610832" MODIFIED="1666963797516" TEXT="change to scale factor &#x27f6; zoom slider"/>
<node CREATED="1666963056273" ID="ID_93260719" MODIFIED="1666963073411" TEXT="any change to">
<node CREATED="1666963074711" ID="ID_509368190" MODIFIED="1666963076010" TEXT="visible window"/>
<node CREATED="1666963076796" ID="ID_246937524" MODIFIED="1666963108407" TEXT="scale factor"/>
<node CREATED="1666963936464" ID="ID_1932975512" MODIFIED="1666963939268" TEXT="overall range"/>
</node>
<node CREATED="1666963799466" ID="ID_647016441" MODIFIED="1666963854969" TEXT="&#x27f9; trigger DisplayEvaluation">
<arrowlink COLOR="#845fa8" DESTINATION="ID_394919753" ENDARROW="Default" ENDINCLINATION="-2;42;" ID="Arrow_ID_803019531" STARTARROW="None" STARTINCLINATION="-87;9;"/>
</node>
</node>
</node>
<node CREATED="1666963111674" ID="ID_366938946" MODIFIED="1666963138899" TEXT="mutate">
<node CREATED="1666963244169" ID="ID_1844613988" MODIFIED="1666963246949" TEXT="in response to scrollbar">
<node CREATED="1666963183793" ID="ID_1981655411" MODIFIED="1666963250103" TEXT="nudge"/>
<node CREATED="1666963216853" ID="ID_508890288" MODIFIED="1666963255428" TEXT="move window"/>
</node>
<node CREATED="1666963419931" ID="ID_1583456327" MODIFIED="1666963425054" TEXT="in response to zoom slider">
<node CREATED="1666963439348" ID="ID_1075634262" MODIFIED="1666963471723" TEXT="change scale"/>
</node>
</node>
</node>
<node CREATED="1666963980515" ID="ID_513917153" MODIFIED="1666963984166" TEXT="TimelineController">
<node CREATED="1666964000088" ID="ID_375195151" MODIFIED="1666964003076" TEXT="mutate">
<node CREATED="1666964004207" ID="ID_1180711772" MODIFIED="1666964011435" TEXT="overall range"/>
<node CREATED="1666964012037" ID="ID_1236595109" MODIFIED="1666964022457" TEXT="window parameters (persistent UI state)"/>
</node>
</node>
<node CREATED="1666964041691" ID="ID_190646112" MODIFIED="1666964239733">
<richcontent TYPE="NODE"><html>
<head>
</head>
<body>
<p style="text-align: center">
Menu
</p>
<p style="text-align: center">
Actions
</p>
<p style="text-align: center">
SpotLocator
</p>
</body>
</html></richcontent>
<node CREATED="1666964045890" ID="ID_967504479" MODIFIED="1666964047758" TEXT="mutate">
<node CREATED="1666964049178" ID="ID_400278283" MODIFIED="1666964054021" TEXT="zoom factor explicit"/>
<node CREATED="1666964054817" ID="ID_13822198" MODIFIED="1666964062332" TEXT="zoom in / out"/>
<node CREATED="1666964136828" ID="ID_1945694373" MODIFIED="1666964147513" TEXT="Zoom history"/>
<node CREATED="1666964064097" ID="ID_476690066" MODIFIED="1666964065796" TEXT="goto"/>
</node>
</node>
<node CREATED="1666963512247" ID="ID_346536876" MODIFIED="1666964491444">
<richcontent TYPE="NODE"><html>
<head>
</head>
<body>
<p>
Gestures
</p>
<p>
<font size="2">(Controller)</font>
</p>
</body>
</html></richcontent>
<node CREATED="1666963587166" ID="ID_442423017" MODIFIED="1666963593121" TEXT="mutate">
<node CREATED="1666963695760" ID="ID_81275579" MODIFIED="1666963720097" TEXT="move window"/>
<node CREATED="1666963721304" ID="ID_454787677" MODIFIED="1666963725688" TEXT="set window new"/>
<node CREATED="1666963726700" ID="ID_1014020184" MODIFIED="1666963749165" TEXT="nudge / navigate scale"/>
</node>
</node>
</node>
<node CREATED="1666964525427" ID="ID_1415774511" MODIFIED="1666964532142" TEXT="Use-Cases">
<node CREATED="1666964535050" ID="ID_1763230201" MODIFIED="1666964538702" TEXT="initial setup">
<node CREATED="1666964596634" ID="ID_787404148" MODIFIED="1666964610730" TEXT="UI-state &#x27a9; set metric"/>
<node CREATED="1666964550288" ID="ID_923399586" MODIFIED="1666964581818" TEXT="TimelineController &#x27a9; set overall range"/>
<node CREATED="1666964631061" ID="ID_1738511451" MODIFIED="1666964652646" TEXT="BodyCanvas &#x27f5; window params"/>
<node CREATED="1666964657466" ID="ID_284701056" MODIFIED="1666964672146" TEXT="DisplayMetric &#x27f5; metric params"/>
</node>
<node CREATED="1666964685556" ID="ID_1149861099" MODIFIED="1666964696392" TEXT="reposition">
<node CREATED="1666964704346" ID="ID_1335047774" MODIFIED="1666964706053" TEXT="actors">
<node CREATED="1666964713328" ID="ID_547389237" MODIFIED="1666964722173" TEXT="TimelineLayout &#x27f5; scrollbar"/>
<node CREATED="1666964723703" ID="ID_369768444" MODIFIED="1666964730506" TEXT="TimelineLayout &#x27f5; zoom slider"/>
<node CREATED="1666964759337" ID="ID_1195706073" MODIFIED="1666964769244" TEXT="Menu/Locator &#x27f5; intentional settings"/>
<node CREATED="1666964789807" ID="ID_1584775566" MODIFIED="1666964800070" TEXT="Gestures &#x27f5; move"/>
<node CREATED="1666964800731" ID="ID_842054315" MODIFIED="1666964813053" TEXT="Gestures &#x27f5; select"/>
<node CREATED="1666964813737" ID="ID_251122612" MODIFIED="1666964819044" TEXT="Gestures &#x27f5; nudge"/>
</node>
<node CREATED="1666964837550" ID="ID_1317851498" MODIFIED="1666964853954" TEXT="push &#x27f6; TimelineLayout"/>
<node CREATED="1666964631061" ID="ID_471374188" MODIFIED="1666964652646" TEXT="BodyCanvas &#x27f5; window params"/>
<node CREATED="1666964657466" ID="ID_640554342" MODIFIED="1666964672146" TEXT="DisplayMetric &#x27f5; metric params"/>
</node>
<node CREATED="1666964906595" ID="ID_414880469" MODIFIED="1666964913822" TEXT="model/Content">
<node CREATED="1666964550288" ID="ID_207665547" MODIFIED="1666964581818" TEXT="TimelineController &#x27a9; set overall range"/>
<node CREATED="1666964837550" ID="ID_510736258" MODIFIED="1666964853954" TEXT="push &#x27f6; TimelineLayout"/>
<node CREATED="1666964631061" ID="ID_1856222450" MODIFIED="1666964652646" TEXT="BodyCanvas &#x27f5; window params"/>
<node CREATED="1666964657466" ID="ID_1703226781" MODIFIED="1666964672146" TEXT="DisplayMetric &#x27f5; metric params"/>
</node>
</node>
<node CREATED="1666965004641" ID="ID_748757781" MODIFIED="1666965813264" TEXT="Interaction Procedure">
<node CREATED="1666965028506" ID="ID_1718237033" MODIFIED="1666965700555" TEXT="mutators">
<icon BUILTIN="full-1"/>
<node CREATED="1666965070532" ID="ID_466715389" MODIFIED="1666965073074" TEXT="set metric"/>
<node CREATED="1666965122916" ID="ID_1552912945" MODIFIED="1666965127135" TEXT="nudge metric"/>
<node CREATED="1666965076899" ID="ID_1566749815" MODIFIED="1666965082213" TEXT="set overall range"/>
<node CREATED="1666965248114" ID="ID_1129946807" MODIFIED="1666965252637" TEXT="set position visible"/>
<node CREATED="1666965253777" ID="ID_170374754" MODIFIED="1666965260590" TEXT="offset position"/>
<node CREATED="1666965363498" ID="ID_122767081" MODIFIED="1666965371954" TEXT="nudge position"/>
<node CREATED="1666965443495" ID="ID_963746010" MODIFIED="1666965450746" TEXT="select/set window"/>
<node CREATED="1666965624006" ID="ID_1012108139" MODIFIED="1666965628305" TEXT="nav History"/>
</node>
<node CREATED="1666965815796" ID="ID_923514380" MODIFIED="1666965835315" TEXT="coordinated adjustment">
<icon BUILTIN="full-2"/>
</node>
<node CREATED="1666965640435" ID="ID_1286173115" MODIFIED="1666965838899" TEXT="any change &#x27f9; trigger TimelineLayout &#x27f6; DisplayEvaluation">
<icon BUILTIN="full-3"/>
</node>
<node CREATED="1666965707418" ID="ID_934685858" MODIFIED="1666965842700" TEXT="read state">
<icon BUILTIN="full-4"/>
<node CREATED="1666965729794" ID="ID_745785368" MODIFIED="1666965732035" TEXT="metric"/>
<node CREATED="1666965732767" ID="ID_193127162" MODIFIED="1666965737818" TEXT="translations"/>
<node CREATED="1666965745869" ID="ID_1594628227" MODIFIED="1666965748929" TEXT="window offset"/>
<node CREATED="1666965749637" ID="ID_1024412644" MODIFIED="1666965752128" TEXT="overall range"/>
</node>
</node>
</node>
<node CREATED="1541860963705" ID="ID_13414263" MODIFIED="1557498707234" TEXT="Design">
<node CREATED="1541860967281" ID="ID_1676646773" MODIFIED="1557498707234" TEXT="manipulativ - mutable">
<font ITALIC="true" NAME="SansSerif" SIZE="12"/>
</node>
<node CREATED="1541860985526" ID="ID_640984391" MODIFIED="1666965921616" TEXT="Klasse mit...">
<node CREATED="1666965922557" ID="ID_1117003417" MODIFIED="1666965925216" TEXT="mutatoren"/>
<node CREATED="1666965925828" ID="ID_1319511593" MODIFIED="1666965930200" TEXT="internem State"/>
<node CREATED="1666965930804" ID="ID_821140216" MODIFIED="1666965935583" TEXT="Listener-Registrierung"/>
</node>
<node CREATED="1541861314456" ID="ID_267107783" MODIFIED="1666965892587" TEXT="generisch">
<richcontent TYPE="NOTE"><html>
<head>
</head>
<body>
<p>
Liegt in unserem gui::model
</p>
@ -38090,18 +38256,13 @@
</p>
</body>
</html></richcontent>
</node>
<node CREATED="1541861007275" ID="ID_411365171" MODIFIED="1666912813880" TEXT="zuweisbar">
<node BACKGROUND_COLOR="#f0d5c5" COLOR="#990033" CREATED="1666912813880" ID="ID_827840038" MODIFIED="1666912945523" TEXT="wirklich??">
<icon BUILTIN="help"/>
</node>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1666913052762" ID="ID_967245137" MODIFIED="1666913090414" TEXT="sieht ehr waus ie ein Use Case f&#xfc;r das time::Control">
<icon BUILTIN="idea"/>
</node>
<node CREATED="1666965873443" ID="ID_689086000" MODIFIED="1666965875623" TEXT="nix GTK"/>
<node CREATED="1666965876091" ID="ID_60793784" MODIFIED="1666965880156" TEXT="allgemeiner als Timeline"/>
</node>
<node CREATED="1541861061984" HGAP="23" ID="ID_426757987" MODIFIED="1557498707234" TEXT="Anwendung" VSHIFT="10">
<node CREATED="1541861069577" ID="ID_1233713010" MODIFIED="1557498707234" TEXT="wird stets atomar angewendet"/>
<node CREATED="1541861281854" ID="ID_1332627357" MODIFIED="1557498707234" TEXT="wird vom LayoutManager auf Widgets &#xfc;bersetzt"/>
<node CREATED="1666965986884" ID="ID_1348737219" MODIFIED="1666966001747" TEXT="dedizierte zweickgebundene Setter"/>
<node CREATED="1541861069577" ID="ID_1233713010" MODIFIED="1666965974618" TEXT="wird stets insgesamt harmonisiert"/>
<node CREATED="1541861281854" ID="ID_1332627357" MODIFIED="1666966014212" TEXT="Notification vom LayoutManager auf Widgets &#xfc;bersetzt"/>
</node>
</node>
<node CREATED="1666741319202" ID="ID_64525813" MODIFIED="1666741321886" TEXT="Einbindung">
@ -38125,20 +38286,17 @@
</node>
</node>
</node>
<node BACKGROUND_COLOR="#f0d5c5" COLOR="#990033" CREATED="1666912636672" ID="ID_693136797" MODIFIED="1666912653302" TEXT="wie kann man ZoomWindow nutzen?">
<node COLOR="#435e98" CREATED="1666912636672" ID="ID_693136797" MODIFIED="1666966124725" TEXT="wie kann man ZoomWindow nutzen?">
<icon BUILTIN="help"/>
<node CREATED="1666912660076" ID="ID_1930000724" MODIFIED="1666912681102" TEXT="dynamische &#xc4;nderungen vornehmen"/>
<node CREATED="1666912682282" ID="ID_1683687274" MODIFIED="1666912699867" TEXT="&#xc4;nderungen beobachten (Listener)"/>
<node CREATED="1666912682282" ID="ID_1683687274" MODIFIED="1666966070412" TEXT="&#xc4;nderungs-Benachrichtigung (Listener)"/>
</node>
<node BACKGROUND_COLOR="#f0d5c5" COLOR="#990033" CREATED="1666913019821" ID="ID_1177711704" MODIFIED="1666913028092" TEXT="Art der Anbindung?">
<node COLOR="#435e98" CREATED="1666913019821" ID="ID_1177711704" MODIFIED="1666966123770" TEXT="Art der Anbindung?">
<icon BUILTIN="help"/>
<node CREATED="1666913114049" ID="ID_603622552" MODIFIED="1666913117356" TEXT="M&#xf6;glichkeiten">
<node CREATED="1666913118791" ID="ID_228116720" MODIFIED="1666913124339" TEXT="per Referenz erreichbar"/>
<node CREATED="1666913124995" ID="ID_1928528678" MODIFIED="1666913131873" TEXT="man kann Wert manipulieren"/>
<node CREATED="1666913138933" ID="ID_784393870" MODIFIED="1666913145814" TEXT="oder eine API-Funktion aufrufen"/>
<node CREATED="1666913150755" ID="ID_836631020" MODIFIED="1666913156806" TEXT="man mu&#xdf; den Wert beobachten"/>
<node CREATED="1666913157418" ID="ID_1129048676" MODIFIED="1666913168293" TEXT="oder kann sich als LIstener registrieren"/>
</node>
<node CREATED="1666913138933" ID="ID_784393870" MODIFIED="1666966103824" TEXT="man kann Kenn-Parameter lesen"/>
<node CREATED="1666966104484" ID="ID_1051593434" MODIFIED="1666966112377" TEXT="man kann Listener registrieren"/>
</node>
</node>
<node BACKGROUND_COLOR="#d2beaf" COLOR="#5c4d6e" CREATED="1666913178575" ID="ID_391252153" MODIFIED="1666913185255" TEXT="Implementierung">
@ -38151,21 +38309,79 @@
</node>
<node BACKGROUND_COLOR="#eef0c5" COLOR="#990000" CREATED="1666913256394" ID="ID_562681542" MODIFIED="1666913269396" TEXT="erster Entwurf 10/2022">
<icon BUILTIN="pencil"/>
<node CREATED="1666913293560" ID="ID_1249096607" MODIFIED="1666913395549" TEXT="wie geschaffen f&#xfc;r das time::Control">
<node COLOR="#690f14" CREATED="1666913293560" ID="ID_1249096607" MODIFIED="1666966326093" TEXT="das time::Control verwenden">
<icon BUILTIN="idea"/>
<icon BUILTIN="button_cancel"/>
<node CREATED="1666913316709" ID="ID_134581234" MODIFIED="1666913343061" TEXT="Kenn-Parameter w&#xe4;ren damit zwei TimeSpan"/>
<node CREATED="1666913354384" ID="ID_1823681184" MODIFIED="1666913364242" TEXT="die API-Methoden bauen direkt darauf auf"/>
<node CREATED="1666966147486" ID="ID_45964930" MODIFIED="1666966157681" TEXT="w&#xfc;rde sich selbst als Listener registrieren"/>
<node CREATED="1666966161556" ID="ID_327156044" MODIFIED="1666966331839" TEXT="Probleme">
<icon BUILTIN="stop-sign"/>
<node CREATED="1666966164548" ID="ID_1667782386" MODIFIED="1666966185522" TEXT="nicht alle Parameter sind Zeiten">
<richcontent TYPE="NOTE"><html>
<head>
</head>
<body>
<p>
aber time::Control ist nur auf Zeiten ausgelegt
</p>
</body>
</html></richcontent>
</node>
<node BACKGROUND_COLOR="#fdfdcf" COLOR="#ff0000" CREATED="1666913370781" ID="ID_1691713188" MODIFIED="1666913500942" TEXT="noch unklar">
<node CREATED="1666966186762" ID="ID_1824326405" MODIFIED="1666966230396" TEXT="Nutzer wollen oft speziellere Dinge tun">
<richcontent TYPE="NOTE"><html>
<head>
</head>
<body>
<p>
m&#252;&#223;ten also dann jeweils selber dies in die Kenn-Parameter &#252;bersetzen &#10233; Gefahr von Code-Duplikation und inkonsistentem Verhalten
</p>
</body>
</html></richcontent>
</node>
<node CREATED="1666966240650" ID="ID_248937535" MODIFIED="1666966303328" TEXT="es g&#xe4;be mehrfach-Benachrichtigung">
<richcontent TYPE="NOTE"><html>
<head>
</head>
<body>
<p>
denn aufgrund der ersten (ggfs sogar <b>falschen</b>&#160;Benachrichtigung) w&#252;rde das ZoomWindow selber die Parameter glattziehen, was erneute Benachrichtigung zur Folge h&#228;tte
</p>
</body>
</html></richcontent>
</node>
<node CREATED="1666966304273" ID="ID_1096389407" MODIFIED="1666966312732" TEXT="API-Nutzung insgesamt nicht selbsterkl&#xe4;rend"/>
</node>
</node>
<node BACKGROUND_COLOR="#fdfdcf" COLOR="#ff0000" CREATED="1666913370781" ID="ID_1691713188" MODIFIED="1666966354349" TEXT="klassisches Klassen-Design">
<icon BUILTIN="yes"/>
<node BACKGROUND_COLOR="#f0d5c5" COLOR="#990033" CREATED="1666913375117" ID="ID_1377108042" MODIFIED="1666913389672" TEXT="wo ist das time::Control selber angesiedelt?">
<icon BUILTIN="help"/>
<node CREATED="1666913375117" ID="ID_1377108042" MODIFIED="1666966405674" TEXT="ZoomWindow ist kopierbare Klasse mit internem State"/>
<node CREATED="1666913471725" ID="ID_1373767277" MODIFIED="1666966473259" TEXT="Master-Setter f&#xfc;hren die Range-Chacks durch"/>
<node CREATED="1666966477745" ID="ID_1255941758" MODIFIED="1666966489636" TEXT="sekund&#xe4;re / convenience-Setter bieten"/>
<node CREATED="1666966493207" ID="ID_872298278" MODIFIED="1666966503634" TEXT="interner State in 4 TimeVar"/>
<node CREATED="1666966543009" ID="ID_1212064261" MODIFIED="1666966550739" TEXT="vorerst nur einen Listener vorsehen">
<node CREATED="1666966571021" ID="ID_166595354" MODIFIED="1666966588771" TEXT="derzeit mu&#xdf; nur TimelineLayout benachrichtigt werden"/>
<node CREATED="1666966555495" ID="ID_1498916070" MODIFIED="1666966567113" TEXT="Overflow wenn sp&#xe4;ter mehr dazukommen"/>
</node>
<node BACKGROUND_COLOR="#f0d5c5" COLOR="#990033" CREATED="1666913471725" ID="ID_1373767277" MODIFIED="1666913483351" TEXT="wie k&#xf6;nnen die range-checks eingebunden werden?">
<icon BUILTIN="help"/>
<node CREATED="1666913506667" ID="ID_1024648562" MODIFIED="1666913516575" TEXT="selber als Listener am time::Control?"/>
<node CREATED="1666966629661" ID="ID_341880226" MODIFIED="1666966661849">
<richcontent TYPE="NODE"><html>
<head>
</head>
<body>
<p>
getter liefern Time-<b>Values</b>
</p>
</body>
</html></richcontent>
</node>
</node>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1666966669487" ID="ID_1477573565" MODIFIED="1666967820569" TEXT="testgetrieben entwickelt">
<icon BUILTIN="flag-yellow"/>
</node>
</node>
<node BACKGROUND_COLOR="#d2beaf" COLOR="#5c4d6e" CREATED="1666913274942" ID="ID_4743528" MODIFIED="1666913286194" TEXT="Design &#xfc;berpr&#xfc;fen">
<icon BUILTIN="hourglass"/>