Merge branch 'dev'

This commit is contained in:
PS 2020-11-14 13:49:11 -08:00
commit fea1863166
22 changed files with 1706 additions and 756 deletions

View File

@ -5,7 +5,7 @@
// //
#ifndef FOLDHAUS_COMMAND_DISPATCH_H #ifndef FOLDHAUS_COMMAND_DISPATCH_H
#define FOLDHAUS_INPUT_COMMAND_PROC(name) void name(app_state* State, input_entry Event, mouse_state Mouse, context Context) #define FOLDHAUS_INPUT_COMMAND_PROC(name) void name(app_state* State, input_entry Event, mouse_state Mouse, context Context, panel* Panel)
typedef FOLDHAUS_INPUT_COMMAND_PROC(input_command_proc); typedef FOLDHAUS_INPUT_COMMAND_PROC(input_command_proc);
// NOTE(Peter): Helper function so I don't have to remember the parameters to this define // NOTE(Peter): Helper function so I don't have to remember the parameters to this define
@ -86,12 +86,12 @@ GetCommandIndexInQueue(input_command_queue* Queue, input_command Command, input_
} }
internal input_command_queue internal input_command_queue
InitializeCommandQueue(command_queue_entry* Memory, s32 MemorySize) CommandQueue_Create(gs_memory_arena* Storage, u64 CommandMaxCount)
{ {
input_command_queue Result = {}; input_command_queue Result = {};
Result.Size = MemorySize; Result.Size = CommandMaxCount;
Result.Used = 0; Result.Used = 0;
Result.Commands = Memory; Result.Commands = PushArray(Storage, command_queue_entry, CommandMaxCount);
return Result; return Result;
} }

View File

@ -0,0 +1,181 @@
//
// File: foldhaus_editor.cpp
// Author: Peter Slattery
// Creation Date: 2020-10-24
//
#ifndef FOLDHAUS_EDITOR_CPP
internal void
Editor_HandleInput (app_state* State, rect2 WindowBounds, input_queue InputQueue, mouse_state Mouse, context Context)
{
DEBUG_TRACK_FUNCTION;
b32 MouseInputHandled = HandleMousePanelInteraction(&State->PanelSystem, State->WindowBounds, Mouse, State);
panel* ActivePanel = PanelSystem_GetPanelContainingPoint(&State->PanelSystem, Mouse.Pos);
if (ActivePanel)
{
panel_definition ActiveDef = State->PanelSystem.PanelDefs[ActivePanel->TypeIndex];
input_command_registry ActiveCommands = {};
if (State->Modes.ActiveModesCount > 0)
{
ActiveCommands = State->Modes.ActiveModes[State->Modes.ActiveModesCount - 1].Commands;
}
else if (ActiveDef.InputCommands)
{
ActiveCommands.Commands = ActiveDef.InputCommands;
ActiveCommands.Size = sizeof(*ActiveDef.InputCommands) / sizeof(ActiveDef.InputCommands[0]);
ActiveCommands.Used = ActiveCommands.Size;
}
// Fill up the command queue
for (s32 EventIdx = 0; EventIdx < InputQueue.QueueUsed; EventIdx++)
{
input_entry Event = InputQueue.Entries[EventIdx];
bool IsMouseEvent = (Event.Key == KeyCode_MouseLeftButton ||
Event.Key == KeyCode_MouseMiddleButton ||
Event.Key == KeyCode_MouseRightButton);
if (IsMouseEvent && MouseInputHandled)
{
continue;
}
// NOTE(Peter): These are in the order Down, Up, Held because we want to privalege
// Down and Up over Held. In other words, we don't want to call a Held command on the
// frame when the button was released, even if the command is registered to both events
if (KeyTransitionedDown(Event))
{
FindAndPushExistingCommand(ActiveCommands, Event, Command_Began, &State->CommandQueue);
}
else if (KeyTransitionedUp(Event))
{
FindAndPushExistingCommand(ActiveCommands, Event, Command_Ended, &State->CommandQueue);
}
else if (KeyHeldDown(Event))
{
FindAndPushExistingCommand(ActiveCommands, Event, Command_Held, &State->CommandQueue);
}
}
}
// Execute all commands in CommandQueue
for (s32 CommandIdx = State->CommandQueue.Used - 1; CommandIdx >= 0; CommandIdx--)
{
command_queue_entry* Entry = &State->CommandQueue.Commands[CommandIdx];
if (Entry->Command.Proc)
{
Entry->Command.Proc(State, Entry->Event, Mouse, Context, ActivePanel);
}
else
{
EndCurrentOperationMode(State);
}
}
ClearCommandQueue(&State->CommandQueue);
}
internal void
Editor_Update(app_state* State, context* Context, input_queue InputQueue)
{
Context->Mouse.CursorType = CursorType_Arrow;
State->WindowBounds = Context->WindowBounds;
State->Interface.Mouse = Context->Mouse;
PanelSystem_UpdateLayout(&State->PanelSystem, State->WindowBounds);
Editor_HandleInput(State, State->WindowBounds, InputQueue, Context->Mouse, *Context);
}
internal void
Editor_Render(app_state* State, context* Context, render_command_buffer* RenderBuffer)
{
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++)
{
operation_mode OperationMode = State->Modes.ActiveModes[m];
if (OperationMode.Render != 0)
{
OperationMode.Render(State, RenderBuffer, OperationMode, Context->Mouse, *Context);
}
}
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);
}
#define FOLDHAUS_EDITOR_CPP
#endif // FOLDHAUS_EDITOR_CPP

View File

