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!