Home > OS >  An optimal way to highlight a circle segment in QML?
An optimal way to highlight a circle segment in QML?

Time:11-10

I'm very new to QML and I want to make a basic application that consists of a segmented circle with 20-30 segments (pizza slices) and a counter. The number on the counter is the number of segment being highlighted. I found a few ways to make segmented circles in other questions but unfortunately none of them seem to work for my assignment.

The only way I see making it right now is by redrwing all the segments every time the counter is changed, and changing the color of the needed segment. So is there an optimal way to implement this?

CodePudding user response:

To reduce the complexity, let's work through a simplified version of the problem:

  • Assume there are 6 pieces
  • Assume we want to draw piece 2
  • Assume we want to fit it in a 300x300 rectangle

Here's the math:

  • Each piece will occupy 60 degrees (i.e. 360 / 6)
  • Piece 2 will occupy angles from 120 to 180

To render the piece the drawing will be:

  • From the center point (150, 150)
  • Then (150 150 * cos(120), 150 150 * sin(120))
  • Then (150 150 * cos(180), 150 150 * sin(180))
  • Then back to the center point (150, 150)

Instead of a straight line, we want to draw a curve line between points 2 and points 3.

To render this, we can use Shape, ShapePath, PathLine, and PathArc.

To generalize, we can replace 6 with 20 and generalize all formulas accordingly. To draw 20 piece slices, we can make use of a Repeater, e.g.

    Repeater {
        model: 20
        PizzaPiece {
            piece: index
        }
    }

To polish it off, I added a Slider so you can interactively change the number of pieces you want from 0-20 and set the color to "orange", otherwise it will be a light yellow "#ffe".

    Repeater {
        model: 20
        PizzaPiece {
            piece: index
            fillColor: index < slider.value ? "orange" : "#ffe"
        }
    }
    Slider {
        id: slider
        from: 0
        to: 20
        stepSize: 1
    }

As an extra bonus, I added a TapHandler so that each piece is clickable. If you leave the mouse pressed down, the piece will appear "red" until you release the mouse.

import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
Page {
    id: page
    property int pieces: 20
    Rectangle {
        anchors.centerIn: parent
        width: 300
        height: 300
        border.color: "grey"
        Repeater {
            model: pieces
            PizzaPiece {
                anchors.fill: parent
                anchors.margins: 10
                pieces: page.pieces
                piece: index
                fillColor: pressed ? "red" : index < slider.value ? "orange" : "#ffe"
                onClicked: {
                    slider.value = index   1;
                }
            }
        }
    }
    footer: Frame {
        RowLayout {
            width: parent.width
            Label {
                text: slider.value
            }
            Slider {
                id: slider
                Layout.fillWidth: true
                from: 0
                to: pieces
                value: 3
                stepSize: 1
            }
        }
    }
}

//PizzaPiece.qml
import QtQuick
import QtQuick.Shapes

Shape {
    id: pizzaPiece
    property int pieces: 20
    property int piece: 0
    property real from: piece * (360 / pieces)
    property real to: (piece   1) * (360 / pieces)
    property real centerX: width / 2
    property real centerY: height / 2
    property alias fillColor: shapePath.fillColor
    property alias strokeColor: shapePath.strokeColor
    property alias pressed: tapHandler.pressed
    property real fromX: centerX   centerX * Math.cos(from * Math.PI / 180)
    property real fromY: centerY   centerY * Math.sin(from * Math.PI / 180)
    property real toX: centerX   centerX * Math.cos(to * Math.PI / 180)
    property real toY: centerY   centerY * Math.sin(to * Math.PI / 180)
    signal clicked()
    containsMode: Shape.FillContains
    ShapePath {
        id: shapePath
        fillColor: "#ffe"
        strokeColor: "grey"
        startX: centerX; startY: centerY
        PathLine { x: fromX; y: fromY }
        PathArc {
            radiusX: centerX; radiusY: centerY
            x: toX; y: toY
        }
        PathLine { x: centerX; y: centerY }
    }
    TapHandler {
        id: tapHandler
        onTapped: pizzaPiece.clicked()
    }
}

You can Try it Online!

  • Related