From 3a04aab4fd88c6d10ddd3bb5fcc7e729dddafb78 Mon Sep 17 00:00:00 2001 From: PS Date: Thu, 18 Mar 2021 00:18:58 -0700 Subject: [PATCH] Updated tests for strings, and added cursor to widget strings if the widget is currently being edited --- src/app/editor/foldhaus_editor_draw.h | 24 +++- .../foldhaus_panel_animation_timeline.h | 12 +- .../panels/foldhaus_panel_sculpture_view.h | 2 +- src/app/foldhaus_renderer.h | 7 + src/app/interface.h | 100 ++++--------- src/gs_libs/gs_types.cpp | 134 ++++++++++++++---- src/tests/sanity_tests.cpp | 62 ++++++++ 7 files changed, 231 insertions(+), 110 deletions(-) diff --git a/src/app/editor/foldhaus_editor_draw.h b/src/app/editor/foldhaus_editor_draw.h index 00c7949..065bc21 100644 --- a/src/app/editor/foldhaus_editor_draw.h +++ b/src/app/editor/foldhaus_editor_draw.h @@ -6,12 +6,12 @@ #ifndef FOLDHAUS_EDITOR_DRAW_H internal void -Editor_DrawWidgetString(app_state* State, context* Context, render_command_buffer* RenderBuffer, ui_widget Widget, rect2 ClippingBox, v4 Color) +Editor_DrawWidgetString(app_state* State, context* Context, render_command_buffer* RenderBuffer, ui_widget Widget, rect2 ClippingBox, v4 Color, s32 CursorPosition) { gs_string Temp = PushString(State->Transient, 256); PrintF(&Temp, "%d", Widget.Id.Id); render_quad_batch_constructor BatchConstructor = PushRenderTexture2DBatch(RenderBuffer, - Widget.String.Length, + Widget.String.Length + 1, State->Interface.Style.Font->BitmapMemory, State->Interface.Style.Font->BitmapTextureHandle, State->Interface.Style.Font->BitmapWidth, @@ -25,7 +25,8 @@ Editor_DrawWidgetString(app_state* State, context* Context, render_command_buffe { case Align_Left: { - RegisterPosition = DrawStringLeftAligned(&BatchConstructor, StringExpand(Widget.String), RegisterPosition, State->Interface.Style.Font, ClippingBox, Color); + RegisterPosition = DrawStringLeftAligned(RenderBuffer, + &BatchConstructor, StringExpand(Widget.String), RegisterPosition, State->Interface.Style.Font, ClippingBox, Color, CursorPosition, GreenV4); }break; case Align_Right: @@ -82,6 +83,8 @@ Editor_DrawWidget(app_state* State, context* Context, render_command_buffer* Ren rect2 WidgetParentUnion = Widget.Bounds; WidgetParentUnion = Rect2Union(Widget.Bounds, ParentClipBounds); + bool IsActiveWidget = ui_WidgetIdsEqual(Widget.Id, State->Interface.ActiveWidget); + ; if (!Widget.Parent || (Rect2Area(WidgetParentUnion) > 0)) { if (ui_WidgetIsFlagSet(Widget, UIWidgetFlag_DrawBackground)) @@ -101,7 +104,13 @@ Editor_DrawWidget(app_state* State, context* Context, render_command_buffer* Ren if (ui_WidgetIsFlagSet(Widget, UIWidgetFlag_DrawString) && Widget.String.Length > 0) { v4 Color = State->Interface.Style.TextColor; - Editor_DrawWidgetString(State, Context, RenderBuffer, Widget, WidgetParentUnion, Color); + s32 CursorPosition = -1; + if (ui_WidgetIsFlagSet(Widget, UIWidgetFlag_Typable) && IsActiveWidget) + { + CursorPosition = State->Interface.CursorPosition; + } + + Editor_DrawWidgetString(State, Context, RenderBuffer, Widget, WidgetParentUnion, Color, CursorPosition); } if (ui_WidgetIsFlagSet(Widget, UIWidgetFlag_DrawHorizontalFill) || @@ -122,11 +131,14 @@ Editor_DrawWidget(app_state* State, context* Context, render_command_buffer* Ren { // TODO(pjs): add this color to the style v4 TextColor = BlackV4; - Editor_DrawWidgetString(State, Context, RenderBuffer, Widget, ClippedFillBounds, TextColor); + Editor_DrawWidgetString(State, Context, RenderBuffer, Widget, ClippedFillBounds, TextColor, -1); } } - if (ui_WidgetIsFlagSet(Widget, UIWidgetFlag_DrawOutline)) + bool DrawOutline = ui_WidgetIsFlagSet(Widget, UIWidgetFlag_DrawOutline); + DrawOutline |= ui_WidgetIsFlagSet(Widget, UIWidgetFlag_Typable) && IsActiveWidget; + + if (DrawOutline) { // TODO(pjs): replace these with values from the style r32 Thickness = 1.0f; diff --git a/src/app/editor/panels/foldhaus_panel_animation_timeline.h b/src/app/editor/panels/foldhaus_panel_animation_timeline.h index 9209ef0..886b1a3 100644 --- a/src/app/editor/panels/foldhaus_panel_animation_timeline.h +++ b/src/app/editor/panels/foldhaus_panel_animation_timeline.h @@ -335,7 +335,7 @@ DrawFrameBar (animation_system* AnimationSystem, ui_interface Interface, frame_r r32 FramePercent = FrameToPercentRange(Frame, VisibleFrames); r32 FrameX = LerpR32(FramePercent, BarBounds.Min.x, BarBounds.Max.x); v2 FrameTextPos = v2{FrameX, BarBounds.Min.y + 2}; - DrawString(Interface.RenderBuffer, TempString, Interface.Style.Font, FrameTextPos, WhiteV4); + DrawString(Interface.RenderBuffer, TempString, Interface.Style.Font, FrameTextPos, WhiteV4, -1, GreenV4); } // Time Slider @@ -352,7 +352,7 @@ DrawFrameBar (animation_system* AnimationSystem, ui_interface Interface, frame_r v2 HeadMin = v2{SliderX - SliderHalfWidth, BarBounds.Min.y}; v2 HeadMax = v2{SliderX + SliderHalfWidth, BarBounds.Max.y}; PushRenderQuad2D(Interface.RenderBuffer, HeadMin, HeadMax, TimeSliderColor); - DrawString(Interface.RenderBuffer, TempString, Interface.Style.Font, HeadMin + v2{6, 4}, WhiteV4); + DrawString(Interface.RenderBuffer, TempString, Interface.Style.Font, HeadMin + v2{6, 4}, WhiteV4, -1, GreenV4); } } @@ -465,7 +465,7 @@ DrawLayerMenu(animation_system* AnimationSystem, animation ActiveAnim, ui_interf { PushRenderBoundingBox2D(Interface.RenderBuffer, LayerBounds.Min, LayerBounds.Max, 1, WhiteV4); } - DrawString(Interface.RenderBuffer, Layer->Name, Interface.Style.Font, LayerTextPos, WhiteV4); + DrawString(Interface.RenderBuffer, Layer->Name, Interface.Style.Font, LayerTextPos, WhiteV4, -1, GreenV4); } } @@ -573,7 +573,7 @@ FrameCount_Render(animation_timeline_state* TimelineState, animation* ActiveAnim r32 FramePercent = FrameToPercentRange(Frame, VisibleFrames); r32 FrameX = LerpR32(FramePercent, Bounds.Min.x, Bounds.Max.x); v2 FrameTextPos = v2{FrameX, Bounds.Min.y + 2}; - DrawString(Interface->RenderBuffer, TempString, Interface->Style.Font, FrameTextPos, WhiteV4); + DrawString(Interface->RenderBuffer, TempString, Interface->Style.Font, FrameTextPos, WhiteV4, -1, GreenV4); } // Time Slider @@ -591,7 +591,7 @@ FrameCount_Render(animation_timeline_state* TimelineState, animation* ActiveAnim v2 HeadMin = v2{SliderX - SliderHalfWidth, Bounds.Min.y}; v2 HeadMax = v2{SliderX + SliderHalfWidth, Bounds.Max.y}; PushRenderQuad2D(Interface->RenderBuffer, HeadMin, HeadMax, TimeSliderColor); - DrawString(Interface->RenderBuffer, TempString, Interface->Style.Font, HeadMin + v2{6, 4}, WhiteV4); + DrawString(Interface->RenderBuffer, TempString, Interface->Style.Font, HeadMin + v2{6, 4}, WhiteV4, -1, GreenV4); } // Interaction @@ -620,7 +620,7 @@ LayerList_DrawLayerButton (ui_interface* Interface, gs_string Name, rect2 Bounds { PushRenderBoundingBox2D(Interface->RenderBuffer, Bounds.Min, Bounds.Max, 1, BoxColor); } - DrawString(Interface->RenderBuffer, Name, Interface->Style.Font, TextPos, WhiteV4); + DrawString(Interface->RenderBuffer, Name, Interface->Style.Font, TextPos, WhiteV4, -1, GreenV4); return Result; } diff --git a/src/app/editor/panels/foldhaus_panel_sculpture_view.h b/src/app/editor/panels/foldhaus_panel_sculpture_view.h index 6d4c803..ad51493 100644 --- a/src/app/editor/panels/foldhaus_panel_sculpture_view.h +++ b/src/app/editor/panels/foldhaus_panel_sculpture_view.h @@ -234,7 +234,7 @@ SculptureView_Render(panel* Panel, rect2 PanelBounds, render_command_buffer* Ren gs_string Tempgs_string = PushString(State->Transient, 256); PrintF(&Tempgs_string, "Hot Id: %u, ZIndex: %u | Active Id: %u", State->Interface.HotWidget.Id, State->Interface.HotWidget.ZIndex,State->Interface.ActiveWidget.Id); - DrawString(RenderBuffer, Tempgs_string, State->Interface.Style.Font, v2{PanelBounds.Min.x + 100, PanelBounds.Max.y - 200}, WhiteV4); + DrawString(RenderBuffer, Tempgs_string, State->Interface.Style.Font, v2{PanelBounds.Min.x + 100, PanelBounds.Max.y - 200}, WhiteV4, -1, GreenV4); } Context.GeneralWorkQueue->CompleteQueueWork(Context.GeneralWorkQueue, Context.ThreadContext); diff --git a/src/app/foldhaus_renderer.h b/src/app/foldhaus_renderer.h index a40c0f4..7e25346 100644 --- a/src/app/foldhaus_renderer.h +++ b/src/app/foldhaus_renderer.h @@ -487,6 +487,13 @@ PushQuad2DOnBatch (render_quad_batch_constructor* Constructor, v2 Min, v2 Max, v v2{0, 0}, v2{1, 1}, Color); } +internal void +PushQuad2DOnBatch (render_quad_batch_constructor* Constructor, rect2 Rect, v4 Color) +{ + PushQuad2DOnBatch(Constructor, v2{Rect.Min.x, Rect.Min.y}, v2{Rect.Max.x, Rect.Min.y}, v2{Rect.Max.x, Rect.Max.y}, v2{Rect.Min.x, Rect.Max.y}, + v2{0, 0}, v2{1, 1}, Color); +} + internal void PushLine2DOnBatch (render_quad_batch_constructor* Constructor, v2 P0, v2 P1, r32 Thickness, v4 Color) { diff --git a/src/app/interface.h b/src/app/interface.h index a54c669..566b1ad 100644 --- a/src/app/interface.h +++ b/src/app/interface.h @@ -99,17 +99,35 @@ DrawCharacterRightAligned (render_quad_batch_constructor* BatchConstructor, char return PointAfterCharacter; } +internal void +DrawCursor (render_command_buffer* RenderBuffer, v2 RegisterPosition, bitmap_font* Font, v4 Color) +{ + rect2 CursorRect = {}; + CursorRect.Min = RegisterPosition; + CursorRect.Max = CursorRect.Min + v2{5, (r32)Font->Ascent}; + PushRenderQuad2D(RenderBuffer, CursorRect, Color); +} + internal v2 -DrawStringLeftAligned (render_quad_batch_constructor* BatchConstructor, s32 Length, char* gs_string, v2 InitialRegisterPosition, bitmap_font* Font, rect2 ClippingBox, v4 Color) +DrawStringLeftAligned (render_command_buffer* RenderBuffer, render_quad_batch_constructor* BatchConstructor, s32 Length, char* gs_string, v2 InitialRegisterPosition, bitmap_font* Font, rect2 ClippingBox, v4 Color, s32 CursorBeforeIndex, v4 CursorColor) { v2 RegisterPosition = InitialRegisterPosition; char* C = gs_string; for (s32 i = 0; i < Length; i++) { + if (i == CursorBeforeIndex) + { + DrawCursor(RenderBuffer, RegisterPosition, Font, CursorColor); + } + v2 PositionAfterCharacter = DrawCharacterLeftAligned(BatchConstructor, *C, *Font, RegisterPosition, ClippingBox, Color); RegisterPosition.x = PositionAfterCharacter.x; C++; } + if (CursorBeforeIndex == Length) + { + DrawCursor(RenderBuffer, RegisterPosition, Font, CursorColor); + } return RegisterPosition; } @@ -128,12 +146,12 @@ DrawStringRightAligned (render_quad_batch_constructor* BatchConstructor, s32 Len } internal v2 -DrawString(render_command_buffer* RenderBuffer, gs_string String, bitmap_font* Font, v2 Position, v4 Color, gs_string_alignment Alignment = Align_Left) +DrawString(render_command_buffer* RenderBuffer, gs_string String, bitmap_font* Font, v2 Position, v4 Color, s32 CursorPosition, v4 CursorColor, gs_string_alignment Alignment = Align_Left) { DEBUG_TRACK_FUNCTION; v2 LowerRight = Position; - render_quad_batch_constructor BatchConstructor = PushRenderTexture2DBatch(RenderBuffer, String.Length, + render_quad_batch_constructor BatchConstructor = PushRenderTexture2DBatch(RenderBuffer, String.Length + 1, Font->BitmapMemory, Font->BitmapTextureHandle, Font->BitmapWidth, @@ -152,7 +170,7 @@ DrawString(render_command_buffer* RenderBuffer, gs_string String, bitmap_font* F v2 RegisterPosition = Position; if (Alignment == Align_Left) { - RegisterPosition = DrawStringLeftAligned(&BatchConstructor, StringExpand(String), RegisterPosition, Font, InfiniteClipBox, Color); + RegisterPosition = DrawStringLeftAligned(RenderBuffer, &BatchConstructor, StringExpand(String), RegisterPosition, Font, InfiniteClipBox, Color, CursorPosition, CursorColor); } else if (Alignment == Align_Right) { @@ -359,6 +377,7 @@ struct ui_interface // A per-frame string of the characters which have been typed gs_const_string TempInputString; + u64 CursorPosition; render_command_buffer* RenderBuffer; @@ -980,6 +999,11 @@ ui_EvaluateWidget(ui_interface* Interface, ui_widget* Widget, rect2 Bounds) { Result.Clicked = true; Interface->ActiveWidget = Widget->Id; + + if (ui_WidgetIsFlagSet(*Widget, UIWidgetFlag_Typable)) + { + Interface->CursorPosition = Widget->String.Length; + } } } @@ -1013,11 +1037,12 @@ ui_EvaluateWidget(ui_interface* Interface, ui_widget* Widget, rect2 Bounds) { ui_widget_retained_state* State = ui_GetRetainedState(Interface, Widget->Id); + Interface->CursorPosition = Clamp(0, Interface->CursorPosition, State->EditString.Length); for (u32 i = 0; i < Interface->TempInputString.Length; i++) { if (Interface->TempInputString.Str[i] == '\b') { - if (State->EditString.Length > 0) + if (Interface->CursorPosition > 0) { State->EditString.Length -= 1; } @@ -1030,71 +1055,6 @@ ui_EvaluateWidget(ui_interface* Interface, ui_widget* Widget, rect2 Bounds) } } -#if 0 - // if you can click it - if (ui_WidgetIsFlagSet(*Widget, UIWidgetFlag_Clickable)) - { - // updating hot widget, and handling mouse clicks - if (PointIsInRect(Widget->Parent->Bounds, Interface->Mouse.Pos) && - PointIsInRect(Widget->Bounds, Interface->Mouse.Pos)) - { - if (ui_WidgetIdsEqual(Interface->HotWidget, Widget->Id) && MouseButtonTransitionedDown(Interface->Mouse.LeftButtonState)) - { - Result.Clicked = true; - Interface->ActiveWidget = Widget->Id; - } - - Interface->HotWidget = Widget->Id; - } - - // click and drag - if (MouseButtonHeldDown(Interface->Mouse.LeftButtonState) && - PointIsInRect(Widget->Bounds, Interface->Mouse.DownPos)) - { - Result.Held = true; - Result.DragDelta = Interface->Mouse.Pos - Interface->Mouse.DownPos; - } - - // if this is the active widget (its been clicked) - if (ui_WidgetIdsEqual(Interface->ActiveWidget, Widget->Id)) - { - // if you can select it - if (ui_WidgetIsFlagSet(*Widget, UIWidgetFlag_Selectable)) - { - // - if (MouseButtonTransitionedDown(Interface->Mouse.LeftButtonState) && - !PointIsInRect(Widget->Bounds, Interface->Mouse.Pos)) - { - Interface->ActiveWidget = {}; - } - - if (ui_WidgetIsFlagSet(*Widget, UIWidgetFlag_Typable) && - Interface->TempInputString.Length > 0) - { - ui_widget_retained_state* State = ui_GetRetainedState(Interface, Widget->Id); - - // TODO(pjs): Backspace? - for (u32 i = 0; i < Interface->TempInputString.Length; i++) - { - if (Interface->TempInputString.Str[i] == '\b') - { - State->EditString.Length -= 1; - } - else - { - OutChar(&State->EditString, Interface->TempInputString.Str[i]); - } - } - } - } - else if (MouseButtonTransitionedUp(Interface->Mouse.LeftButtonState)) - { - Interface->ActiveWidget = {}; - } - } - } -#endif - Assert(Widget->Parent != 0); return Result; } diff --git a/src/gs_libs/gs_types.cpp b/src/gs_libs/gs_types.cpp index 197c270..cbd0678 100644 --- a/src/gs_libs/gs_types.cpp +++ b/src/gs_libs/gs_types.cpp @@ -1522,54 +1522,91 @@ Substring(gs_const_string String, u64 First, u64 Last) Result.Length = Min(Last - First, String.Length); return Result; } -internal u64 +internal gs_const_string +Substring(gs_string String, u64 First, u64 Last) +{ + return Substring(String.ConstString, First, Last); +} + +internal s64 FindFirst(gs_const_string String, u64 StartIndex, char C) { - u64 Result = StartIndex; - for(; Result < String.Length && C != String.Str[Result]; Result++); + s64 Result = -1; + for(u64 i = StartIndex; i < String.Length; i++) + { + if (String.Str[i] == C) { + Result = (s64)i; + break; + } + } return Result; } -internal u64 +internal s64 FindFirst(gs_const_string String, char C) { return FindFirst(String, 0, C); } - -internal u64 -FindLast(gs_const_string String, u64 StartIndex, char C) +internal s64 +FindFirst(gs_string String, u64 StartIndex, char C) { - s64 Result = StartIndex; - for(; Result >= 0 && C != String.Str[Result]; Result--); - return (u64)Result; + return FindFirst(String.ConstString, StartIndex, C); +} +internal s64 +FindFirst(gs_string String, char C) +{ + return FindFirst(String.ConstString, 0, C); } -internal u64 +internal s64 +FindLast(gs_const_string String, u64 StartIndex, char C) +{ + s64 Result = -1; + for(s64 i= StartIndex; i >= 0; i--) + { + if (String.Str[i] == C) { + Result = i; + break; + } + } + return (u64)Result; +} +internal s64 FindLast(gs_const_string String, char C) { return FindLast(String, String.Length - 1, C); } +internal s64 +FindLast(gs_string String, u64 StartIndex, char C) +{ + return FindLast(String.ConstString, StartIndex, C); +} +internal s64 +FindLast(gs_string String, char C) +{ + return FindLast(String.ConstString, String.Length - 1, C); +} -internal u64 +internal s64 FindFirstFromSet(gs_const_string String, char* SetArray) { gs_const_string Set = ConstString(SetArray); - u64 Result = String.Length - 1; - for(u64 At = 0; At < String.Length; At++) + s64 Result = -1; + + s64 CurrMin = String.Length; + for (u64 SetAt = 0; SetAt < Set.Length; SetAt++) { - char CharAt = String.Str[At]; - for (u64 SetAt = 0; SetAt < Set.Length; SetAt++) + s64 Index = FindFirst(String, Set.Str[SetAt]); + if (Index >= 0 && Index < CurrMin) { - if (CharAt == Set.Str[SetAt]) - { - Result = At; - // NOTE(Peter): The alternative to this goto is a break in the inner loop - // followed by an if check in the outer loop, that must be evaluated - // every character you check. This is more efficient - goto find_last_from_set_complete; - } + CurrMin = Index; } } - find_last_from_set_complete: + + if (CurrMin < (s64)String.Length) + { + Result = CurrMin; + } + return Result; } @@ -1648,6 +1685,12 @@ StringEqualsCharArray(gs_const_string A, char* B, u64 Length) return StringsEqual(A, BStr); } internal bool +StringEqualsCharArray(gs_const_string A, char* B) +{ + u64 Length = CStringLength(B); + return StringEqualsCharArray(A, B, Length); +} +internal bool StringsEqualUpToLength(gs_string A, gs_string B, u64 Length) { return StringsEqualUpToLength(A.ConstString, B.ConstString, Length); @@ -1660,8 +1703,12 @@ StringsEqual(gs_string A, gs_string B) internal bool StringEqualsCharArray(gs_string A, char* B, u64 Length) { - gs_string BStr = MakeString(B, Length); - return StringsEqual(A, BStr); + return StringEqualsCharArray(A.ConstString, B, Length); +} +internal bool +StringEqualsCharArray(gs_string A, char* B) +{ + return StringEqualsCharArray(A.ConstString, B); } internal u64 @@ -1907,6 +1954,39 @@ AppendString(gs_string* Base, gs_string Appendix) { return AppendString(Base, Appendix.ConstString); } + +internal void +InsertAt(gs_string* Str, u64 Index, char C) +{ + if (Str->Length > Index) + { + for (u64 i = Str->Length; i > Index; i--) + { + Str->Str[i] = Str->Str[i - 1]; + } + } + + if (Index < Str->Size) + { + Str->Str[Index] = C; + Str->Length += 1; + Assert(Str->Length < Str->Size); + } +} + +internal void +RemoveAt(gs_string* Str, u64 Index) +{ + if (Str->Length > 0 && Index < Str->Length) + { + for (u64 i = Index; i < Str->Length - 1; i++) + { + Str->Str[i] = Str->Str[i + 1]; + } + Str->Length -= 1; + } +} + internal void NullTerminate(gs_string* String) { diff --git a/src/tests/sanity_tests.cpp b/src/tests/sanity_tests.cpp index 281c1af..6130f5d 100644 --- a/src/tests/sanity_tests.cpp +++ b/src/tests/sanity_tests.cpp @@ -16,6 +16,15 @@ gs_memory_arena Scratch = {}; void* Alloc(u64 Size, u64* ResultSize) { *ResultSize = Size; return malloc(Size); } void Free(void* Ptr, u64 Size) { return free(Ptr); } +bool StringTest (gs_const_string StrA, gs_const_string StrB) +{ + return StringsEqual(StrA, StrB); +} +bool StringTest (gs_string StrA, gs_string StrB) +{ + return StringsEqual(StrA, StrB); +} + bool PathTest (char* In, char* Out) { return StringsEqual(SanitizePath(ConstString(In), &Scratch), ConstString(Out)); } @@ -24,6 +33,59 @@ int main (int ArgCount, char** Args) { Scratch = CreateMemoryArena(CreateAllocator(Alloc, Free)); + Test("gs_string") + { + gs_string TestString = PushStringF(&Scratch, 256, "Hello there, Sailor!"); + + NullTerminate(&TestString); + TestResult(IsNullTerminated(TestString)); + + TestResult(StringTest(GetStringPrefix(TestString.ConstString, 5), ConstString("Hello"))); + TestResult(StringTest(GetStringPostfix(TestString.ConstString, 5), ConstString("ilor!"))); + TestResult(StringTest(GetStringAfter(TestString.ConstString, 13), ConstString("Sailor!"))); + TestResult(StringTest(GetStringBefore(TestString.ConstString, 5), ConstString("Hello"))); + TestResult(StringTest(Substring(TestString.ConstString, 5, 11), ConstString(" there"))); + + TestResult(FindFirst(TestString, 5, 'l') == 16); + TestResult(FindFirst(TestString, 0, 'k') == -1); + TestResult(FindLast(TestString, 10, 'l') == 3); + TestResult(FindLast(TestString, 'k') == -1); + + TestResult(FindFirstFromSet(TestString.ConstString, "re") == 1); + TestResult(FindFirstFromSet(TestString.ConstString, "er") == 1); + TestResult(FindFirstFromSet(TestString.ConstString, "bk") == -1); + TestResult(FindFirstFromSet(TestString.ConstString, "ek") == 1); + + TestResult(FindLastFromSet(TestString.ConstString, "re") == 18); + TestResult(FindLastFromSet(TestString.ConstString, "er") == 18); + TestResult(FindLastFromSet(TestString.ConstString, "bk") == -1); + TestResult(FindLastFromSet(TestString.ConstString, "rk") == 18); + + TestResult(StringContains(TestString.ConstString, ',')); + TestResult(!StringContains(TestString.ConstString, '@')); + TestResult(StringsEqual(TestString, TestString)); + + TestResult(StringEqualsCharArray(TestString, "Hello there, Sailor!")); + TestResult(!StringEqualsCharArray(TestString, "Hello there, Sailor")); + TestResult(!StringEqualsCharArray(TestString, "Foobar")); + + ReverseStringInPlace(&TestString); + TestResult(StringTest(TestString, MakeString("!roliaS ,ereht olleH"))); + ReverseStringInPlace(&TestString); + + TestResult(ParseUInt(ConstString("532")) == 532); + TestResult(ParseInt(ConstString("-1234567890")) == -1234567890); + TestResult(ParseFloat(ConstString("-12345.6789")) == -12345.6789); + + TestString.Length = 0; + U64ToASCII(&TestString, 53298, 10); + TestResult(StringTest(TestString.ConstString, ConstString("53298"))); + + TestString.Length = 0; + R64ToASCII(&TestString, 145732.321, 2); + TestResult(StringTest(TestString.ConstString, ConstString("145732.32"))); + } + Test("gs_path.h") { TestResult(PathTest(".", "."));