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 }