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 }