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 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

View File

@ -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);
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;
return(Iter);
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;
}
static void Advance(utf8_iterator *Iter)
{
u8 *At = Iter->Data.Data + Iter->Index;
Result.Codepoint = Codepoint >> FinalShift[Length];
Result.Size = Length;
}
}
return(Result);
}
if(Iter->Index < Iter->Data.Count)
static u32 EncodeUTF8Codepoint(u8 *Dest, u32 Codepoint)
{
if((At[0] & 0x80) == 0x00)
u32 Size = 0;
u8 DummyDest[4];
Dest = Dest?Dest:DummyDest;
if(Codepoint < (1<<8))
{
Iter->Codepoint = (At[0] & 0x7F);
Iter->Index += 1;
Dest[0] = Codepoint;
Size = 1;
}
else if((At[0] & 0xE0) == 0xC0)
else if (Codepoint < (1 << 11))
{
Iter->Codepoint = ((At[0] & 0x1F) << 6)|(At[1] & 0x3F);
Iter->Index += 2;
Dest[0] = 0xC0|(Codepoint >> 6);
Dest[1] = 0x80|(Codepoint & 0x3F);
Size = 2;
}
else if((At[0] & 0xF0) == 0xE0)
else if (Codepoint < (1 << 16))
{
Iter->Codepoint = ((At[0] & 0x0F) << 12)|((At[1] & 0x3F) << 6)|(At[2] & 0x3F);
Iter->Index += 3;
Dest[0] = 0xE0|(Codepoint >> 12);
Dest[1] = 0x80|((Codepoint >> 6) & 0x3F);
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
{
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;

View File

@ -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);

View File

@ -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);

View File

@ -120,6 +120,7 @@ static glyph_atlas *CreateGlyphAtlas(vn_render_commands *RenderCommands,
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"));
@ -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')
{

View File

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

View File

@ -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);
}

View File

@ -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;
}
}

View File

@ -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);
@ -782,10 +781,20 @@ static void UI_LayoutBox(ui_box *Box)
for(ui_box *Child = Box->First;
Child != 0;
Child = Child->Next)
{
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);
}
}

View File

@ -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

View File

@ -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")) ||
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")) ||
AreEqual(TokenString, StrLit("true")) ||
AreEqual(TokenString, StrLit("false")))
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;

View File

@ -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

View File

@ -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);
}

Binary file not shown.

Binary file not shown.

Binary file not shown.