Image
-- **************************************************
-- Provide Moho with the name of this script object
-- **************************************************

ScriptName = "LK_TimelineNavigator"

-- **************************************************
-- General information about this script
-- **************************************************

LK_TimelineNavigator = {}

function LK_TimelineNavigator:Name()
	return "Timeline Navigator"
end

function LK_TimelineNavigator:Version()
	return "2.3"
end

function LK_TimelineNavigator:UILabel()
	return "Timeline Navigator"
end

function LK_TimelineNavigator:Creator()
	return "Lukas Krepel, Frame Order - Original by Stan"
end

function LK_TimelineNavigator:Description()
	return "Move the timeline cursor by specified amount of frames or time intervals and jump to document markers. Press <"..self.previousMarkerShortcut.."/"..self.nextMarkerShortcut.."> to jump to previous/next marker."
end

function LK_TimelineNavigator:ColorizeIcon()
	return true
end

-- **************************************************
-- Is Relevant / Is Enabled
-- **************************************************

function LK_TimelineNavigator:IsRelevant(moho)
	if MohoMode ~= nil then
		if not MohoMode.keys then
			return false
		end
	end
	return true
end

function LK_TimelineNavigator:IsEnabled(moho)
	return true
end

-- **************************************************
-- Keyboard/Mouse Control
-- **************************************************

function LK_TimelineNavigator:OnMouseDown(moho, mouseEvent)
	if Syn_ScrubWorkspace ~= nil then
		Syn_ScrubWorkspace:OnMouseDown(moho, mouseEvent)
	end
end

function LK_TimelineNavigator:OnMouseMoved(moho, mouseEvent)
	if Syn_ScrubWorkspace ~= nil then
		Syn_ScrubWorkspace:OnMouseMoved(moho, mouseEvent)
	end
end

function LK_TimelineNavigator:OnMouseUp(moho, mouseEvent)
	if Syn_ScrubWorkspace ~= nil then
		Syn_ScrubWorkspace:OnMouseUp(moho, mouseEvent)
	end
end

function LK_TimelineNavigator:OnKeyDown(moho, keyEvent)
	if keyEvent.key == self.nextMarkerShortcut then
		self:GoToNextmarker(moho, false)
	end
	if keyEvent.key == self.previousMarkerShortcut then
		self:GoToNextmarker(moho, true)
	end
end

-- **************************************************
-- Recurring Values
-- **************************************************

LK_TimelineNavigator.frames = 1
LK_TimelineNavigator.seconds = 1
LK_TimelineNavigator.timeMinutes = 0
LK_TimelineNavigator.timeSeconds = 0
LK_TimelineNavigator.timeFrames = 0

LK_TimelineNavigator.previousMarkerShortcut = ","
LK_TimelineNavigator.nextMarkerShortcut = "."

LK_TimelineNavigator.markerLevelOptions = { "Document", "Layer" } -- * TODO: Both / Parent / Any layer ?

-- **************************************************
-- Tool options - create and respond to tool's UI
-- **************************************************

LK_TimelineNavigator.FRAMES_BACK =				MOHO.MSG_BASE
LK_TimelineNavigator.FRAMES_INPUT =				MOHO.MSG_BASE + 1
LK_TimelineNavigator.FRAMES_FORWARD =			MOHO.MSG_BASE + 2
LK_TimelineNavigator.SECONDS_BACK =				MOHO.MSG_BASE + 3
LK_TimelineNavigator.SECONDS_INPUT =			MOHO.MSG_BASE + 4
LK_TimelineNavigator.SECONDS_FORWARD =			MOHO.MSG_BASE + 5
LK_TimelineNavigator.TIME_MINUTES_INPUT =		MOHO.MSG_BASE + 6
LK_TimelineNavigator.TIME_SECONDS_INPUT =		MOHO.MSG_BASE + 7
LK_TimelineNavigator.TIME_FRAMES_INPUT =		MOHO.MSG_BASE + 8
LK_TimelineNavigator.TIME_GO =					MOHO.MSG_BASE + 9
LK_TimelineNavigator.GO_TO_FRAME_ZERO =			MOHO.MSG_BASE + 10
LK_TimelineNavigator.PREV_MARKER =				MOHO.MSG_BASE + 13
LK_TimelineNavigator.NEXT_MARKER =				MOHO.MSG_BASE + 14
LK_TimelineNavigator.GO_TO_DOCUMENT_START =		MOHO.MSG_BASE + 15
LK_TimelineNavigator.GO_TO_DOCUMENT_END =		MOHO.MSG_BASE + 16
LK_TimelineNavigator.SET_MARKER_LEVEL =			MOHO.MSG_BASE + 17 -- * 17 to 30
LK_TimelineNavigator.GO_TO_MARKER =				MOHO.MSG_BASE + 30 -- * 30 to x

