Lucas Terrier
Lucas Terrier

Reputation: 13

Change shape of a custom QtQuick Control template

I'm currently writing a custom style module for my QML application (using Qt 6.6.1). I don't want to reinvent the wheel and rewrite the whole backend of basic controls, so I'm using the QtQuick Control template method, as decribed here.

My problem is that I want to create a SpinBox with rounded corner, like on the following design :

SpinBox design

I achieve to round the corners of the text field by applying a radius to the background property's Rectangle. But I find hard to round the corner of the + and - buttons.

SpinBox generated by qml

My first idea was to have a kind of parent rounded Rectangle with clip: true, but I can't do this with the Quick Control Template method. The root Item must be T.SpinBox, and I cannot set another parent to sub-elements properties like background, up.indicator, down.indicator and contentItem

Here is my current code :

import QtQuick
import QtQuick.Controls.impl
import QtQuick.Templates as T


T.SpinBox {
    id: control

    implicitWidth: background.implicitWidth
                   + up.indicator.width
                   + leftPadding
                   + rightPadding

    implicitHeight: background.implicitHeight
                    + topPadding
                    + bottomPadding

    leftPadding: padding
                 + (control.mirrored ?
                        (up.indicator ?
                            up.indicator.width
                            : 0
                        )
                        : (down.indicator ?
                            down.indicator.width
                            : 0
                        )
                    )

    rightPadding: padding
                  + (control.mirrored ?
                         (down.indicator ?
                            down.indicator.width
                            : 0)
                        : (up.indicator ?
                            up.indicator.width
                            : 0
                        )
                    )

    wheelEnabled: true
    editable: true
    font: Theme.fonts.controlText

    validator: IntValidator {
        locale: control.locale.name
        bottom: Math.min(control.from, control.to)
        top: Math.max(control.from, control.to)
    }

    contentItem: TextInput {
        id: textField
        z: 2
        text: control.displayText
        clip: width < implicitWidth

        font: control.font
        color: Theme.colors.textDisabled
        selectionColor: control.palette.highlight
        selectedTextColor: control.palette.highlightedText
        horizontalAlignment: Qt.AlignHCenter
        verticalAlignment: Qt.AlignVCenter
        anchors.left: parent.left
        anchors.right: up.indicator.left
        anchors.top: parent.top
        anchors.bottom: parent.bottom

        readOnly: !control.editable
        validator: control.validator
        inputMethodHints: control.inputMethodHints
    }

    up.indicator: Rectangle {
        id: upIndicator
        implicitWidth: 12
        implicitHeight: 9
        height: parent.height / 2
        anchors.right: parent.mirrored ? undefined : parent.right
        anchors.left: parent.mirrored ? parent.left : undefined
        anchors.top: parent.top
        color: "red"
    }

    down.indicator: Rectangle {
        id: downIndicator
        implicitWidth: 12
        implicitHeight: 9
        height: parent.height / 2
        color: "blue"

        anchors.right: parent.mirrored ? undefined : parent.right
        anchors.left: parent.mirrored ? parent.left : undefined
        anchors.bottom: parent.bottom
    }

    background: Rectangle {
        id: backgroundRectangle
        implicitWidth: 30
        implicitHeight: 18
        color: "green"
        border.width: 0
        radius: 2
    }
}

I've also tried to set Items to SpinBox fields properties, from Items instancied elsewhere (in a clipping Rectangle for example, but it didn't cropped the items :

//imports

T.SpinBox {

    //... some size management

    contentItem: textField
    up.indicator: upIndicator
    down.indicator: downIndicator
    background: backgroundRectangle

    Rectangle {
        id: clippingRectangle
        anchors.fill: parent
        radius: 2
        clip: true

        TextInput {
            id: textField
            //...
        }

        Rectangle {
            id: upIndicator
            //...
        }

        Rectangle {
            id: downIndicator
            //...
        }

        Rectangle {
            id: backgroundRectangle
            //...
        }
    }
}

I've also tried to work with Layers and MultiEffect to put a mask on different items, without success again

import QtQuick.Effects

T.SpinBox {

    //... Size management + assign different fields as in first exemple

    Rectangle {
        id: maskRectangle
        color: "transparent"
        radius: 2
        anchors.fill: control
        border.width: 1
        border.color: Theme.colors.widgetBorder
        z: 3
    }

    layer.enabled: true
    MultiEffect {                  // Mask whole control -> doesn't works !
        source: control
        anchors.fill: control
        maskEnabled: true
        maskSource: maskRectangle
    }

    MultiEffect {                  // Mask up and down indicator items -> doesn't works !
        source: upIndicator
        anchors.fill: upIndicator
        maskEnabled: true
        maskSource: maskRectangle
    }
}

