diff --git a/src/stage/widget/element-box-widget.cpp b/src/stage/widget/element-box-widget.cpp index 36a5a05f0..23fedae1b 100644 --- a/src/stage/widget/element-box-widget.cpp +++ b/src/stage/widget/element-box-widget.cpp @@ -88,9 +88,41 @@ namespace widget { inline void queryNaturalSize (Gtk::Widget const& widget, Gtk::Requisition& natSize) { - Gtk::Requisition minimumDummy; - widget.get_preferred_size (minimumDummy, natSize); + Gtk::Requisition minDummy; + widget.get_preferred_size (minDummy, natSize); } + + inline int + queryNaturalHeight (Gtk::Widget const& widget) + { + int minDummy{0}, natHeight{0}; + widget.get_preferred_height(minDummy, natHeight); + return natHeight; + } + + inline int + queryNaturalWidth (Gtk::Widget const& widget) + { + int minDummy{0}, natWidth{0}; + widget.get_preferred_width(minDummy, natWidth); + return natWidth; + } + + /** point of reference for layout computations */ + Gtk::Requisition ICON_SIZ{}; + + /** excess factor used to prevent "layout flickering" + * @remark once hidden, an element will only be re-shown + * when some excess headroom is available */ + const double HYSTERESIS = 1.6; + + inline void + initIconSizeHeuristic (Gtk::Widget const& icon) + { + if (ICON_SIZ.width > 0) return; + queryNaturalSize (icon, ICON_SIZ); + } + }//(End)helpers @@ -151,6 +183,9 @@ namespace widget { menu_.get_style_context()->add_class(CLASS_idlabel_menu); name_.get_style_context()->add_class(CLASS_idlabel_name); name_.set_hexpand(true); + + this->show_all(); + initIconSizeHeuristic (icon_); } @@ -164,6 +199,8 @@ namespace widget { IDLabel::setCaption(cuString& idCaption) { name_.set_text(idCaption); + // cache required full display size (for size constrained layout) + queryNaturalSize (*this, labelFullSize_); } @@ -287,28 +324,158 @@ namespace widget { /** * Ensure the IDLabel stays within a given size constraint. - * In case the standard rendering of with icon and name caption exceeds - * the given screen space, this simplified initial implementation just - * hides the name caption, assuming without further check that the two - * icons will fit into the constrained space. - * @todo as of 9/22, a planned full implementation will eventually - * shorten the caption text and possibly also combine both - * Icons into a single button when necessary... ///////////////////TICKET #1238 : adjust size of the ID caption + * In case the standard rendering complete with icon and name caption + * exceeds the given screen space, try to bring this widget into imposed + * limits by reducing or hiding some parts. */ void IDLabel::imposeSizeConstraint (int widthC, int heightC) { - if (name_.get_visible()) - { // detect if label box fits within given size constraint - queryNaturalSize (*this, labelFullSize_); - - if (labelFullSize_.width > widthC or labelFullSize_.height > heightC) - name_.hide(); + // short circuit: need to perform precise checks? + if ( labelFullSize_.width > widthC + or labelFullSize_.height > heightC + ) + this->adaptSize(widthC, heightC); + } + + + namespace {//IDLabel layout management internals.... + + /** attempt to reduce space consumption + * @return achieved width reduction in pixels + */ + inline int + reduce(Gtk::Button& icon) + { + int widthReduction{0}; + if (icon.get_visible()) + { + widthReduction = queryNaturalWidth (icon); + icon.hide(); + } + return widthReduction; + } + + /// @todo 10/22 also attempt to shorten the label... ///////////////////TICKET #1242 : adjust size of the ID caption + inline int + reduce(Gtk::Label& label, int goal) + { + ASSERT (goal >=0); + int reduction{0}; + if (label.get_visible()) + { + int width = queryNaturalWidth (label); +///////////////TODO do something to shorten the label /////////////////////////TICKET #1242 : adjust size of the ID caption +/////// int after = queryNaturalWidth (label); +/////// reduction = width - after; + if (reduction < goal) + {//shortening alone does not suffice + label.hide(); + reduction = width; + } + } + return reduction; + } + + /** attempt to use available space to show more content + * @param icon widget to possibly expand + * @param w additional width available + * @param h vertical headroom available + * @param reCheck function to update and verify success + * @return if additional content could successfully be expanded + */ + template + inline bool + maybeShow(Gtk::Button& icon, int w, int h, FUN& reCheck) + { + bool success{false}; + if (w >= ICON_SIZ.width * HYSTERESIS and h >= ICON_SIZ.height) + { + icon.show(); + if (not (success=reCheck())) + icon.hide(); + } + return success; + } + + template + inline bool + maybeShow(Gtk::Label& label, int w, int h, FUN& reCheck) + { + bool success{false}; + // use icon dimensions as as heuristics to determine + // if attempting to show the label is worth trying... + if (w >= ICON_SIZ.width * HYSTERESIS and h >= ICON_SIZ.height) + { + label.show(); + int width = queryNaturalWidth (label); + int goal = width - w; + if (goal > 0) // too large, yet might fit if shortened + reduce (label, goal); + if (not (success=reCheck())) + label.hide(); + } + return success; + } + + + }//(End)Layout helpers + + + /** + * Multi-step procedure to keep this IDLabel widget within the given + * screen size constraints. In case the extension needs to be reduced, + * the name label and both icons are probed and possibly reduced. + * Otherwise, if there is sufficient headroom, an attempt is made + * possibly to show parts again, albeit with some hysteresis. + * @todo as of 10/22, a planned full implementation will eventually + * shorten the caption text and possibly also combine both + * Icons into a single button when necessary... ///////////////////TICKET #1242 : adjust size of the ID caption + */ + void + IDLabel::adaptSize (int widthC, int heightC) + { + // first determine if vertical extension is problematic + int currH = queryNaturalHeight (*this); + if (currH > heightC) + {//hide all child widgets, + // not much options left... + name_.hide(); + menu_.hide(); + icon_.hide(); + return; + } + + // now test if we need to reduce or can expand + int currW = queryNaturalWidth (*this); + if (currW > widthC) + {//reduce to comply + int goal = currW - widthC; + ASSERT (goal > 0); + if (goal -= reduce(name_, goal) <= 0) return; + if (goal -= reduce(menu_) <= 0) return; + if (goal -= reduce(icon_) <= 0) return; + currW = queryNaturalWidth(*this); + goal = currW - widthC; + ENSURE (goal <= 0, "IDLabel layout management floundered. " + "Removed all content, yet remaining width %d > %d" + , currW, widthC); } else - { - if (labelFullSize_.width <= widthC and labelFullSize_.height <= heightC) - name_.show(); + {//maybe some headroom left to show more? + int headroom = widthC - currW; + auto reCheck = [&]() -> bool + {// WARNING: side effect assignment + currW = queryNaturalWidth (*this); + currH = queryNaturalHeight(*this); + headroom = widthC - currW; + return currH <= heightC + and currW <= widthC; + }; + + if (not maybeShow (icon_, headroom, heightC, reCheck)) return; + if (not maybeShow (menu_, headroom, heightC, reCheck)) return; + if (not maybeShow (name_, headroom, heightC, reCheck)) return; } } diff --git a/src/stage/widget/element-box-widget.hpp b/src/stage/widget/element-box-widget.hpp index d57528993..157aa5bb4 100644 --- a/src/stage/widget/element-box-widget.hpp +++ b/src/stage/widget/element-box-widget.hpp @@ -125,6 +125,7 @@ namespace widget { private: Gtk::Requisition labelFullSize_{}; + void adaptSize (int, int); }; diff --git a/wiki/renderengine.html b/wiki/renderengine.html index 44437d3c7..b9f01beff 100644 --- a/wiki/renderengine.html +++ b/wiki/renderengine.html @@ -3217,7 +3217,7 @@ In accordance with this structure, we introduce a central component, the {{{Pane -
+
//A building block used pervasively throughout the Lumiera UI  to represent a named entity.//
 This widget presents a horizontally extended body, which holds a characteristic ''Head-Triplet'' of visual Elements:
 * an //Icon// to create the visual anchor point. In many cases, this will be ''the Placment Icon'' (a hallmark of Lumiera's UI)
