From 51a7670425862365c8128c179a7eea612c564a29 Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Fri, 31 Aug 2018 21:38:33 +0200 Subject: [PATCH] UiElement: integrate a default implementation based on the Expander functor --- src/gui/model/controller.hpp | 6 ------ src/gui/model/expander-revealer.hpp | 4 ++++ src/gui/model/tangible.cpp | 19 +++++++++++++++++++ src/gui/model/tangible.hpp | 25 ++++++++++++++++++++++++- src/gui/model/widget.hpp | 6 ------ wiki/renderengine.html | 16 ++++++++++------ wiki/thinkPad.ichthyo.mm | 27 +++++++++++++++++++++------ 7 files changed, 78 insertions(+), 25 deletions(-) diff --git a/src/gui/model/controller.hpp b/src/gui/model/controller.hpp index 7d4d5a085..f44df49d0 100644 --- a/src/gui/model/controller.hpp +++ b/src/gui/model/controller.hpp @@ -70,12 +70,6 @@ namespace model { UNIMPLEMENTED ("Controller reset"); } - virtual bool - doExpand (bool yes) override - { - UNIMPLEMENTED ("Controller doExpand"); - } - virtual void doReveal (ID child) override { diff --git a/src/gui/model/expander-revealer.hpp b/src/gui/model/expander-revealer.hpp index c2d7b435e..662918b2d 100644 --- a/src/gui/model/expander-revealer.hpp +++ b/src/gui/model/expander-revealer.hpp @@ -83,9 +83,11 @@ namespace model { */ class Expander { + public: using ProbeFun = std::function; using ChangeFun = std::function; + private: ProbeFun probeState_; ChangeFun changeState_; @@ -151,8 +153,10 @@ namespace model { */ class Revealer { + public: using RevealeItFun = std::function; + private: RevealeItFun revealIt_; public: diff --git a/src/gui/model/tangible.cpp b/src/gui/model/tangible.cpp index 1c5f4de5e..f435174b0 100644 --- a/src/gui/model/tangible.cpp +++ b/src/gui/model/tangible.cpp @@ -148,6 +148,9 @@ namespace model { * behaviour. If this virtual method returns `true`, the * state change is deemed relevant and persistent, and * thus a "state mark" is sent on the UI-Bus. + * @remark a default implementation of ::doExpand() is provided, + * based on installing an \ref Expander functor through + * the [configuration function](\ref #installExpander). */ void Tangible::slotExpand() @@ -169,6 +172,22 @@ namespace model { } + /** + * generic default implementation of the expand/collapse functionality. + * Based on the #expand_ functor, which needs to be [configured](\ref installExpander()) + * explicitly to enable this functionality. + * @return `true` if the actual expansion state has been changed. + */ + bool + Tangible::doExpand (bool yes) + { + if (not expand_.canExpand()) + return false; + bool oldState = expand_(yes); + return oldState != yes; // actually changed + } + + /** * @todo 12/2015 not clear yet what needs to be done * @remarks the intention is to request the given child diff --git a/src/gui/model/tangible.hpp b/src/gui/model/tangible.hpp index 9ca3c0c99..137e9ea3e 100644 --- a/src/gui/model/tangible.hpp +++ b/src/gui/model/tangible.hpp @@ -176,9 +176,14 @@ namespace model { ctrl::BusTerm uiBus_; + Expander expand_; + Revealer reveal_; + Tangible(ID identity, ctrl::BusTerm& nexus) : uiBus_{nexus.attach(identity, *this)} + , expand_{} + , reveal_{} { } public: @@ -205,11 +210,13 @@ namespace model { void markErr (string error); void mark(GenNode const&); + void installExpander (Expander::ProbeFun, Expander::ChangeFun); + protected: virtual bool doReset() =0; virtual bool doClearMsg() =0; virtual bool doClearErr() =0; - virtual bool doExpand (bool yes) =0; + virtual bool doExpand (bool yes); virtual void doReveal (ID child) =0; virtual void doRevealYourself () =0; @@ -249,6 +256,22 @@ namespace model { } // not typed, no attributes, all arguments as children + /** + * Configure the (optional) functionality to expand or collapse the UI-Element. + * @param detectCurrExpansionState a lambda or function to retrieve if the element is currently expanded + * @param howto_expand_collapse a lambda or function to switch the element's expansion state + * @note unless this setup function is invoked, the expand/collapse functionality remains disabled; + * invoking the #slotExpand() or sending **mark** "`expand`" messages via UI-Bus has no effect then. + */ + inline void + Tangible::installExpander (Expander::ProbeFun detectCurrExpansionState, + Expander::ChangeFun howto_expand_collapse) + { + expand_ = Expander{move (detectCurrExpansionState), move (howto_expand_collapse)}; + } + + + }} // namespace gui::model #endif /*GUI_MODEL_TANGIBLE_H*/ diff --git a/src/gui/model/widget.hpp b/src/gui/model/widget.hpp index 5302cfbd5..d5b303725 100644 --- a/src/gui/model/widget.hpp +++ b/src/gui/model/widget.hpp @@ -67,12 +67,6 @@ namespace model { UNIMPLEMENTED ("Widget reset"); } - virtual bool - doExpand (bool yes) override - { - UNIMPLEMENTED ("Widget doExpand"); - } - virtual void doReveal (ID child) override { diff --git a/wiki/renderengine.html b/wiki/renderengine.html index 92b89d1d0..af92192b7 100644 --- a/wiki/renderengine.html +++ b/wiki/renderengine.html @@ -1523,10 +1523,10 @@ Lumiera agrees to this common understanding (of most film editing and sound hand → PipeHandling -
+
//mediating entity used to guide and control the presentation of a clip in the UI.//
 
-The clip representation in the UI links together two distinct realms and systems of concerns. For one, there are the properties and actions performed on the clip for sake of editing a movie. Everything which has a tangible effect on the resulting render. These information and operations are embodied into the HighLevelModel and can be manipulated script driven, without relying on an UI. But beyond that, there is also the local mechanics of the interface, everything which makes working on the edit and interacting with the model into a smooth experience and workflow. With respect to this concerns, a clip is a self-contained entity which owns its own state and behaviour. The ClipPresenter is the pivotal element to link together those two realms.
+The clip representation in the UI links together two distinct realms and systems of concerns. For one, there are the properties and actions performed on the clip for sake of editing a movie. Everything which has a tangible effect on the resulting render. These information and operations are embodied into the HighLevelModel and can be manipulated script driven, without relying on an UI. But beyond that, there is also the local mechanics of the interface, everything which makes working on the edit and interacting with the model into a smooth experience and workflow. With respect to these concerns, a clip is a self-contained entity which owns its own state and behaviour. The ClipPresenter is the pivotal element to link together those two realms.
 
 [>img[Clip presentation control|uml/Timeline-clip-display.png]]
 Regarding the global angle, the ClipPresenter is an UI-Element connected to the UI-Bus, and it is added as child element to some parent entity, a TrackPresenter, which likewise serves as UI representation of a [[fork ("track")|Fork]], and controls widgets to render a track like working scope (in the header pane and in the timeline contents pane). Commands manipulating the clip can be sent via the embedded bus terminal, and status regarding clip properties and nested child elements (effects, transitions) is received as messages via the bus, which insofar plays the role of model and controller.
@@ -3241,7 +3241,7 @@ More specifically, the integration is based on ''messaging''. To start with, the
 ----
 In a preliminary attempt to establish an integration between the GUI and the lower layers, in 1/2009 we created an PlayerDummy, which "pulls" dummy frames from the (not yet existing) engine and displays them within an XV viewer widget. This highlighted the problems we're about to encounter and made us think about the more radically decoupled approach we followed thereafter...
-
+
Building a layered architecture is a challenge, since the lower layer //really// needs to be self-contained, while prepared for usage by the higher layer.
 A major fraction of all desktop applications is written in a way where operational logic is built around the invocation from UI events -- what should be a shell turns into a backbone. One possible way to escape from this common anti pattern is to introduce a mediating entity, to translate between two partially incompatible demands and concerns: Sure, the "tangible stuff" is what matters, but you can not build any significant piece of technology if all you want is to "serve" the user.
 
@@ -3267,7 +3267,11 @@ According {{red{to the current plan (2018)}}}, the GuiModel will not be a dedica
 While most actual details are abstracted away by this approach, it can be expected that the handling of typical diff changes is somewhat similar in most actual widgets. For this reason we provide a selection of generic adapters and building blocks to simplify the assembly of actual widget implementations.
 
 !!!expanding and collapsing
-Several UI elements offer the ability to be collapsed into a minimal representation to save screen real estate. The UI-Element protocol defines this to happen either by invoking a dedicated signal slot on the element, or by sending an appropriate message over the UI-Bus. The actual implementation is quite simple, but unfortunately requires knowledge regarding the specific widget configuration. A commonly used approach is to wrap the expandable/collapsible element into a {{{Gtk::Expandable}}}, but there are notable exceptions, where the widget is bound to handle the expanding or reducing of information display all by itself. We bridge this discrepancy by introducing an {{{Expander}}} interface to act as adapter. 
+Several UI elements offer the ability to be collapsed into a minimal representation to save screen real estate. The UI-Element protocol defines this to happen either by invoking a dedicated signal slot on the element, or by sending an appropriate message over the UI-Bus. The actual implementation is quite simple, but unfortunately requires knowledge regarding the specific widget configuration. A commonly used approach is to wrap the expandable/collapsible element into a {{{Gtk::Expandable}}}, but there are notable exceptions, where the widget is bound to handle the expanding or reducing of information display all by itself. We bridge this discrepancy by introducing an {{{Expander}}} interface to act as adapter.
+* the default implementation holds an {{{Expander}}} functor. In default state, this functor as well as expanding / collapsing functionality remains disabled
+* to enable it, two lambdas need to be provided, to configure
+** how to find out about the expansion state of the widget
+** how to change this expansion state (i.e. how to expand or collapse the widget)
 
 !!!revealing an element
 The UI-Element protocol also includes the ability to //reveal an element// -- which means actively to bring this element into sight, in case it is hidden, collapsed or obscured by scrolling away.
@@ -3465,7 +3469,7 @@ In the most general case, there can be per-track content and nested content at t
 → important question: how to [[organise the widgets|GuiTimelineWidgetStructure]]
 
-
+
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).
 
@@ -3474,7 +3478,7 @@ Right away, this topic touches a tricky design and architectural challenge: the
 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 "canvas widget"|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 an 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, it is likely 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?
+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, it is likely 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:
diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm
index 92feff65a..31decc366 100644
--- a/wiki/thinkPad.ichthyo.mm
+++ b/wiki/thinkPad.ichthyo.mm
@@ -1929,18 +1929,24 @@
 
 
 
-
-
-
-
+
+
+
+
 
-
-
+
+
+
+
+
 
 
 
 
 
+
+
+
 
 
 
@@ -2009,6 +2015,9 @@
 
 
 
+
+
+
 
 
 
@@ -2018,6 +2027,12 @@
 
 
 
+
+
+
+
+
+