diff --git a/doc/technical/code/gtk/layout.txt b/doc/technical/code/gtk/layout.txt new file mode 100644 index 000000000..22bb2d4f8 --- /dev/null +++ b/doc/technical/code/gtk/layout.txt @@ -0,0 +1,95 @@ +GTK Widget Layout +================= +:Date: 2022 + +//Menu: label layout + +.collecting insights on inner workings of the GTK Layout Engine +GTK is a powerful framework, yet also carries along some legacy and homegrown structures, +hidden within a huge code base. While there are excellent beginner tutorials, definitive technical +documentation is scarce, and design decisions must be inferred from the existing code sometimes. +Since Lumiera is on the mission of building a quite elaborate user interface -- well beyond the +abilities of a typical office application -- we're often forced to identify possible extension +points and or work out reliable and future proof techniques to create behaviour beyond the +original intentions of the GTK developers. + +Allocation of Screen Space +-------------------------- + +GTK is built from ground up with the assumption of a _dynamical layout_ -- designing the UI layout +with fixed sizes and widget placements is not seen as a viable option and only marginally supported +as an exotic corner case. An user interface built with GTK is comprised of widgets arranged into a +hierarchical structure, and styled by matching a corresponding structure of _layout nodes_ against +a set of _cascading style rules_ (CSS). When a new widget is attached into this structure, it has +to progress through several stages + +- creation / allocation +- wiring +- ``map'' : associate a display window (GDK window) and dedicated extension on screen +- ``realize'' : bring all structures into workable state +- ``draw'' : render the visuals into pixels for display +- ``unrealize'' : remove from active interconnections +- ``unmap'' : release the ties to the associated (GDK) window +- ``destroy'' : detach from managers and deallocate data + +After passing through the _realize phase,_ a widget holds unto a `Allocation` -- a struct defining +the position of the upper left corner, and its extension (width, height) in pixel coordinates. Due +to dynamic interaction responses, a widget can be _resized,_ causing the emission of a _resize event._ +Processing this resize event within the GTK Event Loop will create and assign an _updated allocation,_ +followed by _redrawing_ the widget. GTK uses double buffering, and thus it is sufficient to draw the +widget in its new shape, without having to clear out the old state from the display buffer. + +Allocation strategy +~~~~~~~~~~~~~~~~~~~ +.verified with Gtk 3.24 +In GTK, as a rule, screen extension is never squeezed, but rather expanded to fit. Every widget is +queried through a set of virtual functions (``vfunc'') to define its basic layout trend (the ``size +request mode''), and its minimal and natural extension + +minimal:: the absolute minimum required by the widget to work properly +natural:: the extension necessary to use the widget properly, without wasting screen estate + +size request mode:: + by implementing ´get_request_mode_vfunc()`, the widget defines how its allocation shall be treated... + + - `SIZE_REQUEST_HEIGHT_FOR_WIDTH` : start with a desired width and then accommodate the vertical extension + - `SIZE_REQUEST_WIDTH_FOR_HEIGHT` : start with a desired height and expand horizontally as needed + - `SIZE_REQUEST_CONSTANT_SIZE` (this seems to be there for sake of completeness, but is typically + not treated explicitly as a distinct case in layout code; expect to fall back to the default, + which is height-for-width) + +Starting from these requirements as defined by the widget, next the CSS definitions are accessed through +the *CSS Gadget*, which is associated internally with each widget. This typically causes the allocation +to be _increased_ to allow for borders, margins, padding and drop shadows. In case the widget is placed +into a container with fill-layout, the widget may be expanded further, or margins will be created by +shifting the pixel coordinates. From the allocation worked out thus far, all headroom necessary for +proper drawing is then _subtracted,_ and the resulting bare allocation is passed to the widget through +the function `gtk_widget_set_allocation()`, which also invokes the `Gtk::Widget::on_size_allocate()` +hook. Note that the fully expanded allocation is not stored; GTK just assumes that widgets will +draw themselves properly, including their decorations, thereby possibly using extended screen space. + +Minimal vs. natural size request +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +In short: you need to define both, while the natural size request is more relevant in most cases. +All top level entities and most layout containers will start with the natural size. However, some +containers initiate the with-for-height (or height-for-width) request sequence with the minimal +extension. Most notably, the _canvas control,_ `Gtk::Layout` allocates widgets according to +this scheme (see `gtk_layout_allocate_child()` in 'gtklayout.c'). + +NOTE: judging from the code, it is recommended to implement both `get_preferred_height|width()`, + irrespective of the layout trend. However, it is only necessary to implement the derivative + function matching the trend, e.g. `Gtk::Widget::get_preferred_height_for_width_vfunc() in + case of `SIZE_REQUEST_HEIGHT_FOR_WIDTH`, since the default implementation will fall back + on `get_preferred_with()` for the other one. + +However -- in most cases (custom) widgets are assembled by arranging pre defined standard widgets +into some layout container (Box or Grid or Tree); in those cases, the default implementation works +out the required extension bottom-up from the building blocks, and there is no need to define +any specific size request + +Explicit size_request +^^^^^^^^^^^^^^^^^^^^^ +There is a set of functions `set|get_size_request()`. These seem to be a mostly obsolete leftover from +earlier days. They are implemented by forcibly increasing the _minimal size request_ -- and since many +standard containers today (as of 2022) work based on the natural size request rather, this information +is not treated coherently, and sometimes leads to surprising behaviour. diff --git a/src/stage/widget/element-box-widget.cpp b/src/stage/widget/element-box-widget.cpp index f9633dbe1..e80e5ec24 100644 --- a/src/stage/widget/element-box-widget.cpp +++ b/src/stage/widget/element-box-widget.cpp @@ -23,8 +23,29 @@ /** @file element-box-widget.cpp ** Implementation details of the UI building block to display an ID label. + ** A special twist arises from the requirement to show the temporal extension of media, + ** leading to a display on a time calibrated canvas, where a given time span corresponds + ** to some fixed pixel count, according to the current zoom factor. Such a layout stands in + ** contradiction to the fundamental design principles of GTK — the assumption being that + ** all widget layout shall be determined dynamically, to adopt to screen resolution, system + ** fonts and user provided styles and themes. ** - ** @todo WIP-WIP-WIP as of 11/2018 ///////////////////////////////////////////////////////////////////////TICKET #1185 + ** This conflict can be reconciled by representing this time-to-size calibration in the form + ** of a desired minimal extension reported by the individual widget. Since GTK only ever _expands_ + ** the widget provided size request (e.g. to accommodate for additional borders and padding from CSS), + ** our timeline canvas can be sure to get at least the necessary extension. Moreover, we can assure, + ** with the help of our custom style sheet, that those size constrained widgets do not require any + ** additional decoration, borders or margin. Accordingly, we choose `Gtk::Frame` as the base class, + ** so the border is drawn as part of the widget's content, with the identification label placed on top. + ** + ** However, after declaring to GTK that the widget can be rendered within the specific size constraint, + ** it now becomes our responsibility to enforce this size constraint onto any child widgets used as + ** part of the ElementBoxWidget — especially we have to query the size required to represent the + ** name-ID label, possibly taking measures to reduce this size to fit + ** @todo as of 9/2022 we just hide the label text completely to comply with the constraints; + ** it is conceivable to use a more elaborate approach and to shorten the label text to fit. + ** + ** @todo WIP-WIP as of 09/2022 ///////////////////////////////////////////////////////////////////////TICKET #1185 ** */ @@ -33,8 +54,7 @@ #include "stage/widget/element-box-widget.hpp" #include "stage/style-scheme.hpp" -//#include "stage/ui-bus.hpp" -//#include "lib/format-string.hpp" +//#include "lib/format-string.hpp" //////////////////TODO debugging #include "lib/format-cout.hpp" //////////////////////TODO debugging #include "lib/util.hpp" @@ -44,19 +64,34 @@ -//using util::_Fmt; //using util::contains; //using Gtk::Widget; -//using sigc::mem_fun; -//using sigc::ptr_fun; -//using std::cout; -//using std::endl; namespace stage { namespace widget { + namespace {//Implementation helpers... + /** + * Helper to retrieve what GTK effectively uses as minimal extension of a widget. + * @remark in fact this is the so called "preferred natural size", since GTK always + * allocates at least this amount of screen extension. Judging from the GTK 3.24 + * code (as of 9/2022), the minimal size setting and the "size_request" are in most + * cases just used for consistency checks, while the natural size is determined in accordance + * to the layout preference (height-for-width or width-for-hight) and then only ever increased + * to fit further CSS settings (border, margin) and to handle fill-layout. + * @warning however note that `Gtk::Layout` (which we use as foundation for our Timeline canvas) indeed + * starts its calculation from the **minimal width** of the attached child widget. Thus, as far + * as implementing the VFuncs, both cases should be treated symmetrically. + */ + inline void + queryNaturalSize (Gtk::Widget const& widget, Gtk::Requisition& natSize) + { + Gtk::Requisition minimumDummy; + widget.get_preferred_size (minimumDummy, natSize); + } + }//(End)helpers @@ -127,24 +162,25 @@ namespace widget { /** * Layout preferences are delegated through the Strategy - * - by default, the strategy will just invoke the inherited vfuncs + * - by default, the strategy will just invoke the inherited VFuncs * - however, when a size constraint for the ElementBoxWidget must be observed, * the strategy will control the extension of our child widgets (side-effect) * and then just return the extension as dictated by the constraints - * @remark Based on the GTK-3.22 implementation code, we know that the minimum_width - * is only used for consistency checks (and otherwise ignored), while the natural_width - * will always be respected. The GKT layout management might _increase_ that value... + * @remark Based on the GTK-3.24 implementation code, we know that most usages will draw + * upon the natural_width, but there are some use cases (esp. `Gtk::Layout`), where the + * calculation takes minimum_width as starting point. The returned values will always be + * respected, while the GTK layout engine might _increase_ the given extensions... * - do adjust for additional border and margin settings from CSS * - and to expand the widget when used within a container with fill-alignment * Moreover the #get_preferred_height_for_width_vfunc will be invoked with the * results from this function. The possible adjustment is done by invoking the - * vfunc `adjust_size_allocation` on the GTK-class, which typically delegates to + * VFunc `adjust_size_allocation` on the GTK-class, which typically delegates to * gtk_widget_real_adjust_size_allocation, and the latter invokes * - adjust_for_margin * - adjust_for_align - * After these adjustments and some sanity checks, the resulting size allocation - * is passed to the `size_allocate` vfunc, which could be tapped on the Gtk::Widget - * C++ wrapper, but usually just stores this allocation into the widget private data. + * After these adjustments and some sanity checks, the resulting size allocation is passed + * to the `size_allocate` VFunc, which passes through the `Gtk::Widget::on_size_allocate()` + * and after that by default stores this allocation into the widget's private data. */ void ElementBoxWidget::get_preferred_width_vfunc (int& minimum_width, int& natural_width) const @@ -153,7 +189,19 @@ namespace widget { minimum_width = natural_width = strategy_.getWidth(); else _Base::get_preferred_width_vfunc (minimum_width,natural_width); - cout << "IIII::"< "< -//#include #include #include #include //////TODO debugging @@ -73,7 +78,7 @@ namespace widget { , CONTENT ///< Widget serves to represent a piece of content (Clip) }; - /** the type of content object to derive suitable styling */ + /** the type of content object to derive suitable styling (background colour, icon) */ enum Type { VIDEO ///< represents moving (or still) image data , AUDIO ///< represents sound data , TEXT ///< represents text content @@ -87,35 +92,6 @@ namespace widget { }; using SizeGetter = std::function; -/////////////////////////////////////////////////////////////////////////////////////////////////////////////TICKET #1219 : TESTCODE : remove this - class HackedTxt - : public Gtk::Label - { - public: - using Gtk::Label::Label; - protected: - void - get_preferred_width_vfunc (int& mw, int& nw) const override - { - Gtk::Label::get_preferred_width_vfunc (mw,nw); - cout << "Haxx::"< + - + @@ -18337,8 +18337,8 @@ - - + + @@ -18633,9 +18633,28 @@ + + - - + + + + + + +

