From 8306b2d70f86dc94d9e12813d46b5e11cf76da51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ole-Andr=C3=A9=20Rodlie?= Date: Fri, 2 Jan 2026 19:08:22 +0100 Subject: [PATCH 1/5] Add persistent menu to gizmos/snap/grid buttons --- src/ui/CMakeLists.txt | 1 + src/ui/widgets/persistentmenu.h | 65 +++++++++++++++++++++++++++++++++ src/ui/widgets/toolinteract.cpp | 11 +++--- 3 files changed, 72 insertions(+), 5 deletions(-) create mode 100644 src/ui/widgets/persistentmenu.h diff --git a/src/ui/CMakeLists.txt b/src/ui/CMakeLists.txt index 50a00572f..c35a3d353 100644 --- a/src/ui/CMakeLists.txt +++ b/src/ui/CMakeLists.txt @@ -171,6 +171,7 @@ set( widgets/labeledslider.h widgets/markereditor.h widgets/performancesettingswidget.h + widgets/persistentmenu.h widgets/presetsettingswidget.h widgets/qdoubleslider.h widgets/qrealanimatorvalueslider.h diff --git a/src/ui/widgets/persistentmenu.h b/src/ui/widgets/persistentmenu.h new file mode 100644 index 000000000..a02c2f769 --- /dev/null +++ b/src/ui/widgets/persistentmenu.h @@ -0,0 +1,65 @@ +/* +# +# Friction - https://friction.graphics +# +# Copyright (c) Ole-AndrĂ© Rodlie and contributors +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, version 3. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +# See 'README.md' for more information. +# +*/ + +#ifndef FRICTION_PERSISTENTMENU_H +#define FRICTION_PERSISTENTMENU_H + +#include "ui_global.h" + +#include +#include +#include + +namespace Friction +{ + namespace Ui + { + class UI_EXPORT PersistentMenu : public QMenu + { + Q_OBJECT + public: + using QMenu::QMenu; + + PersistentMenu* addPersistentMenu(const QIcon &icon, + const QString &title) + { + PersistentMenu *subMenu = new PersistentMenu(title, this); + subMenu->setIcon(icon); + this->addMenu(subMenu); + return subMenu; + } + + protected: + void mouseReleaseEvent(QMouseEvent *event) override + { + QAction *action = actionAt(event->pos()); + if (action) { + action->activate(QAction::Trigger); + } else { + QMenu::mouseReleaseEvent(event); + } + } + }; + } +} + +#endif // FRICTION_PERSISTENTMENU_H diff --git a/src/ui/widgets/toolinteract.cpp b/src/ui/widgets/toolinteract.cpp index ba9910a09..df5d26fc6 100644 --- a/src/ui/widgets/toolinteract.cpp +++ b/src/ui/widgets/toolinteract.cpp @@ -24,6 +24,7 @@ #include "Private/document.h" #include "GUI/coloranimatorbutton.h" +#include "widgets/persistentmenu.h" #include #include @@ -50,7 +51,7 @@ ToolInteract::ToolInteract(QWidget *parent) void ToolInteract::setupGizmoButton() { const auto button = new QToolButton(this); - const auto menu = new QMenu(button); + const auto menu = new PersistentMenu(button); button->setObjectName("ToolBoxGizmo"); button->setPopupMode(QToolButton::MenuButtonPopup); @@ -155,7 +156,7 @@ void ToolInteract::setupSnapButton() { const auto grid = Document::sInstance->getGrid(); const auto button = new QToolButton(this); - const auto menu = new QMenu(button); + const auto menu = new PersistentMenu(button); button->setObjectName("ToolBoxGizmo"); button->setPopupMode(QToolButton::MenuButtonPopup); @@ -309,7 +310,7 @@ void ToolInteract::setupGridButton() { const auto grid = Document::sInstance->getGrid(); const auto button = new QToolButton(this); - const auto menu = new QMenu(button); + const auto menu = new PersistentMenu(button); button->setObjectName("ToolBoxGizmo"); button->setPopupMode(QToolButton::MenuButtonPopup); @@ -362,8 +363,8 @@ void ToolInteract::setupGridButton() } menu->addSeparator(); - const auto optMenu = menu->addMenu(QIcon::fromTheme("preferences"), - tr("Settings")); + const auto optMenu = menu->addPersistentMenu(QIcon::fromTheme("preferences"), + tr("Settings")); menu->addSeparator(); setupGridAction(button, Core::Grid::Option::SizeX); From b6bac1fe35c173eb25c06675008cd42714cad50d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ole-Andr=C3=A9=20Rodlie?= Date: Fri, 2 Jan 2026 20:56:07 +0100 Subject: [PATCH 2/5] Update persistentmenu.h Support persistent on checkable only. --- src/ui/widgets/persistentmenu.h | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/ui/widgets/persistentmenu.h b/src/ui/widgets/persistentmenu.h index a02c2f769..2b68472df 100644 --- a/src/ui/widgets/persistentmenu.h +++ b/src/ui/widgets/persistentmenu.h @@ -40,19 +40,29 @@ namespace Friction using QMenu::QMenu; PersistentMenu* addPersistentMenu(const QIcon &icon, - const QString &title) + const QString &title, + const bool onlyCheckable = false) { PersistentMenu *subMenu = new PersistentMenu(title, this); + subMenu->setOnlyCheckable(onlyCheckable); subMenu->setIcon(icon); this->addMenu(subMenu); return subMenu; } + void setOnlyCheckable(const bool checked) + { + mOnlyCheckable = checked; + } + + private: + bool mOnlyCheckable = false; protected: void mouseReleaseEvent(QMouseEvent *event) override { QAction *action = actionAt(event->pos()); - if (action) { + if ((action && !mOnlyCheckable) || + (action && mOnlyCheckable && action->isCheckable())) { action->activate(QAction::Trigger); } else { QMenu::mouseReleaseEvent(event); From 727151e9cd841ad0a6c64bd9e9048ea15de86f5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ole-Andr=C3=A9=20Rodlie?= Date: Fri, 2 Jan 2026 20:59:28 +0100 Subject: [PATCH 3/5] Menu: use persistent view menu Only checkable actions and zoom/filtering submenu is persistent. Only for Linux and Windows. --- src/app/GUI/mainwindow.h | 10 +++++++++- src/app/GUI/menu.cpp | 19 ++++++++++++++++++- 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/src/app/GUI/mainwindow.h b/src/app/GUI/mainwindow.h index cc5b50fe5..9a78c9318 100644 --- a/src/app/GUI/mainwindow.h +++ b/src/app/GUI/mainwindow.h @@ -59,6 +59,10 @@ #include "widgets/uilayout.h" #include "widgets/toolbox.h" +#ifndef Q_OS_MAC +#include "widgets/persistentmenu.h" +#endif + class VideoEncoder; class RenderWidget; class ActionButton; @@ -280,7 +284,11 @@ class MainWindow : public QMainWindow QMenu *mPathMenu; QMenu *mEffectsMenu; QMenu *mSceneMenu; - QMenu *mViewMenu; +#ifndef Q_OS_MAC + Friction::Ui::PersistentMenu *mViewMenu; +#else + QMenu *mViewMenu +#endif QMenu *mPanelsMenu; QMenu *mRenderMenu; diff --git a/src/app/GUI/menu.cpp b/src/app/GUI/menu.cpp index eb7c6efd2..48506ff25 100644 --- a/src/app/GUI/menu.cpp +++ b/src/app/GUI/menu.cpp @@ -333,7 +333,13 @@ void MainWindow::setupMenuBar() }); cmdAddAction(clearRecentAct); +#ifndef Q_OS_MAC + mViewMenu = new Ui::PersistentMenu(tr("View", "MenuBar"), this); + mViewMenu->setOnlyCheckable(true); + mMenuBar->addMenu(mViewMenu); +#else mViewMenu = mMenuBar->addMenu(tr("View", "MenuBar")); +#endif mObjectMenu = mMenuBar->addMenu(tr("Object", "MenuBar")); @@ -505,7 +511,13 @@ void MainWindow::setupMenuBar() mEffectsMenu->setEnabled(false); setupMenuEffects(); - const auto zoomMenu = mViewMenu->addMenu(QIcon::fromTheme("zoom"), tr("Zoom","MenuBar_View")); +#ifndef Q_OS_MAC + const auto zoomMenu = mViewMenu->addPersistentMenu(QIcon::fromTheme("zoom"), + tr("Zoom","MenuBar_View")); +#else + const auto zoomMenu = mViewMenu->addMenu(QIcon::fromTheme("zoom"), + tr("Zoom","MenuBar_View")); +#endif mZoomInAction = zoomMenu->addAction(tr("Zoom In", "MenuBar_View_Zoom")); mZoomInAction->setIcon(QIcon::fromTheme("zoom_in")); @@ -566,8 +578,13 @@ void MainWindow::setupMenuBar() }); cmdAddAction(mResetZoomAction); +#ifndef Q_OS_MAC + const auto filteringMenu = mViewMenu->addPersistentMenu(QIcon::fromTheme("user-desktop"), + tr("Filtering", "MenuBar_View")); +#else const auto filteringMenu = mViewMenu->addMenu(QIcon::fromTheme("user-desktop"), tr("Filtering", "MenuBar_View")); +#endif mNoneQuality = filteringMenu->addAction( tr("None", "MenuBar_View_Filtering"), [this]() { From 54991740ba83cf35c5f277bd6ec75991ab90924d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ole-Andr=C3=A9=20Rodlie?= Date: Fri, 2 Jan 2026 21:23:45 +0100 Subject: [PATCH 4/5] Update persistentmenu.h ignore menu action (sub menu). --- src/ui/widgets/persistentmenu.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/ui/widgets/persistentmenu.h b/src/ui/widgets/persistentmenu.h index 2b68472df..72e79ffe2 100644 --- a/src/ui/widgets/persistentmenu.h +++ b/src/ui/widgets/persistentmenu.h @@ -61,6 +61,10 @@ namespace Friction void mouseReleaseEvent(QMouseEvent *event) override { QAction *action = actionAt(event->pos()); + if (action && action->menu()) { + QMenu::mouseReleaseEvent(event); + return; + } if ((action && !mOnlyCheckable) || (action && mOnlyCheckable && action->isCheckable())) { action->activate(QAction::Trigger); From 26825a966d861c1a3ee7cceedb246b165656e194 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ole-Andr=C3=A9=20Rodlie?= Date: Fri, 2 Jan 2026 21:33:42 +0100 Subject: [PATCH 5/5] Update mainwindow.h MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit fix typo đŸ˜„ --- src/app/GUI/mainwindow.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/GUI/mainwindow.h b/src/app/GUI/mainwindow.h index 9a78c9318..4c8b4f549 100644 --- a/src/app/GUI/mainwindow.h +++ b/src/app/GUI/mainwindow.h @@ -287,7 +287,7 @@ class MainWindow : public QMainWindow #ifndef Q_OS_MAC Friction::Ui::PersistentMenu *mViewMenu; #else - QMenu *mViewMenu + QMenu *mViewMenu; #endif QMenu *mPanelsMenu; QMenu *mRenderMenu;