Add simple animation for AI Tools button.

This commit is contained in:
John Preston
2026-03-27 22:24:20 +07:00
parent d48ceab73d
commit f73793ce8a
8 changed files with 97 additions and 77 deletions
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="24px" height="24px" viewBox="96.5 111.3 766.5 766.5" xmlns="http://www.w3.org/2000/svg">
<g stroke="none" fill="none" fill-rule="evenodd">
<path d="M 498.706 365.42 C 515.594 363.348 538.563 365.861 548.485 381.651 C 566.807 410.809 576.751 446.661 588.311 479.176 L 637.399 618.626 L 665.78 698.579 C 676.659 729.146 687.226 748.313 682.54 781.481 C 675.846 788.102 668.071 795.055 658.868 797.629 C 651.616 799.698 643.834 798.72 637.319 794.919 C 631.617 791.632 622.75 782.719 620.419 776.738 C 608.192 745.354 594.733 707.965 586.262 675.669 C 569.761 672.672 533.607 673.757 515.5 673.803 L 465.584 673.663 C 429.007 673.61 428.662 668.91 417.869 707.395 C 410.414 728.146 405.333 752.038 396.071 771.94 C 379.585 807.367 355.479 802.277 330.202 783.696 C 330.019 782.164 329.86 780.629 329.726 779.091 C 328.901 769.751 329.769 760.338 332.289 751.306 C 338.538 728.396 348.963 702.053 357.021 679.401 L 394.065 573.806 L 430.927 469.343 C 440.963 440.571 450.771 403.685 469.355 379.052 C 476.858 369.108 486.798 366.389 498.706 365.42 z M 504.968 436.525 C 508.866 440.798 524.908 486.278 526.975 494.054 C 537.563 533.876 560.589 582.995 564.841 623.288 C 546.777 622.864 528.711 622.571 510.642 622.408 C 489.848 622.27 469.054 622.56 448.271 623.279 C 449.463 605.6 468.851 551.806 475.731 531.511 C 486.173 500.704 494.943 466.856 504.968 436.525 z" fill="#FFFFFF"></path>
<path d="M 754.216 524.167 C 763.771 523.439 778.578 525.381 783.67 533.512 C 790.57 544.531 788.595 576.397 788.599 589.29 L 788.562 654.629 C 788.553 691.578 790.329 746.525 787.335 782.106 C 781.652 787.948 775.21 792.333 768.254 796.474 C 752.507 799.502 746.874 792.906 735.396 783.22 C 733.424 762.938 733.05 546.574 736.372 537.446 C 738.677 531.114 748.456 526.925 754.216 524.167 z" fill="#FFFFFF"></path>
<path d="M 757.56 426.311 C 768.63 427.03 775.49 429.481 785.226 434.328 C 797.887 454.333 796.6 461.896 785.005 482.127 C 777.16 485.341 772.393 487.022 764.241 489.32 C 731.637 497.151 705.869 438.865 757.56 426.311 z" fill="#FFFFFF"></path>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="24px" height="24px" viewBox="96.5 111.3 766.5 766.5" xmlns="http://www.w3.org/2000/svg">
<g stroke="none" fill="none" fill-rule="evenodd">
<path d="M 361.507 181.705 C 372.454 181.635 380.002 234.986 387.127 242.295 C 407.219 262.908 437.857 263.914 463.429 280.408 C 440.851 302.031 406.127 295.636 389.728 315.608 C 381.989 325.033 369.855 367.603 363.046 382.355 C 351.465 373.05 343.119 333.179 332.642 316.843 C 318.951 295.494 264.524 299.57 262.849 280.314 C 270.405 265.304 317.273 260.399 329.146 248.809 C 346.343 232.022 342.909 197.93 361.507 181.705 z" fill="#FFFFFF"></path>
</g>
</svg>

After

Width:  |  Height:  |  Size: 668 B

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="24px" height="24px" viewBox="96.5 111.3 766.5 766.5" xmlns="http://www.w3.org/2000/svg">
<g stroke="none" fill="none" fill-rule="evenodd">
<path d="M 227.159 367.788 C 233.561 369.307 240.228 369.662 246.771 369.89 C 248.488 420.574 264.795 427.64 311.005 436.455 C 310.491 445.011 310.17 446.339 311.45 454.821 C 305.224 455.372 297.895 455.922 291.885 456.866 C 243.817 464.419 258.4 489.944 240.267 514.706 C 237.443 517.55 234.726 520.712 231.065 522.27 C 229.488 521.415 227.953 519.681 227.797 517.859 C 223.678 469.953 209.284 457.548 161.759 455.794 C 162.291 447.329 162.683 442.73 161.662 434.383 C 213.477 429.408 223.054 418.438 227.159 367.788 z" fill="#FFFFFF"></path>
</g>
</svg>

