1 // Written in the D programming language. 2 /++ 3 + Authors: KanzakiKino 4 + Copyright: KanzakiKino 2018 5 + License: LGPL-3.0 6 ++/ 7 module w4d.task.window; 8 import w4d.style.scalar, 9 w4d.util.clipping, 10 w4d.util.tuple, 11 w4d.app, 12 w4d.event, 13 w4d.exception; 14 import g4d.glfw.lib; 15 static import g4d; 16 import gl3n.linalg; 17 18 /// 19 alias WindowHint = g4d.WindowHint; 20 /// 21 alias MouseButton = g4d.MouseButton; 22 /// 23 alias Key = g4d.Key; 24 /// 25 alias KeyState = g4d.KeyState; 26 27 /// A window to place widgets. 28 class Window : g4d.Window, Task 29 { 30 protected static void retrieveDisplayConfig () 31 { 32 auto mon = enforce!glfwGetPrimaryMonitor(); 33 const mod = enforce!glfwGetVideoMode( mon ); 34 35 int mm_x = 0, mm_y = 0; // millimetres 36 enforce!glfwGetMonitorPhysicalSize( mon, &mm_x, &mm_y ); 37 38 ScalarUnitBase.mm = mod.width*1f / mm_x; 39 ScalarUnitBase.inch = mod.width*1f / mm_x / 25.4f; 40 } 41 42 protected Shaders _shaders; 43 /// A collection of shader. 44 @property shaders () { return _shaders; } 45 46 protected ClipRect _clip; 47 /// ClipRect utility object. 48 @property clip () { return _clip; } 49 50 protected WindowContent _root; 51 protected vec2 _cursorPos; 52 53 /// 54 this ( vec2i size, string text, WindowHint hint = WindowHint.None ) 55 { 56 enforce( size.x > 0 && size.y > 0, "Window size is invalid." ); 57 58 super( size, text, hint ); 59 retrieveDisplayConfig(); 60 61 _shaders = new Shaders; 62 _clip = new ClipRect( shaders.fill3 ); 63 _root = null; 64 _cursorPos = vec2(0,0); 65 66 g4d.ColorBuffer.enableBlend(); 67 g4d.DepthBuffer.disable(); 68 69 handler.onWindowResize = delegate ( vec2i sz ) 70 { 71 enforce( _root, "Content is null." ); 72 recalcMatrix(); 73 _root.layout( sz ); 74 }; 75 handler.onMouseEnter = delegate ( bool entered ) 76 { 77 _root.handleMouseEnter( entered, _cursorPos ); 78 }; 79 handler.onMouseMove = delegate ( vec2 pos ) 80 { 81 _cursorPos = pos; 82 _root.handleMouseMove( pos ); 83 }; 84 handler.onMouseButton = delegate ( MouseButton btn, bool status ) 85 { 86 _root.handleMouseButton( btn, status, _cursorPos ); 87 }; 88 handler.onMouseScroll = delegate ( vec2 amount ) 89 { 90 _root.handleMouseScroll( amount, _cursorPos ); 91 }; 92 handler.onKey = delegate ( Key key, KeyState state ) 93 { 94 _root.handleKey( key, state ); 95 }; 96 handler.onCharacter = delegate ( dchar c ) 97 { 98 _root.handleTextInput( c ); 99 }; 100 } 101 102 /// Sets contents(widget). 103 /// This method can be called only once. 104 void setContent ( WindowContent content ) 105 { 106 enforce( !_root, "Content is already setted." ); 107 _root = content; 108 handler.onWindowResize( size ); 109 } 110 111 protected void recalcMatrix () 112 { 113 auto half = vec2( size ) / 2; 114 auto late = half * -1; 115 116 const orth = mat4.orthographic( -half.x,half.x, 117 half.y,-half.y, short.min,short.max ); 118 119 const proj = orth * mat4.translation( vec3( late, 0 ) ); 120 121 foreach ( s; shaders.list ) { 122 s.matrix.projection = proj; 123 } 124 } 125 126 /// Makes contexts current, 127 /// And clears color and depth buffer. 128 /// Warnings: Stencil buffer won't be cleared. 129 override void resetFrame () 130 { 131 super.resetFrame(); 132 g4d.ColorBuffer.clear(); 133 g4d.DepthBuffer.clear(); 134 } 135 136 /// Updates window. 137 /// This method must be called by App object. 138 override bool exec ( App app ) 139 { 140 enforce( _root, "Content is null." ); 141 142 if ( alive ) { 143 if ( _root.needLayout ) { 144 _root.layout( size ); 145 } 146 if ( _root.needRedraw ) { 147 resetFrame(); 148 _root.draw( this ); 149 applyFrame(); 150 } 151 cursor = _root.cursor; 152 return false; 153 } 154 return true; 155 } 156 } 157 158 /// A struct of shader collection. 159 /// One Shaders class is for only one Window. 160 class Shaders 161 { 162 @("shader") { 163 protected g4d.RGBA3DShader _rgba3; 164 protected g4d.Alpha3DShader _alpha3; 165 protected g4d.Fill3DShader _fill3; 166 } 167 168 /// FIXME: IDK how to fix these fucking codes. 169 170 static foreach ( name; __traits(allMembers,typeof(this)) ) { 171 static if ( "shader".isIn( __traits(getAttributes,mixin(name)) ) ) { 172 mixin( "@property "~name[1..$]~"() { return "~name~"; }" ); 173 } 174 } 175 176 /// 177 this () 178 { 179 static foreach ( name; __traits(allMembers,typeof(this)) ) { 180 static if ( "shader".isIn( __traits(getAttributes,mixin(name)) ) ) { 181 mixin( name~"= new typeof("~name~");" ); 182 } 183 } 184 } 185 186 /// List of all shaders. 187 @property list () 188 { 189 import g4d.shader.base: Shader; 190 Shader[] result; 191 192 static foreach ( name; __traits(allMembers,typeof(this)) ) { 193 static if ( "shader".isIn( __traits(getAttributes,mixin(name)) ) ) { 194 mixin( "result ~= "~name~";" ); 195 } 196 } 197 return result; 198 } 199 } 200 201 /// An interface that objects placed in Window must inherit. 202 /// Some handlers return true if event was handled. 203 interface WindowContent 204 { 205 206 /// Be called with true when cursor is entered. 207 bool handleMouseEnter ( bool, vec2 ); 208 /// Be called when cursor is moved. 209 bool handleMouseMove ( vec2 ); 210 /// Be called when mouse button is clicked. 211 bool handleMouseButton ( MouseButton, bool, vec2 ); 212 /// Be called when mouse wheel is rotated. 213 bool handleMouseScroll ( vec2, vec2 ); 214 215 /// Be called when key is pressed, repeated or released. 216 bool handleKey ( Key, KeyState ); 217 /// Be called when focused and text was inputted. 218 bool handleTextInput ( dchar ); 219 220 /// Current cursor. 221 @property const(g4d.Cursor) cursor (); 222 223 /// Checks if the contents need window size. 224 @property bool needLayout (); 225 /// Checks if the contents need redrawing. 226 @property bool needRedraw (); 227 228 /// Be called with size of Window when needLayout is true. 229 void layout ( vec2i ); 230 /// Be called when needRedraw is true. 231 void draw ( Window ); 232 }