Preliminary proxy model

This commit is contained in:
vladest 2017-12-21 19:32:13 +01:00
parent d6e1a3ea41
commit 2bca0939b3
5 changed files with 242 additions and 461 deletions

View file

@ -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();
}

View file

@ -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();
// }
// }
// }
}

Binary file not shown.

View file

@ -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>("TabletScriptingInterface", 1, 0, "TabletEnums");
qmlRegisterType<TabletButtonsProxyModel>("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() {

View file

@ -16,6 +16,7 @@
#include <QtCore/QUuid>
#include <QtCore/QVariant>
#include <QtCore/QAbstractListModel>
#include <QSortFilterProxyModel>
#include <QtScript/QScriptValue>
#include <QtScript/QScriptEngine>
@ -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*);