Subversion Repositories oidplus

Rev

Rev 748 | Rev 992 | Go to most recent revision | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
733 daniel-mar 1
unit VTSCUI;
2
 
3
(************************************************)
4
(* VTSCUI.PAS                                   *)
5
(* Author:   Daniel Marschall                   *)
758 daniel-mar 6
(* Revision: 2022-02-27                         *)
733 daniel-mar 7
(* License:  Apache 2.0                         *)
8
(* This file contains:                          *)
9
(* - ViaThinkSoft CUI (Console User Interface)  *)
10
(************************************************)
11
 
12
interface
13
 
14
uses
15
  StrList;
16
 
17
const
746 daniel-mar 18
  (* These are available in DRIVERS.TPU, but require a call of InitVideo
733 daniel-mar 19
  ScreenWidth = 80;
20
  ScreenHeight = 25;
746 daniel-mar 21
  *)
733 daniel-mar 22
  SINGLE_LINE_BOX_PADDING = 3;
23
  SINGLE_LINE_BOX_PADDING_INNER = 10;
24
 
25
procedure DrawThinBorder(x, y, width, height: integer);
26
procedure DrawDoubleBorder(x, y, width, height: integer);
27
procedure DrawTextBar(str: string; line: integer);
738 daniel-mar 28
procedure DrawTitleBar(center, left, right: string);
733 daniel-mar 29
procedure DrawStatusBar(str: string);
30
function DrawSelectionList(X, Y, ListWidth, ListHeight: integer;
31
                           items: PStringList; allowESC: boolean;
32
                           Title: string; borderStrength: integer): integer;
33
procedure ClearSection(x, y, width, height: integer);
34
function QueryVal(var s: string; initX, initY, width, height: integer;
35
                  Title: string; borderStrength: integer): boolean;
36
procedure ShowMessage(msg: string; title: string; dobeep: boolean);
746 daniel-mar 37
procedure CursorOn;
38
procedure CursorOff;
747 daniel-mar 39
procedure ResetDefaultDosColors;
748 daniel-mar 40
procedure WriteLnKeepX(s: string);
733 daniel-mar 41
 
42
implementation
43
 
44
uses
45
  Crt, Drivers, VtsFuncs;
46
 
47
type
48
  TCharDefs = array[0..7] of char;
49
 
50
const
51
  ThinLineChars: TCharDefs =
52
    ( #$DA, #$C4, #$BF,
53
      #$B3,       #$B3,
54
      #$C0, #$C4, #$D9
55
    );
56
  DoubleLineChars: TCharDefs =
57
    ( #$C9, #$CD, #$BB,
58
      #$BA,       #$BA,
59
      #$C8, #$CD, #$BC
60
    );
61
 
62
function FillRight(str: string; len: integer; c: char): string;
63
var
64
  s: string;
65
  i: integer;
66
begin
67
  s := str;
68
  for i := Length(str) to len-1 do
69
  begin
70
    s := s + c;
71
  end;
72
  FillRight := s;
73
end;
74
 
75
procedure DrawBorder(x, y, width, height, thickness: integer);
76
var
77
  ix,iy: integer;
78
  chars: TCharDefs;
79
begin
80
  if thickness = 1 then
81
    chars := ThinLineChars;
82
  if thickness = 2 then
83
    chars := DoubleLineChars;
84
 
85
  (* Top line *)
86
  if y >= 1 then
87
  begin
88
    (* Top left corner *)
89
    if x >= 1 then
90
    begin
91
      GotoXY(x,y);
92
      Write(chars[0]);
93
    end
94
    else
95
    begin
96
      GotoXY(1,y);
97
    end;
98
 
99
    (* Top edge *)
100
    for ix := 1 to width-2 do
101
      Write(chars[1]);
102
 
103
    (* Top right corner *)
104
    if x+width-1 <= ScreenWidth then
105
      Write(chars[2]);
106
  end;
107
 
108
  (* Left edge *)
109
  for iy := 1 to height-2 do
110
  begin
111
    if (x    >= 1) and (x    <= ScreenWidth) and
112
       (y+iy >= 1) and (y+iy <= ScreenHeight) then
113
    begin
