Added undo & redo support.

main
sixtenhugosson 2023-07-24 15:50:57 +02:00
parent a9f257ca7c
commit 30e384e0a4
16 changed files with 424 additions and 120 deletions

View File

@ -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 DLLRemove(First, Last, Element) DLLRemove_NP(First, Last, Element, Next, Prev)
#define DLLIsEmpty(First) ((First) == 0) #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 //- sixten: Stringify
#define _Stringify(x) #x #define _Stringify(x) #x

View File

@ -509,54 +509,141 @@ read_only u8 UTF8Lengths[] =
0, // 11111 0, // 11111
}; };
static utf8_iterator IterateUTF8String(string String) static string_decode DecodeUTF8Codepoint(u8 *Data, s64 Count)
{ {
utf8_iterator Iter = {}; string_decode Result = {};
Iter.Data = String; u8 FirstByteMask[] = {0, 0x7F, 0x1F, 0x0F, 0x07};
Advance(&Iter); u8 FinalShift[] = {0, 18, 12, 6, 0};
if(Count > 0)
return(Iter); {
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; u32 Size = 0;
u8 DummyDest[4];
if(Iter->Index < Iter->Data.Count) Dest = Dest?Dest:DummyDest;
if(Codepoint < (1<<8))
{ {
if((At[0] & 0x80) == 0x00) Dest[0] = Codepoint;
{ Size = 1;
Iter->Codepoint = (At[0] & 0x7F); }
Iter->Index += 1; else if (Codepoint < (1 << 11))
} {
else if((At[0] & 0xE0) == 0xC0) Dest[0] = 0xC0|(Codepoint >> 6);
{ Dest[1] = 0x80|(Codepoint & 0x3F);
Iter->Codepoint = ((At[0] & 0x1F) << 6)|(At[1] & 0x3F); Size = 2;
Iter->Index += 2; }
} else if (Codepoint < (1 << 16))
else if((At[0] & 0xF0) == 0xE0) {
{ Dest[0] = 0xE0|(Codepoint >> 12);
Iter->Codepoint = ((At[0] & 0x0F) << 12)|((At[1] & 0x3F) << 6)|(At[2] & 0x3F); Dest[1] = 0x80|((Codepoint >> 6) & 0x3F);
Iter->Index += 3; Dest[2] = 0x80|(Codepoint & 0x3F);
} Size = 3;
else if((Iter->Data.Data[Iter->Index] & 0xF8) == 0xF0) }
{ else if (Codepoint < (1 << 21))
Iter->Codepoint = ((At[0] & 0x0F) << 18)|((At[1] & 0x3F) << 12)|((At[2] & 0x3F) << 6)|(At[3] & 0x3F); {
Iter->Index += 4; 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 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); 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) static s64 UTF8FromCodepoint(u8 *Out, u32 Codepoint)
{ {
s64 Length = 0; s64 Length = 0;

View File

@ -104,11 +104,13 @@ static string RemoveAll(memory_arena *Arena, string Text, char ToRemove);
static s64 StringLength(char *String); static s64 StringLength(char *String);
#if 0
///////////////////////////////////// /////////////////////////////////////
//~ sixten: String Chunk Functions //~ sixten: String Chunk Functions
static string_chunk_list MakeStringChunkList(s64 ChunkSize); static string_chunk_list MakeStringChunkList(s64 ChunkSize);
static string JoinStringChunkList(memory_arena *Arena, string_chunk_list *List); 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); static void ReplaceRange(memory_arena *Arena, string_chunk_list *List, string Text, range1_s64 Range);
#endif
//~ sixten: String list //~ sixten: String list
@ -118,19 +120,17 @@ static string JoinStringList(string_list *List, memory_arena *Arena);
//~ sixten: Unicode //~ sixten: Unicode
struct utf8_iterator struct string_decode
{ {
// sixten: Input
string Data;
s64 Index;
// sixten: Output
u32 Codepoint; u32 Codepoint;
s32 Size;
}; };
static utf8_iterator IterateUTF8String(string String); static string_decode DecodeUTF8Codepoint(u8 *Data, s64 Count);
static void Advance(utf8_iterator *Iter); static u32 EncodeUTF8Codepoint(u8 *Dest, u32 Codepoint);
static b32 IsValid(utf8_iterator *Iter);
static string_decode DecodeUTF16Codepoint(u8 *Data, s64 Count);
static u32 EncodeUTF16Codepoint(u16 *Dest, u32 Codepoint);
static s64 UTF8FromCodepoint(u8 *Out, u32 Codepoint); static s64 UTF8FromCodepoint(u8 *Out, u32 Codepoint);

View File

@ -23,8 +23,8 @@ per_thread debug_settings *DEBUG_DebugSettings = 0;
#include "vn_ui.h" #include "vn_ui.h"
#include "vn_ui_utils.h" #include "vn_ui_utils.h"
#include "vn_workspace.h" #include "vn_workspace.h"
#include "vn_theme_dark.h"
#include "vn_animation_curve.h" #include "vn_animation_curve.h"
#include "vn_theme_dark.h"
#include "vn_scene.h" #include "vn_scene.h"
#include "vn_tokenizer.cpp" #include "vn_tokenizer.cpp"
@ -163,10 +163,12 @@ VN_UPDATE_AND_RENDER(VN_UpdateAndRender)
render_group Group = BeginRenderGroup(RenderCommands); render_group Group = BeginRenderGroup(RenderCommands);
PushClear(&Group, V3(0.1, 0.1, 0.1)); PushClear(&Group, V3(0.1, 0.1, 0.1));
#if 0
PushTexturedQuad(&Group, PushTexturedQuad(&Group,
Range2R32(V2R32(0, 0), RenderCommands->RenderDim), Range2R32(V2R32(0, 0), RenderCommands->RenderDim),
Range2R32(V2R32(0, 0), ConvertV2ToR32(DimFromTexture(State->BackgroundTexture))), Range2R32(V2R32(0, 0), ConvertV2ToR32(DimFromTexture(State->BackgroundTexture))),
Color_White, Color_White, Color_White, Color_White, 0, 0, 0, State->BackgroundTexture); Color_White, Color_White, Color_White, Color_White, 0, 0, 0, State->BackgroundTexture);
#endif
UI_RenderFrame(&Group, State->GlyphAtlas); UI_RenderFrame(&Group, State->GlyphAtlas);

View File

@ -117,11 +117,12 @@ static glyph_atlas *CreateGlyphAtlas(vn_render_commands *RenderCommands,
Atlas->RenderCommands = RenderCommands; Atlas->RenderCommands = RenderCommands;
Atlas->Texture = RenderCommands->AllocateTexture(V2S32(BitmapSize, BitmapSize), Render_TextureFormat_R8, 0); 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_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_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_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_MonospaceOblique].Data = Platform_ReadEntireFile(Atlas->Arena, StrLit("fonts/DejaVuSansMono-Oblique.ttf"));
Atlas->Fonts[Font_Icons].Data = Platform_ReadEntireFile(Atlas->Arena, StrLit("fonts/icons.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; for(s32 FontIndex = 0;
FontIndex < Font_Count; FontIndex < Font_Count;
@ -145,11 +146,13 @@ static r32 PushText(render_group *Group, glyph_atlas *Atlas, font_id Font,
string Text) string Text)
{ {
r32 OffsetX = 0; r32 OffsetX = 0;
for(utf8_iterator Iter = IterateUTF8String(Text); u8 *TextBegin = Text.Data;
IsValid(&Iter); u8 *TextEnd = TextBegin+Text.Count;
Advance(&Iter)) 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)); glyph *Glyph = GetGlyph(Atlas, Font, Codepoint, Size*Font_Oversample, GetSubpixelSegmentAtP(P.x*Font_Oversample));
Assert(Glyph); Assert(Glyph);
@ -189,11 +192,13 @@ inline r32 CalculateRasterizedTextWidth(glyph_atlas *Atlas, font_id Font, r32 Si
{ {
r32 X = 0; r32 X = 0;
for(utf8_iterator Iter = IterateUTF8String(Text); u8 *TextBegin = Text.Data;
Iter.Codepoint != 0; u8 *TextEnd = TextBegin+Text.Count;
Advance(&Iter)) 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)); glyph *Glyph = GetGlyph(Atlas, Font, Codepoint, Size*Font_Oversample, GetSubpixelSegmentAtP(X*Font_Oversample));
Assert(Glyph); Assert(Glyph);
@ -211,11 +216,13 @@ inline r32 CalculateRasterizedTextHeight(glyph_atlas *Atlas, font_id Font, r32 S
r32 Y = Size*Scale; r32 Y = Size*Scale;
for(utf8_iterator Iter = IterateUTF8String(Text); u8 *TextBegin = Text.Data;
Iter.Codepoint != 0; u8 *TextEnd = TextBegin+Text.Count;
Advance(&Iter)) 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') if(Codepoint == '\n')
{ {

View File

@ -8,6 +8,7 @@ enum font_id
Font_Regular, Font_Regular,
Font_Bold, Font_Bold,
Font_Monospace, Font_Monospace,
Font_MonospaceOblique,
Font_Hand, Font_Hand,
Font_Icons, Font_Icons,

View File

@ -423,6 +423,10 @@ static text_op TextOpFromAction(memory_arena *Arena, string String,
Op.NewMark = Op.NewCursor; 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); return(Op);
} }

View File

@ -151,16 +151,16 @@ static tokenize_result T_TokenizeFromText(memory_arena *Arena, string Filename,
TokenFlags = TokenFlag_Identifier; TokenFlags = TokenFlag_Identifier;
TokenStart = Byte; TokenStart = Byte;
TokenEnd = Byte; TokenEnd = Byte;
Byte += 1; Byte += UTF8Lengths[*Byte>>3];
for(;Byte <= TextEnd; Byte += 1) for(;Byte <= TextEnd; Byte += UTF8Lengths[*Byte>>3])
{ {
TokenEnd += 1;
if(Byte == TextEnd || !(('A' <= *Byte && *Byte <= 'Z') || if(Byte == TextEnd || !(('A' <= *Byte && *Byte <= 'Z') ||
('a' <= *Byte && *Byte <= 'z') || ('a' <= *Byte && *Byte <= 'z') ||
('0' <= *Byte && *Byte <= '9') || ('0' <= *Byte && *Byte <= '9') ||
(UTF8Lengths[*Byte>>3] > 1) || (UTF8Lengths[*Byte>>3] > 1) ||
*Byte == '_')) *Byte == '_'))
{ {
TokenEnd = Byte;
break; break;
} }
} }

View File

@ -641,12 +641,6 @@ static void UI_DrawBox(ui_box *Box, render_group *Group, glyph_atlas *GlyphAtlas
} }
#endif #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; for(ui_box *Child = Box->First;
Child != 0; Child != 0;
Child = Child->Next) 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) if(Child->Flags & UI_BoxFlag_DrawDropShadow)
{ {
r32 ShadowRadius = 10; 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), range2_r32 Rect = Range2R32(Child->Rect.Min - V2R32(ShadowRadius, ShadowRadius),
Box->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); 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; for(ui_box *Child = Box->First;
Child != 0; Child != 0;
Child = Child->Next) 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); PushQuad(Group, Box->Rect, Box->BorderColor, Box->CornerRadius, 0.8, Box->BorderThickness);
} }
} }
static r32 UI_CalculateChildrenSum(ui_box *Box, axis2 Axis); static r32 UI_CalculateChildrenSum(ui_box *Box, axis2 Axis);
@ -783,9 +782,19 @@ static void UI_LayoutBox(ui_box *Box)
Child != 0; Child != 0;
Child = Child->Next) Child = Child->Next)
{ {
Child->Rect.Min = Box->Rect.Min + Child->ComputedRelativeP + Box->Offset; if(Child->Flags & UI_BoxFlag_AnimatePosition)
Child->Rect.Max = Child->Rect.Min + Child->ComputedDim; {
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); UI_LayoutBox(Child);
} }
} }