@ -13,6 +13,8 @@
enum panel_edit_mode enum panel_edit_mode
{ {
PanelEdit_Invalid,
PanelEdit_Modify, PanelEdit_Modify,
PanelEdit_Destroy, PanelEdit_Destroy,
@ -92,7 +94,6 @@ OPERATION_RENDER_PROC(UpdateAndRenderDragPanelBorder)
FOLDHAUS_INPUT_COMMAND_PROC(EndDragPanelBorderOperation) FOLDHAUS_INPUT_COMMAND_PROC(EndDragPanelBorderOperation)
{ {
drag_panel_border_operation_state* OpState = GetCurrentOperationState(State->Modes, drag_panel_border_operation_state); drag_panel_border_operation_state* OpState = GetCurrentOperationState(State->Modes, drag_panel_border_operation_state);
panel* Panel = OpState->Panel;
rect2 PanelBounds = OpState->InitialPanelBounds; rect2 PanelBounds = OpState->InitialPanelBounds;
if (OpState->PanelEditMode == PanelEdit_Modify) if (OpState->PanelEditMode == PanelEdit_Modify)
@ -111,6 +112,7 @@ FOLDHAUS_INPUT_COMMAND_PROC(EndDragPanelBorderOperation)
else else
{ {
Panel->SplitPercent = (NewSplitY - PanelBounds.Min.y) / Rect2Height(PanelBounds); Panel->SplitPercent = (NewSplitY - PanelBounds.Min.y) / Rect2Height(PanelBounds);
Panel_UpdateLayout(Panel, PanelBounds);
} }
} }
else if (Panel->SplitDirection == PanelSplit_Vertical) else if (Panel->SplitDirection == PanelSplit_Vertical)
@ -127,6 +129,7 @@ FOLDHAUS_INPUT_COMMAND_PROC(EndDragPanelBorderOperation)
else else
{ {
Panel->SplitPercent = (NewSplitX - PanelBounds.Min.x) / Rect2Width(PanelBounds); Panel->SplitPercent = (NewSplitX - PanelBounds.Min.x) / Rect2Width(PanelBounds);
Panel_UpdateLayout(Panel, PanelBounds);
} }
} }
} }
@ -220,7 +223,6 @@ OPERATION_RENDER_PROC(UpdateAndRenderSplitPanel)
FOLDHAUS_INPUT_COMMAND_PROC(EndSplitPanelOperation) FOLDHAUS_INPUT_COMMAND_PROC(EndSplitPanelOperation)
{ {
split_panel_operation_state* OpState = GetCurrentOperationState(State->Modes, split_panel_operation_state); split_panel_operation_state* OpState = GetCurrentOperationState(State->Modes, split_panel_operation_state);
panel* Panel = OpState->Panel;
rect2 PanelBounds = OpState->InitialPanelBounds; rect2 PanelBounds = OpState->InitialPanelBounds;
r32 XDistance = Abs(Mouse.Pos.x - Mouse.DownPos.x); r32 XDistance = Abs(Mouse.Pos.x - Mouse.DownPos.x);
@ -239,9 +241,9 @@ FOLDHAUS_INPUT_COMMAND_PROC(EndSplitPanelOperation)
s32 PanelTypeIndex = Panel->TypeIndex; s32 PanelTypeIndex = Panel->TypeIndex;
gs_data PanelStateMemory = Panel->StateMemory; gs_data PanelStateMemory = Panel->StateMemory;
Panel_SetCurrentType(&Panel->Left->Panel, &State->PanelSystem, PanelTypeIndex, PanelStateMemory, State, Context); Panel_SetCurrentType(Panel->Left, &State->PanelSystem, PanelTypeIndex, PanelStateMemory, State, Context);
SetAndInitPanelType(&Panel->Right->Panel, &State->PanelSystem, PanelTypeIndex, State, Context); Panel_SetType(Panel->Right, &State->PanelSystem, PanelTypeIndex, State, Context);
DeactivateCurrentOperationMode(&State->Modes); DeactivateCurrentOperationMode(&State->Modes);
} }
@ -251,12 +253,12 @@ input_command SplitPanelCommands[] = {
}; };
internal void internal void
BeginSplitPanelOperation(panel* Panel, rect2 PanelBounds, mouse_state Mouse, app_state* State) BeginSplitPanelOperation(panel* Panel, mouse_state Mouse, app_state* State)
{ {
operation_mode* SplitPanel = ActivateOperationModeWithCommands(&State->Modes, SplitPanelCommands, UpdateAndRenderSplitPanel); operation_mode* SplitPanel = ActivateOperationModeWithCommands(&State->Modes, SplitPanelCommands, UpdateAndRenderSplitPanel);
split_panel_operation_state* OpState = CreateOperationState(SplitPanel, &State->Modes, split_panel_operation_state); split_panel_operation_state* OpState = CreateOperationState(SplitPanel, &State->Modes, split_panel_operation_state);
OpState->Panel = Panel; OpState->Panel = Panel;
OpState->InitialPanelBounds = PanelBounds; OpState->InitialPanelBounds = Panel->Bounds;
} }
@ -265,61 +267,44 @@ BeginSplitPanelOperation(panel* Panel, rect2 PanelBounds, mouse_state Mouse, app
#define PANEL_EDGE_CLICK_MAX_DISTANCE 6 #define PANEL_EDGE_CLICK_MAX_DISTANCE 6
internal b32 internal b32
HandleMouseDownPanelInteractionOrRecurse(panel* Panel, panel_edit_mode PanelEditMode, rect2 PanelBounds, mouse_state Mouse, app_state* State) HandleMouseDownPanelInteractionOrRecurse(panel* Panel, panel_edit_mode PanelEditMode, mouse_state Mouse, app_state* State)
{ {
b32 HandledMouseInput = false; b32 HandledMouseInput = false;
rect2 PanelSplitButtonBounds = rect2{ PanelBounds.Min, PanelBounds.Min + v2{25, 25} }; // TODO(pjs): this can probably live in panel_with_layout
rect2 PanelSplitButtonBounds = rect2{ Panel->Bounds.Min, Panel->Bounds.Min + v2{25, 25} };
if (Panel->SplitDirection == PanelSplit_NoSplit if (Panel->SplitDirection == PanelSplit_NoSplit
&& PointIsInRect(PanelSplitButtonBounds, Mouse.DownPos)) && PointIsInRect(PanelSplitButtonBounds, Mouse.DownPos))
{ {
BeginSplitPanelOperation(Panel, PanelBounds, Mouse, State); BeginSplitPanelOperation(Panel, Mouse, State);
HandledMouseInput = true; HandledMouseInput = true;
} }
else if (Panel->SplitDirection == PanelSplit_Horizontal) else if (Panel->SplitDirection != PanelSplit_NoSplit)
{ {
r32 SplitY = LerpR32(Panel->SplitPercent, PanelBounds.Min.y, PanelBounds.Max.y); u32 ElementIndex = 0;
r32 ClickDistanceFromSplit = Abs(Mouse.DownPos.y - SplitY); switch(Panel->SplitDirection)
{
case PanelSplit_Vertical: { ElementIndex = 0; } break;
case PanelSplit_Horizontal: { ElementIndex = 1; } break;
InvalidDefaultCase;
}
r32 SplitPosition = LerpR32(Panel->SplitPercent, Panel->Bounds.Min.E[ElementIndex], Panel->Bounds.Max.E[ElementIndex]);
r32 ClickDistanceFromSplit = Abs(Mouse.DownPos.E[ElementIndex] - SplitPosition);
if (ClickDistanceFromSplit < PANEL_EDGE_CLICK_MAX_DISTANCE) if (ClickDistanceFromSplit < PANEL_EDGE_CLICK_MAX_DISTANCE)
{ {
BeginDragPanelBorder(Panel, PanelEditMode, PanelBounds, PanelSplit_Horizontal, Mouse, State); BeginDragPanelBorder(Panel, PanelEditMode, Panel->Bounds, Panel->SplitDirection, Mouse, State);
HandledMouseInput = true;
} }
else else
{ {
rect2 TopPanelBounds = GetTopPanelBounds(Panel, PanelBounds); if (PointIsInRect(Panel->Bottom->Bounds, Mouse.DownPos))
rect2 BottomPanelBounds = GetBottomPanelBounds(Panel, PanelBounds);
if (PointIsInRect(BottomPanelBounds, Mouse.DownPos))
{ {
HandleMouseDownPanelInteractionOrRecurse(&Panel->Bottom->Panel, PanelEditMode, BottomPanelBounds, Mouse, State); HandleMouseDownPanelInteractionOrRecurse(Panel->Bottom, PanelEditMode, Mouse, State);
} }
if (PointIsInRect(TopPanelBounds, Mouse.DownPos)) else if (PointIsInRect(Panel->Top->Bounds, Mouse.DownPos))
{ {
HandleMouseDownPanelInteractionOrRecurse(&Panel->Top->Panel, PanelEditMode, TopPanelBounds, Mouse, State); HandleMouseDownPanelInteractionOrRecurse(Panel->Top, PanelEditMode, Mouse, State);
}
}
}
else if (Panel->SplitDirection == PanelSplit_Vertical)
{
r32 SplitX = LerpR32(Panel->SplitPercent, PanelBounds.Min.x, PanelBounds.Max.x);
r32 ClickDistanceFromSplit = Abs(Mouse.DownPos.x - SplitX);
if (ClickDistanceFromSplit < PANEL_EDGE_CLICK_MAX_DISTANCE)
{
BeginDragPanelBorder(Panel, PanelEditMode, PanelBounds, PanelSplit_Vertical, Mouse, State);
HandledMouseInput = true;
}
else
{
rect2 LeftPanelBounds = GetLeftPanelBounds(Panel, PanelBounds);
rect2 RightPanelBounds = GetRightPanelBounds(Panel, PanelBounds);
if (PointIsInRect(LeftPanelBounds, Mouse.DownPos))
{
HandleMouseDownPanelInteractionOrRecurse(&Panel->Left->Panel, PanelEditMode, LeftPanelBounds, Mouse, State);
}
if (PointIsInRect(RightPanelBounds, Mouse.DownPos))
{
HandleMouseDownPanelInteractionOrRecurse(&Panel->Right->Panel, PanelEditMode, RightPanelBounds, Mouse, State);
} }
} }
} }
@ -332,14 +317,21 @@ HandleMousePanelInteraction(panel_system* PanelSystem, rect2 WindowBounds, mouse
{ {
b32 HandledMouseInput = false; b32 HandledMouseInput = false;
panel* FirstPanel = &PanelSystem->Panels[0].Panel; panel* FirstPanel = PanelSystem->Panels + 0;
panel_edit_mode EditMode = PanelEdit_Invalid;
if (MouseButtonTransitionedDown(Mouse.LeftButtonState)) if (MouseButtonTransitionedDown(Mouse.LeftButtonState))
{ {
HandledMouseInput = HandleMouseDownPanelInteractionOrRecurse(FirstPanel, PanelEdit_Modify, WindowBounds, Mouse, State); EditMode = PanelEdit_Modify;
} }
else if (MouseButtonTransitionedDown(Mouse.RightButtonState)) else if (MouseButtonTransitionedDown(Mouse.RightButtonState))
{ {
HandledMouseInput = HandleMouseDownPanelInteractionOrRecurse(FirstPanel, PanelEdit_Destroy, WindowBounds, Mouse, State); EditMode = PanelEdit_Destroy;
}
if (EditMode != PanelEdit_Invalid)
{
HandledMouseInput = HandleMouseDownPanelInteractionOrRecurse(FirstPanel, EditMode, Mouse, State);
} }
return HandledMouseInput; return HandledMouseInput;
@ -394,50 +386,27 @@ DrawPanelFooter(panel* Panel, render_command_buffer* RenderBuffer, rect2 FooterB
rect2 PanelSelectBtnBounds = MakeRect2MinDim(FooterBounds.Min + v2{30, 1}, v2{100, 23}); 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) * GlobalPanelDefsCount)
},
};
if (MouseButtonTransitionedDown(Mouse.LeftButtonState)
&& !PointIsInRect(MenuBounds, Mouse.DownPos))
{
Panel->PanelSelectionMenuOpen = false;
}
for (s32 i = 0; i < GlobalPanelDefsCount; i++) for (s32 i = 0; i < GlobalPanelDefsCount; i++)
{ {
panel_definition Def = GlobalPanelDefs[i]; panel_definition Def = State->PanelSystem.PanelDefs[i];
gs_string DefName = MakeString(Def.PanelName, Def.PanelNameLength); gs_string DefName = MakeString(Def.PanelName, Def.PanelNameLength);
if (ui_Button(&State->Interface, DefName, ButtonBounds)) if (ui_Button(&State->Interface, DefName))
{ {
SetAndInitPanelType(Panel, &State->PanelSystem, i, State, Context); Panel_SetType(Panel, &State->PanelSystem, i, State, Context);
Panel->PanelSelectionMenuOpen = false;
} }
ButtonBounds = Rect2TranslateY(ButtonBounds, Rect2Height(ButtonBounds));
} }
} }
ui_EndDropdown(&State->Interface);
if (ui_Button(&State->Interface, MakeString("Select"), PanelSelectBtnBounds))
{
Panel->PanelSelectionMenuOpen = !Panel->PanelSelectionMenuOpen;
}
} }
internal void internal void
RenderPanel(panel* Panel, rect2 PanelBounds, rect2 WindowBounds, render_command_buffer* RenderBuffer, app_state* State, context Context, mouse_state Mouse) RenderPanel(panel* Panel, rect2 PanelBounds, rect2 WindowBounds, render_command_buffer* RenderBuffer, app_state* State, context Context, mouse_state Mouse)
{ {
s32 PanelType = Panel->TypeIndex; u32 PanelType = Panel->TypeIndex;
Assert(PanelType >= 0); Assert(PanelType >= 0);
Assert(PanelType < State->PanelSystem.PanelDefsCount);
rect2 FooterBounds = rect2{ rect2 FooterBounds = rect2{
PanelBounds.Min, PanelBounds.Min,
@ -448,7 +417,7 @@ RenderPanel(panel* Panel, rect2 PanelBounds, rect2 WindowBounds, render_command_
PanelBounds.Max, PanelBounds.Max,
}; };
panel_definition Definition = GlobalPanelDefs[PanelType]; panel_definition Definition = State->PanelSystem.PanelDefs[PanelType];
Definition.Render(Panel, PanelViewBounds, RenderBuffer, State, Context); Definition.Render(Panel, PanelViewBounds, RenderBuffer, State, Context);
PushRenderOrthographic(RenderBuffer, WindowBounds); PushRenderOrthographic(RenderBuffer, WindowBounds);
@ -456,21 +425,37 @@ RenderPanel(panel* Panel, rect2 PanelBounds, rect2 WindowBounds, render_command_
} }
internal void internal void
DrawAllPanels(panel_layout PanelLayout, render_command_buffer* RenderBuffer, mouse_state* Mouse, app_state* State, context Context) DrawPanelRecursive(panel* Panel, render_command_buffer* RenderBuffer, mouse_state* Mouse, app_state* State, context Context)
{ {
for (u32 i = 0; i < PanelLayout.PanelsCount; i++) switch (Panel->SplitDirection)
{ {
panel_with_layout PanelWithLayout = PanelLayout.Panels[i]; case PanelSplit_Horizontal:
panel* Panel = PanelWithLayout.Panel; case PanelSplit_Vertical:
rect2 PanelBounds = PanelWithLayout.Bounds; {
DrawPanelRecursive(Panel->Left, RenderBuffer, Mouse, State, Context);
DrawPanelRecursive(Panel->Right, RenderBuffer, Mouse, State, Context);
}break;
RenderPanel(Panel, PanelBounds, State->WindowBounds, RenderBuffer, State, Context, *Mouse); case PanelSplit_NoSplit:
v4 BorderColor = v4{0, 0, 0, 1}; {
panel* OverridePanel = Panel_GetModalOverride(Panel);
RenderPanel(OverridePanel, OverridePanel->Bounds, State->WindowBounds, RenderBuffer, State, Context, *Mouse);
v4 BorderColor = v4{0, 0, 0, 1};
PushRenderOrthographic(RenderBuffer, State->WindowBounds);
DrawPanelBorder(*OverridePanel, OverridePanel->Bounds.Min, OverridePanel->Bounds.Max, BorderColor, Mouse, RenderBuffer);
}break;
PushRenderOrthographic(RenderBuffer, State->WindowBounds); InvalidDefaultCase;
DrawPanelBorder(*Panel, PanelBounds.Min, PanelBounds.Max, BorderColor, Mouse, RenderBuffer);
} }
} }
internal void
DrawAllPanels(panel_system System, render_command_buffer* RenderBuffer, mouse_state* Mouse, app_state* State, context Context)
{
panel* PanelAt = System.Panels + 0;
DrawPanelRecursive(PanelAt, RenderBuffer, Mouse, State, Context);
}
#define FOLDHAUS_INTERFACE_CPP #define FOLDHAUS_INTERFACE_CPP
#endif // FOLDHAUS_INTERFACE_CPP #endif // FOLDHAUS_INTERFACE_CPP

View File

@ -18,7 +18,6 @@ enum panel_split_direction
PanelSplit_Count, PanelSplit_Count,
}; };
typedef struct panel_entry panel_entry;
typedef struct panel panel; typedef struct panel panel;
#define PANEL_MODAL_OVERRIDE_CALLBACK(name) void name(panel* ReturningFrom, app_state* State, context Context) #define PANEL_MODAL_OVERRIDE_CALLBACK(name) void name(panel* ReturningFrom, app_state* State, context Context)
@ -29,36 +28,29 @@ struct panel
s32 TypeIndex; s32 TypeIndex;
gs_data StateMemory; gs_data StateMemory;
panel_entry* ModalOverride; panel* ModalOverride;
panel* IsModalOverrideFor; // TODO(pjs): I don't like that this is panel* but ModalOverride is panel_entry* panel* IsModalOverrideFor;
panel_modal_override_callback* ModalOverrideCB; panel_modal_override_callback* ModalOverrideCB;
rect2 Bounds;
panel_split_direction SplitDirection; panel_split_direction SplitDirection;
r32 SplitPercent; r32 SplitPercent;
// TODO(Peter): This REALLY doesn't want to live here panel* Parent;
// Probably belongs in a more generalized PanelInterfaceState or something
b32 PanelSelectionMenuOpen;
union{ union{
panel_entry* Left; panel* Left;
panel_entry* Top; panel* Top;
}; };
union{ union{
panel_entry* Right; panel* Right;
panel_entry* Bottom; panel* Bottom;
}; };
}; };
struct free_panel struct free_panel
{ {
panel_entry* Next; free_panel* Next;
};
struct panel_entry
{
panel Panel;
free_panel Free;
}; };
#define PANEL_INIT_PROC(name) void name(panel* Panel, app_state* State, context Context) #define PANEL_INIT_PROC(name) void name(panel* Panel, app_state* State, context Context)
@ -88,26 +80,10 @@ struct panel_system
panel_definition* PanelDefs; panel_definition* PanelDefs;
u32 PanelDefsCount; u32 PanelDefsCount;
panel_entry Panels[PANELS_MAX]; panel* Panels;
u32 PanelsUsed; u32 PanelsUsed;
panel_entry FreeList; free_panel* FreeList;
};
// NOTE(Peter): This representation is used to let external code render and interact
// with panels. It shouldn't be stored across frame boundaries as the pointers to
// Panel's are liable to change.
struct panel_with_layout
{
panel* Panel;
rect2 Bounds;
};
struct panel_layout
{
panel_with_layout* Panels;
u32 PanelsCount;
u32 PanelsMax;
}; };
///////////////////////////////// /////////////////////////////////
@ -117,21 +93,24 @@ struct panel_layout
///////////////////////////////// /////////////////////////////////
internal void internal void
InitializePanelSystem(panel_system* PanelSystem, panel_definition* PanelDefs, u32 PanelDefsCount) PanelSystem_Init(panel_system* PanelSystem, panel_definition* PanelDefs, u32 PanelDefsCount, gs_memory_arena* Storage)
{ {
PanelSystem->FreeList.Free.Next = &PanelSystem->FreeList; PanelSystem->FreeList = 0;
PanelSystem->PanelDefs = PanelDefs; PanelSystem->PanelDefs = PanelDefs;
PanelSystem->PanelDefsCount = PanelDefsCount; PanelSystem->PanelDefsCount = PanelDefsCount;
PanelSystem->Panels = PushArray(Storage, panel, PANELS_MAX);
} }
internal panel_entry* internal panel*
TakeNewPanelEntry(panel_system* PanelSystem) PanelSystem_TakePanel(panel_system* PanelSystem)
{ {
panel_entry* FreeEntry = 0; panel* FreeEntry = 0;
if (PanelSystem->FreeList.Free.Next != &PanelSystem->FreeList) if (PanelSystem->FreeList != 0)
{ {
FreeEntry = PanelSystem->FreeList.Free.Next; free_panel* FreePanel = PanelSystem->FreeList;
PanelSystem->FreeList.Free.Next = FreeEntry->Free.Next; PanelSystem->FreeList = FreePanel->Next;
FreeEntry = (panel*)FreePanel;
} }
else else
{ {
@ -142,43 +121,24 @@ TakeNewPanelEntry(panel_system* PanelSystem)
} }
internal void internal void
FreePanelEntry(panel_entry* Entry, panel_system* PanelSystem) PanelSystem_FreePanel(panel* Panel, panel_system* PanelSystem)
{ {
Assert(Entry >= PanelSystem->Panels && Entry <= PanelSystem->Panels + PANELS_MAX); Assert(Panel >= PanelSystem->Panels && Panel <= PanelSystem->Panels + PANELS_MAX);
Entry->Panel = {0};
Entry->Free.Next = PanelSystem->FreeList.Free.Next; free_panel* FreeEntry = (free_panel*)Panel;
PanelSystem->FreeList.Free.Next = Entry; FreeEntry->Next = PanelSystem->FreeList;
PanelSystem->FreeList = FreeEntry;
} }
internal void internal void
FreePanelEntryRecursive(panel_entry* Entry, panel_system* PanelSystem) PanelSystem_FreePanelRecursive(panel* Panel, panel_system* PanelSystem)
{ {
if (Entry->Panel.SplitDirection != PanelSplit_NoSplit) if (Panel->SplitDirection != PanelSplit_NoSplit)
{ {
FreePanelEntryRecursive(Entry->Panel.Left, PanelSystem); PanelSystem_FreePanelRecursive(Panel->Left, PanelSystem);
FreePanelEntryRecursive(Entry->Panel.Right, PanelSystem); PanelSystem_FreePanelRecursive(Panel->Right, PanelSystem);
} }
FreePanelEntry(Entry, PanelSystem); PanelSystem_FreePanel(Panel, PanelSystem);
}
internal void
FreePanelAtIndex(s32 Index, panel_system* PanelSystem)
{
Assert(Index > 0 && Index < (s32)PanelSystem->PanelsUsed);
panel_entry* EntryToFree = PanelSystem->Panels + Index;
EntryToFree->Free.Next = PanelSystem->FreeList.Free.Next;
PanelSystem->FreeList.Free.Next = EntryToFree;
}
internal panel_entry*
Panel_GetModalOverride(panel_entry* PanelEntry)
{
panel_entry* Result = PanelEntry;
if (PanelEntry->Panel.ModalOverride != 0)
{
Result = Panel_GetModalOverride(PanelEntry->Panel.ModalOverride);
}
return Result;
} }
internal panel* internal panel*
@ -187,24 +147,25 @@ Panel_GetModalOverride(panel* Panel)
panel* Result = Panel; panel* Result = Panel;
if (Panel->ModalOverride != 0) if (Panel->ModalOverride != 0)
{ {
Result = &Panel_GetModalOverride(Panel->ModalOverride)->Panel; Result = Panel_GetModalOverride(Panel->ModalOverride);
} }
return Result; return Result;
} }
internal void internal void
Panel_PushModalOverride(panel* Root, panel_entry* Override, panel_modal_override_callback* Callback) Panel_PushModalOverride(panel* Root, panel* Override, panel_modal_override_callback* Callback)
{ {
Root->ModalOverride = Override; Root->ModalOverride = Override;
Root->ModalOverrideCB = Callback; Root->ModalOverrideCB = Callback;
Override->Panel.IsModalOverrideFor = Root; Override->IsModalOverrideFor = Root;
Override->Bounds = Root->Bounds;
} }
internal void internal void
Panel_PopModalOverride(panel* Parent, panel_system* System) Panel_PopModalOverride(panel* Parent, panel_system* System)
{ {
// TODO(pjs): Free the overrided panel // TODO(pjs): Free the overrided panel
FreePanelEntry(Parent->ModalOverride, System); PanelSystem_FreePanel(Parent->ModalOverride, System);
Parent->ModalOverride = 0; Parent->ModalOverride = 0;
} }
@ -223,7 +184,7 @@ Panel_SetCurrentType(panel* Panel, panel_system* System, s32 NewPanelType, gs_da
} }
internal void internal void
SetAndInitPanelType(panel* Panel, panel_system* System, s32 NewPanelTypeIndex, app_state* State, context Context) Panel_SetType(panel* Panel, panel_system* System, s32 NewPanelTypeIndex, app_state* State, context Context)
{ {
gs_data EmptyStateData = {0}; gs_data EmptyStateData = {0};
Panel_SetCurrentType(Panel, System, NewPanelTypeIndex, EmptyStateData, State, Context); Panel_SetCurrentType(Panel, System, NewPanelTypeIndex, EmptyStateData, State, Context);
@ -239,12 +200,12 @@ Panel_GetStateMemory(panel* Panel, u64 Size)
return Result; return Result;
} }
internal panel_entry* internal panel*
PanelSystem_PushPanel(panel_system* PanelSystem, s32 PanelTypeIndex, app_state* State, context Context) PanelSystem_PushPanel(panel_system* PanelSystem, s32 PanelTypeIndex, app_state* State, context Context)
{ {
panel_entry* PanelEntry = TakeNewPanelEntry(PanelSystem); panel* Panel = PanelSystem_TakePanel(PanelSystem);
SetAndInitPanelType(&PanelEntry->Panel, PanelSystem, PanelTypeIndex, State, Context); Panel_SetType(Panel, PanelSystem, PanelTypeIndex, State, Context);
return PanelEntry; return Panel;
} }
internal void internal void
@ -257,11 +218,14 @@ SplitPanel(panel* Parent, r32 Percent, panel_split_direction SplitDirection, pan
s32 ParentTypeIndex = Parent->TypeIndex; s32 ParentTypeIndex = Parent->TypeIndex;
gs_data ParentStateMemory = Parent->StateMemory; gs_data ParentStateMemory = Parent->StateMemory;
Parent->Left = TakeNewPanelEntry(PanelSystem);
Panel_SetCurrentType(&Parent->Left->Panel, PanelSystem, ParentTypeIndex, ParentStateMemory, State, Context);
Parent->Right = TakeNewPanelEntry(PanelSystem); Parent->Left = PanelSystem_TakePanel(PanelSystem);
Panel_SetCurrentType(&Parent->Right->Panel, PanelSystem, ParentTypeIndex, ParentStateMemory, State, Context); 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;
} }
} }
@ -278,17 +242,17 @@ SplitPanelHorizontally(panel* Parent, r32 Percent, panel_system* PanelSystem, ap
} }
internal void internal void
ConsolidatePanelsKeepOne(panel* Parent, panel_entry* PanelEntryToKeep, panel_system* PanelSystem) ConsolidatePanelsKeepOne(panel* Parent, panel* PanelToKeep, panel_system* PanelSystem)
{ {
panel_entry* LeftChild = Parent->Left; panel* LeftChild = Parent->Left;
panel_entry* RightChild = Parent->Right; panel* RightChild = Parent->Right;
panel_entry* PanelEntryToDestroy = PanelEntryToKeep == LeftChild ? RightChild : LeftChild; panel* PanelToDestroy = PanelToKeep == LeftChild ? RightChild : LeftChild;
*Parent = PanelEntryToKeep->Panel; *Parent = *PanelToKeep;
FreePanelEntry(PanelEntryToKeep, PanelSystem); PanelSystem_FreePanel(PanelToKeep, PanelSystem);
FreePanelEntryRecursive(PanelEntryToDestroy, PanelSystem); PanelSystem_FreePanelRecursive(PanelToDestroy, PanelSystem);
} }
///////////////////////////////// /////////////////////////////////
@ -297,6 +261,54 @@ ConsolidatePanelsKeepOne(panel* Parent, panel_entry* PanelEntryToKeep, panel_sys
// //
///////////////////////////////// /////////////////////////////////
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 internal rect2
GetTopPanelBounds(panel* Panel, rect2 PanelBounds) GetTopPanelBounds(panel* Panel, rect2 PanelBounds)
{ {
@ -346,103 +358,85 @@ GetLeftPanelBounds(panel* Panel, rect2 PanelBounds)
} }
internal void internal void
LayoutPanel(panel* Panel, rect2 PanelBounds, panel_layout* Layout) Panel_UpdateLayout(panel* Panel, rect2 Bounds)
{ {
if (Panel->SplitDirection == PanelSplit_NoSplit) Panel->Bounds = Bounds;
if (Panel->SplitDirection != PanelSplit_NoSplit)
{ {
panel_with_layout* WithLayout = Layout->Panels + Layout->PanelsCount++; rect2 LeftOrTopBounds = {};
WithLayout->Panel = Panel_GetModalOverride(Panel); rect2 RightOrBottomBounds = {};
WithLayout->Bounds = PanelBounds; switch (Panel->SplitDirection)
} {
else if (Panel->SplitDirection == PanelSplit_Horizontal) case PanelSplit_Horizontal:
{ {
rect2 TopPanelBounds = GetTopPanelBounds(Panel, PanelBounds); LeftOrTopBounds = GetTopPanelBounds(Panel);
rect2 BottomPanelBounds = GetBottomPanelBounds(Panel, PanelBounds); RightOrBottomBounds = GetBottomPanelBounds(Panel);
} break;
case PanelSplit_Vertical:
{
LeftOrTopBounds = GetLeftPanelBounds(Panel);
RightOrBottomBounds = GetRightPanelBounds(Panel);
} break;
InvalidDefaultCase;
}
panel* TopPanel = Panel_GetModalOverride(&Panel->Top->Panel); Panel_UpdateLayout(Panel->Left, LeftOrTopBounds);
panel* BottomPanel = Panel_GetModalOverride(&Panel->Bottom->Panel); Panel_UpdateLayout(Panel->Right, RightOrBottomBounds);
LayoutPanel(&Panel->Top->Panel, TopPanelBounds, Layout);
LayoutPanel(&Panel->Bottom->Panel, BottomPanelBounds, Layout);
}
else if (Panel->SplitDirection == PanelSplit_Vertical)
{
rect2 LeftPanelBounds = GetLeftPanelBounds(Panel, PanelBounds);
rect2 RightPanelBounds = GetRightPanelBounds(Panel, PanelBounds);
panel* LeftPanel = Panel_GetModalOverride(&Panel->Top->Panel);
panel* RightPanel = Panel_GetModalOverride(&Panel->Bottom->Panel);
LayoutPanel(&Panel->Left->Panel, LeftPanelBounds, Layout);
LayoutPanel(&Panel->Right->Panel, RightPanelBounds, Layout);
} }
} }
internal panel_layout internal void
GetPanelLayout(panel_system* System, rect2 WindowBounds, gs_memory_arena* Storage) PanelSystem_UpdateLayout(panel_system* System, rect2 WindowBounds)
{ {
panel_layout Result = {}; panel* Root = System->Panels;
Result.PanelsMax = System->PanelsUsed; Panel_UpdateLayout(Root, WindowBounds);
Result.Panels = PushArray(Storage, panel_with_layout, Result.PanelsMax);
LayoutPanel(&System->Panels[0].Panel, WindowBounds, &Result);
return Result;
} }
internal panel_with_layout internal panel*
GetPanelContainingPoint(v2 Point, panel* Panel, rect2 PanelBounds) GetPanelContainingPoint(panel* Panel, v2 Point)
{ {
panel_with_layout Result = {0}; panel* Result = 0;
if (Panel->SplitDirection == PanelSplit_NoSplit) if (PointIsInRect(Panel->Bounds, Point))
{ {
Result.Panel = Panel; switch (Panel->SplitDirection)
Result.Bounds = PanelBounds;
}
else if (Panel->SplitDirection == PanelSplit_Horizontal)
{
rect2 TopPanelBounds = GetTopPanelBounds(Panel, PanelBounds);
rect2 BottomPanelBounds = GetBottomPanelBounds(Panel, PanelBounds);
if (PointIsInRect(TopPanelBounds, Point))
{ {
Result = GetPanelContainingPoint(Point, &Panel->Top->Panel, TopPanelBounds); case PanelSplit_NoSplit:
} {
else if (PointIsInRect(BottomPanelBounds, Point)) Result = Panel;
{ }break;
Result = GetPanelContainingPoint(Point, &Panel->Bottom->Panel, BottomPanelBounds);
} case PanelSplit_Vertical:
} case PanelSplit_Horizontal:
else if (Panel->SplitDirection == PanelSplit_Vertical) {
{ if (PointIsInRect(Panel->Left->Bounds, Point))
rect2 LeftPanelBounds = GetLeftPanelBounds(Panel, PanelBounds); {
rect2 RightPanelBounds = GetRightPanelBounds(Panel, PanelBounds); Result = GetPanelContainingPoint(Panel->Left, Point);
}
if (PointIsInRect(LeftPanelBounds, Point)) else if (PointIsInRect(Panel->Right->Bounds, Point))
{ {
Result = GetPanelContainingPoint(Point, &Panel->Left->Panel, LeftPanelBounds); Result = GetPanelContainingPoint(Panel->Right, Point);
} }
else if (PointIsInRect(RightPanelBounds, Point)) }break;
{
Result = GetPanelContainingPoint(Point, &Panel->Right->Panel, RightPanelBounds); InvalidDefaultCase;
} }
} }
return Result; return Result;
} }
internal panel_with_layout internal panel*
GetPanelContainingPoint(v2 Point, panel_system* PanelSystem, rect2 WindowBounds) PanelSystem_GetPanelContainingPoint(panel_system* System, v2 Point)
{ {
panel_with_layout Result = {0}; panel* Result = 0;
if (PanelSystem->PanelsUsed > 0) panel* Root = System->Panels;
{ Result = GetPanelContainingPoint(Root, Point);
Result = GetPanelContainingPoint(Point, &PanelSystem->Panels[0].Panel, WindowBounds);
}
return Result; return Result;
} }
#define FOLDHAUS_PANEL_H #define FOLDHAUS_PANEL_H
#endif // FOLDHAUS_PANEL_H #endif // FOLDHAUS_PANEL_H

View File

@ -6,13 +6,13 @@
#ifndef FOLDHAUS_PANEL_ANIMATION_TIMELINE_H #ifndef FOLDHAUS_PANEL_ANIMATION_TIMELINE_H
// Colors // Colors
global v4 TimeSliderColor = GreenV4; //v4{.36f, .52f, .78f, 1.f}; global v4 TimeSliderColor = v4{.36f, .52f, .78f, 1.f};
// //
struct animation_timeline_state struct animation_timeline_state
{ {
frame_range VisibleRange; frame_range VisibleRange;
handle SelectedAnimationBlockHandle; handle SelectedBlockHandle;
u32 SelectedAnimationLayer; u32 SelectedAnimationLayer;
}; };
@ -43,27 +43,17 @@ AddAnimationBlockAtCurrentTime (u32 AnimationProcHandle, u32 LayerHandle, animat
return AnimHandle; return AnimHandle;
} }
internal void
SelectAnimationBlock(handle BlockHandle, app_state* State)
{
State->SelectedAnimationBlockHandle = BlockHandle;
}
internal void
DeselectCurrentAnimationBlock(app_state* State)
{
State->SelectedAnimationBlockHandle = {};
}
FOLDHAUS_INPUT_COMMAND_PROC(DeleteAnimationBlockCommand) FOLDHAUS_INPUT_COMMAND_PROC(DeleteAnimationBlockCommand)
{ {
handle SelectedAnimHandle = State->SelectedAnimationBlockHandle; animation_timeline_state* PanelState = Panel_GetStateStruct(Panel, animation_timeline_state);
handle SelectedBlockHandle = PanelState->SelectedBlockHandle;
animation* ActiveAnim = AnimationSystem_GetActiveAnimation(&State->AnimationSystem); animation* ActiveAnim = AnimationSystem_GetActiveAnimation(&State->AnimationSystem);
if(SelectedAnimHandle.Index < ActiveAnim->Blocks_.Count && if(SelectedBlockHandle.Index < ActiveAnim->Blocks_.Count &&
ActiveAnim->Blocks_.Generations[SelectedAnimHandle.Index] == SelectedAnimHandle.Generation) ActiveAnim->Blocks_.Generations[SelectedBlockHandle.Index] == SelectedBlockHandle.Generation)
{ {
Animation_RemoveBlock(ActiveAnim, State->SelectedAnimationBlockHandle); Animation_RemoveBlock(ActiveAnim, PanelState->SelectedBlockHandle);
State->SelectedAnimationBlockHandle = {0}; PanelState->SelectedBlockHandle = {0};
// TODO(pjs): Introduce an animation_block_selection in this file // TODO(pjs): Introduce an animation_block_selection in this file
// it should have a handle to the animation, block, and a HasSelection flag // it should have a handle to the animation, block, and a HasSelection flag
// as it is now, you kind of always have the first block selected // as it is now, you kind of always have the first block selected
@ -119,9 +109,10 @@ StartDragTimeMarker(rect2 TimelineBounds, frame_range VisibleFrames, app_state*
#define CLICK_ANIMATION_BLOCK_EDGE_MAX_SCREEN_DISTANCE 10 #define CLICK_ANIMATION_BLOCK_EDGE_MAX_SCREEN_DISTANCE 10
OPERATION_STATE_DEF(drag_animation_clip_state) OPERATION_STATE_DEF(drag_animation_block_state)
{ {
rect2 TimelineBounds; rect2 TimelineBounds;
handle BlockHandle;
frame_range VisibleRange; frame_range VisibleRange;
frame_range ClipRange; frame_range ClipRange;
}; };
@ -138,9 +129,9 @@ AttemptToSnapPosition(u32 SnappingFrame, u32 SnapToFrame)
return Result; return Result;
} }
OPERATION_RENDER_PROC(UpdateDragAnimationClip) OPERATION_RENDER_PROC(UpdateDragAnimationBlock)
{ {
drag_animation_clip_state* OpState = (drag_animation_clip_state*)Operation.OpStateMemory; drag_animation_block_state* OpState = (drag_animation_block_state*)Operation.OpStateMemory;
animation* ActiveAnim = AnimationSystem_GetActiveAnimation(&State->AnimationSystem); animation* ActiveAnim = AnimationSystem_GetActiveAnimation(&State->AnimationSystem);
@ -158,10 +149,10 @@ OPERATION_RENDER_PROC(UpdateDragAnimationClip)
u32 FrameAtMouseX = GetFrameFromPointInAnimationPanel(Mouse.Pos, OpState->TimelineBounds, OpState->VisibleRange); u32 FrameAtMouseX = GetFrameFromPointInAnimationPanel(Mouse.Pos, OpState->TimelineBounds, OpState->VisibleRange);
s32 FrameOffset = (s32)FrameAtMouseX - (s32)FrameAtMouseDownX; s32 FrameOffset = (s32)FrameAtMouseX - (s32)FrameAtMouseDownX;
animation_block* AnimationBlock = Animation_GetBlockFromHandle(ActiveAnim, State->SelectedAnimationBlockHandle); animation_block* AnimationBlock = Animation_GetBlockFromHandle(ActiveAnim, OpState->BlockHandle);
if (!AnimationBlock) if (!AnimationBlock)
{ {
EndCurrentOperationMode(State, {}, Mouse, Context); EndCurrentOperationMode(State);
return; return;
} }
@ -237,22 +228,23 @@ OPERATION_RENDER_PROC(UpdateDragAnimationClip)
AnimationBlock->Range.Max = (u32)Clamp(PlayableStartFrame, (s32)AnimationBlock->Range.Max, PlayableEndFrame); AnimationBlock->Range.Max = (u32)Clamp(PlayableStartFrame, (s32)AnimationBlock->Range.Max, PlayableEndFrame);
} }
input_command DragAnimationClipCommands [] = { input_command DragAnimationBlockCommands [] = {
{ KeyCode_MouseLeftButton, KeyCode_Invalid, Command_Ended, EndCurrentOperationMode }, { KeyCode_MouseLeftButton, KeyCode_Invalid, Command_Ended, 0 },
}; };
internal void internal void
SelectAndBeginDragAnimationBlock(handle BlockHandle, frame_range VisibleRange, rect2 TimelineBounds, app_state* State) SelectAndBeginDragAnimationBlock(animation_timeline_state* TimelineState, handle BlockHandle, frame_range VisibleRange, rect2 TimelineBounds, app_state* State)
{ {
SelectAnimationBlock(BlockHandle, State); TimelineState->SelectedBlockHandle = BlockHandle;
animation* ActiveAnim = AnimationSystem_GetActiveAnimation(&State->AnimationSystem); animation* ActiveAnim = AnimationSystem_GetActiveAnimation(&State->AnimationSystem);
operation_mode* DragAnimationClipMode = ActivateOperationModeWithCommands(&State->Modes, DragAnimationClipCommands, UpdateDragAnimationClip); operation_mode* DragAnimationBlockMode = ActivateOperationModeWithCommands(&State->Modes, DragAnimationBlockCommands, UpdateDragAnimationBlock);
drag_animation_clip_state* OpState = CreateOperationState(DragAnimationClipMode, drag_animation_block_state* OpState = CreateOperationState(DragAnimationBlockMode,
&State->Modes, &State->Modes,
drag_animation_clip_state); drag_animation_block_state);
OpState->TimelineBounds = TimelineBounds; OpState->TimelineBounds = TimelineBounds;
OpState->BlockHandle = BlockHandle;
OpState->VisibleRange = VisibleRange; OpState->VisibleRange = VisibleRange;
animation_block* SelectedBlock = Animation_GetBlockFromHandle(ActiveAnim, BlockHandle); animation_block* SelectedBlock = Animation_GetBlockFromHandle(ActiveAnim, BlockHandle);
@ -262,14 +254,15 @@ SelectAndBeginDragAnimationBlock(handle BlockHandle, frame_range VisibleRange, r
FOLDHAUS_INPUT_COMMAND_PROC(AddAnimationBlockCommand) FOLDHAUS_INPUT_COMMAND_PROC(AddAnimationBlockCommand)
{ {
animation_timeline_state* TimelineState = Panel_GetStateStruct(Panel, animation_timeline_state);
animation* ActiveAnim = AnimationSystem_GetActiveAnimation(&State->AnimationSystem); animation* ActiveAnim = AnimationSystem_GetActiveAnimation(&State->AnimationSystem);
panel_with_layout ActivePanel = GetPanelContainingPoint(Mouse.Pos, &State->PanelSystem, State->WindowBounds);
frame_range Range = ActiveAnim->PlayableRange; frame_range Range = ActiveAnim->PlayableRange;
u32 MouseDownFrame = GetFrameFromPointInAnimationPanel(Mouse.Pos, ActivePanel.Bounds, Range); u32 MouseDownFrame = GetFrameFromPointInAnimationPanel(Mouse.Pos, Panel->Bounds, Range);
handle NewBlockHandle = Animation_AddBlock(ActiveAnim, MouseDownFrame, MouseDownFrame + SecondsToFrames(3, State->AnimationSystem), 4, State->SelectedAnimationLayer); handle NewBlockHandle = Animation_AddBlock(ActiveAnim, MouseDownFrame, MouseDownFrame + SecondsToFrames(3, State->AnimationSystem), 4, TimelineState->SelectedAnimationLayer);
SelectAnimationBlock(NewBlockHandle, State); TimelineState->SelectedBlockHandle = NewBlockHandle;
} }
input_command AnimationTimeline_Commands[] = { input_command AnimationTimeline_Commands[] = {
@ -310,6 +303,8 @@ DrawFrameBar (animation_system* AnimationSystem, ui_interface Interface, frame_r
r32 BarWidth = Rect2Width(BarBounds); r32 BarWidth = Rect2Width(BarBounds);
// Mouse clicked inside frame nubmer bar -> change current frame on timeline // Mouse clicked inside frame nubmer bar -> change current frame on timeline
// TODO(pjs): both of these functions can get wrapped in a MouseClickedRect
// and an alternate MouseIsDraggingRect
if (MouseButtonTransitionedDown(Interface.Mouse.LeftButtonState) && if (MouseButtonTransitionedDown(Interface.Mouse.LeftButtonState) &&
PointIsInRect(BarBounds, Interface.Mouse.DownPos)) PointIsInRect(BarBounds, Interface.Mouse.DownPos))
{ {
@ -484,6 +479,8 @@ DrawAnimationBlock (animation_block AnimationBlock, v4 BlockColor, frame_range V
PushRenderQuad2D(RenderBuffer, BlockBounds.Min, BlockBounds.Max, BlockColor); PushRenderQuad2D(RenderBuffer, BlockBounds.Min, BlockBounds.Max, BlockColor);
PushRenderBoundingBox2D(RenderBuffer, BlockBounds.Min, BlockBounds.Max, 1, WhiteV4); PushRenderBoundingBox2D(RenderBuffer, BlockBounds.Min, BlockBounds.Max, 1, WhiteV4);
// TODO(pjs): If mouse is on one of the border hot spots, render an off colored square to signal the region is hot
return BlockBounds; return BlockBounds;
} }
@ -505,7 +502,7 @@ DrawAnimationTimeline (animation_system* AnimationSystem, animation_timeline_sta
RectHSplitAtDistanceFromTop(TimelineBounds, 32, &TimelineFrameBarBounds, &TimelineBounds); RectHSplitAtDistanceFromTop(TimelineBounds, 32, &TimelineFrameBarBounds, &TimelineBounds);
RectHSplitAtDistanceFromBottom(TimelineBounds, 24, &TimelineBlockDisplayBounds, &TimelineRangeBarBounds); RectHSplitAtDistanceFromBottom(TimelineBounds, 24, &TimelineBlockDisplayBounds, &TimelineRangeBarBounds);
DrawLayerMenu(AnimationSystem, CurrAnimation, *Interface, LayerMenuBounds, &State->SelectedAnimationLayer); DrawLayerMenu(AnimationSystem, CurrAnimation, *Interface, LayerMenuBounds, &TimelineState->SelectedAnimationLayer);
frame_range AdjustedViewRange = DrawTimelineRangeBar(AnimationSystem, CurrAnimation, TimelineState, *Interface, TimelineRangeBarBounds); frame_range AdjustedViewRange = DrawTimelineRangeBar(AnimationSystem, CurrAnimation, TimelineState, *Interface, TimelineRangeBarBounds);
@ -546,7 +543,7 @@ DrawAnimationTimeline (animation_system* AnimationSystem, animation_timeline_sta
if (MouseDownAndNotHandled && Handle_IsValid(DragBlockHandle)) if (MouseDownAndNotHandled && Handle_IsValid(DragBlockHandle))
{ {
MouseDownAndNotHandled = false; MouseDownAndNotHandled = false;
SelectAndBeginDragAnimationBlock(DragBlockHandle, AdjustedViewRange, TimelineBounds, State); SelectAndBeginDragAnimationBlock(TimelineState, DragBlockHandle, AdjustedViewRange, TimelineBounds, State);
} }
// Time Slider // Time Slider
@ -567,7 +564,7 @@ DrawAnimationTimeline (animation_system* AnimationSystem, animation_timeline_sta
if (MouseDownAndNotHandled && PointIsInRect(TimelineBounds, Interface->Mouse.Pos)) if (MouseDownAndNotHandled && PointIsInRect(TimelineBounds, Interface->Mouse.Pos))
{ {
DeselectCurrentAnimationBlock(State); TimelineState->SelectedBlockHandle = {0};
} }
return Result; return Result;
@ -579,27 +576,256 @@ PANEL_MODAL_OVERRIDE_CALLBACK(LoadAnimationFileCallback)
file_view_state* FileViewState = Panel_GetStateStruct(ReturningFrom, file_view_state); file_view_state* FileViewState = Panel_GetStateStruct(ReturningFrom, file_view_state);
gs_file_info FileInfo = FileViewState->SelectedFile; gs_file_info FileInfo = FileViewState->SelectedFile;
gs_file AnimFile = ReadEntireFile(Context.ThreadContext.FileHandler, FileInfo.Path); if (FileInfo.Path.Length > 0)
gs_string AnimFileString = MakeString((char*)AnimFile.Data.Memory, AnimFile.Data.Size); {
animation NewAnim = AnimParser_Parse(AnimFileString, State->AnimationSystem.Storage, GlobalAnimationClipsCount, GlobalAnimationClips); gs_file AnimFile = ReadEntireFile(Context.ThreadContext.FileHandler, FileInfo.Path);
gs_string AnimFileString = MakeString((char*)AnimFile.Data.Memory, AnimFile.Data.Size);
u32 NewAnimIndex = AnimationArray_Push(&State->AnimationSystem.Animations, NewAnim); animation NewAnim = AnimParser_Parse(AnimFileString, State->AnimationSystem.Storage, GlobalAnimationPatternsCount, GlobalAnimationPatterns);
State->AnimationSystem.ActiveAnimationIndex = NewAnimIndex;
u32 NewAnimIndex = AnimationArray_Push(&State->AnimationSystem.Animations, NewAnim);
State->AnimationSystem.ActiveAnimationIndex = NewAnimIndex;
}
} }
internal void internal void
DrawAnimationClipsList(rect2 PanelBounds, ui_interface* Interface, u32 SelectedAnimationLayerHandle, animation_system* AnimationSystem) DrawAnimationPatternList(rect2 PanelBounds, ui_interface* Interface, u32 SelectedAnimationLayerHandle, animation_system* AnimationSystem)
{ {
ui_layout Layout = ui_CreateLayout(*Interface, PanelBounds); ui_layout Layout = ui_CreateLayout(Interface, PanelBounds);
for (s32 i = 0; i < GlobalAnimationClipsCount; i++) ui_PushLayout(Interface, Layout);
for (s32 i = 0; i < GlobalAnimationPatternsCount; i++)
{ {
animation_clip Clip = GlobalAnimationClips[i]; animation_pattern Pattern = GlobalAnimationPatterns[i];
gs_string ClipName = MakeString(Clip.Name, Clip.NameLength); gs_string PatternName = MakeString(Pattern.Name, Pattern.NameLength);
if (ui_LayoutListEntry(Interface, &Layout, ClipName, i)) if (ui_LayoutListEntry(Interface, &Layout, PatternName, i))
{ {
AddAnimationBlockAtCurrentTime(i + 1, SelectedAnimationLayerHandle, AnimationSystem); AddAnimationBlockAtCurrentTime(i + 1, SelectedAnimationLayerHandle, AnimationSystem);
} }
} }
ui_PopLayout(Interface);
}
internal void
PlayBar_Render(animation_timeline_state* TimelineState, rect2 Bounds, panel* Panel, render_command_buffer* RenderBuffer, app_state* State, context Context)
{
animation_system* AnimSystem = &State->AnimationSystem;
ui_interface* Interface = &State->Interface;
ui_layout Layout = ui_CreateLayout(Interface, Bounds);
ui_PushLayout(Interface, Layout);
ui_FillRect(Interface, Bounds, Interface->Style.PanelBGColors[0]);
ui_StartRow(&State->Interface, 4);
{
if (ui_Button(Interface, MakeString("Pause")))
{
AnimSystem->TimelineShouldAdvance = false;
}
if (ui_Button(Interface, MakeString("Play")))
{
AnimSystem->TimelineShouldAdvance = true;
}
if (ui_Button(Interface, MakeString("Stop")))
{
AnimSystem->TimelineShouldAdvance = false;
AnimSystem->CurrentFrame = 0;
}
if (ui_Button(Interface, MakeString("Load")))
{
panel* FileBrowser = PanelSystem_PushPanel(&State->PanelSystem, PanelType_FileView, State, Context);
Panel_PushModalOverride(Panel, FileBrowser, LoadAnimationFileCallback);
}
}
ui_EndRow(&State->Interface);
ui_PopLayout(&State->Interface);
}
internal void
FrameCount_Render(animation_timeline_state* TimelineState, rect2 Bounds, render_command_buffer* RenderBuffer, app_state* State, context Context)
{
ui_interface* Interface = &State->Interface;
gs_string TempString = PushString(State->Transient, 256);
frame_range VisibleFrames = TimelineState->VisibleRange;
s32 VisibleFrameCount = VisibleFrames.Max - VisibleFrames.Min;
ui_FillRect(Interface, Bounds, Interface->Style.PanelBGColors[0]);
// Frame Ticks
u32 TickCount = 10;
for (u32 Tick = 0; Tick < TickCount; Tick++)
{
r32 Percent = (r32)Tick / (r32)TickCount;
u32 Frame = PercentToFrameInRange(Percent, VisibleFrames);
PrintF(&TempString, "%d", Frame);
r32 FramePercent = FrameToPercentRange(Frame, VisibleFrames);
r32 FrameX = LerpR32(FramePercent, Bounds.Min.x, Bounds.Max.x);
v2 FrameTextPos = v2{FrameX, Bounds.Min.y + 2};
DrawString(Interface->RenderBuffer, TempString, Interface->Style.Font, FrameTextPos, WhiteV4);
}
// Time Slider
s32 CurrentFrame = State->AnimationSystem.CurrentFrame;
if (FrameIsInRange(VisibleFrames, CurrentFrame))
{
r32 FrameAtPercentVisibleRange = FrameToPercentRange(CurrentFrame, VisibleFrames);
r32 SliderX = LerpR32(FrameAtPercentVisibleRange, Bounds.Min.x, Bounds.Max.x);
PrintF(&TempString, "%d", CurrentFrame);
// space for each character + a margin on either side
r32 SliderWidth = (8 * TempString.Length) + 8;
r32 SliderHalfWidth = SliderWidth / 2.f;
v2 HeadMin = v2{SliderX - SliderHalfWidth, Bounds.Min.y};
v2 HeadMax = v2{SliderX + SliderHalfWidth, Bounds.Max.y};
PushRenderQuad2D(Interface->RenderBuffer, HeadMin, HeadMax, TimeSliderColor);
DrawString(Interface->RenderBuffer, TempString, Interface->Style.Font, HeadMin + v2{6, 4}, WhiteV4);
}
// Interaction
// Mouse clicked inside frame nubmer bar -> change current frame on timeline
if (ui_MouseClickedRect(*Interface, Bounds))
{
StartDragTimeMarker(Bounds, VisibleFrames, State);
}
}
internal void
LayerList_Render(animation_timeline_state* TimelineState, rect2 Bounds, render_command_buffer* RenderBuffer, app_state* State, context Context)
{
ui_interface* Interface = &State->Interface;
animation ActiveAnim = *AnimationSystem_GetActiveAnimation(&State->AnimationSystem);
ui_FillRect(Interface, Bounds, Interface->Style.PanelBGColors[0]);
v2 LayerDim = { Rect2Width(Bounds), LAYER_HEIGHT };
rect2 LayerBounds = {0};
LayerBounds.Min = Bounds.Min;
LayerBounds.Max = LayerBounds.Min + LayerDim;
for (u32 i = 0; i < ActiveAnim.Layers.Count; i++)
{
anim_layer* Layer = ActiveAnim.Layers.Values + i;
if (ui_MouseClickedRect(*Interface, LayerBounds))
{
TimelineState->SelectedAnimationLayer = i;
}
v2 LayerTextPos = { LayerBounds.Min.x + 6, LayerBounds.Max.y - 16};
if (TimelineState->SelectedAnimationLayer == i)
{
PushRenderBoundingBox2D(Interface->RenderBuffer, LayerBounds.Min, LayerBounds.Max, 1, WhiteV4);
}
DrawString(Interface->RenderBuffer, Layer->Name, Interface->Style.Font, LayerTextPos, WhiteV4);
LayerBounds = Rect2TranslateY(LayerBounds, LayerDim.y);
}
}
internal void
TimeRange_Render(animation_timeline_state* TimelineState, rect2 Bounds, render_command_buffer* RenderBuffer, app_state* State, context Context)
{
ui_interface* Interface = &State->Interface;
frame_range ViewRange = TimelineState->VisibleRange;
animation ActiveAnim = *AnimationSystem_GetActiveAnimation(&State->AnimationSystem);
handle SelectedBlockHandle = TimelineState->SelectedBlockHandle;
s32 CurrentFrame = State->AnimationSystem.CurrentFrame;
// Animation Blocks
b32 MouseDownAndNotHandled = MouseButtonTransitionedDown(Interface->Mouse.LeftButtonState);
handle DragBlockHandle = {0};
for (u32 i = 0; i < ActiveAnim.Blocks_.Count; i++)
{
animation_block* AnimationBlockAt = ActiveAnim.Blocks_.Values + i;
// If either end is in the range, we should draw it
b32 RangeIsVisible = (FrameIsInRange(ViewRange, AnimationBlockAt->Range.Min) ||
FrameIsInRange(ViewRange, AnimationBlockAt->Range.Max));
// If neither end is in the range, but the ends surround the visible range,
// we should still draw it.
RangeIsVisible |= (AnimationBlockAt->Range.Min <= ViewRange.Min &&
AnimationBlockAt->Range.Max>= ViewRange.Max);
if (RangeIsVisible)
{
v4 BlockColor = BlackV4;
if (SelectedBlockHandle.Index == i && SelectedBlockHandle.Generation == ActiveAnim.Blocks_.Generations[i])
{
BlockColor = PinkV4;
}
rect2 BlockBounds = DrawAnimationBlock(*AnimationBlockAt, BlockColor, ViewRange, Bounds, Interface->RenderBuffer);
if (PointIsInRect(BlockBounds, Interface->Mouse.Pos))
{
DragBlockHandle.Index = i;
DragBlockHandle.Generation = ActiveAnim.Blocks_.Generations[i];
}
}
}
// Time Slider
if (FrameIsInRange(ViewRange, CurrentFrame))
{
r32 FrameAtPercentVisibleRange = FrameToPercentRange(CurrentFrame, ViewRange);
r32 SliderX = LerpR32(FrameAtPercentVisibleRange, Bounds.Min.x, Bounds.Max.x);
rect2 SliderBounds = {
v2{ SliderX, Bounds.Min.y },
v2{ SliderX + 1, Bounds.Max.y }
};
ui_FillRect(Interface, SliderBounds, TimeSliderColor);
}
// Interaction
if (MouseDownAndNotHandled)
{
if (Handle_IsValid(DragBlockHandle))
{
MouseDownAndNotHandled = false;
SelectAndBeginDragAnimationBlock(TimelineState, DragBlockHandle, ViewRange, Bounds, State);
}
else if (PointIsInRect(Bounds, Interface->Mouse.Pos))
{
TimelineState->SelectedBlockHandle = {0};
}
}
}
internal void
AnimInfoView_Render(animation_timeline_state* TimelineState, rect2 Bounds, render_command_buffer* RenderBuffer, app_state* State, context Context)
{
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
SelectionInfoView_Render(animation_timeline_state* TimelineState, rect2 Bounds, render_command_buffer* RenderBuffer, app_state* State, context Context)
{
ui_FillRect(&State->Interface, Bounds, YellowV4);
} }
GSMetaTag(panel_render); GSMetaTag(panel_render);
@ -608,50 +834,32 @@ internal void
AnimationTimeline_Render(panel* Panel, rect2 PanelBounds, render_command_buffer* RenderBuffer, app_state* State, context Context) AnimationTimeline_Render(panel* Panel, rect2 PanelBounds, render_command_buffer* RenderBuffer, app_state* State, context Context)
{ {
animation_timeline_state* TimelineState = Panel_GetStateStruct(Panel, animation_timeline_state); animation_timeline_state* TimelineState = Panel_GetStateStruct(Panel, animation_timeline_state);
// TODO(pjs): SelectedAnimationBlockHandle should be a property of animation_timeline_state
// unless its used elsewhere. Audit later
handle SelectedBlockHandle = State->SelectedAnimationBlockHandle;
ui_interface* Interface = &State->Interface;
animation_system* AnimationSystem = &State->AnimationSystem;
rect2 TitleBarBounds, PanelContentsBounds; rect2 TimelineBounds, InfoBounds;
rect2 AnimationListBounds, TimelineBounds; RectVSplit(PanelBounds, 300, &InfoBounds, &TimelineBounds);
RectHSplitAtDistanceFromTop(PanelBounds, Interface->Style.RowHeight, &TitleBarBounds, &PanelContentsBounds);
RectVSplitAtDistanceFromLeft(PanelContentsBounds, 300, &AnimationListBounds, &TimelineBounds);
ui_FillRect(Interface, TitleBarBounds, Interface->Style.PanelBGColors[0]); rect2 AnimInfoBounds, SelectionInfoBounds;
ui_layout TitleBarLayout = ui_CreateLayout(*Interface, TitleBarBounds); RectHSplitAtPercent(InfoBounds, .65f, &AnimInfoBounds, &SelectionInfoBounds);
ui_StartRow(&TitleBarLayout, 4);
{ { // Timeline
if (ui_LayoutButton(Interface, &TitleBarLayout, MakeString("Pause"))) rect2 LayersPanelBounds, TimeRangePanelBounds;
{ RectVSplitAtDistanceFromLeft(TimelineBounds, 200, &LayersPanelBounds, &TimeRangePanelBounds);
State->AnimationSystem.TimelineShouldAdvance = false;
}
if (ui_LayoutButton(Interface, &TitleBarLayout, MakeString("Play"), (State->AnimationSystem.TimelineShouldAdvance ? PinkV4 : BlackV4), v4{.3f, .3f, .3f, 1.0f}, TealV4)) r32 TitleBarHeight = State->Interface.Style.RowHeight;
{ // These are the actual rects we will draw in
State->AnimationSystem.TimelineShouldAdvance = true; rect2 PlayBarBounds, FrameCountBounds;
} rect2 LayersBounds, TimeRangeBounds;
RectHSplitAtDistanceFromTop(LayersPanelBounds, TitleBarHeight, &PlayBarBounds, &LayersBounds);
RectHSplitAtDistanceFromTop(TimeRangePanelBounds, TitleBarHeight, &FrameCountBounds, &TimeRangeBounds);
if (ui_LayoutButton(Interface, &TitleBarLayout, MakeString("Stop"))) PlayBar_Render(TimelineState, PlayBarBounds, Panel, RenderBuffer, State, Context);
{ FrameCount_Render(TimelineState, FrameCountBounds, RenderBuffer, State, Context);
State->AnimationSystem.TimelineShouldAdvance = false; LayerList_Render(TimelineState, LayersBounds, RenderBuffer, State, Context);
State->AnimationSystem.CurrentFrame = 0; TimeRange_Render(TimelineState, TimeRangeBounds, RenderBuffer, State, Context);
}
if (ui_LayoutButton(Interface, &TitleBarLayout, MakeString("Load")))
{
panel_entry* FileBrowser = PanelSystem_PushPanel(&State->PanelSystem, PanelType_FileView, State, Context);
Panel_PushModalOverride(Panel, FileBrowser, LoadAnimationFileCallback);
}
} }
ui_EndRow(&TitleBarLayout);
if (Rect2Height(TimelineBounds) > 0) AnimInfoView_Render(TimelineState, AnimInfoBounds, RenderBuffer, State, Context);
{ SelectionInfoView_Render(TimelineState, SelectionInfoBounds, RenderBuffer, State, Context);
SelectedBlockHandle = DrawAnimationTimeline(AnimationSystem, TimelineState, TimelineBounds, SelectedBlockHandle, Interface, State);
DrawAnimationClipsList(AnimationListBounds, Interface, State->SelectedAnimationLayer, &State->AnimationSystem);
}
} }
#define FOLDHAUS_PANEL_ANIMATION_TIMELINE_H #define FOLDHAUS_PANEL_ANIMATION_TIMELINE_H

View File

@ -88,15 +88,16 @@ internal void
FileView_Render(panel* Panel, rect2 PanelBounds, render_command_buffer* RenderBuffer, app_state* State, context Context) 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); 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); FileView_Exit_(Panel, State, Context);
} }
// Header // Header
ui_LayoutDrawString(&State->Interface, &Layout, FileViewState->WorkingDirectory, v4{0, 1, 0, 1}); ui_DrawString(&State->Interface, FileViewState->WorkingDirectory);
// File Display // File Display
for (u32 i = 0; i < FileViewState->FileNames.Count; i++) 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);
} }

View File

@ -38,7 +38,9 @@ GSMetaTag(panel_type_hierarchy);
internal void internal void
HierarchyView_Render(panel* Panel, rect2 PanelBounds, render_command_buffer* RenderBuffer, app_state* State, context Context) 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); gs_string TempString = PushString(State->Transient, 256);
u32 LineCount = (u32)(Rect2Height(PanelBounds) / Layout.RowHeight) + 1; u32 LineCount = (u32)(Rect2Height(PanelBounds) / Layout.RowHeight) + 1;
u32 AssembliesToDraw = Min(LineCount, State->Assemblies.Count); 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]; assembly Assembly = State->Assemblies.Values[AssemblyIndex];
PrintF(&TempString, "%S", Assembly.Name); PrintF(&TempString, "%S", Assembly.Name);
ui_layout ItemLayout = ui_CreateLayout(State->Interface, LineBounds[AssemblyIndex]); ui_StartRow(&State->Interface, 2);
ui_StartRow(&ItemLayout, 2);
{ {
ui_LayoutDrawString(&State->Interface, &ItemLayout, TempString, State->Interface.Style.TextColor); ui_DrawString(&State->Interface, TempString);
if (ui_LayoutListButton(&State->Interface, &ItemLayout, MakeString("X"), AssemblyIndex)) if (ui_LayoutListButton(&State->Interface, &Layout, MakeString("X"), AssemblyIndex))
{ {
UnloadAssembly(AssemblyIndex, State, Context); UnloadAssembly(AssemblyIndex, State, Context);
} }
} }
ui_EndRow(&ItemLayout); ui_EndRow(&State->Interface);
} }
if (AssembliesToDraw < LineCount) if (AssembliesToDraw < LineCount)
@ -75,10 +76,12 @@ HierarchyView_Render(panel* Panel, rect2 PanelBounds, render_command_buffer* Ren
PrintF(&TempString, "+ Add Assembly"); PrintF(&TempString, "+ Add Assembly");
if (ui_ListButton(&State->Interface, TempString, LineBounds[AssembliesToDraw], AssembliesToDraw)) if (ui_ListButton(&State->Interface, TempString, LineBounds[AssembliesToDraw], AssembliesToDraw))
{ {
panel_entry* FileBrowser = PanelSystem_PushPanel(&State->PanelSystem, PanelType_FileView, State, Context); panel* FileBrowser = PanelSystem_PushPanel(&State->PanelSystem, PanelType_FileView, State, Context);
Panel_PushModalOverride(Panel, FileBrowser, LoadAssemblyCallback); Panel_PushModalOverride(Panel, FileBrowser, LoadAssemblyCallback);
} }
} }
ui_PopLayout(&State->Interface);
} }

