diff --git a/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.cpp b/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.cpp index 3efd0154c3..baff887947 100644 --- a/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.cpp +++ b/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.cpp @@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/effects/radial_animation.h" #include "ui/effects/ripple_animation.h" #include "ui/layers/box_content.h" +#include "ui/layers/standalone_layer_stack.h" #include "ui/round_rect.h" #include "ui/style/style_core_palette.h" #include "ui/text/format_values.h" @@ -901,10 +902,21 @@ Panel::Panel(Args &&args) .parent = _externalPanelParent.get(), .menuSt = &st::botWebViewMenu, })) +, _externalLayer(_externalShell + ? std::make_unique() + : nullptr) , _fullscreen(args.fullscreen) , _allowClipboardRead(args.allowClipboardRead) { if (_externalShell) { _widget->setAttribute(Qt::WA_DontShowOnScreen); + _externalLayer->boxAdded( + ) | rpl::on_next([=] { + setExternalShellBlocked(true); + }, _widget->lifetime()); + _externalLayer->boxClosed( + ) | rpl::on_next([=] { + setExternalShellBlocked(false); + }, _widget->lifetime()); } _widget->setWindowFlag(Qt::WindowStaysOnTopHint, false); _widget->setInnerSize(st::botWebViewPanelSize, true); @@ -1006,6 +1018,7 @@ Panel::Panel(Args &&args) Panel::~Panel() { base::take(_webview); _progress = nullptr; + _externalLayer = nullptr; _externalWebviewParent = nullptr; _widget = nullptr; _externalPanelParent = nullptr; @@ -2943,23 +2956,7 @@ void Panel::showBox( LayerOptions options, anim::type animated) { if (_externalShell) { - setExternalShellBlocked(true); - auto panel = std::make_unique(); - panel->setWindowFlag(Qt::WindowStaysOnTopHint, false); - panel->setInnerSize(st::botWebViewPanelSize, true); - const auto rawPanel = panel.get(); - const auto rawBox = box.data(); - rawBox->boxClosing( - ) | rpl::on_next([=] { - finishExternalBox(rawPanel); - }, rawPanel->lifetime()); - rawPanel->closeEvents( - ) | rpl::on_next([=] { - finishExternalBox(rawPanel); - }, rawPanel->lifetime()); - rawPanel->showBox(std::move(box), options, animated); - rawPanel->showAndActivate(); - _externalBoxes.push_back(std::move(panel)); + _externalLayer->showBox(std::move(box), options, animated); return; } if (const auto widget = _webview ? _webview->window.widget() : nullptr) { @@ -3020,18 +3017,6 @@ void Panel::showBox( anim::type::normal); } -void Panel::finishExternalBox(not_null panel) { - const auto i = ranges::find_if(_externalBoxes, [&](const auto &entry) { - return entry.get() == panel.get(); - }); - if (i == end(_externalBoxes)) { - return; - } - panel->hideGetDuration(); - _externalBoxes.erase(i); - setExternalShellBlocked(false); -} - void Panel::showToast(TextWithEntities &&text) { _widget->showToast(std::move(text)); } @@ -3041,6 +3026,10 @@ not_null Panel::toastParent() const { } void Panel::hideLayer(anim::type animated) { + if (_externalShell && _externalLayer) { + _externalLayer->hideLayers(animated); + return; + } _widget->hideLayer(animated); } diff --git a/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.h b/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.h index b42f304852..6e222f6f27 100644 --- a/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.h +++ b/Telegram/SourceFiles/ui/chat/attach/attach_bot_webview.h @@ -25,6 +25,7 @@ class FlatLabel; class BoxContent; class RpWidget; class SeparatePanel; +class StandaloneLayerStack; enum class LayerOption; using LayerOptions = base::flags; } // namespace Ui @@ -302,7 +303,6 @@ private: [[nodiscard]] QRect progressRect() const; void setupProgressGeometry(); void layoutButtons(); - void finishExternalBox(not_null panel); Webview::StorageId _storageId; const not_null _delegate; @@ -322,7 +322,7 @@ private: std::unique_ptr _externalPanelParent; std::unique_ptr _widget; std::unique_ptr _webview; - std::vector> _externalBoxes; + std::unique_ptr _externalLayer; std::unique_ptr _externalWebviewParent; std::unique_ptr _webviewBottom; QPointer _webviewBottomLabel; diff --git a/Telegram/SourceFiles/ui/layers/standalone_layer_stack.cpp b/Telegram/SourceFiles/ui/layers/standalone_layer_stack.cpp new file mode 100644 index 0000000000..3f15cc97a6 --- /dev/null +++ b/Telegram/SourceFiles/ui/layers/standalone_layer_stack.cpp @@ -0,0 +1,199 @@ +/* +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/layers/standalone_layer_stack.h" + +#include "base/event_filter.h" +#include "ui/layers/box_content.h" +#include "ui/layers/box_layer_widget.h" +#include "ui/layers/show.h" +#include "ui/widgets/separate_panel.h" +#include "base/debug_log.h" +#include "styles/style_layers.h" + +#include +#include + +namespace Ui { +namespace { + +class StandaloneShow final : public Show { +public: + explicit StandaloneShow(base::weak_ptr weak); + + void showOrHideBoxOrLayer( + std::variant< + v::null_t, + object_ptr, + std::unique_ptr> &&layer, + LayerOptions options, + anim::type animated) const override; + [[nodiscard]] not_null toastParent() const override; + [[nodiscard]] bool valid() const override; + operator bool() const override; + +private: + const base::weak_ptr _stack; + +}; + +StandaloneShow::StandaloneShow(base::weak_ptr weak) +: _stack(std::move(weak)) { +} + +void StandaloneShow::showOrHideBoxOrLayer( + std::variant< + v::null_t, + object_ptr, + std::unique_ptr> &&layer, + LayerOptions options, + anim::type animated) const { + using UniqueLayer = std::unique_ptr; + using ObjectBox = object_ptr; + const auto stack = _stack.get(); + if (!stack) { + return; + } + if (auto box = std::get_if(&layer)) { + stack->showBox(std::move(*box), options, animated); + } else if (std::get_if(&layer)) { + LOG(("StandaloneLayerStack: showLayer(LayerWidget) " + "is not supported, ignoring.")); + } else { + stack->hideLayers(animated); + } +} + +not_null StandaloneShow::toastParent() const { + const auto stack = _stack.get(); + const auto parent = stack ? stack->currentPanel() : nullptr; + Ensures(parent != nullptr); + return parent; +} + +bool StandaloneShow::valid() const { + return (_stack.get() != nullptr); +} + +StandaloneShow::operator bool() const { + return valid(); +} + +} // namespace + +StandaloneLayerStack::StandaloneLayerStack() = default; + +StandaloneLayerStack::~StandaloneLayerStack() { + for (auto &entry : base::take(_entries)) { + if (entry.box) { + entry.box->setClosing(); + } + } +} + +void StandaloneLayerStack::showBox( + object_ptr box, + LayerOptions options, + anim::type animated) { + if (options & LayerOption::CloseOther) { + hideLayers(anim::type::instant); + } else if (!_entries.empty()) { + _entries.back().panel->hideForStacking(); + } + auto panel = base::make_unique_q(); + panel->setWindowFlag(Qt::WindowStaysOnTopHint, false); + panel->setAttribute(Qt::WA_DeleteOnClose, false); + panel->setTitleHeight(0); + panel->setCloseAllowed(false); + + const auto rawPanel = panel.get(); + auto layerWidget = base::make_unique_q( + rawPanel, + this, + std::move(box)); + const auto rawBox = layerWidget.get(); + + rawPanel->setInnerSize(rawBox->size().isEmpty() + ? QSize(st::boxWideWidth, 1) + : rawBox->size()); + + rawBox->sizeValue( + ) | rpl::on_next([rawPanel](QSize size) { + if (!size.isEmpty()) { + rawPanel->setInnerSize(size); + } + }, rawBox->lifetime()); + + rawBox->setClosedCallback([=] { + closeEntry(rawPanel); + }); + rawPanel->closeRequests( + ) | rpl::on_next([=] { + closeEntry(rawPanel); + }, rawPanel->lifetime()); + rawPanel->closeEvents( + ) | rpl::on_next([=] { + closeEntry(rawPanel); + }, rawPanel->lifetime()); + + rawPanel->showInner(std::move(layerWidget)); + rawPanel->showAndActivate(); + + _entries.push_back({ std::move(panel), rawBox }); + _boxAdded.fire({}); +} + +void StandaloneLayerStack::hideLayers(anim::type animated) { + auto taken = base::take(_entries); + for (auto &entry : taken) { + if (entry.box) { + entry.box->setClosing(); + } + entry.panel->hideGetDuration(); + _boxClosed.fire({}); + } +} + +ShowFactory StandaloneLayerStack::showFactory() { + const auto weak = base::make_weak(this); + return [weak]() -> ShowPtr { + return std::make_shared(weak); + }; +} + +std::optional StandaloneLayerStack::layerOuterSize() { + if (const auto screen = QGuiApplication::primaryScreen()) { + return screen->availableGeometry().size(); + } + return std::nullopt; +} + +QWidget *StandaloneLayerStack::currentPanel() const { + return _entries.empty() ? nullptr : _entries.back().panel.get(); +} + +void StandaloneLayerStack::closeEntry(SeparatePanel *panel) { + const auto i = ranges::find_if(_entries, [&](const Entry &entry) { + return entry.panel.get() == panel; + }); + if (i == end(_entries)) { + return; + } + const auto wasTop = (i + 1 == end(_entries)); + auto entry = std::move(*i); + _entries.erase(i); + if (entry.box) { + entry.box->setClosing(); + } + entry.panel->hideGetDuration(); + _boxClosed.fire({}); + if (wasTop && !_entries.empty()) { + _entries.back().panel->showAndActivate(); + } +} + +} // namespace Ui diff --git a/Telegram/SourceFiles/ui/layers/standalone_layer_stack.h b/Telegram/SourceFiles/ui/layers/standalone_layer_stack.h new file mode 100644 index 0000000000..cf3841d348 --- /dev/null +++ b/Telegram/SourceFiles/ui/layers/standalone_layer_stack.h @@ -0,0 +1,80 @@ +/* +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 "base/object_ptr.h" +#include "base/unique_qptr.h" +#include "base/weak_ptr.h" +#include "ui/layers/layer_widget.h" + +#include + +#include +#include + +namespace anim { +enum class type : uchar; +} // namespace anim + +namespace Ui { + +class BoxContent; +class BoxLayerWidget; +class SeparatePanel; + +// Shows each BoxContent in its own top-level frameless SeparatePanel, +// using BoxLayerWidget as the panel body. Behaves like a layer stack: +// each new box hides the previous one, closing a box reveals the box +// shown before it. +class StandaloneLayerStack final + : public LayerStackDelegate + , public base::has_weak_ptr { +public: + StandaloneLayerStack(); + ~StandaloneLayerStack(); + + void showBox( + object_ptr box, + LayerOptions options, + anim::type animated) override; + void hideLayers(anim::type animated) override; + ShowFactory showFactory() override; + std::optional layerOuterSize() override; + bool centerWithinOuter() override { + return false; + } + bool dragByTitle() override { + return true; + } + + // Top (currently visible) panel, if any. Used as toast parent. + [[nodiscard]] QWidget *currentPanel() const; + + [[nodiscard]] rpl::producer<> boxAdded() const { + return _boxAdded.events(); + } + [[nodiscard]] rpl::producer<> boxClosed() const { + return _boxClosed.events(); + } + +private: + struct Entry { + base::unique_qptr panel; + BoxLayerWidget *box = nullptr; + }; + + void closeEntry(SeparatePanel *panel); + void hideAllPanels(); + + std::vector _entries; + rpl::event_stream<> _boxAdded; + rpl::event_stream<> _boxClosed; + +}; + +} // namespace Ui diff --git a/Telegram/cmake/td_ui.cmake b/Telegram/cmake/td_ui.cmake index 5687b09244..e8b627d767 100644 --- a/Telegram/cmake/td_ui.cmake +++ b/Telegram/cmake/td_ui.cmake @@ -374,6 +374,8 @@ PRIVATE ui/chat/attach/attach_bot_webview.h ui/chat/attach/attach_bot_webview_linux_shell.cpp ui/chat/attach/attach_bot_webview_linux_shell.h + ui/layers/standalone_layer_stack.cpp + ui/layers/standalone_layer_stack.h ui/chat/attach/attach_controls.cpp ui/chat/attach/attach_controls.h ui/chat/attach/attach_extensions.cpp