diff --git a/interface/resources/qml/hifi/tablet/Tablet.qml b/interface/resources/qml/hifi/tablet/Tablet.qml deleted file mode 100644 index 42396073e9..0000000000 --- a/interface/resources/qml/hifi/tablet/Tablet.qml +++ /dev/null @@ -1,420 +0,0 @@ -import QtQuick 2.7 -import QtQuick.Controls 2.2 -import QtGraphicalEffects 1.0 -import QtQuick.Layouts 1.3 - -import "../../styles-uit" -import "../audio" as HifiAudio - -Item { - id: tablet - objectName: "tablet" - property int rowIndex: 6 // by default - property int columnIndex: 1 // point to 'go to location' - property int count: 0 - readonly property int buttonsOnPage: 12 - readonly property int buttonsRowsOnPage: 4 - readonly property int buttonsColumnsOnPage: 3 - - property var currentGridItems: null - property var gridPages: []; - - focus: true - - Timer { - id: gridsRecreateTimer - interval: 1 - repeat: false - onTriggered: { - doRecreateGrids() - } - } - - //fake invisible item for initial buttons creations. - //Also used as a holder for buttons list - Item { - id: fakeParent - visible: false - } - - function doRecreateGrids() { - - if (fakeParent.resources.length <= 0) { - console.warn("Tablet buttons list is empty", fakeParent.resources.length); - return; - } - - //copy buttons list to JS array to be able to sort it - var tabletButtons = []; - for (var btnIndex = 0; btnIndex < fakeParent.resources.length; btnIndex++) { - tabletButtons.push(fakeParent.resources[btnIndex]); - } - - for (var i = swipeView.count - 1; i >= 0; i--) { - swipeView.removeItem(i); - } - gridPages = []; - - swipeView.setCurrentIndex(-1); - sortButtons(tabletButtons); - - var currentPage = 0; - gridPages.push(pageComponent.createObject(null)); - - for (var buttonIndex = 0; buttonIndex < tabletButtons.length; buttonIndex++) { - var button = tabletButtons[buttonIndex] - if (button !== null) { - var grid = gridPages[currentPage]; - if (grid.children[0].children.length === buttonsOnPage) { - gridPages.push(pageComponent.createObject(null)); - currentPage = currentPage + 1; - grid = gridPages[currentPage]; - } - button.row = Math.floor(grid.children[0].children.length / buttonsColumnsOnPage); - button.column = grid.children[0].children.length % buttonsColumnsOnPage; - //reparent to actual grid - button.parent = grid.children[0]; - grid.children[0].children.push(button); - } else { - console.warn("Button is null:", buttonIndex); - } - } - for (var pageIndex = 0; pageIndex < gridPages.length; pageIndex++) { - swipeView.addItem(gridPages[pageIndex]); - } - - swipeView.setCurrentIndex(0); - } - - // used to look up a button by its uuid - function findButtonIndex(uuid) { - if (!uuid) { - return -1; - } - for (var i in fakeParent.resources) { - var child = fakeParent.resources[i]; - if (child.uuid === uuid) { - return i; - } - } - - return -1; - } - - function sortButtons(tabletButtons) { - tabletButtons.sort(function (a, b) { - if (a === null || b === null) { - return 0; - } - if (a.sortOrder === b.sortOrder) { - // subsort by stableOrder, because JS sort is not stable in qml. - return a.stableOrder - b.stableOrder; - } else { - return a.sortOrder - b.sortOrder; - } - }); - } - - function doAddButton(component, properties) { - var button = component.createObject(fakeParent); - - // copy all properites to button - var keys = Object.keys(properties).forEach(function (key) { - button[key] = properties[key]; - }); - - // pass a reference to the tabletRoot object to the button. - if (tabletRoot) { - button.tabletRoot = tabletRoot; - } else { - button.tabletRoot = parent.parent; - } - button.flickable = swipeView.contentItem; - tablet.count++ - fakeParent.resources.push(button); - gridsRecreateTimer.restart(); - - return button; - } - - Component { - id: pageComponent - Item { - Grid { - anchors { - fill: parent - topMargin: 20 - leftMargin: 30 - rightMargin: 30 - bottomMargin: 0 - } - rows: 4; columns: 3; rowSpacing: 16; columnSpacing: 16; - } - } - } - - // called by C++ code when a button should be added to the tablet - function addButtonProxy(properties) { - var component = Qt.createComponent("TabletButton.qml"); - return doAddButton(component, properties); - } - - // called by C++ code when a button should be removed from the tablet - function removeButtonProxy(properties) { - var index = findButtonIndex(properties.uuid); - if (index < 0) { - console.log("Warning: Tablet.qml could not find button with uuid = " + properties.uuid); - } else { - tablet.count--; - fakeParent.resources[index] = null; - //redraw grids - gridsRecreateTimer.restart(); - } - } - - Rectangle { - id: bgTopBar - height: 90 - - anchors { - top: parent.top - topMargin: 0 - left: parent.left - leftMargin: 0 - right: parent.right - rightMargin: 0 - } - - gradient: Gradient { - GradientStop { - position: 0 - color: "#2b2b2b" - } - - GradientStop { - position: 1 - color: "#1e1e1e" - } - } - - HifiAudio.MicBar { - anchors { - left: parent.left - leftMargin: 30 - verticalCenter: parent.verticalCenter - } - } - - Item { - width: 150 - height: 50 - anchors.right: parent.right - anchors.rightMargin: 30 - anchors.verticalCenter: parent.verticalCenter - - ColumnLayout { - anchors.fill: parent - - RalewaySemiBold { - text: Account.loggedIn ? qsTr("Log out") : qsTr("Log in") - horizontalAlignment: Text.AlignRight - anchors.right: parent.right - font.pixelSize: 20 - color: "#afafaf" - } - - RalewaySemiBold { - visible: Account.loggedIn - height: Account.loggedIn ? parent.height/2 - parent.spacing/2 : 0 - text: Account.loggedIn ? "[" + tabletRoot.usernameShort + "]" : "" - horizontalAlignment: Text.AlignRight - anchors.right: parent.right - font.pixelSize: 20 - color: "#afafaf" - } - } - - MouseArea { - anchors.fill: parent - onClicked: { - if (!Account.loggedIn) { - DialogsManager.showLoginDialog() - } else { - Account.logOut() - } - } - } - } - } - - Rectangle { - id: bgMain - gradient: Gradient { - GradientStop { - position: 0 - color: "#2b2b2b" - } - - GradientStop { - position: 1 - color: "#0f212e" - } - } - anchors.bottom: parent.bottom - anchors.right: parent.right - anchors.left: parent.left - anchors.top: bgTopBar.bottom - - SwipeView { - id: swipeView - clip: false - currentIndex: -1 - property int previousIndex: -1 - - onCurrentIndexChanged: { - if (swipeView.currentIndex < 0 - || swipeView.itemAt(swipeView.currentIndex) === null - || swipeView.itemAt(swipeView.currentIndex).children[0] === null - || swipeView.itemAt(swipeView.currentIndex).children[0].children === null) { - return; - } - - currentGridItems = swipeView.itemAt(swipeView.currentIndex).children[0].children; - - var row = rowIndex < 0 ? 0 : rowIndex; - var column = previousIndex > swipeView.currentIndex ? buttonsColumnsOnPage - 1 : 0; - var index = row * buttonsColumnsOnPage + column; - if (index < 0 || index >= currentGridItems.length) { - column = 0; - row = 0; - } - rowIndex = row; - columnIndex = column; - setCurrentItemState("hover state"); - previousIndex = currentIndex; - } - - hoverEnabled: true - anchors { - left: parent.left - right: parent.right - top: parent.top - bottom: pageIndicator.top - } - } - - PageIndicator { - id: pageIndicator - currentIndex: swipeView.currentIndex - - delegate: Item { - width: 15 - height: 15 - - Rectangle { - anchors.centerIn: parent - opacity: index === pageIndicator.currentIndex ? 0.95 : 0.45 - implicitWidth: index === pageIndicator.currentIndex ? 15 : 10 - implicitHeight: implicitWidth - radius: width/2 - color: "white" - Behavior on opacity { - OpacityAnimator { - duration: 100 - } - } - } - } - - interactive: false - anchors.bottom: parent.bottom - anchors.horizontalCenter: parent.horizontalCenter - count: swipeView.count - } - } - - function setCurrentItemState(state) { - var buttonIndex = rowIndex * buttonsColumnsOnPage + columnIndex; - if (currentGridItems !== null && buttonIndex >= 0 && buttonIndex < currentGridItems.length) { - if (currentGridItems[buttonIndex].isActive) { - currentGridItems[buttonIndex].state = "active state"; - } else { - currentGridItems[buttonIndex].state = state; - } - } - } - - function nextItem() { - setCurrentItemState("base state"); - var nextColumnIndex = columnIndex + 1; - var index = rowIndex * buttonsColumnsOnPage + nextColumnIndex; - if (index >= currentGridItems.length || nextColumnIndex >= buttonsColumnsOnPage) { - if (swipeView.currentIndex < swipeView.count - 1) { - swipeView.incrementCurrentIndex(); - } else { - setCurrentItemState("hover state"); - } - return; - } - columnIndex = nextColumnIndex; - setCurrentItemState("hover state"); - } - - function previousItem() { - setCurrentItemState("base state"); - var column = columnIndex - 1; - - if (column < 0) { - if (swipeView.currentIndex > 0) { - swipeView.decrementCurrentIndex(); - } else { - setCurrentItemState("hover state"); - } - return; - } - columnIndex = column; - setCurrentItemState("hover state"); - } - - function upItem() { - setCurrentItemState("base state"); - var row = rowIndex - 1; - - if (row < 0 ) { - row = buttonsRowsOnPage - 1; - var index = row * buttonsColumnsOnPage + columnIndex; - while (index >= currentGridItems.length) { - row = row - 1; - index = row * buttonsColumnsOnPage + columnIndex; - } - } - rowIndex = row; - setCurrentItemState("hover state"); - } - - function downItem() { - setCurrentItemState("base state"); - rowIndex = rowIndex + 1; - var index = rowIndex * buttonsColumnsOnPage + columnIndex; - if (index >= currentGridItems.length) { - rowIndex = 0; - } - setCurrentItemState("hover state"); - } - - function selectItem() { - var index = rowIndex*buttonsColumnsOnPage + columnIndex; - if (currentGridItems !== null && index >= 0 && index < currentGridItems.length) { - currentGridItems[index].clicked(); - if (tabletRoot) { - tabletRoot.playButtonClickSound(); - } - } - } - - Keys.onRightPressed: nextItem(); - Keys.onLeftPressed: previousItem(); - Keys.onDownPressed: downItem(); - Keys.onUpPressed: upItem(); - Keys.onReturnPressed: selectItem(); -} diff --git a/interface/resources/qml/hifi/tablet/TabletHome.qml b/interface/resources/qml/hifi/tablet/TabletHome.qml index 3f9451436c..4bacf078e7 100644 --- a/interface/resources/qml/hifi/tablet/TabletHome.qml +++ b/interface/resources/qml/hifi/tablet/TabletHome.qml @@ -1,4 +1,5 @@ -import QtQuick 2.5 +import QtQuick 2.7 +import QtQuick.Controls 2.2 import QtGraphicalEffects 1.0 import QtQuick.Layouts 1.3 @@ -10,7 +11,16 @@ Item { id: tablet objectName: "tablet" property var tabletProxy: Tablet.getTablet("com.highfidelity.interface.tablet.system"); - + + property int rowIndex: 6 // by default + property int columnIndex: 1 // point to 'go to location' + readonly property int buttonsOnPage: 12 + readonly property int buttonsRowsOnPage: 4 + readonly property int buttonsColumnsOnPage: 3 + + property var currentGridItems: null + property var gridPages: []; + Rectangle { id: bgTopBar height: 90 @@ -85,7 +95,6 @@ Item { Rectangle { id: bgMain - clip: true gradient: Gradient { GradientStop { position: 0 @@ -102,58 +111,200 @@ Item { anchors.left: parent.left anchors.top: bgTopBar.bottom - GridView { - id: flickable - anchors.top: parent.top - anchors.topMargin: 15 - anchors.bottom: parent.bottom - anchors.horizontalCenter: parent.horizontalCenter - width: cellWidth * 3 - cellHeight: 145 - cellWidth: 145 - model: tabletProxy.buttons - delegate: Item { - width: flickable.cellWidth - height: flickable.cellHeight - property var proxy: modelData + SwipeView { + id: swipeView + clip: false + currentIndex: -1 + property int previousIndex: -1 - TabletButton { - id: tabletButton - anchors.centerIn: parent - onClicked: modelData.clicked() - state: wrapper.GridView.isCurrentItem ? "hover state" : "base state" + Repeater { + id: pageRepeater + model: Math.ceil(tabletProxy.buttons.rowCount() / buttonsOnPage) + onCountChanged: console.warn("repeat count", count, tabletProxy.buttons.rowCount()) + onItemAdded: { + item.pageIndex = index } - Connections { - target: modelData; - onPropertiesChanged: { - updateProperties(); + delegate: Item { + id: page + property int pageIndex + onPageIndexChanged: console.warn("page index", pageIndex) + Grid { + anchors { + fill: parent + topMargin: 20 + leftMargin: 30 + rightMargin: 30 + bottomMargin: 0 + } + rows: 4; columns: 3; rowSpacing: 16; columnSpacing: 16; + Repeater { + id: buttonsRepeater + model: tabletProxy.buttons.rowCount() - (((page.pageIndex + 1) * buttonsOnPage) % buttonsOnPage) + delegate: Item { + id: wrapper + width: 129 + height: 129 + property var proxy: modelData + + TabletButton { + id: tabletButton + anchors.centerIn: parent + onClicked: modelData.clicked() + state: wrapper.GridView.isCurrentItem ? "hover state" : "base state" + } + + Connections { + target: modelData; + onPropertiesChanged: { + updateProperties(); + } + } + + Component.onCompleted: updateProperties() + + function updateProperties() { + var keys = Object.keys(modelData.properties).forEach(function (key) { + if (tabletButton[key] !== modelData.properties[key]) { + tabletButton[key] = modelData.properties[key]; + } + }); + } + } + } + Component.onCompleted: { +// tabletProxy.buttonsOnPage.setCurrentPage(page.index); +// buttonsRepeater.model = tabletProxy.buttonsOnPage; +// console.warn("buttons on page:", page.index, tabletProxy.buttonsOnPage.rowCount()) + } } } + } - Component.onCompleted: updateProperties() + onCurrentIndexChanged: { +// if (swipeView.currentIndex < 0 +// || swipeView.itemAt(swipeView.currentIndex) === null +// || swipeView.itemAt(swipeView.currentIndex).children[0] === null +// || swipeView.itemAt(swipeView.currentIndex).children[0].children === null) { +// return; +// } - function updateProperties() { - var keys = Object.keys(modelData.properties).forEach(function (key) { - if (tabletButton[key] !== modelData.properties[key]) { - tabletButton[key] = modelData.properties[key]; +// currentGridItems = swipeView.itemAt(swipeView.currentIndex).children[0].children; + +// var row = rowIndex < 0 ? 0 : rowIndex; +// var column = previousIndex > swipeView.currentIndex ? buttonsColumnsOnPage - 1 : 0; +// var index = row * buttonsColumnsOnPage + column; +// if (index < 0 || index >= currentGridItems.length) { +// column = 0; +// row = 0; +// } +// rowIndex = row; +// columnIndex = column; +// setCurrentItemState("hover state"); +// previousIndex = currentIndex; + } + + hoverEnabled: true + anchors { + left: parent.left + right: parent.right + top: parent.top + bottom: pageIndicator.top + } + } + + PageIndicator { + id: pageIndicator + currentIndex: swipeView.currentIndex + + delegate: Item { + width: 15 + height: 15 + + Rectangle { + anchors.centerIn: parent + opacity: index === pageIndicator.currentIndex ? 0.95 : 0.45 + implicitWidth: index === pageIndicator.currentIndex ? 15 : 10 + implicitHeight: implicitWidth + radius: width/2 + color: "white" + Behavior on opacity { + OpacityAnimator { + duration: 100 } - }); + } } } + + interactive: false + anchors.bottom: parent.bottom + anchors.horizontalCenter: parent.horizontalCenter + count: swipeView.count } +// GridView { +// id: flickable +// anchors.top: parent.top +// anchors.topMargin: 15 +// anchors.bottom: parent.bottom +// anchors.horizontalCenter: parent.horizontalCenter +// width: cellWidth * 3 +// cellHeight: 145 +// cellWidth: 145 +// flow: GridView.LeftToRight +// model: tabletProxy.buttons +// delegate: Item { +// width: flickable.cellWidth +// height: flickable.cellHeight +// property var proxy: modelData + +// TabletButton { +// id: tabletButton +// anchors.centerIn: parent +// onClicked: modelData.clicked() +// state: wrapper.GridView.isCurrentItem ? "hover state" : "base state" +// } + +// Connections { +// target: modelData; +// onPropertiesChanged: { +// updateProperties(); +// } +// } + +// Component.onCompleted: updateProperties() + +// function updateProperties() { +// var keys = Object.keys(modelData.properties).forEach(function (key) { +// if (tabletButton[key] !== modelData.properties[key]) { +// tabletButton[key] = modelData.properties[key]; +// } +// }); +// } +// } +// } } - Keys.onRightPressed: flickable.moveCurrentIndexRight(); - Keys.onLeftPressed: flickable.moveCurrentIndexLeft(); - Keys.onDownPressed: flickable.moveCurrentIndexDown(); - Keys.onUpPressed: flickable.moveCurrentIndexUp(); - Keys.onReturnPressed: { - if (flickable.currentItem) { - flickable.currentItem.proxy.clicked(); - if (tabletRoot) { - tabletRoot.playButtonClickSound(); + function setCurrentItemState(state) { + var buttonIndex = rowIndex * buttonsColumnsOnPage + columnIndex; + if (currentGridItems !== null && buttonIndex >= 0 && buttonIndex < currentGridItems.length) { + if (currentGridItems[buttonIndex].isActive) { + currentGridItems[buttonIndex].state = "active state"; + } else { + currentGridItems[buttonIndex].state = state; } } } + +// Keys.onRightPressed: flickable.moveCurrentIndexRight(); +// Keys.onLeftPressed: flickable.moveCurrentIndexLeft(); +// Keys.onDownPressed: flickable.moveCurrentIndexDown(); +// Keys.onUpPressed: flickable.moveCurrentIndexUp(); +// Keys.onReturnPressed: { +// if (flickable.currentItem) { +// flickable.currentItem.proxy.clicked(); +// if (tabletRoot) { +// tabletRoot.playButtonClickSound(); +// } +// } +// } } diff --git a/interface/resources/qml/js/Utils.jsc b/interface/resources/qml/js/Utils.jsc index ab20e996b9..eded46b485 100644 Binary files a/interface/resources/qml/js/Utils.jsc and b/interface/resources/qml/js/Utils.jsc differ diff --git a/libraries/ui/src/ui/TabletScriptingInterface.cpp b/libraries/ui/src/ui/TabletScriptingInterface.cpp index 9bdd659279..d192f4a7bc 100644 --- a/libraries/ui/src/ui/TabletScriptingInterface.cpp +++ b/libraries/ui/src/ui/TabletScriptingInterface.cpp @@ -92,8 +92,32 @@ void TabletButtonListModel::removeButton(TabletButtonProxy* button) { endResetModel(); } +TabletButtonsProxyModel::TabletButtonsProxyModel(QObject *parent) + : QSortFilterProxyModel(parent){ +} + +int TabletButtonsProxyModel::currentPage() const { + return _currentPage; +} + +void TabletButtonsProxyModel::setCurrentPage(int currentPage) { + _currentPage = currentPage; + invalidateFilter(); +} + +bool TabletButtonsProxyModel::filterAcceptsRow(int sourceRow, + const QModelIndex &sourceParent) const { + return (sourceRow >= _currentPage*12 && sourceRow < _currentPage*12); +} + +bool TabletButtonsProxyModel::lessThan(const QModelIndex &left, + const QModelIndex &right) const { + return true; +} + TabletScriptingInterface::TabletScriptingInterface() { qmlRegisterType("TabletScriptingInterface", 1, 0, "TabletEnums"); + qmlRegisterType("TabletScriptingInterface", 1, 0, "TabletButtonsProxyModel"); } TabletScriptingInterface::~TabletScriptingInterface() { @@ -276,6 +300,7 @@ TabletProxy::TabletProxy(QObject* parent, const QString& name) : QObject(parent) qCWarning(uiLogging) << "Creating tablet proxy on wrong thread " << _name; } connect(this, &TabletProxy::tabletShownChanged, this, &TabletProxy::onTabletShown); + _buttonsProxy.setSourceModel(&_buttons); } TabletProxy::~TabletProxy() { diff --git a/libraries/ui/src/ui/TabletScriptingInterface.h b/libraries/ui/src/ui/TabletScriptingInterface.h index d24b3b6947..1fa1165a02 100644 --- a/libraries/ui/src/ui/TabletScriptingInterface.h +++ b/libraries/ui/src/ui/TabletScriptingInterface.h @@ -16,6 +16,7 @@ #include #include #include +#include #include #include @@ -118,6 +119,26 @@ protected: Q_DECLARE_METATYPE(TabletButtonListModel*); +class TabletButtonsProxyModel : public QSortFilterProxyModel +{ + Q_OBJECT + +public: + TabletButtonsProxyModel(QObject *parent = 0); + + int currentPage() const; + Q_INVOKABLE void setCurrentPage(int currentPage); + +protected: + bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override; + bool lessThan(const QModelIndex &left, const QModelIndex &right) const override; + +private: + int _currentPage; +}; + +Q_DECLARE_METATYPE(TabletButtonsProxyModel*); + /**jsdoc * @class TabletProxy * @property name {string} READ_ONLY: name of this tablet @@ -131,6 +152,7 @@ class TabletProxy : public QObject { Q_PROPERTY(bool landscape READ getLandscape WRITE setLandscape) Q_PROPERTY(bool tabletShown MEMBER _tabletShown NOTIFY tabletShownChanged) Q_PROPERTY(TabletButtonListModel* buttons READ getButtons CONSTANT) + Q_PROPERTY(TabletButtonsProxyModel* buttonsOnPage READ getButtonsOnPage CONSTANT) public: TabletProxy(QObject* parent, const QString& name); ~TabletProxy(); @@ -234,6 +256,8 @@ public: QQuickItem* getQmlMenu() const; TabletButtonListModel* getButtons() { return &_buttons; } + TabletButtonsProxyModel* getButtonsOnPage() { return &_buttonsProxy; } + signals: /**jsdoc * Signaled when this tablet receives an event from the html/js embedded in the tablet @@ -293,6 +317,7 @@ protected: bool _showRunningScripts { false }; TabletButtonListModel _buttons; + TabletButtonsProxyModel _buttonsProxy; }; Q_DECLARE_METATYPE(TabletProxy*);