View File

@ -84,7 +84,7 @@ RenderProfiler_ScopeVisualization(ui_interface* Interface, ui_layout Layout, deb
PrintF(&String, "%S : %d - %d", HotRecordName->Name, HotRecord->StartCycles, HotRecord->EndCycles); PrintF(&String, "%S : %d - %d", HotRecordName->Name, HotRecord->StartCycles, HotRecord->EndCycles);
rect2 TextBounds = MakeRect2MinDim(Interface->Mouse.Pos, v2{256, 32}); 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); gs_string String = MakeString(Backbuffer, 0, 256);
r32 ColumnWidths[] = {256, 128, 128, 128, 128}; 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_DrawString(Interface, MakeString("Procedure"));
ui_LayoutDrawString(Interface, &Layout, MakeString("% Frame"), Interface->Style.TextColor); ui_DrawString(Interface, MakeString("% Frame"));
ui_LayoutDrawString(Interface, &Layout, MakeString("Seconds"), Interface->Style.TextColor); ui_DrawString(Interface, MakeString("Seconds"));
ui_LayoutDrawString(Interface, &Layout, MakeString("Cycles"), Interface->Style.TextColor); ui_DrawString(Interface, MakeString("Cycles"));
ui_LayoutDrawString(Interface, &Layout, MakeString("Calls"), Interface->Style.TextColor); ui_DrawString(Interface, MakeString("Calls"));
} }
ui_EndRow(&Layout); ui_EndRow(Interface);
for (s32 n = 0; n < VisibleFrame->ScopeNamesMax; n++) 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; collated_scope_record* CollatedRecord = VisibleFrame->CollatedScopes + n;
ui_StartRow(&Layout, 5, &ColumnWidths[0]); ui_StartRow(Interface, 5, &ColumnWidths[0]);
{ {
PrintF(&String, "%S", NameEntry.Name); PrintF(&String, "%S", NameEntry.Name);
ui_LayoutDrawString(Interface, &Layout, String, Interface->Style.TextColor); ui_DrawString(Interface, String);
PrintF(&String, "%f%%", CollatedRecord->PercentFrameTime); PrintF(&String, "%f%%", CollatedRecord->PercentFrameTime);
ui_LayoutDrawString(Interface, &Layout, String, Interface->Style.TextColor); ui_DrawString(Interface, String);
PrintF(&String, "%fs", CollatedRecord->TotalSeconds); PrintF(&String, "%fs", CollatedRecord->TotalSeconds);
ui_LayoutDrawString(Interface, &Layout, String, Interface->Style.TextColor); ui_DrawString(Interface, String);
PrintF(&String, "%dcy", CollatedRecord->TotalCycles); PrintF(&String, "%dcy", CollatedRecord->TotalCycles);
ui_LayoutDrawString(Interface, &Layout, String, Interface->Style.TextColor); ui_DrawString(Interface, String);
PrintF(&String, "%d", CollatedRecord->CallCount); 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); debug_frame* VisibleFrame = GetLastDebugFrame(GlobalDebugServices);
ui_layout Layout = ui_CreateLayout(State->Interface, ProcListBounds); ui_layout Layout = ui_CreateLayout(&State->Interface, ProcListBounds);
ui_StartRow(&Layout, 4); ui_PushLayout(&State->Interface, Layout);
ui_StartRow(&State->Interface, 4);
{ {
s64 FrameStartCycles = VisibleFrame->FrameStartCycles; s64 FrameStartCycles = VisibleFrame->FrameStartCycles;
s64 FrameTotalCycles = VisibleFrame->FrameEndCycles - VisibleFrame->FrameStartCycles; s64 FrameTotalCycles = VisibleFrame->FrameEndCycles - VisibleFrame->FrameStartCycles;
u32 CurrentDebugFrame = GlobalDebugServices->CurrentDebugFrame - 1; u32 CurrentDebugFrame = GlobalDebugServices->CurrentDebugFrame - 1;
PrintF(&String, "Frame %d", CurrentDebugFrame); PrintF(&String, "Frame %d", CurrentDebugFrame);
ui_LayoutDrawString(&State->Interface, &Layout, String, WhiteV4); ui_DrawString(&State->Interface, String);
PrintF(&String, "Total Cycles: %lld", FrameTotalCycles); 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 // NOTE(NAME): Skipping a space for aesthetic reasons, not functional, and could
// be removed, or used for something else // be removed, or used for something else
ui_ReserveElementBounds(&Layout); ui_ReserveElementBounds(&Layout);
if (ui_LayoutButton(&State->Interface, &Layout, MakeString("Resume Recording"))) if (ui_Button(&State->Interface, MakeString("Resume Recording")))
{ {
GlobalDebugServices->RecordFrames = true; 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; 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; GlobalDebugServices->Interface.FrameView = FRAME_VIEW_SCOPE_LIST;
} }
} }
ui_EndRow(&Layout); ui_EndRow(&State->Interface);
if (GlobalDebugServices->Interface.FrameView == FRAME_VIEW_PROFILER) 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); RenderProfiler_ListVisualization(&State->Interface, Layout, VisibleFrame, Memory);
} }
ui_PopLayout(&State->Interface);
} }

