Revamped the text editor.

main
sixtenhugosson 2023-12-26 09:11:41 +01:00
parent 0e023d31ed
commit 44f5f66b9f
20 changed files with 875 additions and 1000 deletions

View File

@ -89,13 +89,13 @@ static void ArenaRelease(arena *Arena)
for(;Node != Arena;)
{
arena *Next = Node->Prev;
Platform.Deallocate(Node);
Platform.Deallocate(Node, Node->Size);
Node = Next;
}
#if VN_ASAN_ENABLED
ASAN_POISON_MEMORY_REGION(Arena, Arena->Size);
#endif
Platform.Deallocate(Arena);
Platform.Deallocate(Arena, Arena->Size);
}
else
{
@ -188,7 +188,7 @@ static void ArenaPopTo(arena *Arena, u64 Position)
#if VN_ASAN_ENABLED
ASAN_POISON_MEMORY_REGION(Node, Node->Size);
#endif
Platform.Deallocate(Node);
Platform.Deallocate(Node, Node->Size);
Node = Prev;
}
}

View File

@ -411,118 +411,6 @@ static string JoinStringList(string_list *List, arena *Arena)
return(Result);
}
/////////////////////////////////////
//~ sixten: String Chunk Functions
static string_chunk_list MakeStringChunkList(s64 ChunkSize)
{
string_chunk_list Result = {};
Result.ChunkSize = ChunkSize;
return(Result);
}
static string JoinStringChunkList(arena *Arena, string_chunk_list *List)
{
string Result = {};
Result.Count = List->TotalCount;
Result.Data = PushArrayNoClear(Arena, u8, List->TotalCount + 1);
s64 Index = 0;
s64 CountRemaining = List->TotalCount;
for(string_node *Node = List->First; Node != 0; Node = Node->Next)
{
string String = Node->String;
Copy(Result.Data + Index, String.Data, Min(CountRemaining, List->ChunkSize));
CountRemaining -= List->ChunkSize;
Index += String.Count;
}
return(Result);
}
// sixten(TODO): Incomplete, remove maybe?
static void ReplaceRange(arena *Arena, string_chunk_list *List, string Text, range1_s64 Range)
{
s64 NewTotalCount = Max(0ULL, List->TotalCount - DimOfRange(Range)) + Text.Count;
//- sixten: do we need to allocate more chunks?
if(List->ChunkSize*List->ChunkCount < NewTotalCount)
{
s64 ChunksToAlloc = (NewTotalCount - List->ChunkSize*List->ChunkCount)/List->ChunkSize + 1;
for(s64 Index = 0; Index < ChunksToAlloc; Index += 1)
{
if(DLLIsEmpty(List->FirstFree))
{
string_node *Node = PushStructNoClear(Arena, string_node);
Node->String.Count = 0;
Node->String.Data = PushArrayNoClear(Arena, u8, List->ChunkSize);
DLLInsertLast(List->First, List->Last, Node);
}
else
{
string_node *Node = List->FirstFree;
Node->String.Count = 0;
DLLRemove(List->FirstFree, List->LastFree, Node);
DLLInsertLast(List->First, List->Last, Node);
}
}
List->ChunkCount += ChunksToAlloc;
}
s64 CountDelta = NewTotalCount - List->TotalCount;
// sixten: I cannot be bothered enough to figure out the correct implementation for this. However, if I do this - remember that you can rearrange
// the ordering of the linked list, instead of actually copying over the bytes for the majority of this.
Assert(AbsoluteValue(CountDelta) < List->ChunkSize);
//- sixten: find the first and last affected nodes
s64 FirstAffectedNodeIndex = Range.Min/List->ChunkSize;
s64 LastAffectedNodeIndex = Range.Max/List->ChunkSize;
string_node *FirstAffectedNode = List->First;
for(s64 WalkIndex = 0; WalkIndex < FirstAffectedNodeIndex; WalkIndex += 1)
{
FirstAffectedNode = FirstAffectedNode->Next;
}
string_node *LastAffectedNode = FirstAffectedNode;
for(s64 WalkIndex = 0; WalkIndex < LastAffectedNodeIndex-FirstAffectedNodeIndex; WalkIndex += 1)
{
LastAffectedNode = LastAffectedNode->Next;
}
if(CountDelta >= 0)
{
//- sixten: insertion - make room and the copy the data
s64 WriteOffset = Range.Min%List->ChunkSize;
for(string_node *Node = List->Last; Node != 0; Node = Node->Prev)
{
CopyReverse(Node->String.Data+CountDelta+WriteOffset, Node->String.Data+WriteOffset, List->ChunkSize-CountDelta-WriteOffset);
if(Node == LastAffectedNode)
{
break;
}
else
{
Copy(Node->String.Data, Node->Prev->String.Data+List->ChunkSize-CountDelta, CountDelta);
}
}
s64 SourceOffset = 0;
for(string_node *Node = FirstAffectedNode; Node != 0; Node = Node->Next)
{
Copy(Node->String.Data+WriteOffset, Text.Data+SourceOffset, Min(List->ChunkSize-WriteOffset, Text.Count-SourceOffset));
SourceOffset += List->ChunkSize;
if(Node == LastAffectedNode)
{
break;
}
}
}
else if(CountDelta < 0)
{
//- sixten: deletion
}
List->TotalCount = NewTotalCount;
}
//~ sixten: Unicode

View File

@ -35,20 +35,6 @@ struct string_list
s64 TotalCount;
};
/////////////////////////////////////
//~ sixten: String Chunk Types
struct string_chunk_list
{
s64 ChunkSize;
s64 ChunkCount;
s64 TotalCount;
string_node *First;
string_node *Last;
string_node *FirstFree;
string_node *LastFree;
};
/////////////////////////////////////
//~ sixten: Char funcitons

View File

@ -3,7 +3,7 @@
#define PLATFORM_COMMIT(name) void name(void *Pointer, u64 Size)
#define PLATFORM_DECOMMIT(name) void name(void *Pointer, u64 Size)
#define PLATFORM_ALLOCATE(name) void * name(u64 Size)
#define PLATFORM_DEALLOCATE(name) void name(void *Pointer)
#define PLATFORM_DEALLOCATE(name) void name(void *Pointer, u64 Size)
#define PLATFORM_OPEN_FILE(name) platform_file_handle name(string Path, platform_access_flags FileAccess)
#define PLATFORM_CLOSE_FILE(name) void name(platform_file_handle Handle)
#define PLATFORM_READ_FILE(name) void name(platform_file_handle Handle, void *Dest, u64 Offset, u64 Size)

View File

