From c78e0965c043e8be320c458747b29927efa7b46f Mon Sep 17 00:00:00 2001 From: Thomas Hartmann Date: Wed, 3 May 2023 19:42:51 +0200 Subject: [PATCH] QmlDesigner: Implement transient scrollbar for widgets in studiostyle Task-number: QDS-9556 Task-number: QDS-10368 Task-number: QDS-10385 Change-Id: Idcbc70db3075f7741a754376580f48d7df40e67a Reviewed-by: Thomas Hartmann Reviewed-by: Qt CI Patch Build Bot --- src/libs/advanceddockingsystem/dockwidget.cpp | 17 + src/libs/advanceddockingsystem/dockwidget.h | 12 + src/libs/utils/styleanimator.cpp | 189 ++++++ src/libs/utils/styleanimator.h | 78 ++- src/libs/utils/transientscroll.cpp | 398 +++++++++-- src/libs/utils/transientscroll.h | 25 +- .../find/highlightscrollbarcontroller.cpp | 21 +- .../connectioneditor/connectionviewwidget.cpp | 1 - .../curveeditor/detail/graphicsview.cpp | 5 - .../components/edit3d/edit3dwidget.cpp | 1 - .../formeditor/formeditorwidget.cpp | 1 - .../navigator/navigatortreeview.cpp | 5 +- .../components/navigator/navigatorwidget.cpp | 1 - .../texteditor/texteditorwidget.cpp | 5 - .../timelineeditor/timelinewidget.cpp | 159 ++++- .../timelineeditor/timelinewidget.h | 31 +- .../transitioneditorwidget.cpp | 9 +- .../transitioneditor/transitioneditorwidget.h | 4 +- src/plugins/qmldesigner/designmodewidget.cpp | 6 +- src/plugins/qmldesignerbase/CMakeLists.txt | 1 + .../qmldesignerbase/studio/studiostyle.cpp | 627 +++++++++++------- .../qmldesignerbase/studio/studiostyle.h | 75 +-- .../qmldesignerbase/studio/studiostyle_p.cpp | 240 +++++++ .../qmldesignerbase/studio/studiostyle_p.h | 84 +++ 24 files changed, 1631 insertions(+), 364 deletions(-) create mode 100644 src/plugins/qmldesignerbase/studio/studiostyle_p.cpp create mode 100644 src/plugins/qmldesignerbase/studio/studiostyle_p.h diff --git a/src/libs/advanceddockingsystem/dockwidget.cpp b/src/libs/advanceddockingsystem/dockwidget.cpp index 229995b9322..c9ffcc1bef9 100644 --- a/src/libs/advanceddockingsystem/dockwidget.cpp +++ b/src/libs/advanceddockingsystem/dockwidget.cpp @@ -50,6 +50,7 @@ public: DockAreaWidget *m_dockArea = nullptr; QAction *m_toggleViewAction = nullptr; bool m_closed = false; + bool m_focused = false; QScrollArea *m_scrollArea = nullptr; QToolBar *m_toolBar = nullptr; Qt::ToolButtonStyle m_toolBarStyleDocked = Qt::ToolButtonIconOnly; @@ -219,6 +220,7 @@ void DockWidgetPrivate::setupScrollArea() m_scrollArea = new QScrollArea(q); m_scrollArea->setObjectName("dockWidgetScrollArea"); m_scrollArea->setWidgetResizable(true); + m_scrollArea->setProperty("focused", q->isFocused()); m_layout->addWidget(m_scrollArea); } @@ -439,6 +441,21 @@ bool DockWidget::isClosed() const return d->m_closed; } +void DockWidget::setFocused(bool focused) +{ + if (d->m_focused == focused) + return; + + d->m_focused = focused; + if (d->m_scrollArea) + d->m_scrollArea->setProperty("focused", focused); +} + +bool DockWidget::isFocused() const +{ + return d->m_focused; +} + QAction *DockWidget::toggleViewAction() const { return d->m_toggleViewAction; diff --git a/src/libs/advanceddockingsystem/dockwidget.h b/src/libs/advanceddockingsystem/dockwidget.h index 4f9d13be290..72ac4789cb6 100644 --- a/src/libs/advanceddockingsystem/dockwidget.h +++ b/src/libs/advanceddockingsystem/dockwidget.h @@ -32,6 +32,8 @@ class AutoHideSideBar; class ADS_EXPORT DockWidget : public QFrame { Q_OBJECT + Q_PROPERTY(bool focused READ isFocused WRITE setFocused) + private: DockWidgetPrivate *d; ///< private data (pimpl) friend class DockWidgetPrivate; @@ -367,6 +369,16 @@ public: */ bool isClosed() const; + /** + * Sets the focus property for widget + */ + void setFocused(bool focused); + + /** + * Returns true if this dock widget is focused. + */ + bool isFocused() const; + /** * Returns a checkable action that can be used to show or close this dock widget. * The action's text is set to the dock widget's window title. diff --git a/src/libs/utils/styleanimator.cpp b/src/libs/utils/styleanimator.cpp index 05973677323..02d829711d0 100644 --- a/src/libs/utils/styleanimator.cpp +++ b/src/libs/utils/styleanimator.cpp @@ -5,12 +5,17 @@ #include "algorithm.h" +#include +#include #include #include #include using namespace Utils; +static const qreal ScrollBarFadeOutDuration = 200.0; +static const qreal ScrollBarFadeOutDelay = 450.0; + Animation * StyleAnimator::widgetAnimation(const QWidget *widget) const { if (!widget) @@ -125,3 +130,187 @@ void StyleAnimator::startAnimation(Animation *t) if (animations.size() > 0 && !animationTimer.isActive()) animationTimer.start(35, this); } + +QStyleAnimation::QStyleAnimation(QObject *target) + : QAbstractAnimation(target) + , m_delay(0) + , m_duration(-1) + , m_startTime(QTime::currentTime()) + , m_fps(ThirtyFps) + , m_skip(0) +{} + +QStyleAnimation::~QStyleAnimation() {} + +QObject *QStyleAnimation::target() const +{ + return parent(); +} + +int QStyleAnimation::duration() const +{ + return m_duration; +} + +void QStyleAnimation::setDuration(int duration) +{ + m_duration = duration; +} + +int QStyleAnimation::delay() const +{ + return m_delay; +} + +void QStyleAnimation::setDelay(int delay) +{ + m_delay = delay; +} + +QTime QStyleAnimation::startTime() const +{ + return m_startTime; +} + +void QStyleAnimation::setStartTime(const QTime &time) +{ + m_startTime = time; +} + +QStyleAnimation::FrameRate QStyleAnimation::frameRate() const +{ + return m_fps; +} + +void QStyleAnimation::setFrameRate(FrameRate fps) +{ + m_fps = fps; +} + +void QStyleAnimation::updateTarget() +{ + QEvent event(QEvent::StyleAnimationUpdate); + event.setAccepted(false); + QCoreApplication::sendEvent(target(), &event); + if (!event.isAccepted()) + stop(); +} + +void QStyleAnimation::start() +{ + m_skip = 0; + QAbstractAnimation::start(DeleteWhenStopped); +} + +bool QStyleAnimation::isUpdateNeeded() const +{ + return currentTime() > m_delay; +} + +void QStyleAnimation::updateCurrentTime(int) +{ + if (++m_skip >= m_fps) { + m_skip = 0; + if (target() && isUpdateNeeded()) + updateTarget(); + } +} + +QNumberStyleAnimation::QNumberStyleAnimation(QObject *target) + : QStyleAnimation(target) + , m_start(0.0) + , m_end(1.0) + , m_prev(0.0) +{ + setDuration(250); +} + +qreal QNumberStyleAnimation::startValue() const +{ + return m_start; +} + +void QNumberStyleAnimation::setStartValue(qreal value) +{ + m_start = value; +} + +qreal QNumberStyleAnimation::endValue() const +{ + return m_end; +} + +void QNumberStyleAnimation::setEndValue(qreal value) +{ + m_end = value; +} + +qreal QNumberStyleAnimation::currentValue() const +{ + qreal step = qreal(currentTime() - delay()) / (duration() - delay()); + return m_start + qMax(qreal(0), step) * (m_end - m_start); +} + +bool QNumberStyleAnimation::isUpdateNeeded() const +{ + if (QStyleAnimation::isUpdateNeeded()) { + qreal current = currentValue(); + if (!qFuzzyCompare(m_prev, current)) { + m_prev = current; + return true; + } + } + return false; +} + +QScrollbarStyleAnimation::QScrollbarStyleAnimation(Mode mode, QObject *target) + : QNumberStyleAnimation(target) + , m_mode(mode) + , m_active(false) +{ + switch (mode) { + case Activating: + setDuration(ScrollBarFadeOutDuration); + setStartValue(0.0); + setEndValue(1.0); + break; + case Deactivating: + setDuration(ScrollBarFadeOutDelay + ScrollBarFadeOutDuration); + setDelay(ScrollBarFadeOutDelay); + setStartValue(1.0); + setEndValue(0.0); + break; + } +} + +QScrollbarStyleAnimation::Mode QScrollbarStyleAnimation::mode() const +{ + return m_mode; +} + +bool QScrollbarStyleAnimation::wasActive() const +{ + return m_active; +} + +bool QScrollbarStyleAnimation::wasAdjacent() const +{ + return m_adjacent; +} + +void QScrollbarStyleAnimation::setActive(bool active) +{ + m_active = active; +} + +void QScrollbarStyleAnimation::setAdjacent(bool adjacent) +{ + m_adjacent = adjacent; +} + +void QScrollbarStyleAnimation::updateCurrentTime(int time) +{ + QNumberStyleAnimation::updateCurrentTime(time); + if (m_mode == Deactivating && qFuzzyIsNull(currentValue())) + target()->setProperty("visible", false); +} diff --git a/src/libs/utils/styleanimator.h b/src/libs/utils/styleanimator.h index 22ab0745b30..1cb0f67cc11 100644 --- a/src/libs/utils/styleanimator.h +++ b/src/libs/utils/styleanimator.h @@ -5,6 +5,7 @@ #include "utils_global.h" +#include #include #include #include @@ -13,6 +14,7 @@ QT_BEGIN_NAMESPACE class QPainter; class QStyleOption; +class QTimerEvent; QT_END_NAMESPACE namespace Utils { @@ -76,4 +78,78 @@ private: QBasicTimer animationTimer; QList animations; }; -} + +class QTCREATOR_UTILS_EXPORT QStyleAnimation : public QAbstractAnimation +{ + Q_OBJECT +public: + QStyleAnimation(QObject *target); + virtual ~QStyleAnimation(); + QObject *target() const; + int duration() const override; + void setDuration(int duration); + int delay() const; + void setDelay(int delay); + QTime startTime() const; + void setStartTime(const QTime &time); + enum FrameRate { DefaultFps, SixtyFps, ThirtyFps, TwentyFps, FifteenFps }; + FrameRate frameRate() const; + void setFrameRate(FrameRate fps); + void updateTarget(); +public Q_SLOTS: + void start(); + +protected: + virtual bool isUpdateNeeded() const; + virtual void updateCurrentTime(int time) override; + +private: + int m_delay; + int m_duration; + QTime m_startTime; + FrameRate m_fps; + int m_skip; +}; + +class QTCREATOR_UTILS_EXPORT QNumberStyleAnimation : public QStyleAnimation +{ + Q_OBJECT +public: + QNumberStyleAnimation(QObject *target); + qreal startValue() const; + void setStartValue(qreal value); + qreal endValue() const; + void setEndValue(qreal value); + qreal currentValue() const; + +protected: + bool isUpdateNeeded() const override; + +private: + qreal m_start; + qreal m_end; + mutable qreal m_prev; +}; + +class QTCREATOR_UTILS_EXPORT QScrollbarStyleAnimation : public QNumberStyleAnimation +{ + Q_OBJECT +public: + enum Mode { Activating, Deactivating }; + QScrollbarStyleAnimation(Mode mode, QObject *target); + Mode mode() const; + bool wasActive() const; + bool wasAdjacent() const; + void setActive(bool active); + void setAdjacent(bool adjacent); + +private slots: + void updateCurrentTime(int time) override; + +private: + Mode m_mode; + bool m_active; + bool m_adjacent; +}; + +} // namespace Utils diff --git a/src/libs/utils/transientscroll.cpp b/src/libs/utils/transientscroll.cpp index 844d48de3ad..1d3df1259d8 100644 --- a/src/libs/utils/transientscroll.cpp +++ b/src/libs/utils/transientscroll.cpp @@ -12,6 +12,8 @@ using namespace Utils; static constexpr char transientScrollAreaSupportName[] = "transientScrollAreSupport"; +static constexpr char focusedPropertyName[] = "focused"; +static constexpr char skipChildPropertyName[] = "qds_transient_skipChildArea"; class Utils::ScrollAreaPrivate { @@ -19,14 +21,23 @@ public: ScrollAreaPrivate(QAbstractScrollArea *area) : area(area) { - verticalScrollBar = new ScrollBar(area); - area->setVerticalScrollBar(verticalScrollBar); + verticalScrollBar = dynamic_cast(area->verticalScrollBar()); + if (!verticalScrollBar) { + verticalScrollBar = new ScrollBar(area); + area->setVerticalScrollBar(verticalScrollBar); + } - horizontalScrollBar = new ScrollBar(area); - area->setHorizontalScrollBar(horizontalScrollBar); + horizontalScrollBar = dynamic_cast(area->horizontalScrollBar()); + if (!horizontalScrollBar) { + horizontalScrollBar = new ScrollBar(area); + area->setHorizontalScrollBar(horizontalScrollBar); + } - area->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); - area->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded); + if (area->verticalScrollBarPolicy() != Qt::ScrollBarAlwaysOff) + area->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); + + if (area->horizontalScrollBarPolicy() != Qt::ScrollBarAlwaysOff) + area->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded); } inline QRect scrollBarRect(ScrollBar *scrollBar) @@ -40,15 +51,30 @@ public: return rect.adjusted(0, mDiff, 0, mDiff); } } + inline QPointer adjacentScrollBar(QPointer scrollBar) + { + if (scrollBar == verticalScrollBar) + return horizontalScrollBar; + + if (scrollBar == horizontalScrollBar) + return verticalScrollBar; + + return {}; + } inline bool checkToFlashScroll(QPointer scrollBar, const QPoint &pos) { if (scrollBar.isNull()) return false; - if (!scrollBar->style()->styleHint( - QStyle::SH_ScrollBar_Transient, - nullptr, scrollBar)) + if (!scrollBar->style()->styleHint(QStyle::SH_ScrollBar_Transient, nullptr, scrollBar)) + return false; + + Qt::ScrollBarPolicy policy = (scrollBar->orientation() == Qt::Horizontal) + ? area->horizontalScrollBarPolicy() + : area->verticalScrollBarPolicy(); + + if (policy == Qt::ScrollBarAlwaysOff) return false; if (scrollBarRect(scrollBar).contains(pos)) { @@ -59,16 +85,120 @@ public: return false; } + inline bool setAdjacentHovered(QObject *w, bool setHovered) + { + if (!w) + return false; + + QPointer scrollBar; + if (w == verticalScrollBar) + scrollBar = verticalScrollBar; + else if (w == horizontalScrollBar) + scrollBar = horizontalScrollBar; + + if (!scrollBar) + return false; + + QPointer adjacent = adjacentScrollBar(scrollBar); + if (!adjacent) + return false; + + return adjacent->setAdjacentHovered(setHovered); + } + + inline bool setAdjacentVisible(QObject *changedObject, bool setVisible) + { + if (!changedObject) + return false; + + QPointer scrollBar; + if (changedObject == verticalScrollBar) { + scrollBar = verticalScrollBar; + } else if (changedObject == horizontalScrollBar) { + scrollBar = horizontalScrollBar; + } else if (changedObject == area) { + if (setVisible && verticalScrollBar && horizontalScrollBar) { + bool anyChange = false; + anyChange |= verticalScrollBar->setAdjacentVisible(horizontalScrollBar->isVisible()); + anyChange |= horizontalScrollBar->setAdjacentVisible(verticalScrollBar->isVisible()); + return anyChange; + } + } + + if (!scrollBar) + return false; + + QPointer adjacent = adjacentScrollBar(scrollBar); + if (!adjacent) + return false; + + return adjacent->setAdjacentVisible(setVisible); + } + inline bool checkToFlashScroll(const QPoint &pos) { bool coversScroll = checkToFlashScroll(verticalScrollBar, pos); + if (!coversScroll) coversScroll |= checkToFlashScroll(horizontalScrollBar, pos); return coversScroll; } - inline void installViewPort(QObject *eventHandler) { + inline bool canSetTransientProperty(QPointer scrollBar) const + { + if (scrollBar.isNull()) + return false; + + if (!scrollBar->style()->styleHint(QStyle::SH_ScrollBar_Transient, nullptr, scrollBar)) + return false; + + Qt::ScrollBarPolicy policy = (scrollBar->orientation() == Qt::Horizontal) + ? area->horizontalScrollBarPolicy() + : area->verticalScrollBarPolicy(); + + if (policy == Qt::ScrollBarAlwaysOff) + return false; + + return true; + } + + inline bool setFocus(QPointer scrollBar, const bool &focus) + { + if (!canSetTransientProperty(scrollBar)) + return false; + + return scrollBar->setFocused(focus); + } + + inline bool setFocus(const bool &focus) + { + bool flashChanged = false; + flashChanged |= setFocus(verticalScrollBar, focus); + flashChanged |= setFocus(horizontalScrollBar, focus); + + return flashChanged; + } + + inline bool setViewPortIntraction(QPointer scrollBar, const bool &hovered) + { + if (!canSetTransientProperty(scrollBar)) + return false; + + return scrollBar->setViewPortInteraction(hovered); + } + + inline bool setViewPortIntraction(const bool &hovered) + { + bool interactionChanged = false; + interactionChanged |= setViewPortIntraction(verticalScrollBar, hovered); + interactionChanged |= setViewPortIntraction(horizontalScrollBar, hovered); + + return interactionChanged; + } + + inline void installViewPort(QObject *eventHandler) + { QWidget *viewPort = area->viewport(); if (viewPort && viewPort != this->viewPort @@ -76,14 +206,30 @@ public: && (area->verticalScrollBarPolicy() != Qt::ScrollBarAlwaysOff || area->horizontalScrollBarPolicy() != Qt::ScrollBarAlwaysOff)) { viewPort->installEventFilter(eventHandler); + + if (verticalScrollBar) + verticalScrollBar->installEventFilter(eventHandler); + + if (horizontalScrollBar) + horizontalScrollBar->installEventFilter(eventHandler); + this->viewPort = viewPort; + setViewPortIntraction(true); } } inline void uninstallViewPort(QObject *eventHandler) { if (viewPort) { viewPort->removeEventFilter(eventHandler); + + if (verticalScrollBar) + verticalScrollBar->removeEventFilter(eventHandler); + + if (horizontalScrollBar) + horizontalScrollBar->removeEventFilter(eventHandler); + this->viewPort = nullptr; + setViewPortIntraction(false); } } @@ -111,6 +257,13 @@ void TransientScrollAreaSupport::support(QAbstractScrollArea *scrollArea) ); } +void TransientScrollAreaSupport::supportWidget(QWidget *widget) +{ + for (QAbstractScrollArea *area : widget->findChildren()) { + support(area); + } +} + TransientScrollAreaSupport::~TransientScrollAreaSupport() { delete d; @@ -122,23 +275,65 @@ bool TransientScrollAreaSupport::eventFilter(QObject *watched, QEvent *event) case QEvent::Enter: { if (watched == d->area) d->installViewPort(this); - } - break; + } break; case QEvent::Leave: { if (watched == d->area) d->uninstallViewPort(this); - } - break; + } break; case QEvent::MouseMove: { - if (watched == d->viewPort){ + if (watched == d->viewPort) { QMouseEvent *mouseEvent = static_cast(event); if (mouseEvent) { if (d->checkToFlashScroll(mouseEvent->pos())) return true; } } - } - break; + } break; + case QEvent::HoverEnter: + case QEvent::HoverMove: { + QHoverEvent *hoverEvent = static_cast(event); + if (watched == d->horizontalScrollBar || watched == d->verticalScrollBar) { + if (hoverEvent) + d->setAdjacentHovered(watched, true); + } + } break; + case QEvent::HoverLeave: { + QHoverEvent *hoverEvent = static_cast(event); + if (watched == d->horizontalScrollBar || watched == d->verticalScrollBar) { + if (hoverEvent) + d->setAdjacentHovered(watched, false); + } + } break; + case QEvent::DynamicPropertyChange: { + if (watched == d->area) { + auto *pEvent = static_cast(event); + if (!pEvent || pEvent->propertyName() != focusedPropertyName) + break; + + bool focused = d->area->property(focusedPropertyName).toBool(); + d->setFocus(focused); + + if (!d->area->property(skipChildPropertyName).toBool() && d->area->viewport()) { + const QList scrollChildren + = d->area->viewport()->findChildren(); + for (QAbstractScrollArea *area : scrollChildren) { + area->setProperty(skipChildPropertyName, true); + area->setProperty(focusedPropertyName, focused); + area->setProperty(skipChildPropertyName, false); + } + } + } + } break; + case QEvent::Hide: + d->setAdjacentVisible(watched, false); + break; + case QEvent::Show: + d->setAdjacentVisible(watched, true); + break; + case QEvent::Resize: { + if (watched == d->area) + d->setAdjacentVisible(watched, true); + } break; default: break; } @@ -149,6 +344,12 @@ class Utils::ScrollBarPrivate { public: bool flashed = false; int flashTimer = 0; + bool focused = false; + bool viewPortIntraction = false; + bool adjacentHovered = false; + bool adjacentVisible = false; + bool isHandleUnderCursor = false; + bool isGrooveUnderCursor = false; }; ScrollBar::ScrollBar(QWidget *parent) @@ -162,25 +363,6 @@ ScrollBar::~ScrollBar() delete d; } -QSize ScrollBar::sizeHint() const -{ - QSize sh = QScrollBar::sizeHint(); - if (style()->styleHint(QStyle::SH_ScrollBar_Transient, nullptr, this)) { - constexpr int thickness = 10; - if (orientation() == Qt::Horizontal) - sh.setHeight(thickness); - else - sh.setWidth(thickness); - } else { - constexpr int thickness = 12; - if (orientation() == Qt::Horizontal) - sh.setHeight(thickness); - else - sh.setWidth(thickness); - } - return sh; -} - void ScrollBar::flash() { if (!d->flashed && style()->styleHint(QStyle::SH_ScrollBar_Transient, nullptr, this)) { @@ -197,14 +379,57 @@ void ScrollBar::flash() void ScrollBar::initStyleOption(QStyleOptionSlider *option) const { QScrollBar::initStyleOption(option); + if (style()->styleHint(QStyle::SH_ScrollBar_Transient, nullptr, this)) { + if (d->flashed || d->focused || d->viewPortIntraction) + option->state |= QStyle::State_On; - if (d->flashed && style()->styleHint(QStyle::SH_ScrollBar_Transient, nullptr, this)) - option->state |= QStyle::State_On; + if (d->isGrooveUnderCursor || d->isHandleUnderCursor || d->adjacentHovered) + option->subControls |= QStyle::SC_ScrollBarGroove; + + option->styleObject->setProperty("adjacentScroll", d->adjacentHovered); + + if (d->isHandleUnderCursor) + option->activeSubControls |= QStyle::SC_ScrollBarSlider; + + if (d->adjacentVisible) { + int scrollExtent = style()->pixelMetric(QStyle::PM_ScrollBarExtent, option, this); + if (option->orientation == Qt::Horizontal) + option->rect.adjust(0, 0, -scrollExtent, 0); + else + option->rect.adjust(0, 0, 0, -scrollExtent); + } + } } bool ScrollBar::event(QEvent *event) { switch (event->type()) { + case QEvent::HoverEnter: + case QEvent::HoverMove: { + QHoverEvent *hoverEvent = static_cast(event); + if (hoverEvent) { + QStyleOptionSlider option; + option.initFrom(this); + d->isHandleUnderCursor = style() + ->subControlRect(QStyle::CC_ScrollBar, + &option, + QStyle::SC_ScrollBarSlider, + this) + .contains(hoverEvent->pos()); + + d->isGrooveUnderCursor = !d->isHandleUnderCursor + && style() + ->subControlRect(QStyle::CC_ScrollBar, + &option, + QStyle::SC_ScrollBarGroove, + this) + .contains(hoverEvent->pos()); + } + } break; + case QEvent::HoverLeave: + d->isHandleUnderCursor = false; + d->isGrooveUnderCursor = false; + break; case QEvent::Timer: if (static_cast(event)->timerId() == d->flashTimer) { if (d->flashed && style()->styleHint(QStyle::SH_ScrollBar_Transient, nullptr, this)) { @@ -213,6 +438,7 @@ bool ScrollBar::event(QEvent *event) } killTimer(d->flashTimer); d->flashTimer = 0; + return true; } break; default: @@ -220,3 +446,99 @@ bool ScrollBar::event(QEvent *event) } return QScrollBar::event(event); } + +bool ScrollBar::setFocused(const bool &focused) +{ + if (d->focused == focused) + return false; + + d->focused = focused; + + if (d->focused) + flash(); + else + update(); + + return true; +} + +bool ScrollBar::setAdjacentVisible(const bool &visible) +{ + if (d->adjacentVisible == visible) + return false; + + d->adjacentVisible = visible; + update(); + return true; +} + +bool ScrollBar::setAdjacentHovered(const bool &hovered) +{ + if (d->adjacentHovered == hovered) + return false; + + d->adjacentHovered = hovered; + update(); + return true; +} + +bool ScrollBar::setViewPortInteraction(const bool &hovered) +{ + if (d->viewPortIntraction == hovered) + return false; + + d->viewPortIntraction = hovered; + + if (d->viewPortIntraction) + flash(); + else + update(); + + return true; +} + +void GlobalTransientSupport::support(QWidget *widget) +{ + if (!widget) + return; + + widget->installEventFilter(instance()); + QAbstractScrollArea *area = dynamic_cast(widget); + if (area) + TransientScrollAreaSupport::support(area); + + for (QWidget *childWidget : widget->findChildren(Qt::FindChildOption::FindDirectChildrenOnly)) + support(childWidget); +} + +GlobalTransientSupport::GlobalTransientSupport() + : QObject(nullptr) +{} + +GlobalTransientSupport *GlobalTransientSupport::instance() +{ + static GlobalTransientSupport *gVal = nullptr; + if (!gVal) + gVal = new GlobalTransientSupport; + + return gVal; +} + +bool GlobalTransientSupport::eventFilter(QObject *watched, QEvent *event) +{ + switch (event->type()) { + case QEvent::ChildAdded: { + QChildEvent *childEvent = static_cast(event); + + if (!childEvent || !childEvent->child() || !childEvent->child()->isWidgetType()) + break; + + QWidget *widget = dynamic_cast(childEvent->child()); + if (widget) + support(widget); + } + default: + break; + } + return QObject::eventFilter(watched, event); +} diff --git a/src/libs/utils/transientscroll.h b/src/libs/utils/transientscroll.h index c48880caa78..c63c89d2e1b 100644 --- a/src/libs/utils/transientscroll.h +++ b/src/libs/utils/transientscroll.h @@ -21,6 +21,7 @@ class QTCREATOR_UTILS_EXPORT TransientScrollAreaSupport : public QObject Q_OBJECT public: static void support(QAbstractScrollArea *scrollArea); + static void supportWidget(QWidget *widget); virtual ~TransientScrollAreaSupport(); protected: @@ -35,20 +36,38 @@ private: class QTCREATOR_UTILS_EXPORT ScrollBar : public QScrollBar { Q_OBJECT + + friend class ScrollAreaPrivate; + public: explicit ScrollBar(QWidget *parent = nullptr); virtual ~ScrollBar(); - QSize sizeHint() const override; - virtual void flash(); + bool setFocused(const bool &focused); + protected: - virtual void initStyleOption(QStyleOptionSlider *option) const override; + void initStyleOption(QStyleOptionSlider *option) const override; bool event(QEvent *event) override; private: + bool setAdjacentVisible(const bool &visible); + bool setAdjacentHovered(const bool &hovered); + bool setViewPortInteraction(const bool &hovered); + ScrollBarPrivate *d = nullptr; }; +class QTCREATOR_UTILS_EXPORT GlobalTransientSupport : public QObject +{ + Q_OBJECT +public: + static void support(QWidget *widget); + +private: + GlobalTransientSupport(); + static GlobalTransientSupport *instance(); + virtual bool eventFilter(QObject *watched, QEvent *event) override; +}; } diff --git a/src/plugins/coreplugin/find/highlightscrollbarcontroller.cpp b/src/plugins/coreplugin/find/highlightscrollbarcontroller.cpp index f3b1e20dd21..47a857b26af 100644 --- a/src/plugins/coreplugin/find/highlightscrollbarcontroller.cpp +++ b/src/plugins/coreplugin/find/highlightscrollbarcontroller.cpp @@ -31,11 +31,10 @@ class HighlightScrollBarOverlay : public QWidget public: HighlightScrollBarOverlay(HighlightScrollBarController *scrollBarController) : QWidget(scrollBarController->scrollArea()) - , m_scrollBar(scrollBarController->scrollBar()) , m_highlightController(scrollBarController) { setAttribute(Qt::WA_TransparentForMouseEvents); - m_scrollBar->parentWidget()->installEventFilter(this); + scrollBar()->parentWidget()->installEventFilter(this); doResize(); doMove(); show(); @@ -43,12 +42,12 @@ public: void doResize() { - resize(m_scrollBar->size()); + resize(scrollBar()->size()); } void doMove() { - move(parentWidget()->mapFromGlobal(m_scrollBar->mapToGlobal(m_scrollBar->pos()))); + move(parentWidget()->mapFromGlobal(scrollBar()->mapToGlobal(scrollBar()->pos()))); } void scheduleUpdate(); @@ -71,7 +70,7 @@ private: // line start to line end QMap>> m_highlightCache; - QScrollBar *m_scrollBar; + inline QScrollBar *scrollBar() const { return m_highlightController->scrollBar(); } HighlightScrollBarController *m_highlightController; bool m_isCacheUpdateScheduled = true; }; @@ -115,8 +114,8 @@ void HighlightScrollBarOverlay::paintEvent(QPaintEvent *paintEvent) gRect.width() + marginH, gRect.height() - hRect.height() + gRect.y() - hRect.y()); - const int aboveValue = m_scrollBar->value(); - const int belowValue = m_scrollBar->maximum() - m_scrollBar->value(); + const int aboveValue = scrollBar()->value(); + const int belowValue = scrollBar()->maximum() - scrollBar()->value(); const int sizeDocAbove = int(aboveValue * m_highlightController->lineHeight()); const int sizeDocBelow = int(belowValue * m_highlightController->lineHeight()); const int sizeDocVisible = int(m_highlightController->visibleRange()); @@ -303,14 +302,14 @@ void HighlightScrollBarOverlay::updateCache() QRect HighlightScrollBarOverlay::overlayRect() const { - QStyleOptionSlider opt = qt_qscrollbarStyleOption(m_scrollBar); - return m_scrollBar->style()->subControlRect(QStyle::CC_ScrollBar, &opt, QStyle::SC_ScrollBarGroove, m_scrollBar); + QStyleOptionSlider opt = qt_qscrollbarStyleOption(scrollBar()); + return scrollBar()->style()->subControlRect(QStyle::CC_ScrollBar, &opt, QStyle::SC_ScrollBarGroove, scrollBar()); } QRect HighlightScrollBarOverlay::handleRect() const { - QStyleOptionSlider opt = qt_qscrollbarStyleOption(m_scrollBar); - return m_scrollBar->style()->subControlRect(QStyle::CC_ScrollBar, &opt, QStyle::SC_ScrollBarSlider, m_scrollBar); + QStyleOptionSlider opt = qt_qscrollbarStyleOption(scrollBar()); + return scrollBar()->style()->subControlRect(QStyle::CC_ScrollBar, &opt, QStyle::SC_ScrollBarSlider, scrollBar()); } diff --git a/src/plugins/qmldesigner/components/connectioneditor/connectionviewwidget.cpp b/src/plugins/qmldesigner/components/connectioneditor/connectionviewwidget.cpp index 8a8845b47fd..655cc1a3914 100644 --- a/src/plugins/qmldesigner/components/connectioneditor/connectionviewwidget.cpp +++ b/src/plugins/qmldesigner/components/connectioneditor/connectionviewwidget.cpp @@ -80,7 +80,6 @@ ConnectionViewWidget::ConnectionViewWidget(QWidget *parent) : ui->tabBar->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Fixed); QByteArray sheet = Utils::FileReader::fetchQrc(":/connectionview/stylesheet.css"); - sheet += Utils::FileReader::fetchQrc(":/qmldesigner/scrollbar.css"); setStyleSheet(Theme::replaceCssColors(QString::fromUtf8(sheet))); connect(ui->tabBar, &QTabBar::currentChanged, diff --git a/src/plugins/qmldesigner/components/curveeditor/detail/graphicsview.cpp b/src/plugins/qmldesigner/components/curveeditor/detail/graphicsview.cpp index b7548020723..5127f206042 100644 --- a/src/plugins/qmldesigner/components/curveeditor/detail/graphicsview.cpp +++ b/src/plugins/qmldesigner/components/curveeditor/detail/graphicsview.cpp @@ -87,11 +87,6 @@ GraphicsView::GraphicsView(CurveEditorModel *model, QWidget *parent) applyZoom(m_zoomX, m_zoomY); update(); - - const QString css = Theme::replaceCssColors( - QString::fromUtf8(Utils::FileReader::fetchQrc(":/qmldesigner/scrollbar.css"))); - horizontalScrollBar()->setStyleSheet(css); - verticalScrollBar()->setStyleSheet(css); } GraphicsView::~GraphicsView() diff --git a/src/plugins/qmldesigner/components/edit3d/edit3dwidget.cpp b/src/plugins/qmldesigner/components/edit3d/edit3dwidget.cpp index be0afacfaba..ddf1bff6d29 100644 --- a/src/plugins/qmldesigner/components/edit3d/edit3dwidget.cpp +++ b/src/plugins/qmldesigner/components/edit3d/edit3dwidget.cpp @@ -74,7 +74,6 @@ Edit3DWidget::Edit3DWidget(Edit3DView *view) setAcceptDrops(true); QByteArray sheet = Utils::FileReader::fetchQrc(":/qmldesigner/stylesheet.css"); - sheet += Utils::FileReader::fetchQrc(":/qmldesigner/scrollbar.css"); setStyleSheet(Theme::replaceCssColors(QString::fromUtf8(sheet))); Core::Context context(Constants::C_QMLEDITOR3D); diff --git a/src/plugins/qmldesigner/components/formeditor/formeditorwidget.cpp b/src/plugins/qmldesigner/components/formeditor/formeditorwidget.cpp index ef7ed1d52d4..ceb0356c1d2 100644 --- a/src/plugins/qmldesigner/components/formeditor/formeditorwidget.cpp +++ b/src/plugins/qmldesigner/components/formeditor/formeditorwidget.cpp @@ -291,7 +291,6 @@ FormEditorWidget::FormEditorWidget(FormEditorView *view) fillLayout->addWidget(m_graphicsView.data()); QByteArray sheet = Utils::FileReader::fetchQrc(":/qmldesigner/stylesheet.css"); - sheet += Utils::FileReader::fetchQrc(":/qmldesigner/scrollbar.css"); setStyleSheet(Theme::replaceCssColors(QString::fromUtf8(sheet))); } diff --git a/src/plugins/qmldesigner/components/navigator/navigatortreeview.cpp b/src/plugins/qmldesigner/components/navigator/navigatortreeview.cpp index 1c83dff062a..6e1532155f5 100644 --- a/src/plugins/qmldesigner/components/navigator/navigatortreeview.cpp +++ b/src/plugins/qmldesigner/components/navigator/navigatortreeview.cpp @@ -10,6 +10,8 @@ #include "qproxystyle.h" #include "previewtooltip.h" +#include + #include #include @@ -35,7 +37,8 @@ namespace { class TableViewStyle : public QProxyStyle { public: - TableViewStyle(QObject *parent) : QProxyStyle(QStyleFactory::create("fusion")) + TableViewStyle(QObject *parent) + : QProxyStyle(new StudioStyle("fusion")) { setParent(parent); baseStyle()->setParent(parent); diff --git a/src/plugins/qmldesigner/components/navigator/navigatorwidget.cpp b/src/plugins/qmldesigner/components/navigator/navigatorwidget.cpp index 9c04438d710..91349e04f59 100644 --- a/src/plugins/qmldesigner/components/navigator/navigatorwidget.cpp +++ b/src/plugins/qmldesigner/components/navigator/navigatorwidget.cpp @@ -62,7 +62,6 @@ NavigatorWidget::NavigatorWidget(NavigatorView *view) setWindowTitle(tr("Navigator", "Title of navigator view")); QByteArray sheet = Utils::FileReader::fetchQrc(":/qmldesigner/stylesheet.css"); - sheet += Utils::FileReader::fetchQrc(":/qmldesigner/scrollbar.css"); setStyleSheet(Theme::replaceCssColors(QString::fromUtf8(sheet))); QmlDesignerPlugin::trackWidgetFocusTime(this, Constants::EVENT_NAVIGATORVIEW_TIME); diff --git a/src/plugins/qmldesigner/components/texteditor/texteditorwidget.cpp b/src/plugins/qmldesigner/components/texteditor/texteditorwidget.cpp index 9f4bb59231e..122228f2086 100644 --- a/src/plugins/qmldesigner/components/texteditor/texteditorwidget.cpp +++ b/src/plugins/qmldesigner/components/texteditor/texteditorwidget.cpp @@ -70,11 +70,6 @@ void TextEditorWidget::setTextEditor(Utils::UniqueObjectLatePtreditorWidget()->installEventFilter(this); - - static QString styleSheet = Theme::replaceCssColors( - QString::fromUtf8(Utils::FileReader::fetchQrc(":/qmldesigner/scrollbar.css"))); - m_textEditor->editorWidget()->verticalScrollBar()->setStyleSheet(styleSheet); - m_textEditor->editorWidget()->horizontalScrollBar()->setStyleSheet(styleSheet); } } diff --git a/src/plugins/qmldesigner/components/timelineeditor/timelinewidget.cpp b/src/plugins/qmldesigner/components/timelineeditor/timelinewidget.cpp index 64fce948489..3bccd598b41 100644 --- a/src/plugins/qmldesigner/components/timelineeditor/timelinewidget.cpp +++ b/src/plugins/qmldesigner/components/timelineeditor/timelinewidget.cpp @@ -24,6 +24,7 @@ #include #include #include +#include #include #include @@ -97,7 +98,7 @@ TimelineWidget::TimelineWidget(TimelineView *view) , m_toolbar(new TimelineToolBar(this)) , m_rulerView(new QGraphicsView(this)) , m_graphicsView(new QGraphicsView(this)) - , m_scrollbar(new QScrollBar(this)) + , m_scrollbar(new Utils::ScrollBar(this)) , m_statusBar(new QLabel(this)) , m_timelineView(view) , m_graphicsScene(new TimelineGraphicsScene(this, view->externalDependencies())) @@ -112,11 +113,6 @@ TimelineWidget::TimelineWidget(TimelineView *view) m_toolbar->setStyleSheet(Theme::replaceCssColors( QString::fromUtf8(Utils::FileReader::fetchQrc(":/qmldesigner/stylesheet.css")))); - - const QString css = Theme::replaceCssColors( - QString::fromUtf8(Utils::FileReader::fetchQrc(":/qmldesigner/scrollbar.css"))); - - m_scrollbar->setStyleSheet(css); m_scrollbar->setOrientation(Qt::Horizontal); QSizePolicy sizePolicy1(QSizePolicy::Expanding, QSizePolicy::Preferred); @@ -129,7 +125,6 @@ TimelineWidget::TimelineWidget(TimelineView *view) m_rulerView->setAlignment(Qt::AlignLeft | Qt::AlignTop); m_rulerView->viewport()->installEventFilter(new Eventfilter(this)); m_rulerView->viewport()->setFocusPolicy(Qt::NoFocus); - m_rulerView->setStyleSheet(css); m_rulerView->setFrameShape(QFrame::NoFrame); m_rulerView->setFrameShadow(QFrame::Plain); m_rulerView->setLineWidth(0); @@ -137,12 +132,11 @@ TimelineWidget::TimelineWidget(TimelineView *view) m_rulerView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); m_rulerView->setScene(graphicsScene()); - m_graphicsView->setStyleSheet(css); m_graphicsView->setObjectName("SceneView"); m_graphicsView->setFrameShape(QFrame::NoFrame); m_graphicsView->setFrameShadow(QFrame::Plain); m_graphicsView->setLineWidth(0); - m_graphicsView->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn); + m_graphicsView->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); m_graphicsView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); m_graphicsView->setSizePolicy(sizePolicy1); @@ -286,6 +280,8 @@ TimelineWidget::TimelineWidget(TimelineView *view) auto onFinish = [this]() { graphicsScene()->setCurrentFrame(m_playbackAnimation->startValue().toInt()); }; connect(m_playbackAnimation, &QVariantAnimation::finished, onFinish); + + TimeLineNS::TimelineScrollAreaSupport::support(m_graphicsView, m_scrollbar); } void TimelineWidget::connectToolbar() @@ -541,11 +537,11 @@ void TimelineWidget::invalidateTimelinePosition(const QmlTimeline &timeline) void TimelineWidget::setupScrollbar(int min, int max, int current) { bool b = m_scrollbar->blockSignals(true); - m_scrollbar->setMinimum(min); - m_scrollbar->setMaximum(max); + m_scrollbar->setRange(min, max); m_scrollbar->setValue(current); m_scrollbar->setSingleStep((max - min) / 10); m_scrollbar->blockSignals(b); + m_scrollbar->flash(); } void TimelineWidget::setTimelineId(const QString &id) @@ -634,4 +630,145 @@ TimelineView *TimelineWidget::timelineView() const return m_timelineView; } +namespace TimeLineNS { + +using ScrollBar = Utils::ScrollBar; +static constexpr char timelineScrollAreaSupportName[] = "timelinetransientScrollAreSupport"; +static constexpr char focusedPropertyName[] = "focused"; + +class TimelineScrollAreaPrivate +{ +public: + TimelineScrollAreaPrivate(QAbstractScrollArea *area, ScrollBar *scrollbar) + : area(area) + , scrollbar(scrollbar) + {} + + inline QRect scrollBarRect(ScrollBar *scrollBar) + { + QRect rect = viewPort ? viewPort->rect() : area->rect(); + if (scrollBar->orientation() == Qt::Vertical) { + int mDiff = rect.width() - scrollBar->sizeHint().width(); + return rect.adjusted(mDiff, 0, mDiff, 0); + } else { + int mDiff = rect.height() - scrollBar->sizeHint().height(); + return rect.adjusted(0, mDiff, 0, mDiff); + } + } + + inline bool checkToFlashScroll(QPointer scrollBar, const QPoint &pos) + { + if (scrollBar.isNull()) + return false; + + if (!scrollBar->style()->styleHint(QStyle::SH_ScrollBar_Transient, nullptr, scrollBar)) + return false; + + if (scrollBarRect(scrollBar).contains(pos)) { + scrollBar->flash(); + return true; + } + return false; + } + + inline bool checkToFlashScroll(const QPoint &pos) + { + bool coversScroll = checkToFlashScroll(scrollbar, pos); + + return coversScroll; + } + + inline bool setFocus(const bool &focus) + { + if (scrollbar.isNull()) + return false; + + if (!scrollbar->style()->styleHint(QStyle::SH_ScrollBar_Transient, nullptr, scrollbar)) + return false; + + return scrollbar->setFocused(focus); + } + + inline void installViewPort(QObject *eventHandler) + { + QWidget *viewPort = area->viewport(); + if (viewPort && viewPort != this->viewPort + && viewPort->style()->styleHint(QStyle::SH_ScrollBar_Transient, nullptr, viewPort)) { + viewPort->installEventFilter(eventHandler); + this->viewPort = viewPort; + } + } + + inline void uninstallViewPort(QObject *eventHandler) + { + if (viewPort) { + viewPort->removeEventFilter(eventHandler); + this->viewPort = nullptr; + } + } + + QAbstractScrollArea *area = nullptr; + QPointer viewPort = nullptr; + QPointer scrollbar; +}; + +TimelineScrollAreaSupport::TimelineScrollAreaSupport(QAbstractScrollArea *scrollArea, + Utils::ScrollBar *scrollbar) + : QObject(scrollArea) + , d(new TimelineScrollAreaPrivate(scrollArea, scrollbar)) +{ + scrollArea->installEventFilter(this); +} + +void TimelineScrollAreaSupport::support(QAbstractScrollArea *scrollArea, Utils::ScrollBar *scrollbar) +{ + QObject *prevSupport = scrollArea->property(timelineScrollAreaSupportName).value(); + if (!prevSupport) + scrollArea->setProperty(timelineScrollAreaSupportName, + QVariant::fromValue( + new TimelineScrollAreaSupport(scrollArea, scrollbar))); +} + +TimelineScrollAreaSupport::~TimelineScrollAreaSupport() +{ + delete d; +} + +bool TimelineScrollAreaSupport::eventFilter(QObject *watched, QEvent *event) +{ + switch (event->type()) { + case QEvent::Enter: { + if (watched == d->area) + d->installViewPort(this); + } break; + case QEvent::Leave: { + if (watched == d->area) + d->uninstallViewPort(this); + } break; + case QEvent::MouseMove: { + if (watched == d->viewPort) { + QMouseEvent *mouseEvent = static_cast(event); + if (mouseEvent) { + if (d->checkToFlashScroll(mouseEvent->pos())) + return true; + } + } + } break; + case QEvent::DynamicPropertyChange: { + if (watched == d->area) { + auto *pEvent = static_cast(event); + if (!pEvent || pEvent->propertyName() != focusedPropertyName) + break; + + bool focused = d->area->property(focusedPropertyName).toBool(); + d->setFocus(focused); + } + } break; + default: + break; + } + return QObject::eventFilter(watched, event); +} + +} // namespace TimeLineNS } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/timelineeditor/timelinewidget.h b/src/plugins/qmldesigner/components/timelineeditor/timelinewidget.h index d99e7041947..58d82159d9e 100644 --- a/src/plugins/qmldesigner/components/timelineeditor/timelinewidget.h +++ b/src/plugins/qmldesigner/components/timelineeditor/timelinewidget.h @@ -19,15 +19,44 @@ QT_FORWARD_DECLARE_CLASS(QString) QT_FORWARD_DECLARE_CLASS(QPushButton) QT_FORWARD_DECLARE_CLASS(QVariantAnimation) QT_FORWARD_DECLARE_CLASS(QScrollBar) +QT_FORWARD_DECLARE_CLASS(QAbstractScrollArea) + +namespace Utils { +QT_FORWARD_DECLARE_CLASS(ScrollBar) +} namespace QmlDesigner { class TimelineToolBar; class TimelineView; class TimelineGraphicsScene; +class TimelineWidget; class QmlTimeline; class Navigation2dScrollBar; +namespace TimeLineNS { + +class TimelineScrollAreaPrivate; +class ScrollBarPrivate; + +class TimelineScrollAreaSupport : public QObject +{ + Q_OBJECT +public: + static void support(QAbstractScrollArea *scrollArea, Utils::ScrollBar *scrollbar); + virtual ~TimelineScrollAreaSupport(); + +protected: + virtual bool eventFilter(QObject *watched, QEvent *event) override; + +private: + explicit TimelineScrollAreaSupport(QAbstractScrollArea *scrollArea, Utils::ScrollBar *scrollbar); + + TimelineScrollAreaPrivate *d = nullptr; +}; + +} // namespace TimeLineNS + class TimelineWidget : public QWidget { Q_OBJECT @@ -76,7 +105,7 @@ private: QGraphicsView *m_graphicsView = nullptr; - QScrollBar *m_scrollbar = nullptr; + Utils::ScrollBar *m_scrollbar = nullptr; QLabel *m_statusBar = nullptr; diff --git a/src/plugins/qmldesigner/components/transitioneditor/transitioneditorwidget.cpp b/src/plugins/qmldesigner/components/transitioneditor/transitioneditorwidget.cpp index adbe164201b..6cb9c285be7 100644 --- a/src/plugins/qmldesigner/components/transitioneditor/transitioneditorwidget.cpp +++ b/src/plugins/qmldesigner/components/transitioneditor/transitioneditorwidget.cpp @@ -82,7 +82,7 @@ TransitionEditorWidget::TransitionEditorWidget(TransitionEditorView *view) , m_toolbar(new TransitionEditorToolBar(this)) , m_rulerView(new QGraphicsView(this)) , m_graphicsView(new QGraphicsView(this)) - , m_scrollbar(new QScrollBar(this)) + , m_scrollbar(new Utils::ScrollBar(this)) , m_statusBar(new QLabel(this)) , m_transitionEditorView(view) , m_graphicsScene(new TransitionEditorGraphicsScene(this)) @@ -94,11 +94,6 @@ TransitionEditorWidget::TransitionEditorWidget(TransitionEditorView *view) m_toolbar->setStyleSheet(Theme::replaceCssColors( QString::fromUtf8(Utils::FileReader::fetchQrc(":/qmldesigner/stylesheet.css")))); - - const QString css = Theme::replaceCssColors( - QString::fromUtf8(Utils::FileReader::fetchQrc(":/qmldesigner/scrollbar.css"))); - - m_scrollbar->setStyleSheet(css); m_scrollbar->setOrientation(Qt::Horizontal); QSizePolicy sizePolicy1(QSizePolicy::Expanding, QSizePolicy::Preferred); @@ -111,7 +106,6 @@ TransitionEditorWidget::TransitionEditorWidget(TransitionEditorView *view) m_rulerView->setAlignment(Qt::AlignLeft | Qt::AlignTop); m_rulerView->viewport()->installEventFilter(new Eventfilter(this)); m_rulerView->viewport()->setFocusPolicy(Qt::NoFocus); - m_rulerView->setStyleSheet(css); m_rulerView->setFrameShape(QFrame::NoFrame); m_rulerView->setFrameShadow(QFrame::Plain); m_rulerView->setLineWidth(0); @@ -119,7 +113,6 @@ TransitionEditorWidget::TransitionEditorWidget(TransitionEditorView *view) m_rulerView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); m_rulerView->setScene(graphicsScene()); - m_graphicsView->setStyleSheet(css); m_graphicsView->setObjectName("SceneView"); m_graphicsView->setFrameShape(QFrame::NoFrame); m_graphicsView->setFrameShadow(QFrame::Plain); diff --git a/src/plugins/qmldesigner/components/transitioneditor/transitioneditorwidget.h b/src/plugins/qmldesigner/components/transitioneditor/transitioneditorwidget.h index e40f73ab59c..a8cb884be2e 100644 --- a/src/plugins/qmldesigner/components/transitioneditor/transitioneditorwidget.h +++ b/src/plugins/qmldesigner/components/transitioneditor/transitioneditorwidget.h @@ -7,6 +7,8 @@ #include +#include + #include #include @@ -75,7 +77,7 @@ private: QGraphicsView *m_graphicsView = nullptr; - QScrollBar *m_scrollbar = nullptr; + Utils::ScrollBar *m_scrollbar = nullptr; QLabel *m_statusBar = nullptr; diff --git a/src/plugins/qmldesigner/designmodewidget.cpp b/src/plugins/qmldesigner/designmodewidget.cpp index e1468ca86e4..822b23fc9a7 100644 --- a/src/plugins/qmldesigner/designmodewidget.cpp +++ b/src/plugins/qmldesigner/designmodewidget.cpp @@ -38,6 +38,7 @@ #include #include #include +#include #include #include @@ -125,7 +126,6 @@ QWidget *DesignModeWidget::createProjectExplorerWidget(QWidget *parent) if (navigationView.widget) { QByteArray sheet = Utils::FileReader::fetchQrc(":/qmldesigner/stylesheet.css"); - sheet += Utils::FileReader::fetchQrc(":/qmldesigner/scrollbar.css"); sheet += "QLabel { background-color: #4f4f4f; }"; navigationView.widget->setStyleSheet(Theme::replaceCssColors(QString::fromUtf8(sheet))); navigationView.widget->setParent(parent); @@ -190,7 +190,6 @@ void DesignModeWidget::setup() Core::ICore::resourcePath("qmldesigner/workspacePresets/").toString()); QString sheet = QString::fromUtf8(Utils::FileReader::fetchQrc(":/qmldesigner/dockwidgets.css")); - sheet += QString::fromUtf8(Utils::FileReader::fetchQrc(":/qmldesigner/scrollbar.css")); m_dockManager->setStyleSheet(Theme::replaceCssColors(sheet)); // Setup icons @@ -300,7 +299,6 @@ void DesignModeWidget::setup() // Apply stylesheet to QWidget QByteArray sheet = Utils::FileReader::fetchQrc(":/qmldesigner/stylesheet.css"); - sheet += Utils::FileReader::fetchQrc(":/qmldesigner/scrollbar.css"); sheet += "QLabel { background-color: creatorTheme.DSsectionHeadBackground; }"; navigationView.widget->setStyleSheet(Theme::replaceCssColors(QString::fromUtf8(sheet))); @@ -428,6 +426,8 @@ void DesignModeWidget::setup() setupNavigatorHistory(currentDesignDocument()->textEditor()); m_dockManager->initialize(); + if (style()->styleHint(QStyle::SH_ScrollBar_Transient, nullptr, this)) + Utils::GlobalTransientSupport::support(m_dockManager); // Hide all floating widgets if the initial mode isn't design mode if (Core::ModeManager::instance()->currentModeId() != Core::Constants::MODE_DESIGN) { diff --git a/src/plugins/qmldesignerbase/CMakeLists.txt b/src/plugins/qmldesignerbase/CMakeLists.txt index a191b6d43ca..5f5f1ef258b 100644 --- a/src/plugins/qmldesignerbase/CMakeLists.txt +++ b/src/plugins/qmldesignerbase/CMakeLists.txt @@ -36,6 +36,7 @@ extend_qtc_plugin(QmlDesignerBase SOURCES_PREFIX ${CMAKE_CURRENT_LIST_DIR}/studio SOURCES studiostyle.cpp studiostyle.h + studiostyle_p.cpp studiostyle_p.h studioquickwidget.cpp studioquickwidget.h studiosettingspage.cpp studiosettingspage.h ) diff --git a/src/plugins/qmldesignerbase/studio/studiostyle.cpp b/src/plugins/qmldesignerbase/studio/studiostyle.cpp index 74dd5bfe495..cec8a323686 100644 --- a/src/plugins/qmldesignerbase/studio/studiostyle.cpp +++ b/src/plugins/qmldesignerbase/studio/studiostyle.cpp @@ -1,7 +1,10 @@ // Copyright (C) 2023 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0 #include "studiostyle.h" +#include "studiostyle_p.h" +#include +#include #include #include @@ -9,7 +12,9 @@ #include #include +#define ANIMATE_SCROLLBARS QT_CONFIG(animation) using namespace Utils; +using namespace QmlDesigner; namespace { @@ -65,6 +70,17 @@ inline QColor studioButtonOutlineColor(bool enabled, return creatorTheme()->color(themePenColorId); } +inline bool anyParentsFocused(const QWidget *widget) +{ + const QWidget *p = widget; + while (p) { + if (p->property("focused").toBool()) + return true; + p = p->parentWidget(); + } + return false; +} + bool styleEnabled(const QWidget *widget) { const QWidget *p = widget; @@ -93,221 +109,43 @@ bool isQmlEditorMenu(const QWidget *widget) return false; } -QPixmap getPixmapFromIcon(const QIcon &icon, const QSize &size, bool enabled, bool active, bool checked) +inline QPixmap getPixmapFromIcon( + const QIcon &icon, const QSize &size, bool enabled, bool active, bool checked) { QIcon::Mode mode = enabled ? ((active) ? QIcon::Active : QIcon::Normal) : QIcon::Disabled; QIcon::State state = (checked) ? QIcon::On : QIcon::Off; return icon.pixmap(size, mode, state); } -struct StudioShortcut { - StudioShortcut(const QStyleOptionMenuItem *option, - const QString &shortcutText) - : shortcutText(shortcutText) - , enabled(option->state & QStyle::State_Enabled) - , active(option->state & QStyle::State_Selected) - , font(option->font) - , fm(font) - , defaultHeight(fm.height()) - , spaceConst(fm.boundingRect(".").width()) - { - reset(); - - if (backspaceMatch(shortcutText).hasMatch()) - backspaceIcon = option->styleObject->property("backspaceIcon").value(); - } - - QSize getSize() - { - if (isFirstParticle) - calcResult(); - return _size; - } - - QPixmap getPixmap() - { - if (!isFirstParticle && !_pixmap.isNull()) - return _pixmap; - - _pixmap = QPixmap(getSize()); - _pixmap.fill(Qt::transparent); - QPainter painter(&_pixmap); - painter.setFont(font); - QPen pPen = painter.pen(); - pPen.setColor(studioTextColor(enabled, active, false)); - painter.setPen(pPen); - calcResult(&painter); - painter.end(); - - return _pixmap; - } - -private: - void applySize(const QSize &itemSize) { - width += itemSize.width(); - height = std::max(height, itemSize.height()); - if (isFirstParticle) - isFirstParticle = false; - else - width += spaceConst; - }; - - void addText(const QString &txt, QPainter *painter = nullptr) - { - if (txt.size()) { - int textWidth = fm.horizontalAdvance(txt); - QSize itemSize = {textWidth, defaultHeight}; - if (painter) { - static const QTextOption textOption(Qt::AlignLeft | Qt::AlignVCenter); - QRect placeRect({width, 0}, itemSize); - painter->drawText(placeRect, txt, textOption); - } - applySize(itemSize); - } - }; - - void addPixmap(const QPixmap &pixmap, QPainter *painter = nullptr) - { - if (painter) - painter->drawPixmap(QRect({width, 0}, pixmap.size()), pixmap); - - applySize(pixmap.size()); - }; - - void calcResult(QPainter *painter = nullptr) - { - reset(); -#ifndef QT_NO_SHORTCUT - if (!shortcutText.isEmpty()) { - int fwdIndex = 0; - - QRegularExpressionMatch mMatch = backspaceMatch(shortcutText); - int matchCount = mMatch.lastCapturedIndex(); - - for (int i = 0; i <= matchCount; ++i) { - QString mStr = mMatch.captured(i); - QSize iconSize(defaultHeight * 3, defaultHeight); - const QList iconSizes = backspaceIcon.availableSizes(); - if (iconSizes.size()) - iconSize = iconSizes.last(); - double aspectRatio = (defaultHeight + .0) / iconSize.height(); - int newWidth = iconSize.width() * aspectRatio; - - QPixmap pixmap = getPixmapFromIcon(backspaceIcon, - {newWidth, defaultHeight}, - enabled, active, false); - - int lIndex = shortcutText.indexOf(mStr, fwdIndex); - int diffChars = lIndex - fwdIndex; - addText(shortcutText.mid(fwdIndex, diffChars), painter); - addPixmap(pixmap, painter); - fwdIndex = lIndex + mStr.size(); - } - addText(shortcutText.mid(fwdIndex), painter); - } -#endif - _size = {width, height}; - } - - void reset() - { - isFirstParticle = true; - width = 0; - height = 0; - } - - inline QRegularExpressionMatch backspaceMatch(const QString &str) const - { - static const QRegularExpression backspaceDetect( - "\\+*backspace\\+*", - QRegularExpression::CaseInsensitiveOption); - return backspaceDetect.match(str); - } - - const QString shortcutText; - const bool enabled; - const bool active; - const QFont font; - const QFontMetrics fm; - const int defaultHeight; - const int spaceConst; - QIcon backspaceIcon; - bool isFirstParticle = true; - - int width = 0; - int height = 0; - QSize _size; - QPixmap _pixmap; -}; - -} // blank namespace - -class StudioStylePrivate +inline QRect expandScrollRect(const QRect &ref, + const qreal &factor, + const Qt::Orientation &orientation) { -public: - explicit StudioStylePrivate(); + if (qFuzzyCompare(factor, 1)) + return ref; -public: - QPalette stdPalette; -}; - -StudioStylePrivate::StudioStylePrivate() -{ - auto color = [] (Theme::Color c) { - return creatorTheme()->color(c); - }; - - { - stdPalette.setColorGroup( - QPalette::Disabled, // group - color(Theme::DStextColorDisabled), // windowText - color(Theme::DScontrolBackgroundDisabled), // button - color(Theme::DScontrolOutlineDisabled), // light - color(Theme::DStextSelectedTextColor), // dark - color(Theme::DSstatusbarBackground), // mid - color(Theme::DStextColorDisabled), // text - color(Theme::DStextColorDisabled), // brightText - color(Theme::DStoolbarIcon_blocked), // base - color(Theme::DStoolbarIcon_blocked) // window - ); - - stdPalette.setColorGroup( - QPalette::Inactive, // group - color(Theme::DStextColor), // windowText - color(Theme::DScontrolBackground), // button - color(Theme::DStoolbarBackground), // light - color(Theme::DSstatusbarBackground), // dark - color(Theme::DScontrolBackground), // mid - color(Theme::DStextColor), // text - color(Theme::DStextColor), // brightText - color(Theme::DStoolbarBackground), // base - color(Theme::DStoolbarBackground) // window - ); - - stdPalette.setColorGroup( - QPalette::Active, // group - color(Theme::DStextSelectedTextColor), // windowText - color(Theme::DSnavigatorItemBackgroundHover), // button - color(Theme::DSstateBackgroundColor_hover), // light - color(Theme::DSpanelBackground), // dark - color(Theme::DSnavigatorItemBackgroundHover), // mid - color(Theme::DStextSelectedTextColor), // text - color(Theme::DStextSelectedTextColor), // brightText - color(Theme::DStoolbarBackground), // base - color(Theme::DStoolbarBackground) // window - ); + if (orientation == Qt::Horizontal) { + qreal newExp = ref.height() * factor; + qreal newDiff = ref.height() - newExp; + return ref.adjusted(0, newDiff, 0, 0); + } else { + qreal newExp = ref.width() * factor; + qreal newDiff = ref.width() - newExp; + return ref.adjusted(newDiff, 0, 0, 0); } } +} // namespace + StudioStyle::StudioStyle(QStyle *style) : QProxyStyle(style) - , d(new StudioStylePrivate) + , d(new StudioStylePrivate(this)) { } StudioStyle::StudioStyle(const QString &key) : QProxyStyle(key) - , d(new StudioStylePrivate) + , d(new StudioStylePrivate(this)) { } @@ -358,8 +196,7 @@ void StudioStyle::drawPrimitive( if (!isQmlEditorMenu(widget)) Super::drawPrimitive(element, option, painter, widget); break; - case PE_FrameDefaultButton: - { + case PE_FrameDefaultButton: { if (const auto button = qstyleoption_cast(option)) { bool enabled = button->state & QStyle::State_Enabled; bool hovered = enabled && button->state & QStyle::State_MouseOver; @@ -385,30 +222,29 @@ void StudioStyle::drawPrimitive( painter->restore(); } - } - break; + } break; - case PE_IndicatorToolBarSeparator: - { + case PE_IndicatorToolBarSeparator: { bool horizontal = option->state & State_Horizontal; int thickness = pixelMetric(PM_ToolBarSeparatorExtent, option, widget); QRect colorRect; if (horizontal) { - colorRect = {option->rect.center().x() - thickness / 2 , option->rect.top() + 2, - thickness , option->rect.height() - 4}; + colorRect = {option->rect.center().x() - thickness / 2, + option->rect.top() + 2, + thickness, + option->rect.height() - 4}; } else { - colorRect = {option->rect.left() + 2, option->rect.center().y() - thickness / 2, - option->rect.width() - 4, thickness}; + colorRect = {option->rect.left() + 2, + option->rect.center().y() - thickness / 2, + option->rect.width() - 4, + thickness}; } // The separator color is currently the same as toolbar bg - painter->fillRect(colorRect, - creatorTheme()->color(Theme::DStoolbarBackground)); - } - break; + painter->fillRect(colorRect, creatorTheme()->color(Theme::DStoolbarBackground)); + } break; - default: - { + default: { Super::drawPrimitive(element, option, painter, widget); break; } @@ -452,7 +288,7 @@ void StudioStyle::drawControl( if (item.menuItemType == QStyleOptionMenuItem::Separator) { int commonHeight = item.rect.center().y(); - int additionalMargin = forwardX /*hmargin*/; + int additionalMargin = forwardX; QLineF separatorLine (item.rect.left() + additionalMargin, commonHeight, item.rect.right() - additionalMargin, @@ -764,6 +600,295 @@ void StudioStyle::drawComplexControl( } Super::drawComplexControl(control, option, painter, widget); } break; + +#if QT_CONFIG(slider) + case CC_ScrollBar: { + painter->save(); + if (const QStyleOptionSlider *scrollBar = qstyleoption_cast( + option)) { + bool wasActive = false; + bool wasAdjacent = false; + bool isFocused = anyParentsFocused(widget); + bool isAdjacent = false; + qreal scaleCoFactor = 1.0; + QObject *styleObject = option->styleObject; + bool hasTransientStyle = proxy()->styleHint(SH_ScrollBar_Transient, option, widget); + if (styleObject && hasTransientStyle) { +#if ANIMATE_SCROLLBARS + qreal opacity = 0.0; + bool shouldExpand = false; + const qreal minExpandScale = 0.7; + const qreal maxExpandScale = 1.0; +#endif + isAdjacent = styleObject->property("adjacentScroll").toBool(); + int oldPos = styleObject->property("_qdss_stylepos").toInt(); + int oldMin = styleObject->property("_qdss_stylemin").toInt(); + int oldMax = styleObject->property("_qdss_stylemax").toInt(); + QRect oldRect = styleObject->property("_qdss_stylerect").toRect(); + QStyle::State oldState = static_cast( + qvariant_cast(styleObject->property("_qdss_stylestate"))); + uint oldActiveControls = styleObject->property("_qdss_stylecontrols").toUInt(); + bool oldFocus = styleObject->property("_qdss_focused").toBool(); + bool oldAdjacent = styleObject->property("_qdss_adjacentScroll").toBool(); + // a scrollbar is transient when the scrollbar itself and + // its sibling are both inactive (ie. not pressed/hovered/moved) + bool transient = !option->activeSubControls && !(option->state & State_On); + if (!transient || oldPos != scrollBar->sliderPosition + || oldMin != scrollBar->minimum || oldMax != scrollBar->maximum + || oldRect != scrollBar->rect || oldState != scrollBar->state + || oldActiveControls != scrollBar->activeSubControls || oldFocus != isFocused + || oldAdjacent != isAdjacent) { + styleObject->setProperty("_qdss_stylepos", scrollBar->sliderPosition); + styleObject->setProperty("_qdss_stylemin", scrollBar->minimum); + styleObject->setProperty("_qdss_stylemax", scrollBar->maximum); + styleObject->setProperty("_qdss_stylerect", scrollBar->rect); + styleObject->setProperty("_qdss_stylestate", + static_cast(scrollBar->state)); + styleObject->setProperty("_qdss_stylecontrols", + static_cast(scrollBar->activeSubControls)); + styleObject->setProperty("_qdss_focused", isFocused); + styleObject->setProperty("_qdss_adjacentScroll", isAdjacent); +#if ANIMATE_SCROLLBARS + // if the scrollbar is transient or its attributes, geometry or + // state has changed, the opacity is reset back to 100% opaque + opacity = 1.0; + QScrollbarStyleAnimation *anim = qobject_cast( + d->animation(styleObject)); + if (transient) { + if (anim && anim->mode() != QScrollbarStyleAnimation::Deactivating) { + d->stopAnimation(styleObject); + anim = nullptr; + } + if (!anim) { + anim = new QScrollbarStyleAnimation(QScrollbarStyleAnimation::Deactivating, + styleObject); + d->startAnimation(anim); + } else if (anim->mode() == QScrollbarStyleAnimation::Deactivating) { + // the scrollbar was already fading out while the + // state changed -> restart the fade out animation + anim->setCurrentTime(0); + } + } else if (anim && anim->mode() == QScrollbarStyleAnimation::Deactivating) { + d->stopAnimation(styleObject); + } +#endif // animation + } +#if ANIMATE_SCROLLBARS + QScrollbarStyleAnimation *anim = qobject_cast( + d->animation(styleObject)); + if (anim && anim->mode() == QScrollbarStyleAnimation::Deactivating) { + // once a scrollbar was active (hovered/pressed), it retains + // the active look even if it's no longer active while fading out + if (oldActiveControls) + anim->setActive(true); + + if (oldAdjacent) + anim->setAdjacent(true); + + wasActive = anim->wasActive(); + wasAdjacent = anim->wasAdjacent(); + opacity = anim->currentValue(); + } + shouldExpand = (option->activeSubControls || wasActive); + if (shouldExpand) { + if (!anim && !oldActiveControls) { + // Start expand animation only once and when entering + anim = new QScrollbarStyleAnimation(QScrollbarStyleAnimation::Activating, + styleObject); + d->startAnimation(anim); + } + if (anim && anim->mode() == QScrollbarStyleAnimation::Activating) { + scaleCoFactor = (1.0 - anim->currentValue()) * minExpandScale + + anim->currentValue() * maxExpandScale; + } else { + // Keep expanded state after the animation ends, and when fading out + scaleCoFactor = maxExpandScale; + } + } + painter->setOpacity(opacity); +#endif // animation + } + bool horizontal = scrollBar->orientation == Qt::Horizontal; + bool sunken = scrollBar->state & State_Sunken; + QRect scrollBarSubLine = proxy()->subControlRect(control, + scrollBar, + SC_ScrollBarSubLine, + widget); + QRect scrollBarAddLine = proxy()->subControlRect(control, + scrollBar, + SC_ScrollBarAddLine, + widget); + QRect scrollBarSlider = proxy()->subControlRect(control, + scrollBar, + SC_ScrollBarSlider, + widget); + QRect scrollBarGroove = proxy()->subControlRect(control, + scrollBar, + SC_ScrollBarGroove, + widget); + QRect rect = option->rect; + + QColor alphaOutline = StyleHelper::borderColor(); + alphaOutline.setAlpha(180); + QColor arrowColor = option->palette.windowText().color(); + arrowColor.setAlpha(160); + + bool enabled = scrollBar->state & QStyle::State_Enabled; + bool hovered = enabled && scrollBar->state & QStyle::State_MouseOver; + + QColor buttonColor = hovered ? "#D9D9D9" : "#9B9B9B"; + QColor gradientStartColor = buttonColor.lighter(118); + QColor gradientStopColor = buttonColor; + if (hasTransientStyle) { + rect = expandScrollRect(rect, scaleCoFactor, scrollBar->orientation); + scrollBarSlider = expandScrollRect(scrollBarSlider, + scaleCoFactor, + scrollBar->orientation); + scrollBarGroove = expandScrollRect(scrollBarGroove, + scaleCoFactor, + scrollBar->orientation); + } + // Paint groove + if ((!hasTransientStyle || scrollBar->activeSubControls || wasActive || isAdjacent + || wasAdjacent) + && scrollBar->subControls & SC_ScrollBarGroove) { + painter->save(); + painter->setPen(Qt::NoPen); + if (hasTransientStyle) { + QColor brushColor("#D9D9D9"); + brushColor.setAlpha(0.3 * 255); + painter->setBrush(QBrush(brushColor)); + painter->drawRoundedRect(scrollBarGroove, 4, 4); + } else { + painter->setBrush(QBrush("#773E3E")); + painter->drawRect(rect); + } + painter->restore(); + } + QRect pixmapRect = scrollBarSlider; + QLinearGradient gradient(pixmapRect.center().x(), + pixmapRect.top(), + pixmapRect.center().x(), + pixmapRect.bottom()); + if (!horizontal) + gradient = QLinearGradient(pixmapRect.left(), + pixmapRect.center().y(), + pixmapRect.right(), + pixmapRect.center().y()); + QLinearGradient highlightedGradient = gradient; + QColor midColor2 = StyleHelper::mergedColors(gradientStartColor, gradientStopColor, 40); + gradient.setColorAt(0, buttonColor.lighter(108)); + gradient.setColorAt(1, buttonColor); + QColor innerContrastLine = StyleHelper::highlightColor(); + highlightedGradient.setColorAt(0, gradientStartColor.darker(102)); + highlightedGradient.setColorAt(1, gradientStopColor.lighter(102)); + // Paint slider + if (scrollBar->subControls & SC_ScrollBarSlider) { + if (hasTransientStyle) { + QRect rect = scrollBarSlider; + painter->setPen(Qt::NoPen); + painter->setBrush(highlightedGradient); + int r = qMin(rect.width(), rect.height()) / 2; + painter->save(); + painter->setRenderHint(QPainter::Antialiasing, true); + painter->drawRoundedRect(rect, r, r); + painter->restore(); + } else { + QRect pixmapRect = scrollBarSlider; + painter->setPen(QPen(alphaOutline)); + if (option->state & State_Sunken + && scrollBar->activeSubControls & SC_ScrollBarSlider) + painter->setBrush(midColor2); + else if (option->state & State_MouseOver + && scrollBar->activeSubControls & SC_ScrollBarSlider) + painter->setBrush(highlightedGradient); + else + painter->setBrush(gradient); + painter->drawRect(pixmapRect.adjusted(horizontal ? -1 : 0, + horizontal ? 0 : -1, + horizontal ? 0 : 1, + horizontal ? 1 : 0)); + painter->setPen(innerContrastLine); + painter->drawRect( + scrollBarSlider.adjusted(horizontal ? 0 : 1, horizontal ? 1 : 0, -1, -1)); + } + } + // The SubLine (up/left) buttons + if (!hasTransientStyle && scrollBar->subControls & SC_ScrollBarSubLine) { + if ((scrollBar->activeSubControls & SC_ScrollBarSubLine) && sunken) + painter->setBrush(gradientStopColor); + else if ((scrollBar->activeSubControls & SC_ScrollBarSubLine)) + painter->setBrush(highlightedGradient); + else + painter->setBrush(gradient); + painter->setPen(Qt::NoPen); + painter->drawRect( + scrollBarSubLine.adjusted(horizontal ? 0 : 1, horizontal ? 1 : 0, 0, 0)); + painter->setPen(QPen(alphaOutline)); + if (option->state & State_Horizontal) { + if (option->direction == Qt::RightToLeft) { + pixmapRect.setLeft(scrollBarSubLine.left()); + painter->drawLine(pixmapRect.topLeft(), pixmapRect.bottomLeft()); + } else { + pixmapRect.setRight(scrollBarSubLine.right()); + painter->drawLine(pixmapRect.topRight(), pixmapRect.bottomRight()); + } + } else { + pixmapRect.setBottom(scrollBarSubLine.bottom()); + painter->drawLine(pixmapRect.bottomLeft(), pixmapRect.bottomRight()); + } + QRect upRect = scrollBarSubLine.adjusted(horizontal ? 0 : 1, + horizontal ? 1 : 0, + horizontal ? -2 : -1, + horizontal ? -1 : -2); + painter->setBrush(Qt::NoBrush); + painter->setPen(innerContrastLine); + painter->drawRect(upRect); + // Arrows + PrimitiveElement arrowType = PE_IndicatorArrowUp; + if (option->state & State_Horizontal) + arrowType = option->direction == Qt::LeftToRight ? PE_IndicatorArrowLeft + : PE_IndicatorArrowRight; + StyleHelper::drawArrow(arrowType, painter, option); + } + // The AddLine (down/right) button + if (!hasTransientStyle && scrollBar->subControls & SC_ScrollBarAddLine) { + if ((scrollBar->activeSubControls & SC_ScrollBarAddLine) && sunken) + painter->setBrush(gradientStopColor); + else if ((scrollBar->activeSubControls & SC_ScrollBarAddLine)) + painter->setBrush(midColor2); + else + painter->setBrush(gradient); + painter->setPen(Qt::NoPen); + painter->drawRect( + scrollBarAddLine.adjusted(horizontal ? 0 : 1, horizontal ? 1 : 0, 0, 0)); + painter->setPen(QPen(alphaOutline, 1)); + if (option->state & State_Horizontal) { + if (option->direction == Qt::LeftToRight) { + pixmapRect.setLeft(scrollBarAddLine.left()); + painter->drawLine(pixmapRect.topLeft(), pixmapRect.bottomLeft()); + } else { + pixmapRect.setRight(scrollBarAddLine.right()); + painter->drawLine(pixmapRect.topRight(), pixmapRect.bottomRight()); + } + } else { + pixmapRect.setTop(scrollBarAddLine.top()); + painter->drawLine(pixmapRect.topLeft(), pixmapRect.topRight()); + } + QRect downRect = scrollBarAddLine.adjusted(1, 1, -1, -1); + painter->setPen(innerContrastLine); + painter->setBrush(Qt::NoBrush); + painter->drawRect(downRect); + PrimitiveElement arrowType = PE_IndicatorArrowDown; + if (option->state & State_Horizontal) + arrowType = option->direction == Qt::LeftToRight ? PE_IndicatorArrowRight + : PE_IndicatorArrowLeft; + StyleHelper::drawArrow(arrowType, painter, option); + } + } + painter->restore(); + } break; +#endif // QT_CONFIG(slider) default: Super::drawComplexControl(control, option, painter, widget); break; @@ -779,7 +904,7 @@ QSize StudioStyle::sizeFromContents( QSize newSize; switch (type) { - case CT_MenuItem: + case CT_MenuItem: { if (const auto mbi = qstyleoption_cast(option)) { if (!isQmlEditorMenu(widget)) { newSize = Super::sizeFromContents(type, option, size, widget); @@ -835,8 +960,7 @@ QSize StudioStyle::sizeFromContents( break; } } - break; - + } break; default: newSize = Super::sizeFromContents(type, option, size, widget); break; @@ -860,26 +984,56 @@ QRect StudioStyle::subControlRect( } #endif - switch (control) - { - case CC_Slider: + switch (control) { + case CC_Slider: { if (const auto slider = qstyleoption_cast(option)) { switch (subControl) { case SubControl::SC_SliderGroove: return slider->rect; - case SubControl::SC_SliderHandle: - { + case SubControl::SC_SliderHandle: { QRect retval = Super::subControlRect(control, option, subControl, widget); - return (slider->orientation == Qt::Horizontal) - ? retval.adjusted(0, 1, 0, 0) - : retval.adjusted(1, 0, 0, 0); - } - break; + return (slider->orientation == Qt::Horizontal) ? retval.adjusted(0, 1, 0, 0) + : retval.adjusted(1, 0, 0, 0); + } break; default: break; } } - break; + } break; + case CC_ScrollBar: { + if (!styleHint(SH_ScrollBar_Transient, option, widget)) + break; + + if (const auto scrollbar = qstyleoption_cast(option)) { + QRect newRect = Super::subControlRect(control, option, subControl, widget); + if (Utils::HostOsInfo::isMacHost()) { + if (scrollbar->orientation == Qt::Horizontal) { + const int halfThickness = newRect.height() / 2; + newRect.adjust(0, halfThickness, 0, 0); + } else { + const int halfThickness = newRect.width() / 2; + newRect.adjust(halfThickness, 0, 0, 0); + } + } + if (subControl == SC_ScrollBarSlider) { + bool hasGroove + = (scrollbar->activeSubControls.testFlag(SC_SliderGroove) + || (scrollbar->styleObject + && scrollbar->styleObject->property("adjacentScrollBar").toBool())) + && scrollbar->subControls.testFlag(SC_ScrollBarGroove); + bool interacted = scrollbar->activeSubControls.testFlag(SC_ScrollBarSlider); + + if (hasGroove || interacted) + return newRect; + + if (scrollbar->orientation == Qt::Horizontal) + newRect.adjust(0, 1, 0, -1); + else + newRect.adjust(1, 0, -1, 0); + } + return newRect; + } + } break; default: break; } @@ -893,6 +1047,12 @@ int StudioStyle::styleHint( const QWidget *widget, QStyleHintReturn *returnData) const { + switch (hint) { + case SH_ScrollBar_Transient: + return true; + default: + break; + } return Super::styleHint(hint, option, widget, returnData); } @@ -946,19 +1106,22 @@ int StudioStyle::pixelMetric( return 4; case PM_ToolBarExtensionExtent: return 29; - case PM_ScrollBarExtent: - return 20; + case PM_ScrollBarExtent: { + if (styleHint(SH_ScrollBar_Transient, option, widget)) + return 10; + return 14; + } break; case PM_ScrollBarSliderMin: return 30; case PM_SliderLength: return 5; - case PM_SliderThickness: + case PM_SliderThickness: { if (const auto *slider = qstyleoption_cast(option)) { return (slider->orientation == Qt::Horizontal ? slider->rect.height() : slider->rect.width()) - 1; } - break; + } break; case PM_SliderControlThickness: return 2; default: diff --git a/src/plugins/qmldesignerbase/studio/studiostyle.h b/src/plugins/qmldesignerbase/studio/studiostyle.h index c55797354b3..63250a007de 100644 --- a/src/plugins/qmldesignerbase/studio/studiostyle.h +++ b/src/plugins/qmldesignerbase/studio/studiostyle.h @@ -7,6 +7,7 @@ #include +namespace QmlDesigner { class StudioStylePrivate; class QMLDESIGNERBASE_EXPORT StudioStyle : public QProxyStyle @@ -20,57 +21,51 @@ public: virtual ~StudioStyle() override; // Drawing Methods - void drawPrimitive( - PrimitiveElement element, - const QStyleOption *option, - QPainter *painter, - const QWidget *widget = nullptr) const override; + void drawPrimitive(PrimitiveElement element, + const QStyleOption *option, + QPainter *painter, + const QWidget *widget = nullptr) const override; - void drawControl( - ControlElement element, - const QStyleOption *option, - QPainter *painter, - const QWidget *widget = nullptr) const override; + void drawControl(ControlElement element, + const QStyleOption *option, + QPainter *painter, + const QWidget *widget = nullptr) const override; - void drawComplexControl( - ComplexControl control, - const QStyleOptionComplex *option, - QPainter *painter, - const QWidget *widget = nullptr) const override; + void drawComplexControl(ComplexControl control, + const QStyleOptionComplex *option, + QPainter *painter, + const QWidget *widget = nullptr) const override; // Topology - QSize sizeFromContents( - ContentsType type, - const QStyleOption *option, - const QSize &size, - const QWidget *widget) const override; + QSize sizeFromContents(ContentsType type, + const QStyleOption *option, + const QSize &size, + const QWidget *widget) const override; - QRect subControlRect( - ComplexControl control, - const QStyleOptionComplex *option, - SubControl subControl, - const QWidget *widget) const override; + QRect subControlRect(ComplexControl control, + const QStyleOptionComplex *option, + SubControl subControl, + const QWidget *widget) const override; - int styleHint( - StyleHint hint, - const QStyleOption *option, - const QWidget *widget, - QStyleHintReturn *returnData) const override; + int styleHint(StyleHint hint, + const QStyleOption *option = nullptr, + const QWidget *widget = nullptr, + QStyleHintReturn *returnData = nullptr) const override; - int pixelMetric( - PixelMetric metric, - const QStyleOption *option = nullptr, - const QWidget *widget = nullptr) const override; + int pixelMetric(PixelMetric metric, + const QStyleOption *option = nullptr, + const QWidget *widget = nullptr) const override; QPalette standardPalette() const override; private: - void drawQmlEditorIcon( - PrimitiveElement element, - const QStyleOption *option, - const char *propertyName, - QPainter *painter, - const QWidget *widget = nullptr) const; + void drawQmlEditorIcon(PrimitiveElement element, + const QStyleOption *option, + const char *propertyName, + QPainter *painter, + const QWidget *widget = nullptr) const; StudioStylePrivate *d = nullptr; }; + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesignerbase/studio/studiostyle_p.cpp b/src/plugins/qmldesignerbase/studio/studiostyle_p.cpp new file mode 100644 index 00000000000..b13310f27ad --- /dev/null +++ b/src/plugins/qmldesignerbase/studio/studiostyle_p.cpp @@ -0,0 +1,240 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0 + +#include "studiostyle_p.h" + +#include "studiostyle.h" + +#include +#include + +#include +#include + +using namespace Utils; +using namespace QmlDesigner; + +namespace { +inline QPixmap getPixmapFromIcon( + const QIcon &icon, const QSize &size, bool enabled, bool active, bool checked) +{ + QIcon::Mode mode = enabled ? ((active) ? QIcon::Active : QIcon::Normal) : QIcon::Disabled; + QIcon::State state = (checked) ? QIcon::On : QIcon::Off; + return icon.pixmap(size, mode, state); +} + +inline QColor studioTextColor(bool enabled, bool active, bool checked) +{ + Theme::Color themePenColorId = enabled ? (active ? (checked ? Theme::DSsubPanelBackground + : Theme::DSpanelBackground) + : Theme::DStextColor) + : Theme::DStextColorDisabled; + + return creatorTheme()->color(themePenColorId); +} +}; // namespace + +StudioStylePrivate::StudioStylePrivate(StudioStyle *q) + : QObject(q) + , q(q) +{ + auto color = [](Theme::Color c) { return creatorTheme()->color(c); }; + + { + stdPalette.setColorGroup(QPalette::Disabled, // group + color(Theme::DStextColorDisabled), // windowText + color(Theme::DScontrolBackgroundDisabled), // button + color(Theme::DScontrolOutlineDisabled), // light + color(Theme::DStextSelectedTextColor), // dark + color(Theme::DSstatusbarBackground), // mid + color(Theme::DStextColorDisabled), // text + color(Theme::DStextColorDisabled), // brightText + color(Theme::DStoolbarIcon_blocked), // base + color(Theme::DStoolbarIcon_blocked) // window + ); + + stdPalette.setColorGroup(QPalette::Inactive, // group + color(Theme::DStextColor), // windowText + color(Theme::DScontrolBackground), // button + color(Theme::DStoolbarBackground), // light + color(Theme::DSstatusbarBackground), // dark + color(Theme::DScontrolBackground), // mid + color(Theme::DStextColor), // text + color(Theme::DStextColor), // brightText + color(Theme::DStoolbarBackground), // base + color(Theme::DStoolbarBackground) // window + ); + + stdPalette.setColorGroup(QPalette::Active, // group + color(Theme::DStextSelectedTextColor), // windowText + color(Theme::DSnavigatorItemBackgroundHover), // button + color(Theme::DSstateBackgroundColor_hover), // light + color(Theme::DSpanelBackground), // dark + color(Theme::DSnavigatorItemBackgroundHover), // mid + color(Theme::DStextSelectedTextColor), // text + color(Theme::DStextSelectedTextColor), // brightText + color(Theme::DStoolbarBackground), // base + color(Theme::DStoolbarBackground) // window + ); + } +} + +QList StudioStylePrivate::animationTargets() const +{ + return m_animations.keys(); +} + +QStyleAnimation *StudioStylePrivate::animation(const QObject *target) const +{ + return m_animations.value(target, nullptr); +} + +void StudioStylePrivate::startAnimation(QStyleAnimation *animation) const +{ + stopAnimation(animation->target()); + QObject::connect(animation, + &QObject::destroyed, + this, + &StudioStylePrivate::removeAnimation, + Qt::UniqueConnection); + m_animations.insert(animation->target(), animation); + animation->start(); +} + +void StudioStylePrivate::stopAnimation(const QObject *target) const +{ + QStyleAnimation *animation = m_animations.take(target); + if (animation) { + animation->stop(); + delete animation; + } +} + +void StudioStylePrivate::removeAnimation(const QObject *animationObject) +{ + if (animationObject) + m_animations.remove(animationObject->parent()); +} + +StudioShortcut::StudioShortcut(const QStyleOptionMenuItem *option, const QString &shortcutText) + : m_shortcutText(shortcutText) + , m_enabled(option->state & QStyle::State_Enabled) + , m_active(option->state & QStyle::State_Selected) + , m_font(option->font) + , m_fontMetrics(m_font) + , m_defaultHeight(m_fontMetrics.height()) + , m_spaceConst(m_fontMetrics.boundingRect(".").width()) +{ + reset(); + + if (backspaceMatch(shortcutText).hasMatch() && option->styleObject) + m_backspaceIcon = option->styleObject->property("backspaceIcon").value(); +} + +QSize StudioShortcut::getSize() +{ + if (m_isFirstParticle) + calcResult(); + return m_size; +} + +QPixmap StudioShortcut::getPixmap() +{ + if (!m_isFirstParticle && !m_pixmap.isNull()) + return m_pixmap; + + m_pixmap = QPixmap(getSize()); + m_pixmap.fill(Qt::transparent); + QPainter painter(&m_pixmap); + painter.setFont(m_font); + QPen pPen = painter.pen(); + pPen.setColor(studioTextColor(m_enabled, m_active, false)); + painter.setPen(pPen); + calcResult(&painter); + painter.end(); + + return m_pixmap; +} + +void StudioShortcut::applySize(const QSize &itemSize) +{ + m_width += itemSize.width(); + m_height = std::max(m_height, itemSize.height()); + if (m_isFirstParticle) + m_isFirstParticle = false; + else + m_width += m_spaceConst; +} + +void StudioShortcut::addText(const QString &txt, QPainter *painter) +{ + if (txt.size()) { + int textWidth = m_fontMetrics.horizontalAdvance(txt); + QSize itemSize = {textWidth, m_defaultHeight}; + if (painter) { + static const QTextOption textOption(Qt::AlignLeft | Qt::AlignVCenter); + QRect placeRect({m_width, 0}, itemSize); + painter->drawText(placeRect, txt, textOption); + } + applySize(itemSize); + } +} + +void StudioShortcut::addPixmap(const QPixmap &pixmap, QPainter *painter) +{ + if (painter) + painter->drawPixmap(QRect({m_width, 0}, pixmap.size()), pixmap); + + applySize(pixmap.size()); +} + +void StudioShortcut::calcResult(QPainter *painter) +{ + reset(); +#ifndef QT_NO_SHORTCUT + if (!m_shortcutText.isEmpty()) { + int fwdIndex = 0; + + QRegularExpressionMatch mMatch = backspaceMatch(m_shortcutText); + int matchCount = mMatch.lastCapturedIndex(); + + for (int i = 0; i <= matchCount; ++i) { + QString mStr = mMatch.captured(i); + QSize iconSize(m_defaultHeight * 3, m_defaultHeight); + const QList iconSizes = m_backspaceIcon.availableSizes(); + if (iconSizes.size()) + iconSize = iconSizes.last(); + double aspectRatio = (m_defaultHeight + .0) / iconSize.height(); + int newWidth = iconSize.width() * aspectRatio; + + QPixmap pixmap = getPixmapFromIcon(m_backspaceIcon, + {newWidth, m_defaultHeight}, + m_enabled, + m_active, + false); + + int lIndex = m_shortcutText.indexOf(mStr, fwdIndex); + int diffChars = lIndex - fwdIndex; + addText(m_shortcutText.mid(fwdIndex, diffChars), painter); + addPixmap(pixmap, painter); + fwdIndex = lIndex + mStr.size(); + } + addText(m_shortcutText.mid(fwdIndex), painter); + } +#endif + m_size = {m_width, m_height}; +} + +void StudioShortcut::reset() +{ + m_isFirstParticle = true; + m_width = 0; + m_height = 0; +} + +QRegularExpressionMatch StudioShortcut::backspaceMatch(const QString &str) const +{ + static const QRegularExpression backspaceDetect("\\+*backspace\\+*", + QRegularExpression::CaseInsensitiveOption); + return backspaceDetect.match(str); +} diff --git a/src/plugins/qmldesignerbase/studio/studiostyle_p.h b/src/plugins/qmldesignerbase/studio/studiostyle_p.h new file mode 100644 index 00000000000..ce556e61af2 --- /dev/null +++ b/src/plugins/qmldesignerbase/studio/studiostyle_p.h @@ -0,0 +1,84 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0 + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +class QStyleOptionMenuItem; +class QPainter; + +namespace Utils { +class StyleAnimation; +class QStyleAnimation; +} // namespace Utils + +namespace QmlDesigner { +class StudioStyle; + +class StudioStylePrivate : public QObject +{ + Q_OBJECT + using StyleAnimation = Utils::StyleAnimation; + using QStyleAnimation = Utils::QStyleAnimation; + +public: + explicit StudioStylePrivate(StudioStyle *q); + + QList animationTargets() const; + QStyleAnimation *animation(const QObject *target) const; + void startAnimation(QStyleAnimation *animation) const; + void stopAnimation(const QObject *target) const; + + QPalette stdPalette; + +private: + StudioStyle *q = nullptr; + mutable QHash m_animations; + +private slots: + void removeAnimation(const QObject *animationObject); +}; + +struct StudioShortcut +{ + StudioShortcut(const QStyleOptionMenuItem *option, const QString &shortcutText); + QSize getSize(); + QPixmap getPixmap(); + +private: + void applySize(const QSize &itemSize); + void addText(const QString &txt, QPainter *painter = nullptr); + void addPixmap(const QPixmap &pixmap, QPainter *painter = nullptr); + void calcResult(QPainter *painter = nullptr); + void reset(); + QRegularExpressionMatch backspaceMatch(const QString &str) const; + + const QString m_shortcutText; + const bool m_enabled; + const bool m_active; + const QFont m_font; + const QFontMetrics m_fontMetrics; + const int m_defaultHeight; + const int m_spaceConst; + + QIcon m_backspaceIcon; + bool m_isFirstParticle = true; + + int m_width = 0; + int m_height = 0; + QSize m_size; + QPixmap m_pixmap; +}; + +} // namespace QmlDesigner