diff --git a/.github/actions/conan-install/action.yml b/.github/actions/conan-install/action.yml
index d4e7597e4e3..bf04ed2db0b 100644
--- a/.github/actions/conan-install/action.yml
+++ b/.github/actions/conan-install/action.yml
@@ -23,5 +23,6 @@ runs:
run: |
# Run twice to work around OpenSSL not being found. See: https://github.com/overte-org/overte-conan-recipes/issues/1
# `--format=json > build.json` creates a graph of dependencies, which can later be used to check for cache misses.
- conan install . -s compiler.cppstd=${{ inputs.cppstd }} -s build_type=${{ inputs.build_type }} -o 'Overte/*:qt_source='${{ inputs.qt_source }} -b missing -pr=${{ inputs.conan_profile }} -of build --format=json > build.json
- conan install . -s compiler.cppstd=${{ inputs.cppstd }} -s build_type=${{ inputs.build_type }} -o 'Overte/*:qt_source='${{ inputs.qt_source }} -b missing -pr=${{ inputs.conan_profile }} -of build
+ # `--build="glib/*"` works around https://github.com/conan-io/conan-center-index/issues/17876 by always building glib from source, instead of using the too outdated binary cache on ConanCenter.
+ conan install . -s compiler.cppstd=${{ inputs.cppstd }} -s build_type=${{ inputs.build_type }} -o 'Overte/*:qt_source='${{ inputs.qt_source }} -b missing --build="glib/*" -pr=${{ inputs.conan_profile }} -of build --format=json > build.json
+ conan install . -s compiler.cppstd=${{ inputs.cppstd }} -s build_type=${{ inputs.build_type }} -o 'Overte/*:qt_source='${{ inputs.qt_source }} -b missing --build="glib/*" -pr=${{ inputs.conan_profile }} -of build
diff --git a/.github/actions/setup-windows/action.yml b/.github/actions/setup-windows/action.yml
index 401a9249494..b93a634a74e 100644
--- a/.github/actions/setup-windows/action.yml
+++ b/.github/actions/setup-windows/action.yml
@@ -6,7 +6,7 @@ runs:
- name: Set environment variables
shell: bash
run: |
- if [[ "${{ matrix.os }}" = "Windows 2022" ]]; then
+ if [[ "${{ matrix.os }}" = "Windows 2022"* ]]; then
echo "CONAN_PROFILE='./tools/conan-profiles/vs-22-release'" >> $GITHUB_ENV
else
echo "Not sure which Conan profile to use. Exiting." && exit 1
@@ -27,6 +27,7 @@ runs:
run: |
choco install nsis --allow-downgrade --version=3.06.1
choco install cmake --allow-downgrade --version=3.31.6 # Our dependencies don't support CMake 4.0 yet.
+ choco install nodejs --version="24.16.0" # Qt6 needs NodeJS for building Qt WebEngine, but cannot find preinstalled NodeJS.
Invoke-WebRequest https://build-deps.overte.org/conan/nsis-overte-plugins/NSIS-hifi-plugins-1.0.zip -OutFile NSIS-hifi-plugins.zip
Expand-Archive -LiteralPath 'NSIS-hifi-plugins.zip' -DestinationPath 'C:/Program Files (x86)/'
diff --git a/.github/workflows/pr_build.yml b/.github/workflows/pr_build.yml
index f388666932b..0b320551cc2 100644
--- a/.github/workflows/pr_build.yml
+++ b/.github/workflows/pr_build.yml
@@ -30,49 +30,62 @@ jobs:
strategy:
matrix:
include:
- - os: Windows 2022
+ - os: Windows 2022 Source
# We use a third-party runner hosted by depot.dev to avoid timing out in case Qt has to be built from source.
runner: depot-windows-2022-16 # Which GitHub Actions Runner to use. When using `image` this just needs to run the build container.
arch: x86_64
build_type: full
qt_source: source # Which Qt Conan package to use. `system`, `source`, or `aqt`.
rendering_backend: OpenGL # Which OVERTE_RENDERING_BACKEND to build.
+ - os: Windows 2022 AQt
+ runner: depot-windows-2022-16
+ arch: x86_64
+ build_type: full
+ qt_source: aqt
+ rendering_backend: OpenGL
#- os: macOS-10.15
# build_type: full
# qt_source: aqt
- - os: Ubuntu 22.04 OpenGL
+ - os: Ubuntu 22.04 OpenGL Source
image: docker.io/overte/overte-full-build:2026-02-09-ubuntu-22.04-amd64 # Container image used for building. Built from /tools/ci-script/linux-ci/Dockerfile_x
- runner: ubuntu-latest
+ runner: depot-ubuntu-24.04-16
arch: amd64
build_type: full
- qt_source: system
+ qt_source: source
rendering_backend: OpenGL
- - os: Ubuntu 22.04 Vulkan
- image: docker.io/overte/overte-full-build:2026-02-09-ubuntu-22.04-amd64
- runner: ubuntu-latest
- arch: amd64
- build_type: full
- qt_source: system
- rendering_backend: Vulkan
+ # - os: Ubuntu 22.04 OpenGL system package
+ # image: docker.io/overte/overte-full-build:2026-02-09-ubuntu-22.04-amd64 # Container image used for building. Built from /tools/ci-script/linux-ci/Dockerfile_x
+ # runner: depot-ubuntu-24.04-16
+ # arch: amd64
+ # build_type: full
+ # qt_source: system
+ # rendering_backend: OpenGL
+ # - os: Ubuntu 22.04 Vulkan
+ # image: docker.io/overte/overte-full-build:2026-02-09-ubuntu-22.04-amd64
+ # runner: ubuntu-latest
+ # arch: amd64
+ # build_type: full
+ # qt_source: system
+ # rendering_backend: Vulkan
# Android builds are currently failing
#- os: ubuntu-18.04
# build_type: android
# apt-dependencies: mesa-common-dev libegl1 libglvnd-dev libdouble-conversion1 libpulse0 python3-github python3-distro
# Do not change the names of self-hosted runners without knowing what you are doing, as they correspond to labels that have to be set on the runner.
- - os: Ubuntu 22.04 OpenGL
- image: docker.io/overte/overte-full-build:2026-02-09-ubuntu-22.04-aarch64
- runner: ubuntu-24.04-arm
- arch: aarch64
- build_type: full
- qt_source: system
- rendering_backend: OpenGL
- - os: Ubuntu 22.04 Vulkan
- image: docker.io/overte/overte-full-build:2026-02-09-ubuntu-22.04-aarch64
- runner: ubuntu-24.04-arm
- arch: aarch64
- build_type: full
- qt_source: system
- rendering_backend: Vulkan
+ # - os: Ubuntu 22.04 OpenGL
+ # image: docker.io/overte/overte-full-build:2026-02-09-ubuntu-22.04-aarch64
+ # runner: ubuntu-24.04-arm
+ # arch: aarch64
+ # build_type: full
+ # qt_source: source
+ # rendering_backend: OpenGL
+ # - os: Ubuntu 22.04 Vulkan
+ # image: docker.io/overte/overte-full-build:2026-02-09-ubuntu-22.04-aarch64
+ # runner: ubuntu-24.04-arm
+ # arch: aarch64
+ # build_type: full
+ # qt_source: system
+ # rendering_backend: Vulkan
fail-fast: false
runs-on: ${{matrix.runner}}
container: ${{matrix.image}}
diff --git a/BUILD_WIN.md b/BUILD_WIN.md
index e4d00d2f522..07d3dab7390 100644
--- a/BUILD_WIN.md
+++ b/BUILD_WIN.md
@@ -76,10 +76,13 @@ To create this variable:
*Make sure that you copy the contents of your old `.conan2` folder over, or that you add the remote from step 2 again.*
-## Step 6. (Optional) Node.JS and NPM
+## Step 6. Installing Node.JS and NPM
Install the latest LTS version of [Node.JS and NPM]( ).
This is required to build the server-console and jsdoc, and for JavaScript console autocompletion.
+All of these components are optional, however, Node.JS is also required for building Qt WebEngine.
+
+There are no proper Windows installers for Node.JS and NPM, so install it from [Chocolatey](https://community.chocolatey.org/).
## Step 7. Running CMake to Generate Build Files
@@ -188,4 +191,4 @@ To fix this, install "C++ vXX.XX (...) ATL (...)" replacing XX.XX with the first
### `warning : cannot resolve item 'api-ms-win-(...)-l1-1-0.dll'`
You may be able to ignore this warning. My assumption is that the MSVC version used is incompatible with the currently installed Windows SDK,
-and Overte will just use the newer SDK during runtime.
\ No newline at end of file
+and Overte will just use the newer SDK during runtime.
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 968f8a0826b..b6e538541db 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -136,7 +136,7 @@ if (ANDROID)
add_definitions(-DANDROID_APP_INTERFACE)
endif()
else ()
- set(PLATFORM_QT_COMPONENTS WebEngine Xml)
+ set(PLATFORM_QT_COMPONENTS WebEngineCore Xml)
add_definitions(-DHAVE_EXT_clip_cull_distance)
endif()
@@ -160,7 +160,7 @@ else()
endif()
foreach(PLATFORM_QT_COMPONENT ${PLATFORM_QT_COMPONENTS})
- list(APPEND PLATFORM_QT_LIBRARIES "Qt5::${PLATFORM_QT_COMPONENT}")
+ list(APPEND PLATFORM_QT_LIBRARIES "Qt6::${PLATFORM_QT_COMPONENT}")
endforeach()
message(STATUS "Use optimized IK: " ${OVERTE_USE_OPTIMIZED_IK})
diff --git a/android/apps/framePlayer/src/main/cpp/qml/main.qml b/android/apps/framePlayer/src/main/cpp/qml/main.qml
index 34c4507f9de..a8fd27d907e 100644
--- a/android/apps/framePlayer/src/main/cpp/qml/main.qml
+++ b/android/apps/framePlayer/src/main/cpp/qml/main.qml
@@ -1,5 +1,5 @@
import QtQuick 2.2
-import QtQuick.Dialogs 1.1
+import QtQuick.Dialogs
import Qt.labs.folderlistmodel 2.11
Item {
diff --git a/android/apps/interface/src/main/cpp/native.cpp b/android/apps/interface/src/main/cpp/native.cpp
index 42118d89db0..b8ade2f8452 100644
--- a/android/apps/interface/src/main/cpp/native.cpp
+++ b/android/apps/interface/src/main/cpp/native.cpp
@@ -407,7 +407,7 @@ Java_io_highfidelity_hifiinterface_fragment_LoginFragment_retrieveAccessToken(JN
QMetaObject::invokeMethod(accountManager.data(), "requestAccessTokenWithAuthCode",
Q_ARG(const QString&, authCode), Q_ARG(const QString&, clientId),
- Q_ARG(const QString&, clientSecret), Q_ARG(const QString&, redirectUri));
+ Q_GENERIC_ARG(const QString&, clientSecret), Q_GENERIC_ARG(const QString&, redirectUri));
}
diff --git a/assignment-client/src/Agent.cpp b/assignment-client/src/Agent.cpp
index 39833b9cb95..7b5dd1bd2f0 100644
--- a/assignment-client/src/Agent.cpp
+++ b/assignment-client/src/Agent.cpp
@@ -753,7 +753,7 @@ void Agent::processAgentAvatarAudio() {
if (_numAvatarSoundSentBytes == (int)audioData->getNumBytes()) {
// we're done with this sound object - so set our pointer back to NULL
// and our sent bytes back to zero
- _avatarSound.clear();
+ _avatarSound.reset();
_numAvatarSoundSentBytes = 0;
_flushEncoder = true;
diff --git a/assignment-client/src/AgentScriptingInterface.h b/assignment-client/src/AgentScriptingInterface.h
index 1146c4006ba..277ed3d773f 100644
--- a/assignment-client/src/AgentScriptingInterface.h
+++ b/assignment-client/src/AgentScriptingInterface.h
@@ -101,7 +101,7 @@ public slots:
* }, 1000);
* }());
*/
- void playAvatarSound(SharedSoundPointer avatarSound) const { _agent->playAvatarSound(avatarSound); }
+ void playAvatarSound(std::shared_ptr avatarSound) const { _agent->playAvatarSound(avatarSound); }
private:
Agent* _agent;
diff --git a/assignment-client/src/AssignmentClientMonitor.cpp b/assignment-client/src/AssignmentClientMonitor.cpp
index 901586ce60e..67fdcbae3bb 100644
--- a/assignment-client/src/AssignmentClientMonitor.cpp
+++ b/assignment-client/src/AssignmentClientMonitor.cpp
@@ -295,7 +295,8 @@ void AssignmentClientMonitor::spawnChildClient() {
void AssignmentClientMonitor::checkSpares() {
auto nodeList = DependencyManager::get();
- QUuid aSpareId = "";
+ // QT6TODO: it was originally composed for empty QString, is it null UUID?
+ QUuid aSpareId {};
unsigned int spareCount = 0;
unsigned int totalCount = 0;
diff --git a/assignment-client/src/assets/AssetServer.cpp b/assignment-client/src/assets/AssetServer.cpp
index 731687b6374..d3da1d759cf 100644
--- a/assignment-client/src/assets/AssetServer.cpp
+++ b/assignment-client/src/assets/AssetServer.cpp
@@ -28,6 +28,7 @@
#include
#include
#include
+#include
#include
#include
@@ -434,7 +435,7 @@ void AssetServer::completeSetup() {
// Check the asset directory to output some information about what we have
auto files = _filesDirectory.entryList(QDir::Files);
- QRegExp hashFileRegex { AssetUtils::ASSET_HASH_REGEX_STRING };
+ QRegularExpression hashFileRegex { AssetUtils::ASSET_HASH_REGEX_STRING };
auto hashedFiles = files.filter(hashFileRegex);
qCInfo(asset_server) << "There are" << hashedFiles.size() << "asset files in the asset directory.";
diff --git a/assignment-client/src/avatars/ScriptableAvatar.cpp b/assignment-client/src/avatars/ScriptableAvatar.cpp
index a1db33fbc87..112753b84df 100644
--- a/assignment-client/src/avatars/ScriptableAvatar.cpp
+++ b/assignment-client/src/avatars/ScriptableAvatar.cpp
@@ -64,14 +64,14 @@ void ScriptableAvatar::stopAnimation() {
QMetaObject::invokeMethod(this, "stopAnimation");
return;
}
- _animation.clear();
+ _animation.reset();
}
AnimationDetails ScriptableAvatar::getAnimationDetails() {
if (QThread::currentThread() != thread()) {
AnimationDetails result;
BLOCKING_INVOKE_METHOD(this, "getAnimationDetails",
- Q_RETURN_ARG(AnimationDetails, result));
+ Q_GENERIC_RETURN_ARG(AnimationDetails, result));
return result;
}
return _animationDetails;
@@ -222,7 +222,7 @@ void ScriptableAvatar::update(float deltatime) {
}
} else {
- _animation.clear();
+ _animation.reset();
}
}
}
diff --git a/assignment-client/src/octree/OctreeHeadlessViewer.h b/assignment-client/src/octree/OctreeHeadlessViewer.h
index 2c256c8516c..58044bf3d95 100644
--- a/assignment-client/src/octree/OctreeHeadlessViewer.h
+++ b/assignment-client/src/octree/OctreeHeadlessViewer.h
@@ -43,14 +43,14 @@ public slots:
* @function EntityViewer.setPosition
* @param {Vec3} position - The position of the view frustum.
*/
- void setPosition(const glm::vec3& position) { _hasViewFrustum = true; _viewFrustum.setPosition(position); }
+ void setPosition(const glm::vec<3,float,glm::packed_highp>& position) { _hasViewFrustum = true; _viewFrustum.setPosition(position); }
/*@jsdoc
* Sets the orientation of the view frustum.
* @function EntityViewer.setOrientation
* @param {Quat} orientation - The orientation of the view frustum.
*/
- void setOrientation(const glm::quat& orientation) { _hasViewFrustum = true; _viewFrustum.setOrientation(orientation); }
+ void setOrientation(const glm::qua& orientation) { _hasViewFrustum = true; _viewFrustum.setOrientation(orientation); }
/*@jsdoc
* Sets the radius of the center "keyhole" in the view frustum.
@@ -99,14 +99,14 @@ public slots:
* @function EntityViewer.getPosition
* @returns {Vec3} The position of the view frustum.
*/
- const glm::vec3& getPosition() const { return _viewFrustum.getPosition(); }
+ const glm::vec<3,float,glm::packed_highp>& getPosition() const { return _viewFrustum.getPosition(); }
/*@jsdoc
* Gets the orientation of the view frustum.
* @function EntityViewer.getOrientation
* @returns {Quat} The orientation of the view frustum.
*/
- const glm::quat& getOrientation() const { return _viewFrustum.getOrientation(); }
+ const glm::qua& getOrientation() const { return _viewFrustum.getOrientation(); }
// getters for LOD and PPS
diff --git a/cmake/macros/MemoryDebugger.cmake b/cmake/macros/MemoryDebugger.cmake
index 7181586e0b6..ca470e1166f 100644
--- a/cmake/macros/MemoryDebugger.cmake
+++ b/cmake/macros/MemoryDebugger.cmake
@@ -41,9 +41,9 @@ if (OVERTE_MEMORY_DEBUGGING)
# usr/bin/ld: ../../libraries/audio/libaudio.so: undefined reference to `FIR_1x4_AVX512(float*, float*, float*, float*, float*, float (*) [64], int)'
# The '-DSTACK_PROTECTOR' argument below disables the usage of this function in the code. This should be fine as it only works on the latest Intel hardware,
# and is an optimization that should make no functional difference.
- SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=undefined -fsanitize=address -fsanitize=leak -U_FORTIFY_SOURCE -DSTACK_PROTECTOR -fstack-protector-strong -fno-omit-frame-pointer")
- SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fsanitize=undefined -fsanitize=address -fsanitize=leak")
- SET(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fsanitize=undefined -fsanitize=address -fsanitize=leak")
+ SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=undefined -fsanitize=address -fsanitize=leak -U_FORTIFY_SOURCE -DSTACK_PROTECTOR -fstack-protector-strong -fno-omit-frame-pointer -fsanitize-recover=address")
+ SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fsanitize=undefined -fsanitize=address -fsanitize=leak -fsanitize-recover=address")
+ SET(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fsanitize=undefined -fsanitize=address -fsanitize=leak -fsanitize-recover=address")
elseif(CMAKE_CXX_COMPILER_ID MATCHES "MSVC")
# https://docs.microsoft.com/en-us/cpp/sanitizers/asan?view=msvc-160
# Supported experimentally starting from VS2019 v16.4, and officially from v16.9.
diff --git a/cmake/macros/PackageLibrariesForDeployment.cmake b/cmake/macros/PackageLibrariesForDeployment.cmake
index 65d8e200618..e4207190602 100644
--- a/cmake/macros/PackageLibrariesForDeployment.cmake
+++ b/cmake/macros/PackageLibrariesForDeployment.cmake
@@ -14,7 +14,7 @@ macro(PACKAGE_LIBRARIES_FOR_DEPLOYMENT)
if (WIN32)
set(PLUGIN_PATH "plugins")
- get_target_property(Qt_Core_Location Qt5::Core LOCATION)
+ get_target_property(Qt_Core_Location Qt6::Core LOCATION)
get_filename_component(QT_BIN_DIR ${Qt_Core_Location} DIRECTORY)
find_program(WINDEPLOYQT_COMMAND windeployqt PATHS ${QT_BIN_DIR})
@@ -23,7 +23,7 @@ macro(PACKAGE_LIBRARIES_FOR_DEPLOYMENT)
endif ()
# add a post-build command to call windeployqt to copy Qt plugins
- set(CMD "${WINDEPLOYQT_COMMAND} ${EXTRA_DEPLOY_OPTIONS} --no-compiler-runtime --no-opengl-sw --no-angle -no-system-d3d-compiler")
+ set(CMD "${WINDEPLOYQT_COMMAND} ${EXTRA_DEPLOY_OPTIONS} --no-compiler-runtime --no-opengl-sw -no-system-d3d-compiler")
file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/windeploy-${TARGET_NAME}.bat" "${CMD} %*")
add_custom_command(
diff --git a/cmake/macros/SetupHifiLibrary.cmake b/cmake/macros/SetupHifiLibrary.cmake
index aa37b2a3a87..cbc090c9f76 100644
--- a/cmake/macros/SetupHifiLibrary.cmake
+++ b/cmake/macros/SetupHifiLibrary.cmake
@@ -64,13 +64,13 @@ macro(SETUP_HIFI_LIBRARY)
endif ()
set(${TARGET_NAME}_DEPENDENCY_QT_MODULES ${ARGN})
- list(APPEND ${TARGET_NAME}_DEPENDENCY_QT_MODULES Core)
+ list(APPEND ${TARGET_NAME}_DEPENDENCY_QT_MODULES Core Core5Compat)
# find these Qt modules and link them to our own target
- find_package(Qt5 COMPONENTS ${${TARGET_NAME}_DEPENDENCY_QT_MODULES} REQUIRED CMAKE_FIND_ROOT_PATH_BOTH)
+ find_package(Qt6 COMPONENTS ${${TARGET_NAME}_DEPENDENCY_QT_MODULES} REQUIRED CMAKE_FIND_ROOT_PATH_BOTH)
foreach(QT_MODULE ${${TARGET_NAME}_DEPENDENCY_QT_MODULES})
- target_link_libraries(${TARGET_NAME} Qt5::${QT_MODULE})
+ target_link_libraries(${TARGET_NAME} Qt6::${QT_MODULE})
endforeach()
# Don't make scribed shaders, generated entity files, generated pipelines, or QT resource files cumulative
diff --git a/cmake/macros/SetupHifiProject.cmake b/cmake/macros/SetupHifiProject.cmake
index a4da4a3a056..4b5dc0eb130 100644
--- a/cmake/macros/SetupHifiProject.cmake
+++ b/cmake/macros/SetupHifiProject.cmake
@@ -28,10 +28,10 @@ macro(SETUP_HIFI_PROJECT)
target_include_directories(${TARGET_NAME} PRIVATE "${CMAKE_BINARY_DIR}/includes")
set(${TARGET_NAME}_DEPENDENCY_QT_MODULES ${ARGN})
- list(APPEND ${TARGET_NAME}_DEPENDENCY_QT_MODULES Core)
+ list(APPEND ${TARGET_NAME}_DEPENDENCY_QT_MODULES Core Core5Compat)
# find these Qt modules and link them to our own target
- find_package(Qt5 COMPONENTS ${${TARGET_NAME}_DEPENDENCY_QT_MODULES} REQUIRED)
+ find_package(Qt6 COMPONENTS ${${TARGET_NAME}_DEPENDENCY_QT_MODULES} REQUIRED)
# disable /OPT:REF and /OPT:ICF for the Debug builds
# This will prevent the following linker warnings
@@ -41,7 +41,7 @@ macro(SETUP_HIFI_PROJECT)
endif()
foreach(QT_MODULE ${${TARGET_NAME}_DEPENDENCY_QT_MODULES})
- target_link_libraries(${TARGET_NAME} Qt5::${QT_MODULE})
+ target_link_libraries(${TARGET_NAME} Qt6::${QT_MODULE})
endforeach()
target_link_libraries(${TARGET_NAME} ${CMAKE_THREAD_LIBS_INIT})
diff --git a/cmake/macros/SetupHifiTestCase.cmake b/cmake/macros/SetupHifiTestCase.cmake
index 017a0222f55..4df4829dc4a 100644
--- a/cmake/macros/SetupHifiTestCase.cmake
+++ b/cmake/macros/SetupHifiTestCase.cmake
@@ -108,10 +108,10 @@ macro(SETUP_HIFI_TESTCASE)
list(APPEND ${TARGET_NAME}_DEPENDENCY_QT_MODULES Core Test)
# find these Qt modules and link them to our own target
- find_package(Qt5 COMPONENTS ${${TARGET_NAME}_DEPENDENCY_QT_MODULES} REQUIRED)
+ find_package(Qt6 COMPONENTS ${${TARGET_NAME}_DEPENDENCY_QT_MODULES} REQUIRED)
foreach(QT_MODULE ${${TARGET_NAME}_DEPENDENCY_QT_MODULES})
- target_link_libraries(${TARGET_NAME} Qt5::${QT_MODULE})
+ target_link_libraries(${TARGET_NAME} Qt6::${QT_MODULE})
endforeach()
target_link_libraries(${TARGET_NAME} ${CMAKE_THREAD_LIBS_INIT})
diff --git a/cmake/macros/TargetQuazip.cmake b/cmake/macros/TargetQuazip.cmake
index bf495536475..a37a9197ec8 100644
--- a/cmake/macros/TargetQuazip.cmake
+++ b/cmake/macros/TargetQuazip.cmake
@@ -1,18 +1,26 @@
-#
+#
# Copyright 2015 High Fidelity, Inc.
+# Copyright 2025 Overte e.V.
# Created by Leonardo Murillo on 2015/11/20
#
# Distributed under the Apache License, Version 2.0.
# See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
-#
+#
macro(TARGET_QUAZIP)
+ # System QuaZip sets `IMPORTED_GLOBAL` for itself when finding it using find_package().
+ # Running find_package() a second time shouldn't do anything, since the package was already found,
+ # yet it fails with:
+ # `Attempt to promote imported target "QuaZip::QuaZip" to global scope (by setting IMPORTED_GLOBAL) which is not built in this directory.`
+ # We avoid this error by guarding against running find_package() multiple times.
if(OVERTE_USE_SYSTEM_LIBS)
find_package(PkgConfig REQUIRED)
- pkg_check_modules(QuaZip REQUIRED quazip1-qt5)
+ pkg_check_modules(QuaZip REQUIRED quazip1-qt6)
target_include_directories(${TARGET_NAME} SYSTEM PRIVATE ${QuaZip_INCLUDE_DIRS})
target_link_libraries(${TARGET_NAME} ${QuaZip_LINK_LIBRARIES})
else()
- find_package(QuaZip-Qt5 REQUIRED)
- target_link_libraries(${TARGET_NAME} QuaZip::QuaZip)
+ if(NOT TARGET QuaZip::QuaZip) # if target doesn't exist.
+ find_package(QuaZip-Qt6 REQUIRED)
+ endif()
+ target_link_libraries(${TARGET_NAME} QuaZip::QuaZip)
endif()
endmacro()
diff --git a/cmake/modules/FindQt5LinguistToolsMacros.cmake b/cmake/modules/FindQt5LinguistToolsMacros.cmake
index bd9d55cb160..90e1bbb6dcc 100644
--- a/cmake/modules/FindQt5LinguistToolsMacros.cmake
+++ b/cmake/modules/FindQt5LinguistToolsMacros.cmake
@@ -30,7 +30,7 @@
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#=============================================================================
-function(QT5_CREATE_TRANSLATION_CUSTOM _qm_files)
+function(QT6_CREATE_TRANSLATION_CUSTOM _qm_files)
set(options)
set(oneValueArgs)
set(multiValueArgs OPTIONS)
@@ -76,7 +76,7 @@ function(QT5_CREATE_TRANSLATION_CUSTOM _qm_files)
get_source_file_property(_qm_output_location ${_ts_file} OUTPUT_LOCATION)
add_custom_command(
OUTPUT ${_tmpts_file}
- COMMAND ${Qt5_LUPDATE_EXECUTABLE}
+ COMMAND ${Qt6_LUPDATE_EXECUTABLE}
ARGS ${_lupdate_options} "@${_ts_lst_file}" -ts ${_ts_file}
COMMAND ${CMAKE_COMMAND} -E copy ${_ts_file} ${_tmpts_file}
DEPENDS ${_my_sources} ${_ts_lst_file} VERBATIM)
@@ -84,12 +84,12 @@ function(QT5_CREATE_TRANSLATION_CUSTOM _qm_files)
set_property(SOURCE ${_tmpts_file} PROPERTY OUTPUT_LOCATION ${_qm_output_location})
endif()
endforeach()
- qt5_add_translation(${_qm_files} ${_my_temptsfiles})
+ qt6_add_translation(${_qm_files} ${_my_temptsfiles})
set(${_qm_files} ${${_qm_files}} PARENT_SCOPE)
endfunction()
-function(QT5_ADD_TRANSLATION _qm_files)
+function(QT6_ADD_TRANSLATION _qm_files)
foreach(_current_FILE ${ARGN})
get_filename_component(_abs_FILE ${_current_FILE} ABSOLUTE)
get_filename_component(qm ${_abs_FILE} NAME_WE)
@@ -102,7 +102,7 @@ function(QT5_ADD_TRANSLATION _qm_files)
endif()
add_custom_command(OUTPUT ${qm}
- COMMAND ${Qt5_LRELEASE_EXECUTABLE}
+ COMMAND ${Qt6_LRELEASE_EXECUTABLE}
ARGS ${_abs_FILE} -qm ${qm}
DEPENDS ${_abs_FILE} VERBATIM
)
diff --git a/conanfile.py b/conanfile.py
index 582e9a8b357..9993d18505e 100644
--- a/conanfile.py
+++ b/conanfile.py
@@ -24,6 +24,7 @@ class Overte(ConanFile):
"openssl*:shared": "True",
"qt*:shared": "True",
"qt*:gui": "True",
+ "qt*:qt5compat": "True", # Required by Quazip 1.4 and probably us
"qt*:qtdeclarative": "True",
"qt*:qtimageformats": "True", # WebP texture support
"qt*:qtlocation": "True",
@@ -34,6 +35,7 @@ class Overte(ConanFile):
"qt*:qtsvg": "True",
"qt*:qtwebchannel": "True",
"qt*:qtwebengine": "True",
+ "qt*:qtshadertools": "True", # For Qt WebEngineWidgets
"qt*:qtwebsockets": "True",
"qt*:qtwebview": "True",
"qt*:qtx11extras": "True", # Required by gpu-frame-player on Linux
@@ -72,7 +74,7 @@ def requirements(self):
self.requires("openexr/3.1.9")
self.requires("openvr/2.2.3@overte/stable")
self.requires("openxr/1.1.46@overte/stable")
- self.requires("opus/1.5.2")
+ self.requires("opus/1.5.2", force=True) # The Qt6 source package depends on an older opus version than we do.
self.requires("quazip/1.4")
self.requires("scribe/2019.02@overte/stable")
self.requires("sdl/2.32.10")
@@ -88,13 +90,13 @@ def requirements(self):
openssl = "openssl/1.1.1q"
if self.options.qt_source == "system":
- self.requires("qt/5.15.2@overte/system", force=True)
+ self.requires("qt/6.x@overte/system", force=True)
if self.settings.os == "Linux":
openssl = "openssl/system@anotherfoxguy/stable"
elif self.options.qt_source == "aqt":
- self.requires("qt/5.15.2@overte/aqt", force=True)
+ self.requires("qt/6.10.3@overte/aqt#c692bf111ff0a41e7b82ef12dec20219", force=True)
else:
- self.requires("qt/5.15.18-2026.01.04@overte/stable#4fc772a2dbcd84731eb6ff9904e6e358", force=True)
+ self.requires("qt/6.11.1@overte/experimental#f3b43b7235810a2e064268e976386ca0", force=True)
if self.settings.os == "Windows":
self.requires("neuron/12.2@overte/prebuild")
@@ -103,6 +105,8 @@ def requirements(self):
self.requires(openssl, force=True)
+ self.requires("glib/2.85.3", override=True) # Fix version conflict resulting from Qt and GStreamer.
+
def generate(self):
tc = CMakeToolchain(self)
@@ -134,8 +138,8 @@ def generate(self):
if self.settings.compiler == "gcc":
self.output.status("GCC compiler detected, setting default flags.")
tc.cache_variables.update({
- "CMAKE_CXX_FLAGS_DEBUG_INIT": "-Og -ggdb3",
- "CMAKE_C_FLAGS_DEBUG_INIT": "-Og -ggdb3",
+ "CMAKE_CXX_FLAGS_DEBUG_INIT": "-O0 -ggdb3",
+ "CMAKE_C_FLAGS_DEBUG_INIT": "-O0 -ggdb3",
"CMAKE_CXX_FLAGS_RELWITHDEBINFO_INIT": "-O2 -DNDEBUG -ggdb2",
"CMAKE_C_FLAGS_RELWITHDEBINFO_INIT": "-O2 -DNDEBUG -ggdb2",
"CMAKE_CXX_FLAGS_RELEASE_INIT": "-O3 -DNDEBUG",
@@ -144,8 +148,8 @@ def generate(self):
elif self.settings.compiler == "clang":
self.output.status("Clang compiler detected, setting default flags.")
tc.cache_variables.update({
- "CMAKE_CXX_FLAGS_DEBUG_INIT": "-Og -g",
- "CMAKE_C_FLAGS_DEBUG_INIT": "-Og -g",
+ "CMAKE_CXX_FLAGS_DEBUG_INIT": "-O0 -g",
+ "CMAKE_C_FLAGS_DEBUG_INIT": "-O0 -g",
"CMAKE_CXX_FLAGS_RELWITHDEBINFO_INIT": "-O2 -DNDEBUG -g",
"CMAKE_C_FLAGS_RELWITHDEBINFO_INIT": "-O2 -DNDEBUG -g",
"CMAKE_CXX_FLAGS_RELEASE_INIT": "-O3 -DNDEBUG",
diff --git a/domain-server/src/ContentSettingsBackupHandler.cpp b/domain-server/src/ContentSettingsBackupHandler.cpp
index 90f95349347..dfc75f7e58b 100644
--- a/domain-server/src/ContentSettingsBackupHandler.cpp
+++ b/domain-server/src/ContentSettingsBackupHandler.cpp
@@ -20,6 +20,7 @@
#include
#include
#include
+#include
#if !defined(__clang__) && defined(__GNUC__)
#pragma GCC diagnostic pop
@@ -102,7 +103,7 @@ std::pair ContentSettingsBackupHandler::recoverBackup(const QStri
zipFile.close();
if (zipFile.getZipError() != UNZ_OK) {
- QString errorStr("Failed to unzip " + CONTENT_SETTINGS_BACKUP_FILENAME + ": " + zipFile.getZipError());
+ QString errorStr("Failed to unzip " + CONTENT_SETTINGS_BACKUP_FILENAME + ": " + QString::number(zipFile.getZipError()));
qCritical() << errorStr;
return { false, errorStr };
}
diff --git a/domain-server/src/DomainContentBackupManager.cpp b/domain-server/src/DomainContentBackupManager.cpp
index b22b001144f..0d7f86e0341 100644
--- a/domain-server/src/DomainContentBackupManager.cpp
+++ b/domain-server/src/DomainContentBackupManager.cpp
@@ -28,6 +28,7 @@
#include
#include
#include
+#include
#include
@@ -81,7 +82,7 @@ void DomainContentBackupManager::parseBackupRules(const QVariantList& backupRule
int count = map["maxBackupVersions"].toInt();
auto name = map["Name"].toString();
auto format = name.toLower();
- QRegExp matchDisallowedCharacters { "[^a-zA-Z0-9\\-_]+" };
+ QRegularExpression matchDisallowedCharacters { "[^a-zA-Z0-9\\-_]+" };
format.replace(matchDisallowedCharacters, "_");
qCDebug(domain_server) << " Name:" << name;
@@ -313,8 +314,8 @@ void DomainContentBackupManager::recoverFromBackup(MiniPromise::Promise promise,
};
if (QThread::currentThread() != thread()) {
- QMetaObject::invokeMethod(this, "recoverFromBackup", Q_ARG(MiniPromise::Promise, promise),
- Q_ARG(const QString&, backupName), Q_ARG(const QString&, username));
+ QMetaObject::invokeMethod(this, "recoverFromBackup", Q_GENERIC_ARG(MiniPromise::Promise, promise),
+ Q_GENERIC_ARG(const QString&, backupName), Q_GENERIC_ARG(const QString&, username));
return;
}
diff --git a/domain-server/src/DomainGatekeeper.cpp b/domain-server/src/DomainGatekeeper.cpp
index 3844db12f57..432e8a57495 100644
--- a/domain-server/src/DomainGatekeeper.cpp
+++ b/domain-server/src/DomainGatekeeper.cpp
@@ -20,6 +20,7 @@
#include
#include
+#include
#include
#include
@@ -340,7 +341,7 @@ void DomainGatekeeper::updateNodePermissions() {
// authentication and verifiedUsername is only set once they user's key has been confirmed.
QString verifiedUsername = node->getPermissions().getVerifiedUserName();
QString verifiedDomainUserName = node->getPermissions().getVerifiedDomainUserName();
- NodePermissions userPerms(NodePermissionsKey(verifiedUsername, 0));
+ NodePermissions userPerms(NodePermissionsKey(verifiedUsername, QUuid()));
if (node->getPermissions().isAssignment) {
// this node is an assignment-client
@@ -469,7 +470,7 @@ SharedNodePointer DomainGatekeeper::processAgentConnectRequest(const NodeConnect
auto limitedNodeList = DependencyManager::get();
// start with empty permissions
- NodePermissions userPerms(NodePermissionsKey(username, 0));
+ NodePermissions userPerms(NodePermissionsKey(username, QUuid()));
userPerms.setAll(false);
// check if this user is on our local machine - if this is true set permissions to those for a "localhost" connection
@@ -1094,11 +1095,11 @@ void DomainGatekeeper::getIsGroupMemberJSONCallback(QNetworkReply* requestReply)
QJsonObject groups = data["groups"].toObject();
QString username = data["username"].toString();
_server->_settingsManager.clearGroupMemberships(username);
- foreach (auto groupID, groups.keys()) {
+ for (const auto &groupID: groups.keys()) {
QJsonObject group = groups[groupID].toObject();
QJsonObject rank = group["rank"].toObject();
QUuid rankID = QUuid(rank["id"].toString());
- _server->_settingsManager.recordGroupMembership(username, groupID, rankID);
+ _server->_settingsManager.recordGroupMembership(username, QUuid::fromString(groupID), rankID);
}
} else {
qDebug() << "getIsGroupMember api call returned:" << QJsonDocument(jsonObject).toJson(QJsonDocument::Compact);
diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp
index 2f13fe30587..9b71a18027e 100644
--- a/domain-server/src/DomainServer.cpp
+++ b/domain-server/src/DomainServer.cpp
@@ -255,7 +255,7 @@ DomainServer::DomainServer(int argc, char* argv[]) :
ch.setEnabled(_settingsManager.valueOrDefaultValueForKeyPath(CRASH_REPORTER).toBool());
qRegisterMetaType("DomainServerWebSessionData");
- qRegisterMetaTypeStreamOperators("DomainServerWebSessionData");
+ //qRegisterMetaTypeStreamOperators("DomainServerWebSessionData");
// make sure we hear about newly connected nodes from our gatekeeper
connect(&_gatekeeper, &DomainGatekeeper::connectedNode, this, &DomainServer::handleConnectedNode);
@@ -699,7 +699,7 @@ void DomainServer::handleTempDomainSuccess(QNetworkReply* requestReply) {
// store the new token to the account info
auto accountManager = DependencyManager::get();
- accountManager->setTemporaryDomain(id, key);
+ accountManager->setTemporaryDomain(QUuid::fromString(id), key);
// change our domain ID immediately
DependencyManager::get()->setSessionUUID(QUuid { id });
@@ -831,7 +831,7 @@ void DomainServer::setupNodeListAndAssignments() {
} else {
QVariant idValueVariant = _settingsManager.valueForKeyPath(METAVERSE_DOMAIN_ID_KEY_PATH);
if (idValueVariant.isValid()) {
- nodeList->setSessionUUID(idValueVariant.toString());
+ nodeList->setSessionUUID(QUuid::fromString(idValueVariant.toString()));
isMetaverseDomain = true; // if we have an ID, we'll assume we're a metaverse domain
} else {
nodeList->setSessionUUID(QUuid::createUuid()); // Use random UUID
@@ -1002,7 +1002,7 @@ bool DomainServer::resetAccountManagerAccessToken() {
if (accessToken.isEmpty()) {
QVariant accessTokenVariant = _settingsManager.valueForKeyPath(ACCESS_TOKEN_KEY_PATH);
- if (accessTokenVariant.canConvert(QMetaType::QString)) {
+ if (accessTokenVariant.canConvert(QMetaType(QMetaType::QString))) {
accessToken = accessTokenVariant.toString();
} else {
qWarning() << "No access token is present. Some operations that use the directory services API will fail.";
@@ -1207,7 +1207,7 @@ void DomainServer::createStaticAssignmentsForType(Assignment::Type type, const Q
int configCounter = 0;
foreach(const QVariant& configVariant, configList) {
- if (configVariant.canConvert(QMetaType::QVariantMap)) {
+ if (configVariant.canConvert(QMetaType(QMetaType::QVariantMap))) {
QVariantMap configMap = configVariant.toMap();
// check the config string for a pool
@@ -2849,11 +2849,12 @@ std::pair DomainServer::isAuthenticatedRequest(HTTPConnection* c
if (_oauthEnable) {
QString cookieString = connection->requestHeader(HTTP_COOKIE_HEADER_KEY);
- QRegExp cookieUUIDRegex(COOKIE_UUID_REGEX_STRING);
+ QRegularExpression cookieUUIDRegex(COOKIE_UUID_REGEX_STRING);
QUuid cookieUUID;
- if (cookieString.indexOf(cookieUUIDRegex) != -1) {
- cookieUUID = cookieUUIDRegex.cap(1);
+ auto matches = cookieUUIDRegex.match(cookieString);
+ if (matches.hasMatch()) {
+ cookieUUID = QUuid::fromString(matches.captured(1));
}
if (_settingsManager.valueForKeyPath(BASIC_AUTH_USERNAME_KEY_PATH).isValid()) {
@@ -2950,7 +2951,7 @@ std::pair DomainServer::isAuthenticatedRequest(HTTPConnection* c
QString settingsPassword = settingsPasswordVariant.isValid() ? settingsPasswordVariant.toString() : "";
QString hexHeaderPassword = headerPassword.isEmpty() ?
- "" : QString(QCryptographicHash::hash(headerPassword.toUtf8(), QCryptographicHash::Sha256).toHex());
+ QByteArray("") : QCryptographicHash::hash(headerPassword.toUtf8(), QCryptographicHash::Sha256).toHex();
if (settingsUsername == headerUsername && hexHeaderPassword == settingsPassword) {
qCInfo(domain_server_auth) << httpPeerAddress << "- Basic:" << headerUsername << "-"
@@ -3025,7 +3026,7 @@ Headers DomainServer::setupCookieHeadersFromProfileReply(QNetworkReply* profileR
// setup expiry for cookie to 1 month from today
QDateTime cookieExpiry = QDateTime::currentDateTimeUtc().addMonths(1);
- QString cookieString = HIFI_SESSION_COOKIE_KEY + "=" + uuidStringWithoutCurlyBraces(cookieUUID.toString());
+ QString cookieString = HIFI_SESSION_COOKIE_KEY + "=" + uuidStringWithoutCurlyBraces(cookieUUID);
cookieString += "; expires=" + cookieExpiry.toString("ddd, dd MMM yyyy HH:mm:ss") + " GMT";
cookieString += "; domain=" + _hostname + "; path=/";
diff --git a/domain-server/src/DomainServerNodeData.cpp b/domain-server/src/DomainServerNodeData.cpp
index 80e5fea56a2..a4ed43215c0 100644
--- a/domain-server/src/DomainServerNodeData.cpp
+++ b/domain-server/src/DomainServerNodeData.cpp
@@ -28,7 +28,13 @@ DomainServerNodeData::DomainServerNodeData() {
}
void DomainServerNodeData::updateJSONStats(const QByteArray& statsByteArray) {
- _statsJSONObject = overrideValuesIfNeeded(QCborValue::fromCbor(statsByteArray).toJsonValue().toObject());
+ QCborParserError cborError;
+ auto cbor = QCborValue::fromCbor(statsByteArray, &cborError);
+ if (cborError.error != QCborError::NoError) {
+ qWarning() << "DomainServerNodeData::updateJSONStats: corrupted cbor: " << QString(statsByteArray.toHex());
+ return;
+ }
+ _statsJSONObject = overrideValuesIfNeeded(cbor.toJsonValue().toObject());
}
QJsonObject DomainServerNodeData::overrideValuesIfNeeded(const QJsonObject& newStats) {
diff --git a/domain-server/src/DomainServerSettingsManager.cpp b/domain-server/src/DomainServerSettingsManager.cpp
index fe13c29df21..7eec5c1c56d 100644
--- a/domain-server/src/DomainServerSettingsManager.cpp
+++ b/domain-server/src/DomainServerSettingsManager.cpp
@@ -256,7 +256,7 @@ void DomainServerSettingsManager::setupConfigMap(const QString& userConfigFilena
QVariant* allowedUsers = _configMap.valueForKeyPath(ALLOWED_USERS_SETTINGS_KEYPATH);
if (allowedUsers
- && allowedUsers->canConvert(QMetaType::QVariantList)
+ && allowedUsers->canConvert(QMetaType(QMetaType::QVariantList))
&& reinterpret_cast(allowedUsers)->size() > 0) {
qDebug() << "Forcing security.restricted_access to TRUE since there was an"
@@ -277,7 +277,7 @@ void DomainServerSettingsManager::setupConfigMap(const QString& userConfigFilena
// this was prior to change of poorly named entitiesFileName to entitiesFilePath
QVariant* persistFileNameVariant = _configMap.valueForKeyPath(ENTITY_SERVER_SETTINGS_KEY + "." + ENTITY_FILE_NAME_KEY);
- if (persistFileNameVariant && persistFileNameVariant->canConvert(QMetaType::QString)) {
+ if (persistFileNameVariant && persistFileNameVariant->canConvert(QMetaType(QMetaType::QString))) {
QString persistFileName = persistFileNameVariant->toString();
qDebug() << "Migrating persistFilename to persistFilePath for entity-server settings";
@@ -290,7 +290,7 @@ void DomainServerSettingsManager::setupConfigMap(const QString& userConfigFilena
// remove the old setting
QVariant* entityServerVariant = _configMap.valueForKeyPath(ENTITY_SERVER_SETTINGS_KEY);
- if (entityServerVariant && entityServerVariant->canConvert(QMetaType::QVariantMap)) {
+ if (entityServerVariant && entityServerVariant->canConvert(QMetaType(QMetaType::QVariantMap))) {
QVariantMap entityServerMap = entityServerVariant->toMap();
entityServerMap.remove(ENTITY_FILE_NAME_KEY);
@@ -307,7 +307,7 @@ void DomainServerSettingsManager::setupConfigMap(const QString& userConfigFilena
QVariant* passwordVariant = _configMap.valueForKeyPath(BASIC_AUTH_PASSWORD_KEY_PATH);
- if (passwordVariant && passwordVariant->canConvert(QMetaType::QString)) {
+ if (passwordVariant && passwordVariant->canConvert(QMetaType(QMetaType::QString))) {
QString plaintextPassword = passwordVariant->toString();
qDebug() << "Migrating plaintext password to SHA256 hash in domain-server settings.";
@@ -343,12 +343,12 @@ void DomainServerSettingsManager::setupConfigMap(const QString& userConfigFilena
foreach (QString allowedUser, allowedUsers) {
// even if isRestrictedAccess is false, we have to add explicit rows for these users.
- _agentPermissions[NodePermissionsKey(allowedUser, 0)].reset(new NodePermissions(allowedUser));
- _agentPermissions[NodePermissionsKey(allowedUser, 0)]->set(NodePermissions::Permission::canConnectToDomain);
+ _agentPermissions[NodePermissionsKey(allowedUser, QUuid())].reset(new NodePermissions(allowedUser));
+ _agentPermissions[NodePermissionsKey(allowedUser, QUuid())]->set(NodePermissions::Permission::canConnectToDomain);
}
foreach (QString allowedEditor, allowedEditors) {
- NodePermissionsKey editorKey(allowedEditor, 0);
+ NodePermissionsKey editorKey(allowedEditor, QUuid());
if (!_agentPermissions.contains(editorKey)) {
_agentPermissions[editorKey].reset(new NodePermissions(allowedEditor));
if (isRestrictedAccess) {
@@ -613,13 +613,13 @@ void DomainServerSettingsManager::packPermissionsForMap(QString mapName,
// find (or create) the "security" section of the settings map
QVariant* security = _configMap.valueForKeyPath("security", true);
- if (!security->canConvert(QMetaType::QVariantMap)) {
+ if (!security->canConvert(QMetaType(QMetaType::QVariantMap))) {
(*security) = QVariantMap();
}
// find (or create) whichever subsection of "security" we are packing
QVariant* permissions = _configMap.valueForKeyPath(keyPath, true);
- if (!permissions->canConvert(QMetaType::QVariantList)) {
+ if (!permissions->canConvert(QMetaType(QMetaType::QVariantList))) {
(*permissions) = QVariantList();
}
@@ -707,7 +707,7 @@ bool DomainServerSettingsManager::unpackPermissionsForKeypath(const QString& key
return false;
}
- if (!permissions.canConvert(QMetaType::QVariantList)) {
+ if (!permissions.canConvert(QMetaType(QMetaType::QVariantList))) {
qDebug() << "Failed to extract permissions for key path" << keyPath << "from settings.";
}
@@ -969,7 +969,7 @@ void DomainServerSettingsManager::processNodeKickRequestPacket(QSharedPointergetMachineFingerprint().toString(), 0);
+ NodePermissionsKey machineFingerprintKey(nodeData->getMachineFingerprint().toString(), QUuid());
// check if there were already permissions for the fingerprint
bool hadFingerprintPermissions = hasPermissionsForMachineFingerprint(nodeData->getMachineFingerprint());
@@ -1115,7 +1115,7 @@ NodePermissions DomainServerSettingsManager::getStandardPermissionsForName(const
}
NodePermissions DomainServerSettingsManager::getPermissionsForName(const QString& name) const {
- NodePermissionsKey nameKey = NodePermissionsKey(name, 0);
+ NodePermissionsKey nameKey = NodePermissionsKey(name, QUuid());
if (_agentPermissions.contains(nameKey)) {
return *(_agentPermissions[nameKey].get());
}
@@ -1125,7 +1125,7 @@ NodePermissions DomainServerSettingsManager::getPermissionsForName(const QString
}
NodePermissions DomainServerSettingsManager::getPermissionsForIP(const QHostAddress& address) const {
- NodePermissionsKey ipKey = NodePermissionsKey(address.toString(), 0);
+ NodePermissionsKey ipKey = NodePermissionsKey(address.toString(), QUuid());
if (_ipPermissions.contains(ipKey)) {
return *(_ipPermissions[ipKey].get());
}
@@ -1135,7 +1135,7 @@ NodePermissions DomainServerSettingsManager::getPermissionsForIP(const QHostAddr
}
NodePermissions DomainServerSettingsManager::getPermissionsForMAC(const QString& macAddress) const {
- NodePermissionsKey macKey = NodePermissionsKey(macAddress, 0);
+ NodePermissionsKey macKey = NodePermissionsKey(macAddress, QUuid());
if (_macPermissions.contains(macKey)) {
return *(_macPermissions[macKey].get());
}
@@ -1145,7 +1145,7 @@ NodePermissions DomainServerSettingsManager::getPermissionsForMAC(const QString&
}
NodePermissions DomainServerSettingsManager::getPermissionsForMachineFingerprint(const QUuid& machineFingerprint) const {
- NodePermissionsKey fingerprintKey = NodePermissionsKey(machineFingerprint.toString(), 0);
+ NodePermissionsKey fingerprintKey = NodePermissionsKey(machineFingerprint.toString(), QUuid());
if (_machineFingerprintPermissions.contains(fingerprintKey)) {
return *(_machineFingerprintPermissions[fingerprintKey].get());
}
@@ -1672,7 +1672,7 @@ void DomainServerSettingsManager::updateSetting(const QString& key, const QJsonV
QVariant& possibleMap = settingMap[key];
- if (!possibleMap.canConvert(QMetaType::QVariantMap)) {
+ if (!possibleMap.canConvert(QMetaType(QMetaType::QVariantMap))) {
// if this isn't a map then we need to make it one, otherwise we're about to crash
qDebug() << "Value at" << key << "was not the expected QVariantMap while updating DS settings"
<< "- removing existing value and making it a QVariantMap";
@@ -1890,8 +1890,8 @@ bool DomainServerSettingsManager::recurseJSONObjectAndOverwriteSettings(const QJ
// Compare two members of a permissions list
bool permissionVariantLessThan(const QVariant &v1, const QVariant &v2) {
- if (!v1.canConvert(QMetaType::QVariantMap) ||
- !v2.canConvert(QMetaType::QVariantMap)) {
+ if (!v1.canConvert(QMetaType(QMetaType::QVariantMap)) ||
+ !v2.canConvert(QMetaType(QMetaType::QVariantMap))) {
return v1.toString() < v2.toString();
}
QVariantMap m1 = v1.toMap();
@@ -1916,22 +1916,22 @@ void DomainServerSettingsManager::sortPermissions() {
// sort the permission-names
QVariant* standardPermissions = _configMap.valueForKeyPath(AGENT_STANDARD_PERMISSIONS_KEYPATH);
- if (standardPermissions && standardPermissions->canConvert(QMetaType::QVariantList)) {
+ if (standardPermissions && standardPermissions->canConvert(QMetaType(QMetaType::QVariantList))) {
QList* standardPermissionsList = reinterpret_cast(standardPermissions);
std::sort((*standardPermissionsList).begin(), (*standardPermissionsList).end(), permissionVariantLessThan);
}
QVariant* permissions = _configMap.valueForKeyPath(AGENT_PERMISSIONS_KEYPATH);
- if (permissions && permissions->canConvert(QMetaType::QVariantList)) {
+ if (permissions && permissions->canConvert(QMetaType(QMetaType::QVariantList))) {
QList* permissionsList = reinterpret_cast(permissions);
std::sort((*permissionsList).begin(), (*permissionsList).end(), permissionVariantLessThan);
}
QVariant* groupPermissions = _configMap.valueForKeyPath(GROUP_PERMISSIONS_KEYPATH);
- if (groupPermissions && groupPermissions->canConvert(QMetaType::QVariantList)) {
+ if (groupPermissions && groupPermissions->canConvert(QMetaType(QMetaType::QVariantList))) {
QList* permissionsList = reinterpret_cast(groupPermissions);
std::sort((*permissionsList).begin(), (*permissionsList).end(), permissionVariantLessThan);
}
QVariant* forbiddenPermissions = _configMap.valueForKeyPath(GROUP_FORBIDDENS_KEYPATH);
- if (forbiddenPermissions && forbiddenPermissions->canConvert(QMetaType::QVariantList)) {
+ if (forbiddenPermissions && forbiddenPermissions->canConvert(QMetaType(QMetaType::QVariantList))) {
QList* permissionsList = reinterpret_cast(forbiddenPermissions);
std::sort((*permissionsList).begin(), (*permissionsList).end(), permissionVariantLessThan);
}
@@ -2173,11 +2173,11 @@ void DomainServerSettingsManager::apiGetGroupRanksJSONCallback(QNetworkReply* re
if (jsonObject["status"].toString() == "success") {
QJsonObject groups = jsonObject["data"].toObject()["groups"].toObject();
- foreach (auto groupID, groups.keys()) {
+ for (auto const &groupID: groups.keys()) {
QJsonObject group = groups[groupID].toObject();
QJsonArray ranks = group["ranks"].toArray();
- QHash& ranksForGroup = _groupRanks[groupID];
+ QHash& ranksForGroup = _groupRanks[QUuid::fromString(groupID)];
QHash idsFromThisUpdate;
for (int rankIndex = 0; rankIndex < ranks.size(); rankIndex++) {
@@ -2199,7 +2199,7 @@ void DomainServerSettingsManager::apiGetGroupRanksJSONCallback(QNetworkReply* re
}
// clean up any that went away
- foreach (QUuid rankID, ranksForGroup.keys()) {
+ for (const QUuid &rankID: ranksForGroup.keys()) {
if (!idsFromThisUpdate.contains(rankID)) {
ranksForGroup.remove(rankID);
}
diff --git a/domain-server/src/DomainServerSettingsManager.h b/domain-server/src/DomainServerSettingsManager.h
index 3c7959291cb..c443662761b 100644
--- a/domain-server/src/DomainServerSettingsManager.h
+++ b/domain-server/src/DomainServerSettingsManager.h
@@ -95,25 +95,25 @@ class DomainServerSettingsManager : public QObject {
bool containsKeyPath(const QString& keyPath) { return valueForKeyPath(keyPath).isValid(); }
// these give access to anonymous/localhost/logged-in settings from the domain-server settings page
- bool haveStandardPermissionsForName(const QString& name) const { return _standardAgentPermissions.contains(name, 0); }
+ bool haveStandardPermissionsForName(const QString& name) const { return _standardAgentPermissions.contains(name, QUuid()); }
NodePermissions getStandardPermissionsForName(const NodePermissionsKey& name) const;
// these give access to permissions for specific user-names from the domain-server settings page
- bool havePermissionsForName(const QString& name) const { return _agentPermissions.contains(name, 0); }
+ bool havePermissionsForName(const QString& name) const { return _agentPermissions.contains(name, QUuid()); }
NodePermissions getPermissionsForName(const QString& name) const;
NodePermissions getPermissionsForName(const NodePermissionsKey& key) const { return getPermissionsForName(key.first); }
QStringList getAllNames() const;
// these give access to permissions for specific IPs from the domain-server settings page
- bool hasPermissionsForIP(const QHostAddress& address) const { return _ipPermissions.contains(address.toString(), 0); }
+ bool hasPermissionsForIP(const QHostAddress& address) const { return _ipPermissions.contains(address.toString(), QUuid()); }
NodePermissions getPermissionsForIP(const QHostAddress& address) const;
// these give access to permissions for specific MACs from the domain-server settings page
- bool hasPermissionsForMAC(const QString& macAddress) const { return _macPermissions.contains(macAddress, 0); }
+ bool hasPermissionsForMAC(const QString& macAddress) const { return _macPermissions.contains(macAddress, QUuid()); }
NodePermissions getPermissionsForMAC(const QString& macAddress) const;
// these give access to permissions for specific machine fingerprints from the domain-server settings page
- bool hasPermissionsForMachineFingerprint(const QUuid& machineFingerprint) { return _machineFingerprintPermissions.contains(machineFingerprint.toString(), 0); }
+ bool hasPermissionsForMachineFingerprint(const QUuid& machineFingerprint) { return _machineFingerprintPermissions.contains(machineFingerprint.toString(), QUuid()); }
NodePermissions getPermissionsForMachineFingerprint(const QUuid& machineFingerprint) const;
// these give access to permissions for specific groups from the domain-server settings page
diff --git a/domain-server/src/EntitiesBackupHandler.cpp b/domain-server/src/EntitiesBackupHandler.cpp
index 526bf30a5c6..5346353da30 100644
--- a/domain-server/src/EntitiesBackupHandler.cpp
+++ b/domain-server/src/EntitiesBackupHandler.cpp
@@ -74,7 +74,7 @@ std::pair EntitiesBackupHandler::recoverBackup(const QString& bac
zipFile.close();
if (zipFile.getZipError() != UNZ_OK) {
- QString errorStr("Failed to unzip " + ENTITIES_BACKUP_FILENAME + ": " + zipFile.getZipError());
+ QString errorStr("Failed to unzip " + ENTITIES_BACKUP_FILENAME + ": " + QString::number(zipFile.getZipError()));
qCritical() << errorStr;
return { false, errorStr };
}
diff --git a/domain-server/src/NodeConnectionData.cpp b/domain-server/src/NodeConnectionData.cpp
index d7d813e3df3..c6fa8134291 100644
--- a/domain-server/src/NodeConnectionData.cpp
+++ b/domain-server/src/NodeConnectionData.cpp
@@ -23,7 +23,7 @@ NodeConnectionData NodeConnectionData::fromDataStream(QDataStream& dataStream, c
// Read out the protocol version signature from the connect message
char* rawBytes;
- uint length;
+ qsizetype length;
dataStream.readBytes(rawBytes, length);
newHeader.protocolVersion = QByteArray(rawBytes, length);
diff --git a/interface/CMakeLists.txt b/interface/CMakeLists.txt
index 92ac16c68bd..62422d20201 100644
--- a/interface/CMakeLists.txt
+++ b/interface/CMakeLists.txt
@@ -25,7 +25,7 @@ endfunction()
set(CUSTOM_INTERFACE_QRC_PATHS "")
find_package(
- Qt5 COMPONENTS
+ Qt6 COMPONENTS
Gui Widgets Multimedia Network Qml Quick Svg WebEngineCore WebEngineWidgets
${PLATFORM_QT_COMPONENTS}
WebChannel WebSockets
@@ -43,9 +43,9 @@ generate_qrc(OUTPUT ${RESOURCES_QRC} PATH ${CMAKE_CURRENT_SOURCE_DIR}/resources
if (ANDROID)
# on Android, don't compress the rcc binary
- qt5_add_binary_resources(resources "${RESOURCES_QRC}" DESTINATION "${RESOURCES_RCC}" OPTIONS -no-compress)
+ qt6_add_binary_resources(resources "${RESOURCES_QRC}" DESTINATION "${RESOURCES_RCC}" OPTIONS -no-compress)
else ()
- qt5_add_binary_resources(resources "${RESOURCES_QRC}" DESTINATION "${RESOURCES_RCC}")
+ qt6_add_binary_resources(resources "${RESOURCES_QRC}" DESTINATION "${RESOURCES_RCC}")
endif()
if (OVERTE_BUILD_TOOLS AND JSDOC_ENABLED)
@@ -105,7 +105,7 @@ endif ()
file (GLOB_RECURSE QT_UI_FILES ui/*.ui)
source_group("UI Files" FILES ${QT_UI_FILES})
# have qt5 wrap them and generate the appropriate header files
-qt5_wrap_ui(QT_UI_HEADERS "${QT_UI_FILES}")
+qt6_wrap_ui(QT_UI_HEADERS "${QT_UI_FILES}")
# add them to the interface source files
set(INTERFACE_SRCS ${INTERFACE_SRCS} "${QT_UI_HEADERS}" "${QT_RESOURCES}")
@@ -113,7 +113,7 @@ set(INTERFACE_SRCS ${INTERFACE_SRCS} "${QT_UI_HEADERS}" "${QT_RESOURCES}")
# translation disabled until we strip out the line numbers
# set(QM ${TARGET_NAME}_en.qm)
# set(TS ${TARGET_NAME}_en.ts)
-# qt5_create_translation_custom(${QM} ${INTERFACE_SRCS} ${QT_UI_FILES} ${TS})
+# qt6_create_translation_custom(${QM} ${INTERFACE_SRCS} ${QT_UI_FILES} ${TS})
# setup the android parameters that will help us produce an APK
if (ANDROID)
@@ -295,11 +295,13 @@ if (ANDROID)
target_link_libraries(${TARGET_NAME} ${ANDROID_LOG_LIB})
endif ()
+find_package(Qt6GuiPrivate)
+
target_link_libraries(
${TARGET_NAME}
- Qt5::Gui Qt5::Network Qt5::Multimedia Qt5::Widgets
- Qt5::Qml Qt5::Quick Qt5::Svg
- Qt5::WebChannel Qt5::WebEngineCore Qt5::WebEngineWidgets
+ Qt6::Gui Qt6::GuiPrivate Qt6::Network Qt6::Multimedia Qt6::Widgets
+ Qt6::Qml Qt6::Quick Qt6::Svg
+ Qt6::WebChannel Qt6::WebEngineCore Qt6::WebEngineWidgets
${PLATFORM_QT_LIBRARIES}
)
diff --git a/interface/resources/QtWebEngine/UIDelegates/ConfirmDialog.qml b/interface/resources/QtWebEngine/UIDelegates/ConfirmDialog.qml
index 97d83f43049..6fd28a47032 100644
--- a/interface/resources/QtWebEngine/UIDelegates/ConfirmDialog.qml
+++ b/interface/resources/QtWebEngine/UIDelegates/ConfirmDialog.qml
@@ -1,6 +1,6 @@
import QtQuick 2.4
-import QtQuick.Dialogs 1.1 as OriginalDialogs
+import QtQuick.Dialogs as OriginalDialogs
import "../../qml/dialogs"
diff --git a/interface/resources/fonts/Roboto-Bold.ttf b/interface/resources/fonts/Roboto-Bold.ttf
new file mode 100644
index 00000000000..4658f9a67b1
Binary files /dev/null and b/interface/resources/fonts/Roboto-Bold.ttf differ
diff --git a/interface/resources/fonts/Roboto-BoldItalic.ttf b/interface/resources/fonts/Roboto-BoldItalic.ttf
new file mode 100644
index 00000000000..2ee0765ce17
Binary files /dev/null and b/interface/resources/fonts/Roboto-BoldItalic.ttf differ
diff --git a/interface/resources/fonts/Roboto-Italic.ttf b/interface/resources/fonts/Roboto-Italic.ttf
new file mode 100644
index 00000000000..c3abaefb284
Binary files /dev/null and b/interface/resources/fonts/Roboto-Italic.ttf differ
diff --git a/interface/resources/fonts/Roboto-Regular.ttf b/interface/resources/fonts/Roboto-Regular.ttf
new file mode 100644
index 00000000000..7e3bb2f8ce7
Binary files /dev/null and b/interface/resources/fonts/Roboto-Regular.ttf differ
diff --git a/interface/resources/fonts/Roboto.license b/interface/resources/fonts/Roboto.license
new file mode 100644
index 00000000000..a417551aa4a
--- /dev/null
+++ b/interface/resources/fonts/Roboto.license
@@ -0,0 +1,93 @@
+Copyright 2011 The Roboto Project Authors (https://github.com/googlefonts/roboto-classic)
+
+This Font Software is licensed under the SIL Open Font License, Version 1.1.
+This license is copied below, and is also available with a FAQ at:
+https://openfontlicense.org
+
+
+-----------------------------------------------------------
+SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
+-----------------------------------------------------------
+
+PREAMBLE
+The goals of the Open Font License (OFL) are to stimulate worldwide
+development of collaborative font projects, to support the font creation
+efforts of academic and linguistic communities, and to provide a free and
+open framework in which fonts may be shared and improved in partnership
+with others.
+
+The OFL allows the licensed fonts to be used, studied, modified and
+redistributed freely as long as they are not sold by themselves. The
+fonts, including any derivative works, can be bundled, embedded,
+redistributed and/or sold with any software provided that any reserved
+names are not used by derivative works. The fonts and derivatives,
+however, cannot be released under any other type of license. The
+requirement for fonts to remain under this license does not apply
+to any document created using the fonts or their derivatives.
+
+DEFINITIONS
+"Font Software" refers to the set of files released by the Copyright
+Holder(s) under this license and clearly marked as such. This may
+include source files, build scripts and documentation.
+
+"Reserved Font Name" refers to any names specified as such after the
+copyright statement(s).
+
+"Original Version" refers to the collection of Font Software components as
+distributed by the Copyright Holder(s).
+
+"Modified Version" refers to any derivative made by adding to, deleting,
+or substituting -- in part or in whole -- any of the components of the
+Original Version, by changing formats or by porting the Font Software to a
+new environment.
+
+"Author" refers to any designer, engineer, programmer, technical
+writer or other person who contributed to the Font Software.
+
+PERMISSION & CONDITIONS
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of the Font Software, to use, study, copy, merge, embed, modify,
+redistribute, and sell modified and unmodified copies of the Font
+Software, subject to the following conditions:
+
+1) Neither the Font Software nor any of its individual components,
+in Original or Modified Versions, may be sold by itself.
+
+2) Original or Modified Versions of the Font Software may be bundled,
+redistributed and/or sold with any software, provided that each copy
+contains the above copyright notice and this license. These can be
+included either as stand-alone text files, human-readable headers or
+in the appropriate machine-readable metadata fields within text or
+binary files as long as those fields can be easily viewed by the user.
+
+3) No Modified Version of the Font Software may use the Reserved Font
+Name(s) unless explicit written permission is granted by the corresponding
+Copyright Holder. This restriction only applies to the primary font name as
+presented to the users.
+
+4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
+Software shall not be used to promote, endorse or advertise any
+Modified Version, except to acknowledge the contribution(s) of the
+Copyright Holder(s) and the Author(s) or with their explicit written
+permission.
+
+5) The Font Software, modified or unmodified, in part or in whole,
+must be distributed entirely under this license, and must not be
+distributed under any other license. The requirement for fonts to
+remain under this license does not apply to any document created
+using the Font Software.
+
+TERMINATION
+This license becomes null and void if any of the above conditions are
+not met.
+
+DISCLAIMER
+THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
+OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
+COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
+DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
+OTHER DEALINGS IN THE FONT SOFTWARE.
diff --git a/interface/resources/fonts/RobotoMono-Bold.ttf b/interface/resources/fonts/RobotoMono-Bold.ttf
new file mode 100644
index 00000000000..bef439f3b9a
Binary files /dev/null and b/interface/resources/fonts/RobotoMono-Bold.ttf differ
diff --git a/interface/resources/fonts/RobotoMono-BoldItalic.ttf b/interface/resources/fonts/RobotoMono-BoldItalic.ttf
new file mode 100644
index 00000000000..642dd0589f0
Binary files /dev/null and b/interface/resources/fonts/RobotoMono-BoldItalic.ttf differ
diff --git a/interface/resources/fonts/RobotoMono-Italic.ttf b/interface/resources/fonts/RobotoMono-Italic.ttf
new file mode 100644
index 00000000000..781eff8c0a7
Binary files /dev/null and b/interface/resources/fonts/RobotoMono-Italic.ttf differ
diff --git a/interface/resources/fonts/RobotoMono-Regular.ttf b/interface/resources/fonts/RobotoMono-Regular.ttf
new file mode 100644
index 00000000000..3806bfb1101
Binary files /dev/null and b/interface/resources/fonts/RobotoMono-Regular.ttf differ
diff --git a/interface/resources/icons/tablet-icons/chat-a.svg b/interface/resources/icons/tablet-icons/chat-a.svg
new file mode 100644
index 00000000000..e3793aaf022
--- /dev/null
+++ b/interface/resources/icons/tablet-icons/chat-a.svg
@@ -0,0 +1,2 @@
+
+
diff --git a/interface/resources/icons/tablet-icons/chat-i.svg b/interface/resources/icons/tablet-icons/chat-i.svg
new file mode 100644
index 00000000000..c5d26819952
--- /dev/null
+++ b/interface/resources/icons/tablet-icons/chat-i.svg
@@ -0,0 +1,2 @@
+
+
diff --git a/interface/resources/qml/+android_interface/Web3DSurfaceAndroid.qml b/interface/resources/qml/+android_interface/Web3DSurfaceAndroid.qml
index 6a9f76bc739..135c966c63f 100644
--- a/interface/resources/qml/+android_interface/Web3DSurfaceAndroid.qml
+++ b/interface/resources/qml/+android_interface/Web3DSurfaceAndroid.qml
@@ -9,7 +9,7 @@
//
import QtQuick 2.5
-import QtGraphicalEffects 1.0
+import Qt5Compat.GraphicalEffects
Item {
diff --git a/interface/resources/qml/+webengine/BrowserWebView.qml b/interface/resources/qml/+webengine/BrowserWebView.qml
index 291859c3ec6..7ce14553969 100644
--- a/interface/resources/qml/+webengine/BrowserWebView.qml
+++ b/interface/resources/qml/+webengine/BrowserWebView.qml
@@ -1,8 +1,8 @@
-import QtQuick 2.5
-import QtWebChannel 1.0
-import QtWebEngine 1.5
+import QtQuick
+import QtWebChannel
+import QtWebEngine
-import controlsUit 1.0
+import controlsUit
WebView {
id: webview
@@ -10,23 +10,20 @@ WebView {
profile: FileTypeProfile;
property var parentRoot: null
- // Create a global EventBridge object for raiseAndLowerKeyboard.
- WebEngineScript {
- id: createGlobalEventBridge
- sourceCode: eventBridgeJavaScriptToInject
- injectionPoint: WebEngineScript.Deferred
- worldId: WebEngineScript.MainWorld
- }
-
- // Detect when may want to raise and lower keyboard.
- WebEngineScript {
- id: raiseAndLowerKeyboard
- injectionPoint: WebEngineScript.Deferred
- sourceUrl: resourceDirectoryUrl + "/html/raiseAndLowerKeyboard.js"
- worldId: WebEngineScript.MainWorld
- }
-
- userScripts: [ createGlobalEventBridge, raiseAndLowerKeyboard ]
+ userScripts.collection: [
+ // Create a global EventBridge object for raiseAndLowerKeyboard.
+ {
+ sourceCode: eventBridgeJavaScriptToInject,
+ injectionPoint: WebEngineScript.Deferred,
+ worldId: WebEngineScript.MainWorld,
+ },
+ // Detect when may want to raise and lower keyboard.
+ {
+ injectionPoint: WebEngineScript.Deferred,
+ sourceUrl: resourceDirectoryUrl + "/html/raiseAndLowerKeyboard.js",
+ worldId: WebEngineScript.MainWorld,
+ }
+ ]
onLoadingChanged: {
if (loadRequest.status === WebEngineView.LoadSucceededStatus) {
diff --git a/interface/resources/qml/+webengine/QmlWebWindowView.qml b/interface/resources/qml/+webengine/QmlWebWindowView.qml
index 84ab61ad28b..441267975a3 100644
--- a/interface/resources/qml/+webengine/QmlWebWindowView.qml
+++ b/interface/resources/qml/+webengine/QmlWebWindowView.qml
@@ -1,9 +1,9 @@
-import QtQuick 2.5
-import QtWebEngine 1.1
-import QtWebChannel 1.0
+import QtQuick
+import QtWebEngine
+import QtWebChannel
-import controlsUit 1.0 as Controls
-import stylesUit 1.0
+import controlsUit as Controls
+import stylesUit
Controls.WebView {
id: webview
url: "about:blank"
@@ -14,30 +14,28 @@ Controls.WebView {
property string userScriptUrl: ""
// Create a global EventBridge object for raiseAndLowerKeyboard.
- WebEngineScript {
- id: createGlobalEventBridge
- sourceCode: eventBridgeJavaScriptToInject
- injectionPoint: WebEngineScript.DocumentCreation
- worldId: WebEngineScript.MainWorld
- }
-
- // Detect when may want to raise and lower keyboard.
- WebEngineScript {
- id: raiseAndLowerKeyboard
- injectionPoint: WebEngineScript.Deferred
- sourceUrl: resourceDirectoryUrl + "/html/raiseAndLowerKeyboard.js"
- worldId: WebEngineScript.MainWorld
- }
-
- // User script.
- WebEngineScript {
- id: userScript
- sourceUrl: webview.userScriptUrl
- injectionPoint: WebEngineScript.DocumentReady // DOM ready but page load may not be finished.
- worldId: WebEngineScript.MainWorld
- }
- userScripts: [ createGlobalEventBridge, raiseAndLowerKeyboard, userScript ]
+ userScripts.collection: [ {
+ name: "createGlobalEventBridge",
+ id: createGlobalEventBridge,
+ sourceCode: eventBridgeJavaScriptToInject,
+ injectionPoint: WebEngineScript.DocumentCreation,
+ worldId: WebEngineScript.MainWorld
+ },
+ {
+ name: "raiseAndLowerKeyboardScript",
+ id: raiseAndLowerKeyboard,
+ injectionPoint: WebEngineScript.Deferred,
+ sourceUrl: resourceDirectoryUrl + "/html/raiseAndLowerKeyboard.js",
+ worldId: WebEngineScript.MainWorld
+ },
+ {
+ name: "userWebEngineScript",
+ id: userScript,
+ sourceUrl: webview.userScriptUrl,
+ injectionPoint: WebEngineScript.DocumentReady, // DOM ready but page load may not be finished.
+ worldId: WebEngineScript.MainWorld
+ } ]
function onWebEventReceived(event) {
if (typeof event === "string" && event.slice(0, 17) === "CLARA.IO DOWNLOAD") {
diff --git a/interface/resources/qml/+webengine/TabletBrowser.qml b/interface/resources/qml/+webengine/TabletBrowser.qml
index 49b87e51dd7..367b0872c56 100644
--- a/interface/resources/qml/+webengine/TabletBrowser.qml
+++ b/interface/resources/qml/+webengine/TabletBrowser.qml
@@ -1,11 +1,11 @@
-import QtQuick 2.5
-import QtWebChannel 1.0
-import QtWebEngine 1.5
+import QtQuick
+import QtWebChannel
+import QtWebEngine
import "controls"
-import controlsUit 1.0 as HifiControls
+import controlsUit as HifiControls
import "styles" as HifiStyles
-import stylesUit 1.0
+import stylesUit
import "windows"
Item {
diff --git a/interface/resources/qml/AudioScopeUI.qml b/interface/resources/qml/AudioScopeUI.qml
index 75a71a32c5f..01f2e023baa 100644
--- a/interface/resources/qml/AudioScopeUI.qml
+++ b/interface/resources/qml/AudioScopeUI.qml
@@ -9,7 +9,7 @@
//
import QtQuick 2.5
-import QtQuick.Controls 1.4
+import QtQuick.Controls 2.3
import stylesUit 1.0
import controlsUit 1.0 as HifiControlsUit
diff --git a/interface/resources/qml/AvatarInputsBar.qml b/interface/resources/qml/AvatarInputsBar.qml
index 9230111cb64..3997d28340d 100644
--- a/interface/resources/qml/AvatarInputsBar.qml
+++ b/interface/resources/qml/AvatarInputsBar.qml
@@ -8,7 +8,7 @@
import Hifi 1.0 as Hifi
import QtQuick 2.5
-import QtGraphicalEffects 1.0
+import Qt5Compat.GraphicalEffects
import "./hifi/audio" as HifiAudio
diff --git a/interface/resources/qml/Browser.qml b/interface/resources/qml/Browser.qml
index 2fefb6bff47..8727d558f9e 100644
--- a/interface/resources/qml/Browser.qml
+++ b/interface/resources/qml/Browser.qml
@@ -163,7 +163,7 @@ ScrollingWindow {
anchors.leftMargin: 0
anchors.verticalCenter: parent.verticalCenter
focus: true
- colorScheme: hifi.colorSchemes.dark
+ //colorScheme: hifi.colorSchemes.dark
placeholderText: "Enter URL"
inputMethodHints: Qt.ImhUrlCharactersOnly
Component.onCompleted: ScriptDiscoveryService.scriptsModelFilter.filterRegExp = new RegExp("^.*$", "i")
@@ -229,8 +229,8 @@ ScrollingWindow {
Button {
id:allow
text: "Allow"
- color: hifi.buttons.blue
- colorScheme: root.colorScheme
+ //color: hifi.buttons.blue
+ //colorScheme: root.colorScheme
width: 120
enabled: true
onClicked: root.allowPermissions();
@@ -240,8 +240,8 @@ ScrollingWindow {
Button {
id:block
text: "Block"
- color: hifi.buttons.red
- colorScheme: root.colorScheme
+ //color: hifi.buttons.red
+ //colorScheme: root.colorScheme
width: 120
enabled: true
onClicked: root.hidePermissionsBar();
diff --git a/interface/resources/qml/BubbleIcon.qml b/interface/resources/qml/BubbleIcon.qml
index b5a7ddb2d86..0ec3a927357 100644
--- a/interface/resources/qml/BubbleIcon.qml
+++ b/interface/resources/qml/BubbleIcon.qml
@@ -8,7 +8,7 @@
import Hifi 1.0 as Hifi
import QtQuick 2.5
-import QtGraphicalEffects 1.0
+import Qt5Compat.GraphicalEffects
import "./hifi/audio" as HifiAudio
diff --git a/interface/resources/qml/ConnectionFailureDialog.qml b/interface/resources/qml/ConnectionFailureDialog.qml
index cb6c9db4576..a3186fa642b 100644
--- a/interface/resources/qml/ConnectionFailureDialog.qml
+++ b/interface/resources/qml/ConnectionFailureDialog.qml
@@ -1,4 +1,4 @@
-import QtQuick.Dialogs 1.2 as OriginalDialogs
+import QtQuick.Dialogs as OriginalDialogs
import "dialogs"
diff --git a/interface/resources/qml/CurrentAPI.qml b/interface/resources/qml/CurrentAPI.qml
index 4ea45041c3d..7a7cfa382f0 100644
--- a/interface/resources/qml/CurrentAPI.qml
+++ b/interface/resources/qml/CurrentAPI.qml
@@ -9,7 +9,7 @@
//
import QtQuick 2.5
-import QtQuick.Controls 1.4
+import QtQuick.Controls 2.3
import stylesUit 1.0
import controlsUit 1.0 as HifiControls
diff --git a/interface/resources/qml/InteractiveWindow.qml b/interface/resources/qml/InteractiveWindow.qml
index a8b27762d6c..740717ca973 100644
--- a/interface/resources/qml/InteractiveWindow.qml
+++ b/interface/resources/qml/InteractiveWindow.qml
@@ -76,10 +76,11 @@ Windows.Window {
}
function updateInteractiveWindowPositionForMode() {
- if (presentationMode === Desktop.PresentationMode.VIRTUAL) {
+ // QT6TODO: Desktop isn't being passed to QML?
+ if (presentationMode === /*Desktop.PresentationMode.VIRTUAL*/ 0) {
x = interactiveWindowPosition.x;
y = interactiveWindowPosition.y;
- } else if (presentationMode === Desktop.PresentationMode.NATIVE && nativeWindow) {
+ } else if (presentationMode === /*Desktop.PresentationMode.NATIVE*/ 1 && nativeWindow) {
if (interactiveWindowPosition.x === 0 && interactiveWindowPosition.y === 0) {
// default position for native window in center of main application window
nativeWindow.x = Math.floor(Window.x + (Window.innerWidth / 2) - (interactiveWindowSize.width / 2));
@@ -97,28 +98,31 @@ Windows.Window {
contentHolder.width = interactiveWindowSize.width;
contentHolder.height = interactiveWindowSize.height;
- if (presentationMode === Desktop.PresentationMode.NATIVE && nativeWindow) {
+ // QT6TODO: Desktop isn't being passed to QML?
+ if (presentationMode === /*Desktop.PresentationMode.NATIVE*/ 1 && nativeWindow) {
nativeWindow.width = interactiveWindowSize.width;
nativeWindow.height = interactiveWindowSize.height;
}
}
function updateContentParent() {
- if (presentationMode === Desktop.PresentationMode.VIRTUAL) {
+ // QT6TODO: Desktop isn't being passed to QML?
+ if (presentationMode === /*Desktop.PresentationMode.VIRTUAL*/ 0) {
contentHolder.parent = root;
- } else if (presentationMode === Desktop.PresentationMode.NATIVE && nativeWindow) {
+ } else if (presentationMode === /*Desktop.PresentationMode.NATIVE*/ 1 && nativeWindow) {
contentHolder.parent = nativeWindow.contentItem;
}
}
function setupPresentationMode() {
- if (presentationMode === Desktop.PresentationMode.VIRTUAL) {
+ // QT6TODO: Desktop isn't being passed to QML?
+ if (presentationMode === /*Desktop.PresentationMode.VIRTUAL*/ 0) {
if (nativeWindow) {
nativeWindow.setVisible(false);
}
updateInteractiveWindowPositionForMode();
shown = interactiveWindowVisible;
- } else if (presentationMode === Desktop.PresentationMode.NATIVE) {
+ } else if (presentationMode === /*Desktop.PresentationMode.NATIVE*/ 1) {
shown = false;
if (nativeWindow) {
updateInteractiveWindowPositionForMode();
@@ -175,25 +179,29 @@ Windows.Window {
nativeWindow.height = interactiveWindowSize.height;
nativeWindow.xChanged.connect(function() {
- if (presentationMode === Desktop.PresentationMode.NATIVE && nativeWindow.visible) {
+ // QT6TODO: Desktop isn't being passed to QML?
+ if (presentationMode === /*Desktop.PresentationMode.NATIVE*/ 1 && nativeWindow.visible) {
interactiveWindowPosition = Qt.point(nativeWindow.x, interactiveWindowPosition.y);
}
});
nativeWindow.yChanged.connect(function() {
- if (presentationMode === Desktop.PresentationMode.NATIVE && nativeWindow.visible) {
+ // QT6TODO: Desktop isn't being passed to QML?
+ if (presentationMode === /*Desktop.PresentationMode.NATIVE*/ 1 && nativeWindow.visible) {
interactiveWindowPosition = Qt.point(interactiveWindowPosition.x, nativeWindow.y);
}
});
nativeWindow.widthChanged.connect(function() {
- if (presentationMode === Desktop.PresentationMode.NATIVE && nativeWindow.visible) {
+ // QT6TODO: Desktop isn't being passed to QML?
+ if (presentationMode === /*Desktop.PresentationMode.NATIVE*/ 1 && nativeWindow.visible) {
interactiveWindowSize = Qt.size(nativeWindow.width, interactiveWindowSize.height);
}
});
nativeWindow.heightChanged.connect(function() {
- if (presentationMode === Desktop.PresentationMode.NATIVE && nativeWindow.visible) {
+ // QT6TODO: Desktop isn't being passed to QML?
+ if (presentationMode === /*Desktop.PresentationMode.NATIVE*/ 1 && nativeWindow.visible) {
interactiveWindowSize = Qt.size(interactiveWindowSize.width, nativeWindow.height);
}
});
@@ -223,9 +231,10 @@ Windows.Window {
}
function raiseWindow() {
- if (presentationMode === Desktop.PresentationMode.VIRTUAL) {
+ // QT6TODO: Desktop isn't being passed to QML?
+ if (presentationMode === /*Desktop.PresentationMode.VIRTUAL*/ 0) {
raise();
- } else if (presentationMode === Desktop.PresentationMode.NATIVE && nativeWindow) {
+ } else if (presentationMode === /*Desktop.PresentationMode.NATIVE*/ 1 && nativeWindow) {
nativeWindow.raise();
}
}
@@ -271,9 +280,10 @@ Windows.Window {
}
onInteractiveWindowVisibleChanged: {
- if (presentationMode === Desktop.PresentationMode.VIRTUAL) {
+ // QT6TODO: Desktop isn't being passed to QML?
+ if (presentationMode === /*Desktop.PresentationMode.VIRTUAL*/ 0) {
shown = interactiveWindowVisible;
- } else if (presentationMode === Desktop.PresentationMode.NATIVE && nativeWindow) {
+ } else if (presentationMode === /*Desktop.PresentationMode.NATIVE*/ 1 && nativeWindow) {
if (!nativeWindow.visible && interactiveWindowVisible) {
nativeWindow.showNormal();
} else {
@@ -289,25 +299,29 @@ Windows.Window {
}
onXChanged: {
- if (presentationMode === Desktop.PresentationMode.VIRTUAL) {
+ // QT6TODO: Desktop isn't being passed to QML?
+ if (presentationMode === /*Desktop.PresentationMode.VIRTUAL*/ 0) {
interactiveWindowPosition = Qt.point(x, interactiveWindowPosition.y);
}
}
onYChanged: {
- if (presentationMode === Desktop.PresentationMode.VIRTUAL) {
+ // QT6TODO: Desktop isn't being passed to QML?
+ if (presentationMode === /*Desktop.PresentationMode.VIRTUAL*/ 0) {
interactiveWindowPosition = Qt.point(interactiveWindowPosition.x, y);
}
}
onWidthChanged: {
- if (presentationMode === Desktop.PresentationMode.VIRTUAL) {
+ // QT6TODO: Desktop isn't being passed to QML?
+ if (presentationMode === /*Desktop.PresentationMode.VIRTUAL*/ 0) {
interactiveWindowSize = Qt.size(width, interactiveWindowSize.height);
}
}
onHeightChanged: {
- if (presentationMode === Desktop.PresentationMode.VIRTUAL) {
+ // QT6TODO: Desktop isn't being passed to QML?
+ if (presentationMode === /*Desktop.PresentationMode.VIRTUAL*/ 0) {
interactiveWindowSize = Qt.size(interactiveWindowSize.width, height);
}
}
diff --git a/interface/resources/qml/LoginDialog/+android_interface/LinkAccountBody.qml b/interface/resources/qml/LoginDialog/+android_interface/LinkAccountBody.qml
index 8570273bde7..39852afeccc 100644
--- a/interface/resources/qml/LoginDialog/+android_interface/LinkAccountBody.qml
+++ b/interface/resources/qml/LoginDialog/+android_interface/LinkAccountBody.qml
@@ -10,8 +10,8 @@
import Hifi 1.0
import QtQuick 2.4
-import QtQuick.Controls 1.4
-import QtQuick.Controls.Styles 1.4 as OriginalStyles
+import QtQuick.Controls 2.3
+////import QtQuick.Controls.Styles as OriginalStyles
import controlsUit 1.0
import stylesUit 1.0
diff --git a/interface/resources/qml/LoginDialog/+android_interface/SignUpBody.qml b/interface/resources/qml/LoginDialog/+android_interface/SignUpBody.qml
index f2329c065c5..9b1bc80a5fd 100644
--- a/interface/resources/qml/LoginDialog/+android_interface/SignUpBody.qml
+++ b/interface/resources/qml/LoginDialog/+android_interface/SignUpBody.qml
@@ -10,8 +10,8 @@
import Hifi 1.0
import QtQuick 2.4
-import QtQuick.Controls 1.4
-import QtQuick.Controls.Styles 1.4 as OriginalStyles
+import QtQuick.Controls 2.3
+////import QtQuick.Controls.Styles as OriginalStyles
import controlsUit 1.0
import stylesUit 1.0
diff --git a/interface/resources/qml/LoginDialog/CompleteProfileBody.qml b/interface/resources/qml/LoginDialog/CompleteProfileBody.qml
index b8744251323..0ae8eda186d 100644
--- a/interface/resources/qml/LoginDialog/CompleteProfileBody.qml
+++ b/interface/resources/qml/LoginDialog/CompleteProfileBody.qml
@@ -10,7 +10,7 @@
import Hifi 1.0
import QtQuick 2.4
-import QtQuick.Controls.Styles 1.4 as OriginalStyles
+////import QtQuick.Controls.Styles as OriginalStyles
import controlsUit 1.0 as HifiControlsUit
import stylesUit 1.0 as HifiStylesUit
diff --git a/interface/resources/qml/LoginDialog/LinkAccountBody.qml b/interface/resources/qml/LoginDialog/LinkAccountBody.qml
index 25aa7504ee3..550210451cc 100644
--- a/interface/resources/qml/LoginDialog/LinkAccountBody.qml
+++ b/interface/resources/qml/LoginDialog/LinkAccountBody.qml
@@ -11,8 +11,8 @@
import Hifi 1.0
import QtQuick 2.7
-import QtQuick.Controls 1.4
-import QtQuick.Controls.Styles 1.4 as OriginalStyles
+import QtQuick.Controls 2.3
+////import QtQuick.Controls.Styles as OriginalStyles
import "." as LoginDialog
diff --git a/interface/resources/qml/LoginDialog/LoggingInBody.qml b/interface/resources/qml/LoginDialog/LoggingInBody.qml
index 1c8250cfa8e..d8cfd618d66 100644
--- a/interface/resources/qml/LoginDialog/LoggingInBody.qml
+++ b/interface/resources/qml/LoginDialog/LoggingInBody.qml
@@ -12,8 +12,8 @@
import Hifi 1.0
import QtQuick 2.7
-import QtQuick.Controls 1.4
-import QtQuick.Controls.Styles 1.4 as OriginalStyles
+import QtQuick.Controls 2.3
+////import QtQuick.Controls.Styles as OriginalStyles
import controlsUit 1.0 as HifiControlsUit
import stylesUit 1.0 as HifiStylesUit
diff --git a/interface/resources/qml/LoginDialog/LoginDialogLightbox.qml b/interface/resources/qml/LoginDialog/LoginDialogLightbox.qml
index 8995ecd2613..e49cc8a27f4 100644
--- a/interface/resources/qml/LoginDialog/LoginDialogLightbox.qml
+++ b/interface/resources/qml/LoginDialog/LoginDialogLightbox.qml
@@ -13,7 +13,7 @@
import Hifi 1.0 as Hifi
import QtQuick 2.5
-import QtGraphicalEffects 1.0
+import Qt5Compat.GraphicalEffects
import stylesUit 1.0
import controlsUit 1.0 as HifiControlsUit
import "qrc:////qml//controls" as HifiControls
diff --git a/interface/resources/qml/LoginDialog/SignUpBody.qml b/interface/resources/qml/LoginDialog/SignUpBody.qml
index b1335232110..b3ca2451a01 100644
--- a/interface/resources/qml/LoginDialog/SignUpBody.qml
+++ b/interface/resources/qml/LoginDialog/SignUpBody.qml
@@ -11,8 +11,8 @@
import Hifi 1.0
import QtQuick 2.7
-import QtQuick.Controls 1.4
-import QtQuick.Controls.Styles 1.4 as OriginalStyles
+import QtQuick.Controls 2.3
+////import QtQuick.Controls.Styles as OriginalStyles
import controlsUit 1.0 as HifiControlsUit
import stylesUit 1.0 as HifiStylesUit
diff --git a/interface/resources/qml/LoginDialog/UsernameCollisionBody.qml b/interface/resources/qml/LoginDialog/UsernameCollisionBody.qml
index 86f60bc067d..5911c3ff860 100644
--- a/interface/resources/qml/LoginDialog/UsernameCollisionBody.qml
+++ b/interface/resources/qml/LoginDialog/UsernameCollisionBody.qml
@@ -10,7 +10,7 @@
import Hifi 1.0
import QtQuick 2.4
-import QtQuick.Controls 1.4
+import QtQuick.Controls 2.3
import controlsUit 1.0 as HifiControlsUit
import stylesUit 1.0 as HifiStylesUit
diff --git a/interface/resources/qml/OverlayWindowTest.qml b/interface/resources/qml/OverlayWindowTest.qml
index 9c1b993ba84..16692e4083c 100644
--- a/interface/resources/qml/OverlayWindowTest.qml
+++ b/interface/resources/qml/OverlayWindowTest.qml
@@ -1,5 +1,5 @@
import QtQuick 2.5
-import QtQuick.Controls 1.4
+import QtQuick.Controls 2.3
Rectangle {
width: 100
diff --git a/interface/resources/qml/UpdateDialog.qml b/interface/resources/qml/UpdateDialog.qml
index 4aaefb5cce9..27f6202665c 100644
--- a/interface/resources/qml/UpdateDialog.qml
+++ b/interface/resources/qml/UpdateDialog.qml
@@ -1,8 +1,8 @@
import Hifi 1.0
import QtQuick 2.3
-import QtQuick.Controls 1.3
-import QtQuick.Controls.Styles 1.3
-import QtGraphicalEffects 1.0
+import QtQuick.Controls
+//import QtQuick.Controls.Styles
+import Qt5Compat.GraphicalEffects
import controlsUit 1.0
import "styles" as HifiStyles
diff --git a/interface/resources/qml/Web3DSurface.qml b/interface/resources/qml/Web3DSurface.qml
index 3380eabeac1..fb845debfda 100644
--- a/interface/resources/qml/Web3DSurface.qml
+++ b/interface/resources/qml/Web3DSurface.qml
@@ -17,7 +17,7 @@ Item {
id: root
anchors.fill: parent
property string url: ""
- property string scriptUrl: null
+ property string scriptUrl: ""
property bool useBackground: true
property string userAgent: ""
@@ -83,7 +83,7 @@ Item {
if (root.webViewLoaded) {
loader.item.url = "about:blank"
}
- loader.setSource(undefined);
+ loader.setSource("");
}
if (url.match(/\.qml$/)) {
@@ -93,7 +93,7 @@ Item {
root.webViewLoaded = true;
loader.setSource("./controls/WebView.qml", {
url: url,
- scriptUrl: scriptUrl,
+ //scriptUrl: scriptUrl, // Qt6 TODO
useBackground: useBackground,
userAgent: userAgent
});
diff --git a/interface/resources/qml/controls/+webengine/FlickableWebViewCore.qml b/interface/resources/qml/controls/+webengine/FlickableWebViewCore.qml
index 315b6023d32..5db8d417054 100644
--- a/interface/resources/qml/controls/+webengine/FlickableWebViewCore.qml
+++ b/interface/resources/qml/controls/+webengine/FlickableWebViewCore.qml
@@ -1,11 +1,11 @@
-import QtQuick 2.7
-import QtWebEngine 1.5
-import QtWebChannel 1.0
+import QtQuick
+import QtWebEngine
+import QtWebChannel
-import QtQuick.Controls 2.2
+import QtQuick.Controls
-import stylesUit 1.0 as StylesUIt
-import controlsUit 1.0 as ControlsUit
+import stylesUit as StylesUIt
+import controlsUit as ControlsUit
Item {
id: flick
@@ -28,7 +28,7 @@ Item {
property string urlTag: "noDownload=false";
signal newViewRequestedCallback(var request)
- signal loadingChangedCallback(var loadRequest)
+ signal loadingChangedCallback(var loadingInfo)
width: parent.width
@@ -116,33 +116,56 @@ Item {
settings.touchIconsEnabled: true
settings.allowRunningInsecureContent: true
- // creates a global EventBridge object.
- WebEngineScript {
- id: createGlobalEventBridge
- sourceCode: eventBridgeJavaScriptToInject
- injectionPoint: WebEngineScript.DocumentCreation
- worldId: WebEngineScript.MainWorld
- }
-
- // detects when to raise and lower virtual keyboard
- WebEngineScript {
- id: raiseAndLowerKeyboard
- injectionPoint: WebEngineScript.Deferred
- sourceUrl: resourceDirectoryUrl + "/html/raiseAndLowerKeyboard.js"
- worldId: WebEngineScript.MainWorld
- }
-
- // User script.
- WebEngineScript {
- id: userScript
- sourceUrl: flick.userScriptUrl
- injectionPoint: WebEngineScript.DocumentReady // DOM ready but page load may not be finished.
- worldId: WebEngineScript.MainWorld
- }
-
- userScripts: [ createGlobalEventBridge, raiseAndLowerKeyboard, userScript ]
+ // QT6TODO causes a crash
+ // Even empty collection causes a crash
+ // userScripts.collection: []
+ //userScripts.collection: [
+ // creates a global EventBridge object.
+ /*{
+ sourceCode: eventBridgeJavaScriptToInject,
+ injectionPoint: WebEngineScript.DocumentCreation,
+ worldId: WebEngineScript.MainWorld,
+ },
+
+ // detects when to raise and lower virtual keyboard
+ {
+ injectionPoint: WebEngineScript.Deferred,
+ sourceUrl: resourceDirectoryUrl + "/html/raiseAndLowerKeyboard.js",
+ worldId: WebEngineScript.MainWorld,
+ },
+
+ // User script.
+ {
+ sourceUrl: flick.userScriptUrl,
+ injectionPoint: WebEngineScript.DocumentReady, // DOM ready but page load may not be finished.
+ worldId: WebEngineScript.MainWorld,
+ },*/
+ //]
Component.onCompleted: {
+ // QT6TODO: previous way of doing it crashes and this one doesn't? unless it crashes later
+ userScripts.collection = [
+ // creates a global EventBridge object.
+ {
+ sourceCode: eventBridgeJavaScriptToInject,
+ injectionPoint: WebEngineScript.DocumentCreation,
+ worldId: WebEngineScript.MainWorld,
+ },
+
+ // detects when to raise and lower virtual keyboard
+ {
+ injectionPoint: WebEngineScript.Deferred,
+ sourceUrl: resourceDirectoryUrl + "/html/raiseAndLowerKeyboard.js",
+ worldId: WebEngineScript.MainWorld,
+ },
+
+ // User script.
+ {
+ sourceUrl: flick.userScriptUrl,
+ injectionPoint: WebEngineScript.DocumentReady, // DOM ready but page load may not be finished.
+ worldId: WebEngineScript.MainWorld,
+ },
+ ]
webChannel.registerObject("eventBridge", eventBridge);
webChannel.registerObject("eventBridgeWrapper", eventBridgeWrapper);
@@ -176,9 +199,10 @@ Item {
request.accepted = true;
}
- onNewViewRequested: {
+ // Qt6 TODO
+ /*onNewViewRequested: {
newViewRequestedCallback(request)
- }
+ }*/
// Prior to 5.10, the WebEngineView loading property is true during initial page loading and then stays false
// as in-page javascript adds more html content. However, in 5.10 there is a bug such that adding html turns
@@ -186,15 +210,18 @@ Item {
// when QT fixes this.
property bool safeLoading: false
property bool loadingLatched: false
- property var loadingRequest: null
- onLoadingChanged: {
- webViewCore.loadingRequest = loadRequest;
+ property var loadInfo: null
+ // QT6TODO: useBackground was missing here in Qt5, but I cannot find it anywhere in Qt documentation, so I just defined it here.
+ property bool useBackground: true
+
+ function onLoadingChanged(loadingInfo) {
+ webViewCore.loadInfo = loadingInfo;
webViewCore.safeLoading = webViewCore.loading && !loadingLatched;
webViewCore.loadingLatched |= webViewCore.loading;
}
onSafeLoadingChanged: {
- flick.onLoadingChanged(webViewCore.loadingRequest)
- loadingChangedCallback(webViewCore.loadingRequest)
+ flick.onLoadingChanged(webViewCore.loadInfo)
+ loadingChangedCallback(webViewCore.loadInfo)
}
}
diff --git a/interface/resources/qml/controls/ButtonAwesome.qml b/interface/resources/qml/controls/ButtonAwesome.qml
index 12f2a6b40d4..e772ce544d9 100644
--- a/interface/resources/qml/controls/ButtonAwesome.qml
+++ b/interface/resources/qml/controls/ButtonAwesome.qml
@@ -1,6 +1,6 @@
import QtQuick 2.3
-import QtQuick.Controls 1.3 as Original
-import QtQuick.Controls.Styles 1.3 as OriginalStyles
+import QtQuick.Controls as Original
+////import QtQuick.Controls.Styles as OriginalStyles
import "."
import "../styles"
@@ -11,7 +11,8 @@ Original.Button {
property real size: 32
SystemPalette { id: palette; colorGroup: SystemPalette.Active }
SystemPalette { id: disabledPalette; colorGroup: SystemPalette.Disabled }
- style: OriginalStyles.ButtonStyle {
+ // QT6TODO
+ /*style: OriginalStyles.ButtonStyle {
label: FontAwesome {
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
@@ -48,7 +49,7 @@ Original.Button {
onStopped: if (control.activeFocus) { start(); }
}
}
- }
+ }*/
Keys.onEnterPressed: root.clicked();
Keys.onReturnPressed: root.clicked();
}
diff --git a/interface/resources/qml/controls/CheckBox.qml b/interface/resources/qml/controls/CheckBox.qml
index 91586299a72..f7a6e1c1353 100644
--- a/interface/resources/qml/controls/CheckBox.qml
+++ b/interface/resources/qml/controls/CheckBox.qml
@@ -1,5 +1,5 @@
-import QtQuick.Controls 1.3 as Original
-import QtQuick.Controls.Styles 1.3
+import QtQuick.Controls as Original
+//import QtQuick.Controls.Styles
import "../styles"
import "."
Original.CheckBox {
diff --git a/interface/resources/qml/controls/ComboBox.qml b/interface/resources/qml/controls/ComboBox.qml
index 960ae8127aa..50244af7320 100644
--- a/interface/resources/qml/controls/ComboBox.qml
+++ b/interface/resources/qml/controls/ComboBox.qml
@@ -1,6 +1,6 @@
import QtQuick 2.5
-import QtQuick.Controls 1.4
-import QtQuick.Controls.Styles 1.4
+import QtQuick.Controls 2.3
+//import QtQuick.Controls.Styles
import "." as VrControls
diff --git a/interface/resources/qml/controls/Player.qml b/interface/resources/qml/controls/Player.qml
index 8af0b1527dc..35ce91528a3 100644
--- a/interface/resources/qml/controls/Player.qml
+++ b/interface/resources/qml/controls/Player.qml
@@ -12,7 +12,7 @@
import QtQuick 2.4
import QtQuick.Controls 1.2
import QtQuick.Dialogs 1.2
-import QtQuick.Controls.Styles 1.2
+//import QtQuick.Controls.Styles
import "../styles"
Item {
diff --git a/interface/resources/qml/controls/RadioButton.qml b/interface/resources/qml/controls/RadioButton.qml
index 6bd9c860d2f..dcbae3491bb 100644
--- a/interface/resources/qml/controls/RadioButton.qml
+++ b/interface/resources/qml/controls/RadioButton.qml
@@ -1,5 +1,5 @@
import QtQuick.Controls 1.3 as Original
-import QtQuick.Controls.Styles 1.3
+//import QtQuick.Controls.Styles
import "../styles"
import "."
Original.RadioButton {
diff --git a/interface/resources/qml/controls/Slider.qml b/interface/resources/qml/controls/Slider.qml
index ace20f1e682..3c53bbe62d9 100644
--- a/interface/resources/qml/controls/Slider.qml
+++ b/interface/resources/qml/controls/Slider.qml
@@ -1,5 +1,5 @@
import QtQuick.Controls 1.3 as Original
-import QtQuick.Controls.Styles 1.3
+//import QtQuick.Controls.Styles
import "../styles"
import "."
diff --git a/interface/resources/qml/controls/SpinBox.qml b/interface/resources/qml/controls/SpinBox.qml
index 1acba57409e..c317ca96288 100644
--- a/interface/resources/qml/controls/SpinBox.qml
+++ b/interface/resources/qml/controls/SpinBox.qml
@@ -1,6 +1,6 @@
import QtQuick.Controls 1.3 as Original
-import QtQuick.Controls.Styles 1.3
+//import QtQuick.Controls.Styles
import "../styles"
import "."
diff --git a/interface/resources/qml/controls/TextField.qml b/interface/resources/qml/controls/TextField.qml
index df536a1de53..c4900b069ed 100644
--- a/interface/resources/qml/controls/TextField.qml
+++ b/interface/resources/qml/controls/TextField.qml
@@ -1,7 +1,8 @@
import QtQuick 2.5
-import QtQuick.Controls 1.4
-import QtQuick.Controls.Styles 1.4
+import QtQuick.Controls 2.3
+//import QtQuick.Controls.Styles
TextField {
- style: TextFieldStyle { renderType: Text.QtRendering }
+ //QT6TODO
+ //style: TextFieldStyle { renderType: Text.QtRendering }
}
diff --git a/interface/resources/qml/controls/WrappedMenu.qml b/interface/resources/qml/controls/WrappedMenu.qml
new file mode 100644
index 00000000000..43fc2d438e3
--- /dev/null
+++ b/interface/resources/qml/controls/WrappedMenu.qml
@@ -0,0 +1,13 @@
+import QtQuick.Controls
+
+Menu {
+ id: wrappedMenu
+ objectName: "wrappedMenu"
+
+ function addMenuWrap(menu) {
+ return addMenu(menu);
+ }
+ function addItemWrap(item) {
+ addItem(item);
+ }
+}
\ No newline at end of file
diff --git a/interface/resources/qml/controlsUit/AttachmentsTable.qml b/interface/resources/qml/controlsUit/AttachmentsTable.qml
index a2677962daa..c20bea08180 100644
--- a/interface/resources/qml/controlsUit/AttachmentsTable.qml
+++ b/interface/resources/qml/controlsUit/AttachmentsTable.qml
@@ -9,8 +9,8 @@
//
import QtQuick 2.5
-import QtQuick.Controls 1.4
-import QtQuick.Controls.Styles 1.4
+import QtQuick.Controls 2.3
+//import QtQuick.Controls.Styles
import QtQuick.XmlListModel 2.0
import "../stylesUit"
diff --git a/interface/resources/qml/controlsUit/ContentSection.qml b/interface/resources/qml/controlsUit/ContentSection.qml
index 262c29220fd..a45f45c2956 100644
--- a/interface/resources/qml/controlsUit/ContentSection.qml
+++ b/interface/resources/qml/controlsUit/ContentSection.qml
@@ -9,7 +9,7 @@
//
import QtQuick 2.5
-import QtGraphicalEffects 1.0
+import Qt5Compat.GraphicalEffects
import "../stylesUit"
diff --git a/interface/resources/qml/controlsUit/FilterBar.qml b/interface/resources/qml/controlsUit/FilterBar.qml
index 0892018913c..e57e7dd3b49 100644
--- a/interface/resources/qml/controlsUit/FilterBar.qml
+++ b/interface/resources/qml/controlsUit/FilterBar.qml
@@ -10,7 +10,7 @@
import QtQuick 2.9
import QtQuick.Controls 2.2
-import QtGraphicalEffects 1.0
+import Qt5Compat.GraphicalEffects
import "../stylesUit"
import "." as HifiControls
diff --git a/interface/resources/qml/controlsUit/Keyboard.qml b/interface/resources/qml/controlsUit/Keyboard.qml
index a55523f34ac..f2842f488eb 100644
--- a/interface/resources/qml/controlsUit/Keyboard.qml
+++ b/interface/resources/qml/controlsUit/Keyboard.qml
@@ -9,7 +9,7 @@
//
import QtQuick 2.7
-import QtGraphicalEffects 1.0
+import Qt5Compat.GraphicalEffects
import "."
Rectangle {
diff --git a/interface/resources/qml/controlsUit/Table.qml b/interface/resources/qml/controlsUit/Table.qml
index ab743610460..680a2c42310 100644
--- a/interface/resources/qml/controlsUit/Table.qml
+++ b/interface/resources/qml/controlsUit/Table.qml
@@ -8,10 +8,9 @@
// 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.3 as QQC2
+import QtQuick
+import QtQuick.Controls
+//import QtQuick.Controls.Styles
import "../stylesUit"
@@ -30,12 +29,14 @@ TableView {
model: ListModel { }
Component.onCompleted: {
- if (flickableItem !== null && flickableItem !== undefined) {
+ // QT6TODO: ReferenceError: flickableItem is not defined
+ /*if (flickableItem !== null && flickableItem !== undefined) {
tableView.flickableItem.QQC2.ScrollBar.vertical = scrollbar
- }
+ }*/
}
- QQC2.ScrollBar {
+ // QT6TODO: this errors out with: QML ScrollBar: Cannot anchor to an item that isn't a parent or sibling.
+ ScrollBar {
id: scrollbar
parent: tableView.flickableItem
policy: QQC2.ScrollBar.AsNeeded
@@ -68,8 +69,10 @@ TableView {
}
}
- headerVisible: false
- headerDelegate: Rectangle {
+ // QT6TODO: not available on Qt6
+ //headerVisible: false
+ // QT6TODO: I don't know how to port this
+ /*headerDelegate: Rectangle {
height: hifi.dimensions.tableHeaderHeight
color: isLightColorScheme ? hifi.colors.tableBackgroundLight : hifi.colors.tableBackgroundDark
@@ -130,10 +133,11 @@ TableView {
}
color: isLightColorScheme ? hifi.colors.lightGrayText : hifi.colors.baseGrayHighlight
}
- }
+ }*/
// Use rectangle to draw border with rounded corners.
- frameVisible: false
+ // QT6TODO: does not exist in Qt 6?
+ //frameVisible: false
Rectangle {
color: "#00000000"
anchors { fill: parent; margins: -2 }
@@ -142,19 +146,28 @@ TableView {
}
anchors.margins: 2 // Shrink TableView to lie within border.
- backgroundVisible: true
+ // QT6TODO: this doesn't exist in Qt 6?
+ //backgroundVisible: true
- horizontalScrollBarPolicy: Qt.ScrollBarAlwaysOff
- verticalScrollBarPolicy: Qt.ScrollBarAlwaysOff
+ // QT6TODO: these don't exist in Qt 6?
+ //horizontalScrollBarPolicy: Qt.ScrollBarAlwaysOff
+ //verticalScrollBarPolicy: Qt.ScrollBarAlwaysOff
- style: TableViewStyle {
+ // QT6TODO: how to port this
+ /*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
- }
+ }*/
+
+ // QT6TODO: I'm not sure if this is correct
+ // was originally rowDelegate
+ delegate: Rectangle {
+ // QT6TODO: I added these, but I'm not sure if these are needed
+ required property bool current
+ required property bool selected
- rowDelegate: Rectangle {
height: (styleData.selected && expandSelectedRow ? 1.8 : 1) * hifi.dimensions.tableRowHeight
color: styleData.selected
? hifi.colors.primaryHighlight
diff --git a/interface/resources/qml/controlsUit/TabletContentSection.qml b/interface/resources/qml/controlsUit/TabletContentSection.qml
index dccaf31bbec..c349baa0265 100644
--- a/interface/resources/qml/controlsUit/TabletContentSection.qml
+++ b/interface/resources/qml/controlsUit/TabletContentSection.qml
@@ -9,7 +9,7 @@
//
import QtQuick 2.5
-import QtGraphicalEffects 1.0
+import Qt5Compat.GraphicalEffects
import "../stylesUit"
diff --git a/interface/resources/qml/controlsUit/TextField.qml b/interface/resources/qml/controlsUit/TextField.qml
index 86707395e35..6577bd9c293 100644
--- a/interface/resources/qml/controlsUit/TextField.qml
+++ b/interface/resources/qml/controlsUit/TextField.qml
@@ -9,8 +9,8 @@
//
import QtQuick 2.5
-import QtQuick.Controls 1.4
-import QtQuick.Controls.Styles 1.4
+import QtQuick.Controls 2.3
+//import QtQuick.Controls.Styles
import "../stylesUit"
import "." as HifiControls
@@ -44,7 +44,7 @@ TextField {
y: textFieldLabel.visible ? textFieldLabel.height + textFieldLabel.anchors.bottomMargin : 0
// workaround for https://bugreports.qt.io/browse/QTBUG-49297
- Keys.onPressed: {
+ Keys.onPressed: event => {
switch (event.key) {
case Qt.Key_Return:
case Qt.Key_Enter:
@@ -57,6 +57,8 @@ TextField {
}
}
+ // QT6TODO
+ /*
style: TextFieldStyle {
id: style;
textColor: {
@@ -159,7 +161,7 @@ TextField {
padding.left: hasRoundedBorder ? textField.height / 2 : ((isSearchField || textField.leftPermanentGlyph !== "") ? textField.height - 2 : 0) + hifi.dimensions.textPadding
padding.right: (hasClearButton ? textField.height - 2 : 0) + hifi.dimensions.textPadding
renderType: textField.styleRenderType
- }
+ }*/
HifiControls.Label {
id: textFieldLabel
diff --git a/interface/resources/qml/controlsUit/Tree.qml b/interface/resources/qml/controlsUit/Tree.qml
index f2c49095b12..930e3a09998 100644
--- a/interface/resources/qml/controlsUit/Tree.qml
+++ b/interface/resources/qml/controlsUit/Tree.qml
@@ -8,11 +8,11 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
-import QtQml.Models 2.2
-import QtQuick 2.7
-import QtQuick.Controls 1.4
-import QtQuick.Controls.Styles 1.4
-import QtQuick.Controls 2.2 as QQC2
+import QtQml.Models
+import QtQuick
+import QtQuick.Controls
+//import QtQuick.Controls.Styles
+import QtQuick.Controls as QQC2
import "../stylesUit"
diff --git a/interface/resources/qml/controlsUit/WebView.qml b/interface/resources/qml/controlsUit/WebView.qml
index 2895f36944d..b3bff4e8b36 100644
--- a/interface/resources/qml/controlsUit/WebView.qml
+++ b/interface/resources/qml/controlsUit/WebView.qml
@@ -12,10 +12,11 @@ import QtQuick 2.5
import "."
BaseWebView {
- onNewViewRequested: {
+ // Qt6 TODO
+ /*onNewViewRequested: {
// Load dialog via OffscreenUi so that JavaScript EventBridge is available.
var browser = OffscreenUi.load("Browser.qml");
request.openIn(browser.webView);
browser.webView.forceActiveFocus();
- }
+ }*/
}
diff --git a/interface/resources/qml/desktop/Desktop.qml b/interface/resources/qml/desktop/Desktop.qml
index 971fb064148..0a04f76863b 100644
--- a/interface/resources/qml/desktop/Desktop.qml
+++ b/interface/resources/qml/desktop/Desktop.qml
@@ -8,12 +8,14 @@
// 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 QtQuick.Controls 2.3 as QQC2
+import QtQuick
+import QtQuick.Controls
import "../dialogs"
import "../js/Utils.js" as Utils
+import "../controls" as OverteControls
+
+import "../overte/compat" as OverteCompat
// This is our primary 'desktop' object to which all VR dialogs and windows are childed.
FocusScope {
@@ -51,16 +53,21 @@ FocusScope {
property bool desktopRoot: true
// The VR version of the primary menu
- property var rootMenu: Menu {
+ property var rootMenu: OverteControls.WrappedMenu {
id: rootMenuId
objectName: "rootMenu"
property var exclusionGroups: ({});
property Component exclusiveGroupMaker: Component {
- ExclusiveGroup {
+ //ExclusiveGroup { //QT6TODO
+ ButtonGroup {
}
}
+ function addMenuWrap(menu) {
+ return addMenu(menu);
+ }
+
function addExclusionGroup(qmlAction, exclusionGroup) {
var exclusionGroupId = exclusionGroup.toString();
@@ -68,6 +75,7 @@ FocusScope {
exclusionGroups[exclusionGroupId] = exclusiveGroupMaker.createObject(rootMenuId);
}
+ //QT6TODO:
qmlAction.exclusiveGroup = exclusionGroups[exclusionGroupId]
}
}
@@ -532,12 +540,13 @@ FocusScope {
return customInputDialogBuilder.createObject(desktop, properties);
}
- Component { id: fileDialogBuilder; FileDialog { } }
+ Component { id: fileDialogBuilder; OverteCompat.CompatFileDialog { } }
function fileDialog(properties) {
return fileDialogBuilder.createObject(desktop, properties);
}
- Component { id: assetDialogBuilder; AssetDialog { } }
+ // TODO: is this actually used?
+ Component { id: assetDialogBuilder; Item {}}//AssetDialog { } }
function assetDialog(properties) {
return assetDialogBuilder.createObject(desktop, properties);
}
@@ -582,7 +591,7 @@ FocusScope {
ColorAnimation on color { from: "#7fffff00"; to: "#7f0000ff"; duration: 1000; loops: 9999 }
}
- QQC2.Action {
+ Action {
text: "Toggle Focus Debugger"
shortcut: "Ctrl+Shift+F"
enabled: DebugQML
diff --git a/interface/resources/qml/dialogs/AssetDialog.qml b/interface/resources/qml/dialogs/AssetDialog.qml
index b8eaab0b8df..694c37740cd 100644
--- a/interface/resources/qml/dialogs/AssetDialog.qml
+++ b/interface/resources/qml/dialogs/AssetDialog.qml
@@ -8,8 +8,8 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
+import QtCore
import QtQuick 2.5
-import Qt.labs.settings 1.0
import stylesUit 1.0
import "../windows"
diff --git a/interface/resources/qml/dialogs/CustomQueryDialog.qml b/interface/resources/qml/dialogs/CustomQueryDialog.qml
index 2497781db0d..3839313453f 100644
--- a/interface/resources/qml/dialogs/CustomQueryDialog.qml
+++ b/interface/resources/qml/dialogs/CustomQueryDialog.qml
@@ -9,7 +9,7 @@
//
import QtQuick 2.7;
-import QtQuick.Dialogs 1.2 as OriginalDialogs;
+import QtQuick.Dialogs as OriginalDialogs;
import QtQuick.Controls 2.3
import controlsUit 1.0
diff --git a/interface/resources/qml/dialogs/FileDialog.qml b/interface/resources/qml/dialogs/FileDialog.qml
index fa4d911b653..c575058f39a 100644
--- a/interface/resources/qml/dialogs/FileDialog.qml
+++ b/interface/resources/qml/dialogs/FileDialog.qml
@@ -8,12 +8,12 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
-import QtQuick 2.7
-import Qt.labs.folderlistmodel 2.2
-import Qt.labs.settings 1.0
-import QtQuick.Dialogs 1.2 as OriginalDialogs
-import QtQuick.Controls 1.4 as QQC1
-import QtQuick.Controls 2.3
+import QtCore
+import QtQuick
+import Qt.labs.folderlistmodel
+import Qt.labs.qmlmodels
+import QtQuick.Dialogs as OriginalDialogs
+import QtQuick.Controls
import ".."
import controlsUit 1.0
@@ -34,7 +34,7 @@ ModalWindow {
HifiConstants { id: hifi }
- property var filesModel: ListModel { }
+ property var filesModel: TableModel { }
Settings {
category: "FileDialog"
@@ -76,8 +76,8 @@ ModalWindow {
selected(button);
destroy();
}
-
- property int clickedButton: OriginalDialogs.StandardButton.NoButton;
+
+ property int clickedButton: OriginalDialogs.MessageDialog.NoButton;
Component.onCompleted: {
fileDialogItem.keyboardEnabled = HMD.active;
@@ -261,7 +261,7 @@ ModalWindow {
QtObject {
id: d
property var currentSelectionUrl;
- readonly property string currentSelectionPath: helper.urlToPath(currentSelectionUrl);
+ readonly property string currentSelectionPath: currentSelectionUrl === undefined ? "" : helper.urlToPath(currentSelectionUrl);
property bool currentSelectionIsFolder;
property var backStack: []
property var tableViewConnection: Connections { target: fileTableView; function onCurrentRowChanged() { d.update(); } }
@@ -311,8 +311,9 @@ ModalWindow {
}
function clearSelection() {
- fileTableView.selection.clear();
- fileTableView.currentRow = -1;
+ // QT6TODO
+ //fileTableView.selection.clear();
+ //fileTableView.currentRow = -1;
update();
}
}
@@ -374,7 +375,25 @@ ModalWindow {
Component {
id: filesModelBuilder
- ListModel { }
+ TableModel {
+ TableModelColumn {
+ //id: fileNameColumn
+ display: "fileName"
+ //title: "Name"
+ //width: (selectDirectory ? 1.0 : 0.5) * fileTableView.width
+ }
+ TableModelColumn {
+ //id: fileModifiedColumn
+ display: "fileModified"
+ //title: "Date"
+ //width: 0.3 * fileTableView.width
+ }
+ TableModelColumn {
+ display: "fileSize"
+ //title: "Size"
+ //width: fileTableView.width - fileNameColumn.width - fileModifiedColumn.width
+ }
+ }
}
QtObject {
@@ -429,11 +448,11 @@ ModalWindow {
if (row === -1) {
return false;
}
- return filesModel.get(row).fileIsDir;
+ return filesModel.getRow(row).fileIsDir;
}
function get(row) {
- return filesModel.get(row)
+ return filesModel.getRow(row)
}
function update() {
@@ -473,7 +492,7 @@ ModalWindow {
while (lower < upper) {
middle = Math.floor((lower + upper) / 2);
var lessThan;
- if (comparisonFunction(sortValue, filesModel.get(middle)[sortField])) {
+ if (comparisonFunction(sortValue, filesModel.getRow(middle)[sortField])) {
lessThan = true;
upper = middle;
} else {
@@ -482,7 +501,7 @@ ModalWindow {
}
}
- filesModel.insert(lower, {
+ filesModel.insertRow(lower, {
fileName: fileName,
fileModified: (fileIsDir ? new Date(0) : model.getItem(i, "fileModified")),
fileSize: model.getItem(i, "fileSize"),
@@ -496,9 +515,10 @@ ModalWindow {
}
}
- Table {
+ TableView {
id: fileTableView
- colorScheme: hifi.colorSchemes.light
+ // QT6TODO:
+ //colorScheme: hifi.colorSchemes.light
anchors {
top: navControls.bottom
topMargin: hifi.dimensions.contentSpacing.y
@@ -507,15 +527,17 @@ ModalWindow {
bottom: currentSelection.top
bottomMargin: hifi.dimensions.contentSpacing.y + currentSelection.controlHeight - currentSelection.height
}
- headerVisible: !selectDirectory
- onDoubleClicked: navigateToRow(row);
+ // QT6TODO:
+ //headerVisible: !selectDirectory
+ //onDoubleClicked: navigateToRow(row);
Keys.onReturnPressed: navigateToCurrentRow();
Keys.onEnterPressed: navigateToCurrentRow();
- sortIndicatorColumn: 0
- sortIndicatorOrder: Qt.AscendingOrder
- sortIndicatorVisible: true
+ property int sortIndicatorColumn: 0
+ property var sortIndicatorOrder: Qt.AscendingOrder
+ property bool sortIndicatorVisible: true
+ // QT6TODO
model: filesModel
function updateSort() {
@@ -528,7 +550,20 @@ ModalWindow {
onSortIndicatorOrderChanged: { updateSort(); }
- itemDelegate: Item {
+ delegate: TextInput {
+ text: model.display
+ padding: 12
+ selectByMouse: true
+
+ onAccepted: model.display = text
+
+ Rectangle {
+ anchors.fill: parent
+ color: "#efefef"
+ z: -1
+ }
+ }
+ /*delegate: Item {
clip: true
FiraSansSemiBold {
@@ -571,33 +606,35 @@ ModalWindow {
return size + " " + suffixes[suffixIndex];
}
}
- }
-
- QQC1.TableViewColumn {
- id: fileNameColumn
- role: "fileName"
- title: "Name"
- width: (selectDirectory ? 1.0 : 0.5) * fileTableView.width
- movable: false
- resizable: true
- }
- QQC1.TableViewColumn {
- id: fileModifiedColumn
- role: "fileModified"
- title: "Date"
- width: 0.3 * fileTableView.width
- movable: false
- resizable: true
- visible: !selectDirectory
- }
- QQC1.TableViewColumn {
- role: "fileSize"
- title: "Size"
- width: fileTableView.width - fileNameColumn.width - fileModifiedColumn.width
- movable: false
- resizable: true
- visible: !selectDirectory
- }
+ }*/
+
+ /*model: TableModel {
+ TableModelColumn {
+ id: fileNameColumn
+ role: "fileName"
+ title: "Name"
+ width: (selectDirectory ? 1.0 : 0.5) * fileTableView.width
+ movable: false
+ resizable: true
+ }
+ TableModelColumn {
+ id: fileModifiedColumn
+ role: "fileModified"
+ title: "Date"
+ width: 0.3 * fileTableView.width
+ movable: false
+ resizable: true
+ visible: !selectDirectory
+ }
+ TableModelColumn {
+ role: "fileSize"
+ title: "Size"
+ width: fileTableView.width - fileNameColumn.width - fileModifiedColumn.width
+ movable: false
+ resizable: true
+ visible: !selectDirectory
+ }
+ }*/
function navigateToRow(row) {
currentRow = row;
@@ -651,7 +688,7 @@ ModalWindow {
onTriggered: fileTableView.prefix = "";
}
- Keys.onPressed: {
+ Keys.onPressed: event => {
switch (event.key) {
case Qt.Key_Backspace:
case Qt.Key_Tab:
@@ -820,7 +857,7 @@ ModalWindow {
}
}
- Keys.onPressed: {
+ Keys.onPressed: event => {
switch (event.key) {
case Qt.Key_Backspace:
event.accepted = d.navigateUp();
diff --git a/interface/resources/qml/dialogs/MessageDialog.qml b/interface/resources/qml/dialogs/MessageDialog.qml
index 629027ab2af..ed272669d7b 100644
--- a/interface/resources/qml/dialogs/MessageDialog.qml
+++ b/interface/resources/qml/dialogs/MessageDialog.qml
@@ -9,7 +9,7 @@
//
import QtQuick 2.5
-import QtQuick.Dialogs 1.2 as OriginalDialogs
+import QtQuick.Dialogs as OriginalDialogs
import controlsUit 1.0
import stylesUit 1.0
@@ -36,26 +36,26 @@ ModalWindow {
return OffscreenUi.waitForMessageBoxResult(root);
}
- Keys.onRightPressed: if (defaultButton === OriginalDialogs.StandardButton.Yes) {
+ Keys.onRightPressed: if (defaultButton === OriginalDialogs.MessageDialog.Yes) {
yesButton.forceActiveFocus()
- } else if (defaultButton === OriginalDialogs.StandardButton.Ok) {
+ } else if (defaultButton === OriginalDialogs.MessageDialog.Ok) {
okButton.forceActiveFocus()
}
- Keys.onTabPressed: if (defaultButton === OriginalDialogs.StandardButton.Yes) {
+ Keys.onTabPressed: if (defaultButton === OriginalDialogs.MessageDialog.Yes) {
yesButton.forceActiveFocus()
- } else if (defaultButton === OriginalDialogs.StandardButton.Ok) {
+ } else if (defaultButton === OriginalDialogs.MessageDialog.Ok) {
okButton.forceActiveFocus()
}
property alias detailedText: detailedText.text
property alias text: mainTextContainer.text
property alias informativeText: informativeTextContainer.text
- property int buttons: OriginalDialogs.StandardButton.Ok
+ property int buttons: OriginalDialogs.MessageDialog.Ok
property int icon: OriginalDialogs.StandardIcon.NoIcon
property string iconText: ""
property int iconSize: 50
onIconChanged: updateIcon();
- property int defaultButton: OriginalDialogs.StandardButton.NoButton;
- property int clickedButton: OriginalDialogs.StandardButton.NoButton;
+ property int defaultButton: OriginalDialogs.MessageDialog.NoButton;
+ property int clickedButton: OriginalDialogs.MessageDialog.NoButton;
property int titleWidth: 0
onTitleWidthChanged: d.resize();
@@ -136,41 +136,41 @@ ModalWindow {
margins: 0
topMargin: 2 * hifi.dimensions.contentSpacing.y
}
- MessageDialogButton { dialog: root; text: qsTr("Close"); button: OriginalDialogs.StandardButton.Close; }
- MessageDialogButton { dialog: root; text: qsTr("Abort"); button: OriginalDialogs.StandardButton.Abort; }
- MessageDialogButton { dialog: root; text: qsTr("Cancel"); button: OriginalDialogs.StandardButton.Cancel; }
- MessageDialogButton { dialog: root; text: qsTr("Restore Defaults"); button: OriginalDialogs.StandardButton.RestoreDefaults; }
- MessageDialogButton { dialog: root; text: qsTr("Reset"); button: OriginalDialogs.StandardButton.Reset; }
- MessageDialogButton { dialog: root; text: qsTr("Discard"); button: OriginalDialogs.StandardButton.Discard; }
- MessageDialogButton { dialog: root; text: qsTr("No to All"); button: OriginalDialogs.StandardButton.NoToAll; }
+ MessageDialogButton { dialog: root; text: qsTr("Close"); button: OriginalDialogs.MessageDialog.Close; }
+ MessageDialogButton { dialog: root; text: qsTr("Abort"); button: OriginalDialogs.MessageDialog.Abort; }
+ MessageDialogButton { dialog: root; text: qsTr("Cancel"); button: OriginalDialogs.MessageDialog.Cancel; }
+ MessageDialogButton { dialog: root; text: qsTr("Restore Defaults"); button: OriginalDialogs.MessageDialog.RestoreDefaults; }
+ MessageDialogButton { dialog: root; text: qsTr("Reset"); button: OriginalDialogs.MessageDialog.Reset; }
+ MessageDialogButton { dialog: root; text: qsTr("Discard"); button: OriginalDialogs.MessageDialog.Discard; }
+ MessageDialogButton { dialog: root; text: qsTr("No to All"); button: OriginalDialogs.MessageDialog.NoToAll; }
MessageDialogButton {
id: noButton
dialog: root
text: qsTr("No")
- button: OriginalDialogs.StandardButton.No
+ button: OriginalDialogs.MessageDialog.No
KeyNavigation.left: yesButton
KeyNavigation.backtab: yesButton
}
- MessageDialogButton { dialog: root; text: qsTr("Yes to All"); button: OriginalDialogs.StandardButton.YesToAll; }
+ MessageDialogButton { dialog: root; text: qsTr("Yes to All"); button: OriginalDialogs.MessageDialog.YesToAll; }
MessageDialogButton {
id: yesButton
dialog: root
text: qsTr("Yes")
- button: OriginalDialogs.StandardButton.Yes
+ button: OriginalDialogs.MessageDialog.Yes
KeyNavigation.right: noButton
KeyNavigation.tab: noButton
}
- MessageDialogButton { dialog: root; text: qsTr("Apply"); button: OriginalDialogs.StandardButton.Apply; }
- MessageDialogButton { dialog: root; text: qsTr("Ignore"); button: OriginalDialogs.StandardButton.Ignore; }
- MessageDialogButton { dialog: root; text: qsTr("Retry"); button: OriginalDialogs.StandardButton.Retry; }
- MessageDialogButton { dialog: root; text: qsTr("Save All"); button: OriginalDialogs.StandardButton.SaveAll; }
- MessageDialogButton { dialog: root; text: qsTr("Save"); button: OriginalDialogs.StandardButton.Save; }
- MessageDialogButton { dialog: root; text: qsTr("Open"); button: OriginalDialogs.StandardButton.Open; }
+ MessageDialogButton { dialog: root; text: qsTr("Apply"); button: OriginalDialogs.MessageDialog.Apply; }
+ MessageDialogButton { dialog: root; text: qsTr("Ignore"); button: OriginalDialogs.MessageDialog.Ignore; }
+ MessageDialogButton { dialog: root; text: qsTr("Retry"); button: OriginalDialogs.MessageDialog.Retry; }
+ MessageDialogButton { dialog: root; text: qsTr("Save All"); button: OriginalDialogs.MessageDialog.SaveAll; }
+ MessageDialogButton { dialog: root; text: qsTr("Save"); button: OriginalDialogs.MessageDialog.Save; }
+ MessageDialogButton { dialog: root; text: qsTr("Open"); button: OriginalDialogs.MessageDialog.Open; }
MessageDialogButton {
id: okButton
dialog: root
text: qsTr("OK")
- button: OriginalDialogs.StandardButton.Ok
+ button: OriginalDialogs.MessageDialog.Ok
}
Button {
@@ -180,7 +180,7 @@ ModalWindow {
onClicked: { content.state = (content.state === "" ? "expanded" : "") }
visible: detailedText && detailedText.length > 0
}
- MessageDialogButton { dialog: root; text: qsTr("Help"); button: OriginalDialogs.StandardButton.Help; }
+ MessageDialogButton { dialog: root; text: qsTr("Help"); button: OriginalDialogs.MessageDialog.Help; }
}
Item {
@@ -256,7 +256,7 @@ ModalWindow {
case Qt.Key_Escape:
case Qt.Key_Back:
event.accepted = true
- root.click(OriginalDialogs.StandardButton.Cancel)
+ root.click(OriginalDialogs.MessageDialog.Cancel)
break
}
}
diff --git a/interface/resources/qml/dialogs/TabletConnectionFailureDialog.qml b/interface/resources/qml/dialogs/TabletConnectionFailureDialog.qml
index 3b3f7fb2d74..bece7b980c7 100644
--- a/interface/resources/qml/dialogs/TabletConnectionFailureDialog.qml
+++ b/interface/resources/qml/dialogs/TabletConnectionFailureDialog.qml
@@ -11,7 +11,7 @@
import Hifi 1.0
import QtQuick 2.5
-import QtQuick.Dialogs 1.2 as OriginalDialogs
+import QtQuick.Dialogs as OriginalDialogs
Item {
Component.onCompleted: {
diff --git a/interface/resources/qml/dialogs/TabletCustomQueryDialog.qml b/interface/resources/qml/dialogs/TabletCustomQueryDialog.qml
index c7772984abd..a9566e12f9c 100644
--- a/interface/resources/qml/dialogs/TabletCustomQueryDialog.qml
+++ b/interface/resources/qml/dialogs/TabletCustomQueryDialog.qml
@@ -9,7 +9,7 @@
//
import QtQuick 2.7
-import QtQuick.Dialogs 1.2 as OriginalDialogs
+import QtQuick.Dialogs as OriginalDialogs
import QtQuick.Controls 2.3
import controlsUit 1.0
diff --git a/interface/resources/qml/dialogs/TabletFileDialog.qml b/interface/resources/qml/dialogs/TabletFileDialog.qml
index 4ee53d44ef9..a5f912d367f 100644
--- a/interface/resources/qml/dialogs/TabletFileDialog.qml
+++ b/interface/resources/qml/dialogs/TabletFileDialog.qml
@@ -11,8 +11,7 @@
import QtQuick 2.7
import Qt.labs.folderlistmodel 2.2
import Qt.labs.settings 1.0
-import QtQuick.Dialogs 1.2 as OriginalDialogs
-import QtQuick.Controls 1.4 as QQC1
+import QtQuick.Dialogs as OriginalDialogs
import QtQuick.Controls 2.3
import ".."
@@ -460,7 +459,7 @@ TabletModalWindow {
}
}
- Table {
+ TableView {
id: fileTableView
colorScheme: hifi.colorSchemes.light
anchors {
@@ -472,14 +471,14 @@ TabletModalWindow {
bottomMargin: hifi.dimensions.contentSpacing.y + currentSelection.controlHeight - currentSelection.height
}
headerVisible: !selectDirectory
- onDoubleClicked: navigateToRow(row);
+ //onDoubleClicked: navigateToRow(row);
focus: true
Keys.onReturnPressed: navigateToCurrentRow();
Keys.onEnterPressed: navigateToCurrentRow();
- sortIndicatorColumn: 0
- sortIndicatorOrder: Qt.AscendingOrder
- sortIndicatorVisible: true
+ property int sortIndicatorColumn: 0
+ property var sortIndicatorOrder: Qt.AscendingOrder
+ property bool sortIndicatorVisible: true
model: filesModel
@@ -493,7 +492,7 @@ TabletModalWindow {
onSortIndicatorOrderChanged: { updateSort(); }
- itemDelegate: Item {
+ delegate: Item {
clip: true
FiraSansSemiBold {
@@ -538,7 +537,7 @@ TabletModalWindow {
}
}
- QQC1.TableViewColumn {
+ /*QQC1.TableViewColumn {
id: fileNameColumn
role: "fileName"
title: "Name"
@@ -562,7 +561,7 @@ TabletModalWindow {
movable: false
resizable: true
visible: !selectDirectory
- }
+ }*/
function navigateToRow(row) {
currentRow = row;
diff --git a/interface/resources/qml/dialogs/TabletMessageBox.qml b/interface/resources/qml/dialogs/TabletMessageBox.qml
index 4411651a0f0..a28ff5575e3 100644
--- a/interface/resources/qml/dialogs/TabletMessageBox.qml
+++ b/interface/resources/qml/dialogs/TabletMessageBox.qml
@@ -9,7 +9,7 @@
//
import QtQuick 2.5
-import QtQuick.Dialogs 1.2 as OriginalDialogs
+import QtQuick.Dialogs as OriginalDialogs
import controlsUit 1.0
import stylesUit 1.0
diff --git a/interface/resources/qml/dialogs/TabletQueryDialog.qml b/interface/resources/qml/dialogs/TabletQueryDialog.qml
index 8f63730b8e9..4724ac28162 100644
--- a/interface/resources/qml/dialogs/TabletQueryDialog.qml
+++ b/interface/resources/qml/dialogs/TabletQueryDialog.qml
@@ -9,7 +9,7 @@
//
import QtQuick 2.7
-import QtQuick.Dialogs 1.2 as OriginalDialogs
+import QtQuick.Dialogs as OriginalDialogs
import QtQuick.Controls 2.3
import controlsUit 1.0
diff --git a/interface/resources/qml/dialogs/assetDialog/AssetDialogContent.qml b/interface/resources/qml/dialogs/assetDialog/AssetDialogContent.qml
index eaf1d72fea2..6eacd58c816 100644
--- a/interface/resources/qml/dialogs/assetDialog/AssetDialogContent.qml
+++ b/interface/resources/qml/dialogs/assetDialog/AssetDialogContent.qml
@@ -10,7 +10,7 @@
import QtQuick 2.7
import QtQuick.Controls 2.3
-import QtQuick.Controls 1.5 as QQC1
+import QtQuick.Controls 2.3 as QQC1
import controlsUit 1.0
import stylesUit 1.0
diff --git a/interface/resources/qml/dialogs/messageDialog/MessageDialogButton.qml b/interface/resources/qml/dialogs/messageDialog/MessageDialogButton.qml
index f5715fa2c22..a791b1d0406 100644
--- a/interface/resources/qml/dialogs/messageDialog/MessageDialogButton.qml
+++ b/interface/resources/qml/dialogs/messageDialog/MessageDialogButton.qml
@@ -9,13 +9,13 @@
//
import QtQuick 2.5
-import QtQuick.Dialogs 1.2
+import QtQuick.Dialogs
import controlsUit 1.0
Button {
property var dialog;
- property int button: StandardButton.Ok;
+ property int button: MessageDialog.Ok;
color: focus ? hifi.buttons.blue : hifi.buttons.white
onClicked: dialog.click(button)
diff --git a/interface/resources/qml/dialogs/preferences/AvatarPreference.qml b/interface/resources/qml/dialogs/preferences/AvatarPreference.qml
index 48d29405b02..f668a22a64e 100644
--- a/interface/resources/qml/dialogs/preferences/AvatarPreference.qml
+++ b/interface/resources/qml/dialogs/preferences/AvatarPreference.qml
@@ -8,7 +8,7 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
-import QtQuick 2.5
+import QtQuick
import controlsUit 1.0
import "../../hifi/tablet/tabletWindows/preferences"
@@ -63,8 +63,8 @@ Preference {
placeholderText: root.placeholderText
text: preference.value
colorScheme: dataTextField.acceptableInput ? hifi.colorSchemes.dark : hifi.colorSchemes.light
- validator: RegExpValidator {
- regExp: /.*\.(?:fst).*\?*/ig
+ validator: RegularExpressionValidator {
+ regularExpression: /.*\.(?:fst).*\?*/ig
}
anchors {
left: parent.left
diff --git a/interface/resources/qml/dialogs/preferences/Section.qml b/interface/resources/qml/dialogs/preferences/Section.qml
index a9b755ad83f..038738cacbd 100644
--- a/interface/resources/qml/dialogs/preferences/Section.qml
+++ b/interface/resources/qml/dialogs/preferences/Section.qml
@@ -9,7 +9,7 @@
//
import QtQuick 2.5
-import QtQuick.Controls 1.4
+import QtQuick.Controls 2.3
import Hifi 1.0
import controlsUit 1.0 as HiFiControls
diff --git a/interface/resources/qml/hifi/+android_interface/ActionBar.qml b/interface/resources/qml/hifi/+android_interface/ActionBar.qml
index 3c58156f304..4b44cd3c798 100644
--- a/interface/resources/qml/hifi/+android_interface/ActionBar.qml
+++ b/interface/resources/qml/hifi/+android_interface/ActionBar.qml
@@ -1,6 +1,6 @@
import QtQuick 2.5
-import QtQuick.Controls 1.4
-import QtQuick.Controls.Styles 1.4
+import QtQuick.Controls 2.3
+//import QtQuick.Controls.Styles
import QtQuick.Layouts 1.3
import Qt.labs.settings 1.0
import stylesUit 1.0
diff --git a/interface/resources/qml/hifi/+android_interface/AudioBar.qml b/interface/resources/qml/hifi/+android_interface/AudioBar.qml
index 912572fdf8a..c5ff5775cf0 100644
--- a/interface/resources/qml/hifi/+android_interface/AudioBar.qml
+++ b/interface/resources/qml/hifi/+android_interface/AudioBar.qml
@@ -1,6 +1,6 @@
import QtQuick 2.5
-import QtQuick.Controls 1.4
-import QtQuick.Controls.Styles 1.4
+import QtQuick.Controls 2.3
+//import QtQuick.Controls.Styles
import QtQuick.Layouts 1.3
import Qt.labs.settings 1.0
import stylesUit 1.0
diff --git a/interface/resources/qml/hifi/+android_interface/Desktop.qml b/interface/resources/qml/hifi/+android_interface/Desktop.qml
index 99d792b664f..86d09ec64da 100644
--- a/interface/resources/qml/hifi/+android_interface/Desktop.qml
+++ b/interface/resources/qml/hifi/+android_interface/Desktop.qml
@@ -1,5 +1,5 @@
import QtQuick 2.5
-import QtQuick.Controls 1.4
+import QtQuick.Controls 2.3
import Qt.labs.settings 1.0
diff --git a/interface/resources/qml/hifi/+android_interface/StatsBar.qml b/interface/resources/qml/hifi/+android_interface/StatsBar.qml
index 64e93b4a088..2435d742fba 100644
--- a/interface/resources/qml/hifi/+android_interface/StatsBar.qml
+++ b/interface/resources/qml/hifi/+android_interface/StatsBar.qml
@@ -1,6 +1,6 @@
import QtQuick 2.5
-import QtQuick.Controls 1.4
-import QtQuick.Controls.Styles 1.4
+import QtQuick.Controls 2.3
+//import QtQuick.Controls.Styles
import QtQuick.Layouts 1.3
import Qt.labs.settings 1.0
import stylesUit 1.0
diff --git a/interface/resources/qml/hifi/+android_interface/WindowHeader.qml b/interface/resources/qml/hifi/+android_interface/WindowHeader.qml
index 5316fc4786b..43448bada85 100644
--- a/interface/resources/qml/hifi/+android_interface/WindowHeader.qml
+++ b/interface/resources/qml/hifi/+android_interface/WindowHeader.qml
@@ -10,8 +10,8 @@
//
import QtQuick 2.5
-import QtQuick.Controls 1.4
-import QtQuick.Controls.Styles 1.4
+import QtQuick.Controls 2.3
+//import QtQuick.Controls.Styles
import QtQuick.Layouts 1.3
import Qt.labs.settings 1.0
import "."
diff --git a/interface/resources/qml/hifi/+android_interface/bottomHudOptions.qml b/interface/resources/qml/hifi/+android_interface/bottomHudOptions.qml
index 6b830d94c25..e459e904c1e 100644
--- a/interface/resources/qml/hifi/+android_interface/bottomHudOptions.qml
+++ b/interface/resources/qml/hifi/+android_interface/bottomHudOptions.qml
@@ -11,8 +11,8 @@
import Hifi 1.0
import QtQuick 2.5
-import QtQuick.Controls 1.4
-import QtQuick.Controls.Styles 1.4
+import QtQuick.Controls 2.3
+//import QtQuick.Controls.Styles
import QtQuick.Layouts 1.3
import Qt.labs.settings 1.0
import "../../styles" as HifiStyles
diff --git a/interface/resources/qml/hifi/+android_interface/button.qml b/interface/resources/qml/hifi/+android_interface/button.qml
index a4c65b3c6f1..d83dd41d123 100644
--- a/interface/resources/qml/hifi/+android_interface/button.qml
+++ b/interface/resources/qml/hifi/+android_interface/button.qml
@@ -1,6 +1,6 @@
import QtQuick 2.5
-import QtQuick.Controls 1.4
-import QtQuick.Controls.Styles 1.4
+import QtQuick.Controls 2.3
+//import QtQuick.Controls.Styles
import QtQuick.Layouts 1.3
Item {
diff --git a/interface/resources/qml/hifi/+android_interface/modesbar.qml b/interface/resources/qml/hifi/+android_interface/modesbar.qml
index 1bf04fb8d98..f20e7c8ca0c 100644
--- a/interface/resources/qml/hifi/+android_interface/modesbar.qml
+++ b/interface/resources/qml/hifi/+android_interface/modesbar.qml
@@ -1,6 +1,6 @@
import QtQuick 2.5
-import QtQuick.Controls 1.4
-import QtQuick.Controls.Styles 1.4
+import QtQuick.Controls 2.3
+//import QtQuick.Controls.Styles
import QtQuick.Layouts 1.3
import Qt.labs.settings 1.0
import stylesUit 1.0
diff --git a/interface/resources/qml/hifi/AssetServer.qml b/interface/resources/qml/hifi/AssetServer.qml
index 75d7370c80a..baff11c7ded 100644
--- a/interface/resources/qml/hifi/AssetServer.qml
+++ b/interface/resources/qml/hifi/AssetServer.qml
@@ -8,11 +8,11 @@
// 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.Dialogs 1.2 as OriginalDialogs
-import Qt.labs.settings 1.0
+import QtQuick
+import QtQuick.Controls
+//import QtQuick.Controls.Styles
+import QtQuick.Dialogs as OriginalDialogs
+import Qt.labs.settings
import stylesUit 1.0
import controlsUit 1.0 as HifiControls
@@ -677,6 +677,8 @@ Windows.ScrollingWindow {
font.pixelSize: hifi.fontSizes.textFieldInput
height: hifi.dimensions.tableRowHeight
+ //QT6TODO
+ /*
style: TextFieldStyle {
textColor: readOnly
? hifi.colors.black
@@ -691,10 +693,10 @@ Windows.ScrollingWindow {
selectionColor: hifi.colors.primaryHighlight
padding.left: readOnly ? 0 : hifi.dimensions.textPadding
padding.right: readOnly ? 0 : hifi.dimensions.textPadding
- }
+ }*/
- validator: RegExpValidator {
- regExp: /[^/]+/
+ validator: RegularExpressionValidator {
+ regularExpression: /[^/]+/
}
Keys.onPressed: {
diff --git a/interface/resources/qml/hifi/AvatarApp.qml b/interface/resources/qml/hifi/AvatarApp.qml
index 0a02418a8bf..582d1a468e9 100644
--- a/interface/resources/qml/hifi/AvatarApp.qml
+++ b/interface/resources/qml/hifi/AvatarApp.qml
@@ -1,8 +1,8 @@
-import QtQuick 2.6
-import QtQuick.Controls 2.2
-import QtQuick.Layouts 1.3
-import QtQml.Models 2.1
-import QtGraphicalEffects 1.0
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Layouts
+import QtQml.Models
+import Qt5Compat.GraphicalEffects
import controlsUit 1.0 as HifiControls
import stylesUit 1.0
import "avatarapp"
diff --git a/interface/resources/qml/hifi/Card.qml b/interface/resources/qml/hifi/Card.qml
index 9fb8067371c..2ba146af976 100644
--- a/interface/resources/qml/hifi/Card.qml
+++ b/interface/resources/qml/hifi/Card.qml
@@ -13,7 +13,7 @@
import Hifi 1.0
import QtQuick 2.5
-import QtGraphicalEffects 1.0
+import Qt5Compat.GraphicalEffects
import TabletScriptingInterface 1.0
import "toolbars"
diff --git a/interface/resources/qml/hifi/Desktop.qml b/interface/resources/qml/hifi/Desktop.qml
index 3239471a00f..e3644504e23 100644
--- a/interface/resources/qml/hifi/Desktop.qml
+++ b/interface/resources/qml/hifi/Desktop.qml
@@ -1,5 +1,5 @@
+import QtCore
import QtQuick 2.7
-import Qt.labs.settings 1.0 as QtSettings
import QtQuick.Controls 2.3
@@ -12,9 +12,13 @@ import controlsUit 1.0
OriginalDesktop.Desktop {
id: desktop
+ // QT6TODO: Desktop just eats all mouse inputs
+ visible: true
+
property alias toolbarObjectName: sysToolbar.objectName
- MouseArea {
+ // QT6TODO: this breaks VR mouse cursor since
+ /*MouseArea {
id: hoverWatch
anchors.fill: parent
hoverEnabled: true
@@ -23,7 +27,7 @@ OriginalDesktop.Desktop {
onEntered: if (typeof ApplicationCompositor !== "undefined") ApplicationCompositor.reticleOverDesktop = true
onExited: if (typeof ApplicationCompositor !== "undefined") ApplicationCompositor.reticleOverDesktop = false
acceptedButtons: Qt.NoButton
- }
+ }*/
Action {
text: "Open Browser"
@@ -79,7 +83,7 @@ OriginalDesktop.Desktop {
}
signal toolbarVisibleChanged(bool isVisible, string toolbarName);
- QtSettings.Settings {
+ Settings {
id: settings;
category: "toolbar";
property bool constrainToolbarToCenterX: true;
diff --git a/interface/resources/qml/hifi/Feed.qml b/interface/resources/qml/hifi/Feed.qml
index a9fde05d8d4..8c057244617 100644
--- a/interface/resources/qml/hifi/Feed.qml
+++ b/interface/resources/qml/hifi/Feed.qml
@@ -13,7 +13,7 @@
import Hifi 1.0
import QtQuick 2.5
-import QtGraphicalEffects 1.0
+import Qt5Compat.GraphicalEffects
import "toolbars"
import stylesUit 1.0
import "qrc:////qml//hifi//models" as HifiModels // Absolute path so the same code works everywhere.
diff --git a/interface/resources/qml/hifi/NameCard.qml b/interface/resources/qml/hifi/NameCard.qml
index 0dd29b9e0f4..e7f0100325f 100644
--- a/interface/resources/qml/hifi/NameCard.qml
+++ b/interface/resources/qml/hifi/NameCard.qml
@@ -10,9 +10,9 @@
//
import QtQuick 2.5
-import QtQuick.Controls 1.4
-import QtQuick.Controls.Styles 1.4
-import QtGraphicalEffects 1.0
+import QtQuick.Controls 2.3
+//import QtQuick.Controls.Styles
+import Qt5Compat.GraphicalEffects
import stylesUit 1.0
import controlsUit 1.0 as HifiControls
import "toolbars"
@@ -534,10 +534,12 @@ Item {
anchors.left: nameCardVUMeter.left;
// Properties
visible: (!isMyCard && (selected && pal.activeTab == "nearbyTab")) && isPresent;
- minimumValue: -60.0
- maximumValue: 20.0
+ // QT6TODO
+ //minimumValue: -60.0
+ //maximumValue: 20.0
stepSize: 5
- updateValueWhileDragging: true
+ // QT6TODO
+ //updateValueWhileDragging: true
value: Users.getAvatarGain(uuid)
onValueChanged: {
updateGainFromQML(uuid, value, false);
@@ -565,7 +567,8 @@ Item {
mouse.accepted = false
}
}
- style: SliderStyle {
+ // QT6TODO
+ /*style: SliderStyle {
groove: Rectangle {
color: "#c5c5c5"
implicitWidth: gainSlider.width
@@ -579,7 +582,7 @@ Item {
implicitWidth: 10
implicitHeight: 16
}
- }
+ }*/
}
function updateGainFromQML(avatarUuid, sliderValue, isReleased) {
diff --git a/interface/resources/qml/hifi/Pal.qml b/interface/resources/qml/hifi/Pal.qml
index b842b736d42..39cbaacceb8 100644
--- a/interface/resources/qml/hifi/Pal.qml
+++ b/interface/resources/qml/hifi/Pal.qml
@@ -12,8 +12,8 @@
//
import QtQuick 2.5
-import QtQuick.Controls 1.4
-import QtGraphicalEffects 1.0
+import QtQuick.Controls 2.3
+import Qt5Compat.GraphicalEffects
import Qt.labs.settings 1.0
import stylesUit 1.0
import controlsUit 1.0 as HifiControlsUit
@@ -118,6 +118,7 @@ Rectangle {
comboDialog.populateComboListViewModel();
comboDialog.visible = true;
}
+ // QT6TODO: this is deprecated
Settings {
id: settings;
category: "pal";
@@ -427,16 +428,18 @@ Rectangle {
// This TableView refers to the Nearby Table (on the "Nearby" tab below the current user's NameCard)
HifiControlsUit.Table {
id: nearbyTable;
- flickableItem.interactive: true;
+ // QT6TODO
+ //flickableItem.interactive: true;
// Anchors
anchors.fill: parent;
// Properties
centerHeaderText: true;
- sortIndicatorVisible: true;
- headerVisible: true;
- sortIndicatorColumn: settings.nearbySortIndicatorColumn;
- sortIndicatorOrder: settings.nearbySortIndicatorOrder;
- onSortIndicatorColumnChanged: {
+ // QT6TODO
+ //sortIndicatorVisible: true;
+ //headerVisible: true;
+ //sortIndicatorColumn: settings.nearbySortIndicatorColumn;
+ //sortIndicatorOrder: settings.nearbySortIndicatorOrder;
+ /*onSortIndicatorColumnChanged: {
if (sortIndicatorColumn > 2) {
// these are not sortable, switch back to last column
sortIndicatorColumn = settings.nearbySortIndicatorColumn;
@@ -444,13 +447,15 @@ Rectangle {
settings.nearbySortIndicatorColumn = sortIndicatorColumn;
sortModel();
}
- }
- onSortIndicatorOrderChanged: {
+ }*/
+ // Qt6TODO
+ /*onSortIndicatorOrderChanged: {
settings.nearbySortIndicatorOrder = sortIndicatorOrder;
sortModel();
- }
+ }*/
- TableViewColumn {
+ // QT6TODO: how to port these?
+ /*TableViewColumn {
role: "avgAudioLevel";
title: "LOUD";
width: actionButtonWidth;
@@ -489,20 +494,22 @@ Rectangle {
width: actionButtonWidth;
movable: false;
resizable: false;
- }
+ }*/
model: ListModel {
id: nearbyUserModel;
}
// This Rectangle refers to each Row in the nearbyTable.
- rowDelegate: Rectangle { // The only way I know to specify a row height.
+ // QT6TODO
+ /*rowDelegate: Rectangle { // The only way I know to specify a row height.
// Size
height: rowHeight + (styleData.selected ? 15 : 0);
color: nearbyRowColor(styleData.selected, styleData.alternate);
- }
+ }*/
// This Item refers to the contents of each Cell
- itemDelegate: Item {
+ // QT6TODO
+ /*itemDelegate: Item {
id: itemCell;
property bool isCheckBox: styleData.role === "personalMute" || styleData.role === "ignore";
property bool isButton: styleData.role === "mute" || styleData.role === "kick";
@@ -664,7 +671,7 @@ Rectangle {
: hifi.buttons.disabledTextColor[actionButton.colorScheme];
}
}
- }
+ }*/
}
// Separator between user and admin functions
@@ -824,24 +831,27 @@ Rectangle {
// This TableView refers to the Connections Table (on the "Connections" tab below the current user's NameCard)
HifiControlsUit.Table {
id: connectionsTable;
- flickableItem.interactive: true;
+ // QT6TODO
+ //flickableItem.interactive: true;
visible: !connectionsLoading.visible;
// Anchors
anchors.fill: parent;
// Properties
centerHeaderText: true;
- sortIndicatorVisible: true;
- headerVisible: true;
- sortIndicatorColumn: settings.connectionsSortIndicatorColumn;
+ // QT6TODO
+ //sortIndicatorVisible: true;
+ //headerVisible: true;
+ /*sortIndicatorColumn: settings.connectionsSortIndicatorColumn;
sortIndicatorOrder: settings.connectionsSortIndicatorOrder;
onSortIndicatorColumnChanged: {
settings.connectionsSortIndicatorColumn = sortIndicatorColumn;
}
onSortIndicatorOrderChanged: {
settings.connectionsSortIndicatorOrder = sortIndicatorOrder;
- }
+ }*/
- TableViewColumn {
+ // QT6TODO: how to port these?
+ /*TableViewColumn {
id: connectionsUserNameHeader;
role: "userName";
title: connectionsUserModel.totalEntries + (connectionsUserModel.totalEntries === 1 ? " NAME" : " NAMES");
@@ -862,19 +872,21 @@ Rectangle {
width: actionButtonWidth;
movable: false;
resizable: false;
- }
+ }*/
model: connectionsUserModel;
// This Rectangle refers to each Row in the connectionsTable.
- rowDelegate: Rectangle {
+ // QT6TODO
+ /*rowDelegate: Rectangle {
// Size
height: rowHeight + (styleData.selected ? 15 : 0);
color: connectionsRowColor(styleData.selected, styleData.alternate);
- }
+ }*/
// This Item refers to the contents of each Cell
- itemDelegate: Item {
+ // QT6TODO
+ /*itemDelegate: Item {
id: connectionsItemCell;
// This NameCard refers to the cell that contains a connection's UserName
@@ -947,7 +959,7 @@ Rectangle {
UserActivityLogger["palAction"](checked ? styleData.role : "un-" + styleData.role, model.sessionId);
}
}
- }
+ }*/
}
// "Make a Connection" instructions
diff --git a/interface/resources/qml/hifi/audio/Audio.qml b/interface/resources/qml/hifi/audio/Audio.qml
index 7bfdf028891..c93e28b28e6 100644
--- a/interface/resources/qml/hifi/audio/Audio.qml
+++ b/interface/resources/qml/hifi/audio/Audio.qml
@@ -15,7 +15,7 @@
import QtQuick 2.10
import QtQuick.Controls 2.2
import QtQuick.Layouts 1.3
-import QtGraphicalEffects 1.0
+import Qt5Compat.GraphicalEffects
import stylesUit 1.0
import controlsUit 1.0 as HifiControlsUit
diff --git a/interface/resources/qml/hifi/audio/InputPeak.qml b/interface/resources/qml/hifi/audio/InputPeak.qml
index d8b166cee49..c6b5ce99900 100644
--- a/interface/resources/qml/hifi/audio/InputPeak.qml
+++ b/interface/resources/qml/hifi/audio/InputPeak.qml
@@ -10,7 +10,7 @@
//
import QtQuick 2.5
-import QtGraphicalEffects 1.0
+import Qt5Compat.GraphicalEffects
Item {
property var peak;
diff --git a/interface/resources/qml/hifi/audio/MicBar.qml b/interface/resources/qml/hifi/audio/MicBar.qml
index 55378589ecc..637a02adab0 100644
--- a/interface/resources/qml/hifi/audio/MicBar.qml
+++ b/interface/resources/qml/hifi/audio/MicBar.qml
@@ -10,7 +10,7 @@
//
import QtQuick 2.5
-import QtGraphicalEffects 1.0
+import Qt5Compat.GraphicalEffects
import stylesUit 1.0
import TabletScriptingInterface 1.0
diff --git a/interface/resources/qml/hifi/audio/MicBarApplication.qml b/interface/resources/qml/hifi/audio/MicBarApplication.qml
index d77668e5bfe..798c77f3f59 100644
--- a/interface/resources/qml/hifi/audio/MicBarApplication.qml
+++ b/interface/resources/qml/hifi/audio/MicBarApplication.qml
@@ -10,7 +10,7 @@
//
import QtQuick 2.5
-import QtGraphicalEffects 1.0
+import Qt5Compat.GraphicalEffects
import stylesUit 1.0
import TabletScriptingInterface 1.0
diff --git a/interface/resources/qml/hifi/avatarPackager/AvatarPackagerApp.qml b/interface/resources/qml/hifi/avatarPackager/AvatarPackagerApp.qml
index be9fe932e0b..9d78f68147d 100644
--- a/interface/resources/qml/hifi/avatarPackager/AvatarPackagerApp.qml
+++ b/interface/resources/qml/hifi/avatarPackager/AvatarPackagerApp.qml
@@ -2,7 +2,7 @@ import QtQuick 2.6
import QtQuick.Controls 2.2
import QtQuick.Layouts 1.3
import QtQml.Models 2.1
-import QtGraphicalEffects 1.0
+import Qt5Compat.GraphicalEffects
import Hifi.AvatarPackager.AvatarProjectStatus 1.0
import "../../controlsUit" 1.0 as HifiControls
import "../../stylesUit" 1.0
diff --git a/interface/resources/qml/hifi/avatarPackager/AvatarProject.qml b/interface/resources/qml/hifi/avatarPackager/AvatarProject.qml
index 2b06d7e99ee..f1b55e1b69e 100644
--- a/interface/resources/qml/hifi/avatarPackager/AvatarProject.qml
+++ b/interface/resources/qml/hifi/avatarPackager/AvatarProject.qml
@@ -1,7 +1,7 @@
import QtQuick 2.6
import QtQuick.Controls 2.2
import QtQuick.Layouts 1.3
-import QtGraphicalEffects 1.0
+import Qt5Compat.GraphicalEffects
import QtQuick.Controls 2.2 as Original
diff --git a/interface/resources/qml/hifi/avatarPackager/AvatarProjectCard.qml b/interface/resources/qml/hifi/avatarPackager/AvatarProjectCard.qml
index 21d0683fb11..58d22675ea9 100644
--- a/interface/resources/qml/hifi/avatarPackager/AvatarProjectCard.qml
+++ b/interface/resources/qml/hifi/avatarPackager/AvatarProjectCard.qml
@@ -1,5 +1,5 @@
import QtQuick 2.0
-import QtGraphicalEffects 1.0
+import Qt5Compat.GraphicalEffects
import "../../controlsUit" 1.0 as HifiControls
import "../../stylesUit" 1.0
diff --git a/interface/resources/qml/hifi/avatarPackager/AvatarProjectUpload.qml b/interface/resources/qml/hifi/avatarPackager/AvatarProjectUpload.qml
index 68f465f514f..4f5acd88734 100644
--- a/interface/resources/qml/hifi/avatarPackager/AvatarProjectUpload.qml
+++ b/interface/resources/qml/hifi/avatarPackager/AvatarProjectUpload.qml
@@ -1,7 +1,7 @@
import QtQuick 2.6
import QtQuick.Controls 2.2
import QtQuick.Layouts 1.3
-import QtGraphicalEffects 1.0
+import Qt5Compat.GraphicalEffects
import QtQuick.Controls 2.2 as Original
diff --git a/interface/resources/qml/hifi/avatarPackager/LoadingCircle.qml b/interface/resources/qml/hifi/avatarPackager/LoadingCircle.qml
index a1fac72ae4c..1160e8474a6 100644
--- a/interface/resources/qml/hifi/avatarPackager/LoadingCircle.qml
+++ b/interface/resources/qml/hifi/avatarPackager/LoadingCircle.qml
@@ -1,7 +1,7 @@
import QtQuick 2.6
import QtQuick.Controls 2.2
import QtQuick.Layouts 1.3
-import QtGraphicalEffects 1.0
+import Qt5Compat.GraphicalEffects
AnimatedImage {
id: root
diff --git a/interface/resources/qml/hifi/avatarapp/AvatarThumbnail.qml b/interface/resources/qml/hifi/avatarapp/AvatarThumbnail.qml
index 8582b9249bd..41bb864caee 100644
--- a/interface/resources/qml/hifi/avatarapp/AvatarThumbnail.qml
+++ b/interface/resources/qml/hifi/avatarapp/AvatarThumbnail.qml
@@ -1,5 +1,5 @@
import QtQuick 2.9
-import QtGraphicalEffects 1.0
+import Qt5Compat.GraphicalEffects
Item {
width: 92
diff --git a/interface/resources/qml/hifi/avatarapp/CreateFavoriteDialog.qml b/interface/resources/qml/hifi/avatarapp/CreateFavoriteDialog.qml
index 5d72bef43da..666fbf0b932 100644
--- a/interface/resources/qml/hifi/avatarapp/CreateFavoriteDialog.qml
+++ b/interface/resources/qml/hifi/avatarapp/CreateFavoriteDialog.qml
@@ -13,7 +13,7 @@ Rectangle {
property string titleText: 'Create Favorite'
property string favoriteNameText: favoriteName.text
- property string avatarImageUrl: null
+ property string avatarImageUrl: ""
property int wearablesCount: 0
property string button1color: hifi.buttons.noneBorderlessGray;
diff --git a/interface/resources/qml/hifi/avatarapp/MessageBox.qml b/interface/resources/qml/hifi/avatarapp/MessageBox.qml
index 88f7f888cb0..ecfb193a2a7 100644
--- a/interface/resources/qml/hifi/avatarapp/MessageBox.qml
+++ b/interface/resources/qml/hifi/avatarapp/MessageBox.qml
@@ -16,7 +16,7 @@ Rectangle {
property alias inputText: input;
property alias dialogButtons: buttons
- property string imageSource: null
+ property string imageSource: ""
property string button1color: hifi.buttons.noneBorderlessGray;
property string button1text: ''
@@ -70,6 +70,7 @@ Rectangle {
width: Math.max(parent.width * 0.8, 400)
property int margin: 30;
+ // QT6TODO: QML QQuickRectangle*: Binding loop detected for property "height"
height: childrenRect.height + margin * 2
onHeightChanged: {
console.debug('mainContainer: height = ', height)
diff --git a/interface/resources/qml/hifi/avatarapp/MessageBoxes.qml b/interface/resources/qml/hifi/avatarapp/MessageBoxes.qml
index f0acbd68e78..a466272a2fd 100644
--- a/interface/resources/qml/hifi/avatarapp/MessageBoxes.qml
+++ b/interface/resources/qml/hifi/avatarapp/MessageBoxes.qml
@@ -1,4 +1,4 @@
-import QtQuick 2.5
+import QtQuick
MessageBox {
id: popup
diff --git a/interface/resources/qml/hifi/avatarapp/ShadowGlyph.qml b/interface/resources/qml/hifi/avatarapp/ShadowGlyph.qml
index a2c84fad472..7cf7b2ac03d 100644
--- a/interface/resources/qml/hifi/avatarapp/ShadowGlyph.qml
+++ b/interface/resources/qml/hifi/avatarapp/ShadowGlyph.qml
@@ -1,6 +1,6 @@
import stylesUit 1.0
import QtQuick 2.9
-import QtGraphicalEffects 1.0
+import Qt5Compat.GraphicalEffects
Item {
property alias text: glyph.text
diff --git a/interface/resources/qml/hifi/avatarapp/ShadowImage.qml b/interface/resources/qml/hifi/avatarapp/ShadowImage.qml
index 51e10437025..1b66bfa20d0 100644
--- a/interface/resources/qml/hifi/avatarapp/ShadowImage.qml
+++ b/interface/resources/qml/hifi/avatarapp/ShadowImage.qml
@@ -1,6 +1,6 @@
import stylesUit 1.0
import QtQuick 2.9
-import QtGraphicalEffects 1.0
+import Qt5Compat.GraphicalEffects
Item {
property alias source: image.source
diff --git a/interface/resources/qml/hifi/avatarapp/ShadowRectangle.qml b/interface/resources/qml/hifi/avatarapp/ShadowRectangle.qml
index 3968fcb1ff2..a719627509c 100644
--- a/interface/resources/qml/hifi/avatarapp/ShadowRectangle.qml
+++ b/interface/resources/qml/hifi/avatarapp/ShadowRectangle.qml
@@ -1,6 +1,6 @@
import stylesUit 1.0
import QtQuick 2.9
-import QtGraphicalEffects 1.0
+import Qt5Compat.GraphicalEffects
Item {
property alias color: rectangle.color
diff --git a/interface/resources/qml/hifi/avatarapp/SquareLabel.qml b/interface/resources/qml/hifi/avatarapp/SquareLabel.qml
index 69aff47373c..2615df7f783 100644
--- a/interface/resources/qml/hifi/avatarapp/SquareLabel.qml
+++ b/interface/resources/qml/hifi/avatarapp/SquareLabel.qml
@@ -1,7 +1,7 @@
import stylesUit 1.0
import controlsUit 1.0 as HifiControlsUit
import QtQuick 2.9
-import QtGraphicalEffects 1.0
+import Qt5Compat.GraphicalEffects
Item {
id: root
diff --git a/interface/resources/qml/hifi/avatarapp/TransparencyMask.qml b/interface/resources/qml/hifi/avatarapp/TransparencyMask.qml
index c8ac5f47782..7af7d554ed1 100644
--- a/interface/resources/qml/hifi/avatarapp/TransparencyMask.qml
+++ b/interface/resources/qml/hifi/avatarapp/TransparencyMask.qml
@@ -1,4 +1,4 @@
-import QtQuick 2.0
+import QtQuick
Item {
property alias source: sourceImage.sourceItem
@@ -15,7 +15,8 @@ Item {
hideSource: true
}
- ShaderEffect {
+ // QT6TODO: this needs to be ported to Qt6 - probably converting into .qsb is needed
+/* ShaderEffect {
id: maskEffect
anchors.fill: parent
@@ -40,5 +41,5 @@ void main()
}
"
}
- }
+ }*/
}
\ No newline at end of file
diff --git a/interface/resources/qml/hifi/dialogs/RunningScripts.qml b/interface/resources/qml/hifi/dialogs/RunningScripts.qml
index b9cfe113f08..8c52ce41d19 100644
--- a/interface/resources/qml/hifi/dialogs/RunningScripts.qml
+++ b/interface/resources/qml/hifi/dialogs/RunningScripts.qml
@@ -9,8 +9,8 @@
//
import QtQuick 2.5
-import QtQuick.Controls 1.4
-import QtQuick.Dialogs 1.2 as OriginalDialogs
+import QtQuick.Controls 2.3
+import QtQuick.Dialogs as OriginalDialogs
import Qt.labs.settings 1.0
import stylesUit 1.0
diff --git a/interface/resources/qml/hifi/dialogs/TabletAssetServer.qml b/interface/resources/qml/hifi/dialogs/TabletAssetServer.qml
index 6374e85ada5..c9e2572a24a 100644
--- a/interface/resources/qml/hifi/dialogs/TabletAssetServer.qml
+++ b/interface/resources/qml/hifi/dialogs/TabletAssetServer.qml
@@ -9,9 +9,9 @@
//
import QtQuick 2.5
-import QtQuick.Controls 1.4
-import QtQuick.Controls.Styles 1.4
-import QtQuick.Dialogs 1.2 as OriginalDialogs
+import QtQuick.Controls 2.3
+//import QtQuick.Controls.Styles
+import QtQuick.Dialogs as OriginalDialogs
import Qt.labs.settings 1.0
import stylesUit 1.0
@@ -675,6 +675,8 @@ Rectangle {
font.pixelSize: hifi.fontSizes.textFieldInput
height: hifi.dimensions.tableRowHeight
+ //QT6TODO
+ /*
style: TextFieldStyle {
textColor: readOnly
? hifi.colors.black
@@ -689,10 +691,10 @@ Rectangle {
selectionColor: hifi.colors.primaryHighlight
padding.left: readOnly ? 0 : hifi.dimensions.textPadding
padding.right: readOnly ? 0 : hifi.dimensions.textPadding
- }
+ }*/
- validator: RegExpValidator {
- regExp: /[^/]+/
+ validator: RegularExpressionValidator {
+ regularExpression: /[^/]+/
}
Keys.onPressed: {
diff --git a/interface/resources/qml/hifi/dialogs/TabletRunningScripts.qml b/interface/resources/qml/hifi/dialogs/TabletRunningScripts.qml
index 4aeeada1e5a..9bb91f20c13 100644
--- a/interface/resources/qml/hifi/dialogs/TabletRunningScripts.qml
+++ b/interface/resources/qml/hifi/dialogs/TabletRunningScripts.qml
@@ -9,8 +9,8 @@
//
import QtQuick 2.5
-import QtQuick.Controls 1.4
-import QtQuick.Dialogs 1.2 as OriginalDialogs
+import QtQuick.Controls 2.3
+import QtQuick.Dialogs as OriginalDialogs
import Qt.labs.settings 1.0
import stylesUit 1.0
diff --git a/interface/resources/qml/hifi/dialogs/security/Security.qml b/interface/resources/qml/hifi/dialogs/security/Security.qml
index 8942a451970..a8bb5874681 100644
--- a/interface/resources/qml/hifi/dialogs/security/Security.qml
+++ b/interface/resources/qml/hifi/dialogs/security/Security.qml
@@ -13,7 +13,7 @@
import Hifi 1.0 as Hifi
import QtQuick 2.5
-import QtGraphicalEffects 1.0
+import Qt5Compat.GraphicalEffects
import stylesUit 1.0 as HifiStylesUit
import controlsUit 1.0 as HifiControlsUit
import "qrc:////qml//controls" as HifiControls
diff --git a/interface/resources/qml/hifi/overlays/ImageOverlay.qml b/interface/resources/qml/hifi/overlays/ImageOverlay.qml
index f3fbb88c078..dce25783938 100644
--- a/interface/resources/qml/hifi/overlays/ImageOverlay.qml
+++ b/interface/resources/qml/hifi/overlays/ImageOverlay.qml
@@ -1,5 +1,5 @@
import QtQuick 2.3
-import QtGraphicalEffects 1.0
+import Qt5Compat.GraphicalEffects
import "."
diff --git a/interface/resources/qml/hifi/simplifiedUI/avatarApp/components/AvatarAppListDelegate.qml b/interface/resources/qml/hifi/simplifiedUI/avatarApp/components/AvatarAppListDelegate.qml
index a14f5d7bdef..1cd5003a568 100644
--- a/interface/resources/qml/hifi/simplifiedUI/avatarApp/components/AvatarAppListDelegate.qml
+++ b/interface/resources/qml/hifi/simplifiedUI/avatarApp/components/AvatarAppListDelegate.qml
@@ -12,7 +12,7 @@ import QtQuick 2.10
import "../../simplifiedConstants" as SimplifiedConstants
import "../../simplifiedControls" as SimplifiedControls
import stylesUit 1.0 as HifiStylesUit
-import QtGraphicalEffects 1.0
+import Qt5Compat.GraphicalEffects
Rectangle {
id: root
diff --git a/interface/resources/qml/hifi/simplifiedUI/avatarApp/components/DisplayNameHeader.qml b/interface/resources/qml/hifi/simplifiedUI/avatarApp/components/DisplayNameHeader.qml
index e039734e731..e032150b0b5 100644
--- a/interface/resources/qml/hifi/simplifiedUI/avatarApp/components/DisplayNameHeader.qml
+++ b/interface/resources/qml/hifi/simplifiedUI/avatarApp/components/DisplayNameHeader.qml
@@ -13,7 +13,7 @@ import "../../simplifiedConstants" as SimplifiedConstants
import "../../simplifiedControls" as SimplifiedControls
import stylesUit 1.0 as HifiStylesUit
import controlsUit 1.0 as HifiControlsUit
-import QtGraphicalEffects 1.0
+import Qt5Compat.GraphicalEffects
Item {
id: root
diff --git a/interface/resources/qml/hifi/simplifiedUI/inputDeviceButton/InputDeviceButton.qml b/interface/resources/qml/hifi/simplifiedUI/inputDeviceButton/InputDeviceButton.qml
index 9e02820168e..a893f397158 100644
--- a/interface/resources/qml/hifi/simplifiedUI/inputDeviceButton/InputDeviceButton.qml
+++ b/interface/resources/qml/hifi/simplifiedUI/inputDeviceButton/InputDeviceButton.qml
@@ -10,7 +10,7 @@
//
import QtQuick 2.10
-import QtGraphicalEffects 1.0
+import Qt5Compat.GraphicalEffects
import stylesUit 1.0
import TabletScriptingInterface 1.0
import "../simplifiedConstants" as SimplifiedConstants
diff --git a/interface/resources/qml/hifi/simplifiedUI/simplifiedControls/InputPeak.qml b/interface/resources/qml/hifi/simplifiedUI/simplifiedControls/InputPeak.qml
index c8440dc7d64..89d8f62d7fa 100644
--- a/interface/resources/qml/hifi/simplifiedUI/simplifiedControls/InputPeak.qml
+++ b/interface/resources/qml/hifi/simplifiedUI/simplifiedControls/InputPeak.qml
@@ -9,7 +9,7 @@
//
import QtQuick 2.10
-import QtGraphicalEffects 1.0
+import Qt5Compat.GraphicalEffects
Item {
property var peak
diff --git a/interface/resources/qml/hifi/simplifiedUI/topBar/SimplifiedTopBar.qml b/interface/resources/qml/hifi/simplifiedUI/topBar/SimplifiedTopBar.qml
index 1f0cd9008b2..20a9f77d24b 100644
--- a/interface/resources/qml/hifi/simplifiedUI/topBar/SimplifiedTopBar.qml
+++ b/interface/resources/qml/hifi/simplifiedUI/topBar/SimplifiedTopBar.qml
@@ -14,7 +14,7 @@ import "../simplifiedConstants" as SimplifiedConstants
import "../inputDeviceButton" as InputDeviceButton
import stylesUit 1.0 as HifiStylesUit
import TabletScriptingInterface 1.0
-import QtGraphicalEffects 1.0
+import Qt5Compat.GraphicalEffects
import "qrc:////qml//hifi//models" as HifiModels // Absolute path so the same code works everywhere.
Rectangle {
diff --git a/interface/resources/qml/hifi/tablet/CalibratingScreen.qml b/interface/resources/qml/hifi/tablet/CalibratingScreen.qml
index 6b2aa331e8d..13e713388a9 100644
--- a/interface/resources/qml/hifi/tablet/CalibratingScreen.qml
+++ b/interface/resources/qml/hifi/tablet/CalibratingScreen.qml
@@ -8,7 +8,7 @@
import QtQuick 2.5
-import QtGraphicalEffects 1.0
+import Qt5Compat.GraphicalEffects
import stylesUit 1.0
import "../../controls"
diff --git a/interface/resources/qml/hifi/tablet/ControllerSettings.qml b/interface/resources/qml/hifi/tablet/ControllerSettings.qml
index ee4865258e4..0e2df1a4e98 100644
--- a/interface/resources/qml/hifi/tablet/ControllerSettings.qml
+++ b/interface/resources/qml/hifi/tablet/ControllerSettings.qml
@@ -11,7 +11,7 @@ import QtQuick 2.7
import QtQuick.Controls 2.2
import QtQuick.Layouts 1.3
import QtQuick.Window 2.2
-import QtGraphicalEffects 1.0
+import Qt5Compat.GraphicalEffects
import Qt.labs.settings 1.0
import stylesUit 1.0
import "../../controls"
diff --git a/interface/resources/qml/hifi/tablet/OpenVrConfiguration.qml b/interface/resources/qml/hifi/tablet/OpenVrConfiguration.qml
index 20a68237cf3..8fdb8b478ef 100644
--- a/interface/resources/qml/hifi/tablet/OpenVrConfiguration.qml
+++ b/interface/resources/qml/hifi/tablet/OpenVrConfiguration.qml
@@ -10,7 +10,7 @@
//
import QtQuick 2.5
-import QtGraphicalEffects 1.0
+import Qt5Compat.GraphicalEffects
import QtQuick.Controls 2.2
import stylesUit 1.0
diff --git a/interface/resources/qml/hifi/tablet/TADLightbox.qml b/interface/resources/qml/hifi/tablet/TADLightbox.qml
index 35a01aeec31..abfd3a19ca9 100644
--- a/interface/resources/qml/hifi/tablet/TADLightbox.qml
+++ b/interface/resources/qml/hifi/tablet/TADLightbox.qml
@@ -13,7 +13,7 @@
import Hifi 1.0 as Hifi
import QtQuick 2.5
-import QtGraphicalEffects 1.0
+import Qt5Compat.GraphicalEffects
import stylesUit 1.0
import controlsUit 1.0 as HifiControlsUit
import "qrc:////qml//controls" as HifiControls
diff --git a/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml b/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml
index fdb05c8a35f..96858c855b8 100644
--- a/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml
+++ b/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml
@@ -11,8 +11,8 @@
import Hifi 1.0
import QtQuick 2.7
import QtQuick.Controls 2.2
-import QtQuick.Controls.Styles 1.4
-import QtGraphicalEffects 1.0
+//import QtQuick.Controls.Styles
+import Qt5Compat.GraphicalEffects
import "../../controls"
import "../../styles"
import "../../windows"
diff --git a/interface/resources/qml/hifi/tablet/TabletButton.qml b/interface/resources/qml/hifi/tablet/TabletButton.qml
index bea2d31e2bd..2b1afce685d 100644
--- a/interface/resources/qml/hifi/tablet/TabletButton.qml
+++ b/interface/resources/qml/hifi/tablet/TabletButton.qml
@@ -1,5 +1,5 @@
import QtQuick 2.0
-import QtGraphicalEffects 1.0
+import Qt5Compat.GraphicalEffects
import TabletScriptingInterface 1.0
Item {
diff --git a/interface/resources/qml/hifi/tablet/TabletHome.qml b/interface/resources/qml/hifi/tablet/TabletHome.qml
index 0311a4fa13c..c7b693cf282 100644
--- a/interface/resources/qml/hifi/tablet/TabletHome.qml
+++ b/interface/resources/qml/hifi/tablet/TabletHome.qml
@@ -1,6 +1,6 @@
import QtQuick 2.7
import QtQuick.Controls 2.2
-import QtGraphicalEffects 1.0
+import Qt5Compat.GraphicalEffects
import QtQuick.Layouts 1.3
import TabletScriptingInterface 1.0
@@ -200,7 +200,7 @@ Item {
Repeater {
id: pageRepeater
model: tabletProxy != null ? Math.ceil(tabletProxy.buttons.rowCount() / TabletEnums.ButtonsOnPage) : 0
- onItemAdded: {
+ onItemAdded: (index, item) => {
item.proxyModel.sourceModel = tabletProxy != null ? tabletProxy.buttons : null;
item.proxyModel.pageIndex = index;
}
@@ -284,7 +284,7 @@ Item {
Connections {
target: modelData;
- onPropertiesChanged: {
+ function onPropertiesChanged() {
updateProperties();
}
}
diff --git a/interface/resources/qml/hifi/tablet/TabletMenu.qml b/interface/resources/qml/hifi/tablet/TabletMenu.qml
index 5f06e4fbab7..bc97b515c7d 100644
--- a/interface/resources/qml/hifi/tablet/TabletMenu.qml
+++ b/interface/resources/qml/hifi/tablet/TabletMenu.qml
@@ -1,6 +1,6 @@
import QtQuick 2.5
-import QtGraphicalEffects 1.0
-import QtQuick.Controls 1.4
+import Qt5Compat.GraphicalEffects
+import QtQuick.Controls 2.3
import QtQml 2.2
@@ -15,7 +15,7 @@ FocusScope {
width: parent.width
height: parent.height
- property var rootMenu: Menu { objectName:"rootMenu" }
+ property var rootMenu: WrappedMenu { objectName:"rootMenu" }
property var point: Qt.point(50, 50);
TabletMenuStack { id: menuPopperUpper }
property string subMenu: ""
diff --git a/interface/resources/qml/hifi/tablet/TabletMenuItem.qml b/interface/resources/qml/hifi/tablet/TabletMenuItem.qml
index 25db90c771a..a127c79161f 100644
--- a/interface/resources/qml/hifi/tablet/TabletMenuItem.qml
+++ b/interface/resources/qml/hifi/tablet/TabletMenuItem.qml
@@ -9,7 +9,7 @@
//
import QtQuick 2.5
-import QtQuick.Controls 1.4
+import QtQuick.Controls 2.3
import controlsUit 1.0
import stylesUit 1.0
diff --git a/interface/resources/qml/hifi/tablet/TabletMenuStack.qml b/interface/resources/qml/hifi/tablet/TabletMenuStack.qml
index 76d170cba84..2e427b016af 100644
--- a/interface/resources/qml/hifi/tablet/TabletMenuStack.qml
+++ b/interface/resources/qml/hifi/tablet/TabletMenuStack.qml
@@ -9,7 +9,7 @@
//
import QtQuick 2.5
-import QtQuick.Controls 1.4
+import QtQuick.Controls 2.3
import "."
diff --git a/interface/resources/qml/hifi/tablet/TabletRoot.qml b/interface/resources/qml/hifi/tablet/TabletRoot.qml
index 5e90696adc6..cc11fb7c617 100644
--- a/interface/resources/qml/hifi/tablet/TabletRoot.qml
+++ b/interface/resources/qml/hifi/tablet/TabletRoot.qml
@@ -27,30 +27,30 @@ Rectangle {
option = value;
}
- Component { id: inputDialogBuilder; TabletQueryDialog { } }
+ Component { id: inputDialogBuilder; Item {}}//TabletQueryDialog { } }
function inputDialog(properties) {
openModal = inputDialogBuilder.createObject(tabletRoot, properties);
return openModal;
}
- Component { id: messageBoxBuilder; TabletMessageBox { } }
+ Component { id: messageBoxBuilder; Item {}}//TabletMessageBox { } }
function messageBox(properties) {
openMessage = messageBoxBuilder.createObject(tabletRoot, properties);
return openMessage;
}
- Component { id: customInputDialogBuilder; TabletCustomQueryDialog { } }
+ Component { id: customInputDialogBuilder; Item {}}//TabletCustomQueryDialog { } }
function customInputDialog(properties) {
openModal = customInputDialogBuilder.createObject(tabletRoot, properties);
return openModal;
}
- Component { id: fileDialogBuilder; TabletFileDialog { } }
+ Component { id: fileDialogBuilder; Item { }}//TabletFileDialog { } }
function fileDialog(properties) {
openModal = fileDialogBuilder.createObject(tabletRoot, properties);
return openModal;
}
- Component { id: assetDialogBuilder; TabletAssetDialog { } }
+ Component { id: assetDialogBuilder; Item { }}//TabletAssetDialog { } }
function assetDialog(properties) {
openModal = assetDialogBuilder.createObject(tabletRoot, properties);
return openModal;
@@ -287,7 +287,7 @@ Rectangle {
}
width: 480
- height: 706
+ height: 720
function setShown(value) {
if (value === true) {
diff --git a/interface/resources/qml/hifi/tablet/WindowRoot.qml b/interface/resources/qml/hifi/tablet/WindowRoot.qml
index dcdc711936e..4f3d10aee92 100644
--- a/interface/resources/qml/hifi/tablet/WindowRoot.qml
+++ b/interface/resources/qml/hifi/tablet/WindowRoot.qml
@@ -11,11 +11,10 @@
// TODO: FIXME: this is practically identical to TabletRoot.qml
import "../../windows" as Windows
+import QtCore
import QtQuick 2.0
import Hifi 1.0
-import Qt.labs.settings 1.0
-
Windows.ScrollingWindow {
id: tabletRoot
objectName: "tabletRoot"
@@ -32,7 +31,7 @@ Windows.ScrollingWindow {
id: settings
category: "WindowRoot.Windows"
property real width: 480
- property real height: 706
+ property real height: 720
}
onResizableChanged: {
@@ -41,7 +40,7 @@ Windows.ScrollingWindow {
settings.width = tabletRoot.width
settings.height = tabletRoot.height
tabletRoot.width = 480
- tabletRoot.height = 706
+ tabletRoot.height = 720
} else {
tabletRoot.width = settings.width
tabletRoot.height = settings.height
@@ -205,7 +204,6 @@ Windows.ScrollingWindow {
}
}
-
implicitWidth: 480
- implicitHeight: 706
+ implicitHeight: 720
}
diff --git a/interface/resources/qml/hifi/tablet/tabletWindows/TabletFileDialog.qml b/interface/resources/qml/hifi/tablet/tabletWindows/TabletFileDialog.qml
index 91dc6fe5684..796a93fa39d 100644
--- a/interface/resources/qml/hifi/tablet/tabletWindows/TabletFileDialog.qml
+++ b/interface/resources/qml/hifi/tablet/tabletWindows/TabletFileDialog.qml
@@ -11,8 +11,8 @@
import QtQuick 2.7
import Qt.labs.folderlistmodel 2.2
import Qt.labs.settings 1.0
-import QtQuick.Dialogs 1.2 as OriginalDialogs
-import QtQuick.Controls 1.4 as QQC1
+import QtQuick.Dialogs as OriginalDialogs
+import QtQuick.Controls 2.3 as QQC1
import QtQuick.Controls 2.3
import ".."
@@ -466,15 +466,17 @@ Rectangle {
bottom: currentSelection.top
bottomMargin: hifi.dimensions.contentSpacing.y + currentSelection.controlHeight - currentSelection.height
}
- headerVisible: !selectDirectory
- onDoubleClicked: navigateToRow(row);
+ // QT6TODO: doesn't exist on Qt 6.
+ //headerVisible: !selectDirectory
+ //onDoubleClicked: navigateToRow(row);
focus: true
Keys.onReturnPressed: navigateToCurrentRow();
Keys.onEnterPressed: navigateToCurrentRow();
- sortIndicatorColumn: 0
- sortIndicatorOrder: Qt.AscendingOrder
- sortIndicatorVisible: true
+ // QT6TODO: doesn't exist on Qt 6.
+ //sortIndicatorColumn: 0
+ //sortIndicatorOrder: Qt.AscendingOrder
+ //sortIndicatorVisible: true
model: filesModel
@@ -484,11 +486,14 @@ Rectangle {
fileTableModel.update();
}
- onSortIndicatorColumnChanged: { updateSort(); }
+ // QT6TODO: doesn't exist on Qt 6.
+ //onSortIndicatorColumnChanged: { updateSort(); }
- onSortIndicatorOrderChanged: { updateSort(); }
+ // QT6TODO: doesn't exist on Qt 6.
+ //onSortIndicatorOrderChanged: { updateSort(); }
- itemDelegate: Item {
+ // QT6TODO: How to port this?
+ /*itemDelegate: Item {
clip: true
FiraSansSemiBold {
@@ -531,9 +536,10 @@ Rectangle {
return size + " " + suffixes[suffixIndex];
}
}
- }
+ }*/
- QQC1.TableViewColumn {
+ // QT6TODO: I have no idea how to port this
+ /*QQC1.TableViewColumn {
id: fileNameColumn
role: "fileName"
title: "Name"
@@ -557,7 +563,7 @@ Rectangle {
movable: false
resizable: true
visible: !selectDirectory
- }
+ }*/
function navigateToRow(row) {
currentRow = row;
diff --git a/interface/resources/qml/hifi/tablet/tabletWindows/preferences/Section.qml b/interface/resources/qml/hifi/tablet/tabletWindows/preferences/Section.qml
index 57fdeb482b7..2af1857e5cf 100644
--- a/interface/resources/qml/hifi/tablet/tabletWindows/preferences/Section.qml
+++ b/interface/resources/qml/hifi/tablet/tabletWindows/preferences/Section.qml
@@ -8,7 +8,7 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
-import QtQuick 2.5
+import QtQuick
import Hifi 1.0
import "../../../../dialogs/preferences"
diff --git a/interface/resources/qml/hifi/toolbars/Toolbar.qml b/interface/resources/qml/hifi/toolbars/Toolbar.qml
index 56028d71f05..b4db726566d 100644
--- a/interface/resources/qml/hifi/toolbars/Toolbar.qml
+++ b/interface/resources/qml/hifi/toolbars/Toolbar.qml
@@ -1,5 +1,5 @@
+import QtCore
import QtQuick 2.5
-import Qt.labs.settings 1.0
import "../../windows"
import "."
diff --git a/interface/resources/qml/hifi/toolbars/ToolbarButton.qml b/interface/resources/qml/hifi/toolbars/ToolbarButton.qml
index 303712b4f70..f503e93f0f3 100644
--- a/interface/resources/qml/hifi/toolbars/ToolbarButton.qml
+++ b/interface/resources/qml/hifi/toolbars/ToolbarButton.qml
@@ -1,5 +1,7 @@
import QtQuick 2.5
+// TODO: figure out a way of hooking these up to
+// the theme constants without too much breakage
StateImage {
id: button
diff --git a/interface/resources/qml/overte/BodyText.qml b/interface/resources/qml/overte/BodyText.qml
new file mode 100644
index 00000000000..303301567bd
--- /dev/null
+++ b/interface/resources/qml/overte/BodyText.qml
@@ -0,0 +1,29 @@
+import QtQuick
+import QtQuick.Controls
+
+import "."
+
+TextEdit {
+ selectByMouse: true
+ readOnly: true
+
+ font.pixelSize: Theme.fontPixelSize
+ font.family: Theme.bodyFontFamily
+ color: Theme.paletteActive.text
+
+ textFormat: TextEdit.PlainText
+ wrapMode: TextEdit.Wrap
+
+ selectedTextColor: Theme.paletteActive.highlightedText
+ selectionColor: Theme.paletteActive.highlight
+
+ // TODO: should we continue supporting the in-game browser
+ // or should we transition to always using the system one?
+ // Qt doesn't make it easy to theme rich text
+ onLinkActivated: link => Qt.openUrlExternally(link)
+
+ HoverHandler {
+ enabled: parent.hoveredLink
+ cursorShape: Qt.PointingHandCursor
+ }
+}
diff --git a/interface/resources/qml/overte/Button.qml b/interface/resources/qml/overte/Button.qml
new file mode 100644
index 00000000000..bcc255b0fbb
--- /dev/null
+++ b/interface/resources/qml/overte/Button.qml
@@ -0,0 +1,55 @@
+import QtQuick
+import QtQuick.Controls
+import "."
+
+Button {
+ id: button
+ property color backgroundColor: Theme.paletteActive.button
+ property color color: Theme.paletteActive.buttonText
+
+ palette.buttonText: color
+ font.family: Theme.fontFamily
+ font.pixelSize: Theme.fontPixelSize
+ horizontalPadding: 12
+ verticalPadding: 8
+ hoverEnabled: true
+
+ opacity: enabled ? 1.0 : 0.5
+
+ background: Rectangle {
+ opacity: flat ? 0.0 : 1.0
+
+ id: buttonBg
+ radius: Theme.borderRadius
+ border.width: button.activeFocus ? Theme.borderWidthFocused : Theme.borderWidth
+ border.color: (
+ button.activeFocus ?
+ Theme.paletteActive.focusRing :
+ (
+ Theme.highContrast ?
+ Theme.paletteActive.buttonText :
+ Qt.darker(button.backgroundColor, Theme.borderDarker)
+ )
+ )
+ color: {
+ if (button.down || button.checked) {
+ return Qt.darker(button.backgroundColor, Theme.checkedDarker);
+ } else if (button.hovered && button.enabled) {
+ return Qt.lighter(button.backgroundColor, Theme.hoverLighter);
+ } else {
+ return button.backgroundColor;
+ }
+ }
+ gradient: Gradient {
+ GradientStop {
+ position: 0.0; color: Qt.lighter(buttonBg.color, (button.down || button.checked) ? 0.9 : 1.1)
+ }
+ GradientStop {
+ position: 0.5; color: buttonBg.color
+ }
+ GradientStop {
+ position: 1.0; color: Qt.darker(buttonBg.color, (button.down || button.checked) ? 0.9 : 1.1)
+ }
+ }
+ }
+}
diff --git a/interface/resources/qml/overte/ComboBox.qml b/interface/resources/qml/overte/ComboBox.qml
new file mode 100644
index 00000000000..89a6741ad93
--- /dev/null
+++ b/interface/resources/qml/overte/ComboBox.qml
@@ -0,0 +1,141 @@
+import QtQuick
+import QtQuick.Controls
+import "."
+
+ComboBox {
+ id: control
+
+ property color backgroundColor: flat ? "#00000000" : Theme.paletteActive.window
+ property color color: Theme.paletteActive.windowText
+
+ implicitHeight: Theme.fontPixelSize * 2
+
+ font.pixelSize: Theme.fontPixelSize
+ font.family: Theme.fontFamily
+
+ indicator: Button {
+ anchors.right: control.right
+ width: control.height
+ height: control.height
+
+ horizontalPadding: 2
+ verticalPadding: 2
+ focusPolicy: Qt.NoFocus
+ flat: control.flat
+
+ icon.source: "./icons/triangle_down.svg"
+ icon.width: 24
+ icon.height: 24
+ icon.color: Theme.paletteActive.buttonText
+
+ onClicked: {
+ control.forceActiveFocus();
+
+ if (control.popup.opened) {
+ control.popup.close();
+ } else {
+ control.popup.open();
+ }
+ }
+ }
+
+ contentItem: Text {
+ leftPadding: 6
+ rightPadding: control.indicator.width + control.spacing
+ text: control.displayText
+ font: control.font
+ color: control.color
+ verticalAlignment: Text.AlignVCenter
+ elide: Text.ElideRight
+ opacity: enabled ? 1.0 : 0.5
+ }
+
+ background: Rectangle {
+ opacity: enabled ? 1.0 : 0.5
+ width: (control.width - control.indicator.width) + Theme.borderWidth
+ radius: Theme.borderRadius
+ border.width: control.activeFocus ? Theme.borderWidthFocused : Theme.borderWidth
+ border.color: {
+ if (control.activeFocus) {
+ return Theme.paletteActive.focusRing;
+ } else if (control.flat) {
+ return "#00000000";
+ } else if (Theme.highContrast) {
+ return parent.color;
+ } else {
+ return Qt.darker(control.backgroundColor, Theme.borderDarker);
+ }
+ }
+ gradient: Gradient {
+ GradientStop { position: 0.0; color: Qt.darker(control.backgroundColor, control.flat ? 1.0 : 1.02) }
+ GradientStop { position: 0.5; color: control.backgroundColor }
+ GradientStop { position: 1.0; color: Qt.lighter(control.backgroundColor, control.flat ? 1.0 : 1.02) }
+ }
+ }
+
+ delegate: ItemDelegate {
+ id: delegate
+
+ required property var model
+ required property int index
+
+ width: control.width
+ contentItem: Text {
+ text: delegate.model[control.textRole]
+ color: highlighted ? Theme.paletteActive.highlightedText : Theme.paletteActive.tooltipText
+ font: control.font
+ elide: Text.ElideRight
+ verticalAlignment: Text.AlignVCenter
+ }
+
+ background: Rectangle {
+ color: highlighted ? Theme.paletteActive.highlight : Theme.paletteActive.tooltip
+ }
+
+ highlighted: control.highlightedIndex === index
+ }
+
+ popup: Popup {
+ y: control.height - Theme.borderWidth
+ width: control.width + (contentItem.ScrollBar.vertical.opacity > 0.0 ? contentItem.ScrollBar.vertical.width : 0)
+ height: Math.min(contentItem.implicitHeight, control.Window.height - topMargin - bottomMargin)
+ padding: 3
+
+ contentItem: ListView {
+ clip: true
+ implicitHeight: contentHeight + (parent.padding * 2)
+ model: control.popup.visible ? control.delegateModel : null
+ currentIndex: control.highlightedIndex
+
+ ScrollBar.vertical: ScrollBar {
+ interactive: false
+ }
+ }
+
+ background: Item {
+ // drop shadow
+ Rectangle {
+ x: 3
+ y: 3
+ width: parent.width
+ height: parent.height
+
+ radius: Theme.borderRadius
+ color: "#a0000000"
+ }
+
+ Rectangle {
+ x: 0
+ y: 0
+ width: parent.width
+ height: parent.height
+
+ radius: Theme.borderRadius
+ border.width: Theme.borderWidth
+ border.color: Qt.darker(Theme.paletteActive.tooltip, 2.5)
+ color: Theme.paletteActive.tooltip
+ }
+ }
+ }
+}
+
diff --git a/interface/resources/qml/overte/Dialog.qml b/interface/resources/qml/overte/Dialog.qml
new file mode 100644
index 00000000000..6df695b25bb
--- /dev/null
+++ b/interface/resources/qml/overte/Dialog.qml
@@ -0,0 +1,81 @@
+import QtQuick
+import QtQuick.Layouts
+import QtQuick.Dialogs
+
+import "."
+
+Rectangle {
+ id: dialog
+ visible: false
+
+ default property alias content: dialogWindow.children
+
+ property int maxWidth: 480
+ property int maxHeight: -1
+
+ property bool shadedBackground: true
+
+ function open() {
+ visible = true;
+ opacity = Theme.reducedMotion ? 1 : 0;
+ }
+
+ function close() {
+ visible = false;
+ opacity = 0;
+ }
+
+ color: !shadedBackground ? "transparent" : Theme.paletteActive.dialogShade
+
+ opacity: Theme.reducedMotion ? 1 : 0
+ OpacityAnimator on opacity {
+ from: Theme.reducedMotion ? 1 : 0
+ to: 1
+ duration: 150
+ easing.type: Easing.InQuad
+ running: dialog.visible
+ }
+
+ // block any inputs from underneath
+ MouseArea {
+ enabled: parent.visible
+ anchors.fill: parent
+ hoverEnabled: true
+ }
+
+ Rectangle {
+ id: dialogWindow
+ width: Math.min(
+ (maxWidth == -1 ? Infinity : maxWidth),
+ children[0].implicitWidth + (children[0].anchors.margins * 2),
+ parent.width - 8
+ )
+ height: Math.min(
+ (maxHeight == -1 ? Infinity : maxHeight),
+ children[0].implicitHeight + (children[0].anchors.margins * 2),
+ parent.height - 8
+ )
+ anchors.centerIn: parent
+
+ color: Theme.paletteActive.base
+ radius: 8
+ border.width: 2
+ border.color: Theme.highContrast ? Theme.paletteActive.text : Qt.darker(Theme.paletteActive.base, Theme.borderDarker)
+
+ OpacityAnimator on opacity {
+ from: Theme.reducedMotion ? 1 : 0
+ to: 1
+ easing.type: Easing.OutQuad
+ duration: 200
+ running: dialog.visible
+ }
+
+ ScaleAnimator on scale {
+ from: Theme.reducedMotion ? 1 : 0.9
+ to: 1
+ easing.type: Easing.OutQuad
+ duration: 200
+ running: dialog.visible
+ }
+ }
+}
diff --git a/interface/resources/qml/overte/Label.qml b/interface/resources/qml/overte/Label.qml
new file mode 100644
index 00000000000..86fc51694cd
--- /dev/null
+++ b/interface/resources/qml/overte/Label.qml
@@ -0,0 +1,9 @@
+import QtQuick
+import QtQuick.Controls
+import "."
+
+Label {
+ font.pixelSize: Theme.fontPixelSize
+ font.family: Theme.fontFamily
+ color: Theme.paletteActive.text
+}
diff --git a/interface/resources/qml/overte/MessageDialog.qml b/interface/resources/qml/overte/MessageDialog.qml
new file mode 100644
index 00000000000..33cd5dcde0d
--- /dev/null
+++ b/interface/resources/qml/overte/MessageDialog.qml
@@ -0,0 +1,114 @@
+import QtQuick
+import QtQuick.Layouts
+import QtQuick.Dialogs
+
+import "."
+
+Dialog {
+ id: root
+ implicitWidth: layout.implicitWidth
+ implicitHeight: layout.implicitHeight
+
+ readonly property var buttonRoles: [
+ { label: qsTr("Ok"), flag: MessageDialog.Ok, role: MessageDialog.AcceptRole },
+ { label: qsTr("Save"), flag: MessageDialog.Save, role: MessageDialog.AcceptRole },
+ { label: qsTr("Yes"), flag: MessageDialog.Yes, role: MessageDialog.YesRole },
+ { label: qsTr("Cancel"), flag: MessageDialog.Cancel, role: MessageDialog.RejectRole },
+ { label: qsTr("Discard"), flag: MessageDialog.Discard, role: MessageDialog.DestructiveRole },
+ { label: qsTr("No"), flag: MessageDialog.No, role: MessageDialog.NoRole },
+ ]
+
+ property string text: "Oops! Your ModalDialog doesn't have any text."
+ property string descriptiveText: ""
+ property int buttons: MessageDialog.Ok | MessageDialog.Cancel
+ property int result: MessageDialog.Ok
+
+ property list buttonDataModel: {
+ let list = [];
+
+ for (let role of buttonRoles) {
+ if ((role.flag & buttons) !== 0) {
+ list.push(role);
+ }
+ }
+
+ return list.reverse();
+ }
+
+ signal accepted
+ signal rejected
+ signal buttonClicked(button: int, role: int)
+
+ function reject() {
+ rejected();
+ close();
+ }
+
+ function accept() {
+ accepted();
+ close();
+ }
+
+ onButtonClicked: (button, role) => {
+ result = button;
+
+ switch (role) {
+ case MessageDialog.AcceptRole:
+ case MessageDialog.YesRole:
+ accept();
+ break;
+
+ case MessageDialog.RejectRole:
+ case MessageDialog.NoRole:
+ reject();
+ break;
+ }
+ }
+
+ ColumnLayout {
+ id: layout
+ anchors.fill: parent
+ anchors.margins: 8
+
+ Label {
+ Layout.fillWidth: true
+ Layout.fillHeight: true
+ Layout.margins: 8
+
+ visible: root.text !== ""
+ text: root.text
+ wrapMode: Text.Wrap
+ }
+
+ Label {
+ Layout.fillWidth: true
+ Layout.fillHeight: true
+ Layout.margins: 8
+
+ visible: root.descriptiveText !== ""
+ text: root.descriptiveText
+ wrapMode: Text.Wrap
+ }
+
+ RowLayout {
+ Layout.fillWidth: true
+ Layout.alignment: Qt.AlignRight
+
+ Repeater {
+ model: buttonDataModel
+ delegate: Button {
+ required property string label
+ required property int flag
+ required property int role
+
+ Layout.fillWidth: true
+ Layout.minimumWidth: 96
+ Layout.preferredWidth: 96
+
+ text: label
+ onClicked: buttonClicked(flag, role);
+ }
+ }
+ }
+ }
+}
diff --git a/interface/resources/qml/overte/README.md b/interface/resources/qml/overte/README.md
new file mode 100644
index 00000000000..4fb88540c35
--- /dev/null
+++ b/interface/resources/qml/overte/README.md
@@ -0,0 +1,19 @@
+# TODO List
+
+## Critical
+- [ ] Virtual keyboard support for text fields
+- [x] Asset browser
+- [x] Running scripts window
+- [ ] Actually saving the color scheme and contrast settings
+
+## High
+- [x] Avatar picker
+- [x] People app
+- [x] World list
+
+## Low
+- [x] More app
+- [ ] Desktop apps bar/tablet app picker
+- [ ] Create app
+- [ ] Dashboard
+- [ ] Hook up chat bubbles background opacity to high contrast mode
diff --git a/interface/resources/qml/overte/RoundButton.qml b/interface/resources/qml/overte/RoundButton.qml
new file mode 100644
index 00000000000..5deec714cde
--- /dev/null
+++ b/interface/resources/qml/overte/RoundButton.qml
@@ -0,0 +1,52 @@
+import QtQuick
+import QtQuick.Controls
+import "."
+
+Button {
+ id: button
+ property color backgroundColor: Theme.paletteActive.button
+
+ palette.buttonText: Theme.paletteActive.buttonText
+ font.family: Theme.fontFamily
+ font.pixelSize: Theme.fontPixelSize
+ horizontalPadding: 2
+ verticalPadding: 2
+ hoverEnabled: true
+ implicitHeight: font.pixelSize * 2
+ implicitWidth: font.pixelSize * 2
+
+ background: Rectangle {
+ id: buttonBg
+ radius: button.height / 2
+ border.width: parent.activeFocus ? Theme.borderWidthFocused : Theme.borderWidth
+ border.color: (
+ button.activeFocus ?
+ Theme.paletteActive.focusRing :
+ (
+ Theme.highContrast ?
+ Theme.paletteActive.buttonText :
+ Qt.darker(button.backgroundColor, Theme.borderDarker)
+ )
+ )
+ color: (
+ (button.down || button.checked) ?
+ Qt.darker(button.backgroundColor, Theme.checkedDarker) :
+ (
+ (button.hovered && button.enabled) ?
+ Qt.lighter(button.backgroundColor, Theme.hoverLighter) :
+ button.backgroundColor
+ )
+ )
+ gradient: Gradient {
+ GradientStop {
+ position: 0.0; color: Qt.lighter(buttonBg.color, (button.down || button.checked) ? 0.9 : 1.1)
+ }
+ GradientStop {
+ position: 0.5; color: buttonBg.color
+ }
+ GradientStop {
+ position: 1.0; color: Qt.darker(buttonBg.color, (button.down || button.checked) ? 0.9 : 1.1)
+ }
+ }
+ }
+}
diff --git a/interface/resources/qml/overte/Ruler.qml b/interface/resources/qml/overte/Ruler.qml
new file mode 100644
index 00000000000..522c6208169
--- /dev/null
+++ b/interface/resources/qml/overte/Ruler.qml
@@ -0,0 +1,29 @@
+import QtQuick
+
+import "."
+
+Item {
+ property color color: (
+ Theme.highContrast ?
+ Theme.paletteActive.text :
+ Theme.paletteActive.base
+ )
+
+ implicitHeight: 4
+
+ Rectangle {
+ x: 0
+ y: 0
+ height: Math.floor(parent.height / 2)
+ width: parent.width
+ color: Qt.darker(parent.color, Theme.depthDarker)
+ }
+
+ Rectangle {
+ x: 0
+ y: Math.floor(parent.height / 2)
+ height: Math.floor(parent.height / 2)
+ width: parent.width
+ color: Qt.lighter(parent.color, Theme.depthLighter)
+ }
+}
diff --git a/interface/resources/qml/overte/ScrollBar.qml b/interface/resources/qml/overte/ScrollBar.qml
new file mode 100644
index 00000000000..6fa5d7f985c
--- /dev/null
+++ b/interface/resources/qml/overte/ScrollBar.qml
@@ -0,0 +1,150 @@
+import QtQuick
+import QtQuick.Controls
+import "."
+
+ScrollBar {
+ id: control
+ opacity: {
+ if (
+ control.policy === ScrollBar.AlwaysOn ||
+ (control.policy === ScrollBar.AsNeeded && interactive && control.size < 1.0)
+ ) {
+ return 1.0;
+ } else if (control.active && control.size < 1.0) {
+ return 0.75;
+ } else {
+ return 0.0;
+ }
+ }
+
+ Behavior on opacity {
+ NumberAnimation {}
+ }
+
+ property color backgroundColor: Theme.paletteActive.button
+
+ readonly property bool hasButtons: Theme.scrollbarButtons && interactive
+ readonly property int thumbWidth: Theme.scrollbarWidth - (horizontalPadding * 2)
+ readonly property bool isHorizontal: orientation === Qt.Horizontal
+
+ //horizontalPadding: Theme.borderWidth
+ //verticalPadding: Theme.borderWidth
+ horizontalPadding: 0
+ verticalPadding: 0
+
+ // TODO: magic numbers? minimumSize is weird and confusing,
+ // 0.32 doesn't work on short scrollbars
+ stepSize: 0.03
+ minimumSize: 0.32
+
+ background: Rectangle {
+ color: Qt.darker(
+ Theme.paletteActive.base,
+ Theme.highContrast ? 1.0 : (Theme.darkMode ? 1.2 : 1.1)
+ )
+ implicitWidth: Theme.scrollbarWidth
+ implicitHeight: Theme.scrollbarWidth
+ }
+
+ contentItem: Item {
+ implicitWidth: horizontal ? 32 : thumbWidth
+ implicitHeight: horizontal ? thumbWidth : 32
+
+ Rectangle {
+ // pad away from the scroll buttons, but allow a border width of sink-in
+ // so there isn't a double-border when the thumbs are at their min/max
+ anchors.topMargin: control.hasButtons && !control.isHorizontal ? thumbWidth - Theme.borderWidth : 0
+ anchors.bottomMargin: control.hasButtons && !control.isHorizontal ? thumbWidth - Theme.borderWidth: 0
+ anchors.leftMargin: control.hasButtons && control.isHorizontal ? thumbWidth - Theme.borderWidth: 0
+ anchors.rightMargin: control.hasButtons && control.isHorizontal ? thumbWidth - Theme.borderWidth : 0
+ anchors.fill: parent
+
+ id: buttonBg
+ radius: Theme.borderRadius
+ border.width: Theme.borderWidth
+ border.color: Theme.highContrast ? Theme.paletteActive.buttonText : Qt.darker(control.backgroundColor, Theme.borderDarker)
+ color: Theme.highContrast ? Theme.paletteActive.buttonText : control.backgroundColor;
+ gradient: Gradient {
+ GradientStop {
+ position: 0.0; color: Qt.lighter(buttonBg.color, 1.05)
+ }
+ GradientStop {
+ position: 0.5; color: buttonBg.color
+ }
+ GradientStop {
+ position: 1.0; color: Qt.darker(buttonBg.color, 1.05)
+ }
+ }
+ }
+ }
+
+ Button {
+ anchors.right: !control.isHorizontal ? control.right : undefined
+ anchors.rightMargin: !control.isHorizontal ? control.horizontalPadding : undefined
+
+ anchors.left: control.isHorizontal ? control.left : undefined
+ anchors.leftMargin: control.isHorizontal ? control.horizontalPadding : undefined
+
+ anchors.top: control.top
+ anchors.topMargin: control.verticalPadding
+
+ width: thumbWidth
+ height: width
+
+ id: scrollLessButton
+ visible: control.hasButtons
+ focusPolicy: Qt.NoFocus
+
+ icon.source: (
+ !control.isHorizontal ?
+ "./icons/triangle_up.svg" :
+ "./icons/triangle_left.svg"
+ )
+ icon.width: width - 4
+ icon.height: height - 4
+ icon.color: Theme.paletteActive.buttonText
+ display: AbstractButton.IconOnly
+ horizontalPadding: 0
+ verticalPadding: 0
+ autoRepeat: true
+
+ onClicked: {
+ control.position = Math.max(0.0, control.position - control.stepSize);
+ }
+ }
+
+ Button {
+ anchors.right: control.isHorizontal ? control.right : undefined
+ anchors.rightMargin: control.isHorizontal ? control.horizontalPadding : undefined
+
+ anchors.left: !control.isHorizontal ? control.left : undefined
+ anchors.leftMargin: !control.isHorizontal ? control.horizontalPadding : undefined
+
+ anchors.bottom: control.bottom
+ anchors.bottomMargin: control.verticalPadding
+
+ width: thumbWidth
+ height: width
+
+ id: scrollMoreButton
+ visible: control.hasButtons
+ focusPolicy: Qt.NoFocus
+ display: AbstractButton.IconOnly
+ horizontalPadding: 0
+ verticalPadding: 0
+ autoRepeat: true
+
+ icon.source: (
+ !control.isHorizontal ?
+ "./icons/triangle_down.svg" :
+ "./icons/triangle_right.svg"
+ )
+ icon.width: width - 4
+ icon.height: height - 4
+ icon.color: Theme.paletteActive.buttonText
+
+ onClicked: {
+ control.position = Math.min(1.0 - control.size, control.position + control.stepSize);
+ }
+ }
+}
diff --git a/interface/resources/qml/overte/Slider.qml b/interface/resources/qml/overte/Slider.qml
new file mode 100644
index 00000000000..89ce00528e7
--- /dev/null
+++ b/interface/resources/qml/overte/Slider.qml
@@ -0,0 +1,70 @@
+import QtQuick
+import QtQuick.Controls
+
+import "."
+
+Slider {
+ id: control
+
+ background: Rectangle {
+ y: control.topPadding + control.availableHeight / 2 - height / 2
+ implicitWidth: 200
+ height: 8
+ radius: height / 2
+ color: Theme.paletteActive.base
+
+ border.width: Theme.borderWidth
+ border.color: Qt.darker(color, Theme.borderDarker)
+
+ Rectangle {
+ width: control.visualPosition * parent.width
+ height: parent.height
+ radius: parent.radius
+ color: Theme.paletteActive.highlight
+
+ border.color: Qt.darker(color, Theme.borderDarker)
+ border.width: Theme.borderWidth
+ }
+ }
+
+ handle: Rectangle {
+ implicitWidth: 26
+ implicitHeight: 26
+ radius: height / 2
+
+ x: control.leftPadding + control.visualPosition * (control.availableWidth - width)
+ y: control.topPadding + control.availableHeight / 2 - height / 2
+
+ id: handle
+ border.width: control.activeFocus ? Theme.borderWidthFocused : Theme.borderWidth
+ border.color: (
+ control.activeFocus ?
+ Theme.paletteActive.focusRing :
+ (
+ Theme.highContrast ?
+ Theme.paletteActive.buttonText :
+ Qt.darker(Theme.paletteActive.button, Theme.borderDarker)
+ )
+ )
+ color: {
+ if (control.hovered && control.enabled) {
+ return Qt.lighter(Theme.paletteActive.button, Theme.hoverLighter);
+ } else if (!control.enabled) {
+ return Theme.paletteActive.base;
+ } else {
+ return Theme.paletteActive.button;
+ }
+ }
+ gradient: Gradient {
+ GradientStop {
+ position: 0.0; color: Qt.lighter(handle.color, control.enabled ? 1.1 : 1.0)
+ }
+ GradientStop {
+ position: 0.5; color: handle.color
+ }
+ GradientStop {
+ position: 1.0; color: Qt.darker(handle.color, control.enabled ? 1.1 : 1.0)
+ }
+ }
+ }
+}
diff --git a/interface/resources/qml/overte/SpinBox.qml b/interface/resources/qml/overte/SpinBox.qml
new file mode 100644
index 00000000000..79ef1f16d4c
--- /dev/null
+++ b/interface/resources/qml/overte/SpinBox.qml
@@ -0,0 +1,143 @@
+import QtQuick
+import QtQuick.Controls
+
+import "."
+
+SpinBox {
+ id: control
+
+ font.pixelSize: Theme.fontPixelSize
+ font.family: Theme.fontFamily
+
+ background: Rectangle {
+ anchors.left: control.down.indicator.right
+ anchors.right: control.up.indicator.left
+ anchors.leftMargin: -Theme.borderWidth
+ anchors.rightMargin: -Theme.borderWidth
+
+ implicitHeight: Theme.fontPixelSize * 2
+ implicitWidth: 180
+ // no radius so the borders can mix into the scroll button borders
+ //radius: Theme.borderRadius
+ border.width: control.activeFocus ? Theme.borderWidthFocused : Theme.borderWidth
+ border.color: (
+ parent.activeFocus ?
+ Theme.paletteActive.focusRing :
+ (
+ Theme.highContrast ?
+ Theme.paletteActive.windowText :
+ Qt.darker(Theme.paletteActive.window, Theme.borderDarker)
+ )
+ )
+ color: Theme.paletteActive.window
+ gradient: Gradient {
+ GradientStop { position: 0.0; color: Qt.darker(control.background.color, 1.02) }
+ GradientStop { position: 0.5; color: control.background.color }
+ GradientStop { position: 1.0; color: Qt.lighter(control.background.color, 1.02) }
+ }
+ }
+
+ contentItem: TextInput {
+ anchors.left: control.down.indicator.right
+ anchors.right: control.up.indicator.left
+
+ font: control.font
+ color: Theme.paletteActive.windowText
+ selectionColor: Theme.paletteActive.highlight
+ selectedTextColor: Theme.paletteActive.highlightedText
+ text: control.textFromValue(control.value, control.locale)
+
+ horizontalAlignment: Qt.AlignHCenter
+ verticalAlignment: Qt.AlignVCenter
+
+ readOnly: !control.editable
+ validator: control.validator
+ inputMethodHints: control.inputMethodHints
+ }
+
+ down.indicator: Rectangle{
+ id: downIndicator
+ x: 0
+ width: Theme.fontPixelSize * 2
+ height: parent.height
+ color: (
+ control.down.down ?
+ Qt.darker(Theme.paletteActive.button, Theme.checkedDarker) :
+ (
+ (control.down.hovered && control.enabled) ?
+ Qt.lighter(Theme.paletteActive.button, Theme.hoverLighter) :
+ Theme.paletteActive.button
+ )
+ )
+ opacity: control.value > control.from ? 1.0 : 0.5
+
+ radius: 0
+ topLeftRadius: Theme.borderRadius
+ bottomLeftRadius: Theme.borderRadius
+
+ border.width: Theme.borderWidth
+ border.color: (
+ Theme.highContrast ?
+ Theme.paletteActive.windowText :
+ Qt.darker(Theme.paletteActive.button, Theme.borderDarker)
+ )
+
+ gradient: Gradient {
+ GradientStop { position: 0.0; color: Qt.lighter(downIndicator.color, 1.05) }
+ GradientStop { position: 0.5; color: downIndicator.color }
+ GradientStop { position: 1.0; color: Qt.darker(downIndicator.color, 1.05) }
+ }
+
+ Text {
+ anchors.fill: parent
+ horizontalAlignment: Qt.AlignHCenter
+ verticalAlignment: Qt.AlignVCenter
+ color: Theme.paletteActive.buttonText
+ font: control.font
+ text: "-"
+ }
+ }
+
+ up.indicator: Rectangle{
+ id: upIndicator
+ x: parent.width - width
+ width: Theme.fontPixelSize * 2
+ height: parent.height
+ color: (
+ control.up.down ?
+ Qt.darker(Theme.paletteActive.button, Theme.checkedDarker) :
+ (
+ (control.up.hovered && control.enabled) ?
+ Qt.lighter(Theme.paletteActive.button, Theme.hoverLighter) :
+ Theme.paletteActive.button
+ )
+ )
+ opacity: control.value < control.to ? 1.0 : 0.5
+
+ radius: 0
+ topRightRadius: Theme.borderRadius
+ bottomRightRadius: Theme.borderRadius
+
+ border.width: Theme.borderWidth
+ border.color: (
+ Theme.highContrast ?
+ Theme.paletteActive.windowText :
+ Qt.darker(Theme.paletteActive.button, Theme.borderDarker)
+ )
+
+ gradient: Gradient {
+ GradientStop { position: 0.0; color: Qt.lighter(upIndicator.color, 1.05) }
+ GradientStop { position: 0.5; color: upIndicator.color }
+ GradientStop { position: 1.0; color: Qt.darker(upIndicator.color, 1.05) }
+ }
+
+ Text {
+ anchors.fill: parent
+ horizontalAlignment: Qt.AlignHCenter
+ verticalAlignment: Qt.AlignVCenter
+ color: Theme.paletteActive.buttonText
+ font: control.font
+ text: "+"
+ }
+ }
+}
diff --git a/interface/resources/qml/overte/StackView.qml b/interface/resources/qml/overte/StackView.qml
new file mode 100644
index 00000000000..197a5931107
--- /dev/null
+++ b/interface/resources/qml/overte/StackView.qml
@@ -0,0 +1,74 @@
+import QtQuick
+import QtQuick.Controls
+
+import "."
+
+// a stack view with reasonable defaults for the
+// animations, respecting the theme reduced motion settings
+StackView {
+ pushEnter: Transition {
+ PropertyAnimation {
+ property: "opacity"
+ from: Theme.reducedMotion ? 0 : 1
+ to: 1
+ duration: 80
+ }
+
+ PropertyAnimation {
+ property: "x"
+ from: Theme.reducedMotion ? 0 : width
+ to: 0
+ duration: 350
+ easing.type: Easing.OutQuad
+ }
+ }
+ pushExit: Transition {
+ PropertyAnimation {
+ property: "opacity"
+ from: 1
+ to: Theme.reducedMotion ? 0 : 1
+ duration: 80
+ }
+
+ PropertyAnimation {
+ property: "x"
+ to: Theme.reducedMotion ? 0 : -width
+ from: 0
+ duration: 350
+ easing.type: Easing.OutQuad
+ }
+ }
+
+ popEnter: Transition {
+ PropertyAnimation {
+ property: "opacity"
+ from: Theme.reducedMotion ? 0 : 1
+ to: 1
+ duration: 80
+ }
+
+ PropertyAnimation {
+ property: "x"
+ from: Theme.reducedMotion ? 0 : -width
+ to: 0
+ duration: 350
+ easing.type: Easing.OutQuad
+ }
+ }
+ popExit: Transition {
+ PropertyAnimation {
+ property: "opacity"
+ from: 1
+ to: Theme.reducedMotion ? 0 : 1
+ duration: 80
+ }
+
+ PropertyAnimation {
+ property: "x"
+ to: Theme.reducedMotion ? 0 : width
+ from: 0
+ duration: 350
+ easing.type: Easing.OutQuad
+ }
+ }
+}
diff --git a/interface/resources/qml/overte/Switch.qml b/interface/resources/qml/overte/Switch.qml
new file mode 100644
index 00000000000..5ff772162ed
--- /dev/null
+++ b/interface/resources/qml/overte/Switch.qml
@@ -0,0 +1,74 @@
+import QtQuick
+import QtQuick.Controls
+import "."
+
+Switch {
+ id: control
+ font.pixelSize: Theme.fontPixelSize
+ font.family: Theme.fontFamily
+ opacity: control.enabled ? 1.0 : 0.5
+
+ Rectangle {
+ anchors.fill: indicator
+ anchors.margins: -Theme.borderWidthFocused
+ color: Theme.paletteActive.focusRing
+ visible: control.activeFocus
+ radius: indicator.radius
+ }
+
+ indicator: Rectangle {
+ implicitWidth: 48
+ implicitHeight: 24
+ x: control.leftPadding
+ y: parent.height / 2 - height / 2
+ radius: 24
+ color: {
+ if (Theme.highContrast) {
+ return control.checked ? Theme.paletteActive.buttonText : Theme.paletteActive.button;
+ } else if (control.checked) {
+ return Theme.paletteActive.highlight;
+ } else {
+ return Qt.darker(Theme.paletteActive.base, Theme.checkedDarker);
+ }
+ }
+ border.color: (
+ Theme.highContrast ?
+ Theme.paletteActive.buttonText :
+ Qt.darker(color, Theme.borderDarker)
+ )
+ border.width: Theme.borderWidth
+
+ Rectangle {
+ x: control.checked ? parent.width - width : 0
+ width: 24
+ height: 24
+ radius: 24
+ color: (
+ control.down ?
+ Qt.darker(Theme.paletteActive.button, Theme.depthDarker) :
+ (
+ control.hovered && control.enabled ?
+ Qt.lighter(Theme.paletteActive.button, Theme.depthLighter) :
+ Theme.paletteActive.button
+ )
+ );
+ border.color: {
+ if (Theme.highContrast) {
+ return Theme.paletteActive.buttonText;
+ } else {
+ return Qt.darker(Theme.paletteActive.button, Theme.borderDarker);
+ }
+ }
+ border.width: Theme.borderWidth
+ }
+ }
+
+ contentItem: Text {
+ text: control.text
+ font: control.font
+ color: Theme.paletteActive.text
+ opacity: enabled ? 1.0 : 0.3
+ verticalAlignment: Text.AlignVCenter
+ leftPadding: control.indicator.width + control.spacing
+ }
+}
diff --git a/interface/resources/qml/overte/TabBar.qml b/interface/resources/qml/overte/TabBar.qml
new file mode 100644
index 00000000000..9f7a3c60fec
--- /dev/null
+++ b/interface/resources/qml/overte/TabBar.qml
@@ -0,0 +1,22 @@
+import QtQuick
+import QtQuick.Controls
+import "."
+
+TabBar {
+ id: tabBar
+ spacing: 2
+ clip: true
+
+ background: Item {
+ Rectangle { anchors.fill: parent; color: Theme.paletteActive.base }
+ Rectangle {
+ anchors.left: parent.left
+ anchors.right: parent.right
+ anchors.bottom: parent.bottom
+ height: Theme.borderWidth
+ color: Qt.darker(Theme.paletteActive.base)
+ }
+ }
+
+ implicitHeight: Theme.fontPixelSize + 16
+}
diff --git a/interface/resources/qml/overte/TabButton.qml b/interface/resources/qml/overte/TabButton.qml
new file mode 100644
index 00000000000..a3a4bb550fc
--- /dev/null
+++ b/interface/resources/qml/overte/TabButton.qml
@@ -0,0 +1,56 @@
+import QtQuick
+import QtQuick.Controls
+import "."
+
+TabButton {
+ id: button
+ property color backgroundColor: Theme.paletteActive.base
+
+ readonly property int borderWidth: checked ? Theme.borderWidth * 2 : Theme.borderWidth
+
+ font.family: Theme.fontFamily
+ font.pixelSize: Theme.fontPixelSize
+ horizontalPadding: 12
+ verticalPadding: 8
+
+ anchors.top: parent.top
+ anchors.bottom: parent.bottom
+ anchors.topMargin: checked ? 2 : 6
+ anchors.bottomMargin: -borderWidth
+ implicitHeight: Theme.fontPixelSize + 16
+
+ contentItem: Text {
+ text: button.text
+ font: button.font
+ color: Theme.paletteActive.text
+ opacity: enabled ? 1.0 : 0.3
+ horizontalAlignment: Text.AlignHCenter
+ verticalAlignment: Text.AlignVCenter
+ elide: Text.ElideRight
+ }
+
+ background: Rectangle {
+ id: buttonBg
+ border.width: parent.borderWidth
+ border.color: {
+ if (parent.activeFocus) {
+ return Theme.paletteActive.focusRing;
+ } else if (parent.checked) {
+ return Theme.paletteActive.highlight;
+ } else if (Theme.highContrast) {
+ return Theme.paletteActive.buttonText;
+ } else {
+ return Qt.darker(parent.backgroundColor, Theme.borderDarker);
+ }
+ }
+ color: parent.backgroundColor;
+ gradient: Gradient {
+ GradientStop {
+ position: 0.0; color: Qt.lighter(buttonBg.color, 1.2)
+ }
+ GradientStop {
+ position: 1.0; color: buttonBg.color
+ }
+ }
+ }
+}
diff --git a/interface/resources/qml/overte/TextArea.qml b/interface/resources/qml/overte/TextArea.qml
new file mode 100644
index 00000000000..785b1f3ea99
--- /dev/null
+++ b/interface/resources/qml/overte/TextArea.qml
@@ -0,0 +1,35 @@
+import QtQuick
+import QtQuick.Controls
+import "."
+
+TextArea {
+ id: textArea
+ selectByMouse: true
+
+ property color backgroundColor: Theme.paletteActive.window
+
+ color: Theme.paletteActive.windowText
+ placeholderTextColor: Theme.paletteActive.placeholderText
+ font.pixelSize: Theme.fontPixelSize
+ font.family: Theme.fontFamily
+ selectionColor: Theme.paletteActive.highlight
+ selectedTextColor: Theme.paletteActive.highlightedText
+
+ leftPadding: 6
+ rightPadding: 6
+ topPadding: 8
+ bottomPadding: 8
+
+ background: Rectangle {
+ radius: Theme.borderRadius
+ border.width: parent.activeFocus ? Theme.borderWidthFocused : Theme.borderWidth
+ border.color: textArea.activeFocus ?
+ Theme.paletteActive.focusRing :
+ (Theme.highContrast ? Theme.paletteActive.windowText : Qt.darker(textArea.backgroundColor, Theme.borderDarker))
+ gradient: Gradient {
+ GradientStop { position: 0.0; color: Qt.darker(textArea.backgroundColor, 1.02) }
+ GradientStop { position: 0.5; color: textArea.backgroundColor }
+ GradientStop { position: 1.0; color: Qt.lighter(textArea.backgroundColor, 1.02) }
+ }
+ }
+}
diff --git a/interface/resources/qml/overte/TextField.qml b/interface/resources/qml/overte/TextField.qml
new file mode 100644
index 00000000000..303c8cdee47
--- /dev/null
+++ b/interface/resources/qml/overte/TextField.qml
@@ -0,0 +1,38 @@
+import QtQuick
+import QtQuick.Controls
+import "."
+
+TextField {
+ id: textField
+ selectByMouse: true
+
+ property color backgroundColor: Theme.paletteActive.window
+
+ color: Theme.paletteActive.windowText
+ placeholderTextColor: Theme.paletteActive.placeholderText
+ font.pixelSize: Theme.fontPixelSize
+ font.family: Theme.fontFamily
+ selectionColor: Theme.paletteActive.highlight
+ selectedTextColor: Theme.paletteActive.highlightedText
+
+ opacity: enabled ? 1.0 : 0.5
+
+ leftPadding: 6
+ rightPadding: 6
+ topPadding: 8
+ bottomPadding: 8
+
+ background: Rectangle {
+ implicitHeight: Theme.fontPixelSize * 2
+ radius: Theme.borderRadius
+ border.width: parent.activeFocus ? Theme.borderWidthFocused : Theme.borderWidth
+ border.color: parent.activeFocus ?
+ Theme.paletteActive.focusRing :
+ (Theme.highContrast ? Theme.paletteActive.windowText : Qt.darker(parent.backgroundColor, Theme.borderDarker))
+ gradient: Gradient {
+ GradientStop { position: 0.0; color: Qt.darker(textField.backgroundColor, 1.02) }
+ GradientStop { position: 0.5; color: textField.backgroundColor }
+ GradientStop { position: 1.0; color: Qt.lighter(textField.backgroundColor, 1.02) }
+ }
+ }
+}
diff --git a/interface/resources/qml/overte/Theme.qml b/interface/resources/qml/overte/Theme.qml
new file mode 100644
index 00000000000..5c05fdfc911
--- /dev/null
+++ b/interface/resources/qml/overte/Theme.qml
@@ -0,0 +1,236 @@
+pragma Singleton
+import QtCore
+import QtQml
+import QtQuick
+
+QtObject {
+ property bool darkMode: _settings.darkMode // WindowScriptingInterface.darkMode
+ property bool highContrast: _settings.highContrast // WindowScriptingInterface.highContrast
+ property bool reducedMotion: _settings.reducedMotion // WindowScriptingInterface.reducedMotion
+
+ // FIXME: singletons can't access context properties?
+ readonly property Settings _settings: Settings {
+ category: "Theme"
+ property bool darkMode: true
+ property bool highContrast: false
+ property bool reducedMotion: false
+ }
+
+ // font face for UI elements
+ readonly property string fontFamily: "Roboto"
+
+ // font face for document text
+ readonly property string bodyFontFamily: "Roboto"
+
+ // font face for code editors
+ readonly property string monoFontFamily: "Roboto Mono"
+
+ readonly property int fontPixelSize: 18
+ readonly property int fontPixelSizeSmall: 14
+ readonly property int fontPixelSizeXSmall: 11
+
+ readonly property real borderRadius: 4.0
+ readonly property real borderWidth: 2.0
+ readonly property real borderWidthFocused: highContrast ? borderWidth * 2 : borderWidth
+
+ // TODO: set these on mobile where scroll buttons aren't useful
+ readonly property int scrollbarWidth: 24
+ readonly property bool scrollbarButtons: true
+
+ // Qt.lighten and Qt.darken constants for subtle 3D effect
+ readonly property real borderDarker: darkMode ? 2.5 : 1.5
+ readonly property real depthLighter: highContrast ? 1.0 : 1.2
+ readonly property real depthDarker: highContrast ? 1.0 : 1.3
+ readonly property real checkedDarker: highContrast ? 1.5 : 1.2
+ readonly property real hoverLighter: {
+ if (darkMode) {
+ // don't apply hover lightness on dark high contrast
+ return highContrast ? 1.0 : 1.3;
+ } else {
+ // 1.3 blows out the button colors to white on the light theme
+ return 1.1;
+ }
+ }
+
+ readonly property var paletteActive: (
+ highContrast ?
+ (darkMode ? paletteDarkContrast : paletteLightContrast) :
+ (darkMode ? paletteDark : paletteLight)
+ )
+
+ readonly property var paletteDark: QtObject {
+ readonly property color alternateBase: Qt.darker(base, 1.1)
+ readonly property color base: "#403849"
+ readonly property color text: "#eeeeee"
+ readonly property color button: "#605868"
+ readonly property color buttonText: "#eeeeee"
+ readonly property color window: "#524c59"
+ readonly property color windowText: "#eeeeee"
+ readonly property color highlight: "#0a9dce"
+ readonly property color highlightedText: "#ffffff"
+
+ readonly property color focusRing: "#f4801a"
+ readonly property color placeholderText: "#80eeeeee"
+
+ readonly property color tooltip: "#524c59"
+ readonly property color tooltipText: "#eeeeee"
+
+ readonly property color buttonDestructive: "#823d3d"
+ readonly property color buttonAdd: "#3a753a"
+ readonly property color buttonInfo: "#1e6591"
+ readonly property color buttonFavorite: "#b28c01"
+
+ readonly property color statusOffline: "#808080"
+ readonly property color statusFriendsOnly: "orange"
+ readonly property color statusContacts: "lime"
+ readonly property color statusEveryone: "cyan"
+
+ readonly property color link: highlight
+ readonly property color dialogShade: "#d0000000"
+
+ readonly property color activeWindowTitleBg: Qt.darker("#403849", 1.2)
+ readonly property color activeWindowTitleFg: text
+
+ readonly property color userCountEmpty: "#b0b0b0"
+ readonly property color userCountActive: "#22ef22"
+ readonly property color userCountFull: "#ef2f1f"
+
+ readonly property color appIconBackground: "#202020"
+ readonly property color appInstalledRunning: statusContacts
+ readonly property color appInstalledNotRunning: statusFriendsOnly
+ readonly property color appNotInstalledRunning: "red"
+ readonly property color appNotInstalled: statusOffline
+ }
+
+ readonly property var paletteLight: QtObject {
+ readonly property color alternateBase: Qt.darker(base, 1.05)
+ readonly property color base: "#f5f5f5"
+ readonly property color text: "#111111"
+ readonly property color button: "#f3f2f4"
+ readonly property color buttonText: "#111111"
+ readonly property color window: "#eeeeee"
+ readonly property color windowText: "#111111"
+ readonly property color highlight: "#0b3ebf"
+ readonly property color highlightedText: "#ffffff"
+
+ readonly property color focusRing: "#f4801a"
+ readonly property color placeholderText: "#60000000"
+
+ readonly property color tooltip: "#fffecc"
+ readonly property color tooltipText: "#111111"
+
+ readonly property color buttonDestructive: "#fccccc"
+ readonly property color buttonAdd: "#bef4c5"
+ readonly property color buttonInfo: "#bfe5fc"
+ readonly property color buttonFavorite: "#eddda6"
+
+ readonly property color statusOffline: "#808080"
+ readonly property color statusFriendsOnly: "brown"
+ readonly property color statusContacts: "green"
+ readonly property color statusEveryone: "teal"
+
+ readonly property color link: highlight
+ readonly property color dialogShade: "#d0808080"
+
+ readonly property color activeWindowTitleBg: "#000080"
+ readonly property color activeWindowTitleFg: "white"
+
+ readonly property color userCountEmpty: "#303030"
+ readonly property color userCountActive: "#008000"
+ readonly property color userCountFull: "#800000"
+
+ readonly property color appIconBackground: "#202020"
+ readonly property color appInstalledRunning: "#00ff00"
+ readonly property color appInstalledNotRunning: "#ffaf00"
+ readonly property color appNotInstalledRunning: "red"
+ readonly property color appNotInstalled: statusOffline
+ }
+
+ readonly property var paletteDarkContrast: QtObject {
+ readonly property color alternateBase: Qt.darker(base, 1.1)
+ readonly property color base: "#000000"
+ readonly property color text: "#f0f0f0"
+ readonly property color button: "#000000"
+ readonly property color buttonText: "#f0f0f0"
+ readonly property color window: "#000000"
+ readonly property color windowText: "#f0f0f0"
+ readonly property color highlight: "#ffffff"
+ readonly property color highlightedText: "#000000"
+
+ readonly property color focusRing: "#ff00ff"
+ readonly property color placeholderText: "#00ff00"
+
+ readonly property color tooltip: "#000000"
+ readonly property color tooltipText: "#ffff00"
+
+ readonly property color buttonDestructive: "#600000"
+ readonly property color buttonAdd: "#006000"
+ readonly property color buttonInfo: "#000080"
+ readonly property color buttonFavorite: "#606000"
+
+ readonly property color statusOffline: "#808080"
+ readonly property color statusFriendsOnly: "orange"
+ readonly property color statusContacts: "lime"
+ readonly property color statusEveryone: "cyan"
+
+ readonly property color link: "#ffff00"
+ readonly property color dialogShade: "#e8000000"
+
+ readonly property color activeWindowTitleBg: base
+ readonly property color activeWindowTitleFg: "white"
+
+ readonly property color userCountEmpty: text
+ readonly property color userCountActive: "#00ff00"
+ readonly property color userCountFull: "#ff00ff"
+
+ readonly property color appIconBackground: "black"
+ readonly property color appInstalledRunning: statusContacts
+ readonly property color appInstalledNotRunning: statusFriendsOnly
+ readonly property color appNotInstalledRunning: "red"
+ readonly property color appNotInstalled: statusOffline
+ }
+
+ readonly property var paletteLightContrast: QtObject {
+ readonly property color alternateBase: Qt.darker(base, 1.05)
+ readonly property color base: "#f5f5f5"
+ readonly property color text: "#000000"
+ readonly property color button: "#f5f5f5"
+ readonly property color buttonText: "#000000"
+ readonly property color window: "#f0f0f0"
+ readonly property color windowText: "#000000"
+ readonly property color highlight: "#600060"
+ readonly property color highlightedText: "#ffffff"
+
+ readonly property color focusRing: "#ff00ff"
+ readonly property color placeholderText: "#007000"
+
+ readonly property color tooltip: "#fffeee"
+ readonly property color tooltipText: "#111111"
+
+ readonly property color buttonDestructive: "#ffdddd"
+ readonly property color buttonAdd: "#ddffdd"
+ readonly property color buttonInfo: "#ddffff"
+ readonly property color buttonFavorite: "#ffffdd"
+
+ readonly property color statusOffline: "#808080"
+ readonly property color statusFriendsOnly: "brown"
+ readonly property color statusContacts: "green"
+ readonly property color statusEveryone: "teal"
+
+ readonly property color link: "#000080"
+ readonly property color dialogShade: "#fad0d0d0"
+
+ readonly property color activeWindowTitleBg: base
+ readonly property color activeWindowTitleFg: "black"
+
+ readonly property color userCountEmpty: text
+ readonly property color userCountActive: "#006000"
+ readonly property color userCountFull: "#600060"
+
+ readonly property color appIconBackground: "black"
+ readonly property color appInstalledRunning: "#00ff00"
+ readonly property color appInstalledNotRunning: "#ffaf00"
+ readonly property color appNotInstalledRunning: "red"
+ readonly property color appNotInstalled: statusOffline
+ }
+}
diff --git a/interface/resources/qml/overte/ToolTip.qml b/interface/resources/qml/overte/ToolTip.qml
new file mode 100644
index 00000000000..01391f8204f
--- /dev/null
+++ b/interface/resources/qml/overte/ToolTip.qml
@@ -0,0 +1,47 @@
+import QtQuick
+import QtQuick.Controls
+import "."
+
+ToolTip {
+ id: control
+ delay: 500
+
+ // NOTE: this default is for Controls,
+ // if you're using something else then override this
+ visible: parent.hovered
+
+ font.family: Theme.fontFamily
+ font.pixelSize: Theme.fontPixelSizeSmall
+
+ background: Item {
+ // drop shadow
+ Rectangle {
+ x: 3
+ y: 3
+ width: parent.width
+ height: parent.height
+
+ radius: Theme.borderRadius
+ color: "#a0000000"
+ }
+
+ Rectangle {
+ x: 0
+ y: 0
+ width: parent.width
+ height: parent.height
+
+ radius: Theme.borderRadius
+ border.width: Theme.borderWidth
+ border.color: Theme.highContrast ? Theme.paletteActive.tooltipText : Qt.darker(Theme.paletteActive.tooltip, Theme.borderDarker)
+ color: Theme.paletteActive.tooltip
+ }
+ }
+
+ contentItem: Text {
+ text: control.text
+ font: control.font
+ color: Theme.paletteActive.tooltipText
+ wrapMode: Text.Wrap
+ }
+}
diff --git a/interface/resources/qml/overte/WidgetZoo.qml b/interface/resources/qml/overte/WidgetZoo.qml
new file mode 100644
index 00000000000..c2d08878a50
--- /dev/null
+++ b/interface/resources/qml/overte/WidgetZoo.qml
@@ -0,0 +1,148 @@
+import QtQuick
+import QtQuick.Window
+import QtQuick.Layouts
+import QtQuick.Controls
+
+import "." as Overte
+
+// debugging test case to view the themed widgets
+Rectangle {
+ id: root
+ width: 480
+ height: 720
+ visible: true
+ color: Overte.Theme.paletteActive.base
+
+ Overte.TabBar {
+ anchors.left: parent.left
+ anchors.right: parent.right
+ anchors.top: parent.top
+ id: tabBar
+ Overte.TabButton { text: "Tab 1"; width: 120 }
+ Overte.TabButton { text: "Tab 2"; width: 120 }
+ Overte.TabButton { text: "Tab 3"; width: 120 }
+ Overte.TabButton { text: "Tab 4"; width: 120 }
+ Overte.TabButton { text: "Tab 5"; width: 120 }
+ Overte.TabButton { text: "Tab 6"; width: 120 }
+ Overte.TabButton { text: "Tab 7"; width: 120 }
+ Overte.TabButton { text: "Tab 8"; width: 120 }
+ }
+
+ ColumnLayout {
+ anchors.left: parent.left
+ anchors.right: parent.right
+ anchors.top: tabBar.bottom
+ anchors.leftMargin: 16
+ anchors.rightMargin: 16
+ anchors.topMargin: 16
+ spacing: 8
+
+ RowLayout {
+ Layout.fillWidth: true
+ spacing: 8
+ Overte.Button {
+ text: "Button"
+ }
+ Overte.Button {
+ text: "Destroy"
+ backgroundColor: Overte.Theme.paletteActive.buttonDestructive
+ }
+ Overte.Button {
+ text: "Add"
+ backgroundColor: Overte.Theme.paletteActive.buttonAdd
+ }
+ Overte.Button {
+ text: "Info"
+ backgroundColor: Overte.Theme.paletteActive.buttonInfo
+ }
+ Overte.RoundButton {
+ icon.source: "./icons/folder.svg"
+ icon.width: 24
+ icon.height: 24
+ }
+ }
+
+ Overte.TextField {
+ Layout.fillWidth: true
+ placeholderText: "Text field"
+ }
+ Overte.TextArea {
+ Layout.fillWidth: true
+ Layout.preferredHeight: 64
+ placeholderText: "Text area"
+ }
+ Overte.TextArea {
+ Layout.fillWidth: true
+ Layout.preferredHeight: 80
+ placeholderText: "Code area"
+ font.family: Overte.Theme.monoFontFamily
+ text: "function doSomething() {\n console.info(\"Hello, world\");\n}\n"
+ }
+ Overte.Switch {
+ text: "Switch"
+ }
+ Overte.Slider {
+ value: 0.5
+ }
+ Overte.SpinBox {
+ editable: true
+ }
+ Overte.Ruler { Layout.fillWidth: true }
+ Row {
+ spacing: 16
+
+ Overte.ComboBox {
+ model: [
+ "ComboBox",
+ "Singing Dogs",
+ "Golden Combs",
+ "Swirly Bombs",
+ ]
+ }
+
+ Overte.Label {
+ font.pixelSize: Overte.Theme.fontPixelSizeSmall
+ text: "This is a note label."
+ }
+ }
+
+ Overte.BodyText {
+ Layout.fillWidth: true
+ text: "This is body text. It's meant for selectable text documents or chat messages."
+ }
+
+ ScrollView {
+ id: scrollView
+ Layout.fillWidth: true
+ Layout.preferredHeight: 256
+
+ clip: true
+ contentWidth: 1024
+ contentHeight: 1024
+
+ ScrollBar.vertical: Overte.ScrollBar {
+ anchors.top: scrollView.top
+ anchors.bottom: scrollView.bottom
+ anchors.right: scrollView.right
+ anchors.bottomMargin: Theme.scrollbarWidth
+ }
+ ScrollBar.horizontal: Overte.ScrollBar {
+ anchors.bottom: scrollView.bottom
+ anchors.left: scrollView.left
+ anchors.right: scrollView.right
+ anchors.rightMargin: Theme.scrollbarWidth
+ }
+
+ Image {
+ anchors.fill: parent
+ source: "./icons/unset_avatar.svg"
+ fillMode: Image.Tile
+ opacity: 0.2
+ }
+
+ Overte.Label {
+ text: "ScrollView and ScrollBar"
+ }
+ }
+ }
+}
diff --git a/interface/resources/qml/overte/avatar_picker/AvatarItem.qml b/interface/resources/qml/overte/avatar_picker/AvatarItem.qml
new file mode 100644
index 00000000000..738e9341bd7
--- /dev/null
+++ b/interface/resources/qml/overte/avatar_picker/AvatarItem.qml
@@ -0,0 +1,123 @@
+import QtQuick
+import QtQuick.Controls
+import QtQuick.VectorImage
+
+import "../" as Overte
+
+Item {
+ id: item
+ required property int index
+ required property string name
+ required property url avatarUrl
+ required property url iconUrl
+ required property list tags
+
+ implicitWidth: GridView.view.cellWidth
+ implicitHeight: GridView.view.cellHeight
+
+ Overte.Button {
+ anchors.fill: parent
+ anchors.margins: 4
+
+ id: avatarButton
+
+ Image {
+ id: buttonIcon
+ source: item.iconUrl
+ fillMode: Image.PreserveAspectFit
+
+ anchors.top: parent.top
+ anchors.left: parent.left
+ anchors.right: parent.right
+ anchors.bottom: buttonLabel.top
+ anchors.margins: 4
+
+ // placeholder icon
+ Image {
+ anchors.centerIn: parent
+ width: 64
+ height: 64
+
+ fillMode: Image.PreserveAspectFit
+ source: "../icons/avatars.png"
+ sourceSize.width: width
+ sourceSize.height: height
+ visible: buttonIcon.status === Image.Error || buttonIcon.status === Image.Null
+ }
+ }
+
+ Overte.Label {
+ anchors.top: parent.top
+ anchors.left: parent.left
+ anchors.right: parent.right
+ anchors.bottom: buttonLabel.top
+ anchors.margins: 4
+ visible: buttonIcon.status === Image.Loading
+ opacity: Overte.Theme.highContrast ? 1.0 : 0.6
+ wrapMode: Text.Wrap
+ horizontalAlignment: Text.AlignHCenter
+ verticalAlignment: Text.AlignVCenter
+ text: qsTr("Loading…")
+ }
+
+ Overte.Label {
+ anchors.left: parent.left
+ anchors.right: parent.right
+ anchors.bottom: parent.bottom
+ anchors.margins: 4
+
+ text: item.name
+ horizontalAlignment: Text.AlignHCenter
+ verticalAlignment: Text.AlignBottom
+ wrapMode: Text.Wrap
+
+ id: buttonLabel
+ }
+
+ onClicked: root.loadBookmark(item.name)
+ }
+
+ Overte.RoundButton {
+ id: deleteButton
+ anchors.top: parent.top
+ anchors.left: parent.left
+
+ visible: root.editable && (hovered || editButton.hovered || avatarButton.hovered)
+ opacity: hovered || Overte.Theme.highContrast ? 1.0 : 0.75
+ backgroundColor: Overte.Theme.paletteActive.buttonDestructive
+
+ implicitWidth: 44
+ implicitHeight: 44
+
+ icon.source: "../icons/delete.svg"
+ icon.width: 32
+ icon.height: 32
+ icon.color: Overte.Theme.paletteActive.buttonText
+
+ Overte.ToolTip { text: qsTr("Delete") }
+
+ onClicked: root.requestDelete(item.index, item.name)
+ }
+
+ Overte.RoundButton {
+ id: editButton
+ anchors.top: parent.top
+ anchors.right: parent.right
+
+ visible: root.editable && (hovered || deleteButton.hovered || avatarButton.hovered)
+ opacity: hovered || Overte.Theme.highContrast ? 1.0 : 0.75
+ backgroundColor: Overte.Theme.paletteActive.buttonInfo
+
+ implicitWidth: 44
+ implicitHeight: 44
+
+ icon.source: "../icons/pencil.svg"
+ icon.width: 32
+ icon.height: 32
+ icon.color: Overte.Theme.paletteActive.buttonText
+
+ Overte.ToolTip { text: qsTr("Edit") }
+
+ onClicked: root.requestEdit(item.index)
+ }
+}
diff --git a/interface/resources/qml/overte/avatar_picker/AvatarPicker.qml b/interface/resources/qml/overte/avatar_picker/AvatarPicker.qml
new file mode 100644
index 00000000000..609b7442488
--- /dev/null
+++ b/interface/resources/qml/overte/avatar_picker/AvatarPicker.qml
@@ -0,0 +1,240 @@
+import QtQuick
+import QtQuick.Layouts
+import QtQuick.Controls
+import QtQuick.Dialogs as QtDialogs
+
+import "../" as Overte
+import "."
+
+Rectangle {
+ id: root
+ anchors.fill: parent
+ color: Overte.Theme.paletteActive.base
+ implicitWidth: 480
+ implicitHeight: 720
+
+ property bool editable: true
+ property list availableTags: []
+ property string searchExpression: ".*"
+ property var avatarModel: []
+
+ function updateBookmarkModel() {
+ const data = AvatarBookmarks.getBookmarks();
+ let tmp = [];
+
+ for (const [name, avatar] of Object.entries(data)) {
+ let iconUrl;
+
+ if (avatar.avatarIcon !== "") {
+ iconUrl = avatar.avatarIcon;
+ } else {
+ iconUrl = new URL(avatar.avatarUrl);
+ iconUrl.pathname = iconUrl.pathname.replace(/[.](?:fst|glb|fbx|vrm)$/i, ".jpg");
+ iconUrl = iconUrl.toString();
+ }
+
+ tmp.push({
+ name: name,
+ avatarUrl: avatar.avatarUrl,
+ iconUrl: iconUrl,
+ });
+ }
+
+ tmp.sort((a, b) => a.name.localeCompare(b.name));
+
+ avatarModel = tmp;
+ }
+
+ Component.onCompleted: updateBookmarkModel()
+
+ Connections {
+ target: AvatarBookmarks
+
+ function onBookmarkAdded() { updateBookmarkModel(); }
+ function onBookmarkDeleted() { updateBookmarkModel(); }
+ }
+
+ function loadBookmark(name) {
+ AvatarBookmarks.loadBookmark(name);
+ currentName.text = name;
+ }
+
+ Overte.StackView {
+ id: stack
+ anchors.fill: parent
+
+ initialItem: MainPage {}
+ }
+
+ property int requestedDeleteIndex: -1
+
+ function requestDelete(index, name) {
+ requestedDeleteIndex = index;
+ deleteWarningDialog.text = qsTr("Are you sure you want to delete %1?").arg(name);
+ deleteWarningDialog.open();
+ }
+
+ function requestEdit(index) {
+ if (index === -1) {
+ // getAvatarEntitiesVariant has its own separate format,
+ // { id, properties: {} }
+ let entityDataRaw = MyAvatar.getAvatarEntitiesVariant();
+ let entityData = [];
+
+ for (let entry of entityDataRaw) {
+ let data = entry.properties;
+ data.id = entry.id;
+ entityData.push(data);
+ }
+
+ stack.push("./EditorPage.qml", {
+ bookmarkToReplace: "",
+ avatarName: MyAvatar.getFullAvatarModelName(),
+ avatarURL: MyAvatar.skeletonModelURL,
+ avatarScale: MyAvatar.scale,
+ iconURL: "",
+ entityData: entityData,
+ });
+ } else {
+ // AvatarBookmarks has its own separate format,
+ // { properties: { id } }
+ const name = avatarModel[index].name;
+ const data = AvatarBookmarks.getBookmark(name);
+
+ const modelURL = data?.avatarUrl ?? "";
+ const iconURL = data?.avatarIcon ?? "";
+ const scale = data?.avatarScale ?? 1.0;
+
+ // FIXME: yes, it's "avatarEntites", not "avatarEntities"
+ let entityData = (data?.avatarEntites ?? []).map(e => e.properties);
+
+ stack.push("./EditorPage.qml", {
+ bookmarkToReplace: name,
+ avatarName: name,
+ avatarURL: modelURL,
+ avatarScale: scale,
+ iconURL: iconURL,
+ entityData: entityData,
+ });
+ }
+ }
+
+ Overte.MessageDialog {
+ id: deleteWarningDialog
+ anchors.fill: parent
+ buttons: QtDialogs.MessageDialog.Yes | QtDialogs.MessageDialog.No
+
+ onAccepted: {
+ AvatarBookmarks.removeBookmark(avatarModel[requestedDeleteIndex].name);
+ requestedDeleteIndex = -1;
+ }
+ }
+
+ Overte.Dialog {
+ id: addNewDialog
+ anchors.fill: parent
+ maxWidth: -1
+
+ property string avatarName: ""
+ property string avatarUrl: ""
+
+ signal accepted
+ signal rejected
+
+ onAccepted: {
+ const prevData = AvatarBookmarks.getBookmark(addNewDialog.avatarName);
+
+ if (addNewDialog.avatarName !== avatarNameField.text) {
+ AvatarBookmarks.removeBookmark(addNewDialog.avatarName);
+ }
+
+ // Qt's V4 doesn't support { ...spread } syntax :(
+ let newData = prevData;
+
+ if (avatarUrlField.text !== "") {
+ newData.avatarUrl = avatarUrlField.text;
+ }
+
+ AvatarBookmarks.setBookmarkData(avatarNameField.text, newData);
+
+ avatarName = "";
+ avatarUrl = "";
+ avatarNameField.text = "";
+ avatarUrlField.text = "";
+ close();
+ }
+
+ onRejected: {
+ avatarName = "";
+ avatarUrl = "";
+ avatarNameField.text = "";
+ avatarUrlField.text = "";
+ close();
+ }
+
+ ColumnLayout {
+ anchors.fill: parent
+ anchors.margins: 8
+
+ Overte.Label {
+ Layout.fillWidth: true
+ horizontalAlignment: Text.AlignHCenter
+ opacity: Overte.Theme.highContrast ? 1.0 : 0.6
+ text: qsTr("Add new avatar")
+ }
+ Overte.Ruler { Layout.fillWidth: true }
+
+ Overte.TextField {
+ Layout.fillWidth: true
+ placeholderText: qsTr("Avatar name")
+ text: addNewDialog.avatarName
+ id: avatarNameField
+ }
+
+ Overte.TextField {
+ Layout.fillWidth: true
+ placeholderText: qsTr("Avatar URL (.fst, .glb, .vrm, .fbx)")
+ text: addNewDialog.avatarUrl
+ id: avatarUrlField
+ }
+
+ RowLayout {
+ Layout.preferredWidth: 720
+ Layout.fillWidth: true
+
+ Overte.Button {
+ Layout.fillWidth: true
+ Layout.preferredWidth: 1
+ text: qsTr("Cancel")
+
+ onClicked: addNewDialog.rejected()
+ }
+
+ Overte.Button {
+ Layout.fillWidth: true
+ Layout.preferredWidth: 1
+
+ backgroundColor: Overte.Theme.paletteActive.buttonAdd
+ enabled: avatarUrlField.text.trim() === "" || avatarNameField.text.trim() === ""
+ text: qsTr("Add Current")
+
+ onClicked: {
+ avatarUrlField.text = MyAvatar.skeletonModelURL;
+ addNewDialog.accepted();
+ }
+ }
+
+ Overte.Button {
+ Layout.fillWidth: true
+ Layout.preferredWidth: 1
+
+ backgroundColor: Overte.Theme.paletteActive.buttonAdd
+ text: qsTr("Add")
+ enabled: avatarNameField.text !== "" && avatarUrlField.text !== ""
+
+ onClicked: addNewDialog.accepted()
+ }
+ }
+ }
+ }
+}
diff --git a/interface/resources/qml/overte/avatar_picker/EditorPage.qml b/interface/resources/qml/overte/avatar_picker/EditorPage.qml
new file mode 100644
index 00000000000..276d683b9ee
--- /dev/null
+++ b/interface/resources/qml/overte/avatar_picker/EditorPage.qml
@@ -0,0 +1,273 @@
+import QtQuick
+import QtQuick.Layouts
+import QtQuick.Controls
+
+import "../" as Overte
+import "."
+
+ColumnLayout {
+ id: editor
+
+ required property string bookmarkToReplace
+ required property string avatarName
+ required property string avatarURL
+ required property real avatarScale
+ required property string iconURL
+ required property var entityData
+
+ ScrollView {
+ Layout.fillWidth: true
+ Layout.fillHeight: true
+
+ ScrollBar.vertical: Overte.ScrollBar {
+ anchors.top: parent.top
+ anchors.bottom: parent.bottom
+ anchors.right: parent.right
+ policy: ScrollBar.AsNeeded
+ }
+ contentWidth: width - ScrollBar.vertical.width
+
+ component TextSetting: Column {
+ anchors.left: column.left
+ anchors.right: column.right
+ spacing: 4
+
+ required property string label
+ property string description
+ property alias text: inputField.text
+ property alias enabled: inputField.enabled
+
+ Overte.Label {
+ anchors.left: parent.left
+ anchors.right: parent.right
+ wrapMode: Text.Wrap
+ text: parent.label
+ }
+
+ Overte.Label {
+ anchors.left: parent.left
+ anchors.right: parent.right
+ font.pixelSize: Overte.Theme.fontPixelSizeSmall
+ wrapMode: Text.Wrap
+ text: parent.description
+ visible: text !== ""
+ }
+
+ Overte.TextField {
+ anchors.left: parent.left
+ anchors.right: parent.right
+ id: inputField
+ }
+ }
+
+ component SliderSetting: Column {
+ anchors.left: column.left
+ anchors.right: column.right
+ spacing: 4
+
+ required property string label
+ property string description
+ property alias from: inputSlider.from
+ property alias to: inputSlider.to
+ property alias value: inputSlider.value
+ property alias stepSize: inputSlider.stepSize
+ property alias snapMode: inputSlider.snapMode
+
+ Overte.Label {
+ anchors.left: parent.left
+ anchors.right: parent.right
+ wrapMode: Text.Wrap
+ text: parent.label
+ }
+
+ Overte.Label {
+ anchors.left: parent.left
+ anchors.right: parent.right
+ font.pixelSize: Overte.Theme.fontPixelSizeSmall
+ wrapMode: Text.Wrap
+ text: parent.description
+ visible: text !== ""
+ }
+
+ Overte.Slider {
+ anchors.left: parent.left
+ anchors.right: parent.right
+ id: inputSlider
+ }
+
+ Overte.Label {
+ anchors.left: parent.left
+ anchors.right: parent.right
+ horizontalAlignment: Text.AlignHCenter
+ font.pixelSize: Overte.Theme.fontPixelSizeSmall
+ text: inputSlider.value.toFixed(1)
+ }
+ }
+
+ Column {
+ anchors.fill: parent
+ anchors.margins: 8
+
+ id: column
+ spacing: Overte.Theme.fontPixelSize
+ padding: 8
+
+ Column {
+ anchors.left: column.left
+ anchors.right: column.right
+ visible: bookmarkToReplace === ""
+ spacing: 4
+
+ Overte.Label {
+ width: parent.width
+ horizontalAlignment: Text.AlignHCenter
+ opacity: Overte.Theme.highContrast ? 1.0 : 0.6
+ text: qsTr("Editing your current avatar info")
+ }
+
+ Overte.Ruler { width: parent.width }
+ }
+
+ TextSetting {
+ id: settingName
+ label: qsTr("Name")
+ text: editor.avatarName
+ enabled: bookmarkToReplace !== ""
+ }
+
+ TextSetting {
+ id: settingURL
+ label: qsTr("Model URL")
+ description: qsTr("Supported formats are FST, glTF, and FBX. FST files contain metadata about an avatar, such as its name, material overrides, and flow bone properties.")
+ text: editor.avatarURL
+ }
+
+ TextSetting {
+ id: settingIconURL
+ label: qsTr("Icon URL (optional)")
+ description: qsTr("If left empty, the game will attempt to use a .jpg file with the same name and path as the model URL.")
+ text: editor.iconURL
+ }
+
+ SliderSetting {
+ id: settingScale
+ label: qsTr("Model scale")
+ description: qsTr("A scale of 1 uses the avatar model's natural height. Some domains may have their own avatar scale limits. Note that very large or very small avatars may exhibit buggy behavior.")
+ value: editor.avatarScale
+ stepSize: 0.1
+ from: 0.1
+ to: 5.0
+ }
+
+ Column {
+ anchors.left: column.left
+ anchors.right: column.right
+ spacing: 4
+
+ Overte.Label {
+ anchors.left: parent.left
+ anchors.right: parent.right
+ wrapMode: Text.Wrap
+ text: qsTr("Avatar Entities")
+ }
+
+ Overte.Label {
+ anchors.left: parent.left
+ anchors.right: parent.right
+ font.pixelSize: Overte.Theme.fontPixelSizeSmall
+ wrapMode: Text.Wrap
+ text: qsTr("Avatar entities follow you across domains and are saved as part of the avatar settings. Avatar entities are commonly used for clothes and other wearables. You can edit your avatar entities in further detail using the Create app. Note that some domains may not allow avatar entities.")
+ }
+
+ Overte.Ruler {
+ anchors.left: parent.left
+ anchors.right: parent.right
+ }
+
+ EntityEditor {
+ anchors.left: parent.left
+ anchors.right: parent.right
+ model: editor.entityData
+ id: entityEditor
+ }
+ }
+ }
+ }
+
+ RowLayout {
+ Layout.fillWidth: true
+
+ Overte.Button {
+ // make the buttons equal size
+ Layout.fillWidth: true
+ Layout.preferredWidth: 1
+
+ text: qsTr("Cancel")
+ onClicked: stack.pop()
+ }
+
+ Item {
+ // make the buttons equal size
+ Layout.fillWidth: true
+ Layout.preferredWidth: 1
+ }
+
+ Overte.Button {
+ // make the buttons equal size
+ Layout.fillWidth: true
+ Layout.preferredWidth: 1
+
+ backgroundColor: Overte.Theme.paletteActive.buttonAdd
+ text: bookmarkToReplace !== "" ? qsTr("Save") : qsTr("Apply")
+
+ onClicked: {
+ if (bookmarkToReplace !== "") {
+ // AvatarBookmarks has its own separate format,
+ // { properties: { id } }
+ let entityData = [];
+
+ for (let item of entityEditor.model) {
+ entityData.push({ properties: item });
+ }
+
+ // editing an existing bookmark
+ AvatarBookmarks.setBookmarkData(bookmarkToReplace, {
+ version: 3,
+ avatarUrl: settingURL.text,
+ avatarIcon: settingIconURL.text,
+ avatarScale: settingScale.value,
+ // FIXME: yes, it's "avatarEntites", not "avatarEntities"
+ avatarEntites: entityData,
+ });
+ } else {
+ // editing the current unsaved avatar state
+ MyAvatar.skeletonModelURL = settingURL.text;
+ MyAvatar.scale = settingScale.value;
+
+ // marked as deprecated, but this is the only way
+ // of editing avatar entities in QML, since QVariantMap
+ // isn't convertible to EntityItemProperties
+
+ // updateAvatarEntities has its own separate format,
+ // { id, properties: {} }
+ let entityData = [];
+
+ for (let item of entityEditor.model) {
+ let props = {};
+ Object.assign(props, item);
+ delete props.id;
+
+ entityData.push({
+ id: item.id,
+ properties: props,
+ });
+ }
+
+ AvatarBookmarks.updateAvatarEntities(entityData);
+ }
+
+ stack.pop();
+ }
+ }
+ }
+}
diff --git a/interface/resources/qml/overte/avatar_picker/EntityEditor.qml b/interface/resources/qml/overte/avatar_picker/EntityEditor.qml
new file mode 100644
index 00000000000..ed58e4329c5
--- /dev/null
+++ b/interface/resources/qml/overte/avatar_picker/EntityEditor.qml
@@ -0,0 +1,183 @@
+import QtQuick
+import QtQuick.Layouts
+import QtQuick.Controls
+
+import "../" as Overte
+import "."
+
+Column {
+ id: entityEditor
+ spacing: 4
+
+ required property var model
+
+ Overte.Label {
+ width: entityEditor.width
+ font.pixelSize: Overte.Theme.fontPixelSizeSmall
+ wrapMode: Text.Wrap
+ text: qsTr("Currently, only Model entities can be added from here. In the future, you will be able to create any entity type through the Create app.")
+ }
+
+ Overte.Label {
+ width: entityEditor.width
+ text: qsTr("Entity name")
+ }
+
+ Overte.TextField {
+ width: entityEditor.width
+ id: addEntityName
+ }
+
+ Overte.Label {
+ width: entityEditor.width
+ text: qsTr("Model URL")
+ }
+
+ Overte.TextField {
+ width: entityEditor.width
+ id: addEntityURL
+ }
+
+ RowLayout {
+ width: entityEditor.width
+
+ Overte.Label {
+ Layout.fillWidth: true
+ text: qsTr("Skin model to avatar skeleton")
+ }
+
+ Overte.Switch {
+ id: addEntitySkinned
+ }
+ }
+
+ Overte.Label {
+ visible: !addEntitySkinned.checked
+ width: entityEditor.width
+ text: qsTr("Parent joint name or index")
+ }
+
+ Overte.TextField {
+ visible: !addEntitySkinned.checked
+ width: entityEditor.width
+ id: addEntityJoint
+ }
+
+ Overte.Button {
+ width: entityEditor.width
+ text: qsTr("Add entity")
+
+ icon.source: "../icons/plus.svg"
+ icon.width: 24
+ icon.height: 24
+ icon.color: Overte.Theme.paletteActive.buttonText
+ backgroundColor: Overte.Theme.paletteActive.buttonAdd
+
+ enabled: addEntityName.text.trim() !== "" && addEntityURL.text.trim() !== ""
+
+ onClicked: {
+ let joint = undefined;
+
+ if (!addEntitySkinned.checked) {
+ joint = MyAvatar.getJointIndex(addEntityJoint.text);
+
+ if (joint === -1) {
+ if (addEntityJoint.text.match(/[0-9]+/)) {
+ joint = parseInt(addEntityJoint.text);
+ } else {
+ joint = undefined;
+ }
+ }
+ }
+
+ entityEditor.model.push({
+ id: Uuid.generate(),
+ type: "Model",
+ name: addEntityName.text,
+ parentID: MyAvatar.SELF_ID,
+ parentJointIndex: joint,
+ modelURL: addEntityURL.text,
+ relayParentJoints: addEntitySkinned.checked,
+ useOriginalPivot: true,
+ });
+
+ // force an update on the repeater, since
+ // JS arrays don't trigger change signals
+ entityEditor.modelChanged();
+
+ addEntityName.text = "";
+ addEntityURL.text = "";
+ addEntityJoint.text = "";
+ addEntitySkinned.checked = false;
+ }
+ }
+
+ Overte.Ruler { width: entityEditor.width }
+
+ Repeater {
+ model: entityEditor.model
+
+ RowLayout {
+ width: entityEditor.width
+
+ required property int index
+ required property var modelData
+
+ Column {
+ Layout.fillWidth: true
+ Layout.fillHeight: true
+
+ Overte.Label {
+ width: parent.width
+ elide: Text.ElideLeft
+ text: {
+ if (modelData.name) {
+ return modelData.name;
+ } else {
+ return modelData.id ?? "No ID";
+ }
+ }
+ }
+
+ Overte.Label {
+ width: parent.width
+ opacity: Overte.Theme.highContrast ? 1.0 : 0.6
+ font.pixelSize: Overte.Theme.fontPixelSizeSmall
+ elide: Text.ElideRight
+ text: {
+ if (
+ modelData.type === "Model" &&
+ modelData.modelURL
+ ) {
+ return modelData.modelURL;
+ } else {
+ return modelData.type ?? "Unknown";
+ }
+ }
+ }
+ }
+
+ Overte.RoundButton {
+ backgroundColor: Overte.Theme.paletteActive.buttonDestructive
+
+ icon.source: "../icons/delete.svg"
+ icon.width: 24
+ icon.height: 24
+
+ implicitWidth: 32
+ implicitHeight: 32
+
+ horizontalPadding: 0
+ verticalPadding: 0
+
+ onClicked: {
+ entityEditor.model.splice(index, 1);
+
+ // force an update on the repeater, since
+ // JS arrays don't trigger change signals
+ entityEditor.modelChanged();
+ }
+ }
+ }
+ }
+}
diff --git a/interface/resources/qml/overte/avatar_picker/MainPage.qml b/interface/resources/qml/overte/avatar_picker/MainPage.qml
new file mode 100644
index 00000000000..30872f7e798
--- /dev/null
+++ b/interface/resources/qml/overte/avatar_picker/MainPage.qml
@@ -0,0 +1,233 @@
+import QtQuick
+import QtQuick.Layouts
+import QtQuick.Controls
+
+import "../" as Overte
+import "."
+
+ColumnLayout {
+ RowLayout {
+ Image {
+ Layout.preferredWidth: 64
+ Layout.preferredHeight: 64
+ Layout.margins: 4
+ id: currentIcon
+
+ source: {
+ const meshURL = MyAvatar.skeletonModelURL;
+ let iconUrl = new URL(meshURL);
+ iconUrl.pathname = iconUrl.pathname.replace(/[.](?:fst|glb|fbx|vrm)$/i, ".jpg");
+ return iconUrl;
+ }
+
+ fillMode: Image.PreserveAspectFit
+ sourceSize.width: width
+ sourceSize.height: height
+
+ // placeholder icon
+ Image {
+ anchors.fill: parent
+
+ fillMode: Image.PreserveAspectFit
+ source: "../icons/avatars.png"
+ sourceSize.width: width
+ sourceSize.height: height
+ visible: currentIcon.status === Image.Error || currentIcon.status === Image.Null
+ }
+ }
+
+ Overte.Label {
+ Layout.margins: 4
+ Layout.fillWidth: true
+ id: currentName
+
+ verticalAlignment: Text.AlignVCenter
+ text: {
+ const name = MyAvatar.getFullAvatarModelName();
+
+ if (name !== "") {
+ return name;
+ } else {
+ return qsTr("Unnamed");
+ }
+ }
+
+ Connections {
+ target: MyAvatar
+
+ function onSkeletonModelURLChanged() {
+ const name = MyAvatar.getFullAvatarModelName();
+
+ if (name !== "") {
+ currentName.text = name;
+ } else {
+ currentName.text = qsTr("Unnamed");
+ }
+
+ console.info(name);
+ }
+ }
+ }
+
+ Overte.RoundButton {
+ Layout.margins: 4
+
+ Overte.ToolTip { text: qsTr("Edit current") }
+
+ icon.source: "../icons/pencil.svg"
+ icon.width: 32
+ icon.height: 32
+ icon.color: Overte.Theme.paletteActive.buttonText
+ backgroundColor: Overte.Theme.paletteActive.buttonInfo
+
+ implicitWidth: 44
+ implicitHeight: 44
+
+ onClicked: root.requestEdit(-1)
+ }
+ }
+
+ RowLayout {
+ Layout.margins: 4
+
+ Overte.TextField {
+ Layout.fillWidth: true
+
+ id: searchField
+ placeholderText: qsTr("Search…")
+
+ Keys.onEnterPressed: {
+ searchButton.click();
+ forceActiveFocus();
+ }
+ Keys.onReturnPressed: {
+ searchButton.click();
+ forceActiveFocus();
+ }
+ }
+
+ Overte.RoundButton {
+ id: searchButton
+ icon.source: "../icons/search.svg"
+ icon.width: 24
+ icon.height: 24
+ icon.color: Overte.Theme.paletteActive.buttonText
+
+ onClicked: {
+ searchExpression = searchField.text === "" ? ".*" : searchField.text;
+ }
+ }
+ }
+
+ RowLayout {
+ Layout.fillWidth: true
+ Layout.leftMargin: 8
+ Layout.rightMargin: 8
+ visible: root.availableTags.length !== 0
+
+ Overte.Label {
+ Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter
+ text: qsTr("Tags")
+ }
+
+ ListView {
+ Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
+ Layout.fillWidth: true
+ implicitHeight: Overte.Theme.fontPixelSize * 2
+ orientation: Qt.Horizontal
+ spacing: 2
+ clip: true
+
+ model: root.availableTags
+ delegate: Overte.Button {
+ required property int index
+
+ implicitHeight: Overte.Theme.fontPixelSize * 2
+ text: qsTr(ListView.view.model[index])
+ checkable: true
+ checked: true
+
+ palette.buttonText: checked ? Overte.Theme.paletteActive.highlightedText : Overte.Theme.paletteActive.button
+ backgroundColor: checked ? Overte.Theme.paletteActive.highlight : Overte.Theme.paletteActive.button
+ }
+ }
+ }
+
+ GridView {
+ Layout.fillWidth: true
+ Layout.fillHeight: true
+
+ // QT6TODO: remove this once mouse inputs work properly
+ interactive: false
+
+ clip: true
+ // scales the cells to never leave dead space, but looks bad when scaling window
+ //cellWidth: (width - ScrollBar.vertical.width) / Math.floor(3 * (width / 480))
+ cellWidth: Math.floor((480 - ScrollBar.vertical.width) / 3)
+ cellHeight: cellWidth + Overte.Theme.fontPixelSize + 6
+
+ ScrollBar.vertical: Overte.ScrollBar {
+ policy: ScrollBar.AsNeeded
+ interactive: true
+ anchors.right: parent.right
+ anchors.top: parent.top
+ anchors.bottom: parent.bottom
+ }
+
+ delegate: AvatarItem {}
+
+ model: {
+ const searchRegex = new RegExp(searchExpression, "i");
+ let tmp = [];
+
+ for (const item of root.avatarModel) {
+ if (item.name.match(searchRegex)) {
+ let modelItem = item;
+
+ if (!modelItem.iconUrl) {
+ modelItem.iconUrl = "../icons/avatars.svg";
+ }
+
+ if (!modelItem.tags) { modelItem.tags = []; }
+
+ tmp.push(modelItem);
+ }
+ }
+
+ return tmp;
+ }
+ }
+
+ RowLayout {
+ Layout.margins: 4
+ spacing: 8
+
+ Overte.Label {
+ Layout.fillWidth: true
+ horizontalAlignment: Text.AlignLeft
+ verticalAlignment: Text.AlignVCenter
+ text: qsTr("%1 avatar(s)").arg(avatarModel.length)
+ }
+
+ Overte.Label {
+ Layout.fillWidth: true
+ visible: editable
+ horizontalAlignment: Text.AlignRight
+ verticalAlignment: Text.AlignVCenter
+ text: qsTr("Add new avatar")
+ }
+
+ Overte.RoundButton {
+ icon.source: "../icons/plus.svg"
+ icon.width: 24
+ icon.height: 24
+ icon.color: Overte.Theme.paletteActive.buttonText
+ backgroundColor: Overte.Theme.paletteActive.buttonAdd
+ implicitWidth: 48
+ implicitHeight: 48
+ visible: editable
+
+ onClicked: addNewDialog.open()
+ }
+ }
+}
diff --git a/interface/resources/qml/overte/avatar_picker/qmldir b/interface/resources/qml/overte/avatar_picker/qmldir
new file mode 100644
index 00000000000..1a6345b3c89
--- /dev/null
+++ b/interface/resources/qml/overte/avatar_picker/qmldir
@@ -0,0 +1,6 @@
+module AvatarPicker
+AvatarPicker 1.0 AvatarPicker.qml
+AvatarItem 1.0 AvatarItem.qml
+MainPage 1.0 MainPage.qml
+EditorPage 1.0 EditorPage.qml
+EntityEditor 1.0 EntityEditor.qml
diff --git a/interface/resources/qml/overte/chat/Chat.qml b/interface/resources/qml/overte/chat/Chat.qml
new file mode 100644
index 00000000000..e12ec83ad76
--- /dev/null
+++ b/interface/resources/qml/overte/chat/Chat.qml
@@ -0,0 +1,129 @@
+import QtQuick
+import QtQuick.Controls
+
+import "../" as Overte
+import "." as OverteChat
+
+Rectangle {
+ id: root
+ anchors.fill: parent
+ color: Overte.Theme.paletteActive.base
+
+ // stop the StackView from leaking out of overlay windows
+ clip: true
+
+ property bool settingJoinNotifications: true
+ property bool settingBroadcast: false
+ property bool settingChatBubbles: true
+ property bool settingDesktopWindow: false
+
+ property var typingIndicatorNames: ({})
+
+ // When the window gets closed, the chat history is forgotten.
+ // Keep a log of events we've received so we can replay them
+ // when recreating the window.
+ property list eventsLog: []
+
+ Component.onCompleted: {
+ const savedEvents = SettingsInterface.getValue("chat/eventsLog") ?? [];
+ for (let event of savedEvents) {
+ fromScript(event);
+ }
+ }
+
+ onMessagesCleared: {
+ eventsLog = [];
+ toScript({event: "clear_history"});
+ }
+
+ // NOTE: "int" makes sense here as the timestamps are whole milliseconds,
+ // but it's 32 bits and overflows, so we need real's ~53 bits to work properly
+ signal messagePushed(name: string, body: string, timestamp: real)
+ signal notificationPushed(text: string, timestamp: real)
+ signal messagesCleared()
+
+ function toScript(obj) {
+ sendToScript(JSON.stringify(obj));
+ }
+
+ function fromScript(rawObj) {
+ let obj = (typeof(rawObj) === "string") ? JSON.parse(rawObj) : rawObj;
+ const timestamp = obj.timestamp ?? Date.now();
+
+ switch (obj.event) {
+ case "recv_message":
+ messagePushed(obj.name ? obj.name : "", obj.body, timestamp);
+ break;
+
+ case "user_joined": if (settingJoinNotifications) {
+ notificationPushed(qsTr("%1 joined").arg(obj.name), timestamp);
+ } break;
+
+ case "user_left": if (settingJoinNotifications) {
+ notificationPushed(qsTr("%1 left").arg(obj.name), timestamp);
+ } break;
+
+ case "user_name_changed":
+ notificationPushed(
+ qsTr("%1 changed their name to %2").arg(obj.old_name).arg(obj.new_name),
+ timestamp
+ );
+ break;
+
+ case "start_typing":
+ typingIndicatorNames[obj.uuid] = obj.name;
+ // propChanged is only fired by Qt on variable assignments, not property changes
+ typingIndicatorNamesChanged();
+ break;
+
+ case "end_typing":
+ delete typingIndicatorNames[obj.uuid];
+ // propChanged is only fired by Qt on variable assignments, not property changes
+ typingIndicatorNamesChanged();
+ break;
+
+ case "change_setting":
+ updateSetting(obj.name, obj.value);
+ break;
+
+ default:
+ console.error(`fromScript: Unknown event type "${obj.event}"`);
+ console.error(JSON.stringify(obj));
+ break;
+ }
+ }
+
+ function updateSetting(name, value) {
+ switch (name) {
+ case "join_notify":
+ settingJoinNotifications = value;
+ break;
+
+ case "broadcast":
+ settingBroadcast = value;
+ break;
+
+ case "chat_bubbles":
+ settingChatBubbles = value;
+ break;
+
+ case "desktop_window":
+ settingDesktopWindow = value;
+ break;
+ }
+ }
+
+ function sendSettingsUpdate() {
+ toScript({event: "change_setting", setting: "join_notify", value: settingJoinNotifications});
+ toScript({event: "change_setting", setting: "broadcast", value: settingBroadcast});
+ toScript({event: "change_setting", setting: "chat_bubbles", value: settingChatBubbles});
+ toScript({event: "change_setting", setting: "desktop_window", value: settingDesktopWindow});
+ }
+
+ Overte.StackView {
+ anchors.fill: parent
+
+ id: stack
+ initialItem: OverteChat.ChatPage {}
+ }
+}
diff --git a/interface/resources/qml/overte/chat/ChatPage.qml b/interface/resources/qml/overte/chat/ChatPage.qml
new file mode 100644
index 00000000000..aa49ba0a27f
--- /dev/null
+++ b/interface/resources/qml/overte/chat/ChatPage.qml
@@ -0,0 +1,273 @@
+import QtQuick
+import QtQuick.Layouts
+import QtQuick.Controls
+
+import "../" as Overte
+import "../settings" as OverteSettings
+import "." as OverteChat
+
+ColumnLayout {
+ id: chatPage
+
+ RowLayout {
+ Layout.fillWidth: true
+
+ // push everything to the right
+ Item { Layout.fillWidth: true }
+
+ Overte.RoundButton {
+ Overte.ToolTip { text: qsTr("Clear chat history") }
+
+ backgroundColor: (
+ hovered ?
+ Overte.Theme.paletteActive.buttonDestructive :
+ Overte.Theme.paletteActive.button
+ )
+
+ implicitWidth: 36
+ implicitHeight: 36
+ horizontalPadding: 2
+ verticalPadding: 2
+
+ icon.source: "../icons/delete.svg"
+ icon.width: 24
+ icon.height: 24
+ icon.color: Overte.Theme.paletteActive.buttonText
+
+ onClicked: root.messagesCleared()
+ }
+
+ Overte.RoundButton {
+ Overte.ToolTip { text: qsTr("Broadcast messages to whole domain") }
+
+ backgroundColor: (
+ checked ?
+ Overte.Theme.paletteActive.highlight :
+ Overte.Theme.paletteActive.button
+ )
+ color: (
+ checked ?
+ Overte.Theme.paletteActive.highlightedText :
+ Overte.Theme.paletteActive.buttonText
+ )
+
+ implicitWidth: 36
+ implicitHeight: 36
+ horizontalPadding: 2
+ verticalPadding: 2
+
+ icon.source: "../icons/broadcast.svg"
+ icon.width: 24
+ icon.height: 24
+ icon.color: color
+
+ checkable: true
+ checked: root.settingBroadcast
+ onToggled: {
+ root.settingBroadcast = checked;
+ root.sendSettingsUpdate();
+ }
+ }
+
+ Overte.RoundButton {
+ Overte.ToolTip { text: qsTr("Settings") }
+
+ implicitWidth: 36
+ implicitHeight: 36
+ horizontalPadding: 2
+ verticalPadding: 2
+
+ icon.source: "../icons/settings_cog.svg"
+ icon.width: 24
+ icon.height: 24
+
+ onClicked: stack.push("./SettingsPage.qml")
+ }
+ }
+
+ Overte.Label {
+ Layout.fillWidth: true
+ Layout.fillHeight: true
+ visible: chatLog.model.count === 0
+
+ id: noMessagesLabel
+ text: qsTr("No messages")
+ opacity: 0.5
+ horizontalAlignment: Text.AlignHCenter
+ verticalAlignment: Text.AlignVCenter
+ }
+
+ ScrollView {
+ Layout.fillWidth: true
+ Layout.fillHeight: true
+ ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
+ ScrollBar.vertical: Overte.ScrollBar {
+ interactive: true
+ policy: ScrollBar.AlwaysOn
+ anchors.right: parent.right
+ anchors.top: parent.top
+ anchors.bottom: parent.bottom
+ }
+ visible: chatLog.model.count > 0
+
+ ListView {
+ id: chatLog
+ clip: true
+ spacing: 8
+
+ model: ListModel {}
+ delegate: MessageBlock {}
+
+ highlightMoveDuration: Overte.Theme.reducedMotion ? 1 : 500
+ highlightResizeDuration: Overte.Theme.reducedMotion ? 1 : 500
+
+ Connections {
+ target: root
+
+ function onMessagesCleared() {
+ chatLog.model.clear();
+ }
+
+ function onMessagePushed(name, body, timestamp) {
+ const imageUrlRegex = /(https?:\/\/\S+\.(?:png|jpg|jpeg|gif|webp|svg)\S*)/gmi;
+ let detectedImageUrls = body.match(imageUrlRegex) ?? [];
+
+ chatLog.model.append({
+ name: name,
+ body: (
+ body
+ .replace(/\&/g, "&")
+ .replace(/\[/g, "[")
+ .replace(/\]/g, "]")
+ .replace(/\/g, ">")
+ .replace(/\'/g, "'")
+ .replace(/\"/g, """)
+ .replace(/\n/g, " ")
+ // strip any image links, as they're
+ // embedded and clickable anyway
+ .replace(imageUrlRegex, "")
+ ),
+ notification: "",
+ timestamp: timestamp,
+ imageEmbeds: detectedImageUrls.map(e => ({ modelData: e })),
+ });
+ chatLog.currentIndex = chatLog.model.count - 1;
+ }
+
+ function onNotificationPushed(text, timestamp) {
+ chatLog.model.append({
+ name: "",
+ body: "",
+ notification: text,
+ timestamp: timestamp,
+ imageEmbeds: [],
+ });
+ chatLog.currentIndex = chatLog.model.count - 1;
+ }
+ }
+ }
+ }
+
+ Overte.Label {
+ Layout.fillWidth: true
+ Layout.leftMargin: 8
+ Layout.rightMargin: 8
+
+ id: typingIndicator
+
+ text: ""
+ opacity: Overte.Theme.highContrast ? 1.0 : 0.6
+ wrapMode: Text.Wrap
+ font.pixelSize: Overte.Theme.fontPixelSizeSmall
+ font.italic: true
+
+ Connections {
+ target: root
+
+ function onTypingIndicatorNamesChanged() {
+ const values = Object.values(root.typingIndicatorNames);
+
+ if (values.length === 0) {
+ typingIndicator.text = "";
+ } else {
+ typingIndicator.text = values.join(", ") + qsTr(" typing…");
+ }
+ }
+ }
+ }
+
+ RowLayout {
+ Layout.fillWidth: true
+ Layout.fillHeight: false
+
+ ScrollView {
+ Layout.fillWidth: true
+ Layout.fillHeight: true
+ Layout.maximumHeight: Overte.Theme.fontPixelSize * 6
+
+ ScrollBar.vertical: Overte.ScrollBar {
+ interactive: false
+ anchors.right: parent.right
+ anchors.top: parent.top
+ anchors.bottom: parent.bottom
+ }
+
+ Overte.TextArea {
+ id: messageInput
+ placeholderText: (
+ root.settingBroadcast ?
+ qsTr("Broadcast chat message…") :
+ qsTr("Local chat message…")
+ )
+ wrapMode: TextEdit.Wrap
+ KeyNavigation.priority: KeyNavigation.BeforeItem
+ KeyNavigation.tab: messageSend
+
+ Keys.onPressed: event => {
+ if (
+ (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) &&
+ !(event.modifiers & Qt.ShiftModifier)
+ ) {
+ messageSend.clicked();
+ event.accepted = true;
+ }
+ }
+
+ property bool hadText: false
+
+ onTextChanged: {
+ if (text === "") {
+ toScript({event: "end_typing"});
+ hadText = false;
+ } else if (!hadText) {
+ toScript({event: "start_typing"});
+ hadText = true;
+ }
+ }
+ }
+ }
+
+ Overte.RoundButton {
+ id: messageSend
+ Layout.fillHeight: true
+ Layout.preferredWidth: 40
+ horizontalPadding: 2
+ verticalPadding: 2
+
+ icon.source: "../icons/send.svg"
+ icon.width: 24
+ icon.height: 24
+ icon.color: Overte.Theme.paletteActive.buttonText
+
+ onClicked: {
+ let text = messageInput.text.trim();
+ messageInput.text = "";
+
+ if (text === "") { return; }
+
+ toScript({event: "send_message", body: text});
+ }
+ }
+ }
+}
diff --git a/interface/resources/qml/overte/chat/MessageBlock.qml b/interface/resources/qml/overte/chat/MessageBlock.qml
new file mode 100644
index 00000000000..c5bfdb0ab1c
--- /dev/null
+++ b/interface/resources/qml/overte/chat/MessageBlock.qml
@@ -0,0 +1,171 @@
+import QtQuick
+import QtQuick.Layouts
+
+import "../" as Overte
+
+ColumnLayout {
+ required property int index
+ required property string name
+ required property string body
+ required property string notification
+ required property real timestamp
+ required property var imageEmbeds
+
+ id: messageBlock
+
+ anchors.left: parent ? parent.left : undefined
+ anchors.right: parent ? parent.right : undefined
+ anchors.leftMargin: 4
+ anchors.rightMargin: Overte.Theme.scrollbarWidth
+
+ Rectangle {
+ Layout.fillWidth: true
+ implicitHeight: 3
+ color: Overte.Theme.paletteActive.highlight
+
+ ColorAnimation on color {
+ to: Overte.Theme.paletteActive.alternateBase
+ duration: 500
+ }
+ }
+
+ RowLayout {
+ Layout.leftMargin: 6
+ Layout.rightMargin: 8
+ Layout.fillWidth: true
+ opacity: Overte.Theme.highContrast ? 1.0 : 0.6
+
+ Overte.Label {
+ Layout.fillWidth: true
+ id: nameLabel
+ text: notification ? notification : name
+ font.pixelSize: Overte.Theme.fontPixelSizeSmall
+ font.bold: true
+ elide: Text.ElideRight
+ children: truncated ? [nameTooltipArea] : []
+
+ MouseArea {
+ id: nameTooltipArea
+ anchors.fill: parent
+ cursorShape: Qt.WhatsThisCursor
+ hoverEnabled: true
+
+ Overte.ToolTip {
+ visible: parent.containsMouse
+ text: nameLabel.text
+ }
+ }
+ }
+
+ Overte.Label {
+ Layout.alignment: Qt.AlignRight
+ horizontalAlignment: Text.AlignRight
+ font.pixelSize: Overte.Theme.fontPixelSizeSmall
+ text: (new Date(timestamp)).toLocaleTimeString(undefined, Locale.ShortFormat)
+
+ MouseArea {
+ anchors.fill: parent
+ cursorShape: Qt.WhatsThisCursor
+ hoverEnabled: true
+
+ Overte.ToolTip {
+ visible: parent.containsMouse
+ text: (new Date(timestamp)).toLocaleString(undefined, Locale.LongFormat)
+ }
+ }
+ }
+ }
+
+ Overte.BodyText {
+ Layout.leftMargin: 6
+ Layout.rightMargin: 16
+ // If we used Layout.fillWidth, then the entire block width would be selectable.
+ // With this hack we only use the width we really need to
+ Layout.maximumWidth: parent.width - parent.anchors.leftMargin - parent.anchors.rightMargin
+
+ visible: text.length > 0
+
+ // MD support is cool, but it'd only work properly in the QML chat
+ // app and not chat bubbles, where formatting isn't supported in Text entities.
+ //textFormat: TextEdit.MarkdownText
+ textFormat: TextEdit.RichText
+
+ text: {
+ if (notification) { return ""; }
+
+ return (
+ body
+ .replace(
+ /(https?:\/\/\S+)/gi,
+ `$1 `
+ )
+ );
+ }
+ }
+
+ Repeater {
+ model: imageEmbeds
+
+ Rectangle {
+ required property string modelData
+
+ color: Overte.Theme.paletteActive.alternateBase
+ radius: Overte.Theme.borderRadius
+
+ Layout.maximumWidth: messageBlock.width - 64
+ Layout.maximumHeight: 320
+
+ implicitWidth: (
+ embeddedImage.status === Image.Ready ?
+ embeddedImage.sourceSize.width + (Overte.Theme.borderWidth * 2) :
+ messageBlock.width - 64
+ )
+ implicitHeight: Math.max(
+ Overte.Theme.fontPixelSize * 2,
+ embeddedImage.sourceSize.width + (Overte.Theme.borderWidth * 2)
+ )
+
+ AnimatedImage {
+ anchors.fill: parent
+ anchors.margins: Overte.Theme.borderWidth
+
+ id: embeddedImage
+ fillMode: Image.PreserveAspectFit
+ autoTransform: true
+ source: modelData
+ //sourceSize.width: width
+ //sourceSize.height: height
+
+ MouseArea {
+ anchors.fill: parent
+ id: embedMouseArea
+
+ hoverEnabled: true
+ propagateComposedEvents: true
+ cursorShape: Qt.PointingHandCursor
+ acceptedButtons: Qt.LeftButton
+
+ onClicked: Qt.openUrlExternally(modelData)
+ }
+ }
+
+ Overte.ToolTip {
+ text: modelData
+ visible: embedMouseArea.containsMouse
+ }
+
+ Overte.Label {
+ visible: embeddedImage.status !== Image.Ready
+ anchors.fill: parent
+ horizontalAlignment: Text.AlignHCenter
+ verticalAlignment: Text.AlignVCenter
+ opacity: Overte.Theme.highContrast ? 1.0 : 0.6
+ text: (
+ embeddedImage.status === Image.Loading ?
+ qsTr("Loading image… %1%%").arg(Math.floor(embeddedImage.progress * 100)) :
+ qsTr("Error loading image")
+ )
+ }
+ }
+ }
+}
diff --git a/interface/resources/qml/overte/chat/SettingsPage.qml b/interface/resources/qml/overte/chat/SettingsPage.qml
new file mode 100644
index 00000000000..b014635307a
--- /dev/null
+++ b/interface/resources/qml/overte/chat/SettingsPage.qml
@@ -0,0 +1,71 @@
+import QtQuick
+import QtQuick.Layouts
+import QtQuick.Controls
+
+import "../" as Overte
+import "../settings" as OverteSettings
+
+Column {
+ id: settingsPage
+ spacing: 16
+
+ Item {
+ anchors.left: parent.left
+ anchors.right: parent.right
+ implicitHeight: Math.max(settingsBackBtn.implicitHeight, settingsTitleLabel.implicitHeight)
+
+ Overte.Label {
+ anchors.fill: parent
+
+ id: settingsTitleLabel
+
+ horizontalAlignment: Text.AlignHCenter
+ verticalAlignment: Text.AlignVCenter
+ text: qsTr("Settings - Chat")
+ }
+
+ Overte.Button {
+ id: settingsBackBtn
+ icon.source: "../icons/triangle_left.svg"
+ icon.width: 24
+ icon.height: 24
+ icon.color: Overte.Theme.paletteActive.buttonText
+
+ text: qsTr("Back", "Return to previous page")
+
+ onClicked: {
+ stack.pop();
+ }
+ }
+ }
+
+ OverteSettings.SwitchSetting {
+ text: qsTr("Join/Leave Notifications")
+
+ value: root.settingJoinNotifications
+ onValueChanged: {
+ root.settingJoinNotifications = value;
+ root.sendSettingsUpdate();
+ }
+ }
+
+ OverteSettings.SwitchSetting {
+ text: qsTr("Chat Bubbles")
+
+ value: root.settingChatBubbles
+ onValueChanged: {
+ root.settingChatBubbles = value;
+ root.sendSettingsUpdate();
+ }
+ }
+
+ OverteSettings.SwitchSetting {
+ text: qsTr("Desktop Window")
+
+ value: root.settingDesktopWindow
+ onValueChanged: {
+ root.settingDesktopWindow = value;
+ root.sendSettingsUpdate();
+ }
+ }
+}
diff --git a/interface/resources/qml/overte/compat/CompatAssetDialog.qml b/interface/resources/qml/overte/compat/CompatAssetDialog.qml
new file mode 100644
index 00000000000..addf54b4102
--- /dev/null
+++ b/interface/resources/qml/overte/compat/CompatAssetDialog.qml
@@ -0,0 +1,33 @@
+import QtCore
+import QtQuick
+
+import ".." as Overte
+import "../dialogs" as OverteDialogs
+
+import "../../windows" as HifiWindows
+
+// compatibility shim with Hifi FileDialog
+HifiWindows.Window {
+ id: root
+ resizable: true
+ implicitWidth: 480
+ implicitHeight: 480
+ minSize: Qt.vector2d(480, 360)
+ destroyOnHidden: true
+ objectName: "AssetServer"
+ title: qsTr("Asset Browser")
+ opacity: parent.opacity
+
+ Settings {
+ category: "Overlay.AssetServer"
+ property alias width: root.width
+ property alias height: root.height
+ property alias x: root.x
+ property alias y: root.y
+ }
+
+ OverteDialogs.AssetDialog {
+ anchors.fill: parent
+ id: assetDialog
+ }
+}
diff --git a/interface/resources/qml/overte/compat/CompatFileDialog.qml b/interface/resources/qml/overte/compat/CompatFileDialog.qml
new file mode 100644
index 00000000000..3d93c1feec1
--- /dev/null
+++ b/interface/resources/qml/overte/compat/CompatFileDialog.qml
@@ -0,0 +1,72 @@
+import QtCore
+import QtQuick
+
+import ".." as Overte
+import "../dialogs" as OverteDialogs
+
+import "../../windows" as HifiWindows
+
+// compatibility shim with Hifi FileDialog
+HifiWindows.Window {
+ id: root
+ resizable: true
+ implicitWidth: 480
+ implicitHeight: 360
+ minSize: Qt.vector2d(360, 240)
+ destroyOnCloseButton: true
+ destroyOnHidden: true
+ modality: Qt.ApplicationModal
+
+ Settings {
+ category: "FileDialog"
+ property alias width: root.width
+ property alias height: root.height
+ property alias x: root.x
+ property alias y: root.y
+ }
+
+ property alias caption: root.title
+ property string dir: ""
+ property var filter // maybe not necessary?
+
+ property bool selectDirectory: false
+ property bool multiSelect: false // unused
+ property bool saveDialog: false
+
+ property var options
+
+ signal selectedFile(var file)
+ signal canceled
+
+ onSelectedFile: root.destroy()
+ onCanceled: root.destroy()
+ onWindowClosed: canceled()
+
+ Component.onCompleted: {
+ if (dir !== "") {
+ fileDialog.currentFolder = dir;
+ }
+ }
+
+ OverteDialogs.FileDialog {
+ anchors.fill: parent
+
+ id: fileDialog
+ visible: true
+
+ fileMode: {
+ if (root.selectDirectory) {
+ return OverteDialogs.FileDialog.FileMode.OpenFolder;
+ } else if (root.saveDialog) {
+ return OverteDialogs.FileDialog.FileMode.SaveFile;
+ } else if (root.multiSelect) {
+ return OverteDialogs.FileDialog.FileMode.OpenFiles;
+ } else {
+ return OverteDialogs.FileDialog.FileMode.OpenFile;
+ }
+ }
+
+ onAccepted: root.selectedFile(fileDialog.selectedFile)
+ onRejected: root.canceled()
+ }
+}
diff --git a/interface/resources/qml/overte/compat/RunningScripts_Window.qml b/interface/resources/qml/overte/compat/RunningScripts_Window.qml
new file mode 100644
index 00000000000..b5a45d86ccc
--- /dev/null
+++ b/interface/resources/qml/overte/compat/RunningScripts_Window.qml
@@ -0,0 +1,14 @@
+import ".." as Overte
+import "../dialogs" as OverteDialogs
+
+import "../../windows" as HifiWindows
+
+HifiWindows.Window {
+ objectName: "RunningScripts"
+ title: qsTr("Running Scripts")
+ opacity: parent.opacity
+ resizable: true
+ minSize: Qt.vector2d(384, 192)
+
+ OverteDialogs.RunningScriptsDialog {}
+}
diff --git a/interface/resources/qml/overte/compat/qmldir b/interface/resources/qml/overte/compat/qmldir
new file mode 100644
index 00000000000..66496dbc57d
--- /dev/null
+++ b/interface/resources/qml/overte/compat/qmldir
@@ -0,0 +1,4 @@
+module Compat
+RunningScripts_Window 1.0 RunningScripts_Window.qml
+CompatFileDialog 1.0 CompatFileDialog.qml
+CompatAssetDialog 1.0 CompatAssetDialog.qml
diff --git a/interface/resources/qml/overte/contacts/AccountAvatar.qml b/interface/resources/qml/overte/contacts/AccountAvatar.qml
new file mode 100644
index 00000000000..b1530d75937
--- /dev/null
+++ b/interface/resources/qml/overte/contacts/AccountAvatar.qml
@@ -0,0 +1,59 @@
+import QtQuick
+import QtQuick.Effects
+
+import ".." as Overte
+
+Rectangle {
+ required property int status
+ property alias source: avatarImage.source
+ property alias retainWhileLoading: avatarImage.retainWhileLoading
+
+ color: "transparent"
+
+ implicitWidth: 64 + border.width
+ implicitHeight: 64 + border.width
+
+ id: avatar
+ radius: Overte.Theme.borderRadius
+ border.width: Math.max(2, Overte.Theme.borderWidth)
+ border.color: {
+ switch (status) {
+ case -1: return Overte.Theme.paletteActive.statusOffline;
+ case 0: return Overte.Theme.paletteActive.statusOffline;
+ case 1: return Overte.Theme.paletteActive.statusFriendsOnly;
+ case 2: return Overte.Theme.paletteActive.statusContacts;
+ case 3: return Overte.Theme.paletteActive.statusEveryone;
+ }
+ }
+
+ Image {
+ anchors.fill: avatar
+ anchors.margins: avatar.border.width
+ fillMode: Image.PreserveAspectCrop
+
+ id: avatarImage
+ sourceSize.width: width
+ sourceSize.height: height
+
+ layer.enabled: true
+ layer.effect: MultiEffect {
+ anchors.fill: avatarImage
+ source: avatarImage
+ maskEnabled: true
+ maskSource: mask
+ }
+ }
+
+ Item {
+ id: mask
+ anchors.fill: avatar
+ visible: false
+
+ layer.enabled: true
+ Rectangle {
+ anchors.fill: parent
+ radius: avatar.radius
+ color: "black"
+ }
+ }
+}
diff --git a/interface/resources/qml/overte/contacts/AccountContact.qml b/interface/resources/qml/overte/contacts/AccountContact.qml
new file mode 100644
index 00000000000..fce5036b66d
--- /dev/null
+++ b/interface/resources/qml/overte/contacts/AccountContact.qml
@@ -0,0 +1,111 @@
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Layouts
+
+import ".." as Overte
+import "."
+
+Rectangle {
+ id: control
+ color: index % 2 === 0 ? Overte.Theme.paletteActive.base : Overte.Theme.paletteActive.alternateBase
+ implicitWidth: ListView.view.contentWidth
+ implicitHeight: 96
+
+ required property int index
+
+ // directory username
+ required property string user
+ required property url avatarUrl
+ required property int status
+ required property bool friend
+ required property string currentPlaceName
+
+ ColumnLayout {
+ anchors.fill: parent
+ anchors.margins: 4
+ anchors.leftMargin: 8
+ anchors.rightMargin: 8
+
+ RowLayout {
+ Layout.fillWidth: true
+ spacing: 16
+
+ AccountAvatar {
+ source: avatarUrl
+ status: control.status
+ }
+
+ ColumnLayout {
+ Layout.fillWidth: true
+ Layout.fillHeight: true
+
+ Overte.Label {
+ Layout.fillWidth: true
+ Layout.fillHeight: true
+ elide: Text.ElideRight
+ verticalAlignment: Text.AlignVCenter
+ text: user
+ }
+
+ Overte.Label {
+ Layout.fillWidth: true
+ Layout.fillHeight: true
+ verticalAlignment: Text.AlignVCenter
+
+ color: {
+ switch (control.status) {
+ case 0: return Overte.Theme.paletteActive.text;
+ case 1: return Overte.Theme.paletteActive.statusFriendsOnly;
+ case 2: return Overte.Theme.paletteActive.statusContacts;
+ case 3: return Overte.Theme.paletteActive.statusEveryone;
+ }
+ }
+
+ text: {
+ switch (control.status) {
+ case 0: return qsTr("Offline");
+ case 1: return qsTr("Friends Only");
+ case 2: return qsTr("Contacts")
+ case 3: return qsTr("Everyone")
+ }
+ }
+ }
+
+ Overte.Label {
+ Layout.fillWidth: true
+ Layout.fillHeight: true
+ font.pixelSize: Overte.Theme.fontPixelSizeSmall
+ opacity: Overte.Theme.highContrast ? 1.0 : 0.8
+ verticalAlignment: Text.AlignVCenter
+ text: currentPlaceName
+ visible: currentPlaceName !== ""
+ }
+ }
+
+ Overte.RoundButton {
+ // TODO
+ visible: false
+ enabled: false
+
+ backgroundColor: (
+ control.friend ?
+ Overte.Theme.paletteActive.buttonDestructive :
+ Overte.Theme.paletteActive.buttonAdd
+ )
+ icon.source: (
+ control.friend ?
+ "../icons/remove_friend.svg" :
+ "../icons/add_friend.svg"
+ )
+ icon.width: 24
+ icon.height: 24
+ icon.color: Overte.Theme.paletteActive.buttonText
+
+ onClicked: {
+ // TODO
+ control.friend = !control.friend;
+ }
+ }
+ }
+ }
+}
diff --git a/interface/resources/qml/overte/contacts/ContactsList.qml b/interface/resources/qml/overte/contacts/ContactsList.qml
new file mode 100644
index 00000000000..aa9de3aed6f
--- /dev/null
+++ b/interface/resources/qml/overte/contacts/ContactsList.qml
@@ -0,0 +1,431 @@
+import QtQuick
+import QtQuick.Layouts
+import QtQuick.Controls
+
+import ".." as Overte
+import "."
+
+Rectangle {
+ id: contactsList
+ anchors.fill: parent
+ implicitWidth: 480
+ implicitHeight: 720
+ color: Overte.Theme.paletteActive.base
+
+ property string localSearchExpression: ".*"
+ property string accountSearchExpression: ".*"
+
+ property list localContactsModel: []
+ property list accountContactsModel: []
+ property var adminData: ({})
+
+ property var waitingRequestCallbacks: ({})
+
+ function fromScript(message) {
+ const data = JSON.parse(message);
+
+ switch (data.action) {
+ case "system:auth_request": {
+ if (contactsList.waitingRequestCallbacks[data.data.cookie]) {
+ contactsList.waitingRequestCallbacks[data.data.cookie](data.data);
+ delete contactsList.waitingRequestCallbacks[data.data.cookie];
+ }
+ } break;
+ }
+ }
+
+ function authRequest(data) {
+ let cookie = Date.now() + Math.floor(Math.random() * (1000 - -1000) + -1000);
+
+ contactsList.waitingRequestCallbacks[cookie] = data.callback;
+
+ sendToScript(JSON.stringify({
+ action: "system:auth_request",
+ data: {
+ method: data.method ?? "GET",
+ url: data.url,
+ body: data.body,
+ cookie: cookie,
+ }
+ }));
+ }
+
+ function updateLocalContacts() {
+ const palData = AvatarManager.getPalData().data;
+ let tmp = [];
+
+ for (const data of palData) {
+ // don't add ourselves to the contacts list
+ if (!data.sessionUUID) { continue; }
+
+ if (!contactsList.adminData[data.sessionUUID]) {
+ Users.requestUsernameFromID(data.sessionUUID);
+ }
+
+ tmp.push({
+ uuid: data.sessionUUID,
+ name: data.sessionDisplayName ? data.sessionDisplayName : `${qsTr("Unnamed")} ${data.sessionUUID}`,
+ volume: Users.getAvatarGain(data.sessionUUID),
+ });
+ }
+
+ localContactsModel = tmp;
+ }
+
+ function updateAccountContactsImpl(accounts) {
+ console.debug("Contacts list data received");
+ let tmp = [];
+
+ for (const entry of accounts) {
+ // FIXME: /api/v1/users/connections doesn't give you the availability
+ // of a connection, only whether they're on a server or not
+ let status = entry.location?.online ? 2 : 0;
+
+ let img = entry.images?.tiny ?? entry.images?.thumbnail;
+
+ // some accounts have a bugged default avatar that doesn't exist
+ if (
+ img === "assets/brand-icon-256.png" ||
+ !(img && img.match(/^https?:\/\//))
+ ) {
+ img = undefined;
+ }
+
+ tmp.push({
+ user: entry.username,
+ avatarUrl: img ?? "../icons/unset_avatar.svg",
+ status: status,
+ currentPlaceName: "",
+ friend: entry.connection === "friend",
+ });
+ }
+
+ tmp.sort((a, b) => (
+ ((a.status !== 0 ? 1 : 0) - (b.status !== 0 ? 1 : 0)) ||
+ a.user.localeCompare(b.user)
+ ));
+
+ accountContactsModel = tmp;
+ }
+
+ function updateAccountContacts() {
+ if (!AccountServices.loggedIn) {
+ accountContactsModel = [];
+ return;
+ }
+
+ console.debug("Requesting contacts list…");
+ authRequest({
+ method: "GET",
+ url: `${AccountServices.metaverseServerURL}/api/v1/users/connections`,
+ callback: response => {
+ try {
+ let data = JSON.parse(response.responseText);
+ if (data.status === "success" && data?.data?.users) {
+ updateAccountContactsImpl(data.data.users);
+ } else {
+ console.error("Failed to get contacts list");
+ console.debug(response.responseText);
+ }
+ } catch (e) {
+ console.error(e);
+ }
+ },
+ });
+ }
+
+ Connections {
+ target: Users
+
+ function usernameFromIDReply(sessionUUID, username, _fingerprint, isAdmin) {
+ contactsList.adminData[sessionUUID] = {
+ username: username,
+ badge: isAdmin ? "../icons/admin_shield.svg" : ""
+ };
+
+ // only assignments are checked, not property changes, so force the update signal
+ contactsList.adminDataChanged();
+ }
+
+ function avatarRemovedEvent(sessionUUID) {
+ delete contactsList.adminData[sessionUUID];
+ updateLocalContacts();
+ }
+
+ function avatarAddedEvent(sessionUUID) {
+ updateLocalContacts();
+ }
+ }
+
+ Connections {
+ target: AccountServices
+
+ function loggedInChanged(_loggedIn) {
+ updateAccountContacts();
+ }
+ }
+
+ Component.onCompleted: {
+ updateLocalContacts();
+ updateAccountContacts();
+ }
+
+ ColumnLayout {
+ anchors.fill: contactsList
+
+ MyAccountInfo {
+ Layout.fillWidth: true
+ id: myAccountInfo
+ }
+
+ Overte.TabBar {
+ Layout.fillWidth: true
+ id: tabBar
+
+ Overte.TabButton { text: qsTr("Local") }
+ Overte.TabButton { text: qsTr("Account") }
+ }
+
+ StackLayout {
+ currentIndex: tabBar.currentIndex
+
+ // Local
+ ColumnLayout {
+ Overte.Label {
+ Layout.fillWidth: true
+ Layout.fillHeight: true
+
+ visible: !localContactsList.visible
+ opacity: Overte.Theme.highContrast ? 1.0 : 0.7
+ text: qsTr("It's just you here")
+
+ horizontalAlignment: Text.AlignHCenter
+ verticalAlignment: Text.AlignVCenter
+ }
+
+ ListView {
+ Layout.fillWidth: true
+ Layout.fillHeight: true
+
+ id: localContactsList
+ visible: model.length > 0
+ clip: true
+
+ ScrollBar.vertical: Overte.ScrollBar {
+ policy: ScrollBar.AsNeeded
+ }
+
+ contentWidth: contactsList.width - Overte.Theme.scrollbarWidth
+
+ model: {
+ const regex = new RegExp(localSearchExpression, "i");
+ let tmp = [];
+
+ for (const item of localContactsModel) {
+ if (item.name.match(regex) || item.user.match(regex)) {
+ tmp.push(item);
+ }
+ }
+
+ return tmp;
+ }
+ delegate: SessionContact {}
+ }
+
+ RowLayout {
+ Layout.fillWidth: true
+ Layout.leftMargin: 4
+ Layout.rightMargin: 4
+
+ Overte.TextField {
+ Layout.fillWidth: true
+
+ Keys.onEnterPressed: {
+ localSearchButton.clicked();
+ forceActiveFocus();
+ }
+
+ Keys.onReturnPressed: {
+ localSearchButton.clicked();
+ forceActiveFocus();
+ }
+
+ placeholderText: qsTr("Search…")
+ id: localSearchField
+ }
+
+ Overte.RoundButton {
+ icon.source: "../icons/search.svg"
+ icon.width: 24
+ icon.height: 24
+ icon.color: Overte.Theme.paletteActive.buttonText
+ id: localSearchButton
+
+ onClicked: localSearchExpression = localSearchField.text
+ }
+ }
+ }
+
+ // Account
+ ColumnLayout {
+ ListView {
+ Layout.fillWidth: true
+ Layout.fillHeight: true
+ clip: true
+ visible: myAccountInfo.status !== MyAccountInfo.Status.LoggedOut
+
+ ScrollBar.vertical: Overte.ScrollBar {
+ policy: ScrollBar.AsNeeded
+ }
+
+ contentWidth: contactsList.width - Overte.Theme.scrollbarWidth
+
+ model: {
+ const regex = new RegExp(accountSearchExpression, "i");
+ let tmp = [];
+
+ for (const item of accountContactsModel) {
+ if (item.user.match(regex)) {
+ tmp.push(item);
+ }
+ }
+
+ return tmp;
+ }
+ delegate: AccountContact {}
+ }
+
+ RowLayout {
+ Layout.fillWidth: true
+ Layout.leftMargin: 4
+ Layout.rightMargin: 4
+ visible: myAccountInfo.status !== MyAccountInfo.Status.LoggedOut
+
+ Overte.TextField {
+ Layout.fillWidth: true
+
+ Keys.onEnterPressed: {
+ accountSearchButton.clicked();
+ forceActiveFocus();
+ }
+
+ Keys.onReturnPressed: {
+ accountSearchButton.clicked();
+ forceActiveFocus();
+ }
+
+ placeholderText: qsTr("Search…")
+ id: accountSearchField
+ }
+
+ Overte.RoundButton {
+ icon.source: "../icons/search.svg"
+ icon.width: 24
+ icon.height: 24
+ icon.color: Overte.Theme.paletteActive.buttonText
+ id: accountSearchButton
+
+ onClicked: accountSearchExpression = accountSearchField.text
+ }
+ }
+
+ Overte.Label {
+ Layout.fillWidth: true
+ Layout.fillHeight: true
+ visible: myAccountInfo.status === MyAccountInfo.Status.LoggedOut
+ wrapMode: Text.Wrap
+ horizontalAlignment: Text.AlignHCenter
+ verticalAlignment: Text.AlignVCenter
+ text: qsTr("Log in to track contacts")
+ }
+ }
+ }
+ }
+
+ Overte.Dialog {
+ id: changeAvatarDialog
+ anchors.fill: parent
+ maxWidth: -1
+
+ signal accepted
+ signal rejected
+
+ onAccepted: {
+ myAccountInfo.avatarUrl = avatarChangeUrlField.text;
+
+ authRequest({
+ method: "POST",
+ url: `${AccountServices.metaverseServerURL}/api/v1/account/${AccountServices.username}/field/images_tiny`,
+ body: JSON.stringify({ set: myAccountInfo.avatarUrl }),
+ });
+
+ avatarChangeUrlField.text = "";
+ close();
+ }
+
+ onRejected: {
+ avatarChangeUrlField.text = "";
+ close();
+ }
+
+ ColumnLayout {
+ anchors.fill: parent
+ anchors.margins: 8
+
+ Overte.Label {
+ Layout.fillWidth: true
+ horizontalAlignment: Text.AlignHCenter
+ opacity: Overte.Theme.highContrast ? 1.0 : 0.6
+ text: qsTr("Change profile picture")
+ }
+ Overte.Ruler { Layout.fillWidth: true }
+
+ Overte.Label {
+ Layout.topMargin: 12
+ Layout.bottomMargin: 12
+ Layout.fillWidth: true
+ wrapMode: Text.Wrap
+ font.pixelSize: Overte.Theme.fontPixelSizeSmall
+ text: qsTr("Profile pictures aren't stored on the directory server, so you need to supply a URL to your profile picture.\n\nSome services that let you upload images will give you links that will expire and stop working after a while.\n\n100x100 JPEG or WebP is recommended.")
+ }
+
+ Overte.TextField {
+ Layout.fillWidth: true
+ placeholderText: qsTr("Profile picture URL")
+ id: avatarChangeUrlField
+ }
+
+ RowLayout {
+ Layout.preferredWidth: 720
+ Layout.fillWidth: true
+
+ Overte.Button {
+ Layout.fillWidth: true
+ Layout.preferredWidth: 1
+ text: qsTr("Cancel")
+
+ onClicked: changeAvatarDialog.rejected();
+ }
+
+ Item {
+ Layout.preferredWidth: 1
+ Layout.fillWidth: true
+ }
+
+ Overte.Button {
+ Layout.fillWidth: true
+ Layout.preferredWidth: 1
+
+ backgroundColor: Overte.Theme.paletteActive.buttonAdd
+ text: qsTr("Apply")
+ enabled: (
+ avatarChangeUrlField.text !== "" &&
+ avatarChangeUrlField.text.match(/^https?:\/\//)
+ )
+
+ onClicked: changeAvatarDialog.accepted()
+ }
+ }
+ }
+ }
+}
diff --git a/interface/resources/qml/overte/contacts/MyAccountInfo.qml b/interface/resources/qml/overte/contacts/MyAccountInfo.qml
new file mode 100644
index 00000000000..ded4de11954
--- /dev/null
+++ b/interface/resources/qml/overte/contacts/MyAccountInfo.qml
@@ -0,0 +1,213 @@
+import QtCore as QtCore
+import QtQuick
+import QtQuick.Layouts
+import QtQuick.Controls
+
+import ".." as Overte
+import "."
+
+Rectangle {
+ id: root
+ radius: 8
+ border.width: Overte.Theme.borderWidth
+ border.color: Qt.darker(color, 1.2)
+ color: Overte.Theme.paletteActive.base
+
+ implicitHeight: 96
+ implicitWidth: 320
+
+ enum Status {
+ LoggedOut = -1,
+ Invisible,
+ FriendsOnly,
+ Contacts,
+ Everyone
+ }
+
+ // cache the last avatar image url so there's not a few
+ // seconds of placeholder avatar while the profile loads
+ QtCore.Settings {
+ id: settings
+ category: "MyAccountInfo"
+ property url cachedAvatarUrl: "../icons/unset_avatar.svg"
+ }
+
+ Connections {
+ target: AccountServices
+
+ function loggedInChanged(loggedIn) {
+ updateStatus();
+ }
+ }
+
+ Component.onCompleted: updateStatus()
+
+ function updateStatus() {
+ if (status === MyAccountInfo.Status.LoggedOut && AccountServices.loggedIn) {
+ contactsList.authRequest({
+ url: `${AccountServices.metaverseServerURL}/api/v1/account/${AccountServices.username}/field/images_tiny`,
+ callback: response => {
+ const data = JSON.parse(response.responseText);
+ if (data.data) {
+ root.avatarUrl = data.data;
+ }
+ },
+ });
+ }
+
+ if (!AccountServices.loggedIn) {
+ status = MyAccountInfo.Status.LoggedOut;
+ } else {
+ switch (AccountServices.findableBy) {
+ case "none": status = MyAccountInfo.Status.Invisible; break;
+ case "friends": status = MyAccountInfo.Status.FriendsOnly; break;
+ case "contacts": status = MyAccountInfo.Status.Contacts; break;
+ case "all": status = MyAccountInfo.Status.Everyone; break;
+ }
+ }
+ }
+
+ property int status: MyAccountInfo.Status.LoggedOut
+
+ property string avatarUrl: settings.cachedAvatarUrl
+
+ onAvatarUrlChanged: settings.cachedAvatarUrl = avatarUrl
+
+ readonly property color currentStatusColor: {
+ switch (status) {
+ case MyAccountInfo.Status.LoggedOut:
+ return Overte.Theme.paletteActive.statusOffline;
+
+ case MyAccountInfo.Status.Invisible:
+ return Overte.Theme.paletteActive.statusOffline;
+
+ case MyAccountInfo.Status.FriendsOnly:
+ return Overte.Theme.paletteActive.statusFriendsOnly;
+
+ case MyAccountInfo.Status.Contacts:
+ return Overte.Theme.paletteActive.statusContacts;
+
+ case MyAccountInfo.Status.Everyone:
+ return Overte.Theme.paletteActive.statusEveryone;
+ }
+ }
+
+ readonly property string currentStatusName: {
+ switch (status) {
+ case MyAccountInfo.Status.LoggedOut:
+ return qsTr("Logged Out");
+
+ case MyAccountInfo.Status.Invisible:
+ return qsTr("Invisible");
+
+ case MyAccountInfo.Status.FriendsOnly:
+ return qsTr("Friends Only");
+
+ case MyAccountInfo.Status.Contacts:
+ return qsTr("Contacts");
+
+ case MyAccountInfo.Status.Everyone:
+ return qsTr("Everyone");
+ }
+ }
+
+ RowLayout {
+ anchors.fill: parent
+
+ AccountAvatar {
+ id: avatarImg
+ source: (
+ root.status !== MyAccountInfo.Status.LoggedOut ?
+ root.avatarUrl :
+ "../icons/unset_avatar.svg"
+ )
+ status: root.status
+ retainWhileLoading: true
+ Layout.preferredWidth: 64
+ Layout.preferredHeight: 64
+ Layout.leftMargin: 8
+ Layout.rightMargin: 8
+
+ Overte.Button {
+ anchors.fill: parent
+ anchors.margins: Overte.Theme.borderWidth
+
+ backgroundColor: Overte.Theme.paletteActive.buttonAdd
+ opacity: hovered ? (Overte.Theme.highContrast ? 1.0 : 0.7) : 0
+ enabled: AccountServices.loggedIn
+ visible: AccountServices.loggedIn
+
+ icon.source: "../icons/plus.svg"
+ icon.width: 24
+ icon.height: 24
+ icon.color: Overte.Theme.paletteActive.buttonText
+
+ onClicked: changeAvatarDialog.open()
+
+ Overte.ToolTip { text: qsTr("Change profile picture") }
+ }
+ }
+
+ ColumnLayout {
+ Layout.rightMargin: 8
+ Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter
+
+ Overte.TextField {
+ Layout.fillWidth: true
+ verticalAlignment: Text.AlignVCenter
+
+ id: displayName
+ text: MyAvatar.displayName
+ placeholderText: AccountServices.username
+
+ onEditingFinished: MyAvatar.displayName = text
+ }
+
+ RowLayout {
+ Layout.fillWidth: true
+
+ Overte.Label {
+ text: root.status === MyAccountInfo.Status.Invisible ? "â—‹" : "â—"
+ color: root.currentStatusColor
+ }
+
+ Overte.ComboBox {
+ Layout.fillWidth: true
+
+ id: statusBox
+ flat: true
+ color: root.status === MyAccountInfo.Status.Invisible ? Overte.Theme.paletteActive.windowText : root.currentStatusColor
+ textRole: "text"
+ valueRole: "value"
+ visible: root.status !== MyAccountInfo.Status.LoggedOut
+
+ onActivated: index => {
+ root.status = index;
+
+ switch (index) {
+ case 0: AccountServices.findableBy = "none"; break;
+ case 1: AccountServices.findableBy = "friends"; break;
+ case 2: AccountServices.findableBy = "connections"; break;
+ case 3: AccountServices.findableBy = "all"; break;
+ }
+ }
+
+ currentIndex: root.status
+ model: [
+ { value: MyAccountInfo.Status.Invisible, text: qsTr("Invisible") },
+ { value: MyAccountInfo.Status.FriendsOnly, text: qsTr("Friends Only") },
+ { value: MyAccountInfo.Status.Contacts, text: qsTr("Contacts") },
+ { value: MyAccountInfo.Status.Everyone, text: qsTr("Everyone") },
+ ]
+ }
+
+ Overte.Label {
+ Layout.fillWidth: true
+ visible: root.status === MyAccountInfo.Status.LoggedOut
+ verticalAlignment: Text.AlignVCenter
+ text: qsTr("Logged Out")
+ }
+ }
+ }
+ }
+}
diff --git a/interface/resources/qml/overte/contacts/SessionContact.qml b/interface/resources/qml/overte/contacts/SessionContact.qml
new file mode 100644
index 00000000000..e0c37949f66
--- /dev/null
+++ b/interface/resources/qml/overte/contacts/SessionContact.qml
@@ -0,0 +1,114 @@
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Layouts
+
+import ".." as Overte
+
+Rectangle {
+ id: control
+ color: index % 2 === 0 ? Overte.Theme.paletteActive.base : Overte.Theme.paletteActive.alternateBase
+ implicitWidth: ListView.view.contentWidth
+ implicitHeight: 96
+
+ required property int index
+
+ required property string uuid
+ required property string name
+ required property real volume
+
+ readonly property string username: contactsList.adminData[uuid]?.username ?? ""
+ readonly property url badgeIconSource: contactsList.adminData[uuid]?.badge ?? ""
+
+ // TODO: does this work?
+ function dbToReal(x) {
+ return 1.0 - (x / 100);
+ }
+
+ function realToDb(x) {
+ return (1.0 - x) * 100;
+ }
+
+ ColumnLayout {
+ anchors.fill: parent
+ anchors.margins: 4
+ anchors.leftMargin: 8
+ anchors.rightMargin: 8
+
+ RowLayout {
+ Layout.fillWidth: true
+ spacing: 16
+
+ Image {
+ Layout.leftMargin: 6
+ Layout.preferredWidth: 24
+ Layout.preferredHeight: 24
+ source: badgeIconSource
+ }
+
+ ColumnLayout {
+ Layout.fillWidth: true
+
+ Overte.Label {
+ Layout.fillWidth: true
+ elide: Text.ElideRight
+ text: name
+ }
+
+ Overte.BodyText {
+ font.pixelSize: Overte.Theme.fontPixelSizeSmall
+ visible: username !== ""
+ //elide: Text.ElideRight
+ text: username
+ palette.buttonText: Overte.Theme.paletteActive.link
+ opacity: Overte.Theme.highContrast ? 1.0 : 0.7
+ }
+ }
+
+ Item { Layout.fillWidth: true }
+
+ // TODO: find a way of disabling this if the target is already in the account
+ // contacts, might be tricky with how usernames are hidden from non-admins
+ /*Overte.RoundButton {
+ icon.source: "../icons/add_friend.svg"
+ icon.width: 24
+ icon.height: 24
+
+ backgroundColor: hovered ? Overte.Theme.paletteActive.buttonAdd : Overte.Theme.paletteActive.button
+
+ Overte.ToolTip { text: qsTr("Send contact request") }
+
+ onClicked: console.warn("TODO")
+ }*/
+ }
+
+ RowLayout {
+ Overte.RoundButton {
+ icon.source: volumeSlider.value === 0.0 ? "../icons/speaker_muted.svg" : "../icons/speaker_active.svg"
+ icon.width: 24
+ icon.height: 24
+ icon.color: Overte.Theme.paletteActive.buttonText
+
+ onClicked: volumeSlider.value = 0.0
+ }
+
+ Overte.Slider {
+ Layout.fillWidth: true
+ id: volumeSlider
+ from: 0.0
+ to: 1.2
+ stepSize: 0.1
+ snapMode: Slider.SnapAlways
+ value: dbToReal(volume)
+
+ onMoved: Users.setAvatarGain(control.uuid, realToDb(value))
+ }
+
+ Overte.Label {
+ Layout.preferredWidth: Overte.Theme.fontPixelSize * 3
+ //text: `${Math.round(volumeSlider.value * 100)}%`
+ // why did hifi have to use decibels for avatar volume??
+ text: `${Math.round(volumeSlider.value * 100)}%\n${volume} db`
+ }
+ }
+ }
+}
diff --git a/interface/resources/qml/overte/contacts/qmldir b/interface/resources/qml/overte/contacts/qmldir
new file mode 100644
index 00000000000..f32fee462df
--- /dev/null
+++ b/interface/resources/qml/overte/contacts/qmldir
@@ -0,0 +1,6 @@
+module Contacts
+ContactsList 1.0 ContactsList.qml
+AccountAvatar 1.0 AccountAvatar.qml
+SessionContact 1.0 SessionContact.qml
+AccountContact 1.0 AccountContact.qml
+MyAccountInfo 1.0 MyAccountInfo.qml
diff --git a/interface/resources/qml/overte/dialogs/AssetDialog.qml b/interface/resources/qml/overte/dialogs/AssetDialog.qml
new file mode 100644
index 00000000000..5fda22bdceb
--- /dev/null
+++ b/interface/resources/qml/overte/dialogs/AssetDialog.qml
@@ -0,0 +1,384 @@
+import QtCore
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Layouts
+import Qt.labs.folderlistmodel
+import Qt.labs.qmlmodels
+
+import ".." as Overte
+
+Rectangle {
+ id: dialog
+ color: Overte.Theme.paletteActive.base
+
+ property url selectedFile: ""
+ property string searchExpression: ".*"
+
+ // FIXME: "Could not convert argument 0 from [object Object] to EntityItemProperties"
+ //readonly property var spawnableRegex: /\.(gltf|glb|vrm|fbx|fst|obj|png|jpeg|jpg|webp)$/i
+ readonly property var spawnableRegex: /\.(gltf|glb|vrm|fbx|fst|obj)$/i
+
+ property var assetMappingModel: ({})
+
+ Component.onCompleted: refreshModel()
+
+ function refreshModel() {
+ Assets.getAllMappings((error, maps) => {
+ if (error === "") {
+ assetMappingModel = maps;
+ tableModel.rows = getDirectoryModel();
+ } else {
+ console.error(error);
+ }
+ });
+ }
+
+ // this seems suboptimal but i can't figure out how else to use the Assets models
+ function getDirectoryModel() {
+ let tmp = [];
+
+ for (const [name, hash] of Object.entries(assetMappingModel)) {
+ if (!name.slice(1).match(new RegExp(searchExpression, "i"))) {
+ continue;
+ }
+
+ tmp.push({
+ // trim off the leading slash
+ name: name.slice(1),
+ path: name,
+ hash: hash,
+ });
+ }
+
+ tmp.sort((a, b) => a.name.localeCompare(b.name));
+
+ return tmp;
+ }
+
+ component TableDelegate: Rectangle {
+ required property TableView tableView
+ required property int row
+ required property bool current
+
+ property string name: tableModel.rows[row]?.name ?? "undefined"
+
+ implicitWidth: Math.max(tableView.contentWidth, label.implicitWidth)
+ implicitHeight: Overte.Theme.fontPixelSize * 2
+
+ color: {
+ if (current) {
+ return Overte.Theme.paletteActive.highlight;
+ } else if (row % 2 === 0) {
+ return Overte.Theme.paletteActive.base;
+ } else {
+ return Overte.Theme.paletteActive.alternateBase;
+ }
+ }
+
+ Overte.Label {
+ id: label
+ anchors.top: parent.top
+ anchors.bottom: parent.bottom
+ anchors.right: parent.right
+ anchors.left: parent.left
+ anchors.leftMargin: 4
+ verticalAlignment: Text.AlignVCenter
+ color: current ? Overte.Theme.paletteActive.highlightedText : Overte.Theme.paletteActive.buttonText
+ text: name
+ }
+ }
+
+ ColumnLayout {
+ anchors.fill: parent
+
+ RowLayout {
+ Layout.fillWidth: true
+ Layout.margins: 4
+
+ Overte.Button {
+ backgroundColor: Overte.Theme.paletteActive.buttonAdd
+ text: qsTr("Upload File")
+
+ // TODO
+ enabled: false
+ onClicked: {}
+ }
+
+ Overte.TextField {
+ Layout.fillWidth: true
+ Layout.preferredHeight: parent.height
+
+ id: searchField
+ placeholderText: qsTr("Search…")
+
+ Keys.onEnterPressed: {
+ searchButton.click();
+ searchField.forceActiveFocus();
+ }
+
+ Keys.onReturnPressed: {
+ searchButton.click();
+ searchField.forceActiveFocus();
+ }
+ }
+
+ Overte.RoundButton {
+ id: searchButton
+ icon.source: "../icons/search.svg"
+ icon.width: 24
+ icon.height: 24
+ icon.color: Overte.Theme.paletteActive.buttonText
+
+ onClicked: {
+ searchExpression = searchField.text === "" ? ".*" : searchField.text;
+
+ // refresh the listing model
+ tableModel.rows = getDirectoryModel();
+ }
+ }
+ }
+
+ TableView {
+ Layout.fillWidth: true
+ Layout.fillHeight: true
+
+ id: tableView
+ clip: true
+ selectionBehavior: TableView.SelectRows
+ selectionMode: TableView.SingleSelection
+ pixelAligned: true
+ rowSpacing: 1
+ columnSpacing: 0
+ boundsBehavior: Flickable.StopAtBounds
+
+ ScrollBar.vertical: Overte.ScrollBar {
+ policy: ScrollBar.AlwaysOn
+ interactive: true
+ }
+
+ ScrollBar.horizontal: Overte.ScrollBar {
+ policy: ScrollBar.AsNeeded
+ interactive: true
+ }
+
+ contentWidth: width - ScrollBar.vertical.width
+
+ model: TableModel {
+ id: tableModel
+
+ TableModelColumn { display: "name" }
+
+ rows: []
+ }
+
+ selectionModel: ItemSelectionModel {}
+ delegate: TableDelegate {}
+ }
+
+ GridLayout {
+ rows: 2
+ columns: 2
+ Layout.fillWidth: true
+ Layout.margins: 8
+
+ Overte.Button {
+ Layout.fillWidth: true
+ Layout.preferredWidth: 1
+
+ // TODO: might work, don't have a way of testing atm
+ //enabled: tableView.currentRow !== -1
+ enabled: false
+ backgroundColor: Overte.Theme.paletteActive.buttonDestructive
+ text: qsTr("Delete")
+
+ onClicked: {
+ const data = tableModel.rows[tableView.currentRow];
+ deleteWarningDialog.entryToDelete = data.name;
+ deleteWarningDialog.open();
+ }
+ }
+
+ Overte.Button {
+ Layout.fillWidth: true
+ Layout.preferredWidth: 1
+
+ enabled: tableView.currentRow !== -1
+ backgroundColor: Overte.Theme.paletteActive.buttonInfo
+ text: qsTr("Copy Link")
+
+ onClicked: {
+ const data = tableModel.rows[tableView.currentRow];
+ WindowScriptingInterface.copyToClipboard(`atp:${data.path}`);
+ }
+ }
+
+ Overte.Button {
+ Layout.fillWidth: true
+ Layout.preferredWidth: 1
+
+ // TODO: might work, don't have a way of testing atm
+ // enabled: tableView.currentRow !== -1
+ enabled: false
+ text: qsTr("Rename")
+
+ onClicked: {
+ const data = tableModel.rows[tableView.currentRow];
+ renameDialog.entryToRename = data.name;
+ renameDialog.open();
+ }
+ }
+
+ Overte.Button {
+ Layout.fillWidth: true
+ Layout.preferredWidth: 1
+
+ enabled: {
+ if (tableView.currentRow === -1) { return false; }
+ return tableModel.rows[tableView.currentRow].name.match(spawnableRegex);
+ }
+ backgroundColor: Overte.Theme.paletteActive.buttonAdd
+ text: qsTr("Create Entity")
+
+ onClicked: {
+ const data = tableModel.rows[tableView.currentRow];
+ spawnEntity(data.path);
+ }
+ }
+ }
+ }
+
+ Overte.MessageDialog {
+ id: deleteWarningDialog
+ anchors.fill: parent
+
+ property string entryToDelete: ""
+ text: qsTr("Are you sure you want to delete %1?").arg(entryToDelete)
+
+ onAccepted: {
+ // put the leading slash back on
+ Assets.deleteMapping(`/${entryToDelete}`);
+ entryToDelete = "";
+ close();
+ }
+
+ onRejected: {
+ entryToDelete = "";
+ close();
+ }
+ }
+
+ Overte.Dialog {
+ id: renameDialog
+ anchors.fill: parent
+
+ property string entryToRename: ""
+
+ signal accepted
+ signal rejected
+
+ onAccepted: {
+ // put the leading slash back on
+ Assets.renameMapping(`/${entryToRename}`, `/${assetRenameField.text}`, refreshModel);
+ assetRenameField.text = "";
+ entryToRename = "";
+ close();
+ }
+
+ onRejected: {
+ assetRenameField.text = "";
+ entryToRename = "";
+ close();
+ }
+
+ ColumnLayout {
+ anchors.fill: parent
+ anchors.margins: 8
+
+ Overte.Label {
+ Layout.fillWidth: true
+ horizontalAlignment: Text.AlignHCenter
+ opacity: Overte.Theme.highContrast ? 1.0 : 0.6
+ text: qsTr("Rename asset")
+ }
+ Overte.Ruler { Layout.fillWidth: true }
+
+ Overte.TextField {
+ Layout.preferredWidth: 720
+ Layout.fillWidth: true
+ placeholderText: qsTr("New asset name")
+ text: renameDialog.entryToRename
+ id: assetRenameField
+ }
+
+ RowLayout {
+ Overte.Button {
+ Layout.minimumWidth: 128
+ Layout.fillWidth: true
+ text: qsTr("Cancel")
+
+ onClicked: renameDialog.rejected()
+ }
+
+ Item { Layout.fillWidth: true }
+
+ Overte.Button {
+ Layout.minimumWidth: 128
+ Layout.fillWidth: true
+
+ enabled: assetRenameField.text !== ""
+ backgroundColor: Overte.Theme.paletteActive.buttonAdd
+ text: qsTr("Rename")
+
+ onClicked: renameDialog.accepted()
+ }
+ }
+ }
+ }
+
+ function spawnEntity(path) {
+ let isImage = path.match(/\.(png|jpeg|jpg|webp)$/i);
+ let isMesh = path.match(/\.(gltf|glb|vrm|fbx|fst|obj)$/i);
+
+ // FIXME: "Could not convert argument 0 from [object Object] to EntityItemProperties"
+ if (isImage) {
+ /*Entities.addEntity({
+ type: "Image",
+ imageURL: `atp:${path}`,
+ emissive: true,
+ keepAspectRatio: true,
+ rotation: MyAvatar.orientation,
+ position: Vec3.sum(
+ MyAvatar.position,
+ Vec3.multiply(2, Quat.getForward(MyAvatar.orientation))
+ ),
+ });*/
+ } else if (isMesh) {
+ /*Entities.addEntity({
+ type: "Model",
+ modelURL: `atp:${path}`,
+ shapeType: "simple-hull",
+ rotation: MyAvatar.orientation,
+ position: Vec3.sum(
+ MyAvatar.position,
+ Vec3.multiply(2, Quat.getForward(MyAvatar.orientation))
+ ),
+ });*/
+ Entities.addModelEntity(
+ /* name */ path,
+ /* modelUrl */ `atp:${path}`,
+ /* textures */ "",
+ /* shapeType */ "simple-hull",
+ /* dynamic */ false,
+ /* collisionless */ false,
+ /* grabbable */ true,
+ /* position */ Vec3.sum(
+ MyAvatar.position,
+ Vec3.multiply(2, Quat.getForward(MyAvatar.orientation))
+ ),
+ /* gravity */ Vec3.ZERO
+ );
+ } else {
+ console.error(`spawnEntity called with path that wasn't image or mesh! ${path}`);
+ }
+ }
+}
diff --git a/interface/resources/qml/overte/dialogs/BuiltinScriptsDialog.qml b/interface/resources/qml/overte/dialogs/BuiltinScriptsDialog.qml
new file mode 100644
index 00000000000..0a19fa61147
--- /dev/null
+++ b/interface/resources/qml/overte/dialogs/BuiltinScriptsDialog.qml
@@ -0,0 +1,195 @@
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Layouts
+import Qt.labs.qmlmodels
+
+import ".." as Overte
+
+Rectangle {
+ anchors.fill: parent
+ implicitWidth: 400
+ implicitHeight: 500
+ visible: false
+ color: Overte.Theme.paletteActive.base
+ id: picker
+
+ signal accepted(file: url)
+ signal rejected
+
+ onAccepted: visible = false
+ onRejected: visible = false
+
+ function open() {
+ visible = true;
+ }
+
+ function getDirectoryModel(parentIndex = undefined) {
+ const DISPLAY_ROLE = 0;
+ const PATH_ROLE = 256;
+ const sourceModel = ScriptDiscoveryService.scriptsModel;
+ const sourceModelRootLength = sourceModel.rowCount(parentIndex);
+ let tmp = [];
+
+ for (let i = 0; i < sourceModelRootLength; i++) {
+ const index = sourceModel.index(i, 0, parentIndex);
+
+ const name = sourceModel.data(index, DISPLAY_ROLE);
+ const path = sourceModel.data(index, PATH_ROLE);
+ const hasChildren = sourceModel.hasChildren(index);
+
+ if (hasChildren) {
+ tmp.push({
+ name: name,
+ path: path,
+ rows: getDirectoryModel(index)
+ });
+ } else {
+ tmp.push({
+ name: name,
+ path: path,
+ });
+ }
+ }
+
+ tmp.sort((a, b) => ((b.rows ? 1 : 0) - (a.rows ? 1 : 0)) || a.name.localeCompare(b.name));
+
+ return tmp;
+ }
+
+ component TreeDelegate: Rectangle {
+ required property TreeView treeView
+ required property int depth
+ required property int row
+ required property bool current
+ required property bool expanded
+ required property bool hasChildren
+
+ property string name: treeView.model.getRow(treeView.index(row, 0)).name
+
+ implicitWidth: treeView.contentWidth
+ implicitHeight: Overte.Theme.fontPixelSize * 2
+
+ color: {
+ if (current) {
+ return Overte.Theme.paletteActive.highlight;
+ } else if (row % 2 === 0) {
+ return Overte.Theme.paletteActive.base;
+ } else {
+ return Overte.Theme.paletteActive.alternateBase;
+ }
+ }
+
+ // IconImage and ColorImage are private in Qt 6.10,
+ // so hijack AbstractButton's icon
+ Overte.Button {
+ anchors.top: parent.top
+ anchors.bottom: parent.bottom
+ anchors.left: parent.left
+ anchors.leftMargin: Overte.Theme.fontPixelSize * depth
+ id: indicator
+
+ visible: hasChildren
+ width: parent.height
+ height: width
+
+ flat: true
+ horizontalPadding: 0
+ verticalPadding: 0
+
+ icon.source: expanded ? "../icons/triangle_down.svg" : "../icons/triangle_right.svg"
+ icon.width: Math.min(24, width)
+ icon.height: Math.min(24, width)
+ icon.color: current ? Overte.Theme.paletteActive.highlightedText : Overte.Theme.paletteActive.buttonText
+
+ onClicked: treeView.toggleExpanded(row)
+ }
+
+ Overte.Label {
+ anchors.top: parent.top
+ anchors.bottom: parent.bottom
+ anchors.right: parent.right
+ anchors.left: indicator.right
+ anchors.leftMargin: 4
+ verticalAlignment: Text.AlignVCenter
+ color: current ? Overte.Theme.paletteActive.highlightedText : Overte.Theme.paletteActive.buttonText
+ text: name
+ }
+ }
+
+ ColumnLayout {
+ anchors.fill: parent
+
+ Overte.Label {
+ Layout.alignment: Qt.AlignBottom | Qt.AlignHCenter
+ opacity: Overte.Theme.highContrast ? 1.0 : 0.6
+ wrapMode: Text.Wrap
+ text: qsTr("Load built-in script")
+ }
+
+ Overte.Ruler {
+ Layout.fillWidth: true
+ Layout.alignment: Qt.AlignBottom
+ }
+
+ TreeView {
+ Layout.fillWidth: true
+ Layout.fillHeight: true
+ id: treeView
+ clip: true
+
+ // QT6TODO: remove this once mouse input is working properly again,
+ // this needs to be false until then or the expander arrows are entirely unusable
+ interactive: false
+
+ ScrollBar.vertical: Overte.ScrollBar {
+ policy: ScrollBar.AlwaysOn
+ }
+
+ contentWidth: width - ScrollBar.vertical.width
+ flickableDirection: Flickable.VerticalFlick
+ selectionModel: ItemSelectionModel {}
+
+ model: TreeModel {
+ id: treeModel
+
+ TableModelColumn { display: "name" }
+
+ rows: getDirectoryModel()
+ }
+
+ delegate: TreeDelegate {}
+ }
+
+ RowLayout {
+ Layout.margins: 8
+ Layout.fillWidth: true
+
+ Item { Layout.fillWidth: true }
+
+ Overte.Button {
+ Layout.preferredWidth: 128
+ text: qsTr("Cancel")
+
+ onClicked: rejected()
+ }
+
+ Overte.Button {
+ Layout.preferredWidth: 128
+
+ enabled: {
+ if (treeView.selectionModel.currentIndex === -1) { return false; }
+
+ const data = treeModel.getRow(treeView.selectionModel.currentIndex);
+ return !data?.rows;
+ }
+ text: qsTr("Load")
+ backgroundColor: Overte.Theme.paletteActive.buttonAdd
+
+ onClicked: {
+ const data = treeModel.getRow(treeView.selectionModel.currentIndex);
+ accepted(data.path);
+ }
+ }
+ }
+ }
+}
diff --git a/interface/resources/qml/overte/dialogs/FileDialog.qml b/interface/resources/qml/overte/dialogs/FileDialog.qml
new file mode 100644
index 00000000000..beca7d6d55a
--- /dev/null
+++ b/interface/resources/qml/overte/dialogs/FileDialog.qml
@@ -0,0 +1,610 @@
+import QtCore
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Layouts
+import QtQuick.Dialogs as QtDialogs
+import Qt.labs.folderlistmodel
+import Qt.labs.qmlmodels
+
+import ".."
+import "."
+
+Rectangle {
+ enum FileMode {
+ OpenFile,
+ OpenFiles,
+ SaveFile,
+ OpenFolder
+ }
+
+ id: fileDialog
+ color: Theme.paletteActive.base
+ visible: false
+
+ signal accepted
+ signal rejected
+
+ property url currentFolder: StandardPaths.writableLocation(StandardPaths.HomeLocation)
+ property list nameFilters: ["*"]
+ property int fileMode: FileDialog.OpenFile
+
+ property url selectedFile: ""
+ property list selectedFiles: []
+
+ // TODO, FIXME
+ property list history: []
+ property int historyIndex: 0
+
+ property string searchExpression: ".*"
+
+ onAccepted: {
+ visible = false;
+ }
+
+ onRejected: {
+ visible = false;
+ }
+
+ function open() {
+ visible = true;
+ }
+
+ function close() {
+ visible = false;
+ }
+
+ function urlToPathString(urlRaw) {
+ const url = new URL(urlRaw);
+
+ if (url.protocol !== "file:") {
+ return url;
+ }
+
+ return url.pathname;
+ }
+
+ function directoryRowActivated(currentData) {
+ const parentDir = new URL(currentFolder).pathname.match(/^\/?(.*)/)[1];
+ const path = `file:///${parentDir}${parentDir ? "/" : ""}${currentData.fileName}`;
+
+ if (currentData.fileIsDir) {
+ currentFolder = path;
+ tableView.selectionModel.clear();
+ } else {
+ selectedFile = path;
+ selectedFiles = [];
+
+ let possibleConflict = false;
+
+ for (const index of tableView.selectionModel.selectedRows(0)) {
+ const rowData = tableView.model.getRow(index.row);
+ selectedFiles.push(`${currentFolder.toString()}/${rowData.fileName}`);
+
+ if (rowData.fileName === saveName.text) {
+ possibleConflict = true;
+ }
+ }
+
+ if (fileMode === FileDialog.FileMode.SaveFile && possibleConflict) {
+ replaceWarningDialog.text = qsTr("Are you sure you want to replace %1?").arg(saveName.text);
+ replaceWarningDialog.open();
+ } else {
+ fileDialog.accepted();
+ }
+ }
+ }
+
+ ColumnLayout {
+ anchors.fill: parent
+
+ RowLayout {
+ Layout.fillWidth: true
+ Layout.leftMargin: 8
+ Layout.rightMargin: 8
+ Layout.topMargin: 8
+
+ RoundButton {
+ icon.source: "../icons/triangle_left.svg"
+ icon.width: 24
+ icon.height: 24
+ icon.color: Theme.paletteActive.buttonText
+
+ enabled: historyIndex > 0
+
+ // TODO
+ onClicked: {}
+ }
+ RoundButton {
+ icon.source: "../icons/triangle_right.svg"
+ icon.width: 24
+ icon.height: 24
+ icon.color: Theme.paletteActive.buttonText
+
+ enabled: historyIndex < history.length - 1
+
+ // TODO
+ onClicked: {}
+ }
+
+ RoundButton {
+ icon.source: "../icons/arrow_up.svg"
+ icon.width: 24
+ icon.height: 24
+ icon.color: Theme.paletteActive.buttonText
+
+ enabled: folderModel.parentFolder.toString() !== ""
+
+ onClicked: {
+ currentFolder = folderModel.parentFolder;
+ }
+ }
+
+ TextField {
+ Layout.fillWidth: true
+ Layout.preferredHeight: parent.height
+
+ text: urlToPathString(fileDialog.currentFolder)
+ visible: pathEditToggle.checked
+
+ Keys.onEnterPressed: {
+ fileDialog.currentFolder = `file:///${text.match(/^\/?(.*)/)[1]}`;
+ }
+ Keys.onReturnPressed: {
+ fileDialog.currentFolder = `file:///${text.match(/^\/?(.*)/)[1]}`;
+ }
+ }
+
+ ListView {
+ Layout.fillWidth: true
+ Layout.preferredHeight: parent.height
+
+ id: breadcrumbBar
+ visible: !pathEditToggle.checked
+ clip: true
+ pixelAligned: true
+ boundsBehavior: Flickable.StopAtBounds
+ orientation: Qt.Horizontal
+
+ onCountChanged: {
+ positionViewAtEnd();
+ }
+
+ model: {
+ let breadcrumbs = [];
+
+ let parts = new URL(currentFolder).pathname.split("/").filter(Boolean);
+
+ if (SystemInformation.kernelType !== "winnt") {
+ parts.unshift("");
+ }
+
+ for (let i = 0; i < parts.length; i++) {
+ let targetUrl = parts.slice(1, i + 1).join("/");
+ if (targetUrl === "") {
+ targetUrl = "file:///";
+ } else {
+ targetUrl = "file:///" + targetUrl;
+ }
+
+ breadcrumbs.push({
+ label: parts[i] === "" ? "/" : parts[i],
+ targetUrl: targetUrl,
+ });
+ }
+
+ return breadcrumbs;
+ }
+
+ delegate: Button {
+ required property string label
+ required property url targetUrl
+
+ height: breadcrumbBar.height
+ text: label
+
+ onClicked: {
+ currentFolder = targetUrl;
+ }
+ }
+ }
+
+ RoundButton {
+ checkable: true
+ icon.source: "../icons/pencil.svg"
+ icon.color: Theme.paletteActive.buttonText
+ icon.width: 24
+ icon.height: 24
+
+ id: pathEditToggle
+ }
+ }
+
+ RowLayout {
+ Layout.fillWidth: true
+
+ TextField {
+ Layout.fillWidth: true
+ Layout.leftMargin: 8
+ Layout.preferredHeight: parent.height
+
+ id: searchField
+ placeholderText: qsTr("Search…")
+
+ Keys.onEnterPressed: searchButton.click()
+ Keys.onReturnPressed: searchButton.click()
+ }
+
+ RoundButton {
+ Layout.rightMargin: 8
+
+ id: searchButton
+ icon.source: "../icons/search.svg"
+ icon.width: 24
+ icon.height: 24
+ icon.color: Theme.paletteActive.buttonText
+
+ onClicked: {
+ searchExpression = searchField.text === "" ? ".*" : searchField.text;
+
+ // force a refresh
+ folderModel.folder = "";
+ folderModel.folder = fileDialog.currentFolder;
+ }
+ }
+ }
+
+ Item {
+ Layout.fillWidth: true
+ Layout.fillHeight: true
+ visible: folderModel.status === FolderListModel.Null
+
+ ColumnLayout {
+ anchors.centerIn: parent
+ spacing: 8
+
+ Label {
+ Layout.fillWidth: true
+
+ text: qsTr("Invalid or unreachable folder")
+ horizontalAlignment: Text.AlignHCenter
+ verticalAlignment: Text.AlignVCenter
+ wrapMode: Text.Wrap
+ }
+
+ Button {
+ Layout.alignment: Qt.AlignHCenter
+
+ text: qsTr("Go Home")
+ onClicked: {
+ currentFolder = StandardPaths.writableLocation(StandardPaths.HomeLocation);
+ }
+ }
+ }
+ }
+
+ TableView {
+ Layout.fillWidth: true
+ Layout.fillHeight: true
+ visible: folderModel.status !== FolderListModel.Null
+
+ // QT6TODO: Double-clicking on stuff flat out doesn't work yet,
+ // remove this once clicks on the overlay desktop work again
+ interactive: false
+
+ ScrollBar.vertical: ScrollBar {
+ policy: ScrollBar.AlwaysOn
+ interactive: true
+ anchors.right: parent.right
+ anchors.top: parent.top
+ anchors.bottom: parent.bottom
+ }
+
+ rightMargin: ScrollBar.vertical.width
+
+ id: tableView
+ clip: true
+ selectionBehavior: TableView.SelectRows
+ selectionMode: (
+ fileDialog.fileMode === FileDialog.FileMode.OpenFiles ?
+ TableView.ExtendedSelection :
+ TableView.SingleSelection
+ )
+ pixelAligned: true
+ rowSpacing: 1
+ columnSpacing: 0
+ boundsBehavior: Flickable.StopAtBounds
+
+ model: TableModel {
+ TableModelColumn {
+ display: "fileName"
+ textAlignment: () => Text.AlignLeft
+ }
+ TableModelColumn {
+ display: "fileModified"
+ textAlignment: () => Text.AlignRight
+ }
+ TableModelColumn {
+ display: "fileSize"
+ textAlignment: () => Text.AlignLeft
+ }
+ }
+
+ FolderListModel {
+ id: folderModel
+ nameFilters: fileDialog.nameFilters
+ folder: fileDialog.currentFolder
+ showDirsFirst: true
+ showFiles: fileDialog.fileMode !== FileDialog.OpenFolder
+ showOnlyReadable: true
+ caseSensitive: false
+ sortCaseSensitive: false
+
+ // TableModel can't have a ListModel for its rows,
+ // so we have to turn it into a JS array
+ onStatusChanged: {
+ if (folderModel.status === FolderListModel.Ready) {
+ if (folder === "") { return; }
+
+ let data = [];
+
+ for (let i = 0; i < folderModel.count; i++) {
+ let datum = {
+ fileName: folderModel.get(i, "fileName"),
+ fileModified: folderModel.get(i, "fileModified").toLocaleString(null, Locale.ShortFormat),
+ fileSize: folderModel.get(i, "fileSize"),
+ fileIsDir: folderModel.get(i, "fileIsDir"),
+ };
+
+ if (!datum.fileName.match(new RegExp(searchExpression, "i"))) {
+ continue;
+ }
+
+ if (datum.fileSize > 1024 * 1024 * 1024) {
+ let value = datum.fileSize / (1024 * 1024 * 1024);
+ value = Math.round(value * 100) / 100;
+ datum.fileSize = `${value} GiB`;
+ } else if (datum.fileSize > 1024 * 1024) {
+ let value = datum.fileSize / (1024 * 1024);
+ value = Math.round(value * 100) / 100;
+ datum.fileSize = `${value} MiB`;
+ } else if (datum.fileSize > 1024) {
+ let value = datum.fileSize / 1024;
+ value = Math.round(value * 100) / 100;
+ datum.fileSize = `${value} KiB`;
+ } else {
+ datum.fileSize = qsTr("%n byte(s)", "", datum.fileSize);
+ }
+
+ if (datum.fileIsDir) {
+ datum.fileSize = qsTr("Folder");
+ }
+
+ data.push(datum);
+ }
+
+ tableView.model.rows = data;
+ } else {
+ tableView.model.rows = [];
+ }
+
+ tableView.positionViewAtRow(0, TableView.AlignVCenter, Qt.point(0, 0), Qt.rect(0, 0, 0, 0));
+ }
+ }
+
+ selectionModel: ItemSelectionModel {
+ onCurrentChanged: (index, _) => {
+ if (index.row === -1) { return; }
+
+ const data = tableView.model.getRow(index.row);
+ selectedFile = `${currentFolder.toString()}/${data.fileName}`;
+ }
+ }
+
+ delegate: Rectangle {
+ required property bool selected
+ required property string display
+ required property int textAlignment
+
+ color: (
+ selected ?
+ Theme.paletteActive.highlight :
+ (row % 2 !== 0) ? Theme.paletteActive.alternateBase : Theme.paletteActive.base
+ )
+
+ id: cell
+ implicitHeight: {
+ // hide the mtime column if the window isn't big enough to fit it comfortably
+ if (column === 1 && tableView.width < 720) {
+ return 0;
+ } else {
+ return text.implicitHeight * 2
+ }
+ }
+ implicitWidth: {
+ let nameWidth = tableView.width;
+ let mtimeWidth = tableView.width * (1 / 4);
+ let sizeWidth = tableView.width * (1 / 4);
+
+ // qt doesn't let us do stretchy columns so emulate it ourselves
+ nameWidth -= sizeWidth;
+ nameWidth -= mtimeWidth;
+
+ // hide the mtime column if the window isn't big enough to fit it comfortably
+ if (tableView.width < 720) {
+ // can't be zero or qt complains
+ nameWidth += mtimeWidth;
+ mtimeWidth = 1;
+ }
+
+ // how come there's one extra pixel?
+ nameWidth -= tableView.rightMargin + 1;
+
+ switch (column) {
+ case 0: return nameWidth;
+ case 1: return mtimeWidth;
+ case 2: return sizeWidth;
+ }
+ }
+
+ MouseArea {
+ anchors.fill: parent
+ propagateComposedEvents: true
+
+ onPressed: mouse => {
+ parent.forceActiveFocus();
+
+ const index = tableView.model.index(row, column);
+
+ if (
+ fileMode === FileDialog.FileMode.OpenFiles &&
+ (mouse.modifiers & Qt.ControlModifier) === Qt.ControlModifier
+ ) {
+ tableView.selectionModel.select(
+ index,
+ ItemSelectionModel.Toggle | ItemSelectionModel.Rows
+ );
+ } else if (
+ fileMode === FileDialog.FileMode.OpenFiles &&
+ (mouse.modifiers & Qt.ShiftModifier) === Qt.ShiftModifier
+ ) {
+ const prevRow = tableView.selectionModel.currentIndex.row;
+ const currentRow = row;
+ const start = prevRow < currentRow ? prevRow : currentRow;
+ const end = prevRow < currentRow ? currentRow : prevRow;
+
+ // select everything between the previous "current" row and the next "current" row
+ for (let i = start; i <= end; i++) {
+ tableView.selectionModel.select(
+ tableView.model.index(i, 0),
+ ItemSelectionModel.Select | ItemSelectionModel.Rows
+ );
+ }
+ } else {
+ tableView.selectionModel.select(
+ index,
+ ItemSelectionModel.ClearAndSelect | ItemSelectionModel.Rows
+ );
+ }
+
+ tableView.selectionModel.setCurrentIndex(index, ItemSelectionModel.Current);
+
+ if (fileMode === FileDialog.FileMode.SaveFile) {
+ const rowData = tableView.model.getRow(index.row);
+ saveName.text = !rowData.fileIsDir ? rowData.fileName : "";
+ }
+ }
+
+ onDoubleClicked: {
+ directoryRowActivated(tableView.model.getRow(row));
+ }
+ }
+
+ Text {
+ id: text
+ anchors.fill: parent
+ anchors.leftMargin: 6
+ anchors.rightMargin: 6
+
+ visible: parent.implicitWidth > 1
+ text: cell.display
+ color: (
+ selected ?
+ Theme.paletteActive.highlightedText :
+ Theme.paletteActive.text
+ )
+ font.family: Theme.fontFamily
+ font.pixelSize: Theme.fontPixelSize
+ horizontalAlignment: cell.textAlignment
+ verticalAlignment: Text.AlignVCenter
+ wrapMode: Text.Wrap
+ }
+ }
+ }
+
+ RowLayout {
+ visible: fileMode === FileDialog.FileMode.SaveFile
+
+ Layout.fillWidth: true
+ Layout.leftMargin: 8
+ Layout.rightMargin: 8
+
+ TextField {
+ Layout.fillWidth: true
+
+ id: saveName
+ placeholderText: qsTr("Saved file name")
+ }
+ }
+
+ RowLayout {
+ Layout.fillWidth: true
+ Layout.leftMargin: 8
+ Layout.rightMargin: 8
+ Layout.bottomMargin: 8
+
+ Label {
+ Layout.fillWidth: true
+ Layout.rightMargin: 8
+ Layout.preferredHeight: parent.height
+
+ text: nameFilters.join(", ")
+ verticalAlignment: Text.AlignVCenter
+ elide: Text.ElideRight
+ }
+
+ Button {
+ implicitWidth: 128
+ text: qsTr("Cancel")
+
+ Component.onCompleted: {
+ clicked.connect(fileDialog.rejected)
+ }
+ }
+
+ Button {
+ implicitWidth: 128
+ backgroundColor: Theme.paletteActive.buttonAdd
+ text: {
+ if (fileMode === FileDialog.FileMode.OpenFolder) {
+ return qsTr("Open Here");
+ }
+
+ let text = fileMode === FileDialog.FileMode.SaveFile ? qsTr("Save") : qsTr("Open");
+
+ const currentRow = tableView.selectionModel.currentIndex.row;
+ if (currentRow !== -1) {
+ const currentData = tableView.model.getRow(currentRow);
+
+ if (currentData.fileIsDir) {
+ text = qsTr("Open");
+ }
+ }
+
+ return text;
+ }
+
+ enabled: tableView.selectionModel.hasSelection || fileMode === FileDialog.FileMode.OpenFolder
+ onClicked: {
+ if (fileMode === FileDialog.FileMode.OpenFolder) {
+ selectedFile = currentFolder;
+ fileDialog.accepted();
+ } else {
+ const currentRow = tableView.selectionModel.currentIndex.row;
+ const currentData = tableView.model.getRow(currentRow);
+ directoryRowActivated(currentData);
+ }
+ }
+ }
+ }
+ }
+
+ MessageDialog {
+ id: replaceWarningDialog
+ anchors.fill: parent
+ buttons: QtDialogs.MessageDialog.Yes | QtDialogs.MessageDialog.No
+
+ onAccepted: {
+ fileDialog.accepted();
+ }
+ }
+}
diff --git a/interface/resources/qml/overte/dialogs/README.md b/interface/resources/qml/overte/dialogs/README.md
new file mode 100644
index 00000000000..cffd0b0ea75
--- /dev/null
+++ b/interface/resources/qml/overte/dialogs/README.md
@@ -0,0 +1 @@
+These are top-level windowed dialogs. They aren't derived from Dialog, which is an inline modal popup.
diff --git a/interface/resources/qml/overte/dialogs/RunningScriptsDialog.qml b/interface/resources/qml/overte/dialogs/RunningScriptsDialog.qml
new file mode 100644
index 00000000000..674d0cbd4a4
--- /dev/null
+++ b/interface/resources/qml/overte/dialogs/RunningScriptsDialog.qml
@@ -0,0 +1,312 @@
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Layouts
+
+import ".." as Overte
+import "." as OverteDialogs
+
+Rectangle {
+ anchors.fill: parent
+
+ id: root
+ implicitWidth: 480
+ implicitHeight: 720
+ color: Overte.Theme.paletteActive.base
+
+ property list runningScriptsModel: []
+
+ function refreshRunningScriptsModel() {
+ let tmp = [];
+
+ for (const script of ScriptDiscoveryService.getRunning()) {
+ tmp.push({
+ name: script.name,
+ url: script.url,
+ });
+ }
+
+ tmp.sort((a, b) => a.name.localeCompare(b.name));
+
+ runningScriptsModel = tmp;
+ }
+
+ Connections {
+ target: ScriptDiscoveryService
+
+ function onScriptCountChanged() {
+ refreshRunningScriptsModel();
+ }
+ }
+
+ Component.onCompleted: refreshRunningScriptsModel()
+
+ component ScriptDelegate: Rectangle {
+ required property int index
+ required property string name
+ required property string url
+
+ width: runningList.width - Overte.Theme.scrollbarWidth
+ height: layout.implicitHeight
+
+ color: (
+ index % 2 == 0 ?
+ Overte.Theme.paletteActive.base :
+ Overte.Theme.paletteActive.alternateBase
+ )
+
+ RowLayout {
+ id: layout
+ anchors.fill: parent
+
+ ColumnLayout {
+ Layout.margins: 8
+
+ Overte.Label {
+ Layout.alignment: Qt.AlignLeft | Qt.AlignTop
+ Layout.fillWidth: true
+ elide: Text.ElideRight
+ text: name.replace(/(.*).js$/, "$1");
+ }
+
+ Overte.Label {
+ Layout.alignment: Qt.AlignLeft | Qt.AlignBottom
+ Layout.fillWidth: true
+ font.pixelSize: Overte.Theme.fontPixelSizeSmall
+ opacity: Overte.Theme.highContrast ? 1.0 : 0.6
+ elide: Text.ElideRight
+ text: {
+ if (url.startsWith("qrc:")) {
+ return qsTr("Built-in: %1").arg(url.toString().replace(/qrc:\/+(.*)$/, "$1"));
+ } else if (url.startsWith("file:///~/")) {
+ return qsTr("Built-in: %1").arg(url.toString().replace(/file:\/+\~\/+(.*)$/, "$1"));
+ } else {
+ return url;
+ }
+ }
+ }
+ }
+
+ Row {
+ Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
+ Layout.margins: 8
+ spacing: 2
+
+ Overte.RoundButton {
+ backgroundColor: Overte.Theme.paletteActive.buttonInfo
+
+ icon.source: "../icons/copy.svg"
+ icon.color: Overte.Theme.paletteActive.buttonText
+ icon.width: 20
+ icon.height: 20
+
+ implicitWidth: 28
+ implicitHeight: 28
+ horizontalPadding: 0
+ verticalPadding: 0
+
+ onClicked: WindowScriptingInterface.copyToClipboard(url)
+
+ Overte.ToolTip { text: qsTr("Copy URL to clipboard") }
+ }
+
+ Overte.RoundButton {
+ icon.source: "../icons/reload.svg"
+ icon.color: Overte.Theme.paletteActive.buttonText
+ icon.width: 20
+ icon.height: 20
+
+ implicitWidth: 28
+ implicitHeight: 28
+ horizontalPadding: 0
+ verticalPadding: 0
+
+ // restart the script
+ onClicked: ScriptDiscoveryService.stopScript(url, true)
+
+ Overte.ToolTip { text: qsTr("Reload") }
+ }
+
+ Overte.RoundButton {
+ backgroundColor: Overte.Theme.paletteActive.buttonDestructive
+
+ icon.source: "../icons/close.svg"
+ icon.color: Overte.Theme.paletteActive.buttonText
+ icon.width: 20
+ icon.height: 20
+
+ implicitWidth: 28
+ implicitHeight: 28
+ horizontalPadding: 0
+ verticalPadding: 0
+
+ onClicked: ScriptDiscoveryService.stopScript(url)
+
+ Overte.ToolTip { text: qsTr("Stop and remove") }
+ }
+ }
+ }
+ }
+
+ ColumnLayout {
+ anchors.fill: parent
+
+ ListView {
+ Layout.fillWidth: true
+ Layout.fillHeight: true
+
+ ScrollBar.vertical: Overte.ScrollBar {
+ policy: ScrollBar.AlwaysOn
+ anchors.top: parent.top
+ anchors.bottom: parent.bottom
+ anchors.right: parent.right
+ }
+
+ id: runningList
+ clip: true
+ model: root.runningScriptsModel
+ delegate: ScriptDelegate {}
+ }
+
+ Overte.Ruler { Layout.fillWidth: true }
+
+ Overte.Label {
+ Layout.fillWidth: true
+
+ horizontalAlignment: Text.AlignHCenter
+ opacity: Overte.Theme.highContrast ? 1.0 : 0.6
+ text: qsTr("Add new script from...")
+ }
+
+ RowLayout {
+ Layout.margins: 8
+ Layout.fillHeight: true
+
+ Overte.Button {
+ // force equal button widths
+ Layout.preferredWidth: 1
+ Layout.fillWidth: true
+
+ text: qsTr("File")
+
+ onClicked: fileDialog.open()
+ }
+
+ Overte.Button {
+ // force equal button widths
+ Layout.preferredWidth: 1
+ Layout.fillWidth: true
+
+ text: qsTr("URL")
+
+ onClicked: addFromUrlDialog.open()
+ }
+
+ Overte.Button {
+ // force equal button widths
+ Layout.preferredWidth: 1
+ Layout.fillWidth: true
+
+ text: qsTr("Built-in")
+
+ onClicked: builtinDialog.open()
+ }
+ }
+ }
+
+ Overte.Dialog {
+ id: addFromUrlDialog
+ anchors.fill: parent
+ maxWidth: -1
+
+ signal accepted
+ signal rejected
+
+ onAccepted: {
+ ScriptDiscoveryService.loadScript(targetUrlField.text, !oneSessionSwitch.checked);
+ close();
+
+ targetUrlField.text = "";
+ oneSessionSwitch.checked = false;
+ }
+
+ onRejected: {
+ close();
+
+ targetUrlField.text = "";
+ oneSessionSwitch.checked = false;
+ }
+
+ ColumnLayout {
+ anchors.fill: parent
+ anchors.margins: 8
+
+ Overte.Label {
+ Layout.fillWidth: true
+ horizontalAlignment: Text.AlignHCenter
+ opacity: Overte.Theme.highContrast ? 1.0 : 0.6
+ text: qsTr("Load script from URL")
+ }
+ Overte.Ruler { Layout.fillWidth: true }
+
+ Overte.TextField {
+ Layout.fillWidth: true
+ placeholderText: qsTr("Script URL")
+ id: targetUrlField
+ }
+
+ Overte.Switch {
+ id: oneSessionSwitch
+ text: qsTr("Only for this session")
+ }
+
+ RowLayout {
+ Layout.preferredWidth: 720
+ Layout.fillWidth: true
+
+ Overte.Button {
+ Layout.fillWidth: true
+ Layout.preferredWidth: 1
+ text: qsTr("Cancel")
+
+ onClicked: {
+ addFromUrlDialog.rejected();
+ }
+ }
+
+ Item {
+ Layout.preferredWidth: 1
+ Layout.fillWidth: true
+ }
+
+ Overte.Button {
+ Layout.fillWidth: true
+ Layout.preferredWidth: 1
+
+ backgroundColor: Overte.Theme.paletteActive.buttonAdd
+ text: qsTr("Load")
+ enabled: targetUrlField.text !== ""
+
+ onClicked: {
+ addFromUrlDialog.accepted();
+ }
+ }
+ }
+ }
+ }
+
+ OverteDialogs.BuiltinScriptsDialog {
+ anchors.fill: parent
+ id: builtinDialog
+
+ onAccepted: file => ScriptDiscoveryService.loadScript(file, true)
+ }
+
+ OverteDialogs.FileDialog {
+ anchors.fill: parent
+ id: fileDialog
+
+ nameFilters: ["*.js"]
+
+ onAccepted: ScriptDiscoveryService.loadScript(fileDialog.selectedFile, true)
+ }
+}
diff --git a/interface/resources/qml/overte/dialogs/qmldir b/interface/resources/qml/overte/dialogs/qmldir
new file mode 100644
index 00000000000..86450bd753a
--- /dev/null
+++ b/interface/resources/qml/overte/dialogs/qmldir
@@ -0,0 +1,5 @@
+module Dialogs
+FileDialog 1.0 FileDialog.qml
+AssetDialog 1.0 AssetDialog.qml
+RunningScriptsDialog 1.0 RunningScriptsDialog.qml
+BuiltinScriptsDialog 1.0 BuiltinScriptsDialog.qml
diff --git a/interface/resources/qml/overte/icons/add_friend.svg b/interface/resources/qml/overte/icons/add_friend.svg
new file mode 100644
index 00000000000..3894d6a3b95
--- /dev/null
+++ b/interface/resources/qml/overte/icons/add_friend.svg
@@ -0,0 +1,2 @@
+
+
diff --git a/interface/resources/qml/overte/icons/admin_shield.svg b/interface/resources/qml/overte/icons/admin_shield.svg
new file mode 100644
index 00000000000..14dc66daab1
--- /dev/null
+++ b/interface/resources/qml/overte/icons/admin_shield.svg
@@ -0,0 +1,2 @@
+
+
diff --git a/interface/resources/qml/overte/icons/arrow_up.svg b/interface/resources/qml/overte/icons/arrow_up.svg
new file mode 100644
index 00000000000..b313e472a5c
--- /dev/null
+++ b/interface/resources/qml/overte/icons/arrow_up.svg
@@ -0,0 +1,2 @@
+
+
diff --git a/interface/resources/qml/overte/icons/avatars.png b/interface/resources/qml/overte/icons/avatars.png
new file mode 100644
index 00000000000..95d11f284d0
Binary files /dev/null and b/interface/resources/qml/overte/icons/avatars.png differ
diff --git a/interface/resources/qml/overte/icons/broadcast.svg b/interface/resources/qml/overte/icons/broadcast.svg
new file mode 100644
index 00000000000..e3887ffe945
--- /dev/null
+++ b/interface/resources/qml/overte/icons/broadcast.svg
@@ -0,0 +1,2 @@
+
+
diff --git a/interface/resources/qml/overte/icons/close.svg b/interface/resources/qml/overte/icons/close.svg
new file mode 100644
index 00000000000..c640a18e28d
--- /dev/null
+++ b/interface/resources/qml/overte/icons/close.svg
@@ -0,0 +1,2 @@
+
+
diff --git a/interface/resources/qml/overte/icons/copy.svg b/interface/resources/qml/overte/icons/copy.svg
new file mode 100644
index 00000000000..98cde8781af
--- /dev/null
+++ b/interface/resources/qml/overte/icons/copy.svg
@@ -0,0 +1,2 @@
+
+
diff --git a/interface/resources/qml/overte/icons/delete.svg b/interface/resources/qml/overte/icons/delete.svg
new file mode 100644
index 00000000000..26fd93f4e17
--- /dev/null
+++ b/interface/resources/qml/overte/icons/delete.svg
@@ -0,0 +1,2 @@
+
+
diff --git a/interface/resources/qml/overte/icons/eye_closed.svg b/interface/resources/qml/overte/icons/eye_closed.svg
new file mode 100644
index 00000000000..35905a61f5b
--- /dev/null
+++ b/interface/resources/qml/overte/icons/eye_closed.svg
@@ -0,0 +1,2 @@
+
+
diff --git a/interface/resources/qml/overte/icons/eye_open.svg b/interface/resources/qml/overte/icons/eye_open.svg
new file mode 100644
index 00000000000..983636e21f7
--- /dev/null
+++ b/interface/resources/qml/overte/icons/eye_open.svg
@@ -0,0 +1,2 @@
+
+
diff --git a/interface/resources/qml/overte/icons/filter.svg b/interface/resources/qml/overte/icons/filter.svg
new file mode 100644
index 00000000000..75e22db7db6
--- /dev/null
+++ b/interface/resources/qml/overte/icons/filter.svg
@@ -0,0 +1,2 @@
+
+
diff --git a/interface/resources/qml/overte/icons/folder.svg b/interface/resources/qml/overte/icons/folder.svg
new file mode 100644
index 00000000000..6e1d56a35ce
--- /dev/null
+++ b/interface/resources/qml/overte/icons/folder.svg
@@ -0,0 +1,2 @@
+
+
diff --git a/interface/resources/qml/overte/icons/gold_star.svg b/interface/resources/qml/overte/icons/gold_star.svg
new file mode 100644
index 00000000000..22cbc130ee8
--- /dev/null
+++ b/interface/resources/qml/overte/icons/gold_star.svg
@@ -0,0 +1,2 @@
+
+
diff --git a/interface/resources/qml/overte/icons/home.svg b/interface/resources/qml/overte/icons/home.svg
new file mode 100644
index 00000000000..f0f8acf355b
--- /dev/null
+++ b/interface/resources/qml/overte/icons/home.svg
@@ -0,0 +1,2 @@
+
+
diff --git a/interface/resources/qml/overte/icons/info.svg b/interface/resources/qml/overte/icons/info.svg
new file mode 100644
index 00000000000..057d846de31
--- /dev/null
+++ b/interface/resources/qml/overte/icons/info.svg
@@ -0,0 +1,2 @@
+
+
diff --git a/interface/resources/qml/overte/icons/pause.svg b/interface/resources/qml/overte/icons/pause.svg
new file mode 100644
index 00000000000..aed337a41ef
--- /dev/null
+++ b/interface/resources/qml/overte/icons/pause.svg
@@ -0,0 +1,2 @@
+
+
diff --git a/interface/resources/qml/overte/icons/pencil.svg b/interface/resources/qml/overte/icons/pencil.svg
new file mode 100644
index 00000000000..2882fb5b046
--- /dev/null
+++ b/interface/resources/qml/overte/icons/pencil.svg
@@ -0,0 +1,2 @@
+
+
diff --git a/interface/resources/qml/overte/icons/plus.svg b/interface/resources/qml/overte/icons/plus.svg
new file mode 100644
index 00000000000..e910fffc74d
--- /dev/null
+++ b/interface/resources/qml/overte/icons/plus.svg
@@ -0,0 +1,2 @@
+
+
diff --git a/interface/resources/qml/overte/icons/reload.svg b/interface/resources/qml/overte/icons/reload.svg
new file mode 100644
index 00000000000..3c641fa6b4c
--- /dev/null
+++ b/interface/resources/qml/overte/icons/reload.svg
@@ -0,0 +1,2 @@
+
+
diff --git a/interface/resources/qml/overte/icons/remove_friend.svg b/interface/resources/qml/overte/icons/remove_friend.svg
new file mode 100644
index 00000000000..1858c67f977
--- /dev/null
+++ b/interface/resources/qml/overte/icons/remove_friend.svg
@@ -0,0 +1,2 @@
+
+
diff --git a/interface/resources/qml/overte/icons/search.svg b/interface/resources/qml/overte/icons/search.svg
new file mode 100644
index 00000000000..dc72dcea553
--- /dev/null
+++ b/interface/resources/qml/overte/icons/search.svg
@@ -0,0 +1,2 @@
+
+
diff --git a/interface/resources/qml/overte/icons/send.svg b/interface/resources/qml/overte/icons/send.svg
new file mode 100644
index 00000000000..9d81595d392
--- /dev/null
+++ b/interface/resources/qml/overte/icons/send.svg
@@ -0,0 +1,2 @@
+
+
diff --git a/interface/resources/qml/overte/icons/settings_cog.svg b/interface/resources/qml/overte/icons/settings_cog.svg
new file mode 100644
index 00000000000..1662ccd361b
--- /dev/null
+++ b/interface/resources/qml/overte/icons/settings_cog.svg
@@ -0,0 +1,2 @@
+
+
diff --git a/interface/resources/qml/overte/icons/skip_backward.svg b/interface/resources/qml/overte/icons/skip_backward.svg
new file mode 100644
index 00000000000..d8075f911c1
--- /dev/null
+++ b/interface/resources/qml/overte/icons/skip_backward.svg
@@ -0,0 +1,2 @@
+
+
diff --git a/interface/resources/qml/overte/icons/skip_forward.svg b/interface/resources/qml/overte/icons/skip_forward.svg
new file mode 100644
index 00000000000..c4696316862
--- /dev/null
+++ b/interface/resources/qml/overte/icons/skip_forward.svg
@@ -0,0 +1,2 @@
+
+
diff --git a/interface/resources/qml/overte/icons/speaker_active.svg b/interface/resources/qml/overte/icons/speaker_active.svg
new file mode 100644
index 00000000000..3f9a72c1118
--- /dev/null
+++ b/interface/resources/qml/overte/icons/speaker_active.svg
@@ -0,0 +1,2 @@
+
+
diff --git a/interface/resources/qml/overte/icons/speaker_inactive.svg b/interface/resources/qml/overte/icons/speaker_inactive.svg
new file mode 100644
index 00000000000..2ba23eb8023
--- /dev/null
+++ b/interface/resources/qml/overte/icons/speaker_inactive.svg
@@ -0,0 +1,2 @@
+
+
diff --git a/interface/resources/qml/overte/icons/speaker_muted.svg b/interface/resources/qml/overte/icons/speaker_muted.svg
new file mode 100644
index 00000000000..88baaa884cf
--- /dev/null
+++ b/interface/resources/qml/overte/icons/speaker_muted.svg
@@ -0,0 +1,2 @@
+
+
diff --git a/interface/resources/qml/overte/icons/triangle_down.svg b/interface/resources/qml/overte/icons/triangle_down.svg
new file mode 100644
index 00000000000..be961ea6755
--- /dev/null
+++ b/interface/resources/qml/overte/icons/triangle_down.svg
@@ -0,0 +1,2 @@
+
+
diff --git a/interface/resources/qml/overte/icons/triangle_left.svg b/interface/resources/qml/overte/icons/triangle_left.svg
new file mode 100644
index 00000000000..400b2a50bdf
--- /dev/null
+++ b/interface/resources/qml/overte/icons/triangle_left.svg
@@ -0,0 +1,2 @@
+
+
diff --git a/interface/resources/qml/overte/icons/triangle_right.svg b/interface/resources/qml/overte/icons/triangle_right.svg
new file mode 100644
index 00000000000..0c09d409d9b
--- /dev/null
+++ b/interface/resources/qml/overte/icons/triangle_right.svg
@@ -0,0 +1,2 @@
+
+
diff --git a/interface/resources/qml/overte/icons/triangle_up.svg b/interface/resources/qml/overte/icons/triangle_up.svg
new file mode 100644
index 00000000000..4f683634316
--- /dev/null
+++ b/interface/resources/qml/overte/icons/triangle_up.svg
@@ -0,0 +1,2 @@
+
+
diff --git a/interface/resources/qml/overte/icons/unset_avatar.svg b/interface/resources/qml/overte/icons/unset_avatar.svg
new file mode 100644
index 00000000000..033a6abaf3b
--- /dev/null
+++ b/interface/resources/qml/overte/icons/unset_avatar.svg
@@ -0,0 +1,2 @@
+
+
diff --git a/interface/resources/qml/overte/icons/users.svg b/interface/resources/qml/overte/icons/users.svg
new file mode 100644
index 00000000000..ba42470f859
--- /dev/null
+++ b/interface/resources/qml/overte/icons/users.svg
@@ -0,0 +1,2 @@
+
+
diff --git a/interface/resources/qml/overte/icons_src/add_friend.svg b/interface/resources/qml/overte/icons_src/add_friend.svg
new file mode 100644
index 00000000000..3894d6a3b95
--- /dev/null
+++ b/interface/resources/qml/overte/icons_src/add_friend.svg
@@ -0,0 +1,2 @@
+
+
diff --git a/interface/resources/qml/overte/icons_src/admin_shield.svg b/interface/resources/qml/overte/icons_src/admin_shield.svg
new file mode 100644
index 00000000000..1414c45e590
--- /dev/null
+++ b/interface/resources/qml/overte/icons_src/admin_shield.svg
@@ -0,0 +1,78 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/interface/resources/qml/overte/icons_src/arrow_up.svg b/interface/resources/qml/overte/icons_src/arrow_up.svg
new file mode 100644
index 00000000000..86a760fb64a
--- /dev/null
+++ b/interface/resources/qml/overte/icons_src/arrow_up.svg
@@ -0,0 +1,67 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/interface/resources/qml/overte/icons_src/avatars.svg b/interface/resources/qml/overte/icons_src/avatars.svg
new file mode 100644
index 00000000000..a92c54814a7
--- /dev/null
+++ b/interface/resources/qml/overte/icons_src/avatars.svg
@@ -0,0 +1,410 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/interface/resources/qml/overte/icons_src/broadcast.svg b/interface/resources/qml/overte/icons_src/broadcast.svg
new file mode 100644
index 00000000000..ea3f6b9f853
--- /dev/null
+++ b/interface/resources/qml/overte/icons_src/broadcast.svg
@@ -0,0 +1,103 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/interface/resources/qml/overte/icons_src/close.svg b/interface/resources/qml/overte/icons_src/close.svg
new file mode 100644
index 00000000000..a1fef407dc0
--- /dev/null
+++ b/interface/resources/qml/overte/icons_src/close.svg
@@ -0,0 +1,67 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/interface/resources/qml/overte/icons_src/copy.svg b/interface/resources/qml/overte/icons_src/copy.svg
new file mode 100644
index 00000000000..7c67bf09ee7
--- /dev/null
+++ b/interface/resources/qml/overte/icons_src/copy.svg
@@ -0,0 +1,68 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/interface/resources/qml/overte/icons_src/delete.svg b/interface/resources/qml/overte/icons_src/delete.svg
new file mode 100644
index 00000000000..1783ffb0565
--- /dev/null
+++ b/interface/resources/qml/overte/icons_src/delete.svg
@@ -0,0 +1,108 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/interface/resources/qml/overte/icons_src/eye_closed.svg b/interface/resources/qml/overte/icons_src/eye_closed.svg
new file mode 100644
index 00000000000..963d827da6a
--- /dev/null
+++ b/interface/resources/qml/overte/icons_src/eye_closed.svg
@@ -0,0 +1,84 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/interface/resources/qml/overte/icons_src/eye_open.svg b/interface/resources/qml/overte/icons_src/eye_open.svg
new file mode 100644
index 00000000000..d5988f287bc
--- /dev/null
+++ b/interface/resources/qml/overte/icons_src/eye_open.svg
@@ -0,0 +1,69 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/interface/resources/qml/overte/icons_src/filter.svg b/interface/resources/qml/overte/icons_src/filter.svg
new file mode 100644
index 00000000000..913041ad0d0
--- /dev/null
+++ b/interface/resources/qml/overte/icons_src/filter.svg
@@ -0,0 +1,68 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/interface/resources/qml/overte/icons_src/folder.svg b/interface/resources/qml/overte/icons_src/folder.svg
new file mode 100644
index 00000000000..f05c66fda90
--- /dev/null
+++ b/interface/resources/qml/overte/icons_src/folder.svg
@@ -0,0 +1,154 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/interface/resources/qml/overte/icons_src/gold_star.svg b/interface/resources/qml/overte/icons_src/gold_star.svg
new file mode 100644
index 00000000000..3cf28b19080
--- /dev/null
+++ b/interface/resources/qml/overte/icons_src/gold_star.svg
@@ -0,0 +1,122 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/interface/resources/qml/overte/icons_src/home.svg b/interface/resources/qml/overte/icons_src/home.svg
new file mode 100644
index 00000000000..bb60859c10f
--- /dev/null
+++ b/interface/resources/qml/overte/icons_src/home.svg
@@ -0,0 +1,68 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/interface/resources/qml/overte/icons_src/info.svg b/interface/resources/qml/overte/icons_src/info.svg
new file mode 100644
index 00000000000..fe836b5fb48
--- /dev/null
+++ b/interface/resources/qml/overte/icons_src/info.svg
@@ -0,0 +1,68 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/interface/resources/qml/overte/icons_src/pause.svg b/interface/resources/qml/overte/icons_src/pause.svg
new file mode 100644
index 00000000000..8e537ed0743
--- /dev/null
+++ b/interface/resources/qml/overte/icons_src/pause.svg
@@ -0,0 +1,76 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/interface/resources/qml/overte/icons_src/pencil.svg b/interface/resources/qml/overte/icons_src/pencil.svg
new file mode 100644
index 00000000000..fef919a80a6
--- /dev/null
+++ b/interface/resources/qml/overte/icons_src/pencil.svg
@@ -0,0 +1,77 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/interface/resources/qml/overte/icons_src/plus.svg b/interface/resources/qml/overte/icons_src/plus.svg
new file mode 100644
index 00000000000..74b09c41340
--- /dev/null
+++ b/interface/resources/qml/overte/icons_src/plus.svg
@@ -0,0 +1,72 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/interface/resources/qml/overte/icons_src/reload.svg b/interface/resources/qml/overte/icons_src/reload.svg
new file mode 100644
index 00000000000..8e315f2b86a
--- /dev/null
+++ b/interface/resources/qml/overte/icons_src/reload.svg
@@ -0,0 +1,76 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/interface/resources/qml/overte/icons_src/remove_friend.svg b/interface/resources/qml/overte/icons_src/remove_friend.svg
new file mode 100644
index 00000000000..d5906ccfb3d
--- /dev/null
+++ b/interface/resources/qml/overte/icons_src/remove_friend.svg
@@ -0,0 +1,81 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/interface/resources/qml/overte/icons_src/search.svg b/interface/resources/qml/overte/icons_src/search.svg
new file mode 100644
index 00000000000..b1a0037eb00
--- /dev/null
+++ b/interface/resources/qml/overte/icons_src/search.svg
@@ -0,0 +1,69 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/interface/resources/qml/overte/icons_src/send.svg b/interface/resources/qml/overte/icons_src/send.svg
new file mode 100644
index 00000000000..f6d42c4c67b
--- /dev/null
+++ b/interface/resources/qml/overte/icons_src/send.svg
@@ -0,0 +1,62 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/interface/resources/qml/overte/icons_src/settings_cog.svg b/interface/resources/qml/overte/icons_src/settings_cog.svg
new file mode 100644
index 00000000000..06f58ef8537
--- /dev/null
+++ b/interface/resources/qml/overte/icons_src/settings_cog.svg
@@ -0,0 +1,197 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/interface/resources/qml/overte/icons_src/skip_backward.svg b/interface/resources/qml/overte/icons_src/skip_backward.svg
new file mode 100644
index 00000000000..9ff33f128fc
--- /dev/null
+++ b/interface/resources/qml/overte/icons_src/skip_backward.svg
@@ -0,0 +1,68 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/interface/resources/qml/overte/icons_src/skip_forward.svg b/interface/resources/qml/overte/icons_src/skip_forward.svg
new file mode 100644
index 00000000000..92c5ac673c3
--- /dev/null
+++ b/interface/resources/qml/overte/icons_src/skip_forward.svg
@@ -0,0 +1,68 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/interface/resources/qml/overte/icons_src/speaker.svg b/interface/resources/qml/overte/icons_src/speaker.svg
new file mode 100644
index 00000000000..c45eb64b1ad
--- /dev/null
+++ b/interface/resources/qml/overte/icons_src/speaker.svg
@@ -0,0 +1,94 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/interface/resources/qml/overte/icons_src/triangle_down.svg b/interface/resources/qml/overte/icons_src/triangle_down.svg
new file mode 100644
index 00000000000..61077b34108
--- /dev/null
+++ b/interface/resources/qml/overte/icons_src/triangle_down.svg
@@ -0,0 +1,63 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/interface/resources/qml/overte/icons_src/triangle_left.svg b/interface/resources/qml/overte/icons_src/triangle_left.svg
new file mode 100644
index 00000000000..d5c80b69e90
--- /dev/null
+++ b/interface/resources/qml/overte/icons_src/triangle_left.svg
@@ -0,0 +1,63 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/interface/resources/qml/overte/icons_src/triangle_right.svg b/interface/resources/qml/overte/icons_src/triangle_right.svg
new file mode 100644
index 00000000000..5e81b99d186
--- /dev/null
+++ b/interface/resources/qml/overte/icons_src/triangle_right.svg
@@ -0,0 +1,62 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/interface/resources/qml/overte/icons_src/triangle_up.svg b/interface/resources/qml/overte/icons_src/triangle_up.svg
new file mode 100644
index 00000000000..ec54a2ae794
--- /dev/null
+++ b/interface/resources/qml/overte/icons_src/triangle_up.svg
@@ -0,0 +1,63 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/interface/resources/qml/overte/icons_src/unset_avatar.svg b/interface/resources/qml/overte/icons_src/unset_avatar.svg
new file mode 100644
index 00000000000..32efcc20c69
--- /dev/null
+++ b/interface/resources/qml/overte/icons_src/unset_avatar.svg
@@ -0,0 +1,79 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/interface/resources/qml/overte/icons_src/users.svg b/interface/resources/qml/overte/icons_src/users.svg
new file mode 100644
index 00000000000..68c2b0e27bc
--- /dev/null
+++ b/interface/resources/qml/overte/icons_src/users.svg
@@ -0,0 +1,91 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/interface/resources/qml/overte/more_apps/AppDelegate.qml b/interface/resources/qml/overte/more_apps/AppDelegate.qml
new file mode 100644
index 00000000000..d2b1197d41d
--- /dev/null
+++ b/interface/resources/qml/overte/more_apps/AppDelegate.qml
@@ -0,0 +1,229 @@
+import QtQuick
+import QtQuick.Layouts
+
+import ".." as Overte
+
+Rectangle {
+ id: item
+ width: ListView.view.contentWidth
+ implicitHeight: column.implicitHeight
+ color: index % 2 === 0 ? Overte.Theme.paletteActive.base : Overte.Theme.paletteActive.alternateBase
+
+ required property int index
+
+ required property string name
+ required property string description
+ required property url icon
+ required property string author
+ required property string scriptUrl
+ required property string appID
+ required property string repoHost
+
+ property bool running: moreApps.runningScripts.includes(scriptUrl)
+ property bool installed: moreApps.installedScripts.includes(scriptUrl)
+ property bool processing: false
+
+ Connections {
+ target: moreApps
+ ignoreUnknownSignals: false
+
+ function onRunningScriptsChanged() {
+ processing = false;
+ }
+ }
+
+ ColumnLayout {
+ anchors.fill: parent
+ id: column
+
+ RowLayout {
+ Layout.margins: 8
+ Layout.fillWidth: true
+
+ Overte.RoundButton {
+ id: dropdownButton
+ checkable: true
+ icon.source: checked ? "../icons/triangle_up.svg" : "../icons/triangle_down.svg"
+ icon.width: 24
+ icon.height: 24
+ icon.color: Overte.Theme.paletteActive.buttonText
+ }
+
+ Rectangle {
+ Layout.preferredWidth: 64
+ Layout.preferredHeight: 64
+ color: Overte.Theme.paletteActive.appIconBackground
+ radius: Overte.Theme.borderRadius
+
+ border.width: Math.max(2, Overte.Theme.borderWidth)
+ border.color: {
+ if (installed && running) {
+ return Overte.Theme.paletteActive.appInstalledRunning;
+ } else if (installed && !running) {
+ return Overte.Theme.paletteActive.appInstalledNotRunning;
+ } else if (!installed && running) {
+ return Overte.Theme.paletteActive.appNotInstalledRunning;
+ } else {
+ return Overte.Theme.paletteActive.appNotInstalled;
+ }
+ }
+
+ Image {
+ anchors.fill: parent
+ anchors.margins: 8
+ source: icon
+ sourceSize.width: width
+ sourceSize.height: height
+ }
+ }
+
+ ColumnLayout {
+ Layout.fillWidth: true
+
+ Overte.Label {
+ Layout.fillWidth: true
+ wrapMode: Text.Wrap
+ text: name
+ }
+
+ Overte.Label {
+ Layout.fillWidth: true
+ font.pixelSize: Overte.Theme.fontPixelSizeSmall
+ opacity: Overte.Theme.highContrast ? 1.0 : 0.7
+ visible: !dropdownButton.checked
+ elide: Text.ElideRight
+ text: description
+ }
+
+ Overte.Label {
+ Layout.fillWidth: true
+ font.pixelSize: Overte.Theme.fontPixelSizeSmall
+ opacity: Overte.Theme.highContrast ? 1.0 : 0.7
+ visible: dropdownButton.checked
+ wrapMode: Text.Wrap
+ text: {
+ if (running && installed) {
+ return qsTr("Running");
+ } else if (running && !installed) {
+ return qsTr("Running, not installed");
+ } else if (!running && installed) {
+ return qsTr("Paused");
+ } else {
+ return "";
+ }
+ }
+ }
+ }
+
+ Overte.RoundButton {
+ visible: installed || running
+ enabled: !processing
+ icon.source: "../icons/delete.svg"
+ icon.width: 24
+ icon.height: 24
+ icon.color: Overte.Theme.paletteActive.buttonText
+ backgroundColor: Overte.Theme.paletteActive.buttonDestructive
+
+ onClicked: {
+ processing = true;
+ ScriptDiscoveryService.stopScript(scriptUrl);
+
+ // only variable assignments are automatically tracked by Qt
+ const indexOf = moreApps.installedScripts.indexOf(scriptUrl);
+ moreApps.installedScripts.splice(indexOf);
+ moreApps.installedScriptsChanged();
+
+ moreApps.refreshRunningScripts();
+ moreApps.refreshFilteredModel();
+ }
+ }
+
+ Overte.RoundButton {
+ enabled: !processing
+ visible: installed && running
+ icon.source: "../icons/reload.svg"
+ icon.width: 24
+ icon.height: 24
+ icon.color: Overte.Theme.paletteActive.buttonText
+ onClicked: ScriptDiscoveryService.stopScript(scriptUrl, true)
+ }
+
+ Overte.RoundButton {
+ enabled: !processing
+ visible: installed
+ icon.source: running ? "../icons/pause.svg" : "../icons/triangle_right.svg"
+ icon.width: 24
+ icon.height: 24
+ icon.color: Overte.Theme.paletteActive.buttonText
+ onClicked: {
+ if (running) {
+ ScriptDiscoveryService.stopScript(scriptUrl, false);
+ } else {
+ ScriptDiscoveryService.loadScript(scriptUrl, false);
+ }
+
+ processing = true;
+ moreApps.refreshRunningScripts();
+ moreApps.refreshFilteredModel();
+ }
+ }
+
+ Overte.RoundButton {
+ id: installButton
+ enabled: !processing
+ visible: !installed
+ backgroundColor: Overte.Theme.paletteActive.buttonAdd
+
+ icon.source: "../icons/plus.svg"
+ icon.width: 24
+ icon.height: 24
+ icon.color: Overte.Theme.paletteActive.buttonText
+
+ onClicked: {
+ processing = true;
+ ScriptDiscoveryService.loadScript(scriptUrl, false);
+
+ // only variable assignments are automatically tracked by Qt
+ moreApps.installedScripts.push(scriptUrl);
+ moreApps.installedScriptsChanged();
+
+ moreApps.refreshRunningScripts();
+ moreApps.refreshFilteredModel();
+ }
+ }
+ }
+
+ RowLayout {
+ Layout.leftMargin: 8
+ Layout.rightMargin: 8
+ Layout.fillWidth: true
+ visible: dropdownButton.checked
+
+ Overte.Label {
+ Layout.fillWidth: true
+ horizontalAlignment: Text.AlignLeft
+ font.pixelSize: Overte.Theme.fontPixelSizeSmall
+ opacity: Overte.Theme.highContrast ? 1.0 : 0.7
+ text: qsTr("By %1").arg(author)
+ }
+
+ Overte.Label {
+ Layout.fillWidth: true
+ horizontalAlignment: Text.AlignRight
+ font.pixelSize: Overte.Theme.fontPixelSizeSmall
+ opacity: Overte.Theme.highContrast ? 1.0 : 0.7
+ text: `${appID} @ ${repoHost}`
+ }
+ }
+
+ Overte.Label {
+ Layout.leftMargin: 8
+ Layout.rightMargin: 8
+ Layout.bottomMargin: 8
+ Layout.fillWidth: true
+ visible: dropdownButton.checked
+ wrapMode: Text.Wrap
+ text: description
+ }
+ }
+}
diff --git a/interface/resources/qml/overte/more_apps/MoreApps.qml b/interface/resources/qml/overte/more_apps/MoreApps.qml
new file mode 100644
index 00000000000..7adff94d1c9
--- /dev/null
+++ b/interface/resources/qml/overte/more_apps/MoreApps.qml
@@ -0,0 +1,468 @@
+import QtCore as QtCore
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Layouts
+
+import ".." as Overte
+import "."
+
+Rectangle {
+ id: moreApps
+ implicitWidth: 480
+ implicitHeight: 720
+ color: Overte.Theme.paletteActive.base
+
+ QtCore.Settings {
+ id: settings
+ category: "moreApp"
+ property list installedScripts: []
+ property list repoSources: []
+ }
+
+ property alias installedScripts: settings.installedScripts
+ property alias repoSources: settings.repoSources
+
+ property string searchExpression: ".*"
+ property list rawListModel: []
+ property list filteredModel: []
+
+ property list runningScripts: []
+
+ // the running scripts list doesn't immediately update, so give it a bit to update
+ Timer {
+ id: runningScriptsRefreshTimer
+ running: false
+ repeat: false
+ interval: 500
+
+ onTriggered: {
+ runningScripts = ScriptDiscoveryService.getRunning().map(x => x.url);
+ }
+ }
+
+ function refreshRunningScripts() {
+ runningScriptsRefreshTimer.start();
+ }
+
+ function refreshFilteredModel() {
+ const searchRegex = new RegExp(searchExpression, "i");
+ let tmp = [];
+
+ for (const item of rawListModel) {
+ // if onlyInstalled is checked and we're not running or installed, skip
+ if (onlyInstalled.checked) {
+ if (
+ !runningScripts.includes(item.scriptUrl) &&
+ !installedScripts.includes(item.scriptUrl)
+ ) {
+ continue;
+ }
+ }
+
+ if (
+ item.name.match(searchRegex) ||
+ item.id.match(searchRegex) ||
+ item.description.match(searchRegex)
+ ) {
+ tmp.push(item);
+ }
+ }
+
+ tmp.sort((a, b) => (
+ a.name.localeCompare(b.name)
+ ));
+
+ filteredModel = tmp;
+ }
+
+ onRawListModelChanged: refreshFilteredModel()
+
+ function parseList(list, repo) {
+ if (list.version !== 2) {
+ console.warn(`App list "${list}" is version ${list.version}, expected 2`);
+ return;
+ }
+ const repoHost = (new URL(repo)).hostname;
+
+ let tmp = [];
+
+ for (const item of list.applicationList) {
+ if (!item.appActive) { continue; }
+
+ const baseUrl = `${repo}/applications`;
+ const scriptUrl = `${baseUrl}/${item.appBaseDirectory}/${item.appScriptVersions.Stable}`;
+
+ tmp.push({
+ appID: item.appBaseDirectory,
+ name: item.appName,
+ description: item.appDescription,
+ scriptUrl: scriptUrl,
+ author: item.appAuthor,
+ icon: `${baseUrl}/${item.appBaseDirectory}/${item.appIcon}`,
+ repoHost: repoHost,
+ });
+ }
+
+ rawListModel = rawListModel.concat(tmp);
+ }
+
+ property var pendingRequests: ({})
+
+ function fetchList(repo) {
+ let xhr = new XMLHttpRequest();
+
+ pendingRequests[repo] = {
+ abort: () => xhr.abort(),
+ expectedSize: 1,
+ currentSize: 0,
+ };
+
+ xhr.onreadystatechange = () => {
+ if (xhr.readyState === XMLHttpRequest.HEADERS_RECEIVED) {
+ const length = xhr.getResponseHeader("Content-Length");
+ if (length !== "") {
+ pendingRequests[repo].expectedSize = Number(length);
+ }
+ } else if (xhr.readyState === XMLHttpRequest.LOADING) {
+ pendingRequests[repo].currentSize = xhr.response.length;
+ } else if (xhr.readyState === XMLHttpRequest.DONE) {
+ if (xhr.status !== 200) {
+ console.warn(repo, xhr.status, xhr.statusText);
+ downloadProgressLabel.updateText();
+ delete pendingRequests[repo];
+ pendingRequestsChanged();
+ return;
+ }
+
+ pendingRequests[repo].currentSize = xhr.response.length;
+
+ try {
+ const data = JSON.parse(xhr.response);
+ parseList(data, repo);
+ } catch(e) {
+ console.error(repo, e);
+ }
+
+ delete pendingRequests[repo];
+ }
+
+ downloadProgressLabel.updateText();
+ pendingRequestsChanged();
+ };
+
+ xhr.open("GET", `${repo}/applications/metadata.json`);
+ xhr.send();
+
+ pendingRequestsChanged();
+ downloadProgressLabel.updateText();
+ }
+
+ function clearAllPendingRequests() {
+ for (const req of Object.values(pendingRequests)) {
+ req.abort();
+ }
+
+ pendingRequests = [];
+ }
+
+ function fetchAllLists() {
+ // cancel any downloads that are already in-flight
+ // so we don't accidentally get two responses for the same repo
+ clearAllPendingRequests();
+
+ rawListModel = [];
+ filteredModel = [];
+
+ for (const repo of repoSources) {
+ fetchList(repo);
+ }
+ }
+
+ onRepoSourcesChanged: {
+ refreshRunningScripts();
+ fetchAllLists();
+ }
+
+ Component.onCompleted: {
+ // don't put this in the default because it'll load
+ // the default first, *then* the actual repos stored
+ // in the settings. this way we don't double up on requests
+ if (repoSources.length === 0) {
+ repoSources.push("https://more.overte.org");
+ }
+ }
+
+ Component.onDestruction: {
+ // we're quitting, cancel any downloads that are already in-flight
+ // so they don't try to update items that have been destroyed
+ clearAllPendingRequests();
+ }
+
+ Component {
+ id: settingsPage
+
+ ColumnLayout {
+ RowLayout {
+ Layout.fillWidth: true
+
+ Overte.RoundButton {
+ icon.source: "../icons/triangle_left.svg"
+ icon.width: 24
+ icon.height: 24
+ icon.color: Overte.Theme.paletteActive.buttonText
+ onClicked: stack.pop()
+ }
+
+ Overte.Label {
+ Layout.fillWidth: true
+ horizontalAlignment: Text.AlignHCenter
+ verticalAlignment: Text.AlignVCenter
+ text: qsTr("Settings - More Apps")
+ }
+ }
+
+ Item {
+ Layout.fillWidth: true
+ implicitHeight: 12
+ }
+
+ Overte.Label {
+ Layout.fillWidth: true
+ horizontalAlignment: Text.AlignHCenter
+ verticalAlignment: Text.AlignBottom
+ text: qsTr("App Sources")
+ opacity: Overte.Theme.highContrast ? 1.0 : 0.6
+ }
+
+ Overte.Ruler { Layout.fillWidth: true }
+
+ ListView {
+ Layout.fillWidth: true
+
+ // QT6TODO: remove this when mouse inputs are working properly
+ interactive: false
+
+ clip: true
+ ScrollBar.vertical: Overte.ScrollBar {
+ policy: ScrollBar.AlwaysOn
+ }
+ contentWidth: width - ScrollBar.vertical.width
+
+ implicitHeight: (Overte.Theme.fontPixelSizeSmall * 3) * 6
+
+ model: moreApps.repoSources
+ delegate: Rectangle {
+ required property int index
+ readonly property var text: moreApps.repoSources[index]
+
+ width: ListView.view.contentWidth
+ implicitHeight: Overte.Theme.fontPixelSizeSmall * 3
+ color: index % 2 === 0 ? Overte.Theme.paletteActive.base : Overte.Theme.paletteActive.alternateBase
+
+ Overte.Label {
+ anchors.margins: 4
+ anchors.left: parent.left
+ anchors.right: sourceRemoveButton.right
+ anchors.verticalCenter: parent.verticalCenter
+ font.pixelSize: Overte.Theme.fontPixelSizeSmall
+ elide: Text.ElideRight
+
+ // FIXME: shouldn't ever be undefined, but sometimes is anyway?
+ text: parent.text ?? "undefined"
+ }
+
+ Overte.RoundButton {
+ id: sourceRemoveButton
+ anchors.margins: 4
+ anchors.right: parent.right
+ anchors.verticalCenter: parent.verticalCenter
+
+ backgroundColor: (
+ (hovered || Overte.Theme.highContrast) ?
+ Overte.Theme.paletteActive.buttonDestructive :
+ Overte.Theme.paletteActive.button
+ )
+
+ horizontalPadding: 0
+ verticalPadding: 0
+ implicitWidth: 32
+ implicitHeight: 32
+
+ icon.source: "../icons/close.svg"
+ icon.width: 18
+ icon.height: 18
+ icon.color: Overte.Theme.paletteActive.buttonText
+
+ onClicked: {
+ // FIXME: this throws an error about not being able to
+ // resolve "moreApps", but it seems to work fine anyway?
+ moreApps.repoSources.splice(index, 1)
+ moreApps.repoSourcesChanged()
+ }
+ }
+ }
+ }
+
+ RowLayout {
+ Layout.fillWidth: true
+
+ Overte.TextField {
+ Layout.fillWidth: true
+
+ id: newSourceField
+ placeholderText: qsTr("App list source URL")
+ }
+
+ Overte.RoundButton {
+ icon.source: "../icons/plus.svg"
+ icon.width: 24
+ icon.height: 24
+ icon.color: Overte.Theme.paletteActive.buttonText
+ backgroundColor: Overte.Theme.paletteActive.buttonAdd
+
+ enabled: !!newSourceField.text.match(/^https?:\/\/.+/i)
+
+ onClicked: {
+ moreApps.repoSources.push(newSourceField.text.replace(/\/$/g, ""));
+ moreApps.repoSourcesChanged();
+ newSourceField.text = "";
+ }
+ }
+ }
+
+ // spacer
+ Item {
+ Layout.fillWidth: true
+ Layout.fillHeight: true
+ }
+ }
+ }
+
+ Overte.StackView {
+ id: stack
+ anchors.top: parent.top
+ anchors.left: parent.left
+ anchors.right: parent.right
+ anchors.bottom: downloadProgressLabel.top
+
+ initialItem: ColumnLayout {
+ RowLayout {
+ Layout.margins: 4
+
+ Overte.RoundButton {
+ icon.source: "../icons/settings_cog.svg"
+ icon.width: 24
+ icon.height: 24
+
+ onClicked: stack.push(settingsPage.createObject(stack))
+ }
+
+ Overte.RoundButton {
+ // TODO: is the eye icon acceptable here? it feels too vague to grok
+ icon.source: checked ? "../icons/eye_closed.svg" : "../icons/eye_open.svg"
+ icon.width: 24
+ icon.height: 24
+
+ id: onlyInstalled
+ checkable: true
+ onToggled: refreshFilteredModel()
+
+ backgroundColor: (
+ checked ?
+ Overte.Theme.paletteActive.highlight :
+ Overte.Theme.paletteActive.button
+ )
+ color: (
+ checked ?
+ Overte.Theme.paletteActive.highlightedText :
+ Overte.Theme.paletteActive.buttonText
+ )
+
+ Overte.ToolTip { text: qsTr("Only show installed and running apps") }
+ }
+
+ Overte.TextField {
+ Layout.fillWidth: true
+ placeholderText: qsTr("Search…")
+ id: searchField
+
+ Keys.onEnterPressed: {
+ searchButton.clicked();
+ forceActiveFocus();
+ }
+
+ Keys.onReturnPressed: {
+ searchButton.clicked();
+ forceActiveFocus();
+ }
+ }
+
+ Overte.RoundButton {
+ icon.source: "../icons/search.svg"
+ icon.width: 24
+ icon.height: 24
+ icon.color: Overte.Theme.paletteActive.buttonText
+ id: searchButton
+
+ onClicked: {
+ moreApps.searchExpression = searchField.text;
+ moreApps.refreshFilteredModel();
+ }
+ }
+ }
+
+ Overte.Label {
+ Layout.fillWidth: true
+ Layout.fillHeight: true
+ horizontalAlignment: Text.AlignHCenter
+ verticalAlignment: Text.AlignVCenter
+ text: qsTr("Loading %n app source(s)…", "", repoSources.length)
+ visible: rawListModel.length === 0
+ }
+
+ ListView {
+ Layout.fillWidth: true
+ Layout.fillHeight: true
+ visible: rawListModel.length > 0
+ clip: true
+
+ // QT6TODO: broken mouse input, remove when that's fixed
+ interactive: false
+
+ ScrollBar.vertical: Overte.ScrollBar {}
+ contentWidth: width - ScrollBar.vertical.width
+
+ model: filteredModel
+ delegate: AppDelegate {}
+ }
+ }
+ }
+
+ Overte.Label {
+ anchors.margins: 4
+ anchors.bottom: parent.bottom
+ anchors.left: parent.left
+ anchors.right: parent.right
+
+ id: downloadProgressLabel
+ visible: Object.values(pendingRequests).length !== 0
+
+ function updateText() {
+ const requests = Object.values(pendingRequests);
+
+ if (requests.length === 0) {
+ this.text = "";
+ return;
+ }
+
+ let accum = 0;
+
+ for (const req of requests) {
+ accum = (req.currentSize / req.expectedSize);
+ }
+
+ const percent = Math.round((accum / requests.length) * 100);
+ this.text = `${qsTr("Downloading…")} ${percent}%`;
+ }
+ }
+}
diff --git a/interface/resources/qml/overte/more_apps/qmldir b/interface/resources/qml/overte/more_apps/qmldir
new file mode 100644
index 00000000000..31d350227ae
--- /dev/null
+++ b/interface/resources/qml/overte/more_apps/qmldir
@@ -0,0 +1,3 @@
+module MoreApps
+MoreApps 1.0 MoreApps.qml
+AppDelegate 1.0 AppDelegate.qml
diff --git a/interface/resources/qml/overte/place_picker/PlaceItem.qml b/interface/resources/qml/overte/place_picker/PlaceItem.qml
new file mode 100644
index 00000000000..bdfcc20f433
--- /dev/null
+++ b/interface/resources/qml/overte/place_picker/PlaceItem.qml
@@ -0,0 +1,184 @@
+import QtQuick
+import QtQuick.Layouts
+
+import ".." as Overte
+
+Rectangle {
+ id: item
+ color: compatible ? Overte.Theme.paletteActive.alternateBase : Overte.Theme.paletteActive.buttonDestructive
+
+ implicitWidth: gridView.cellWidth
+ implicitHeight: gridView.cellHeight
+
+ required property int index
+ readonly property var modelData: gridView.model[index]
+
+ required property string name
+ readonly property string domainName: modelData.domain.name
+ readonly property url thumbnail: modelData.thumbnail ?? ""
+ readonly property bool compatible: modelData.compatibleProtocol ?? true
+ readonly property url placeUrl: `hifi://${name}${modelData.path}`
+
+ readonly property int currentUsers: modelData.current_attendance ?? 0
+ readonly property int maxUsers: {
+ const capacity = modelData.domain.capacity;
+ return (capacity !== 0) ? capacity : 9999;
+ }
+
+ readonly property color textBackgroundColor: {
+ if (Overte.Theme.highContrast) {
+ return Overte.Theme.darkMode ? "black" : "white"
+ } else {
+ return Overte.Theme.darkMode ? "#d0000000" : "#e0ffffff"
+ }
+ }
+
+ readonly property bool hovered: mouseArea.containsMouse || infoButton.hovered || joinButton.hovered
+
+ Overte.Label {
+ anchors.margins: Overte.Theme.borderWidth
+ anchors.fill: item
+ visible: thumbnailImage.status !== Image.Ready
+ opacity: Overte.Theme.highContrast ? 1.0 : 0.6
+ wrapMode: Text.Wrap
+ horizontalAlignment: Text.AlignHCenter
+ verticalAlignment: Text.AlignVCenter
+ text: thumbnailImage.status === Image.Loading ? "Loading…" : "No icon"
+ }
+
+ Image {
+ anchors.margins: Overte.Theme.borderWidth
+ anchors.fill: item
+ id: thumbnailImage
+
+ source: thumbnail
+ sourceSize.width: width
+ sourceSize.height: height
+ fillMode: Image.PreserveAspectCrop
+ }
+
+ Rectangle {
+ anchors.margins: Overte.Theme.borderWidth
+ anchors.top: item.top
+ anchors.left: item.left
+
+ width: Math.min(titleText.implicitWidth + 8, item.width - (2 * item.border.width))
+ height: Math.min(titleText.implicitHeight + 8, item.height - (2 * item.border.width))
+ color: textBackgroundColor
+
+ Overte.Label {
+ anchors.margins: 4
+ anchors.fill: parent
+
+ id: titleText
+ text: name
+ font.bold: true
+ font.pixelSize: Overte.Theme.fontPixelSizeSmall
+ horizontalAlignment: Text.AlignLeft
+ verticalAlignment: Text.AlignTop
+ }
+ }
+
+ Rectangle {
+ anchors.margins: Overte.Theme.borderWidth
+ anchors.bottom: item.bottom
+ anchors.left: item.left
+
+ width: Math.min(userCountText.implicitWidth + 8, item.width - (2 * item.border.width))
+ height: Math.min(userCountText.implicitHeight + 8, item.height - (2 * item.border.width))
+ color: textBackgroundColor
+
+ Overte.Label {
+ anchors.margins: 4
+ anchors.fill: parent
+
+ id: userCountText
+ text: {
+ if (maxUsers === 9999) {
+ return `${domainName}: ${currentUsers}`;
+ } else {
+ return `${domainName}: ${currentUsers}/${maxUsers}`;
+ }
+ }
+ color: {
+ if (currentUsers === 0) {
+ return Overte.Theme.paletteActive.userCountEmpty;
+ } else if (currentUsers < maxUsers) {
+ return Overte.Theme.paletteActive.userCountActive;
+ } else {
+ return Overte.Theme.paletteActive.userCountFull;
+ }
+ }
+ font.bold: true
+ font.pixelSize: Overte.Theme.fontPixelSizeXSmall
+ horizontalAlignment: Text.AlignLeft
+ verticalAlignment: Text.AlignTop
+ elide: Text.ElideLeft
+ }
+ }
+
+ MouseArea {
+ anchors.fill: item
+ hoverEnabled: true
+ propagateComposedEvents: true
+ id: mouseArea
+ }
+
+ Overte.RoundButton {
+ anchors.margins: 4
+ anchors.top: item.top
+ anchors.right: item.right
+ id: infoButton
+
+ implicitWidth: 32
+ implicitHeight: 32
+ horizontalPadding: 0
+ verticalPadding: 0
+
+ opacity: {
+ if (item.hovered) {
+ return Overte.Theme.highContrast || hovered ? 1.0 : 0.9;
+ } else {
+ return 0.0;
+ }
+ }
+ backgroundColor: Overte.Theme.paletteActive.buttonInfo
+
+ icon.source: "../icons/info.svg"
+ icon.width: 24
+ icon.height: 24
+ icon.color: Overte.Theme.paletteActive.buttonText
+
+ onClicked: infoDialog.open(index)
+ }
+
+ Overte.RoundButton {
+ anchors.margins: 4
+ anchors.bottom: item.bottom
+ anchors.right: item.right
+ id: joinButton
+
+ implicitWidth: 40
+ implicitHeight: 40
+ horizontalPadding: 0
+ verticalPadding: 0
+
+ enabled: item.compatible
+ visible: item.compatible
+ opacity: {
+ if (item.hovered) {
+ return Overte.Theme.highContrast || hovered ? 1.0 : 0.9;
+ } else {
+ return 0.0;
+ }
+ }
+ backgroundColor: Overte.Theme.paletteActive.buttonAdd
+
+ icon.source: "../icons/triangle_right.svg"
+ icon.width: 24
+ icon.height: 24
+ icon.color: Overte.Theme.paletteActive.buttonText
+
+ onClicked: placePicker.goToLocation(placeUrl)
+ }
+}
diff --git a/interface/resources/qml/overte/place_picker/PlacePicker.qml b/interface/resources/qml/overte/place_picker/PlacePicker.qml
new file mode 100644
index 00000000000..0cef61fe0fa
--- /dev/null
+++ b/interface/resources/qml/overte/place_picker/PlacePicker.qml
@@ -0,0 +1,675 @@
+import QtCore as QtCore
+import QtQuick
+import QtQuick.Layouts
+import QtQuick.Controls
+import QtQuick.Dialogs as QtDialogs
+
+import "../" as Overte
+import "."
+
+Rectangle {
+ id: placePicker
+ anchors.fill: parent
+ color: Overte.Theme.paletteActive.base
+ implicitWidth: 480
+ implicitHeight: 720
+
+ property bool hasHomeButton: true
+
+ readonly property string protocolSignature: WindowScriptingInterface.protocolSignature()
+
+ QtCore.Settings {
+ id: filters
+ category: "placesApp"
+ property string searchExpression: ".*"
+ property bool includeIncompatible: false
+ property list maturity: [
+ "everyone",
+ "teen",
+ "mature",
+ "adult",
+ "unrated",
+ ]
+ property list favoritedPlaceIds: []
+ }
+
+ function goBack() {
+ let cookie = Date.now() + Math.floor(Math.random() * (1000 - -1000) + -1000);
+
+ sendToScript(JSON.stringify({
+ action: "system:location_go_back",
+ data: { cookie: cookie },
+ }));
+ }
+
+ function goForward() {
+ let cookie = Date.now() + Math.floor(Math.random() * (1000 - -1000) + -1000);
+
+ sendToScript(JSON.stringify({
+ action: "system:location_go_forward",
+ data: { cookie: cookie },
+ }));
+ }
+
+ function goToLocation(path) {
+ let cookie = Date.now() + Math.floor(Math.random() * (1000 - -1000) + -1000);
+
+ sendToScript(JSON.stringify({
+ action: "system:location_go_to",
+ data: {
+ cookie: cookie,
+ path: path,
+ },
+ }));
+ }
+
+ // The mv.overte.org directory listing is about 300KiB
+ // and takes about 10 seconds to download. Is there something
+ // we could use to safely cache the last results and only refresh
+ // after a set period?
+ property list rawPlaces: []
+ property real downloadProgress: 0.0
+
+ function delayedShuffleHash(x) {
+ const ONE_DAY_MS = 24 * 3600 * 1000;
+ const SHUFFLE_CYCLE_MS = 5 * ONE_DAY_MS;
+
+ const FNV_OFFSET = 0x811c9dc5;
+ const FNV_PRIME = 0x01000193;
+ const ALL_32BIT = 0xffffffff;
+
+ let hashAccum = (FNV_OFFSET + Math.floor(Date.now() / SHUFFLE_CYCLE_MS)) & ALL_32BIT;
+ for (let p of x) {
+ // not quite FNV-1a, because we're counting 32-bit codepoints rather than bytes
+ hashAccum ^= p;
+ hashAccum = (hashAccum * FNV_PRIME) & ALL_32BIT;
+ }
+
+ return hashAccum;
+ }
+
+ function filterPlaces() {
+ // TODO: federation support
+ const hostname = (new URL(AccountServices.metaverseServerURL)).hostname;
+ const searchExpression = new RegExp(filters.searchExpression, "i");
+ const ONE_DAY_SECS = 60 * 60 * 24;
+ let tmp = [];
+
+ for (let place of rawPlaces) {
+ const compatibleProtocol = place.domain.protocol_version === protocolSignature;
+ // ?status=active should filter out dead places already
+ //const recentHeartbeat = ((Date.now() - parseInt(place.domain.time_of_last_heartbeat_s)) / 1000) < ONE_DAY_SECS;
+ const filterName = Boolean(place.name.match(searchExpression));
+ const filterDomain = Boolean(place.domain.name.match(searchExpression));
+ const filterDesc = Boolean(place.description.match(searchExpression));
+ const filterMaturity = filters.maturity.includes(place.maturity);
+ const filterHasUsers = !onlyShowActiveButton.checked || place.current_attendance > 0;
+
+ if (
+ (compatibleProtocol || filters.includeIncompatible) &&
+ (filterName || filterDomain || filterDesc) &&
+ filterMaturity &&
+ filterHasUsers
+ ) {
+ place.directoryHost = hostname;
+ place.compatibleProtocol = compatibleProtocol;
+ tmp.push(place);
+ }
+ }
+
+ tmp.sort((a, b) => (
+ // if "show incompatible servers" is on, sort compatible ones first
+ ((b.compatibleProtocol ? 1 : 0) - (a.compatibleProtocol ? 1 : 0)) ||
+ // sort favorited places up
+ ((filters.favoritedPlaceIds.includes(b.placeId) ? 1 : 0) - (filters.favoritedPlaceIds.includes(a.placeId) ? 1 : 0)) ||
+ // prefer places with a thumbnail
+ ((b.thumbnail ? 1 : 0) - (a.thumbnail ? 1 : 0)) ||
+ // random shuffle that's stable for a few days to mix things up
+ delayedShuffleHash(b.placeId) - delayedShuffleHash(a.placeId)
+ ));
+
+ gridView.model = tmp;
+ }
+
+ Component.onCompleted: {
+ let fileSize = 1;
+ let xhr = new XMLHttpRequest();
+
+ xhr.onreadystatechange = () => {
+ if (xhr.readyState === XMLHttpRequest.HEADERS_RECEIVED) {
+ const length = xhr.getResponseHeader("Content-Length");
+ if (length !== "") { fileSize = Number(length); }
+ } else if (xhr.readyState === XMLHttpRequest.LOADING) {
+ downloadProgress = xhr.response.length / fileSize;
+ } else if (xhr.readyState === XMLHttpRequest.DONE) {
+ console.debug("Finished downloading place list");
+ downloadProgress = 1.0;
+
+ try {
+ const body = JSON.parse(xhr.responseText);
+ rawPlaces = body.data.places;
+ filterPlaces();
+ } catch (e) {
+ console.error(e);
+ }
+ }
+ };
+
+ console.debug("Downloading place list…");
+ downloadProgress = 0.0;
+
+ // TODO: federation support, multiple directory sources
+ xhr.open("GET", `${AccountServices.metaverseServerURL}/api/v1/places?status=online`);
+ xhr.send();
+ }
+
+ ColumnLayout {
+ anchors.fill: parent
+ spacing: 0
+
+ RowLayout {
+ Layout.margins: 4
+
+ Overte.RoundButton {
+ visible: placePicker.hasHomeButton
+
+ icon.source: "../icons/triangle_left.svg"
+ icon.width: 24
+ icon.height: 24
+ icon.color: Overte.Theme.paletteActive.buttonText
+
+ onClicked: placePicker.goBack()
+ }
+
+ Overte.RoundButton {
+ visible: placePicker.hasHomeButton
+
+ icon.source: "../icons/triangle_right.svg"
+ icon.width: 24
+ icon.height: 24
+ icon.color: Overte.Theme.paletteActive.buttonText
+
+ onClicked: placePicker.goForward()
+ }
+
+ Overte.RoundButton {
+ visible: placePicker.hasHomeButton
+
+ icon.source: "../icons/home.svg"
+ icon.width: 24
+ icon.height: 24
+ icon.color: Overte.Theme.paletteActive.buttonText
+
+ onClicked: placePicker.goToLocation(LocationBookmarks.getHomeLocationAddress())
+
+ Overte.ToolTip { text: qsTr("Go to Home bookmark") }
+ }
+
+ Overte.TextField {
+ Layout.fillWidth: true
+
+ id: searchField
+ placeholderText: qsTr("Search…")
+
+ Keys.onEnterPressed: {
+ searchButton.click();
+ forceActiveFocus();
+ }
+ Keys.onReturnPressed: {
+ searchButton.click();
+ forceActiveFocus();
+ }
+ }
+
+ Overte.RoundButton {
+ id: searchButton
+ icon.source: (
+ searchField.text.match(/(?:hifi|https?|file):\/\//) ?
+ "../icons/send.svg" :
+ "../icons/search.svg"
+ )
+ icon.width: 24
+ icon.height: 24
+ icon.color: Overte.Theme.paletteActive.buttonText
+
+ onClicked: {
+ if (searchField.text.startsWith("hifi://")) {
+ placePicker.goToLocation(infoDialog.placeUrl);
+ searchField.text = "";
+ } else {
+ filters.searchExpression = searchField.text === "" ? ".*" : searchField.text;
+ filterPlaces();
+ }
+ }
+ }
+
+ Overte.RoundButton {
+ id: onlyShowActiveButton
+ icon.source: "../icons/users.svg"
+ icon.width: 24
+ icon.height: 24
+ icon.color: Overte.Theme.paletteActive.buttonText
+ checkable: true
+
+ Overte.ToolTip { text: qsTr("Only show places with users") }
+
+ onClicked: filterPlaces()
+ }
+
+ Overte.RoundButton {
+ id: filterButton
+ icon.source: "../icons/filter.svg"
+ icon.width: 24
+ icon.height: 24
+ icon.color: Overte.Theme.paletteActive.buttonText
+
+ onClicked: filterDialog.open()
+
+ Overte.ToolTip { text: qsTr("Search options") }
+ }
+ }
+
+ Overte.TabBar {
+ Layout.fillWidth: true
+ id: tabBar
+
+ Overte.TabButton { text: qsTr("Public") }
+ Overte.TabButton { text: qsTr("Bookmarks") }
+ }
+
+ ColumnLayout {
+ Layout.fillWidth: true
+ Layout.fillHeight: true
+ visible: rawPlaces.length === 0
+
+ Item { Layout.fillHeight: true }
+
+ Overte.Label {
+ Layout.fillWidth: true
+ horizontalAlignment: Text.AlignHCenter
+ verticalAlignment: Text.AlignVCenter
+ text: qsTr("Loading…")
+ }
+
+ Rectangle {
+ Layout.margins: 8
+ Layout.leftMargin: 64
+ Layout.rightMargin: 64
+ Layout.fillWidth: true
+
+ implicitWidth: 256
+ implicitHeight: Overte.Theme.fontPixelSize + (Overte.Theme.borderWidth * 2)
+ color: Overte.Theme.paletteActive.base
+ border.color: Qt.darker(color, Overte.Theme.borderDarker)
+ border.width: Overte.Theme.borderWidth
+ radius: Overte.Theme.borderRadius
+
+ Rectangle {
+ x: parent.border.width
+ y: parent.border.width
+ height: parent.height - (parent.border.width * 2)
+ width: downloadProgress * (parent.width - (parent.border.width * 2))
+ radius: parent.radius - Overte.Theme.borderWidth
+ color: Overte.Theme.paletteActive.highlight
+ }
+ }
+
+ Item { Layout.fillHeight: true }
+ }
+
+ StackLayout {
+ Layout.fillWidth: true
+ Layout.fillHeight: true
+
+ GridView {
+ // QT6TODO: remove this once mouse inputs work properly
+ interactive: false
+
+ id: gridView
+ visible: rawPlaces.length !== 0
+ // fit two cells onto the default tablet
+ cellWidth: (480 - Overte.Theme.scrollbarWidth) / 2
+ cellHeight: Math.floor(cellWidth * 0.6)
+ clip: true
+
+ ScrollBar.vertical: Overte.ScrollBar {}
+ rightMargin: ScrollBar.vertical.width
+
+ model: []
+ delegate: PlaceItem {}
+ }
+ }
+
+ RowLayout {
+ Layout.margins: 2
+ Layout.fillWidth: true
+
+ Overte.Label {
+ Layout.fillWidth: true
+ verticalAlignment: Text.AlignVCenter
+ text: (
+ gridView.model.length !== 0 ?
+ qsTr("%1 place(s)").arg(gridView.model.length) :
+ ""
+ )
+ }
+
+ Overte.RoundButton {
+ id: settingsButton
+ icon.source: "../icons/settings_cog.svg"
+ icon.width: 24
+ icon.height: 24
+
+ onClicked: console.error("TODO: settings")
+ visible: false
+ }
+ }
+ }
+
+ Overte.Dialog {
+ id: filterDialog
+ visible: false
+ anchors.fill: placePicker
+
+ function open() {
+ filterControlMaturityEveryone.checked = filters.maturity.includes("everyone");
+ filterControlMaturityTeen.checked = filters.maturity.includes("teen");
+ filterControlMaturityMature.checked = filters.maturity.includes("mature");
+ filterControlMaturityAdult.checked = filters.maturity.includes("adult");
+ filterControlIncludeIncompatible.checked = filters.includeIncompatible;
+
+ visible = true;
+ opacity = Overte.Theme.reducedMotion ? 1 : 0;
+ }
+
+ function accept() {
+ let maturityList = [];
+
+ if (filterControlMaturityEveryone.checked) {
+ maturityList.push("everyone");
+ }
+
+ if (filterControlMaturityTeen.checked) {
+ maturityList.push("teen");
+ }
+
+ if (filterControlMaturityMature.checked) {
+ maturityList.push("mature");
+ }
+
+ if (filterControlMaturityAdult.checked) {
+ maturityList.push("adult");
+ }
+
+ // if everything is enabled or disabled, include unrated places too
+ if (maturityList.length === 0 || maturityList.length === 4) {
+ maturityList.push("unrated");
+ }
+
+ filters.includeIncompatible = filterControlIncludeIncompatible.checked;
+ filters.maturity = maturityList;
+ placePicker.filterPlaces();
+ close();
+ }
+
+ ColumnLayout {
+ anchors.fill: parent
+ anchors.margins: 8
+
+ Overte.Label {
+ Layout.fillWidth: true
+ text: qsTr("Filters")
+ horizontalAlignment: Text.AlignHCenter
+ opacity: Overte.Theme.highContrast ? 1.0 : 0.6
+ }
+
+ Overte.Ruler { Layout.fillWidth: true }
+
+ RowLayout {
+ Overte.Label {
+ Layout.fillWidth: true
+ text: qsTr("Maturity ratings")
+ }
+
+ GridLayout {
+ Layout.alignment: Qt.AlignRight
+ columns: 2
+
+ Overte.Switch {
+ id: filterControlMaturityEveryone
+ text: qsTr("Everyone")
+ }
+
+ Overte.Switch {
+ id: filterControlMaturityTeen
+ text: qsTr("Teen")
+ }
+
+ Overte.Switch {
+ id: filterControlMaturityMature
+ text: qsTr("Mature")
+ }
+
+ Overte.Switch {
+ id: filterControlMaturityAdult
+ text: qsTr("Adult")
+ }
+ }
+ }
+
+ RowLayout {
+ Layout.fillWidth: true
+
+ Overte.Label {
+ Layout.fillWidth: true
+ text: qsTr("Show incompatible servers")
+ }
+
+ Overte.Switch { id: filterControlIncludeIncompatible }
+ }
+
+ RowLayout {
+ Layout.preferredWidth: 480
+
+ Overte.Button {
+ Layout.preferredWidth: 1
+ Layout.fillWidth: true
+ text: qsTr("Cancel")
+ onClicked: filterDialog.close()
+ }
+
+ Item { Layout.fillWidth: true }
+
+ Overte.Button {
+ Layout.preferredWidth: 1
+ Layout.fillWidth: true
+ backgroundColor: Overte.Theme.paletteActive.buttonAdd
+ text: qsTr("Apply")
+ onClicked: filterDialog.accept()
+ }
+ }
+ }
+ }
+
+ Overte.Dialog {
+ id: infoDialog
+ visible: false
+ anchors.fill: placePicker
+
+ property string placeName: ""
+ property string placeDesc: ""
+ property string domainName: ""
+ property string directoryHost: ""
+ property int currentUsers: 0
+ property int maxUsers: 0
+ property list managers: []
+ property url placeUrl: ""
+ property bool compatible: true
+ property string placeId: ""
+
+ function open(index) {
+ const data = gridView.model[index];
+ placeName = data.name;
+ domainName = data.domain.name;
+ placeDesc = data.description;
+ directoryHost = data.directoryHost;
+ compatible = data.compatibleProtocol;
+
+ // ignore the redundant default place description
+ if (
+ placeDesc === `A place in ${domainName}` ||
+ placeDesc === `A place in ${placeName}`
+ ) {
+ placeDesc = "";
+ }
+
+ currentUsers = data.current_attendance;
+ maxUsers = data.domain.capacity;
+ managers = data.managers;
+ placeUrl = data.finalPlaceUrl ?? `hifi://${placeName}${data.path}`;
+ placeId = data.placeId;
+
+ visible = true;
+ opacity = Overte.Theme.reducedMotion ? 1 : 0;
+ }
+
+ ColumnLayout {
+ anchors.fill: parent
+ anchors.margins: 8
+
+ ColumnLayout {
+ Layout.alignment: Qt.AlignTop | Qt.AlignLeft
+ Layout.fillWidth: true
+
+ Overte.Label {
+ // leave space for the close button
+ Layout.rightMargin: 44
+ Layout.fillWidth: true
+ font.bold: true
+ text: infoDialog.placeName
+ }
+
+ Overte.Label {
+ // leave space for the close button
+ Layout.rightMargin: 44
+ font.pixelSize: Overte.Theme.fontPixelSizeSmall
+ opacity: Overte.Theme.highContrast ? 1.0 : 0.6
+ text: `${infoDialog.domainName} @ ${infoDialog.directoryHost}`
+ }
+
+ Overte.Label {
+ Layout.fillWidth: true
+ wrapMode: Text.Wrap
+ opacity: Overte.Theme.highContrast ? 1.0 : 0.6
+ font.pixelSize: Overte.Theme.fontPixelSizeSmall
+ text: qsTr("Managed by %1").arg(infoDialog.managers.join(", "))
+ }
+
+ Overte.Ruler { Layout.fillWidth: true }
+
+ Overte.Label {
+ Layout.fillWidth: true
+ Layout.fillHeight: true
+ wrapMode: Text.Wrap
+ font.pixelSize: Overte.Theme.fontPixelSizeSmall
+ text: infoDialog.placeDesc
+ }
+ }
+
+ RowLayout {
+ implicitWidth: 480
+ Layout.preferredWidth: 480
+ Layout.fillWidth: true
+
+ Overte.Button {
+ Layout.fillWidth: true
+ Layout.fillHeight: true
+ Layout.preferredWidth: 1
+ text: qsTr("Copy URL")
+ onClicked: WindowScriptingInterface.copyToClipboard(infoDialog.placeUrl)
+ }
+
+ Overte.Button {
+ Layout.fillWidth: true
+ Layout.fillHeight: true
+ Layout.preferredWidth: 1
+ backgroundColor: Overte.Theme.paletteActive.buttonInfo
+ enabled: infoDialog.compatible
+ text: qsTr("Portal")
+ onClicked: {
+ infoDialog.close();
+ Messages.sendMessage("org.overte.PlacePortal.Create", JSON.stringify({
+ place_name: infoDialog.placeName,
+ place_url: infoDialog.placeUrl,
+ }));
+ }
+ }
+
+ Overte.Button {
+ Layout.fillWidth: true
+ Layout.fillHeight: true
+ Layout.preferredWidth: 1
+ backgroundColor: Overte.Theme.paletteActive.buttonAdd
+ enabled: infoDialog.compatible
+ text: infoDialog.compatible ? qsTr("Join") : qsTr("Incompatible")
+ onClicked: {
+ infoDialog.close();
+ placePicker.goToLocation(infoDialog.placeUrl);
+ }
+ }
+ }
+ }
+
+ Row {
+ anchors.top: parent.top
+ anchors.right: parent.right
+ anchors.margins: 8
+ spacing: 4
+
+ Overte.RoundButton {
+ implicitWidth: 28
+ implicitHeight: 28
+
+ icon.source: "../icons/gold_star.svg"
+ icon.width: 20
+ icon.height: 20
+ icon.color: (
+ checked ?
+ Overte.Theme.paletteActive.buttonText :
+ Overte.Theme.paletteActive.placeholderText
+ )
+
+ backgroundColor: checked ? Overte.Theme.paletteActive.buttonFavorite : Overte.Theme.paletteActive.button
+ checkable: true
+ checked: filters.favoritedPlaceIds.includes(infoDialog.placeId)
+
+ onToggled: {
+ if (!checked) {
+ const index = filters.favoritedPlaceIds.indexOf(infoDialog.placeId);
+ filters.favoritedPlaceIds.splice(index);
+ } else {
+ filters.favoritedPlaceIds.push(infoDialog.placeId);
+ }
+
+ filterPlaces();
+ }
+
+ Overte.ToolTip { text: qsTr("Favorite\nSorts this place before unfavorited places.") }
+ }
+
+ Overte.RoundButton {
+ implicitWidth: 28
+ implicitHeight: 28
+
+ backgroundColor: Overte.Theme.paletteActive.buttonDestructive
+
+ icon.source: "../icons/close.svg"
+ icon.width: 18
+ icon.height: 18
+ icon.color: Overte.Theme.paletteActive.buttonText
+
+ onClicked: infoDialog.close()
+ }
+ }
+ }
+}
diff --git a/interface/resources/qml/overte/place_picker/qmldir b/interface/resources/qml/overte/place_picker/qmldir
new file mode 100644
index 00000000000..5b37f768aef
--- /dev/null
+++ b/interface/resources/qml/overte/place_picker/qmldir
@@ -0,0 +1,3 @@
+module PlacePicker
+PlacePicker 1.0 PlacePicker.qml
+PlaceItem 1.0 PlaceItem.qml
diff --git a/interface/resources/qml/overte/qmldir b/interface/resources/qml/overte/qmldir
new file mode 100644
index 00000000000..f5e84c0e293
--- /dev/null
+++ b/interface/resources/qml/overte/qmldir
@@ -0,0 +1,19 @@
+module Overte
+singleton Theme 1.0 Theme.qml
+Button 1.0 Button.qml
+TextField 1.0 TextField.qml
+Switch 1.0 Switch.qml
+TabBar 1.0 TabBar.qml
+TabButton 1.0 TabButton.qml
+Label 1.0 Label.qml
+AppButton 1.0 AppButton.qml
+RoundButton 1.0 RoundButton.qml
+ScrollBar 1.0 ScrollBar.qml
+ToolTip 1.0 ToolTip.qml
+NodeGraph 1.0 NodeGraph.qml
+Node 1.0 Node.qml
+FileDialog 1.0 FileDialog.qml
+Slider 1.0 Slider.qml
+SpinBox 1.0 SpinBox.qml
+BodyText 1.0 BodyText.qml
+StackView 1.0 StackView.qml
diff --git a/interface/resources/qml/overte/settings/ComboSetting.qml b/interface/resources/qml/overte/settings/ComboSetting.qml
new file mode 100644
index 00000000000..467aa35cda0
--- /dev/null
+++ b/interface/resources/qml/overte/settings/ComboSetting.qml
@@ -0,0 +1,37 @@
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Layouts
+
+import "../" as Overte
+
+RowLayout {
+ property alias text: labelItem.text
+ property alias model: comboItem.model
+ property alias textRole: comboItem.textRole
+ property alias valueRole: comboItem.valueRole
+ property alias currentIndex: comboItem.currentIndex
+ property alias enabled: comboItem.enabled
+
+ id: item
+ anchors.left: parent.left
+ anchors.right: parent.right
+ anchors.margins: 16
+ spacing: 16
+
+ Overte.Label {
+ // equally sized items
+ Layout.preferredWidth: 1
+ Layout.fillWidth: true
+
+ id: labelItem
+ wrapMode: Text.Wrap
+ }
+
+ Overte.ComboBox {
+ // equally sized items
+ Layout.preferredWidth: 1
+ Layout.fillWidth: true
+
+ id: comboItem
+ }
+}
diff --git a/interface/resources/qml/overte/settings/FolderSetting.qml b/interface/resources/qml/overte/settings/FolderSetting.qml
new file mode 100644
index 00000000000..dbf7001c290
--- /dev/null
+++ b/interface/resources/qml/overte/settings/FolderSetting.qml
@@ -0,0 +1,49 @@
+import QtQuick
+import QtQuick.Layouts
+
+import "../" as Overte
+import "../dialogs" as OverteDialogs
+
+ColumnLayout {
+ property alias text: labelItem.text
+ property alias value: textFieldItem.text
+ property bool enabled: true
+
+ id: item
+ anchors.left: parent.left
+ anchors.right: parent.right
+ anchors.margins: 16
+ spacing: 4
+
+ Overte.Label {
+ Layout.alignment: Qt.AlignBottom
+ id: labelItem
+ wrapMode: Text.Wrap
+ }
+
+ RowLayout {
+ Layout.fillWidth: true
+
+ Overte.TextField {
+ Layout.fillWidth: true
+
+ id: textFieldItem
+ enabled: item.enabled
+ }
+
+ Overte.RoundButton {
+ enabled: item.enabled
+
+ icon.source: "../icons/folder.svg"
+ icon.width: 24
+ icon.height: 24
+
+ onClicked: settingsRoot.openFolderPicker(
+ folder => {
+ value = folder;
+ },
+ value
+ );
+ }
+ }
+}
diff --git a/interface/resources/qml/overte/settings/Header.qml b/interface/resources/qml/overte/settings/Header.qml
new file mode 100644
index 00000000000..e5e5e9901c7
--- /dev/null
+++ b/interface/resources/qml/overte/settings/Header.qml
@@ -0,0 +1,27 @@
+import QtQuick
+import QtQuick.Layouts
+
+import "../" as Overte
+
+ColumnLayout {
+ property alias text: labelItem.text
+
+ id: item
+ anchors.left: parent.left
+ anchors.right: parent.right
+ anchors.margins: 16
+ spacing: 2
+ height: Overte.Theme.fontPixelSize * 3
+
+ Overte.Label {
+ id: labelItem
+ Layout.alignment: Qt.AlignBottom | Qt.AlignHCenter
+ opacity: Overte.Theme.highContrast ? 1.0 : 0.6
+ wrapMode: Text.Wrap
+ }
+
+ Overte.Ruler {
+ Layout.fillWidth: true
+ Layout.alignment: Qt.AlignBottom
+ }
+}
diff --git a/interface/resources/qml/overte/settings/SettingNote.qml b/interface/resources/qml/overte/settings/SettingNote.qml
new file mode 100644
index 00000000000..4844b6af065
--- /dev/null
+++ b/interface/resources/qml/overte/settings/SettingNote.qml
@@ -0,0 +1,13 @@
+import QtQuick
+import QtQuick.Layouts
+
+import "../" as Overte
+
+Overte.Label {
+ id: item
+ anchors.left: parent.left
+ anchors.right: parent.right
+ anchors.margins: 16
+ wrapMode: Text.Wrap
+ font.pixelSize: Overte.Theme.fontPixelSizeSmall
+}
diff --git a/interface/resources/qml/overte/settings/Settings.qml b/interface/resources/qml/overte/settings/Settings.qml
new file mode 100644
index 00000000000..b69937cf422
--- /dev/null
+++ b/interface/resources/qml/overte/settings/Settings.qml
@@ -0,0 +1,71 @@
+import QtQuick
+import QtQuick.Layouts
+import QtQuick.Controls
+
+import "../" as Overte
+import "." as OverteSettings
+import "./pages" as SettingsPages
+import "../dialogs" as OverteDialogs
+
+Rectangle {
+ id: settingsRoot
+ width: 480
+ height: 720
+ visible: true
+ anchors.fill: parent
+ color: Overte.Theme.paletteActive.base
+
+ Overte.TabBar {
+ anchors.left: parent.left
+ anchors.right: parent.right
+ anchors.top: parent.top
+ id: tabBar
+
+ Overte.TabButton { text: qsTr("General") }
+ Overte.TabButton { text: qsTr("Graphics") }
+ Overte.TabButton { text: qsTr("Controls") }
+ Overte.TabButton { text: qsTr("Audio") }
+ }
+
+ StackLayout {
+ anchors.left: parent.left
+ anchors.right: parent.right
+ anchors.top: tabBar.bottom
+ anchors.bottom: parent.bottom
+ anchors.topMargin: Overte.Theme.fontPixelSize
+ currentIndex: tabBar.currentIndex
+
+ SettingsPages.General {}
+
+ SettingsPages.Graphics {}
+
+ SettingsPages.Controls {}
+
+ SettingsPages.Audio {}
+ }
+
+ OverteDialogs.FileDialog {
+ anchors.fill: settingsRoot
+ visible: false
+
+ id: folderDialog
+
+ property var acceptedCallback: folder => {}
+ fileMode: OverteDialogs.FileDialog.OpenFolder
+
+ onAccepted: {
+ acceptedCallback(new URL(folderDialog.selectedFile).pathname);
+ close();
+ }
+
+ onRejected: close()
+ }
+
+ function openFolderPicker(callback, root) {
+ folderDialog.acceptedCallback = callback;
+ if (root) {
+ folderDialog.currentFolder = `file://${root}`;
+ }
+ folderDialog.open();
+ }
+}
diff --git a/interface/resources/qml/overte/settings/SettingsPage.qml b/interface/resources/qml/overte/settings/SettingsPage.qml
new file mode 100644
index 00000000000..9ce3826428c
--- /dev/null
+++ b/interface/resources/qml/overte/settings/SettingsPage.qml
@@ -0,0 +1,23 @@
+import QtQuick
+import QtQuick.Controls
+
+import "../" as Overte
+
+ScrollView {
+ default property alias children: column.children
+
+ ScrollBar.vertical: Overte.ScrollBar {
+ anchors.top: parent.top
+ anchors.bottom: parent.bottom
+ anchors.right: parent.right
+ policy: ScrollBar.AsNeeded
+ }
+ contentWidth: width - ScrollBar.vertical.width
+
+ Column {
+ id: column
+ anchors.fill: parent
+ spacing: 8
+ padding: 16
+ }
+}
diff --git a/interface/resources/qml/overte/settings/SliderSetting.qml b/interface/resources/qml/overte/settings/SliderSetting.qml
new file mode 100644
index 00000000000..da70e463cfe
--- /dev/null
+++ b/interface/resources/qml/overte/settings/SliderSetting.qml
@@ -0,0 +1,76 @@
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Layouts
+
+import "../" as Overte
+
+ColumnLayout {
+ property alias text: labelItem.text
+ property alias value: sliderItem.value
+ property alias from: sliderItem.from
+ property alias to: sliderItem.to
+ property alias stepSize: sliderItem.stepSize
+ property alias enabled: sliderItem.enabled
+ property string valueText: valueToText()
+ property bool fineTweakButtons: false
+
+ property var valueToText: () => value.toString()
+
+ id: item
+ anchors.left: parent.left
+ anchors.right: parent.right
+ anchors.margins: 16
+ spacing: 16
+
+ RowLayout {
+ Overte.Label {
+ Layout.fillWidth: true
+
+ id: labelItem
+ wrapMode: Text.Wrap
+ }
+
+ Overte.Label {
+ text: valueText
+ wrapMode: Text.Wrap
+ }
+ }
+
+ RowLayout {
+ Overte.RoundButton {
+ visible: fineTweakButtons
+
+ icon.width: 24
+ icon.height: 24
+ icon.source: "../icons/triangle_left.svg"
+ icon.color: Overte.Theme.paletteActive.buttonText
+
+ onClicked: {
+ sliderItem.value -= item.stepSize;
+ }
+ }
+
+ Overte.Slider {
+ Layout.fillWidth: true
+
+ id: sliderItem
+ snapMode: Slider.SnapAlways
+ }
+
+ Overte.RoundButton {
+ visible: fineTweakButtons
+
+ icon.width: 24
+ icon.height: 24
+ icon.source: "../icons/triangle_right.svg"
+ icon.color: Overte.Theme.paletteActive.buttonText
+
+ onClicked: {
+ sliderItem.value += item.stepSize;
+ }
+ }
+ }
+
+ // an extra spacer so the slider doesn't crowd with the setting below
+ Item {}
+}
diff --git a/interface/resources/qml/overte/settings/SpinBoxSetting.qml b/interface/resources/qml/overte/settings/SpinBoxSetting.qml
new file mode 100644
index 00000000000..ca5e4d440e1
--- /dev/null
+++ b/interface/resources/qml/overte/settings/SpinBoxSetting.qml
@@ -0,0 +1,35 @@
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Layouts
+
+import "../" as Overte
+
+RowLayout {
+ property alias text: labelItem.text
+ property alias value: spinboxItem.value
+ property alias from: spinboxItem.from
+ property alias to: spinboxItem.to
+ property alias enabled: spinboxItem.enabled
+
+ id: item
+ anchors.left: parent.left
+ anchors.right: parent.right
+ anchors.margins: 16
+ spacing: 16
+
+ Overte.Label {
+ // equally sized items
+ Layout.preferredWidth: 1
+ Layout.fillWidth: true
+
+ id: labelItem
+ wrapMode: Text.Wrap
+ }
+
+ Overte.SpinBox {
+ Layout.alignment: Qt.AlignRight
+
+ id: spinboxItem
+ editable: true
+ }
+}
diff --git a/interface/resources/qml/overte/settings/SwitchSetting.qml b/interface/resources/qml/overte/settings/SwitchSetting.qml
new file mode 100644
index 00000000000..3d928cf80fa
--- /dev/null
+++ b/interface/resources/qml/overte/settings/SwitchSetting.qml
@@ -0,0 +1,28 @@
+import QtQuick
+import QtQuick.Layouts
+
+import "../" as Overte
+
+RowLayout {
+ property alias text: labelItem.text
+ property alias value: switchItem.checked
+ property bool enabled: true
+
+ id: item
+ anchors.left: parent.left
+ anchors.right: parent.right
+ anchors.margins: 16
+ spacing: 16
+
+ Overte.Label {
+ Layout.fillWidth: true
+
+ id: labelItem
+ wrapMode: Text.Wrap
+ }
+
+ Overte.Switch {
+ id: switchItem
+ enabled: item.enabled
+ }
+}
diff --git a/interface/resources/qml/overte/settings/WideComboSetting.qml b/interface/resources/qml/overte/settings/WideComboSetting.qml
new file mode 100644
index 00000000000..15490ba2c6d
--- /dev/null
+++ b/interface/resources/qml/overte/settings/WideComboSetting.qml
@@ -0,0 +1,33 @@
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Layouts
+
+import "../" as Overte
+
+ColumnLayout {
+ property alias text: labelItem.text
+ property alias model: comboItem.model
+ property alias textRole: comboItem.textRole
+ property alias valueRole: comboItem.valueRole
+ property alias currentIndex: comboItem.currentIndex
+ property alias enabled: comboItem.enabled
+
+ id: item
+ anchors.left: parent.left
+ anchors.right: parent.right
+ anchors.margins: 16
+ spacing: 4
+
+ Overte.Label {
+ Layout.fillWidth: true
+
+ id: labelItem
+ }
+
+ Overte.ComboBox {
+ Layout.fillWidth: true
+
+ id: comboItem
+ font.pixelSize: Overte.Theme.fontPixelSizeSmall
+ }
+}
diff --git a/interface/resources/qml/overte/settings/pages/Audio.qml b/interface/resources/qml/overte/settings/pages/Audio.qml
new file mode 100644
index 00000000000..0d8c8affe1a
--- /dev/null
+++ b/interface/resources/qml/overte/settings/pages/Audio.qml
@@ -0,0 +1,64 @@
+import "../../" as Overte
+import "../"
+
+SettingsPage {
+ SwitchSetting {
+ text: "Mute Microphone"
+
+ value: AudioScriptingInterface.muted
+ onValueChanged: AudioScriptingInterface.muted = value
+ }
+
+ SwitchSetting {
+ text: "Push-to-Talk"
+
+ value: AudioScriptingInterface.pushToTalk
+ onValueChanged: AudioScriptingInterface.pushToTalk = value
+ }
+
+ SettingNote {
+ text: "Push [T] or squeeze both controller grips to talk."
+ }
+
+ Header { text: qsTr("Audio Devices") }
+
+ WideComboSetting {
+ text: "Output Device"
+ model: {
+ let tmp = [];
+ const source = AudioScriptingInterface.devices.output;
+
+ for (let i = 0; i < source.rowCount(); i++) {
+ tmp.push(source.data(source.index(i, 0), /* DeviceNameRole */ 0x100));
+ }
+
+ return tmp;
+ }
+
+ // TODO: how do these work???
+ //currentIndex: 0
+ //onCurrentIndexChanged: AudioScriptingInterface.setOutputDevice(currentIndex, false)
+ }
+
+ WideComboSetting {
+ text: "Input Device"
+ model: {
+ let tmp = [];
+ const source = AudioScriptingInterface.devices.input;
+
+ for (let i = 0; i < source.rowCount(); i++) {
+ tmp.push(source.data(source.index(i, 0), /* DeviceNameRole */ 0x100));
+ }
+
+ return tmp;
+ }
+
+ // TODO: how do these work???
+ //currentIndex: 0
+ //onCurrentIndexChanged: AudioScriptingInterface.setInputDevice(currentIndex, false)
+ }
+
+ SettingNote {
+ text: qsTr("Most VR runtimes will automatically switch the default audio devices to your headset.")
+ }
+}
diff --git a/interface/resources/qml/overte/settings/pages/Controls.qml b/interface/resources/qml/overte/settings/pages/Controls.qml
new file mode 100644
index 00000000000..7f7a102d03a
--- /dev/null
+++ b/interface/resources/qml/overte/settings/pages/Controls.qml
@@ -0,0 +1,199 @@
+import "../../" as Overte
+import "../"
+
+// NOTE: There's a lot of "x depends on non-bindable properties" warnings,
+// there's not much that can be done about them and I don't think there's
+// a way of explicitly telling QML to not try binding to stuff.
+SettingsPage {
+ Header {
+ text: qsTr("Desktop")
+
+ // Hack to reduce the dead space on the top of the page
+ height: implicitHeight
+ }
+
+ SwitchSetting {
+ id: invertMouseY
+ text: qsTr("Invert Y")
+
+ value: MyAvatar.pitchSpeed < 0
+ onValueChanged: {
+ MyAvatar.pitchSpeed = mouseSensitivity.value * (value ? -75 : 75);
+ }
+ }
+
+ SliderSetting {
+ id: mouseSensitivity
+ text: qsTr("Mouse Sensitivity")
+ stepSize: 0.1
+ from: 0.1
+ to: 5.0
+ valueToText: () => `${value.toLocaleString()}`;
+
+ value: MyAvatar.yawSpeed / 75
+ onValueChanged: {
+ MyAvatar.yawSpeed = value * 75;
+ MyAvatar.pitchSpeed = value * (invertMouseY.value ? -75 : 75);
+ }
+ }
+
+ Header { text: qsTr("VR User") }
+
+ SliderSetting {
+ text: qsTr("Height")
+ fineTweakButtons: true
+ stepSize: 0.01
+ from: 0.8
+ to: 2.5
+
+ valueToText: () => {
+ let meters = value.toLocaleString();
+ let totalInches = Math.round(value * 39.37008);
+
+ let feet = Math.floor(totalInches / 12);
+ let inches = totalInches % 12;
+
+ return `${feet}'${inches}" ${meters.toLocaleString()}m`;
+ }
+
+ value: MyAvatar.userHeight
+ onValueChanged: MyAvatar.userHeight = value
+ }
+
+ ComboSetting {
+ text: qsTr("Dominant Hand")
+ model: [
+ qsTr("Left"),
+ qsTr("Right"),
+ ]
+
+ currentIndex: MyAvatar.getDominantHand() === "left" ? 0 : 1
+ onCurrentIndexChanged: MyAvatar.setDominantHand(currentIndex === 0 ? "left" : "right")
+ }
+
+ SwitchSetting {
+ text: qsTr("Seated Mode")
+ value: MyAvatar.standingMode !== 0
+ onValueChanged: {
+ MyAvatar.standingMode = (
+ value ?
+ 2 : // ForcedHeight
+ 0 // Standing
+ );
+ }
+ }
+
+ Header { text: qsTr("VR Movement") }
+
+ SliderSetting {
+ text: qsTr("Turning Speed")
+ stepSize: 10
+ from: 40
+ to: 400
+ valueToText: () => value < 50 ? qsTr("Snap turning") : `${value.toLocaleString()}`;
+
+ value: MyAvatar.hmdYawSpeed
+ onValueChanged: {
+ MyAvatar.setSnapTurn(value < 50);
+ MyAvatar.HMDYawSpeed = value;
+ }
+ }
+
+ SliderSetting {
+ id: walkingSpeed
+ text: qsTr("Walking Speed")
+ stepSize: 0.5
+ from: 1.0
+ to: 9
+ valueToText: () => value < 1.5 ? qsTr("Teleport Only") : `${value.toLocaleString()} m/s`;
+ //enabled: !useAvatarDefaultWalkingSpeed.value
+
+ value: MyAvatar.vrWalkSpeed
+ onValueChanged: {
+ if (value === 0.0) {
+ MyAvatar.useAdvancedMovementControls = false;
+ } else {
+ MyAvatar.useAdvancedMovementControls = true;
+ MyAvatar.vrWalkSpeed = value;
+ }
+ }
+ }
+
+ ComboSetting {
+ text: qsTr("Movement Relative To")
+ enabled: walkingSpeed.value >= 1.5
+ model: [
+ qsTr("Head"),
+ qsTr("Hand"),
+ ]
+
+ currentIndex: MyAvatar.getMovementReference()
+ onCurrentIndexChanged: MyAvatar.setMovementReference(currentIndex)
+ }
+
+ /*SwitchSetting {
+ id: useAvatarDefaultWalkingSpeed
+ text: qsTr("Use equipped avatar's default walking speed if available")
+
+ // TODO
+ value: false
+ }*/
+
+ Header { text: qsTr("VR UI") }
+
+ ComboSetting {
+ text: qsTr("Virtual Keyboard Input")
+ model: [
+ qsTr("Lasers"),
+ qsTr("Mallets"),
+ ]
+
+ currentIndex: KeyboardScriptingInterface.preferMalletsOverLasers ? 1 : 0
+ onCurrentIndexChanged: {
+ KeyboardScriptingInterface.preferMalletsOverLasers = currentIndex == 1;
+ }
+ }
+
+ SliderSetting {
+ text: qsTr("Laser Smoothing Delay")
+ stepSize: 0.05
+ from: 0.0
+ to: 2.0
+ valueToText: () => `${value.toLocaleString()}s`;
+
+ value: PickScriptingInterface.handLaserDelay
+ onValueChanged: PickScriptingInterface.handLaserDelay = value
+ }
+
+ // for later, once the gesture scripts are stable and merged
+ /*
+ Header { text: qsTr("VR Gestures") }
+
+ SwitchSetting {
+ text: qsTr("Take Photo")
+ value: true
+ }
+
+ SettingNote {
+ text: qsTr("Double-click the trigger on your dominant hand near your ear to take a photo, or hold the trigger to take an animated screenshot.")
+ }
+
+ SwitchSetting {
+ text: qsTr("Laser Toggle")
+ value: true
+ }
+
+ SettingNote {
+ text: qsTr("Click both triggers with your hands behind your head to toggle the interaction lasers.")
+ }
+
+ SwitchSetting {
+ text: qsTr("Seated Mode Toggle")
+ value: true
+ }
+
+ SettingNote {
+ text: qsTr("Double-tap the controller grip on your non-dominant hand near your ear to switch between seated and standing mode.")
+ }
+ */
+}
diff --git a/interface/resources/qml/overte/settings/pages/General.qml b/interface/resources/qml/overte/settings/pages/General.qml
new file mode 100644
index 00000000000..869aa43d88e
--- /dev/null
+++ b/interface/resources/qml/overte/settings/pages/General.qml
@@ -0,0 +1,235 @@
+import "../../" as Overte
+import "../"
+
+SettingsPage {
+ id: page
+
+ Header {
+ text: qsTr("UI")
+
+ // Hack to reduce the dead space on the top of the page
+ height: implicitHeight
+ }
+
+ ComboSetting {
+ text: qsTr("Color Scheme")
+ model: [
+ qsTr("Dark"),
+ qsTr("Light"),
+ qsTr("System"),
+ ]
+
+ currentIndex: {
+ if (WindowScriptingInterface.useSystemColorScheme) {
+ return 2;
+ } else if (WindowScriptingInterface.darkMode) {
+ return 0;
+ } else {
+ return 1;
+ }
+ }
+
+ onCurrentIndexChanged: {
+ switch (currentIndex) {
+ case 0:
+ WindowScriptingInterface.useSystemColorScheme = false;
+ WindowScriptingInterface.darkMode = true;
+ // FIXME: singletons can't access context properties?
+ Overte.Theme.darkMode = true;
+ break;
+
+ case 1:
+ WindowScriptingInterface.useSystemColorScheme = false;
+ WindowScriptingInterface.darkMode = false;
+ // FIXME: singletons can't access context properties?
+ Overte.Theme.darkMode = false;
+ break;
+
+ case 2:
+ WindowScriptingInterface.useSystemColorScheme = true;
+ WindowScriptingInterface.darkMode = true;
+ // FIXME: singletons can't access context properties?
+ Overte.Theme.darkMode = true;
+ break;
+ }
+ }
+ }
+
+ ComboSetting {
+ text: qsTr("Contrast")
+ model: [
+ qsTr("Standard"),
+ qsTr("High"),
+ qsTr("System"),
+ ]
+
+ currentIndex: {
+ if (WindowScriptingInterface.useSystemContrast) {
+ return 2;
+ } else if (WindowScriptingInterface.highContrast) {
+ return 1;
+ } else {
+ return 0;
+ }
+ }
+
+ onCurrentIndexChanged: {
+ switch (currentIndex) {
+ case 0:
+ WindowScriptingInterface.useSystemContrast = false;
+ WindowScriptingInterface.highContrast = false;
+ // FIXME: singletons can't access context properties?
+ Overte.Theme.highContrast = false;
+ break;
+
+ case 1:
+ WindowScriptingInterface.useSystemContrast = false;
+ WindowScriptingInterface.highContrast = true;
+ // FIXME: singletons can't access context properties?
+ Overte.Theme.highContrast = true;
+ break;
+
+ case 2:
+ WindowScriptingInterface.useSystemContrast = true;
+ WindowScriptingInterface.highContrast = false;
+ // FIXME: singletons can't access context properties?
+ Overte.Theme.highContrast = false;
+ break;
+ }
+ }
+ }
+
+ SwitchSetting {
+ text: qsTr("Reduced Motion")
+
+ value: WindowScriptingInterface.reducedMotion
+ onValueChanged: {
+ WindowScriptingInterface.reducedMotion = value;
+
+ // FIXME: singletons can't access context properties?
+ Overte.Theme.reducedMotion = value;
+ }
+ }
+
+ SettingNote {
+ text: qsTr("This setting will disable UI animations that slide or scale.")
+ }
+
+ ComboSetting {
+ text: qsTr("Avatar Nametags")
+ textRole: "text"
+ valueRole: "value"
+ model: [
+ { text: qsTr("Never "), value: "off" },
+ { text: qsTr("When clicked"), value: "on" },
+ { text: qsTr("Always"), value: "alwaysOn" },
+ ]
+
+ currentIndex: {
+ const setting = SettingsInterface.getValue("simplifiedNametag/avatarNametagMode", "on");
+ switch (setting) {
+ case "off": return 0;
+ case "on": return 1;
+ case "alwaysOn": return 2;
+ }
+ }
+
+ onCurrentIndexChanged: SettingsInterface.setValue("simplifiedNametag/avatarNametagMode", model[currentIndex].value)
+ }
+
+ Header { text: qsTr("Screenshots") }
+
+ FolderSetting {
+ text: qsTr("Folder")
+ value: Snapshot.getSnapshotsLocation()
+ onValueChanged: Snapshot.setSnapshotsLocation(value)
+ }
+
+ ComboSetting {
+ text: qsTr("Format")
+ model: [
+ "png",
+ "jpg",
+ "webp",
+ ]
+
+ currentIndex: {
+ let current = Snapshot.getSnapshotFormat();
+ for (let i in model) {
+ if (model[i] === current) { return i; }
+ }
+ return 0;
+ }
+ onCurrentIndexChanged: Snapshot.setSnapshotFormat(model[currentIndex])
+ }
+
+ SpinBoxSetting {
+ text: qsTr("Animation Duration")
+ from: 1
+ to: 30
+
+ value: SettingsInterface.getValue("snapshotAnimatedDuration", 3)
+ onValueChanged: SettingsInterface.setValue("snapshotAnimatedDuration", value)
+ }
+
+ SettingNote {
+ text: "Animated screenshots are saved as GIFs."
+ }
+
+ Header { text: qsTr("Privacy") }
+
+ SwitchSetting {
+ text: qsTr("Send Crash Reports")
+
+ // FIXME: MenuScriptingInterface is super cursed, we shouldn't be relying on
+ // the *names* of the menu items to access them, especially once they're translated.
+ // Why are these even menu items in the first place?
+ value: MenuInterface.isOptionChecked("Enable Crash Reporting")
+ onValueChanged: MenuInterface.setIsOptionChecked("Enable Crash Reporting", value)
+ }
+
+ SettingNote {
+ text: qsTr("Sending crash reports helps Overte development.")
+ }
+
+ SwitchSetting {
+ text: qsTr("Discord Rich Presence")
+ value: SettingsInterface.getValue("useDiscordPresence", true)
+ onValueChanged: SettingsInterface.setValue("useDiscordPresence", value)
+ }
+
+ SettingNote {
+ text: qsTr("If this setting is enabled, your current world will be shown on your Discord profile.")
+ }
+
+ Header { text: qsTr("Legacy Features") }
+
+ SettingNote {
+ text: qsTr("These settings are for legacy functionality that may be removed in a later release.")
+ }
+
+ SliderSetting {
+ text: qsTr("VR Tablet Scale")
+ stepSize: 5
+ from: 50
+ to: 150
+ valueToText: () => `${value}%`
+
+ value: SettingsInterface.getValue("hmdTabletScale", 75)
+ onValueChanged: SettingsInterface.setValue("hmdTabletScale", value)
+ }
+
+ ComboSetting {
+ text: qsTr("VR Tablet Input")
+
+ // keep this in the same order as MyAvatar.TabletInputMode
+ model: [
+ qsTr("Laser"),
+ qsTr("Stylus"),
+ qsTr("Finger Touch"),
+ ]
+
+ currentIndex: MyAvatar.tabletInputMode
+ onCurrentIndexChanged: MyAvatar.tabletInputMode = currentIndex;
+ }
+}
diff --git a/interface/resources/qml/overte/settings/pages/Graphics.qml b/interface/resources/qml/overte/settings/pages/Graphics.qml
new file mode 100644
index 00000000000..b37b00cf184
--- /dev/null
+++ b/interface/resources/qml/overte/settings/pages/Graphics.qml
@@ -0,0 +1,213 @@
+import "../../" as Overte
+import "../"
+
+SettingsPage {
+ SliderSetting {
+ text: qsTr("Field of View")
+ stepSize: 5
+ from: 20
+ to: 130
+ valueToText: () => `${value}°`;
+
+ value: Render.getVerticalFieldOfView()
+ onValueChanged: Render.setVerticalFieldOfView(value)
+ }
+
+ SliderSetting {
+ text: qsTr("Resolution Scale")
+ stepSize: 10
+ from: 10
+ to: 200
+ valueToText: () => `${value}%`;
+
+ value: Render.viewportResolutionScale * 100
+ onValueChanged: Render.viewportResolutionScale = value / 100
+ }
+
+ ComboSetting {
+ enabled: deferredRendering.value
+
+ text: qsTr("Anti-Aliasing")
+
+ model: [
+ // TODO: separate none and MSAA?
+ // RenderMainView.PreparePrimaryBufferForward.numSamples is a massive hack
+ deferredRendering.value ? qsTr("None") : qsTr("4x MSAA"),
+ qsTr("TAA"),
+ qsTr("FXAA"),
+ ]
+
+ currentIndex: deferredRendering.value ? Render.getAntialiasingMode() : 0
+ onCurrentIndexChanged: Render.setAntialiasingMode(currentIndex)
+ }
+
+ ComboSetting {
+ text: qsTr("Level-of-Detail")
+ textRole: "text"
+ valueRole: "mode"
+
+ model: [
+ { text: qsTr("High"), mode: 0 },
+ { text: qsTr("Medium"), mode: 1 },
+ { text: qsTr("Low"), mode: 2 },
+ ]
+
+ currentIndex: LODManager.worldDetailQuality
+ onCurrentIndexChanged: LODManager.worldDetailQuality = model[currentIndex].mode
+ }
+
+ Header { text: qsTr("Advanced") }
+
+ SwitchSetting {
+ id: deferredRendering
+ text: qsTr("Deferred Rendering")
+
+ value: Render.renderMethod === 0
+ onValueChanged: Render.renderMethod = value ? 0 : 1
+ }
+
+ SettingNote {
+ text: qsTr("May affect performance, especially on mobile devices. Not compatible with MSAA.")
+ }
+
+ SwitchSetting {
+ enabled: deferredRendering.value
+ text: qsTr("Shadows")
+
+ value: Render.shadowsEnabled && deferredRendering.value
+ onValueChanged: Render.shadowsEnabled = value
+ }
+
+ SwitchSetting {
+ enabled: deferredRendering.value
+ text: qsTr("Ambient Occlusion")
+
+ value: Render.ambientOcclusionEnabled && deferredRendering.value
+ onValueChanged: Render.ambientOcclusionEnabled = value
+ }
+
+ SwitchSetting {
+ text: qsTr("Local Lights")
+
+ value: Render.localLightingEnabled
+ onValueChanged: Render.localLightingEnabled = value
+ }
+
+ SwitchSetting {
+ text: qsTr("Bloom")
+
+ value: Render.bloomEnabled
+ onValueChanged: Render.bloomEnabled = value
+ }
+
+ SwitchSetting {
+ text: qsTr("Custom Shaders")
+
+ value: Render.proceduralMaterialsEnabled
+ onValueChanged: Render.proceduralMaterialsEnabled = value
+ }
+
+ SettingNote {
+ text: qsTr("Custom shaders are currently always unlit when deferred rendering is disabled.")
+ }
+
+ SwitchSetting {
+ text: qsTr("Allow third-person camera to pass through walls")
+
+ value: !Render.getCameraClippingEnabled()
+ onValueChanged: !Render.setCameraClippingEnabled(value)
+ }
+
+ Header { text: qsTr("Desktop Window") }
+
+ ComboSetting {
+ text: qsTr("Fullscreen Monitor")
+ model: Render.getScreens()
+
+ currentIndex: {
+ const index = model.indexOf(Render.getFullScreenScreen());
+ return index !== -1 ? index : 0;
+ }
+ onCurrentIndexChanged: Render.setFullScreenScreen(model[currentIndex])
+ }
+
+ ComboSetting {
+ id: fpsLimit
+ text: qsTr("Framerate Limit")
+ model: [
+ // see Performance.RefreshRateProfile
+ qsTr("20 FPS"),
+ qsTr("30 FPS"),
+ qsTr("60 FPS"),
+ qsTr("Custom"),
+ ]
+
+ currentIndex: Performance.getRefreshRateProfile()
+ onCurrentIndexChanged: Performance.setRefreshRateProfile(currentIndex)
+ }
+
+ SettingNote {
+ text: qsTr("Higher settings may increase battery usage. VR is always run at your headset's native framerate when possible.")
+ }
+
+ Header {
+ text: qsTr("Custom Framerate Limit")
+ visible: fpsLimit.currentIndex === 3
+ }
+
+ SpinBoxSetting {
+ visible: fpsLimit.currentIndex === 3
+ text: qsTr("Focused Active")
+ from: 5
+ to: 500
+ value: Performance.getCustomRefreshRate(0 /* FOCUS_ACTIVE */)
+ onValueChanged: Performance.setCustomRefreshRate(0 /* FOCUS_ACTIVE */, value)
+ }
+
+ SpinBoxSetting {
+ visible: fpsLimit.currentIndex === 3
+ text: qsTr("Focused AFK")
+ from: 1
+ to: 500
+ value: Performance.getCustomRefreshRate(1 /* FOCUS_INACTIVE */)
+ onValueChanged: Performance.setCustomRefreshRate(1 /* FOCUS_INACTIVE */, value)
+ }
+
+ SpinBoxSetting {
+ visible: fpsLimit.currentIndex === 3
+ text: qsTr("Unfocused")
+ from: 1
+ to: 500
+ value: Performance.getCustomRefreshRate(2 /* UNFOCUS */)
+ onValueChanged: Performance.setCustomRefreshRate(2 /* FOCUS */, value)
+ }
+
+ SpinBoxSetting {
+ visible: fpsLimit.currentIndex === 3
+ text: qsTr("Minimized")
+ from: 1
+ to: 500
+ value: Performance.getCustomRefreshRate(3 /* MINIMIZED */)
+ onValueChanged: Performance.setCustomRefreshRate(3 /* MINIMIZED */, value)
+ }
+
+ // FIXME: Does anybody actually care about these? Are they useful?
+ SpinBoxSetting {
+ visible: fpsLimit.currentIndex === 3
+ text: qsTr("Startup")
+ from: 5
+ to: 500
+ value: Performance.getCustomRefreshRate(4 /* STARTUP */)
+ onValueChanged: Performance.setCustomRefreshRate(4 /* STARTUP */, value)
+ }
+
+ SpinBoxSetting {
+ visible: fpsLimit.currentIndex === 3
+ text: qsTr("Shutdown")
+ from: 5
+ to: 500
+ value: Performance.getCustomRefreshRate(5 /* SHUTDOWN */)
+ onValueChanged: Performance.setCustomRefreshRate(5 /* SHUTDOWN */, value)
+ }
+}
+
diff --git a/interface/resources/qml/overte/settings/pages/qmldir b/interface/resources/qml/overte/settings/pages/qmldir
new file mode 100644
index 00000000000..db33df213d5
--- /dev/null
+++ b/interface/resources/qml/overte/settings/pages/qmldir
@@ -0,0 +1,5 @@
+module SettingsPages
+General 1.0 General.qml
+Graphics 1.0 Graphics.qml
+Controls 1.0 Controls.qml
+Audio 1.0 Audio.qml
diff --git a/interface/resources/qml/overte/settings/qmldir b/interface/resources/qml/overte/settings/qmldir
new file mode 100644
index 00000000000..88909ca8660
--- /dev/null
+++ b/interface/resources/qml/overte/settings/qmldir
@@ -0,0 +1,11 @@
+module Settings
+Settings 1.0 Settings.qml
+SettingsPage 1.0 SettingsPage.qml
+SettingNote 1.0 SettingNote.qml
+Header 1.0 Header.qml
+SwitchSetting 1.0 SwitchSetting.qml
+FolderSetting 1.0 FolderSetting.qml
+ComboSetting 1.0 ComboSetting.qml
+WideComboSetting 1.0 WideComboSetting.qml
+SpinBoxSetting 1.0 SpinBoxSetting.qml
+SliderSetting 1.0 SliderSetting.qml
diff --git a/interface/resources/qml/overte/staging/MediaPlayer.qml b/interface/resources/qml/overte/staging/MediaPlayer.qml
new file mode 100644
index 00000000000..0e4a6c39a14
--- /dev/null
+++ b/interface/resources/qml/overte/staging/MediaPlayer.qml
@@ -0,0 +1,386 @@
+import QtQuick
+import QtQuick.Layouts
+import QtMultimedia
+
+import ".." as Overte
+
+Rectangle {
+ property url source: "file:///home/ada/var/livestream/clips/corrupted-seagull.mp4"
+
+ anchors.fill: parent
+
+ id: root
+ radius: 8
+ color: Qt.darker(Overte.Theme.paletteActive.base)
+ border.width: Overte.Theme.borderWidth
+ border.color: Overte.Theme.paletteActive.base
+
+ implicitWidth: 854
+ implicitHeight: 480
+
+ MediaPlayer {
+ id: mediaPlayer
+ source: root.source
+ audioOutput: audioOutput
+ videoOutput: videoOutput
+ }
+
+ AudioOutput {
+ id: audioOutput
+ volume: volumeSlider.value
+ }
+
+ VideoOutput {
+ anchors.fill: parent
+ anchors.margins: root.border.width
+
+ id: videoOutput
+ }
+
+ Item {
+ id: controlsOverlay
+ anchors.fill: root
+ anchors.margins: root.border.width
+ opacity: 0.0
+
+ transitions: Transition {
+ NumberAnimation {
+ properties: "opacity"
+ easing.type: Easing.OutExpo
+ duration: 500
+ }
+ }
+
+ states: [
+ State {
+ name: "idle"
+ when: !hoverArea.hovered
+
+ PropertyChanges {
+ target: controlsOverlay
+ opacity: 0.0
+ }
+ },
+ State {
+ name: "hovered"
+ when: hoverArea.hovered
+
+ PropertyChanges {
+ target: controlsOverlay
+ opacity: 1.0
+ }
+ }
+ ]
+
+ HoverHandler {
+ id: hoverArea
+ target: parent
+ }
+
+ Rectangle {
+ anchors.top: controlsOverlay.top
+ anchors.left: controlsOverlay.left
+ anchors.margins: 8
+ width: childrenRect.width
+ height: childrenRect.height
+ radius: 8
+ color: Overte.Theme.highContrast ? "black" : "#80000000"
+ visible: titleLabel.text !== "" || statusLabel.text !== ""
+
+ ColumnLayout {
+ id: statusColumn
+
+ Overte.Label {
+ Layout.margins: 8
+
+ id: titleLabel
+ visible: text !== ""
+
+ // metadata title > extracted filename > raw url
+ text: {
+ const metaTitle = mediaPlayer.metaData.value(MediaMetaData.Title);
+
+ if (metaTitle) {
+ return metaTitle;
+ } else {
+ const file = new URL(root.source).pathname.replace(/^.+?([^\/]+?)(?:\..+)?$/, "$1")
+
+ if (file) {
+ return file;
+ } else {
+ return root.source;
+ }
+ }
+ }
+ }
+
+ Overte.Label {
+ Layout.margins: 8
+
+ id: statusLabel
+ visible: text !== ""
+
+ text: {
+ switch (mediaPlayer.mediaStatus) {
+ case MediaPlayer.LoadingMedia:
+ return qsTr("Loading…");
+
+ case MediaPlayer.BufferingMedia:
+ return qsTr("Buffering…");
+
+ case MediaPlayer.StalledMedia:
+ return qsTr("Stalled! Connection may have been lost.");
+
+ case MediaPlayer.InvalidMedia:
+ return qsTr("Media file is not playable.");
+
+ default: return "";
+ }
+ }
+ }
+ }
+ }
+
+ Overte.RoundButton {
+ anchors.top: controlsOverlay.top
+ anchors.right: controlsOverlay.right
+ anchors.margins: 8
+
+ icon.color: Overte.Theme.paletteActive.buttonText
+ icon.source: "../icons/info.svg"
+ icon.width: 24
+ icon.height: 24
+
+ id: infoButton
+ checkable: true
+ }
+
+ Rectangle {
+ anchors.right: infoButton.left
+ anchors.top: infoButton.top
+ anchors.rightMargin: 8
+ radius: 8
+ color: Overte.Theme.highContrast ? "black" : "#80000000"
+ visible: infoButton.checked
+
+ width: Math.min(controlsOverlay.width / 2, metadataColumn.implicitWidth + 16)
+ height: metadataColumn.implicitHeight + 16
+
+ ColumnLayout {
+ id: metadataColumn
+ anchors.fill: parent
+ anchors.margins: 8
+
+ Overte.Label {
+ Layout.fillWidth: true
+ font.family: Overte.Theme.monoFontFamily
+ font.pixelSize: Overte.Theme.fontPixelSizeSmall
+ elide: Text.ElideMiddle
+ text: root.source
+ }
+
+ Overte.Label {
+ Layout.fillWidth: true
+ text: {
+ const containerNames = new Map([
+ [0, "WMV"],
+ [1, "AVI"],
+ [2, "Matroska (mkv)"],
+ [3, "MPEG-4 (mp4)"],
+ [4, "Ogg"],
+ [5, "QuickTime (mov)"],
+ [6, "WebM"],
+ [7, "MPEG-4 Audio"],
+ [8, "AAC"],
+ [9, "WMA"],
+ [10, "MP3"],
+ [11, "FLAC"],
+ [12, "WAV"],
+ ]);
+ const vcodecNames = new Map([
+ [0, "MPEG-1"],
+ [1, "MPEG-2"],
+ [2, "MPEG-4"],
+ [3, "H.264"],
+ [4, "H.265"],
+ [5, "VP8"],
+ [6, "VP9"],
+ [7, "AV1"],
+ [8, "Theora"],
+ [9, "WMV Video"],
+ [10, "Motion JPEG"],
+ ]);
+ const acodecNames = new Map([
+ [0, "MP3"],
+ [1, "AAC"],
+ [2, "Dolby AC3"],
+ [3, "Dolby EAC3"],
+ [4, "FLAC"],
+ [5, "Dolby TrueHD"],
+ [6, "Opus"],
+ [7, "Ogg Vorbis"],
+ [8, "WAV"],
+ [9, "WMA"],
+ [10, "ALAC"],
+ ]);
+ let chunks = [];
+
+ if (
+ mediaPlayer.mediaStatus === MediaPlayer.NoMedia ||
+ mediaPlayer.mediaStatus === MediaPlayer.InvalidMedia
+ ) {
+ return qsTr("Unloaded");
+ }
+
+ const containerName = containerNames.get(mediaPlayer.metaData.value(MediaMetaData.FileFormat));
+ chunks.push(`${qsTr("Container:")} ${containerName ?? qsTr("Other")}`);
+
+ if (mediaPlayer.hasVideo) {
+ const track = mediaPlayer.videoTracks[mediaPlayer.activeVideoTrack];
+ const codecName = vcodecNames.get(track.value(MediaMetaData.VideoCodec));
+ const fps = track.value(MediaMetaData.VideoFrameRate);
+ const resolution = track.value(MediaMetaData.Resolution);
+ chunks.push(`${qsTr("Video:")} ${resolution.width}x${resolution.height}@${fps.toFixed(2)} ${codecName ?? qsTr("Other")}`);
+ }
+
+ if (mediaPlayer.hasAudio) {
+ const track = mediaPlayer.audioTracks[mediaPlayer.activeAudioTrack];
+ const codecName = acodecNames.get(track.value(MediaMetaData.AudioCodec));
+ const bitrate = Math.round(track.value(MediaMetaData.AudioBitRate) / 1000);
+ chunks.push(`${qsTr("Audio:")} ${bitrate} kbps ${codecName ?? qsTr("Other")}`);
+ }
+
+ return chunks.join("\n");
+ }
+ }
+ }
+ }
+
+ Rectangle {
+ anchors.left: controlsOverlay.left
+ anchors.right: controlsOverlay.right
+ anchors.bottom: controlsOverlay.bottom
+ anchors.margins: 8
+ height: mediaControlsLayout.implicitHeight + 16
+ radius: 16
+ color: Overte.Theme.highContrast ? "black" : "#80000000"
+
+ RowLayout {
+ anchors.fill: parent
+ anchors.margins: 8
+ id: mediaControlsLayout
+
+ Overte.RoundButton {
+ implicitWidth: 48
+ implicitHeight: 48
+ icon.source: (
+ mediaPlayer.playbackState === MediaPlayer.PlayingState ?
+ "../icons/pause.svg" :
+ "../icons/triangle_right.svg"
+ )
+ icon.color: Overte.Theme.paletteActive.buttonText
+ icon.width: 32
+ icon.height: 32
+
+ onClicked: {
+ if (mediaPlayer.playbackState === MediaPlayer.PlayingState) {
+ mediaPlayer.pause();
+ } else {
+ mediaPlayer.play();
+ }
+ }
+ }
+
+ ColumnLayout {
+ RowLayout {
+ Overte.Label {
+ font.family: Overte.Theme.monoFontFamily
+ text: {
+ const totalSeconds = Math.floor(mediaPlayer.position / 1000);
+
+ const seconds = (totalSeconds % 60).toString().padStart(2, "0");
+ const minutes = (Math.floor(totalSeconds / 60) % 60).toString().padStart(2, "0");
+ const hours = Math.floor(totalSeconds / 60 / 60).toString().padStart(2, "0");
+
+ return `${hours}:${minutes}:${seconds}`;
+ }
+ }
+
+ Item { Layout.fillWidth: true }
+
+ Overte.Label {
+ font.family: Overte.Theme.monoFontFamily
+ text: {
+ const totalSeconds = Math.floor(mediaPlayer.duration / 1000);
+
+ const seconds = (totalSeconds % 60).toString().padStart(2, "0");
+ const minutes = (Math.floor(totalSeconds / 60) % 60).toString().padStart(2, "0");
+ const hours = Math.floor(totalSeconds / 60 / 60).toString().padStart(2, "0");
+
+ return `${hours}:${minutes}:${seconds}`;
+ }
+ }
+ }
+
+ RowLayout {
+ Overte.RoundButton {
+ icon.source: "../icons/skip_backward.svg"
+ icon.width: 24
+ icon.height: 24
+ icon.color: Overte.Theme.paletteActive.buttonText
+ onClicked: mediaPlayer.position -= 5000
+ }
+
+ Overte.Slider {
+ Layout.fillWidth: true
+ value: mediaPlayer.position / mediaPlayer.duration
+
+ onMoved: {
+ mediaPlayer.position = value * mediaPlayer.duration;
+ }
+ }
+
+ Overte.RoundButton {
+ icon.source: "../icons/skip_forward.svg"
+ icon.width: 24
+ icon.height: 24
+ icon.color: Overte.Theme.paletteActive.buttonText
+ onClicked: mediaPlayer.position += 5000
+ }
+ }
+ }
+
+ Overte.RoundButton {
+ id: muteButton
+ icon.source: {
+ if (audioOutput.muted) {
+ return "../icons/speaker_muted.svg";
+ } else if (audioOutput.volume === 0) {
+ return "../icons/speaker_inactive.svg"
+ } else {
+ return "../icons/speaker_active.svg";
+ }
+ }
+ icon.color: Overte.Theme.paletteActive.buttonText
+ icon.width: 24
+ icon.height: 24
+ checkable: true
+ checked: audioOutput.muted
+
+ onClicked: audioOutput.muted = !audioOutput.muted;
+ }
+
+ Overte.Slider {
+ Layout.preferredWidth: 96
+
+ id: volumeSlider
+ value: 0.75
+
+ onValueChanged: {
+ audioOutput.muted = false;
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/interface/resources/qml/overte/staging/Node.qml b/interface/resources/qml/overte/staging/Node.qml
new file mode 100644
index 00000000000..3c8a07ba543
--- /dev/null
+++ b/interface/resources/qml/overte/staging/Node.qml
@@ -0,0 +1,181 @@
+import QtQuick
+import QtQuick.Layouts
+import QtQuick.Effects
+
+import ".." as Overte
+
+Rectangle {
+ property color titleColor: "#a000a0"
+ property string title: "Error"
+ property string bodyText: ""
+ property string helpText: ""
+
+ property list inputs: []
+ property list outputs: []
+
+ DragHandler {
+ dragThreshold: Overte.Theme.fontPixelSize
+ cursorShape: Qt.DragMoveCursor
+ }
+
+ width: {
+ let accum = 0;
+
+ accum += titleLayout.implicitWidth;
+
+ return accum;
+ }
+
+ height: {
+ let accum = 0;
+
+ if (title !== "") { accum += titlebar.height + 4; }
+
+ let inputsAccum = 0, outputsAccum = 0;
+ for (const input of inputs) { inputsAccum += 24 + 4; }
+ for (const output of outputs) { outputsAccum += 24 + 4; }
+ accum += Math.max(inputsAccum, outputsAccum) + 4;
+
+ return accum;
+ }
+
+ id: control
+ radius: Overte.Theme.borderRadius
+ topLeftRadius: control.radius * 3
+ topRightRadius: control.radius * 3
+
+ border.width: Overte.Theme.borderWidth
+ border.color: (
+ Overte.Theme.highContrast ?
+ Overte.Theme.paletteActive.buttonText :
+ Qt.darker(Overte.Theme.paletteActive.base, Overte.Theme.borderDarker)
+ )
+ gradient: Gradient {
+ GradientStop {
+ position: 0.0; color: Qt.lighter(Overte.Theme.paletteActive.base, 1.1)
+ }
+ GradientStop {
+ position: 0.5; color: Overte.Theme.paletteActive.base
+ }
+ GradientStop {
+ position: 1.0; color: Qt.darker(Overte.Theme.paletteActive.base, 1.1)
+ }
+ }
+
+ Rectangle {
+ anchors.fill: titleLayout
+ visible: control.title !== ""
+ id: titlebar
+
+ topLeftRadius: control.radius * 2
+ topRightRadius: control.radius * 2
+
+ gradient: Gradient {
+ GradientStop {
+ position: 0.0
+ color: Qt.lighter(control.titleColor, 1.1)
+ }
+ GradientStop {
+ position: 0.2
+ color: Qt.darker(control.titleColor, 1.1)
+ }
+ GradientStop {
+ position: 0.8
+ color: control.titleColor
+ }
+ GradientStop {
+ position: 1.0
+ color: Qt.darker(control.titleColor, 1.3)
+ }
+ }
+ }
+
+ RowLayout {
+ anchors.left: control.left
+ anchors.right: control.right
+ anchors.top: control.top
+ anchors.margins: Overte.Theme.borderWidth
+
+ id: titleLayout
+ spacing: 4
+
+ Overte.Label {
+ Layout.margins: 6
+ Layout.fillWidth: true
+ Layout.fillHeight: true
+ Layout.minimumHeight: Overte.Theme.fontPixelSize * 1.5
+ visible: control.title !== ""
+ verticalAlignment: Text.AlignVCenter
+ text: control.title
+ id: titleText
+ color: "white"
+
+ layer.enabled: true
+ layer.effect: MultiEffect {
+ shadowEnabled: true
+ shadowVerticalOffset: 2
+ shadowHorizontalOffset: 2
+ shadowColor: Qt.darker(control.titleColor, 2.0)
+ shadowBlur: 0.2
+ }
+ }
+
+ Overte.RoundButton {
+ Layout.margins: 4
+ Layout.alignment: Qt.AlignCenter
+ visible: control.helpText !== ""
+ implicitWidth: Overte.Theme.fontPixelSize + 8
+ implicitHeight: Overte.Theme.fontPixelSize + 8
+ text: "?"
+ }
+ }
+
+ Column {
+ anchors.left: control.left
+ anchors.top: titlebar.bottom
+ anchors.topMargin: 4
+ spacing: 4
+ id: leftPlugs
+
+ Repeater {
+ model: control.inputs
+ delegate: NodePlug {
+ required property string type
+
+ x: -10
+ color: Overte.Theme.paletteActive.scriptTypeColors[type] ?? "#ff00ff"
+ }
+ }
+ }
+
+ Overte.Label {
+ anchors.margins: 8
+ anchors.bottom: control.bottom
+ anchors.left: leftPlugs.right
+ anchors.right: rightPlugs.left
+ anchors.top: control.title !== "" ? titlebar.bottom : control.top
+ visible: control.bodyText !== ""
+ horizontalAlignment: Text.AlignHCenter
+ verticalAlignment: Text.AlignVCenter
+ font.pixelSize: control.height / 2
+ text: control.bodyText
+ }
+
+ Column {
+ anchors.right: control.right
+ anchors.top: titlebar.bottom
+ anchors.topMargin: 4
+ spacing: 4
+ id: rightPlugs
+
+ Repeater {
+ model: control.outputs
+ delegate: NodePlug {
+ required property string type
+
+ x: 10
+ color: Overte.Theme.paletteActive.scriptTypeColors[type] ?? "#ff00ff"
+ }
+ }
+ }
+}
diff --git a/interface/resources/qml/overte/staging/NodeGraph.qml b/interface/resources/qml/overte/staging/NodeGraph.qml
new file mode 100644
index 00000000000..974fdcbff60
--- /dev/null
+++ b/interface/resources/qml/overte/staging/NodeGraph.qml
@@ -0,0 +1,29 @@
+import QtQuick
+
+import ".." as Overte
+import "."
+
+Rectangle {
+ id: nodeGraph
+ color: Qt.darker(Overte.Theme.paletteActive.base, 1.1)
+
+ Overte.Switch {
+ onToggled: Overte.Theme.darkMode = checked
+ }
+
+ Node {
+ x: 64
+ y: 64
+
+ titleColor: "#00a000"
+ title: qsTr("Flush Entity Properties")
+ helpText: qsTr("Commits all changes to an entity's properties at once.")
+ inputs: [
+ { type: "exec" },
+ { type: "entity" },
+ ]
+ outputs: [
+ { type: "exec" },
+ ]
+ }
+}
diff --git a/interface/resources/qml/overte/staging/NodePlug.qml b/interface/resources/qml/overte/staging/NodePlug.qml
new file mode 100644
index 00000000000..d80277d3593
--- /dev/null
+++ b/interface/resources/qml/overte/staging/NodePlug.qml
@@ -0,0 +1,52 @@
+import QtQuick
+
+import ".." as Overte
+
+Rectangle {
+ property bool active: false
+
+ implicitWidth: 24
+ implicitHeight: 24
+
+ id: plug
+ radius: height / 2
+ border.width: Overte.Theme.borderWidth
+ border.color: Qt.darker(color, Overte.Theme.borderDarker)
+ color: "#a000a0"
+
+ gradient: Gradient {
+ GradientStop {
+ position: 0.0
+ color: Qt.lighter(plug.color, 1.3)
+ }
+ GradientStop {
+ position: 0.5
+ color: plug.color
+ }
+ GradientStop {
+ position: 1.0
+ color: Qt.darker(plug.color, 1.3)
+ }
+ }
+
+ Rectangle {
+ anchors.fill: plug
+ anchors.margins: plug.height / 5
+ radius: height / 2
+
+ gradient: Gradient {
+ GradientStop {
+ position: 0.0
+ color: Qt.lighter(plug.color, plug.active ? 2.5 : 0.6)
+ }
+ GradientStop {
+ position: 0.5
+ color: Qt.lighter(plug.color, plug.active ? 1.5 : 0.7)
+ }
+ GradientStop {
+ position: 1.0
+ color: Qt.lighter(plug.color, plug.active ? 1.0 : 0.8)
+ }
+ }
+ }
+}
diff --git a/interface/resources/qml/overte/staging/README.md b/interface/resources/qml/overte/staging/README.md
new file mode 100644
index 00000000000..05b0f5676c1
--- /dev/null
+++ b/interface/resources/qml/overte/staging/README.md
@@ -0,0 +1 @@
+Stuff in this folder isn't ready yet. They're here so they can be picked up later.
diff --git a/interface/resources/qml/overte/staging/desktop/AppToolbar.qml b/interface/resources/qml/overte/staging/desktop/AppToolbar.qml
new file mode 100644
index 00000000000..07a0b1b5d3e
--- /dev/null
+++ b/interface/resources/qml/overte/staging/desktop/AppToolbar.qml
@@ -0,0 +1,82 @@
+import QtQuick
+import QtQuick.Controls
+
+import ".." as Overte
+
+Item {
+ readonly property int buttonSize: 80
+
+ id: root
+ implicitHeight: buttonSize + (buttonList.anchors.margins * 2) + Overte.Theme.scrollbarWidth + 4
+ implicitWidth: 8 * (buttonSize + buttonList.spacing) + buttonList.anchors.margins
+
+ Rectangle {
+ anchors.fill: root
+ radius: Overte.Theme.borderRadius
+ color: Overte.Theme.paletteActive.dialogShade
+ }
+
+ ListView {
+ anchors.fill: parent
+ anchors.margins: 4
+
+ id: buttonList
+ orientation: Qt.Horizontal
+ spacing: 4
+ clip: true
+
+ ScrollBar.horizontal: Overte.ScrollBar {
+ policy: ScrollBar.AsNeeded
+ }
+
+ model: [
+ {name: "Mute", iconSource: "../icons/speaker_muted.svg", checkable: true},
+ {name: "Settings", iconSource: "../icons/settings_cog.svg", checkable: true},
+ {name: "Contacts", iconSource: "../icons/add_friend.svg", checkable: true},
+ {name: "Body Paint", iconSource: "../icons/pencil.svg", checkable: true},
+ {name: "Eyes", iconSource: "../icons/eye_open.svg", checkable: false},
+
+ {name: "Star", iconSource: "../icons/gold_star.svg", checkable: false},
+ {name: "Star", iconSource: "../icons/gold_star.svg", checkable: false},
+ {name: "Star", iconSource: "../icons/gold_star.svg", checkable: false},
+ {name: "Star", iconSource: "../icons/gold_star.svg", checkable: false},
+ {name: "Star", iconSource: "../icons/gold_star.svg", checkable: false},
+ ]
+ delegate: Overte.Button {
+ required property url iconSource
+ required property string name
+ required checkable
+
+ implicitWidth: buttonSize
+ implicitHeight: buttonSize
+
+ Image {
+ anchors.top: parent.top
+ anchors.left: parent.left
+ anchors.right: parent.right
+ anchors.bottom: buttonLabel.top
+ anchors.margins: 2
+
+ source: iconSource
+ sourceSize.width: width
+ sourceSize.height: height
+
+ fillMode: Image.PreserveAspectFit
+ }
+
+ Overte.Label {
+ id: buttonLabel
+ anchors.left: parent.left
+ anchors.right: parent.right
+ anchors.bottom: parent.bottom
+ anchors.margins: 2
+ wrapMode: Text.Wrap
+ horizontalAlignment: Text.AlignHCenter
+ verticalAlignment: Text.AlignBottom
+ text: name
+
+ font.pixelSize: Overte.Theme.fontPixelSizeSmall
+ }
+ }
+ }
+}
diff --git a/interface/resources/qml/overte/staging/desktop/Desktop.qml b/interface/resources/qml/overte/staging/desktop/Desktop.qml
new file mode 100644
index 00000000000..00e52b1085d
--- /dev/null
+++ b/interface/resources/qml/overte/staging/desktop/Desktop.qml
@@ -0,0 +1,70 @@
+import QtQuick
+
+import ".." as Overte
+import "."
+
+//Item {
+Rectangle {
+ color: "#303030"
+
+ anchors.fill: parent
+ implicitWidth: 800
+ implicitHeight: 600
+
+ id: desktopRoot
+
+ Item {
+ id: toolbarContainer
+ anchors.horizontalCenter: desktopRoot.horizontalCenter
+ anchors.bottom: toolbarToggleButton.top
+ anchors.margins: 4
+ height: appToolbar.height
+
+ AppToolbar {
+ id: appToolbar
+ x: -width / 2
+ y: Overte.Theme.reducedMotion ? 0 : height * 2
+ opacity: 0.0
+
+ states: State {
+ name: "open"
+ when: toolbarToggleButton.checked
+ PropertyChanges {
+ target: appToolbar
+ y: 0
+ opacity: 1.0
+ }
+ }
+
+ transitions: Transition {
+ reversible: true
+
+ NumberAnimation {
+ properties: "y"
+ easing.type: Easing.OutExpo
+ duration: 500
+ }
+
+ NumberAnimation {
+ properties: "opacity"
+ easing.type: Easing.OutExpo
+ duration: 500
+ }
+ }
+ }
+ }
+
+ Overte.RoundButton {
+ anchors.horizontalCenter: desktopRoot.horizontalCenter
+ anchors.bottom: desktopRoot.bottom
+ anchors.margins: 16
+ focusPolicy: Qt.NoFocus
+
+ id: toolbarToggleButton
+ checkable: true
+ icon.source: checked ? "../icons/triangle_down.svg" : "../icons/triangle_up.svg"
+ icon.width: 24
+ icon.height: 24
+ icon.color: Overte.Theme.paletteActive.buttonText
+ }
+}
diff --git a/interface/resources/qml/overte/staging/desktop/qmldir b/interface/resources/qml/overte/staging/desktop/qmldir
new file mode 100644
index 00000000000..5621c982de8
--- /dev/null
+++ b/interface/resources/qml/overte/staging/desktop/qmldir
@@ -0,0 +1,3 @@
+module Desktop
+Desktop 1.0 Desktop.qml
+AppToolbar 1.0 AppToolbar.qml
diff --git a/interface/resources/qml/overte/staging/keyboard/Keyboard.qml b/interface/resources/qml/overte/staging/keyboard/Keyboard.qml
new file mode 100644
index 00000000000..ce92428a7d7
--- /dev/null
+++ b/interface/resources/qml/overte/staging/keyboard/Keyboard.qml
@@ -0,0 +1,192 @@
+import QtQuick
+import QtQuick.Layouts
+
+import "../" as Overte
+import "." as OverteKeyboard
+
+Rectangle {
+ id: keyboardRoot
+ color: Overte.Theme.paletteActive.base
+ border.color: Qt.lighter(color)
+ border.width: 2
+ radius: 12
+ width: 1300
+ height: 380
+
+ PropertyAnimation on opacity {
+ from: 0
+ to: 1
+ duration: 500
+ }
+
+ // TODO: replace this with Keybaord.layout or something
+ Component.onCompleted: {
+ const xhr = new XMLHttpRequest;
+ xhr.open("GET", "../../keyboard_ansi.json");
+ xhr.onreadystatechange = () => {
+ if (xhr.readyState === XMLHttpRequest.DONE) {
+ buildLayout(JSON.parse(xhr.responseText));
+ }
+ };
+ xhr.send();
+ }
+
+ function keyPressed(key) {
+ // TODO
+ print(`Press ${key.legend}, ${key.unshifted_keycode}`);
+
+ if (key.unshifted_char !== "") {
+ modifiers = 0;
+ }
+ }
+
+ function keyReleased(key) {
+ // TODO
+ print(`Release ${key.legend}, ${key.unshifted_keycode}`);
+
+ switch (key.unshifted_keycode) {
+ case Qt.Key_Shift: modifiers ^= Qt.ShiftModifier; break;
+ case Qt.Key_Control: modifiers ^= Qt.ControlModifier; break;
+ case Qt.Key_Alt: modifiers ^= Qt.AltModifier; break;
+ case Qt.Key_Meta: modifiers ^= Qt.MetaModifier; break;
+ }
+ }
+
+ property int modifiers: 0
+
+ Component {
+ id: columnLayout
+ ColumnLayout {
+ property real horizontalSpan: 1.0
+ property real verticalSpan: 1.0
+
+ Layout.fillWidth: true
+ Layout.fillHeight: true
+ Layout.horizontalStretchFactor: Math.round(horizontalSpan * 1000)
+ Layout.verticalStretchFactor: Math.round(verticalSpan * 1000)
+ }
+ }
+ Component {
+ id: rowLayout
+ RowLayout {
+ property real horizontalSpan: 1.0
+ property real verticalSpan: 1.0
+
+ Layout.fillWidth: true
+ Layout.fillHeight: true
+ Layout.horizontalStretchFactor: Math.round(horizontalSpan * 1000)
+ Layout.verticalStretchFactor: Math.round(verticalSpan * 1000)
+ }
+ }
+ Component {
+ id: item
+
+ Item {
+ property real horizontalSpan: 1.0
+ property real verticalSpan: 1.0
+
+ Layout.fillWidth: true
+ Layout.fillHeight: true
+ Layout.horizontalStretchFactor: Math.round(horizontalSpan * 1000)
+ Layout.verticalStretchFactor: Math.round(verticalSpan * 1000)
+ implicitWidth: 1
+ implicitHeight: 1
+ }
+ }
+ Component {
+ id: keyboardKey
+ KeyboardKey {
+ Layout.fillWidth: true
+ Layout.fillHeight: true
+ Layout.horizontalStretchFactor: Math.round(span * 1000)
+ implicitWidth: 1
+ implicitHeight: 1
+ }
+ }
+
+ function buildLayout(layout) {
+ for (const topColumn of layout) {
+ let topColumnSpan = 1.0;
+ if (!isNaN(topColumn[0])) {
+ topColumnSpan = topColumn[0];
+ }
+
+ const topColumnItem = columnLayout.createObject(layoutRoot, {
+ horizontalSpan: topColumnSpan,
+ });
+
+ for (const row of topColumn) {
+ // the span width we handled earlier
+ if (!isNaN(parseFloat(row))) { continue; }
+
+ // vertical spacer
+ if (!Array.isArray(row)) {
+ item.createObject(topColumnItem, {
+ verticalSpan: row.verticalSpan ?? 1.0,
+ });
+ continue;
+ }
+
+ const rowItem = rowLayout.createObject(topColumnItem);
+
+ for (const key of row) {
+ if (key.legend) {
+ const o = keyboardKey.createObject(rowItem, {
+ unshifted_keycode: Qt[key.keycode ?? key.unshifted_keycode] ?? 0,
+ shifted_keycode: Qt[key.keycode ?? key.shifted_keycode] ?? 0,
+ unshifted_char: key.unshifted_char ?? "",
+ shifted_char: key.shifted_char ?? "",
+ legend: key.legend ?? "",
+ span: key.span ?? 1.0,
+ });
+
+ if (
+ o.unshifted_keycode === Qt.Key_Insert ||
+ o.unshifted_keycode === Qt.Key_Home ||
+ o.unshifted_keycode === Qt.Key_PageUp ||
+ o.unshifted_keycode === Qt.Key_Delete ||
+ o.unshifted_keycode === Qt.Key_End ||
+ o.unshifted_keycode === Qt.Key_PageDown
+ ) {
+ o.font.pixelSize = Overte.Theme.fontPixelSizeSmall;
+ }
+
+ if (
+ o.unshifted_keycode === Qt.Key_CapsLock ||
+ o.unshifted_keycode === Qt.Key_Shift ||
+ o.unshifted_keycode === Qt.Key_Control ||
+ o.unshifted_keycode === Qt.Key_Alt ||
+ o.unshifted_keycode === Qt.Key_Meta
+ ) {
+ o.checkable = true;
+ }
+ } else {
+ // horizontal spacer
+ item.createObject(rowItem, {
+ horizontalSpan: key.span ?? 1.0,
+ });
+ }
+ }
+ }
+ }
+ }
+
+ RowLayout {
+ id: layoutRoot
+ anchors.fill: parent
+ anchors.margins: 12
+ }
+
+ Overte.RoundButton {
+ anchors.top: parent.top
+ anchors.right: parent.right
+ anchors.margins: 6
+ horizontalPadding: 2
+ verticalPadding: 2
+ width: 40
+ height: 40
+
+ backgroundColor: Overte.Theme.paletteActive.buttonDestructive
+ text: "🗙"
+ }
+}
diff --git a/interface/resources/qml/overte/staging/keyboard/KeyboardKey.qml b/interface/resources/qml/overte/staging/keyboard/KeyboardKey.qml
new file mode 100644
index 00000000000..9aaa7e7b6bb
--- /dev/null
+++ b/interface/resources/qml/overte/staging/keyboard/KeyboardKey.qml
@@ -0,0 +1,82 @@
+import QtQuick
+
+import "../"
+
+Button {
+ property int unshifted_keycode: 0
+ property int shifted_keycode: 0
+
+ property string unshifted_char: ""
+ property string shifted_char: ""
+
+ property string legend: ""
+ property real span: 1.0
+
+ id: control
+ text: legend
+ focusPolicy: Qt.NoFocus
+ horizontalPadding: 4
+ verticalPadding: 4
+
+ onPressed: keyboardRoot.keyPressed(this)
+ onReleased: keyboardRoot.keyReleased(this)
+
+ Connections {
+ target: keyboardRoot
+
+ function onModifiersChanged() {
+ const value = keyboardRoot.modifiers;
+
+ switch (unshifted_keycode) {
+ case Qt.Key_Shift:
+ checked = (value & Qt.ShiftModifier) !== 0;
+ break;
+
+ case Qt.Key_Control:
+ checked = (value & Qt.ControlModifier) !== 0;
+ break;
+
+ case Qt.Key_Alt:
+ checked = (value & Qt.AltModifier) !== 0;
+ break;
+
+ case Qt.Key_Meta:
+ checked = (value & Qt.MetaModifier) !== 0;
+ break;
+ }
+ }
+ }
+
+ contentItem: Text {
+ text: control.text
+ font.family: control.font.family
+ font.pixelSize: control.height / 3
+ color: Theme.paletteActive.buttonText
+ horizontalAlignment: Text.AlignHCenter
+ verticalAlignment: Text.AlignVCenter
+ elide: Text.ElideNone
+ }
+
+ background: Rectangle {
+ id: controlBg
+ radius: Theme.borderRadius
+ border.width: Theme.borderWidth
+ border.color: control.checked ?
+ Theme.paletteActive.focusRing :
+ (Theme.highContrast ? Theme.paletteActive.controlText : Qt.darker(control.backgroundColor, Theme.borderDarker))
+ color: (control.down || control.checked) ? Qt.darker(control.backgroundColor, Theme.checkedDarker) :
+ control.hovered ? Qt.lighter(control.backgroundColor, Theme.hoverLighter) :
+ control.backgroundColor;
+ gradient: Gradient {
+ GradientStop {
+ position: 0.0; color: Qt.lighter(controlBg.color, (control.down || control.checked) ? 0.9 : 1.1)
+ }
+ GradientStop {
+ position: 0.5; color: controlBg.color
+ }
+ GradientStop {
+ position: 1.0; color: Qt.darker(controlBg.color, (control.down || control.checked) ? 0.9 : 1.1)
+ }
+ }
+ }
+}
diff --git a/interface/resources/qml/overte/staging/keyboard/keyboard_ansi.json b/interface/resources/qml/overte/staging/keyboard/keyboard_ansi.json
new file mode 100644
index 00000000000..e812db21438
--- /dev/null
+++ b/interface/resources/qml/overte/staging/keyboard/keyboard_ansi.json
@@ -0,0 +1,506 @@
+[
+ [
+ [
+ {
+ "legend": "Esc",
+ "keycode": "Key_Escape"
+ },
+ { "span": 0.25 },
+ {
+ "legend": "F1",
+ "keycode": "Key_F1"
+ },
+ {
+ "legend": "F2",
+ "keycode": "Key_F2"
+ },
+ {
+ "legend": "F3",
+ "keycode": "Key_F3"
+ },
+ {
+ "legend": "F4",
+ "keycode": "Key_F4"
+ },
+ { "span": 0.25 },
+ {
+ "legend": "F5",
+ "keycode": "Key_F5"
+ },
+ {
+ "legend": "F6",
+ "keycode": "Key_F6"
+ },
+ {
+ "legend": "F7",
+ "keycode": "Key_F7"
+ },
+ {
+ "legend": "F8",
+ "keycode": "Key_F8"
+ },
+ { "span": 0.25 },
+ {
+ "legend": "F9",
+ "keycode": "Key_F9"
+ },
+ {
+ "legend": "F10",
+ "keycode": "Key_F10"
+ },
+ {
+ "legend": "F11",
+ "keycode": "Key_F11"
+ },
+ {
+ "legend": "F12",
+ "keycode": "Key_F12"
+ }
+ ],
+ { "verticalSpan": 0.003 },
+ [
+ {
+ "legend": "`\n~",
+ "unshifted_keycode": "Key_QuoteLeft",
+ "shifted_keycode": "Key_Tilde",
+ "unshifted_char": "`",
+ "shifted_char": "~"
+ },
+ {
+ "legend": "!\n1",
+ "unshifted_keycode": "Key_1",
+ "shifted_keycode": "Key_Exclam",
+ "unshifted_char": "1",
+ "shifted_char": "!"
+ },
+ {
+ "legend": "@\n2",
+ "unshifted_keycode": "Key_2",
+ "shifted_keycode": "Key_At",
+ "unshifted_char": "2",
+ "shifted_char": "@"
+ },
+ {
+ "legend": "#\n3",
+ "unshifted_keycode": "Key_3",
+ "shifted_keycode": "Key_NumberSign",
+ "unshifted_char": "3",
+ "shifted_char": "#"
+ },
+ {
+ "legend": "$\n4",
+ "unshifted_keycode": "Key_4",
+ "shifted_keycode": "Key_Dollar",
+ "unshifted_char": "4",
+ "shifted_char": "#"
+ },
+ {
+ "legend": "%\n5",
+ "unshifted_keycode": "Key_5",
+ "shifted_keycode": "Key_Percent",
+ "unshifted_char": "5",
+ "shifted_char": "%"
+ },
+ {
+ "legend": "^\n6",
+ "unshifted_keycode": "Key_6",
+ "shifted_keycode": "Key_AsciiCircum",
+ "unshifted_char": "6",
+ "shifted_char": "^"
+ },
+ {
+ "legend": "&\n7",
+ "unshifted_keycode": "Key_7",
+ "shifted_keycode": "Key_Ampersand",
+ "unshifted_char": "7",
+ "shifted_char": "&"
+ },
+ {
+ "legend": "*\n8",
+ "unshifted_keycode": "Key_8",
+ "shifted_keycode": "Key_Asterisk",
+ "unshifted_char": "8",
+ "shifted_char": "*"
+ },
+ {
+ "legend": "(\n9",
+ "unshifted_keycode": "Key_9",
+ "shifted_keycode": "Key_ParenLeft",
+ "unshifted_key": "9",
+ "shifted_key": "("
+ },
+ {
+ "legend": ")\n0",
+ "unshifted_keycode": "Key_0",
+ "shifted_keycode": "Key_ParentRight",
+ "unshifted_key": "0",
+ "shifted_key": ")"
+ },
+ {
+ "legend": "_\n-",
+ "unshifted_keycode": "Key_Minus",
+ "shifted_keycode": "Key_Underscore",
+ "unshifted_char": "-",
+ "shifted_char": "_"
+ },
+ {
+ "legend": "+\n=",
+ "unshifted_keycode": "Key_Equals",
+ "shifted_keycode": "Key_Plus",
+ "unshifted_char": "=",
+ "shifted_char": "+"
+ },
+ {
+ "legend": "Backspace",
+ "keycode": "Key_Backspace",
+ "span": 1.5
+ }
+ ],
+ [
+ {
+ "legend": "Tab",
+ "keycode": "Key_Tab",
+ "span": 1.1
+ },
+ {
+ "legend": "Q",
+ "keycode": "Key_Q",
+ "unshifted_char": "q",
+ "shifted_char": "Q"
+ },
+ {
+ "legend": "W",
+ "keycode": "Key_W",
+ "unshifted_char": "w",
+ "shifted_char": "W"
+ },
+ {
+ "legend": "E",
+ "keycode": "Key_E",
+ "unshifted_char": "e",
+ "shifted_char": "E"
+ },
+ {
+ "legend": "R",
+ "keycode": "Key_R",
+ "unshifted_char": "r",
+ "shifted_char": "R"
+ },
+ {
+ "legend": "T",
+ "keycode": "Key_T",
+ "unshifted_char": "t",
+ "shifted_char": "T"
+ },
+ {
+ "legend": "Y",
+ "keycode": "Key_Y",
+ "unshifted_char": "y",
+ "shifted_char": "Y"
+ },
+ {
+ "legend": "U",
+ "keycode": "Key_U",
+ "unshifted_char": "u",
+ "shifted_char": "U"
+ },
+ {
+ "legend": "I",
+ "keycode": "Key_I",
+ "unshifted_char": "i",
+ "shifted_char": "I"
+ },
+ {
+ "legend": "O",
+ "keycode": "Key_O",
+ "unshifted_char": "o",
+ "shifted_char": "O"
+ },
+ {
+ "legend": "P",
+ "keycode": "Key_P",
+ "unshifted_char": "p",
+ "shifted_char": "P"
+ },
+ {
+ "legend": "{\n[",
+ "unshifted_keycode": "Key_BracketLeft",
+ "unshifted_keycode": "Key_BraceLeft",
+ "unshifted_char": "[",
+ "shifted_char": "{"
+ },
+ {
+ "legend": "}\n]",
+ "unshifted_keycode": "Key_BracketRight",
+ "unshifted_keycode": "Key_BraceRight",
+ "unshifted_char": "]",
+ "shifted_char": "}"
+ },
+ {
+ "legend": "|\n\\",
+ "unshifted_keycode": "Key_Backslash",
+ "unshifted_keycode": "Key_Bar",
+ "unshifted_char": "\\",
+ "shifted_char": "|",
+ "span": 1.3
+ }
+ ],
+ [
+ {
+ "legend": "Caps",
+ "keycode": "Key_CapsLock",
+ "span": 1.3
+ },
+ {
+ "legend": "A",
+ "keycode": "Key_A",
+ "unshifted_char": "a",
+ "shifted_char": "A"
+ },
+ {
+ "legend": "S",
+ "keycode": "Key_S",
+ "unshifted_char": "s",
+ "shifted_char": "S"
+ },
+ {
+ "legend": "D",
+ "keycode": "Key_D",
+ "unshifted_char": "d",
+ "shifted_char": "D"
+ },
+ {
+ "legend": "F",
+ "keycode": "Key_F",
+ "unshifted_char": "f",
+ "shifted_char": "F"
+ },
+ {
+ "legend": "G",
+ "keycode": "Key_G",
+ "unshifted_char": "g",
+ "shifted_char": "G"
+ },
+ {
+ "legend": "H",
+ "keycode": "Key_H",
+ "unshifted_char": "h",
+ "shifted_char": "H"
+ },
+ {
+ "legend": "J",
+ "keycode": "Key_J",
+ "unshifted_char": "j",
+ "shifted_char": "J"
+ },
+ {
+ "legend": "K",
+ "keycode": "Key_K",
+ "unshifted_char": "k",
+ "shifted_char": "K"
+ },
+ {
+ "legend": "L",
+ "keycode": "Key_L",
+ "unshifted_char": "l",
+ "shifted_char": "L"
+ },
+ {
+ "legend": ":\n;",
+ "unshifted_keycode": "Key_Semicolon",
+ "shifted_keycode": "Key_Colon",
+ "unshifted_char": ";",
+ "shifted_char": ":"
+ },
+ {
+ "legend": "\"\n'",
+ "unshifted_keycode": "Key_Apostrophe",
+ "shifted_keycode": "Key_QuoteDbl",
+ "unshifted_char": "'",
+ "shifted_char": "\""
+ },
+ {
+ "legend": "Return",
+ "keycode": "Key_Return",
+ "span": 1.5
+ }
+ ],
+ [
+ {
+ "legend": "Shift",
+ "keycode": "Key_Shift",
+ "span": 1.4
+ },
+ {
+ "legend": "Z",
+ "keycode": "Key_Z",
+ "unshifted_char": "z",
+ "shifted_char": "Z"
+ },
+ {
+ "legend": "X",
+ "keycode": "Key_X",
+ "unshifted_char": "x",
+ "shifted_char": "X"
+ },
+ {
+ "legend": "C",
+ "keycode": "Key_C",
+ "unshifted_char": "c",
+ "shifted_char": "C"
+ },
+ {
+ "legend": "V",
+ "keycode": "Key_V",
+ "unshifted_char": "v",
+ "shifted_char": "V"
+ },
+ {
+ "legend": "B",
+ "keycode": "Key_B",
+ "unshifted_char": "b",
+ "shifted_char": "B"
+ },
+ {
+ "legend": "N",
+ "keycode": "Key_N",
+ "unshifted_char": "n",
+ "shifted_char": "N"
+ },
+ {
+ "legend": "M",
+ "keycode": "Key_M",
+ "unshifted_char": "m",
+ "shifted_char": "M"
+ },
+ {
+ "legend": "<\n,",
+ "unshifted_keycode": "Key_Comma",
+ "shifted_keycode": "Key_Less",
+ "unshifted_char": ",",
+ "shifted_char": "<"
+ },
+ {
+ "legend": ">\n.",
+ "unshifted_keycode": "Key_Period",
+ "shifted_keycode": "Key_Greater",
+ "unshifted_char": ".",
+ "shifted_char": ">"
+ },
+ {
+ "legend": "?\n/",
+ "unshifted_keycode": "Key_Slash",
+ "shifted_keycode": "Key_Question",
+ "unshifted_char": "/",
+ "shifted_char": "?"
+ },
+ {
+ "legend": "Shift",
+ "keycode": "Key_Shift",
+ "span": 1.6
+ }
+ ],
+ [
+ {
+ "legend": "Ctrl",
+ "keycode": "Key_Control"
+ },
+ {
+ "legend": "⌘",
+ "keycode": "Key_Meta"
+ },
+ {
+ "legend": "Alt",
+ "keycode": "Key_Alt"
+ },
+ {
+ "legend": " ",
+ "keycode": "Key_Space",
+ "unshifted_char": " ",
+ "shifted_char": " ",
+ "span": 4.0
+ },
+ {
+ "legend": "Alt",
+ "keycode": "Key_Alt"
+ },
+ {
+ "legend": "⌘",
+ "keycode": "Key_Meta"
+ },
+ {
+ "legend": "≡",
+ "keycode": "Key_Menu"
+ },
+ {
+ "legend": "Ctrl",
+ "keycode": "Key_Control"
+ }
+ ]
+ ],
+ [ 0.003 ],
+ [
+ 0.15,
+ [
+ {
+ "legend": "🗗"
+ },
+ {
+ "legend": "📋"
+ },
+ {}
+ ],
+ { "verticalSpan": 0.003 },
+ [
+ {
+ "legend": "Ins",
+ "keycode": "Key_Insert"
+ },
+ {
+ "legend": "Home",
+ "keycode": "Key_Home"
+ },
+ {
+ "legend": "Page\nUp",
+ "keycode": "Key_PageUp"
+ }
+ ],
+ [
+ {
+ "legend": "Del",
+ "keycode": "Key_Delete"
+ },
+ {
+ "legend": "End",
+ "keycode": "Key_End"
+ },
+ {
+ "legend": "Page\nDown",
+ "keycode": "Key_PageDown"
+ }
+ ],
+ {},
+ [
+ {},
+ {
+ "legend": "↑",
+ "keycode": "Key_Up"
+ },
+ {}
+ ],
+ [
+ {
+ "legend": "â†",
+ "keycode": "Key_Left"
+ },
+ {
+ "legend": "↓",
+ "keycode": "Key_Down"
+ },
+ {
+ "legend": "→",
+ "keycode": "Key_Right"
+ }
+ ]
+ ]
+]
diff --git a/interface/resources/qml/overte/staging/keyboard/qmldir b/interface/resources/qml/overte/staging/keyboard/qmldir
new file mode 100644
index 00000000000..0cd7e02edd0
--- /dev/null
+++ b/interface/resources/qml/overte/staging/keyboard/qmldir
@@ -0,0 +1,3 @@
+module Keyboard
+Keyboard 1.0 Keyboard.qml
+KeyboardKey 1.0 KeyboardKey.qml
diff --git a/interface/resources/qml/overte/staging/login/LoginScreen.qml b/interface/resources/qml/overte/staging/login/LoginScreen.qml
new file mode 100644
index 00000000000..550126e7528
--- /dev/null
+++ b/interface/resources/qml/overte/staging/login/LoginScreen.qml
@@ -0,0 +1,52 @@
+import QtQuick
+import QtQuick.Layouts
+import QtQuick.Controls
+import QtQuick.Effects
+
+import "../" as Overte
+import "./pages" as Pages
+
+Rectangle {
+ anchors.fill: parent
+ implicitWidth: 480
+ implicitHeight: 720
+ id: root
+
+ color: Overte.Theme.paletteActive.base
+
+ Image {
+ visible: !Overte.Theme.highContrast
+ anchors.fill: parent
+ source: "./assets/background.png"
+
+ layer.enabled: true
+ layer.effect: MultiEffect {
+ blurEnabled: true
+ blurMax: 64
+ blur: 1.0
+ contrast: Overte.Theme.darkMode ? -0.7 : -0.8
+ brightness: Overte.Theme.darkMode ? -0.2 : 0.25
+ }
+ }
+
+ ColumnLayout {
+ anchors.fill: parent
+
+ Image {
+ Layout.fillWidth: true
+
+ id: logo
+ fillMode: Image.PreserveAspectFit
+
+ source: Overte.Theme.darkMode ? "./assets/logo_dark.png" : "./assets/logo_light.png"
+ }
+
+ Overte.StackView {
+ Layout.fillWidth: true
+ Layout.fillHeight: true
+
+ id: stack
+ initialItem: Pages.StartPage {}
+ }
+ }
+}
diff --git a/interface/resources/qml/overte/staging/login/assets/background.png b/interface/resources/qml/overte/staging/login/assets/background.png
new file mode 100644
index 00000000000..9b18c52b57a
Binary files /dev/null and b/interface/resources/qml/overte/staging/login/assets/background.png differ
diff --git a/interface/resources/qml/overte/staging/login/assets/logo_dark.png b/interface/resources/qml/overte/staging/login/assets/logo_dark.png
new file mode 100644
index 00000000000..bf3380a55d3
Binary files /dev/null and b/interface/resources/qml/overte/staging/login/assets/logo_dark.png differ
diff --git a/interface/resources/qml/overte/staging/login/assets/logo_light.png b/interface/resources/qml/overte/staging/login/assets/logo_light.png
new file mode 100644
index 00000000000..35285a0a0e3
Binary files /dev/null and b/interface/resources/qml/overte/staging/login/assets/logo_light.png differ
diff --git a/interface/resources/qml/overte/staging/login/pages/LoginPage.qml b/interface/resources/qml/overte/staging/login/pages/LoginPage.qml
new file mode 100644
index 00000000000..a22dc08c53a
--- /dev/null
+++ b/interface/resources/qml/overte/staging/login/pages/LoginPage.qml
@@ -0,0 +1,137 @@
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Layouts
+
+import "../../" as Overte
+
+Item {
+ // TODO: once the account server can accept
+ // usernames starting with numbers or underscores, modify this
+ readonly property var usernameRegex: /[a-zA-Z][0-9a-zA-Z_.-]{2,}/
+
+ // passwords must be at least 6 characters
+ readonly property var passwordRegex: /.{6,}/
+
+ ColumnLayout {
+ anchors.fill: parent
+ anchors.margins: 32
+ spacing: Overte.Theme.fontPixelSize * 2
+
+ Overte.Label {
+ Layout.fillWidth: true
+
+ text: qsTr("Log In")
+ horizontalAlignment: Text.AlignHCenter
+ verticalAlignment: Text.AlignVCenter
+ }
+
+ Overte.TextField {
+ Layout.fillWidth: true
+
+ id: usernameField
+ placeholderText: qsTr("Username")
+
+ validator: RegularExpressionValidator {
+ regularExpression: usernameRegex
+ }
+
+ Overte.ToolTip {
+ visible: parent.activeFocus && !parent.text.match(usernameRegex)
+ text: qsTr("Usernames must start with a letter and be at least 3 characters long.")
+ }
+ }
+
+ RowLayout {
+ Layout.fillWidth: true
+
+ Overte.TextField {
+ id: passwordField
+ Layout.fillWidth: true
+ placeholderText: qsTr("Password")
+ echoMode: showPassword.checked ? TextInput.Normal : TextInput.Password
+ passwordMaskDelay: 500
+
+ validator: RegularExpressionValidator {
+ regularExpression: passwordRegex
+ }
+
+ Overte.ToolTip {
+ visible: parent.activeFocus && !parent.text.match(usernameRegex)
+ text: qsTr("Passwords must be at least 6 characters.")
+ }
+ }
+
+ Overte.RoundButton {
+ id: showPassword
+ checkable: true
+ icon.source: checked ? "../../icons/eye_closed.svg" : "../../icons/eye_open.svg"
+ icon.color: Overte.Theme.paletteActive.buttonText
+ icon.width: 24
+ icon.height: 24
+ }
+ }
+
+ Overte.TextField {
+ Layout.fillWidth: true
+
+ id: accountServerField
+ placeholderText: qsTr("Account server (Optional)")
+ }
+
+ RowLayout {
+ spacing: 16
+ Layout.maximumHeight: Overte.Theme.fontPixelSize * 3
+
+ Overte.Button {
+ // make the buttons equal size
+ Layout.preferredWidth: 1
+ Layout.preferredHeight: 1
+ Layout.fillWidth: true
+ Layout.fillHeight: true
+
+ text: qsTr("Back", "Return to previous page")
+ icon.source: "../../icons/triangle_left.svg"
+ icon.width: 24
+ icon.height: 24
+ icon.color: Overte.Theme.paletteActive.buttonText
+
+ onClicked: {
+ stack.pop();
+ }
+ }
+
+ Overte.Button {
+ // make the buttons equal size
+ Layout.preferredWidth: 1
+ Layout.preferredHeight: 1
+ Layout.fillWidth: true
+ Layout.fillHeight: true
+
+ text: qsTr("Log in", "Log in button")
+ backgroundColor: Overte.Theme.paletteActive.buttonAdd
+
+ enabled: usernameField.text.match(usernameRegex) && passwordField.text.match(passwordRegex)
+
+ onClicked: {
+ let props = {
+ mode: "login",
+ username: usernameField.text,
+ // don't let stray newlines through
+ // or stuff can break
+ password: (
+ passwordField.text
+ .replace("\n", "")
+ .replace("\r", "")
+ ),
+ };
+
+ if (accountServerField.text !== "") {
+ props.accountServer = accountServerField.text;
+ }
+
+ stack.push("./ProgressPage.qml", props);
+ }
+ }
+ }
+ }
+}
diff --git a/interface/resources/qml/overte/staging/login/pages/ProgressPage.qml b/interface/resources/qml/overte/staging/login/pages/ProgressPage.qml
new file mode 100644
index 00000000000..2dfd8e4e5fd
--- /dev/null
+++ b/interface/resources/qml/overte/staging/login/pages/ProgressPage.qml
@@ -0,0 +1,83 @@
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Layouts
+
+import "../../" as Overte
+
+Item {
+ required property string mode
+ required property string username
+ required property string password
+ property url accountServer: "https://mv.overte.org/server"
+
+ function loginError(desc) {
+ let topLine = (
+ mode === "register" ?
+ qsTr("Failed to register!") :
+ qsTr("Failed to log in!")
+ );
+ statusText.text = `${topLine}\n\n${qsTr(desc)}`;
+ backButton.visible = true;
+ }
+
+ // DEBUG
+ Timer {
+ interval: 1000
+ running: true
+ onTriggered: {
+ loginError("Debug mode! Not connected to anything.");
+ }
+ }
+
+ ColumnLayout {
+ anchors.fill: parent
+ anchors.margins: 32
+ spacing: Overte.Theme.fontPixelSize * 2
+
+ Overte.Label {
+ Layout.fillWidth: true
+ Layout.fillHeight: true
+
+ id: statusText
+ text: mode === "register" ? qsTr("Registering…") : qsTr("Logging in…")
+ horizontalAlignment: Text.AlignHCenter
+ verticalAlignment: Text.AlignVCenter
+ }
+
+ RowLayout {
+ spacing: 16
+ Layout.minimumHeight: Overte.Theme.fontPixelSize * 3
+ Layout.maximumHeight: Overte.Theme.fontPixelSize * 3
+
+ Overte.Button {
+ id: backButton
+ visible: false
+
+ // make the buttons equal size
+ Layout.preferredWidth: 1
+ Layout.preferredHeight: 1
+ Layout.fillWidth: true
+ Layout.fillHeight: true
+
+ text: qsTr("Back", "Return to previous page")
+ icon.source: "../../icons/triangle_left.svg"
+ icon.width: 24
+ icon.height: 24
+ icon.color: Overte.Theme.paletteActive.buttonText
+
+ onClicked: {
+ stack.pop();
+ }
+ }
+
+ Item {
+ // make the buttons equal size
+ Layout.preferredWidth: 1
+ Layout.preferredHeight: 1
+ Layout.fillWidth: true
+ Layout.fillHeight: true
+ }
+ }
+ }
+}
+
diff --git a/interface/resources/qml/overte/staging/login/pages/RegisterPage.qml b/interface/resources/qml/overte/staging/login/pages/RegisterPage.qml
new file mode 100644
index 00000000000..250aa3d9c08
--- /dev/null
+++ b/interface/resources/qml/overte/staging/login/pages/RegisterPage.qml
@@ -0,0 +1,137 @@
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Layouts
+
+import "../../" as Overte
+
+Item {
+ // TODO: once the account server can accept
+ // usernames starting with numbers or underscores, modify this
+ readonly property var usernameRegex: /[a-zA-Z][0-9a-zA-Z_.-]{2,}/
+
+ // passwords must be at least 6 characters
+ readonly property var passwordRegex: /.{6,}/
+
+ ColumnLayout {
+ anchors.fill: parent
+ anchors.margins: 32
+ spacing: Overte.Theme.fontPixelSize * 2
+
+ Overte.Label {
+ Layout.fillWidth: true
+
+ text: qsTr("Register")
+ horizontalAlignment: Text.AlignHCenter
+ verticalAlignment: Text.AlignVCenter
+ }
+
+ Overte.TextField {
+ Layout.fillWidth: true
+
+ id: usernameField
+ placeholderText: qsTr("Username")
+
+ validator: RegularExpressionValidator {
+ regularExpression: usernameRegex
+ }
+
+ Overte.ToolTip {
+ visible: parent.activeFocus && !parent.text.match(usernameRegex)
+ text: qsTr("Usernames must start with a letter and be at least 3 characters long.")
+ }
+ }
+
+ RowLayout {
+ Layout.fillWidth: true
+
+ Overte.TextField {
+ id: passwordField
+ Layout.fillWidth: true
+ placeholderText: qsTr("Password")
+ echoMode: showPassword.checked ? TextInput.Normal : TextInput.Password
+ passwordMaskDelay: 500
+
+ validator: RegularExpressionValidator {
+ regularExpression: passwordRegex
+ }
+
+ Overte.ToolTip {
+ visible: parent.activeFocus && !parent.text.match(usernameRegex)
+ text: qsTr("Passwords must be at least 6 characters.")
+ }
+ }
+
+ Overte.RoundButton {
+ id: showPassword
+ checkable: true
+ icon.source: checked ? "../../icons/eye_closed.svg" : "../../icons/eye_open.svg"
+ icon.color: Overte.Theme.paletteActive.buttonText
+ icon.width: 24
+ icon.height: 24
+ }
+ }
+
+ Overte.TextField {
+ Layout.fillWidth: true
+
+ id: accountServerField
+ placeholderText: qsTr("Account server (Optional)")
+ }
+
+ RowLayout {
+ spacing: 16
+ Layout.maximumHeight: Overte.Theme.fontPixelSize * 3
+
+ Overte.Button {
+ // make the buttons equal size
+ Layout.preferredWidth: 1
+ Layout.preferredHeight: 1
+ Layout.fillWidth: true
+ Layout.fillHeight: true
+
+ text: qsTr("Back", "Return to previous page")
+ icon.source: "../../icons/triangle_left.svg"
+ icon.width: 24
+ icon.height: 24
+ icon.color: Overte.Theme.paletteActive.buttonText
+
+ onClicked: {
+ stack.pop();
+ }
+ }
+
+ Overte.Button {
+ // make the buttons equal size
+ Layout.preferredWidth: 1
+ Layout.preferredHeight: 1
+ Layout.fillWidth: true
+ Layout.fillHeight: true
+
+ text: qsTr("Register", "Register button")
+ backgroundColor: Overte.Theme.paletteActive.buttonAdd
+
+ enabled: usernameField.text.match(usernameRegex) && passwordField.text.match(passwordRegex)
+
+ onClicked: {
+ let props = {
+ mode: "register",
+ username: usernameField.text,
+ // don't let stray newlines through
+ // or stuff can break
+ password: (
+ passwordField.text
+ .replace("\n", "")
+ .replace("\r", "")
+ ),
+ };
+
+ if (accountServerField.text !== "") {
+ props.accountServer = accountServerField.text;
+ }
+
+ stack.push("./ProgressPage.qml", props);
+ }
+ }
+ }
+ }
+}
diff --git a/interface/resources/qml/overte/staging/login/pages/StartPage.qml b/interface/resources/qml/overte/staging/login/pages/StartPage.qml
new file mode 100644
index 00000000000..9ab8c4b8624
--- /dev/null
+++ b/interface/resources/qml/overte/staging/login/pages/StartPage.qml
@@ -0,0 +1,69 @@
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Layouts
+
+import "../../" as Overte
+
+Item {
+ readonly property url codeOfConduct: "https://overte.org/code_of_conduct.html"
+
+ ColumnLayout {
+ anchors.left: parent.left
+ anchors.right: parent.right
+ anchors.verticalCenter: parent.verticalCenter
+ anchors.margins: 32
+ spacing: Overte.Theme.fontPixelSize * 2
+
+ Overte.Label {
+ Layout.fillWidth: true
+
+ text: qsTr("Welcome to Overte!\n\nAn account gives you a contacts list and lets you publish to the public worlds list.\n\nAccounts are entirely optional.")
+ horizontalAlignment: Text.AlignHCenter
+ verticalAlignment: Text.AlignVCenter
+ wrapMode: Text.Wrap
+ }
+
+ RowLayout {
+ Layout.fillWidth: true
+ Layout.alignment: Qt.AlignHCenter
+ spacing: 16
+
+ Overte.Button {
+ Layout.alignment: Qt.AlignCenter
+ Layout.fillWidth: true
+
+ // force equally sized buttons
+ Layout.preferredWidth: 1
+
+ text: qsTr("Register")
+
+ onClicked: {
+ stack.push("./RegisterPage.qml");
+ }
+ }
+
+ Overte.Button {
+ Layout.alignment: Qt.AlignCenter
+ Layout.fillWidth: true
+
+ // force equally sized buttons
+ Layout.preferredWidth: 1
+
+ text: qsTr("Log in")
+
+ onClicked: {
+ stack.push("./LoginPage.qml");
+ }
+ }
+ }
+
+ Overte.Button {
+ Layout.alignment: Qt.AlignCenter
+ Layout.fillWidth: true
+
+ text: qsTr("Code of Conduct")
+
+ onClicked: () => Qt.openUrlExternally(codeOfConduct)
+ }
+ }
+}
diff --git a/interface/resources/qml/overte/staging/login/pages/qmldir b/interface/resources/qml/overte/staging/login/pages/qmldir
new file mode 100644
index 00000000000..816c3d70cec
--- /dev/null
+++ b/interface/resources/qml/overte/staging/login/pages/qmldir
@@ -0,0 +1,4 @@
+module LoginPages
+StartPage 1.0 StartPage.qml
+LoginPage 1.0 LoginPage.qml
+RegisterPage 1.0 RegisterPage.qml
diff --git a/interface/resources/qml/overte/staging/login/qmldir b/interface/resources/qml/overte/staging/login/qmldir
new file mode 100644
index 00000000000..02fe9379c9c
--- /dev/null
+++ b/interface/resources/qml/overte/staging/login/qmldir
@@ -0,0 +1,2 @@
+module Login
+LoginScreen 1.0 LoginScreen.qml
diff --git a/interface/resources/qml/overte/staging/tutorial/AvatarPicker.qml b/interface/resources/qml/overte/staging/tutorial/AvatarPicker.qml
new file mode 100644
index 00000000000..0702431e715
--- /dev/null
+++ b/interface/resources/qml/overte/staging/tutorial/AvatarPicker.qml
@@ -0,0 +1,6 @@
+import "../avatar_picker"
+
+AvatarPicker {
+ editable: false
+ availableTags: ["Human", "Furry", "Anime", "Robot", "Other"]
+}
diff --git a/interface/resources/qml/overte/staging/tutorial/ControlsGuide.qml b/interface/resources/qml/overte/staging/tutorial/ControlsGuide.qml
new file mode 100644
index 00000000000..02d16d4fd5d
--- /dev/null
+++ b/interface/resources/qml/overte/staging/tutorial/ControlsGuide.qml
@@ -0,0 +1,72 @@
+import QtQuick
+import QtQuick.Layouts
+
+import "../" as Overte
+
+Rectangle {
+ id: root
+ implicitWidth: 720
+ implicitHeight: 480
+ color: Overte.Theme.paletteActive.base
+
+ Overte.TabBar {
+ id: tabBar
+ anchors.top: root.top
+ anchors.left: root.left
+ anchors.right: root.right
+
+ Overte.TabButton { width: implicitWidth; text: qsTr("Keyboard") }
+ Overte.TabButton { width: implicitWidth; text: qsTr("Oculus Touch") }
+ Overte.TabButton { width: implicitWidth; text: qsTr("Mixed Reality") }
+ Overte.TabButton { width: implicitWidth; text: qsTr("Index") }
+ Overte.TabButton { width: implicitWidth; text: qsTr("Vive") }
+ }
+
+ StackLayout {
+ anchors.top: tabBar.bottom
+ anchors.left: root.left
+ anchors.right: root.right
+ anchors.bottom: root.bottom
+ currentIndex: tabBar.currentIndex
+
+ // Keyboard
+ Image {
+ Layout.fillWidth: true
+ Layout.fillHeight: true
+ fillMode: Image.PreserveAspectFit
+ source: Overte.Theme.darkMode ? "./assets/controls_dark.png" : "./assets/controls_light.png"
+ }
+
+ // Oculus Touch
+ Image {
+ Layout.fillWidth: true
+ Layout.fillHeight: true
+ fillMode: Image.PreserveAspectFit
+ source: Overte.Theme.darkMode ? "./assets/controls_dark.png" : "./assets/controls_light.png"
+ }
+
+ // Mixed Reality
+ Image {
+ Layout.fillWidth: true
+ Layout.fillHeight: true
+ fillMode: Image.PreserveAspectFit
+ source: Overte.Theme.darkMode ? "./assets/controls_dark.png" : "./assets/controls_light.png"
+ }
+
+ // Index
+ Image {
+ Layout.fillWidth: true
+ Layout.fillHeight: true
+ fillMode: Image.PreserveAspectFit
+ source: Overte.Theme.darkMode ? "./assets/controls_dark.png" : "./assets/controls_light.png"
+ }
+
+ // Vive
+ Image {
+ Layout.fillWidth: true
+ Layout.fillHeight: true
+ fillMode: Image.PreserveAspectFit
+ source: Overte.Theme.darkMode ? "./assets/controls_dark.png" : "./assets/controls_light.png"
+ }
+ }
+}
diff --git a/interface/resources/qml/overte/staging/tutorial/Welcome.qml b/interface/resources/qml/overte/staging/tutorial/Welcome.qml
new file mode 100644
index 00000000000..610cd8e14b0
--- /dev/null
+++ b/interface/resources/qml/overte/staging/tutorial/Welcome.qml
@@ -0,0 +1,46 @@
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Layouts
+
+import "../" as Overte
+
+Rectangle {
+ id: root
+ implicitWidth: 480
+ implicitHeight: 360
+ color: Overte.Theme.paletteActive.base
+
+ ScrollView {
+ anchors.fill: parent
+
+ ScrollBar.vertical: Overte.ScrollBar {
+ policy: ScrollBar.AsNeeded
+ anchors.top: parent.top
+ anchors.bottom: parent.bottom
+ anchors.right: parent.right
+ }
+
+ contentWidth: width - ScrollBar.vertical.width
+
+ Column {
+ spacing: 16
+ padding: 8
+
+ Image {
+ width: root.width
+ height: sourceSize.height
+ fillMode: Image.PreserveAspectFit
+ source: Overte.Theme.darkMode ? "./assets/logo_dark.png" : "./assets/logo_light.png"
+ }
+
+ Overte.Label {
+ anchors.left: parent.left
+ anchors.right: parent.right
+ anchors.leftMargin: root.width / 8
+ anchors.rightMargin: root.width / 8
+ text: qsTr("Welcome to Overte!\nThis is an offline tutorial world.\n\nTODO: More intro text")
+ wrapMode: Text.Wrap
+ }
+ }
+ }
+}
diff --git a/interface/resources/qml/overte/staging/tutorial/assets/controls_dark.png b/interface/resources/qml/overte/staging/tutorial/assets/controls_dark.png
new file mode 100644
index 00000000000..3914ae96af6
Binary files /dev/null and b/interface/resources/qml/overte/staging/tutorial/assets/controls_dark.png differ
diff --git a/interface/resources/qml/overte/staging/tutorial/assets/controls_light.png b/interface/resources/qml/overte/staging/tutorial/assets/controls_light.png
new file mode 100644
index 00000000000..52abbe450d8
Binary files /dev/null and b/interface/resources/qml/overte/staging/tutorial/assets/controls_light.png differ
diff --git a/interface/resources/qml/overte/staging/tutorial/assets/logo_dark.png b/interface/resources/qml/overte/staging/tutorial/assets/logo_dark.png
new file mode 100644
index 00000000000..bf3380a55d3
Binary files /dev/null and b/interface/resources/qml/overte/staging/tutorial/assets/logo_dark.png differ
diff --git a/interface/resources/qml/overte/staging/tutorial/assets/logo_light.png b/interface/resources/qml/overte/staging/tutorial/assets/logo_light.png
new file mode 100644
index 00000000000..35285a0a0e3
Binary files /dev/null and b/interface/resources/qml/overte/staging/tutorial/assets/logo_light.png differ
diff --git a/interface/resources/qml/overte/staging/tutorial/qmldir b/interface/resources/qml/overte/staging/tutorial/qmldir
new file mode 100644
index 00000000000..ed867fa3f36
--- /dev/null
+++ b/interface/resources/qml/overte/staging/tutorial/qmldir
@@ -0,0 +1,4 @@
+module Tutorial
+Welcome 1.0 Welcome.qml
+ControlsGuide 1.0 ControlsGuide.qml
+AvatarPicker 1.0 AvatarPicker.qml
diff --git a/interface/resources/qml/windows/Decoration.qml b/interface/resources/qml/windows/Decoration.qml
index 1ba7ad125d6..1d2d6320ec8 100644
--- a/interface/resources/qml/windows/Decoration.qml
+++ b/interface/resources/qml/windows/Decoration.qml
@@ -9,22 +9,24 @@
//
import QtQuick 2.5
-import QtGraphicalEffects 1.0
import "."
import stylesUit 1.0
+// TODO: look into whether we should outright replace the Hifi desktop system
+import "../overte" as Overte
+
Rectangle {
HifiConstants { id: hifi }
signal inflateDecorations();
signal deflateDecorations();
- property int frameMargin: 9
+ property int frameMargin: 4
property int frameMarginLeft: frameMargin
property int frameMarginRight: frameMargin
- property int frameMarginTop: 2 * frameMargin + iconSize
- property int frameMarginBottom: iconSize + 11
+ property int frameMarginTop: (2 * frameMargin) + iconSize
+ property int frameMarginBottom: (2 * frameMargin) + (window.resizable || DebugQML ? 18 : 0)
anchors {
topMargin: -frameMarginTop
@@ -33,12 +35,18 @@ Rectangle {
bottomMargin: -frameMarginBottom
}
anchors.fill: parent
- color: hifi.colors.baseGrayHighlight40
- border {
- width: hifi.dimensions.borderWidth
- color: hifi.colors.faintGray50
+ color: Overte.Theme.paletteActive.base
+ border.width: Overte.Theme.borderWidth
+ border.color: {
+ if (Overte.Theme.highContrast) {
+ return Overte.Theme.paletteActive.text;
+ } else if (Overte.Theme.darkMode) {
+ return Qt.lighter(Overte.Theme.paletteActive.base);
+ } else {
+ return Qt.darker(Overte.Theme.paletteActive.base);
+ }
}
- radius: hifi.dimensions.borderRadius
+ radius: Overte.Theme.borderRadius
// Enable dragging of the window,
// detect mouseover of the window (including decoration)
@@ -90,4 +98,3 @@ Rectangle {
}
}
}
-
diff --git a/interface/resources/qml/windows/DefaultFrameDecoration.qml b/interface/resources/qml/windows/DefaultFrameDecoration.qml
index fb0dd55985c..67a2033e494 100644
--- a/interface/resources/qml/windows/DefaultFrameDecoration.qml
+++ b/interface/resources/qml/windows/DefaultFrameDecoration.qml
@@ -8,52 +8,74 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
-import QtQuick 2.5
-import QtGraphicalEffects 1.0
+import QtQuick
+import QtQuick.Layouts
import "."
import stylesUit 1.0
+// TODO: look into whether we should outright replace the Hifi desktop system
+import "../overte" as Overte
+
Decoration {
HifiConstants { id: hifi }
// Dialog frame
id: root
- property int iconSize: hifi.dimensions.frameIconSize
- frameMargin: 9
+ property int iconSize: 24
+ frameMargin: 4
frameMarginLeft: frameMargin
frameMarginRight: frameMargin
- frameMarginTop: 2 * frameMargin + iconSize
- frameMarginBottom: iconSize + 11
+ frameMarginTop: (2 * frameMargin) + iconSize
+ frameMarginBottom: (2 * frameMargin) + (window.resizable || DebugQML ? 18 : 0)
onInflateDecorations: {
if (!HMD.active) {
return;
}
- root.frameMargin = 18
- titleText.size = hifi.fontSizes.overlayTitle * 2
- root.iconSize = hifi.dimensions.frameIconSize * 2
+ root.frameMargin = 18;
+ root.iconSize = 32;
}
onDeflateDecorations: {
- root.frameMargin = 9
- titleText.size = hifi.fontSizes.overlayTitle
- root.iconSize = hifi.dimensions.frameIconSize
+ root.frameMargin = 4;
+ root.iconSize = 24;
}
+ Rectangle {
+ anchors.fill: controlsRow
+ color: Overte.Theme.paletteActive.activeWindowTitleBg
+ topLeftRadius: Overte.Theme.borderRadius
+ topRightRadius: Overte.Theme.borderRadius
+ }
- Row {
+ RowLayout {
id: controlsRow
anchors {
- right: parent.right;
- top: parent.top;
- topMargin: root.frameMargin + 1 // Move down a little to visually align with the title
- rightMargin: root.frameMarginRight;
+ top: parent.top
+ left: parent.left
+ right: parent.right
+ margins: Overte.Theme.borderWidth
+ }
+ spacing: 2
+ height: root.frameMarginTop - root.frameMargin
+
+ Overte.Label {
+ Layout.fillWidth: true
+ Layout.fillHeight: true
+ Layout.leftMargin: 4
+
+ // Title
+ id: titleText
+ text: window ? window.title : ""
+ verticalAlignment: Text.AlignVCenter
+ color: Overte.Theme.paletteActive.activeWindowTitleFg
}
- spacing: root.iconSize / 4
HiFiGlyphs {
+ Layout.alignment: Qt.AlignCenter
+
// "Pin" button
visible: window.pinnable
text: window.pinned ? hifi.glyphs.pinInverted : hifi.glyphs.pin
@@ -68,49 +90,28 @@ Decoration {
}
}
- HiFiGlyphs {
- // "Close" button
+ Overte.RoundButton {
+ Layout.alignment: Qt.AlignCenter
+ Layout.rightMargin: 4
+
visible: window ? window.closable : false
- text: closeClickArea.containsPress ? hifi.glyphs.closeInverted : hifi.glyphs.close
- color: closeClickArea.containsMouse ? hifi.colors.redHighlight : hifi.colors.white
- size: root.iconSize
- MouseArea {
- id: closeClickArea
- anchors.fill: parent
- hoverEnabled: true
- onClicked: {
- window.shown = false;
- window.windowClosed();
- }
- }
- }
- }
+ icon.source: "../overte/icons/close.svg"
+ icon.width: root.iconSize == 24 ? 12 : 24
+ icon.height: root.iconSize == 24 ? 12 : 24
+ icon.color: Overte.Theme.paletteActive.buttonText
+ backgroundColor: (
+ hovered ?
+ Overte.Theme.paletteActive.buttonDestructive :
+ Overte.Theme.paletteActive.button
+ )
- RalewayRegular {
- // Title
- id: titleText
- anchors {
- left: parent.left
- leftMargin: root.frameMarginLeft + hifi.dimensions.contentMargin.x
- right: controlsRow.left
- rightMargin: root.iconSize
- top: parent.top
- topMargin: root.frameMargin
- }
- text: window ? window.title : ""
- color: hifi.colors.white
- size: hifi.fontSizes.overlayTitle
- }
+ implicitWidth: root.iconSize
+ implicitHeight: root.iconSize
- DropShadow {
- source: titleText
- anchors.fill: titleText
- horizontalOffset: 2
- verticalOffset: 2
- samples: 2
- color: hifi.colors.baseGrayShadow60
- visible: (desktop.gradientsSupported && window && window.focus)
- cached: true
+ onClicked: {
+ window.shown = false;
+ window.windowClosed();
+ }
+ }
}
}
-
diff --git a/interface/resources/qml/windows/Frame.qml b/interface/resources/qml/windows/Frame.qml
index cfff40bbf62..58c31540166 100644
--- a/interface/resources/qml/windows/Frame.qml
+++ b/interface/resources/qml/windows/Frame.qml
@@ -9,11 +9,14 @@
//
import QtQuick 2.5
-import QtGraphicalEffects 1.0
+import Qt5Compat.GraphicalEffects
import stylesUit 1.0
import "../js/Utils.js" as Utils
+// TODO: look into whether we should outright replace the Hifi desktop system
+import "../overte" as Overte
+
Item {
id: frame
objectName: "Frame"
@@ -21,7 +24,6 @@ Item {
default property var decoration
property string qmlFile: "N/A"
- property bool gradientsSupported: desktop.gradientsSupported
readonly property int frameMarginLeft: frame.decoration ? frame.decoration.frameMarginLeft : 0
readonly property int frameMarginRight: frame.decoration ? frame.decoration.frameMarginRight : 0
@@ -33,21 +35,12 @@ Item {
anchors.fill: parent
children: [
- focusShadow,
decoration,
sizeOutline,
debugZ,
sizeDrag
]
- Text {
- id: debugZ
- visible: (typeof DebugQML !== 'undefined') ? DebugQML : false
- color: "red"
- text: (window ? "Z: " + window.z : "") + " " + qmlFile
- y: window ? window.height + 4 : 0
- }
-
function deltaSize(dx, dy) {
var newSize = Qt.vector2d(window.width + dx, window.height + dy);
newSize = Utils.clampVector(newSize, window.minSize, window.maxSize);
@@ -55,41 +48,32 @@ Item {
window.height = newSize.y
}
- RadialGradient {
- id: focusShadow
- width: 1.66 * window.width
- height: 1.66 * window.height
- x: (window.width - width) / 2
- y: window.height / 2 - 0.375 * height
- visible: gradientsSupported && window && window.focus && window.content.visible
- gradient: Gradient {
- // GradientStop position 0.5 is at full circumference of circle that fits inside the square.
- GradientStop { position: 0.0; color: "#ff000000" } // black, 100% opacity
- GradientStop { position: 0.333; color: "#1f000000" } // black, 12% opacity
- GradientStop { position: 0.5; color: "#00000000" } // black, 0% opacity
- GradientStop { position: 1.0; color: "#00000000" }
- }
- cached: true
- }
-
Rectangle {
id: sizeOutline
x: -frameMarginLeft
y: -frameMarginTop
width: window ? window.width + frameMarginLeft + frameMarginRight + 2 : 0
height: window ? window.height + frameMarginTop + frameMarginBottom + 2 : 0
- color: hifi.colors.baseGrayHighlight15
- border.width: 3
- border.color: hifi.colors.white50
- radius: hifi.dimensions.borderRadius
+ color: Overte.Theme.paletteActive.base
+ border.width: Overte.Theme.borderWidth
+ border.color: {
+ if (Overte.Theme.highContrast) {
+ return Overte.Theme.paletteActive.text;
+ } else if (Overte.Theme.darkMode) {
+ return Qt.lighter(Overte.Theme.paletteActive.base);
+ } else {
+ return Qt.darker(Overte.Theme.paletteActive.base);
+ }
+ }
+ radius: Overte.Theme.borderRadius
visible: window ? !window.content.visible : false
}
MouseArea {
// Resize handle
id: sizeDrag
- width: hifi.dimensions.frameIconSize
- height: hifi.dimensions.frameIconSize
+ width: 18
+ height: 18
enabled: window ? window.resizable : false
hoverEnabled: true
x: window ? window.width + frameMarginRight - hifi.dimensions.frameIconSize : 0
@@ -119,15 +103,30 @@ Item {
frame.deltaSize(delta.x, delta.y)
}
}
+
+ // TODO: use an SVG icon instead of HifiGlyphs
HiFiGlyphs {
+ x: -9
+ y: -9
visible: sizeDrag.enabled
- x: -11 // Move a little to visually align
- y: window.modality == Qt.ApplicationModal ? -6 : -4
text: hifi.glyphs.resizeHandle
- size: hifi.dimensions.frameIconSize + 10
- color: sizeDrag.containsMouse || sizeDrag.pressed
- ? hifi.colors.white
- : (window.colorScheme == hifi.colorSchemes.dark ? hifi.colors.white50 : hifi.colors.lightGrayText80)
+ size: 32
+ color: {
+ if (Overte.Theme.highContrast) {
+ return Overte.Theme.paletteActive.buttonText;
+ } else {
+ return Qt.darker(Overte.Theme.paletteActive.base, Overte.Theme.borderDarker);
+ }
+ }
}
}
+
+ Text {
+ id: debugZ
+ visible: (typeof DebugQML !== 'undefined') ? DebugQML : false
+ color: "red"
+ text: (window ? "Z: " + window.z : "") + " " + qmlFile
+ y: window ? window.height + 4 : 0
+ font.pixelSize: 12
+ }
}
diff --git a/interface/resources/qml/windows/ScrollingWindow.qml b/interface/resources/qml/windows/ScrollingWindow.qml
index 0c91b58e315..51a62c5d075 100644
--- a/interface/resources/qml/windows/ScrollingWindow.qml
+++ b/interface/resources/qml/windows/ScrollingWindow.qml
@@ -11,12 +11,14 @@
import QtQuick 2.5
import QtQuick.Controls 2.2
-import QtGraphicalEffects 1.0
+import Qt5Compat.GraphicalEffects
import "." as Windows
import stylesUit 1.0
import controlsUit 1.0 as HifiControlsUit
+import "../overte" as Overte
+
// FIXME how do I set the initial position of a window without
// overriding places where the a individual client of the window
// might be setting the position with a Settings{} element?
@@ -55,25 +57,10 @@ Windows.Window {
id: contentBackground
anchors.fill: parent
//anchors.rightMargin: parent.isScrolling ? verticalScrollWidth + 1 : 0
- color: hifi.colors.baseGray
+ color: Overte.Theme.paletteActive.base
visible: !window.hideBackground && modality != Qt.ApplicationModal
}
- LinearGradient {
- visible: !window.hideBackground && gradientsSupported && modality != Qt.ApplicationModal
- anchors.top: contentBackground.bottom
- anchors.left: contentBackground.left
- width: contentBackground.width - 1
- height: 4
- start: Qt.point(0, 0)
- end: Qt.point(0, 4)
- gradient: Gradient {
- GradientStop { position: 0.0; color: hifi.colors.darkGray }
- GradientStop { position: 1.0; color: hifi.colors.darkGray0 }
- }
- cached: true
- }
-
Flickable {
id: scrollView
contentItem.children: [ content ]
@@ -176,6 +163,7 @@ Windows.Window {
children: [ footer ]
}
+ // TODO: remove the old 2D hifi keyboard
HifiControlsUit.Keyboard {
id: keyboard
enabled: !keyboardOverride
diff --git a/interface/resources/qml/windows/ToolFrame.qml b/interface/resources/qml/windows/ToolFrame.qml
index bb2bada498e..8ea5bd669ba 100644
--- a/interface/resources/qml/windows/ToolFrame.qml
+++ b/interface/resources/qml/windows/ToolFrame.qml
@@ -9,7 +9,7 @@
//
import QtQuick 2.5
-import QtGraphicalEffects 1.0
+import Qt5Compat.GraphicalEffects
import "."
import stylesUit 1.0
diff --git a/interface/resources/qml/windows/ToolFrameDecoration.qml b/interface/resources/qml/windows/ToolFrameDecoration.qml
index 4f149037b38..2e7044123e9 100644
--- a/interface/resources/qml/windows/ToolFrameDecoration.qml
+++ b/interface/resources/qml/windows/ToolFrameDecoration.qml
@@ -9,7 +9,7 @@
//
import QtQuick 2.5
-import QtGraphicalEffects 1.0
+import Qt5Compat.GraphicalEffects
import "."
import stylesUit 1.0
diff --git a/interface/resources/qml/windows/Window.qml b/interface/resources/qml/windows/Window.qml
index 77cb35ceba4..64b26f28ab7 100644
--- a/interface/resources/qml/windows/Window.qml
+++ b/interface/resources/qml/windows/Window.qml
@@ -9,7 +9,7 @@
//
import QtQuick 2.5
-import QtGraphicalEffects 1.0
+import Qt5Compat.GraphicalEffects
import "."
import stylesUit 1.0
@@ -67,7 +67,9 @@ Fadable {
property bool destroyOnCloseButton: true
// Should hiding the window destroy it or just hide it?
property bool destroyOnHidden: false
- property bool pinnable: true
+ // TODO: Remove the pinnable property entirely
+ // Nobody knows what the pin is supposed to do, disable it by default
+ property bool pinnable: false
property bool pinned: false
property bool resizable: false
property bool gradientsSupported: desktop.gradientsSupported
@@ -127,7 +129,7 @@ Fadable {
propagateComposedEvents: true
acceptedButtons: Qt.AllButtons
enabled: window.visible
- onPressed: {
+ onPressed: mouse => {
window.raise();
mouse.accepted = false;
}
@@ -142,7 +144,7 @@ Fadable {
propagateComposedEvents: true
acceptedButtons: Qt.AllButtons
enabled: window.visible
- onPressed: {
+ onPressed: mouse => {
frame.forceActiveFocus();
mouse.accepted = false;
}
@@ -291,7 +293,7 @@ Fadable {
window.height - frame.decoration.anchors.topMargin - frame.decoration.anchors.bottomMargin)
}
- Keys.onPressed: {
+ Keys.onPressed: event => {
switch(event.key) {
case Qt.Key_Control:
case Qt.Key_Shift:
diff --git a/interface/resources/serverless/Scripts/Wizard.qml b/interface/resources/serverless/Scripts/Wizard.qml
index f258de9247b..2799bcc5df1 100644
--- a/interface/resources/serverless/Scripts/Wizard.qml
+++ b/interface/resources/serverless/Scripts/Wizard.qml
@@ -49,7 +49,7 @@ Rectangle {
loader.sourceComponent = step5;
break;
default:
- loader.setSource(undefined);
+ loader.setSource("");
}
}
diff --git a/interface/src/AndroidHelper.cpp b/interface/src/AndroidHelper.cpp
index e5007d706e8..f242273479a 100644
--- a/interface/src/AndroidHelper.cpp
+++ b/interface/src/AndroidHelper.cpp
@@ -23,7 +23,7 @@
#define qApp (static_cast(QCoreApplication::instance()))
AndroidHelper::AndroidHelper() {
- qRegisterMetaType("QAudio::Mode");
+ qRegisterMetaType("QAudioDevice::Mode");
}
AndroidHelper::~AndroidHelper() {
diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp
index 0c656d209a9..a50506379f9 100644
--- a/interface/src/Application.cpp
+++ b/interface/src/Application.cpp
@@ -14,7 +14,6 @@
#include "Application.h"
-#include
#include
#include
#include
@@ -216,7 +215,9 @@ Application::Application(
) :
QApplication(argc, argv),
#ifdef USE_GL
- _window(new MainWindow(desktop())),
+ // QT6TODO: is this correct?
+ _window(new MainWindow()),
+ //_window(new MainWindow(desktop())),
#else
_vkWindow(new VKWindow()),
_vkWindowWrapper(QWidget::createWindowContainer(_vkWindow)),
@@ -235,6 +236,15 @@ Application::Application(
_previousScriptLocation("LastScriptLocation", DESKTOP_LOCATION),
_previousPreferredDisplayMode("previousPreferredDisplayMode", 0),
// UI
+ _virtualPointingDevice(std::make_shared(
+ "OffscreenUiPointingDevice",
+ 0,
+ QInputDevice::DeviceType::AllDevices,
+ QPointingDevice::PointerType::AllPointerTypes,
+ QInputDevice::Capability::All,
+ 4, // maxPoints
+ 2 // buttonCount
+ )),
_hmdTabletScale("hmdTabletScale", DEFAULT_HMD_TABLET_SCALE_PERCENT),
_desktopTabletScale("desktopTabletScale", DEFAULT_DESKTOP_TABLET_SCALE_PERCENT),
_desktopTabletBecomesToolbarSetting("desktopTabletBecomesToolbar", DEFAULT_DESKTOP_TABLET_BECOMES_TOOLBAR),
@@ -245,8 +255,8 @@ Application::Application(
_constrainToolbarPosition("toolbar/constrainToolbarToCenterX", true),
_awayStateWhenFocusLostInVREnabled("awayStateWhenFocusLostInVREnabled", DEFAULT_AWAY_STATE_WHEN_FOCUS_LOST_IN_VR_ENABLED),
_preferredCursor("preferredCursor", DEFAULT_CURSOR_NAME),
- _darkTheme("darkTheme", true),
_miniTabletEnabledSetting("miniTabletEnabled", DEFAULT_MINI_TABLET_ENABLED),
+ _themePrefs(new ThemePrefs(this)),
// Entities
_maxOctreePacketsPerSecond("maxOctreePPS", DEFAULT_MAX_OCTREE_PPS),
_maxOctreePPS(_maxOctreePacketsPerSecond.get()),
@@ -415,7 +425,7 @@ QString Application::getUserAgent() {
if (QThread::currentThread() != thread()) {
QString userAgent;
- BLOCKING_INVOKE_METHOD(this, "getUserAgent", Q_RETURN_ARG(QString, userAgent));
+ BLOCKING_INVOKE_METHOD(this, "getUserAgent", Q_GENERIC_RETURN_ARG(QString, userAgent));
return userAgent;
}
@@ -613,7 +623,8 @@ void Application::registerScriptEngineWithApplicationServices(ScriptManagerPoint
scriptEngine->registerGlobalObject(sgp, "Menu", MenuScriptingInterface::getInstance());
scriptEngine->registerGlobalObject(sgp, "DesktopPreviewProvider", DependencyManager::get().data());
#if !defined(DISABLE_QML)
- scriptEngine->registerGlobalObject(sgp, "Stats", Stats::getInstance());
+ // QT6TODO: Stats instance is not created?
+ //scriptEngine->registerGlobalObject(sgp, "Stats", Stats::getInstance());
#endif
scriptEngine->registerGlobalObject(sgp, "Settings", SettingsScriptingInterface::getInstance());
scriptEngine->registerGlobalObject(sgp, "Snapshot", DependencyManager::get().data());
@@ -1009,7 +1020,7 @@ std::map Application::prepareServerlessDomainContents(QUrl dom
void Application::loadServerlessDomain(QUrl domainURL) {
if (QThread::currentThread() != thread()) {
- QMetaObject::invokeMethod(this, "loadServerlessDomain", Q_ARG(QUrl, domainURL));
+ QMetaObject::invokeMethod(this, "loadServerlessDomain", Q_GENERIC_ARG(QUrl, domainURL));
return;
}
@@ -1041,7 +1052,7 @@ void Application::loadServerlessDomain(QUrl domainURL) {
void Application::loadErrorDomain(QUrl domainURL) {
if (QThread::currentThread() != thread()) {
- QMetaObject::invokeMethod(this, "loadErrorDomain", Q_ARG(QUrl, domainURL));
+ QMetaObject::invokeMethod(this, "loadErrorDomain", Q_GENERIC_ARG(QUrl, domainURL));
return;
}
@@ -1289,7 +1300,13 @@ void Application::loadSettings(const QCommandLineParser& parser) {
externalResource->setBase(bucket, url.get());
}
- // the setter function isn't called, so update the theme colors now
+ connect(_themePrefs, &ThemePrefs::themeChanged, [this]() {
+ if (QThread::currentThread() != thread()) {
+ QMetaObject::invokeMethod(qApp, "updateThemeColors");
+ } else {
+ updateThemeColors();
+ }
+ });
updateThemeColors();
_settingsLoaded = true;
diff --git a/interface/src/Application.h b/interface/src/Application.h
index 327eb6b900d..1de2d0ee462 100644
--- a/interface/src/Application.h
+++ b/interface/src/Application.h
@@ -44,6 +44,7 @@
#include "ui/overlays/Overlays.h"
#include "VisionSqueeze.h"
#include "workload/GameWorkload.h"
+#include "ThemePrefs.h"
#ifndef USE_GL
#include "VKCanvas.h"
#endif
@@ -93,6 +94,8 @@ class Application : public QApplication,
);
~Application();
+ bool setupEssentials(const QCommandLineParser& parser, bool runningMarkerExisted);
+
/**
* @brief Initialize everything
*
@@ -177,6 +180,8 @@ class Application : public QApplication,
const ApplicationOverlay& getApplicationOverlay() const { return *_applicationOverlay; }
CompositorHelper& getApplicationCompositor() const;
+ std::shared_ptr getVirtualPointingDevice() const;
+
virtual PickRay computePickRay(float x, float y) const override;
static void setupQmlSurface(QQmlContext* surfaceContext, bool setAdditionalContextProperties);
@@ -325,7 +330,8 @@ class Application : public QApplication,
int getMaxOctreePacketsPerSecond() const { return _maxOctreePPS; }
bool isMissingSequenceNumbers() { return _isMissingSequenceNumbers; }
- NodeToOctreeSceneStats* getOcteeSceneStats() { return _octreeProcessor->getOctreeSceneStats(); }
+ // This function returns a value only when octree processor is available.
+ std::optional getOcteeSceneStats();
// Assets
@@ -392,7 +398,6 @@ class Application : public QApplication,
void miniTabletEnabledChanged(bool enabled);
void awayStateWhenFocusLostInVRChanged(bool enabled);
- void darkThemePreferenceChanged(bool useDarkTheme);
void menuBarVisibilityChanged(bool visible);
public slots:
@@ -469,8 +474,7 @@ public slots:
const QString getPreferredCursor() const { return _preferredCursor.get(); }
void setPreferredCursor(const QString& cursor);
- bool getDarkThemePreference() const { return _darkTheme.get(); }
- void setDarkThemePreference(bool value);
+ ThemePrefs* themePrefs() { return _themePrefs; }
bool getMenuBarVisible() const { return _menuBarVisible.get(); }
void setMenuBarVisible(bool visible);
@@ -789,6 +793,8 @@ private slots:
bool _cursorNeedsChanging { false };
bool _useSystemCursor { false };
+ std::shared_ptr _virtualPointingDevice;
+
DialogsManagerScriptingInterface* _dialogsManagerScriptingInterface;
QPointer _logDialog;
@@ -826,13 +832,12 @@ private slots:
Setting::Handle _constrainToolbarPosition;
Setting::Handle _awayStateWhenFocusLostInVREnabled;
Setting::Handle _preferredCursor;
- // TODO Qt6: Qt5 doesn't have anything for system theme preferences, Qt6.5+ does
- Setting::Handle _darkTheme;
Setting::Handle _miniTabletEnabledSetting;
Setting::Handle _keepLogWindowOnTop { "keepLogWindowOnTop", false };
Setting::Handle _menuBarVisible { "menuBarVisible", true };
- void updateThemeColors();
+ ThemePrefs *_themePrefs;
+ Q_INVOKABLE void updateThemeColors();
// Plugins
@@ -853,7 +858,13 @@ private slots:
// Events
- QHash _keysPressed;
+ class KeyEventRecord {
+ public:
+ KeyEventRecord(const int key, const QString &text) : key(key), text(text) {}
+ int key;
+ QString text;
+ };
+ QHash _keysPressed;
TouchEvent _lastTouchEvent;
quint64 _lastAcceptedKeyPress { 0 };
diff --git a/interface/src/ApplicationEventHandler.h b/interface/src/ApplicationEventHandler.h
index d7e5bf171c7..7a252d3b171 100644
--- a/interface/src/ApplicationEventHandler.h
+++ b/interface/src/ApplicationEventHandler.h
@@ -24,10 +24,12 @@
#include "Application.h"
#ifdef Q_OS_WIN
-static const UINT UWM_IDENTIFY_INSTANCES =
- RegisterWindowMessage("UWM_IDENTIFY_INSTANCES_{8AB82783-B74A-4258-955B-8188C22AA0D6}_" + qgetenv("USERNAME"));
-static const UINT UWM_SHOW_APPLICATION =
- RegisterWindowMessage("UWM_SHOW_APPLICATION_{71123FD6-3DA8-4DC1-9C27-8A12A6250CBA}_" + qgetenv("USERNAME"));
+#include
+
+static const UINT UWM_IDENTIFY_INSTANCES = RegisterWindowMessage(
+ qUtf16Printable("UWM_IDENTIFY_INSTANCES_{8AB82783-B74A-4258-955B-8188C22AA0D6}_" + qEnvironmentVariable("USERNAME")));
+static const UINT UWM_SHOW_APPLICATION = RegisterWindowMessage(
+ qUtf16Printable("UWM_SHOW_APPLICATION_{71123FD6-3DA8-4DC1-9C27-8A12A6250CBA}_" + qEnvironmentVariable("USERNAME")));
class MyNativeEventFilter : public QAbstractNativeEventFilter {
public:
@@ -36,7 +38,7 @@ class MyNativeEventFilter : public QAbstractNativeEventFilter {
return staticInstance;
}
- bool nativeEventFilter(const QByteArray &eventType, void* msg, long* result) Q_DECL_OVERRIDE {
+ bool nativeEventFilter(const QByteArray& eventType, void* msg, qintptr* result) Q_DECL_OVERRIDE {
if (eventType == "windows_generic_MSG") {
MSG* message = (MSG*)msg;
diff --git a/interface/src/Application_Assets.cpp b/interface/src/Application_Assets.cpp
index 96404063b67..4ac698786e0 100644
--- a/interface/src/Application_Assets.cpp
+++ b/interface/src/Application_Assets.cpp
@@ -17,6 +17,7 @@
#include
#include
+#include
#include
#include
@@ -171,7 +172,7 @@ void Application::addAssetToWorld(QString path, QString zipFile, bool isZip) {
QString mapping;
QString filename = filenameFromPath(path);
if (isZip) {
- QString assetName = zipFile.section("/", -1).remove(QRegExp("[.]zip(.*)$"));
+ QString assetName = zipFile.section("/", -1).remove(QRegularExpression("[.]zip(.*)$"));
QString assetFolder = path.section("model_repo/", -1);
mapping = "/" + assetName + "/" + assetFolder;
} else {
diff --git a/interface/src/Application_Entities.cpp b/interface/src/Application_Entities.cpp
index 1ef78e84b60..1c1d7673e32 100644
--- a/interface/src/Application_Entities.cpp
+++ b/interface/src/Application_Entities.cpp
@@ -37,6 +37,15 @@ void Application::setMaxOctreePacketsPerSecond(int maxOctreePPS) {
}
}
+std::optional Application::getOcteeSceneStats() {
+ // On start this function is sometimes called before _octreeProcessor is available.
+ if (_octreeProcessor) {
+ return _octreeProcessor->getOctreeSceneStats();
+ } else {
+ return {};
+ }
+}
+
QVector Application::pasteEntities(const QString& entityHostType, float x, float y, float z) {
return _entityClipboard->sendEntities(_entityEditSender.get(), getEntities()->getTree(), entityHostType, x, y, z);
}
@@ -313,9 +322,11 @@ void Application::clearDomainOctreeDetails(bool clearAll) {
setIsInterstitialMode(true);
auto octreeServerSceneStats = getOcteeSceneStats();
- octreeServerSceneStats->withWriteLock([&] {
- octreeServerSceneStats->clear();
- });
+ if (octreeServerSceneStats) {
+ octreeServerSceneStats.value()->withWriteLock([&] {
+ octreeServerSceneStats.value()->clear();
+ });
+ }
// reset the model renderer
clearAll ? getEntities()->clear() : getEntities()->clearDomainAndNonOwnedEntities();
@@ -413,16 +424,18 @@ int Application::sendNackPackets() {
QSet missingSequenceNumbers;
auto octreeServerSceneStats = getOcteeSceneStats();
- octreeServerSceneStats->withReadLock([&] {
- // retrieve octree scene stats of this node
- if (octreeServerSceneStats->find(nodeUUID) == octreeServerSceneStats->end()) {
- return;
- }
- // get sequence number stats of node, prune its missing set, and make a copy of the missing set
- SequenceNumberStats& sequenceNumberStats = (*octreeServerSceneStats)[nodeUUID].getIncomingOctreeSequenceNumberStats();
- sequenceNumberStats.pruneMissingSet();
- missingSequenceNumbers = sequenceNumberStats.getMissingSet();
- });
+ if (octreeServerSceneStats) {
+ octreeServerSceneStats.value()->withReadLock([&] {
+ // retrieve octree scene stats of this node
+ if (octreeServerSceneStats.value()->find(nodeUUID) == octreeServerSceneStats.value()->end()) {
+ return;
+ }
+ // get sequence number stats of node, prune its missing set, and make a copy of the missing set
+ SequenceNumberStats& sequenceNumberStats = (*octreeServerSceneStats.value())[nodeUUID].getIncomingOctreeSequenceNumberStats();
+ sequenceNumberStats.pruneMissingSet();
+ missingSequenceNumbers = sequenceNumberStats.getMissingSet();
+ });
+ }
_isMissingSequenceNumbers = (missingSequenceNumbers.size() != 0);
diff --git a/interface/src/Application_Events.cpp b/interface/src/Application_Events.cpp
index cd3a9327e48..d29c92c9338 100644
--- a/interface/src/Application_Events.cpp
+++ b/interface/src/Application_Events.cpp
@@ -55,6 +55,10 @@ class LambdaEvent : public QEvent {
void call() const { _fun(); }
};
+std::shared_ptr Application::getVirtualPointingDevice() const {
+ return _virtualPointingDevice;
+}
+
bool Application::notify(QObject* object, QEvent* event) {
if (thread() == QThread::currentThread()) {
PROFILE_RANGE_IF_LONGER(app, "notify", 2)
@@ -211,7 +215,8 @@ bool Application::eventFilter(QObject* object, QEvent* event) {
QMouseEvent* newEvent = new QMouseEvent(
QEvent::MouseButtonPress, mouseEvent->localPos(), mouseEvent->windowPos(),
mouseEvent->screenPos(), Qt::RightButton, Qt::MouseButtons(Qt::RightButton),
- mouseEvent->modifiers());
+ mouseEvent->modifiers(),
+ getVirtualPointingDevice().get());
QApplication::postEvent(object, newEvent);
return true;
}
@@ -336,7 +341,7 @@ void Application::windowMinimizedChanged(bool minimized) {
void Application::keyPressEvent(QKeyEvent* event) {
if (!event->isAutoRepeat()) {
- _keysPressed.insert(event->key(), *event);
+ _keysPressed.insert(event->key(), KeyEventRecord(event->key(), event->text()));
}
_controllerScriptingInterface->emitKeyPressEvent(event); // send events to any registered scripts
@@ -607,10 +612,10 @@ void Application::synthesizeKeyReleasEvents() {
// synthesize events for keys currently pressed, since we may not get their release events
// Because our key event handlers may manipulate _keysPressed, lets swap the keys pressed into a local copy,
// clearing the existing list.
- QHash keysPressed;
+ QHash keysPressed;
std::swap(keysPressed, _keysPressed);
- for (auto& ev : keysPressed) {
- QKeyEvent synthesizedEvent { QKeyEvent::KeyRelease, ev.key(), Qt::NoModifier, ev.text() };
+ for (auto& eventRecord : keysPressed) {
+ QKeyEvent synthesizedEvent { QKeyEvent::KeyRelease, eventRecord.key, Qt::NoModifier, eventRecord.text };
keyReleaseEvent(&synthesizedEvent);
}
}
@@ -652,11 +657,13 @@ void Application::mouseMoveEvent(QMouseEvent* event) {
QMouseEvent mappedEvent(event->type(),
transformedPos,
- event->screenPos(), button,
- buttons, event->modifiers());
+ event->globalPosition(), button,
+ buttons, event->modifiers(),
+ getVirtualPointingDevice().get());
if (compositor.getReticleVisible() || !isHMDMode() || !compositor.getReticleOverDesktop() ||
getOverlays().getOverlayAtPoint(glm::vec2(transformedPos.x(), transformedPos.y())) != UNKNOWN_ENTITY_ID) {
+ // QT6TODO: compositor.getReticleOverDesktop() does not work currently.
getEntities()->mouseMoveEvent(&mappedEvent);
}
@@ -687,7 +694,12 @@ void Application::mousePressEvent(QMouseEvent* event) {
QPointF transformedPos;
#endif
- QMouseEvent mappedEvent(event->type(), transformedPos, event->screenPos(), event->button(), event->buttons(), event->modifiers());
+ QMouseEvent mappedEvent(event->type(),
+ transformedPos,
+ event->globalPosition(), event->button(),
+ event->buttons(), event->modifiers(),
+ getVirtualPointingDevice().get());
+
QUuid result = getEntities()->mousePressEvent(&mappedEvent);
setKeyboardFocusEntity(getEntities()->wantsKeyboardFocus(result) ? result : UNKNOWN_ENTITY_ID);
@@ -726,8 +738,9 @@ void Application::mouseDoublePressEvent(QMouseEvent* event) {
#endif
QMouseEvent mappedEvent(event->type(),
transformedPos,
- event->screenPos(), event->button(),
- event->buttons(), event->modifiers());
+ event->globalPosition(), event->button(),
+ event->buttons(), event->modifiers(),
+ getVirtualPointingDevice().get());
getEntities()->mouseDoublePressEvent(&mappedEvent);
// if one of our scripts have asked to capture this event, then stop processing it
@@ -748,8 +761,9 @@ void Application::mouseReleaseEvent(QMouseEvent* event) {
#endif
QMouseEvent mappedEvent(event->type(),
transformedPos,
- event->screenPos(), event->button(),
- event->buttons(), event->modifiers());
+ event->globalPosition(), event->button(),
+ event->buttons(), event->modifiers(),
+ getVirtualPointingDevice().get());
getEntities()->mouseReleaseEvent(&mappedEvent);
diff --git a/interface/src/Application_Graphics.cpp b/interface/src/Application_Graphics.cpp
index 50a325370ee..9ec171e7595 100644
--- a/interface/src/Application_Graphics.cpp
+++ b/interface/src/Application_Graphics.cpp
@@ -18,6 +18,7 @@
#include
#include
+#include
#include
#include
@@ -57,6 +58,8 @@
#include "Menu.h"
#include "webbrowser/WebBrowserSuggestionsEngine.h"
+#include
+
#if defined(Q_OS_ANDROID)
#include "AndroidHelper.h"
#endif
@@ -81,6 +84,7 @@ void Application::initializeGL() {
}
#ifdef USE_GL
+ _primaryWidget->windowHandle()->setSurfaceType(QSurface::OpenGLSurface);
_primaryWidget->windowHandle()->setFormat(getDefaultOpenGLSurfaceFormat());
#else
//_primaryWidget->windowHandle()->setFormat(getDefaultOpenGLSurfaceFormat()); // VKTODO
@@ -163,6 +167,7 @@ void Application::initializeGL() {
{
OffscreenGLCanvas* qmlShareContext = new OffscreenGLCanvas();
qmlShareContext->setObjectName("QmlShareContext");
+ qmlShareContext->getContext()->setFormat(getDefaultOpenGLSurfaceFormat());
qmlShareContext->create(globalShareContext);
if (!qmlShareContext->makeCurrent()) {
qCWarning(interfaceapp, "Unable to make QML shared context current");
@@ -222,7 +227,7 @@ static void addDisplayPluginToMenu(const DisplayPluginPointer& displayPlugin, in
}
auto parent = menu->getMenu(MenuOption::OutputMenu);
auto action = menu->addActionToQMenuAndActionHash(parent,
- name, QKeySequence(Qt::CTRL + (Qt::Key_0 + index)), qApp,
+ name, QKeySequence(Qt::CTRL | static_cast(static_cast(Qt::Key_0) + index)), qApp,
SLOT(updateDisplayMode()),
QAction::NoRole, Menu::UNSPECIFIED_POSITION, groupingMenu);
@@ -243,12 +248,12 @@ void Application::initializeUi() {
auto newValidator = [=](const QUrl& url) -> bool {
QString allowlistPrefix = "[ALLOWLIST ENTITY SCRIPTS]";
QList safeURLS = { "" };
- safeURLS += qEnvironmentVariable("EXTRA_ALLOWLIST").trimmed().split(QRegExp("\\s*,\\s*"), Qt::SkipEmptyParts);
+ safeURLS += qEnvironmentVariable("EXTRA_ALLOWLIST").trimmed().split(QRegularExpression("\\s*,\\s*"), Qt::SkipEmptyParts);
// PULL SAFEURLS FROM INTERFACE.JSON Settings
QVariant raw = Setting::Handle("private/settingsSafeURLS").get();
- QStringList settingsSafeURLS = raw.toString().trimmed().split(QRegExp("\\s*[,\r\n]+\\s*"), Qt::SkipEmptyParts);
+ QStringList settingsSafeURLS = raw.toString().trimmed().split(QRegularExpression("\\s*[,\r\n]+\\s*"), Qt::SkipEmptyParts);
safeURLS += settingsSafeURLS;
// END PULL SAFEURLS FROM INTERFACE.JSON Settings
diff --git a/interface/src/Application_Setup.cpp b/interface/src/Application_Setup.cpp
index ba57063baea..fba5c6be658 100644
--- a/interface/src/Application_Setup.cpp
+++ b/interface/src/Application_Setup.cpp
@@ -251,7 +251,7 @@ static const int WATCHDOG_TIMER_TIMEOUT = 100;
static const QString TESTER_FILE = "/sdcard/_hifi_test_device.txt";
#endif
-bool setupEssentials(const QCommandLineParser& parser, bool runningMarkerExisted) {
+bool Application::setupEssentials(const QCommandLineParser& parser, bool runningMarkerExisted) {
const int listenPort = parser.isSet("listenPort") ? parser.value("listenPort").toInt() : INVALID_PORT;
bool suppressPrompt = parser.isSet("suppress-settings-reset");
@@ -330,6 +330,8 @@ bool setupEssentials(const QCommandLineParser& parser, bool runningMarkerExisted
DependencyManager::set();
DependencyManager::set();
DependencyManager::set(NodeType::Agent, listenPort);
+ // Octree processor requires NodeList.
+ _octreeProcessor = std::make_shared();
DependencyManager::set();
DependencyManager::set();
DependencyManager::set(); // ModelFormatRegistry must be defined before ModelCache. See the ModelCache constructor.
@@ -416,7 +418,14 @@ bool setupEssentials(const QCommandLineParser& parser, bool runningMarkerExisted
DependencyManager::set();
- DependencyManager::set(nullptr);
+ {
+ auto octreeSceneStats = qApp->getOcteeSceneStats();
+ if (!octreeSceneStats) {
+ qCritical() << "setupEssentials: Octree stats provider not available yet. This should never happen";
+ std::abort();
+ }
+ DependencyManager::set(nullptr, octreeSceneStats.value());
+ }
DependencyManager::set();
DependencyManager::set();
DependencyManager::set();
@@ -461,7 +470,6 @@ void Application::initialize(const QCommandLineParser &parser) {
_entitySimulation = std::make_shared();
_physicsEngine = std::make_shared(Vectors::ZERO);
_entityClipboard = std::make_shared();
- _octreeProcessor = std::make_shared();
_entityEditSender = std::make_shared();
_graphicsEngine = std::make_shared();
_applicationOverlay = std::make_shared();
@@ -660,6 +668,14 @@ void Application::initialize(const QCommandLineParser &parser) {
QFontDatabase::addApplicationFont(PathUtils::resourcesPath() + "fonts/FiraSans-SemiBold.ttf");
QFontDatabase::addApplicationFont(PathUtils::resourcesPath() + "fonts/FiraSans-Regular.ttf");
QFontDatabase::addApplicationFont(PathUtils::resourcesPath() + "fonts/FiraSans-Medium.ttf");
+ QFontDatabase::addApplicationFont(PathUtils::resourcesPath() + "fonts/Roboto-Regular.ttf");
+ QFontDatabase::addApplicationFont(PathUtils::resourcesPath() + "fonts/Roboto-Bold.ttf");
+ QFontDatabase::addApplicationFont(PathUtils::resourcesPath() + "fonts/Roboto-Italic.ttf");
+ QFontDatabase::addApplicationFont(PathUtils::resourcesPath() + "fonts/Roboto-BoldItalic.ttf");
+ QFontDatabase::addApplicationFont(PathUtils::resourcesPath() + "fonts/RobotoMono-Regular.ttf");
+ QFontDatabase::addApplicationFont(PathUtils::resourcesPath() + "fonts/RobotoMono-Bold.ttf");
+ QFontDatabase::addApplicationFont(PathUtils::resourcesPath() + "fonts/RobotoMono-Italic.ttf");
+ QFontDatabase::addApplicationFont(PathUtils::resourcesPath() + "fonts/RobotoMono-BoldItalic.ttf");
_window->setWindowTitle("Overte");
Model::setAbstractViewStateInterface(this); // The model class will sometimes need to know view state details from us
@@ -764,7 +780,8 @@ void Application::initialize(const QCommandLineParser &parser) {
<< NodeType::EntityServer << NodeType::AssetServer << NodeType::MessagesMixer << NodeType::EntityScriptServer);
// setDefaultFormat has no effect after the platform window has been created, so call it here.
- QSurfaceFormat::setDefaultFormat(getDefaultOpenGLSurfaceFormat());
+ // QT6TODO: I was getting a warning when it was here, so I moved it to the beginning.
+ //QSurfaceFormat::setDefaultFormat(getDefaultOpenGLSurfaceFormat());
#ifdef USE_GL
_primaryWidget = new GLCanvas();
@@ -878,14 +895,17 @@ void Application::initialize(const QCommandLineParser &parser) {
{ "gl_renderer", glContextData.renderer.c_str() },
{ "ideal_thread_count", QThread::idealThreadCount() }
};
+#ifdef Q_OS_MAC
auto macVersion = QSysInfo::macVersion();
if (macVersion != QSysInfo::MV_None) {
properties["os_osx_version"] = QSysInfo::macVersion();
}
+#elifdef Q_OS_WIN
auto windowsVersion = QSysInfo::windowsVersion();
if (windowsVersion != QSysInfo::WV_None) {
properties["os_win_version"] = QSysInfo::windowsVersion();
}
+#endif
ProcessorInfo procInfo;
if (getProcessorInfo(procInfo)) {
@@ -1196,10 +1216,12 @@ void Application::initialize(const QCommandLineParser &parser) {
properties["deleted_entity_cnt"] = entityActivityTracking.deletedEntityCount;
properties["edited_entity_cnt"] = entityActivityTracking.editedEntityCount;
- NodeToOctreeSceneStats* octreeServerSceneStats = getOcteeSceneStats();
+ auto octreeServerSceneStats = getOcteeSceneStats();
unsigned long totalServerOctreeElements = 0;
- for (NodeToOctreeSceneStatsIterator i = octreeServerSceneStats->begin(); i != octreeServerSceneStats->end(); i++) {
- totalServerOctreeElements += i->second.getTotalElements();
+ if (octreeServerSceneStats) {
+ for (NodeToOctreeSceneStatsIterator i = octreeServerSceneStats.value()->begin(); i != octreeServerSceneStats.value()->end(); i++) {
+ totalServerOctreeElements += i->second.getTotalElements();
+ }
}
properties["local_octree_elements"] = (qint64) OctreeElement::getInternalNodeCount();
@@ -2010,11 +2032,11 @@ void Application::setupSignalsAndOperators() {
auto reticlePos = getApplicationCompositor().getReticlePosition();
QPoint localPos(reticlePos.x, reticlePos.y); // both hmd and desktop already handle this in our coordinates.
if (state) {
- QMouseEvent mousePress(QEvent::MouseButtonPress, localPos, Qt::LeftButton, Qt::LeftButton, Qt::NoModifier);
+ QMouseEvent mousePress(QEvent::MouseButtonPress, localPos, localPos, Qt::LeftButton, Qt::LeftButton, Qt::NoModifier, getVirtualPointingDevice().get());
sendEvent(_primaryWidget, &mousePress);
_reticleClickPressed = true;
} else {
- QMouseEvent mouseRelease(QEvent::MouseButtonRelease, localPos, Qt::LeftButton, Qt::NoButton, Qt::NoModifier);
+ QMouseEvent mouseRelease(QEvent::MouseButtonRelease, localPos, localPos, Qt::LeftButton, Qt::NoButton, Qt::NoModifier, getVirtualPointingDevice().get());
sendEvent(_primaryWidget, &mouseRelease);
_reticleClickPressed = false;
}
diff --git a/interface/src/Application_UI.cpp b/interface/src/Application_UI.cpp
index 53e8600b717..cdb1ab9aee0 100644
--- a/interface/src/Application_UI.cpp
+++ b/interface/src/Application_UI.cpp
@@ -84,6 +84,7 @@
#include
#include
#include
+#include
#include "AboutUtil.h"
#include "ArchiveDownloadInterface.h"
@@ -229,7 +230,10 @@ void Application::setupQmlSurface(QQmlContext* surfaceContext, bool setAdditiona
surfaceContext->setContextProperty("offscreenFlags", flags);
surfaceContext->setContextProperty("AddressManager", DependencyManager::get().data());
+ // TODO: Replace Settings (conflicts with QtCore.Settings) with SettingsInterface
surfaceContext->setContextProperty("Settings", new QMLSettingsScriptingInterface(surfaceContext));
+ surfaceContext->setContextProperty("SettingsInterface", new QMLSettingsScriptingInterface(surfaceContext));
+
surfaceContext->setContextProperty("MenuInterface", MenuScriptingInterface::getInstance());
surfaceContext->setContextProperty("Performance", new PerformanceScriptingInterface());
@@ -258,8 +262,16 @@ void Application::setupQmlSurface(QQmlContext* surfaceContext, bool setAdditiona
surfaceContext->setContextProperty("Workload", qApp->getGameWorkload()._engine->getConfiguration().get());
surfaceContext->setContextProperty("Controller", DependencyManager::get().data());
surfaceContext->setContextProperty("Pointers", DependencyManager::get().data());
+
+ // QT6TODO: replace all of the instances of "Window" in QML with WindowScriptingInterface
surfaceContext->setContextProperty("Window", DependencyManager::get().data());
+
+ // There's a lot of stuff in our QML called "Window",
+ // so this needs to be WindowScriptingInterface
+ surfaceContext->setContextProperty("WindowScriptingInterface", DependencyManager::get().data());
+
surfaceContext->setContextProperty("Reticle", qApp->getApplicationCompositor().getReticleInterface());
+ surfaceContext->setContextProperty("PickScriptingInterface", DependencyManager::get().data());
surfaceContext->setContextProperty("About", AboutUtil::getInstance());
surfaceContext->setContextProperty("HiFiAbout", AboutUtil::getInstance()); // Deprecated.
surfaceContext->setContextProperty("ResourceRequestObserver", DependencyManager::get().data());
@@ -580,7 +592,7 @@ void Application::showAssetServerWidget(QString filePath) {
if (!DependencyManager::get()->getThisNodeCanWriteAssets() || getLoginDialogPoppedUp()) {
return;
}
- static const QUrl url { "hifi/AssetServer.qml" };
+ static const QUrl url { "overte/compat/CompatAssetDialog.qml" };
auto startUpload = [=, this](QQmlContext* context, QObject* newObject){
if (!filePath.isEmpty()) {
@@ -596,7 +608,7 @@ void Application::showAssetServerWidget(QString filePath) {
if (!hmd->getShouldShowTablet() && !isHMDMode()) {
getOffscreenUI()->show(url, "AssetServer", startUpload);
} else {
- static const QUrl url("qrc:///qml/hifi/dialogs/TabletAssetServer.qml");
+ static const QUrl url("qrc:///qml/overte/dialogs/AssetDialog.qml");
if (!tablet->isPathLoaded(url)) {
tablet->pushOntoStack(url);
}
@@ -808,7 +820,7 @@ void Application::updateThemeColors() {
QPalette palette = style->standardPalette();
- if (_darkTheme.get()) {
+ if (_themePrefs->getDarkMode()) {
palette.setColor(QPalette::Window, QColor(48, 48, 48));
palette.setColor(QPalette::WindowText, QColor(224, 224, 224));
@@ -838,16 +850,6 @@ void Application::updateThemeColors() {
qApp->getPrimaryMenu()->setPalette(palette); // weird Qt bug workaround
}
-void Application::setDarkThemePreference(bool value) {
- bool previousValue = _darkTheme.get();
-
- if (value == previousValue) { return; }
-
- _darkTheme.set(value);
- updateThemeColors();
- emit darkThemePreferenceChanged(value);
-}
-
void Application::showVRKeyboardForHudUI(bool show) {
if (show) {
DependencyManager::get()->setRaised(true, true);
@@ -901,6 +903,9 @@ void Application::onDesktopRootContextCreated(QQmlContext* surfaceContext) {
surfaceContext->setContextProperty("Assets", DependencyManager::get().data());
surfaceContext->setContextProperty("Keyboard", DependencyManager::get().data());
+ // TODO: replace instances of Keyboard with KeyboardScriptingInterface
+ surfaceContext->setContextProperty("KeyboardScriptingInterface", DependencyManager::get().data());
+
surfaceContext->setContextProperty("AvatarList", DependencyManager::get().data());
surfaceContext->setContextProperty("Users", DependencyManager::get().data());
@@ -912,10 +917,21 @@ void Application::onDesktopRootContextCreated(QQmlContext* surfaceContext) {
#endif
surfaceContext->setContextProperty("Overlays", &_overlays);
+
+ // QT6TODO: replace all of the instances of "Window" in QML with WindowScriptingInterface
surfaceContext->setContextProperty("Window", DependencyManager::get().data());
+
+ // There's a lot of stuff in our QML called "Window",
+ // so this needs to be WindowScriptingInterface
+ surfaceContext->setContextProperty("WindowScriptingInterface", DependencyManager::get().data());
+
surfaceContext->setContextProperty("Desktop", DependencyManager::get().data());
surfaceContext->setContextProperty("MenuInterface", MenuScriptingInterface::getInstance());
+
+ // TODO: Replace Settings (conflicts with QtCore.Settings) with SettingsInterface
surfaceContext->setContextProperty("Settings", new QMLSettingsScriptingInterface(surfaceContext));
+ surfaceContext->setContextProperty("SettingsInterface", new QMLSettingsScriptingInterface(surfaceContext));
+
surfaceContext->setContextProperty("ScriptDiscoveryService", DependencyManager::get().data());
surfaceContext->setContextProperty("AvatarBookmarks", DependencyManager::get().data());
surfaceContext->setContextProperty("LocationBookmarks", DependencyManager::get().data());
@@ -941,6 +957,7 @@ void Application::onDesktopRootContextCreated(QQmlContext* surfaceContext) {
surfaceContext->setContextProperty("Render", RenderScriptingInterface::getInstance());
surfaceContext->setContextProperty("PlatformInfo", PlatformInfoScriptingInterface::getInstance());
surfaceContext->setContextProperty("Workload", _gameWorkload._engine->getConfiguration().get());
+ surfaceContext->setContextProperty("PickScriptingInterface", DependencyManager::get().data());
surfaceContext->setContextProperty("Reticle", getApplicationCompositor().getReticleInterface());
surfaceContext->setContextProperty("Snapshot", DependencyManager::get().data());
@@ -981,7 +998,7 @@ bool Application::askToSetAvatarUrl(const QString& url) {
}
// Download the FST file, to attempt to determine its model type
- QVariantHash fstMapping = FSTReader::downloadMapping(url);
+ hifi::VariantMultiHash fstMapping = FSTReader::downloadMapping(url);
FSTReader::ModelType modelType = FSTReader::predictModelType(fstMapping);
@@ -1462,7 +1479,7 @@ void Application::addAssetToWorldError(QString modelName, QString errorText) {
void Application::setMenuBarVisible(bool visible) {
if (QThread::currentThread() != qApp->thread()) {
- QMetaObject::invokeMethod(this, "setMenuBarVisible", Q_ARG(bool, visible));
+ QMetaObject::invokeMethod(this, "setMenuBarVisible", Q_GENERIC_ARG(bool, visible));
return;
}
diff --git a/interface/src/ArchiveDownloadInterface.cpp b/interface/src/ArchiveDownloadInterface.cpp
index d82fd0bec2e..ea187ab051e 100644
--- a/interface/src/ArchiveDownloadInterface.cpp
+++ b/interface/src/ArchiveDownloadInterface.cpp
@@ -17,7 +17,7 @@
#include
#include
#include
-#include
+#include
#include
#include
#include
diff --git a/interface/src/AvatarBookmarks.cpp b/interface/src/AvatarBookmarks.cpp
index a394825f2ad..eaa018cfe21 100644
--- a/interface/src/AvatarBookmarks.cpp
+++ b/interface/src/AvatarBookmarks.cpp
@@ -140,7 +140,7 @@ void AvatarBookmarks::addBookmark(const QString& bookmarkName) {
void AvatarBookmarks::addBookmarkInternal(const QString& bookmarkName) {
if (QThread::currentThread() != thread()) {
- BLOCKING_INVOKE_METHOD(this, "addBookmark", Q_ARG(QString, bookmarkName));
+ BLOCKING_INVOKE_METHOD(this, "addBookmark", Q_GENERIC_ARG(QString, bookmarkName));
return;
}
QVariantMap bookmark = getAvatarDataToBookmark();
@@ -157,7 +157,7 @@ void AvatarBookmarks::saveBookmark(const QString& bookmarkName) {
void AvatarBookmarks::saveBookmarkInternal(const QString& bookmarkName) {
if (QThread::currentThread() != thread()) {
- BLOCKING_INVOKE_METHOD(this, "saveBookmark", Q_ARG(QString, bookmarkName));
+ BLOCKING_INVOKE_METHOD(this, "saveBookmark", Q_GENERIC_ARG(QString, bookmarkName));
return;
}
if (contains(bookmarkName)) {
@@ -174,7 +174,7 @@ void AvatarBookmarks::removeBookmark(const QString& bookmarkName) {
void AvatarBookmarks::removeBookmarkInternal(const QString& bookmarkName) {
if (QThread::currentThread() != thread()) {
- BLOCKING_INVOKE_METHOD(this, "removeBookmark", Q_ARG(QString, bookmarkName));
+ BLOCKING_INVOKE_METHOD(this, "removeBookmark", Q_GENERIC_ARG(QString, bookmarkName));
return;
}
@@ -230,7 +230,7 @@ void AvatarBookmarks::loadBookmark(const QString& bookmarkName) {
void AvatarBookmarks::loadBookmarkInternal(const QString& bookmarkName) {
if (QThread::currentThread() != thread()) {
- BLOCKING_INVOKE_METHOD(this, "loadBookmark", Q_ARG(QString, bookmarkName));
+ BLOCKING_INVOKE_METHOD(this, "loadBookmark", Q_GENERIC_ARG(QString, bookmarkName));
return;
}
@@ -277,7 +277,7 @@ void AvatarBookmarks::loadBookmarkInternal(const QString& bookmarkName) {
void AvatarBookmarks::readFromFile() {
// migrate old avatarbookmarks.json, used to be in 'local' folder on windows
- QString oldConfigPath = QStandardPaths::writableLocation(QStandardPaths::DataLocation) + "/" + AVATARBOOKMARKS_FILENAME;
+ QString oldConfigPath = QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation) + "/" + AVATARBOOKMARKS_FILENAME;
QFile oldConfig(oldConfigPath);
// I imagine that in a year from now, this code for migrating (as well as the two lines above)
@@ -307,7 +307,7 @@ QVariantMap AvatarBookmarks::getBookmarkInternal(const QString &bookmarkName)
{
if (QThread::currentThread() != thread()) {
QVariantMap result;
- BLOCKING_INVOKE_METHOD(this, "getBookmark", Q_RETURN_ARG(QVariantMap, result), Q_ARG(QString, bookmarkName));
+ BLOCKING_INVOKE_METHOD(this, "getBookmark", Q_GENERIC_RETURN_ARG(QVariantMap, result), Q_GENERIC_ARG(QString, bookmarkName));
return result;
}
@@ -369,3 +369,50 @@ QVariantMap AvatarBookmarks::getAvatarDataToBookmark() {
bookmark.insert(ENTRY_AVATAR_ENTITIES, wearableEntities);
return bookmark;
}
+
+void AvatarBookmarks::setBookmarkData(const QString& bookmarkName, const QVariantMap& data) {
+ if (!ScriptPermissions::isCurrentScriptAllowed(ScriptPermissions::Permission::SCRIPT_PERMISSION_GET_AVATAR_URL)) {
+ return;
+ }
+
+ if (QThread::currentThread() != thread()) {
+ BLOCKING_INVOKE_METHOD(
+ this,
+ "setBookmarkData",
+ Q_GENERIC_ARG(QString, bookmarkName),
+ Q_GENERIC_ARG(QVariantMap, data)
+ );
+ return;
+ }
+
+ auto sanitizedData = data;
+
+ if (!sanitizedData.contains(ENTRY_VERSION)) {
+ sanitizedData.insert(ENTRY_VERSION, AVATAR_BOOKMARK_VERSION);
+ }
+
+ if (!sanitizedData.contains(ENTRY_AVATAR_URL)) {
+ qCritical() << "setBookmarkData called without \"avatarUrl\" field!";
+ return;
+ }
+
+ if (!sanitizedData.contains(ENTRY_AVATAR_ICON)) {
+ sanitizedData.insert(ENTRY_AVATAR_ICON, "");
+ }
+
+ if (!sanitizedData.contains(ENTRY_AVATAR_SCALE)) {
+ sanitizedData.insert(ENTRY_AVATAR_SCALE, 1.0);
+ }
+
+ if (!sanitizedData.contains(ENTRY_AVATAR_ENTITIES)) {
+ sanitizedData.insert(ENTRY_AVATAR_ENTITIES, QVariantList());
+ }
+
+ auto alreadyExists = contains(bookmarkName);
+
+ insert(bookmarkName, sanitizedData);
+
+ if (!alreadyExists) {
+ emit bookmarkAdded(bookmarkName);
+ }
+}
diff --git a/interface/src/AvatarBookmarks.h b/interface/src/AvatarBookmarks.h
index 40d862b83ed..8eccc545fcb 100644
--- a/interface/src/AvatarBookmarks.h
+++ b/interface/src/AvatarBookmarks.h
@@ -105,6 +105,22 @@ public slots:
*/
QVariantMap getBookmarks();
+ /*@jsdoc
+ * Directly sets the data object of a bookmark.
+ * @function AvatarBookmarks.setBookmarkData
+ * @param {string} - The name of the bookmark. If no bookmark with the specified name exists, it will be created. Otherwise, it will edit the existing one.
+ * @param {object} - The bookmark data. See the example below for valid keys and their defaults.
+ * @example
+ * AvatarBookmarks.setBookmarkData("Woody", {
+ * version: 3,
+ * avatarScale: 1.0,
+ * avatarEntities: [],
+ * avatarIcon: "",
+ * avatarUrl: "qrc:///meshes/defaultAvatar_full.fst",
+ * });
+ */
+ void setBookmarkData(const QString& bookmarkName, const QVariantMap& data);
+
signals:
/*@jsdoc
* Triggered when an avatar bookmark is loaded, setting your avatar model, scale, and avatar entities to those in the bookmark.
diff --git a/interface/src/DiscoverabilityManager.cpp b/interface/src/DiscoverabilityManager.cpp
index 624d5430a26..5c64f3f81a2 100644
--- a/interface/src/DiscoverabilityManager.cpp
+++ b/interface/src/DiscoverabilityManager.cpp
@@ -145,7 +145,7 @@ void DiscoverabilityManager::handleHeartbeatResponse(QNetworkReply* requestReply
// give that session ID to the account manager
auto accountManager = DependencyManager::get();
- accountManager->setSessionID(sessionID);
+ accountManager->setSessionID(QUuid(sessionID));
}
}
diff --git a/interface/src/LocationBookmarks.cpp b/interface/src/LocationBookmarks.cpp
index 8a10e636ae3..3ac51996330 100644
--- a/interface/src/LocationBookmarks.cpp
+++ b/interface/src/LocationBookmarks.cpp
@@ -15,6 +15,7 @@
#include
#include
+#include
#include
#include
@@ -25,7 +26,7 @@
const QString LocationBookmarks::HOME_BOOKMARK = "Home";
LocationBookmarks::LocationBookmarks() {
- _bookmarksFilename = QStandardPaths::writableLocation(QStandardPaths::DataLocation) + "/" + LOCATIONBOOKMARKS_FILENAME;
+ _bookmarksFilename = QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation) + "/" + LOCATIONBOOKMARKS_FILENAME;
readFromFile();
}
@@ -104,7 +105,7 @@ void LocationBookmarks::addBookmark() {
disconnect(dlg, &ModalDialogListener::response, this, nullptr);
auto bookmarkName = response.toString();
- bookmarkName = bookmarkName.trimmed().replace(QRegExp("(\r\n|[\r\n\t\v ])+"), " ");
+ bookmarkName = bookmarkName.trimmed().replace(QRegularExpression("(\r\n|[\r\n\t\v ])+"), " ");
if (bookmarkName.length() == 0) {
return;
}
diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp
index a92f1b3ffd0..749b3d1abb0 100644
--- a/interface/src/Menu.cpp
+++ b/interface/src/Menu.cpp
@@ -19,6 +19,7 @@
// QT6TODO: The Key Enums should be replaced by QKeyCombination in Qt6
#include "Menu.h"
+#include
#include
#include
#include
@@ -97,6 +98,8 @@ Menu::Menu() {
dialogsManager.data(), &DialogsManager::toggleLoginDialog);
}
+ // Qt6 TODO: This crashes when the domain changes, maybe a thread safety thing?
+#if 0
auto domainLogin = addActionToQMenuAndActionHash(fileMenu, "Domain: Log In");
domainLogin->setVisible(false);
connect(domainLogin, &QAction::triggered, [] {
@@ -107,6 +110,7 @@ Menu::Menu() {
connect(domainAccountManager.data(), &DomainAccountManager::hasLogInChanged, [domainLogin](bool hasLogIn) {
domainLogin->setVisible(hasLogIn);
});
+#endif
// File > Quit
addActionToQMenuAndActionHash(fileMenu, MenuOption::Quit, static_cast(Qt::CTRL) | static_cast(Qt::Key_Q), qApp, SLOT(quit()), QAction::QuitRole);
@@ -149,8 +153,8 @@ Menu::Menu() {
auto action = addActionToQMenuAndActionHash(editMenu, MenuOption::RunningScripts, static_cast(Qt::CTRL) | static_cast(Qt::Key_J));
connect(action, &QAction::triggered, [] {
if (!qApp->getLoginDialogPoppedUp()) {
- static const QUrl widgetUrl("hifi/dialogs/RunningScripts.qml");
- static const QUrl tabletUrl("hifi/dialogs/TabletRunningScripts.qml");
+ static const QUrl widgetUrl("overte/compat/RunningScripts_Window.qml");
+ static const QUrl tabletUrl("overte/dialogs/RunningScriptsDialog.qml");
static const QString name("RunningScripts");
qApp->showDialog(widgetUrl, tabletUrl, name);
}
@@ -255,8 +259,11 @@ Menu::Menu() {
// Settings menu ----------------------------------
MenuWrapper* settingsMenu = addMenu("Settings");
+ // Legacy settings, some stuff isn't accessible from the new one yet
+ MenuWrapper *legacySettingsMenu = settingsMenu->addMenu("Legacy");
+
// Settings > General...
- action = addActionToQMenuAndActionHash(settingsMenu, MenuOption::Preferences, static_cast(Qt::CTRL) | static_cast(Qt::Key_G), nullptr, nullptr);
+ action = addActionToQMenuAndActionHash(legacySettingsMenu, MenuOption::Preferences, Qt::CTRL | Qt::Key_G, nullptr, nullptr);
connect(action, &QAction::triggered, [] {
if (!qApp->getLoginDialogPoppedUp()) {
qApp->showDialog(QString("hifi/dialogs/GeneralPreferencesDialog.qml"),
@@ -265,7 +272,7 @@ Menu::Menu() {
});
// Settings > Controls...
- action = addActionToQMenuAndActionHash(settingsMenu, "Controls...");
+ action = addActionToQMenuAndActionHash(legacySettingsMenu, "Controls...");
connect(action, &QAction::triggered, [] {
auto tablet = DependencyManager::get()->getTablet("com.highfidelity.interface.tablet.system");
auto hmd = DependencyManager::get();
@@ -277,7 +284,7 @@ Menu::Menu() {
});
// Settings > Audio...
- action = addActionToQMenuAndActionHash(settingsMenu, "Audio...");
+ action = addActionToQMenuAndActionHash(legacySettingsMenu, "Audio...");
connect(action, &QAction::triggered, [] {
static const QUrl tabletUrl("hifi/audio/Audio.qml");
auto tablet = DependencyManager::get()->getTablet("com.highfidelity.interface.tablet.system");
diff --git a/interface/src/ModelPackager.cpp b/interface/src/ModelPackager.cpp
index e6f7c508408..56e1199badb 100644
--- a/interface/src/ModelPackager.cpp
+++ b/interface/src/ModelPackager.cpp
@@ -23,6 +23,7 @@
#include "ModelSelector.h"
#include "ModelPropertiesDialog.h"
#include "InterfaceLogging.h"
+#include "shared/QtHelpers.h"
static const int MAX_TEXTURE_SIZE = 8192;
@@ -109,7 +110,7 @@ bool ModelPackager::loadModel() {
qCDebug(interfaceapp) << "Reading FBX file : " << _fbxInfo.filePath();
QByteArray fbxContents = fbx.readAll();
- _hfmModel = FBXSerializer().read(fbxContents, QVariantHash(), _fbxInfo.filePath());
+ _hfmModel = FBXSerializer().read(fbxContents, hifi::VariantMultiHash(), _fbxInfo.filePath());
// make sure we have some basic mappings
populateBasicMapping(_mapping, _fbxInfo.filePath(), *_hfmModel);
@@ -233,7 +234,7 @@ bool ModelPackager::zipModel() {
return true;
}
-void ModelPackager::populateBasicMapping(QVariantHash& mapping, QString filename, const hfm::Model& hfmModel) {
+void ModelPackager::populateBasicMapping(hifi::VariantMultiHash& mapping, QString filename, const hfm::Model& hfmModel) {
// mixamo files - in the event that a mixamo file was edited by some other tool, it's likely the applicationName will
// be rewritten, so we detect the existence of several different blendshapes which indicate we're likely a mixamo file
@@ -349,7 +350,7 @@ void ModelPackager::populateBasicMapping(QVariantHash& mapping, QString filename
blendshapes.insert("Sneer", QVariantList() << "NoseScrunch_Right" << 0.75);
blendshapes.insert("Sneer", QVariantList() << "Squint_Left" << 0.5);
blendshapes.insert("Sneer", QVariantList() << "Squint_Right" << 0.5);
- mapping.insert(BLENDSHAPE_FIELD, blendshapes);
+ mapping.insert(BLENDSHAPE_FIELD, qMultiHashToQVariant(blendshapes));
}
}
diff --git a/interface/src/ModelPackager.h b/interface/src/ModelPackager.h
index 9a2a49562a6..245a5632d90 100644
--- a/interface/src/ModelPackager.h
+++ b/interface/src/ModelPackager.h
@@ -34,7 +34,7 @@ class ModelPackager : public QObject {
bool editProperties();
bool zipModel();
- void populateBasicMapping(QVariantHash& mapping, QString filename, const hfm::Model& hfmModel);
+ void populateBasicMapping(hifi::VariantMultiHash& mapping, QString filename, const hfm::Model& hfmModel);
void listTextures();
bool copyTextures(const QString& oldDir, const QDir& newDir);
diff --git a/interface/src/ModelPropertiesDialog.cpp b/interface/src/ModelPropertiesDialog.cpp
index d67341990db..33c39fcb3c6 100644
--- a/interface/src/ModelPropertiesDialog.cpp
+++ b/interface/src/ModelPropertiesDialog.cpp
@@ -25,8 +25,10 @@
#include
#include
+#include "shared/QtHelpers.h"
-ModelPropertiesDialog::ModelPropertiesDialog(const QVariantHash& originalMapping,
+
+ModelPropertiesDialog::ModelPropertiesDialog(const hifi::VariantMultiHash& originalMapping,
const QString& basePath, const HFMModel& hfmModel) :
_originalMapping(originalMapping),
_basePath(basePath),
@@ -70,8 +72,8 @@ _hfmModel(hfmModel)
reset();
}
-QVariantHash ModelPropertiesDialog::getMapping() const {
- QVariantHash mapping = _originalMapping;
+hifi::VariantMultiHash ModelPropertiesDialog::getMapping() const {
+ hifi::VariantMultiHash mapping = _originalMapping;
mapping.insert(TYPE_FIELD, FSTReader::getNameFromType(FSTReader::HEAD_AND_BODY_MODEL));
mapping.insert(NAME_FIELD, _name->text());
mapping.insert(TEXDIR_FIELD, _textureDirectory->text());
@@ -85,7 +87,7 @@ QVariantHash ModelPropertiesDialog::getMapping() const {
}
mapping.insert(JOINT_INDEX_FIELD, jointIndices);
- QVariantHash joints = mapping.value(JOINT_FIELD).toHash();
+ hifi::VariantMultiHash joints = qVariantToQMultiHash(mapping.value(JOINT_FIELD));
insertJointMapping(joints, "jointEyeLeft", _leftEyeJoint->currentText());
insertJointMapping(joints, "jointEyeRight", _rightEyeJoint->currentText());
insertJointMapping(joints, "jointNeck", _neckJoint->currentText());
@@ -97,7 +99,7 @@ QVariantHash ModelPropertiesDialog::getMapping() const {
insertJointMapping(joints, "jointLeftHand", _leftHandJoint->currentText());
insertJointMapping(joints, "jointRightHand", _rightHandJoint->currentText());
- mapping.insert(JOINT_FIELD, joints);
+ mapping.insert(JOINT_FIELD, qMultiHashToQVariant(joints));
return mapping;
}
@@ -177,7 +179,7 @@ QDoubleSpinBox* ModelPropertiesDialog::createTranslationBox() const {
return box;
}
-void ModelPropertiesDialog::insertJointMapping(QVariantHash& joints, const QString& joint, const QString& name) const {
+void ModelPropertiesDialog::insertJointMapping(hifi::VariantMultiHash& joints, const QString& joint, const QString& name) const {
if (_hfmModel.jointIndices.contains(name)) {
joints.insert(joint, name);
} else {
diff --git a/interface/src/ModelPropertiesDialog.h b/interface/src/ModelPropertiesDialog.h
index 8cf9bd52483..cb6aadaaa28 100644
--- a/interface/src/ModelPropertiesDialog.h
+++ b/interface/src/ModelPropertiesDialog.h
@@ -29,10 +29,10 @@ class ModelPropertiesDialog : public QDialog {
Q_OBJECT
public:
- ModelPropertiesDialog(const QVariantHash& originalMapping,
+ ModelPropertiesDialog(const hifi::VariantMultiHash& originalMapping,
const QString& basePath, const HFMModel& hfmModel);
- QVariantHash getMapping() const;
+ hifi::VariantMultiHash getMapping() const;
private slots:
void reset();
@@ -43,9 +43,9 @@ private slots:
private:
QComboBox* createJointBox(bool withNone = true) const;
QDoubleSpinBox* createTranslationBox() const;
- void insertJointMapping(QVariantHash& joints, const QString& joint, const QString& name) const;
+ void insertJointMapping(hifi::VariantMultiHash& joints, const QString& joint, const QString& name) const;
- QVariantHash _originalMapping;
+ hifi::VariantMultiHash _originalMapping;
QString _basePath;
HFMModel _hfmModel;
QLineEdit* _name = nullptr;
diff --git a/interface/src/ModelSelector.cpp b/interface/src/ModelSelector.cpp
index 6da9327caca..e855546f071 100644
--- a/interface/src/ModelSelector.cpp
+++ b/interface/src/ModelSelector.cpp
@@ -17,6 +17,7 @@
#include
#include
#include
+#include
ModelSelector::ModelSelector() {
QFormLayout* form = new QFormLayout(this);
@@ -53,7 +54,7 @@ void ModelSelector::browse() {
"Model files (*.fst *.fbx)");
QFileInfo fileInfo(filename);
- if (fileInfo.isFile() && fileInfo.completeSuffix().contains(QRegExp("fst|fbx|FST|FBX"))) {
+ if (fileInfo.isFile() && fileInfo.completeSuffix().contains(QRegularExpression("fst|fbx|FST|FBX"))) {
_modelFile = fileInfo;
_browseButton->setText(fileInfo.fileName());
lastModelBrowseLocation.set(fileInfo.path());
diff --git a/interface/src/ScriptHighlighting.cpp b/interface/src/ScriptHighlighting.cpp
index 1d31f2823b8..a9c0e7500b6 100644
--- a/interface/src/ScriptHighlighting.cpp
+++ b/interface/src/ScriptHighlighting.cpp
@@ -15,14 +15,14 @@
ScriptHighlighting::ScriptHighlighting(QTextDocument* parent) :
QSyntaxHighlighter(parent)
{
- _keywordRegex = QRegExp("\\b(break|case|catch|continue|debugger|default|delete|do|else|finally|for|function|if|in|instanceof|new|return|switch|this|throw|try|typeof|var|void|while|with)\\b");
- _quotedTextRegex = QRegExp("(\"[^\"]*(\"){0,1}|\'[^\']*(\'){0,1})");
- _multiLineCommentBegin = QRegExp("/\\*");
- _multiLineCommentEnd = QRegExp("\\*/");
- _numberRegex = QRegExp("[0-9]+(\\.[0-9]+){0,1}");
- _singleLineComment = QRegExp("//[^\n]*");
- _truefalseRegex = QRegExp("\\b(true|false)\\b");
- _alphacharRegex = QRegExp("[A-Za-z]");
+ _keywordRegex = QRegularExpression("\\b(break|case|catch|continue|debugger|default|delete|do|else|finally|for|function|if|in|instanceof|new|return|switch|this|throw|try|typeof|var|void|while|with)\\b");
+ _quotedTextRegex = QRegularExpression("(\"[^\"]*(\"){0,1}|\'[^\']*(\'){0,1})");
+ _multiLineCommentBegin = QRegularExpression("/\\*");
+ _multiLineCommentEnd = QRegularExpression("\\*/");
+ _numberRegex = QRegularExpression("[0-9]+(\\.[0-9]+){0,1}");
+ _singleLineComment = QRegularExpression("//[^\n]*");
+ _truefalseRegex = QRegularExpression("\\b(true|false)\\b");
+ _alphacharRegex = QRegularExpression("[A-Za-z]");
}
void ScriptHighlighting::highlightBlock(const QString& text) {
@@ -34,11 +34,12 @@ void ScriptHighlighting::highlightBlock(const QString& text) {
}
void ScriptHighlighting::highlightKeywords(const QString& text) {
- int index = _keywordRegex.indexIn(text);
- while (index >= 0) {
- int length = _keywordRegex.matchedLength();
- setFormat(index, length, Qt::blue);
- index = _keywordRegex.indexIn(text, index + length);
+ auto keywordMatch = _keywordRegex.match(text);
+ while (keywordMatch.hasMatch()) {
+ qsizetype index = keywordMatch.capturedStart();
+ qsizetype length = keywordMatch.capturedLength();
+ setFormat(static_cast(index), static_cast(length), Qt::blue);
+ keywordMatch = _keywordRegex.match(text, index + length);
}
}
@@ -46,11 +47,12 @@ void ScriptHighlighting::formatComments(const QString& text) {
setCurrentBlockState(BlockStateClean);
- int start = (previousBlockState() != BlockStateInMultiComment) ? text.indexOf(_multiLineCommentBegin) : 0;
+ qsizetype start = (previousBlockState() != BlockStateInMultiComment) ? text.indexOf(_multiLineCommentBegin) : 0;
while (start > -1) {
- int end = text.indexOf(_multiLineCommentEnd, start);
- int length = (end == -1 ? text.length() : (end + _multiLineCommentEnd.matchedLength())) - start;
+ auto multiLineCommentEndMatch = _multiLineCommentEnd.match(text, start);
+ qsizetype end = multiLineCommentEndMatch.capturedEnd();
+ qsizetype length = (end == qsizetype(-1) ? text.length() : (end + multiLineCommentEndMatch.capturedLength())) - start;
setFormat(start, length, Qt::lightGray);
start = text.indexOf(_multiLineCommentBegin, start + length);
if (end == -1) {
@@ -58,53 +60,63 @@ void ScriptHighlighting::formatComments(const QString& text) {
}
}
- int index = _singleLineComment.indexIn(text);
+ auto singleLineCommentMatch = _singleLineComment.match(text);
+ qsizetype index = singleLineCommentMatch.capturedStart();
while (index >= 0) {
- int length = _singleLineComment.matchedLength();
- int quoted_index = _quotedTextRegex.indexIn(text);
+ qsizetype length = singleLineCommentMatch.capturedLength();
+ auto quotedTextRegexMatch = _quotedTextRegex.match(text);
+ qsizetype quoted_index = quotedTextRegexMatch.capturedStart();
bool valid = true;
while (quoted_index >= 0 && valid) {
- int quoted_length = _quotedTextRegex.matchedLength();
+ qsizetype quoted_length = quotedTextRegexMatch.capturedLength();
if (quoted_index <= index && index <= (quoted_index + quoted_length)) {
valid = false;
}
- quoted_index = _quotedTextRegex.indexIn(text, quoted_index + quoted_length);
+ quotedTextRegexMatch = _quotedTextRegex.match(text, quoted_index + quoted_length);
+ quoted_index = quotedTextRegexMatch.capturedStart();
}
if (valid) {
setFormat(index, length, Qt::lightGray);
}
- index = _singleLineComment.indexIn(text, index + length);
+ singleLineCommentMatch = _singleLineComment.match(text, index + length);
+ index = singleLineCommentMatch.capturedStart();
}
}
void ScriptHighlighting::formatQuotedText(const QString& text){
- int index = _quotedTextRegex.indexIn(text);
+ auto quotedTextRegexMatch = _quotedTextRegex.match(text);
+ qsizetype index = quotedTextRegexMatch.capturedStart();;
while (index >= 0) {
- int length = _quotedTextRegex.matchedLength();
+ qsizetype length = quotedTextRegexMatch.capturedLength();
setFormat(index, length, Qt::red);
- index = _quotedTextRegex.indexIn(text, index + length);
+ quotedTextRegexMatch = _quotedTextRegex.match(text, index + length);
+ index = quotedTextRegexMatch.capturedStart();
}
}
void ScriptHighlighting::formatNumbers(const QString& text){
- int index = _numberRegex.indexIn(text);
+ auto numberRegexMatch = _numberRegex.match(text);
+ qsizetype index = numberRegexMatch.capturedStart();
while (index >= 0) {
- int length = _numberRegex.matchedLength();
- if (index == 0 || _alphacharRegex.indexIn(text, index - 1) != (index - 1)) {
+ qsizetype length = numberRegexMatch.capturedLength();
+ if (index == 0 || text.indexOf(_alphacharRegex, index - 1) != (index - 1)) {
setFormat(index, length, Qt::green);
}
- index = _numberRegex.indexIn(text, index + length);
+ numberRegexMatch = _numberRegex.match(text, index + length);
+ index = numberRegexMatch.capturedStart();
}
}
void ScriptHighlighting::formatTrueFalse(const QString& text){
- int index = _truefalseRegex.indexIn(text);
+ auto trueFalseRegexMatch = _truefalseRegex.match(text);
+ qsizetype index = trueFalseRegexMatch.capturedStart();
while (index >= 0) {
- int length = _truefalseRegex.matchedLength();
+ qsizetype length = trueFalseRegexMatch.capturedLength();
QFont* font = new QFont(this->document()->defaultFont());
font->setBold(true);
setFormat(index, length, *font);
- index = _truefalseRegex.indexIn(text, index + length);
+ trueFalseRegexMatch = _truefalseRegex.match(text, index + length);
+ index = trueFalseRegexMatch.capturedStart();
}
}
diff --git a/interface/src/ScriptHighlighting.h b/interface/src/ScriptHighlighting.h
index 8952b1bcbd2..cfab657f3d0 100644
--- a/interface/src/ScriptHighlighting.h
+++ b/interface/src/ScriptHighlighting.h
@@ -13,6 +13,7 @@
#define hifi_ScriptHighlighting_h
#include
+#include
class ScriptHighlighting : public QSyntaxHighlighter {
Q_OBJECT
@@ -34,14 +35,14 @@ class ScriptHighlighting : public QSyntaxHighlighter {
void formatTrueFalse(const QString& text);
private:
- QRegExp _alphacharRegex;
- QRegExp _keywordRegex;
- QRegExp _quotedTextRegex;
- QRegExp _multiLineCommentBegin;
- QRegExp _multiLineCommentEnd;
- QRegExp _numberRegex;
- QRegExp _singleLineComment;
- QRegExp _truefalseRegex;
+ QRegularExpression _alphacharRegex;
+ QRegularExpression _keywordRegex;
+ QRegularExpression _quotedTextRegex;
+ QRegularExpression _multiLineCommentBegin;
+ QRegularExpression _multiLineCommentEnd;
+ QRegularExpression _numberRegex;
+ QRegularExpression _singleLineComment;
+ QRegularExpression _truefalseRegex;
};
#endif // hifi_ScriptHighlighting_h
diff --git a/interface/src/SecondaryCamera.h b/interface/src/SecondaryCamera.h
index 463034527ac..f7eac000181 100644
--- a/interface/src/SecondaryCamera.h
+++ b/interface/src/SecondaryCamera.h
@@ -21,8 +21,8 @@ class SecondaryCameraJobConfig : public render::Task::Config { // Exposes second
Q_OBJECT
Q_PROPERTY(QUuid attachedEntityId MEMBER attachedEntityId NOTIFY dirty) // entity whose properties define camera position and orientation
Q_PROPERTY(QUuid portalEntranceEntityId MEMBER portalEntranceEntityId NOTIFY dirty) // entity whose properties define a portal's entrance position and orientation
- Q_PROPERTY(glm::vec3 position READ getPosition WRITE setPosition) // of viewpoint to render from
- Q_PROPERTY(glm::quat orientation READ getOrientation WRITE setOrientation) // of viewpoint to render from
+ Q_PROPERTY(glm::vec<3,float,glm::packed_highp> position READ getPosition WRITE setPosition) // of viewpoint to render from
+ Q_PROPERTY(glm::qua orientation READ getOrientation WRITE setOrientation) // of viewpoint to render from
Q_PROPERTY(float vFoV MEMBER vFoV NOTIFY dirty) // Secondary camera's vertical field of view. In degrees.
Q_PROPERTY(float nearClipPlaneDistance MEMBER nearClipPlaneDistance NOTIFY dirty) // Secondary camera's near clip plane distance. In meters.
Q_PROPERTY(float farClipPlaneDistance MEMBER farClipPlaneDistance NOTIFY dirty) // Secondary camera's far clip plane distance. In meters.
@@ -31,8 +31,8 @@ class SecondaryCameraJobConfig : public render::Task::Config { // Exposes second
public:
QUuid attachedEntityId;
QUuid portalEntranceEntityId;
- glm::vec3 position;
- glm::quat orientation;
+ glm::vec<3,float,glm::packed_highp> position;
+ glm::qua orientation;
float vFoV { DEFAULT_FIELD_OF_VIEW_DEGREES };
float nearClipPlaneDistance { DEFAULT_NEAR_CLIP };
float farClipPlaneDistance { DEFAULT_FAR_CLIP };
@@ -45,10 +45,10 @@ class SecondaryCameraJobConfig : public render::Task::Config { // Exposes second
signals:
void dirty();
public slots:
- glm::vec3 getPosition() { return position; }
- void setPosition(glm::vec3 pos);
- glm::quat getOrientation() { return orientation; }
- void setOrientation(glm::quat orient);
+ glm::vec<3,float,glm::packed_highp> getPosition() { return position; }
+ void setPosition(glm::vec<3,float,glm::packed_highp> pos);
+ glm::qua getOrientation() { return orientation; }
+ void setOrientation(glm::qua orient);
void enableSecondaryCameraRenderConfigs(bool enabled);
void resetSizeSpectatorCamera(int width, int height);
};
diff --git a/interface/src/ThemePrefs.cpp b/interface/src/ThemePrefs.cpp
new file mode 100644
index 00000000000..e201375e73f
--- /dev/null
+++ b/interface/src/ThemePrefs.cpp
@@ -0,0 +1,110 @@
+//
+// ThemePrefs.cpp
+// interface/src
+//
+// Created by Ada on 2026-02-22
+// Copyright 2026 Overte e.V.
+//
+// Distributed under the Apache License, Version 2.0.
+// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
+// SPDX-License-Identifier: Apache-2.0
+//
+
+#include
+#include
+
+#include "Application.h"
+#include "ThemePrefs.h"
+
+bool ThemePrefs::getDarkMode() const {
+ QMutexLocker lock(&_mutex);
+
+ if (_useSystemColorScheme.get()) {
+ const auto* styleHints = qApp->styleHints();
+ // default to dark theme if qt doesn't know what to use
+ return styleHints->colorScheme() != Qt::ColorScheme::Light;
+ } else {
+ return _darkMode.get();
+ }
+}
+
+bool ThemePrefs::getHighContrast() const {
+ QMutexLocker lock(&_mutex);
+
+ if (_useSystemContrast.get()) {
+ const auto* a11yHints = qApp->styleHints()->accessibility();
+ return a11yHints->contrastPreference() == Qt::ContrastPreference::HighContrast;
+ } else {
+ return _highContrast.get();
+ }
+}
+
+bool ThemePrefs::getReducedMotion() const {
+ QMutexLocker lock(&_mutex);
+ return _reducedMotion.get();
+}
+
+bool ThemePrefs::getUseSystemColorScheme() const {
+ QMutexLocker lock(&_mutex);
+ return _useSystemColorScheme.get();
+}
+
+bool ThemePrefs::getUseSystemContrast() const {
+ QMutexLocker lock(&_mutex);
+ return _useSystemContrast.get();
+}
+
+void ThemePrefs::setDarkMode(bool value) {
+ QMutexLocker lock(&_mutex);
+
+ auto old = _darkMode.get();
+
+ if (value != old) {
+ _darkMode.set(value);
+ emit themeChanged();
+ }
+}
+
+void ThemePrefs::setHighContrast(bool value) {
+ QMutexLocker lock(&_mutex);
+
+ auto old = _highContrast.get();
+
+ if (old != value) {
+ _highContrast.set(value);
+ emit themeChanged();
+ }
+}
+
+void ThemePrefs::setReducedMotion(bool value) {
+ QMutexLocker lock(&_mutex);
+
+ auto old = _reducedMotion.get();
+
+ if (old != value) {
+ _reducedMotion.set(value);
+ emit themeChanged();
+ }
+}
+
+void ThemePrefs::setUseSystemColorScheme(bool value) {
+ QMutexLocker lock(&_mutex);
+
+ auto old = _useSystemColorScheme.get();
+
+ if (old != value) {
+ _useSystemColorScheme.set(value);
+ emit themeChanged();
+ }
+}
+
+void ThemePrefs::setUseSystemContrast(bool value) {
+ QMutexLocker lock(&_mutex);
+
+ auto old = _useSystemContrast.get();
+
+ if (old != value) {
+ _useSystemContrast.set(value);
+ emit themeChanged();
+ }
+}
diff --git a/interface/src/ThemePrefs.h b/interface/src/ThemePrefs.h
new file mode 100644
index 00000000000..d6b774bd91c
--- /dev/null
+++ b/interface/src/ThemePrefs.h
@@ -0,0 +1,64 @@
+//
+// ThemePrefs.h
+// interface/src
+//
+// Created by Ada on 2026-02-22
+// Copyright 2026 Overte e.V.
+//
+// Distributed under the Apache License, Version 2.0.
+// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
+// SPDX-License-Identifier: Apache-2.0
+//
+
+#ifndef hifi_ThemePrefs_h
+#define hifi_ThemePrefs_h
+
+#include
+
+#include
+
+class ThemePrefs : public QObject {
+ Q_OBJECT
+ Q_PROPERTY(bool darkMode READ getDarkMode WRITE setDarkMode NOTIFY themeChanged)
+ Q_PROPERTY(bool highContrast READ getHighContrast WRITE setHighContrast NOTIFY themeChanged)
+ Q_PROPERTY(bool reducedMotion READ getReducedMotion WRITE setReducedMotion NOTIFY themeChanged)
+
+ Q_PROPERTY(bool useSystemColorScheme READ getUseSystemColorScheme WRITE setUseSystemColorScheme NOTIFY themeChanged)
+ Q_PROPERTY(bool useSystemContrast READ getUseSystemContrast WRITE setUseSystemContrast NOTIFY themeChanged)
+
+public:
+ ThemePrefs(QObject* parent = nullptr) : QObject(parent) {}
+
+ // returns the appropriate values depending on whether we're
+ // using the system preferences or custom ones
+ bool getDarkMode() const;
+ bool getHighContrast() const;
+ bool getReducedMotion() const;
+
+ bool getUseSystemColorScheme() const;
+ bool getUseSystemContrast() const;
+
+ void setDarkMode(bool value);
+ void setHighContrast(bool value);
+ void setReducedMotion(bool value);
+
+ void setUseSystemColorScheme(bool value);
+ void setUseSystemContrast(bool value);
+
+signals:
+ void themeChanged();
+
+private:
+ mutable QRecursiveMutex _mutex;
+
+ Setting::Handle _darkMode { "Theme/darkMode", true };
+ Setting::Handle _highContrast { "Theme/highContrast", true };
+ Setting::Handle _reducedMotion { "Theme/reducedMotion", true };
+ Setting::Handle _useSystemColorScheme { "Theme/useSystemColorScheme", true };
+ Setting::Handle _useSystemContrast { "Theme/useSystemContrast", true };
+ // It looks like Qt doesn't have a system reduced motion setting?
+ // KDE has one, but it modifies an internal KDE setting, not a Qt one.
+ //Setting::Handle _useSystemReducedMotion { "Theme/useSystemReducedMotion", true };
+};
+
+#endif // hifi_ThemePrefs_h
diff --git a/interface/src/WindowsSystemInfo.h b/interface/src/WindowsSystemInfo.h
index 7c8a1d3e270..3a83ae2764e 100644
--- a/interface/src/WindowsSystemInfo.h
+++ b/interface/src/WindowsSystemInfo.h
@@ -185,7 +185,7 @@ void initCpuUsage() {
memcpy(&lastUserCPU, &fuser, sizeof(FILETIME));
PdhOpenQuery(NULL, NULL, &cpuQuery);
- PdhAddCounter(cpuQuery, "\\Processor(_Total)\\% Processor Time", NULL, &cpuTotal);
+ PdhAddCounter(cpuQuery, L"\\Processor(_Total)\\% Processor Time", NULL, &cpuTotal);
PdhCollectQueryData(cpuQuery);
}
diff --git a/interface/src/avatar/AvatarActionHold.cpp b/interface/src/avatar/AvatarActionHold.cpp
index 36b1020990a..c7d206b1eb2 100644
--- a/interface/src/avatar/AvatarActionHold.cpp
+++ b/interface/src/avatar/AvatarActionHold.cpp
@@ -378,7 +378,7 @@ bool AvatarActionHold::updateArguments(QVariantMap arguments) {
}
ok = true;
- holderID = EntityDynamicInterface::extractStringArgument("hold", arguments, "holderID", ok, false);
+ holderID = QUuid(EntityDynamicInterface::extractStringArgument("hold", arguments, "holderID", ok, false));
if (!ok) {
auto myAvatar = DependencyManager::get()->getMyAvatar();
holderID = myAvatar->getSessionUUID();
diff --git a/interface/src/avatar/AvatarDoctor.cpp b/interface/src/avatar/AvatarDoctor.cpp
index a46652c7d9b..4af52489a11 100644
--- a/interface/src/avatar/AvatarDoctor.cpp
+++ b/interface/src/avatar/AvatarDoctor.cpp
@@ -90,8 +90,8 @@ void AvatarDoctor::startDiagnosing() {
return;
}
_model = resource;
- const auto model = resource.data();
- const auto avatarModel = resource.data()->getHFMModel();
+ const auto model = resource.get();
+ const auto avatarModel = resource.get()->getHFMModel();
if (!avatarModel.originalURL.toLower().endsWith(".fbx")) {
addError("Unsupported avatar model format.", "unsupported-format");
emit complete(getErrors());
@@ -230,7 +230,7 @@ void AvatarDoctor::startDiagnosing() {
auto mapping = resource->getMapping();
- if (mapping.contains(JOINT_NAME_MAPPING_FIELD) && mapping[JOINT_NAME_MAPPING_FIELD].type() == QVariant::Hash) {
+ if (mapping.contains(JOINT_NAME_MAPPING_FIELD) && mapping[JOINT_NAME_MAPPING_FIELD].typeId() == QMetaType::QVariantHash) {
const auto& jointNameMappings = mapping[JOINT_NAME_MAPPING_FIELD].toHash();
QStringList jointValues;
for (const auto& jointVariant: jointNameMappings.values()) {
@@ -277,7 +277,7 @@ void AvatarDoctor::startDiagnosing() {
if (materialMappingResource->isLoaded()) {
materialMappingHandled();
} else {
- connect(materialMappingResource.data(), &NetworkTexture::finished, this,
+ connect(materialMappingResource.get(), &NetworkTexture::finished, this,
[materialMappingHandled](bool success) mutable {
materialMappingHandled();
@@ -297,7 +297,7 @@ void AvatarDoctor::startDiagnosing() {
if (resource->isLoaded()) {
resourceLoaded(!resource->isFailed());
} else {
- connect(resource.data(), &GeometryResource::finished, this, resourceLoaded);
+ connect(resource.get(), &GeometryResource::finished, this, resourceLoaded);
}
} else {
addError("Model file cannot be opened", "missing-file");
@@ -306,8 +306,8 @@ void AvatarDoctor::startDiagnosing() {
}
void AvatarDoctor::diagnoseTextures() {
- const auto model = _model.data();
- const auto avatarModel = _model.data()->getHFMModel();
+ const auto model = _model.get();
+ const auto avatarModel = _model.get()->getHFMModel();
QVector externalTextures{};
QVector textureNames{};
int texturesFound = 0;
@@ -344,7 +344,7 @@ void AvatarDoctor::diagnoseTextures() {
}
for (const auto& materialMapping : model->getMaterialMapping()) {
- for (const auto& networkMaterial : materialMapping.second.data()->parsedMaterials.networkMaterials) {
+ for (const auto& networkMaterial : materialMapping.second.get()->parsedMaterials.networkMaterials) {
texturesFound += (int)networkMaterial.second->getTextureMaps().size();
}
}
@@ -405,7 +405,7 @@ void AvatarDoctor::diagnoseTextures() {
if (textureResource->isLoaded()) {
textureLoaded(!textureResource->isFailed());
} else {
- connect(textureResource.data(), &NetworkTexture::finished, this, textureLoaded);
+ connect(textureResource.get(), &NetworkTexture::finished, this, textureLoaded);
}
} else {
_missingTextureCount++;
diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp
index 6a4b7955f0d..e4dbabcf349 100644
--- a/interface/src/avatar/AvatarManager.cpp
+++ b/interface/src/avatar/AvatarManager.cpp
@@ -768,11 +768,11 @@ RayToAvatarIntersectionResult AvatarManager::findRayIntersectionVector(const Pic
RayToAvatarIntersectionResult result;
if (QThread::currentThread() != thread()) {
BLOCKING_INVOKE_METHOD(const_cast(this), "findRayIntersectionVector",
- Q_RETURN_ARG(RayToAvatarIntersectionResult, result),
- Q_ARG(const PickRay&, ray),
- Q_ARG(const QVector&, avatarsToInclude),
- Q_ARG(const QVector&, avatarsToDiscard),
- Q_ARG(bool, pickAgainstMesh));
+ Q_GENERIC_RETURN_ARG(RayToAvatarIntersectionResult, result),
+ Q_GENERIC_ARG(const PickRay&, ray),
+ Q_GENERIC_ARG(const QVector&, avatarsToInclude),
+ Q_GENERIC_ARG(const QVector&, avatarsToDiscard),
+ Q_GENERIC_ARG(bool, pickAgainstMesh));
return result;
}
@@ -897,10 +897,10 @@ ParabolaToAvatarIntersectionResult AvatarManager::findParabolaIntersectionVector
ParabolaToAvatarIntersectionResult result;
if (QThread::currentThread() != thread()) {
BLOCKING_INVOKE_METHOD(const_cast(this), "findParabolaIntersectionVector",
- Q_RETURN_ARG(ParabolaToAvatarIntersectionResult, result),
- Q_ARG(const PickParabola&, pick),
- Q_ARG(const QVector&, avatarsToInclude),
- Q_ARG(const QVector&, avatarsToDiscard));
+ Q_GENERIC_RETURN_ARG(ParabolaToAvatarIntersectionResult, result),
+ Q_GENERIC_ARG(const PickParabola&, pick),
+ Q_GENERIC_ARG(const QVector&, avatarsToInclude),
+ Q_GENERIC_ARG(const QVector&, avatarsToDiscard));
return result;
}
diff --git a/interface/src/avatar/AvatarProject.cpp b/interface/src/avatar/AvatarProject.cpp
index cf41c0a0407..6a73ea59a25 100644
--- a/interface/src/avatar/AvatarProject.cpp
+++ b/interface/src/avatar/AvatarProject.cpp
@@ -110,7 +110,7 @@ AvatarProject* AvatarProject::createAvatarProject(const QString& projectsFolder,
try {
const QByteArray fbxContents = fbx.readAll();
- hfmModel = FBXSerializer().read(fbxContents, QVariantHash(), fbxInfo.filePath());
+ hfmModel = FBXSerializer().read(fbxContents, hifi::VariantMultiHash(), fbxInfo.filePath());
} catch (const QString& error) {
Q_UNUSED(error)
status = AvatarProjectStatus::ERROR_CREATE_READ_MODEL;
diff --git a/interface/src/avatar/DetailedMotionState.cpp b/interface/src/avatar/DetailedMotionState.cpp
index a22d533bc95..4ea1d40b525 100644
--- a/interface/src/avatar/DetailedMotionState.cpp
+++ b/interface/src/avatar/DetailedMotionState.cpp
@@ -133,7 +133,7 @@ const QUuid DetailedMotionState::getObjectID() const {
}
QString DetailedMotionState::getName() const {
- return _avatar->getName() + "_" + _jointIndex;
+ return _avatar->getName() + "_" + QString::number(_jointIndex);
}
// virtual
diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp
index c4b000a802b..0e14bd16353 100644
--- a/interface/src/avatar/MyAvatar.cpp
+++ b/interface/src/avatar/MyAvatar.cpp
@@ -1246,7 +1246,7 @@ void MyAvatar::restoreHandAnimation(bool isLeft) {
QStringList MyAvatar::getAnimationRoles() {
if (QThread::currentThread() != thread()) {
QStringList result;
- BLOCKING_INVOKE_METHOD(this, "getAnimationRoles", Q_RETURN_ARG(QStringList, result));
+ BLOCKING_INVOKE_METHOD(this, "getAnimationRoles", Q_GENERIC_RETURN_ARG(QStringList, result));
return result;
}
return _skeletonModel->getRig().getAnimationRoles();
@@ -2016,7 +2016,13 @@ void MyAvatar::updateAvatarEntity(const QUuid& entityID, const QByteArray& entit
bool changed = false;
_avatarEntitiesLock.withWriteLock([&] {
- auto data = QJsonDocument(QCborValue::fromCbor(entityData).toJsonValue().toObject());
+ QCborParserError cborError;
+ auto cbor = QCborValue::fromCbor(entityData, &cborError);
+ if (cborError.error != QCborError::NoError) {
+ qWarning() << "MyAvatar::updateAvatarEntity: corrupted cbor" << QString(entityData.toHex());
+ return;
+ }
+ auto data = QJsonDocument(cbor.toJsonValue().toObject());
if (data.isEmpty() || data.isNull() || !data.isObject() || data.object().isEmpty()) {
qDebug() << "ERROR! Trying to update with invalid avatar entity data. Skipping." << data;
} else {
@@ -2313,7 +2319,7 @@ const float SCRIPT_PRIORITY = 1.0f + 1.0f;
const float RECORDER_PRIORITY = 1.0f + 1.0f;
void MyAvatar::setJointRotations(const QVector& jointRotations) {
- int numStates = glm::min(_skeletonModel->getJointStateCount(), jointRotations.size());
+ int numStates = glm::min(static_cast(_skeletonModel->getJointStateCount()), jointRotations.size());
for (int i = 0; i < numStates; ++i) {
// HACK: ATM only Recorder calls setJointRotations() so we hardcode its priority here
_skeletonModel->setJointRotation(i, true, jointRotations[i], RECORDER_PRIORITY);
@@ -2624,8 +2630,8 @@ void MyAvatar::useFullAvatarURL(const QUrl& fullAvatarURL, const QString& modelN
if (QThread::currentThread() != thread()) {
BLOCKING_INVOKE_METHOD(this, "useFullAvatarURL",
- Q_ARG(const QUrl&, fullAvatarURL),
- Q_ARG(const QString&, modelName));
+ Q_GENERIC_ARG(const QUrl&, fullAvatarURL),
+ Q_GENERIC_ARG(const QString&, modelName));
return;
}
@@ -4088,7 +4094,7 @@ bool MyAvatar::safeLanding(const glm::vec3& position) {
if (QThread::currentThread() != thread()) {
bool result;
- BLOCKING_INVOKE_METHOD(this, "safeLanding", Q_RETURN_ARG(bool, result), Q_ARG(const glm::vec3&, position));
+ BLOCKING_INVOKE_METHOD(this, "safeLanding", Q_GENERIC_RETURN_ARG(bool, result), Q_GENERIC_ARG(const glm::vec3&, position));
return result;
}
glm::vec3 better;
@@ -6162,7 +6168,7 @@ QVariantMap MyAvatar::getFlowData() {
QVariantMap result;
if (QThread::currentThread() != thread()) {
BLOCKING_INVOKE_METHOD(this, "getFlowData",
- Q_RETURN_ARG(QVariantMap, result));
+ Q_GENERIC_RETURN_ARG(QVariantMap, result));
return result;
}
if (_skeletonModel->isLoaded()) {
@@ -6230,7 +6236,7 @@ QVariantList MyAvatar::getCollidingFlowJoints() {
QVariantList result;
if (QThread::currentThread() != thread()) {
BLOCKING_INVOKE_METHOD(this, "getCollidingFlowJoints",
- Q_RETURN_ARG(QVariantList, result));
+ Q_GENERIC_RETURN_ARG(QVariantList, result));
return result;
}
@@ -6549,7 +6555,7 @@ void MyAvatar::updateHeadLookAt(float deltaTime) {
void MyAvatar::setHeadLookAt(const glm::vec3& lookAtTarget) {
if (QThread::currentThread() != thread()) {
BLOCKING_INVOKE_METHOD(this, "setHeadLookAt",
- Q_ARG(const glm::vec3&, lookAtTarget));
+ Q_GENERIC_ARG(const glm::vec3&, lookAtTarget));
return;
}
_headLookAtActive = true;
@@ -6561,7 +6567,7 @@ void MyAvatar::setHeadLookAt(const glm::vec3& lookAtTarget) {
void MyAvatar::setEyesLookAt(const glm::vec3& lookAtTarget) {
if (QThread::currentThread() != thread()) {
BLOCKING_INVOKE_METHOD(this, "setEyesLookAt",
- Q_ARG(const glm::vec3&, lookAtTarget));
+ Q_GENERIC_ARG(const glm::vec3&, lookAtTarget));
return;
}
_eyesLookAtTarget.set(lookAtTarget);
@@ -6658,8 +6664,8 @@ bool MyAvatar::getHMDCrouchRecenterEnabled() const {
bool MyAvatar::setPointAt(const glm::vec3& pointAtTarget) {
if (QThread::currentThread() != thread()) {
bool result = false;
- BLOCKING_INVOKE_METHOD(this, "setPointAt", Q_RETURN_ARG(bool, result),
- Q_ARG(const glm::vec3&, pointAtTarget));
+ BLOCKING_INVOKE_METHOD(this, "setPointAt", Q_GENERIC_RETURN_ARG(bool, result),
+ Q_GENERIC_ARG(const glm::vec3&, pointAtTarget));
return result;
}
if (_skeletonModel->isLoaded() && _pointAtActive) {
@@ -6689,3 +6695,37 @@ float MyAvatar::getCameraSensitivity() const {
void MyAvatar::setCameraSensitivity(float cameraSensitivity) {
qApp->getCamera().setSensitivity(cameraSensitivity);
}
+
+MyAvatar::TabletInputMode MyAvatar::getTabletInputMode() {
+ auto prefersStylus = qApp->getPreferStylusOverLaser();
+ auto prefersFinger = qApp->getPreferAvatarFingerOverStylus();
+
+ if (prefersFinger) {
+ return TabletInputMode::AvatarFingers;
+ } else if (prefersStylus) {
+ return TabletInputMode::Styluses;
+ } else {
+ return TabletInputMode::Lasers;
+ }
+}
+
+void MyAvatar::setTabletInputMode(MyAvatar::TabletInputMode mode) {
+ switch (mode) {
+ case TabletInputMode::Lasers:
+ qApp->setPreferStylusOverLaser(false);
+ qApp->setPreferAvatarFingerOverStylus(false);
+ break;
+
+ case TabletInputMode::Styluses:
+ qApp->setPreferStylusOverLaser(true);
+ qApp->setPreferAvatarFingerOverStylus(false);
+ break;
+
+ case TabletInputMode::AvatarFingers:
+ qApp->setPreferStylusOverLaser(true);
+ qApp->setPreferAvatarFingerOverStylus(true);
+ break;
+ }
+
+ emit tabletInputModeChanged(mode);
+}
diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h
index fa8c7153d44..f911113a450 100644
--- a/interface/src/avatar/MyAvatar.h
+++ b/interface/src/avatar/MyAvatar.h
@@ -326,9 +326,9 @@ class MyAvatar : public Avatar {
Q_PROPERTY(QVector3D qmlPosition READ getQmlPosition)
QVector3D getQmlPosition() { auto p = getWorldPosition(); return QVector3D(p.x, p.y, p.z); }
- Q_PROPERTY(glm::vec3 feetPosition READ getWorldFeetPosition WRITE goToFeetLocation)
+ Q_PROPERTY(glm::vec<3,float,glm::packed_highp> feetPosition READ getWorldFeetPosition WRITE goToFeetLocation)
Q_PROPERTY(bool shouldRenderLocally READ getShouldRenderLocally WRITE setShouldRenderLocally)
- Q_PROPERTY(glm::vec3 motorVelocity READ getScriptedMotorVelocity WRITE setScriptedMotorVelocity)
+ Q_PROPERTY(glm::vec<3,float,glm::packed_highp> motorVelocity READ getScriptedMotorVelocity WRITE setScriptedMotorVelocity)
Q_PROPERTY(float motorTimescale READ getScriptedMotorTimescale WRITE setScriptedMotorTimescale)
Q_PROPERTY(QString motorReferenceFrame READ getScriptedMotorFrame WRITE setScriptedMotorFrame)
Q_PROPERTY(QString motorMode READ getScriptedMotorMode WRITE setScriptedMotorMode)
@@ -337,18 +337,18 @@ class MyAvatar : public Avatar {
Q_PROPERTY(AudioListenerMode audioListenerModeHead READ getAudioListenerModeHead)
Q_PROPERTY(AudioListenerMode audioListenerModeCamera READ getAudioListenerModeCamera)
Q_PROPERTY(AudioListenerMode audioListenerModeCustom READ getAudioListenerModeCustom)
- Q_PROPERTY(glm::vec3 customListenPosition READ getCustomListenPosition WRITE setCustomListenPosition)
- Q_PROPERTY(glm::quat customListenOrientation READ getCustomListenOrientation WRITE setCustomListenOrientation)
+ Q_PROPERTY(glm::vec<3,float,glm::packed_highp> customListenPosition READ getCustomListenPosition WRITE setCustomListenPosition)
+ Q_PROPERTY(glm::qua customListenOrientation READ getCustomListenOrientation WRITE setCustomListenOrientation)
Q_PROPERTY(float rotationRecenterFilterLength READ getRotationRecenterFilterLength WRITE setRotationRecenterFilterLength)
Q_PROPERTY(float rotationThreshold READ getRotationThreshold WRITE setRotationThreshold)
Q_PROPERTY(bool enableStepResetRotation READ getEnableStepResetRotation WRITE setEnableStepResetRotation)
Q_PROPERTY(bool enableDrawAverageFacing READ getEnableDrawAverageFacing WRITE setEnableDrawAverageFacing)
//TODO: make gravity feature work Q_PROPERTY(glm::vec3 gravity READ getGravity WRITE setGravity)
- Q_PROPERTY(glm::vec3 leftHandPosition READ getLeftHandPosition)
- Q_PROPERTY(glm::vec3 rightHandPosition READ getRightHandPosition)
- Q_PROPERTY(glm::vec3 leftHandTipPosition READ getLeftHandTipPosition)
- Q_PROPERTY(glm::vec3 rightHandTipPosition READ getRightHandTipPosition)
+ Q_PROPERTY(glm::vec<3,float,glm::packed_highp> leftHandPosition READ getLeftHandPosition)
+ Q_PROPERTY(glm::vec<3,float,glm::packed_highp> rightHandPosition READ getRightHandPosition)
+ Q_PROPERTY(glm::vec<3,float,glm::packed_highp> leftHandTipPosition READ getLeftHandTipPosition)
+ Q_PROPERTY(glm::vec<3,float,glm::packed_highp> rightHandTipPosition READ getRightHandTipPosition)
Q_PROPERTY(controller::Pose leftHandPose READ getLeftHandPose)
Q_PROPERTY(controller::Pose rightHandPose READ getRightHandPose)
@@ -388,6 +388,8 @@ class MyAvatar : public Avatar {
Q_PROPERTY(bool allowTeleporting READ getAllowTeleporting)
Q_PROPERTY(float cameraBoomLength MEMBER _boomLength)
+ Q_PROPERTY(MyAvatar::TabletInputMode tabletInputMode READ getTabletInputMode WRITE setTabletInputMode NOTIFY tabletInputModeChanged)
+
const QString DOMINANT_LEFT_HAND = "left";
const QString DOMINANT_RIGHT_HAND = "right";
const QString DEFAULT_HMD_AVATAR_ALIGNMENT_TYPE = "head";
@@ -563,6 +565,13 @@ class MyAvatar : public Avatar {
static const std::array allowAvatarStandingPreferenceStrings;
static const std::array allowAvatarLeaningPreferenceStrings;
+ enum class TabletInputMode : uint {
+ Lasers,
+ Styluses,
+ AvatarFingers,
+ };
+ Q_ENUM(TabletInputMode);
+
explicit MyAvatar(QThread* thread);
virtual ~MyAvatar();
@@ -604,9 +613,9 @@ class MyAvatar : public Avatar {
virtual void postUpdate(float deltaTime, const render::ScenePointer& scene) override;
void preDisplaySide(const RenderArgs* renderArgs);
- const glm::mat4& getHMDSensorMatrix() const { return _hmdSensorMatrix; }
- const glm::vec3& getHMDSensorPosition() const { return _hmdSensorPosition; }
- const glm::quat& getHMDSensorOrientation() const { return _hmdSensorOrientation; }
+ const glm::mat<4,4,float,glm::packed_highp>& getHMDSensorMatrix() const { return _hmdSensorMatrix; }
+ const glm::vec<3,float,glm::packed_highp>& getHMDSensorPosition() const { return _hmdSensorPosition; }
+ const glm::qua& getHMDSensorOrientation() const { return _hmdSensorOrientation; }
/*@jsdoc
* Gets the avatar orientation. Suitable for use in QML.
@@ -623,18 +632,18 @@ class MyAvatar : public Avatar {
Q_INVOKABLE QVariant getOrientationVar() const;
// A method intended to be overriden by MyAvatar for polling orientation for network transmission.
- glm::quat getOrientationOutbound() const override;
+ glm::qua getOrientationOutbound() const override;
// Pass a recent sample of the HMD to the avatar.
// This can also update the avatar's position to follow the HMD
// as it moves through the world.
- void updateFromHMDSensorMatrix(const glm::mat4& hmdSensorMatrix);
+ void updateFromHMDSensorMatrix(const glm::mat<4,4,float,glm::packed_highp>& hmdSensorMatrix);
// compute the hip to hand average azimuth.
- glm::vec2 computeHandAzimuth() const;
+ glm::vec<2,float,glm::packed_highp> computeHandAzimuth() const;
// read the location of a hand controller and save the transform
- void updateJointFromController(controller::Action poseKey, ThreadSafeValueCache& matrixCache);
+ void updateJointFromController(controller::Action poseKey, ThreadSafeValueCache>& matrixCache);
// best called at end of main loop, just before rendering.
// update sensor to world matrix from current body position and hmd sensor.
@@ -653,7 +662,7 @@ class MyAvatar : public Avatar {
* var defaultEyePosition = MyAvatar.getDefaultEyePosition();
* print(JSON.stringify(defaultEyePosition));
*/
- Q_INVOKABLE glm::vec3 getDefaultEyePosition() const;
+ Q_INVOKABLE glm::vec<3,float,glm::packed_highp> getDefaultEyePosition() const;
float getRealWorldFieldOfView() { return _realWorldFieldOfView.get(); }
@@ -1020,6 +1029,9 @@ class MyAvatar : public Avatar {
bool getAllowTeleporting() { return _allowTeleportingSetting.get(); }
void setAllowTeleporting(bool allowTeleporting) { _allowTeleportingSetting.set(allowTeleporting); }
+ MyAvatar::TabletInputMode getTabletInputMode();
+ void setTabletInputMode(MyAvatar::TabletInputMode mode);
+
bool getShowPlayArea() const { return _showPlayArea.get(); }
void setShowPlayArea(bool showPlayArea) { _showPlayArea.set(showPlayArea); }
@@ -1123,7 +1135,7 @@ class MyAvatar : public Avatar {
* @example Report the current position of your avatar's head.
* print(JSON.stringify(MyAvatar.getHeadPosition()));
*/
- Q_INVOKABLE glm::vec3 getHeadPosition() const { return getHead()->getPosition(); }
+ Q_INVOKABLE glm::vec<3,float,glm::packed_highp> getHeadPosition() const { return getHead()->getPosition(); }
/*@jsdoc
* Gets the yaw of the avatar's head relative to its body.
@@ -1163,7 +1175,7 @@ class MyAvatar : public Avatar {
* var eyePosition = MyAvatar.getEyePosition();
* print(JSON.stringify(eyePosition));
*/
- Q_INVOKABLE glm::vec3 getEyePosition() const { return getHead()->getEyePosition(); }
+ Q_INVOKABLE glm::vec<3,float,glm::packed_highp> getEyePosition() const { return getHead()->getEyePosition(); }
/*@jsdoc
* Gets the position of the avatar your avatar is currently looking at.
@@ -1174,7 +1186,7 @@ class MyAvatar : public Avatar {
*/
// FIXME: If not looking at an avatar, the most recently looked-at position is returned. This should be fixed to return
// undefined or {NaN, NaN, NaN} or similar.
- Q_INVOKABLE glm::vec3 getTargetAvatarPosition() const { return _targetAvatarPosition; }
+ Q_INVOKABLE glm::vec<3,float,glm::packed_highp> getTargetAvatarPosition() const { return _targetAvatarPosition; }
/*@jsdoc
* Gets information on the avatar your avatar is currently looking at.
@@ -1195,7 +1207,7 @@ class MyAvatar : public Avatar {
* @example Report the position of your left hand relative to your avatar.
* print(JSON.stringify(MyAvatar.getLeftHandPosition()));
*/
- Q_INVOKABLE glm::vec3 getLeftHandPosition() const;
+ Q_INVOKABLE glm::vec<3,float,glm::packed_highp> getLeftHandPosition() const;
/*@jsdoc
* Gets the position of the avatar's right hand, relative to the avatar, as positioned by a hand controller (e.g., Oculus
@@ -1208,7 +1220,7 @@ class MyAvatar : public Avatar {
* @example Report the position of your right hand relative to your avatar.
* print(JSON.stringify(MyAvatar.getLeftHandPosition()));
*/
- Q_INVOKABLE glm::vec3 getRightHandPosition() const;
+ Q_INVOKABLE glm::vec<3,float,glm::packed_highp> getRightHandPosition() const;
/*@jsdoc
* Gets the position 0.3m in front of the left hand's position in the direction along the palm, in avatar coordinates, as
@@ -1217,7 +1229,7 @@ class MyAvatar : public Avatar {
* @returns {Vec3} The position 0.3m in front of the left hand's position in the direction along the palm, in avatar
* coordinates. If the hand isn't being positioned by a controller, {@link Vec3(0)|Vec3.ZERO} is returned.
*/
- Q_INVOKABLE glm::vec3 getLeftHandTipPosition() const;
+ Q_INVOKABLE glm::vec<3,float,glm::packed_highp> getLeftHandTipPosition() const;
/*@jsdoc
* Gets the position 0.3m in front of the right hand's position in the direction along the palm, in avatar coordinates, as
@@ -1226,7 +1238,7 @@ class MyAvatar : public Avatar {
* @returns {Vec3} The position 0.3m in front of the right hand's position in the direction along the palm, in avatar
* coordinates. If the hand isn't being positioned by a controller, {@link Vec3(0)|Vec3.ZERO} is returned.
*/
- Q_INVOKABLE glm::vec3 getRightHandTipPosition() const;
+ Q_INVOKABLE glm::vec<3,float,glm::packed_highp> getRightHandTipPosition() const;
/*@jsdoc
@@ -1285,15 +1297,15 @@ class MyAvatar : public Avatar {
void snapOtherAvatarLookAtTargetsToMe(const AvatarHash& hash);
void clearLookAtTargetAvatar();
- virtual void setJointRotations(const QVector& jointRotations) override;
- virtual void setJointData(int index, const glm::quat& rotation, const glm::vec3& translation) override;
- virtual void setJointRotation(int index, const glm::quat& rotation) override;
- virtual void setJointTranslation(int index, const glm::vec3& translation) override;
+ virtual void setJointRotations(const QVector>& jointRotations) override;
+ virtual void setJointData(int index, const glm::qua& rotation, const glm::vec<3,float,glm::packed_highp>& translation) override;
+ virtual void setJointRotation(int index, const glm::qua& rotation) override;
+ virtual void setJointTranslation(int index, const glm::vec<3,float,glm::packed_highp>& translation) override;
virtual void clearJointData(int index) override;
- virtual void setJointData(const QString& name, const glm::quat& rotation, const glm::vec3& translation) override;
- virtual void setJointRotation(const QString& name, const glm::quat& rotation) override;
- virtual void setJointTranslation(const QString& name, const glm::vec3& translation) override;
+ virtual void setJointData(const QString& name, const glm::qua& rotation, const glm::vec<3,float,glm::packed_highp>& translation) override;
+ virtual void setJointRotation(const QString& name, const glm::qua& rotation) override;
+ virtual void setJointTranslation(const QString& name, const glm::vec<3,float,glm::packed_highp>& translation) override;
virtual void clearJointData(const QString& name) override;
virtual void clearJointsData() override;
@@ -1306,7 +1318,7 @@ class MyAvatar : public Avatar {
* @param {Quat} orientation - The orientation of the joint in world coordinates.
* @returns {boolean} true if the joint was pinned, false if it wasn't.
*/
- Q_INVOKABLE bool pinJoint(int index, const glm::vec3& position, const glm::quat& orientation);
+ Q_INVOKABLE bool pinJoint(int index, const glm::vec<3,float,glm::packed_highp>& position, const glm::qua& orientation);
bool isJointPinned(int index);
@@ -1359,7 +1371,7 @@ class MyAvatar : public Avatar {
void updateMotors();
void prepareForPhysicsSimulation();
- void nextAttitude(glm::vec3 position, glm::quat orientation); // Can be safely called at any time.
+ void nextAttitude(glm::vec<3,float,glm::packed_highp> position, glm::qua orientation); // Can be safely called at any time.
void harvestResultsFromPhysicsSimulation(float deltaTime);
const QString& getCollisionSoundURL() { return _collisionSoundURL; }
@@ -1393,10 +1405,10 @@ class MyAvatar : public Avatar {
AudioListenerMode getAudioListenerMode() { return _audioListenerMode; }
void setAudioListenerMode(AudioListenerMode audioListenerMode);
- glm::vec3 getCustomListenPosition() { return _customListenPosition; }
- void setCustomListenPosition(glm::vec3 customListenPosition) { _customListenPosition = customListenPosition; }
- glm::quat getCustomListenOrientation() { return _customListenOrientation; }
- void setCustomListenOrientation(glm::quat customListenOrientation) { _customListenOrientation = customListenOrientation; }
+ glm::vec<3,float,glm::packed_highp> getCustomListenPosition() { return _customListenPosition; }
+ void setCustomListenPosition(glm::vec<3,float,glm::packed_highp> customListenPosition) { _customListenPosition = customListenPosition; }
+ glm::qua getCustomListenOrientation() { return _customListenOrientation; }
+ void setCustomListenOrientation(glm::qua customListenOrientation) { _customListenOrientation = customListenOrientation; }
virtual void rebuildCollisionShape() override;
@@ -1408,8 +1420,8 @@ class MyAvatar : public Avatar {
void setHeadControllerFacingMovingAverage(glm::vec2 currentHeadControllerFacing) { _headControllerFacingMovingAverage = currentHeadControllerFacing; }
float getCurrentStandingHeight() const { return _currentStandingHeight; }
void setCurrentStandingHeight(float newMode) { _currentStandingHeight = newMode; }
- const glm::quat getAverageHeadRotation() const { return _averageHeadRotation; }
- void setAverageHeadRotation(glm::quat rotation) { _averageHeadRotation = rotation; }
+ const glm::qua getAverageHeadRotation() const { return _averageHeadRotation; }
+ void setAverageHeadRotation(glm::qua rotation) { _averageHeadRotation = rotation; }
bool getResetMode() const { return _resetMode; }
void setResetMode(bool hasBeenReset) { _resetMode = hasBeenReset; }
@@ -1601,7 +1613,7 @@ class MyAvatar : public Avatar {
* var headRotation = MyAvatar.getAbsoluteJointRotationInObjectFrame(headIndex);
* print("Head rotation: " + JSON.stringify(Quat.safeEulerAngles(headRotation))); // Degrees
*/
- virtual glm::quat getAbsoluteJointRotationInObjectFrame(int index) const override;
+ virtual glm::qua getAbsoluteJointRotationInObjectFrame(int index) const override;
/*@jsdoc
* Gets the translation of a joint relative to the avatar.
@@ -1614,19 +1626,19 @@ class MyAvatar : public Avatar {
* var headTranslation = MyAvatar.getAbsoluteJointTranslationInObjectFrame(headIndex);
* print("Head translation: " + JSON.stringify(headTranslation));
*/
- virtual glm::vec3 getAbsoluteJointTranslationInObjectFrame(int index) const override;
+ virtual glm::vec<3,float,glm::packed_highp> getAbsoluteJointTranslationInObjectFrame(int index) const override;
// all calibration matrices are in absolute sensor space.
- glm::mat4 getCenterEyeCalibrationMat() const;
- glm::mat4 getHeadCalibrationMat() const;
- glm::mat4 getSpine2CalibrationMat() const;
- glm::mat4 getHipsCalibrationMat() const;
- glm::mat4 getLeftFootCalibrationMat() const;
- glm::mat4 getRightFootCalibrationMat() const;
- glm::mat4 getRightArmCalibrationMat() const;
- glm::mat4 getLeftArmCalibrationMat() const;
- glm::mat4 getLeftHandCalibrationMat() const;
- glm::mat4 getRightHandCalibrationMat() const;
+ glm::mat<4,4,float,glm::packed_highp> getCenterEyeCalibrationMat() const;
+ glm::mat<4,4,float,glm::packed_highp> getHeadCalibrationMat() const;
+ glm::mat<4,4,float,glm::packed_highp> getSpine2CalibrationMat() const;
+ glm::mat<4,4,float,glm::packed_highp> getHipsCalibrationMat() const;
+ glm::mat<4,4,float,glm::packed_highp> getLeftFootCalibrationMat() const;
+ glm::mat<4,4,float,glm::packed_highp> getRightFootCalibrationMat() const;
+ glm::mat<4,4,float,glm::packed_highp> getRightArmCalibrationMat() const;
+ glm::mat<4,4,float,glm::packed_highp> getLeftArmCalibrationMat() const;
+ glm::mat<4,4,float,glm::packed_highp> getLeftHandCalibrationMat() const;
+ glm::mat<4,4,float,glm::packed_highp> getRightHandCalibrationMat() const;
void addHoldAction(AvatarActionHold* holdAction); // thread-safe
void removeHoldAction(AvatarActionHold* holdAction); // thread-safe
@@ -1635,16 +1647,16 @@ class MyAvatar : public Avatar {
// derive avatar body position and orientation from the current HMD Sensor location.
// results are in sensor frame (-z forward)
- glm::mat4 deriveBodyFromHMDSensor(const bool forceFollowYPos = false) const;
+ glm::mat<4,4,float,glm::packed_highp> deriveBodyFromHMDSensor(const bool forceFollowYPos = false) const;
- glm::mat4 getSpine2RotationRigSpace() const;
+ glm::mat<4,4,float,glm::packed_highp> getSpine2RotationRigSpace() const;
- glm::vec3 computeCounterBalance();
+ glm::vec<3,float,glm::packed_highp> computeCounterBalance();
// derive avatar body position and orientation from using the current HMD Sensor location in relation to the previous
// location of the base of support of the avatar.
// results are in sensor frame (-z foward)
- glm::mat4 deriveBodyUsingCgModel();
+ glm::mat<4,4,float,glm::packed_highp> deriveBodyUsingCgModel();
/*@jsdoc
* Tests whether a vector is pointing in the general direction of the avatar's "up" direction (i.e., dot product of vectors
@@ -1654,7 +1666,7 @@ class MyAvatar : public Avatar {
* @returns {boolean} true if the direction vector is pointing generally in the direction of the avatar's "up"
* direction.
*/
- Q_INVOKABLE bool isUp(const glm::vec3& direction) { return glm::dot(direction, _worldUpDirection) > 0.0f; }; // true iff direction points up wrt avatar's definition of up.
+ Q_INVOKABLE bool isUp(const glm::vec<3,float,glm::packed_highp>& direction) { return glm::dot(direction, _worldUpDirection) > 0.0f; }; // true iff direction points up wrt avatar's definition of up.
/*@jsdoc
* Tests whether a vector is pointing in the general direction of the avatar's "down" direction (i.e., dot product of
@@ -1664,14 +1676,14 @@ class MyAvatar : public Avatar {
* @returns {boolean} true if the direction vector is pointing generally in the direction of the avatar's
* "down" direction.
*/
- Q_INVOKABLE bool isDown(const glm::vec3& direction) { return glm::dot(direction, _worldUpDirection) < 0.0f; };
+ Q_INVOKABLE bool isDown(const glm::vec<3,float,glm::packed_highp>& direction) { return glm::dot(direction, _worldUpDirection) < 0.0f; };
void setUserHeight(float value);
float getUserHeight() const;
float getUserEyeHeight() const;
virtual SpatialParentTree* getParentTree() const override;
- virtual glm::vec3 scaleForChildren() const override { return glm::vec3(getSensorToWorldScale()); }
+ virtual glm::vec<3,float,glm::packed_highp> scaleForChildren() const override { return glm::vec3(getSensorToWorldScale()); }
const QUuid& getSelfID() const { return AVATAR_SELF_ID; }
@@ -1702,9 +1714,9 @@ class MyAvatar : public Avatar {
bool isReadyForPhysics() const;
float computeStandingHeightMode(const controller::Pose& head);
- glm::quat computeAverageHeadRotation(const controller::Pose& head);
+ glm::qua computeAverageHeadRotation(const controller::Pose& head);
- glm::vec3 getNextPosition() { return _goToPending ? _goToPosition : getWorldPosition(); }
+ glm::vec<3,float,glm::packed_highp> getNextPosition() { return _goToPending ? _goToPosition : getWorldPosition(); }
void prepareAvatarEntityDataForReload();
/*@jsdoc
@@ -1714,14 +1726,14 @@ class MyAvatar : public Avatar {
* @function MyAvatar.setHeadLookAt
* @param {Vec3} lookAtTarget - The target point in world coordinates.
*/
- Q_INVOKABLE void setHeadLookAt(const glm::vec3& lookAtTarget);
+ Q_INVOKABLE void setHeadLookAt(const glm::vec<3,float,glm::packed_highp>& lookAtTarget);
/*@jsdoc
* Gets the current target point of the head's look direction in world coordinates.
* @function MyAvatar.getHeadLookAt
* @returns {Vec3} The head's look-at target in world coordinates.
*/
- Q_INVOKABLE glm::vec3 getHeadLookAt() { return _lookAtCameraTarget; }
+ Q_INVOKABLE glm::vec<3,float,glm::packed_highp> getHeadLookAt() { return _lookAtCameraTarget; }
/*@jsdoc
* Returns control of the avatar's head to the engine, and releases control from API calls.
@@ -1736,14 +1748,14 @@ class MyAvatar : public Avatar {
* @function MyAvatar.setEyesLookAt
* @param {Vec3} lookAtTarget - The target point in world coordinates.
*/
- Q_INVOKABLE void setEyesLookAt(const glm::vec3& lookAtTarget);
+ Q_INVOKABLE void setEyesLookAt(const glm::vec<3,float,glm::packed_highp>& lookAtTarget);
/*@jsdoc
* Gets the current target point of the eyes look direction in world coordinates.
* @function MyAvatar.getEyesLookAt
* @returns {Vec3} The eyes' look-at target in world coordinates.
*/
- Q_INVOKABLE glm::vec3 getEyesLookAt() { return _eyesLookAtTarget.get(); }
+ Q_INVOKABLE glm::vec<3,float,glm::packed_highp> getEyesLookAt() { return _eyesLookAtTarget.get(); }
/*@jsdoc
* Returns control of the avatar's eyes to the engine, and releases control from API calls.
@@ -1759,9 +1771,9 @@ class MyAvatar : public Avatar {
* @param {Vec3} pointAtTarget - The target to point at, in world coordinates.
* @returns {boolean} true if the target point was set, false if it wasn't.
*/
- Q_INVOKABLE bool setPointAt(const glm::vec3& pointAtTarget);
+ Q_INVOKABLE bool setPointAt(const glm::vec<3,float,glm::packed_highp>& pointAtTarget);
- glm::quat getLookAtRotation() { return _lookAtYaw * _lookAtPitch; }
+ glm::qua getLookAtRotation() { return _lookAtYaw * _lookAtPitch; }
/*@jsdoc
* Creates a new grab that grabs an entity.
@@ -1791,7 +1803,7 @@ class MyAvatar : public Avatar {
* }, 10000);
*/
Q_INVOKABLE const QUuid grab(const QUuid& targetID, int parentJointIndex,
- glm::vec3 positionalOffset, glm::quat rotationalOffset);
+ glm::vec<3,float,glm::packed_highp> positionalOffset, glm::qua rotationalOffset);
/*@jsdoc
* Releases (deletes) a grab to stop grabbing an entity.
@@ -1871,7 +1883,7 @@ class MyAvatar : public Avatar {
* @param {Vec3} position - The position where the avatar should sit.
* @param {Quat} rotation - The initial orientation of the seated avatar.
*/
- Q_INVOKABLE void beginSit(const glm::vec3& position, const glm::quat& rotation);
+ Q_INVOKABLE void beginSit(const glm::vec<3,float,glm::packed_highp>& position, const glm::qua& rotation);
/*@jsdoc
* Ends a sitting action for the avatar.
@@ -1879,7 +1891,7 @@ class MyAvatar : public Avatar {
* @param {Vec3} position - The position of the avatar when standing up.
* @param {Quat} rotation - The orientation of the avatar when standing up.
*/
- Q_INVOKABLE void endSit(const glm::vec3& position, const glm::quat& rotation);
+ Q_INVOKABLE void endSit(const glm::vec<3,float,glm::packed_highp>& position, const glm::qua& rotation);
/*@jsdoc
* Gets whether the avatar is in a seated pose. The seated pose is set by calling {@link MyAvatar.beginSit}.
@@ -1904,8 +1916,8 @@ class MyAvatar : public Avatar {
void debugDrawPose(controller::Action action, const char* channelName, float size);
bool getIsJointOverridden(int jointIndex) const;
- glm::vec3 getLookAtPivotPoint();
- glm::vec3 getCameraEyesPosition(float deltaTime);
+ glm::vec<3,float,glm::packed_highp> getLookAtPivotPoint();
+ glm::vec<3,float,glm::packed_highp> getCameraEyesPosition(float deltaTime);
bool isJumping();
bool getHMDCrouchRecenterEnabled() const;
bool isAllowedToLean() const;
@@ -1982,8 +1994,8 @@ public slots:
* @param {boolean} [shouldFaceLocation=false] - Set to