// // File: foldhaus_panel.h // Author: Peter Slattery // Creation Date: 2019-12-26 // // Usage: // Include this file in ONE file in your project. // Define SetPanelDefinitionExternal // #ifndef FOLDHAUS_PANEL_H enum panel_split_direction { PanelSplit_NoSplit, PanelSplit_Horizontal, PanelSplit_Vertical, PanelSplit_Count, }; typedef struct panel panel; #define PANEL_MODAL_OVERRIDE_CALLBACK(name) void name(panel* ReturningFrom, app_state* State, context Context) typedef PANEL_MODAL_OVERRIDE_CALLBACK(panel_modal_override_callback); struct panel { s32 TypeIndex; gs_data StateMemory; panel* ModalOverride; panel* IsModalOverrideFor; panel_modal_override_callback* ModalOverrideCB; rect2 Bounds; panel_split_direction SplitDirection; r32 SplitPercent; panel* Parent; union{ panel* Left; panel* Top; }; union{ panel* Right; panel* Bottom; }; }; struct free_panel { free_panel* Next; }; #define PANEL_INIT_PROC(name) void name(panel* Panel, app_state* State, context Context) typedef PANEL_INIT_PROC(panel_init_proc); #define PANEL_CLEANUP_PROC(name) void name(panel* Panel, app_state* State) typedef PANEL_CLEANUP_PROC(panel_cleanup_proc); #define PANEL_RENDER_PROC(name) void name(panel* Panel, rect2 PanelBounds, render_command_buffer* RenderBuffer, app_state* State, context Context) typedef PANEL_RENDER_PROC(panel_render_proc); // NOTE(Peter): This is used by the meta system to generate panel type info struct panel_definition { char* PanelName; s32 PanelNameLength; panel_init_proc* Init; panel_cleanup_proc* Cleanup; panel_render_proc* Render; input_command* InputCommands; s32 InputCommandsCount; }; #define PANELS_MAX 16 struct panel_system { panel_definition* PanelDefs; u32 PanelDefsCount; panel* Panels; u32 PanelsUsed; free_panel* FreeList; }; ///////////////////////////////// // // Book-Keeping // ///////////////////////////////// internal void PanelSystem_Init(panel_system* PanelSystem, panel_definition* PanelDefs, u32 PanelDefsCount, gs_memory_arena* Storage) { PanelSystem->FreeList = 0; PanelSystem->PanelDefs = PanelDefs; PanelSystem->PanelDefsCount = PanelDefsCount; PanelSystem->Panels = PushArray(Storage, panel, PANELS_MAX); } internal panel* PanelSystem_TakePanel(panel_system* PanelSystem) { panel* FreeEntry = 0; if (PanelSystem->FreeList != 0) { free_panel* FreePanel = PanelSystem->FreeList; PanelSystem->FreeList = FreePanel->Next; FreeEntry = (panel*)FreePanel; } else { Assert(PanelSystem->PanelsUsed < PANELS_MAX); FreeEntry = PanelSystem->Panels + PanelSystem->PanelsUsed++; } return FreeEntry; } internal void PanelSystem_FreePanel(panel* Panel, panel_system* PanelSystem) { Assert(Panel >= PanelSystem->Panels && Panel <= PanelSystem->Panels + PANELS_MAX); free_panel* FreeEntry = (free_panel*)Panel; FreeEntry->Next = PanelSystem->FreeList; PanelSystem->FreeList = FreeEntry; } internal void PanelSystem_FreePanelRecursive(panel* Panel, panel_system* PanelSystem) { if (Panel->SplitDirection != PanelSplit_NoSplit) { PanelSystem_FreePanelRecursive(Panel->Left, PanelSystem); PanelSystem_FreePanelRecursive(Panel->Right, PanelSystem); } PanelSystem_FreePanel(Panel, PanelSystem); } internal panel* Panel_GetModalOverride(panel* Panel) { panel* Result = Panel; if (Panel->ModalOverride != 0) { Result = Panel_GetModalOverride(Panel->ModalOverride); } return Result; } internal void Panel_PushModalOverride(panel* Root, panel* Override, panel_modal_override_callback* Callback) { Root->ModalOverride = Override; Root->ModalOverrideCB = Callback; Override->IsModalOverrideFor = Root; Override->Bounds = Root->Bounds; } internal void Panel_PopModalOverride(panel* Parent, panel_system* System) { // TODO(pjs): Free the overrided panel PanelSystem_FreePanel(Parent->ModalOverride, System); Parent->ModalOverride = 0; } internal void Panel_SetCurrentType(panel* Panel, panel_system* System, s32 NewPanelType, gs_data TypeStateMemory, app_state* State, context Context) { s32 OldTypeIndex = Panel->TypeIndex; Panel->TypeIndex = NewPanelType; Panel->StateMemory = TypeStateMemory; if(OldTypeIndex >= 0) { System->PanelDefs[OldTypeIndex].Cleanup(Panel, State); } } internal void Panel_SetType(panel* Panel, panel_system* System, s32 NewPanelTypeIndex, app_state* State, context Context) { gs_data EmptyStateData = {0}; Panel_SetCurrentType(Panel, System, NewPanelTypeIndex, EmptyStateData, State, Context); System->PanelDefs[NewPanelTypeIndex].Init(Panel, State, Context); } #define Panel_GetStateStruct(p, type) (type*)Panel_GetStateMemory((p), sizeof(type)).Memory internal gs_data Panel_GetStateMemory(panel* Panel, u64 Size) { Assert(Panel->StateMemory.Size == Size); gs_data Result = Panel->StateMemory; return Result; } internal panel* PanelSystem_PushPanel(panel_system* PanelSystem, s32 PanelTypeIndex, app_state* State, context Context) { panel* Panel = PanelSystem_TakePanel(PanelSystem); Panel_SetType(Panel, PanelSystem, PanelTypeIndex, State, Context); return Panel; } internal void SplitPanel(panel* Parent, r32 Percent, panel_split_direction SplitDirection, panel_system* PanelSystem, app_state* State, context Context) { if (Percent >= 0.0f && Percent <= 1.0f) { Parent->SplitDirection = SplitDirection; Parent->SplitPercent = Percent; s32 ParentTypeIndex = Parent->TypeIndex; gs_data ParentStateMemory = Parent->StateMemory; Parent->Left = PanelSystem_TakePanel(PanelSystem); Panel_SetCurrentType(Parent->Left, PanelSystem, ParentTypeIndex, ParentStateMemory, State, Context); Parent->Left->Parent = Parent; Parent->Right = PanelSystem_TakePanel(PanelSystem); Panel_SetCurrentType(Parent->Right, PanelSystem, ParentTypeIndex, ParentStateMemory, State, Context); Parent->Right->Parent = Parent; } } internal void SplitPanelVertically(panel* Parent, r32 Percent, panel_system* PanelSystem, app_state* State, context Context) { SplitPanel(Parent, Percent, PanelSplit_Vertical, PanelSystem, State, Context); } internal void SplitPanelHorizontally(panel* Parent, r32 Percent, panel_system* PanelSystem, app_state* State, context Context) { SplitPanel(Parent, Percent, PanelSplit_Horizontal, PanelSystem, State, Context); } internal void ConsolidatePanelsKeepOne(panel* Parent, panel* PanelToKeep, panel_system* PanelSystem) { panel* LeftChild = Parent->Left; panel* RightChild = Parent->Right; panel* PanelToDestroy = PanelToKeep == LeftChild ? RightChild : LeftChild; *Parent = *PanelToKeep; PanelSystem_FreePanel(PanelToKeep, PanelSystem); PanelSystem_FreePanelRecursive(PanelToDestroy, PanelSystem); } ///////////////////////////////// // // Rendering And Interaction // ///////////////////////////////// internal rect2 GetTopPanelBounds(panel* Panel) { rect2 Result = {}; Result.Min = v2{ Panel->Bounds.Min.x, LerpR32(Panel->SplitPercent, Panel->Bounds.Min.y, Panel->Bounds.Max.y) }; Result.Max = Panel->Bounds.Max; return Result; } internal rect2 GetBottomPanelBounds(panel* Panel) { rect2 Result = {}; Result.Min = Panel->Bounds.Min; Result.Max = v2{ Panel->Bounds.Max.x, LerpR32(Panel->SplitPercent, Panel->Bounds.Min.y, Panel->Bounds.Max.y) }; return Result; } internal rect2 GetRightPanelBounds(panel* Panel) { rect2 Result = {}; Result.Min = v2{ LerpR32(Panel->SplitPercent, Panel->Bounds.Min.x, Panel->Bounds.Max.x), Panel->Bounds.Min.y }; Result.Max = Panel->Bounds.Max; return Result; } internal rect2 GetLeftPanelBounds(panel* Panel) { rect2 Result = {}; Result.Min = Panel->Bounds.Min; Result.Max = v2{ LerpR32(Panel->SplitPercent, Panel->Bounds.Min.x, Panel->Bounds.Max.x), Panel->Bounds.Max.y }; return Result; } internal rect2 GetTopPanelBounds(panel* Panel, rect2 PanelBounds) { rect2 Result = {}; Result.Min = v2{ PanelBounds.Min.x, LerpR32(Panel->SplitPercent, PanelBounds.Min.y, PanelBounds.Max.y) }; Result.Max = PanelBounds.Max; return Result; } internal rect2 GetBottomPanelBounds(panel* Panel, rect2 PanelBounds) { rect2 Result = {}; Result.Min = PanelBounds.Min; Result.Max = v2{ PanelBounds.Max.x, LerpR32(Panel->SplitPercent, PanelBounds.Min.y, PanelBounds.Max.y) }; return Result; } internal rect2 GetRightPanelBounds(panel* Panel, rect2 PanelBounds) { rect2 Result = {}; Result.Min = v2{ LerpR32(Panel->SplitPercent, PanelBounds.Min.x, PanelBounds.Max.x), PanelBounds.Min.y }; Result.Max = PanelBounds.Max; return Result; } internal rect2 GetLeftPanelBounds(panel* Panel, rect2 PanelBounds) { rect2 Result = {}; Result.Min = PanelBounds.Min; Result.Max = v2{ LerpR32(Panel->SplitPercent, PanelBounds.Min.x, PanelBounds.Max.x), PanelBounds.Max.y }; return Result; } internal void Panel_UpdateLayout(panel* Panel, rect2 Bounds) { Panel->Bounds = Bounds; if (Panel->SplitDirection != PanelSplit_NoSplit) { rect2 LeftOrTopBounds = {}; rect2 RightOrBottomBounds = {}; switch (Panel->SplitDirection) { case PanelSplit_Horizontal: { LeftOrTopBounds = GetTopPanelBounds(Panel); RightOrBottomBounds = GetBottomPanelBounds(Panel); } break; case PanelSplit_Vertical: { LeftOrTopBounds = GetLeftPanelBounds(Panel); RightOrBottomBounds = GetRightPanelBounds(Panel); } break; InvalidDefaultCase; } Panel_UpdateLayout(Panel->Left, LeftOrTopBounds); Panel_UpdateLayout(Panel->Right, RightOrBottomBounds); } } internal void PanelSystem_UpdateLayout(panel_system* System, rect2 WindowBounds) { panel* Root = System->Panels; Panel_UpdateLayout(Root, WindowBounds); } internal panel* GetPanelContainingPoint(panel* Panel, v2 Point) { panel* Result = 0; if (PointIsInRect(Panel->Bounds, Point)) { switch (Panel->SplitDirection) { case PanelSplit_NoSplit: { Result = Panel; }break; case PanelSplit_Vertical: case PanelSplit_Horizontal: {asdfasdfasdfasdfasdf if (PointIsInRect(Panel->Left->Bounds, Point)) { Result = GetPanelContainingPoint(Panel->Left, Point); } else if (PointIsInRect(Panel->Right->Bounds, Point)) { Result = GetPanelContainingPoint(Panel->Right, Point); } }break; InvalidDefaultCase; } } return Result; } internal panel* PanelSystem_GetPanelContainingPoint(panel_system* System, v2 Point) { panel* Result = 0; panel* Root = System->Panels; Result = GetPanelContainingPoint(Root, Point); return Result; } #define FOLDHAUS_PANEL_H #endif // FOLDHAUS_PANEL_H