added context menu to tablet

This commit is contained in:
Dante Ruiz 2017-01-06 22:29:07 +00:00
parent 3fab901b49
commit 6c51edec77
9 changed files with 460 additions and 16 deletions

View file

@ -1,12 +1,27 @@
import QtQuick 2.0
import QtQuick 2.5
import QtGraphicalEffects 1.0
"../../menus"
import QtQuick.Controls 1.4
import QtQml 2.2
import "."
Item {
id: tabletMenu
objectName: "menu"
property var rootMenu: Menu { objectName: "rootMenu" }
objectName: "tabletMenu"
Rectangle {
color: "#2b2b2b"
width: parent.width
height: parent.height
property var rootMenu: Menu { objectName:"rootMenu" }
property var point: Qt.point(50, 50)
TabletMouseHandler { id: menuPopperUpper }
function setRootMenu(menu) {
tabletMenu.rootMenu = menu
buildMenu()
}
function buildMenu() {
menuPopperUpper.popup(tabletMenu, rootMenu.items)
}
}

View file

@ -0,0 +1,113 @@
//
// VrMenuItem.qml
//
// Created by Bradley Austin Davis on 29 Apr 2015
// Copyright 2015 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
import QtQuick 2.5
import QtQuick.Controls 1.4
import QtQuick.Controls.Styles 1.4
import "../../controls-uit"
import "../../styles-uit"
Item {
id: root
HifiConstants { id: hifi }
property alias text: label.text
property var source
implicitHeight: source.visible ? 2 * label.implicitHeight : 0
implicitWidth: 2 * hifi.dimensions.menuPadding.x + check.width + label.width + tail.width
visible: source.visible
width: parent.width
CheckBox {
id: check
// FIXME: Should use radio buttons if source.exclusiveGroup.
anchors {
left: parent.left
leftMargin: hifi.dimensions.menuPadding.x
top: label.top
topMargin: 0
}
width: 20
visible: source.visible && source.type === 1 && source.checkable
checked: setChecked()
function setChecked() {
if (!source || source.type !== 1 || !source.checkable) {
return false;
}
// FIXME this works for native QML menus but I don't think it will
// for proxied QML menus
return source.checked;
}
}
RalewaySemiBold {
id: label
size: hifi.fontSizes.rootMenu
font.capitalization: isSubMenu ? Font.MixedCase : Font.AllUppercase
anchors.left: check.right
anchors.verticalCenter: parent.verticalCenter
verticalAlignment: Text.AlignVCenter
color: source.enabled ? hifi.colors.baseGrayShadow : hifi.colors.baseGrayShadow50
enabled: source.visible && (source.type !== 0 ? source.enabled : false)
visible: source.visible
}
Item {
id: separator
anchors {
fill: parent
leftMargin: hifi.dimensions.menuPadding.x + check.width
rightMargin: hifi.dimensions.menuPadding.x + tail.width
}
visible: source.type === MenuItemType.Separator
Rectangle {
anchors {
left: parent.left
right: parent.right
verticalCenter: parent.verticalCenter
}
height: 1
color: hifi.colors.lightGray50
}
}
Item {
id: tail
width: 48 + (shortcut.visible ? shortcut.width : 0)
anchors {
verticalCenter: parent.verticalCenter
right: parent.right
rightMargin: hifi.dimensions.menuPadding.x
}
RalewayLight {
id: shortcut
text: source.shortcut ? source.shortcut : ""
size: hifi.fontSizes.shortcutText
color: hifi.colors.baseGrayShadow
anchors.verticalCenter: parent.verticalCenter
anchors.right: parent.right
anchors.rightMargin: 15
visible: source.visible && text != ""
}
HiFiGlyphs {
text: hifi.glyphs.disclosureExpand
color: source.enabled ? hifi.colors.baseGrayShadow : hifi.colors.baseGrayShadow25
size: 2 * hifi.fontSizes.rootMenuDisclosure
anchors.verticalCenter: parent.verticalCenter
anchors.right: parent.right
horizontalAlignment: Text.AlignRight
visible: source.visible && (source.type === 2)
}
}
}

View file

@ -0,0 +1,117 @@
//
// VrMenuView.qml
//
// Created by Bradley Austin Davis on 18 Jan 2016
// Copyright 2016 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
import QtQuick 2.5
import QtQuick.Controls 1.4
import QtQuick.Controls.Styles 1.4
import "../../styles-uit"
FocusScope {
id: root
implicitHeight: background.height
implicitWidth: background.width
property alias currentItem: listView.currentItem
property alias model: listView.model
property bool isSubMenu: false
signal selected(var item)
HifiConstants { id: hifi }
Rectangle {
id: background
anchors.fill: listView
radius: hifi.dimensions.borderRadius
border.width: hifi.dimensions.borderWidth
border.color: hifi.colors.lightGrayText80
color: isSubMenu ? hifi.colors.faintGray : hifi.colors.faintGray80
}
ListView {
id: listView
x: 8; y: 8
width: parent.width
height: parent.height
topMargin: hifi.dimensions.menuPadding.y
onEnabledChanged: recalcSize();
onVisibleChanged: recalcSize();
onCountChanged: recalcSize();
focus: true
highlightMoveDuration: 0
highlight: Rectangle {
anchors {
left: parent ? parent.left : undefined
right: parent ? parent.right : undefined
leftMargin: hifi.dimensions.borderWidth
rightMargin: hifi.dimensions.borderWidth
}
color: hifi.colors.white
}
delegate: TabletMenuItem {
text: name
source: item
onImplicitHeightChanged: listView.recalcSize()
onImplicitWidthChanged: listView.recalcSize()
MouseArea {
anchors.fill: parent
hoverEnabled: true
onEntered: listView.currentIndex = index
onClicked: root.selected(item)
}
}
function recalcSize() {
if (model.count !== count || !visible) {
return;
}
var originalIndex = currentIndex;
var maxWidth = width;
var newHeight = 0;
for (var i = 0; i < count; ++i) {
currentIndex = i;
if (!currentItem) {
continue;
}
if (currentItem && currentItem.implicitWidth > maxWidth) {
maxWidth = currentItem.implicitWidth
}
if (currentItem.visible) {
newHeight += currentItem.implicitHeight
}
}
newHeight += 2 * hifi.dimensions.menuPadding.y; // White space at top and bottom.
if (maxWidth > width) {
width = maxWidth;
}
if (newHeight > height) {
height = newHeight
}
currentIndex = originalIndex;
}
function previousItem() { currentIndex = (currentIndex + count - 1) % count; }
function nextItem() { currentIndex = (currentIndex + count + 1) % count; }
function selectCurrentItem() { if (currentIndex != -1) root.selected(currentItem.source); }
Keys.onUpPressed: previousItem();
Keys.onDownPressed: nextItem();
Keys.onSpacePressed: selectCurrentItem();
Keys.onRightPressed: selectCurrentItem();
Keys.onReturnPressed: selectCurrentItem();
}
}

View file

@ -0,0 +1,163 @@
//
// MessageDialog.qml
//
// Created by Bradley Austin Davis on 18 Jan 2016
// Copyright 2016 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
import QtQuick 2.5
import QtQuick.Controls 1.4
import "."
Item {
id: root
anchors.fill: parent
objectName: "tabletMenuHandlerItem"
MouseArea {
id: menuRoot;
objectName: "tabletMenuHandlerMouseArea"
anchors.fill: parent
enabled: d.topMenu !== null
onClicked: {
d.clearMenus();
}
}
QtObject {
id: d
property var menuStack: []
property var topMenu: null;
property var modelMaker: Component { ListModel { } }
property var menuViewMaker: Component {
TabletMenuView {
id: subMenu
onSelected: d.handleSelection(subMenu, currentItem, item)
}
}
property var delay: Timer { // No setTimeout in QML.
property var menuItem: null;
interval: 0
repeat: false
running: false
function trigger(item) { // Capture item and schedule asynchronous Timer.
menuItem = item;
start();
}
onTriggered: {
menuItem.trigger(); // Now trigger the item.
}
}
function toModel(items) {
var result = modelMaker.createObject(tabletMenu);
for (var i = 0; i < items.length; ++i) {
var item = items[i];
if (!item.visible) continue;
console.log(item.title)
switch (item.type) {
case MenuItemType.Menu:
result.append({"name": item.title, "item": item})
break;
case MenuItemType.Item:
result.append({"name": item.text, "item": item})
break;
case MenuItemType.Separator:
result.append({"name": "", "item": item})
break;
}
}
return result;
}
function popMenu() {
if (menuStack.length) {
menuStack.pop().destroy();
}
if (menuStack.length) {
topMenu = menuStack[menuStack.length - 1];
topMenu.focus = true;
} else {
topMenu = null;
//offscreenFlags.navigationFocused = false;
menuRoot.enabled = false;
}
}
function pushMenu(newMenu) {
menuStack.push(newMenu);
topMenu = newMenu;
topMenu.focus = true;
//offscreenFlags.navigationFocused = true;
}
function clearMenus() {
while (menuStack.length) {
popMenu()
}
}
function clampMenuPosition(menu) {
var margins = 0;
if (menu.x < margins) {
menu.x = margins
} else if ((menu.x + menu.width + margins) > root.width) {
menu.x = root.width - (menu.width + margins);
}
if (menu.y < 0) {
menu.y = margins
} else if ((menu.y + menu.height + margins) > root.height) {
menu.y = root.height - (menu.height + margins);
}
}
function buildMenu(items, targetPosition) {
var model = toModel(items);
// Menus must be childed to desktop for Z-ordering
var newMenu = menuViewMaker.createObject(tabletMenu, { model: model, isSubMenu: topMenu !== null });
pushMenu(newMenu);
return newMenu;
}
function handleSelection(parentMenu, selectedItem, item) {
while (topMenu && topMenu !== parentMenu) {
popMenu();
}
switch (item.type) {
case MenuItemType.Menu:
var target = Qt.vector2d(topMenu.x, topMenu.y).plus(Qt.vector2d(selectedItem.x + 96, selectedItem.y));
buildMenu(item.items, target).objectName = item.title;
break;
case MenuItemType.Item:
console.log("Triggering " + item.text)
// Don't block waiting for modal dialogs and such that the menu might open.
delay.trigger(item);
clearMenus();
break;
}
}
}
function popup(parent, items) {
d.clearMenus();
menuRoot.enabled = true;
d.buildMenu(items, point);
}
function closeLastMenu() {
if (d.menuStack.length) {
d.popMenu();
return true;
}
return false;
}
}

View file

@ -100,9 +100,9 @@ void TabletProxy::setQmlTabletRoot(QQuickItem* qmlTabletRoot, QObject* qmlOffscr
void TabletProxy::gotoMenuScreen() {
if (_qmlTabletRoot) {
_qmlTabletRoot->setProperty(LOADER_SOURCE_PROPERTY_NAME, TABLET_SOURCE_URL);
//auto loader = _qmlTabletRoot->findChild<QQuickItem*>("loader");
//QObject::connect(loader, SIGNAL(loaded()), this, SLOT(addButtonsToMenuScreen()));
_qmlTabletRoot->setProperty(LOADER_SOURCE_PROPERTY_NAME, VRMENU_SOURCE_URL);
auto loader = _qmlTabletRoot->findChild<QQuickItem*>("loader");
QObject::connect(loader, SIGNAL(loaded()), this, SLOT(addButtonsToMenuScreen()));
QMetaObject::invokeMethod(_qmlTabletRoot, "loadSource", Q_ARG(const QVariant&, QVariant(VRMENU_SOURCE_URL)));
}
}
@ -172,7 +172,7 @@ void TabletProxy::removeButton(QObject* tabletButtonProxy) {
void TabletProxy::updateAudioBar(const double micLevel) {
auto tablet = getQmlTablet();
if (!tablet) {
qCCritical(scriptengine) << "Could not find tablet in TabletRoot.qml";
//qCCritical(scriptengine) << "Could not find tablet in TabletRoot.qml";
} else {
QMetaObject::invokeMethod(tablet, "setMicLevel", Qt::AutoConnection, Q_ARG(QVariant, QVariant(micLevel)));
}
@ -208,14 +208,15 @@ void TabletProxy::addButtonsToMenuScreen() {
return;
}
QQuickItem* VrMenu = loader->findChild<QQuickItem*>("VrMenu");
QQuickItem* VrMenu = loader->findChild<QQuickItem*>("tabletMenu");
if (!VrMenu) {
qDebug() << "----------> could not find vr menu";
return;
}
QString name = "Menu";
QVariant returnedValue;
auto offscreenUi = DependencyManager::get<OffscreenUi>();
QObject* menu = offscreenUi->getRootMenu();
QMetaObject::invokeMethod(VrMenu, "setRootMenu", Qt::AutoConnection, Q_ARG(QVariant, QVariant::fromValue(menu)));
}
void TabletProxy::removeButtonsFromHomeScreen() {

View file

@ -524,6 +524,10 @@ QQuickItem* OffscreenUi::getDesktop() {
return _desktop;
}
QObject* OffscreenUi::getRootMenu() {
return getRootItem()->findChild<QObject*>("rootMenu");
}
QQuickItem* OffscreenUi::getToolWindow() {
return _toolWindow;
}

View file

@ -50,7 +50,7 @@ public:
// Setting pinned to true will hide all overlay elements on the desktop that don't have a pinned flag
void setPinned(bool pinned = true);
void togglePinned();
void setConstrainToolbarToCenterX(bool constrained);
@ -59,7 +59,7 @@ public:
QObject* getFlags();
QQuickItem* getDesktop();
QQuickItem* getToolWindow();
QObject* getRootMenu();
enum Icon {
ICON_NONE = 0,
ICON_QUESTION,

View file

@ -35,7 +35,8 @@ var DEFAULT_SCRIPTS = [
"system/snapshot.js",
"system/help.js",
"system/bubble.js",
"system/tablet-ui/tabletUI.js"
"system/tablet-ui/tabletUI.js",
"system/menu.js"
];
// add a menu item for debugging

30
scripts/system/menu.js Normal file
View file

@ -0,0 +1,30 @@
//
// menu.js
// scripts/system/
//
// Created by Dante Ruiz on 5 Jun 2017
// Copyright 2016 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
(function() {
var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system");
var button = tablet.addButton({
icon: "icons/tablet-icons/menu-i.svg",
text: "Menu"
});
function onClicked() {
tablet.gotoMenuScreen();
}
button.clicked.connect(onClicked);
Script.scriptEnding.connect(function () {
button.clicked.disconnect(onClicked);
tablet.removeButton(button);
})
}());