0
|
1 // Written in the D programming language
|
|
2 // www.digitalmars.com/d/
|
|
3
|
|
4 /*
|
|
5 * The contents of this file are subject to the Mozilla Public License Version
|
|
6 * 1.1 (the "License"); you may not use this file except in compliance with
|
|
7 * the License. You may obtain a copy of the License at
|
|
8 * http://www.mozilla.org/MPL/
|
|
9 *
|
|
10 * Software distributed under the License is distributed on an "AS IS" basis,
|
|
11 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
|
12 * for the specific language governing rights and limitations under the
|
|
13 * License.
|
|
14 *
|
|
15 * The Original Code is the Dynamin library.
|
|
16 *
|
|
17 * The Initial Developer of the Original Code is Jordan Miner.
|
|
18 * Portions created by the Initial Developer are Copyright (C) 2006-2009
|
|
19 * the Initial Developer. All Rights Reserved.
|
|
20 *
|
|
21 * Contributor(s):
|
|
22 * Jordan Miner <jminer7@gmail.com>
|
|
23 *
|
|
24 */
|
|
25
|
|
26 module dynamin.gui.control;
|
|
27
|
|
28 import dynamin.all_core;
|
|
29 import dynamin.all_painting;
|
|
30 import dynamin.gui.container;
|
|
31 import dynamin.gui.events;
|
|
32 import dynamin.gui.window;
|
|
33 import dynamin.gui.cursor;
|
|
34 import tango.io.Stdout;
|
|
35
|
|
36 Control hotControl;
|
|
37 // the hot control is the one the mouse is over
|
|
38 package void setHotControl(Control c) {
|
|
39 if(c !is hotControl) {
|
|
40 if(hotControl)
|
|
41 hotControl.mouseLeft(new EventArgs);
|
|
42 hotControl = c;
|
|
43 if(c)
|
|
44 c.mouseEntered(new EventArgs);
|
|
45 }
|
|
46 }
|
|
47 package Control getHotControl() { return hotControl; }
|
|
48 Control captorControl;
|
|
49 package void setCaptorControl(Control c) {
|
|
50 captorControl = c;
|
|
51 }
|
|
52 package Control getCaptorControl() { return captorControl; }
|
|
53
|
|
54 /**
|
|
55 * The painting event is an exception to the rule that added handlers are called
|
|
56 * before whenPainting. The painting event is far more useful since
|
|
57 * added handles are called after the control's whenPainting has finished.
|
|
58 * Otherwise, anything handlers painted would likely be painted over when the
|
|
59 * control painted.
|
|
60 */
|
|
61 class Control {
|
|
62 protected:
|
|
63 bool _visible;
|
|
64 bool _focusable;
|
|
65 bool _focused;
|
|
66 package Point _location;
|
|
67 package Size _size;
|
|
68 string _text;
|
|
69 Color _backColor;
|
|
70 Color _foreColor;
|
|
71 Font _font;
|
|
72 Cursor _cursor;
|
|
73 Container _parent;
|
|
74 bool _elasticX, _elasticY;
|
|
75 public:
|
|
76 /// This event occurs after the control has been moved.
|
|
77 Event!() moved;
|
|
78 /// Override this method in a subclass to handle the Moved event.
|
|
79 protected void whenMoved(EventArgs e) { }
|
|
80
|
|
81 /// This event occurs after the control has been resized.
|
|
82 Event!() resized;
|
|
83 /// Override this method in a subclass to handle the Resized event.
|
|
84 protected void whenResized(EventArgs e) { }
|
|
85
|
|
86 /// This event occurs after the mouse has entered the control.
|
|
87 Event!() mouseEntered;
|
|
88 /// Override this method in a subclass to handle the MouseEntered event.
|
|
89 protected void whenMouseEntered(EventArgs e) { }
|
|
90
|
|
91 /// This event occurs after the mouse has left the control.
|
|
92 Event!() mouseLeft;
|
|
93 /// Override this method in a subclass to handle the MouseLeft event.
|
|
94 protected void whenMouseLeft(EventArgs e) { }
|
|
95
|
|
96 /// This event occurs after a mouse button is pressed.
|
|
97 Event!(MouseEventArgs) mouseDown;
|
|
98 /// Override this method in a subclass to handle the MouseDown event.
|
|
99 protected void whenMouseDown(MouseEventArgs e) { }
|
|
100
|
|
101 /// This event occurs after a mouse button is released.
|
|
102 Event!(MouseEventArgs) mouseUp;
|
|
103 /// Override this method in a subclass to handle the MouseUp event.
|
|
104 protected void whenMouseUp(MouseEventArgs e) { }
|
|
105
|
|
106 /// This event occurs after the mouse has been moved.
|
|
107 Event!(MouseEventArgs) mouseMoved;
|
|
108 /// Override this method in a subclass to handle the MouseMoved event.
|
|
109 protected void whenMouseMoved(MouseEventArgs e) { }
|
|
110
|
|
111 /// This event occurs after the mouse has been moved.
|
|
112 Event!(MouseEventArgs) mouseDragged;
|
|
113 /// Override this method in a subclass to handle the MouseMoved event.
|
|
114 protected void whenMouseDragged(MouseEventArgs e) { }
|
|
115
|
|
116 /// This event occurs after the mouse wheel has been turned.
|
|
117 Event!(MouseTurnedEventArgs) mouseTurned;
|
|
118 /// Override this method in a subclass to handle the MouseTurned event.
|
|
119 protected void whenMouseTurned(MouseTurnedEventArgs e) { }
|
|
120
|
|
121 /// This event occurs after a key is pressed.
|
|
122 Event!(KeyEventArgs) keyDown;
|
|
123 /// Override this method in a subclass to handle the KeyDown event.
|
|
124 protected void whenKeyDown(KeyEventArgs e) { }
|
|
125
|
|
126 /// This event occurs after a character key is pressed.
|
|
127 Event!(KeyTypedEventArgs) keyTyped;
|
|
128 /// Override this method in a subclass to handle the KeyTyped event.
|
|
129 protected void whenKeyTyped(KeyTypedEventArgs e) { }
|
|
130
|
|
131 /// This event occurs after a key is released.
|
|
132 Event!(KeyEventArgs) keyUp;
|
|
133 /// Override this method in a subclass to handle the KeyUp event.
|
|
134 protected void whenKeyUp(KeyEventArgs e) { }
|
|
135
|
|
136 /// This event occurs when the control needs to be painted.
|
|
137 Event!(PaintingEventArgs) painting;
|
|
138 /// Override this method in a subclass to handle the painting event.
|
|
139 protected void whenPainting(PaintingEventArgs e) {
|
|
140 e.graphics.source = backColor;
|
|
141 e.graphics.paint();
|
|
142 }
|
|
143
|
|
144 protected void dispatchMouseEntered(EventArgs e) {
|
|
145 Cursor.setCurrent(this, _cursor);
|
|
146 mouseEntered.callHandlers(e);
|
|
147 mouseEntered.callMainHandler(e);
|
|
148 }
|
|
149 protected void dispatchMouseDown(MouseEventArgs e) {
|
|
150 setCaptorControl(this);
|
|
151 mouseDown.callHandlers(e);
|
|
152 mouseDown.callMainHandler(e);
|
|
153 }
|
|
154 protected void dispatchMouseUp(MouseEventArgs e) {
|
|
155 setCaptorControl(null);
|
|
156 mouseUp.callHandlers(e);
|
|
157 mouseUp.callMainHandler(e);
|
|
158 }
|
|
159 protected void dispatchMouseMoved(MouseEventArgs e) {
|
|
160 setHotControl(this);
|
|
161 mouseMoved.callHandlers(e);
|
|
162 mouseMoved.callMainHandler(e);
|
|
163 }
|
|
164 protected void dispatchMouseDragged(MouseEventArgs e) {
|
|
165 setHotControl(this);
|
|
166 mouseDragged.callHandlers(e);
|
|
167 mouseDragged.callMainHandler(e);
|
|
168 }
|
|
169 protected void dispatchMouseTurned(MouseTurnedEventArgs e) {
|
|
170 mouseTurned.callHandlers(e);
|
|
171 mouseTurned.callMainHandler(e);
|
|
172 if(!e.stopped && _parent)
|
|
173 _parent.mouseTurned(e);
|
|
174 }
|
|
175 protected void dispatchPainting(PaintingEventArgs e) {
|
|
176 e.graphics.save();
|
|
177 painting.callMainHandler(e);
|
|
178 e.graphics.restore();
|
|
179 e.graphics.save();
|
|
180 // TODO: every call to a handler should be wrapped in a save/restore
|
|
181 painting.callHandlers(e);
|
|
182 e.graphics.restore();
|
|
183 }
|
|
184 this() {
|
|
185 moved = new Event!()(&whenMoved);
|
|
186 resized = new Event!()(&whenResized);
|
|
187 mouseEntered = new Event!()(&whenMouseEntered, &dispatchMouseEntered);
|
|
188 mouseLeft = new Event!()(&whenMouseLeft);
|
|
189 mouseDown = new Event!(MouseEventArgs)(&whenMouseDown, &dispatchMouseDown);
|
|
190 mouseUp = new Event!(MouseEventArgs)(&whenMouseUp, &dispatchMouseUp);
|
|
191 mouseMoved = new Event!(MouseEventArgs)(&whenMouseMoved, &dispatchMouseMoved);
|
|
192 mouseDragged = new Event!(MouseEventArgs)(&whenMouseDragged, &dispatchMouseDragged);
|
|
193 mouseTurned = new Event!(MouseTurnedEventArgs)(&whenMouseTurned, &dispatchMouseTurned);
|
|
194 keyDown = new Event!(KeyEventArgs)(&whenKeyDown);
|
|
195 keyTyped = new Event!(KeyTypedEventArgs)(&whenKeyTyped);
|
|
196 keyUp = new Event!(KeyEventArgs)(&whenKeyUp);
|
|
197 painting = new Event!(PaintingEventArgs)(&whenPainting, &dispatchPainting);
|
|
198
|
|
199 _location = Point(0, 0);
|
|
200 _size = Size(100, 100);
|
|
201 _text = "";
|
|
202 _focusable = false;
|
|
203 _focused = false;
|
|
204 _visible = true;
|
|
205 _cursor = Cursor.Arrow;
|
|
206 _elasticX = false;
|
|
207 _elasticY = false;
|
|
208
|
|
209 // TODO: remove these when themes mature
|
|
210 _foreColor = Color.Black;
|
|
211 _font = new Font("Tahoma", 11);
|
|
212 }
|
|
213 protected Graphics quickCreateGraphics() {
|
|
214 if(_parent is null)
|
|
215 return null;
|
|
216 auto g = _parent.quickCreateGraphics();
|
|
217 g.translate(location);
|
|
218 return g;
|
|
219 }
|
|
220 /**
|
|
221 * Sets the specified Graphics' font and source to this control's font
|
|
222 * and foreground color. Also, clips to this control's rectangle.
|
|
223 */
|
|
224 void setupGraphics(Graphics g) {
|
|
225 g.rectangle(0, 0, width, height);
|
|
226 g.clip();
|
|
227 g.font = font;
|
|
228 g.source = foreColor;
|
|
229 }
|
|
230 /**
|
|
231 * Creates a Graphics, calls the specified delegate with it, and deletes
|
|
232 * it to release resources.
|
|
233 */
|
|
234 void withGraphics(void delegate(Graphics g) dg) {
|
|
235 auto g = quickCreateGraphics();
|
|
236 setupGraphics(g);
|
|
237 dg(g);
|
|
238 delete g;
|
|
239 }
|
|
240 /**
|
|
241 *
|
|
242 */
|
|
243 bool focused() {
|
|
244 return _focused;
|
|
245 }
|
|
246 /**
|
|
247 *
|
|
248 */
|
|
249 void focus() {
|
|
250 if(!_focusable)
|
|
251 return;
|
|
252 Control c = this;
|
|
253 while(c.parent)
|
|
254 c = c.parent;
|
|
255 if(auto win = cast(Window)c) {
|
|
256 if(win.focusedControl) {
|
|
257 win.focusedControl._focused = false;
|
|
258 win.focusedControl.repaint();
|
|
259 }
|
|
260 win.focusedControl = this;
|
|
261 _focused = true;
|
|
262 repaint();
|
|
263 }
|
|
264 }
|
|
265 /**
|
|
266 * Returns whether this control is on the screen. A control is
|
|
267 * on screen if one of its ancestors is a top level window. Whether or
|
|
268 * not the control is visible has no bearing on this value. If a control
|
|
269 * has no parent, then it is clearly not on the screen.
|
|
270 */
|
|
271 bool onScreen() {
|
|
272 return _parent && _parent.onScreen;
|
|
273 }
|
|
274 /**
|
|
275 * Gets the location of this control in screen coordinates. An exception is
|
|
276 * thrown if this control is not on the screen.
|
|
277 */
|
|
278 Point screenLocation() {
|
|
279 if(!_parent)
|
|
280 throw new Exception("control is not on screen");
|
|
281 return _parent.screenLocation + location;
|
|
282 }
|
|
283 /**
|
|
284 * Converts the specified point in content coordinates into screen
|
|
285 * coordinates. An exception is thrown if this control is not on the screen.
|
|
286 */
|
|
287 Point contentToScreen(Point pt) { // TODO: content?? even on Window??
|
|
288 if(!_parent)
|
|
289 throw new Exception("control is not on screen");
|
|
290 return _parent.contentToScreen(pt + location);
|
|
291 }
|
|
292 /**
|
|
293 * Converts the specified point in screen coordinates into content
|
|
294 * coordinates. An exception is thrown if this control is not on the screen.
|
|
295 */
|
|
296 Point screenToContent(Point pt) {
|
|
297 if(!_parent)
|
|
298 throw new Exception("control is not on screen");
|
|
299 return _parent.screenToContent(pt) - location; // TODO: borders
|
|
300 }
|
|
301 /**
|
|
302 * Converts the specified point in this control's content coordinates
|
|
303 * into the specified control's content coordinates. An exception is
|
|
304 * thrown if this control is not on the screen.
|
|
305 */
|
|
306 Point contentToContent(Point pt, Control c) {
|
|
307 return c.screenToContent(contentToScreen(pt));
|
|
308 }
|
|
309 /**
|
|
310 * Returns whether the specified point is inside this control.
|
|
311 */
|
|
312 bool contains(Point pt) {
|
|
313 return pt.x >= 0 && pt.y >= 0 && pt.x < width && pt.y < height;
|
|
314 }
|
|
315 /// ditto
|
|
316 bool contains(real x, real y) {
|
|
317 return contains(Point(x, y));
|
|
318 }
|
|
319 bool topLevel() { return false; }
|
|
320 // TODO: return NativeControl/Window?
|
|
321 Control getTopLevel() {
|
|
322 Control c = this;
|
|
323 while(c.parent)
|
|
324 c = c.parent;
|
|
325 return c.topLevel ? c : null;
|
|
326 }
|
|
327 /**
|
|
328 * Gets this control's parent.
|
|
329 */
|
|
330 Container parent() { return _parent; }
|
|
331 package void parent(Container c) {
|
|
332 _parent = c;
|
|
333 //ParentChanged(new EventArgs); // TODO: add event
|
|
334 }
|
|
335 /**
|
|
336 * Gets or sets whether is control is visible. The default is true, except on top-level windows.
|
|
337 */
|
|
338 //void visible(bool b) { visible = b; }
|
|
339 bool visible() { return _visible; }
|
|
340
|
|
341 /**
|
|
342 * Gets or sets the location of this control in its parent's content
|
|
343 * coordinates.
|
|
344 * Examples:
|
|
345 * -----
|
|
346 * control.location = [5, 35];
|
|
347 * control.location = Point(50, 50);
|
|
348 * -----
|
|
349 */
|
|
350 Point location() { return _location; }
|
|
351 /// ditto
|
|
352 void location(Point pt) {
|
|
353 if((cast(Window)_parent) !is null)
|
|
354 throw new Exception("cannot set location of a window's content");
|
|
355 pt.x = round(pt.x);
|
|
356 pt.y = round(pt.y);
|
|
357 repaint();
|
|
358 _location = pt;
|
|
359 repaint();
|
|
360 moved(new EventArgs);
|
|
361 }
|
|
362 /// ditto
|
|
363 void location(real[] pt) {
|
|
364 assert(pt.length == 2, "pt must be just an x and y");
|
|
365 location = Point(pt[0], pt[1]);
|
|
366 }
|
|
367
|
|
368 /**
|
|
369 * Gets or sets the size of this control.
|
|
370 * Examples:
|
|
371 * -----
|
|
372 * control.size = [75, 23];
|
|
373 * control.size = Size(80, 20);
|
|
374 * -----
|
|
375 */
|
|
376 Size size() { return _size; }
|
|
377 /// ditto
|
|
378 void size(Size newSize) {
|
|
379 if(newSize.width < 0)
|
|
380 newSize.width = 0;
|
|
381 if(newSize.height < 0)
|
|
382 newSize.height = 0;
|
|
383 newSize.width = round(newSize.width);
|
|
384 newSize.height = round(newSize.height);
|
|
385 repaint();
|
|
386 _size = newSize;
|
|
387 repaint();
|
|
388 resized(new EventArgs);
|
|
389 }
|
|
390 /// ditto
|
|
391 void size(real[] newSize) {
|
|
392 assert(newSize.length == 2, "size must be just a width and height");
|
|
393 size = Size(newSize[0], newSize[1]);
|
|
394 }
|
|
395
|
|
396 /**
|
|
397 * Gets the size at which this control looks the best. It is intended that
|
|
398 * the control not be made smaller than this size, and only be made larger
|
|
399 * if it is elastic, or if it needs to be aligned with other controls.
|
|
400 *
|
|
401 * This property should be overridden in subclasses to return a best size.
|
|
402 */
|
|
403 Size bestSize() { return Size(100, 100); }
|
|
404 /**
|
|
405 * Gets the distance from the top of this control to the baseline of
|
|
406 * the first line of this control's text. If this control does not have
|
|
407 * text, 0 may be returned.
|
|
408 */
|
|
409 int baseline() { return 0; }
|
|
410
|
|
411 /// Same as location.x
|
|
412 real x() { return location.x; }
|
|
413 /// Same as location.y
|
|
414 real y() { return location.y; }
|
|
415 /// Same as size.width
|
|
416 real width() { return size.width; }
|
|
417 /// Same as size.height
|
|
418 real height() { return size.height; }
|
|
419
|
|
420 /**
|
|
421 * Gets or sets whether this control is elastic horizontally or vertically.
|
|
422 * If a control is elastic, then it is intended to be made larger than its
|
|
423 * best size.
|
|
424 */
|
|
425 bool elasticX() { return _elasticX; }
|
|
426 /// ditto
|
|
427 void elasticX(bool b) { _elasticX = b; }
|
|
428 /// ditto
|
|
429 bool elasticY() { return _elasticY; }
|
|
430 /// ditto
|
|
431 void elasticY(bool b) { _elasticY = b; }
|
|
432 /**
|
|
433 * Gets or sets the text that this control shows.
|
|
434 */
|
|
435 string text() { return _text.dup; }
|
|
436 /// ditto
|
|
437 void text(string str) {
|
|
438 _text = str.dup;
|
|
439 repaint();
|
|
440 //TextChanged(EventArgs e) // TODO: add event
|
|
441 }
|
|
442
|
|
443 /**
|
|
444 * Gets or sets the background color of this control.
|
|
445 */
|
|
446 Color backColor() { return _backColor; }
|
|
447 /// ditto
|
|
448 void backColor(Color c) {
|
|
449 _backColor = c;
|
|
450 repaint();
|
|
451 }
|
|
452
|
|
453 /**
|
|
454 * Gets or sets the foreground color of this control.
|
|
455 */
|
|
456 Color foreColor() { return _foreColor; }
|
|
457 /// ditto
|
|
458 void foreColor(Color c) {
|
|
459 _foreColor = c;
|
|
460 repaint();
|
|
461 }
|
|
462
|
|
463 /**
|
|
464 * Gets or sets the font of this control uses to display text. A value of
|
|
465 * null means that the font is unset. When the font is null, the
|
|
466 * current theme's font is used. The default is null.
|
|
467 */
|
|
468 Font font() {
|
|
469 // TODO: if font is null (unset), return from theme
|
|
470 //if(font is null)
|
|
471 // return Theme.Current.Control_Font(this);
|
|
472 //else
|
|
473 return _font;
|
|
474 }
|
|
475 /// ditto
|
|
476 void font(Font f) {
|
|
477 _font = f;
|
|
478 repaint();
|
|
479 }
|
|
480 Cursor cursor() {
|
|
481 return _cursor;
|
|
482 }
|
|
483 void cursor(Cursor cur) {
|
|
484 if(_cursor is cur)
|
|
485 return;
|
|
486 _cursor = cur;
|
|
487 if(getHotControl() is this)
|
|
488 Cursor.setCurrent(this, _cursor);
|
|
489 }
|
|
490
|
|
491 /**
|
|
492 * Causes the part of the control inside the specified
|
|
493 * rectangle to be repainted. The rectangle is in content coordinates.
|
|
494 *
|
|
495 * The control will not be repainted before this method returns; rather,
|
|
496 * the area is just marked as needing to be repainted. The next time there
|
|
497 * are no other system events to be processed, a painting event will called.
|
|
498 */
|
|
499 void repaint(Rect rect) {
|
|
500 // TODO: make sure that parts clipped off by the parent are not invalidated
|
|
501 if(_parent)
|
|
502 _parent.repaint(rect + location);
|
|
503 }
|
|
504 /// ditto
|
|
505 void repaint(real x, real y, real width, real height) {
|
|
506 repaint(Rect(x, y, width, height));
|
|
507 }
|
|
508 /**
|
|
509 * Causes the entire control to be repainted.
|
|
510 */
|
|
511 void repaint() {
|
|
512 repaint(Rect(0, 0, width, height));
|
|
513 }
|
|
514 }
|
|
515
|
|
516
|