View File

@ -5,11 +5,17 @@
// //
#ifndef FOLDHAUS_PANEL_SCULPTURE_VIEW_H #ifndef FOLDHAUS_PANEL_SCULPTURE_VIEW_H
struct sculpture_view_panel_state
{
camera Camera;
};
// 3D Mouse View // 3D Mouse View
OPERATION_STATE_DEF(mouse_rotate_view_operation_state) OPERATION_STATE_DEF(mouse_rotate_view_operation_state)
{ {
v4 CameraStartPos; v4 CameraStartPos;
camera* Camera;
}; };
OPERATION_RENDER_PROC(Update3DViewMouseRotate) OPERATION_RENDER_PROC(Update3DViewMouseRotate)
@ -22,7 +28,7 @@ OPERATION_RENDER_PROC(Update3DViewMouseRotate)
m44 YRotation = M44RotationY(TotalDeltaPos.x * State->PixelsToWorldScale); m44 YRotation = M44RotationY(TotalDeltaPos.x * State->PixelsToWorldScale);
m44 Combined = XRotation * YRotation; m44 Combined = XRotation * YRotation;
State->Camera.Position = (Combined * OpState->CameraStartPos).xyz; OpState->Camera->Position = (Combined * OpState->CameraStartPos).xyz;
} }
FOLDHAUS_INPUT_COMMAND_PROC(End3DViewMouseRotate) FOLDHAUS_INPUT_COMMAND_PROC(End3DViewMouseRotate)
@ -36,11 +42,14 @@ input_command MouseRotateViewCommands [] = {
FOLDHAUS_INPUT_COMMAND_PROC(Begin3DViewMouseRotate) FOLDHAUS_INPUT_COMMAND_PROC(Begin3DViewMouseRotate)
{ {
sculpture_view_panel_state* PanelState = Panel_GetStateStruct(Panel, sculpture_view_panel_state);
operation_mode* RotateViewMode = ActivateOperationModeWithCommands(&State->Modes, MouseRotateViewCommands, Update3DViewMouseRotate); operation_mode* RotateViewMode = ActivateOperationModeWithCommands(&State->Modes, MouseRotateViewCommands, Update3DViewMouseRotate);
mouse_rotate_view_operation_state* OpState = CreateOperationState(RotateViewMode, mouse_rotate_view_operation_state* OpState = CreateOperationState(RotateViewMode,
&State->Modes, &State->Modes,
mouse_rotate_view_operation_state); mouse_rotate_view_operation_state);
OpState->CameraStartPos = ToV4Point(State->Camera.Position); OpState->CameraStartPos = ToV4Point(PanelState->Camera.Position);
OpState->Camera = &PanelState->Camera;
} }
// ---------------- // ----------------
@ -57,7 +66,16 @@ GSMetaTag(panel_type_sculpture_view);
internal void internal void
SculptureView_Init(panel* Panel, app_state* State, context Context) SculptureView_Init(panel* Panel, app_state* State, context Context)
{ {
sculpture_view_panel_state* PanelState = PushStruct(&State->Permanent, sculpture_view_panel_state);
PanelState->Camera.FieldOfView = 45.0f;
PanelState->Camera.AspectRatio = RectAspectRatio(State->WindowBounds);
PanelState->Camera.Near = .1f;
PanelState->Camera.Far = 800.0f;
PanelState->Camera.Position = v3{0, 0, 400};
PanelState->Camera.LookAt = v3{0, 0, 0};
Panel->StateMemory = StructToData(PanelState, sculpture_view_panel_state);
} }
GSMetaTag(panel_cleanup); GSMetaTag(panel_cleanup);
@ -159,9 +177,10 @@ internal void
SculptureView_Render(panel* Panel, rect2 PanelBounds, render_command_buffer* RenderBuffer, app_state* State, context Context) SculptureView_Render(panel* Panel, rect2 PanelBounds, render_command_buffer* RenderBuffer, app_state* State, context Context)
{ {
DEBUG_TRACK_SCOPE(RenderSculpture); DEBUG_TRACK_SCOPE(RenderSculpture);
State->Camera.AspectRatio = RectAspectRatio(PanelBounds); sculpture_view_panel_state* PanelState = Panel_GetStateStruct(Panel, sculpture_view_panel_state);
PanelState->Camera.AspectRatio = RectAspectRatio(PanelBounds);
PushRenderPerspective(RenderBuffer, PanelBounds, State->Camera); PushRenderPerspective(RenderBuffer, PanelBounds, PanelState->Camera);
u32 MaxLEDsPerJob = 2048; u32 MaxLEDsPerJob = 2048;
render_quad_batch_constructor RenderLEDsBatch = PushRenderQuad3DBatch(RenderBuffer, State->LedSystem.LedsCountTotal); render_quad_batch_constructor RenderLEDsBatch = PushRenderQuad3DBatch(RenderBuffer, State->LedSystem.LedsCountTotal);
@ -184,7 +203,7 @@ SculptureView_Render(panel* Panel, rect2 PanelBounds, render_command_buffer* Ren
JobData->Batch = &RenderLEDsBatch; JobData->Batch = &RenderLEDsBatch;
JobData->BatchReservedRange = ReserveRangeInQuadConstructor(JobData->Batch, JobLedCount * 2); JobData->BatchReservedRange = ReserveRangeInQuadConstructor(JobData->Batch, JobLedCount * 2);
JobData->LEDHalfWidth = .5f; JobData->LEDHalfWidth = .5f;
JobData->CameraPosition = ToV4Point(State->Camera.Position); JobData->CameraPosition = ToV4Point(PanelState->Camera.Position);
#if 1 #if 1
Context.GeneralWorkQueue->PushWorkOnQueue(Context.GeneralWorkQueue, (thread_proc*)DrawLEDsInBufferRangeJob, Data, ConstString("Sculpture Draw LEDS")); Context.GeneralWorkQueue->PushWorkOnQueue(Context.GeneralWorkQueue, (thread_proc*)DrawLEDsInBufferRangeJob, Data, ConstString("Sculpture Draw LEDS"));
#else #else
@ -204,7 +223,7 @@ SculptureView_Render(panel* Panel, rect2 PanelBounds, render_command_buffer* Ren
led_buffer* LedBuffer = LedSystemGetBuffer(&State->LedSystem, Assembly.LedBufferIndex); led_buffer* LedBuffer = LedSystemGetBuffer(&State->LedSystem, Assembly.LedBufferIndex);
v4 LedPosition = LedBuffer->Positions[FocusPixel]; v4 LedPosition = LedBuffer->Positions[FocusPixel];
v2 LedOnScreenPosition = SculptureView_WorldToScreenPosition(LedPosition, State->Camera, PanelBounds); v2 LedOnScreenPosition = SculptureView_WorldToScreenPosition(LedPosition, PanelState->Camera, PanelBounds);
gs_string Tempgs_string = PushString(State->Transient, 256); gs_string Tempgs_string = PushString(State->Transient, 256);
PrintF(&Tempgs_string, "%f %f", LedOnScreenPosition.x, LedOnScreenPosition.y); PrintF(&Tempgs_string, "%f %f", LedOnScreenPosition.x, LedOnScreenPosition.y);

View File

@ -14,6 +14,10 @@ struct frame_range
s32 Max; s32 Max;
}; };
// NOTE(pjs): An animation block is a time range paired with an
// animation_pattern (see below). While a timeline's current time
// is within the range of a block, that particular block's animation
// will run
struct animation_block struct animation_block
{ {
frame_range Range; frame_range Range;
@ -37,6 +41,9 @@ enum blend_mode
BlendMode_Count, BlendMode_Count,
}; };
// TODO(pjs): Add Opacity to this
typedef pixel led_blend_proc(pixel PixelA, pixel PixelB);
global gs_const_string BlendModeStrings[] = { global gs_const_string BlendModeStrings[] = {
ConstString("Overwrite"), ConstString("Overwrite"),
ConstString("Add"), ConstString("Add"),
@ -57,11 +64,15 @@ struct anim_layer_array
u32 CountMax; u32 CountMax;
}; };
// NOTE(pjs): An animation is a stack of layers, each of which
// is a timeline of animation blocks.
struct animation struct animation
{ {
gs_string Name; gs_string Name;
anim_layer_array Layers; anim_layer_array Layers;
// TODO(pjs): Pretty sure Blocks_ should be obsolete and
// Layers should contain their own blocks
animation_block_array Blocks_; animation_block_array Blocks_;
frame_range PlayableRange; frame_range PlayableRange;
@ -74,14 +85,23 @@ struct animation_array
u32 CountMax; u32 CountMax;
}; };
struct animation_layer_frame
{
animation_block Hot;
bool HasHot;
animation_block NextHot;
bool HasNextHot;
r32 HotOpacity;
};
// NOTE(pjs): This is an evaluated frame - across all layers in an
// animation, these are the blocks that need to be run
struct animation_frame struct animation_frame
{ {
// NOTE(pjs): These are all parallel arrays of equal length animation_layer_frame* Layers;
animation_block* Blocks; u32 LayersCount;
b8* BlocksFilled;
u32 BlocksCountMax;
u32 BlocksCount;
}; };
#define ANIMATION_SYSTEM_LAYERS_MAX 128 #define ANIMATION_SYSTEM_LAYERS_MAX 128
@ -102,8 +122,10 @@ struct animation_system
}; };
// TODO(pjs): Better name - something like animation_prototype // NOTE(pjs): A Pattern is a named procedure which can be used as
struct animation_clip // an element of an animation. Patterns are sequenced on a timeline
// and blended via layers to create an animation
struct animation_pattern
{ {
char* Name; char* Name;
s32 NameLength; s32 NameLength;
@ -411,10 +433,9 @@ AnimationSystem_CalculateAnimationFrame(animation_system* System, gs_memory_aren
animation* ActiveAnim = AnimationSystem_GetActiveAnimation(System); animation* ActiveAnim = AnimationSystem_GetActiveAnimation(System);
animation_frame Result = {0}; animation_frame Result = {0};
Result.BlocksCountMax = ActiveAnim->Layers.Count; Result.LayersCount = ActiveAnim->Layers.Count;
Result.Blocks = PushArray(Arena, animation_block, Result.BlocksCountMax); Result.Layers = PushArray(Arena, animation_layer_frame, Result.LayersCount);
Result.BlocksFilled = PushArray(Arena, b8, Result.BlocksCountMax); ZeroArray(Result.Layers, animation_layer_frame, Result.LayersCount);
ZeroArray(Result.BlocksFilled, b8, Result.BlocksCountMax);
for (u32 i = 0; i < ActiveAnim->Blocks_.Count; i++) for (u32 i = 0; i < ActiveAnim->Blocks_.Count; i++)
{ {
@ -422,14 +443,66 @@ AnimationSystem_CalculateAnimationFrame(animation_system* System, gs_memory_aren
if (FrameIsInRange(Block.Range, System->CurrentFrame)) if (FrameIsInRange(Block.Range, System->CurrentFrame))
{ {
Result.BlocksFilled[Block.Layer] = true; animation_layer_frame* Layer = Result.Layers + Block.Layer;
Result.Blocks[Block.Layer] = Block; if (Layer->HasHot)
Result.BlocksCount++; {
// NOTE(pjs): With current implementation, we don't allow
// animations to hvae more than 2 concurrent blocks in the
// timeline
Assert(!Layer->HasNextHot);
// NOTE(pjs): Make sure that Hot comes before NextHot
if (Layer->Hot.Range.Min < Block.Range.Min)
{
Layer->NextHot = Block;
}
else
{
Layer->NextHot = Layer->Hot;
Layer->Hot = Block;
}
Layer->HasNextHot = true;
frame_range BlendRange = {};
BlendRange.Min = Layer->NextHot.Range.Min;
BlendRange.Max = Layer->Hot.Range.Max;
Layer->HotOpacity = 1.0f - FrameToPercentRange(System->CurrentFrame, BlendRange);
}
else
{
Layer->Hot = Block;
Layer->HotOpacity = 1.0f;
Layer->HasHot = true;
}
} }
} }
return Result; return Result;
} }
internal void
AnimationSystem_Update(animation_system* System)
{
animation* ActiveAnim = AnimationSystem_GetActiveAnimation(System);
if (System->TimelineShouldAdvance) {
// TODO(Peter): Revisit this. This implies that the framerate of the animation system
// is tied to the framerate of the simulation. That seems correct to me, but I'm not sure
System->CurrentFrame += 1;
// Loop back to the beginning
if (System->CurrentFrame > ActiveAnim->PlayableRange.Max)
{
System->CurrentFrame = 0;
}
}
}
inline bool
AnimationSystem_NeedsRender(animation_system System)
{
bool Result = (System.CurrentFrame != System.LastUpdatedFrame);
return Result;
}
#define FOLDHAUS_ANIMATION #define FOLDHAUS_ANIMATION
#endif // FOLDHAUS_ANIMATION #endif // FOLDHAUS_ANIMATION

View File

@ -0,0 +1,200 @@
//
// File: foldhaus_animation_renderer.cpp
// Author: Peter Slattery
// Creation Date: 2020-11-14
//
#ifndef FOLDHAUS_ANIMATION_RENDERER_CPP
internal pixel
LedBlend_Overwrite(pixel PixelA, pixel PixelB)
{
return PixelB;
}
internal pixel
LedBlend_Overwrite(pixel PixelA, pixel PixelB, r32 Opacity)
{
pixel Result = {};
r32 BOpacity = 1.0f - Opacity;
Result.R = (u8)((PixelA.R * Opacity) + (PixelB.R * BOpacity));
Result.G = (u8)((PixelA.G * Opacity) + (PixelB.G * BOpacity));
Result.B = (u8)((PixelA.B * Opacity) + (PixelB.B * BOpacity));
return Result;
}
internal pixel
LedBlend_Add(pixel PixelA, pixel PixelB)
{
pixel Result = {};
u32 R = (u32)PixelA.R + (u32)PixelB.R;
u32 G = (u32)PixelA.G + (u32)PixelB.G;
u32 B = (u32)PixelA.B + (u32)PixelB.B;
Result.R = (u8)Min(R, (u32)255);
Result.G = (u8)Min(G, (u32)255);
Result.B = (u8)Min(B, (u32)255);
return Result;
}
internal pixel
LedBlend_Multiply(pixel PixelA, pixel PixelB)
{
pixel Result = {};
r32 DR = (r32)PixelA.R / 255.f;
r32 DG = (r32)PixelA.G / 255.f;
r32 DB = (r32)PixelA.B / 255.f;
r32 SR = (r32)PixelB.R / 255.f;
r32 SG = (r32)PixelB.G / 255.f;
r32 SB = (r32)PixelB.B / 255.f;
Result.R = (u8)((DR * SR) * 255.f);
Result.G = (u8)((DG * SG) * 255.f);
Result.B = (u8)((DB * SB) * 255.f);
return Result;
}
internal pixel
LedBlend_Overlay(pixel PixelA, pixel PixelB)
{
pixel Result = {};
return Result;
}
internal led_blend_proc*
LedBlend_GetProc(blend_mode BlendMode)
{
led_blend_proc* Result = 0;
switch (BlendMode)
{
case BlendMode_Overwrite: { Result = LedBlend_Overwrite; }break;
case BlendMode_Add: { Result = LedBlend_Add; }break;
case BlendMode_Multiply: { Result = LedBlend_Multiply; }break;
InvalidDefaultCase;
}
return Result;
}
internal void
AnimationSystem_RenderBlockToLedBuffer(animation_system* System, animation_block Block, led_buffer* Buffer, assembly Assembly, animation_pattern* Patterns, gs_memory_arena* Transient)
{
u32 FramesIntoBlock = System->CurrentFrame - Block.Range.Min;
r32 SecondsIntoBlock = FramesIntoBlock * System->SecondsPerFrame;
// :AnimProcHandle
u32 AnimationProcIndex = Block.AnimationProcHandle - 1;
animation_proc* AnimationProc = Patterns[AnimationProcIndex].Proc;
AnimationProc(Buffer, Assembly, SecondsIntoBlock, Transient);
}
internal void
AnimationSystem_RenderToLedBuffers(animation_system* System, assembly_array Assemblies,
led_system* LedSystem,
animation_pattern* Patterns,
gs_memory_arena* Transient)
{
s32 CurrentFrame = System->CurrentFrame;
r32 FrameTime = CurrentFrame * System->SecondsPerFrame;
animation* ActiveAnim = AnimationSystem_GetActiveAnimation(System);
animation_frame CurrFrame = AnimationSystem_CalculateAnimationFrame(System, Transient);
// NOTE(pjs): This mirrors animation_layer_frame to account
// for overlapping
struct layer_led_buffer
{
led_buffer HotBuffer;
led_buffer NextHotBuffer;
};
layer_led_buffer* LayerBuffers = PushArray(Transient, layer_led_buffer, CurrFrame.LayersCount);
for (u32 AssemblyIndex = 0; AssemblyIndex < Assemblies.Count; AssemblyIndex++)
{
assembly* Assembly = &Assemblies.Values[AssemblyIndex];
led_buffer* AssemblyLedBuffer = LedSystemGetBuffer(LedSystem, Assembly->LedBufferIndex);
// Create the LayerLEDBuffers
for (u32 Layer = 0; Layer < CurrFrame.LayersCount; Layer++)
{
layer_led_buffer TempBuffer = {};
if (CurrFrame.Layers[Layer].HasHot)
{
TempBuffer.HotBuffer.LedCount = AssemblyLedBuffer->LedCount;
TempBuffer.HotBuffer.Positions = AssemblyLedBuffer->Positions;
TempBuffer.HotBuffer.Colors = PushArray(Transient, pixel, TempBuffer.HotBuffer.LedCount);
LedBuffer_ClearToBlack(&TempBuffer.HotBuffer);
}
if (CurrFrame.Layers[Layer].HasNextHot)
{
TempBuffer.NextHotBuffer.LedCount = AssemblyLedBuffer->LedCount;
TempBuffer.NextHotBuffer.Positions = AssemblyLedBuffer->Positions;
TempBuffer.NextHotBuffer.Colors = PushArray(Transient, pixel, TempBuffer.HotBuffer.LedCount);
LedBuffer_ClearToBlack(&TempBuffer.NextHotBuffer);
}
LayerBuffers[Layer] = TempBuffer;
}
// Render Each layer's block to the appropriate temp buffer
for (u32 Layer = 0; Layer < CurrFrame.LayersCount; Layer++)
{
animation_layer_frame LayerFrame = CurrFrame.Layers[Layer];
if (LayerFrame.HasHot)
{
led_buffer TempBuffer = LayerBuffers[Layer].HotBuffer;
animation_block Block = LayerFrame.Hot;
AnimationSystem_RenderBlockToLedBuffer(System, Block, &TempBuffer, *Assembly, Patterns, Transient);
}
if (LayerFrame.HasNextHot)
{
led_buffer TempBuffer = LayerBuffers[Layer].NextHotBuffer;
animation_block Block = LayerFrame.NextHot;
AnimationSystem_RenderBlockToLedBuffer(System, Block, &TempBuffer, *Assembly, Patterns, Transient);
}
}
// Blend together any layers that have a hot and next hot buffer
for (u32 Layer = 0; Layer < CurrFrame.LayersCount; Layer++)
{
animation_layer_frame LayerFrame = CurrFrame.Layers[Layer];
layer_led_buffer LayerBuffer = LayerBuffers[Layer];
if (LayerFrame.HasNextHot)
{
for (u32 LED = 0; LED < AssemblyLedBuffer->LedCount; LED++)
{
pixel A = LayerBuffer.HotBuffer.Colors[LED];
pixel B = LayerBuffer.NextHotBuffer.Colors[LED];
LayerBuffer.HotBuffer.Colors[LED] = LedBlend_Overwrite(A, B, LayerFrame.HotOpacity);
}
}
}
// Consolidate Temp Buffers
// We do this in reverse order so that they go from top to bottom
for (u32 Layer = 0; Layer < CurrFrame.LayersCount; Layer++)
{
if (CurrFrame.Layers[Layer].HasHot)
{
led_blend_proc* Blend = LedBlend_GetProc(ActiveAnim->Layers.Values[Layer].BlendMode);
Assert(Blend != 0);
for (u32 LED = 0; LED < AssemblyLedBuffer->LedCount; LED++)
{
pixel A = AssemblyLedBuffer->Colors[LED];
pixel B = LayerBuffers[Layer].HotBuffer.Colors[LED];
AssemblyLedBuffer->Colors[LED] = Blend(A, B);
}
}
}
}
}
#define FOLDHAUS_ANIMATION_RENDERER_CPP
#endif // FOLDHAUS_ANIMATION_RENDERER_CPP

View File

@ -6,7 +6,7 @@
#ifndef FOLDHAUS_ANIMATION_SERIALIZER_CPP #ifndef FOLDHAUS_ANIMATION_SERIALIZER_CPP
internal gs_string internal gs_string
AnimSerializer_Serialize(animation Anim, animation_clip* GlobalClips, gs_memory_arena* Arena) AnimSerializer_Serialize(animation Anim, animation_pattern* GlobalClips, gs_memory_arena* Arena)
{ {
serializer Serializer = {0}; serializer Serializer = {0};
Serializer.String = PushString(Arena, 4096); Serializer.String = PushString(Arena, 4096);
@ -48,7 +48,7 @@ AnimSerializer_Serialize(animation Anim, animation_clip* GlobalClips, gs_memory_
// TODO(pjs): Systematize the AnimationProcHandle // TODO(pjs): Systematize the AnimationProcHandle
// :AnimProcHandle // :AnimProcHandle
u32 AnimationProcIndex = AnimationBlockAt.AnimationProcHandle - 1; u32 AnimationProcIndex = AnimationBlockAt.AnimationProcHandle - 1;
animation_clip Animation = GlobalClips[AnimationProcIndex]; animation_pattern Animation = GlobalClips[AnimationProcIndex];
Serializer_OpenStruct(&Serializer, AnimField_Block); Serializer_OpenStruct(&Serializer, AnimField_Block);
{ {
@ -70,7 +70,7 @@ AnimSerializer_Serialize(animation Anim, animation_clip* GlobalClips, gs_memory_
} }
internal animation internal animation
AnimParser_Parse(gs_string File, gs_memory_arena* Arena, u32 AnimClipsCount, animation_clip* AnimClips) AnimParser_Parse(gs_string File, gs_memory_arena* Arena, u32 AnimClipsCount, animation_pattern* AnimClips)
{ {
animation Result = {0}; animation Result = {0};

View File

@ -168,6 +168,17 @@ LedSystemGetBuffer(led_system* System, u32 Index)
return Result; return Result;
} }
internal void
LedBuffer_ClearToBlack(led_buffer* Buffer)
{
for (u32 i = 0; i < Buffer->LedCount; i++)
{
Buffer->Colors[i].R = 0;
Buffer->Colors[i].G = 0;
Buffer->Colors[i].B = 0;
}
}
internal u32 internal u32
StripGenData_CountLeds(strip_gen_data Data) StripGenData_CountLeds(strip_gen_data Data)
{ {

View File

@ -15,6 +15,8 @@ RELOAD_STATIC_DATA(ReloadStaticData)
app_state* State = (app_state*)Context.MemoryBase; app_state* State = (app_state*)Context.MemoryBase;
GlobalDebugServices = DebugServices; GlobalDebugServices = DebugServices;
State->PanelSystem.PanelDefs = GlobalPanelDefs;
State->PanelSystem.PanelDefsCount = GlobalPanelDefsCount;
} }
INITIALIZE_APPLICATION(InitializeApplication) INITIALIZE_APPLICATION(InitializeApplication)
@ -30,11 +32,7 @@ INITIALIZE_APPLICATION(InitializeApplication)
State->GlobalLog = PushStruct(State->Transient, event_log); State->GlobalLog = PushStruct(State->Transient, event_log);
*State->GlobalLog = {0}; *State->GlobalLog = {0};
s32 CommandQueueSize = 32; State->CommandQueue = CommandQueue_Create(&State->Permanent, 32);
command_queue_entry* CommandQueueMemory = PushArray(&State->Permanent,
command_queue_entry,
CommandQueueSize);
State->CommandQueue = InitializeCommandQueue(CommandQueueMemory, CommandQueueSize);
// TODO(Peter): put in InitializeInterface? // TODO(Peter): put in InitializeInterface?
r32 FontSize = 14; r32 FontSize = 14;
@ -101,7 +99,7 @@ INITIALIZE_APPLICATION(InitializeApplication)
State->Interface.Style.PanelBGColors[3] = v4{.6f, .6f, .6f, 1}; State->Interface.Style.PanelBGColors[3] = v4{.6f, .6f, .6f, 1};
State->Interface.Style.ButtonColor_Inactive = BlackV4; State->Interface.Style.ButtonColor_Inactive = BlackV4;
State->Interface.Style.ButtonColor_Active = v4{.1f, .1f, .1f, 1}; 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.TextColor = WhiteV4;
State->Interface.Style.ListBGColors[0] = v4{ .16f, .16f, .16f, 1.f }; State->Interface.Style.ListBGColors[0] = v4{ .16f, .16f, .16f, 1.f };
State->Interface.Style.ListBGColors[1] = v4{ .18f, .18f, .18f, 1.f }; State->Interface.Style.ListBGColors[1] = v4{ .18f, .18f, .18f, 1.f };
@ -110,15 +108,10 @@ INITIALIZE_APPLICATION(InitializeApplication)
State->Interface.Style.Margin = v2{5, 5}; State->Interface.Style.Margin = v2{5, 5};
State->Interface.Style.RowHeight = ui_GetTextLineHeight(State->Interface); State->Interface.Style.RowHeight = ui_GetTextLineHeight(State->Interface);
State->SACN = SACN_Initialize(Context); State->Interface.WidgetsCountMax = 4096;
State->Interface.Widgets = PushArray(&State->Permanent, ui_widget, State->Interface.WidgetsCountMax);
State->Camera.FieldOfView = 45.0f; State->SACN = SACN_Initialize(Context);
State->Camera.AspectRatio = RectAspectRatio(State->WindowBounds);
State->Camera.Near = .1f;
State->Camera.Far = 800.0f;
State->Camera.Position = v3{0, 0, 400};
State->Camera.LookAt = v3{0, 0, 0
};
State->LedSystem = LedSystemInitialize(Context.ThreadContext.Allocator, 128); State->LedSystem = LedSystemInitialize(Context.ThreadContext.Allocator, 128);
@ -158,70 +151,10 @@ INITIALIZE_APPLICATION(InitializeApplication)
} // End Animation Playground } // End Animation Playground
InitializePanelSystem(&State->PanelSystem, GlobalPanelDefs, GlobalPanelDefsCount); PanelSystem_Init(&State->PanelSystem, GlobalPanelDefs, GlobalPanelDefsCount, &State->Permanent);
PanelSystem_PushPanel(&State->PanelSystem, PanelType_SculptureView, State, Context); PanelSystem_PushPanel(&State->PanelSystem, PanelType_SculptureView, State, Context);
} }
internal void
HandleInput (app_state* State, rect2 WindowBounds, input_queue InputQueue, mouse_state Mouse, context Context)
{
DEBUG_TRACK_FUNCTION;
b32 PanelSystemHandledInput = HandleMousePanelInteraction(&State->PanelSystem, State->WindowBounds, Mouse, State);
if (!PanelSystemHandledInput)
{
input_command_registry ActiveCommands = {};
if (State->Modes.ActiveModesCount > 0)
{
ActiveCommands = State->Modes.ActiveModes[State->Modes.ActiveModesCount - 1].Commands;
}
else
{
panel_with_layout PanelWithMouseOverIt = GetPanelContainingPoint(Mouse.Pos, &State->PanelSystem, WindowBounds);
if (!PanelWithMouseOverIt.Panel) { return; }
State->HotPanel = PanelWithMouseOverIt.Panel;
s32 PanelTypeIndex = PanelWithMouseOverIt.Panel->TypeIndex;
panel_definition PanelDefinition = GlobalPanelDefs[PanelTypeIndex];
if (!PanelDefinition.InputCommands) { return; }
ActiveCommands.Commands = PanelDefinition.InputCommands;
ActiveCommands.Size = sizeof(*PanelDefinition.InputCommands) / sizeof(PanelDefinition.InputCommands[0]);
ActiveCommands.Used = ActiveCommands.Size;
}
for (s32 EventIdx = 0; EventIdx < InputQueue.QueueUsed; EventIdx++)
{
input_entry Event = InputQueue.Entries[EventIdx];
// NOTE(Peter): These are in the order Down, Up, Held because we want to privalege
// Down and Up over Held. In other words, we don't want to call a Held command on the
// frame when the button was released, even if the command is registered to both events
if (KeyTransitionedDown(Event))
{
FindAndPushExistingCommand(ActiveCommands, Event, Command_Began, &State->CommandQueue);
}
else if (KeyTransitionedUp(Event))
{
FindAndPushExistingCommand(ActiveCommands, Event, Command_Ended, &State->CommandQueue);
}
else if (KeyHeldDown(Event))
{
FindAndPushExistingCommand(ActiveCommands, Event, Command_Held, &State->CommandQueue);
}
}
}
// Execute all commands in CommandQueue
for (s32 CommandIdx = State->CommandQueue.Used - 1; CommandIdx >= 0; CommandIdx--)
{
command_queue_entry* Entry = &State->CommandQueue.Commands[CommandIdx];
Entry->Command.Proc(State, Entry->Event, Mouse, Context);
}
ClearCommandQueue(&State->CommandQueue);
}
UPDATE_AND_RENDER(UpdateAndRender) UPDATE_AND_RENDER(UpdateAndRender)
{ {
DEBUG_TRACK_FUNCTION; DEBUG_TRACK_FUNCTION;
@ -232,111 +165,18 @@ UPDATE_AND_RENDER(UpdateAndRender)
// zero the Transient arena when we clear it so it wouldn't be a problem, but it is technically // zero the Transient arena when we clear it so it wouldn't be a problem, but it is technically
// incorrect to clear the arena, and then access the memory later. // incorrect to clear the arena, and then access the memory later.
ClearArena(State->Transient); ClearArena(State->Transient);
Context->Mouse.CursorType = CursorType_Arrow;
PushRenderClearScreen(RenderBuffer); Editor_Update(State, Context, InputQueue);
State->Camera.AspectRatio = RectAspectRatio(Context->WindowBounds);
HandleInput(State, State->WindowBounds, InputQueue, Context->Mouse, *Context);
AnimationSystem_Update(&State->AnimationSystem);
if (AnimationSystem_NeedsRender(State->AnimationSystem))
{ {
animation* ActiveAnim = AnimationSystem_GetActiveAnimation(&State->AnimationSystem); State->AnimationSystem.LastUpdatedFrame = State->AnimationSystem.CurrentFrame;
if (State->AnimationSystem.TimelineShouldAdvance) { AnimationSystem_RenderToLedBuffers(&State->AnimationSystem,
// TODO(Peter): Revisit this. This implies that the framerate of the animation system State->Assemblies,
// is tied to the framerate of the simulation. That seems correct to me, but I'm not sure &State->LedSystem,
State->AnimationSystem.CurrentFrame += 1; GlobalAnimationPatterns,
State->Transient);
// Loop back to the beginning
if (State->AnimationSystem.CurrentFrame > ActiveAnim->PlayableRange.Max)
{
State->AnimationSystem.CurrentFrame = 0;
}
}
}
s32 CurrentFrame = State->AnimationSystem.CurrentFrame;
if (CurrentFrame != State->AnimationSystem.LastUpdatedFrame)
{
State->AnimationSystem.LastUpdatedFrame = CurrentFrame;
r32 FrameTime = CurrentFrame * State->AnimationSystem.SecondsPerFrame;
animation_frame CurrFrame = AnimationSystem_CalculateAnimationFrame(&State->AnimationSystem, State->Transient);
led_buffer* LayerLEDBuffers = PushArray(State->Transient, led_buffer, CurrFrame.BlocksCountMax);
for (u32 AssemblyIndex = 0; AssemblyIndex < State->Assemblies.Count; AssemblyIndex++)
{
assembly* Assembly = &State->Assemblies.Values[AssemblyIndex];
led_buffer* AssemblyLedBuffer = LedSystemGetBuffer(&State->LedSystem, Assembly->LedBufferIndex);
for (u32 Layer = 0; Layer < CurrFrame.BlocksCountMax; Layer++)
{
if (!CurrFrame.BlocksFilled[Layer]) { continue; }
animation_block Block = CurrFrame.Blocks[Layer];
// Prep Temp Buffer
LayerLEDBuffers[Layer] = *AssemblyLedBuffer;
LayerLEDBuffers[Layer].Colors = PushArray(State->Transient, pixel, AssemblyLedBuffer->LedCount);
u32 FramesIntoBlock = CurrentFrame - Block.Range.Min;
r32 SecondsIntoBlock = FramesIntoBlock * State->AnimationSystem.SecondsPerFrame;
// :AnimProcHandle
u32 AnimationProcIndex = Block.AnimationProcHandle - 1;
animation_proc* AnimationProc = GlobalAnimationClips[AnimationProcIndex].Proc;
AnimationProc(&LayerLEDBuffers[Layer], *Assembly, SecondsIntoBlock, State->Transient);
}
// Consolidate Temp Buffers
// We do this in reverse order so that they go from top to bottom
animation* ActiveAnim = AnimationSystem_GetActiveAnimation(&State->AnimationSystem);
for (u32 Layer = 0; Layer < CurrFrame.BlocksCountMax; Layer++)
{
if (!CurrFrame.BlocksFilled[Layer]) { continue; }
switch (ActiveAnim->Layers.Values[Layer].BlendMode)
{
case BlendMode_Overwrite:
{
for (u32 LED = 0; LED < AssemblyLedBuffer->LedCount; LED++)
{
AssemblyLedBuffer->Colors[LED] = LayerLEDBuffers[Layer].Colors[LED];
}
}break;
case BlendMode_Add:
{
for (u32 LED = 0; LED < AssemblyLedBuffer->LedCount; LED++)
{
u32 R = (u32)AssemblyLedBuffer->Colors[LED].R + (u32)LayerLEDBuffers[Layer].Colors[LED].R;
u32 G = (u32)AssemblyLedBuffer->Colors[LED].G + (u32)LayerLEDBuffers[Layer].Colors[LED].G;
u32 B = (u32)AssemblyLedBuffer->Colors[LED].B + (u32)LayerLEDBuffers[Layer].Colors[LED].B;
AssemblyLedBuffer->Colors[LED].R = (u8)Min(R, (u32)255);
AssemblyLedBuffer->Colors[LED].G = (u8)Min(G, (u32)255);
AssemblyLedBuffer->Colors[LED].B = (u8)Min(B, (u32)255);
}
}break;
case BlendMode_Multiply:
{
for (u32 LED = 0; LED < AssemblyLedBuffer->LedCount; LED++)
{
r32 DR = (r32)AssemblyLedBuffer->Colors[LED].R / 255.f;
r32 DG = (r32)AssemblyLedBuffer->Colors[LED].G / 255.f;
r32 DB = (r32)AssemblyLedBuffer->Colors[LED].B / 255.f;
r32 SR = (r32)LayerLEDBuffers[Layer].Colors[LED].R / 255.f;
r32 SG = (r32)LayerLEDBuffers[Layer].Colors[LED].G / 255.f;
r32 SB = (r32)LayerLEDBuffers[Layer].Colors[LED].B / 255.f;
AssemblyLedBuffer->Colors[LED].R = (u8)((DR * SR) * 255.f);
AssemblyLedBuffer->Colors[LED].G = (u8)((DG * SG) * 255.f);
AssemblyLedBuffer->Colors[LED].B = (u8)((DB * SB) * 255.f);
}
}break;
}
}
}
} }
{ {
@ -348,27 +188,7 @@ UPDATE_AND_RENDER(UpdateAndRender)
UART_BuildOutputData(OutputData, UARTAssemblies, &State->LedSystem); UART_BuildOutputData(OutputData, UARTAssemblies, &State->LedSystem);
} }
PushRenderOrthographic(RenderBuffer, State->WindowBounds); Editor_Render(State, Context, RenderBuffer);
PushRenderClearScreen(RenderBuffer);
State->WindowBounds = Context->WindowBounds;
State->Interface.RenderBuffer = RenderBuffer;
State->Interface.Mouse = Context->Mouse;
panel_layout PanelsToRender = GetPanelLayout(&State->PanelSystem, State->WindowBounds, State->Transient);
DrawAllPanels(PanelsToRender, RenderBuffer, &Context->Mouse, State, *Context);
for (s32 m = 0; m < State->Modes.ActiveModesCount; m++)
{
operation_mode OperationMode = State->Modes.ActiveModes[m];
if (OperationMode.Render != 0)
{
OperationMode.Render(State, RenderBuffer, OperationMode, Context->Mouse, *Context);
}
}
Context->GeneralWorkQueue->CompleteQueueWork(Context->GeneralWorkQueue, Context->ThreadContext);
Context->GeneralWorkQueue->ResetWorkQueue(Context->GeneralWorkQueue);
// Checking for overflows // Checking for overflows
#if 0 #if 0

View File

@ -26,6 +26,8 @@
typedef struct app_state app_state; typedef struct app_state app_state;
typedef struct panel panel;
#include "editor/foldhaus_command_dispatch.h" #include "editor/foldhaus_command_dispatch.h"
#include "editor/foldhaus_operation_mode.h" #include "editor/foldhaus_operation_mode.h"
@ -36,6 +38,7 @@ typedef struct app_state app_state;
#include "engine/animation/foldhaus_animation.h" #include "engine/animation/foldhaus_animation.h"
#include "engine/animation/foldhaus_animation_serializer.cpp" #include "engine/animation/foldhaus_animation_serializer.cpp"
#include "engine/animation/foldhaus_animation_renderer.cpp"
struct app_state struct app_state
{ {
@ -62,10 +65,7 @@ struct app_state
panel_system PanelSystem; panel_system PanelSystem;
panel* HotPanel; panel* HotPanel;
camera Camera; // TODO(Peter): move into the sculpture view
r32 PixelsToWorldScale; r32 PixelsToWorldScale;
handle SelectedAnimationBlockHandle; // TODO(Peter): move into animation panel
u32 SelectedAnimationLayer; // TODO(Peter): move into animation panel
}; };
internal void OpenColorPicker(app_state* State, v4* Address); internal void OpenColorPicker(app_state* State, v4* Address);
@ -202,18 +202,31 @@ TestPatternThree(led_buffer* Leds, assembly Assembly, r32 Time, gs_memory_arena*
} }
} }
internal void
Pattern_AllGreen(led_buffer* Leds, assembly Assembly, r32 Time, gs_memory_arena* Transient)
{
for (u32 LedIndex = 0; LedIndex < Leds->LedCount; LedIndex++)
{
Leds->Colors[LedIndex].R = 0;
Leds->Colors[LedIndex].B = 255;
Leds->Colors[LedIndex].G = 255;
}
}
// END TEMPORARY PATTERNS // END TEMPORARY PATTERNS
FOLDHAUS_INPUT_COMMAND_PROC(EndCurrentOperationMode) internal void
EndCurrentOperationMode(app_state* State)
{ {
DeactivateCurrentOperationMode(&State->Modes); DeactivateCurrentOperationMode(&State->Modes);
} }
s32 GlobalAnimationClipsCount = 3; s32 GlobalAnimationPatternsCount = 4;
animation_clip GlobalAnimationClips[] = { animation_pattern GlobalAnimationPatterns[] = {
{ "Test Pattern One", 16, TestPatternOne }, { "Test Pattern One", 16, TestPatternOne },
{ "Test Pattern Two", 16, TestPatternTwo }, { "Test Pattern Two", 16, TestPatternTwo },
{ "Test Pattern Three", 18, TestPatternThree }, { "Test Pattern Three", 18, TestPatternThree },
{ "Pattern_AllGreen", 16, Pattern_AllGreen },
}; };
#include "editor/panels/foldhaus_panel_types.h" #include "editor/panels/foldhaus_panel_types.h"
@ -233,5 +246,7 @@ animation_clip GlobalAnimationClips[] = {
#include "../meta/gs_meta_include.cpp" #include "../meta/gs_meta_include.cpp"
#include "editor/foldhaus_editor.cpp"
#define FOLDHAUS_APP_H #define FOLDHAUS_APP_H
#endif // FOLDHAUS_APP_H #endif // FOLDHAUS_APP_H

View File

@ -148,7 +148,7 @@ typedef DRAW_FONT_CODEPOINT(platform_draw_font_codepoint);
// Worker Threads // Worker Threads
#define PLATFORM_THREAD_COUNT 4 #define PLATFORM_THREAD_COUNT 0
RESET_WORK_QUEUE(ResetWorkQueue) RESET_WORK_QUEUE(ResetWorkQueue)
{ {

View File

@ -5,6 +5,14 @@
// //
#ifndef INTERFACE_H #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 enum gs_string_alignment
{ {
Align_Left, Align_Left,
@ -129,7 +137,7 @@ DrawCursor (render_quad_batch_constructor* BatchConstructor, v2 Position, v4 Col
} }
internal v2 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; DEBUG_TRACK_FUNCTION;
v2 LowerRight = Position; v2 LowerRight = Position;
@ -180,10 +188,44 @@ Drawgs_stringWithCursor (render_command_buffer* RenderBuffer, gs_string String,
return LowerRight; 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 struct interface_config
{ {
v4 PanelBGColors[4]; v4 PanelBGColors[4];
// TODO(pjs): Turn these into _Default, _Hot, _Active
v4 ButtonColor_Inactive, ButtonColor_Active, ButtonColor_Selected; v4 ButtonColor_Inactive, ButtonColor_Active, ButtonColor_Selected;
v4 TextColor; v4 TextColor;
@ -199,17 +241,38 @@ struct interface_config
r32 RowHeight; r32 RowHeight;
}; };
enum ui_layout_direction
{
LayoutDirection_TopDown,
LayoutDirection_BottomUp,
};
struct ui_layout struct ui_layout
{ {
u64 Id;
rect2 Bounds; rect2 Bounds;
v2 Margin; v2 Margin;
r32 RowHeight; r32 RowHeight;
r32 RowYAt; r32 RowYAt;
ui_layout_direction FillDirection;
b32 DrawHorizontal; b32 DrawHorizontal;
u32 ColumnsMax; u32 ColumnsMax;
r32* ColumnWidths; r32* ColumnWidths;
u32 ColumnsCount; 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 struct ui_interface
@ -217,49 +280,144 @@ struct ui_interface
interface_config Style; interface_config Style;
mouse_state Mouse; mouse_state Mouse;
render_command_buffer* RenderBuffer; 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 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}; ui_layout Result = {0};
Result.Bounds = Bounds; Result.Bounds = Bounds;
Result.Margin = Interface.Style.Margin; Result.Margin = Interface->Style.Margin;
Result.RowHeight = Interface.Style.RowHeight; Result.RowHeight = Interface->Style.RowHeight;
Result.RowYAt = Bounds.Max.y - Result.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; return Result;
} }
static void static void
ui_StartRow(ui_layout* Layout, u32 ColumnsMax) ui_PushLayout(ui_interface* Interface, ui_layout Layout)
{ {
Layout->DrawHorizontal = true; Assert(Interface->LayoutStackCount < LAYOUT_COUNT_MAX);
Layout->ColumnsMax = ColumnsMax; Interface->LayoutStack[Interface->LayoutStackCount++] = Layout;
Layout->ColumnWidths = 0;
Layout->ColumnsCount = 0;
} }
static void 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 static void
ui_StartRow(ui_layout* Layout, u32 ColumnsMax, r32* ColumnWidths) ui_StartRow(ui_interface* Interface, u32 ColumnsMax = 0)
{ {
Layout->DrawHorizontal = true; u64 LayoutIdx = Interface->LayoutStackCount - 1;
Layout->ColumnsMax = ColumnsMax; Interface->LayoutStack[LayoutIdx].DrawHorizontal = true;
Layout->ColumnWidths = ColumnWidths; Interface->LayoutStack[LayoutIdx].ColumnsMax = ColumnsMax;
Layout->ColumnsCount = 0; Interface->LayoutStack[LayoutIdx].ColumnWidths = 0;
Interface->LayoutStack[LayoutIdx].ColumnsCount = 0;
} }
static void static void
ui_EndRow(ui_layout* Layout) ui_StartRow(ui_interface* Interface, u32 ColumnsMax, r32* ColumnWidths)
{ {
Layout->DrawHorizontal = false; u64 LayoutIdx = Interface->LayoutStackCount - 1;
Layout->ColumnWidths = 0; Interface->LayoutStack[LayoutIdx].DrawHorizontal = true;
Layout->RowYAt -= Layout->RowHeight; 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 static b32
@ -270,7 +428,21 @@ ui_TryReserveElementBounds(ui_layout* Layout, rect2* Bounds)
{ {
Bounds->Min = { Layout->Bounds.Min.x, Layout->RowYAt }; Bounds->Min = { Layout->Bounds.Min.x, Layout->RowYAt };
Bounds->Max = { Layout->Bounds.Max.x, Bounds->Min.y + Layout->RowHeight }; 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 else
{ {
@ -309,14 +481,6 @@ ui_TryReserveElementBounds(ui_layout* Layout, rect2* Bounds)
return Result; return Result;
} }
static rect2
ui_ReserveTextLineBounds(ui_interface Interface, gs_string Text, ui_layout* Layout)
{
rect2 Bounds = {0};
return Bounds;
}
static rect2 static rect2
ui_ReserveElementBounds(ui_layout* Layout) ui_ReserveElementBounds(ui_layout* Layout)
{ {
@ -340,6 +504,81 @@ ui_LayoutRemaining(ui_layout Layout)
return Result; return Result;
} }
// Widgets
internal ui_widget
ui_CreateWidget(gs_string String)
{
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 // Drawing Functions
// //
@ -364,78 +603,40 @@ ui_OutlineRect(ui_interface* Interface, rect2 Bounds, r32 Thickness, v4 Color)
} }
internal void 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; DEBUG_TRACK_FUNCTION;
render_quad_batch_constructor BatchConstructor = PushRenderTexture2DBatch(Interface->RenderBuffer, ui_widget Widget = ui_CreateWidget(String);
String.Length, Widget.Bounds = Bounds;
Interface->Style.Font->BitmapMemory, ui_EvaluateWidget(Interface, &Widget);
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;
}
} }
static void internal void
ui_LayoutDrawString(ui_interface* Interface, ui_layout* Layout, gs_string String, v4 Color, gs_string_alignment Alignment = Align_Left) ui_DrawString(ui_interface* Interface, gs_string String, gs_string_alignment Alignment = Align_Left)
{ {
rect2 Bounds = {0}; DEBUG_TRACK_FUNCTION;
if (!ui_TryReserveElementBounds(Layout, &Bounds)) ui_widget Widget = ui_CreateWidget(String);
{ ui_EvaluateWidget(Interface, &Widget);
// 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);
} }
static b32 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; ui_widget Widget = ui_CreateWidget(Text);
v4 ButtonBG = InactiveColor; ui_WidgetSetFlag(&Widget, UIWidgetFlag_Clickable);
if (PointIsInRect(Bounds, Interface->Mouse.Pos)) ui_WidgetSetFlag(&Widget, UIWidgetFlag_DrawBackground);
{ ui_eval_result Result = ui_EvaluateWidget(Interface, &Widget);
ButtonBG = HoverColor; return Result.Clicked;
if (MouseButtonTransitionedDown(Interface->Mouse.LeftButtonState))
{
ButtonBG = ClickedColor;
Pressed = true;
}
}
ui_TextBox(Interface, Bounds, Text, ButtonBG, Interface->Style.TextColor);
return Pressed;
} }
static b32 static b32
ui_Button(ui_interface* Interface, gs_string Text, rect2 Bounds) ui_Button(ui_interface* Interface, gs_string Text, rect2 Bounds)
{ {
v4 BGColor = Interface->Style.ButtonColor_Inactive; ui_widget Widget = ui_CreateWidget(Text);
v4 HoverColor = Interface->Style.ButtonColor_Active; ui_WidgetSetFlag(&Widget, UIWidgetFlag_Clickable);
v4 SelectedColor = Interface->Style.ButtonColor_Selected; ui_WidgetSetFlag(&Widget, UIWidgetFlag_DrawBackground);
return ui_Button(Interface, Text, Bounds, BGColor, HoverColor, SelectedColor); ui_eval_result Result = ui_EvaluateWidget(Interface, &Widget, Bounds);
return Result.Clicked;
} }
struct list_item_colors struct list_item_colors
@ -465,35 +666,19 @@ ui_GetListItemColors(ui_interface* Interface, u32 ListItemIndex)
static b32 static b32
ui_ListButton(ui_interface* Interface, gs_string Text, rect2 Bounds, u32 ListItemIndex) ui_ListButton(ui_interface* Interface, gs_string Text, rect2 Bounds, u32 ListItemIndex)
{ {
list_item_colors Colors = ui_GetListItemColors(Interface, ListItemIndex); ui_widget Widget = ui_CreateWidget(Text);
return ui_Button(Interface, Text, Bounds, Colors.Hover, Colors.Selected, Colors.BGColor); ui_WidgetSetFlag(&Widget, UIWidgetFlag_DrawBackground);
} ui_WidgetSetFlag(&Widget, UIWidgetFlag_Clickable);
// TODO(pjs): Reimplement alternating color backgrounds
static b32 Widget.Bounds = Bounds;
ui_LayoutButton(ui_interface* Interface, ui_layout* Layout, gs_string Text, v4 BGColor, v4 HoverColor, v4 SelectColor) ui_eval_result Result = ui_EvaluateWidget(Interface, &Widget);
{ return Result.Clicked;
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);
} }
static b32 static b32
ui_LayoutListButton(ui_interface* Interface, ui_layout* Layout, gs_string Text, u32 ListItemIndex) ui_LayoutListButton(ui_interface* Interface, ui_layout* Layout, gs_string Text, u32 ListItemIndex)
{ {
list_item_colors Colors = ui_GetListItemColors(Interface, ListItemIndex); return ui_Button(Interface, Text);
return ui_LayoutButton(Interface, Layout, Text, Colors.Hover, Colors.Selected, Colors.BGColor);
} }
static b32 static b32
@ -508,10 +693,90 @@ ui_LayoutListEntry(ui_interface* Interface, ui_layout* Layout, gs_string Text, u
// Punting this till I have a use case // Punting this till I have a use case
InvalidCodePath; InvalidCodePath;
} }
v4 BGColor = ui_GetListItemBGColor(Interface->Style, Index); return ui_Button(Interface, Text, Bounds);
v4 HoverColor = Interface->Style.ListBGHover; }
v4 SelectedColor = Interface->Style.ListBGSelected;
return ui_Button(Interface, Text, Bounds, BGColor, HoverColor, SelectedColor); 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);
}
}
} }
// //

