diff options
Diffstat (limited to 'src/quickcontrols/material')
48 files changed, 1551 insertions, 334 deletions
diff --git a/src/quickcontrols/material/Button.qml b/src/quickcontrols/material/Button.qml index e2bbb58687..8ffe07bbc3 100644 --- a/src/quickcontrols/material/Button.qml +++ b/src/quickcontrols/material/Button.qml @@ -17,10 +17,10 @@ T.Button { topInset: 6 bottomInset: 6 - verticalPadding: 14 - // https://m3.material.io/components/buttons/specs#256326ad-f934-40e7-b05f-0bcb41aa4382 - leftPadding: !flat ? (!hasIcon ? 24 : 16) : 12 - rightPadding: !flat ? 24 : (!hasIcon ? 12 : 16) + verticalPadding: Material.buttonVerticalPadding + leftPadding: Material.buttonLeftPadding(flat, hasIcon && (display !== AbstractButton.TextOnly)) + rightPadding: Material.buttonRightPadding(flat, hasIcon && (display !== AbstractButton.TextOnly), + (text !== "") && (display !== AbstractButton.IconOnly)) spacing: 8 icon.width: 24 diff --git a/src/quickcontrols/material/CMakeLists.txt b/src/quickcontrols/material/CMakeLists.txt index f328e775c3..0c7416ea7f 100644 --- a/src/quickcontrols/material/CMakeLists.txt +++ b/src/quickcontrols/material/CMakeLists.txt @@ -94,7 +94,9 @@ set_source_files_properties(VerticalHeaderView.qml PROPERTIES QT_QML_SOURCE_VERSIONS "2.15;6.0" ) -qt_internal_add_qml_module(qtquickcontrols2materialstyleplugin +add_subdirectory(impl) + +qt_internal_add_qml_module(QuickControls2Material URI "QtQuick.Controls.Material" VERSION "${PROJECT_VERSION}" PAST_MAJOR_VERSIONS 2 @@ -109,7 +111,6 @@ qt_internal_add_qml_module(qtquickcontrols2materialstyleplugin SOURCES qquickmaterialstyle.cpp qquickmaterialstyle_p.h qquickmaterialtheme.cpp qquickmaterialtheme_p.h - qtquickcontrols2materialstyleplugin.cpp QML_FILES ${qml_files} DEFINES @@ -120,12 +121,23 @@ qt_internal_add_qml_module(qtquickcontrols2materialstyleplugin Qt::GuiPrivate Qt::QmlPrivate Qt::QuickControls2ImplPrivate + Qt::QuickControls2MaterialStyleImpl Qt::QuickControls2Private Qt::QuickPrivate Qt::QuickTemplates2Private ) -qt_internal_add_resource(qtquickcontrols2materialstyleplugin "qtquickcontrols2materialstyleplugin" +target_sources(qtquickcontrols2materialstyleplugin + PRIVATE + qtquickcontrols2materialstyleplugin.cpp +) + +target_link_libraries(qtquickcontrols2materialstyleplugin + PRIVATE + Qt::QuickControls2Private +) + +qt_internal_add_resource(QuickControls2Material "qtquickcontrols2materialstyle" PREFIX "/qt-project.org/imports/QtQuick/Controls/Material" FILES @@ -141,13 +153,18 @@ qt_internal_add_resource(qtquickcontrols2materialstyleplugin "qtquickcontrols2ma "images/drop-indicator@2x.png" "images/drop-indicator@3x.png" "images/drop-indicator@4x.png" - "shaders/+glslcore/RectangularGlow.frag" - "shaders/+hlsl/RectangularGlow.frag" - "shaders/+qsb/RectangularGlow.frag" - "shaders/RectangularGlow.frag" ) -add_subdirectory(impl) +qt_internal_add_shaders(QuickControls2Material "qtquickcontrols2materialstyle_shaders" + SILENT + BATCHABLE + PRECOMPILE + OPTIMIZED + PREFIX + "/qt-project.org/imports/QtQuick/Controls/Material" + FILES + "shaders/RectangularGlow.frag" +) _qt_internal_add_qml_static_plugin_dependency(qtquickcontrols2materialstyleplugin quickwindow) _qt_internal_add_qml_static_plugin_dependency(qtquickcontrols2materialstyleplugin diff --git a/src/quickcontrols/material/ComboBox.qml b/src/quickcontrols/material/ComboBox.qml index 30222c4180..5694aa055c 100644 --- a/src/quickcontrols/material/ComboBox.qml +++ b/src/quickcontrols/material/ComboBox.qml @@ -1,6 +1,8 @@ // Copyright (C) 2017 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +pragma ComponentBehavior: Bound + import QtQuick import QtQuick.Window import QtQuick.Controls.impl @@ -17,20 +19,18 @@ T.ComboBox { implicitContentHeight + topPadding + bottomPadding, implicitIndicatorHeight + topPadding + bottomPadding) - topInset: 6 - bottomInset: 6 - leftPadding: padding + (!control.mirrored || !indicator || !indicator.visible ? 0 : indicator.width + spacing) rightPadding: padding + (control.mirrored || !indicator || !indicator.visible ? 0 : indicator.width + spacing) - Material.elevation: flat ? control.pressed || (enabled && control.hovered) ? 2 : 0 - : control.pressed ? 8 : 2 Material.background: flat ? "transparent" : undefined Material.foreground: flat ? undefined : Material.primaryTextColor delegate: MenuItem { + required property var model + required property int index + width: ListView.view.width - text: control.textRole ? (Array.isArray(control.model) ? modelData[control.textRole] : model[control.textRole]) : modelData + text: model[control.textRole] Material.foreground: control.currentIndex === index ? ListView.view.contentItem.Material.accent : ListView.view.contentItem.Material.foreground highlighted: control.highlightedIndex === index hoverEnabled: control.hoverEnabled @@ -44,9 +44,9 @@ T.ComboBox { } contentItem: T.TextField { - padding: 6 - leftPadding: control.editable ? 2 : control.mirrored ? 0 : 12 - rightPadding: control.editable ? 2 : control.mirrored ? 12 : 0 + leftPadding: Material.textFieldHorizontalPadding + topPadding: Material.textFieldVerticalPadding + bottomPadding: Material.textFieldVerticalPadding text: control.editable ? control.editText : control.displayText @@ -65,46 +65,25 @@ T.ComboBox { cursorDelegate: CursorDelegate { } } - background: Rectangle { + background: MaterialTextContainer { implicitWidth: 120 - implicitHeight: control.Material.buttonHeight + implicitHeight: control.Material.textFieldHeight - radius: control.flat ? 0 : 2 - color: !control.editable ? control.Material.dialogColor : "transparent" - - layer.enabled: control.enabled && !control.editable && control.Material.background.a > 0 - layer.effect: ElevationEffect { - elevation: control.Material.elevation - } - - Rectangle { - visible: control.editable - y: parent.y + control.baselineOffset - width: parent.width - height: control.activeFocus ? 2 : 1 - color: control.editable && control.activeFocus ? control.Material.accentColor : control.Material.hintTextColor - } - - Ripple { - clip: control.flat - clipRadius: control.flat ? 0 : 2 - x: control.editable && control.indicator ? control.indicator.x : 0 - width: control.editable && control.indicator ? control.indicator.width : parent.width - height: parent.height - pressed: control.pressed - anchor: control.editable && control.indicator ? control.indicator : control - active: enabled && (control.pressed || control.visualFocus || control.hovered) - color: control.Material.rippleColor - } + outlineColor: (enabled && control.hovered) ? control.Material.primaryTextColor : control.Material.hintTextColor + focusedOutlineColor: control.Material.accentColor + controlHasActiveFocus: control.activeFocus + controlHasText: true + horizontalPadding: control.Material.textFieldHorizontalPadding } popup: T.Popup { y: control.editable ? control.height - 5 : 0 width: control.width - height: Math.min(contentItem.implicitHeight, control.Window.height - topMargin - bottomMargin) + height: Math.min(contentItem.implicitHeight + verticalPadding * 2, control.Window.height - topMargin - bottomMargin) transformOrigin: Item.Top topMargin: 12 bottomMargin: 12 + verticalPadding: 8 Material.theme: control.Material.theme Material.accent: control.Material.accent @@ -133,12 +112,13 @@ T.ComboBox { } background: Rectangle { - radius: 2 + radius: 4 color: parent.Material.dialogColor layer.enabled: control.enabled - layer.effect: ElevationEffect { - elevation: 8 + layer.effect: RoundedElevationEffect { + elevation: 4 + roundedScale: Material.ExtraSmallScale } } } diff --git a/src/quickcontrols/material/Dial.qml b/src/quickcontrols/material/Dial.qml index a44938fe20..465c1f3631 100644 --- a/src/quickcontrols/material/Dial.qml +++ b/src/quickcontrols/material/Dial.qml @@ -33,12 +33,13 @@ T.Dial { y: control.background.y + control.background.height / 2 - height / 2 transform: [ Translate { - y: -control.background.height * 0.4 + control.handle.height / 2 + y: -control.background.height * 0.4 + + (control.handle ? control.handle.height / 2 : 0) }, Rotation { angle: control.angle - origin.x: control.handle.width / 2 - origin.y: control.handle.height / 2 + origin.x: control.handle ? control.handle.width / 2 : 0 + origin.y: control.handle ? control.handle.height / 2 : 0 } ] implicitWidth: 10 diff --git a/src/quickcontrols/material/Dialog.qml b/src/quickcontrols/material/Dialog.qml index 33d8ce97d1..014fcc67c5 100644 --- a/src/quickcontrols/material/Dialog.qml +++ b/src/quickcontrols/material/Dialog.qml @@ -28,8 +28,7 @@ T.Dialog { // https://m3.material.io/components/dialogs/specs#401a48c3-f50c-4fa9-b798-701f5adcf155 // Specs say level 3 (6 dp) is the default, yet the screenshots there show 0. Native Android defaults to non-zero. Material.elevation: 6 - // https://m3.material.io/components/dialogs/specs#6771d107-624e-47cc-b6d8-2b7b620ba2f1 - Material.roundedScale: Material.ExtraLargeScale + Material.roundedScale: Material.dialogRoundedScale enter: Transition { // grow_fade_in @@ -63,7 +62,7 @@ T.Dialog { bottomPadding: 0 // TODO: QPlatformTheme::TitleBarFont // https://m3.material.io/components/dialogs/specs#401a48c3-f50c-4fa9-b798-701f5adcf155 - font.pixelSize: 24 + font.pixelSize: Material.dialogTitleFontPixelSize background: PaddedRectangle { radius: control.background.radius color: control.Material.dialogColor diff --git a/src/quickcontrols/material/Drawer.qml b/src/quickcontrols/material/Drawer.qml index ecfe8836b9..fc31a19a84 100644 --- a/src/quickcontrols/material/Drawer.qml +++ b/src/quickcontrols/material/Drawer.qml @@ -3,6 +3,7 @@ import QtQuick import QtQuick.Templates as T +import QtQuick.Controls.impl import QtQuick.Controls.Material import QtQuick.Controls.Material.impl @@ -16,33 +17,32 @@ T.Drawer { implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset, contentHeight + topPadding + bottomPadding) - topPadding: !dim && edge === Qt.BottomEdge && Material.elevation === 0 - leftPadding: !dim && edge === Qt.RightEdge && Material.elevation === 0 - rightPadding: !dim && edge === Qt.LeftEdge && Material.elevation === 0 - bottomPadding: !dim && edge === Qt.TopEdge && Material.elevation === 0 + topPadding: edge !== Qt.TopEdge ? Material.roundedScale : 0 + bottomPadding: edge !== Qt.BottomEdge ? Material.roundedScale : 0 enter: Transition { SmoothedAnimation { velocity: 5 } } exit: Transition { SmoothedAnimation { velocity: 5 } } - Material.elevation: !interactive && !dim ? 0 : 16 + // https://m3.material.io/components/navigation-drawer/specs#e616dc8f-d61a-4d56-a311-50c68ecda744 + Material.elevation: !interactive && !dim ? 0 : 1 + Material.roundedScale: Material.LargeScale - background: Rectangle { + background: PaddedRectangle { + // https://m3.material.io/components/navigation-drawer/specs#ce8bfbcf-3dec-45d2-9d8b-5e10af1cf87d + implicitWidth: 360 color: control.Material.dialogColor - - Rectangle { - readonly property bool horizontal: control.edge === Qt.LeftEdge || control.edge === Qt.RightEdge - width: horizontal ? 1 : parent.width - height: horizontal ? parent.height : 1 - color: control.Material.dividerColor - x: control.edge === Qt.LeftEdge ? parent.width - 1 : 0 - y: control.edge === Qt.TopEdge ? parent.height - 1 : 0 - visible: !control.dim && control.Material.elevation === 0 - } - - layer.enabled: control.position > 0 - layer.effect: ElevationEffect { + // FullScale doesn't make sense for Drawer. + radius: control.Material.roundedScale + leftPadding: edge === Qt.LeftEdge ? -radius : 0 + rightPadding: edge === Qt.RightEdge ? -radius : 0 + topPadding: edge === Qt.TopEdge ? -radius : 0 + bottomPadding: edge === Qt.BottomEdge ? -radius : 0 + clip: true + + layer.enabled: control.position > 0 && control.Material.elevation > 0 + layer.effect: RoundedElevationEffect { elevation: control.Material.elevation - fullHeight: true + roundedScale: control.background.radius } } diff --git a/src/quickcontrols/material/Frame.qml b/src/quickcontrols/material/Frame.qml index 8b6674e16f..da9cd65581 100644 --- a/src/quickcontrols/material/Frame.qml +++ b/src/quickcontrols/material/Frame.qml @@ -17,14 +17,17 @@ T.Frame { padding: 12 verticalPadding: Material.frameVerticalPadding + Material.roundedScale: Material.ExtraSmallScale + background: Rectangle { - radius: 2 + radius: control.Material.roundedScale color: control.Material.elevation > 0 ? control.Material.backgroundColor : "transparent" border.color: control.Material.frameColor layer.enabled: control.enabled && control.Material.elevation > 0 - layer.effect: ElevationEffect { + layer.effect: RoundedElevationEffect { elevation: control.Material.elevation + roundedScale: control.background.radius } } } diff --git a/src/quickcontrols/material/GroupBox.qml b/src/quickcontrols/material/GroupBox.qml index be7e6fbadc..f0cee7c854 100644 --- a/src/quickcontrols/material/GroupBox.qml +++ b/src/quickcontrols/material/GroupBox.qml @@ -20,8 +20,10 @@ T.GroupBox { topPadding: Material.frameVerticalPadding + (implicitLabelWidth > 0 ? implicitLabelHeight + spacing : 0) bottomPadding: Material.frameVerticalPadding + Material.roundedScale: Material.ExtraSmallScale + label: Text { - x: control.leftPadding + x: Math.max(control.leftPadding, control.Material.roundedScale) width: control.availableWidth text: control.title @@ -36,13 +38,14 @@ T.GroupBox { width: parent.width height: parent.height - control.topPadding + control.bottomPadding - radius: 2 + radius: control.Material.roundedScale color: control.Material.elevation > 0 ? control.Material.backgroundColor : "transparent" border.color: control.Material.frameColor layer.enabled: control.enabled && control.Material.elevation > 0 - layer.effect: ElevationEffect { + layer.effect: RoundedElevationEffect { elevation: control.Material.elevation + roundedScale: control.background.radius } } } diff --git a/src/quickcontrols/material/HorizontalHeaderView.qml b/src/quickcontrols/material/HorizontalHeaderView.qml index 0a5fe4f9f5..76060d8e83 100644 --- a/src/quickcontrols/material/HorizontalHeaderView.qml +++ b/src/quickcontrols/material/HorizontalHeaderView.qml @@ -1,6 +1,8 @@ // Copyright (C) 2020 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +pragma ComponentBehavior: Bound + import QtQuick import QtQuick.Templates as T import QtQuick.Controls.Material @@ -18,6 +20,10 @@ T.HorizontalHeaderView { implicitHeight: Math.max(1, contentHeight) delegate: Rectangle { + id: delegate + + required property var model + // Qt6: add cellPadding (and font etc) as public API in headerview readonly property real cellPadding: 8 @@ -27,11 +33,9 @@ T.HorizontalHeaderView { Label { id: text - text: control.textRole ? (Array.isArray(control.model) ? modelData[control.textRole] - : model[control.textRole]) - : modelData - width: parent.width - height: parent.height + text: delegate.model[control.textRole] + width: delegate.width + height: delegate.height horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter color: enabled ? control.Material.foreground : control.Material.hintTextColor diff --git a/src/quickcontrols/material/Menu.qml b/src/quickcontrols/material/Menu.qml index 4601772196..641bb7c07b 100644 --- a/src/quickcontrols/material/Menu.qml +++ b/src/quickcontrols/material/Menu.qml @@ -10,8 +10,6 @@ import QtQuick.Window T.Menu { id: control - Material.elevation: 8 - implicitWidth: Math.max(implicitBackgroundWidth + leftInset + rightInset, contentWidth + leftPadding + rightPadding) implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset, @@ -22,6 +20,9 @@ T.Menu { transformOrigin: !cascade ? Item.Top : (mirrored ? Item.TopRight : Item.TopLeft) + Material.elevation: 4 + Material.roundedScale: Material.ExtraSmallScale + delegate: MenuItem { } enter: Transition { @@ -41,8 +42,8 @@ T.Menu { model: control.contentModel interactive: Window.window - ? contentHeight + control.topPadding + control.bottomPadding > Window.window.height - : false + ? contentHeight + control.topPadding + control.bottomPadding > control.height + : false clip: true currentIndex: control.currentIndex @@ -52,13 +53,14 @@ T.Menu { background: Rectangle { implicitWidth: 200 implicitHeight: control.Material.menuItemHeight - - radius: 3 + // FullScale doesn't make sense for Menu. + radius: control.Material.roundedScale color: control.Material.dialogColor layer.enabled: control.Material.elevation > 0 - layer.effect: ElevationEffect { + layer.effect: RoundedElevationEffect { elevation: control.Material.elevation + roundedScale: control.background.radius } } diff --git a/src/quickcontrols/material/Page.qml b/src/quickcontrols/material/Page.qml index f8b97a2d3e..f8a1804f32 100644 --- a/src/quickcontrols/material/Page.qml +++ b/src/quickcontrols/material/Page.qml @@ -4,6 +4,7 @@ import QtQuick import QtQuick.Templates as T import QtQuick.Controls.Material +import QtQuick.Controls.Material.impl T.Page { id: control @@ -19,5 +20,10 @@ T.Page { background: Rectangle { color: control.Material.backgroundColor + + layer.enabled: control.enabled && control.Material.elevation > 0 + layer.effect: ElevationEffect { + elevation: control.Material.elevation + } } } diff --git a/src/quickcontrols/material/Pane.qml b/src/quickcontrols/material/Pane.qml index a6e0ae5b33..80385a073f 100644 --- a/src/quickcontrols/material/Pane.qml +++ b/src/quickcontrols/material/Pane.qml @@ -15,14 +15,16 @@ T.Pane { contentHeight + topPadding + bottomPadding) padding: 12 + Material.roundedScale: control.Material.elevation > 0 ? Material.ExtraSmallScale : Material.NotRounded background: Rectangle { color: control.Material.backgroundColor - radius: control.Material.elevation > 0 ? 2 : 0 + radius: control.Material.roundedScale layer.enabled: control.enabled && control.Material.elevation > 0 - layer.effect: ElevationEffect { + layer.effect: RoundedElevationEffect { elevation: control.Material.elevation + roundedScale: control.background.radius } } } diff --git a/src/quickcontrols/material/Popup.qml b/src/quickcontrols/material/Popup.qml index 7727ef0d3c..e443a1c2ff 100644 --- a/src/quickcontrols/material/Popup.qml +++ b/src/quickcontrols/material/Popup.qml @@ -9,8 +9,6 @@ import QtQuick.Controls.Material.impl T.Popup { id: control - Material.elevation: 24 - implicitWidth: Math.max(implicitBackgroundWidth + leftInset + rightInset, contentWidth + leftPadding + rightPadding) implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset, @@ -18,6 +16,9 @@ T.Popup { padding: 12 + Material.elevation: 4 + Material.roundedScale: Material.ExtraSmallScale + enter: Transition { // grow_fade_in NumberAnimation { property: "scale"; from: 0.9; to: 1.0; easing.type: Easing.OutQuint; duration: 220 } @@ -31,12 +32,14 @@ T.Popup { } background: Rectangle { - radius: 2 + // FullScale doesn't make sense for Popup. + radius: control.Material.roundedScale color: control.Material.dialogColor layer.enabled: control.Material.elevation > 0 - layer.effect: ElevationEffect { + layer.effect: RoundedElevationEffect { elevation: control.Material.elevation + roundedScale: control.Material.roundedScale } } diff --git a/src/quickcontrols/material/RangeSlider.qml b/src/quickcontrols/material/RangeSlider.qml index 81507f334d..7547d3d04f 100644 --- a/src/quickcontrols/material/RangeSlider.qml +++ b/src/quickcontrols/material/RangeSlider.qml @@ -1,4 +1,4 @@ -// Copyright (C) 2017 The Qt Company Ltd. +// Copyright (C) 2022 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only import QtQuick @@ -19,6 +19,17 @@ T.RangeSlider { padding: 6 + // The RangeSlider is discrete if all of the following requirements are met: + // * stepSize is positive + // * snapMode is set to SnapAlways + // * the difference between to and from is cleanly divisible by the stepSize + // * the number of tick marks intended to be rendered is less than the width to height ratio, or vice versa for vertical sliders. + readonly property real __steps: Math.abs(to - from) / stepSize + readonly property bool __isDiscrete: stepSize >= Number.EPSILON + && snapMode === Slider.SnapAlways + && Math.abs(Math.round(__steps) - __steps) < Number.EPSILON + && Math.floor(__steps) < (horizontal ? background.width / background.height : background.height / background.width) + first.handle: SliderHandle { x: control.leftPadding + (control.horizontal ? control.first.visualPosition * (control.availableWidth - width) : (control.availableWidth - width) / 2) y: control.topPadding + (control.horizontal ? (control.availableHeight - height) / 2 : control.first.visualPosition * (control.availableHeight - height)) @@ -37,23 +48,51 @@ T.RangeSlider { handleHovered: control.second.hovered } - background: Rectangle { + background: Item { x: control.leftPadding + (control.horizontal ? 0 : (control.availableWidth - width) / 2) y: control.topPadding + (control.horizontal ? (control.availableHeight - height) / 2 : 0) implicitWidth: control.horizontal ? 200 : 48 implicitHeight: control.horizontal ? 48 : 200 width: control.horizontal ? control.availableWidth : 4 height: control.horizontal ? 4 : control.availableHeight - scale: control.horizontal && control.mirrored ? -1 : 1 - color: control.enabled ? Color.transparent(control.Material.accentColor, 0.33) : control.Material.sliderDisabledColor Rectangle { - x: control.horizontal ? control.first.position * parent.width : 0 - y: control.horizontal ? 0 : control.second.visualPosition * parent.height - width: control.horizontal ? control.second.position * parent.width - control.first.position * parent.width : 4 - height: control.horizontal ? 4 : control.second.position * parent.height - control.first.position * parent.height + x: (control.horizontal ? (control.first.implicitHandleWidth / 2) - (control.__isDiscrete ? 2 : 0) : 0) + y: (control.horizontal ? 0 : (control.first.implicitHandleHeight / 2) - (control.__isDiscrete ? 2 : 0)) + width: parent.width - (control.horizontal ? (control.first.implicitHandleWidth - (control.__isDiscrete ? 4 : 0)) : 0) + height: parent.height - (control.horizontal ? 0 : (control.first.implicitHandleHeight - (control.__isDiscrete ? 4 : 0))) + scale: control.horizontal && control.mirrored ? -1 : 1 + radius: Math.min(width, height) / 2 + color: control.enabled ? Color.transparent(control.Material.accentColor, 0.33) : control.Material.sliderDisabledColor + + Rectangle { + x: control.horizontal ? control.first.position * parent.width : 0 + y: control.horizontal ? 0 : control.second.visualPosition * parent.height + width: control.horizontal ? control.second.position * parent.width - control.first.position * parent.width : 4 + height: control.horizontal ? 4 : control.second.position * parent.height - control.first.position * parent.height + radius: Math.min(width, height) / 2 + color: control.enabled ? control.Material.accentColor : control.Material.sliderDisabledColor + } + + // Declaring this as a property (in combination with the parent binding below) avoids ids, + // which prevent deferred execution. + property Repeater repeater: Repeater { + parent: control.background.children[0] + model: control.__isDiscrete ? Math.floor(control.__steps) + 1 : 0 + delegate: Rectangle { + width: 2 + height: 2 + radius: 2 + x: control.horizontal ? (parent.width - width * 2) * currentPosition + (width / 2) : (parent.width - width) / 2 + y: control.horizontal ? (parent.height - height) / 2 : (parent.height - height * 2) * currentPosition + (height / 2) + color: (control.horizontal && control.first.visualPosition < currentPosition && control.second.visualPosition > currentPosition) + || (!control.horizontal && control.first.visualPosition > currentPosition && control.second.visualPosition < currentPosition) + ? control.Material.primaryHighlightedTextColor : control.Material.accentColor - color: control.enabled ? control.Material.accentColor : control.Material.sliderDisabledColor + required property int index + readonly property real currentPosition: index / (parent.repeater.count - 1) + } + } } } } diff --git a/src/quickcontrols/material/Slider.qml b/src/quickcontrols/material/Slider.qml index 5061ecf03f..817b3022aa 100644 --- a/src/quickcontrols/material/Slider.qml +++ b/src/quickcontrols/material/Slider.qml @@ -1,4 +1,4 @@ -// Copyright (C) 2017 The Qt Company Ltd. +// Copyright (C) 2022 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only import QtQuick @@ -17,6 +17,17 @@ T.Slider { padding: 6 + // The Slider is discrete if all of the following requirements are met: + // * stepSize is positive + // * snapMode is set to SnapAlways + // * the difference between to and from is cleanly divisible by the stepSize + // * the number of tick marks intended to be rendered is less than the width to height ratio, or vice versa for vertical sliders. + readonly property real __steps: Math.abs(to - from) / stepSize + readonly property bool __isDiscrete: stepSize >= Number.EPSILON + && snapMode === Slider.SnapAlways + && Math.abs(Math.round(__steps) - __steps) < Number.EPSILON + && Math.floor(__steps) < (horizontal ? background.width / background.height : background.height / background.width) + handle: SliderHandle { x: control.leftPadding + (control.horizontal ? control.visualPosition * (control.availableWidth - width) : (control.availableWidth - width) / 2) y: control.topPadding + (control.horizontal ? (control.availableHeight - height) / 2 : control.visualPosition * (control.availableHeight - height)) @@ -26,23 +37,51 @@ T.Slider { handleHovered: control.hovered } - background: Rectangle { + background: Item { x: control.leftPadding + (control.horizontal ? 0 : (control.availableWidth - width) / 2) y: control.topPadding + (control.horizontal ? (control.availableHeight - height) / 2 : 0) implicitWidth: control.horizontal ? 200 : 48 implicitHeight: control.horizontal ? 48 : 200 width: control.horizontal ? control.availableWidth : 4 height: control.horizontal ? 4 : control.availableHeight - scale: control.horizontal && control.mirrored ? -1 : 1 - color: control.enabled ? Color.transparent(control.Material.accentColor, 0.33) : control.Material.sliderDisabledColor Rectangle { - x: control.horizontal ? 0 : (parent.width - width) / 2 - y: control.horizontal ? (parent.height - height) / 2 : control.visualPosition * parent.height - width: control.horizontal ? control.position * parent.width : 4 - height: control.horizontal ? 4 : control.position * parent.height + x: (control.horizontal ? (control.implicitHandleWidth / 2) - (control.__isDiscrete ? 2 : 0) : 0) + y: (control.horizontal ? 0 : (control.implicitHandleHeight / 2) - (control.__isDiscrete ? 2 : 0)) + width: parent.width - (control.horizontal ? (control.implicitHandleWidth - (control.__isDiscrete ? 4 : 0)) : 0) + height: parent.height - (control.horizontal ? 0 : (control.implicitHandleHeight - (control.__isDiscrete ? 4 : 0))) + scale: control.horizontal && control.mirrored ? -1 : 1 + radius: Math.min(width, height) / 2 + color: control.enabled ? Color.transparent(control.Material.accentColor, 0.33) : control.Material.sliderDisabledColor + + Rectangle { + x: control.horizontal ? 0 : (parent.width - width) / 2 + y: control.horizontal ? (parent.height - height) / 2 : control.visualPosition * parent.height + width: control.horizontal ? control.position * parent.width : 4 + height: control.horizontal ? 4 : control.position * parent.height + radius: Math.min(width, height) / 2 + color: control.enabled ? control.Material.accentColor : control.Material.sliderDisabledColor + } + + // Declaring this as a property (in combination with the parent binding below) avoids ids, + // which prevent deferred execution. + property Repeater repeater: Repeater { + parent: control.background.children[0] + model: control.__isDiscrete ? Math.floor(control.__steps) + 1 : 0 + delegate: Rectangle { + width: 2 + height: 2 + radius: 2 + x: control.horizontal ? (parent.width - width * 2) * currentPosition + (width / 2) : (parent.width - width) / 2 + y: control.horizontal ? (parent.height - height) / 2 : (parent.height - height * 2) * currentPosition + (height / 2) + color: (control.horizontal && control.visualPosition > currentPosition) + || (!control.horizontal && control.visualPosition <= currentPosition) + ? control.Material.primaryHighlightedTextColor : control.Material.accentColor - color: control.enabled ? control.Material.accentColor : control.Material.sliderDisabledColor + required property int index + readonly property real currentPosition: index / (parent.repeater.count - 1) + } + } } } } diff --git a/src/quickcontrols/material/SpinBox.qml b/src/quickcontrols/material/SpinBox.qml index 591f5061fa..44fd59f17f 100644 --- a/src/quickcontrols/material/SpinBox.qml +++ b/src/quickcontrols/material/SpinBox.qml @@ -17,8 +17,8 @@ T.SpinBox { up.implicitIndicatorHeight, down.implicitIndicatorHeight) spacing: 6 - topPadding: 8 - bottomPadding: 16 + topPadding: Material.textFieldVerticalPadding + bottomPadding: Material.textFieldVerticalPadding leftPadding: control.mirrored ? (up.indicator ? up.indicator.width : 0) : (down.indicator ? down.indicator.width : 0) rightPadding: control.mirrored ? (down.indicator ? down.indicator.width : 0) : (up.indicator ? up.indicator.width : 0) @@ -107,16 +107,14 @@ T.SpinBox { } } - background: Item { - implicitWidth: 192 - implicitHeight: control.Material.touchTarget + background: MaterialTextContainer { + implicitWidth: 140 + implicitHeight: control.Material.textFieldHeight - Rectangle { - x: parent.width / 2 - width / 2 - y: parent.y + parent.height - height - control.bottomPadding / 2 - width: control.availableWidth - height: control.activeFocus ? 2 : 1 - color: control.activeFocus ? control.Material.accentColor : control.Material.hintTextColor - } + outlineColor: (enabled && control.hovered) ? control.Material.primaryTextColor : control.Material.hintTextColor + focusedOutlineColor: control.Material.accentColor + controlHasActiveFocus: control.activeFocus + controlHasText: true + horizontalPadding: control.Material.textFieldHorizontalPadding } } diff --git a/src/quickcontrols/material/StackView.qml b/src/quickcontrols/material/StackView.qml index 241b91f2d7..c3182442a2 100644 --- a/src/quickcontrols/material/StackView.qml +++ b/src/quickcontrols/material/StackView.qml @@ -8,39 +8,56 @@ import QtQuick.Controls.Material T.StackView { id: control + component LineAnimation: NumberAnimation { + duration: 200 + easing.type: Easing.OutCubic + } + + component FadeIn: LineAnimation { + property: "opacity" + from: 0.0 + to: 1.0 + } + + component FadeOut: LineAnimation { + property: "opacity" + from: 1.0 + to: 0.0 + } + popEnter: Transition { // slide_in_left - NumberAnimation { property: "x"; from: (control.mirrored ? -0.5 : 0.5) * -control.width; to: 0; duration: 200; easing.type: Easing.OutCubic } - NumberAnimation { property: "opacity"; from: 0.0; to: 1.0; duration: 200; easing.type: Easing.OutCubic } + LineAnimation { property: "x"; from: (control.mirrored ? -0.5 : 0.5) * -control.width; to: 0 } + FadeIn {} } popExit: Transition { // slide_out_right - NumberAnimation { property: "x"; from: 0; to: (control.mirrored ? -0.5 : 0.5) * control.width; duration: 200; easing.type: Easing.OutCubic } - NumberAnimation { property: "opacity"; from: 1.0; to: 0.0; duration: 200; easing.type: Easing.OutCubic } + LineAnimation { property: "x"; from: 0; to: (control.mirrored ? -0.5 : 0.5) * control.width } + FadeOut {} } pushEnter: Transition { // slide_in_right - NumberAnimation { property: "x"; from: (control.mirrored ? -0.5 : 0.5) * control.width; to: 0; duration: 200; easing.type: Easing.OutCubic } - NumberAnimation { property: "opacity"; from: 0.0; to: 1.0; duration: 200; easing.type: Easing.OutCubic } + LineAnimation { property: "x"; from: (control.mirrored ? -0.5 : 0.5) * control.width; to: 0 } + FadeIn {} } pushExit: Transition { // slide_out_left - NumberAnimation { property: "x"; from: 0; to: (control.mirrored ? -0.5 : 0.5) * -control.width; duration: 200; easing.type: Easing.OutCubic } - NumberAnimation { property: "opacity"; from: 1.0; to: 0.0; duration: 200; easing.type: Easing.OutCubic } + LineAnimation { property: "x"; from: 0; to: (control.mirrored ? -0.5 : 0.5) * -control.width } + FadeOut {} } replaceEnter: Transition { // slide_in_right - NumberAnimation { property: "x"; from: (control.mirrored ? -0.5 : 0.5) * control.width; to: 0; duration: 200; easing.type: Easing.OutCubic } - NumberAnimation { property: "opacity"; from: 0.0; to: 1.0; duration: 200; easing.type: Easing.OutCubic } + LineAnimation { property: "x"; from: (control.mirrored ? -0.5 : 0.5) * control.width; to: 0 } + FadeIn {} } replaceExit: Transition { // slide_out_left - NumberAnimation { property: "x"; from: 0; to: (control.mirrored ? -0.5 : 0.5) * -control.width; duration: 200; easing.type: Easing.OutCubic } - NumberAnimation { property: "opacity"; from: 1.0; to: 0.0; duration: 200; easing.type: Easing.OutCubic } + LineAnimation { property: "x"; from: 0; to: (control.mirrored ? -0.5 : 0.5) * -control.width } + FadeOut {} } } diff --git a/src/quickcontrols/material/Switch.qml b/src/quickcontrols/material/Switch.qml index d27621a74f..29a1297684 100644 --- a/src/quickcontrols/material/Switch.qml +++ b/src/quickcontrols/material/Switch.qml @@ -34,7 +34,8 @@ T.Switch { Ripple { x: parent.handle.x + parent.handle.width / 2 - width / 2 y: parent.handle.y + parent.handle.height / 2 - height / 2 - width: 28; height: 28 + width: 28 + height: 28 pressed: control.pressed active: enabled && (control.down || control.visualFocus || control.hovered) color: control.checked ? control.Material.highlightedRippleColor : control.Material.rippleColor diff --git a/src/quickcontrols/material/TextArea.qml b/src/quickcontrols/material/TextArea.qml index 242cbf691b..99efa222cf 100644 --- a/src/quickcontrols/material/TextArea.qml +++ b/src/quickcontrols/material/TextArea.qml @@ -14,37 +14,70 @@ T.TextArea { implicitBackgroundWidth + leftInset + rightInset, placeholder.implicitWidth + leftPadding + rightPadding) implicitHeight: Math.max(contentHeight + topPadding + bottomPadding, - implicitBackgroundHeight + topInset + bottomInset, - placeholder.implicitHeight + 1 + topPadding + bottomPadding) + implicitBackgroundHeight + topInset + bottomInset) - topPadding: 8 - bottomPadding: 16 + // If we're clipped, or we're in a Flickable that's clipped, set our topInset + // to half the height of the placeholder text to avoid it being clipped. + topInset: clip || (parent?.parent as Flickable && parent?.parent.clip) ? placeholder.largestHeight / 2 : 0 + + leftPadding: Material.textFieldHorizontalPadding + rightPadding: Material.textFieldHorizontalPadding + // Need to account for the placeholder text when it's sitting on top. + topPadding: Material.containerStyle === Material.Filled && placeholderText.length > 0 && (activeFocus || length > 0) + ? Material.textFieldVerticalPadding + placeholder.largestHeight + // When the condition above is not met, the text should always sit in the middle + // of a default-height TextArea, which is just near the top for a higher-than-default one. + // Account for any topInset as well, otherwise the text will be too close to the background. + : ((implicitBackgroundHeight - placeholder.largestHeight) / 2) + topInset + bottomPadding: Material.textFieldVerticalPadding color: enabled ? Material.foreground : Material.hintTextColor selectionColor: Material.accentColor selectedTextColor: Material.primaryHighlightedTextColor - placeholderTextColor: Material.hintTextColor + placeholderTextColor: enabled && activeFocus ? Material.accentColor : Material.hintTextColor + + Material.containerStyle: Material.Outlined + cursorDelegate: CursorDelegate { } - PlaceholderText { + FloatingPlaceholderText { id: placeholder - x: control.leftPadding - y: control.topPadding + // Don't set this to control.leftPadding, because we don't want it to change if the user changes leftPadding. + x: control.Material.textFieldHorizontalPadding width: control.width - (control.leftPadding + control.rightPadding) - height: control.height - (control.topPadding + control.bottomPadding) text: control.placeholderText font: control.font color: control.placeholderTextColor - verticalAlignment: control.verticalAlignment elide: Text.ElideRight renderType: control.renderType - visible: !control.length && !control.preeditText && (!control.activeFocus || control.horizontalAlignment !== Qt.AlignHCenter) + // When the TextArea is in a Flickable, the background is reparented to it + // so that decorations don't move with the content. We need to do the same. + // Also allow the background to be set to null; in that case we're just not visible. + parent: control.background?.parent ?? null + + filled: control.Material.containerStyle === Material.Filled + verticalPadding: control.Material.textFieldVerticalPadding + controlHasActiveFocus: control.activeFocus + controlHasText: control.length > 0 + controlImplicitBackgroundHeight: control.implicitBackgroundHeight + controlHeight: control.height } - background: Rectangle { - y: parent.height - height - control.bottomPadding / 2 + background: MaterialTextContainer { implicitWidth: 120 - height: control.activeFocus ? 2 : 1 - color: control.activeFocus ? control.Material.accentColor : control.Material.hintTextColor + implicitHeight: control.Material.textFieldHeight + + filled: control.Material.containerStyle === Material.Filled + fillColor: control.Material.textFieldFilledContainerColor + outlineColor: (enabled && control.hovered) ? control.Material.primaryTextColor : control.Material.hintTextColor + focusedOutlineColor: control.Material.accentColor + // 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 + horizontalPadding: control.Material.textFieldHorizontalPadding } } diff --git a/src/quickcontrols/material/TextField.qml b/src/quickcontrols/material/TextField.qml index 598f66f938..9294146fac 100644 --- a/src/quickcontrols/material/TextField.qml +++ b/src/quickcontrols/material/TextField.qml @@ -13,40 +13,67 @@ T.TextField { implicitWidth: implicitBackgroundWidth + leftInset + rightInset || Math.max(contentWidth, placeholder.implicitWidth) + leftPadding + rightPadding implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset, - contentHeight + topPadding + bottomPadding, - placeholder.implicitHeight + topPadding + bottomPadding) + contentHeight + topPadding + bottomPadding) - topPadding: 8 - bottomPadding: 16 + // If we're clipped, set topInset to half the height of the placeholder text to avoid it being clipped. + topInset: clip ? placeholder.largestHeight / 2 : 0 + + leftPadding: Material.textFieldHorizontalPadding + rightPadding: Material.textFieldHorizontalPadding + // Need to account for the placeholder text when it's sitting on top. + topPadding: Material.containerStyle === Material.Filled + ? placeholderText.length > 0 && (activeFocus || length > 0) + ? Material.textFieldVerticalPadding + placeholder.largestHeight + : Material.textFieldVerticalPadding + // Account for any topInset (used to avoid floating placeholder text being clipped), + // otherwise the text will be too close to the background. + : Material.textFieldVerticalPadding + topInset + bottomPadding: Material.textFieldVerticalPadding color: enabled ? Material.foreground : Material.hintTextColor selectionColor: Material.accentColor selectedTextColor: Material.primaryHighlightedTextColor - placeholderTextColor: Material.hintTextColor + placeholderTextColor: enabled && activeFocus ? Material.accentColor : Material.hintTextColor verticalAlignment: TextInput.AlignVCenter + Material.containerStyle: Material.Outlined + cursorDelegate: CursorDelegate { } - PlaceholderText { + FloatingPlaceholderText { id: placeholder - x: control.leftPadding - y: control.topPadding + // Don't set this to control.leftPadding, because we don't want it to change if the user changes leftPadding. + x: control.Material.textFieldHorizontalPadding width: control.width - (control.leftPadding + control.rightPadding) - height: control.height - (control.topPadding + control.bottomPadding) text: control.placeholderText font: control.font color: control.placeholderTextColor - verticalAlignment: control.verticalAlignment elide: Text.ElideRight renderType: control.renderType - visible: !control.length && !control.preeditText && (!control.activeFocus || control.horizontalAlignment !== Qt.AlignHCenter) + + filled: control.Material.containerStyle === Material.Filled + verticalPadding: control.Material.textFieldVerticalPadding + controlHasActiveFocus: control.activeFocus + controlHasText: control.length > 0 + controlImplicitBackgroundHeight: control.implicitBackgroundHeight + controlHeight: control.height } - background: Rectangle { - y: control.height - height - control.bottomPadding + 8 + background: MaterialTextContainer { implicitWidth: 120 - height: control.activeFocus || (enabled && control.hovered) ? 2 : 1 - color: control.activeFocus ? control.Material.accentColor - : ((enabled && control.hovered) ? control.Material.primaryTextColor : control.Material.hintTextColor) + implicitHeight: control.Material.textFieldHeight + + filled: control.Material.containerStyle === Material.Filled + fillColor: control.Material.textFieldFilledContainerColor + outlineColor: (enabled && control.hovered) ? control.Material.primaryTextColor : control.Material.hintTextColor + focusedOutlineColor: control.Material.accentColor + // 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 + horizontalPadding: control.Material.textFieldHorizontalPadding } } diff --git a/src/quickcontrols/material/TreeViewDelegate.qml b/src/quickcontrols/material/TreeViewDelegate.qml index 1a6e35dadc..9f1d444383 100644 --- a/src/quickcontrols/material/TreeViewDelegate.qml +++ b/src/quickcontrols/material/TreeViewDelegate.qml @@ -64,6 +64,7 @@ T.TreeViewDelegate { // The edit delegate is a separate component, and doesn't need // to follow the same strict rules that are applied to a control. // qmllint disable attached-property-reuse + // qmllint disable controls-attached-property-reuse // qmllint disable controls-sanity TableView.editDelegate: FocusScope { width: parent.width @@ -71,7 +72,7 @@ T.TreeViewDelegate { readonly property int __role: { let model = control.treeView.model - let index = control.treeView.modelIndex(column, row) + let index = control.treeView.index(row, column) let editText = model.data(index, Qt.EditRole) return editText !== undefined ? Qt.EditRole : Qt.DisplayRole } @@ -81,17 +82,18 @@ T.TreeViewDelegate { x: control.contentItem.x y: (parent.height - height) / 2 width: control.contentItem.width - text: control.treeView.model.data(control.treeView.modelIndex(column, row), __role) + text: control.treeView.model.data(control.treeView.index(row, column), __role) focus: true } TableView.onCommit: { - let index = TableView.view.modelIndex(column, row) + let index = TableView.view.index(row, column) TableView.view.model.setData(index, textField.text, __role) } Component.onCompleted: textField.selectAll() } // qmllint enable attached-property-reuse + // qmllint enable controls-attached-property-reuse // qmllint enable controls-sanity } diff --git a/src/quickcontrols/material/Tumbler.qml b/src/quickcontrols/material/Tumbler.qml index 59320cf52b..48d0c2e739 100644 --- a/src/quickcontrols/material/Tumbler.qml +++ b/src/quickcontrols/material/Tumbler.qml @@ -14,6 +14,8 @@ T.Tumbler { implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset, implicitContentHeight + topPadding + bottomPadding) + readonly property real __delegateHeight: availableHeight / visibleItemCount + delegate: Text { text: modelData color: control.Material.foreground @@ -33,13 +35,11 @@ T.Tumbler { delegate: control.delegate path: Path { startX: control.contentItem.width / 2 - startY: -control.contentItem.delegateHeight / 2 + startY: -control.__delegateHeight / 2 PathLine { x: control.contentItem.width / 2 - y: (control.visibleItemCount + 1) * control.contentItem.delegateHeight - control.contentItem.delegateHeight / 2 + y: (control.visibleItemCount + 1) * control.__delegateHeight - control.__delegateHeight / 2 } } - - property real delegateHeight: control.availableHeight / control.visibleItemCount } } diff --git a/src/quickcontrols/material/VerticalHeaderView.qml b/src/quickcontrols/material/VerticalHeaderView.qml index 6b4d9e658b..0646f6135d 100644 --- a/src/quickcontrols/material/VerticalHeaderView.qml +++ b/src/quickcontrols/material/VerticalHeaderView.qml @@ -1,6 +1,8 @@ // Copyright (C) 2020 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +pragma ComponentBehavior: Bound + import QtQuick import QtQuick.Templates as T import QtQuick.Controls.Material @@ -18,6 +20,10 @@ T.VerticalHeaderView { implicitHeight: syncView ? syncView.height : 0 delegate: Rectangle { + id: delegate + + required property var model + // Qt6: add cellPadding (and font etc) as public API in headerview readonly property real cellPadding: 8 @@ -27,11 +33,9 @@ T.VerticalHeaderView { Label { id: text - text: control.textRole ? (Array.isArray(control.model) ? modelData[control.textRole] - : model[control.textRole]) - : modelData - width: parent.width - height: parent.height + text: delegate.model[control.textRole] + width: delegate.width + height: delegate.height horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter color: enabled ? control.Material.foreground : control.Material.hintTextColor diff --git a/src/quickcontrols/material/impl/CMakeLists.txt b/src/quickcontrols/material/impl/CMakeLists.txt index ebf3e1cbf3..774655c2ff 100644 --- a/src/quickcontrols/material/impl/CMakeLists.txt +++ b/src/quickcontrols/material/impl/CMakeLists.txt @@ -17,19 +17,21 @@ set(qml_files "SwitchIndicator.qml" ) -qt_internal_add_qml_module(qtquickcontrols2materialstyleimplplugin +qt_internal_add_qml_module(QuickControls2MaterialStyleImpl URI "QtQuick.Controls.Material.impl" VERSION "${PROJECT_VERSION}" PAST_MAJOR_VERSIONS 2 CLASS_NAME QtQuickControls2MaterialStyleImplPlugin DEPENDENCIES QtQuick/auto + QtQuick.Controls.impl/auto PLUGIN_TARGET qtquickcontrols2materialstyleimplplugin - NO_PLUGIN_OPTIONAL SOURCES qquickmaterialbusyindicator.cpp qquickmaterialbusyindicator_p.h + qquickmaterialplaceholdertext.cpp qquickmaterialplaceholdertext_p.h qquickmaterialprogressbar.cpp qquickmaterialprogressbar_p.h qquickmaterialripple.cpp qquickmaterialripple_p.h + qquickmaterialtextcontainer.cpp qquickmaterialtextcontainer_p.h QML_FILES ${qml_files} DEFINES @@ -38,8 +40,10 @@ qt_internal_add_qml_module(qtquickcontrols2materialstyleimplplugin LIBRARIES Qt::CorePrivate Qt::Gui + Qt::Qml Qt::QmlPrivate Qt::QuickControls2ImplPrivate + Qt::Quick Qt::QuickPrivate Qt::QuickTemplates2Private ) diff --git a/src/quickcontrols/material/impl/RadioIndicator.qml b/src/quickcontrols/material/impl/RadioIndicator.qml index 9570065692..2cf46a69cb 100644 --- a/src/quickcontrols/material/impl/RadioIndicator.qml +++ b/src/quickcontrols/material/impl/RadioIndicator.qml @@ -12,19 +12,42 @@ Rectangle { implicitHeight: 20 radius: width / 2 border.width: 2 - border.color: !control.enabled ? control.Material.hintTextColor - : control.checked || control.down ? control.Material.accentColor : control.Material.secondaryTextColor + border.color: targetColor color: "transparent" + // Store the target color in a separate property, because there are two animations that depend on it. + readonly property color targetColor: !control.enabled ? control.Material.hintTextColor + : control.checked || control.down ? control.Material.accentColor : control.Material.secondaryTextColor + property T.AbstractButton control + Behavior on border.color { + ColorAnimation { + duration: 100 + easing.type: Easing.OutCubic + } + } + Rectangle { x: (parent.width - width) / 2 y: (parent.height - height) / 2 width: 10 height: 10 radius: width / 2 - color: parent.border.color - visible: indicator.control.checked || indicator.control.down + color: indicator.targetColor + scale: indicator.control.checked || indicator.control.down ? 1 : 0 + + Behavior on color { + ColorAnimation { + duration: 100 + easing.type: Easing.OutCubic + } + } + + Behavior on scale { + NumberAnimation { + duration: 100 + } + } } } diff --git a/src/quickcontrols/material/impl/RectangularGlow.qml b/src/quickcontrols/material/impl/RectangularGlow.qml index b604049cf9..5fc649c393 100644 --- a/src/quickcontrols/material/impl/RectangularGlow.qml +++ b/src/quickcontrols/material/impl/RectangularGlow.qml @@ -202,6 +202,6 @@ Item { property real spread: rootItem.spread / 2.0 property real cornerRadius: clampedCornerRadius() - fragmentShader: "qrc:/qt-project.org/imports/QtQuick/Controls/Material/shaders/RectangularGlow.frag" + fragmentShader: "qrc:/qt-project.org/imports/QtQuick/Controls/Material/shaders/RectangularGlow.frag.qsb" } } diff --git a/src/quickcontrols/material/impl/SliderHandle.qml b/src/quickcontrols/material/impl/SliderHandle.qml index ca7912b91d..4681992ab0 100644 --- a/src/quickcontrols/material/impl/SliderHandle.qml +++ b/src/quickcontrols/material/impl/SliderHandle.qml @@ -1,4 +1,4 @@ -// Copyright (C) 2017 The Qt Company Ltd. +// Copyright (C) 2023 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only import QtQuick @@ -22,14 +22,9 @@ Item { width: parent.width height: parent.height radius: width / 2 - scale: root.handlePressed ? 1.5 : 1 - color: root.control.enabled ? root.control.Material.accentColor : root.control.Material.sliderDisabledColor - - Behavior on scale { - NumberAnimation { - duration: 250 - } - } + color: root.control + ? root.control.enabled ? root.control.Material.accentColor : root.control.Material.sliderDisabledColor + : "transparent" } Ripple { @@ -38,6 +33,6 @@ Item { width: 22; height: 22 pressed: root.handlePressed active: root.handlePressed || root.handleHasFocus || (enabled && root.handleHovered) - color: root.control.Material.highlightedRippleColor + color: root.control ? root.control.Material.highlightedRippleColor : "transparent" } } diff --git a/src/quickcontrols/material/impl/SwitchIndicator.qml b/src/quickcontrols/material/impl/SwitchIndicator.qml index 2d48302e01..d864f3887a 100644 --- a/src/quickcontrols/material/impl/SwitchIndicator.qml +++ b/src/quickcontrols/material/impl/SwitchIndicator.qml @@ -9,8 +9,8 @@ import QtQuick.Controls.Material.impl Rectangle { id: indicator - width: 52 - height: 32 + width: control.Material.switchIndicatorWidth + height: control.Material.switchIndicatorHeight radius: height / 2 y: parent.height / 2 - height / 2 color: control.enabled @@ -62,9 +62,9 @@ Rectangle { scale: indicator.control.down ? 1 : (indicator.control.checked ? checkedSize / largestSize : normalSize / largestSize) readonly property int offset: 2 - readonly property real normalSize: !hasIcon ? 16 : checkedSize - readonly property real checkedSize: 24 - readonly property real largestSize: 28 + readonly property real normalSize: !hasIcon ? indicator.control.Material.switchNormalHandleHeight : checkedSize + readonly property real checkedSize: indicator.control.Material.switchCheckedHandleHeight + readonly property real largestSize: indicator.control.Material.switchLargestHandleHeight readonly property real largestScale: largestSize / normalSize readonly property bool hasIcon: indicator.control.icon.name.length > 0 || indicator.control.icon.source.toString().length > 0 diff --git a/src/quickcontrols/material/impl/qquickmaterialbusyindicator_p.h b/src/quickcontrols/material/impl/qquickmaterialbusyindicator_p.h index b6bbd925c7..c18257127d 100644 --- a/src/quickcontrols/material/impl/qquickmaterialbusyindicator_p.h +++ b/src/quickcontrols/material/impl/qquickmaterialbusyindicator_p.h @@ -51,6 +51,4 @@ private: QT_END_NAMESPACE -QML_DECLARE_TYPE(QQuickMaterialBusyIndicator) - #endif // QQUICKMATERIALBUSYINDICATOR_P_H diff --git a/src/quickcontrols/material/impl/qquickmaterialplaceholdertext.cpp b/src/quickcontrols/material/impl/qquickmaterialplaceholdertext.cpp new file mode 100644 index 0000000000..2447193d6a --- /dev/null +++ b/src/quickcontrols/material/impl/qquickmaterialplaceholdertext.cpp @@ -0,0 +1,321 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "qquickmaterialplaceholdertext_p.h" + +#include <QtCore/qpropertyanimation.h> +#include <QtCore/qparallelanimationgroup.h> +#include <QtGui/qpainter.h> +#include <QtGui/qpainterpath.h> +#include <QtQml/qqmlinfo.h> +#include <QtQuickTemplates2/private/qquicktheme_p.h> +#include <QtQuickTemplates2/private/qquicktextarea_p.h> +#include <QtQuickTemplates2/private/qquicktextfield_p.h> + +QT_BEGIN_NAMESPACE + +static const qreal floatingScale = 0.8; +Q_GLOBAL_STATIC(QEasingCurve, animationEasingCurve, QEasingCurve::OutSine); + +/* + This class makes it easier to animate the various placeholder text changes + for each type of text container (filled, outlined). + + By doing animations in C++, we avoid having a bunch of states, transitions, + and animations (which are all QObjects) declared in QML, even if that text + control never gets focus and hence never needs them. +*/ + +QQuickMaterialPlaceholderText::QQuickMaterialPlaceholderText(QQuickItem *parent) + : QQuickPlaceholderText(parent) +{ + connect(this, &QQuickMaterialPlaceholderText::effectiveHorizontalAlignmentChanged, + this, &QQuickMaterialPlaceholderText::adjustTransformOrigin); +} + +bool QQuickMaterialPlaceholderText::isFilled() const +{ + return m_filled; +} + +void QQuickMaterialPlaceholderText::setFilled(bool filled) +{ + if (filled == m_filled) + return; + + m_filled = filled; + update(); + void filledChanged(); +} + +bool QQuickMaterialPlaceholderText::controlHasActiveFocus() const +{ + return m_controlHasActiveFocus; +} + +void QQuickMaterialPlaceholderText::setControlHasActiveFocus(bool controlHasActiveFocus) +{ + if (m_controlHasActiveFocus == controlHasActiveFocus) + return; + + m_controlHasActiveFocus = controlHasActiveFocus; + if (m_controlHasActiveFocus) + controlGotActiveFocus(); + else + controlLostActiveFocus(); + emit controlHasActiveFocusChanged(); +} + +bool QQuickMaterialPlaceholderText::controlHasText() const +{ + return m_controlHasText; +} + +void QQuickMaterialPlaceholderText::setControlHasText(bool controlHasText) +{ + if (m_controlHasText == controlHasText) + return; + + m_controlHasText = controlHasText; + maybeSetFocusAnimationProgress(); + emit controlHasTextChanged(); +} + +/* + Placeholder text of outlined text fields should float when: + - There is placeholder text, and + - The control has active focus, or + - The control has text +*/ +bool QQuickMaterialPlaceholderText::shouldFloat() const +{ + const bool controlHasActiveFocusOrText = m_controlHasActiveFocus || m_controlHasText; + return m_filled + ? controlHasActiveFocusOrText + : !text().isEmpty() && controlHasActiveFocusOrText; +} + +bool QQuickMaterialPlaceholderText::shouldAnimate() const +{ + return m_filled + ? !m_controlHasText + : !m_controlHasText && !text().isEmpty(); +} + +void QQuickMaterialPlaceholderText::updateY() +{ + setY(shouldFloat() ? floatingTargetY() : normalTargetY()); +} + +qreal controlTopInset(QQuickItem *textControl) +{ + if (const auto textArea = qobject_cast<QQuickTextArea *>(textControl)) + return textArea->topInset(); + + if (const auto textField = qobject_cast<QQuickTextField *>(textControl)) + return textField->topInset(); + + return 0; +} + +qreal QQuickMaterialPlaceholderText::normalTargetY() const +{ + auto *textArea = qobject_cast<QQuickTextArea *>(textControl()); + if (textArea && m_controlHeight >= textArea->implicitHeight()) { + // TextArea can be multiple lines in height, and we want the + // placeholder text to sit in the middle of its default-height + // (one-line) if its explicit height is greater than or equal to its + // implicit height - i.e. if it has room for it. If it doesn't have + // room, just do what TextField does. + // We should also account for any topInset the user might have specified, + // which is useful to ensure that the text doesn't get clipped. + return ((m_controlImplicitBackgroundHeight - m_largestHeight) / 2.0) + + controlTopInset(textControl()); + } + + // When the placeholder text shouldn't float, it should sit in the middle of the TextField. + return (m_controlHeight - height()) / 2.0; +} + +qreal QQuickMaterialPlaceholderText::floatingTargetY() const +{ + // For filled text fields, the placeholder text sits just above + // the text when floating. + if (m_filled) + return m_verticalPadding; + + // Outlined text fields have the placeaholder vertically centered + // along the outline at the top. + return (-m_largestHeight / 2.0) + controlTopInset(textControl()); +} + +/*! + \internal + + The height of the text at its largest size that we set. +*/ +int QQuickMaterialPlaceholderText::largestHeight() const +{ + return m_largestHeight; +} + +qreal QQuickMaterialPlaceholderText::controlImplicitBackgroundHeight() const +{ + return m_controlImplicitBackgroundHeight; +} + +void QQuickMaterialPlaceholderText::setControlImplicitBackgroundHeight(qreal controlImplicitBackgroundHeight) +{ + if (qFuzzyCompare(m_controlImplicitBackgroundHeight, controlImplicitBackgroundHeight)) + return; + + m_controlImplicitBackgroundHeight = controlImplicitBackgroundHeight; + updateY(); + emit controlImplicitBackgroundHeightChanged(); +} + +/*! + \internal + + Exists so that we can call updateY when the control's height changes, + which is necessary for some y position calculations. + + We don't really need it for the actual calculations, since we already + have access to the control, from which the property comes, but + it's simpler just to use it. +*/ +qreal QQuickMaterialPlaceholderText::controlHeight() const +{ + return m_controlHeight; +} + +void QQuickMaterialPlaceholderText::setControlHeight(qreal controlHeight) +{ + if (qFuzzyCompare(m_controlHeight, controlHeight)) + return; + + m_controlHeight = controlHeight; + updateY(); +} + +qreal QQuickMaterialPlaceholderText::verticalPadding() const +{ + return m_verticalPadding; +} + +void QQuickMaterialPlaceholderText::setVerticalPadding(qreal verticalPadding) +{ + if (qFuzzyCompare(m_verticalPadding, verticalPadding)) + return; + + m_verticalPadding = 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) { + // Focus changes can happen before the animations finish. + // In that case, stop the animation, which will eventually delete it. + // Until it's deleted, we clear the pointer so that our asserts don't fail + // for the wrong reason. + m_focusOutAnimation->stop(); + m_focusOutAnimation.clear(); + } + + Q_ASSERT(!m_focusInAnimation); + if (shouldAnimate()) { + m_focusInAnimation = new QParallelAnimationGroup(this); + + QPropertyAnimation *yAnimation = new QPropertyAnimation(this, "y", this); + yAnimation->setDuration(300); + yAnimation->setStartValue(y()); + yAnimation->setEndValue(floatingTargetY()); + yAnimation->setEasingCurve(*animationEasingCurve); + m_focusInAnimation->addAnimation(yAnimation); + + auto *scaleAnimation = new QPropertyAnimation(this, "scale", this); + scaleAnimation->setDuration(300); + scaleAnimation->setStartValue(1); + scaleAnimation->setEndValue(floatingScale); + yAnimation->setEasingCurve(*animationEasingCurve); + m_focusInAnimation->addAnimation(scaleAnimation); + + m_focusInAnimation->start(QAbstractAnimation::DeleteWhenStopped); + } else { + updateY(); + } +} + +void QQuickMaterialPlaceholderText::controlLostActiveFocus() +{ + if (m_focusInAnimation) { + m_focusInAnimation->stop(); + m_focusInAnimation.clear(); + } + + Q_ASSERT(!m_focusOutAnimation); + if (shouldAnimate()) { + m_focusOutAnimation = new QParallelAnimationGroup(this); + + auto *yAnimation = new QPropertyAnimation(this, "y", this); + yAnimation->setDuration(300); + yAnimation->setStartValue(y()); + yAnimation->setEndValue(normalTargetY()); + yAnimation->setEasingCurve(*animationEasingCurve); + m_focusOutAnimation->addAnimation(yAnimation); + + auto *scaleAnimation = new QPropertyAnimation(this, "scale", this); + scaleAnimation->setDuration(300); + scaleAnimation->setStartValue(floatingScale); + scaleAnimation->setEndValue(1); + yAnimation->setEasingCurve(*animationEasingCurve); + m_focusOutAnimation->addAnimation(scaleAnimation); + + m_focusOutAnimation->start(QAbstractAnimation::DeleteWhenStopped); + } else { + updateY(); + } +} + +void QQuickMaterialPlaceholderText::maybeSetFocusAnimationProgress() +{ + updateY(); + setScale(shouldFloat() ? floatingScale : 1.0); +} + +void QQuickMaterialPlaceholderText::componentComplete() +{ + QQuickPlaceholderText::componentComplete(); + + adjustTransformOrigin(); + + m_largestHeight = implicitHeight(); + if (m_largestHeight > 0) { + emit largestHeightChanged(); + } else { + qmlWarning(this) << "Expected implicitHeight of placeholder text" << text() + << "to be greater than 0 by component completion!"; + } + + maybeSetFocusAnimationProgress(); +} + +QT_END_NAMESPACE diff --git a/src/quickcontrols/material/impl/qquickmaterialplaceholdertext_p.h b/src/quickcontrols/material/impl/qquickmaterialplaceholdertext_p.h new file mode 100644 index 0000000000..bf851d367a --- /dev/null +++ b/src/quickcontrols/material/impl/qquickmaterialplaceholdertext_p.h @@ -0,0 +1,105 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef QQUICKMATERIALPLACEHOLDERTEXT_P_H +#define QQUICKMATERIALPLACEHOLDERTEXT_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtCore/private/qglobal_p.h> +#include <QtGui/qcolor.h> +#include <QtQuickControls2Impl/private/qquickplaceholdertext_p.h> + +#include <QtCore/qpointer.h> + +QT_BEGIN_NAMESPACE + +class QParallelAnimationGroup; + +class QQuickMaterialPlaceholderText : public QQuickPlaceholderText +{ + Q_OBJECT + Q_PROPERTY(bool filled READ isFilled WRITE setFilled NOTIFY filledChanged FINAL) + Q_PROPERTY(bool controlHasActiveFocus READ controlHasActiveFocus + WRITE setControlHasActiveFocus NOTIFY controlHasActiveFocusChanged FINAL) + Q_PROPERTY(bool controlHasText READ controlHasText WRITE setControlHasText NOTIFY controlHasTextChanged FINAL) + Q_PROPERTY(int largestHeight READ largestHeight NOTIFY largestHeightChanged FINAL) + Q_PROPERTY(qreal verticalPadding READ verticalPadding WRITE setVerticalPadding NOTIFY verticalPaddingChanged FINAL) + Q_PROPERTY(qreal controlImplicitBackgroundHeight READ controlImplicitBackgroundHeight + WRITE setControlImplicitBackgroundHeight NOTIFY controlImplicitBackgroundHeightChanged FINAL) + Q_PROPERTY(qreal controlHeight READ controlHeight WRITE setControlHeight FINAL) + QML_NAMED_ELEMENT(FloatingPlaceholderText) + QML_ADDED_IN_VERSION(6, 5) + +public: + explicit QQuickMaterialPlaceholderText(QQuickItem *parent = nullptr); + + bool isFilled() const; + void setFilled(bool filled); + + int largestHeight() const; + + bool controlHasActiveFocus() const; + void setControlHasActiveFocus(bool controlHasActiveFocus); + + bool controlHasText() const; + void setControlHasText(bool controlHasText); + + qreal controlImplicitBackgroundHeight() const; + void setControlImplicitBackgroundHeight(qreal controlImplicitBackgroundHeight); + + qreal controlHeight() const; + void setControlHeight(qreal controlHeight); + + qreal verticalPadding() const; + void setVerticalPadding(qreal verticalPadding); + +signals: + void filledChanged(); + void largestHeightChanged(); + void controlHasActiveFocusChanged(); + void controlHasTextChanged(); + void controlImplicitBackgroundHeightChanged(); + void verticalPaddingChanged(); + +private slots: + void adjustTransformOrigin(); + +private: + bool shouldFloat() const; + bool shouldAnimate() const; + + void updateY(); + qreal normalTargetY() const; + qreal floatingTargetY() const; + + void controlGotActiveFocus(); + void controlLostActiveFocus(); + + void maybeSetFocusAnimationProgress(); + + void componentComplete() override; + + bool m_filled = false; + bool m_controlHasActiveFocus = false; + bool m_controlHasText = false; + int m_largestHeight = 0; + qreal m_verticalPadding = 0; + qreal m_controlImplicitBackgroundHeight = 0; + qreal m_controlHeight = 0; + QPointer<QParallelAnimationGroup> m_focusInAnimation; + QPointer<QParallelAnimationGroup> m_focusOutAnimation; +}; + +QT_END_NAMESPACE + +#endif // QQUICKMATERIALPLACEHOLDERTEXT_P_H diff --git a/src/quickcontrols/material/impl/qquickmaterialprogressbar.cpp b/src/quickcontrols/material/impl/qquickmaterialprogressbar.cpp index 67c9f614f5..19e02dc5b7 100644 --- a/src/quickcontrols/material/impl/qquickmaterialprogressbar.cpp +++ b/src/quickcontrols/material/impl/qquickmaterialprogressbar.cpp @@ -16,7 +16,7 @@ QT_BEGIN_NAMESPACE static const int PauseDuration = 520; static const int SlideDuration = 1240; -static const int TotalDuration = SlideDuration + PauseDuration; +static const int QmpbTotalDuration = SlideDuration + PauseDuration; class QQuickMaterialProgressBarNode : public QQuickAnimatedNode { @@ -37,7 +37,7 @@ QQuickMaterialProgressBarNode::QQuickMaterialProgressBarNode(QQuickMaterialProgr : QQuickAnimatedNode(item) { setLoopCount(Infinite); - setDuration(TotalDuration); + setDuration(QmpbTotalDuration); } void QQuickMaterialProgressBarNode::updateCurrentTime(int time) diff --git a/src/quickcontrols/material/impl/qquickmaterialprogressbar_p.h b/src/quickcontrols/material/impl/qquickmaterialprogressbar_p.h index d250c2f76d..46d5c1b20f 100644 --- a/src/quickcontrols/material/impl/qquickmaterialprogressbar_p.h +++ b/src/quickcontrols/material/impl/qquickmaterialprogressbar_p.h @@ -54,6 +54,4 @@ private: QT_END_NAMESPACE -QML_DECLARE_TYPE(QQuickMaterialProgressBar) - #endif // QQUICKMATERIALPROGRESSBAR_P_H diff --git a/src/quickcontrols/material/impl/qquickmaterialripple.cpp b/src/quickcontrols/material/impl/qquickmaterialripple.cpp index 89648182d4..2472b940a3 100644 --- a/src/quickcontrols/material/impl/qquickmaterialripple.cpp +++ b/src/quickcontrols/material/impl/qquickmaterialripple.cpp @@ -12,9 +12,7 @@ QT_BEGIN_NAMESPACE -namespace { - enum WavePhase { WaveEnter, WaveExit }; -} +enum WavePhase { WaveEnter, WaveExit }; static const int RIPPLE_ENTER_DELAY = 80; static const int OPACITY_ENTER_DURATION_FAST = 120; @@ -90,6 +88,10 @@ void QQuickMaterialRippleWaveNode::updateCurrentTime(int time) Q_ASSERT(rectNode->type() == QSGNode::GeometryNodeType); rectNode->setRect(QRectF(0, 0, m_value, m_value)); rectNode->setRadius(m_value / 2); + rectNode->setTopLeftRadius(-1); + rectNode->setTopRightRadius(-1); + rectNode->setBottomLeftRadius(-1); + rectNode->setBottomRightRadius(-1); rectNode->update(); } @@ -176,6 +178,10 @@ void QQuickMaterialRippleBackgroundNode::sync(QQuickItem *item) rectNode->setRect(QRectF(0, 0, w, h)); rectNode->setRadius(ripple->clipRadius()); } + rectNode->setTopLeftRadius(-1); + rectNode->setTopRightRadius(-1); + rectNode->setBottomLeftRadius(-1); + rectNode->setBottomRightRadius(-1); setMatrix(matrix); rectNode->setColor(ripple->color()); diff --git a/src/quickcontrols/material/impl/qquickmaterialripple_p.h b/src/quickcontrols/material/impl/qquickmaterialripple_p.h index cdaaf13b1a..d0772052bd 100644 --- a/src/quickcontrols/material/impl/qquickmaterialripple_p.h +++ b/src/quickcontrols/material/impl/qquickmaterialripple_p.h @@ -83,6 +83,4 @@ private: QT_END_NAMESPACE -QML_DECLARE_TYPE(QQuickMaterialRipple) - #endif // QQUICKMATERIALRIPPLE_P_H diff --git a/src/quickcontrols/material/impl/qquickmaterialtextcontainer.cpp b/src/quickcontrols/material/impl/qquickmaterialtextcontainer.cpp new file mode 100644 index 0000000000..ab07c9d9b2 --- /dev/null +++ b/src/quickcontrols/material/impl/qquickmaterialtextcontainer.cpp @@ -0,0 +1,422 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "qquickmaterialtextcontainer_p.h" + +#include <QtCore/qpropertyanimation.h> +#include <QtGui/qpainter.h> +#include <QtGui/qpainterpath.h> +#include <QtQml/qqmlinfo.h> + +QT_BEGIN_NAMESPACE + +/* + This class exists because: + + - Rectangle doesn't support individual radii for each corner (QTBUG-48774). + - We need to draw an interrupted (where the placeholder text is) line for outlined containers. + - We need to animate the focus line for filled containers, and we can't use "Behavior on" + syntax because we only want to animate activeFocus becoming true, not also false. To do this + requires imperative code, and we want to keep the QML declarative. + + focusAnimationProgress has to be a property even though it's only used internally, + because we have to use QPropertyAnimation on it. + + An advantage of doing the animation in C++ is that we avoid the memory + overhead of an animation instance even when we're not using it, and instead + create it on demand and delete it when it's done. I tried doing the animation + declaratively with states and transitions, but it was more difficult to implement + and would have been harder to maintain, as well as having more overhead. +*/ + +QQuickMaterialTextContainer::QQuickMaterialTextContainer(QQuickItem *parent) + : QQuickPaintedItem(parent) +{ +} + +bool QQuickMaterialTextContainer::isFilled() const +{ + return m_filled; +} + +void QQuickMaterialTextContainer::setFilled(bool filled) +{ + if (filled == m_filled) + return; + + m_filled = filled; + update(); +} + +QColor QQuickMaterialTextContainer::fillColor() const +{ + return m_fillColor; +} + +void QQuickMaterialTextContainer::setFillColor(const QColor &fillColor) +{ + if (fillColor == m_fillColor) + return; + + m_fillColor = fillColor; + update(); +} + +QColor QQuickMaterialTextContainer::outlineColor() const +{ + return m_outlineColor; +} + +void QQuickMaterialTextContainer::setOutlineColor(const QColor &outlineColor) +{ + if (outlineColor == m_outlineColor) + return; + + m_outlineColor = outlineColor; + update(); +} + +QColor QQuickMaterialTextContainer::focusedOutlineColor() const +{ + return m_outlineColor; +} + +void QQuickMaterialTextContainer::setFocusedOutlineColor(const QColor &focusedOutlineColor) +{ + if (focusedOutlineColor == m_focusedOutlineColor) + return; + + m_focusedOutlineColor = focusedOutlineColor; + update(); +} + +qreal QQuickMaterialTextContainer::focusAnimationProgress() const +{ + return m_focusAnimationProgress; +} + +void QQuickMaterialTextContainer::setFocusAnimationProgress(qreal progress) +{ + if (qFuzzyCompare(progress, m_focusAnimationProgress)) + return; + + m_focusAnimationProgress = progress; + update(); +} + +qreal QQuickMaterialTextContainer::placeholderTextWidth() const +{ + return m_placeholderTextWidth; +} + +void QQuickMaterialTextContainer::setPlaceholderTextWidth(qreal placeholderTextWidth) +{ + if (qFuzzyCompare(placeholderTextWidth, m_placeholderTextWidth)) + return; + + m_placeholderTextWidth = placeholderTextWidth; + 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; +} + +void QQuickMaterialTextContainer::setControlHasActiveFocus(bool controlHasActiveFocus) +{ + if (m_controlHasActiveFocus == controlHasActiveFocus) + return; + + m_controlHasActiveFocus = controlHasActiveFocus; + if (m_controlHasActiveFocus) + controlGotActiveFocus(); + else + controlLostActiveFocus(); + emit controlHasActiveFocusChanged(); +} + +bool QQuickMaterialTextContainer::controlHasText() const +{ + return m_controlHasText; +} + +void QQuickMaterialTextContainer::setControlHasText(bool controlHasText) +{ + if (m_controlHasText == controlHasText) + return; + + m_controlHasText = controlHasText; + // TextArea's text length is updated after component completion, + // so account for that here and in setPlaceholderHasText(). + maybeSetFocusAnimationProgress(); + update(); + emit controlHasTextChanged(); +} + +bool QQuickMaterialTextContainer::placeholderHasText() const +{ + return m_placeholderHasText; +} + +void QQuickMaterialTextContainer::setPlaceholderHasText(bool placeholderHasText) +{ + if (m_placeholderHasText == placeholderHasText) + return; + + m_placeholderHasText = placeholderHasText; + maybeSetFocusAnimationProgress(); + update(); + emit placeholderHasTextChanged(); +} + +int QQuickMaterialTextContainer::horizontalPadding() const +{ + return m_horizontalPadding; +} + +/*! + \internal + + The text field's horizontal padding. + + We need this to be a property so that the QML can set it, since we can't + access QQuickMaterialStyle's C++ API from this plugin. +*/ +void QQuickMaterialTextContainer::setHorizontalPadding(int horizontalPadding) +{ + if (m_horizontalPadding == horizontalPadding) + return; + m_horizontalPadding = horizontalPadding; + update(); + emit horizontalPaddingChanged(); +} + +void QQuickMaterialTextContainer::paint(QPainter *painter) +{ + qreal w = width(); + qreal h = height(); + if (w <= 0 || h <= 0) + return; + + // Account for pen width. + const qreal penWidth = m_filled ? 1 : (m_controlHasActiveFocus ? 2 : 1); + w -= penWidth; + h -= penWidth; + + const qreal cornerRadius = 4; + // 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; + + // Top-left rounded corner. + if (m_filled || m_focusAnimationProgress == 0) { + startPos = QPointF(cornerRadius, 0); + } else { + // 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. + // Changing the cap style to Qt::FlatCap would only fix this by half the pen width, + // but it has no effect anyway (perhaps it literally only affects end points and not "start" points?). + startPos = QPointF(gapCenterX - (m_focusAnimationProgress * halfPlaceholderWidth) - gapPadding - penWidth, 0); + } + path.moveTo(startPos); + path.arcTo(0, 0, cornerRadius * 2, cornerRadius * 2, 90, 90); + + // Bottom-left corner. + if (m_filled) { + path.lineTo(0, h); + } else { + path.lineTo(0, h - cornerRadius * 2); + path.arcTo(0, h - cornerRadius * 2, cornerRadius * 2, cornerRadius * 2, 180, 90); + } + + // Bottom-right corner. + if (m_filled) { + path.lineTo(w, h); + } else { + path.lineTo(w - cornerRadius * 2, h); + path.arcTo(w - cornerRadius * 2, h - cornerRadius * 2, cornerRadius * 2, cornerRadius * 2, 270, 90); + } + + // Top-right rounded corner. + path.lineTo(w, cornerRadius); + path.arcTo(w - (cornerRadius * 2), 0, cornerRadius * 2, cornerRadius * 2, 0, 90); + + if (m_filled || qFuzzyIsNull(m_focusAnimationProgress)) { + // Back to the start. + path.lineTo(startPos.x(), startPos.y()); + } else { + path.lineTo(gapCenterX + (m_focusAnimationProgress * halfPlaceholderWidth) + gapPadding, startPos.y()); + } + + // Account for pen width. + painter->translate(penWidth / 2, penWidth / 2); + + painter->setRenderHint(QPainter::Antialiasing, true); + + auto control = textControl(); + const bool focused = control && control->hasActiveFocus(); + // We still want to draw the stroke when it's filled, otherwise it will be a pixel + // (the pen width) too narrow on either side. + QPen pen; + pen.setColor(m_filled ? m_fillColor : (focused ? m_focusedOutlineColor : m_outlineColor)); + pen.setWidthF(penWidth); + painter->setPen(pen); + if (m_filled) + painter->setBrush(QBrush(m_fillColor)); + + // Fill or stroke the container's shape. + // If not filling, the default brush will be used, which is Qt::NoBrush. + painter->drawPath(path); + + // Draw the focus line at the bottom for filled containers. + if (m_filled) { + if (!qFuzzyCompare(m_focusAnimationProgress, 1.0)) { + // Draw the enabled active indicator line (#10) that's at the bottom when it's not focused: + // https://m3.material.io/components/text-fields/specs#6d654d1d-262e-4697-858c-9a75e8e7c81d + // Don't bother drawing it when the animation has finished, as the focused active indicator + // line below will obscure it. + pen.setColor(m_outlineColor); + painter->setPen(pen); + painter->drawLine(0, h, w, h); + } + + if (!qFuzzyIsNull(m_focusAnimationProgress)) { + // Draw the focused active indicator line (#6) that's at the bottom when it's focused. + // Start at the center and expand outwards. + const int lineLength = m_focusAnimationProgress * w; + const int horizontalCenter = w / 2; + pen.setColor(m_focusedOutlineColor); + pen.setWidth(2); + painter->setPen(pen); + painter->drawLine(horizontalCenter - (lineLength / 2), h, + horizontalCenter + (lineLength / 2) + pen.width() / 2, h); + } + } +} + +bool QQuickMaterialTextContainer::shouldAnimateOutline() const +{ + return !m_controlHasText && m_placeholderHasText; +} + +/*! + \internal + + \sa QQuickPlaceholderText::textControl(). +*/ +QQuickItem *QQuickMaterialTextContainer::textControl() const +{ + return qobject_cast<QQuickItem *>(parent()); +} + +void QQuickMaterialTextContainer::controlGotActiveFocus() +{ + const bool shouldAnimate = m_filled ? !m_controlHasText : shouldAnimateOutline(); + if (!shouldAnimate) { + // It does have focus, but sometimes we don't need to animate anything, just change colors. + if (m_filled && m_controlHasText) { + // When a filled container has text already entered, we should just immediately change + // the color and thickness of the indicator line. + m_focusAnimationProgress = 1; + } + update(); + return; + } + + startFocusAnimation(); +} + +void QQuickMaterialTextContainer::controlLostActiveFocus() +{ + // We don't want to animate the active indicator line (at the bottom) of filled containers + // when the control loses focus, only when it gets it. + if (m_filled || !shouldAnimateOutline()) { + // Ensure that we set this so that filled containers go back to a non-accent-colored + // active indicator line when losing focus. + if (m_filled) + m_focusAnimationProgress = 0; + update(); + return; + } + + QPropertyAnimation *animation = new QPropertyAnimation(this, "focusAnimationProgress", this); + animation->setDuration(300); + animation->setStartValue(1); + animation->setEndValue(0); + animation->start(QAbstractAnimation::DeleteWhenStopped); +} + +void QQuickMaterialTextContainer::startFocusAnimation() +{ + // Each time setFocusAnimationProgress is called by the animation, it'll call update(), + // which will cause us to be re-rendered. + QPropertyAnimation *animation = new QPropertyAnimation(this, "focusAnimationProgress", this); + animation->setDuration(300); + animation->setStartValue(0); + animation->setEndValue(1); + animation->start(QAbstractAnimation::DeleteWhenStopped); +} + +void QQuickMaterialTextContainer::maybeSetFocusAnimationProgress() +{ + if (m_filled) + return; + + if (m_controlHasText && m_placeholderHasText) { + // Show the interrupted outline when there is text. + setFocusAnimationProgress(1); + } else if (!m_controlHasText && !m_controlHasActiveFocus) { + // If the text was cleared while it didn't have focus, don't animate, just close the gap. + setFocusAnimationProgress(0); + } +} + +void QQuickMaterialTextContainer::componentComplete() +{ + QQuickPaintedItem::componentComplete(); + + if (!parentItem()) + qmlWarning(this) << "Expected parent item by component completion!"; + + maybeSetFocusAnimationProgress(); +} + +QT_END_NAMESPACE diff --git a/src/quickcontrols/material/impl/qquickmaterialtextcontainer_p.h b/src/quickcontrols/material/impl/qquickmaterialtextcontainer_p.h new file mode 100644 index 0000000000..648e83521f --- /dev/null +++ b/src/quickcontrols/material/impl/qquickmaterialtextcontainer_p.h @@ -0,0 +1,122 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef QQUICKMATERIALTEXTCONTAINER_P_H +#define QQUICKMATERIALTEXTCONTAINER_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtCore/private/qglobal_p.h> +#include <QtGui/qcolor.h> +#include <QtQuick/qquickpainteditem.h> + +QT_BEGIN_NAMESPACE + +class QQuickMaterialTextContainer : public QQuickPaintedItem +{ + Q_OBJECT + Q_PROPERTY(bool filled READ isFilled WRITE setFilled FINAL) + Q_PROPERTY(bool controlHasActiveFocus READ controlHasActiveFocus + WRITE setControlHasActiveFocus NOTIFY controlHasActiveFocusChanged FINAL) + Q_PROPERTY(QColor fillColor READ fillColor WRITE setFillColor FINAL) + Q_PROPERTY(QColor outlineColor READ outlineColor WRITE setOutlineColor FINAL) + 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) + QML_NAMED_ELEMENT(MaterialTextContainer) + QML_ADDED_IN_VERSION(6, 5) + +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); + + QColor fillColor() const; + void setFillColor(const QColor &fillColor); + + QColor outlineColor() const; + void setOutlineColor(const QColor &outlineColor); + + QColor focusedOutlineColor() const; + void setFocusedOutlineColor(const QColor &focusedOutlineColor); + + qreal focusAnimationProgress() const; + void setFocusAnimationProgress(qreal progress); + + qreal placeholderTextWidth() const; + void setPlaceholderTextWidth(qreal placeholderTextWidth); + + bool controlHasActiveFocus() const; + void setControlHasActiveFocus(bool controlHasActiveFocus); + + bool controlHasText() const; + void setControlHasText(bool controlHasText); + + bool placeholderHasText() const; + void setPlaceholderHasText(bool placeholderHasText); + + int horizontalPadding() const; + void setHorizontalPadding(int horizontalPadding); + + void paint(QPainter *painter) override; + + PlaceHolderHAlignment placeholderTextHAlign() const; + void setPlaceholderTextHAlign(PlaceHolderHAlignment placeHolderTextHAlign); + +signals: + void animateChanged(); + void controlHasActiveFocusChanged(); + void controlHasTextChanged(); + void placeholderHasTextChanged(); + void horizontalPaddingChanged(); + +private: + bool shouldAnimateOutline() const; + + QQuickItem *textControl() const; + void controlGotActiveFocus(); + void controlLostActiveFocus(); + void startFocusAnimation(); + + void maybeSetFocusAnimationProgress(); + + void componentComplete() override; + + QColor m_fillColor; + QColor m_outlineColor; + QColor m_focusedOutlineColor; + qreal m_focusAnimationProgress = 0; + qreal m_placeholderTextWidth = 0; + bool m_filled = false; + bool m_controlHasActiveFocus = false; + bool m_controlHasText = false; + bool m_placeholderHasText = false; + int m_horizontalPadding = 0; + PlaceHolderHAlignment m_placeholderTextHAlign; +}; + +QT_END_NAMESPACE + +#endif // QQUICKMATERIALTEXTCONTAINER_P_H diff --git a/src/quickcontrols/material/qquickmaterialstyle.cpp b/src/quickcontrols/material/qquickmaterialstyle.cpp index d7eb72f830..f8ced5821b 100644 --- a/src/quickcontrols/material/qquickmaterialstyle.cpp +++ b/src/quickcontrols/material/qquickmaterialstyle.cpp @@ -4,7 +4,9 @@ #include "qquickmaterialstyle_p.h" #include <QtCore/qdebug.h> +#if QT_CONFIG(settings) #include <QtCore/qsettings.h> +#endif #include <QtQml/qqmlinfo.h> #include <QtQuickControls2/private/qquickstyle_p.h> @@ -422,6 +424,8 @@ static const QRgb switchDisabledCheckedTrackColorLight = 0x1E1C1B1F; static const QRgb switchDisabledCheckedTrackColorDark = 0x1EE6E1E5; static const QRgb switchDisabledUncheckedIconColorLight = 0x611C1B1F; static const QRgb switchDisabledUncheckedIconColorDark = 0x61E6E1E5; +static const QRgb textFieldFilledContainerColorLight = 0xFFE7E0EC; +static const QRgb textFieldFilledContainerColorDark = 0xFF49454F; static QQuickMaterialStyle::Theme effectiveTheme(QQuickMaterialStyle::Theme theme) { @@ -941,10 +945,7 @@ QColor QQuickMaterialStyle::iconDisabledColor() const QColor QQuickMaterialStyle::buttonColor(Theme theme, const QVariant &background, const QVariant &accent, bool enabled, bool flat, bool highlighted, bool checked) const { - if (flat) - return Qt::transparent; - - if (!enabled) { + if (!enabled && !flat) { return QColor::fromRgba(m_theme == Light ? raisedButtonDisabledColorLight : raisedButtonDisabledColorDark); } @@ -970,7 +971,11 @@ QColor QQuickMaterialStyle::buttonColor(Theme theme, const QVariant &background, // A highlighted + checked button should become darker. color = accentColor(checked ? Shade100 : shade); } - } else { + // Flat, highlighted buttons need to have a semi-transparent background, + // otherwise the text won't be visible. + if (flat) + color.setAlphaF(0.25); + } else if (!flat) { // Even if the elevation is zero, it should still have a background if it's not flat. color = QColor::fromRgba(m_theme == Light ? raisedButtonColorLight : raisedButtonColorDark); @@ -1158,6 +1163,11 @@ QColor QQuickMaterialStyle::sliderDisabledColor() const return QColor::fromRgba(m_theme == Light ? sliderDisabledColorLight : sliderDisabledColorDark); } +QColor QQuickMaterialStyle::textFieldFilledContainerColor() const +{ + return QColor::fromRgba(m_theme == Light ? textFieldFilledContainerColorLight : textFieldFilledContainerColorDark); +} + QColor QQuickMaterialStyle::color(QQuickMaterialStyle::Color color, QQuickMaterialStyle::Shade shade) const { int count = sizeof(colors) / sizeof(colors[0]); @@ -1259,6 +1269,32 @@ int QQuickMaterialStyle::touchTarget() const return globalVariant == Dense ? 44 : 48; } +int QQuickMaterialStyle::buttonVerticalPadding() const +{ + return globalVariant == Dense ? 10 : 14; +} + +// https://m3.material.io/components/buttons/specs#256326ad-f934-40e7-b05f-0bcb41aa4382 +int QQuickMaterialStyle::buttonLeftPadding(bool flat, bool hasIcon) const +{ + static const int noIconPadding = globalVariant == Dense ? 12 : 24; + static const int iconPadding = globalVariant == Dense ? 8 : 16; + static const int flatPadding = globalVariant == Dense ? 6 : 12; + return !flat ? (!hasIcon ? noIconPadding : iconPadding) : flatPadding; +} + +int QQuickMaterialStyle::buttonRightPadding(bool flat, bool hasIcon, bool hasText) const +{ + static const int noTextPadding = globalVariant == Dense ? 8 : 16; + static const int textPadding = globalVariant == Dense ? 12 : 24; + static const int flatNoIconPadding = globalVariant == Dense ? 6 : 12; + static const int flatNoTextPadding = globalVariant == Dense ? 6 : 12; + static const int flatTextPadding = globalVariant == Dense ? 8 : 16; + return !flat + ? (!hasText ? noTextPadding : textPadding) + : (!hasIcon ? flatNoIconPadding : (!hasText ? flatNoTextPadding : flatTextPadding)); +} + int QQuickMaterialStyle::buttonHeight() const { // https://m3.material.io/components/buttons/specs#256326ad-f934-40e7-b05f-0bcb41aa4382 @@ -1276,6 +1312,19 @@ int QQuickMaterialStyle::dialogButtonBoxHeight() const return globalVariant == Dense ? 48 : 52; } +int QQuickMaterialStyle::dialogTitleFontPixelSize() const +{ + return globalVariant == Dense ? 16 : 24; +} + +// https://m3.material.io/components/dialogs/specs#6771d107-624e-47cc-b6d8-2b7b620ba2f1 +QQuickMaterialStyle::RoundedScale QQuickMaterialStyle::dialogRoundedScale() const +{ + return globalVariant == Dense + ? QQuickMaterialStyle::RoundedScale::LargeScale + : QQuickMaterialStyle::RoundedScale::ExtraLargeScale; +} + int QQuickMaterialStyle::frameVerticalPadding() const { return globalVariant == Dense ? 8 : 12; @@ -1292,6 +1341,31 @@ int QQuickMaterialStyle::menuItemVerticalPadding() const return globalVariant == Dense ? 8 : 12; } +int QQuickMaterialStyle::switchIndicatorWidth() const +{ + return globalVariant == Dense ? 40 : 52; +} + +int QQuickMaterialStyle::switchIndicatorHeight() const +{ + return globalVariant == Dense ? 22 : 32; +} + +int QQuickMaterialStyle::switchNormalHandleHeight() const +{ + return globalVariant == Dense ? 10 : 16; +} + +int QQuickMaterialStyle::switchCheckedHandleHeight() const +{ + return globalVariant == Dense ? 16 : 24; +} + +int QQuickMaterialStyle::switchLargestHandleHeight() const +{ + return globalVariant == Dense ? 18 : 28; +} + int QQuickMaterialStyle::switchDelegateVerticalPadding() const { // SwitchDelegate's indicator is much larger than the others due to the shadow, @@ -1299,6 +1373,21 @@ int QQuickMaterialStyle::switchDelegateVerticalPadding() const return globalVariant == Dense ? 4 : 8; } +int QQuickMaterialStyle::textFieldHeight() const +{ + // filled: https://m3.material.io/components/text-fields/specs#8c032848-e442-46df-b25d-28f1315f234b + // outlined: https://m3.material.io/components/text-fields/specs#605e24f1-1c1f-4c00-b385-4bf50733a5ef + return globalVariant == Dense ? 44 : 56; +} +int QQuickMaterialStyle::textFieldHorizontalPadding() const +{ + return globalVariant == Dense ? 12 : 16; +} +int QQuickMaterialStyle::textFieldVerticalPadding() const +{ + return globalVariant == Dense ? 4 : 8; +} + int QQuickMaterialStyle::tooltipHeight() const { // https://material.io/guidelines/components/tooltips.html diff --git a/src/quickcontrols/material/qquickmaterialstyle_p.h b/src/quickcontrols/material/qquickmaterialstyle_p.h index ab3483f04d..f7c2b256ba 100644 --- a/src/quickcontrols/material/qquickmaterialstyle_p.h +++ b/src/quickcontrols/material/qquickmaterialstyle_p.h @@ -18,10 +18,11 @@ #include <QtGui/qcolor.h> #include <QtQml/qqml.h> #include <QtQuickControls2/qquickattachedpropertypropagator.h> +#include <QtQuickControls2Material/qtquickcontrols2materialexports.h> QT_BEGIN_NAMESPACE -class QQuickMaterialStyle : public QQuickAttachedPropertyPropagator +class Q_QUICKCONTROLS2MATERIAL_EXPORT QQuickMaterialStyle : public QQuickAttachedPropertyPropagator { Q_OBJECT Q_PROPERTY(Theme theme READ theme WRITE setTheme RESET resetTheme NOTIFY themeChanged FINAL) @@ -73,15 +74,27 @@ class QQuickMaterialStyle : public QQuickAttachedPropertyPropagator Q_PROPERTY(QColor toolTextColor READ toolTextColor NOTIFY toolTextColorChanged FINAL) Q_PROPERTY(QColor spinBoxDisabledIconColor READ spinBoxDisabledIconColor NOTIFY themeChanged FINAL) Q_PROPERTY(QColor sliderDisabledColor READ sliderDisabledColor NOTIFY themeChanged FINAL REVISION(2, 15)) + Q_PROPERTY(QColor textFieldFilledContainerColor READ textFieldFilledContainerColor NOTIFY themeChanged FINAL) Q_PROPERTY(int touchTarget READ touchTarget CONSTANT FINAL) + Q_PROPERTY(int buttonVerticalPadding READ buttonVerticalPadding CONSTANT FINAL) Q_PROPERTY(int buttonHeight READ buttonHeight CONSTANT FINAL) Q_PROPERTY(int delegateHeight READ delegateHeight CONSTANT FINAL) Q_PROPERTY(int dialogButtonBoxHeight READ dialogButtonBoxHeight CONSTANT FINAL) + Q_PROPERTY(int dialogTitleFontPixelSize READ dialogTitleFontPixelSize CONSTANT FINAL) + Q_PROPERTY(RoundedScale dialogRoundedScale READ dialogRoundedScale CONSTANT FINAL) Q_PROPERTY(int frameVerticalPadding READ frameVerticalPadding CONSTANT FINAL) Q_PROPERTY(int menuItemHeight READ menuItemHeight CONSTANT FINAL) Q_PROPERTY(int menuItemVerticalPadding READ menuItemVerticalPadding CONSTANT FINAL) + Q_PROPERTY(int switchIndicatorWidth READ switchIndicatorWidth CONSTANT FINAL) + Q_PROPERTY(int switchIndicatorHeight READ switchIndicatorHeight CONSTANT FINAL) + Q_PROPERTY(int switchNormalHandleHeight READ switchNormalHandleHeight CONSTANT FINAL) + Q_PROPERTY(int switchCheckedHandleHeight READ switchCheckedHandleHeight CONSTANT FINAL) + Q_PROPERTY(int switchLargestHandleHeight READ switchLargestHandleHeight CONSTANT FINAL) Q_PROPERTY(int switchDelegateVerticalPadding READ switchDelegateVerticalPadding CONSTANT FINAL) + Q_PROPERTY(int textFieldHeight READ textFieldHeight CONSTANT FINAL) + Q_PROPERTY(int textFieldHorizontalPadding READ textFieldHorizontalPadding CONSTANT FINAL) + Q_PROPERTY(int textFieldVerticalPadding READ textFieldVerticalPadding CONSTANT FINAL) Q_PROPERTY(int tooltipHeight READ tooltipHeight CONSTANT FINAL) QML_NAMED_ELEMENT(Material) @@ -254,18 +267,32 @@ public: QColor toolTextColor() const; QColor spinBoxDisabledIconColor() const; QColor sliderDisabledColor() const; + QColor textFieldFilledContainerColor() const; Q_INVOKABLE QColor color(Color color, Shade shade = Shade500) const; Q_INVOKABLE QColor shade(const QColor &color, Shade shade) const; int touchTarget() const; + int buttonVerticalPadding() const; + Q_INVOKABLE int buttonLeftPadding(bool flat, bool hasIcon) const; + Q_INVOKABLE int buttonRightPadding(bool flat, bool hasIcon, bool hasText) const; int buttonHeight() const; int delegateHeight() const; int dialogButtonBoxHeight() const; + int dialogTitleFontPixelSize() const; + RoundedScale dialogRoundedScale() const; int frameVerticalPadding() const; int menuItemHeight() const; int menuItemVerticalPadding() const; + int switchIndicatorWidth() const; + int switchIndicatorHeight() const; + int switchNormalHandleHeight() const; + int switchCheckedHandleHeight() const; + int switchLargestHandleHeight() const; int switchDelegateVerticalPadding() const; + int textFieldHeight() const; + int textFieldHorizontalPadding() const; + int textFieldVerticalPadding() const; int tooltipHeight() const; static void initGlobals(); diff --git a/src/quickcontrols/material/qquickmaterialtheme.cpp b/src/quickcontrols/material/qquickmaterialtheme.cpp index 629c389be5..d121a1a47e 100644 --- a/src/quickcontrols/material/qquickmaterialtheme.cpp +++ b/src/quickcontrols/material/qquickmaterialtheme.cpp @@ -6,7 +6,7 @@ #include <QtGui/qpa/qplatformdialoghelper.h> #include <QtGui/qfont.h> -#include <QtGui/qfontinfo.h> +#include <QtGui/qfontdatabase.h> #include <QtQuickTemplates2/private/qquicktheme_p.h> QT_BEGIN_NAMESPACE @@ -21,17 +21,15 @@ void QQuickMaterialTheme::initialize(QQuickTheme *theme) QFont menuItemFont; QFont editorFont; - QFont font; - font.setFamilies(QStringList{QLatin1String("Roboto")}); - QString family = QFontInfo(font).family(); - - if (family != QLatin1String("Roboto")) { - font.setFamilies(QStringList{QLatin1String("Noto")}); - family = QFontInfo(font).family(); + auto defaultFontFamily = QLatin1String("Roboto"); + if (!QFontDatabase::hasFamily(defaultFontFamily)) { + defaultFontFamily = QLatin1String("Noto"); // fallback + if (!QFontDatabase::hasFamily(defaultFontFamily)) + defaultFontFamily = {}; } - if (family == QLatin1String("Roboto") || family == QLatin1String("Noto")) { - const QStringList families{family}; + if (!defaultFontFamily.isEmpty()) { + const QStringList families{defaultFontFamily}; systemFont.setFamilies(families); buttonFont.setFamilies(families); toolTipFont.setFamilies(families); @@ -47,7 +45,6 @@ void QQuickMaterialTheme::initialize(QQuickTheme *theme) // https://material.io/guidelines/components/buttons.html#buttons-style buttonFont.setPixelSize(dense ? 13 : 14); - buttonFont.setCapitalization(QFont::AllUppercase); buttonFont.setWeight(QFont::Medium); theme->setFont(QQuickTheme::Button, buttonFont); theme->setFont(QQuickTheme::TabBar, buttonFont); diff --git a/src/quickcontrols/material/qquickmaterialtheme_p.h b/src/quickcontrols/material/qquickmaterialtheme_p.h index 893f441c86..bdaecd1a87 100644 --- a/src/quickcontrols/material/qquickmaterialtheme_p.h +++ b/src/quickcontrols/material/qquickmaterialtheme_p.h @@ -15,13 +15,13 @@ // We mean it. // -#include <QtCore/private/qglobal_p.h> +#include <QtQuickControls2Material/qtquickcontrols2materialexports.h> QT_BEGIN_NAMESPACE class QQuickTheme; -class QQuickMaterialTheme +class Q_QUICKCONTROLS2MATERIAL_EXPORT QQuickMaterialTheme { public: static void initialize(QQuickTheme *theme); diff --git a/src/quickcontrols/material/qtquickcontrols2materialstyleplugin.cpp b/src/quickcontrols/material/qtquickcontrols2materialstyleplugin.cpp index 10aa64b8cf..4911a3e0f2 100644 --- a/src/quickcontrols/material/qtquickcontrols2materialstyleplugin.cpp +++ b/src/quickcontrols/material/qtquickcontrols2materialstyleplugin.cpp @@ -5,8 +5,6 @@ #include "qquickmaterialtheme_p.h" #include <QtQuickControls2/private/qquickstyleplugin_p.h> -#include <QtQuickControls2Impl/private/qquickpaddedrectangle_p.h> -#include <QtQuickTemplates2/private/qquicktheme_p.h> QT_BEGIN_NAMESPACE diff --git a/src/quickcontrols/material/shaders/+glslcore/RectangularGlow.frag b/src/quickcontrols/material/shaders/+glslcore/RectangularGlow.frag deleted file mode 100644 index 432d86b5d5..0000000000 --- a/src/quickcontrols/material/shaders/+glslcore/RectangularGlow.frag +++ /dev/null @@ -1,25 +0,0 @@ -#version 150 - -uniform float qt_Opacity; -uniform float relativeSizeX; -uniform float relativeSizeY; -uniform float spread; -uniform vec4 color; - -in vec2 qt_TexCoord0; -out vec4 fragColor; - -float linearstep(float e0, float e1, float x) -{ - return clamp((x - e0) / (e1 - e0), 0.0, 1.0); -} - -void main() -{ - float alpha = - smoothstep(0.0, relativeSizeX, 0.5 - abs(0.5 - qt_TexCoord0.x)) * - smoothstep(0.0, relativeSizeY, 0.5 - abs(0.5 - qt_TexCoord0.y)); - - float spreadMultiplier = linearstep(spread, 1.0 - spread, alpha); - fragColor = color * qt_Opacity * spreadMultiplier * spreadMultiplier; -} diff --git a/src/quickcontrols/material/shaders/+hlsl/RectangularGlow.frag b/src/quickcontrols/material/shaders/+hlsl/RectangularGlow.frag deleted file mode 100644 index 69d9f852fb..0000000000 --- a/src/quickcontrols/material/shaders/+hlsl/RectangularGlow.frag +++ /dev/null @@ -1,21 +0,0 @@ -cbuffer ConstantBuffer : register(b0) -{ - float4x4 qt_Matrix; - float qt_Opacity; - float relativeSizeX; - float relativeSizeY; - float spread; - float4 color; -} - -float linearstep(float e0, float e1, float x) { return clamp((x - e0) / (e1 - e0), 0.0, 1.0); } - -float4 main(float4 position : SV_POSITION, float2 coord : TEXCOORD0) : SV_TARGET -{ - float alpha = - smoothstep(0.0, relativeSizeX, 0.5 - abs(0.5 - coord.x)) * - smoothstep(0.0, relativeSizeY, 0.5 - abs(0.5 - coord.y)); - - float spreadMultiplier = linearstep(spread, 1.0 - spread, alpha); - return color * qt_Opacity * spreadMultiplier * spreadMultiplier; -} diff --git a/src/quickcontrols/material/shaders/+qsb/RectangularGlow.frag b/src/quickcontrols/material/shaders/+qsb/RectangularGlow.frag Binary files differdeleted file mode 100644 index 5cfa2db6ed..0000000000 --- a/src/quickcontrols/material/shaders/+qsb/RectangularGlow.frag +++ /dev/null diff --git a/src/quickcontrols/material/shaders/RectangularGlow.frag b/src/quickcontrols/material/shaders/RectangularGlow.frag index 40bab5806c..50188d4516 100644 --- a/src/quickcontrols/material/shaders/RectangularGlow.frag +++ b/src/quickcontrols/material/shaders/RectangularGlow.frag @@ -1,19 +1,31 @@ -uniform highp float qt_Opacity; -uniform mediump float relativeSizeX; -uniform mediump float relativeSizeY; -uniform mediump float spread; -uniform lowp vec4 color; -varying highp vec2 qt_TexCoord0; +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only -highp float linearstep(highp float e0, highp float e1, highp float x) { +#version 440 + +layout(location = 0) in vec2 qt_TexCoord0; +layout(location = 0) out vec4 fragColor; + +layout(std140, binding = 0) uniform buf { + mat4 qt_Matrix; + float qt_Opacity; + float relativeSizeX; + float relativeSizeY; + float spread; + vec4 color; +} ubuf; + +float linearstep(float e0, float e1, float x) +{ return clamp((x - e0) / (e1 - e0), 0.0, 1.0); } -void main() { - lowp float alpha = - smoothstep(0.0, relativeSizeX, 0.5 - abs(0.5 - qt_TexCoord0.x)) * - smoothstep(0.0, relativeSizeY, 0.5 - abs(0.5 - qt_TexCoord0.y)); +void main() +{ + float alpha = + smoothstep(0.0, ubuf.relativeSizeX, 0.5 - abs(0.5 - qt_TexCoord0.x)) * + smoothstep(0.0, ubuf.relativeSizeY, 0.5 - abs(0.5 - qt_TexCoord0.y)); - highp float spreadMultiplier = linearstep(spread, 1.0 - spread, alpha); - gl_FragColor = color * qt_Opacity * spreadMultiplier * spreadMultiplier; + float spreadMultiplier = linearstep(ubuf.spread, 1.0 - ubuf.spread, alpha); + fragColor = ubuf.color * ubuf.qt_Opacity * spreadMultiplier * spreadMultiplier; } diff --git a/src/quickcontrols/material/shaders/RectangularGlow_rhi.frag b/src/quickcontrols/material/shaders/RectangularGlow_rhi.frag deleted file mode 100644 index 3e7d2dfe6f..0000000000 --- a/src/quickcontrols/material/shaders/RectangularGlow_rhi.frag +++ /dev/null @@ -1,28 +0,0 @@ -#version 440 - -layout(location = 0) in vec2 qt_TexCoord0; -layout(location = 0) out vec4 fragColor; - -layout(std140, binding = 0) uniform buf { - mat4 qt_Matrix; - float qt_Opacity; - float relativeSizeX; - float relativeSizeY; - float spread; - vec4 color; -} ubuf; - -float linearstep(float e0, float e1, float x) -{ - return clamp((x - e0) / (e1 - e0), 0.0, 1.0); -} - -void main() -{ - float alpha = - smoothstep(0.0, ubuf.relativeSizeX, 0.5 - abs(0.5 - qt_TexCoord0.x)) * - smoothstep(0.0, ubuf.relativeSizeY, 0.5 - abs(0.5 - qt_TexCoord0.y)); - - float spreadMultiplier = linearstep(ubuf.spread, 1.0 - ubuf.spread, alpha); - fragColor = ubuf.color * ubuf.qt_Opacity * spreadMultiplier * spreadMultiplier; -} diff --git a/src/quickcontrols/material/shaders/compile.bat b/src/quickcontrols/material/shaders/compile.bat deleted file mode 100644 index e0ccbb7ed5..0000000000 --- a/src/quickcontrols/material/shaders/compile.bat +++ /dev/null @@ -1,4 +0,0 @@ -:: Copyright (C) 2019 The Qt Company Ltd. -:: SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only - -qsb --glsl "150,120,100 es" --hlsl 50 --msl 12 -o +qsb/RectangularGlow.frag RectangularGlow_rhi.frag |