+ im Besonderen keine Border! +

+

+ Das ist auch genau ein Argument, warum wir auf Gtk::Frame aufsetzen — damit ist der Frame nämlich ins Innere des Widget verlagert, und das Label liegt on-top und grenzt an den Rand an. +

+ +
+ +
+
+ + @@ -18649,29 +18668,56 @@ - - - + + + - - - - + + + + - - + + + + + + + + + + + +
    +
  • + die wichtigste Implementierung ist in gtk_widget_size_allocate_with_baseline; sie wird von fast allen Containern verwendet, um ihre Kinder zu allozieren +
  • +
  • + top-level Windows, Dialogboxen und Menu-Items verwenden ihre eigene Implementierung +
  • +
  • + Gtk::Layout hat ebenfalls eine vereinfachte Variante: diese geht von dem minimal_size des Widgets aus! +
  • +
+ +
+
+ + + + @@ -18831,8 +18877,8 @@ - - + + @@ -18969,7 +19015,7 @@

- Fazit: ja die Lösung funktioniert uns erscheint stabil + Fazit: ja die Lösung funktioniert und erscheint stabil

@@ -18980,8 +19026,72 @@ - - + + + + + + + + + + + +

+ ...daher scheidet die offensichtliche einfache Lösung aus: nämlich einen size-request in der Größe des Icon zu setzen... +

