From 5ccf82b3664289971644d0e9d6e6209ac31a2cf1 Mon Sep 17 00:00:00 2001 From: Hatem ElKharashy Date: Wed, 8 Nov 2023 14:39:49 +0200 Subject: [PATCH] Material: respect horizontalAlignment in placeholder text This allows placeholder text to follow the Alignment set to the TextField or TextArea components when using Material style. The placeholder text will float to the left, right, or center depending on the alignment set, and the arc will be drawn properly in case of Material.Outline container style. Fixes: QTBUG-118856 Change-Id: Ic9cede806dc2f6109e7e2c4b2b2fc960d9c6a1b6 Reviewed-by: Mitch Curtis (cherry picked from commit 7e678acbb6167218d349c49436499121b2025af5) Reviewed-by: Qt Cherry-pick Bot (cherry picked from commit c8a95333eb532ad1611f6e2f1a74d96b156cfb16) --- src/quickcontrols/material/TextArea.qml | 1 + src/quickcontrols/material/TextField.qml | 1 + .../impl/qquickmaterialplaceholdertext.cpp | 27 +++++++++-- .../impl/qquickmaterialplaceholdertext_p.h | 3 ++ .../impl/qquickmaterialtextcontainer.cpp | 47 +++++++++++++------ .../impl/qquickmaterialtextcontainer_p.h | 13 +++++ .../qquickmaterialstyle/data/tst_material.qml | 11 +++-- 7 files changed, 79 insertions(+), 24 deletions(-) diff --git a/src/quickcontrols/material/TextArea.qml b/src/quickcontrols/material/TextArea.qml index 1da79c0415..34814d8f2c 100644 --- a/src/quickcontrols/material/TextArea.qml +++ b/src/quickcontrols/material/TextArea.qml @@ -73,6 +73,7 @@ T.TextArea { // When the control's size is set larger than its implicit size, use whatever size is smaller // so that the gap isn't too big. placeholderTextWidth: Math.min(placeholder.width, placeholder.implicitWidth) * placeholder.scale + placeholderTextHAlign: control.effectiveHorizontalAlignment controlHasActiveFocus: control.activeFocus controlHasText: control.length > 0 placeholderHasText: placeholder.text.length > 0 diff --git a/src/quickcontrols/material/TextField.qml b/src/quickcontrols/material/TextField.qml index 9d16c0d329..3d1d821889 100644 --- a/src/quickcontrols/material/TextField.qml +++ b/src/quickcontrols/material/TextField.qml @@ -69,6 +69,7 @@ T.TextField { // When the control's size is set larger than its implicit size, use whatever size is smaller // so that the gap isn't too big. placeholderTextWidth: Math.min(placeholder.width, placeholder.implicitWidth) * placeholder.scale + placeholderTextHAlign: control.effectiveHorizontalAlignment controlHasActiveFocus: control.activeFocus controlHasText: control.length > 0 placeholderHasText: placeholder.text.length > 0 diff --git a/src/quickcontrols/material/impl/qquickmaterialplaceholdertext.cpp b/src/quickcontrols/material/impl/qquickmaterialplaceholdertext.cpp index d1c652f53f..9d697da4cf 100644 --- a/src/quickcontrols/material/impl/qquickmaterialplaceholdertext.cpp +++ b/src/quickcontrols/material/impl/qquickmaterialplaceholdertext.cpp @@ -29,8 +29,8 @@ Q_GLOBAL_STATIC(QEasingCurve, animationEasingCurve, QEasingCurve::OutSine); QQuickMaterialPlaceholderText::QQuickMaterialPlaceholderText(QQuickItem *parent) : QQuickPlaceholderText(parent) { - // Ensure that scaling happens on the left side, at the vertical center. - setTransformOrigin(QQuickItem::Left); + connect(this, &QQuickMaterialPlaceholderText::effectiveHorizontalAlignmentChanged, + this, &QQuickMaterialPlaceholderText::adjustTransformOrigin); } bool QQuickMaterialPlaceholderText::isFilled() const @@ -212,6 +212,23 @@ void QQuickMaterialPlaceholderText::setVerticalPadding(qreal verticalPadding) emit verticalPaddingChanged(); } +void QQuickMaterialPlaceholderText::adjustTransformOrigin() +{ + switch (effectiveHAlign()) { + case QQuickText::AlignLeft: + Q_FALLTHROUGH(); + case QQuickText::AlignJustify: + setTransformOrigin(QQuickItem::Left); + break; + case QQuickText::AlignRight: + setTransformOrigin(QQuickItem::Right); + break; + case QQuickText::AlignHCenter: + setTransformOrigin(QQuickItem::Center); + break; + } +} + void QQuickMaterialPlaceholderText::controlGotActiveFocus() { if (m_focusOutAnimation) @@ -275,9 +292,9 @@ void QQuickMaterialPlaceholderText::maybeSetFocusAnimationProgress() void QQuickMaterialPlaceholderText::componentComplete() { - // We deliberately do not call QQuickPlaceholderText's implementation here, - // as Material 3 placeholder text should always be left-aligned. - QQuickText::componentComplete(); + QQuickPlaceholderText::componentComplete(); + + adjustTransformOrigin(); m_largestHeight = implicitHeight(); if (m_largestHeight > 0) { diff --git a/src/quickcontrols/material/impl/qquickmaterialplaceholdertext_p.h b/src/quickcontrols/material/impl/qquickmaterialplaceholdertext_p.h index 7cd496a551..1eaa22ec71 100644 --- a/src/quickcontrols/material/impl/qquickmaterialplaceholdertext_p.h +++ b/src/quickcontrols/material/impl/qquickmaterialplaceholdertext_p.h @@ -69,6 +69,9 @@ signals: void controlImplicitBackgroundHeightChanged(); void verticalPaddingChanged(); +private slots: + void adjustTransformOrigin(); + private: bool shouldFloat() const; bool shouldAnimate() const; diff --git a/src/quickcontrols/material/impl/qquickmaterialtextcontainer.cpp b/src/quickcontrols/material/impl/qquickmaterialtextcontainer.cpp index baa1236b5c..1731145a6d 100644 --- a/src/quickcontrols/material/impl/qquickmaterialtextcontainer.cpp +++ b/src/quickcontrols/material/impl/qquickmaterialtextcontainer.cpp @@ -118,6 +118,20 @@ void QQuickMaterialTextContainer::setPlaceholderTextWidth(qreal placeholderTextW update(); } +QQuickMaterialTextContainer::PlaceHolderHAlignment QQuickMaterialTextContainer::placeholderTextHAlign() const +{ + return m_placeholderTextHAlign; +} + +void QQuickMaterialTextContainer::setPlaceholderTextHAlign(PlaceHolderHAlignment placeholderTextHAlign) +{ + if (m_placeholderTextHAlign == placeholderTextHAlign) + return; + + m_placeholderTextHAlign = placeholderTextHAlign; + update(); +} + bool QQuickMaterialTextContainer::controlHasActiveFocus() const { return m_controlHasActiveFocus; @@ -208,6 +222,25 @@ void QQuickMaterialTextContainer::paint(QPainter *painter) // This is coincidentally the same as cornerRadius, but use different variable names // to keep the code understandable. const qreal gapPadding = 4; + // When animating focus on outlined containers, we need to make a gap + // at the top left for the placeholder text. + // If the text is too wide for the container, it will be elided, so + // we shouldn't need to clamp its width here. TODO: check that this is the case for TextArea. + const qreal halfPlaceholderWidth = m_placeholderTextWidth / 2; + // Take care of different Alignment cases for the placeholder text. + qreal gapCenterX; + switch (m_placeholderTextHAlign) { + case PlaceHolderHAlignment::AlignHCenter: + gapCenterX = width() / 2; + break; + case PlaceHolderHAlignment::AlignRight: + gapCenterX = width() - halfPlaceholderWidth - m_horizontalPadding; + break; + default: + gapCenterX = m_horizontalPadding + halfPlaceholderWidth; + break; + } + QPainterPath path; QPointF startPos; @@ -216,15 +249,6 @@ void QQuickMaterialTextContainer::paint(QPainter *painter) if (m_filled || m_focusAnimationProgress == 0) { startPos = QPointF(cornerRadius, 0); } else { - // When animating focus on outlined containers, we need to make a gap - // at the top left for the placeholder text. - // If the text is too wide for the container, it will be elided, so - // we shouldn't need to clamp its width here. TODO: check that this is the case for TextArea. - const qreal halfPlaceholderWidth = m_placeholderTextWidth / 2; - // Left padding plus half of the placeholder text gives the center of the placeholder text gap. - // Note that the placeholder gap is always aligned to the left side of the TextField, - // not the center, so we can't just use half the container's width. - const qreal gapCenterX = m_horizontalPadding + halfPlaceholderWidth; // Start at the center of the gap and animate outwards towards the left-hand side. // Subtract gapPadding to account for the gap between the line and the placeholder text. // Also subtract the pen width because otherwise it extends by that distance too much to the right. @@ -259,11 +283,6 @@ void QQuickMaterialTextContainer::paint(QPainter *painter) // Back to the start. path.lineTo(startPos.x(), startPos.y()); } else { - // Go to the end (right-hand side) of the gap. - const qreal halfPlaceholderWidth = (/*placeholderTextGap * 2 + */m_placeholderTextWidth) / 2; - const qreal gapCenterX = m_horizontalPadding + halfPlaceholderWidth; - // Just "+ placeholderTextGap" should be enough to get us to the correct place - not - // sure why doubling it is necessary... path.lineTo(gapCenterX + (m_focusAnimationProgress * halfPlaceholderWidth) + gapPadding, startPos.y()); } diff --git a/src/quickcontrols/material/impl/qquickmaterialtextcontainer_p.h b/src/quickcontrols/material/impl/qquickmaterialtextcontainer_p.h index 025f3a570c..5871b22f23 100644 --- a/src/quickcontrols/material/impl/qquickmaterialtextcontainer_p.h +++ b/src/quickcontrols/material/impl/qquickmaterialtextcontainer_p.h @@ -32,6 +32,7 @@ class QQuickMaterialTextContainer : public QQuickPaintedItem Q_PROPERTY(QColor focusedOutlineColor READ focusedOutlineColor WRITE setFocusedOutlineColor FINAL) Q_PROPERTY(qreal focusAnimationProgress READ focusAnimationProgress WRITE setFocusAnimationProgress FINAL) Q_PROPERTY(qreal placeholderTextWidth READ placeholderTextWidth WRITE setPlaceholderTextWidth FINAL) + Q_PROPERTY(PlaceHolderHAlignment placeholderTextHAlign READ placeholderTextHAlign WRITE setPlaceholderTextHAlign FINAL) Q_PROPERTY(bool controlHasText READ controlHasText WRITE setControlHasText NOTIFY controlHasTextChanged FINAL) Q_PROPERTY(bool placeholderHasText READ placeholderHasText WRITE setPlaceholderHasText NOTIFY placeholderHasTextChanged FINAL) Q_PROPERTY(int horizontalPadding READ horizontalPadding WRITE setHorizontalPadding NOTIFY horizontalPaddingChanged FINAL) @@ -41,6 +42,14 @@ class QQuickMaterialTextContainer : public QQuickPaintedItem public: explicit QQuickMaterialTextContainer(QQuickItem *parent = nullptr); + enum PlaceHolderHAlignment { + AlignLeft = Qt::AlignLeft, + AlignRight = Qt::AlignRight, + AlignHCenter = Qt::AlignHCenter, + AlignJustify = Qt::AlignJustify + }; + Q_ENUM(PlaceHolderHAlignment) + bool isFilled() const; void setFilled(bool filled); @@ -73,6 +82,9 @@ public: void paint(QPainter *painter) override; + PlaceHolderHAlignment placeholderTextHAlign() const; + void setPlaceholderTextHAlign(PlaceHolderHAlignment placeHolderTextHAlign); + signals: void animateChanged(); void controlHasActiveFocusChanged(); @@ -102,6 +114,7 @@ private: bool m_controlHasText = false; bool m_placeholderHasText = false; int m_horizontalPadding = 0; + PlaceHolderHAlignment m_placeholderTextHAlign; }; QT_END_NAMESPACE diff --git a/tests/auto/quickcontrols/qquickmaterialstyle/data/tst_material.qml b/tests/auto/quickcontrols/qquickmaterialstyle/data/tst_material.qml index a5ae0201c2..ae42355dfd 100644 --- a/tests/auto/quickcontrols/qquickmaterialstyle/data/tst_material.qml +++ b/tests/auto/quickcontrols/qquickmaterialstyle/data/tst_material.qml @@ -984,10 +984,10 @@ TestCase { verify(textField) let placeholderTextItem = textField.children[0] verify(placeholderTextItem as MaterialImpl.FloatingPlaceholderText) - compare(placeholderTextItem.horizontalAlignment, TextField.AlignLeft) + compare(placeholderTextItem.horizontalAlignment, data.horizontalAlignment) textField.forceActiveFocus() - compare(placeholderTextItem.horizontalAlignment, TextField.AlignLeft) + compare(placeholderTextItem.horizontalAlignment, data.horizontalAlignment) textField.destroy() } @@ -995,7 +995,8 @@ TestCase { return [ { tag: "AlignLeft", horizontalAlignment: TextArea.AlignLeft }, { tag: "AlignHCenter", horizontalAlignment: TextArea.AlignHCenter }, - { tag: "AlignRight", horizontalAlignment: TextArea.AlignRight } + { tag: "AlignRight", horizontalAlignment: TextArea.AlignRight }, + { tag: "AlignJustify", horizontalAlignment: TextArea.AlignJustify } ] } @@ -1008,10 +1009,10 @@ TestCase { verify(textArea) let placeholderTextItem = textArea.children[0] verify(placeholderTextItem as MaterialImpl.FloatingPlaceholderText) - compare(placeholderTextItem.horizontalAlignment, TextArea.AlignLeft) + compare(placeholderTextItem.horizontalAlignment, data.horizontalAlignment) textArea.forceActiveFocus() - compare(placeholderTextItem.horizontalAlignment, TextArea.AlignLeft) + compare(placeholderTextItem.horizontalAlignment, data.horizontalAlignment) } function test_placeholderTextPos() {