diff --git a/app.go b/app.go index cab11e1..7bee719 100644 --- a/app.go +++ b/app.go @@ -401,6 +401,10 @@ func (a *App) EnableVideoMenus() { vimCommandsMenu.AddText("Zoom Out Timeline", keys.Shift("-"), func(cd *menu.CallbackData) { wruntime.EventsEmit(a.ctx, video.EVT_ZOOM_TIMELINE, "out") }) + + vimCommandsMenu.AddText("Add Timeline Track", keys.Shift("t"), func(cd *menu.CallbackData) { + wruntime.EventsEmit(a.ctx, video.EVT_ADD_TRACK) + }) vimCommandsMenu.AddText("Save Timeline", keys.Shift("w"), func(cd *menu.CallbackData) { err := a.SaveTimeline() if err != nil { diff --git a/frontend/src/ToolingLayout.svelte b/frontend/src/ToolingLayout.svelte index 0640c14..c0f5443 100644 --- a/frontend/src/ToolingLayout.svelte +++ b/frontend/src/ToolingLayout.svelte @@ -124,6 +124,10 @@ EventsOn("evt_execute_edit", () => { if ($vimMode) handleEditAction(); }); + + EventsOn("evt_add_track", () => { + handleAddTrack(); + }); onDestroy(() => { EventsOff( "evt_toggle_vim_mode", diff --git a/frontend/src/components/Timeline.svelte b/frontend/src/components/Timeline.svelte index 99c5054..ea3bfca 100644 --- a/frontend/src/components/Timeline.svelte +++ b/frontend/src/components/Timeline.svelte @@ -24,11 +24,16 @@ } from "../../wailsjs/go/main/App"; import RenameIcon from "../icons/RenameIcon.svelte"; import SearchList from "../components/SearchList.svelte"; - import { NODE_AUDIO, NODE_PLACEHOLDER, NODE_VIDEO } from "../lib/utils"; + import { NODE_AUDIO, NODE_PLACEHOLDER } from "../lib/utils"; import VideoNode from "./VideoNode.svelte"; import AudioNode from "./AudioNode.svelte"; import PlaceholderNode from "./PlaceholderNode.svelte"; - import { isNodeVideo, type TimelineNode } from "../lib/timeline"; + import { + scrollToNode, + isNodeVideo, + type TimelineNode, + scrollToTrack, + } from "../lib/timeline"; const { isOpen, close, open } = createBooleanStore(false); const { setVideoSrc, currentTime, setCurrentTime } = videoStore; @@ -325,23 +330,9 @@ } } - function scrollToNode(node: HTMLDivElement) { - const timelineContainer = document.getElementById("timeline"); - const timelineRect = timelineContainer.getBoundingClientRect(); - const nodeRect = node.getBoundingClientRect(); - - const isNodeVisible = - nodeRect.left >= timelineRect.left && - nodeRect.right <= timelineRect.right; - - if (!isNodeVisible) { - const scrollX = - nodeRect.left - timelineRect.left + timelineContainer.scrollLeft; - timelineContainer.scrollTo({ - left: scrollX, - behavior: "smooth", - }); - } + function handleKeybindTrackMove() { + const trackNode = document.getElementById(`track-${$trackCursorIdx}`); + if (trackNode) scrollToTrack(trackNode); } EventsOn("evt_open_rename_clip_modal", () => { @@ -358,6 +349,7 @@ moveTrackCursor(inc); moveClipCursor(0); handleKeybindTrackClipMove(); + handleKeybindTrackMove(); }); EventsOn("evt_clip_move", (inc: number) => { moveClipCursor(inc); @@ -394,7 +386,7 @@ InsertInterval( $trackCursorIdx, $timelineNodePos, - NODE_VIDEO, + $timelineNode.type, $clipRegister.rid, $clipRegister.name, $clipRegister.start, @@ -473,7 +465,7 @@
= timelineRect.top && nodeRect.bottom <= timelineRect.bottom; + + if (!isNodeVisible) { + const scrollY = + nodeRect.top - timelineRect.top + timelineContainer.scrollTop; + timelineContainer.scrollTo({ + top: scrollY, + behavior: "smooth", + }); + } +} diff --git a/frontend/src/stores.ts b/frontend/src/stores.ts index 0ad3135..51f6700 100644 --- a/frontend/src/stores.ts +++ b/frontend/src/stores.ts @@ -460,6 +460,10 @@ function createVideoToolingStore() { return get(clipCursorIdx); } + function getTrackCursorIdx(): number { + return get(trackCursorIdx); + } + function moveTrackCursor(inc: number) { if (!get(vimMode)) return; const numTracks = get(trackStore).length; @@ -530,6 +534,7 @@ function createVideoToolingStore() { moveTrackCursor, clipCursorIdx, getCursorIdx, + getTrackCursorIdx, setClipCursorIdx, moveClipCursor, clipRegister, @@ -826,6 +831,7 @@ function createSearchListStore() { get(toolingStore.timelineNodePos), node, ); + toolingStore.setTimelineNode(node); toolingStore.setActionMsg("-- PLACEHOLDER ADDED --"); }) .catch(() => diff --git a/internal/timeline/timeline.go b/internal/timeline/timeline.go index e5a5ad0..3dc3745 100644 --- a/internal/timeline/timeline.go +++ b/internal/timeline/timeline.go @@ -41,11 +41,11 @@ func (t *Timeline) AddTrack() { t.Nodes = append(t.Nodes, []TimelineNode{}) } -func (t *Timeline) RemoveTrack(id int) error { - if id < 0 || id >= len(t.Nodes) { +func (t *Timeline) RemoveTrack(tid int) error { + if tid < 0 || tid >= len(t.Nodes) { return fmt.Errorf("invalid track index") } - t.Nodes = append(t.Nodes[:id], t.Nodes[id+1:]...) + t.Nodes = append(t.Nodes[:tid], t.Nodes[tid+1:]...) return nil } diff --git a/internal/video/video.go b/internal/video/video.go index b0eeef3..222c1c0 100644 --- a/internal/video/video.go +++ b/internal/video/video.go @@ -71,6 +71,8 @@ const ( EVT_OPEN_SEARCH_LIST = "evt_open_search_list" //EVT_YANK_CLIP: copies the selected video node on track EVT_YANK_CLIP = "evt_yank_clip" + // EVT_ADD_TRACK: adds a new track + EVT_ADD_TRACK = "evt_add_track" // EVT_OPEN_RENAME_CLIP_MODAL: opens the rename clip modal EVT_OPEN_RENAME_CLIP_MODAL = "evt_open_rename_clip_modal" // EVT_INTERVAL_CUT: interval cut event