Search code examples
qtqmlrepeat

Why is spacing creating more than expected?


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:

enter image description here


Solution

  • 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 = []
        }
    }
    

    enter image description here