1 // This file is part of Desktop App Toolkit,
2 // a set of libraries for developing nice desktop applications.
3 //
4 // For license and copyright information please follow this link:
5 // https://github.com/desktop-app/legal/blob/master/LEGAL
6 //
7 #include "ui/widgets/checkbox.h"
8
9 #include "ui/effects/ripple_animation.h"
10 #include "ui/ui_utility.h"
11
12 #include <QtGui/QtEvents>
13
14 namespace Ui {
15 namespace {
16
17 TextParseOptions _checkboxOptions = {
18 TextParseMultiline, // flags
19 0, // maxw
20 0, // maxh
21 Qt::LayoutDirectionAuto, // dir
22 };
23
24 TextParseOptions _checkboxRichOptions = {
25 TextParseMultiline | TextParseRichText, // flags
26 0, // maxw
27 0, // maxh
28 Qt::LayoutDirectionAuto, // dir
29 };
30
31 } // namespace
32
AbstractCheckView(int duration,bool checked,Fn<void ()> updateCallback)33 AbstractCheckView::AbstractCheckView(int duration, bool checked, Fn<void()> updateCallback)
34 : _duration(duration)
35 , _checked(checked)
36 , _updateCallback(std::move(updateCallback)) {
37 }
38
setChecked(bool checked,anim::type animated)39 void AbstractCheckView::setChecked(bool checked, anim::type animated) {
40 const auto changed = (_checked != checked);
41 _checked = checked;
42 if (animated == anim::type::instant) {
43 finishAnimating();
44 if (_updateCallback) {
45 _updateCallback();
46 }
47 } else if (changed) {
48 _toggleAnimation.start(
49 [=] { if (_updateCallback) _updateCallback(); },
50 _checked ? 0. : 1.,
51 _checked ? 1. : 0.,
52 _duration);
53 }
54 checkedChangedHook(animated);
55 if (changed) {
56 _checks.fire_copy(_checked);
57 }
58 }
59
setUpdateCallback(Fn<void ()> updateCallback)60 void AbstractCheckView::setUpdateCallback(Fn<void()> updateCallback) {
61 _updateCallback = std::move(updateCallback);
62 }
63
update()64 void AbstractCheckView::update() {
65 if (_updateCallback) {
66 _updateCallback();
67 }
68 }
69
finishAnimating()70 void AbstractCheckView::finishAnimating() {
71 _toggleAnimation.stop();
72 }
73
currentAnimationValue()74 float64 AbstractCheckView::currentAnimationValue() {
75 return _toggleAnimation.value(_checked ? 1. : 0.);
76 }
77
animating() const78 bool AbstractCheckView::animating() const {
79 return _toggleAnimation.animating();
80 }
81
ToggleView(const style::Toggle & st,bool checked,Fn<void ()> updateCallback)82 ToggleView::ToggleView(
83 const style::Toggle &st,
84 bool checked,
85 Fn<void()> updateCallback)
86 : AbstractCheckView(st.duration, checked, std::move(updateCallback))
87 , _st(&st) {
88 }
89
getSize() const90 QSize ToggleView::getSize() const {
91 return QSize(2 * _st->border + _st->diameter + _st->width, 2 * _st->border + _st->diameter);
92 }
93
setStyle(const style::Toggle & st)94 void ToggleView::setStyle(const style::Toggle &st) {
95 _st = &st;
96 }
97
paint(Painter & p,int left,int top,int outerWidth)98 void ToggleView::paint(Painter &p, int left, int top, int outerWidth) {
99 left += _st->border;
100 top += _st->border;
101
102 PainterHighQualityEnabler hq(p);
103 auto toggled = currentAnimationValue();
104 auto fullWidth = _st->diameter + _st->width;
105 auto innerDiameter = _st->diameter - 2 * _st->shift;
106 auto innerRadius = float64(innerDiameter) / 2.;
107 auto toggleLeft = left + anim::interpolate(0, fullWidth - _st->diameter, toggled);
108 auto bgRect = style::rtlrect(left + _st->shift, top + _st->shift, fullWidth - 2 * _st->shift, innerDiameter, outerWidth);
109 auto fgRect = style::rtlrect(toggleLeft, top, _st->diameter, _st->diameter, outerWidth);
110 auto fgBrush = anim::brush(_st->untoggledFg, _st->toggledFg, toggled);
111
112 p.setPen(Qt::NoPen);
113 p.setBrush(fgBrush);
114 p.drawRoundedRect(bgRect, innerRadius, innerRadius);
115
116 auto pen = anim::pen(_st->untoggledFg, _st->toggledFg, toggled);
117 pen.setWidth(_st->border);
118 p.setPen(pen);
119 p.setBrush(anim::brush(_st->untoggledBg, _st->toggledBg, toggled));
120 p.drawEllipse(fgRect);
121
122 if (_st->xsize > 0) {
123 p.setPen(Qt::NoPen);
124 p.setBrush(fgBrush);
125 if (_locked) {
126 const auto color = anim::color(_st->untoggledFg, _st->toggledFg, toggled);
127 _st->lockIcon.paint(p, toggleLeft, top, outerWidth, color);
128 } else {
129 paintXV(p, toggleLeft, top, outerWidth, toggled, fgBrush);
130 }
131 }
132 }
133
paintXV(Painter & p,int left,int top,int outerWidth,float64 toggled,const QBrush & brush)134 void ToggleView::paintXV(Painter &p, int left, int top, int outerWidth, float64 toggled, const QBrush &brush) {
135 Expects(_st->vsize > 0);
136 Expects(_st->stroke > 0);
137
138 static const auto sqrt2 = sqrt(2.);
139 const auto stroke = (0. + _st->stroke) / sqrt2;
140 if (toggled < 1) {
141 // Just X or X->V.
142 const auto xSize = 0. + _st->xsize;
143 const auto xLeft = left + (_st->diameter - xSize) / 2.;
144 const auto xTop = top + (_st->diameter - xSize) / 2.;
145 QPointF pathX[] = {
146 { xLeft, xTop + stroke },
147 { xLeft + stroke, xTop },
148 { xLeft + (xSize / 2.), xTop + (xSize / 2.) - stroke },
149 { xLeft + xSize - stroke, xTop },
150 { xLeft + xSize, xTop + stroke },
151 { xLeft + (xSize / 2.) + stroke, xTop + (xSize / 2.) },
152 { xLeft + xSize, xTop + xSize - stroke },
153 { xLeft + xSize - stroke, xTop + xSize },
154 { xLeft + (xSize / 2.), xTop + (xSize / 2.) + stroke },
155 { xLeft + stroke, xTop + xSize },
156 { xLeft, xTop + xSize - stroke },
157 { xLeft + (xSize / 2.) - stroke, xTop + (xSize / 2.) },
158 };
159 for (auto &point : pathX) {
160 point = style::rtlpoint(point, outerWidth);
161 }
162 if (toggled > 0) {
163 // X->V.
164 const auto vSize = 0. + _st->vsize;
165 const auto fSize = (xSize + vSize - 2. * stroke);
166 const auto vLeft = left + (_st->diameter - fSize) / 2.;
167 const auto vTop = 0. + xTop + _st->vshift;
168 QPointF pathV[] = {
169 { vLeft, vTop + xSize - vSize + stroke },
170 { vLeft + stroke, vTop + xSize - vSize },
171 { vLeft + vSize - stroke, vTop + xSize - 2 * stroke },
172 { vLeft + fSize - stroke, vTop },
173 { vLeft + fSize, vTop + stroke },
174 { vLeft + vSize, vTop + xSize - stroke },
175 { vLeft + vSize, vTop + xSize - stroke },
176 { vLeft + vSize - stroke, vTop + xSize },
177 { vLeft + vSize - stroke, vTop + xSize },
178 { vLeft + vSize - stroke, vTop + xSize },
179 { vLeft + vSize - 2 * stroke, vTop + xSize - stroke },
180 { vLeft + vSize - 2 * stroke, vTop + xSize - stroke },
181 };
182 for (auto &point : pathV) {
183 point = style::rtlpoint(point, outerWidth);
184 }
185 p.fillPath(anim::interpolate(pathX, pathV, toggled), brush);
186 } else {
187 // Just X.
188 p.fillPath(anim::path(pathX), brush);
189 }
190 } else {
191 // Just V.
192 const auto xSize = 0. + _st->xsize;
193 const auto xTop = top + (_st->diameter - xSize) / 2.;
194 const auto vSize = 0. + _st->vsize;
195 const auto fSize = (xSize + vSize - 2. * stroke);
196 const auto vLeft = left + (_st->diameter - (_st->xsize + _st->vsize - 2. * stroke)) / 2.;
197 const auto vTop = 0. + xTop + _st->vshift;
198 QPointF pathV[] = {
199 { vLeft, vTop + xSize - vSize + stroke },
200 { vLeft + stroke, vTop + xSize - vSize },
201 { vLeft + vSize - stroke, vTop + xSize - 2 * stroke },
202 { vLeft + fSize - stroke, vTop },
203 { vLeft + fSize, vTop + stroke },
204 { vLeft + vSize, vTop + xSize - stroke },
205 { vLeft + vSize, vTop + xSize - stroke },
206 { vLeft + vSize - stroke, vTop + xSize },
207 { vLeft + vSize - stroke, vTop + xSize },
208 { vLeft + vSize - stroke, vTop + xSize },
209 { vLeft + vSize - 2 * stroke, vTop + xSize - stroke },
210 { vLeft + vSize - 2 * stroke, vTop + xSize - stroke },
211 };
212
213 p.fillPath(anim::path(pathV), brush);
214 }
215 }
216
rippleSize() const217 QSize ToggleView::rippleSize() const {
218 return getSize() + 2 * QSize(_st->rippleAreaPadding, _st->rippleAreaPadding);
219 }
220
prepareRippleMask() const221 QImage ToggleView::prepareRippleMask() const {
222 auto size = rippleSize();
223 return RippleAnimation::roundRectMask(size, size.height() / 2);
224 }
225
checkRippleStartPosition(QPoint position) const226 bool ToggleView::checkRippleStartPosition(QPoint position) const {
227 return QRect(QPoint(0, 0), rippleSize()).contains(position);
228 }
229
setLocked(bool locked)230 void ToggleView::setLocked(bool locked) {
231 if (_locked != locked) {
232 _locked = locked;
233 update();
234 }
235 }
236
CheckView(const style::Check & st,bool checked,Fn<void ()> updateCallback)237 CheckView::CheckView(const style::Check &st, bool checked, Fn<void()> updateCallback) : AbstractCheckView(st.duration, checked, std::move(updateCallback))
238 , _st(&st) {
239 }
240
getSize() const241 QSize CheckView::getSize() const {
242 return QSize(_st->diameter, _st->diameter);
243 }
244
setStyle(const style::Check & st)245 void CheckView::setStyle(const style::Check &st) {
246 _st = &st;
247 }
248
paint(Painter & p,int left,int top,int outerWidth)249 void CheckView::paint(Painter &p, int left, int top, int outerWidth) {
250 auto toggled = currentAnimationValue();
251 auto pen = _untoggledOverride
252 ? anim::pen(*_untoggledOverride, _st->toggledFg, toggled)
253 : anim::pen(_st->untoggledFg, _st->toggledFg, toggled);
254 pen.setWidth(_st->thickness);
255 p.setPen(pen);
256 p.setBrush(anim::brush(
257 _st->bg,
258 (_untoggledOverride
259 ? anim::color(*_untoggledOverride, _st->toggledFg, toggled)
260 : anim::color(_st->untoggledFg, _st->toggledFg, toggled)),
261 toggled));
262
263 {
264 PainterHighQualityEnabler hq(p);
265 p.drawRoundedRect(style::rtlrect(QRectF(left, top, _st->diameter, _st->diameter).marginsRemoved(QMarginsF(_st->thickness / 2., _st->thickness / 2., _st->thickness / 2., _st->thickness / 2.)), outerWidth), st::roundRadiusSmall - (_st->thickness / 2.), st::roundRadiusSmall - (_st->thickness / 2.));
266 }
267
268 if (toggled > 0) {
269 _st->icon.paint(p, QPoint(left, top), outerWidth);
270 }
271 }
272
rippleSize() const273 QSize CheckView::rippleSize() const {
274 return getSize() + 2 * QSize(_st->rippleAreaPadding, _st->rippleAreaPadding);
275 }
276
prepareRippleMask() const277 QImage CheckView::prepareRippleMask() const {
278 return RippleAnimation::ellipseMask(rippleSize());
279 }
280
checkRippleStartPosition(QPoint position) const281 bool CheckView::checkRippleStartPosition(QPoint position) const {
282 return QRect(QPoint(0, 0), rippleSize()).contains(position);
283 }
284
setUntoggledOverride(std::optional<QColor> untoggledOverride)285 void CheckView::setUntoggledOverride(
286 std::optional<QColor> untoggledOverride) {
287 _untoggledOverride = untoggledOverride;
288 update();
289 }
290
RadioView(const style::Radio & st,bool checked,Fn<void ()> updateCallback)291 RadioView::RadioView(
292 const style::Radio &st,
293 bool checked,
294 Fn<void()> updateCallback)
295 : AbstractCheckView(st.duration, checked, std::move(updateCallback))
296 , _st(&st) {
297 }
298
getSize() const299 QSize RadioView::getSize() const {
300 return QSize(_st->diameter, _st->diameter);
301 }
302
setStyle(const style::Radio & st)303 void RadioView::setStyle(const style::Radio &st) {
304 _st = &st;
305 }
306
paint(Painter & p,int left,int top,int outerWidth)307 void RadioView::paint(Painter &p, int left, int top, int outerWidth) {
308 PainterHighQualityEnabler hq(p);
309
310 auto toggled = currentAnimationValue();
311 auto pen = _toggledOverride
312 ? (_untoggledOverride
313 ? anim::pen(*_untoggledOverride, *_toggledOverride, toggled)
314 : anim::pen(_st->untoggledFg, *_toggledOverride, toggled))
315 : (_untoggledOverride
316 ? anim::pen(*_untoggledOverride, _st->toggledFg, toggled)
317 : anim::pen(_st->untoggledFg, _st->toggledFg, toggled));
318 pen.setWidth(_st->thickness);
319 p.setPen(pen);
320 p.setBrush(_st->bg);
321 //int32 skip = qCeil(_st->thickness / 2.);
322 //p.drawEllipse(_checkRect.marginsRemoved(QMargins(skip, skip, skip, skip)));
323 const auto skip = (_st->outerSkip / 10.) + (_st->thickness / 2);
324 p.drawEllipse(style::rtlrect(QRectF(left, top, _st->diameter, _st->diameter).marginsRemoved(QMarginsF(skip, skip, skip, skip)), outerWidth));
325
326 if (toggled > 0) {
327 p.setPen(Qt::NoPen);
328 p.setBrush(_toggledOverride
329 ? (_untoggledOverride
330 ? anim::brush(*_untoggledOverride, *_toggledOverride, toggled)
331 : anim::brush(_st->untoggledFg, *_toggledOverride, toggled))
332 : (_untoggledOverride
333 ? anim::brush(*_untoggledOverride, _st->toggledFg, toggled)
334 : anim::brush(_st->untoggledFg, _st->toggledFg, toggled)));
335
336 const auto skip0 = _st->diameter / 2., skip1 = _st->skip / 10., checkSkip = skip0 * (1. - toggled) + skip1 * toggled;
337 p.drawEllipse(style::rtlrect(QRectF(left, top, _st->diameter, _st->diameter).marginsRemoved(QMarginsF(checkSkip, checkSkip, checkSkip, checkSkip)), outerWidth));
338 //int32 fskip = qFloor(checkSkip), cskip = qCeil(checkSkip);
339 //if (2 * fskip < _checkRect.width()) {
340 // if (fskip != cskip) {
341 // p.setOpacity(float64(cskip) - checkSkip);
342 // p.drawEllipse(_checkRect.marginsRemoved(QMargins(fskip, fskip, fskip, fskip)));
343 // p.setOpacity(1.);
344 // }
345 // if (2 * cskip < _checkRect.width()) {
346 // p.drawEllipse(_checkRect.marginsRemoved(QMargins(cskip, cskip, cskip, cskip)));
347 // }
348 //}
349 }
350 }
351
rippleSize() const352 QSize RadioView::rippleSize() const {
353 return getSize() + 2 * QSize(_st->rippleAreaPadding, _st->rippleAreaPadding);
354 }
355
prepareRippleMask() const356 QImage RadioView::prepareRippleMask() const {
357 return RippleAnimation::ellipseMask(rippleSize());
358 }
359
checkRippleStartPosition(QPoint position) const360 bool RadioView::checkRippleStartPosition(QPoint position) const {
361 return QRect(QPoint(0, 0), rippleSize()).contains(position);
362 }
363
setToggledOverride(std::optional<QColor> toggledOverride)364 void RadioView::setToggledOverride(std::optional<QColor> toggledOverride) {
365 _toggledOverride = toggledOverride;
366 update();
367 }
368
setUntoggledOverride(std::optional<QColor> untoggledOverride)369 void RadioView::setUntoggledOverride(
370 std::optional<QColor> untoggledOverride) {
371 _untoggledOverride = untoggledOverride;
372 update();
373 }
374
Checkbox(QWidget * parent,const QString & text,bool checked,const style::Checkbox & st,const style::Check & checkSt)375 Checkbox::Checkbox(
376 QWidget *parent,
377 const QString &text,
378 bool checked,
379 const style::Checkbox &st,
380 const style::Check &checkSt)
381 : Checkbox(
382 parent,
383 text,
384 st,
385 std::make_unique<CheckView>(
386 checkSt,
387 checked)) {
388 }
389
Checkbox(QWidget * parent,const QString & text,bool checked,const style::Checkbox & st,const style::Toggle & toggleSt)390 Checkbox::Checkbox(
391 QWidget *parent,
392 const QString &text,
393 bool checked,
394 const style::Checkbox &st,
395 const style::Toggle &toggleSt)
396 : Checkbox(
397 parent,
398 rpl::single(text),
399 st,
400 std::make_unique<ToggleView>(
401 toggleSt,
402 checked)) {
403 }
404
Checkbox(QWidget * parent,rpl::producer<QString> && text,bool checked,const style::Checkbox & st,const style::Check & checkSt)405 Checkbox::Checkbox(
406 QWidget *parent,
407 rpl::producer<QString> &&text,
408 bool checked,
409 const style::Checkbox &st,
410 const style::Check &checkSt)
411 : Checkbox(
412 parent,
413 std::move(text),
414 st,
415 std::make_unique<CheckView>(
416 checkSt,
417 checked)) {
418 }
419
Checkbox(QWidget * parent,rpl::producer<QString> && text,bool checked,const style::Checkbox & st,const style::Toggle & toggleSt)420 Checkbox::Checkbox(
421 QWidget *parent,
422 rpl::producer<QString> &&text,
423 bool checked,
424 const style::Checkbox &st,
425 const style::Toggle &toggleSt)
426 : Checkbox(
427 parent,
428 std::move(text),
429 st,
430 std::make_unique<ToggleView>(
431 toggleSt,
432 checked)) {
433 }
434
Checkbox(QWidget * parent,const QString & text,const style::Checkbox & st,std::unique_ptr<AbstractCheckView> check)435 Checkbox::Checkbox(
436 QWidget *parent,
437 const QString &text,
438 const style::Checkbox &st,
439 std::unique_ptr<AbstractCheckView> check)
440 : Checkbox(
441 parent,
442 rpl::single(text),
443 st,
444 std::move(check)) {
445 }
446
Checkbox(QWidget * parent,rpl::producer<QString> && text,const style::Checkbox & st,std::unique_ptr<AbstractCheckView> check)447 Checkbox::Checkbox(
448 QWidget *parent,
449 rpl::producer<QString> &&text,
450 const style::Checkbox &st,
451 std::unique_ptr<AbstractCheckView> check)
452 : RippleButton(parent, st.ripple)
453 , _st(st)
454 , _check(std::move(check))
455 , _text(
456 _st.style,
457 QString(),
458 _checkboxOptions,
459 countTextMinWidth()) {
460 _check->setUpdateCallback([=] { update(); });
461 resizeToText();
462 setCursor(style::cur_pointer);
463 std::move(
464 text
465 ) | rpl::start_with_next([=](QString &&value) {
466 setText(std::move(value));
467 }, lifetime());
468 }
469
countTextMinWidth() const470 int Checkbox::countTextMinWidth() const {
471 const auto leftSkip = _st.checkPosition.x()
472 + checkRect().width()
473 + _st.textPosition.x();
474 return (_st.width > 0)
475 ? std::max(_st.width - leftSkip, 1)
476 : QFIXED_MAX;
477 }
478
checkRect() const479 QRect Checkbox::checkRect() const {
480 auto size = _check->getSize();
481 return QRect({
482 (_checkAlignment & Qt::AlignHCenter)
483 ? (width() - size.width()) / 2
484 : (_checkAlignment & Qt::AlignRight)
485 ? (width() - _st.checkPosition.x() - size.width())
486 : _st.checkPosition.x(),
487 (_checkAlignment & Qt::AlignVCenter)
488 ? (height() - size.height()) / 2
489 : (_checkAlignment & Qt::AlignBottom)
490 ? (height() - _st.checkPosition.y() - size.height())
491 : _st.checkPosition.y()
492 }, size);
493 }
494
setText(const QString & text,bool rich)495 void Checkbox::setText(const QString &text, bool rich) {
496 _text.setText(_st.style, text, rich ? _checkboxRichOptions : _checkboxOptions);
497 resizeToText();
498 update();
499 }
500
setCheckAlignment(style::align alignment)501 void Checkbox::setCheckAlignment(style::align alignment) {
502 if (_checkAlignment != alignment) {
503 _checkAlignment = alignment;
504 resizeToText();
505 update();
506 }
507 }
508
setAllowTextLines(int lines)509 void Checkbox::setAllowTextLines(int lines) {
510 _allowTextLines = lines;
511 resizeToText();
512 update();
513 }
514
setTextBreakEverywhere(bool allow)515 void Checkbox::setTextBreakEverywhere(bool allow) {
516 _textBreakEverywhere = allow;
517 }
518
checked() const519 bool Checkbox::checked() const {
520 return _check->checked();
521 }
522
checkedChanges() const523 rpl::producer<bool> Checkbox::checkedChanges() const {
524 return _checkedChanges.events();
525 }
526
checkedValue() const527 rpl::producer<bool> Checkbox::checkedValue() const {
528 return _checkedChanges.events_starting_with(checked());
529 }
530
resizeToText()531 void Checkbox::resizeToText() {
532 if (_st.width <= 0) {
533 resizeToWidth(_text.maxWidth() - _st.width);
534 } else {
535 resizeToWidth(_st.width);
536 }
537 }
538
setChecked(bool checked,NotifyAboutChange notify)539 void Checkbox::setChecked(bool checked, NotifyAboutChange notify) {
540 if (_check->checked() != checked) {
541 _check->setChecked(checked, anim::type::normal);
542 if (notify == NotifyAboutChange::Notify) {
543 _checkedChanges.fire_copy(checked);
544 }
545 }
546 }
547
finishAnimating()548 void Checkbox::finishAnimating() {
549 _check->finishAnimating();
550 }
551
naturalWidth() const552 int Checkbox::naturalWidth() const {
553 if (_st.width > 0) {
554 return _st.width;
555 }
556 auto result = _st.checkPosition.x() + _check->getSize().width();
557 if (!_text.isEmpty()) {
558 result += _st.textPosition.x() + _text.maxWidth();
559 }
560 return result - _st.width;
561 }
562
paintEvent(QPaintEvent * e)563 void Checkbox::paintEvent(QPaintEvent *e) {
564 Painter p(this);
565
566 auto check = checkRect();
567 auto active = _check->currentAnimationValue();
568 if (isDisabled()) {
569 p.setOpacity(_st.disabledOpacity);
570 } else {
571 auto color = anim::color(_st.rippleBg, _st.rippleBgActive, active);
572 paintRipple(p, check.topLeft() + _st.rippleAreaPosition, &color);
573 }
574
575 auto realCheckRect = myrtlrect(check);
576 if (realCheckRect.intersects(e->rect())) {
577 if (isDisabled()) {
578 p.drawPixmapLeft(check.left(), check.top(), width(), _checkCache);
579 } else {
580 _check->paint(p, check.left(), check.top(), width());
581 }
582 }
583 if (realCheckRect.contains(e->rect()) || _text.isEmpty()) {
584 return;
585 }
586
587 const auto alignLeft = (_checkAlignment & Qt::AlignLeft);
588 const auto alignRight = (_checkAlignment & Qt::AlignRight);
589 const auto textSkip = _st.checkPosition.x()
590 + check.width()
591 + _st.textPosition.x();
592 const auto availableTextWidth = (alignLeft || alignRight)
593 ? std::max(width() - textSkip, 1)
594 : std::max(width() - _st.margin.left() - _st.margin.right(), 1);
595 const auto textTop = _st.margin.top() + _st.textPosition.y();
596
597 p.setPen(anim::pen(_st.textFg, _st.textFgActive, active));
598 if (alignLeft) {
599 if (!_allowTextLines) {
600 _text.drawLeft(
601 p,
602 textSkip,
603 textTop,
604 availableTextWidth,
605 width());
606 } else {
607 _text.drawLeftElided(
608 p,
609 textSkip,
610 textTop,
611 availableTextWidth,
612 width(),
613 _allowTextLines,
614 style::al_left,
615 0,
616 -1,
617 0,
618 _textBreakEverywhere);
619 }
620 } else if (alignRight) {
621 if (!_allowTextLines) {
622 _text.drawRight(
623 p,
624 textSkip,
625 textTop,
626 availableTextWidth,
627 width());
628 } else {
629 _text.drawRightElided(
630 p,
631 textSkip,
632 textTop,
633 availableTextWidth,
634 width(),
635 _allowTextLines,
636 style::al_left,
637 0,
638 -1,
639 0,
640 _textBreakEverywhere);
641 }
642 } else if (!_allowTextLines
643 || (_text.countHeight(availableTextWidth)
644 < (_allowTextLines + 1) * _st.style.font->height)) {
645 _text.drawLeft(
646 p,
647 _st.margin.left(),
648 textTop,
649 width() - _st.margin.left() - _st.margin.right(),
650 width(),
651 style::al_top);
652 } else {
653 _text.drawLeftElided(
654 p,
655 _st.margin.left(),
656 textTop,
657 width() - _st.margin.left() - _st.margin.right(),
658 width(),
659 _allowTextLines,
660 style::al_top,
661 0,
662 -1,
663 0,
664 _textBreakEverywhere);
665 }
666 }
667
grabCheckCache() const668 QPixmap Checkbox::grabCheckCache() const {
669 auto checkSize = _check->getSize();
670 auto image = QImage(
671 checkSize * style::DevicePixelRatio(),
672 QImage::Format_ARGB32_Premultiplied);
673 image.fill(Qt::transparent);
674 image.setDevicePixelRatio(style::DevicePixelRatio());
675 {
676 Painter p(&image);
677 _check->paint(p, 0, 0, checkSize.width());
678 }
679 return PixmapFromImage(std::move(image));
680 }
681
onStateChanged(State was,StateChangeSource source)682 void Checkbox::onStateChanged(State was, StateChangeSource source) {
683 RippleButton::onStateChanged(was, source);
684
685 if (isDisabled() && !(was & StateFlag::Disabled)) {
686 setCursor(style::cur_default);
687 finishAnimating();
688 _checkCache = grabCheckCache();
689 } else if (!isDisabled() && (was & StateFlag::Disabled)) {
690 setCursor(style::cur_pointer);
691 _checkCache = QPixmap();
692 }
693
694 auto now = state();
695 if (!isDisabled() && (was & StateFlag::Over) && (now & StateFlag::Over)) {
696 if ((was & StateFlag::Down) && !(now & StateFlag::Down)) {
697 handlePress();
698 }
699 }
700 }
701
handlePress()702 void Checkbox::handlePress() {
703 setChecked(!checked());
704 }
705
resizeGetHeight(int newWidth)706 int Checkbox::resizeGetHeight(int newWidth) {
707 const auto result = _check->getSize().height();
708 const auto centered = ((_checkAlignment & Qt::AlignHCenter) != 0);
709 if (!centered && _allowTextLines == 1) {
710 return result;
711 }
712 const auto leftSkip = _st.checkPosition.x()
713 + checkRect().width()
714 + _st.textPosition.x();
715 const auto availableTextWidth = centered
716 ? (newWidth - _st.margin.left() - _st.margin.right())
717 : std::max(width() - leftSkip, 1);
718 const auto textHeight = _text.countHeight(availableTextWidth);
719 const auto textBottom = _st.textPosition.y()
720 + (_allowTextLines
721 ? std::min(textHeight, _allowTextLines * _st.style.font->height)
722 : textHeight);
723 return std::max(result, textBottom);
724 }
725
prepareRippleMask() const726 QImage Checkbox::prepareRippleMask() const {
727 return _check->prepareRippleMask();
728 }
729
prepareRippleStartPosition() const730 QPoint Checkbox::prepareRippleStartPosition() const {
731 if (isDisabled()) {
732 return DisabledRippleStartPosition();
733 }
734 auto position = myrtlpoint(mapFromGlobal(QCursor::pos()))
735 - checkRect().topLeft()
736 - _st.rippleAreaPosition;
737 return _check->checkRippleStartPosition(position)
738 ? position
739 : DisabledRippleStartPosition();
740 }
741
setValue(int value)742 void RadiobuttonGroup::setValue(int value) {
743 if (_hasValue && _value == value) {
744 return;
745 }
746 _hasValue = true;
747 _value = value;
748 for (const auto button : _buttons) {
749 button->handleNewGroupValue(_value);
750 }
751 if (const auto callback = _changedCallback) {
752 callback(_value);
753 }
754 }
755
Radiobutton(QWidget * parent,const std::shared_ptr<RadiobuttonGroup> & group,int value,const QString & text,const style::Checkbox & st,const style::Radio & radioSt)756 Radiobutton::Radiobutton(
757 QWidget *parent,
758 const std::shared_ptr<RadiobuttonGroup> &group,
759 int value,
760 const QString &text,
761 const style::Checkbox &st,
762 const style::Radio &radioSt)
763 : Radiobutton(
764 parent,
765 group,
766 value,
767 text,
768 st,
769 std::make_unique<RadioView>(
770 radioSt,
771 (group->hasValue() && group->value() == value))) {
772 }
773
Radiobutton(QWidget * parent,const std::shared_ptr<RadiobuttonGroup> & group,int value,const QString & text,const style::Checkbox & st,std::unique_ptr<AbstractCheckView> check)774 Radiobutton::Radiobutton(
775 QWidget *parent,
776 const std::shared_ptr<RadiobuttonGroup> &group,
777 int value,
778 const QString &text,
779 const style::Checkbox &st,
780 std::unique_ptr<AbstractCheckView> check)
781 : Checkbox(
782 parent,
783 text,
784 st,
785 std::move(check))
786 , _group(group)
787 , _value(value) {
788 using namespace rpl::mappers;
789
790 checkbox()->setChecked(group->hasValue() && group->value() == value);
791 _group->registerButton(this);
792 checkbox()->checkedChanges(
793 ) | rpl::filter(
794 _1
795 ) | rpl::start_with_next([=] {
796 _group->setValue(_value);
797 }, lifetime());
798 }
799
handleNewGroupValue(int value)800 void Radiobutton::handleNewGroupValue(int value) {
801 auto checked = (value == _value);
802 if (checkbox()->checked() != checked) {
803 checkbox()->setChecked(
804 checked,
805 Ui::Checkbox::NotifyAboutChange::DontNotify);
806 }
807 }
808
handlePress()809 void Radiobutton::handlePress() {
810 if (!checkbox()->checked()) {
811 checkbox()->setChecked(true);
812 }
813 }
814
~Radiobutton()815 Radiobutton::~Radiobutton() {
816 _group->unregisterButton(this);
817 }
818
819 } // namespace Ui
820