Subversion Repositories oidplus

Rev

Rev 740 | Rev 747 | Go to most recent revision | Blame | Compare with Previous | Last modification | View Log | RSS feed

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