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 }