Subversion Repositories oidplus

Rev

Rev 748 | Rev 992 | 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-27                         *)
  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. procedure ResetDefaultDosColors;
  40. procedure WriteLnKeepX(s: string);
  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;
  191.   GoToXY(1,1);
  192.  
  193.   TextBackground(Black);
  194.   TextColor(White);
  195. end;
  196.  
  197. procedure DrawTitleBar(center, left, right: string);
  198. var
  199.   bakx, baky: integer;
  200. begin
  201.   bakx := WhereX;
  202.   baky := WhereY;
  203.  
  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);
  221.  
  222.   GoToXY(bakx, baky);
  223. end;
  224.  
  225. procedure DrawStatusBar(str: string);
  226. var
  227.   bakx, baky: integer;
  228. begin
  229.   bakx := WhereX;
  230.   baky := WhereY;
  231.   DrawTextBar(str, ScreenHeight);
  232.   GoToXY(bakx, baky);
  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;
  243.   sTmp: string;
  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
  339.     begin
  340.       sTmp := ListGetElement(items, i);
  341.       sTmp := TrimLineToWidth(sTmp, ListWidth);
  342.       Write(FillRight(sTmp, ListWidth, ' '));
  343.     end;
  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;
  368.         iStartScope := itemIndex;
  369.         iEndScope := iStartScope + ListHeight;
  370.         goto doAgain;
  371.       end
  372.       else if sc = #$4F(*END*) then
  373.       begin
  374.         itemIndex := ListCount(items)-1;
  375.         iStartScope := itemIndex - Min(ListHeight,ListCount(items));
  376.         iEndScope := itemIndex;
  377.         goto doAgain;
  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;
  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.  
  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.  
  618. procedure ResetDefaultDosColors;
  619. begin
  620.   TextBackground(Black);
  621.   TextColor(LightGray);
  622. end;
  623.  
  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.  
  645. end.
  646.