1
0
mirror of https://github.com/cookiengineer/audacity synced 2025-05-07 15:22:34 +02:00

Cherry-pick some of PR#1207, Track affordances

This commit is contained in:
Paul Licameli 2021-07-16 06:07:19 -04:00
commit 731af5109a
25 changed files with 717 additions and 60 deletions

View File

@ -371,3 +371,7 @@ from there. Audacity will look for a file called "Pause.png".
DEFINE_COLOUR( clrSpectro4Sel, wxColour( 191, 0, 0), wxT("Spectro4Sel") );
DEFINE_COLOUR( clrSpectro5Sel, wxColour( 191, 191, 191), wxT("Spectro5Sel") );
DEFINE_COLOUR( clrClipAffordanceOutlinePen, wxColour( 0, 0, 0), wxT("ClipAffordanceOutlinePen") );
DEFINE_COLOUR( clrClipAffordanceInactiveBrush, wxColour( 202, 202, 202), wxT("ClipAffordanceUnselectedBrush") );
DEFINE_COLOUR( clrClipAffordanceActiveBrush, wxColour( 219, 219, 219), wxT("ClipAffordanceSelectedBrush") );
DEFINE_COLOUR( clrClipAffordanceStroke, wxColour( 255, 255, 255), wxT("ClipAffordanceStroke") );

View File

