diff --git a/NorthernSkyRaidTools/Locales/enUS.lua b/NorthernSkyRaidTools/Locales/enUS.lua index b5a65cdc..3516fa33 100644 --- a/NorthernSkyRaidTools/Locales/enUS.lua +++ b/NorthernSkyRaidTools/Locales/enUS.lua @@ -1560,7 +1560,7 @@ L["Glow Unit: "] = "Glow Unit: " L["Edit Reminder"] = "Edit Reminder" L["Delete Reminder"] = "Delete Reminder" L["Add Reminder"] = "Add Reminder" -L["Scroll: Zoom | Right-drag: Navigate | Ctrl+Scroll: Vertical | Edit mode: Right-click add, Left-drag retime"] = "Scroll: Zoom | Right-drag: Navigate | Ctrl+Scroll: Vertical | Edit mode: Right-click add, Left-drag retime" +L["Scroll: Zoom | Right-drag: Navigate | Ctrl+Scroll: Vertical | Shift+Click: Seek preview | Edit mode: Right-click add, Left-drag retime"] = "Scroll: Zoom | Right-drag: Navigate | Ctrl+Scroll: Vertical | Shift+Click: Seek preview | Edit mode: Right-click add, Left-drag retime" L["Select a reminder set from the dropdown."] = "Select a reminder set from the dropdown." L["No reminders loaded for you.\nLoad a reminder set with /ns and ensure it contains assignments for you."] = "No reminders loaded for you.\nLoad a reminder set with /ns and ensure it contains assignments for you." L["No player-specific reminders found in this reminder set.\n(Only showing named player assignments, not role/group tags)"] = "No player-specific reminders found in this reminder set.\n(Only showing named player assignments, not role/group tags)" diff --git a/NorthernSkyRaidTools/Locales/koKR.lua b/NorthernSkyRaidTools/Locales/koKR.lua index 09dc5cab..a3136217 100644 --- a/NorthernSkyRaidTools/Locales/koKR.lua +++ b/NorthernSkyRaidTools/Locales/koKR.lua @@ -1553,7 +1553,7 @@ L["Glow Unit: "] = "유닛 반짝임: " L["Edit Reminder"] = "리마인더 수정" L["Delete Reminder"] = "리마인더 삭제" L["Add Reminder"] = "리마인더 추가" -L["Scroll: Zoom | Right-drag: Navigate | Ctrl+Scroll: Vertical | Edit mode: Right-click add, Left-drag retime"] = "스크롤: 확대/축소 | 우클릭 후 드래그: 이동 | Ctrl+스크롤: 수직 이동 | 편집 모드: 우클릭으로 추가, 좌클릭 후 드래그로 시간 조절" +L["Scroll: Zoom | Right-drag: Navigate | Ctrl+Scroll: Vertical | Shift+Click: Seek preview | Edit mode: Right-click add, Left-drag retime"] = "스크롤: 확대/축소 | 우클릭 후 드래그: 이동 | Ctrl+스크롤: 수직 이동 | Shift+클릭: 미리보기 위치 이동 | 편집 모드: 우클릭으로 추가, 좌클릭 후 드래그로 시간 조절" L["Select a reminder set from the dropdown."] = "드롭다운에서 리마인더 세트를 선택하세요." L["No reminders loaded for you.\nLoad a reminder set with /ns and ensure it contains assignments for you."] = "내가 포함된 리마인더가 없습니다.\n/ns 명령어로 리마인더 세트를 불러오고 나에게 배정된 내역이 있는지 확인하세요." L["No player-specific reminders found in this reminder set.\n(Only showing named player assignments, not role/group tags)"] = "이 리마인더 세트에는 플레이어 지정 리마인더가 없습니다.\n(역할/파티 태그는 표시하지 않고 지정된 이름의 플레이어 배정 내역만 표시)" diff --git a/NorthernSkyRaidTools/Locales/ruRU.lua b/NorthernSkyRaidTools/Locales/ruRU.lua index 8232f4c6..d1aab7a2 100644 --- a/NorthernSkyRaidTools/Locales/ruRU.lua +++ b/NorthernSkyRaidTools/Locales/ruRU.lua @@ -1584,7 +1584,7 @@ L["Glow Unit: "] = "Подсветка юнита: " L["Edit Reminder"] = "Редактировать напоминание" L["Delete Reminder"] = "Удалить напоминание" L["Add Reminder"] = "Добавить напоминание" -L["Scroll: Zoom | Right-drag: Navigate | Ctrl+Scroll: Vertical | Edit mode: Right-click add, Left-drag retime"] = "Колёсико: Масштабирование | Перетаскивание вправо: Навигация | Ctrl+колёсико: Вертикальная прокрутка | Режим редактирования: добавить щелчком ПКМ, изменить время перетаскиванием ЛКМ" +L["Scroll: Zoom | Right-drag: Navigate | Ctrl+Scroll: Vertical | Shift+Click: Seek preview | Edit mode: Right-click add, Left-drag retime"] = "Колёсико: Масштабирование | Перетаскивание вправо: Навигация | Ctrl+колёсико: Вертикальная прокрутка | Shift+клик: перемотка предпросмотра | Режим редактирования: добавить щелчком ПКМ, изменить время перетаскиванием ЛКМ" L["Select a reminder set from the dropdown."] = "Выберите набор напоминаний из выпадающего списка." L["No reminders loaded for you.\nLoad a reminder set with /ns and ensure it contains assignments for you."] = "Для Вас не загружено напоминаний.\nЗагрузите набор напоминаний с помощью /ns и убедитесь, что он содержит назначения для Вас." L["No player-specific reminders found in this reminder set.\n(Only showing named player assignments, not role/group tags)"] = "В этом наборе напоминаний не найдено напоминаний для конкретных игроков.\n(Показываются только назначения по имени игрока, не по ролям/группам)" diff --git a/NorthernSkyRaidTools/Locales/zhCN.lua b/NorthernSkyRaidTools/Locales/zhCN.lua index d7f5139d..385b00e8 100644 --- a/NorthernSkyRaidTools/Locales/zhCN.lua +++ b/NorthernSkyRaidTools/Locales/zhCN.lua @@ -1560,7 +1560,7 @@ L["Glow Unit: "] = "发光单位:" L["Edit Reminder"] = "编辑提醒" L["Delete Reminder"] = "删除提醒" L["Add Reminder"] = "添加提醒" -L["Scroll: Zoom | Right-drag: Navigate | Ctrl+Scroll: Vertical | Edit mode: Right-click add, Left-drag retime"] = "滚轮:缩放 | 右键拖拽:浏览 | Ctrl+滚轮:垂直滚动 | 编辑模式:右键点击添加提醒,左键拖拽调整时间" +L["Scroll: Zoom | Right-drag: Navigate | Ctrl+Scroll: Vertical | Shift+Click: Seek preview | Edit mode: Right-click add, Left-drag retime"] = "滚轮:缩放 | 右键拖拽:浏览 | Ctrl+滚轮:垂直滚动 | Shift+点击:预览定位 | 编辑模式:右键点击添加提醒,左键拖拽调整时间" L["Select a reminder set from the dropdown."] = "从下拉菜单中选择一个提醒方案。" L["No reminders loaded for you.\nLoad a reminder set with /ns and ensure it contains assignments for you."] = "尚未启用个人提醒。\n请使用“/ns”启用一个提醒方案,需要包含关于自己的分配。" L["No player-specific reminders found in this reminder set.\n(Only showing named player assignments, not role/group tags)"] = "此提醒方案中未找到针对指定玩家的提醒。\n(仅显示指定玩家名称的分配,不显示职责/小队标签)" diff --git a/NorthernSkyRaidTools/Locales/zhTW.lua b/NorthernSkyRaidTools/Locales/zhTW.lua index 1ebdb07c..12af11ca 100644 --- a/NorthernSkyRaidTools/Locales/zhTW.lua +++ b/NorthernSkyRaidTools/Locales/zhTW.lua @@ -1347,7 +1347,7 @@ L["Glow Unit: "] = "高亮單位: " L["Edit Reminder"] = "編輯提醒" L["Delete Reminder"] = "刪除提醒" L["Add Reminder"] = "新增提醒" -L["Scroll: Zoom | Right-drag: Navigate | Ctrl+Scroll: Vertical | Edit mode: Right-click add, Left-drag retime"] = "滾動:縮放 | 右鍵拖曳:導覽 | Ctrl+滾動:垂直 | 編輯模式:右鍵新增,左鍵拖曳調整時間" +L["Scroll: Zoom | Right-drag: Navigate | Ctrl+Scroll: Vertical | Shift+Click: Seek preview | Edit mode: Right-click add, Left-drag retime"] = "滾動:縮放 | 右鍵拖曳:導覽 | Ctrl+滾動:垂直 | Shift+點擊:預覽定位 | 編輯模式:右鍵新增,左鍵拖曳調整時間" L["Select a reminder set from the dropdown."] = "從下拉選單選擇提醒組合。" L["No reminders loaded for you.\nLoad a reminder set with /ns and ensure it contains assignments for you."] = "尚未為你載入提醒。\n請使用 /ns 載入提醒組合,並確保其中包含你的分配。" L["No player-specific reminders found in this reminder set.\n(Only showing named player assignments, not role/group tags)"] = "此提醒組合中找不到玩家專屬提醒。\n(僅顯示具名玩家分配,不含角色/群組標籤)" diff --git a/NorthernSkyRaidTools/Reminders.lua b/NorthernSkyRaidTools/Reminders.lua index ec14a9d2..56320070 100644 --- a/NorthernSkyRaidTools/Reminders.lua +++ b/NorthernSkyRaidTools/Reminders.lua @@ -1562,7 +1562,7 @@ function NSI:PlayReminderSound(info, default) end end -function NSI:StartReminders(phase, testrun) +function NSI:StartReminders(phase, testrun, startOffset) if not testrun then self:LogTimeline("NSRT_PHASE", phase) end self:FireCallback("NSRT_PHASE", phase, self.EncounterID, testrun) self:HideAllReminders() @@ -1575,13 +1575,19 @@ function NSI:StartReminders(phase, testrun) end if not self.ProcessedReminder then self:ProcessReminder() end if not self.ProcessedReminder then return end + startOffset = startOffset or 0 + local timerIndex = 0 for encID, encData in pairs(self.ProcessedReminder) do - for i, info in ipairs(encData[phase] or {}) do - local time = math.max(info.time-info.dur, 0) - info.encID = encID - self.ReminderTimer[i] = C_Timer.NewTimer(time, function() - self:DisplayReminder(info) - end) + for _, info in ipairs(encData[phase] or {}) do + local appearAt = math.max((info.time or 0) - (info.dur or 0), 0) + local delay = appearAt - startOffset + if delay >= 0 then + timerIndex = timerIndex + 1 + info.encID = encID + self.ReminderTimer[timerIndex] = C_Timer.NewTimer(delay, function() + self:DisplayReminder(info) + end) + end end end return diff --git a/NorthernSkyRaidTools_UI/Timeline.lua b/NorthernSkyRaidTools_UI/Timeline.lua index 69286154..d881e428 100644 --- a/NorthernSkyRaidTools_UI/Timeline.lua +++ b/NorthernSkyRaidTools_UI/Timeline.lua @@ -1017,15 +1017,8 @@ function NSI:CreateTimelineWindow() timelineWindow.editNote = nil if timelineWindow.playButton then -- Stop preview and hide play button in "All Reminders" mode - if timelineWindow.previewActive then - timelineWindow.previewActive = false - timelineWindow.previewStartTime = nil - if timelineWindow.timeline and timelineWindow.timeline.previewLine then - timelineWindow.timeline.previewLine:Hide() - end - NSI:HideAllReminders() - timelineWindow.playButton.text = T("Play Preview") - timelineWindow.playButton:SetIcon(NSI.LSM:Fetch("statusbar", "play_icon"), 14, 14, "OVERLAY", nil, {0, 1, 0, 1}) + if timelineWindow.StopPreview then + timelineWindow.StopPreview(true) end timelineWindow.playButton:Hide() end @@ -1101,26 +1094,66 @@ function NSI:CreateTimelineWindow() local options_button_template = DF:GetTemplate("button", "OPTIONS_BUTTON_TEMPLATE") local playColor = { 20 / 255, 245 / 255, 87 / 255, 1 } -- {r,g,b,a} local stopColor = { 247 / 255, 32 / 255, 61 / 255, 1 } -- {r,g,b,a} - local playButton = DF:CreateButton(timelineWindow, function() - if timelineWindow.previewActive then - timelineWindow.previewActive = false - timelineWindow.previewStartTime = nil - if timelineWindow.timeline and timelineWindow.timeline.previewLine then - timelineWindow.timeline.previewLine:Hide() - end - NSI:HideAllReminders() + + -- previewSeekTime is the parked playhead (absolute fight seconds). Play Preview + -- always starts from there; Shift+Click on the timeline moves it. + timelineWindow.previewSeekTime = 0 + + local function SetPlayButtonPlaying(playing) + if not timelineWindow.playButton then return end + if playing then + timelineWindow.playButton.text = T("Stop Preview") + timelineWindow.playButton:SetIcon("Interface\\AddOns\\NorthernSkyRaidTools\\Media\\Icons\\stop_icon.png", 14, + 14, "OVERLAY", { 0.12, 0.88, 0.12, 0.88 }, stopColor) + else timelineWindow.playButton.text = T("Play Preview") timelineWindow.playButton:SetIcon("Interface\\AddOns\\NorthernSkyRaidTools\\Media\\Icons\\play_icon.png", 14, 14, "OVERLAY", { 0.1, 0.9, 0.09, 0.91 }, playColor) + end + end + + function timelineWindow.StopPreview(resetSeek) + timelineWindow.previewActive = false + timelineWindow.previewStartTime = nil + if resetSeek then + timelineWindow.previewSeekTime = 0 + if timelineWindow.timeline and timelineWindow.timeline.previewLine then + timelineWindow.timeline.previewLine:Hide() + end + end + NSI:HideAllReminders() + SetPlayButtonPlaying(false) + end + + function timelineWindow.StartPreview(absoluteTime) + if not NSI.ProcessedReminder then NSI:ProcessReminder() end + if not NSI.ProcessedReminder then return end + if absoluteTime then + timelineWindow.previewSeekTime = math.max(0, absoluteTime) + end + local seek = timelineWindow.previewSeekTime or 0 + local phase, phaseStart = NSI:PhaseFromTime(timelineWindow.currentEncounterID, seek, timelineWindow.currentDifficulty) + local phaseOffset = math.max(0, seek - (phaseStart or 0)) + timelineWindow.previewActive = true + timelineWindow.previewStartTime = GetTime() + NSI:StartReminders(phase, true, phaseOffset) + SetPlayButtonPlaying(true) + end + + function timelineWindow.SeekPreview(absoluteTime) + local timelineLength = (timelineWindow.timeline and timelineWindow.timeline.data and timelineWindow.timeline.data.length) or 300 + local seek = math.max(0, math.min(timelineLength, absoluteTime or 0)) + timelineWindow.previewSeekTime = seek + if timelineWindow.previewActive then + timelineWindow.StartPreview(seek) + end + end + + local playButton = DF:CreateButton(timelineWindow, function() + if timelineWindow.previewActive then + timelineWindow.StopPreview(false) else - if not NSI.ProcessedReminder then NSI:ProcessReminder() end - if not NSI.ProcessedReminder then return end - timelineWindow.previewActive = true - timelineWindow.previewStartTime = GetTime() - NSI:StartReminders(1, true) - timelineWindow.playButton.text = T("Stop Preview") - timelineWindow.playButton:SetIcon("Interface\\AddOns\\NorthernSkyRaidTools\\Media\\Icons\\stop_icon.png", 14, - 14, "OVERLAY", { 0.12, 0.88, 0.12, 0.88 }, stopColor) + timelineWindow.StartPreview() end end, 32, 22, T("Play Preview")) playButton:SetTemplate(options_button_template) @@ -1661,6 +1694,20 @@ function NSI:CreateTimelineWindow() local payload = data.payload + -- Shift+Click seeks the preview playhead on any block (boss events + -- included). Personal-note blocks also get drag-to-retime below. + if not block._seekHookInstalled then + block._seekHookInstalled = true + block:EnableMouse(true) + block:HookScript("OnMouseDown", function(_, button) + if button == "LeftButton" and IsShiftKeyDown() and timelineWindow.SeekPreview then + local tl = timelineWindow.timeline + local time = tl and tl.GetTimeUnderMouse and tl:GetTimeUnderMouse() or 0 + timelineWindow.SeekPreview(math.max(0, math.floor(time + 0.5))) + end + end) + end + -- Wire up drag-to-retime and right-click-add for personal-note blocks. -- Guard: install once per block (blocks are pooled and reused). if payload and payload.srcLineIndex and not block._dragHooksInstalled then @@ -1669,6 +1716,14 @@ function NSI:CreateTimelineWindow() block:HookScript("OnMouseDown", function(self, button) if button == "LeftButton" then + if IsShiftKeyDown() then + local tl = timelineWindow.timeline + local time = tl and tl.GetTimeUnderMouse and tl:GetTimeUnderMouse() or 0 + if timelineWindow.SeekPreview then + timelineWindow.SeekPreview(math.max(0, math.floor(time + 0.5))) + end + return + end self._clickStartX, self._clickStartY = GetCursorPosition() if timelineWindow.editNote then timelineWindow.draggingBlock = self @@ -2080,16 +2135,37 @@ function NSI:CreateTimelineWindow() end end + local function seekPreviewAtCursor() + if not timelineWindow.SeekPreview then return end + -- Don't let DetailsFramework's body-drag start panning on a seek click. + if timelineFrame.body then + timelineFrame.body.isDragging = false + end + local time + if timelineFrame.GetTimeUnderMouse then + time = timelineFrame:GetTimeUnderMouse() + else + local uiScale = 1 / timelineWindow:GetEffectiveScale() + local cursorX = GetCursorPosition() * uiScale + local bodyLeft = timelineFrame.body and (timelineFrame.body:GetLeft() or 0) or 0 + local pps = (timelineFrame.options.pixels_per_second or 15) * (timelineFrame.currentScale or 1) + time = math.max(0, (cursorX - bodyLeft) / pps) + end + timelineWindow.SeekPreview(math.max(0, math.floor((time or 0) + 0.5))) + end + timelineFrame:EnableMouse(true) timelineFrame:HookScript("OnMouseDown", function(self, button) - if button == "RightButton" then startRightDrag() end + if button == "RightButton" then startRightDrag() + elseif button == "LeftButton" and IsShiftKeyDown() then seekPreviewAtCursor() end end) timelineFrame:HookScript("OnMouseUp", function(self, button) if button == "RightButton" then stopRightDrag() end end) timelineFrame.body:HookScript("OnMouseDown", function(self, button) - if button == "RightButton" then startRightDrag() end + if button == "RightButton" then startRightDrag() + elseif button == "LeftButton" and IsShiftKeyDown() then seekPreviewAtCursor() end end) timelineFrame.body:HookScript("OnMouseUp", function(self, button) if button == "RightButton" then stopRightDrag() end @@ -2237,8 +2313,11 @@ function NSI:CreateTimelineWindow() dragGhostLine:Hide() dragGhostIcon:Hide() end + local previewElapsed = timelineWindow.previewSeekTime or 0 if timelineWindow.previewActive and timelineWindow.previewStartTime then - local previewElapsed = GetTime() - timelineWindow.previewStartTime + previewElapsed = previewElapsed + (GetTime() - timelineWindow.previewStartTime) + end + if timelineWindow.previewActive or (previewElapsed > 0) then local pixelsPerSecond = timelineFrame.options.pixels_per_second or 15 local currentScale = timelineFrame.currentScale or 1 local elapsedHeight = timelineFrame.options.elapsed_timeline_height or 20 @@ -2260,16 +2339,18 @@ function NSI:CreateTimelineWindow() end local timelineLength = (timelineFrame.data and timelineFrame.data.length) or 300 - if previewElapsed >= timelineLength then - timelineWindow.previewActive = false - timelineWindow.previewStartTime = nil - previewLine:Hide() - NSI:HideAllReminders() - if timelineWindow.playButton then - timelineWindow.playButton.text = T("Play Preview") - timelineWindow.playButton:SetIcon(NSI.LSM:Fetch("statusbar", "play_icon"), 14, 14, "OVERLAY", nil, {0, 1, 0, 1}) + if timelineWindow.previewActive and previewElapsed >= timelineLength then + if timelineWindow.StopPreview then + timelineWindow.StopPreview(true) + else + timelineWindow.previewActive = false + timelineWindow.previewStartTime = nil + previewLine:Hide() + NSI:HideAllReminders() end end + elseif not timelineWindow.previewActive then + previewLine:Hide() end end end) @@ -2294,7 +2375,7 @@ function NSI:CreateTimelineWindow() end -- Help text (positioned at bottom, below the sliders) - local helpLabel = DF:CreateLabel(timelineWindow, T("Scroll: Zoom | Right-drag: Navigate | Ctrl+Scroll: Vertical | Edit mode: Right-click add, Left-drag retime"), 10, "gray") + local helpLabel = DF:CreateLabel(timelineWindow, T("Scroll: Zoom | Right-drag: Navigate | Ctrl+Scroll: Vertical | Shift+Click: Seek preview | Edit mode: Right-click add, Left-drag retime"), 10, "gray") helpLabel:SetPoint("BOTTOMLEFT", timelineWindow, "BOTTOMLEFT", 10, 5) -- Handle window resize to update timeline dimensions @@ -2407,15 +2488,8 @@ function NSI:ViewNoteInTimeline(name, personal) window.editable = false window.editNote = nil if window.playButton then - if window.previewActive then - window.previewActive = false - window.previewStartTime = nil - if window.timeline and window.timeline.previewLine then - window.timeline.previewLine:Hide() - end - self:HideAllReminders() - window.playButton.text = "Play Preview" - window.playButton:SetIcon(self.LSM:Fetch("statusbar", "play_icon"), 14, 14, "OVERLAY", nil, {0, 1, 0, 1}) + if window.StopPreview then + window.StopPreview(true) end window.playButton:Hide() end