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 __gshared Window _aliveWindow;
43 
44     static dstring getClipboard ()
45     {
46         if ( _aliveWindow ) {
47             return _aliveWindow.clipboard;
48         }
49         return ""d;
50     }
51     static void setClipboard ( dstring v )
52     {
53         if ( _aliveWindow ) {
54             _aliveWindow.clipboard = v;
55         }
56     }
57 
58     protected Shaders _shaders;
59     /// A collection of shader.
60     @property shaders () { return _shaders; }
61 
62     protected ClipRect _clip;
63     /// ClipRect utility object.
64     @property clip () { return _clip; }
65 
66     protected WindowContent _root;
67     protected vec2          _cursorPos;
68 
69     ///
70     this ( vec2i size, string text, WindowHint hint = WindowHint.None )
71     {
72         enforce( size.x > 0 && size.y > 0, "Window size is invalid." );
73 
74         super( size, text, hint );
75         retrieveDisplayConfig();
76 
77         _shaders    = new Shaders;
78         _clip       = new ClipRect( shaders.fill3 );
79         _root       = null;
80         _cursorPos  = vec2(0,0);
81 
82         g4d.ColorBuffer.enableBlend();
83         g4d.DepthBuffer.disable();
84 
85         handler.onWindowResize = delegate ( vec2i sz )
86         {
87             enforce( _root, "Content is null." );
88             recalcMatrix();
89             _root.layout( sz );
90         };
91         handler.onWindowRefresh = delegate ()
92         {
93             _root.requestRedraw();
94         };
95         handler.onMouseEnter = delegate ( bool entered )
96         {
97             _root.handleMouseEnter( entered, _cursorPos );
98         };
99         handler.onMouseMove = delegate ( vec2 pos )
100         {
101             _cursorPos = pos;
102             _root.handleMouseMove( pos );
103         };
104         handler.onMouseButton = delegate ( MouseButton btn, bool status )
105         {
106             _root.handleMouseButton( btn, status, _cursorPos );
107         };
108         handler.onMouseScroll = delegate ( vec2 amount )
109         {
110             _root.handleMouseScroll( amount, _cursorPos );
111         };
112         handler.onKey = delegate ( Key key, KeyState state )
113         {
114             _root.handleKey( key, state );
115         };
116         handler.onCharacter = delegate ( dchar c )
117         {
118             _root.handleTextInput( c );
119         };
120     }
121 
122     /// Sets contents(widget).
123     /// This method can be called only once.
124     void setContent ( WindowContent content )
125     {
126         enforce( !_root, "Content is already setted." );
127         _root = content;
128         handler.onWindowResize( size );
129     }
130 
131     protected void recalcMatrix ()
132     {
133         auto half = vec2( size ) / 2;
134         auto late = half * -1;
135 
136         const orth = mat4.orthographic( -half.x,half.x,
137                 half.y,-half.y, short.min,short.max );
138 
139         const proj = orth * mat4.translation( vec3( late, 0 ) );
140 
141         foreach ( s; shaders.list ) {
142             s.matrix.projection = proj;
143         }
144     }
145 
146     /// Makes contexts current,
147     /// And clears color and depth buffer.
148     /// Warnings: Stencil buffer won't be cleared.
149     override void resetFrame ()
150     {
151         super.resetFrame();
152         g4d.ColorBuffer.clear();
153         g4d.DepthBuffer.clear();
154     }
155 
156     /// Updates window.
157     /// This method must be called by App object.
158     override bool exec ( App app )
159     {
160         enforce( _root, "Content is null." );
161 
162         if ( alive ) {
163             _aliveWindow = this;
164 
165             if ( _root.needLayout ) {
166                 _root.layout( size );
167             }
168             if ( _root.needRedraw ) {
169                 resetFrame();
170                 _root.draw( this );
171                 applyFrame();
172             }
173             cursor = _root.cursor;
174             return false;
175         }
176         return true;
177     }
178 }
179 
180 /// A struct of shader collection.
181 /// One Shaders class is for only one Window.
182 class Shaders
183 {
184     @("shader") {
185         protected g4d.RGBA3DShader  _rgba3;
186         protected g4d.Alpha3DShader _alpha3;
187         protected g4d.Fill3DShader  _fill3;
188     }
189 
190     /// FIXME: IDK how to fix these fucking codes.
191 
192     static foreach ( name; __traits(allMembers,typeof(this)) ) {
193         static if ( "shader".isIn( __traits(getAttributes,mixin(name)) ) ) {
194             mixin( "@property "~name[1..$]~"() { return "~name~"; }" );
195         }
196     }
197 
198     ///
199     this ()
200     {
201         static foreach ( name; __traits(allMembers,typeof(this)) ) {
202             static if ( "shader".isIn( __traits(getAttributes,mixin(name)) ) ) {
203                 mixin( name~"= new typeof("~name~");" );
204             }
205         }
206     }
207 
208     /// List of all shaders.
209     @property list ()
210     {
211         import g4d.shader.base: Shader;
212         Shader[] result;
213 
214         static foreach ( name; __traits(allMembers,typeof(this)) ) {
215             static if ( "shader".isIn( __traits(getAttributes,mixin(name)) ) ) {
216                 mixin( "result ~= "~name~";" );
217             }
218         }
219         return result;
220     }
221 }
222 
223 /// An interface that objects placed in Window must inherit.
224 /// Some handlers return true if event was handled.
225 interface WindowContent
226 {
227 
228     /// Be called with true when cursor is entered.
229     bool handleMouseEnter ( bool, vec2 );
230     /// Be called when cursor is moved.
231     bool handleMouseMove ( vec2 );
232     /// Be called when mouse button is clicked.
233     bool handleMouseButton ( MouseButton, bool, vec2 );
234     /// Be called when mouse wheel is rotated.
235     bool handleMouseScroll ( vec2, vec2 );
236 
237     /// Be called when key is pressed, repeated or released.
238     bool handleKey ( Key, KeyState );
239     /// Be called when focused and text was inputted.
240     bool handleTextInput ( dchar );
241 
242     /// Current cursor.
243     @property const(g4d.Cursor) cursor ();
244 
245     /// Checks if the contents need window size.
246     @property bool needLayout ();
247     /// Checks if the contents need redrawing.
248     @property bool needRedraw ();
249     /// Sets the content redraw next frame.
250     void requestRedraw ();
251 
252     /// Be called with size of Window when needLayout is true.
253     void layout ( vec2i );
254     /// Be called when needRedraw is true.
255     void draw   ( Window );
256 }