diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index ca60a742a..b3de34a80 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -853,6 +853,8 @@ list( APPEND SOURCES # Tracks UI + tracks/ui/AffordanceHandle.cpp + tracks/ui/AffordanceHandle.h tracks/ui/BackgroundCell.cpp tracks/ui/BackgroundCell.h tracks/ui/ButtonHandle.cpp diff --git a/src/tracks/ui/AffordanceHandle.cpp b/src/tracks/ui/AffordanceHandle.cpp new file mode 100644 index 000000000..7739df491 --- /dev/null +++ b/src/tracks/ui/AffordanceHandle.cpp @@ -0,0 +1,116 @@ +/*!******************************************************************** +* + Audacity: A Digital Audio Editor + + AffordanceHandle.cpp + + Vitaly Sverchinsky + + **********************************************************************/ + +#include "AffordanceHandle.h" + +#include "../../HitTestResult.h" +#include "../../ProjectAudioIO.h" +#include "../../RefreshCode.h" +#include "../../ViewInfo.h" +#include "../../SelectionState.h" +#include "../../ProjectSettings.h" +#include "../../TrackPanelMouseEvent.h" +#include "../../WaveClip.h" +#include "../../ProjectHistory.h" +#include "../../Track.h" +#include "../../WaveTrack.h" +#include "../../../images/Cursors.h" + +UIHandlePtr AffordanceHandle::HitAnywhere(std::weak_ptr& holder, const std::shared_ptr& pTrack) +{ + auto result = std::make_shared(pTrack); + result = AssignUIHandlePtr(holder, result); + return result; +} + +HitTestPreview AffordanceHandle::HitPreview(const AudacityProject*, bool unsafe, bool moving) +{ + static auto disabledCursor = + MakeCursor(wxCURSOR_NO_ENTRY, DisabledCursorXpm, 16, 16); + static auto handOpenCursor = + MakeCursor(wxCURSOR_HAND, RearrangeCursorXpm, 16, 16); + static auto handClosedCursor = + MakeCursor(wxCURSOR_HAND, RearrangingCursorXpm, 16, 16); + // i18n-hint Appears on hovering mouse over clip affordance + auto message = XO("Click and drag to move a clip in time, or click to select"); + + if (unsafe) + return { message, &*disabledCursor }; + return { + message, + (moving + ? &*handClosedCursor + : &*handOpenCursor) + }; +} + +HitTestPreview AffordanceHandle::Preview(const TrackPanelMouseState& mouseState, AudacityProject* pProject) +{ + const bool unsafe = ProjectAudioIO::Get(*pProject).IsAudioActive(); + return HitPreview(pProject, unsafe, Clicked()); +} + +AffordanceHandle::AffordanceHandle(const std::shared_ptr& track) + : TimeShiftHandle(track, false) +{ + SetChangeHighlight(RefreshCode::RefreshCell | RefreshCode::RefreshLatestCell); +} + +UIHandle::Result AffordanceHandle::Click(const TrackPanelMouseEvent& evt, AudacityProject* pProject) +{ + auto result = TimeShiftHandle::Click(evt, pProject); + return result | RefreshCode::RefreshCell; +} + +UIHandle::Result AffordanceHandle::Release(const TrackPanelMouseEvent& event, AudacityProject* pProject, wxWindow* pParent) +{ + auto result = TimeShiftHandle::Release(event, pProject, pParent); + //Clip was not moved + if (!TimeShiftHandle::WasMoved()) + { + //almost the same behaviour as provided by SelectHandle + auto& viewInfo = ViewInfo::Get(*pProject); + const auto& settings = ProjectSettings::Get(*pProject); + + const auto sTrack = TrackList::Get(*pProject).Lock(GetTrack()); + const auto pTrack = sTrack.get(); + + auto& selectionState = SelectionState::Get(*pProject); + + auto& trackList = TrackList::Get(*pProject); + + // Deselect all other tracks and select this one. + selectionState.SelectNone(trackList); + selectionState.SelectTrack(*pTrack, true, true); + + pTrack->TypeSwitch( + [&](WaveTrack* wt) + { + auto time = viewInfo.PositionToTime(event.event.m_x, event.rect.x); + WaveClip* const selectedClip = wt->GetClipAtTime(time); + if (selectedClip) { + viewInfo.selectedRegion.setTimes( + selectedClip->GetOffset(), selectedClip->GetEndTime()); + } + }, + [&](Track* track) + { + // Default behavior: select whole track + SelectionState::SelectTrackLength(viewInfo, *track, settings.IsSyncLocked()); + } + ); + + ProjectHistory::Get(*pProject).ModifyState(false); + + // Do not start a drag + result |= RefreshCode::RefreshAll | RefreshCode::Cancelled; + } + return result; +} diff --git a/src/tracks/ui/AffordanceHandle.h b/src/tracks/ui/AffordanceHandle.h new file mode 100644 index 000000000..1224bf16f --- /dev/null +++ b/src/tracks/ui/AffordanceHandle.h @@ -0,0 +1,28 @@ +/*!******************************************************************** +* + Audacity: A Digital Audio Editor + + AffordanceHandle.h + + Vitaly Sverchinsky + + **********************************************************************/ + +#pragma once + +#include "TimeShiftHandle.h" + +class AUDACITY_DLL_API AffordanceHandle : public TimeShiftHandle +{ + static HitTestPreview HitPreview(const AudacityProject*, bool unsafe, bool moving); +public: + + static UIHandlePtr HitAnywhere(std::weak_ptr& holder, const std::shared_ptr& pTrack); + + HitTestPreview Preview(const TrackPanelMouseState& mouseState, AudacityProject* pProject) override; + + AffordanceHandle(const std::shared_ptr& track); + + Result Click(const TrackPanelMouseEvent& evt, AudacityProject* pProject) override; + Result Release(const TrackPanelMouseEvent& event, AudacityProject* pProject, wxWindow* pParent) override; +}; diff --git a/src/tracks/ui/TimeShiftHandle.cpp b/src/tracks/ui/TimeShiftHandle.cpp index 5972e15ea..078a1f267 100644 --- a/src/tracks/ui/TimeShiftHandle.cpp +++ b/src/tracks/ui/TimeShiftHandle.cpp @@ -34,6 +34,21 @@ TimeShiftHandle::TimeShiftHandle mClipMoveState.mCapturedTrack = pTrack; } +std::shared_ptr TimeShiftHandle::GetTrack() const +{ + return mClipMoveState.mCapturedTrack; +} + +bool TimeShiftHandle::WasMoved() const +{ + return mDidSlideVertically || (mClipMoveState.initialized && mClipMoveState.wasMoved); +} + +bool TimeShiftHandle::Clicked() const +{ + return mClipMoveState.initialized; +} + void TimeShiftHandle::Enter(bool, AudacityProject *) { #ifdef EXPERIMENTAL_TRACK_PANEL_HIGHLIGHTING @@ -275,6 +290,8 @@ void ClipMoveState::Init( { shifters.clear(); + initialized = true; + auto &state = *this; state.mCapturedTrack = capturedTrack.SharedPointer(); @@ -427,6 +444,9 @@ double ClipMoveState::DoSlideHorizontal( double desiredSlideAmount ) if ( desiredSlideAmount != 0.0 ) state.DoHorizontalOffset( desiredSlideAmount ); + //attempt to move a clip is counted to + wasMoved = true; + return (state.hSlideAmount = desiredSlideAmount); } diff --git a/src/tracks/ui/TimeShiftHandle.h b/src/tracks/ui/TimeShiftHandle.h index 0b8ecfe6f..20b4b6d5f 100644 --- a/src/tracks/ui/TimeShiftHandle.h +++ b/src/tracks/ui/TimeShiftHandle.h @@ -234,7 +234,9 @@ struct AUDACITY_DLL_API ClipMoveState { std::shared_ptr mCapturedTrack; + bool initialized{ false }; bool movingSelection {}; + bool wasMoved{ false }; double hSlideAmount {}; ShifterMap shifters; wxInt64 snapLeft { -1 }, snapRight { -1 }; @@ -243,6 +245,8 @@ struct AUDACITY_DLL_API ClipMoveState { void clear() { + initialized = false; + wasMoved = false; movingSelection = false; hSlideAmount = 0; shifters.clear(); @@ -251,7 +255,7 @@ struct AUDACITY_DLL_API ClipMoveState { } }; -class AUDACITY_DLL_API TimeShiftHandle final : public UIHandle +class AUDACITY_DLL_API TimeShiftHandle : public UIHandle { TimeShiftHandle(const TimeShiftHandle&) = delete; static HitTestPreview HitPreview @@ -264,7 +268,6 @@ public: TimeShiftHandle &operator=(TimeShiftHandle&&) = default; bool IsGripHit() const { return mGripHit; } - std::shared_ptr GetTrack() const = delete; // Try to move clips from one track to another, before also moving // by some horizontal amount, which may be slightly adjusted to fit the @@ -304,6 +307,12 @@ public: bool StopsOnKeystroke() override { return true; } + bool Clicked() const; + +protected: + std::shared_ptr GetTrack() const; + //There were attempt to move clip/track horizontally, or to move it vertically + bool WasMoved() const; private: // TrackPanelDrawable implementation void Draw(