1 module sily.tui; 2 3 import std.conv: to; 4 import std.stdio: stdout; 5 6 import sily.tui.node; 7 import sily.tui.event; 8 import sily.tui.input; 9 import sily.tui.render; 10 11 import sily.vector: uvec2; 12 import sily.terminal; 13 import sily.terminal.input; 14 import sily.bashfmt: screenEnableAltBuffer, cursorHide, screenDisableAltBuffer, cursorShow; 15 import sily.logger: fatal; 16 import sily.time; 17 18 private float _fpsTarget = 30.0f; 19 private bool _isRunning = false; 20 private float _frameTime; 21 private int _frames; 22 private int _fps; 23 24 void run() { 25 if (!stdout.isatty) { 26 fatal("STDOUT is not a tty"); 27 exit(ErrorCode.noperm); 28 return; 29 } 30 31 screenEnableAltBuffer(); 32 screenClearOnly(); 33 version (Have_speed_stdio) { 34 terminalModeSetRaw(true); 35 } else { 36 terminalModeSetRaw(false); 37 } 38 cursorMoveHome(); 39 cursorHide(); 40 41 _isRunning = true; 42 43 loop(); 44 45 cleanup(); 46 } 47 48 void stop() { 49 _isRunning = false; 50 } 51 52 void loop() { 53 _frameTime = 1.0f / _fpsTarget; 54 _frames = 0; 55 _fps = _fpsTarget.to!int; // 30 by default 56 57 double frameCounter = 0; 58 double lastTime = currTime(); 59 double unprocessedTime = 0; 60 61 while (_isRunning) { 62 bool doNeedRender = false; 63 double startTime = currTime(); 64 double passedTime = startTime - lastTime; 65 lastTime = startTime; 66 67 unprocessedTime += passedTime; 68 frameCounter += passedTime; 69 70 while (unprocessedTime > _frameTime) { 71 // write(unprocessedTime, " ", _frameTime, " ", frameCounter, " ", _frames, "\n"); 72 doNeedRender = true; 73 unprocessedTime -= _frameTime; 74 75 // PROCESS LOGIC HERE 76 pollInputEvent(); 77 triggerEvent!EventUpdate; 78 // propagate update 79 80 if (frameCounter >= 1.0) { 81 _fps = _frames; 82 _frames = 0; 83 frameCounter = 0; 84 } 85 } 86 87 if (doNeedRender) { 88 // Process Render 89 render(); 90 ++_frames; 91 } 92 sleep(1); 93 94 scope (failure) { 95 cleanup(); 96 fatal("Fatal error have occured. Aborting execution."); 97 } 98 } 99 } 100 101 private size_t _drawFrameNumber = 0; 102 size_t renderFrameNumber() { 103 return _drawFrameNumber; 104 } 105 106 void render() { 107 // root updates entire screen 108 // update steps 109 // updates in parent polls updates in children 110 // updates in children polls update in direct parent 111 // updates are polled only if there are changes in something like position 112 // i.e if position of parent/child is changed parent will update 113 // and so all the children of parent 114 // this way it is ensured that there's no flashing or too much updates 115 116 // We shouldn't use force render here 117 // forceRender(); 118 requestRender(); 119 120 if (sizeofBuffer != 0) { 121 flushBuffer(); 122 clearBuffer(); 123 124 ++_drawFrameNumber; 125 } 126 } 127 128 void cleanup() { 129 terminalModeReset(); 130 screenDisableAltBuffer(); 131 cursorShow(); 132 } 133 134 void setTitle(string title) { 135 .setTitle(title); 136 } 137 138 /// Returns app width/height 139 uint width() { 140 return terminalWidth(); 141 } 142 /// Ditto 143 uint height() { 144 return terminalHeight() * 2; 145 } 146 /// Ditto 147 uvec2 size() { 148 return uvec2(width, height); 149 } 150 151 /// Returns current FPS 152 int fps() { 153 return _fps; 154 } 155 156 /// Returns current FPS as string 157 string fpsString() { 158 return _fps.to!string; 159 } 160 161 /// Returns true if app is running 162 bool isRunning() { 163 return _isRunning; 164 } 165 166 /// Returns aspect ratio (w / h) 167 float aspectRatio() { 168 return width.to!float / height.to!float; 169 }