//- sixten: Views inline workspace_view *W_CreateNewView(workspace_view_kind Kind, workspace_panel *Parent) { arena *Arena = ArenaAlloc(Kilobytes(4), true); workspace_view *View = PushStruct(Arena, workspace_view); View->Arena = Arena; View->Kind = Kind; View->Parent = Parent; switch(View->Kind) { case W_ViewKind_Settings: { View->Data = PushStruct(View->Arena, workspace_view_settings); } break; case W_ViewKind_FileLister: { workspace_view_file_lister *Lister = PushStruct(View->Arena, workspace_view_file_lister); View->Data = Lister; Lister->Path = StrLit("data"); Lister->SelectedItem = -1; } break; case W_ViewKind_TextEditor: { View->Data = PushStruct(View->Arena, workspace_view_text_editor); workspace_view_text_editor *Editor = (workspace_view_text_editor *)View->Data; Editor->ProcessingArena = ArenaAlloc(Gigabytes(1)); Editor->Text = MutableStringAllocate(Gigabytes(1)); Editor->HistoryArena = ArenaAlloc(Gigabytes(1)); SenDLLInit(&Editor->History.Sentinel); Editor->History.At = &Editor->History.Sentinel; Editor->SavePoint = Editor->History.At; workspace_text_data TextData = W_TextDataFromString(Editor->ProcessingArena, Editor->Text.String); Editor->Tokens = TextData.Tokens; Editor->Lines = TextData.Lines; } break; case W_ViewKind_NavEditor: { View->Data = PushStruct(View->Arena, workspace_view_nav_editor); } break; case W_ViewKind_ImageViewer: { View->Data = PushStruct(View->Arena, workspace_view_image_viewer); workspace_view_image_viewer *Viewer = (workspace_view_image_viewer *)View->Data; Viewer->Scale = 1.0f; //- sixten(TODO): write an image viewer // beign with loading the image from the file lister load thingy // show it on build // release the handle when done (and add a way to remove texture ig) } break; case W_ViewKind_Error: { View->Data = PushStruct(View->Arena, workspace_view_error); } break; default: break; } DLLInsertLast(Parent->FirstView, Parent->LastView, View); if(View->Parent) { Parent->CurrentView = View; } return(View); } inline void W_DestroyView(workspace_view *View) { switch(View->Kind) { case W_ViewKind_TextEditor: { workspace_view_text_editor *Editor = (workspace_view_text_editor *)View->Data; ArenaRelease(Editor->ProcessingArena); MutableStringRelease(&Editor->Text); ArenaRelease(Editor->HistoryArena); } break; case W_ViewKind_ImageViewer: { workspace_view_image_viewer *Viewer = (workspace_view_image_viewer *)View->Data; GlobalRenderCommands->DeallocateTexture(Viewer->Texture); } break; default: break; } // sixten(NOTE): This function does not ensure that the view is not being used anywhere else. ArenaRelease(View->Arena); } inline b32 W_ViewIsCurrent(workspace_view *View) { workspace *Workspace = W_GetState(); b32 Result = (Workspace->CurrentPanel && Workspace->CurrentPanel->CurrentView == View); return(Result); } inline string W_NameFromView(arena *Arena, workspace_view *View) { string Result = StrLit("Unnamed view"); switch(View->Kind) { case W_ViewKind_Startup: { Result = StrLit("Welcome"); } break; case W_ViewKind_Settings: { Result = StrLit("Settings"); } break; case W_ViewKind_TextEditor: { workspace_view_text_editor *Editor = (workspace_view_text_editor *)View->Data; if(AreEqual(Editor->FileName, StrLit(""))) { Result = StrLit("Open File"); } else { if(Editor->History.At == Editor->SavePoint) { Result = PushString(Arena, Editor->FileName); } else { Result = PushFormat(Arena, "* %S", Editor->FileName); } } } break; case W_ViewKind_SceneView: { Result = StrLit("Scene View"); } break; case W_ViewKind_NavEditor: { Result = StrLit("Navigation Editor"); } break; case W_ViewKind_FileLister: { Result = StrLit("File Lister"); } break; case W_ViewKind_ImageViewer: { workspace_view_image_viewer *Viewer = (workspace_view_image_viewer *)View->Data; Result = PushString(Arena, Viewer->FileName); } break; case W_ViewKind_Error: { Result = StrLit("Error"); } break; default: {} break; } return(Result); } static b32 W_ImageViewerSetup(workspace_view *View, string Name, string Contents) { b32 Result = false; workspace_view_image_viewer *Viewer = (workspace_view_image_viewer *)View->Data; Viewer->Texture = EmptyRenderHandle(); if(Contents.Count && Contents.Data) { s32 Width, Height, BPP; u8 *TextureData = stbi_load_from_memory(Contents.Data, Contents.Count, &Width, &Height, &BPP, 0); if(TextureData) { render_texture_format TextureFormat = Render_TextureFormat_Invalid; switch(BPP) { InvalidDefaultCase; case 1: { TextureFormat = Render_TextureFormat_R8; } break; case 3: { TextureFormat = Render_TextureFormat_RGB8; } break; case 4: { TextureFormat = Render_TextureFormat_RGBA8; } break; } Viewer->Texture = GlobalRenderCommands->AllocateTexture(V2S32(Width, Height), TextureFormat, true, TextureData); Result = true; stbi_image_free(TextureData); } } Viewer->FileName = PushString(View->Arena, Name); return(Result); } //- sixten: Builder code static void W_BuildSettingsTabButton(workspace_view_settings *Settings, char *Name, workspace_settings_category Category) { b32 IsSelected = (Settings->Category == Category); v4 Color = LinearBlend(Theme_TextColor, Theme_HighlightBorderColor, AC_AnimateValueF(IsSelected, IsSelected, 0.3, "Workspace Settings %s %p", Name, Settings)); UI_SetNextFont(Font_Bold); UI_SetNextHeight(UI_TextContent(0, 1)); UI_SetNextTextColor(Color); ui_box *Box = UI_MakeBoxF(UI_BoxFlag_DrawText | UI_BoxFlag_Clickable, Name); ui_signal Signal = UI_SignalFromBox(Box); if(Signal.Hovering) { Platform.SetCursor(PlatformCursor_Hand); } if(Signal.Pressed) { Settings->Category = Category; } } static b32 UI_DropdownSelection(char **Alternatives, s32 AlternativeCount, b32 *Open, s32 *Selected) { b32 Result = false; b32 ActiveInDropdown = false; UI_SetNextLayoutAxis(Axis2_X); ui_box *DropdownBox = UI_MakeBoxF(UI_BoxFlag_DrawBackground | UI_BoxFlag_DrawBorder | UI_BoxFlag_HotAnimation | UI_BoxFlag_ActiveAnimation | UI_BoxFlag_Clickable, "Dropdown"); UI_Parent(DropdownBox) { UI_Width(UI_Percent(1, 0)) UI_LabelF(Alternatives[*Selected]); UI_BackgroundColor(Theme_BorderColor) UI_Width(UI_Pixels(1, 1)) UI_MakeBoxF(UI_BoxFlag_DrawBackground, ""); UI_Width(UI_Pixels(25, 1)) UI_Font(Font_Icons) UI_LabelF("%U", *Open?FontIcon_DownDir:FontIcon_RightDir); } ui_signal DropdownSignal = UI_SignalFromBox(DropdownBox); if(DropdownSignal.Pressed) { *Open = !(*Open); } if(AreEqual(UI_ActiveKey(), DropdownBox->Key)) { ActiveInDropdown = true; } r32 OpenTransition = AC_AnimateValueF(*Open, 0, 0.175, "UI Dropdown %p%p", Alternatives, Open); if(OpenTransition > 0.1) { UI_Tooltip { UI_SetNextFixedP(V2(DropdownBox->Rect.Min.x, DropdownBox->Rect.Max.y)); UI_SetNextCornerRadius(4); UI_SetNextWidth(UI_Pixels(200, 1)); UI_SetNextHeight(UI_ChildrenSum(OpenTransition, 1)); UI_Parent(UI_MakeBoxF(UI_BoxFlag_Clip | UI_BoxFlag_DrawDropShadow | UI_BoxFlag_FloatingX | UI_BoxFlag_FloatingY, "Dropdown Contents %p%p", Alternatives, Open)) { UI_PushWidth(UI_Percent(1, 1)); for(s64 Index = 0; Index < Round(AlternativeCount*OpenTransition); ++Index) { ui_signal ButtonSignal = UI_ButtonF(Alternatives[Index]); if(AreEqual(UI_ActiveKey(), ButtonSignal.Box->Key)) { ActiveInDropdown = true; } if(ButtonSignal.Pressed) { *Selected = Index; Result = true; } } UI_PopWidth(); } } } if(!ActiveInDropdown && !AreEqual(UI_ActiveKey(), UI_EmptyKey())) { *Open = false; } return(Result); } UI_CUSTOM_DRAW_CALLBACK(W_ImageViewerUICallback) { workspace_view_image_viewer *Viewer = (workspace_view_image_viewer *)Data; v2_r32 ImageDim = ConvertV2ToR32(DimFromTexture(Viewer->Texture)); v2_r32 ScaleImageDim = ImageDim*Viewer->Scale; v4 Color = Color_White; range2_r32 Dest; Dest.Min = (Box->Rect.Max+Box->Rect.Min)*0.5-ScaleImageDim*0.5+Viewer->Offset*Viewer->Scale; Dest.Max = Dest.Min+ScaleImageDim; range2_r32 Source = Range2R32(V2R32(0, 0), ImageDim); PushTexturedQuad(Group, Dest, Source, Color, Color, Color, Color, 0, 0, 0, Viewer->Texture); } static void W_BuildImageViewer(workspace_view *View) { workspace_view_image_viewer *Viewer = (workspace_view_image_viewer *)View->Data; //- sixten: find image dim v2_r32 ImageDim = ConvertV2ToR32(DimFromTexture(Viewer->Texture)); UI_WidthFill UI_HeightFill { ui_box *ImageBox = UI_MakeBoxF(UI_BoxFlag_DrawBorder|UI_BoxFlag_Clickable, "Workspace View Image Viewer %p", View); UI_EquipBoxCustomDrawCallback(ImageBox, W_ImageViewerUICallback, Viewer); //- sixten: handle scrolling if(AreEqual(UI_HotKey(), ImageBox->Key)) { for(platform_event *Event = UI_EventList()->First; Event != 0; Event = Event->Next) { if(Event->Type == PlatformEvent_MouseScroll && Event->Scroll.y != 0) { Viewer->Scale *= Pow(1.10f, Event->Scroll.y); Platform_ConsumeEvent(UI_EventList(), Event); } } } ui_signal Signal = UI_SignalFromBox(ImageBox); if(Signal.Dragging) { if(Signal.Pressed) { UI_StoreDragV2(Viewer->Offset); } v2_r32 StartOffset = UI_GetDragV2(); v2_r32 EndOffset = StartOffset+Signal.DragDelta*(1.0f/Viewer->Scale); Viewer->Offset = EndOffset; } } UI_WidthFill UI_Height(UI_Em(3, 1)) UI_Row(UI_BoxFlag_DrawBackground|UI_BoxFlag_DrawBorder) { UI_Width(UI_TextContent(15, 1)) { UI_Label(Viewer->FileName); UI_LabelF("Width: %i Height: %i", (int)ImageDim.x, (int)ImageDim.y); } UI_Spacer(UI_Percent(1, 0)); } } static void W_BuildSceneView(workspace_view *View) { workspace *Workspace = W_GetState(); SV_BuildSceneView(Workspace->Input); } static void W_BuildSettings(workspace_view *View) { workspace_view_settings *Settings = (workspace_view_settings *)View->Data; workspace *Workspace = W_GetState(); UI_Height(UI_ChildrenSum(1, 1)) UI_Column() UI_Padding(UI_Pixels(50, 0)) UI_Row() UI_Padding(UI_Pixels(50, 0)) { UI_Size(UI_TextContent(0, 1), UI_TextContent(10, 1)) UI_Font(Font_Bold) UI_FontSize(36) UI_LabelF("Settings"); } UI_LayoutAxis(Axis2_X) UI_Size(UI_Percent(1, 0), UI_Percent(1, 0)) UI_Parent(UI_MakeBoxF(0, "")) { UI_Width(UI_Pixels(300, 1)) UI_Parent(UI_MakeBoxF(0, "Navigation")) { UI_Row() UI_Padding(UI_Pixels(50, 1)) UI_Width(UI_Percent(1, 0)) UI_Column() UI_Padding(UI_Percent(1, 0)) UI_Height(UI_ChildrenSum(1, 1)) UI_LayoutAxis(Axis2_Y) UI_Parent(UI_MakeBoxF(0, "")) { W_BuildSettingsTabButton(Settings, "All", W_Settings_All); UI_Spacer(UI_Pixels(30, 1)); W_BuildSettingsTabButton(Settings, "General", W_Settings_General); UI_Spacer(UI_Pixels(30, 1)); W_BuildSettingsTabButton(Settings, "Developer", W_Settings_Developer); UI_Spacer(UI_Pixels(150, 1)); } } UI_CornerRadius(5) UI_Width(UI_Pixels(1.25, 1)) UI_BackgroundColor(Color_Grey) UI_MakeBoxF(UI_BoxFlag_DrawBackground, "Separator"); UI_Padding(UI_Pixels(70, 0)) UI_LayoutAxis(Axis2_Y) UI_Width(UI_Percent(1, 0)) UI_Parent(UI_MakeBoxF(0, "Tab")) UI_Size(UI_TextContent(0, 1), UI_TextContent(10, 1)) { UI_SetNextSize(UI_Percent(1, 0), UI_Percent(1, 0)); UI_Scroll(0, &Settings->GlobalScroll, UI_BoxFlag_Clip) { workspace_settings_category Category = Settings->Category; if(!Category || (Category == W_Settings_General)) { UI_Font(Font_Bold) UI_FontSize(36) UI_LabelF("General"); char *Alternatives[] = {"15 Hz", "30 Hz", "60 Hz", "120 Hz", "144 Hz", "Uncapped", "V-Sync"}; s64 AlternativeMapping[] = {15, 30, 60, 120, 144, -1, 0}; s32 DropdownSelected; FindIndexOfElement(DropdownSelected, AlternativeMapping, 0, Workspace->Input->RefreshRate); UI_Row() { UI_LabelF("Refresh Rate:"); UI_Spacer(UI_Pixels(10, 1)); UI_SetNextWidth(UI_Pixels(200, 1)); UI_SetNextCornerRadius(4); if(UI_DropdownSelection(Alternatives, ArrayCount(Alternatives), &Settings->GeneralDropdownOpen, &DropdownSelected)) { Workspace->Input->RefreshRate = AlternativeMapping[DropdownSelected]; Settings->GeneralDropdownOpen = false; } } UI_Spacer(UI_Pixels(50, 1)); } if(!Category || (Category == W_Settings_Developer)) { UI_Font(Font_Bold) UI_FontSize(36) UI_LabelF("Developer"); UI_Checkbox(&DEBUG_DebugSettings->RenderUIDebugRects, StrLit("Render UI Debug Rects")); UI_Spacer(UI_Pixels(5, 1)); UI_Checkbox(&DEBUG_DebugSettings->RenderFPSCounter, StrLit("Render FPS Counter")); UI_Spacer(UI_Pixels(5, 1)); UI_Checkbox(&DEBUG_DebugSettings->ListHotAndActive, StrLit("List Hot & Active")); UI_Spacer(UI_Pixels(50, 1)); } } } } UI_Spacer(UI_Pixels(50, 1)); } static void W_BuildView(workspace_view *View) { r32 ViewHighlightTransition = AC_AnimateValueF(W_ViewIsCurrent(View), 0, 0.25, "Workspace View Highlight %p", View); UI_SetNextBorderColor(LinearBlend(Theme_BorderColor, Theme_HighlightBorderColor, ViewHighlightTransition)); UI_PushBackgroundColor(Theme_BackgroundColor); UI_SetNextCornerRadius(3); ui_box *ViewBox = UI_MakeBoxF(UI_BoxFlag_Clickable | UI_BoxFlag_DrawBackground | UI_BoxFlag_DrawBorder | UI_BoxFlag_Clip, "Workspace View %p", View); UI_Parent(ViewBox) UI_Size(UI_Percent(1, 0), UI_Percent(1, 0)) { switch(View->Kind) { case W_ViewKind_None: {} break; case W_ViewKind_Startup: { UI_Row() UI_Padding(UI_Pixels(50, 0)) UI_Width(UI_ChildrenSum(1, 1)) UI_Column() UI_Padding(UI_Pixels(50, 0)) { UI_Size(UI_TextContent(0, 1), UI_TextContent(10, 1)) { UI_Font(Font_Bold) UI_FontSize(36) UI_LabelF("Welcome to VN"); UI_TextColor(Theme_BorderColor) UI_LabelF("An impractical way to make a game"); UI_Spacer(UI_Percent(1, 0)); UI_Checkbox(&DEBUG_DebugSettings->ShowWelcomeMessage, StrLit("Show this message on startup")); } } } break; case W_ViewKind_Settings: { W_BuildSettings(View); } break; case W_ViewKind_ImageViewer: { W_BuildImageViewer(View); } break; case W_ViewKind_TextEditor: { W_BuildTextEditor(View); } break; case W_ViewKind_SceneView: { W_BuildSceneView(View); } break; case W_ViewKind_NavEditor: { W_BuildNavEditor(View); } break; case W_ViewKind_FileLister: { workspace_file_lister_action Action = W_BuildFileLister(View); if(Action.HasRequestedFile) { workspace_file_lister_action *ActionOnDataArena = W_FileListerActionCopy(W_GetState()->CommandDataArena, &Action); W_IssueCommand(W_Command_OpenFile, PointerToU64(ActionOnDataArena)); W_IssueCommand(W_Command_CloseView, PointerToU64(View)); } } break; case W_ViewKind_Error: { workspace_view_error *Error = (workspace_view_error *)View->Data; UI_WidthFill UI_HeightFill UI_Column() { UI_Label(Error->Message); UI_Height(UI_Em(2, 1)) UI_Row() { UI_Spacer(UI_Percent(1, 0)); UI_Width(UI_TextContent(15, 1)) UI_CornerRadius(4.0f) if(UI_Button(StrLit("Close View")).Clicked) { W_IssueCommand(W_Command_CloseView, PointerToU64(View)); } UI_Spacer(UI_Em(1, 1)); } UI_Spacer(UI_Em(1, 1)); } } break; } } UI_PopBackgroundColor(); UI_SignalFromBox(ViewBox); }