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