wiRedPanda
Logic Circuit Simulator
Loading...
Searching...
No Matches
LabeledSlider.cpp
Go to the documentation of this file.
1// Copyright 2015 - 2026, GIBIS-UNIFESP and the wiRedPanda contributors
2// SPDX-License-Identifier: GPL-3.0-or-later
3
5
6#include <algorithm>
7#include <limits>
8#include <tuple>
9
10#include <QPainter>
11#include <QSlider>
12#include <QStyle>
13
15 : QSlider(parent)
16{
17 // No contentsMargins() call here — see sizeHint() for why that approach doesn't
18 // work (it doesn't grow the size the owning layout actually allocates).
19}
20
22{
23 QSize hint = QSlider::sizeHint();
24 if (tickPosition() != QSlider::NoTicks) {
25 hint.setHeight(hint.height() + fontMetrics().height() + 4);
26 }
27 return hint;
28}
29
31{
32 return sizeHint();
33}
34
35// Converts a slider tick \a value (eighths of a clock period) to a compact fraction string.
36// Uses human-readable vulgar fractions for the canonical eighths; falls back to a 3-decimal
37// float for any unexpected value outside the designed range.
38static QString periodFractionLabel(const int value)
39{
40 const float periodFraction = static_cast<float>(value) / 8.0f;
41
42 if (periodFraction == 0.0f) { return "0"; }
43 if (periodFraction == 0.125f) { return "1/8"; }
44 if (periodFraction == 0.25f) { return "1/4"; }
45 if (periodFraction == 0.375f) { return "3/8"; }
46 if (periodFraction == 0.5f) { return "1/2"; }
47 if (periodFraction == -0.125f) { return "-1/8"; }
48 if (periodFraction == -0.25f) { return "-1/4"; }
49 if (periodFraction == -0.375f) { return "-3/8"; }
50 if (periodFraction == -0.5f) { return "-1/2"; }
51 return QString::number(static_cast<double>(periodFraction), 'f', 3);
52}
53
54void LabeledSlider::paintEvent(QPaintEvent *event)
55{
56 // Call parent to draw the slider
57 QSlider::paintEvent(event);
58
59 // Draw fraction labels under each tick mark. Only runs when tick marks are
60 // enabled so ordinary QSlider users aren't affected.
61 if (tickPosition() != QSlider::NoTicks) {
62 QPainter painter(this);
63 painter.setFont(font());
64
65 const int min = minimum();
66 const int max = maximum();
67 const int interval = tickInterval();
68
69 // tickInterval() defaults to 0 (QSlider) until explicitly set; guard before the
70 // divide below rather than relying on every future caller pairing setTickPosition()
71 // with a non-zero setTickInterval().
72 if (interval <= 0) {
73 return;
74 }
75
76 // Number of gaps between ticks; labels at both ends means tickCount+1 labels total.
77 const int tickCount = (max - min) / interval;
78
79 if (tickCount <= 0) {
80 return;
81 }
82
83 // PM_SliderLength is the groove handle length; halving it gives the inset where
84 // the track actually starts and ends, matching how Qt positions tick marks
85 const int trackStart = style()->pixelMetric(QStyle::PM_SliderLength) / 2;
86 const int trackEnd = width() - trackStart;
87 const int trackWidth = trackEnd - trackStart;
88 const QFontMetrics metrics(font());
89 const int textHeight = metrics.height();
90
91 // Computes the clamped draw position for tick index \a i's label.
92 auto labelRectFor = [&](const int i) {
93 const int value = min + (i * interval);
94 const double pos = static_cast<double>(value - min) / (max - min);
95 const int xPos = trackStart + static_cast<int>(pos * trackWidth);
96 const QString text = periodFractionLabel(value);
97 const int textWidth = metrics.horizontalAdvance(text);
98 // Centre the label under its tick but clamp to a 2px inset so the
99 // first and last labels don't overflow the widget rectangle.
100 int textX = xPos - textWidth / 2;
101 textX = (std::max)(textX, 2);
102 textX = (std::min)(textX, width() - textWidth - 2);
103 return std::tuple(text, textX, textWidth);
104 };
105
106 constexpr int minLabelGap = 4;
107
108 // The endpoints (min/max) are the most useful reference points, so their space
109 // is reserved up front; interior labels are only drawn where they fit between
110 // whatever was drawn immediately before them and the reserved right endpoint —
111 // never overlapping either. Adapts to any widget width/font instead of assuming
112 // a fixed "label every Nth tick" stride.
113 const auto [maxText, maxTextX, maxTextWidth] = labelRectFor(tickCount);
114
115 int lastLabelRight = std::numeric_limits<int>::min();
116 for (int i = 0; i < tickCount; ++i) {
117 const auto [text, textX, textWidth] = labelRectFor(i);
118 const bool isEndpoint = (i == 0);
119 const bool collidesWithPrevious = !isEndpoint && textX < lastLabelRight + minLabelGap;
120 const bool collidesWithMaxEndpoint = textX + textWidth + minLabelGap > maxTextX;
121 if (collidesWithPrevious || (!isEndpoint && collidesWithMaxEndpoint)) {
122 continue;
123 }
124 lastLabelRight = textX + textWidth;
125 painter.drawText(textX, height() - textHeight - 2, textWidth, textHeight, Qt::AlignLeft, text);
126 }
127
128 painter.drawText(maxTextX, height() - textHeight - 2, maxTextWidth, textHeight, Qt::AlignLeft, maxText);
129 }
130}
static QString periodFractionLabel(const int value)
LabeledSlider: QSlider subclass that draws numeric labels beneath each tick mark.
void paintEvent(QPaintEvent *event) override
QSize sizeHint() const override
LabeledSlider(QWidget *parent=nullptr)
Constructs the labeled slider with horizontal orientation.
QSize minimumSizeHint() const override