Usermode/GUI Terminal - Added scrolling and cursor/save restore
[tpg/acess2.git] / Usermode / Applications / gui_terminal_src / display.c
1 /*
2  * Acess GUI Terminal
3  * - By John Hodge (thePowersGang)
4  *
5  * display.c
6  * - Abstract display manipulation methods
7  */
8 #include "include/display.h"
9 #include <acess/sys.h>  // _SysDebug
10 #include <stdlib.h>     // exit
11 #include <string.h>
12 #include <unicode.h>
13 #include <stdio.h>
14 #include <axwin3/axwin.h>
15 #include <axwin3/richtext.h>
16 #include <stdbool.h>
17 #include <assert.h>
18
19 #define UNIMPLIMENTED() do{_SysDebug("UNIMPLIMENTED %s", __func__); exit(-1);}while(0)
20
21 static inline int MIN(int a, int b) { return (a < b ? a : b); }
22 static inline int MAX(int a, int b) { return (a > b ? a : b); }
23
24 // === EXTERN ==
25 extern tHWND    gMainWindow;
26
27 typedef struct sLine    tLine;
28
29 struct sLine {
30         char    *Data;
31         // TODO: Cache offsets to avoid scan-forward
32         size_t  Len;
33         size_t  Size;
34         bool    IsDirty;
35 };
36
37 struct sTerminal {
38          int    ViewCols;
39          int    ViewRows;
40
41          int    ScrollTop;
42          int    ScrollRows;
43
44          int    CursorRow;
45          int    CursorCol;
46
47          int    SavedRow;
48          int    SavedCol;
49
50         size_t  CursorByte;
51
52         bool    bUsingAltBuf;
53         bool    bHaveSwappedBuffers;
54
55         size_t  ViewOffset;     
56         size_t  TotalLines;
57         struct sLine    *PriBuf;
58         
59         struct sLine    *AltBuf;
60 };
61
62 // === GLOBALS ===
63 tTerminal       gMainBuffer;
64  int    giCurrentLine;
65  int    giCurrentLinePos;       // byte offset, not column
66  int    giCurrentCol;
67  int    giFirstDispLine;        // First displayed line
68  int    giFirstLine;    // Ring buffer start
69 char    **gasDisplayLines;
70  int    *gaiDisplayLineSizes;
71 char    *gabDisplayLinesDirty;
72
73 // === CODE ===
74 tTerminal *Display_Init(int Cols, int Lines, int ExtraScrollbackLines)
75 {
76         tTerminal       *term = &gMainBuffer;
77         term->ViewCols = Cols;
78         term->ViewRows = Lines;
79         term->TotalLines = Lines + ExtraScrollbackLines;
80         term->PriBuf = calloc( sizeof(tLine), (Lines + ExtraScrollbackLines) );
81         
82         AxWin3_RichText_SetLineCount(gMainWindow, Lines+ExtraScrollbackLines);
83         AxWin3_RichText_SetCursorType(gMainWindow, 1);  // TODO: enum
84         return term;
85 }
86
87 // Return the byte length of a single on-screen character
88 size_t _GetCharLength(size_t AvailLength, const char *Text, uint32_t *BaseCodepoint)
89 {
90         if( !AvailLength )
91                 return 0;
92         
93         size_t  charlen = ReadUTF8(Text, BaseCodepoint);
94         
95         while(charlen < AvailLength)
96         {
97                 uint32_t        cp;
98                 size_t size = ReadUTF8(Text+charlen, &cp);
99                 if( Unicode_IsPrinting(cp) )
100                         break;
101                 charlen += size;
102         }
103         
104         return charlen;
105 }
106
107 tLine *Display_int_GetCurLine(tTerminal *Term)
108 {
109          int    lineidx = Term->CursorRow + (Term->bUsingAltBuf ? 0 : Term->ViewOffset);
110         //_SysDebug("lineidx = %i", lineidx);
111         return (Term->bUsingAltBuf ? Term->AltBuf : Term->PriBuf) + lineidx;
112 }
113
114 size_t Display_int_PushCharacter(tTerminal *Term, size_t AvailLength, const char *Text)
115 {
116         tLine   *lineptr = Display_int_GetCurLine(Term);
117         uint32_t        cp;
118         size_t  charlen = _GetCharLength(AvailLength, Text, &cp);
119         bool    bOverwrite = Unicode_IsPrinting(cp);
120         
121         // Figure out how much we need to shift the stream
122          int    shift;
123         if( bOverwrite ) {
124                 //_SysDebug("GetCharLen(%i-%i, %p+%i, NULL)", lineptr->Len, Term->CursorByte,
125                 //      lineptr->Data, Term->CursorByte);
126                 size_t nextlen = _GetCharLength(
127                         lineptr->Len-Term->CursorByte,
128                         lineptr->Data+Term->CursorByte,
129                         NULL);
130                 shift = charlen - nextlen;
131         }
132         else {
133                 shift = charlen;
134         }
135         
136         // Ensure we have space enough
137         if( !lineptr->Data || shift > 0 ) {
138                 const size_t    size_step = 64;
139                 assert(shift > 0);
140                 size_t  newsize = (lineptr->Len+shift+1 + size_step-1) & ~(size_step-1);
141                 if( newsize > lineptr->Size ) {
142                         lineptr->Size = newsize;
143                         void *tmp = realloc(lineptr->Data, lineptr->Size);
144                         if( !tmp )      perror("Display_int_PushCharacter - realloc");
145                         lineptr->Data = tmp;
146                 }
147         }
148         
149         // Insert into stream
150         char    *base = lineptr->Data + Term->CursorByte;
151         if( Term->CursorByte == lineptr->Len ) {
152                 // No shifting needed
153         }
154         else if( shift >= 0 ) {
155                 size_t  bytes = lineptr->Len - (Term->CursorByte+shift);
156                 memmove(base+shift, base, bytes);
157         }
158         else {
159                 shift = -shift;
160                 size_t  bytes = lineptr->Len - (Term->CursorByte+shift);
161                 memmove(base, base+shift, bytes);
162         }
163         memcpy(base, Text, charlen);
164         lineptr->IsDirty = true;
165         lineptr->Len += shift;
166         lineptr->Data[lineptr->Len] = 0;        // NULL Terminate
167
168         Term->CursorByte += charlen;
169         
170         // HACKY: Prevents the CursorCol++ in Display_AddText from having an effect
171         if( !bOverwrite )
172                 Term->CursorCol --;
173         
174         return charlen;
175 }
176
177 void Display_AddText(tTerminal *Term, size_t Length, const char *UTF8Text)
178 {
179         _SysDebug("%i '%.*s'", Length, Length, UTF8Text);
180         while( Length > 0 )
181         {
182                 size_t used = Display_int_PushCharacter(Term, Length, UTF8Text);
183         
184                 Length -= used;
185                 UTF8Text += used;
186                 
187                 Term->CursorCol ++;
188                 if( Term->CursorCol == Term->ViewCols ) {
189                         Display_Newline(Term, 1);
190                 }
191         }
192 }
193
194 void Display_Newline(tTerminal *Term, bool bCarriageReturn)
195 {
196         // Going down!
197         if( Term->bUsingAltBuf )
198         {
199                 if( Term->CursorRow == Term->ScrollTop + Term->ScrollRows-1 ) {
200                         Display_ScrollDown(Term, 1);
201                 }
202                 else if( Term->CursorRow == Term->ViewRows-1 ) {
203                         if( Term->ScrollRows == 0 ) {
204                                 // Scroll entire buffer
205                                 Display_ScrollDown(Term, 1);
206                         }
207                         else {
208                                 // Don't go down a line
209                                 Term->CursorRow --;
210                         }
211                 }
212                 else {
213                         // No scroll needed
214                 }
215         }
216         else
217         {
218                 if( Term->CursorRow == Term->TotalLines-1 ) {
219                         Display_ScrollDown(Term, 1);
220                 }
221         }
222         Term->CursorRow ++;
223         
224         if( bCarriageReturn )
225         {
226                 Term->CursorByte = 0;
227                 Term->CursorCol = 0;
228         }
229         else
230         {
231                 tLine   *line = Display_int_GetCurLine(Term);
232                 
233                 Term->CursorByte = 0;
234                  int    old_col = Term->CursorCol;
235                 Term->CursorCol = 0;
236
237                 size_t  ofs = 0;
238                 while( Term->CursorCol < old_col && ofs < line->Len ) {
239                         ofs += _GetCharLength(line->Len-ofs, line->Data+ofs, NULL);
240                         Term->CursorCol ++;
241                 }
242                 Term->CursorByte = ofs;
243
244                 while( Term->CursorCol < old_col )
245                         Display_AddText(Term, 1, " ");
246         }
247 }
248
249 void Display_SetScrollArea(tTerminal *Term, int Start, int Count)
250 {
251         assert(Start >= 0);
252         assert(Count >= 0);
253         Term->ScrollTop = Start;
254         Term->ScrollRows = MIN(Count, Term->ViewRows - Start);
255 }
256
257 void Display_ScrollDown(tTerminal *Term, int Count)
258 {
259          int    top, max;
260         tLine   *buffer;
261         
262         if( Term->bUsingAltBuf )
263         {
264                 top = (Term->ScrollRows == 0 ? 0 : Term->ScrollTop);
265                 max = (Term->ScrollRows == 0 ? Term->ViewRows : Term->ScrollRows);
266                 buffer = Term->AltBuf;
267         }
268         else
269         {
270                 top = 0;
271                 max = Term->TotalLines;
272                 buffer = Term->PriBuf;
273         }
274         
275         assert(Count < max);
276         assert(Count > -max);
277
278         _SysDebug("Scroll %p %i-%i down by %i", buffer, top, max, Count);
279         
280         buffer += top;
281
282         if( Term->CursorRow >= top && Term->CursorRow < top+max ) {
283                 Term->CursorRow -= Count;
284                 Term->CursorRow = MAX(Term->CursorRow, top);
285                 Term->CursorRow = MIN(Term->CursorRow, top+max);
286         }
287
288          int    clear_top, clear_max;
289         if( Count < 0 )
290         {
291                 // -ve: Scroll up, move buffer contents down
292                 Count = -Count;
293                 memmove(buffer+Count, buffer, (max-Count)*sizeof(*buffer));
294                 
295                 clear_top = 0;
296                 clear_max = Count;
297         }
298         else
299         {
300                 memmove(buffer, buffer+Count, (max-Count)*sizeof(*buffer));
301                 clear_top = max-Count;
302                 clear_max = max;
303         }
304         // Clear exposed lines
305         for( int i = clear_top; i < clear_max; i ++ )
306         {
307                 free(buffer[i].Data);
308                 buffer[i].Data = NULL;
309                 buffer[i].Len = 0;
310                 buffer[i].Size = 0;
311                 buffer[i].IsDirty = true;
312                 if( Term->CursorRow == i ) {
313                         Term->CursorCol = 0;
314                         Term->CursorByte = 0;
315                 }
316         }
317 }
318
319 void Display_SetCursor(tTerminal *Term, int Row, int Col)
320 {
321         assert(Row >= 0);
322         assert(Col >= 0);
323
324         _SysDebug("Set cursor R%i,C%i", Row, Col);      
325
326         if( !Term->bUsingAltBuf ) {
327                 _SysDebug("NOTE: Using \\e[%i;%iH outside of alternat buffer is undefined", Row, Col);
328         }
329         
330         // NOTE: This may be interesting outside of AltBuffer
331         Term->CursorRow = Row;
332         
333         tLine   *line = Display_int_GetCurLine(Term);
334         size_t ofs = 0;
335         for( int i = 0; i < Col; i ++ )
336         {
337         
338                 size_t clen = _GetCharLength(line->Len-ofs, line->Data+ofs, NULL);
339                 if( clen == 0 ) {
340                         // TODO: Should moving the cursor up to an unoccupied cell cause a jump?
341                         Col = i;
342                         break;
343                 }
344                 ofs += clen;
345         }
346
347         Term->CursorCol = Col;
348         Term->CursorByte = ofs;
349 }
350
351 void Display_MoveCursor(tTerminal *Term, int RelRow, int RelCol)
352 {
353         if( RelRow != 0 )
354         {
355                 UNIMPLIMENTED();
356         }
357         
358         if( RelCol != 0 )
359         {
360                 int req_col = Term->CursorCol + RelCol;
361                 if( req_col < 0 )       req_col = 0;
362                 if( req_col > Term->ViewCols )  req_col = Term->ViewCols;
363
364                 tLine   *line = Display_int_GetCurLine(Term);
365                 size_t  ofs = 0;
366                 for( int i = 0; i < req_col; i ++ )
367                 {
368                         size_t clen = _GetCharLength(line->Len-ofs, line->Data+ofs, NULL);
369                         if( clen == 0 ) {
370                                 req_col = i;
371                                 break;
372                         }
373                         ofs += clen;
374                 }
375
376                 Term->CursorCol = req_col;
377                 Term->CursorByte = ofs;
378         }
379 }
380
381 void Display_SaveCursor(tTerminal *Term)
382 {
383         Term->SavedRow = Term->CursorRow;
384         Term->SavedCol = Term->CursorCol;
385 }
386 void Display_RestoreCursor(tTerminal *Term)
387 {
388         Display_SetCursor(Term, Term->SavedRow, Term->SavedCol);
389 }
390
391 void Display_ClearLine(tTerminal *Term, int Dir)        // 0: All, 1: Forward, -1: Reverse
392 {
393         if( Dir == 0 )
394         {
395                 tLine   *line = Display_int_GetCurLine(Term);
396                 // Completely clear line
397                 if( line->Data )
398                         free(line->Data);
399                 line->Data = NULL;
400                 line->IsDirty = true;
401         }
402         else if( Dir == 1 )
403         {
404                 // Forward clear (truncate)
405         }
406         else if( Dir == -1 )
407         {
408                 // Reverse clear (replace with spaces)
409         }
410         else
411         {
412                 // BUGCHECK
413         }
414 }
415
416 void Display_ClearLines(tTerminal *Term, int Dir)       // 0: All, 1: Forward, -1: Reverse
417 {
418         if( Dir == 0 )
419         {
420                 // Push giDisplayLines worth of empty lines
421                 // Move cursor back up by giDisplayLines
422         }
423         else if( Dir == 1 )
424         {
425                 // Push (giDisplayLines - (giCurrentLine-giFirstDispLine)) and reverse
426         }
427         else if( Dir == -1 )
428         {
429                 // Reverse clear (replace with spaces)
430         }
431         else
432         {
433                 // BUGCHECK
434         }
435 }
436
437 void Display_SetForeground(tTerminal *Term, uint32_t RGB)
438 {
439         char    buf[7+1];
440         sprintf(buf, "\1%06x", RGB&0xFFFFFF);
441         Display_AddText(Term, 7, buf);
442 }
443
444 void Display_SetBackground(tTerminal *Term, uint32_t RGB)
445 {
446         char    buf[7+1];
447         sprintf(buf, "\2%06x", RGB&0xFFFFFF);
448         Display_AddText(Term, 7, buf);
449 }
450
451 void Display_Flush(tTerminal *Term)
452 {
453          int    viewOfs = (Term->bUsingAltBuf ? 0 : Term->ViewOffset);
454         tLine   *buffer = (Term->bUsingAltBuf ? Term->AltBuf : Term->PriBuf );
455         for( int i = 0; i < Term->ViewRows; i ++ )
456         {
457                  int    line = (viewOfs + i) % Term->TotalLines;
458                 tLine   *lineptr = &buffer[line];
459                 // Swapping buffers should cause a full resend
460                 if( !Term->bHaveSwappedBuffers && !lineptr->IsDirty )
461                         continue;
462                 _SysDebug("Line %i+%i '%.*s'", viewOfs, i, lineptr->Len, lineptr->Data);
463                 AxWin3_RichText_SendLine(gMainWindow, viewOfs + i,
464                         lineptr->Data ? lineptr->Data : "" );
465                 lineptr->IsDirty = 0;
466         }
467         AxWin3_RichText_SetCursorPos(gMainWindow, Term->CursorRow, Term->CursorCol);
468         Term->bHaveSwappedBuffers = false;
469 }
470
471 void Display_ShowAltBuffer(tTerminal *Term, bool AltBufEnabled)
472 {
473         if( Term->bUsingAltBuf == AltBufEnabled )
474         {
475                 // Nothing to do, so do nothing
476                 return ;
477         }
478         
479         Term->bUsingAltBuf = AltBufEnabled;
480         Term->bHaveSwappedBuffers = true;
481         if( AltBufEnabled )
482         {
483                 if( !Term->AltBuf )
484                 {
485                         Term->AltBuf = calloc( sizeof(Term->AltBuf[0]), Term->ViewRows );
486                 }
487         }
488 }
489

UCC git Repository :: git.ucc.asn.au