Converted thinsg over to using time ranges and implemented zoom. Also restructured how different parts of the timeline view know where they are. The timeline now constructs all the bounds, and each element just draws itself inside the provided bounds.
This commit is contained in:
parent
197b6accc7
commit
780ccbd1a3
|
@ -313,6 +313,10 @@ static parse_result ParseFloat (s32 Length, char* String);
|
||||||
static void PrintFArgList(char* Dest, s32 DestMax, char* Format, va_list Args);
|
static void PrintFArgList(char* Dest, s32 DestMax, char* Format, va_list Args);
|
||||||
static void PrintF(string* String, char* Format, ...);
|
static void PrintF(string* String, char* Format, ...);
|
||||||
|
|
||||||
|
// Printing Helper Functions
|
||||||
|
static u32 GetU32NumberOfCharactersNeeded(u32 Value, u32 Base = 10);
|
||||||
|
static u32 GetS32NumberOfCharactersNeeded(u32 Value, s32 Base = 10);
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////
|
||||||
// String Memory Function Declarations
|
// String Memory Function Declarations
|
||||||
////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////
|
||||||
|
@ -1999,7 +2003,7 @@ PrintFArgsList (char* Dest, s32 DestMax, char* Format, va_list Args)
|
||||||
return FormattedLength;
|
return FormattedLength;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void
|
static void
|
||||||
PrintF (string* String, char* Format, ...)
|
PrintF (string* String, char* Format, ...)
|
||||||
{
|
{
|
||||||
va_list Args;
|
va_list Args;
|
||||||
|
@ -2009,7 +2013,7 @@ PrintF (string* String, char* Format, ...)
|
||||||
va_end(Args);
|
va_end(Args);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void
|
static void
|
||||||
AppendPrintF (string* String, char* Format, ...)
|
AppendPrintF (string* String, char* Format, ...)
|
||||||
{
|
{
|
||||||
va_list Args;
|
va_list Args;
|
||||||
|
@ -2018,6 +2022,42 @@ AppendPrintF (string* String, char* Format, ...)
|
||||||
va_end(Args);
|
va_end(Args);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Printing Helper Functions
|
||||||
|
static u32
|
||||||
|
GetU32NumberOfCharactersNeeded(u32 Value, u32 Base)
|
||||||
|
{
|
||||||
|
u32 Result = 0;
|
||||||
|
u32 ValueLeft = Value;
|
||||||
|
// NOTE(Peter): This is in a do while loop because even if the number is 0,
|
||||||
|
// it'll still take one character to display that.
|
||||||
|
do
|
||||||
|
{
|
||||||
|
Result += 1;
|
||||||
|
ValueLeft /= Base;
|
||||||
|
}while (ValueLeft > 0);
|
||||||
|
return Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static u32
|
||||||
|
GetS32NumberOfCharactersNeeded(s32 Value, s32 Base)
|
||||||
|
{
|
||||||
|
u32 Result = 0;
|
||||||
|
s32 ValueLeft = Value;
|
||||||
|
if (Value < 0)
|
||||||
|
{
|
||||||
|
Result += 1;
|
||||||
|
ValueLeft = Value * -1;
|
||||||
|
}
|
||||||
|
// NOTE(Peter): This is in a do while loop because even if the number is 0,
|
||||||
|
// it'll still take one character to display that.
|
||||||
|
do
|
||||||
|
{
|
||||||
|
Result += 1;
|
||||||
|
ValueLeft /= Base;
|
||||||
|
}while (ValueLeft > 0);
|
||||||
|
return Result;
|
||||||
|
}
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////
|
||||||
// String Memory Function Definitions
|
// String Memory Function Definitions
|
||||||
////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////
|
||||||
|
|
|
@ -627,20 +627,14 @@ v4 HSVToRGB (v4 In)
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
PointIsInRange (
|
PointIsInRange (v2 _P, v2 _Min, v2 _Max)
|
||||||
v2 _P,
|
|
||||||
v2 _Min, v2 _Max
|
|
||||||
)
|
|
||||||
{
|
{
|
||||||
return (_P.x >= _Min.x && _P.x <= _Max.x &&
|
return (_P.x >= _Min.x && _P.x <= _Max.x &&
|
||||||
_P.y >= _Min.y && _P.y <= _Max.y);
|
_P.y >= _Min.y && _P.y <= _Max.y);
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
PointIsInRangeSafe (
|
PointIsInRangeSafe (v2 _P, v2 _Min, v2 _Max)
|
||||||
v2 _P,
|
|
||||||
v2 _Min, v2 _Max
|
|
||||||
)
|
|
||||||
{
|
{
|
||||||
s32 MinX = GSMin(_Min.x, _Max.x);
|
s32 MinX = GSMin(_Min.x, _Max.x);
|
||||||
s32 MinY = GSMin(_Min.y, _Max.y);
|
s32 MinY = GSMin(_Min.y, _Max.y);
|
||||||
|
@ -666,6 +660,15 @@ PointToPercentRange (v2 P, v2 Min, v2 Max)
|
||||||
// RECT
|
// RECT
|
||||||
//////////////////////////////////////
|
//////////////////////////////////////
|
||||||
|
|
||||||
|
// NOTE(Peter): This is useful when you have a function that has a call signature like:
|
||||||
|
// void Foo(v2 Min, v2 Max)
|
||||||
|
// because instead of having to do:
|
||||||
|
// Foo(MyRect.Min, MyRect.Max)
|
||||||
|
// you can just do:
|
||||||
|
// Foo(RectExpand(MyRect))
|
||||||
|
// which makes refactoring easier as you only have to change the identifier in one place
|
||||||
|
#define RectExpand(r) (r).Min, (r).Max
|
||||||
|
|
||||||
inline float
|
inline float
|
||||||
Width (rect Rect)
|
Width (rect Rect)
|
||||||
{
|
{
|
||||||
|
@ -680,12 +683,43 @@ Height (rect Rect)
|
||||||
return Result;
|
return Result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline v2
|
||||||
|
TopLeft(rect Rect)
|
||||||
|
{
|
||||||
|
v2 Result = {0};
|
||||||
|
Result.x = Rect.Min.x;
|
||||||
|
Result.y = Rect.Max.y;
|
||||||
|
return Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline v2
|
||||||
|
TopRight(rect Rect)
|
||||||
|
{
|
||||||
|
return Rect.Max;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline v2
|
||||||
|
BottomLeft(rect Rect)
|
||||||
|
{
|
||||||
|
return Rect.Min;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline v2
|
||||||
|
BottomRight(rect Rect)
|
||||||
|
{
|
||||||
|
v2 Result = {0};
|
||||||
|
Result.x = Rect.Max.x;
|
||||||
|
Result.y = Rect.Min.y;
|
||||||
|
return Result;
|
||||||
|
}
|
||||||
|
|
||||||
inline float
|
inline float
|
||||||
AspectRatio (rect Rect)
|
AspectRatio (rect Rect)
|
||||||
{
|
{
|
||||||
float Result = Width(Rect) / Height(Rect);
|
float Result = Width(Rect) / Height(Rect);
|
||||||
return Result;
|
return Result;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline v2
|
inline v2
|
||||||
CalculateRectCenter (rect Rect)
|
CalculateRectCenter (rect Rect)
|
||||||
{
|
{
|
||||||
|
|
|
@ -32,6 +32,7 @@ struct animation_system
|
||||||
b32 TimelineShouldAdvance;
|
b32 TimelineShouldAdvance;
|
||||||
|
|
||||||
// Timeline
|
// Timeline
|
||||||
|
// TODO(Peter): Reverse these. Should be FrameStart and FrameEnd for consistency
|
||||||
r32 StartFrame;
|
r32 StartFrame;
|
||||||
r32 EndFrame;
|
r32 EndFrame;
|
||||||
};
|
};
|
||||||
|
|
|
@ -5,6 +5,70 @@
|
||||||
//
|
//
|
||||||
#ifndef FOLDHAUS_PANEL_ANIMATION_TIMELINE_H
|
#ifndef FOLDHAUS_PANEL_ANIMATION_TIMELINE_H
|
||||||
|
|
||||||
|
// Colors
|
||||||
|
global_variable v4 TimeSliderColor = v4{.36f, .52f, .78f, 1.f};
|
||||||
|
|
||||||
|
//
|
||||||
|
struct animation_timeline_state
|
||||||
|
{
|
||||||
|
s32 VisibleFrameRangeMin;
|
||||||
|
s32 VisibleFrameRangeMax;
|
||||||
|
};
|
||||||
|
|
||||||
|
// TODO(Peter): Move this over to the animation system just as frame_range
|
||||||
|
struct timeline_frame_range
|
||||||
|
{
|
||||||
|
u32 FrameMin;
|
||||||
|
u32 FrameMax;
|
||||||
|
};
|
||||||
|
|
||||||
|
inline b32
|
||||||
|
FrameIsInRange(u32 Frame, timeline_frame_range Range)
|
||||||
|
{
|
||||||
|
b32 Result = (Frame >= Range.FrameMin) && (Frame <= Range.FrameMax);
|
||||||
|
return Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal u32
|
||||||
|
GetFrameCount(timeline_frame_range Range)
|
||||||
|
{
|
||||||
|
u32 Result = Range.FrameMax - Range.FrameMin;
|
||||||
|
return Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal r32
|
||||||
|
FrameToPercentRange(s32 Frame, timeline_frame_range Range)
|
||||||
|
{
|
||||||
|
r32 Result = (r32)(Frame - Range.FrameMin);
|
||||||
|
Result = Result / GetFrameCount(Range);
|
||||||
|
return Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal u32
|
||||||
|
PercentToFrameInRange(r32 Percent, timeline_frame_range Range)
|
||||||
|
{
|
||||||
|
u32 Result = Range.FrameMin + (u32)(Percent * GetFrameCount(Range));
|
||||||
|
return Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal u32
|
||||||
|
ClampFrameToRange(u32 Frame, timeline_frame_range Range)
|
||||||
|
{
|
||||||
|
u32 Result = Frame;
|
||||||
|
if (Result < Range.FrameMin)
|
||||||
|
{
|
||||||
|
Result = Range.FrameMin;
|
||||||
|
}
|
||||||
|
else if (Result > Range.FrameMax)
|
||||||
|
{
|
||||||
|
Result = Range.FrameMax;
|
||||||
|
}
|
||||||
|
return Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
//
|
||||||
|
|
||||||
inline u32
|
inline u32
|
||||||
GetFrameFromPointInAnimationPanel(v2 Point, rect PanelBounds, u32 StartFrame, u32 EndFrame)
|
GetFrameFromPointInAnimationPanel(v2 Point, rect PanelBounds, u32 StartFrame, u32 EndFrame)
|
||||||
{
|
{
|
||||||
|
@ -97,15 +161,15 @@ input_command DragTimeMarkerCommands [] = {
|
||||||
};
|
};
|
||||||
|
|
||||||
internal void
|
internal void
|
||||||
StartDragTimeMarker(rect TimelineBounds, s32 PanelStartFrame, s32 PanelEndFrame, app_state* State)
|
StartDragTimeMarker(rect TimelineBounds, timeline_frame_range VisibleFrames, app_state* State)
|
||||||
{
|
{
|
||||||
operation_mode* DragTimeMarkerMode = ActivateOperationModeWithCommands(&State->Modes, DragTimeMarkerCommands, UpdateDragTimeMarker);
|
operation_mode* DragTimeMarkerMode = ActivateOperationModeWithCommands(&State->Modes, DragTimeMarkerCommands, UpdateDragTimeMarker);
|
||||||
|
|
||||||
drag_time_marker_operation_state* OpState = CreateOperationState(DragTimeMarkerMode,
|
drag_time_marker_operation_state* OpState = CreateOperationState(DragTimeMarkerMode,
|
||||||
&State->Modes,
|
&State->Modes,
|
||||||
drag_time_marker_operation_state);
|
drag_time_marker_operation_state);
|
||||||
OpState->StartFrame = PanelStartFrame;
|
OpState->StartFrame = VisibleFrames.FrameMin;
|
||||||
OpState->EndFrame = PanelEndFrame ;
|
OpState->EndFrame = VisibleFrames.FrameMax;
|
||||||
OpState->TimelineBounds = TimelineBounds;
|
OpState->TimelineBounds = TimelineBounds;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -240,7 +304,7 @@ input_command DragAnimationClipCommands [] = {
|
||||||
};
|
};
|
||||||
|
|
||||||
internal void
|
internal void
|
||||||
SelectAndBeginDragAnimationBlock(gs_list_handle BlockHandle, s32 PanelStartFrame, s32 PanelEndFrame, rect TimelineBounds, app_state* State)
|
SelectAndBeginDragAnimationBlock(gs_list_handle BlockHandle, timeline_frame_range VisibleRange, rect TimelineBounds, app_state* State)
|
||||||
{
|
{
|
||||||
SelectAnimationBlock(BlockHandle, State);
|
SelectAnimationBlock(BlockHandle, State);
|
||||||
|
|
||||||
|
@ -250,8 +314,8 @@ SelectAndBeginDragAnimationBlock(gs_list_handle BlockHandle, s32 PanelStartFrame
|
||||||
&State->Modes,
|
&State->Modes,
|
||||||
drag_animation_clip_state);
|
drag_animation_clip_state);
|
||||||
OpState->TimelineBounds = TimelineBounds;
|
OpState->TimelineBounds = TimelineBounds;
|
||||||
OpState->AnimationPanel_StartFrame = PanelStartFrame;
|
OpState->AnimationPanel_StartFrame = VisibleRange.FrameMin;
|
||||||
OpState->AnimationPanel_EndFrame = PanelEndFrame ;
|
OpState->AnimationPanel_EndFrame = VisibleRange.FrameMax;
|
||||||
|
|
||||||
animation_block* SelectedBlock = State->AnimationSystem.Blocks.GetElementWithHandle(BlockHandle);
|
animation_block* SelectedBlock = State->AnimationSystem.Blocks.GetElementWithHandle(BlockHandle);
|
||||||
OpState->SelectedClip_InitialStartFrame = SelectedBlock->StartFrame;
|
OpState->SelectedClip_InitialStartFrame = SelectedBlock->StartFrame;
|
||||||
|
@ -278,7 +342,11 @@ GSMetaTag(panel_type_animation_timeline);
|
||||||
internal void
|
internal void
|
||||||
AnimationTimeline_Init(panel* Panel, app_state* State)
|
AnimationTimeline_Init(panel* Panel, app_state* State)
|
||||||
{
|
{
|
||||||
|
// TODO: :FreePanelMemory
|
||||||
|
animation_timeline_state* TimelineState = PushStruct(&State->Permanent, animation_timeline_state);
|
||||||
|
TimelineState->VisibleFrameRangeMin = State->AnimationSystem.StartFrame;
|
||||||
|
TimelineState->VisibleFrameRangeMax = State->AnimationSystem.EndFrame;
|
||||||
|
Panel->PanelStateMemory = (u8*)TimelineState;
|
||||||
}
|
}
|
||||||
|
|
||||||
GSMetaTag(panel_cleanup);
|
GSMetaTag(panel_cleanup);
|
||||||
|
@ -289,57 +357,131 @@ AnimationTimeline_Cleanup(panel* Panel, app_state* State)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
internal r32
|
internal void
|
||||||
DrawFrameBar (animation_system* AnimationSystem, render_command_buffer* RenderBuffer, s32 StartFrame, s32 EndFrame, rect PanelBounds, mouse_state Mouse, app_state* State)
|
DrawFrameBar (animation_system* AnimationSystem, render_command_buffer* RenderBuffer, timeline_frame_range VisibleFrames, rect BarBounds, mouse_state Mouse, app_state* State)
|
||||||
{
|
{
|
||||||
MakeStringBuffer(TempString, 256);
|
MakeStringBuffer(TempString, 256);
|
||||||
|
|
||||||
s32 FrameCount = EndFrame - StartFrame;
|
s32 VisibleFrameCount = VisibleFrames.FrameMax - VisibleFrames.FrameMin;
|
||||||
|
|
||||||
r32 FrameBarHeight = 24;
|
r32 BarHeight = Height(BarBounds);
|
||||||
v2 FrameBarMin = v2{PanelBounds.Min.x, PanelBounds.Max.y - FrameBarHeight};
|
r32 BarWidth = Width(BarBounds);
|
||||||
v2 FrameBarMax = PanelBounds.Max;
|
|
||||||
|
|
||||||
PushRenderQuad2D(RenderBuffer, FrameBarMin, FrameBarMax, v4{.16f, .16f, .16f, 1.f});
|
PushRenderQuad2D(RenderBuffer, RectExpand(BarBounds), v4{.16f, .16f, .16f, 1.f});
|
||||||
|
|
||||||
// Mouse clicked inside frame nubmer bar -> change current frame on timeline
|
// Mouse clicked inside frame nubmer bar -> change current frame on timeline
|
||||||
if (MouseButtonTransitionedDown(Mouse.LeftButtonState)
|
if (MouseButtonTransitionedDown(Mouse.LeftButtonState) &&
|
||||||
&& PointIsInRange(Mouse.DownPos, FrameBarMin, FrameBarMax))
|
PointIsInRange(Mouse.DownPos, RectExpand(BarBounds)))
|
||||||
{
|
{
|
||||||
StartDragTimeMarker(rect{FrameBarMin, FrameBarMax}, StartFrame, EndFrame, State);
|
StartDragTimeMarker(BarBounds, VisibleFrames, State);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Frame Ticks
|
// Frame Ticks
|
||||||
for (s32 f = 0; f < FrameCount; f += 10)
|
u32 TickCount = 10;
|
||||||
|
for (u32 Tick = 0; Tick < TickCount; Tick++)
|
||||||
{
|
{
|
||||||
s32 Frame = StartFrame + f;
|
r32 Percent = (r32)Tick / (r32)TickCount;
|
||||||
|
u32 Frame = PercentToFrameInRange(Percent, VisibleFrames);
|
||||||
PrintF(&TempString, "%d", Frame);
|
PrintF(&TempString, "%d", Frame);
|
||||||
|
r32 FramePercent = FrameToPercentRange(Frame, VisibleFrames);
|
||||||
r32 FramePercent = (r32)f / (r32)FrameCount;
|
r32 FrameX = GSLerp(BarBounds.Min.x, BarBounds.Max.x, FramePercent);
|
||||||
r32 FrameX = GSLerp(PanelBounds.Min.x, PanelBounds.Max.x, FramePercent);
|
v2 FrameTextPos = v2{FrameX, BarBounds.Min.y + 2};
|
||||||
v2 FrameTextPos = v2{FrameX, FrameBarMin.y + 2};
|
|
||||||
DrawString(RenderBuffer, TempString, State->Interface.Font, FrameTextPos, WhiteV4);
|
DrawString(RenderBuffer, TempString, State->Interface.Font, FrameTextPos, WhiteV4);
|
||||||
|
|
||||||
// Frame Vertical Slices
|
// Frame Vertical Slices
|
||||||
v2 LineTop = v2{FrameX, FrameBarMin.y};
|
v2 LineTop = v2{FrameX, BarBounds.Min.y};
|
||||||
v2 LineBottom = v2{FrameX + 1, PanelBounds.Min.y};
|
v2 LineBottom = v2{FrameX + 1, BarBounds.Min.y};
|
||||||
PushRenderQuad2D(RenderBuffer, LineTop, LineBottom, v4{.16f, .16f, .16f, 1.f});
|
PushRenderQuad2D(RenderBuffer, LineTop, LineBottom, v4{.16f, .16f, .16f, 1.f});
|
||||||
}
|
}
|
||||||
|
|
||||||
return FrameBarMin.y;
|
// Time Slider
|
||||||
|
if (FrameIsInRange(AnimationSystem->CurrentFrame, VisibleFrames))
|
||||||
|
{
|
||||||
|
r32 FrameAtPercentVisibleRange = FrameToPercentRange(AnimationSystem->CurrentFrame, VisibleFrames);
|
||||||
|
r32 SliderX = GSLerp(BarBounds.Min.x, BarBounds.Max.x, FrameAtPercentVisibleRange);
|
||||||
|
|
||||||
|
u32 FrameNumberCharCount = GetU32NumberOfCharactersNeeded(AnimationSystem->CurrentFrame);
|
||||||
|
// space for each character + a margin on either side
|
||||||
|
r32 SliderWidth = (8 * FrameNumberCharCount) + 8;
|
||||||
|
r32 SliderHalfWidth = SliderWidth / 2.f;
|
||||||
|
v2 HeadMin = v2{SliderX - SliderHalfWidth, BarBounds.Min.y};
|
||||||
|
v2 HeadMax = v2{SliderX + SliderHalfWidth, BarBounds.Max.y};
|
||||||
|
PushRenderQuad2D(RenderBuffer, HeadMin, HeadMax, TimeSliderColor);
|
||||||
|
|
||||||
|
PrintF(&TempString, "%d", AnimationSystem->CurrentFrame);
|
||||||
|
DrawString(RenderBuffer, TempString, State->Interface.Font, HeadMin + v2{6, 4}, WhiteV4);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal timeline_frame_range
|
||||||
|
DrawTimelineRangeBar (animation_system* AnimationSystem, animation_timeline_state* TimelineState, render_command_buffer* RenderBuffer, rect BarBounds, mouse_state Mouse)
|
||||||
|
{
|
||||||
|
timeline_frame_range Result = {0};
|
||||||
|
|
||||||
|
r32 BarHeight = Height(BarBounds);
|
||||||
|
r32 BarWidth = Width(BarBounds);
|
||||||
|
PushRenderQuad2D(RenderBuffer, RectExpand(BarBounds), v4{.16f, .16f, .16f, 1.f});
|
||||||
|
|
||||||
|
r32 PlayableFrames = (r32)(AnimationSystem->EndFrame - AnimationSystem->StartFrame);
|
||||||
|
v2 SliderBarDim = v2{25, BarHeight};
|
||||||
|
|
||||||
|
// Convert Frames To Pixels
|
||||||
|
// TODO(Peter): When the animation system is storing a frame range rather than individual values
|
||||||
|
// come back and use the utility functions here
|
||||||
|
r32 RangeMinPercentPlayable = (r32)(TimelineState->VisibleFrameRangeMin - AnimationSystem->StartFrame) / PlayableFrames;
|
||||||
|
r32 RangeMaxPercentPlayable = (r32)(TimelineState->VisibleFrameRangeMax - AnimationSystem->StartFrame) / PlayableFrames;
|
||||||
|
v2 RangeMinSliderMin = v2{BarBounds.Min.x + (RangeMinPercentPlayable * Width(BarBounds)), BarBounds.Min.y};
|
||||||
|
v2 RangeMaxSliderMin = v2{BarBounds.Min.x + (RangeMaxPercentPlayable * Width(BarBounds)) - 25, BarBounds.Min.y};
|
||||||
|
|
||||||
|
if (MouseButtonHeldDown(Mouse.LeftButtonState) ||
|
||||||
|
MouseButtonTransitionedUp(Mouse.LeftButtonState))
|
||||||
|
{
|
||||||
|
v2 MouseDragOffset = Mouse.Pos - Mouse.DownPos;
|
||||||
|
if (PointIsInRange(Mouse.DownPos, RangeMinSliderMin, RangeMinSliderMin + SliderBarDim))
|
||||||
|
{
|
||||||
|
r32 NewSliderX = RangeMinSliderMin.x + MouseDragOffset.x;
|
||||||
|
RangeMinSliderMin.x = GSClamp(BarBounds.Min.x, NewSliderX, RangeMaxSliderMin.x - SliderBarDim.x);
|
||||||
|
}
|
||||||
|
if (PointIsInRange(Mouse.DownPos, RangeMaxSliderMin, RangeMaxSliderMin + SliderBarDim))
|
||||||
|
{
|
||||||
|
r32 NewSliderX = RangeMaxSliderMin.x + MouseDragOffset.x;
|
||||||
|
RangeMaxSliderMin.x = GSClamp(RangeMinSliderMin.x + SliderBarDim.x, NewSliderX, BarBounds.Max.x - SliderBarDim.x);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
v2 RangeMinSliderMax = v2{RangeMinSliderMin.x + 25, BarBounds.Max.y};
|
||||||
|
v2 RangeMaxSliderMax = v2{RangeMaxSliderMin.x + 25, BarBounds.Max.y};
|
||||||
|
PushRenderQuad2D(RenderBuffer, RangeMinSliderMin, RangeMinSliderMax, v4{.8f, .8f, .8f, 1.f});
|
||||||
|
PushRenderQuad2D(RenderBuffer, RangeMaxSliderMin, RangeMaxSliderMax, v4{.8f, .8f, .8f, 1.f});
|
||||||
|
|
||||||
|
// Convert Pixels Back To Frames and store
|
||||||
|
RangeMinPercentPlayable = (RangeMinSliderMin.x - BarBounds.Min.x) / BarWidth;
|
||||||
|
RangeMaxPercentPlayable = (RangeMaxSliderMax.x - BarBounds.Min.x) / BarWidth;
|
||||||
|
u32 VisibleFrameCount = AnimationSystem->EndFrame - AnimationSystem->StartFrame;
|
||||||
|
Result.FrameMin = RangeMinPercentPlayable * VisibleFrameCount;
|
||||||
|
Result.FrameMax = RangeMaxPercentPlayable * VisibleFrameCount;
|
||||||
|
|
||||||
|
if (MouseButtonTransitionedUp(Mouse.LeftButtonState))
|
||||||
|
{
|
||||||
|
TimelineState->VisibleFrameRangeMin = Result.FrameMin;
|
||||||
|
TimelineState->VisibleFrameRangeMax = Result.FrameMax;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal rect
|
internal rect
|
||||||
DrawAnimationBlock (animation_block AnimationBlock, v4 BlockColor, s32 FrameCount, s32 StartFrame, rect TimelineBounds, render_command_buffer* RenderBuffer)
|
DrawAnimationBlock (animation_block AnimationBlock, v4 BlockColor, timeline_frame_range VisibleFrames, rect TimelineBounds, render_command_buffer* RenderBuffer)
|
||||||
{
|
{
|
||||||
rect BlockBounds = {};
|
rect BlockBounds = {};
|
||||||
|
|
||||||
r32 TimelineWidth = Width(TimelineBounds);
|
r32 TimelineWidth = Width(TimelineBounds);
|
||||||
|
|
||||||
r32 StartFramePercent = (r32)(AnimationBlock.StartFrame - StartFrame) / (r32)FrameCount;
|
u32 ClampedBlockStartFrame = ClampFrameToRange(AnimationBlock.StartFrame, VisibleFrames);
|
||||||
|
r32 StartFramePercent = FrameToPercentRange(ClampedBlockStartFrame, VisibleFrames);
|
||||||
r32 StartPosition = TimelineWidth * StartFramePercent;
|
r32 StartPosition = TimelineWidth * StartFramePercent;
|
||||||
|
|
||||||
r32 EndFramePercent = (r32)(AnimationBlock.EndFrame - StartFrame) / (r32)FrameCount;
|
u32 ClampedBlockEndFrame = ClampFrameToRange(AnimationBlock.EndFrame, VisibleFrames);
|
||||||
|
r32 EndFramePercent = FrameToPercentRange(ClampedBlockEndFrame, VisibleFrames);
|
||||||
r32 EndPosition = TimelineWidth * EndFramePercent;
|
r32 EndPosition = TimelineWidth * EndFramePercent;
|
||||||
|
|
||||||
BlockBounds.Min = TimelineBounds.Min + v2{StartPosition, 25};
|
BlockBounds.Min = TimelineBounds.Min + v2{StartPosition, 25};
|
||||||
|
@ -352,31 +494,33 @@ DrawAnimationBlock (animation_block AnimationBlock, v4 BlockColor, s32 FrameCoun
|
||||||
}
|
}
|
||||||
|
|
||||||
internal gs_list_handle
|
internal gs_list_handle
|
||||||
DrawAnimationTimeline (animation_system* AnimationSystem, s32 VisibleStartFrame, s32 VisibleEndFrame, rect PanelBounds, gs_list_handle SelectedBlockHandle, render_command_buffer* RenderBuffer, app_state* State, mouse_state Mouse)
|
DrawAnimationTimeline (animation_system* AnimationSystem, animation_timeline_state* TimelineState, rect PanelBounds, gs_list_handle SelectedBlockHandle, render_command_buffer* RenderBuffer, app_state* State, mouse_state Mouse)
|
||||||
{
|
{
|
||||||
string TempString = MakeString(PushArray(&State->Transient, char, 256), 256);
|
string TempString = MakeString(PushArray(&State->Transient, char, 256), 256);
|
||||||
s32 VisibleFrameCount = VisibleEndFrame - VisibleStartFrame;
|
|
||||||
|
|
||||||
gs_list_handle Result = SelectedBlockHandle;
|
gs_list_handle Result = SelectedBlockHandle;
|
||||||
|
|
||||||
r32 AnimationPanelHeight = PanelBounds.Max.y - PanelBounds.Min.y;
|
r32 AnimationPanelHeight = PanelBounds.Max.y - PanelBounds.Min.y;
|
||||||
r32 AnimationPanelWidth = PanelBounds.Max.x - PanelBounds.Min.x;
|
r32 AnimationPanelWidth = PanelBounds.Max.x - PanelBounds.Min.x;
|
||||||
|
|
||||||
{
|
rect TimeRangeBarBounds = {0};
|
||||||
r32 FirstPlayablePercentX = ((r32)(AnimationSystem->StartFrame - VisibleStartFrame) / (r32)VisibleFrameCount);
|
TimeRangeBarBounds.Min = PanelBounds.Min;
|
||||||
r32 LastPlayablePercentX = ((r32)(AnimationSystem->EndFrame - VisibleStartFrame) / (r32)VisibleFrameCount);
|
TimeRangeBarBounds.Max = { PanelBounds.Max.x, PanelBounds.Min.y + 24 };
|
||||||
|
|
||||||
v2 PlayableMin = v2{(FirstPlayablePercentX * AnimationPanelWidth) + PanelBounds.Min.x, PanelBounds.Min.y };
|
rect FrameBarBounds = {0};
|
||||||
v2 PlayableMax = v2{(LastPlayablePercentX * AnimationPanelWidth) + PanelBounds.Min.x, PanelBounds.Max.y };
|
FrameBarBounds.Min = { PanelBounds.Min.x, PanelBounds.Max.y - 32 };
|
||||||
|
FrameBarBounds.Max = PanelBounds.Max;
|
||||||
|
|
||||||
PushRenderQuad2D(RenderBuffer, PanelBounds.Min, PanelBounds.Max, v4{.16f, .16f, .16f, 1.f});
|
rect TimelineBounds = {0};
|
||||||
PushRenderQuad2D(RenderBuffer, PlayableMin, PlayableMax, v4{.22f, .22f, .22f, 1.f});
|
TimelineBounds.Min = TopLeft(TimeRangeBarBounds);
|
||||||
}
|
TimelineBounds.Max = BottomRight(FrameBarBounds);
|
||||||
|
|
||||||
r32 FrameBarBottom = DrawFrameBar(AnimationSystem, RenderBuffer, VisibleStartFrame, VisibleEndFrame, PanelBounds, Mouse, State);
|
timeline_frame_range AdjustedViewRange = {0};
|
||||||
|
AdjustedViewRange = DrawTimelineRangeBar(AnimationSystem, TimelineState, RenderBuffer, TimeRangeBarBounds, Mouse);
|
||||||
|
s32 VisibleFrameCount = AdjustedViewRange.FrameMax - AdjustedViewRange.FrameMin;
|
||||||
|
|
||||||
|
DrawFrameBar(AnimationSystem, RenderBuffer, AdjustedViewRange, FrameBarBounds, Mouse, State);
|
||||||
|
|
||||||
// Animation Blocks
|
// Animation Blocks
|
||||||
rect TimelineBounds = rect{ PanelBounds.Min, v2{PanelBounds.Max.x, FrameBarBottom} };
|
|
||||||
b32 MouseDownAndNotHandled = MouseButtonTransitionedDown(Mouse.LeftButtonState);
|
b32 MouseDownAndNotHandled = MouseButtonTransitionedDown(Mouse.LeftButtonState);
|
||||||
for (u32 i = 0; i < AnimationSystem->Blocks.Used; i++)
|
for (u32 i = 0; i < AnimationSystem->Blocks.Used; i++)
|
||||||
{
|
{
|
||||||
|
@ -386,38 +530,58 @@ DrawAnimationTimeline (animation_system* AnimationSystem, s32 VisibleStartFrame,
|
||||||
gs_list_handle CurrentBlockHandle = AnimationBlockEntry->Handle;
|
gs_list_handle CurrentBlockHandle = AnimationBlockEntry->Handle;
|
||||||
animation_block AnimationBlockAt = AnimationBlockEntry->Value;
|
animation_block AnimationBlockAt = AnimationBlockEntry->Value;
|
||||||
|
|
||||||
|
// If either end is in the range, we should draw it
|
||||||
|
b32 RangeIsVisible = (FrameIsInRange(AnimationBlockAt.StartFrame, AdjustedViewRange) ||
|
||||||
|
FrameIsInRange(AnimationBlockAt.EndFrame, AdjustedViewRange));
|
||||||
|
// If neither end is in the range, but the ends surround the visible range,
|
||||||
|
// we should still draw it.
|
||||||
|
RangeIsVisible |= (AnimationBlockAt.StartFrame <= AdjustedViewRange.FrameMin &&
|
||||||
|
AnimationBlockAt.EndFrame >= AdjustedViewRange.FrameMax);
|
||||||
|
if (RangeIsVisible)
|
||||||
|
{
|
||||||
v4 BlockColor = BlackV4;
|
v4 BlockColor = BlackV4;
|
||||||
if (GSListHandlesAreEqual(SelectedBlockHandle, CurrentBlockHandle))
|
if (GSListHandlesAreEqual(SelectedBlockHandle, CurrentBlockHandle))
|
||||||
{
|
{
|
||||||
BlockColor = PinkV4;
|
BlockColor = PinkV4;
|
||||||
}
|
}
|
||||||
|
rect BlockBounds = DrawAnimationBlock(AnimationBlockAt, BlockColor, AdjustedViewRange, TimelineBounds, RenderBuffer);
|
||||||
rect BlockBounds = DrawAnimationBlock(AnimationBlockAt, BlockColor, VisibleFrameCount, VisibleStartFrame, TimelineBounds, RenderBuffer);
|
|
||||||
|
|
||||||
if (PointIsInRange(Mouse.Pos, BlockBounds.Min, BlockBounds.Max)
|
if (PointIsInRange(Mouse.Pos, BlockBounds.Min, BlockBounds.Max)
|
||||||
&& MouseButtonTransitionedDown(Mouse.LeftButtonState))
|
&& MouseButtonTransitionedDown(Mouse.LeftButtonState))
|
||||||
{
|
{
|
||||||
MouseDownAndNotHandled = false;
|
MouseDownAndNotHandled = false;
|
||||||
SelectAndBeginDragAnimationBlock(CurrentBlockHandle, VisibleStartFrame, VisibleEndFrame, TimelineBounds, State);
|
SelectAndBeginDragAnimationBlock(CurrentBlockHandle, AdjustedViewRange, TimelineBounds, State);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Time Slider
|
// Time Slider
|
||||||
r32 TimePercent = (r32)(AnimationSystem->CurrentFrame - VisibleStartFrame) / (r32)VisibleFrameCount;
|
if (FrameIsInRange(AnimationSystem->CurrentFrame, AdjustedViewRange))
|
||||||
r32 SliderX = PanelBounds.Min.x + (AnimationPanelWidth * TimePercent);
|
{
|
||||||
v2 SliderMin = v2{SliderX, PanelBounds.Min.y};
|
r32 FrameAtPercentVisibleRange = FrameToPercentRange(AnimationSystem->CurrentFrame, AdjustedViewRange);
|
||||||
v2 SliderMax = v2{SliderX + 1, PanelBounds.Max.y - 25};
|
r32 SliderX = GSLerp(TimelineBounds.Min.x, TimelineBounds.Max.x, FrameAtPercentVisibleRange);
|
||||||
v4 TimeSliderColor = v4{.36f, .52f, .78f, 1.f};
|
v2 SliderMin = v2{SliderX, TimelineBounds.Min.y};
|
||||||
|
v2 SliderMax = v2{SliderX + 1, TimelineBounds.Max.y};
|
||||||
PushRenderQuad2D(RenderBuffer, SliderMin, SliderMax, TimeSliderColor);
|
PushRenderQuad2D(RenderBuffer, SliderMin, SliderMax, TimeSliderColor);
|
||||||
|
}
|
||||||
|
|
||||||
r32 SliderHalfWidth = 10;
|
PushRenderBoundingBox2D(RenderBuffer, RectExpand(TimeRangeBarBounds), 1.f, RedV4);
|
||||||
v2 HeadMin = v2{SliderX - SliderHalfWidth, SliderMax.y};
|
PushRenderBoundingBox2D(RenderBuffer, RectExpand(FrameBarBounds), 1.f, TealV4);
|
||||||
v2 HeadMax = v2{SliderX + SliderHalfWidth, PanelBounds.Max.y};
|
PushRenderBoundingBox2D(RenderBuffer, RectExpand(TimelineBounds), 1.f, PinkV4);
|
||||||
PushRenderQuad2D(RenderBuffer, HeadMin, HeadMax, TimeSliderColor);
|
|
||||||
|
|
||||||
PrintF(&TempString, "%d", AnimationSystem->CurrentFrame);
|
return Result;
|
||||||
DrawString(RenderBuffer, TempString, State->Interface.Font, HeadMin + v2{4, 4}, WhiteV4);
|
|
||||||
|
|
||||||
|
|
||||||
|
{
|
||||||
|
r32 FirstPlayablePercentX = FrameToPercentRange(AnimationSystem->StartFrame, AdjustedViewRange);
|
||||||
|
r32 LastPlayablePercentX = FrameToPercentRange(AnimationSystem->EndFrame, AdjustedViewRange);
|
||||||
|
|
||||||
|
v2 PlayableMin = v2{(FirstPlayablePercentX * AnimationPanelWidth) + PanelBounds.Min.x, PanelBounds.Min.y };
|
||||||
|
v2 PlayableMax = v2{(LastPlayablePercentX * AnimationPanelWidth) + PanelBounds.Min.x, PanelBounds.Max.y };
|
||||||
|
|
||||||
|
PushRenderQuad2D(RenderBuffer, PanelBounds.Min, PanelBounds.Max, v4{.16f, .16f, .16f, 1.f});
|
||||||
|
PushRenderQuad2D(RenderBuffer, PlayableMin, PlayableMax, v4{.22f, .22f, .22f, 1.f});
|
||||||
|
}
|
||||||
|
|
||||||
if (MouseDownAndNotHandled && PointIsInRect(Mouse.Pos, TimelineBounds))
|
if (MouseDownAndNotHandled && PointIsInRect(Mouse.Pos, TimelineBounds))
|
||||||
{
|
{
|
||||||
|
@ -484,6 +648,8 @@ GSMetaTag(panel_type_animation_timeline);
|
||||||
internal void
|
internal void
|
||||||
AnimationTimeline_Render(panel Panel, rect PanelBounds, render_command_buffer* RenderBuffer, app_state* State, context Context, mouse_state Mouse)
|
AnimationTimeline_Render(panel Panel, rect PanelBounds, render_command_buffer* RenderBuffer, app_state* State, context Context, mouse_state Mouse)
|
||||||
{
|
{
|
||||||
|
animation_timeline_state* TimelineState = (animation_timeline_state*)Panel.PanelStateMemory;
|
||||||
|
|
||||||
gs_list_handle SelectedBlockHandle = State->SelectedAnimationBlockHandle;
|
gs_list_handle SelectedBlockHandle = State->SelectedAnimationBlockHandle;
|
||||||
|
|
||||||
r32 OptionsRowHeight = 25;
|
r32 OptionsRowHeight = 25;
|
||||||
|
@ -495,16 +661,15 @@ AnimationTimeline_Render(panel Panel, rect PanelBounds, render_command_buffer* R
|
||||||
v2{AnimationClipListBounds.Max.x, PanelBounds.Min.y},
|
v2{AnimationClipListBounds.Max.x, PanelBounds.Min.y},
|
||||||
v2{PanelBounds.Max.x, PanelBounds.Max.y - OptionsRowHeight},
|
v2{PanelBounds.Max.x, PanelBounds.Max.y - OptionsRowHeight},
|
||||||
};
|
};
|
||||||
|
|
||||||
if (Height(TimelineBounds) > 0)
|
if (Height(TimelineBounds) > 0)
|
||||||
{
|
{
|
||||||
DrawAnimationClipsList(AnimationClipListBounds, Mouse, RenderBuffer, State);
|
|
||||||
|
|
||||||
SelectedBlockHandle = DrawAnimationTimeline(&State->AnimationSystem,
|
SelectedBlockHandle = DrawAnimationTimeline(&State->AnimationSystem,
|
||||||
State->AnimationSystem.StartFrame - 20,
|
TimelineState,
|
||||||
State->AnimationSystem.EndFrame + 20,
|
|
||||||
TimelineBounds,
|
TimelineBounds,
|
||||||
SelectedBlockHandle,
|
SelectedBlockHandle,
|
||||||
RenderBuffer, State, Mouse);
|
RenderBuffer, State, Mouse);
|
||||||
|
DrawAnimationClipsList(AnimationClipListBounds, Mouse, RenderBuffer, State);
|
||||||
}
|
}
|
||||||
|
|
||||||
v2 OptionsRowMin = v2{ PanelBounds.Min.x, TimelineBounds.Max.y };
|
v2 OptionsRowMin = v2{ PanelBounds.Min.x, TimelineBounds.Max.y };
|
||||||
|
|
|
@ -180,6 +180,7 @@ NodeGraph_Init(panel* Panel, app_state* State)
|
||||||
// TODO(Peter): We aren't able to free this memory. We need a system for
|
// TODO(Peter): We aren't able to free this memory. We need a system for
|
||||||
// taking fixed size chunks off the Memory stack and then reusing them. THis
|
// taking fixed size chunks off the Memory stack and then reusing them. THis
|
||||||
// should probably live outside the paneling system.
|
// should probably live outside the paneling system.
|
||||||
|
// TODO: :FreePanelMemory
|
||||||
Panel->PanelStateMemory = (u8*)PushStruct(&State->Permanent, node_graph_state);
|
Panel->PanelStateMemory = (u8*)PushStruct(&State->Permanent, node_graph_state);
|
||||||
node_graph_state* GraphState = (node_graph_state*)Panel->PanelStateMemory;
|
node_graph_state* GraphState = (node_graph_state*)Panel->PanelStateMemory;
|
||||||
GraphState->LayoutIsDirty = true;
|
GraphState->LayoutIsDirty = true;
|
||||||
|
|
4
todo.txt
4
todo.txt
|
@ -65,8 +65,8 @@ Ground Up Reengineering
|
||||||
|
|
||||||
- Animation System
|
- Animation System
|
||||||
x snapping clips
|
x snapping clips
|
||||||
- convert everything from time to frames
|
x convert everything from time to frames
|
||||||
- zoom in and out
|
x zoom in and out
|
||||||
- blending between animation
|
- blending between animation
|
||||||
- layers
|
- layers
|
||||||
- display more than one layer
|
- display more than one layer
|
||||||
|
|
Loading…
Reference in New Issue