View File

@ -53,6 +53,7 @@ enum
UI_BoxFlag_FloatingX = (1 << 10), UI_BoxFlag_FloatingX = (1 << 10),
UI_BoxFlag_FloatingY = (1 << 11), UI_BoxFlag_FloatingY = (1 << 11),
UI_BoxFlag_Scrollable = (1 << 12), UI_BoxFlag_Scrollable = (1 << 12),
UI_BoxFlag_AnimatePosition = (1 << 13),
}; };
typedef u32 ui_box_flags; typedef u32 ui_box_flags;
@ -61,19 +62,21 @@ typedef UI_CUSTOM_DRAW_CALLBACK(ui_custom_draw_callback);
struct ui_box struct ui_box
{ {
// sixten: building
ui_box *First; ui_box *First;
ui_box *Last; ui_box *Last;
ui_box *Next; ui_box *Next;
ui_box *Prev; ui_box *Prev;
ui_box *Parent; ui_box *Parent;
// sixten: hashing
ui_box *HashNext; ui_box *HashNext;
ui_box *HashPrev; ui_box *HashPrev;
ui_key Key; ui_key Key;
ui_key Seed; ui_key Seed;
u64 LastFrameTouched; u64 LastFrameTouched;
// sixten: per-frame data
ui_box_flags Flags; ui_box_flags Flags;
string String; string String;
ui_size SemanticSize[Axis2_Count]; ui_size SemanticSize[Axis2_Count];
@ -87,17 +90,17 @@ struct ui_box
font_id Font; font_id Font;
r32 FontSize; r32 FontSize;
v2 Offset; v2 Offset;
ui_custom_draw_callback *DrawCallback; ui_custom_draw_callback *DrawCallback;
void *DrawCallbackData; void *DrawCallbackData;
v2 ComputedRelativeP; v2 ComputedRelativeP;
v2 ComputedDim; v2 ComputedDim;
// sixten: retained data
range2_r32 Rect; range2_r32 Rect;
r32 HotTransition; r32 HotTransition;
r32 ActiveTransition; r32 ActiveTransition;
v2 ApproachingRelativeP;
}; };
struct ui_box_bucket struct ui_box_bucket

View File

@ -23,13 +23,40 @@ static void MutableStringReplaceRange(mutable_string *MutString, string ReplaceS
if(NewCount > MutString->String.Count) if(NewCount > MutString->String.Count)
{ {
s64 ToAllocate = 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)); 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); Copy(MutString->String.Data+Range.Min, ReplaceString.Data, ReplaceString.Count);
MutString->String.Count = NewCount; 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); temporary_memory Scratch = GetScratch(&Arena, 1);
//- sixten: clear the target arena
ArenaClear(Arena);
//- sixten: tokenize the text //- sixten: tokenize the text
tokenize_result TokenizeResult = T_TokenizeFromText(Arena, StrLit("*scratch*"), Text); tokenize_result TokenizeResult = T_TokenizeFromText(Arena, StrLit("*scratch*"), Text);
token_array Tokens = TokenizeResult.Tokens; token_array Tokens = TokenizeResult.Tokens;
@ -164,9 +194,10 @@ static UI_CUSTOM_DRAW_CALLBACK(Workspace_TextEditorDrawCallback)
{ {
string TokenString = T_StringFromToken(Editor->Text.String, *Token); 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; 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_Reserved) { Color = Color_Grey; }
else if(Token->Flags & TokenFlag_Symbol) { Color = ColorFromHex(0xbd2d2dff); } else if(Token->Flags & TokenFlag_Symbol) { Color = ColorFromHex(0xbd2d2dff); }
else if(Token->Flags & TokenFlag_StringLiteral) { Color = ColorFromHex(0xffa900ff); } 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) else if(Token->Flags & TokenFlag_Identifier)
{ {
//- sixten: check for keywords //- sixten: check for keywords
if(AreEqual(TokenString, StrLit("var")) || if(AreEqual(TokenString, StrLit("true")) ||
AreEqual(TokenString, StrLit("proc")) ||
AreEqual(TokenString, StrLit("branch")) ||
AreEqual(TokenString, StrLit("jump")) ||
AreEqual(TokenString, StrLit("if")) ||
AreEqual(TokenString, StrLit("true")) ||
AreEqual(TokenString, StrLit("false"))) 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); Color = ColorFromHex(0xf0c674ff);
} }
@ -192,7 +226,7 @@ static UI_CUSTOM_DRAW_CALLBACK(Workspace_TextEditorDrawCallback)
//- sixten: render & advance by token //- sixten: render & advance by token
if(!(Token->Flags & TokenGroup_Whitespace)) 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 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), 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)); AnimationCurve_AnimateValueF(TargetCursorP.y, TargetCursorP.y, 0.1, "Workspace Text Editor Cursor Y %p", Editor));
v2 CursorDim = V2(2, LineHeight); v2 CursorDim = V2(2, LineHeight);
@ -231,28 +270,33 @@ static UI_CUSTOM_DRAW_CALLBACK(Workspace_TextEditorDrawCallback)
{ {
text_range Selection = TextRange(CursorTextP, MarkTextP); text_range Selection = TextRange(CursorTextP, MarkTextP);
range1_s64 LineRange = Range1S64(Selection.Min.Line, Selection.Max.Line); 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; r32 LineY = Box->Rect.Min.y + LineIndex*LineHeight;
if(Contains(LineRange, Line + 1)) if(Contains(LineRange, LineIndex + 1))
{ {
range1_s64 ColumnRange = Lines->Ranges[Line]; range1_s64 ColumnRange = Lines->Ranges[LineIndex];
range1_s64 NormalizedColumnRange = Range1S64(0, DimOfRange(ColumnRange)); 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); 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)); 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); 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); v4_r32 LineHighlightColor = ColorFromHex(0x66B3CC4C);
range2_r32 LineHighlightBox = Range2R32(V2(LineMarginDim.x+NormalizedColumnRange.Min*GlyphAdvance, LineY), range2_r32 LineHighlightBox = Range2R32(V2(LineMarginDim.x+ColumnOffsetRange.Min*GlyphAdvance, LineY),
V2(LineMarginDim.x+NormalizedColumnRange.Max*GlyphAdvance, LineY+LineHeight)); V2(LineMarginDim.x+ColumnOffsetRange.Max*GlyphAdvance, LineY+LineHeight));
PushQuad(Group, LineHighlightBox, LineHighlightColor, LineHighlightColor, LineHighlightColor, LineHighlightColor, 4, 1.4, 0); 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; b32 CursorHasBeenModified = false;
//- sixten: keyboard input -> text op plus handling
if(Workspace_ViewIsCurrent(View)) 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; for(platform_event *Event = UI_EventList()->First;
Event != 0; Event != 0;
Event = Event->Next) 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); 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); MutableStringReplaceRange(&Editor->Text, Op.ReplaceString, Op.Range);
ArenaClear(Editor->ProcessingArena);
workspace_text_data TextData = Workspace_TextDataFromStringChunkList(Editor->ProcessingArena, Editor->Text.String); workspace_text_data TextData = Workspace_TextDataFromStringChunkList(Editor->ProcessingArena, Editor->Text.String);
Editor->Tokens = TextData.Tokens; Editor->Tokens = TextData.Tokens;
Editor->Lines = TextData.Lines; Editor->Lines = TextData.Lines;
} }
CursorHasBeenModified = true;
Editor->EditState.Cursor = Op.NewCursor; Editor->EditState.Cursor = Op.NewCursor;
Editor->EditState.Mark = Op.NewMark; Editor->EditState.Mark = Op.NewMark;
} }
@ -331,13 +444,23 @@ static void Workspace_BuildTextEditor(workspace_view *View)
{ {
//- sixten: translate mouse position to text point //- sixten: translate mouse position to text point
v2 MouseOffset = Signal.MouseP - EditorBox->Rect.Min - V2(LineMarginWidth, 0); 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); Editor->EditState.Cursor = Editor->EditState.Mark = OffsetFromTextPoint(Editor->Text.String, Editor->Lines, Point);
} }
//- sixten: translate mouse position to text point //- sixten: translate mouse position to text point
v2 MouseOffset = Signal.MouseP - EditorBox->Rect.Min - V2(LineMarginWidth, 0); 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); Editor->EditState.Cursor = OffsetFromTextPoint(Editor->Text.String, Editor->Lines, Point);
CursorHasBeenModified = true; CursorHasBeenModified = true;

View File

@ -12,19 +12,27 @@ struct mutable_string
string String; string String;
}; };
struct history_node ////////////////////////////////
//~ sixten: History & Undo Types
struct history_entry
{ {
range1_s64 Range; range1_s64 Range;
string ReplaceString; string ReplaceString;
};
struct history_node
{
history_node *Next; history_node *Next;
history_node *Prev;
history_entry Forward;
history_entry Backward;
}; };
struct history_list struct history_list
{ {
memory_arena *HistoryArena; history_node *At;
history_node Sentinel;
history_node *First;
history_node *Last;
}; };
//////////////////////////////// ////////////////////////////////
@ -38,19 +46,23 @@ struct workspace_text_data
struct workspace_view_text_editor struct workspace_view_text_editor
{ {
//- sixten: processed text // sixten: processed text
memory_arena *ProcessingArena; memory_arena *ProcessingArena;
token_array Tokens; token_array Tokens;
range1_s64_array Lines; range1_s64_array Lines;
//- sixten: text being edited // sixten: text being edited
mutable_string Text; mutable_string Text;
//- sixten: text editing // sixten: text editing
text_edit_state EditState; text_edit_state EditState;
text_point LastTextPoint; text_point LastTextPoint;
//- sixten: ui building & rendering // sixten: history
memory_arena *HistoryArena;
history_list History;
// sixten: ui building & rendering
ui_box *ContainerBox; ui_box *ContainerBox;
v2 TextDim; v2 TextDim;
v2 Offset; v2 Offset;
@ -63,6 +75,12 @@ static mutable_string MutableStringAllocate(u64 Size);
static void MutableStringRelease(mutable_string *String); static void MutableStringRelease(mutable_string *String);
static void MutableStringReplaceRange(mutable_string *String, string ReplaceString, range1_s64 Range); 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 //~ sixten: Workspace Text Editor Functions

View File

@ -10,17 +10,35 @@ inline workspace_view *Workspace_CreateNewView(workspace_view_type Type, workspa
switch(View->Type) switch(View->Type)
{ {
case Workspace_View_Editor: 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: 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: 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: case Workspace_View_TextEditor:
{ {
View->Data = PushStruct(View->Arena, workspace_view_text_editor); View->Data = PushStruct(View->Arena, workspace_view_text_editor);
workspace_view_text_editor *Editor = (workspace_view_text_editor *)View->Data; workspace_view_text_editor *Editor = (workspace_view_text_editor *)View->Data;
Editor->ProcessingArena = ArenaAllocate(Gigabytes(1)); 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; } break;
} }
@ -36,6 +54,17 @@ inline workspace_view *Workspace_CreateNewView(workspace_view_type Type, workspa
inline void Workspace_DestroyView(workspace_view *View) 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. // sixten(NOTE): This function does not ensure that the view is not being used anywhere else.
ArenaRelease(View->Arena); ArenaRelease(View->Arena);
} }

Binary file not shown.

Binary file not shown.

Binary file not shown.