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.2"
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 = 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)
		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 05 2023, 12:58

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
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: 585