View File

@ -423,6 +423,26 @@ Win32_SendAddressedDataBuffers_Job(gs_thread_context Context, gs_data Arg)
Win32_SendAddressedDataBuffers(Context, *OutputData); Win32_SendAddressedDataBuffers(Context, *OutputData);
} }
#pragma pack(push, 1)
struct test_microphone_packet
{
b8 ChangeAnimation;
char AnimationFileName[32];
b8 SetLayer;
char LayerName[32];
r32 LayerOpacity;
b8 SetLayerParamColor;
char LayerParamColor[7];
r32 OverrideDuration;
};
#pragma pack(pop)
inline u32
UpackB4(const u8* ptr)
{
return (u32)(ptr[3] | (ptr[2] << 8) | (ptr[1] << 16) | (ptr[0] << 24));
}
int WINAPI int WINAPI
WinMain ( WinMain (
HINSTANCE HInstance, HINSTANCE HInstance,
@ -434,6 +454,10 @@ WinMain (
gs_thread_context ThreadContext = Win32CreateThreadContext(); gs_thread_context ThreadContext = Win32CreateThreadContext();
#if 0 #if 0
// NOTE(pjs): UART TEST CODE
// NOTE(pjs): UART TEST CODE
// NOTE(pjs): UART TEST CODE
u32 LedCount = 48; u32 LedCount = 48;
u32 MessageBaseSize = sizeof(uart_header) + sizeof(uart_channel) + sizeof(uart_footer); u32 MessageBaseSize = sizeof(uart_header) + sizeof(uart_channel) + sizeof(uart_footer);
MessageBaseSize += sizeof(u8) * 3 * LedCount; MessageBaseSize += sizeof(u8) * 3 * LedCount;
@ -459,8 +483,6 @@ WinMain (
#endif #endif
MainWindow = Win32CreateWindow (HInstance, "Foldhaus", 1440, 768, HandleWindowEvents); MainWindow = Win32CreateWindow (HInstance, "Foldhaus", 1440, 768, HandleWindowEvents);
Win32UpdateWindowDimension(&MainWindow); Win32UpdateWindowDimension(&MainWindow);
@ -562,6 +584,25 @@ WinMain (
WSAStartup(MAKEWORD(2, 2), &WSAData); WSAStartup(MAKEWORD(2, 2), &WSAData);
Win32Sockets = Win32SocketArray_Create(16, &PlatformPermanent); Win32Sockets = Win32SocketArray_Create(16, &PlatformPermanent);
#if 0
// NOTE(pjs): SOCKET READING TEST CODE
// NOTE(pjs): SOCKET READING TEST CODE
// NOTE(pjs): SOCKET READING TEST CODE
win32_socket TestSocket = Win32Socket_ConnectToAddress("127.0.0.1", "20185");
test_microphone_packet* Recv = 0;
while (true)
{
gs_data Data = Win32Socket_Receive(&TestSocket, ThreadContext.Transient);
if (Data.Size > 0)
{
OutputDebugStringA("Received\n");
Recv = (test_microphone_packet*)Data.Memory;
}
ClearArena(ThreadContext.Transient);
}
#endif
Win32SerialArray_Create(ThreadContext); Win32SerialArray_Create(ThreadContext);
s32 RenderMemorySize = MB(12); s32 RenderMemorySize = MB(12);

View File

@ -54,6 +54,82 @@ Win32SocketArray_Get(win32_socket_array Array, s32 Index)
global win32_socket_array Win32Sockets; global win32_socket_array Win32Sockets;
internal win32_socket
Win32Socket_Create(s32 AddressFamily, s32 Type, s32 Protocol)
{
win32_socket Result = {0};
Result.Socket = socket(AddressFamily, Type, Protocol);
if (Result.Socket == INVALID_SOCKET)
{
s32 Error = WSAGetLastError();
InvalidCodePath;
}
return Result;
}
internal void
Win32Socket_Bind(win32_socket* Socket, s32 AddressFamily, char* Address, s32 Port)
{
sockaddr_in Service = {0};
Service.sin_family = AddressFamily;
Service.sin_addr.s_addr = inet_addr(Address);
Service.sin_port = htons(Port);
s32 Result = bind(Socket->Socket, (SOCKADDR*)&Service, sizeof(Service));
if (Result == SOCKET_ERROR)
{
s32 Error = WSAGetLastError();
InvalidCodePath;
}
}
internal win32_socket
Win32Socket_ConnectToAddress(char* Address, char* DefaultPort)
{
win32_socket Result = {};
addrinfo Hints = {0};
Hints.ai_family = AF_UNSPEC;
Hints.ai_socktype = SOCK_STREAM;
Hints.ai_protocol = IPPROTO_TCP;
addrinfo* PotentialConnections;
s32 Error = getaddrinfo(Address, DefaultPort, &Hints, &PotentialConnections);
if (Error == 0)
{
for (addrinfo* InfoAt = PotentialConnections; InfoAt != NULL; InfoAt = InfoAt->ai_next)
{
win32_socket Socket = Win32Socket_Create(InfoAt->ai_family, InfoAt->ai_socktype, InfoAt->ai_protocol);
if (Socket.Socket == INVALID_SOCKET)
{
Error = WSAGetLastError();
InvalidCodePath;
}
Error = connect(Socket.Socket, InfoAt->ai_addr, (int)InfoAt->ai_addrlen);
if (Error == SOCKET_ERROR)
{
closesocket(Socket.Socket);
continue;
}
else
{
Result = Socket;
break;
}
}
}
else
{
Error = WSAGetLastError();
InvalidCodePath;
}
freeaddrinfo(PotentialConnections);
return Result;
}
internal s32 internal s32
Win32Socket_SetOption(win32_socket* Socket, s32 Level, s32 Option, const char* OptionValue, s32 OptionLength) Win32Socket_SetOption(win32_socket* Socket, s32 Level, s32 Option, const char* OptionValue, s32 OptionLength)
{ {
@ -74,32 +150,6 @@ Win32Socket_SetOption(platform_socket_handle SocketHandle, s32 Level, s32 Option
return Win32Socket_SetOption(Socket, Level, Option, OptionValue, OptionLength); return Win32Socket_SetOption(Socket, Level, Option, OptionValue, OptionLength);
} }
PLATFORM_GET_SOCKET_HANDLE(Win32GetSocketHandle)
{
// NOTE(Peter): These used to be passed in as paramters, but we only use this function
// with AF_INET, SOCK_DGRAM, and Protocol = 0. These are also platform specific values
// so I was having to include windows.h in the platform agnostic code to accomodate that
// function signature.
s32 AddressFamily = AF_INET;
s32 Type = SOCK_DGRAM;
s32 Protocol = 0;
s32 Result = Win32SocketArray_Take(&Win32Sockets);
win32_socket* Socket = Win32SocketArray_Get(Win32Sockets, Result);
Socket->Socket = socket(AddressFamily, Type, Protocol);
if (Socket->Socket != INVALID_SOCKET)
{
int Error = Win32Socket_SetOption(Socket, IPPROTO_IP, IP_MULTICAST_TTL,
(const char*)(&Multicast_TimeToLive), sizeof(Multicast_TimeToLive));
}
else
{
s32 Error = WSAGetLastError();
InvalidCodePath;
}
return (platform_socket_handle)Result;
}
internal s32 internal s32
Win32Socket_SendTo(platform_socket_handle SocketHandle, u32 Address, u32 Port, const char* Buffer, s32 BufferLength, s32 Flags) Win32Socket_SendTo(platform_socket_handle SocketHandle, u32 Address, u32 Port, const char* Buffer, s32 BufferLength, s32 Flags)
@ -129,6 +179,61 @@ Win32Socket_SendTo(platform_socket_handle SocketHandle, u32 Address, u32 Port, c
return LengthSent; return LengthSent;
} }
internal void
Win32Socket_SetListening(win32_socket* Socket)
{
if (listen(Socket->Socket, SOMAXCONN) == SOCKET_ERROR)
{
// TODO(pjs): Error logging
s32 Error = WSAGetLastError();
InvalidCodePath;
}
}
internal s32
Win32Socket_PeekGetTotalSize(win32_socket* Socket)
{
s32 Flags = MSG_PEEK;
char Temp[4];
s32 BytesQueued = recv(Socket->Socket, Temp, 4, Flags);
if (BytesQueued == SOCKET_ERROR)
{
// TODO(pjs): Error logging
s32 Error = WSAGetLastError();
BytesQueued = 0;
}
return BytesQueued;
}
internal gs_data
Win32Socket_Receive(win32_socket* Socket, gs_memory_arena* Storage)
{
#if 0
gs_data Result = {};
s32 BytesQueued = Win32Socket_PeekGetTotalSize(Socket);
if (BytesQueued > 0)
{
Result = PushSizeToData(Storage, BytesQueued);
s32 Flags = 0;
s32 BytesReceived = recv(Socket->Socket, (char*)Result.Memory, Result.Size, Flags);
Assert(BytesReceived == BytesQueued);
}
return Result;
#else
gs_data Result = PushSizeToData(Storage, 1024);
s32 Flags = 0;
s32 BytesReceived = recv(Socket->Socket, (char*)Result.Memory, Result.Size, Flags);
if (BytesReceived == SOCKET_ERROR)
{
// TODO(pjs): Error logging
s32 Error = WSAGetLastError();
InvalidCodePath;
}
Result.Size = BytesReceived;
return Result;
#endif
}
internal void internal void
Win32Socket_Close(win32_socket* Socket) Win32Socket_Close(win32_socket* Socket)
{ {
@ -136,5 +241,16 @@ Win32Socket_Close(win32_socket* Socket)
Socket->Socket = INVALID_SOCKET; Socket->Socket = INVALID_SOCKET;
} }
PLATFORM_GET_SOCKET_HANDLE(Win32GetSocketHandle)
{
s32 Result = Win32SocketArray_Take(&Win32Sockets);
s32 Error = 0;
win32_socket* Socket = Win32SocketArray_Get(Win32Sockets, Result);
*Socket = Win32Socket_Create(AF_INET, SOCK_DGRAM, 0);
Error = Win32Socket_SetOption(Socket, IPPROTO_IP, IP_MULTICAST_TTL,
(const char*)(&Multicast_TimeToLive), sizeof(Multicast_TimeToLive));
return (platform_socket_handle)Result;
}
#define WIN32_FOLDHAUS_SOCKET_H #define WIN32_FOLDHAUS_SOCKET_H
#endif // WIN32_FOLDHAUS_SOCKET_H #endif // WIN32_FOLDHAUS_SOCKET_H

View File

@ -855,6 +855,7 @@ RectHSplit(rect2 Rect, r32 YValue, rect2* Top, rect2* Bottom)
Bottom->Max = { Rect.Max.x, ClampedYValue }; Bottom->Max = { Rect.Max.x, ClampedYValue };
Bottom->Min = Rect.Min; Bottom->Min = Rect.Min;
} }
internal void internal void
RectVSplit(rect2 Rect, r32 XValue, rect2* Left, rect2* Right) RectVSplit(rect2 Rect, r32 XValue, rect2* Left, rect2* Right)
{ {

View File

@ -231,7 +231,7 @@ struct u64_array
# define InvalidDefaultCase default: { AssertBreak("invalid default case"); } break; # define InvalidDefaultCase default: { AssertBreak("invalid default case"); } break;
# define StaticAssert(c) \ # define StaticAssert(c) \
enum { \ enum { \
Glue(gs_AssertFail_, __LINE__) = 1 / (int)(!!(c)), \ Glue(gs_AssertFail_, __LINE__) = 1 / (int)(!!(c)), \
} }
#else #else
# define Assert(c) # define Assert(c)

View File

@ -1,5 +1,17 @@
TODO FOLDHAUS TODO FOLDHAUS
- engine environment
- primary color
- default controller
- gets its own update function
- has access to the engine state
- can select animations
- change playback speed, primary color
- user space controller
- socket listener
STREAM #1: 3D Overhaul STREAM #1: 3D Overhaul
- Rendering (Working on this elsewhere) - Rendering (Working on this elsewhere)
- OpenGL 3 - OpenGL 3