diff --git a/src/plugins/qmldesigner/CMakeLists.txt b/src/plugins/qmldesigner/CMakeLists.txt index a686a4efd8c..33b4df0292f 100644 --- a/src/plugins/qmldesigner/CMakeLists.txt +++ b/src/plugins/qmldesigner/CMakeLists.txt @@ -663,6 +663,7 @@ extend_qtc_plugin(QmlDesigner edit3dtoolbarmenu.cpp edit3dtoolbarmenu.h backgroundcolorselection.cpp backgroundcolorselection.h bakelights.cpp bakelights.h + indicatoractionwidget.cpp indicatoractionwidget.h snapconfiguration.cpp snapconfiguration.h cameraspeedconfiguration.cpp cameraspeedconfiguration.h bakelightsdatamodel.cpp bakelightsdatamodel.h diff --git a/src/plugins/qmldesigner/components/edit3d/edit3dactions.cpp b/src/plugins/qmldesigner/components/edit3d/edit3dactions.cpp index b3d140bc887..83299a485d5 100644 --- a/src/plugins/qmldesigner/components/edit3d/edit3dactions.cpp +++ b/src/plugins/qmldesigner/components/edit3d/edit3dactions.cpp @@ -3,9 +3,8 @@ #include "edit3dactions.h" -#include "bakelights.h" #include "edit3dview.h" -#include "nodemetainfo.h" +#include "indicatoractionwidget.h" #include "qmldesignerconstants.h" #include "seekerslider.h" @@ -34,10 +33,25 @@ void Edit3DActionTemplate::actionTriggered(bool b) m_action(m_selectionContext); } -Edit3DWidgetActionTemplate::Edit3DWidgetActionTemplate(QWidgetAction *widget) +Edit3DWidgetActionTemplate::Edit3DWidgetActionTemplate(QWidgetAction *widget, + SelectionContextOperation action) : PureActionInterface(widget) + , m_action(action) { + QObject::connect(widget, &QAction::triggered, widget, [this](bool value) { + actionTriggered(value); + }); +} +void Edit3DWidgetActionTemplate::setSelectionContext(const SelectionContext &selectionContext) +{ + m_selectionContext = selectionContext; +} + +void Edit3DWidgetActionTemplate::actionTriggered([[maybe_unused]] bool b) +{ + if (m_action) + m_action(m_selectionContext); } Edit3DAction::Edit3DAction(const QByteArray &menuId, @@ -154,4 +168,34 @@ bool Edit3DBakeLightsAction::isEnabled(const SelectionContext &) const && !Utils3D::activeView3dId(m_view).isEmpty(); } +Edit3DIndicatorButtonAction::Edit3DIndicatorButtonAction(const QByteArray &menuId, + View3DActionType type, + const QString &description, + const QIcon &icon, + SelectionContextOperation customAction, + Edit3DView *view) + : Edit3DAction(menuId, + type, + view, + new Edit3DWidgetActionTemplate(new IndicatorButtonAction(description, icon), + customAction)) +{ + m_buttonAction = qobject_cast(action()); } + +void Edit3DIndicatorButtonAction::setIndicator(bool indicator) +{ + m_buttonAction->setIndicator(indicator); +} + +bool Edit3DIndicatorButtonAction::isVisible(const SelectionContext &) const +{ + return m_buttonAction->isVisible(); +} + +bool Edit3DIndicatorButtonAction::isEnabled(const SelectionContext &) const +{ + return m_buttonAction->isEnabled(); +} + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/edit3d/edit3dactions.h b/src/plugins/qmldesigner/components/edit3d/edit3dactions.h index 3a451ea40d5..03aeb22902a 100644 --- a/src/plugins/qmldesigner/components/edit3d/edit3dactions.h +++ b/src/plugins/qmldesigner/components/edit3d/edit3dactions.h @@ -17,6 +17,7 @@ namespace QmlDesigner { using SelectionContextOperation = std::function; class Edit3DView; class SeekerSliderAction; +class IndicatorButtonAction; class Edit3DActionTemplate : public DefaultAction { @@ -40,8 +41,13 @@ class Edit3DWidgetActionTemplate : public PureActionInterface Q_DISABLE_COPY(Edit3DWidgetActionTemplate) public: - explicit Edit3DWidgetActionTemplate(QWidgetAction *widget); - virtual void setSelectionContext(const SelectionContext &) {} + explicit Edit3DWidgetActionTemplate(QWidgetAction *widget, SelectionContextOperation action = {}); + + void setSelectionContext(const SelectionContext &selectionContext) override; + virtual void actionTriggered(bool b); + + SelectionContextOperation m_action; + SelectionContext m_selectionContext; }; class Edit3DAction : public AbstractAction @@ -108,6 +114,27 @@ private: SeekerSliderAction *m_seeker = nullptr; }; +class Edit3DIndicatorButtonAction : public Edit3DAction +{ +public: + Edit3DIndicatorButtonAction(const QByteArray &menuId, + View3DActionType type, + const QString &description, + const QIcon &icon, + SelectionContextOperation customAction, + Edit3DView *view); + + IndicatorButtonAction *buttonAction(); + void setIndicator(bool indicator); + +protected: + bool isVisible(const SelectionContext &) const override; + bool isEnabled(const SelectionContext &) const override; + +private: + IndicatorButtonAction *m_buttonAction = nullptr; +}; + class Edit3DBakeLightsAction : public Edit3DAction { public: diff --git a/src/plugins/qmldesigner/components/edit3d/edit3dview.cpp b/src/plugins/qmldesigner/components/edit3d/edit3dview.cpp index bb7404f2522..810265a6c0f 100644 --- a/src/plugins/qmldesigner/components/edit3d/edit3dview.cpp +++ b/src/plugins/qmldesigner/components/edit3d/edit3dview.cpp @@ -57,6 +57,19 @@ inline static QIcon toolbarIcon(const DesignerIcons::IconId &iconId) return DesignerActionManager::instance().toolbarIcon(iconId); }; +#ifdef Q_OS_MACOS +extern "C" bool AXIsProcessTrusted(); +#endif + +static bool isAXITrusted() +{ +#ifdef Q_OS_MACOS + return AXIsProcessTrusted(); +#else + return true; +#endif +} + Edit3DView::Edit3DView(ExternalDependenciesInterface &externalDependencies) : AbstractView{externalDependencies} { @@ -1315,17 +1328,16 @@ void Edit3DView::createEdit3DActions() m_cameraSpeedConfiguration->showConfigDialog(resolveToolbarPopupPos(m_cameraSpeedConfigAction.get())); }; - m_cameraSpeedConfigAction = std::make_unique( + m_cameraSpeedConfigAction = std::make_unique( QmlDesigner::Constants::EDIT3D_CAMERA_SPEED_CONFIG, View3DActionType::Empty, - QCoreApplication::translate("CameraSpeedConfigAction", "Open camera speed configuration dialog"), - QKeySequence(), - false, - false, + QCoreApplication::translate("CameraSpeedConfigAction", + "Open camera speed configuration dialog"), toolbarIcon(DesignerIcons::CameraSpeedConfigIcon), - this, - cameraSpeedConfigTrigger); + cameraSpeedConfigTrigger, + this); + m_cameraSpeedConfigAction->setIndicator(!isAXITrusted()); m_leftActions << m_selectionModeAction.get(); m_leftActions << nullptr; // Null indicates separator diff --git a/src/plugins/qmldesigner/components/edit3d/edit3dview.h b/src/plugins/qmldesigner/components/edit3d/edit3dview.h index 755efc0ae38..25d620551a3 100644 --- a/src/plugins/qmldesigner/components/edit3d/edit3dview.h +++ b/src/plugins/qmldesigner/components/edit3d/edit3dview.h @@ -180,7 +180,7 @@ private: std::unique_ptr m_backgroundColorMenuAction; std::unique_ptr m_snapToggleAction; std::unique_ptr m_snapConfigAction; - std::unique_ptr m_cameraSpeedConfigAction; + std::unique_ptr m_cameraSpeedConfigAction; std::unique_ptr m_bakeLightsAction; int particlemode; diff --git a/src/plugins/qmldesigner/components/edit3d/indicatoractionwidget.cpp b/src/plugins/qmldesigner/components/edit3d/indicatoractionwidget.cpp new file mode 100644 index 00000000000..a1ad82f2a7a --- /dev/null +++ b/src/plugins/qmldesigner/components/edit3d/indicatoractionwidget.cpp @@ -0,0 +1,193 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "indicatoractionwidget.h" + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +namespace QmlDesigner { + +static void drawIndicator(QPainter *painter, const QPoint &point, int dimension) +{ + painter->save(); + painter->setPen(Qt::NoPen); + painter->setBrush(Theme::getColor(Theme::DSamberLight)); + painter->drawEllipse(point, dimension, dimension); + painter->restore(); +} + +IndicatorButton::IndicatorButton(QWidget *parent) + : QToolButton(parent) +{ + Utils::StyleHelper::setPanelWidget(this); + Utils::StyleHelper::setPanelWidgetSingleRow(this); +} + +bool IndicatorButton::indicator() const +{ + return m_indicator; +} + +void IndicatorButton::setIndicator(bool newIndicator) +{ + if (m_indicator != newIndicator) { + m_indicator = newIndicator; + emit indicatorChanged(m_indicator); + update(); + } +} + +QSize IndicatorButton::sizeHint() const +{ + if (QMenu *menu = qobject_cast(parent())) { + ensurePolished(); + QStyleOptionMenuItem opt; + initMenuStyleOption(menu, &opt, defaultAction()); + QSize sz = style() + ->itemTextRect(fontMetrics(), QRect(), Qt::TextShowMnemonic, false, text()) + .size(); + if (!opt.icon.isNull()) + sz = QSize(sz.width() + opt.maxIconWidth + 4, qMax(sz.height(), opt.maxIconWidth)); + QSize size = style()->sizeFromContents(QStyle::CT_MenuItem, &opt, sz, this); + return size; + } + return Super::sizeHint(); +} + +void IndicatorButton::paintEvent([[maybe_unused]] QPaintEvent *event) +{ + QStylePainter p(this); + + if (QMenu *menu = qobject_cast(parent())) { + this->setFixedWidth(menu->width()); + QStyleOptionMenuItem opt; + initMenuStyleOption(menu, &opt, defaultAction()); + p.drawControl(QStyle::CE_MenuItem, opt); + + if (indicator() && opt.maxIconWidth && !opt.icon.isNull()) { + const int indicatorDim = opt.rect.height() / 8; + const int indicatorOffset = indicatorDim * 5 / 4; + + drawIndicator(&p, + opt.rect.topLeft() + + QPoint{opt.rect.height() - indicatorOffset, indicatorOffset}, + indicatorDim); + } + } else { + QStyleOptionToolButton option; + initStyleOption(&option); + p.drawComplexControl(QStyle::CC_ToolButton, option); + + if (indicator() && option.iconSize.isValid() && !option.icon.isNull()) { + const int indicatorDim = std::min(option.rect.width(), option.rect.height()) / 8; + const int indicatorOffset = indicatorDim * 5 / 4; + drawIndicator(&p, + option.rect.topRight() + QPoint{-indicatorOffset, indicatorOffset}, + indicatorDim); + } + } +} + +void IndicatorButton::initMenuStyleOption(QMenu *menu, + QStyleOptionMenuItem *option, + const QAction *action) const +{ + if (!option || !action) + return; + + option->initFrom(menu); + option->palette = palette(); + option->state = QStyle::State_None; + + if (window()->isActiveWindow()) + option->state |= QStyle::State_Active; + if (isEnabled() && action->isEnabled() && (!action->menu() || action->menu()->isEnabled())) + option->state |= QStyle::State_Enabled; + else + option->palette.setCurrentColorGroup(QPalette::Disabled); + + option->font = action->font().resolve(font()); + option->fontMetrics = QFontMetrics(option->font); + + if (menu->activeAction() && menu->activeAction() == action) + option->state |= QStyle::State_Selected; + + option->menuHasCheckableItems = false; + if (!action->isCheckable()) { + option->checkType = QStyleOptionMenuItem::NotCheckable; + } else { + option->checkType = (action->actionGroup() && action->actionGroup()->isExclusive()) + ? QStyleOptionMenuItem::Exclusive + : QStyleOptionMenuItem::NonExclusive; + option->checked = action->isChecked(); + } + if (action->menu()) + option->menuItemType = QStyleOptionMenuItem::SubMenu; + else if (action->isSeparator()) + option->menuItemType = QStyleOptionMenuItem::Separator; + else + option->menuItemType = QStyleOptionMenuItem::Normal; + if (action->isIconVisibleInMenu()) + option->icon = action->icon(); + + option->text = action->text(); + option->maxIconWidth = 20; + option->rect = rect(); +} + +IndicatorButtonAction::IndicatorButtonAction(const QString &description, + const QIcon &icon, + QObject *parent) + : QWidgetAction(parent) +{ + setText(description); + setToolTip(description); + setIcon(icon); +} + +IndicatorButtonAction::~IndicatorButtonAction() = default; + +void IndicatorButtonAction::setIndicator(bool indicator) +{ + if (m_indicator != indicator) { + m_indicator = indicator; + emit indicatorChanged(m_indicator, QPrivateSignal{}); + } +} + +QWidget *IndicatorButtonAction::createWidget(QWidget *parent) +{ + if (QMenu *menu = qobject_cast(parent)) + return nullptr; + + IndicatorButton *button = new IndicatorButton(parent); + + connect(this, &IndicatorButtonAction::indicatorChanged, button, &IndicatorButton::setIndicator); + connect(button, &IndicatorButton::indicatorChanged, this, &IndicatorButtonAction::setIndicator); + connect(button, &QToolButton::clicked, this, &QAction::trigger); + button->setIndicator(m_indicator); + button->setDefaultAction(this); + + if (QToolBar *tb = qobject_cast(parent)) { + button->setAutoRaise(true); + button->setFocusPolicy(Qt::NoFocus); + button->setIconSize(tb->iconSize()); + button->setToolButtonStyle(tb->toolButtonStyle()); + connect(tb, &QToolBar::iconSizeChanged, button, &IndicatorButton::setIconSize); + connect(tb, &QToolBar::toolButtonStyleChanged, button, &IndicatorButton::setToolButtonStyle); + connect(button, &IndicatorButton::triggered, tb, &QToolBar::actionTriggered); + } + + return button; +} + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/edit3d/indicatoractionwidget.h b/src/plugins/qmldesigner/components/edit3d/indicatoractionwidget.h new file mode 100644 index 00000000000..dd34fa50588 --- /dev/null +++ b/src/plugins/qmldesigner/components/edit3d/indicatoractionwidget.h @@ -0,0 +1,63 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 +#pragma once + +#include +#include + +QT_BEGIN_NAMESPACE +class QStyleOptionToolButton; +class QPaintEvent; +class QStyleOptionMenuItem; +QT_END_NAMESPACE + +namespace QmlDesigner { + +class IndicatorButton : public QToolButton +{ + Q_OBJECT + + Q_PROPERTY(bool indicator READ indicator WRITE setIndicator NOTIFY indicatorChanged FINAL) +public: + explicit IndicatorButton(QWidget *parent = nullptr); + + bool indicator() const; + void setIndicator(bool newIndicator); + +protected: + QSize sizeHint() const override; + void paintEvent(QPaintEvent *event) override; + void initMenuStyleOption(QMenu *menu, QStyleOptionMenuItem *option, const QAction *action) const; + +signals: + void indicatorChanged(bool); + +private: + bool m_indicator = false; + using Super = QToolButton; +}; + +class IndicatorButtonAction : public QWidgetAction +{ + Q_OBJECT + +public: + explicit IndicatorButtonAction(const QString &description, + const QIcon &icon, + QObject *parent = nullptr); + virtual ~IndicatorButtonAction(); + +public slots: + void setIndicator(bool indicator); + +protected: + virtual QWidget *createWidget(QWidget *parent) override; + +private: + bool m_indicator; + +signals: + void indicatorChanged(bool, QPrivateSignal); +}; + +} // namespace QmlDesigner