function LK_TimelineNavigator:LoadPrefs(prefs)
	self.markerLevel = prefs:GetString("LK_TimelineNavigator.markerLevel", self.markerLevelOptions[1])
	self.frames = prefs:GetInt("LK_TimelineNavigator.frames", 1)
	self.seconds = prefs:GetInt("LK_TimelineNavigator.seconds", 1)
	self.timeMinutes = prefs:GetInt("LK_TimelineNavigator.timeMinutes", 0)
	self.timeSeconds = prefs:GetInt("LK_TimelineNavigator.timeSeconds", 0)
	self.timeFrames = prefs:GetInt("LK_TimelineNavigator.timeFrames", 0)
end

function LK_TimelineNavigator:SavePrefs(prefs)
	prefs:SetString("LK_TimelineNavigator.markerLevel", self.markerLevel)
	prefs:SetInt("LK_TimelineNavigator.frames", self.frames)
	prefs:SetInt("LK_TimelineNavigator.seconds", self.seconds)
	prefs:SetInt("LK_TimelineNavigator.timeMinutes", self.timeMinutes)
	prefs:SetInt("LK_TimelineNavigator.timeSeconds", self.timeSeconds)
	prefs:SetInt("LK_TimelineNavigator.timeFrames", self.timeFrames)
end

function LK_TimelineNavigator:ResetPrefs(prefs)
	self.markerLevel = self.markerLevelOptions[1]
	self.frames = 1
	self.seconds = 1
	self.timeMinutes = 0
	self.timeSeconds = 0
	self.timeFrames = 0
end

-- **************************************************
-- Tool Panel Layout
-- **************************************************

