From 39eed53aba6bb3a16ae71dba6ea0a858906a70db Mon Sep 17 00:00:00 2001 From: UnknownShadow200 Date: Tue, 24 Oct 2017 13:59:19 +1100 Subject: [PATCH] Port InputWidget to C. --- ClassicalSharp/2D/Widgets/InputWidget.cs | 5 +- ClassicalSharp/Utils/WrappableStringBuffer.cs | 10 +- src/Client/Drawer2D.c | 8 +- src/Client/Drawer2D.h | 1 + src/Client/IModel.c | 6 +- src/Client/Input.h | 1 + src/Client/PackedCol.c | 8 +- src/Client/PackedCol.h | 23 +- src/Client/String.c | 2 +- src/Client/String.h | 2 +- src/Client/Utils.c | 4 + src/Client/Utils.h | 2 + src/Client/Widgets.c | 444 +++++++++++++++++- src/Client/Widgets.h | 8 +- src/Client/WinWindow.c | 1 + src/Client/WordWrap.c | 14 +- src/Client/WordWrap.h | 4 +- 17 files changed, 498 insertions(+), 45 deletions(-) diff --git a/ClassicalSharp/2D/Widgets/InputWidget.cs b/ClassicalSharp/2D/Widgets/InputWidget.cs index 428e3a29d..a7adb7be4 100644 --- a/ClassicalSharp/2D/Widgets/InputWidget.cs +++ b/ClassicalSharp/2D/Widgets/InputWidget.cs @@ -68,8 +68,9 @@ namespace ClassicalSharp.Gui.Widgets { protected double caretAccumulator; public override void Init() { - if (UsedLines > 1) { - Text.WordWrap(game.Drawer2D, lines, MaxCharsPerLine); + int numLines = UsedLines; + if (numLines > 1) { + Text.WordWrap(game.Drawer2D, lines, numLines, MaxCharsPerLine); } else { lines[0] = Text.ToString(); } diff --git a/ClassicalSharp/Utils/WrappableStringBuffer.cs b/ClassicalSharp/Utils/WrappableStringBuffer.cs index 6f524e353..fcf80d406 100644 --- a/ClassicalSharp/Utils/WrappableStringBuffer.cs +++ b/ClassicalSharp/Utils/WrappableStringBuffer.cs @@ -10,7 +10,7 @@ namespace ClassicalSharp { wrap = new char[capacity]; } - public void WordWrap(IDrawer2D drawer, string[] lines, int maxPerLine) { + public void WordWrap(IDrawer2D drawer, string[] lines, int maxLines, int maxPerLine) { int len = Length; int* lineLens = stackalloc int[lines.Length]; for (int i = 0; i < lines.Length; i++) { @@ -22,7 +22,7 @@ namespace ClassicalSharp { char[] realText = value; MakeWrapCopy(); - int usedLines = 0, totalChars = maxPerLine * lines.Length; + int usedLines = 0, totalChars = maxPerLine * maxLines; for (int index = 0; index < totalChars; index += maxPerLine) { if (value[index] == '\0') break; @@ -44,7 +44,7 @@ namespace ClassicalSharp { } // Output the used lines - OutputLines(drawer, lines, lineLens, usedLines, maxPerLine); + OutputLines(drawer, lines, lineLens, usedLines, maxLines, maxPerLine); value = realText; } @@ -58,8 +58,8 @@ namespace ClassicalSharp { value = wrap; } - void OutputLines(IDrawer2D drawer, string[] lines, int* lineLens, int usedLines, int charsPerLine) { - int totalChars = charsPerLine * lines.Length; + void OutputLines(IDrawer2D drawer, string[] lines, int* lineLens, int usedLines, int maxLines, int charsPerLine) { + int totalChars = charsPerLine * maxLines; for (int i = 0; i < totalChars; i++) { if (value[i] == '\0') value[i] = ' '; } diff --git a/src/Client/Drawer2D.c b/src/Client/Drawer2D.c index 72b0e317a..6a87efdae 100644 --- a/src/Client/Drawer2D.c +++ b/src/Client/Drawer2D.c @@ -10,6 +10,12 @@ void DrawTextArgs_Make(DrawTextArgs* args, STRING_REF String* text, FontDesc* fo args->UseShadow = useShadow; } +void DrawTextArgs_MakeEmpty(DrawTextArgs* args, FontDesc* font, bool useShadow) { + args->Text = String_MakeNull(); + args->Font = *font; + args->UseShadow = useShadow; +} + Bitmap Drawer2D_FontBitmap; Bitmap* Drawer2D_Cur; Int32 Drawer2D_BoxSize; @@ -269,7 +275,7 @@ void Drawer2D_DrawPart(DrawTextArgs* args, Int32 x, Int32 y, bool shadowCol) { coordsPtr[0] = (UInt8)coords; runCount = 1; continue; } - if (lastY == (coords >> 4) && col.Packed == lastCol.Packed) { + if (lastY == (coords >> 4) && PackedCol_Equals(col, lastCol)) { coordsPtr[runCount] = (UInt8)coords; runCount++; continue; } diff --git a/src/Client/Drawer2D.h b/src/Client/Drawer2D.h index 412d63c38..5d632ce07 100644 --- a/src/Client/Drawer2D.h +++ b/src/Client/Drawer2D.h @@ -13,6 +13,7 @@ Copyright 2017 ClassicalSharp | Licensed under BSD-3 typedef struct DrawTextArgs_ { String Text; FontDesc Font; bool UseShadow; } DrawTextArgs; void DrawTextArgs_Make(DrawTextArgs* args, STRING_REF String* text, FontDesc* font, bool useShadow); +void DrawTextArgs_MakeEmpty(DrawTextArgs* args, FontDesc* font, bool useShadow); const float Offset = 1.3f; diff --git a/src/Client/IModel.c b/src/Client/IModel.c index 6fce87466..31eb99dde 100644 --- a/src/Client/IModel.c +++ b/src/Client/IModel.c @@ -104,9 +104,9 @@ void IModel_SetupState(IModel* model, Entity* entity) { IModel_Cols[0] = col; if (!entity->NoShade) { - IModel_Cols[1] = PackedCol_Scale(col, PackedCol_ShadeYBottom); - IModel_Cols[2] = PackedCol_Scale(col, PackedCol_ShadeZ); - IModel_Cols[4] = PackedCol_Scale(col, PackedCol_ShadeX); + IModel_Cols[1] = PackedCol_Scale(col, PACKEDCOL_SHADE_YMIN); + IModel_Cols[2] = PackedCol_Scale(col, PACKEDCOL_SHADE_Z); + IModel_Cols[4] = PackedCol_Scale(col, PACKEDCOL_SHADE_X); } else { IModel_Cols[1] = col; IModel_Cols[2] = col; IModel_Cols[4] = col; } diff --git a/src/Client/Input.h b/src/Client/Input.h index ff9f2b71c..c94028884 100644 --- a/src/Client/Input.h +++ b/src/Client/Input.h @@ -83,6 +83,7 @@ void Key_SetPressed(Key key, bool pressed); #define Key_IsAltPressed() (Key_GetPressed(Key_AltLeft) || Key_GetPressed(Key_AltRight)) #define Key_IsControlPressed() (Key_GetPressed(Key_ControlLeft) || Key_GetPressed(Key_ControlRight)) #define Key_IsShiftPressed() (Key_GetPressed(Key_ShiftLeft) || Key_GetPressed(Key_ShiftRight)) +#define Key_IsWinPressed() (Key_GetPressed(Key_WinLeft) || Key_GetPressed(Key_WinRight)) /* Gets whether key repeating is on or not. If on (desirable for text input), multiple KeyDowns (varies by OS) are generated for the same key when it is held down for a period of time. Should be off for game input. */ diff --git a/src/Client/PackedCol.c b/src/Client/PackedCol.c index 8f9656c02..9905ba57b 100644 --- a/src/Client/PackedCol.c +++ b/src/Client/PackedCol.c @@ -31,8 +31,8 @@ PackedCol PackedCol_Lerp(PackedCol a, PackedCol b, Real32 t) { return a; } -void PackedCol_GetShaded(PackedCol normal, PackedCol* xSide, PackedCol* zSide, PackedCol* yBottom) { - *xSide = PackedCol_Scale(normal, PackedCol_ShadeX); - *zSide = PackedCol_Scale(normal, PackedCol_ShadeZ); - *yBottom = PackedCol_Scale(normal, PackedCol_ShadeYBottom); +void PackedCol_GetShaded(PackedCol normal, PackedCol* xSide, PackedCol* zSide, PackedCol* yMin) { + *xSide = PackedCol_Scale(normal, PACKEDCOL_SHADE_X); + *zSide = PackedCol_Scale(normal, PACKEDCOL_SHADE_Z); + *yMin = PackedCol_Scale(normal, PACKEDCOL_SHADE_YMIN); } \ No newline at end of file diff --git a/src/Client/PackedCol.h b/src/Client/PackedCol.h index f5348ddcd..2cd62af6e 100644 --- a/src/Client/PackedCol.h +++ b/src/Client/PackedCol.h @@ -29,7 +29,7 @@ PackedCol PackedCol_Create4(UInt8 r, UInt8 g, UInt8 b, UInt8 a); /* Constructs a new ARGB colour. */ PackedCol PackedCol_Create3(UInt8 r, UInt8 g, UInt8 b); /* Returns whether two packed colours are equal. */ -#define PackedCol_Equals(a, b) (a.Packed == b.Packed) +#define PackedCol_Equals(a, b) ((a).Packed == (b).Packed) /* Converts a colour to ARGB form. */ #define PackedCol_ARGB(r, g, b, a) (((UInt32)(r) << 16) | ((UInt32)(g) << 8) | ((UInt32)(b)) | ((UInt32)(a) << 24)) /* Converts a colour to ARGB form. */ @@ -39,15 +39,18 @@ PackedCol PackedCol_Scale(PackedCol value, Real32 t); /* Linearly interpolates the RGB components of both colours by t, where t is in [0, 1] */ PackedCol PackedCol_Lerp(PackedCol a, PackedCol b, Real32 t); -#define PackedCol_ShadeX 0.6f -#define PackedCol_ShadeZ 0.8f -#define PackedCol_ShadeYBottom 0.5f +#define PACKEDCOL_SHADE_X 0.6f +#define PACKEDCOL_SHADE_Z 0.8f +#define PACKEDCOL_SHADE_YMIN 0.5f /* Retrieves shaded colours for ambient block face lighting. */ -void PackedCol_GetShaded(PackedCol normal, PackedCol* xSide, PackedCol* zSide, PackedCol* yBottom); +void PackedCol_GetShaded(PackedCol normal, PackedCol* xSide, PackedCol* zSide, PackedCol* yMin); -#define PACKEDCOL_WHITE PACKEDCOL_CONST(255, 255, 255, 255) -#define PACKEDCOL_BLACK PACKEDCOL_CONST( 0, 0, 0, 255) -#define PACKEDCOL_RED PACKEDCOL_CONST(255, 0, 0, 255) -#define PACKEDCOL_GREEN PACKEDCOL_CONST( 0, 255, 0, 255) -#define PACKEDCOL_BLUE PACKEDCOL_CONST( 0, 0, 255, 255) +#define PACKEDCOL_WHITE PACKEDCOL_CONST(255, 255, 255, 255) +#define PACKEDCOL_BLACK PACKEDCOL_CONST( 0, 0, 0, 255) +#define PACKEDCOL_RED PACKEDCOL_CONST(255, 0, 0, 255) +#define PACKEDCOL_GREEN PACKEDCOL_CONST( 0, 255, 0, 255) +#define PACKEDCOL_BLUE PACKEDCOL_CONST( 0, 0, 255, 255) +#define PACKEDCOL_YELLOW PACKEDCOL_CONST(255, 255, 0, 255); +#define PACKEDCOL_MAGENTA PACKEDCOL_CONST(255, 0, 255, 255); +#define PACKEDCOL_CYAN PACKEDCOL_CONST( 0, 255, 255, 255); #endif \ No newline at end of file diff --git a/src/Client/String.c b/src/Client/String.c index d8eec0cb1..2c098f251 100644 --- a/src/Client/String.c +++ b/src/Client/String.c @@ -188,7 +188,7 @@ UInt8 String_CharAt(STRING_TRANSIENT String* str, Int32 offset) { return str->buffer[offset]; } -void String_InsertAt(STRING_TRANSIENT String* str, UInt8 c, Int32 offset) { +void String_InsertAt(STRING_TRANSIENT String* str, Int32 offset, UInt8 c) { if (offset < 0 || offset > str->length) { ErrorHandler_Fail("Offset for InsertAt out of range"); } diff --git a/src/Client/String.h b/src/Client/String.h index 84334d4c2..5c6fc380c 100644 --- a/src/Client/String.h +++ b/src/Client/String.h @@ -64,7 +64,7 @@ Int32 String_LastIndexOf(STRING_TRANSIENT String* str, UInt8 c); /* Gets the character at the given index in the string. */ UInt8 String_CharAt(STRING_TRANSIENT String* str, Int32 offset); /* Inserts a character at the given index in the string. */ -void String_InsertAt(STRING_TRANSIENT String* str, UInt8 c, Int32 offset); +void String_InsertAt(STRING_TRANSIENT String* str, Int32 offset, UInt8 c); /* Deletes a character at the given index in the string. */ void String_DeleteAt(STRING_TRANSIENT String* str, Int32 offset); diff --git a/src/Client/Utils.c b/src/Client/Utils.c index 87c1e72eb..b2145d360 100644 --- a/src/Client/Utils.c +++ b/src/Client/Utils.c @@ -16,4 +16,8 @@ UInt32 Utils_ParseEnum(STRING_TRANSIENT String* text, UInt32 defValue, const UIn if (String_CaselessEquals(text, &name)) return i; } return defValue; +} + +bool Utils_IsValidInputChar(UInt8 c, bool supportsCP437) { + return supportsCP437 || (Convert_CP437ToUnicode(c) == c); } \ No newline at end of file diff --git a/src/Client/Utils.h b/src/Client/Utils.h index 042661a44..2d8ab873b 100644 --- a/src/Client/Utils.h +++ b/src/Client/Utils.h @@ -7,5 +7,7 @@ Int32 Utils_AccumulateWheelDelta(Real32* accmulator, Real32 delta); UInt32 Utils_ParseEnum(STRING_TRANSIENT String* text, UInt32 defValue, const UInt8** names, UInt32 namesCount); +bool Utils_IsValidInputChar(UInt8 c, bool supportsCP437); + #define Utils_AdjViewDist(value) ((Int32)(1.4142135f * (value))) #endif \ No newline at end of file diff --git a/src/Client/Widgets.c b/src/Client/Widgets.c index 57f40c9af..d70907252 100644 --- a/src/Client/Widgets.c +++ b/src/Client/Widgets.c @@ -10,6 +10,8 @@ #include "Utils.h" #include "ModelCache.h" #include "Screens.h" +#include "Platform.h" +#include "WordWrap.h" void Widget_SetLocation(Widget* widget, Anchor horAnchor, Anchor verAnchor, Int32 xOffset, Int32 yOffset) { widget->HorAnchor = horAnchor; widget->VerAnchor = verAnchor; @@ -878,7 +880,7 @@ void SpecialInputWidget_IntersectsBody(SpecialInputWidget* widget, Int32 x, Int3 SpecialInputAppendFunc append = widget->AppendFunc; if (widget->SelectedIndex == 0) { - /* TODO: need to insert characters that don't affect caret index, adjust caret colour */ + /* TODO: need to insert characters that don't affect widget->CaretPos index, adjust widget->CaretPos colour */ append(widget->AppendObj, e.Contents.buffer[index * e.CharsPerItem]); append(widget->AppendObj, e.Contents.buffer[index * e.CharsPerItem + 1]); } else { @@ -921,8 +923,7 @@ void SpecialInputWidget_InitTabs(SpecialInputWidget* widget) { #define SPECIAL_CONTENT_SPACING 5 Int32 SpecialInputWidget_MeasureTitles(SpecialInputWidget* widget) { Int32 totalWidth = 0; - String str = String_MakeNull(); - DrawTextArgs args; DrawTextArgs_Make(&args, &str, &widget->Font, false); + DrawTextArgs args; DrawTextArgs_MakeEmpty(&args, &widget->Font, false); Int32 i; for (i = 0; i < Array_NumElements(widget->Tabs); i++) { @@ -936,8 +937,7 @@ Int32 SpecialInputWidget_MeasureTitles(SpecialInputWidget* widget) { void SpecialInputWidget_DrawTitles(SpecialInputWidget* widget) { Int32 x = 0; - String str = String_MakeNull(); - DrawTextArgs args; DrawTextArgs_Make(&args, &str, &widget->Font, false); + DrawTextArgs args; DrawTextArgs_MakeEmpty(&args, &widget->Font, false); Int32 i; PackedCol col_selected = PACKEDCOL_CONST(30, 30, 30, 200); @@ -1079,4 +1079,438 @@ void SpecialInputWidget_Create(SpecialInputWidget* widget, FontDesc* font, Speci widget->Base.Base.Render = SpecialInputWidget_Render; widget->Base.Base.Free = SpecialInputWidget_Free; widget->Base.Base.HandlesMouseDown = SpecialInputWidget_HandlesMouseDown; +} + + +void InputWidget_CalculateLineSizes(InputWidget* widget) { + Int32 y; + for (y = 0; y < INPUTWIDGET_MAX_LINES; y++) { + widget->LineSizes[y] = Size2D_Empty; + } + widget->LineSizes[0].Width = widget->PrefixWidth; + + DrawTextArgs args; DrawTextArgs_MakeEmpty(&args, &widget->Font, true); + for (y = 0; y < widget->GetMaxLines(); y++) { + args.Text = widget->Lines[y]; + Size2D textSize = Drawer2D_MeasureText(&args); + widget->LineSizes[y].Width += textSize.Width; + widget->LineSizes[y].Height = textSize.Height; + } + + if (widget->LineSizes[0].Height == 0) { + widget->LineSizes[0].Height = widget->PrefixHeight; + } +} + +UInt8 InputWidget_GetLastCol(InputWidget* widget, Int32 indexX, Int32 indexY) { + Int32 x = indexX, y; + for (y = indexY; y >= 0; y--) { + UInt8 code = Drawer2D_LastCol(&widget->Lines[y], x); + if (code != NULL) return code; + if (y > 0) { x = widget->Lines[y - 1].length; } + } + return NULL; +} + +void InputWidget_UpdateCaret(InputWidget* widget) { + Int32 maxChars = widget->GetMaxLines() * widget->MaxCharsPerLine; + if (widget->CaretPos >= maxChars) widget->CaretPos = -1; + WordWrap_GetCoords(widget->CaretPos, widget->Lines, widget->GetMaxLines(), &widget->CaretX, &widget->CaretY); + DrawTextArgs args; DrawTextArgs_MakeEmpty(&args, &widget->Font, false); + widget->CaretAccumulator = 0; + + /* Caret is at last character on line */ + Widget* elem = &widget->Base; + if (widget->CaretX == widget->MaxCharsPerLine) { + widget->CaretTex.X = elem->X + widget->Padding + widget->LineSizes[widget->CaretY].Width; + PackedCol yellow = PACKEDCOL_YELLOW; widget->CaretCol = yellow; + widget->CaretTex.Width = widget->CaretWidth; + } else { + String* line = &widget->Lines[widget->CaretY]; + args.Text = String_UNSAFE_Substring(line, 0, widget->CaretX); + Size2D trimmedSize = Drawer2D_MeasureText(&args); + if (widget->CaretY == 0) { trimmedSize.Width += widget->PrefixWidth; } + + widget->CaretTex.X = elem->X + widget->Padding + trimmedSize.Width; + PackedCol white = PACKEDCOL_WHITE; + widget->CaretCol = PackedCol_Scale(white, 0.8f); + + if (widget->CaretX < line->length) { + args.Text = String_UNSAFE_Substring(line, widget->CaretX, 1); + args.UseShadow = true; + widget->CaretTex.Width = (UInt16)Drawer2D_MeasureText(&args).Width; + } else { + widget->CaretTex.Width = widget->CaretWidth; + } + } + widget->CaretTex.Y = widget->LineSizes[0].Height * widget->CaretY + widget->InputTex.Y + 2; + + /* Update the colour of the widget->CaretPos */ + UInt8 code = InputWidget_GetLastCol(widget, widget->CaretX, widget->CaretY); + if (code != NULL) widget->CaretCol = Drawer2D_Cols[code]; +} + +void InputWidget_RenderCaret(InputWidget* widget, Real64 delta) { + if (!widget->ShowCaret) return; + + widget->CaretAccumulator += delta; + Real64 second = widget->CaretAccumulator - (Real64)Math_Floor((Real32)widget->CaretAccumulator); + if (second < 0.5) { + GfxCommon_Draw2DTexture(&widget->CaretTex, widget->CaretCol); + } +} + +void InputWidget_RemakeTexture(InputWidget* widget) { + Int32 totalHeight = 0, maxWidth = 0, i; + for (i = 0; i < widget->GetMaxLines(); i++) { + totalHeight += widget->LineSizes[i].Height; + maxWidth = max(maxWidth, widget->LineSizes[i].Width); + } + Size2D size = Size2D_Make(maxWidth, totalHeight); + widget->CaretAccumulator = 0; + + Int32 realHeight = 0; + Bitmap bmp; Bitmap_AllocatePow2(&bmp, size.Width, size.Height); + Drawer2D_Begin(&bmp); + + DrawTextArgs args; DrawTextArgs_MakeEmpty(&args, &widget->Font, true); + if (widget->Prefix.length > 0) { + args.Text = widget->Prefix; + Drawer2D_DrawText(&args, 0, 0); + } + + UInt8 tmpBuffer[String_BufferSize(STRING_SIZE + 2)]; + for (i = 0; i < Array_NumElements(widget->Lines); i++) { + if (widget->Lines[i].length == 0) break; + args.Text = widget->Lines[i]; + UInt8 lastCol = InputWidget_GetLastCol(widget, 0, i); + + /* Colour code goes to next line */ + if (!Drawer2D_IsWhiteCol(lastCol)) { + String tmp = String_FromRawBuffer(tmpBuffer, STRING_SIZE + 2); + String_Append(&tmp, '&'); String_Append(&tmp, lastCol); + String_AppendString(&tmp, &args.Text); + args.Text = tmp; + } + + Int32 offset = i == 0 ? widget->PrefixWidth : 0; + Drawer2D_DrawText(&args, offset, realHeight); + realHeight += widget->LineSizes[i].Height; + } + widget->InputTex = Drawer2D_Make2DTexture(&bmp, size, 0, 0); + + Drawer2D_End(); + Platform_MemFree(bmp.Scan0); + + Widget* elem = &widget->Base; + elem->Width = size.Width; + elem->Height = realHeight == 0 ? widget->PrefixHeight : realHeight; + elem->Reposition(elem); + widget->InputTex.X = elem->X + widget->Padding; + widget->InputTex.Y = elem->Y; +} + +void InputWidget_EnterInput(InputWidget* widget) { + InputWidget_Clear(widget); + widget->Base.Height = widget->PrefixHeight; +} + +void InputWidget_Clear(InputWidget* widget) { + String_Clear(&widget->Text); + Int32 i; + for (i = 0; i < Array_NumElements(widget->Lines); i++) { + String_Clear(&widget->Lines[i]); + } + + widget->CaretPos = -1; + Gfx_DeleteTexture(&widget->InputTex.ID); +} + +bool InputWidget_AllowedChar(InputWidget* widget, UInt8 c) { + return Utils_IsValidInputChar(c, game.Server.SupportsFullCP437); +} + +void InputWidget_AppendChar(InputWidget* widget, UInt8 c) { + if (widget->CaretPos == -1) { + String_InsertAt(&widget->Text, widget->Text.length, c); + } else { + String_InsertAt(&widget->Text, widget->CaretPos, c); + widget->CaretPos++; + if (widget->CaretPos >= widget->Text.length) { widget->CaretPos = -1; } + } +} + +bool InputWidget_TryAppendChar(InputWidget* widget, UInt8 c) { + Int32 maxChars = widget->GetMaxLines() * widget->MaxCharsPerLine; + if (widget->Text.length >= maxChars) return false; + if (!InputWidget_AllowedChar(widget, c)) return false; + + InputWidget_AppendChar(widget, c); + return true; +} + +void InputWidget_AppendString(InputWidget* widget, String text) { + Int32 appended = 0, i; + for (i = 0; i < text.length; i++) { + if (InputWidget_TryAppendChar(widget, text.buffer[i])) appended++; + } + + if (appended == 0) return; + GuiElement* elem = &widget->Base.Base; + elem->Recreate(elem); +} + +void InputWidget_Append(InputWidget* widget, UInt8 c) { + if (!InputWidget_TryAppendChar(widget, c)) return; + GuiElement* elem = &widget->Base.Base; + elem->Recreate(elem); +} + +void InputWidget_DeleteChar(InputWidget* widget) { + if (widget->Text.length == 0) return; + + if (widget->CaretPos == -1) { + String_DeleteAt(&widget->Text, widget->Text.length - 1); + } else if (widget->CaretPos > 0) { + widget->CaretPos--; + String_DeleteAt(&widget->Text, widget->CaretPos); + } +} + +bool InputWidget_CheckCol(InputWidget* widget, Int32 index) { + if (index < 0) return false; + UInt8 code = widget->Text.buffer[index]; + UInt8 col = widget->Text.buffer[index + 1]; + return (code == '%' || code == '&') && Drawer2D_ValidColCode(col); +} + +void InputWidget_BackspaceKey(InputWidget* widget, bool controlDown) { + if (controlDown) { + if (widget->CaretPos == -1) { widget->CaretPos = widget->Text.length - 1; } + Int32 len = WordWrap_GetBackLength(&widget->Text, widget->CaretPos); + if (len == 0) return; + + widget->CaretPos -= len; + if (widget->CaretPos < 0) { widget->CaretPos = 0; } + Int32 i; + for (i = 0; i <= len; i++) { + String_DeleteAt(&widget->Text, widget->CaretPos); + } + + if (widget->CaretPos >= widget->Text.length) { widget->CaretPos = -1; } + if (widget->CaretPos == -1 && widget->Text.length > 0) { + String_InsertAt(&widget->Text, widget->Text.length, ' '); + } else if (widget->CaretPos >= 0 && widget->Text.buffer[widget->CaretPos] != ' ') { + String_InsertAt(&widget->Text, widget->CaretPos, ' '); + } + GuiElement* elem = &widget->Base.Base; + elem->Recreate(elem); + } else if (widget->Text.length > 0 && widget->CaretPos != 0) { + Int32 index = widget->CaretPos == -1 ? widget->Text.length - 1 : widget->CaretPos; + if (InputWidget_CheckCol(widget, index - 1)) { + InputWidget_DeleteChar(widget); /* backspace XYZ%e to XYZ */ + } else if (InputWidget_CheckCol(widget, index - 2)) { + InputWidget_DeleteChar(widget); + InputWidget_DeleteChar(widget); /* backspace XYZ%eH to XYZ */ + } + + InputWidget_DeleteChar(widget); + GuiElement* elem = &widget->Base.Base; + elem->Recreate(elem); + } +} + +void InputWidget_DeleteKey(InputWidget* widget) { + if (widget->Text.length > 0 && widget->CaretPos != -1) { + String_DeleteAt(&widget->Text, widget->CaretPos); + if (widget->CaretPos >= widget->Text.length) { widget->CaretPos = -1; } + GuiElement* elem = &widget->Base.Base; + elem->Recreate(elem); + } +} + +void InputWidget_LeftKey(InputWidget* widget, bool controlDown) { + if (controlDown) { + if (widget->CaretPos == -1) { widget->CaretPos = widget->Text.length - 1; } + widget->CaretPos -= WordWrap_GetBackLength(&widget->Text, widget->CaretPos); + InputWidget_UpdateCaret(widget); + return; + } + + if (widget->Text.length > 0) { + if (widget->CaretPos == -1) { widget->CaretPos = widget->Text.length; } + widget->CaretPos--; + if (widget->CaretPos < 0) { widget->CaretPos = 0; } + InputWidget_UpdateCaret(widget); + } +} + +void InputWidget_RightKey(InputWidget* widget, bool controlDown) { + if (controlDown) { + widget->CaretPos += WordWrap_GetForwardLength(&widget->Text, widget->CaretPos); + if (widget->CaretPos >= widget->Text.length) { widget->CaretPos = -1; } + InputWidget_UpdateCaret(widget); + return; + } + + if (widget->Text.length > 0 && widget->CaretPos != -1) { + widget->CaretPos++; + if (widget->CaretPos >= widget->Text.length) { widget->CaretPos = -1; } + InputWidget_UpdateCaret(widget); + } +} + +void InputWidget_HomeKey(InputWidget* widget) { + if (widget->Text.length == 0) return; + widget->CaretPos = 0; + InputWidget_UpdateCaret(widget); +} + +void InputWidget_EndKey(InputWidget* widget) { + widget->CaretPos = -1; + InputWidget_UpdateCaret(widget); +} + +bool InputWidget_OtherKey(InputWidget* widget, Key key) { + Int32 maxChars = widget->GetMaxLines() * widget->MaxCharsPerLine; + if (key == Key_V && widget->Text.length < maxChars) { + UInt8 textBuffer[String_BufferSize(INPUTWIDGET_MAX_LINES * STRING_SIZE)]; + String text = String_FromRawBuffer(textBuffer, INPUTWIDGET_MAX_LINES * STRING_SIZE); + Window_GetClipboardText(&text); + + if (text.length == 0) return true; + InputWidget_AppendString(widget, text); + return true; + } else if (key == Key_C) { + if (widget->Text.length == 0) return true; + Window_SetClipboardText(&widget->Text); + return true; + } + return false; +} + +void InputWidget_Init(GuiElement* elem) { + InputWidget* widget = (InputWidget*)elem; + Int32 lines = widget->GetMaxLines(); + if (lines > 1) { + WordWrap_Do(&widget->Text, widget->Lines, lines, widget->MaxCharsPerLine); + } else { + String_Clear(&widget->Lines[0]); + String_AppendString(&widget->Lines[0], &widget->Text); + } + + InputWidget_CalculateLineSizes(widget); + InputWidget_RemakeTexture(widget); + InputWidget_UpdateCaret(widget); +} + +void InputWidget_Free(GuiElement* elem) { + InputWidget* widget = (InputWidget*)elem; + Gfx_DeleteTexture(&widget->InputTex.ID); + Gfx_DeleteTexture(&widget->CaretTex.ID); + Gfx_DeleteTexture(&widget->PrefixTex.ID); +} + +void InputWidget_Recreate(GuiElement* elem) { + InputWidget* widget = (InputWidget*)elem; + Gfx_DeleteTexture(&widget->InputTex.ID); + InputWidget_Init(elem); +} + +void InputWidget_Reposition(Widget* elem) { + Int32 oldX = elem->X, oldY = elem->Y; + Widget_DoReposition(elem); + + InputWidget* widget = (InputWidget*)elem; + widget->CaretTex.X += elem->X - oldX; widget->CaretTex.Y += elem->Y - oldY; + widget->InputTex.X += elem->X - oldX; widget->InputTex.Y += elem->Y - oldY; +} + +bool InputWidget_HandlesKeyDown(GuiElement* elem, Key key) { +#if CC_BUILD_OSX + bool clipboardDown = Key_IsWinPressed(); +#else + bool clipboardDown = Key_IsControlPressed(); +#endif + InputWidget* widget = (InputWidget*)elem; + + if (key == Key_Left) { + InputWidget_LeftKey(widget, clipboardDown); + } else if (key == Key_Right) { + InputWidget_RightKey(widget, clipboardDown); + } else if (key == Key_BackSpace) { + InputWidget_BackspaceKey(widget, clipboardDown); + } else if (key == Key_Delete) { + InputWidget_DeleteKey(widget); + } else if (key == Key_Home) { + InputWidget_HomeKey(widget); + } else if (key == Key_End) { + InputWidget_EndKey(widget); + } else if (clipboardDown && !InputWidget_OtherKey(widget, key)) { + return false; + } + return true; +} + +bool InputWidget_HandlesKeyUp(GuiElement* elem, Key key) { return true; } + +bool InputWidget_HandlesKeyPress(GuiElement* elem, UInt8 key) { + InputWidget* widget = (InputWidget*)elem; + InputWidget_AppendChar(widget, key); + return true; +} + +bool InputWidget_HandlesMouseDown(GuiElement* elem, Int32 x, Int32 y, MouseButton button) { + InputWidget* widget = (InputWidget*)elem; + if (button == MouseButton_Left) { + x -= widget->InputTex.X; y -= widget->InputTex.Y; + DrawTextArgs args; DrawTextArgs_MakeEmpty(&args, &widget->Font, true); + Int32 offset = 0, charHeight = widget->CaretHeight; + + Int32 charX, i; + for (i = 0; i < widget->GetMaxLines(); i++) { + String* line = &widget->Lines[i]; + Int32 xOffset = i == 0 ? widget->PrefixWidth : 0; + if (line->length == 0) continue; + + for (charX = 0; charX < line->length; charX++) { + args.Text = String_UNSAFE_Substring(line, 0, charX); + Int32 charOffset = Drawer2D_MeasureText(&args).Width + xOffset; + + args.Text = String_UNSAFE_Substring(line, charX, 1); + Int32 charWidth = Drawer2D_MeasureText(&args).Width; + + if (Gui_Contains(charOffset, i * charHeight, charWidth, charHeight, x, y)) { + widget->CaretPos = offset + charX; + InputWidget_UpdateCaret(widget); + return true; + } + } + offset += line->length; + } + widget->CaretPos = -1; + InputWidget_UpdateCaret(widget); + } + return true; +} + +void InputWidget_Create(InputWidget* widget, FontDesc* font, STRING_REF String* prefix) { + Widget_Init(&widget->Base); + widget->Font = *font; + widget->Prefix = *prefix; + widget->CaretPos = -1; + widget->MaxCharsPerLine = STRING_SIZE; + + String caret = String_FromConstant("_"); + DrawTextArgs args; DrawTextArgs_Make(&args, &caret, font, true); + widget->CaretTex = Drawer2D_MakeTextTexture(&args, 0, 0); + widget->CaretTex.Width = (UInt16)((widget->CaretTex.Width * 3) / 4); + widget->CaretWidth = (UInt16)widget->CaretTex.Width; + widget->CaretHeight = (UInt16)widget->CaretTex.Height; + + if (prefix->length == 0) return; + DrawTextArgs_Make(&args, prefix, font, true); + Size2D size = Drawer2D_MeasureText(&args); + widget->PrefixWidth = (UInt16)size.Width; widget->Base.Width = size.Width; + widget->PrefixHeight = (UInt16)size.Height; widget->Base.Height = size.Height; } \ No newline at end of file diff --git a/src/Client/Widgets.h b/src/Client/Widgets.h index e77c031fc..73bb6e05a 100644 --- a/src/Client/Widgets.h +++ b/src/Client/Widgets.h @@ -124,8 +124,8 @@ struct InputWidget_; typedef struct InputWidget_ { Widget Base; FontDesc Font; - Int32 (*GetMaxLines)(GuiElement* elem); - Int32 Padding; + Int32 (*GetMaxLines)(void); + Int32 Padding, MaxCharsPerLine; void (*RemakeTexture)(GuiElement* elem); /* Remakes the raw texture containing all the chat lines. Also updates dimensions. */ void (*OnPressedEnter)(GuiElement* elem); /* Invoked when the user presses enter. */ @@ -134,11 +134,11 @@ typedef struct InputWidget_ { Size2D LineSizes[INPUTWIDGET_MAX_LINES]; /* size of each line in pixels */ Texture InputTex; String Prefix; - Int32 PrefixWidth, PrefixHeight; + UInt16 PrefixWidth, PrefixHeight; Texture PrefixTex; Int32 CaretX, CaretY; /* Coordinates of caret in lines */ - Int32 CaretWidth, CaretHeight; + UInt16 CaretWidth, CaretHeight; Int32 CaretPos; /* Position of caret, -1 for at end of string. */ bool ShowCaret; PackedCol CaretCol; diff --git a/src/Client/WinWindow.c b/src/Client/WinWindow.c index 4e977e233..3f9a1cdd9 100644 --- a/src/Client/WinWindow.c +++ b/src/Client/WinWindow.c @@ -473,6 +473,7 @@ void Window_GetClipboardText(STRING_TRANSIENT String* value) { if (hGlobal == NULL) { CloseClipboard(); return; } LPVOID src = GlobalLock(hGlobal); + /* TODO: Trim space / tabs from start and end of clipboard text */ if (isUnicode) { UInt16* text = (UInt16*)src; while (*text != NULL) { diff --git a/src/Client/WordWrap.c b/src/Client/WordWrap.c index e961972eb..5bbe18943 100644 --- a/src/Client/WordWrap.c +++ b/src/Client/WordWrap.c @@ -3,7 +3,7 @@ #include "Funcs.h" #include "Platform.h" -void WordWrap_OutputLines(String* text, String** lines, Int32* lineLens, Int32 numLines, Int32 usedLines, Int32 charsPerLine) { +void WordWrap_OutputLines(String* text, String* lines, Int32* lineLens, Int32 numLines, Int32 usedLines, Int32 charsPerLine) { Int32 totalChars = charsPerLine * numLines, i, j; for (i = 0; i < totalChars; i++) { if (text->buffer[i] == NULL) text->buffer[i] = ' '; @@ -18,7 +18,7 @@ void WordWrap_OutputLines(String* text, String** lines, Int32* lineLens, Int32 n usedLines = max(1, usedLines); for (i = 0; i < usedLines; i++) { - String* dst = lines[i]; + String* dst = &lines[i]; UInt8* src = &text->buffer[i * charsPerLine]; for (j = 0; j < lineLens[i]; j++) { String_Append(dst, src[j]); } } @@ -45,11 +45,11 @@ Int32 WordWrap_WrapLine(String* text, Int32 index, Int32 lineSize) { return lineSize; } -void WordWrap_Do(STRING_TRANSIENT String* text, STRING_TRANSIENT String** lines, Int32 numLines, Int32 maxPerLine) { +void WordWrap_Do(STRING_TRANSIENT String* text, STRING_TRANSIENT String* lines, Int32 numLines, Int32 maxPerLine) { Int32 len = text->length, i; Int32 lineLens[WORDWRAP_MAX_LINES_TO_WRAP]; for (i = 0; i < numLines; i++) { - String_Clear(lines[i]); + String_Clear(&lines[i]); lineLens[i] = 0; } @@ -83,12 +83,12 @@ void WordWrap_Do(STRING_TRANSIENT String* text, STRING_TRANSIENT String** lines, } /* Calculates where the given raw index is located in the wrapped lines. */ -void WordWrap_GetCoords(Int32 index, STRING_TRANSIENT String** lines, Int32 numLines, Int32* coordX, Int32* coordY) { +void WordWrap_GetCoords(Int32 index, STRING_TRANSIENT String* lines, Int32 numLines, Int32* coordX, Int32* coordY) { if (index == -1) index = Int32_MaxValue; Int32 offset = 0; *coordX = -1; *coordY = 0; for (Int32 y = 0; y < numLines; y++) { - Int32 lineLength = lines[y]->length; + Int32 lineLength = lines[y].length; if (lineLength == 0) break; *coordY = y; @@ -97,7 +97,7 @@ void WordWrap_GetCoords(Int32 index, STRING_TRANSIENT String** lines, Int32 numL } offset += lineLength; } - if (*coordX == -1) *coordX = lines[*coordY]->length; + if (*coordX == -1) *coordX = lines[*coordY].length; } Int32 WordWrap_GetBackLength(STRING_TRANSIENT String* text, Int32 index) { diff --git a/src/Client/WordWrap.h b/src/Client/WordWrap.h index e531c19d4..83c13ee1b 100644 --- a/src/Client/WordWrap.h +++ b/src/Client/WordWrap.h @@ -8,9 +8,9 @@ #define WORDWRAP_MAX_LINES_TO_WRAP 128 #define WORDWRAP_MAX_BUFFER_SIZE 2048 -void WordWrap_Do(STRING_TRANSIENT String* text, STRING_TRANSIENT String** lines, Int32 numLines, Int32 maxPerLine); +void WordWrap_Do(STRING_TRANSIENT String* text, STRING_TRANSIENT String* lines, Int32 numLines, Int32 maxPerLine); /* Calculates where the given raw index is located in the wrapped lines. */ -void WordWrap_GetCoords(Int32 index, STRING_TRANSIENT String** lines, Int32 numLines, Int32* coordX, Int32* coordY); +void WordWrap_GetCoords(Int32 index, STRING_TRANSIENT String* lines, Int32 numLines, Int32* coordX, Int32* coordY); Int32 WordWrap_GetBackLength(STRING_TRANSIENT String* text, Int32 index); Int32 WordWrap_GetForwardLength(STRING_TRANSIENT String* text, Int32 index); #endif \ No newline at end of file