I recognized in my example a small gap between the repeated objects about one pixel more or less. Depending on the property int size this gap might change position. I also tested with property double size but this seem not changing the effect.
// FILE main.qml
import QtQuick 2.11
import QtQuick.Controls 2.4
Rectangle {
width: 640
height: 320
BarrierTape {}
}
// File BarrierTape.qml
import QtQuick 2.11
import QtQuick.Layouts 1.11
Item {
id: barrierTape
property int size: 100
x:200
y:100
ColumnLayout {
spacing: barrierTape.size * Math.sin(Math.PI/3)
Repeater {
model: 2
Rectangle {
width: barrierTape.size
height: barrierTape.size * Math.sin(Math.PI/3)
color: "#ffd100"
}
}
}
ColumnLayout {
x: -barrierTape.size*3/4
y: +barrierTape.size/2 * Math.sin(Math.PI/3)
spacing: barrierTape.size * Math.sin(Math.PI/3)
Repeater {
model: 2
Rectangle {
width: barrierTape.size
height: barrierTape.size * Math.sin(Math.PI/3)
color: "#ffd100"
}
}
}
ColumnLayout {
x: +barrierTape.size*3/4
y: -barrierTape.size/2 * Math.sin(Math.PI/3)
spacing: barrierTape.size * Math.sin(Math.PI/3)
Repeater {
model: 2
Rectangle {
width: barrierTape.size
height: barrierTape.size * Math.sin(Math.PI/3)
color: "#ffd100"
}
}
}
ColumnLayout {
spacing: barrierTape.size * Math.sin(Math.PI/3)
y: +barrierTape.size * Math.sin(Math.PI/3)
Repeater {
model: 2
Rectangle {
width: barrierTape.size
height: barrierTape.size * Math.sin(Math.PI/3)
color: "#222222"
}
}
}
ColumnLayout {
x: -barrierTape.size * 3/4
y: +barrierTape.size * 3/2 * Math.sin(Math.PI/3)
spacing: barrierTape.size* Math.sin(Math.PI/3)
Repeater {
model: 2
Rectangle {
width: barrierTape.size
height: barrierTape.size * Math.sin(Math.PI/3)
color: "#222222"
}
}
}
ColumnLayout {
x: +barrierTape.size * 3/4
y: +barrierTape.size * 1/2 * Math.sin(Math.PI/3)
spacing: barrierTape.size* Math.sin(Math.PI/3)
Repeater {
model: 2
Rectangle {
width: barrierTape.size
height: barrierTape.size * Math.sin(Math.PI/3)
color: "#222222"
}
}
}
}
Further the code have a lot of Boilerplate and I don't know how to achieve the same behavior with less. I thought that creating first an item with all six rectangles and than repeating them would help, but if I put Repeater in one ColumnLayout seems to ignoring the x and y values. I struggle to refactor this file.
EDIT 1:
This is a sample of what it kind of should look like:
This uses a slightly complicated Hexagon component which I did a while ago.
main.qml
import QtQuick
Window {
id: root
width: 640
height: 640
visible: true
title: qsTr("Hello World")
color: "#444444"
property color color1: "#ffd100"
property color color2: "#222222"
property int hexRadius: 50
property real hexInnerRadius: (Math.sqrt(3) / 2) * root.hexRadius
property int spacing: 2
component BarrierTape : Column {
spacing: root.spacing
Repeater {
model: 4
Item {
width: root.hexRadius * 1.5
height: 2 * root.hexInnerRadius
Hexagon {
anchors.centerIn: parent
width: 2 * root.hexRadius
height: 2 * root.hexRadius
radius: 10
fillColor: (index % 2 == 0) ? root.color1 : root.color2
antialiasing: true
}
}
}
}
Row {
x: 100
y: 150
spacing: root.spacing
Repeater {
model: 4
BarrierTape {
y: index * -root.hexInnerRadius
}
}
}
}
Hexagon.qml
import QtQuick
import QtQuick.Shapes
Shape {
id: root
width: 200
height: 200
property int radius: 10
property alias fillColor: path.fillColor
layer.enabled: root.antialiasing
layer.smooth: root.antialiasing
layer.samples: root.antialiasing ? 4 : 0
ShapePath {
id: path
joinStyle: ShapePath.MiterJoin
strokeWidth: 0
strokeColor: "transparent"
startX: 0
startY: 0
}
onRadiusChanged: {
// Only construct polygon if radius changed from 0 to 1 or vice versa.
if ((root.radius + root.__previousRadius) === 1)
root.constructPolygon()
root.__previousRadius = root.radius
}
Component.onCompleted: root.constructPolygon()
property real __centerX: root.width / 2
property real __centerY: root.height / 2
property real __radius: Math.min(root.width, root.height) / 2
property int __previousRadius: root.radius
property int minRadius: 0
property int maxRadius: root.__radius * Math.cos(root.toRadians(180.0 / 6))
property int __actualRadius: Math.max(root.minRadius, Math.min(root.maxRadius, root.radius))
function constructPolygon() {
root.clearPathElements()
if (root.radius === 0)
root.constructNonRoundedPolygonPath()
else
root.constructRoundedPolygonPath()
}
function toRadians(degrees) {
return degrees * (Math.PI / 180.0)
}
function constructNonRoundedPolygonPath() {
for (var cornerNumber = 0; cornerNumber < 6; cornerNumber++) {
let angleToCorner = root.toRadians(cornerNumber * (360.0 / 6))
if (cornerNumber === 0) {
path.startX = Qt.binding(function() {
return root.__centerX + root.__radius * Math.cos(0)
})
path.startY = Qt.binding(function() {
return root.__centerY + root.__radius * Math.sin(0)
})
} else {
let pathLine = Qt.createQmlObject('import QtQuick 2.15; PathLine {}', path)
pathLine.x = Qt.binding(function() {
return root.__centerX + root.__radius * Math.cos(angleToCorner)
})
pathLine.y = Qt.binding(function() {
return root.__centerY + root.__radius * Math.sin(angleToCorner)
})
path.pathElements.push(pathLine)
}
}
// Close the polygon
var pathLineClose = Qt.createQmlObject('import QtQuick 2.15; PathLine {}', path)
pathLineClose.x = Qt.binding(function() { return path.startX } )
pathLineClose.y = Qt.binding(function() { return path.startY } )
path.pathElements.push(pathLineClose)
}
property real __halfInteriorCornerAngle: 90 - (180.0 / 6)
property real __halfCornerArcSweepAngle: 90 - root.__halfInteriorCornerAngle
property real __distanceToCornerArcCenter: root.__radius - root.__actualRadius /
Math.sin(root.toRadians(root.__halfInteriorCornerAngle))
function constructRoundedPolygonPath() {
for (var cornerNumber = 0; cornerNumber < 6; cornerNumber++) {
let angleToCorner = cornerNumber * (360.0 / 6)
let pathArc = Qt.createQmlObject('import QtQuick 2.15; PathArc {
property real centerX;
property real centerY }', path)
pathArc.centerX = Qt.binding(function() {
return root.__centerX + root.__distanceToCornerArcCenter
* Math.cos(root.toRadians(angleToCorner))
})
pathArc.centerY = Qt.binding(function() {
return root.__centerY + root.__distanceToCornerArcCenter
* Math.sin(root.toRadians(angleToCorner))
})
pathArc.x = Qt.binding(function() {
return pathArc.centerX + root.__actualRadius
* (Math.cos(root.toRadians(angleToCorner + root.__halfCornerArcSweepAngle)))
})
pathArc.y = Qt.binding(function() {
return pathArc.centerY + root.__actualRadius
* (Math.sin(root.toRadians(angleToCorner + root.__halfCornerArcSweepAngle)))
})
pathArc.radiusX = Qt.binding(function() { return root.__actualRadius })
pathArc.radiusY = Qt.binding(function() { return root.__actualRadius })
if (cornerNumber === 0) {
path.startX = Qt.binding(function() {
return pathArc.centerX + root.__actualRadius
* (Math.cos(root.toRadians(angleToCorner - root.__halfCornerArcSweepAngle)))
})
path.startY = Qt.binding(function() {
return pathArc.centerY + root.__actualRadius
* (Math.sin(root.toRadians(angleToCorner - root.__halfCornerArcSweepAngle)))
})
} else {
let pathLine = Qt.createQmlObject('import QtQuick 2.15; PathLine {}', path)
pathLine.x = Qt.binding(function() {
return pathArc.centerX + root.__actualRadius
* (Math.cos(root.toRadians(angleToCorner - root.__halfCornerArcSweepAngle)))
})
pathLine.y = Qt.binding(function() {
return pathArc.centerY + root.__actualRadius
* (Math.sin(root.toRadians(angleToCorner - root.__halfCornerArcSweepAngle)))
})
path.pathElements.push(pathLine)
}
path.pathElements.push(pathArc)
}
// Close the polygon
var pathLineClose = Qt.createQmlObject('import QtQuick 2.15; PathLine {}', path)
pathLineClose.x = Qt.binding(function() { return path.startX} )
pathLineClose.y = Qt.binding(function() { return path.startY} )
path.pathElements.push(pathLineClose)
}
function clearPathElements() {
for (var i = 0; i !== path.pathElements.length; ++i)
path.pathElements[i].destroy()
path.pathElements = []
}
}
I have been having great success at using SVG and a Button hack for rendering SVGs in different colors:
import QtQuick 2.15
import QtQuick.Controls 2.15
Page {
background: Rectangle { color: "#444444" }
Hex { x: 87.5; y: 150; color: "#ffd100" }
Hex { x: 175; y: 100; color: "#ffd100" }
Hex { x: 262.5; y: 50; color: "#ffd100" }
Hex { x: 87.5; y: 450; color: "#222222" }
Hex { x: 175; y: 400; color: "#222222" }
Hex { x: 262.5; y: 350; color: "#222222" }
Hex { x: 87.5; y: 350; color: "#ffd100" }
Hex { x: 175; y: 300; color: "#ffd100" }
Hex { x: 262.5; y: 250; color: "#ffd100" }
Hex { x: 87.5; y: 250; color: "#222222" }
Hex { x: 175; y: 200; color: "#222222" }
Hex { x: 262.5; y: 150; color: "#222222" }
}
//Hex.qml
import QtQuick 2.15
import QtQuick.Controls 2.15
Item {
property alias color: btn.icon.color
Item {
anchors.centerIn: parent
width: 200
height: 200
Button {
id: btn
background: Item { }
icon.color: "#ffd100"
icon.height: parent.height
icon.width: parent.width
icon.source: "Hex.svg"
}
}
}
//Hex.svg
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-200 -200 400 400">
<path stroke="black" stroke-width="25" stroke-linejoin="round" d="M 100 0 L 50 86.6 L -50 86.6 L -100 0 L -50 -86.6 L 50 -86.6 z" />
</svg>
You can Try it Online!
Alternative to the Button icon.source, another way to recolor a SVG is to make use of data-uri and recolor the SVG by altering the color definitions within the SVG itself.
The following uses the Image component so it needs to set sourceSize: Qt.size(width, height) to ensure that the SVG is upscaled property. The color property is pushed directly into the data-uri so that it changes both stroke and fill properties:
//Hex.qml
import QtQuick 2.15
import QtQuick.Controls 2.15
Image {
width: 110
height: 110
property color color: "orange"
sourceSize: Qt.size(width, height)
source: `data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="-115 -115 230 230">
<path stroke="%1" stroke-width="25" stroke-linejoin="round" fill="%1" d="M 100 0 L 50 86.6 L -50 86.6 L -100 0 L -50 -86.6 L 50 -86.6 z" />
</svg>
`.arg(color)
}
The idea of #iam_peter leaded me to the correct implementation. I accepted his answer, since it was based on my question the most accurate approach without an svg.
To complete this journey I provide my at the moment final solution:
import QtQuick 2.11
import QtQuick.Controls 2.4
import QtQuick.Layouts 1.11
import "Components"
Rectangle {
width: 640
height: 320
color: "#444444"
BarrierTape {}
}
//BarrierTape.qml
import QtQuick 2.11
import QtQuick.Layouts 1.11
Item {
id: root
property color color1: "#ffd100"
property color color2: "#222222"
property real hexRadius: 100
property real hexInnerRadius: (Math.sqrt(3)/2) * root.hexRadius
component BarrierTapeRow: Row{
id: hexRow
property color rowColor: "pink"
spacing: -1/4 * root.hexRadius
Repeater {
model: 4
Hexagon_svg {
y: index * -root.hexInnerRadius/2
radius: root.hexRadius
color: hexRow.rowColor
}
}
}
Column {
x:100
y:150
spacing: 0
Repeater {
model: 5
BarrierTapeRow {
rowColor: (index % 2 == 0) ? root.color1 : root.color2
}
}
}
}
//Hexagon_svg.qml
import QtQuick 2.11
import QtGraphicalEffects 1.12
import QtQuick.Controls 2.4
Item {
id: root
property color color:"yellow"
property real radius: 30
height: Math.sqrt(3)/2 * root.radius
width: root.radius
Image {
anchors.centerIn: parent
id: hexagonSvg
source: Qt.resolvedUrl("Hex.svg")
sourceSize.width: root.radius
antialiasing: true
visible: false
}
ColorOverlay {
anchors.fill: hexagonSvg
source: hexagonSvg
color: root.color
antialiasing: true
}
}
//Hex.svg
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-200 -200 400 400">
<path stroke="black" stroke-width="25" stroke-linejoin="round" d="M 100 0 L 50 86.6 L -50 86.6 L -100 0 L -50 -86.6 L 50 -86.6 z" />
</svg>
Sadly I could not tested in the qml6online editor, because of the missing Package QtGraphicalEffects.
Result:
Related
I'm trying to create a reusable slider. I'm having trouble to set implicit sizes correctly so that the CustomSlider includes the Slider and the Label. I would like to have a implicit size specified, but let the user set a width for the slider itself.
I tried using childrenRect but that gives me a binding loop error.
How can I have the yellow background span across all the components: the slider and the green label?
Currently:
Would like:
CustomSlider.qml
Item {
id: root
property int startval: 0
property int endval: 20
property int sliderWidth: 200
// This results in binding loop
//implicitHeight: childrenRect.height
implicitHeight: control.height + label.height
implicitWidth: sliderWidth
Rectangle {
color: "yellow"
width: root.width
height: root.height
}
Slider {
id: control
stepSize: 1
anchors.centerIn: parent
snapMode: Slider.SnapOnRelease
width: root.sliderWidth
from: root.startval
to: root.endval
handle: Rectangle {
id: handleId
x: control.visualPosition * (control.width - width)
y: (control.height - height) / 2
width: 20
height: 20
radius: 20
color: "gray"
}
background: Rectangle {
y: (control.height - height) / 2
height: 4
radius: 2
color: "green"
Rectangle {
width: control.visualPosition * parent.width
height: parent.height
color: "red"
radius: 2
}
}
}
Label {
id: label
width: 20
height: 20
text: control.value
font.pixelSize: 15
color: "black"
x: handleId.x + control.x
y: handleId.y - 20
Rectangle {
color: "green"
anchors.fill: parent
opacity: 0.3
}
}
}
Main.qml
CustomSlider {
anchors.centerIn: parent
startval: 0
endval: 10
//sliderWidth: 100
}
I usually set the Width and height based on the parent.
I set sliderWidth: parent.width/2.0 and for your green label I add TextMetrics and calculate its width based on the text that it wants to show.
import QtQuick 2.15
import QtQuick.Controls 2.15
Item {
id: root
property int startval: 0
property int endval: 0
property int sliderWidth: parent.width/2.0
// This results in binding loop
//implicitHeight: childrenRect.height
implicitHeight: control.height + label.height
implicitWidth: sliderWidth
Rectangle {
color: "yellow"
width: root.width
height: root.height
}
Slider {
id: control
stepSize: 1
anchors.centerIn: parent
snapMode: Slider.SnapOnRelease
width: root.sliderWidth
from: root.startval
to: root.endval
handle: Rectangle {
id: handleId
x: control.visualPosition * (control.width - width)
y: (control.height - height) / 2
width: 20
height: 20
radius: 20
color: "gray"
}
background: Rectangle {
y: (control.height - height) / 2
height: 4
radius: 2
color: "green"
Rectangle {
width: control.visualPosition * parent.width
height: parent.height
color: "red"
radius: 2
}
}
}
Label {
id: label
width: t_metrics.tightBoundingRect.width +4
height: t_metrics.tightBoundingRect.height +7
text: control.value
font.pixelSize: 15
color: "black"
x: handleId.x + control.x
y: handleId.y - 20
Rectangle {
color: "green"
anchors.fill: parent
opacity: 0.3
}
}
TextMetrics {
id: t_metrics
text: control.value.toString()
}
}
Updated:
in CustomSlider.qml , changed it to this code:
import QtQuick 2.15
import QtQuick.Controls 2.15
Item {
id: root
property int startval: 0
property int endval: 0
property int sliderWidth: parent.width/2.0
implicitHeight: control.height + label.height
implicitWidth: sliderWidth
Rectangle {
color: "yellow"
width: root.width +10
height: root.height +control.height + label.height + t_metrics.tightBoundingRect.height +7
Slider {
id: control
stepSize: 1
anchors.centerIn: parent
snapMode: Slider.SnapOnRelease
width: root.sliderWidth
from: root.startval
to: root.endval
handle: Rectangle {
id: handleId
x: control.visualPosition * (control.width - width)
y: (control.height - height) / 2
width: 20
height: 20
radius: 20
color: "gray"
}
background: Rectangle {
y: (control.height - height) / 2
height: 4
radius: 2
color: "green"
Rectangle {
width: control.visualPosition * parent.width
height: parent.height
color: "red"
radius: 2
}
}
}
Label {
id: label
width: t_metrics.tightBoundingRect.width +4
height: t_metrics.tightBoundingRect.height +7
text: control.value
font.pixelSize: 15
color: "black"
x: handleId.x + control.x
y: handleId.y /2 + 4
Rectangle {
color: "green"
anchors.fill: parent
opacity: 0.3
}
}
TextMetrics {
id: t_metrics
text: control.value.toString()
}
}
}
This makes your text label shows inside the yellow rectangle and in main.qml I add one Row with labels and spinboxes for the test.
If you want that user to set sliderWidth or startval and endval and then show him Slider you need to create that object dynamically.
and if you want to be displayed in the Column and don't overlap you need ColumnLayout.
If you want to scroll them then you need to use ScrollView.
import QtQuick.Window 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
Window {
width: 660
height: 480
visible: true
title: qsTr("Hello World")
ScrollView {
width: parent.width
height : parent.height
contentWidth: slidersColumn.width
contentHeight: slidersColumn.height
clip : true
ColumnLayout {
id: slidersColumn
anchors.fill: parent
spacing:50
}
}
function addSlider(sliderWidth,startval,endval) {
var obj = Qt.createComponent("CustomSlider.qml");
var slider = obj.createObject(slidersColumn);
slider.startval=startval;
slider.endval=endval;
slider.sliderWidth=sliderWidth;
}
Row
{
x: 0
y: 5
width: parent.width
height: 30
Label {
id: lbl_slider_width
text: qsTr(" Slider width ")
}
SpinBox {
id: spinBox_slider_width
editable: true
from:0
to:parent.width
}
Label {
id: lbl_startval
text: qsTr(" Startval ")
}
SpinBox {
id: spinBox_startval
editable: true
from:0
to:parent.width
}
Label {
id: lbl_endval
text: qsTr(" Endval ")
}
SpinBox {
id: spinBox_endval
editable: true
from:0
to:parent.width
}
Button {
id: button
text: qsTr(" Create Slider")
onClicked:
{
addSlider(spinBox_slider_width.value,spinBox_startval.value,spinBox_endval.value)
}
}
}
}
The result is:
I'm using QtQuick/QML and I want to create a ripple effect when I click on a button. I do know that this is available in Material Style, but I think it's an inherent property when you change the theme and I don't want to change anything else in my project.
Is there a way to add ONLY the ripple effect onto my button, and change nothing else? If so, how do I do it?
As Kostia Hvorov said, QtQuick.Controls.Material.impl.Ripple is the easiest way to go.
I would like to add my trick to handle rectangular background with radius:
import QtQuick 2.12
import QtQuick.Controls 2.12
import QtQuick.Controls.Material.impl 2.12
import QtGraphicalEffects 1.12
Column
{
spacing: 20
Button
{
anchors.horizontalCenter: parent.horizontalCenter
id: button
text: "ripple demo"
}
Ripple {
id: ripple
anchors.horizontalCenter: parent.horizontalCenter
clipRadius: 4
width: 200
height: 64
pressed: button.pressed
active: button.down || button.visualFocus || button.hovered
color: "#20FFFFFF"
layer.enabled: true
layer.effect: OpacityMask {
maskSource: Rectangle
{
width: ripple.width
height: ripple.height
radius: 4
}
}
}
}
Try it Online
Easiest way to do it - using Ripple from QtQuick.Controls.Material.impl
So just add Ripple to your background Rect:
Ripple {
clipRadius: height
width: parent.width
height: parent.height
pressed: control.pressed
anchor: control
active: control.down || control.visualFocus || control.hovered
color: control.flat && control.highlighted ? control.Material.highlightedRippleColor : control.Material.rippleColor
}
You can replace "control.Material.rippleColor" or/and "control.Material.highlightedRippleColor" to any color and get any ripple color effect.
But there is one problem, it will work only with rectangular background(without round) otherwise it will be looking bad.
I have made this with some PropertyAnimation. Here is how:
import QtQuick 2.15
import QtQuick.Controls 2.15
Button {
id: control
opacity: enabled ? 1.0 : 0.2
property int tripleWidth: width * 3
background: Rectangle {
border.width: 1
border.color: "black"
radius: 3
color: "white"
clip: true
Rectangle {
id: ripple
property int diameter: 0
property int pressX: 0
property int pressY: 0
x: pressX - radius
y: pressY - radius
color: "green"
radius: diameter / 2
width: diameter
height: diameter
opacity: 1 - diameter / control.tripleWidth
function animate(x, y, size) {
pressX = x
pressY = y
diameter = size
}
Behavior on diameter {
PropertyAnimation {
duration: 200
onRunningChanged: {
if(!running) {
duration = 0;
ripple.diameter = 0;
duration = 200;
}
}
}
}
}
}
onClicked: {
ripple.animate(pressX, pressY, control.tripleWidth)
}
contentItem: Item {
implicitWidth: txt.implicitWidth
implicitHeight: 20
Text {
id: txt
anchors.centerIn: parent
text: control.text
}
}
}
I Edit last Answer and its work.. Here is How:
import QtQuick 2.5
import QtQuick.Window 2.5
import QtQuick.Controls 2.5
RoundButton {
id: control
width: 93
height: 39
property int tripleWidth: width * 3
background: Rectangle {
border.width: 1
border.color: "black"
radius: 3
color: "white"
clip: true
Rectangle {
id: ripple
property int diameter: 0
property int pressX: 0
property int pressY: 0
x: pressX - radius
y: pressY - radius
color: "green"
radius: diameter / 2
width: diameter
height: diameter
opacity: 1
function animate(x, y, size) {
pressX = x
pressY = y
diameter = size
}
Behavior on diameter {
PropertyAnimation {
duration: 300
}
}
}
}
onHoveredChanged: {
ripple.opacity = 0
ripple.diameter = 0
}
onPressed: {
ripple.opacity = 0.8
ripple.animate(pressX, pressY, control.tripleWidth)
}
Timer {
id: timer
}
contentItem: Item {
implicitWidth: txt.implicitWidth
implicitHeight: 20
Text {
id: txt
font.pointSize: 15
anchors.centerIn: parent
}
}
onClicked: {
function delay(delayTime, cb) {
timer.interval = delayTime;
timer.repeat = false;
timer.triggered.connect(cb);
timer.start();
}
delay(10, function() {
ripple.opacity = 0
ripple.diameter = 0
})
}
}
Try it....
How to achieve something like this.
Should the text thin and thick must be outside slider as labels or can they be part of tickmarks?
That can be easily done with styles. I advice you to look at QML controls/styles source in $QTHOME/qml/QtQuick/Controls[/Styles/Base] to have an understanding of default styles of QML controls.
import QtQuick 2.5
import QtQuick.Window 2.2
import QtQuick.Controls 1.2
import QtQuick.Controls.Styles 1.4
Window {
id: rootWindow
visible: true
height: 800
width: 800
Rectangle {
width: 350
height: 100
color: "#555"
anchors.centerIn: parent
Slider {
anchors.centerIn: parent
minimumValue: 1
maximumValue: 5
stepSize: 1
tickmarksEnabled: true
width: 300
style: SliderStyle {
handle: Rectangle {
width: 18
height: 30
border.width: 2
border.color: "#555"
color: "#CCC"
radius: 5
}
groove: Rectangle {
height: 15
width: parent.width
anchors.verticalCenter: parent.verticalCenter
color: "#CCC"
}
tickmarks: Repeater {
id: repeater
model: control.stepSize > 0 ? 1 + (control.maximumValue - control.minimumValue) / control.stepSize : 0
Item {
Text {
y: repeater.height + 5
width : repeater.width / repeater.count
x: width * index
height: 30
color: "#CCC"
font.pixelSize: 20
text: getText()
horizontalAlignment: getAlign()
function getText() {
if(index === 0) return "THIN"
else if(index === repeater.count - 1) return "THICK"
else return "";
}
function getAlign() {
if(index === "0") return Text.AlignLeft
else if(index === repeater.count - 1) return Text.AlignRight
else return Text.AlignHCenter;
}
}
Rectangle {
color: "#CCC"
width: 2 ; height: 5
y: repeater.height
x: styleData.handleWidth / 2 + index * ((repeater.width - styleData.handleWidth) / (repeater.count-1))
}
}
}
}
}
}
}
The exampe is full of excessive and worthless code but that's good for understanding.
It doesn't seem like they can be a part of the tick marks, but you can easily achieve this with separate text labels:
Slider {
id: slide
width: 200
}
Text {
text: "THIN"
anchors.top: slide.bottom
anchors.left: slide.left
}
Text {
text: "THICK"
anchors.top: slide.bottom
anchors.right: slide.right
}
I have a component named Tile in Tile.qml, which I want to create by a Repeater. Tile.qml is as follows:
import QtQuick 2.0
Rectangle {
id: tile
property string tileLabel: label.text
property int tileSize: height
width: 50
height: tileSize
color: "green"
border.color: Qt.lighter(color)
anchors.bottom: parent.bottom
Text {
id: label
color: "white";
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.bottom
text: tileLabel
}
}
And my main.qml is as follows:
import QtQuick 2.0
Rectangle {
id: root
width: 552; height: 300
color: "#3C3C3C"
border.color: Qt.lighter(color)
Row {
id: tilesRow
anchors.margins: 8
anchors.horizontalCenter: parent.horizontalCenter
anchors.verticalCenter: parent.verticalCenter
spacing: 4
Repeater {
id: repeater
model: 10
delegate: Tile {
tileSize: Math.random() * 100 + 1
tileLabel: tileSize
}
}
}
Rectangle {
id: button
width: 100
height: 30
color: "gray"
focus: true
Text {
anchors.centerIn: parent
color: "white"
text: "Button"
}
MouseArea {
hoverEnabled: true
anchors.fill: parent
onEntered: { button.color = Qt.lighter("blue")}
onExited: { button.color = "gray" }
onPressed: { button.color = "blue" }
onReleased: { button.color = Qt.lighter("blue") }
onClicked: func()
}
}
}
I need to sort the tiles when the button is clicked so that the tiles are in ascending order by their labels. I can access the labels of the tiles using repeater.itemAt(i).tileSize. How can I animate the movement of tiles as they are moved/swapped?
Small example:
import QtQuick 2.3
import QtQuick.Window 2.2
Window {
visible: true
width: 800
height: 600
Row {
anchors.centerIn: parent
property var word: ['H','e','l','l','o','!']
id: row
Repeater {
id: repeater
model: row.word.length
delegate: Rectangle {
id: delegate;
width: 100
height: 100
property int pos
color: Qt.rgba(Math.random(),Math.random(),Math.random(),1);
Text {
anchors.centerIn: parent
font.pixelSize: 36
color: "white"
text: row.word[index]
}
Behavior on x {
ParallelAnimation {
PropertyAnimation {
duration: 500
easing.type: Easing.InOutBack
}
SequentialAnimation {
PropertyAnimation {
target: delegate
property: "y"
from: 0
to: delegate.pos == 1 ? 20 : -20
duration: 250
}
PropertyAnimation {
target: delegate
property: "y"
from: delegate.pos == 1 ? 20 : -20
to: 0
duration: 250
}
}
}
}
Behavior on rotation {
RotationAnimation {
direction: RotationAnimation.Clockwise
duration: 300
}
}
}
}
}
Timer {
interval: 1000
running: true
repeat: true
onTriggered: {
var element1 = repeater.itemAt(Math.round(Math.random() * (repeater.count - 1)));
var element2 = repeater.itemAt(Math.round(Math.random() * (repeater.count - 1)));
if(element1 === element2) {
element1.rotation = element1.rotation + 90;
} else {
element1.pos = 1;
element2.pos = 2;
var temp = element1.x;
element1.x = element2.x;
element2.x = temp;
}
}
}
}
I'm using Qt 5.2 beta, Qt Quick 2.1.
I have straight ahead problem with Flickable component.
Here is minimal working code example:
import QtQuick 2.1
import QtQuick.Controls 1.0
ApplicationWindow {
width: 300
height: 200
Rectangle {
anchors.fill: parent
color: "green"
Flickable {
id: flickArea
anchors {fill: parent; margins: 10; }
contentWidth: rect.width;
contentHeight: rect.height
Rectangle {
id: rect
x: 0; y: 0;
width: 200; height: 300;
color: "lightgrey"
Rectangle {
anchors { fill: parent; margins: 10; }
color: "red"
}
}
}
}
Button {
anchors.bottom: parent.bottom;
anchors.horizontalCenter: parent.horizontalCenter;
text: "Scale flickArea"
onClicked: { flickArea.scale += 0.2; }
}
}
When I'm doing scaling, I expect that all child elements will stay visible as it was before and inner area to become bigger.
But instead, child elements move out of Flickable content.
Can someone propose a normal way to avoid this problem without the need for manual recalculation of all offsets?
I got it work as expected this way, but IMO this way is little bit tricky:
import QtQuick 2.1
import QtQuick.Controls 1.0
ApplicationWindow {
width: 300
height: 200
Rectangle {
anchors.fill: parent
color: "green"
Flickable {
id: flickArea
anchors {fill: parent; margins: 10; }
contentWidth: rect.width*rect.scale
contentHeight: rect.height*rect.scale
Rectangle {
id: rect
transformOrigin: Item.TopLeft
x: 0; y: 0;
width: 200; height: 300;
color: "lightgrey"
Rectangle {
id: inner
anchors { fill: parent; margins: 10; }
color: "red"
}
}
}
}
Button {
anchors.bottom: parent.bottom;
anchors.horizontalCenter: parent.horizontalCenter;
text: "Scale flickArea"
onClicked: {
rect.scale += 0.2;
}
}
}
Can someone propose something better?
UPD. I did not close this question for 1 year, but still didn't get any better solution or better way to doing things.
using Scale transform seems better solution:
import QtQml 2.11
import QtQuick 2.11
import QtQuick.Window 2.11
import QtQuick.Controls 2.0
import QtQuick.Layouts 1.11
import QtQuick.Controls 2.4
import QtQuick.Controls.Material 2.4
import QtCharts 2.2
import QtGraphicalEffects 1.0
ApplicationWindow {
visible: true
width: 640
height: 480
title: qsTr("Hello World")
property int scaleX: 1;
Rectangle {
anchors.fill: parent
color: "green"
Flickable {
id: flickArea
anchors {fill: parent; margins: 10; }
contentWidth: rect.width*rect.scale
contentHeight: rect.height*rect.scale
transform: Scale { origin.x: 0; origin.y: 240; xScale: scaleX}
Rectangle {
id: rect
transformOrigin: Item.TopLeft
x: 0; y: 0;
width: 200; height: 300;
color: "lightgrey"
Rectangle {
id: inner
anchors { fill: parent; margins: 10; }
color: "red"
}
}
}
}
Button {
anchors.bottom: parent.bottom;
anchors.horizontalCenter: parent.horizontalCenter;
text: "Scale flickArea"
onClicked: {
// flickArea.scale += 0.2;
scaleX += 1;
console.log(flickArea.contentWidth);
console.log(flickArea.scale)
}
}
}
A propar way to implement zoomable image in qml
import QtQuick 2.15
Flickable
{
id:flickable
property int imageWidth
property int imageHeight
property alias source: mImage.source
contentWidth: imageWidth
contentHeight:imageHeight
boundsBehavior: Flickable.StopAtBounds
boundsMovement: Flickable.StopAtBounds
states: [
State {
name: "state_StickToCenter" // state is used when content size is less than flickable size then content
// center should stick to flickable center
when : ( flickable.contentWidth < flickable.width || flickable.contentHeight< flickable.height )
PropertyChanges {
target: flickable.contentItem
anchors.horizontalCenter: width < flickable.width ? flickable.horizontalCenter : undefined
anchors.verticalCenter: height < flickable.height ? flickable.verticalCenter : undefined
}
}
]
onStateChanged: { cancelFlick(); returnToBounds(); }
Image
{
id:mImage
width:flickable.contentWidth;
height: flickable.contentHeight;
source: flickable.imageSource
fillMode: Image.PreserveAspectFit;
Component.onCompleted:
{
imageWidth = mImage.paintedWidth;
imageHeight = mImage.paintedHeight
}
autoTransform: true
PinchArea
{
id:pinchArea
anchors.fill: parent
property bool zoomTriggeredFromPinchArea:false
property point pinchCenter;
onPinchStarted: zoomTriggeredFromPinchArea=true;
onPinchUpdated:
{
var newZoomFactor = privateProperties.currentZoomFactor+ privateProperties.currentZoomFactor*(pinch.scale-1);
pinchCenter =pinch.center;
privateProperties.zoom(privateProperties.getBoundedScaleFactor(newZoomFactor))
}
onPinchFinished:
{
privateProperties.currentZoomFactor += privateProperties.currentZoomFactor*(pinch.scale-1);
privateProperties.currentZoomFactor = privateProperties.getBoundedScaleFactor(privateProperties.currentZoomFactor);
zoomTriggeredFromPinchArea=false;
}
MouseArea
{
id:mouseArea
anchors.fill: parent
propagateComposedEvents :true
scrollGestureEnabled: false
hoverEnabled: true
onDoubleClicked:
{
if(privateProperties.currentZoomFactor>1)resetScale();
else
{
var widthScale = (flickable.width+20)/mImage.width;
var heightScale = (flickable.height+20)/mImage.height;
var maxScale = Math.max(widthScale,heightScale);
if(maxScale>1)
{
privateProperties.pointOfDoubleClick = Qt.point(mouseX,mouseY);
privateProperties.useDoubleClickPoint = true;
privateProperties.currentZoomFactor = maxScale;
}
}
}
onWheel:
{
if(wheel.modifiers===Qt.ControlModifier)
{
wheel.accepted=true;
var newZoomFactor;
if(wheel.angleDelta.y>0)
newZoomFactor = privateProperties.currentZoomFactor + (privateProperties.currentZoomFactor*privateProperties.zoomStepFactor);
else newZoomFactor = privateProperties.currentZoomFactor - (privateProperties.currentZoomFactor*privateProperties.zoomStepFactor);
privateProperties.currentZoomFactor = privateProperties.getBoundedScaleFactor(newZoomFactor);
return;
}
wheel.accepted=false;
}
}
}
}
QtObject
{
id : privateProperties
property bool useDoubleClickPoint:false;
property point pointOfDoubleClick;
property real maxZoomFactor : 30.0
property real zoomStepFactor :0.3;
property real currentZoomFactor: 1
property real minZoomFactor :1;
property point scaleCenter : pinchArea.zoomTriggeredFromPinchArea
? pinchArea.pinchCenter : Qt.point(mouseArea.mouseX,mouseArea.mouseY);
Behavior on currentZoomFactor {
NumberAnimation { id:scaleNumberAnimation
duration: pinchArea.zoomTriggeredFromPinchArea ? 0 : privateProperties.useDoubleClickPoint ?
Math.min(200*privateProperties.currentZoomFactor,500) : 200 ;
onRunningChanged: if(!running) privateProperties.useDoubleClickPoint=false;
}
}
onCurrentZoomFactorChanged:
{
if(!pinchArea.zoomTriggeredFromPinchArea)
zoom(currentZoomFactor);
}
function zoom(scaleFactor)
{
var targetWidth = imageWidth*scaleFactor;
var targetHeight = imageHeight*scaleFactor;
if(useDoubleClickPoint) resizeContent(targetWidth,targetHeight,mapToItem(mImage,pointOfDoubleClick));
else resizeContent(targetWidth,targetHeight,scaleCenter);
returnToBounds();
}
function getBoundedScaleFactor(ScaleFactor)
{
if(ScaleFactor>maxZoomFactor)ScaleFactor = maxZoomFactor;
else if(ScaleFactor<minZoomFactor)ScaleFactor = minZoomFactor;
return ScaleFactor;
}
}
function resetScale()
{
privateProperties.pointOfDoubleClick = Qt.point(0,0);
privateProperties.useDoubleClickPoint = true;
privateProperties.currentZoomFactor = 1;
}
}