diff --git a/CMakeLists.txt b/CMakeLists.txt index bb67d85..4448b46 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -161,6 +161,8 @@ PRIVATE ui/toast/toast_manager.h ui/toast/toast_widget.cpp ui/toast/toast_widget.h + ui/toasts/common_toasts.cpp + ui/toasts/common_toasts.h ui/widgets/box_content_divider.cpp ui/widgets/box_content_divider.h ui/widgets/buttons.cpp @@ -197,6 +199,8 @@ PRIVATE ui/widgets/popup_menu.h ui/widgets/rp_window.cpp ui/widgets/rp_window.h + ui/widgets/separate_panel.cpp + ui/widgets/separate_panel.h ui/widgets/scroll_area.cpp ui/widgets/scroll_area.h ui/widgets/side_bar_button.cpp diff --git a/ui/integration.cpp b/ui/integration.cpp index 28ffb7c..e34243d 100644 --- a/ui/integration.cpp +++ b/ui/integration.cpp @@ -143,4 +143,12 @@ QString Integration::phraseFormattingSpoiler() { return "Spoiler"; } +QString Integration::phraseButtonOk() { + return "OK"; +} + +QString Integration::phraseButtonCancel() { + return "Cancel"; +} + } // namespace Ui diff --git a/ui/integration.h b/ui/integration.h index c981689..67a953e 100644 --- a/ui/integration.h +++ b/ui/integration.h @@ -8,6 +8,8 @@ #include "base/basic_types.h" +#include + #include // Methods that must be implemented outside lib_ui. @@ -71,6 +73,8 @@ public: [[nodiscard]] virtual QString phraseFormattingStrikeOut(); [[nodiscard]] virtual QString phraseFormattingMonospace(); [[nodiscard]] virtual QString phraseFormattingSpoiler(); + [[nodiscard]] virtual QString phraseButtonOk(); + [[nodiscard]] virtual QString phraseButtonCancel(); }; diff --git a/ui/layers/layers.style b/ui/layers/layers.style index 84317e6..75675c1 100644 --- a/ui/layers/layers.style +++ b/ui/layers/layers.style @@ -157,3 +157,43 @@ boxLoadingSize: 20px; boxDividerTop: icon {{ "box_divider_top", boxDividerFg }}; boxDividerBottom: icon {{ "box_divider_bottom", boxDividerFg }}; boxDividerHeight: 10px; + +separatePanelBorderCacheSize: 60px; +separatePanelTitleHeight: 62px; +separatePanelClose: IconButton(boxTitleClose) { + width: 60px; + height: 60px; + + rippleAreaPosition: point(8px, 8px); + rippleAreaSize: 44px; + ripple: RippleAnimation(defaultRippleAnimation) { + color: windowBgOver; + } +} +separatePanelTitleFont: font(18px semibold); +separatePanelTitle: FlatLabel(defaultFlatLabel) { + textFg: boxTitleFg; + maxHeight: 26px; + style: TextStyle(defaultTextStyle) { + font: separatePanelTitleFont; + linkFont: separatePanelTitleFont; + linkFontOver: font(18px semibold underline); + } +} +separatePanelTitleTop: 18px; +separatePanelTitleLeft: 22px; +separatePanelTitleSkip: 0px; +separatePanelBack: IconButton(separatePanelClose) { + icon: icon {{ "box_button_back", boxTitleCloseFg }}; + iconOver: icon {{ "box_button_back", boxTitleCloseFgOver }}; +} +separatePanelDuration: 150; + +webviewDialogButton: defaultBoxButton; +webviewDialogSubmit: RoundButton(defaultActiveButton) { + width: -48px; + height: 34px; + textTop: 7px; + font: font(14px semibold); +} +webviewDialogPadding: margins(8px, 12px, 15px, 12px); diff --git a/ui/toasts/common_toasts.cpp b/ui/toasts/common_toasts.cpp new file mode 100644 index 0000000..0f06547 --- /dev/null +++ b/ui/toasts/common_toasts.cpp @@ -0,0 +1,30 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#include "ui/toasts/common_toasts.h" + +#include "ui/toast/toast.h" +#include "styles/style_widgets.h" + +namespace Ui { + +base::weak_ptr ShowMultilineToast( + MultilineToastArgs &&args) { + auto config = Ui::Toast::Config{ + .text = std::move(args.text), + .st = &st::defaultMultilineToast, + .durationMs = (args.duration + ? args.duration + : Ui::Toast::kDefaultDuration), + .multiline = true, + }; + return args.parentOverride + ? Ui::Toast::Show(args.parentOverride, std::move(config)) + : Ui::Toast::Show(std::move(config)); +} + +} // namespace Ui diff --git a/ui/toasts/common_toasts.h b/ui/toasts/common_toasts.h new file mode 100644 index 0000000..7384c0a --- /dev/null +++ b/ui/toasts/common_toasts.h @@ -0,0 +1,27 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#pragma once + +#include "ui/text/text_entity.h" +#include "base/weak_ptr.h" + +namespace Ui { +namespace Toast { +class Instance; +} // namespace Toast + +struct MultilineToastArgs { + QWidget *parentOverride = nullptr; + TextWithEntities text; + crl::time duration = 0; +}; + +base::weak_ptr ShowMultilineToast( + MultilineToastArgs &&args); + +} // namespace Ui diff --git a/ui/ui_utility.h b/ui/ui_utility.h index f7f6fb1..80764f9 100644 --- a/ui/ui_utility.h +++ b/ui/ui_utility.h @@ -10,7 +10,9 @@ #include "ui/rect_part.h" #include "ui/integration.h" +#include #include +#include class QPixmap; class QImage; diff --git a/ui/widgets/input_fields.h b/ui/widgets/input_fields.h index b86bbac..01fd43e 100644 --- a/ui/widgets/input_fields.h +++ b/ui/widgets/input_fields.h @@ -17,6 +17,8 @@ #include #include +#include + class QTouchEvent; class Painter; diff --git a/ui/widgets/separate_panel.cpp b/ui/widgets/separate_panel.cpp new file mode 100644 index 0000000..9cfcbb4 --- /dev/null +++ b/ui/widgets/separate_panel.cpp @@ -0,0 +1,641 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#include "ui/widgets/separate_panel.h" + +#include "ui/widgets/shadow.h" +#include "ui/widgets/buttons.h" +#include "ui/widgets/labels.h" +#include "ui/wrap/padding_wrap.h" +#include "ui/wrap/fade_wrap.h" +#include "ui/toasts/common_toasts.h" +#include "ui/widgets/tooltip.h" +#include "ui/platform/ui_platform_utility.h" +#include "ui/layers/layer_widget.h" +#include "styles/style_widgets.h" +#include "styles/style_layers.h" +#include "styles/palette.h" +#include "base/debug_log.h" + +#include +#include +#include + +namespace Ui { + +SeparatePanel::SeparatePanel() +: _close(this, st::separatePanelClose) +, _back(this, object_ptr(this, st::separatePanelBack)) +, _body(this) { + setMouseTracking(true); + setWindowIcon(QGuiApplication::windowIcon()); + initControls(); + initLayout(); +} + +void SeparatePanel::setTitle(rpl::producer title) { + _title.create(this, std::move(title), st::separatePanelTitle); + _title->setAttribute(Qt::WA_TransparentForMouseEvents); + _title->show(); + updateTitleGeometry(width()); +} + +void SeparatePanel::initControls() { + widthValue( + ) | rpl::start_with_next([=](int width) { + _back->moveToLeft(_padding.left(), _padding.top()); + _close->moveToRight(_padding.right(), _padding.top()); + if (_title) { + updateTitleGeometry(width); + } + }, lifetime()); + + _back->toggledValue( + ) | rpl::start_with_next([=](bool toggled) { + _titleLeft.start( + [=] { updateTitlePosition(); }, + toggled ? 0. : 1., + toggled ? 1. : 0., + st::fadeWrapDuration); + }, _back->lifetime()); + _back->hide(anim::type::instant); + _titleLeft.stop(); +} + +void SeparatePanel::updateTitleGeometry(int newWidth) { + _title->resizeToWidth(newWidth + - _padding.left() - _back->width() + - _padding.right() - _close->width()); + updateTitlePosition(); +} + +void SeparatePanel::updateTitlePosition() { + if (!_title) { + return; + } + const auto progress = _titleLeft.value(_back->toggled() ? 1. : 0.); + const auto left = anim::interpolate( + st::separatePanelTitleLeft, + _back->width() + st::separatePanelTitleSkip, + progress); + _title->moveToLeft( + _padding.left() + left, + _padding.top() + st::separatePanelTitleTop); +} + +rpl::producer<> SeparatePanel::backRequests() const { + return rpl::merge( + _back->entity()->clicks() | rpl::to_empty, + _synteticBackRequests.events()); +} + +rpl::producer<> SeparatePanel::closeRequests() const { + return rpl::merge( + _close->clicks() | rpl::to_empty, + _userCloseRequests.events()); +} + +rpl::producer<> SeparatePanel::closeEvents() const { + return _closeEvents.events(); +} + +void SeparatePanel::setBackAllowed(bool allowed) { + if (allowed != _back->toggled()) { + _back->toggle(allowed, anim::type::normal); + } +} + +void SeparatePanel::setHideOnDeactivate(bool hideOnDeactivate) { + _hideOnDeactivate = hideOnDeactivate; + if (!_hideOnDeactivate) { + showAndActivate(); + } else if (!isActiveWindow()) { + LOG(("Export Info: Panel Hide On Inactive Change.")); + hideGetDuration(); + } +} + +void SeparatePanel::showAndActivate() { + if (isHidden()) { + while (const auto widget = QApplication::activePopupWidget()) { + if (!widget->close()) { + break; + } + } + } + toggleOpacityAnimation(true); + raise(); + setWindowState(windowState() | Qt::WindowActive); + activateWindow(); + setFocus(); +} + +void SeparatePanel::keyPressEvent(QKeyEvent *e) { + if (e->key() == Qt::Key_Escape) { + crl::on_main(this, [=] { + if (_back->toggled()) { + _synteticBackRequests.fire({}); + } else { + _userCloseRequests.fire({}); + } + }); + } + return RpWidget::keyPressEvent(e); +} + +bool SeparatePanel::eventHook(QEvent *e) { + if (e->type() == QEvent::WindowDeactivate && _hideOnDeactivate) { + LOG(("Export Info: Panel Hide On Inactive Window.")); + hideGetDuration(); + } + return RpWidget::eventHook(e); +} + +void SeparatePanel::initLayout() { + setWindowFlags(Qt::WindowFlags(Qt::FramelessWindowHint) + | Qt::WindowStaysOnTopHint + | Qt::NoDropShadowWindowHint + | Qt::Dialog); + setAttribute(Qt::WA_MacAlwaysShowToolWindow); + setAttribute(Qt::WA_NoSystemBackground, true); + setAttribute(Qt::WA_TranslucentBackground, true); + + createBorderImage(); + style::PaletteChanged( + ) | rpl::start_with_next([=] { + createBorderImage(); + Ui::ForceFullRepaint(this); + }, lifetime()); + + Ui::Platform::InitOnTopPanel(this); +} + +void SeparatePanel::createBorderImage() { + const auto shadowPadding = st::callShadow.extend; + const auto cacheSize = st::separatePanelBorderCacheSize; + auto cache = QImage( + cacheSize * style::DevicePixelRatio(), + cacheSize * style::DevicePixelRatio(), + QImage::Format_ARGB32_Premultiplied); + cache.setDevicePixelRatio(style::DevicePixelRatio()); + cache.fill(Qt::transparent); + { + Painter p(&cache); + auto inner = QRect(0, 0, cacheSize, cacheSize).marginsRemoved( + shadowPadding); + Ui::Shadow::paint(p, inner, cacheSize, st::callShadow); + p.setCompositionMode(QPainter::CompositionMode_Source); + p.setBrush(st::windowBg); + p.setPen(Qt::NoPen); + PainterHighQualityEnabler hq(p); + p.drawRoundedRect( + myrtlrect(inner), + st::callRadius, + st::callRadius); + } + _borderParts = Ui::PixmapFromImage(std::move(cache)); +} + +void SeparatePanel::toggleOpacityAnimation(bool visible) { + if (_visible == visible) { + return; + } + + _visible = visible; + if (_useTransparency) { + if (_animationCache.isNull()) { + showControls(); + _animationCache = Ui::GrabWidget(this); + hideChildren(); + } + _opacityAnimation.start( + [this] { opacityCallback(); }, + _visible ? 0. : 1., + _visible ? 1. : 0., + st::separatePanelDuration, + _visible ? anim::easeOutCirc : anim::easeInCirc); + } + if (isHidden() && _visible) { + show(); + } +} + +void SeparatePanel::opacityCallback() { + update(); + if (!_visible && !_opacityAnimation.animating()) { + finishAnimating(); + } +} + +void SeparatePanel::finishAnimating() { + _animationCache = QPixmap(); + if (_visible) { + showControls(); + if (_inner) { + _inner->setFocus(); + } + } else { + finishClose(); + } +} + +void SeparatePanel::showControls() { + showChildren(); + if (!_back->toggled()) { + _back->setVisible(false); + } +} + +void SeparatePanel::finishClose() { + hide(); + crl::on_main(this, [=] { + if (isHidden() && !_visible && !_opacityAnimation.animating()) { + LOG(("Export Info: Panel Closed.")); + _closeEvents.fire({}); + } + }); +} + +int SeparatePanel::hideGetDuration() { + LOG(("Export Info: Panel Hide Requested.")); + toggleOpacityAnimation(false); + if (_animationCache.isNull()) { + finishClose(); + return 0; + } + return st::separatePanelDuration; +} + +void SeparatePanel::showBox( + object_ptr box, + Ui::LayerOptions options, + anim::type animated) { + ensureLayerCreated(); + _layer->showBox(std::move(box), options, animated); +} + +void SeparatePanel::showToast(const TextWithEntities &text) { + Ui::ShowMultilineToast({ + .parentOverride = this, + .text = text, + }); +} + +void SeparatePanel::ensureLayerCreated() { + if (_layer) { + return; + } + _layer = base::make_unique_q(_body); + _layer->setHideByBackgroundClick(false); + _layer->move(0, 0); + _body->sizeValue( + ) | rpl::start_with_next([=](QSize size) { + _layer->resize(size); + }, _layer->lifetime()); + _layer->hideFinishEvents( + ) | rpl::filter([=] { + return _layer != nullptr; // Last hide finish is sent from destructor. + }) | rpl::start_with_next([=] { + destroyLayer(); + }, _layer->lifetime()); +} + +void SeparatePanel::destroyLayer() { + if (!_layer) { + return; + } + + auto layer = base::take(_layer); + const auto resetFocus = Ui::InFocusChain(layer); + if (resetFocus) { + setFocus(); + } + layer = nullptr; +} + +void SeparatePanel::showInner(base::unique_qptr inner) { + Expects(!size().isEmpty()); + + _inner = std::move(inner); + _inner->setParent(_body); + _inner->move(0, 0); + _body->sizeValue( + ) | rpl::start_with_next([=](QSize size) { + _inner->resize(size); + }, _inner->lifetime()); + _inner->show(); + + if (_layer) { + _layer->raise(); + } + + showAndActivate(); +} + +void SeparatePanel::focusInEvent(QFocusEvent *e) { + crl::on_main(this, [=] { + if (_layer) { + _layer->setInnerFocus(); + } else if (_inner && !_inner->isHidden()) { + _inner->setFocus(); + } + }); +} + +void SeparatePanel::setInnerSize(QSize size) { + Expects(!size.isEmpty()); + + if (rect().isEmpty()) { + initGeometry(size); + } else { + updateGeometry(size); + } +} + +QRect SeparatePanel::innerGeometry() const { + return _body->geometry(); +} + +void SeparatePanel::initGeometry(QSize size) { + const auto active = QApplication::activeWindow(); + const auto available = !active + ? QGuiApplication::primaryScreen()->availableGeometry() + : active->screen()->availableGeometry(); + const auto parentGeometry = (active + && active->isVisible() + && active->isActiveWindow()) + ? active->geometry() + : available; + + auto center = parentGeometry.center(); + if (size.height() > available.height()) { + size = QSize(size.width(), available.height()); + } + if (center.x() + size.width() / 2 + > available.x() + available.width()) { + center.setX( + available.x() + available.width() - size.width() / 2); + } + if (center.x() - size.width() / 2 < available.x()) { + center.setX(available.x() + size.width() / 2); + } + if (center.y() + size.height() / 2 + > available.y() + available.height()) { + center.setY( + available.y() + available.height() - size.height() / 2); + } + if (center.y() - size.height() / 2 < available.y()) { + center.setY(available.y() + size.height() / 2); + } + _useTransparency = Ui::Platform::TranslucentWindowsSupported(center); + _padding = _useTransparency + ? st::callShadow.extend + : style::margins( + st::lineWidth, + st::lineWidth, + st::lineWidth, + st::lineWidth); + setAttribute(Qt::WA_OpaquePaintEvent, !_useTransparency); + const auto rect = [&] { + const QRect initRect(QPoint(), size); + return initRect.translated(center - initRect.center()).marginsAdded(_padding); + }(); + setGeometry(rect); + setMinimumSize(rect.size()); + setMaximumSize(rect.size()); + updateControlsGeometry(); +} + +void SeparatePanel::updateGeometry(QSize size) { + const auto rect = QRect( + x(), + y(), + _padding.left() + size.width() + _padding.right(), + _padding.top() + size.height() + _padding.bottom()); + setGeometry(rect); + setMinimumSize(rect.size()); + setMaximumSize(rect.size()); + updateControlsGeometry(); + update(); +} + +void SeparatePanel::resizeEvent(QResizeEvent *e) { + updateControlsGeometry(); +} + +void SeparatePanel::updateControlsGeometry() { + const auto top = _padding.top() + st::separatePanelTitleHeight; + _body->setGeometry( + _padding.left(), + top, + width() - _padding.left() - _padding.right(), + height() - top - _padding.bottom()); +} + +void SeparatePanel::paintEvent(QPaintEvent *e) { + Painter p(this); + if (!_animationCache.isNull()) { + auto opacity = _opacityAnimation.value(_visible ? 1. : 0.); + if (!_opacityAnimation.animating()) { + finishAnimating(); + if (isHidden()) return; + } else { + p.setOpacity(opacity); + + PainterHighQualityEnabler hq(p); + auto marginRatio = (1. - opacity) / 5; + auto marginWidth = qRound(width() * marginRatio); + auto marginHeight = qRound(height() * marginRatio); + p.drawPixmap( + rect().marginsRemoved( + QMargins( + marginWidth, + marginHeight, + marginWidth, + marginHeight)), + _animationCache, + QRect(QPoint(0, 0), _animationCache.size())); + return; + } + } + + if (_useTransparency) { + paintShadowBorder(p); + } else { + paintOpaqueBorder(p); + } +} + +void SeparatePanel::paintShadowBorder(Painter &p) const { + const auto factor = style::DevicePixelRatio(); + const auto size = st::separatePanelBorderCacheSize; + const auto part1 = size / 3; + const auto part2 = size - part1; + const auto corner = QSize(part1, part1) * factor; + + const auto topleft = QRect(QPoint(0, 0), corner); + p.drawPixmap(QRect(0, 0, part1, part1), _borderParts, topleft); + + const auto topright = QRect(QPoint(part2, 0) * factor, corner); + p.drawPixmap( + QRect(width() - part1, 0, part1, part1), + _borderParts, + topright); + + const auto bottomleft = QRect(QPoint(0, part2) * factor, corner); + p.drawPixmap( + QRect(0, height() - part1, part1, part1), + _borderParts, + bottomleft); + + const auto bottomright = QRect(QPoint(part2, part2) * factor, corner); + p.drawPixmap( + QRect(width() - part1, height() - part1, part1, part1), + _borderParts, + bottomright); + + const auto left = QRect( + QPoint(0, part1) * factor, + QSize(_padding.left(), part2 - part1) * factor); + p.drawPixmap( + QRect(0, part1, _padding.left(), height() - 2 * part1), + _borderParts, + left); + + const auto top = QRect( + QPoint(part1, 0) * factor, + QSize(part2 - part1, _padding.top() + st::callRadius) * factor); + p.drawPixmap( + QRect( + part1, + 0, + width() - 2 * part1, + _padding.top() + st::callRadius), + _borderParts, + top); + + const auto right = QRect( + QPoint(size - _padding.right(), part1) * factor, + QSize(_padding.right(), part2 - part1) * factor); + p.drawPixmap( + QRect( + width() - _padding.right(), + part1, + _padding.right(), + height() - 2 * part1), + _borderParts, + right); + + const auto bottom = QRect( + QPoint(part1, size - _padding.bottom() - st::callRadius) * factor, + QSize(part2 - part1, _padding.bottom() + st::callRadius) * factor); + p.drawPixmap( + QRect( + part1, + height() - _padding.bottom() - st::callRadius, + width() - 2 * part1, + _padding.bottom() + st::callRadius), + _borderParts, + bottom); + + p.fillRect( + _padding.left(), + _padding.top() + st::callRadius, + width() - _padding.left() - _padding.right(), + height() - _padding.top() - _padding.bottom() - 2 * st::callRadius, + st::windowBg); +} + +void SeparatePanel::paintOpaqueBorder(Painter &p) const { + const auto border = st::windowShadowFgFallback; + p.fillRect(0, 0, width(), _padding.top(), border); + p.fillRect( + myrtlrect( + 0, + _padding.top(), + _padding.left(), + height() - _padding.top()), + border); + p.fillRect( + myrtlrect( + width() - _padding.right(), + _padding.top(), + _padding.right(), + height() - _padding.top()), + border); + p.fillRect( + _padding.left(), + height() - _padding.bottom(), + width() - _padding.left() - _padding.right(), + _padding.bottom(), + border); + + p.fillRect( + _padding.left(), + _padding.top(), + width() - _padding.left() - _padding.right(), + height() - _padding.top() - _padding.bottom(), + st::windowBg); +} + +void SeparatePanel::closeEvent(QCloseEvent *e) { + e->ignore(); + _userCloseRequests.fire({}); +} + +void SeparatePanel::mousePressEvent(QMouseEvent *e) { + auto dragArea = myrtlrect( + _padding.left(), + _padding.top(), + width() - _padding.left() - _padding.right(), + st::separatePanelTitleHeight); + if (e->button() == Qt::LeftButton) { + if (dragArea.contains(e->pos())) { + const auto dragViaSystem = [&] { + if (windowHandle()->startSystemMove()) { + return true; + } + return false; + }(); + if (!dragViaSystem) { + _dragging = true; + _dragStartMousePosition = e->globalPos(); + _dragStartMyPosition = QPoint(x(), y()); + } + } else if (!rect().contains(e->pos()) && _hideOnDeactivate) { + LOG(("Export Info: Panel Hide On Click.")); + hideGetDuration(); + } + } +} + +void SeparatePanel::mouseMoveEvent(QMouseEvent *e) { + if (_dragging) { + if (!(e->buttons() & Qt::LeftButton)) { + _dragging = false; + } else { + move(_dragStartMyPosition + + (e->globalPos() - _dragStartMousePosition)); + } + } +} + +void SeparatePanel::mouseReleaseEvent(QMouseEvent *e) { + if (e->button() == Qt::LeftButton && _dragging) { + _dragging = false; + } +} + +void SeparatePanel::leaveEventHook(QEvent *e) { + Ui::Tooltip::Hide(); +} + +void SeparatePanel::leaveToChildEvent(QEvent *e, QWidget *child) { + Ui::Tooltip::Hide(); +} + +} // namespace Ui diff --git a/ui/widgets/separate_panel.h b/ui/widgets/separate_panel.h new file mode 100644 index 0000000..49a5ab8 --- /dev/null +++ b/ui/widgets/separate_panel.h @@ -0,0 +1,113 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#pragma once + +#include "ui/rp_widget.h" +#include "ui/effects/animations.h" +#include "ui/layers/layer_widget.h" +#include "ui/text/text_entity.h" + +class Painter; + +namespace Ui { +class BoxContent; +class IconButton; +class LayerStackWidget; +class FlatLabel; +template +class FadeWrapScaled; +} // namespace Ui + +namespace Ui { + +class SeparatePanel final : public Ui::RpWidget { +public: + SeparatePanel(); + + void setTitle(rpl::producer title); + void setInnerSize(QSize size); + [[nodiscard]] QRect innerGeometry() const; + + void setHideOnDeactivate(bool hideOnDeactivate); + void showAndActivate(); + int hideGetDuration(); + + void showInner(base::unique_qptr inner); + void showBox( + object_ptr box, + Ui::LayerOptions options, + anim::type animated); + void showToast(const TextWithEntities &text); + void destroyLayer(); + + [[nodiscard]] rpl::producer<> backRequests() const; + [[nodiscard]] rpl::producer<> closeRequests() const; + [[nodiscard]] rpl::producer<> closeEvents() const; + void setBackAllowed(bool allowed); + +protected: + void paintEvent(QPaintEvent *e) override; + void closeEvent(QCloseEvent *e) override; + void resizeEvent(QResizeEvent *e) override; + void focusInEvent(QFocusEvent *e) override; + void mousePressEvent(QMouseEvent *e) override; + void mouseReleaseEvent(QMouseEvent *e) override; + void mouseMoveEvent(QMouseEvent *e) override; + void leaveEventHook(QEvent *e) override; + void leaveToChildEvent(QEvent *e, QWidget *child) override; + void keyPressEvent(QKeyEvent *e) override; + bool eventHook(QEvent *e) override; + +private: + void initControls(); + void initLayout(); + void initGeometry(QSize size); + void updateGeometry(QSize size); + void showControls(); + void updateControlsGeometry(); + void createBorderImage(); + void opacityCallback(); + void ensureLayerCreated(); + + void updateTitleGeometry(int newWidth); + void updateTitlePosition(); + void paintShadowBorder(Painter &p) const; + void paintOpaqueBorder(Painter &p) const; + + void toggleOpacityAnimation(bool visible); + void finishAnimating(); + void finishClose(); + + object_ptr _close; + object_ptr _title = { nullptr }; + object_ptr> _back; + object_ptr _body; + base::unique_qptr _inner; + base::unique_qptr _layer = { nullptr }; + rpl::event_stream<> _synteticBackRequests; + rpl::event_stream<> _userCloseRequests; + rpl::event_stream<> _closeEvents; + + bool _hideOnDeactivate = false; + bool _useTransparency = true; + style::margins _padding; + + bool _dragging = false; + QPoint _dragStartMousePosition; + QPoint _dragStartMyPosition; + + Ui::Animations::Simple _titleLeft; + bool _visible = false; + + Ui::Animations::Simple _opacityAnimation; + QPixmap _animationCache; + QPixmap _borderParts; + +}; + +} // namespace Ui diff --git a/ui/widgets/widgets.style b/ui/widgets/widgets.style index fd3bb69..ce1032e 100644 --- a/ui/widgets/widgets.style +++ b/ui/widgets/widgets.style @@ -1445,6 +1445,10 @@ defaultToast: Toast { durationSlide: 160; } +defaultMultilineToast: Toast(defaultToast) { + minWidth: msgMinWidth; +} + shakeShift: 4px; // Windows specific title