114
      GotoXY(x,y+iy);
115
      Write(chars[3]);
116
    end;
117
  end;
118
 
119
  (* Right edge *)
120
  for iy := 1 to height-2 do
121
  begin
122
    if (x+width-1 >= 1) and (x+width-1 <= ScreenWidth) and
123
       (y+iy      >= 1) and (y+iy      <= ScreenHeight) then
124
    begin
125
      GotoXY(x+width-1,y+iy);
126
      Write(chars[4]);
127
    end;
128
  end;
129
 
130
  (* Bottom line *)
131
  if y+height-1 <= ScreenHeight then
132
  begin
133
    (* Bottom left corner *)
134
    if x >= 1 then
135
    begin
136
      GotoXY(x,y+height-1);
137
      Write(chars[5]);
138
    end
139
    else
140
    begin
141
      GotoXY(1,y+height-1);
142
    end;
143
 
144
    (* Bottom edge *)
145
    for ix := 1 to width-2 do
146
      Write(chars[6]);
147
 
148
    (* Bottom right corner *)
149
    if x+width-1 <= ScreenWidth then
150
      Write(chars[7]);
151
  end;
152
end;
153
 
154
procedure DrawThinBorder(x, y, width, height: integer);
155
begin
156
  DrawBorder(x, y, width, height, 1);
157
end;
158
 
159
procedure DrawDoubleBorder(x, y, width, height: integer);
160
begin
161
  DrawBorder(x, y, width, height, 2);
162
end;
163
 
164
procedure DrawTextBar(str: string; line: integer);
165
var
166
  i, left, right: integer;
167
  len: integer;
168
begin
169
  GotoXY(1,line);
170
 
171
  TextBackground(White);
172
  TextColor(Black);
173
  len := Length(str);
174
 
175
  left := round((ScreenWidth-len)/2);
176
  right := ScreenWidth - left - len;
177
 
178
  for i := 1 to left do
179
  begin
180
    Write(' ');
181
  end;
182
 
183
  Write(str);
184
 
185
  (* TODO: If we do "for i := 1 to right", then the console will scroll *)
186
  (*       since the char in the right bottom corner is written!        *)
187
  for i := 1 to right-1 do
188
  begin
189
    Write(' ');
190
  end;
746 daniel-mar 191
  GoToXY(1,1);
733 daniel-mar 192
 
193
  TextBackground(Black);
194
  TextColor(White);
195
end;
196
 
738 daniel-mar 197
procedure DrawTitleBar(center, left, right: string);
746 daniel-mar 198
var
199
  bakx, baky: integer;
733 daniel-mar 200
begin
746 daniel-mar 201
  bakx := WhereX;
202
  baky := WhereY;
203
 
738 daniel-mar 204
  DrawTextBar(center, 1);
205
 
206
  (* Put left text into the title bar *)
207
  GoToXY(1,1);
208
  TextBackground(White);
209
  TextColor(Black);
210
  WriteLn(left);
211
  TextBackground(Black);
212
  TextColor(White);
213
 
214
  (* Put right text into the title bar *)
215
  GotoXY(ScreenWidth-Length(right)+1,1);
216
  TextBackground(White);
217
  TextColor(Black);
218
  WriteLn(right);
219
  TextBackground(Black);
220
  TextColor(White);
746 daniel-mar 221
 
222
  GoToXY(bakx, baky);
733 daniel-mar 223
end;
224
 
225
procedure DrawStatusBar(str: string);
746 daniel-mar 226
var
227
  bakx, baky: integer;
733 daniel-mar 228
begin
746 daniel-mar 229
  bakx := WhereX;
230
  baky := WhereY;
733 daniel-mar 231
  DrawTextBar(str, ScreenHeight);
746 daniel-mar 232
  GoToXY(bakx, baky);
733 daniel-mar 233
end;
234
 
235
function DrawSelectionList(X, Y, ListWidth, ListHeight: integer;
236
                           items: PStringList; allowESC: boolean;
237
                           Title: string; borderStrength: integer): integer;
238
var
239
  i: integer;
240
  itemIndex: integer;
241
  sc: char;
242
  iStartScope, iEndScope: integer;
735 daniel-mar 243
  sTmp: string;