I'm out of ideas to achive this design. I would prefer to don't rewrite a full custom spinBox from scratch. I like the way that the custom style works, and can adapt controls depending on application stye. Does anyone have any idea or clues to help me ?

Upvotes: 0

Views: 118

Answers (1)

Lucas Terrier
Lucas Terrier

Reputation: 13

I achieved to do this according to @JarmMan's suggestion.

I preferred to use several Rectangles because it's easier to maintain and understand than Shapes.

Here is the resulting code :

import QtQuick
import QtQuick.Controls.impl
import QtQuick.Shapes
import QtQuick.Templates as T

import MyTheme

T.SpinBox {
    id: control

    implicitWidth: background.implicitWidth + up.indicator.width + leftPadding + rightPadding
    implicitHeight: background.implicitHeight + topPadding + bottomPadding

    wheelEnabled: true
    editable: true
    font: Theme.fonts.controlText
    clip: true

    validator: IntValidator {
        locale: control.locale.name
        bottom: Math.min(control.from, control.to)
        top: Math.max(control.from, control.to)
    }

    contentItem: TextInput {
        id: textField

        font: control.font
        color: Theme.colors.textDisabled
        text: control.displayText
        selectionColor: control.palette.highlight
        selectedTextColor: control.palette.highlightedText
        horizontalAlignment: Qt.AlignHCenter
        verticalAlignment: Qt.AlignVCenter
        anchors.left: parent.left
        anchors.right: upIndicator.left
        anchors.top: parent.top
        anchors.bottom: parent.bottom

        readOnly: !control.editable
        validator: control.validator
        inputMethodHints: control.inputMethodHints
    }

    up.indicator: Rectangle {
        id: upIndicator
        implicitWidth: 12
        implicitHeight: 9
        height: control.height / 2
        anchors.right: control.right
        anchors.top: control.top
        color: "transparent"
        radius: 2

        Rectangle {
            height: parent.radius
            anchors.bottom: parent.bottom
            anchors.left: parent.left
            anchors.right: parent.right
            color: parent.color
            border.width: 0
        }

        Rectangle {
            width: parent.radius
            anchors.left: parent.left
            anchors.top: parent.top
            anchors.bottom: parent.bottom
            color: parent.color
            border.width: 0
        }

        Image {
            source: "qrc:/qt/qml/MyTheme/Images/SpinBox-Up.svg"
            sourceSize.height: 3
            sourceSize.width: 6
            fillMode: Image.PreserveAspectFit
            anchors.centerIn: upIndicator
        }
    }

    down.indicator: Rectangle {
        id: downIndicator
        implicitWidth: 12
        implicitHeight: 9
        height: control.height / 2
        color: "transparent"
        radius: 2

        anchors.right: control.right
        anchors.bottom: control.bottom

        Rectangle {
            height: parent.radius
            anchors.top: parent.top
            anchors.left: parent.left
            anchors.right: parent.right
            color: parent.color
            border.width: 0
        }

        Rectangle {
            width: parent.radius
            anchors.left: parent.left
            anchors.top: parent.top
            anchors.bottom: parent.bottom
            color: parent.color
            border.width: 0
        }

        Image {
            source: "qrc:/qt/qml/MyTheme/Images/SpinBox-Down.svg"
            sourceSize.height: 3
            sourceSize.width: 6
            fillMode: Image.PreserveAspectFit
            anchors.centerIn: downIndicator
        }
    }

    background: Rectangle {
        id: backgroundRectangle
        implicitWidth: 30
        implicitHeight: 18
        color: "transparent"
        border {
            width: 1
            color: Theme.colors.widgetBorder
        }
        radius: 2

        Shape {
            anchors.fill: parent

            ShapePath {
                startX: upIndicator.x
                startY: 0
                PathLine {
                    relativeX: 0
                    relativeY: backgroundRectangle.height
                }

                strokeColor: Theme.colors.widgetBorder
                strokeWidth: 1
                fillColor: "transparent"

            }

            ShapePath {
                startX: upIndicator.x
                startY: control.height / 2
                PathLine {
                    relativeX : upIndicator.width
                    relativeY: 0
                }

                strokeColor: Theme.colors.widgetBorder
                strokeWidth: 1
                fillColor: "transparent"

            }
        }
    }
}

Upvotes: 0

Related Questions