Files
create/src/Gui/OverlayWidgets.h
2025-11-11 13:49:01 +01:00

752 lines
21 KiB
C++

/****************************************************************************
* Copyright (c) 2020 Zheng Lei (realthunder) <realthunder.dev@gmail.com> *
* *
* This file is part of the FreeCAD CAx development system. *
* *
* This library is free software; you can redistribute it and/or *
* modify it under the terms of the GNU Library General Public *
* License as published by the Free Software Foundation; either *
* version 2 of the License, or (at your option) any later version. *
* *
* This library 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 Library General Public License for more details. *
* *
* You should have received a copy of the GNU Library General Public *
* License along with this library; see the file COPYING.LIB. If not, *
* write to the Free Software Foundation, Inc., 59 Temple Place, *
* Suite 330, Boston, MA 02111-1307, USA *
* *
****************************************************************************/
#ifndef FC_OVERLAYWIDGETS_H
#define FC_OVERLAYWIDGETS_H
#include <QTabWidget>
#include <QTimer>
#include <QTime>
#include <QAction>
#include <QToolButton>
#include <QGraphicsEffect>
#include <QImage>
#include <QDockWidget>
#include <QSplitter>
#include <QMenu>
#include <Base/Parameter.h>
#include "OverlayManager.h"
class QPropertyAnimation;
class QLayoutItem;
namespace Gui
{
class OverlayTabWidget;
class OverlayTitleBar;
class OverlaySplitterHandle;
class OverlaySizeGrip;
class OverlayProxyWidget;
class OverlayGraphicsEffect;
class OverlayDragFrame;
/// Tab widget to contain dock widgets in overlay mode
class OverlayTabWidget: public QTabWidget
{
Q_OBJECT
/** @name Graphics effect properties for customization through stylesheet */
//@{
Q_PROPERTY(
QColor effectColor READ effectColor WRITE setEffectColor
) // clazy:exclude=qproperty-without-notify
Q_PROPERTY(
int effectWidth READ effectWidth WRITE setEffectWidth
) // clazy:exclude=qproperty-without-notify
Q_PROPERTY(
int effectHeight READ effectHeight WRITE setEffectHeight
) // clazy:exclude=qproperty-without-notify
Q_PROPERTY(
qreal effectOffsetX READ effectOffsetX WRITE setEffectOffsetX
) // clazy:exclude=qproperty-without-notify
Q_PROPERTY(
qreal effectOffsetY READ effectOffsetY WRITE setEffectOffsetY
) // clazy:exclude=qproperty-without-notify
Q_PROPERTY(
qreal effectBlurRadius READ effectBlurRadius WRITE setEffectBlurRadius
) // clazy:exclude=qproperty-without-notify
Q_PROPERTY(
bool enableEffect READ effectEnabled WRITE setEffectEnabled
) // clazy:exclude=qproperty-without-notify
Q_PROPERTY(qreal animation READ animation WRITE setAnimation) // clazy:exclude=qproperty-without-notify
//@}
public:
/** Constructor
* @param parent: parent widget
* @param pos: docking position
*/
OverlayTabWidget(QWidget* parent, Qt::DockWidgetArea pos);
/// Enable/disable overlay mode for this tab widget
void setOverlayMode(bool enable);
/// Update splitter handle according to overlay status
void updateSplitterHandles();
/** Add a dock widget
* @param widget: dock widget to be added
* @param title: title for the dock widget
*/
void addWidget(QDockWidget* widget, const QString& title);
/** Remove a dock widget
* @param widget: dock widget to be removed
* @param last: optional non overlaid widget. If given, then the removed
* dock widget will be tabified together with this one.
*/
void removeWidget(QDockWidget* widget, QDockWidget* last = nullptr);
/** Set the current dock widget
* @param widget: an overlay dock widget
*
* All other dock widget in the same tab widget will be collapsed, and the
* give widget will take the whole space. This is actually handled inside
* onCurrentChanged().
*/
void setCurrent(QDockWidget* widget);
/** Handle ESC key press
*
* If the this overlay tab widget is hidden and its hint/tabs are visible,
* pressing ESC will hide the hint and tabs.
*
* If this overlay tab widget is visible and the current mouse cursor is on
* top of the tab widget title bar or any of its split handler, then
* pressing ESC will hide the title bar and split handler.
*/
bool onEscape();
/// Enable/disable transparent background mode
void setTransparent(bool enable);
/// Check if transparent background mode is active
bool isTransparent() const;
/// Auto mode to show or hide the tab widget
enum class AutoMode
{
/// No auto show or hide
NoAutoMode,
/// Auto hide tab widget on lost of focus
AutoHide,
/// Auto show tab widget on any new editing task
EditShow,
/// Auto hide tab widget on any new editing task
EditHide,
/// Auto show on any task panel e.g. suggestive task panel in PartDesign
TaskShow,
};
/// Set auto mode to show or hide the tab widget
void setAutoMode(AutoMode mode);
/// Get current auto mode
AutoMode getAutoMode() const
{
return autoMode;
}
/// Touch the tab widget to trigger saving of settings
void touch()
{
touched = true;
}
/// Check if the tab widget settings need to be saved
bool isTouched() const
{
return touched;
}
/// Set geometry of this tab widget
void setRect(QRect rect);
/// Get the geometry of this tab widget
const QRect& getRect();
/// Overlay query option
enum class QueryOption
{
/// Report the current overlay status
QueryOverlay,
/// Report true if transparency status has been changed
TransparencyChanged,
/// Report true if transparency status has not been changed
TransparencyNotChanged,
};
/// Query overlay status
bool isOverlaid(QueryOption option = QueryOption::QueryOverlay) const;
/** Check if needs to auto hide this tab widget
*
* Besides when auto hide mode is activated by user, the tab widget will
* also auto hide if the current view does not have panning capability
* (queried through MdiView::hasMsg("CanPan")). The user can explicitly
* disable this auto hide if no pan by setting user parameter
* View/DockOverlayAutoView, or preference option Display -> UI -> Auto
* hide in non 3D view.
*/
bool checkAutoHide() const;
/** Obtain geometry of auto hiding tab widget
* @param rect: output geometry of the tab widget
* @return Return true if the tab widget should be auto hiding
*/
bool getAutoHideRect(QRect& rect) const;
/// Handler of various actions exposed as buttons on title bar
void onAction(QAction*);
/// Sync relevant actions status with the current auto mode
void syncAutoMode();
// Establish if stylesheet is dark
static bool isStyleSheetDark(std::string curStyleSheet);
// Rotate the AutoHide icon according to the dock area
static QPixmap rotateAutoHideIcon(QPixmap pxAutoHide, Qt::DockWidgetArea dockArea);
/** Set tab widget position offset
* @param ofs: the offset size. Width is the x offset for top and bottom
* docking tab widget, and y offset for left and right ones. Height is the y
* offset for top and bottom, and x offset for left and right ones.
*/
void setOffset(const QSize& ofs);
/// Get the tab widget position offset
const QSize& getOffset() const
{
return offset;
}
/** Set tab widget size delta
* @param delta: the size delta. For left and right widget, the delta is
* added to the height of the tab widget. For top and bottom widget, it is
* added to the width.
*/
void setSizeDelta(int delta);
/// Get the tab widget size delta
int getSizeDelta() const
{
return sizeDelta;
}
/// Obtain the proxy widget
OverlayProxyWidget* getProxyWidget()
{
return proxyWidget;
}
/// Obtain the current dock widget
QDockWidget* currentDockWidget() const;
/// Obtain the dock widget by index
QDockWidget* dockWidget(int index) const;
/// Obtain the index of a given dock widget
int dockWidgetIndex(QDockWidget*) const;
/// Set the title bar for this tab widget
void setTitleBar(QWidget*);
/// Get the splitter
QSplitter* getSplitter() const
{
return splitter;
}
/// Get the title bar
QWidget* getTitleBar() const
{
return titleBar;
}
/// Get the docking position of this tab widget
Qt::DockWidgetArea getDockArea() const
{
return dockArea;
}
/// Get delay time for animated reveal
const QTime& getRevealTime() const
{
return revealTime;
}
/// Set delay time for animated reveal
void setRevealTime(const QTime& time);
/// Restore state
void restore(ParameterGrp::handle handle);
/// Save tab orders and positions
void saveTabs();
/** @name Graphics effect properties setters and getters */
//@{
QColor effectColor() const;
void setEffectColor(const QColor&);
int effectWidth() const;
void setEffectWidth(int);
int effectHeight() const;
void setEffectHeight(int);
qreal effectOffsetX() const;
void setEffectOffsetX(qreal);
qreal effectOffsetY() const;
void setEffectOffsetY(qreal);
qreal effectBlurRadius() const;
void setEffectBlurRadius(qreal);
bool effectEnabled() const;
void setEffectEnabled(bool);
qreal animation() const
{
return _animation;
}
void setAnimation(qreal);
//@}
/// Schedule for repaint
void scheduleRepaint();
/** Return the pixel alpha value at the give position
* @param pos: position
* @param radiusScale: scale of the radius to check for alpha.
*
* @return Returns the largest alpha value of a circular area of 'pos' as
* center and radius as defined by user parameter
* View/DockOverlayAlphaRadius. May return -1 if out side of the widget, or
* zero if transparent.
*/
int testAlpha(const QPoint& pos, int radiusScale);
/// Start animated showing
void startShow();
/// Start animated hiding
void startHide();
/// Internal state of the tab widget
enum class State
{
/// The tab widget is showing
Showing,
/// Normal visible state
Normal,
/// Visual hint is visible
Hint,
/// Hint is hidden by user after pressing ESC
HintHidden,
/// The tab widget is explicitly hidden by user
Hidden,
};
/// Set state of the tab widget
void setState(State);
/// Get the state of the widget
State getState() const
{
return _state;
}
/// Handle splitter resize
void onSplitterResize(int index);
/// Check if the tab widget is saving its state
bool isSaving() const
{
return _saving;
}
/// Helper function to create title bar for a dock widget
static QWidget* createTitleButton(QAction* action, int size);
/// Helper function to prepare a widget as a title widget
static QLayoutItem* prepareTitleWidget(QWidget* widget, const QList<QAction*>& actions);
protected:
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
void enterEvent(QEvent* ev) override;
#else
void enterEvent(QEnterEvent* ev) override;
#endif
void leaveEvent(QEvent* ev) override;
void changeEvent(QEvent* ev) override;
void resizeEvent(QResizeEvent* ev) override;
void paintEvent(QPaintEvent* ev) override;
bool event(QEvent* ev) override;
bool eventFilter(QObject* obj, QEvent* ev) override;
void retranslate();
void refreshIcons();
/// Overlay mode options
enum class OverlayOption
{
/// Enable overlay
Enable,
/// Disable overlay
Disable,
/// Enable overlay and show tab bar
ShowTab,
};
/// Toggle overlay mode for a given widget
void setOverlayMode(QWidget* widget, OverlayOption option);
/// Helper function to set overlay mode for a give widget
static void _setOverlayMode(QWidget* widget, OverlayOption option);
protected:
void onCurrentChanged(int index);
void onTabMoved(int from, int to);
void onRepaint();
void onAnimationStateChanged();
void setupLayout();
void onSizeGripMove(const QPoint&);
private:
friend class OverlayProxyWidget;
friend class OverlayTitleBar;
friend class OverlayManager;
friend class OverlayManager::Private;
friend class OverlaySplitterHandle;
friend class OverlaySizeGrip;
QSize offset;
int sizeDelta = 0;
QRect rectOverlay;
OverlayProxyWidget* proxyWidget;
QSplitter* splitter = nullptr;
QWidget* titleBar = nullptr;
QAction actNoAutoMode;
QAction actAutoHide;
QAction actEditHide;
QAction actEditShow;
QAction actTaskShow;
QAction actAutoMode;
QMenu autoModeMenu;
QAction actTransparent;
QAction actIncrease;
QAction actDecrease;
QAction actOverlay;
QTimer timer;
QTimer repaintTimer;
AutoMode autoMode = AutoMode::NoAutoMode;
bool repainting = false;
bool overlaid = false;
bool currentTransparent = false;
bool touched = false;
bool busy = false;
Qt::DockWidgetArea dockArea;
int tabSize = 0;
QTime revealTime;
ParameterGrp::handle hGrp;
OverlayGraphicsEffect* _graphicsEffect = nullptr;
OverlayGraphicsEffect* _graphicsEffectTab = nullptr;
bool _effectEnabled = false;
QImage _image;
qreal _imageScale;
qreal _animation = 0;
QPropertyAnimation* _animator = nullptr;
State _state = State::Normal;
std::map<QDockWidget*, int> _sizemap;
bool _saving = false;
// NOLINTBEGIN
static OverlayDragFrame* _DragFrame;
static QDockWidget* _DragFloating;
static QWidget* _Dragging;
static OverlayTabWidget* _LeftOverlay;
static OverlayTabWidget* _RightOverlay;
static OverlayTabWidget* _TopOverlay;
static OverlayTabWidget* _BottomOverlay;
// NOLINTEND
};
/// A translucent frame as a visual indicator when dragging a dock widget
class OverlayDragFrame: public QWidget
{
Q_OBJECT
public:
explicit OverlayDragFrame(QWidget* parent);
QSize sizeHint() const override;
QSize minimumSizeHint() const override;
protected:
void paintEvent(QPaintEvent* ev) override;
};
/// Title bar for OverlayTabWidget
class OverlayTitleBar: public QWidget
{
Q_OBJECT
public:
explicit OverlayTitleBar(QWidget* parent);
void setTitleItem(QLayoutItem*);
void endDrag();
protected:
void mouseMoveEvent(QMouseEvent* ev) override;
void mousePressEvent(QMouseEvent* ev) override;
void mouseReleaseEvent(QMouseEvent* ev) override;
void paintEvent(QPaintEvent* ev) override;
void keyPressEvent(QKeyEvent* ke) override;
void timerEvent(QTimerEvent* te) override;
private:
QPoint dragOffset;
QSize dragSize;
QLayoutItem* titleItem = nullptr;
QColor textcolor;
int timerId = 0;
bool blink = false;
bool mouseMovePending = false;
bool ignoreMouse = false;
};
/// Size grip for title bar and split handler of OverlayTabWidget
class OverlaySizeGrip: public QWidget
{
Q_OBJECT
public:
OverlaySizeGrip(QWidget* parent, bool vertical);
Q_SIGNALS:
void dragMove(const QPoint& globalPos);
protected:
void paintEvent(QPaintEvent* ev) override;
void mouseMoveEvent(QMouseEvent* ev) override;
void mousePressEvent(QMouseEvent* ev) override;
void mouseReleaseEvent(QMouseEvent* ev) override;
const QPixmap& pixmap() const;
private:
bool vertical;
};
/// Splitter for OverlayTabWidget
class OverlaySplitter: public QSplitter
{
Q_OBJECT
public:
explicit OverlaySplitter(QWidget* parent);
void retranslate();
protected:
QSplitterHandle* createHandle() override;
};
/// Splitter handle for dragging the splitter
class OverlaySplitterHandle: public QSplitterHandle
{
Q_OBJECT
public:
friend class OverlaySplitter;
OverlaySplitterHandle(Qt::Orientation, QSplitter* parent);
void setTitleItem(QLayoutItem*);
void retranslate();
void refreshIcons();
QDockWidget* dockWidget();
void showTitle(bool enable);
bool isShowing() const
{
return _showTitle;
}
void endDrag();
protected:
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
void enterEvent(QEvent* ev) override;
#else
void enterEvent(QEnterEvent* ev) override;
#endif
void showEvent(QShowEvent* ev) override;
void leaveEvent(QEvent* ev) override;
void paintEvent(QPaintEvent* ev) override;
void changeEvent(QEvent* ev) override;
void mouseMoveEvent(QMouseEvent* ev) override;
void mousePressEvent(QMouseEvent* ev) override;
void mouseReleaseEvent(QMouseEvent* ev) override;
void keyPressEvent(QKeyEvent* ev) override;
QSize sizeHint() const override;
protected:
void onAction();
void onTimer();
private:
QLayoutItem* titleItem = nullptr;
int idx = -1;
QAction actFloat;
bool _showTitle = true;
int dragging = 0;
QPoint dragOffset;
QSize dragSize;
QTimer timer;
};
/// Tool button for the title bar of the OverlayTabWidget
class OverlayToolButton: public QToolButton
{
Q_OBJECT
public:
explicit OverlayToolButton(QWidget* parent);
};
/** Class for handling visual hint for bringing back hidden overlay dock widget
*
* The proxy widget is transparent except a customizable rectangle area with a
* selectable color shown as the visual hint. The hint is normally hidden, and
* is shown only if the mouse hovers within the widget. When the hint area is
* clicked, it will bring back hidden overlay dock panel. Note that the proxy
* widget itself is mouse transparent as well, meaning that it will not receive
* any mouse event. It is handled in the OverlayManager event filter.
*/
class OverlayProxyWidget: public QWidget
{
Q_OBJECT
Q_PROPERTY(QBrush hintColor READ hintColor WRITE setHintColor) // clazy:exclude=qproperty-without-notify
public:
explicit OverlayProxyWidget(OverlayTabWidget*);
OverlayTabWidget* getOwner() const
{
return owner;
}
/// For representing hit region
enum class HitTest
{
/// Not hitting
HitNone = 0,
/// Hitting the proxy widget size but not within the visible hint area.
HitOuter = 1,
/// Hitting the visible hint area.
HitInner = 2,
};
/** Mouse cursor hit test
* @param pos: cursor position
* @param delay: Whether to delay showing hint on mouse hit
*/
HitTest hitTest(const QPoint& pos, bool delay = true);
/// Check if the visual hint is showing
bool isActivated() const;
QBrush hintColor() const;
void setHintColor(const QBrush&);
QRect getRect() const;
void onMousePress();
protected:
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
void enterEvent(QEvent* ev) override;
#else
void enterEvent(QEnterEvent* ev) override;
#endif
void hideEvent(QHideEvent* ev) override;
void paintEvent(QPaintEvent* ev) override;
protected:
void onTimer();
private:
OverlayTabWidget* owner;
bool drawLine = false;
int dockArea;
QTimer timer;
QBrush _hintColor;
};
/** Graphic effects for drawing shadow and outline of text on transparent background
*
* Modified from https://stackoverflow.com/a/23752747
*/
class OverlayGraphicsEffect: public QGraphicsEffect
{
Q_OBJECT
public:
explicit OverlayGraphicsEffect(QObject* parent);
void draw(QPainter* painter) override;
QRectF boundingRectFor(const QRectF& rect) const override;
inline void setSize(const QSize& size)
{
if (_size != size) {
_size = size;
updateBoundingRect();
}
}
inline QSize size() const
{
return _size;
}
inline void setOffset(const QPointF& offset)
{
if (_offset != offset) {
_offset = offset;
updateBoundingRect();
}
}
inline QPointF offset() const
{
return _offset;
}
inline void setBlurRadius(qreal blurRadius)
{
if (_blurRadius != blurRadius) {
_blurRadius = blurRadius;
updateBoundingRect();
}
}
inline qreal blurRadius() const
{
return _blurRadius;
}
inline void setColor(const QColor& color)
{
_color = color;
}
inline QColor color() const
{
return _color;
}
inline bool enabled() const
{
return _enabled;
}
inline void setEnabled(bool enabled)
{
if (_enabled != enabled) {
_enabled = enabled;
updateBoundingRect();
}
}
private:
bool _enabled;
QSize _size;
qreal _blurRadius;
QColor _color;
QPointF _offset;
};
} // namespace Gui
#endif // FC_OVERLAYWIDGETS_H