733 daniel-mar 244
label
245
  doAgain;
246
begin
247
  if borderStrength = 1 then
248
  begin
249
    DrawThinBorder(X-1, Y-1, ListWidth+2, ListHeight+2);
250
  end;
251
 
252
  if borderStrength = 2 then
253
  begin
254
    DrawDoubleBorder(X-1, Y-1, ListWidth+2, ListHeight+2);
255
  end;
256
 
257
  if Title <> '' then
258
  begin
259
    if borderStrength > 0 then
260
      GoToXY(X+1, Y-1)
261
    else
262
      GoToXY(X, Y-1);
263
    Write(Title);
264
  end;
265
 
266
  (*CursorOff;*)
267
 
268
  itemIndex := 0;
269
  iStartScope := itemIndex;
270
  iEndScope := itemIndex + ListHeight;
271
 
272
doAgain:
273
 
274
  if itemIndex < 0 then
275
    itemIndex := 0;
276
  if itemIndex > ListCount(items)-1 then
277
    itemIndex := ListCount(items)-1;
278
 
279
  if itemIndex < iStartScope then
280
  begin
281
    Dec(iEndScope);
282
    Dec(iStartScope);
283
  end;
284
 
285
  if itemIndex > iEndScope-1 then
286
  begin
287
    Inc(iEndScope);
288
    Inc(iStartScope);
289
  end;
290
 
291
  if borderStrength > 0 then
292
  begin
293
    (* Show scroll arrows *)
294
    GotoXY(X+ListWidth, Y);
295
    if iStartScope > 0 then
296
    begin
297
      TextBackground(White);
298
      TextColor(Black);
