1 // Written in the D programming language.
2 /++
3  + Authors: KanzakiKino
4  + Copyright: KanzakiKino 2018
5  + License: LGPL-3.0
6 ++/
7 module w4d.widget.base;
8 import w4d.element.box,
9        w4d.layout.base,
10        w4d.layout.fill,
11        w4d.parser.colorset,
12        w4d.style.color,
13        w4d.style.widget,
14        w4d.task.window,
15        w4d.widget.base.context,
16        w4d.widget.base.keyboard,
17        w4d.widget.base.mouse,
18        w4d.exception;
19 import g4d.glfw.cursor,
20        g4d.shader.base;
21 import gl3n.linalg;
22 import std.algorithm,
23        std.array,
24        std.conv;
25 
26 mixin Context;
27 
28 /// An empty widget.
29 class Widget : WindowContent, Layoutable
30 {
31     protected uint _status;
32     /// Current status.
33     @property status () { return _status; }
34 
35     protected WindowContext _context;
36 
37     protected WidgetStyle _style;
38     /// Style of the widget.
39     @property WidgetStyle style () { return _style; }
40 
41     protected ColorSet _colorset;
42     /// Current colorset.
43     @property colorset () { return _colorset; }
44 
45     protected Layout        _layout;
46     protected BoxElement    _box;
47 
48     protected bool _needLayout;
49 
50     protected Widget findChildAt ( vec2 pt )
51     {
52         // Use reversed foreach to match the order with drawing.
53         foreach_reverse ( c; calcedChildren ) {
54             if ( c.style.isPointInside(pt) ) {
55                 return c;
56             }
57         }
58         return null;
59     }
60 
61     mixin Mouse;
62     mixin Keyboard;
63 
64     ///
65     void handlePopup ( bool, WindowContext )
66     {
67     }
68 
69     ///
70     this ()
71     {
72         _status = WidgetState.None;
73         _style  = new WidgetStyle;
74 
75         _context = null;
76         _box     = new BoxElement;
77 
78         _needLayout = true;
79 
80         parseColorSetsFromFile!"colorset/widget.yaml"( style );
81         setLayout!FillLayout();
82     }
83 
84     ///
85     @property vec2 wantedSize ()
86     {
87         return vec2(-1,-1);
88     }
89     /// Child widgets.
90     @property Widget[] children ()
91     {
92         return [];
93     }
94     /// Child widgets that have no uncalculated style properties.
95     @property Widget[] calcedChildren ()
96     {
97         return children.filter!"a.style.isCalced".array;
98     }
99     /// Child widgets that are converted to Layoutables.
100     @property Layoutable[] childLayoutables ()
101     {
102         return children.to!( Layoutable[] );
103     }
104 
105     protected void infectWindowContext ()
106     {
107         enforce( _context, "WindowContext is null." );
108         children.each!( x => x._context = _context );
109     }
110 
111     /// Enables the state.
112     void enableState ( WidgetState state )
113     {
114         _status |= state;
115         requestRedraw();
116     }
117     /// Disables the state.
118     void disableState ( WidgetState state )
119     {
120         _status &= ~state;
121         requestRedraw();
122     }
123 
124     /// Changes Layout object.
125     void setLayout (L,Args...) ( Args args )
126     {
127         _layout = new L(this,args);
128     }
129     /// Checks if the widget needs re-layout.
130     @property bool needLayout ()
131     {
132         return _needLayout || children.canFind!"a.needLayout" || !style.isCalced;
133     }
134     /// Set needLayout true.
135     void requestLayout ()
136     {
137         _needLayout = true;
138     }
139     /// Re-layouts the widget.
140     /// Be called only by Window.
141     void layout ( vec2i size )
142     {
143         layout( vec2(0,0), vec2(size) );
144     }
145     /// Re-layouts the widget.
146     vec2 layout ( vec2 pos, vec2 size )
147     {
148         infectWindowContext();
149 
150         enforce( _layout, "Layout is null." );
151         _layout.place( pos, size );
152         _needLayout = false;
153 
154         _box.resize( _style.box );
155         requestRedraw();
156 
157         return style.box.collisionSize;
158     }
159     /// Moves all children.
160     void shiftChildren ( vec2 size )
161     {
162         foreach ( c; calcedChildren ) {
163             c.style.shift( size );
164             c.shiftChildren( size );
165         }
166         requestRedraw();
167     }
168 
169     /// Checks if the widget needs redrawing.
170     @property bool needRedraw ()
171     {
172         return _context && _context.needRedraw;
173     }
174     /// Sets needRedraw true.
175     void requestRedraw ()
176     {
177         if ( _context ) {
178             _context.requestRedraw();
179         }
180     }
181     protected void drawBox ( Window win )
182     {
183         auto  shader = win.shaders.fill3;
184         const saver  = ShaderStateSaver( shader );
185 
186         shader.use();
187         shader.matrix.late = vec3( style.translate, 0 );
188 
189         _box.setColor( colorset );
190         _box.draw( shader );
191     }
192     protected void drawChildren ( Window win )
193     {
194         //children.each!( x => x.draw(win,colorset) );
195         foreach ( child; children ) {
196             child.draw( win, colorset );
197         }
198     }
199     /// Draws the widget.
200     /// Be called only by Window.
201     void draw ( Window win )
202     {
203         draw( win, ColorSet() );
204     }
205     /// Draws the widget.
206     void draw ( Window win, ColorSet parent )
207     {
208         _colorset = style.getColorSet(status);
209         _colorset.inherit( parent );
210 
211         drawBox( win );
212         drawChildren( win );
213         _context.setNoNeedRedraw();
214     }
215 }