1 /**
2 Grimoire
3 Copyright (c) 2017 Enalye
4 
5 This software is provided 'as-is', without any express or implied warranty.
6 In no event will the authors be held liable for any damages arising
7 from the use of this software.
8 
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute
11 it freely, subject to the following restrictions:
12 
13 	1. The origin of this software must not be misrepresented;
14 	   you must not claim that you wrote the original software.
15 	   If you use this software in a product, an acknowledgment
16 	   in the product documentation would be appreciated but
17 	   is not required.
18 
19 	2. Altered source versions must be plainly marked as such,
20 	   and must not be misrepresented as being the original software.
21 
22 	3. This notice may not be removed or altered from any source distribution.
23 */
24 
25 module common.application;
26 
27 import derelict.sdl2.sdl;
28 
29 import core.thread;
30 import std.datetime;
31 
32 import core.all;
33 import render.all;
34 import ui.all;
35 
36 import common.event;
37 import common.settings;
38 import common.resource;
39 
40 private Application _application;
41 uint nominalFps = 60u;
42 
43 void createApplication(Vec2u size, string title = "Atelier") {
44 	if(_application !is null)
45 		throw new Exception("The application cannot be run twice.");
46 	_application = new Application(size, title);
47 }
48 
49 void runApplication() {
50 	if(_application is null)
51 		throw new Exception("Cannot run the application.");
52 	_application.run();
53 }
54 
55 void addWidget(Widget widget) {
56 	if(_application is null)
57 		throw new Exception("The application is not running.");
58 	_application.addChild(widget);
59 }
60 
61 void removeWidgets() {
62 	if(_application is null)
63 		throw new Exception("The application is not running.");
64 	_application.removeChildren();
65 }
66 
67 void setWidgets(Widget[] widgets) {
68 	if(_application is null)
69 		throw new Exception("The application is not running.");
70 	_application.setChildren(widgets);
71 }
72 
73 Widget[] getWidgets() {
74 	if(_application is null)
75 		throw new Exception("The application is not running.");
76 	return _application.getChildren();
77 }
78 
79 private class Application: IMainWidget {
80 	private {
81 		float _deltaTime = 1f;
82 		float _currentFps;
83 		long _tickStartFrame;
84 
85 		bool _isChildGrabbed;
86 		uint _idChildGrabbed;
87 		Widget[] _children;
88 	}
89 
90 	@property {
91 		float deltaTime() const { return _deltaTime; }
92 		float currentFps() const { return _currentFps; }
93 	}
94 
95 	this(Vec2u size, string title) {
96 		createWindow(size, title);
97 		initializeEvents();
98 		loadResources();
99 		initializeOverlay();
100 		_tickStartFrame = Clock.currStdTime();
101 	}
102 
103 	~this() {
104         destroyEvents();
105 		destroyWindow();
106 	}
107 
108 	void onEvent(Event event) {
109 		if(isOverlay()) {
110 			_isChildGrabbed = false;
111 			processOverlayEvent(event);
112 			return;
113 		}
114 
115 		switch(event.type) with(EventType) {
116 		case MouseDown:
117 			bool hasClickedWidget = false;
118 			foreach(uint id, Widget widget; _children) {
119 				widget.hasFocus = false;
120 				if(!widget.isInteractable)
121 					continue;
122 
123 				if(!hasClickedWidget && widget.isInside(event.position)) {
124 					widget.hasFocus = true;
125 					widget.isSelected = true;
126 					widget.isHovered = true;
127 					_isChildGrabbed = true;
128 					_idChildGrabbed = id;
129 					widget.onEvent(event);
130 					hasClickedWidget = true;
131 				}
132 			}
133 			break;
134 		case MouseUp:
135 			if(_isChildGrabbed) {
136 				_isChildGrabbed = false;
137 				_children[_idChildGrabbed].isSelected = false;
138 				_children[_idChildGrabbed].onEvent(event);
139 			}
140 			break;
141 		case MouseUpdate:
142 			foreach(uint id, Widget widget; _children) {
143 				widget.isHovered = widget.isInside(event.position);
144 				if(widget.isHovered)
145 					widget.onEvent(event);
146 			}
147 
148 			if(_isChildGrabbed && !_children[_idChildGrabbed].isHovered)
149 				_children[_idChildGrabbed].onEvent(event);
150 			break;
151 		case MouseWheel:
152 			foreach(uint id, Widget widget; _children) {
153 				if(widget.isHovered)
154 					widget.onEvent(event);
155 			}
156 
157 			if(_isChildGrabbed && !_children[_idChildGrabbed].isHovered)
158 				_children[_idChildGrabbed].onEvent(event);
159 			break;
160 		default:
161 			foreach (Widget widget; _children)
162 				widget.onEvent(event);
163 			break;
164 		}
165 	}
166 
167 	void run() {
168 		while(processEvents(this)) {
169             updateEvents(_deltaTime);
170 			processOverlayBack(_deltaTime);
171 			foreach(Widget widget; _children) {
172 				widget.update(_deltaTime);
173 				widget.draw();
174 				widget.drawOverlay();
175 			}
176 			processOverlayFront(_deltaTime);
177 			renderWindow();
178 			endOverlay();
179 			
180 			long deltaTicks = Clock.currStdTime() - _tickStartFrame;
181 			if(deltaTicks < (10_000_000 / nominalFps))
182 				Thread.sleep(dur!("hnsecs")((10_000_000 / nominalFps) - deltaTicks));
183 
184 			deltaTicks = Clock.currStdTime() - _tickStartFrame;
185 			_deltaTime = (cast(float)(deltaTicks) / 10_000_000f) * nominalFps;
186 			_currentFps = (_deltaTime == .0f) ? .0f : (10_000_000f / cast(float)(deltaTicks));
187 			_tickStartFrame = Clock.currStdTime();
188 		}
189 	}
190 
191 	void addChild(Widget widget) {
192 		_children ~= widget;
193 	}
194 
195 	void removeChildren() {
196         _isChildGrabbed = false;
197 		_children.length = 0uL;
198 	}
199 
200 	Widget[] getChildren() {
201 		return _children;
202 	}
203 
204 	void setChildren(Widget[] newChildren) {
205 		_children = newChildren;
206 	}
207 }