Aug 2004, S.Geerken@ping.de =========================== Dw Render Abstraction =========================== This document describes the rendering abstraction used in Dw. At the time this document was written, some other documents about Dw were out of date, and have to be rewritten. Sometimes, you will find remarks about the old design (with one concrete Gtk+ widget instead of the layout/multiple rendering views); those are useful for those, who are already familiar with the old design. Furthermore, it makes the explanation often simpler, because the new design is a bit more complex. Naming ------ As stated in "Dw.txt", the prefix "p_Dw_" is used for functions used within the whole Dw module, but not outside. Typically, these are protected functions, which are called by inherited classes. E.g., a specific widget will have to call p_Dw_widget_queue_resize(), but this function should not called outside from Dw. The "Dw_" prefix is not only used for functions, which use is restricted to a sub-module, but also for functions used only within the core of Dw. The distinction between both can simply made, whether the function is declared as static or not. The core of Dw consists of the modules Dw_widget and Dw_render, anything else is inherited from these two modules (widgets, platforms, views). As an example, Dw_gtk_viewport_queue_resize() is only called in the Dw_widget module, it should not be called by a widget implementation. Overview ======== Rendering of Dw is done in a way resembling the model-view pattern, at least formally. Actually, the counterpart of the model, the layout (DwRenderLayout), does a bit more than a typical model, namely calculating the layout, and the views do a bit less than a typical view, i.e. only the actual drawing. Additionally, there is a structure representing common properties of the platform, views generally work only together with one specific platform. A platform is typically related to the underlying UI toolkit, but other uses may be thought of. The general structure looks like this: 1 +------------------+ .---->| DwRenderPlatform | +----------------+ 1 / +------------------+ | DwRenderLayout |--< ^ 1 +----------------+ \ | * 1 ^ | 1 \ +--------------+ | | `---->| DwRenderView | | | * +--------------+ | | layout | | toplevel_dw | | * | V 0..1 +----------+ 0..1 | DwWidget |--. parent +----------+ | * | | `-------' (children) (This is a bit simplified, actually, only DwContainer has child widgets, and the relation is only defined in an abstract way). DwRenderLayout and DwWidget (including sub classes) are platform and view independant, i.e., it should be possible to adopt them into another platform, after suitable implementations of DwRenderPlatform and DwRenderView have been developed. (The only exception is DwEmbedGtk, a widget which, in the old design, embeds Gtk+ widgets into the DwWidget tree. This will still remain a platform specific widget, see notes in the section "UI Widgets". Furthermore, this design does not yet cover DwStyle, which is still bound to the Gtk+ platform. It may be simply replaced for other platforms, but this will lose the possibility to have multiple implementations of DwRenderPlatform running within the same program. The same aplies to DwTooltip.) This new design helps to archieve two important goals: 1. Abstraction of the actual rendering. Currently, we only have the normal viewport, but a list of planned implementations can be found below in this text. 2. It makes different views of the same document simple, e.g. the normal viewport and the preview window. 3. It makes portability simpler. Vieports are handles by this design, but only in a rather abstract way, and may be removed completely, i.e., they will be only part of specific view implementations. This also means, that the distinction between world coordinates and viewport coordinates vanishes from most parts of Dw. World ===== The world is the whole area, in which content is rendered. For a view without a viewport, this is the whole itself. A view with a viewport provides a way, so that the user may see some part of the world within a (in most cases) smaller window, the viewport. The world may have a base line, so that the worls size is described by three numbers, width, ascent and descent. Any view must implement the method set_world_size(), which is called, whenever the world size changes. For viewports, this will change scroll bars etc., for views without viewport, this will normally change the size of the view itself. (Although on this level, "view size" is not defined. This should not be confused with the viewport size!) Drawing ======= A view must implement several drawing methods, which work on the whole world. If it is neccesary to convert them (e.g. in DwGtkViewport), this is done in a way fully transparent to DwWidget and DwRenderingLayout, instead, this is done by the view implementation. There exist following situations: - A view gets an expose event: It will delegate this to the rendering layout, which will then pass it to the widgets, with the view as a parameter. Eventually, the widgets will call drawing methods of the view. - A widget requests a redraw: In this case, the widget will delegate this to the layout. The drawing requests are queued, and compressed. in an this idle function, DwWidget::draw is called for the toplevel widget, for each view. (This is still something to consider, the queueing may be moved again into the view implementations.) If the draw method of a widget is implemented in a way that it may draw outside of the widget's allocation, it should draw into a clipping view. A clipping view is a rendering view related to the actual rendering view, which guarantees that the parts drawn outside are discarded. At the end, the clipping view is merged into the actual view. Sample code for widget DwFoo: void Dw_foo_draw (DwWidget *widget, DwRenderView *view, DwRectangle *area) { DwRenderView *clip_view; /* 1. Create a clipping view. */ clip_view = p_Dw_render_view_get_clipping_view (view, widget->allocation.x, widget->allocation.y, widget->allocation.width DW_WIDGET_HEIGHT (widget)); /* 2. Draw into clip_view. */ Dw_render_view_do_some_drawing (clip_view, ...); /* 3. Draw the children, they receive the clipping view as argument. */ for () { if (p_Dw_widget_intersect (button->child, area, &child_area)) p_Dw_widget_draw (child, clip_view, child_area); } p_Dw_render_view_merge_clipping_view (view, clip_view); } A drawing process is always embedded into calls of DwRenderView::start_drawing() and DwRenderView::finish_drawing(). An implementation of this are backing pixmaps, to prevent flickering. Viewports and Scrolling Positions ================================= Although the design implies that the usage of viewports should be fully transparent to the Dw_render module, this cannot be fully archived, for the following reasons: 1. Some features, which are used on the level of DwWidget, e.g. anchors, refer to scrolling positions. 2. Some code in DwWidget is currently tightly bound to viewports. This may be change, by delegating some of this functionality to viewports, and defining scrolling positions in a more abstract way. Therefor, DwRenderLayout keeps track of the viewport size, the viewport position, and even the thickness of the scrollbars (or, generally, "viewport marker"), they are relevant, see below. These sizes are always equal in all views. However, a given view may not use viewports at all, and there may be the case, that no view related to a layout uses viewports, in this case, the viewport size is not defined at all. Unlike world sized, viewports are not considered to have a base line, i.e., they are described by only two numbers, width and height. Viewport Size ------------- All viewport sizes and positions are the same in all views, which uses viewports. There are two cases, in which the viewport size changes: 1. As an reaction on a user event, e.g. when the user changes the window size. In this case, the affected view delegates this change to the layout, by calling p_Dw_render_layout_vieport_size_changed(). All other views are told about this, by calling DwRenderView::set_viewport_size(). 2. The viewport size may also depend on the visibility of UI widgets, which depend on the world size, e.g scrollbars, generally called "viewport markers". This is described in an own section. After the creation of the layout, the viewport size is undefined. When a view is attached to a layout, and this view is already to be able to define its viewport size, it may already call p_Dw_render_layout_vieport_size_changed() within the implementation of set_layout. If not, it may do this, as soon as the viewport size gets known. Each call of p_Dw_render_layout_vieport_size_changed() will change the viewport size of all other views, which use viewports, so this function has to be called with care. If there is no view attached, which used viewports, the viewport size remains undefined. Viewport Markers ---------------- Viewport markers are UI widgets, which display to the user the relation of the world size and the widget size. Most commonly, scrollbars will be used. When they are not needed, they are hidden, as in the following figure: +--------------------+ | This is only a | | very short text. | | | | | | | +--------------------+ but shown, as soon as the world size exceeds the viewport size: +------------------+-+ | In this example, |^| | there is some |#| | more text, so |#| | that the | | | vertical |v| +------------------+-+ A view using viewports must provide, how large the differences are. Generally, there are four cases, from the combinations of whether the world width is smaller (-) or greater (+) than the viewport width, and whether the world height is smaller (-) or greater (+) than the viewport height. So there are eight numbers, the horizontal difference dh, and the vertical difference dv, for the cases --, -+, +-, and ++. For scrollbars, the values are rather simple: - dh is 0 for the cases -- and -+, and the thickness of the vertical scrollbar in the cases +- and ++. - dv is 0 for the cases -- and +-, and the thickness of the horizontal scrollbar in the cases -+ and ++. For any view implementation, the following rules must be fullfeeded (dx means either dh or dv): - dx(-+) >= d(--) - dx(++) >= d(+-) - dx(+-) >= d(--) - dx(++) >= d(-+) In short, when smaller world dimensions (-) grow (switch to +), the differences may not become less. The sizes of viewport markers are part of the viewport size, the method DwRenderView::set_viewport_size() has four parameters: - two for the size of the viewport, *including* the markers (i.e. the markers never change the size), and - two, which denote the differences between the above and the actual viewport size, caused by the markers. If a value of these is 0, the respective marker is hidden, if it is greater than 0, it is shown. In the latter case, the maximun is calculated, and passed to all views. (Actually, the decision, whether the markers are visible or not, is a bit more complicated, since the vieport size also defines the size hints for the topmost widget, which may affect the widget size, and so the world size. Handling this problem is done within DwRenderLayout, look at the comments there.) Scrolling Positions ------------------- The scrolling position is the world position at the upper left corner of the viewport. Views using viewports must 1. change this value on request, and 2. tell other changes to the layout, e.g. caused by user events. Applications of scrolling positions (anchors, test search etc.) are handled by the layout, in a way fully transparent to the views. An Example with Nested Views ============================ The following example refers to graphical plugins, which are not yet realized (I have some working proof-of-concept code), but which will most likely follow the design, which is here shortly described, as needed to understand the example (since there is no specification of graphical plugins yet). I included this, to demonstrate, how nested layouts can be used. Graphical plugins will communicate with dillo via two protocols, dpi1 (for anything not directly related to rendering), and a new protocol, which is an extension of the XEmbed protocol (see somewhere at http://freedesktop.org, this is the protocol, which GtkPlug and GtkSocket use). Within dillo, a socket window will be created, which window id will be (via dpi1?) passed to the plugin. The plugin will then create a plugin window, in which the contents of a web recource is shown, which dillo cannot show natively. XEmbed will be extended, so that the plugins may make use of the extensions, which the Dw size model adds to the XEmbed size model. Such a plugin behaves (on an abstract level) like a DwWidget, so following extensions are necessary: - a plugin may not simply have a size, but instead distinguish between ascent and descent, - a plugin may return width extremes, and - it is possible to send size hints to the plugin. Within Dw, the socket will be realized by a special plugin, DwSocketGtk (or a respective widget for other platforms), which is a sub class of DwEmbedGtk, and embeds another UI widget, which will provide the socket window (for Gtk+, either GtkSocket, or a sub class of this). The plugins may be implemented independently of Dw, they either do not support the extensions to XEmbed (then, the respective features behave in a default way), or they may contain there own implementation. However, Dw may be extracted from dillo, to become an own library. A plugin using this library may then use a special type of view, GtkDwFlatView (for plugins based on Gtk+, actually, the UI toolkit for dillo and the plugin must not be the same, since the protocol will be UI toolkit independant.) This will lead to a structure like this: top_layout:DwRenderLayout ----- top_page:DwPage / \ | :GtkDwPlatform top_view:GtkDwView `- table:DwTable | `- cell:DwTableCell | `- socket:DwSocketGtk | DILLO gtk_socket:GtkSocket . - - - - - - - - - - - - - - - - - - - - - - - - - - - - - . (extension of . XEmbed) PLUGIN plugin_layout:DwRenderLayout ----- dw_flat_view:GtkDwFlatView \ `------- foo:DwFoo GtkDwFlatView is both an extension of GtkSocket, as well as an implementation of DwRenderView. This case must be equivalent to the case, that the widget "foo" is a direct child of the widget "cell", since all objects between them only delegate all functionality. Size hints must so sent as in the following scenario: 1. The view "top_view" receieves an event, which will change the viewport size, and delegates this to the layout. 2. The layout will make size hints of this viewport size, i.e., it calls the set_* methods of the widget "top_page". 3. DwPage::set_* will queue a resize request, which is delegated to the layout. 4. Handling the resize request is done within a idle loop. Here, size_request is called for the widget "top_page", to determine the preferred size. 5. DwPage::size_request will depend on the size hints, especially, it will use the hinted size to determine the word wrap width. Within DwPage::size_request, size hints will be sent to the child widgets. The size hints sent to the children are those sent to the page, minus the respective border widths (margins, borders, paddings). (How the ascent and descent hints should generally be delegated to the children, is still a matter of consideration, and may change.) 6. DwTable::size_request, which is called within this context, has a different way to delegate the size hints to the children: ascent and descent are handled the same way, but the widths are calculated from the column widths, which are calculated within DwTable::size_request. 7. Via the widget "cell", finally the widget "socket" receives appropriate size hints. These must be (equally) delegated to the widget "foo", this is done in a way described in the following steps. 8. The size hints are transmitted via the protocol extending XEmbed. The Gtk+ widget "dw_flat_view" receives them (via the widget "gtk_socket"), and delegates them to the respective layout "plugin_layout". (TODO: How is this done? What has to be done is to send the size hints to the toplevel widget, but a view has no access to it.) 9. The layout "plugin_layout" will then delegate this (what ever "this" means), as size hints, to the widget "foo". 10. The widget "foo" will queue a size_request, which is delegated to the layout. In the idle function handling this request, the recalculation of the widget size will change the world size, which is delegated to the view "dw_flat_view". 11. For GtkDwFlatView, the world size is the view size. For this reason, it changes its size, to fit the world size. This size is send to the DwSocket, which will then request a size request. UI Widgets ========== A note before: This chapter refers often to Gtk+ as base UI toolkit, just to make naming simpler, For other UI toolkits, the same, mostly only with other names, applies. In some cases, it is necessary to embed widgets from the UI toolkit into the view, e.g. to realize HTML forms in dillo. The Dw_render module provides no interface at all, instead, this is completely left to the platform implementations. Below, two design approaches are discussed, which have following in common: 1. There is a special Dw widget, which embeds something related to the UI widgets (below called DwEmbedGtk). 2. This Dw widget, the platform, and the views are tightly bound, beyond the methods, which the respective interfaces provide. The Dw widget can simply refer to the platform by (DwWidget::render_layout->platform, and the platform may refer to the views, when DwPlatform::attach_view and DwPlatform::detach_view are implemented. General Problems ---------------- For most UI toolkits, there have to be multiple instances of widgets in different views. Furthermore, there may be, on the same platform, different classes for the same widgets. Consider this simple example: :DwRenderPlatform ------------------- :DwPage / \ | ,----' `----. dw:DwEmbedGtk / \ view1:GtkDwViewport view2:GtkDwViewport | | gtk1:GtkButton gtk2:GtkButton This structure represents a page with one button in it, which is, in the DwWidget tree, represented by the DwEmbedGtk "dw", and, in the views "view1" and "view2", by the Gtk+ widgets "gtk1" and "gtk2", respectively. The old approach, where the UI widget (in this case GtkButton) is not directly applicable, since there must be multiple instances of this UI widget. Here is a more complex example: :DwRenderPlatform ------------------- :DwPage / \ | ,----' `----. dw:DwEmbedGtk / \ view1:GtkDwFooView view2:GtkDwBarView | | gtk1:GtkFooButton gtk2:GtkBarButton In this case, the different views GtkDwFooView and GtkDwBarView deal with different classes for buttons, GtkFooButton and GtkBarButton. Simple Approach --------------- Within dillo, the following limitations are reasonable: 1. There is only one view instance, which actually needs UI widgets (GtkDwViewport for Gtk+). There may be multiple views, but these additional views do not need instances of widgets, e.g. in the preview, a UI widget is simply shown as a grey box. 2. There is only one type of UI widget, i.e. normal Gtk+ widgets. Because of these limitations, the implementation of UI widgets is currently quite simple in dillo. As before, the widget DwEmbedGtk refers to an instance of a concrete Gtk+ widget. This Gtk+ widget is told to the platform, of which DwEmbedGtk knows, that it is an instance of GtkDwPlatform. GtkDwPlatform will add this Gtk+ widget to the single view, which needs it, and DwEmbedGtk will be responsible of allocating etc. of the Gtk+ widget. Advanced Approach ----------------- This is a short overview of an approach, which will overcome the limitations of the simple approach, but with the costs of a greater complexity. It will be detailed, in the case, that it is implemented. +----­-------+ factory +---------------------+ | DwEmbedGtk | ---------> | DwGtkWidgetFactory | +------------+ 1 1 +---------------------+ | create_foo_widget() | | create_bar_widget() | +---------------------+ . . . /_\ /_\ /_\ | | | - - - - - - - - - - - - - - - - - . | | +---------------------+ +---------------------+ | | DwGtkButtonFactory | | DwGtkListFactoty | (other ...) +---------------------+ +---------------------+ | create_foo_widget() | | create_foo_widget() | | create_bar_widget() | | create_bar_widget() | +---------------------+ | add_list_item(...) | +---------------------+ DwEmbedGtk refers to a factory, which creates the UI widgets for each view. For each general widget type (e.g. button, list, ...), there is an implementation of this interface. This interface DwGtkWidgetFactory contains different method for different views. E.g., when a button is shown, and so DwEmbedGtk refers to a DwGtkButtonFactoty, GtkDwFooView may need a GtkFooButton, which is created by create_foo_widget(), while GtkDwBarView may call create_bar_widget(), to get a GtkBarButton. (This design still makes it hard to add new views, which then need, say, GtkBazButtons.) The concrete factories may contain additional methods, in the diagram above, DwGtkListFactory must provide a function to add items to the lists. This method will add a item to all list widget, which were created by this factory. More Ideas for View Implementations =================================== Preview Window -------------- The status bar gets a new button, on which the user may click to get a small image of the small page, in which he can select the viewport position by dragging a rubber-band rectangle. The actual scaling factor will probably depend on the aspect ratio ofthe page. Notice that in this case, the viewport and the preview are related, while typically, the rendering windows are independent of each other. Graphical Plugin ---------------- See section "An Example with Nested Views" for explanations.