@@ -3302,7 +3302,7 @@ Especially when the component determines a placement of the //window// within it
 
 !!!constrained Widget size
 When used as Clip display on a timeline canvas with calibrated time axis, the requirement arises to confine a GTK widget to a pre established size constraint. This requirement is problematic, as it stands in ''direct contradiction'' to ''GTK's fundamental design'': Elements are never to be positioned explicitly, but rather the layout will flow into balance, factoring in the specific font, language support and interface styling and theming. GTK does not provide any API to set layout explicitly — not even for special corner cases.
-Thus, if we intend to bring a custom widget into compliance with our contextual size constraints, the only solution is to //make the GTK layout engine turn out the desired extension allocation// for this custom widget. An detailed survey of the GTK implementation reveals the possible extension points, were such a layout manipulation could be injected.
+Thus, if we intend to bring a custom widget into compliance with our contextual size constraints, the only solution is to //make the GTK layout engine turn out the desired extension allocation in accordance to our constraints// for this custom widget. An detailed survey of the GTK implementation reveals the possible extension points, were such a layout manipulation could be injected.
 * some amount of screen extension is allocated by the framework or by a container widget for its children
 * this happens on first "realisation" of the Widget, or later, when a resize request is processed and the layout cache invalidated
 * the entry point is {{{Gtk::Widget::size_allocate()}}}  (possibly with an additional "baseline" value)
