// 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 "styles/style_widgets.h" #include "styles/palette.h" #include "base/algorithm.h" #include "base/event_filter.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 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([=] { window()->setWindowState( window()->windowState() | Qt::WindowMinimized); _minimize->clearState(); }); _minimize->setPointerCursor(false); _maximizeRestore->setClickedCallback([=] { if (maximize) { maximize(!_maximizedState); } else { window()->setWindowState(_maximizedState ? Qt::WindowNoState : Qt::WindowMaximized); } _maximizeRestore->clearState(); }); _maximizeRestore->setPointerCursor(false); _close->setClickedCallback([=] { window()->close(); _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(); } Ui::IconButton *TitleControls::controlWidget(Control control) const { switch (control) { case Control::Minimize: return _minimize; case Control::Maximize: return _maximizeRestore; case Control::Close: return _close; } return nullptr; } void TitleControls::updateControlsPosition() { const auto controlsLayout = TitleControlsLayout(); auto controlsLeft = controlsLayout.left; auto controlsRight = controlsLayout.right; const auto moveFromTo = [&](auto &from, auto &to) { for (const auto control : from) { if (!ranges::contains(to, control)) { to.push_back(control); } } from.clear(); }; if (ranges::contains(controlsLeft, Control::Close)) { moveFromTo(controlsRight, controlsLeft); } else if (ranges::contains(controlsRight, Control::Close)) { moveFromTo(controlsLeft, controlsRight); } else if (controlsLeft.size() > controlsRight.size()) { moveFromTo(controlsRight, controlsLeft); } else { moveFromTo(controlsLeft, controlsRight); } const auto controlPresent = [&](Control control) { return ranges::contains(controlsLeft, control) || ranges::contains(controlsRight, control); }; const auto eraseControl = [&](Control control) { controlsLeft.erase( ranges::remove(controlsLeft, control), end(controlsLeft)); controlsRight.erase( ranges::remove(controlsRight, control), end(controlsRight)); }; if (!_resizeEnabled) { eraseControl(Control::Maximize); } if (controlPresent(Control::Minimize)) { _minimize->show(); } else { _minimize->hide(); } if (controlPresent(Control::Maximize)) { _maximizeRestore->show(); } else { _maximizeRestore->hide(); } if (controlPresent(Control::Close)) { _close->show(); } else { _close->hide(); } updateControlsPositionBySide(controlsLeft, false); updateControlsPositionBySide(controlsRight, true); } void TitleControls::updateControlsPositionBySide( const std::vector &controls, bool right) { auto preparedControls = right ? (ranges::views::reverse(controls) | ranges::to_vector) : controls; RemoveDuplicates(preparedControls); auto position = 0; for (const auto &control : preparedControls) { const auto widget = controlWidget(control); if (!widget) { continue; } if (right) { widget->moveToRight(position, 0); } else { widget->moveToLeft(position, 0); } position += widget->width(); } } void TitleControls::handleWindowStateChanged(Qt::WindowState state) { if (state == Qt::WindowMinimized) { return; } auto maximized = (state == Qt::WindowMaximized) || (state == Qt::WindowFullScreen); if (_maximizedState != maximized) { _maximizedState = maximized; updateButtonsState(); } } void TitleControls::updateButtonsState() { const auto minimize = _activeState ? &_st->minimizeIconActive : &_st->minimize.icon; const auto minimizeOver = _activeState ? &_st->minimizeIconActiveOver : &_st->minimize.iconOver; _minimize->setIconOverride(minimize, minimizeOver); if (_maximizedState) { const auto restore = _activeState ? &_st->restoreIconActive : &_st->restoreIcon; const auto restoreOver = _activeState ? &_st->restoreIconActiveOver : &_st->restoreIconOver; _maximizeRestore->setIconOverride(restore, restoreOver); } else { const auto maximize = _activeState ? &_st->maximizeIconActive : &_st->maximize.icon; const auto maximizeOver = _activeState ? &_st->maximizeIconActiveOver : &_st->maximize.iconOver; _maximizeRestore->setIconOverride(maximize, maximizeOver); } const auto close = _activeState ? &_st->closeIconActive : &_st->close.icon; const auto closeOver = _activeState ? &_st->closeIconActiveOver : &_st->close.iconOver; _close->setIconOverride(close, closeOver); } DefaultTitleWidget::DefaultTitleWidget(not_null parent) : RpWidget(parent) , _controls(this, st::defaultWindowTitle) , _shadow(this, st::titleShadow) { setAttribute(Qt::WA_OpaquePaintEvent); } not_null DefaultTitleWidget::st() const { return _controls.st(); } void DefaultTitleWidget::setText(const QString &text) { window()->setWindowTitle(text); } void DefaultTitleWidget::setStyle(const style::WindowTitle &st) { _controls.setStyle(st); update(); } void DefaultTitleWidget::setResizeEnabled(bool enabled) { _controls.setResizeEnabled(enabled); } void DefaultTitleWidget::paintEvent(QPaintEvent *e) { const auto active = window()->isActiveWindow(); QPainter(this).fillRect( e->rect(), active ? _controls.st()->bgActive : _controls.st()->bg); } void DefaultTitleWidget::resizeEvent(QResizeEvent *e) { _shadow->setGeometry(0, height() - st::lineWidth, width(), st::lineWidth); } void DefaultTitleWidget::mousePressEvent(QMouseEvent *e) { if (e->button() == Qt::LeftButton) { _mousePressed = true; } else if (e->button() == Qt::RightButton) { ShowWindowMenu(window()->windowHandle()); } } void DefaultTitleWidget::mouseReleaseEvent(QMouseEvent *e) { if (e->button() == Qt::LeftButton) { _mousePressed = false; } } void DefaultTitleWidget::mouseMoveEvent(QMouseEvent *e) { if (_mousePressed) { window()->windowHandle()->startSystemMove(); } } void DefaultTitleWidget::mouseDoubleClickEvent(QMouseEvent *e) { const auto state = window()->windowState(); if (state & Qt::WindowMaximized) { window()->setWindowState(state & ~Qt::WindowMaximized); } else { window()->setWindowState(state | Qt::WindowMaximized); } } } // namespace Platform } // namespace Ui