mirror of
https://github.com/cookiengineer/audacity
synced 2025-05-04 01:29:43 +02:00
Pinned play head can be dragged during transport; dbl-click recenters
This commit is contained in:
commit
511b810fdc
@ -248,4 +248,7 @@
|
||||
// PRL 1 Jun 2018
|
||||
#define EXPERIMENTAL_PUNCH_AND_ROLL
|
||||
|
||||
// PRL 31 July 2018
|
||||
#define EXPERIMENTAL_DRAGGABLE_PLAY_HEAD
|
||||
|
||||
#endif
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -813,7 +813,7 @@ public:
|
||||
enum class Mode {
|
||||
Off,
|
||||
Refresh,
|
||||
Centered,
|
||||
Pinned,
|
||||
Right,
|
||||
};
|
||||
|
||||
|
@ -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 =
|
||||
|
@ -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();
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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<PlayheadHandle>
|
||||
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<PlayheadHandle>( 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<QPHandle> mHolder;
|
||||
std::weak_ptr<PlayheadHandle> mPlayheadHolder;
|
||||
};
|
||||
|
||||
std::vector<UIHandlePtr> AdornedRulerPanel::QPCell::HitTest
|
||||
@ -2203,11 +2319,21 @@ std::vector<UIHandlePtr> AdornedRulerPanel::QPCell::HitTest
|
||||
mParent->CreateOverlays();
|
||||
|
||||
std::vector<UIHandlePtr> 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<QPHandle>( mParent, xx );
|
||||
|
Loading…
x
Reference in New Issue
Block a user