@@ -3316,6 +3316,10 @@ It turns out that the GTK layout management implementation always observes the w
 So the seemingly ''optimal leverage point'' is to ''return our pre established size constraint as result'' from these query functions — which can be overridden in the Gtkmm C++ implementation through the {{{Gtk::Widget::get_preferred_width_vfunc()}}} and {{{Gtk::Widget::get_preferred_height_for_width_vfunc()}}}. However, since GTK assumes these values to be sane and sufficient for a proper realisation of any embedded content, at this point it becomes our responsibility to control and reduce the embedded child widget's extension to bring them into compliance. Failing to do so will lead to garbled drawing on screen.
 
 Our solution approach is to watch the results returned by the default implementation and possibly to hide content until the remaining content conforms to the size constraint; after that, we can return //exactly our calibrated size// and expect GTK to observe this request, passing it down to embedded widgets reduced by style decorations.
+
+{{red{🛆 ''Warning'':}}} the code to perform these successive layout adjustments is //potentially performance critical.//
+This code is called //for each focus change// and might have to treat //hundreds of widgets// in a typical film edit timeline.
+Further empiric survey of memory footprint and invocation times seems indicated &rArr; [[Ticket #1240|https://issues.lumiera.org/ticket/1240]]
 
@@ -3658,7 +3662,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.
-
+
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
@@ -3703,7 +3707,8 @@ As indicated in the drawing above, pretty much every UI element exposes a button
 
 !!!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 &rarr; [[#1083|http://issues.lumiera.org/ticket/1083]]
+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 &rarr; [[#1083|https://issues.lumiera.org/ticket/1083]]
+{{red{should also reconsider the term »slave«}}}
 
 !!!nesting
 By principle, this workspace structure is //not a list of "Tracks"// -- it is a system of ''nested scopes''. The nesting emerges on demand.
diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm
index 6f3e9f32f..162f9d4c5 100644
--- a/wiki/thinkPad.ichthyo.mm
+++ b/wiki/thinkPad.ichthyo.mm
@@ -19269,21 +19269,302 @@
 
 
 
-
+
 
+
+
 
 
 
 
 
-
+
+
 
+
+
+
+
+
+
+
+  
+    
+  
+  
+    

+ BEDINGUNG: Constraint < Vollgröße +

+
    +
  • + Vollgröße ist im Widget gespeichert und wird nach Textänderung ermittelt (‣damit implizit auch initial) +
  • +
+ +
+ + +
+ + + + + + +

+ BEDINUNG: cH > aktuelleHöhe +

+ +
+ + + + + + + + + +
+ + + + + + +

+ BEDINUNG: cW > aktuelleWeite +

+ +
+ + + + + + + + + + +

+ BEDINGUNG: ΔName > goal +

+
    +
  • + Hilfsfunktion reduce(Name) : kann im Extremfall den Namen ausblenden +
  • +
+ +
+ +
+ + + + + + +

+ BEDINGUNG: ΔMenu > goal +

+
    +
  • + Hilfsfunktion reduce(Menu) : kann das Menü ggfs ausblenden und liefert das dadurch erzielte Delta +
  • +
+ +
+ +
+ + + + + + +

+ Hier kein Test mehr notwendig; mehr als alles ausblenden können wir nicht +

+ +
+ +
+
+ + + + + + + + +

+ Mehrstufige Prüfung mit Hysterese (um Flackern zu vermeiden)... +

+
    +
  • + rechnerische Prüfung: Nominalgröße Icon + Hysterese < cW und ebenso < cH +
  • +
  • + danach: Icon einblenden und reale Größe ermitteln und gegen Constraint checken +
  • +
+ +
+ +
+ + + + + + +

+ auch hier mehrstufige Prüfung mit Hysterese... +

+ +
+ +
+ + + + + + +

+ auch hier mehrstufige Prüfung... +

+
    +
  • + zunächst rechnerisch... +
  • +
      +
    • + in der ersten (einfachen) Version wird gegen die nominelle Gesamtgröße geprüft + Hysterese +
    • +
    • + in der (geplanten) Vollversion prüfen wir gegen die Icon-Größe + Hysterese, und unterstellen, daß sich der Name dann hinreichend kürzen kann +
    • +
    +
  • + danach wird der Name eingeblendet und reduce(Name) aufgerufen; sollte dies scheitern, muß eine Warnung und Assertion-Failure erfolgen, da die Logik sonst zwangsläufig in ein Schleife mit permanentem Flackern läuft! +
  • +
+ +
+ +
+
+
+
+ + + + + + + + + + + + + +

+ premature optimisation?? +

+

+ Normalwerweise funktionieren alle unsere GUIs trotzdem schnell genug.
Aber hier wollen wir diese Sequenz hunderte Mal für jeden Fokus-Wechsel machen... +

+

+ +

+

+ Problematisch ist, daß hier über zwei Ebenen hinweg und über zwei mal zwei virtuelle Calls gegangen wird +

+
    +
  • + wir springen jeweils in eine non-inline-Funktion auf Gtkmm +
  • +
  • + diese delegiert auf eine non-inline-Funktion in Gtk+ +
  • +
  • + und diese nutzt u.U zwei drei virtuelle Callbacks (Layout-Trend, Haupt-Dimension, sekundäre Dimension) +
  • +
  • + und jeder dieser Callbacks wir ein weiteres Mal von Gtkmm durch eine vtable dispatched +
  • +
+ +
+
+ + + + + + +

+ ...denn sie wird für jeden Fokuswechsel und für jeden erfolglosen Versuch erneut durchlaufen, und zwar in den meisten Fällen (Label) bis zum 3.Schritt, nur um dann zu merken, daß eben doch nichts mehr rauszuholen ist. +

+ +
+
+ + + + + + +

+ wenn das Stylesheet eben doch zusätzliche Paddings definiert, dann geht die Rechnung nicht auf, da sie auf den required-sizes der Kind-Widgets beruht, und daher (mit Paddings) zu optimistisch ist. Daher müssen wir zwingend nach einem versuchten wieder-Einblenden die reale Ausdehnung des ganzen IDLabel ermitteln und gegen die Constraints prüfen. Bei der Verkleinerung ist das nicht der Fall, denn da wirken die wegfallenden Paddings als zusätzlicher "Bonus". +

+ +
+ +
+ + + + + + +

+ Damit muß ich nach jedem Schritt nur einmal die Größe neu ermitteln; diese Werte schlagen dann aus dem λ per Seiteneffekt auf die Variablen des umschließenden Scope durch. Das λ selber ist "scheinbar" nur eine Prüf-Funktion, und wird als Solche an die Hilfsfunktionen gegeben. Nicht schön, aber auch nicht wirklich gefährlich, da sich alles in einem lokalen Namespace abspielt. Habe eine Warnung im Code hinterlassen +

+ +
+ +
+
+ + + + + + + + + + + +
+
+ + + + + - + + @@ -19321,6 +19602,10 @@ + + + + @@ -19712,6 +19997,25 @@ + + + + + + + + + + + + + + + + + + + @@ -36091,6 +36395,17 @@ + + + + + + + + + + + @@ -64126,6 +64441,23 @@ + + + + + + + + + + + + + + + + +