34 |
34 |
35 implementation |
35 implementation |
36 uses SDLh, uInputHandler, uTypes, uVariables, uCommands, uUtils, uTextures, uRender, uIO, uScript, uRenderUtils; |
36 uses SDLh, uInputHandler, uTypes, uVariables, uCommands, uUtils, uTextures, uRender, uIO, uScript, uRenderUtils; |
37 |
37 |
38 const MaxStrIndex = 27; |
38 const MaxStrIndex = 27; |
39 MaxInputStrLen = 240; |
39 MaxInputStrLen = 200; |
40 |
40 |
41 type TChatLine = record |
41 type TChatLine = record |
42 Tex: PTexture; |
42 Tex: PTexture; |
43 Time: Longword; |
43 Time: Longword; |
44 Width: LongInt; |
44 Width: LongInt; |
45 s: shortstring; |
45 s: shortstring; |
46 Color: TSDL_Color; |
46 Color: TSDL_Color; |
47 end; |
47 end; |
48 TChatCmd = (ccQuit, ccPause, ccFinish, ccShowHistory, ccFullScreen); |
48 TChatCmd = (ccQuit, ccPause, ccFinish, ccShowHistory, ccFullScreen); |
49 |
49 |
50 type TInputStrL = array[0..260] of byte; |
|
51 |
|
52 var Strs: array[0 .. MaxStrIndex] of TChatLine; |
50 var Strs: array[0 .. MaxStrIndex] of TChatLine; |
53 MStrs: array[0 .. MaxStrIndex] of shortstring; |
51 MStrs: array[0 .. MaxStrIndex] of shortstring; |
54 LocalStrs: array[0 .. MaxStrIndex] of shortstring; |
52 LocalStrs: array[0 .. MaxStrIndex] of shortstring; |
55 LocalStrsL: array[0 .. MaxStrIndex] of TInputStrL; |
|
56 missedCount: LongWord; |
53 missedCount: LongWord; |
57 lastStr: LongWord; |
54 lastStr: LongWord; |
58 localLastStr: LongInt; |
55 localLastStr: LongInt; |
59 history: LongInt; |
56 history: LongInt; |
60 visibleCount: LongWord; |
57 visibleCount: LongWord; |
61 InputStr: TChatLine; |
58 InputStr: TChatLine; |
62 InputStrL: TInputStrL; // for full str + 4-byte utf-8 char |
|
63 ChatReady: boolean; |
59 ChatReady: boolean; |
64 showAll: boolean; |
60 showAll: boolean; |
65 liveLua: boolean; |
61 liveLua: boolean; |
66 ChatHidden: boolean; |
62 ChatHidden: boolean; |
67 firstDraw: boolean; |
63 firstDraw: boolean; |
70 cursorPos, cursorX, selectedPos, selectionDx: LongInt; |
66 cursorPos, cursorX, selectedPos, selectionDx: LongInt; |
71 LastKeyPressTick: LongWord; |
67 LastKeyPressTick: LongWord; |
72 |
68 |
73 |
69 |
74 const |
70 const |
75 InputStrLNoPred: byte = 255; |
|
76 |
|
77 colors: array[#0..#6] of TSDL_Color = ( |
71 colors: array[#0..#6] of TSDL_Color = ( |
78 (r:$FF; g:$FF; b:$FF; a:$FF), // unused, feel free to take it for anything |
72 (r:$FF; g:$FF; b:$FF; a:$FF), // unused, feel free to take it for anything |
79 (r:$FF; g:$FF; b:$FF; a:$FF), // chat message [White] |
73 (r:$FF; g:$FF; b:$FF; a:$FF), // chat message [White] |
80 (r:$FF; g:$00; b:$FF; a:$FF), // action message [Purple] |
74 (r:$FF; g:$00; b:$FF; a:$FF), // action message [Purple] |
81 (r:$90; g:$FF; b:$90; a:$FF), // join/leave message [Lime] |
75 (r:$90; g:$FF; b:$90; a:$FF), // join/leave message [Lime] |
586 startIdx:= endIdx - (count - 1); |
586 startIdx:= endIdx - (count - 1); |
587 |
587 |
588 // delete bytes from string |
588 // delete bytes from string |
589 Delete(InputStr.s, startIdx, count); |
589 Delete(InputStr.s, startIdx, count); |
590 |
590 |
591 // wipe utf8 info for deleted char |
|
592 InputStrL[endIdx]:= InputStrLNoPred; |
|
593 |
|
594 // shift utf8 char info to reflect new string |
|
595 for i:= endIdx + 1 to Length(InputStr.s) + count do |
|
596 begin |
|
597 if InputStrL[i] <> InputStrLNoPred then |
|
598 begin |
|
599 InputStrL[i-count]:= InputStrL[i] - count; |
|
600 InputStrL[i]:= InputStrLNoPred; |
|
601 end; |
|
602 end; |
|
603 |
|
604 SetLine(InputStr, InputStr.s, true); |
591 SetLine(InputStr, InputStr.s, true); |
605 end; |
592 end; |
606 |
593 |
607 // returns count of removed bytes |
594 procedure MoveCursorToPreviousChar(); |
608 function DelCharFromInputStr(idx: integer): integer; |
595 begin |
609 var btw: byte; |
596 if cursorPos > 0 then |
610 begin |
597 begin |
611 // note: idx is always at last byte of utf8 chars. cuz relevant for InputStrL |
598 while (not IsFirstCharByte(InputStr.s[cursorPos])) do |
612 |
599 begin |
613 if (Length(InputStr.s) < 1) or (idx < 1) or (idx > Length(InputStr.s)) then |
600 dec(cursorPos); |
614 exit(0); |
601 end; |
615 |
602 dec(cursorPos); |
616 btw:= byte(idx) - InputStrL[idx]; |
603 end; |
617 |
604 end; |
618 DelCharFromInputStr:= btw; |
605 |
619 |
606 procedure MoveCursorToNextChar(); |
620 DelBytesFromInputStrBack(idx, btw); |
607 begin |
621 end; |
608 if cursorPos < Length(InputStr.s) then |
622 |
609 begin |
623 // unchecked |
610 inc(cursorPos, 2); |
624 procedure DoCursorStepForward(); |
611 while (cursorPos < Length(InputStr.s)) and (not IsFirstCharByte(InputStr.s[cursorPos])) do |
625 begin |
612 begin |
626 // go to end of next utf8-char |
613 inc(cursorPos); |
627 repeat |
614 end; |
628 inc(cursorPos); |
615 dec(cursorPos); |
629 until InputStrL[cursorPos] <> InputStrLNoPred; |
616 end; |
|
617 end; |
|
618 |
|
619 procedure DeleteLastUTF8CharFromStr(var s: shortstring); |
|
620 var l: byte; |
|
621 begin |
|
622 l:= Length(s); |
|
623 |
|
624 while (l > 1) and (not IsFirstCharByte(s[l])) do |
|
625 begin |
|
626 dec(l); |
|
627 end; |
|
628 |
|
629 if l > 0 then |
|
630 dec(l); |
|
631 |
|
632 s[0]:= char(l); |
630 end; |
633 end; |
631 |
634 |
632 procedure DeleteSelected(); |
635 procedure DeleteSelected(); |
633 begin |
636 begin |
634 if (selectedPos >= 0) and (cursorPos <> selectedPos) then |
637 if (selectedPos >= 0) and (cursorPos <> selectedPos) then |
635 begin |
638 begin |
636 DelBytesFromInputStrBack(max(cursorPos, selectedPos), abs(selectedPos-cursorPos)); |
639 DelBytesFromInputStrBack(max(cursorPos, selectedPos), abs(selectedPos-cursorPos)); |
637 cursorPos:= min(cursorPos, selectedPos); |
640 cursorPos:= min(cursorPos, selectedPos); |
638 ResetSelection(); |
641 end; |
639 end; |
642 ResetSelection(); |
640 UpdateCursorCoords(); |
643 UpdateCursorCoords(); |
641 end; |
644 end; |
642 |
645 |
643 procedure HandleSelection(enabled: boolean); |
646 procedure HandleSelection(enabled: boolean); |
644 begin |
647 begin |
654 type TCharSkip = ( none, wspace, numalpha, special ); |
657 type TCharSkip = ( none, wspace, numalpha, special ); |
655 |
658 |
656 function GetInputCharSkipClass(index: LongInt): TCharSkip; |
659 function GetInputCharSkipClass(index: LongInt): TCharSkip; |
657 var c: char; |
660 var c: char; |
658 begin |
661 begin |
659 // multi-byte chars counts as letter |
|
660 if (index > 1) and (InputStrL[index] <> index - 1) then |
|
661 exit(numalpha); |
|
662 |
|
663 c:= InputStr.s[index]; |
662 c:= InputStr.s[index]; |
664 |
663 |
665 // non-ascii counts as letter |
664 // non-ascii counts as letter |
666 if c > #127 then |
665 if c > #127 then |
667 exit(numalpha); |
666 exit(numalpha); |
698 // skip trailing whitespace, similar to Qt |
697 // skip trailing whitespace, similar to Qt |
699 while (skip = wspace) and (cursorPos > 0) do |
698 while (skip = wspace) and (cursorPos > 0) do |
700 begin |
699 begin |
701 skip:= GetInputCharSkipClass(cursorPos); |
700 skip:= GetInputCharSkipClass(cursorPos); |
702 if skip = wspace then |
701 if skip = wspace then |
703 cursorPos:= InputStrL[cursorPos]; |
702 MoveCursorToPreviousChar(); |
704 end; |
703 end; |
705 // skip same-type chars |
704 // skip same-type chars |
706 while (cursorPos > 0) and (GetInputCharSkipClass(cursorPos) = skip) do |
705 while (cursorPos > 0) and (GetInputCharSkipClass(cursorPos) = skip) do |
707 cursorPos:= InputStrL[cursorPos]; |
706 MoveCursorToPreviousChar(); |
708 end |
707 end |
709 else |
708 else |
710 begin |
709 begin |
711 // skip same-type chars |
710 // skip same-type chars |
712 while cursorPos < Length(InputStr.s) do |
711 while cursorPos < Length(InputStr.s) do |
713 begin |
712 begin |
714 DoCursorStepForward(); |
713 MoveCursorToNextChar(); |
715 if (GetInputCharSkipClass(cursorPos) <> skip) then |
714 if (GetInputCharSkipClass(cursorPos) <> skip) then |
716 begin |
715 begin |
717 // go back 1 char |
716 MoveCursorToPreviousChar(); |
718 cursorPos:= InputStrL[cursorPos]; |
|
719 break; |
717 break; |
720 end; |
718 end; |
721 end; |
719 end; |
722 // skip trailing whitespace, similar to Qt |
720 // skip trailing whitespace, similar to Qt |
723 while cursorPos < Length(InputStr.s) do |
721 while cursorPos < Length(InputStr.s) do |
724 begin |
722 begin |
725 DoCursorStepForward(); |
723 MoveCursorToNextChar(); |
726 if (GetInputCharSkipClass(cursorPos) <> wspace) then |
724 if (GetInputCharSkipClass(cursorPos) <> wspace) then |
727 begin |
725 begin |
728 // go back 1 char |
726 MoveCursorToPreviousChar(); |
729 cursorPos:= InputStrL[cursorPos]; |
|
730 break; |
727 break; |
731 end; |
728 end; |
732 end; |
729 end; |
733 end; |
730 end; |
734 end; |
731 end; |
746 selection:= copy(InputStr.s, min(CursorPos, selectedPos) + 1, abs(CursorPos - selectedPos)); |
743 selection:= copy(InputStr.s, min(CursorPos, selectedPos) + 1, abs(CursorPos - selectedPos)); |
747 CopyToClipboard(selection); |
744 CopyToClipboard(selection); |
748 end; |
745 end; |
749 end; |
746 end; |
750 |
747 |
751 // TODO: honor utf8, don't break utf8 chars when shifting chars beyond limit |
|
752 procedure InsertIntoInputStr(s: shortstring); |
748 procedure InsertIntoInputStr(s: shortstring); |
753 var i, l, il, lastc: integer; |
749 var limit: integer; |
754 begin |
750 begin |
755 // safe length for string |
751 // we check limit for trailing stuff before insertion limit for a reason |
756 l:= min(MaxInputStrLen-cursorPos, Length(s)); |
752 // (possible remaining space after too long UTF8-insertion has been shortened) |
757 s:= copy(s,1,l); |
753 |
758 |
754 // length limit for stuff to that will trail the insertion |
759 // if we insert rather than append, shift info in InputStrL accordingly |
755 limit:= max(cursorPos, MaxInputStrLen-Length(s)); |
760 if cursorPos < Length(InputStr.s) then |
756 |
761 begin |
757 while Length(InputStr.s) > limit do |
762 for i:= Length(InputStr.s) downto cursorPos + 1 do |
758 begin |
763 begin |
759 DeleteLastUTF8CharFromStr(InputStr.s); |
764 if InputStrL[i] <> InputStrLNoPred then |
760 end; |
765 begin |
761 |
766 il:= i + l; |
762 // length limit for stuff to insert |
767 // only shift if not overflowing |
763 limit:= max(0, MaxInputStrLen-cursorPos); |
768 if il <= MaxInputStrLen then |
764 |
769 InputStrL[il]:= InputStrL[i] + l; |
765 if limit = 0 then |
770 InputStrL[i]:= InputStrLNoPred; |
766 s:= '' |
771 end; |
767 else while Length(s) > limit do |
772 end; |
768 begin |
773 end; |
769 DeleteLastUTF8CharFromStr(s); |
774 |
770 end; |
775 InputStrL[cursorPos + l]:= cursorPos; |
771 |
776 // insert string truncated to safe length |
772 if Length(s) > 0 then |
777 Insert(s, InputStr.s, cursorPos + 1); |
773 begin |
778 if Length(InputStr.s) > MaxInputStrLen then |
774 // insert string truncated to safe length |
779 InputStr.s[0]:= char(MaxInputStrLen); |
775 Insert(s, InputStr.s, cursorPos + 1); |
780 |
776 |
781 SetLine(InputStr, InputStr.s, true); |
777 if Length(InputStr.s) > MaxInputStrLen then |
782 |
778 InputStr.s[0]:= char(MaxInputStrLen); |
783 // move cursor to end of inserted string |
779 |
784 lastc:= MaxInputStrLen; |
780 SetLine(InputStr, InputStr.s, true); |
785 cursorPos:= min(lastc, cursorPos + l); |
781 |
786 UpdateCursorCoords(); |
782 // move cursor to end of inserted string |
|
783 inc(cursorPos, Length(s)); |
|
784 UpdateCursorCoords(); |
|
785 end; |
787 end; |
786 end; |
788 |
787 |
789 procedure PasteFromClipboard(); |
788 procedure PasteFromClipboard(); |
790 begin |
789 begin |
791 SendIPC(_S'Y'); |
790 SendIPC(_S'Y'); |
819 case Sym of |
818 case Sym of |
820 SDLK_BACKSPACE: |
819 SDLK_BACKSPACE: |
821 begin |
820 begin |
822 if selectedPos < 0 then |
821 if selectedPos < 0 then |
823 begin |
822 begin |
|
823 HandleSelection(true); |
|
824 |
|
825 // delete more if ctrl is held |
824 if ctrl then |
826 if ctrl then |
825 skip:= GetInputCharSkipClass(cursorPos); |
827 SkipInputChars(GetInputCharSkipClass(cursorPos), true) |
826 |
828 else |
827 // remove char before cursor |
829 MoveCursorToPreviousChar(); |
828 dec(cursorPos, DelCharFromInputStr(cursorPos)); |
830 |
|
831 end; |
|
832 |
|
833 DeleteSelected(); |
|
834 UpdateCursorCoords(); |
|
835 end; |
|
836 SDLK_DELETE: |
|
837 begin |
|
838 if selectedPos < 0 then |
|
839 begin |
|
840 HandleSelection(true); |
829 |
841 |
830 // delete more if ctrl is held |
842 // delete more if ctrl is held |
831 if ctrl and (selectedPos < 0) then |
843 if ctrl then |
832 begin |
844 SkipInputChars(GetInputCharSkipClass(cursorPos), false) |
833 HandleSelection(true); |
|
834 SkipInputChars(skip, true); |
|
835 DeleteSelected(); |
|
836 end |
|
837 else |
845 else |
838 UpdateCursorCoords(); |
846 MoveCursorToNextChar(); |
839 |
847 |
840 end |
848 end; |
841 else |
849 |
842 DeleteSelected(); |
850 DeleteSelected(); |
843 end; |
851 UpdateCursorCoords(); |
844 SDLK_DELETE: |
|
845 begin |
|
846 if selectedPos < 0 then |
|
847 begin |
|
848 // remove char after cursor |
|
849 if cursorPos < Length(InputStr.s) then |
|
850 begin |
|
851 DoCursorStepForward(); |
|
852 if ctrl then |
|
853 skip:= GetInputCharSkipClass(cursorPos); |
|
854 |
|
855 // delete char |
|
856 dec(cursorPos, DelCharFromInputStr(cursorPos)); |
|
857 |
|
858 // delete more if ctrl is held |
|
859 if ctrl and (cursorPos < Length(InputStr.s)) then |
|
860 begin |
|
861 HandleSelection(true); |
|
862 SkipInputChars(skip, false); |
|
863 DeleteSelected(); |
|
864 end; |
|
865 end |
|
866 else |
|
867 UpdateCursorCoords(); |
|
868 end |
|
869 else |
|
870 DeleteSelected(); |
|
871 end; |
852 end; |
872 SDLK_ESCAPE: |
853 SDLK_ESCAPE: |
873 begin |
854 begin |
874 if Length(InputStr.s) > 0 then |
855 if Length(InputStr.s) > 0 then |
875 begin |
856 begin |
876 SetLine(InputStr, '', true); |
857 SetLine(InputStr, '', true); |
877 FillChar(InputStrL, sizeof(InputStrL), InputStrLNoPred); |
|
878 ResetCursor(); |
858 ResetCursor(); |
879 end |
859 end |
880 else CleanupInput |
860 else CleanupInput |
881 end; |
861 end; |
882 SDLK_RETURN, SDLK_KP_ENTER: |
862 SDLK_RETURN, SDLK_KP_ENTER: |
883 begin |
863 begin |
884 if Length(InputStr.s) > 0 then |
864 if Length(InputStr.s) > 0 then |
885 begin |
865 begin |
886 AcceptChatString(InputStr.s); |
866 AcceptChatString(InputStr.s); |
887 SetLine(InputStr, '', false); |
867 SetLine(InputStr, '', false); |
888 FillChar(InputStrL, sizeof(InputStrL), InputStrLNoPred); |
|
889 ResetCursor(); |
868 ResetCursor(); |
890 end; |
869 end; |
891 CleanupInput |
870 CleanupInput |
892 end; |
871 end; |
893 SDLK_UP, SDLK_DOWN: |
872 SDLK_UP, SDLK_DOWN: |
896 if (Sym = SDLK_DOWN) and (history > 0) then dec(history); |
875 if (Sym = SDLK_DOWN) and (history > 0) then dec(history); |
897 index:= localLastStr - history + 1; |
876 index:= localLastStr - history + 1; |
898 if (index > localLastStr) then |
877 if (index > localLastStr) then |
899 begin |
878 begin |
900 SetLine(InputStr, '', true); |
879 SetLine(InputStr, '', true); |
901 FillChar(InputStrL, sizeof(InputStrL), InputStrLNoPred); |
|
902 end |
880 end |
903 else |
881 else |
904 begin |
882 begin |
905 SetLine(InputStr, LocalStrs[index], true); |
883 SetLine(InputStr, LocalStrs[index], true); |
906 InputStrL:= LocalStrsL[index]; |
|
907 end; |
884 end; |
908 cursorPos:= Length(InputStr.s); |
885 cursorPos:= Length(InputStr.s); |
909 ResetSelection(); |
886 ResetSelection(); |
910 UpdateCursorCoords(); |
887 UpdateCursorCoords(); |
911 end; |
888 end; |
944 |
921 |
945 if selMode or (selectedPos < 0) then |
922 if selMode or (selectedPos < 0) then |
946 begin |
923 begin |
947 HandleSelection(selMode); |
924 HandleSelection(selMode); |
948 // go to end of previous utf8-char |
925 // go to end of previous utf8-char |
949 cursorPos:= InputStrL[cursorPos]; |
926 MoveCursorToPreviousChar(); |
950 end |
927 end |
951 else // if we're leaving selection mode, jump to its left end |
928 else // if we're leaving selection mode, jump to its left end |
952 begin |
929 begin |
953 cursorPos:= min(cursorPos, selectedPos); |
930 cursorPos:= min(cursorPos, selectedPos); |
954 ResetSelection(); |
931 ResetSelection(); |
1060 utf8:= char(Key or firstByteMark[Pred(btw)]) + utf8; |
1040 utf8:= char(Key or firstByteMark[Pred(btw)]) + utf8; |
1061 |
1041 |
1062 if Length(InputStr.s) + btw > MaxInputStrLen then |
1042 if Length(InputStr.s) + btw > MaxInputStrLen then |
1063 exit; |
1043 exit; |
1064 |
1044 |
|
1045 // if speech bubble quotes are used as first input, add the closing quote and place cursor inbetween |
1065 if (Length(InputStr.s) = 0) and (Length(utf8) = 1) and (charIsForHogSpeech(utf8[1])) then |
1046 if (Length(InputStr.s) = 0) and (Length(utf8) = 1) and (charIsForHogSpeech(utf8[1])) then |
1066 begin |
1047 begin |
1067 InsertIntoInputStr(utf8); |
1048 InsertIntoInputStr(utf8); |
1068 InsertIntoInputStr(utf8); |
1049 InsertIntoInputStr(utf8); |
1069 cursorPos:= 1; |
1050 cursorPos:= 1; |
1125 if length(s) = 0 then |
1106 if length(s) = 0 then |
1126 SetLine(InputStr, '', true) |
1107 SetLine(InputStr, '', true) |
1127 else |
1108 else |
1128 begin |
1109 begin |
1129 SetLine(InputStr, '/team ', true); |
1110 SetLine(InputStr, '/team ', true); |
1130 // update InputStrL and cursor accordingly |
|
1131 // this allows cursor-jumping over '/team ' as if it was a single char |
|
1132 InputStrL[6]:= 0; |
|
1133 cursorPos:= 6; |
1111 cursorPos:= 6; |
1134 UpdateCursorCoords(); |
1112 UpdateCursorCoords(); |
1135 end; |
1113 end; |
1136 end; |
1114 end; |
1137 |
1115 |