3 * - By John Hodge (thePowersGang)
6 * - Abstract display manipulation methods
9 #include "include/display.h"
10 #include <acess/sys.h> // _SysDebug
11 #include <stdlib.h> // exit
15 #include <axwin3/axwin.h>
16 #include <axwin3/richtext.h>
21 # define DEBUGS(v...) _SysDebug(v)
23 # define DEBUGS(v...) do{}while(0)
26 #define UNIMPLIMENTED() do{_SysDebug("UNIMPLIMENTED %s", __func__); exit(-1);}while(0)
28 static inline int MIN(int a, int b) { return (a < b ? a : b); }
29 static inline int MAX(int a, int b) { return (a > b ? a : b); }
32 extern tHWND gMainWindow;
34 typedef struct sLine tLine;
38 // TODO: Cache offsets to avoid scan-forward
62 bool bHaveSwappedBuffers;
75 void Display_int_SetCursor(tTerminal *Term, int Row, int Col);
78 tTerminal gMainBuffer;
80 int giCurrentLinePos; // byte offset, not column
82 int giFirstDispLine; // First displayed line
83 int giFirstLine; // Ring buffer start
84 char **gasDisplayLines;
85 int *gaiDisplayLineSizes;
86 char *gabDisplayLinesDirty;
89 tTerminal *Display_Init(int Cols, int Lines, int ExtraScrollbackLines)
91 tTerminal *term = &gMainBuffer;
92 term->ViewCols = Cols;
93 term->ViewRows = Lines;
94 term->TotalLines = Lines + ExtraScrollbackLines;
95 term->PriBuf = calloc( sizeof(tLine), (Lines + ExtraScrollbackLines) );
97 AxWin3_RichText_SetLineCount(gMainWindow, Lines+ExtraScrollbackLines);
98 AxWin3_RichText_SetCursorType(gMainWindow, 1); // TODO: enum
102 void *Display_GetTermState(tTerminal *Term) {
103 return Term->TermState;
105 void Display_SetTermState(tTerminal *Term, void *State) {
106 Term->TermState = State;
109 // Return the byte length of a single on-screen character
110 size_t _GetCharLength(size_t AvailLength, const char *Text, uint32_t *BaseCodepoint)
115 size_t charlen = ReadUTF8(Text, BaseCodepoint);
117 while(charlen < AvailLength)
120 size_t size = ReadUTF8(Text+charlen, &cp);
121 if( Unicode_IsPrinting(cp) )
129 tLine *Display_int_GetCurLine(tTerminal *Term)
131 int lineidx = Term->CursorRow + (Term->bUsingAltBuf ? 0 : Term->ViewOffset);
132 //_SysDebug("lineidx = %i", lineidx);
133 return (Term->bUsingAltBuf ? Term->AltBuf : Term->PriBuf) + lineidx;
136 size_t Display_int_PushCharacter(tTerminal *Term, size_t AvailLength, const char *Text)
138 tLine *lineptr = Display_int_GetCurLine(Term);
140 size_t charlen = _GetCharLength(AvailLength, Text, &cp);
141 bool bOverwrite = Unicode_IsPrinting(cp);
143 // Figure out how much we need to shift the stream
146 //_SysDebug("GetCharLen(%i-%i, %p+%i, NULL)", lineptr->Len, Term->CursorByte,
147 // lineptr->Data, Term->CursorByte);
148 if( Term->CursorByte )
149 assert(lineptr->Data);
150 size_t nextlen = _GetCharLength(
151 lineptr->Len-Term->CursorByte,
152 lineptr->Data+Term->CursorByte,
154 shift = charlen - nextlen;
160 // Ensure we have space enough
161 if( !lineptr->Data || shift > 0 ) {
162 const size_t size_step = 64;
164 size_t newsize = (lineptr->Len+shift+1 + size_step-1) & ~(size_step-1);
165 if( newsize > lineptr->Size ) {
166 lineptr->Size = newsize;
167 void *tmp = realloc(lineptr->Data, lineptr->Size);
168 if( !tmp ) perror("Display_int_PushCharacter - realloc");
169 //_SysDebug("realloc gave %p from %p for line %i", tmp, lineptr->Data,
175 // Insert into stream
176 char *base = lineptr->Data + Term->CursorByte;
177 if( Term->CursorByte == lineptr->Len ) {
178 // No shifting needed
180 else if( shift >= 0 ) {
181 size_t bytes = lineptr->Len - (Term->CursorByte+shift);
182 memmove(base+shift, base, bytes);
186 size_t bytes = lineptr->Len - (Term->CursorByte+shift);
187 memmove(base, base+shift, bytes);
189 memcpy(base, Text, charlen);
190 lineptr->IsDirty = true;
191 lineptr->Len += shift;
192 lineptr->Data[lineptr->Len] = 0; // NULL Terminate
194 Term->CursorByte += charlen;
196 // HACKY: Prevents the CursorCol++ in Display_AddText from having an effect
203 void Display_AddText(tTerminal *Term, size_t Length, const char *UTF8Text)
205 DEBUGS("%i += %i '%.*s'", Term->CursorRow, Length, Length, UTF8Text);
208 if( Term->CursorCol == Term->ViewCols ) {
209 Display_Newline(Term, 1);
211 size_t used = Display_int_PushCharacter(Term, Length, UTF8Text);
220 void Display_Newline(tTerminal *Term, bool bCarriageReturn)
223 if( Term->bUsingAltBuf )
225 if( Term->CursorRow == Term->ScrollTop + Term->ScrollRows-1 ) {
226 Display_ScrollDown(Term, 1);
228 else if( Term->CursorRow == Term->ViewRows-1 ) {
229 if( Term->ScrollRows == 0 ) {
230 // Scroll entire buffer
231 Display_ScrollDown(Term, 1);
234 // Don't go down a line
244 if( Term->CursorRow == Term->TotalLines-1 ) {
245 Display_ScrollDown(Term, 1);
252 if( bCarriageReturn )
254 Term->CursorByte = 0;
259 tLine *line = Display_int_GetCurLine(Term);
261 Term->CursorByte = 0;
262 int old_col = Term->CursorCol;
266 while( Term->CursorCol < old_col && ofs < line->Len ) {
267 ofs += _GetCharLength(line->Len-ofs, line->Data+ofs, NULL);
270 Term->CursorByte = ofs;
272 while( Term->CursorCol < old_col )
273 Display_AddText(Term, 1, " ");
277 void Display_SetScrollArea(tTerminal *Term, int Start, int Count)
281 Term->ScrollTop = Start;
282 Term->ScrollRows = MIN(Count, Term->ViewRows - Start);
285 void Display_ScrollDown(tTerminal *Term, int Count)
290 if( Term->bUsingAltBuf )
292 top = (Term->ScrollRows == 0 ? 0 : Term->ScrollTop);
293 max = (Term->ScrollRows == 0 ? Term->ViewRows : Term->ScrollRows);
294 buffer = Term->AltBuf;
299 max = Term->TotalLines;
300 buffer = Term->PriBuf;
304 assert(Count > -max);
306 DEBUGS("Scroll %p %i-%i down by %i", buffer, top, max, Count);
310 int clear_top, clear_max;
313 // -ve: Scroll up, move buffer contents down
315 for( int i = max-Count; i < max; i ++ )
316 free(buffer[i].Data);
317 memmove(buffer+Count, buffer, (max-Count)*sizeof(*buffer));
324 for( int i = 0; i < Count; i ++ )
325 free(buffer[i].Data);
326 memmove(buffer, buffer+Count, (max-Count)*sizeof(*buffer));
327 clear_top = max-Count;
330 // Clear exposed lines
331 for( int i = clear_top; i < clear_max; i ++ )
333 buffer[i].Data = NULL;
336 buffer[i].IsDirty = true;
338 // Send scroll command to GUI
339 AxWin3_RichText_ScrollRange(gMainWindow, top, max, Count);
341 Display_int_SetCursor(Term, Term->CursorRow, Term->CursorCol);
344 void Display_SetCursor(tTerminal *Term, int Row, int Col)
349 DEBUGS("Set cursor R%i,C%i", Row, Col);
351 if( !Term->bUsingAltBuf ) {
352 _SysDebug("NOTE: Using \\e[%i;%iH outside of alternat buffer is undefined", Row, Col);
355 // NOTE: This may be interesting outside of AltBuffer
356 Display_int_SetCursor(Term, Row, Col);
358 void Display_int_SetCursor(tTerminal *Term, int Row, int Col)
360 Term->CursorRow = Row;
361 tLine *line = Display_int_GetCurLine(Term);
364 for( i = 0; i < Col; i ++ )
367 size_t clen = _GetCharLength(line->Len-ofs, line->Data+ofs, NULL);
374 Term->CursorByte = ofs;
375 // Move to exactly the column specified
376 for( ; i < Col; i ++ ) {
377 Display_int_PushCharacter(Term, 1, " ");
382 void Display_MoveCursor(tTerminal *Term, int RelRow, int RelCol)
391 int req_col = Term->CursorCol + RelCol;
392 if( req_col < 0 ) req_col = 0;
393 if( req_col > Term->ViewCols ) req_col = Term->ViewCols;
395 tLine *line = Display_int_GetCurLine(Term);
397 for( int i = 0; i < req_col; i ++ )
399 size_t clen = _GetCharLength(line->Len-ofs, line->Data+ofs, NULL);
407 Term->CursorCol = req_col;
408 Term->CursorByte = ofs;
412 void Display_SaveCursor(tTerminal *Term)
414 Term->SavedRow = Term->CursorRow;
415 Term->SavedCol = Term->CursorCol;
417 void Display_RestoreCursor(tTerminal *Term)
419 Display_SetCursor(Term, Term->SavedRow, Term->SavedCol);
422 void Display_ClearLine(tTerminal *Term, int Dir) // 0: All, 1: Forward, -1: Reverse
426 tLine *line = Display_int_GetCurLine(Term);
427 // Completely clear line
431 line->IsDirty = true;
433 Term->CursorByte = 0;
437 // Forward clear (truncate)
441 // Reverse clear (replace with spaces)
449 void Display_ClearLines(tTerminal *Term, int Dir) // 0: All, 1: Forward, -1: Reverse
453 // Push giDisplayLines worth of empty lines
454 // Move cursor back up by giDisplayLines
458 // Push (giDisplayLines - (giCurrentLine-giFirstDispLine)) and reverse
462 // Reverse clear (replace with spaces)
470 void Display_ResetAttributes(tTerminal *Term)
474 void Display_SetForeground(tTerminal *Term, uint32_t RGB)
477 sprintf(buf, "\1%06x", RGB&0xFFFFFF);
478 Display_AddText(Term, 7, buf);
481 void Display_SetBackground(tTerminal *Term, uint32_t RGB)
484 sprintf(buf, "\2%06x", RGB&0xFFFFFF);
485 Display_AddText(Term, 7, buf);
488 void Display_Flush(tTerminal *Term)
490 int viewOfs = (Term->bUsingAltBuf ? 0 : Term->ViewOffset);
491 tLine *buffer = (Term->bUsingAltBuf ? Term->AltBuf : Term->PriBuf );
492 AxWin3_RichText_SetLineCount(gMainWindow, (Term->bUsingAltBuf ? Term->ViewRows : Term->TotalLines));
493 for( int i = 0; i < Term->ViewRows; i ++ )
495 int line = (viewOfs + i) % Term->TotalLines;
496 tLine *lineptr = &buffer[line];
497 // Swapping buffers should cause a full resend
498 if( !Term->bHaveSwappedBuffers && !lineptr->IsDirty )
500 DEBUGS("Line %i+%i %p '%.*s'", viewOfs, i, lineptr->Data, lineptr->Len, lineptr->Data);
501 AxWin3_RichText_SendLine(gMainWindow, viewOfs + i,
502 lineptr->Data ? lineptr->Data : "" );
503 lineptr->IsDirty = false;
505 AxWin3_RichText_SetCursorPos(gMainWindow, Term->CursorRow, Term->CursorCol);
506 Term->bHaveSwappedBuffers = false;
509 void Display_ShowAltBuffer(tTerminal *Term, bool AltBufEnabled)
511 if( Term->bUsingAltBuf == AltBufEnabled )
513 // Nothing to do, so do nothing
517 int row = Term->OtherBufRow;
518 int col = Term->OtherBufCol;
519 Term->OtherBufRow = Term->CursorRow;
520 Term->OtherBufCol = Term->CursorCol;
522 Term->bUsingAltBuf = AltBufEnabled;
523 Term->bHaveSwappedBuffers = true;
528 Term->AltBuf = calloc( sizeof(Term->AltBuf[0]), Term->ViewRows );
530 AxWin3_RichText_SetLineCount(gMainWindow, Term->ViewRows);
534 AxWin3_RichText_SetLineCount(gMainWindow, Term->TotalLines);
536 Display_int_SetCursor(Term, row, col);
539 void Display_SetTitle(tTerminal *Term, const char *Title)
541 _SysDebug("TODO: Set window title to '%s'", Title);