After

Width:  |  Height:  |  Size: 762 B

@@ -1421,27 +1421,15 @@ historySend: SendButton {
sendIconFg: historySendIconFg;
}
historyAiComposeButton: IconButton(historyAttach) {
width: 34px;
height: 34px;
icon: historySendIcon;
iconOver: historySendIconOver;
iconPosition: point(10px, 10px);
width: 28px;
height: 28px;
rippleAreaSize: 28px;
rippleAreaPosition: point(0px, 0px);
rippleAreaSize: 34px;
ripple: defaultRippleAnimationBgOver;
}
historyAiComposeButtonBg: transparent;
historyAiComposeButtonBgOver: transparent;
historyAiComposeButtonTextFg: historyComposeIconFg;
historyAiComposeButtonTextFgOver: historyComposeIconFgOver;
historyAiComposeButtonFont: font(15px semibold);
historyAiComposeButtonTextShift: point(1px, 1px);
historyAiComposeButtonSparkleBigPosition: point(10px, 12px);
historyAiComposeButtonSparkleBigRadius: 3px;
historyAiComposeButtonSparkleSmallPosition: point(16px, 8px);
historyAiComposeButtonSparkleSmallRadius: 2px;
historyAiComposeButtonSparkleStroke: 1px;
historyAiComposeButtonTop: -4px;
historyAiComposeButtonLetters: icon{{ "chat/ai_letters-20x20", historyComposeIconFg }};
historyAiComposeButtonStar1: icon{{ "chat/ai_star1-20x20", historyComposeIconFg }};
historyAiComposeButtonStar2: icon{{ "chat/ai_star2-20x20", historyComposeIconFg }};
historyAiComposeButtonPosition: point(-8px, -4px);
historyAiComposeTooltipSkip: 8px;
historyRecordFrameIndex: 30;
@@ -6274,20 +6274,24 @@ bool HistoryWidget::hasEnoughLinesForAi() const {
}
void HistoryWidget::updateAiButtonVisibility() {
const auto shown = hasEnoughLinesForAi()
&& _send->isVisible()
&& _field->isVisible();
const auto hidden = !hasEnoughLinesForAi()
|| !_send->isVisible()
|| !_field->isVisible();
if (_aiButton->isHidden() == hidden) {
return;
}
const auto shown = !hidden;
_aiButton->setVisible(shown);
if (_aiTooltip) {
if (shown) {
updateAiButtonGeometry();
}
if (_aiTooltip) {
const auto showTooltip = shown
&& !Core::App().settings().readPref<bool>(kAiComposeTooltipHiddenPref);
if (showTooltip) {
updateAiTooltipGeometry();
}
if ((showTooltip != _aiTooltipShown)
if ((_aiTooltipShown != showTooltip)
|| (showTooltip && _aiTooltip->isHidden())) {
_aiTooltipShown = showTooltip;
_aiTooltip->toggleAnimated(showTooltip);
@@ -6299,9 +6303,8 @@ void HistoryWidget::updateAiButtonGeometry() {
if (_aiButton->isHidden()) {
return;
}
_aiButton->move(
_send->x() + _send->width() - _aiButton->width(),
_field->y() + st::historyAiComposeButtonTop);
const auto x = _send->x() + _send->width() - _aiButton->width();
_aiButton->move(QPoint(x, _field->y()) + st::historyAiComposeButtonPosition);
updateAiTooltipGeometry();
}
@@ -14,18 +14,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace HistoryView::Controls {
namespace {
void PaintSparkle(QPainter &p, QPoint center, int radius) {
p.drawLine(
center.x() - radius,
center.y(),
center.x() + radius,
center.y());
p.drawLine(
center.x(),
center.y() - radius,
center.x(),
center.y() + radius);
}
constexpr auto kAnimationDuration = crl::time(640);
} // namespace
@@ -36,6 +25,15 @@ ComposeAiButton::ComposeAiButton(
, _st(st) {
resize(_st.width, _st.height);
setCursor(style::cur_pointer);
shownValue(
) | rpl::on_next([=](bool shown) {
if (shown) {
_animation.start([=] { update(); }, 0., 1., kAnimationDuration);
} else {
_animation.stop();
}
}, lifetime());
}
void ComposeAiButton::paintEvent(QPaintEvent *e) {
@@ -43,37 +41,39 @@ void ComposeAiButton::paintEvent(QPaintEvent *e) {
PainterHighQualityEnabler hq(p);
const auto over = isDown() || isOver() || forceRippled();
const auto bg = over
? st::historyAiComposeButtonBgOver
: st::historyAiComposeButtonBg;
if (bg->c.alpha()) {
p.setPen(Qt::NoPen);
p.setBrush(bg);
p.drawEllipse(rect());
}
paintRipple(p, _st.rippleAreaPosition);
const auto fg = over
? st::historyAiComposeButtonTextFgOver
: st::historyAiComposeButtonTextFg;
auto pen = QPen(fg);
pen.setWidth(st::historyAiComposeButtonSparkleStroke);
pen.setCapStyle(Qt::RoundCap);
p.setPen(pen);
PaintSparkle(
p,
st::historyAiComposeButtonSparkleBigPosition,
st::historyAiComposeButtonSparkleBigRadius);
PaintSparkle(
p,
st::historyAiComposeButtonSparkleSmallPosition,
st::historyAiComposeButtonSparkleSmallRadius);
const auto progress = _animation.value(1.);
auto star1Opacity = 1.;
auto star2Opacity = 1.;
if (progress < 0.25) {
star1Opacity = 1. - (progress / 0.25);
} else if (progress < 0.5) {
star1Opacity = 0.;
star2Opacity = 1. - ((progress - 0.25) / 0.25);
} else if (progress < 0.75) {
star1Opacity = (progress - 0.5) / 0.25;
star2Opacity = 0.;
} else {
star2Opacity = (progress - 0.75) / 0.25;
}
p.setFont(st::historyAiComposeButtonFont);
p.drawText(
rect().translated(st::historyAiComposeButtonTextShift),
Qt::AlignCenter,
u"Ai"_q);
const auto part = [&](const style::icon &icon) {
if (over) {
icon.paintInCenter(p, rect(), st::historyComposeIconFgOver->c);
} else {
icon.paintInCenter(p, rect());
}
};
part(st::historyAiComposeButtonLetters);
if (star1Opacity > 0.) {
p.setOpacity(star1Opacity);
part(st::historyAiComposeButtonStar1);
}
if (star2Opacity > 0.) {
p.setOpacity(star2Opacity);
part(st::historyAiComposeButtonStar2);
}
}
void ComposeAiButton::onStateChanged(State was, StateChangeSource source) {
@@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
#include "ui/effects/animations.h"
#include "ui/widgets/buttons.h"
namespace HistoryView::Controls {
@@ -24,6 +25,7 @@ protected:
private:
const style::IconButton &_st;
Ui::Animations::Simple _animation;
};
@@ -3550,18 +3550,26 @@ void ComposeControls::updateControlsVisibility() {
}
void ComposeControls::updateAiButtonVisibility() {
const auto shown = hasEnoughLinesForAi()
&& _wrap->isVisible()
&& !_recording.current()
&& _field->isVisible();
const auto hidden = !hasEnoughLinesForAi()
|| !_wrap->isVisible()
|| _recording.current()
|| !_field->isVisible();
if (_aiButton->isHidden() == hidden) {
return;
}
const auto shown = !hidden;
_aiButton->setVisible(shown);
if (shown) {
updateAiButtonGeometry();
}
if (_aiTooltip) {
const auto showTooltip = shown
&& !Core::App().settings().readPref<bool>(kAiComposeTooltipHiddenPref);
if (showTooltip) {
updateAiTooltipGeometry();
}
if (showTooltip != _aiTooltipShown) {
if ((_aiTooltipShown != showTooltip)
|| (showTooltip && _aiTooltip->isHidden())) {
_aiTooltipShown = showTooltip;
_aiTooltip->toggleAnimated(showTooltip);
}
@@ -3572,9 +3580,8 @@ void ComposeControls::updateAiButtonGeometry() {
if (_aiButton->isHidden()) {
return;
}
_aiButton->move(
_send->x() + (_send->width() - _aiButton->width()) / 2,
_field->y() + st::historyAiComposeButtonTop);
const auto x = _send->x() + _send->width() - _aiButton->width();
_aiButton->move(QPoint(x, _field->y()) + st::historyAiComposeButtonPosition);
updateAiTooltipGeometry();
}