+

+ Stattdessen müssen wir dieses Minimum explizit in die Verarbeitung des size-Constraint einarbeiten, und dabe auf der natural-size des Icon aufbauen +

+ +
+ +
+ + + + + + +

+ Nein! minimal und natural size sollten gleich sein +

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

+ wenn, dann muß man das dynamisch implementieren... +

+ +
+ +
+
+
+ + @@ -19384,9 +19494,9 @@ - - - + + + @@ -32218,7 +32328,7 @@ - + @@ -32598,7 +32708,7 @@ - + @@ -58211,14 +58321,28 @@ - + + + + + + +

+ ....bin damals mit size_request eingestiegen — aber von dort bald auf gtk_widget_size_allocate_with_baseline gestoßen; letzteres ist die eigentliche zentrale Funktion +

+ +
- + + + + + @@ -58260,6 +58384,284 @@ + + + + + + + + + + + +
    +
  • + Container-Widgets haben so etwas wie ein generisches Layout-Muster ⟹ gtk_widget_size_allocate_with_baseline +
  • +
  • + ABER es gibt anscheinend viele Widgets, die das direkt machen (und vom Container-Konzept bewegt sich GTK weg, zu Gunsten des Konzepts "CSS Gadget"); konkret habe ich bei einem GtkFrame durchaus beobachtet, daß dem Widget nur seine geforderte min-size zugeteilt wurde +
  • +
+ +
+ + + + + + + + + + + + + + + + + + + +

+ Einstiegspunkt aus vielen Widgets +

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

+ gtk_widget_size_allocate_with_baseline +

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

+ aber: spezielle Widgets/Container +

+

+ behandeln hier die Child-Widgets +

+ +
+ + + + + + + +

+ gtk_widget_size_allocate_with_baseline +

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

+ Beachte: _GtkCssGadgetClass->allocate +

+ +
+ + + + + + + +

+ typischerweise(via GadgetClass): gtk_css_gadget_real_allocate +

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

+ und damit wieder von gtk_css_gadget_allocate +

+ +
+ +
+ + + + + + +

+ allerdings dann vom umschließenden Widget +

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

+ Implementierung: gtk_frame_allocate +

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

+ dieses baut auf auf: gtk_css_gadget_allocate +

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

+ (konkret im Beispiel): gtk_frame_size_allocate +

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

+ ruft damit gtk_css_gadget_allocate +

+ +
+ + +
+ + + +
+
+
@@ -58289,6 +58691,8 @@
+ + @@ -61046,7 +61450,7 @@
- +