SegmentButton
A push button that rounds only the corners at the end of a row.
| Declared in | src/libs/libwidgets/components/buttons/SegmentButton.hpp |
| Inherits | QPushButton |
| Group | widgets |
Detailed description
Why this exists
A style draws a button with one corner radius on all four corners. This project's does: OctoStyle::drawPrimitive paints drawRoundedRect(r, 6.0, 6.0) and offers no way to ask for less (OctoStyle.cpp:1171). A row of ordinary buttons therefore reads as a row of separate buttons and never as one pill.
How it works, and why nothing else had to change
QPushButton paints itself with one call into the style:
QStylePainter p(this);
QStyleOptionButton option;
initStyleOption(&option);
p.drawControl(QStyle::CE_PushButton, option);
That is the whole of QPushButton::paintEvent (qtbase/src/widgets/widgets/qpushbutton.cpp:411-417). Qt splits the work into CE_PushButtonBevel, which is the frame, and CE_PushButtonLabel, which is the text and the icon.
So this class draws the bevel into a rect that is WIDER than the button and clips the painter to the button. The rounded corners fall outside the clip and the edge that remains is square. The label is drawn afterwards at the true rect, so text centers in the segment rather than in the extension.
Nothing in the style changed. The control therefore draws correctly under any style, because every style implements CE_PushButtonBevel.
What makes it safe
Three properties of the painter decide whether drawing wider distorts anything, and all three were read in OctoStyle before this was written:
- The fill gradient is vertical —
QLinearGradient(x, top, x, bottom)atOctoStyle.cpp:467— so widening sideways cannot change the visible fill. - The panel is laid out relative to its own rect —
r = rect.adjusted(0, 1, -1, 0)atOctoStyle.cpp:1143. - The pixmap cache is keyed by size and never by position, and it blits with an ordinary
drawPixmapthat the painter's clip applies to (OctoStyle.cpp:72-76).
A style whose button gradient ran sideways would need a different technique. testSegmentedControlPaint asserts the fill is unmoved, so that case is caught rather than discovered.
Member type documentation
Position enum
enum SegmentButton::Position
Where a segment sits in its row. The ends of the row are the rounded ones.
| Constant | Value | Description |
|---|---|---|
SegmentButton::Position::Only |
0 |
The whole pill. The segment paints as an ordinary button. |
SegmentButton::Position::First |
1 |
Rounded on the left, square on the right. |
SegmentButton::Position::Middle |
2 |
Square on both sides. |
SegmentButton::Position::Last |
3 |
Square on the left, rounded on the right. |
Property documentation
position property
position : Position
where the segment sits in its row.
The position decides which corners the button rounds. The default is Position::Only, which paints an ordinary button. A change repaints the button.
Access functions:
SegmentButton::Positionposition() constvoidsetPosition(SegmentButton::Positionposition)
Member function documentation
paintEvent
[override virtual protected] void SegmentButton::paintEvent(QPaintEvent *event)
Reimplements: QPushButton::paintEvent(QPaintEvent *).
Draw the bevel wider than the button, clipped to the button.
The order matters and it is the order the style uses: the bevel first, then the label over it. CE_PushButton would draw both plus the focus frame, and it would draw them at one rect — which is exactly what must not happen here.
When the position is Position::Only, the function passes event to QPushButton::paintEvent() and draws nothing itself. The SegmentButton class description tells why this is a clip and not a new path.