1 module evael.graphics.gui.controls.Control;
2 
3 import std.conv;
4 import std.typecons;
5 
6 public
7 {
8     import derelict.nanovg.nanovg;
9     
10     import evael.graphics.gui.controls.ContextMenuStrip;
11     import evael.graphics.gui.Theme;
12     import evael.graphics.gui.State;
13     import evael.graphics.gui.Icons;
14 
15     import evael.system.Input;
16 }
17 
18 import evael.graphics.gui.animations.Animation;
19 import evael.graphics.gui.animations.AnimationSet;
20 
21 import evael.graphics.Font;
22 import evael.graphics.Texture;
23 
24 import evael.utils.Math;
25 import evael.utils.Size;
26 import evael.utils.Rectangle;
27 
28 abstract class Control
29 {
30     enum Dock : ubyte
31     {
32         None,
33         Fill,
34         Left,
35         Bottom,
36         Right,
37         Top,
38     }
39 
40     protected alias OnClickEvent = void delegate(Control sender);
41     protected alias OnDragEvent = void delegate(Control sender, vec2 mousePosition);
42     protected alias OnDropEvent = void delegate(Control sender, vec2 mousePosition);
43 
44     protected OnClickEvent m_onClickEvent;
45     protected OnClickEvent m_onDoubleClickEvent;
46     protected OnDragEvent m_onDragEvent;
47     protected OnDropEvent m_onDropEvent;
48 
49     /// NanoVG context
50     public NVGcontext* m_nvg;
51 
52     /// Control id
53     private uint m_id;
54 
55     /// Control theme
56     protected Theme m_theme;
57 
58     /// Parent control
59     protected Control m_parent;
60 
61     /// Control name, used in GuiManager when loading themes
62     protected string m_name;
63 
64     /// Control position
65     protected vec2 m_position;
66 
67     /// Control real position for borders in shader
68     protected vec2 m_realPosition;
69 
70     /// Control position before drag and drop
71     protected vec2 m_positionBeforeDragAndDrop;
72 
73     /// Control size
74     protected Size!int m_size;
75 
76     /// Control texture
77     protected Texture m_texture;
78 
79     /// Is control under the mouse ?
80     protected bool m_hasFocus;
81 
82     /// Is control displayed  ?
83     protected bool m_isVisible;
84 
85     /// Is control enabled ?
86     protected bool m_isEnabled;
87 
88     /// Is control focusable ?
89     protected bool m_isFocusable;
90 
91     /// Control has been initialized ?
92     protected bool m_initialized;
93 
94     /// Control can be moved with mouse ?
95     protected bool m_movable;
96 
97     /// Control opacity
98     protected ubyte m_opacity;
99 
100     /// Dock
101     protected Dock m_dock;
102 
103     /// Control tooltip
104     protected wstring m_tooltipText;
105 
106     /// Texture coords
107     protected Rectangle!float m_textureCoords;
108 
109     /// Last click tick for button double click detection
110     private long m_lastClickTick;
111 
112     /// ContextMenu for this control
113     protected ContextMenuStrip m_contextMenu;
114 
115     /// Last mouse position
116     protected vec2 m_mousePosition;
117 
118     /// Current fill color
119     protected Color m_fillColor;
120 
121     /// Clicked buttons
122     protected bool[MouseButton] m_mouseButtonsStates;
123 
124     /// Last mouse button clicked
125     protected MouseButton m_lastClickedMouseButton;
126 
127     /// Mouse button used to drag current control
128     protected Nullable!MouseButton m_draggingMouseButton;
129 
130     /// z-index
131     protected uint m_zIndex;
132 
133     /// Control animations
134     protected AnimationSet m_animationSet;
135 
136     
137     public this(in vec2 position, in Size!int size) nothrow
138     {
139         this.m_position = position;
140         this.m_size = size;
141 
142         this.m_hasFocus = false;
143         this.m_isVisible = true;
144         this.m_isEnabled = true;
145         this.m_isFocusable = false;
146         this.m_movable = false;
147 
148         this.m_opacity = 255;
149 
150         this.m_textureCoords = Rectangle!float(0, 0, 0, 0);
151 
152         this.m_mouseButtonsStates = [MouseButton.Left : false, MouseButton.Right : false];
153         this.m_draggingMouseButton = Nullable!MouseButton();
154     }
155 
156     public void update(in float deltaTime)
157     {
158         if (this.m_animationSet !is null)
159         {
160             this.m_animationSet.update(deltaTime);
161         }
162     }
163 
164     /**
165      * Draws the control
166      */
167     public void draw(in float deltaTime)
168     {
169         if (this.m_theme.background.type == Background.Type.Transparent)
170         {
171             return;
172         }
173 
174         immutable x = this.m_realPosition.x;
175         immutable y = this.m_realPosition.y;
176         immutable w = this.m_size.width;
177         immutable h = this.m_size.height;
178 
179         immutable cornerRadius = this.m_theme.cornerRadius;
180 
181         auto vg = this.m_nvg;
182 
183         NVGpaint shadowPaint;
184         NVGpaint headerPaint;
185 
186         // Control
187         nvgSave(vg);
188 
189         nvgTranslate(vg, x, y);
190         // nvgScale(vg, 2, 2);
191 
192         if(this.m_theme.background.type == Background.Type.Solid)
193         {
194             nvgBeginPath(vg);
195             nvgRoundedRect(vg, 0, 0, w, h, cornerRadius);
196             nvgFillColor(vg, this.m_fillColor.asNvg);
197             nvgFill(vg);
198 
199             if (this.m_theme.drawDropShadow)
200             {
201                 // Drop shadow
202                 shadowPaint = nvgBoxGradient(vg, 0, 0, w, h, cornerRadius, 10,
203                         nvgRGBA(0, 0, 0, this.m_opacity), nvgRGBA(0, 0, 0, 0));
204 
205                 nvgBeginPath(vg);
206                 nvgRect(vg, -10, -10, w + 20, h + 20);
207                 nvgRoundedRect(vg, 0, 0, w, h, cornerRadius);
208                 nvgPathWinding(vg, NVGsolidity.NVG_HOLE);
209                 nvgFillPaint(vg, shadowPaint);
210                 nvgFill(vg);
211             }
212         }
213 
214         // Border
215         if (this.m_theme.borderType == Theme.BorderType.Solid)
216         {
217             nvgBeginPath(vg);
218             nvgRoundedRect(vg, 0, 0, w, h, cornerRadius);
219             nvgStrokeColor(vg, this.m_theme.borderColor.asNvg);
220             nvgStroke(vg);
221         }
222 
223         nvgRestore(vg);
224     }
225 
226     /**
227      * Event called on mouse button click
228      * Params:
229      *		mouseButton : mouse button
230      *		mousePosition : mouse position
231      */
232     public void onMouseClick(in MouseButton mouseButton, in ref vec2 mousePosition)
233     {
234         import evael.utils.Functions;
235 
236         this.m_mouseButtonsStates[mouseButton] = true;
237         this.m_lastClickedMouseButton = mouseButton;
238 
239         immutable long currentAppTick = timeSinceProgramStarted().total!"msecs";
240 
241         // We check for fast double click
242         if (currentAppTick - this.m_lastClickTick < 400)
243         {
244             // Avoid triple click
245             this.m_lastClickTick = 0;
246 
247             if (this.m_onDoubleClickEvent !is null)
248             {
249                 this.m_onDoubleClickEvent(this);
250             }
251 
252             return;
253         }
254 
255         this.m_lastClickTick = currentAppTick;
256     }
257 
258     /**
259      * Event called on mouse button release
260      * Params:
261      *		mouseButton : mouse button
262      */
263     public void onMouseUp(in MouseButton mouseButton)
264     {
265         this.m_mouseButtonsStates[mouseButton] = false;
266 
267         if (this.m_onClickEvent !is null && this.m_isEnabled)
268         {
269             this.m_onClickEvent(this);
270         }
271     }
272 
273     /**
274      * Event called when mouse enters in control's rect
275      * Params:
276      * 		 mousePosition : mouse position
277      */
278     public void onMouseMove(in ref vec2 mousePosition)
279     {
280         this.m_mousePosition = mousePosition;
281     }
282 
283     /**
284      * Event called when mouse leaves control's rect
285      */
286     public void onMouseLeave()
287     {
288 
289     }
290 
291     /**
292      * Event called on character input
293      * Params:
294      *		text :
295      */
296     public void onText(in int key)
297     {
298 
299     }
300 
301     /**
302      * Event called on key input
303      * Params:
304      *		key : pressed key
305      */
306     public void onKey(in int key)
307     {
308 
309     }
310 
311     /**
312      * Event called when control has been dragged
313      * Params:
314      *		mousePosition : mouse position
315      */
316     public void onDrag(in ref vec2 mousePosition)
317     {
318         // We set drag and drop mousebutton
319         if (this.m_draggingMouseButton.isNull)
320         {
321             this.m_draggingMouseButton = Nullable!MouseButton(this.m_lastClickedMouseButton);
322         }
323 
324         if (this.m_onDragEvent !is null)
325         {
326             this.m_onDragEvent(this, mousePosition);
327         }
328     }
329 
330     /**
331      * Event called when control has been dropped
332      * Params:
333      *		mousePosition : mouse position
334      */
335     public void onDrop(in ref vec2 mousePosition)
336     {
337         // We unset drag and drop mousebutton
338         if (this.m_draggingMouseButton.isNull)
339         {
340             this.m_draggingMouseButton = Nullable!MouseButton();
341         }
342 
343         if (this.m_onDropEvent !is null)
344         {
345             this.m_onDropEvent(this, mousePosition);
346         }
347     }
348 
349     /**
350      * Control gains focus
351      */
352     public void onFocus()
353     {
354 
355     }
356 
357     /**
358      * Control lose focus
359      */
360     public void onUnfocus()
361     {
362 
363     }
364 
365     /**
366      * Condition for drag and drop of this control
367      */
368     public bool canBeDragged() nothrow
369     {
370         return this.m_movable;
371     }
372 
373     /**
374      * Initializes control
375      */
376     public void initialize()
377     {
378         this.m_initialized = true;
379 
380         this.m_fillColor = this.m_theme.background.colorStateList.normal;
381 
382         if (this.m_parent !is null)
383         {
384             this.m_realPosition = this.m_position + this.m_parent.m_realPosition;
385         }
386         else
387         {
388             this.m_realPosition = this.m_position;
389         }
390 
391         if (this.m_contextMenu !is null)
392         {
393             this.m_contextMenu.initialize();
394         }
395 
396         // We create a nvg id for the texture
397         if (this.m_texture !is null)
398         {
399             if (this.m_nvg !is null && this.m_texture.nvgId == 0)
400             {
401                 this.m_texture.nvgId = nvglCreateImageFromHandleGL3(this.m_nvg,
402                         this.m_texture.id, this.m_texture.size.width,
403                         this.m_texture.size.height, 0);
404             }
405         }
406     }
407 
408     /**
409      * Displays the control
410      */
411     public void show() nothrow @nogc
412     {
413         this.m_isVisible = true;
414     }
415 
416     /**
417      * Hides the control
418      */
419     public void hide() nothrow @nogc
420     {
421         this.m_isVisible = false;
422     }
423 
424     /**
425      * Gives focus to the control
426      */
427     public void focus()
428     {
429         this.m_hasFocus = true;
430 
431         this.onFocus();
432     }
433 
434     /**
435      * Removes focus from the control
436      */
437     public void unfocus()
438     {
439         this.m_hasFocus = false;
440 
441         this.onUnfocus();
442     }
443 
444     /**
445      * Enables the control
446      */
447     public void enable()
448     {
449         this.m_isEnabled = true;
450     }
451 
452     /**
453      * Disables the control
454      */
455     public void disable()
456     {
457         this.m_isEnabled = false;
458         this.switchState!(State.Disabled);
459     }
460 
461     /**
462      * Switchs control state
463      */
464     public void switchState(State state)() nothrow
465     {
466         this.m_fillColor = this.m_theme.background.colorStateList.fromEnum!(state)();
467     }
468 
469     /**
470      * Starts a single animation
471      * Params:
472      *      animation :
473      */
474     public void startAnimation(Animation animation)
475     {
476         auto animationSet = new AnimationSet();
477         animationSet.add(animation);
478 
479         this.startAnimation(animationSet);
480     }
481 
482     /**
483      * Starts a set of animations
484      * Params:
485      *      animationSet : animations
486      */
487     public void startAnimation(AnimationSet animationSet)
488     {
489         this.m_animationSet = animationSet;
490         this.m_animationSet.control = this;
491 
492         this.m_animationSet.onSequenceEndEvent = () {
493             this.m_animationSet.dispose();
494             this.m_animationSet = null;
495         };
496     }
497 
498     @property public void opacity(in ubyte value)   @nogc
499     {
500         this.m_fillColor.a = value;
501         this.m_opacity = value;
502 
503         if (this.theme !is null)
504         {
505             this.m_theme.borderColor.a = value;
506             this.m_theme.fontColor.a = value;
507         }
508     }
509 
510     /**
511      * Properties
512      */
513     @property
514     {
515         public void nvg(NVGcontext* nvg) nothrow @nogc
516         {
517             this.m_nvg = nvg;
518         }
519 
520         public Theme theme() nothrow @nogc
521         {
522             return this.m_theme;
523         }
524 
525         public void theme(Theme value) nothrow @nogc
526         {
527             this.m_theme = value;
528         }
529 
530         public uint id() const nothrow @nogc
531         {
532             return this.m_id;
533         }
534 
535         public void id(in uint value) nothrow @nogc
536         {
537             this.m_id = value;
538         }
539 
540         public ubyte opacity() nothrow
541         {
542             return this.m_opacity;
543         }
544 
545         public ref const(vec2) position() const nothrow @nogc
546         {
547             return this.m_position;
548         }
549 
550         public void position(in vec2 value) nothrow @nogc
551         {
552             this.m_position = value;
553         }
554 
555         public ref const(vec2) realPosition() const nothrow @nogc
556         {
557             return this.m_realPosition;
558         }
559 
560         public void realPosition(in vec2 value) nothrow @nogc
561         {
562             this.m_realPosition = value;
563         }
564 
565         public ref const(vec2) positionBeforeDragAndDrop() const nothrow @nogc
566         {
567             return this.m_positionBeforeDragAndDrop;
568         }
569 
570         public void positionBeforeDragAndDrop(in vec2 value) nothrow @nogc
571         {
572             this.m_positionBeforeDragAndDrop = value;
573         }
574 
575         public ref const(Size!int) size() const nothrow @nogc
576         {
577             return this.m_size;
578         }
579 
580         public void size(in Size!int value) nothrow @nogc
581         {
582             this.m_size = value;
583         }
584 
585         public Texture texture() nothrow @nogc
586         {
587             return this.m_texture;
588         }
589 
590         public void texture(Texture value) nothrow @nogc
591         {
592             this.m_texture = value;
593 
594             // Default texcoords : full texture
595             if (this.m_texture !is null)
596             {
597                 if (this.m_nvg !is null && this.m_texture.nvgId == 0)
598                 {
599                     this.m_texture.nvgId = nvglCreateImageFromHandleGL3(this.m_nvg,
600                             this.m_texture.id, this.m_texture.size.width,
601                             this.m_texture.size.height, 0);
602                 }
603 
604                 this.m_textureCoords = Rectanglef(0, 0,
605                         this.m_texture.size.width, this.m_texture.size.height);
606             }
607         }
608 
609         public bool hasFocus() const nothrow @nogc
610         {
611             return this.m_hasFocus;
612         }
613 
614         public bool isVisible() const nothrow @nogc
615         {
616             return this.m_isVisible;
617         }
618 
619         public void isVisible(in bool value) nothrow @nogc
620         {
621             this.m_isVisible = value;
622         }
623 
624         public Control parent() nothrow @nogc
625         {
626             return this.m_parent;
627         }
628 
629         public void parent(Control value) nothrow @nogc
630         {
631             this.m_parent = value;
632         }
633 
634         public bool isClicked() const nothrow
635         {
636             return this.m_mouseButtonsStates[MouseButton.Left]
637                 || this.m_mouseButtonsStates[MouseButton.Right];
638         }
639 
640         public bool isEnabled() const nothrow @nogc
641         {
642             return this.m_isEnabled;
643         }
644 
645         public bool movable() const nothrow @nogc
646         {
647             return this.m_movable;
648         }
649 
650         public void movable(in bool value) nothrow @nogc
651         {
652             this.m_movable = value;
653         }
654 
655         public bool isFocusable() const nothrow @nogc
656         {
657             return this.m_isFocusable;
658         }
659 
660         public void isFocusable(in bool value) nothrow @nogc
661         {
662             this.m_isFocusable = value;
663         }
664 
665         public Dock dock() const nothrow @nogc
666         {
667             return this.m_dock;
668         }
669 
670         public void dock(Dock value) nothrow @nogc
671         {
672             this.m_dock = value;
673         }
674 
675         public void onClickEvent(OnClickEvent callback) nothrow @nogc
676         {
677             this.m_onClickEvent = callback;
678         }
679 
680         public void onDoubleClickEvent(OnClickEvent callback) nothrow @nogc
681         {
682             this.m_onDoubleClickEvent = callback;
683         }
684 
685         public wstring tooltipText() const nothrow @nogc
686         {
687             return this.m_tooltipText;
688         }
689 
690         public void tooltipText(in wstring tooltip) nothrow @nogc
691         {
692             this.m_tooltipText = tooltip;
693         }
694 
695         public void tooltipText(in string tooltip)
696         {
697             this.m_tooltipText = to!wstring(tooltip);
698         }
699 
700         public ContextMenuStrip contextMenu() nothrow @nogc
701         {
702             return this.m_contextMenu;
703         }
704 
705         public void onDragEvent(OnDragEvent callback) nothrow @nogc
706         {
707             this.m_onDragEvent = callback;
708         }
709 
710         public void onDropEvent(OnDropEvent callback) nothrow @nogc
711         {
712             this.m_onDropEvent = callback;
713         }
714 
715         public void textureCoords(in Rectangle!float value) nothrow @nogc
716         {
717             this.m_textureCoords = value;
718         }
719 
720         public string name() const nothrow @nogc
721         {
722             return this.m_name;
723         }
724 
725         public void name(in string value) nothrow @nogc
726         {
727             this.m_name = value;
728         }
729 
730         public const(bool[MouseButton]) mouseButtonsStates() const nothrow @nogc
731         {
732             return this.m_mouseButtonsStates;
733         }
734 
735         public MouseButton lastClickedMouseButton() const nothrow @nogc
736         {
737             return this.m_lastClickedMouseButton;
738         }
739 
740         public Nullable!MouseButton draggingMouseButton() const nothrow @nogc
741         {
742             return this.m_draggingMouseButton;
743         }
744 
745         public ref const(Color) fillColor() const nothrow @nogc
746         {
747             return this.m_fillColor;
748         }
749 
750         public void fillColor(in Color value) nothrow @nogc
751         {
752             this.m_fillColor = value;
753         }
754 
755         public uint zIndex() const nothrow @nogc
756         {
757             return this.m_zIndex;
758         }
759 
760         public void zIndex(in uint value) nothrow @nogc
761         {
762             this.m_zIndex = value;
763         }
764     }
765 }