Audacity 3.2.0
TrackArt.cpp
Go to the documentation of this file.
1/**********************************************************************
2
3 Audacity: A Digital Audio Editor
4
5 @file TrackArt.cpp
6
7 Paul Licameli split from TrackArtist.cpp
8
9**********************************************************************/
10
11#include "TrackArt.h"
12
13#include "AColor.h"
14#include "AllThemeResources.h"
15#include "SelectedRegion.h"
16#include "SyncLock.h"
17#include "Theme.h"
19#include "Track.h"
20#include "TrackArtist.h"
22#include "ZoomInfo.h"
23#include "TimeDisplayMode.h"
24#include "ProjectTimeRuler.h"
25
27
28#include "widgets/BeatsFormat.h"
29
30#include <wx/app.h>
31#include <wx/dc.h>
32
33#include <cassert>
34#include <cstdint>
35#include <cmath>
36#include <utility>
37
38static constexpr int ClipSelectionStrokeSize{ 1 };//px
39
40namespace
41{
42 //Helper function that takes affordance rectangle as
43 //argument and returns rectangle to be used for title
44 //drawings
45 wxRect GetAffordanceTitleRect(const wxRect& rect)
46 {
47 constexpr int FrameThickness{ 1 };
48 return wxRect(
49 rect.GetLeft() + TrackArt::ClipFrameRadius,
50 rect.GetTop() + ClipSelectionStrokeSize + FrameThickness,
51 rect.GetWidth() - TrackArt::ClipFrameRadius * 2,
52 rect.GetHeight() - ClipSelectionStrokeSize - FrameThickness);
53 }
54
55}
56
59int GetWaveYPos(float value, float min, float max,
60 int height, bool dB, bool outer,
61 float dBr, bool clip)
62{
63 if (dB) {
64 if (height == 0) {
65 return 0;
66 }
67
68 float sign = (value >= 0 ? 1 : -1);
69
70 if (value != 0.) {
71 float db = LINEAR_TO_DB(fabs(value));
72 value = (db + dBr) / dBr;
73 if (!outer) {
74 value -= 0.5;
75 }
76 if (value < 0.0) {
77 value = 0.0;
78 }
79 value *= sign;
80 }
81 }
82 else {
83 if (!outer) {
84 if (value >= 0.0) {
85 value -= 0.5;
86 }
87 else {
88 value += 0.5;
89 }
90 }
91 }
92
93 if (clip) {
94 if (value < min) {
95 value = min;
96 }
97 if (value > max) {
98 value = max;
99 }
100 }
101
102 value = (max - value) / (max - min);
103 return (int) (value * (height - 1) + 0.5);
104}
105
106float FromDB(float value, double dBRange)
107{
108 if (value == 0)
109 return 0;
110
111 double sign = (value >= 0 ? 1 : -1);
112 return DB_TO_LINEAR((fabs(value) * dBRange) - dBRange) * sign;
113}
114
115float ValueOfPixel(int yy, int height, bool offset,
116 bool dB, double dBRange, float zoomMin, float zoomMax)
117{
118 wxASSERT(height > 0);
119 // Map 0 to max and height - 1 (not height) to min
120 float v =
121 height == 1 ? (zoomMin + zoomMax) / 2 :
122 zoomMax - (yy / (float)(height - 1)) * (zoomMax - zoomMin);
123 if (offset) {
124 if (v > 0.0)
125 v += .5;
126 else
127 v -= .5;
128 }
129
130 if (dB)
131 v = FromDB(v, dBRange);
132
133 return v;
134}
135
137 TrackPanelDrawingContext &context, const wxRect &rect )
138{
139 auto &dc = context.dc;
140
141 // Draws two black arrows on the left side of the track to
142 // indicate the user that the track has been time-shifted
143 // to the left beyond t=0.0.
144
145 dc.SetPen(*wxBLACK_PEN);
146 AColor::Line(dc,
147 rect.x + 2, rect.y + 6,
148 rect.x + 8, rect.y + 6);
149 AColor::Line(dc,
150 rect.x + 2, rect.y + 6,
151 rect.x + 6, rect.y + 2);
152 AColor::Line(dc,
153 rect.x + 2, rect.y + 6,
154 rect.x + 6, rect.y + 10);
155 AColor::Line(dc,
156 rect.x + 2, rect.y + rect.height - 8,
157 rect.x + 8, rect.y + rect.height - 8);
158 AColor::Line(dc,
159 rect.x + 2, rect.y + rect.height - 8,
160 rect.x + 6, rect.y + rect.height - 4);
161 AColor::Line(dc,
162 rect.x + 2, rect.y + rect.height - 8,
163 rect.x + 6, rect.y + rect.height - 12);
164}
165
166wxString TrackArt::TruncateText(wxDC& dc, const wxString& text, const int maxWidth)
167{
168 static const wxString ellipsis = "\u2026";
169
170 if (dc.GetTextExtent(text).GetWidth() <= maxWidth)
171 return text;
172
173 auto left = 0;
174 //no need to check text + '...'
175 auto right = static_cast<int>(text.Length() - 2);
176
177 while (left <= right)
178 {
179 auto middle = (left + right) / 2;
180 auto str = text.SubString(0, middle).Trim() + ellipsis;
181 auto strWidth = dc.GetTextExtent(str).GetWidth();
182 if (strWidth < maxWidth)
183 //if left == right (== middle), then exit loop
184 //with right equals to the last known index for which
185 //strWidth < maxWidth
186 left = middle + 1;
187 else if (strWidth > maxWidth)
188 //if right == left (== middle), then exit loop with
189 //right equals to (left - 1), which is the last known
190 //index for which (strWidth < maxWidth) or -1
191 right = middle - 1;
192 else
193 return str;
194 }
195 if (right >= 0)
196 return text.SubString(0, right).Trim() + ellipsis;
197
198 return wxEmptyString;
199}
200
201wxRect TrackArt::DrawClipAffordance(wxDC& dc, const wxRect& rect, bool highlight, bool selected)
202{
203 //To make sure that roundings do not overlap each other
204 auto clipFrameRadius = std::min(ClipFrameRadius, rect.width / 2);
205
206 wxRect clipRect;
207 bool hasClipRect = dc.GetClippingBox(clipRect);
208 //Fix #1689: visual glitches appear on attempt to draw a rectangle
209 //larger than 0x7FFFFFF pixels wide (value was discovered
210 //by manual testing, and maybe depends on OS being used), but
211 //it's very unlikely that such huge rectangle will be ever fully visible
212 //on the screen, so we can safely reduce its size to be slightly larger than
213 //clipping rectangle, and avoid that problem
214 auto drawingRect = rect;
215 if (hasClipRect)
216 {
217 //to make sure that rounding happends outside the clipping rectangle
218 drawingRect.SetLeft(std::max(rect.GetLeft(), clipRect.GetLeft() - clipFrameRadius - 1));
219 drawingRect.SetRight(std::min(rect.GetRight(), clipRect.GetRight() + clipFrameRadius + 1));
220 }
221
222 if (selected)
223 {
224 wxRect strokeRect{
225 drawingRect.x - ClipSelectionStrokeSize,
226 drawingRect.y,
227 drawingRect.width + ClipSelectionStrokeSize * 2,
228 drawingRect.height + clipFrameRadius };
229 dc.SetBrush(*wxTRANSPARENT_BRUSH);
230 AColor::UseThemeColour(&dc, clrClipAffordanceStroke, clrClipAffordanceStroke);
231 dc.DrawRoundedRectangle(strokeRect, clipFrameRadius);
232 }
233
234 AColor::UseThemeColour(&dc, highlight ? clrClipAffordanceActiveBrush : clrClipAffordanceInactiveBrush, clrClipAffordanceOutlinePen);
235 dc.DrawRoundedRectangle(
236 wxRect(
237 drawingRect.x,
238 drawingRect.y + ClipSelectionStrokeSize,
239 drawingRect.width,
240 drawingRect.height + clipFrameRadius
241 ), clipFrameRadius
242 );
243
244 auto titleRect = hasClipRect ?
245 //avoid drawing text outside the clipping rectangle if possible
246 GetAffordanceTitleRect(rect.Intersect(clipRect)) :
248
249 return titleRect;
250}
251
252namespace{
253wxString GetPlaybackSpeedFullText(double clipStretchRatio)
254{
255 // clang-format off
256 // We reckon that most of the time, a rounded percentage value is sufficient.
257 // There are two exceptions:
258 // - The clip is only slightly stretched such that the rounded value is 100.
259 // There should be an indicator if and only if the clip is stretched, this
260 // must be reliable. Yet showing "100%" would be confusing. Hence in that
261 // case we constrain the values to [99.1, ..., 99.9, 100.1,
262 // ..., 100.9], i.e., omitting 100.0.
263 // - The clip is stretched so much that the playback speed is less than 1%.
264 // Make sure in that case that we never show 0%, but always at least 0.1%.
265 // clang-format on
266
267 const auto playbackSpeed = 100 / clipStretchRatio;
268 wxString fullText;
269
270 // We compare with .95 rather than 1, since playback speeds within [100.95,
271 // 101) get rounded to 101.0 and (99, 99.05] to 99.0 by `wxString::Format`.
272 // Let these be processed by the integer-display branch of this if statement
273 // instead.
274 if (fabs(playbackSpeed - 100.) < .95)
275 // Never show 100.0%
276 fullText = wxString::Format(
277 "%.1f%% speed", playbackSpeed > 100 ?
278 std::max(playbackSpeed, 100.1) :
279 std::min(playbackSpeed, 99.9));
280 else if (playbackSpeed < 1)
281 // Never show 0.0%
282 fullText =
283 wxString::Format("%.1f%% speed", std::max(playbackSpeed, 0.1));
284 else {
285 const auto roundedPlaybackSpeed =
286 static_cast<int>(std::round(playbackSpeed));
287 fullText = wxString::Format("%d%% speed", roundedPlaybackSpeed);
288 }
289 return fullText;
290}
291
292enum class HAlign
293{
294 left,
295 right
296};
297
298struct ClipTitle {
299 const wxString text;
301};
302
303std::optional<ClipTitle> DoDrawAudioTitle(
304 wxDC& dc, const wxRect& titleRect, const wxString& title)
305{
306 if(titleRect.IsEmpty())
307 return std::nullopt;
308 const auto hAlign = wxTheApp->GetLayoutDirection() == wxLayout_RightToLeft ?
309 HAlign::right :
310 HAlign::left;
311 if(title.empty())
312 return ClipTitle { "", hAlign };
313 auto truncatedTitle = TrackArt::TruncateText(dc, title, titleRect.GetWidth());
314 if (!truncatedTitle.empty())
315 {
316 dc.DrawLabel(
317 truncatedTitle, titleRect,
318 (hAlign == HAlign::left ? wxALIGN_LEFT : wxALIGN_RIGHT) |
319 wxALIGN_CENTER_VERTICAL);
320 return ClipTitle{ truncatedTitle, hAlign };
321 }
322 return std::nullopt;
323}
324
325}
326
328 wxDC& dc, const wxRect& titleRect, const wxString& title)
329{
330 return DoDrawAudioTitle(dc, titleRect, title).has_value();
331}
332
334 wxDC& dc, const wxRect& titleRect, const wxString& title,
335 double clipStretchRatio)
336{
337 const auto clipTitle = DoDrawAudioTitle(dc, titleRect, title);
338 if (!clipTitle.has_value())
339 return false;
340 if (!TimeAndPitchInterface::IsPassThroughMode(clipStretchRatio))
341 {
342 const auto fullText = GetPlaybackSpeedFullText(clipStretchRatio);
343 constexpr auto minSpaceBetweenTitleAndSpeed = 12; // pixels
344 const auto remainingWidth = std::max(
345 titleRect.GetWidth() - dc.GetTextExtent(clipTitle->text).GetWidth() -
346 minSpaceBetweenTitleAndSpeed,
347 0);
348 const auto truncatedText =
349 TrackArt::TruncateText(dc, fullText, remainingWidth);
350 if (truncatedText.find('%') != std::string::npos)
351 // Only show if there is room for the % sign, or else it can be hard to
352 // interpret.
353 dc.DrawLabel(
354 TrackArt::TruncateText(dc, fullText, remainingWidth), titleRect,
355 (clipTitle->alignment == HAlign::left ? wxALIGN_RIGHT :
356 wxALIGN_LEFT) |
357 wxALIGN_CENTER_VERTICAL);
358 }
359 return true;
360}
361
362void TrackArt::DrawClipEdges(wxDC& dc, const wxRect& clipRect, bool selected)
363{
364 dc.SetBrush(*wxTRANSPARENT_BRUSH);
365 {
366 AColor::UseThemeColour(&dc, -1, clrClipAffordanceOutlinePen);
367 AColor::Line(dc,
368 clipRect.GetLeft(), clipRect.GetTop(),
369 clipRect.GetLeft(), clipRect.GetBottom());
370 AColor::Line(dc,
371 clipRect.GetRight(), clipRect.GetTop(),
372 clipRect.GetRight(), clipRect.GetBottom());
373 }
374 if(selected)
375 {
376 if constexpr (ClipSelectionStrokeSize == 1)
377 {
378 AColor::UseThemeColour(&dc, -1, clrClipAffordanceStroke);
379 AColor::Line(dc,
380 clipRect.GetLeft() - ClipSelectionStrokeSize, clipRect.GetTop(),
381 clipRect.GetLeft() - ClipSelectionStrokeSize, clipRect.GetBottom());
382 AColor::Line(dc,
383 clipRect.GetRight() + ClipSelectionStrokeSize, clipRect.GetTop(),
384 clipRect.GetRight() + ClipSelectionStrokeSize, clipRect.GetBottom());
385 }
386 else if constexpr (ClipSelectionStrokeSize > 1)
387 {
388 AColor::UseThemeColour(&dc, clrClipAffordanceStroke, clrClipAffordanceStroke);
389 dc.DrawRectangle(wxRect(
390 clipRect.GetLeft() - ClipSelectionStrokeSize, clipRect.GetTop(),
391 ClipSelectionStrokeSize, clipRect.GetHeight()));
392 dc.DrawRectangle(wxRect(
393 clipRect.GetRight() + 1, clipRect.GetTop(),
394 ClipSelectionStrokeSize, clipRect.GetHeight()));
395 }
396 }
397}
398
399void TrackArt::DrawClipFolded(wxDC& dc, const wxRect& rect)
400{
401 AColor::UseThemeColour(&dc, clrClipAffordanceOutlinePen);
402 dc.DrawRectangle(rect);
403}
404
405// Draws the sync-lock bitmap, tiled; always draws stationary relative to the DC
406//
407// AWD: now that the tiles don't link together, we're drawing a tilted grid, at
408// two steps down for every one across. This creates a pattern that repeats in
409// 5-step by 5-step boxes. Because we're only drawing in 5/25 possible positions
410// we have a grid spacing somewhat smaller than the image dimensions. Thus we
411// achieve lower density than with a square grid and eliminate edge cases where
412// no tiles are displayed.
413//
414// The pattern draws in tiles at (0,0), (2,1), (4,2), (1,3), and (3,4) in each
415// 5x5 box.
416//
417// There may be a better way to do this, or a more appealing pattern.
419 TrackPanelDrawingContext &context, const wxRect &rect )
420{
421 const auto dc = &context.dc;
422
423 wxBitmap syncLockBitmap(theTheme.Image(bmpSyncLockSelTile));
424
425 // Grid spacing is a bit smaller than actual image size
426 int gridW = syncLockBitmap.GetWidth() - 6;
427 int gridH = syncLockBitmap.GetHeight() - 8;
428
429 // Horizontal position within the grid, modulo its period
430 int blockX = (rect.x / gridW) % 5;
431
432 // Amount to offset drawing of first column
433 int xOffset = rect.x % gridW;
434 if (xOffset < 0) xOffset += gridW;
435
436 // Check if we're missing an extra column to the left (this can happen
437 // because the tiles are bigger than the grid spacing)
438 bool extraCol = false;
439 if (syncLockBitmap.GetWidth() - gridW > xOffset) {
440 extraCol = true;
441 xOffset += gridW;
442 blockX = (blockX - 1) % 5;
443 }
444 // Make sure blockX is non-negative
445 if (blockX < 0) blockX += 5;
446
447 int xx = 0;
448 while (xx < rect.width) {
449 int width = syncLockBitmap.GetWidth() - xOffset;
450 if (xx + width > rect.width)
451 width = rect.width - xx;
452
453 //
454 // Draw each row in this column
455 //
456
457 // Vertical position in the grid, modulo its period
458 int blockY = (rect.y / gridH) % 5;
459
460 // Amount to offset drawing of first row
461 int yOffset = rect.y % gridH;
462 if (yOffset < 0) yOffset += gridH;
463
464 // Check if we're missing an extra row on top (this can happen because
465 // the tiles are bigger than the grid spacing)
466 bool extraRow = false;
467 if (syncLockBitmap.GetHeight() - gridH > yOffset) {
468 extraRow = true;
469 yOffset += gridH;
470 blockY = (blockY - 1) % 5;
471 }
472 // Make sure blockY is non-negative
473 if (blockY < 0) blockY += 5;
474
475 int yy = 0;
476 while (yy < rect.height)
477 {
478 int height = syncLockBitmap.GetHeight() - yOffset;
479 if (yy + height > rect.height)
480 height = rect.height - yy;
481
482 // AWD: draw blocks according to our pattern
483 if ((blockX == 0 && blockY == 0) || (blockX == 2 && blockY == 1) ||
484 (blockX == 4 && blockY == 2) || (blockX == 1 && blockY == 3) ||
485 (blockX == 3 && blockY == 4))
486 {
487
488 // Do we need to get a sub-bitmap?
489 if (width != syncLockBitmap.GetWidth() || height != syncLockBitmap.GetHeight()) {
490 wxBitmap subSyncLockBitmap =
491 syncLockBitmap.GetSubBitmap(wxRect(xOffset, yOffset, width, height));
492 dc->DrawBitmap(subSyncLockBitmap, rect.x + xx, rect.y + yy, true);
493 }
494 else {
495 dc->DrawBitmap(syncLockBitmap, rect.x + xx, rect.y + yy, true);
496 }
497 }
498
499 // Updates for next row
500 if (extraRow) {
501 // Second offset row, still at y = 0; no more extra rows
502 yOffset -= gridH;
503 extraRow = false;
504 }
505 else {
506 // Move on in y, no more offset rows
507 yy += gridH - yOffset;
508 yOffset = 0;
509 }
510 blockY = (blockY + 1) % 5;
511 }
512
513 // Updates for next column
514 if (extraCol) {
515 // Second offset column, still at x = 0; no more extra columns
516 xOffset -= gridW;
517 extraCol = false;
518 }
519 else {
520 // Move on in x, no more offset rows
521 xx += gridW - xOffset;
522 xOffset = 0;
523 }
524 blockX = (blockX + 1) % 5;
525 }
526}
527
528namespace
529{
530constexpr double minSubdivisionWidth = 12.0;
531
533{
535 const bool enabled;
536
538
541
542 // Note duration in seconds
543 const double noteDuration;
544 // Note width in pixels
545 const double noteWidth;
546 // How many "notes" should be colored with a single color?
547 const int64_t notesInBeat;
548
549
550BeatsGridlinePainter(const ZoomInfo& zoomInfo, const Track& track) noexcept
551 : zoomInfo { zoomInfo }
552 , enabled { TimeDisplayModePreference.ReadEnum() ==
554 , beatsRulerFormat { ProjectTimeRuler::Get(GetProject(track))
555 .GetBeatsFormat() }
556 , majorTick { beatsRulerFormat.GetSubdivision().major }
557 , minorTick { GetMinorTick() }
558 , noteDuration { minorTick.duration }
559 , noteWidth { zoomInfo.TimeRangeToPixelWidth(noteDuration) }
560 , notesInBeat { CalculateNotesInBeat() }
561 {
562 }
563
565 wxDC& dc, const wxRect& rect, const wxPen& beatSepearatorPen, const wxPen& barSeparatorPen) const
566{
567 dc.SetPen (beatSepearatorPen);
568
569 const auto majorTick = beatsRulerFormat.GetSubdivision().major;
570 const auto minorTick = GetMinorTick();
571
572 const auto [firstNote, lastNote] = GetBoundaries(
573 rect, rect, noteWidth);
574
575 for (auto noteIndex = firstNote; noteIndex < lastNote; ++noteIndex)
576 {
577 const auto position = GetPositionInRect (noteIndex, rect, noteDuration);
578
579 if (position < rect.GetLeft () || position >= rect.GetRight ())
580 continue;
581
582 dc.SetPen(IsFirstInMajorTick(noteIndex) ? barSeparatorPen : beatSepearatorPen);
583 dc.DrawLine (position, rect.GetTop (), position, rect.GetBottom () + 1);
584 }
585}
586
588 wxDC& dc, const wxRect& subRect, const wxRect& fullRect, const wxBrush& strongBeatBrush,
589 const wxBrush& weakBeatBrush) const
590{
591 if (!UseAlternatingColors ())
592 {
593 dc.SetBrush(strongBeatBrush);
594 dc.DrawRectangle(subRect);
595 return;
596 }
597
598 auto [firstIndex, lastIndex] =
599 GetBoundaries (subRect, fullRect, noteWidth);
600
601 // Make first index to be on notesInBeatBoundary
602 firstIndex = (firstIndex / notesInBeat) * notesInBeat;
603
604 const auto beatDuration = noteDuration;
605
606 const auto top = fullRect.GetTop ();
607 const auto height = fullRect.GetHeight ();
608
609 bool strongBeat = (firstIndex / notesInBeat) % 2 == 0;
610 for (auto index = firstIndex; index < lastIndex; index += notesInBeat, strongBeat = !strongBeat)
611 {
612 const auto left = std::max<int> (
613 GetPositionInRect(index, fullRect, beatDuration),
614 subRect.GetLeft());
615 const auto right = std::min<int> (
616 GetPositionInRect(index + notesInBeat, fullRect, beatDuration),
617 subRect.GetRight());
618
619 const auto& brush = strongBeat ? strongBeatBrush : weakBeatBrush;
620
621 dc.SetBrush (brush);
622 dc.DrawRectangle (left, top, right - left + 1, height);
623 }
624}
625
626private:
627 const AudacityProject& GetProject(const Track& track) const
628 {
629 // Track is expected to have owner
630 assert(track.GetOwner());
631 // TracList is expected to have owner
632 assert(track.GetOwner()->GetOwner());
633
634 return *track.GetOwner()->GetOwner();
635 }
636
637 int64_t CalculateNotesInBeat() const
638 {
639 if (UseAlternatingColors())
640 return minorTick.lower / 4;
641
642 return 1;
643 }
644
645 bool IsFirstInMajorTick(int64_t noteIndex) const
646 {
647 const auto notesInMajorTick =
648 minorTick.lower * majorTick.upper / majorTick.lower / minorTick.upper;
649
650 if (notesInMajorTick == 0)
651 return false;
652
653 return noteIndex % notesInMajorTick == 0;
654 }
655
657 {
658 return minorTick.lower >= 4 && minorTick.upper == 1;
659 }
660
661 double GetPositionInRect(int64_t index, const wxRect& rect, double duration) const
662 {
663 return zoomInfo.TimeToPosition(index * duration) + rect.x + 1;
664 }
665
666 std::pair<int64_t, int64_t> GetBoundaries(const wxRect& subRect, const wxRect& fullRect, double width) const
667 {
668 const auto offset = subRect.x - fullRect.x;
669
670 return { std::floor(zoomInfo.GetAbsoluteOffset(offset) / width),
671 std::ceil(
672 zoomInfo.GetAbsoluteOffset(offset + subRect.GetWidth()) /
673 width) };
674 }
675
677 {
678 const auto& subdivision = beatsRulerFormat.GetSubdivision();
679
680 auto tick = subdivision.minorMinor;
681
682 auto minorMinorLength =
683 zoomInfo.TimeRangeToPixelWidth(subdivision.minorMinor.duration);
684
685 const auto nextSubdivision = subdivision.minor.duration == 0.0 ?
686 subdivision.major :
687 subdivision.minor;
688
689 while (minorMinorLength <= minSubdivisionWidth)
690 {
691 tick.lower /= 2;
692 tick.duration *= 2.0;
693 minorMinorLength *= 2.0;
694
695 if (nextSubdivision.lower >= tick.lower)
696 return nextSubdivision;
697 }
698
699 return tick;
700 }
701};
702}
703
705 TrackPanelDrawingContext &context, const wxRect &rect,
706 const Track *track, const wxBrush &selBrush, const wxBrush &unselBrush,
707 bool useSelection)
708{
709 const auto dc = &context.dc;
710 const auto artist = TrackArtist::Get( context );
711 const auto &selectedRegion = *artist->pSelectedRegion;
712 const auto& zoomInfo = *artist->pZoomInfo;
713
714
715 //MM: Draw background. We should optimize that a bit more.
716 const double sel0 = useSelection ? selectedRegion.t0() : 0.0;
717 const double sel1 = useSelection ? selectedRegion.t1() : 0.0;
718
719 BeatsGridlinePainter gridlinePainter(zoomInfo, *track);
720
721 dc->SetPen(*wxTRANSPARENT_PEN);
722
723 auto drawBgRect = [dc, &gridlinePainter, artist, &rect](
724 const wxBrush& regularBrush,
725 const wxBrush& beatStrongBrush,
726 const wxBrush& beatWeakBrush, const wxRect& subRect)
727 {
728 if (!gridlinePainter.enabled)
729 {
730 // Track not selected; just draw background
731 dc->SetBrush(regularBrush);
732 dc->DrawRectangle(subRect);
733 }
734 else
735 {
736 gridlinePainter.DrawBackground(
737 *dc, subRect, rect, beatStrongBrush, beatWeakBrush);
738 }
739 };
740
742 {
743 // Rectangles before, within, after the selection
744 wxRect before = rect;
745 wxRect within = rect;
746 wxRect after = rect;
747
748 before.width = (int)(zoomInfo.TimeToPosition(sel0) );
749 if (before.GetRight() > rect.GetRight()) {
750 before.width = rect.width;
751 }
752
753 if (before.width > 0) {
754 drawBgRect(unselBrush, artist->beatStrongBrush, artist->beatWeakBrush, before);
755
756 within.x = 1 + before.GetRight();
757 }
758 within.width = rect.x + (int)(zoomInfo.TimeToPosition(sel1) ) - within.x -1;
759
760 if (within.GetRight() > rect.GetRight()) {
761 within.width = 1 + rect.GetRight() - within.x;
762 }
763
764 // Bug 2389 - Selection can disappear
765 // This handles case where no waveform is visible.
766 if (within.width < 1)
767 within.width = 1;
768
769 if (within.width > 0) {
770 if (track->GetSelected()) {
771 drawBgRect(selBrush, artist->beatStrongSelBrush, artist->beatWeakSelBrush, within);
772 }
773 else {
774 // Per condition above, track must be sync-lock selected
775 drawBgRect(
776 unselBrush, artist->beatStrongBrush, artist->beatWeakBrush, within);
777 DrawSyncLockTiles( context, within );
778 }
779
780 after.x = 1 + within.GetRight();
781 }
782 else {
783 // `within` not drawn; start where it would have gone
784 after.x = within.x;
785 }
786
787 after.width = 1 + rect.GetRight() - after.x;
788 if (after.width > 0)
789 drawBgRect(
790 unselBrush, artist->beatStrongBrush, artist->beatWeakBrush, after);
791 }
792 else
793 {
794 drawBgRect(
795 unselBrush, artist->beatStrongBrush, artist->beatWeakBrush, rect);
796 }
797
798 if (gridlinePainter.enabled)
799 gridlinePainter.DrawSeparators(*dc, rect, artist->beatSepearatorPen, artist->barSepearatorPen);
800}
801
803 const wxRect& rect, const Track* track)
804{
805 const auto dc = &context.dc;
806 const auto artist = TrackArtist::Get(context);
807 const auto& selectedRegion = *artist->pSelectedRegion;
808
809 if (selectedRegion.isPoint())
810 {
811 const auto& zoomInfo = *artist->pZoomInfo;
812 auto x = static_cast<int>(zoomInfo.TimeToPosition(selectedRegion.t0(), rect.x));
813 if (x >= rect.GetLeft() && x <= rect.GetRight())
814 {
816 AColor::Line(*dc, x, rect.GetTop(), x, rect.GetBottom());
817 }
818 }
819}
static const wxPoint2DDouble outer[]
Definition: ASlider.cpp:397
int min(int a, int b)
#define str(a)
#define LINEAR_TO_DB(x)
Definition: MemoryX.h:562
#define DB_TO_LINEAR(x)
Definition: MemoryX.h:561
static const auto title
THEME_API Theme theTheme
Definition: Theme.cpp:82
TimeDisplayModeSetting TimeDisplayModePreference
declares abstract base class Track, TrackList, and iterators over TrackList
int GetWaveYPos(float value, float min, float max, int height, bool dB, bool outer, float dBr, bool clip)
Definition: TrackArt.cpp:59
float FromDB(float value, double dBRange)
Definition: TrackArt.cpp:106
float ValueOfPixel(int yy, int height, bool offset, bool dB, double dBRange, float zoomMin, float zoomMax)
Definition: TrackArt.cpp:115
static constexpr int ClipSelectionStrokeSize
Definition: TrackArt.cpp:38
bool within(A a, B b, DIST d)
Definition: TrackPanel.cpp:168
static void Line(wxDC &dc, wxCoord x1, wxCoord y1, wxCoord x2, wxCoord y2)
Definition: AColor.cpp:187
static void CursorColor(wxDC *dc)
Definition: AColor.cpp:463
static void UseThemeColour(wxDC *dc, int iBrush, int iPen=-1, int alpha=255)
Definition: AColor.cpp:372
The top-level handle to an Audacity project. It serves as a source of events that other objects can b...
Definition: Project.h:90
const Ticks & GetSubdivision() const
Enum ReadEnum() const
Definition: Prefs.h:532
static ProjectTimeRuler & Get(AudacityProject &project)
BeatsFormat & GetBeatsFormat()
static bool IsSelectedOrSyncLockSelected(const Track *pTrack)
Definition: SyncLock.cpp:112
wxImage & Image(int iIndex)
static bool IsPassThroughMode(double stretchRatio)
static TrackArtist * Get(TrackPanelDrawingContext &)
Definition: TrackArtist.cpp:69
Abstract base class for an object holding data associated with points on a time axis.
Definition: Track.h:123
bool GetSelected() const
Selectedness is always the same for all channels of a group.
Definition: Track.cpp:70
std::shared_ptr< TrackList > GetOwner() const
Definition: Track.h:255
double GetAbsoluteOffset(double offset) const
Definition: ZoomInfo.cpp:78
double TimeRangeToPixelWidth(double timeRange) const
Definition: ZoomInfo.cpp:61
int64 TimeToPosition(double time, int64 origin=0, bool ignoreFisheye=false) const
STM: Converts a project time to screen x position.
Definition: ZoomInfo.cpp:45
AUDACITY_DLL_API void DrawSyncLockTiles(TrackPanelDrawingContext &context, const wxRect &rect)
Definition: TrackArt.cpp:418
AUDACITY_DLL_API wxString TruncateText(wxDC &dc, const wxString &text, const int maxWidth)
Definition: TrackArt.cpp:166
AUDACITY_DLL_API void DrawClipFolded(wxDC &dc, const wxRect &rect)
Definition: TrackArt.cpp:399
AUDACITY_DLL_API void DrawClipEdges(wxDC &dc, const wxRect &clipRect, bool selected=false)
Definition: TrackArt.cpp:362
AUDACITY_DLL_API wxRect DrawClipAffordance(wxDC &dc, const wxRect &affordanceRect, bool highlight=false, bool selected=false)
Definition: TrackArt.cpp:201
AUDACITY_DLL_API void DrawBackgroundWithSelection(TrackPanelDrawingContext &context, const wxRect &rect, const Track *track, const wxBrush &selBrush, const wxBrush &unselBrush, bool useSelection=true)
Definition: TrackArt.cpp:704
AUDACITY_DLL_API void DrawCursor(TrackPanelDrawingContext &context, const wxRect &rect, const Track *track)
Definition: TrackArt.cpp:802
AUDACITY_DLL_API void DrawNegativeOffsetTrackArrows(TrackPanelDrawingContext &context, const wxRect &rect)
Definition: TrackArt.cpp:136
AUDACITY_DLL_API bool DrawAudioClipTitle(wxDC &dc, const wxRect &titleRect, const wxString &title, double clipStretchRatio)
Definition: TrackArt.cpp:333
static constexpr int ClipFrameRadius
Definition: TrackArt.h:22
AUDACITY_DLL_API bool DrawClipTitle(wxDC &dc, const wxRect &titleRect, const wxString &title)
Definition: TrackArt.cpp:327
constexpr size_t npos(-1)
wxString GetPlaybackSpeedFullText(double clipStretchRatio)
Definition: TrackArt.cpp:253
constexpr double minSubdivisionWidth
Definition: TrackArt.cpp:530
std::optional< ClipTitle > DoDrawAudioTitle(wxDC &dc, const wxRect &titleRect, const wxString &title)
Definition: TrackArt.cpp:303
wxRect GetAffordanceTitleRect(const wxRect &rect)
Definition: TrackArt.cpp:45
fastfloat_really_inline void round(adjusted_mantissa &am, callback cb) noexcept
Definition: fast_float.h:2512
void DrawBackground(wxDC &dc, const wxRect &subRect, const wxRect &fullRect, const wxBrush &strongBeatBrush, const wxBrush &weakBeatBrush) const
Definition: TrackArt.cpp:587
std::pair< int64_t, int64_t > GetBoundaries(const wxRect &subRect, const wxRect &fullRect, double width) const
Definition: TrackArt.cpp:666
double GetPositionInRect(int64_t index, const wxRect &rect, double duration) const
Definition: TrackArt.cpp:661
void DrawSeparators(wxDC &dc, const wxRect &rect, const wxPen &beatSepearatorPen, const wxPen &barSeparatorPen) const
Definition: TrackArt.cpp:564
BeatsGridlinePainter(const ZoomInfo &zoomInfo, const Track &track) noexcept
Definition: TrackArt.cpp:550
const AudacityProject & GetProject(const Track &track) const
Definition: TrackArt.cpp:627