summaryrefslogtreecommitdiff
path: root/examples/script/context2d/doc/src/context2d.qdoc
blob: 73259fe2aa94a9494646bdeae1991b0e47572b33 (plain)
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
/****************************************************************************
**
** Copyright (C) 2015 The Qt Company Ltd.
** Contact: http://www.qt.io/licensing/
**
** This file is part of the documentation of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:FDL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see http://www.qt.io/terms-conditions. For further
** information use the contact form at http://www.qt.io/contact-us.
**
** GNU Free Documentation License Usage
** Alternatively, this file may be used under the terms of the GNU Free
** Documentation License version 1.3 as published by the Free Software
** Foundation and appearing in the file included in the packaging of
** this file. Please review the following information to ensure
** the GNU Free Documentation License version 1.3 requirements
** will be met: http://www.gnu.org/copyleft/fdl.html.
** $QT_END_LICENSE$
**
****************************************************************************/

/*!
    \example script/context2d
    \title Context2D Example

    This Qt Script example is an implementation of the Context2D API.

    \image context2d-example.png

    Context2D is part of the specification for the HTML \c{<canvas>}
    element. It can be used to draw graphics via scripting. A good
    resource for learning more about the HTML \c{<canvas>} element is
    the \l{http://developer.mozilla.org/en/docs/HTML:Canvas}{Mozilla Developer Center}.

    \section1 Using The HTML Canvas Element in a Web Browser

    First, let's look at how the \c{<canvas>} element is typically
    used in a web browser. The following HTML snippet defines a
    canvas of size 400x400 pixels with id \c{mycanvas}:

    \code
    <canvas width="400" height="400" id="mycanvas">Fallback content goes here.</canvas>
    \endcode

    To draw on the canvas, we must first obtain a reference to the
    DOM element corresponding to the \c{<canvas>} tag and then call
    the element's getContext() function. The resulting object
    implements the Context2D API that we use to draw.

    \code
    <script>
    var canvas = document.getElementById("mycanvas");
    var ctx = canvas.getContext("2d");

    // Draw a face
    ctx.beginPath();
    ctx.arc(75,75,50,0,Math.PI*2,true); // Outer circle
    ctx.moveTo(110,75);
    ctx.arc(75,75,35,0,Math.PI,false);   // Mouth
    ctx.moveTo(65,65);
    ctx.arc(60,65,5,0,Math.PI*2,true);  // Left eye
    ctx.moveTo(95,65);
    ctx.arc(90,65,5,0,Math.PI*2,true);  // Right eye
    ctx.stroke();
    </script>
    \endcode

    When the page is rendered by a browser that supports the
    \c{<canvas>} tag, this would be the result:

    \image context2d-example-smileysmile.png

    \section1 Using Qt Script to script a Canvas

    The goal of this example is to be able to evaluate scripts
    that use the Context2D API, and render the results. Basic
    interaction (mouse, keyboard) should also be supported.
    In other words, we want to present scripts with an execution
    environment that very much resembles that of a web browser. Of
    course, our environment is only a small subset of what a browser
    provides; i.e. we don't provide a full DOM API, only what is
    needed to run "self-contained" Context2D scripts (i.e. scripts
    that don't depend on other parts of the DOM document).

    Our "Context2D-browser" is set up through the following steps:
    \list
    \li Create an Environment.
    \li Create a Context2D, and a QContext2DCanvas widget to render it.
    \li Add the canvas object to the environment; this will enable
       scripts to obtain a reference to it.
    \li Evaluate scripts in the environment.
    \endlist

    Once a script has been evaluated, the application handles any
    timer events and input events that occur subsequently
    (i.e. forwards events to their associated script targets).

    \section1 The Context2D Class

    The "heart" of this example is the Context2D C++ class that implements
    the drawing API. Its interface is defined in terms of properties
    and slots. Note that this class isn't tied to Qt Script in any
    way.

    \snippet script/context2d/context2d.h 0

    The properties define various aspects of the Context2D
    configuration.
    
    \snippet script/context2d/context2d.h 1

    The slots define the operations that can be performed.
    
    \snippet script/context2d/context2d.h 2

    The changed() signal is emitted when the contents of the drawing
    area has changed, so that clients associated with the Context2D
    object (i.e. the canvas widget that renders it) are notified.

    \section2 Implementation

    Conveniently enough, the concepts, data structures and operations
    of the Context2D API map more or less directly to Qt's painting
    API. Conceptually, all we have to do is initialize a QPainter
    according to the Context2D properties, and use functions like
    QPainter::strokePath() to do the painting. Painting is done on a
    QImage.

    \snippet script/context2d/context2d.cpp 0

    The property accessors and most of the slots manipulate the
    internal Context2D state in some way. For the \c{lineCap}
    property, Context2D uses a string representation; we therefore
    have to map it from/to a Qt::PenCapStyle. The \c{lineJoin}
    property is handled in the same fashion. All the property setters
    also set a \e{dirty flag} for the property; this is used to
    decide which aspects of the QPainter that need to be updated
    before doing the next painting operation.

    \snippet script/context2d/context2d.cpp 3

    The implementation of the \c{fillStyle} property is interesting,
    since the value can be either a string or a \c{CanvasGradient}.
    We handle this by having the property be of type QVariant,
    and check the actual type of the value to see how to handle the
    write.

    \snippet script/context2d/context2d.cpp 1

    Context2D does not have a concept of a paint event; painting
    operations can happen at any time. We would like to be efficient,
    and not have to call QPainter::begin() and QPainter::end() for
    every painting operation, since typically many painting operations
    will follow in quick succession. The implementations of the
    painting operations use a helper function, beginPainting(), that
    activates the QPainter if it isn't active already, and updates
    the state of the QPainter (brush, pen, etc.) so that it reflects
    the current Context2D state.

    \snippet script/context2d/context2d.cpp 2

    The implementation of each painting operation ends by calling
    scheduleChange(), which will post a zero-timer event if one is
    not already pending. When the application returns to the event
    loop later (presumably after all the drawing operations have
    finished), the timer will trigger, QPainter::end() will be
    called, and the changed() signal is emitted with the new
    image as argument. The net effect is that there will typically
    be only a single (QPainter::begin(), QPainter::end()) pair
    executed for the full sequence of painting operations.

    \section1 The Canvas Widget

    \snippet script/context2d/qcontext2dcanvas.h 0

    The QContext2DCanvas class provides a widget that renders
    the contents of a Context2D object. It also provides a
    minimal scripting API, most notably the getContext() function.

    \snippet script/context2d/qcontext2dcanvas.cpp 3

    The constructor connects to the changed() signal of the
    Context2D object, so that the widget can update itself
    when it needs to do so. Mouse tracking is enabled so that
    mouse move events will be received even when no mouse
    buttons are depressed.

    \snippet script/context2d/qcontext2dcanvas.cpp 0

    The getContext() function asks the environment to wrap the
    Context2D object; the resulting proxy object makes the
    Context2D API available to scripts.

    \snippet script/context2d/qcontext2dcanvas.cpp 1

    The paintEvent() function simply paints the contents that
    was last received from the Context2D object.

    \snippet script/context2d/qcontext2dcanvas.cpp 2

    The canvas widget reimplements mouse and key event handlers, and
    forwards these events to the scripting environment. The
    environment will take care of delivering the event to the proper
    script target, if any.

    \section1 The Environment

    \snippet script/context2d/environment.h 0

    The Environment class provides a scripting environment where a
    Canvas C++ object can be registered, looked up by ID (name),
    and where scripts can be evaluated. The environment has a
    \c{document} property, just like the scripting environment of a
    web browser, so that scripts can call
    \c{document.getElementById()} to obtain a reference to a canvas.

    \snippet script/context2d/environment.h 1

    The Environment class provides the timer attributes of the DOM
    Window Object interface. This enables us to support scripts that
    do animation, for example.
    
    \snippet script/context2d/environment.h 2

    The scriptError() signal is emitted when evaluation of a script
    causes a script exception. For example, if a mouse press handler
    or timeout handler causes an exception, the environment's client(s)
    will be notified of this and can report the error.

    \snippet script/context2d/environment.cpp 0

    The constructor initializes the environment. First it creates
    the QScriptEngine that will be used to evaluate scripts. It
    creates the Document object that provides the getElementById()
    function. Note that the QScriptEngine::ExcludeSuperClassContents
    flag is specified to avoid the wrapper objects from exposing properties
    and methods inherited from QObject. Next, the environment wraps
    a pointer to \e{itself}; this is to prepare for setting this object
    as the script engine's Global Object. The properties of the standard
    Global Object are copied, so that these will also be available in
    our custom Global Object. We also create two self-references to the
    object; again, this is to provide a minimal level of compabilitity
    with the scripting environment that web browsers provide.

    \snippet script/context2d/environment.cpp 5

    The addCanvas() function adds the given canvas to the list of
    registered canvas objects. The canvasByName() function looks up
    a canvas by QObject::objectName(). This function is used to
    implement the \c{document.getElementById()} script function.

    \snippet script/context2d/environment.cpp 1

    The setInterval() and clearInterval() implementations use a QHash
    to map from timer ID to the QScriptValue that holds the expression
    to evaluate when the timer is triggered. A helper function,
    maybeEmitScriptError(), is called after invoking the script handler;
    it will emit the scriptError() signal if the script engine has an
    uncaught exception.

    \snippet script/context2d/environment.cpp 2

    The toWrapper() functions creates a QScriptValue that wraps the
    given QObject. Note that the QScriptEngine::PreferExistingWrapperObject
    flag is specified; this guarantees that a single, unique wrapper
    object will be returned, even if toWrapper() is called several times
    with the same argument. This is important, since it is possible that
    a script can set new properties on the resulting wrapper object (e.g.
    event handlers like \c{onmousedown}), and we want these to persist.

    \snippet script/context2d/environment.cpp 3

    The handleEvent() function determines if there exists a handler
    for the given event in the environment, and if so, invokes that
    handler. Since the script expects a DOM event, the Qt C++ event
    must be converted to a DOM event before it is passed to the
    script. This mapping is relatively straightforward, but again,
    we only implement a subset of the full DOM API; just enough to
    get most scripts to work.

    \snippet script/context2d/environment.cpp 4

    The newFakeDomEvent() function is a helper function that creates
    a new script object and initializes it with default values for
    the attributes defined in the DOM Event and DOM UIEvent
    interfaces.

    \snippet script/context2d/environment.h 3

    The Document class defines two slots that become available to
    scripts: getElementById() and getElementsByTagName().
    When the tag name is "canvas", getElementsByTagName() will
    return a list of all canvas objects that are registered in
    the environment.

    \section1 The Application Window

    \snippet script/context2d/window.cpp 0

    The Window constructor creates an Environment object and
    connects to its scriptError() signal. It then creates a
    Context2D object, and a QContext2DCanvas widget to hold it.
    The canvas widget is given the name \c{tutorial}, and added to the
    environment; scripts can access the canvas by e.g.
    \c{document.getElementById('tutorial')}.

    \snippet script/context2d/window.cpp 1

    The window contains a list widget that is populated with
    available scripts (read from a \c{scripts/} folder).

    \snippet script/context2d/window.cpp 2

    When an item is selected, the corresponding script is
    evaluated in the environment.

    \snippet script/context2d/window.cpp 3

    When the "Run in Debugger" button is clicked, the Qt Script debugger will
    automatically be invoked when the first statement of the script is
    reached. This enables the user to inspect the scripting environment and
    control further execution of the script; e.g. he can single-step through
    the script and/or set breakpoints. It is also possible to enter script
    statements in the debugger's console widget, e.g. to perform custom
    Context2D drawing operations, interactively.

    \snippet script/context2d/window.cpp 4

    If the evaluation of a script causes an uncaught exception, the Qt Script
    debugger will automatically be invoked; this enables the user to get an
    idea of what went wrong.

*/