// This file is part of Desktop App Toolkit, // a set of libraries for developing nice desktop applications. // // For license and copyright information please follow this link: // https://github.com/desktop-app/legal/blob/master/LEGAL // #include "ui/platform/ui_platform_window_title.h" #include "ui/platform/ui_platform_utility.h" #include "ui/widgets/buttons.h" #include "ui/widgets/shadow.h" #include "ui/ui_utility.h" #include "ui/widgets/rp_window.h" #include "styles/style_widgets.h" #include "styles/palette.h" #include "base/algorithm.h" #include "base/event_filter.h" #include "base/platform/base_platform_info.h" #include #include #include namespace Ui { namespace Platform { namespace { template void RemoveDuplicates(std::vector &v) { auto end = v.end(); for (auto it = v.begin(); it != end; ++it) { end = std::remove(it + 1, end, *it); } v.erase(end, v.end()); } } // namespace bool SemiNativeSystemButtonProcessing() { return ::Platform::IsWindows11OrGreater(); } void SetupSemiNativeSystemButtons( not_null controls, not_null window, rpl::lifetime &lifetime, Fn filter) { if (!SemiNativeSystemButtonProcessing()) { return; } window->systemButtonOver( ) | rpl::filter([=](HitTestResult button) { return !filter || filter() || (button == HitTestResult::None); }) | rpl::start_with_next([=](HitTestResult button) { controls->buttonOver(button); }, lifetime); window->systemButtonDown( ) | rpl::filter([=](HitTestResult button) { return !filter || filter() || (button == HitTestResult::None); }) | rpl::start_with_next([=](HitTestResult button) { controls->buttonDown(button); }, lifetime); } class TitleControls::Button final : public IconButton { public: using IconButton::IconButton; void setOver(bool over) { IconButton::setOver(over, StateChangeSource::ByPress); } void setDown(bool down) { IconButton::setDown( down, StateChangeSource::ByPress, {}, Qt::LeftButton); } }; TitleControls::TitleControls( not_null parent, const style::WindowTitle &st, Fn maximize) : _st(&st) , _minimize(parent, _st->minimize) , _maximizeRestore(parent, _st->maximize) , _close(parent, _st->close) , _maximizedState(parent->windowState() & (Qt::WindowMaximized | Qt::WindowFullScreen)) , _activeState(parent->isActiveWindow()) { init(std::move(maximize)); _close->paintRequest( ) | rpl::start_with_next([=] { const auto active = window()->isActiveWindow(); if (_activeState != active) { _activeState = active; updateButtonsState(); } }, _close->lifetime()); } void TitleControls::setStyle(const style::WindowTitle &st) { _st = &st; updateButtonsState(); } not_null TitleControls::st() const { return _st; } QRect TitleControls::geometry() const { auto result = QRect(); const auto add = [&](auto &&control) { if (!control->isHidden()) { result = result.united(control->geometry()); } }; add(_minimize); add(_maximizeRestore); add(_close); return result; } not_null TitleControls::parent() const { return static_cast(_close->parentWidget()); } not_null TitleControls::window() const { return _close->window(); } void TitleControls::init(Fn maximize) { _minimize->setClickedCallback([=] { const auto weak = MakeWeak(_minimize.data()); window()->setWindowState( window()->windowState() | Qt::WindowMinimized); if (weak) { _minimize->clearState(); } }); _minimize->setPointerCursor(false); _maximizeRestore->setClickedCallback([=] { const auto weak = MakeWeak(_maximizeRestore.data()); if (maximize) { maximize(!_maximizedState); } else { window()->setWindowState(_maximizedState ? Qt::WindowNoState : Qt::WindowMaximized); } if (weak) { _maximizeRestore->clearState(); } }); _maximizeRestore->setPointerCursor(false); _close->setClickedCallback([=] { const auto weak = MakeWeak(_close.data()); window()->close(); if (weak) { _close->clearState(); } }); _close->setPointerCursor(false); parent()->widthValue( ) | rpl::start_with_next([=](int width) { updateControlsPosition(); }, _close->lifetime()); TitleControlsLayoutChanged( ) | rpl::start_with_next([=] { updateControlsPosition(); }, _close->lifetime()); subscribeToStateChanges(); _activeState = parent()->isActiveWindow(); updateButtonsState(); } void TitleControls::subscribeToStateChanges() { const auto subscribe = [=] { QObject::connect( window()->windowHandle(), &QWindow::windowStateChanged, [=](Qt::WindowState state) { handleWindowStateChanged(state); }); }; if (window()->windowHandle()) { subscribe(); } else { const auto winIdEventFilter = std::make_shared(nullptr); *winIdEventFilter = base::install_event_filter( window(), [=](not_null e) { if (!*winIdEventFilter || e->type() != QEvent::WinIdChange) { return base::EventFilterResult::Continue; } subscribe(); base::take(*winIdEventFilter)->deleteLater(); return base::EventFilterResult::Continue; }); } } void TitleControls::setResizeEnabled(bool enabled) { _resizeEnabled = enabled; updateControlsPosition(); } void TitleControls::raise() { _minimize->raise(); _maximizeRestore->raise(); _close->raise(); } HitTestResult TitleControls::hitTest(QPoint point) const { const auto test = [&](const object_ptr