Subversion Repositories oidplus

Rev

Rev 747 | Rev 758 | 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                   *)
748 daniel-mar 6
(* Revision: 2022-02-19                         *)
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;
740 daniel-mar 368
        iStartScope := 0;
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;
378
      end;
747 daniel-mar 379
      (* TODO: Implement PgUp and PgDown keys *)
733 daniel-mar 380
    end;
381
 
382
    if sc = #13(*Return*) then
383
    begin
384
      DrawSelectionList := itemIndex;
385
      break;
386
    end;
387
 
388
    if allowESC and (sc = #27(*ESC*)) then
389
    begin
390
      DrawSelectionList := -1;
391
      break;
392
    end;
393
  until false;
394
 
395
  (*CursorOn;*)
396
end;
397
 
398
procedure ClearSection(x, y, width, height: integer);
399
var
400
  ix, iy: integer;
401
begin
402
  for iy := y to y+height-1 do
403
  begin
404
    for ix := x to x+width-1 do
405
    begin
406
      GoToXY(ix,iy);
407
      Write(' ');
408
    end;
409
  end;
410
end;
411
 
412
function QueryVal(var s: string; initX, initY, width, height: integer;
413
                  Title: string; borderStrength: integer): boolean;
414
var
415
  x, y: integer;
416
  i, j: integer;
417
  sc: char;
418
  stmp: string;
419
begin
420
  if borderStrength = 1 then
421
    DrawThinBorder(initX-1,initY-1,width+2,height+2);
422
  if borderStrength = 2 then
423
    DrawDoubleBorder(initX-1,initY-1,width+2,height+2);
424
  if title <> '' then
425
  begin
426
    if borderStrength > 0 then
427
      GoToXY(initX+1, initY-1)
428
    else
429
      GoToXY(initX, initY-1);
430
    Write(title);
431
  end;
432
  ClearSection(initX,initY,width,height);
433
 
434
  x := initX;
435
  y := initY;
436
 
437
  (* Write existing string value and set cursor *)
438
  stmp := s;
439
  s := '';
440
  for i := 1 to Length(stmp) do
441
  begin
442
    if stmp[i] = #10 then
443
    begin
444
      s := s + stmp[i];
445
      continue;
446
    end;
447
 
448
    GoToXY(x,y);
449
    s := s + stmp[i];
450
    Write(stmp[i]);
451
    Inc(x);
452
 
453
    if (x=initX+width-1) and (y=initY+height-1) then
454
    begin
455
      (* Attention: Data following after this will be lost! *)
456
      break;
457
    end;
458
 
459
    if stmp[i] = #13 then
460
    begin
461
      if y=initY+height-1 then
462
      begin
463
        (* Attention: Data following after this will be lost! *)
464
        s := Copy(s, 1, Length(s)-1);
465
        Dec(x);
466
        break;
467
      end;
468
      x := initX;
469
      Inc(y);
470
      continue;
471
    end;
472
 
473
    if x=initX+width then
474
    begin
475
      Inc(y);
476
      x := initX;
477
    end;
478
  end;
479
 
480
  repeat
481
    GotoXY(x, y);
482
    sc := ReadKey;
483
 
484
    if sc = #0 then
485
    begin
486
      (* Extended key code *)
487
      sc := ReadKey;
488
      (* TODO: Implement keys like DEL, END, POS1, and ArrowKeys *)
489
      Beep;
490
      continue;
491
    end
492
    else if sc = #8(*Backspace*) then
493
    begin
494
      if x <= initX then
495
      begin
496
        if y = initY then
497
        begin
498
          Beep;
499
          continue;
500
        end;
501
        Dec(y);
502
 
503
        (* Find out length of previous line *)
504
        j := Length(s)-2(*CRLF*);
505
        while (j >= 0) do
506
        begin
507
          if (s[j]=#13) or (s[j]=#10) then break;
508
          Dec(j);
509
        end;
510
        j := Length(s)-2(*CRLF*)-j;
511
        x := initX + j;
512
        s := Copy(s, 1, Length(s)-1); (* Remove #10 now. #13 will be removed below *)
513
      end
514
      else
515
      begin
516
        Dec(x);
517
      end;
518
      GotoXY(x, y);
519
      Write(' ');
520
      GotoXY(x, y);
521
      s := Copy(s, 1, Length(s)-1);
522
      continue;
523
    end
524
    else if sc = #13(*Return*) then
525
    begin
526
      if GetShiftState and kbRightShift <> 0 then
527
      begin
528
        if y=initY+height-1 then
529
        begin
530
          Beep;
531
          continue;
532
        end;
533
        s := s + #13 + #10;
534
        x := initX;
535
        Inc(y);
536
      end
537
      else
538
      begin
539
        QueryVal := true;
540
        exit;
541
      end;
542
    end
543
    else if sc = #27(*ESC*) then
544
    begin
545
      QueryVal := false;
546
      exit;
547
    end
548
    else
549
    begin
550
      if (x=initX+width-1) and (y=initY+height-1) then
551
      begin
552
        Beep;
553
        continue;
554
      end;
555
      s := s + sc;
556
      Write(sc);
557
      Inc(x);
558
      if x >= initX+width then
559
      begin
560
        Inc(y);
561
        x := initX;
562
      end;
563
    end;
564
  until false;
565
end;
566
 
567
procedure ShowMessage(msg: string; title: string; dobeep: boolean);
568
var
569
  x, y, w, h: integer;
570
begin
571
  x := SINGLE_LINE_BOX_PADDING_INNER;
572
  y := ScreenHeight div 2 - 1;
573
  w := ScreenWidth - (SINGLE_LINE_BOX_PADDING_INNER-1)*2;
574
  h := 1;
575
  DrawDoubleBorder(x-1, y, w+2, h+2);
576
  ClearSection(x, y+1, w-2, h);
577
  if title <> '' then
578
  begin
579
    GoToXY(x+1, y);
580
    Write(title);
581
  end;
582
  GoToXY(x, y+1);
583
  Write(msg);
584
  if DoBeep then Beep;
585
end;
586
 
746 daniel-mar 587
procedure CursorOn; assembler;
588
asm
589
  mov ah,1     (* Set text-mode cursor shape *)
590
  mov cx,0607h (* normal underline cursor *)
591
  int 10h
592
end;
593
 
594
procedure CursorOff; assembler;
595
asm
596
  mov ah,1     (* Set text-mode cursor shape *)
597
  mov cx,2607h (* hide cursor (Start>End) *)
598
  int 10h
599
end;
600
 
747 daniel-mar 601
procedure ResetDefaultDosColors;
602
begin
603
  TextBackground(Black);
604
  TextColor(LightGray);
605
end;
606
 
748 daniel-mar 607
procedure WriteLnKeepX(s: string);
608
var
609
  i: integer;
610
  initX: integer;
611
  bytesToWrite: integer;
612
  pNewLine: integer;
613
begin
614
  initX := WhereX;
615
  while Length(s) > 0 do
616
  begin
617
    pNewLine := Pos(#13#10, s);
618
    GoToXy(initX, WhereY);
619
    bytesToWrite := ScreenWidth - initX + 1;
620
    if (pNewLine > 0) and (pNewLine < bytesToWrite) then
621
      bytesToWrite := pNewLine;
622
    Write(Copy(s, 1, bytesToWrite)); (* No WriteLn because there is automatic scrolling *)
623
    Delete(s, 1, bytesToWrite);
624
  end;
625
  WriteLn('');
626
end;
627
 
733 daniel-mar 628
end.