aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDaniel d'Andrada <daniel.dandrada@luxoft.com>2017-10-24 09:30:33 +0200
committerDaniel d'Andrada <daniel.dandrada@luxoft.com>2018-02-28 15:58:02 +0100
commitd563abee402d616bdff2639c275775184069e042 (patch)
tree0959e93a7fe4379a934635e9b924464d9f32acb6
parent9962fb888ad888b768c1f4db549d0c250d07a43c (diff)
Improve widget resizing
- Consider all widgets while dragging a handle and not just those immediately above and below it - Make it follow the users finger but still snap at the proper places both during drag and upon release
-rw-r--r--sysui/display/HomePage.qml220
-rw-r--r--sysui/display/Resizer.qml102
-rw-r--r--tests/qmltests/tst_HomePage.qml27
3 files changed, 231 insertions, 118 deletions
diff --git a/sysui/display/HomePage.qml b/sysui/display/HomePage.qml
index c64d66d0..f9fb29a5 100644
--- a/sysui/display/HomePage.qml
+++ b/sysui/display/HomePage.qml
@@ -84,18 +84,216 @@ Item {
}
}
+ property bool resizingWidgets: false
+ property real startResizeDragY
+ property int widgetIndexAboveHandle
+ function onResizeHandlePressed(pos) {
+ startResizeDragY = pos.y;
+
+ var i = 0;
+ var accumulatedHeight = 0;
+ while (true) {
+ var appInfo = root.widgetsList.get(i).appInfo;
+ accumulatedHeight += appInfo.heightRows * rowHeight;
+ if (accumulatedHeight >= pos.y) {
+ widgetIndexAboveHandle = i;
+ break;
+ } else {
+ i++;
+ }
+ }
+
+ animateWidgetResize = false;
+
+ // init resize heights
+ for (i = 0; i < repeater.count; i++) {
+ var delegate = repeater.itemAt(i);
+ delegate.heightWhenResizing = delegate.appInfo.heightRows * widgetGrid.rowHeight;
+ }
+
+ resizingWidgets = true;
+ }
+ function onResizeHandleDragged(pos) {
+ // init resize heights
+ for (i = 0; i < repeater.count; i++) {
+ var delegate = repeater.itemAt(i);
+ delegate.heightWhenResizing = delegate.appInfo.heightRows * widgetGrid.rowHeight;
+ }
+
+ var delta = snapToRowMultiplierIfTooClose(withinBoundsY(pos.y) - startResizeDragY);
+ delta = computeUsableDragDelta(delta);
+
+ // above handle. positive delta enlarges widgets
+ var i = widgetIndexAboveHandle;
+ var remainingDelta = delta;
+ while (remainingDelta !== 0 && i >= 0) {
+ var delegate = repeater.itemAt(i);
+ var appInfo = delegate.appInfo;
+ var minHeight = appInfo.minHeightRows * rowHeight;
+ var targetHeight = (appInfo.heightRows * rowHeight) + remainingDelta;
+
+ if (remainingDelta > 0) {
+ // no upper limit at the moment on widget height (other than grid size)
+ delegate.heightWhenResizing = targetHeight;
+ remainingDelta = 0;
+ } else {
+ if (targetHeight >= minHeight) {
+ delegate.heightWhenResizing = targetHeight;
+ remainingDelta = 0;
+ } else {
+ delegate.heightWhenResizing = minHeight;
+ remainingDelta -= -((appInfo.heightRows * rowHeight) - minHeight)
+ i--;
+ }
+ }
+ }
+
+ // below handle. positive delta shrinks widgets
+ i = widgetIndexAboveHandle + 1;
+ remainingDelta = delta;
+ while (remainingDelta !== 0 && i < repeater.count) {
+ var delegate = repeater.itemAt(i);
+ var appInfo = delegate.appInfo;
+ var minHeight = appInfo.minHeightRows * rowHeight;
+ var targetHeight = (appInfo.heightRows * rowHeight) - remainingDelta;
+
+ if (remainingDelta < 0) {
+ // no upper limit at the moment on widget height (other than grid size)
+ delegate.heightWhenResizing = targetHeight;
+ remainingDelta = 0;
+ } else {
+ if (targetHeight >= minHeight) {
+ delegate.heightWhenResizing = targetHeight;
+ remainingDelta = 0;
+ } else {
+ delegate.heightWhenResizing = minHeight;
+ remainingDelta -= (appInfo.heightRows * rowHeight) - minHeight;
+ i++;
+ }
+ }
+ }
+ }
+ function onResizeHandleReleased(pos) {
+ // apply sizes
+ var i;
+ for (i = 0; i < repeater.count; i++) {
+ var delegate = repeater.itemAt(i);
+ delegate.appInfo.heightRows = Math.round(delegate.heightWhenResizing / widgetGrid.rowHeight);
+ }
+
+ animateWidgetResize = true;
+ resizingWidgets = false;
+ }
+ function computeUsableDragDelta(delta) {
+ // above handle. positive delta enlarges widgets
+ var i = widgetIndexAboveHandle;
+ var remainingDelta = delta;
+ var usableDeltaAbove = 0
+ while (remainingDelta !== 0 && i >= 0) {
+ var appInfo = root.widgetsList.get(i).appInfo;
+ var minHeight = appInfo.minHeightRows * rowHeight;
+ var targetHeight = (appInfo.heightRows * rowHeight) + remainingDelta;
+
+ if (remainingDelta > 0) {
+ // no upper limit at the moment on widget height (other than grid size)
+ usableDeltaAbove += remainingDelta;
+ remainingDelta = 0;
+ } else {
+ if (targetHeight >= minHeight) {
+ usableDeltaAbove += remainingDelta;
+ remainingDelta = 0;
+ } else {
+ var thisWidgetDelta = -((appInfo.heightRows * rowHeight) - minHeight);
+ usableDeltaAbove += thisWidgetDelta;
+ remainingDelta -= thisWidgetDelta;
+ i--;
+ }
+ }
+ }
+
+ // below handle. positive delta shrinks widgets
+ i = widgetIndexAboveHandle + 1;
+ remainingDelta = usableDeltaAbove;
+ var usableDelta = 0
+ while (remainingDelta !== 0 && i < root.widgetsList.count) {
+ var appInfo = root.widgetsList.get(i).appInfo;
+ var minHeight = appInfo.minHeightRows * rowHeight;
+ var targetHeight = (appInfo.heightRows * rowHeight) - remainingDelta;
+
+ if (remainingDelta < 0) {
+ // no upper limit at the moment on widget height (other than grid size)
+ usableDelta += remainingDelta;
+ remainingDelta = 0;
+ } else {
+ if (targetHeight >= minHeight) {
+ usableDelta += remainingDelta;
+ remainingDelta = 0;
+ } else {
+ var thisWidgetDelta = (appInfo.heightRows * rowHeight) - minHeight;
+ usableDelta += thisWidgetDelta;
+ remainingDelta -= thisWidgetDelta;
+ i++;
+ }
+ }
+ }
+ return usableDelta;
+ }
+ function withinBoundsY(someY) {
+ return Math.max(0, Math.min(someY, height));
+ }
+ function snapToRowMultiplierIfTooClose(delta) {
+ var deltaRows = delta / rowHeight;
+ var snapThreshold = 0.5;
+
+ var floorDiff = deltaRows - Math.floor(deltaRows);
+ var ceilDiff = Math.ceil(deltaRows) - deltaRows;
+
+ if (floorDiff < ceilDiff && floorDiff <= snapThreshold) {
+ //return Math.floor(deltaRows) * rowHeight;
+ return applyEasing(Math.floor(deltaRows)*rowHeight - snapThreshold*rowHeight,
+ Math.floor(deltaRows)*rowHeight + snapThreshold*rowHeight, delta);
+ } else if (floorDiff >= ceilDiff && ceilDiff <= snapThreshold) {
+ //return Math.ceil(deltaRows) * rowHeight;
+ return applyEasing(Math.ceil(deltaRows)*rowHeight - snapThreshold*rowHeight,
+ Math.ceil(deltaRows)*rowHeight + snapThreshold*rowHeight, delta);
+ } else {
+ return delta;
+ }
+ }
+ function applyEasing(min, max, value) {
+ // apply an OutInCubic easing function, ie:
+ // y=x^3, ranging from -1 (min) to 1 (max)
+
+ // normalize the value to be in the [-1,1] range
+ var x = (((value - min) / (max - min)) * 2) - 1;
+
+ var y = Math.pow(x, 3);
+
+ // and map it back to its original range
+ return (((y + 1) / 2) * (max - min)) + min;
+ }
+
+ property bool animateWidgetResize: true
+
Repeater {
id: repeater
model: root.widgetsList
delegate: Column {
id: repeaterDelegate
width: widgetGrid.width
- height: appInfo ? appInfo.heightRows * widgetGrid.rowHeight : 0
+ height: {
+ if (widgetGrid.resizingWidgets) {
+ return heightWhenResizing
+ } else {
+ return appInfo ? appInfo.heightRows * widgetGrid.rowHeight : 0
+ }
+ }
+ property real heightWhenResizing
- Behavior on x { enabled:!moveTransition.enabled; SmoothedAnimation { easing.type: Easing.InOutQuad; duration: 270 } }
- Behavior on y { enabled:!moveTransition.enabled; SmoothedAnimation { easing.type: Easing.InOutQuad; duration: 270 } }
- Behavior on width { enabled:!moveTransition.enabled; SmoothedAnimation { easing.type: Easing.InOutQuad; duration: 270 } }
- Behavior on height { enabled:!moveTransition.enabled; SmoothedAnimation { easing.type: Easing.InOutQuad; duration: 270 } }
+ Behavior on x { enabled:!moveTransition.enabled && widgetGrid.animateWidgetResize; SmoothedAnimation { easing.type: Easing.InOutQuad; duration: 270 } }
+ Behavior on y { enabled:!moveTransition.enabled && widgetGrid.animateWidgetResize; SmoothedAnimation { easing.type: Easing.InOutQuad; duration: 270 } }
+ Behavior on width { enabled:!moveTransition.enabled && widgetGrid.animateWidgetResize; SmoothedAnimation { easing.type: Easing.InOutQuad; duration: 270 } }
+ Behavior on height { enabled:!moveTransition.enabled && widgetGrid.animateWidgetResize; SmoothedAnimation { easing.type: Easing.InOutQuad; duration: 270 } }
property alias appInfo: appWidget.appInfo
readonly property int modelIndex: model.index
@@ -112,7 +310,7 @@ Item {
Item {
id: appWidgetSlot
width: repeaterDelegate.width
- height: repeaterDelegate.height - resizerHandle.height
+ height: repeaterDelegate.height - resizeHandle.height
ApplicationWidget {
id: appWidget
@@ -132,18 +330,18 @@ Item {
}
}
Rectangle {
- id: resizerHandle
+ id: resizeHandle
visible: repeaterDelegate.isAtBottom ? false : true
color: "grey"
width: parent.width
height: widgetGrid.resizerHandleHeight
- Resizer {
+ MouseArea {
anchors.fill: parent
- topAppInfo: model.appInfo
- bottomAppInfo: repeaterDelegate.isAtBottom ? null : repeater.model.get(model.index + 1).appInfo
- grid: widgetGrid
+ onPressed: widgetGrid.onResizeHandlePressed(mapToItem(widgetGrid, mouseX, mouseY))
+ onReleased: widgetGrid.onResizeHandleReleased(mapToItem(widgetGrid, mouseX, mouseY))
+ onPositionChanged: widgetGrid.onResizeHandleDragged(mapToItem(widgetGrid, mouseX, mouseY))
}
}
}
diff --git a/sysui/display/Resizer.qml b/sysui/display/Resizer.qml
deleted file mode 100644
index 6b545175..00000000
--- a/sysui/display/Resizer.qml
+++ /dev/null
@@ -1,102 +0,0 @@
-/****************************************************************************
-**
-** Copyright (C) 2017 Pelagicore AG
-** Contact: https://www.qt.io/licensing/
-**
-** This file is part of the Triton IVI UI.
-**
-** $QT_BEGIN_LICENSE:GPL-QTAS$
-** Commercial License Usage
-** Licensees holding valid commercial Qt Automotive Suite licenses may use
-** this file in accordance with the commercial license agreement provided
-** with the Software or, alternatively, in accordance with the terms
-** contained in a written agreement between you and The Qt Company. For
-** licensing terms and conditions see https://www.qt.io/terms-conditions.
-** For further information use the contact form at https://www.qt.io/contact-us.
-**
-** GNU General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU
-** General Public License version 3 or (at your option) any later version
-** approved by the KDE Free Qt Foundation. The licenses are as published by
-** the Free Software Foundation and appearing in the file LICENSE.GPL3
-** included in the packaging of this file. Please review the following
-** information to ensure the GNU General Public License requirements will
-** be met: https://www.gnu.org/licenses/gpl-3.0.html.
-**
-** $QT_END_LICENSE$
-**
-** SPDX-License-Identifier: GPL-3.0
-**
-****************************************************************************/
-
-import QtQuick 2.6
-
-import utils 1.0
-
-MultiPointTouchArea {
- id: root
-
- property var topAppInfo
- property var bottomAppInfo
-
- /*
- A grid item is expected to have the following properties:
- numRows: number of rows
- rowHeight: height of each row
- */
- property Item grid
-
- touchPoints: [
- TouchPoint {
- id: touch1
- onXChanged: d.onTouchMoved()
- onYChanged: d.onTouchMoved()
- onPressedChanged: {
- if (pressed) {
- d.dragging = true;
-
- var gridPos = root.mapToItem(root.grid, touch1.x, touch1.y);
- d.startGridPosY = gridPos.y
-
- d.startTopHeightRows = root.topAppInfo.heightRows;
-
- if (root.bottomAppInfo) {
- d.startBottomHeightRows = root.bottomAppInfo.heightRows;
- }
- } else {
- d.dragging = false;
- }
- }
- }
- ]
-
- QtObject {
- id: d
-
- readonly property int heightIncrement: root.grid ? root.grid.rowHeight : 0
-
- property bool dragging: false
-
- property real startGridPosY
- property int startTopHeightRows
- property int startBottomHeightRows
-
- function onTouchMoved() {
- if (!d.dragging) {
- return;
- }
-
- var gridPos = root.mapToItem(root.grid, touch1.x, touch1.y);
- var deltaRows = Math.round((gridPos.y - d.startGridPosY) / root.grid.rowHeight);
-
- var targetTopHeightRows = Math.max(d.startTopHeightRows + deltaRows, root.topAppInfo.minHeightRows);
- deltaRows = targetTopHeightRows - d.startTopHeightRows;
-
- var targetBottomHeightRows = Math.max(d.startBottomHeightRows - deltaRows, root.bottomAppInfo.minHeightRows);
- deltaRows = -targetBottomHeightRows + d.startBottomHeightRows;
-
- root.topAppInfo.heightRows = d.startTopHeightRows + deltaRows
- root.bottomAppInfo.heightRows = d.startBottomHeightRows - deltaRows
- }
- }
-}
diff --git a/tests/qmltests/tst_HomePage.qml b/tests/qmltests/tst_HomePage.qml
index f4012545..32654372 100644
--- a/tests/qmltests/tst_HomePage.qml
+++ b/tests/qmltests/tst_HomePage.qml
@@ -9,7 +9,7 @@ Item {
height: 600
QtObject {
- id: foo
+ id: redApp
property Item window
property Item loadedWindow: Rectangle { color: "red" }
@@ -25,14 +25,30 @@ Item {
}
QtObject {
- id: bar
+ id: greenApp
+ property Item window
+ property Item loadedWindow: Rectangle { color: "green" }
+
+ property bool active: false
+ property bool canBeActive: true
+
+ property int heightRows: 2
+ property int minHeightRows: 1
+
+ function start() {
+ window = loadedWindow;
+ }
+ }
+
+ QtObject {
+ id: blueApp
property Item window
property Item loadedWindow: Rectangle { color: "blue" }
property bool active: false
property bool canBeActive: true
- property int heightRows: 3
+ property int heightRows: 2
property int minHeightRows: 1
function start() {
@@ -44,8 +60,9 @@ Item {
anchors.fill: parent
widgetsList: ListModel { id: listModel }
Component.onCompleted: {
- listModel.append({"appInfo":foo})
- listModel.append({"appInfo":bar})
+ listModel.append({"appInfo":redApp})
+ listModel.append({"appInfo":greenApp})
+ listModel.append({"appInfo":blueApp})
}
}