@ -98,7 +98,7 @@ VN_UPDATE_AND_RENDER(VN_UpdateAndRender)
arena *Arena = ArenaAlloc(Kilobytes(24), true);
State = Memory->State = PushStruct(Arena, vn_state);
State->Arena = Arena;
State->FrameArena = ArenaAlloc(Megabytes(1), true);
State->FrameArena = ArenaAlloc(Kilobytes(1), true);
State->GlyphAtlas = CreateGlyphAtlas(RenderCommands);
State->Config = CreateConfig();
@ -213,12 +213,23 @@ VN_UPDATE_AND_RENDER(VN_UpdateAndRender)
UI_RenderFrame(&Group, State->GlyphAtlas);
#if VN_INTERNAL
r32 DEBUGDisplayOffsetY = 20;
if(DEBUG_DebugSettings->ListHotAndActive)
{
PushText(&Group, State->GlyphAtlas, Font_Regular, V2(5, RenderCommands->RenderDim.y - 20), 15, Color_Grey,
PushText(&Group, State->GlyphAtlas, Font_Regular, V2(5, RenderCommands->RenderDim.y - DEBUGDisplayOffsetY), 15, Color_Grey,
PushFormat(State->UI.FrameArena, "Hot: %S:%llu", UI_BoxStringFromKey(UI_HotKey()), UI_HotKey()));
PushText(&Group, State->GlyphAtlas, Font_Regular, V2(5, RenderCommands->RenderDim.y - 40), 15, Color_Grey,
DEBUGDisplayOffsetY += 20;
PushText(&Group, State->GlyphAtlas, Font_Regular, V2(5, RenderCommands->RenderDim.y - DEBUGDisplayOffsetY), 15, Color_Grey,
PushFormat(State->UI.FrameArena, "Active: %S:%llu", UI_BoxStringFromKey(UI_ActiveKey()), UI_ActiveKey()));
DEBUGDisplayOffsetY += 20;
}
if(DEBUG_DebugSettings->RenderFPSCounter)
{
PushText(&Group, State->GlyphAtlas, Font_Regular, V2(5, RenderCommands->RenderDim.y - DEBUGDisplayOffsetY), 15, Color_Grey,
PushFormat(State->UI.FrameArena, "FPS: %.2f", 1.0f/Input->dtForFrame));
DEBUGDisplayOffsetY += 20;
}
#endif

View File

@ -92,6 +92,7 @@ enum platform_key
Key_PageUp, Key_PageDown,
Key_Home, Key_End,
Key_Plus, Key_Minus,
Key_Backspace, Key_Delete,

View File

@ -5,7 +5,7 @@
{ Commit commit COMMIT `void` `void *Pointer, u64 Size` }
{ Decommit decommit DECOMMIT `void` `void *Pointer, u64 Size` }
{ Allocate allocate ALLOCATE `void *` `u64 Size` }
{ Deallocate deallocate DEALLOCATE `void` `void *Pointer` }
{ Deallocate deallocate DEALLOCATE `void` `void *Pointer, u64 Size` }
{ OpenFile open_file OPEN_FILE `platform_file_handle` `string Path, platform_access_flags FileAccess` }
{ CloseFile close_file CLOSE_FILE `void` `platform_file_handle Handle` }
{ ReadFile read_file READ_FILE `void` `platform_file_handle Handle, void *Dest, u64 Offset, u64 Size` }

View File

@ -91,22 +91,6 @@ static void S_PopEmissionTarget(scene_compiler *Compiler)
Compiler->TargetStackIndex -= 1;
}
static scene_bytecode_chunk *S_FindBytecodeChunkByName(scene_compiler *Compiler, string Name)
{
scene_bytecode_chunk *Result = 0;
u64 Hash = HashString(Name);
scene_bytecode_bucket *Bucket = &Compiler->ProcBuckets[Hash%ArrayCount(Compiler->ProcBuckets)];
for(scene_bytecode_chunk *Chunk = Bucket->First; Chunk != 0; Chunk = Chunk->Next)
{
if(AreEqual(Chunk->Name, Name))
{
Result = Chunk;
break;
}
}
return(Result);
}
static void S_AdvanceCompiler(scene_compiler *Compiler)
{
Compiler->At += 1;
@ -143,17 +127,6 @@ static scene_parse_rule S_ParseRuleFromToken(scene_compiler *Compiler, token Tok
return(Result);
}
static b32 S_MatchToken(scene_compiler *Compiler, token Token, token_kind Kind)
{
b32 Result = false;
string String = T_StringFromToken(Compiler->Text, Token);
if(Token.Kind == Kind)
{
Result = true;
}
return(Result);
}
static token S_ConsumeToken(scene_compiler *Compiler, token_kind Kind, char *Message)
{
token Token = Compiler->At[0];
@ -1002,7 +975,7 @@ static void S_ResetRuntime(scene_runtime *Runtime)
}
}
static scene_runtime_result S_Run(scene_runtime *Runtime, arena *FrameArena, b32 AdvanceOnAwait)
static scene_runtime_result S_Run(scene_runtime *Runtime, arena *Arena, b32 AdvanceOnAwait)
{
scene_runtime_result Result = {};
compiled_scene *Compiled = &Runtime->Compiled;
@ -1194,7 +1167,7 @@ static scene_runtime_result S_Run(scene_runtime *Runtime, arena *FrameArena, b32
case S_Op_ClearDialog:
{
scene_textbox_action *Action = PushStructNoClear(FrameArena, scene_textbox_action);
scene_textbox_action *Action = PushStructNoClear(Arena, scene_textbox_action);
Action->Kind = S_TextboxActionKind_Set;
Action->String = StrLit("");
QueuePush(Runtime->FirstTextboxAction, Runtime->LastTextboxAction, Action);
@ -1220,7 +1193,7 @@ static scene_runtime_result S_Run(scene_runtime *Runtime, arena *FrameArena, b32
}
// sixten: create & apply character action
scene_character_action *Action = PushStruct(FrameArena, scene_character_action);
scene_character_action *Action = PushStruct(Arena, scene_character_action);
Action->Target = Character;
if(State.Kind == S_ValueKind_Nil)
@ -1236,7 +1209,7 @@ static scene_runtime_result S_Run(scene_runtime *Runtime, arena *FrameArena, b32
case S_Op_LineEntry:
{
scene_textbox_action *Action = PushStructNoClear(FrameArena, scene_textbox_action);
scene_textbox_action *Action = PushStructNoClear(Arena, scene_textbox_action);
Action->Kind = S_TextboxActionKind_Append;
Runtime->IP += 1;

View File

@ -393,7 +393,6 @@ static void S_EmitConstant(scene_compiler *Compiler, scene_value Value);
static void S_SetEmissionTarget(scene_compiler *Compiler, scene_emission_target Target);
static void S_PushEmissionTarget(scene_compiler *Compiler, scene_emission_target Target);
static void S_PopEmissionTarget(scene_compiler *Compiler);
static scene_bytecode_chunk *S_FindBytecodeChunkByName(scene_compiler *Compiler, string Name);
inline scene_emission_target S_RawEmissionTarget(arena *Arena, scene_bytecode_bucket *Bucket)
{
@ -417,7 +416,6 @@ inline scene_emission_target S_NamedEmissionTarget(arena *Arena, scene_bytecode_
//- sixten: parsing helpers
static void S_AdvanceCompiler(scene_compiler *Compiler);
static scene_parse_rule S_ParseRuleFromToken(scene_compiler *Compiler, token Token);
static b32 S_MatchToken(scene_compiler *Compiler, token Token, token_kind Kind);
static token S_ConsumeToken(scene_compiler *Compiler, token_kind Kind, char *Message);
//- sixten: parsing
@ -455,6 +453,6 @@ static compiled_scene S_CopyCompiledScene(arena *Arena, compiled_scene *Compiled
static scene_proc *S_FindProcByName(compiled_scene *Compiled, string Name);
static scene_named_value *S_FindGlobalVariableByName(scene_runtime *Runtime, string Name, b32 AlwaysCreate = true);
static void S_ResetRuntime(scene_runtime *Runtime);
static scene_runtime_result S_Run(scene_runtime *Runtime, arena *FrameArena, b32 AdvanceOnAwait);
static scene_runtime_result S_Run(scene_runtime *Runtime, arena *Arena, b32 AdvanceOnAwait);
#endif //VN_SCENE_H

View File

@ -482,26 +482,26 @@ static text_op TextOpFromAction(arena *Arena, string String,
Op.NewCursor = Op.NewMark = Op.Range.Min;
}
//- sixten: handle insertions
{
b32 InsertedSomething = false;
if(Action->Codepoint != 0)
{
Op.ReplaceString = StringFromCodepoint(Arena, Action->Codepoint);
if(State->Cursor == State->Mark)
{
Op.NewCursor += Op.ReplaceString.Count;
Op.Range = Range1S64(State->Cursor, State->Cursor);
}
else
{
Op.NewCursor += Op.ReplaceString.Count;
Op.Range = Range1S64(State->Cursor, State->Mark);
}
InsertedSomething = true;
}
else if(Action->Flags & TextActionFlag_Paste)
{
Op.ReplaceString = Platform.GetClipboard(Arena);
Op.Range = Range1S64(State->Cursor, State->Cursor);
Op.NewCursor += Op.ReplaceString.Count;
Op.ReplaceString = RemoveAll(Arena, Platform.GetClipboard(Arena), '\r');;
InsertedSomething = true;
}
if(InsertedSomething)
{
range1_s64 Selection = Range1S64(State->Cursor, State->Mark);
Op.Range = Selection;
Op.NewCursor = Op.NewMark = Selection.Min+Op.ReplaceString.Count;
}
}
if(!(Action->Flags & TextActionFlag_KeepMark))

View File

@ -200,6 +200,8 @@ static ui_signal UI_Scrollbar(axis2 Axis, string Name, r32 Size, r32 Offset)
UI_SetNextFont(Font_Icons);
UI_MakeBoxF(UI_BoxFlag_DrawText, "%U", Axis?FontIcon_UpDir:FontIcon_LeftDir);
Offset = Clamp(Offset, 0, DimOfRange(UI_TopParent()->Rect).E[Axis]-Size-UI_TopFontSize()*2);
UI_Spacer(UI_Pixels(Offset, 1));
UI_SetNextCornerRadius(4.0f);

View File

@ -300,40 +300,6 @@ static void W_DeletePanel(workspace_panel *Panel)
DLLInsertLast(Workspace->FirstFreePanel, Workspace->LastFreePanel, Panel);
}
static void W_PanelViewPush(workspace_panel_view_list *List, workspace_panel_view *PanelView)
{
DLLInsertLast(List->First, List->Last, PanelView);
List->Count += 1;
}
static void W_PanelViewRemove(workspace_panel_view_list *List, workspace_panel_view *PanelView)
{
DLLRemove(List->First, List->Last, PanelView);
List->Count -= 1;
}
static workspace_panel_view *W_CreatePanelView(void)
{
workspace *Workspace = W_GetState();
workspace_panel_view *PanelView = Workspace->PanelViewFreeList.First;
if(PanelView)
{
W_PanelViewRemove(&Workspace->PanelViewFreeList, PanelView);
}
else
{
PanelView = PushStruct(Workspace->PanelViewArena, workspace_panel_view);
}
return(PanelView);
}
static void W_DeletePanelView(workspace_panel_view *PanelView)
{
workspace *Workspace = W_GetState();
ZeroStruct(PanelView);
W_PanelViewPush(&Workspace->PanelViewFreeList, PanelView);
}
static void W_SplitPanel(workspace_panel *Panel, axis2 Axis)
{
workspace *Workspace = W_GetState();

View File

@ -103,9 +103,6 @@ struct workspace
workspace_drag_payload_state DragPayloadState;
workspace_drag_payload DragPayload;
arena *PanelViewArena;
workspace_panel_view_list PanelViewFreeList;
workspace_toolbar_menu Menu;
v2 MenuP;
r32 MenuT;
@ -138,12 +135,6 @@ static void W_SplitPanel(workspace_panel *Panel, axis2 Axis);
//- sixten: Views
static b32 W_ViewIsDragged(workspace_view *View);
//- sixten: Panel Views
static void W_PanelViewPush(workspace_panel_view_list *List, workspace_panel_view *PanelView);
static void W_PanelViewRemove(workspace_panel_view_list *List, workspace_panel_view *PanelView);
static workspace_panel_view *W_CreatePanelView(void);
static void W_DeletePanelView(workspace_panel_view *PanelView);
//- sixten: Builder code
static ui_signal W_BuildToolbarButton(char *Text, workspace_toolbar_menu Menu);
static ui_signal W_BuildMenuItem(u32 Icon, char *Text, char *Shortcut);

View File

@ -75,6 +75,7 @@ static workspace_text_data W_TextDataFromString(arena *Arena, string Text)
//- sixten: gather all line ranges
range1_s64_list Lines = {};
s64 MaxLineCount = 0;
{
u8 *TextBegin = Text.Data;
u8 *TextEnd = TextBegin + Text.Count;
@ -86,6 +87,10 @@ static workspace_text_data W_TextDataFromString(arena *Arena, string Text)
//- sixten: push line range on newline and EOF
if(Char == TextEnd || *Char == '\n')
{
if(DimOfRange(Range) > MaxLineCount)
{
MaxLineCount = DimOfRange(Range);
}
Range1S64ListPush(Scratch.Arena, &Lines, Range);
Range = Range1S64(Range.Max, Range.Max);
}
@ -97,6 +102,7 @@ static workspace_text_data W_TextDataFromString(arena *Arena, string Text)
{
Result.Tokens = Tokens;
Result.Lines = Range1S64ArrayFromList(Arena, &Lines);;
Result.MaxLineCount = MaxLineCount;
}
ReleaseScratch(Scratch);
return(Result);
@ -107,6 +113,7 @@ static void W_TextEditorApplyChanges(workspace_view_text_editor *Editor)
workspace_text_data TextData = W_TextDataFromString(Editor->ProcessingArena, Editor->Text.String);
Editor->Tokens = TextData.Tokens;
Editor->Lines = TextData.Lines;
Editor->MaxLineCount = TextData.MaxLineCount;
Editor->Compiled = S_ScriptFromText(Editor->ProcessingArena, Editor->Text.String);
if(Editor->Compiled.IsValid)
{
@ -136,247 +143,6 @@ static void W_SaveTextEditorToFile(workspace_view_text_editor *Editor)
////////////////////////////////
//~ sixten: Workspace Text Editor Builder Functions
static UI_CUSTOM_DRAW_CALLBACK(W_TextEditorDrawCallback)
{
temp Scratch = GetScratch();
workspace_view_text_editor *Editor = (workspace_view_text_editor *)Data;
//- sixten: get dimensions & scroll offset from container
ui_box *ContainerBox = Editor->ContainerBox;
range2_r32 ParentRect = ContainerBox->Rect;
v2 ParentDim = DimOfRange(ParentRect);
v2 Offset = Box->Parent->Offset;
//- sixten: rendering properties
r32 FontSize = Editor->FontSize;
r32 LineHeight = FontSize + 4.0f;
//- sixten: calculate the dimensions of the glyphs
glyph *Glyph = GetGlyph(Atlas, Font_Monospace, 'A', FontSize, 0);
r32 GlyphAdvance = Glyph->Advance;
//- sixten: find the text point
text_point CursorTextP = TextPointFromOffset(Editor->Text.String, Editor->EditState.Cursor);
text_point MarkTextP = TextPointFromOffset(Editor->Text.String, Editor->EditState.Mark);
//- sixten: get the line count
range1_s64_array *Lines = &Editor->Lines;
s64 LineCount = Lines->Count;
//- sixten: calculate the text dim
Editor->TextDim = V2(1900, LineCount*LineHeight);
//- sixten: calculate the line margin dim
s32 LineMarginDigitsRequired = 6;
v2_r32 LineMarginDim = V2((LineMarginDigitsRequired)*GlyphAdvance, ParentRect.Max.y - ParentRect.Min.y);
//- sixten: tokenize text
tokenize_result TokenizeResult = T_TokenizeFromText(Scratch.Arena, Editor->Text.String);
token_array Tokens = TokenizeResult.Tokens;
token *TokensBegin = Tokens.Tokens;
token *TokensEnd = TokensBegin + Tokens.Count;
//- sixten: find the first visible token
token *VisibleTokensBegin = TokensBegin;
s64 TopMostLine = Min((s64)Floor(-Offset.y / LineHeight), LineCount);
for(s64 LinesFound = 0; LinesFound < TopMostLine && VisibleTokensBegin < TokensEnd; VisibleTokensBegin += 1)
{
if(VisibleTokensBegin->Kind == TokenKind_Newline)
{
LinesFound += 1;
}
}
//- sixten: find the last visible token
token *VisibleTokensEnd = VisibleTokensBegin;
s64 LinesOnScreen = Min((s64)Floor(ParentDim.y / LineHeight)+1, LineCount-TopMostLine);
for(s64 LinesFound = 0; LinesFound < LinesOnScreen && VisibleTokensEnd < TokensEnd; VisibleTokensEnd += 1)
{
if(VisibleTokensEnd->Kind == TokenKind_Newline)
{
LinesFound += 1;
}
}
//- sixten: draw line numbers & line highlights
{
//- sixten: draw the background
v4 LineMarginColor = ColorFromHex(0x10203080);
range2_r32 LineMarginBox = Range2R32(ParentRect.Min, ParentRect.Min+LineMarginDim);
PushQuad(Group, LineMarginBox, LineMarginColor, LineMarginColor, LineMarginColor, LineMarginColor, 0, 0, 0);
//- sixten: draw the numbers
v2_r32 LineOffset = Box->Rect.Min;
for(s64 LineIndex = TopMostLine; LineIndex < TopMostLine + LinesOnScreen; LineIndex += 1)
{
r32 LineY = LineOffset.y + LineIndex*LineHeight;
PushTextF(Group, Atlas, Font_Monospace, V2(0, LineY), FontSize, Color_Grey, "%*.i", LineMarginDigitsRequired, LineIndex+1);
if(LineIndex + 1 == CursorTextP.Line)
{
v4_r32 LineHighlightColor = ColorFromHex(0x10204080);
range2_r32 LineHighlightBox = Range2R32(V2(LineMarginBox.Max.x, LineY), V2(Box->Rect.Max.x, LineY+LineHeight));
PushQuad(Group, LineHighlightBox, LineHighlightColor, LineHighlightColor, LineHighlightColor, LineHighlightColor, 0, 0, 0);
}
}
}
//- sixten: render tokens
v2 BaseTokenP = Box->Rect.Min+V2(LineMarginDim.x, TopMostLine*LineHeight);
v2 TokenP = BaseTokenP;
for(token *Token = VisibleTokensBegin; Token < VisibleTokensEnd; Token += 1)
{
string TokenString = T_StringFromToken(Editor->Text.String, *Token);
//- sixten: get color & font from token
font_id Font = Font_Monospace;
v4 Color = Color_Magenta;
if(Token->Kind == TokenKind_Comment) { Color = Color_Grey; Font = Font_MonospaceOblique; }
else if(Token->Kind > TokenKind_SymbolsBegin && Token->Kind < TokenKind_SymbolsEnd) { Color = Color_Grey; }
else if(Token->Kind == TokenKind_StringLiteral) { Color = ColorFromHex(0xffa900ff); }
else if(Token->Kind == TokenKind_Numeric) { Color = ColorFromHex(0xffa900ff); }
else if(Token->Kind > TokenKind_KeywordsBegin && Token->Kind < TokenKind_KeywordsEnd)
{
if(Token->Kind == TokenKind_True || Token->Kind == TokenKind_False)
{
Color = ColorFromHex(0xffa900ff);
}
else
{
Color = ColorFromHex(0xf0c674ff);
}
}
else if(Token->Kind == TokenKind_Identifier)
{
Color = Theme_TextColor;
}
//- sixten: check for errors
b32 ConsideredError = false;
for(scene_compile_error *Error = Editor->Compiled.Errors.First; Error != 0; Error = Error->Next)
{
if(Error->Token.Range.Min == Token->Range.Min &&
Error->Token.Range.Max == Token->Range.Max)
{
ConsideredError = true;
break;
}
}
//- sixten: render & advance by token
if(!(T_IsWhitespace(Token->Kind)))
{
if(Token->Kind == TokenKind_Comment)
{
//- sixten: advance to newline and push text
// sixten(TODO): proper multiline comment rendering.
u8 *TextBegin = TokenString.Data;
u8 *TextEnd = TextBegin+TokenString.Count;
u8 *Marker = TextBegin;
for(u8 *Byte = TextBegin; Byte <= TextEnd; Byte += 1)
{
if(*Byte == '\n' || Byte == TextEnd)
{
PushText(Group, Atlas, Font, TokenP, FontSize, Color, MakeString(Marker, Byte-Marker));
Marker = Byte+1;
if(*Byte == '\n' && Byte != TextEnd)
{
TokenP.x = BaseTokenP.x;
TokenP.y += LineHeight;
}
}
}
}
else
{
r32 TokenWidth = PushText(Group, Atlas, Font, TokenP, FontSize, Color, TokenString);
//- sixten: render error highlight
if(ConsideredError)
{
range2_r32 Dest = Range2R32(TokenP+V2R32(0, LineHeight-3), TokenP+V2R32(TokenWidth, LineHeight));
v4_r32 ErrorColor = V4R32(0.9f, 0.3f, 0.3f, 0.8f);
PushQuad(Group, Dest, ErrorColor, ErrorColor, ErrorColor, ErrorColor, 3, 0.4, 0);
}
TokenP.x += TokenWidth;
}
}
else
{
if(Token->Kind == TokenKind_Newline)
{
TokenP.x = BaseTokenP.x;
TokenP.y += LineHeight;
}
else
{
u8 *StringBegin = TokenString.Data;
u8 *StringEnd = StringBegin + TokenString.Count;
for(u8 *Char = StringBegin; Char < StringEnd; Char += 1)
{
if(*Char == ' ' || *Char == '\t')
{
TokenP.x += GlyphAdvance;
}
}
}
}
}
//- 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(AC_AnimateValueF(TargetCursorP.x, TargetCursorP.x, 0.1, "Workspace Text Editor Cursor X %p", Editor),
AC_AnimateValueF(TargetCursorP.y, TargetCursorP.y, 0.1, "Workspace Text Editor Cursor Y %p", Editor));
v2 CursorDim = V2(2, LineHeight);
range2_r32 CursorRect = Range2R32(CursorP, CursorP+CursorDim);
v4 CursorColor = ColorFromHex(0x10FF20FF);
PushQuad(Group, CursorRect, CursorColor, CursorColor, CursorColor, CursorColor, 2, 0.4, 0);
}
//- sixten: render the selection
{
text_range Selection = TextRange(CursorTextP, MarkTextP);
range1_s64 LineRange = Range1S64(Selection.Min.Line, Selection.Max.Line);
for(s64 LineIndex = TopMostLine; LineIndex < TopMostLine + LinesOnScreen; LineIndex += 1)
{
if(Contains(LineRange, LineIndex + 1))
{
range1_s64 ColumnRange = Lines->Ranges[LineIndex];
range1_s64 NormalizedColumnRange = Range1S64(0, DimOfRange(ColumnRange));
if(LineIndex+1 == LineRange.Min && LineIndex+1 == LineRange.Max)
{
NormalizedColumnRange = Range1S64(Editor->EditState.Cursor - ColumnRange.Min, Editor->EditState.Mark - ColumnRange.Min);
}
else if(LineIndex+1 == LineRange.Min)
{
NormalizedColumnRange = Range1S64(Min(Editor->EditState.Mark, Editor->EditState.Cursor) - ColumnRange.Min, DimOfRange(ColumnRange));
}
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));
r32 LineY = LineIndex*LineHeight;
v4_r32 LineHighlightColor = ColorFromHex(0x66B3CC4C);
range2_r32 LineHighlightBox = Range2R32(Box->Rect.Min+V2(LineMarginDim.x+ColumnOffsetRange.Min*GlyphAdvance, LineY),
Box->Rect.Min+V2(LineMarginDim.x+ColumnOffsetRange.Max*GlyphAdvance, LineY+LineHeight));
PushQuad(Group, LineHighlightBox, LineHighlightColor, LineHighlightColor, LineHighlightColor, LineHighlightColor, 4, 1.4, 0);
}
}
}
ReleaseScratch(Scratch);
}
static b32 W_ProcessTextEditorEvent(workspace_view_text_editor *Editor, platform_event *Event)
{
b32 CursorHasBeenModified = false;
@ -435,40 +201,8 @@ static void W_BuildTextEditorInfoBar(workspace_view_text_editor *Editor)
}
}
static void W_BuildTextEditor(workspace_view *View)
static void W_BuildTextEditorErrorList(workspace_view_text_editor *Editor)
{
workspace_view_text_editor *Editor = (workspace_view_text_editor *)View->Data;
temp Scratch = GetScratch();
//- sixten: rendering properties
r32 FontSize = Editor->FontSize = 13.0f;
r32 LineHeight = FontSize + 4.0f;
//- sixten: calculate the dimensions of the glyphs
glyph *Glyph = GetGlyph(UI_GlyphAtlas(), Font_Monospace, 'A', FontSize, 0);
r32 GlyphAdvance = Glyph->Advance;
//- sixten: calculate the line margin dim
s32 LineMarginDigitsRequired = 6;
r32 LineMarginWidth = (LineMarginDigitsRequired)*GlyphAdvance;
{
//- sixten: build & handle the text editor
ui_box *EditorBox = 0;
UI_SetNextSize(UI_Percent(1, 1), UI_Percent(1, 0));
UI_Scroll(0, &Editor->Offset.y)
{
//- sixten: find the container box for the scrollable region
Editor->ContainerBox = UI_TopParent()->Parent->Parent;
UI_SetNextSize(UI_Pixels(Editor->TextDim.x, 1), UI_Pixels(Editor->TextDim.y, 1));
EditorBox = UI_MakeBoxF(UI_BoxFlag_DrawBackground|UI_BoxFlag_Clickable, "Workspace Text Editor %p", View);
UI_EquipBoxCustomDrawCallback(EditorBox, W_TextEditorDrawCallback, Editor);
}
//- sixten: build footer
W_BuildTextEditorInfoBar(Editor);
r32 TargetFooterHeightEm = 2.25f*Min(Editor->Compiled.Errors.Count, 10LL);
UI_Size(UI_Percent(1, 0), UI_Em(AC_AnimateValueF(TargetFooterHeightEm, TargetFooterHeightEm, 0.3, "Error Lister %p", Editor), 1)) UI_Column() UI_Height(UI_TextContent(0, 1))
{
@ -506,9 +240,388 @@ static void W_BuildTextEditor(workspace_view *View)
UI_Spacer(UI_Em(0.5, 1));
}
}
}
UI_CUSTOM_DRAW_CALLBACK(W_TextEditorLinesBarDrawCallback)
{
workspace_view_text_editor *Editor = (workspace_view_text_editor *)Data;
//- sixten: calculate dimensions
ui_box *Parent = Box->Parent;
v2_r32 ParentDim = DimOfRange(Parent->Rect);
r32 FontSize = Editor->FontSize;
r32 LineHeight = FontSize + 4.0f;
s64 LineStart = Floor(-Parent->Offset.y/LineHeight);
s64 LineEnd = Min(Editor->Lines.Count, (s64)Floor((ParentDim.y-Parent->Offset.y)/LineHeight)+1);
s64 LineNumberCount = (s64)(Log(Editor->Lines.Count)/Log(10))+1;
for(s64 Line = LineStart; Line < LineEnd; Line += 1)
{
PushTextF(Group, Atlas, Font_Monospace, Box->Rect.Min+V2R32(0, Line*LineHeight), Editor->FontSize, Color_Grey, " %*i", LineNumberCount, Line+1);
}
}
UI_CUSTOM_DRAW_CALLBACK(W_TextEditorDrawCallback)
{
workspace_view_text_editor *Editor = (workspace_view_text_editor *)Data;
temp Scratch = GetScratch();
//- sixten: calculate dimensions
ui_box *Parent = Box->Parent;
v2_r32 ParentDim = DimOfRange(Parent->Rect);
//- sixten: rendering properties
r32 FontSize = Editor->FontSize;
r32 LineHeight = FontSize + 4.0f;
//- sixten: calculate the dimensions of the glyphs
glyph *Glyph = GetGlyph(Atlas, Font_Monospace, 'A', FontSize, 0);
r32 GlyphAdvance = Glyph->Advance;
//- sixten: find the text point
text_point CursorTextP = TextPointFromOffset(Editor->Text.String, Editor->EditState.Cursor);
text_point MarkTextP = TextPointFromOffset(Editor->Text.String, Editor->EditState.Mark);
s64 LineStart = Floor(-Parent->Offset.y/LineHeight);
s64 LineEnd = Min(Editor->Lines.Count, (s64)Floor((ParentDim.y-Parent->Offset.y)/LineHeight)+1);
//- sixten: tokenize text
tokenize_result TokenizeResult = T_TokenizeFromText(Scratch.Arena, Editor->Text.String);
token_array Tokens = TokenizeResult.Tokens;
token *TokensBegin = Tokens.Tokens;
token *TokensEnd = TokensBegin + Tokens.Count;
v2_r32 StartTokenP = Box->Rect.Min;
//- sixten: find the first visible token
token *VisibleTokensBegin = TokensBegin;
for(s64 LinesFound = 0; LinesFound < LineStart && VisibleTokensBegin < TokensEnd; VisibleTokensBegin += 1)
{
if(VisibleTokensBegin->Kind == TokenKind_Newline)
{
LinesFound += 1;
StartTokenP.y += LineHeight;
}
}
//- sixten: find the last visible token
token *VisibleTokensEnd = VisibleTokensBegin;
for(s64 LinesFound = 0; LinesFound < LineEnd-LineStart && VisibleTokensEnd < TokensEnd; VisibleTokensEnd += 1)
{
if(VisibleTokensEnd->Kind == TokenKind_Newline)
{
LinesFound += 1;
}
}
//- sixten: render tokens
v2_r32 TokenP = StartTokenP;
for(token *Token = VisibleTokensBegin; Token < VisibleTokensEnd; Token += 1)
{
string TokenString = T_StringFromToken(Editor->Text.String, *Token);
//- sixten: get color & font from token
font_id Font = Font_Monospace;
v4 Color = Color_Magenta;
if(Token->Kind == TokenKind_Comment) { Color = Color_Grey; Font = Font_MonospaceOblique; }
else if(Token->Kind > TokenKind_SymbolsBegin && Token->Kind < TokenKind_SymbolsEnd) { Color = Color_Grey; }
else if(Token->Kind == TokenKind_StringLiteral) { Color = ColorFromHex(0xffa900ff); }
else if(Token->Kind == TokenKind_Numeric) { Color = ColorFromHex(0xffa900ff); }
else if(Token->Kind > TokenKind_KeywordsBegin && Token->Kind < TokenKind_KeywordsEnd)
{
if(Token->Kind == TokenKind_True || Token->Kind == TokenKind_False)
{
Color = ColorFromHex(0xffa900ff);
}
else
{
Color = ColorFromHex(0xf0c674ff);
}
}
else if(Token->Kind == TokenKind_Identifier)
{
Color = Theme_TextColor;
}
//- sixten: check for errors
b32 ConsideredError = false;
for(scene_compile_error *Error = Editor->Compiled.Errors.First; Error != 0; Error = Error->Next)
{
if(Error->Token.Range.Min == Token->Range.Min &&
Error->Token.Range.Max == Token->Range.Max)
{
ConsideredError = true;
break;
}
}
//- sixten: render & advance by token
if(!(T_IsWhitespace(Token->Kind)))
{
if(Token->Kind == TokenKind_Comment)
{
//- sixten: advance to newline and push text
// sixten(TODO): proper multiline comment rendering.
u8 *TextBegin = TokenString.Data;
u8 *TextEnd = TextBegin+TokenString.Count;
u8 *Marker = TextBegin;
for(u8 *Byte = TextBegin; Byte <= TextEnd; Byte += 1)
{
if(*Byte == '\n' || Byte == TextEnd)
{
PushText(Group, Atlas, Font, TokenP, FontSize, Color, MakeString(Marker, Byte-Marker));
Marker = Byte+1;
if(*Byte == '\n' && Byte != TextEnd)
{
TokenP.x = StartTokenP.x;
TokenP.y += LineHeight;
}
}
}
}
else
{
r32 TokenWidth = PushText(Group, Atlas, Font, TokenP, FontSize, Color, TokenString);
//- sixten: render error highlight
if(ConsideredError)
{
range2_r32 Dest = Range2R32(TokenP+V2R32(0, LineHeight-3), TokenP+V2R32(TokenWidth, LineHeight));
v4_r32 ErrorColor = V4R32(0.9f, 0.3f, 0.3f, 0.8f);
PushQuad(Group, Dest, ErrorColor, ErrorColor, ErrorColor, ErrorColor, 3, 0.4, 0);
}
TokenP.x += TokenWidth;
}
}
else
{
if(Token->Kind == TokenKind_Newline)
{
TokenP.x = StartTokenP.x;
TokenP.y += LineHeight;
}
else
{
u8 *StringBegin = TokenString.Data;
u8 *StringEnd = StringBegin + TokenString.Count;
for(u8 *Char = StringBegin; Char < StringEnd; Char += 1)
{
if(*Char == ' ' || *Char == '\t')
{
TokenP.x += GlyphAdvance;
}
}
}
}
}
//- sixten: render the selection
{
text_range Selection = TextRange(CursorTextP, MarkTextP);
range1_s64 LineRange = Range1S64(Selection.Min.Line, Selection.Max.Line);
for(s64 LineIndex = LineStart; LineIndex < LineEnd; LineIndex += 1)
{
if(Contains(LineRange, LineIndex + 1))
{
range1_s64 ColumnRange = Editor->Lines.Ranges[LineIndex];
range1_s64 NormalizedColumnRange = Range1S64(0, DimOfRange(ColumnRange));
if(LineIndex+1 == LineRange.Min && LineIndex+1 == LineRange.Max)
{
NormalizedColumnRange = Range1S64(Editor->EditState.Cursor - ColumnRange.Min, Editor->EditState.Mark - ColumnRange.Min);
}
else if(LineIndex+1 == LineRange.Min)
{
NormalizedColumnRange = Range1S64(Min(Editor->EditState.Mark, Editor->EditState.Cursor) - ColumnRange.Min, DimOfRange(ColumnRange));
}
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));
r32 LineY = LineIndex*LineHeight;
v4_r32 LineHighlightColor = ColorFromHex(0x66B3CC4C);
range2_r32 LineHighlightBox = Range2R32(Box->Rect.Min+V2(ColumnOffsetRange.Min*GlyphAdvance, LineY),
Box->Rect.Min+V2(ColumnOffsetRange.Max*GlyphAdvance, LineY+LineHeight));
PushQuad(Group, LineHighlightBox, LineHighlightColor, LineHighlightColor, LineHighlightColor, LineHighlightColor, 4, 1.4, 0);
}
}
}
//- 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(ColumnOffset*GlyphAdvance, LineIndex*LineHeight);
v2 CursorP = V2(AC_AnimateValueF(TargetCursorP.x, TargetCursorP.x, 0.1, "Workspace Text Editor Cursor X %p", Editor),
AC_AnimateValueF(TargetCursorP.y, TargetCursorP.y, 0.1, "Workspace Text Editor Cursor Y %p", Editor));
v2 CursorDim = V2(2, LineHeight);
range2_r32 CursorRect = Range2R32(CursorP, CursorP+CursorDim);
v4 CursorColor = ColorFromHex(0x10FF20FF);
PushQuad(Group, CursorRect, CursorColor, CursorColor, CursorColor, CursorColor, 2, 0.4, 0);
}
}
static void W_BuildTextEditor(workspace_view *View)
{
workspace_view_text_editor *Editor = (workspace_view_text_editor *)View->Data;
//- sixten: calculate dimensions
r32 FontSize = Editor->FontSize;
r32 LineHeight = FontSize + 4.0f;
glyph *Glyph = GetGlyph(UI_GlyphAtlas(), Font_Monospace, 'A', FontSize, 0);
r32 GlyphAdvance = Glyph->Advance;
s64 LineCount = Editor->Lines.Count;
s64 MaxLineCount = Editor->MaxLineCount;
s64 LineNumberCount = (s64)(Log(Editor->Lines.Count)/Log(10))+1;
v2_r32 LinesBarDim = V2R32((LineNumberCount + 2)*GlyphAdvance, LineCount*LineHeight);
v2_r32 TextEditorDim = V2R32((MaxLineCount+LineNumberCount+2)*GlyphAdvance, (LineCount+1)*LineHeight);
v2_r32 VisibleRegionDim; // sixten(NOTE): This is set further down
b32 CursorHasBeenModified = false;
//- sixten: layout editor
UI_WidthFill UI_HeightFill UI_Column()
{
//- sixten: line numbers, editor & scrollbar
UI_HeightFill UI_Row(UI_BoxFlag_DrawBorder)
{
UI_WidthFill UI_Column(0, StrLit("Scrollable & Scroll X"))
{
UI_Row(0, StrLit("Scroll Region")) // contains lines bar & text editor
{
v2_r32 ScrollRegionDim = DimOfRange(UI_TopParent()->Rect);
v2_r32 AnimatedOffset = V2R32(AC_AnimateValueF(Editor->Offset.x, Editor->Offset.x, 0.3f, "%p Offset X", View),
AC_AnimateValueF(Editor->Offset.y, Editor->Offset.y, 0.3f, "%p Offset Y", View));
// sixten(NOTE): We put the lines bar & text editor in containers to be able to easily offset them.
UI_SetNextBackgroundColor(ColorFromHex(0x10203080));
UI_SetNextOffsetY(-AnimatedOffset.y);
UI_Width(UI_ChildrenSum(1, 1)) UI_Parent(UI_MakeBox(UI_BoxFlag_Clip|UI_BoxFlag_DrawBackground|UI_BoxFlag_DrawDropShadow, StrLit("Lines Bar Container")))
{
UI_SetNextSize(UI_Pixels(LinesBarDim.x, 1), UI_Pixels(LinesBarDim.y, 1));
ui_box *LinesBarBox = UI_MakeBoxF(0, "Workspace View Text Editor Lines Bar");
UI_EquipBoxCustomDrawCallback(LinesBarBox, W_TextEditorLinesBarDrawCallback, Editor);
}
UI_SetNextOffset(-AnimatedOffset.x, -AnimatedOffset.y);
UI_WidthFill UI_Parent(UI_MakeBox(UI_BoxFlag_Clip|UI_BoxFlag_Clickable, StrLit("Text Editor Container")))
{
ui_box *EditorContainerBox = UI_TopParent();
VisibleRegionDim = DimOfRange(EditorContainerBox->Rect);
ui_signal EditorContainerSignal = UI_SignalFromBox(EditorContainerBox);
//- sixten: text editor scrolling
if(AreEqual(UI_HotKey(), EditorContainerBox->Key))
{
for(platform_event *Event = UI_EventList()->First;
Event != 0;
Event = Event->Next)
{
if(Event->Type == PlatformEvent_MouseScroll && (Event->Modifiers != PlatformModifier_Ctrl) && Event->Scroll.y != 0)
{
Editor->Offset.y -= Event->Scroll.y*LineHeight*4;
Platform_ConsumeEvent(UI_EventList(), Event);
}
}
}
UI_SetNextSize(UI_Pixels(TextEditorDim.x, 1), UI_Pixels(TextEditorDim.y, 1));
ui_box *TextEditorBox = UI_MakeBoxF(0, "Workspace View Text Editor");
UI_EquipBoxCustomDrawCallback(TextEditorBox, W_TextEditorDrawCallback, Editor);
if(EditorContainerSignal.Dragging)
{
//- sixten: translate mouse position to text point
v2 MouseOffset = EditorContainerSignal.MouseP - TextEditorBox->Rect.Min;
s64 LineIndex = Clamp((s64)(MouseOffset.y / LineHeight), 0, Editor->Lines.Count);
string Line = Substring(Editor->Text.String, Editor->Lines.Ranges[LineIndex]);
s64 ColumnOffset = (s64)(MouseOffset.x / GlyphAdvance);
s64 ColumnIndex = UTF8IndexFromOffset(Line, ColumnOffset);
CursorHasBeenModified = true;
Editor->DropdownActive = false;
text_point Point = {LineIndex + 1, ColumnIndex + 1};
Editor->EditState.Cursor = OffsetFromTextPoint(Editor->Text.String, Editor->Lines, Point);
if(EditorContainerSignal.Pressed)
{
Editor->EditState.Mark = Editor->EditState.Cursor;
}
}
}
}
//- sixten: build scrollbar x
{
ui_box *ParentBox = UI_TopParent();
v2_r32 ParentDim = DimOfRange(ParentBox->Rect);
r32 MaxScrollWidth = ParentDim.x-UI_TopFontSize()*2;
r32 ScrollScaleX = ParentDim.x/TextEditorDim.x;
if(ScrollScaleX > 1)
{
ScrollScaleX = 0;
}
ui_signal ScrollX = UI_Scrollbar(Axis2_X, StrLit("Workspace View Text Editor Scrollbar X"), MaxScrollWidth*ScrollScaleX, Editor->Offset.x/TextEditorDim.x*MaxScrollWidth);
if(ScrollX.Dragging)
{
if(ScrollX.Pressed)
{
UI_StoreDragR32(Editor->Offset.x);
}
Editor->Offset.x = UI_GetDragR32() + ScrollX.DragDelta.x/ScrollScaleX;
}
Editor->Offset.x = Clamp(Editor->Offset.x, 0, Max(0.0f, TextEditorDim.x-ParentDim.x));
}
}
UI_Width(UI_ChildrenSum(1, 1)) UI_Column(0, StrLit("Scrollable & Scroll Y"))
{
//- sixten: build scrollbar y
{
ui_box *ParentBox = UI_TopParent();
v2_r32 ParentDim = DimOfRange(ParentBox->Rect);
r32 MaxScrollHeight = ParentDim.y-UI_TopFontSize()*3;
r32 ScrollScaleY = ParentDim.y/TextEditorDim.y;
if(ScrollScaleY > 1)
{
MaxScrollHeight = 0;
}
ui_signal ScrollY = UI_Scrollbar(Axis2_Y, StrLit("Workspace View Text Editor Scrollbar Y"), MaxScrollHeight*ScrollScaleY, Editor->Offset.y/TextEditorDim.y*MaxScrollHeight);
if(ScrollY.Dragging)
{
if(ScrollY.Pressed)
{
UI_StoreDragR32(Editor->Offset.y);
}
Editor->Offset.y = UI_GetDragR32() + ScrollY.DragDelta.y/ScrollScaleY;
}
Editor->Offset.y = Clamp(Editor->Offset.y, 0, Max(0.0f, TextEditorDim.y-ParentDim.y));
}
UI_Width(UI_Em(1, 1)) UI_Height(UI_Em(1, 1)) UI_MakeBox(UI_BoxFlag_DrawBorder, StrLit(""));
}
}
//- sixten: info bar
W_BuildTextEditorInfoBar(Editor);
W_BuildTextEditorErrorList(Editor);
}
//- sixten: process inputs
if(W_ViewIsCurrent(View))
{
//- sixten: handle history
@ -568,6 +681,18 @@ static void W_BuildTextEditor(workspace_view *View)
Event != 0;
Event = Event->Next)
{
//- sixten: check for scaling
if(Event->Type == PlatformEvent_Press && Event->Modifiers == PlatformModifier_Ctrl && (Event->Key == Key_Plus || Event->Key == Key_Minus))
{
Editor->FontSize += (Event->Key == Key_Plus ? 1 : -1);
continue;
}
if(Event->Type == PlatformEvent_MouseScroll && Event->Modifiers == PlatformModifier_Ctrl && (Event->Scroll.y != 0))
{
Editor->FontSize += (Event->Scroll.y);
continue;
}
if(Event->Type == PlatformEvent_Press || Event->Type == PlatformEvent_Text)
{
//- sixten: auto-indent
@ -610,7 +735,12 @@ static void W_BuildTextEditor(workspace_view *View)
CursorHasBeenModified |= W_ProcessTextEditorEvent(Editor, &FakeEvent);
}
CursorHasBeenModified |= W_ProcessTextEditorEvent(Editor, Event);
//- sixten: default event
if(W_ProcessTextEditorEvent(Editor, Event))
{
CursorHasBeenModified = true;
Platform_ConsumeEvent(UI_EventList(), Event);
}
//- sixten: apply indent
{
@ -625,103 +755,27 @@ static void W_BuildTextEditor(workspace_view *View)
}
}
}
}
//- sixten: right-click dropdown
{
if(Editor->DropdownActive)
{
UI_Tooltip
{
UI_SetNextFixedP(Editor->DropdownP);
UI_SetNextWidth(UI_Em(20, 1));
UI_SetNextHeight(UI_ChildrenSum(AC_AnimateValueDirect(1, 0.2, &Editor->DropdownTransition), 1));
UI_SetNextCornerRadius(4);
UI_Parent(UI_MakeBox(UI_BoxFlag_DrawBackground |
UI_BoxFlag_DrawDropShadow |
UI_BoxFlag_Clip |
UI_BoxFlag_FloatingX |
UI_BoxFlag_FloatingY, StrLit("Text Editor Dropdown")))
UI_Width(UI_Percent(1, 1)) UI_Height(UI_Em(1.66, 1))
UI_BackgroundColor(V4(0.25, 0.25, 0.25, 1))
UI_BorderColor(V4(0.45, 0.45, 0.45, 1))
UI_CornerRadius(2)
{
// sixten(TODO): MAKE THESE BUTTONS ACTUALLY DO STUFF!!!
if(W_BuildMenuItem(FontIcon_Gamepad, "Run in scene view", "").Clicked)
{
SV_SetCurrentSource(&Editor->Compiled);
Editor->DropdownActive = false;
}
if(Editor->EditState.Cursor != Editor->EditState.Mark)
{
if(W_BuildMenuItem(FontIcon_Document, "Copy", "Ctrl+C").Clicked)
{
Editor->DropdownActive = false;
}
if(W_BuildMenuItem(FontIcon_Cut, "Cut", "Ctrl+X").Clicked)
{
Editor->DropdownActive = false;
}
}
if(W_BuildMenuItem(FontIcon_Paste, "Paste", "Ctrl+V").Clicked)
{
Editor->DropdownActive = false;
}
if(W_BuildMenuItem(FontIcon_Floppy, "Save", "Ctrl+S").Clicked)
{
W_SaveTextEditorToFile(Editor);
Editor->DropdownActive = false;
}
}
}
}
}
ui_signal Signal = UI_SignalFromBox(EditorBox);
if(Signal.Pressed || (Signal.PressedRight && (Editor->EditState.Cursor == Editor->EditState.Mark)))
{
//- sixten: translate mouse position to text point
v2 MouseOffset = Signal.MouseP - EditorBox->Rect.Min - V2(LineMarginWidth, 0);
s64 LineIndex = Clamp((s64)(MouseOffset.y / LineHeight), 0, Editor->Lines.Count);
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);
}
if(Signal.Dragging)
{
//- sixten: translate mouse position to text point
v2 MouseOffset = Signal.MouseP - EditorBox->Rect.Min - V2(LineMarginWidth, 0);
s64 LineIndex = Clamp((s64)(MouseOffset.y / LineHeight), 0, Editor->Lines.Count);
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;
Editor->DropdownActive = false;
}
if(Signal.PressedRight)
{
Editor->DropdownActive = true;
Editor->DropdownP = UI_MouseP();
Editor->DropdownTransition = 0;
}
//- sixten: update eventual text point extents
//- sixten: change last text point & adapt view to cursor
if(CursorHasBeenModified)
{
text_point Point = TextPointFromOffset(Editor->Text.String, Editor->EditState.Cursor);
s64 LineIndex = Point.Line-1;
string Line = Substring(Editor->Text.String, Editor->Lines.Ranges[LineIndex]);
s64 ColumnIndex = Point.Column-1;
s64 ColumnOffset = UTF8OffsetFromIndex(Line, ColumnIndex);
v2 CursorP = V2(ColumnOffset*GlyphAdvance, LineIndex*LineHeight);
if(CursorP.y < Editor->Offset.y)
{
Editor->Offset.y = CursorP.y;
}
if(CursorP.y > Editor->Offset.y + VisibleRegionDim.y - LineHeight)
{
Editor->Offset.y = CursorP.y - VisibleRegionDim.y + LineHeight;
}
if(Editor->LastTextPoint.Line == Point.Line)
{
Editor->LastTextPoint = Point;
@ -733,5 +787,4 @@ static void W_BuildTextEditor(workspace_view *View)
}
}
}
ReleaseScratch(Scratch);
}

