continuous_sliders.cpp 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399
  1. /*
  2. This file is part of Telegram Desktop,
  3. the official desktop application for the Telegram messaging service.
  4. For license and copyright information please follow this link:
  5. https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
  6. */
  7. #include "ui/widgets/continuous_sliders.h"
  8. #include "ui/painter.h"
  9. #include "base/timer.h"
  10. #include "base/platform/base_platform_info.h"
  11. #include "styles/style_widgets.h"
  12. namespace Ui {
  13. namespace {
  14. constexpr auto kByWheelFinishedTimeout = 1000;
  15. } // namespace
  16. ContinuousSlider::ContinuousSlider(QWidget *parent) : RpWidget(parent) {
  17. setCursor(style::cur_pointer);
  18. }
  19. void ContinuousSlider::setDisabled(bool disabled) {
  20. if (_disabled != disabled) {
  21. _disabled = disabled;
  22. setCursor(_disabled ? style::cur_default : style::cur_pointer);
  23. update();
  24. }
  25. }
  26. void ContinuousSlider::setMoveByWheel(bool move) {
  27. if (move != moveByWheel()) {
  28. if (move) {
  29. _byWheelFinished = std::make_unique<base::Timer>([=] {
  30. if (_changeFinishedCallback) {
  31. _changeFinishedCallback(getCurrentValue());
  32. }
  33. });
  34. } else {
  35. _byWheelFinished = nullptr;
  36. }
  37. }
  38. }
  39. QRect ContinuousSlider::getSeekRect() const {
  40. const auto decrease = getSeekDecreaseSize();
  41. return isHorizontal()
  42. ? QRect(decrease.width() / 2, 0, width() - decrease.width(), height())
  43. : QRect(0, decrease.height() / 2, width(), height() - decrease.width());
  44. }
  45. float64 ContinuousSlider::value() const {
  46. return getCurrentValue();
  47. }
  48. void ContinuousSlider::setValue(float64 value) {
  49. setValue(value, -1);
  50. }
  51. void ContinuousSlider::setValue(float64 value, float64 receivedTill) {
  52. if (_value != value || _receivedTill != receivedTill) {
  53. _value = value;
  54. _receivedTill = receivedTill;
  55. update();
  56. }
  57. }
  58. void ContinuousSlider::setFadeOpacity(float64 opacity) {
  59. _fadeOpacity = opacity;
  60. update();
  61. }
  62. void ContinuousSlider::mouseMoveEvent(QMouseEvent *e) {
  63. if (_mouseDown) {
  64. updateDownValueFromPos(e->pos());
  65. }
  66. }
  67. float64 ContinuousSlider::computeValue(const QPoint &pos) const {
  68. const auto seekRect = myrtlrect(getSeekRect());
  69. const auto result = isHorizontal() ?
  70. (pos.x() - seekRect.x()) / float64(seekRect.width()) :
  71. (1. - (pos.y() - seekRect.y()) / float64(seekRect.height()));
  72. const auto snapped = std::clamp(result, 0., 1.);
  73. return _adjustCallback ? _adjustCallback(snapped) : snapped;
  74. }
  75. void ContinuousSlider::mousePressEvent(QMouseEvent *e) {
  76. _mouseDown = true;
  77. _downValue = computeValue(e->pos());
  78. update();
  79. if (_changeProgressCallback) {
  80. _changeProgressCallback(_downValue);
  81. }
  82. }
  83. void ContinuousSlider::mouseReleaseEvent(QMouseEvent *e) {
  84. if (_mouseDown) {
  85. _mouseDown = false;
  86. if (_changeFinishedCallback) {
  87. _changeFinishedCallback(_downValue);
  88. }
  89. _value = _downValue;
  90. update();
  91. }
  92. }
  93. void ContinuousSlider::wheelEvent(QWheelEvent *e) {
  94. if (_mouseDown || !moveByWheel()) {
  95. return;
  96. }
  97. constexpr auto step = static_cast<int>(QWheelEvent::DefaultDeltasPerStep);
  98. constexpr auto coef = 1. / (step * 10.);
  99. auto deltaX = e->angleDelta().x(), deltaY = e->angleDelta().y();
  100. if (Platform::IsMac()) {
  101. deltaY *= -1;
  102. } else {
  103. deltaX *= -1;
  104. }
  105. auto delta = (qAbs(deltaX) > qAbs(deltaY)) ? deltaX : deltaY;
  106. auto finalValue = std::clamp(_value + delta * coef, 0., 1.);
  107. setValue(finalValue);
  108. if (_changeProgressCallback) {
  109. _changeProgressCallback(finalValue);
  110. }
  111. _byWheelFinished->callOnce(kByWheelFinishedTimeout);
  112. }
  113. void ContinuousSlider::updateDownValueFromPos(const QPoint &pos) {
  114. _downValue = computeValue(pos);
  115. update();
  116. if (_changeProgressCallback) {
  117. _changeProgressCallback(_downValue);
  118. }
  119. }
  120. void ContinuousSlider::enterEventHook(QEnterEvent *e) {
  121. setOver(true);
  122. }
  123. void ContinuousSlider::leaveEventHook(QEvent *e) {
  124. setOver(false);
  125. }
  126. void ContinuousSlider::setOver(bool over) {
  127. if (_over == over) return;
  128. _over = over;
  129. auto from = _over ? 0. : 1., to = _over ? 1. : 0.;
  130. _overAnimation.start([=] { update(); }, from, to, getOverDuration());
  131. }
  132. FilledSlider::FilledSlider(QWidget *parent, const style::FilledSlider &st) : ContinuousSlider(parent)
  133. , _st(st) {
  134. }
  135. QSize FilledSlider::getSeekDecreaseSize() const {
  136. return QSize(0, 0);
  137. }
  138. float64 FilledSlider::getOverDuration() const {
  139. return _st.duration;
  140. }
  141. void FilledSlider::paintEvent(QPaintEvent *e) {
  142. auto p = QPainter(this);
  143. PainterHighQualityEnabler hq(p);
  144. p.setPen(Qt::NoPen);
  145. const auto masterOpacity = fadeOpacity();
  146. const auto disabled = isDisabled();
  147. const auto over = getCurrentOverFactor();
  148. const auto lineWidth = _st.lineWidth + ((_st.fullWidth - _st.lineWidth) * over);
  149. const auto lineWidthRounded = std::floor(lineWidth);
  150. const auto lineWidthPartial = lineWidth - lineWidthRounded;
  151. const auto seekRect = getSeekRect();
  152. const auto value = getCurrentValue();
  153. const auto from = seekRect.x();
  154. const auto mid = qRound(from + value * seekRect.width());
  155. const auto end = from + seekRect.width();
  156. if (mid > from) {
  157. p.setOpacity(masterOpacity);
  158. p.fillRect(from, height() - lineWidthRounded, (mid - from), lineWidthRounded, disabled ? _st.disabledFg : _st.activeFg);
  159. if (lineWidthPartial > 0.01) {
  160. p.setOpacity(masterOpacity * lineWidthPartial);
  161. p.fillRect(from, height() - lineWidthRounded - 1, (mid - from), 1, disabled ? _st.disabledFg : _st.activeFg);
  162. }
  163. }
  164. if (end > mid && over > 0) {
  165. p.setOpacity(masterOpacity * over);
  166. p.fillRect(mid, height() - lineWidthRounded, (end - mid), lineWidthRounded, _st.inactiveFg);
  167. if (lineWidthPartial > 0.01) {
  168. p.setOpacity(masterOpacity * over * lineWidthPartial);
  169. p.fillRect(mid, height() - lineWidthRounded - 1, (end - mid), 1, _st.inactiveFg);
  170. }
  171. }
  172. }
  173. MediaSlider::MediaSlider(QWidget *parent, const style::MediaSlider &st) : ContinuousSlider(parent)
  174. , _st(st) {
  175. }
  176. QSize MediaSlider::getSeekDecreaseSize() const {
  177. return _alwaysDisplayMarker ? _st.seekSize : QSize();
  178. }
  179. float64 MediaSlider::getOverDuration() const {
  180. return _st.duration;
  181. }
  182. void MediaSlider::disablePaint(bool disabled) {
  183. _paintDisabled = disabled;
  184. }
  185. void MediaSlider::addDivider(float64 atValue, const QSize &size) {
  186. _dividers.push_back(Divider{ atValue, size });
  187. }
  188. void MediaSlider::setActiveFgOverride(std::optional<QColor> color) {
  189. _activeFgOverride = color;
  190. update();
  191. }
  192. void MediaSlider::paintEvent(QPaintEvent *e) {
  193. if (_paintDisabled) {
  194. return;
  195. }
  196. auto p = QPainter(this);
  197. PainterHighQualityEnabler hq(p);
  198. p.setPen(Qt::NoPen);
  199. p.setOpacity(fadeOpacity());
  200. const auto horizontal = isHorizontal();
  201. const auto radius = _st.width / 2;
  202. const auto disabled = isDisabled();
  203. const auto over = getCurrentOverFactor();
  204. const auto seekRect = getSeekRect();
  205. // invert colors and value for vertical
  206. const auto value = horizontal
  207. ? getCurrentValue()
  208. : (1. - getCurrentValue());
  209. // receivedTill is not supported for vertical
  210. const auto receivedTill = horizontal
  211. ? getCurrentReceivedTill()
  212. : value;
  213. const auto markerFrom = (horizontal ? seekRect.x() : seekRect.y());
  214. const auto markerLength = horizontal
  215. ? seekRect.width()
  216. : seekRect.height();
  217. const auto from = 0;
  218. const auto length = (horizontal ? width() : height());
  219. const auto alwaysSeekSize = horizontal
  220. ? _st.seekSize.width()
  221. : _st.seekSize.height();
  222. const auto mid = _alwaysDisplayMarker
  223. ? qRound(from
  224. + (alwaysSeekSize / 2.)
  225. + value * (length - alwaysSeekSize))
  226. : qRound(from + value * length);
  227. const auto till = horizontal
  228. ? std::max(mid, qRound(from + receivedTill * length))
  229. : mid;
  230. const auto end = from + length;
  231. const auto activeFg = disabled
  232. ? _st.activeFgDisabled
  233. : _activeFgOverride
  234. ? QBrush(*_activeFgOverride)
  235. : anim::brush(_st.activeFg, _st.activeFgOver, over);
  236. const auto receivedTillFg = _st.receivedTillFg;
  237. const auto inactiveFg = disabled
  238. ? _st.inactiveFgDisabled
  239. : anim::brush(_st.inactiveFg, _st.inactiveFgOver, over);
  240. if (mid > from) {
  241. const auto fromClipRect = horizontal
  242. ? QRect(0, 0, mid, height())
  243. : QRect(0, 0, width(), mid);
  244. const auto till = std::min(mid + radius, end);
  245. const auto fromRect = horizontal
  246. ? QRect(from, (height() - _st.width) / 2, till - from, _st.width)
  247. : QRect((width() - _st.width) / 2, from, _st.width, till - from);
  248. p.setClipRect(fromClipRect);
  249. p.setBrush(horizontal ? activeFg : inactiveFg);
  250. p.drawRoundedRect(fromRect, radius, radius);
  251. }
  252. if (till > mid) {
  253. Assert(horizontal);
  254. auto clipRect = QRect(mid, 0, till - mid, height());
  255. const auto left = std::max(mid - radius, from);
  256. const auto right = std::min(till + radius, end);
  257. const auto rect = QRect(
  258. left,
  259. (height() - _st.width) / 2,
  260. right - left,
  261. _st.width);
  262. p.setClipRect(clipRect);
  263. p.setBrush(receivedTillFg);
  264. p.drawRoundedRect(rect, radius, radius);
  265. }
  266. if (end > till) {
  267. const auto endClipRect = horizontal
  268. ? QRect(till, 0, width() - till, height())
  269. : QRect(0, till, width(), height() - till);
  270. const auto begin = std::max(till - radius, from);
  271. const auto endRect = horizontal
  272. ? QRect(
  273. begin,
  274. (height() - _st.width) / 2,
  275. end - begin,
  276. _st.width)
  277. : QRect(
  278. (width() - _st.width) / 2,
  279. begin,
  280. _st.width,
  281. end - begin);
  282. p.setClipRect(endClipRect);
  283. p.setBrush(horizontal ? inactiveFg : activeFg);
  284. p.drawRoundedRect(endRect, radius, radius);
  285. }
  286. if (!_dividers.empty()) {
  287. p.setClipRect(rect());
  288. for (const auto &divider : _dividers) {
  289. const auto dividerValue = horizontal
  290. ? divider.atValue
  291. : (1. - divider.atValue);
  292. const auto dividerMid = base::SafeRound(from
  293. + dividerValue * length);
  294. const auto &size = divider.size;
  295. const auto rect = horizontal
  296. ? QRect(
  297. dividerMid - size.width() / 2,
  298. (height() - size.height()) / 2,
  299. size.width(),
  300. size.height())
  301. : QRect(
  302. (width() - size.height()) / 2,
  303. dividerMid - size.width() / 2,
  304. size.height(),
  305. size.width());
  306. p.setBrush(((value < dividerValue) == horizontal)
  307. ? inactiveFg
  308. : activeFg);
  309. const auto dividerRadius = size.width() / 2.;
  310. p.drawRoundedRect(rect, dividerRadius, dividerRadius);
  311. }
  312. }
  313. const auto markerSizeRatio = disabled
  314. ? 0.
  315. : (_alwaysDisplayMarker ? 1. : over);
  316. if (markerSizeRatio > 0) {
  317. const auto position = qRound(markerFrom + value * markerLength)
  318. - (horizontal
  319. ? (_st.seekSize.width() / 2)
  320. : (_st.seekSize.height() / 2));
  321. const auto seekButton = horizontal
  322. ? QRect(
  323. position,
  324. (height() - _st.seekSize.height()) / 2,
  325. _st.seekSize.width(),
  326. _st.seekSize.height())
  327. : QRect(
  328. (width() - _st.seekSize.width()) / 2,
  329. position,
  330. _st.seekSize.width(),
  331. _st.seekSize.height());
  332. const auto size = horizontal
  333. ? _st.seekSize.width()
  334. : _st.seekSize.height();
  335. const auto remove = static_cast<int>(
  336. ((1. - markerSizeRatio) * size) / 2.);
  337. if (remove * 2 < size) {
  338. p.setClipRect(rect());
  339. p.setBrush(activeFg);
  340. const auto xshift = horizontal
  341. ? std::max(
  342. seekButton.x() + seekButton.width() - remove - width(),
  343. 0) + std::min(seekButton.x() + remove, 0)
  344. : 0;
  345. const auto yshift = horizontal
  346. ? 0
  347. : std::max(
  348. seekButton.y() + seekButton.height() - remove - height(),
  349. 0) + std::min(seekButton.y() + remove, 0);
  350. p.drawEllipse(seekButton.marginsRemoved(
  351. QMargins(remove, remove, remove, remove)
  352. ).translated(-xshift, -yshift));
  353. }
  354. }
  355. }
  356. } // namespace Ui