@ -796,6 +796,8 @@ list( APPEND SOURCES
tracks/labeltrack/ui/LabelTrackVRulerControls.h
tracks/labeltrack/ui/LabelTrackView.cpp
tracks/labeltrack/ui/LabelTrackView.h
tracks/playabletrack/notetrack/ui/NoteTrackAffordanceControls.h
tracks/playabletrack/notetrack/ui/NoteTrackAffordanceControls.cpp
tracks/playabletrack/notetrack/ui/NoteTrackButtonHandle.cpp
tracks/playabletrack/notetrack/ui/NoteTrackButtonHandle.h
tracks/playabletrack/notetrack/ui/NoteTrackControls.cpp
@ -825,6 +827,8 @@ list( APPEND SOURCES
tracks/playabletrack/wavetrack/ui/SpectrumVZoomHandle.h
tracks/playabletrack/wavetrack/ui/SpectrumView.cpp
tracks/playabletrack/wavetrack/ui/SpectrumView.h
tracks/playabletrack/wavetrack/ui/WaveTrackAffordanceControls.cpp
tracks/playabletrack/wavetrack/ui/WaveTrackAffordanceControls.h
tracks/playabletrack/wavetrack/ui/WaveTrackControls.cpp
tracks/playabletrack/wavetrack/ui/WaveTrackControls.h
tracks/playabletrack/wavetrack/ui/WaveTrackShifter.cpp
@ -855,6 +859,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

View File

@ -56,6 +56,9 @@ audio tracks.
#include <wx/dc.h>
//Thickness of the clip frame outline, shown when clip is dragged
static constexpr int ClipSelectionStrokeSize{ 1 };//px
TrackArtist::TrackArtist( TrackPanel *parent_ )
: parent( parent_ )
{
@ -260,6 +263,60 @@ void TrackArtist::UpdatePrefs()
SetColours(0);
}
void TrackArt::DrawClipAffordance(wxDC& dc, const wxRect& rect, bool highlight, bool selected)
{
if (selected)
{
wxRect strokeRect{
rect.x - ClipSelectionStrokeSize,
rect.y,
rect.width + ClipSelectionStrokeSize * 2,
rect.height + ClipFrameRadius };
dc.SetBrush(*wxTRANSPARENT_BRUSH);
AColor::UseThemeColour(&dc, clrClipAffordanceStroke, clrClipAffordanceStroke);
dc.DrawRoundedRectangle(strokeRect, ClipFrameRadius);
}
AColor::UseThemeColour(&dc, highlight ? clrClipAffordanceActiveBrush : clrClipAffordanceInactiveBrush, clrClipAffordanceOutlinePen);
dc.DrawRoundedRectangle(wxRect(rect.x, rect.y + ClipSelectionStrokeSize, rect.width, rect.height + ClipFrameRadius), ClipFrameRadius);
}
void TrackArt::DrawClipEdges(wxDC& dc, const wxRect& clipRect, bool selected)
{
dc.SetBrush(*wxTRANSPARENT_BRUSH);
{
AColor::UseThemeColour(&dc, -1, clrClipAffordanceOutlinePen);
AColor::Line(dc,
clipRect.GetLeft(), clipRect.GetTop(),
clipRect.GetLeft(), clipRect.GetBottom());
AColor::Line(dc,
clipRect.GetRight(), clipRect.GetTop(),
clipRect.GetRight(), clipRect.GetBottom());
}
if(selected)
{
if constexpr (ClipSelectionStrokeSize == 1)
{
AColor::UseThemeColour(&dc, -1, clrClipAffordanceStroke);
AColor::Line(dc,
clipRect.GetLeft() - ClipSelectionStrokeSize, clipRect.GetTop(),
clipRect.GetLeft() - ClipSelectionStrokeSize, clipRect.GetBottom());
AColor::Line(dc,
clipRect.GetRight() + ClipSelectionStrokeSize, clipRect.GetTop(),
clipRect.GetRight() + ClipSelectionStrokeSize, clipRect.GetBottom());
}
else if constexpr (ClipSelectionStrokeSize > 1)
{
AColor::UseThemeColour(&dc, clrClipAffordanceStroke, clrClipAffordanceStroke);
dc.DrawRectangle(wxRect(
clipRect.GetLeft() - ClipSelectionStrokeSize, clipRect.GetTop(),
ClipSelectionStrokeSize, clipRect.GetHeight()));
dc.DrawRectangle(wxRect(
clipRect.GetRight() + 1, clipRect.GetTop(),
ClipSelectionStrokeSize, clipRect.GetHeight()));
}
}
}
// Draws the sync-lock bitmap, tiled; always draws stationary relative to the DC
//
// AWD: now that the tiles don't link together, we're drawing a tilted grid, at

View File

@ -38,6 +38,14 @@ class ZoomInfo;
namespace TrackArt {
static constexpr int ClipFrameRadius{ 6 };
AUDACITY_DLL_API
void DrawClipAffordance(wxDC& dc, const wxRect& affordanceRect, bool highlight = false, bool selected = false);
AUDACITY_DLL_API
void DrawClipEdges(wxDC& dc, const wxRect& clipRect, bool selected = false);
// Helper: draws the "sync-locked" watermark tiled to a rectangle
AUDACITY_DLL_API
void DrawSyncLockTiles(

View File

@ -1270,7 +1270,61 @@ struct VRulersAndChannels final : TrackPanelGroup {
wxCoord mLeftOffset;
};
// n channels with vertical rulers, alternating with n - 1 resizers;
//Simply fills area using specified brush and outlines borders
class EmptyPanelRect final : public CommonTrackPanelCell
{
int mFillBrushName;
public:
explicit EmptyPanelRect(int fillBrushName)
: mFillBrushName(fillBrushName)
{
}
~EmptyPanelRect() { }
void Draw(TrackPanelDrawingContext& context, const wxRect& rect, unsigned iPass)
{
if (iPass == TrackArtist::PassBackground)
{
context.dc.SetPen(*wxTRANSPARENT_PEN);
AColor::UseThemeColour(&context.dc, mFillBrushName);
context.dc.DrawRectangle(rect);
wxRect bevel(rect.x, rect.y, rect.width - 1, rect.height - 1);
AColor::BevelTrackInfo(context.dc, true, bevel, false);
}
}
std::shared_ptr<Track> DoFindTrack() override
{
return {};
}
std::vector<UIHandlePtr> HitTest(const TrackPanelMouseState& state, const AudacityProject* pProject)
{
return {};
}
};
//Simply place children one after another horizontally, without any specific logic
struct HorizontalGroup final : TrackPanelGroup {
Refinement mRefinement;
HorizontalGroup(Refinement refinement)
: mRefinement(std::move(refinement))
{
}
Subdivision Children(const wxRect& /*rect*/) override
{
return { Axis::X, mRefinement };
}
};
// optional affordance area, and n channels with vertical rulers,
// alternating with n - 1 resizers;
// each channel-ruler pair might be divided into multiple views
struct ChannelGroup final : TrackPanelGroup {
ChannelGroup( const std::shared_ptr< Track > &pTrack, wxCoord leftOffset )
@ -1283,8 +1337,19 @@ struct ChannelGroup final : TrackPanelGroup {
const auto channels = TrackList::Channels( mpTrack.get() );
const auto pLast = *channels.rbegin();
wxCoord yy = rect.GetTop();
for ( auto channel : channels ) {
for ( auto channel : channels )
{
auto &view = TrackView::Get( *channel );
if (auto affordance = view.GetAffordanceControls())
{
Refinement hgroup {
std::make_pair(rect.GetLeft() + 1, std::make_shared<EmptyPanelRect>(channel->GetSelected() ? clrTrackInfoSelected : clrTrackInfo)),
std::make_pair(mLeftOffset, affordance)
};
refinement.emplace_back(yy, std::make_shared<HorizontalGroup>(hgroup));
yy += kAffordancesAreaHeight;
}
auto height = view.GetHeight();
rect.SetTop( yy );
rect.SetHeight( height - kSeparatorThickness );
@ -1444,6 +1509,9 @@ struct Subgroup final : TrackPanelGroup {
for ( auto channel : TrackList::Channels( leader ) ) {
auto &view = TrackView::Get( *channel );
height += view.GetHeight();
if (view.GetAffordanceControls())
height += kAffordancesAreaHeight;
}
refinement.emplace_back( yy,
std::make_shared< ResizingChannelGroup >(

View File

@ -101,6 +101,7 @@ private:
// See big pictorial comment in TrackPanel.cpp for explanation of these numbers
enum : int {
// constants related to y coordinates in the track panel
kAffordancesAreaHeight = 20,
kTopInset = 4,
kTopMargin = kTopInset + kBorderThickness,
kBottomMargin = kShadowThickness + kBorderThickness,

View File

@ -0,0 +1,93 @@
/*!********************************************************************
*
Audacity: A Digital Audio Editor
NoteTrackAffordanceControls.cpp
Vitaly Sverchinsky
**********************************************************************/
#include "NoteTrackAffordanceControls.h"
#include <wx/dc.h>
#include "../../../ui/AffordanceHandle.h"
#include "../../../../AllThemeResources.h"
#include "../../../../AColor.h"
#include "../../../../NoteTrack.h"
#include "../../../../ViewInfo.h"
#include "../../../../TrackArtist.h"
#include "../../../../TrackPanelMouseEvent.h"
#include "../../../../TrackPanelDrawingContext.h"
#include "../lib-src/header-substitutes/allegro.h"
NoteTrackAffordanceControls::NoteTrackAffordanceControls(const std::shared_ptr<Track>& pTrack)
: CommonTrackCell(pTrack)
{
}
std::vector<UIHandlePtr> NoteTrackAffordanceControls::HitTest(const TrackPanelMouseState& state, const AudacityProject* pProject)
{
std::vector<UIHandlePtr> results;
auto track = FindTrack();
const auto nt = std::static_pointer_cast<const NoteTrack>(track->SubstitutePendingChangedTrack());
const auto rect = state.rect;
auto& zoomInfo = ViewInfo::Get(*pProject);
auto left = zoomInfo.TimeToPosition(nt->GetOffset(), rect.x);
auto right = zoomInfo.TimeToPosition(nt->GetOffset() + nt->GetSeq().get_real_dur(), rect.x);
auto headerRect = wxRect(left, rect.y, right - left, rect.height);
auto px = state.state.m_x;
auto py = state.state.m_y;
if (px >= headerRect.GetLeft() && px <= headerRect.GetRight() &&
py >= headerRect.GetTop() && py <= headerRect.GetBottom())
{
results.push_back(AffordanceHandle::HitAnywhere(mAffordanceHandle, track));
}
return results;
}
void NoteTrackAffordanceControls::Draw(TrackPanelDrawingContext& context, const wxRect& rect, unsigned iPass)
{
if (iPass == TrackArtist::PassBackground) {
const auto nt = std::static_pointer_cast<const NoteTrack>(FindTrack()->SubstitutePendingChangedTrack());
const auto artist = TrackArtist::Get(context);
TrackArt::DrawBackgroundWithSelection(context, rect, nt.get(), AColor::labelSelectedBrush, AColor::labelUnselectedBrush);
const auto& zoomInfo = *artist->pZoomInfo;
auto left = zoomInfo.TimeToPosition(nt->GetOffset(), rect.x);
auto right = zoomInfo.TimeToPosition(nt->GetOffset() + nt->GetSeq().get_real_dur(), rect.x);
auto clipRect = wxRect(left, rect.y, right - left + 1, rect.height);
auto px = context.lastState.m_x;
auto py = context.lastState.m_y;
auto selected = IsSelected();
auto highlight = selected ||
(px >= clipRect.GetLeft() && px <= clipRect.GetRight() &&
py >= clipRect.GetTop() && py <= clipRect.GetBottom());
context.dc.SetClippingRegion(rect);
TrackArt::DrawClipAffordance(context.dc, clipRect, highlight, selected);
context.dc.DestroyClippingRegion();
}
}
bool NoteTrackAffordanceControls::IsSelected() const
{
if (auto handle = mAffordanceHandle.lock())
{
return handle->Clicked();
}
return false;
}

View File

@ -0,0 +1,28 @@
/*!********************************************************************
*
Audacity: A Digital Audio Editor
NoteTrackAffordanceControls.h
Vitaly Sverchinsky
**********************************************************************/
#pragma once
#include "../../../ui/CommonTrackPanelCell.h"
class AffordanceHandle;
class AUDACITY_DLL_API NoteTrackAffordanceControls : public CommonTrackCell
{
std::weak_ptr<AffordanceHandle> mAffordanceHandle;
public:
NoteTrackAffordanceControls(const std::shared_ptr<Track>& pTrack);
std::vector<UIHandlePtr> HitTest(const TrackPanelMouseState& state, const AudacityProject* pProject) override;
void Draw(TrackPanelDrawingContext& context, const wxRect& rect, unsigned iPass) override;
bool IsSelected() const;
};

View File

@ -27,6 +27,7 @@ Paul Licameli split from TrackPanel.cpp
#include "../../../../ViewInfo.h"
#include "../../../ui/SelectHandle.h"
#include "StretchHandle.h"
#include "NoteTrackAffordanceControls.h"
#include <wx/dc.h>
@ -75,6 +76,11 @@ std::shared_ptr<TrackVRulerControls> NoteTrackView::DoGetVRulerControls()
#define TIME_TO_X(t) (zoomInfo.TimeToPosition((t), rect.x))
#define X_TO_TIME(xx) (zoomInfo.PositionToTime((xx), rect.x))
std::shared_ptr<CommonTrackCell> NoteTrackView::DoGetAffordanceControls()
{
return std::make_shared<NoteTrackAffordanceControls>(DoFindTrack());
}
namespace {
/*
@ -352,7 +358,8 @@ window and draw out-of-bounds notes here instead.
void DrawNoteTrack(TrackPanelDrawingContext &context,
const NoteTrack *track,
const wxRect & rect,
bool muted)
bool muted,
bool selected)
{
auto &dc = context.dc;
const auto artist = TrackArtist::Get( context );
@ -378,11 +385,6 @@ void DrawNoteTrack(TrackPanelDrawingContext &context,
int numPitches = (rect.height) / data.GetPitchHeight(1);
if (numPitches < 0) numPitches = 0; // cannot be negative
#ifdef EXPERIMENTAL_NOTETRACK_OVERLAY
TrackArt::DrawBackgroundWithSelection(context, rect, track,
AColor::labelSelectedBrush, AColor::labelUnselectedBrush);
#endif
// Background comes in 4 colors, that are now themed.
// 214, 214,214 -- unselected white keys
// 192,192,192 -- black keys
@ -704,6 +706,14 @@ void DrawNoteTrack(TrackPanelDrawingContext &context,
TrackArt::DrawNegativeOffsetTrackArrows( context, rect );
}
//draw clip edges
{
int left = TIME_TO_X(track->GetOffset());
int right = TIME_TO_X(track->GetOffset() + track->GetSeq().get_real_dur());
TrackArt::DrawClipEdges(dc, wxRect(left, rect.GetTop(), right - left + 1, rect.GetHeight()), selected);
}
dc.DestroyClippingRegion();
SonifyEndNoteForeground();
}
@ -723,7 +733,17 @@ void NoteTrackView::Draw(
const auto hasSolo = artist->hasSolo;
muted = (hasSolo || nt->GetMute()) && !nt->GetSolo();
#endif
DrawNoteTrack( context, nt.get(), rect, muted );
#ifdef EXPERIMENTAL_NOTETRACK_OVERLAY
TrackArt::DrawBackgroundWithSelection(context, rect, nt.get(), AColor::labelSelectedBrush, AColor::labelUnselectedBrush);
#endif
bool selected{ false };
if (auto affordance = std::dynamic_pointer_cast<NoteTrackAffordanceControls>(GetAffordanceControls()))
{
selected = affordance->IsSelected();
}
DrawNoteTrack(context, nt.get(), rect, muted, selected);
}
CommonTrackView::Draw( context, rect, iPass );
}

View File

@ -23,9 +23,10 @@ public:
NoteTrackView( const std::shared_ptr<Track> &pTrack );
~NoteTrackView() override;
std::shared_ptr<TrackVRulerControls> DoGetVRulerControls() override;
private:
std::shared_ptr<TrackVRulerControls> DoGetVRulerControls() override;
std::shared_ptr<CommonTrackCell> DoGetAffordanceControls() override;
std::vector<UIHandlePtr> DetailedHitTest
(const TrackPanelMouseState &state,
const AudacityProject *pProject, int currentTool, bool bMultiTool)

View File

@ -172,7 +172,8 @@ ChooseColorSet( float bin0, float bin1, float selBinLo,
void DrawClipSpectrum(TrackPanelDrawingContext &context,
WaveTrackCache &waveTrackCache,
const WaveClip *clip,
const wxRect & rect)
const wxRect & rect,
bool selected)
{
auto &dc = context.dc;
const auto artist = TrackArtist::Get( context );
@ -608,13 +609,19 @@ void DrawClipSpectrum(TrackPanelDrawingContext &context,
// Draw clip edges, as also in waveform view, which improves the appearance
// of split views
params.DrawClipEdges( dc, rect );
{
//increase virtual view size by px to hide edges that should not be visible
auto clipRect = ClipParameters::GetClipRect(*clip, zoomInfo, rect.Inflate(1, 0), 1);
if (!clipRect.IsEmpty())
TrackArt::DrawClipEdges(dc, clipRect, selected);
}
}
}
void SpectrumView::DoDraw( TrackPanelDrawingContext &context,
const WaveTrack *track,
void SpectrumView::DoDraw(TrackPanelDrawingContext& context,
const WaveTrack* track,
const WaveClip* selectedClip,
const wxRect & rect )
{
const auto artist = TrackArtist::Get( context );
@ -625,7 +632,7 @@ void SpectrumView::DoDraw( TrackPanelDrawingContext &context,
WaveTrackCache cache(track->SharedPointer<const WaveTrack>());
for (const auto &clip: track->GetClips())
DrawClipSpectrum( context, cache, clip.get(), rect );
DrawClipSpectrum( context, cache, clip.get(), rect, clip.get() == selectedClip );
DrawBoldBoundaries( context, track, rect );
}
@ -653,14 +660,18 @@ void SpectrumView::Draw(
wxAntialiasMode aamode = dc.GetGraphicsContext()->GetAntialiasMode();
dc.GetGraphicsContext()->SetAntialiasMode(wxANTIALIAS_NONE);
#endif
auto waveTrackView = GetWaveTrackView().lock();
wxASSERT(waveTrackView.use_count());
DoDraw( context, wt.get(), rect );
auto seletedClip = waveTrackView->GetSelectedClip().lock();
DoDraw( context, wt.get(), seletedClip.get(), rect );
#if defined(__WXMAC__)
dc.GetGraphicsContext()->SetAntialiasMode(aamode);
#endif
}
CommonTrackView::Draw( context, rect, iPass );
WaveTrackSubView::Draw( context, rect, iPass );
}
static const WaveTrackSubViews::RegisteredFactory key{

View File

@ -38,6 +38,7 @@ private:
static void DoDraw( TrackPanelDrawingContext &context,
const WaveTrack *track,
const WaveClip* selectedClip,
const wxRect & rect );
std::vector<UIHandlePtr> DetailedHitTest(

View File

@ -0,0 +1,105 @@
/*!********************************************************************
*
Audacity: A Digital Audio Editor
WaveTrackAffordanceControls.cpp
Vitaly Sverchinsky
**********************************************************************/
#include "WaveTrackAffordanceControls.h"
#include <wx/dc.h>
#include "../../../../AllThemeResources.h"
#include "../../../../TrackPanelMouseEvent.h"
#include "../../../../TrackArtist.h"
#include "../../../../TrackPanelDrawingContext.h"
#include "../../../../ViewInfo.h"
#include "../../../../WaveTrack.h"
#include "../../../../WaveClip.h"
#include "../../../ui/AffordanceHandle.h"
#include "WaveTrackView.h"//need only ClipParameters
WaveTrackAffordanceControls::WaveTrackAffordanceControls(const std::shared_ptr<Track>& pTrack)
: CommonTrackCell(pTrack)
{
}
std::vector<UIHandlePtr> WaveTrackAffordanceControls::HitTest(const TrackPanelMouseState& state, const AudacityProject* pProject)
{
mFocusClip.reset();
std::vector<UIHandlePtr> results;
const auto track = FindTrack();
const auto waveTrack = std::static_pointer_cast<WaveTrack>(track->SubstitutePendingChangedTrack());
const auto rect = state.rect;
auto px = state.state.m_x;
auto py = state.state.m_y;
auto& zoomInfo = ViewInfo::Get(*pProject);
for (const auto& clip : waveTrack->GetClips())
{
auto affordanceRect = ClipParameters::GetClipRect(*clip.get(), zoomInfo, rect);
if (affordanceRect.Contains(px, py))
{
results.push_back(AffordanceHandle::HitAnywhere(mAffordanceHandle, track));
mFocusClip = clip;
break;
}
}
return results;
}
void WaveTrackAffordanceControls::Draw(TrackPanelDrawingContext& context, const wxRect& rect, unsigned iPass)
{
if (iPass == TrackArtist::PassBackground) {
auto track = FindTrack();
const auto artist = TrackArtist::Get(context);
TrackArt::DrawBackgroundWithSelection(context, rect, track.get(), artist->blankSelectedBrush, artist->blankBrush);
const auto waveTrack = std::static_pointer_cast<WaveTrack>(track->SubstitutePendingChangedTrack());
const auto& zoomInfo = *artist->pZoomInfo;
context.dc.SetClippingRegion(rect);
auto px = context.lastState.m_x;
auto py = context.lastState.m_y;
for (const auto& clip : waveTrack->GetClips())
{
auto affordanceRect = ClipParameters::GetClipRect(
*clip.get(),
zoomInfo,
rect.Inflate(TrackArt::ClipFrameRadius, 0),
TrackArt::ClipFrameRadius
);
if (affordanceRect.IsEmpty())
continue;
auto selected = GetSelectedClip().lock() == clip;
auto highlight = selected || affordanceRect.Contains(px, py);
TrackArt::DrawClipAffordance(context.dc, affordanceRect, highlight, selected);
}
context.dc.DestroyClippingRegion();
}
}
std::weak_ptr<WaveClip> WaveTrackAffordanceControls::GetSelectedClip() const
{
if (auto handle = mAffordanceHandle.lock())
{
return handle->Clicked() ? mFocusClip : std::weak_ptr<WaveClip>();
}
return {};
}

View File

@ -0,0 +1,30 @@
/*!********************************************************************
*
Audacity: A Digital Audio Editor
WaveTrackAffordanceControls.h
Vitaly Sverchinsky
**********************************************************************/
#pragma once
#include "../../../ui/CommonTrackPanelCell.h"
class AffordanceHandle;
class WaveClip;
class AUDACITY_DLL_API WaveTrackAffordanceControls : public CommonTrackCell
{
std::weak_ptr<WaveClip> mFocusClip;
std::weak_ptr<AffordanceHandle> mAffordanceHandle;
public:
WaveTrackAffordanceControls(const std::shared_ptr<Track>& pTrack);
std::vector<UIHandlePtr> HitTest(const TrackPanelMouseState& state, const AudacityProject* pProject) override;
void Draw(TrackPanelDrawingContext& context, const wxRect& rect, unsigned iPass) override;
std::weak_ptr<WaveClip> GetSelectedClip() const;
};

View File

@ -38,8 +38,11 @@ Paul Licameli split from TrackPanel.cpp
#include "../../../ui/ButtonHandle.h"
#include "../../../../TrackInfo.h"
#include "WaveTrackAffordanceControls.h"
namespace {
using WaveTrackSubViewPtrs = std::vector< std::shared_ptr< WaveTrackSubView > >;
// Structure that collects and modifies information on sub-view positions
@ -760,6 +763,11 @@ void WaveTrackSubView::DrawBoldBoundaries(
}
}
std::weak_ptr<WaveTrackView> WaveTrackSubView::GetWaveTrackView() const
{
return mwWaveTrackView;
}
WaveTrackView &WaveTrackView::Get( WaveTrack &track )
{
return static_cast< WaveTrackView& >( TrackView::Get( track ) );
@ -1018,6 +1026,11 @@ WaveTrackView::GetAllSubViews()
return results;
}
std::shared_ptr<CommonTrackCell> WaveTrackView::DoGetAffordanceControls()
{
return std::make_shared<WaveTrackAffordanceControls>(FindTrack());
}
void WaveTrackView::DoSetMinimized( bool minimized )
{
BuildSubViews();
@ -1229,20 +1242,23 @@ ClipParameters::ClipParameters
}
}
void ClipParameters::DrawClipEdges( wxDC &dc, const wxRect &rect ) const
wxRect ClipParameters::GetClipRect(const WaveClip& clip, const ZoomInfo& zoomInfo, const wxRect& viewRect, int clipOffsetX)
{
// Draw clip edges
dc.SetPen(*wxGREY_PEN);
if (tpre < 0) {
AColor::Line(dc,
mid.x - 1, mid.y,
mid.x - 1, mid.y + rect.height);
}
if (tpost > t1) {
AColor::Line(dc,
mid.x + mid.width, mid.y,
mid.x + mid.width, mid.y + rect.height);
}
auto srs = 1. / static_cast<double>(clip.GetRate());
//to prevent overlap left and right most samples with frame border
auto margin = .25 * srs;
auto edgeLeft = static_cast<wxInt64>(viewRect.GetLeft());
auto edgeRight = static_cast<wxInt64>(viewRect.GetRight());
auto left = std::clamp(zoomInfo.TimeToPosition(clip.GetOffset() - margin, viewRect.x + clipOffsetX, true), edgeLeft, edgeRight);
auto right = std::clamp(zoomInfo.TimeToPosition(clip.GetEndTime() - srs + margin, viewRect.x + clipOffsetX, true), edgeLeft, edgeRight);
if (right - left > 0)
{
//after clamping we can expect that left and right
//are small enough to be put into int
return wxRect(static_cast<int>(left), viewRect.y, static_cast<int>(right - left), viewRect.height);
}
//off the screen
return wxRect();
}
void WaveTrackView::Reparent( const std::shared_ptr<Track> &parent )
@ -1252,6 +1268,17 @@ void WaveTrackView::Reparent( const std::shared_ptr<Track> &parent )
WaveTrackSubViews::ForEach( [&parent](WaveTrackSubView &subView){
subView.Reparent( parent );
} );
if (mpAffordanceCellControl)
mpAffordanceCellControl->Reparent(parent);
}
std::weak_ptr<WaveClip> WaveTrackView::GetSelectedClip()
{
if (auto affordance = std::dynamic_pointer_cast<WaveTrackAffordanceControls>(GetAffordanceControls()))
{
return affordance->GetSelectedClip();
}
return {};
}
void WaveTrackView::BuildSubViews() const

View File

@ -21,6 +21,7 @@ class CutlineHandle;
class TranslatableString;
class WaveTrack;
class WaveTrackView;
class WaveClip;
class AUDACITY_DLL_API WaveTrackSubView : public CommonTrackView
{
@ -47,6 +48,8 @@ protected:
TrackPanelDrawingContext &context, const WaveTrack *track,
const wxRect &rect );
std::weak_ptr<WaveTrackView> GetWaveTrackView() const;
private:
std::weak_ptr<UIHandle> mCloseHandle;
std::weak_ptr<UIHandle> mAdjustHandle;
@ -122,6 +125,9 @@ public:
bool GetMultiView() const { return mMultiView; }
void SetMultiView( bool value ) { mMultiView = value; }
std::weak_ptr<WaveClip> GetSelectedClip();
private:
void BuildSubViews() const;
void DoSetDisplay(Display display, bool exclusive = true);
@ -141,6 +147,8 @@ private:
Refinement GetSubViews( const wxRect &rect ) override;
protected:
std::shared_ptr<CommonTrackCell> DoGetAffordanceControls() override;
void DoSetMinimized( bool minimized ) override;
// Placements are in correspondence with the array of sub-views
@ -188,7 +196,9 @@ struct AUDACITY_DLL_API ClipParameters
wxRect mid;
int leftOffset;
void DrawClipEdges( wxDC &dc, const wxRect &rect ) const;
// returns a clip rectangle restricted by viewRect,
// and with clipOffsetX - clip horizontal origin offset within view rect
static wxRect GetClipRect(const WaveClip& clip, const ZoomInfo& zoomInfo, const wxRect& viewRect, int clipOffsetX = 0);
};
#endif

View File

@ -670,7 +670,8 @@ void DrawClipWaveform(TrackPanelDrawingContext &context,
const WaveClip *clip,
const wxRect & rect,
bool dB,
bool muted)
bool muted,
bool selected)
{
auto &dc = context.dc;
const auto artist = TrackArtist::Get( context );
@ -900,7 +901,12 @@ void DrawClipWaveform(TrackPanelDrawingContext &context,
if (h == 0.0 && tOffset < 0.0) {
TrackArt::DrawNegativeOffsetTrackArrows( context, rect );
}
params.DrawClipEdges( dc, rect );
{
//increase virtual view size by px to hide edges that should not be visible
auto clipRect = ClipParameters::GetClipRect(*clip, zoomInfo, rect.Inflate(1, 0), 1);
if (!clipRect.IsEmpty())
TrackArt::DrawClipEdges(dc, clipRect, selected);
}
}
void DrawTimeSlider( TrackPanelDrawingContext &context,
@ -967,7 +973,8 @@ void DrawTimeSlider( TrackPanelDrawingContext &context,
//#include "tracks/ui/TimeShiftHandle.h"
void WaveformView::DoDraw(TrackPanelDrawingContext &context,
const WaveTrack *track,
const wxRect & rect,
const WaveClip* selectedClip,
const wxRect& rect,
bool muted)
{
auto &dc = context.dc;
@ -988,10 +995,11 @@ void WaveformView::DoDraw(TrackPanelDrawingContext &context,
TrackArt::DrawBackgroundWithSelection(
context, rect, track, blankSelectedBrush, blankBrush );
for (const auto &clip: track->GetClips())
for (const auto& clip : track->GetClips())
{
DrawClipWaveform(context, track, clip.get(), rect,
dB, muted);
dB, muted, clip.get() == selectedClip);
}
DrawBoldBoundaries( context, track, rect );
const auto drawSliders = artist->drawSliders;
@ -1028,13 +1036,17 @@ void WaveformView::Draw(
dc.GetGraphicsContext()->SetAntialiasMode(wxANTIALIAS_NONE);
#endif
DoDraw(context, wt.get(), rect, muted);
auto waveTrackView = GetWaveTrackView().lock();
wxASSERT(waveTrackView.use_count());
auto selectedClip = waveTrackView->GetSelectedClip().lock();
DoDraw(context, wt.get(), selectedClip.get(), rect, muted);
#if defined(__WXMAC__)
dc.GetGraphicsContext()->SetAntialiasMode(aamode);
#endif
}
CommonTrackView::Draw( context, rect, iPass );
WaveTrackSubView::Draw( context, rect, iPass );
}
static const WaveTrackSubViews::RegisteredFactory key{

View File

@ -38,6 +38,7 @@ private:
const wxRect &rect, unsigned iPass ) override;
static void DoDraw(TrackPanelDrawingContext &context,
const WaveTrack *track,
const WaveClip* selectedClip,
const wxRect & rect,
bool muted);

View File

@ -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<AffordanceHandle>& holder, const std::shared_ptr<Track>& pTrack)
{
auto result = std::make_shared<AffordanceHandle>(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>& 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<Track>(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;
}

View File

@ -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<AffordanceHandle>& holder, const std::shared_ptr<Track>& pTrack);
HitTestPreview Preview(const TrackPanelMouseState& mouseState, AudacityProject* pProject) override;
AffordanceHandle(const std::shared_ptr<Track>& track);
Result Click(const TrackPanelMouseEvent& evt, AudacityProject* pProject) override;
Result Release(const TrackPanelMouseEvent& event, AudacityProject* pProject, wxWindow* pParent) override;
};

View File

@ -100,24 +100,7 @@ void PlayIndicatorOverlayBase::Draw(OverlayPanel &panel, wxDC &dc)
if(auto tp = dynamic_cast<TrackPanel*>(&panel)) {
wxASSERT(mIsMaster);
// Draw indicator in all visible tracks
tp->VisitCells( [&]( const wxRect &rect, TrackPanelCell &cell ) {
const auto pTrackView = dynamic_cast<TrackView*>(&cell);
if (pTrackView) pTrackView->FindTrack()->TypeSwitch(
[](LabelTrack *) {
// Don't draw the indicator in label tracks
},
[&](Track *) {
// Draw the NEW indicator in its NEW location
// AColor::Line includes both endpoints so use GetBottom()
AColor::Line(dc,
mLastIndicatorX,
rect.GetTop(),
mLastIndicatorX,
rect.GetBottom());
}
);
} );
AColor::Line(dc, mLastIndicatorX, tp->GetRect().GetTop(), mLastIndicatorX, tp->GetRect().GetBottom());
}
else if(auto ruler = dynamic_cast<AdornedRulerPanel*>(&panel)) {
wxASSERT(!mIsMaster);

View File

@ -34,6 +34,21 @@ TimeShiftHandle::TimeShiftHandle
mClipMoveState.mCapturedTrack = pTrack;
}
std::shared_ptr<Track> 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);
}

View File

@ -235,7 +235,9 @@ struct AUDACITY_DLL_API ClipMoveState {
std::shared_ptr<Track> mCapturedTrack;
bool initialized{ false };
bool movingSelection {};
bool wasMoved{ false };
double hSlideAmount {};
ShifterMap shifters;
wxInt64 snapLeft { -1 }, snapRight { -1 };
@ -244,6 +246,8 @@ struct AUDACITY_DLL_API ClipMoveState {
void clear()
{
initialized = false;
wasMoved = false;
movingSelection = false;
hSlideAmount = 0;
shifters.clear();
@ -252,7 +256,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
@ -265,7 +269,6 @@ public:
TimeShiftHandle &operator=(TimeShiftHandle&&) = default;
bool IsGripHit() const { return mGripHit; }
std::shared_ptr<Track> 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
@ -305,6 +308,12 @@ public:
bool StopsOnKeystroke() override { return true; }
bool Clicked() const;
protected:
std::shared_ptr<Track> GetTrack() const;
//There were attempt to move clip/track horizontally, or to move it vertically
bool WasMoved() const;
private:
// TrackPanelDrawable implementation
void Draw(

View File

@ -141,6 +141,13 @@ std::shared_ptr<const TrackVRulerControls> TrackView::GetVRulerControls() const
return const_cast< TrackView* >( this )->GetVRulerControls();
}
std::shared_ptr<CommonTrackCell> TrackView::GetAffordanceControls()
{
if (!mpAffordanceCellControl)
mpAffordanceCellControl = DoGetAffordanceControls();
return mpAffordanceCellControl;
}
void TrackView::DoSetY(int y)
{
mY = y;
@ -165,6 +172,11 @@ void TrackView::DoSetHeight(int h)
mHeight = h;
}
std::shared_ptr<CommonTrackCell> TrackView::DoGetAffordanceControls()
{
return {};
}
namespace {
// Attach an object to each project. It receives track list events and updates

View File

@ -62,6 +62,9 @@ public:
std::shared_ptr<TrackVRulerControls> GetVRulerControls();
std::shared_ptr<const TrackVRulerControls> GetVRulerControls() const;
// by default returns nullptr, meaning that track has no drag controls area
std::shared_ptr<CommonTrackCell> GetAffordanceControls();
void WriteXMLAttributes( XMLWriter & ) const override;
bool HandleXMLAttribute( const wxChar *attr, const wxChar *value ) override;
@ -88,8 +91,11 @@ protected:
// Private factory to make appropriate object; class TrackView handles
// memory management thereafter
virtual std::shared_ptr<TrackVRulerControls> DoGetVRulerControls() = 0;
// May return nullptr (which is default) if track does not need affordance area
virtual std::shared_ptr<CommonTrackCell> DoGetAffordanceControls();
std::shared_ptr<TrackVRulerControls> mpVRulerControls;
std::shared_ptr<CommonTrackCell> mpAffordanceCellControl;
private:
bool mMinimized{ false };