299
      WriteLn(#$18(*ArrowUp*));
300
      TextBackground(Black);
301
      TextColor(White);
302
    end
303
    else if borderStrength = 1 then
304
      WriteLn(ThinLineChars[4])
305
    else if borderStrength = 2 then
306
      WriteLn(DoubleLineChars[4]);
307
 
308
    GotoXY(X+ListWidth, Y+ListHeight-1);
309
    if ListCount(items) > iEndScope then
310
    begin
311
      TextBackground(White);
312
      TextColor(Black);
313
      WriteLn(#$19(*ArrowDown*));
314
      TextBackground(Black);
315
      TextColor(White);
316
    end
317
    else if borderStrength = 1 then
318
      WriteLn(ThinLineChars[4])
319
    else if borderStrength = 2 then
320
      WriteLn(DoubleLineChars[4]);
321
  end;
322
 
323
  for i := iStartScope to iEndScope-1 do
324
  begin
325
    if itemIndex = i then
326
    begin
327
      TextColor(Black);
328
      TextBackground(White);
329
    end
330
    else
331
    begin
332
      TextColor(White);
333
      TextBackground(Black);
334
    end;
335
    GotoXY(x,y+i-iStartScope);
336
    if i > ListCount(items)-1 then
337
      Write(FillRight('', ListWidth, ' '))
338
    else
735 daniel-mar 339
    begin
340
      sTmp := ListGetElement(items, i);
740 daniel-mar 341
      sTmp := TrimLineToWidth(sTmp, ListWidth);
735 daniel-mar 342
      Write(FillRight(sTmp, ListWidth, ' '));
343
    end;
733 daniel-mar 344
    TextColor(White);
345
    TextBackground(Black);
346
  end;
347
 
348
  repeat
349
    GotoXY(ScreenWidth, ScreenHeight);
350
 
351
    sc := ReadKey;
352
    if sc = #$00(*ExtendedKeyCode*) then
353
    begin
354
      sc := ReadKey;
355
      if sc = #$48(*UpKey*) then
356
      begin
357
        dec(itemIndex);
358
        goto doAgain;
359
      end
360
      else if sc = #$50(*DownKey*) then
361
      begin
362
        inc(itemIndex);
363
        goto doAgain;
364
      end
365
      else if sc = #$47(*POS1*) then
366
      begin
367
        itemIndex := 0;
758 daniel-mar 368
        iStartScope := itemIndex;
740 daniel-mar 369
        iEndScope := iStartScope + ListHeight;
733 daniel-mar 370
        goto doAgain;
371
      end
372
      else if sc = #$4F(*END*) then
373
      begin
740 daniel-mar 374
        itemIndex := ListCount(items)-1;
375
        iStartScope := itemIndex - Min(ListHeight,ListCount(items));
376
        iEndScope := itemIndex;
733 daniel-mar 377
        goto doAgain;
758 daniel-mar 378
      end
379
      else if sc = #$49(*PgUp*) then
380
      begin
381
        Dec(itemIndex, ListHeight);
382
        if itemIndex < 0 then
383
          itemIndex := 0;
384
        iStartScope := itemIndex;
385
        iEndScope := itemIndex + ListHeight;
386
        goto doAgain;
387
      end
388
      else if sc = #$51(*PgDown*) then
389
      begin
390
        Inc(itemIndex, ListHeight);
391
        if itemIndex > ListCount(items)-1 then
392
          itemIndex := ListCount(items)-1;
393
        iStartScope := itemIndex - Min(ListHeight,ListCount(items));
394
        iEndScope := itemIndex;
395
        goto doAgain;
733 daniel-mar 396
      end;
397
    end;
398
 
399
    if sc = #13(*Return*) then
400
    begin
401
      DrawSelectionList := itemIndex;
402
      break;
403
    end;
404
 
405
    if allowESC and (sc = #27(*ESC*)) then
406
    begin
407
      DrawSelectionList := -1;
408
      break;
409
    end;
410
  until false;
411
 
412
  (*CursorOn;*)
413
end;
414
 
415
procedure ClearSection(x, y, width, height: integer);
416
var
417
  ix, iy: integer;
418
begin
419
  for iy := y to y+height-1 do
420
  begin
421
    for ix := x to x+width-1 do
422
    begin
423
      GoToXY(ix,iy);
424
      Write(' ');
425
    end;
426
  end;
427
end;
428
 
429
function QueryVal(var s: string; initX, initY, width, height: integer;
430
                  Title: string; borderStrength: integer): boolean;
431
var
432
  x, y: integer;
433
  i, j: integer;
434
  sc: char;
435
  stmp: string;
436
begin
437
  if borderStrength = 1 then
438
    DrawThinBorder(initX-1,initY-1,width+2,height+2);
439
  if borderStrength = 2 then
440
    DrawDoubleBorder(initX-1,initY-1,width+2,height+2);
441
  if title <> '' then
442
  begin
443
    if borderStrength > 0 then
444
      GoToXY(initX+1, initY-1)
445
    else
446
      GoToXY(initX, initY-1);
447
    Write(title);
448
  end;
449
  ClearSection(initX,initY,width,height);
450
 
451
  x := initX;
452
  y := initY;
453
 
454
  (* Write existing string value and set cursor *)
455
  stmp := s;
456
  s := '';
457
  for i := 1 to Length(stmp) do
458
  begin
459
    if stmp[i] = #10 then
460
    begin
461
      s := s + stmp[i];
462
      continue;
463
    end;
464
 
465
    GoToXY(x,y);
466
    s := s + stmp[i];
467
    Write(stmp[i]);
468
    Inc(x);
469
 
470
    if (x=initX+width-1) and (y=initY+height-1) then
471
    begin
472
      (* Attention: Data following after this will be lost! *)
473
      break;
474
    end;
475
 
476
    if stmp[i] = #13 then
477
    begin
478
      if y=initY+height-1 then
479
      begin
480
        (* Attention: Data following after this will be lost! *)
481
        s := Copy(s, 1, Length(s)-1);
482
        Dec(x);
483
        break;
484
      end;
485
      x := initX;
486
      Inc(y);
487
      continue;
488
    end;
489
 
490
    if x=initX+width then
491
    begin
492
      Inc(y);
493
      x := initX;
494
    end;
495
  end;
496
 
497
  repeat
498
    GotoXY(x, y);
499
    sc := ReadKey;
500
 
501
    if sc = #0 then
502
    begin
503
      (* Extended key code *)
504
      sc := ReadKey;
505
      (* TODO: Implement keys like DEL, END, POS1, and ArrowKeys *)
506
      Beep;
507
      continue;
508
    end
509
    else if sc = #8(*Backspace*) then
510
    begin
511
      if x <= initX then
512
      begin
513
        if y = initY then
514
        begin
515
          Beep;
516
          continue;
517
        end;
518
        Dec(y);
519
 
520
        (* Find out length of previous line *)
521
        j := Length(s)-2(*CRLF*);
522
        while (j >= 0) do
523
        begin
524
          if (s[j]=#13) or (s[j]=#10) then break;
525
          Dec(j);
526
        end;
527
        j := Length(s)-2(*CRLF*)-j;
528
        x := initX + j;
529
        s := Copy(s, 1, Length(s)-1); (* Remove #10 now. #13 will be removed below *)
530
      end
531
      else
532
      begin
533
        Dec(x);
534
      end;
535
      GotoXY(x, y);
536
      Write(' ');
537
      GotoXY(x, y);
538
      s := Copy(s, 1, Length(s)-1);
539
      continue;
540
    end
541
    else if sc = #13(*Return*) then
542
    begin
543
      if GetShiftState and kbRightShift <> 0 then
544
      begin
545
        if y=initY+height-1 then
546
        begin
547
          Beep;
548
          continue;
549
        end;
550
        s := s + #13 + #10;
551
        x := initX;
552
        Inc(y);
553
      end
554
      else
555
      begin
556
        QueryVal := true;
557
        exit;
558
      end;
559
    end
560
    else if sc = #27(*ESC*) then
561
    begin
562
      QueryVal := false;
563
      exit;
564
    end
565
    else
566
    begin
567
      if (x=initX+width-1) and (y=initY+height-1) then
568
      begin
569
        Beep;
570
        continue;
571
      end;
572
      s := s + sc;
573
      Write(sc);
574
      Inc(x);
575
      if x >= initX+width then
576
      begin
577
        Inc(y);
578
        x := initX;
579
      end;
580
    end;
581
  until false;
582
end;
583
 
584
procedure ShowMessage(msg: string; title: string; dobeep: boolean);
585
var
586
  x, y, w, h: integer;
587
begin
588
  x := SINGLE_LINE_BOX_PADDING_INNER;
589
  y := ScreenHeight div 2 - 1;
590
  w := ScreenWidth - (SINGLE_LINE_BOX_PADDING_INNER-1)*2;
591
  h := 1;
592
  DrawDoubleBorder(x-1, y, w+2, h+2);
593
  ClearSection(x, y+1, w-2, h);
594
  if title <> '' then
595
  begin
596
    GoToXY(x+1, y);
597
    Write(title);
598
  end;
599
  GoToXY(x, y+1);
600
  Write(msg);
601
  if DoBeep then Beep;
602
end;
603
 
746 daniel-mar 604
procedure CursorOn; assembler;
605
asm
606
  mov ah,1     (* Set text-mode cursor shape *)
607
  mov cx,0607h (* normal underline cursor *)
608
  int 10h
609
end;
610
 
611
procedure CursorOff; assembler;
612
asm
613
  mov ah,1     (* Set text-mode cursor shape *)
614
  mov cx,2607h (* hide cursor (Start>End) *)
615
  int 10h
616
end;
617
 
747 daniel-mar 618
procedure ResetDefaultDosColors;
619
begin
620
  TextBackground(Black);
621
  TextColor(LightGray);
622
end;
623
 
748 daniel-mar 624
procedure WriteLnKeepX(s: string);
625
var
626
  i: integer;
627
  initX: integer;
628
  bytesToWrite: integer;
629
  pNewLine: integer;
630
begin
631
  initX := WhereX;
632
  while Length(s) > 0 do
633
  begin
634
    pNewLine := Pos(#13#10, s);
635
    GoToXy(initX, WhereY);
636
    bytesToWrite := ScreenWidth - initX + 1;
637
    if (pNewLine > 0) and (pNewLine < bytesToWrite) then
638
      bytesToWrite := pNewLine;
639
    Write(Copy(s, 1, bytesToWrite)); (* No WriteLn because there is automatic scrolling *)
640
    Delete(s, 1, bytesToWrite);
641
  end;
642
  WriteLn('');
643
end;
644
 
733 daniel-mar 645
end.