View File

@ -42,6 +42,7 @@ struct workspace_text_data
{
token_array Tokens;
range1_s64_array Lines;
s64 MaxLineCount;
};
struct workspace_view_text_editor
@ -50,6 +51,7 @@ struct workspace_view_text_editor
arena *ProcessingArena;
token_array Tokens;
range1_s64_array Lines;
s64 MaxLineCount;
compiled_scene Compiled;
// sixten: text being edited

View File

@ -39,6 +39,8 @@ inline workspace_view *W_CreateNewView(workspace_view_kind Kind, workspace_panel
workspace_text_data TextData = W_TextDataFromString(Editor->ProcessingArena, Editor->Text.String);
Editor->Tokens = TextData.Tokens;
Editor->Lines = TextData.Lines;
Editor->FontSize = 13.0f;
} break;
case W_ViewKind_NavEditor:

Binary file not shown.

View File

@ -573,6 +573,8 @@ static LRESULT Win32_WindowCallback(HWND Window, UINT Message, WPARAM WParam, LP
else if(VKCode == VK_NEXT) { Key = Key_PageDown; }
else if(VKCode == VK_HOME) { Key = Key_Home; }
else if(VKCode == VK_END) { Key = Key_End; }
else if(VKCode == VK_OEM_PLUS) { Key = Key_Plus; }
else if(VKCode == VK_OEM_MINUS) { Key = Key_Minus; }
else if(VKCode == VK_BACK) { Key = Key_Backspace; }
else if(VKCode == VK_DELETE) { Key = Key_Delete; }
else if(VKCode == VK_ESCAPE) { Key = Key_Escape; }

View File

@ -1,6 +1,6 @@
Platform
{
RefreshRate = 0;
RefreshRate = 60;
}
Dev