UI/Qt: Add interface for exiting fullscreen
This adds the following features to the Qt UI: - An "exit fullscreen" button that displays when entering fullscreen This will disappear after a certain amount of time. Moving the cursor to the top of the screen will make it reappear. - Switching tabs exits fullscreen The look and feel of this is preliminary and should be expanded upon in the future.
This commit is contained in:
parent
4396e5b8d0
commit
25eef2e6e6
3 changed files with 202 additions and 3 deletions
|
|
@ -4,10 +4,12 @@
|
|||
* Copyright (c) 2022, Filiph Sandström <filiph.sandstrom@filfatstudios.com>
|
||||
* Copyright (c) 2023, Linus Groh <linusg@serenityos.org>
|
||||
* Copyright (c) 2024-2025, Sam Atkins <sam@ladybird.org>
|
||||
* Copyright (c) 2025, Simon Farre <simon.farre.cx@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <AK/RefPtr.h>
|
||||
#include <AK/TypeCasts.h>
|
||||
#include <LibWebView/Application.h>
|
||||
#include <UI/Qt/Application.h>
|
||||
|
|
@ -25,14 +27,128 @@
|
|||
#include <QInputDialog>
|
||||
#include <QMessageBox>
|
||||
#include <QMouseEvent>
|
||||
#include <QPropertyAnimation>
|
||||
#include <QPushButton>
|
||||
#include <QScreen>
|
||||
#include <QShortcut>
|
||||
#include <QStatusBar>
|
||||
#include <QTabBar>
|
||||
#include <QTimer>
|
||||
#include <QWheelEvent>
|
||||
#include <QWidget>
|
||||
#include <QWindow>
|
||||
|
||||
namespace Ladybird {
|
||||
|
||||
FullscreenMode::FullscreenMode(BrowserWindow* window, ExitFullscreenButton* exit_button)
|
||||
: QObject(window)
|
||||
, m_window(window)
|
||||
, m_exit_button(exit_button)
|
||||
{
|
||||
connect(m_exit_button, &QPushButton::clicked, this, [this]() {
|
||||
exit();
|
||||
});
|
||||
}
|
||||
|
||||
void FullscreenMode::exit()
|
||||
{
|
||||
// If there's a document tree in fullscreen, exit fully on root document.
|
||||
if (is_api_fullscreen()) {
|
||||
qApp->removeEventFilter(this);
|
||||
if (m_window->tab_index(m_fullscreen_tab) != -1) {
|
||||
m_fullscreen_tab->view().exit_fullscreen();
|
||||
}
|
||||
emit on_exit_fullscreen();
|
||||
}
|
||||
m_fullscreen_tab = nullptr;
|
||||
}
|
||||
|
||||
void FullscreenMode::enter(Tab* tab)
|
||||
{
|
||||
qApp->installEventFilter(this);
|
||||
m_fullscreen_tab = tab;
|
||||
m_window->enter_fullscreen();
|
||||
}
|
||||
|
||||
void FullscreenMode::entered_fullscreen()
|
||||
{
|
||||
m_debounce = true;
|
||||
m_exit_button->animate_show();
|
||||
// Let button float in place 3 * time it takes to animate it in place
|
||||
QTimer::singleShot(button_animation_time() * 3, [this]() { m_debounce = false; });
|
||||
}
|
||||
|
||||
bool FullscreenMode::is_api_fullscreen() const
|
||||
{
|
||||
return m_fullscreen_tab;
|
||||
}
|
||||
|
||||
bool FullscreenMode::debounce() const
|
||||
{
|
||||
return m_debounce;
|
||||
}
|
||||
|
||||
void FullscreenMode::maybe_animate_show_exit_button(QPointF pos)
|
||||
{
|
||||
u64 const mouse_y = static_cast<u64>(pos.y());
|
||||
u64 const threshold = static_cast<u64>(m_window->height() * 0.01);
|
||||
|
||||
if (debounce()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Display the button if the mouse is 1% from the top
|
||||
if (mouse_y <= threshold) {
|
||||
if (!m_exit_button->isVisible()) {
|
||||
m_debounce = true;
|
||||
m_exit_button->animate_show();
|
||||
QTimer::singleShot(button_animation_time() * 3, [this]() { m_debounce = false; });
|
||||
}
|
||||
} else if (mouse_y > (threshold * 10) && m_exit_button->isVisible()) {
|
||||
// if the button has floated in, we want to hide it when leaving the top 10%
|
||||
m_exit_button->hide();
|
||||
}
|
||||
}
|
||||
|
||||
bool FullscreenMode::eventFilter(QObject* obj, QEvent* event)
|
||||
{
|
||||
ASSERT(is_api_fullscreen());
|
||||
if (event->type() == QEvent::MouseMove) {
|
||||
QMouseEvent* mouse_event = static_cast<QMouseEvent*>(event);
|
||||
maybe_animate_show_exit_button(mouse_event->pos());
|
||||
}
|
||||
|
||||
return QObject::eventFilter(obj, event);
|
||||
}
|
||||
|
||||
ExitFullscreenButton::ExitFullscreenButton(QWidget* parent)
|
||||
: QPushButton("Exit fullscreen", parent)
|
||||
{
|
||||
setStyleSheet("background-color:rgb(55, 99, 129); color: white; padding: 10px; border-radius: 5px;");
|
||||
adjustSize();
|
||||
hide();
|
||||
m_widget_animation = new QPropertyAnimation(this, "pos");
|
||||
}
|
||||
|
||||
void ExitFullscreenButton::animate_show()
|
||||
{
|
||||
if (isVisible())
|
||||
return;
|
||||
|
||||
show();
|
||||
QScreen* current_screen = screen();
|
||||
QRect screen_geometry = current_screen->geometry();
|
||||
|
||||
int const destination_x = (screen_geometry.width() - width()) / 2;
|
||||
int const destination_y = static_cast<int>(static_cast<float>(screen_geometry.height()) * 0.05);
|
||||
|
||||
m_widget_animation->setDuration(FullscreenMode::button_animation_time());
|
||||
m_widget_animation->setStartValue(QPoint(destination_x, -height()));
|
||||
m_widget_animation->setEndValue(QPoint(destination_x, destination_y));
|
||||
m_widget_animation->setEasingCurve(QEasingCurve::OutBounce);
|
||||
m_widget_animation->start();
|
||||
}
|
||||
|
||||
static QIcon const& app_icon()
|
||||
{
|
||||
static QIcon icon;
|
||||
|
|
@ -230,12 +346,19 @@ BrowserWindow::BrowserWindow(Vector<URL::URL> const& initial_urls, IsPopupWindow
|
|||
(void)Application::the().new_window({});
|
||||
});
|
||||
QObject::connect(open_file_action, &QAction::triggered, this, &BrowserWindow::open_file);
|
||||
|
||||
m_exit_button = new ExitFullscreenButton { this };
|
||||
m_fullscreen_mode = new FullscreenMode { this, m_exit_button };
|
||||
connect(m_fullscreen_mode, &FullscreenMode::on_exit_fullscreen, this, &BrowserWindow::exit_fullscreen);
|
||||
connect(m_fullscreen_mode, &FullscreenMode::on_exit_fullscreen, m_exit_button, &ExitFullscreenButton::hide);
|
||||
|
||||
QObject::connect(m_tabs_container, &QTabWidget::currentChanged, [this](int index) {
|
||||
auto* tab = as<Tab>(m_tabs_container->widget(index));
|
||||
if (tab)
|
||||
setWindowTitle(QString("%1 - Ladybird").arg(tab->title()));
|
||||
|
||||
set_current_tab(tab);
|
||||
fullscreen_mode().exit();
|
||||
});
|
||||
QObject::connect(m_tabs_container, &QTabWidget::tabCloseRequested, this, &BrowserWindow::request_to_close_tab);
|
||||
QObject::connect(close_current_tab_action, &QAction::triggered, this, &BrowserWindow::request_to_close_current_tab);
|
||||
|
|
@ -328,6 +451,11 @@ Tab& BrowserWindow::create_new_tab(Web::HTML::ActivateTab activate_tab, Tab& par
|
|||
return *tab;
|
||||
}
|
||||
|
||||
FullscreenMode& BrowserWindow::fullscreen_mode()
|
||||
{
|
||||
return *m_fullscreen_mode;
|
||||
}
|
||||
|
||||
Tab& BrowserWindow::create_new_tab(Web::HTML::ActivateTab activate_tab)
|
||||
{
|
||||
auto* tab = new Tab(this);
|
||||
|
|
@ -581,6 +709,7 @@ void BrowserWindow::enter_fullscreen()
|
|||
{
|
||||
m_tabs_container->tabBar()->hide();
|
||||
m_tabs_container->cornerWidget()->hide();
|
||||
m_restore_to_maximized = isMaximized();
|
||||
showFullScreen();
|
||||
}
|
||||
|
||||
|
|
@ -588,7 +717,10 @@ void BrowserWindow::exit_fullscreen()
|
|||
{
|
||||
m_tabs_container->tabBar()->show();
|
||||
m_tabs_container->cornerWidget()->show();
|
||||
showNormal();
|
||||
if (m_restore_to_maximized)
|
||||
showMaximized();
|
||||
else
|
||||
showNormal();
|
||||
}
|
||||
|
||||
bool BrowserWindow::event(QEvent* event)
|
||||
|
|
@ -615,6 +747,17 @@ void BrowserWindow::resizeEvent(QResizeEvent* event)
|
|||
});
|
||||
}
|
||||
|
||||
void BrowserWindow::changeEvent(QEvent* event)
|
||||
{
|
||||
if (event->type() == QEvent::WindowStateChange) {
|
||||
QWindowStateChangeEvent* stateChangeEvent = static_cast<QWindowStateChangeEvent*>(event);
|
||||
if (windowState() & Qt::WindowFullScreen && !(stateChangeEvent->oldState() & Qt::WindowFullScreen)) {
|
||||
m_fullscreen_mode->entered_fullscreen();
|
||||
}
|
||||
}
|
||||
QWidget::changeEvent(event);
|
||||
}
|
||||
|
||||
void BrowserWindow::moveEvent(QMoveEvent* event)
|
||||
{
|
||||
QWidget::moveEvent(event);
|
||||
|
|
|
|||
|
|
@ -15,14 +15,63 @@
|
|||
#include <QIcon>
|
||||
#include <QMainWindow>
|
||||
#include <QMenuBar>
|
||||
#include <QPushButton>
|
||||
#include <QTabBar>
|
||||
#include <QTabWidget>
|
||||
#include <QToolBar>
|
||||
|
||||
class QPropertyAnimation;
|
||||
|
||||
namespace Ladybird {
|
||||
|
||||
class Tab;
|
||||
class WebContentView;
|
||||
class BrowserWindow;
|
||||
|
||||
class ExitFullscreenButton : public QPushButton {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
ExitFullscreenButton(QWidget* parent = nullptr);
|
||||
~ExitFullscreenButton() override = default;
|
||||
void animate_show();
|
||||
|
||||
private:
|
||||
QPropertyAnimation* m_widget_animation;
|
||||
};
|
||||
|
||||
// Handles Qt UI state related to when Ladybird has entered fullscreen,
|
||||
// like displaying an exit button, listening for escape key presses and so on
|
||||
class FullscreenMode : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
static constexpr int button_animation_time() { return 750; }
|
||||
explicit FullscreenMode(BrowserWindow* window, ExitFullscreenButton* exit_button);
|
||||
|
||||
void exit();
|
||||
void enter(Tab* tab);
|
||||
// Called after a window change event that has identifed the current window state to be fullscreen.
|
||||
void entered_fullscreen();
|
||||
bool is_api_fullscreen() const;
|
||||
signals:
|
||||
void on_exit_fullscreen();
|
||||
|
||||
protected:
|
||||
// FullscreenMode's eventFilter is responsible for things that need to happen when a document
|
||||
// is in "fullscreen API fullscreen", like exiting when pushing escape, showing the exit button
|
||||
virtual bool eventFilter(QObject* obj, QEvent* event) override;
|
||||
|
||||
private:
|
||||
bool debounce() const;
|
||||
// Called when in fullscreen. Displays exit fullscreen button if mouse comes close to the top of the screen.
|
||||
void maybe_animate_show_exit_button(QPointF pos);
|
||||
BrowserWindow* m_window;
|
||||
ExitFullscreenButton* m_exit_button;
|
||||
// Never access this directly. First check m_window->tab_index(m_fullscreen_tab) != -1, to verify it's liveness.
|
||||
Tab* m_fullscreen_tab { nullptr };
|
||||
bool m_debounce { false };
|
||||
};
|
||||
|
||||
class BrowserWindow : public QMainWindow {
|
||||
Q_OBJECT
|
||||
|
|
@ -42,6 +91,7 @@ public:
|
|||
|
||||
Tab& create_new_tab(Web::HTML::ActivateTab activate_tab);
|
||||
Tab* current_tab() const { return m_current_tab; }
|
||||
FullscreenMode& fullscreen_mode();
|
||||
|
||||
QMenu& hamburger_menu() const { return *m_hamburger_menu; }
|
||||
|
||||
|
|
@ -80,6 +130,7 @@ protected:
|
|||
private:
|
||||
virtual bool event(QEvent*) override;
|
||||
virtual void resizeEvent(QResizeEvent*) override;
|
||||
virtual void changeEvent(QEvent* event) override;
|
||||
virtual void moveEvent(QMoveEvent*) override;
|
||||
virtual void wheelEvent(QWheelEvent*) override;
|
||||
virtual void closeEvent(QCloseEvent*) override;
|
||||
|
|
@ -122,6 +173,11 @@ private:
|
|||
QAction* m_find_in_page_action { nullptr };
|
||||
|
||||
IsPopupWindow m_is_popup_window { IsPopupWindow::No };
|
||||
|
||||
ExitFullscreenButton* m_exit_button { nullptr };
|
||||
FullscreenMode* m_fullscreen_mode { nullptr };
|
||||
// Determine if window should restore to maximized or normal, when exiting fullscreen.
|
||||
bool m_restore_to_maximized { false };
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -350,12 +350,12 @@ Tab::Tab(BrowserWindow* window, RefPtr<WebView::WebContentClient> parent_client,
|
|||
|
||||
view().on_fullscreen_window = [this]() {
|
||||
m_toolbar->hide();
|
||||
m_window->enter_fullscreen();
|
||||
m_window->fullscreen_mode().enter(this);
|
||||
view().did_update_window_rect();
|
||||
};
|
||||
|
||||
view().on_exit_fullscreen_window = [this]() {
|
||||
m_window->exit_fullscreen();
|
||||
m_window->fullscreen_mode().exit();
|
||||
m_toolbar->show();
|
||||
view().did_update_window_rect();
|
||||
};
|
||||
|
|
|
|||
Loading…
Reference in a new issue