1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
|
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 and Imgbuf.)
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 (<all relevant children>) {
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:
:DwRenderLayout ------------------- :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:
:DwRenderLayout ------------------- :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 of the page.
(This has already been implemented for Gtk+, as GtkDwPreview and
GtkDwPreviewButton.)
Graphical Plugin
----------------
See section "An Example with Nested Views" for explanations.
======================================================================
Alternative design for handling UI widgets:
- There is a platform *independant* widget, the platform dependencies
are within the factories.
DwEmbedUI::draw
-> DwRenderView::draw_ui_widget(..., factory)
Implementation of "draw_ui_widget" in a preview view:
-> draw some gray boxes etc.
Implementation of "draw_ui_widget" in a normal "bar" view (for platform "foo"):
-> determine that is is a factory for a "baz" widget?
-> (BarFactory*)factory->get_foo_bar_widget(this)
This method either returns an already created widget, or creates one
with the current state of the factory. "This" is taken as a key.
----
| general | widget specific
----------------------+------------------+------------------------
platform independant | nothing? | e.g. adding list items
----------------------+------------------+------------------------
platform dependant | e.g. creating a | ???
| widget, or |
| informations |
| about drawing in |
| a preview |
======================================================================
Abstraction of DwStyle
----------------------
structures needed:
|