function LK_TimelineNavigator:DoLayout(moho, layout)

	self.markerChannel = moho.layer.fTimelineMarkers
	-- *** Move x amount of frames left/right:

	FO_Utilities:Divider(layout, "Move frame(s)", true)
	layout:AddPadding(-15)
	self.framesInput = LM.GUI.TextControl(48, '1', self.FRAMES_INPUT, LM.GUI.FIELD_UINT)
	layout:AddChild(self.framesInput)
	layout:AddPadding(-15)
	self.framesBackButton = LM.GUI.ImageButton("ScriptResources/FO_icons/arrow_left", "Back", false, self.FRAMES_BACK, true)
	layout:AddChild(self.framesBackButton)
	 
	self.framesForwardButton = LM.GUI.ImageButton("ScriptResources/FO_icons/arrow_right", "Forward", false, self.FRAMES_FORWARD, true)
	layout:AddChild(self.framesForwardButton)

	-- *** Move x amount of seconds left/right:

	FO_Utilities:Divider(layout, "Move second(s)")
	layout:AddPadding(-15)
	self.secondsInput = LM.GUI.TextControl(48, '1', self.SECONDS_INPUT, LM.GUI.FIELD_UFLOAT)
	layout:AddChild(self.secondsInput)
	layout:AddPadding(-15)
	self.secondsBackButton = LM.GUI.ImageButton("ScriptResources/FO_icons/arrow_left", "Back", false, self.SECONDS_BACK, true)
	layout:AddChild(self.secondsBackButton)
	 
	self.secondsForwardButton = LM.GUI.ImageButton("ScriptResources/FO_icons/arrow_right", "Forward", false, self.SECONDS_FORWARD, true)
	layout:AddChild(self.secondsForwardButton)

	-- *** Go to specified timecode or frame:

	FO_Utilities:Divider(layout, "Go to (MM:SS:FF)")
	self.timeMinutesInput = LM.GUI.TextControl(28, '0', self.TIME_MINUTES_INPUT, LM.GUI.FIELD_UINT)
	layout:AddChild(self.timeMinutesInput)
	layout:AddPadding(-15)
	layout:AddChild(LM.GUI.StaticText(':'))
	layout:AddPadding(-19)
	self.timeSecondsInput = LM.GUI.TextControl(28, '0', self.TIME_SECONDS_INPUT, LM.GUI.FIELD_UINT)
	layout:AddChild(self.timeSecondsInput)
	layout:AddPadding(-15)
	layout:AddChild(LM.GUI.StaticText(':'))
	layout:AddPadding(-19)
	self.timeFramesInput = LM.GUI.TextControl(28, '0', self.TIME_FRAMES_INPUT, LM.GUI.FIELD_UINT)
	layout:AddChild(self.timeFramesInput)
	layout:AddPadding(-15)
	self.goButton = LM.GUI.ImageButton("ScriptResources/FO_icons/Set", "Go", false, self.TIME_GO, true)
	layout:AddChild(self.goButton)
	-- *******************************
	-- *** Go to Markers: ***
	-- *******************************
	FO_Utilities:Divider(layout, "Go to Markers")
	-- *** Level Dropdown:
	-- * Level Dropdown:
	if self.markerLevel == "" then -- * Prefs bugfix
		self.markerLevel = self.markerLevelOptions[1]
	end
	self.levelPopup = LM.GUI.PopupMenu(75, false)
	layout:AddChild(self.levelPopup)
	layout:AddPadding(-14)
	-- * Marker Dropdown:
	self.markerPopup = LM.GUI.PopupMenu(200, false)
	layout:AddChild(self.markerPopup)
	-- * Previous/Next Marker:
	self.prevMarkerButton = LM.GUI.ImageButton("ScriptResources/FO_icons/timeline_marker_prev", "Back", false, self.PREV_MARKER, true)
	layout:AddChild(self.prevMarkerButton)
	layout:AddPadding(-14)
	self.nextMarkerButton = LM.GUI.ImageButton("ScriptResources/FO_icons/timeline_marker_next", "Forward", false, self.NEXT_MARKER, true)
	layout:AddChild(self.nextMarkerButton)
end


