//- sixten: Managing nodes static workspace_editor_node *Workspace_GetNewEditorNode(workspace_view *View) { Assert(View->Type == W_View_Editor); workspace_view_editor *Editor = (workspace_view_editor *)View->Data; workspace_editor_node *Result = 0; if(DLLIsEmpty(Editor->FirstFreeNode)) { Result = PushStruct(View->Arena, workspace_editor_node); } else { Result = Editor->FirstFreeNode; DLLRemove(Editor->FirstFreeNode, Editor->LastFreeNode, Result); } if(Result) { *Result = {}; DLLInsertLast(Editor->FirstNode, Editor->LastNode, Result); } return(Result); } //- sixten: Transformations inline r32 Workspace_ViewToWorld(r32 Offset, r32 Scale, r32 Dim, r32 P) { r32 Result = (P - Dim*0.5)*(1.0/Scale) - Offset; return(Result); } inline r32 Workspace_WorldToView(r32 Offset, r32 Scale, r32 Dim, r32 P) { r32 Result = (P + Offset)*Scale + Dim*0.5; return(Result); } inline v2 Workspace_ViewToWorld(v2 Offset, r32 Scale, v2 Dim, v2 P) { v2 Result = (P - Dim*0.5)*(1.0/Scale) - Offset; return(Result); } inline v2 Workspace_WorldToView(v2 Offset, r32 Scale, v2 Dim, v2 P) { v2 Result = (P + Offset)*Scale + Dim*0.5; return(Result); } inline r32 Workspace_CalculateScaleFromZoomLevel(r32 ZoomLevel) { r32 PixelsPerUnit = 100.0; r32 Scale = PixelsPerUnit*Pow(1.25, ZoomLevel); return(Scale); } //- sixten: Commands //- sixten: Builder code static void Workspace_BuildEditorListerDropdown(workspace_editor_lister_dropdown *ListerDropdown) { b32 ActiveInDropdown = false; UI_PushBackgroundColor(SetAlpha(Color_Black, 0.3)); UI_PushBorderColor(SetAlpha(Color_Black, 0.7)); r32 HeightTransition = AC_AnimateValueF(1, 0, 0.3, "Editor Lister Dropdown %p", ListerDropdown); UI_SetNextSize(UI_Em(15, 1), UI_Em(25*HeightTransition, 1)); UI_SetNextCornerRadius(4.0); UI_SetNextFixedP(ListerDropdown->P); UI_SetNextLayoutAxis(Axis2_Y); ui_box *Box = UI_MakeBox(UI_BoxFlag_DrawBackground | UI_BoxFlag_FloatingX | UI_BoxFlag_FloatingY | UI_BoxFlag_Clickable | UI_BoxFlag_Clip, StrLit("Editor Lister Dropdown")); UI_Parent(Box) { UI_BorderColor(SetAlpha(Color_Grey, 0.7)) UI_Row() UI_Column() { UI_Size(UI_TextContent(15, 1), UI_TextContent(12, 1)) UI_LabelF("Create new node"); // sixten: Build search bar UI_SetNextWidth(UI_Percent(1, 1)); UI_SetNextHeight(UI_Em(2, 1)); UI_SetNextCornerRadius(4.0); UI_SetNextLayoutAxis(Axis2_X); UI_Parent(UI_MakeBox(UI_BoxFlag_DrawBorder|UI_BoxFlag_DrawBackground, StrLit("Editor Lister Field"))) { UI_Spacer(UI_Em(0.8, 1)); UI_Width(UI_TextContent(1, 1)) UI_Font(Font_Icons) UI_LabelF("%U", FontIcon_Filter); UI_Width(UI_Percent(1, 0)) { UI_MakeBox(0, StrLit("")); } } struct lister_option { char *Text; u32 Icons; }; lister_option Options[] = { {"Text", FontIcon_Pencil}, {"Branch", FontIcon_Menu}, }; for(s32 OptionIndex = 0; OptionIndex < ArrayCount(Options); ++OptionIndex) { lister_option *Option = Options + OptionIndex; UI_SetNextSize(UI_Percent(1, 1), UI_Em(2, 1)); UI_SetNextLayoutAxis(Axis2_X); ui_box *ClickableBox = UI_MakeBoxF(UI_BoxFlag_Clickable, "Editor Lister %s", Option->Text); ui_signal Signal = UI_SignalFromBox(ClickableBox); if(AreEqual(UI_GetActive(), ClickableBox->Key)) { ActiveInDropdown = true; } UI_Parent(ClickableBox) UI_Height(UI_Percent(1, 1)) { b32 IsHot = AreEqual(UI_GetHot(), ClickableBox->Key); r32 TargetHot = IsHot*0.8; UI_SetNextCornerRadius(3.0); UI_SetNextSize(UI_Percent(1, 1), UI_Percent(1, 1)); UI_SetNextBackgroundColor(SetAlpha(Theme_HighlightBorderColor, AC_AnimateValueF(TargetHot, TargetHot, 0.1, "Editor Lister %s%p", Option->Text, ListerDropdown))); UI_SetNextLayoutAxis(Axis2_X); ui_box *HighlightBox = UI_MakeBox(UI_BoxFlag_DrawBackground, StrLit("")); UI_Parent(HighlightBox) UI_Padding(UI_Em(0.8, 1)) { UI_Width(UI_TextContent(0, 1)) UI_Font(Font_Icons) UI_MakeBoxF(UI_BoxFlag_DrawText, "%U", Option->Icons); UI_Spacer(UI_Em(0.6, 1)); UI_Width(UI_TextContent(0, 1)) UI_MakeBoxF(UI_BoxFlag_DrawText, "%s", Option->Text); if(Signal.Clicked) { // sixten(TODO): Issue the requested command. ListerDropdown->Open = false; } } } } } ui_signal Signal = UI_SignalFromBox(Box); if(AreEqual(UI_GetActive(), Box->Key)) { ActiveInDropdown = true; } } UI_PopBackgroundColor(); UI_PopBorderColor(); if(!ActiveInDropdown && !AreEqual(UI_GetActive(), UI_EmptyKey())) { ListerDropdown->Open = false; } } static void Workspace_EditorDrawCallback(render_group *Group, glyph_atlas *Atlas, ui_box *Box, void *Data) { workspace_view_editor *Editor = (workspace_view_editor *)Data; r32 Scale = Editor->Scale; v4 LineColor = Theme_BorderColor; v2 Dim = DimOfRange(Box->Rect); s32 VerticalLineCount = Dim.x / Scale + 4; for(s32 LineIndex = -VerticalLineCount/2; LineIndex < VerticalLineCount/2; ++LineIndex) { r32 OffsetX = Workspace_WorldToView(Editor->Offset.x, Scale, Dim.x, LineIndex - (s32)Editor->Offset.x); v2 Min = Box->Rect.Min + V2(OffsetX, 0); v2 Max = Min + V2(1.5, Dim.y); PushQuad(Group, Range2R32(Min, Max), LineColor, 0, 1.2, 0); } s32 HorizontalLineCount = Dim.y / Scale + 4; for(s32 LineIndex = -HorizontalLineCount/2; LineIndex < HorizontalLineCount/2; ++LineIndex) { r32 OffsetY = Workspace_WorldToView(Editor->Offset.y, Scale, Dim.y, LineIndex - (s32)Editor->Offset.y); v2 Min = Box->Rect.Min + V2(0, OffsetY); v2 Max = Min + V2(Dim.x, 1.5); PushQuad(Group, Range2R32(Min, Max), LineColor, 0, 1.2, 0); } } static void Workspace_BuildEditor(workspace_view *View) { workspace *Workspace = W_GetState(); workspace_view_editor *Editor = (workspace_view_editor *)View->Data; UI_SetNextWidth(UI_Percent(1, 1)); UI_SetNextHeight(UI_Percent(1, 1)); ui_box *EditorBox = UI_MakeBoxF(UI_BoxFlag_Clip|UI_BoxFlag_Clickable, "Workspace Editor %p", View); UI_EquipBoxCustomDrawCallback(EditorBox, Workspace_EditorDrawCallback, Editor); r32 AnimatedZoomLevel = AC_AnimateValueF(Editor->ZoomLevel, 0, 0.25, "Workspace Editor Zoom"); Editor->Scale = Workspace_CalculateScaleFromZoomLevel(AnimatedZoomLevel); v2 EditorDim = DimOfRange(EditorBox->Rect); UI_Parent(EditorBox) { // sixten: Build the node boxes. for(workspace_editor_node *Node = Editor->FirstNode; Node != 0; Node = Node->Next) { v2 ViewDim = V2(2, 1.5)*Editor->Scale; v2 ViewP = Workspace_WorldToView(Editor->Offset, Editor->Scale, EditorDim, Node->P) - ViewDim*0.5; UI_SetNextSize(UI_Pixels(ViewDim.x, 1), UI_Pixels(ViewDim.y, 1)); UI_SetNextFixedP(ViewP); UI_SetNextLayoutAxis(Axis2_Y); Node->Box = UI_MakeBoxF(UI_BoxFlag_DrawBackground | UI_BoxFlag_DrawBorder | UI_BoxFlag_FloatingX | UI_BoxFlag_FloatingY | UI_BoxFlag_DrawDropShadow, "Workspace Editor Node %p", Node); UI_Parent(Node->Box) { UI_SetNextBackgroundColor(LinearBlend(Theme_BackgroundColor, Color_Black, 0.3)); UI_SetNextLayoutAxis(Axis2_X); UI_SetNextSize(UI_Percent(1, 1), UI_Em(1.8, 1)); Node->TitleBox = UI_MakeBoxF(UI_BoxFlag_DrawBackground | UI_BoxFlag_DrawBorder | UI_BoxFlag_Clickable, "Workspace Editor Node Title"); UI_Parent(Node->TitleBox) { UI_Spacer(UI_Em(0.5, 1)); UI_Width(UI_TextContent(0, 1)) UI_Font(Font_Bold) UI_LabelF("Node"); UI_Spacer(UI_Percent(1, 0)); UI_SetNextSize(UI_Em(1.8, 1), UI_Percent(1, 1)); UI_SetNextFont(Font_Icons); Node->CloseBox = UI_MakeBoxF(UI_BoxFlag_DrawText, "%U", FontIcon_Cancel); } } } if(Editor->ListerDropdown.Open) { Workspace_BuildEditorListerDropdown(&Editor->ListerDropdown); } } // sixten: Get input from boxes. { workspace_editor_node *Next = 0; for(workspace_editor_node *Node = Editor->FirstNode; Node != 0; Node = Next) { Next = Node->Next; ui_signal Signal = UI_SignalFromBox(Node->TitleBox); if(Signal.Dragging) { if(Signal.Pressed) { UI_StoreDragV2(Node->P); } v2 StartP = UI_GetDragV2(); v2 EndP = StartP + Signal.DragDelta*(1.0 / Editor->Scale); Node->P = EndP; } if(Signal.Dragging || Signal.Hovering) { Platform.SetCursor(PlatformCursor_ArrowAll); } } } // sixten: Process panning and zooming of the editor. ui_signal Signal = UI_SignalFromBox(EditorBox); { if(Signal.Dragging) { if(Signal.Pressed) { UI_StoreDragV2(Editor->Offset); } v2 StartOffset = UI_GetDragV2(); v2 EndOffset = StartOffset + Signal.DragDelta*(1.0 / Editor->Scale); Editor->Offset = EndOffset; // sixten: Update node positions, as to not get a one frame delay. for(workspace_editor_node *Node = 0; Node != 0; Node = Node->Next) { v2 ViewDim = V2(2, 1.5)*Editor->Scale; v2 ViewP = Workspace_WorldToView(Editor->Offset, Editor->Scale, EditorDim, Node->P) - ViewDim*0.5; Node->Box->FixedP = ViewP; } } } for(platform_event *Event = Workspace->EventList->First; Event != 0; Event = Event->Next) { if(Event->Type == PlatformEvent_MouseScroll) { if(Signal.Dragging) { UI_StoreDragV2(Editor->Offset); UI_UpdateDragStartP(); } Editor->ZoomLevel = Clamp(Editor->ZoomLevel + Event->Scroll.y, -4, 5); } } // sixten: Process shortcuts. if(Platform_KeyPress(Workspace->EventList, Key_Space, PlatformModifier_Ctrl)) { workspace_editor_lister_dropdown *ListerDropdown = &Editor->ListerDropdown; if(ListerDropdown->Open) { ListerDropdown->Open = false; } else if(InRange(EditorBox->Rect, UI_GetState()->MouseP)) { ListerDropdown->Open = true; ListerDropdown->P = UI_GetState()->MouseP - EditorBox->Rect.Min; } } }