feat: add option to provide a titlebar (default on)
This commit is contained in:
@@ -44,6 +44,7 @@ set (PPIC_CPP_FILES
|
||||
app/graphicsview.cpp
|
||||
app/graphicsscene.cpp
|
||||
app/bottombuttongroup.cpp
|
||||
app/titlebar.cpp
|
||||
app/navigatorview.cpp
|
||||
app/opacityhelper.cpp
|
||||
app/toolbutton.cpp
|
||||
@@ -65,6 +66,7 @@ set (PPIC_HEADER_FILES
|
||||
app/graphicsview.h
|
||||
app/graphicsscene.h
|
||||
app/bottombuttongroup.h
|
||||
app/titlebar.h
|
||||
app/navigatorview.h
|
||||
app/opacityhelper.h
|
||||
app/toolbutton.h
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include "settings.h"
|
||||
#include "toolbutton.h"
|
||||
#include "bottombuttongroup.h"
|
||||
#include "titlebar.h"
|
||||
#include "graphicsview.h"
|
||||
#include "navigatorview.h"
|
||||
#include "graphicsscene.h"
|
||||
@@ -101,20 +102,20 @@ MainWindow::MainWindow(QWidget *parent)
|
||||
connect(m_graphicsView, &GraphicsView::viewportRectChanged,
|
||||
m_gv, &NavigatorView::updateMainViewportRegion);
|
||||
|
||||
m_closeButton = new ToolButton(true, m_graphicsView);
|
||||
m_closeButton->setIconSize(QSize(32, 32));
|
||||
m_closeButton->setFixedSize(QSize(50, 50));
|
||||
m_closeButton->setIconResourcePath(":/icons/window-close.svg");
|
||||
m_titleBar = new TitleBar(this);
|
||||
m_titleBar->setCloseButtonOnly(!Settings::instance()->showTitleBar());
|
||||
|
||||
connect(m_closeButton, &QAbstractButton::clicked,
|
||||
connect(m_titleBar, &TitleBar::closeRequested,
|
||||
this, &MainWindow::closeWindow);
|
||||
connect(m_titleBar, &TitleBar::maximizeToggleRequested,
|
||||
this, &MainWindow::toggleMaximize);
|
||||
|
||||
m_prevButton = new ToolButton(false, m_graphicsView);
|
||||
m_prevButton = new ToolButton(m_graphicsView);
|
||||
m_prevButton->setIconSize(QSize(75, 75));
|
||||
m_prevButton->setIconResourcePath(":/icons/go-previous.svg");
|
||||
m_prevButton->setVisible(false);
|
||||
m_prevButton->setOpacity(0, false);
|
||||
m_nextButton = new ToolButton(false, m_graphicsView);
|
||||
m_nextButton = new ToolButton(m_graphicsView);
|
||||
m_nextButton->setIconSize(QSize(75, 75));
|
||||
m_nextButton->setIconResourcePath(":/icons/go-next.svg");
|
||||
m_nextButton->setVisible(false);
|
||||
@@ -138,7 +139,7 @@ MainWindow::MainWindow(QWidget *parent)
|
||||
|
||||
m_bottomButtonGroup->setOpacity(0, false);
|
||||
m_gv->setOpacity(0, false);
|
||||
m_closeButton->setOpacity(0, false);
|
||||
m_titleBar->setOpacity(0, false);
|
||||
|
||||
connect(m_pm, &PlaylistManager::totalCountChanged, this, &MainWindow::updateGalleryButtonsVisibility);
|
||||
|
||||
@@ -161,7 +162,7 @@ MainWindow::MainWindow(QWidget *parent)
|
||||
});
|
||||
|
||||
// allow some mouse events can go through these widgets for resizing window.
|
||||
installResizeCapture(m_closeButton);
|
||||
installResizeCapture(m_titleBar);
|
||||
installResizeCapture(m_graphicsView);
|
||||
installResizeCapture(m_graphicsView->viewport());
|
||||
installResizeCapture(m_gv);
|
||||
@@ -323,7 +324,7 @@ void MainWindow::enterEvent(QEnterEvent *event)
|
||||
m_bottomButtonGroup->setOpacity(1);
|
||||
m_gv->setOpacity(1);
|
||||
|
||||
m_closeButton->setOpacity(1);
|
||||
m_titleBar->setOpacity(1);
|
||||
m_prevButton->setOpacity(1);
|
||||
m_nextButton->setOpacity(1);
|
||||
|
||||
@@ -335,7 +336,7 @@ void MainWindow::leaveEvent(QEvent *event)
|
||||
m_bottomButtonGroup->setOpacity(0);
|
||||
m_gv->setOpacity(0);
|
||||
|
||||
m_closeButton->setOpacity(0);
|
||||
m_titleBar->setOpacity(0);
|
||||
m_prevButton->setOpacity(0);
|
||||
m_nextButton->setOpacity(0);
|
||||
|
||||
@@ -612,7 +613,12 @@ void MainWindow::closeWindow()
|
||||
|
||||
void MainWindow::updateWidgetsPosition()
|
||||
{
|
||||
m_closeButton->move(width() - m_closeButton->width(), 0);
|
||||
if (m_titleBar->closeButtonOnly()) {
|
||||
const int bw = m_titleBar->closeButtonWidth();
|
||||
m_titleBar->setGeometry(width() - bw, 0, bw, m_titleBar->height());
|
||||
} else {
|
||||
m_titleBar->setGeometry(0, 0, width(), m_titleBar->height());
|
||||
}
|
||||
m_prevButton->move(25, (height() - m_prevButton->sizeHint().height()) / 2);
|
||||
m_nextButton->move(width() - m_nextButton->sizeHint().width() - 25,
|
||||
(height() - m_prevButton->sizeHint().height()) / 2);
|
||||
@@ -624,7 +630,7 @@ void MainWindow::updateWidgetsPosition()
|
||||
void MainWindow::toggleProtectedMode()
|
||||
{
|
||||
m_protectedMode = !m_protectedMode;
|
||||
m_closeButton->setVisible(!m_protectedMode);
|
||||
m_titleBar->setCloseButtonVisible(!m_protectedMode);
|
||||
updateGalleryButtonsVisibility();
|
||||
}
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ QT_END_NAMESPACE
|
||||
class ActionManager;
|
||||
class PlaylistManager;
|
||||
class ToolButton;
|
||||
class TitleBar;
|
||||
class GraphicsView;
|
||||
class NavigatorView;
|
||||
class BottomButtonGroup;
|
||||
@@ -123,9 +124,9 @@ private:
|
||||
QPropertyAnimation *m_floatUpAnimation;
|
||||
QParallelAnimationGroup *m_exitAnimationGroup;
|
||||
QFileSystemWatcher *m_fileSystemWatcher;
|
||||
ToolButton *m_closeButton;
|
||||
ToolButton *m_prevButton;
|
||||
ToolButton *m_nextButton;
|
||||
TitleBar *m_titleBar;
|
||||
GraphicsView *m_graphicsView;
|
||||
NavigatorView *m_gv;
|
||||
BottomButtonGroup *m_bottomButtonGroup;
|
||||
|
||||
@@ -55,6 +55,11 @@ bool Settings::useBuiltInCloseAnimation() const
|
||||
return m_qsettings->value("use_built_in_close_animation", true).toBool();
|
||||
}
|
||||
|
||||
bool Settings::showTitleBar() const
|
||||
{
|
||||
return m_qsettings->value("show_title_bar", true).toBool();
|
||||
}
|
||||
|
||||
bool Settings::useLightCheckerboard() const
|
||||
{
|
||||
return m_qsettings->value("use_light_checkerboard", false).toBool();
|
||||
@@ -123,6 +128,12 @@ void Settings::setUseBuiltInCloseAnimation(bool on)
|
||||
m_qsettings->sync();
|
||||
}
|
||||
|
||||
void Settings::setShowTitleBar(bool on)
|
||||
{
|
||||
m_qsettings->setValue("show_title_bar", on);
|
||||
m_qsettings->sync();
|
||||
}
|
||||
|
||||
void Settings::setUseLightCheckerboard(bool light)
|
||||
{
|
||||
m_qsettings->setValue("use_light_checkerboard", light);
|
||||
|
||||
@@ -36,6 +36,7 @@ public:
|
||||
|
||||
bool stayOnTop() const;
|
||||
bool useBuiltInCloseAnimation() const;
|
||||
bool showTitleBar() const;
|
||||
bool useLightCheckerboard() const;
|
||||
bool loopGallery() const;
|
||||
bool autoLongImageMode() const;
|
||||
@@ -47,6 +48,7 @@ public:
|
||||
|
||||
void setStayOnTop(bool on);
|
||||
void setUseBuiltInCloseAnimation(bool on);
|
||||
void setShowTitleBar(bool on);
|
||||
void setUseLightCheckerboard(bool light);
|
||||
void setLoopGallery(bool on);
|
||||
void setAutoLongImageMode(bool on);
|
||||
|
||||
@@ -21,6 +21,7 @@ SettingsDialog::SettingsDialog(QWidget *parent)
|
||||
: QDialog(parent)
|
||||
, m_stayOnTop(new QCheckBox)
|
||||
, m_useBuiltInCloseAnimation(new QCheckBox)
|
||||
, m_showTitleBar(new QCheckBox)
|
||||
, m_useLightCheckerboard(new QCheckBox)
|
||||
, m_loopGallery(new QCheckBox)
|
||||
, m_autoLongImageMode(new QCheckBox)
|
||||
@@ -123,6 +124,7 @@ SettingsDialog::SettingsDialog(QWidget *parent)
|
||||
|
||||
settingsForm->addRow(tr("Stay on top when start-up"), m_stayOnTop);
|
||||
settingsForm->addRow(tr("Use built-in close window animation"), m_useBuiltInCloseAnimation);
|
||||
settingsForm->addRow(tr("Show title bar"), m_showTitleBar);
|
||||
settingsForm->addRow(tr("Use light-color checkerboard"), m_useLightCheckerboard);
|
||||
settingsForm->addRow(tr("Loop the loaded gallery"), m_loopGallery);
|
||||
settingsForm->addRow(tr("Auto long image mode"), m_autoLongImageMode);
|
||||
@@ -134,6 +136,7 @@ SettingsDialog::SettingsDialog(QWidget *parent)
|
||||
|
||||
m_stayOnTop->setChecked(Settings::instance()->stayOnTop());
|
||||
m_useBuiltInCloseAnimation->setChecked(Settings::instance()->useBuiltInCloseAnimation());
|
||||
m_showTitleBar->setChecked(Settings::instance()->showTitleBar());
|
||||
m_useLightCheckerboard->setChecked(Settings::instance()->useLightCheckerboard());
|
||||
m_loopGallery->setChecked(Settings::instance()->loopGallery());
|
||||
m_autoLongImageMode->setChecked(Settings::instance()->autoLongImageMode());
|
||||
@@ -172,6 +175,10 @@ SettingsDialog::SettingsDialog(QWidget *parent)
|
||||
Settings::instance()->setUseBuiltInCloseAnimation(state == Qt::Checked);
|
||||
});
|
||||
|
||||
connect(m_showTitleBar, &QCHECKBOX_CHECKSTATECHANGED, this, [ = ](QT_CHECKSTATE state){
|
||||
Settings::instance()->setShowTitleBar(state == Qt::Checked);
|
||||
});
|
||||
|
||||
connect(m_useLightCheckerboard, &QCHECKBOX_CHECKSTATECHANGED, this, [ = ](QT_CHECKSTATE state){
|
||||
Settings::instance()->setUseLightCheckerboard(state == Qt::Checked);
|
||||
});
|
||||
|
||||
@@ -24,6 +24,7 @@ public slots:
|
||||
private:
|
||||
QCheckBox * m_stayOnTop = nullptr;
|
||||
QCheckBox * m_useBuiltInCloseAnimation = nullptr;
|
||||
QCheckBox * m_showTitleBar = nullptr;
|
||||
QCheckBox * m_useLightCheckerboard = nullptr;
|
||||
QCheckBox * m_loopGallery = nullptr;
|
||||
QCheckBox * m_autoLongImageMode = nullptr;
|
||||
|
||||
218
app/titlebar.cpp
Normal file
218
app/titlebar.cpp
Normal file
@@ -0,0 +1,218 @@
|
||||
// SPDX-FileCopyrightText: 2025 Gary Wang <git@blumia.net>
|
||||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#include "titlebar.h"
|
||||
|
||||
#include "opacityhelper.h"
|
||||
|
||||
#include <QPainter>
|
||||
#include <QStyle>
|
||||
#include <QMouseEvent>
|
||||
#include <QEvent>
|
||||
#include <QEnterEvent>
|
||||
#include <QWindow>
|
||||
#include <QCursor>
|
||||
|
||||
TitleBar::TitleBar(QWidget *parent)
|
||||
: QWidget(parent)
|
||||
, m_opacityHelper(new OpacityHelper(this))
|
||||
, m_closeIcon(QStringLiteral(":/icons/window-close.svg"))
|
||||
{
|
||||
setFixedHeight(32);
|
||||
setMouseTracking(true);
|
||||
setAttribute(Qt::WA_Hover, true);
|
||||
|
||||
if (QWidget *win = window())
|
||||
win->installEventFilter(this);
|
||||
}
|
||||
|
||||
void TitleBar::setOpacity(qreal opacity, bool animated)
|
||||
{
|
||||
m_opacityHelper->setOpacity(opacity, animated);
|
||||
}
|
||||
|
||||
void TitleBar::setCloseButtonVisible(bool visible)
|
||||
{
|
||||
if (m_closeButtonVisible == visible)
|
||||
return;
|
||||
m_closeButtonVisible = visible;
|
||||
if (!visible) {
|
||||
m_closeHovered = false;
|
||||
m_closePressed = false;
|
||||
}
|
||||
update();
|
||||
}
|
||||
|
||||
void TitleBar::setCloseButtonOnly(bool only)
|
||||
{
|
||||
if (m_closeButtonOnly == only)
|
||||
return;
|
||||
m_closeButtonOnly = only;
|
||||
update();
|
||||
}
|
||||
|
||||
QRect TitleBar::closeButtonRect() const
|
||||
{
|
||||
const int btnWidth = closeButtonWidth();
|
||||
return QRect(width() - btnWidth, 0, btnWidth, height());
|
||||
}
|
||||
|
||||
void TitleBar::paintEvent(QPaintEvent *event)
|
||||
{
|
||||
Q_UNUSED(event);
|
||||
QPainter painter(this);
|
||||
|
||||
// Subtle translucent backdrop so the title bar region is distinguishable
|
||||
// (similar to the bottom button group). Skipped in close-button-only mode.
|
||||
if (!m_closeButtonOnly) {
|
||||
painter.fillRect(rect(), QColor(0, 0, 0, 120));
|
||||
}
|
||||
|
||||
const QRect closeRect = closeButtonRect();
|
||||
|
||||
// Title text (leave room for the close button).
|
||||
QRect labelRect = rect().adjusted(8, 0, 0, 0);
|
||||
if (m_closeButtonVisible)
|
||||
labelRect.setRight(closeRect.left() - 2);
|
||||
|
||||
const QString title = window() ? window()->windowTitle() : QString();
|
||||
if (!m_closeButtonOnly && !title.isEmpty()) {
|
||||
const QString elided = painter.fontMetrics().elidedText(title, Qt::ElideRight, labelRect.width());
|
||||
const int flags = Qt::AlignLeft | Qt::AlignVCenter | Qt::TextSingleLine;
|
||||
painter.setPen(Qt::black);
|
||||
painter.drawText(labelRect.adjusted(1, 1, 1, 1), flags, elided);
|
||||
painter.setPen(Qt::white);
|
||||
painter.drawText(labelRect, flags, elided);
|
||||
}
|
||||
|
||||
if (m_closeButtonVisible) {
|
||||
if (m_closeHovered) {
|
||||
painter.fillRect(closeRect,
|
||||
m_closePressed ? QColor(0xC5, 0x0F, 0x1F)
|
||||
: QColor(0xE8, 0x11, 0x23));
|
||||
}
|
||||
const int sz = height() / 3 * 2;
|
||||
const QRect iconRect = QStyle::alignedRect(layoutDirection(), Qt::AlignCenter,
|
||||
QSize(sz, sz), closeRect);
|
||||
m_closeIcon.paint(&painter, iconRect);
|
||||
}
|
||||
}
|
||||
|
||||
void TitleBar::mousePressEvent(QMouseEvent *event)
|
||||
{
|
||||
if (event->button() != Qt::LeftButton) {
|
||||
QWidget::mousePressEvent(event);
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_closeButtonVisible && closeButtonRect().contains(event->pos())) {
|
||||
m_closePressed = true;
|
||||
m_dragPending = false;
|
||||
update();
|
||||
event->accept();
|
||||
return;
|
||||
}
|
||||
|
||||
m_dragPending = true;
|
||||
m_moveStartPos = event->pos();
|
||||
event->accept();
|
||||
}
|
||||
|
||||
void TitleBar::mouseMoveEvent(QMouseEvent *event)
|
||||
{
|
||||
if (m_closeButtonVisible) {
|
||||
const bool hovered = closeButtonRect().contains(event->pos());
|
||||
if (hovered != m_closeHovered) {
|
||||
m_closeHovered = hovered;
|
||||
update();
|
||||
}
|
||||
}
|
||||
|
||||
if (event->buttons() & Qt::LeftButton && m_dragPending
|
||||
&& !window()->isMaximized() && !window()->isFullScreen()) {
|
||||
if (QWindow *wh = window()->windowHandle()) {
|
||||
if (!wh->startSystemMove())
|
||||
window()->move(event->globalPosition().toPoint() - m_moveStartPos);
|
||||
}
|
||||
event->accept();
|
||||
}
|
||||
|
||||
QWidget::mouseMoveEvent(event);
|
||||
}
|
||||
|
||||
void TitleBar::mouseReleaseEvent(QMouseEvent *event)
|
||||
{
|
||||
if (event->button() == Qt::LeftButton) {
|
||||
const bool wasClosePressed = m_closePressed;
|
||||
m_closePressed = false;
|
||||
m_dragPending = false;
|
||||
update();
|
||||
if (wasClosePressed && m_closeButtonVisible
|
||||
&& closeButtonRect().contains(event->pos())) {
|
||||
emit closeRequested();
|
||||
event->accept();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
QWidget::mouseReleaseEvent(event);
|
||||
}
|
||||
|
||||
void TitleBar::mouseDoubleClickEvent(QMouseEvent *event)
|
||||
{
|
||||
if (event->button() == Qt::LeftButton
|
||||
&& !(m_closeButtonVisible && closeButtonRect().contains(event->pos()))) {
|
||||
emit maximizeToggleRequested();
|
||||
event->accept();
|
||||
return;
|
||||
}
|
||||
|
||||
QWidget::mouseDoubleClickEvent(event);
|
||||
}
|
||||
|
||||
void TitleBar::enterEvent(QEnterEvent *event)
|
||||
{
|
||||
Q_UNUSED(event);
|
||||
if (m_closeButtonVisible) {
|
||||
const bool hovered = closeButtonRect().contains(mapFromGlobal(QCursor::pos()));
|
||||
if (hovered != m_closeHovered) {
|
||||
m_closeHovered = hovered;
|
||||
update();
|
||||
}
|
||||
}
|
||||
|
||||
QWidget::enterEvent(event);
|
||||
}
|
||||
|
||||
void TitleBar::leaveEvent(QEvent *event)
|
||||
{
|
||||
if (m_closeHovered) {
|
||||
m_closeHovered = false;
|
||||
update();
|
||||
}
|
||||
|
||||
QWidget::leaveEvent(event);
|
||||
}
|
||||
|
||||
bool TitleBar::eventFilter(QObject *watched, QEvent *event)
|
||||
{
|
||||
if (watched == window()) {
|
||||
switch (event->type()) {
|
||||
case QEvent::WindowTitleChange:
|
||||
case QEvent::WindowStateChange:
|
||||
case QEvent::ActivationChange:
|
||||
update();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return QWidget::eventFilter(watched, event);
|
||||
}
|
||||
|
||||
QSize TitleBar::sizeHint() const
|
||||
{
|
||||
return QSize(0, 32);
|
||||
}
|
||||
60
app/titlebar.h
Normal file
60
app/titlebar.h
Normal file
@@ -0,0 +1,60 @@
|
||||
// SPDX-FileCopyrightText: 2025 Gary Wang <git@blumia.net>
|
||||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#ifndef TITLEBAR_H
|
||||
#define TITLEBAR_H
|
||||
|
||||
#include <QWidget>
|
||||
#include <QIcon>
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
class QPaintEvent;
|
||||
class QMouseEvent;
|
||||
class QEvent;
|
||||
class QEnterEvent;
|
||||
QT_END_NAMESPACE
|
||||
|
||||
class OpacityHelper;
|
||||
|
||||
class TitleBar : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit TitleBar(QWidget *parent = nullptr);
|
||||
|
||||
void setOpacity(qreal opacity, bool animated = true);
|
||||
void setCloseButtonVisible(bool visible);
|
||||
bool closeButtonOnly() const { return m_closeButtonOnly; }
|
||||
void setCloseButtonOnly(bool only);
|
||||
int closeButtonWidth() const { return qMax(height(), 46); }
|
||||
|
||||
signals:
|
||||
void closeRequested();
|
||||
void maximizeToggleRequested();
|
||||
|
||||
protected:
|
||||
void paintEvent(QPaintEvent *event) override;
|
||||
void mousePressEvent(QMouseEvent *event) override;
|
||||
void mouseMoveEvent(QMouseEvent *event) override;
|
||||
void mouseReleaseEvent(QMouseEvent *event) override;
|
||||
void mouseDoubleClickEvent(QMouseEvent *event) override;
|
||||
void enterEvent(QEnterEvent *event) override;
|
||||
void leaveEvent(QEvent *event) override;
|
||||
bool eventFilter(QObject *watched, QEvent *event) override;
|
||||
QSize sizeHint() const override;
|
||||
|
||||
private:
|
||||
QRect closeButtonRect() const;
|
||||
|
||||
OpacityHelper *m_opacityHelper;
|
||||
QIcon m_closeIcon;
|
||||
bool m_closeButtonVisible = true;
|
||||
bool m_closeButtonOnly = false;
|
||||
bool m_closeHovered = false;
|
||||
bool m_closePressed = false;
|
||||
bool m_dragPending = false;
|
||||
QPoint m_moveStartPos;
|
||||
};
|
||||
|
||||
#endif // TITLEBAR_H
|
||||
@@ -11,7 +11,7 @@
|
||||
#include <QGraphicsOpacityEffect>
|
||||
#include <QPropertyAnimation>
|
||||
|
||||
ToolButton::ToolButton(bool hoverColor, QWidget *parent)
|
||||
ToolButton::ToolButton(QWidget *parent)
|
||||
: QPushButton(parent)
|
||||
, m_opacityHelper(new OpacityHelper(this))
|
||||
{
|
||||
@@ -19,11 +19,6 @@ ToolButton::ToolButton(bool hoverColor, QWidget *parent)
|
||||
QString qss = "QPushButton {"
|
||||
"background: transparent;"
|
||||
"}";
|
||||
if (hoverColor) {
|
||||
qss += "QPushButton:hover {"
|
||||
"background: red;"
|
||||
"}";
|
||||
}
|
||||
setStyleSheet(qss);
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ class ToolButton : public QPushButton
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
ToolButton(bool hoverColor = false, QWidget * parent = nullptr);
|
||||
ToolButton(QWidget * parent = nullptr);
|
||||
void setIconResourcePath(const QString &iconp);
|
||||
|
||||
public slots:
|
||||
|
||||
Reference in New Issue
Block a user