function LK_TimelineNavigator:UpdateWidgets(moho)
	if moho:IsPlaying() then
		return
	end
	FO_Utilities.tinyUI = false

	self.framesInput:SetValue(self.frames)
	self.secondsInput:SetValue(self.seconds)
	self.timeMinutesInput:SetValue(self.timeMinutes)
	self.timeSecondsInput:SetValue(self.timeSeconds)
	self.timeFramesInput:SetValue(self.timeFrames)

	if self.markerLevel == "Document" then
		self.markerChannel = moho.document.fTimelineMarkers
	else
		self.markerChannel = moho.layer.fTimelineMarkers
	end
	-- *** Marker Dropdown:
	local markerChannel = self.markerChannel
	local markerLabel = ""
	if moho.frame == 0 then
		markerLabel = " - Zero"
	elseif moho.frame == moho.document:StartFrame() then
		markerLabel = " - Start"
	elseif moho.frame == moho.document:EndFrame() then
		markerLabel = " - End"
	elseif markerChannel:HasKey(moho.frame) then
		markerLabel = markerChannel:GetValue(moho.frame)
		markerLabel = string.gsub(markerLabel, "\n", " ")
		markerLabel = " - "..markerLabel
		if markerChannel:GetValue(moho.frame) == "" then
			local markerInterp = MOHO.InterpSetting:new_local()
			markerChannel:GetKeyInterp(moho.frame, markerInterp)
			markerLabel = " - "..FO_Utilities.colorNames[markerInterp.tags]
		end
	end
	local menuLabel = string.format("%d", moho.frame)..markerLabel
	self.markerMenu = LM.GUI.Menu(menuLabel)
	-- *** Dropdown items:
	-- * Zero:
	self.markerMenu:AddItem("0 - Zero", 0, self.GO_TO_FRAME_ZERO)
	-- * Start:
	local startFrame = moho.document:StartFrame().." - Start"
	self.markerMenu:AddItem(startFrame, 0, self.GO_TO_DOCUMENT_START)
	self.markerMenu:AddItem("---", 0, 0)
	-- * Markers:
	local markers = {}
	for i = 1, markerChannel:CountKeys()-1 do
		local markerFrame = markerChannel:GetKeyWhen(i)
		markerFrame = string.format("%d", markerFrame) -- Remove .0 from 1.0
		local markerLabel = markerChannel:GetValueByID(i)
		markerLabel = string.gsub(markerLabel, "\n", " ")
		if markerLabel == "" then
			local markerInterp = MOHO.InterpSetting:new_local()
			markerChannel:GetKeyInterp(markerChannel:GetKeyWhen(i), markerInterp)
			markerLabel = FO_Utilities.colorNames[markerInterp.tags]
		end
		local marker = markerFrame.." - "..markerLabel
		table.insert(markers, marker)
	end
	local i
	for i = 1, #markers do
		local file = markers[i]
		self.markerMenu:AddItem((tostring(markers[i])), 0, (self.GO_TO_MARKER + i))
	end
	if #markers ~= 0 then
		self.markerMenu:AddItem("---", 0, 0)
	end
	-- * End:
	local endFrame = moho.document:EndFrame().." - End"
	self.markerMenu:AddItem(endFrame, 0, self.GO_TO_DOCUMENT_END)
	-- *** Checks:
	self.markerMenu:SetChecked(self.GO_TO_DOCUMENT_START, moho.document:StartFrame() == moho.frame)
	for i = 1, markerChannel:CountKeys()-1 do
		local markerFrame = markerChannel:GetKeyWhen(i)
		self.markerMenu:SetChecked(self.GO_TO_MARKER + i, moho.frame == markerFrame)
	end
	self.markerMenu:SetChecked(self.GO_TO_DOCUMENT_END, moho.document:EndFrame() == moho.frame)
	-- * Set menu to dropdown:
	self.markerPopup:SetMenu(self.markerMenu)
	self.markerPopup:Redraw()
	-- * Level Menu:
	self.levelMenu = LM.GUI.Menu(self.markerLevel)
	for i = 1, #self.markerLevelOptions do
		local option = self.markerLevelOptions[i]
		self.levelMenu:AddItem(option, 0, self.SET_MARKER_LEVEL+i)
		self.levelMenu:SetChecked(self.SET_MARKER_LEVEL+i, self.markerLevel == option)
	end
	self.levelPopup:SetMenu(self.levelMenu)
	self.levelPopup:Redraw()
end


