diff --git a/src/Experimental.h b/src/Experimental.h index be391b59a..557cbfd9b 100644 --- a/src/Experimental.h +++ b/src/Experimental.h @@ -248,4 +248,7 @@ // PRL 1 Jun 2018 #define EXPERIMENTAL_PUNCH_AND_ROLL +// PRL 31 July 2018 +#define EXPERIMENTAL_DRAGGABLE_PLAY_HEAD + #endif diff --git a/src/Project.cpp b/src/Project.cpp index 59071c19a..91b2d6ff7 100644 --- a/src/Project.cpp +++ b/src/Project.cpp @@ -169,6 +169,7 @@ scroll information. It also has some status flags. #include "commands/CommandContext.h" #include "prefs/QualityPrefs.h" +#include "prefs/TracksPrefs.h" #include "../images/AudacityLogoAlpha.xpm" @@ -1804,7 +1805,7 @@ bool AudacityProject::MayScrollBeyondZero() const IsAudioActive()) { if (mPlaybackScroller) { auto mode = mPlaybackScroller->GetMode(); - if (mode == PlaybackScroller::Mode::Centered || + if (mode == PlaybackScroller::Mode::Pinned || mode == PlaybackScroller::Mode::Right) return true; } @@ -1818,7 +1819,7 @@ double AudacityProject::ScrollingLowerBoundTime() const if (!MayScrollBeyondZero()) return 0; const double screen = mTrackPanel->GetScreenEndTime() - mViewInfo.h; - return std::min(mTracks->GetStartTime(), -screen / 2.0); + return std::min(mTracks->GetStartTime(), -screen); } // PRL: Bug1197: we seem to need to compute all in double, to avoid differing results on Mac @@ -6287,7 +6288,8 @@ void AudacityProject::PlaybackScroller::OnTimer(wxCommandEvent &event) trackPanel->Refresh(false); } else if (mMode != Mode::Off) { - // Pan the view, so that we center the play indicator. + // Pan the view, so that we put the play indicator at some fixed + // fraction of the window width. ViewInfo &viewInfo = mProject->GetViewInfo(); TrackPanel *const trackPanel = mProject->GetTrackPanel(); @@ -6300,8 +6302,10 @@ void AudacityProject::PlaybackScroller::OnTimer(wxCommandEvent &event) default: wxASSERT(false); /* fallthru */ - case Mode::Centered: - deltaX = posX - width / 2; break; + case Mode::Pinned: + deltaX = + posX - width * TracksPrefs::GetPinnedHeadPositionPreference(); + break; case Mode::Right: deltaX = posX - width; break; } diff --git a/src/Project.h b/src/Project.h index e782a2f8f..c0e18c973 100644 --- a/src/Project.h +++ b/src/Project.h @@ -813,7 +813,7 @@ public: enum class Mode { Off, Refresh, - Centered, + Pinned, Right, }; diff --git a/src/prefs/TracksPrefs.cpp b/src/prefs/TracksPrefs.cpp index eb171e5e7..9182facde 100644 --- a/src/prefs/TracksPrefs.cpp +++ b/src/prefs/TracksPrefs.cpp @@ -42,6 +42,16 @@ namespace { { return false; } + + const wxChar *PinnedHeadPositionPreferenceKey() + { + return wxT("/AudioIO/PinnedHeadPosition"); + } + + double PinnedHeadPositionPreferenceDefault() + { + return 0.5; + } } @@ -324,6 +334,21 @@ void TracksPrefs::SetPinnedHeadPreference(bool value, bool flush) gPrefs->Flush(); } +double TracksPrefs::GetPinnedHeadPositionPreference() +{ + auto value = gPrefs->ReadDouble( + PinnedHeadPositionPreferenceKey(), + PinnedHeadPositionPreferenceDefault()); + return std::max(0.0, std::min(1.0, value)); +} + +void TracksPrefs::SetPinnedHeadPositionPreference(double value, bool flush) +{ + gPrefs->Write(PinnedHeadPositionPreferenceKey(), value); + if(flush) + gPrefs->Flush(); +} + wxString TracksPrefs::GetDefaultAudioTrackNamePreference() { const auto name = diff --git a/src/prefs/TracksPrefs.h b/src/prefs/TracksPrefs.h index 026da949c..4c56d3c25 100644 --- a/src/prefs/TracksPrefs.h +++ b/src/prefs/TracksPrefs.h @@ -34,7 +34,10 @@ class TracksPrefs final : public PrefsPanel static bool GetPinnedHeadPreference(); static void SetPinnedHeadPreference(bool value, bool flush = false); - + + static double GetPinnedHeadPositionPreference(); + static void SetPinnedHeadPositionPreference(double value, bool flush = false); + static wxString GetDefaultAudioTrackNamePreference(); static WaveTrack::WaveTrackDisplay ViewModeChoice(); diff --git a/src/toolbars/ControlToolBar.cpp b/src/toolbars/ControlToolBar.cpp index 913acb6d5..bc5faad63 100644 --- a/src/toolbars/ControlToolBar.cpp +++ b/src/toolbars/ControlToolBar.cpp @@ -1514,7 +1514,7 @@ void ControlToolBar::StartScrolling() using Mode = AudacityProject::PlaybackScroller::Mode; const auto project = GetActiveProject(); if (project) { - auto mode = Mode::Centered; + auto mode = Mode::Pinned; #if 0 // Enable these lines to pin the playhead right instead of center, diff --git a/src/tracks/ui/PlayIndicatorOverlay.cpp b/src/tracks/ui/PlayIndicatorOverlay.cpp index fbb01ccc0..4d9eda7de 100644 --- a/src/tracks/ui/PlayIndicatorOverlay.cpp +++ b/src/tracks/ui/PlayIndicatorOverlay.cpp @@ -153,32 +153,37 @@ void PlayIndicatorOverlay::OnTimer(wxCommandEvent &event) // Calculate the horizontal position of the indicator const double playPos = viewInfo.mRecentStreamTime; + // Use a small tolerance to avoid flicker of play head pinned all the way + // left or right + const auto tolerance = kTimerInterval / 1000.0; bool onScreen = playPos >= 0.0 && - between_incexc(viewInfo.h, + between_incexc(viewInfo.h - tolerance, playPos, - mProject->GetScreenEndTime()); + mProject->GetScreenEndTime() + tolerance); // This displays the audio time, too... mProject->TP_DisplaySelection(); // BG: Scroll screen if option is set - // msmeyer: But only if not playing looped or in one-second mode - // PRL: and not scrolling with play/record head fixed right - if (viewInfo.bUpdateTrackIndicator && - mProject->mLastPlayMode != PlayMode::loopedPlay && - mProject->mLastPlayMode != PlayMode::oneSecondPlay && - mProject->GetPlaybackScroller().GetMode() != - AudacityProject::PlaybackScroller::Mode::Right && - playPos >= 0 && - !onScreen && - !gAudioIO->IsPaused()) - { - mProject->TP_ScrollWindow(playPos); - // Might yet be off screen, check it - onScreen = playPos >= 0.0 && + if( viewInfo.bUpdateTrackIndicator && + playPos >= 0 && !onScreen ) { + // msmeyer: But only if not playing looped or in one-second mode + // PRL: and not scrolling with play/record head fixed + using Mode = AudacityProject::PlaybackScroller::Mode; + const Mode mode = mProject->GetPlaybackScroller().GetMode(); + const bool pinned = ( mode == Mode::Pinned || mode == Mode::Right ); + if (!pinned && + mProject->mLastPlayMode != PlayMode::loopedPlay && + mProject->mLastPlayMode != PlayMode::oneSecondPlay && + !gAudioIO->IsPaused()) + { + mProject->TP_ScrollWindow(playPos); + // Might yet be off screen, check it + onScreen = playPos >= 0.0 && between_incexc(viewInfo.h, playPos, mProject->GetScreenEndTime()); + } } // Always update scrollbars even if not scrolling the window. This is diff --git a/src/tracks/ui/Scrubbing.cpp b/src/tracks/ui/Scrubbing.cpp index 551af8ca1..a3023c42d 100644 --- a/src/tracks/ui/Scrubbing.cpp +++ b/src/tracks/ui/Scrubbing.cpp @@ -68,8 +68,10 @@ namespace { // with the time at the midline of the screen mapping to 0, // and the extremes to the maximum scrub speed. - // Width of visible track area, in time terms: - const double origin = viewInfo.h + screen / 2.0; + auto partScreen = screen * TracksPrefs::GetPinnedHeadPositionPreference(); + const double origin = viewInfo.h + partScreen; + if (timeAtMouse >= origin) + partScreen = screen - partScreen; // There are various snapping zones that are this fraction of screen: const double snap = 0.05; @@ -77,8 +79,9 @@ namespace { // By shrinking denom a bit, we make margins left and right // that snap to maximum and negative maximum speeds. const double factor = 1.0 - (snap * 2); - const double denom = factor * screen / 2.0; - double fraction = std::min(1.0, fabs(timeAtMouse - origin) / denom); + const double denom = factor * partScreen; + double fraction = (denom <= 0.0) ? 0.0 : + std::min(1.0, fabs(timeAtMouse - origin) / denom); // Snap to 1.0 and -1.0 const double unity = 1.0 / maxScrubSpeed; @@ -115,14 +118,16 @@ namespace { const double extreme = std::max(1.0, maxScrubSpeed * ARBITRARY_MULTIPLIER); // Width of visible track area, in time terms: - const double halfScreen = screen / 2.0; - const double origin = viewInfo.h + halfScreen; + auto partScreen = screen * TracksPrefs::GetPinnedHeadPositionPreference(); + const double origin = viewInfo.h + partScreen; + if (timeAtMouse >= origin) + partScreen = screen - partScreen; // The snapping zone is this fraction of screen, on each side of the // center line: const double snap = 0.05; - const double fraction = - std::max(snap, std::min(1.0, fabs(timeAtMouse - origin) / halfScreen)); + const double fraction = (partScreen <= 0.0) ? 0.0 : + std::max(snap, std::min(1.0, fabs(timeAtMouse - origin) / partScreen)); double result = 1.0 + ((fraction - snap) / (1.0 - snap)) * (extreme - 1.0); if (timeAtMouse < origin) @@ -343,7 +348,9 @@ bool Scrubber::MaybeStartScrubbing(wxCoord xx) if (mDragging && mSmoothScrollingScrub) { auto delta = time0 - time1; time0 = std::max(0.0, std::min(maxTime, - (viewInfo.h + mProject->GetScreenEndTime()) / 2 + viewInfo.h + + (mProject->GetScreenEndTime() - viewInfo.h) + * TracksPrefs::GetPinnedHeadPositionPreference() )); time1 = time0 + delta; } diff --git a/src/widgets/Ruler.cpp b/src/widgets/Ruler.cpp index 208aab6e2..540e3bb72 100644 --- a/src/widgets/Ruler.cpp +++ b/src/widgets/Ruler.cpp @@ -2168,6 +2168,121 @@ private: SelectedRegion mOldSelection; }; +namespace +{ + +wxCoord GetPlayHeadX( const AudacityProject *pProject ) +{ + const TrackPanel *tp = pProject->GetTrackPanel(); + int width; + tp->GetTracksUsableArea(&width, NULL); + return tp->GetLeftOffset() + + width * TracksPrefs::GetPinnedHeadPositionPreference(); +} + +double GetPlayHeadFraction( const AudacityProject *pProject, wxCoord xx ) +{ + const TrackPanel *tp = pProject->GetTrackPanel(); + int width; + tp->GetTracksUsableArea(&width, NULL); + auto fraction = (xx - tp->GetLeftOffset()) / double(width); + return std::max(0.0, std::min(1.0, fraction)); +} + +// Handle for dragging the pinned play head, which so far does not need +// to be a friend of the AdornedRulerPanel class, so we don't make it nested. +class PlayheadHandle : public UIHandle +{ +public: + explicit + PlayheadHandle( wxCoord xx ) + : mX( xx ) + {} + + static UIHandle::Result NeedChangeHighlight + (const PlayheadHandle &oldState, const PlayheadHandle &newState) + { + if (oldState.mX != newState.mX) + return RefreshCode::DrawOverlays; + return 0; + } + + static std::shared_ptr + HitTest( const AudacityProject *pProject, wxCoord xx ) + { + if( TracksPrefs::GetPinnedHeadPreference() && + pProject->IsAudioActive() ) + { + const auto targetX = GetPlayHeadX( pProject ); + if ( abs( xx - targetX ) <= SELECT_TOLERANCE_PIXEL ) + return std::make_shared( xx ); + } + return {}; + } + +protected: + Result Click + (const TrackPanelMouseEvent &event, AudacityProject *pProject) override + { + if (event.event.LeftDClick()) { + // Restore default position on double click + TracksPrefs::SetPinnedHeadPositionPreference( 0.5, true ); + + return RefreshCode::DrawOverlays | + // Do not start a drag + RefreshCode::Cancelled; + } + + mOrigPreference = TracksPrefs::GetPinnedHeadPositionPreference(); + return 0; + } + + Result Drag + (const TrackPanelMouseEvent &event, AudacityProject *pProject) override + { + auto value = GetPlayHeadFraction(pProject, event.event.m_x ); + TracksPrefs::SetPinnedHeadPositionPreference( value ); + return RefreshCode::DrawOverlays; + } + + HitTestPreview Preview + (const TrackPanelMouseState &state, const AudacityProject *pProject) + override + { + static wxCursor cursor{ wxCURSOR_SIZEWE }; + return { + _( "Click and drag to adjust, double-click to reset" ), + &cursor, + _( "Record/Play head" ) + }; + } + + Result Release + (const TrackPanelMouseEvent &event, AudacityProject *pProject, + wxWindow *) override + { + auto value = GetPlayHeadFraction(pProject, event.event.m_x ); + TracksPrefs::SetPinnedHeadPositionPreference( value, true ); + return RefreshCode::DrawOverlays; + } + + Result Cancel(AudacityProject *pProject) override + { + TracksPrefs::SetPinnedHeadPositionPreference( mOrigPreference ); + return RefreshCode::DrawOverlays; + } + + void Enter(bool) override + { + mChangeHighlight = RefreshCode::DrawOverlays; + } + + wxCoord mX; + double mOrigPreference {}; +}; + +} + class AdornedRulerPanel::QPCell final : public CommonCell { public: @@ -2192,6 +2307,7 @@ public: } std::weak_ptr mHolder; + std::weak_ptr mPlayheadHolder; }; std::vector AdornedRulerPanel::QPCell::HitTest @@ -2203,11 +2319,21 @@ std::vector AdornedRulerPanel::QPCell::HitTest mParent->CreateOverlays(); std::vector results; + auto xx = state.state.m_x; +#ifdef EXPERIMENTAL_DRAGGABLE_PLAY_HEAD + // Allow click and drag on the play head even while recording + // Make this handle more prominent then the quick play handle + auto result = PlayheadHandle::HitTest( pProject, xx ); + if (result) { + result = AssignUIHandlePtr( mPlayheadHolder, result ); + results.push_back( result ); + } +#endif + // Disable mouse actions on Timeline while recording. if (!mParent->mIsRecording) { - auto xx = state.state.m_x; mParent->UpdateQuickPlayPos( xx, state.state.ShiftDown() ); auto result = std::make_shared( mParent, xx );