Merge branch 'master' of github.com:highfidelity/hifi into MS16562_massivePalSpeedup
|
@ -304,6 +304,11 @@ Java_io_highfidelity_hifiinterface_MainActivity_nativeGetDisplayName(JNIEnv *env
|
||||||
return env->NewStringUTF(username.toLatin1().data());
|
return env->NewStringUTF(username.toLatin1().data());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
JNIEXPORT void JNICALL
|
||||||
|
Java_io_highfidelity_hifiinterface_InterfaceActivity_nativeBeforeEnterBackground(JNIEnv *env, jobject obj) {
|
||||||
|
AndroidHelper::instance().notifyBeforeEnterBackground();
|
||||||
|
}
|
||||||
|
|
||||||
JNIEXPORT void JNICALL
|
JNIEXPORT void JNICALL
|
||||||
Java_io_highfidelity_hifiinterface_InterfaceActivity_nativeEnterBackground(JNIEnv *env, jobject obj) {
|
Java_io_highfidelity_hifiinterface_InterfaceActivity_nativeEnterBackground(JNIEnv *env, jobject obj) {
|
||||||
AndroidHelper::instance().notifyEnterBackground();
|
AndroidHelper::instance().notifyEnterBackground();
|
||||||
|
|
|
@ -59,6 +59,7 @@ public class InterfaceActivity extends QtActivity implements WebViewFragment.OnW
|
||||||
private native long nativeOnCreate(InterfaceActivity instance, AssetManager assetManager);
|
private native long nativeOnCreate(InterfaceActivity instance, AssetManager assetManager);
|
||||||
private native void nativeOnDestroy();
|
private native void nativeOnDestroy();
|
||||||
private native void nativeGotoUrl(String url);
|
private native void nativeGotoUrl(String url);
|
||||||
|
private native void nativeBeforeEnterBackground();
|
||||||
private native void nativeEnterBackground();
|
private native void nativeEnterBackground();
|
||||||
private native void nativeEnterForeground();
|
private native void nativeEnterForeground();
|
||||||
private native long nativeOnExitVr();
|
private native long nativeOnExitVr();
|
||||||
|
@ -291,6 +292,7 @@ public class InterfaceActivity extends QtActivity implements WebViewFragment.OnW
|
||||||
case "Home":
|
case "Home":
|
||||||
case "Privacy Policy":
|
case "Privacy Policy":
|
||||||
case "Login": {
|
case "Login": {
|
||||||
|
nativeBeforeEnterBackground();
|
||||||
Intent intent = new Intent(this, MainActivity.class);
|
Intent intent = new Intent(this, MainActivity.class);
|
||||||
intent.putExtra(MainActivity.EXTRA_FRAGMENT, activityName);
|
intent.putExtra(MainActivity.EXTRA_FRAGMENT, activityName);
|
||||||
intent.putExtra(MainActivity.EXTRA_BACK_TO_SCENE, backToScene);
|
intent.putExtra(MainActivity.EXTRA_BACK_TO_SCENE, backToScene);
|
||||||
|
|
Before Width: | Height: | Size: 298 KiB After Width: | Height: | Size: 91 KiB |
Before Width: | Height: | Size: 229 KiB After Width: | Height: | Size: 76 KiB |
Before Width: | Height: | Size: 289 KiB After Width: | Height: | Size: 107 KiB |
Before Width: | Height: | Size: 254 KiB After Width: | Height: | Size: 92 KiB |
BIN
interface/resources/html/img/tablet-help-windowsMR.jpg
Normal file
After Width: | Height: | Size: 94 KiB |
|
@ -66,7 +66,7 @@
|
||||||
<script>
|
<script>
|
||||||
var handControllerImageURL = null;
|
var handControllerImageURL = null;
|
||||||
var index = 0;
|
var index = 0;
|
||||||
var count = 4;
|
var count = 5;
|
||||||
|
|
||||||
function showKbm() {
|
function showKbm() {
|
||||||
document.getElementById("main_image").setAttribute("src", "img/tablet-help-keyboard.jpg");
|
document.getElementById("main_image").setAttribute("src", "img/tablet-help-keyboard.jpg");
|
||||||
|
@ -102,9 +102,13 @@
|
||||||
showHandControllers();
|
showHandControllers();
|
||||||
break;
|
break;
|
||||||
case 2:
|
case 2:
|
||||||
showGamepad();
|
handControllerImageURL = "img/tablet-help-windowsMR.jpg";
|
||||||
|
showHandControllers();
|
||||||
break;
|
break;
|
||||||
case 3:
|
case 3:
|
||||||
|
showGamepad();
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
showKbm();
|
showKbm();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
@ -144,7 +148,10 @@
|
||||||
handControllerImageURL = "img/tablet-help-oculus.jpg";
|
handControllerImageURL = "img/tablet-help-oculus.jpg";
|
||||||
index = 0;
|
index = 0;
|
||||||
break;
|
break;
|
||||||
|
case "windowsMR":
|
||||||
|
handControllerImageURL = "img/tablet-help-windowsMR.jpg";
|
||||||
|
index = 2;
|
||||||
|
break;
|
||||||
case "vive":
|
case "vive":
|
||||||
default:
|
default:
|
||||||
handControllerImageURL = "img/tablet-help-vive.jpg";
|
handControllerImageURL = "img/tablet-help-vive.jpg";
|
||||||
|
@ -154,7 +161,7 @@
|
||||||
switch (params.defaultTab) {
|
switch (params.defaultTab) {
|
||||||
case "gamepad":
|
case "gamepad":
|
||||||
showGamepad();
|
showGamepad();
|
||||||
index = 2;
|
index = 3;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "handControllers":
|
case "handControllers":
|
||||||
|
@ -164,7 +171,7 @@
|
||||||
case "kbm":
|
case "kbm":
|
||||||
default:
|
default:
|
||||||
showKbm();
|
showKbm();
|
||||||
index = 3;
|
index = 4;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -1,125 +0,0 @@
|
||||||
//
|
|
||||||
// Button.qml
|
|
||||||
//
|
|
||||||
// Created by David Rowe on 16 Feb 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 as Original
|
|
||||||
import QtQuick.Controls.Styles 1.4
|
|
||||||
import TabletScriptingInterface 1.0
|
|
||||||
|
|
||||||
import "../styles-uit"
|
|
||||||
|
|
||||||
Original.Button {
|
|
||||||
id: root;
|
|
||||||
|
|
||||||
property int color: 0
|
|
||||||
property int colorScheme: hifi.colorSchemes.light
|
|
||||||
property string buttonGlyph: "";
|
|
||||||
|
|
||||||
width: hifi.dimensions.buttonWidth
|
|
||||||
height: hifi.dimensions.controlLineHeight
|
|
||||||
|
|
||||||
HifiConstants { id: hifi }
|
|
||||||
|
|
||||||
onHoveredChanged: {
|
|
||||||
if (hovered) {
|
|
||||||
Tablet.playSound(TabletEnums.ButtonHover);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onFocusChanged: {
|
|
||||||
if (focus) {
|
|
||||||
Tablet.playSound(TabletEnums.ButtonHover);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onClicked: {
|
|
||||||
Tablet.playSound(TabletEnums.ButtonClick);
|
|
||||||
}
|
|
||||||
|
|
||||||
style: ButtonStyle {
|
|
||||||
|
|
||||||
background: Rectangle {
|
|
||||||
radius: hifi.buttons.radius
|
|
||||||
|
|
||||||
border.width: (control.color === hifi.buttons.none ||
|
|
||||||
(control.color === hifi.buttons.noneBorderless && control.hovered) ||
|
|
||||||
(control.color === hifi.buttons.noneBorderlessWhite && control.hovered) ||
|
|
||||||
(control.color === hifi.buttons.noneBorderlessGray && control.hovered)) ? 1 : 0;
|
|
||||||
border.color: control.color === hifi.buttons.noneBorderless ? hifi.colors.blueHighlight :
|
|
||||||
(control.color === hifi.buttons.noneBorderlessGray ? hifi.colors.baseGray : hifi.colors.white);
|
|
||||||
|
|
||||||
gradient: Gradient {
|
|
||||||
GradientStop {
|
|
||||||
position: 0.2
|
|
||||||
color: {
|
|
||||||
if (!control.enabled) {
|
|
||||||
hifi.buttons.disabledColorStart[control.colorScheme]
|
|
||||||
} else if (control.pressed) {
|
|
||||||
hifi.buttons.pressedColor[control.color]
|
|
||||||
} else if (control.hovered) {
|
|
||||||
hifi.buttons.hoveredColor[control.color]
|
|
||||||
} else if (!control.hovered && control.focus) {
|
|
||||||
hifi.buttons.focusedColor[control.color]
|
|
||||||
} else {
|
|
||||||
hifi.buttons.colorStart[control.color]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
GradientStop {
|
|
||||||
position: 1.0
|
|
||||||
color: {
|
|
||||||
if (!control.enabled) {
|
|
||||||
hifi.buttons.disabledColorFinish[control.colorScheme]
|
|
||||||
} else if (control.pressed) {
|
|
||||||
hifi.buttons.pressedColor[control.color]
|
|
||||||
} else if (control.hovered) {
|
|
||||||
hifi.buttons.hoveredColor[control.color]
|
|
||||||
} else if (!control.hovered && control.focus) {
|
|
||||||
hifi.buttons.focusedColor[control.color]
|
|
||||||
} else {
|
|
||||||
hifi.buttons.colorFinish[control.color]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
label: Item {
|
|
||||||
HiFiGlyphs {
|
|
||||||
id: buttonGlyph;
|
|
||||||
visible: root.buttonGlyph !== "";
|
|
||||||
text: root.buttonGlyph === "" ? hifi.glyphs.question : root.buttonGlyph;
|
|
||||||
// Size
|
|
||||||
size: 34;
|
|
||||||
// Anchors
|
|
||||||
anchors.right: buttonText.left;
|
|
||||||
anchors.top: parent.top;
|
|
||||||
anchors.bottom: parent.bottom;
|
|
||||||
// Style
|
|
||||||
color: enabled ? hifi.buttons.textColor[control.color]
|
|
||||||
: hifi.buttons.disabledTextColor[control.colorScheme];
|
|
||||||
// Alignment
|
|
||||||
horizontalAlignment: Text.AlignHCenter;
|
|
||||||
verticalAlignment: Text.AlignVCenter;
|
|
||||||
}
|
|
||||||
RalewayBold {
|
|
||||||
id: buttonText;
|
|
||||||
anchors.centerIn: parent;
|
|
||||||
font.capitalization: Font.AllUppercase
|
|
||||||
color: enabled ? hifi.buttons.textColor[control.color]
|
|
||||||
: hifi.buttons.disabledTextColor[control.colorScheme]
|
|
||||||
size: hifi.fontSizes.buttonLabel
|
|
||||||
verticalAlignment: Text.AlignVCenter
|
|
||||||
horizontalAlignment: Text.AlignHCenter
|
|
||||||
text: control.text
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,165 +0,0 @@
|
||||||
//
|
|
||||||
// Table.qml
|
|
||||||
//
|
|
||||||
// Created by David Rowe on 18 Feb 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 QtQuick.Controls 2.2 as QQC2
|
|
||||||
|
|
||||||
import "../styles-uit"
|
|
||||||
|
|
||||||
TableView {
|
|
||||||
id: tableView
|
|
||||||
|
|
||||||
property int colorScheme: hifi.colorSchemes.light
|
|
||||||
readonly property bool isLightColorScheme: colorScheme == hifi.colorSchemes.light
|
|
||||||
property bool expandSelectedRow: false
|
|
||||||
property bool centerHeaderText: false
|
|
||||||
readonly property real headerSpacing: 3 //spacing between sort indicator and table header title
|
|
||||||
property var titlePaintedPos: [] // storing extra data position behind painted
|
|
||||||
// title text and sort indicatorin table's header
|
|
||||||
signal titlePaintedPosSignal(int column) //signal that extradata position gets changed
|
|
||||||
|
|
||||||
model: ListModel { }
|
|
||||||
|
|
||||||
Component.onCompleted: {
|
|
||||||
if (flickableItem !== null && flickableItem !== undefined) {
|
|
||||||
tableView.flickableItem.QQC2.ScrollBar.vertical = scrollbar
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
QQC2.ScrollBar {
|
|
||||||
id: scrollbar
|
|
||||||
parent: tableView.flickableItem
|
|
||||||
policy: QQC2.ScrollBar.AsNeeded
|
|
||||||
orientation: Qt.Vertical
|
|
||||||
visible: size < 1.0
|
|
||||||
topPadding: tableView.headerVisible ? hifi.dimensions.tableHeaderHeight + 1 : 1
|
|
||||||
anchors.top: tableView.top
|
|
||||||
anchors.left: tableView.right
|
|
||||||
anchors.bottom: tableView.bottom
|
|
||||||
|
|
||||||
background: Item {
|
|
||||||
implicitWidth: hifi.dimensions.scrollbarBackgroundWidth
|
|
||||||
Rectangle {
|
|
||||||
anchors {
|
|
||||||
fill: parent;
|
|
||||||
topMargin: tableView.headerVisible ? hifi.dimensions.tableHeaderHeight : 0
|
|
||||||
}
|
|
||||||
color: isLightColorScheme ? hifi.colors.tableScrollBackgroundLight
|
|
||||||
: hifi.colors.tableScrollBackgroundDark
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
contentItem: Item {
|
|
||||||
implicitWidth: hifi.dimensions.scrollbarHandleWidth
|
|
||||||
Rectangle {
|
|
||||||
anchors.fill: parent
|
|
||||||
radius: (width - 4)/2
|
|
||||||
color: isLightColorScheme ? hifi.colors.tableScrollHandleLight : hifi.colors.tableScrollHandleDark
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
headerVisible: false
|
|
||||||
headerDelegate: Rectangle {
|
|
||||||
height: hifi.dimensions.tableHeaderHeight
|
|
||||||
color: isLightColorScheme ? hifi.colors.tableBackgroundLight : hifi.colors.tableBackgroundDark
|
|
||||||
|
|
||||||
|
|
||||||
RalewayRegular {
|
|
||||||
id: titleText
|
|
||||||
x: centerHeaderText ? (parent.width - paintedWidth -
|
|
||||||
((sortIndicatorVisible &&
|
|
||||||
sortIndicatorColumn === styleData.column) ?
|
|
||||||
(titleSort.paintedWidth / 5 + tableView.headerSpacing) : 0)) / 2 :
|
|
||||||
hifi.dimensions.tablePadding
|
|
||||||
text: styleData.value
|
|
||||||
size: hifi.fontSizes.tableHeading
|
|
||||||
font.capitalization: Font.AllUppercase
|
|
||||||
color: hifi.colors.baseGrayHighlight
|
|
||||||
horizontalAlignment: (centerHeaderText ? Text.AlignHCenter : Text.AlignLeft)
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
//actual image of sort indicator in glyph font only 20% of real font size
|
|
||||||
//i.e. if the charachter size set to 60 pixels, actual image is 12 pixels
|
|
||||||
HiFiGlyphs {
|
|
||||||
id: titleSort
|
|
||||||
text: sortIndicatorOrder == Qt.AscendingOrder ? hifi.glyphs.caratUp : hifi.glyphs.caratDn
|
|
||||||
color: hifi.colors.darkGray
|
|
||||||
opacity: 0.6;
|
|
||||||
size: hifi.fontSizes.tableHeadingIcon
|
|
||||||
anchors.verticalCenter: titleText.verticalCenter
|
|
||||||
anchors.left: titleText.right
|
|
||||||
anchors.leftMargin: -(hifi.fontSizes.tableHeadingIcon / 2.5) + tableView.headerSpacing
|
|
||||||
visible: sortIndicatorVisible && sortIndicatorColumn === styleData.column
|
|
||||||
onXChanged: {
|
|
||||||
titlePaintedPos[styleData.column] = titleText.x + titleText.paintedWidth +
|
|
||||||
paintedWidth / 5 + tableView.headerSpacing*2
|
|
||||||
titlePaintedPosSignal(styleData.column)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
width: 1
|
|
||||||
anchors {
|
|
||||||
left: parent.left
|
|
||||||
top: parent.top
|
|
||||||
topMargin: 1
|
|
||||||
bottom: parent.bottom
|
|
||||||
bottomMargin: 2
|
|
||||||
}
|
|
||||||
color: isLightColorScheme ? hifi.colors.lightGrayText : hifi.colors.baseGrayHighlight
|
|
||||||
visible: styleData.column > 0
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
height: 1
|
|
||||||
anchors {
|
|
||||||
left: parent.left
|
|
||||||
right: parent.right
|
|
||||||
bottom: parent.bottom
|
|
||||||
}
|
|
||||||
color: isLightColorScheme ? hifi.colors.lightGrayText : hifi.colors.baseGrayHighlight
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Use rectangle to draw border with rounded corners.
|
|
||||||
frameVisible: false
|
|
||||||
Rectangle {
|
|
||||||
color: "#00000000"
|
|
||||||
anchors { fill: parent; margins: -2 }
|
|
||||||
border.color: isLightColorScheme ? hifi.colors.lightGrayText : hifi.colors.baseGrayHighlight
|
|
||||||
border.width: 2
|
|
||||||
}
|
|
||||||
anchors.margins: 2 // Shrink TableView to lie within border.
|
|
||||||
|
|
||||||
backgroundVisible: true
|
|
||||||
|
|
||||||
horizontalScrollBarPolicy: Qt.ScrollBarAlwaysOff
|
|
||||||
verticalScrollBarPolicy: Qt.ScrollBarAlwaysOff
|
|
||||||
|
|
||||||
style: TableViewStyle {
|
|
||||||
// Needed in order for rows to keep displaying rows after end of table entries.
|
|
||||||
backgroundColor: tableView.isLightColorScheme ? hifi.colors.tableBackgroundLight : hifi.colors.tableBackgroundDark
|
|
||||||
alternateBackgroundColor: tableView.isLightColorScheme ? hifi.colors.tableRowLightOdd : hifi.colors.tableRowDarkOdd
|
|
||||||
padding.top: headerVisible ? hifi.dimensions.tableHeaderHeight: 0
|
|
||||||
}
|
|
||||||
|
|
||||||
rowDelegate: Rectangle {
|
|
||||||
height: (styleData.selected && expandSelectedRow ? 1.8 : 1) * hifi.dimensions.tableRowHeight
|
|
||||||
color: styleData.selected
|
|
||||||
? hifi.colors.primaryHighlight
|
|
||||||
: tableView.isLightColorScheme
|
|
||||||
? (styleData.alternate ? hifi.colors.tableRowLightEven : hifi.colors.tableRowLightOdd)
|
|
||||||
: (styleData.alternate ? hifi.colors.tableRowDarkEven : hifi.colors.tableRowDarkOdd)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,575 +0,0 @@
|
||||||
//
|
|
||||||
// Desktop.qml
|
|
||||||
//
|
|
||||||
// Created by Bradley Austin Davis on 15 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.7
|
|
||||||
import QtQuick.Controls 1.4
|
|
||||||
|
|
||||||
import "../dialogs"
|
|
||||||
import "../js/Utils.js" as Utils
|
|
||||||
|
|
||||||
// This is our primary 'desktop' object to which all VR dialogs and windows are childed.
|
|
||||||
FocusScope {
|
|
||||||
id: desktop
|
|
||||||
objectName: "desktop"
|
|
||||||
anchors.fill: parent
|
|
||||||
|
|
||||||
readonly property int invalid_position: -9999;
|
|
||||||
property rect recommendedRect: Qt.rect(0,0,0,0);
|
|
||||||
property var expectedChildren;
|
|
||||||
property bool repositionLocked: true
|
|
||||||
property bool hmdHandMouseActive: false
|
|
||||||
|
|
||||||
onRepositionLockedChanged: {
|
|
||||||
if (!repositionLocked) {
|
|
||||||
d.handleSizeChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
onHeightChanged: d.handleSizeChanged();
|
|
||||||
|
|
||||||
onWidthChanged: d.handleSizeChanged();
|
|
||||||
|
|
||||||
// Controls and windows can trigger this signal to ensure the desktop becomes visible
|
|
||||||
// when they're opened.
|
|
||||||
signal showDesktop();
|
|
||||||
|
|
||||||
// This is for JS/QML communication, which is unused in the Desktop,
|
|
||||||
// but not having this here results in spurious warnings about a
|
|
||||||
// missing signal
|
|
||||||
signal sendToScript(var message);
|
|
||||||
|
|
||||||
// Allows QML/JS to find the desktop through the parent chain
|
|
||||||
property bool desktopRoot: true
|
|
||||||
|
|
||||||
// The VR version of the primary menu
|
|
||||||
property var rootMenu: Menu {
|
|
||||||
id: rootMenuId
|
|
||||||
objectName: "rootMenu"
|
|
||||||
|
|
||||||
property var exclusionGroups: ({});
|
|
||||||
property Component exclusiveGroupMaker: Component {
|
|
||||||
ExclusiveGroup {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function addExclusionGroup(qmlAction, exclusionGroup) {
|
|
||||||
|
|
||||||
var exclusionGroupId = exclusionGroup.toString();
|
|
||||||
if(!exclusionGroups[exclusionGroupId]) {
|
|
||||||
exclusionGroups[exclusionGroupId] = exclusiveGroupMaker.createObject(rootMenuId);
|
|
||||||
}
|
|
||||||
|
|
||||||
qmlAction.exclusiveGroup = exclusionGroups[exclusionGroupId]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// FIXME: Alpha gradients display as fuschia under QtQuick 2.5 on OSX/AMD
|
|
||||||
// because shaders are 4.2, and do not include #version declarations.
|
|
||||||
property bool gradientsSupported: Qt.platform.os != "osx" && !~GL.vendor.indexOf("ATI")
|
|
||||||
|
|
||||||
readonly property alias zLevels: zLevels
|
|
||||||
QtObject {
|
|
||||||
id: zLevels;
|
|
||||||
readonly property real normal: 1 // make windows always appear higher than QML overlays and other non-window controls.
|
|
||||||
readonly property real top: 2000
|
|
||||||
readonly property real modal: 4000
|
|
||||||
readonly property real menu: 8000
|
|
||||||
}
|
|
||||||
|
|
||||||
QtObject {
|
|
||||||
id: d
|
|
||||||
|
|
||||||
function handleSizeChanged() {
|
|
||||||
if (desktop.repositionLocked) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
var oldRecommendedRect = recommendedRect;
|
|
||||||
var newRecommendedRectJS = (typeof Controller === "undefined") ? Qt.rect(0,0,0,0) : Controller.getRecommendedHUDRect();
|
|
||||||
var newRecommendedRect = Qt.rect(newRecommendedRectJS.x, newRecommendedRectJS.y,
|
|
||||||
newRecommendedRectJS.width,
|
|
||||||
newRecommendedRectJS.height);
|
|
||||||
|
|
||||||
var oldChildren = expectedChildren;
|
|
||||||
var newChildren = d.getRepositionChildren();
|
|
||||||
if (oldRecommendedRect != Qt.rect(0,0,0,0) && oldRecommendedRect != Qt.rect(0,0,1,1)
|
|
||||||
&& (oldRecommendedRect != newRecommendedRect
|
|
||||||
|| oldChildren != newChildren)
|
|
||||||
) {
|
|
||||||
expectedChildren = newChildren;
|
|
||||||
d.repositionAll();
|
|
||||||
}
|
|
||||||
recommendedRect = newRecommendedRect;
|
|
||||||
}
|
|
||||||
|
|
||||||
function findChild(item, name) {
|
|
||||||
for (var i = 0; i < item.children.length; ++i) {
|
|
||||||
if (item.children[i].objectName === name) {
|
|
||||||
return item.children[i];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
function findParentMatching(item, predicate) {
|
|
||||||
while (item) {
|
|
||||||
if (predicate(item)) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
item = item.parent;
|
|
||||||
}
|
|
||||||
return item;
|
|
||||||
}
|
|
||||||
|
|
||||||
function findMatchingChildren(item, predicate) {
|
|
||||||
var results = [];
|
|
||||||
for (var i in item.children) {
|
|
||||||
var child = item.children[i];
|
|
||||||
if (predicate(child)) {
|
|
||||||
results.push(child);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return results;
|
|
||||||
}
|
|
||||||
|
|
||||||
function isTopLevelWindow(item) {
|
|
||||||
return item.topLevelWindow;
|
|
||||||
}
|
|
||||||
|
|
||||||
function isAlwaysOnTopWindow(window) {
|
|
||||||
return window.alwaysOnTop;
|
|
||||||
}
|
|
||||||
|
|
||||||
function isModalWindow(window) {
|
|
||||||
return window.modality !== Qt.NonModal;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getTopLevelWindows(predicate) {
|
|
||||||
return findMatchingChildren(desktop, function(child) {
|
|
||||||
return (isTopLevelWindow(child) && (!predicate || predicate(child)));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function getDesktopWindow(item) {
|
|
||||||
return findParentMatching(item, isTopLevelWindow)
|
|
||||||
}
|
|
||||||
|
|
||||||
function fixupZOrder(windows, basis, topWindow) {
|
|
||||||
windows.sort(function(a, b){ return a.z - b.z; });
|
|
||||||
|
|
||||||
if ((topWindow.z >= basis) && (windows[windows.length - 1] === topWindow)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var lastZ = -1;
|
|
||||||
var lastTargetZ = basis - 1;
|
|
||||||
for (var i = 0; i < windows.length; ++i) {
|
|
||||||
var window = windows[i];
|
|
||||||
if (!window.visible) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if (topWindow && (topWindow === window)) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if (window.z > lastZ) {
|
|
||||||
lastZ = window.z;
|
|
||||||
++lastTargetZ;
|
|
||||||
}
|
|
||||||
if (DebugQML) {
|
|
||||||
console.log("Assigning z order " + lastTargetZ + " to " + window)
|
|
||||||
}
|
|
||||||
|
|
||||||
window.z = lastTargetZ;
|
|
||||||
}
|
|
||||||
if (topWindow) {
|
|
||||||
++lastTargetZ;
|
|
||||||
if (DebugQML) {
|
|
||||||
console.log("Assigning z order " + lastTargetZ + " to " + topWindow)
|
|
||||||
}
|
|
||||||
topWindow.z = lastTargetZ;
|
|
||||||
}
|
|
||||||
|
|
||||||
return lastTargetZ;
|
|
||||||
}
|
|
||||||
|
|
||||||
function raiseWindow(targetWindow) {
|
|
||||||
var predicate;
|
|
||||||
var zBasis;
|
|
||||||
if (isModalWindow(targetWindow)) {
|
|
||||||
predicate = isModalWindow;
|
|
||||||
zBasis = zLevels.modal
|
|
||||||
} else if (isAlwaysOnTopWindow(targetWindow)) {
|
|
||||||
predicate = function(window) {
|
|
||||||
return (isAlwaysOnTopWindow(window) && !isModalWindow(window));
|
|
||||||
}
|
|
||||||
zBasis = zLevels.top
|
|
||||||
} else {
|
|
||||||
predicate = function(window) {
|
|
||||||
return (!isAlwaysOnTopWindow(window) && !isModalWindow(window));
|
|
||||||
}
|
|
||||||
zBasis = zLevels.normal
|
|
||||||
}
|
|
||||||
|
|
||||||
var windows = getTopLevelWindows(predicate);
|
|
||||||
fixupZOrder(windows, zBasis, targetWindow);
|
|
||||||
}
|
|
||||||
|
|
||||||
Component.onCompleted: {
|
|
||||||
//offscreenWindow.activeFocusItemChanged.connect(onWindowFocusChanged);
|
|
||||||
focusHack.start();
|
|
||||||
}
|
|
||||||
|
|
||||||
function onWindowFocusChanged() {
|
|
||||||
//console.log("Focus item is " + offscreenWindow.activeFocusItem);
|
|
||||||
|
|
||||||
// FIXME this needs more testing before it can go into production
|
|
||||||
// and I already cant produce any way to have a modal dialog lose focus
|
|
||||||
// to a non-modal one.
|
|
||||||
/*
|
|
||||||
var focusedWindow = getDesktopWindow(offscreenWindow.activeFocusItem);
|
|
||||||
|
|
||||||
if (isModalWindow(focusedWindow)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// new focused window is not modal... check if there are any modal windows
|
|
||||||
var windows = getTopLevelWindows(isModalWindow);
|
|
||||||
if (0 === windows.length) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// There are modal windows present, force focus back to the top-most modal window
|
|
||||||
windows.sort(function(a, b){ return a.z - b.z; });
|
|
||||||
windows[windows.length - 1].focus = true;
|
|
||||||
*/
|
|
||||||
|
|
||||||
// var focusedItem = offscreenWindow.activeFocusItem ;
|
|
||||||
// if (DebugQML && focusedItem) {
|
|
||||||
// var rect = desktop.mapFromItem(focusedItem, 0, 0, focusedItem.width, focusedItem.height);
|
|
||||||
// focusDebugger.x = rect.x;
|
|
||||||
// focusDebugger.y = rect.y;
|
|
||||||
// focusDebugger.width = rect.width
|
|
||||||
// focusDebugger.height = rect.height
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
|
|
||||||
function getRepositionChildren(predicate) {
|
|
||||||
return findMatchingChildren(desktop, function(child) {
|
|
||||||
return (child.shouldReposition === true && (!predicate || predicate(child)));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function repositionAll() {
|
|
||||||
if (desktop.repositionLocked) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var oldRecommendedRect = recommendedRect;
|
|
||||||
var oldRecommendedDimmensions = { x: oldRecommendedRect.width, y: oldRecommendedRect.height };
|
|
||||||
var newRecommendedRect = Controller.getRecommendedHUDRect();
|
|
||||||
var newRecommendedDimmensions = { x: newRecommendedRect.width, y: newRecommendedRect.height };
|
|
||||||
var windows = d.getTopLevelWindows();
|
|
||||||
for (var i = 0; i < windows.length; ++i) {
|
|
||||||
var targetWindow = windows[i];
|
|
||||||
if (targetWindow.visible) {
|
|
||||||
repositionWindow(targetWindow, true, oldRecommendedRect, oldRecommendedDimmensions, newRecommendedRect, newRecommendedDimmensions);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// also reposition the other children that aren't top level windows but want to be repositioned
|
|
||||||
var otherChildren = d.getRepositionChildren();
|
|
||||||
for (var i = 0; i < otherChildren.length; ++i) {
|
|
||||||
var child = otherChildren[i];
|
|
||||||
repositionWindow(child, true, oldRecommendedRect, oldRecommendedDimmensions, newRecommendedRect, newRecommendedDimmensions);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
property bool pinned: false
|
|
||||||
property var hiddenChildren: []
|
|
||||||
|
|
||||||
function togglePinned() {
|
|
||||||
pinned = !pinned
|
|
||||||
}
|
|
||||||
|
|
||||||
function isPointOnWindow(point) {
|
|
||||||
for (var i = 0; i < desktop.visibleChildren.length; i++) {
|
|
||||||
var child = desktop.visibleChildren[i];
|
|
||||||
if (child.hasOwnProperty("modality")) {
|
|
||||||
var mappedPoint = mapToItem(child, point.x, point.y);
|
|
||||||
if (child.hasOwnProperty("frame")) {
|
|
||||||
var outLine = child.frame.children[2];
|
|
||||||
var framePoint = outLine.mapFromGlobal(point.x, point.y);
|
|
||||||
if (outLine.contains(framePoint)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (child.contains(mappedPoint)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
function setPinned(newPinned) {
|
|
||||||
pinned = newPinned
|
|
||||||
}
|
|
||||||
|
|
||||||
property real unpinnedAlpha: 1.0;
|
|
||||||
|
|
||||||
Behavior on unpinnedAlpha {
|
|
||||||
NumberAnimation {
|
|
||||||
easing.type: Easing.Linear;
|
|
||||||
duration: 300
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
state: "NORMAL"
|
|
||||||
states: [
|
|
||||||
State {
|
|
||||||
name: "NORMAL"
|
|
||||||
PropertyChanges { target: desktop; unpinnedAlpha: 1.0 }
|
|
||||||
},
|
|
||||||
State {
|
|
||||||
name: "PINNED"
|
|
||||||
PropertyChanges { target: desktop; unpinnedAlpha: 0.0 }
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
transitions: [
|
|
||||||
Transition {
|
|
||||||
NumberAnimation { properties: "unpinnedAlpha"; duration: 300 }
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
onPinnedChanged: {
|
|
||||||
if (pinned) {
|
|
||||||
d.raiseWindow(desktop);
|
|
||||||
desktop.focus = true;
|
|
||||||
desktop.forceActiveFocus();
|
|
||||||
|
|
||||||
// recalculate our non-pinned children
|
|
||||||
hiddenChildren = d.findMatchingChildren(desktop, function(child){
|
|
||||||
return !d.isTopLevelWindow(child) && child.visible && !child.pinned;
|
|
||||||
});
|
|
||||||
|
|
||||||
hiddenChildren.forEach(function(child){
|
|
||||||
child.opacity = Qt.binding(function(){ return desktop.unpinnedAlpha });
|
|
||||||
});
|
|
||||||
}
|
|
||||||
state = pinned ? "PINNED" : "NORMAL"
|
|
||||||
}
|
|
||||||
|
|
||||||
onShowDesktop: pinned = false
|
|
||||||
|
|
||||||
function raise(item) {
|
|
||||||
var targetWindow = d.getDesktopWindow(item);
|
|
||||||
if (!targetWindow) {
|
|
||||||
console.warn("Could not find top level window for " + item);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fix up the Z-order (takes into account if this is a modal window)
|
|
||||||
d.raiseWindow(targetWindow);
|
|
||||||
var setFocus = true;
|
|
||||||
if (!d.isModalWindow(targetWindow)) {
|
|
||||||
var modalWindows = d.getTopLevelWindows(d.isModalWindow);
|
|
||||||
if (modalWindows.length) {
|
|
||||||
setFocus = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (setFocus) {
|
|
||||||
targetWindow.focus = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
showDesktop();
|
|
||||||
}
|
|
||||||
|
|
||||||
function ensureTitleBarVisible(targetWindow) {
|
|
||||||
// Reposition window to ensure that title bar is vertically inside window.
|
|
||||||
if (targetWindow.frame && targetWindow.frame.decoration) {
|
|
||||||
var topMargin = -targetWindow.frame.decoration.anchors.topMargin; // Frame's topMargin is a negative value.
|
|
||||||
targetWindow.y = Math.max(targetWindow.y, topMargin);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function centerOnVisible(item) {
|
|
||||||
var targetWindow = d.getDesktopWindow(item);
|
|
||||||
if (!targetWindow) {
|
|
||||||
console.warn("Could not find top level window for " + item);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof Controller === "undefined") {
|
|
||||||
console.warn("Controller not yet available... can't center");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var newRecommendedRectJS = (typeof Controller === "undefined") ? Qt.rect(0,0,0,0) : Controller.getRecommendedHUDRect();
|
|
||||||
var newRecommendedRect = Qt.rect(newRecommendedRectJS.x, newRecommendedRectJS.y,
|
|
||||||
newRecommendedRectJS.width,
|
|
||||||
newRecommendedRectJS.height);
|
|
||||||
var newRecommendedDimmensions = { x: newRecommendedRect.width, y: newRecommendedRect.height };
|
|
||||||
var newX = newRecommendedRect.x + ((newRecommendedRect.width - targetWindow.width) / 2);
|
|
||||||
var newY = newRecommendedRect.y + ((newRecommendedRect.height - targetWindow.height) / 2);
|
|
||||||
targetWindow.x = newX;
|
|
||||||
targetWindow.y = newY;
|
|
||||||
|
|
||||||
ensureTitleBarVisible(targetWindow);
|
|
||||||
|
|
||||||
// If we've noticed that our recommended desktop rect has changed, record that change here.
|
|
||||||
if (recommendedRect != newRecommendedRect) {
|
|
||||||
recommendedRect = newRecommendedRect;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function repositionOnVisible(item) {
|
|
||||||
var targetWindow = d.getDesktopWindow(item);
|
|
||||||
if (!targetWindow) {
|
|
||||||
console.warn("Could not find top level window for " + item);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof Controller === "undefined") {
|
|
||||||
console.warn("Controller not yet available... can't reposition targetWindow:" + targetWindow);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var oldRecommendedRect = recommendedRect;
|
|
||||||
var oldRecommendedDimmensions = { x: oldRecommendedRect.width, y: oldRecommendedRect.height };
|
|
||||||
var newRecommendedRect = Controller.getRecommendedHUDRect();
|
|
||||||
var newRecommendedDimmensions = { x: newRecommendedRect.width, y: newRecommendedRect.height };
|
|
||||||
repositionWindow(targetWindow, false, oldRecommendedRect, oldRecommendedDimmensions, newRecommendedRect, newRecommendedDimmensions);
|
|
||||||
}
|
|
||||||
|
|
||||||
function repositionWindow(targetWindow, forceReposition,
|
|
||||||
oldRecommendedRect, oldRecommendedDimmensions, newRecommendedRect, newRecommendedDimmensions) {
|
|
||||||
|
|
||||||
if (desktop.width === 0 || desktop.height === 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!targetWindow) {
|
|
||||||
console.warn("Could not find top level window for " + item);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var recommended = Controller.getRecommendedHUDRect();
|
|
||||||
var maxX = recommended.x + recommended.width;
|
|
||||||
var maxY = recommended.y + recommended.height;
|
|
||||||
var newPosition = Qt.vector2d(targetWindow.x, targetWindow.y);
|
|
||||||
|
|
||||||
// if we asked to force reposition, or if the window is completely outside of the recommended rectangle, reposition it
|
|
||||||
if (forceReposition || (targetWindow.x > maxX || (targetWindow.x + targetWindow.width) < recommended.x) ||
|
|
||||||
(targetWindow.y > maxY || (targetWindow.y + targetWindow.height) < recommended.y)) {
|
|
||||||
newPosition.x = -1
|
|
||||||
newPosition.y = -1
|
|
||||||
}
|
|
||||||
|
|
||||||
if (newPosition.x === -1 && newPosition.y === -1) {
|
|
||||||
var originRelativeX = (targetWindow.x - oldRecommendedRect.x);
|
|
||||||
var originRelativeY = (targetWindow.y - oldRecommendedRect.y);
|
|
||||||
if (isNaN(originRelativeX)) {
|
|
||||||
originRelativeX = 0;
|
|
||||||
}
|
|
||||||
if (isNaN(originRelativeY)) {
|
|
||||||
originRelativeY = 0;
|
|
||||||
}
|
|
||||||
var fractionX = Utils.clamp(originRelativeX / oldRecommendedDimmensions.x, 0, 1);
|
|
||||||
var fractionY = Utils.clamp(originRelativeY / oldRecommendedDimmensions.y, 0, 1);
|
|
||||||
var newX = (fractionX * newRecommendedDimmensions.x) + newRecommendedRect.x;
|
|
||||||
var newY = (fractionY * newRecommendedDimmensions.y) + newRecommendedRect.y;
|
|
||||||
newPosition = Qt.vector2d(newX, newY);
|
|
||||||
}
|
|
||||||
targetWindow.x = newPosition.x;
|
|
||||||
targetWindow.y = newPosition.y;
|
|
||||||
|
|
||||||
ensureTitleBarVisible(targetWindow);
|
|
||||||
}
|
|
||||||
|
|
||||||
Component { id: messageDialogBuilder; MessageDialog { } }
|
|
||||||
function messageBox(properties) {
|
|
||||||
return messageDialogBuilder.createObject(desktop, properties);
|
|
||||||
}
|
|
||||||
|
|
||||||
Component { id: inputDialogBuilder; QueryDialog { } }
|
|
||||||
function inputDialog(properties) {
|
|
||||||
return inputDialogBuilder.createObject(desktop, properties);
|
|
||||||
}
|
|
||||||
|
|
||||||
Component { id: customInputDialogBuilder; CustomQueryDialog { } }
|
|
||||||
function customInputDialog(properties) {
|
|
||||||
return customInputDialogBuilder.createObject(desktop, properties);
|
|
||||||
}
|
|
||||||
|
|
||||||
Component { id: fileDialogBuilder; FileDialog { } }
|
|
||||||
function fileDialog(properties) {
|
|
||||||
return fileDialogBuilder.createObject(desktop, properties);
|
|
||||||
}
|
|
||||||
|
|
||||||
Component { id: assetDialogBuilder; AssetDialog { } }
|
|
||||||
function assetDialog(properties) {
|
|
||||||
return assetDialogBuilder.createObject(desktop, properties);
|
|
||||||
}
|
|
||||||
|
|
||||||
function unfocusWindows() {
|
|
||||||
// First find the active focus item, and unfocus it, all the way
|
|
||||||
// up the parent chain to the window
|
|
||||||
var currentFocus = offscreenWindow.activeFocusItem;
|
|
||||||
var targetWindow = d.getDesktopWindow(currentFocus);
|
|
||||||
while (currentFocus) {
|
|
||||||
if (currentFocus === targetWindow) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
currentFocus.focus = false;
|
|
||||||
currentFocus = currentFocus.parent;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Unfocus all windows
|
|
||||||
var windows = d.getTopLevelWindows();
|
|
||||||
for (var i = 0; i < windows.length; ++i) {
|
|
||||||
windows[i].focus = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// For the desktop to have active focus
|
|
||||||
desktop.focus = true;
|
|
||||||
desktop.forceActiveFocus();
|
|
||||||
}
|
|
||||||
|
|
||||||
function openBrowserWindow(request, profile) {
|
|
||||||
var component = Qt.createComponent("../Browser.qml");
|
|
||||||
var newWindow = component.createObject(desktop);
|
|
||||||
newWindow.webView.profile = profile;
|
|
||||||
request.openIn(newWindow.webView);
|
|
||||||
}
|
|
||||||
|
|
||||||
FocusHack { id: focusHack; }
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
id: focusDebugger;
|
|
||||||
objectName: "focusDebugger"
|
|
||||||
z: 9999; visible: false; color: "red"
|
|
||||||
ColorAnimation on color { from: "#7fffff00"; to: "#7f0000ff"; duration: 1000; loops: 9999 }
|
|
||||||
}
|
|
||||||
|
|
||||||
Action {
|
|
||||||
text: "Toggle Focus Debugger"
|
|
||||||
shortcut: "Ctrl+Shift+F"
|
|
||||||
enabled: DebugQML
|
|
||||||
onTriggered: focusDebugger.visible = !focusDebugger.visible
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,338 +0,0 @@
|
||||||
//
|
|
||||||
// CustomQueryDialog.qml
|
|
||||||
//
|
|
||||||
// Created by Zander Otavka on 7/14/16
|
|
||||||
// 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.7;
|
|
||||||
import QtQuick.Dialogs 1.2 as OriginalDialogs;
|
|
||||||
import QtQuick.Controls 1.4;
|
|
||||||
|
|
||||||
import "../controls-uit";
|
|
||||||
import "../styles-uit";
|
|
||||||
import "../windows";
|
|
||||||
|
|
||||||
ModalWindow {
|
|
||||||
id: root;
|
|
||||||
HifiConstants { id: hifi; }
|
|
||||||
implicitWidth: 640;
|
|
||||||
implicitHeight: 320;
|
|
||||||
visible: true;
|
|
||||||
keyboardOverride: true // Disable ModalWindow's keyboard.
|
|
||||||
|
|
||||||
signal selected(var result);
|
|
||||||
signal canceled();
|
|
||||||
|
|
||||||
property int icon: hifi.icons.none;
|
|
||||||
property string iconText: "";
|
|
||||||
property int iconSize: 35;
|
|
||||||
onIconChanged: updateIcon();
|
|
||||||
|
|
||||||
property var textInput;
|
|
||||||
property var comboBox;
|
|
||||||
property var checkBox;
|
|
||||||
onTextInputChanged: {
|
|
||||||
if (textInput && textInput.text !== undefined) {
|
|
||||||
textField.text = textInput.text;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
onComboBoxChanged: {
|
|
||||||
if (comboBox && comboBox.index !== undefined) {
|
|
||||||
comboBoxField.currentIndex = comboBox.index;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
onCheckBoxChanged: {
|
|
||||||
if (checkBox && checkBox.checked !== undefined) {
|
|
||||||
checkBoxField.checked = checkBox.checked;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
property bool keyboardEnabled: false
|
|
||||||
property bool keyboardRaised: false
|
|
||||||
property bool punctuationMode: false
|
|
||||||
onKeyboardRaisedChanged: d.resize();
|
|
||||||
|
|
||||||
property var warning: "";
|
|
||||||
property var result;
|
|
||||||
|
|
||||||
property var implicitCheckState: null;
|
|
||||||
|
|
||||||
property int titleWidth: 0;
|
|
||||||
onTitleWidthChanged: d.resize();
|
|
||||||
|
|
||||||
function updateIcon() {
|
|
||||||
if (!root) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
iconText = hifi.glyphForIcon(root.icon);
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateCheckbox() {
|
|
||||||
if (checkBox.disableForItems) {
|
|
||||||
var currentItemInDisableList = false;
|
|
||||||
for (var i in checkBox.disableForItems) {
|
|
||||||
if (comboBoxField.currentIndex === checkBox.disableForItems[i]) {
|
|
||||||
currentItemInDisableList = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (currentItemInDisableList) {
|
|
||||||
checkBoxField.enabled = false;
|
|
||||||
if (checkBox.checkStateOnDisable !== null && checkBox.checkStateOnDisable !== undefined) {
|
|
||||||
root.implicitCheckState = checkBoxField.checked;
|
|
||||||
checkBoxField.checked = checkBox.checkStateOnDisable;
|
|
||||||
}
|
|
||||||
root.warning = checkBox.warningOnDisable;
|
|
||||||
} else {
|
|
||||||
checkBoxField.enabled = true;
|
|
||||||
if (root.implicitCheckState !== null) {
|
|
||||||
checkBoxField.checked = root.implicitCheckState;
|
|
||||||
root.implicitCheckState = null;
|
|
||||||
}
|
|
||||||
root.warning = "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Item {
|
|
||||||
clip: true;
|
|
||||||
width: pane.width;
|
|
||||||
height: pane.height;
|
|
||||||
anchors.margins: 0;
|
|
||||||
|
|
||||||
QtObject {
|
|
||||||
id: d;
|
|
||||||
readonly property int minWidth: 480
|
|
||||||
readonly property int maxWdith: 1280
|
|
||||||
readonly property int minHeight: 120
|
|
||||||
readonly property int maxHeight: 720
|
|
||||||
|
|
||||||
function resize() {
|
|
||||||
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) +
|
|
||||||
((keyboardEnabled && keyboardRaised) ? (keyboard.raisedHeight + 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Item {
|
|
||||||
anchors {
|
|
||||||
top: parent.top;
|
|
||||||
bottom: extraInputs.visible ? extraInputs.top : buttons.top;
|
|
||||||
left: parent.left;
|
|
||||||
right: parent.right;
|
|
||||||
margins: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// FIXME make a text field type that can be bound to a history for autocompletion
|
|
||||||
TextField {
|
|
||||||
id: textField;
|
|
||||||
label: root.textInput.label;
|
|
||||||
focus: root.textInput ? true : false;
|
|
||||||
visible: root.textInput ? true : false;
|
|
||||||
anchors {
|
|
||||||
left: parent.left;
|
|
||||||
right: parent.right;
|
|
||||||
bottom: keyboard.top;
|
|
||||||
bottomMargin: hifi.dimensions.contentSpacing.y;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Keyboard {
|
|
||||||
id: keyboard
|
|
||||||
raised: keyboardEnabled && keyboardRaised
|
|
||||||
numeric: punctuationMode
|
|
||||||
anchors {
|
|
||||||
left: parent.left
|
|
||||||
right: parent.right
|
|
||||||
bottom: parent.bottom
|
|
||||||
bottomMargin: raised ? hifi.dimensions.contentSpacing.y : 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Item {
|
|
||||||
id: extraInputs;
|
|
||||||
visible: Boolean(root.checkBox || root.comboBox);
|
|
||||||
anchors {
|
|
||||||
left: parent.left;
|
|
||||||
right: parent.right;
|
|
||||||
bottom: buttons.top;
|
|
||||||
bottomMargin: hifi.dimensions.contentSpacing.y;
|
|
||||||
}
|
|
||||||
height: comboBoxField.controlHeight;
|
|
||||||
onHeightChanged: d.resize();
|
|
||||||
onWidthChanged: d.resize();
|
|
||||||
|
|
||||||
CheckBox {
|
|
||||||
id: checkBoxField;
|
|
||||||
text: root.checkBox.label;
|
|
||||||
focus: Boolean(root.checkBox);
|
|
||||||
visible: Boolean(root.checkBox);
|
|
||||||
anchors {
|
|
||||||
left: parent.left;
|
|
||||||
bottom: parent.bottom;
|
|
||||||
leftMargin: 6; // Magic number to align with warning icon
|
|
||||||
bottomMargin: 6;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ComboBox {
|
|
||||||
id: comboBoxField;
|
|
||||||
label: root.comboBox.label;
|
|
||||||
focus: Boolean(root.comboBox);
|
|
||||||
visible: Boolean(root.comboBox);
|
|
||||||
Binding on x {
|
|
||||||
when: comboBoxField.visible
|
|
||||||
value: !checkBoxField.visible ? buttons.x : acceptButton.x
|
|
||||||
}
|
|
||||||
|
|
||||||
Binding on width {
|
|
||||||
when: comboBoxField.visible
|
|
||||||
value: !checkBoxField.visible ? buttons.width : buttons.width - acceptButton.x
|
|
||||||
}
|
|
||||||
anchors {
|
|
||||||
right: parent.right;
|
|
||||||
bottom: parent.bottom;
|
|
||||||
}
|
|
||||||
model: root.comboBox ? root.comboBox.items : [];
|
|
||||||
onAccepted: {
|
|
||||||
updateCheckbox();
|
|
||||||
focus = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Row {
|
|
||||||
id: buttons;
|
|
||||||
focus: true;
|
|
||||||
spacing: hifi.dimensions.contentSpacing.x;
|
|
||||||
layoutDirection: Qt.RightToLeft;
|
|
||||||
onHeightChanged: d.resize();
|
|
||||||
onWidthChanged: {
|
|
||||||
d.resize();
|
|
||||||
resizeWarningText();
|
|
||||||
}
|
|
||||||
|
|
||||||
anchors {
|
|
||||||
bottom: parent.bottom;
|
|
||||||
left: parent.left;
|
|
||||||
right: parent.right;
|
|
||||||
bottomMargin: hifi.dimensions.contentSpacing.y;
|
|
||||||
}
|
|
||||||
|
|
||||||
function resizeWarningText() {
|
|
||||||
var rowWidth = buttons.width;
|
|
||||||
var buttonsWidth = acceptButton.width + cancelButton.width + hifi.dimensions.contentSpacing.x * 2;
|
|
||||||
var warningIconWidth = warningIcon.width + hifi.dimensions.contentSpacing.x;
|
|
||||||
warningText.width = rowWidth - buttonsWidth - warningIconWidth;
|
|
||||||
}
|
|
||||||
|
|
||||||
Button {
|
|
||||||
id: cancelButton;
|
|
||||||
action: cancelAction;
|
|
||||||
}
|
|
||||||
|
|
||||||
Button {
|
|
||||||
id: acceptButton;
|
|
||||||
action: acceptAction;
|
|
||||||
}
|
|
||||||
|
|
||||||
Text {
|
|
||||||
id: warningText;
|
|
||||||
visible: Boolean(root.warning);
|
|
||||||
text: root.warning;
|
|
||||||
wrapMode: Text.WordWrap;
|
|
||||||
font.italic: true;
|
|
||||||
maximumLineCount: 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
HiFiGlyphs {
|
|
||||||
id: warningIcon;
|
|
||||||
visible: Boolean(root.warning);
|
|
||||||
text: hifi.glyphs.alert;
|
|
||||||
size: hifi.dimensions.controlLineHeight;
|
|
||||||
width: 20 // Line up with checkbox.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Action {
|
|
||||||
id: cancelAction;
|
|
||||||
text: qsTr("Cancel");
|
|
||||||
shortcut: "Esc";
|
|
||||||
onTriggered: {
|
|
||||||
root.result = null;
|
|
||||||
root.canceled();
|
|
||||||
// FIXME we are leaking memory to avoid a crash
|
|
||||||
// root.destroy();
|
|
||||||
|
|
||||||
root.disableFade = true
|
|
||||||
visible = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Action {
|
|
||||||
id: acceptAction;
|
|
||||||
text: qsTr("Add");
|
|
||||||
shortcut: "Return"
|
|
||||||
onTriggered: {
|
|
||||||
var result = {};
|
|
||||||
if (textInput) {
|
|
||||||
result.textInput = textField.text;
|
|
||||||
}
|
|
||||||
if (comboBox) {
|
|
||||||
result.comboBox = comboBoxField.currentIndex;
|
|
||||||
result.comboBoxText = comboBoxField.currentText;
|
|
||||||
}
|
|
||||||
if (checkBox) {
|
|
||||||
result.checkBox = checkBoxField.enabled ? checkBoxField.checked : null;
|
|
||||||
}
|
|
||||||
root.result = JSON.stringify(result);
|
|
||||||
root.selected(root.result);
|
|
||||||
// FIXME we are leaking memory to avoid a crash
|
|
||||||
// root.destroy();
|
|
||||||
|
|
||||||
root.disableFade = true
|
|
||||||
visible = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Keys.onPressed: {
|
|
||||||
if (!visible) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (event.key) {
|
|
||||||
case Qt.Key_Escape:
|
|
||||||
case Qt.Key_Back:
|
|
||||||
cancelAction.trigger();
|
|
||||||
event.accepted = true;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case Qt.Key_Return:
|
|
||||||
case Qt.Key_Enter:
|
|
||||||
acceptAction.trigger();
|
|
||||||
event.accepted = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Component.onCompleted: {
|
|
||||||
keyboardEnabled = HMD.active;
|
|
||||||
updateIcon();
|
|
||||||
updateCheckbox();
|
|
||||||
d.resize();
|
|
||||||
textField.forceActiveFocus();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,840 +0,0 @@
|
||||||
//
|
|
||||||
// FileDialog.qml
|
|
||||||
//
|
|
||||||
// Created by Bradley Austin Davis on 14 Jan 2016
|
|
||||||
// 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.7
|
|
||||||
import Qt.labs.folderlistmodel 2.1
|
|
||||||
import Qt.labs.settings 1.0
|
|
||||||
import QtQuick.Dialogs 1.2 as OriginalDialogs
|
|
||||||
import QtQuick.Controls 1.4
|
|
||||||
|
|
||||||
import ".."
|
|
||||||
import "../controls-uit"
|
|
||||||
import "../styles-uit"
|
|
||||||
import "../windows"
|
|
||||||
|
|
||||||
import "fileDialog"
|
|
||||||
|
|
||||||
//FIXME implement shortcuts for favorite location
|
|
||||||
ModalWindow {
|
|
||||||
id: root
|
|
||||||
resizable: true
|
|
||||||
implicitWidth: 480
|
|
||||||
implicitHeight: 360 + (fileDialogItem.keyboardEnabled && fileDialogItem.keyboardRaised ? keyboard.raisedHeight + hifi.dimensions.contentSpacing.y : 0)
|
|
||||||
|
|
||||||
minSize: Qt.vector2d(360, 240)
|
|
||||||
draggable: true
|
|
||||||
|
|
||||||
HifiConstants { id: hifi }
|
|
||||||
|
|
||||||
property var filesModel: ListModel { }
|
|
||||||
|
|
||||||
Settings {
|
|
||||||
category: "FileDialog"
|
|
||||||
property alias width: root.width
|
|
||||||
property alias height: root.height
|
|
||||||
property alias x: root.x
|
|
||||||
property alias y: root.y
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Set from OffscreenUi::getOpenFile()
|
|
||||||
property alias caption: root.title;
|
|
||||||
// Set from OffscreenUi::getOpenFile()
|
|
||||||
property alias dir: fileTableModel.folder;
|
|
||||||
// Set from OffscreenUi::getOpenFile()
|
|
||||||
property alias filter: selectionType.filtersString;
|
|
||||||
// Set from OffscreenUi::getOpenFile()
|
|
||||||
property int options; // <-- FIXME unused
|
|
||||||
|
|
||||||
property string iconText: root.title !== "" ? hifi.glyphs.scriptUpload : ""
|
|
||||||
property int iconSize: 40
|
|
||||||
|
|
||||||
property bool selectDirectory: false;
|
|
||||||
property bool showHidden: true;
|
|
||||||
// FIXME implement
|
|
||||||
property bool multiSelect: false;
|
|
||||||
property bool saveDialog: false;
|
|
||||||
property var helper: fileDialogHelper
|
|
||||||
property alias model: fileTableView.model
|
|
||||||
property var drives: helper.drives()
|
|
||||||
|
|
||||||
property int titleWidth: 0
|
|
||||||
|
|
||||||
signal selectedFile(var file);
|
|
||||||
signal canceled();
|
|
||||||
signal selected(int button);
|
|
||||||
function click(button) {
|
|
||||||
clickedButton = button;
|
|
||||||
selected(button);
|
|
||||||
destroy();
|
|
||||||
}
|
|
||||||
|
|
||||||
property int clickedButton: OriginalDialogs.StandardButton.NoButton;
|
|
||||||
|
|
||||||
Component.onCompleted: {
|
|
||||||
console.log("Helper " + helper + " drives " + drives);
|
|
||||||
|
|
||||||
fileDialogItem.keyboardEnabled = HMD.active;
|
|
||||||
|
|
||||||
// HACK: The following lines force the model to initialize properly such that the go-up button
|
|
||||||
// works properly from the initial screen.
|
|
||||||
var initialFolder = folderListModel.folder;
|
|
||||||
fileTableModel.folder = helper.pathToUrl(drives[0]);
|
|
||||||
fileTableModel.folder = initialFolder;
|
|
||||||
|
|
||||||
iconText = root.title !== "" ? hifi.glyphs.scriptUpload : "";
|
|
||||||
|
|
||||||
// Clear selection when click on external frame.
|
|
||||||
frameClicked.connect(function() { d.clearSelection(); });
|
|
||||||
|
|
||||||
if (selectDirectory) {
|
|
||||||
currentSelection.text = d.capitalizeDrive(helper.urlToPath(initialFolder));
|
|
||||||
d.currentSelectionIsFolder = true;
|
|
||||||
d.currentSelectionUrl = initialFolder;
|
|
||||||
}
|
|
||||||
|
|
||||||
helper.contentsChanged.connect(function() {
|
|
||||||
if (folderListModel) {
|
|
||||||
// Make folderListModel refresh.
|
|
||||||
var save = folderListModel.folder;
|
|
||||||
folderListModel.folder = "";
|
|
||||||
folderListModel.folder = save;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
focusTimer.start();
|
|
||||||
}
|
|
||||||
|
|
||||||
Timer {
|
|
||||||
id: focusTimer
|
|
||||||
interval: 10
|
|
||||||
running: false
|
|
||||||
repeat: false
|
|
||||||
onTriggered: {
|
|
||||||
fileTableView.contentItem.forceActiveFocus();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Item {
|
|
||||||
id: fileDialogItem
|
|
||||||
clip: true
|
|
||||||
width: pane.width
|
|
||||||
height: pane.height
|
|
||||||
anchors.margins: 0
|
|
||||||
|
|
||||||
property bool keyboardEnabled: false
|
|
||||||
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();
|
|
||||||
// Defocus text field so that the keyboard gets hidden.
|
|
||||||
// Clicking also breaks keyboard navigation apart from backtabbing to cancel
|
|
||||||
frame.forceActiveFocus();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Row {
|
|
||||||
id: navControls
|
|
||||||
anchors {
|
|
||||||
top: parent.top
|
|
||||||
topMargin: hifi.dimensions.contentMargin.y
|
|
||||||
left: parent.left
|
|
||||||
}
|
|
||||||
spacing: hifi.dimensions.contentSpacing.x
|
|
||||||
|
|
||||||
GlyphButton {
|
|
||||||
id: upButton
|
|
||||||
glyph: hifi.glyphs.levelUp
|
|
||||||
width: height
|
|
||||||
size: 30
|
|
||||||
enabled: fileTableModel.parentFolder && fileTableModel.parentFolder !== ""
|
|
||||||
onClicked: d.navigateUp();
|
|
||||||
Keys.onReturnPressed: { d.navigateUp(); }
|
|
||||||
KeyNavigation.tab: homeButton
|
|
||||||
KeyNavigation.backtab: upButton
|
|
||||||
KeyNavigation.left: upButton
|
|
||||||
KeyNavigation.right: homeButton
|
|
||||||
}
|
|
||||||
|
|
||||||
GlyphButton {
|
|
||||||
id: homeButton
|
|
||||||
property var destination: helper.home();
|
|
||||||
glyph: hifi.glyphs.home
|
|
||||||
size: 28
|
|
||||||
width: height
|
|
||||||
enabled: d.homeDestination ? true : false
|
|
||||||
onClicked: d.navigateHome();
|
|
||||||
Keys.onReturnPressed: { d.navigateHome(); }
|
|
||||||
KeyNavigation.tab: fileTableView.contentItem
|
|
||||||
KeyNavigation.backtab: upButton
|
|
||||||
KeyNavigation.left: upButton
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ComboBox {
|
|
||||||
id: pathSelector
|
|
||||||
anchors {
|
|
||||||
top: parent.top
|
|
||||||
topMargin: hifi.dimensions.contentMargin.y
|
|
||||||
left: navControls.right
|
|
||||||
leftMargin: hifi.dimensions.contentSpacing.x
|
|
||||||
right: parent.right
|
|
||||||
}
|
|
||||||
|
|
||||||
property var lastValidFolder: helper.urlToPath(fileTableModel.folder)
|
|
||||||
|
|
||||||
function calculatePathChoices(folder) {
|
|
||||||
var folders = folder.split("/"),
|
|
||||||
choices = [],
|
|
||||||
i, length;
|
|
||||||
|
|
||||||
if (folders[folders.length - 1] === "") {
|
|
||||||
folders.pop();
|
|
||||||
}
|
|
||||||
|
|
||||||
choices.push(folders[0]);
|
|
||||||
|
|
||||||
for (i = 1, length = folders.length; i < length; i++) {
|
|
||||||
choices.push(choices[i - 1] + "/" + folders[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (folders[0] === "") {
|
|
||||||
// Special handling for OSX root dir.
|
|
||||||
choices[0] = "/";
|
|
||||||
}
|
|
||||||
|
|
||||||
choices.reverse();
|
|
||||||
|
|
||||||
if (drives && drives.length > 1) {
|
|
||||||
choices.push("This PC");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (choices.length > 0) {
|
|
||||||
pathSelector.model = choices;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onLastValidFolderChanged: {
|
|
||||||
var folder = d.capitalizeDrive(lastValidFolder);
|
|
||||||
calculatePathChoices(folder);
|
|
||||||
}
|
|
||||||
|
|
||||||
onCurrentTextChanged: {
|
|
||||||
var folder = currentText;
|
|
||||||
|
|
||||||
if (/^[a-zA-z]:$/.test(folder)) {
|
|
||||||
folder = "file:///" + folder + "/";
|
|
||||||
} else if (folder === "This PC") {
|
|
||||||
folder = "file:///";
|
|
||||||
} else {
|
|
||||||
folder = helper.pathToUrl(folder);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (helper.urlToPath(folder).toLowerCase() !== helper.urlToPath(fileTableModel.folder).toLowerCase()) {
|
|
||||||
if (root.selectDirectory) {
|
|
||||||
currentSelection.text = currentText !== "This PC" ? currentText : "";
|
|
||||||
d.currentSelectionUrl = helper.pathToUrl(currentText);
|
|
||||||
}
|
|
||||||
fileTableModel.folder = folder;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
KeyNavigation.up: fileTableView.contentItem
|
|
||||||
KeyNavigation.down: fileTableView.contentItem
|
|
||||||
KeyNavigation.tab: fileTableView.contentItem
|
|
||||||
KeyNavigation.backtab: fileTableView.contentItem
|
|
||||||
KeyNavigation.left: fileTableView.contentItem
|
|
||||||
KeyNavigation.right: fileTableView.contentItem
|
|
||||||
}
|
|
||||||
|
|
||||||
QtObject {
|
|
||||||
id: d
|
|
||||||
property var currentSelectionUrl;
|
|
||||||
readonly property string currentSelectionPath: helper.urlToPath(currentSelectionUrl);
|
|
||||||
property bool currentSelectionIsFolder;
|
|
||||||
property var backStack: []
|
|
||||||
property var tableViewConnection: Connections { target: fileTableView; onCurrentRowChanged: d.update(); }
|
|
||||||
property var modelConnection: Connections { target: fileTableModel; onFolderChanged: d.update(); }
|
|
||||||
property var homeDestination: helper.home();
|
|
||||||
|
|
||||||
function capitalizeDrive(path) {
|
|
||||||
// Consistently capitalize drive letter for Windows.
|
|
||||||
if (/[a-zA-Z]:/.test(path)) {
|
|
||||||
return path.charAt(0).toUpperCase() + path.slice(1);
|
|
||||||
}
|
|
||||||
return path;
|
|
||||||
}
|
|
||||||
|
|
||||||
function update() {
|
|
||||||
var row = fileTableView.currentRow;
|
|
||||||
|
|
||||||
if (row === -1) {
|
|
||||||
if (!root.selectDirectory) {
|
|
||||||
currentSelection.text = "";
|
|
||||||
currentSelectionIsFolder = false;
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
currentSelectionUrl = helper.pathToUrl(fileTableView.model.get(row).filePath);
|
|
||||||
currentSelectionIsFolder = fileTableView.model !== filesModel ?
|
|
||||||
fileTableView.model.isFolder(row) :
|
|
||||||
fileTableModel.isFolder(row);
|
|
||||||
if (root.selectDirectory || !currentSelectionIsFolder) {
|
|
||||||
currentSelection.text = capitalizeDrive(helper.urlToPath(currentSelectionUrl));
|
|
||||||
} else {
|
|
||||||
currentSelection.text = "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function navigateUp() {
|
|
||||||
if (fileTableModel.parentFolder && fileTableModel.parentFolder !== "") {
|
|
||||||
fileTableModel.folder = fileTableModel.parentFolder;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function navigateHome() {
|
|
||||||
fileTableModel.folder = homeDestination;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
function clearSelection() {
|
|
||||||
fileTableView.selection.clear();
|
|
||||||
fileTableView.currentRow = -1;
|
|
||||||
update();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
FolderListModel {
|
|
||||||
id: folderListModel
|
|
||||||
nameFilters: selectionType.currentFilter
|
|
||||||
showDirsFirst: true
|
|
||||||
showDotAndDotDot: false
|
|
||||||
showFiles: !root.selectDirectory
|
|
||||||
showHidden: root.showHidden
|
|
||||||
Component.onCompleted: {
|
|
||||||
showFiles = !root.selectDirectory
|
|
||||||
showHidden = root.showHidden
|
|
||||||
}
|
|
||||||
|
|
||||||
onFolderChanged: {
|
|
||||||
d.clearSelection();
|
|
||||||
fileTableModel.update(); // Update once the data from the folder change is available.
|
|
||||||
}
|
|
||||||
|
|
||||||
function getItem(index, field) {
|
|
||||||
return get(index, field);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ListModel {
|
|
||||||
// Emulates FolderListModel but contains drive data.
|
|
||||||
id: driveListModel
|
|
||||||
|
|
||||||
property int count: 1
|
|
||||||
|
|
||||||
Component.onCompleted: initialize();
|
|
||||||
|
|
||||||
function initialize() {
|
|
||||||
var drive,
|
|
||||||
i;
|
|
||||||
|
|
||||||
count = drives.length;
|
|
||||||
|
|
||||||
for (i = 0; i < count; i++) {
|
|
||||||
drive = drives[i].slice(0, -1); // Remove trailing "/".
|
|
||||||
append({
|
|
||||||
fileName: drive,
|
|
||||||
fileModified: new Date(0),
|
|
||||||
fileSize: 0,
|
|
||||||
filePath: drive + "/",
|
|
||||||
fileIsDir: true,
|
|
||||||
fileNameSort: drive.toLowerCase()
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function getItem(index, field) {
|
|
||||||
return get(index)[field];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Component {
|
|
||||||
id: filesModelBuilder
|
|
||||||
ListModel { }
|
|
||||||
}
|
|
||||||
|
|
||||||
QtObject {
|
|
||||||
id: fileTableModel
|
|
||||||
|
|
||||||
// FolderListModel has a couple of problems:
|
|
||||||
// 1) Files and directories sort case-sensitively: https://bugreports.qt.io/browse/QTBUG-48757
|
|
||||||
// 2) Cannot browse up to the "computer" level to view Windows drives: https://bugreports.qt.io/browse/QTBUG-42901
|
|
||||||
//
|
|
||||||
// To solve these problems an intermediary ListModel is used that implements proper sorting and can be populated with
|
|
||||||
// drive information when viewing at the computer level.
|
|
||||||
|
|
||||||
property var folder
|
|
||||||
property int sortOrder: Qt.AscendingOrder
|
|
||||||
property int sortColumn: 0
|
|
||||||
property var model: folderListModel
|
|
||||||
property string parentFolder: calculateParentFolder();
|
|
||||||
|
|
||||||
readonly property string rootFolder: "file:///"
|
|
||||||
|
|
||||||
function calculateParentFolder() {
|
|
||||||
if (model === folderListModel) {
|
|
||||||
if (folderListModel.parentFolder.toString() === "" && driveListModel.count > 1) {
|
|
||||||
return rootFolder;
|
|
||||||
} else {
|
|
||||||
return folderListModel.parentFolder;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onFolderChanged: {
|
|
||||||
if (folder === rootFolder) {
|
|
||||||
model = driveListModel;
|
|
||||||
helper.monitorDirectory("");
|
|
||||||
update();
|
|
||||||
} else {
|
|
||||||
var needsUpdate = model === driveListModel && folder === folderListModel.folder;
|
|
||||||
|
|
||||||
model = folderListModel;
|
|
||||||
folderListModel.folder = folder;
|
|
||||||
helper.monitorDirectory(helper.urlToPath(folder));
|
|
||||||
|
|
||||||
if (needsUpdate) {
|
|
||||||
update();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function isFolder(row) {
|
|
||||||
if (row === -1) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return filesModel.get(row).fileIsDir;
|
|
||||||
}
|
|
||||||
|
|
||||||
function get(row) {
|
|
||||||
return filesModel.get(row)
|
|
||||||
}
|
|
||||||
|
|
||||||
function update() {
|
|
||||||
var dataFields = ["fileName", "fileModified", "fileSize"],
|
|
||||||
sortFields = ["fileNameSort", "fileModified", "fileSize"],
|
|
||||||
dataField = dataFields[sortColumn],
|
|
||||||
sortField = sortFields[sortColumn],
|
|
||||||
sortValue,
|
|
||||||
fileName,
|
|
||||||
fileIsDir,
|
|
||||||
comparisonFunction,
|
|
||||||
lower,
|
|
||||||
middle,
|
|
||||||
upper,
|
|
||||||
rows = 0,
|
|
||||||
i;
|
|
||||||
|
|
||||||
filesModel = filesModelBuilder.createObject(root);
|
|
||||||
|
|
||||||
comparisonFunction = sortOrder === Qt.AscendingOrder
|
|
||||||
? function(a, b) { return a < b; }
|
|
||||||
: function(a, b) { return a > b; }
|
|
||||||
|
|
||||||
for (i = 0; i < model.count; i++) {
|
|
||||||
fileName = model.getItem(i, "fileName");
|
|
||||||
fileIsDir = model.getItem(i, "fileIsDir");
|
|
||||||
|
|
||||||
sortValue = model.getItem(i, dataField);
|
|
||||||
if (dataField === "fileName") {
|
|
||||||
// Directories first by prefixing a "*".
|
|
||||||
// Case-insensitive.
|
|
||||||
sortValue = (fileIsDir ? "*" : "") + sortValue.toLowerCase();
|
|
||||||
}
|
|
||||||
|
|
||||||
lower = 0;
|
|
||||||
upper = rows;
|
|
||||||
while (lower < upper) {
|
|
||||||
middle = Math.floor((lower + upper) / 2);
|
|
||||||
var lessThan;
|
|
||||||
if (comparisonFunction(sortValue, filesModel.get(middle)[sortField])) {
|
|
||||||
lessThan = true;
|
|
||||||
upper = middle;
|
|
||||||
} else {
|
|
||||||
lessThan = false;
|
|
||||||
lower = middle + 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
filesModel.insert(lower, {
|
|
||||||
fileName: fileName,
|
|
||||||
fileModified: (fileIsDir ? new Date(0) : model.getItem(i, "fileModified")),
|
|
||||||
fileSize: model.getItem(i, "fileSize"),
|
|
||||||
filePath: model.getItem(i, "filePath"),
|
|
||||||
fileIsDir: fileIsDir,
|
|
||||||
fileNameSort: (fileIsDir ? "*" : "") + fileName.toLowerCase()
|
|
||||||
});
|
|
||||||
|
|
||||||
rows++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Table {
|
|
||||||
id: fileTableView
|
|
||||||
colorScheme: hifi.colorSchemes.light
|
|
||||||
anchors {
|
|
||||||
top: navControls.bottom
|
|
||||||
topMargin: hifi.dimensions.contentSpacing.y
|
|
||||||
left: parent.left
|
|
||||||
right: parent.right
|
|
||||||
bottom: currentSelection.top
|
|
||||||
bottomMargin: hifi.dimensions.contentSpacing.y + currentSelection.controlHeight - currentSelection.height
|
|
||||||
}
|
|
||||||
headerVisible: !selectDirectory
|
|
||||||
onDoubleClicked: navigateToRow(row);
|
|
||||||
Keys.onReturnPressed: navigateToCurrentRow();
|
|
||||||
Keys.onEnterPressed: navigateToCurrentRow();
|
|
||||||
|
|
||||||
sortIndicatorColumn: 0
|
|
||||||
sortIndicatorOrder: Qt.AscendingOrder
|
|
||||||
sortIndicatorVisible: true
|
|
||||||
|
|
||||||
model: filesModel
|
|
||||||
|
|
||||||
function updateSort() {
|
|
||||||
fileTableModel.sortOrder = sortIndicatorOrder;
|
|
||||||
fileTableModel.sortColumn = sortIndicatorColumn;
|
|
||||||
fileTableModel.update();
|
|
||||||
}
|
|
||||||
|
|
||||||
onSortIndicatorColumnChanged: { updateSort(); }
|
|
||||||
|
|
||||||
onSortIndicatorOrderChanged: { updateSort(); }
|
|
||||||
|
|
||||||
itemDelegate: Item {
|
|
||||||
clip: true
|
|
||||||
|
|
||||||
FiraSansSemiBold {
|
|
||||||
text: getText();
|
|
||||||
elide: styleData.elideMode
|
|
||||||
anchors {
|
|
||||||
left: parent.left
|
|
||||||
leftMargin: hifi.dimensions.tablePadding
|
|
||||||
right: parent.right
|
|
||||||
rightMargin: hifi.dimensions.tablePadding
|
|
||||||
verticalCenter: parent.verticalCenter
|
|
||||||
}
|
|
||||||
size: hifi.fontSizes.tableText
|
|
||||||
color: hifi.colors.baseGrayHighlight
|
|
||||||
font.family: (styleData.row !== -1 && fileTableView.model.get(styleData.row).fileIsDir)
|
|
||||||
? "Fira Sans SemiBold" : "Fira Sans"
|
|
||||||
|
|
||||||
function getText() {
|
|
||||||
if (styleData.row === -1) {
|
|
||||||
return styleData.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (styleData.column) {
|
|
||||||
case 1: return fileTableView.model.get(styleData.row).fileIsDir ? "" : styleData.value;
|
|
||||||
case 2: return fileTableView.model.get(styleData.row).fileIsDir ? "" : formatSize(styleData.value);
|
|
||||||
default: return styleData.value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function formatSize(size) {
|
|
||||||
var suffixes = [ "bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB" ];
|
|
||||||
var suffixIndex = 0
|
|
||||||
while ((size / 1024.0) > 1.1) {
|
|
||||||
size /= 1024.0;
|
|
||||||
++suffixIndex;
|
|
||||||
}
|
|
||||||
|
|
||||||
size = Math.round(size*1000)/1000;
|
|
||||||
size = size.toLocaleString()
|
|
||||||
|
|
||||||
return size + " " + suffixes[suffixIndex];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
TableViewColumn {
|
|
||||||
id: fileNameColumn
|
|
||||||
role: "fileName"
|
|
||||||
title: "Name"
|
|
||||||
width: (selectDirectory ? 1.0 : 0.5) * fileTableView.width
|
|
||||||
movable: false
|
|
||||||
resizable: true
|
|
||||||
}
|
|
||||||
TableViewColumn {
|
|
||||||
id: fileModifiedColumn
|
|
||||||
role: "fileModified"
|
|
||||||
title: "Date"
|
|
||||||
width: 0.3 * fileTableView.width
|
|
||||||
movable: false
|
|
||||||
resizable: true
|
|
||||||
visible: !selectDirectory
|
|
||||||
}
|
|
||||||
TableViewColumn {
|
|
||||||
role: "fileSize"
|
|
||||||
title: "Size"
|
|
||||||
width: fileTableView.width - fileNameColumn.width - fileModifiedColumn.width
|
|
||||||
movable: false
|
|
||||||
resizable: true
|
|
||||||
visible: !selectDirectory
|
|
||||||
}
|
|
||||||
|
|
||||||
function navigateToRow(row) {
|
|
||||||
currentRow = row;
|
|
||||||
navigateToCurrentRow();
|
|
||||||
}
|
|
||||||
|
|
||||||
function navigateToCurrentRow() {
|
|
||||||
var currentModel = fileTableView.model !== filesModel ? fileTableView.model : fileTableModel
|
|
||||||
var row = fileTableView.currentRow
|
|
||||||
var isFolder = currentModel.isFolder(row);
|
|
||||||
var file = currentModel.get(row).filePath;
|
|
||||||
if (isFolder) {
|
|
||||||
currentModel.folder = helper.pathToUrl(file);
|
|
||||||
} else {
|
|
||||||
okAction.trigger();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
property string prefix: ""
|
|
||||||
|
|
||||||
function addToPrefix(event) {
|
|
||||||
if (!event.text || event.text === "") {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
var newPrefix = prefix + event.text.toLowerCase();
|
|
||||||
var matchedIndex = -1;
|
|
||||||
for (var i = 0; i < model.count; ++i) {
|
|
||||||
var name = model !== filesModel ? model.get(i).fileName.toLowerCase() :
|
|
||||||
filesModel.get(i).fileName.toLowerCase();
|
|
||||||
if (0 === name.indexOf(newPrefix)) {
|
|
||||||
matchedIndex = i;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (matchedIndex !== -1) {
|
|
||||||
fileTableView.selection.clear();
|
|
||||||
fileTableView.selection.select(matchedIndex);
|
|
||||||
fileTableView.currentRow = matchedIndex;
|
|
||||||
fileTableView.prefix = newPrefix;
|
|
||||||
}
|
|
||||||
prefixClearTimer.restart();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
Timer {
|
|
||||||
id: prefixClearTimer
|
|
||||||
interval: 1000
|
|
||||||
repeat: false
|
|
||||||
running: false
|
|
||||||
onTriggered: fileTableView.prefix = "";
|
|
||||||
}
|
|
||||||
|
|
||||||
Keys.onPressed: {
|
|
||||||
switch (event.key) {
|
|
||||||
case Qt.Key_Backspace:
|
|
||||||
case Qt.Key_Tab:
|
|
||||||
case Qt.Key_Backtab:
|
|
||||||
event.accepted = false;
|
|
||||||
break;
|
|
||||||
case Qt.Key_Escape:
|
|
||||||
event.accepted = true;
|
|
||||||
root.click(OriginalDialogs.StandardButton.Cancel);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
if (addToPrefix(event)) {
|
|
||||||
event.accepted = true
|
|
||||||
} else {
|
|
||||||
event.accepted = false;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
KeyNavigation.tab: root.saveDialog ? currentSelection : openButton
|
|
||||||
}
|
|
||||||
|
|
||||||
TextField {
|
|
||||||
id: currentSelection
|
|
||||||
label: selectDirectory ? "Directory:" : "File name:"
|
|
||||||
anchors {
|
|
||||||
left: parent.left
|
|
||||||
right: selectionType.visible ? selectionType.left: parent.right
|
|
||||||
rightMargin: selectionType.visible ? hifi.dimensions.contentSpacing.x : 0
|
|
||||||
bottom: keyboard.top
|
|
||||||
bottomMargin: hifi.dimensions.contentSpacing.y
|
|
||||||
}
|
|
||||||
readOnly: !root.saveDialog
|
|
||||||
activeFocusOnTab: !readOnly
|
|
||||||
onActiveFocusChanged: if (activeFocus) { selectAll(); }
|
|
||||||
onAccepted: okAction.trigger();
|
|
||||||
KeyNavigation.up: fileTableView.contentItem
|
|
||||||
KeyNavigation.down: openButton
|
|
||||||
KeyNavigation.tab: openButton
|
|
||||||
KeyNavigation.backtab: fileTableView.contentItem
|
|
||||||
}
|
|
||||||
|
|
||||||
FileTypeSelection {
|
|
||||||
id: selectionType
|
|
||||||
anchors {
|
|
||||||
top: currentSelection.top
|
|
||||||
left: buttonRow.left
|
|
||||||
right: parent.right
|
|
||||||
}
|
|
||||||
visible: !selectDirectory && filtersCount > 1
|
|
||||||
}
|
|
||||||
|
|
||||||
Keyboard {
|
|
||||||
id: keyboard
|
|
||||||
raised: parent.keyboardEnabled && parent.keyboardRaised
|
|
||||||
numeric: parent.punctuationMode
|
|
||||||
anchors {
|
|
||||||
left: parent.left
|
|
||||||
right: parent.right
|
|
||||||
bottom: buttonRow.top
|
|
||||||
bottomMargin: visible ? hifi.dimensions.contentSpacing.y : 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Row {
|
|
||||||
id: buttonRow
|
|
||||||
anchors {
|
|
||||||
right: parent.right
|
|
||||||
bottom: parent.bottom
|
|
||||||
}
|
|
||||||
spacing: hifi.dimensions.contentSpacing.y
|
|
||||||
|
|
||||||
Button {
|
|
||||||
id: openButton
|
|
||||||
color: hifi.buttons.blue
|
|
||||||
action: okAction
|
|
||||||
Keys.onReturnPressed: okAction.trigger()
|
|
||||||
KeyNavigation.right: cancelButton
|
|
||||||
KeyNavigation.up: root.saveDialog ? currentSelection : fileTableView.contentItem
|
|
||||||
KeyNavigation.tab: cancelButton
|
|
||||||
}
|
|
||||||
|
|
||||||
Button {
|
|
||||||
id: cancelButton
|
|
||||||
action: cancelAction
|
|
||||||
Keys.onReturnPressed: { cancelAction.trigger() }
|
|
||||||
KeyNavigation.left: openButton
|
|
||||||
KeyNavigation.up: root.saveDialog ? currentSelection : fileTableView.contentItem
|
|
||||||
KeyNavigation.backtab: openButton
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Action {
|
|
||||||
id: okAction
|
|
||||||
text: currentSelection.text ? (root.selectDirectory && fileTableView.currentRow === -1 ? "Choose" : (root.saveDialog ? "Save" : "Open")) : "Open"
|
|
||||||
enabled: currentSelection.text || !root.selectDirectory && d.currentSelectionIsFolder ? true : false
|
|
||||||
onTriggered: {
|
|
||||||
if (!root.selectDirectory && !d.currentSelectionIsFolder
|
|
||||||
|| root.selectDirectory && fileTableView.currentRow === -1) {
|
|
||||||
okActionTimer.start();
|
|
||||||
} else {
|
|
||||||
fileTableView.navigateToCurrentRow();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Timer {
|
|
||||||
id: okActionTimer
|
|
||||||
interval: 50
|
|
||||||
running: false
|
|
||||||
repeat: false
|
|
||||||
onTriggered: {
|
|
||||||
if (!root.saveDialog) {
|
|
||||||
selectedFile(d.currentSelectionUrl);
|
|
||||||
root.destroy()
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle the ambiguity between different cases
|
|
||||||
// * typed name (with or without extension)
|
|
||||||
// * full path vs relative vs filename only
|
|
||||||
var selection = helper.saveHelper(currentSelection.text, root.dir, selectionType.currentFilter);
|
|
||||||
|
|
||||||
if (!selection) {
|
|
||||||
desktop.messageBox({ icon: OriginalDialogs.StandardIcon.Warning, text: "Unable to parse selection" })
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (helper.urlIsDir(selection)) {
|
|
||||||
root.dir = selection;
|
|
||||||
currentSelection.text = "";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if the file is a valid target
|
|
||||||
if (!helper.urlIsWritable(selection)) {
|
|
||||||
desktop.messageBox({
|
|
||||||
icon: OriginalDialogs.StandardIcon.Warning,
|
|
||||||
text: "Unable to write to location " + selection
|
|
||||||
})
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (helper.urlExists(selection)) {
|
|
||||||
var messageBox = desktop.messageBox({
|
|
||||||
icon: OriginalDialogs.StandardIcon.Question,
|
|
||||||
buttons: OriginalDialogs.StandardButton.Yes | OriginalDialogs.StandardButton.No,
|
|
||||||
text: "Do you wish to overwrite " + selection + "?",
|
|
||||||
});
|
|
||||||
var result = messageBox.exec();
|
|
||||||
if (OriginalDialogs.StandardButton.Yes !== result) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log("Selecting " + selection)
|
|
||||||
selectedFile(selection);
|
|
||||||
root.destroy();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Action {
|
|
||||||
id: cancelAction
|
|
||||||
text: "Cancel"
|
|
||||||
onTriggered: { canceled(); root.shown = false; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Keys.onPressed: {
|
|
||||||
switch (event.key) {
|
|
||||||
case Qt.Key_Backspace:
|
|
||||||
event.accepted = d.navigateUp();
|
|
||||||
break;
|
|
||||||
|
|
||||||
case Qt.Key_Home:
|
|
||||||
event.accepted = d.navigateHome();
|
|
||||||
break;
|
|
||||||
|
|
||||||
case Qt.Key_Escape:
|
|
||||||
event.accepted = true;
|
|
||||||
root.click(OriginalDialogs.StandardButton.Cancel);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,231 +0,0 @@
|
||||||
//
|
|
||||||
// QueryDialog.qml
|
|
||||||
//
|
|
||||||
// Created by Bradley Austin Davis on 22 Jan 2016
|
|
||||||
// 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.7
|
|
||||||
import QtQuick.Controls 1.4
|
|
||||||
|
|
||||||
import "../controls-uit"
|
|
||||||
import "../styles-uit"
|
|
||||||
import "../windows"
|
|
||||||
|
|
||||||
ModalWindow {
|
|
||||||
id: root
|
|
||||||
HifiConstants { id: hifi }
|
|
||||||
implicitWidth: 640
|
|
||||||
implicitHeight: 320
|
|
||||||
visible: true
|
|
||||||
keyboardOverride: true // Disable ModalWindow's keyboard.
|
|
||||||
|
|
||||||
signal selected(var result);
|
|
||||||
signal canceled();
|
|
||||||
|
|
||||||
property int icon: hifi.icons.none
|
|
||||||
property string iconText: ""
|
|
||||||
property int iconSize: 35
|
|
||||||
onIconChanged: updateIcon();
|
|
||||||
|
|
||||||
property var items;
|
|
||||||
property string label
|
|
||||||
property var result;
|
|
||||||
property alias current: textResult.text
|
|
||||||
|
|
||||||
// For text boxes
|
|
||||||
property alias placeholderText: textResult.placeholderText
|
|
||||||
|
|
||||||
// For combo boxes
|
|
||||||
property bool editable: true;
|
|
||||||
|
|
||||||
property int titleWidth: 0
|
|
||||||
onTitleWidthChanged: d.resize();
|
|
||||||
|
|
||||||
property bool keyboardEnabled: false
|
|
||||||
property bool keyboardRaised: false
|
|
||||||
property bool punctuationMode: false
|
|
||||||
|
|
||||||
onKeyboardRaisedChanged: d.resize();
|
|
||||||
|
|
||||||
function updateIcon() {
|
|
||||||
if (!root) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
iconText = hifi.glyphForIcon(root.icon);
|
|
||||||
}
|
|
||||||
|
|
||||||
Item {
|
|
||||||
id: modalWindowItem
|
|
||||||
clip: true
|
|
||||||
width: pane.width
|
|
||||||
height: pane.height
|
|
||||||
anchors.margins: 0
|
|
||||||
|
|
||||||
QtObject {
|
|
||||||
id: d
|
|
||||||
readonly property int minWidth: 480
|
|
||||||
readonly property int maxWdith: 1280
|
|
||||||
readonly property int minHeight: 120
|
|
||||||
readonly property int maxHeight: 720
|
|
||||||
|
|
||||||
function resize() {
|
|
||||||
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)) + ((keyboardEnabled && keyboardRaised) ? (keyboard.raisedHeight + 2 * hifi.dimensions.contentSpacing.y) : 0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Item {
|
|
||||||
anchors {
|
|
||||||
top: parent.top
|
|
||||||
bottom: keyboard.top;
|
|
||||||
left: parent.left;
|
|
||||||
right: parent.right;
|
|
||||||
margins: 0
|
|
||||||
bottomMargin: 2 * hifi.dimensions.contentSpacing.y
|
|
||||||
}
|
|
||||||
|
|
||||||
// FIXME make a text field type that can be bound to a history for autocompletion
|
|
||||||
TextField {
|
|
||||||
id: textResult
|
|
||||||
label: root.label
|
|
||||||
visible: items ? false : true
|
|
||||||
anchors {
|
|
||||||
left: parent.left;
|
|
||||||
right: parent.right;
|
|
||||||
bottom: parent.bottom
|
|
||||||
}
|
|
||||||
KeyNavigation.down: acceptButton
|
|
||||||
KeyNavigation.tab: acceptButton
|
|
||||||
}
|
|
||||||
|
|
||||||
ComboBox {
|
|
||||||
id: comboBox
|
|
||||||
label: root.label
|
|
||||||
visible: items ? true : false
|
|
||||||
anchors {
|
|
||||||
left: parent.left
|
|
||||||
right: parent.right
|
|
||||||
bottom: parent.bottom
|
|
||||||
}
|
|
||||||
model: items ? items : []
|
|
||||||
KeyNavigation.down: acceptButton
|
|
||||||
KeyNavigation.tab: acceptButton
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
property alias keyboardOverride: root.keyboardOverride
|
|
||||||
property alias keyboardRaised: root.keyboardRaised
|
|
||||||
property alias punctuationMode: root.punctuationMode
|
|
||||||
Keyboard {
|
|
||||||
id: keyboard
|
|
||||||
raised: keyboardEnabled && keyboardRaised
|
|
||||||
numeric: punctuationMode
|
|
||||||
anchors {
|
|
||||||
left: parent.left
|
|
||||||
right: parent.right
|
|
||||||
bottom: buttons.top
|
|
||||||
bottomMargin: raised ? 2 * hifi.dimensions.contentSpacing.y : 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Flow {
|
|
||||||
id: buttons
|
|
||||||
spacing: hifi.dimensions.contentSpacing.x
|
|
||||||
onHeightChanged: d.resize(); onWidthChanged: d.resize();
|
|
||||||
layoutDirection: Qt.RightToLeft
|
|
||||||
anchors {
|
|
||||||
bottom: parent.bottom
|
|
||||||
right: parent.right
|
|
||||||
margins: 0
|
|
||||||
bottomMargin: hifi.dimensions.contentSpacing.y
|
|
||||||
}
|
|
||||||
Button {
|
|
||||||
id: cancelButton
|
|
||||||
action: cancelAction
|
|
||||||
KeyNavigation.left: acceptButton
|
|
||||||
KeyNavigation.up: items ? comboBox : textResult
|
|
||||||
KeyNavigation.backtab: acceptButton
|
|
||||||
}
|
|
||||||
Button {
|
|
||||||
id: acceptButton
|
|
||||||
action: acceptAction
|
|
||||||
KeyNavigation.right: cancelButton
|
|
||||||
KeyNavigation.up: items ? comboBox : textResult
|
|
||||||
KeyNavigation.tab: cancelButton
|
|
||||||
KeyNavigation.backtab: items ? comboBox : textResult
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Action {
|
|
||||||
id: cancelAction
|
|
||||||
text: qsTr("Cancel");
|
|
||||||
shortcut: "Esc"
|
|
||||||
onTriggered: {
|
|
||||||
root.canceled();
|
|
||||||
// FIXME we are leaking memory to avoid a crash
|
|
||||||
// root.destroy();
|
|
||||||
|
|
||||||
root.disableFade = true
|
|
||||||
visible = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Action {
|
|
||||||
id: acceptAction
|
|
||||||
text: qsTr("OK");
|
|
||||||
shortcut: "Return"
|
|
||||||
onTriggered: {
|
|
||||||
root.result = items ? comboBox.currentText : textResult.text
|
|
||||||
root.selected(root.result);
|
|
||||||
// FIXME we are leaking memory to avoid a crash
|
|
||||||
// root.destroy();
|
|
||||||
|
|
||||||
root.disableFade = true
|
|
||||||
visible = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Keys.onPressed: {
|
|
||||||
if (!visible) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (event.key) {
|
|
||||||
case Qt.Key_Escape:
|
|
||||||
case Qt.Key_Back:
|
|
||||||
cancelAction.trigger()
|
|
||||||
event.accepted = true;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case Qt.Key_Return:
|
|
||||||
case Qt.Key_Enter:
|
|
||||||
if (acceptButton.focus) {
|
|
||||||
acceptAction.trigger()
|
|
||||||
} else if (cancelButton.focus) {
|
|
||||||
cancelAction.trigger()
|
|
||||||
} else if (comboBox.focus || comboBox.popup.focus) {
|
|
||||||
comboBox.showList()
|
|
||||||
}
|
|
||||||
event.accepted = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Component.onCompleted: {
|
|
||||||
keyboardEnabled = HMD.active;
|
|
||||||
updateIcon();
|
|
||||||
d.resize();
|
|
||||||
if (items) {
|
|
||||||
comboBox.forceActiveFocus()
|
|
||||||
} else {
|
|
||||||
textResult.forceActiveFocus()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,533 +0,0 @@
|
||||||
//
|
|
||||||
// AssetDialogContent.qml
|
|
||||||
//
|
|
||||||
// Created by David Rowe on 19 Apr 2017
|
|
||||||
// Copyright 2017 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.7
|
|
||||||
import QtQuick.Controls 1.5
|
|
||||||
|
|
||||||
import "../../controls-uit"
|
|
||||||
import "../../styles-uit"
|
|
||||||
|
|
||||||
import "../fileDialog"
|
|
||||||
|
|
||||||
Item {
|
|
||||||
// Set from OffscreenUi::assetDialog()
|
|
||||||
property alias dir: assetTableModel.folder
|
|
||||||
property alias filter: selectionType.filtersString // FIXME: Currently only supports simple filters, "*.xxx".
|
|
||||||
property int options // Not used.
|
|
||||||
|
|
||||||
property bool selectDirectory: false
|
|
||||||
|
|
||||||
// Not implemented.
|
|
||||||
//property bool saveDialog: false;
|
|
||||||
//property bool multiSelect: false;
|
|
||||||
|
|
||||||
property bool singleClickNavigate: false
|
|
||||||
|
|
||||||
HifiConstants { id: hifi }
|
|
||||||
|
|
||||||
Component.onCompleted: {
|
|
||||||
homeButton.destination = dir;
|
|
||||||
|
|
||||||
if (selectDirectory) {
|
|
||||||
d.currentSelectionIsFolder = true;
|
|
||||||
d.currentSelectionPath = assetTableModel.folder;
|
|
||||||
}
|
|
||||||
|
|
||||||
assetTableView.forceActiveFocus();
|
|
||||||
}
|
|
||||||
|
|
||||||
Item {
|
|
||||||
id: assetDialogItem
|
|
||||||
anchors.fill: parent
|
|
||||||
clip: true
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
// Clear selection when click on internal unused area.
|
|
||||||
anchors.fill: parent
|
|
||||||
drag.target: root
|
|
||||||
onClicked: {
|
|
||||||
d.clearSelection();
|
|
||||||
frame.forceActiveFocus();
|
|
||||||
assetTableView.forceActiveFocus();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Row {
|
|
||||||
id: navControls
|
|
||||||
anchors {
|
|
||||||
top: parent.top
|
|
||||||
topMargin: hifi.dimensions.contentMargin.y
|
|
||||||
left: parent.left
|
|
||||||
}
|
|
||||||
spacing: hifi.dimensions.contentSpacing.x
|
|
||||||
|
|
||||||
GlyphButton {
|
|
||||||
id: upButton
|
|
||||||
glyph: hifi.glyphs.levelUp
|
|
||||||
width: height
|
|
||||||
size: 30
|
|
||||||
enabled: assetTableModel.parentFolder !== ""
|
|
||||||
onClicked: d.navigateUp();
|
|
||||||
}
|
|
||||||
|
|
||||||
GlyphButton {
|
|
||||||
id: homeButton
|
|
||||||
property string destination: ""
|
|
||||||
glyph: hifi.glyphs.home
|
|
||||||
size: 28
|
|
||||||
width: height
|
|
||||||
enabled: destination !== ""
|
|
||||||
//onClicked: d.navigateHome();
|
|
||||||
onClicked: assetTableModel.folder = destination;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ComboBox {
|
|
||||||
id: pathSelector
|
|
||||||
anchors {
|
|
||||||
top: parent.top
|
|
||||||
topMargin: hifi.dimensions.contentMargin.y
|
|
||||||
left: navControls.right
|
|
||||||
leftMargin: hifi.dimensions.contentSpacing.x
|
|
||||||
right: parent.right
|
|
||||||
}
|
|
||||||
z: 10
|
|
||||||
|
|
||||||
property string lastValidFolder: assetTableModel.folder
|
|
||||||
|
|
||||||
function calculatePathChoices(folder) {
|
|
||||||
var folders = folder.split("/"),
|
|
||||||
choices = [],
|
|
||||||
i, length;
|
|
||||||
|
|
||||||
if (folders[folders.length - 1] === "") {
|
|
||||||
folders.pop();
|
|
||||||
}
|
|
||||||
|
|
||||||
choices.push(folders[0]);
|
|
||||||
|
|
||||||
for (i = 1, length = folders.length; i < length; i++) {
|
|
||||||
choices.push(choices[i - 1] + "/" + folders[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (folders[0] === "") {
|
|
||||||
choices[0] = "/";
|
|
||||||
}
|
|
||||||
|
|
||||||
choices.reverse();
|
|
||||||
|
|
||||||
if (choices.length > 0) {
|
|
||||||
pathSelector.model = choices;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onLastValidFolderChanged: {
|
|
||||||
var folder = lastValidFolder;
|
|
||||||
calculatePathChoices(folder);
|
|
||||||
}
|
|
||||||
|
|
||||||
onCurrentTextChanged: {
|
|
||||||
var folder = currentText;
|
|
||||||
|
|
||||||
if (folder !== "/") {
|
|
||||||
folder += "/";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (folder !== assetTableModel.folder) {
|
|
||||||
if (root.selectDirectory) {
|
|
||||||
currentSelection.text = currentText;
|
|
||||||
d.currentSelectionPath = currentText;
|
|
||||||
}
|
|
||||||
assetTableModel.folder = folder;
|
|
||||||
assetTableView.forceActiveFocus();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
QtObject {
|
|
||||||
id: d
|
|
||||||
|
|
||||||
property string currentSelectionPath
|
|
||||||
property bool currentSelectionIsFolder
|
|
||||||
property var tableViewConnection: Connections { target: assetTableView; onCurrentRowChanged: d.update(); }
|
|
||||||
|
|
||||||
function update() {
|
|
||||||
var row = assetTableView.currentRow;
|
|
||||||
|
|
||||||
if (row === -1) {
|
|
||||||
if (!root.selectDirectory) {
|
|
||||||
currentSelection.text = "";
|
|
||||||
currentSelectionIsFolder = false;
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var rowInfo = assetTableModel.get(row);
|
|
||||||
currentSelectionPath = rowInfo.filePath;
|
|
||||||
currentSelectionIsFolder = rowInfo.fileIsDir;
|
|
||||||
if (root.selectDirectory || !currentSelectionIsFolder) {
|
|
||||||
currentSelection.text = currentSelectionPath;
|
|
||||||
} else {
|
|
||||||
currentSelection.text = "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function navigateUp() {
|
|
||||||
if (assetTableModel.parentFolder !== "") {
|
|
||||||
assetTableModel.folder = assetTableModel.parentFolder;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
function navigateHome() {
|
|
||||||
assetTableModel.folder = homeButton.destination;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
function clearSelection() {
|
|
||||||
assetTableView.selection.clear();
|
|
||||||
assetTableView.currentRow = -1;
|
|
||||||
update();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ListModel {
|
|
||||||
id: assetTableModel
|
|
||||||
|
|
||||||
property string folder
|
|
||||||
property string parentFolder: ""
|
|
||||||
readonly property string rootFolder: "/"
|
|
||||||
|
|
||||||
onFolderChanged: {
|
|
||||||
parentFolder = calculateParentFolder();
|
|
||||||
update();
|
|
||||||
}
|
|
||||||
|
|
||||||
function calculateParentFolder() {
|
|
||||||
if (folder !== "/") {
|
|
||||||
return folder.slice(0, folder.slice(0, -1).lastIndexOf("/") + 1);
|
|
||||||
}
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
function isFolder(row) {
|
|
||||||
if (row === -1) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return get(row).fileIsDir;
|
|
||||||
}
|
|
||||||
|
|
||||||
function onGetAllMappings(error, map) {
|
|
||||||
var mappings,
|
|
||||||
fileTypeFilter,
|
|
||||||
index,
|
|
||||||
path,
|
|
||||||
fileName,
|
|
||||||
fileType,
|
|
||||||
fileIsDir,
|
|
||||||
isValid,
|
|
||||||
subDirectory,
|
|
||||||
subDirectories = [],
|
|
||||||
fileNameSort,
|
|
||||||
rows = 0,
|
|
||||||
lower,
|
|
||||||
middle,
|
|
||||||
upper,
|
|
||||||
i,
|
|
||||||
length;
|
|
||||||
|
|
||||||
clear();
|
|
||||||
|
|
||||||
if (error === "") {
|
|
||||||
mappings = Object.keys(map);
|
|
||||||
fileTypeFilter = filter.replace("*", "").toLowerCase();
|
|
||||||
|
|
||||||
for (i = 0, length = mappings.length; i < length; i++) {
|
|
||||||
index = mappings[i].lastIndexOf("/");
|
|
||||||
|
|
||||||
path = mappings[i].slice(0, mappings[i].lastIndexOf("/") + 1);
|
|
||||||
fileName = mappings[i].slice(path.length);
|
|
||||||
fileType = fileName.slice(fileName.lastIndexOf("."));
|
|
||||||
fileIsDir = false;
|
|
||||||
isValid = false;
|
|
||||||
|
|
||||||
if (fileType.toLowerCase() === fileTypeFilter) {
|
|
||||||
if (path === folder) {
|
|
||||||
isValid = !selectDirectory;
|
|
||||||
} else if (path.length > folder.length) {
|
|
||||||
subDirectory = path.slice(folder.length);
|
|
||||||
index = subDirectory.indexOf("/");
|
|
||||||
if (index === subDirectory.lastIndexOf("/")) {
|
|
||||||
fileName = subDirectory.slice(0, index);
|
|
||||||
if (subDirectories.indexOf(fileName) === -1) {
|
|
||||||
fileIsDir = true;
|
|
||||||
isValid = true;
|
|
||||||
subDirectories.push(fileName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isValid) {
|
|
||||||
fileNameSort = (fileIsDir ? "*" : "") + fileName.toLowerCase();
|
|
||||||
|
|
||||||
lower = 0;
|
|
||||||
upper = rows;
|
|
||||||
while (lower < upper) {
|
|
||||||
middle = Math.floor((lower + upper) / 2);
|
|
||||||
var lessThan;
|
|
||||||
if (fileNameSort < get(middle)["fileNameSort"]) {
|
|
||||||
lessThan = true;
|
|
||||||
upper = middle;
|
|
||||||
} else {
|
|
||||||
lessThan = false;
|
|
||||||
lower = middle + 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
insert(lower, {
|
|
||||||
fileName: fileName,
|
|
||||||
filePath: path + (fileIsDir ? "" : fileName),
|
|
||||||
fileIsDir: fileIsDir,
|
|
||||||
fileNameSort: fileNameSort
|
|
||||||
});
|
|
||||||
|
|
||||||
rows++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
console.log("Error getting mappings from Asset Server");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function update() {
|
|
||||||
d.clearSelection();
|
|
||||||
clear();
|
|
||||||
Assets.getAllMappings(onGetAllMappings);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Table {
|
|
||||||
id: assetTableView
|
|
||||||
colorScheme: hifi.colorSchemes.light
|
|
||||||
anchors {
|
|
||||||
top: navControls.bottom
|
|
||||||
topMargin: hifi.dimensions.contentSpacing.y
|
|
||||||
left: parent.left
|
|
||||||
right: parent.right
|
|
||||||
bottom: currentSelection.top
|
|
||||||
bottomMargin: hifi.dimensions.contentSpacing.y + currentSelection.controlHeight - currentSelection.height
|
|
||||||
}
|
|
||||||
|
|
||||||
model: assetTableModel
|
|
||||||
|
|
||||||
focus: true
|
|
||||||
|
|
||||||
onClicked: {
|
|
||||||
if (singleClickNavigate) {
|
|
||||||
navigateToRow(row);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onDoubleClicked: navigateToRow(row);
|
|
||||||
Keys.onReturnPressed: navigateToCurrentRow();
|
|
||||||
Keys.onEnterPressed: navigateToCurrentRow();
|
|
||||||
|
|
||||||
itemDelegate: Item {
|
|
||||||
clip: true
|
|
||||||
|
|
||||||
FiraSansSemiBold {
|
|
||||||
text: styleData.value
|
|
||||||
elide: styleData.elideMode
|
|
||||||
anchors {
|
|
||||||
left: parent.left
|
|
||||||
leftMargin: hifi.dimensions.tablePadding
|
|
||||||
right: parent.right
|
|
||||||
rightMargin: hifi.dimensions.tablePadding
|
|
||||||
verticalCenter: parent.verticalCenter
|
|
||||||
}
|
|
||||||
size: hifi.fontSizes.tableText
|
|
||||||
color: hifi.colors.baseGrayHighlight
|
|
||||||
font.family: (styleData.row !== -1 && assetTableView.model.get(styleData.row).fileIsDir)
|
|
||||||
? "Fira Sans SemiBold" : "Fira Sans"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
TableViewColumn {
|
|
||||||
id: fileNameColumn
|
|
||||||
role: "fileName"
|
|
||||||
title: "Name"
|
|
||||||
width: assetTableView.width
|
|
||||||
movable: false
|
|
||||||
resizable: false
|
|
||||||
}
|
|
||||||
|
|
||||||
function navigateToRow(row) {
|
|
||||||
currentRow = row;
|
|
||||||
navigateToCurrentRow();
|
|
||||||
}
|
|
||||||
|
|
||||||
function navigateToCurrentRow() {
|
|
||||||
if (model.isFolder(currentRow)) {
|
|
||||||
model.folder = model.get(currentRow).filePath;
|
|
||||||
} else {
|
|
||||||
okAction.trigger();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Timer {
|
|
||||||
id: prefixClearTimer
|
|
||||||
interval: 1000
|
|
||||||
repeat: false
|
|
||||||
running: false
|
|
||||||
onTriggered: assetTableView.prefix = "";
|
|
||||||
}
|
|
||||||
|
|
||||||
property string prefix: ""
|
|
||||||
|
|
||||||
function addToPrefix(event) {
|
|
||||||
if (!event.text || event.text === "") {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
var newPrefix = prefix + event.text.toLowerCase();
|
|
||||||
var matchedIndex = -1;
|
|
||||||
for (var i = 0; i < model.count; ++i) {
|
|
||||||
var name = model.get(i).fileName.toLowerCase();
|
|
||||||
if (0 === name.indexOf(newPrefix)) {
|
|
||||||
matchedIndex = i;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (matchedIndex !== -1) {
|
|
||||||
assetTableView.selection.clear();
|
|
||||||
assetTableView.selection.select(matchedIndex);
|
|
||||||
assetTableView.currentRow = matchedIndex;
|
|
||||||
assetTableView.prefix = newPrefix;
|
|
||||||
}
|
|
||||||
prefixClearTimer.restart();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
Keys.onPressed: {
|
|
||||||
switch (event.key) {
|
|
||||||
case Qt.Key_Backspace:
|
|
||||||
case Qt.Key_Tab:
|
|
||||||
case Qt.Key_Backtab:
|
|
||||||
event.accepted = false;
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
if (addToPrefix(event)) {
|
|
||||||
event.accepted = true
|
|
||||||
} else {
|
|
||||||
event.accepted = false;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
TextField {
|
|
||||||
id: currentSelection
|
|
||||||
label: selectDirectory ? "Directory:" : "File name:"
|
|
||||||
anchors {
|
|
||||||
left: parent.left
|
|
||||||
right: selectionType.visible ? selectionType.left: parent.right
|
|
||||||
rightMargin: selectionType.visible ? hifi.dimensions.contentSpacing.x : 0
|
|
||||||
bottom: buttonRow.top
|
|
||||||
bottomMargin: hifi.dimensions.contentSpacing.y
|
|
||||||
}
|
|
||||||
readOnly: true
|
|
||||||
activeFocusOnTab: !readOnly
|
|
||||||
onActiveFocusChanged: if (activeFocus) { selectAll(); }
|
|
||||||
onAccepted: okAction.trigger();
|
|
||||||
}
|
|
||||||
|
|
||||||
FileTypeSelection {
|
|
||||||
id: selectionType
|
|
||||||
anchors {
|
|
||||||
top: currentSelection.top
|
|
||||||
left: buttonRow.left
|
|
||||||
right: parent.right
|
|
||||||
}
|
|
||||||
visible: !selectDirectory && filtersCount > 1
|
|
||||||
KeyNavigation.left: assetTableView
|
|
||||||
KeyNavigation.right: openButton
|
|
||||||
}
|
|
||||||
|
|
||||||
Action {
|
|
||||||
id: okAction
|
|
||||||
text: currentSelection.text && root.selectDirectory && assetTableView.currentRow === -1 ? "Choose" : "Open"
|
|
||||||
enabled: currentSelection.text || !root.selectDirectory && d.currentSelectionIsFolder ? true : false
|
|
||||||
onTriggered: {
|
|
||||||
if (!root.selectDirectory && !d.currentSelectionIsFolder
|
|
||||||
|| root.selectDirectory && assetTableView.currentRow === -1) {
|
|
||||||
selectedAsset(d.currentSelectionPath);
|
|
||||||
root.destroy();
|
|
||||||
} else {
|
|
||||||
assetTableView.navigateToCurrentRow();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Action {
|
|
||||||
id: cancelAction
|
|
||||||
text: "Cancel"
|
|
||||||
onTriggered: {
|
|
||||||
canceled();
|
|
||||||
root.destroy();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Row {
|
|
||||||
id: buttonRow
|
|
||||||
anchors {
|
|
||||||
right: parent.right
|
|
||||||
bottom: parent.bottom
|
|
||||||
}
|
|
||||||
spacing: hifi.dimensions.contentSpacing.y
|
|
||||||
|
|
||||||
Button {
|
|
||||||
id: openButton
|
|
||||||
color: hifi.buttons.blue
|
|
||||||
action: okAction
|
|
||||||
Keys.onReturnPressed: okAction.trigger()
|
|
||||||
KeyNavigation.up: selectionType
|
|
||||||
KeyNavigation.left: selectionType
|
|
||||||
KeyNavigation.right: cancelButton
|
|
||||||
}
|
|
||||||
|
|
||||||
Button {
|
|
||||||
id: cancelButton
|
|
||||||
action: cancelAction
|
|
||||||
KeyNavigation.up: selectionType
|
|
||||||
KeyNavigation.left: openButton
|
|
||||||
KeyNavigation.right: assetTableView.contentItem
|
|
||||||
Keys.onReturnPressed: { canceled(); root.enabled = false }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Keys.onPressed: {
|
|
||||||
switch (event.key) {
|
|
||||||
case Qt.Key_Backspace:
|
|
||||||
event.accepted = d.navigateUp();
|
|
||||||
break;
|
|
||||||
|
|
||||||
case Qt.Key_Home:
|
|
||||||
event.accepted = d.navigateHome();
|
|
||||||
break;
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -129,7 +129,7 @@ Rectangle {
|
||||||
}
|
}
|
||||||
|
|
||||||
onAppInstalled: {
|
onAppInstalled: {
|
||||||
if (appHref === root.itemHref) {
|
if (appID === root.itemId) {
|
||||||
root.isInstalled = true;
|
root.isInstalled = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -67,13 +67,13 @@ Item {
|
||||||
}
|
}
|
||||||
|
|
||||||
onAppInstalled: {
|
onAppInstalled: {
|
||||||
if (appHref === root.itemHref) {
|
if (appID === root.itemId) {
|
||||||
root.isInstalled = true;
|
root.isInstalled = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onAppUninstalled: {
|
onAppUninstalled: {
|
||||||
if (appHref === root.itemHref) {
|
if (appID === root.itemId) {
|
||||||
root.isInstalled = false;
|
root.isInstalled = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -98,7 +98,7 @@ Rectangle {
|
||||||
}
|
}
|
||||||
|
|
||||||
onAppInstalled: {
|
onAppInstalled: {
|
||||||
root.installedApps = Commerce.getInstalledApps();
|
root.installedApps = Commerce.getInstalledApps(appID);
|
||||||
}
|
}
|
||||||
|
|
||||||
onAppUninstalled: {
|
onAppUninstalled: {
|
||||||
|
|
|
@ -35,6 +35,10 @@ void AndroidHelper::notifyEnterForeground() {
|
||||||
emit enterForeground();
|
emit enterForeground();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void AndroidHelper::notifyBeforeEnterBackground() {
|
||||||
|
emit beforeEnterBackground();
|
||||||
|
}
|
||||||
|
|
||||||
void AndroidHelper::notifyEnterBackground() {
|
void AndroidHelper::notifyEnterBackground() {
|
||||||
emit enterBackground();
|
emit enterBackground();
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,6 +24,7 @@ public:
|
||||||
void requestActivity(const QString &activityName, const bool backToScene, QList<QString> args = QList<QString>());
|
void requestActivity(const QString &activityName, const bool backToScene, QList<QString> args = QList<QString>());
|
||||||
void notifyLoadComplete();
|
void notifyLoadComplete();
|
||||||
void notifyEnterForeground();
|
void notifyEnterForeground();
|
||||||
|
void notifyBeforeEnterBackground();
|
||||||
void notifyEnterBackground();
|
void notifyEnterBackground();
|
||||||
|
|
||||||
void performHapticFeedback(int duration);
|
void performHapticFeedback(int duration);
|
||||||
|
@ -39,6 +40,7 @@ signals:
|
||||||
void androidActivityRequested(const QString &activityName, const bool backToScene, QList<QString> args = QList<QString>());
|
void androidActivityRequested(const QString &activityName, const bool backToScene, QList<QString> args = QList<QString>());
|
||||||
void qtAppLoadComplete();
|
void qtAppLoadComplete();
|
||||||
void enterForeground();
|
void enterForeground();
|
||||||
|
void beforeEnterBackground();
|
||||||
void enterBackground();
|
void enterBackground();
|
||||||
|
|
||||||
void hapticFeedbackRequested(int duration);
|
void hapticFeedbackRequested(int duration);
|
||||||
|
|
|
@ -2258,6 +2258,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
|
||||||
qCDebug(interfaceapp) << "Metaverse session ID is" << uuidStringWithoutCurlyBraces(accountManager->getSessionID());
|
qCDebug(interfaceapp) << "Metaverse session ID is" << uuidStringWithoutCurlyBraces(accountManager->getSessionID());
|
||||||
|
|
||||||
#if defined(Q_OS_ANDROID)
|
#if defined(Q_OS_ANDROID)
|
||||||
|
connect(&AndroidHelper::instance(), &AndroidHelper::beforeEnterBackground, this, &Application::beforeEnterBackground);
|
||||||
connect(&AndroidHelper::instance(), &AndroidHelper::enterBackground, this, &Application::enterBackground);
|
connect(&AndroidHelper::instance(), &AndroidHelper::enterBackground, this, &Application::enterBackground);
|
||||||
connect(&AndroidHelper::instance(), &AndroidHelper::enterForeground, this, &Application::enterForeground);
|
connect(&AndroidHelper::instance(), &AndroidHelper::enterForeground, this, &Application::enterForeground);
|
||||||
AndroidHelper::instance().notifyLoadComplete();
|
AndroidHelper::instance().notifyLoadComplete();
|
||||||
|
@ -3220,6 +3221,7 @@ void Application::setSettingConstrainToolbarPosition(bool setting) {
|
||||||
void Application::showHelp() {
|
void Application::showHelp() {
|
||||||
static const QString HAND_CONTROLLER_NAME_VIVE = "vive";
|
static const QString HAND_CONTROLLER_NAME_VIVE = "vive";
|
||||||
static const QString HAND_CONTROLLER_NAME_OCULUS_TOUCH = "oculus";
|
static const QString HAND_CONTROLLER_NAME_OCULUS_TOUCH = "oculus";
|
||||||
|
static const QString HAND_CONTROLLER_NAME_WINDOWS_MR = "windowsMR";
|
||||||
|
|
||||||
static const QString TAB_KEYBOARD_MOUSE = "kbm";
|
static const QString TAB_KEYBOARD_MOUSE = "kbm";
|
||||||
static const QString TAB_GAMEPAD = "gamepad";
|
static const QString TAB_GAMEPAD = "gamepad";
|
||||||
|
@ -3234,9 +3236,13 @@ void Application::showHelp() {
|
||||||
} else if (PluginUtils::isOculusTouchControllerAvailable()) {
|
} else if (PluginUtils::isOculusTouchControllerAvailable()) {
|
||||||
defaultTab = TAB_HAND_CONTROLLERS;
|
defaultTab = TAB_HAND_CONTROLLERS;
|
||||||
handControllerName = HAND_CONTROLLER_NAME_OCULUS_TOUCH;
|
handControllerName = HAND_CONTROLLER_NAME_OCULUS_TOUCH;
|
||||||
|
} else if (qApp->getActiveDisplayPlugin()->getName() == "WindowMS") {
|
||||||
|
defaultTab = TAB_HAND_CONTROLLERS;
|
||||||
|
handControllerName = HAND_CONTROLLER_NAME_WINDOWS_MR;
|
||||||
} else if (PluginUtils::isXboxControllerAvailable()) {
|
} else if (PluginUtils::isXboxControllerAvailable()) {
|
||||||
defaultTab = TAB_GAMEPAD;
|
defaultTab = TAB_GAMEPAD;
|
||||||
}
|
}
|
||||||
|
// TODO need some way to detect windowsMR to load controls reference default tab in Help > Controls Reference menu.
|
||||||
|
|
||||||
QUrlQuery queryString;
|
QUrlQuery queryString;
|
||||||
queryString.addQueryItem("handControllerName", handControllerName);
|
queryString.addQueryItem("handControllerName", handControllerName);
|
||||||
|
@ -3262,13 +3268,22 @@ void Application::resizeGL() {
|
||||||
// Set the desired FBO texture size. If it hasn't changed, this does nothing.
|
// Set the desired FBO texture size. If it hasn't changed, this does nothing.
|
||||||
// Otherwise, it must rebuild the FBOs
|
// Otherwise, it must rebuild the FBOs
|
||||||
uvec2 framebufferSize = displayPlugin->getRecommendedRenderSize();
|
uvec2 framebufferSize = displayPlugin->getRecommendedRenderSize();
|
||||||
float renderResolutionScale = getRenderResolutionScale();
|
uvec2 renderSize = uvec2(framebufferSize);
|
||||||
uvec2 renderSize = uvec2(vec2(framebufferSize) * renderResolutionScale);
|
|
||||||
if (_renderResolution != renderSize) {
|
if (_renderResolution != renderSize) {
|
||||||
_renderResolution = renderSize;
|
_renderResolution = renderSize;
|
||||||
DependencyManager::get<FramebufferCache>()->setFrameBufferSize(fromGlm(renderSize));
|
DependencyManager::get<FramebufferCache>()->setFrameBufferSize(fromGlm(renderSize));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto renderResolutionScale = getRenderResolutionScale();
|
||||||
|
if (displayPlugin->getRenderResolutionScale() != renderResolutionScale) {
|
||||||
|
auto renderConfig = _renderEngine->getConfiguration();
|
||||||
|
assert(renderConfig);
|
||||||
|
auto mainView = renderConfig->getConfig("RenderMainView.RenderDeferredTask");
|
||||||
|
assert(mainView);
|
||||||
|
mainView->setProperty("resolutionScale", renderResolutionScale);
|
||||||
|
displayPlugin->setRenderResolutionScale(renderResolutionScale);
|
||||||
|
}
|
||||||
|
|
||||||
// FIXME the aspect ratio for stereo displays is incorrect based on this.
|
// FIXME the aspect ratio for stereo displays is incorrect based on this.
|
||||||
float aspectRatio = displayPlugin->getRecommendedAspectRatio();
|
float aspectRatio = displayPlugin->getRecommendedAspectRatio();
|
||||||
_myCamera.setProjection(glm::perspective(glm::radians(_fieldOfView.get()), aspectRatio,
|
_myCamera.setProjection(glm::perspective(glm::radians(_fieldOfView.get()), aspectRatio,
|
||||||
|
@ -3280,7 +3295,6 @@ void Application::resizeGL() {
|
||||||
}
|
}
|
||||||
|
|
||||||
DependencyManager::get<OffscreenUi>()->resize(fromGlm(displayPlugin->getRecommendedUiSize()));
|
DependencyManager::get<OffscreenUi>()->resize(fromGlm(displayPlugin->getRecommendedUiSize()));
|
||||||
displayPlugin->setRenderResolutionScale(renderResolutionScale);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Application::handleSandboxStatus(QNetworkReply* reply) {
|
void Application::handleSandboxStatus(QNetworkReply* reply) {
|
||||||
|
@ -8329,6 +8343,13 @@ void Application::copyToClipboard(const QString& text) {
|
||||||
}
|
}
|
||||||
|
|
||||||
#if defined(Q_OS_ANDROID)
|
#if defined(Q_OS_ANDROID)
|
||||||
|
void Application::beforeEnterBackground() {
|
||||||
|
auto nodeList = DependencyManager::get<NodeList>();
|
||||||
|
nodeList->setSendDomainServerCheckInEnabled(false);
|
||||||
|
nodeList->reset(true);
|
||||||
|
clearDomainOctreeDetails();
|
||||||
|
}
|
||||||
|
|
||||||
void Application::enterBackground() {
|
void Application::enterBackground() {
|
||||||
QMetaObject::invokeMethod(DependencyManager::get<AudioClient>().data(),
|
QMetaObject::invokeMethod(DependencyManager::get<AudioClient>().data(),
|
||||||
"stop", Qt::BlockingQueuedConnection);
|
"stop", Qt::BlockingQueuedConnection);
|
||||||
|
@ -8343,6 +8364,8 @@ void Application::enterForeground() {
|
||||||
if (!getActiveDisplayPlugin() || getActiveDisplayPlugin()->isActive() || !getActiveDisplayPlugin()->activate()) {
|
if (!getActiveDisplayPlugin() || getActiveDisplayPlugin()->isActive() || !getActiveDisplayPlugin()->activate()) {
|
||||||
qWarning() << "Could not re-activate display plugin";
|
qWarning() << "Could not re-activate display plugin";
|
||||||
}
|
}
|
||||||
|
auto nodeList = DependencyManager::get<NodeList>();
|
||||||
|
nodeList->setSendDomainServerCheckInEnabled(true);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
|
@ -313,6 +313,7 @@ public:
|
||||||
Q_INVOKABLE void copyToClipboard(const QString& text);
|
Q_INVOKABLE void copyToClipboard(const QString& text);
|
||||||
|
|
||||||
#if defined(Q_OS_ANDROID)
|
#if defined(Q_OS_ANDROID)
|
||||||
|
void beforeEnterBackground();
|
||||||
void enterBackground();
|
void enterBackground();
|
||||||
void enterForeground();
|
void enterForeground();
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -102,7 +102,7 @@ void Application::paintGL() {
|
||||||
PerformanceTimer perfTimer("renderOverlay");
|
PerformanceTimer perfTimer("renderOverlay");
|
||||||
// NOTE: There is no batch associated with this renderArgs
|
// NOTE: There is no batch associated with this renderArgs
|
||||||
// the ApplicationOverlay class assumes it's viewport is setup to be the device size
|
// the ApplicationOverlay class assumes it's viewport is setup to be the device size
|
||||||
renderArgs._viewport = glm::ivec4(0, 0, getDeviceSize() * getRenderResolutionScale());
|
renderArgs._viewport = glm::ivec4(0, 0, getDeviceSize());
|
||||||
_applicationOverlay.renderOverlay(&renderArgs);
|
_applicationOverlay.renderOverlay(&renderArgs);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -118,7 +118,7 @@ void Application::paintGL() {
|
||||||
// Primary rendering pass
|
// Primary rendering pass
|
||||||
auto framebufferCache = DependencyManager::get<FramebufferCache>();
|
auto framebufferCache = DependencyManager::get<FramebufferCache>();
|
||||||
finalFramebufferSize = framebufferCache->getFrameBufferSize();
|
finalFramebufferSize = framebufferCache->getFrameBufferSize();
|
||||||
// Final framebuffer that will be handled to the display-plugin
|
// Final framebuffer that will be handed to the display-plugin
|
||||||
finalFramebuffer = framebufferCache->getFramebuffer();
|
finalFramebuffer = framebufferCache->getFramebuffer();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -208,7 +208,7 @@ void QmlCommerce::alreadyOwned(const QString& marketplaceId) {
|
||||||
ledger->alreadyOwned(marketplaceId);
|
ledger->alreadyOwned(marketplaceId);
|
||||||
}
|
}
|
||||||
|
|
||||||
QString QmlCommerce::getInstalledApps() {
|
QString QmlCommerce::getInstalledApps(const QString& justInstalledAppID) {
|
||||||
QString installedAppsFromMarketplace;
|
QString installedAppsFromMarketplace;
|
||||||
QStringList runningScripts = DependencyManager::get<ScriptEngines>()->getRunningScripts();
|
QStringList runningScripts = DependencyManager::get<ScriptEngines>()->getRunningScripts();
|
||||||
|
|
||||||
|
@ -217,6 +217,18 @@ QString QmlCommerce::getInstalledApps() {
|
||||||
foreach(QString appFileName, apps) {
|
foreach(QString appFileName, apps) {
|
||||||
installedAppsFromMarketplace += appFileName;
|
installedAppsFromMarketplace += appFileName;
|
||||||
installedAppsFromMarketplace += ",";
|
installedAppsFromMarketplace += ",";
|
||||||
|
|
||||||
|
// If we were supplied a "justInstalledAppID" argument, that means we're entering this function
|
||||||
|
// to get the new list of installed apps immediately after installing an app.
|
||||||
|
// In that case, the app we installed may not yet have its associated script running -
|
||||||
|
// that task is asynchronous and takes a nonzero amount of time. This is especially true
|
||||||
|
// for apps that are not in Interface's script cache.
|
||||||
|
// Thus, we protect against deleting the .app.json from the user's disk (below)
|
||||||
|
// by skipping that check for the app we just installed.
|
||||||
|
if ((justInstalledAppID != "") && ((justInstalledAppID + ".app.json") == appFileName)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
QFile appFile(_appsPath + appFileName);
|
QFile appFile(_appsPath + appFileName);
|
||||||
if (appFile.open(QIODevice::ReadOnly)) {
|
if (appFile.open(QIODevice::ReadOnly)) {
|
||||||
QJsonDocument appFileJsonDocument = QJsonDocument::fromJson(appFile.readAll());
|
QJsonDocument appFileJsonDocument = QJsonDocument::fromJson(appFile.readAll());
|
||||||
|
@ -291,7 +303,8 @@ bool QmlCommerce::installApp(const QString& itemHref) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
emit appInstalled(itemHref);
|
QFileInfo appFileInfo(appFile);
|
||||||
|
emit appInstalled(appFileInfo.baseName());
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
request->send();
|
request->send();
|
||||||
|
@ -321,7 +334,8 @@ bool QmlCommerce::uninstallApp(const QString& itemHref) {
|
||||||
qCWarning(commerce) << "Couldn't delete local .app.json file during app uninstall. Continuing anyway. App filename is:" << appHref.fileName();
|
qCWarning(commerce) << "Couldn't delete local .app.json file during app uninstall. Continuing anyway. App filename is:" << appHref.fileName();
|
||||||
}
|
}
|
||||||
|
|
||||||
emit appUninstalled(itemHref);
|
QFileInfo appFileInfo(appFile);
|
||||||
|
emit appUninstalled(appFileInfo.baseName());
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -53,8 +53,8 @@ signals:
|
||||||
|
|
||||||
void contentSetChanged(const QString& contentSetHref);
|
void contentSetChanged(const QString& contentSetHref);
|
||||||
|
|
||||||
void appInstalled(const QString& appHref);
|
void appInstalled(const QString& appID);
|
||||||
void appUninstalled(const QString& appHref);
|
void appUninstalled(const QString& appID);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
Q_INVOKABLE void getWalletStatus();
|
Q_INVOKABLE void getWalletStatus();
|
||||||
|
@ -86,7 +86,7 @@ protected:
|
||||||
|
|
||||||
Q_INVOKABLE void replaceContentSet(const QString& itemHref, const QString& certificateID);
|
Q_INVOKABLE void replaceContentSet(const QString& itemHref, const QString& certificateID);
|
||||||
|
|
||||||
Q_INVOKABLE QString getInstalledApps();
|
Q_INVOKABLE QString getInstalledApps(const QString& justInstalledAppID = "");
|
||||||
Q_INVOKABLE bool installApp(const QString& appHref);
|
Q_INVOKABLE bool installApp(const QString& appHref);
|
||||||
Q_INVOKABLE bool uninstallApp(const QString& appHref);
|
Q_INVOKABLE bool uninstallApp(const QString& appHref);
|
||||||
Q_INVOKABLE bool openApp(const QString& appHref);
|
Q_INVOKABLE bool openApp(const QString& appHref);
|
||||||
|
|
|
@ -86,10 +86,6 @@ void WindowScriptingInterface::raise() {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void WindowScriptingInterface::raiseMainWindow() {
|
|
||||||
raise();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Display an alert box
|
/// Display an alert box
|
||||||
/// \param const QString& message message to display
|
/// \param const QString& message message to display
|
||||||
/// \return QScriptValue::UndefinedValue
|
/// \return QScriptValue::UndefinedValue
|
||||||
|
|
|
@ -80,13 +80,6 @@ public slots:
|
||||||
*/
|
*/
|
||||||
void raise();
|
void raise();
|
||||||
|
|
||||||
/**jsdoc
|
|
||||||
* Raise the Interface window if it is minimized. If raised, the window gains focus.
|
|
||||||
* @function Window.raiseMainWindow
|
|
||||||
* @deprecated Use {@link Window.raise|raise} instead.
|
|
||||||
*/
|
|
||||||
void raiseMainWindow();
|
|
||||||
|
|
||||||
/**jsdoc
|
/**jsdoc
|
||||||
* Display a dialog with the specified message and an "OK" button. The dialog is non-modal; the script continues without
|
* Display a dialog with the specified message and an "OK" button. The dialog is non-modal; the script continues without
|
||||||
* waiting for a user response.
|
* waiting for a user response.
|
||||||
|
|
|
@ -179,7 +179,7 @@ static const auto DEPTH_FORMAT = gpu::Element(gpu::SCALAR, gpu::FLOAT, gpu::DEPT
|
||||||
void ApplicationOverlay::buildFramebufferObject() {
|
void ApplicationOverlay::buildFramebufferObject() {
|
||||||
PROFILE_RANGE(app, __FUNCTION__);
|
PROFILE_RANGE(app, __FUNCTION__);
|
||||||
|
|
||||||
auto uiSize = glm::uvec2(glm::vec2(qApp->getUiSize()) * qApp->getRenderResolutionScale());
|
auto uiSize = glm::uvec2(qApp->getUiSize());
|
||||||
if (!_overlayFramebuffer || uiSize != _overlayFramebuffer->getSize()) {
|
if (!_overlayFramebuffer || uiSize != _overlayFramebuffer->getSize()) {
|
||||||
_overlayFramebuffer = gpu::FramebufferPointer(gpu::Framebuffer::create("ApplicationOverlay"));
|
_overlayFramebuffer = gpu::FramebufferPointer(gpu::Framebuffer::create("ApplicationOverlay"));
|
||||||
}
|
}
|
||||||
|
|
|
@ -116,7 +116,7 @@ void Overlays::renderHUD(RenderArgs* renderArgs) {
|
||||||
auto geometryCache = DependencyManager::get<GeometryCache>();
|
auto geometryCache = DependencyManager::get<GeometryCache>();
|
||||||
auto textureCache = DependencyManager::get<TextureCache>();
|
auto textureCache = DependencyManager::get<TextureCache>();
|
||||||
|
|
||||||
auto size = glm::uvec2(glm::vec2(qApp->getUiSize()) * qApp->getRenderResolutionScale());
|
auto size = glm::uvec2(qApp->getUiSize());
|
||||||
int width = size.x;
|
int width = size.x;
|
||||||
int height = size.y;
|
int height = size.y;
|
||||||
mat4 legacyProjection = glm::ortho<float>(0, width, height, 0, -1000, 1000);
|
mat4 legacyProjection = glm::ortho<float>(0, width, height, 0, -1000, 1000);
|
||||||
|
|
|
@ -275,7 +275,7 @@ bool CompositorHelper::getReticleOverDesktop() const {
|
||||||
// as being over the desktop.
|
// as being over the desktop.
|
||||||
if (isHMD()) {
|
if (isHMD()) {
|
||||||
QMutexLocker locker(&_reticleLock);
|
QMutexLocker locker(&_reticleLock);
|
||||||
glm::vec2 maxOverlayPosition = glm::vec2(_currentDisplayPlugin->getRecommendedUiSize()) * _currentDisplayPlugin->getRenderResolutionScale();
|
glm::vec2 maxOverlayPosition = glm::vec2(_currentDisplayPlugin->getRecommendedUiSize());
|
||||||
static const glm::vec2 minOverlayPosition;
|
static const glm::vec2 minOverlayPosition;
|
||||||
if (glm::any(glm::lessThan(_reticlePositionInHMD, minOverlayPosition)) ||
|
if (glm::any(glm::lessThan(_reticlePositionInHMD, minOverlayPosition)) ||
|
||||||
glm::any(glm::greaterThan(_reticlePositionInHMD, maxOverlayPosition))) {
|
glm::any(glm::greaterThan(_reticlePositionInHMD, maxOverlayPosition))) {
|
||||||
|
@ -317,7 +317,7 @@ void CompositorHelper::sendFakeMouseEvent() {
|
||||||
|
|
||||||
void CompositorHelper::setReticlePosition(const glm::vec2& position, bool sendFakeEvent) {
|
void CompositorHelper::setReticlePosition(const glm::vec2& position, bool sendFakeEvent) {
|
||||||
if (isHMD()) {
|
if (isHMD()) {
|
||||||
glm::vec2 maxOverlayPosition = glm::vec2(_currentDisplayPlugin->getRecommendedUiSize()) * _currentDisplayPlugin->getRenderResolutionScale();
|
glm::vec2 maxOverlayPosition = glm::vec2(_currentDisplayPlugin->getRecommendedUiSize());
|
||||||
// FIXME don't allow negative mouseExtra
|
// FIXME don't allow negative mouseExtra
|
||||||
glm::vec2 mouseExtra = (MOUSE_EXTENTS_PIXELS - maxOverlayPosition) / 2.0f;
|
glm::vec2 mouseExtra = (MOUSE_EXTENTS_PIXELS - maxOverlayPosition) / 2.0f;
|
||||||
glm::vec2 minMouse = vec2(0) - mouseExtra;
|
glm::vec2 minMouse = vec2(0) - mouseExtra;
|
||||||
|
|
|
@ -888,7 +888,7 @@ OpenGLDisplayPlugin::~OpenGLDisplayPlugin() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void OpenGLDisplayPlugin::updateCompositeFramebuffer() {
|
void OpenGLDisplayPlugin::updateCompositeFramebuffer() {
|
||||||
auto renderSize = glm::uvec2(glm::vec2(getRecommendedRenderSize()) * getRenderResolutionScale());
|
auto renderSize = glm::uvec2(getRecommendedRenderSize());
|
||||||
if (!_compositeFramebuffer || _compositeFramebuffer->getSize() != renderSize) {
|
if (!_compositeFramebuffer || _compositeFramebuffer->getSize() != renderSize) {
|
||||||
_compositeFramebuffer = gpu::FramebufferPointer(gpu::Framebuffer::create("OpenGLDisplayPlugin::composite", gpu::Element::COLOR_RGBA_32, renderSize.x, renderSize.y));
|
_compositeFramebuffer = gpu::FramebufferPointer(gpu::Framebuffer::create("OpenGLDisplayPlugin::composite", gpu::Element::COLOR_RGBA_32, renderSize.x, renderSize.y));
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,13 +22,14 @@ const float AnimationPropertyGroup::MAXIMUM_POSSIBLE_FRAME = 100000.0f;
|
||||||
|
|
||||||
bool operator==(const AnimationPropertyGroup& a, const AnimationPropertyGroup& b) {
|
bool operator==(const AnimationPropertyGroup& a, const AnimationPropertyGroup& b) {
|
||||||
return
|
return
|
||||||
|
|
||||||
(a._currentFrame == b._currentFrame) &&
|
(a._currentFrame == b._currentFrame) &&
|
||||||
(a._running == b._running) &&
|
(a._running == b._running) &&
|
||||||
(a._loop == b._loop) &&
|
(a._loop == b._loop) &&
|
||||||
(a._hold == b._hold) &&
|
(a._hold == b._hold) &&
|
||||||
(a._firstFrame == b._firstFrame) &&
|
(a._firstFrame == b._firstFrame) &&
|
||||||
(a._lastFrame == b._lastFrame) &&
|
(a._lastFrame == b._lastFrame) &&
|
||||||
|
(a._fps == b._fps) &&
|
||||||
|
(a._allowTranslation == b._allowTranslation) &&
|
||||||
(a._url == b._url);
|
(a._url == b._url);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -40,6 +41,8 @@ bool operator!=(const AnimationPropertyGroup& a, const AnimationPropertyGroup& b
|
||||||
(a._hold != b._hold) ||
|
(a._hold != b._hold) ||
|
||||||
(a._firstFrame != b._firstFrame) ||
|
(a._firstFrame != b._firstFrame) ||
|
||||||
(a._lastFrame != b._lastFrame) ||
|
(a._lastFrame != b._lastFrame) ||
|
||||||
|
(a._fps != b._fps) ||
|
||||||
|
(a._allowTranslation != b._allowTranslation) ||
|
||||||
(a._url != b._url);
|
(a._url != b._url);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -289,6 +289,12 @@ void NodeList::addSetOfNodeTypesToNodeInterestSet(const NodeSet& setOfNodeTypes)
|
||||||
}
|
}
|
||||||
|
|
||||||
void NodeList::sendDomainServerCheckIn() {
|
void NodeList::sendDomainServerCheckIn() {
|
||||||
|
|
||||||
|
if (!_sendDomainServerCheckInEnabled) {
|
||||||
|
qCDebug(networking) << "Refusing to send a domain-server check in while it is disabled.";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (thread() != QThread::currentThread()) {
|
if (thread() != QThread::currentThread()) {
|
||||||
QMetaObject::invokeMethod(this, "sendDomainServerCheckIn", Qt::QueuedConnection);
|
QMetaObject::invokeMethod(this, "sendDomainServerCheckIn", Qt::QueuedConnection);
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -90,6 +90,9 @@ public:
|
||||||
bool getRequestsDomainListData() { return _requestsDomainListData; }
|
bool getRequestsDomainListData() { return _requestsDomainListData; }
|
||||||
void setRequestsDomainListData(bool isRequesting);
|
void setRequestsDomainListData(bool isRequesting);
|
||||||
|
|
||||||
|
bool getSendDomainServerCheckInEnabled() { return _sendDomainServerCheckInEnabled; }
|
||||||
|
void setSendDomainServerCheckInEnabled(bool enabled) { _sendDomainServerCheckInEnabled = enabled; }
|
||||||
|
|
||||||
void removeFromIgnoreMuteSets(const QUuid& nodeID);
|
void removeFromIgnoreMuteSets(const QUuid& nodeID);
|
||||||
|
|
||||||
virtual bool isDomainServer() const override { return false; }
|
virtual bool isDomainServer() const override { return false; }
|
||||||
|
@ -169,6 +172,8 @@ private:
|
||||||
QTimer _keepAlivePingTimer;
|
QTimer _keepAlivePingTimer;
|
||||||
bool _requestsDomainListData { false };
|
bool _requestsDomainListData { false };
|
||||||
|
|
||||||
|
bool _sendDomainServerCheckInEnabled { true };
|
||||||
|
|
||||||
mutable QReadWriteLock _ignoredSetLock;
|
mutable QReadWriteLock _ignoredSetLock;
|
||||||
tbb::concurrent_unordered_set<QUuid, UUIDHasher> _ignoredNodeIDs;
|
tbb::concurrent_unordered_set<QUuid, UUIDHasher> _ignoredNodeIDs;
|
||||||
mutable QReadWriteLock _personalMutedSetLock;
|
mutable QReadWriteLock _personalMutedSetLock;
|
||||||
|
|
|
@ -262,24 +262,53 @@ void CharacterController::playerStep(btCollisionWorld* collisionWorld, btScalar
|
||||||
btVector3 linearDisplacement = clampLength(vel * dt, MAX_DISPLACEMENT); // clamp displacement to prevent tunneling.
|
btVector3 linearDisplacement = clampLength(vel * dt, MAX_DISPLACEMENT); // clamp displacement to prevent tunneling.
|
||||||
btVector3 endPos = startPos + linearDisplacement;
|
btVector3 endPos = startPos + linearDisplacement;
|
||||||
|
|
||||||
|
// resolve the simple linearDisplacement
|
||||||
|
_followLinearDisplacement += linearDisplacement;
|
||||||
|
|
||||||
|
// now for the rotational part...
|
||||||
btQuaternion startRot = bodyTransform.getRotation();
|
btQuaternion startRot = bodyTransform.getRotation();
|
||||||
btQuaternion desiredRot = _followDesiredBodyTransform.getRotation();
|
btQuaternion desiredRot = _followDesiredBodyTransform.getRotation();
|
||||||
if (desiredRot.dot(startRot) < 0.0f) {
|
|
||||||
desiredRot = -desiredRot;
|
// startRot as default rotation
|
||||||
|
btQuaternion endRot = startRot;
|
||||||
|
|
||||||
|
// the dot product between two quaternions is equal to +/- cos(angle/2)
|
||||||
|
// where 'angle' is that of the rotation between them
|
||||||
|
float qDot = desiredRot.dot(startRot);
|
||||||
|
|
||||||
|
// when the abs() value of the dot product is approximately 1.0
|
||||||
|
// then the two rotations are effectively adjacent
|
||||||
|
const float MIN_DOT_PRODUCT_OF_ADJACENT_QUATERNIONS = 0.99999f; // corresponds to approx 0.5 degrees
|
||||||
|
if (fabsf(qDot) < MIN_DOT_PRODUCT_OF_ADJACENT_QUATERNIONS) {
|
||||||
|
if (qDot < 0.0f) {
|
||||||
|
// the quaternions are actually on opposite hyperhemispheres
|
||||||
|
// so we move one to agree with the other and negate qDot
|
||||||
|
desiredRot = -desiredRot;
|
||||||
|
qDot = -qDot;
|
||||||
|
}
|
||||||
|
btQuaternion deltaRot = desiredRot * startRot.inverse();
|
||||||
|
|
||||||
|
// the axis is the imaginary part, but scaled by sin(angle/2)
|
||||||
|
btVector3 axis(deltaRot.getX(), deltaRot.getY(), deltaRot.getZ());
|
||||||
|
axis /= sqrtf(1.0f - qDot * qDot);
|
||||||
|
|
||||||
|
// compute the angle we will resolve for this dt, but don't overshoot
|
||||||
|
float angle = 2.0f * acosf(qDot);
|
||||||
|
if ( dt < _followTimeRemaining) {
|
||||||
|
angle *= dt / _followTimeRemaining;
|
||||||
|
}
|
||||||
|
|
||||||
|
// accumulate rotation
|
||||||
|
deltaRot = btQuaternion(axis, angle);
|
||||||
|
_followAngularDisplacement = (deltaRot * _followAngularDisplacement).normalize();
|
||||||
|
|
||||||
|
// in order to accumulate displacement of avatar position, we need to take _shapeLocalOffset into account.
|
||||||
|
btVector3 shapeLocalOffset = glmToBullet(_shapeLocalOffset);
|
||||||
|
|
||||||
|
endRot = deltaRot * startRot;
|
||||||
|
btVector3 swingDisplacement = rotateVector(endRot, -shapeLocalOffset) - rotateVector(startRot, -shapeLocalOffset);
|
||||||
|
_followLinearDisplacement += swingDisplacement;
|
||||||
}
|
}
|
||||||
btQuaternion deltaRot = desiredRot * startRot.inverse();
|
|
||||||
float angularSpeed = deltaRot.getAngle() / _followTimeRemaining;
|
|
||||||
glm::vec3 rotationAxis = glm::normalize(glm::axis(bulletToGLM(deltaRot))); // deltaRot.getAxis() is inaccurate
|
|
||||||
btQuaternion angularDisplacement = btQuaternion(glmToBullet(rotationAxis), angularSpeed * dt);
|
|
||||||
btQuaternion endRot = angularDisplacement * startRot;
|
|
||||||
|
|
||||||
// in order to accumulate displacement of avatar position, we need to take _shapeLocalOffset into account.
|
|
||||||
btVector3 shapeLocalOffset = glmToBullet(_shapeLocalOffset);
|
|
||||||
btVector3 swingDisplacement = rotateVector(endRot, -shapeLocalOffset) - rotateVector(startRot, -shapeLocalOffset);
|
|
||||||
|
|
||||||
_followLinearDisplacement = linearDisplacement + swingDisplacement + _followLinearDisplacement;
|
|
||||||
_followAngularDisplacement = angularDisplacement * _followAngularDisplacement;
|
|
||||||
|
|
||||||
_rigidBody->setWorldTransform(btTransform(endRot, endPos));
|
_rigidBody->setWorldTransform(btTransform(endRot, endPos));
|
||||||
}
|
}
|
||||||
_followTime += dt;
|
_followTime += dt;
|
||||||
|
|
|
@ -393,34 +393,42 @@ graphics::MeshPointer DeferredLightingEffect::getSpotLightMesh() {
|
||||||
return _spotLightMesh;
|
return _spotLightMesh;
|
||||||
}
|
}
|
||||||
|
|
||||||
void PreparePrimaryFramebuffer::run(const RenderContextPointer& renderContext, gpu::FramebufferPointer& primaryFramebuffer) {
|
gpu::FramebufferPointer PreparePrimaryFramebuffer::createFramebuffer(const char* name, const glm::uvec2& frameSize) {
|
||||||
|
gpu::FramebufferPointer framebuffer = gpu::FramebufferPointer(gpu::Framebuffer::create(name));
|
||||||
|
auto colorFormat = gpu::Element::COLOR_SRGBA_32;
|
||||||
|
|
||||||
|
auto defaultSampler = gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_LINEAR);
|
||||||
|
auto primaryColorTexture = gpu::Texture::createRenderBuffer(colorFormat, frameSize.x, frameSize.y, gpu::Texture::SINGLE_MIP, defaultSampler);
|
||||||
|
|
||||||
|
framebuffer->setRenderBuffer(0, primaryColorTexture);
|
||||||
|
|
||||||
|
auto depthFormat = gpu::Element(gpu::SCALAR, gpu::UINT32, gpu::DEPTH_STENCIL); // Depth24_Stencil8 texel format
|
||||||
|
auto primaryDepthTexture = gpu::Texture::createRenderBuffer(depthFormat, frameSize.x, frameSize.y, gpu::Texture::SINGLE_MIP, defaultSampler);
|
||||||
|
|
||||||
|
framebuffer->setDepthStencilBuffer(primaryDepthTexture, depthFormat);
|
||||||
|
|
||||||
|
return framebuffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PreparePrimaryFramebuffer::configure(const Config& config) {
|
||||||
|
_resolutionScale = config.resolutionScale;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PreparePrimaryFramebuffer::run(const RenderContextPointer& renderContext, Output& primaryFramebuffer) {
|
||||||
glm::uvec2 frameSize(renderContext->args->_viewport.z, renderContext->args->_viewport.w);
|
glm::uvec2 frameSize(renderContext->args->_viewport.z, renderContext->args->_viewport.w);
|
||||||
|
glm::uvec2 scaledFrameSize(glm::vec2(frameSize) * _resolutionScale);
|
||||||
|
|
||||||
// Resizing framebuffers instead of re-building them seems to cause issues with threaded
|
// Resizing framebuffers instead of re-building them seems to cause issues with threaded
|
||||||
// rendering
|
// rendering
|
||||||
if (_primaryFramebuffer && _primaryFramebuffer->getSize() != frameSize) {
|
if (!_primaryFramebuffer || _primaryFramebuffer->getSize() != scaledFrameSize) {
|
||||||
_primaryFramebuffer.reset();
|
_primaryFramebuffer = createFramebuffer("deferredPrimary", scaledFrameSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!_primaryFramebuffer) {
|
|
||||||
_primaryFramebuffer = gpu::FramebufferPointer(gpu::Framebuffer::create("deferredPrimary"));
|
|
||||||
auto colorFormat = gpu::Element::COLOR_SRGBA_32;
|
|
||||||
|
|
||||||
auto defaultSampler = gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_POINT);
|
|
||||||
auto primaryColorTexture = gpu::Texture::createRenderBuffer(colorFormat, frameSize.x, frameSize.y, gpu::Texture::SINGLE_MIP, defaultSampler);
|
|
||||||
|
|
||||||
|
|
||||||
_primaryFramebuffer->setRenderBuffer(0, primaryColorTexture);
|
|
||||||
|
|
||||||
|
|
||||||
auto depthFormat = gpu::Element(gpu::SCALAR, gpu::UINT32, gpu::DEPTH_STENCIL); // Depth24_Stencil8 texel format
|
|
||||||
auto primaryDepthTexture = gpu::Texture::createRenderBuffer(depthFormat, frameSize.x, frameSize.y, gpu::Texture::SINGLE_MIP, defaultSampler);
|
|
||||||
|
|
||||||
_primaryFramebuffer->setDepthStencilBuffer(primaryDepthTexture, depthFormat);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
primaryFramebuffer = _primaryFramebuffer;
|
primaryFramebuffer = _primaryFramebuffer;
|
||||||
|
|
||||||
|
// Set viewport for the rest of the scaled passes
|
||||||
|
renderContext->args->_viewport.z = scaledFrameSize.x;
|
||||||
|
renderContext->args->_viewport.w = scaledFrameSize.y;
|
||||||
}
|
}
|
||||||
|
|
||||||
void PrepareDeferred::run(const RenderContextPointer& renderContext, const Inputs& inputs, Outputs& outputs) {
|
void PrepareDeferred::run(const RenderContextPointer& renderContext, const Inputs& inputs, Outputs& outputs) {
|
||||||
|
|
|
@ -93,13 +93,34 @@ private:
|
||||||
friend class RenderDeferredCleanup;
|
friend class RenderDeferredCleanup;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class PreparePrimaryFramebufferConfig : public render::Job::Config {
|
||||||
|
Q_OBJECT
|
||||||
|
Q_PROPERTY(float resolutionScale MEMBER resolutionScale NOTIFY dirty)
|
||||||
|
public:
|
||||||
|
|
||||||
|
float resolutionScale{ 1.0f };
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void dirty();
|
||||||
|
};
|
||||||
|
|
||||||
class PreparePrimaryFramebuffer {
|
class PreparePrimaryFramebuffer {
|
||||||
public:
|
public:
|
||||||
using JobModel = render::Job::ModelO<PreparePrimaryFramebuffer, gpu::FramebufferPointer>;
|
|
||||||
|
|
||||||
void run(const render::RenderContextPointer& renderContext, gpu::FramebufferPointer& primaryFramebuffer);
|
using Output = gpu::FramebufferPointer;
|
||||||
|
using Config = PreparePrimaryFramebufferConfig;
|
||||||
|
using JobModel = render::Job::ModelO<PreparePrimaryFramebuffer, Output, Config>;
|
||||||
|
|
||||||
|
PreparePrimaryFramebuffer(float resolutionScale = 1.0f) : _resolutionScale{resolutionScale} {}
|
||||||
|
void configure(const Config& config);
|
||||||
|
void run(const render::RenderContextPointer& renderContext, Output& primaryFramebuffer);
|
||||||
|
|
||||||
gpu::FramebufferPointer _primaryFramebuffer;
|
gpu::FramebufferPointer _primaryFramebuffer;
|
||||||
|
float _resolutionScale{ 1.0f };
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
static gpu::FramebufferPointer createFramebuffer(const char* name, const glm::uvec2& size);
|
||||||
};
|
};
|
||||||
|
|
||||||
class PrepareDeferred {
|
class PrepareDeferred {
|
||||||
|
|
|
@ -15,19 +15,20 @@
|
||||||
#define CATEGORY_COUNT 5
|
#define CATEGORY_COUNT 5
|
||||||
|
|
||||||
<@include Fade_shared.slh@>
|
<@include Fade_shared.slh@>
|
||||||
|
<@include FadeObjectParams.shared.slh@>
|
||||||
|
|
||||||
layout(std140) uniform fadeParametersBuffer {
|
layout(std140) uniform fadeParametersBuffer {
|
||||||
FadeParameters fadeParameters[CATEGORY_COUNT];
|
FadeParameters fadeParameters[CATEGORY_COUNT];
|
||||||
};
|
};
|
||||||
uniform sampler2D fadeMaskMap;
|
uniform sampler2D fadeMaskMap;
|
||||||
|
|
||||||
struct FadeObjectParams {
|
vec3 getNoiseInverseSize(int category) {
|
||||||
int category;
|
return fadeParameters[category]._noiseInvSizeAndLevel.xyz;
|
||||||
float threshold;
|
}
|
||||||
vec3 noiseOffset;
|
|
||||||
vec3 baseOffset;
|
float getNoiseLevel(int category) {
|
||||||
vec3 baseInvSize;
|
return fadeParameters[category]._noiseInvSizeAndLevel.w;
|
||||||
};
|
}
|
||||||
|
|
||||||
vec2 hash2D(vec3 position) {
|
vec2 hash2D(vec3 position) {
|
||||||
return position.xy* vec2(0.1677, 0.221765) + position.z*0.561;
|
return position.xy* vec2(0.1677, 0.221765) + position.z*0.561;
|
||||||
|
@ -40,7 +41,7 @@ float noise3D(vec3 position) {
|
||||||
|
|
||||||
float evalFadeNoiseGradient(FadeObjectParams params, vec3 position) {
|
float evalFadeNoiseGradient(FadeObjectParams params, vec3 position) {
|
||||||
// Do tri-linear interpolation
|
// Do tri-linear interpolation
|
||||||
vec3 noisePosition = position * fadeParameters[params.category]._noiseInvSizeAndLevel.xyz + params.noiseOffset;
|
vec3 noisePosition = position * getNoiseInverseSize(params.category) + params.noiseOffset.xyz;
|
||||||
vec3 noisePositionFloored = floor(noisePosition);
|
vec3 noisePositionFloored = floor(noisePosition);
|
||||||
vec3 noisePositionFraction = fract(noisePosition);
|
vec3 noisePositionFraction = fract(noisePosition);
|
||||||
|
|
||||||
|
@ -61,11 +62,11 @@ float evalFadeNoiseGradient(FadeObjectParams params, vec3 position) {
|
||||||
|
|
||||||
float noise = mix(maskY.x, maskY.y, noisePositionFraction.y);
|
float noise = mix(maskY.x, maskY.y, noisePositionFraction.y);
|
||||||
noise -= 0.5; // Center on value 0
|
noise -= 0.5; // Center on value 0
|
||||||
return noise * fadeParameters[params.category]._noiseInvSizeAndLevel.w;
|
return noise * getNoiseLevel(params.category);
|
||||||
}
|
}
|
||||||
|
|
||||||
float evalFadeBaseGradient(FadeObjectParams params, vec3 position) {
|
float evalFadeBaseGradient(FadeObjectParams params, vec3 position) {
|
||||||
float gradient = length((position - params.baseOffset) * params.baseInvSize.xyz);
|
float gradient = length((position - params.baseOffset.xyz) * params.baseInvSize.xyz);
|
||||||
gradient = gradient-0.5; // Center on value 0.5
|
gradient = gradient-0.5; // Center on value 0.5
|
||||||
gradient *= fadeParameters[params.category]._baseLevel;
|
gradient *= fadeParameters[params.category]._baseLevel;
|
||||||
return gradient;
|
return gradient;
|
||||||
|
@ -112,20 +113,14 @@ void applyFade(FadeObjectParams params, vec3 position, out vec3 emissive) {
|
||||||
|
|
||||||
<@func declareFadeFragmentUniform()@>
|
<@func declareFadeFragmentUniform()@>
|
||||||
|
|
||||||
uniform int fadeCategory;
|
layout(std140) uniform fadeObjectParametersBuffer {
|
||||||
uniform vec3 fadeNoiseOffset;
|
FadeObjectParams fadeObjectParams;
|
||||||
uniform vec3 fadeBaseOffset;
|
};
|
||||||
uniform vec3 fadeBaseInvSize;
|
|
||||||
uniform float fadeThreshold;
|
|
||||||
|
|
||||||
<@endfunc@>
|
<@endfunc@>
|
||||||
|
|
||||||
<@func fetchFadeObjectParams(fadeParams)@>
|
<@func fetchFadeObjectParams(fadeParams)@>
|
||||||
<$fadeParams$>.category = fadeCategory;
|
<$fadeParams$> = fadeObjectParams;
|
||||||
<$fadeParams$>.threshold = fadeThreshold;
|
|
||||||
<$fadeParams$>.noiseOffset = fadeNoiseOffset;
|
|
||||||
<$fadeParams$>.baseOffset = fadeBaseOffset;
|
|
||||||
<$fadeParams$>.baseInvSize = fadeBaseInvSize;
|
|
||||||
<@endfunc@>
|
<@endfunc@>
|
||||||
|
|
||||||
<@func declareFadeFragmentVertexInput()@>
|
<@func declareFadeFragmentVertexInput()@>
|
||||||
|
@ -139,9 +134,9 @@ in vec4 _fadeData3;
|
||||||
<@func fetchFadeObjectParamsInstanced(fadeParams)@>
|
<@func fetchFadeObjectParamsInstanced(fadeParams)@>
|
||||||
<$fadeParams$>.category = int(_fadeData1.w);
|
<$fadeParams$>.category = int(_fadeData1.w);
|
||||||
<$fadeParams$>.threshold = _fadeData2.w;
|
<$fadeParams$>.threshold = _fadeData2.w;
|
||||||
<$fadeParams$>.noiseOffset = _fadeData1.xyz;
|
<$fadeParams$>.noiseOffset = _fadeData1;
|
||||||
<$fadeParams$>.baseOffset = _fadeData2.xyz;
|
<$fadeParams$>.baseOffset = _fadeData2;
|
||||||
<$fadeParams$>.baseInvSize = _fadeData3.xyz;
|
<$fadeParams$>.baseInvSize = _fadeData3;
|
||||||
<@endfunc@>
|
<@endfunc@>
|
||||||
|
|
||||||
<@func declareFadeFragment()@>
|
<@func declareFadeFragment()@>
|
||||||
|
|
|
@ -13,6 +13,8 @@
|
||||||
|
|
||||||
#include "render/TransitionStage.h"
|
#include "render/TransitionStage.h"
|
||||||
|
|
||||||
|
#include "FadeObjectParams.shared.slh"
|
||||||
|
|
||||||
#include <PathUtils.h>
|
#include <PathUtils.h>
|
||||||
|
|
||||||
FadeEffect::FadeEffect() {
|
FadeEffect::FadeEffect() {
|
||||||
|
@ -31,15 +33,8 @@ void FadeEffect::build(render::Task::TaskConcept& task, const task::Varying& edi
|
||||||
|
|
||||||
render::ShapePipeline::BatchSetter FadeEffect::getBatchSetter() const {
|
render::ShapePipeline::BatchSetter FadeEffect::getBatchSetter() const {
|
||||||
return [this](const render::ShapePipeline& shapePipeline, gpu::Batch& batch, render::Args*) {
|
return [this](const render::ShapePipeline& shapePipeline, gpu::Batch& batch, render::Args*) {
|
||||||
auto program = shapePipeline.pipeline->getProgram();
|
batch.setResourceTexture(render::ShapePipeline::Slot::FADE_MASK, _maskMap);
|
||||||
auto maskMapLocation = program->getTextures().findLocation("fadeMaskMap");
|
batch.setUniformBuffer(render::ShapePipeline::Slot::FADE_PARAMETERS, _configurations);
|
||||||
auto bufferLocation = program->getUniformBuffers().findLocation("fadeParametersBuffer");
|
|
||||||
if (maskMapLocation != -1) {
|
|
||||||
batch.setResourceTexture(maskMapLocation, _maskMap);
|
|
||||||
}
|
|
||||||
if (bufferLocation != -1) {
|
|
||||||
batch.setUniformBuffer(bufferLocation, _configurations);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -50,23 +45,29 @@ render::ShapePipeline::ItemSetter FadeEffect::getItemUniformSetter() const {
|
||||||
auto batch = args->_batch;
|
auto batch = args->_batch;
|
||||||
auto transitionStage = scene->getStage<render::TransitionStage>(render::TransitionStage::getName());
|
auto transitionStage = scene->getStage<render::TransitionStage>(render::TransitionStage::getName());
|
||||||
auto& transitionState = transitionStage->getTransition(item.getTransitionId());
|
auto& transitionState = transitionStage->getTransition(item.getTransitionId());
|
||||||
auto program = shapePipeline.pipeline->getProgram();
|
|
||||||
auto& uniforms = program->getUniforms();
|
|
||||||
auto fadeNoiseOffsetLocation = uniforms.findLocation("fadeNoiseOffset");
|
|
||||||
auto fadeBaseOffsetLocation = uniforms.findLocation("fadeBaseOffset");
|
|
||||||
auto fadeBaseInvSizeLocation = uniforms.findLocation("fadeBaseInvSize");
|
|
||||||
auto fadeThresholdLocation = uniforms.findLocation("fadeThreshold");
|
|
||||||
auto fadeCategoryLocation = uniforms.findLocation("fadeCategory");
|
|
||||||
|
|
||||||
if (fadeNoiseOffsetLocation >= 0 || fadeBaseInvSizeLocation >= 0 || fadeBaseOffsetLocation >= 0 || fadeThresholdLocation >= 0 || fadeCategoryLocation >= 0) {
|
if (transitionState.paramsBuffer._size != sizeof(gpu::StructBuffer<FadeObjectParams>)) {
|
||||||
const auto fadeCategory = FadeJob::transitionToCategory[transitionState.eventType];
|
static_assert(sizeof(transitionState.paramsBuffer) == sizeof(gpu::StructBuffer<FadeObjectParams>), "Assuming gpu::StructBuffer is a helper class for gpu::BufferView");
|
||||||
|
transitionState.paramsBuffer = gpu::StructBuffer<FadeObjectParams>();
|
||||||
batch->_glUniform1i(fadeCategoryLocation, fadeCategory);
|
|
||||||
batch->_glUniform1f(fadeThresholdLocation, transitionState.threshold);
|
|
||||||
batch->_glUniform3f(fadeNoiseOffsetLocation, transitionState.noiseOffset.x, transitionState.noiseOffset.y, transitionState.noiseOffset.z);
|
|
||||||
batch->_glUniform3f(fadeBaseOffsetLocation, transitionState.baseOffset.x, transitionState.baseOffset.y, transitionState.baseOffset.z);
|
|
||||||
batch->_glUniform3f(fadeBaseInvSizeLocation, transitionState.baseInvSize.x, transitionState.baseInvSize.y, transitionState.baseInvSize.z);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const auto fadeCategory = FadeJob::transitionToCategory[transitionState.eventType];
|
||||||
|
auto& paramsConst = static_cast<gpu::StructBuffer<FadeObjectParams>&>(transitionState.paramsBuffer).get();
|
||||||
|
|
||||||
|
if (paramsConst.category != fadeCategory
|
||||||
|
|| paramsConst.threshold != transitionState.threshold
|
||||||
|
|| glm::vec3(paramsConst.baseOffset) != transitionState.baseOffset
|
||||||
|
|| glm::vec3(paramsConst.noiseOffset) != transitionState.noiseOffset
|
||||||
|
|| glm::vec3(paramsConst.baseInvSize) != transitionState.baseInvSize) {
|
||||||
|
auto& params = static_cast<gpu::StructBuffer<FadeObjectParams>&>(transitionState.paramsBuffer).edit();
|
||||||
|
|
||||||
|
params.category = fadeCategory;
|
||||||
|
params.threshold = transitionState.threshold;
|
||||||
|
params.baseInvSize = glm::vec4(transitionState.baseInvSize, 0.0f);
|
||||||
|
params.noiseOffset = glm::vec4(transitionState.noiseOffset, 0.0f);
|
||||||
|
params.baseOffset = glm::vec4(transitionState.baseOffset, 0.0f);
|
||||||
|
}
|
||||||
|
batch->setUniformBuffer(render::ShapePipeline::Slot::FADE_OBJECT_PARAMETERS, transitionState.paramsBuffer);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
25
libraries/render-utils/src/FadeObjectParams.shared.slh
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
// glsl / C++ compatible source as interface for FadeObjectParams
|
||||||
|
#ifdef __cplusplus
|
||||||
|
# define FOP_VEC4 glm::vec4
|
||||||
|
# define FOP_VEC2 glm::vec2
|
||||||
|
# define FOP_FLOAT32 glm::float32
|
||||||
|
# define FOP_INT32 glm::int32
|
||||||
|
#else
|
||||||
|
# define FOP_VEC4 vec4
|
||||||
|
# define FOP_VEC2 vec2
|
||||||
|
# define FOP_FLOAT32 float
|
||||||
|
# define FOP_INT32 int
|
||||||
|
#endif
|
||||||
|
|
||||||
|
struct FadeObjectParams {
|
||||||
|
FOP_VEC4 noiseOffset;
|
||||||
|
FOP_VEC4 baseOffset;
|
||||||
|
FOP_VEC4 baseInvSize;
|
||||||
|
FOP_INT32 category;
|
||||||
|
FOP_FLOAT32 threshold;
|
||||||
|
};
|
||||||
|
|
||||||
|
// <@if 1@>
|
||||||
|
// Trigger Scribe include
|
||||||
|
// <@endif@> <!def that !>
|
||||||
|
//
|
|
@ -51,19 +51,19 @@ void DrawOverlay3D::run(const RenderContextPointer& renderContext, const Inputs&
|
||||||
config->setNumDrawn((int)inItems.size());
|
config->setNumDrawn((int)inItems.size());
|
||||||
emit config->numDrawnChanged();
|
emit config->numDrawnChanged();
|
||||||
|
|
||||||
|
RenderArgs* args = renderContext->args;
|
||||||
|
|
||||||
|
// Clear the framebuffer without stereo
|
||||||
|
// Needs to be distinct from the other batch because using the clear call
|
||||||
|
// while stereo is enabled triggers a warning
|
||||||
|
if (_opaquePass) {
|
||||||
|
gpu::doInBatch("DrawOverlay3D::run::clear", args->_context, [&](gpu::Batch& batch) {
|
||||||
|
batch.enableStereo(false);
|
||||||
|
batch.clearFramebuffer(gpu::Framebuffer::BUFFER_DEPTH, glm::vec4(), 1.f, 0, false);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (!inItems.empty()) {
|
if (!inItems.empty()) {
|
||||||
RenderArgs* args = renderContext->args;
|
|
||||||
|
|
||||||
// Clear the framebuffer without stereo
|
|
||||||
// Needs to be distinct from the other batch because using the clear call
|
|
||||||
// while stereo is enabled triggers a warning
|
|
||||||
if (_opaquePass) {
|
|
||||||
gpu::doInBatch("DrawOverlay3D::run::clear", args->_context, [&](gpu::Batch& batch){
|
|
||||||
batch.enableStereo(false);
|
|
||||||
batch.clearFramebuffer(gpu::Framebuffer::BUFFER_DEPTH, glm::vec4(), 1.f, 0, false);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Render the items
|
// Render the items
|
||||||
gpu::doInBatch("DrawOverlay3D::main", args->_context, [&](gpu::Batch& batch) {
|
gpu::doInBatch("DrawOverlay3D::main", args->_context, [&](gpu::Batch& batch) {
|
||||||
args->_batch = &batch;
|
args->_batch = &batch;
|
||||||
|
|
|
@ -26,6 +26,7 @@
|
||||||
#include <render/DrawStatus.h>
|
#include <render/DrawStatus.h>
|
||||||
#include <render/DrawSceneOctree.h>
|
#include <render/DrawSceneOctree.h>
|
||||||
#include <render/BlurTask.h>
|
#include <render/BlurTask.h>
|
||||||
|
#include <render/ResampleTask.h>
|
||||||
|
|
||||||
#include "RenderHifi.h"
|
#include "RenderHifi.h"
|
||||||
#include "RenderCommonTask.h"
|
#include "RenderCommonTask.h"
|
||||||
|
@ -59,8 +60,14 @@ RenderDeferredTask::RenderDeferredTask()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
void RenderDeferredTask::configure(const Config& config)
|
void RenderDeferredTask::configure(const Config& config) {
|
||||||
{
|
// Propagate resolution scale to sub jobs who need it
|
||||||
|
auto preparePrimaryBufferConfig = config.getConfig<PreparePrimaryFramebuffer>("PreparePrimaryBuffer");
|
||||||
|
auto upsamplePrimaryBufferConfig = config.getConfig<Upsample>("PrimaryBufferUpscale");
|
||||||
|
assert(preparePrimaryBufferConfig);
|
||||||
|
assert(upsamplePrimaryBufferConfig);
|
||||||
|
preparePrimaryBufferConfig->setProperty("resolutionScale", config.resolutionScale);
|
||||||
|
upsamplePrimaryBufferConfig->setProperty("factor", 1.0f / config.resolutionScale);
|
||||||
}
|
}
|
||||||
|
|
||||||
const render::Varying RenderDeferredTask::addSelectItemJobs(JobModel& task, const char* selectionName,
|
const render::Varying RenderDeferredTask::addSelectItemJobs(JobModel& task, const char* selectionName,
|
||||||
|
@ -97,23 +104,22 @@ void RenderDeferredTask::build(JobModel& task, const render::Varying& input, ren
|
||||||
|
|
||||||
const auto jitter = task.addJob<JitterSample>("JitterCam");
|
const auto jitter = task.addJob<JitterSample>("JitterCam");
|
||||||
|
|
||||||
// Prepare deferred, generate the shared Deferred Frame Transform
|
// GPU jobs: Start preparing the primary, deferred and lighting buffer
|
||||||
|
const auto scaledPrimaryFramebuffer = task.addJob<PreparePrimaryFramebuffer>("PreparePrimaryBuffer");
|
||||||
|
|
||||||
|
// Prepare deferred, generate the shared Deferred Frame Transform. Only valid with the scaled frame buffer
|
||||||
const auto deferredFrameTransform = task.addJob<GenerateDeferredFrameTransform>("DeferredFrameTransform", jitter);
|
const auto deferredFrameTransform = task.addJob<GenerateDeferredFrameTransform>("DeferredFrameTransform", jitter);
|
||||||
const auto lightingModel = task.addJob<MakeLightingModel>("LightingModel");
|
const auto lightingModel = task.addJob<MakeLightingModel>("LightingModel");
|
||||||
|
|
||||||
|
|
||||||
// GPU jobs: Start preparing the primary, deferred and lighting buffer
|
|
||||||
const auto primaryFramebuffer = task.addJob<PreparePrimaryFramebuffer>("PreparePrimaryBuffer");
|
|
||||||
|
|
||||||
const auto opaqueRangeTimer = task.addJob<BeginGPURangeTimer>("BeginOpaqueRangeTimer", "DrawOpaques");
|
const auto opaqueRangeTimer = task.addJob<BeginGPURangeTimer>("BeginOpaqueRangeTimer", "DrawOpaques");
|
||||||
|
|
||||||
const auto prepareDeferredInputs = PrepareDeferred::Inputs(primaryFramebuffer, lightingModel).asVarying();
|
const auto prepareDeferredInputs = PrepareDeferred::Inputs(scaledPrimaryFramebuffer, lightingModel).asVarying();
|
||||||
const auto prepareDeferredOutputs = task.addJob<PrepareDeferred>("PrepareDeferred", prepareDeferredInputs);
|
const auto prepareDeferredOutputs = task.addJob<PrepareDeferred>("PrepareDeferred", prepareDeferredInputs);
|
||||||
const auto deferredFramebuffer = prepareDeferredOutputs.getN<PrepareDeferred::Outputs>(0);
|
const auto deferredFramebuffer = prepareDeferredOutputs.getN<PrepareDeferred::Outputs>(0);
|
||||||
const auto lightingFramebuffer = prepareDeferredOutputs.getN<PrepareDeferred::Outputs>(1);
|
const auto lightingFramebuffer = prepareDeferredOutputs.getN<PrepareDeferred::Outputs>(1);
|
||||||
|
|
||||||
// draw a stencil mask in hidden regions of the framebuffer.
|
// draw a stencil mask in hidden regions of the framebuffer.
|
||||||
task.addJob<PrepareStencil>("PrepareStencil", primaryFramebuffer);
|
task.addJob<PrepareStencil>("PrepareStencil", scaledPrimaryFramebuffer);
|
||||||
|
|
||||||
// Render opaque objects in DeferredBuffer
|
// Render opaque objects in DeferredBuffer
|
||||||
const auto opaqueInputs = DrawStateSortDeferred::Inputs(opaques, lightingModel, jitter).asVarying();
|
const auto opaqueInputs = DrawStateSortDeferred::Inputs(opaques, lightingModel, jitter).asVarying();
|
||||||
|
@ -223,7 +229,7 @@ void RenderDeferredTask::build(JobModel& task, const render::Varying& input, ren
|
||||||
task.addJob<Bloom>("Bloom", bloomInputs);
|
task.addJob<Bloom>("Bloom", bloomInputs);
|
||||||
|
|
||||||
// Lighting Buffer ready for tone mapping
|
// Lighting Buffer ready for tone mapping
|
||||||
const auto toneMappingInputs = ToneMappingDeferred::Inputs(lightingFramebuffer, primaryFramebuffer).asVarying();
|
const auto toneMappingInputs = ToneMappingDeferred::Inputs(lightingFramebuffer, scaledPrimaryFramebuffer).asVarying();
|
||||||
task.addJob<ToneMappingDeferred>("ToneMapping", toneMappingInputs);
|
task.addJob<ToneMappingDeferred>("ToneMapping", toneMappingInputs);
|
||||||
|
|
||||||
{ // Debug the bounds of the rendered items, still look at the zbuffer
|
{ // Debug the bounds of the rendered items, still look at the zbuffer
|
||||||
|
@ -284,6 +290,9 @@ void RenderDeferredTask::build(JobModel& task, const render::Varying& input, ren
|
||||||
task.addJob<DebugZoneLighting>("DrawZoneStack", deferredFrameTransform);
|
task.addJob<DebugZoneLighting>("DrawZoneStack", deferredFrameTransform);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Upscale to finale resolution
|
||||||
|
const auto primaryFramebuffer = task.addJob<render::Upsample>("PrimaryBufferUpscale", scaledPrimaryFramebuffer);
|
||||||
|
|
||||||
// Composite the HUD and HUD overlays
|
// Composite the HUD and HUD overlays
|
||||||
task.addJob<CompositeHUD>("HUD");
|
task.addJob<CompositeHUD>("HUD");
|
||||||
|
|
||||||
|
|
|
@ -105,11 +105,13 @@ class RenderDeferredTaskConfig : public render::Task::Config {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
Q_PROPERTY(float fadeScale MEMBER fadeScale NOTIFY dirty)
|
Q_PROPERTY(float fadeScale MEMBER fadeScale NOTIFY dirty)
|
||||||
Q_PROPERTY(float fadeDuration MEMBER fadeDuration NOTIFY dirty)
|
Q_PROPERTY(float fadeDuration MEMBER fadeDuration NOTIFY dirty)
|
||||||
|
Q_PROPERTY(float resolutionScale MEMBER resolutionScale NOTIFY dirty)
|
||||||
Q_PROPERTY(bool debugFade MEMBER debugFade NOTIFY dirty)
|
Q_PROPERTY(bool debugFade MEMBER debugFade NOTIFY dirty)
|
||||||
Q_PROPERTY(float debugFadePercent MEMBER debugFadePercent NOTIFY dirty)
|
Q_PROPERTY(float debugFadePercent MEMBER debugFadePercent NOTIFY dirty)
|
||||||
public:
|
public:
|
||||||
float fadeScale{ 0.5f };
|
float fadeScale{ 0.5f };
|
||||||
float fadeDuration{ 3.0f };
|
float fadeDuration{ 3.0f };
|
||||||
|
float resolutionScale{ 1.f };
|
||||||
float debugFadePercent{ 0.f };
|
float debugFadePercent{ 0.f };
|
||||||
bool debugFade{ false };
|
bool debugFade{ false };
|
||||||
|
|
||||||
|
|
|
@ -81,3 +81,69 @@ void HalfDownsample::run(const RenderContextPointer& renderContext, const gpu::F
|
||||||
batch.draw(gpu::TRIANGLE_STRIP, 4);
|
batch.draw(gpu::TRIANGLE_STRIP, 4);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
gpu::PipelinePointer Upsample::_pipeline;
|
||||||
|
|
||||||
|
void Upsample::configure(const Config& config) {
|
||||||
|
_factor = config.factor;
|
||||||
|
}
|
||||||
|
|
||||||
|
gpu::FramebufferPointer Upsample::getResampledFrameBuffer(const gpu::FramebufferPointer& sourceFramebuffer) {
|
||||||
|
if (_factor == 1.0f) {
|
||||||
|
return sourceFramebuffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto resampledFramebufferSize = glm::uvec2(glm::vec2(sourceFramebuffer->getSize()) * _factor);
|
||||||
|
|
||||||
|
if (!_destinationFrameBuffer || resampledFramebufferSize != _destinationFrameBuffer->getSize()) {
|
||||||
|
_destinationFrameBuffer = gpu::FramebufferPointer(gpu::Framebuffer::create("UpsampledOutput"));
|
||||||
|
|
||||||
|
auto sampler = gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_LINEAR);
|
||||||
|
auto target = gpu::Texture::createRenderBuffer(sourceFramebuffer->getRenderBuffer(0)->getTexelFormat(), resampledFramebufferSize.x, resampledFramebufferSize.y, gpu::Texture::SINGLE_MIP, sampler);
|
||||||
|
_destinationFrameBuffer->setRenderBuffer(0, target);
|
||||||
|
}
|
||||||
|
return _destinationFrameBuffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Upsample::run(const RenderContextPointer& renderContext, const gpu::FramebufferPointer& sourceFramebuffer, gpu::FramebufferPointer& resampledFrameBuffer) {
|
||||||
|
assert(renderContext->args);
|
||||||
|
assert(renderContext->args->hasViewFrustum());
|
||||||
|
RenderArgs* args = renderContext->args;
|
||||||
|
|
||||||
|
resampledFrameBuffer = getResampledFrameBuffer(sourceFramebuffer);
|
||||||
|
if (resampledFrameBuffer != sourceFramebuffer) {
|
||||||
|
if (!_pipeline) {
|
||||||
|
auto vs = gpu::StandardShaderLib::getDrawTransformUnitQuadVS();
|
||||||
|
auto ps = gpu::StandardShaderLib::getDrawTextureOpaquePS();
|
||||||
|
gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps);
|
||||||
|
|
||||||
|
gpu::Shader::BindingSet slotBindings;
|
||||||
|
gpu::Shader::makeProgram(*program, slotBindings);
|
||||||
|
|
||||||
|
gpu::StatePointer state = gpu::StatePointer(new gpu::State());
|
||||||
|
state->setDepthTest(gpu::State::DepthTest(false, false));
|
||||||
|
_pipeline = gpu::Pipeline::create(program, state);
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto bufferSize = resampledFrameBuffer->getSize();
|
||||||
|
glm::ivec4 viewport{ 0, 0, bufferSize.x, bufferSize.y };
|
||||||
|
|
||||||
|
gpu::doInBatch("Upsample::run", args->_context, [&](gpu::Batch& batch) {
|
||||||
|
batch.enableStereo(false);
|
||||||
|
|
||||||
|
batch.setFramebuffer(resampledFrameBuffer);
|
||||||
|
|
||||||
|
batch.setViewportTransform(viewport);
|
||||||
|
batch.setProjectionTransform(glm::mat4());
|
||||||
|
batch.resetViewTransform();
|
||||||
|
batch.setPipeline(_pipeline);
|
||||||
|
|
||||||
|
batch.setModelTransform(gpu::Framebuffer::evalSubregionTexcoordTransform(bufferSize, viewport));
|
||||||
|
batch.setResourceTexture(0, sourceFramebuffer->getRenderBuffer(0));
|
||||||
|
batch.draw(gpu::TRIANGLE_STRIP, 4);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Set full final viewport
|
||||||
|
args->_viewport = viewport;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -36,6 +36,37 @@ namespace render {
|
||||||
|
|
||||||
gpu::FramebufferPointer getResampledFrameBuffer(const gpu::FramebufferPointer& sourceFramebuffer);
|
gpu::FramebufferPointer getResampledFrameBuffer(const gpu::FramebufferPointer& sourceFramebuffer);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class UpsampleConfig : public render::Job::Config {
|
||||||
|
Q_OBJECT
|
||||||
|
Q_PROPERTY(float factor MEMBER factor NOTIFY dirty)
|
||||||
|
public:
|
||||||
|
|
||||||
|
float factor{ 1.0f };
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void dirty();
|
||||||
|
};
|
||||||
|
|
||||||
|
class Upsample {
|
||||||
|
public:
|
||||||
|
using Config = UpsampleConfig;
|
||||||
|
using JobModel = Job::ModelIO<Upsample, gpu::FramebufferPointer, gpu::FramebufferPointer, Config>;
|
||||||
|
|
||||||
|
Upsample(float factor = 2.0f) : _factor{ factor } {}
|
||||||
|
|
||||||
|
void configure(const Config& config);
|
||||||
|
void run(const RenderContextPointer& renderContext, const gpu::FramebufferPointer& sourceFramebuffer, gpu::FramebufferPointer& resampledFrameBuffer);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
|
||||||
|
static gpu::PipelinePointer _pipeline;
|
||||||
|
|
||||||
|
gpu::FramebufferPointer _destinationFrameBuffer;
|
||||||
|
float _factor{ 2.0f };
|
||||||
|
|
||||||
|
gpu::FramebufferPointer getResampledFrameBuffer(const gpu::FramebufferPointer& sourceFramebuffer);
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif // hifi_render_ResampleTask_h
|
#endif // hifi_render_ResampleTask_h
|
||||||
|
|
|
@ -95,6 +95,7 @@ void ShapePlumber::addPipeline(const Filter& filter, const gpu::ShaderPointer& p
|
||||||
slotBindings.insert(gpu::Shader::Binding(std::string("skyboxMap"), Slot::MAP::LIGHT_AMBIENT_MAP));
|
slotBindings.insert(gpu::Shader::Binding(std::string("skyboxMap"), Slot::MAP::LIGHT_AMBIENT_MAP));
|
||||||
slotBindings.insert(gpu::Shader::Binding(std::string("fadeMaskMap"), Slot::MAP::FADE_MASK));
|
slotBindings.insert(gpu::Shader::Binding(std::string("fadeMaskMap"), Slot::MAP::FADE_MASK));
|
||||||
slotBindings.insert(gpu::Shader::Binding(std::string("fadeParametersBuffer"), Slot::BUFFER::FADE_PARAMETERS));
|
slotBindings.insert(gpu::Shader::Binding(std::string("fadeParametersBuffer"), Slot::BUFFER::FADE_PARAMETERS));
|
||||||
|
slotBindings.insert(gpu::Shader::Binding(std::string("fadeObjectParametersBuffer"), Slot::BUFFER::FADE_OBJECT_PARAMETERS));
|
||||||
slotBindings.insert(gpu::Shader::Binding(std::string("hazeBuffer"), Slot::BUFFER::HAZE_MODEL));
|
slotBindings.insert(gpu::Shader::Binding(std::string("hazeBuffer"), Slot::BUFFER::HAZE_MODEL));
|
||||||
|
|
||||||
if (key.isTranslucent()) {
|
if (key.isTranslucent()) {
|
||||||
|
@ -124,6 +125,7 @@ void ShapePlumber::addPipeline(const Filter& filter, const gpu::ShaderPointer& p
|
||||||
locations->lightAmbientMapUnit = program->getTextures().findLocation("skyboxMap");
|
locations->lightAmbientMapUnit = program->getTextures().findLocation("skyboxMap");
|
||||||
locations->fadeMaskTextureUnit = program->getTextures().findLocation("fadeMaskMap");
|
locations->fadeMaskTextureUnit = program->getTextures().findLocation("fadeMaskMap");
|
||||||
locations->fadeParameterBufferUnit = program->getUniformBuffers().findLocation("fadeParametersBuffer");
|
locations->fadeParameterBufferUnit = program->getUniformBuffers().findLocation("fadeParametersBuffer");
|
||||||
|
locations->fadeObjectParameterBufferUnit = program->getUniformBuffers().findLocation("fadeObjectParametersBuffer");
|
||||||
locations->hazeParameterBufferUnit = program->getUniformBuffers().findLocation("hazeBuffer");
|
locations->hazeParameterBufferUnit = program->getUniformBuffers().findLocation("hazeBuffer");
|
||||||
if (key.isTranslucent()) {
|
if (key.isTranslucent()) {
|
||||||
locations->lightClusterGridBufferUnit = program->getUniformBuffers().findLocation("clusterGridBuffer");
|
locations->lightClusterGridBufferUnit = program->getUniformBuffers().findLocation("clusterGridBuffer");
|
||||||
|
|
|
@ -240,6 +240,7 @@ public:
|
||||||
LIGHT_AMBIENT_BUFFER,
|
LIGHT_AMBIENT_BUFFER,
|
||||||
HAZE_MODEL,
|
HAZE_MODEL,
|
||||||
FADE_PARAMETERS,
|
FADE_PARAMETERS,
|
||||||
|
FADE_OBJECT_PARAMETERS,
|
||||||
LIGHT_CLUSTER_GRID_FRUSTUM_GRID_SLOT,
|
LIGHT_CLUSTER_GRID_FRUSTUM_GRID_SLOT,
|
||||||
LIGHT_CLUSTER_GRID_CLUSTER_GRID_SLOT,
|
LIGHT_CLUSTER_GRID_CLUSTER_GRID_SLOT,
|
||||||
LIGHT_CLUSTER_GRID_CLUSTER_CONTENT_SLOT,
|
LIGHT_CLUSTER_GRID_CLUSTER_CONTENT_SLOT,
|
||||||
|
@ -254,9 +255,9 @@ public:
|
||||||
ROUGHNESS,
|
ROUGHNESS,
|
||||||
OCCLUSION,
|
OCCLUSION,
|
||||||
SCATTERING,
|
SCATTERING,
|
||||||
FADE_MASK,
|
|
||||||
|
|
||||||
LIGHT_AMBIENT_MAP = 10,
|
LIGHT_AMBIENT_MAP = 10,
|
||||||
|
FADE_MASK,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -278,6 +279,7 @@ public:
|
||||||
int lightAmbientMapUnit;
|
int lightAmbientMapUnit;
|
||||||
int fadeMaskTextureUnit;
|
int fadeMaskTextureUnit;
|
||||||
int fadeParameterBufferUnit;
|
int fadeParameterBufferUnit;
|
||||||
|
int fadeObjectParameterBufferUnit;
|
||||||
int hazeParameterBufferUnit;
|
int hazeParameterBufferUnit;
|
||||||
int lightClusterGridBufferUnit;
|
int lightClusterGridBufferUnit;
|
||||||
int lightClusterContentBufferUnit;
|
int lightClusterContentBufferUnit;
|
||||||
|
|
|
@ -42,6 +42,8 @@ namespace render {
|
||||||
glm::vec3 baseInvSize{ 1.f, 1.f, 1.f };
|
glm::vec3 baseInvSize{ 1.f, 1.f, 1.f };
|
||||||
float threshold{ 0.f };
|
float threshold{ 0.f };
|
||||||
uint8_t isFinished{ 0 };
|
uint8_t isFinished{ 0 };
|
||||||
|
|
||||||
|
mutable gpu::BufferView paramsBuffer;
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef std::shared_ptr<Transition> TransitionPointer;
|
typedef std::shared_ptr<Transition> TransitionPointer;
|
||||||
|
|
|
@ -75,11 +75,10 @@ ScriptAudioInjector* AudioScriptingInterface::playSound(SharedSoundPointer sound
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool AudioScriptingInterface::setStereoInput(bool stereo) {
|
void AudioScriptingInterface::setStereoInput(bool stereo) {
|
||||||
if (_localAudioInterface) {
|
if (_localAudioInterface) {
|
||||||
QMetaObject::invokeMethod(_localAudioInterface, "setIsStereoInput", Q_ARG(bool, stereo));
|
QMetaObject::invokeMethod(_localAudioInterface, "setIsStereoInput", Q_ARG(bool, stereo));
|
||||||
}
|
}
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool AudioScriptingInterface::isStereoInput() {
|
bool AudioScriptingInterface::isStereoInput() {
|
||||||
|
|
|
@ -54,9 +54,8 @@ protected:
|
||||||
/**jsdoc
|
/**jsdoc
|
||||||
* @function Audio.setStereoInput
|
* @function Audio.setStereoInput
|
||||||
* @param {boolean} stereo
|
* @param {boolean} stereo
|
||||||
* @returns {boolean}
|
|
||||||
*/
|
*/
|
||||||
Q_INVOKABLE bool setStereoInput(bool stereo);
|
Q_INVOKABLE void setStereoInput(bool stereo);
|
||||||
|
|
||||||
/**jsdoc
|
/**jsdoc
|
||||||
* @function Audio.isStereoInput
|
* @function Audio.isStereoInput
|
||||||
|
|