function LK_TimelineNavigator:HandleMessage(moho, view, msg)
	local curFrame = moho.frame
	local fps = moho.document:Fps()
	self.frames = self.framesInput:IntValue()
	self.seconds = self.secondsInput:FloatValue()
	self.timeMinutes = self.timeMinutesInput:IntValue()
	self.timeSeconds = LM.Clamp(self.timeSecondsInput:IntValue(), 0, 59)
	self.timeFrames = self.timeFramesInput:IntValue()
	if msg == self.GO_TO_FRAME_ZERO then
		moho:SetCurFrame(0)
	elseif msg == self.GO_TO_DOCUMENT_START then
		moho:SetCurFrame(moho.document:StartFrame())
	elseif msg == self.GO_TO_DOCUMENT_END then
		moho:SetCurFrame(moho.document:EndFrame())
	elseif msg >= self.GO_TO_MARKER then
		local option = msg - self.GO_TO_MARKER
		moho:SetCurFrame(self.markerChannel:GetKeyWhen(option))
	elseif msg == self.PREV_LAYER_MARKER then
		self:GoToNextLayerMarker(moho, true)
	elseif msg == self.NEXT_LAYER_MARKER then
		self:GoToNextLayerMarker(moho, false)
	elseif msg == self.PREV_MARKER then
		self:GoToNextmarker(moho, true)
	elseif msg == self.NEXT_MARKER then
		self:GoToNextmarker(moho, false)
	elseif msg == self.FRAMES_BACK then
		local newFrame = curFrame - self.frames
		if newFrame < 0 then newFrame = 0 end
		moho:SetCurFrame(newFrame)
	elseif msg == self.FRAMES_FORWARD then
		moho:SetCurFrame(curFrame + self.frames)
	elseif msg == self.SECONDS_BACK then
		local frames = self.seconds * fps
		local newFrame = curFrame - math.floor(frames)
		if newFrame < 0 then newFrame = 0 end
		moho:SetCurFrame(newFrame)
	elseif msg == self.SECONDS_FORWARD then
		local frames = self.seconds * fps
		moho:SetCurFrame(curFrame + math.floor(frames))
	elseif msg == self.TIME_GO then
		local newFrame = (self.timeMinutes * 60 * fps) + (self.timeSeconds * fps) + self.timeFrames
		moho:SetCurFrame(newFrame)
	elseif msg == self.TIME_SECONDS_INPUT then
		self.timeSecondsInput:SetValue(LM.Clamp(self.timeSecondsInput:IntValue(), 0, 59))
	elseif msg >= self.SET_MARKER_LEVEL and msg < self.GO_TO_MARKER then
		local option = msg - self.SET_MARKER_LEVEL
		self.markerLevel = self.markerLevelOptions[option]
		self:UpdateWidgets(moho)
	end
end


function LK_TimelineNavigator:GoToNextmarker(moho, backwards)
	backwards = backwards or false
	local markerFrames = {}
	local markerChannel = self.markerChannel -- * TODO
	table.insert(markerFrames, moho.document:StartFrame())
	table.insert(markerFrames, moho.document:EndFrame())
	for markerID = 1, markerChannel:CountKeys()-1 do
		local markerFrame = markerChannel:GetKeyWhen(markerID)
		table.insert(markerFrames, markerFrame)
		local interp = MOHO.InterpSetting:new_local()
		markerChannel:GetKeyInterp(markerFrame, interp)
		if interp.hold > 0 then
			local holdEndFrame = markerFrame + interp.hold
			table.insert(markerFrames, holdEndFrame)
		end
	end
	local gotoFrame
	if backwards then
		gotoFrame = -1000000000
	else
		gotoFrame = 1000000000
	end
	for i = 1, #markerFrames do
		local markerFrame = markerFrames[i]
		if backwards then
			if markerFrame < moho.frame and gotoFrame < markerFrame then
				gotoFrame = markerFrame
			end
		else
			if markerFrame > moho.frame and gotoFrame > markerFrame then
				gotoFrame = markerFrame
			end
		end
	end
	if gotoFrame == -1000000000 or gotoFrame == 1000000000 then
		return
	end
	moho:SetCurFrame(gotoFrame)
end

Icon
TimelineNavigator
Listed

Author: Lukas View Script
Script type: Tool

Uploaded: Jun 25 2022, 03:42

Last modified: Sep 20 2024, 01:45

Script Version: 2.3

Jump through the timeline by amount of frames/times or to document/layer markers
Image
Use keyboard shortcuts , and . to jump from marker to marker. It also jumps to marker ends if you're using hold interpolation on the marker keys and document start/end frames.

Based on Stan's Timeline Navigator tool.

Changelog:
- v2.0 Initial release
- v2.1 Fixed LK_Powertool error
- v2.2 Fixed MohoMode error
- v2.3 Fixed bug where framenumbers would display as floats (1.0 instead of 1)
This script, and all other scripts on this site are distributed as free software under the GNU General Public License 3.0 or later.
Downloads count: 1144