diff --git a/code/core/core.h b/code/core/core.h index fd6d77b..53fecc1 100644 --- a/code/core/core.h +++ b/code/core/core.h @@ -148,6 +148,27 @@ IsNull(p) ? (SetNull((n)->prev), (n)->next = (f), (IsNull(f) ? (0) : ((f)->prev #define DLLRemove(First, Last, Element) DLLRemove_NP(First, Last, Element, Next, Prev) #define DLLIsEmpty(First) ((First) == 0) +#define SenDLLInit(Sentinel)\ +(Sentinel)->Next = (Sentinel);\ +(Sentinel)->Prev = (Sentinel); + +#define SenDLLInsertFirst(Sentinel, Element)\ +(Element)->Next = (Sentinel)->Next;\ +(Element)->Prev = (Sentinel);\ +(Element)->Next->Prev = (Element);\ +(Element)->Prev->Next = (Element); + +#define SenDLLInsertLast(Sentinel, Element)\ +(Element)->Next = (Sentinel);\ +(Element)->Prev = (Sentinel)->Prev;\ +(Element)->Next->Prev = (Element);\ +(Element)->Prev->Next = (Element); + +#define SenDLLRemove(Element)\ +auto __Temp = (Element)->Next->Prev;\ +(Element)->Next->Prev = (Element)->Prev->Next;\ +(Element)->Prev->Next = __Temp; + //- sixten: Stringify #define _Stringify(x) #x diff --git a/code/core/core_string.cpp b/code/core/core_string.cpp index 3374b29..ee174fe 100644 --- a/code/core/core_string.cpp +++ b/code/core/core_string.cpp @@ -509,54 +509,141 @@ read_only u8 UTF8Lengths[] = 0, // 11111 }; -static utf8_iterator IterateUTF8String(string String) +static string_decode DecodeUTF8Codepoint(u8 *Data, s64 Count) { - utf8_iterator Iter = {}; - Iter.Data = String; - Advance(&Iter); - - return(Iter); + string_decode Result = {}; + u8 FirstByteMask[] = {0, 0x7F, 0x1F, 0x0F, 0x07}; + u8 FinalShift[] = {0, 18, 12, 6, 0}; + if(Count > 0) + { + Result.Codepoint = '#'; + Result.Size = 1; + + u8 Byte = Data[0]; + u8 Length = UTF8Lengths[Byte>>3]; + if(0 < Length && Length <= Count) + { + u32 Codepoint = (Byte&FirstByteMask[Length])<<18; + switch(Length) + { + case 4: {Codepoint |= ((Data[3] & 0x3F) << 0);} fallthrough; + case 3: {Codepoint |= ((Data[2] & 0x3F) << 6);} fallthrough; + case 2: {Codepoint |= ((Data[1] & 0x3F) << 12);} fallthrough; + default: break; + } + + Result.Codepoint = Codepoint >> FinalShift[Length]; + Result.Size = Length; + } + } + return(Result); } -static void Advance(utf8_iterator *Iter) +static u32 EncodeUTF8Codepoint(u8 *Dest, u32 Codepoint) { - u8 *At = Iter->Data.Data + Iter->Index; - - if(Iter->Index < Iter->Data.Count) + u32 Size = 0; + u8 DummyDest[4]; + Dest = Dest?Dest:DummyDest; + if(Codepoint < (1<<8)) { - if((At[0] & 0x80) == 0x00) - { - Iter->Codepoint = (At[0] & 0x7F); - Iter->Index += 1; - } - else if((At[0] & 0xE0) == 0xC0) - { - Iter->Codepoint = ((At[0] & 0x1F) << 6)|(At[1] & 0x3F); - Iter->Index += 2; - } - else if((At[0] & 0xF0) == 0xE0) - { - Iter->Codepoint = ((At[0] & 0x0F) << 12)|((At[1] & 0x3F) << 6)|(At[2] & 0x3F); - Iter->Index += 3; - } - else if((Iter->Data.Data[Iter->Index] & 0xF8) == 0xF0) - { - Iter->Codepoint = ((At[0] & 0x0F) << 18)|((At[1] & 0x3F) << 12)|((At[2] & 0x3F) << 6)|(At[3] & 0x3F); - Iter->Index += 4; - } + Dest[0] = Codepoint; + Size = 1; + } + else if (Codepoint < (1 << 11)) + { + Dest[0] = 0xC0|(Codepoint >> 6); + Dest[1] = 0x80|(Codepoint & 0x3F); + Size = 2; + } + else if (Codepoint < (1 << 16)) + { + Dest[0] = 0xE0|(Codepoint >> 12); + Dest[1] = 0x80|((Codepoint >> 6) & 0x3F); + Dest[2] = 0x80|(Codepoint & 0x3F); + Size = 3; + } + else if (Codepoint < (1 << 21)) + { + Dest[0] = 0xF0|(Codepoint >> 18); + Dest[1] = 0x80|((Codepoint >> 12) & 0x3F); + Dest[2] = 0x80|((Codepoint >> 6) & 0x3F); + Dest[3] = 0x80|(Codepoint & 0x3F); + Size = 4; } else { - Iter->Codepoint = 0; + Dest[0] = '#'; + Size = 1; } + return(Size); } -static b32 IsValid(utf8_iterator *Iter) +static string_decode DecodeUTF16Codepoint(u8 *Data, s64 Count) { - b32 Result = (Iter->Codepoint != 0); + string_decode Result = {'#', 1}; + if(Data[0] < 0xD800 || 0xDFFF < Data[0]) + { + Result.Codepoint = Data[0]; + Result.Size = 1; + } + else if(Count >= 2) + { + if(0xD800 <= Data[0] && Data[0] < 0xDC00 && + 0xDC00 <= Data[1] && Data[1] < 0xE000) + { + Result.Codepoint = ((Data[0] - 0xD800)<<10)|(Data[1]-0xDC00); + Result.Size = 2; + } + } return(Result); } +static u32 EncodeUTF16Codepoint(u16 *Dest, u32 Codepoint) +{ + u32 Size = 0; + u16 DummyDest[2]; + Dest = Dest?Dest:DummyDest; + if(Codepoint < 0x10000) + { + Dest[0] = Codepoint; + Size = 1; + } + else + { + Dest[0] = ((Codepoint - 0x10000) >> 10) + 0xD800; + Dest[1] = ((Codepoint - 0x10000) & 0x3FF) + 0xDC00; + Size = 2; + } + return(Size); +} + +static s64 UTF8IndexFromOffset(string String, s64 Offset) +{ + u8 *StringBegin = String.Data; + u8 *StringEnd = StringBegin+String.Count; + u8 *Byte = StringBegin; + for(;Byte < StringEnd && Offset > 1; Offset -= 1) + { + Byte += DecodeUTF8Codepoint(Byte, StringEnd-Byte).Size; + } + s64 Result = Byte-StringBegin; + return(Result); +} + +static s64 UTF8OffsetFromIndex(string String, s64 Index) +{ + s64 Offset = 0; + u8 *StringBegin = String.Data; + u8 *StringEnd = StringBegin+Min(Index, String.Count); + u8 *Byte = StringBegin; + for(;Byte < StringEnd;) + { + Offset += 1; + Byte += DecodeUTF8Codepoint(Byte, StringEnd-Byte).Size; + } + return(Offset); +} + static s64 UTF8FromCodepoint(u8 *Out, u32 Codepoint) { s64 Length = 0; diff --git a/code/core/core_string.h b/code/core/core_string.h index 3cbcca0..9890e57 100644 --- a/code/core/core_string.h +++ b/code/core/core_string.h @@ -104,11 +104,13 @@ static string RemoveAll(memory_arena *Arena, string Text, char ToRemove); static s64 StringLength(char *String); +#if 0 ///////////////////////////////////// //~ sixten: String Chunk Functions static string_chunk_list MakeStringChunkList(s64 ChunkSize); static string JoinStringChunkList(memory_arena *Arena, string_chunk_list *List); static void ReplaceRange(memory_arena *Arena, string_chunk_list *List, string Text, range1_s64 Range); +#endif //~ sixten: String list @@ -118,19 +120,17 @@ static string JoinStringList(string_list *List, memory_arena *Arena); //~ sixten: Unicode -struct utf8_iterator +struct string_decode { - // sixten: Input - string Data; - s64 Index; - - // sixten: Output u32 Codepoint; + s32 Size; }; -static utf8_iterator IterateUTF8String(string String); -static void Advance(utf8_iterator *Iter); -static b32 IsValid(utf8_iterator *Iter); +static string_decode DecodeUTF8Codepoint(u8 *Data, s64 Count); +static u32 EncodeUTF8Codepoint(u8 *Dest, u32 Codepoint); + +static string_decode DecodeUTF16Codepoint(u8 *Data, s64 Count); +static u32 EncodeUTF16Codepoint(u16 *Dest, u32 Codepoint); static s64 UTF8FromCodepoint(u8 *Out, u32 Codepoint); diff --git a/code/vn.cpp b/code/vn.cpp index f92c0aa..da9dd6b 100644 --- a/code/vn.cpp +++ b/code/vn.cpp @@ -23,8 +23,8 @@ per_thread debug_settings *DEBUG_DebugSettings = 0; #include "vn_ui.h" #include "vn_ui_utils.h" #include "vn_workspace.h" -#include "vn_theme_dark.h" #include "vn_animation_curve.h" +#include "vn_theme_dark.h" #include "vn_scene.h" #include "vn_tokenizer.cpp" @@ -163,10 +163,12 @@ VN_UPDATE_AND_RENDER(VN_UpdateAndRender) render_group Group = BeginRenderGroup(RenderCommands); PushClear(&Group, V3(0.1, 0.1, 0.1)); +#if 0 PushTexturedQuad(&Group, Range2R32(V2R32(0, 0), RenderCommands->RenderDim), Range2R32(V2R32(0, 0), ConvertV2ToR32(DimFromTexture(State->BackgroundTexture))), Color_White, Color_White, Color_White, Color_White, 0, 0, 0, State->BackgroundTexture); +#endif UI_RenderFrame(&Group, State->GlyphAtlas); diff --git a/code/vn_font.cpp b/code/vn_font.cpp index 2863936..1465b3e 100644 --- a/code/vn_font.cpp +++ b/code/vn_font.cpp @@ -117,11 +117,12 @@ static glyph_atlas *CreateGlyphAtlas(vn_render_commands *RenderCommands, Atlas->RenderCommands = RenderCommands; Atlas->Texture = RenderCommands->AllocateTexture(V2S32(BitmapSize, BitmapSize), Render_TextureFormat_R8, 0); - Atlas->Fonts[Font_Regular].Data = Platform_ReadEntireFile(Atlas->Arena, StrLit("fonts/Roboto-Regular.ttf")); - Atlas->Fonts[Font_Bold].Data = Platform_ReadEntireFile(Atlas->Arena, StrLit("fonts/Roboto-Bold.ttf")); - Atlas->Fonts[Font_Monospace].Data = Platform_ReadEntireFile(Atlas->Arena, StrLit("fonts/DejaVuSansMono.ttf")); - Atlas->Fonts[Font_Hand].Data = Platform_ReadEntireFile(Atlas->Arena, StrLit("fonts/PatrickHand-Regular.ttf")); - Atlas->Fonts[Font_Icons].Data = Platform_ReadEntireFile(Atlas->Arena, StrLit("fonts/icons.ttf")); + Atlas->Fonts[Font_Regular].Data = Platform_ReadEntireFile(Atlas->Arena, StrLit("fonts/Roboto-Regular.ttf")); + Atlas->Fonts[Font_Bold].Data = Platform_ReadEntireFile(Atlas->Arena, StrLit("fonts/Roboto-Bold.ttf")); + Atlas->Fonts[Font_Monospace].Data = Platform_ReadEntireFile(Atlas->Arena, StrLit("fonts/DejaVuSansMono.ttf")); + Atlas->Fonts[Font_MonospaceOblique].Data = Platform_ReadEntireFile(Atlas->Arena, StrLit("fonts/DejaVuSansMono-Oblique.ttf")); + Atlas->Fonts[Font_Hand].Data = Platform_ReadEntireFile(Atlas->Arena, StrLit("fonts/PatrickHand-Regular.ttf")); + Atlas->Fonts[Font_Icons].Data = Platform_ReadEntireFile(Atlas->Arena, StrLit("fonts/icons.ttf")); for(s32 FontIndex = 0; FontIndex < Font_Count; @@ -145,11 +146,13 @@ static r32 PushText(render_group *Group, glyph_atlas *Atlas, font_id Font, string Text) { r32 OffsetX = 0; - for(utf8_iterator Iter = IterateUTF8String(Text); - IsValid(&Iter); - Advance(&Iter)) + u8 *TextBegin = Text.Data; + u8 *TextEnd = TextBegin+Text.Count; + for(u8 *Byte = TextBegin; Byte < TextEnd;) { - u32 Codepoint = Iter.Codepoint; + string_decode Decode = DecodeUTF8Codepoint(Byte, TextEnd-Byte); + Byte += Decode.Size; + u32 Codepoint = Decode.Codepoint; glyph *Glyph = GetGlyph(Atlas, Font, Codepoint, Size*Font_Oversample, GetSubpixelSegmentAtP(P.x*Font_Oversample)); Assert(Glyph); @@ -189,11 +192,13 @@ inline r32 CalculateRasterizedTextWidth(glyph_atlas *Atlas, font_id Font, r32 Si { r32 X = 0; - for(utf8_iterator Iter = IterateUTF8String(Text); - Iter.Codepoint != 0; - Advance(&Iter)) + u8 *TextBegin = Text.Data; + u8 *TextEnd = TextBegin+Text.Count; + for(u8 *Byte = TextBegin; Byte < TextEnd;) { - u32 Codepoint = Iter.Codepoint; + string_decode Decode = DecodeUTF8Codepoint(Byte, TextEnd-Byte); + Byte += Decode.Size; + u32 Codepoint = Decode.Codepoint; glyph *Glyph = GetGlyph(Atlas, Font, Codepoint, Size*Font_Oversample, GetSubpixelSegmentAtP(X*Font_Oversample)); Assert(Glyph); @@ -211,11 +216,13 @@ inline r32 CalculateRasterizedTextHeight(glyph_atlas *Atlas, font_id Font, r32 S r32 Y = Size*Scale; - for(utf8_iterator Iter = IterateUTF8String(Text); - Iter.Codepoint != 0; - Advance(&Iter)) + u8 *TextBegin = Text.Data; + u8 *TextEnd = TextBegin+Text.Count; + for(u8 *Byte = TextBegin; Byte < TextEnd;) { - u32 Codepoint = Iter.Codepoint; + string_decode Decode = DecodeUTF8Codepoint(Byte, TextEnd-Byte); + Byte += Decode.Size; + u32 Codepoint = Decode.Codepoint; if(Codepoint == '\n') { diff --git a/code/vn_font.h b/code/vn_font.h index 85a69ca..f5d1edb 100644 --- a/code/vn_font.h +++ b/code/vn_font.h @@ -8,6 +8,7 @@ enum font_id Font_Regular, Font_Bold, Font_Monospace, + Font_MonospaceOblique, Font_Hand, Font_Icons, diff --git a/code/vn_text_op.h b/code/vn_text_op.h index 3fe5fb1..3c57861 100644 --- a/code/vn_text_op.h +++ b/code/vn_text_op.h @@ -423,6 +423,10 @@ static text_op TextOpFromAction(memory_arena *Arena, string String, Op.NewMark = Op.NewCursor; } + s64 NewStringCount = String.Count-DimOfRange(Op.Range)+Op.ReplaceString.Count; + Op.NewCursor = Clamp(Op.NewCursor, 0, NewStringCount); + Op.NewMark = Clamp(Op.NewMark, 0, NewStringCount); + return(Op); } diff --git a/code/vn_tokenizer.cpp b/code/vn_tokenizer.cpp index ee5aa45..50920a7 100644 --- a/code/vn_tokenizer.cpp +++ b/code/vn_tokenizer.cpp @@ -151,16 +151,16 @@ static tokenize_result T_TokenizeFromText(memory_arena *Arena, string Filename, TokenFlags = TokenFlag_Identifier; TokenStart = Byte; TokenEnd = Byte; - Byte += 1; - for(;Byte <= TextEnd; Byte += 1) + Byte += UTF8Lengths[*Byte>>3]; + for(;Byte <= TextEnd; Byte += UTF8Lengths[*Byte>>3]) { - TokenEnd += 1; if(Byte == TextEnd || !(('A' <= *Byte && *Byte <= 'Z') || ('a' <= *Byte && *Byte <= 'z') || ('0' <= *Byte && *Byte <= '9') || (UTF8Lengths[*Byte>>3] > 1) || *Byte == '_')) { + TokenEnd = Byte; break; } } diff --git a/code/vn_ui.cpp b/code/vn_ui.cpp index d16b4de..a1525a2 100644 --- a/code/vn_ui.cpp +++ b/code/vn_ui.cpp @@ -641,12 +641,6 @@ static void UI_DrawBox(ui_box *Box, render_group *Group, glyph_atlas *GlyphAtlas } #endif - if(Box->Flags & UI_BoxFlag_Clip) - { - range2_r32 Rect = Intersection(Group->ClipStack[Group->ClipStackUsed], Box->Rect); - PushClip(Group, Rect); - } - for(ui_box *Child = Box->First; Child != 0; Child = Child->Next) @@ -654,18 +648,22 @@ static void UI_DrawBox(ui_box *Box, render_group *Group, glyph_atlas *GlyphAtlas if(Child->Flags & UI_BoxFlag_DrawDropShadow) { r32 ShadowRadius = 10; - v2 P = Child->Rect.Min - V2(ShadowRadius, ShadowRadius); - v2 Dim = Child->ComputedDim + V2(ShadowRadius, ShadowRadius)*2; - range2_r32 Rect = Range2R32(Box->Rect.Min - V2R32(ShadowRadius, ShadowRadius), - Box->Rect.Min + V2R32(ShadowRadius, ShadowRadius)); + range2_r32 Rect = Range2R32(Child->Rect.Min - V2R32(ShadowRadius, ShadowRadius), + Child->Rect.Max + V2R32(ShadowRadius, ShadowRadius)); - v4 ShadowColor = V4(0, 0, 0, 0.7); + v4 ShadowColor = V4(0, 0, 0, 0.3); PushQuad(Group, Rect, ShadowColor, 0, ShadowRadius, 0); } } + if(Box->Flags & UI_BoxFlag_Clip) + { + range2_r32 Rect = Intersection(Group->ClipStack[Group->ClipStackUsed], Box->Rect); + PushClip(Group, Rect); + } + for(ui_box *Child = Box->First; Child != 0; Child = Child->Next) @@ -682,6 +680,7 @@ static void UI_DrawBox(ui_box *Box, render_group *Group, glyph_atlas *GlyphAtlas { PushQuad(Group, Box->Rect, Box->BorderColor, Box->CornerRadius, 0.8, Box->BorderThickness); } + } static r32 UI_CalculateChildrenSum(ui_box *Box, axis2 Axis); @@ -783,9 +782,19 @@ static void UI_LayoutBox(ui_box *Box) Child != 0; Child = Child->Next) { - Child->Rect.Min = Box->Rect.Min + Child->ComputedRelativeP + Box->Offset; - Child->Rect.Max = Child->Rect.Min + Child->ComputedDim; - + if(Child->Flags & UI_BoxFlag_AnimatePosition) + { + Child->ApproachingRelativeP.x = AnimationCurve_AnimateValueF(Child->ComputedRelativeP.x, Child->ComputedRelativeP.x, 0.1, "Box P.X %p", Child); + Child->ApproachingRelativeP.y = AnimationCurve_AnimateValueF(Child->ComputedRelativeP.y, Child->ComputedRelativeP.y, 0.1, "Box P.Y %p", Child); + + Child->Rect.Min = Box->Rect.Min + Child->ApproachingRelativeP + Box->Offset; + Child->Rect.Max = Child->Rect.Min + Child->ComputedDim; + } + else + { + Child->Rect.Min = Box->Rect.Min + Child->ComputedRelativeP + Box->Offset; + Child->Rect.Max = Child->Rect.Min + Child->ComputedDim; + } UI_LayoutBox(Child); } } diff --git a/code/vn_ui.h b/code/vn_ui.h index ba74f74..8fdeb65 100644 --- a/code/vn_ui.h +++ b/code/vn_ui.h @@ -53,6 +53,7 @@ enum UI_BoxFlag_FloatingX = (1 << 10), UI_BoxFlag_FloatingY = (1 << 11), UI_BoxFlag_Scrollable = (1 << 12), + UI_BoxFlag_AnimatePosition = (1 << 13), }; typedef u32 ui_box_flags; @@ -61,19 +62,21 @@ typedef UI_CUSTOM_DRAW_CALLBACK(ui_custom_draw_callback); struct ui_box { + // sixten: building ui_box *First; ui_box *Last; ui_box *Next; ui_box *Prev; ui_box *Parent; + // sixten: hashing ui_box *HashNext; ui_box *HashPrev; - ui_key Key; ui_key Seed; u64 LastFrameTouched; + // sixten: per-frame data ui_box_flags Flags; string String; ui_size SemanticSize[Axis2_Count]; @@ -87,17 +90,17 @@ struct ui_box font_id Font; r32 FontSize; v2 Offset; - ui_custom_draw_callback *DrawCallback; void *DrawCallbackData; v2 ComputedRelativeP; v2 ComputedDim; + // sixten: retained data range2_r32 Rect; - r32 HotTransition; r32 ActiveTransition; + v2 ApproachingRelativeP; }; struct ui_box_bucket diff --git a/code/vn_workspace_text_editor.cpp b/code/vn_workspace_text_editor.cpp index c127068..6cc050d 100644 --- a/code/vn_workspace_text_editor.cpp +++ b/code/vn_workspace_text_editor.cpp @@ -23,13 +23,40 @@ static void MutableStringReplaceRange(mutable_string *MutString, string ReplaceS if(NewCount > MutString->String.Count) { s64 ToAllocate = NewCount-MutString->String.Count; - PushArray(MutString->Arena, u8, ToAllocate); + PushArrayNoClear(MutString->Arena, u8, ToAllocate); + } + else if(NewCount < MutString->String.Count) + { + ArenaPopTo(MutString->Arena, sizeof(memory_arena)+NewCount+1); } Move(MutString->String.Data+Range.Min+ReplaceString.Count, MutString->String.Data+Range.Max, MutString->String.Count-DimOfRange(Range)); Copy(MutString->String.Data+Range.Min, ReplaceString.Data, ReplaceString.Count); MutString->String.Count = NewCount; + MutString->String.Data[NewCount] = 0; +} + +//////////////////////////////// +//~ sixten: History & Undo Functions + +static history_entry HistoryEntry(memory_arena *Arena, string ReplaceString, range1_s64 Range) +{ + // sixten(TODO): proper memory management, right now we just keep appending to the arena, which works but never frees any memory + history_entry Entry = {}; + Entry.ReplaceString = PushString(Arena, ReplaceString); + Entry.Range = Range; + return(Entry); +} + +static void AppendToHistory(memory_arena *Arena, history_list *List, history_entry Forward, history_entry Backward) +{ + // sixten(TODO): proper memory management, right now we just keep appending to the arena, which works but never frees any memory + history_node *Node = PushStructNoClear(Arena, history_node); + Node->Forward = Forward; + Node->Backward = Backward; + + SenDLLInsertLast(&List->Sentinel, Node); } //////////////////////////////// @@ -39,6 +66,9 @@ static workspace_text_data Workspace_TextDataFromStringChunkList(memory_arena *A { temporary_memory Scratch = GetScratch(&Arena, 1); + //- sixten: clear the target arena + ArenaClear(Arena); + //- sixten: tokenize the text tokenize_result TokenizeResult = T_TokenizeFromText(Arena, StrLit("*scratch*"), Text); token_array Tokens = TokenizeResult.Tokens; @@ -164,9 +194,10 @@ static UI_CUSTOM_DRAW_CALLBACK(Workspace_TextEditorDrawCallback) { string TokenString = T_StringFromToken(Editor->Text.String, *Token); - //- sixten: get color from token + //- sixten: get color & font from token + font_id Font = Font_Monospace; v4 Color = Color_Magenta; - if(Token->Flags & TokenGroup_Comment) { Color = Color_Grey; } + if(Token->Flags & TokenGroup_Comment) { Color = Color_Grey; Font = Font_MonospaceOblique; } else if(Token->Flags & TokenFlag_Reserved) { Color = Color_Grey; } else if(Token->Flags & TokenFlag_Symbol) { Color = ColorFromHex(0xbd2d2dff); } else if(Token->Flags & TokenFlag_StringLiteral) { Color = ColorFromHex(0xffa900ff); } @@ -174,13 +205,16 @@ static UI_CUSTOM_DRAW_CALLBACK(Workspace_TextEditorDrawCallback) else if(Token->Flags & TokenFlag_Identifier) { //- sixten: check for keywords - if(AreEqual(TokenString, StrLit("var")) || - AreEqual(TokenString, StrLit("proc")) || - AreEqual(TokenString, StrLit("branch")) || - AreEqual(TokenString, StrLit("jump")) || - AreEqual(TokenString, StrLit("if")) || - AreEqual(TokenString, StrLit("true")) || + if(AreEqual(TokenString, StrLit("true")) || AreEqual(TokenString, StrLit("false"))) + { + Color = ColorFromHex(0xffa900ff); + } + else if(AreEqual(TokenString, StrLit("var")) || + AreEqual(TokenString, StrLit("proc")) || + AreEqual(TokenString, StrLit("branch")) || + AreEqual(TokenString, StrLit("jump")) || + AreEqual(TokenString, StrLit("if"))) { Color = ColorFromHex(0xf0c674ff); } @@ -192,7 +226,7 @@ static UI_CUSTOM_DRAW_CALLBACK(Workspace_TextEditorDrawCallback) //- sixten: render & advance by token if(!(Token->Flags & TokenGroup_Whitespace)) { - TokenP.x += PushText(Group, Atlas, Font_Monospace, TokenP, FontSize, Color, TokenString); + TokenP.x += PushText(Group, Atlas, Font, TokenP, FontSize, Color, TokenString); } else { @@ -216,9 +250,14 @@ static UI_CUSTOM_DRAW_CALLBACK(Workspace_TextEditorDrawCallback) } } - //- sixten: render cursor { - v2 TargetCursorP = Box->Rect.Min+V2(LineMarginDim.x+(CursorTextP.Column-1)*GlyphAdvance,(CursorTextP.Line-1)*LineHeight); + //- sixten: render cursor + s64 LineIndex = CursorTextP.Line-1; + string Line = Substring(Editor->Text.String, Editor->Lines.Ranges[LineIndex]); + s64 ColumnIndex = CursorTextP.Column-1; + s64 ColumnOffset = UTF8OffsetFromIndex(Line, ColumnIndex); + + v2 TargetCursorP = Box->Rect.Min+V2(LineMarginDim.x+ColumnOffset*GlyphAdvance, LineIndex*LineHeight); v2 CursorP = V2(AnimationCurve_AnimateValueF(TargetCursorP.x, TargetCursorP.x, 0.1, "Workspace Text Editor Cursor X %p", Editor), AnimationCurve_AnimateValueF(TargetCursorP.y, TargetCursorP.y, 0.1, "Workspace Text Editor Cursor Y %p", Editor)); v2 CursorDim = V2(2, LineHeight); @@ -231,28 +270,33 @@ static UI_CUSTOM_DRAW_CALLBACK(Workspace_TextEditorDrawCallback) { text_range Selection = TextRange(CursorTextP, MarkTextP); range1_s64 LineRange = Range1S64(Selection.Min.Line, Selection.Max.Line); - for(s64 Line = TopMostLine; Line < TopMostLine + LinesOnScreen; Line += 1) + for(s64 LineIndex = TopMostLine; LineIndex < TopMostLine + LinesOnScreen; LineIndex += 1) { - r32 LineY = Box->Rect.Min.y + Line*LineHeight; - if(Contains(LineRange, Line + 1)) + r32 LineY = Box->Rect.Min.y + LineIndex*LineHeight; + if(Contains(LineRange, LineIndex + 1)) { - range1_s64 ColumnRange = Lines->Ranges[Line]; + range1_s64 ColumnRange = Lines->Ranges[LineIndex]; range1_s64 NormalizedColumnRange = Range1S64(0, DimOfRange(ColumnRange)); - if(Line + 1 == LineRange.Min && Line + 1 == LineRange.Max) + if(LineIndex+1 == LineRange.Min && LineIndex+1 == LineRange.Max) { NormalizedColumnRange = Range1S64(Editor->EditState.Cursor - ColumnRange.Min, Editor->EditState.Mark - ColumnRange.Min); } - else if(Line + 1 == LineRange.Min) + else if(LineIndex+1 == LineRange.Min) { NormalizedColumnRange = Range1S64(Min(Editor->EditState.Mark, Editor->EditState.Cursor) - ColumnRange.Min, DimOfRange(ColumnRange)); } - else if(Line + 1 == LineRange.Max) + else if(LineIndex+1 == LineRange.Max) { NormalizedColumnRange = Range1S64(0, Max(Editor->EditState.Mark, Editor->EditState.Cursor) - ColumnRange.Min); } + + string Line = Substring(Editor->Text.String, ColumnRange); + range1_s64 ColumnOffsetRange = Range1S64(UTF8OffsetFromIndex(Line, NormalizedColumnRange.Min), + UTF8OffsetFromIndex(Line, NormalizedColumnRange.Max)); + v4_r32 LineHighlightColor = ColorFromHex(0x66B3CC4C); - range2_r32 LineHighlightBox = Range2R32(V2(LineMarginDim.x+NormalizedColumnRange.Min*GlyphAdvance, LineY), - V2(LineMarginDim.x+NormalizedColumnRange.Max*GlyphAdvance, LineY+LineHeight)); + range2_r32 LineHighlightBox = Range2R32(V2(LineMarginDim.x+ColumnOffsetRange.Min*GlyphAdvance, LineY), + V2(LineMarginDim.x+ColumnOffsetRange.Max*GlyphAdvance, LineY+LineHeight)); PushQuad(Group, LineHighlightBox, LineHighlightColor, LineHighlightColor, LineHighlightColor, LineHighlightColor, 4, 1.4, 0); } } @@ -293,9 +337,59 @@ static void Workspace_BuildTextEditor(workspace_view *View) b32 CursorHasBeenModified = false; - //- sixten: keyboard input -> text op plus handling if(Workspace_ViewIsCurrent(View)) { + //- sixten: handle history + { + history_list *List = &Editor->History; + + //- sixten: undo + if(Platform_KeyPress(UI_EventList(), Key_Z, PlatformModifier_Ctrl)) + { + history_node *Node = List->At; + if(Node != &List->Sentinel) + { + //- sixten: get entry & apply + history_entry Entry = Node->Backward; + MutableStringReplaceRange(&Editor->Text, Entry.ReplaceString, Entry.Range); + workspace_text_data TextData = Workspace_TextDataFromStringChunkList(Editor->ProcessingArena, Editor->Text.String); + Editor->Tokens = TextData.Tokens; + Editor->Lines = TextData.Lines; + Editor->EditState.Cursor = Editor->EditState.Mark = Entry.Range.Min+Entry.ReplaceString.Count; + CursorHasBeenModified = true; + + List->At = Node->Prev; + } + } + + //- sixten: redo + if(Platform_KeyPress(UI_EventList(), Key_Y, PlatformModifier_Ctrl)) + { + history_node *Node = List->At->Next; + if(Node != &List->Sentinel) + { + //- sixten: get entry & apply + history_entry Entry = Node->Forward; + MutableStringReplaceRange(&Editor->Text, Entry.ReplaceString, Entry.Range); + workspace_text_data TextData = Workspace_TextDataFromStringChunkList(Editor->ProcessingArena, Editor->Text.String); + Editor->Tokens = TextData.Tokens; + Editor->Lines = TextData.Lines; + Editor->EditState.Cursor = Editor->EditState.Mark = Entry.Range.Min+Entry.ReplaceString.Count; + CursorHasBeenModified = true; + + List->At = Node; + } + } + } + + //- sixten: select all + if(Platform_KeyPress(UI_EventList(), Key_A, PlatformModifier_Ctrl)) + { + Editor->EditState.Mark = 0; + Editor->EditState.Cursor = Editor->Text.String.Count; + } + + //- sixten: keyboard input -> text op for(platform_event *Event = UI_EventList()->First; Event != 0; Event = Event->Next) @@ -307,16 +401,35 @@ static void Workspace_BuildTextEditor(workspace_view *View) { text_op Op = TextOpFromAction(Scratch.Arena, Editor->Text.String, &Editor->EditState, &Action, &Editor->Lines, Editor->LastTextPoint.Column - 1); - CursorHasBeenModified = true; - + if(DimOfRange(Op.Range) != 0 || !AreEqual(StrLit(""), Op.ReplaceString)) { + //- sixten: append to the history + { + history_list *List = &Editor->History; + + //- sixten: remove the pre-existing history if needed + if(List->Sentinel.Prev != List->At) + { + // sixten(TODO): instead of just removing the links to the old memory, find some way to manage it. + List->Sentinel.Prev->Next = List->At; + List->Sentinel.Prev = List->At; + } + + range1_s64 Selection = Range1S64(Editor->EditState.Cursor, Editor->EditState.Mark); + AppendToHistory(Editor->HistoryArena, List, + HistoryEntry(Editor->HistoryArena, Op.ReplaceString, Op.Range), + HistoryEntry(Editor->HistoryArena, Substring(Editor->Text.String, Op.Range), Range1S64(Op.Range.Min, Op.Range.Min+Op.ReplaceString.Count))); + + List->At = List->Sentinel.Prev; + } + //- sixten: apply the text action MutableStringReplaceRange(&Editor->Text, Op.ReplaceString, Op.Range); - ArenaClear(Editor->ProcessingArena); workspace_text_data TextData = Workspace_TextDataFromStringChunkList(Editor->ProcessingArena, Editor->Text.String); Editor->Tokens = TextData.Tokens; Editor->Lines = TextData.Lines; } + CursorHasBeenModified = true; Editor->EditState.Cursor = Op.NewCursor; Editor->EditState.Mark = Op.NewMark; } @@ -331,13 +444,23 @@ static void Workspace_BuildTextEditor(workspace_view *View) { //- sixten: translate mouse position to text point v2 MouseOffset = Signal.MouseP - EditorBox->Rect.Min - V2(LineMarginWidth, 0); - text_point Point = {(s64)(MouseOffset.y / LineHeight) + 1, (s64)(MouseOffset.x / GlyphAdvance) + 1}; + s64 LineIndex = (s64)(MouseOffset.y / LineHeight); + string Line = Substring(Editor->Text.String, Editor->Lines.Ranges[LineIndex]); + s64 ColumnOffset = (s64)(MouseOffset.x / GlyphAdvance); + s64 ColumnIndex = UTF8IndexFromOffset(Line, ColumnOffset); + + text_point Point = {LineIndex + 1, ColumnIndex + 1}; Editor->EditState.Cursor = Editor->EditState.Mark = OffsetFromTextPoint(Editor->Text.String, Editor->Lines, Point); } //- sixten: translate mouse position to text point v2 MouseOffset = Signal.MouseP - EditorBox->Rect.Min - V2(LineMarginWidth, 0); - text_point Point = {(s64)(MouseOffset.y / LineHeight) + 1, (s64)(MouseOffset.x / GlyphAdvance) + 1}; + s64 LineIndex = (s64)(MouseOffset.y / LineHeight); + string Line = Substring(Editor->Text.String, Editor->Lines.Ranges[LineIndex]); + s64 ColumnOffset = (s64)(MouseOffset.x / GlyphAdvance); + s64 ColumnIndex = UTF8IndexFromOffset(Line, ColumnOffset); + + text_point Point = {LineIndex + 1, ColumnIndex + 1}; Editor->EditState.Cursor = OffsetFromTextPoint(Editor->Text.String, Editor->Lines, Point); CursorHasBeenModified = true; diff --git a/code/vn_workspace_text_editor.h b/code/vn_workspace_text_editor.h index 65aabcd..3f49b9d 100644 --- a/code/vn_workspace_text_editor.h +++ b/code/vn_workspace_text_editor.h @@ -12,19 +12,27 @@ struct mutable_string string String; }; -struct history_node +//////////////////////////////// +//~ sixten: History & Undo Types + +struct history_entry { range1_s64 Range; string ReplaceString; +}; + +struct history_node +{ history_node *Next; + history_node *Prev; + history_entry Forward; + history_entry Backward; }; struct history_list { - memory_arena *HistoryArena; - - history_node *First; - history_node *Last; + history_node *At; + history_node Sentinel; }; //////////////////////////////// @@ -38,19 +46,23 @@ struct workspace_text_data struct workspace_view_text_editor { - //- sixten: processed text + // sixten: processed text memory_arena *ProcessingArena; token_array Tokens; range1_s64_array Lines; - //- sixten: text being edited + // sixten: text being edited mutable_string Text; - //- sixten: text editing + // sixten: text editing text_edit_state EditState; text_point LastTextPoint; - //- sixten: ui building & rendering + // sixten: history + memory_arena *HistoryArena; + history_list History; + + // sixten: ui building & rendering ui_box *ContainerBox; v2 TextDim; v2 Offset; @@ -63,6 +75,12 @@ static mutable_string MutableStringAllocate(u64 Size); static void MutableStringRelease(mutable_string *String); static void MutableStringReplaceRange(mutable_string *String, string ReplaceString, range1_s64 Range); +//////////////////////////////// +//~ sixten: History & Undo Functions + +static history_entry HistoryEntry(memory_arena *Arena, string ReplaceString, range1_s64 Range); +static void AppendToHistory(memory_arena *Arena, history_list *List, history_entry Forward, history_entry Backward); + //////////////////////////////// //~ sixten: Workspace Text Editor Functions diff --git a/code/vn_workspace_view.cpp b/code/vn_workspace_view.cpp index fb95ec1..a9ff068 100644 --- a/code/vn_workspace_view.cpp +++ b/code/vn_workspace_view.cpp @@ -10,17 +10,35 @@ inline workspace_view *Workspace_CreateNewView(workspace_view_type Type, workspa switch(View->Type) { case Workspace_View_Editor: - { View->Data = PushStruct(View->Arena, workspace_view_editor); } break; + { + View->Data = PushStruct(View->Arena, workspace_view_editor); + } break; + case Workspace_View_CommandPalette: - { View->Data = PushStruct(View->Arena, workspace_view_command_palette); } break; + { + View->Data = PushStruct(View->Arena, workspace_view_command_palette); + } break; + case Workspace_View_Settings: - { View->Data = PushStruct(View->Arena, workspace_view_settings); } break; + { + View->Data = PushStruct(View->Arena, workspace_view_settings); + } break; + case Workspace_View_TextEditor: { View->Data = PushStruct(View->Arena, workspace_view_text_editor); + workspace_view_text_editor *Editor = (workspace_view_text_editor *)View->Data; Editor->ProcessingArena = ArenaAllocate(Gigabytes(1)); - Editor->Text = MutableStringAllocate(Gigabytes(2)); + Editor->Text = MutableStringAllocate(Gigabytes(1)); + Editor->HistoryArena = ArenaAllocate(Gigabytes(1)); + + SenDLLInit(&Editor->History.Sentinel); + Editor->History.At = &Editor->History.Sentinel; + + workspace_text_data TextData = Workspace_TextDataFromStringChunkList(Editor->ProcessingArena, Editor->Text.String); + Editor->Tokens = TextData.Tokens; + Editor->Lines = TextData.Lines; } break; } @@ -36,6 +54,17 @@ inline workspace_view *Workspace_CreateNewView(workspace_view_type Type, workspa inline void Workspace_DestroyView(workspace_view *View) { + switch(View->Type) + { + case Workspace_View_TextEditor: + { + workspace_view_text_editor *Editor = (workspace_view_text_editor *)View->Data; + ArenaRelease(Editor->ProcessingArena); + MutableStringRelease(&Editor->Text); + ArenaRelease(Editor->HistoryArena); + } break; + } + // sixten(NOTE): This function does not ensure that the view is not being used anywhere else. ArenaRelease(View->Arena); } diff --git a/code/win32__main.rdbg b/code/win32__main.rdbg index dab87b4..714c650 100644 Binary files a/code/win32__main.rdbg and b/code/win32__main.rdbg differ diff --git a/fonts/DejaVuSansMono-Bold.ttf b/fonts/DejaVuSansMono-Bold.ttf new file mode 100644 index 0000000..8184ced Binary files /dev/null and b/fonts/DejaVuSansMono-Bold.ttf differ diff --git a/fonts/DejaVuSansMono-Oblique.ttf b/fonts/DejaVuSansMono-Oblique.ttf new file mode 100644 index 0000000..4c858d4 Binary files /dev/null and b/fonts/DejaVuSansMono-Oblique.ttf differ