@@ -381,39 +381,41 @@ void BlockClockDial::paintBlocks(QPainter * painter)
381381
382382 QPen pen (m_confirmation_colors.constLast ());
383383 pen.setWidthF (m_pen_width);
384- pen.setCapStyle (Qt::FlatCap );
384+ pen.setCapStyle (Qt::RoundCap );
385385 const QRectF bounds = getBoundsForPen (pen);
386386 painter->setPen (pen);
387387
388- // Boundaries are explicit: the period starts at zero, each block closes
389- // one confirmation segment, and the current time closes the newest one.
390- QList<qreal> boundaries;
391- boundaries.reserve (m_block_time_fractions.size () + 2 );
392- boundaries.push_back (0.0 );
393- boundaries.append (m_block_time_fractions);
394- boundaries.push_back (m_current_time_fraction);
395-
396- const qreal gap{degreesPerPixel ()};
388+ // Rounded caps extend half a stroke beyond each path endpoint. Separate
389+ // their center lines by one stroke plus a small visible gap so neighboring
390+ // confirmation segments retain the pill-shaped spacing from the design.
391+ const QList<qreal> boundaries{coalescedBlockBoundaries (bounds)};
392+ const qreal boundary_gap_degrees{
393+ degreesForArcPixels (m_pen_width + blockSegmentGapPixels (), bounds)};
394+ const qreal half_boundary_gap{boundary_gap_degrees / 2.0 };
395+ const qreal animated_fraction{qBound<qreal>(0.0 , m_animating_max_angle / 360.0 , 1.0 )};
397396 const qsizetype segment_count{boundaries.size () - 1 };
398397 for (qsizetype segment{0 }; segment < segment_count; ++segment) {
398+ const qreal segment_start{boundaries.at (segment)};
399+ const qreal segment_end{qMin (boundaries.at (segment + 1 ), animated_fraction)};
400+ const qreal available_degrees{(segment_end - segment_start) * 360.0 };
401+ if (available_degrees <= boundary_gap_degrees) {
402+ if (boundaries.at (segment + 1 ) > animated_fraction) break ;
403+ continue ;
404+ }
405+
399406 const qsizetype color_index{qMin<qsizetype>(5 , segment_count - segment - 1 )};
400407 pen.setColor (m_confirmation_colors.at (color_index));
401408 painter->setPen (pen);
402409
403- const qreal startAngle {90 - 360 * boundaries. at (segment) };
404- qreal nextAngle {90 - 360 * boundaries. at (segment + 1 ) };
410+ const qreal start_angle {90 - 360 * segment_start - half_boundary_gap };
411+ const qreal end_angle {90 - 360 * segment_end + half_boundary_gap };
405412
406413 QPainterPath path;
407- path.arcMoveTo (bounds, startAngle);
408-
409- if (-1 * nextAngle + 90 > m_animating_max_angle) {
410- nextAngle = -1 * m_animating_max_angle + 90 ;
411- segment = segment_count;
412- }
413-
414- const qreal spanAngle = -1 * (startAngle - nextAngle) + gap;
415- path.arcTo (bounds, startAngle, spanAngle);
414+ path.arcMoveTo (bounds, start_angle);
415+ path.arcTo (bounds, start_angle, end_angle - start_angle);
416416 painter->drawPath (path);
417+
418+ if (boundaries.at (segment + 1 ) > animated_fraction) break ;
417419 }
418420}
419421
@@ -512,6 +514,50 @@ double BlockClockDial::degreesPerPixel()
512514 return 360 / circumference;
513515}
514516
517+ qreal BlockClockDial::degreesForArcPixels (qreal pixels, const QRectF& bounds) const
518+ {
519+ const qreal radius{qMin (bounds.width (), bounds.height ()) / 2.0 };
520+ if (radius <= 0.0 ) return 360.0 ;
521+ return qRadiansToDegrees (pixels / radius);
522+ }
523+
524+ qreal BlockClockDial::blockSegmentGapPixels () const
525+ {
526+ // The Figma dial uses a gap around half the stroke width, with one device
527+ // pixel as the lower bound for small miniatures.
528+ return qMax<qreal>(1.0 , m_pen_width / 2.0 );
529+ }
530+
531+ QList<qreal> BlockClockDial::coalescedBlockBoundaries (const QRectF& bounds) const
532+ {
533+ const qreal current_fraction{qBound<qreal>(0.0 , m_current_time_fraction, 1.0 )};
534+ if (current_fraction <= 0.0 ) return {0.0 };
535+
536+ // A rounded segment needs room for both half-caps, the visual gap, and at
537+ // least one pixel of center line. Keep the newest boundary in each cluster
538+ // so simplification affects presentation only and favors recent blocks.
539+ const qreal minimum_interval_fraction{
540+ degreesForArcPixels (m_pen_width + blockSegmentGapPixels () + 1.0 , bounds) / 360.0 };
541+ QList<qreal> descending_boundaries{current_fraction};
542+ qreal next_boundary{current_fraction};
543+ for (auto it{m_block_time_fractions.crbegin ()}; it != m_block_time_fractions.crend (); ++it) {
544+ const qreal boundary{*it};
545+ if (boundary <= 0.0 || boundary >= current_fraction) continue ;
546+ if (next_boundary - boundary < minimum_interval_fraction) continue ;
547+
548+ descending_boundaries.push_back (boundary);
549+ next_boundary = boundary;
550+ }
551+
552+ // Do not leave an undersized first segment against the period boundary.
553+ if (descending_boundaries.size () > 1 && descending_boundaries.constLast () < minimum_interval_fraction) {
554+ descending_boundaries.removeLast ();
555+ }
556+ descending_boundaries.push_back (0.0 );
557+ std::reverse (descending_boundaries.begin (), descending_boundaries.end ());
558+ return descending_boundaries;
559+ }
560+
515561void BlockClockDial::paintTimeTicks (QPainter * painter)
516562{
517563 QPen pen (m_time_tick_color);
0 commit comments