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 }