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