scrollbar rewrite

no binding loops
no rotation transformations
actual scrollbar is a loaded component: will be shared with scrolldecorator
This commit is contained in:
Marco Martin 2011-11-09 14:21:54 +01:00
parent 6a19c2da5b
commit 8cccd7c834

View File

@ -1,5 +1,6 @@
/*
* Copyright (C) 2011 by Daker Fernandes Pinheiro <dakerfp@gmail.com>
* Copyright 2011 Marco Martin <mart@kde.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Library General Public License as
@ -26,22 +27,20 @@ Item {
// Common API
property Flickable flickableItem: null
property bool interactive
property int orientation: Qt.Vertical
property bool interactive: true
// Plasma API
property int orientation: Qt.Vertical
property bool animated: true
property bool inverted: false
property bool updateValueWhileDragging: true
property alias stepSize: range.stepSize
property alias pressed: mouseArea.pressed
//property alias pressed: mouseArea.pressed
property real scrollButtonInterval: 50
// Convinience API
property bool _isVertical: orientation == Qt.Vertical
property bool _showButtons: stepSize != 0
property bool _inverted: _isVertical ?
!scrollbar.inverted : scrollbar.inverted
implicitWidth: _isVertical ? 22 : 200
implicitHeight: _isVertical ? 200 : 22
@ -49,7 +48,7 @@ Item {
// disabled scroll bars
opacity: enabled ? 1.0 : 0.5
visible: flickableItem && handle.width < contents.width
visible: flickableItem && internalLoader.handleEnabled
anchors {
right: flickableItem.right
@ -58,67 +57,161 @@ Item {
bottom: flickableItem.bottom
}
function incrementValue(increment) {
if (!flickableItem)
return;
if (_isVertical) {
flickableItem.contentY = Math.min(flickableItem.contentHeight,
flickableItem.contentY + increment);
} else {
flickableItem.contentX = Math.min(flickableItem.contentWidth,
flickableItem.contentX + increment);
}
}
Keys.onUpPressed: {
if (!enabled || !_isVertical)
return;
if (_inverted)
incrementValue(-stepSize);
if (inverted)
internalLoader.incrementValue(-stepSize);
else
incrementValue(stepSize);
internalLoader.incrementValue(stepSize);
}
Keys.onDownPressed: {
if (!enabled || !_isVertical)
return;
if (_inverted)
incrementValue(stepSize);
if (inverted)
internalLoader.incrementValue(stepSize);
else
incrementValue(-stepSize);
internalLoader.incrementValue(-stepSize);
}
Keys.onLeftPressed: {
if (!enabled || _isVertical)
return;
if (_inverted)
incrementValue(stepSize);
if (inverted)
internalLoader.incrementValue(stepSize);
else
incrementValue(-stepSize);
internalLoader.incrementValue(-stepSize);
}
Keys.onRightPressed: {
if (!enabled || _isVertical)
return;
if (_inverted)
incrementValue(-stepSize);
if (inverted)
internalLoader.incrementValue(-stepSize);
else
incrementValue(stepSize);
internalLoader.incrementValue(stepSize);
}
Loader {
id: internalLoader
anchors.fill: parent
//property bool handleEnabled: _isVertical ? item.handle.height < item.contents.height : item.handle.width < item.contents.width
property bool handleEnabled: _isVertical ? flickableItem.contentHeight > flickableItem.height : flickableItem.contentWidth > flickableItem.width
function incrementValue(increment)
{
if (!flickableItem)
return;
if (_isVertical) {
flickableItem.contentY = Math.max(0, Math.min(flickableItem.contentHeight,
flickableItem.contentY + increment))
} else {
flickableItem.contentX = Math.max(0, Math.min(flickableItem.contentWidth,
flickableItem.contentX + increment))
}
}
RangeModel {
id: range
minimumValue: 0
maximumValue: {
var diff;
if (_isVertical) {
diff = flickableItem.contentHeight - flickableItem.height
} else {
diff = flickableItem.contentWidth - flickableItem.width
}
return Math.max(0, diff)
}
stepSize: 10
inverted: scrollbar.inverted
positionAtMinimum: 0
positionAtMaximum: {
if (_isVertical) {
internalLoader.item.contents.height - internalLoader.item.handle.height
} else {
internalLoader.item.contents.width - internalLoader.item.handle.width
}
}
value: _isVertical ? flickableItem.contentY : flickableItem.contentX
onValueChanged: {
if (flickableItem.moving) {
return
}
if (_isVertical) {
flickableItem.contentY = value
} else {
flickableItem.contentX = value
}
}
position: _isVertical ? internalLoader.item.handle.y : internalLoader.item.handle.x
onPositionChanged: {
if (internalLoader.item.mouseArea.pressed) {
return
}
if (_isVertical) {
internalLoader.item.handle.y = position
} else {
internalLoader.item.handle.x = position
}
}
}
sourceComponent: Component {
PlasmaCore.FrameSvgItem {
id: background
anchors.fill: parent
imagePath:"widgets/scrollbar"
prefix: _isVertical ? "background-vertical" : "background-horizontal"
property Item handle: handle
property Item contents: contents
Item {
id: contents
anchors {
fill: parent
leftMargin: _isVertical || stepSize <= 0 ? 0 : leftButton.width
rightMargin: _isVertical || stepSize <= 0 ? 0 : rightButton.width
topMargin: _isVertical && stepSize > 0 ? leftButton.height : 0
bottomMargin: _isVertical && stepSize > 0 ? rightButton.height : 0
}
width: _isVertical ? scrollbar.height : scrollbar.width
height: _isVertical ? scrollbar.width : scrollbar.height
rotation: _isVertical ? -90 : 0
PlasmaCore.FrameSvgItem {
id: handle
imagePath:"widgets/scrollbar"
prefix: {
if (mouseArea.pressed) {
return "sunken-slider"
}
if (scrollbar.activeFocus || mouseArea.containsMouse) {
return "mouseover-slider"
} else {
return "slider"
}
}
property int length: _isVertical? flickableItem.visibleArea.heightRatio * parent.height : flickableItem.visibleArea.widthRatio * parent.width
width: _isVertical ? parent.width : length
height: _isVertical ? length : parent.height
}
}
anchors.centerIn: parent
PlasmaCore.Svg {
id: scrollbarSvg
imagePath: "widgets/scrollbar"
@ -126,26 +219,31 @@ Item {
PlasmaCore.SvgItem {
id: leftButton
visible: stepSize > 0
anchors {
left: parent.left
verticalCenter: parent.verticalCenter
left: _isVertical ? undefined : parent.left
verticalCenter: _isVertical ? undefined : parent.verticalCenter
top: _isVertical ? 0 : undefined
horizontalCenter: _isVertical ? parent.horizontalCenter : undefined
}
height: 18
width: _showButtons ? 18 : 0
svg: scrollbarSvg
elementId: {
if (leftMousArea.pressed)
return "sunken-arrow-left";
if (leftMouseArea.pressed) {
return _isVertical ? "sunken-arrow-up" : "sunken-arrow-left"
}
if (scrollbar.activeFocus || leftMousArea.containsMouse)
return "mouseover-arrow-left";
else
return "arrow-left";
if (scrollbar.activeFocus || leftMouseArea.containsMouse) {
return _isVertical ? "mouseover-arrow-up" : "mouseover-arrow-left"
} else {
return _isVertical ? "mouseover-arrow-up" : "arrow-left"
}
}
MouseArea {
id: leftMousArea
id: leftMouseArea
anchors.fill: parent
enabled: scrollbar.enabled
@ -155,11 +253,12 @@ Item {
running: parent.pressed
repeat: true
onTriggered: {
scrollbar.forceActiveFocus();
if (_inverted)
incrementValue(stepSize);
else
incrementValue(-stepSize);
scrollbar.forceActiveFocus()
if (inverted) {
internalLoader.incrementValue(stepSize);
} else {
internalLoader.incrementValue(-stepSize);
}
}
}
}
@ -167,26 +266,31 @@ Item {
PlasmaCore.SvgItem {
id: rightButton
visible: stepSize > 0
anchors {
right: parent.right
verticalCenter: parent.verticalCenter
right: _isVertical ? undefined : parent.right
verticalCenter: _isVertical ? undefined : parent.verticalCenter
bottom: _isVertical ? parent.bottom : undefined
horizontalCenter: _isVertical ? parent.horizontalCenter : undefined
}
height: 18
width: _showButtons ? 18 : 0
svg: scrollbarSvg
elementId: {
if (rightMousArea.pressed)
return "sunken-arrow-right";
if (leftMouseArea.pressed) {
return _isVertical ? "sunken-arrow-down" : "sunken-arrow-right"
}
if (scrollbar.activeFocus || rightMousArea.containsMouse)
return "mouseover-arrow-right";
else
return "arrow-right";
if (scrollbar.activeFocus || leftMouseArea.containsMouse) {
return _isVertical ? "mouseover-arrow-down" : "mouseover-arrow-right"
} else {
return _isVertical ? "mouseover-arrow-down" : "arrow-right"
}
}
MouseArea {
id: rightMousArea
id: rightMouseArea
anchors.fill: parent
enabled: scrollbar.enabled
@ -197,161 +301,62 @@ Item {
repeat: true
onTriggered: {
scrollbar.forceActiveFocus();
if (_inverted)
incrementValue(-stepSize);
if (inverted)
internalLoader.incrementValue(-stepSize);
else
incrementValue(stepSize);
internalLoader.incrementValue(stepSize);
}
}
}
}
Item {
id: contents
anchors {
left: leftButton.right
top: parent.top
bottom: parent.bottom
right: rightButton.left
}
RangeModel {
id: range
minimumValue: 0
maximumValue: {
var diff;
if (_isVertical)
diff = flickableItem.contentHeight - flickableItem.height;
else
diff = flickableItem.contentWidth - flickableItem.width;
return Math.max(0, diff);
}
stepSize: 10
inverted: _inverted
positionAtMinimum: 0 + handle.width / 2
positionAtMaximum: contents.width - handle.width / 2
value: _isVertical ? flickableItem.contentY : flickableItem.contentX
onValueChanged: {
if (flickableItem.flicking)
return;
if (_isVertical)
flickableItem.contentY = value;
else
flickableItem.contentX = value;
}
position: handle.x
onPositionChanged: { handle.x = position; }
}
PlasmaCore.FrameSvgItem {
id: groove
anchors.fill: parent
imagePath: "widgets/scrollbar"
prefix: "background-horizontal"
}
PlasmaCore.FrameSvgItem {
id: handle
transform: Translate { x: - handle.width / 2 }
x: fakeHandle.x
anchors.verticalCenter: groove.verticalCenter
width: {
var ratio;
if (_isVertical)
ratio = flickableItem.visibleArea.heightRatio;
else
ratio = flickableItem.visibleArea.widthRatio;
return ratio * parent.width;
}
height: parent.height - margins.top // TODO: check mergin
imagePath: "widgets/scrollbar"
prefix: {
if (scrollbar.pressed)
return "sunken-slider";
if (scrollbar.activeFocus || mouseArea.containsMouse)
return "mouseover-slider";
else
return "slider";
}
Behavior on x {
id: behavior
enabled: !mouseArea.drag.active && scrollbar.animated &&
!flickableItem.flicking
PropertyAnimation {
duration: behavior.enabled ? 150 : 0
easing.type: Easing.OutSine
}
}
}
Item {
id: fakeHandle
width: handle.width
height: handle.height
transform: Translate { x: - handle.width / 2 }
}
property MouseArea mouseArea: mouseArea
MouseArea {
id: mouseArea
anchors.fill: parent
anchors.fill: contents
enabled: scrollbar.enabled
hoverEnabled: true
drag {
target: fakeHandle
axis: Drag.XAxis
target: handle
axis: _isVertical ? Drag.YAxis : Drag.XAxis
minimumX: range.positionAtMinimum
maximumX: range.positionAtMaximum
minimumY: range.positionAtMinimum
maximumY: range.positionAtMaximum
}
onPressed: {
if (_isVertical) {
// Clamp the value
var newY = Math.max(mouse.y, drag.minimumY);
newY = Math.min(newY, drag.maximumY);
// Debounce the press: a press event inside the handler will not
// change its position, the user needs to drag it.
if (newY > handle.y + handle.height) {
handle.y = mouse.y - handle.height
} else if (newY < handle.y) {
handle.y = mouse.y
}
} else {
// Clamp the value
var newX = Math.max(mouse.x, drag.minimumX);
newX = Math.min(newX, drag.maximumX);
// Debounce the press: a press event inside the handler will not
// change its position, the user needs to drag it.
if (Math.abs(newX - fakeHandle.x) > handle.width / 2)
range.position = newX;
if (newX > handle.x + handle.width) {
handle.x = mouse.x - handle.width
} else if (newX < handle.x) {
handle.x = mouse.x
}
}
scrollbar.forceActiveFocus();
}
onReleased: {
// If we don't update while dragging, this is the only
// moment that the range is updated.
if (!scrollbar.updateValueWhileDragging)
range.position = fakeHandle.x;
}
}
}
// Range position normally follow fakeHandle, except when
// 'updateValueWhileDragging' is false. In this case it will only follow
// if the user is not pressing the handle.
Binding {
when: updateValueWhileDragging || !mouseArea.pressed
target: range
property: "position"
value: fakeHandle.x
}
// During the drag, we simply ignore position set from the range, this
// means that setting a value while dragging will not "interrupt" the
// dragging activity.
Binding {
when: !mouseArea.drag.active
target: fakeHandle
property: "x"
value: range.position
}
}
}