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