mirror of
https://github.com/lubosz/overte.git
synced 2025-04-24 09:43:25 +02:00
Merge remote-tracking branch 'upstream/master' into scaleAssert
This commit is contained in:
commit
c8afaf27e5
55 changed files with 3637 additions and 203 deletions
8
BUILD.md
8
BUILD.md
|
@ -1,7 +1,7 @@
|
|||
### Dependencies
|
||||
|
||||
- [cmake](https://cmake.org/download/): 3.9
|
||||
- [Qt](https://www.qt.io/download-open-source): 5.9.1
|
||||
- [Qt](https://www.qt.io/download-open-source): 5.10.1
|
||||
- [OpenSSL](https://www.openssl.org/): Use the latest available 1.0 version (**NOT** 1.1) of OpenSSL to avoid security vulnerabilities.
|
||||
- [VHACD](https://github.com/virneo/v-hacd)(clone this repository)(Optional)
|
||||
|
||||
|
@ -46,8 +46,8 @@ This can either be entered directly into your shell session before you build or
|
|||
|
||||
The path it needs to be set to will depend on where and how Qt5 was installed. e.g.
|
||||
|
||||
export QT_CMAKE_PREFIX_PATH=/usr/local/qt/5.6.2/clang_64/lib/cmake/
|
||||
export QT_CMAKE_PREFIX_PATH=/usr/local/Cellar/qt5/5.6.2/lib/cmake
|
||||
export QT_CMAKE_PREFIX_PATH=/usr/local/qt/5.10.1/clang_64/lib/cmake/
|
||||
export QT_CMAKE_PREFIX_PATH=/usr/local/Cellar/qt5/5.10.1/lib/cmake
|
||||
export QT_CMAKE_PREFIX_PATH=/usr/local/opt/qt5/lib/cmake
|
||||
|
||||
#### Generating build files
|
||||
|
@ -66,7 +66,7 @@ Any variables that need to be set for CMake to find dependencies can be set as E
|
|||
|
||||
For example, to pass the QT_CMAKE_PREFIX_PATH variable during build file generation:
|
||||
|
||||
cmake .. -DQT_CMAKE_PREFIX_PATH=/usr/local/qt/5.6.2/lib/cmake
|
||||
cmake .. -DQT_CMAKE_PREFIX_PATH=/usr/local/qt/5.10.1/lib/cmake
|
||||
|
||||
#### Finding Dependencies
|
||||
|
||||
|
|
|
@ -14,8 +14,8 @@ Should you choose not to install Qt5 via a package manager that handles dependen
|
|||
|
||||
Install qt:
|
||||
```bash
|
||||
wget http://debian.highfidelity.com/pool/h/hi/hifi-qt5.6.1_5.6.1_amd64.deb
|
||||
sudo dpkg -i hifi-qt5.6.1_5.6.1_amd64.deb
|
||||
wget http://debian.highfidelity.com/pool/h/hi/hifi-qt5.10.1_5.10.1_amd64.deb
|
||||
sudo dpkg -i hifi-qt5.10.1_5.10.1_amd64.deb
|
||||
```
|
||||
|
||||
Install build dependencies:
|
||||
|
@ -66,7 +66,7 @@ cd hifi/build
|
|||
|
||||
Prepare makefiles:
|
||||
```bash
|
||||
cmake -DQT_CMAKE_PREFIX_PATH=/usr/local/Qt5.6.1/5.6/gcc_64/lib/cmake ..
|
||||
cmake -DQT_CMAKE_PREFIX_PATH=/usr/local/Qt5.10.1/5.10/gcc_64/lib/cmake ..
|
||||
```
|
||||
|
||||
Start compilation and get a cup of coffee:
|
||||
|
@ -74,7 +74,7 @@ Start compilation and get a cup of coffee:
|
|||
make domain-server assignment-client interface
|
||||
```
|
||||
|
||||
In a server does not make sense to compile interface
|
||||
In a server does not make sense to compile interface
|
||||
|
||||
### Running the software
|
||||
|
||||
|
|
|
@ -20,7 +20,7 @@ Note that this uses the version from the homebrew formula at the time of this wr
|
|||
Assuming you've installed Qt using the homebrew instructions above, you'll need to set QT_CMAKE_PREFIX_PATH so CMake can find your installations.
|
||||
For Qt installed via homebrew, set QT_CMAKE_PREFIX_PATH:
|
||||
|
||||
export QT_CMAKE_PREFIX_PATH=/usr/local/Cellar/qt/5.9.1/lib/cmake
|
||||
export QT_CMAKE_PREFIX_PATH=/usr/local/Cellar/qt/5.10.1/lib/cmake
|
||||
|
||||
Note that this uses the version from the homebrew formula at the time of this writing, and the version in the path will likely change.
|
||||
|
||||
|
|
20
BUILD_WIN.md
20
BUILD_WIN.md
|
@ -1,13 +1,13 @@
|
|||
This is a stand-alone guide for creating your first High Fidelity build for Windows 64-bit.
|
||||
|
||||
## Building High Fidelity
|
||||
Note: We are now using Visual Studio 2017 and Qt 5.9.1. If you are upgrading from Visual Studio 2013 and Qt 5.6.2, do a clean uninstall of those versions before going through this guide.
|
||||
Note: We are now using Visual Studio 2017 and Qt 5.10.1. If you are upgrading from Visual Studio 2013 and Qt 5.6.2, do a clean uninstall of those versions before going through this guide.
|
||||
|
||||
Note: The prerequisites will require about 10 GB of space on your drive. You will also need a system with at least 8GB of main memory.
|
||||
|
||||
### Step 1. Visual Studio 2017
|
||||
|
||||
If you don’t have Community or Professional edition of Visual Studio 2017, download [Visual Studio Community 2017](https://www.visualstudio.com/downloads/).
|
||||
If you don’t have Community or Professional edition of Visual Studio 2017, download [Visual Studio Community 2017](https://www.visualstudio.com/downloads/).
|
||||
|
||||
When selecting components, check "Desktop development with C++." Also check "Windows 8.1 SDK and UCRT SDK" and "VC++ 2015.3 v140 toolset (x86,x64)" on the Summary toolbar on the right.
|
||||
|
||||
|
@ -17,15 +17,15 @@ Download and install the latest version of CMake 3.9. Download the file named w
|
|||
|
||||
### Step 3. Installing Qt
|
||||
|
||||
Download and install the [Qt Online Installer](https://www.qt.io/download-open-source/?hsCtaTracking=f977210e-de67-475f-a32b-65cec207fd03%7Cd62710cd-e1db-46aa-8d4d-2f1c1ffdacea). While installing, you only need to have the following components checked under Qt 5.9.1: "msvc2017 64-bit", "Qt WebEngine", and "Qt Script (Deprecated)".
|
||||
Download and install the [Qt Online Installer](https://www.qt.io/download-open-source/?hsCtaTracking=f977210e-de67-475f-a32b-65cec207fd03%7Cd62710cd-e1db-46aa-8d4d-2f1c1ffdacea). While installing, you only need to have the following components checked under Qt 5.10.1: "msvc2017 64-bit", "Qt WebEngine", and "Qt Script (Deprecated)".
|
||||
|
||||
Note: Installing the Sources is optional but recommended if you have room for them (~2GB).
|
||||
Note: Installing the Sources is optional but recommended if you have room for them (~2GB).
|
||||
|
||||
### Step 4. Setting Qt Environment Variable
|
||||
|
||||
Go to `Control Panel > System > Advanced System Settings > Environment Variables > New...` (or search “Environment Variables” in Start Search).
|
||||
* Set "Variable name": `QT_CMAKE_PREFIX_PATH`
|
||||
* Set "Variable value": `C:\Qt\5.9.1\msvc2017_64\lib\cmake`
|
||||
* Set "Variable value": `C:\Qt\5.10.1\msvc2017_64\lib\cmake`
|
||||
|
||||
### Step 5. Installing [vcpkg](https://github.com/Microsoft/vcpkg)
|
||||
|
||||
|
@ -39,7 +39,7 @@ Go to `Control Panel > System > Advanced System Settings > Environment Variables
|
|||
|
||||
* In the vcpkg directory, install the 64 bit OpenSSL package with the command `vcpkg install openssl:x64-windows`
|
||||
* Once the build completes you should have a file `ssl.h` in `${VCPKG_ROOT}/installed/x64-windows/include/openssl`
|
||||
|
||||
|
||||
### Step 7. Running CMake to Generate Build Files
|
||||
|
||||
Run Command Prompt from Start and run the following commands:
|
||||
|
@ -49,7 +49,7 @@ mkdir build
|
|||
cd build
|
||||
cmake .. -G "Visual Studio 15 Win64"
|
||||
```
|
||||
|
||||
|
||||
Where `%HIFI_DIR%` is the directory for the highfidelity repository.
|
||||
|
||||
### Step 8. Making a Build
|
||||
|
@ -74,10 +74,10 @@ Note: You can also run Interface by launching it from command line or File Explo
|
|||
|
||||
## Troubleshooting
|
||||
|
||||
For any problems after Step #7, first try this:
|
||||
For any problems after Step #7, first try this:
|
||||
* Delete your locally cloned copy of the highfidelity repository
|
||||
* Restart your computer
|
||||
* Redownload the [repository](https://github.com/highfidelity/hifi)
|
||||
* Redownload the [repository](https://github.com/highfidelity/hifi)
|
||||
* Restart directions from Step #7
|
||||
|
||||
#### CMake gives you the same error message repeatedly after the build fails
|
||||
|
@ -90,4 +90,4 @@ Remove `CMakeCache.txt` found in the `%HIFI_DIR%\build` directory. Verify that
|
|||
|
||||
#### Qt is throwing an error
|
||||
|
||||
Make sure you have the correct version (5.9.1) installed and `QT_CMAKE_PREFIX_PATH` environment variable is set correctly.
|
||||
Make sure you have the correct version (5.10.1) installed and `QT_CMAKE_PREFIX_PATH` environment variable is set correctly.
|
||||
|
|
|
@ -322,8 +322,16 @@ void AudioMixerSlave::addStream(AudioMixerClientData& listenerNodeData, const QU
|
|||
|
||||
// stereo sources are not passed through HRTF
|
||||
if (streamToAdd.isStereo()) {
|
||||
for (int i = 0; i < AudioConstants::NETWORK_FRAME_SAMPLES_STEREO; ++i) {
|
||||
_mixSamples[i] += float(streamPopOutput[i] * gain / AudioConstants::MAX_SAMPLE_VALUE);
|
||||
|
||||
// apply the avatar gain adjustment
|
||||
auto& hrtf = listenerNodeData.hrtfForStream(sourceNodeID, streamToAdd.getStreamIdentifier());
|
||||
gain *= hrtf.getGainAdjustment();
|
||||
|
||||
const float scale = 1/32768.0f; // int16_t to float
|
||||
|
||||
for (int i = 0; i < AudioConstants::NETWORK_FRAME_SAMPLES_PER_CHANNEL; i++) {
|
||||
_mixSamples[2*i+0] += (float)streamPopOutput[2*i+0] * gain * scale;
|
||||
_mixSamples[2*i+1] += (float)streamPopOutput[2*i+1] * gain * scale;
|
||||
}
|
||||
|
||||
++stats.manualStereoMixes;
|
||||
|
@ -332,10 +340,13 @@ void AudioMixerSlave::addStream(AudioMixerClientData& listenerNodeData, const QU
|
|||
|
||||
// echo sources are not passed through HRTF
|
||||
if (isEcho) {
|
||||
for (int i = 0; i < AudioConstants::NETWORK_FRAME_SAMPLES_STEREO; i += 2) {
|
||||
auto monoSample = float(streamPopOutput[i / 2] * gain / AudioConstants::MAX_SAMPLE_VALUE);
|
||||
_mixSamples[i] += monoSample;
|
||||
_mixSamples[i + 1] += monoSample;
|
||||
|
||||
const float scale = 1/32768.0f; // int16_t to float
|
||||
|
||||
for (int i = 0; i < AudioConstants::NETWORK_FRAME_SAMPLES_PER_CHANNEL; i++) {
|
||||
float sample = (float)streamPopOutput[i] * gain * scale;
|
||||
_mixSamples[2*i+0] += sample;
|
||||
_mixSamples[2*i+1] += sample;
|
||||
}
|
||||
|
||||
++stats.manualEchoMixes;
|
||||
|
|
4
cmake/externals/crashpad/CMakeLists.txt
vendored
4
cmake/externals/crashpad/CMakeLists.txt
vendored
|
@ -6,8 +6,8 @@ string(TOUPPER ${EXTERNAL_NAME} EXTERNAL_NAME_UPPER)
|
|||
if (WIN32)
|
||||
ExternalProject_Add(
|
||||
${EXTERNAL_NAME}
|
||||
URL https://backtrace.io/download/crashpad_062317.zip
|
||||
URL_MD5 65817e564b3628492abfc1dbd2a1e98b
|
||||
URL http://public.highfidelity.com/dependencies/crashpad_062317.1.zip
|
||||
URL_MD5 9c84b77f5f23daf939da1371825ed2b1
|
||||
CONFIGURE_COMMAND ""
|
||||
BUILD_COMMAND ""
|
||||
INSTALL_COMMAND ""
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
#
|
||||
#
|
||||
# Created by Bradley Austin Davis on 2017/09/02
|
||||
# Copyright 2013-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
|
||||
#
|
||||
#
|
||||
|
||||
# Construct a default QT location from a root path, a version and an architecture
|
||||
function(calculate_default_qt_dir _RESULT_NAME)
|
||||
|
@ -27,7 +27,7 @@ function(calculate_default_qt_dir _RESULT_NAME)
|
|||
endif()
|
||||
|
||||
set_from_env(QT_ROOT QT_ROOT ${QT_DEFAULT_ROOT})
|
||||
set_from_env(QT_VERSION QT_VERSION "5.9.1")
|
||||
set_from_env(QT_VERSION QT_VERSION "5.10.1")
|
||||
set_from_env(QT_ARCH QT_ARCH ${QT_DEFAULT_ARCH})
|
||||
|
||||
set(${_RESULT_NAME} "${QT_ROOT}/${QT_VERSION}/${QT_ARCH}" PARENT_SCOPE)
|
||||
|
@ -60,11 +60,11 @@ macro(setup_qt)
|
|||
#if (NOT EXISTS "${QT_DIR}/include/QtCore/QtGlobal")
|
||||
# message(FATAL_ERROR "Unable to locate Qt includes in ${QT_DIR}")
|
||||
#endif()
|
||||
|
||||
|
||||
if (NOT EXISTS "${QT_CMAKE_PREFIX_PATH}/Qt5Core/Qt5CoreConfig.cmake")
|
||||
message(FATAL_ERROR "Unable to locate Qt cmake config in ${QT_CMAKE_PREFIX_PATH}")
|
||||
endif()
|
||||
|
||||
|
||||
message(STATUS "The Qt build in use is: \"${QT_DIR}\"")
|
||||
|
||||
# Instruct CMake to run moc automatically when needed.
|
||||
|
@ -72,7 +72,7 @@ macro(setup_qt)
|
|||
|
||||
# Instruct CMake to run rcc automatically when needed
|
||||
set(CMAKE_AUTORCC ON)
|
||||
|
||||
|
||||
if (WIN32)
|
||||
add_paths_to_fixup_libs("${QT_DIR}/bin")
|
||||
endif ()
|
||||
|
|
125
interface/resources/qml/controls-uit/+android/Button.qml
Normal file
125
interface/resources/qml/controls-uit/+android/Button.qml
Normal file
|
@ -0,0 +1,125 @@
|
|||
//
|
||||
// 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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
165
interface/resources/qml/controls-uit/+android/Table.qml
Normal file
165
interface/resources/qml/controls-uit/+android/Table.qml
Normal file
|
@ -0,0 +1,165 @@
|
|||
//
|
||||
// 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)
|
||||
}
|
||||
}
|
575
interface/resources/qml/desktop/+android/Desktop.qml
Normal file
575
interface/resources/qml/desktop/+android/Desktop.qml
Normal file
|
@ -0,0 +1,575 @@
|
|||
//
|
||||
// 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
|
||||
}
|
||||
|
||||
}
|
|
@ -324,6 +324,18 @@ FocusScope {
|
|||
return false;
|
||||
}
|
||||
|
||||
function hideDesktopWindows() {
|
||||
for (var index = 0; index < desktop.visibleChildren.length; index++) {
|
||||
var child = desktop.visibleChildren[index];
|
||||
if (child.topLevelWindow && child.hasOwnProperty("modality")) {
|
||||
var TOOLBAR_NAME = "com.highfidelity.interface.toolbar.system"
|
||||
if (child.objectName !== TOOLBAR_NAME) {
|
||||
child.setShown(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function setPinned(newPinned) {
|
||||
pinned = newPinned
|
||||
}
|
||||
|
|
338
interface/resources/qml/dialogs/+android/CustomQueryDialog.qml
Normal file
338
interface/resources/qml/dialogs/+android/CustomQueryDialog.qml
Normal file
|
@ -0,0 +1,338 @@
|
|||
//
|
||||
// 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: 2;
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
840
interface/resources/qml/dialogs/+android/FileDialog.qml
Normal file
840
interface/resources/qml/dialogs/+android/FileDialog.qml
Normal file
|
@ -0,0 +1,840 @@
|
|||
//
|
||||
// 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: false;
|
||||
// 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
|
||||
Component.onCompleted: {
|
||||
showFiles = !root.selectDirectory
|
||||
}
|
||||
|
||||
onFolderChanged: {
|
||||
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;
|
||||
|
||||
var newFilesModel = 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, newFilesModel.get(middle)[sortField])) {
|
||||
lessThan = true;
|
||||
upper = middle;
|
||||
} else {
|
||||
lessThan = false;
|
||||
lower = middle + 1;
|
||||
}
|
||||
}
|
||||
|
||||
newFilesModel.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++;
|
||||
}
|
||||
filesModel = newFilesModel;
|
||||
|
||||
d.clearSelection();
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
231
interface/resources/qml/dialogs/+android/QueryDialog.qml
Normal file
231
interface/resources/qml/dialogs/+android/QueryDialog.qml
Normal file
|
@ -0,0 +1,231 @@
|
|||
//
|
||||
// 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()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,533 @@
|
|||
//
|
||||
// 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;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
|
@ -225,8 +225,8 @@
|
|||
#ifdef DEBUG_EVENT_QUEUE
|
||||
// This is a HACK that uses private headers included with the qt source distrubution.
|
||||
// To use this feature you need to add these directores to your include path:
|
||||
// E:/Qt/5.9.1/Src/qtbase/include/QtCore/5.9.1/QtCore
|
||||
// E:/Qt/5.9.1/Src/qtbase/include/QtCore/5.9.1
|
||||
// E:/Qt/5.10.1/Src/qtbase/include/QtCore/5.10.1/QtCore
|
||||
// E:/Qt/5.10.1/Src/qtbase/include/QtCore/5.10.1
|
||||
#define QT_BOOTSTRAPPED
|
||||
#include <private/qthread_p.h>
|
||||
#include <private/qobject_p.h>
|
||||
|
@ -2038,7 +2038,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
|
|||
});
|
||||
|
||||
_snapshotSound = DependencyManager::get<SoundCache>()->getSound(PathUtils::resourcesUrl("sounds/snap.wav"));
|
||||
|
||||
|
||||
QVariant testProperty = property(hifi::properties::TEST);
|
||||
qDebug() << testProperty;
|
||||
if (testProperty.isValid()) {
|
||||
|
@ -6577,7 +6577,7 @@ void Application::addAssetToWorldFromURL(QString url) {
|
|||
} else {
|
||||
filename.remove(".zip");
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
if (!DependencyManager::get<NodeList>()->getThisNodeCanWriteAssets()) {
|
||||
|
@ -6751,7 +6751,7 @@ void Application::addAssetToWorldSetMapping(QString filePath, QString mapping, Q
|
|||
addAssetToWorldError(filenameFromPath(filePath), errorInfo);
|
||||
} else {
|
||||
// to prevent files that aren't models or texture files from being loaded into world automatically
|
||||
if ((filePath.toLower().endsWith(OBJ_EXTENSION) || filePath.toLower().endsWith(FBX_EXTENSION)) ||
|
||||
if ((filePath.toLower().endsWith(OBJ_EXTENSION) || filePath.toLower().endsWith(FBX_EXTENSION)) ||
|
||||
((filePath.toLower().endsWith(JPG_EXTENSION) || filePath.toLower().endsWith(PNG_EXTENSION)) &&
|
||||
((!isBlocks) && (!isZip)))) {
|
||||
addAssetToWorldAddEntity(filePath, mapping);
|
||||
|
@ -7400,8 +7400,8 @@ bool Application::isThrottleRendering() const {
|
|||
bool Application::hasFocus() const {
|
||||
bool result = (QApplication::activeWindow() != nullptr);
|
||||
#if defined(Q_OS_WIN)
|
||||
// On Windows, QWidget::activateWindow() - as called in setFocus() - makes the application's taskbar icon flash but doesn't
|
||||
// take user focus away from their current window. So also check whether the application is the user's current foreground
|
||||
// On Windows, QWidget::activateWindow() - as called in setFocus() - makes the application's taskbar icon flash but doesn't
|
||||
// take user focus away from their current window. So also check whether the application is the user's current foreground
|
||||
// window.
|
||||
result = result && (HWND)QApplication::activeWindow()->winId() == GetForegroundWindow();
|
||||
#endif
|
||||
|
@ -7409,7 +7409,7 @@ bool Application::hasFocus() const {
|
|||
}
|
||||
|
||||
void Application::setFocus() {
|
||||
// Note: Windows doesn't allow a user focus to be taken away from another application. Instead, it changes the color of and
|
||||
// Note: Windows doesn't allow a user focus to be taken away from another application. Instead, it changes the color of and
|
||||
// flashes the taskbar icon.
|
||||
auto window = qApp->getWindow();
|
||||
window->activateWindow();
|
||||
|
@ -7650,7 +7650,7 @@ void Application::updateDisplayMode() {
|
|||
menu->setIsOptionChecked(MenuOption::FirstPerson, true);
|
||||
cameraMenuChanged();
|
||||
}
|
||||
|
||||
|
||||
// Remove the mirror camera option from menu if in HMD mode
|
||||
auto mirrorAction = menu->getActionForOption(MenuOption::FullscreenMirror);
|
||||
mirrorAction->setVisible(!isHmd);
|
||||
|
|
|
@ -35,7 +35,19 @@ ModelOverlay::ModelOverlay(const ModelOverlay* modelOverlay) :
|
|||
_url(modelOverlay->_url),
|
||||
_updateModel(false),
|
||||
_scaleToFit(modelOverlay->_scaleToFit),
|
||||
_loadPriority(modelOverlay->_loadPriority)
|
||||
_loadPriority(modelOverlay->_loadPriority),
|
||||
|
||||
_animationURL(modelOverlay->_animationURL),
|
||||
_animationFPS(modelOverlay->_animationFPS),
|
||||
_animationCurrentFrame(modelOverlay->_animationCurrentFrame),
|
||||
_animationRunning(modelOverlay->_animationRunning),
|
||||
_animationLoop(modelOverlay->_animationLoop),
|
||||
_animationFirstFrame(modelOverlay->_animationFirstFrame),
|
||||
_animationLastFrame(modelOverlay->_animationLastFrame),
|
||||
_animationHold(modelOverlay->_animationHold),
|
||||
_animationAllowTranslation(modelOverlay->_animationAllowTranslation)
|
||||
|
||||
// Joint translations and rotations aren't copied because the model needs to load before they can be applied.
|
||||
{
|
||||
_model->setLoadingPriority(_loadPriority);
|
||||
if (_url.isValid()) {
|
||||
|
@ -341,20 +353,25 @@ vectorType ModelOverlay::mapJoints(mapFunction<itemType> function) const {
|
|||
* <code>parentID</code> is an avatar skeleton. A value of <code>65535</code> means "no joint".
|
||||
*
|
||||
* @property {string} url - The URL of the FBX or OBJ model used for the overlay.
|
||||
* @property {number} loadPriority=0.0 - The priority for loading and displaying the overlay. Overlays with higher values load
|
||||
* first.
|
||||
* @property {Vec3} dimensions - The dimensions of the overlay. Synonym: <code>size</code>.
|
||||
* @property {Vec3} scale - The scale factor applied to the model's dimensions.
|
||||
* @property {object.<name, url>} textures - Maps the named textures in the model to the JPG or PNG images in the urls.
|
||||
* @property {Array.<string>} jointNames - The names of the joints - if any - in the model. <em>Read-only</em>
|
||||
* @property {Array.<Quat>} jointRotations - The relative rotations of the model's joints.
|
||||
* @property {Array.<Vec3>} jointTranslations - The relative translations of the model's joints.
|
||||
* @property {Array.<string>} jointNames - The names of the joints - if any - in the model. <em>Read-only.</em>
|
||||
* @property {Array.<Quat>} jointRotations - The relative rotations of the model's joints. <em>Not copied if overlay is
|
||||
* cloned.</em>
|
||||
* @property {Array.<Vec3>} jointTranslations - The relative translations of the model's joints. <em>Not copied if overlay is
|
||||
* cloned.</em>
|
||||
* @property {Array.<Quat>} jointOrientations - The absolute orientations of the model's joints, in world coordinates.
|
||||
* <em>Read-only</em>
|
||||
* <em>Read-only.</em>
|
||||
* @property {Array.<Vec3>} jointPositions - The absolute positions of the model's joints, in world coordinates.
|
||||
* <em>Read-only</em>
|
||||
* <em>Read-only.</em>
|
||||
* @property {string} animationSettings.url="" - The URL of an FBX file containing an animation to play.
|
||||
* @property {number} animationSettings.fps=0 - The frame rate (frames/sec) to play the animation at.
|
||||
* @property {number} animationSettings.firstFrame=0 - The frame to start playing at.
|
||||
* @property {number} animationSettings.lastFrame=0 - The frame to finish playing at.
|
||||
* @property {number} animationSettings.currentFrame=0 - The current frame being played.
|
||||
* @property {boolean} animationSettings.running=false - Whether or not the animation is playing.
|
||||
* @property {boolean} animationSettings.loop=false - Whether or not the animation should repeat in a loop.
|
||||
* @property {boolean} animationSettings.hold=false - Whether or not when the animation finishes, the rotations and
|
||||
|
@ -384,6 +401,10 @@ QVariant ModelOverlay::getProperty(const QString& property) {
|
|||
}
|
||||
}
|
||||
|
||||
if (property == "loadPriority") {
|
||||
return _loadPriority;
|
||||
}
|
||||
|
||||
if (property == "jointNames") {
|
||||
if (_model && _model->isActive()) {
|
||||
// note: going through Rig because Model::getJointNames() (which proxies to FBXGeometry) was always empty
|
||||
|
|
|
@ -2058,24 +2058,34 @@ static const QString JSON_AVATAR_ENTITIES = QStringLiteral("attachedEntities");
|
|||
static const QString JSON_AVATAR_SCALE = QStringLiteral("scale");
|
||||
static const QString JSON_AVATAR_VERSION = QStringLiteral("version");
|
||||
|
||||
static const int JSON_AVATAR_JOINT_ROTATIONS_IN_RELATIVE_FRAME_VERSION = 0;
|
||||
static const int JSON_AVATAR_JOINT_ROTATIONS_IN_ABSOLUTE_FRAME_VERSION = 1;
|
||||
enum class JsonAvatarFrameVersion : int {
|
||||
JointRotationsInRelativeFrame = 0,
|
||||
JointRotationsInAbsoluteFrame,
|
||||
JointDefaultPoseBits
|
||||
};
|
||||
|
||||
QJsonValue toJsonValue(const JointData& joint) {
|
||||
QJsonArray result;
|
||||
result.push_back(toJsonValue(joint.rotation));
|
||||
result.push_back(toJsonValue(joint.translation));
|
||||
result.push_back(QJsonValue(joint.rotationIsDefaultPose));
|
||||
result.push_back(QJsonValue(joint.translationIsDefaultPose));
|
||||
return result;
|
||||
}
|
||||
|
||||
JointData jointDataFromJsonValue(const QJsonValue& json) {
|
||||
JointData jointDataFromJsonValue(int version, const QJsonValue& json) {
|
||||
JointData result;
|
||||
if (json.isArray()) {
|
||||
QJsonArray array = json.toArray();
|
||||
result.rotation = quatFromJsonValue(array[0]);
|
||||
result.rotationIsDefaultPose = false;
|
||||
result.translation = vec3FromJsonValue(array[1]);
|
||||
result.translationIsDefaultPose = false;
|
||||
if (version >= (int)JsonAvatarFrameVersion::JointDefaultPoseBits) {
|
||||
result.rotationIsDefaultPose = array[2].toBool();
|
||||
result.translationIsDefaultPose = array[3].toBool();
|
||||
} else {
|
||||
result.rotationIsDefaultPose = false;
|
||||
result.translationIsDefaultPose = false;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
@ -2083,7 +2093,7 @@ JointData jointDataFromJsonValue(const QJsonValue& json) {
|
|||
QJsonObject AvatarData::toJson() const {
|
||||
QJsonObject root;
|
||||
|
||||
root[JSON_AVATAR_VERSION] = JSON_AVATAR_JOINT_ROTATIONS_IN_ABSOLUTE_FRAME_VERSION;
|
||||
root[JSON_AVATAR_VERSION] = (int)JsonAvatarFrameVersion::JointDefaultPoseBits;
|
||||
|
||||
if (!getSkeletonModelURL().isEmpty()) {
|
||||
root[JSON_AVATAR_BODY_MODEL] = getSkeletonModelURL().toString();
|
||||
|
@ -2158,7 +2168,7 @@ void AvatarData::fromJson(const QJsonObject& json, bool useFrameSkeleton) {
|
|||
version = json[JSON_AVATAR_VERSION].toInt();
|
||||
} else {
|
||||
// initial data did not have a version field.
|
||||
version = JSON_AVATAR_JOINT_ROTATIONS_IN_RELATIVE_FRAME_VERSION;
|
||||
version = (int)JsonAvatarFrameVersion::JointRotationsInRelativeFrame;
|
||||
}
|
||||
|
||||
if (json.contains(JSON_AVATAR_BODY_MODEL)) {
|
||||
|
@ -2235,7 +2245,7 @@ void AvatarData::fromJson(const QJsonObject& json, bool useFrameSkeleton) {
|
|||
// }
|
||||
|
||||
if (json.contains(JSON_AVATAR_JOINT_ARRAY)) {
|
||||
if (version == JSON_AVATAR_JOINT_ROTATIONS_IN_RELATIVE_FRAME_VERSION) {
|
||||
if (version == (int)JsonAvatarFrameVersion::JointRotationsInRelativeFrame) {
|
||||
// because we don't have the full joint hierarchy skeleton of the model,
|
||||
// we can't properly convert from relative rotations into absolute rotations.
|
||||
quint64 now = usecTimestampNow();
|
||||
|
@ -2247,7 +2257,7 @@ void AvatarData::fromJson(const QJsonObject& json, bool useFrameSkeleton) {
|
|||
QJsonArray jointArrayJson = json[JSON_AVATAR_JOINT_ARRAY].toArray();
|
||||
jointArray.reserve(jointArrayJson.size());
|
||||
for (const auto& jointJson : jointArrayJson) {
|
||||
auto joint = jointDataFromJsonValue(jointJson);
|
||||
auto joint = jointDataFromJsonValue(version, jointJson);
|
||||
jointArray.push_back(joint);
|
||||
}
|
||||
setRawJointData(jointArray);
|
||||
|
@ -2558,4 +2568,4 @@ void AvatarEntityMapFromScriptValue(const QScriptValue& object, AvatarEntityMap&
|
|||
|
||||
value[EntityID] = binaryEntityProperties;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,7 +18,7 @@ bool MaterialEntityRenderer::needsRenderUpdateFromTypedEntity(const TypedEntityP
|
|||
if (entity->getMaterial() != _drawMaterial) {
|
||||
return true;
|
||||
}
|
||||
if (entity->getParentID() != _parentID || entity->getClientOnly() != _clientOnly || entity->getOwningAvatarID() != _owningAvatarID) {
|
||||
if (entity->getParentID() != _parentID) {
|
||||
return true;
|
||||
}
|
||||
if (entity->getMaterialMappingPos() != _materialMappingPos || entity->getMaterialMappingScale() != _materialMappingScale || entity->getMaterialMappingRot() != _materialMappingRot) {
|
||||
|
@ -31,8 +31,6 @@ void MaterialEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer&
|
|||
withWriteLock([&] {
|
||||
_drawMaterial = entity->getMaterial();
|
||||
_parentID = entity->getParentID();
|
||||
_clientOnly = entity->getClientOnly();
|
||||
_owningAvatarID = entity->getOwningAvatarID();
|
||||
_materialMappingPos = entity->getMaterialMappingPos();
|
||||
_materialMappingScale = entity->getMaterialMappingScale();
|
||||
_materialMappingRot = entity->getMaterialMappingRot();
|
||||
|
@ -102,7 +100,7 @@ void MaterialEntityRenderer::doRender(RenderArgs* args) {
|
|||
graphics::MaterialPointer drawMaterial;
|
||||
Transform textureTransform;
|
||||
withReadLock([&] {
|
||||
parentID = _clientOnly ? _owningAvatarID : _parentID;
|
||||
parentID = _parentID;
|
||||
renderTransform = _renderTransform;
|
||||
drawMaterial = _drawMaterial;
|
||||
textureTransform.setTranslation(glm::vec3(_materialMappingPos, 0));
|
||||
|
|
|
@ -32,8 +32,6 @@ private:
|
|||
ShapeKey getShapeKey() override;
|
||||
|
||||
QUuid _parentID;
|
||||
bool _clientOnly;
|
||||
QUuid _owningAvatarID;
|
||||
glm::vec2 _materialMappingPos;
|
||||
glm::vec2 _materialMappingScale;
|
||||
float _materialMappingRot;
|
||||
|
|
|
@ -363,6 +363,7 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const {
|
|||
CHECK_PROPERTY_CHANGE(PROP_MATERIAL_MAPPING_POS, materialMappingPos);
|
||||
CHECK_PROPERTY_CHANGE(PROP_MATERIAL_MAPPING_SCALE, materialMappingScale);
|
||||
CHECK_PROPERTY_CHANGE(PROP_MATERIAL_MAPPING_ROT, materialMappingRot);
|
||||
CHECK_PROPERTY_CHANGE(PROP_MATERIAL_DATA, materialData);
|
||||
|
||||
// Certifiable Properties
|
||||
CHECK_PROPERTY_CHANGE(PROP_ITEM_NAME, itemName);
|
||||
|
@ -1208,6 +1209,7 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool
|
|||
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_ACTION_DATA, actionData);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_LOCKED, locked);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_USER_DATA, userData);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_ALPHA, alpha);
|
||||
|
||||
// Certifiable Properties
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_ITEM_NAME, itemName);
|
||||
|
@ -1252,7 +1254,6 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool
|
|||
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_COLOR_SPREAD, colorSpread);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_COLOR_START, colorStart);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_COLOR_FINISH, colorFinish);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_ALPHA, alpha);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_ALPHA_SPREAD, alphaSpread);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_ALPHA_START, alphaStart);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_ALPHA_FINISH, alphaFinish);
|
||||
|
@ -1376,6 +1377,7 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool
|
|||
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_MATERIAL_MAPPING_POS, materialMappingPos);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_MATERIAL_MAPPING_SCALE, materialMappingScale);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_MATERIAL_MAPPING_ROT, materialMappingRot);
|
||||
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_MATERIAL_DATA, materialData);
|
||||
}
|
||||
|
||||
/**jsdoc
|
||||
|
@ -1539,6 +1541,7 @@ void EntityItemProperties::copyFromScriptValue(const QScriptValue& object, bool
|
|||
COPY_PROPERTY_FROM_QSCRIPTVALUE(materialMappingPos, glmVec2, setMaterialMappingPos);
|
||||
COPY_PROPERTY_FROM_QSCRIPTVALUE(materialMappingScale, glmVec2, setMaterialMappingScale);
|
||||
COPY_PROPERTY_FROM_QSCRIPTVALUE(materialMappingRot, float, setMaterialMappingRot);
|
||||
COPY_PROPERTY_FROM_QSCRIPTVALUE(materialData, QString, setMaterialData);
|
||||
|
||||
// Certifiable Properties
|
||||
COPY_PROPERTY_FROM_QSCRIPTVALUE(itemName, QString, setItemName);
|
||||
|
@ -1902,6 +1905,7 @@ void EntityItemProperties::entityPropertyFlagsFromScriptValue(const QScriptValue
|
|||
ADD_PROPERTY_TO_MAP(PROP_MATERIAL_MAPPING_POS, MaterialMappingPos, materialMappingPos, glmVec2);
|
||||
ADD_PROPERTY_TO_MAP(PROP_MATERIAL_MAPPING_SCALE, MaterialMappingScale, materialMappingScale, glmVec2);
|
||||
ADD_PROPERTY_TO_MAP(PROP_MATERIAL_MAPPING_ROT, MaterialMappingRot, materialMappingRot, float);
|
||||
ADD_PROPERTY_TO_MAP(PROP_MATERIAL_DATA, MaterialData, materialData, QString);
|
||||
|
||||
// Certifiable Properties
|
||||
ADD_PROPERTY_TO_MAP(PROP_ITEM_NAME, ItemName, itemName, QString);
|
||||
|
@ -2298,6 +2302,7 @@ OctreeElement::AppendState EntityItemProperties::encodeEntityEditPacket(PacketTy
|
|||
APPEND_ENTITY_PROPERTY(PROP_MATERIAL_MAPPING_POS, properties.getMaterialMappingPos());
|
||||
APPEND_ENTITY_PROPERTY(PROP_MATERIAL_MAPPING_SCALE, properties.getMaterialMappingScale());
|
||||
APPEND_ENTITY_PROPERTY(PROP_MATERIAL_MAPPING_ROT, properties.getMaterialMappingRot());
|
||||
APPEND_ENTITY_PROPERTY(PROP_MATERIAL_DATA, properties.getMaterialData());
|
||||
}
|
||||
|
||||
APPEND_ENTITY_PROPERTY(PROP_NAME, properties.getName());
|
||||
|
@ -2666,6 +2671,7 @@ bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int
|
|||
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_MATERIAL_MAPPING_POS, glmVec2, setMaterialMappingPos);
|
||||
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_MATERIAL_MAPPING_SCALE, glmVec2, setMaterialMappingScale);
|
||||
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_MATERIAL_MAPPING_ROT, float, setMaterialMappingRot);
|
||||
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_MATERIAL_DATA, QString, setMaterialData);
|
||||
}
|
||||
|
||||
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_NAME, QString, setName);
|
||||
|
@ -2849,6 +2855,7 @@ void EntityItemProperties::markAllChanged() {
|
|||
_materialMappingPosChanged = true;
|
||||
_materialMappingScaleChanged = true;
|
||||
_materialMappingRotChanged = true;
|
||||
_materialDataChanged = true;
|
||||
|
||||
// Certifiable Properties
|
||||
_itemNameChanged = true;
|
||||
|
@ -3200,6 +3207,9 @@ QList<QString> EntityItemProperties::listChangedProperties() {
|
|||
if (materialMappingRotChanged()) {
|
||||
out += "materialMappingRot";
|
||||
}
|
||||
if (materialDataChanged()) {
|
||||
out += "materialData";
|
||||
}
|
||||
|
||||
// Certifiable Properties
|
||||
if (itemNameChanged()) {
|
||||
|
|
|
@ -230,6 +230,7 @@ public:
|
|||
DEFINE_PROPERTY_REF(PROP_MATERIAL_MAPPING_POS, MaterialMappingPos, materialMappingPos, glmVec2, glm::vec2(0, 0));
|
||||
DEFINE_PROPERTY_REF(PROP_MATERIAL_MAPPING_SCALE, MaterialMappingScale, materialMappingScale, glmVec2, glm::vec2(1, 1));
|
||||
DEFINE_PROPERTY_REF(PROP_MATERIAL_MAPPING_ROT, MaterialMappingRot, materialMappingRot, float, 0);
|
||||
DEFINE_PROPERTY_REF(PROP_MATERIAL_DATA, MaterialData, materialData, QString, "");
|
||||
|
||||
// Certifiable Properties - related to Proof of Purchase certificates
|
||||
DEFINE_PROPERTY_REF(PROP_ITEM_NAME, ItemName, itemName, QString, ENTITY_ITEM_DEFAULT_ITEM_NAME);
|
||||
|
|
|
@ -240,6 +240,7 @@ enum EntityPropertyList {
|
|||
PROP_MATERIAL_MAPPING_POS,
|
||||
PROP_MATERIAL_MAPPING_SCALE,
|
||||
PROP_MATERIAL_MAPPING_ROT,
|
||||
PROP_MATERIAL_DATA,
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// ATTENTION: add new properties to end of list just ABOVE this line
|
||||
|
|
|
@ -2382,6 +2382,30 @@ bool EntityTree::readFromMap(QVariantMap& map) {
|
|||
}
|
||||
}
|
||||
|
||||
// Convert old materials so that they use materialData instead of userData
|
||||
if (contentVersion < (int)EntityVersion::MaterialData && properties.getType() == EntityTypes::EntityType::Material) {
|
||||
if (properties.getMaterialURL().startsWith("userData")) {
|
||||
QString materialURL = properties.getMaterialURL();
|
||||
properties.setMaterialURL(materialURL.replace("userData", "materialData"));
|
||||
|
||||
QJsonObject userData = QJsonDocument::fromJson(properties.getUserData().toUtf8()).object();
|
||||
QJsonObject materialData;
|
||||
QJsonValue materialVersion = userData["materialVersion"];
|
||||
if (!materialVersion.isNull()) {
|
||||
materialData.insert("materialVersion", materialVersion);
|
||||
userData.remove("materialVersion");
|
||||
}
|
||||
QJsonValue materials = userData["materials"];
|
||||
if (!materials.isNull()) {
|
||||
materialData.insert("materials", materials);
|
||||
userData.remove("materials");
|
||||
}
|
||||
|
||||
properties.setMaterialData(QJsonDocument(materialData).toJson());
|
||||
properties.setUserData(QJsonDocument(userData).toJson());
|
||||
}
|
||||
}
|
||||
|
||||
EntityItemPointer entity = addEntity(entityItemID, properties);
|
||||
if (!entity) {
|
||||
qCDebug(entities) << "adding Entity failed:" << entityItemID << properties.getType();
|
||||
|
|
|
@ -27,6 +27,10 @@ MaterialEntityItem::MaterialEntityItem(const EntityItemID& entityItemID) : Entit
|
|||
_type = EntityTypes::Material;
|
||||
}
|
||||
|
||||
MaterialEntityItem::~MaterialEntityItem() {
|
||||
removeMaterial();
|
||||
}
|
||||
|
||||
EntityItemProperties MaterialEntityItem::getProperties(EntityPropertyFlags desiredProperties) const {
|
||||
EntityItemProperties properties = EntityItem::getProperties(desiredProperties); // get the properties from our base class
|
||||
COPY_ENTITY_PROPERTY_TO_PROPERTIES(materialURL, getMaterialURL);
|
||||
|
@ -36,6 +40,7 @@ EntityItemProperties MaterialEntityItem::getProperties(EntityPropertyFlags desir
|
|||
COPY_ENTITY_PROPERTY_TO_PROPERTIES(materialMappingPos, getMaterialMappingPos);
|
||||
COPY_ENTITY_PROPERTY_TO_PROPERTIES(materialMappingScale, getMaterialMappingScale);
|
||||
COPY_ENTITY_PROPERTY_TO_PROPERTIES(materialMappingRot, getMaterialMappingRot);
|
||||
COPY_ENTITY_PROPERTY_TO_PROPERTIES(materialData, getMaterialData);
|
||||
return properties;
|
||||
}
|
||||
|
||||
|
@ -49,6 +54,7 @@ bool MaterialEntityItem::setProperties(const EntityItemProperties& properties) {
|
|||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(materialMappingPos, setMaterialMappingPos);
|
||||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(materialMappingScale, setMaterialMappingScale);
|
||||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(materialMappingRot, setMaterialMappingRot);
|
||||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(materialData, setMaterialData);
|
||||
|
||||
if (somethingChanged) {
|
||||
bool wantDebug = false;
|
||||
|
@ -78,6 +84,7 @@ int MaterialEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* da
|
|||
READ_ENTITY_PROPERTY(PROP_MATERIAL_MAPPING_POS, glm::vec2, setMaterialMappingPos);
|
||||
READ_ENTITY_PROPERTY(PROP_MATERIAL_MAPPING_SCALE, glm::vec2, setMaterialMappingScale);
|
||||
READ_ENTITY_PROPERTY(PROP_MATERIAL_MAPPING_ROT, float, setMaterialMappingRot);
|
||||
READ_ENTITY_PROPERTY(PROP_MATERIAL_DATA, QString, setMaterialData);
|
||||
|
||||
return bytesRead;
|
||||
}
|
||||
|
@ -93,6 +100,7 @@ EntityPropertyFlags MaterialEntityItem::getEntityProperties(EncodeBitstreamParam
|
|||
requestedProperties += PROP_MATERIAL_MAPPING_POS;
|
||||
requestedProperties += PROP_MATERIAL_MAPPING_SCALE;
|
||||
requestedProperties += PROP_MATERIAL_MAPPING_ROT;
|
||||
requestedProperties += PROP_MATERIAL_DATA;
|
||||
return requestedProperties;
|
||||
}
|
||||
|
||||
|
@ -112,6 +120,7 @@ void MaterialEntityItem::appendSubclassData(OctreePacketData* packetData, Encode
|
|||
APPEND_ENTITY_PROPERTY(PROP_MATERIAL_MAPPING_POS, getMaterialMappingPos());
|
||||
APPEND_ENTITY_PROPERTY(PROP_MATERIAL_MAPPING_SCALE, getMaterialMappingScale());
|
||||
APPEND_ENTITY_PROPERTY(PROP_MATERIAL_MAPPING_ROT, getMaterialMappingRot());
|
||||
APPEND_ENTITY_PROPERTY(PROP_MATERIAL_DATA, getMaterialData());
|
||||
}
|
||||
|
||||
void MaterialEntityItem::debugDump() const {
|
||||
|
@ -145,9 +154,9 @@ std::shared_ptr<NetworkMaterial> MaterialEntityItem::getMaterial() const {
|
|||
}
|
||||
}
|
||||
|
||||
void MaterialEntityItem::setMaterialURL(const QString& materialURLString, bool userDataChanged) {
|
||||
bool usingUserData = materialURLString.startsWith("userData");
|
||||
if (_materialURL != materialURLString || (usingUserData && userDataChanged)) {
|
||||
void MaterialEntityItem::setMaterialURL(const QString& materialURLString, bool materialDataChanged) {
|
||||
bool usingMaterialData = materialDataChanged || materialURLString.startsWith("materialData");
|
||||
if (_materialURL != materialURLString || (usingMaterialData && materialDataChanged)) {
|
||||
removeMaterial();
|
||||
_materialURL = materialURLString;
|
||||
|
||||
|
@ -156,8 +165,8 @@ void MaterialEntityItem::setMaterialURL(const QString& materialURLString, bool u
|
|||
_currentMaterialName = split.last().toStdString();
|
||||
}
|
||||
|
||||
if (usingUserData) {
|
||||
_parsedMaterials = NetworkMaterialResource::parseJSONMaterials(QJsonDocument::fromJson(getUserData().toUtf8()), materialURLString);
|
||||
if (usingMaterialData) {
|
||||
_parsedMaterials = NetworkMaterialResource::parseJSONMaterials(QJsonDocument::fromJson(getMaterialData().toUtf8()), materialURLString);
|
||||
|
||||
// Since our material changed, the current name might not be valid anymore, so we need to update
|
||||
setCurrentMaterialName(_currentMaterialName);
|
||||
|
@ -191,11 +200,11 @@ void MaterialEntityItem::setCurrentMaterialName(const std::string& currentMateri
|
|||
}
|
||||
}
|
||||
|
||||
void MaterialEntityItem::setUserData(const QString& userData) {
|
||||
if (_userData != userData) {
|
||||
EntityItem::setUserData(userData);
|
||||
if (_materialURL.startsWith("userData")) {
|
||||
// Trigger material update when user data changes
|
||||
void MaterialEntityItem::setMaterialData(const QString& materialData) {
|
||||
if (_materialData != materialData) {
|
||||
_materialData = materialData;
|
||||
if (_materialURL.startsWith("materialData")) {
|
||||
// Trigger material update when material data changes
|
||||
setMaterialURL(_materialURL, true);
|
||||
}
|
||||
}
|
||||
|
@ -249,28 +258,12 @@ void MaterialEntityItem::setParentID(const QUuid& parentID) {
|
|||
}
|
||||
}
|
||||
|
||||
void MaterialEntityItem::setClientOnly(bool clientOnly) {
|
||||
if (getClientOnly() != clientOnly) {
|
||||
removeMaterial();
|
||||
EntityItem::setClientOnly(clientOnly);
|
||||
applyMaterial();
|
||||
}
|
||||
}
|
||||
|
||||
void MaterialEntityItem::setOwningAvatarID(const QUuid& owningAvatarID) {
|
||||
if (getOwningAvatarID() != owningAvatarID) {
|
||||
removeMaterial();
|
||||
EntityItem::setOwningAvatarID(owningAvatarID);
|
||||
applyMaterial();
|
||||
}
|
||||
}
|
||||
|
||||
void MaterialEntityItem::removeMaterial() {
|
||||
graphics::MaterialPointer material = getMaterial();
|
||||
if (!material) {
|
||||
return;
|
||||
}
|
||||
QUuid parentID = getClientOnly() ? getOwningAvatarID() : getParentID();
|
||||
QUuid parentID = getParentID();
|
||||
if (parentID.isNull()) {
|
||||
return;
|
||||
}
|
||||
|
@ -294,7 +287,7 @@ void MaterialEntityItem::removeMaterial() {
|
|||
void MaterialEntityItem::applyMaterial() {
|
||||
_retryApply = false;
|
||||
graphics::MaterialPointer material = getMaterial();
|
||||
QUuid parentID = getClientOnly() ? getOwningAvatarID() : getParentID();
|
||||
QUuid parentID = getParentID();
|
||||
if (!material || parentID.isNull()) {
|
||||
return;
|
||||
}
|
||||
|
@ -328,11 +321,6 @@ void MaterialEntityItem::postParentFixup() {
|
|||
applyMaterial();
|
||||
}
|
||||
|
||||
void MaterialEntityItem::preDelete() {
|
||||
EntityItem::preDelete();
|
||||
removeMaterial();
|
||||
}
|
||||
|
||||
void MaterialEntityItem::update(const quint64& now) {
|
||||
if (_retryApply) {
|
||||
applyMaterial();
|
||||
|
|
|
@ -21,6 +21,7 @@ public:
|
|||
static EntityItemPointer factory(const EntityItemID& entityID, const EntityItemProperties& properties);
|
||||
|
||||
MaterialEntityItem(const EntityItemID& entityItemID);
|
||||
~MaterialEntityItem();
|
||||
|
||||
ALLOW_INSTANTIATION // This class can be instantiated
|
||||
|
||||
|
@ -53,7 +54,7 @@ public:
|
|||
virtual void setUnscaledDimensions(const glm::vec3& value) override;
|
||||
|
||||
QString getMaterialURL() const { return _materialURL; }
|
||||
void setMaterialURL(const QString& materialURLString, bool userDataChanged = false);
|
||||
void setMaterialURL(const QString& materialURLString, bool materialDataChanged = false);
|
||||
|
||||
void setCurrentMaterialName(const std::string& currentMaterialName);
|
||||
|
||||
|
@ -73,21 +74,20 @@ public:
|
|||
float getMaterialMappingRot() const { return _materialMappingRot; }
|
||||
void setMaterialMappingRot(const float& materialMappingRot);
|
||||
|
||||
QString getMaterialData() const { return _materialData; }
|
||||
void setMaterialData(const QString& materialData);
|
||||
|
||||
std::shared_ptr<NetworkMaterial> getMaterial() const;
|
||||
|
||||
void setUserData(const QString& userData) override;
|
||||
void setParentID(const QUuid& parentID) override;
|
||||
void setClientOnly(bool clientOnly) override;
|
||||
void setOwningAvatarID(const QUuid& owningAvatarID) override;
|
||||
|
||||
void applyMaterial();
|
||||
void removeMaterial();
|
||||
|
||||
void postParentFixup() override;
|
||||
void preDelete() override;
|
||||
|
||||
private:
|
||||
// URL for this material. Currently, only JSON format is supported. Set to "userData" to use the user data to live edit a material.
|
||||
// URL for this material. Currently, only JSON format is supported. Set to "materialData" to use the material data to live edit a material.
|
||||
// The following fields are supported in the JSON:
|
||||
// materialVersion: a uint for the version of this network material (currently, only 1 is supported)
|
||||
// materials, which is either an object or an array of objects, each with the following properties:
|
||||
|
@ -117,6 +117,7 @@ private:
|
|||
glm::vec2 _materialMappingScale { 1, 1 };
|
||||
// How much to rotate this material within its parent's UV-space (degrees)
|
||||
float _materialMappingRot { 0 };
|
||||
QString _materialData;
|
||||
|
||||
NetworkMaterialResourcePointer _networkMaterial;
|
||||
NetworkMaterialResource::ParsedMaterials _parsedMaterials;
|
||||
|
|
|
@ -117,8 +117,10 @@ ShapeEntityItem::ShapeEntityItem(const EntityItemID& entityItemID) : EntityItem(
|
|||
|
||||
EntityItemProperties ShapeEntityItem::getProperties(EntityPropertyFlags desiredProperties) const {
|
||||
EntityItemProperties properties = EntityItem::getProperties(desiredProperties); // get the properties from our base class
|
||||
properties.setColor(getXColor());
|
||||
properties.setShape(entity::stringFromShape(getShape()));
|
||||
COPY_ENTITY_PROPERTY_TO_PROPERTIES(color, getXColor);
|
||||
COPY_ENTITY_PROPERTY_TO_PROPERTIES(alpha, getAlpha);
|
||||
|
||||
return properties;
|
||||
}
|
||||
|
||||
|
|
|
@ -105,7 +105,7 @@ public:
|
|||
|
||||
protected:
|
||||
|
||||
float _alpha { 1 }; // FIXME: This property is not used.
|
||||
float _alpha { 1.0f };
|
||||
rgbColor _color;
|
||||
entity::Shape _shape { entity::Shape::Sphere };
|
||||
|
||||
|
|
|
@ -370,7 +370,36 @@ GLenum GLTexelFormat::evalGLTexelFormatInternal(const gpu::Element& dstFormat) {
|
|||
case gpu::COMPRESSED_BC7_SRGBA:
|
||||
result = GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM;
|
||||
break;
|
||||
|
||||
case gpu::COMPRESSED_ETC2_RGB:
|
||||
result = GL_COMPRESSED_RGB8_ETC2;
|
||||
break;
|
||||
case gpu::COMPRESSED_ETC2_SRGB:
|
||||
result = GL_COMPRESSED_SRGB8_ETC2;
|
||||
break;
|
||||
case gpu::COMPRESSED_ETC2_RGB_PUNCHTHROUGH_ALPHA:
|
||||
result = GL_COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2;
|
||||
break;
|
||||
case gpu::COMPRESSED_ETC2_SRGB_PUNCHTHROUGH_ALPHA:
|
||||
result = GL_COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2;
|
||||
break;
|
||||
case gpu::COMPRESSED_ETC2_RGBA:
|
||||
result = GL_COMPRESSED_RGBA8_ETC2_EAC;
|
||||
break;
|
||||
case gpu::COMPRESSED_ETC2_SRGBA:
|
||||
result = GL_COMPRESSED_SRGB8_ALPHA8_ETC2_EAC;
|
||||
break;
|
||||
case gpu::COMPRESSED_EAC_RED:
|
||||
result = GL_COMPRESSED_R11_EAC;
|
||||
break;
|
||||
case gpu::COMPRESSED_EAC_RED_SIGNED:
|
||||
result = GL_COMPRESSED_SIGNED_R11_EAC;
|
||||
break;
|
||||
case gpu::COMPRESSED_EAC_XY:
|
||||
result = GL_COMPRESSED_RG11_EAC;
|
||||
break;
|
||||
case gpu::COMPRESSED_EAC_XY_SIGNED:
|
||||
result = GL_COMPRESSED_SIGNED_RG11_EAC;
|
||||
break;
|
||||
default:
|
||||
qCWarning(gpugllogging) << "Unknown combination of texel format";
|
||||
}
|
||||
|
@ -531,6 +560,36 @@ GLTexelFormat GLTexelFormat::evalGLTexelFormat(const Element& dstFormat, const E
|
|||
case gpu::COMPRESSED_BC7_SRGBA:
|
||||
texel.internalFormat = GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM;
|
||||
break;
|
||||
case gpu::COMPRESSED_ETC2_RGB:
|
||||
texel.internalFormat = GL_COMPRESSED_RGB8_ETC2;
|
||||
break;
|
||||
case gpu::COMPRESSED_ETC2_SRGB:
|
||||
texel.internalFormat = GL_COMPRESSED_SRGB8_ETC2;
|
||||
break;
|
||||
case gpu::COMPRESSED_ETC2_RGB_PUNCHTHROUGH_ALPHA:
|
||||
texel.internalFormat = GL_COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2;
|
||||
break;
|
||||
case gpu::COMPRESSED_ETC2_SRGB_PUNCHTHROUGH_ALPHA:
|
||||
texel.internalFormat = GL_COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2;
|
||||
break;
|
||||
case gpu::COMPRESSED_ETC2_RGBA:
|
||||
texel.internalFormat = GL_COMPRESSED_RGBA8_ETC2_EAC;
|
||||
break;
|
||||
case gpu::COMPRESSED_ETC2_SRGBA:
|
||||
texel.internalFormat = GL_COMPRESSED_SRGB8_ALPHA8_ETC2_EAC;
|
||||
break;
|
||||
case gpu::COMPRESSED_EAC_RED:
|
||||
texel.internalFormat = GL_COMPRESSED_R11_EAC;
|
||||
break;
|
||||
case gpu::COMPRESSED_EAC_RED_SIGNED:
|
||||
texel.internalFormat = GL_COMPRESSED_SIGNED_R11_EAC;
|
||||
break;
|
||||
case gpu::COMPRESSED_EAC_XY:
|
||||
texel.internalFormat = GL_COMPRESSED_RG11_EAC;
|
||||
break;
|
||||
case gpu::COMPRESSED_EAC_XY_SIGNED:
|
||||
texel.internalFormat = GL_COMPRESSED_SIGNED_RG11_EAC;
|
||||
break;
|
||||
default:
|
||||
qCWarning(gpugllogging) << "Unknown combination of texel format";
|
||||
}
|
||||
|
@ -895,7 +954,36 @@ GLTexelFormat GLTexelFormat::evalGLTexelFormat(const Element& dstFormat, const E
|
|||
case gpu::COMPRESSED_BC7_SRGBA:
|
||||
texel.internalFormat = GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM;
|
||||
break;
|
||||
|
||||
case gpu::COMPRESSED_ETC2_RGB:
|
||||
texel.internalFormat = GL_COMPRESSED_RGB8_ETC2;
|
||||
break;
|
||||
case gpu::COMPRESSED_ETC2_SRGB:
|
||||
texel.internalFormat = GL_COMPRESSED_SRGB8_ETC2;
|
||||
break;
|
||||
case gpu::COMPRESSED_ETC2_RGB_PUNCHTHROUGH_ALPHA:
|
||||
texel.internalFormat = GL_COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2;
|
||||
break;
|
||||
case gpu::COMPRESSED_ETC2_SRGB_PUNCHTHROUGH_ALPHA:
|
||||
texel.internalFormat = GL_COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2;
|
||||
break;
|
||||
case gpu::COMPRESSED_ETC2_RGBA:
|
||||
texel.internalFormat = GL_COMPRESSED_RGBA8_ETC2_EAC;
|
||||
break;
|
||||
case gpu::COMPRESSED_ETC2_SRGBA:
|
||||
texel.internalFormat = GL_COMPRESSED_SRGB8_ALPHA8_ETC2_EAC;
|
||||
break;
|
||||
case gpu::COMPRESSED_EAC_RED:
|
||||
texel.internalFormat = GL_COMPRESSED_R11_EAC;
|
||||
break;
|
||||
case gpu::COMPRESSED_EAC_RED_SIGNED:
|
||||
texel.internalFormat = GL_COMPRESSED_SIGNED_R11_EAC;
|
||||
break;
|
||||
case gpu::COMPRESSED_EAC_XY:
|
||||
texel.internalFormat = GL_COMPRESSED_RG11_EAC;
|
||||
break;
|
||||
case gpu::COMPRESSED_EAC_XY_SIGNED:
|
||||
texel.internalFormat = GL_COMPRESSED_SIGNED_RG11_EAC;
|
||||
break;
|
||||
default:
|
||||
qCWarning(gpugllogging) << "Unknown combination of texel format";
|
||||
}
|
||||
|
|
|
@ -173,6 +173,8 @@ protected:
|
|||
void makeProgramBindings(ShaderObject& shaderObject) override;
|
||||
int makeResourceBufferSlots(GLuint glprogram, const Shader::BindingSet& slotBindings,Shader::SlotSet& resourceBuffers) override;
|
||||
|
||||
static bool supportedTextureFormat(const gpu::Element& format);
|
||||
|
||||
};
|
||||
|
||||
} }
|
||||
|
|
|
@ -19,6 +19,24 @@ using namespace gpu;
|
|||
using namespace gpu::gl;
|
||||
using namespace gpu::gl41;
|
||||
|
||||
bool GL41Backend::supportedTextureFormat(const gpu::Element& format) {
|
||||
switch (format.getSemantic()) {
|
||||
case gpu::Semantic::COMPRESSED_ETC2_RGB:
|
||||
case gpu::Semantic::COMPRESSED_ETC2_SRGB:
|
||||
case gpu::Semantic::COMPRESSED_ETC2_RGB_PUNCHTHROUGH_ALPHA:
|
||||
case gpu::Semantic::COMPRESSED_ETC2_SRGB_PUNCHTHROUGH_ALPHA:
|
||||
case gpu::Semantic::COMPRESSED_ETC2_RGBA:
|
||||
case gpu::Semantic::COMPRESSED_ETC2_SRGBA:
|
||||
case gpu::Semantic::COMPRESSED_EAC_RED:
|
||||
case gpu::Semantic::COMPRESSED_EAC_RED_SIGNED:
|
||||
case gpu::Semantic::COMPRESSED_EAC_XY:
|
||||
case gpu::Semantic::COMPRESSED_EAC_XY_SIGNED:
|
||||
return false;
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
GLTexture* GL41Backend::syncGPUObject(const TexturePointer& texturePointer) {
|
||||
if (!texturePointer) {
|
||||
return nullptr;
|
||||
|
@ -34,6 +52,11 @@ GLTexture* GL41Backend::syncGPUObject(const TexturePointer& texturePointer) {
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
// Check whether the texture is in a format we can deal with
|
||||
if (!supportedTextureFormat(texture.getTexelFormat())) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
GL41Texture* object = Backend::getGPUObject<GL41Texture>(texture);
|
||||
if (!object) {
|
||||
switch (texture.getUsageType()) {
|
||||
|
|
|
@ -206,6 +206,16 @@ Size GL45Texture::copyMipFaceLinesFromTexture(uint16_t mip, uint8_t face, const
|
|||
case GL_COMPRESSED_RG_RGTC2:
|
||||
case GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM:
|
||||
case GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT:
|
||||
case GL_COMPRESSED_RGB8_ETC2:
|
||||
case GL_COMPRESSED_SRGB8_ETC2:
|
||||
case GL_COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2:
|
||||
case GL_COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2:
|
||||
case GL_COMPRESSED_RGBA8_ETC2_EAC:
|
||||
case GL_COMPRESSED_SRGB8_ALPHA8_ETC2_EAC:
|
||||
case GL_COMPRESSED_R11_EAC:
|
||||
case GL_COMPRESSED_SIGNED_R11_EAC:
|
||||
case GL_COMPRESSED_RG11_EAC:
|
||||
case GL_COMPRESSED_SIGNED_RG11_EAC:
|
||||
glCompressedTextureSubImage2D(_id, mip, 0, yOffset, size.x, size.y, internalFormat,
|
||||
static_cast<GLsizei>(sourceSize), sourcePointer);
|
||||
break;
|
||||
|
@ -222,6 +232,16 @@ Size GL45Texture::copyMipFaceLinesFromTexture(uint16_t mip, uint8_t face, const
|
|||
case GL_COMPRESSED_RG_RGTC2:
|
||||
case GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM:
|
||||
case GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT:
|
||||
case GL_COMPRESSED_RGB8_ETC2:
|
||||
case GL_COMPRESSED_SRGB8_ETC2:
|
||||
case GL_COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2:
|
||||
case GL_COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2:
|
||||
case GL_COMPRESSED_RGBA8_ETC2_EAC:
|
||||
case GL_COMPRESSED_SRGB8_ALPHA8_ETC2_EAC:
|
||||
case GL_COMPRESSED_R11_EAC:
|
||||
case GL_COMPRESSED_SIGNED_R11_EAC:
|
||||
case GL_COMPRESSED_RG11_EAC:
|
||||
case GL_COMPRESSED_SIGNED_RG11_EAC:
|
||||
if (glCompressedTextureSubImage2DEXT) {
|
||||
auto target = GLTexture::CUBE_FACE_LAYOUT[face];
|
||||
glCompressedTextureSubImage2DEXT(_id, target, mip, 0, yOffset, size.x, size.y, internalFormat,
|
||||
|
|
|
@ -20,9 +20,21 @@ using namespace gpu::gl;
|
|||
using namespace gpu::gles;
|
||||
|
||||
bool GLESBackend::supportedTextureFormat(const gpu::Element& format) {
|
||||
// FIXME distinguish between GLES and GL compressed formats after support
|
||||
// for the former is added to gpu::Element
|
||||
return !format.isCompressed();
|
||||
switch (format.getSemantic()) {
|
||||
case gpu::Semantic::COMPRESSED_ETC2_RGB:
|
||||
case gpu::Semantic::COMPRESSED_ETC2_SRGB:
|
||||
case gpu::Semantic::COMPRESSED_ETC2_RGB_PUNCHTHROUGH_ALPHA:
|
||||
case gpu::Semantic::COMPRESSED_ETC2_SRGB_PUNCHTHROUGH_ALPHA:
|
||||
case gpu::Semantic::COMPRESSED_ETC2_RGBA:
|
||||
case gpu::Semantic::COMPRESSED_ETC2_SRGBA:
|
||||
case gpu::Semantic::COMPRESSED_EAC_RED:
|
||||
case gpu::Semantic::COMPRESSED_EAC_RED_SIGNED:
|
||||
case gpu::Semantic::COMPRESSED_EAC_XY:
|
||||
case gpu::Semantic::COMPRESSED_EAC_XY_SIGNED:
|
||||
return true;
|
||||
default:
|
||||
return !format.isCompressed();
|
||||
}
|
||||
}
|
||||
|
||||
GLTexture* GLESBackend::syncGPUObject(const TexturePointer& texturePointer) {
|
||||
|
@ -231,6 +243,29 @@ GLESFixedAllocationTexture::GLESFixedAllocationTexture(const std::weak_ptr<GLBac
|
|||
GLESFixedAllocationTexture::~GLESFixedAllocationTexture() {
|
||||
}
|
||||
|
||||
GLsizei getCompressedImageSize(int width, int height, GLenum internalFormat) {
|
||||
GLsizei blockSize;
|
||||
switch (internalFormat) {
|
||||
case GL_COMPRESSED_R11_EAC:
|
||||
case GL_COMPRESSED_SIGNED_R11_EAC:
|
||||
case GL_COMPRESSED_RGB8_ETC2:
|
||||
case GL_COMPRESSED_SRGB8_ETC2:
|
||||
case GL_COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2:
|
||||
case GL_COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2:
|
||||
blockSize = 8;
|
||||
break;
|
||||
case GL_COMPRESSED_RG11_EAC:
|
||||
case GL_COMPRESSED_SIGNED_RG11_EAC:
|
||||
case GL_COMPRESSED_RGBA8_ETC2_EAC:
|
||||
case GL_COMPRESSED_SRGB8_ALPHA8_ETC2_EAC:
|
||||
default:
|
||||
blockSize = 16;
|
||||
}
|
||||
|
||||
// See https://www.khronos.org/registry/OpenGL-Refpages/es3.0/html/glCompressedTexImage2D.xhtml
|
||||
return (GLsizei)ceil(width / 4.0f) * (GLsizei)ceil(height / 4.0f) * blockSize;
|
||||
}
|
||||
|
||||
void GLESFixedAllocationTexture::allocateStorage() const {
|
||||
const GLTexelFormat texelFormat = GLTexelFormat::evalGLTexelFormat(_gpuObject.getTexelFormat());
|
||||
const auto numMips = _gpuObject.getNumMips();
|
||||
|
@ -239,7 +274,12 @@ void GLESFixedAllocationTexture::allocateStorage() const {
|
|||
for (GLint level = 0; level < numMips; level++) {
|
||||
Vec3u dimensions = _gpuObject.evalMipDimensions(level);
|
||||
for (GLenum target : getFaceTargets(_target)) {
|
||||
glTexImage2D(target, level, texelFormat.internalFormat, dimensions.x, dimensions.y, 0, texelFormat.format, texelFormat.type, nullptr);
|
||||
if (texelFormat.isCompressed()) {
|
||||
glCompressedTexImage2D(target, level, texelFormat.internalFormat, dimensions.x, dimensions.y, 0,
|
||||
getCompressedImageSize(dimensions.x, dimensions.y, texelFormat.internalFormat), nullptr);
|
||||
} else {
|
||||
glTexImage2D(target, level, texelFormat.internalFormat, dimensions.x, dimensions.y, 0, texelFormat.format, texelFormat.type, nullptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -21,13 +21,24 @@ const Element Element::COLOR_SBGRA_32{ VEC4, NUINT8, SBGRA };
|
|||
|
||||
const Element Element::COLOR_RGBA_2{ VEC4, NUINT2, RGBA };
|
||||
|
||||
const Element Element::COLOR_COMPRESSED_RED{ TILE4x4, COMPRESSED, COMPRESSED_BC4_RED };
|
||||
const Element Element::COLOR_COMPRESSED_SRGB { TILE4x4, COMPRESSED, COMPRESSED_BC1_SRGB };
|
||||
const Element Element::COLOR_COMPRESSED_SRGBA_MASK { TILE4x4, COMPRESSED, COMPRESSED_BC1_SRGBA };
|
||||
const Element Element::COLOR_COMPRESSED_SRGBA { TILE4x4, COMPRESSED, COMPRESSED_BC3_SRGBA };
|
||||
const Element Element::COLOR_COMPRESSED_XY { TILE4x4, COMPRESSED, COMPRESSED_BC5_XY };
|
||||
const Element Element::COLOR_COMPRESSED_SRGBA_HIGH{ TILE4x4, COMPRESSED, COMPRESSED_BC7_SRGBA };
|
||||
const Element Element::COLOR_COMPRESSED_HDR_RGB{ TILE4x4, COMPRESSED, COMPRESSED_BC6_RGB };
|
||||
const Element Element::COLOR_COMPRESSED_BCX_RED { TILE4x4, COMPRESSED, COMPRESSED_BC4_RED };
|
||||
const Element Element::COLOR_COMPRESSED_BCX_SRGB { TILE4x4, COMPRESSED, COMPRESSED_BC1_SRGB };
|
||||
const Element Element::COLOR_COMPRESSED_BCX_SRGBA_MASK { TILE4x4, COMPRESSED, COMPRESSED_BC1_SRGBA };
|
||||
const Element Element::COLOR_COMPRESSED_BCX_SRGBA { TILE4x4, COMPRESSED, COMPRESSED_BC3_SRGBA };
|
||||
const Element Element::COLOR_COMPRESSED_BCX_XY { TILE4x4, COMPRESSED, COMPRESSED_BC5_XY };
|
||||
const Element Element::COLOR_COMPRESSED_BCX_SRGBA_HIGH { TILE4x4, COMPRESSED, COMPRESSED_BC7_SRGBA };
|
||||
const Element Element::COLOR_COMPRESSED_BCX_HDR_RGB { TILE4x4, COMPRESSED, COMPRESSED_BC6_RGB };
|
||||
|
||||
const Element Element::COLOR_COMPRESSED_ETC2_RGB { TILE4x4, COMPRESSED, COMPRESSED_ETC2_RGB };
|
||||
const Element Element::COLOR_COMPRESSED_ETC2_SRGB { TILE4x4, COMPRESSED, COMPRESSED_ETC2_SRGB };
|
||||
const Element Element::COLOR_COMPRESSED_ETC2_RGB_PUNCHTHROUGH_ALPHA { TILE4x4, COMPRESSED, COMPRESSED_ETC2_RGB_PUNCHTHROUGH_ALPHA };
|
||||
const Element Element::COLOR_COMPRESSED_ETC2_SRGB_PUNCHTHROUGH_ALPHA { TILE4x4, COMPRESSED, COMPRESSED_ETC2_SRGB_PUNCHTHROUGH_ALPHA };
|
||||
const Element Element::COLOR_COMPRESSED_ETC2_RGBA { TILE4x4, COMPRESSED, COMPRESSED_ETC2_RGBA };
|
||||
const Element Element::COLOR_COMPRESSED_ETC2_SRGBA { TILE4x4, COMPRESSED, COMPRESSED_ETC2_SRGBA };
|
||||
const Element Element::COLOR_COMPRESSED_EAC_RED { TILE4x4, COMPRESSED, COMPRESSED_EAC_RED };
|
||||
const Element Element::COLOR_COMPRESSED_EAC_RED_SIGNED { TILE4x4, COMPRESSED, COMPRESSED_EAC_RED_SIGNED };
|
||||
const Element Element::COLOR_COMPRESSED_EAC_XY { TILE4x4, COMPRESSED, COMPRESSED_EAC_XY };
|
||||
const Element Element::COLOR_COMPRESSED_EAC_XY_SIGNED { TILE4x4, COMPRESSED, COMPRESSED_EAC_XY_SIGNED };
|
||||
|
||||
const Element Element::VEC2NU8_XY{ VEC2, NUINT8, XY };
|
||||
|
||||
|
|
|
@ -194,6 +194,17 @@ enum Semantic : uint8_t {
|
|||
COMPRESSED_BC6_RGB,
|
||||
COMPRESSED_BC7_SRGBA,
|
||||
|
||||
COMPRESSED_ETC2_RGB,
|
||||
COMPRESSED_ETC2_SRGB,
|
||||
COMPRESSED_ETC2_RGB_PUNCHTHROUGH_ALPHA,
|
||||
COMPRESSED_ETC2_SRGB_PUNCHTHROUGH_ALPHA,
|
||||
COMPRESSED_ETC2_RGBA,
|
||||
COMPRESSED_ETC2_SRGBA,
|
||||
COMPRESSED_EAC_RED,
|
||||
COMPRESSED_EAC_RED_SIGNED,
|
||||
COMPRESSED_EAC_XY,
|
||||
COMPRESSED_EAC_XY_SIGNED,
|
||||
|
||||
_LAST_COMPRESSED,
|
||||
|
||||
R11G11B10,
|
||||
|
@ -249,6 +260,17 @@ static const int SEMANTIC_SIZE_FACTOR[NUM_SEMANTICS] = {
|
|||
16, //COMPRESSED_BC6_RGB, 1 byte/pixel * 4x4 pixels = 16 bytes
|
||||
16, //COMPRESSED_BC7_SRGBA, 1 byte/pixel * 4x4 pixels = 16 bytes
|
||||
|
||||
8, //COMPRESSED_ETC2_RGB,
|
||||
8, //COMPRESSED_ETC2_SRGB,
|
||||
8, //COMPRESSED_ETC2_RGB_PUNCHTHROUGH_ALPHA,
|
||||
8, //COMPRESSED_ETC2_SRGB_PUNCHTHROUGH_ALPHA,
|
||||
16, //COMPRESSED_ETC2_RGBA,
|
||||
16, //COMPRESSED_ETC2_SRGBA,
|
||||
8, //COMPRESSED_EAC_RED,
|
||||
8, //COMPRESSED_EAC_RED_SIGNED,
|
||||
16, //COMPRESSED_EAC_XY,
|
||||
16, //COMPRESSED_EAC_XY_SIGNED,
|
||||
|
||||
1, //_LAST_COMPRESSED,
|
||||
|
||||
1, //R11G11B10,
|
||||
|
@ -316,13 +338,23 @@ public:
|
|||
static const Element COLOR_RGBA_2;
|
||||
static const Element COLOR_R11G11B10;
|
||||
static const Element COLOR_RGB9E5;
|
||||
static const Element COLOR_COMPRESSED_RED;
|
||||
static const Element COLOR_COMPRESSED_SRGB;
|
||||
static const Element COLOR_COMPRESSED_SRGBA_MASK;
|
||||
static const Element COLOR_COMPRESSED_SRGBA;
|
||||
static const Element COLOR_COMPRESSED_XY;
|
||||
static const Element COLOR_COMPRESSED_SRGBA_HIGH;
|
||||
static const Element COLOR_COMPRESSED_HDR_RGB;
|
||||
static const Element COLOR_COMPRESSED_BCX_RED;
|
||||
static const Element COLOR_COMPRESSED_BCX_SRGB;
|
||||
static const Element COLOR_COMPRESSED_BCX_SRGBA_MASK;
|
||||
static const Element COLOR_COMPRESSED_BCX_SRGBA;
|
||||
static const Element COLOR_COMPRESSED_BCX_XY;
|
||||
static const Element COLOR_COMPRESSED_BCX_SRGBA_HIGH;
|
||||
static const Element COLOR_COMPRESSED_BCX_HDR_RGB;
|
||||
static const Element COLOR_COMPRESSED_ETC2_RGB;
|
||||
static const Element COLOR_COMPRESSED_ETC2_SRGB;
|
||||
static const Element COLOR_COMPRESSED_ETC2_RGB_PUNCHTHROUGH_ALPHA;
|
||||
static const Element COLOR_COMPRESSED_ETC2_SRGB_PUNCHTHROUGH_ALPHA;
|
||||
static const Element COLOR_COMPRESSED_ETC2_RGBA;
|
||||
static const Element COLOR_COMPRESSED_ETC2_SRGBA;
|
||||
static const Element COLOR_COMPRESSED_EAC_RED;
|
||||
static const Element COLOR_COMPRESSED_EAC_RED_SIGNED;
|
||||
static const Element COLOR_COMPRESSED_EAC_XY;
|
||||
static const Element COLOR_COMPRESSED_EAC_XY_SIGNED;
|
||||
static const Element VEC2NU8_XY;
|
||||
static const Element VEC4F_COLOR_RGBA;
|
||||
static const Element VEC2F_UV;
|
||||
|
|
|
@ -128,6 +128,7 @@ public:
|
|||
uint8 _wrapModeV = WRAP_REPEAT;
|
||||
uint8 _wrapModeW = WRAP_REPEAT;
|
||||
|
||||
uint8 _mipOffset = 0;
|
||||
uint8 _minMip = 0;
|
||||
uint8 _maxMip = MAX_MIP_LEVEL;
|
||||
|
||||
|
@ -142,6 +143,7 @@ public:
|
|||
_wrapModeU == other._wrapModeU &&
|
||||
_wrapModeV == other._wrapModeV &&
|
||||
_wrapModeW == other._wrapModeW &&
|
||||
_mipOffset == other._mipOffset &&
|
||||
_minMip == other._minMip &&
|
||||
_maxMip == other._maxMip;
|
||||
}
|
||||
|
@ -164,6 +166,7 @@ public:
|
|||
ComparisonFunction getComparisonFunction() const { return ComparisonFunction(_desc._comparisonFunc); }
|
||||
bool doComparison() const { return getComparisonFunction() != ALWAYS; }
|
||||
|
||||
uint8 getMipOffset() const { return _desc._mipOffset; }
|
||||
uint8 getMinMip() const { return _desc._minMip; }
|
||||
uint8 getMaxMip() const { return _desc._maxMip; }
|
||||
|
||||
|
|
|
@ -574,20 +574,40 @@ bool Texture::evalKTXFormat(const Element& mipFormat, const Element& texelFormat
|
|||
header.setUncompressed(ktx::GLType::UNSIGNED_BYTE, 1, ktx::GLFormat::RED, ktx::GLInternalFormat::R8, ktx::GLBaseInternalFormat::RED);
|
||||
} else if (texelFormat == Format::VEC2NU8_XY && mipFormat == Format::VEC2NU8_XY) {
|
||||
header.setUncompressed(ktx::GLType::UNSIGNED_BYTE, 1, ktx::GLFormat::RG, ktx::GLInternalFormat::RG8, ktx::GLBaseInternalFormat::RG);
|
||||
} else if (texelFormat == Format::COLOR_COMPRESSED_SRGB && mipFormat == Format::COLOR_COMPRESSED_SRGB) {
|
||||
} else if (texelFormat == Format::COLOR_COMPRESSED_BCX_SRGB && mipFormat == Format::COLOR_COMPRESSED_BCX_SRGB) {
|
||||
header.setCompressed(ktx::GLInternalFormat::COMPRESSED_SRGB_S3TC_DXT1_EXT, ktx::GLBaseInternalFormat::RGB);
|
||||
} else if (texelFormat == Format::COLOR_COMPRESSED_SRGBA_MASK && mipFormat == Format::COLOR_COMPRESSED_SRGBA_MASK) {
|
||||
} else if (texelFormat == Format::COLOR_COMPRESSED_BCX_SRGBA_MASK && mipFormat == Format::COLOR_COMPRESSED_BCX_SRGBA_MASK) {
|
||||
header.setCompressed(ktx::GLInternalFormat::COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT, ktx::GLBaseInternalFormat::RGBA);
|
||||
} else if (texelFormat == Format::COLOR_COMPRESSED_SRGBA && mipFormat == Format::COLOR_COMPRESSED_SRGBA) {
|
||||
} else if (texelFormat == Format::COLOR_COMPRESSED_BCX_SRGBA && mipFormat == Format::COLOR_COMPRESSED_BCX_SRGBA) {
|
||||
header.setCompressed(ktx::GLInternalFormat::COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT, ktx::GLBaseInternalFormat::RGBA);
|
||||
} else if (texelFormat == Format::COLOR_COMPRESSED_RED && mipFormat == Format::COLOR_COMPRESSED_RED) {
|
||||
} else if (texelFormat == Format::COLOR_COMPRESSED_BCX_RED && mipFormat == Format::COLOR_COMPRESSED_BCX_RED) {
|
||||
header.setCompressed(ktx::GLInternalFormat::COMPRESSED_RED_RGTC1, ktx::GLBaseInternalFormat::RED);
|
||||
} else if (texelFormat == Format::COLOR_COMPRESSED_XY && mipFormat == Format::COLOR_COMPRESSED_XY) {
|
||||
} else if (texelFormat == Format::COLOR_COMPRESSED_BCX_XY && mipFormat == Format::COLOR_COMPRESSED_BCX_XY) {
|
||||
header.setCompressed(ktx::GLInternalFormat::COMPRESSED_RG_RGTC2, ktx::GLBaseInternalFormat::RG);
|
||||
} else if (texelFormat == Format::COLOR_COMPRESSED_SRGBA_HIGH && mipFormat == Format::COLOR_COMPRESSED_SRGBA_HIGH) {
|
||||
} else if (texelFormat == Format::COLOR_COMPRESSED_BCX_SRGBA_HIGH && mipFormat == Format::COLOR_COMPRESSED_BCX_SRGBA_HIGH) {
|
||||
header.setCompressed(ktx::GLInternalFormat::COMPRESSED_SRGB_ALPHA_BPTC_UNORM, ktx::GLBaseInternalFormat::RGBA);
|
||||
} else if (texelFormat == Format::COLOR_COMPRESSED_HDR_RGB && mipFormat == Format::COLOR_COMPRESSED_HDR_RGB) {
|
||||
} else if (texelFormat == Format::COLOR_COMPRESSED_BCX_HDR_RGB && mipFormat == Format::COLOR_COMPRESSED_BCX_HDR_RGB) {
|
||||
header.setCompressed(ktx::GLInternalFormat::COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT, ktx::GLBaseInternalFormat::RGB);
|
||||
} else if (texelFormat == Format::COLOR_COMPRESSED_ETC2_RGB && mipFormat == Format::COLOR_COMPRESSED_ETC2_RGB) {
|
||||
header.setCompressed(ktx::GLInternalFormat::COMPRESSED_RGB8_ETC2, ktx::GLBaseInternalFormat::RGB);
|
||||
} else if (texelFormat == Format::COLOR_COMPRESSED_ETC2_SRGB && mipFormat == Format::COLOR_COMPRESSED_ETC2_SRGB) {
|
||||
header.setCompressed(ktx::GLInternalFormat::COMPRESSED_SRGB8_ETC2, ktx::GLBaseInternalFormat::RGB);
|
||||
} else if (texelFormat == Format::COLOR_COMPRESSED_ETC2_RGB_PUNCHTHROUGH_ALPHA && mipFormat == Format::COLOR_COMPRESSED_ETC2_RGB_PUNCHTHROUGH_ALPHA) {
|
||||
header.setCompressed(ktx::GLInternalFormat::COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2, ktx::GLBaseInternalFormat::RGBA);
|
||||
} else if (texelFormat == Format::COLOR_COMPRESSED_ETC2_SRGB_PUNCHTHROUGH_ALPHA && mipFormat == Format::COLOR_COMPRESSED_ETC2_SRGB_PUNCHTHROUGH_ALPHA) {
|
||||
header.setCompressed(ktx::GLInternalFormat::COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2, ktx::GLBaseInternalFormat::RGBA);
|
||||
} else if (texelFormat == Format::COLOR_COMPRESSED_ETC2_RGBA && mipFormat == Format::COLOR_COMPRESSED_ETC2_RGBA) {
|
||||
header.setCompressed(ktx::GLInternalFormat::COMPRESSED_RGBA8_ETC2_EAC, ktx::GLBaseInternalFormat::RGBA);
|
||||
} else if (texelFormat == Format::COLOR_COMPRESSED_ETC2_SRGBA && mipFormat == Format::COLOR_COMPRESSED_ETC2_SRGBA) {
|
||||
header.setCompressed(ktx::GLInternalFormat::COMPRESSED_SRGB8_ALPHA8_ETC2_EAC, ktx::GLBaseInternalFormat::RGBA);
|
||||
} else if (texelFormat == Format::COLOR_COMPRESSED_EAC_RED && mipFormat == Format::COLOR_COMPRESSED_EAC_RED) {
|
||||
header.setCompressed(ktx::GLInternalFormat::COMPRESSED_R11_EAC, ktx::GLBaseInternalFormat::RED);
|
||||
} else if (texelFormat == Format::COLOR_COMPRESSED_EAC_RED_SIGNED && mipFormat == Format::COLOR_COMPRESSED_EAC_RED_SIGNED) {
|
||||
header.setCompressed(ktx::GLInternalFormat::COMPRESSED_SIGNED_R11_EAC, ktx::GLBaseInternalFormat::RED);
|
||||
} else if (texelFormat == Format::COLOR_COMPRESSED_EAC_XY && mipFormat == Format::COLOR_COMPRESSED_EAC_XY) {
|
||||
header.setCompressed(ktx::GLInternalFormat::COMPRESSED_RG11_EAC, ktx::GLBaseInternalFormat::RG);
|
||||
} else if (texelFormat == Format::COLOR_COMPRESSED_EAC_XY_SIGNED && mipFormat == Format::COLOR_COMPRESSED_EAC_XY_SIGNED) {
|
||||
header.setCompressed(ktx::GLInternalFormat::COMPRESSED_SIGNED_RG11_EAC, ktx::GLBaseInternalFormat::RG);
|
||||
} else if (texelFormat == Format::COLOR_RGB9E5 && mipFormat == Format::COLOR_RGB9E5) {
|
||||
header.setUncompressed(ktx::GLType::UNSIGNED_INT_5_9_9_9_REV, 1, ktx::GLFormat::RGB, ktx::GLInternalFormat::RGB9_E5, ktx::GLBaseInternalFormat::RGB);
|
||||
} else if (texelFormat == Format::COLOR_R11G11B10 && mipFormat == Format::COLOR_R11G11B10) {
|
||||
|
@ -642,31 +662,45 @@ bool Texture::evalTextureFormat(const ktx::Header& header, Element& mipFormat, E
|
|||
texelFormat = Format::COLOR_RGB9E5;
|
||||
} else if (header.isCompressed()) {
|
||||
if (header.getGLInternaFormat() == ktx::GLInternalFormat::COMPRESSED_SRGB_S3TC_DXT1_EXT) {
|
||||
mipFormat = Format::COLOR_COMPRESSED_SRGB;
|
||||
texelFormat = Format::COLOR_COMPRESSED_SRGB;
|
||||
texelFormat = Format::COLOR_COMPRESSED_BCX_SRGB;
|
||||
} else if (header.getGLInternaFormat() == ktx::GLInternalFormat::COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT) {
|
||||
mipFormat = Format::COLOR_COMPRESSED_SRGBA_MASK;
|
||||
texelFormat = Format::COLOR_COMPRESSED_SRGBA_MASK;
|
||||
texelFormat = Format::COLOR_COMPRESSED_BCX_SRGBA_MASK;
|
||||
} else if (header.getGLInternaFormat() == ktx::GLInternalFormat::COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT) {
|
||||
mipFormat = Format::COLOR_COMPRESSED_SRGBA;
|
||||
texelFormat = Format::COLOR_COMPRESSED_SRGBA;
|
||||
texelFormat = Format::COLOR_COMPRESSED_BCX_SRGBA;
|
||||
} else if (header.getGLInternaFormat() == ktx::GLInternalFormat::COMPRESSED_RED_RGTC1) {
|
||||
mipFormat = Format::COLOR_COMPRESSED_RED;
|
||||
texelFormat = Format::COLOR_COMPRESSED_RED;
|
||||
texelFormat = Format::COLOR_COMPRESSED_BCX_RED;
|
||||
} else if (header.getGLInternaFormat() == ktx::GLInternalFormat::COMPRESSED_RG_RGTC2) {
|
||||
mipFormat = Format::COLOR_COMPRESSED_XY;
|
||||
texelFormat = Format::COLOR_COMPRESSED_XY;
|
||||
texelFormat = Format::COLOR_COMPRESSED_BCX_XY;
|
||||
} else if (header.getGLInternaFormat() == ktx::GLInternalFormat::COMPRESSED_SRGB_ALPHA_BPTC_UNORM) {
|
||||
mipFormat = Format::COLOR_COMPRESSED_SRGBA_HIGH;
|
||||
texelFormat = Format::COLOR_COMPRESSED_SRGBA_HIGH;
|
||||
texelFormat = Format::COLOR_COMPRESSED_BCX_SRGBA_HIGH;
|
||||
} else if (header.getGLInternaFormat() == ktx::GLInternalFormat::COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT) {
|
||||
mipFormat = Format::COLOR_COMPRESSED_HDR_RGB;
|
||||
texelFormat = Format::COLOR_COMPRESSED_HDR_RGB;
|
||||
texelFormat = Format::COLOR_COMPRESSED_BCX_HDR_RGB;
|
||||
} else if (header.getGLInternaFormat() == ktx::GLInternalFormat::COMPRESSED_RGB8_ETC2) {
|
||||
texelFormat = Format::COLOR_COMPRESSED_ETC2_RGB;
|
||||
} else if (header.getGLInternaFormat() == ktx::GLInternalFormat::COMPRESSED_SRGB8_ETC2) {
|
||||
texelFormat = Format::COLOR_COMPRESSED_ETC2_SRGB;
|
||||
} else if (header.getGLInternaFormat() == ktx::GLInternalFormat::COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2) {
|
||||
texelFormat = Format::COLOR_COMPRESSED_ETC2_RGB_PUNCHTHROUGH_ALPHA;
|
||||
} else if (header.getGLInternaFormat() == ktx::GLInternalFormat::COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2) {
|
||||
texelFormat = Format::COLOR_COMPRESSED_ETC2_SRGB_PUNCHTHROUGH_ALPHA;
|
||||
} else if (header.getGLInternaFormat() == ktx::GLInternalFormat::COMPRESSED_RGBA8_ETC2_EAC) {
|
||||
texelFormat = Format::COLOR_COMPRESSED_ETC2_RGBA;
|
||||
} else if (header.getGLInternaFormat() == ktx::GLInternalFormat::COMPRESSED_SRGB8_ALPHA8_ETC2_EAC) {
|
||||
texelFormat = Format::COLOR_COMPRESSED_ETC2_SRGBA;
|
||||
} else if (header.getGLInternaFormat() == ktx::GLInternalFormat::COMPRESSED_R11_EAC) {
|
||||
texelFormat = Format::COLOR_COMPRESSED_EAC_RED;
|
||||
} else if (header.getGLInternaFormat() == ktx::GLInternalFormat::COMPRESSED_SIGNED_R11_EAC) {
|
||||
texelFormat = Format::COLOR_COMPRESSED_EAC_RED_SIGNED;
|
||||
} else if (header.getGLInternaFormat() == ktx::GLInternalFormat::COMPRESSED_RG11_EAC) {
|
||||
texelFormat = Format::COLOR_COMPRESSED_EAC_XY;
|
||||
} else if (header.getGLInternaFormat() == ktx::GLInternalFormat::COMPRESSED_SIGNED_RG11_EAC) {
|
||||
texelFormat = Format::COLOR_COMPRESSED_EAC_XY_SIGNED;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
mipFormat = texelFormat;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
|
@ -87,6 +87,17 @@ namespace gpu {
|
|||
{ Semantic::COMPRESSED_BC6_RGB, "compressed_bc6_rgb" },
|
||||
{ Semantic::COMPRESSED_BC7_SRGBA, "compressed_bc7_srgba" },
|
||||
|
||||
{ Semantic::COMPRESSED_ETC2_RGB, "compressed_etc2_rgb" },
|
||||
{ Semantic::COMPRESSED_ETC2_SRGB, "compressed_etc2_srgb" },
|
||||
{ Semantic::COMPRESSED_ETC2_RGB_PUNCHTHROUGH_ALPHA, "compressed_etc2_rgb_punchthrough_alpha" },
|
||||
{ Semantic::COMPRESSED_ETC2_SRGB_PUNCHTHROUGH_ALPHA, "compressed_etc2_srgb_punchthrough_alpha" },
|
||||
{ Semantic::COMPRESSED_ETC2_RGBA, "compressed_etc2_rgba" },
|
||||
{ Semantic::COMPRESSED_ETC2_SRGBA, "compressed_etc2_srgba" },
|
||||
{ Semantic::COMPRESSED_EAC_RED, "compressed_eac_red" },
|
||||
{ Semantic::COMPRESSED_EAC_RED_SIGNED, "compressed_eac_red_signed" },
|
||||
{ Semantic::COMPRESSED_EAC_XY, "compressed_eac_xy" },
|
||||
{ Semantic::COMPRESSED_EAC_XY_SIGNED, "compressed_eac_xy_signed" },
|
||||
|
||||
{ Semantic::_LAST_COMPRESSED, "_last_compressed" },
|
||||
|
||||
{ Semantic::R11G11B10, "r11g11b10" },
|
||||
|
|
|
@ -466,7 +466,8 @@ void generateHDRMips(gpu::Texture* texture, QImage&& image, const std::atomic<bo
|
|||
nvtt::CompressionOptions compressionOptions;
|
||||
compressionOptions.setQuality(nvtt::Quality_Production);
|
||||
|
||||
if (mipFormat == gpu::Element::COLOR_COMPRESSED_HDR_RGB) {
|
||||
// TODO: gles: generate ETC mips instead?
|
||||
if (mipFormat == gpu::Element::COLOR_COMPRESSED_BCX_HDR_RGB) {
|
||||
compressionOptions.setFormat(nvtt::Format_BC6);
|
||||
} else if (mipFormat == gpu::Element::COLOR_RGB9E5) {
|
||||
compressionOptions.setFormat(nvtt::Format_RGB);
|
||||
|
@ -583,20 +584,21 @@ void generateLDRMips(gpu::Texture* texture, QImage&& image, const std::atomic<bo
|
|||
nvtt::CompressionOptions compressionOptions;
|
||||
compressionOptions.setQuality(nvtt::Quality_Production);
|
||||
|
||||
// TODO: gles: generate ETC mips instead?
|
||||
auto mipFormat = texture->getStoredMipFormat();
|
||||
if (mipFormat == gpu::Element::COLOR_COMPRESSED_SRGB) {
|
||||
if (mipFormat == gpu::Element::COLOR_COMPRESSED_BCX_SRGB) {
|
||||
compressionOptions.setFormat(nvtt::Format_BC1);
|
||||
} else if (mipFormat == gpu::Element::COLOR_COMPRESSED_SRGBA_MASK) {
|
||||
} else if (mipFormat == gpu::Element::COLOR_COMPRESSED_BCX_SRGBA_MASK) {
|
||||
alphaMode = nvtt::AlphaMode_Transparency;
|
||||
compressionOptions.setFormat(nvtt::Format_BC1a);
|
||||
} else if (mipFormat == gpu::Element::COLOR_COMPRESSED_SRGBA) {
|
||||
} else if (mipFormat == gpu::Element::COLOR_COMPRESSED_BCX_SRGBA) {
|
||||
alphaMode = nvtt::AlphaMode_Transparency;
|
||||
compressionOptions.setFormat(nvtt::Format_BC3);
|
||||
} else if (mipFormat == gpu::Element::COLOR_COMPRESSED_RED) {
|
||||
} else if (mipFormat == gpu::Element::COLOR_COMPRESSED_BCX_RED) {
|
||||
compressionOptions.setFormat(nvtt::Format_BC4);
|
||||
} else if (mipFormat == gpu::Element::COLOR_COMPRESSED_XY) {
|
||||
} else if (mipFormat == gpu::Element::COLOR_COMPRESSED_BCX_XY) {
|
||||
compressionOptions.setFormat(nvtt::Format_BC5);
|
||||
} else if (mipFormat == gpu::Element::COLOR_COMPRESSED_SRGBA_HIGH) {
|
||||
} else if (mipFormat == gpu::Element::COLOR_COMPRESSED_BCX_SRGBA_HIGH) {
|
||||
alphaMode = nvtt::AlphaMode_Transparency;
|
||||
compressionOptions.setFormat(nvtt::Format_BC7);
|
||||
} else if (mipFormat == gpu::Element::COLOR_RGBA_32) {
|
||||
|
@ -736,13 +738,21 @@ gpu::TexturePointer TextureUsage::process2DTextureColorFromImage(QImage&& srcIma
|
|||
gpu::Element formatMip;
|
||||
gpu::Element formatGPU;
|
||||
if (isColorTexturesCompressionEnabled()) {
|
||||
#ifndef USE_GLES
|
||||
if (validAlpha) {
|
||||
// NOTE: This disables BC1a compression because it was producing odd artifacts on text textures
|
||||
// for the tutorial. Instead we use BC3 (which is larger) but doesn't produce the same artifacts).
|
||||
formatGPU = gpu::Element::COLOR_COMPRESSED_SRGBA;
|
||||
formatGPU = gpu::Element::COLOR_COMPRESSED_BCX_SRGBA;
|
||||
} else {
|
||||
formatGPU = gpu::Element::COLOR_COMPRESSED_SRGB;
|
||||
formatGPU = gpu::Element::COLOR_COMPRESSED_BCX_SRGB;
|
||||
}
|
||||
#else
|
||||
if (validAlpha) {
|
||||
formatGPU = gpu::Element::COLOR_COMPRESSED_ETC2_SRGBA;
|
||||
} else {
|
||||
formatGPU = gpu::Element::COLOR_COMPRESSED_ETC2_SRGB;
|
||||
}
|
||||
#endif
|
||||
formatMip = formatGPU;
|
||||
} else {
|
||||
#ifdef USE_GLES
|
||||
|
@ -869,8 +879,12 @@ gpu::TexturePointer TextureUsage::process2DTextureNormalMapFromImage(QImage&& sr
|
|||
gpu::Element formatMip = gpu::Element::VEC2NU8_XY;
|
||||
gpu::Element formatGPU = gpu::Element::VEC2NU8_XY;
|
||||
if (isNormalTexturesCompressionEnabled()) {
|
||||
formatMip = gpu::Element::COLOR_COMPRESSED_XY;
|
||||
formatGPU = gpu::Element::COLOR_COMPRESSED_XY;
|
||||
#ifndef USE_GLES
|
||||
formatGPU = gpu::Element::COLOR_COMPRESSED_BCX_XY;
|
||||
#else
|
||||
formatGPU = gpu::Element::COLOR_COMPRESSED_EAC_XY;
|
||||
#endif
|
||||
formatMip = formatGPU;
|
||||
}
|
||||
|
||||
theTexture = gpu::Texture::create2D(formatGPU, image.width(), image.height(), gpu::Texture::MAX_NUM_MIPS, gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR));
|
||||
|
@ -903,8 +917,12 @@ gpu::TexturePointer TextureUsage::process2DTextureGrayscaleFromImage(QImage&& sr
|
|||
gpu::Element formatMip;
|
||||
gpu::Element formatGPU;
|
||||
if (isGrayscaleTexturesCompressionEnabled()) {
|
||||
formatMip = gpu::Element::COLOR_COMPRESSED_RED;
|
||||
formatGPU = gpu::Element::COLOR_COMPRESSED_RED;
|
||||
#ifndef USE_GLES
|
||||
formatGPU = gpu::Element::COLOR_COMPRESSED_BCX_RED;
|
||||
#else
|
||||
formatGPU = gpu::Element::COLOR_COMPRESSED_EAC_RED;
|
||||
#endif
|
||||
formatMip = formatGPU;
|
||||
} else {
|
||||
formatMip = gpu::Element::COLOR_R_8;
|
||||
formatGPU = gpu::Element::COLOR_R_8;
|
||||
|
@ -1271,8 +1289,9 @@ gpu::TexturePointer TextureUsage::processCubeTextureColorFromImage(QImage&& srcI
|
|||
gpu::Element formatMip;
|
||||
gpu::Element formatGPU;
|
||||
if (isCubeTexturesCompressionEnabled()) {
|
||||
formatMip = gpu::Element::COLOR_COMPRESSED_HDR_RGB;
|
||||
formatGPU = gpu::Element::COLOR_COMPRESSED_HDR_RGB;
|
||||
// TODO: gles: pick HDR ETC format
|
||||
formatMip = gpu::Element::COLOR_COMPRESSED_BCX_HDR_RGB;
|
||||
formatGPU = gpu::Element::COLOR_COMPRESSED_BCX_HDR_RGB;
|
||||
} else {
|
||||
formatMip = HDR_FORMAT;
|
||||
formatGPU = HDR_FORMAT;
|
||||
|
|
|
@ -358,6 +358,17 @@ namespace khronos {
|
|||
case InternalFormat::COMPRESSED_RG_RGTC2: // BC5
|
||||
case InternalFormat::COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT: // BC6
|
||||
case InternalFormat::COMPRESSED_SRGB_ALPHA_BPTC_UNORM: // BC7
|
||||
// ETC2 / EAC
|
||||
case InternalFormat::COMPRESSED_RGB8_ETC2:
|
||||
case InternalFormat::COMPRESSED_SRGB8_ETC2:
|
||||
case InternalFormat::COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2:
|
||||
case InternalFormat::COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2:
|
||||
case InternalFormat::COMPRESSED_RGBA8_ETC2_EAC:
|
||||
case InternalFormat::COMPRESSED_SRGB8_ALPHA8_ETC2_EAC:
|
||||
case InternalFormat::COMPRESSED_R11_EAC:
|
||||
case InternalFormat::COMPRESSED_SIGNED_R11_EAC:
|
||||
case InternalFormat::COMPRESSED_RG11_EAC:
|
||||
case InternalFormat::COMPRESSED_SIGNED_RG11_EAC:
|
||||
return evalAlignedCompressedBlockCount<4>(value);
|
||||
|
||||
default:
|
||||
|
@ -370,12 +381,22 @@ namespace khronos {
|
|||
case InternalFormat::COMPRESSED_SRGB_S3TC_DXT1_EXT:
|
||||
case InternalFormat::COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT:
|
||||
case InternalFormat::COMPRESSED_RED_RGTC1:
|
||||
case InternalFormat::COMPRESSED_RGB8_ETC2:
|
||||
case InternalFormat::COMPRESSED_SRGB8_ETC2:
|
||||
case InternalFormat::COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2:
|
||||
case InternalFormat::COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2:
|
||||
case InternalFormat::COMPRESSED_R11_EAC:
|
||||
case InternalFormat::COMPRESSED_SIGNED_R11_EAC:
|
||||
return 8;
|
||||
|
||||
case InternalFormat::COMPRESSED_SRGB_ALPHA_BPTC_UNORM:
|
||||
case InternalFormat::COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT:
|
||||
case InternalFormat::COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT:
|
||||
case InternalFormat::COMPRESSED_RG_RGTC2:
|
||||
case InternalFormat::COMPRESSED_RGBA8_ETC2_EAC:
|
||||
case InternalFormat::COMPRESSED_SRGB8_ALPHA8_ETC2_EAC:
|
||||
case InternalFormat::COMPRESSED_RG11_EAC:
|
||||
case InternalFormat::COMPRESSED_SIGNED_RG11_EAC:
|
||||
return 16;
|
||||
|
||||
default:
|
||||
|
|
|
@ -39,6 +39,10 @@ bool AddressManager::isConnected() {
|
|||
return DependencyManager::get<NodeList>()->getDomainHandler().isConnected();
|
||||
}
|
||||
|
||||
QString AddressManager::getProtocol() const {
|
||||
return _domainURL.scheme();
|
||||
}
|
||||
|
||||
QUrl AddressManager::currentAddress(bool domainOnly) const {
|
||||
QUrl hifiURL = _domainURL;
|
||||
|
||||
|
|
|
@ -145,7 +145,7 @@ public:
|
|||
};
|
||||
|
||||
bool isConnected();
|
||||
const QString& getProtocol() { return URL_SCHEME_HIFI; };
|
||||
QString getProtocol() const;
|
||||
|
||||
QUrl currentAddress(bool domainOnly = false) const;
|
||||
QUrl currentFacingAddress() const;
|
||||
|
|
|
@ -30,7 +30,7 @@ PacketVersion versionForPacketType(PacketType packetType) {
|
|||
case PacketType::EntityEdit:
|
||||
case PacketType::EntityData:
|
||||
case PacketType::EntityPhysics:
|
||||
return static_cast<PacketVersion>(EntityVersion::ShadowControl);
|
||||
return static_cast<PacketVersion>(EntityVersion::MaterialData);
|
||||
case PacketType::EntityQuery:
|
||||
return static_cast<PacketVersion>(EntityQueryPacketVersion::RemovedJurisdictions);
|
||||
case PacketType::AvatarIdentity:
|
||||
|
|
|
@ -231,7 +231,8 @@ enum class EntityVersion : PacketVersion {
|
|||
ZoneStageRemoved,
|
||||
SoftEntities,
|
||||
MaterialEntities,
|
||||
ShadowControl
|
||||
ShadowControl,
|
||||
MaterialData
|
||||
};
|
||||
|
||||
enum class EntityScriptCallMethodVersion : PacketVersion {
|
||||
|
|
|
@ -532,7 +532,7 @@ void RenderDeferredSetup::run(const render::RenderContextPointer& renderContext,
|
|||
}
|
||||
}
|
||||
|
||||
auto& program = deferredLightingEffect->_directionalSkyboxLight;
|
||||
auto program = deferredLightingEffect->_directionalSkyboxLight;
|
||||
LightLocationsPtr locations = deferredLightingEffect->_directionalSkyboxLightLocations;
|
||||
|
||||
auto keyLight = lightAndShadow.first;
|
||||
|
|
|
@ -154,6 +154,13 @@ void OffscreenUi::show(const QUrl& url, const QString& name, std::function<void(
|
|||
}
|
||||
}
|
||||
|
||||
void OffscreenUi::hideDesktopWindows() {
|
||||
if (QThread::currentThread() != thread()) {
|
||||
BLOCKING_INVOKE_METHOD(this, "hideDesktopWindows");
|
||||
}
|
||||
QMetaObject::invokeMethod(_desktop, "hideDesktopWindows");
|
||||
}
|
||||
|
||||
void OffscreenUi::toggle(const QUrl& url, const QString& name, std::function<void(QQmlContext*, QObject*)> f) {
|
||||
QQuickItem* item = getRootItem()->findChild<QQuickItem*>(name);
|
||||
if (!item) {
|
||||
|
|
|
@ -60,6 +60,7 @@ public:
|
|||
void createDesktop(const QUrl& url);
|
||||
void show(const QUrl& url, const QString& name, std::function<void(QQmlContext*, QObject*)> f = [](QQmlContext*, QObject*) {});
|
||||
void hide(const QString& name);
|
||||
void hideDesktopWindows();
|
||||
bool isVisible(const QString& name);
|
||||
void toggle(const QUrl& url, const QString& name, std::function<void(QQmlContext*, QObject*)> f = [](QQmlContext*, QObject*) {});
|
||||
bool shouldSwallowShortcut(QEvent* event);
|
||||
|
|
|
@ -387,6 +387,8 @@ void TabletProxy::setToolbarMode(bool toolbarMode) {
|
|||
offscreenUi->hide("RunningScripts");
|
||||
_showRunningScripts = true;
|
||||
}
|
||||
|
||||
offscreenUi->hideDesktopWindows();
|
||||
// destroy desktop window
|
||||
if (_desktopWindow) {
|
||||
_desktopWindow->deleteLater();
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
//
|
||||
// Grab's physically moveable entities with the mouse, by applying a spring force.
|
||||
//
|
||||
// Updated November 22, 2016 by Philip Rosedale: Add distance attenuation of grab effect
|
||||
// Updated November 22, 2016 by Philip Rosedale: Add distance attenuation of grab effect
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
|
@ -444,6 +444,7 @@ Grabber.prototype.releaseEvent = function(event) {
|
|||
this.actionID = null;
|
||||
|
||||
Pointers.setRenderState(this.mouseRayEntities, "");
|
||||
Pointers.setLockEndUUID(this.mouseRayEntities, null, false);
|
||||
|
||||
var args = "mouse";
|
||||
Entities.callEntityMethod(this.entityID, "releaseGrab", args);
|
||||
|
|
|
@ -416,6 +416,12 @@ var toolBar = (function () {
|
|||
// default:
|
||||
// shapeType = "uv";
|
||||
//}
|
||||
var materialData = "";
|
||||
if (materialURL.startsWith("materialData")) {
|
||||
materialData = JSON.stringify({
|
||||
"materials": {}
|
||||
})
|
||||
}
|
||||
|
||||
var DEFAULT_LAYERED_MATERIAL_PRIORITY = 1;
|
||||
if (materialURL) {
|
||||
|
@ -423,7 +429,8 @@ var toolBar = (function () {
|
|||
type: "Material",
|
||||
materialURL: materialURL,
|
||||
//materialMappingMode: materialMappingMode,
|
||||
priority: DEFAULT_LAYERED_MATERIAL_PRIORITY
|
||||
priority: DEFAULT_LAYERED_MATERIAL_PRIORITY,
|
||||
materialData: materialData
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -2061,7 +2068,7 @@ var PropertiesTool = function (opts) {
|
|||
parentSelectedEntities();
|
||||
} else if (data.type === 'unparent') {
|
||||
unparentSelectedEntities();
|
||||
} else if (data.type === 'saveUserData') {
|
||||
} else if (data.type === 'saveUserData' || data.type === 'saveMaterialData') {
|
||||
//the event bridge and json parsing handle our avatar id string differently.
|
||||
var actualID = data.id.split('"')[1];
|
||||
Entities.editEntity(actualID, data.properties);
|
||||
|
|
|
@ -1387,12 +1387,14 @@ input#reset-to-natural-dimensions {
|
|||
margin-top: 48px;
|
||||
}
|
||||
|
||||
#userdata-clear{
|
||||
#userdata-clear,
|
||||
#materialdata-clear {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
|
||||
#static-userdata {
|
||||
#static-userdata,
|
||||
#static-materialData {
|
||||
display: none;
|
||||
z-index: 99;
|
||||
position: absolute;
|
||||
|
@ -1403,7 +1405,8 @@ input#reset-to-natural-dimensions {
|
|||
background-color: #2e2e2e;
|
||||
}
|
||||
|
||||
#userdata-saved {
|
||||
#userdata-saved,
|
||||
#materialData-saved {
|
||||
margin-top:5px;
|
||||
font-size:16px;
|
||||
display:none;
|
||||
|
|
|
@ -781,6 +781,20 @@
|
|||
<label for="property-material-url">Material URL</label>
|
||||
<input type="text" id="property-material-url">
|
||||
</div>
|
||||
|
||||
<div class="property textarea">
|
||||
<label for="property-material-data">Material data</label>
|
||||
<br><br>
|
||||
<div class="row">
|
||||
<input type="button" class="red" id="materialdata-clear" value="Clear Material Data">
|
||||
<input type="button" class="blue" id="materialdata-new-editor" value="Edit as JSON">
|
||||
<input disabled type="button" class="black" id="materialdata-save" value="Save Material Data">
|
||||
<span id="materialdata-saved" visible="false">Saved!</span>
|
||||
</div>
|
||||
<div id="static-naterialdata"></div>
|
||||
<div id="materialdata-editor"></div>
|
||||
<textarea id="property-material-data"></textarea>
|
||||
</div>
|
||||
</fieldset>
|
||||
<fieldset class="minor">
|
||||
<div class="material-group material-section property text" id="property-parent-material-id-string-container">
|
||||
|
|
|
@ -66,6 +66,7 @@ function enableProperties() {
|
|||
|
||||
if (elLocked.checked === false) {
|
||||
removeStaticUserData();
|
||||
removeStaticMaterialData();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -78,8 +79,13 @@ function disableProperties() {
|
|||
}
|
||||
var elLocked = document.getElementById("property-locked");
|
||||
|
||||
if ($('#userdata-editor').css('display') === "block" && elLocked.checked === true) {
|
||||
showStaticUserData();
|
||||
if (elLocked.checked === true) {
|
||||
if ($('#userdata-editor').css('display') === "block") {
|
||||
showStaticUserData();
|
||||
}
|
||||
if ($('#materialdata-editor').css('display') === "block") {
|
||||
showStaticMaterialData();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -356,15 +362,139 @@ function userDataChanger(groupName, keyName, values, userDataElement, defaultVal
|
|||
multiDataUpdater(groupName, val, userDataElement, def);
|
||||
}
|
||||
|
||||
function setMaterialDataFromEditor(noUpdate) {
|
||||
var json = null;
|
||||
try {
|
||||
json = materialEditor.get();
|
||||
} catch (e) {
|
||||
alert('Invalid JSON code - look for red X in your code ', +e);
|
||||
}
|
||||
if (json === null) {
|
||||
return;
|
||||
} else {
|
||||
var text = materialEditor.getText();
|
||||
if (noUpdate === true) {
|
||||
EventBridge.emitWebEvent(
|
||||
JSON.stringify({
|
||||
id: lastEntityID,
|
||||
type: "saveMaterialData",
|
||||
properties: {
|
||||
materialData: text
|
||||
}
|
||||
})
|
||||
);
|
||||
return;
|
||||
} else {
|
||||
updateProperty('materialData', text);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function setTextareaScrolling(element) {
|
||||
var isScrolling = element.scrollHeight > element.offsetHeight;
|
||||
element.setAttribute("scrolling", isScrolling ? "true" : "false");
|
||||
}
|
||||
|
||||
|
||||
var materialEditor = null;
|
||||
|
||||
function createJSONMaterialEditor() {
|
||||
var container = document.getElementById("materialdata-editor");
|
||||
var options = {
|
||||
search: false,
|
||||
mode: 'tree',
|
||||
modes: ['code', 'tree'],
|
||||
name: 'materialData',
|
||||
onModeChange: function() {
|
||||
$('.jsoneditor-poweredBy').remove();
|
||||
},
|
||||
onError: function(e) {
|
||||
alert('JSON editor:' + e);
|
||||
},
|
||||
onChange: function() {
|
||||
var currentJSONString = materialEditor.getText();
|
||||
|
||||
if (currentJSONString === '{"":""}') {
|
||||
return;
|
||||
}
|
||||
$('#materialdata-save').attr('disabled', false);
|
||||
|
||||
|
||||
}
|
||||
};
|
||||
materialEditor = new JSONEditor(container, options);
|
||||
}
|
||||
|
||||
function hideNewJSONMaterialEditorButton() {
|
||||
$('#materialdata-new-editor').hide();
|
||||
}
|
||||
|
||||
function showSaveMaterialDataButton() {
|
||||
$('#materialdata-save').show();
|
||||
}
|
||||
|
||||
function hideSaveMaterialDataButton() {
|
||||
$('#materialdata-save').hide();
|
||||
}
|
||||
|
||||
function showNewJSONMaterialEditorButton() {
|
||||
$('#materialdata-new-editor').show();
|
||||
}
|
||||
|
||||
function showMaterialDataTextArea() {
|
||||
$('#property-material-data').show();
|
||||
}
|
||||
|
||||
function hideMaterialDataTextArea() {
|
||||
$('#property-material-data').hide();
|
||||
}
|
||||
|
||||
function showStaticMaterialData() {
|
||||
if (materialEditor !== null) {
|
||||
$('#static-materialdata').show();
|
||||
$('#static-materialdata').css('height', $('#materialdata-editor').height());
|
||||
$('#static-materialdata').text(materialEditor.getText());
|
||||
}
|
||||
}
|
||||
|
||||
function removeStaticMaterialData() {
|
||||
$('#static-materialdata').hide();
|
||||
}
|
||||
|
||||
function setMaterialEditorJSON(json) {
|
||||
materialEditor.set(json);
|
||||
if (materialEditor.hasOwnProperty('expandAll')) {
|
||||
materialEditor.expandAll();
|
||||
}
|
||||
}
|
||||
|
||||
function getMaterialEditorJSON() {
|
||||
return materialEditor.get();
|
||||
}
|
||||
|
||||
function deleteJSONMaterialEditor() {
|
||||
if (materialEditor !== null) {
|
||||
materialEditor.destroy();
|
||||
materialEditor = null;
|
||||
}
|
||||
}
|
||||
|
||||
var savedMaterialJSONTimer = null;
|
||||
|
||||
function saveJSONMaterialData(noUpdate) {
|
||||
setMaterialDataFromEditor(noUpdate);
|
||||
$('#materialdata-saved').show();
|
||||
$('#materialdata-save').attr('disabled', true);
|
||||
if (savedMaterialJSONTimer !== null) {
|
||||
clearTimeout(savedMaterialJSONTimer);
|
||||
}
|
||||
savedMaterialJSONTimer = setTimeout(function() {
|
||||
$('#materialdata-saved').hide();
|
||||
|
||||
}, EDITOR_TIMEOUT_DURATION);
|
||||
}
|
||||
|
||||
var editor = null;
|
||||
var editorTimeout = null;
|
||||
var lastJSONString = null;
|
||||
|
||||
function createJSONEditor() {
|
||||
var container = document.getElementById("userdata-editor");
|
||||
|
@ -395,11 +525,6 @@ function createJSONEditor() {
|
|||
|
||||
function hideNewJSONEditorButton() {
|
||||
$('#userdata-new-editor').hide();
|
||||
|
||||
}
|
||||
|
||||
function hideClearUserDataButton() {
|
||||
$('#userdata-clear').hide();
|
||||
}
|
||||
|
||||
function showSaveUserDataButton() {
|
||||
|
@ -408,17 +533,10 @@ function showSaveUserDataButton() {
|
|||
|
||||
function hideSaveUserDataButton() {
|
||||
$('#userdata-save').hide();
|
||||
|
||||
}
|
||||
|
||||
function showNewJSONEditorButton() {
|
||||
$('#userdata-new-editor').show();
|
||||
|
||||
}
|
||||
|
||||
function showClearUserDataButton() {
|
||||
$('#userdata-clear').show();
|
||||
|
||||
}
|
||||
|
||||
function showUserDataTextArea() {
|
||||
|
@ -446,7 +564,6 @@ function setEditorJSON(json) {
|
|||
if (editor.hasOwnProperty('expandAll')) {
|
||||
editor.expandAll();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function getEditorJSON() {
|
||||
|
@ -484,12 +601,15 @@ function bindAllNonJSONEditorElements() {
|
|||
// TODO FIXME: (JSHint) Functions declared within loops referencing
|
||||
// an outer scoped variable may lead to confusing semantics.
|
||||
field.on('focus', function(e) {
|
||||
if (e.target.id === "userdata-new-editor" || e.target.id === "userdata-clear") {
|
||||
if (e.target.id === "userdata-new-editor" || e.target.id === "userdata-clear" || e.target.id === "materialdata-new-editor" || e.target.id === "materialdata-clear") {
|
||||
return;
|
||||
} else {
|
||||
if ($('#userdata-editor').css('height') !== "0px") {
|
||||
saveJSONUserData(true);
|
||||
}
|
||||
if ($('#materialdata-editor').css('height') !== "0px") {
|
||||
saveJSONMaterialData(true);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -652,6 +772,10 @@ function loaded() {
|
|||
var elMaterialMappingScaleX = document.getElementById("property-material-mapping-scale-x");
|
||||
var elMaterialMappingScaleY = document.getElementById("property-material-mapping-scale-y");
|
||||
var elMaterialMappingRot = document.getElementById("property-material-mapping-rot");
|
||||
var elMaterialData = document.getElementById("property-material-data");
|
||||
var elClearMaterialData = document.getElementById("materialdata-clear");
|
||||
var elSaveMaterialData = document.getElementById("materialdata-save");
|
||||
var elNewJSONMaterialEditor = document.getElementById('materialdata-new-editor');
|
||||
|
||||
var elImageURL = document.getElementById("property-image-url");
|
||||
|
||||
|
@ -772,9 +896,15 @@ function loaded() {
|
|||
} else if (data.type === "update") {
|
||||
|
||||
if (!data.selections || data.selections.length === 0) {
|
||||
if (editor !== null && lastEntityID !== null) {
|
||||
saveJSONUserData(true);
|
||||
deleteJSONEditor();
|
||||
if (lastEntityID !== null) {
|
||||
if (editor !== null) {
|
||||
saveJSONUserData(true);
|
||||
deleteJSONEditor();
|
||||
}
|
||||
if (materialEditor !== null) {
|
||||
saveJSONMaterialData(true);
|
||||
deleteJSONMaterialEditor();
|
||||
}
|
||||
}
|
||||
elTypeIcon.style.display = "none";
|
||||
elType.innerHTML = "<i>No selection</i>";
|
||||
|
@ -783,6 +913,7 @@ function loaded() {
|
|||
disableProperties();
|
||||
} else if (data.selections && data.selections.length > 1) {
|
||||
deleteJSONEditor();
|
||||
deleteJSONMaterialEditor();
|
||||
var selections = data.selections;
|
||||
|
||||
var ids = [];
|
||||
|
@ -815,8 +946,13 @@ function loaded() {
|
|||
} else {
|
||||
|
||||
properties = data.selections[0].properties;
|
||||
if (lastEntityID !== '"' + properties.id + '"' && lastEntityID !== null && editor !== null) {
|
||||
saveJSONUserData(true);
|
||||
if (lastEntityID !== '"' + properties.id + '"' && lastEntityID !== null) {
|
||||
if (editor !== null) {
|
||||
saveJSONUserData(true);
|
||||
}
|
||||
if (materialEditor !== null) {
|
||||
saveJSONMaterialData(true);
|
||||
}
|
||||
}
|
||||
|
||||
var doSelectElement = lastEntityID === '"' + properties.id + '"';
|
||||
|
@ -993,6 +1129,28 @@ function loaded() {
|
|||
hideNewJSONEditorButton();
|
||||
}
|
||||
|
||||
var materialJson = null;
|
||||
try {
|
||||
materialJson = JSON.parse(properties.materialData);
|
||||
} catch (e) {
|
||||
// normal text
|
||||
deleteJSONMaterialEditor();
|
||||
elMaterialData.value = properties.materialData;
|
||||
showMaterialDataTextArea();
|
||||
showNewJSONMaterialEditorButton();
|
||||
hideSaveMaterialDataButton();
|
||||
}
|
||||
if (materialJson !== null) {
|
||||
if (materialEditor === null) {
|
||||
createJSONMaterialEditor();
|
||||
}
|
||||
|
||||
setMaterialEditorJSON(materialJson);
|
||||
showSaveMaterialDataButton();
|
||||
hideMaterialDataTextArea();
|
||||
hideNewJSONMaterialEditorButton();
|
||||
}
|
||||
|
||||
elHyperlinkHref.value = properties.href;
|
||||
elDescription.value = properties.description;
|
||||
|
||||
|
@ -1200,6 +1358,7 @@ function loaded() {
|
|||
} else {
|
||||
enableProperties();
|
||||
elSaveUserData.disabled = true;
|
||||
elSaveMaterialData.disabled = true;
|
||||
}
|
||||
|
||||
var activeElement = document.activeElement;
|
||||
|
@ -1384,6 +1543,31 @@ function loaded() {
|
|||
showSaveUserDataButton();
|
||||
});
|
||||
|
||||
elClearMaterialData.addEventListener("click", function() {
|
||||
deleteJSONMaterialEditor();
|
||||
elMaterialData.value = "";
|
||||
showMaterialDataTextArea();
|
||||
showNewJSONMaterialEditorButton();
|
||||
hideSaveMaterialDataButton();
|
||||
updateProperty('materialData', elMaterialData.value);
|
||||
});
|
||||
|
||||
elSaveMaterialData.addEventListener("click", function() {
|
||||
saveJSONMaterialData(true);
|
||||
});
|
||||
|
||||
elMaterialData.addEventListener('change', createEmitTextPropertyUpdateFunction('materialData'));
|
||||
|
||||
elNewJSONMaterialEditor.addEventListener('click', function() {
|
||||
deleteJSONMaterialEditor();
|
||||
createJSONMaterialEditor();
|
||||
var data = {};
|
||||
setMaterialEditorJSON(data);
|
||||
hideMaterialDataTextArea();
|
||||
hideNewJSONMaterialEditorButton();
|
||||
showSaveMaterialDataButton();
|
||||
});
|
||||
|
||||
var colorChangeFunction = createEmitColorPropertyUpdateFunction(
|
||||
'color', elColorRed, elColorGreen, elColorBlue);
|
||||
elColorRed.addEventListener('change', colorChangeFunction);
|
||||
|
|
|
@ -44,26 +44,35 @@ def groupKTXFiles(directory, filePath):
|
|||
originalFilePath.strip()
|
||||
shutil.move(originalFilePath, newFilePath)
|
||||
|
||||
def bakeFile(filePath, outputDirectory):
|
||||
def bakeFile(filePath, outputDirectory, fileType):
|
||||
createDirectory(outputDirectory)
|
||||
cmd = EXE + ' -i ' + filePath + ' -o ' + outputDirectory + ' -t fbx'
|
||||
cmd = EXE + ' -i ' + filePath + ' -o ' + outputDirectory + ' -t ' + fileType
|
||||
args = shlex.split(cmd)
|
||||
process = subprocess.Popen(cmd, stdout=False, stderr=False)
|
||||
process.wait()
|
||||
bakedFile = os.path.splitext(filePath)[0]
|
||||
groupKTXFiles(outputDirectory, bakedFile)
|
||||
if fileType == 'fbx':
|
||||
groupKTXFiles(outputDirectory, bakedFile)
|
||||
|
||||
def bakeFilesInDirectory(directory, outputDirectory):
|
||||
rootDirectory = os.path.basename(os.path.normpath(directory))
|
||||
for root, subFolders, filenames in os.walk(directory):
|
||||
for filename in filenames:
|
||||
appendPath = getRelativePath(directory, root, rootDirectory);
|
||||
name, ext = os.path.splitext('file.txt')
|
||||
if filename.endswith('.fbx'):
|
||||
filePath = os.sep.join([root, filename])
|
||||
absFilePath = os.path.abspath(filePath)
|
||||
outputFolder = os.path.join(outputDirectory, appendPath)
|
||||
print "Baking file: " + filename
|
||||
bakeFile(absFilePath, outputFolder)
|
||||
bakeFile(absFilePath, outputFolder, 'fbx')
|
||||
elif os.path.basename(root) == 'skyboxes':
|
||||
filePath = os.sep.join([root, filename])
|
||||
absFilePath = os.path.abspath(filePath)
|
||||
outputFolder = os.path.join(outputDirectory, appendPath)
|
||||
print "Baking file: " + filename
|
||||
bakeType = os.path.splitext(filename)[1][1:]
|
||||
bakeFile(absFilePath, outputFolder, bakeType)
|
||||
else:
|
||||
filePath = os.sep.join([root, filename])
|
||||
absFilePath = os.path.abspath(filePath)
|
||||
|
|
|
@ -6,7 +6,7 @@ def createAssetMapping(assetDirectory):
|
|||
baseDirectory = os.path.basename(os.path.normpath(assetDirectory))
|
||||
for root, subfolder, filenames in os.walk(assetDirectory):
|
||||
for filename in filenames:
|
||||
if not filename.endswith('.ktx'):
|
||||
if not filename.endswith('.ktx') or os.path.basename(root) == 'skyboxes':
|
||||
substring = os.path.commonprefix([assetDirectory, root])
|
||||
newPath = root.replace(substring, '');
|
||||
filePath = os.sep.join([newPath, filename])
|
||||
|
@ -30,7 +30,7 @@ def handleURL(url):
|
|||
baseFilename = os.path.basename(url)
|
||||
filename = os.path.splitext(baseFilename)[0]
|
||||
newUrl = MAP[filename]
|
||||
print newUrl
|
||||
print url + ' -> ' + newUrl
|
||||
return newUrl
|
||||
|
||||
def handleOptions():
|
||||
|
@ -43,8 +43,12 @@ def main():
|
|||
argsLength = len(sys.argv)
|
||||
if argsLength == 3:
|
||||
jsonFile = sys.argv[1]
|
||||
gzipFile = jsonFile + '.gz'
|
||||
assetDirectory = sys.argv[2]
|
||||
inputFilename = os.path.basename(jsonFile);
|
||||
absPath = os.path.abspath(assetDirectory)
|
||||
finalPath = absPath.replace('\\', '/');
|
||||
gzipFile = finalPath + '/' + inputFilename + '.gz'
|
||||
print gzipFile
|
||||
createAssetMapping(assetDirectory)
|
||||
f = open(jsonFile)
|
||||
data = json.load(f)
|
||||
|
@ -65,6 +69,15 @@ def main():
|
|||
except:
|
||||
value = handleURL(value)
|
||||
|
||||
|
||||
if prop == "ambientLight":
|
||||
for index in value:
|
||||
value[index] = handleURL(value[index])
|
||||
|
||||
if prop == "skybox":
|
||||
for index in value:
|
||||
value[index] = handleURL(value[index])
|
||||
|
||||
if prop == "serverScripts":
|
||||
value = handleURL(value)
|
||||
|
||||
|
|
Loading…
Reference in a new issue