diff --git a/src/app/editor/foldhaus_editor.cpp b/src/app/editor/foldhaus_editor.cpp index 2548f54..29ce994 100644 --- a/src/app/editor/foldhaus_editor.cpp +++ b/src/app/editor/foldhaus_editor.cpp @@ -84,8 +84,12 @@ Editor_Render(app_state* State, context* Context, render_command_buffer* RenderB PushRenderOrthographic(RenderBuffer, State->WindowBounds); PushRenderClearScreen(RenderBuffer); + ui_InterfaceReset(&State->Interface); State->Interface.RenderBuffer = RenderBuffer; + ui_layout Layout = ui_CreateLayout(&State->Interface, Context->WindowBounds); + ui_PushLayout(&State->Interface, Layout); + DrawAllPanels(State->PanelSystem, RenderBuffer, &Context->Mouse, State, *Context); for (s32 m = 0; m < State->Modes.ActiveModesCount; m++) @@ -97,6 +101,66 @@ Editor_Render(app_state* State, context* Context, render_command_buffer* RenderB } } + ui_PopLayout(&State->Interface); + + // Draw the Interface + for (u32 i = 0; i < State->Interface.WidgetsCount; i++) + { + ui_widget Widget = State->Interface.Widgets[i]; + + if (ui_WidgetIsFlagSet(Widget, UIWidgetFlag_DrawBackground)) + { + v4 Color = State->Interface.Style.ButtonColor_Inactive; + if (ui_WidgetIdsEqual(Widget.Id, State->Interface.HotWidget)) + { + Color = State->Interface.Style.ButtonColor_Active; + } + if (ui_WidgetIdsEqual(Widget.Id, State->Interface.ActiveWidget)) + { + Color = State->Interface.Style.ButtonColor_Selected; + } + PushRenderQuad2D(RenderBuffer, Widget.Bounds.Min, Widget.Bounds.Max, Color); + } + + if (Widget.String.Length > 0) + { + v4 Color = State->Interface.Style.TextColor; + render_quad_batch_constructor BatchConstructor = PushRenderTexture2DBatch(RenderBuffer, + Widget.String.Length, + State->Interface.Style.Font->BitmapMemory, + State->Interface.Style.Font->BitmapTextureHandle, + State->Interface.Style.Font->BitmapWidth, + State->Interface.Style.Font->BitmapHeight, + State->Interface.Style.Font->BitmapBytesPerPixel, + State->Interface.Style.Font->BitmapStride); + + v2 RegisterPosition = Widget.Bounds.Min + State->Interface.Style.Margin; + + switch (Widget.Alignment) + { + case Align_Left: + { + RegisterPosition = DrawStringLeftAligned(&BatchConstructor, StringExpand(Widget.String), RegisterPosition, State->Interface.Style.Font, Color); + }break; + + case Align_Right: + { + RegisterPosition = DrawStringRightAligned(&BatchConstructor, StringExpand(Widget.String), RegisterPosition, State->Interface.Style.Font, Color); + }break; + + InvalidDefaultCase; + } + } + + if (ui_WidgetIsFlagSet(Widget, UIWidgetFlag_DrawOutline)) + { + // TODO(pjs): replace these with values from the style + r32 Thickness = 1.0f; + v4 Color = WhiteV4; + PushRenderBoundingBox2D(RenderBuffer, Widget.Bounds.Min, Widget.Bounds.Max, Thickness, Color); + } + } + Context->GeneralWorkQueue->CompleteQueueWork(Context->GeneralWorkQueue, Context->ThreadContext); Context->GeneralWorkQueue->ResetWorkQueue(Context->GeneralWorkQueue); diff --git a/src/app/editor/foldhaus_interface.cpp b/src/app/editor/foldhaus_interface.cpp index c0bad58..0a33f8f 100644 --- a/src/app/editor/foldhaus_interface.cpp +++ b/src/app/editor/foldhaus_interface.cpp @@ -388,42 +388,19 @@ DrawPanelFooter(panel* Panel, render_command_buffer* RenderBuffer, rect2 FooterB rect2 PanelSelectBtnBounds = MakeRect2MinDim(FooterBounds.Min + v2{30, 1}, v2{100, 23}); - if (Panel->PanelSelectionMenuOpen) + if (ui_BeginDropdown(&State->Interface, MakeString("Select"), PanelSelectBtnBounds)) { - rect2 ButtonBounds = MakeRect2MinDim(v2{ PanelSelectBtnBounds.Min.x, FooterBounds.Max.y }, v2{ 100, 25 }); - - rect2 MenuBounds = rect2 - { - ButtonBounds.Min, - v2{ - ButtonBounds.Min.x + Rect2Width(ButtonBounds), ButtonBounds.Min.y + (Rect2Height(ButtonBounds) * State->PanelSystem.PanelDefsCount) - }, - }; - - if (ui_MouseClickedRect(State->Interface, MenuBounds)) - { - Panel->PanelSelectionMenuOpen = false; - } - for (s32 i = 0; i < GlobalPanelDefsCount; i++) { panel_definition Def = State->PanelSystem.PanelDefs[i]; gs_string DefName = MakeString(Def.PanelName, Def.PanelNameLength); - if (ui_Button(&State->Interface, DefName, ButtonBounds)) + if (ui_Button(&State->Interface, DefName)) { Panel_SetType(Panel, &State->PanelSystem, i, State, Context); - Panel->PanelSelectionMenuOpen = false; } - - ButtonBounds = Rect2TranslateY(ButtonBounds, Rect2Height(ButtonBounds)); } } - - if (ui_Button(&State->Interface, MakeString("Select"), PanelSelectBtnBounds)) - { - Panel->PanelSelectionMenuOpen = !Panel->PanelSelectionMenuOpen; - } - + ui_EndDropdown(&State->Interface); } internal void diff --git a/src/app/editor/foldhaus_panel.h b/src/app/editor/foldhaus_panel.h index 697db65..a715de3 100644 --- a/src/app/editor/foldhaus_panel.h +++ b/src/app/editor/foldhaus_panel.h @@ -36,10 +36,6 @@ struct panel panel_split_direction SplitDirection; r32 SplitPercent; - // TODO(Peter): This REALLY doesn't want to live here - // Probably belongs in a more generalized PanelInterfaceState or something - b32 PanelSelectionMenuOpen; - panel* Parent; union{ diff --git a/src/app/editor/panels/foldhaus_panel_animation_timeline.h b/src/app/editor/panels/foldhaus_panel_animation_timeline.h index 460f228..d28fff5 100644 --- a/src/app/editor/panels/foldhaus_panel_animation_timeline.h +++ b/src/app/editor/panels/foldhaus_panel_animation_timeline.h @@ -588,7 +588,8 @@ PANEL_MODAL_OVERRIDE_CALLBACK(LoadAnimationFileCallback) internal void DrawAnimationClipsList(rect2 PanelBounds, ui_interface* Interface, u32 SelectedAnimationLayerHandle, animation_system* AnimationSystem) { - ui_layout Layout = ui_CreateLayout(*Interface, PanelBounds); + ui_layout Layout = ui_CreateLayout(Interface, PanelBounds); + ui_PushLayout(Interface, Layout); for (s32 i = 0; i < GlobalAnimationClipsCount; i++) { animation_clip Clip = GlobalAnimationClips[i]; @@ -598,6 +599,7 @@ DrawAnimationClipsList(rect2 PanelBounds, ui_interface* Interface, u32 SelectedA AddAnimationBlockAtCurrentTime(i + 1, SelectedAnimationLayerHandle, AnimationSystem); } } + ui_PopLayout(Interface); } internal void @@ -605,34 +607,36 @@ PlayBar_Render(animation_timeline_state* TimelineState, rect2 Bounds, panel* Pan { animation_system* AnimSystem = &State->AnimationSystem; ui_interface* Interface = &State->Interface; - ui_layout Layout = ui_CreateLayout(*Interface, Bounds); + ui_layout Layout = ui_CreateLayout(Interface, Bounds); + ui_PushLayout(Interface, Layout); ui_FillRect(Interface, Bounds, Interface->Style.PanelBGColors[0]); - ui_StartRow(&Layout, 4); + ui_StartRow(&State->Interface, 4); { - if (ui_LayoutButton(Interface, &Layout, MakeString("Pause"))) + if (ui_Button(Interface, MakeString("Pause"))) { AnimSystem->TimelineShouldAdvance = false; } - if (ui_LayoutButton(Interface, &Layout, MakeString("Play"), (State->AnimationSystem.TimelineShouldAdvance ? PinkV4 : BlackV4), v4{.3f, .3f, .3f, 1.0f}, TealV4)) + if (ui_Button(Interface, MakeString("Play"))) { AnimSystem->TimelineShouldAdvance = true; } - if (ui_LayoutButton(Interface, &Layout, MakeString("Stop"))) + if (ui_Button(Interface, MakeString("Stop"))) { AnimSystem->TimelineShouldAdvance = false; AnimSystem->CurrentFrame = 0; } - if (ui_LayoutButton(Interface, &Layout, MakeString("Load"))) + if (ui_Button(Interface, MakeString("Load"))) { panel* FileBrowser = PanelSystem_PushPanel(&State->PanelSystem, PanelType_FileView, State, Context); Panel_PushModalOverride(Panel, FileBrowser, LoadAnimationFileCallback); } } - ui_EndRow(&Layout); + ui_EndRow(&State->Interface); + ui_PopLayout(&State->Interface); } internal void @@ -787,7 +791,33 @@ TimeRange_Render(animation_timeline_state* TimelineState, rect2 Bounds, render_c internal void AnimInfoView_Render(animation_timeline_state* TimelineState, rect2 Bounds, render_command_buffer* RenderBuffer, app_state* State, context Context) { - ui_FillRect(&State->Interface, Bounds, PinkV4); + animation_system* AnimSystem = &State->AnimationSystem; + animation* ActiveAnim = AnimationSystem_GetActiveAnimation(AnimSystem); + + ui_interface* Interface = &State->Interface; + ui_layout Layout = ui_CreateLayout(Interface, Bounds); + ui_PushLayout(Interface, Layout); + + ui_FillRect(&State->Interface, Bounds, Interface->Style.PanelBGColors[0]); + + ui_StartRow(&State->Interface, 2); + { + ui_DrawString(Interface, MakeString("Active Animation")); + if (ui_BeginDropdown(Interface, ActiveAnim->Name)) + { + for (u32 i = 0; i < AnimSystem->Animations.Count; i++) + { + animation Animation = AnimSystem->Animations.Values[i]; + if (ui_Button(Interface, Animation.Name)) + { + AnimSystem->ActiveAnimationIndex = i; + } + } + } + ui_EndDropdown(Interface); + } + ui_EndRow(&State->Interface); + ui_PopLayout(Interface); } internal void @@ -807,7 +837,7 @@ AnimationTimeline_Render(panel* Panel, rect2 PanelBounds, render_command_buffer* RectVSplit(PanelBounds, 300, &InfoBounds, &TimelineBounds); rect2 AnimInfoBounds, SelectionInfoBounds; - RectHSplitAtPercent(InfoBounds, .35f, &AnimInfoBounds, &SelectionInfoBounds); + RectHSplitAtPercent(InfoBounds, .65f, &AnimInfoBounds, &SelectionInfoBounds); { // Timeline rect2 LayersPanelBounds, TimeRangePanelBounds; diff --git a/src/app/editor/panels/foldhaus_panel_file_view.h b/src/app/editor/panels/foldhaus_panel_file_view.h index e369e7f..f4427c0 100644 --- a/src/app/editor/panels/foldhaus_panel_file_view.h +++ b/src/app/editor/panels/foldhaus_panel_file_view.h @@ -88,15 +88,16 @@ internal void FileView_Render(panel* Panel, rect2 PanelBounds, render_command_buffer* RenderBuffer, app_state* State, context Context) { file_view_state* FileViewState = Panel_GetStateStruct(Panel, file_view_state); - ui_layout Layout = ui_CreateLayout(State->Interface, PanelBounds); + ui_layout Layout = ui_CreateLayout(&State->Interface, PanelBounds); + ui_PushLayout(&State->Interface, Layout); - if (ui_LayoutButton(&State->Interface, &Layout, MakeString("Exit"))) + if (ui_Button(&State->Interface, MakeString("Exit"))) { FileView_Exit_(Panel, State, Context); } // Header - ui_LayoutDrawString(&State->Interface, &Layout, FileViewState->WorkingDirectory, v4{0, 1, 0, 1}); + ui_DrawString(&State->Interface, FileViewState->WorkingDirectory); // File Display for (u32 i = 0; i < FileViewState->FileNames.Count; i++) @@ -121,6 +122,7 @@ FileView_Render(panel* Panel, rect2 PanelBounds, render_command_buffer* RenderBu } } + ui_PopLayout(&State->Interface); } diff --git a/src/app/editor/panels/foldhaus_panel_hierarchy.h b/src/app/editor/panels/foldhaus_panel_hierarchy.h index 2c076b3..54b6b8c 100644 --- a/src/app/editor/panels/foldhaus_panel_hierarchy.h +++ b/src/app/editor/panels/foldhaus_panel_hierarchy.h @@ -38,7 +38,9 @@ GSMetaTag(panel_type_hierarchy); internal void HierarchyView_Render(panel* Panel, rect2 PanelBounds, render_command_buffer* RenderBuffer, app_state* State, context Context) { - ui_layout Layout = ui_CreateLayout(State->Interface, PanelBounds); + ui_layout Layout = ui_CreateLayout(&State->Interface, PanelBounds); + ui_PushLayout(&State->Interface, Layout); + gs_string TempString = PushString(State->Transient, 256); u32 LineCount = (u32)(Rect2Height(PanelBounds) / Layout.RowHeight) + 1; u32 AssembliesToDraw = Min(LineCount, State->Assemblies.Count); @@ -57,16 +59,15 @@ HierarchyView_Render(panel* Panel, rect2 PanelBounds, render_command_buffer* Ren assembly Assembly = State->Assemblies.Values[AssemblyIndex]; PrintF(&TempString, "%S", Assembly.Name); - ui_layout ItemLayout = ui_CreateLayout(State->Interface, LineBounds[AssemblyIndex]); - ui_StartRow(&ItemLayout, 2); + ui_StartRow(&State->Interface, 2); { - ui_LayoutDrawString(&State->Interface, &ItemLayout, TempString, State->Interface.Style.TextColor); - if (ui_LayoutListButton(&State->Interface, &ItemLayout, MakeString("X"), AssemblyIndex)) + ui_DrawString(&State->Interface, TempString); + if (ui_LayoutListButton(&State->Interface, &Layout, MakeString("X"), AssemblyIndex)) { UnloadAssembly(AssemblyIndex, State, Context); } } - ui_EndRow(&ItemLayout); + ui_EndRow(&State->Interface); } if (AssembliesToDraw < LineCount) @@ -79,6 +80,8 @@ HierarchyView_Render(panel* Panel, rect2 PanelBounds, render_command_buffer* Ren Panel_PushModalOverride(Panel, FileBrowser, LoadAssemblyCallback); } } + + ui_PopLayout(&State->Interface); } diff --git a/src/app/editor/panels/foldhaus_panel_profiler.h b/src/app/editor/panels/foldhaus_panel_profiler.h index 90fbafa..0f38547 100644 --- a/src/app/editor/panels/foldhaus_panel_profiler.h +++ b/src/app/editor/panels/foldhaus_panel_profiler.h @@ -84,7 +84,7 @@ RenderProfiler_ScopeVisualization(ui_interface* Interface, ui_layout Layout, deb PrintF(&String, "%S : %d - %d", HotRecordName->Name, HotRecord->StartCycles, HotRecord->EndCycles); rect2 TextBounds = MakeRect2MinDim(Interface->Mouse.Pos, v2{256, 32}); - ui_TextBox(Interface, TextBounds, String, BlackV4, WhiteV4); + ui_DrawString(Interface, String, TextBounds); } } @@ -95,15 +95,15 @@ RenderProfiler_ListVisualization(ui_interface* Interface, ui_layout Layout, debu gs_string String = MakeString(Backbuffer, 0, 256); r32 ColumnWidths[] = {256, 128, 128, 128, 128}; - ui_StartRow(&Layout, 5, &ColumnWidths[0]); + ui_StartRow(Interface, 5, &ColumnWidths[0]); { - ui_LayoutDrawString(Interface, &Layout, MakeString("Procedure"), Interface->Style.TextColor); - ui_LayoutDrawString(Interface, &Layout, MakeString("% Frame"), Interface->Style.TextColor); - ui_LayoutDrawString(Interface, &Layout, MakeString("Seconds"), Interface->Style.TextColor); - ui_LayoutDrawString(Interface, &Layout, MakeString("Cycles"), Interface->Style.TextColor); - ui_LayoutDrawString(Interface, &Layout, MakeString("Calls"), Interface->Style.TextColor); + ui_DrawString(Interface, MakeString("Procedure")); + ui_DrawString(Interface, MakeString("% Frame")); + ui_DrawString(Interface, MakeString("Seconds")); + ui_DrawString(Interface, MakeString("Cycles")); + ui_DrawString(Interface, MakeString("Calls")); } - ui_EndRow(&Layout); + ui_EndRow(Interface); for (s32 n = 0; n < VisibleFrame->ScopeNamesMax; n++) { @@ -112,24 +112,24 @@ RenderProfiler_ListVisualization(ui_interface* Interface, ui_layout Layout, debu { collated_scope_record* CollatedRecord = VisibleFrame->CollatedScopes + n; - ui_StartRow(&Layout, 5, &ColumnWidths[0]); + ui_StartRow(Interface, 5, &ColumnWidths[0]); { PrintF(&String, "%S", NameEntry.Name); - ui_LayoutDrawString(Interface, &Layout, String, Interface->Style.TextColor); + ui_DrawString(Interface, String); PrintF(&String, "%f%%", CollatedRecord->PercentFrameTime); - ui_LayoutDrawString(Interface, &Layout, String, Interface->Style.TextColor); + ui_DrawString(Interface, String); PrintF(&String, "%fs", CollatedRecord->TotalSeconds); - ui_LayoutDrawString(Interface, &Layout, String, Interface->Style.TextColor); + ui_DrawString(Interface, String); PrintF(&String, "%dcy", CollatedRecord->TotalCycles); - ui_LayoutDrawString(Interface, &Layout, String, Interface->Style.TextColor); + ui_DrawString(Interface, String); PrintF(&String, "%d", CollatedRecord->CallCount); - ui_LayoutDrawString(Interface, &Layout, String, Interface->Style.TextColor); + ui_DrawString(Interface, String); } - ui_EndRow(&Layout); + ui_EndRow(Interface); } } } @@ -179,41 +179,43 @@ ProfilerView_Render(panel* Panel, rect2 PanelBounds, render_command_buffer* Rend debug_frame* VisibleFrame = GetLastDebugFrame(GlobalDebugServices); - ui_layout Layout = ui_CreateLayout(State->Interface, ProcListBounds); - ui_StartRow(&Layout, 4); + ui_layout Layout = ui_CreateLayout(&State->Interface, ProcListBounds); + ui_PushLayout(&State->Interface, Layout); + + ui_StartRow(&State->Interface, 4); { s64 FrameStartCycles = VisibleFrame->FrameStartCycles; s64 FrameTotalCycles = VisibleFrame->FrameEndCycles - VisibleFrame->FrameStartCycles; u32 CurrentDebugFrame = GlobalDebugServices->CurrentDebugFrame - 1; PrintF(&String, "Frame %d", CurrentDebugFrame); - ui_LayoutDrawString(&State->Interface, &Layout, String, WhiteV4); + ui_DrawString(&State->Interface, String); PrintF(&String, "Total Cycles: %lld", FrameTotalCycles); - ui_LayoutDrawString(&State->Interface, &Layout, String, WhiteV4); + ui_DrawString(&State->Interface, String); // NOTE(NAME): Skipping a space for aesthetic reasons, not functional, and could // be removed, or used for something else ui_ReserveElementBounds(&Layout); - if (ui_LayoutButton(&State->Interface, &Layout, MakeString("Resume Recording"))) + if (ui_Button(&State->Interface, MakeString("Resume Recording"))) { GlobalDebugServices->RecordFrames = true; } } - ui_EndRow(&Layout); + ui_EndRow(&State->Interface); - ui_StartRow(&Layout, 8); + ui_StartRow(&State->Interface, 8); { - if (ui_LayoutButton(&State->Interface, &Layout, MakeString("Scope View"))) + if (ui_Button(&State->Interface, MakeString("Scope View"))) { GlobalDebugServices->Interface.FrameView = FRAME_VIEW_PROFILER; } - if (ui_LayoutButton(&State->Interface, &Layout, MakeString("List View"))) + if (ui_Button(&State->Interface, MakeString("List View"))) { GlobalDebugServices->Interface.FrameView = FRAME_VIEW_SCOPE_LIST; } } - ui_EndRow(&Layout); + ui_EndRow(&State->Interface); if (GlobalDebugServices->Interface.FrameView == FRAME_VIEW_PROFILER) { @@ -223,6 +225,8 @@ ProfilerView_Render(panel* Panel, rect2 PanelBounds, render_command_buffer* Rend { RenderProfiler_ListVisualization(&State->Interface, Layout, VisibleFrame, Memory); } + + ui_PopLayout(&State->Interface); } diff --git a/src/app/foldhaus_app.cpp b/src/app/foldhaus_app.cpp index f9a1e3b..2f6acaf 100644 --- a/src/app/foldhaus_app.cpp +++ b/src/app/foldhaus_app.cpp @@ -103,7 +103,7 @@ INITIALIZE_APPLICATION(InitializeApplication) State->Interface.Style.PanelBGColors[3] = v4{.6f, .6f, .6f, 1}; State->Interface.Style.ButtonColor_Inactive = BlackV4; State->Interface.Style.ButtonColor_Active = v4{.1f, .1f, .1f, 1}; - State->Interface.Style.ButtonColor_Selected = v4{.1f, .1f, .3f, 1}; + State->Interface.Style.ButtonColor_Selected = v4{.3f, .3f, .3f, 1}; State->Interface.Style.TextColor = WhiteV4; State->Interface.Style.ListBGColors[0] = v4{ .16f, .16f, .16f, 1.f }; State->Interface.Style.ListBGColors[1] = v4{ .18f, .18f, .18f, 1.f }; @@ -112,6 +112,9 @@ INITIALIZE_APPLICATION(InitializeApplication) State->Interface.Style.Margin = v2{5, 5}; State->Interface.Style.RowHeight = ui_GetTextLineHeight(State->Interface); + State->Interface.WidgetsCountMax = 4096; + State->Interface.Widgets = PushArray(&State->Permanent, ui_widget, State->Interface.WidgetsCountMax); + State->SACN = SACN_Initialize(Context); State->Camera.FieldOfView = 45.0f; @@ -119,8 +122,7 @@ INITIALIZE_APPLICATION(InitializeApplication) State->Camera.Near = .1f; State->Camera.Far = 800.0f; State->Camera.Position = v3{0, 0, 400}; - State->Camera.LookAt = v3{0, 0, 0 - }; + State->Camera.LookAt = v3{0, 0, 0}; State->LedSystem = LedSystemInitialize(Context.ThreadContext.Allocator, 128); diff --git a/src/app/interface.h b/src/app/interface.h index 12ed3d3..d390069 100644 --- a/src/app/interface.h +++ b/src/app/interface.h @@ -5,6 +5,14 @@ // #ifndef INTERFACE_H +// Widget Capabilities +// - string +// - background +// - outline +// - active (mouse is interacting) +// - hot (mouse could be about to interact) +// - retained state - if a toggle is active, or a drop down is open + enum gs_string_alignment { Align_Left, @@ -129,7 +137,7 @@ DrawCursor (render_quad_batch_constructor* BatchConstructor, v2 Position, v4 Col } internal v2 -Drawgs_stringWithCursor (render_command_buffer* RenderBuffer, gs_string String, s32 CursorPosition, bitmap_font* Font, v2 Position, v4 Color, v4 CursorColor, gs_string_alignment Alignment = Align_Left) +DrawStringWithCursor (render_command_buffer* RenderBuffer, gs_string String, s32 CursorPosition, bitmap_font* Font, v2 Position, v4 Color, v4 CursorColor, gs_string_alignment Alignment = Align_Left) { DEBUG_TRACK_FUNCTION; v2 LowerRight = Position; @@ -180,10 +188,44 @@ Drawgs_stringWithCursor (render_command_buffer* RenderBuffer, gs_string String, return LowerRight; } +// TODO(pjs): remove the need for htis (go thru and remove code that's in the #else block of #ifdef EXTERNAL_RENDERER s +#define EXTERNAL_RENDERER + +enum ui_widget_flag +{ + UIWidgetFlag_DrawBackground, + UIWidgetFlag_DrawOutline, + UIWidgetFlag_Clickable, +}; + +struct ui_widget_id +{ + u64 Index; + u64 LayoutId; +}; + +struct ui_widget +{ + ui_widget_id Id; + + gs_string String; + gs_string_alignment Alignment; + + rect2 Bounds; + u64 Flags; + bool RetainedState; +}; + +struct ui_eval_result +{ + bool Clicked; +}; + struct interface_config { v4 PanelBGColors[4]; + // TODO(pjs): Turn these into _Default, _Hot, _Active v4 ButtonColor_Inactive, ButtonColor_Active, ButtonColor_Selected; v4 TextColor; @@ -199,17 +241,38 @@ struct interface_config r32 RowHeight; }; +enum ui_layout_direction +{ + LayoutDirection_TopDown, + LayoutDirection_BottomUp, +}; + struct ui_layout { + u64 Id; + rect2 Bounds; v2 Margin; r32 RowHeight; r32 RowYAt; + ui_layout_direction FillDirection; + b32 DrawHorizontal; u32 ColumnsMax; r32* ColumnWidths; u32 ColumnsCount; + + // NOTE(pjs): I'm not sure this will stay but + // its here so that when we end things like a dropdown, + // we can check the retained state of that dropdown + ui_widget_id WidgetReference; +}; + +struct ui_widget_retained_state +{ + ui_widget_id Id; + bool Value; }; struct ui_interface @@ -217,49 +280,144 @@ struct ui_interface interface_config Style; mouse_state Mouse; render_command_buffer* RenderBuffer; + + ui_widget* Widgets; + u64 WidgetsCount; + u64 WidgetsCountMax; + + ui_widget_id HotWidget; + ui_widget_id ActiveWidget; + +#define LAYOUT_COUNT_MAX 8 + ui_layout LayoutStack[LAYOUT_COUNT_MAX]; + u64 LayoutStackCount; + u64 LayoutIdAcc; + +#define RETAINED_STATE_MAX 128 + ui_widget_retained_state RetainedState[RETAINED_STATE_MAX]; + u64 RetainedStateCount; }; +internal void +ui_InterfaceReset(ui_interface* Interface) +{ + Interface->WidgetsCount = 0; + Interface->LayoutStackCount = 0; + Interface->LayoutIdAcc = 0; +} + +internal bool +ui_WidgetIdsEqual(ui_widget_id A, ui_widget_id B) +{ + bool Result = (A.Index == B.Index) && (A.LayoutId == B.LayoutId); + return Result; +} + +internal ui_widget_retained_state* +ui_GetRetainedState(ui_interface* Interface, ui_widget_id Id) +{ + ui_widget_retained_state* Result = 0; + for (u64 i = 0; i < Interface->RetainedStateCount; i++) + { + if (ui_WidgetIdsEqual(Interface->RetainedState[i].Id, Id)) + { + Result = Interface->RetainedState + i; + break; + } + } + return Result; +} + +internal ui_widget_retained_state* +ui_CreateRetainedState(ui_interface* Interface, ui_widget_id Id) +{ + u64 Index = Interface->RetainedStateCount++; + ui_widget_retained_state* Result = Interface->RetainedState + Index; + Result->Id = Id; + return Result; +} + +// +// Interaction +// + +internal b32 +ui_MouseClickedRect(ui_interface Interface, rect2 Rect) +{ + b32 Result = MouseButtonTransitionedDown(Interface.Mouse.LeftButtonState); + Result &= PointIsInRect(Rect, Interface.Mouse.Pos); + return Result; +} + +// Layout + static ui_layout -ui_CreateLayout(ui_interface Interface, rect2 Bounds) +ui_CreateLayout(ui_interface* Interface, rect2 Bounds, ui_layout_direction FillDirection = LayoutDirection_TopDown) { ui_layout Result = {0}; Result.Bounds = Bounds; - Result.Margin = Interface.Style.Margin; - Result.RowHeight = Interface.Style.RowHeight; - Result.RowYAt = Bounds.Max.y - Result.RowHeight; + Result.Margin = Interface->Style.Margin; + Result.RowHeight = Interface->Style.RowHeight; + Result.FillDirection = FillDirection; + switch(FillDirection) + { + case LayoutDirection_BottomUp: + { + Result.RowYAt = Bounds.Min.y; + }break; + + case LayoutDirection_TopDown: + { + Result.RowYAt = Bounds.Max.y - Result.RowHeight; + }break; + } + + Result.Id = ++Interface->LayoutIdAcc; + return Result; } static void -ui_StartRow(ui_layout* Layout, u32 ColumnsMax) +ui_PushLayout(ui_interface* Interface, ui_layout Layout) { - Layout->DrawHorizontal = true; - Layout->ColumnsMax = ColumnsMax; - Layout->ColumnWidths = 0; - Layout->ColumnsCount = 0; + Assert(Interface->LayoutStackCount < LAYOUT_COUNT_MAX); + Interface->LayoutStack[Interface->LayoutStackCount++] = Layout; } static void -ui_StartRow(ui_layout* Layout) +ui_PopLayout(ui_interface* Interface) { - ui_StartRow(Layout, 0); + Assert(Interface->LayoutStackCount > 0); + Interface->LayoutStackCount -= 1; } static void -ui_StartRow(ui_layout* Layout, u32 ColumnsMax, r32* ColumnWidths) +ui_StartRow(ui_interface* Interface, u32 ColumnsMax = 0) { - Layout->DrawHorizontal = true; - Layout->ColumnsMax = ColumnsMax; - Layout->ColumnWidths = ColumnWidths; - Layout->ColumnsCount = 0; + u64 LayoutIdx = Interface->LayoutStackCount - 1; + Interface->LayoutStack[LayoutIdx].DrawHorizontal = true; + Interface->LayoutStack[LayoutIdx].ColumnsMax = ColumnsMax; + Interface->LayoutStack[LayoutIdx].ColumnWidths = 0; + Interface->LayoutStack[LayoutIdx].ColumnsCount = 0; } static void -ui_EndRow(ui_layout* Layout) +ui_StartRow(ui_interface* Interface, u32 ColumnsMax, r32* ColumnWidths) { - Layout->DrawHorizontal = false; - Layout->ColumnWidths = 0; - Layout->RowYAt -= Layout->RowHeight; + u64 LayoutIdx = Interface->LayoutStackCount - 1; + Interface->LayoutStack[LayoutIdx].DrawHorizontal = true; + Interface->LayoutStack[LayoutIdx].ColumnsMax = ColumnsMax; + Interface->LayoutStack[LayoutIdx].ColumnWidths = ColumnWidths; + Interface->LayoutStack[LayoutIdx].ColumnsCount = 0; +} + +static void +ui_EndRow(ui_interface* Interface) +{ + u64 LayoutIdx = Interface->LayoutStackCount - 1; + Interface->LayoutStack[LayoutIdx].DrawHorizontal = false; + Interface->LayoutStack[LayoutIdx].ColumnWidths = 0; + Interface->LayoutStack[LayoutIdx].RowYAt -= Interface->LayoutStack[LayoutIdx].RowHeight; } static b32 @@ -270,7 +428,21 @@ ui_TryReserveElementBounds(ui_layout* Layout, rect2* Bounds) { Bounds->Min = { Layout->Bounds.Min.x, Layout->RowYAt }; Bounds->Max = { Layout->Bounds.Max.x, Bounds->Min.y + Layout->RowHeight }; - Layout->RowYAt -= Layout->RowHeight; + + switch (Layout->FillDirection) + { + case LayoutDirection_BottomUp: + { + Layout->RowYAt += Layout->RowHeight; + }break; + + case LayoutDirection_TopDown: + { + Layout->RowYAt -= Layout->RowHeight; + }break; + + InvalidDefaultCase; + } } else { @@ -309,14 +481,6 @@ ui_TryReserveElementBounds(ui_layout* Layout, rect2* Bounds) return Result; } -static rect2 -ui_ReserveTextLineBounds(ui_interface Interface, gs_string Text, ui_layout* Layout) -{ - rect2 Bounds = {0}; - - return Bounds; -} - static rect2 ui_ReserveElementBounds(ui_layout* Layout) { @@ -340,18 +504,81 @@ ui_LayoutRemaining(ui_layout Layout) return Result; } -// -// Interaction -// +// Widgets -internal b32 -ui_MouseClickedRect(ui_interface Interface, rect2 Rect) +internal ui_widget +ui_CreateWidget(gs_string String) { - b32 Result = MouseButtonTransitionedDown(Interface.Mouse.LeftButtonState); - Result &= PointIsInRect(Rect, Interface.Mouse.Pos); + ui_widget Result = {}; + Result.String = String; + Result.Alignment = Align_Left; return Result; } +internal void +ui_WidgetSetFlag(ui_widget* Widget, u64 Flag) +{ + u64 Value = ((u64)1 << Flag); + Widget->Flags = Widget->Flags | Value; +} + +internal bool +ui_WidgetIsFlagSet(ui_widget Widget, u64 Flag) +{ + u64 Value = ((u64)1 << Flag); + bool Result = (Widget.Flags & Value); + return Result; +} + +internal ui_eval_result +ui_EvaluateWidget(ui_interface* Interface, ui_widget* Widget, rect2 Bounds) +{ + ui_eval_result Result = {}; + + Assert(Interface->WidgetsCount < Interface->WidgetsCountMax); + Widget->Id.Index = Interface->WidgetsCount++; + Widget->Id.LayoutId = Interface->LayoutStack[Interface->LayoutStackCount - 1].Id; + + Widget->Bounds = Bounds; + Interface->Widgets[Widget->Id.Index] = *Widget; + + if (ui_WidgetIsFlagSet(*Widget, UIWidgetFlag_Clickable)) + { + if (PointIsInRect(Widget->Bounds, Interface->Mouse.Pos)) + { + if (ui_WidgetIdsEqual(Interface->HotWidget, Widget->Id) && MouseButtonTransitionedDown(Interface->Mouse.LeftButtonState)) + { + Result.Clicked = true; + Interface->ActiveWidget = Widget->Id; + } + + if (ui_WidgetIdsEqual(Interface->ActiveWidget, Widget->Id) && + MouseButtonTransitionedUp(Interface->Mouse.LeftButtonState)) + { + Interface->ActiveWidget = {}; + } + + Interface->HotWidget = Widget->Id; + } + } + + return Result; +} + +internal ui_eval_result +ui_EvaluateWidget(ui_interface* Interface, ui_widget* Widget) +{ + rect2 Bounds = {0}; + ui_layout* Layout = Interface->LayoutStack + Interface->LayoutStackCount - 1; + if (!ui_TryReserveElementBounds(Layout, &Bounds)) + { + // TODO(pjs): This isn't invalid, but Idk when we'd hit this case yet + InvalidCodePath; + } + + return ui_EvaluateWidget(Interface, Widget, Bounds); +} + // // Drawing Functions // @@ -376,78 +603,40 @@ ui_OutlineRect(ui_interface* Interface, rect2 Bounds, r32 Thickness, v4 Color) } internal void -ui_DrawString(ui_interface* Interface, gs_string String, rect2 Bounds, v4 Color, gs_string_alignment Alignment = Align_Left) +ui_DrawString(ui_interface* Interface, gs_string String, rect2 Bounds, gs_string_alignment Alignment = Align_Left) { DEBUG_TRACK_FUNCTION; - render_quad_batch_constructor BatchConstructor = PushRenderTexture2DBatch(Interface->RenderBuffer, - String.Length, - Interface->Style.Font->BitmapMemory, - Interface->Style.Font->BitmapTextureHandle, - Interface->Style.Font->BitmapWidth, - Interface->Style.Font->BitmapHeight, - Interface->Style.Font->BitmapBytesPerPixel, - Interface->Style.Font->BitmapStride); - - v2 RegisterPosition = Bounds.Min + Interface->Style.Margin; - if (Alignment == Align_Left) - { - RegisterPosition = DrawStringLeftAligned(&BatchConstructor, StringExpand(String), RegisterPosition, Interface->Style.Font, Color); - } - else if (Alignment == Align_Right) - { - RegisterPosition = DrawStringRightAligned(&BatchConstructor, StringExpand(String), RegisterPosition, Interface->Style.Font, Color); - } - else - { - InvalidCodePath; - } + ui_widget Widget = ui_CreateWidget(String); + Widget.Bounds = Bounds; + ui_EvaluateWidget(Interface, &Widget); } -static void -ui_LayoutDrawString(ui_interface* Interface, ui_layout* Layout, gs_string String, v4 Color, gs_string_alignment Alignment = Align_Left) +internal void +ui_DrawString(ui_interface* Interface, gs_string String, gs_string_alignment Alignment = Align_Left) { - rect2 Bounds = {0}; - if (!ui_TryReserveElementBounds(Layout, &Bounds)) - { - // TODO(NAME): Not invalid, just haven't implemented yet. - // This is the case where Layout is in row mode without a fixed number of elements - InvalidCodePath; - } - ui_DrawString(Interface, String, Bounds, Color, Alignment); -} - -static void -ui_TextBox(ui_interface* Interface, rect2 Bounds, gs_string Text, v4 BGColor, v4 TextColor) -{ - ui_FillRect(Interface, Bounds, BGColor); - ui_DrawString(Interface, Text, Bounds, TextColor); + DEBUG_TRACK_FUNCTION; + ui_widget Widget = ui_CreateWidget(String); + ui_EvaluateWidget(Interface, &Widget); } static b32 -ui_Button(ui_interface* Interface, gs_string Text, rect2 Bounds, v4 InactiveColor, v4 HoverColor, v4 ClickedColor) +ui_Button(ui_interface* Interface, gs_string Text) { - b32 Pressed = false; - v4 ButtonBG = InactiveColor; - if (PointIsInRect(Bounds, Interface->Mouse.Pos)) - { - ButtonBG = HoverColor; - if (MouseButtonTransitionedDown(Interface->Mouse.LeftButtonState)) - { - ButtonBG = ClickedColor; - Pressed = true; - } - } - ui_TextBox(Interface, Bounds, Text, ButtonBG, Interface->Style.TextColor); - return Pressed; + ui_widget Widget = ui_CreateWidget(Text); + ui_WidgetSetFlag(&Widget, UIWidgetFlag_Clickable); + ui_WidgetSetFlag(&Widget, UIWidgetFlag_DrawBackground); + ui_eval_result Result = ui_EvaluateWidget(Interface, &Widget); + return Result.Clicked; } static b32 ui_Button(ui_interface* Interface, gs_string Text, rect2 Bounds) { - v4 BGColor = Interface->Style.ButtonColor_Inactive; - v4 HoverColor = Interface->Style.ButtonColor_Active; - v4 SelectedColor = Interface->Style.ButtonColor_Selected; - return ui_Button(Interface, Text, Bounds, BGColor, HoverColor, SelectedColor); + ui_widget Widget = ui_CreateWidget(Text); + ui_WidgetSetFlag(&Widget, UIWidgetFlag_Clickable); + ui_WidgetSetFlag(&Widget, UIWidgetFlag_DrawBackground); + ui_eval_result Result = ui_EvaluateWidget(Interface, &Widget, Bounds); + return Result.Clicked; } struct list_item_colors @@ -477,35 +666,19 @@ ui_GetListItemColors(ui_interface* Interface, u32 ListItemIndex) static b32 ui_ListButton(ui_interface* Interface, gs_string Text, rect2 Bounds, u32 ListItemIndex) { - list_item_colors Colors = ui_GetListItemColors(Interface, ListItemIndex); - return ui_Button(Interface, Text, Bounds, Colors.Hover, Colors.Selected, Colors.BGColor); -} - -static b32 -ui_LayoutButton(ui_interface* Interface, ui_layout* Layout, gs_string Text, v4 BGColor, v4 HoverColor, v4 SelectColor) -{ - rect2 ButtonBounds = {0}; - if (!ui_TryReserveElementBounds(Layout, &ButtonBounds)) - { - ButtonBounds = ui_ReserveTextLineBounds(*Interface, Text, Layout); - } - return ui_Button(Interface, Text, ButtonBounds, BGColor, HoverColor, SelectColor); -} - -static b32 -ui_LayoutButton(ui_interface* Interface, ui_layout* Layout, gs_string Text) -{ - v4 BGColor = Interface->Style.ButtonColor_Inactive; - v4 HoverColor = Interface->Style.ButtonColor_Active; - v4 SelectedColor = Interface->Style.ButtonColor_Selected; - return ui_LayoutButton(Interface, Layout, Text, BGColor, HoverColor, SelectedColor); + ui_widget Widget = ui_CreateWidget(Text); + ui_WidgetSetFlag(&Widget, UIWidgetFlag_DrawBackground); + ui_WidgetSetFlag(&Widget, UIWidgetFlag_Clickable); + // TODO(pjs): Reimplement alternating color backgrounds + Widget.Bounds = Bounds; + ui_eval_result Result = ui_EvaluateWidget(Interface, &Widget); + return Result.Clicked; } static b32 ui_LayoutListButton(ui_interface* Interface, ui_layout* Layout, gs_string Text, u32 ListItemIndex) { - list_item_colors Colors = ui_GetListItemColors(Interface, ListItemIndex); - return ui_LayoutButton(Interface, Layout, Text, Colors.Hover, Colors.Selected, Colors.BGColor); + return ui_Button(Interface, Text); } static b32 @@ -520,10 +693,90 @@ ui_LayoutListEntry(ui_interface* Interface, ui_layout* Layout, gs_string Text, u // Punting this till I have a use case InvalidCodePath; } - v4 BGColor = ui_GetListItemBGColor(Interface->Style, Index); - v4 HoverColor = Interface->Style.ListBGHover; - v4 SelectedColor = Interface->Style.ListBGSelected; - return ui_Button(Interface, Text, Bounds, BGColor, HoverColor, SelectedColor); + return ui_Button(Interface, Text, Bounds); +} + +internal bool +ui_EvaluateDropdown(ui_interface* Interface, ui_widget Widget, ui_eval_result EvalResult) +{ + ui_widget_retained_state* State = ui_GetRetainedState(Interface, Widget.Id); + if (!State) { + State = ui_CreateRetainedState(Interface, Widget.Id); + } + + if (EvalResult.Clicked) + { + State->Value = !State->Value; + } + + if (State->Value) + { + ui_layout ParentLayout = Interface->LayoutStack[Interface->LayoutStackCount - 1]; + + r32 SpaceAbove = ParentLayout.Bounds.Max.y - Widget.Bounds.Max.y; + r32 SpaceBelow = Widget.Bounds.Min.y - ParentLayout.Bounds.Min.y; + ui_layout_direction Direction = LayoutDirection_TopDown; + rect2 MenuBounds = {}; + + if (SpaceAbove > SpaceBelow) + { + r32 ParentLayoutMaxY = ParentLayout.Bounds.Max.y; + Direction = LayoutDirection_BottomUp; + MenuBounds = rect2{ + v2{ Widget.Bounds.Min.x, Widget.Bounds.Max.y }, + v2{ Widget.Bounds.Max.x, ParentLayoutMaxY } + }; + } + else + { + r32 ParentLayoutMinY = ParentLayout.Bounds.Min.y; + Direction = LayoutDirection_TopDown; + MenuBounds = rect2{ + v2{ Widget.Bounds.Min.x, ParentLayoutMinY }, + v2{ Widget.Bounds.Max.x, Widget.Bounds.Min.y } + }; + } + + ui_layout Layout = ui_CreateLayout(Interface, MenuBounds, Direction); + Layout.WidgetReference = Widget.Id; + ui_PushLayout(Interface, Layout); + } + + return State->Value; +} + +internal bool +ui_BeginDropdown(ui_interface* Interface, gs_string Text, rect2 Bounds) +{ + ui_widget Widget = ui_CreateWidget(Text); + ui_WidgetSetFlag(&Widget, UIWidgetFlag_Clickable); + ui_WidgetSetFlag(&Widget, UIWidgetFlag_DrawBackground); + ui_eval_result Result = ui_EvaluateWidget(Interface, &Widget, Bounds); + return ui_EvaluateDropdown(Interface, Widget, Result); +} + +internal bool +ui_BeginDropdown(ui_interface* Interface, gs_string Text) +{ + ui_widget Widget = ui_CreateWidget(Text); + ui_WidgetSetFlag(&Widget, UIWidgetFlag_Clickable); + ui_WidgetSetFlag(&Widget, UIWidgetFlag_DrawBackground); + ui_eval_result Result = ui_EvaluateWidget(Interface, &Widget); + return ui_EvaluateDropdown(Interface, Widget, Result); +} + +internal void +ui_EndDropdown(ui_interface* Interface) +{ + ui_layout Layout = Interface->LayoutStack[Interface->LayoutStackCount - 1]; + ui_widget_retained_state* State = ui_GetRetainedState(Interface, Layout.WidgetReference); + if (State) + { + if (State->Value) + { + ui_PopLayout(Interface); + } + } } //