mirror of
https://github.com/lubosz/overte.git
synced 2025-04-24 03:53:52 +02:00
commit
f1f8f6323a
34 changed files with 769 additions and 371 deletions
|
@ -11,9 +11,12 @@
|
|||
var POLL_FREQUENCY = 500; // ms
|
||||
var MAX_WARNINGS = 3;
|
||||
var numWarnings = 0;
|
||||
var isKeyboardRaised = false;
|
||||
var isNumericKeyboard = false;
|
||||
var KEYBOARD_HEIGHT = 200;
|
||||
|
||||
function shouldRaiseKeyboard() {
|
||||
if (document.activeElement.nodeName == "INPUT" || document.activeElement.nodeName == "TEXTAREA") {
|
||||
if (document.activeElement.nodeName === "INPUT" || document.activeElement.nodeName === "TEXTAREA") {
|
||||
return true;
|
||||
} else {
|
||||
// check for contenteditable attribute
|
||||
|
@ -27,15 +30,39 @@
|
|||
}
|
||||
};
|
||||
|
||||
function shouldSetNumeric() {
|
||||
return document.activeElement.type === "number";
|
||||
};
|
||||
|
||||
setInterval(function () {
|
||||
var event = shouldRaiseKeyboard() ? "_RAISE_KEYBOARD" : "_LOWER_KEYBOARD";
|
||||
if (typeof EventBridge != "undefined") {
|
||||
EventBridge.emitWebEvent(event);
|
||||
} else {
|
||||
if (numWarnings < MAX_WARNINGS) {
|
||||
console.log("WARNING: no global EventBridge object found");
|
||||
numWarnings++;
|
||||
var keyboardRaised = shouldRaiseKeyboard();
|
||||
var numericKeyboard = shouldSetNumeric();
|
||||
|
||||
if (keyboardRaised !== isKeyboardRaised || numericKeyboard !== isNumericKeyboard) {
|
||||
|
||||
if (typeof EventBridge !== "undefined") {
|
||||
EventBridge.emitWebEvent(
|
||||
keyboardRaised ? ("_RAISE_KEYBOARD" + (numericKeyboard ? "_NUMERIC" : "")) : "_LOWER_KEYBOARD"
|
||||
);
|
||||
} else {
|
||||
if (numWarnings < MAX_WARNINGS) {
|
||||
console.log("WARNING: no global EventBridge object found");
|
||||
numWarnings++;
|
||||
}
|
||||
}
|
||||
|
||||
if (!isKeyboardRaised) {
|
||||
var delta = document.activeElement.getBoundingClientRect().bottom + 10
|
||||
- (document.body.clientHeight - KEYBOARD_HEIGHT);
|
||||
if (delta > 0) {
|
||||
setTimeout(function () {
|
||||
document.body.scrollTop += delta;
|
||||
}, 500); // Allow time for keyboard to be raised in QML.
|
||||
}
|
||||
}
|
||||
|
||||
isKeyboardRaised = keyboardRaised;
|
||||
isNumericKeyboard = numericKeyboard;
|
||||
}
|
||||
}, POLL_FREQUENCY);
|
||||
})();
|
||||
|
|
|
@ -69,8 +69,12 @@ Window {
|
|||
|
||||
AddressBarDialog {
|
||||
id: addressBarDialog
|
||||
|
||||
property bool keyboardRaised: false
|
||||
property bool punctuationMode: false
|
||||
|
||||
implicitWidth: backgroundImage.width
|
||||
implicitHeight: backgroundImage.height
|
||||
implicitHeight: backgroundImage.height + (keyboardRaised ? 200 : 0)
|
||||
|
||||
// The buttons have their button state changed on hover, so we have to manually fix them up here
|
||||
onBackEnabledChanged: backArrow.buttonState = addressBarDialog.backEnabled ? 1 : 0;
|
||||
|
@ -252,8 +256,8 @@ Window {
|
|||
}
|
||||
|
||||
Window {
|
||||
width: 938;
|
||||
height: 625;
|
||||
width: 938
|
||||
height: 625
|
||||
scale: 0.8 // Reset scale of Window to 1.0 (counteract address bar's scale value of 1.25)
|
||||
HifiControls.WebView {
|
||||
anchors.fill: parent;
|
||||
|
@ -270,6 +274,35 @@ Window {
|
|||
horizontalCenter: scroll.horizontalCenter;
|
||||
}
|
||||
}
|
||||
|
||||
// virtual keyboard, letters
|
||||
HifiControls.Keyboard {
|
||||
id: keyboard1
|
||||
y: parent.keyboardRaised ? parent.height : 0
|
||||
height: parent.keyboardRaised ? 200 : 0
|
||||
visible: parent.keyboardRaised && !parent.punctuationMode
|
||||
enabled: parent.keyboardRaised && !parent.punctuationMode
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 0
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 0
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.bottomMargin: 0
|
||||
}
|
||||
|
||||
HifiControls.KeyboardPunctuation {
|
||||
id: keyboard2
|
||||
y: parent.keyboardRaised ? parent.height : 0
|
||||
height: parent.keyboardRaised ? 200 : 0
|
||||
visible: parent.keyboardRaised && parent.punctuationMode
|
||||
enabled: parent.keyboardRaised && parent.punctuationMode
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 0
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 0
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.bottomMargin: 0
|
||||
}
|
||||
}
|
||||
|
||||
function getRequest(url, cb) { // cb(error, responseOfCorrectContentType) of url. General for 'get' text/html/json, but without redirects.
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import QtQuick 2.5
|
||||
import QtQuick.Controls 1.2
|
||||
import QtWebChannel 1.0
|
||||
import QtWebEngine 1.2
|
||||
|
||||
import "controls-uit"
|
||||
|
@ -19,6 +20,9 @@ ScrollingWindow {
|
|||
property variant permissionsBar: {'securityOrigin':'none','feature':'none'}
|
||||
property alias url: webview.url
|
||||
property alias webView: webview
|
||||
|
||||
property alias eventBridge: eventBridgeWrapper.eventBridge
|
||||
|
||||
x: 100
|
||||
y: 100
|
||||
|
||||
|
@ -130,10 +134,11 @@ ScrollingWindow {
|
|||
case Qt.Key_Return:
|
||||
event.accepted = true
|
||||
if (text.indexOf("http") != 0) {
|
||||
text = "http://" + text
|
||||
text = "http://" + text;
|
||||
}
|
||||
root.hidePermissionsBar();
|
||||
webview.url = text
|
||||
root.keyboardRaised = false;
|
||||
webview.url = text;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -197,32 +202,60 @@ ScrollingWindow {
|
|||
}
|
||||
}
|
||||
|
||||
WebEngineView {
|
||||
WebView {
|
||||
id: webview
|
||||
url: "https://highfidelity.com"
|
||||
|
||||
property alias eventBridgeWrapper: eventBridgeWrapper
|
||||
|
||||
QtObject {
|
||||
id: eventBridgeWrapper
|
||||
WebChannel.id: "eventBridgeWrapper"
|
||||
property var eventBridge;
|
||||
}
|
||||
|
||||
webChannel.registeredObjects: [eventBridgeWrapper]
|
||||
|
||||
// Create a global EventBridge object for raiseAndLowerKeyboard.
|
||||
WebEngineScript {
|
||||
id: createGlobalEventBridge
|
||||
sourceCode: eventBridgeJavaScriptToInject
|
||||
injectionPoint: WebEngineScript.DocumentCreation
|
||||
worldId: WebEngineScript.MainWorld
|
||||
}
|
||||
|
||||
// Detect when may want to raise and lower keyboard.
|
||||
WebEngineScript {
|
||||
id: raiseAndLowerKeyboard
|
||||
injectionPoint: WebEngineScript.Deferred
|
||||
sourceUrl: resourceDirectoryUrl + "/html/raiseAndLowerKeyboard.js"
|
||||
worldId: WebEngineScript.MainWorld
|
||||
}
|
||||
|
||||
userScripts: [ createGlobalEventBridge, raiseAndLowerKeyboard ]
|
||||
|
||||
anchors.top: buttons.bottom
|
||||
anchors.topMargin: 8
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
|
||||
onFeaturePermissionRequested: {
|
||||
permissionsBar.securityOrigin = securityOrigin;
|
||||
permissionsBar.feature = feature;
|
||||
root.showPermissionsBar();
|
||||
}
|
||||
|
||||
onLoadingChanged: {
|
||||
if (loadRequest.status === WebEngineView.LoadSucceededStatus) {
|
||||
addressBar.text = loadRequest.url
|
||||
}
|
||||
}
|
||||
|
||||
onIconChanged: {
|
||||
console.log("New icon: " + icon)
|
||||
}
|
||||
onNewViewRequested: {
|
||||
var component = Qt.createComponent("Browser.qml");
|
||||
var newWindow = component.createObject(desktop);
|
||||
request.openIn(newWindow.webView)
|
||||
}
|
||||
|
||||
onWindowCloseRequested: {
|
||||
root.destroy();
|
||||
}
|
||||
|
@ -230,8 +263,6 @@ ScrollingWindow {
|
|||
Component.onCompleted: {
|
||||
desktop.initWebviewProfileHandlers(webview.profile)
|
||||
}
|
||||
|
||||
profile: desktop.browserProfile
|
||||
}
|
||||
|
||||
} // item
|
||||
|
|
|
@ -66,6 +66,24 @@ Windows.ScrollingWindow {
|
|||
anchors.fill: parent
|
||||
focus: true
|
||||
webChannel.registeredObjects: [eventBridgeWrapper]
|
||||
|
||||
// Create a global EventBridge object for raiseAndLowerKeyboard.
|
||||
WebEngineScript {
|
||||
id: createGlobalEventBridge
|
||||
sourceCode: eventBridgeJavaScriptToInject
|
||||
injectionPoint: WebEngineScript.DocumentCreation
|
||||
worldId: WebEngineScript.MainWorld
|
||||
}
|
||||
|
||||
// Detect when may want to raise and lower keyboard.
|
||||
WebEngineScript {
|
||||
id: raiseAndLowerKeyboard
|
||||
injectionPoint: WebEngineScript.Deferred
|
||||
sourceUrl: resourceDirectoryUrl + "/html/raiseAndLowerKeyboard.js"
|
||||
worldId: WebEngineScript.MainWorld
|
||||
}
|
||||
|
||||
userScripts: [ createGlobalEventBridge, raiseAndLowerKeyboard ]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@ import "windows"
|
|||
import "controls-uit"
|
||||
import "styles-uit"
|
||||
|
||||
|
||||
ScrollingWindow {
|
||||
id: toolWindow
|
||||
resizable: true
|
||||
|
@ -47,92 +48,101 @@ ScrollingWindow {
|
|||
property alias y: toolWindow.y
|
||||
}
|
||||
|
||||
TabView {
|
||||
id: tabView;
|
||||
Item {
|
||||
id: toolWindowTabViewItem
|
||||
height: pane.scrollHeight
|
||||
width: pane.contentWidth
|
||||
height: pane.scrollHeight // Pane height so that don't use Window's scrollbars otherwise tabs may be scrolled out of view.
|
||||
property int tabCount: 0
|
||||
anchors.left: parent.left
|
||||
anchors.top: parent.top
|
||||
|
||||
Repeater {
|
||||
model: 4
|
||||
Tab {
|
||||
// Force loading of the content even if the tab is not visible
|
||||
// (required for letting the C++ code access the webview)
|
||||
active: true
|
||||
enabled: false
|
||||
property string originalUrl: "";
|
||||
TabView {
|
||||
id: tabView
|
||||
width: pane.contentWidth
|
||||
// Pane height so that don't use Window's scrollbars otherwise tabs may be scrolled out of view.
|
||||
height: pane.scrollHeight
|
||||
property int tabCount: 0
|
||||
|
||||
WebView {
|
||||
id: webView;
|
||||
anchors.fill: parent
|
||||
Repeater {
|
||||
model: 4
|
||||
Tab {
|
||||
// Force loading of the content even if the tab is not visible
|
||||
// (required for letting the C++ code access the webview)
|
||||
active: true
|
||||
enabled: false
|
||||
property alias eventBridgeWrapper: eventBridgeWrapper
|
||||
|
||||
QtObject {
|
||||
id: eventBridgeWrapper
|
||||
WebChannel.id: "eventBridgeWrapper"
|
||||
property var eventBridge;
|
||||
property string originalUrl: ""
|
||||
|
||||
WebView {
|
||||
id: webView
|
||||
anchors.fill: parent
|
||||
enabled: false
|
||||
property alias eventBridgeWrapper: eventBridgeWrapper
|
||||
|
||||
QtObject {
|
||||
id: eventBridgeWrapper
|
||||
WebChannel.id: "eventBridgeWrapper"
|
||||
property var eventBridge
|
||||
}
|
||||
|
||||
webChannel.registeredObjects: [eventBridgeWrapper]
|
||||
onEnabledChanged: toolWindow.updateVisiblity()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
style: TabViewStyle {
|
||||
|
||||
frame: Rectangle { // Background shown before content loads.
|
||||
anchors.fill: parent
|
||||
color: hifi.colors.baseGray
|
||||
}
|
||||
|
||||
frameOverlap: 0
|
||||
|
||||
tab: Rectangle {
|
||||
implicitWidth: text.width
|
||||
implicitHeight: 3 * text.height
|
||||
color: styleData.selected ? hifi.colors.black : hifi.colors.tabBackgroundDark
|
||||
|
||||
RalewayRegular {
|
||||
id: text
|
||||
text: styleData.title
|
||||
font.capitalization: Font.AllUppercase
|
||||
size: hifi.fontSizes.tabName
|
||||
width: tabView.tabCount > 1 ? styleData.availableWidth / tabView.tabCount : implicitWidth + 2 * hifi.dimensions.contentSpacing.x
|
||||
elide: Text.ElideRight
|
||||
color: styleData.selected ? hifi.colors.primaryHighlight : hifi.colors.lightGrayText
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
anchors.centerIn: parent
|
||||
}
|
||||
|
||||
webChannel.registeredObjects: [eventBridgeWrapper]
|
||||
onEnabledChanged: toolWindow.updateVisiblity();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
style: TabViewStyle {
|
||||
|
||||
frame: Rectangle { // Background shown before content loads.
|
||||
anchors.fill: parent
|
||||
color: hifi.colors.baseGray
|
||||
}
|
||||
|
||||
frameOverlap: 0
|
||||
|
||||
tab: Rectangle {
|
||||
implicitWidth: text.width
|
||||
implicitHeight: 3 * text.height
|
||||
color: styleData.selected ? hifi.colors.black : hifi.colors.tabBackgroundDark
|
||||
|
||||
RalewayRegular {
|
||||
id: text
|
||||
text: styleData.title
|
||||
font.capitalization: Font.AllUppercase
|
||||
size: hifi.fontSizes.tabName
|
||||
width: tabView.tabCount > 1 ? styleData.availableWidth / tabView.tabCount : implicitWidth + 2 * hifi.dimensions.contentSpacing.x
|
||||
elide: Text.ElideRight
|
||||
color: styleData.selected ? hifi.colors.primaryHighlight : hifi.colors.lightGrayText
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
anchors.centerIn: parent
|
||||
}
|
||||
|
||||
Rectangle { // Separator.
|
||||
width: 1
|
||||
height: parent.height
|
||||
color: hifi.colors.black
|
||||
anchors.left: parent.left
|
||||
anchors.top: parent.top
|
||||
visible: styleData.index > 0
|
||||
|
||||
Rectangle {
|
||||
Rectangle { // Separator.
|
||||
width: 1
|
||||
height: 1
|
||||
color: hifi.colors.baseGray
|
||||
height: parent.height
|
||||
color: hifi.colors.black
|
||||
anchors.left: parent.left
|
||||
anchors.top: parent.top
|
||||
visible: styleData.index > 0
|
||||
|
||||
Rectangle {
|
||||
width: 1
|
||||
height: 1
|
||||
color: hifi.colors.baseGray
|
||||
anchors.left: parent.left
|
||||
anchors.bottom: parent.bottom
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle { // Active underline.
|
||||
width: parent.width - (styleData.index > 0 ? 1 : 0)
|
||||
height: 1
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: parent.bottom
|
||||
color: styleData.selected ? hifi.colors.primaryHighlight : hifi.colors.baseGray
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle { // Active underline.
|
||||
width: parent.width - (styleData.index > 0 ? 1 : 0)
|
||||
height: 1
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: parent.bottom
|
||||
color: styleData.selected ? hifi.colors.primaryHighlight : hifi.colors.baseGray
|
||||
}
|
||||
tabOverlap: 0
|
||||
}
|
||||
|
||||
tabOverlap: 0
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -224,7 +234,6 @@ ScrollingWindow {
|
|||
return;
|
||||
}
|
||||
|
||||
|
||||
if (properties.width) {
|
||||
tabView.width = Math.min(Math.max(tabView.width, properties.width), toolWindow.maxSize.x);
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
//
|
||||
|
||||
import QtQuick 2.5
|
||||
import QtWebEngine 1.1
|
||||
import QtWebEngine 1.2
|
||||
|
||||
WebEngineView {
|
||||
id: root
|
||||
|
|
|
@ -109,8 +109,13 @@ Column {
|
|||
}
|
||||
|
||||
MouseArea {
|
||||
// Events are propogated so that any active control is defocused.
|
||||
anchors.fill: parent
|
||||
onClicked: toggleCollapsed()
|
||||
propagateComposedEvents: true
|
||||
onPressed: {
|
||||
toggleCollapsed();
|
||||
mouse.accepted = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -304,13 +304,13 @@ Item {
|
|||
Key {
|
||||
id: key31
|
||||
width: 43
|
||||
glyph: ","
|
||||
glyph: "_"
|
||||
}
|
||||
|
||||
Key {
|
||||
id: key33
|
||||
width: 43
|
||||
glyph: "."
|
||||
glyph: "?"
|
||||
}
|
||||
|
||||
Key {
|
|
@ -208,49 +208,49 @@ Item {
|
|||
Key {
|
||||
id: key22
|
||||
width: 43
|
||||
glyph: "_"
|
||||
glyph: ","
|
||||
}
|
||||
|
||||
Key {
|
||||
id: key23
|
||||
width: 43
|
||||
glyph: ";"
|
||||
glyph: "."
|
||||
}
|
||||
|
||||
Key {
|
||||
id: key24
|
||||
width: 43
|
||||
glyph: ":"
|
||||
glyph: ";"
|
||||
}
|
||||
|
||||
Key {
|
||||
id: key25
|
||||
width: 43
|
||||
glyph: "'"
|
||||
glyph: ":"
|
||||
}
|
||||
|
||||
Key {
|
||||
id: key26
|
||||
width: 43
|
||||
glyph: "\""
|
||||
glyph: "'"
|
||||
}
|
||||
|
||||
Key {
|
||||
id: key31
|
||||
width: 43
|
||||
glyph: "<"
|
||||
glyph: "\""
|
||||
}
|
||||
|
||||
Key {
|
||||
id: key33
|
||||
width: 43
|
||||
glyph: ">"
|
||||
glyph: "<"
|
||||
}
|
||||
|
||||
Key {
|
||||
id: key36
|
||||
width: 43
|
||||
glyph: "?"
|
||||
glyph: ">"
|
||||
}
|
||||
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
import QtQuick 2.5
|
||||
import QtWebEngine 1.1
|
||||
import QtWebChannel 1.0
|
||||
import "../controls-uit" as HiFiControls
|
||||
|
||||
Item {
|
||||
property alias url: root.url
|
||||
|
@ -105,7 +106,7 @@ Item {
|
|||
}
|
||||
|
||||
// virtual keyboard, letters
|
||||
Keyboard {
|
||||
HiFiControls.Keyboard {
|
||||
id: keyboard1
|
||||
y: keyboardRaised ? parent.height : 0
|
||||
height: keyboardRaised ? 200 : 0
|
||||
|
@ -119,7 +120,7 @@ Item {
|
|||
anchors.bottomMargin: 0
|
||||
}
|
||||
|
||||
KeyboardPunctuation {
|
||||
HiFiControls.KeyboardPunctuation {
|
||||
id: keyboard2
|
||||
y: keyboardRaised ? parent.height : 0
|
||||
height: keyboardRaised ? 200 : 0
|
||||
|
|
|
@ -22,6 +22,7 @@ ModalWindow {
|
|||
implicitWidth: 640;
|
||||
implicitHeight: 320;
|
||||
visible: true;
|
||||
keyboardEnabled: false // Disable ModalWindow's keyboard.
|
||||
|
||||
signal selected(var result);
|
||||
signal canceled();
|
||||
|
@ -50,6 +51,10 @@ ModalWindow {
|
|||
}
|
||||
}
|
||||
|
||||
property bool keyboardRaised: false
|
||||
property bool punctuationMode: false
|
||||
onKeyboardRaisedChanged: d.resize();
|
||||
|
||||
property var warning: "";
|
||||
property var result;
|
||||
|
||||
|
@ -110,7 +115,9 @@ ModalWindow {
|
|||
var targetWidth = Math.max(titleWidth, pane.width);
|
||||
var targetHeight = (textField.visible ? textField.controlHeight + hifi.dimensions.contentSpacing.y : 0) +
|
||||
(extraInputs.visible ? extraInputs.height + hifi.dimensions.contentSpacing.y : 0) +
|
||||
(buttons.height + 3 * hifi.dimensions.contentSpacing.y);
|
||||
(buttons.height + 3 * hifi.dimensions.contentSpacing.y) +
|
||||
(root.keyboardRaised ? (200 + hifi.dimensions.contentSpacing.y) : 0);
|
||||
|
||||
root.width = (targetWidth < d.minWidth) ? d.minWidth : ((targetWidth > d.maxWdith) ? d.maxWidth : targetWidth);
|
||||
root.height = (targetHeight < d.minHeight) ? d.minHeight : ((targetHeight > d.maxHeight) ?
|
||||
d.maxHeight : targetHeight);
|
||||
|
@ -130,7 +137,6 @@ ModalWindow {
|
|||
left: parent.left;
|
||||
right: parent.right;
|
||||
margins: 0;
|
||||
bottomMargin: hifi.dimensions.contentSpacing.y;
|
||||
}
|
||||
|
||||
// FIXME make a text field type that can be bound to a history for autocompletion
|
||||
|
@ -142,7 +148,43 @@ ModalWindow {
|
|||
anchors {
|
||||
left: parent.left;
|
||||
right: parent.right;
|
||||
bottom: parent.bottom;
|
||||
bottom: keyboard.top;
|
||||
bottomMargin: hifi.dimensions.contentSpacing.y;
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
id: keyboard
|
||||
|
||||
height: keyboardRaised ? 200 : 0
|
||||
|
||||
anchors {
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
bottom: parent.bottom
|
||||
bottomMargin: keyboardRaised ? hifi.dimensions.contentSpacing.y : 0
|
||||
}
|
||||
|
||||
Keyboard {
|
||||
id: keyboard1
|
||||
visible: keyboardRaised && !punctuationMode
|
||||
enabled: keyboardRaised && !punctuationMode
|
||||
anchors {
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
bottom: parent.bottom
|
||||
}
|
||||
}
|
||||
|
||||
KeyboardPunctuation {
|
||||
id: keyboard2
|
||||
visible: keyboardRaised && punctuationMode
|
||||
enabled: keyboardRaised && punctuationMode
|
||||
anchors {
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
bottom: parent.bottom
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,7 +27,7 @@ ModalWindow {
|
|||
id: root
|
||||
resizable: true
|
||||
implicitWidth: 480
|
||||
implicitHeight: 360
|
||||
implicitHeight: 360 + (fileDialogItem.keyboardRaised ? 200 + hifi.dimensions.contentSpacing.y : 0)
|
||||
|
||||
minSize: Qt.vector2d(360, 240)
|
||||
draggable: true
|
||||
|
@ -100,16 +100,23 @@ ModalWindow {
|
|||
}
|
||||
|
||||
Item {
|
||||
id: fileDialogItem
|
||||
clip: true
|
||||
width: pane.width
|
||||
height: pane.height
|
||||
anchors.margins: 0
|
||||
|
||||
property bool keyboardRaised: false
|
||||
property bool punctuationMode: false
|
||||
|
||||
MouseArea {
|
||||
// Clear selection when click on internal unused area.
|
||||
anchors.fill: parent
|
||||
drag.target: root
|
||||
onClicked: d.clearSelection()
|
||||
onClicked: {
|
||||
d.clearSelection();
|
||||
frame.forceActiveFocus(); // Defocus text field so that the keyboard gets hidden.
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
|
@ -619,7 +626,7 @@ ModalWindow {
|
|||
left: parent.left
|
||||
right: selectionType.visible ? selectionType.left: parent.right
|
||||
rightMargin: selectionType.visible ? hifi.dimensions.contentSpacing.x : 0
|
||||
bottom: buttonRow.top
|
||||
bottom: keyboard1.top
|
||||
bottomMargin: hifi.dimensions.contentSpacing.y
|
||||
}
|
||||
readOnly: !root.saveDialog
|
||||
|
@ -640,6 +647,28 @@ ModalWindow {
|
|||
KeyNavigation.right: openButton
|
||||
}
|
||||
|
||||
Keyboard {
|
||||
id: keyboard1
|
||||
height: parent.keyboardRaised ? 200 : 0
|
||||
visible: parent.keyboardRaised && !parent.punctuationMode
|
||||
enabled: visible
|
||||
anchors.right: parent.right
|
||||
anchors.left: parent.left
|
||||
anchors.bottom: buttonRow.top
|
||||
anchors.bottomMargin: visible ? hifi.dimensions.contentSpacing.y : 0
|
||||
}
|
||||
|
||||
KeyboardPunctuation {
|
||||
id: keyboard2
|
||||
height: parent.keyboardRaised ? 200 : 0
|
||||
visible: parent.keyboardRaised && parent.punctuationMode
|
||||
enabled: visible
|
||||
anchors.right: parent.right
|
||||
anchors.left: parent.left
|
||||
anchors.bottom: buttonRow.top
|
||||
anchors.bottomMargin: visible ? hifi.dimensions.contentSpacing.y : 0
|
||||
}
|
||||
|
||||
Row {
|
||||
id: buttonRow
|
||||
anchors {
|
||||
|
|
|
@ -97,9 +97,9 @@ ScrollingWindow {
|
|||
|
||||
footer: Row {
|
||||
anchors {
|
||||
right: parent.right;
|
||||
top: parent.top
|
||||
right: parent.right
|
||||
rightMargin: hifi.dimensions.contentMargin.x
|
||||
verticalCenter: parent.verticalCenter
|
||||
}
|
||||
spacing: hifi.dimensions.contentSpacing.x
|
||||
|
||||
|
|
|
@ -53,11 +53,17 @@ ModalWindow {
|
|||
}
|
||||
|
||||
Item {
|
||||
id: modalWindowItem
|
||||
clip: true
|
||||
width: pane.width
|
||||
height: pane.height
|
||||
anchors.margins: 0
|
||||
|
||||
property bool keyboardRaised: false
|
||||
property bool punctuationMode: false
|
||||
|
||||
onKeyboardRaisedChanged: d.resize();
|
||||
|
||||
QtObject {
|
||||
id: d
|
||||
readonly property int minWidth: 480
|
||||
|
@ -69,14 +75,14 @@ ModalWindow {
|
|||
var targetWidth = Math.max(titleWidth, pane.width)
|
||||
var targetHeight = (items ? comboBox.controlHeight : textResult.controlHeight) + 5 * hifi.dimensions.contentSpacing.y + buttons.height
|
||||
root.width = (targetWidth < d.minWidth) ? d.minWidth : ((targetWidth > d.maxWdith) ? d.maxWidth : targetWidth)
|
||||
root.height = (targetHeight < d.minHeight) ? d.minHeight: ((targetHeight > d.maxHeight) ? d.maxHeight : targetHeight)
|
||||
root.height = ((targetHeight < d.minHeight) ? d.minHeight: ((targetHeight > d.maxHeight) ? d.maxHeight : targetHeight)) + (modalWindowItem.keyboardRaised ? (200 + 2 * hifi.dimensions.contentSpacing.y) : 0)
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
anchors {
|
||||
top: parent.top
|
||||
bottom: buttons.top;
|
||||
bottom: keyboard1.top;
|
||||
left: parent.left;
|
||||
right: parent.right;
|
||||
margins: 0
|
||||
|
@ -110,6 +116,35 @@ ModalWindow {
|
|||
}
|
||||
}
|
||||
|
||||
// virtual keyboard, letters
|
||||
Keyboard {
|
||||
id: keyboard1
|
||||
y: parent.keyboardRaised ? parent.height : 0
|
||||
height: parent.keyboardRaised ? 200 : 0
|
||||
visible: parent.keyboardRaised && !parent.punctuationMode
|
||||
enabled: parent.keyboardRaised && !parent.punctuationMode
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 0
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 0
|
||||
anchors.bottom: buttons.top
|
||||
anchors.bottomMargin: parent.keyboardRaised ? 2 * hifi.dimensions.contentSpacing.y : 0
|
||||
}
|
||||
|
||||
KeyboardPunctuation {
|
||||
id: keyboard2
|
||||
y: parent.keyboardRaised ? parent.height : 0
|
||||
height: parent.keyboardRaised ? 200 : 0
|
||||
visible: parent.keyboardRaised && parent.punctuationMode
|
||||
enabled: parent.keyboardRaised && parent.punctuationMode
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 0
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 0
|
||||
anchors.bottom: buttons.top
|
||||
anchors.bottomMargin: parent.keyboardRaised ? 2 * hifi.dimensions.contentSpacing.y : 0
|
||||
}
|
||||
|
||||
Flow {
|
||||
id: buttons
|
||||
focus: true
|
||||
|
|
|
@ -45,7 +45,7 @@ ScrollingWindow {
|
|||
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: root.height
|
||||
height: root.height - (keyboardRaised ? 200 : 0)
|
||||
radius: 4
|
||||
color: hifi.colors.baseGray
|
||||
|
||||
|
@ -128,6 +128,10 @@ ScrollingWindow {
|
|||
}
|
||||
onCountChanged: MyAvatar.setAttachmentsVariant(attachments);
|
||||
}
|
||||
|
||||
function scrollBy(delta) {
|
||||
flickableItem.contentY += delta;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -204,5 +208,22 @@ ScrollingWindow {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
onKeyboardRaisedChanged: {
|
||||
if (keyboardRaised) {
|
||||
// Scroll to item with focus if necessary.
|
||||
var footerHeight = newAttachmentButton.height + buttonRow.height + 3 * hifi.dimensions.contentSpacing.y;
|
||||
var delta = activator.mouseY
|
||||
- (activator.height + activator.y - 200 - footerHeight - hifi.dimensions.controlLineHeight);
|
||||
|
||||
if (delta > 0) {
|
||||
scrollView.scrollBy(delta);
|
||||
} else {
|
||||
// HACK: Work around for case where are 100% scrolled; stops window from erroneously scrolling to 100% when show keyboard.
|
||||
scrollView.scrollBy(-1);
|
||||
scrollView.scrollBy(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -30,7 +30,7 @@ ScrollingWindow {
|
|||
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: root.height
|
||||
height: root.height - (keyboardRaised ? 200 : 0)
|
||||
radius: 4
|
||||
color: hifi.colors.baseGray
|
||||
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
|
||||
//
|
||||
// Window.qml
|
||||
//
|
||||
|
@ -15,6 +16,7 @@ import QtGraphicalEffects 1.0
|
|||
|
||||
import "."
|
||||
import "../styles-uit"
|
||||
import "../controls-uit" as HiFiControls
|
||||
|
||||
// FIXME how do I set the initial position of a window without
|
||||
// overriding places where the a individual client of the window
|
||||
|
@ -23,12 +25,18 @@ import "../styles-uit"
|
|||
// FIXME how to I enable dragging without allowing the window to lay outside
|
||||
// of the desktop? How do I ensure when the desktop resizes all the windows
|
||||
// are still at least partially visible?
|
||||
|
||||
Window {
|
||||
id: window
|
||||
HifiConstants { id: hifi }
|
||||
children: [ swallower, frame, pane, activator ]
|
||||
children: [ swallower, frame, defocuser, pane, activator ]
|
||||
|
||||
property var footer: Item { } // Optional static footer at the bottom of the dialog.
|
||||
readonly property var footerContentHeight: footer.height > 0 ? (footer.height + 2 * hifi.dimensions.contentSpacing.y + 3) : 0
|
||||
|
||||
property bool keyboardEnabled: true // Set false if derived control implements its own keyboard.
|
||||
property bool keyboardRaised: false
|
||||
property bool punctuationMode: false
|
||||
|
||||
// Scrollable window content.
|
||||
// FIXME this should not define any visual content in this type. The base window
|
||||
|
@ -73,7 +81,7 @@ Window {
|
|||
verticalScrollBarPolicy: Qt.ScrollBarAsNeeded
|
||||
anchors.fill: parent
|
||||
anchors.rightMargin: parent.isScrolling ? 1 : 0
|
||||
anchors.bottomMargin: footer.height > 0 ? footerPane.height : 0
|
||||
anchors.bottomMargin: footerPane.height
|
||||
|
||||
style: ScrollViewStyle {
|
||||
|
||||
|
@ -116,21 +124,36 @@ Window {
|
|||
}
|
||||
}
|
||||
|
||||
function scrollBy(delta) {
|
||||
scrollView.flickableItem.contentY += delta;
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
// Optional non-scrolling footer.
|
||||
id: footerPane
|
||||
|
||||
property alias keyboardEnabled: window.keyboardEnabled
|
||||
property alias keyboardRaised: window.keyboardRaised
|
||||
property alias punctuationMode: window.punctuationMode
|
||||
|
||||
anchors {
|
||||
left: parent.left
|
||||
bottom: parent.bottom
|
||||
}
|
||||
width: parent.contentWidth
|
||||
height: footer.height + 2 * hifi.dimensions.contentSpacing.y + 3
|
||||
height: footerContentHeight + (keyboardEnabled && keyboardRaised ? 200 : 0)
|
||||
color: hifi.colors.baseGray
|
||||
visible: footer.height > 0
|
||||
visible: footer.height > 0 || keyboardEnabled && keyboardRaised
|
||||
|
||||
Item {
|
||||
// Horizontal rule.
|
||||
anchors.fill: parent
|
||||
anchors {
|
||||
top: parent.top
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
}
|
||||
|
||||
visible: footer.height > 0
|
||||
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
|
@ -148,10 +171,53 @@ Window {
|
|||
}
|
||||
|
||||
Item {
|
||||
anchors.fill: parent
|
||||
anchors.topMargin: 3 // Horizontal rule.
|
||||
anchors {
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
top: parent.top
|
||||
topMargin: hifi.dimensions.contentSpacing.y + 3
|
||||
}
|
||||
children: [ footer ]
|
||||
}
|
||||
|
||||
HiFiControls.Keyboard {
|
||||
id: keyboard1
|
||||
height: parent.keyboardEnabled && parent.keyboardRaised ? 200 : 0
|
||||
visible: parent.keyboardEnabled && parent.keyboardRaised && !parent.punctuationMode
|
||||
enabled: parent.keyboardEnabled && parent.keyboardRaised && !parent.punctuationMode
|
||||
anchors {
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
bottom: parent.bottom
|
||||
}
|
||||
}
|
||||
|
||||
HiFiControls.KeyboardPunctuation {
|
||||
id: keyboard2
|
||||
height: parent.keyboardEnabled && parent.keyboardRaised ? 200 : 0
|
||||
visible: parent.keyboardEnabled && parent.keyboardRaised && parent.punctuationMode
|
||||
enabled: parent.keyboardEnabled && parent.keyboardRaised && parent.punctuationMode
|
||||
anchors {
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
bottom: parent.bottom
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onKeyboardRaisedChanged: {
|
||||
if (keyboardEnabled && keyboardRaised) {
|
||||
var delta = activator.mouseY
|
||||
- (activator.height + activator.y - 200 - footerContentHeight - hifi.dimensions.controlLineHeight);
|
||||
|
||||
if (delta > 0) {
|
||||
pane.scrollBy(delta);
|
||||
} else {
|
||||
// HACK: Work around for case where are 100% scrolled; stops window from erroneously scrolling to 100% when show keyboard.
|
||||
pane.scrollBy(-1);
|
||||
pane.scrollBy(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -44,7 +44,7 @@ Fadable {
|
|||
implicitHeight: content ? content.height : 0
|
||||
implicitWidth: content ? content.width : 0
|
||||
x: desktop.invalid_position; y: desktop.invalid_position;
|
||||
children: [ swallower, frame, content, activator ]
|
||||
children: [ swallower, frame, defocuser, content, activator ]
|
||||
|
||||
//
|
||||
// Custom properties
|
||||
|
@ -122,6 +122,21 @@ Fadable {
|
|||
}
|
||||
}
|
||||
|
||||
// This mouse area defocuses the current control so that the HMD keyboard gets hidden.
|
||||
property var defocuser: MouseArea {
|
||||
width: frame.decoration ? frame.decoration.width : window.width
|
||||
height: frame.decoration ? frame.decoration.height : window.height
|
||||
x: frame.decoration ? frame.decoration.anchors.leftMargin : 0
|
||||
y: frame.decoration ? frame.decoration.anchors.topMargin : 0
|
||||
propagateComposedEvents: true
|
||||
acceptedButtons: Qt.AllButtons
|
||||
enabled: window.visible
|
||||
onPressed: {
|
||||
frame.forceActiveFocus();
|
||||
mouse.accepted = false;
|
||||
}
|
||||
}
|
||||
|
||||
// This mouse area serves to swallow mouse events while the mouse is over the window
|
||||
// to prevent things like mouse wheel events from reaching the application and changing
|
||||
// the camera if the user is scrolling through a list and gets to the end.
|
||||
|
|
|
@ -486,7 +486,7 @@ bool setupEssentials(int& argc, char** argv) {
|
|||
// FIXME move to header, or better yet, design some kind of UI manager
|
||||
// to take care of highlighting keyboard focused items, rather than
|
||||
// continuing to overburden Application.cpp
|
||||
Cube3DOverlay* _keyboardFocusHighlight{ nullptr };
|
||||
std::shared_ptr<Cube3DOverlay> _keyboardFocusHighlight{ nullptr };
|
||||
int _keyboardFocusHighlightID{ -1 };
|
||||
|
||||
|
||||
|
@ -3625,7 +3625,7 @@ void Application::setKeyboardFocusEntity(EntityItemID entityItemID) {
|
|||
_keyboardFocusedItem.set(entityItemID);
|
||||
_lastAcceptedKeyPress = usecTimestampNow();
|
||||
if (_keyboardFocusHighlightID < 0 || !getOverlays().isAddedOverlay(_keyboardFocusHighlightID)) {
|
||||
_keyboardFocusHighlight = new Cube3DOverlay();
|
||||
_keyboardFocusHighlight = std::make_shared<Cube3DOverlay>();
|
||||
_keyboardFocusHighlight->setAlpha(1.0f);
|
||||
_keyboardFocusHighlight->setBorderSize(1.0f);
|
||||
_keyboardFocusHighlight->setColor({ 0xFF, 0xEF, 0x00 });
|
||||
|
|
|
@ -38,39 +38,6 @@ static uint64_t MAX_NO_RENDER_INTERVAL = 30 * USECS_PER_SECOND;
|
|||
static int MAX_WINDOW_SIZE = 4096;
|
||||
static float OPAQUE_ALPHA_THRESHOLD = 0.99f;
|
||||
|
||||
void WebEntityAPIHelper::synthesizeKeyPress(QString key) {
|
||||
if (_renderableWebEntityItem) {
|
||||
_renderableWebEntityItem->synthesizeKeyPress(key);
|
||||
}
|
||||
}
|
||||
|
||||
void WebEntityAPIHelper::emitScriptEvent(const QVariant& message) {
|
||||
if (QThread::currentThread() != thread()) {
|
||||
QMetaObject::invokeMethod(this, "emitScriptEvent", Qt::QueuedConnection, Q_ARG(QVariant, message));
|
||||
} else {
|
||||
emit scriptEventReceived(message);
|
||||
}
|
||||
}
|
||||
|
||||
void WebEntityAPIHelper::emitWebEvent(const QVariant& message) {
|
||||
if (QThread::currentThread() != thread()) {
|
||||
QMetaObject::invokeMethod(this, "emitWebEvent", Qt::QueuedConnection, Q_ARG(QVariant, message));
|
||||
} else {
|
||||
// special case to handle raising and lowering the virtual keyboard
|
||||
if (message.type() == QVariant::String && message.toString() == "_RAISE_KEYBOARD" && _renderableWebEntityItem) {
|
||||
if (_renderableWebEntityItem) {
|
||||
_renderableWebEntityItem->setKeyboardRaised(true);
|
||||
}
|
||||
} else if (message.type() == QVariant::String && message.toString() == "_LOWER_KEYBOARD" && _renderableWebEntityItem) {
|
||||
if (_renderableWebEntityItem) {
|
||||
_renderableWebEntityItem->setKeyboardRaised(false);
|
||||
}
|
||||
} else {
|
||||
emit webEventReceived(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
EntityItemPointer RenderableWebEntityItem::factory(const EntityItemID& entityID, const EntityItemProperties& properties) {
|
||||
EntityItemPointer entity{ new RenderableWebEntityItem(entityID) };
|
||||
entity->setProperties(properties);
|
||||
|
@ -85,21 +52,9 @@ RenderableWebEntityItem::RenderableWebEntityItem(const EntityItemID& entityItemI
|
|||
_touchDevice.setType(QTouchDevice::TouchScreen);
|
||||
_touchDevice.setName("RenderableWebEntityItemTouchDevice");
|
||||
_touchDevice.setMaximumTouchPoints(4);
|
||||
|
||||
_webEntityAPIHelper = new WebEntityAPIHelper;
|
||||
_webEntityAPIHelper->setRenderableWebEntityItem(this);
|
||||
_webEntityAPIHelper->moveToThread(qApp->thread());
|
||||
|
||||
// forward web events to EntityScriptingInterface
|
||||
auto entities = DependencyManager::get<EntityScriptingInterface>();
|
||||
QObject::connect(_webEntityAPIHelper, &WebEntityAPIHelper::webEventReceived, [=](const QVariant& message) {
|
||||
emit entities->webEventReceived(entityItemID, message);
|
||||
});
|
||||
}
|
||||
|
||||
RenderableWebEntityItem::~RenderableWebEntityItem() {
|
||||
_webEntityAPIHelper->setRenderableWebEntityItem(nullptr);
|
||||
_webEntityAPIHelper->deleteLater();
|
||||
destroyWebSurface();
|
||||
qDebug() << "Destroyed web entity " << getID();
|
||||
}
|
||||
|
@ -148,10 +103,16 @@ bool RenderableWebEntityItem::buildWebSurface(EntityTreeRenderer* renderer) {
|
|||
context->setContextProperty("eventBridgeJavaScriptToInject", QVariant(javaScriptToInject));
|
||||
});
|
||||
_webSurface->resume();
|
||||
_webSurface->getRootItem()->setProperty("eventBridge", QVariant::fromValue(_webEntityAPIHelper));
|
||||
_webSurface->getRootItem()->setProperty("url", _sourceUrl);
|
||||
_webSurface->getRootContext()->setContextProperty("desktop", QVariant());
|
||||
_webSurface->getRootContext()->setContextProperty("webEntity", _webEntityAPIHelper);
|
||||
|
||||
// forward web events to EntityScriptingInterface
|
||||
auto entities = DependencyManager::get<EntityScriptingInterface>();
|
||||
const EntityItemID entityItemID = getID();
|
||||
QObject::connect(_webSurface.data(), &OffscreenQmlSurface::webEventReceived, [=](const QVariant& message) {
|
||||
emit entities->webEventReceived(entityItemID, message);
|
||||
});
|
||||
|
||||
// Restore the original GL context
|
||||
currentContext->makeCurrent(currentSurface);
|
||||
|
||||
|
@ -370,7 +331,6 @@ void RenderableWebEntityItem::destroyWebSurface() {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
void RenderableWebEntityItem::update(const quint64& now) {
|
||||
auto interval = now - _lastRenderTime;
|
||||
if (interval > MAX_NO_RENDER_INTERVAL) {
|
||||
|
@ -378,78 +338,13 @@ void RenderableWebEntityItem::update(const quint64& now) {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
bool RenderableWebEntityItem::isTransparent() {
|
||||
float fadeRatio = _isFading ? Interpolate::calculateFadeRatio(_fadeStartTime) : 1.0f;
|
||||
return fadeRatio < OPAQUE_ALPHA_THRESHOLD;
|
||||
}
|
||||
|
||||
// UTF-8 encoded symbols
|
||||
static const uint8_t UPWARDS_WHITE_ARROW_FROM_BAR[] = { 0xE2, 0x87, 0xAA, 0x00 }; // shift
|
||||
static const uint8_t LEFT_ARROW[] = { 0xE2, 0x86, 0x90, 0x00 }; // backspace
|
||||
static const uint8_t LEFTWARD_WHITE_ARROW[] = { 0xE2, 0x87, 0xA6, 0x00 }; // left arrow
|
||||
static const uint8_t RIGHTWARD_WHITE_ARROW[] = { 0xE2, 0x87, 0xA8, 0x00 }; // right arrow
|
||||
static const uint8_t ASTERISIM[] = { 0xE2, 0x81, 0x82, 0x00 }; // symbols
|
||||
static const uint8_t RETURN_SYMBOL[] = { 0xE2, 0x8F, 0x8E, 0x00 }; // return
|
||||
static const char PUNCTUATION_STRING[] = "&123";
|
||||
static const char ALPHABET_STRING[] = "abc";
|
||||
|
||||
static bool equals(const QByteArray& byteArray, const uint8_t* ptr) {
|
||||
int i;
|
||||
for (i = 0; i < byteArray.size(); i++) {
|
||||
if ((char)ptr[i] != byteArray[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return ptr[i] == 0x00;
|
||||
}
|
||||
|
||||
void RenderableWebEntityItem::synthesizeKeyPress(QString key) {
|
||||
auto eventHandler = getEventHandler();
|
||||
if (eventHandler) {
|
||||
auto utf8Key = key.toUtf8();
|
||||
|
||||
int scanCode = (int)utf8Key[0];
|
||||
QString keyString = key;
|
||||
if (equals(utf8Key, UPWARDS_WHITE_ARROW_FROM_BAR) || equals(utf8Key, ASTERISIM) ||
|
||||
equals(utf8Key, (uint8_t*)PUNCTUATION_STRING) || equals(utf8Key, (uint8_t*)ALPHABET_STRING)) {
|
||||
return; // ignore
|
||||
} else if (equals(utf8Key, LEFT_ARROW)) {
|
||||
scanCode = Qt::Key_Backspace;
|
||||
keyString = "\x08";
|
||||
} else if (equals(utf8Key, RETURN_SYMBOL)) {
|
||||
scanCode = Qt::Key_Return;
|
||||
keyString = "\x0d";
|
||||
} else if (equals(utf8Key, LEFTWARD_WHITE_ARROW)) {
|
||||
scanCode = Qt::Key_Left;
|
||||
keyString = "";
|
||||
} else if (equals(utf8Key, RIGHTWARD_WHITE_ARROW)) {
|
||||
scanCode = Qt::Key_Right;
|
||||
keyString = "";
|
||||
}
|
||||
|
||||
QKeyEvent* pressEvent = new QKeyEvent(QEvent::KeyPress, scanCode, Qt::NoModifier, keyString);
|
||||
QKeyEvent* releaseEvent = new QKeyEvent(QEvent::KeyRelease, scanCode, Qt::NoModifier, keyString);
|
||||
QCoreApplication::postEvent(eventHandler, pressEvent);
|
||||
QCoreApplication::postEvent(eventHandler, releaseEvent);
|
||||
}
|
||||
}
|
||||
|
||||
void RenderableWebEntityItem::emitScriptEvent(const QVariant& message) {
|
||||
if (_webEntityAPIHelper) {
|
||||
_webEntityAPIHelper->emitScriptEvent(message);
|
||||
}
|
||||
}
|
||||
|
||||
void RenderableWebEntityItem::setKeyboardRaised(bool raised) {
|
||||
|
||||
// raise the keyboard only while in HMD mode and it's being requested.
|
||||
bool value = AbstractViewStateInterface::instance()->isHMDMode() && raised;
|
||||
|
||||
if (_webSurface) {
|
||||
auto rootItem = _webSurface->getRootItem();
|
||||
if (rootItem) {
|
||||
rootItem->setProperty("keyboardRaised", QVariant(value));
|
||||
}
|
||||
_webSurface->emitScriptEvent(message);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,36 +13,18 @@
|
|||
#include <QMouseEvent>
|
||||
#include <QTouchEvent>
|
||||
#include <PointerEvent.h>
|
||||
#include <gl/OffscreenQmlSurface.h>
|
||||
|
||||
#include <WebEntityItem.h>
|
||||
|
||||
#include "RenderableEntityItem.h"
|
||||
|
||||
class OffscreenQmlSurface;
|
||||
|
||||
class QWindow;
|
||||
class QObject;
|
||||
class EntityTreeRenderer;
|
||||
class RenderableWebEntityItem;
|
||||
|
||||
class WebEntityAPIHelper : public QObject {
|
||||
Q_OBJECT
|
||||
public:
|
||||
void setRenderableWebEntityItem(RenderableWebEntityItem* renderableWebEntityItem) {
|
||||
_renderableWebEntityItem = renderableWebEntityItem;
|
||||
}
|
||||
Q_INVOKABLE void synthesizeKeyPress(QString key);
|
||||
|
||||
// event bridge
|
||||
public slots:
|
||||
void emitScriptEvent(const QVariant& scriptMessage);
|
||||
void emitWebEvent(const QVariant& webMessage);
|
||||
signals:
|
||||
void scriptEventReceived(const QVariant& message);
|
||||
void webEventReceived(const QVariant& message);
|
||||
|
||||
protected:
|
||||
RenderableWebEntityItem* _renderableWebEntityItem{ nullptr };
|
||||
};
|
||||
|
||||
class RenderableWebEntityItem : public WebEntityItem {
|
||||
public:
|
||||
|
@ -64,15 +46,11 @@ public:
|
|||
bool needsToCallUpdate() const override { return _webSurface != nullptr; }
|
||||
|
||||
virtual void emitScriptEvent(const QVariant& message) override;
|
||||
void setKeyboardRaised(bool raised);
|
||||
|
||||
SIMPLE_RENDERABLE();
|
||||
|
||||
virtual bool isTransparent() override;
|
||||
|
||||
public:
|
||||
void synthesizeKeyPress(QString key);
|
||||
|
||||
private:
|
||||
bool buildWebSurface(EntityTreeRenderer* renderer);
|
||||
void destroyWebSurface();
|
||||
|
@ -86,7 +64,6 @@ private:
|
|||
QTouchEvent _lastTouchEvent { QEvent::TouchUpdate };
|
||||
uint64_t _lastRenderTime{ 0 };
|
||||
QTouchDevice _touchDevice;
|
||||
WebEntityAPIHelper* _webEntityAPIHelper;
|
||||
|
||||
QMetaObject::Connection _mousePressConnection;
|
||||
QMetaObject::Connection _mouseReleaseConnection;
|
||||
|
|
|
@ -41,16 +41,16 @@
|
|||
#include "Context.h"
|
||||
|
||||
QString fixupHifiUrl(const QString& urlString) {
|
||||
static const QString ACCESS_TOKEN_PARAMETER = "access_token";
|
||||
static const QString ALLOWED_HOST = "metaverse.highfidelity.com";
|
||||
static const QString ACCESS_TOKEN_PARAMETER = "access_token";
|
||||
static const QString ALLOWED_HOST = "metaverse.highfidelity.com";
|
||||
QUrl url(urlString);
|
||||
QUrlQuery query(url);
|
||||
if (url.host() == ALLOWED_HOST && query.allQueryItemValues(ACCESS_TOKEN_PARAMETER).empty()) {
|
||||
auto accountManager = DependencyManager::get<AccountManager>();
|
||||
query.addQueryItem(ACCESS_TOKEN_PARAMETER, accountManager->getAccountInfo().getAccessToken().token);
|
||||
url.setQuery(query.query());
|
||||
return url.toString();
|
||||
}
|
||||
QUrlQuery query(url);
|
||||
if (url.host() == ALLOWED_HOST && query.allQueryItemValues(ACCESS_TOKEN_PARAMETER).empty()) {
|
||||
auto accountManager = DependencyManager::get<AccountManager>();
|
||||
query.addQueryItem(ACCESS_TOKEN_PARAMETER, accountManager->getAccountInfo().getAccessToken().token);
|
||||
url.setQuery(query.query());
|
||||
return url.toString();
|
||||
}
|
||||
return urlString;
|
||||
}
|
||||
|
||||
|
@ -403,13 +403,13 @@ QObject* OffscreenQmlSurface::load(const QUrl& qmlSource, std::function<void(QQm
|
|||
_qmlComponent->loadUrl(qmlSource, QQmlComponent::PreferSynchronous);
|
||||
|
||||
if (_qmlComponent->isLoading()) {
|
||||
connect(_qmlComponent, &QQmlComponent::statusChanged, this,
|
||||
connect(_qmlComponent, &QQmlComponent::statusChanged, this,
|
||||
[this, f](QQmlComponent::Status){
|
||||
finishQmlLoad(f);
|
||||
});
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
||||
return finishQmlLoad(f);
|
||||
}
|
||||
|
||||
|
@ -427,6 +427,19 @@ QObject* OffscreenQmlSurface::finishQmlLoad(std::function<void(QQmlContext*, QOb
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
// FIXME: Refactor with similar code in RenderableWebEntityItem
|
||||
QString javaScriptToInject;
|
||||
QFile webChannelFile(":qtwebchannel/qwebchannel.js");
|
||||
QFile createGlobalEventBridgeFile(PathUtils::resourcesPath() + "/html/createGlobalEventBridge.js");
|
||||
if (webChannelFile.open(QFile::ReadOnly | QFile::Text) &&
|
||||
createGlobalEventBridgeFile.open(QFile::ReadOnly | QFile::Text)) {
|
||||
QString webChannelStr = QTextStream(&webChannelFile).readAll();
|
||||
QString createGlobalEventBridgeStr = QTextStream(&createGlobalEventBridgeFile).readAll();
|
||||
javaScriptToInject = webChannelStr + createGlobalEventBridgeStr;
|
||||
} else {
|
||||
qWarning() << "Unable to find qwebchannel.js or createGlobalEventBridge.js";
|
||||
}
|
||||
|
||||
QQmlContext* newContext = new QQmlContext(_qmlEngine, qApp);
|
||||
QObject* newObject = _qmlComponent->beginCreate(newContext);
|
||||
if (_qmlComponent->isError()) {
|
||||
|
@ -439,6 +452,9 @@ QObject* OffscreenQmlSurface::finishQmlLoad(std::function<void(QQmlContext*, QOb
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
newObject->setProperty("eventBridge", QVariant::fromValue(this));
|
||||
newContext->setContextProperty("eventBridgeJavaScriptToInject", QVariant(javaScriptToInject));
|
||||
|
||||
f(newContext, newObject);
|
||||
_qmlComponent->completeCreate();
|
||||
|
||||
|
@ -446,7 +462,7 @@ QObject* OffscreenQmlSurface::finishQmlLoad(std::function<void(QQmlContext*, QOb
|
|||
// All quick items should be focusable
|
||||
QQuickItem* newItem = qobject_cast<QQuickItem*>(newObject);
|
||||
if (newItem) {
|
||||
// Make sure we make items focusable (critical for
|
||||
// Make sure we make items focusable (critical for
|
||||
// supporting keyboard shortcuts)
|
||||
newItem->setFlag(QQuickItem::ItemIsFocusScope, true);
|
||||
}
|
||||
|
@ -474,11 +490,11 @@ QObject* OffscreenQmlSurface::finishQmlLoad(std::function<void(QQmlContext*, QOb
|
|||
}
|
||||
|
||||
void OffscreenQmlSurface::updateQuick() {
|
||||
// If we're
|
||||
// If we're
|
||||
// a) not set up
|
||||
// b) already rendering a frame
|
||||
// c) rendering too fast
|
||||
// then skip this
|
||||
// then skip this
|
||||
if (!allowNewFrame(_maxFps)) {
|
||||
return;
|
||||
}
|
||||
|
@ -541,7 +557,6 @@ bool OffscreenQmlSurface::eventFilter(QObject* originalDestination, QEvent* even
|
|||
}
|
||||
#endif
|
||||
|
||||
|
||||
switch (event->type()) {
|
||||
case QEvent::Resize: {
|
||||
QResizeEvent* resizeEvent = static_cast<QResizeEvent*>(event);
|
||||
|
@ -610,6 +625,9 @@ void OffscreenQmlSurface::pause() {
|
|||
void OffscreenQmlSurface::resume() {
|
||||
_paused = false;
|
||||
_render = true;
|
||||
|
||||
getRootItem()->setProperty("eventBridge", QVariant::fromValue(this));
|
||||
getRootContext()->setContextProperty("webEntity", this);
|
||||
}
|
||||
|
||||
bool OffscreenQmlSurface::isPaused() const {
|
||||
|
@ -667,15 +685,37 @@ QVariant OffscreenQmlSurface::returnFromUiThread(std::function<QVariant()> funct
|
|||
return function();
|
||||
}
|
||||
|
||||
void OffscreenQmlSurface::focusDestroyed(QObject *obj) {
|
||||
_currentFocusItem = nullptr;
|
||||
}
|
||||
|
||||
void OffscreenQmlSurface::onFocusObjectChanged(QObject* object) {
|
||||
if (!object) {
|
||||
QQuickItem* item = dynamic_cast<QQuickItem*>(object);
|
||||
if (!item) {
|
||||
setFocusText(false);
|
||||
_currentFocusItem = nullptr;
|
||||
return;
|
||||
}
|
||||
|
||||
QInputMethodQueryEvent query(Qt::ImEnabled);
|
||||
qApp->sendEvent(object, &query);
|
||||
setFocusText(query.value(Qt::ImEnabled).toBool());
|
||||
|
||||
if (_currentFocusItem) {
|
||||
disconnect(_currentFocusItem, &QObject::destroyed, this, 0);
|
||||
}
|
||||
|
||||
// Raise and lower keyboard for QML text fields.
|
||||
// HTML text fields are handled in emitWebEvent() methods - testing READ_ONLY_PROPERTY prevents action for HTML files.
|
||||
const char* READ_ONLY_PROPERTY = "readOnly";
|
||||
bool raiseKeyboard = item->hasActiveFocus() && item->property(READ_ONLY_PROPERTY) == false;
|
||||
if (_currentFocusItem && !raiseKeyboard) {
|
||||
setKeyboardRaised(_currentFocusItem, false);
|
||||
}
|
||||
setKeyboardRaised(item, raiseKeyboard); // Always set focus so that alphabetic / numeric setting is updated.
|
||||
|
||||
_currentFocusItem = item;
|
||||
connect(_currentFocusItem, &QObject::destroyed, this, &OffscreenQmlSurface::focusDestroyed);
|
||||
}
|
||||
|
||||
void OffscreenQmlSurface::setFocusText(bool newFocusText) {
|
||||
|
@ -685,4 +725,103 @@ void OffscreenQmlSurface::setFocusText(bool newFocusText) {
|
|||
}
|
||||
}
|
||||
|
||||
// UTF-8 encoded symbols
|
||||
static const uint8_t UPWARDS_WHITE_ARROW_FROM_BAR[] = { 0xE2, 0x87, 0xAA, 0x00 }; // shift
|
||||
static const uint8_t LEFT_ARROW[] = { 0xE2, 0x86, 0x90, 0x00 }; // backspace
|
||||
static const uint8_t LEFTWARD_WHITE_ARROW[] = { 0xE2, 0x87, 0xA6, 0x00 }; // left arrow
|
||||
static const uint8_t RIGHTWARD_WHITE_ARROW[] = { 0xE2, 0x87, 0xA8, 0x00 }; // right arrow
|
||||
static const uint8_t ASTERISIM[] = { 0xE2, 0x81, 0x82, 0x00 }; // symbols
|
||||
static const uint8_t RETURN_SYMBOL[] = { 0xE2, 0x8F, 0x8E, 0x00 }; // return
|
||||
static const char PUNCTUATION_STRING[] = "&123";
|
||||
static const char ALPHABET_STRING[] = "abc";
|
||||
|
||||
static bool equals(const QByteArray& byteArray, const uint8_t* ptr) {
|
||||
int i;
|
||||
for (i = 0; i < byteArray.size(); i++) {
|
||||
if ((char)ptr[i] != byteArray[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return ptr[i] == 0x00;
|
||||
}
|
||||
|
||||
void OffscreenQmlSurface::synthesizeKeyPress(QString key) {
|
||||
auto eventHandler = getEventHandler();
|
||||
if (eventHandler) {
|
||||
auto utf8Key = key.toUtf8();
|
||||
|
||||
int scanCode = (int)utf8Key[0];
|
||||
QString keyString = key;
|
||||
if (equals(utf8Key, UPWARDS_WHITE_ARROW_FROM_BAR) || equals(utf8Key, ASTERISIM) ||
|
||||
equals(utf8Key, (uint8_t*)PUNCTUATION_STRING) || equals(utf8Key, (uint8_t*)ALPHABET_STRING)) {
|
||||
return; // ignore
|
||||
} else if (equals(utf8Key, LEFT_ARROW)) {
|
||||
scanCode = Qt::Key_Backspace;
|
||||
keyString = "\x08";
|
||||
} else if (equals(utf8Key, RETURN_SYMBOL)) {
|
||||
scanCode = Qt::Key_Return;
|
||||
keyString = "\x0d";
|
||||
} else if (equals(utf8Key, LEFTWARD_WHITE_ARROW)) {
|
||||
scanCode = Qt::Key_Left;
|
||||
keyString = "";
|
||||
} else if (equals(utf8Key, RIGHTWARD_WHITE_ARROW)) {
|
||||
scanCode = Qt::Key_Right;
|
||||
keyString = "";
|
||||
}
|
||||
|
||||
QKeyEvent* pressEvent = new QKeyEvent(QEvent::KeyPress, scanCode, Qt::NoModifier, keyString);
|
||||
QKeyEvent* releaseEvent = new QKeyEvent(QEvent::KeyRelease, scanCode, Qt::NoModifier, keyString);
|
||||
QCoreApplication::postEvent(eventHandler, pressEvent);
|
||||
QCoreApplication::postEvent(eventHandler, releaseEvent);
|
||||
}
|
||||
}
|
||||
|
||||
void OffscreenQmlSurface::setKeyboardRaised(QObject* object, bool raised, bool numeric) {
|
||||
if (!object) {
|
||||
return;
|
||||
}
|
||||
|
||||
QQuickItem* item = dynamic_cast<QQuickItem*>(object);
|
||||
while (item) {
|
||||
// Numeric value may be set in parameter from HTML UI; for QML UI, detect numeric fields here.
|
||||
numeric = numeric || QString(item->metaObject()->className()).left(7) == "SpinBox";
|
||||
|
||||
if (item->property("keyboardRaised").isValid()) {
|
||||
if (item->property("punctuationMode").isValid()) {
|
||||
item->setProperty("punctuationMode", QVariant(numeric));
|
||||
}
|
||||
item->setProperty("keyboardRaised", QVariant(raised));
|
||||
return;
|
||||
}
|
||||
item = dynamic_cast<QQuickItem*>(item->parentItem());
|
||||
}
|
||||
}
|
||||
|
||||
void OffscreenQmlSurface::emitScriptEvent(const QVariant& message) {
|
||||
if (QThread::currentThread() != thread()) {
|
||||
QMetaObject::invokeMethod(this, "emitScriptEvent", Qt::QueuedConnection, Q_ARG(QVariant, message));
|
||||
} else {
|
||||
emit scriptEventReceived(message);
|
||||
}
|
||||
}
|
||||
|
||||
void OffscreenQmlSurface::emitWebEvent(const QVariant& message) {
|
||||
if (QThread::currentThread() != thread()) {
|
||||
QMetaObject::invokeMethod(this, "emitWebEvent", Qt::QueuedConnection, Q_ARG(QVariant, message));
|
||||
} else {
|
||||
// Special case to handle raising and lowering the virtual keyboard.
|
||||
const QString RAISE_KEYBOARD = "_RAISE_KEYBOARD";
|
||||
const QString RAISE_KEYBOARD_NUMERIC = "_RAISE_KEYBOARD_NUMERIC";
|
||||
const QString LOWER_KEYBOARD = "_LOWER_KEYBOARD";
|
||||
QString messageString = message.type() == QVariant::String ? message.toString() : "";
|
||||
if (messageString.left(RAISE_KEYBOARD.length()) == RAISE_KEYBOARD) {
|
||||
setKeyboardRaised(_currentFocusItem, true, messageString == RAISE_KEYBOARD_NUMERIC);
|
||||
} else if (messageString == LOWER_KEYBOARD) {
|
||||
setKeyboardRaised(_currentFocusItem, false);
|
||||
} else {
|
||||
emit webEventReceived(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#include "OffscreenQmlSurface.moc"
|
||||
|
|
|
@ -30,7 +30,6 @@ class QQmlContext;
|
|||
class QQmlComponent;
|
||||
class QQuickWindow;
|
||||
class QQuickItem;
|
||||
|
||||
class OffscreenQmlSurface : public QObject {
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(bool focusText READ isFocusText NOTIFY focusTextChanged)
|
||||
|
@ -74,6 +73,9 @@ public:
|
|||
QPointF mapToVirtualScreen(const QPointF& originalPoint, QObject* originalWidget);
|
||||
bool eventFilter(QObject* originalDestination, QEvent* event) override;
|
||||
|
||||
void setKeyboardRaised(QObject* object, bool raised, bool numeric = false);
|
||||
Q_INVOKABLE void synthesizeKeyPress(QString key);
|
||||
|
||||
using TextureAndFence = std::pair<uint32_t, void*>;
|
||||
// Checks to see if a new texture is available. If one is, the function returns true and
|
||||
// textureAndFence will be populated with the texture ID and a fence which will be signalled
|
||||
|
@ -90,6 +92,16 @@ signals:
|
|||
|
||||
public slots:
|
||||
void onAboutToQuit();
|
||||
void focusDestroyed(QObject *obj);
|
||||
|
||||
// event bridge
|
||||
public slots:
|
||||
void emitScriptEvent(const QVariant& scriptMessage);
|
||||
void emitWebEvent(const QVariant& webMessage);
|
||||
signals:
|
||||
void scriptEventReceived(const QVariant& message);
|
||||
void webEventReceived(const QVariant& message);
|
||||
|
||||
|
||||
protected:
|
||||
bool filterEnabled(QObject* originalDestination, QEvent* event) const;
|
||||
|
@ -137,6 +149,8 @@ private:
|
|||
uint8_t _maxFps { 60 };
|
||||
MouseTranslator _mouseTranslator { [](const QPointF& p) { return p.toPoint(); } };
|
||||
QWindow* _proxyWindow { nullptr };
|
||||
|
||||
QQuickItem* _currentFocusItem { nullptr };
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
|
@ -43,7 +43,36 @@ void QmlWebWindowClass::emitWebEvent(const QVariant& webMessage) {
|
|||
if (QThread::currentThread() != thread()) {
|
||||
QMetaObject::invokeMethod(this, "emitWebEvent", Qt::QueuedConnection, Q_ARG(QVariant, webMessage));
|
||||
} else {
|
||||
emit webEventReceived(webMessage);
|
||||
// Special case to handle raising and lowering the virtual keyboard.
|
||||
const QString RAISE_KEYBOARD = "_RAISE_KEYBOARD";
|
||||
const QString RAISE_KEYBOARD_NUMERIC = "_RAISE_KEYBOARD_NUMERIC";
|
||||
const QString LOWER_KEYBOARD = "_LOWER_KEYBOARD";
|
||||
QString messageString = webMessage.type() == QVariant::String ? webMessage.toString() : "";
|
||||
if (messageString.left(RAISE_KEYBOARD.length()) == RAISE_KEYBOARD) {
|
||||
setKeyboardRaised(asQuickItem(), true, messageString == RAISE_KEYBOARD_NUMERIC);
|
||||
} else if (messageString == LOWER_KEYBOARD) {
|
||||
setKeyboardRaised(asQuickItem(), false);
|
||||
} else {
|
||||
emit webEventReceived(webMessage);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void QmlWebWindowClass::setKeyboardRaised(QObject* object, bool raised, bool numeric) {
|
||||
if (!object) {
|
||||
return;
|
||||
}
|
||||
|
||||
QQuickItem* item = dynamic_cast<QQuickItem*>(object);
|
||||
while (item) {
|
||||
if (item->property("keyboardRaised").isValid()) {
|
||||
if (item->property("punctuationMode").isValid()) {
|
||||
item->setProperty("punctuationMode", QVariant(numeric));
|
||||
}
|
||||
item->setProperty("keyboardRaised", QVariant(raised));
|
||||
return;
|
||||
}
|
||||
item = dynamic_cast<QQuickItem*>(item->parentItem());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -33,6 +33,9 @@ signals:
|
|||
|
||||
protected:
|
||||
QString qmlSource() const override { return "QmlWebWindow.qml"; }
|
||||
|
||||
private:
|
||||
void setKeyboardRaised(QObject* object, bool raised, bool numeric = false);
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
|
@ -44,7 +44,7 @@ static const uint32_t RELEASE_OPENVR_HMD_DELAY_MS = 5000;
|
|||
|
||||
bool isOculusPresent() {
|
||||
bool result = false;
|
||||
#if defined(Q_OS_WIN32)
|
||||
#if defined(Q_OS_WIN32)
|
||||
HANDLE oculusServiceEvent = ::OpenEventW(SYNCHRONIZE, FALSE, L"OculusHMDConnected");
|
||||
// The existence of the service indicates a running Oculus runtime
|
||||
if (oculusServiceEvent) {
|
||||
|
@ -54,7 +54,7 @@ bool isOculusPresent() {
|
|||
}
|
||||
::CloseHandle(oculusServiceEvent);
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
return result;
|
||||
}
|
||||
|
||||
|
@ -123,65 +123,12 @@ static bool _keyboardShown { false };
|
|||
static bool _overlayRevealed { false };
|
||||
static const uint32_t SHOW_KEYBOARD_DELAY_MS = 400;
|
||||
|
||||
void showOpenVrKeyboard(bool show = true) {
|
||||
if (!_overlay) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (show) {
|
||||
// To avoid flickering the keyboard when a text element is only briefly selected,
|
||||
// show the keyboard asynchrnously after a very short delay, but only after we check
|
||||
// that the current focus object is still one that is text enabled
|
||||
QTimer::singleShot(SHOW_KEYBOARD_DELAY_MS, [] {
|
||||
auto offscreenUi = DependencyManager::get<OffscreenUi>();
|
||||
auto currentFocus = offscreenUi->getWindow()->focusObject();
|
||||
QInputMethodQueryEvent query(Qt::ImEnabled | Qt::ImQueryInput | Qt::ImHints);
|
||||
qApp->sendEvent(currentFocus, &query);
|
||||
// Current focus isn't text enabled, bail early.
|
||||
if (!query.value(Qt::ImEnabled).toBool()) {
|
||||
return;
|
||||
}
|
||||
// We're going to show the keyboard now...
|
||||
_keyboardFocusObject = currentFocus;
|
||||
_currentHints = Qt::InputMethodHints(query.value(Qt::ImHints).toUInt());
|
||||
vr::EGamepadTextInputMode inputMode = vr::k_EGamepadTextInputModeNormal;
|
||||
if (_currentHints & Qt::ImhHiddenText) {
|
||||
inputMode = vr::k_EGamepadTextInputModePassword;
|
||||
}
|
||||
vr::EGamepadTextInputLineMode lineMode = vr::k_EGamepadTextInputLineModeSingleLine;
|
||||
if (_currentHints & Qt::ImhMultiLine) {
|
||||
lineMode = vr::k_EGamepadTextInputLineModeMultipleLines;
|
||||
}
|
||||
_existingText = query.value(Qt::ImSurroundingText).toString();
|
||||
|
||||
auto showKeyboardResult = _overlay->ShowKeyboard(inputMode, lineMode, "Keyboard", 1024,
|
||||
_existingText.toLocal8Bit().toStdString().c_str(), false, 0);
|
||||
|
||||
if (vr::VROverlayError_None == showKeyboardResult) {
|
||||
_keyboardShown = true;
|
||||
// Try to position the keyboard slightly below where the user is looking.
|
||||
mat4 headPose = cancelOutRollAndPitch(toGlm(_nextSimPoseData.vrPoses[0].mDeviceToAbsoluteTracking));
|
||||
mat4 keyboardTransform = glm::translate(headPose, vec3(0, -0.5, -1));
|
||||
keyboardTransform = keyboardTransform * glm::rotate(mat4(), 3.14159f / 4.0f, vec3(-1, 0, 0));
|
||||
auto keyboardTransformVr = toOpenVr(keyboardTransform);
|
||||
_overlay->SetKeyboardTransformAbsolute(vr::ETrackingUniverseOrigin::TrackingUniverseStanding, &keyboardTransformVr);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
_keyboardFocusObject = nullptr;
|
||||
if (_keyboardShown) {
|
||||
_overlay->HideKeyboard();
|
||||
_keyboardShown = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void updateFromOpenVrKeyboardInput() {
|
||||
auto chars = _overlay->GetKeyboardText(textArray, 8192);
|
||||
auto newText = QString(QByteArray(textArray, chars));
|
||||
_keyboardFocusObject->setProperty("text", newText);
|
||||
//// TODO modify the new text to match the possible input hints:
|
||||
//// ImhDigitsOnly ImhFormattedNumbersOnly ImhUppercaseOnly ImhLowercaseOnly
|
||||
//// ImhDigitsOnly ImhFormattedNumbersOnly ImhUppercaseOnly ImhLowercaseOnly
|
||||
//// ImhDialableCharactersOnly ImhEmailCharactersOnly ImhUrlCharactersOnly ImhLatinOnly
|
||||
//QInputMethodEvent event(_existingText, QList<QInputMethodEvent::Attribute>());
|
||||
//event.setCommitString(newText, 0, _existingText.size());
|
||||
|
@ -208,11 +155,11 @@ void enableOpenVrKeyboard(PluginContainer* container) {
|
|||
auto offscreenUi = DependencyManager::get<OffscreenUi>();
|
||||
_overlay = vr::VROverlay();
|
||||
|
||||
|
||||
|
||||
auto menu = container->getPrimaryMenu();
|
||||
auto action = menu->getActionForOption(MenuOption::Overlays);
|
||||
|
||||
// When the overlays are revealed, suppress the keyboard from appearing on text focus for a tenth of a second.
|
||||
// When the overlays are revealed, suppress the keyboard from appearing on text focus for a tenth of a second.
|
||||
_overlayMenuConnection = QObject::connect(action, &QAction::triggered, [action] {
|
||||
if (action->isChecked()) {
|
||||
_overlayRevealed = true;
|
||||
|
@ -220,23 +167,6 @@ void enableOpenVrKeyboard(PluginContainer* container) {
|
|||
QTimer::singleShot(KEYBOARD_DELAY_MS, [&] { _overlayRevealed = false; });
|
||||
}
|
||||
});
|
||||
|
||||
_focusConnection = QObject::connect(offscreenUi->getWindow(), &QQuickWindow::focusObjectChanged, [](QObject* object) {
|
||||
if (object != _keyboardFocusObject) {
|
||||
showOpenVrKeyboard(false);
|
||||
}
|
||||
});
|
||||
|
||||
_focusTextConnection = QObject::connect(offscreenUi.data(), &OffscreenUi::focusTextChanged, [](bool focusText) {
|
||||
if (_openVrDisplayActive) {
|
||||
if (_overlayRevealed) {
|
||||
// suppress at most one text focus event
|
||||
_overlayRevealed = false;
|
||||
return;
|
||||
}
|
||||
showOpenVrKeyboard(focusText);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
@ -276,7 +206,7 @@ void handleOpenVrEvents() {
|
|||
updateFromOpenVrKeyboardInput();
|
||||
break;
|
||||
|
||||
case vr::VREvent_KeyboardDone:
|
||||
case vr::VREvent_KeyboardDone:
|
||||
finishOpenVrKeyboardInput();
|
||||
|
||||
// FALL THROUGH
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
<script type="text/javascript" src="qrc:///qtwebchannel/qwebchannel.js"></script>
|
||||
<script type="text/javascript" src="js/eventBridgeLoader.js"></script>
|
||||
<script type="text/javascript" src="js/spinButtons.js"></script>
|
||||
<script type="text/javascript" src="js/keyboardControl.js"></script>
|
||||
<script type="text/javascript" src="js/entityList.js"></script>
|
||||
</head>
|
||||
<body onload='loaded();'>
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
<script type="text/javascript" src="qrc:///qtwebchannel/qwebchannel.js"></script>
|
||||
<script type="text/javascript" src="js/eventBridgeLoader.js"></script>
|
||||
<script type="text/javascript" src="js/spinButtons.js"></script>
|
||||
<script type="text/javascript" src="js/keyboardControl.js"></script>
|
||||
<script type="text/javascript" src="js/entityProperties.js"></script>
|
||||
<script src="js/jsoneditor.min.js"></script>
|
||||
</head>
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
<script type="text/javascript" src="qrc:///qtwebchannel/qwebchannel.js"></script>
|
||||
<script type="text/javascript" src="js/eventBridgeLoader.js"></script>
|
||||
<script type="text/javascript" src="js/spinButtons.js"></script>
|
||||
<script type="text/javascript" src="js/keyboardControl.js"></script>
|
||||
<script type="text/javascript" src="js/gridControls.js"></script>
|
||||
</head>
|
||||
<body onload='loaded();'>
|
||||
|
|
|
@ -122,6 +122,8 @@ function loaded() {
|
|||
focus: false,
|
||||
entityIds: selection,
|
||||
}));
|
||||
|
||||
refreshFooter();
|
||||
}
|
||||
|
||||
function onRowDoubleClicked() {
|
||||
|
@ -184,6 +186,7 @@ function loaded() {
|
|||
function clearEntities() {
|
||||
entities = {};
|
||||
entityList.clear();
|
||||
refreshFooter();
|
||||
}
|
||||
|
||||
var elSortOrder = {
|
||||
|
@ -236,13 +239,16 @@ function loaded() {
|
|||
refreshFooter();
|
||||
}
|
||||
|
||||
function updateSelectedEntities(selectedEntities) {
|
||||
function updateSelectedEntities(selectedIDs) {
|
||||
var notFound = false;
|
||||
for (var id in entities) {
|
||||
entities[id].el.className = '';
|
||||
}
|
||||
for (var i = 0; i < selectedEntities.length; i++) {
|
||||
var id = selectedEntities[i];
|
||||
|
||||
selectedEntities = [];
|
||||
for (var i = 0; i < selectedIDs.length; i++) {
|
||||
var id = selectedIDs[i];
|
||||
selectedEntities.push(id);
|
||||
if (id in entities) {
|
||||
var entity = entities[id];
|
||||
entity.el.className = 'selected';
|
||||
|
@ -251,10 +257,7 @@ function loaded() {
|
|||
}
|
||||
}
|
||||
|
||||
// HACK: Fixes the footer and header text sometimes not displaying after adding or deleting entities.
|
||||
// The problem appears to be a bug in the Qt HTML/CSS rendering (Qt 5.5).
|
||||
document.getElementById("radius").focus();
|
||||
document.getElementById("radius").blur();
|
||||
refreshFooter();
|
||||
|
||||
return notFound;
|
||||
}
|
||||
|
@ -412,6 +415,8 @@ function loaded() {
|
|||
|
||||
augmentSpinButtons();
|
||||
|
||||
setUpKeyboardControl();
|
||||
|
||||
// Disable right-click context menu which is not visible in the HMD and makes it seem like the app has locked
|
||||
document.addEventListener("contextmenu", function (event) {
|
||||
event.preventDefault();
|
||||
|
|
|
@ -1590,6 +1590,8 @@ function loaded() {
|
|||
|
||||
augmentSpinButtons();
|
||||
|
||||
setUpKeyboardControl();
|
||||
|
||||
// Disable right-click context menu which is not visible in the HMD and makes it seem like the app has locked
|
||||
document.addEventListener("contextmenu", function(event) {
|
||||
event.preventDefault();
|
||||
|
|
|
@ -127,6 +127,8 @@ function loaded() {
|
|||
|
||||
augmentSpinButtons();
|
||||
|
||||
setUpKeyboardControl();
|
||||
|
||||
EventBridge.emitWebEvent(JSON.stringify({ type: 'init' }));
|
||||
});
|
||||
|
||||
|
|
67
scripts/system/html/js/keyboardControl.js
Normal file
67
scripts/system/html/js/keyboardControl.js
Normal file
|
@ -0,0 +1,67 @@
|
|||
//
|
||||
// keyboardControl.js
|
||||
//
|
||||
// Created by David Rowe on 28 Sep 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
|
||||
//
|
||||
|
||||
function setUpKeyboardControl() {
|
||||
|
||||
var lowerTimer = null;
|
||||
var isRaised = false;
|
||||
var KEYBOARD_HEIGHT = 200;
|
||||
|
||||
function raiseKeyboard() {
|
||||
if (lowerTimer !== null) {
|
||||
clearTimeout(lowerTimer);
|
||||
lowerTimer = null;
|
||||
}
|
||||
|
||||
EventBridge.emitWebEvent("_RAISE_KEYBOARD" + (this.type === "number" ? "_NUMERIC" : ""));
|
||||
|
||||
if (!isRaised) {
|
||||
var delta = this.getBoundingClientRect().bottom + 10 - (document.body.clientHeight - KEYBOARD_HEIGHT);
|
||||
if (delta > 0) {
|
||||
setTimeout(function () {
|
||||
document.body.scrollTop += delta;
|
||||
}, 500); // Allow time for keyboard to be raised in QML.
|
||||
}
|
||||
}
|
||||
|
||||
isRaised = true;
|
||||
}
|
||||
|
||||
function doLowerKeyboard() {
|
||||
EventBridge.emitWebEvent("_LOWER_KEYBOARD");
|
||||
lowerTimer = null;
|
||||
isRaised = false;
|
||||
}
|
||||
|
||||
function lowerKeyboard() {
|
||||
// Delay lowering keyboard a little in case immediately raise it again.
|
||||
if (lowerTimer === null) {
|
||||
lowerTimer = setTimeout(doLowerKeyboard, 20);
|
||||
}
|
||||
}
|
||||
|
||||
function documentBlur() {
|
||||
// Action any pending Lower keyboard event immediately upon leaving document window so that they don't interfere with
|
||||
// other Entities Editor tab.
|
||||
if (lowerTimer !== null) {
|
||||
clearTimeout(lowerTimer);
|
||||
doLowerKeyboard();
|
||||
}
|
||||
}
|
||||
|
||||
var inputs = document.querySelectorAll("input[type=text], input[type=number], textarea");
|
||||
for (var i = 0, length = inputs.length; i < length; i++) {
|
||||
inputs[i].addEventListener("focus", raiseKeyboard);
|
||||
inputs[i].addEventListener("blur", lowerKeyboard);
|
||||
}
|
||||
|
||||
window.addEventListener("blur", documentBlur);
|
||||
}
|
||||
|
Loading…
Reference in a new issue