Wednesday, April 4, 2012

QML ScrollBar

I had to implement a ScrollBar component but the examples I could find did not do what I needed. I needed the ScrollBar with arrows on the sides, which would move the contents of the field a certain number of points to the required direction. After that, I added the sliding feature, that moved the contents of the field when the slider is dragged.

The ScrollBar consists of four rectangles. Two of them are arrows at the ends. One is the whole length of the scroll bar, and the last one is the slider that can be dragged along the body of the scroll bar. I used the Flickable component because I need to know the size of the field contents. Otherwise, I disabled the functionality of the flickable. When any of the arrows is clicked, first action is to identify if the slider can move in the required direction. This is calculated based on the height/width of the field (returned by flickable.height or width), height/width of the contents of the field (returned by flickable.contentHeight or contentWidth) and the current position of the contents within the flickable control (returned by flickable.contentX or contentY). If there is space to move, the flickable.contentX or contentY is changed, which moves the contents inside the field, and the slider position is adjusted.

The slide operates on the similar principle. When it is released, its position is read and then basing on the position and the length of the slider body the percentage of length the slider has traveled is calculated. From that percentage, the flickable.contentX or contentY is calculated so that the field contents move to reflect that percentage. It is not yet completely presice, but it does what is expected to do.

To use the ScrollBar, it needs to know several things: the id of the Flickable component associated with the field, the desired width of the scroll bar, the amount of pixels the contents need to move each click and the orientation - vertical or horisontal. In the example below, the Flickable component called view is associated with both scroll bars, the width of the scroll bar is 10 and the image moves 20 pixels each arrow click.

import QtQuick 1.1

Rectangle {
width: 360
height: 360

Flickable{

id:view
anchors.fill: parent
contentWidth: picture.width
contentHeight: picture.height
interactive: false

Image {
id: picture
source: "images/Desert.jpg"
asynchronous: true
}
}

ScrollBar{
id: verticalScroll
flickable: view
step: 20
size: 10
orientation: Qt.Vertical
}

ScrollBar{
id: horisontalScroll
flickable: view
step: 20
size: 10
orientation: Qt.Horizontal
}
}

QML ScrollBar

The listing of the ScrollBar component is as follows:

import QtQuick 1.1

Rectangle {

id: scrollBar
property variant orientation
property variant flickable
property int step
property int size
height: orientation === Qt.Vertical ? flickable.height : size
width: orientation === Qt.Vertical ? size : flickable.width

function canMove(first)
{
if(first)
return orientation === Qt.Vertical ? flickable.contentY > 0 : flickable.contentX > 0;
else
return orientation === Qt.Vertical ? flickable.height + flickable.contentY < flickable.contentHeight :
flickable.width + flickable.contentX < flickable.contentWidth
}

function arrowClicked(first)
{
if(canMove(first))
{
if(first)
{
if(orientation === Qt.Vertical)
flickable.contentY -= step;
else
flickable.contentX -= step;
}
else
{
if(orientation === Qt.Vertical)
flickable.contentY += step;
else
flickable.contentX += step;
}
positionSlider();
}
}

function moveContents()
{
if(orientation === Qt.Vertical)
flickable.contentY = (slider.y - size)*(flickable.contentHeight - flickable.height)/(body.height - slider.height);
else
flickable.contentX = (flickable.contentWidth - flickable.width)*slider.x/flickable.width;
}

function positionSlider()
{
var percentage = orientation === Qt.Vertical ? (flickable.contentY)/(flickable.contentHeight - flickable.height)
: (flickable.contentX + size)/(flickable.contentWidth - flickable.width);
if(orientation === Qt.Vertical)
slider.y = percentage*(body.height - slider.height) + size;
else
slider.x = percentage*(body.width - slider.width) + size;
}

onHeightChanged: {
if(canMove(true) || canMove(false))
positionSlider();
}

onWidthChanged: {
if(canMove(true) || canMove(false))
positionSlider();
}

Component.onCompleted: {
sliderArea.drag.axis = orientation === Qt.Vertical ? Drag.YAxis : Drag.XAxis;

if(orientation === Qt.Vertical)
{
scrollBar.anchors.right = flickable.right
firstArrow.anchors.top = scrollBar.top;
body.anchors.top = firstArrow.bottom;
secondArrow.anchors.bottom = scrollBar.bottom;
slider.y = size;
}
else
{
scrollBar.anchors.bottom = flickable.bottom;
firstArrow.anchors.left = scrollBar.left;
body.anchors.left = firstArrow.right;
secondArrow.anchors.right = scrollBar.right;
slider.x = size;
}
}

Rectangle{
id: firstArrow
width: size
height: size

Image{
id: imgFirstArrow
anchors.fill: parent
source: orientation === Qt.Vertical ? "images/vertUpArrow.png" : "images/horisLeftArrow.png"
}

MouseArea{
id: firstArrowArea
anchors.fill: parent
onClicked: {
arrowClicked(true);
}
}
}

Rectangle{
id: body
width: orientation === Qt.Vertical ? size : scrollBar.width - 2*size
height: orientation === Qt.Vertical ? scrollBar.height - 2*size : size
color: "#575B5E"
}

Rectangle{
id: slider
width: orientation === Qt.Vertical ? size : 3*size;
height: orientation === Qt.Vertical ? 3*size : size;

Image{
id: imgSlider
anchors.fill: parent
source: orientation === Qt.Vertical ? "images/SliderVert-Enabled.png" : "images/SliderHoris-Enabled.png"
}

MouseArea{
id:sliderArea
anchors.fill: parent
drag.target: slider
drag.minimumX: 10
drag.minimumY: 10
drag.maximumX: orientation === Qt.Vertical ? 0 : body.width - slider.width + size
drag.maximumY: orientation === Qt.Vertical ? body.height - slider.height + size : 0

onReleased: {
moveContents();
}
}
}

Rectangle{
id: secondArrow
width: size
height: size

Image{
id: imgSecondArrow
anchors.fill: parent
source: orientation === Qt.Vertical ? "images/vertDownArrow.png" : "images/horisRightArrow.png"
}

MouseArea{
id: secondArrowArea
anchors.fill: parent
hoverEnabled: true
onClicked: {
arrowClicked(false);
}
}
}
}

References:

ScrollBar.qml Example File
Scrollable and Scroll indicators with QML by . Also posted on my website

No comments: