QmlDesigner: Fix stacked container UI

* Add a new check for stacked container and index property being either
  a variant or a resolvable binding.
* Decrease/increase checks read resolvable bindings if applicable and
  use model value if not.
* Increase/decrease functions use resolvable bindings if applicable and
  use model value if not.
* Fix FormEditorToolButton opacity being set on the widget instead of
  the painter.

Task-number: QDS-15274
Change-Id: Ia8c95b900204d3d4041cda7f60534eb3a52c3f65
Reviewed-by: Thomas Hartmann <thomas.hartmann@qt.io>
This commit is contained in:
Henning Gruendl
2025-06-04 11:30:01 +02:00
committed by Henning Gründl
parent 1a3998e51c
commit a80281c6ed
4 changed files with 206 additions and 72 deletions

View File

@@ -25,6 +25,7 @@
#include <nodeproperty.h> #include <nodeproperty.h>
#include <qmldesignertr.h> #include <qmldesignertr.h>
#include <theme.h> #include <theme.h>
#include <variantproperty.h>
#include <formeditortoolbutton.h> #include <formeditortoolbutton.h>
@@ -1161,6 +1162,38 @@ bool isStackedContainer(const SelectionContext &context)
return NodeHints::fromModelNode(currentSelectedNode).isStackedContainer(); return NodeHints::fromModelNode(currentSelectedNode).isStackedContainer();
} }
bool isStackedContainerAndIndexIsVariantOrResolvableBinding(const SelectionContext &context)
{
if (!isStackedContainer(context))
return false;
ModelNode currentSelectedNode = context.currentSingleSelectedNode();
const PropertyName propertyName = ModelNodeOperations::getIndexPropertyName(currentSelectedNode);
QTC_ASSERT(currentSelectedNode.metaInfo().hasProperty(propertyName), return false);
QmlItemNode containerItemNode(currentSelectedNode);
QTC_ASSERT(containerItemNode.isValid(), return false);
if (containerItemNode.hasBindingProperty(propertyName)) {
const AbstractProperty resolvedProperty = containerItemNode.bindingProperty(propertyName)
.resolveToProperty();
if (resolvedProperty.isValid() && resolvedProperty.isVariantProperty()) {
auto variantProperty = resolvedProperty.toVariantProperty();
if (!variantProperty.isValid())
return false;
return true;
}
return false;
}
return containerItemNode.modelNode().hasVariantProperty(propertyName);
}
bool isStackedContainerWithoutTabBar(const SelectionContext &context) bool isStackedContainerWithoutTabBar(const SelectionContext &context)
{ {
if (!isStackedContainer(context)) if (!isStackedContainer(context))
@@ -1196,11 +1229,38 @@ bool isStackedContainerAndIndexCanBeDecreased(const SelectionContext &context)
QTC_ASSERT(currentSelectedNode.metaInfo().hasProperty(propertyName), return false); QTC_ASSERT(currentSelectedNode.metaInfo().hasProperty(propertyName), return false);
QmlItemNode containerItemNode(currentSelectedNode); QmlItemNode containerItemNode(currentSelectedNode);
QTC_ASSERT(containerItemNode.isValid(), return false); QTC_ASSERT(containerItemNode.isValid(), return false);
const int value = containerItemNode.instanceValue(propertyName).toInt(); auto isMoreThan = [](const QVariant &variant, int min) -> bool {
if (!variant.isValid())
return false;
return value > 0; bool ok = false;
int value = variant.toInt(&ok);
return ok && value > min;
};
if (currentSelectedNode.hasBindingProperty(propertyName)) {
const AbstractProperty resolvedProperty = currentSelectedNode.bindingProperty(propertyName)
.resolveToProperty();
if (resolvedProperty.isValid() && resolvedProperty.isVariantProperty()) {
const auto variantProperty = resolvedProperty.toVariantProperty();
if (!variantProperty.isValid())
return false;
if (isMoreThan(variantProperty.value(), 0))
return true;
}
return false;
}
QVariant modelValue = containerItemNode.modelValue(propertyName);
if (isMoreThan(modelValue, 0))
return true;
return false;
} }
bool isStackedContainerAndIndexCanBeIncreased(const SelectionContext &context) bool isStackedContainerAndIndexCanBeIncreased(const SelectionContext &context)
@@ -1215,13 +1275,40 @@ bool isStackedContainerAndIndexCanBeIncreased(const SelectionContext &context)
QTC_ASSERT(currentSelectedNode.metaInfo().hasProperty(propertyName), return false); QTC_ASSERT(currentSelectedNode.metaInfo().hasProperty(propertyName), return false);
QmlItemNode containerItemNode(currentSelectedNode); QmlItemNode containerItemNode(currentSelectedNode);
QTC_ASSERT(containerItemNode.isValid(), return false); QTC_ASSERT(containerItemNode.isValid(), return false);
const int value = containerItemNode.instanceValue(propertyName).toInt(); auto isLessThan = [](const QVariant &variant, int max) -> bool {
if (!variant.isValid())
return false;
bool ok = false;
int value = variant.toInt(&ok);
return ok && value < max;
};
const int maxValue = currentSelectedNode.directSubModelNodes().size() - 1; const int maxValue = currentSelectedNode.directSubModelNodes().size() - 1;
return value < maxValue; if (currentSelectedNode.hasBindingProperty(propertyName)) {
const AbstractProperty resolvedProperty = currentSelectedNode.bindingProperty(propertyName)
.resolveToProperty();
if (resolvedProperty.isValid() && resolvedProperty.isVariantProperty()) {
const auto variantProperty = resolvedProperty.toVariantProperty();
if (!variantProperty.isValid())
return false;
if (isLessThan(variantProperty.value(), maxValue))
return true;
}
return false;
}
QVariant modelValue = containerItemNode.modelValue(propertyName);
if (isLessThan(modelValue, maxValue))
return true;
return false;
} }
bool isGroup(const SelectionContext &context) bool isGroup(const SelectionContext &context)
@@ -1851,8 +1938,7 @@ void DesignerActionManager::createDefaultDesignerActions()
&isStackedContainer, &isStackedContainer,
&isStackedContainer)); &isStackedContainer));
addDesignerAction(new ModelNodeContextMenuAction( addDesignerAction(new ModelNodeContextMenuAction(addTabBarToStackedContainerCommandId,
addTabBarToStackedContainerCommandId,
addTabBarToStackedContainerDisplayName, addTabBarToStackedContainerDisplayName,
{}, {},
stackedContainerCategory, stackedContainerCategory,
@@ -1862,8 +1948,8 @@ void DesignerActionManager::createDefaultDesignerActions()
&isStackedContainerWithoutTabBar, &isStackedContainerWithoutTabBar,
&isStackedContainer)); &isStackedContainer));
addDesignerAction(new ModelNodeFormEditorAction( addDesignerAction(
decreaseIndexOfStackedContainerCommandId, new ModelNodeFormEditorAction(decreaseIndexOfStackedContainerCommandId,
decreaseIndexToStackedContainerDisplayName, decreaseIndexToStackedContainerDisplayName,
prevIcon.icon(), prevIcon.icon(),
decreaseIndexOfStackedContainerToolTip, decreaseIndexOfStackedContainerToolTip,
@@ -1872,10 +1958,9 @@ void DesignerActionManager::createDefaultDesignerActions()
3, 3,
&decreaseIndexOfStackedContainer, &decreaseIndexOfStackedContainer,
&isStackedContainerAndIndexCanBeDecreased, &isStackedContainerAndIndexCanBeDecreased,
&isStackedContainer)); &isStackedContainerAndIndexIsVariantOrResolvableBinding));
addDesignerAction(
addDesignerAction(new ModelNodeFormEditorAction( new ModelNodeFormEditorAction(increaseIndexOfStackedContainerCommandId,
increaseIndexOfStackedContainerCommandId,
increaseIndexToStackedContainerDisplayName, increaseIndexToStackedContainerDisplayName,
nextIcon.icon(), nextIcon.icon(),
increaseIndexOfStackedContainerToolTip, increaseIndexOfStackedContainerToolTip,
@@ -1884,7 +1969,7 @@ void DesignerActionManager::createDefaultDesignerActions()
4, 4,
&increaseIndexOfStackedContainer, &increaseIndexOfStackedContainer,
&isStackedContainerAndIndexCanBeIncreased, &isStackedContainerAndIndexCanBeIncreased,
&isStackedContainer)); &isStackedContainerAndIndexIsVariantOrResolvableBinding));
addDesignerAction( addDesignerAction(
new ModelNodeAction(layoutRowLayoutCommandId, new ModelNodeAction(layoutRowLayoutCommandId,

View File

@@ -1132,6 +1132,46 @@ static void setIndexProperty(const AbstractProperty &property, const QVariant &v
Core::AsynchronousMessageBox::warning(title, description); Core::AsynchronousMessageBox::warning(title, description);
} }
static std::optional<int> getIndexProperty(const AbstractProperty &property)
{
if (property.isBindingProperty()) {
const AbstractProperty resolvedProperty = property.toBindingProperty().resolveToProperty();
if (resolvedProperty.isValid() && resolvedProperty.isVariantProperty()) {
const auto variantProperty = resolvedProperty.toVariantProperty();
if (!variantProperty.isValid())
return std::nullopt;
auto variant = variantProperty.value();
if (!variant.isValid())
return std::nullopt;
bool ok = false;
int value = variant.toInt(&ok);
if (!ok)
return std::nullopt;
return value;
}
} else {
QmlItemNode itemNode(property.parentModelNode());
if (!itemNode.isValid())
return std::nullopt;
QVariant modelValue(itemNode.modelValue(property.name()));
if (!modelValue.isValid())
return std::nullopt;
bool ok = false;
int value = modelValue.toInt(&ok);
if (!ok)
return std::nullopt;
return value;
}
return std::nullopt;
}
void increaseIndexOfStackedContainer(const SelectionContext &selectionContext) void increaseIndexOfStackedContainer(const SelectionContext &selectionContext)
{ {
AbstractView *view = selectionContext.view(); AbstractView *view = selectionContext.view();
@@ -1144,17 +1184,16 @@ void increaseIndexOfStackedContainer(const SelectionContext &selectionContext)
const PropertyName propertyName = getIndexPropertyName(container); const PropertyName propertyName = getIndexPropertyName(container);
QTC_ASSERT(container.metaInfo().hasProperty(propertyName), return); QTC_ASSERT(container.metaInfo().hasProperty(propertyName), return);
QmlItemNode containerItemNode(container); std::optional<int> value = getIndexProperty(container.property(propertyName));
QTC_ASSERT(containerItemNode.isValid(), return); QTC_ASSERT(value, return);
int value = containerItemNode.instanceValue(propertyName).toInt(); ++*value;
++value;
const int maxValue = container.directSubModelNodes().size(); const int maxValue = container.directSubModelNodes().size();
QTC_ASSERT(value < maxValue, return); QTC_ASSERT(value < maxValue, return);
setIndexProperty(container.property(propertyName), value); setIndexProperty(container.property(propertyName), *value);
} }
void decreaseIndexOfStackedContainer(const SelectionContext &selectionContext) void decreaseIndexOfStackedContainer(const SelectionContext &selectionContext)
@@ -1169,15 +1208,14 @@ void decreaseIndexOfStackedContainer(const SelectionContext &selectionContext)
const PropertyName propertyName = getIndexPropertyName(container); const PropertyName propertyName = getIndexPropertyName(container);
QTC_ASSERT(container.metaInfo().hasProperty(propertyName), return); QTC_ASSERT(container.metaInfo().hasProperty(propertyName), return);
QmlItemNode containerItemNode(container); std::optional<int> value = getIndexProperty(container.property(propertyName));
QTC_ASSERT(containerItemNode.isValid(), return); QTC_ASSERT(value, return);
int value = containerItemNode.instanceValue(propertyName).toInt(); --*value;
--value;
QTC_ASSERT(value > -1, return); QTC_ASSERT(value > -1, return);
setIndexProperty(container.property(propertyName), value); setIndexProperty(container.property(propertyName), *value);
} }
void addTabBarToStackedContainer(const SelectionContext &selectionContext) void addTabBarToStackedContainer(const SelectionContext &selectionContext)

View File

@@ -7,8 +7,8 @@
#include <QGraphicsSceneMouseEvent> #include <QGraphicsSceneMouseEvent>
#include <QPainter> #include <QPainter>
#include <utils/theme/theme.h>
#include <utils/stylehelper.h> #include <utils/stylehelper.h>
#include <utils/theme/theme.h>
#include <QAction> #include <QAction>
#include <QCursor> #include <QCursor>
@@ -19,16 +19,21 @@ static const auto category = FormEditorTracing::category;
const int toolButtonSize = 14; const int toolButtonSize = 14;
FormEditorToolButton::FormEditorToolButton(QAction *action, QGraphicsItem *parent) : QGraphicsWidget(parent), m_action(action) FormEditorToolButton::FormEditorToolButton(QAction *action, QGraphicsItem *parent)
: QGraphicsWidget(parent)
, m_action(action)
{ {
NanotraceHR::Tracer tracer{"form editor tool button constructor", category()}; NanotraceHR::Tracer tracer{"form editor tool button constructor", category()};
resize(toolButtonSize, toolButtonSize + 2); resize(toolButtonSize, toolButtonSize + 2);
setPreferredSize(toolButtonSize, toolButtonSize + 2); setPreferredSize(toolButtonSize, toolButtonSize + 2);
setAcceptHoverEvents(true); setAcceptHoverEvents(true);
connect(action, &QAction::changed, this, [this](){ connect(action, &QAction::changed, this, [this]() {
setEnabled(m_action->isEnabled()); setEnabled(m_action->isEnabled());
setVisible(m_action->isVisible()); setVisible(m_action->isVisible());
// Reset to Normal. When action isn't enabled anymore and therefor the button too, it
// doesn't receives events anymore, which leaves the state in Hover.
m_state = Normal;
update(); update();
}); });
@@ -46,10 +51,14 @@ void FormEditorToolButton::paint(QPainter *painter, const QStyleOptionGraphicsIt
painter->save(); painter->save();
painter->setRenderHint(QPainter::Antialiasing); painter->setRenderHint(QPainter::Antialiasing);
QRectF adjustedRect(size().width() - toolButtonSize, size().height() - toolButtonSize, toolButtonSize, toolButtonSize); QRectF adjustedRect(size().width() - toolButtonSize,
size().height() - toolButtonSize,
toolButtonSize,
toolButtonSize);
painter->setPen(Qt::NoPen); painter->setPen(Qt::NoPen);
static QColor selectionColor = Utils::creatorColor(Utils::Theme::QmlDesigner_FormEditorSelectionColor); static QColor selectionColor = Utils::creatorColor(
Utils::Theme::QmlDesigner_FormEditorSelectionColor);
if (m_state == Hovered) if (m_state == Hovered)
painter->setBrush(selectionColor.lighter(110)); painter->setBrush(selectionColor.lighter(110));
@@ -60,9 +69,11 @@ void FormEditorToolButton::paint(QPainter *painter, const QStyleOptionGraphicsIt
painter->drawRoundedRect(adjustedRect, 1, 1, Qt::AbsoluteSize); painter->drawRoundedRect(adjustedRect, 1, 1, Qt::AbsoluteSize);
if (!isEnabled()) if (!isEnabled())
setOpacity(0.5); painter->setOpacity(0.5);
painter->drawPixmap(size().width() - toolButtonSize, size().height() - toolButtonSize, m_action->icon().pixmap(toolButtonSize, toolButtonSize)); painter->drawPixmap(size().width() - toolButtonSize,
size().height() - toolButtonSize,
m_action->icon().pixmap(toolButtonSize, toolButtonSize));
painter->restore(); painter->restore();
} }
@@ -74,7 +85,7 @@ QRectF FormEditorToolButton::boundingRect() const
return QRectF(0, 0, toolButtonSize, toolButtonSize + 2); return QRectF(0, 0, toolButtonSize, toolButtonSize + 2);
} }
void FormEditorToolButton::hoverEnterEvent(QGraphicsSceneHoverEvent * event) void FormEditorToolButton::hoverEnterEvent(QGraphicsSceneHoverEvent *event)
{ {
NanotraceHR::Tracer tracer{"form editor tool button hover enter event", category()}; NanotraceHR::Tracer tracer{"form editor tool button hover enter event", category()};
@@ -85,7 +96,7 @@ void FormEditorToolButton::hoverEnterEvent(QGraphicsSceneHoverEvent * event)
update(); update();
} }
void FormEditorToolButton::hoverLeaveEvent(QGraphicsSceneHoverEvent * event) void FormEditorToolButton::hoverLeaveEvent(QGraphicsSceneHoverEvent *event)
{ {
NanotraceHR::Tracer tracer{"form editor tool button hover leave event", category()}; NanotraceHR::Tracer tracer{"form editor tool button hover leave event", category()};
@@ -96,14 +107,14 @@ void FormEditorToolButton::hoverLeaveEvent(QGraphicsSceneHoverEvent * event)
update(); update();
} }
void FormEditorToolButton::hoverMoveEvent(QGraphicsSceneHoverEvent * event) void FormEditorToolButton::hoverMoveEvent(QGraphicsSceneHoverEvent *event)
{ {
NanotraceHR::Tracer tracer{"form editor tool button hover move event", category()}; NanotraceHR::Tracer tracer{"form editor tool button hover move event", category()};
QGraphicsWidget::hoverMoveEvent(event); QGraphicsWidget::hoverMoveEvent(event);
} }
void FormEditorToolButton::mousePressEvent(QGraphicsSceneMouseEvent * event) void FormEditorToolButton::mousePressEvent(QGraphicsSceneMouseEvent *event)
{ {
NanotraceHR::Tracer tracer{"form editor tool button mouse press event", category()}; NanotraceHR::Tracer tracer{"form editor tool button mouse press event", category()};
@@ -112,7 +123,7 @@ void FormEditorToolButton::mousePressEvent(QGraphicsSceneMouseEvent * event)
update(); update();
} }
void FormEditorToolButton::mouseReleaseEvent(QGraphicsSceneMouseEvent * event) void FormEditorToolButton::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
{ {
NanotraceHR::Tracer tracer{"form editor tool button mouse release event", category()}; NanotraceHR::Tracer tracer{"form editor tool button mouse release event", category()};
@@ -122,4 +133,4 @@ void FormEditorToolButton::mouseReleaseEvent(QGraphicsSceneMouseEvent * event)
emit clicked(); emit clicked();
} }
} //QmlDesigner } // namespace QmlDesigner

