UI_CUSTOM_DRAW_CALLBACK(BuildNavViewDrawCallback) { scene_view *SceneView = (scene_view *)Data; SV_DrawBackground(SceneView, Box, Group); } static void W_NavEditorSerializeItems(workspace_view_nav_editor *Editor) { temp Scratch = GetScratch(); //- sixten: find total size u64 TotalSize = sizeof(u16); // item count for(scene_nav_item_node *Node = Editor->Items.First; Node != 0; Node = Node->Next) { string AssetName = MakeString(AssetNameLUT[Node->Item.TextureID]); TotalSize += 1; TotalSize += sizeof(u16); TotalSize += AssetName.Count; // texture id TotalSize += 1; TotalSize += sizeof(r32); // scale TotalSize += 1; TotalSize += sizeof(v2_r32); // origin TotalSize += 1; TotalSize += sizeof(v2_r32); // p TotalSize += 1; TotalSize += sizeof(u16); TotalSize += Node->Item.HoverText.Count; // hover text TotalSize += 1; TotalSize += sizeof(u16); TotalSize += sizeof(u8); TotalSize += Node->Item.Action.Content.Count; // action TotalSize += 1; // end of item } u8 *FileData = PushArray(Scratch.Arena, u8, TotalSize); u8 *Data = FileData; *(u16 *)Data = Editor->Items.Count; Data += sizeof(u16); #define WriteByte(Op) do { *Data++ = Op; } while(0) #define WriteField(Op, type, Member) do { *Data++ = Op; *(type *)Data = Member; Data += sizeof(type); } while(0) #define WriteString(String) do { *(u16 *)Data = (u16)String.Count; Data += sizeof(u16); Copy(Data, String.Data, String.Count); Data += String.Count; } while(0) for(scene_nav_item_node *Node = Editor->Items.First; Node != 0; Node = Node->Next) { scene_nav_item *Item = &Node->Item; string AssetName = MakeString(AssetNameLUT[Node->Item.TextureID]); WriteByte(S_NavItemOp_TextureID); WriteString(AssetName); WriteField(S_NavItemOp_Scale, r32, Item->Scale); WriteField(S_NavItemOp_Origin, v2_r32, Item->Origin); WriteField(S_NavItemOp_P, v2_r32, Item->P); WriteByte(S_NavItemOp_HoverText); WriteString(Item->HoverText); WriteByte(S_NavItemOp_Action); WriteByte(Item->Action.Kind); WriteString(Item->Action.Content); WriteByte(S_NavItemOp_None); } #undef WriteByte #undef WriteField #undef WriteString platform_file_handle File = Platform.OpenFile(PushFormat(Scratch.Arena, "%S/%S", Editor->FilePath, Editor->FileName), PlatformAccess_Write); if(File.IsValid) { Platform.WriteFile(File, FileData, 0, TotalSize); Platform.CloseFile(File); } ReleaseScratch(Scratch); } static workspace_string_chunk *W_StringChunkAlloc(arena *Arena, workspace_string_chunk_list *FreeList) { workspace_string_chunk *Result = FreeList->First; if(Result) { DLLRemove(FreeList->First, FreeList->Last, Result); *Result = {}; } else { Result = PushStruct(Arena, workspace_string_chunk); } return(Result); } static void W_StringChunkRelease(workspace_string_chunk_list *FreeList, workspace_string_chunk *Chunk) { DLLInsertLast(FreeList->First, FreeList->Last, Chunk); } static scene_nav_item_node *W_SceneNavItemNodeAlloc(arena *Arena, workspace_view_nav_editor *Editor) { scene_nav_item_node *Result = Editor->FirstFree; if(Result) { DLLRemove(Editor->FirstFree, Editor->LastFree, Result); *Result = {}; } else { Result = PushStruct(Arena, scene_nav_item_node); } Result->HoverTextStringChunk = W_StringChunkAlloc(Arena, &Editor->FreeStrings); Result->ActionStringChunk = W_StringChunkAlloc(Arena, &Editor->FreeStrings); Result->Item.HoverText.Data = Result->HoverTextStringChunk->Data; Result->Item.Action.Content.Data = Result->ActionStringChunk->Data; DLLInsertLast(Editor->Items.First, Editor->Items.Last, Result); Editor->Items.Count += 1; return(Result); } static void W_SceneNavItemNodeRelease(workspace_view_nav_editor *Editor, scene_nav_item_node *Node) { W_StringChunkRelease(&Editor->FreeStrings, Node->HoverTextStringChunk); W_StringChunkRelease(&Editor->FreeStrings, Node->ActionStringChunk); DLLRemove(Editor->Items.First, Editor->Items.Last, Node); DLLInsertLast(Editor->FirstFree, Editor->LastFree, Node); Editor->Items.Count -= 1; } static void W_NavEditorInspectNode(workspace_view_nav_editor *Editor, scene_nav_item_node *Node) { Editor->SelectedItem = Node; Editor->TextureDropdownOpen = false; Editor->NavActionDropdownOpen = false; } static void W_NavEditorSetup(workspace_view *View, string FilePath, string FileName, string FileContents) { workspace_view_nav_editor *Editor = (workspace_view_nav_editor *)View->Data; //- sixten: setup file info Editor->FileName = PushString(View->Arena, FileName); Editor->FilePath = PushString(View->Arena, FilePath); //- sixten: deserialize data if(FileContents.Count != 0) { u8 *DataBegin = FileContents.Data; u8 *Byte = DataBegin; u16 ItemCount = *(u16 *)Byte; Byte += 2; //- sixten: parse items for(u64 ItemIndex = 0; ItemIndex < ItemCount; ItemIndex += 1) { scene_nav_item_node *Node = W_SceneNavItemNodeAlloc(View->Arena, Editor); scene_nav_item *Item = &Node->Item; for(;;) { switch(*Byte++) { case S_NavItemOp_TextureID: { string AssetName; AssetName.Count = *(u16 *)Byte; Byte += sizeof(u16); AssetName.Data = Byte; Byte += AssetName.Count; Item->TextureID = AssetID_Error; for(u64 AssetIndex = 0; AssetIndex < AssetID_COUNT; AssetIndex += 1) { if(AreEqual(MakeString(AssetNameLUT[AssetIndex]), AssetName)) { Item->TextureID = AssetIndex; break; } } } goto Next; case S_NavItemOp_Scale: { Item->Scale = *(r32 *)Byte; Byte += sizeof(r32); } goto Next; case S_NavItemOp_Origin: { Item->Origin = *(v2_r32 *)Byte; Byte += sizeof(v2_r32); } goto Next; case S_NavItemOp_P: { Item->P = *(v2_r32 *)Byte; Byte += sizeof(v2_r32); } goto Next; case S_NavItemOp_HoverText: { Item->HoverText.Count = *(u16 *)Byte; Byte += 2; Assert(Item->HoverText.Count <= W_STRING_CHUNK_SIZE-2*sizeof(void *)); Copy(Item->HoverText.Data, Byte, Item->HoverText.Count); Byte += Item->HoverText.Count; } goto Next; case S_NavItemOp_Action: { Item->Action.Kind = (scene_nav_action_kind)*Byte; Byte += 1; Item->Action.Content.Count = *(u16 *)Byte; Byte += 2; Assert(Item->Action.Content.Count <= W_STRING_CHUNK_SIZE-2*sizeof(void *)); Copy(Item->Action.Content.Data, Byte, Item->Action.Content.Count); Byte += Item->Action.Content.Count; } goto Next; } //- sixten: no op found, assume item done break; Next:; } } } } static void W_BuildNavEditor(workspace_view *View) { workspace_view_nav_editor *Editor = (workspace_view_nav_editor *)View->Data; scene_view *SceneView = SV_GetState(); UI_BackgroundColor(V4R32(0.25, 0.25, 0.25, 1)) UI_BorderColor(V4R32(0.45, 0.45, 0.45, 1)) UI_WidthFill UI_Height(UI_Em(2.0f, 1.0f)) UI_LayoutAxis(Axis2_X) UI_Parent(UI_MakeBoxF(UI_BoxFlag_DrawBackground|UI_BoxFlag_DrawBorder, "Workspace Nav Editor Toolbar")) { UI_Width(UI_Em(2.0f, 1.0f)) UI_Font(Font_Icons) UI_CornerRadius(4.0f) { //- sixten: create nav item button if(UI_ButtonF("%U", FontIcon_UserPlus).Clicked) { scene_nav_item_node *Node = W_SceneNavItemNodeAlloc(View->Arena, Editor); //- sixten: apply default options Node->Item.Scale = 0.01f; Node->Item.Origin = V2R32(0.5, 1.0f); Node->Item.P = V2R32(0.0, 0.0f); Node->Item.TextureID = AssetID_ArthurNormal; } //- sixten: create save nav items button if(UI_ButtonF("%U", FontIcon_Floppy).Clicked) { W_NavEditorSerializeItems(Editor); } } } UI_Row() { //- sixten: build inspector panel scene_nav_item_node *SelectedItem = Editor->SelectedItem; UI_SetNextLayoutAxis(Axis2_X); UI_Width(UI_Em(24.0f*AC_AnimateValueF(SelectedItem != 0, 0, 0.3f, "Workspace Nav Editor Inspector %p", Editor), 1.0f)) UI_HeightFill UI_Parent(UI_MakeBox(UI_BoxFlag_DrawBorder, StrLit("Workspace Nav Editor Inspector"))) UI_Padding(UI_Em(1, 1)) UI_Width(UI_Percent(1.0f, 0.0f)) UI_Column() { if(SelectedItem) { UI_CornerRadius(4.0f) UI_Width(UI_Percent(1, 1)) UI_Height(UI_TextContent(15, 1)) { //- sixten: inspect position UI_Row() UI_Width(UI_Percent(0.5f, 1.0f)) {UI_LabelF("Position X:"); UI_Font(Font_Monospace) UI_LabelF("%f", SelectedItem->Item.P.x);} UI_Row() UI_Width(UI_Percent(0.5f, 1.0f)) {UI_LabelF("Position Y:"); UI_Font(Font_Monospace) UI_LabelF("%f", SelectedItem->Item.P.y);} //- sixten: inspect scale UI_Row() UI_Width(UI_Percent(0.5f, 1.0f)) { UI_LabelF("Scale:"); UI_Font(Font_Monospace) { UI_SetNextHoverCursor(PlatformCursor_ArrowHorizontal); ui_box *ScaleBox = UI_MakeBoxF(UI_BoxFlag_Clickable|UI_BoxFlag_DrawText, "%f##Scale", SelectedItem->Item.Scale); ui_signal ScaleSignal = UI_SignalFromBox(ScaleBox); if(ScaleSignal.Dragging) { if(ScaleSignal.Pressed) { UI_StoreDragR32(SelectedItem->Item.Scale); } SelectedItem->Item.Scale = UI_GetDragR32() + ScaleSignal.DragDelta.x*0.001f; } } } //- sixten: inspect texture UI_Row(0, StrLit("Texture")) UI_Width(UI_Percent(0.5f, 1.0f)) { UI_LabelF("Texture:"); UI_DropdownSelection(AssetNameLUT, ArrayCount(AssetNameLUT), &Editor->TextureDropdownOpen, &SelectedItem->Item.TextureID); } //- sixten: inspect nav action kind UI_Row(0, StrLit("Nav Action")) UI_Width(UI_Percent(0.5f, 1.0f)) { UI_LabelF("Nav Action Type:"); char *Actions[] = { "None", "Trigger Proc", "Change Scene" }; UI_DropdownSelection(Actions, ArrayCount(Actions), &Editor->NavActionDropdownOpen, (s32 *)&SelectedItem->Item.Action); } //- sixten: inspect nav action contents UI_Row(0, StrLit("Nav Action String")) UI_Width(UI_Percent(0.5f, 1.0f)) { UI_LabelF("Nav Action Contents:"); ui_signal Signal = UI_LineEdit(&Editor->NavActionStringEditState, ArrayCount(SelectedItem->ActionStringChunk->Data), SelectedItem->ActionStringChunk->Data, &SelectedItem->Item.Action.Content.Count, StrLit("Nav Action Text"), Editor->ActiveTextThing == 1); if(AreEqual(UI_ActiveKey(), Signal.Box->Key)) { Editor->ActiveTextThing = 1; } else if(!AreEqual(UI_ActiveKey(), UI_EmptyKey()) && Editor->ActiveTextThing == 1) { Editor->ActiveTextThing = 0; } } //- sixten: inspect hover text UI_Row(0, StrLit("Hover Text")) UI_Width(UI_Percent(0.5f, 1.0f)) { UI_LabelF("Hover Text:"); ui_signal Signal = UI_LineEdit(&Editor->HoverTextEditState, ArrayCount(SelectedItem->HoverTextStringChunk->Data), SelectedItem->HoverTextStringChunk->Data, &SelectedItem->Item.HoverText.Count, StrLit("Hover Text Input"), Editor->ActiveTextThing == 2); if(AreEqual(UI_ActiveKey(), Signal.Box->Key)) { Editor->ActiveTextThing = 2; } else if(!AreEqual(UI_ActiveKey(), UI_EmptyKey()) && Editor->ActiveTextThing == 2) { Editor->ActiveTextThing = 0; } } UI_Spacer(UI_TextContent(15, 1)); UI_Row() UI_FillPadding { UI_Width(UI_TextContent(15, 1)) if(UI_ButtonF("Remove Item").Pressed) { W_SceneNavItemNodeRelease(Editor, SelectedItem); Editor->SelectedItem = 0; } } } } } //- sixten: build nav view UI_Width(UI_Percent(1, 0)) UI_Height(UI_Percent(1, 0)) UI_Parent(UI_MakeBoxF(UI_BoxFlag_Clip, "Workspace View Nav Editor Thing")) { //- sixten: calculate nav view size v2_r32 ParentDim = DimOfRange(UI_TopParent()->Rect); v2_r32 BoxDim = ParentDim; r32 TargetRatio = 16.0f/9.0f; r32 ActualRatio = ParentDim.x/ParentDim.y; if(ActualRatio>TargetRatio) { BoxDim.x = BoxDim.y*TargetRatio; } else { BoxDim.y = BoxDim.x/TargetRatio; } UI_SetNextWidth(UI_Pixels(BoxDim.x, 1)); UI_SetNextHeight(UI_Pixels(BoxDim.y, 1)); UI_SetNextLayoutAxis(Axis2_Y); UI_SetNextFixedP((ParentDim-BoxDim)*0.5f); ui_box *Box = UI_MakeBoxF(UI_BoxFlag_Clip|UI_BoxFlag_DrawDropShadow|UI_BoxFlag_FloatingX|UI_BoxFlag_FloatingY|UI_BoxFlag_Clickable, "Nav View %p", View); UI_EquipBoxCustomDrawCallback(Box, BuildNavViewDrawCallback, SceneView); r32 GlobalScale = CalculateGlobalScaleFromDim(BoxDim); //- sixten: build all nav items s32 ItemIndex = 0; UI_Parent(Box) for(scene_nav_item_node *Node = Editor->Items.First; Node != 0; Node = Node->Next, ItemIndex += 1) { scene_nav_item *Item = &Node->Item; //- sixten: calculate item position r32 AppliedScale = GlobalScale*Item->Scale; render_handle Texture = A_TextureFromAssetID(Item->TextureID); v2_r32 TextureDim = ConvertV2ToR32(DimFromTexture(Texture)); v2_r32 TextureOrigin = Hadamard(TextureDim, Item->Origin); v2_r32 Dim = TextureDim*AppliedScale; v2_r32 OriginP = TextureOrigin*AppliedScale; //- sixten: build the item scene_nav_item_info *Data = PushStruct(UI_FrameArena(), scene_nav_item_info); Data->Item = Item; UI_SetNextHoverCursor(PlatformCursor_Hand); UI_SetNextSize(UI_Pixels(Dim.x, 1), UI_Pixels(Dim.y, 1)); ui_box *ItemBox = UI_MakeBoxF(UI_BoxFlag_Clickable|UI_BoxFlag_FloatingX|UI_BoxFlag_FloatingY|((Node==Editor->SelectedItem)?UI_BoxFlag_DrawBorder:0), "View Item Box %i", ItemIndex); UI_EquipBoxCustomDrawCallback(ItemBox, BuildNavItemDrawCallback, Data); if(Node == Editor->SelectedItem) { // sixten(TODO): this: //ui_box *OriginBox = UI_MakeBoxF(UI_BoxFlag_Clickable|UI_BoxFlag_FloatingX|UI_BoxFlag_FloatingY|UI_BoxFlag_DrawBorder|UI_BoxFlag_DrawBackground, "View Item Origin Box %i", ItemIndex); } //- sixten: handle signals { ui_signal Signal = UI_SignalFromBox(ItemBox); Data->Signal = Signal; //- sixten: inspect pressed item if(Signal.Clicked && Signal.DragDelta == V2R32(0, 0)) { W_NavEditorInspectNode(Editor, Node); } //- sixten: handle dragging if(Signal.Dragging) { if(Signal.Pressed) { UI_StoreDragV2(Item->P); } Item->P = UI_GetDragV2() + Signal.DragDelta/BoxDim*2; } } //- sixten: apply the calculated position v2_r32 OffsetP = BoxDim*(V2R32(1, 1) + Item->P)*0.5f; ItemBox->FixedP = (OffsetP-OriginP); } ui_signal BackgroundSignal = UI_SignalFromBox(Box); if(BackgroundSignal.Pressed) { W_NavEditorInspectNode(Editor, 0); } } } }