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.widget.base.status,
21        w4d.exception;
22 import g4d.glfw.cursor,
23        g4d.shader.base;
24 import gl3n.linalg;
25 import std.algorithm,
26        std.array,
27        std.conv;
28 
29 mixin Context;
30 
31 /// An empty widget.
32 class Widget : WindowContent, Layoutable
33 {
34     protected WidgetStatus _status;
35     inout @property ref inout(WidgetStatus) 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 handleChangeStatus ( WidgetState s, bool )
71     {
72         if ( s in style.colorsets ) {
73             requestRedraw();
74         }
75     }
76 
77     ///
78     void handlePopup ( bool, WindowContext )
79     {
80     }
81 
82     ///
83     this ()
84     {
85         _status   = WidgetStatus();
86         _style    = new WidgetStyle;
87         _colorset = new ColorSet;
88 
89         _context = null;
90         _box     = new BoxElement;
91 
92         _needLayout = true;
93 
94         _status.onChangeStatus = &handleChangeStatus;
95 
96         parseColorSetsFromFile!"colorset/widget.yaml"( style );
97         setLayout!( FillLayout, HorizontalLineupPlacer )();
98     }
99 
100     ///
101     @property vec2 wantedSize ()
102     {
103         return vec2(-1,-1);
104     }
105     /// Child widgets.
106     @property Widget[] children ()
107     {
108         return [];
109     }
110     /// Child widgets that have no uncalculated style properties.
111     @property Widget[] calcedChildren ()
112     {
113         return children.filter!"a.style.isCalced".array;
114     }
115     /// Child widgets that are casted to PlacerOwner.
116     @property PlacerOwner[] childPlacerOwners ()
117     {
118         return children.to!( PlacerOwner[] );
119     }
120     /// Child widgets that are casted to Layoutable.
121     @property Layoutable[] childLayoutables ()
122     {
123         return children.to!( Layoutable[] );
124     }
125 
126     protected void infectWindowContext ()
127     {
128         enforce( _context, "WindowContext is null." );
129         children.each!( x => x._context = _context );
130     }
131 
132     /// Changes Layout object.
133     void setLayout (L,P,Args...) ( Args args )
134     {
135         _layout = new L( new P( this ), this, args );
136     }
137     /// Checks if the widget needs re-layout.
138     bool checkNeedLayout ( bool recursively )
139     {
140         if ( recursively ) {
141             return _needLayout ||
142                    children.canFind!"a.needLayout" ||
143                    !style.isCalced;
144         }
145         return _needLayout;
146     }
147     /// Checks if the widget needs re-layout.
148     @property bool needLayout ()
149     {
150         return checkNeedLayout( true );
151     }
152     /// Set needLayout true.
153     void requestLayout ()
154     {
155         _needLayout = true;
156     }
157     /// Re-layouts the widget.
158     /// Be called only by Window.
159     void layout ( vec2i size )
160     {
161         layout( vec2(0,0), vec2(size) );
162     }
163     /// Re-layouts the widget.
164     vec2 layout ( vec2 pos, vec2 size )
165     {
166         infectWindowContext();
167 
168         enforce( _layout, "Layout is null." );
169         _layout.place( pos, size );
170         _needLayout = false;
171 
172         _box.resize( _style.box );
173         requestRedraw();
174 
175         return style.box.collisionSize;
176     }
177     ///
178     void shift ( vec2 size )
179     {
180         _layout.shift( size );
181         requestRedraw();
182     }
183 
184     /// Checks if the widget needs redrawing.
185     @property bool needRedraw ()
186     {
187         return _context && _context.needRedraw;
188     }
189     /// Sets needRedraw true.
190     void requestRedraw ()
191     {
192         if ( _context ) {
193             _context.requestRedraw();
194         }
195     }
196     protected void drawBox ( Window win )
197     {
198         auto  shader = win.shaders.fill3;
199         const saver  = ShaderStateSaver( shader );
200 
201         shader.use();
202         shader.matrix.late = vec3(
203                 style.clientLeftTop + style.box.clientSize/2, 0 );
204 
205         _box.setColor( colorset );
206         _box.draw( shader );
207     }
208     protected void drawChildren ( Window win )
209     {
210         //children.each!( x => x.draw(win,colorset) );
211         foreach ( child; children ) {
212             child.draw( win, _colorset );
213         }
214     }
215     /// Draws the widget.
216     /// Be called only by Window.
217     void draw ( Window win )
218     {
219         draw( win, null );
220     }
221     /// Draws the widget.
222     void draw ( Window win, in ColorSet parent )
223     {
224         _colorset.clear();
225         style.inheritColorSet( _colorset, status.flags );
226         if ( parent ) {
227             _colorset.inherit( parent );
228         }
229 
230         drawBox( win );
231         drawChildren( win );
232         _context.setNoNeedRedraw();
233     }
234 }