View File

@@ -212,23 +212,23 @@ void SelectionIndicator::adjustAnnotationPosition(const QRectF &itemRect, const
{ {
NanotraceHR::Tracer tracer{"selection indicator adjust annotation position", category()}; NanotraceHR::Tracer tracer{"selection indicator adjust annotation position", category()};
if (!m_annotationItem) return; if (!m_annotationItem)
return;
const qreal iconWShift = m_annotationItem->iconWidth() * 0.5; const qreal iconWShift = m_annotationItem->iconWidth() * 0.5;
const qreal iconHShift = (m_annotationItem->iconHeight() * 0.45)/scaleFactor; const qreal iconHShift = (m_annotationItem->iconHeight() * 0.45) / scaleFactor;
qreal iconX = 0.0; qreal iconX = 0.0;
qreal iconY = -(iconHShift); qreal iconY = -(iconHShift);
if (((labelRect.width() + iconWShift)/scaleFactor) > itemRect.width()) if (((labelRect.width() + iconWShift) / scaleFactor) > itemRect.width())
iconY -= labelRect.height()/scaleFactor; iconY -= labelRect.height() / scaleFactor;
if ((iconWShift/scaleFactor) > itemRect.width()) if ((iconWShift / scaleFactor) > itemRect.width())
iconX = 0.0; iconX = 0.0;
else else
iconX = (itemRect.width()) - (iconWShift/scaleFactor); iconX = (itemRect.width()) - (iconWShift / scaleFactor);
m_annotationItem->setPos(iconX*scaleFactor, iconY*scaleFactor);
}
m_annotationItem->setPos(iconX * scaleFactor, iconY * scaleFactor);
}
} }