Files
wnsrc/lua/pac3/editor/client/animation_timeline.lua
lifestorm 94063e4369 Upload
2024-08-04 22:55:00 +03:00

1142 lines
31 KiB
Lua

--[[
| This file was obtained through the combined efforts
| of Madbluntz & Plymouth Antiquarian Society.
|
| Credits: lifestorm, Gregory Wayne Rossel JR.,
| Maloy, DrPepper10 @ RIP, Atle!
|
| Visit for more: https://plymouth.thetwilightzone.ru/
--]]
local animations = pac.animations
local eases = animations.eases
local L = pace.LanguageString
pace.timeline = pace.timeline or {}
local timeline = pace.timeline
local secondDistance = 200 --100px per second on timeline
do
local BUILDER, PART = pac.PartTemplate("base_movable")
PART.ClassName = "timeline_dummy_bone"
PART.show_in_editor = false
PART.PropertyWhitelist = {
Position = true,
Angles = true,
Bone = true,
}
function PART:GetParentOwner()
return self:GetOwner()
end
function PART:GetBonePosition()
local ent = self:GetOwner()
local index = self:GetModelBoneIndex(self.Bone)
if not index then return ent:GetPos(), ent:GetAngles() end
pac.SetupBones(ent)
local m = ent:GetBoneMatrix(index)
local lm = Matrix()
lm:SetTranslation(self.Position)
lm:SetAngles(self.Angles)
m = m * lm:GetInverse()
if not m then return ent:GetPos(), ent:GetAngles() end
return m:GetTranslation(), m:GetAngles()
end
BUILDER:Register()
end
function timeline.IsActive()
return timeline.editing
end
local function check_tpose()
if not timeline.entity:IsPlayer() then return end
if timeline.data.Type == "sequence" then
pac.AddHook("CalcMainActivity", "pac3_timeline", function(ply)
if ply == timeline.entity then
local act = ply:LookupSequence("ragdoll") or ply:LookupSequence("reference")
return act, act
end
end)
else
pac.RemoveHook("CalcMainActivity", "pac3_timeline")
end
end
timeline.interpolation = "cosine"
function timeline.SetInterpolation(str)
timeline.interpolation = str
timeline.data = timeline.data or {FrameData = {}}
timeline.data.Interpolation = timeline.interpolation
timeline.Save()
end
timeline.animation_type = "sequence"
function timeline.SetAnimationType(str)
timeline.animation_type = str
timeline.frame.add_keyframe_button:SetDisabled(timeline.animation_type == "posture")
timeline.data = timeline.data or {FrameData = {}}
timeline.data.Type = timeline.animation_type
timeline.Save()
end
function timeline.SetCycle(f)
animations.SetEntityAnimationCycle(timeline.entity, timeline.animation_part:GetAnimID(), f)
end
function timeline.GetCycle()
return animations.GetEntityAnimationCycle(timeline.entity, timeline.animation_part:GetAnimID()) or 0
end
function timeline.Stop()
timeline.frame:Stop()
end
function timeline.UpdateFrameData()
if not timeline.selected_keyframe or not timeline.selected_bone then return end
local data = timeline.selected_keyframe:GetData().BoneInfo[timeline.selected_bone] or {}
data.MF = data.MF or 0
data.MR = data.MR or 0
data.MU = data.MU or 0
data.RR = data.RR or 0
data.RU = data.RU or 0
data.RF = data.RF or 0
timeline.dummy_bone:SetPosition(Vector(data.MF, -data.MR, data.MU))
timeline.dummy_bone:SetAngles(Angle(data.RR, data.RU, data.RF))
end
function timeline.EditBone()
pace.Call("PartSelected", timeline.dummy_bone)
local boneData = pac.GetModelBones(timeline.entity)
timeline.selected_bone = timeline.dummy_bone and
timeline.dummy_bone:GetBone() and
boneData[timeline.dummy_bone:GetBone()] and
boneData[timeline.dummy_bone:GetBone()].real
or false
if not timeline.selected_bone then
for k, v in pairs(boneData) do
if not v.is_special and not v.is_attachment then
timeline.selected_bone = v.real
break
end
end
if not timeline.selected_bone then
timeline.selected_bone = '????'
end
end
timeline.UpdateFrameData()
pace.PopulateProperties(timeline.dummy_bone)
check_tpose()
end
function timeline.Load(data)
timeline.data = data
if data and data.FrameData then
animations.ConvertOldData(data)
if not data.Type then
data.Type = 0
local frames = {}
for k, v in pairs(data.FrameData) do
table.insert(frames, v)
end
data.FrameData = frames
end
timeline.animation_part:SetInterpolation(data.Interpolation)
timeline.animation_part:SetAnimationType(data.Type)
timeline.frame:Clear()
for i, v in ipairs(data.FrameData) do
local keyframe = timeline.frame:AddKeyFrame(true)
keyframe:SetFrameData(i, v)
end
timeline.SelectKeyframe(timeline.frame.keyframe_scroll:GetCanvas():GetChildren()[1])
else
timeline.data = {FrameData = {}, Type = timeline.animation_type, Interpolation = timeline.interpolation}
timeline.frame:Clear()
timeline.SelectKeyframe(timeline.frame:AddKeyFrame())
timeline.Save()
end
timeline.UpdateFrameData()
end
function timeline.Save()
local data = table.Copy(timeline.data)
local part = timeline.animation_part
timer.Create("pace_timeline_save", 0.1, 1, function()
if part and part:IsValid() then
animations.RegisterAnimation(part:GetAnimID(), data)
if part:GetURL() ~= "" then
file.Write("pac3/__animations/" .. part:GetName() .. ".txt", util.TableToJSON(data))
part:SetData("")
else
part.Data = util.TableToJSON(data)
timer.Create("pace_backup", 1, 1, function() pace.Backup() end)
end
end
end)
end
function timeline.SelectKeyframe(keyframe)
timeline.selected_keyframe = keyframe
timeline.UpdateFrameData()
timeline.EditBone()
timeline.Save()
animations.SetEntityAnimationFrame(timeline.entity, timeline.animation_part:GetAnimID(), keyframe.AnimationKeyIndex, 1)
timeline.frame:Pause()
end
function timeline.IsEditingBone()
return timeline.dummy_bone == pace.current_part
end
function timeline.Close()
timeline.Save()
-- old animeditor behavior
if timeline.animation_part:GetURL() ~= "" then
file.Write("pac3/__animations/backups/previous_session_"..os.date("%m%d%y%H%M%S")..".txt", util.TableToJSON(timeline.data))
end
timeline.editing = false
if timeline.entity:IsValid() then
timeline.Stop()
end
timeline.animation_part = nil
timeline.frame:Remove()
if timeline.dummy_bone and timeline.dummy_bone:IsValid() then
timeline.dummy_bone:Remove()
end
pac.RemoveHook("pace_OnVariableChanged", "pac3_timeline")
pac.RemoveHook("CalcMainActivity", "pac3_timeline")
end
function timeline.Open(part)
file.CreateDir("pac3")
file.CreateDir("pac3/__animations")
file.CreateDir("pac3/__animations/backups")
timeline.editing = false
timeline.first_pass = true
timeline.editing = true
timeline.animation_part = part
timeline.entity = part:GetOwner()
timeline.frame = vgui.Create("pac3_timeline")
timeline.frame:SetSize(ScrW()-pace.Editor:GetWide(),93)
timeline.frame:SetPos(pace.Editor:GetWide(),ScrH()-timeline.frame:GetTall())
timeline.frame:SetTitle("")
timeline.frame:ShowCloseButton(false)
timeline.SetAnimationType(part.AnimationType)
if timeline.dummy_bone and timeline.dummy_bone:IsValid() then timeline.dummy_bone:Remove() end
timeline.dummy_bone = pac.CreatePart("timeline_dummy_bone", timeline.entity)
timeline.dummy_bone:SetOwner(timeline.entity)
pac.AddHook("pace_OnVariableChanged", "pac3_timeline", function(part, key, val)
if part == timeline.dummy_bone then
if key == "Bone" then
local boneData = pac.GetModelBones(timeline.entity)
timeline.selected_bone = boneData[val] and boneData[val].real or false
if not timeline.selected_bone then
for k, v in pairs(boneData) do
if not v.is_special and not v.is_attachment then
timeline.selected_bone = v.real
break
end
end
if not timeline.selected_bone then
timeline.selected_bone = '????'
end
end
timer.Simple(0, function() timeline.EditBone() end) -- post variable changed?
else
local data = timeline.selected_keyframe:GetData()
data.BoneInfo = data.BoneInfo or {}
data.BoneInfo[timeline.selected_bone] = data.BoneInfo[timeline.selected_bone] or {}
data.BoneInfo[timeline.selected_bone].MF = data.BoneInfo[timeline.selected_bone].MF or 0
data.BoneInfo[timeline.selected_bone].MR = data.BoneInfo[timeline.selected_bone].MR or 0
data.BoneInfo[timeline.selected_bone].MU = data.BoneInfo[timeline.selected_bone].MU or 0
data.BoneInfo[timeline.selected_bone].RR = data.BoneInfo[timeline.selected_bone].RR or 0
data.BoneInfo[timeline.selected_bone].RU = data.BoneInfo[timeline.selected_bone].RU or 0
data.BoneInfo[timeline.selected_bone].RF = data.BoneInfo[timeline.selected_bone].RF or 0
if key == "Position" then
data.BoneInfo[timeline.selected_bone].MF = val.x
data.BoneInfo[timeline.selected_bone].MR = -val.y
data.BoneInfo[timeline.selected_bone].MU = val.z
elseif key == "Angles" then
data.BoneInfo[timeline.selected_bone].RR = val.p
data.BoneInfo[timeline.selected_bone].RU = val.y
data.BoneInfo[timeline.selected_bone].RF = val.r
end
end
timeline.Save()
elseif part == timeline.animation_part then
if key == "Data" or key == "URL" then
timeline.Load(animations.GetRegisteredAnimations()[part:GetAnimID()])
elseif key == "AnimationType" then
timeline.SetAnimationType(val)
elseif key == "Interpolation" then
timeline.SetInterpolation(val)
elseif key == "Rate" then
timeline.data.TimeScale = val
timeline.Save()
elseif key == "BonePower" then
timeline.data.Power = val
timeline.Save()
end
end
end)
timeline.Load(animations.GetRegisteredAnimations()[part:GetAnimID()])
pac.RemoveHook("CalcMainActivity", "pac3_timeline")
timeline.Stop()
end
pac.AddHook("pace_OnPartSelected", "pac3_timeline", function(part)
if part.ClassName == "timeline_dummy_bone" then return end
if part.ClassName == "custom_animation" then
if timeline.editing then
timeline.Close()
end
timeline.Open(part)
elseif timeline.editing then
timeline.Close()
end
end)
do
local TIMELINE = {}
function TIMELINE:Init()
self:DockMargin(0,0,0,0)
self:DockPadding(0,35,0,0)
do -- time display info
local time = self:Add("DPanel")
local test = L"frame" .. ": 10.888"
surface.SetFont(pace.CurrentFont)
local w,h = surface.GetTextSize(test)
time:SetWide(w)
time:SetTall(h*2 + 2)
time:SetPos(0,1)
time.Paint = function(s, w,h)
self:GetSkin().tex.Tab_Control( 0, 0, w, h )
self:GetSkin().tex.CategoryList.Header( 0, 0, w, h )
if not timeline.animation_part then return end
local w,h = draw.TextShadow({
text = L"frame" .. ": " .. (animations.GetEntityAnimationFrame(timeline.entity, timeline.animation_part:GetAnimID()) or 0),
font = pace.CurrentFont,
pos = {2, 0},
color = self:GetSkin().Colours.Category.Header
}, 1, 100)
draw.TextShadow({
text = L"time" .. ": " .. math.Round(timeline.GetCycle() * animations.GetAnimationDuration(timeline.entity, timeline.animation_part:GetAnimID()), 3),
font = pace.CurrentFont,
pos = {2, h},
color = self:GetSkin().Colours.Category.Header
}, 1, 100)
end
end
do
local bottom = vgui.Create("DPanel", self)
bottom:Dock(RIGHT)
bottom:SetWide(72)
do -- time controls
local controls = bottom:Add("DPanel")
controls:SetWide(100)
controls:SetTall(bottom:GetTall())
controls:Dock(BOTTOM)
controls:SetTall(36)
local size = 36
local spacing = (size - 24)/2
local play = controls:Add("DButton")
play:SetSize(size,size)
play:SetText("")
play.DoClick = function() self:Toggle() end
play:Dock(LEFT)
local stop = controls:Add("DButton")
stop:SetSize(size,size)
stop:SetText("")
stop.DoClick = function() self:Stop() end
stop:Dock(LEFT)
function play.PaintOver(_,w,h)
surface.SetDrawColor(self:GetSkin().Colours.Button.Normal)
draw.NoTexture()
if self:IsPlaying() then
surface.DrawRect(spacing, spacing, 10, h - spacing * 2)
surface.DrawRect(spacing + 13, spacing, 10, h - spacing * 2)
else
surface.DrawPoly({
{ x = spacing, y = spacing },
{ x = w - spacing, y = h / 2 },
{ x = spacing, y = h - spacing },
})
end
end
function stop:PaintOver(w,h)
surface.SetDrawColor(self:GetSkin().Colours.Button.Normal)
surface.DrawRect(spacing,spacing,24,24)
end
end
do -- save/load
local saveload = bottom:Add("DPanel")
saveload:SetWide(100)
saveload:SetTall(bottom:GetTall())
saveload:Dock(TOP)
saveload:SetTall(16)
local add = saveload:Add("DImageButton")
add:SetImage("icon16/add.png")
add:SetTooltip(L"add keyframe")
add:SizeToContents()
add.DoClick = function() timeline.SelectKeyframe(self:AddKeyFrame()) timeline.Save() end
add:Dock(LEFT)
add:SetDisabled(true)
self.add_keyframe_button = add
local bone = saveload:Add("DImageButton")
bone:SetImage("icon16/connect.png")
bone:SetTooltip(L"edit bones")
bone:SizeToContents()
bone:Dock(LEFT)
bone.DoClick = function()
timeline.EditBone()
end
local save = saveload:Add("DImageButton")
save:SetImage("icon16/disk.png")
save:SetTooltip(L"save")
save:SizeToContents()
save:Dock(RIGHT)
save.DoClick = function()
Derma_StringRequest(
L"question",
L"save as",
timeline.animation_part:GetName(),
function(name)
animations.RegisterAnimation(name, table.Copy(timeline.data))
file.Write("pac3/__animations/" .. name .. ".txt", util.TableToJSON(timeline.data)) end,
function() end,
L"save",
L"cancel"
)
end
local load = saveload:Add("DImageButton")
load:SetImage("icon16/folder.png")
load:SizeToContents()
load:Dock(RIGHT)
load:SetTooltip(L"load")
load.DoClick = function()
local menu = DermaMenu()
menu:SetPos(load:LocalToScreen())
for _, name in pairs(file.Find("animations/*.txt", "DATA")) do
menu:AddOption(name:match("(.+)%.txt"), function()
timeline.Load(util.JSONToTable(file.Read("animations/" .. name)))
end)
end
for _, name in pairs(file.Find("pac3/__animations/*.txt", "DATA")) do
menu:AddOption(name:match("(.+)%.txt"), function()
timeline.Load(util.JSONToTable(file.Read("pac3/__animations/" .. name)))
end)
end
menu:PerformLayout()
local x, y = bottom:LocalToScreen(0,0)
x = x + bottom:GetWide()
menu:SetPos(x - menu:GetWide(), y - menu:GetTall())
end
end
end
do -- keyframes
local pnl = vgui.Create("pac_scrollpanel_horizontal",self)
pnl:Dock(FILL)
pnl:GetCanvas().Paint = function(_,w,h)
derma.SkinHook( "Paint", "ListBox", self, w, h )
end
pnl.PaintOver = function()
if not timeline.animation_part then return end
local offset = -self.keyframe_scroll:GetCanvas():GetPos()
local x = timeline.GetCycle() * self.keyframe_scroll:GetCanvas():GetWide()
--self.keyframe_scroll.VBar:SetScroll(x - self.keyframe_scroll:GetWide()/2)
end
local old = pnl.PerformLayout
function pnl.PerformLayout()
old(pnl)
local h = self:GetTall() - 45
pnl:GetCanvas():SetTall(h)
if self.moving then return end
local x = 0
for k,v in ipairs(pnl:GetCanvas():GetChildren()) do
v:SetWide(math.max(1/v:GetData().FrameRate * secondDistance, 4))
v:SetTall(h)
v:SetPos(x, 0)
x = x + v:GetWide()
end
end
self.keyframe_scroll = pnl
end
do -- timeline
local pnl = vgui.Create("DPanel",self)
surface.SetFont(pace.CurrentFont)
local _, h = surface.GetTextSize("|")
pnl:SetTall(h + 2)
pnl:Dock(TOP)
pnl:NoClipping(true)
pnl:SetCursor("sizewe")
pnl.Think = function(_)
if (self.dragging or pnl:IsHovered()) and input.IsMouseDown(MOUSE_LEFT) then
if not self:IsPlaying() then
self:Play()
self:Pause()
end
if timeline.data and timeline.data.FrameData then
local X = -self.keyframe_scroll:GetCanvas():GetPos() + pnl:ScreenToLocal(gui.MouseX(), 0)
X = X / self.keyframe_scroll:GetCanvas():GetWide()
timeline.SetCycle(X)
end
self.dragging = true
end
if not input.IsMouseDown(MOUSE_LEFT) then
self.dragging = false
end
end
local scrub = Material("icon16/bullet_arrow_down.png")
local start = Material("icon16/control_play_blue.png")
local restart = Material("icon16/control_repeat_blue.png")
local estyle = Material("icon16/arrow_branch.png")
pnl.Paint = function(s,w,h)
local offset = -self.keyframe_scroll:GetCanvas():GetPos()
local esoffset = self.keyframe_scroll:GetCanvas():GetPos()
self:GetSkin().tex.Tab_Control( 0, 0, w, h )
self:GetSkin().tex.CategoryList.Header( 0, 0, w, h )
local previousSecond = offset-(offset%secondDistance)
for i=previousSecond,previousSecond+s:GetWide(),secondDistance/2 do
if i-offset > 0 and i-offset < ScrW() then
local sec = i/secondDistance
local x = i-offset
surface.SetDrawColor(0,0,0,100)
surface.DrawLine(x+1, 1+1, x+1, pnl:GetTall() - 3+1)
surface.SetDrawColor(self:GetSkin().Colours.Category.Header)
surface.DrawLine(x, 1, x, pnl:GetTall() - 3)
surface.SetTextPos(x+2+1, 1+1)
surface.SetFont(pace.CurrentFont)
surface.SetTextColor(0,0,0,100)
surface.DrawText(sec)
surface.SetTextPos(x+2, 1)
surface.SetFont(pace.CurrentFont)
surface.SetTextColor(self:GetSkin().Colours.Category.Header)
surface.DrawText(sec)
end
end
for i=previousSecond,previousSecond+s:GetWide(),secondDistance/8 do
if i-offset > 0 and i-offset < ScrW() then
local x = i-offset
surface.SetDrawColor(0,0,0,100)
surface.DrawLine(x+1, 1+1, x+1, pnl:GetTall()/2+1)
surface.SetDrawColor(self:GetSkin().Colours.Category.Header)
surface.DrawLine(x, 1, x, pnl:GetTall()/2)
end
end
local h = self.keyframe_scroll:GetCanvas():GetTall() + pnl:GetTall()
if self.keyframe_scroll:GetVBar():IsVisible() then
h = h - self.keyframe_scroll:GetVBar():GetTall() + 5
end
for i, v in ipairs(self.keyframe_scroll:GetCanvas():GetChildren()) do
local mat = v.restart and restart or v.start and start or false
local esmat = v.estyle and estyle or false
if mat then
local x = v:GetPos()
surface.SetDrawColor(255,255,255,200)
surface.DrawLine(x, -mat:Height()/2 - 5, x, h)
surface.SetDrawColor(255,255,255,255)
surface.SetMaterial(mat)
surface.DrawTexturedRect(1+x,mat:Height() - 5,mat:Width(), mat:Height())
end
if esmat then
local ps = v:GetSize()
local x = v:GetPos() + (ps * 0.5)
surface.SetDrawColor(255,255,255,255)
surface.SetMaterial(esmat)
surface.DrawTexturedRect(1+x - (esmat:Width() * 0.5), esmat:Height(),esmat:Width(), esmat:Height())
if ps >= 65 then
draw.SimpleText( v.estyle, "Default", x, esmat:Height() * 2, color_white, TEXT_ALIGN_CENTER, TEXT_ALIGN_TOP )
end
end
end
if not timeline.animation_part then return end
local x = timeline.GetCycle() * self.keyframe_scroll:GetCanvas():GetWide()
x = x - offset
surface.SetDrawColor(255,0,0,200)
surface.DrawLine(x, 0, x, h)
surface.SetDrawColor(255,0,0,255)
surface.SetMaterial(scrub)
surface.DrawTexturedRect(1 + x - scrub:Width()/2,-11,scrub:Width(), scrub:Height())
end
end
end
function TIMELINE:Paint(w,h)
self:GetSkin().tex.Tab_Control(0, 35, w, h-35)
end
function TIMELINE:Think()
DFrame.Think(self)
if pace.Editor:GetPos() + pace.Editor:GetWide() / 2 < ScrW() / 2 then
self:SetSize(ScrW()-(pace.Editor.x+pace.Editor:GetWide()),93)
self:SetPos(pace.Editor.x+pace.Editor:GetWide(),ScrH()-self:GetTall())
else
self:SetSize(ScrW()-(ScrW()-pace.Editor.x),93)
self:SetPos(0,ScrH()-self:GetTall())
end
if input.IsKeyDown(KEY_SPACE) then
if not self.toggled then
self:Toggle()
self.toggled = true
end
else
self.toggled = false
end
end
function TIMELINE:Play()
animations.RegisterAnimation(timeline.animation_part:GetAnimID(), timeline.data)
animations.SetEntityAnimation(timeline.entity, timeline.animation_part:GetAnimID())
animations.GetEntityAnimation(timeline.entity, timeline.animation_part:GetAnimID()).Paused = false
self.playing = true
end
function TIMELINE:OnMouseWheeled(dt)
if input.IsControlDown() then
secondDistance = secondDistance + dt * 10
end
end
function TIMELINE:Pause()
local anim = animations.GetEntityAnimation(timeline.entity, timeline.animation_part:GetAnimID())
if not anim then return end
animations.GetEntityAnimation(timeline.entity, timeline.animation_part:GetAnimID()).Paused = true
self.playing = false
end
function TIMELINE:IsPlaying()
return self.playing
end
function TIMELINE:Toggle()
if self:IsPlaying() then
self:Pause()
else
self:Play()
end
end
function TIMELINE:Stop()
self:Pause()
animations.StopAllEntityAnimations(timeline.entity)
animations.ResetEntityBoneMatrix(timeline.entity)
end
function TIMELINE:Clear()
for i,v in pairs(self.keyframe_scroll:GetCanvas():GetChildren()) do
v:Remove()
end
self.add_keyframe_button:SetDisabled(false)
end
function TIMELINE:GetAnimationTime()
local total = 0
if timeline.data and timeline.data.FrameData then
for i=1, #timeline.data.FrameData do
local v = timeline.data.FrameData[i]
total = total+(1/(v.FrameRate or 1))
end
end
return total
end
function TIMELINE:ResolveRestart() --get restart pos in seconds
timeline.first_pass = false
local timeInSeconds = 0
local restartFrame = timeline.data.RestartFrame
if not restartFrame then return 0 end --no restart pos? start at the start
for i,v in ipairs(timeline.data.FrameData) do
if i == restartFrame then return timeInSeconds end
timeInSeconds = timeInSeconds+(1/(v.FrameRate or 1))
end
return 0
end
function TIMELINE:ResolveStart() --get restart pos in seconds
timeline.first_pass = true
local timeInSeconds = 0
local startFrame = timeline.data.StartFrame
if not startFrame then return 0 end --no restart pos? start at the start
for i,v in ipairs(timeline.data.FrameData) do
if i == startFrame then return timeInSeconds end
timeInSeconds = timeInSeconds+(1/(v.FrameRate or 1))
end
return 0
end
function TIMELINE:AddKeyFrame(raw)
local keyframe = vgui.Create("pac3_timeline_keyframe")
if not raw then
keyframe.AnimationKeyIndex = table.insert(timeline.data.FrameData, {FrameRate = 1, BoneInfo = {}})
keyframe.DataTable = timeline.data.FrameData[keyframe.AnimationKeyIndex]
end
keyframe:SetWide(secondDistance) --default to 1 second animations
keyframe:SetParent(self.keyframe_scroll)
self.keyframe_scroll:InvalidateLayout()
keyframe.Alternate = #timeline.frame.keyframe_scroll:GetCanvas():GetChildren()%2 == 1
return keyframe
end
vgui.Register("pac3_timeline",TIMELINE,"DFrame")
end
do
local KEYFRAME = {}
function KEYFRAME:Init()
self:SetCursor("hand")
end
function KEYFRAME:OnCursorMoved(x, y)
if x > self:GetWide() - 4 then
self:SetCursor("sizewe")
else
self:SetCursor("hand")
end
end
function KEYFRAME:SetStart(b)
self.start = b
end
function KEYFRAME:GetStart()
return self.start
end
function KEYFRAME:SetRestart(b)
self.restart = b
end
function KEYFRAME:GetRestart()
return self.restart
end
function KEYFRAME:GetData()
return self.DataTable
end
function KEYFRAME:SetFrameData(index,tbl)
self.DataTable = tbl
self.AnimationKeyIndex = index
self:GetParent():GetParent():InvalidateLayout() --rebuild the timeline
if tbl.EaseStyle then
self.estyle = tbl.EaseStyle
end
if timeline.data.RestartFrame == index then
self:SetRestart(true)
end
if timeline.data.StartFrame == index then
self:SetStart(true)
end
end
function KEYFRAME:GetAnimationIndex()
return self.AnimationKeyIndex
end
function KEYFRAME:Paint(w,h)
self.AltLine = self.Alternate
derma.SkinHook( "Paint", "CategoryButton", self, w, h )
if timeline.selected_keyframe == self then
local c = self:GetSkin().Colours.Category.Line.Button_Selected
surface.SetDrawColor(c.r,c.g,c.b,250)
end
surface.DrawRect(0,0,w,h)
surface.SetDrawColor(0,0,0,75)
surface.DrawOutlinedRect(0,0,w,h)
end
function KEYFRAME:Think()
if self.size_x then
local delta = self.size_x - gui.MouseX()
self:SetLength((self.size_w - delta) / secondDistance)
elseif self.move then
local x, y = self:GetPos()
local delta = gui.MouseX() - self.move
self:SetPos(self.move_x + delta, y)
end
end
function KEYFRAME:OnMouseReleased(mc)
if mc == MOUSE_LEFT then
if self.size_x then
self.size_x = nil
self.size_w = nil
self:MouseCapture(false)
self:SetCursor("sizewe")
timeline.Save()
elseif self.move then
local panels = {}
local frames = {}
for k, v in pairs(timeline.frame.keyframe_scroll:GetCanvas():GetChildren()) do
table.insert(panels, v)
v:SetParent()
end
table.sort(panels, function(a, b)
return (a:GetPos() + a:GetWide() / 2) < (b:GetPos() + b:GetWide() / 2)
end)
for i,v in ipairs(panels) do
v:SetParent(timeline.frame.keyframe_scroll)
v.Alternate = #timeline.frame.keyframe_scroll:GetCanvas():GetChildren()%2 == 1
frames[i] = timeline.data.FrameData[v:GetAnimationIndex()]
end
for i,v in ipairs(frames) do
timeline.data.FrameData[i] = v
panels[i].AnimationKeyIndex = i
end
self:MouseCapture(false)
self:SetCursor("hand")
self.move = nil
self.move_x = nil
timeline.frame.moving = false
end
end
end
function KEYFRAME:OnMousePressed(mc)
if mc == MOUSE_LEFT then
local x = self:CursorPos()
if x >= self:GetWide() - 4 then
self.size_x = gui.MouseX()
self.size_w = self:GetWide()
self:MouseCapture(true)
self:SetCursor("sizewe")
else
self.move = gui.MouseX()
self.move_x = self:GetPos()
self:MoveToFront()
self:MouseCapture(true)
self:SetCursor("sizeall")
timeline.frame.moving = true
end
timeline.frame:Toggle(false)
timeline.SelectKeyframe(self)
elseif mc == MOUSE_RIGHT then
local menu = DermaMenu()
menu:AddOption(L"set length",function()
Derma_StringRequest(L"question",
L"how long should this frame be in seconds?",
tostring(self:GetWide()/secondDistance),
function(str) self:SetLength(tonumber(str)) end,
function() end,
L"set length",
L"cancel" )
end):SetImage("icon16/time.png")
menu:AddOption(L"multiply length",function()
Derma_StringRequest(L"question",
L"multiply "..self:GetAnimationIndex().."'s length",
"1.0",
function(str) self:SetLength(1/tonumber(str)) end,
function() end,
L"multiply length",
L"cancel" )
end):SetImage("icon16/time_add.png")
if not self:GetRestart() then
menu:AddOption(L"set restart",function()
for _,v in pairs(timeline.frame.keyframe_scroll:GetCanvas():GetChildren()) do
v:SetRestart(false)
end
self:SetRestart(true)
timeline.data.RestartFrame = self:GetAnimationIndex()
end):SetImage("icon16/control_repeat_blue.png")
else
menu:AddOption(L"unset restart",function()
self:SetRestart(false)
timeline.data.StartFrame = nil
end):SetImage("icon16/control_repeat.png")
end
if not self:GetStart() then
menu:AddOption(L"set start",function()
for _,v in pairs(timeline.frame.keyframe_scroll:GetCanvas():GetChildren()) do
v:SetStart(false)
end
self:SetStart(true)
timeline.data.StartFrame = self:GetAnimationIndex()
end):SetImage("icon16/control_play_blue.png")
else
menu:AddOption(L"unset start",function()
self:SetStart(false)
timeline.data.StartFrame = nil
end):SetImage("icon16/control_play.png")
end
menu:AddOption(L"reverse",function()
local frame = timeline.data.FrameData[self:GetAnimationIndex() - 1]
if not frame then
frame = timeline.data.FrameData[#timeline.data.FrameData]
end
local tbl = frame.BoneInfo
for i, v in pairs(tbl) do
self:GetData().BoneInfo[i] = table.Copy(self:GetData().BoneInfo[i] or {})
self:GetData().BoneInfo[i].MU = v.MU * -1
self:GetData().BoneInfo[i].MR = v.MR * -1
self:GetData().BoneInfo[i].MF = v.MF * -1
self:GetData().BoneInfo[i].RU = v.RU * -1
self:GetData().BoneInfo[i].RR = v.RR * -1
self:GetData().BoneInfo[i].RF = v.RF * -1
end
timeline.UpdateFrameData()
end):SetImage("icon16/control_rewind_blue.png")
menu:AddOption(L"duplicate to end", function()
local keyframe = timeline.frame:AddKeyFrame()
local tbl = self:GetData().BoneInfo
for i, v in pairs(tbl) do
local data = keyframe:GetData()
data.BoneInfo[i] = table.Copy(self:GetData().BoneInfo[i] or {})
data.BoneInfo[i].MU = v.MU
data.BoneInfo[i].MR = v.MR
data.BoneInfo[i].MF = v.MF
data.BoneInfo[i].RU = v.RU
data.BoneInfo[i].RR = v.RR
data.BoneInfo[i].RF = v.RF
end
keyframe:SetLength(1/(self:GetData().FrameRate))
timeline.SelectKeyframe(keyframe)
end):SetImage("icon16/application_double.png")
menu:AddOption(L"remove",function()
local frameNum = self:GetAnimationIndex()
if frameNum == 1 and not timeline.data.FrameData[2] then return end
table.remove(timeline.data.FrameData, frameNum)
local remove_i
for i,v in pairs(timeline.frame.keyframe_scroll:GetCanvas():GetChildren()) do
if v == self then
remove_i = i
elseif v:GetAnimationIndex() > frameNum then
v.AnimationKeyIndex = v.AnimationKeyIndex - 1
v.Alternate = not v.Alternate
end
end
table.remove(timeline.frame.keyframe_scroll:GetCanvas():GetChildren(), remove_i)
timeline.frame.keyframe_scroll:InvalidateLayout()
self:Remove()
-- * even if it was removed from the table it still exists for some reason
local count = #timeline.frame.keyframe_scroll:GetCanvas():GetChildren()
local offset = frameNum == count and count - 1 or count
timeline.SelectKeyframe(timeline.frame.keyframe_scroll:GetCanvas():GetChildren()[offset])
end):SetImage("icon16/application_delete.png")
menu:AddOption(L"set easing style", function()
if timeline.data.Interpolation != "linear" then
local frame = vgui.Create("DFrame")
frame:SetSize(300, 100)
frame:Center()
frame:SetTitle("Easing styles work only with the linear interpolation type!")
frame:ShowCloseButton(false)
local button = vgui.Create("DButton", frame)
button:SetText("Okay")
button:Dock(FILL)
button.DoClick = function()
frame:Close()
end
frame:MakePopup()
return
end
local frameNum = self:GetAnimationIndex()
local frame = vgui.Create( "DFrame" )
frame:SetSize( 200, 100 )
frame:Center()
frame:SetTitle("Select easing type")
frame:MakePopup()
local combo = vgui.Create( "DComboBox", frame )
combo:SetPos( 5, 30 )
combo:Dock(FILL)
combo:SetValue("None")
for easeName, _ in pairs(eases) do
combo:AddChoice(easeName)
end
combo.OnSelect = function(sf, index, val)
self:SetEaseStyle(val)
frame:Close()
end
end):SetImage("icon16/arrow_turn_right.png")
if self:GetEaseStyle() then
menu:AddOption(L"unset easing style", function()
self:RemoveEaseStyle()
end):SetImage("icon16/arrow_up.png")
end
menu:Open()
end
end
function KEYFRAME:SetLength(int)
if not int then return end
self:GetParent():GetParent():InvalidateLayout() --rebuild the timeline
self:GetData().FrameRate = 1/math.max(int, 0.001) --set animation frame rate
end
function KEYFRAME:GetEaseStyle()
return self.estyle
end
function KEYFRAME:SetEaseStyle(style)
if not style then return end
self:GetData().EaseStyle = style
self.estyle = style
end
function KEYFRAME:RemoveEaseStyle()
self:GetData().EaseStyle = nil
self.estyle = nil
end
vgui.Register("pac3_timeline_keyframe",KEYFRAME,"DPanel")
end