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 }