From 876cf602e74d36781e4cbf5f2e8cc8e8f4c272ae Mon Sep 17 00:00:00 2001 From: Karol Suprynowicz Date: Sat, 6 Sep 2025 20:45:45 +0200 Subject: [PATCH 001/111] Initial upgrade to Qt6 --- CMakeLists.txt | 4 +- .../apps/interface/src/main/cpp/native.cpp | 2 +- .../src/AssignmentClientMonitor.cpp | 3 +- assignment-client/src/assets/AssetServer.cpp | 3 +- .../src/avatars/ScriptableAvatar.cpp | 2 +- .../PackageLibrariesForDeployment.cmake | 2 +- cmake/macros/SetupHifiLibrary.cmake | 6 +- cmake/macros/SetupHifiProject.cmake | 6 +- cmake/macros/SetupHifiTestCase.cmake | 4 +- cmake/macros/TargetQuazip.cmake | 4 +- .../modules/FindQt5LinguistToolsMacros.cmake | 10 +- conanfile.py | 3 +- .../src/ContentSettingsBackupHandler.cpp | 3 +- .../src/DomainContentBackupManager.cpp | 7 +- domain-server/src/DomainGatekeeper.cpp | 9 +- domain-server/src/DomainServer.cpp | 15 +- .../src/DomainServerSettingsManager.cpp | 22 +-- .../src/DomainServerSettingsManager.h | 10 +- domain-server/src/EntitiesBackupHandler.cpp | 2 +- interface/CMakeLists.txt | 16 +- interface/resources/qml/AudioScopeUI.qml | 2 +- interface/resources/qml/CurrentAPI.qml | 2 +- .../+android_interface/LinkAccountBody.qml | 2 +- .../+android_interface/SignUpBody.qml | 2 +- .../qml/LoginDialog/LinkAccountBody.qml | 2 +- .../qml/LoginDialog/LoggingInBody.qml | 2 +- .../resources/qml/LoginDialog/SignUpBody.qml | 2 +- .../qml/LoginDialog/UsernameCollisionBody.qml | 2 +- interface/resources/qml/OverlayWindowTest.qml | 2 +- interface/resources/qml/controls/ComboBox.qml | 2 +- .../resources/qml/controls/TextField.qml | 2 +- .../qml/controlsUit/AttachmentsTable.qml | 2 +- interface/resources/qml/controlsUit/Table.qml | 2 +- .../resources/qml/controlsUit/TextField.qml | 2 +- interface/resources/qml/controlsUit/Tree.qml | 2 +- interface/resources/qml/desktop/Desktop.qml | 2 +- .../resources/qml/dialogs/FileDialog.qml | 2 +- .../qml/dialogs/TabletFileDialog.qml | 2 +- .../qml/dialogs/preferences/Section.qml | 2 +- .../qml/hifi/+android_interface/ActionBar.qml | 2 +- .../qml/hifi/+android_interface/AudioBar.qml | 2 +- .../qml/hifi/+android_interface/Desktop.qml | 2 +- .../qml/hifi/+android_interface/StatsBar.qml | 2 +- .../hifi/+android_interface/WindowHeader.qml | 2 +- .../+android_interface/bottomHudOptions.qml | 2 +- .../qml/hifi/+android_interface/button.qml | 2 +- .../qml/hifi/+android_interface/modesbar.qml | 2 +- interface/resources/qml/hifi/AssetServer.qml | 2 +- interface/resources/qml/hifi/NameCard.qml | 2 +- interface/resources/qml/hifi/Pal.qml | 2 +- .../qml/hifi/dialogs/RunningScripts.qml | 2 +- .../qml/hifi/dialogs/TabletAssetServer.qml | 2 +- .../qml/hifi/dialogs/TabletRunningScripts.qml | 2 +- .../resources/qml/hifi/tablet/TabletMenu.qml | 2 +- .../qml/hifi/tablet/TabletMenuItem.qml | 2 +- .../qml/hifi/tablet/TabletMenuStack.qml | 2 +- .../tablet/tabletWindows/TabletFileDialog.qml | 2 +- interface/src/AndroidHelper.cpp | 2 +- interface/src/Application.cpp | 14 +- interface/src/Application.h | 8 +- interface/src/Application_Assets.cpp | 3 +- interface/src/Application_Events.cpp | 8 +- interface/src/Application_Graphics.cpp | 8 +- interface/src/Application_Setup.cpp | 3 + interface/src/Application_UI.cpp | 4 +- interface/src/ArchiveDownloadInterface.cpp | 2 +- interface/src/AvatarBookmarks.cpp | 12 +- interface/src/DiscoverabilityManager.cpp | 2 +- interface/src/LocationBookmarks.cpp | 5 +- interface/src/Menu.cpp | 1 + interface/src/ModelPackager.cpp | 7 +- interface/src/ModelPackager.h | 2 +- interface/src/ModelPropertiesDialog.cpp | 14 +- interface/src/ModelPropertiesDialog.h | 8 +- interface/src/ModelSelector.cpp | 3 +- interface/src/ScriptHighlighting.cpp | 76 +++++--- interface/src/ScriptHighlighting.h | 17 +- interface/src/avatar/AvatarActionHold.cpp | 2 +- interface/src/avatar/AvatarManager.cpp | 18 +- interface/src/avatar/AvatarProject.cpp | 2 +- interface/src/avatar/DetailedMotionState.cpp | 2 +- interface/src/avatar/MyAvatar.cpp | 22 +-- interface/src/networking/CloseEventSender.h | 1 + interface/src/raypick/CollisionPick.cpp | 4 +- .../src/raypick/PointerScriptingInterface.cpp | 2 +- .../AssetMappingsScriptingInterface.cpp | 9 +- interface/src/scripting/AudioDevices.cpp | 82 ++++---- interface/src/scripting/AudioDevices.h | 20 +- .../scripting/ClipboardScriptingInterface.cpp | 36 ++-- .../scripting/DesktopScriptingInterface.cpp | 8 +- .../src/scripting/MenuScriptingInterface.cpp | 32 +-- .../src/scripting/TestScriptingInterface.cpp | 2 +- .../scripting/WindowScriptingInterface.cpp | 10 +- interface/src/ui/BaseLogDialog.cpp | 11 +- interface/src/ui/HMDToolsDialog.cpp | 1 - interface/src/ui/InteractiveWindow.cpp | 2 +- interface/src/ui/InteractiveWindow.h | 2 +- interface/src/ui/JSConsole.cpp | 11 +- interface/src/ui/ModelsBrowser.cpp | 1 + interface/src/ui/ResourceImageItem.cpp | 3 +- interface/src/ui/Snapshot.cpp | 3 +- interface/src/ui/StandAloneJSConsole.cpp | 3 +- interface/src/ui/Stats.cpp | 2 +- interface/src/ui/UpdateDialog.cpp | 5 +- interface/src/ui/overlays/Overlays.cpp | 16 +- libraries/animation/src/AnimExpression.h | 1 + libraries/animation/src/AnimationCache.cpp | 6 +- libraries/animation/src/Flow.cpp | 2 + libraries/animation/src/Flow.h | 3 + libraries/audio-client/src/AudioClient.cpp | 145 +++++++------- libraries/audio-client/src/AudioClient.h | 24 +-- libraries/audio-client/src/AudioFileWav.cpp | 6 +- .../audio-client/src/HifiAudioDeviceInfo.cpp | 4 +- .../audio-client/src/HifiAudioDeviceInfo.h | 23 +-- libraries/audio/src/InboundAudioStream.h | 4 +- libraries/auto-updater/src/AutoUpdater.h | 2 +- libraries/avatars/src/AvatarData.cpp | 3 +- libraries/baking/src/JSBaker.cpp | 2 +- libraries/baking/src/MaterialBaker.cpp | 2 +- libraries/baking/src/ModelBaker.cpp | 6 +- libraries/baking/src/ModelBaker.h | 4 +- .../src/controllers/UserInputMapper.h | 6 +- .../Basic2DWindowOpenGLDisplayPlugin.cpp | 2 +- .../display-plugins/OpenGLDisplayPlugin.cpp | 2 +- .../stereo/StereoDisplayPlugin.cpp | 3 +- .../embedded-webserver/src/HTTPManager.cpp | 1 + .../src/RenderableModelEntityItem.cpp | 5 +- .../src/RenderableWebEntityItem.cpp | 21 +- libraries/entities/src/EntityTree.cpp | 14 +- libraries/gl/CMakeLists.txt | 2 +- libraries/gl/src/gl/ContextQt.cpp | 2 +- libraries/gl/src/gl/GLHelpers.cpp | 2 +- libraries/gl/src/gl/GLWidget.cpp | 2 +- libraries/gl/src/gl/GLWidget.h | 2 +- libraries/gl/src/gl/OffscreenGLCanvas.cpp | 2 +- libraries/gl/src/gl/QOpenGLContextWrapper.cpp | 5 +- .../GraphicsScriptingInterface.h | 3 +- .../GraphicsScriptingUtil.cpp | 2 +- .../graphics-scripting/ScriptableMeshPart.h | 5 +- .../graphics-scripting/ScriptableModel.cpp | 3 +- libraries/hfm/src/hfm/HFMSerializer.h | 2 +- .../src/input-plugins/KeyboardMouseDevice.cpp | 2 +- .../src/input-plugins/TouchscreenDevice.cpp | 10 +- .../src/input-plugins/TouchscreenDevice.h | 1 - .../TouchscreenVirtualPadDevice.cpp | 9 +- .../TouchscreenVirtualPadDevice.h | 1 - .../src/material-networking/TextureCache.cpp | 2 +- libraries/midi/src/Midi.h | 1 + .../model-baker/src/model-baker/Baker.cpp | 2 +- libraries/model-baker/src/model-baker/Baker.h | 2 +- .../src/model-networking/ModelCache.cpp | 18 +- .../src/model-networking/ModelCache.h | 6 +- .../src/model-networking/ModelLoader.cpp | 2 +- .../src/model-networking/ModelLoader.h | 2 +- .../model-serializers/src/FBXSerializer.cpp | 14 +- .../model-serializers/src/FBXSerializer.h | 4 +- libraries/model-serializers/src/FBXToJSON.h | 4 +- libraries/model-serializers/src/FBXWriter.cpp | 1 + libraries/model-serializers/src/FST.cpp | 6 +- libraries/model-serializers/src/FST.h | 2 +- libraries/model-serializers/src/FSTReader.cpp | 12 +- libraries/model-serializers/src/FSTReader.h | 4 +- .../model-serializers/src/GLTFSerializer.cpp | 7 +- .../model-serializers/src/GLTFSerializer.h | 4 +- .../model-serializers/src/OBJSerializer.cpp | 4 +- .../model-serializers/src/OBJSerializer.h | 4 +- libraries/model-serializers/src/OBJWriter.cpp | 4 +- libraries/networking/src/AccountManager.cpp | 4 +- libraries/networking/src/AddressManager.cpp | 3 +- libraries/networking/src/AssetClient.cpp | 2 +- .../networking/src/AssetResourceRequest.cpp | 1 + libraries/networking/src/AssetUtils.cpp | 1 + .../src/BaseAssetScriptingInterface.cpp | 3 +- libraries/networking/src/LimitedNodeList.cpp | 2 +- libraries/networking/src/NodePermissions.cpp | 8 +- libraries/networking/src/ResourceCache.cpp | 6 +- .../UserActivityLoggerScriptingInterface.cpp | 2 + .../networking/src/udt/NetworkSocket.cpp | 2 + .../networking/src/udt/PacketHeaders.cpp | 1 + .../src/plugins/InputConfiguration.cpp | 18 +- libraries/qml/src/qml/OffscreenSurface.cpp | 2 + .../qml/src/qml/impl/RenderEventHandler.cpp | 3 +- libraries/qml/src/qml/impl/SharedObject.cpp | 10 +- .../recording/src/recording/ClipCache.cpp | 2 +- .../recording/RecordingScriptingInterface.cpp | 2 +- libraries/render-utils/CMakeLists.txt | 2 +- libraries/render-utils/src/Model.cpp | 2 +- libraries/render-utils/src/text/Font.cpp | 2 +- .../src/SceneScriptingInterface.h | 2 + libraries/script-engine/src/ScriptCache.cpp | 2 +- libraries/script-engine/src/ScriptCache.h | 2 + libraries/script-engine/src/ScriptEngines.cpp | 14 +- libraries/script-engine/src/ScriptEngines.h | 2 +- libraries/script-engine/src/ScriptManager.cpp | 9 +- libraries/script-engine/src/ScriptMessage.cpp | 2 +- .../script-engine/src/ScriptValueUtils.cpp | 3 +- libraries/script-engine/src/ScriptsModel.cpp | 1 + .../script-engine/src/ScriptsModelFilter.cpp | 2 +- libraries/script-engine/src/TouchEvent.cpp | 20 +- .../src/UsersScriptingInterface.h | 2 + .../script-engine/src/v8/ScriptEngineV8.cpp | 12 +- .../src/v8/ScriptEngineV8_cast.cpp | 4 +- .../src/v8/ScriptObjectV8Proxy.cpp | 11 +- .../src/v8/ScriptValueV8Wrapper.cpp | 2 +- libraries/shared/src/ApplicationVersion.cpp | 2 +- libraries/shared/src/Grab.cpp | 2 + libraries/shared/src/HifiConfigVariantMap.cpp | 7 +- libraries/shared/src/LogHandler.h | 2 +- libraries/shared/src/PathUtils.cpp | 6 +- libraries/shared/src/RegisteredMetaTypes.h | 7 +- libraries/shared/src/RunningMarker.cpp | 2 +- libraries/shared/src/SettingHelpers.cpp | 1 + libraries/shared/src/SharedUtil.cpp | 6 +- libraries/shared/src/SharedUtil.h | 1 + .../shared/src/ShutdownEventListener.cpp | 2 +- libraries/shared/src/ShutdownEventListener.h | 2 +- libraries/shared/src/ThreadHelpers.h | 1 + libraries/shared/src/shared/FileLogger.cpp | 1 + libraries/shared/src/shared/FileUtils.cpp | 3 +- libraries/shared/src/shared/JSONHelpers.cpp | 4 +- libraries/shared/src/shared/PlatformHelper.h | 1 + libraries/shared/src/shared/QtHelpers.h | 36 ++++ libraries/shared/src/shared/StringHelpers.cpp | 3 +- libraries/task/src/task/Config.h | 20 +- .../src/ui-plugins/PluginContainer.cpp | 1 + libraries/ui/CMakeLists.txt | 2 +- libraries/ui/src/DesktopPreviewProvider.h | 1 + libraries/ui/src/InfoView.cpp | 16 +- libraries/ui/src/OffscreenUi.cpp | 184 +++++++++--------- libraries/ui/src/QmlFragmentClass.cpp | 2 +- libraries/ui/src/QmlWebWindowClass.cpp | 2 +- libraries/ui/src/QmlWindowClass.cpp | 8 +- libraries/ui/src/QmlWindowClass.h | 2 +- libraries/ui/src/Tooltip.cpp | 1 + libraries/ui/src/ui/Menu.cpp | 6 +- libraries/ui/src/ui/OffscreenQmlSurface.cpp | 96 +++++---- libraries/ui/src/ui/OffscreenQmlSurface.h | 6 +- libraries/ui/src/ui/QmlWrapper.cpp | 4 +- .../ui/src/ui/TabletScriptingInterface.cpp | 32 +-- .../ui/src/ui/ToolbarScriptingInterface.cpp | 4 +- .../ui/src/ui/types/ContextAwareProfile.cpp | 2 +- .../ui/src/ui/types/ContextAwareProfile.h | 2 +- libraries/ui/src/ui/types/FileTypeProfile.cpp | 2 +- .../ui/src/ui/types/HFWebEngineProfile.cpp | 2 +- plugins/JSAPIExample/src/JSAPIExample.cpp | 1 + plugins/steamClient/src/SteamAPIPlugin.cpp | 1 + script-archive/floofChat/FloofChat.qml | 2 +- .../armored_chat_quick_message.qml | 2 +- scripts/developer/debugging/debugHaze.qml | 2 +- scripts/developer/debugging/debugWindow.qml | 2 +- .../debugging/surfaceGeometryPass.qml | 2 +- .../developer/tests/playaPerformanceTest.qml | 2 +- scripts/developer/tests/textureStress.qml | 2 +- scripts/developer/utilities/audio/Jitter.qml | 2 +- .../developer/utilities/audio/MovingValue.qml | 2 +- scripts/developer/utilities/audio/Section.qml | 2 +- scripts/developer/utilities/audio/Stats.qml | 2 +- scripts/developer/utilities/audio/Stream.qml | 2 +- .../developer/utilities/audio/TabletStats.qml | 2 +- scripts/developer/utilities/audio/Value.qml | 2 +- scripts/developer/utilities/cache/stats.qml | 2 +- .../utilities/lib/jet/qml/TaskList.qml | 2 +- .../utilities/lib/jet/qml/TaskListView.qml | 2 +- .../utilities/lib/jet/qml/TaskPropView.qml | 2 +- .../lib/jet/qml/TaskTimeFrameView.qml | 2 +- .../utilities/lib/plotperf/Color.qml | 2 +- .../utilities/lib/plotperf/PlotPerf.qml | 2 +- .../utilities/render/ambientOcclusionPass.qml | 2 +- .../render/configSlider/ConfigSlider.qml | 2 +- .../render/configSlider/RichSlider.qml | 2 +- .../developer/utilities/render/engineList.qml | 2 +- .../utilities/render/engineProfiler.qml | 2 +- .../developer/utilities/render/highlight.qml | 2 +- .../render/highlight/HighlightStyle.qml | 2 +- .../utilities/render/lightClustering.qml | 2 +- scripts/developer/utilities/render/lod.qml | 2 +- .../utilities/render/luci/Antialiasing.qml | 2 +- .../utilities/render/luci/Culling.qml | 2 +- scripts/developer/utilities/render/rates.qml | 2 +- scripts/developer/utilities/render/shadow.qml | 2 +- scripts/developer/utilities/render/stats.qml | 2 +- .../developer/utilities/render/statsGPU.qml | 2 +- .../utilities/render/subsurfaceScattering.qml | 2 +- .../utilities/render/surfaceGeometryPass.qml | 2 +- .../utilities/render/textureMonitor.qml | 2 +- .../developer/utilities/render/transition.qml | 2 +- .../developer/utilities/workload/avatars.qml | 2 +- .../utilities/workload/workloadInspector.qml | 2 +- tests-manual/ui/qml/ControlsGalleryWindow.qml | 2 +- tests/audio/src/AudioTests.cpp | 6 +- tests/networking/src/ResourceTests.cpp | 2 +- tools/nitpick/CMakeLists.txt | 6 +- tools/nitpick/src/AWSInterface.cpp | 2 +- tools/oven/src/DomainBaker.cpp | 2 +- tools/skeleton-dump/src/SkeletonDumpApp.cpp | 2 +- 295 files changed, 1068 insertions(+), 861 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 43eb5858a1c..9590b9f2d73 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -141,7 +141,7 @@ if (ANDROID) add_definitions(-DANDROID_APP_INTERFACE) endif() else () - set(PLATFORM_QT_COMPONENTS WebEngine Xml) + set(PLATFORM_QT_COMPONENTS WebEngineCore Xml) endif() @@ -168,7 +168,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/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/AssignmentClientMonitor.cpp b/assignment-client/src/AssignmentClientMonitor.cpp index 026b2e3dae8..36e1a892293 100644 --- a/assignment-client/src/AssignmentClientMonitor.cpp +++ b/assignment-client/src/AssignmentClientMonitor.cpp @@ -288,7 +288,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..fc5f576ddbc 100644 --- a/assignment-client/src/avatars/ScriptableAvatar.cpp +++ b/assignment-client/src/avatars/ScriptableAvatar.cpp @@ -71,7 +71,7 @@ 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; diff --git a/cmake/macros/PackageLibrariesForDeployment.cmake b/cmake/macros/PackageLibrariesForDeployment.cmake index 65d8e200618..1c78adf8783 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}) 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..873769ef027 100644 --- a/cmake/macros/TargetQuazip.cmake +++ b/cmake/macros/TargetQuazip.cmake @@ -8,11 +8,11 @@ macro(TARGET_QUAZIP) 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) + find_package(QuaZip-Qt6 REQUIRED) 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 8f5abfba046..e183d0cab09 100644 --- a/conanfile.py +++ b/conanfile.py @@ -73,7 +73,8 @@ def requirements(self): self.requires("openxr/1.1.46@overte/stable") self.requires("opus/1.4") self.requires("polyvox/2025.09.19@overte/experimental#76ce908c1078988dceae5ad32ead2909") - self.requires("quazip/1.4") + # QT6TODO + #self.requires("quazip/1.4") self.requires("scribe/2019.02@overte/stable") self.requires("sdl/2.32.8") self.requires("spirv-cross/1.3.268.0") 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..004cfce6e49 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::fromUInt128(0))); 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::fromUInt128(0))); 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..6feed867665 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 @@ -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()) { @@ -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/DomainServerSettingsManager.cpp b/domain-server/src/DomainServerSettingsManager.cpp index fe13c29df21..e740615938a 100644 --- a/domain-server/src/DomainServerSettingsManager.cpp +++ b/domain-server/src/DomainServerSettingsManager.cpp @@ -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::fromUInt128(0))].reset(new NodePermissions(allowedUser)); + _agentPermissions[NodePermissionsKey(allowedUser, QUuid::fromUInt128(0))]->set(NodePermissions::Permission::canConnectToDomain); } foreach (QString allowedEditor, allowedEditors) { - NodePermissionsKey editorKey(allowedEditor, 0); + NodePermissionsKey editorKey(allowedEditor, QUuid::fromUInt128(0)); if (!_agentPermissions.contains(editorKey)) { _agentPermissions[editorKey].reset(new NodePermissions(allowedEditor)); if (isRestrictedAccess) { @@ -969,7 +969,7 @@ void DomainServerSettingsManager::processNodeKickRequestPacket(QSharedPointergetMachineFingerprint().toString(), 0); + NodePermissionsKey machineFingerprintKey(nodeData->getMachineFingerprint().toString(), QUuid::fromUInt128(0)); // 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::fromUInt128(0)); 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::fromUInt128(0)); 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::fromUInt128(0)); 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::fromUInt128(0)); if (_machineFingerprintPermissions.contains(fingerprintKey)) { return *(_machineFingerprintPermissions[fingerprintKey].get()); } @@ -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..7cd70b14e15 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::fromUInt128(0)); } 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::fromUInt128(0)); } 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::fromUInt128(0)); } 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::fromUInt128(0)); } 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::fromUInt128(0)); } 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/interface/CMakeLists.txt b/interface/CMakeLists.txt index 92ac16c68bd..49a4cd90aa9 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) @@ -297,9 +297,9 @@ endif () 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::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/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/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/LoginDialog/+android_interface/LinkAccountBody.qml b/interface/resources/qml/LoginDialog/+android_interface/LinkAccountBody.qml index 8570273bde7..cea9b188769 100644 --- a/interface/resources/qml/LoginDialog/+android_interface/LinkAccountBody.qml +++ b/interface/resources/qml/LoginDialog/+android_interface/LinkAccountBody.qml @@ -10,7 +10,7 @@ import Hifi 1.0 import QtQuick 2.4 -import QtQuick.Controls 1.4 +import QtQuick.Controls 2.3 import QtQuick.Controls.Styles 1.4 as OriginalStyles import controlsUit 1.0 diff --git a/interface/resources/qml/LoginDialog/+android_interface/SignUpBody.qml b/interface/resources/qml/LoginDialog/+android_interface/SignUpBody.qml index f2329c065c5..cfd750bf305 100644 --- a/interface/resources/qml/LoginDialog/+android_interface/SignUpBody.qml +++ b/interface/resources/qml/LoginDialog/+android_interface/SignUpBody.qml @@ -10,7 +10,7 @@ import Hifi 1.0 import QtQuick 2.4 -import QtQuick.Controls 1.4 +import QtQuick.Controls 2.3 import QtQuick.Controls.Styles 1.4 as OriginalStyles import controlsUit 1.0 diff --git a/interface/resources/qml/LoginDialog/LinkAccountBody.qml b/interface/resources/qml/LoginDialog/LinkAccountBody.qml index 25aa7504ee3..b9b2677c486 100644 --- a/interface/resources/qml/LoginDialog/LinkAccountBody.qml +++ b/interface/resources/qml/LoginDialog/LinkAccountBody.qml @@ -11,7 +11,7 @@ import Hifi 1.0 import QtQuick 2.7 -import QtQuick.Controls 1.4 +import QtQuick.Controls 2.3 import QtQuick.Controls.Styles 1.4 as OriginalStyles import "." as LoginDialog diff --git a/interface/resources/qml/LoginDialog/LoggingInBody.qml b/interface/resources/qml/LoginDialog/LoggingInBody.qml index 1c8250cfa8e..00c9bc96833 100644 --- a/interface/resources/qml/LoginDialog/LoggingInBody.qml +++ b/interface/resources/qml/LoginDialog/LoggingInBody.qml @@ -12,7 +12,7 @@ import Hifi 1.0 import QtQuick 2.7 -import QtQuick.Controls 1.4 +import QtQuick.Controls 2.3 import QtQuick.Controls.Styles 1.4 as OriginalStyles import controlsUit 1.0 as HifiControlsUit diff --git a/interface/resources/qml/LoginDialog/SignUpBody.qml b/interface/resources/qml/LoginDialog/SignUpBody.qml index b1335232110..38ede9f27a9 100644 --- a/interface/resources/qml/LoginDialog/SignUpBody.qml +++ b/interface/resources/qml/LoginDialog/SignUpBody.qml @@ -11,7 +11,7 @@ import Hifi 1.0 import QtQuick 2.7 -import QtQuick.Controls 1.4 +import QtQuick.Controls 2.3 import QtQuick.Controls.Styles 1.4 as OriginalStyles import controlsUit 1.0 as HifiControlsUit 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/controls/ComboBox.qml b/interface/resources/qml/controls/ComboBox.qml index 960ae8127aa..309542f6bba 100644 --- a/interface/resources/qml/controls/ComboBox.qml +++ b/interface/resources/qml/controls/ComboBox.qml @@ -1,5 +1,5 @@ import QtQuick 2.5 -import QtQuick.Controls 1.4 +import QtQuick.Controls 2.3 import QtQuick.Controls.Styles 1.4 import "." as VrControls diff --git a/interface/resources/qml/controls/TextField.qml b/interface/resources/qml/controls/TextField.qml index df536a1de53..09b065a20ed 100644 --- a/interface/resources/qml/controls/TextField.qml +++ b/interface/resources/qml/controls/TextField.qml @@ -1,5 +1,5 @@ import QtQuick 2.5 -import QtQuick.Controls 1.4 +import QtQuick.Controls 2.3 import QtQuick.Controls.Styles 1.4 TextField { diff --git a/interface/resources/qml/controlsUit/AttachmentsTable.qml b/interface/resources/qml/controlsUit/AttachmentsTable.qml index a2677962daa..949c1c2e42b 100644 --- a/interface/resources/qml/controlsUit/AttachmentsTable.qml +++ b/interface/resources/qml/controlsUit/AttachmentsTable.qml @@ -9,7 +9,7 @@ // import QtQuick 2.5 -import QtQuick.Controls 1.4 +import QtQuick.Controls 2.3 import QtQuick.Controls.Styles 1.4 import QtQuick.XmlListModel 2.0 diff --git a/interface/resources/qml/controlsUit/Table.qml b/interface/resources/qml/controlsUit/Table.qml index ab743610460..1b340b986bb 100644 --- a/interface/resources/qml/controlsUit/Table.qml +++ b/interface/resources/qml/controlsUit/Table.qml @@ -9,7 +9,7 @@ // import QtQuick 2.5 -import QtQuick.Controls 1.4 +import QtQuick.Controls 2.3 import QtQuick.Controls.Styles 1.4 import QtQuick.Controls 2.3 as QQC2 diff --git a/interface/resources/qml/controlsUit/TextField.qml b/interface/resources/qml/controlsUit/TextField.qml index 86707395e35..e3db7096017 100644 --- a/interface/resources/qml/controlsUit/TextField.qml +++ b/interface/resources/qml/controlsUit/TextField.qml @@ -9,7 +9,7 @@ // import QtQuick 2.5 -import QtQuick.Controls 1.4 +import QtQuick.Controls 2.3 import QtQuick.Controls.Styles 1.4 import "../stylesUit" diff --git a/interface/resources/qml/controlsUit/Tree.qml b/interface/resources/qml/controlsUit/Tree.qml index f2c49095b12..3ba02fcbd3d 100644 --- a/interface/resources/qml/controlsUit/Tree.qml +++ b/interface/resources/qml/controlsUit/Tree.qml @@ -10,7 +10,7 @@ import QtQml.Models 2.2 import QtQuick 2.7 -import QtQuick.Controls 1.4 +import QtQuick.Controls 2.3 import QtQuick.Controls.Styles 1.4 import QtQuick.Controls 2.2 as QQC2 diff --git a/interface/resources/qml/desktop/Desktop.qml b/interface/resources/qml/desktop/Desktop.qml index 971fb064148..d07c5525f3d 100644 --- a/interface/resources/qml/desktop/Desktop.qml +++ b/interface/resources/qml/desktop/Desktop.qml @@ -9,7 +9,7 @@ // import QtQuick 2.7 -import QtQuick.Controls 1.4 +import QtQuick.Controls 2.3 import QtQuick.Controls 2.3 as QQC2 import "../dialogs" diff --git a/interface/resources/qml/dialogs/FileDialog.qml b/interface/resources/qml/dialogs/FileDialog.qml index fa4d911b653..c1d39995a46 100644 --- a/interface/resources/qml/dialogs/FileDialog.qml +++ b/interface/resources/qml/dialogs/FileDialog.qml @@ -12,7 +12,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.Controls 2.3 as QQC1 import QtQuick.Controls 2.3 import ".." diff --git a/interface/resources/qml/dialogs/TabletFileDialog.qml b/interface/resources/qml/dialogs/TabletFileDialog.qml index 4ee53d44ef9..94bd8fc2c45 100644 --- a/interface/resources/qml/dialogs/TabletFileDialog.qml +++ b/interface/resources/qml/dialogs/TabletFileDialog.qml @@ -12,7 +12,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.Controls 2.3 as QQC1 import QtQuick.Controls 2.3 import ".." 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..c6719131842 100644 --- a/interface/resources/qml/hifi/+android_interface/ActionBar.qml +++ b/interface/resources/qml/hifi/+android_interface/ActionBar.qml @@ -1,5 +1,5 @@ import QtQuick 2.5 -import QtQuick.Controls 1.4 +import QtQuick.Controls 2.3 import QtQuick.Controls.Styles 1.4 import QtQuick.Layouts 1.3 import Qt.labs.settings 1.0 diff --git a/interface/resources/qml/hifi/+android_interface/AudioBar.qml b/interface/resources/qml/hifi/+android_interface/AudioBar.qml index 912572fdf8a..37ae3f4fad7 100644 --- a/interface/resources/qml/hifi/+android_interface/AudioBar.qml +++ b/interface/resources/qml/hifi/+android_interface/AudioBar.qml @@ -1,5 +1,5 @@ import QtQuick 2.5 -import QtQuick.Controls 1.4 +import QtQuick.Controls 2.3 import QtQuick.Controls.Styles 1.4 import QtQuick.Layouts 1.3 import Qt.labs.settings 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..6dc5967e014 100644 --- a/interface/resources/qml/hifi/+android_interface/StatsBar.qml +++ b/interface/resources/qml/hifi/+android_interface/StatsBar.qml @@ -1,5 +1,5 @@ import QtQuick 2.5 -import QtQuick.Controls 1.4 +import QtQuick.Controls 2.3 import QtQuick.Controls.Styles 1.4 import QtQuick.Layouts 1.3 import Qt.labs.settings 1.0 diff --git a/interface/resources/qml/hifi/+android_interface/WindowHeader.qml b/interface/resources/qml/hifi/+android_interface/WindowHeader.qml index 5316fc4786b..446181d761f 100644 --- a/interface/resources/qml/hifi/+android_interface/WindowHeader.qml +++ b/interface/resources/qml/hifi/+android_interface/WindowHeader.qml @@ -10,7 +10,7 @@ // import QtQuick 2.5 -import QtQuick.Controls 1.4 +import QtQuick.Controls 2.3 import QtQuick.Controls.Styles 1.4 import QtQuick.Layouts 1.3 import Qt.labs.settings 1.0 diff --git a/interface/resources/qml/hifi/+android_interface/bottomHudOptions.qml b/interface/resources/qml/hifi/+android_interface/bottomHudOptions.qml index 6b830d94c25..c2259338073 100644 --- a/interface/resources/qml/hifi/+android_interface/bottomHudOptions.qml +++ b/interface/resources/qml/hifi/+android_interface/bottomHudOptions.qml @@ -11,7 +11,7 @@ import Hifi 1.0 import QtQuick 2.5 -import QtQuick.Controls 1.4 +import QtQuick.Controls 2.3 import QtQuick.Controls.Styles 1.4 import QtQuick.Layouts 1.3 import Qt.labs.settings 1.0 diff --git a/interface/resources/qml/hifi/+android_interface/button.qml b/interface/resources/qml/hifi/+android_interface/button.qml index a4c65b3c6f1..7195645e5ae 100644 --- a/interface/resources/qml/hifi/+android_interface/button.qml +++ b/interface/resources/qml/hifi/+android_interface/button.qml @@ -1,5 +1,5 @@ import QtQuick 2.5 -import QtQuick.Controls 1.4 +import QtQuick.Controls 2.3 import QtQuick.Controls.Styles 1.4 import QtQuick.Layouts 1.3 diff --git a/interface/resources/qml/hifi/+android_interface/modesbar.qml b/interface/resources/qml/hifi/+android_interface/modesbar.qml index 1bf04fb8d98..2bb957d182c 100644 --- a/interface/resources/qml/hifi/+android_interface/modesbar.qml +++ b/interface/resources/qml/hifi/+android_interface/modesbar.qml @@ -1,5 +1,5 @@ import QtQuick 2.5 -import QtQuick.Controls 1.4 +import QtQuick.Controls 2.3 import QtQuick.Controls.Styles 1.4 import QtQuick.Layouts 1.3 import Qt.labs.settings 1.0 diff --git a/interface/resources/qml/hifi/AssetServer.qml b/interface/resources/qml/hifi/AssetServer.qml index 75d7370c80a..0863a0831ff 100644 --- a/interface/resources/qml/hifi/AssetServer.qml +++ b/interface/resources/qml/hifi/AssetServer.qml @@ -9,7 +9,7 @@ // import QtQuick 2.5 -import QtQuick.Controls 1.4 +import QtQuick.Controls 2.3 import QtQuick.Controls.Styles 1.4 import QtQuick.Dialogs 1.2 as OriginalDialogs import Qt.labs.settings 1.0 diff --git a/interface/resources/qml/hifi/NameCard.qml b/interface/resources/qml/hifi/NameCard.qml index 0dd29b9e0f4..331d1763659 100644 --- a/interface/resources/qml/hifi/NameCard.qml +++ b/interface/resources/qml/hifi/NameCard.qml @@ -10,7 +10,7 @@ // import QtQuick 2.5 -import QtQuick.Controls 1.4 +import QtQuick.Controls 2.3 import QtQuick.Controls.Styles 1.4 import QtGraphicalEffects 1.0 import stylesUit 1.0 diff --git a/interface/resources/qml/hifi/Pal.qml b/interface/resources/qml/hifi/Pal.qml index b842b736d42..9f81f5596d9 100644 --- a/interface/resources/qml/hifi/Pal.qml +++ b/interface/resources/qml/hifi/Pal.qml @@ -12,7 +12,7 @@ // import QtQuick 2.5 -import QtQuick.Controls 1.4 +import QtQuick.Controls 2.3 import QtGraphicalEffects 1.0 import Qt.labs.settings 1.0 import stylesUit 1.0 diff --git a/interface/resources/qml/hifi/dialogs/RunningScripts.qml b/interface/resources/qml/hifi/dialogs/RunningScripts.qml index b9cfe113f08..5fe0f090c59 100644 --- a/interface/resources/qml/hifi/dialogs/RunningScripts.qml +++ b/interface/resources/qml/hifi/dialogs/RunningScripts.qml @@ -9,7 +9,7 @@ // import QtQuick 2.5 -import QtQuick.Controls 1.4 +import QtQuick.Controls 2.3 import QtQuick.Dialogs 1.2 as OriginalDialogs import Qt.labs.settings 1.0 diff --git a/interface/resources/qml/hifi/dialogs/TabletAssetServer.qml b/interface/resources/qml/hifi/dialogs/TabletAssetServer.qml index 6374e85ada5..cb90cf3fac2 100644 --- a/interface/resources/qml/hifi/dialogs/TabletAssetServer.qml +++ b/interface/resources/qml/hifi/dialogs/TabletAssetServer.qml @@ -9,7 +9,7 @@ // import QtQuick 2.5 -import QtQuick.Controls 1.4 +import QtQuick.Controls 2.3 import QtQuick.Controls.Styles 1.4 import QtQuick.Dialogs 1.2 as OriginalDialogs import Qt.labs.settings 1.0 diff --git a/interface/resources/qml/hifi/dialogs/TabletRunningScripts.qml b/interface/resources/qml/hifi/dialogs/TabletRunningScripts.qml index 4aeeada1e5a..ef66764c756 100644 --- a/interface/resources/qml/hifi/dialogs/TabletRunningScripts.qml +++ b/interface/resources/qml/hifi/dialogs/TabletRunningScripts.qml @@ -9,7 +9,7 @@ // import QtQuick 2.5 -import QtQuick.Controls 1.4 +import QtQuick.Controls 2.3 import QtQuick.Dialogs 1.2 as OriginalDialogs import Qt.labs.settings 1.0 diff --git a/interface/resources/qml/hifi/tablet/TabletMenu.qml b/interface/resources/qml/hifi/tablet/TabletMenu.qml index 5f06e4fbab7..ac17d48a344 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 QtQuick.Controls 2.3 import QtQml 2.2 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/tabletWindows/TabletFileDialog.qml b/interface/resources/qml/hifi/tablet/tabletWindows/TabletFileDialog.qml index 91dc6fe5684..fb1bdd38811 100644 --- a/interface/resources/qml/hifi/tablet/tabletWindows/TabletFileDialog.qml +++ b/interface/resources/qml/hifi/tablet/tabletWindows/TabletFileDialog.qml @@ -12,7 +12,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.Controls 2.3 as QQC1 import QtQuick.Controls 2.3 import ".." 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 5c878abde76..abdde2bd000 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)), @@ -415,7 +416,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; } @@ -611,7 +612,8 @@ void Application::registerScriptEngineWithApplicationServices(ScriptManagerPoint scriptEngine->registerGlobalObject("Menu", MenuScriptingInterface::getInstance()); scriptEngine->registerGlobalObject("DesktopPreviewProvider", DependencyManager::get().data()); #if !defined(DISABLE_QML) - scriptEngine->registerGlobalObject("Stats", Stats::getInstance()); + // QT6TODO: Stats instance is not created? + //scriptEngine->registerGlobalObject("Stats", Stats::getInstance()); #endif scriptEngine->registerGlobalObject("Settings", SettingsScriptingInterface::getInstance()); scriptEngine->registerGlobalObject("Snapshot", DependencyManager::get().data()); @@ -1004,7 +1006,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; } @@ -1036,7 +1038,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; } diff --git a/interface/src/Application.h b/interface/src/Application.h index f38f6dff04f..aa7a52bd626 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -854,7 +854,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/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_Events.cpp b/interface/src/Application_Events.cpp index cd3a9327e48..6106e267fd5 100644 --- a/interface/src/Application_Events.cpp +++ b/interface/src/Application_Events.cpp @@ -336,7 +336,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 +607,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); } } diff --git a/interface/src/Application_Graphics.cpp b/interface/src/Application_Graphics.cpp index a7e7c7e217d..a660b319887 100644 --- a/interface/src/Application_Graphics.cpp +++ b/interface/src/Application_Graphics.cpp @@ -18,6 +18,7 @@ #include #include +#include #include #include @@ -81,6 +82,7 @@ void Application::initializeGL() { } #ifdef USE_GL + _glWidget->windowHandle()->setSurfaceType(QSurface::OpenGLSurface); _primaryWidget->windowHandle()->setFormat(getDefaultOpenGLSurfaceFormat()); #else //_primaryWidget->windowHandle()->setFormat(getDefaultOpenGLSurfaceFormat()); // VKTODO @@ -222,7 +224,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 +245,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 ee8fa932c95..9a9ecefd62c 100644 --- a/interface/src/Application_Setup.cpp +++ b/interface/src/Application_Setup.cpp @@ -876,14 +876,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)) { diff --git a/interface/src/Application_UI.cpp b/interface/src/Application_UI.cpp index 53e8600b717..66c8120303e 100644 --- a/interface/src/Application_UI.cpp +++ b/interface/src/Application_UI.cpp @@ -981,7 +981,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 +1462,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 8410aadebe7..63ced1c9369 100644 --- a/interface/src/AvatarBookmarks.cpp +++ b/interface/src/AvatarBookmarks.cpp @@ -139,7 +139,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(); @@ -156,7 +156,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)) { @@ -173,7 +173,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; } @@ -229,7 +229,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; } @@ -276,7 +276,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) @@ -306,7 +306,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; } 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 b7982f57f70..40ba372b896 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 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/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/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 6e1a115d632..3dfc07aee2f 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -1245,7 +1245,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(); @@ -2312,7 +2312,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); @@ -2623,8 +2623,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; } @@ -4087,7 +4087,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; @@ -6159,7 +6159,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()) { @@ -6227,7 +6227,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; } @@ -6546,7 +6546,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; @@ -6558,7 +6558,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); @@ -6655,8 +6655,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) { diff --git a/interface/src/networking/CloseEventSender.h b/interface/src/networking/CloseEventSender.h index b74412c41c0..4be84c1f792 100644 --- a/interface/src/networking/CloseEventSender.h +++ b/interface/src/networking/CloseEventSender.h @@ -16,6 +16,7 @@ #include #include +#include #include diff --git a/interface/src/raypick/CollisionPick.cpp b/interface/src/raypick/CollisionPick.cpp index f652639d20e..961476b519b 100644 --- a/interface/src/raypick/CollisionPick.cpp +++ b/interface/src/raypick/CollisionPick.cpp @@ -305,7 +305,7 @@ void CollisionPick::computeShapeInfo(const CollisionRegion& pick, ShapeInfo& sha points.reserve(sizeToReserve); // copy points - const glm::vec3* vertexItr = vertices.cbegin(); + auto vertexItr = vertices.cbegin(); while (vertexItr != vertices.cend()) { glm::vec3 point = *vertexItr; points.push_back(point); @@ -322,7 +322,7 @@ void CollisionPick::computeShapeInfo(const CollisionRegion& pick, ShapeInfo& sha triangleIndices.reserve((int)triangleIndicesCount); for (const HFMMeshPart& meshPart : mesh.parts) { - const int* indexItr = meshPart.triangleIndices.cbegin(); + auto indexItr = meshPart.triangleIndices.cbegin(); while (indexItr != meshPart.triangleIndices.cend()) { triangleIndices.push_back(*indexItr); ++indexItr; diff --git a/interface/src/raypick/PointerScriptingInterface.cpp b/interface/src/raypick/PointerScriptingInterface.cpp index 508745ca32d..94a24295922 100644 --- a/interface/src/raypick/PointerScriptingInterface.cpp +++ b/interface/src/raypick/PointerScriptingInterface.cpp @@ -49,7 +49,7 @@ unsigned int PointerScriptingInterface::createPointerInternal(const PickQuery::P // Interaction with managers should always happen on the main thread if (QThread::currentThread() != qApp->thread()) { unsigned int result; - BLOCKING_INVOKE_METHOD(this, "createPointerInternal", Q_RETURN_ARG(unsigned int, result), Q_ARG(PickQuery::PickType, type), Q_ARG(PointerProperties, properties)); + BLOCKING_INVOKE_METHOD(this, "createPointerInternal", Q_GENERIC_RETURN_ARG(unsigned int, result), Q_GENERIC_ARG(PickQuery::PickType, type), Q_GENERIC_ARG(PointerProperties, properties)); return result; } diff --git a/interface/src/scripting/AssetMappingsScriptingInterface.cpp b/interface/src/scripting/AssetMappingsScriptingInterface.cpp index 13954cf2972..8acb47a1333 100644 --- a/interface/src/scripting/AssetMappingsScriptingInterface.cpp +++ b/interface/src/scripting/AssetMappingsScriptingInterface.cpp @@ -167,13 +167,12 @@ void AssetMappingsScriptingInterface::getAllMappings(QJSValue callback) { auto assetClient = DependencyManager::get(); auto request = assetClient->createGetAllMappingsRequest(); - connect(request, &GetAllMappingsRequest::finished, this, [callback](GetAllMappingsRequest* request) mutable { + connect(request, &GetAllMappingsRequest::finished, this, [callback, this](GetAllMappingsRequest* request) mutable { auto mappings = request->getMappings(); - OVERTE_IGNORE_DEPRECATED_BEGIN - // Still using QScriptEngine - auto map = callback.engine()->newObject(); - OVERTE_IGNORE_WARNING_END + // QT6TODO: is this correct? + auto engine = qjsEngine(this); + auto map = engine->newObject(); for (auto& kv : mappings ) { map.setProperty(kv.first, kv.second.hash); diff --git a/interface/src/scripting/AudioDevices.cpp b/interface/src/scripting/AudioDevices.cpp index f90a7d84a8b..90e09ef69dd 100644 --- a/interface/src/scripting/AudioDevices.cpp +++ b/interface/src/scripting/AudioDevices.cpp @@ -33,8 +33,8 @@ static Setting::Handle hmdOutputDeviceSetting { QStringList { OVERTE_AU Q_DECLARE_METATYPE(HifiAudioDeviceInfo); -Setting::Handle& getSetting(bool contextIsHMD, QAudio::Mode mode) { - if (mode == QAudio::AudioInput) { +Setting::Handle& getSetting(bool contextIsHMD, QAudioDevice::Mode mode) { + if (mode == QAudioDevice::Mode::Input) { return contextIsHMD ? hmdInputDeviceSetting : desktopInputDeviceSetting; } else { // if (mode == QAudio::AudioOutput) return contextIsHMD ? hmdOutputDeviceSetting : desktopOutputDeviceSetting; @@ -59,7 +59,7 @@ QHash AudioDeviceList::_roles { { TypeRole, "type"} }; -static QString getTargetDevice(bool hmd, QAudio::Mode mode) { +static QString getTargetDevice(bool hmd, QAudioDevice::Mode mode) { QString deviceName; auto& setting = getSetting(hmd, mode); if (setting.isSet()) { @@ -70,11 +70,11 @@ static QString getTargetDevice(bool hmd, QAudio::Mode mode) { return deviceName; } -static void checkHmdDefaultsChange(QAudio::Mode mode) { +static void checkHmdDefaultsChange(QAudioDevice::Mode mode) { QString name; foreach(DisplayPluginPointer displayPlugin, PluginManager::getInstance()->getAllDisplayPlugins()) { if (displayPlugin && displayPlugin->isHmd()) { - if (mode == QAudio::AudioInput) { + if (mode == QAudioDevice::Mode::Input) { name = displayPlugin->getPreferredAudioInDevice(); } else { name = displayPlugin->getPreferredAudioOutDevice(); @@ -86,16 +86,16 @@ static void checkHmdDefaultsChange(QAudio::Mode mode) { if (!name.isEmpty()) { auto client = DependencyManager::get().data(); QMetaObject::invokeMethod(client, "setHmdAudioName", - Q_ARG(QAudio::Mode, mode), + Q_ARG(QAudioDevice::Mode, mode), Q_ARG(const QString&, name)); } } Qt::ItemFlags AudioDeviceList::_flags { Qt::ItemIsSelectable | Qt::ItemIsEnabled }; -AudioDeviceList::AudioDeviceList(QAudio::Mode mode) : _mode(mode) { - if (mode == QAudio::AudioInput) { - auto& setting1 = getSetting(true, QAudio::AudioInput); +AudioDeviceList::AudioDeviceList(QAudioDevice::Mode mode) : _mode(mode) { + if (mode == QAudioDevice::Mode::Input) { + auto& setting1 = getSetting(true, QAudioDevice::Mode::Input); if (setting1.isSet()) { qDebug() << "Device name in settings for HMD, Input" << setting1.get(); _backupSelectedHMDDeviceName = setting1.get(); @@ -104,8 +104,8 @@ AudioDeviceList::AudioDeviceList(QAudio::Mode mode) : _mode(mode) { } } - if (mode == QAudio::AudioOutput) { - auto& setting2 = getSetting(true, QAudio::AudioOutput); + if (mode == QAudioDevice::Mode::Output) { + auto& setting2 = getSetting(true, QAudioDevice::Mode::Output); if (setting2.isSet()) { qDebug() << "Device name in settings for HMD, Output" << setting2.get(); _backupSelectedHMDDeviceName = setting2.get(); @@ -114,8 +114,8 @@ AudioDeviceList::AudioDeviceList(QAudio::Mode mode) : _mode(mode) { } } - if (mode == QAudio::AudioInput) { - auto& setting3 = getSetting(false, QAudio::AudioInput); + if (mode == QAudioDevice::Mode::Input) { + auto& setting3 = getSetting(false, QAudioDevice::Mode::Input); if (setting3.isSet()) { qDebug() << "Device name in settings for Desktop, Input" << setting3.get(); _backupSelectedDesktopDeviceName = setting3.get(); @@ -124,8 +124,8 @@ AudioDeviceList::AudioDeviceList(QAudio::Mode mode) : _mode(mode) { } } - if (mode == QAudio::AudioOutput) { - auto& setting4 = getSetting(false, QAudio::AudioOutput); + if (mode == QAudioDevice::Mode::Output) { + auto& setting4 = getSetting(false, QAudioDevice::Mode::Output); if (setting4.isSet()) { qDebug() << "Device name in settings for Desktop, Output" << setting4.get(); _backupSelectedDesktopDeviceName = setting4.get(); @@ -188,13 +188,13 @@ void AudioDeviceList::resetDevice(bool contextIsHMD) { // FIXME can't use blocking connections here, so we can't determine whether the switch succeeded or not // We need to have the AudioClient emit signals on switch success / failure QMetaObject::invokeMethod(client, "switchAudioDevice", - Q_ARG(QAudio::Mode, _mode), Q_ARG(QString, deviceName), Q_ARG(bool, contextIsHMD)); + Q_ARG(QAudioDevice::Mode, _mode), Q_ARG(QString, deviceName), Q_ARG(bool, contextIsHMD)); #if 0 bool switchResult = false; QMetaObject::invokeMethod(client, "switchAudioDevice", Qt::BlockingQueuedConnection, Q_RETURN_ARG(bool, switchResult), - Q_ARG(QAudio::Mode, _mode), Q_ARG(QString, deviceName)); + Q_ARG(QAudioDevice::Mode, _mode), Q_ARG(QString, deviceName)); // try to set to the default device for this mode if (!switchResult) { @@ -206,11 +206,11 @@ void AudioDeviceList::resetDevice(bool contextIsHMD) { deviceName = qApp->getActiveDisplayPlugin()->getPreferredAudioOutDevice(); } if (!deviceName.isNull()) { - QMetaObject::invokeMethod(client, "switchAudioDevice", Q_ARG(QAudio::Mode, _mode), Q_ARG(QString, deviceName)); + QMetaObject::invokeMethod(client, "switchAudioDevice", Q_ARG(QAudioDevice::Mode, _mode), Q_ARG(QString, deviceName)); } } else { // use the system default - QMetaObject::invokeMethod(client, "switchAudioDevice", Q_ARG(QAudio::Mode, _mode)); + QMetaObject::invokeMethod(client, "switchAudioDevice", Q_ARG(QAudioDevice::Mode, _mode)); } } #endif @@ -280,7 +280,7 @@ std::shared_ptr getSimilarDevice(const QString& deviceNa } -void AudioDeviceList::onDevicesChanged(QAudio::Mode mode, const QList& devices) { +void AudioDeviceList::onDevicesChanged(QAudioDevice::Mode mode, const QList& devices) { beginResetModel(); QList> newDevices; @@ -312,13 +312,13 @@ void AudioDeviceList::onDevicesChanged(QAudio::Mode mode, const QList& peakValueList) { - assert(_mode == QAudio::AudioInput); + assert(_mode == QAudioDevice::Mode::Input); if (peakValueList.length() != rowCount()) { qWarning() << "AudioDeviceList" << __FUNCTION__ << "length mismatch"; @@ -434,8 +434,8 @@ AudioDevices::AudioDevices(bool& contextIsHMD) : _contextIsHMD(contextIsHMD) { connect(client, &AudioClient::devicesChanged, this, &AudioDevices::onDevicesChanged, Qt::QueuedConnection); connect(client, &AudioClient::peakValueListChanged, &_inputs, &AudioInputDeviceList::onPeakValueListChanged, Qt::QueuedConnection); - checkHmdDefaultsChange(QAudio::AudioInput); - checkHmdDefaultsChange(QAudio::AudioOutput); + checkHmdDefaultsChange(QAudioDevice::Mode::Input); + checkHmdDefaultsChange(QAudioDevice::Mode::Output); } AudioDevices::~AudioDevices() {} @@ -445,7 +445,7 @@ void AudioDevices::onContextChanged(const QString& context) { _outputs.resetDevice(_contextIsHMD); } -void AudioDevices::onDeviceSelected(QAudio::Mode mode, const HifiAudioDeviceInfo& device, +void AudioDevices::onDeviceSelected(QAudioDevice::Mode mode, const HifiAudioDeviceInfo& device, const HifiAudioDeviceInfo& previousDevice, bool isHMD) { QString deviceName = device.deviceName(); @@ -463,7 +463,7 @@ void AudioDevices::onDeviceSelected(QAudio::Mode mode, const HifiAudioDeviceInfo const QString MODE = "audio_mode"; const QString INPUT = "INPUT"; - const QString OUTPUT = "OUTPUT"; data[MODE] = mode == QAudio::AudioInput ? INPUT : OUTPUT; + const QString OUTPUT = "OUTPUT"; data[MODE] = mode == QAudioDevice::Mode::Input ? INPUT : OUTPUT; const QString CONTEXT = "display_mode"; data[CONTEXT] = _contextIsHMD ? Audio::HMD : Audio::DESKTOP; @@ -482,10 +482,10 @@ void AudioDevices::onDeviceSelected(QAudio::Mode mode, const HifiAudioDeviceInfo } } -void AudioDevices::onDeviceChanged(QAudio::Mode mode, const HifiAudioDeviceInfo& device) { - if (mode == QAudio::AudioInput) { +void AudioDevices::onDeviceChanged(QAudioDevice::Mode mode, const HifiAudioDeviceInfo& device) { + if (mode == QAudioDevice::Mode::Input) { if (_requestedInputDevice == device) { - onDeviceSelected(QAudio::AudioInput, device, + onDeviceSelected(QAudioDevice::Mode::Input, device, _contextIsHMD ? _inputs._selectedHMDDevice : _inputs._selectedDesktopDevice, _contextIsHMD); _requestedInputDevice = HifiAudioDeviceInfo(); @@ -493,7 +493,7 @@ void AudioDevices::onDeviceChanged(QAudio::Mode mode, const HifiAudioDeviceInfo& _inputs.onDeviceChanged(device, _contextIsHMD); } else { // if (mode == QAudio::AudioOutput) if (_requestedOutputDevice == device) { - onDeviceSelected(QAudio::AudioOutput, device, + onDeviceSelected(QAudioDevice::Mode::Output, device, _contextIsHMD ? _outputs._selectedHMDDevice : _outputs._selectedDesktopDevice, _contextIsHMD); _requestedOutputDevice = HifiAudioDeviceInfo(); @@ -502,21 +502,21 @@ void AudioDevices::onDeviceChanged(QAudio::Mode mode, const HifiAudioDeviceInfo& } } -void AudioDevices::onDevicesChanged(QAudio::Mode mode, const QList& devices) { +void AudioDevices::onDevicesChanged(QAudioDevice::Mode mode, const QList& devices) { static std::once_flag once; std::call_once(once, [&] { //readout settings - _inputs._hmdSavedDeviceName = getTargetDevice(true, QAudio::AudioInput); - _inputs._desktopSavedDeviceName = getTargetDevice(false, QAudio::AudioInput); + _inputs._hmdSavedDeviceName = getTargetDevice(true, QAudioDevice::Mode::Input); + _inputs._desktopSavedDeviceName = getTargetDevice(false, QAudioDevice::Mode::Input); - _outputs._hmdSavedDeviceName = getTargetDevice(true, QAudio::AudioOutput); - _outputs._desktopSavedDeviceName = getTargetDevice(false, QAudio::AudioOutput); + _outputs._hmdSavedDeviceName = getTargetDevice(true, QAudioDevice::Mode::Output); + _outputs._desktopSavedDeviceName = getTargetDevice(false, QAudioDevice::Mode::Output); onContextChanged(QString()); }); //set devices for both contexts - if (mode == QAudio::AudioInput) { + if (mode == QAudioDevice::Mode::Input) { _inputs.onDevicesChanged(mode, devices); static std::once_flag onceAfterInputDevicesChanged; @@ -540,11 +540,11 @@ void AudioDevices::chooseInputDevice(const HifiAudioDeviceInfo& device, bool isH auto client = DependencyManager::get().data(); _requestedInputDevice = device; QMetaObject::invokeMethod(client, "switchAudioDevice", - Q_ARG(QAudio::Mode, QAudio::AudioInput), + Q_ARG(QAudioDevice::Mode, QAudioDevice::Mode::Input), Q_ARG(const HifiAudioDeviceInfo&, device)); } else { //context is different. just save device in settings - onDeviceSelected(QAudio::AudioInput, device, + onDeviceSelected(QAudioDevice::Mode::Input, device, isHMD ? _inputs._selectedHMDDevice : _inputs._selectedDesktopDevice, isHMD); _inputs.onDeviceChanged(device, isHMD); @@ -557,11 +557,11 @@ void AudioDevices::chooseOutputDevice(const HifiAudioDeviceInfo& device, bool is auto client = DependencyManager::get().data(); _requestedOutputDevice = device; QMetaObject::invokeMethod(client, "switchAudioDevice", - Q_ARG(QAudio::Mode, QAudio::AudioOutput), + Q_ARG(QAudioDevice::Mode, QAudioDevice::Mode::Output), Q_ARG(const HifiAudioDeviceInfo&, device)); } else { //context is different. just save device in settings - onDeviceSelected(QAudio::AudioOutput, device, + onDeviceSelected(QAudioDevice::Mode::Output, device, isHMD ? _outputs._selectedHMDDevice : _outputs._selectedDesktopDevice, isHMD); _outputs.onDeviceChanged(device, isHMD); diff --git a/interface/src/scripting/AudioDevices.h b/interface/src/scripting/AudioDevices.h index 24d7580d0d6..8e61cb7c7b2 100644 --- a/interface/src/scripting/AudioDevices.h +++ b/interface/src/scripting/AudioDevices.h @@ -17,9 +17,9 @@ #include #include -#include +#include -#include +#include <../../audio-client/src/HifiAudioDeviceInfo.h> namespace scripting { @@ -36,7 +36,7 @@ class AudioDeviceList : public QAbstractListModel { Q_OBJECT public: - AudioDeviceList(QAudio::Mode mode = QAudio::AudioOutput); + AudioDeviceList(QAudioDevice::Mode mode = QAudioDevice::Mode::Output); virtual ~AudioDeviceList(); virtual std::shared_ptr newDevice(const AudioDevice& device) @@ -58,14 +58,14 @@ class AudioDeviceList : public QAbstractListModel { protected slots: void onDeviceChanged(const HifiAudioDeviceInfo& device, bool isHMD); - void onDevicesChanged(QAudio::Mode mode, const QList& devices); + void onDevicesChanged(QAudioDevice::Mode mode, const QList& devices); protected: friend class AudioDevices; static QHash _roles; static Qt::ItemFlags _flags; - const QAudio::Mode _mode; + const QAudioDevice::Mode _mode; HifiAudioDeviceInfo _selectedDesktopDevice; HifiAudioDeviceInfo _selectedHMDDevice; QString _backupSelectedDesktopDeviceName; @@ -87,7 +87,7 @@ class AudioInputDeviceList : public AudioDeviceList { Q_PROPERTY(bool peakValuesEnabled READ peakValuesEnabled WRITE setPeakValuesEnabled NOTIFY peakValuesEnabledChanged) public: - AudioInputDeviceList() : AudioDeviceList(QAudio::AudioInput) {} + AudioInputDeviceList() : AudioDeviceList(QAudioDevice::Input) {} virtual ~AudioInputDeviceList() = default; virtual std::shared_ptr newDevice(const AudioDevice& device) override @@ -131,10 +131,10 @@ private slots: void chooseOutputDevice(const HifiAudioDeviceInfo& device, bool isHMD); void onContextChanged(const QString& context); - void onDeviceSelected(QAudio::Mode mode, const HifiAudioDeviceInfo& device, + void onDeviceSelected(QAudioDevice::Mode mode, const HifiAudioDeviceInfo& device, const HifiAudioDeviceInfo& previousDevice, bool isHMD); - void onDeviceChanged(QAudio::Mode mode, const HifiAudioDeviceInfo& device); - void onDevicesChanged(QAudio::Mode mode, const QList& devices); + void onDeviceChanged(QAudioDevice::Mode mode, const HifiAudioDeviceInfo& device); + void onDevicesChanged(QAudioDevice::Mode mode, const QList& devices); private: friend class Audio; @@ -143,7 +143,7 @@ private slots: AudioDeviceList* getOutputList() { return &_outputs; } AudioInputDeviceList _inputs; - AudioDeviceList _outputs { QAudio::AudioOutput }; + AudioDeviceList _outputs { QAudioDevice::Mode::Output }; HifiAudioDeviceInfo _requestedOutputDevice; HifiAudioDeviceInfo _requestedInputDevice; diff --git a/interface/src/scripting/ClipboardScriptingInterface.cpp b/interface/src/scripting/ClipboardScriptingInterface.cpp index af7ac8165b9..0ff9e670883 100644 --- a/interface/src/scripting/ClipboardScriptingInterface.cpp +++ b/interface/src/scripting/ClipboardScriptingInterface.cpp @@ -28,21 +28,21 @@ float ClipboardScriptingInterface::getClipboardContentsLargestDimension() { bool ClipboardScriptingInterface::exportEntities(const QString& filename, const QVector& entityIDs) { bool retVal; BLOCKING_INVOKE_METHOD(qApp, "exportEntities", - Q_RETURN_ARG(bool, retVal), - Q_ARG(const QString&, filename), - Q_ARG(const QVector&, entityIDs)); + Q_GENERIC_RETURN_ARG(bool, retVal), + Q_GENERIC_ARG(const QString&, filename), + Q_GENERIC_ARG(const QVector&, entityIDs)); return retVal; } bool ClipboardScriptingInterface::exportEntities(const QString& filename, float x, float y, float z, float scale) { bool retVal; BLOCKING_INVOKE_METHOD(qApp, "exportEntities", - Q_RETURN_ARG(bool, retVal), - Q_ARG(const QString&, filename), - Q_ARG(float, x), - Q_ARG(float, y), - Q_ARG(float, z), - Q_ARG(float, scale)); + Q_GENERIC_RETURN_ARG(bool, retVal), + Q_GENERIC_ARG(const QString&, filename), + Q_GENERIC_ARG(float, x), + Q_GENERIC_ARG(float, y), + Q_GENERIC_ARG(float, z), + Q_GENERIC_ARG(float, scale)); return retVal; } @@ -53,20 +53,20 @@ bool ClipboardScriptingInterface::importEntities( ) { bool retVal; BLOCKING_INVOKE_METHOD(qApp, "importEntities", - Q_RETURN_ARG(bool, retVal), - Q_ARG(const QString&, filename), - Q_ARG(const bool, isObservable), - Q_ARG(const qint64, callerId)); + Q_GENERIC_RETURN_ARG(bool, retVal), + Q_GENERIC_ARG(const QString&, filename), + Q_GENERIC_ARG(const bool, isObservable), + Q_GENERIC_ARG(const qint64, callerId)); return retVal; } QVector ClipboardScriptingInterface::pasteEntities(glm::vec3 position, const QString& entityHostType) { QVector retVal; BLOCKING_INVOKE_METHOD(qApp, "pasteEntities", - Q_RETURN_ARG(QVector, retVal), - Q_ARG(const QString&, entityHostType), - Q_ARG(float, position.x), - Q_ARG(float, position.y), - Q_ARG(float, position.z)); + Q_GENERIC_RETURN_ARG(QVector, retVal), + Q_GENERIC_ARG(const QString&, entityHostType), + Q_GENERIC_ARG(float, position.x), + Q_GENERIC_ARG(float, position.y), + Q_GENERIC_ARG(float, position.z)); return retVal; } diff --git a/interface/src/scripting/DesktopScriptingInterface.cpp b/interface/src/scripting/DesktopScriptingInterface.cpp index f7ac8f43c91..ab45d0248f9 100644 --- a/interface/src/scripting/DesktopScriptingInterface.cpp +++ b/interface/src/scripting/DesktopScriptingInterface.cpp @@ -118,10 +118,10 @@ InteractiveWindowPointer DesktopScriptingInterface::createWindow(const QString& if (QThread::currentThread() != thread()) { InteractiveWindowPointer interactiveWindow = nullptr; BLOCKING_INVOKE_METHOD(this, "createWindowOnThread", - Q_RETURN_ARG(InteractiveWindowPointer, interactiveWindow), - Q_ARG(QString, sourceUrl), - Q_ARG(QVariantMap, properties), - Q_ARG(QThread*, QThread::currentThread())); + Q_GENERIC_RETURN_ARG(InteractiveWindowPointer, interactiveWindow), + Q_GENERIC_ARG(QString, sourceUrl), + Q_GENERIC_ARG(QVariantMap, properties), + Q_GENERIC_ARG(QThread*, QThread::currentThread())); return interactiveWindow; } diff --git a/interface/src/scripting/MenuScriptingInterface.cpp b/interface/src/scripting/MenuScriptingInterface.cpp index 9829ab8343f..4fe93987f32 100644 --- a/interface/src/scripting/MenuScriptingInterface.cpp +++ b/interface/src/scripting/MenuScriptingInterface.cpp @@ -68,8 +68,8 @@ bool MenuScriptingInterface::menuExists(const QString& menu) { bool result { false }; BLOCKING_INVOKE_METHOD(menuInstance, "menuExists", - Q_RETURN_ARG(bool, result), - Q_ARG(const QString&, menu)); + Q_GENERIC_RETURN_ARG(bool, result), + Q_GENERIC_ARG(const QString&, menu)); return result; } @@ -131,8 +131,8 @@ void MenuScriptingInterface::removeMenuItem(const QString& menu, const QString& return; } QMetaObject::invokeMethod(menuInstance, "removeMenuItem", - Q_ARG(const QString&, menu), - Q_ARG(const QString&, menuitem)); + Q_GENERIC_ARG(const QString&, menu), + Q_GENERIC_ARG(const QString&, menuitem)); }; bool MenuScriptingInterface::menuItemExists(const QString& menu, const QString& menuitem) { @@ -148,9 +148,9 @@ bool MenuScriptingInterface::menuItemExists(const QString& menu, const QString& bool result { false }; BLOCKING_INVOKE_METHOD(menuInstance, "menuItemExists", - Q_RETURN_ARG(bool, result), - Q_ARG(const QString&, menu), - Q_ARG(const QString&, menuitem)); + Q_GENERIC_RETURN_ARG(bool, result), + Q_GENERIC_ARG(const QString&, menu), + Q_GENERIC_ARG(const QString&, menuitem)); return result; } @@ -168,8 +168,8 @@ bool MenuScriptingInterface::isOptionChecked(const QString& menuOption) { bool result { false }; BLOCKING_INVOKE_METHOD(menuInstance, "isOptionChecked", - Q_RETURN_ARG(bool, result), - Q_ARG(const QString&, menuOption)); + Q_GENERIC_RETURN_ARG(bool, result), + Q_GENERIC_ARG(const QString&, menuOption)); return result; } @@ -180,8 +180,8 @@ void MenuScriptingInterface::setIsOptionChecked(const QString& menuOption, bool } QMetaObject::invokeMethod(menuInstance, "setIsOptionChecked", - Q_ARG(const QString&, menuOption), - Q_ARG(bool, isChecked)); + Q_GENERIC_ARG(const QString&, menuOption), + Q_GENERIC_ARG(bool, isChecked)); } bool MenuScriptingInterface::isMenuEnabled(const QString& menuOption) { @@ -197,8 +197,8 @@ bool MenuScriptingInterface::isMenuEnabled(const QString& menuOption) { bool result { false }; BLOCKING_INVOKE_METHOD(menuInstance, "isMenuEnabled", - Q_RETURN_ARG(bool, result), - Q_ARG(const QString&, menuOption)); + Q_GENERIC_RETURN_ARG(bool, result), + Q_GENERIC_ARG(const QString&, menuOption)); return result; } @@ -210,8 +210,8 @@ void MenuScriptingInterface::setMenuEnabled(const QString& menuOption, bool isCh } QMetaObject::invokeMethod(menuInstance, "setMenuEnabled", - Q_ARG(const QString&, menuOption), - Q_ARG(bool, isChecked)); + Q_GENERIC_ARG(const QString&, menuOption), + Q_GENERIC_ARG(bool, isChecked)); } void MenuScriptingInterface::triggerOption(const QString& menuOption) { @@ -220,7 +220,7 @@ void MenuScriptingInterface::triggerOption(const QString& menuOption) { return; } - QMetaObject::invokeMethod(menuInstance, "triggerOption", Q_ARG(const QString&, menuOption)); + QMetaObject::invokeMethod(menuInstance, "triggerOption", Q_GENERIC_ARG(const QString&, menuOption)); } void MenuScriptingInterface::setVisible(bool visible) { diff --git a/interface/src/scripting/TestScriptingInterface.cpp b/interface/src/scripting/TestScriptingInterface.cpp index bc3f59e9174..10d9bc3cd0c 100644 --- a/interface/src/scripting/TestScriptingInterface.cpp +++ b/interface/src/scripting/TestScriptingInterface.cpp @@ -66,7 +66,7 @@ void TestScriptingInterface::waitIdle() { bool TestScriptingInterface::loadTestScene(QString scene) { if (QThread::currentThread() != thread()) { bool result; - BLOCKING_INVOKE_METHOD(this, "loadTestScene", Q_RETURN_ARG(bool, result), Q_ARG(QString, scene)); + BLOCKING_INVOKE_METHOD(this, "loadTestScene", Q_GENERIC_RETURN_ARG(bool, result), Q_GENERIC_ARG(QString, scene)); return result; } diff --git a/interface/src/scripting/WindowScriptingInterface.cpp b/interface/src/scripting/WindowScriptingInterface.cpp index 81d88451e3d..b8f362eb12a 100644 --- a/interface/src/scripting/WindowScriptingInterface.cpp +++ b/interface/src/scripting/WindowScriptingInterface.cpp @@ -517,11 +517,11 @@ int WindowScriptingInterface::openMessageBox(QString title, QString text, int bu if (QThread::currentThread() != thread()) { int result; BLOCKING_INVOKE_METHOD(this, "openMessageBox", - Q_RETURN_ARG(int, result), - Q_ARG(QString, title), - Q_ARG(QString, text), - Q_ARG(int, buttons), - Q_ARG(int, defaultButton)); + Q_GENERIC_RETURN_ARG(int, result), + Q_GENERIC_ARG(QString, title), + Q_GENERIC_ARG(QString, text), + Q_GENERIC_ARG(int, buttons), + Q_GENERIC_ARG(int, defaultButton)); return result; } diff --git a/interface/src/ui/BaseLogDialog.cpp b/interface/src/ui/BaseLogDialog.cpp index c018e6508cf..7b2b912c520 100644 --- a/interface/src/ui/BaseLogDialog.cpp +++ b/interface/src/ui/BaseLogDialog.cpp @@ -16,6 +16,7 @@ #include #include #include +#include #include @@ -180,14 +181,16 @@ Highlighter::Highlighter(QTextDocument* parent) : QSyntaxHighlighter(parent) { } void Highlighter::highlightBlock(const QString& text) { - QRegExp expression(BOLD_PATTERN); + QRegularExpression expression(BOLD_PATTERN); - int index = text.indexOf(expression, 0); + auto expressionMatch = expression.match(text); + qsizetype index = expressionMatch.capturedStart(); while (index >= 0) { - int length = expression.matchedLength(); + qsizetype length = expressionMatch.capturedLength(); setFormat(index, length, boldFormat); - index = text.indexOf(expression, index + length); + expressionMatch = expression.match(text, index + length); + index = expressionMatch.capturedStart(); } if (keyword.isNull() || keyword.isEmpty()) { diff --git a/interface/src/ui/HMDToolsDialog.cpp b/interface/src/ui/HMDToolsDialog.cpp index 702a24fd1a9..bb1b5afb488 100644 --- a/interface/src/ui/HMDToolsDialog.cpp +++ b/interface/src/ui/HMDToolsDialog.cpp @@ -11,7 +11,6 @@ #include "HMDToolsDialog.h" -#include #include #include #include diff --git a/interface/src/ui/InteractiveWindow.cpp b/interface/src/ui/InteractiveWindow.cpp index 2761b1c9f07..e39287abb47 100644 --- a/interface/src/ui/InteractiveWindow.cpp +++ b/interface/src/ui/InteractiveWindow.cpp @@ -421,7 +421,7 @@ void InteractiveWindow::close() { if (_dockWidget) { auto window = qApp->getWindow(); if (QThread::currentThread() != window->thread()) { - BLOCKING_INVOKE_METHOD(window, "removeDockWidget", Q_ARG(QDockWidget*, _dockWidget.get())); + BLOCKING_INVOKE_METHOD(window, "removeDockWidget", Q_GENERIC_ARG(QDockWidget*, _dockWidget.get())); } else { window->removeDockWidget(_dockWidget.get()); } diff --git a/interface/src/ui/InteractiveWindow.h b/interface/src/ui/InteractiveWindow.h index 3d69185ecfc..d2322d27c57 100644 --- a/interface/src/ui/InteractiveWindow.h +++ b/interface/src/ui/InteractiveWindow.h @@ -263,7 +263,7 @@ public slots: * // QML file, "InteractiveWindow.qml". * * import QtQuick 2.5 - * import QtQuick.Controls 1.4 + * import QtQuick.Controls 2.3 * * Rectangle { * diff --git a/interface/src/ui/JSConsole.cpp b/interface/src/ui/JSConsole.cpp index 0a7b125a481..dcd3f02fd5a 100644 --- a/interface/src/ui/JSConsole.cpp +++ b/interface/src/ui/JSConsole.cpp @@ -129,7 +129,7 @@ JSConsole::JSConsole(QWidget* parent, const ScriptManagerPointer& scriptManager) QWidget(parent), _ui(new Ui::Console), _currentCommandInHistory(NO_CURRENT_HISTORY_COMMAND), - _savedHistoryFilename(QStandardPaths::writableLocation(QStandardPaths::DataLocation) + "/" + HISTORY_FILENAME), + _savedHistoryFilename(QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation) + "/" + HISTORY_FILENAME), _commandHistory(_readLines(_savedHistoryFilename)), _completer(new QCompleter(this)), _monospaceFont(QFontDatabase::systemFont(QFontDatabase::FixedFont)), @@ -512,9 +512,9 @@ bool JSConsole::eventFilter(QObject* sender, QEvent* event) { const int MODULE_INDEX = 3; const int PROPERTY_INDEX = 4; // TODO: disallow invalid characters on left of property - QRegExp regExp("((([A-Za-z0-9_\\.]+)\\.)|(?!\\.))([a-zA-Z0-9_]*)$"); - regExp.indexIn(leftOfCursor); - auto rexExpCapturedTexts = regExp.capturedTexts(); + QRegularExpression regExp("((([A-Za-z0-9_\\.]+)\\.)|(?!\\.))([a-zA-Z0-9_]*)$"); + auto regExpMatch = regExp.match(leftOfCursor); + auto rexExpCapturedTexts = regExpMatch.capturedTexts(); auto memberOf = rexExpCapturedTexts[MODULE_INDEX]; completionPrefix = rexExpCapturedTexts[PROPERTY_INDEX]; bool switchedModule = false; @@ -591,7 +591,8 @@ void JSConsole::scrollToBottom() { void JSConsole::appendMessage(const QString& gutter, const QString& message, const QColor& fgColor, const QColor& bgColor) { QWidget* logLine = new QWidget(_ui->logArea); QHBoxLayout* layout = new QHBoxLayout(logLine); - layout->setMargin(0); + // QT6TODO: + //layout->setMargin(0); layout->setSpacing(4); QLabel* gutterLabel = new QLabel(logLine); diff --git a/interface/src/ui/ModelsBrowser.cpp b/interface/src/ui/ModelsBrowser.cpp index cd706d0bcb5..aa9a64b32ec 100644 --- a/interface/src/ui/ModelsBrowser.cpp +++ b/interface/src/ui/ModelsBrowser.cpp @@ -26,6 +26,7 @@ #include #include #include +#include #include #include diff --git a/interface/src/ui/ResourceImageItem.cpp b/interface/src/ui/ResourceImageItem.cpp index 46ffffe62dd..8b60234df34 100644 --- a/interface/src/ui/ResourceImageItem.cpp +++ b/interface/src/ui/ResourceImageItem.cpp @@ -162,5 +162,6 @@ void ResourceImageItemRenderer::render() { _fboMutex.unlock(); } glFlush(); - _window->resetOpenGLState(); + // QT6TODO: check if this can be omitted + //_window->resetOpenGLState(); } diff --git a/interface/src/ui/Snapshot.cpp b/interface/src/ui/Snapshot.cpp index 2884574767c..48d41805b47 100644 --- a/interface/src/ui/Snapshot.cpp +++ b/interface/src/ui/Snapshot.cpp @@ -24,6 +24,7 @@ #include #include #include +#include #include #include @@ -361,7 +362,7 @@ QFile* Snapshot::savedFileForSnapshot(QImage& shot, QString username = DependencyManager::get()->getAccountInfo().getUsername(); // normalize username, replace all non alphanumeric with '-' - username.replace(QRegExp("[^A-Za-z0-9_]"), "-"); + username.replace(QRegularExpression("[^A-Za-z0-9_]"), "-"); QDateTime now = QDateTime::currentDateTime(); diff --git a/interface/src/ui/StandAloneJSConsole.cpp b/interface/src/ui/StandAloneJSConsole.cpp index e6c3748d208..0bc3d653223 100644 --- a/interface/src/ui/StandAloneJSConsole.cpp +++ b/interface/src/ui/StandAloneJSConsole.cpp @@ -26,7 +26,8 @@ void StandAloneJSConsole::toggleConsole() { dialog->setLayout(layout); dialog->resize(QSize(CONSOLE_WIDTH, CONSOLE_HEIGHT)); - layout->setMargin(0); + // QT6TODO: + //layout->setMargin(0); layout->setSpacing(0); layout->addWidget(new JSConsole(dialog)); dialog->setWindowOpacity(CONSOLE_WINDOW_OPACITY); diff --git a/interface/src/ui/Stats.cpp b/interface/src/ui/Stats.cpp index 8306274884a..463e92b4aa4 100644 --- a/interface/src/ui/Stats.cpp +++ b/interface/src/ui/Stats.cpp @@ -505,7 +505,7 @@ void Stats::updateStats(bool force) { } int linesDisplayed = 0; - QMapIterator j(sortedRecords); + QMultiMapIterator j(sortedRecords); j.toBack(); QString perfLines; while (j.hasPrevious()) { diff --git a/interface/src/ui/UpdateDialog.cpp b/interface/src/ui/UpdateDialog.cpp index 7ff2132ab99..68c7d844de3 100644 --- a/interface/src/ui/UpdateDialog.cpp +++ b/interface/src/ui/UpdateDialog.cpp @@ -11,7 +11,10 @@ #include "UpdateDialog.h" +#include + #include +#include #include "DependencyManager.h" @@ -38,7 +41,7 @@ UpdateDialog::UpdateDialog(QQuickItem* parent) : // grab the release notes for this later version QString releaseNotes = it.value()["releaseNotes"]; releaseNotes.remove("
"); - releaseNotes.remove(QRegExp("^\n+")); + releaseNotes.remove(QRegularExpression("^\n+")); _releaseNotes += "\n" + it.key().versionString + "\n" + releaseNotes + "\n"; } else { break; diff --git a/interface/src/ui/overlays/Overlays.cpp b/interface/src/ui/overlays/Overlays.cpp index e204b140f5c..d4b0b4706bb 100644 --- a/interface/src/ui/overlays/Overlays.cpp +++ b/interface/src/ui/overlays/Overlays.cpp @@ -201,7 +201,7 @@ QUuid Overlays::addOverlay(const QString& type, const QVariant& properties) { if (QThread::currentThread() != thread()) { QUuid result; PROFILE_RANGE(script, __FUNCTION__); - BLOCKING_INVOKE_METHOD(this, "addOverlay", Q_RETURN_ARG(QUuid, result), Q_ARG(const QString&, type), Q_ARG(const QVariant&, properties)); + BLOCKING_INVOKE_METHOD(this, "addOverlay", Q_GENERIC_RETURN_ARG(QUuid, result), Q_GENERIC_ARG(const QString&, type), Q_GENERIC_ARG(const QVariant&, properties)); return result; } @@ -250,7 +250,7 @@ QUuid Overlays::cloneOverlay(const QUuid& id) { if (QThread::currentThread() != thread()) { QUuid result; PROFILE_RANGE(script, __FUNCTION__); - BLOCKING_INVOKE_METHOD(this, "cloneOverlay", Q_RETURN_ARG(QUuid, result), Q_ARG(const QUuid&, id)); + BLOCKING_INVOKE_METHOD(this, "cloneOverlay", Q_GENERIC_RETURN_ARG(QUuid, result), Q_GENERIC_ARG(const QUuid&, id)); return result; } return add2DOverlay(Overlay::Pointer(overlay->createClone(), [](Overlay* ptr) { ptr->deleteLater(); })); @@ -347,7 +347,7 @@ QString Overlays::getOverlayType(const QUuid& id) { if (QThread::currentThread() != thread()) { QString result; PROFILE_RANGE(script, __FUNCTION__); - BLOCKING_INVOKE_METHOD(this, "getOverlayType", Q_RETURN_ARG(QString, result), Q_ARG(const QUuid&, id)); + BLOCKING_INVOKE_METHOD(this, "getOverlayType", Q_GENERIC_RETURN_ARG(QString, result), Q_GENERIC_ARG(const QUuid&, id)); return result; } return overlay->getType(); @@ -362,7 +362,7 @@ QObject* Overlays::getOverlayObject(const QUuid& id) { if (QThread::currentThread() != thread()) { QObject* result; PROFILE_RANGE(script, __FUNCTION__); - BLOCKING_INVOKE_METHOD(this, "getOverlayObject", Q_RETURN_ARG(QObject*, result), Q_ARG(const QUuid&, id)); + BLOCKING_INVOKE_METHOD(this, "getOverlayObject", Q_GENERIC_RETURN_ARG(QObject*, result), Q_GENERIC_ARG(const QUuid&, id)); return result; } return qobject_cast(&(*overlay)); @@ -378,7 +378,7 @@ QUuid Overlays::getOverlayAtPoint(const glm::vec2& point) { if (QThread::currentThread() != thread()) { QUuid result; - BLOCKING_INVOKE_METHOD(this, "getOverlayAtPoint", Q_RETURN_ARG(QUuid, result), Q_ARG(const glm::vec2&, point)); + BLOCKING_INVOKE_METHOD(this, "getOverlayAtPoint", Q_GENERIC_RETURN_ARG(QUuid, result), Q_GENERIC_ARG(const glm::vec2&, point)); return result; } @@ -520,7 +520,7 @@ QSizeF Overlays::textSize(const QUuid& id, const QString& text) { if (QThread::currentThread() != thread()) { QSizeF result; PROFILE_RANGE(script, __FUNCTION__); - BLOCKING_INVOKE_METHOD(this, "textSize", Q_RETURN_ARG(QSizeF, result), Q_ARG(const QUuid&, id), Q_ARG(QString, text)); + BLOCKING_INVOKE_METHOD(this, "textSize", Q_GENERIC_RETURN_ARG(QSizeF, result), Q_GENERIC_ARG(const QUuid&, id), Q_GENERIC_ARG(QString, text)); return result; } if (auto textOverlay = std::dynamic_pointer_cast(overlay)) { @@ -569,7 +569,7 @@ float Overlays::width() { if (QThread::currentThread() != thread()) { float result; PROFILE_RANGE(script, __FUNCTION__); - BLOCKING_INVOKE_METHOD(this, "width", Q_RETURN_ARG(float, result)); + BLOCKING_INVOKE_METHOD(this, "width", Q_GENERIC_RETURN_ARG(float, result)); return result; } @@ -581,7 +581,7 @@ float Overlays::height() { if (QThread::currentThread() != thread()) { float result; PROFILE_RANGE(script, __FUNCTION__); - BLOCKING_INVOKE_METHOD(this, "height", Q_RETURN_ARG(float, result)); + BLOCKING_INVOKE_METHOD(this, "height", Q_GENERIC_RETURN_ARG(float, result)); return result; } diff --git a/libraries/animation/src/AnimExpression.h b/libraries/animation/src/AnimExpression.h index 87fb3ca20cc..cc52060c02b 100644 --- a/libraries/animation/src/AnimExpression.h +++ b/libraries/animation/src/AnimExpression.h @@ -12,6 +12,7 @@ #define hifi_AnimExpression #include +#include #include #include #include diff --git a/libraries/animation/src/AnimationCache.cpp b/libraries/animation/src/AnimationCache.cpp index f7112cff73d..f1c90b6a68c 100644 --- a/libraries/animation/src/AnimationCache.cpp +++ b/libraries/animation/src/AnimationCache.cpp @@ -73,7 +73,7 @@ void AnimationReader::run() { // Parse the FBX directly from the QNetworkReply HFMModel::Pointer hfmModel; if (_url.path().toLower().endsWith(".fbx")) { - hfmModel = FBXSerializer().read(_data, QVariantHash(), _url.path()); + hfmModel = FBXSerializer().read(_data, hifi::VariantMultiHash(), _url.path()); } else { QString errorStr("usupported format"); emit onError(299, errorStr); @@ -97,7 +97,7 @@ QStringList Animation::getJointNames() const { if (QThread::currentThread() != thread()) { QStringList result; BLOCKING_INVOKE_METHOD(const_cast(this), "getJointNames", - Q_RETURN_ARG(QStringList, result)); + Q_GENERIC_RETURN_ARG(QStringList, result)); return result; } QStringList names; @@ -113,7 +113,7 @@ QVector Animation::getFrames() const { if (QThread::currentThread() != thread()) { QVector result; BLOCKING_INVOKE_METHOD(const_cast(this), "getFrames", - Q_RETURN_ARG(QVector, result)); + Q_GENERIC_RETURN_ARG(QVector, result)); return result; } if (_hfmModel) { diff --git a/libraries/animation/src/Flow.cpp b/libraries/animation/src/Flow.cpp index 56194ea0119..eecae48d181 100644 --- a/libraries/animation/src/Flow.cpp +++ b/libraries/animation/src/Flow.cpp @@ -12,6 +12,8 @@ #include "Rig.h" #include "AnimSkeleton.h" +#include + const std::map PRESET_FLOW_DATA = { { "hair", FlowPhysicsSettings() }, { "skirt", FlowPhysicsSettings(true, 1.0f, DEFAULT_GRAVITY, 0.65f, 0.8f, 0.45f, 0.01f) }, { "breast", FlowPhysicsSettings(true, 1.0f, DEFAULT_GRAVITY, 0.65f, 0.8f, 0.45f, 0.01f) } }; diff --git a/libraries/animation/src/Flow.h b/libraries/animation/src/Flow.h index 6235b977578..50f5adb3130 100644 --- a/libraries/animation/src/Flow.h +++ b/libraries/animation/src/Flow.h @@ -18,6 +18,9 @@ #include #include #include + +#include + #include "AnimPose.h" class Rig; diff --git a/libraries/audio-client/src/AudioClient.cpp b/libraries/audio-client/src/AudioClient.cpp index a6b8b282ed4..5bdb2a1a147 100644 --- a/libraries/audio-client/src/AudioClient.cpp +++ b/libraries/audio-client/src/AudioClient.cpp @@ -38,6 +38,8 @@ #include #endif +#include +#include #include #include #include @@ -57,6 +59,7 @@ #include "AudioClientLogging.h" #include "AudioLogging.h" #include "AudioHelpers.h" +#include "../../../interface/src/scripting/AudioDevices.h" #if defined(Q_OS_ANDROID) #include @@ -85,11 +88,11 @@ using Lock = std::unique_lock; Mutex _deviceMutex; Mutex _recordMutex; -QString defaultAudioDeviceName(QAudio::Mode mode); +QString defaultAudioDeviceName(QAudioDevice::Mode mode); -void AudioClient::setHmdAudioName(QAudio::Mode mode, const QString& name) { +void AudioClient::setHmdAudioName(QAudioDevice::Mode mode, const QString& name) { QWriteLocker lock(&_hmdNameLock); - if (mode == QAudio::AudioInput) { + if (mode == QAudioDevice::Input) { _hmdInputName = name; } else { _hmdOutputName = name; @@ -97,20 +100,27 @@ void AudioClient::setHmdAudioName(QAudio::Mode mode, const QString& name) { } // thread-safe -QList getAvailableDevices(QAudio::Mode mode, const QString& hmdName) { +QList getAvailableDevices(QAudioDevice::Mode mode, const QString& hmdName) { //get hmd device name prior to locking device mutex. in case of shutdown, this thread will be locked and audio client //cannot properly shut down. QString defDeviceName = defaultAudioDeviceName(mode); // NOTE: availableDevices() clobbers the Qt internal device list + QList devices; Lock lock(_deviceMutex); - auto devices = QAudioDeviceInfo::availableDevices(mode); + if (mode == QAudioDevice::Input) { + devices = QMediaDevices::audioInputs(); + } else if (mode == QAudioDevice::Output) { + devices = QMediaDevices::audioOutputs(); + } else if (mode == QAudioDevice::Null) { + Q_ASSERT(false); + } HifiAudioDeviceInfo defaultDesktopDevice; QList newDevices; for (auto& device : devices) { newDevices.push_back(HifiAudioDeviceInfo(device, false, mode)); - if (device.deviceName() == defDeviceName.trimmed()) { + if (device.description() == defDeviceName.trimmed()) { defaultDesktopDevice = HifiAudioDeviceInfo(device, true, mode, HifiAudioDeviceInfo::both); } } @@ -118,7 +128,7 @@ QList getAvailableDevices(QAudio::Mode mode, const QString& if (defaultDesktopDevice.getDevice().isNull()) { if (devices.size() > 0) { qCDebug(audioclient) << __FUNCTION__ << "Default device not found in list:" << defDeviceName - << "Setting Default to: " << devices.first().deviceName(); + << "Setting Default to: " << devices.first().description(); newDevices.push_front(HifiAudioDeviceInfo(devices.first(), true, mode, HifiAudioDeviceInfo::both)); } else { //current audio list is empty for some reason. @@ -131,7 +141,7 @@ QList getAvailableDevices(QAudio::Mode mode, const QString& if (!hmdName.isNull()) { HifiAudioDeviceInfo hmdDevice; foreach(auto device, newDevices) { - if (device.getDevice().deviceName() == hmdName) { + if (device.getDevice().description() == hmdName) { hmdDevice = HifiAudioDeviceInfo(device.getDevice(), true, mode, HifiAudioDeviceInfo::hmd); break; } @@ -161,8 +171,8 @@ void AudioClient::checkDevices() { hmdOutputName = _hmdOutputName; } - auto inputDevices = getAvailableDevices(QAudio::AudioInput, hmdInputName); - auto outputDevices = getAvailableDevices(QAudio::AudioOutput, hmdOutputName); + auto inputDevices = getAvailableDevices(QAudioDevice::Mode::Input, hmdInputName); + auto outputDevices = getAvailableDevices(QAudioDevice::Mode::Output, hmdOutputName); static const QMetaMethod devicesChangedSig= QMetaMethod::fromSignal(&AudioClient::devicesChanged); //only emit once the scripting interface has connected to the signal @@ -170,30 +180,30 @@ void AudioClient::checkDevices() { Lock lock(_deviceMutex); if (inputDevices != _inputDevices) { _inputDevices.swap(inputDevices); - emit devicesChanged(QAudio::AudioInput, _inputDevices); + emit devicesChanged(QAudioDevice::Mode::Input, _inputDevices); } if (outputDevices != _outputDevices) { _outputDevices.swap(outputDevices); - emit devicesChanged(QAudio::AudioOutput, _outputDevices); + emit devicesChanged(QAudioDevice::Mode::Output, _outputDevices); } } } -HifiAudioDeviceInfo AudioClient::getActiveAudioDevice(QAudio::Mode mode) const { +HifiAudioDeviceInfo AudioClient::getActiveAudioDevice(QAudioDevice::Mode mode) const { Lock lock(_deviceMutex); - if (mode == QAudio::AudioInput) { + if (mode == QAudioDevice::Mode::Input) { return _inputDeviceInfo; } else { return _outputDeviceInfo; } } -QList AudioClient::getAudioDevices(QAudio::Mode mode) const { +QList AudioClient::getAudioDevices(QAudioDevice::Mode mode) const { Lock lock(_deviceMutex); - if (mode == QAudio::AudioInput) { + if (mode == QAudioDevice::Mode::Input) { return _inputDevices; } else { return _outputDevices; @@ -323,10 +333,8 @@ AudioClient::AudioClient() { // Set up the desired audio format, since scripting API expects it to be set and audio scripting API // is initialized before audio thread starts. _desiredInputFormat.setSampleRate(AudioConstants::SAMPLE_RATE); - _desiredInputFormat.setSampleSize(16); - _desiredInputFormat.setCodec("audio/pcm"); - _desiredInputFormat.setSampleType(QAudioFormat::SignedInt); - _desiredInputFormat.setByteOrder(QAudioFormat::LittleEndian); + _desiredInputFormat.setSampleFormat(QAudioFormat::Int16); + _desiredInputFormat.setChannelCount(1); _desiredOutputFormat = _desiredInputFormat; @@ -352,8 +360,8 @@ AudioClient::AudioClient() { connect(&_receivedAudioStream, &InboundAudioStream::mismatchedAudioCodec, this, &AudioClient::handleMismatchAudioFormat); // initialize wasapi; if getAvailableDevices is called from the CheckDevicesThread before this, it will crash - defaultAudioDeviceName(QAudio::AudioInput); - defaultAudioDeviceName(QAudio::AudioOutput); + defaultAudioDeviceName(QAudioDevice::Mode::Input); + defaultAudioDeviceName(QAudioDevice::Mode::Output); checkDevices(); // start a thread to detect any device changes @@ -461,7 +469,7 @@ void AudioClient::setAudioPaused(bool pause) { } } -HifiAudioDeviceInfo getNamedAudioDeviceForMode(QAudio::Mode mode, const QString& deviceName, const QString& hmdName, bool isHmd=false) { +HifiAudioDeviceInfo getNamedAudioDeviceForMode(QAudioDevice::Mode mode, const QString& deviceName, const QString& hmdName, bool isHmd=false) { HifiAudioDeviceInfo result; foreach (HifiAudioDeviceInfo audioDevice, getAvailableDevices(mode,hmdName)) { if (audioDevice.deviceName().trimmed() == deviceName.trimmed()) { @@ -518,7 +526,7 @@ QString AudioClient::getWinDeviceName(wchar_t* guid) { #endif -HifiAudioDeviceInfo defaultAudioDeviceForMode(QAudio::Mode mode, const QString& hmdName) { +HifiAudioDeviceInfo defaultAudioDeviceForMode(QAudioDevice::Mode mode, const QString& hmdName) { QString deviceName = defaultAudioDeviceName(mode); #if defined (Q_OS_ANDROID) if (mode == QAudio::AudioInput) { @@ -537,7 +545,7 @@ HifiAudioDeviceInfo defaultAudioDeviceForMode(QAudio::Mode mode, const QString& return getNamedAudioDeviceForMode(mode, deviceName, hmdName); } -QString defaultAudioDeviceName(QAudio::Mode mode) { +QString defaultAudioDeviceName(QAudioDevice::Mode mode) { QString deviceName; #ifdef __APPLE__ @@ -638,24 +646,24 @@ QString defaultAudioDeviceName(QAudio::Mode mode) { #endif #ifdef Q_OS_LINUX - if ( mode == QAudio::AudioInput ) { - deviceName = QAudioDeviceInfo::defaultInputDevice().deviceName(); + if ( mode == QAudioDevice::Mode::Input ) { + deviceName = QMediaDevices::defaultAudioInput().description(); } else { - deviceName = QAudioDeviceInfo::defaultOutputDevice().deviceName(); + deviceName = QMediaDevices::defaultAudioOutput().description(); } #endif return deviceName; } -bool AudioClient::getNamedAudioDeviceForModeExists(QAudio::Mode mode, const QString& deviceName) { +bool AudioClient::getNamedAudioDeviceForModeExists(QAudioDevice::Mode mode, const QString& deviceName) { QReadLocker readLock(&_hmdNameLock); - QString hmdName = mode == QAudio::AudioInput ? _hmdInputName : _hmdOutputName; + QString hmdName = mode == QAudioDevice::Mode::Input ? _hmdInputName : _hmdOutputName; return (getNamedAudioDeviceForMode(mode, deviceName, hmdName).deviceName() == deviceName); } // attempt to use the native sample rate and channel count -bool nativeFormatForAudioDevice(const QAudioDeviceInfo& audioDevice, QAudioFormat& audioFormat) { +bool nativeFormatForAudioDevice(const QAudioDevice& audioDevice, QAudioFormat& audioFormat) { audioFormat = audioDevice.preferredFormat(); @@ -666,10 +674,7 @@ bool nativeFormatForAudioDevice(const QAudioDeviceInfo& audioDevice, QAudioForma return false; } - audioFormat.setCodec("audio/pcm"); - audioFormat.setSampleSize(16); - audioFormat.setSampleType(QAudioFormat::SignedInt); - audioFormat.setByteOrder(QAudioFormat::LittleEndian); + audioFormat.setSampleFormat(QAudioFormat::Int16); if (!audioDevice.isFormatSupported(audioFormat)) { qCWarning(audioclient) << "The native format is" << audioFormat << "but isFormatSupported() failed."; @@ -688,7 +693,7 @@ bool nativeFormatForAudioDevice(const QAudioDeviceInfo& audioDevice, QAudioForma return true; } -bool adjustedFormatForAudioDevice(const QAudioDeviceInfo& audioDevice, +bool adjustedFormatForAudioDevice(const QAudioDevice& audioDevice, const QAudioFormat& desiredAudioFormat, QAudioFormat& adjustedAudioFormat) { @@ -804,10 +809,8 @@ void AudioClient::start() { // set up the desired audio format _desiredInputFormat.setSampleRate(AudioConstants::SAMPLE_RATE); - _desiredInputFormat.setSampleSize(16); - _desiredInputFormat.setCodec("audio/pcm"); - _desiredInputFormat.setSampleType(QAudioFormat::SignedInt); - _desiredInputFormat.setByteOrder(QAudioFormat::LittleEndian); + _desiredInputFormat.setSampleFormat(QAudioFormat::Int16); + _desiredInputFormat.setChannelCount(1); _desiredOutputFormat = _desiredInputFormat; @@ -823,8 +826,8 @@ void AudioClient::start() { // Input was originally set to HifiAudioDeviceInfo(), but that was causing trouble. //Original comment: initialize input to the dummy device to prevent starves - switchInputToAudioDevice(defaultAudioDeviceForMode(QAudio::AudioInput, QString())); - switchOutputToAudioDevice(defaultAudioDeviceForMode(QAudio::AudioOutput, QString())); + switchInputToAudioDevice(defaultAudioDeviceForMode(QAudioDevice::Mode::Input, QString())); + switchOutputToAudioDevice(defaultAudioDeviceForMode(QAudioDevice::Mode::Output, QString())); #if defined(Q_OS_ANDROID) connect(&_checkInputTimer, &QTimer::timeout, this, &AudioClient::checkInputTimeout); @@ -1030,25 +1033,25 @@ void AudioClient::selectAudioFormat(const QString& selectedCodecName) { } -bool AudioClient::switchAudioDevice(QAudio::Mode mode, const HifiAudioDeviceInfo& deviceInfo) { +bool AudioClient::switchAudioDevice(QAudioDevice::Mode mode, const HifiAudioDeviceInfo& deviceInfo) { auto device = deviceInfo; if (deviceInfo.getDevice().isNull()) { qCDebug(audioclient) << __FUNCTION__ << " switching to null device :" - << deviceInfo.deviceName() << " : " << deviceInfo.getDevice().deviceName(); + << deviceInfo.deviceName() << " : " << deviceInfo.getDevice().description(); } - if (mode == QAudio::AudioInput) { + if (mode == QAudioDevice::Mode::Input) { return switchInputToAudioDevice(device); } else { return switchOutputToAudioDevice(device); } } -bool AudioClient::switchAudioDevice(QAudio::Mode mode, const QString& deviceName, bool isHmd) { +bool AudioClient::switchAudioDevice(QAudioDevice::Mode mode, const QString& deviceName, bool isHmd) { QString hmdName; { QReadLocker readLock(&_hmdNameLock); - hmdName = mode == QAudio::AudioInput ? _hmdInputName : _hmdOutputName; + hmdName = mode == QAudioDevice::Mode::Input ? _hmdInputName : _hmdOutputName; } return switchAudioDevice(mode, getNamedAudioDeviceForMode(mode, deviceName, hmdName, isHmd)); } @@ -1815,7 +1818,9 @@ void AudioClient::setAcousticEchoCancellation(bool enable, bool emitSignal) { bool AudioClient::setIsStereoInput(bool isStereoInput) { bool stereoInputChanged = false; - if (isStereoInput != _isStereoInput && _inputDeviceInfo.getDevice().supportedChannelCounts().contains(2)) { + if (isStereoInput != _isStereoInput + && (_inputDeviceInfo.getDevice().minimumChannelCount() <= 2 + && _inputDeviceInfo.getDevice().maximumChannelCount() >= 2)) { _isStereoInput = isStereoInput; stereoInputChanged = true; @@ -1880,8 +1885,8 @@ void AudioClient::outputFormatChanged() { bool AudioClient::switchInputToAudioDevice(const HifiAudioDeviceInfo inputDeviceInfo, bool isShutdownRequest) { Q_ASSERT_X(QThread::currentThread() == thread(), Q_FUNC_INFO, "Function invoked on wrong thread"); - qCDebug(audioclient) << __FUNCTION__ << "_inputDeviceInfo: [" << _inputDeviceInfo.deviceName() << ":" << _inputDeviceInfo.getDevice().deviceName() - << "-- inputDeviceInfo:" << inputDeviceInfo.deviceName() << ":" << inputDeviceInfo.getDevice().deviceName() << "]"; + qCDebug(audioclient) << __FUNCTION__ << "_inputDeviceInfo: [" << _inputDeviceInfo.deviceName() << ":" << _inputDeviceInfo.getDevice().description() + << "-- inputDeviceInfo:" << inputDeviceInfo.deviceName() << ":" << inputDeviceInfo.getDevice().description() << "]"; bool supportedFormat = false; // NOTE: device start() uses the Qt internal device list @@ -1903,7 +1908,7 @@ bool AudioClient::switchInputToAudioDevice(const HifiAudioDeviceInfo inputDevice _audioInput = NULL; _numInputCallbackBytes = 0; - _inputDeviceInfo.setDevice(QAudioDeviceInfo()); + _inputDeviceInfo.setDevice(QAudioDevice()); } if (_dummyAudioInput) { @@ -1934,11 +1939,11 @@ bool AudioClient::switchInputToAudioDevice(const HifiAudioDeviceInfo inputDevice } if (!inputDeviceInfo.getDevice().isNull()) { - qCDebug(audioclient) << "The audio input device" << inputDeviceInfo.deviceName() << ":" << inputDeviceInfo.getDevice().deviceName() << "is available."; + qCDebug(audioclient) << "The audio input device" << inputDeviceInfo.deviceName() << ":" << inputDeviceInfo.getDevice().description() << "is available."; //do not update UI that we're changing devices if default or same device _inputDeviceInfo = inputDeviceInfo; - emit deviceChanged(QAudio::AudioInput, _inputDeviceInfo); + emit deviceChanged(QAudioDevice::Mode::Input, _inputDeviceInfo); if (adjustedFormatForAudioDevice(_inputDeviceInfo.getDevice(), _desiredInputFormat, _inputFormat)) { qCDebug(audioclient) << "The format to be used for audio input is" << _inputFormat; @@ -1949,8 +1954,8 @@ bool AudioClient::switchInputToAudioDevice(const HifiAudioDeviceInfo inputDevice && _inputFormat.sampleRate() != _desiredInputFormat.sampleRate()) { qCDebug(audioclient) << "Attemping to create a resampler for input format to network format."; - assert(_inputFormat.sampleSize() == 16); - assert(_desiredInputFormat.sampleSize() == 16); + assert(_inputFormat.sampleFormat() == QAudioFormat::Int16); + assert(_desiredInputFormat.sampleFormat() == QAudioFormat::Int16); int channelCount = (_inputFormat.channelCount() == 2 && _desiredInputFormat.channelCount() == 2) ? 2 : 1; _inputToNetworkResampler = new AudioSRC(_inputFormat.sampleRate(), _desiredInputFormat.sampleRate(), channelCount); @@ -1965,7 +1970,7 @@ bool AudioClient::switchInputToAudioDevice(const HifiAudioDeviceInfo inputDevice // if the user wants stereo but this device can't provide then bail if (!_isStereoInput || _inputFormat.channelCount() == 2) { - _audioInput = new QAudioInput(_inputDeviceInfo.getDevice(), _inputFormat, this); + _audioInput = new QAudioSource(_inputDeviceInfo.getDevice(), _inputFormat, this); _numInputCallbackBytes = calculateNumberOfInputCallbackBytes(_inputFormat); _audioInput->setBufferSize(_numInputCallbackBytes); // different audio input devices may have different volumes @@ -2000,8 +2005,8 @@ bool AudioClient::switchInputToAudioDevice(const HifiAudioDeviceInfo inputDevice // This enables clients without a mic to still receive an audio stream from the mixer. if (!_audioInput) { qCDebug(audioclient) << "Audio input device is not available, using dummy input."; - _inputDeviceInfo.setDevice(QAudioDeviceInfo()); - emit deviceChanged(QAudio::AudioInput, _inputDeviceInfo); + _inputDeviceInfo.setDevice(QAudioDevice()); + emit deviceChanged(QAudioDevice::Mode::Input, _inputDeviceInfo); _inputFormat = _desiredInputFormat; qCDebug(audioclient) << "The format to be used for audio input is" << _inputFormat; @@ -2116,8 +2121,8 @@ void AudioClient::noteAwakening() { bool AudioClient::switchOutputToAudioDevice(const HifiAudioDeviceInfo outputDeviceInfo, bool isShutdownRequest) { Q_ASSERT_X(QThread::currentThread() == thread(), Q_FUNC_INFO, "Function invoked on wrong thread"); - qCDebug(audioclient) << __FUNCTION__ << "_outputdeviceInfo: [" << _outputDeviceInfo.deviceName() << ":" << _outputDeviceInfo.getDevice().deviceName() - << "-- outputDeviceInfo:" << outputDeviceInfo.deviceName() << ":" << outputDeviceInfo.getDevice().deviceName() << "]"; + qCDebug(audioclient) << __FUNCTION__ << "_outputdeviceInfo: [" << _outputDeviceInfo.deviceName() << ":" << _outputDeviceInfo.getDevice().description() + << "-- outputDeviceInfo:" << outputDeviceInfo.deviceName() << ":" << outputDeviceInfo.getDevice().description() << "]"; bool supportedFormat = false; // NOTE: device start() uses the Qt internal device list @@ -2156,7 +2161,7 @@ bool AudioClient::switchOutputToAudioDevice(const HifiAudioDeviceInfo outputDevi delete[] _localOutputMixBuffer; _localOutputMixBuffer = NULL; - _outputDeviceInfo.setDevice(QAudioDeviceInfo()); + _outputDeviceInfo.setDevice(QAudioDevice()); } // cleanup any resamplers @@ -2181,11 +2186,11 @@ bool AudioClient::switchOutputToAudioDevice(const HifiAudioDeviceInfo outputDevi } if (!outputDeviceInfo.getDevice().isNull()) { - qCDebug(audioclient) << "The audio output device" << outputDeviceInfo.deviceName() << ":" << outputDeviceInfo.getDevice().deviceName() << "is available."; + qCDebug(audioclient) << "The audio output device" << outputDeviceInfo.deviceName() << ":" << outputDeviceInfo.getDevice().description() << "is available."; //do not update UI that we're changing devices if default or same device _outputDeviceInfo = outputDeviceInfo; - emit deviceChanged(QAudio::AudioOutput, _outputDeviceInfo); + emit deviceChanged(QAudioDevice::Mode::Output, _outputDeviceInfo); if (adjustedFormatForAudioDevice(_outputDeviceInfo.getDevice(), _desiredOutputFormat, _outputFormat)) { qCDebug(audioclient) << "The format to be used for audio output is" << _outputFormat; @@ -2196,8 +2201,8 @@ bool AudioClient::switchOutputToAudioDevice(const HifiAudioDeviceInfo outputDevi && _desiredOutputFormat.sampleRate() != _outputFormat.sampleRate()) { qCDebug(audioclient) << "Attemping to create a resampler for network format to output format."; - assert(_desiredOutputFormat.sampleSize() == 16); - assert(_outputFormat.sampleSize() == 16); + assert(_desiredOutputFormat.sampleFormat() == QAudioFormat::Int16); + assert(_outputFormat.sampleFormat() == QAudioFormat::Int16); _networkToOutputResampler = new AudioSRC(_desiredOutputFormat.sampleRate(), _outputFormat.sampleRate(), OUTPUT_CHANNEL_COUNT); _localToOutputResampler = new AudioSRC(_desiredOutputFormat.sampleRate(), _outputFormat.sampleRate(), OUTPUT_CHANNEL_COUNT); @@ -2209,7 +2214,7 @@ bool AudioClient::switchOutputToAudioDevice(const HifiAudioDeviceInfo outputDevi outputFormatChanged(); // setup our general output device for audio-mixer audio - _audioOutput = new QAudioOutput(_outputDeviceInfo.getDevice(), _outputFormat, this); + _audioOutput = new QAudioSink(_outputDeviceInfo.getDevice(), _outputFormat, this); int deviceChannelCount = _outputFormat.channelCount(); int frameSize = (AudioConstants::NETWORK_FRAME_SAMPLES_PER_CHANNEL * deviceChannelCount * _outputFormat.sampleRate()) / _desiredOutputFormat.sampleRate(); @@ -2222,8 +2227,10 @@ bool AudioClient::switchOutputToAudioDevice(const HifiAudioDeviceInfo outputDevi _audioOutput->setBufferSize(requestedSize * 8); #endif - connect(_audioOutput, &QAudioOutput::notify, this, &AudioClient::outputNotify); + // QT6TODO: I have no idea what to do about this, QAudioSins has no notify signal in Qt6 + //connect(_audioOutput, &QAudioOutput::notify, this, &AudioClient::outputNotify); + // QT6TODO: we could set the buffer size before starting the IO device and sink // start the output device _audioOutputIODevice.start(); _audioOutput->start(&_audioOutputIODevice); @@ -2231,7 +2238,7 @@ bool AudioClient::switchOutputToAudioDevice(const HifiAudioDeviceInfo outputDevi // initialize mix buffers // restrict device callback to _outputPeriod samples - _outputPeriod = _audioOutput->periodSize() / AudioConstants::SAMPLE_SIZE; + _outputPeriod = _audioOutput->bufferSize() / AudioConstants::SAMPLE_SIZE / _audioOutput->format().channelCount(); // device callback may exceed reported period, so double it to avoid stutter _outputPeriod *= 2; @@ -2267,7 +2274,7 @@ bool AudioClient::switchOutputToAudioDevice(const HifiAudioDeviceInfo outputDevi localAudioLock.unlock(); // setup a loopback audio output device - _loopbackAudioOutput = new QAudioOutput(outputDeviceInfo.getDevice(), _outputFormat, this); + _loopbackAudioOutput = new QAudioSink(outputDeviceInfo.getDevice(), _outputFormat, this); _timeSinceLastReceived.start(); diff --git a/libraries/audio-client/src/AudioClient.h b/libraries/audio-client/src/AudioClient.h index 06316efee26..0f09ed68036 100644 --- a/libraries/audio-client/src/AudioClient.h +++ b/libraries/audio-client/src/AudioClient.h @@ -20,6 +20,8 @@ #include #include +#include +#include #include #include #include @@ -168,15 +170,15 @@ class AudioClient : public AbstractAudioInterface, public Dependency { bool outputLocalInjector(const AudioInjectorPointer& injector) override; - HifiAudioDeviceInfo getActiveAudioDevice(QAudio::Mode mode) const; - QList getAudioDevices(QAudio::Mode mode) const; + HifiAudioDeviceInfo getActiveAudioDevice(QAudioDevice::Mode mode) const; + QList getAudioDevices(QAudioDevice::Mode mode) const; void enablePeakValues(bool enable) { _enablePeakValues = enable; } bool peakValuesAvailable() const; static const float CALLBACK_ACCELERATOR_RATIO; - bool getNamedAudioDeviceForModeExists(QAudio::Mode mode, const QString& deviceName); + bool getNamedAudioDeviceForModeExists(QAudioDevice::Mode mode, const QString& deviceName); void setRecording(bool isRecording) { _isRecording = isRecording; }; bool getRecording() { return _isRecording; }; @@ -254,9 +256,9 @@ public slots: bool shouldLoopbackInjectors() override { return _shouldEchoToServer; } // calling with a null QAudioDevice will use the system default - bool switchAudioDevice(QAudio::Mode mode, const HifiAudioDeviceInfo& deviceInfo = HifiAudioDeviceInfo()); - bool switchAudioDevice(QAudio::Mode mode, const QString& deviceName, bool isHmd); - void setHmdAudioName(QAudio::Mode mode, const QString& name); + bool switchAudioDevice(QAudioDevice::Mode mode, const HifiAudioDeviceInfo& deviceInfo = HifiAudioDeviceInfo()); + bool switchAudioDevice(QAudioDevice::Mode mode, const QString& deviceName, bool isHmd); + void setHmdAudioName(QAudioDevice::Mode mode, const QString& name); // Qt opensles plugin is not able to detect when the headset is plugged in void setHeadsetPluggedIn(bool pluggedIn); @@ -293,8 +295,8 @@ public slots: void changeDevice(const HifiAudioDeviceInfo& outputDeviceInfo); - void deviceChanged(QAudio::Mode mode, const HifiAudioDeviceInfo& device); - void devicesChanged(QAudio::Mode mode, const QList& devices); + void deviceChanged(QAudioDevice::Mode mode, const HifiAudioDeviceInfo& device); + void devicesChanged(QAudioDevice::Mode mode, const QList& devices); void peakValueListChanged(const QList peakValueList); void receivedFirstPacket(); @@ -372,19 +374,19 @@ public slots: Gate _gate{ this }; Mutex _injectorsMutex; - QAudioInput* _audioInput{ nullptr }; + QAudioSource* _audioInput{ nullptr }; QTimer* _dummyAudioInput{ nullptr }; QAudioFormat _desiredInputFormat; QAudioFormat _inputFormat; QIODevice* _inputDevice{ nullptr }; int _numInputCallbackBytes{ 0 }; - QAudioOutput* _audioOutput{ nullptr }; + QAudioSink* _audioOutput{ nullptr }; std::atomic _audioOutputInitialized { false }; QAudioFormat _desiredOutputFormat; QAudioFormat _outputFormat; int _outputFrameSize{ 0 }; int _numOutputCallbackBytes{ 0 }; - QAudioOutput* _loopbackAudioOutput{ nullptr }; + QAudioSink* _loopbackAudioOutput{ nullptr }; QIODevice* _loopbackOutputDevice{ nullptr }; AudioRingBuffer _inputRingBuffer{ 0 }; LocalInjectorsStream _localInjectorsStream{ 0 , 1 }; diff --git a/libraries/audio-client/src/AudioFileWav.cpp b/libraries/audio-client/src/AudioFileWav.cpp index 613628883c1..57188bd62da 100644 --- a/libraries/audio-client/src/AudioFileWav.cpp +++ b/libraries/audio-client/src/AudioFileWav.cpp @@ -60,9 +60,9 @@ void AudioFileWav::addHeader(const QAudioFormat& audioFormat) { stream << quint16(1); stream << quint16(audioFormat.channelCount()); stream << quint32(audioFormat.sampleRate()); - stream << quint32(audioFormat.sampleRate() * audioFormat.channelCount() * audioFormat.sampleSize() / 8); // bytes per second - stream << quint16(audioFormat.channelCount() * audioFormat.sampleSize() / 8); // block align - stream << quint16(audioFormat.sampleSize()); // bits Per Sample + stream << quint32(audioFormat.sampleRate() * audioFormat.channelCount() * audioFormat.bytesPerSample()); // bytes per second + stream << quint16(audioFormat.channelCount() * audioFormat.bytesPerSample()); // block align + stream << quint16(audioFormat.bytesPerSample() * 8); // bits Per Sample // Init data chunck stream.writeRawData("data", 4); stream << quint32(0); diff --git a/libraries/audio-client/src/HifiAudioDeviceInfo.cpp b/libraries/audio-client/src/HifiAudioDeviceInfo.cpp index ed3d94f2b81..c3545fde84d 100644 --- a/libraries/audio-client/src/HifiAudioDeviceInfo.cpp +++ b/libraries/audio-client/src/HifiAudioDeviceInfo.cpp @@ -14,7 +14,7 @@ const QString HifiAudioDeviceInfo::DEFAULT_DEVICE_NAME = HIFI_AUDIO_DEVICE_INFO_DEFAULT_DEVICE_NAME; -void HifiAudioDeviceInfo::setDevice(QAudioDeviceInfo devInfo) { +void HifiAudioDeviceInfo::setDevice(QAudioDevice devInfo) { _audioDeviceInfo = devInfo; } @@ -23,7 +23,7 @@ HifiAudioDeviceInfo& HifiAudioDeviceInfo::operator=(const HifiAudioDeviceInfo& o _mode = other.getMode(); _isDefault = other.isDefault(); _deviceType = other.getDeviceType(); - _debugName = other.getDevice().deviceName(); + _debugName = other.getDevice().description(); return *this; } diff --git a/libraries/audio-client/src/HifiAudioDeviceInfo.h b/libraries/audio-client/src/HifiAudioDeviceInfo.h index dee7d20b23a..6560be2c547 100644 --- a/libraries/audio-client/src/HifiAudioDeviceInfo.h +++ b/libraries/audio-client/src/HifiAudioDeviceInfo.h @@ -15,8 +15,9 @@ #include -#include +//#include #include +#include #include #define HIFI_AUDIO_DEVICE_INFO_DEFAULT_DEVICE_NAME "default " @@ -37,20 +38,20 @@ class HifiAudioDeviceInfo : public QObject { _mode = deviceInfo.getMode(); _isDefault = deviceInfo.isDefault(); _deviceType = deviceInfo.getDeviceType(); - _debugName = deviceInfo.getDevice().deviceName(); + _debugName = deviceInfo.getDevice().description(); } - HifiAudioDeviceInfo(QAudioDeviceInfo deviceInfo, bool isDefault, QAudio::Mode mode, DeviceType devType=both) : + HifiAudioDeviceInfo(QAudioDevice deviceInfo, bool isDefault, QAudioDevice::Mode mode, DeviceType devType=both) : _audioDeviceInfo(deviceInfo), _isDefault(isDefault), _mode(mode), _deviceType(devType), - _debugName(deviceInfo.deviceName()) { + _debugName(deviceInfo.description()) { } - void setMode(QAudio::Mode mode) { _mode = mode; } + void setMode(QAudioDevice::Mode mode) { _mode = mode; } void setIsDefault() { _isDefault = true; } - void setDevice(QAudioDeviceInfo devInfo); + void setDevice(QAudioDevice devInfo); QString deviceName() const { #if defined(Q_OS_ANDROID) return _audioDeviceInfo.deviceName(); @@ -58,21 +59,21 @@ class HifiAudioDeviceInfo : public QObject { if (_isDefault) { return DEFAULT_DEVICE_NAME; } else { - return _audioDeviceInfo.deviceName(); + return _audioDeviceInfo.description(); } } - QAudioDeviceInfo getDevice() const { return _audioDeviceInfo; } + QAudioDevice getDevice() const { return _audioDeviceInfo; } bool isDefault() const { return _isDefault; } - QAudio::Mode getMode() const { return _mode; } + QAudioDevice::Mode getMode() const { return _mode; } DeviceType getDeviceType() const { return _deviceType; } HifiAudioDeviceInfo& operator=(const HifiAudioDeviceInfo& other); bool operator==(const HifiAudioDeviceInfo& rhs) const; bool operator!=(const HifiAudioDeviceInfo& rhs) const; private: - QAudioDeviceInfo _audioDeviceInfo; + QAudioDevice _audioDeviceInfo; bool _isDefault { false }; - QAudio::Mode _mode { QAudio::AudioInput }; + QAudioDevice::Mode _mode { QAudioDevice::Input }; DeviceType _deviceType{ both }; QString _debugName; diff --git a/libraries/audio/src/InboundAudioStream.h b/libraries/audio/src/InboundAudioStream.h index b42609d5769..9d00d292388 100644 --- a/libraries/audio/src/InboundAudioStream.h +++ b/libraries/audio/src/InboundAudioStream.h @@ -122,8 +122,8 @@ public slots: protected: // disallow copying of InboundAudioStream objects - InboundAudioStream(const InboundAudioStream&); - InboundAudioStream& operator= (const InboundAudioStream&); + InboundAudioStream(const InboundAudioStream&) = delete; + InboundAudioStream& operator= (const InboundAudioStream&) = delete; /// parses the info between the seq num and the audio data in the network packet and calculates /// how many audio samples this packet contains (used when filling in samples for dropped packets). diff --git a/libraries/auto-updater/src/AutoUpdater.h b/libraries/auto-updater/src/AutoUpdater.h index c788ac31d10..34350f8943e 100644 --- a/libraries/auto-updater/src/AutoUpdater.h +++ b/libraries/auto-updater/src/AutoUpdater.h @@ -22,7 +22,7 @@ #include #include #include -#include +#include #include #include diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index b5c9e2eb0aa..be285c7ccc7 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -26,6 +26,7 @@ #include #include #include +#include #include #include @@ -1899,7 +1900,7 @@ QVector AvatarData::getJointRotations() const { if (QThread::currentThread() != thread()) { QVector result; BLOCKING_INVOKE_METHOD(const_cast(this), "getJointRotations", - Q_RETURN_ARG(QVector, result)); + Q_GENERIC_RETURN_ARG(QVector, result)); return result; } QReadLocker readLock(&_jointDataLock); diff --git a/libraries/baking/src/JSBaker.cpp b/libraries/baking/src/JSBaker.cpp index e4fdd742561..26786a6a2b6 100644 --- a/libraries/baking/src/JSBaker.cpp +++ b/libraries/baking/src/JSBaker.cpp @@ -274,7 +274,7 @@ bool JSBaker::canOmitNewLine(QChar previousCharacter, QChar nextCharacter) { //Check if character is alphabet, number or one of the following: '_', '$', '\\' or a non-ASCII character bool JSBaker::isAlphanum(QChar c) { return ((c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') - || c == '_' || c == '$' || c == '\\' || c > ASCII_CHARACTERS_UPPER_LIMIT); + || c == '_' || c == '$' || c == '\\' || c > QChar(ASCII_CHARACTERS_UPPER_LIMIT)); } bool JSBaker::isNonAscii(QChar c) { diff --git a/libraries/baking/src/MaterialBaker.cpp b/libraries/baking/src/MaterialBaker.cpp index 631a10fd11f..767eac0eb96 100644 --- a/libraries/baking/src/MaterialBaker.cpp +++ b/libraries/baking/src/MaterialBaker.cpp @@ -139,7 +139,7 @@ void MaterialBaker::processMaterial() { if (QImageReader::supportedImageFormats().contains(extension.toLatin1())) { TextureKey textureKey(textureURL, type); if (!_textureBakers.contains(textureKey)) { - auto baseTextureFileName = _textureFileNamer.createBaseTextureFileName(textureURL.fileName(), type); + auto baseTextureFileName = _textureFileNamer.createBaseTextureFileName(QFileInfo(textureURL.fileName()), type); QSharedPointer textureBaker { new TextureBaker(textureURL, type, _textureOutputDir, baseTextureFileName, content), diff --git a/libraries/baking/src/ModelBaker.cpp b/libraries/baking/src/ModelBaker.cpp index 11b52ba69c8..8d8f4c93029 100644 --- a/libraries/baking/src/ModelBaker.cpp +++ b/libraries/baking/src/ModelBaker.cpp @@ -70,7 +70,7 @@ void ModelBaker::setMappingURL(const QUrl& mappingURL) { _mappingURL = mappingURL; } -void ModelBaker::setMapping(const hifi::VariantHash& mapping) { +void ModelBaker::setMapping(const hifi::VariantMultiHash& mapping) { _mapping = mapping; } @@ -227,7 +227,7 @@ void ModelBaker::bakeSourceCopy() { handleError("Could not recognize file type of model file " + _originalOutputModelPath); return; } - hifi::VariantHash serializerMapping = _mapping; + hifi::VariantMultiHash serializerMapping = _mapping; serializerMapping["combineParts"] = true; // set true so that OBJSerializer reads material info from material library serializerMapping["deduplicateIndices"] = true; // Draco compression also deduplicates, but we might as well shave it off to save on some earlier processing (currently FBXSerializer only) hfm::Model::Pointer loadedModel = serializer->read(modelData, serializerMapping, _modelURL); @@ -380,7 +380,7 @@ void ModelBaker::outputUnbakedFST() { outputFSTFilename += FST_EXTENSION; QString outputFSTURL = _originalOutputDir + "/" + outputFSTFilename; - hifi::VariantHash outputMapping; + hifi::VariantMultiHash outputMapping; outputMapping[FST_VERSION_FIELD] = FST_VERSION; outputMapping[FILENAME_FIELD] = _modelURL.fileName(); outputMapping[COMMENT_FIELD] = "This FST file was generated by Oven for use during rebaking. It is not part of the original model. This file's existence is subject to change."; diff --git a/libraries/baking/src/ModelBaker.h b/libraries/baking/src/ModelBaker.h index 4280f8af70b..af33bdf0bf7 100644 --- a/libraries/baking/src/ModelBaker.h +++ b/libraries/baking/src/ModelBaker.h @@ -45,7 +45,7 @@ class ModelBaker : public Baker { void setOutputURLSuffix(const QUrl& urlSuffix); void setMappingURL(const QUrl& mappingURL); - void setMapping(const hifi::VariantHash& mapping); + void setMapping(const hifi::VariantMultiHash& mapping); void initializeOutputDirs(); @@ -74,7 +74,7 @@ public slots: QUrl _modelURL; QUrl _outputURLSuffix; QUrl _mappingURL; - hifi::VariantHash _mapping; + hifi::VariantMultiHash _mapping; QString _bakedOutputDir; QString _originalOutputDir; QString _originalOutputModelPath; diff --git a/libraries/controllers/src/controllers/UserInputMapper.h b/libraries/controllers/src/controllers/UserInputMapper.h index c20b040a850..065d5b27763 100644 --- a/libraries/controllers/src/controllers/UserInputMapper.h +++ b/libraries/controllers/src/controllers/UserInputMapper.h @@ -232,9 +232,11 @@ namespace controller { } Q_DECLARE_METATYPE(controller::Input::NamedPair) -Q_DECLARE_METATYPE(controller::Pose) +// QT6TODO: what to do about compiler error here? +//Q_DECLARE_METATYPE(controller::Pose) Q_DECLARE_METATYPE(QVector) -Q_DECLARE_METATYPE(controller::Input) +// QT6TODO: what to do about compiler error here? +//Q_DECLARE_METATYPE(controller::Input) Q_DECLARE_METATYPE(controller::Action) Q_DECLARE_METATYPE(QVector) Q_DECLARE_METATYPE(controller::Hand) diff --git a/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.cpp index a080a094a23..dc6dc860d68 100644 --- a/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.cpp @@ -14,7 +14,7 @@ #include #include #include -#include +#include #include #include diff --git a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp index f184027570f..4b1cc3c1677 100644 --- a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp @@ -21,7 +21,7 @@ #include #include -#include +#include #include #include diff --git a/libraries/display-plugins/src/display-plugins/stereo/StereoDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/stereo/StereoDisplayPlugin.cpp index 3ff3441f5cf..aaa7dd4be23 100644 --- a/libraries/display-plugins/src/display-plugins/stereo/StereoDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/stereo/StereoDisplayPlugin.cpp @@ -9,9 +9,8 @@ #include "StereoDisplayPlugin.h" #include -#include +#include #include -#include #include #include diff --git a/libraries/embedded-webserver/src/HTTPManager.cpp b/libraries/embedded-webserver/src/HTTPManager.cpp index c89c46bd822..0fe4bf3460e 100644 --- a/libraries/embedded-webserver/src/HTTPManager.cpp +++ b/libraries/embedded-webserver/src/HTTPManager.cpp @@ -17,6 +17,7 @@ #include #include #include +#include #include "HTTPConnection.h" #include "EmbeddedWebserverLogging.h" diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp index 82e11f72a00..a5acebd315e 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp @@ -465,7 +465,8 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& shapeInfo) { pointCollection[i][j] = scaleToFit * (pointCollection[i][j] + offset) - registrationOffset; } } - shapeInfo.setParams(type, 0.5f * extents, getCompoundShapeURL() + model->getSnapModelToRegistrationPoint()); + // QT6TODO: I have no idea what implicit conversion from bool to QString did in Qt5 + shapeInfo.setParams(type, 0.5f * extents, getCompoundShapeURL() + QString::number(model->getSnapModelToRegistrationPoint())); adjustShapeInfoByRegistration(shapeInfo, model->getSnapModelToRegistrationPoint()); } else if (type >= SHAPE_TYPE_SIMPLE_HULL && type <= SHAPE_TYPE_STATIC_MESH) { updateModelBounds(); @@ -698,7 +699,7 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& shapeInfo) { } } - shapeInfo.setParams(type, 0.5f * extents.size(), getModelURL() + model->getSnapModelToRegistrationPoint()); + shapeInfo.setParams(type, 0.5f * extents.size(), getModelURL() + QString::number(model->getSnapModelToRegistrationPoint())); adjustShapeInfoByRegistration(shapeInfo, model->getSnapModelToRegistrationPoint()); } else { EntityItem::computeShapeInfo(shapeInfo); diff --git a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp index 69b59e947b4..dd281c93ce2 100644 --- a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp @@ -13,7 +13,7 @@ #include #include -#include +#include #include #include #include @@ -59,7 +59,7 @@ static uint8_t YOUTUBE_MAX_FPS = 30; static std::atomic _currentWebCount(0); static const uint32_t MAX_CONCURRENT_WEB_VIEWS = 20; -static QTouchDevice _touchDevice; +static std::shared_ptr _touchDevice; static uint8_t CUSTOM_PIPELINE_NUMBER; // transparent, forward, shadow, fade @@ -139,10 +139,13 @@ WebEntityRenderer::WebEntityRenderer(const EntityItemPointer& entity) : Parent(e static std::once_flag once; std::call_once(once, [&]{ CUSTOM_PIPELINE_NUMBER = render::ShapePipeline::registerCustomShapePipelineFactory(webPipelineFactory); - _touchDevice.setCapabilities(QTouchDevice::Position); - _touchDevice.setType(QTouchDevice::TouchScreen); - _touchDevice.setName("WebEntityRendererTouchDevice"); - _touchDevice.setMaximumTouchPoints(4); + _touchDevice = std::make_shared("OffscreenUiTouchDevice", 0, + QInputDevice::DeviceType::TouchScreen, + QPointingDevice::PointerType::Pen, + QInputDevice::Capability::Position, + 4, //maxPoints + 2 // buttonCount + ); }); _geometryId = DependencyManager::get()->allocateID(); @@ -473,7 +476,7 @@ void WebEntityRenderer::hoverEnterEntity(const PointerEvent& event) { if (_webSurface) { PointerEvent webEvent = event; webEvent.setPos2D(event.getPos2D() * (METERS_TO_INCHES * _dpi)); - _webSurface->hoverBeginEvent(webEvent, _touchDevice); + _webSurface->hoverBeginEvent(webEvent, *_touchDevice); } }); } @@ -493,7 +496,7 @@ void WebEntityRenderer::hoverLeaveEntity(const PointerEvent& event) { if (_webSurface) { PointerEvent webEvent = event; webEvent.setPos2D(event.getPos2D() * (METERS_TO_INCHES * _dpi)); - _webSurface->hoverEndEvent(webEvent, _touchDevice); + _webSurface->hoverEndEvent(webEvent, *_touchDevice); } }); } @@ -515,7 +518,7 @@ void WebEntityRenderer::handlePointerEvent(const PointerEvent& event) { void WebEntityRenderer::handlePointerEventAsTouch(const PointerEvent& event) { PointerEvent webEvent = event; webEvent.setPos2D(event.getPos2D() * (METERS_TO_INCHES * _dpi)); - _webSurface->handlePointerEvent(webEvent, _touchDevice); + _webSurface->handlePointerEvent(webEvent, *_touchDevice); } void WebEntityRenderer::handlePointerEventAsMouse(const PointerEvent& event) { diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index 58a79a81265..c21941cee81 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -440,7 +440,7 @@ bool EntityTree::updateEntity(EntityItemPointer entity, const EntityItemProperti properties.setParentJointIndexChanged(false); if (wantTerseEditLogging()) { - qCDebug(entities) << (senderNode ? senderNode->getUUID() : "null") << "physical edits suppressed"; + qCDebug(entities) << (senderNode ? senderNode->getUUID().toString() : "null") << "physical edits suppressed"; } } } @@ -615,7 +615,7 @@ void EntityTree::unhookChildAvatar(const EntityItemID entityID) { EntityItemPointer entity = findEntityByEntityItemID(entityID); entity->forEachDescendant([&](SpatiallyNestablePointer child) { if (child->getNestableType() == NestableType::Avatar) { - child->setParentID(nullptr); + child->setParentID(QUuid()); } }); } @@ -764,7 +764,7 @@ void EntityTree::processRemovedEntities(const DeleteEntityOperator& theOperator) } else { theEntity->forEachDescendant([&](SpatiallyNestablePointer child) { if (child->getNestableType() == NestableType::Avatar) { - child->setParentID(nullptr); + child->setParentID(QUuid()); } }); @@ -2400,7 +2400,7 @@ QVector EntityTree::sendEntities(EntityEditPacketSender* packetSen QJsonValue replaceEntityIDsInJSONHelper(const QJsonValue& jsonValue, std::function getMapped) { if (jsonValue.isString()) { QString stringValue = jsonValue.toString(); - QUuid oldID = stringValue; + QUuid oldID(stringValue); if (!oldID.isNull()) { return QJsonValue(getMapped(oldID).toString()); } @@ -2525,11 +2525,11 @@ bool EntityTree::sendEntitiesOperation(const OctreeElementPointer& element, void } } - QUuid oldID = uuidString; + QUuid oldID(uuidString); if (!oldID.isNull()) { uuidString = getMapped(oldID).toString(); } - QUuid oldMaterialName = materialName; + QUuid oldMaterialName(materialName); if (!oldMaterialName.isNull()) { materialName = getMapped(oldMaterialName).toString(); } @@ -2543,7 +2543,7 @@ bool EntityTree::sendEntitiesOperation(const OctreeElementPointer& element, void QString imageURL = properties.getImageURL(); if (imageURL.startsWith("{")) { - QUuid oldID = imageURL; + QUuid oldID(imageURL); if (!oldID.isNull()) { properties.setImageURL(getMapped(oldID).toString()); } diff --git a/libraries/gl/CMakeLists.txt b/libraries/gl/CMakeLists.txt index 069420aaac8..eaad11c60b4 100644 --- a/libraries/gl/CMakeLists.txt +++ b/libraries/gl/CMakeLists.txt @@ -1,5 +1,5 @@ set(TARGET_NAME gl) -setup_hifi_library(Gui Widgets) +setup_hifi_library(Gui Widgets OpenGL) link_hifi_libraries(shared) set(OpenGL_GL_PREFERENCE "GLVND") diff --git a/libraries/gl/src/gl/ContextQt.cpp b/libraries/gl/src/gl/ContextQt.cpp index a27ba30a2b5..1dbe074a3ef 100644 --- a/libraries/gl/src/gl/ContextQt.cpp +++ b/libraries/gl/src/gl/ContextQt.cpp @@ -20,7 +20,7 @@ #include #endif -#include +#include #include "GLHelpers.h" diff --git a/libraries/gl/src/gl/GLHelpers.cpp b/libraries/gl/src/gl/GLHelpers.cpp index 50d98d3fbf5..bb583f9eef7 100644 --- a/libraries/gl/src/gl/GLHelpers.cpp +++ b/libraries/gl/src/gl/GLHelpers.cpp @@ -11,7 +11,7 @@ #include #include -#include +#include #include "Context.h" diff --git a/libraries/gl/src/gl/GLWidget.cpp b/libraries/gl/src/gl/GLWidget.cpp index 078737443c8..d91d4b6b4ad 100644 --- a/libraries/gl/src/gl/GLWidget.cpp +++ b/libraries/gl/src/gl/GLWidget.cpp @@ -135,7 +135,7 @@ bool GLWidget::event(QEvent* event) { return QWidget::event(event); } -bool GLWidget::nativeEvent(const QByteArray &eventType, void *message, long *result) { +bool GLWidget::nativeEvent(const QByteArray &eventType, void *message, qintptr *result) { #ifdef Q_OS_WIN32 MSG* win32message = static_cast(message); switch (win32message->message) { diff --git a/libraries/gl/src/gl/GLWidget.h b/libraries/gl/src/gl/GLWidget.h index 0d8b23cd0af..1f907f60a7c 100644 --- a/libraries/gl/src/gl/GLWidget.h +++ b/libraries/gl/src/gl/GLWidget.h @@ -38,7 +38,7 @@ class GLWidget : public QWidget { virtual QVariant inputMethodQuery(Qt::InputMethodQuery query) const override; protected: - virtual bool nativeEvent(const QByteArray &eventType, void *message, long *result) override; + virtual bool nativeEvent(const QByteArray &eventType, void *message, qintptr *result) override; virtual bool event(QEvent* event) override; gl::Context* _context { nullptr }; diff --git a/libraries/gl/src/gl/OffscreenGLCanvas.cpp b/libraries/gl/src/gl/OffscreenGLCanvas.cpp index c5b8baa6d44..17aef0724cd 100644 --- a/libraries/gl/src/gl/OffscreenGLCanvas.cpp +++ b/libraries/gl/src/gl/OffscreenGLCanvas.cpp @@ -21,7 +21,7 @@ #include #include #include -#include +#include #include diff --git a/libraries/gl/src/gl/QOpenGLContextWrapper.cpp b/libraries/gl/src/gl/QOpenGLContextWrapper.cpp index 842c7abd089..6d2cc7c03c1 100644 --- a/libraries/gl/src/gl/QOpenGLContextWrapper.cpp +++ b/libraries/gl/src/gl/QOpenGLContextWrapper.cpp @@ -24,11 +24,12 @@ QOpenGLContextWrapper::Pointer QOpenGLContextWrapper::currentContextWrapper() { QOpenGLContextWrapper::NativeContextPointer QOpenGLContextWrapper::getNativeContext() const { QOpenGLContextWrapper::NativeContextPointer result; - auto nativeHandle = _context->nativeHandle(); + // QT6TODO: + /*auto nativeHandle = _context->nativeHandle(); if (nativeHandle.canConvert()) { result = std::make_shared(); *result = nativeHandle.value(); - } + }*/ return result; } diff --git a/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingInterface.h b/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingInterface.h index 2412a17d1ac..6e452c91589 100644 --- a/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingInterface.h +++ b/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingInterface.h @@ -154,6 +154,7 @@ namespace scriptable { ScriptValue scriptableMaterialToScriptValue(ScriptEngine* engine, const scriptable::ScriptableMaterial &material); }; -Q_DECLARE_METATYPE(NestableType) +// QT6TODO +//Q_DECLARE_METATYPE(NestableType) #endif // hifi_GraphicsScriptingInterface_h diff --git a/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingUtil.cpp b/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingUtil.cpp index 632303ebc7b..1e00f424ba8 100644 --- a/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingUtil.cpp +++ b/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingUtil.cpp @@ -27,7 +27,7 @@ QVariant toVariant(const glm::mat4& mat4) { floats.resize(16); memcpy(floats.data(), &mat4, sizeof(glm::mat4)); QVariant v; - v.setValue>(floats); + v.setValue>(std::move(floats)); return v; }; diff --git a/libraries/graphics-scripting/src/graphics-scripting/ScriptableMeshPart.h b/libraries/graphics-scripting/src/graphics-scripting/ScriptableMeshPart.h index cb06b397107..5c09c09ebe2 100644 --- a/libraries/graphics-scripting/src/graphics-scripting/ScriptableMeshPart.h +++ b/libraries/graphics-scripting/src/graphics-scripting/ScriptableMeshPart.h @@ -339,5 +339,6 @@ namespace scriptable { }; } -Q_DECLARE_METATYPE(scriptable::ScriptableMeshPartPointer) -Q_DECLARE_METATYPE(QVector) +// QT6TODO: +//Q_DECLARE_METATYPE(scriptable::ScriptableMeshPartPointer) +//Q_DECLARE_METATYPE(QVector) diff --git a/libraries/graphics-scripting/src/graphics-scripting/ScriptableModel.cpp b/libraries/graphics-scripting/src/graphics-scripting/ScriptableModel.cpp index 9f96f776ef7..1a3de45c5fd 100644 --- a/libraries/graphics-scripting/src/graphics-scripting/ScriptableModel.cpp +++ b/libraries/graphics-scripting/src/graphics-scripting/ScriptableModel.cpp @@ -179,7 +179,8 @@ scriptable::ScriptableMaterial::ScriptableMaterial(const graphics::MaterialPoint parametricRimFresnelPower = material->getParametricRimFresnelPower(); parametricRimLift = material->getParametricRimLift(); rimLightingMix = material->getRimLightingMix(); - outlineWidthMode = material->getOutlineWidthMode(); + // QT6TODO: something is wrong there, isn't outlineWidthMode supposed to be a string like "worldCoordinates" or "screenCoordinates" + outlineWidthMode = QString::number(material->getOutlineWidthMode()); outlineWidth = material->getOutlineWidth(); outline = material->getOutline(); uvAnimationScrollXSpeed = material->getUVAnimationScrollXSpeed(); diff --git a/libraries/hfm/src/hfm/HFMSerializer.h b/libraries/hfm/src/hfm/HFMSerializer.h index d0be588d602..c99d021977e 100644 --- a/libraries/hfm/src/hfm/HFMSerializer.h +++ b/libraries/hfm/src/hfm/HFMSerializer.h @@ -37,7 +37,7 @@ class Serializer { virtual MediaType getMediaType() const = 0; virtual std::unique_ptr getFactory() const = 0; - virtual Model::Pointer read(const hifi::ByteArray& data, const hifi::VariantHash& mapping, const hifi::URL& url = hifi::URL()) = 0; + virtual Model::Pointer read(const hifi::ByteArray& data, const hifi::VariantMultiHash& mapping, const hifi::URL& url = hifi::URL()) = 0; }; }; diff --git a/libraries/input-plugins/src/input-plugins/KeyboardMouseDevice.cpp b/libraries/input-plugins/src/input-plugins/KeyboardMouseDevice.cpp index b6513340a30..be6a12aa88e 100644 --- a/libraries/input-plugins/src/input-plugins/KeyboardMouseDevice.cpp +++ b/libraries/input-plugins/src/input-plugins/KeyboardMouseDevice.cpp @@ -244,7 +244,7 @@ void KeyboardMouseDevice::touchGestureEvent(const QGestureEvent* event) { void KeyboardMouseDevice::touchBeginEvent(const QTouchEvent* event) { if (_enableTouch) { - _isTouching = event->touchPointStates().testFlag(Qt::TouchPointPressed); + _isTouching = event->touchPointStates().testFlag(QEventPoint::State::Pressed); _lastTouch = evalAverageTouchPoints(event->touchPoints()); _lastTouchTime = _clock.now(); } diff --git a/libraries/input-plugins/src/input-plugins/TouchscreenDevice.cpp b/libraries/input-plugins/src/input-plugins/TouchscreenDevice.cpp index 807d7f0ef92..c6a22a60ed2 100644 --- a/libraries/input-plugins/src/input-plugins/TouchscreenDevice.cpp +++ b/libraries/input-plugins/src/input-plugins/TouchscreenDevice.cpp @@ -24,8 +24,8 @@ const char* TouchscreenDevice::NAME = "Touchscreen"; bool TouchscreenDevice::isSupported() const { - for (auto touchDevice : QTouchDevice::devices()) { - if (touchDevice->type() == QTouchDevice::TouchScreen) { + for (auto touchDevice : QInputDevice::devices()) { + if (touchDevice->type() == QInputDevice::DeviceType::TouchScreen) { return true; } } @@ -76,12 +76,14 @@ void TouchscreenDevice::touchBeginEvent(const QTouchEvent* event) { const QTouchEvent::TouchPoint& point = event->touchPoints().at(0); _firstTouchVec = glm::vec2(point.pos().x(), point.pos().y()); KeyboardMouseDevice::enableTouch(false); - QScreen* eventScreen = event->window()->screen(); + // QT6TODO: I'm not sure how to do this part yet + /*QScreen* eventScreen = event->window()->screen(); + QScreen* eventScreen = event->tagret()window()->screen(); if (_screenDPI != eventScreen->physicalDotsPerInch()) { _screenDPIScale.x = (float)eventScreen->physicalDotsPerInchX(); _screenDPIScale.y = (float)eventScreen->physicalDotsPerInchY(); _screenDPI = eventScreen->physicalDotsPerInch(); - } + }*/ } void TouchscreenDevice::touchEndEvent(const QTouchEvent* event) { diff --git a/libraries/input-plugins/src/input-plugins/TouchscreenDevice.h b/libraries/input-plugins/src/input-plugins/TouchscreenDevice.h index c0cca200d75..eb024247707 100644 --- a/libraries/input-plugins/src/input-plugins/TouchscreenDevice.h +++ b/libraries/input-plugins/src/input-plugins/TouchscreenDevice.h @@ -14,7 +14,6 @@ #include #include "InputPlugin.h" -#include class QTouchEvent; class QGestureEvent; diff --git a/libraries/input-plugins/src/input-plugins/TouchscreenVirtualPadDevice.cpp b/libraries/input-plugins/src/input-plugins/TouchscreenVirtualPadDevice.cpp index 6ba2d0eeea0..56a435ebf45 100644 --- a/libraries/input-plugins/src/input-plugins/TouchscreenVirtualPadDevice.cpp +++ b/libraries/input-plugins/src/input-plugins/TouchscreenVirtualPadDevice.cpp @@ -27,8 +27,8 @@ const char* TouchscreenVirtualPadDevice::NAME = "TouchscreenVirtualPad"; bool TouchscreenVirtualPadDevice::isSupported() const { - for (auto touchDevice : QTouchDevice::devices()) { - if (touchDevice->type() == QTouchDevice::TouchScreen) { + for (const auto &touchDevice : QInputDevice::devices()) { + if (touchDevice->type() == QInputDevice::DeviceType::TouchScreen) { return true; } } @@ -218,7 +218,8 @@ void TouchscreenVirtualPadDevice::debugPoints(const QTouchEvent* event, QString glm::vec2 thisPoint(tPoints[i].pos().x(), tPoints[i].pos().y()); points << thisPoint; } - QScreen* eventScreen = event->window()->screen(); + // QT6TODO: I have no idea how to do this part yet + /*QScreen* eventScreen = event->window()->screen(); int midScreenX = eventScreen->availableSize().width()/2; int lefties = 0; int righties = 0; @@ -230,7 +231,7 @@ void TouchscreenVirtualPadDevice::debugPoints(const QTouchEvent* event, QString } else { righties++; } - } + }*/ } void TouchscreenVirtualPadDevice::touchBeginEvent(const QTouchEvent* event) { diff --git a/libraries/input-plugins/src/input-plugins/TouchscreenVirtualPadDevice.h b/libraries/input-plugins/src/input-plugins/TouchscreenVirtualPadDevice.h index 1fa46f8a247..29c09af0372 100644 --- a/libraries/input-plugins/src/input-plugins/TouchscreenVirtualPadDevice.h +++ b/libraries/input-plugins/src/input-plugins/TouchscreenVirtualPadDevice.h @@ -14,7 +14,6 @@ #include #include "InputPlugin.h" -#include #if (QT_VERSION < QT_VERSION_CHECK(5, 14, 0)) #include #else diff --git a/libraries/material-networking/src/material-networking/TextureCache.cpp b/libraries/material-networking/src/material-networking/TextureCache.cpp index e2d48225431..51bbfdfbb3f 100644 --- a/libraries/material-networking/src/material-networking/TextureCache.cpp +++ b/libraries/material-networking/src/material-networking/TextureCache.cpp @@ -1403,7 +1403,7 @@ NetworkTexturePointer TextureCache::getTextureByUUID(const QString& uuid) { // We mark this as a resource texture because it's just a reference to another texture. The source // texture will be marked properly NetworkTexturePointer toReturn = NetworkTexturePointer::create(uuid, true); - toReturn->setImageOperator(Texture::getTextureForUUIDOperator(uuid)); + toReturn->setImageOperator(Texture::getTextureForUUIDOperator(QUuid(uuid))); return toReturn; } return NetworkTexturePointer(); diff --git a/libraries/midi/src/Midi.h b/libraries/midi/src/Midi.h index ee1ba6e8b36..2ec1ca50887 100644 --- a/libraries/midi/src/Midi.h +++ b/libraries/midi/src/Midi.h @@ -15,6 +15,7 @@ #define hifi_Midi_h #include +#include #include #include diff --git a/libraries/model-baker/src/model-baker/Baker.cpp b/libraries/model-baker/src/model-baker/Baker.cpp index 9fd3f520799..1be39ad1f60 100644 --- a/libraries/model-baker/src/model-baker/Baker.cpp +++ b/libraries/model-baker/src/model-baker/Baker.cpp @@ -193,7 +193,7 @@ namespace baker { } }; - Baker::Baker(const hfm::Model::Pointer& hfmModel, const hifi::VariantHash& mapping, const hifi::URL& materialMappingBaseURL) : + Baker::Baker(const hfm::Model::Pointer& hfmModel, const hifi::VariantMultiHash& mapping, const hifi::URL& materialMappingBaseURL) : _engine(std::make_shared(BakerEngineBuilder::JobModel::create("Baker"), std::make_shared())) { _engine->feedInput(0, hfmModel); _engine->feedInput(1, mapping); diff --git a/libraries/model-baker/src/model-baker/Baker.h b/libraries/model-baker/src/model-baker/Baker.h index 9780484fa48..3bc9f7197b6 100644 --- a/libraries/model-baker/src/model-baker/Baker.h +++ b/libraries/model-baker/src/model-baker/Baker.h @@ -23,7 +23,7 @@ namespace baker { class Baker { public: - Baker(const hfm::Model::Pointer& hfmModel, const hifi::VariantHash& mapping, const hifi::URL& materialMappingBaseURL); + Baker(const hfm::Model::Pointer& hfmModel, const hifi::VariantMultiHash& mapping, const hifi::URL& materialMappingBaseURL); std::shared_ptr getConfiguration(); diff --git a/libraries/model-networking/src/model-networking/ModelCache.cpp b/libraries/model-networking/src/model-networking/ModelCache.cpp index 264c6b98019..7ad10d4e9ff 100644 --- a/libraries/model-networking/src/model-networking/ModelCache.cpp +++ b/libraries/model-networking/src/model-networking/ModelCache.cpp @@ -29,6 +29,8 @@ #include #include +#include "shared/QtHelpers.h" + Q_LOGGING_CATEGORY(trace_resource_parse_geometry, "trace.resource.parse.geometry") class GeometryExtra { @@ -67,6 +69,15 @@ namespace std { } }; + template <> + struct hash { + size_t operator()(const hifi::VariantMultiHash& a) const { + QVariantHasher hasher; + // QT6TODO: check with profiler if this is not causing performance problems + return hasher.hash(qMultiHashToQVariant(a)); + } + }; + template <> struct hash { size_t operator()(const QUrl& a) const { @@ -100,6 +111,7 @@ class GeometryReader : public QRunnable { ModelLoader _modelLoader; QWeakPointer _resource; QUrl _url; + // QT6TODO: I'm not sure if _mapping should be QHash or QMultiHash GeometryMappingPair _mapping; QByteArray _data; bool _combineParts; @@ -140,9 +152,9 @@ void GeometryReader::run() { } HFMModel::Pointer hfmModel; - QMultiHash serializerMapping = _mapping.second; - serializerMapping.replace("combineParts",_combineParts); - serializerMapping.replace("deduplicateIndices", true); + hifi::VariantMultiHash serializerMapping = _mapping.second; + serializerMapping["combineParts"] = _combineParts; + serializerMapping["deduplicateIndices"] = true; if (_url.path().toLower().endsWith(".gz")) { QByteArray uncompressedData; diff --git a/libraries/model-networking/src/model-networking/ModelCache.h b/libraries/model-networking/src/model-networking/ModelCache.h index 79021087095..194723c58d0 100644 --- a/libraries/model-networking/src/model-networking/ModelCache.h +++ b/libraries/model-networking/src/model-networking/ModelCache.h @@ -26,7 +26,7 @@ class MeshPart; -using GeometryMappingPair = std::pair; +using GeometryMappingPair = std::pair; Q_DECLARE_METATYPE(GeometryMappingPair) class Geometry { @@ -59,7 +59,7 @@ class Geometry { virtual bool areTexturesLoaded() const; const QUrl& getAnimGraphOverrideUrl() const { return _animGraphOverrideUrl; } bool shouldWaitForWearables() const { return _waitForWearables; } - const QVariantHash& getMapping() const { return _mapping; } + const hifi::VariantMultiHash& getMapping() const { return _mapping; } protected: // Shared across all geometries, constant throughout lifetime @@ -72,7 +72,7 @@ class Geometry { NetworkMaterials _materials; QUrl _animGraphOverrideUrl; - QVariantHash _mapping; // parsed contents of FST file. + hifi::VariantMultiHash _mapping; // parsed contents of FST file. bool _waitForWearables { false }; private: diff --git a/libraries/model-networking/src/model-networking/ModelLoader.cpp b/libraries/model-networking/src/model-networking/ModelLoader.cpp index 65314633c96..63282ac7a10 100644 --- a/libraries/model-networking/src/model-networking/ModelLoader.cpp +++ b/libraries/model-networking/src/model-networking/ModelLoader.cpp @@ -15,7 +15,7 @@ #include -hfm::Model::Pointer ModelLoader::load(const hifi::ByteArray& data, const hifi::VariantHash& mapping, const hifi::URL& url, const std::string& webMediaType) const { +hfm::Model::Pointer ModelLoader::load(const hifi::ByteArray& data, const hifi::VariantMultiHash& mapping, const hifi::URL& url, const std::string& webMediaType) const { auto serializer = DependencyManager::get()->getSerializerForMediaType(data, url, webMediaType); if (!serializer) { return hfm::Model::Pointer(); diff --git a/libraries/model-networking/src/model-networking/ModelLoader.h b/libraries/model-networking/src/model-networking/ModelLoader.h index 5fbab4fb650..095fa5c22d8 100644 --- a/libraries/model-networking/src/model-networking/ModelLoader.h +++ b/libraries/model-networking/src/model-networking/ModelLoader.h @@ -20,7 +20,7 @@ class ModelLoader { // Given the currently stored list of supported file formats, determine how to load a model from the given parameters. // If successful, return an owned reference to the newly loaded model. // If failed, return an empty reference. - hfm::Model::Pointer load(const hifi::ByteArray& data, const hifi::VariantHash& mapping, const hifi::URL& url, const std::string& webMediaType) const; + hfm::Model::Pointer load(const hifi::ByteArray& data, const hifi::VariantMultiHash& mapping, const hifi::URL& url, const std::string& webMediaType) const; }; #endif // hifi_ModelLoader_h diff --git a/libraries/model-serializers/src/FBXSerializer.cpp b/libraries/model-serializers/src/FBXSerializer.cpp index 58eed40bdb4..7523e742427 100644 --- a/libraries/model-serializers/src/FBXSerializer.cpp +++ b/libraries/model-serializers/src/FBXSerializer.cpp @@ -23,6 +23,8 @@ #include +#include "shared/QtHelpers.h" + // TOOL: Uncomment the following line to enable the filtering of all the unknown fields of a node so we can break point easily while loading a model with problems... //#define DEBUG_FBXSERIALIZER @@ -34,7 +36,7 @@ glm::vec3 parseVec3(const QString& string) { return glm::vec3(); } glm::vec3 value; - for (int i = 0; i < 3; i++) { + for (qsizetype i = 0; i < 3; i++) { // duplicate last value if there aren't three elements value[i] = elements.at(min(i, elements.size() - 1)).trimmed().toFloat(); } @@ -260,7 +262,7 @@ typedef QPair WeightedIndex; void addBlendshapes(const ExtractedBlendshape& extracted, const QList& indices, ExtractedMesh& extractedMesh) { foreach (const WeightedIndex& index, indices) { - extractedMesh.mesh.blendshapes.resize(max(extractedMesh.mesh.blendshapes.size(), index.first + 1)); + extractedMesh.mesh.blendshapes.resize(max(extractedMesh.mesh.blendshapes.size(), static_cast(index.first + 1))); extractedMesh.blendshapeIndexMaps.resize(extractedMesh.mesh.blendshapes.size()); HFMBlendshape& blendshape = extractedMesh.mesh.blendshapes[index.first]; QHash& blendshapeIndexMap = extractedMesh.blendshapeIndexMaps[index.first]; @@ -394,7 +396,7 @@ hifi::ByteArray fileOnUrl(const hifi::ByteArray& filepath, const QString& url) { return filepath.mid(filepath.lastIndexOf('/') + 1); } -HFMModel* FBXSerializer::extractHFMModel(const hifi::VariantHash& mapping, const QString& url) { +HFMModel* FBXSerializer::extractHFMModel(const hifi::VariantMultiHash& mapping, const QString& url) { const FBXNode& node = _rootNode; bool deduplicateIndices = mapping["deduplicateIndices"].toBool(); @@ -419,7 +421,7 @@ HFMModel* FBXSerializer::extractHFMModel(const hifi::VariantHash& mapping, const std::map lights; - hifi::VariantMultiHash blendshapeMappings = mapping.value("bs").toHash(); + hifi::VariantMultiHash blendshapeMappings = qVariantToQMultiHash(mapping.value("bs")); QMultiHash blendshapeIndices; for (int i = 0;; i++) { @@ -1394,7 +1396,7 @@ HFMModel* FBXSerializer::extractHFMModel(const hifi::VariantHash& mapping, const } // NOTE: shapeVertices are in joint-frame - hfmModel.shapeVertices.resize(std::max(1, hfmModel.joints.size()) ); + hfmModel.shapeVertices.resize(std::max(qsizetype(1), hfmModel.joints.size()) ); hfmModel.bindExtents.reset(); hfmModel.meshExtents.reset(); @@ -1700,7 +1702,7 @@ std::unique_ptr FBXSerializer::getFactory() const { return std::make_unique>(); } -HFMModel::Pointer FBXSerializer::read(const hifi::ByteArray& data, const hifi::VariantHash& mapping, const hifi::URL& url) { +HFMModel::Pointer FBXSerializer::read(const hifi::ByteArray& data, const hifi::VariantMultiHash& mapping, const hifi::URL& url) { QBuffer buffer(const_cast(&data)); buffer.open(QIODevice::ReadOnly); diff --git a/libraries/model-serializers/src/FBXSerializer.h b/libraries/model-serializers/src/FBXSerializer.h index e528885754c..f6fe0e42313 100644 --- a/libraries/model-serializers/src/FBXSerializer.h +++ b/libraries/model-serializers/src/FBXSerializer.h @@ -112,12 +112,12 @@ class FBXSerializer : public HFMSerializer { HFMModel* _hfmModel; /// Reads HFMModel from the supplied model and mapping data. /// \exception QString if an error occurs in parsing - HFMModel::Pointer read(const hifi::ByteArray& data, const hifi::VariantHash& mapping, const hifi::URL& url = hifi::URL()) override; + HFMModel::Pointer read(const hifi::ByteArray& data, const hifi::VariantMultiHash& mapping, const hifi::URL& url = hifi::URL()) override; FBXNode _rootNode; static FBXNode parseFBX(QIODevice* device); - HFMModel* extractHFMModel(const hifi::VariantHash& mapping, const QString& url); + HFMModel* extractHFMModel(const hifi::VariantMultiHash& mapping, const QString& url); static ExtractedMesh extractMesh(const FBXNode& object, unsigned int& meshIndex, bool deduplicate); QHash meshes; diff --git a/libraries/model-serializers/src/FBXToJSON.h b/libraries/model-serializers/src/FBXToJSON.h index f36235abca8..6d9117d0326 100644 --- a/libraries/model-serializers/src/FBXToJSON.h +++ b/libraries/model-serializers/src/FBXToJSON.h @@ -15,9 +15,11 @@ #include #include +#include + // Forward declarations. class FBXNode; -template class QVector; +//template class QVector; class FBXToJSON : public std::ostringstream { public: diff --git a/libraries/model-serializers/src/FBXWriter.cpp b/libraries/model-serializers/src/FBXWriter.cpp index efc94461aa0..095d4073439 100644 --- a/libraries/model-serializers/src/FBXWriter.cpp +++ b/libraries/model-serializers/src/FBXWriter.cpp @@ -12,6 +12,7 @@ #include "FBXWriter.h" #include +#include #ifdef USE_FBX_2016_FORMAT using FBXEndOffset = int64_t; diff --git a/libraries/model-serializers/src/FST.cpp b/libraries/model-serializers/src/FST.cpp index 8d03933717b..080df2ce500 100644 --- a/libraries/model-serializers/src/FST.cpp +++ b/libraries/model-serializers/src/FST.cpp @@ -15,6 +15,8 @@ #include #include +#include "shared/QtHelpers.h" + constexpr float DEFAULT_SCALE { 1.0f }; FST::FST(QString fstPath, QMultiHash data) : _fstPath(std::move(fstPath)) { @@ -40,7 +42,7 @@ FST::FST(QString fstPath, QMultiHash data) : _fstPath(std::mo } FST* FST::createFSTFromModel(const QString& fstPath, const QString& modelFilePath, const hfm::Model& hfmModel) { - QVariantHash mapping; + hifi::VariantMultiHash mapping; // 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 @@ -136,7 +138,7 @@ FST* FST::createFSTFromModel(const QString& fstPath, const QString& modelFilePat 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)); } return new FST(fstPath, mapping); } diff --git a/libraries/model-serializers/src/FST.h b/libraries/model-serializers/src/FST.h index e1273987a26..aed46a49cfe 100644 --- a/libraries/model-serializers/src/FST.h +++ b/libraries/model-serializers/src/FST.h @@ -57,7 +57,7 @@ class FST : public QObject { QStringList _scriptPaths{}; - QVariantHash _other{}; + hifi::VariantMultiHash _other{}; }; #endif // hifi_FST_h diff --git a/libraries/model-serializers/src/FSTReader.cpp b/libraries/model-serializers/src/FSTReader.cpp index 9c1ff5a431d..c63b6224873 100644 --- a/libraries/model-serializers/src/FSTReader.cpp +++ b/libraries/model-serializers/src/FSTReader.cpp @@ -21,6 +21,8 @@ #include #include +#include "shared/QtHelpers.h" + const QStringList SINGLE_VALUE_PROPERTIES { NAME_FIELD, FILENAME_FIELD, TEXDIR_FIELD, SCRIPT_FIELD, WAIT_FOR_WEARABLES_FIELD, COMMENT_FIELD }; @@ -73,7 +75,7 @@ hifi::VariantMultiHash FSTReader::parseMapping(QIODevice* device) { return properties; } -static void removeBlendshape(QVariantHash& bs, const QString& key) { +static void removeBlendshape(hifi::VariantMultiHash& bs, const QString& key) { if (bs.contains(key)) { bs.remove(key); } @@ -96,8 +98,9 @@ static void splitBlendshapes(hifi::VariantMultiHash& bs, const QString& key, con } // convert legacy blendshapes to arkit blendshapes +// QT6TODO: I'm not sure if qVariantToQMultiHash will work correctly. static void fixUpLegacyBlendshapes(hifi::VariantMultiHash & properties) { - hifi::VariantMultiHash bs = properties.value("bs").toHash(); + hifi::VariantMultiHash bs = qVariantToQMultiHash(properties.value("bs")); // These blendshapes have no ARKit equivalent, so we remove them. removeBlendshape(bs, "JawChew"); @@ -112,7 +115,8 @@ static void fixUpLegacyBlendshapes(hifi::VariantMultiHash & properties) { splitBlendshapes(bs, "Sneer", "NoseSneer_L", "NoseSneer_R"); // re-insert new mutated bs hash into mapping properties. - properties.insert("bs", bs); + // QT6TODO: add VariantMultiHash to QVariant conversion + properties.insert("bs", qMultiHashToQVariant(bs)); } hifi::VariantMultiHash FSTReader::readMapping(const QByteArray& data) { @@ -123,7 +127,7 @@ hifi::VariantMultiHash FSTReader::readMapping(const QByteArray& data) { return mapping; } -void FSTReader::writeVariant(QBuffer& buffer, QVariantHash::const_iterator& it) { +void FSTReader::writeVariant(QBuffer& buffer, const hifi::VariantMultiHash::const_iterator& it) { QByteArray key = it.key().toUtf8() + " = "; QVariantHash hashValue = it.value().toHash(); if (hashValue.isEmpty()) { diff --git a/libraries/model-serializers/src/FSTReader.h b/libraries/model-serializers/src/FSTReader.h index 5557df67c69..4becf1c9275 100644 --- a/libraries/model-serializers/src/FSTReader.h +++ b/libraries/model-serializers/src/FSTReader.h @@ -55,14 +55,14 @@ class FSTReader { /// Predicts the type of model by examining the mapping static ModelType predictModelType(const hifi::VariantMultiHash& mapping); - static QVector getScripts(const QUrl& fstUrl, const hifi::VariantMultiHash& mapping = QVariantHash()); + static QVector getScripts(const QUrl& fstUrl, const hifi::VariantMultiHash& mapping = hifi::VariantMultiHash()); static QString getNameFromType(ModelType modelType); static FSTReader::ModelType getTypeFromName(const QString& name); static hifi::VariantMultiHash downloadMapping(const QString& url); private: - static void writeVariant(QBuffer& buffer, QVariantHash::const_iterator& it); + static void writeVariant(QBuffer& buffer, const hifi::VariantMultiHash::const_iterator& it); static hifi::VariantMultiHash parseMapping(QIODevice* device); static QHash _typesToNames; diff --git a/libraries/model-serializers/src/GLTFSerializer.cpp b/libraries/model-serializers/src/GLTFSerializer.cpp index 9e8e5aa68af..393f0317cdf 100644 --- a/libraries/model-serializers/src/GLTFSerializer.cpp +++ b/libraries/model-serializers/src/GLTFSerializer.cpp @@ -44,6 +44,7 @@ #include #include "FBXSerializer.h" +#include "shared/QtHelpers.h" float atof_locale_independent(char* str) { //TODO: Once we have C++17 we can use std::from_chars @@ -188,7 +189,7 @@ bool findAttribute(const QString &name, const cgltf_attribute *attributes, size_ return false; } -bool GLTFSerializer::buildGeometry(HFMModel& hfmModel, const hifi::VariantHash& mapping, const hifi::URL& url) { +bool GLTFSerializer::buildGeometry(HFMModel& hfmModel, const hifi::VariantMultiHash& mapping, const hifi::URL& url) { hfmModel.originalURL = url.toString(); int numNodes = (int)_data->nodes_count; @@ -999,7 +1000,7 @@ bool GLTFSerializer::buildGeometry(HFMModel& hfmModel, const hifi::VariantHash& // Build list of blendshapes from FST and model. typedef QPair WeightedIndex; - hifi::VariantMultiHash blendshapeMappings = mapping.value("bs").toHash(); + hifi::VariantMultiHash blendshapeMappings = qVariantToQMultiHash(mapping.value("bs")); QMultiHash blendshapeIndices; for (int i = 0;; ++i) { auto blendshapeName = QString(BLENDSHAPE_NAMES[i]); @@ -1198,7 +1199,7 @@ std::unique_ptr GLTFSerializer::getFactory() const { return std::make_unique>(); } -HFMModel::Pointer GLTFSerializer::read(const hifi::ByteArray& data, const hifi::VariantHash& mapping, const hifi::URL& url) { +HFMModel::Pointer GLTFSerializer::read(const hifi::ByteArray& data, const hifi::VariantMultiHash& mapping, const hifi::URL& url) { _url = url; diff --git a/libraries/model-serializers/src/GLTFSerializer.h b/libraries/model-serializers/src/GLTFSerializer.h index 0786eac98b3..790ef1b0d00 100644 --- a/libraries/model-serializers/src/GLTFSerializer.h +++ b/libraries/model-serializers/src/GLTFSerializer.h @@ -32,7 +32,7 @@ class GLTFSerializer : public QObject, public HFMSerializer { MediaType getMediaType() const override; std::unique_ptr getFactory() const override; - HFMModel::Pointer read(const hifi::ByteArray& data, const hifi::VariantHash& mapping, const hifi::URL& url = hifi::URL()) override; + HFMModel::Pointer read(const hifi::ByteArray& data, const hifi::VariantMultiHash& mapping, const hifi::URL& url = hifi::URL()) override; ~GLTFSerializer(); private: cgltf_data* _data {nullptr}; @@ -43,7 +43,7 @@ class GLTFSerializer : public QObject, public HFMSerializer { bool getSkinInverseBindMatrices(std::vector>& inverseBindMatrixValues); bool generateTargetData(cgltf_accessor *accessor, float weight, QVector& returnVector); - bool buildGeometry(HFMModel& hfmModel, const hifi::VariantHash& mapping, const hifi::URL& url); + bool buildGeometry(HFMModel& hfmModel, const hifi::VariantMultiHash& mapping, const hifi::URL& url); bool readBinary(const QString& url, cgltf_buffer &buffer); diff --git a/libraries/model-serializers/src/OBJSerializer.cpp b/libraries/model-serializers/src/OBJSerializer.cpp index 8529c72a338..1b4b11061b0 100644 --- a/libraries/model-serializers/src/OBJSerializer.cpp +++ b/libraries/model-serializers/src/OBJSerializer.cpp @@ -493,7 +493,7 @@ QNetworkReply* request(hifi::URL& url, bool isTest) { } -bool OBJSerializer::parseOBJGroup(OBJTokenizer& tokenizer, const hifi::VariantHash& mapping, HFMModel& hfmModel, +bool OBJSerializer::parseOBJGroup(OBJTokenizer& tokenizer, const hifi::VariantMultiHash& mapping, HFMModel& hfmModel, float& scaleGuess, bool combineParts) { FaceGroup faces; HFMMesh& mesh = hfmModel.meshes[0]; @@ -666,7 +666,7 @@ std::unique_ptr OBJSerializer::getFactory() const { return std::make_unique>(); } -HFMModel::Pointer OBJSerializer::read(const hifi::ByteArray& data, const hifi::VariantHash& mapping, const hifi::URL& url) { +HFMModel::Pointer OBJSerializer::read(const hifi::ByteArray& data, const hifi::VariantMultiHash& mapping, const hifi::URL& url) { PROFILE_RANGE_EX(resource_parse, __FUNCTION__, 0xffff0000, nullptr); QBuffer buffer { const_cast(&data) }; buffer.open(QIODevice::ReadOnly); diff --git a/libraries/model-serializers/src/OBJSerializer.h b/libraries/model-serializers/src/OBJSerializer.h index 20633e90e4c..b3ce01a2027 100644 --- a/libraries/model-serializers/src/OBJSerializer.h +++ b/libraries/model-serializers/src/OBJSerializer.h @@ -103,13 +103,13 @@ class OBJSerializer: public QObject, public HFMSerializer { // QObject so we can QString currentMaterialName; QHash materials; - HFMModel::Pointer read(const hifi::ByteArray& data, const hifi::VariantHash& mapping, const hifi::URL& url = hifi::URL()) override; + HFMModel::Pointer read(const hifi::ByteArray& data, const hifi::VariantMultiHash& mapping, const hifi::URL& url = hifi::URL()) override; private: hifi::URL _url; QHash librariesSeen; - bool parseOBJGroup(OBJTokenizer& tokenizer, const hifi::VariantHash& mapping, HFMModel& hfmModel, + bool parseOBJGroup(OBJTokenizer& tokenizer, const hifi::VariantMultiHash& mapping, HFMModel& hfmModel, float& scaleGuess, bool combineParts); void parseMaterialLibrary(QIODevice* device); void parseTextureLine(const hifi::ByteArray& textureLine, hifi::ByteArray& filename, OBJMaterialTextureOptions& textureOptions); diff --git a/libraries/model-serializers/src/OBJWriter.cpp b/libraries/model-serializers/src/OBJWriter.cpp index dec35c7aac6..5a7c4cee806 100644 --- a/libraries/model-serializers/src/OBJWriter.cpp +++ b/libraries/model-serializers/src/OBJWriter.cpp @@ -13,6 +13,8 @@ #include #include +#include + #include #include #include @@ -110,7 +112,7 @@ bool writeOBJToTextStream(QTextStream& out, QList meshes) { graphics::Index partCount = (graphics::Index)mesh->getNumParts(); QString name = (!mesh->displayName.size() ? QString("mesh-%1-part").arg(nth) : QString::fromStdString(mesh->displayName)) - .replace(QRegExp("[^-_a-zA-Z0-9]"), "_"); + .replace(QRegularExpression("[^-_a-zA-Z0-9]"), "_"); for (int partIndex = 0; partIndex < partCount; partIndex++) { const graphics::Mesh::Part& part = partBuffer.get(partIndex); diff --git a/libraries/networking/src/AccountManager.cpp b/libraries/networking/src/AccountManager.cpp index 9454853418a..d90a1e7a6ac 100644 --- a/libraries/networking/src/AccountManager.cpp +++ b/libraries/networking/src/AccountManager.cpp @@ -83,10 +83,8 @@ AccountManager::AccountManager(bool accountSettingsEnabled, UserAgentGetter user _accountSettingsEnabled(accountSettingsEnabled) { qRegisterMetaType("OAuthAccessToken"); - qRegisterMetaTypeStreamOperators("OAuthAccessToken"); qRegisterMetaType("DataServerAccountInfo"); - qRegisterMetaTypeStreamOperators("DataServerAccountInfo"); qRegisterMetaType("QNetworkAccessManager::Operation"); qRegisterMetaType("JSONCallbackParameters"); @@ -347,7 +345,7 @@ void AccountManager::sendRequest(const QString& path, // double check if the finished network reply had a session ID in the header and make // sure that our session ID matches that value if so if (networkReply->hasRawHeader(METAVERSE_SESSION_ID_HEADER)) { - _sessionID = networkReply->rawHeader(METAVERSE_SESSION_ID_HEADER); + _sessionID = QUuid::fromBytes(networkReply->rawHeader(METAVERSE_SESSION_ID_HEADER)); } }); diff --git a/libraries/networking/src/AddressManager.cpp b/libraries/networking/src/AddressManager.cpp index e8ca40c17dd..78eb3a37595 100644 --- a/libraries/networking/src/AddressManager.cpp +++ b/libraries/networking/src/AddressManager.cpp @@ -33,6 +33,7 @@ #include "NetworkingConstants.h" #include "UserActivityLogger.h" #include "udt/PacketHeaders.h" +#include const QString REDIRECT_HIFI_ADDRESS = NetworkingConstants::REDIRECT_HIFI_ADDRESS; const QString ADDRESS_MANAGER_SETTINGS_GROUP = "AddressManager"; @@ -284,7 +285,7 @@ bool AddressManager::handleUrl(const QUrl& lookupUrlIn, LookupTrigger trigger, c if (lookupUrl.host().isEmpty()) { // this was in the form hifi:/somewhere or hifi:somewhere. Fix it by making it hifi://somewhere - static const QRegExp HIFI_SCHEME_REGEX = QRegExp(URL_SCHEME_OVERTE + ":\\/{0,2}", Qt::CaseInsensitive); + static const QRegularExpression HIFI_SCHEME_REGEX = QRegularExpression(URL_SCHEME_OVERTE + ":\\/{0,2}", QRegularExpression::CaseInsensitiveOption); lookupUrl = QUrl(lookupUrl.toString().replace(HIFI_SCHEME_REGEX, URL_SCHEME_OVERTE + "://")); } diff --git a/libraries/networking/src/AssetClient.cpp b/libraries/networking/src/AssetClient.cpp index 5f87c31b865..4ae347bb77a 100644 --- a/libraries/networking/src/AssetClient.cpp +++ b/libraries/networking/src/AssetClient.cpp @@ -68,7 +68,7 @@ void AssetClient::initCaching() { #ifdef Q_OS_ANDROID QString cachePath = QStandardPaths::writableLocation(QStandardPaths::CacheLocation); #else - QString cachePath = QStandardPaths::writableLocation(QStandardPaths::DataLocation); + QString cachePath = QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation); #endif _cacheDir = !cachePath.isEmpty() ? cachePath : "interfaceCache"; } diff --git a/libraries/networking/src/AssetResourceRequest.cpp b/libraries/networking/src/AssetResourceRequest.cpp index 5419424f46b..f5e34584bd2 100644 --- a/libraries/networking/src/AssetResourceRequest.cpp +++ b/libraries/networking/src/AssetResourceRequest.cpp @@ -12,6 +12,7 @@ #include "AssetResourceRequest.h" #include +#include #include #include diff --git a/libraries/networking/src/AssetUtils.cpp b/libraries/networking/src/AssetUtils.cpp index b0eb19bb735..7b64e2201e1 100644 --- a/libraries/networking/src/AssetUtils.cpp +++ b/libraries/networking/src/AssetUtils.cpp @@ -17,6 +17,7 @@ #include #include // for baseName #include +#include #include "NetworkAccessManager.h" #include "NetworkLogging.h" diff --git a/libraries/networking/src/BaseAssetScriptingInterface.cpp b/libraries/networking/src/BaseAssetScriptingInterface.cpp index 6dcc862c414..d5861316431 100644 --- a/libraries/networking/src/BaseAssetScriptingInterface.cpp +++ b/libraries/networking/src/BaseAssetScriptingInterface.cpp @@ -13,6 +13,7 @@ #include #include #include +#include #include #include @@ -262,7 +263,7 @@ Promise BaseAssetScriptingInterface::downloadBytes(QString hash) { { "data", data }, }; } else { - error = request->getError(); + error = QMetaEnum::fromType().valueToKey(request->getError()); result = { { "error", request->getError() } }; } // forward thread-safe copies back to our thread diff --git a/libraries/networking/src/LimitedNodeList.cpp b/libraries/networking/src/LimitedNodeList.cpp index d182e7ea94a..fcb48b00674 100644 --- a/libraries/networking/src/LimitedNodeList.cpp +++ b/libraries/networking/src/LimitedNodeList.cpp @@ -281,7 +281,7 @@ bool LimitedNodeList::packetVersionMatch(const udt::Packet& packet) { if (!hasBeenOutput) { sourcedVersionDebugSuppressMap.insert(sourceID, headerType); - senderString = uuidStringWithoutCurlyBraces(sourceID.toString()); + senderString = uuidStringWithoutCurlyBraces(sourceID); } } } diff --git a/libraries/networking/src/NodePermissions.cpp b/libraries/networking/src/NodePermissions.cpp index 8a23c7dbd31..64653c0c29b 100644 --- a/libraries/networking/src/NodePermissions.cpp +++ b/libraries/networking/src/NodePermissions.cpp @@ -32,10 +32,10 @@ size_t std::hash::operator()(const NodePermissionsKey& key) } -NodePermissionsKey NodePermissions::standardNameLocalhost = NodePermissionsKey("localhost", 0); -NodePermissionsKey NodePermissions::standardNameLoggedIn = NodePermissionsKey("logged-in", 0); -NodePermissionsKey NodePermissions::standardNameAnonymous = NodePermissionsKey("anonymous", 0); -NodePermissionsKey NodePermissions::standardNameFriends = NodePermissionsKey("friends", 0); +NodePermissionsKey NodePermissions::standardNameLocalhost = NodePermissionsKey("localhost", QUuid::fromUInt128(0)); +NodePermissionsKey NodePermissions::standardNameLoggedIn = NodePermissionsKey("logged-in", QUuid::fromUInt128(0)); +NodePermissionsKey NodePermissions::standardNameAnonymous = NodePermissionsKey("anonymous", QUuid::fromUInt128(0)); +NodePermissionsKey NodePermissions::standardNameFriends = NodePermissionsKey("friends", QUuid::fromUInt128(0)); QStringList NodePermissions::standardNames = QList() << NodePermissions::standardNameLocalhost.first diff --git a/libraries/networking/src/ResourceCache.cpp b/libraries/networking/src/ResourceCache.cpp index 782c40609de..9903548609d 100644 --- a/libraries/networking/src/ResourceCache.cpp +++ b/libraries/networking/src/ResourceCache.cpp @@ -221,8 +221,8 @@ ScriptableResource* ResourceCache::prefetch(const QUrl& url, void* extra, size_t if (QThread::currentThread() != thread()) { // Must be called in thread to ensure getResource returns a valid pointer BLOCKING_INVOKE_METHOD(this, "prefetch", - Q_RETURN_ARG(ScriptableResource*, result), - Q_ARG(QUrl, url), Q_ARG(void*, extra), Q_ARG(size_t, extraHash)); + Q_GENERIC_RETURN_ARG(ScriptableResource*, result), + Q_GENERIC_ARG(QUrl, url), Q_GENERIC_ARG(void*, extra), Q_GENERIC_ARG(size_t, extraHash)); return result; } @@ -325,7 +325,7 @@ QVariantList ResourceCache::getResourceList() { if (QThread::currentThread() != thread()) { // NOTE: invokeMethod does not allow a const QObject* BLOCKING_INVOKE_METHOD(this, "getResourceList", - Q_RETURN_ARG(QVariantList, list)); + Q_GENERIC_RETURN_ARG(QVariantList, list)); } else { QList resources; { diff --git a/libraries/networking/src/UserActivityLoggerScriptingInterface.cpp b/libraries/networking/src/UserActivityLoggerScriptingInterface.cpp index 8e4e6b8fb57..4b78cc5fa29 100644 --- a/libraries/networking/src/UserActivityLoggerScriptingInterface.cpp +++ b/libraries/networking/src/UserActivityLoggerScriptingInterface.cpp @@ -12,6 +12,8 @@ #include "UserActivityLoggerScriptingInterface.h" #include "UserActivityLogger.h" +#include + void UserActivityLoggerScriptingInterface::enabledEdit() { doLogAction("enabled_edit"); } diff --git a/libraries/networking/src/udt/NetworkSocket.cpp b/libraries/networking/src/udt/NetworkSocket.cpp index 298455e33ce..c115d642b2d 100644 --- a/libraries/networking/src/udt/NetworkSocket.cpp +++ b/libraries/networking/src/udt/NetworkSocket.cpp @@ -8,6 +8,8 @@ #include "NetworkSocket.h" +#include + #include "../NetworkLogging.h" diff --git a/libraries/networking/src/udt/PacketHeaders.cpp b/libraries/networking/src/udt/PacketHeaders.cpp index 2221c4bf2b0..d200be9e594 100644 --- a/libraries/networking/src/udt/PacketHeaders.cpp +++ b/libraries/networking/src/udt/PacketHeaders.cpp @@ -16,6 +16,7 @@ #include #include +#include #include diff --git a/libraries/plugins/src/plugins/InputConfiguration.cpp b/libraries/plugins/src/plugins/InputConfiguration.cpp index c206d67b66c..89e639e853c 100644 --- a/libraries/plugins/src/plugins/InputConfiguration.cpp +++ b/libraries/plugins/src/plugins/InputConfiguration.cpp @@ -24,7 +24,7 @@ QStringList InputConfiguration::inputPlugins() { if (QThread::currentThread() != thread()) { QStringList result; BLOCKING_INVOKE_METHOD(this, "inputPlugins", - Q_RETURN_ARG(QStringList, result)); + Q_GENERIC_RETURN_ARG(QStringList, result)); return result; } @@ -46,7 +46,7 @@ QStringList InputConfiguration::activeInputPlugins() { if (QThread::currentThread() != thread()) { QStringList result; BLOCKING_INVOKE_METHOD(this, "activeInputPlugins", - Q_RETURN_ARG(QStringList, result)); + Q_GENERIC_RETURN_ARG(QStringList, result)); return result; } @@ -69,8 +69,8 @@ QString InputConfiguration::configurationLayout(QString pluginName) { if (QThread::currentThread() != thread()) { QString result; BLOCKING_INVOKE_METHOD(this, "configurationLayout", - Q_RETURN_ARG(QString, result), - Q_ARG(QString, pluginName)); + Q_GENERIC_RETURN_ARG(QString, result), + Q_GENERIC_ARG(QString, pluginName)); return result; } @@ -86,8 +86,8 @@ QString InputConfiguration::configurationLayout(QString pluginName) { void InputConfiguration::setConfigurationSettings(QJsonObject configurationSettings, QString pluginName) { if (QThread::currentThread() != thread()) { BLOCKING_INVOKE_METHOD(this, "setConfigurationSettings", - Q_ARG(QJsonObject, configurationSettings), - Q_ARG(QString, pluginName)); + Q_GENERIC_ARG(QJsonObject, configurationSettings), + Q_GENERIC_ARG(QString, pluginName)); return; } @@ -102,8 +102,8 @@ QJsonObject InputConfiguration::configurationSettings(QString pluginName) { if (QThread::currentThread() != thread()) { QJsonObject result; BLOCKING_INVOKE_METHOD(this, "configurationSettings", - Q_RETURN_ARG(QJsonObject, result), - Q_ARG(QString, pluginName)); + Q_GENERIC_RETURN_ARG(QJsonObject, result), + Q_GENERIC_ARG(QString, pluginName)); return result; } @@ -133,7 +133,7 @@ bool InputConfiguration::uncalibratePlugin(QString pluginName) { if (QThread::currentThread() != thread()) { bool result; BLOCKING_INVOKE_METHOD(this, "uncalibratePlugin", - Q_ARG(bool, result)); + Q_GENERIC_ARG(bool, result)); return result; } diff --git a/libraries/qml/src/qml/OffscreenSurface.cpp b/libraries/qml/src/qml/OffscreenSurface.cpp index aabe6f36ba5..e9cdf4454d1 100644 --- a/libraries/qml/src/qml/OffscreenSurface.cpp +++ b/libraries/qml/src/qml/OffscreenSurface.cpp @@ -10,6 +10,8 @@ #include #include +#include + #include #include #include diff --git a/libraries/qml/src/qml/impl/RenderEventHandler.cpp b/libraries/qml/src/qml/impl/RenderEventHandler.cpp index c2e69d5a9b2..95f4abfe5de 100644 --- a/libraries/qml/src/qml/impl/RenderEventHandler.cpp +++ b/libraries/qml/src/qml/impl/RenderEventHandler.cpp @@ -15,6 +15,7 @@ #include #include +#include #include #include "Profiling.h" @@ -166,7 +167,7 @@ void RenderEventHandler::qmlRender(bool sceneGraphSync) { // Fence will be used in another thread / context, so a flush is required glFlush(); _shared->updateTextureAndFence({ texture, fence }); - _shared->_quickWindow->resetOpenGLState(); + QQuickOpenGLUtils::resetOpenGLState(); } gl::globalRelease(); } diff --git a/libraries/qml/src/qml/impl/SharedObject.cpp b/libraries/qml/src/qml/impl/SharedObject.cpp index 555389c4c3a..6ac567d4df4 100644 --- a/libraries/qml/src/qml/impl/SharedObject.cpp +++ b/libraries/qml/src/qml/impl/SharedObject.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include #include @@ -72,7 +73,9 @@ SharedObject::SharedObject() { _quickWindow = new QQuickWindow(_renderControl); _quickWindow->setFormat(getDefaultOpenGLSurfaceFormat()); _quickWindow->setColor(Qt::transparent); - _quickWindow->setClearBeforeRendering(true); + // QT6TODO: setClearBeforeRendering was reoved, what to do about this? + // https://doc.qt.io/qt-6/quick-changes-qt6.html + //_quickWindow->setClearBeforeRendering(true); #endif @@ -292,7 +295,8 @@ void SharedObject::initializeRenderControl(QOpenGLContext* context) { #ifndef DISABLE_QML if (!nsightActive()) { - _renderControl->initialize(context); + // QT6TODO: Qt6 does not accept context as parameter here. I'm not sure if it's a problem. + _renderControl->initialize(); } #endif } @@ -310,7 +314,7 @@ void SharedObject::releaseTextureAndFence() { void SharedObject::setRenderTarget(uint32_t fbo, const QSize& size) { #ifndef DISABLE_QML - _quickWindow->setRenderTarget(fbo, size); + _quickWindow->setRenderTarget(QQuickRenderTarget::fromOpenGLTexture(fbo, size)); #endif } diff --git a/libraries/recording/src/recording/ClipCache.cpp b/libraries/recording/src/recording/ClipCache.cpp index 285b5b7acff..621bdbbccd6 100644 --- a/libraries/recording/src/recording/ClipCache.cpp +++ b/libraries/recording/src/recording/ClipCache.cpp @@ -47,7 +47,7 @@ NetworkClipLoaderPointer ClipCache::getClipLoader(const QUrl& url) { if (QThread::currentThread() != thread()) { NetworkClipLoaderPointer result; BLOCKING_INVOKE_METHOD(this, "getClipLoader", - Q_RETURN_ARG(NetworkClipLoaderPointer, result), Q_ARG(const QUrl&, url)); + Q_GENERIC_RETURN_ARG(NetworkClipLoaderPointer, result), Q_GENERIC_ARG(const QUrl&, url)); return result; } diff --git a/libraries/recording/src/recording/RecordingScriptingInterface.cpp b/libraries/recording/src/recording/RecordingScriptingInterface.cpp index a50453bb697..f5da3522026 100644 --- a/libraries/recording/src/recording/RecordingScriptingInterface.cpp +++ b/libraries/recording/src/recording/RecordingScriptingInterface.cpp @@ -151,7 +151,7 @@ void RecordingScriptingInterface::setPlayerAudioOffset(float audioOffset) { void RecordingScriptingInterface::setPlayerTime(float time) { if (QThread::currentThread() != thread()) { - BLOCKING_INVOKE_METHOD(this, "setPlayerTime", Q_ARG(float, time)); + BLOCKING_INVOKE_METHOD(this, "setPlayerTime", Q_GENERIC_ARG(float, time)); return; } diff --git a/libraries/render-utils/CMakeLists.txt b/libraries/render-utils/CMakeLists.txt index 971d09fbb65..8ece39525ed 100644 --- a/libraries/render-utils/CMakeLists.txt +++ b/libraries/render-utils/CMakeLists.txt @@ -5,7 +5,7 @@ set(TARGET_NAME render-utils) generate_render_pipelines() # pull in the resources.qrc file -qt5_add_resources(QT_RESOURCES_FILE "${CMAKE_CURRENT_SOURCE_DIR}/res/fonts/fonts.qrc") +qt6_add_resources(QT_RESOURCES_FILE "${CMAKE_CURRENT_SOURCE_DIR}/res/fonts/fonts.qrc") setup_hifi_library(Gui Network Qml Quick) target_include_directories(${TARGET_NAME} PRIVATE "${CMAKE_BINARY_DIR}/libraries/render-utils/src") target_include_directories(${TARGET_NAME} PRIVATE "${CMAKE_SOURCE_DIR}/libraries/render-utils/src") diff --git a/libraries/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp index 9784ba13460..a4fd959a6c2 100644 --- a/libraries/render-utils/src/Model.cpp +++ b/libraries/render-utils/src/Model.cpp @@ -1399,7 +1399,7 @@ QStringList Model::getJointNames() const { if (QThread::currentThread() != thread()) { QStringList result; BLOCKING_INVOKE_METHOD(const_cast(this), "getJointNames", - Q_RETURN_ARG(QStringList, result)); + Q_GENERIC_RETURN_ARG(QStringList, result)); return result; } return isLoaded() ? getHFMModel().getJointNames() : QStringList(); diff --git a/libraries/render-utils/src/text/Font.cpp b/libraries/render-utils/src/text/Font.cpp index f0c4ecd0ae4..ed487c4597b 100644 --- a/libraries/render-utils/src/text/Font.cpp +++ b/libraries/render-utils/src/text/Font.cpp @@ -147,7 +147,7 @@ void Font::read(QIODevice& in) { auto& g = arteryFont.variants[0].glyphs[i]; Glyph glyph; - glyph.c = g.codepoint; + glyph.c = QChar(g.codepoint); glyph.texOffset = glm::vec2(g.imageBounds.l, g.imageBounds.b); glyph.texSize = glm::vec2(g.imageBounds.r, g.imageBounds.t) - glyph.texOffset; glyph.offset = glm::vec2(g.planeBounds.l, g.planeBounds.b); diff --git a/libraries/script-engine/src/SceneScriptingInterface.h b/libraries/script-engine/src/SceneScriptingInterface.h index 26a0f130a1e..cede6fb3782 100644 --- a/libraries/script-engine/src/SceneScriptingInterface.h +++ b/libraries/script-engine/src/SceneScriptingInterface.h @@ -17,6 +17,8 @@ #ifndef hifi_SceneScriptingInterface_h #define hifi_SceneScriptingInterface_h +#include + #include /*@jsdoc diff --git a/libraries/script-engine/src/ScriptCache.cpp b/libraries/script-engine/src/ScriptCache.cpp index b9e9c83ad1c..eee6f3474a6 100644 --- a/libraries/script-engine/src/ScriptCache.cpp +++ b/libraries/script-engine/src/ScriptCache.cpp @@ -16,7 +16,7 @@ #include #include #include -#include +#include #include #include #include diff --git a/libraries/script-engine/src/ScriptCache.h b/libraries/script-engine/src/ScriptCache.h index 2fb9f91046d..f480a317ae4 100644 --- a/libraries/script-engine/src/ScriptCache.h +++ b/libraries/script-engine/src/ScriptCache.h @@ -18,6 +18,8 @@ #define hifi_ScriptCache_h #include +#include + #include using contentAvailableCallback = std::function; diff --git a/libraries/script-engine/src/ScriptEngines.cpp b/libraries/script-engine/src/ScriptEngines.cpp index ed446269834..597bab81f4e 100644 --- a/libraries/script-engine/src/ScriptEngines.cpp +++ b/libraries/script-engine/src/ScriptEngines.cpp @@ -557,13 +557,13 @@ ScriptManagerPointer ScriptEngines::loadScript(const QUrl& scriptFilename, bool bool activateMainWindow, bool reload, bool quitWhenFinished) { if (thread() != QThread::currentThread()) { ScriptManagerPointer result { nullptr }; - BLOCKING_INVOKE_METHOD(this, "loadScript", Q_RETURN_ARG(ScriptManagerPointer, result), - Q_ARG(QUrl, scriptFilename), - Q_ARG(bool, isUserLoaded), - Q_ARG(bool, loadScriptFromEditor), - Q_ARG(bool, activateMainWindow), - Q_ARG(bool, reload), - Q_ARG(bool, quitWhenFinished)); + BLOCKING_INVOKE_METHOD(this, "loadScript", Q_GENERIC_RETURN_ARG(ScriptManagerPointer, result), + Q_GENERIC_ARG(QUrl, scriptFilename), + Q_GENERIC_ARG(bool, isUserLoaded), + Q_GENERIC_ARG(bool, loadScriptFromEditor), + Q_GENERIC_ARG(bool, activateMainWindow), + Q_GENERIC_ARG(bool, reload), + Q_GENERIC_ARG(bool, quitWhenFinished)); return result; } QUrl scriptUrl; diff --git a/libraries/script-engine/src/ScriptEngines.h b/libraries/script-engine/src/ScriptEngines.h index 97f798b804d..3da02d7d600 100644 --- a/libraries/script-engine/src/ScriptEngines.h +++ b/libraries/script-engine/src/ScriptEngines.h @@ -409,7 +409,7 @@ protected slots: ScriptManager::Context _context; QReadWriteLock _scriptManagersHashLock; - QMultiHash _scriptManagersHash; + QHash _scriptManagersHash; QSet _allKnownScriptManagers; QMutex _allScriptsMutex; ScriptsModel _scriptsModel; diff --git a/libraries/script-engine/src/ScriptManager.cpp b/libraries/script-engine/src/ScriptManager.cpp index 65b333e5cee..6f1a8c18ca0 100644 --- a/libraries/script-engine/src/ScriptManager.cpp +++ b/libraries/script-engine/src/ScriptManager.cpp @@ -22,6 +22,7 @@ #include #include #include +#include #include #include @@ -1612,7 +1613,7 @@ ScriptValue ScriptManager::instantiateModule(const ScriptValue& module, const QS // scoped vars for consistency with Node.js closure.setProperty("require", module.property("require")); closure.setProperty("__filename", modulePath, READONLY_HIDDEN_PROP_FLAGS); - closure.setProperty("__dirname", QString(modulePath).replace(QRegExp("/[^/]*$"), ""), READONLY_HIDDEN_PROP_FLAGS); + closure.setProperty("__dirname", QString(modulePath).replace(QRegularExpression("/[^/]*$"), ""), READONLY_HIDDEN_PROP_FLAGS); //_engine->scriptValueDebugDetails(module); result = _engine->evaluateInClosure(closure, _engine->newProgram( sourceCode, modulePath )); } @@ -2016,7 +2017,7 @@ QVariant ScriptManager::cloneEntityScriptDetails(const EntityItemID& entityID, c } QFuture ScriptManager::getLocalEntityScriptDetails(const EntityItemID& entityID, const QString& scriptURL) { - return QtConcurrent::run(this, &ScriptManager::cloneEntityScriptDetails, entityID, scriptURL); + return QtConcurrent::run(&ScriptManager::cloneEntityScriptDetails, this, entityID, scriptURL); } bool ScriptManager::getEntityScriptDetails(const EntityItemID& entityID, const QString& scriptURL, EntityScriptDetails &details) const { @@ -2337,7 +2338,7 @@ void ScriptManager::entityScriptContentAvailable(const EntityItemID& entityID, c bool passList = false; // assume unsafe QString allowlistPrefix = "[ALLOWLIST ENTITY SCRIPTS]"; QList safeURLPrefixes = { "file:///", "atp:", "cache:" }; - safeURLPrefixes += qEnvironmentVariable("EXTRA_ALLOWLIST").trimmed().split(QRegExp("\\s*,\\s*"), Qt::SkipEmptyParts); + safeURLPrefixes += qEnvironmentVariable("EXTRA_ALLOWLIST").trimmed().split(QRegularExpression("\\s*,\\s*"), Qt::SkipEmptyParts); // Entity Script Allowlist toggle check. Setting::Handle allowlistEnabled {"private/allowlistEnabled", false }; @@ -2348,7 +2349,7 @@ void ScriptManager::entityScriptContentAvailable(const EntityItemID& entityID, c // Pull SAFEURLS from the 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); safeURLPrefixes += settingsSafeURLS; // END Pull SAFEURLS from the Interface.JSON settings. diff --git a/libraries/script-engine/src/ScriptMessage.cpp b/libraries/script-engine/src/ScriptMessage.cpp index 0b3ea1abc86..3650e02ad18 100644 --- a/libraries/script-engine/src/ScriptMessage.cpp +++ b/libraries/script-engine/src/ScriptMessage.cpp @@ -40,7 +40,7 @@ bool ScriptMessage::fromJson(const QJsonObject &object) { } _messageContent = object["message"].toString(); _lineNumber = object["lineNumber"].toInt(); - _fileName = object["fileName"].toInt(); + _fileName = object["fileName"].toString(); _entityID = QUuid::fromString(object["entityID"].toString()); _scriptType = static_cast(object["type"].toInt()); _severity = static_cast(object["severity"].toInt()); diff --git a/libraries/script-engine/src/ScriptValueUtils.cpp b/libraries/script-engine/src/ScriptValueUtils.cpp index 450e8797c50..da48cba29f3 100644 --- a/libraries/script-engine/src/ScriptValueUtils.cpp +++ b/libraries/script-engine/src/ScriptValueUtils.cpp @@ -1097,7 +1097,8 @@ QVector qVectorEntityItemIDFromScriptValue(const ScriptValue& arra newVector.reserve(length); for (int i = 0; i < length; i++) { QString uuidAsString = array.property(i).toString(); - EntityItemID fromString(uuidAsString); + QUuid uuid(uuidAsString); + EntityItemID fromString(uuid); newVector << fromString; } return newVector; diff --git a/libraries/script-engine/src/ScriptsModel.cpp b/libraries/script-engine/src/ScriptsModel.cpp index 18adf103529..b1ef09377a7 100644 --- a/libraries/script-engine/src/ScriptsModel.cpp +++ b/libraries/script-engine/src/ScriptsModel.cpp @@ -15,6 +15,7 @@ #include #include #include +#include #include #include diff --git a/libraries/script-engine/src/ScriptsModelFilter.cpp b/libraries/script-engine/src/ScriptsModelFilter.cpp index 1879e547c09..31ea329f68a 100644 --- a/libraries/script-engine/src/ScriptsModelFilter.cpp +++ b/libraries/script-engine/src/ScriptsModelFilter.cpp @@ -26,7 +26,7 @@ bool ScriptsModelFilter::lessThan(const QModelIndex& left, const QModelIndex& ri } bool ScriptsModelFilter::filterAcceptsRow(int sourceRow, const QModelIndex& sourceParent) const { - if (!filterRegExp().isEmpty()) { + if (!filterRegularExpression().pattern().isEmpty()) { ScriptsModel* scriptsModel = static_cast(sourceModel()); TreeNodeBase* node = scriptsModel->getFolderNodes( static_cast(scriptsModel->getTreeNodeFromIndex(sourceParent))).at(sourceRow); diff --git a/libraries/script-engine/src/TouchEvent.cpp b/libraries/script-engine/src/TouchEvent.cpp index f6b61a49f29..c4352698568 100644 --- a/libraries/script-engine/src/TouchEvent.cpp +++ b/libraries/script-engine/src/TouchEvent.cpp @@ -79,11 +79,11 @@ void TouchEvent::initWithQTouchEvent(const QTouchEvent& event) { touchPoints = tPoints.count(); if (touchPoints > 1) { for (int i = 0; i < touchPoints; ++i) { - touchAvgX += (float)tPoints[i].pos().x(); - touchAvgY += (float)tPoints[i].pos().y(); + touchAvgX += (float)tPoints[i].position().x(); + touchAvgY += (float)tPoints[i].position().y(); // add it to our points vector - glm::vec2 thisPoint(tPoints[i].pos().x(), tPoints[i].pos().y()); + glm::vec2 thisPoint(tPoints[i].position().x(), tPoints[i].position().y()); points << thisPoint; } touchAvgX /= (float)(touchPoints); @@ -91,8 +91,8 @@ void TouchEvent::initWithQTouchEvent(const QTouchEvent& event) { } else { // I'm not sure this should ever happen, why would Qt send us a touch event for only one point? // maybe this happens in the case of a multi-touch where all but the last finger is released? - touchAvgX = tPoints[0].pos().x(); - touchAvgY = tPoints[0].pos().y(); + touchAvgX = tPoints[0].position().x(); + touchAvgY = tPoints[0].position().y(); } x = touchAvgX; y = touchAvgY; @@ -102,7 +102,7 @@ void TouchEvent::initWithQTouchEvent(const QTouchEvent& event) { float maxRadius = 0.0f; glm::vec2 center(x,y); for (int i = 0; i < touchPoints; ++i) { - glm::vec2 touchPoint(tPoints[i].pos().x(), tPoints[i].pos().y()); + glm::vec2 touchPoint(tPoints[i].position().x(), tPoints[i].position().y()); float thisRadius = glm::distance(center,touchPoint); if (thisRadius > maxRadius) { maxRadius = thisRadius; @@ -121,10 +121,10 @@ void TouchEvent::initWithQTouchEvent(const QTouchEvent& event) { } angle = totalAngle/(float)touchPoints; - isPressed = event.touchPointStates().testFlag(Qt::TouchPointPressed); - isMoved = event.touchPointStates().testFlag(Qt::TouchPointMoved); - isStationary = event.touchPointStates().testFlag(Qt::TouchPointStationary); - isReleased = event.touchPointStates().testFlag(Qt::TouchPointReleased); + isPressed = event.touchPointStates().testFlag(QEventPoint::State::Pressed); + isMoved = event.touchPointStates().testFlag(QEventPoint::State::Updated); + isStationary = event.touchPointStates().testFlag(QEventPoint::State::Stationary); + isReleased = event.touchPointStates().testFlag(QEventPoint::State::Released); // keyboard modifiers isShifted = event.modifiers().testFlag(Qt::ShiftModifier); diff --git a/libraries/script-engine/src/UsersScriptingInterface.h b/libraries/script-engine/src/UsersScriptingInterface.h index 026441f5fef..0ac79bda58b 100644 --- a/libraries/script-engine/src/UsersScriptingInterface.h +++ b/libraries/script-engine/src/UsersScriptingInterface.h @@ -18,6 +18,8 @@ #ifndef hifi_UsersScriptingInterface_h #define hifi_UsersScriptingInterface_h +#include + #include #include #include diff --git a/libraries/script-engine/src/v8/ScriptEngineV8.cpp b/libraries/script-engine/src/v8/ScriptEngineV8.cpp index ca19abd9572..6b364cab6ba 100644 --- a/libraries/script-engine/src/v8/ScriptEngineV8.cpp +++ b/libraries/script-engine/src/v8/ScriptEngineV8.cpp @@ -798,9 +798,9 @@ ScriptValue ScriptEngineV8::evaluate(const QString& sourceCode, const QString& f "sourceCode:" << sourceCode << " fileName:" << fileName; #endif BLOCKING_INVOKE_METHOD(this, "evaluate", - Q_RETURN_ARG(ScriptValue, result), - Q_ARG(const QString&, sourceCode), - Q_ARG(const QString&, fileName)); + Q_GENERIC_RETURN_ARG(ScriptValue, result), + Q_GENERIC_ARG(const QString&, sourceCode), + Q_GENERIC_ARG(const QString&, fileName)); return result; }*/ // Compile and check syntax @@ -1039,8 +1039,8 @@ Q_INVOKABLE ScriptValue ScriptEngineV8::evaluate(const ScriptProgramPointer& pro "sourceCode:" << sourceCode << " fileName:" << fileName; #endif BLOCKING_INVOKE_METHOD(this, "evaluate", - Q_RETURN_ARG(ScriptValue, result), - Q_ARG(const ScriptProgramPointer&, program)); + Q_GENERIC_RETURN_ARG(ScriptValue, result), + Q_GENERIC_ARG(const ScriptProgramPointer&, program)); return result; } _evaluatingCounter++; @@ -1422,7 +1422,7 @@ ScriptValue ScriptEngineV8::create(int type, const void* ptr) { Q_ASSERT(_v8Isolate->IsCurrent()); v8::HandleScope handleScope(_v8Isolate); v8::Context::Scope contextScope(getContext()); - QVariant variant(type, ptr); + QVariant variant(QMetaType(type), ptr); V8ScriptValue scriptValue = castVariantToValue(variant); return ScriptValue(new ScriptValueV8Wrapper(this, std::move(scriptValue))); } diff --git a/libraries/script-engine/src/v8/ScriptEngineV8_cast.cpp b/libraries/script-engine/src/v8/ScriptEngineV8_cast.cpp index 9dfce6f6e9c..6eb5f7efd72 100644 --- a/libraries/script-engine/src/v8/ScriptEngineV8_cast.cpp +++ b/libraries/script-engine/src/v8/ScriptEngineV8_cast.cpp @@ -562,7 +562,7 @@ bool ScriptEngineV8::convertJSArrayToVariant(v8::Local array, QVarian for (int i = 0; i < length; i++) { v8::Local v8Property; if (!array->Get(context, i).ToLocal(&v8Property)) { - qCDebug(scriptengine_v8) << "ScriptEngineV8::convertJSArrayToVariant could not get property: " + QString(i); + qCDebug(scriptengine_v8) << "ScriptEngineV8::convertJSArrayToVariant could not get property: " + QString::number(i); continue; } QVariant property; @@ -570,7 +570,7 @@ bool ScriptEngineV8::convertJSArrayToVariant(v8::Local array, QVarian if (castValueToVariant(V8ScriptValue(this, v8Property), property, QMetaType::UnknownType)) { properties.append(property); } else { - qCDebug(scriptengine_v8) << "ScriptEngineV8::convertJSArrayToVariant could cast property to variant: " + QString(i); + qCDebug(scriptengine_v8) << "ScriptEngineV8::convertJSArrayToVariant could cast property to variant: " + QString::number(i); ; } } diff --git a/libraries/script-engine/src/v8/ScriptObjectV8Proxy.cpp b/libraries/script-engine/src/v8/ScriptObjectV8Proxy.cpp index d2c73708e7d..e53add90e7d 100644 --- a/libraries/script-engine/src/v8/ScriptObjectV8Proxy.cpp +++ b/libraries/script-engine/src/v8/ScriptObjectV8Proxy.cpp @@ -23,6 +23,7 @@ #include "ScriptContextV8Wrapper.h" #include "ScriptValueV8Wrapper.h" #include "ScriptEngineLoggingV8.h" +#include "shared/QtHelpers.h" Q_DECLARE_METATYPE(ScriptValue) @@ -1035,14 +1036,14 @@ void ScriptMethodV8Proxy::call(const v8::FunctionCallbackInfo& argume v8::Local argVal = arguments[arg]; if (methodArgTypeId == scriptValueTypeId) { qScriptArgLists[i].append(ScriptValue(new ScriptValueV8Wrapper(_engine, V8ScriptValue(_engine, argVal)))); - qGenArgsVectors[i][arg] = Q_ARG(ScriptValue, qScriptArgLists[i].back()); + qGenArgsVectors[i][arg] = Q_GENERIC_ARG(ScriptValue, qScriptArgLists[i].back()); } else if (methodArgTypeId == QMetaType::QVariant) { QVariant varArgVal; if (!_engine->castValueToVariant(V8ScriptValue(_engine, argVal), varArgVal, methodArgTypeId)) { conversionFailures++; } else { qVarArgLists[i].append(varArgVal); - qGenArgsVectors[i][arg] = Q_ARG(QVariant, qVarArgLists[i].back()); + qGenArgsVectors[i][arg] = Q_GENERIC_ARG(QVariant, qVarArgLists[i].back()); } } else { QVariant varArgVal; @@ -1108,7 +1109,7 @@ void ScriptMethodV8Proxy::call(const v8::FunctionCallbackInfo& argume return; } else if (returnTypeId == scriptValueTypeId) { ScriptValue result; - bool success = meta.invoke(qobject, Qt::DirectConnection, Q_RETURN_ARG(ScriptValue, result), qGenArgs[0], + bool success = meta.invoke(qobject, Qt::DirectConnection, Q_GENERIC_RETURN_ARG(ScriptValue, result), qGenArgs[0], qGenArgs[1], qGenArgs[2], qGenArgs[3], qGenArgs[4], qGenArgs[5], qGenArgs[6], qGenArgs[7], qGenArgs[8], qGenArgs[9]); if (!success) { @@ -1121,7 +1122,7 @@ void ScriptMethodV8Proxy::call(const v8::FunctionCallbackInfo& argume } else { // a lot of type conversion assistance thanks to https://stackoverflow.com/questions/28457819/qt-invoke-method-with-qvariant const char* typeName = meta.typeName(); - QVariant qRetVal(returnTypeId, static_cast(NULL)); + QVariant qRetVal(QMetaType(returnTypeId), static_cast(NULL)); QGenericReturnArgument sRetVal(typeName, const_cast(qRetVal.constData())); bool success = @@ -1259,7 +1260,7 @@ int ScriptSignalV8Proxy::qt_metacall(QMetaObject::Call call, int id, void** argu for (int arg = 0; arg < numArgs; ++arg) { int methodArgTypeId = _meta.parameterType(arg); Q_ASSERT(methodArgTypeId != QMetaType::UnknownType); - QVariant argValue(methodArgTypeId, arguments[arg + 1]); + QVariant argValue(QMetaType(methodArgTypeId), arguments[arg + 1]); args[arg] = _engine->castVariantToValue(argValue).get(); } for (ConnectionList::iterator iter = connections.begin(); iter != connections.end(); ++iter) { diff --git a/libraries/script-engine/src/v8/ScriptValueV8Wrapper.cpp b/libraries/script-engine/src/v8/ScriptValueV8Wrapper.cpp index 769e2c66044..9658f825104 100644 --- a/libraries/script-engine/src/v8/ScriptValueV8Wrapper.cpp +++ b/libraries/script-engine/src/v8/ScriptValueV8Wrapper.cpp @@ -448,7 +448,7 @@ void ScriptValueV8Wrapper::setProperty(quint32 arrayIndex, const ScriptValue& va qCDebug(scriptengine_v8) << "Failed to set property"; } } else { - qCDebug(scriptengine_v8) << "Failed to set property: " + QString(arrayIndex) + " - parent is not an object"; + qCDebug(scriptengine_v8) << "Failed to set property: " + QString::number(arrayIndex) + " - parent is not an object"; } //V8TODO: what about flags? //_value.setProperty(arrayIndex, unwrapped, (V8ScriptValue::PropertyFlags)(int)flags); diff --git a/libraries/shared/src/ApplicationVersion.cpp b/libraries/shared/src/ApplicationVersion.cpp index 5c2d5ad11ce..c6be7532b76 100644 --- a/libraries/shared/src/ApplicationVersion.cpp +++ b/libraries/shared/src/ApplicationVersion.cpp @@ -14,7 +14,7 @@ #include #include -#include +#include #include ApplicationVersion::ApplicationVersion(const QString& versionString) : diff --git a/libraries/shared/src/Grab.cpp b/libraries/shared/src/Grab.cpp index 195bc61f66c..118794ff70b 100644 --- a/libraries/shared/src/Grab.cpp +++ b/libraries/shared/src/Grab.cpp @@ -11,6 +11,8 @@ #include "Grab.h" +#include + QByteArray Grab::toByteArray() { QByteArray ba; QDataStream dataStream(&ba, QIODevice::WriteOnly); diff --git a/libraries/shared/src/HifiConfigVariantMap.cpp b/libraries/shared/src/HifiConfigVariantMap.cpp index 71748ffd10e..11c4f94a32c 100644 --- a/libraries/shared/src/HifiConfigVariantMap.cpp +++ b/libraries/shared/src/HifiConfigVariantMap.cpp @@ -20,19 +20,20 @@ #include #include #include +#include #include "PathUtils.h" #include "SharedLogging.h" QVariantMap HifiConfigVariantMap::mergeCLParametersWithJSONConfig(const QStringList& argumentList) { - QMultiMap mergedMap; + QVariantMap mergedMap; // Add anything in the CL parameter list to the variant map. // Take anything with a dash in it as a key, and the values after it as the value. const QString DASHED_KEY_REGEX_STRING = "(^-{1,2})([\\w-]+)"; - QRegExp dashedKeyRegex(DASHED_KEY_REGEX_STRING); + QRegularExpression dashedKeyRegex(DASHED_KEY_REGEX_STRING); int keyIndex = argumentList.indexOf(dashedKeyRegex); int nextKeyIndex = 0; @@ -43,7 +44,7 @@ QVariantMap HifiConfigVariantMap::mergeCLParametersWithJSONConfig(const QStringL while (keyIndex != -1) { if (argumentList[keyIndex] != CONFIG_FILE_OPTION) { // we have a key - look forward to see how many values associate to it - QString key = dashedKeyRegex.cap(2); + QString key = dashedKeyRegex.namedCaptureGroups()[2]; nextKeyIndex = argumentList.indexOf(dashedKeyRegex, keyIndex + 1); diff --git a/libraries/shared/src/LogHandler.h b/libraries/shared/src/LogHandler.h index e64f8e29c35..60c85bd1009 100644 --- a/libraries/shared/src/LogHandler.h +++ b/libraries/shared/src/LogHandler.h @@ -15,7 +15,7 @@ #include #include -#include +#include #include #include #include diff --git a/libraries/shared/src/PathUtils.cpp b/libraries/shared/src/PathUtils.cpp index be605334068..6f0b65aa09a 100644 --- a/libraries/shared/src/PathUtils.cpp +++ b/libraries/shared/src/PathUtils.cpp @@ -192,7 +192,7 @@ bool PathUtils::deleteMyTemporaryDir(QString dirName) { QRegularExpression re { "^" + QRegularExpression::escape(appName) + "\\-(?\\d+)\\-(?\\d+)$" }; auto match = re.match(dirName); - auto pid = match.capturedRef("pid").toLongLong(); + auto pid = match.capturedView("pid").toLongLong(); if (match.hasMatch() && rootTempDir.exists(dirName) && pid == qApp->applicationPid()) { auto absoluteDirPath = QDir(rootTempDir.absoluteFilePath(dirName)); @@ -227,8 +227,8 @@ int PathUtils::removeTemporaryApplicationDirs(QString appName) { auto match = re.match(dirName); if (match.hasMatch()) { - auto pid = match.capturedRef("pid").toLongLong(); - auto timestamp = match.capturedRef("timestamp"); + auto pid = match.capturedView("pid").toLongLong(); + auto timestamp = match.capturedView("timestamp"); if (!processIsRunning(pid)) { qDebug() << " Removing old temporary directory: " << dir.absoluteFilePath(); absoluteDirPath.removeRecursively(); diff --git a/libraries/shared/src/RegisteredMetaTypes.h b/libraries/shared/src/RegisteredMetaTypes.h index b3b742e1ba3..a9fc605c3cd 100644 --- a/libraries/shared/src/RegisteredMetaTypes.h +++ b/libraries/shared/src/RegisteredMetaTypes.h @@ -31,12 +31,13 @@ class QColor; class QUrl; +// QT6TODO: Q_DECLARE_METATYPE(uint16_t) Q_DECLARE_METATYPE(glm::vec2) Q_DECLARE_METATYPE(glm::u8vec3) -Q_DECLARE_METATYPE(glm::vec3) -Q_DECLARE_METATYPE(glm::vec4) -Q_DECLARE_METATYPE(glm::quat) +//Q_DECLARE_METATYPE(glm::vec3) +//Q_DECLARE_METATYPE(glm::vec4) +//Q_DECLARE_METATYPE(glm::quat) Q_DECLARE_METATYPE(glm::mat4) Q_DECLARE_METATYPE(QVector) Q_DECLARE_METATYPE(unsigned int) diff --git a/libraries/shared/src/RunningMarker.cpp b/libraries/shared/src/RunningMarker.cpp index cb7b39320c4..09e16a40253 100644 --- a/libraries/shared/src/RunningMarker.cpp +++ b/libraries/shared/src/RunningMarker.cpp @@ -47,5 +47,5 @@ void RunningMarker::deleteRunningMarkerFile() { } QString RunningMarker::getFilePath() const { - return QStandardPaths::writableLocation(QStandardPaths::DataLocation) + "/" + _name; + return QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation) + "/" + _name; } diff --git a/libraries/shared/src/SettingHelpers.cpp b/libraries/shared/src/SettingHelpers.cpp index b4caa2f7bf4..ab84c1ca3df 100644 --- a/libraries/shared/src/SettingHelpers.cpp +++ b/libraries/shared/src/SettingHelpers.cpp @@ -22,6 +22,7 @@ #include #include #include +#include #include "SharedLogging.h" diff --git a/libraries/shared/src/SharedUtil.cpp b/libraries/shared/src/SharedUtil.cpp index 55bb1f01763..7003fb7e246 100644 --- a/libraries/shared/src/SharedUtil.cpp +++ b/libraries/shared/src/SharedUtil.cpp @@ -190,7 +190,7 @@ void outputBits(unsigned char byte, QDebug* continuedDebug) { qts << qSetPadChar('0'); if (isalnum(byte)) { - qts << " (" << QString(byte) << ") : "; + qts << " (" << QString(QChar(byte)) << ") : "; } else { qts << " (0x" << Qt::hex << qSetFieldWidth(2) << byte << qSetFieldWidth(0) << "): "; } @@ -752,9 +752,9 @@ QString formatSecondsElapsed(float seconds) { } else { result += " day "; } - result += QDateTime::fromTime_t(rest).toUTC().toString("h 'hours' m 'minutes' s 'seconds'"); + result += QDateTime::fromSecsSinceEpoch(rest).toUTC().toString("h 'hours' m 'minutes' s 'seconds'"); } else { - result = QDateTime::fromTime_t(seconds).toUTC().toString("h 'hours' m 'minutes' s 'seconds'"); + result = QDateTime::fromSecsSinceEpoch(seconds).toUTC().toString("h 'hours' m 'minutes' s 'seconds'"); } return result; } diff --git a/libraries/shared/src/SharedUtil.h b/libraries/shared/src/SharedUtil.h index c40cae5f766..ac440759559 100644 --- a/libraries/shared/src/SharedUtil.h +++ b/libraries/shared/src/SharedUtil.h @@ -24,6 +24,7 @@ #include #include #include +#include #include "NumericalConstants.h" // When writing out avatarEntities to a QByteArray, if the parentID is the ID of MyAvatar, use this ID instead. This allows diff --git a/libraries/shared/src/ShutdownEventListener.cpp b/libraries/shared/src/ShutdownEventListener.cpp index 6f043cd43e6..53e90e8aa34 100644 --- a/libraries/shared/src/ShutdownEventListener.cpp +++ b/libraries/shared/src/ShutdownEventListener.cpp @@ -39,7 +39,7 @@ ShutdownEventListener::ShutdownEventListener(QObject* parent) : QObject(parent) } -bool ShutdownEventListener::nativeEventFilter(const QByteArray &eventType, void* msg, long* result) { +bool ShutdownEventListener::nativeEventFilter(const QByteArray &eventType, void* msg, qintptr* result) { #ifdef Q_OS_WIN if (eventType == "windows_generic_MSG") { MSG* message = (MSG*)msg; diff --git a/libraries/shared/src/ShutdownEventListener.h b/libraries/shared/src/ShutdownEventListener.h index 8a9363e2c84..7076aff70e2 100644 --- a/libraries/shared/src/ShutdownEventListener.h +++ b/libraries/shared/src/ShutdownEventListener.h @@ -20,7 +20,7 @@ class ShutdownEventListener : public QObject, public QAbstractNativeEventFilter public: static ShutdownEventListener& getInstance(); - virtual bool nativeEventFilter(const QByteArray& eventType, void* message, long* result) override; + virtual bool nativeEventFilter(const QByteArray& eventType, void* message, qintptr* result) override; private: ShutdownEventListener(QObject* parent = 0); }; diff --git a/libraries/shared/src/ThreadHelpers.h b/libraries/shared/src/ThreadHelpers.h index 42de117e670..db77493872d 100644 --- a/libraries/shared/src/ThreadHelpers.h +++ b/libraries/shared/src/ThreadHelpers.h @@ -11,6 +11,7 @@ #ifndef hifi_ThreadHelpers_h #define hifi_ThreadHelpers_h +#include #include #include diff --git a/libraries/shared/src/shared/FileLogger.cpp b/libraries/shared/src/shared/FileLogger.cpp index d467aed0f86..f16bdc6ebc8 100644 --- a/libraries/shared/src/shared/FileLogger.cpp +++ b/libraries/shared/src/shared/FileLogger.cpp @@ -17,6 +17,7 @@ #include #include #include +#include #include "FileUtils.h" #include "NetworkUtils.h" diff --git a/libraries/shared/src/shared/FileUtils.cpp b/libraries/shared/src/shared/FileUtils.cpp index 14ac78bfbd9..5ba37e4d980 100644 --- a/libraries/shared/src/shared/FileUtils.cpp +++ b/libraries/shared/src/shared/FileUtils.cpp @@ -19,6 +19,7 @@ #include #include #include +#include #include #include #include @@ -122,7 +123,7 @@ QString FileUtils::standardPath(QString subfolder) { #ifdef Q_OS_ANDROID QString path = QStandardPaths::writableLocation(QStandardPaths::CacheLocation); #else - QString path = QStandardPaths::writableLocation(QStandardPaths::DataLocation); + QString path = QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation); #endif if (!subfolder.startsWith("/")) { subfolder.prepend("/"); diff --git a/libraries/shared/src/shared/JSONHelpers.cpp b/libraries/shared/src/shared/JSONHelpers.cpp index 298a1ea85d6..d151f4241d2 100644 --- a/libraries/shared/src/shared/JSONHelpers.cpp +++ b/libraries/shared/src/shared/JSONHelpers.cpp @@ -30,7 +30,7 @@ T glmFromJson(const QJsonValue& json) { T result; if (json.isArray()) { QJsonArray array = json.toArray(); - auto length = std::min(array.size(), result.length()); + auto length = std::min(array.size(), static_cast(result.length())); for (auto i = 0; i < length; ++i) { result[i] = (float)array[i].toDouble(); } @@ -136,7 +136,7 @@ void qObjectFromJsonValue(const QJsonValue& j, QObject& o) { qObjectFromJsonValue(it.value(), *child); } } else { - o.setProperty(key.c_str(), it.value()); + o.setProperty(key.c_str(), it.value().toVariant()); } } } diff --git a/libraries/shared/src/shared/PlatformHelper.h b/libraries/shared/src/shared/PlatformHelper.h index 760eb7bd420..5896c2ab4bf 100644 --- a/libraries/shared/src/shared/PlatformHelper.h +++ b/libraries/shared/src/shared/PlatformHelper.h @@ -12,6 +12,7 @@ #include #include +#include #include "../DependencyManager.h" class PlatformHelper : public QObject, public Dependency { diff --git a/libraries/shared/src/shared/QtHelpers.h b/libraries/shared/src/shared/QtHelpers.h index 880d285f815..9c8c0daf67a 100644 --- a/libraries/shared/src/shared/QtHelpers.h +++ b/libraries/shared/src/shared/QtHelpers.h @@ -18,6 +18,11 @@ #include "../Profile.h" +// Macros for these are missing from QT6 +// QT6TODO: replace QGenericArgument and QGenericReturnArgument with modern equivalents +#define Q_GENERIC_ARG(type, data) QArgument(#type, data) +#define Q_GENERIC_RETURN_ARG(type, data) QReturnArgument(#type, data) + #if defined(Q_OS_WIN) // Enable event queue debugging //#define DEBUG_EVENT_QUEUE @@ -113,4 +118,35 @@ blockingInvokeMethod(const char* callingFunction, QObject* context, Func functio #define BLOCKING_INVOKE_METHOD(obj, member, ...) \ ::hifi::qt::blockingInvokeMethod(__FUNCTION__, obj, member, ##__VA_ARGS__) +inline QMultiHash qVariantToQMultiHash(const QVariant &variant) { + QMultiHash multiHash; + // QT6TODO: check if variant can be converted to a list + + for(QVariant item : variant.toList()) { + // QT6TODO: I'm not sure if this will work correctly + multiHash.insert(item.toList()[0].toString(), item.toList()[1]); + } + return multiHash; +} + +inline QVariant qMultiHashToQVariant(QMultiHash hash) { + QVariantList variant; + // QT6TODO: I'm not sure if this will work correctly + for(QString key : hash.keys()) { + QVariantList list; + list << key; + list << hash.values(key); + variant << QVariant(list); + } + return variant; +} + +inline QMultiHash qHashToQMultiHash(const QHash &hash) { + QMultiHash multiHash; + for(const QString &key : hash.keys()) { + multiHash.insert(key, hash.value(key)); + } + return multiHash; +} + #endif diff --git a/libraries/shared/src/shared/StringHelpers.cpp b/libraries/shared/src/shared/StringHelpers.cpp index 39ac23e510f..fbcded6937d 100644 --- a/libraries/shared/src/shared/StringHelpers.cpp +++ b/libraries/shared/src/shared/StringHelpers.cpp @@ -8,11 +8,12 @@ #include "StringHelpers.h" +#include #include /// Note: this will not preserve line breaks in the original input. QString simpleWordWrap(const QString& input, int maxCharactersPerLine) { - QStringList words = input.split(QRegExp("\\s+")); + QStringList words = input.split(QRegularExpression("\\s+")); QString output; QString currentLine; foreach(const auto& word, words) { diff --git a/libraries/task/src/task/Config.h b/libraries/task/src/task/Config.h index 785f94d4285..5313daf3705 100644 --- a/libraries/task/src/task/Config.h +++ b/libraries/task/src/task/Config.h @@ -50,9 +50,18 @@ template class PersistentConfig : public C { _default = toJsonValue(*this).toObject().toVariantMap(); - _presets.unite(list.toVariantMap()); + auto listMap = list.toVariantMap(); + // QT6TODO: I'm not sure about this. + //_presets.unite(listMap); + for (auto it = listMap.cbegin(); it != listMap.cend(); it++) { + if (_presets.contains(it.key())) { + _presets[it.key()] = it.value(); + } else { + _presets.insert(it.key(), it.value()); + } + } if (C::isEnabled()) { - _presets.insert(DEFAULT, _default); + _presets.insert(DEFAULT, toJsonValue(*this).toObject().toVariantMap()); } _presets.insert(NONE, QVariantMap{{ "enabled", false }}); @@ -69,8 +78,9 @@ template class PersistentConfig : public C { _preset.set(preset); if (_presets.contains(preset)) { // Always start back at default to remain deterministic + // QT6TODO: I'm not sure about maps and multimaps here. Shouldn't there be one entry per key? QVariantMap config = _default; - QVariantMap presetConfig = _presets[preset].toMap(); + QVariantMap presetConfig = _presets.value(preset).toMap(); for (auto it = presetConfig.cbegin(); it != presetConfig.cend(); it++) { config.insert(it.key(), it.value()); } @@ -79,8 +89,8 @@ template class PersistentConfig : public C { } protected: - QMultiMap _default; - QMultiMap _presets; + QVariantMap _default; + QVariantMap _presets; Setting::Handle _preset; }; diff --git a/libraries/ui-plugins/src/ui-plugins/PluginContainer.cpp b/libraries/ui-plugins/src/ui-plugins/PluginContainer.cpp index 0c71d4fa69a..f277c20eb49 100644 --- a/libraries/ui-plugins/src/ui-plugins/PluginContainer.cpp +++ b/libraries/ui-plugins/src/ui-plugins/PluginContainer.cpp @@ -7,6 +7,7 @@ // #include "PluginContainer.h" +#include #include #include #include diff --git a/libraries/ui/CMakeLists.txt b/libraries/ui/CMakeLists.txt index 4056d5026dc..a7e8eb1cf8e 100644 --- a/libraries/ui/CMakeLists.txt +++ b/libraries/ui/CMakeLists.txt @@ -3,7 +3,7 @@ # SPDX-License-Identifier: Apache-2.0 set(TARGET_NAME ui) -setup_hifi_library(OpenGL Multimedia Network Qml Quick WebChannel WebSockets XmlPatterns ${PLATFORM_QT_COMPONENTS}) +setup_hifi_library(OpenGL Multimedia Network Qml Quick WebChannel WebSockets Xml WebEngineQuick ${PLATFORM_QT_COMPONENTS}) link_hifi_libraries(shared networking qml gl audio audio-client plugins pointers script-engine) include_hifi_library_headers(controllers) diff --git a/libraries/ui/src/DesktopPreviewProvider.h b/libraries/ui/src/DesktopPreviewProvider.h index b0cc64245f4..c6c57949c5a 100644 --- a/libraries/ui/src/DesktopPreviewProvider.h +++ b/libraries/ui/src/DesktopPreviewProvider.h @@ -8,6 +8,7 @@ #include #include +#include #include class DesktopPreviewProvider : public QObject, public Dependency { diff --git a/libraries/ui/src/InfoView.cpp b/libraries/ui/src/InfoView.cpp index c14ff6bf64a..e9d73dcef26 100644 --- a/libraries/ui/src/InfoView.cpp +++ b/libraries/ui/src/InfoView.cpp @@ -13,7 +13,7 @@ #include #include -#include +//#include #include const QUrl InfoView::QML{ "InfoView.qml" }; const QString InfoView::NAME{ "InfoView" }; @@ -34,12 +34,14 @@ void InfoView::registerType() { } QString fetchVersion(const QUrl& url) { - QXmlQuery query; - query.bindVariable("file", QVariant(url)); - query.setQuery("string((doc($file)//input[@id='version'])[1]/@value)"); - QString r; - query.evaluateTo(&r); - return r.trimmed(); + // QT6TODO: QXmlQuery is not available in Qt6 + //QXmlQuery query; + //query.bindVariable("file", QVariant(url)); + //query.setQuery("string((doc($file)//input[@id='version'])[1]/@value)"); + //QString r; + //query.evaluateTo(&r); + //return r.trimmed(); + return {}; } void InfoView::show(const QString& path, bool firstOrChangedOnly, QString urlQuery) { diff --git a/libraries/ui/src/OffscreenUi.cpp b/libraries/ui/src/OffscreenUi.cpp index 72320252f59..d76a5d84722 100644 --- a/libraries/ui/src/OffscreenUi.cpp +++ b/libraries/ui/src/OffscreenUi.cpp @@ -119,14 +119,18 @@ bool OffscreenUi::shouldSwallowShortcut(QEvent* event) { return false; } -static QTouchDevice _touchDevice; +static std::shared_ptr _touchDevice; + OffscreenUi::OffscreenUi() { static std::once_flag once; std::call_once(once, [&] { - _touchDevice.setCapabilities(QTouchDevice::Position); - _touchDevice.setType(QTouchDevice::TouchScreen); - _touchDevice.setName("OffscreenUiTouchDevice"); - _touchDevice.setMaximumTouchPoints(4); + _touchDevice = std::make_shared("OffscreenUiTouchDevice", 0, + QInputDevice::DeviceType::TouchScreen, + QPointingDevice::PointerType::Pen, + QInputDevice::Capability::Position, + 4, //maxPoints + 2 // buttonCount + ); }); auto pointerManager = DependencyManager::get(); @@ -140,15 +144,15 @@ OffscreenUi::OffscreenUi() { } void OffscreenUi::hoverBeginEvent(const PointerEvent& event) { - OffscreenQmlSurface::hoverBeginEvent(event, _touchDevice); + OffscreenQmlSurface::hoverBeginEvent(event, *_touchDevice); } void OffscreenUi::hoverEndEvent(const PointerEvent& event) { - OffscreenQmlSurface::hoverEndEvent(event, _touchDevice); + OffscreenQmlSurface::hoverEndEvent(event, *_touchDevice); } void OffscreenUi::handlePointerEvent(const PointerEvent& event) { - OffscreenQmlSurface::handlePointerEvent(event, _touchDevice); + OffscreenQmlSurface::handlePointerEvent(event, *_touchDevice); } QObject* OffscreenUi::getFlags() { @@ -214,8 +218,8 @@ bool OffscreenUi::isPointOnDesktopWindow(QVariant point) { if (_desktop) { QVariant result; BLOCKING_INVOKE_METHOD(_desktop, "isPointOnWindow", - Q_RETURN_ARG(QVariant, result), - Q_ARG(QVariant, point)); + Q_GENERIC_RETURN_ARG(QVariant, result), + Q_GENERIC_ARG(QVariant, point)); return result.toBool(); } return false; @@ -319,12 +323,12 @@ QMessageBox::StandardButton OffscreenUi::messageBox(Icon icon, const QString& ti if (QThread::currentThread() != thread()) { QMessageBox::StandardButton result = QMessageBox::StandardButton::NoButton; BLOCKING_INVOKE_METHOD(this, "messageBox", - Q_RETURN_ARG(QMessageBox::StandardButton, result), - Q_ARG(Icon, icon), - Q_ARG(QString, title), - Q_ARG(QString, text), - Q_ARG(QMessageBox::StandardButtons, buttons), - Q_ARG(QMessageBox::StandardButton, defaultButton)); + Q_GENERIC_RETURN_ARG(QMessageBox::StandardButton, result), + Q_GENERIC_ARG(Icon, icon), + Q_GENERIC_ARG(QString, title), + Q_GENERIC_ARG(QString, text), + Q_GENERIC_ARG(QMessageBox::StandardButtons, buttons), + Q_GENERIC_ARG(QMessageBox::StandardButton, defaultButton)); return result; } @@ -335,12 +339,12 @@ ModalDialogListener* OffscreenUi::asyncMessageBox(Icon icon, const QString& titl if (QThread::currentThread() != thread()) { ModalDialogListener* ret; BLOCKING_INVOKE_METHOD(this, "asyncMessageBox", - Q_RETURN_ARG(ModalDialogListener*, ret), - Q_ARG(Icon, icon), - Q_ARG(QString, title), - Q_ARG(QString, text), - Q_ARG(QMessageBox::StandardButtons, buttons), - Q_ARG(QMessageBox::StandardButton, defaultButton)); + Q_GENERIC_RETURN_ARG(ModalDialogListener*, ret), + Q_GENERIC_ARG(Icon, icon), + Q_GENERIC_ARG(QString, title), + Q_GENERIC_ARG(QString, text), + Q_GENERIC_ARG(QMessageBox::StandardButtons, buttons), + Q_GENERIC_ARG(QMessageBox::StandardButton, defaultButton)); return ret; } @@ -473,11 +477,11 @@ QVariant OffscreenUi::inputDialog(const Icon icon, const QString& title, const Q if (QThread::currentThread() != thread()) { QVariant result; BLOCKING_INVOKE_METHOD(this, "inputDialog", - Q_RETURN_ARG(QVariant, result), - Q_ARG(Icon, icon), - Q_ARG(QString, title), - Q_ARG(QString, label), - Q_ARG(QVariant, current)); + Q_GENERIC_RETURN_ARG(QVariant, result), + Q_GENERIC_ARG(Icon, icon), + Q_GENERIC_ARG(QString, title), + Q_GENERIC_ARG(QString, label), + Q_GENERIC_ARG(QVariant, current)); return result; } @@ -488,11 +492,11 @@ ModalDialogListener* OffscreenUi::inputDialogAsync(const Icon icon, const QStrin if (QThread::currentThread() != thread()) { ModalDialogListener* ret; BLOCKING_INVOKE_METHOD(this, "inputDialogAsync", - Q_RETURN_ARG(ModalDialogListener*, ret), - Q_ARG(Icon, icon), - Q_ARG(QString, title), - Q_ARG(QString, label), - Q_ARG(QVariant, current)); + Q_GENERIC_RETURN_ARG(ModalDialogListener*, ret), + Q_GENERIC_ARG(Icon, icon), + Q_GENERIC_ARG(QString, title), + Q_GENERIC_ARG(QString, label), + Q_GENERIC_ARG(QVariant, current)); return ret; } @@ -506,10 +510,10 @@ QVariant OffscreenUi::customInputDialog(const Icon icon, const QString& title, c if (QThread::currentThread() != thread()) { QVariant result; BLOCKING_INVOKE_METHOD(this, "customInputDialog", - Q_RETURN_ARG(QVariant, result), - Q_ARG(Icon, icon), - Q_ARG(QString, title), - Q_ARG(QVariantMap, config)); + Q_GENERIC_RETURN_ARG(QVariant, result), + Q_GENERIC_ARG(Icon, icon), + Q_GENERIC_ARG(QString, title), + Q_GENERIC_ARG(QVariantMap, config)); return result; } @@ -526,10 +530,10 @@ ModalDialogListener* OffscreenUi::customInputDialogAsync(const Icon icon, const if (QThread::currentThread() != thread()) { ModalDialogListener* ret; BLOCKING_INVOKE_METHOD(this, "customInputDialogAsync", - Q_RETURN_ARG(ModalDialogListener*, ret), - Q_ARG(Icon, icon), - Q_ARG(QString, title), - Q_ARG(QVariantMap, config)); + Q_GENERIC_RETURN_ARG(ModalDialogListener*, ret), + Q_GENERIC_ARG(Icon, icon), + Q_GENERIC_ARG(QString, title), + Q_GENERIC_ARG(QVariantMap, config)); return ret; } @@ -547,14 +551,14 @@ void OffscreenUi::togglePinned() { } void OffscreenUi::setPinned(bool pinned) { - bool invokeResult = _desktop && QMetaObject::invokeMethod(_desktop, "setPinned", Q_ARG(QVariant, pinned)); + bool invokeResult = _desktop && QMetaObject::invokeMethod(_desktop, "setPinned", Q_GENERIC_ARG(QVariant, pinned)); if (!invokeResult) { qWarning() << "Failed to set window visibility"; } } void OffscreenUi::setConstrainToolbarToCenterX(bool constrained) { - bool invokeResult = _desktop && QMetaObject::invokeMethod(_desktop, "setConstrainToolbarToCenterX", Q_ARG(QVariant, constrained)); + bool invokeResult = _desktop && QMetaObject::invokeMethod(_desktop, "setConstrainToolbarToCenterX", Q_GENERIC_ARG(QVariant, constrained)); if (!invokeResult) { qWarning() << "Failed to set toolbar constraint"; } @@ -814,12 +818,12 @@ QString OffscreenUi::fileOpenDialog(const QString& caption, const QString& dir, if (QThread::currentThread() != thread()) { QString result; BLOCKING_INVOKE_METHOD(this, "fileOpenDialog", - Q_RETURN_ARG(QString, result), - Q_ARG(QString, caption), - Q_ARG(QString, dir), - Q_ARG(QString, filter), - Q_ARG(QString*, selectedFilter), - Q_ARG(QFileDialog::Options, options)); + Q_GENERIC_RETURN_ARG(QString, result), + Q_GENERIC_ARG(QString, caption), + Q_GENERIC_ARG(QString, dir), + Q_GENERIC_ARG(QString, filter), + Q_GENERIC_ARG(QString*, selectedFilter), + Q_GENERIC_ARG(QFileDialog::Options, options)); return result; } @@ -836,12 +840,12 @@ ModalDialogListener* OffscreenUi::fileOpenDialogAsync(const QString& caption, co if (QThread::currentThread() != thread()) { ModalDialogListener* ret; BLOCKING_INVOKE_METHOD(this, "fileOpenDialogAsync", - Q_RETURN_ARG(ModalDialogListener*, ret), - Q_ARG(QString, caption), - Q_ARG(QString, dir), - Q_ARG(QString, filter), - Q_ARG(QString*, selectedFilter), - Q_ARG(QFileDialog::Options, options)); + Q_GENERIC_RETURN_ARG(ModalDialogListener*, ret), + Q_GENERIC_ARG(QString, caption), + Q_GENERIC_ARG(QString, dir), + Q_GENERIC_ARG(QString, filter), + Q_GENERIC_ARG(QString*, selectedFilter), + Q_GENERIC_ARG(QFileDialog::Options, options)); return ret; } @@ -858,12 +862,12 @@ QString OffscreenUi::fileSaveDialog(const QString& caption, const QString& dir, if (QThread::currentThread() != thread()) { QString result; BLOCKING_INVOKE_METHOD(this, "fileSaveDialog", - Q_RETURN_ARG(QString, result), - Q_ARG(QString, caption), - Q_ARG(QString, dir), - Q_ARG(QString, filter), - Q_ARG(QString*, selectedFilter), - Q_ARG(QFileDialog::Options, options)); + Q_GENERIC_RETURN_ARG(QString, result), + Q_GENERIC_ARG(QString, caption), + Q_GENERIC_ARG(QString, dir), + Q_GENERIC_ARG(QString, filter), + Q_GENERIC_ARG(QString*, selectedFilter), + Q_GENERIC_ARG(QFileDialog::Options, options)); return result; } @@ -882,12 +886,12 @@ ModalDialogListener* OffscreenUi::fileSaveDialogAsync(const QString& caption, co if (QThread::currentThread() != thread()) { ModalDialogListener* ret; BLOCKING_INVOKE_METHOD(this, "fileSaveDialogAsync", - Q_RETURN_ARG(ModalDialogListener*, ret), - Q_ARG(QString, caption), - Q_ARG(QString, dir), - Q_ARG(QString, filter), - Q_ARG(QString*, selectedFilter), - Q_ARG(QFileDialog::Options, options)); + Q_GENERIC_RETURN_ARG(ModalDialogListener*, ret), + Q_GENERIC_ARG(QString, caption), + Q_GENERIC_ARG(QString, dir), + Q_GENERIC_ARG(QString, filter), + Q_GENERIC_ARG(QString*, selectedFilter), + Q_GENERIC_ARG(QFileDialog::Options, options)); return ret; } @@ -906,12 +910,12 @@ QString OffscreenUi::existingDirectoryDialog(const QString& caption, const QStri if (QThread::currentThread() != thread()) { QString result; BLOCKING_INVOKE_METHOD(this, "existingDirectoryDialog", - Q_RETURN_ARG(QString, result), - Q_ARG(QString, caption), - Q_ARG(QString, dir), - Q_ARG(QString, filter), - Q_ARG(QString*, selectedFilter), - Q_ARG(QFileDialog::Options, options)); + Q_GENERIC_RETURN_ARG(QString, result), + Q_GENERIC_ARG(QString, caption), + Q_GENERIC_ARG(QString, dir), + Q_GENERIC_ARG(QString, filter), + Q_GENERIC_ARG(QString*, selectedFilter), + Q_GENERIC_ARG(QFileDialog::Options, options)); return result; } @@ -928,12 +932,12 @@ ModalDialogListener* OffscreenUi::existingDirectoryDialogAsync(const QString& ca if (QThread::currentThread() != thread()) { ModalDialogListener* ret; BLOCKING_INVOKE_METHOD(this, "existingDirectoryDialogAsync", - Q_RETURN_ARG(ModalDialogListener*, ret), - Q_ARG(QString, caption), - Q_ARG(QString, dir), - Q_ARG(QString, filter), - Q_ARG(QString*, selectedFilter), - Q_ARG(QFileDialog::Options, options)); + Q_GENERIC_RETURN_ARG(ModalDialogListener*, ret), + Q_GENERIC_ARG(QString, caption), + Q_GENERIC_ARG(QString, dir), + Q_GENERIC_ARG(QString, filter), + Q_GENERIC_ARG(QString*, selectedFilter), + Q_GENERIC_ARG(QFileDialog::Options, options)); return ret; } @@ -1068,12 +1072,12 @@ QString OffscreenUi::assetOpenDialog(const QString& caption, const QString& dir, if (QThread::currentThread() != thread()) { QString result; BLOCKING_INVOKE_METHOD(this, "assetOpenDialog", - Q_RETURN_ARG(QString, result), - Q_ARG(QString, caption), - Q_ARG(QString, dir), - Q_ARG(QString, filter), - Q_ARG(QString*, selectedFilter), - Q_ARG(QFileDialog::Options, options)); + Q_GENERIC_RETURN_ARG(QString, result), + Q_GENERIC_ARG(QString, caption), + Q_GENERIC_ARG(QString, dir), + Q_GENERIC_ARG(QString, filter), + Q_GENERIC_ARG(QString*, selectedFilter), + Q_GENERIC_ARG(QFileDialog::Options, options)); return result; } @@ -1091,12 +1095,12 @@ ModalDialogListener* OffscreenUi::assetOpenDialogAsync(const QString& caption, c if (QThread::currentThread() != thread()) { ModalDialogListener* ret; BLOCKING_INVOKE_METHOD(this, "assetOpenDialogAsync", - Q_RETURN_ARG(ModalDialogListener*, ret), - Q_ARG(QString, caption), - Q_ARG(QString, dir), - Q_ARG(QString, filter), - Q_ARG(QString*, selectedFilter), - Q_ARG(QFileDialog::Options, options)); + Q_GENERIC_RETURN_ARG(ModalDialogListener*, ret), + Q_GENERIC_ARG(QString, caption), + Q_GENERIC_ARG(QString, dir), + Q_GENERIC_ARG(QString, filter), + Q_GENERIC_ARG(QString*, selectedFilter), + Q_GENERIC_ARG(QFileDialog::Options, options)); return ret; } diff --git a/libraries/ui/src/QmlFragmentClass.cpp b/libraries/ui/src/QmlFragmentClass.cpp index e3f31557023..3eaca595134 100644 --- a/libraries/ui/src/QmlFragmentClass.cpp +++ b/libraries/ui/src/QmlFragmentClass.cpp @@ -45,7 +45,7 @@ ScriptValue QmlFragmentClass::internal_constructor(ScriptContext* context, Scrip Q_ASSERT(retVal); if (QThread::currentThread() != qApp->thread()) { retVal->moveToThread(qApp->thread()); - BLOCKING_INVOKE_METHOD(retVal, "initQml", Q_ARG(QVariantMap, properties)); + BLOCKING_INVOKE_METHOD(retVal, "initQml", Q_GENERIC_ARG(QVariantMap, properties)); } else { retVal->initQml(properties); } diff --git a/libraries/ui/src/QmlWebWindowClass.cpp b/libraries/ui/src/QmlWebWindowClass.cpp index 6542bc750f0..1987da14a8a 100644 --- a/libraries/ui/src/QmlWebWindowClass.cpp +++ b/libraries/ui/src/QmlWebWindowClass.cpp @@ -40,7 +40,7 @@ ScriptValue QmlWebWindowClass::internal_constructor(ScriptContext* context, Scri QString QmlWebWindowClass::getURL() { if (QThread::currentThread() != thread()) { QString result; - BLOCKING_INVOKE_METHOD(this, "getURL", Q_RETURN_ARG(QString, result)); + BLOCKING_INVOKE_METHOD(this, "getURL", Q_GENERIC_RETURN_ARG(QString, result)); return result; } diff --git a/libraries/ui/src/QmlWindowClass.cpp b/libraries/ui/src/QmlWindowClass.cpp index 74734fdc43c..f36e64d487e 100644 --- a/libraries/ui/src/QmlWindowClass.cpp +++ b/libraries/ui/src/QmlWindowClass.cpp @@ -80,7 +80,7 @@ ScriptValue QmlWindowClass::internal_constructor(ScriptContext* context, ScriptE Q_ASSERT(retVal); if (QThread::currentThread() != qApp->thread()) { retVal->moveToThread(qApp->thread()); - BLOCKING_INVOKE_METHOD(retVal, "initQml", Q_ARG(QVariantMap, properties)); + BLOCKING_INVOKE_METHOD(retVal, "initQml", Q_GENERIC_ARG(QVariantMap, properties)); } else { retVal->initQml(properties); } @@ -265,7 +265,7 @@ void QmlWindowClass::setVisible(bool visible) { bool QmlWindowClass::isVisible() { if (QThread::currentThread() != thread()) { bool result = false; - BLOCKING_INVOKE_METHOD(this, "isVisible", Q_RETURN_ARG(bool, result)); + BLOCKING_INVOKE_METHOD(this, "isVisible", Q_GENERIC_RETURN_ARG(bool, result)); return result; } @@ -286,7 +286,7 @@ bool QmlWindowClass::isVisible() { glm::vec2 QmlWindowClass::getPosition() { if (QThread::currentThread() != thread()) { vec2 result; - BLOCKING_INVOKE_METHOD(this, "getPosition", Q_RETURN_ARG(glm::vec2, result)); + BLOCKING_INVOKE_METHOD(this, "getPosition", Q_GENERIC_RETURN_ARG(glm::vec2, result)); return result; } @@ -327,7 +327,7 @@ glm::vec2 toGlm(const QSizeF& size) { glm::vec2 QmlWindowClass::getSize() { if (QThread::currentThread() != thread()) { vec2 result; - BLOCKING_INVOKE_METHOD(this, "getSize", Q_RETURN_ARG(glm::vec2, result)); + BLOCKING_INVOKE_METHOD(this, "getSize", Q_GENERIC_RETURN_ARG(glm::vec2, result)); return result; } diff --git a/libraries/ui/src/QmlWindowClass.h b/libraries/ui/src/QmlWindowClass.h index 76a5be0c2d0..a37559612b9 100644 --- a/libraries/ui/src/QmlWindowClass.h +++ b/libraries/ui/src/QmlWindowClass.h @@ -200,7 +200,7 @@ public slots: * // QML file, "OverlayWindow.qml". * * import QtQuick 2.5 - * import QtQuick.Controls 1.4 + * import QtQuick.Controls 2.3 * * Rectangle { * width: parent.width diff --git a/libraries/ui/src/Tooltip.cpp b/libraries/ui/src/Tooltip.cpp index bd2c4e6d8f4..d0d87406b6d 100644 --- a/libraries/ui/src/Tooltip.cpp +++ b/libraries/ui/src/Tooltip.cpp @@ -12,6 +12,7 @@ #include #include +#include #include #include diff --git a/libraries/ui/src/ui/Menu.cpp b/libraries/ui/src/ui/Menu.cpp index 6e8557e1d24..1a2c9ef32aa 100644 --- a/libraries/ui/src/ui/Menu.cpp +++ b/libraries/ui/src/ui/Menu.cpp @@ -10,7 +10,7 @@ #include #include -#include +#include #include #include @@ -247,8 +247,8 @@ void Menu::removeAction(MenuWrapper* menu, const QString& actionName) { void Menu::setIsOptionChecked(const QString& menuOption, bool isChecked) { if (thread() != QThread::currentThread()) { BLOCKING_INVOKE_METHOD(this, "setIsOptionChecked", - Q_ARG(const QString&, menuOption), - Q_ARG(bool, isChecked)); + Q_GENERIC_ARG(const QString&, menuOption), + Q_GENERIC_ARG(bool, isChecked)); return; } QAction* menu = _actionHash.value(menuOption); diff --git a/libraries/ui/src/ui/OffscreenQmlSurface.cpp b/libraries/ui/src/ui/OffscreenQmlSurface.cpp index bac992492cf..ea25c92a152 100644 --- a/libraries/ui/src/ui/OffscreenQmlSurface.cpp +++ b/libraries/ui/src/ui/OffscreenQmlSurface.cpp @@ -14,7 +14,8 @@ #include #include -#include +// QT6TODO +//#include #include #include #include @@ -27,8 +28,10 @@ #include #include #include -#include -#include +// QT6TODO +//#include +// QT6TODO +//#include #include #include #include @@ -45,6 +48,7 @@ #include #include +#include #include #include @@ -112,6 +116,7 @@ class AudioHandler : public QObject, QRunnable { private: QString _newTargetDevice; + std::shared_ptr _audioOutput; QSharedPointer _surface; std::vector _players; }; @@ -194,34 +199,39 @@ AudioHandler::AudioHandler(OffscreenQmlSurface* surface, const QString& deviceNa } void AudioHandler::run() { + for (auto player : _players) { - auto mediaState = player->state(); - QMediaService* svc = player->service(); + auto mediaState = player->playbackState(); + /*QMediaService* svc = player->service(); if (nullptr == svc) { continue; - } - QAudioOutputSelectorControl* out = + }*/ + /*QAudioOutputSelectorControl* out = qobject_cast(svc->requestControl(QAudioOutputSelectorControl_iid)); if (nullptr == out) { continue; - } - QString deviceOuput; - auto outputs = out->availableOutputs(); - for (int i = 0; i < outputs.size(); i++) { - QString output = outputs[i]; - QString description = out->outputDescription(output); - if (description == _newTargetDevice) { - deviceOuput = output; + }*/ + //auto audioClient = DependencyManager::get(); + //auto outputs = audioClient->getAudioDevices(QAudioDevice::Output); + auto outputs = QMediaDevices::audioOutputs(); + QAudioDevice selectedDevice; + for (const auto &output: outputs) { + if (output.description() == _newTargetDevice) { + selectedDevice = output; break; } } - out->setActiveOutput(deviceOuput); - svc->releaseControl(out); + auto oldAudioOutput = _audioOutput; + _audioOutput = std::make_shared(); + _audioOutput->setDevice(selectedDevice); + // Q6TODO: we could set volume here in the future. + player->setAudioOutput(_audioOutput.get()); + //svc->releaseControl(out); // if multimedia was paused, it will start playing automatically after changing audio device // this will reset it back to a paused state - if (mediaState == QMediaPlayer::State::PausedState) { + if (mediaState == QMediaPlayer::PlaybackState::PausedState) { player->pause(); - } else if (mediaState == QMediaPlayer::State::StoppedState) { + } else if (mediaState == QMediaPlayer::PlaybackState::StoppedState) { player->stop(); } } @@ -355,8 +365,8 @@ void OffscreenQmlSurface::onRootCreated() { getSurfaceContext()->setContextProperty("offscreenWindow", QVariant::fromValue(getWindow())); // Connect with the audio client and listen for audio device changes - connect(DependencyManager::get().data(), &AudioClient::deviceChanged, this, [this](QAudio::Mode mode, const HifiAudioDeviceInfo& device) { - if (mode == QAudio::Mode::AudioOutput) { + connect(DependencyManager::get().data(), &AudioClient::deviceChanged, this, [this](QAudioDevice::Mode mode, const HifiAudioDeviceInfo& device) { + if (mode == QAudioDevice::Mode::Output) { QMetaObject::invokeMethod(this, "changeAudioOutputDevice", Qt::QueuedConnection, Q_ARG(QString, device.deviceName())); } }); @@ -449,7 +459,7 @@ PointerEvent::EventType OffscreenQmlSurface::choosePointerEventType(QEvent::Type } } -void OffscreenQmlSurface::hoverBeginEvent(const PointerEvent& event, class QTouchDevice& device) { +void OffscreenQmlSurface::hoverBeginEvent(const PointerEvent& event, class QPointingDevice& device) { #if defined(DISABLE_QML) return; #endif @@ -457,7 +467,7 @@ void OffscreenQmlSurface::hoverBeginEvent(const PointerEvent& event, class QTouc _activeTouchPoints[event.getID()].hovering = true; } -void OffscreenQmlSurface::hoverEndEvent(const PointerEvent& event, class QTouchDevice& device) { +void OffscreenQmlSurface::hoverEndEvent(const PointerEvent& event, class QPointingDevice& device) { #if defined(DISABLE_QML) return; #endif @@ -473,7 +483,7 @@ void OffscreenQmlSurface::hoverEndEvent(const PointerEvent& event, class QTouchD } } -bool OffscreenQmlSurface::handlePointerEvent(const PointerEvent& event, class QTouchDevice& device, bool release) { +bool OffscreenQmlSurface::handlePointerEvent(const PointerEvent& event, class QPointingDevice& device, bool release) { #if defined(DISABLE_QML) return false; #endif @@ -485,13 +495,15 @@ bool OffscreenQmlSurface::handlePointerEvent(const PointerEvent& event, class QT QPointF windowPoint(event.getPos2D().x, event.getPos2D().y); - Qt::TouchPointState state = Qt::TouchPointStationary; + //Qt::TouchPointState state = Qt::TouchPointStationary; + QEventPoint::State state = QEventPoint::State::Stationary; if (event.getType() == PointerEvent::Press && event.getButton() == PointerEvent::PrimaryButton) { - state = Qt::TouchPointPressed; + state = QEventPoint::State::Pressed; } else if (event.getType() == PointerEvent::Release && event.getButton() == PointerEvent::PrimaryButton) { - state = Qt::TouchPointReleased; + state = QEventPoint::State::Released; } else if (_activeTouchPoints.count(event.getID()) && windowPoint != _activeTouchPoints[event.getID()].touchPoint.pos()) { - state = Qt::TouchPointMoved; + state = QEventPoint::State::Updated; + // QT6TODO: is Updated equivalent to TouchPointMoved?; } // Remove the touch point if: @@ -510,11 +522,12 @@ bool OffscreenQmlSurface::handlePointerEvent(const PointerEvent& event, class QT } { - QTouchEvent::TouchPoint point; - point.setId(event.getID()); - point.setState(state); - point.setPos(windowPoint); - point.setScreenPos(windowPoint); + // QT6TODO: I'm not sure about positions + QTouchEvent::TouchPoint point(event.getID(), state, windowPoint, windowPoint); + //point.setId(event.getID()); + //point.setState(state); + //point.setPos(windowPoint); + //point.setScreenPos(windowPoint); _activeTouchPoints[event.getID()].touchPoint = point; if (state == Qt::TouchPointPressed) { _activeTouchPoints[event.getID()].pressed = true; @@ -523,23 +536,20 @@ bool OffscreenQmlSurface::handlePointerEvent(const PointerEvent& event, class QT } } - QTouchEvent touchEvent(touchType, &device, event.getKeyboardModifiers()); - { QList touchPoints; - Qt::TouchPointStates touchPointStates; + //Qt::TouchPointStates touchPointStates; for (const auto& entry : _activeTouchPoints) { - touchPointStates |= entry.second.touchPoint.state(); + //touchPointStates |= entry.second.touchPoint.state(); touchPoints.push_back(entry.second.touchPoint); } - touchEvent.setDevice(&device); - touchEvent.setWindow(getWindow()); - touchEvent.setTarget(getRootItem()); - touchEvent.setTouchPoints(touchPoints); - touchEvent.setTouchPointStates(touchPointStates); + QTouchEvent touchEvent(touchType, &device, event.getKeyboardModifiers(), touchPoints); + + // QT6TODO: I'm not sure about what to do about this, there doesn't seem to be a way to set window and target + //touchEvent.setWindow(getWindow()); + //ouchEvent.setTarget(getRootItem()); touchEvent.setTimestamp((ulong)QDateTime::currentMSecsSinceEpoch()); touchEvent.ignore(); - } // Send mouse events to the surface so that HTML dialog elements work with mouse press and hover. // diff --git a/libraries/ui/src/ui/OffscreenQmlSurface.h b/libraries/ui/src/ui/OffscreenQmlSurface.h index 07358dcf386..9914b057e14 100644 --- a/libraries/ui/src/ui/OffscreenQmlSurface.h +++ b/libraries/ui/src/ui/OffscreenQmlSurface.h @@ -81,9 +81,9 @@ private slots: void onFocusObjectChanged(QObject* newFocus) override; public slots: - void hoverBeginEvent(const PointerEvent& event, class QTouchDevice& device); - void hoverEndEvent(const PointerEvent& event, class QTouchDevice& device); - bool handlePointerEvent(const PointerEvent& event, class QTouchDevice& device, bool release = false); + void hoverBeginEvent(const PointerEvent& event, QPointingDevice& device); + void hoverEndEvent(const PointerEvent& event, QPointingDevice& device); + bool handlePointerEvent(const PointerEvent& event, QPointingDevice& device, bool release = false); void changeAudioOutputDevice(const QString& deviceName, bool isHtmlUpdate = false); void forceHtmlAudioOutputDeviceUpdate(); void forceQmlAudioOutputDeviceUpdate(); diff --git a/libraries/ui/src/ui/QmlWrapper.cpp b/libraries/ui/src/ui/QmlWrapper.cpp index 3879e1bc8f4..2cb65bf2f08 100644 --- a/libraries/ui/src/ui/QmlWrapper.cpp +++ b/libraries/ui/src/ui/QmlWrapper.cpp @@ -38,7 +38,7 @@ void QmlWrapper::writeProperties(QVariant propertyMap) { QVariant QmlWrapper::readProperty(const QString& propertyName) { if (QThread::currentThread() != thread()) { QVariant result; - BLOCKING_INVOKE_METHOD(this, "readProperty", Q_RETURN_ARG(QVariant, result), Q_ARG(QString, propertyName)); + BLOCKING_INVOKE_METHOD(this, "readProperty", Q_GENERIC_RETURN_ARG(QVariant, result), Q_GENERIC_ARG(QString, propertyName)); return result; } @@ -48,7 +48,7 @@ QVariant QmlWrapper::readProperty(const QString& propertyName) { QVariant QmlWrapper::readProperties(const QVariant& propertyList) { if (QThread::currentThread() != thread()) { QVariant result; - BLOCKING_INVOKE_METHOD(this, "readProperties", Q_RETURN_ARG(QVariant, result), Q_ARG(QVariant, propertyList)); + BLOCKING_INVOKE_METHOD(this, "readProperties", Q_GENERIC_RETURN_ARG(QVariant, result), Q_GENERIC_ARG(QVariant, propertyList)); return result; } diff --git a/libraries/ui/src/ui/TabletScriptingInterface.cpp b/libraries/ui/src/ui/TabletScriptingInterface.cpp index 42af68f820f..5cbfa0c8576 100644 --- a/libraries/ui/src/ui/TabletScriptingInterface.cpp +++ b/libraries/ui/src/ui/TabletScriptingInterface.cpp @@ -188,7 +188,7 @@ ToolbarProxy* TabletScriptingInterface::getSystemToolbarProxy() { TabletProxy* TabletScriptingInterface::getTablet(const QString& tabletId) { TabletProxy* tabletProxy = nullptr; if (QThread::currentThread() != thread()) { - BLOCKING_INVOKE_METHOD(this, "getTablet", Q_RETURN_ARG(TabletProxy*, tabletProxy), Q_ARG(QString, tabletId)); + BLOCKING_INVOKE_METHOD(this, "getTablet", Q_GENERIC_RETURN_ARG(TabletProxy*, tabletProxy), Q_GENERIC_ARG(QString, tabletId)); return tabletProxy; } @@ -441,7 +441,7 @@ void TabletProxy::initialScreen(const QVariant& url) { bool TabletProxy::isMessageDialogOpen() { if (QThread::currentThread() != thread()) { bool result = false; - BLOCKING_INVOKE_METHOD(this, "isMessageDialogOpen", Q_RETURN_ARG(bool, result)); + BLOCKING_INVOKE_METHOD(this, "isMessageDialogOpen", Q_GENERIC_RETURN_ARG(bool, result)); return result; } @@ -501,7 +501,7 @@ void TabletProxy::onTabletShown() { bool TabletProxy::isPathLoaded(const QVariant& path) { if (QThread::currentThread() != thread()) { bool result = false; - BLOCKING_INVOKE_METHOD(this, "isPathLoaded", Q_RETURN_ARG(bool, result), Q_ARG(QVariant, path)); + BLOCKING_INVOKE_METHOD(this, "isPathLoaded", Q_GENERIC_RETURN_ARG(bool, result), Q_GENERIC_ARG(QVariant, path)); return result; } @@ -757,7 +757,7 @@ void TabletProxy::stopQMLSource() { bool TabletProxy::pushOntoStack(const QVariant& path) { if (QThread::currentThread() != thread()) { bool result = false; - BLOCKING_INVOKE_METHOD(this, "pushOntoStack", Q_RETURN_ARG(bool, result), Q_ARG(QVariant, path)); + BLOCKING_INVOKE_METHOD(this, "pushOntoStack", Q_GENERIC_RETURN_ARG(bool, result), Q_GENERIC_ARG(QVariant, path)); return result; } @@ -900,7 +900,7 @@ void TabletProxy::loadHTMLSourceOnTopImpl(const QString& url, const QString& inj TabletButtonProxy* TabletProxy::addButton(const QVariant& properties) { if (QThread::currentThread() != thread()) { TabletButtonProxy* result = nullptr; - BLOCKING_INVOKE_METHOD(this, "addButton", Q_RETURN_ARG(TabletButtonProxy*, result), Q_ARG(QVariant, properties)); + BLOCKING_INVOKE_METHOD(this, "addButton", Q_GENERIC_RETURN_ARG(TabletButtonProxy*, result), Q_GENERIC_ARG(QVariant, properties)); return result; } @@ -910,7 +910,7 @@ TabletButtonProxy* TabletProxy::addButton(const QVariant& properties) { bool TabletProxy::onHomeScreen() { if (QThread::currentThread() != thread()) { bool result = false; - BLOCKING_INVOKE_METHOD(this, "onHomeScreen", Q_RETURN_ARG(bool, result)); + BLOCKING_INVOKE_METHOD(this, "onHomeScreen", Q_GENERIC_RETURN_ARG(bool, result)); return result; } @@ -919,7 +919,7 @@ bool TabletProxy::onHomeScreen() { void TabletProxy::removeButton(TabletButtonProxy* tabletButtonProxy) { if (QThread::currentThread() != thread()) { - QMetaObject::invokeMethod(this, "removeButton", Q_ARG(TabletButtonProxy*, tabletButtonProxy)); + QMetaObject::invokeMethod(this, "removeButton", Q_GENERIC_ARG(TabletButtonProxy*, tabletButtonProxy)); return; } @@ -928,34 +928,34 @@ void TabletProxy::removeButton(TabletButtonProxy* tabletButtonProxy) { void TabletProxy::emitScriptEvent(const QVariant& msg) { if (QThread::currentThread() != thread()) { - QMetaObject::invokeMethod(this, "emitScriptEvent", Q_ARG(QVariant, msg)); + QMetaObject::invokeMethod(this, "emitScriptEvent", Q_GENERIC_ARG(QVariant, msg)); return; } if (!_toolbarMode && _qmlOffscreenSurface) { - QMetaObject::invokeMethod(_qmlOffscreenSurface, "emitScriptEvent", Qt::AutoConnection, Q_ARG(QVariant, msg)); + QMetaObject::invokeMethod(_qmlOffscreenSurface, "emitScriptEvent", Qt::AutoConnection, Q_GENERIC_ARG(QVariant, msg)); } else if (_toolbarMode && _desktopWindow) { - QMetaObject::invokeMethod(_desktopWindow, "emitScriptEvent", Qt::AutoConnection, Q_ARG(QVariant, msg)); + QMetaObject::invokeMethod(_desktopWindow, "emitScriptEvent", Qt::AutoConnection, Q_GENERIC_ARG(QVariant, msg)); } } void TabletProxy::sendToQml(const QVariant& msg) { if (QThread::currentThread() != thread()) { - QMetaObject::invokeMethod(this, "sendToQml", Q_ARG(QVariant, msg)); + QMetaObject::invokeMethod(this, "sendToQml", Q_GENERIC_ARG(QVariant, msg)); return; } if (!_toolbarMode && _qmlOffscreenSurface) { - QMetaObject::invokeMethod(_qmlOffscreenSurface, "sendToQml", Qt::AutoConnection, Q_ARG(QVariant, msg)); + QMetaObject::invokeMethod(_qmlOffscreenSurface, "sendToQml", Qt::AutoConnection, Q_GENERIC_ARG(QVariant, msg)); } else if (_toolbarMode && _desktopWindow) { - QMetaObject::invokeMethod(_desktopWindow, "sendToQml", Qt::AutoConnection, Q_ARG(QVariant, msg)); + QMetaObject::invokeMethod(_desktopWindow, "sendToQml", Qt::AutoConnection, Q_GENERIC_ARG(QVariant, msg)); } } OffscreenQmlSurface* TabletProxy::getTabletSurface() { if (QThread::currentThread() != thread()) { OffscreenQmlSurface* result = nullptr; - BLOCKING_INVOKE_METHOD(this, "getTabletSurface", Q_RETURN_ARG(OffscreenQmlSurface*, result)); + BLOCKING_INVOKE_METHOD(this, "getTabletSurface", Q_GENERIC_RETURN_ARG(OffscreenQmlSurface*, result)); return result; } @@ -1091,7 +1091,7 @@ TabletButtonProxy::~TabletButtonProxy() { QVariantMap TabletButtonProxy::getProperties() { if (QThread::currentThread() != thread()) { QVariantMap result; - BLOCKING_INVOKE_METHOD(this, "getProperties", Q_RETURN_ARG(QVariantMap, result)); + BLOCKING_INVOKE_METHOD(this, "getProperties", Q_GENERIC_RETURN_ARG(QVariantMap, result)); return result; } @@ -1100,7 +1100,7 @@ QVariantMap TabletButtonProxy::getProperties() { void TabletButtonProxy::editProperties(const QVariantMap& properties) { if (QThread::currentThread() != thread()) { - QMetaObject::invokeMethod(this, "editProperties", Q_ARG(QVariantMap, properties)); + QMetaObject::invokeMethod(this, "editProperties", Q_GENERIC_ARG(QVariantMap, properties)); return; } diff --git a/libraries/ui/src/ui/ToolbarScriptingInterface.cpp b/libraries/ui/src/ui/ToolbarScriptingInterface.cpp index 4934ba4e351..a978bd23c3a 100644 --- a/libraries/ui/src/ui/ToolbarScriptingInterface.cpp +++ b/libraries/ui/src/ui/ToolbarScriptingInterface.cpp @@ -83,7 +83,7 @@ ToolbarProxy::ToolbarProxy(QObject* qmlObject, QObject* parent) : QmlWrapper(qml ToolbarButtonProxy* ToolbarProxy::addButton(const QVariant& properties) { if (QThread::currentThread() != thread()) { ToolbarButtonProxy* result = nullptr; - BLOCKING_INVOKE_METHOD(this, "addButton", Q_RETURN_ARG(ToolbarButtonProxy*, result), Q_ARG(QVariant, properties)); + BLOCKING_INVOKE_METHOD(this, "addButton", Q_GENERIC_RETURN_ARG(ToolbarButtonProxy*, result), Q_GENERIC_ARG(QVariant, properties)); return result; } @@ -114,7 +114,7 @@ void ToolbarProxy::removeButton(const QVariant& name) { ToolbarProxy* ToolbarScriptingInterface::getToolbar(const QString& toolbarId) { if (QThread::currentThread() != thread()) { ToolbarProxy* result = nullptr; - BLOCKING_INVOKE_METHOD(this, "getToolbar", Q_RETURN_ARG(ToolbarProxy*, result), Q_ARG(QString, toolbarId)); + BLOCKING_INVOKE_METHOD(this, "getToolbar", Q_GENERIC_RETURN_ARG(ToolbarProxy*, result), Q_GENERIC_ARG(QString, toolbarId)); return result; } diff --git a/libraries/ui/src/ui/types/ContextAwareProfile.cpp b/libraries/ui/src/ui/types/ContextAwareProfile.cpp index 210e1f36b1c..c00f1d2148e 100644 --- a/libraries/ui/src/ui/types/ContextAwareProfile.cpp +++ b/libraries/ui/src/ui/types/ContextAwareProfile.cpp @@ -78,7 +78,7 @@ void ContextAwareProfile::restrictContext(QQmlContext* context, bool restrict) { bool ContextAwareProfile::isRestrictedGetProperty() { if (QThread::currentThread() != thread()) { bool restrictedResult = false; - BLOCKING_INVOKE_METHOD(this, "isRestrictedGetProperty", Q_RETURN_ARG(bool, restrictedResult)); + BLOCKING_INVOKE_METHOD(this, "isRestrictedGetProperty", Q_GENERIC_RETURN_ARG(bool, restrictedResult)); return restrictedResult; } diff --git a/libraries/ui/src/ui/types/ContextAwareProfile.h b/libraries/ui/src/ui/types/ContextAwareProfile.h index 7ea56846992..584b54946e9 100644 --- a/libraries/ui/src/ui/types/ContextAwareProfile.h +++ b/libraries/ui/src/ui/types/ContextAwareProfile.h @@ -17,7 +17,7 @@ #include #if !defined(Q_OS_ANDROID) -#include +#include #include using ContextAwareProfileParent = QQuickWebEngineProfile; diff --git a/libraries/ui/src/ui/types/FileTypeProfile.cpp b/libraries/ui/src/ui/types/FileTypeProfile.cpp index 9fca1be4361..f2ac9aa4f50 100644 --- a/libraries/ui/src/ui/types/FileTypeProfile.cpp +++ b/libraries/ui/src/ui/types/FileTypeProfile.cpp @@ -33,7 +33,7 @@ FileTypeProfile::FileTypeProfile(QQmlContext* parent) : setOffTheRecord(false); auto requestInterceptor = new RequestInterceptor(this); - setRequestInterceptor(requestInterceptor); + setUrlRequestInterceptor(requestInterceptor); std::lock_guard lock(FileTypeProfile_mutex); FileTypeProfile_instances.insert(this); diff --git a/libraries/ui/src/ui/types/HFWebEngineProfile.cpp b/libraries/ui/src/ui/types/HFWebEngineProfile.cpp index 3f2d50e8fa8..7a5c668e59f 100644 --- a/libraries/ui/src/ui/types/HFWebEngineProfile.cpp +++ b/libraries/ui/src/ui/types/HFWebEngineProfile.cpp @@ -30,7 +30,7 @@ HFWebEngineProfile::HFWebEngineProfile(QQmlContext* parent) : Parent(parent) setOffTheRecord(false); // we use the HFWebEngineRequestInterceptor to make sure that web requests are authenticated for the interface user - setRequestInterceptor(new RequestInterceptor(this)); + setUrlRequestInterceptor(new RequestInterceptor(this)); std::lock_guard lock(HFWebEngineProfile_mutex); HFWebEngineProfile_instances.insert(this); diff --git a/plugins/JSAPIExample/src/JSAPIExample.cpp b/plugins/JSAPIExample/src/JSAPIExample.cpp index 02b59ba412f..49ef023131c 100644 --- a/plugins/JSAPIExample/src/JSAPIExample.cpp +++ b/plugins/JSAPIExample/src/JSAPIExample.cpp @@ -23,6 +23,7 @@ #include #include #include +#include #include // for ::settingsFilename() #include // for ::usecTimestampNow() diff --git a/plugins/steamClient/src/SteamAPIPlugin.cpp b/plugins/steamClient/src/SteamAPIPlugin.cpp index b6e5b2ea770..0c2ee74395a 100644 --- a/plugins/steamClient/src/SteamAPIPlugin.cpp +++ b/plugins/steamClient/src/SteamAPIPlugin.cpp @@ -15,6 +15,7 @@ #include #include +#include #include #include #include diff --git a/script-archive/floofChat/FloofChat.qml b/script-archive/floofChat/FloofChat.qml index 8d111222927..a96383880b5 100644 --- a/script-archive/floofChat/FloofChat.qml +++ b/script-archive/floofChat/FloofChat.qml @@ -1,5 +1,5 @@ import QtQuick 2.5 -import QtQuick.Controls 1.4 +import QtQuick.Controls 2.3 //import Hifi 1.0 as Hifi Rectangle { diff --git a/scripts/communityScripts/armored-chat/armored_chat_quick_message.qml b/scripts/communityScripts/armored-chat/armored_chat_quick_message.qml index 7470af55dce..f85b380020f 100644 --- a/scripts/communityScripts/armored-chat/armored_chat_quick_message.qml +++ b/scripts/communityScripts/armored-chat/armored_chat_quick_message.qml @@ -1,5 +1,5 @@ import QtQuick 2.5 -import QtQuick.Controls 1.4 +import QtQuick.Controls 2.3 Item { id: root diff --git a/scripts/developer/debugging/debugHaze.qml b/scripts/developer/debugging/debugHaze.qml index 5a18f88c79a..039dc23023f 100644 --- a/scripts/developer/debugging/debugHaze.qml +++ b/scripts/developer/debugging/debugHaze.qml @@ -9,7 +9,7 @@ // See the accompanying file LICENSE or https://www.apache.org/licenses/LICENSE-2.0.html // import QtQuick 2.5 -import QtQuick.Controls 1.4 +import QtQuick.Controls 2.3 import "../utilities/render/configSlider" Item { diff --git a/scripts/developer/debugging/debugWindow.qml b/scripts/developer/debugging/debugWindow.qml index 23708033351..eca330cb99e 100644 --- a/scripts/developer/debugging/debugWindow.qml +++ b/scripts/developer/debugging/debugWindow.qml @@ -9,7 +9,7 @@ // import QtQuick 2.5 -import QtQuick.Controls 1.4 +import QtQuick.Controls 2.3 import Hifi 1.0 as Hifi diff --git a/scripts/developer/debugging/surfaceGeometryPass.qml b/scripts/developer/debugging/surfaceGeometryPass.qml index ba1db66d16e..0c17789794b 100644 --- a/scripts/developer/debugging/surfaceGeometryPass.qml +++ b/scripts/developer/debugging/surfaceGeometryPass.qml @@ -8,7 +8,7 @@ // See the accompanying file LICENSE or https://www.apache.org/licenses/LICENSE-2.0.html // import QtQuick 2.5 -import QtQuick.Controls 1.4 +import QtQuick.Controls 2.3 import "configSlider" Column { diff --git a/scripts/developer/tests/playaPerformanceTest.qml b/scripts/developer/tests/playaPerformanceTest.qml index 028cfc9e1ab..fd6fec15734 100644 --- a/scripts/developer/tests/playaPerformanceTest.qml +++ b/scripts/developer/tests/playaPerformanceTest.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 Rectangle { diff --git a/scripts/developer/tests/textureStress.qml b/scripts/developer/tests/textureStress.qml index 1a8b9944757..eca135a1c46 100644 --- a/scripts/developer/tests/textureStress.qml +++ b/scripts/developer/tests/textureStress.qml @@ -1,5 +1,5 @@ import QtQuick 2.5 -import QtQuick.Controls 1.4 +import QtQuick.Controls 2.3 Rectangle { id: root diff --git a/scripts/developer/utilities/audio/Jitter.qml b/scripts/developer/utilities/audio/Jitter.qml index 91f197a9191..524b17ba82f 100644 --- a/scripts/developer/utilities/audio/Jitter.qml +++ b/scripts/developer/utilities/audio/Jitter.qml @@ -9,7 +9,7 @@ // See the accompanying file LICENSE or https://www.apache.org/licenses/LICENSE-2.0.html // import QtQuick 2.5 -import QtQuick.Controls 1.4 +import QtQuick.Controls 2.3 import QtQuick.Layouts 1.3 ColumnLayout { diff --git a/scripts/developer/utilities/audio/MovingValue.qml b/scripts/developer/utilities/audio/MovingValue.qml index bbd9c31d6be..b10691867d6 100644 --- a/scripts/developer/utilities/audio/MovingValue.qml +++ b/scripts/developer/utilities/audio/MovingValue.qml @@ -9,7 +9,7 @@ // See the accompanying file LICENSE or https://www.apache.org/licenses/LICENSE-2.0.html // import QtQuick 2.5 -import QtQuick.Controls 1.4 +import QtQuick.Controls 2.3 import QtQuick.Layouts 1.3 import "../lib/plotperf" diff --git a/scripts/developer/utilities/audio/Section.qml b/scripts/developer/utilities/audio/Section.qml index 100d05f6b2f..f6bd324646d 100644 --- a/scripts/developer/utilities/audio/Section.qml +++ b/scripts/developer/utilities/audio/Section.qml @@ -9,7 +9,7 @@ // See the accompanying file LICENSE or https://www.apache.org/licenses/LICENSE-2.0.html // import QtQuick 2.5 -import QtQuick.Controls 1.4 +import QtQuick.Controls 2.3 import QtQuick.Layouts 1.3 Rectangle { diff --git a/scripts/developer/utilities/audio/Stats.qml b/scripts/developer/utilities/audio/Stats.qml index e2291e485d6..91a3fc63ae3 100644 --- a/scripts/developer/utilities/audio/Stats.qml +++ b/scripts/developer/utilities/audio/Stats.qml @@ -9,7 +9,7 @@ // See the accompanying file LICENSE or https://www.apache.org/licenses/LICENSE-2.0.html // import QtQuick 2.5 -import QtQuick.Controls 1.4 +import QtQuick.Controls 2.3 import QtQuick.Layouts 1.3 import controlsUit 1.0 as HifiControls diff --git a/scripts/developer/utilities/audio/Stream.qml b/scripts/developer/utilities/audio/Stream.qml index e9383b627aa..8cd944d33d6 100644 --- a/scripts/developer/utilities/audio/Stream.qml +++ b/scripts/developer/utilities/audio/Stream.qml @@ -9,7 +9,7 @@ // See the accompanying file LICENSE or https://www.apache.org/licenses/LICENSE-2.0.html // import QtQuick 2.5 -import QtQuick.Controls 1.4 +import QtQuick.Controls 2.3 import QtQuick.Layouts 1.3 ColumnLayout { diff --git a/scripts/developer/utilities/audio/TabletStats.qml b/scripts/developer/utilities/audio/TabletStats.qml index b50acabec48..00213b4666f 100644 --- a/scripts/developer/utilities/audio/TabletStats.qml +++ b/scripts/developer/utilities/audio/TabletStats.qml @@ -9,7 +9,7 @@ // See the accompanying file LICENSE or https://www.apache.org/licenses/LICENSE-2.0.html // import QtQuick 2.5 -import QtQuick.Controls 1.4 +import QtQuick.Controls 2.3 import QtQuick.Layouts 1.3 import stylesUit 1.0 diff --git a/scripts/developer/utilities/audio/Value.qml b/scripts/developer/utilities/audio/Value.qml index 70df7695bc4..9ed90b5b331 100644 --- a/scripts/developer/utilities/audio/Value.qml +++ b/scripts/developer/utilities/audio/Value.qml @@ -9,7 +9,7 @@ // See the accompanying file LICENSE or https://www.apache.org/licenses/LICENSE-2.0.html // import QtQuick 2.5 -import QtQuick.Controls 1.4 +import QtQuick.Controls 2.3 import QtQuick.Layouts 1.3 RowLayout { diff --git a/scripts/developer/utilities/cache/stats.qml b/scripts/developer/utilities/cache/stats.qml index d7888eb1aaf..c454ea21ff9 100644 --- a/scripts/developer/utilities/cache/stats.qml +++ b/scripts/developer/utilities/cache/stats.qml @@ -9,7 +9,7 @@ // See the accompanying file LICENSE or https://www.apache.org/licenses/LICENSE-2.0.html // import QtQuick 2.5 -import QtQuick.Controls 1.4 +import QtQuick.Controls 2.3 import "../lib/plotperf" Item { diff --git a/scripts/developer/utilities/lib/jet/qml/TaskList.qml b/scripts/developer/utilities/lib/jet/qml/TaskList.qml index e4b0267d3f1..5bde2a8743f 100644 --- a/scripts/developer/utilities/lib/jet/qml/TaskList.qml +++ b/scripts/developer/utilities/lib/jet/qml/TaskList.qml @@ -9,7 +9,7 @@ // import QtQuick 2.7 -import QtQuick.Controls 1.4 as Original +import QtQuick.Controls 2.3 as Original import QtQuick.Controls.Styles 1.4 import stylesUit 1.0 diff --git a/scripts/developer/utilities/lib/jet/qml/TaskListView.qml b/scripts/developer/utilities/lib/jet/qml/TaskListView.qml index a935163bd9a..4cdee16ffe1 100644 --- a/scripts/developer/utilities/lib/jet/qml/TaskListView.qml +++ b/scripts/developer/utilities/lib/jet/qml/TaskListView.qml @@ -9,7 +9,7 @@ // import QtQuick 2.7 -import QtQuick.Controls 1.4 as Original +import QtQuick.Controls 2.3 as Original import QtQuick.Controls.Styles 1.4 import stylesUit 1.0 diff --git a/scripts/developer/utilities/lib/jet/qml/TaskPropView.qml b/scripts/developer/utilities/lib/jet/qml/TaskPropView.qml index 6a658a33e34..0a5b529580b 100644 --- a/scripts/developer/utilities/lib/jet/qml/TaskPropView.qml +++ b/scripts/developer/utilities/lib/jet/qml/TaskPropView.qml @@ -9,7 +9,7 @@ // import QtQuick 2.7 -import QtQuick.Controls 1.4 as Original +import QtQuick.Controls 2.3 as Original import QtQuick.Controls.Styles 1.4 import "../../prop" as Prop diff --git a/scripts/developer/utilities/lib/jet/qml/TaskTimeFrameView.qml b/scripts/developer/utilities/lib/jet/qml/TaskTimeFrameView.qml index 0b89b5d5be1..80ac9b17d25 100644 --- a/scripts/developer/utilities/lib/jet/qml/TaskTimeFrameView.qml +++ b/scripts/developer/utilities/lib/jet/qml/TaskTimeFrameView.qml @@ -11,7 +11,7 @@ // import QtQuick 2.7 -import QtQuick.Controls 1.4 as Original +import QtQuick.Controls 2.3 as Original import QtQuick.Controls.Styles 1.4 import stylesUit 1.0 diff --git a/scripts/developer/utilities/lib/plotperf/Color.qml b/scripts/developer/utilities/lib/plotperf/Color.qml index 1ad72fe2e61..19c1ed3dd38 100644 --- a/scripts/developer/utilities/lib/plotperf/Color.qml +++ b/scripts/developer/utilities/lib/plotperf/Color.qml @@ -9,7 +9,7 @@ // import QtQuick 2.7 -import QtQuick.Controls 1.4 as Original +import QtQuick.Controls 2.3 as Original import QtQuick.Controls.Styles 1.4 import stylesUit 1.0 diff --git a/scripts/developer/utilities/lib/plotperf/PlotPerf.qml b/scripts/developer/utilities/lib/plotperf/PlotPerf.qml index f239cc010a5..0d08678deae 100644 --- a/scripts/developer/utilities/lib/plotperf/PlotPerf.qml +++ b/scripts/developer/utilities/lib/plotperf/PlotPerf.qml @@ -9,7 +9,7 @@ // See the accompanying file LICENSE or https://www.apache.org/licenses/LICENSE-2.0.html // import QtQuick 2.5 -import QtQuick.Controls 1.4 +import QtQuick.Controls 2.3 Item { id: root diff --git a/scripts/developer/utilities/render/ambientOcclusionPass.qml b/scripts/developer/utilities/render/ambientOcclusionPass.qml index 75b927a2c95..0104d4ec8fd 100644 --- a/scripts/developer/utilities/render/ambientOcclusionPass.qml +++ b/scripts/developer/utilities/render/ambientOcclusionPass.qml @@ -10,7 +10,7 @@ // SPDX-License-Identifier: Apache-2.0 // import QtQuick 2.7 -import QtQuick.Controls 1.4 +import QtQuick.Controls 2.3 import QtQuick.Layouts 1.3 import stylesUit 1.0 diff --git a/scripts/developer/utilities/render/configSlider/ConfigSlider.qml b/scripts/developer/utilities/render/configSlider/ConfigSlider.qml index bf9089d82c9..63c1382ec64 100644 --- a/scripts/developer/utilities/render/configSlider/ConfigSlider.qml +++ b/scripts/developer/utilities/render/configSlider/ConfigSlider.qml @@ -9,7 +9,7 @@ // import QtQuick 2.7 -import QtQuick.Controls 1.4 as Original +import QtQuick.Controls 2.3 as Original import QtQuick.Controls.Styles 1.4 import stylesUit 1.0 diff --git a/scripts/developer/utilities/render/configSlider/RichSlider.qml b/scripts/developer/utilities/render/configSlider/RichSlider.qml index ff16cb32ad5..304232302a6 100644 --- a/scripts/developer/utilities/render/configSlider/RichSlider.qml +++ b/scripts/developer/utilities/render/configSlider/RichSlider.qml @@ -9,7 +9,7 @@ // import QtQuick 2.7 -import QtQuick.Controls 1.4 as Original +import QtQuick.Controls 2.3 as Original import QtQuick.Controls.Styles 1.4 import stylesUit 1.0 diff --git a/scripts/developer/utilities/render/engineList.qml b/scripts/developer/utilities/render/engineList.qml index 55df1bd1875..7aa05f8d36d 100644 --- a/scripts/developer/utilities/render/engineList.qml +++ b/scripts/developer/utilities/render/engineList.qml @@ -8,7 +8,7 @@ // See the accompanying file LICENSE or https://www.apache.org/licenses/LICENSE-2.0.html // import QtQuick 2.7 -import QtQuick.Controls 1.4 +import QtQuick.Controls 2.3 import QtQuick.Layouts 1.3 import stylesUit 1.0 diff --git a/scripts/developer/utilities/render/engineProfiler.qml b/scripts/developer/utilities/render/engineProfiler.qml index 11373187812..ecb3d0c2e49 100644 --- a/scripts/developer/utilities/render/engineProfiler.qml +++ b/scripts/developer/utilities/render/engineProfiler.qml @@ -10,7 +10,7 @@ // SPDX-License-Identifier: Apache-2.0 // import QtQuick 2.7 -import QtQuick.Controls 1.4 +import QtQuick.Controls 2.3 import QtQuick.Layouts 1.3 import stylesUit 1.0 diff --git a/scripts/developer/utilities/render/highlight.qml b/scripts/developer/utilities/render/highlight.qml index d8af2a828ef..3a1f4098fcd 100644 --- a/scripts/developer/utilities/render/highlight.qml +++ b/scripts/developer/utilities/render/highlight.qml @@ -9,7 +9,7 @@ // See the accompanying file LICENSE or https://www.apache.org/licenses/LICENSE-2.0.html // import QtQuick 2.7 -import QtQuick.Controls 1.4 +import QtQuick.Controls 2.3 import QtQuick.Layouts 1.3 import stylesUit 1.0 diff --git a/scripts/developer/utilities/render/highlight/HighlightStyle.qml b/scripts/developer/utilities/render/highlight/HighlightStyle.qml index 475aadfdce0..511094f5975 100644 --- a/scripts/developer/utilities/render/highlight/HighlightStyle.qml +++ b/scripts/developer/utilities/render/highlight/HighlightStyle.qml @@ -8,7 +8,7 @@ // See the accompanying file LICENSE or https://www.apache.org/licenses/LICENSE-2.0.html // import QtQuick 2.7 -import QtQuick.Controls 1.4 +import QtQuick.Controls 2.3 import QtQuick.Layouts 1.3 import "../configSlider" import "../../lib/plotperf" diff --git a/scripts/developer/utilities/render/lightClustering.qml b/scripts/developer/utilities/render/lightClustering.qml index 69cf1a6064f..75f007ed2ea 100644 --- a/scripts/developer/utilities/render/lightClustering.qml +++ b/scripts/developer/utilities/render/lightClustering.qml @@ -8,7 +8,7 @@ // See the accompanying file LICENSE or https://www.apache.org/licenses/LICENSE-2.0.html // import QtQuick 2.5 -import QtQuick.Controls 1.4 +import QtQuick.Controls 2.3 import "configSlider" import "../lib/plotperf" diff --git a/scripts/developer/utilities/render/lod.qml b/scripts/developer/utilities/render/lod.qml index 3d4cafdd38c..5ffeadd37e2 100644 --- a/scripts/developer/utilities/render/lod.qml +++ b/scripts/developer/utilities/render/lod.qml @@ -9,7 +9,7 @@ // See the accompanying file LICENSE or https://www.apache.org/licenses/LICENSE-2.0.html // 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/scripts/developer/utilities/render/luci/Antialiasing.qml b/scripts/developer/utilities/render/luci/Antialiasing.qml index 7c174d53c5b..e3773dcc181 100644 --- a/scripts/developer/utilities/render/luci/Antialiasing.qml +++ b/scripts/developer/utilities/render/luci/Antialiasing.qml @@ -10,7 +10,7 @@ // import QtQuick 2.7 -import QtQuick.Controls 1.4 +import QtQuick.Controls 2.3 import QtQuick.Layouts 1.3 import stylesUit 1.0 diff --git a/scripts/developer/utilities/render/luci/Culling.qml b/scripts/developer/utilities/render/luci/Culling.qml index 548b6908b35..b6a0bedfd36 100644 --- a/scripts/developer/utilities/render/luci/Culling.qml +++ b/scripts/developer/utilities/render/luci/Culling.qml @@ -8,7 +8,7 @@ // See the accompanying file LICENSE or https://www.apache.org/licenses/LICENSE-2.0.html // import QtQuick 2.5 -import QtQuick.Controls 1.4 +import QtQuick.Controls 2.3 import "../../lib/prop" as Prop diff --git a/scripts/developer/utilities/render/rates.qml b/scripts/developer/utilities/render/rates.qml index f4a4ee2c6cb..17e95aa722e 100644 --- a/scripts/developer/utilities/render/rates.qml +++ b/scripts/developer/utilities/render/rates.qml @@ -9,7 +9,7 @@ // See the accompanying file LICENSE or https://www.apache.org/licenses/LICENSE-2.0.html // import QtQuick 2.5 -import QtQuick.Controls 1.4 +import QtQuick.Controls 2.3 import "../lib/plotperf" Item { diff --git a/scripts/developer/utilities/render/shadow.qml b/scripts/developer/utilities/render/shadow.qml index 6af0b21d1bf..831aa6d3b29 100644 --- a/scripts/developer/utilities/render/shadow.qml +++ b/scripts/developer/utilities/render/shadow.qml @@ -9,7 +9,7 @@ // See the accompanying file LICENSE or https://www.apache.org/licenses/LICENSE-2.0.html // import QtQuick 2.7 -import QtQuick.Controls 1.4 +import QtQuick.Controls 2.3 import QtQuick.Layouts 1.3 import stylesUit 1.0 diff --git a/scripts/developer/utilities/render/stats.qml b/scripts/developer/utilities/render/stats.qml index 25c108884a0..c776885469e 100644 --- a/scripts/developer/utilities/render/stats.qml +++ b/scripts/developer/utilities/render/stats.qml @@ -9,7 +9,7 @@ // See the accompanying file LICENSE or https://www.apache.org/licenses/LICENSE-2.0.html // import QtQuick 2.5 -import QtQuick.Controls 1.4 +import QtQuick.Controls 2.3 import "../lib/plotperf" Item { diff --git a/scripts/developer/utilities/render/statsGPU.qml b/scripts/developer/utilities/render/statsGPU.qml index 8d284c11ca8..bba61eb7f21 100644 --- a/scripts/developer/utilities/render/statsGPU.qml +++ b/scripts/developer/utilities/render/statsGPU.qml @@ -9,7 +9,7 @@ // See the accompanying file LICENSE or https://www.apache.org/licenses/LICENSE-2.0.html // import QtQuick 2.5 -import QtQuick.Controls 1.4 +import QtQuick.Controls 2.3 import "../lib/plotperf" Item { diff --git a/scripts/developer/utilities/render/subsurfaceScattering.qml b/scripts/developer/utilities/render/subsurfaceScattering.qml index ec7367217ef..a4552f529ef 100644 --- a/scripts/developer/utilities/render/subsurfaceScattering.qml +++ b/scripts/developer/utilities/render/subsurfaceScattering.qml @@ -8,7 +8,7 @@ // See the accompanying file LICENSE or https://www.apache.org/licenses/LICENSE-2.0.html // import QtQuick 2.5 -import QtQuick.Controls 1.4 +import QtQuick.Controls 2.3 import "configSlider" Column { diff --git a/scripts/developer/utilities/render/surfaceGeometryPass.qml b/scripts/developer/utilities/render/surfaceGeometryPass.qml index ba1db66d16e..0c17789794b 100644 --- a/scripts/developer/utilities/render/surfaceGeometryPass.qml +++ b/scripts/developer/utilities/render/surfaceGeometryPass.qml @@ -8,7 +8,7 @@ // See the accompanying file LICENSE or https://www.apache.org/licenses/LICENSE-2.0.html // import QtQuick 2.5 -import QtQuick.Controls 1.4 +import QtQuick.Controls 2.3 import "configSlider" Column { diff --git a/scripts/developer/utilities/render/textureMonitor.qml b/scripts/developer/utilities/render/textureMonitor.qml index b01a390fa80..7e1a594ad93 100644 --- a/scripts/developer/utilities/render/textureMonitor.qml +++ b/scripts/developer/utilities/render/textureMonitor.qml @@ -9,7 +9,7 @@ // See the accompanying file LICENSE or https://www.apache.org/licenses/LICENSE-2.0.html // import QtQuick 2.5 -import QtQuick.Controls 1.4 +import QtQuick.Controls 2.3 import "../lib/plotperf" diff --git a/scripts/developer/utilities/render/transition.qml b/scripts/developer/utilities/render/transition.qml index 440075c9496..8da1e16e383 100644 --- a/scripts/developer/utilities/render/transition.qml +++ b/scripts/developer/utilities/render/transition.qml @@ -9,7 +9,7 @@ // See the accompanying file LICENSE or https://www.apache.org/licenses/LICENSE-2.0.html // import QtQuick 2.7 -import QtQuick.Controls 1.4 +import QtQuick.Controls 2.3 import QtQuick.Layouts 1.3 import QtQuick.Dialogs 1.0 diff --git a/scripts/developer/utilities/workload/avatars.qml b/scripts/developer/utilities/workload/avatars.qml index 5951e72c315..c4e325d841f 100644 --- a/scripts/developer/utilities/workload/avatars.qml +++ b/scripts/developer/utilities/workload/avatars.qml @@ -9,7 +9,7 @@ // See the accompanying file LICENSE or https://www.apache.org/licenses/LICENSE-2.0.html // 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/scripts/developer/utilities/workload/workloadInspector.qml b/scripts/developer/utilities/workload/workloadInspector.qml index 746a572f29b..0c5e007bc2f 100644 --- a/scripts/developer/utilities/workload/workloadInspector.qml +++ b/scripts/developer/utilities/workload/workloadInspector.qml @@ -8,7 +8,7 @@ // See the accompanying file LICENSE or https://www.apache.org/licenses/LICENSE-2.0.html // import QtQuick 2.7 -import QtQuick.Controls 1.4 +import QtQuick.Controls 2.3 import QtQuick.Layouts 1.3 import stylesUit 1.0 diff --git a/tests-manual/ui/qml/ControlsGalleryWindow.qml b/tests-manual/ui/qml/ControlsGalleryWindow.qml index 32fd62da363..56cd0a35ed7 100644 --- a/tests-manual/ui/qml/ControlsGalleryWindow.qml +++ b/tests-manual/ui/qml/ControlsGalleryWindow.qml @@ -1,6 +1,6 @@ import QtQuick 2.0 import QtQuick.Window 2.3 -import QtQuick.Controls 1.4 +import QtQuick.Controls 2.3 import '../../../scripts/developer/tests' as Tests ApplicationWindow { diff --git a/tests/audio/src/AudioTests.cpp b/tests/audio/src/AudioTests.cpp index 1e097855e8f..f3acc6c96e1 100644 --- a/tests/audio/src/AudioTests.cpp +++ b/tests/audio/src/AudioTests.cpp @@ -38,7 +38,7 @@ void AudioTests::listAudioDevices() { /* // AudioClient::devicesChanged is declared as: - // void devicesChanged(QAudio::Mode mode, const QList& devices); + // void devicesChanged(QAudioDevice::Mode mode, const QList& devices); // // Unfortunately with QSignalSpy we have to use the old SIGNAL() syntax, so it was a bit tricky // to figure out how to get the signal to connect. The snippet below lists signals in the format @@ -59,7 +59,7 @@ void AudioTests::listAudioDevices() { } */ - QSignalSpy spy(ac.get(), SIGNAL(devicesChanged(QAudio::Mode,QList))); + QSignalSpy spy(ac.get(), SIGNAL(devicesChanged(QAudioDevice::Mode,QList))); QVERIFY(spy.isValid()); // This checks that the signal has connected spy.wait(15000); @@ -72,7 +72,7 @@ void AudioTests::listAudioDevices() { // QSignalSpy is a QList, which stores the received signals. We can then examine it to see // what we got. for(auto event : spy) { - QAudio::Mode mode = qvariant_cast(event.at(0)); + QAudioDevice::Mode mode = qvariant_cast(event.at(0)); QList devs = qvariant_cast>(event.at(1)); QVERIFY(devs.count() > 0); diff --git a/tests/networking/src/ResourceTests.cpp b/tests/networking/src/ResourceTests.cpp index 18c55d38098..33042743556 100644 --- a/tests/networking/src/ResourceTests.cpp +++ b/tests/networking/src/ResourceTests.cpp @@ -35,7 +35,7 @@ void ResourceTests::initTestCase() { const qint64 MAXIMUM_CACHE_SIZE = 1024 * 1024 * 1024; // 1GB // set up the file cache - //QString cachePath = QStandardPaths::writableLocation(QStandardPaths::DataLocation); + //QString cachePath = QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation); QString cachePath = "./resourceTestCache"; QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); QNetworkDiskCache* cache = new QNetworkDiskCache(); diff --git a/tools/nitpick/CMakeLists.txt b/tools/nitpick/CMakeLists.txt index 3b4f3917201..0b3179a6889 100644 --- a/tools/nitpick/CMakeLists.txt +++ b/tools/nitpick/CMakeLists.txt @@ -4,13 +4,13 @@ project(${TARGET_NAME}) set(CUSTOM_NITPICK_QRC_PATHS "") find_npm() -find_package(Qt5 COMPONENTS Widgets) +find_package(Qt6 COMPONENTS Widgets) set(RESOURCES_QRC ${CMAKE_CURRENT_BINARY_DIR}/resources.qrc) set(RESOURCES_RCC ${CMAKE_CURRENT_BINARY_DIR}/resources.rcc) generate_qrc(OUTPUT ${RESOURCES_QRC} PATH ${CMAKE_CURRENT_SOURCE_DIR}/resources CUSTOM_PATHS ${CUSTOM_NITPICK_QRC_PATHS} GLOBS *) -qt5_add_binary_resources(nitpick_resources "${RESOURCES_QRC}" DESTINATION "${RESOURCES_RCC}" OPTIONS -no-compress) +qt6_add_binary_resources(nitpick_resources "${RESOURCES_QRC}" DESTINATION "${RESOURCES_RCC}" OPTIONS -no-compress) # grab the implementation and header files from src dirs file(GLOB_RECURSE NITPICK_SRCS "src/*.cpp" "src/*.h") @@ -23,7 +23,7 @@ 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}") setup_memory_debugger() setup_thread_debugger() diff --git a/tools/nitpick/src/AWSInterface.cpp b/tools/nitpick/src/AWSInterface.cpp index 894dfa85f7b..76aee17cde6 100644 --- a/tools/nitpick/src/AWSInterface.cpp +++ b/tools/nitpick/src/AWSInterface.cpp @@ -43,7 +43,7 @@ void AWSInterface::createWebPageFromResults(const QString& testResults, QStringList parts = testResults.split('/'); QString zipFilename = parts[parts.length() - 1]; - QStringList zipFolderNameParts = zipFilename.split(QRegExp("[\\(\\)\\[\\]]"), Qt::SkipEmptyParts); + QStringList zipFolderNameParts = zipFilename.split(QRegularExpression("[\\(\\)\\[\\]]"), Qt::SkipEmptyParts); if (!QRegularExpression("TestResults--\\d{4}(-\\d\\d){2}_\\d\\d(-\\d\\d){2}").match(zipFolderNameParts[0]).hasMatch() || !QRegularExpression("\\w").match(zipFolderNameParts[1]).hasMatch() || // build (local, build number or PR number) diff --git a/tools/oven/src/DomainBaker.cpp b/tools/oven/src/DomainBaker.cpp index 0853b41872c..9f770bea985 100644 --- a/tools/oven/src/DomainBaker.cpp +++ b/tools/oven/src/DomainBaker.cpp @@ -197,7 +197,7 @@ void DomainBaker::addTextureBaker(const QString& property, const QString& url, i // setup a texture baker for this URL, as long as we aren't baking a texture already if (!_textureBakers.contains(key)) { - auto baseTextureFileName = _textureFileNamer.createBaseTextureFileName(textureURL.fileName(), type); + auto baseTextureFileName = _textureFileNamer.createBaseTextureFileName(QFileInfo(textureURL.fileName()), type); // setup a baker for this texture QSharedPointer textureBaker { diff --git a/tools/skeleton-dump/src/SkeletonDumpApp.cpp b/tools/skeleton-dump/src/SkeletonDumpApp.cpp index a736cf60b80..552e42b7d26 100644 --- a/tools/skeleton-dump/src/SkeletonDumpApp.cpp +++ b/tools/skeleton-dump/src/SkeletonDumpApp.cpp @@ -54,7 +54,7 @@ SkeletonDumpApp::SkeletonDumpApp(int argc, char* argv[]) : QCoreApplication(argc return; } QByteArray blob = file.readAll(); - HFMModel::Pointer geometry = FBXSerializer().read(blob, QVariantHash()); + HFMModel::Pointer geometry = FBXSerializer().read(blob, hifi::VariantMultiHash()); std::unique_ptr skeleton(new AnimSkeleton(*geometry)); skeleton->dump(verbose); } From bc17f35634b1df30aba7f35940d9adf2410ad09c Mon Sep 17 00:00:00 2001 From: Karol Suprynowicz Date: Sat, 6 Sep 2025 22:22:11 +0200 Subject: [PATCH 002/111] Initial work on upgrading QML to Qt6 --- android/apps/framePlayer/src/main/cpp/qml/main.qml | 2 +- .../resources/QtWebEngine/UIDelegates/ConfirmDialog.qml | 2 +- .../qml/+android_interface/Web3DSurfaceAndroid.qml | 2 +- interface/resources/qml/AvatarInputsBar.qml | 2 +- interface/resources/qml/BubbleIcon.qml | 2 +- interface/resources/qml/ConnectionFailureDialog.qml | 2 +- .../LoginDialog/+android_interface/LinkAccountBody.qml | 2 +- .../qml/LoginDialog/+android_interface/SignUpBody.qml | 2 +- .../resources/qml/LoginDialog/CompleteProfileBody.qml | 2 +- interface/resources/qml/LoginDialog/LinkAccountBody.qml | 2 +- interface/resources/qml/LoginDialog/LoggingInBody.qml | 2 +- .../resources/qml/LoginDialog/LoginDialogLightbox.qml | 2 +- interface/resources/qml/LoginDialog/SignUpBody.qml | 2 +- interface/resources/qml/UpdateDialog.qml | 6 +++--- interface/resources/qml/controls/ButtonAwesome.qml | 9 +++++---- interface/resources/qml/controls/CheckBox.qml | 4 ++-- interface/resources/qml/controls/ComboBox.qml | 2 +- interface/resources/qml/controls/Player.qml | 2 +- interface/resources/qml/controls/RadioButton.qml | 2 +- interface/resources/qml/controls/Slider.qml | 2 +- interface/resources/qml/controls/SpinBox.qml | 2 +- interface/resources/qml/controls/TextField.qml | 5 +++-- interface/resources/qml/controlsUit/AttachmentsTable.qml | 2 +- interface/resources/qml/controlsUit/ContentSection.qml | 2 +- interface/resources/qml/controlsUit/FilterBar.qml | 2 +- interface/resources/qml/controlsUit/Keyboard.qml | 2 +- interface/resources/qml/controlsUit/Table.qml | 2 +- .../resources/qml/controlsUit/TabletContentSection.qml | 2 +- interface/resources/qml/controlsUit/TextField.qml | 6 ++++-- interface/resources/qml/controlsUit/Tree.qml | 2 +- interface/resources/qml/desktop/Desktop.qml | 4 +++- interface/resources/qml/dialogs/CustomQueryDialog.qml | 2 +- interface/resources/qml/dialogs/FileDialog.qml | 2 +- interface/resources/qml/dialogs/MessageDialog.qml | 2 +- .../qml/dialogs/TabletConnectionFailureDialog.qml | 2 +- .../resources/qml/dialogs/TabletCustomQueryDialog.qml | 2 +- interface/resources/qml/dialogs/TabletFileDialog.qml | 2 +- interface/resources/qml/dialogs/TabletMessageBox.qml | 2 +- interface/resources/qml/dialogs/TabletQueryDialog.qml | 2 +- .../qml/dialogs/assetDialog/AssetDialogContent.qml | 2 +- .../qml/dialogs/messageDialog/MessageDialogButton.qml | 2 +- .../resources/qml/hifi/+android_interface/ActionBar.qml | 2 +- .../resources/qml/hifi/+android_interface/AudioBar.qml | 2 +- .../resources/qml/hifi/+android_interface/StatsBar.qml | 2 +- .../qml/hifi/+android_interface/WindowHeader.qml | 2 +- .../qml/hifi/+android_interface/bottomHudOptions.qml | 2 +- .../resources/qml/hifi/+android_interface/button.qml | 2 +- .../resources/qml/hifi/+android_interface/modesbar.qml | 2 +- interface/resources/qml/hifi/AssetServer.qml | 8 +++++--- interface/resources/qml/hifi/AvatarApp.qml | 2 +- interface/resources/qml/hifi/Card.qml | 2 +- interface/resources/qml/hifi/Feed.qml | 2 +- interface/resources/qml/hifi/NameCard.qml | 4 ++-- interface/resources/qml/hifi/Pal.qml | 2 +- interface/resources/qml/hifi/audio/Audio.qml | 2 +- interface/resources/qml/hifi/audio/InputPeak.qml | 2 +- interface/resources/qml/hifi/audio/MicBar.qml | 2 +- interface/resources/qml/hifi/audio/MicBarApplication.qml | 2 +- .../qml/hifi/avatarPackager/AvatarPackagerApp.qml | 2 +- .../resources/qml/hifi/avatarPackager/AvatarProject.qml | 2 +- .../qml/hifi/avatarPackager/AvatarProjectCard.qml | 2 +- .../qml/hifi/avatarPackager/AvatarProjectUpload.qml | 2 +- .../resources/qml/hifi/avatarPackager/LoadingCircle.qml | 2 +- .../resources/qml/hifi/avatarapp/AvatarThumbnail.qml | 2 +- interface/resources/qml/hifi/avatarapp/ShadowGlyph.qml | 2 +- interface/resources/qml/hifi/avatarapp/ShadowImage.qml | 2 +- .../resources/qml/hifi/avatarapp/ShadowRectangle.qml | 2 +- interface/resources/qml/hifi/avatarapp/SquareLabel.qml | 2 +- interface/resources/qml/hifi/dialogs/RunningScripts.qml | 2 +- .../resources/qml/hifi/dialogs/TabletAssetServer.qml | 8 +++++--- .../resources/qml/hifi/dialogs/TabletRunningScripts.qml | 2 +- .../resources/qml/hifi/dialogs/security/Security.qml | 2 +- interface/resources/qml/hifi/overlays/ImageOverlay.qml | 2 +- .../avatarApp/components/AvatarAppListDelegate.qml | 2 +- .../avatarApp/components/DisplayNameHeader.qml | 2 +- .../simplifiedUI/inputDeviceButton/InputDeviceButton.qml | 2 +- .../hifi/simplifiedUI/simplifiedControls/InputPeak.qml | 2 +- .../qml/hifi/simplifiedUI/topBar/SimplifiedTopBar.qml | 2 +- .../resources/qml/hifi/tablet/CalibratingScreen.qml | 2 +- .../resources/qml/hifi/tablet/ControllerSettings.qml | 2 +- .../resources/qml/hifi/tablet/OpenVrConfiguration.qml | 2 +- interface/resources/qml/hifi/tablet/TADLightbox.qml | 2 +- .../resources/qml/hifi/tablet/TabletAddressDialog.qml | 4 ++-- interface/resources/qml/hifi/tablet/TabletButton.qml | 2 +- interface/resources/qml/hifi/tablet/TabletHome.qml | 2 +- interface/resources/qml/hifi/tablet/TabletMenu.qml | 2 +- .../qml/hifi/tablet/tabletWindows/TabletFileDialog.qml | 2 +- interface/resources/qml/windows/Decoration.qml | 2 +- .../resources/qml/windows/DefaultFrameDecoration.qml | 2 +- interface/resources/qml/windows/Frame.qml | 2 +- interface/resources/qml/windows/ScrollingWindow.qml | 2 +- interface/resources/qml/windows/ToolFrame.qml | 2 +- interface/resources/qml/windows/ToolFrameDecoration.qml | 2 +- interface/resources/qml/windows/Window.qml | 2 +- scripts/developer/utilities/lib/jet/qml/TaskList.qml | 2 +- scripts/developer/utilities/lib/jet/qml/TaskListView.qml | 2 +- scripts/developer/utilities/lib/jet/qml/TaskPropView.qml | 2 +- .../utilities/lib/jet/qml/TaskTimeFrameView.qml | 2 +- scripts/developer/utilities/lib/plotperf/Color.qml | 2 +- .../utilities/render/configSlider/ConfigSlider.qml | 2 +- .../utilities/render/configSlider/RichSlider.qml | 2 +- scripts/developer/utilities/render/transition.qml | 2 +- .../simplifiedEmote/ui/qml/SimplifiedEmoteIndicator.qml | 2 +- .../ui/simplifiedFTUE/InitialLaunchWindow.qml | 2 +- .../ui/simplifiedFTUE/SecondLaunchWindow.qml | 2 +- scripts/system/create/entityList/qml/EditEntityList.qml | 2 +- scripts/system/create/qml/EditTabView.qml | 2 +- scripts/system/create/qml/EditToolsTabView.qml | 2 +- scripts/system/create/qml/NewEntityButton.qml | 2 +- scripts/system/create/qml/NewMaterialDialog.qml | 2 +- scripts/system/create/qml/NewModelDialog.qml | 2 +- scripts/system/create/qml/NewParticleDialog.qml | 2 +- scripts/system/create/qml/NewPolyVoxDialog.qml | 2 +- scripts/system/create/qml/NewSoundDialog.qml | 2 +- scripts/system/settings/qml/AdvancedOptions.qml | 2 +- scripts/system/settings/qml/SettingNumber.qml | 2 +- scripts/system/settings/qml/SettingSlider.qml | 2 +- tests-manual/controllers/qml/main.qml | 2 +- tests-manual/qml/qml/MacQml.qml | 2 +- tests-manual/qml/qml/main.qml | 2 +- tools/animedit/qml/fields/BooleanField.qml | 2 +- tools/animedit/qml/fields/IdField.qml | 2 +- tools/animedit/qml/fields/JSONField.qml | 2 +- tools/animedit/qml/fields/NumberArrayField.qml | 2 +- tools/animedit/qml/fields/NumberField.qml | 2 +- tools/animedit/qml/fields/StringField.qml | 2 +- tools/animedit/qml/fields/TypeField.qml | 2 +- tools/animedit/qml/main.qml | 6 +++--- tools/animedit/qml/nodes/BlendDirectional.qml | 2 +- tools/animedit/qml/nodes/BlendLinear.qml | 2 +- tools/animedit/qml/nodes/BlendLinearMove.qml | 2 +- tools/animedit/qml/nodes/ClipData.qml | 2 +- tools/animedit/qml/nodes/DefaultPose.qml | 2 +- tools/animedit/qml/nodes/InverseKinematics.qml | 2 +- tools/animedit/qml/nodes/Manipulator.qml | 2 +- tools/animedit/qml/nodes/Overlay.qml | 2 +- tools/animedit/qml/nodes/PoleVector.qml | 2 +- tools/animedit/qml/nodes/RandomStateMachine.qml | 2 +- tools/animedit/qml/nodes/SplineIK.qml | 2 +- tools/animedit/qml/nodes/StateMachine.qml | 2 +- tools/animedit/qml/nodes/TwoBoneIK.qml | 2 +- .../marketplace/spectator-camera/SpectatorCamera.qml | 2 +- 142 files changed, 168 insertions(+), 158 deletions(-) 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/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/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/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/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/LoginDialog/+android_interface/LinkAccountBody.qml b/interface/resources/qml/LoginDialog/+android_interface/LinkAccountBody.qml index cea9b188769..39852afeccc 100644 --- a/interface/resources/qml/LoginDialog/+android_interface/LinkAccountBody.qml +++ b/interface/resources/qml/LoginDialog/+android_interface/LinkAccountBody.qml @@ -11,7 +11,7 @@ import Hifi 1.0 import QtQuick 2.4 import QtQuick.Controls 2.3 -import QtQuick.Controls.Styles 1.4 as OriginalStyles +////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 cfd750bf305..9b1bc80a5fd 100644 --- a/interface/resources/qml/LoginDialog/+android_interface/SignUpBody.qml +++ b/interface/resources/qml/LoginDialog/+android_interface/SignUpBody.qml @@ -11,7 +11,7 @@ import Hifi 1.0 import QtQuick 2.4 import QtQuick.Controls 2.3 -import QtQuick.Controls.Styles 1.4 as OriginalStyles +////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 b9b2677c486..550210451cc 100644 --- a/interface/resources/qml/LoginDialog/LinkAccountBody.qml +++ b/interface/resources/qml/LoginDialog/LinkAccountBody.qml @@ -12,7 +12,7 @@ import Hifi 1.0 import QtQuick 2.7 import QtQuick.Controls 2.3 -import QtQuick.Controls.Styles 1.4 as OriginalStyles +////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 00c9bc96833..d8cfd618d66 100644 --- a/interface/resources/qml/LoginDialog/LoggingInBody.qml +++ b/interface/resources/qml/LoginDialog/LoggingInBody.qml @@ -13,7 +13,7 @@ import Hifi 1.0 import QtQuick 2.7 import QtQuick.Controls 2.3 -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/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 38ede9f27a9..b3ca2451a01 100644 --- a/interface/resources/qml/LoginDialog/SignUpBody.qml +++ b/interface/resources/qml/LoginDialog/SignUpBody.qml @@ -12,7 +12,7 @@ import Hifi 1.0 import QtQuick 2.7 import QtQuick.Controls 2.3 -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/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/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 309542f6bba..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 2.3 -import QtQuick.Controls.Styles 1.4 +//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 09b065a20ed..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 2.3 -import QtQuick.Controls.Styles 1.4 +//import QtQuick.Controls.Styles TextField { - style: TextFieldStyle { renderType: Text.QtRendering } + //QT6TODO + //style: TextFieldStyle { renderType: Text.QtRendering } } diff --git a/interface/resources/qml/controlsUit/AttachmentsTable.qml b/interface/resources/qml/controlsUit/AttachmentsTable.qml index 949c1c2e42b..c20bea08180 100644 --- a/interface/resources/qml/controlsUit/AttachmentsTable.qml +++ b/interface/resources/qml/controlsUit/AttachmentsTable.qml @@ -10,7 +10,7 @@ import QtQuick 2.5 import QtQuick.Controls 2.3 -import QtQuick.Controls.Styles 1.4 +//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 1b340b986bb..580d79cdb52 100644 --- a/interface/resources/qml/controlsUit/Table.qml +++ b/interface/resources/qml/controlsUit/Table.qml @@ -10,7 +10,7 @@ import QtQuick 2.5 import QtQuick.Controls 2.3 -import QtQuick.Controls.Styles 1.4 +//import QtQuick.Controls.Styles import QtQuick.Controls 2.3 as QQC2 import "../stylesUit" 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 e3db7096017..0d04f363e5a 100644 --- a/interface/resources/qml/controlsUit/TextField.qml +++ b/interface/resources/qml/controlsUit/TextField.qml @@ -10,7 +10,7 @@ import QtQuick 2.5 import QtQuick.Controls 2.3 -import QtQuick.Controls.Styles 1.4 +//import QtQuick.Controls.Styles import "../stylesUit" import "." as HifiControls @@ -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 3ba02fcbd3d..99b6fcb9319 100644 --- a/interface/resources/qml/controlsUit/Tree.qml +++ b/interface/resources/qml/controlsUit/Tree.qml @@ -11,7 +11,7 @@ import QtQml.Models 2.2 import QtQuick 2.7 import QtQuick.Controls 2.3 -import QtQuick.Controls.Styles 1.4 +//import QtQuick.Controls.Styles import QtQuick.Controls 2.2 as QQC2 diff --git a/interface/resources/qml/desktop/Desktop.qml b/interface/resources/qml/desktop/Desktop.qml index d07c5525f3d..e21d7a65490 100644 --- a/interface/resources/qml/desktop/Desktop.qml +++ b/interface/resources/qml/desktop/Desktop.qml @@ -57,7 +57,8 @@ FocusScope { property var exclusionGroups: ({}); property Component exclusiveGroupMaker: Component { - ExclusiveGroup { + //ExclusiveGroup { //QT6TODO + ButtonGroup { } } @@ -68,6 +69,7 @@ FocusScope { exclusionGroups[exclusionGroupId] = exclusiveGroupMaker.createObject(rootMenuId); } + //QT6TODO: qmlAction.exclusiveGroup = exclusionGroups[exclusionGroupId] } } 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 c1d39995a46..6141bbc37de 100644 --- a/interface/resources/qml/dialogs/FileDialog.qml +++ b/interface/resources/qml/dialogs/FileDialog.qml @@ -11,7 +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.Dialogs as OriginalDialogs import QtQuick.Controls 2.3 as QQC1 import QtQuick.Controls 2.3 diff --git a/interface/resources/qml/dialogs/MessageDialog.qml b/interface/resources/qml/dialogs/MessageDialog.qml index 629027ab2af..050c4cf0310 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 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 94bd8fc2c45..222596be268 100644 --- a/interface/resources/qml/dialogs/TabletFileDialog.qml +++ b/interface/resources/qml/dialogs/TabletFileDialog.qml @@ -11,7 +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.Dialogs as OriginalDialogs import QtQuick.Controls 2.3 as QQC1 import QtQuick.Controls 2.3 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..d8537b7fec3 100644 --- a/interface/resources/qml/dialogs/messageDialog/MessageDialogButton.qml +++ b/interface/resources/qml/dialogs/messageDialog/MessageDialogButton.qml @@ -9,7 +9,7 @@ // import QtQuick 2.5 -import QtQuick.Dialogs 1.2 +import QtQuick.Dialogs import controlsUit 1.0 diff --git a/interface/resources/qml/hifi/+android_interface/ActionBar.qml b/interface/resources/qml/hifi/+android_interface/ActionBar.qml index c6719131842..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 2.3 -import QtQuick.Controls.Styles 1.4 +//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 37ae3f4fad7..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 2.3 -import QtQuick.Controls.Styles 1.4 +//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/StatsBar.qml b/interface/resources/qml/hifi/+android_interface/StatsBar.qml index 6dc5967e014..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 2.3 -import QtQuick.Controls.Styles 1.4 +//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 446181d761f..43448bada85 100644 --- a/interface/resources/qml/hifi/+android_interface/WindowHeader.qml +++ b/interface/resources/qml/hifi/+android_interface/WindowHeader.qml @@ -11,7 +11,7 @@ import QtQuick 2.5 import QtQuick.Controls 2.3 -import QtQuick.Controls.Styles 1.4 +//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 c2259338073..e459e904c1e 100644 --- a/interface/resources/qml/hifi/+android_interface/bottomHudOptions.qml +++ b/interface/resources/qml/hifi/+android_interface/bottomHudOptions.qml @@ -12,7 +12,7 @@ import Hifi 1.0 import QtQuick 2.5 import QtQuick.Controls 2.3 -import QtQuick.Controls.Styles 1.4 +//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 7195645e5ae..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 2.3 -import QtQuick.Controls.Styles 1.4 +//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 2bb957d182c..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 2.3 -import QtQuick.Controls.Styles 1.4 +//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 0863a0831ff..98de845fc6d 100644 --- a/interface/resources/qml/hifi/AssetServer.qml +++ b/interface/resources/qml/hifi/AssetServer.qml @@ -10,8 +10,8 @@ import QtQuick 2.5 import QtQuick.Controls 2.3 -import QtQuick.Controls.Styles 1.4 -import QtQuick.Dialogs 1.2 as OriginalDialogs +//import QtQuick.Controls.Styles +import QtQuick.Dialogs as OriginalDialogs import Qt.labs.settings 1.0 import stylesUit 1.0 @@ -677,6 +677,8 @@ Windows.ScrollingWindow { font.pixelSize: hifi.fontSizes.textFieldInput height: hifi.dimensions.tableRowHeight + //QT6TODO + /* style: TextFieldStyle { textColor: readOnly ? hifi.colors.black @@ -691,7 +693,7 @@ Windows.ScrollingWindow { selectionColor: hifi.colors.primaryHighlight padding.left: readOnly ? 0 : hifi.dimensions.textPadding padding.right: readOnly ? 0 : hifi.dimensions.textPadding - } + }*/ validator: RegExpValidator { regExp: /[^/]+/ diff --git a/interface/resources/qml/hifi/AvatarApp.qml b/interface/resources/qml/hifi/AvatarApp.qml index 0a02418a8bf..160b5bd211a 100644 --- a/interface/resources/qml/hifi/AvatarApp.qml +++ b/interface/resources/qml/hifi/AvatarApp.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 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/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 331d1763659..6e7b3097ae9 100644 --- a/interface/resources/qml/hifi/NameCard.qml +++ b/interface/resources/qml/hifi/NameCard.qml @@ -11,8 +11,8 @@ import QtQuick 2.5 import QtQuick.Controls 2.3 -import QtQuick.Controls.Styles 1.4 -import QtGraphicalEffects 1.0 +//import QtQuick.Controls.Styles +import Qt5Compat.GraphicalEffects import stylesUit 1.0 import controlsUit 1.0 as HifiControls import "toolbars" diff --git a/interface/resources/qml/hifi/Pal.qml b/interface/resources/qml/hifi/Pal.qml index 9f81f5596d9..6af28e7bbcc 100644 --- a/interface/resources/qml/hifi/Pal.qml +++ b/interface/resources/qml/hifi/Pal.qml @@ -13,7 +13,7 @@ import QtQuick 2.5 import QtQuick.Controls 2.3 -import QtGraphicalEffects 1.0 +import Qt5Compat.GraphicalEffects import Qt.labs.settings 1.0 import stylesUit 1.0 import controlsUit 1.0 as HifiControlsUit 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/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/dialogs/RunningScripts.qml b/interface/resources/qml/hifi/dialogs/RunningScripts.qml index 5fe0f090c59..8c52ce41d19 100644 --- a/interface/resources/qml/hifi/dialogs/RunningScripts.qml +++ b/interface/resources/qml/hifi/dialogs/RunningScripts.qml @@ -10,7 +10,7 @@ import QtQuick 2.5 import QtQuick.Controls 2.3 -import QtQuick.Dialogs 1.2 as OriginalDialogs +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 cb90cf3fac2..474c4ffb903 100644 --- a/interface/resources/qml/hifi/dialogs/TabletAssetServer.qml +++ b/interface/resources/qml/hifi/dialogs/TabletAssetServer.qml @@ -10,8 +10,8 @@ import QtQuick 2.5 import QtQuick.Controls 2.3 -import QtQuick.Controls.Styles 1.4 -import QtQuick.Dialogs 1.2 as OriginalDialogs +//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,7 +691,7 @@ Rectangle { selectionColor: hifi.colors.primaryHighlight padding.left: readOnly ? 0 : hifi.dimensions.textPadding padding.right: readOnly ? 0 : hifi.dimensions.textPadding - } + }*/ validator: RegExpValidator { regExp: /[^/]+/ diff --git a/interface/resources/qml/hifi/dialogs/TabletRunningScripts.qml b/interface/resources/qml/hifi/dialogs/TabletRunningScripts.qml index ef66764c756..9bb91f20c13 100644 --- a/interface/resources/qml/hifi/dialogs/TabletRunningScripts.qml +++ b/interface/resources/qml/hifi/dialogs/TabletRunningScripts.qml @@ -10,7 +10,7 @@ import QtQuick 2.5 import QtQuick.Controls 2.3 -import QtQuick.Dialogs 1.2 as OriginalDialogs +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 1a1e0a96ff2..c770c2dc0f2 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 diff --git a/interface/resources/qml/hifi/tablet/TabletMenu.qml b/interface/resources/qml/hifi/tablet/TabletMenu.qml index ac17d48a344..d8d88272a41 100644 --- a/interface/resources/qml/hifi/tablet/TabletMenu.qml +++ b/interface/resources/qml/hifi/tablet/TabletMenu.qml @@ -1,5 +1,5 @@ import QtQuick 2.5 -import QtGraphicalEffects 1.0 +import Qt5Compat.GraphicalEffects import QtQuick.Controls 2.3 import QtQml 2.2 diff --git a/interface/resources/qml/hifi/tablet/tabletWindows/TabletFileDialog.qml b/interface/resources/qml/hifi/tablet/tabletWindows/TabletFileDialog.qml index fb1bdd38811..025b245786c 100644 --- a/interface/resources/qml/hifi/tablet/tabletWindows/TabletFileDialog.qml +++ b/interface/resources/qml/hifi/tablet/tabletWindows/TabletFileDialog.qml @@ -11,7 +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.Dialogs as OriginalDialogs import QtQuick.Controls 2.3 as QQC1 import QtQuick.Controls 2.3 diff --git a/interface/resources/qml/windows/Decoration.qml b/interface/resources/qml/windows/Decoration.qml index 1ba7ad125d6..eca67925019 100644 --- a/interface/resources/qml/windows/Decoration.qml +++ b/interface/resources/qml/windows/Decoration.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/DefaultFrameDecoration.qml b/interface/resources/qml/windows/DefaultFrameDecoration.qml index fb0dd55985c..3d4a01142e9 100644 --- a/interface/resources/qml/windows/DefaultFrameDecoration.qml +++ b/interface/resources/qml/windows/DefaultFrameDecoration.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/Frame.qml b/interface/resources/qml/windows/Frame.qml index cfff40bbf62..8460deddf03 100644 --- a/interface/resources/qml/windows/Frame.qml +++ b/interface/resources/qml/windows/Frame.qml @@ -9,7 +9,7 @@ // import QtQuick 2.5 -import QtGraphicalEffects 1.0 +import Qt5Compat.GraphicalEffects import stylesUit 1.0 import "../js/Utils.js" as Utils diff --git a/interface/resources/qml/windows/ScrollingWindow.qml b/interface/resources/qml/windows/ScrollingWindow.qml index 0c91b58e315..3bc98336b7f 100644 --- a/interface/resources/qml/windows/ScrollingWindow.qml +++ b/interface/resources/qml/windows/ScrollingWindow.qml @@ -11,7 +11,7 @@ import QtQuick 2.5 import QtQuick.Controls 2.2 -import QtGraphicalEffects 1.0 +import Qt5Compat.GraphicalEffects import "." as Windows import stylesUit 1.0 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..3aef97e9b12 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 diff --git a/scripts/developer/utilities/lib/jet/qml/TaskList.qml b/scripts/developer/utilities/lib/jet/qml/TaskList.qml index 5bde2a8743f..02c68b152f8 100644 --- a/scripts/developer/utilities/lib/jet/qml/TaskList.qml +++ b/scripts/developer/utilities/lib/jet/qml/TaskList.qml @@ -10,7 +10,7 @@ import QtQuick 2.7 import QtQuick.Controls 2.3 as Original -import QtQuick.Controls.Styles 1.4 +//import QtQuick.Controls.Styles import stylesUit 1.0 import controlsUit 1.0 as HifiControls diff --git a/scripts/developer/utilities/lib/jet/qml/TaskListView.qml b/scripts/developer/utilities/lib/jet/qml/TaskListView.qml index 4cdee16ffe1..e65d76ea3e1 100644 --- a/scripts/developer/utilities/lib/jet/qml/TaskListView.qml +++ b/scripts/developer/utilities/lib/jet/qml/TaskListView.qml @@ -10,7 +10,7 @@ import QtQuick 2.7 import QtQuick.Controls 2.3 as Original -import QtQuick.Controls.Styles 1.4 +//import QtQuick.Controls.Styles import stylesUit 1.0 import controlsUit 1.0 as HifiControls diff --git a/scripts/developer/utilities/lib/jet/qml/TaskPropView.qml b/scripts/developer/utilities/lib/jet/qml/TaskPropView.qml index 0a5b529580b..1f2a9be23c2 100644 --- a/scripts/developer/utilities/lib/jet/qml/TaskPropView.qml +++ b/scripts/developer/utilities/lib/jet/qml/TaskPropView.qml @@ -10,7 +10,7 @@ import QtQuick 2.7 import QtQuick.Controls 2.3 as Original -import QtQuick.Controls.Styles 1.4 +//import QtQuick.Controls.Styles import "../../prop" as Prop diff --git a/scripts/developer/utilities/lib/jet/qml/TaskTimeFrameView.qml b/scripts/developer/utilities/lib/jet/qml/TaskTimeFrameView.qml index 80ac9b17d25..2c8949d68e5 100644 --- a/scripts/developer/utilities/lib/jet/qml/TaskTimeFrameView.qml +++ b/scripts/developer/utilities/lib/jet/qml/TaskTimeFrameView.qml @@ -12,7 +12,7 @@ import QtQuick 2.7 import QtQuick.Controls 2.3 as Original -import QtQuick.Controls.Styles 1.4 +//import QtQuick.Controls.Styles import stylesUit 1.0 import controlsUit 1.0 as HifiControls diff --git a/scripts/developer/utilities/lib/plotperf/Color.qml b/scripts/developer/utilities/lib/plotperf/Color.qml index 19c1ed3dd38..c8add4d20d5 100644 --- a/scripts/developer/utilities/lib/plotperf/Color.qml +++ b/scripts/developer/utilities/lib/plotperf/Color.qml @@ -10,7 +10,7 @@ import QtQuick 2.7 import QtQuick.Controls 2.3 as Original -import QtQuick.Controls.Styles 1.4 +//import QtQuick.Controls.Styles import stylesUit 1.0 import controlsUit 1.0 as HifiControls diff --git a/scripts/developer/utilities/render/configSlider/ConfigSlider.qml b/scripts/developer/utilities/render/configSlider/ConfigSlider.qml index 63c1382ec64..1a02a6b770f 100644 --- a/scripts/developer/utilities/render/configSlider/ConfigSlider.qml +++ b/scripts/developer/utilities/render/configSlider/ConfigSlider.qml @@ -10,7 +10,7 @@ import QtQuick 2.7 import QtQuick.Controls 2.3 as Original -import QtQuick.Controls.Styles 1.4 +//import QtQuick.Controls.Styles import stylesUit 1.0 import controlsUit 1.0 as HifiControls diff --git a/scripts/developer/utilities/render/configSlider/RichSlider.qml b/scripts/developer/utilities/render/configSlider/RichSlider.qml index 304232302a6..db6993f2930 100644 --- a/scripts/developer/utilities/render/configSlider/RichSlider.qml +++ b/scripts/developer/utilities/render/configSlider/RichSlider.qml @@ -10,7 +10,7 @@ import QtQuick 2.7 import QtQuick.Controls 2.3 as Original -import QtQuick.Controls.Styles 1.4 +//import QtQuick.Controls.Styles import stylesUit 1.0 import controlsUit 1.0 as HifiControls diff --git a/scripts/developer/utilities/render/transition.qml b/scripts/developer/utilities/render/transition.qml index 8da1e16e383..52b839dd3b0 100644 --- a/scripts/developer/utilities/render/transition.qml +++ b/scripts/developer/utilities/render/transition.qml @@ -11,7 +11,7 @@ import QtQuick 2.7 import QtQuick.Controls 2.3 import QtQuick.Layouts 1.3 -import QtQuick.Dialogs 1.0 +import QtQuick.Dialogs import stylesUit 1.0 import controlsUit 1.0 as HifiControls diff --git a/scripts/simplifiedUI/simplifiedEmote/ui/qml/SimplifiedEmoteIndicator.qml b/scripts/simplifiedUI/simplifiedEmote/ui/qml/SimplifiedEmoteIndicator.qml index 32a4664e0ac..f1ffeabcfe8 100644 --- a/scripts/simplifiedUI/simplifiedEmote/ui/qml/SimplifiedEmoteIndicator.qml +++ b/scripts/simplifiedUI/simplifiedEmote/ui/qml/SimplifiedEmoteIndicator.qml @@ -11,7 +11,7 @@ import QtQuick 2.10 import QtQuick.Controls 2.3 -import QtGraphicalEffects 1.0 +import Qt5Compat.GraphicalEffects import stylesUit 1.0 as HifiStylesUit import TabletScriptingInterface 1.0 import hifi.simplifiedUI.simplifiedConstants 1.0 as SimplifiedConstants diff --git a/scripts/simplifiedUI/ui/simplifiedFTUE/InitialLaunchWindow.qml b/scripts/simplifiedUI/ui/simplifiedFTUE/InitialLaunchWindow.qml index 1938586edb8..5fefde32e7b 100644 --- a/scripts/simplifiedUI/ui/simplifiedFTUE/InitialLaunchWindow.qml +++ b/scripts/simplifiedUI/ui/simplifiedFTUE/InitialLaunchWindow.qml @@ -9,7 +9,7 @@ import QtQuick 2.10 import QtQuick.Controls 2.3 -import QtGraphicalEffects 1.0 +import Qt5Compat.GraphicalEffects import QtQuick.Layouts 1.3 import stylesUit 1.0 as HifiStylesUit import TabletScriptingInterface 1.0 diff --git a/scripts/simplifiedUI/ui/simplifiedFTUE/SecondLaunchWindow.qml b/scripts/simplifiedUI/ui/simplifiedFTUE/SecondLaunchWindow.qml index 2a796465aed..0cc89ed9898 100644 --- a/scripts/simplifiedUI/ui/simplifiedFTUE/SecondLaunchWindow.qml +++ b/scripts/simplifiedUI/ui/simplifiedFTUE/SecondLaunchWindow.qml @@ -9,7 +9,7 @@ import QtQuick 2.10 import QtQuick.Controls 2.3 -import QtGraphicalEffects 1.0 +import Qt5Compat.GraphicalEffects import QtQuick.Layouts 1.3 import stylesUit 1.0 as HifiStylesUit import TabletScriptingInterface 1.0 diff --git a/scripts/system/create/entityList/qml/EditEntityList.qml b/scripts/system/create/entityList/qml/EditEntityList.qml index 1d5beb9914f..55be0d8212a 100644 --- a/scripts/system/create/entityList/qml/EditEntityList.qml +++ b/scripts/system/create/entityList/qml/EditEntityList.qml @@ -3,7 +3,7 @@ import QtQuick.Controls 2.2 import QtWebChannel 1.0 import controls 1.0 import hifi.toolbars 1.0 -import QtGraphicalEffects 1.0 +import Qt5Compat.GraphicalEffects import controlsUit 1.0 as HifiControls import stylesUit 1.0 diff --git a/scripts/system/create/qml/EditTabView.qml b/scripts/system/create/qml/EditTabView.qml index 091ddf0ac77..016b18da340 100644 --- a/scripts/system/create/qml/EditTabView.qml +++ b/scripts/system/create/qml/EditTabView.qml @@ -3,7 +3,7 @@ import QtQuick.Controls 2.2 import QtWebChannel 1.0 import controls 1.0 import hifi.toolbars 1.0 -import QtGraphicalEffects 1.0 +import Qt5Compat.GraphicalEffects import controlsUit 1.0 as HifiControls import stylesUit 1.0 diff --git a/scripts/system/create/qml/EditToolsTabView.qml b/scripts/system/create/qml/EditToolsTabView.qml index 87295ff543f..12e8a1f14c6 100644 --- a/scripts/system/create/qml/EditToolsTabView.qml +++ b/scripts/system/create/qml/EditToolsTabView.qml @@ -3,7 +3,7 @@ import QtQuick.Controls 2.2 import QtWebChannel 1.0 import controls 1.0 import hifi.toolbars 1.0 -import QtGraphicalEffects 1.0 +import Qt5Compat.GraphicalEffects import controlsUit 1.0 as HifiControls import stylesUit 1.0 diff --git a/scripts/system/create/qml/NewEntityButton.qml b/scripts/system/create/qml/NewEntityButton.qml index a78af7a845e..fd87f9dba0e 100644 --- a/scripts/system/create/qml/NewEntityButton.qml +++ b/scripts/system/create/qml/NewEntityButton.qml @@ -1,5 +1,5 @@ import QtQuick 2.0 -import QtGraphicalEffects 1.0 +import Qt5Compat.GraphicalEffects import TabletScriptingInterface 1.0 Item { diff --git a/scripts/system/create/qml/NewMaterialDialog.qml b/scripts/system/create/qml/NewMaterialDialog.qml index 423afe4b186..e3a9d0b1437 100644 --- a/scripts/system/create/qml/NewMaterialDialog.qml +++ b/scripts/system/create/qml/NewMaterialDialog.qml @@ -13,7 +13,7 @@ import QtQuick 2.7 import QtQuick.Controls 2.2 -import QtQuick.Dialogs 1.2 as OriginalDialogs +import QtQuick.Dialogs as OriginalDialogs import stylesUit 1.0 import controlsUit 1.0 diff --git a/scripts/system/create/qml/NewModelDialog.qml b/scripts/system/create/qml/NewModelDialog.qml index 5dcbfafa513..0df9b72045c 100644 --- a/scripts/system/create/qml/NewModelDialog.qml +++ b/scripts/system/create/qml/NewModelDialog.qml @@ -12,7 +12,7 @@ // import QtQuick 2.5 -import QtQuick.Dialogs 1.2 as OriginalDialogs +import QtQuick.Dialogs as OriginalDialogs import stylesUit 1.0 import controlsUit 1.0 diff --git a/scripts/system/create/qml/NewParticleDialog.qml b/scripts/system/create/qml/NewParticleDialog.qml index c07843f30ad..08f22403dba 100644 --- a/scripts/system/create/qml/NewParticleDialog.qml +++ b/scripts/system/create/qml/NewParticleDialog.qml @@ -10,7 +10,7 @@ // import QtQuick 2.5 -import QtQuick.Dialogs 1.2 as OriginalDialogs +import QtQuick.Dialogs as OriginalDialogs import stylesUit 1.0 import controlsUit 1.0 diff --git a/scripts/system/create/qml/NewPolyVoxDialog.qml b/scripts/system/create/qml/NewPolyVoxDialog.qml index 38a8d5d77e3..34cf253ad83 100644 --- a/scripts/system/create/qml/NewPolyVoxDialog.qml +++ b/scripts/system/create/qml/NewPolyVoxDialog.qml @@ -13,7 +13,7 @@ // import QtQuick 2.5 -import QtQuick.Dialogs 1.2 as OriginalDialogs +import QtQuick.Dialogs as OriginalDialogs import stylesUit 1.0 import controlsUit 1.0 diff --git a/scripts/system/create/qml/NewSoundDialog.qml b/scripts/system/create/qml/NewSoundDialog.qml index f43a2707acc..13a8ef345bd 100644 --- a/scripts/system/create/qml/NewSoundDialog.qml +++ b/scripts/system/create/qml/NewSoundDialog.qml @@ -11,7 +11,7 @@ import QtQuick 2.7 import QtQuick.Controls 2.2 -import QtQuick.Dialogs 1.2 as OriginalDialogs +import QtQuick.Dialogs as OriginalDialogs import stylesUit 1.0 import controlsUit 1.0 diff --git a/scripts/system/settings/qml/AdvancedOptions.qml b/scripts/system/settings/qml/AdvancedOptions.qml index c1d38a0c9dd..cbd2bff3c81 100644 --- a/scripts/system/settings/qml/AdvancedOptions.qml +++ b/scripts/system/settings/qml/AdvancedOptions.qml @@ -1,6 +1,6 @@ import QtQuick 2.7 import QtQuick.Controls 2.5 -import QtQuick.Controls.Styles 1.4 +//import QtQuick.Controls.Styles import QtQuick.Layouts 1.3 Item { diff --git a/scripts/system/settings/qml/SettingNumber.qml b/scripts/system/settings/qml/SettingNumber.qml index aa00a3f3c47..e1a05b021b1 100644 --- a/scripts/system/settings/qml/SettingNumber.qml +++ b/scripts/system/settings/qml/SettingNumber.qml @@ -1,6 +1,6 @@ import QtQuick 2.7 import QtQuick.Controls 2.5 -import QtQuick.Controls.Styles 1.4 +//import QtQuick.Controls.Styles import QtQuick.Layouts 1.3 Item { diff --git a/scripts/system/settings/qml/SettingSlider.qml b/scripts/system/settings/qml/SettingSlider.qml index 05093724423..5654e8574b2 100644 --- a/scripts/system/settings/qml/SettingSlider.qml +++ b/scripts/system/settings/qml/SettingSlider.qml @@ -1,6 +1,6 @@ import QtQuick 2.7 import QtQuick.Controls 2.5 -import QtQuick.Controls.Styles 1.4 +//import QtQuick.Controls.Styles import QtQuick.Layouts 1.3 Item { diff --git a/tests-manual/controllers/qml/main.qml b/tests-manual/controllers/qml/main.qml index 66060399a67..085e5ccbc9d 100644 --- a/tests-manual/controllers/qml/main.qml +++ b/tests-manual/controllers/qml/main.qml @@ -1,7 +1,7 @@ import QtQuick 2.1 import QtQuick.Controls 1.0 import QtQuick.Layouts 1.0 -import QtQuick.Dialogs 1.0 +import QtQuick.Dialogs ApplicationWindow { id: window diff --git a/tests-manual/qml/qml/MacQml.qml b/tests-manual/qml/qml/MacQml.qml index 14749bb8264..7df1e23a5f3 100644 --- a/tests-manual/qml/qml/MacQml.qml +++ b/tests-manual/qml/qml/MacQml.qml @@ -50,7 +50,7 @@ import QtQuick 2.2 import QtQuick.Layouts 1.1 -import QtQuick.Dialogs 1.1 +import QtQuick.Dialogs import QtQuick.Controls 1.2 import QtWebEngine 1.5 diff --git a/tests-manual/qml/qml/main.qml b/tests-manual/qml/qml/main.qml index 3b375433939..bd0f9e19fca 100644 --- a/tests-manual/qml/qml/main.qml +++ b/tests-manual/qml/qml/main.qml @@ -50,7 +50,7 @@ import QtQuick 2.2 import QtQuick.Layouts 1.1 -import QtQuick.Dialogs 1.1 +import QtQuick.Dialogs import QtQuick.Controls 1.2 import "qml/UI.js" as UI import "qml" diff --git a/tools/animedit/qml/fields/BooleanField.qml b/tools/animedit/qml/fields/BooleanField.qml index 8ba2b938554..5d2956b549f 100644 --- a/tools/animedit/qml/fields/BooleanField.qml +++ b/tools/animedit/qml/fields/BooleanField.qml @@ -2,7 +2,7 @@ import QtQuick 2.12 import QtQuick.Window 2.12 import QtQuick.Controls 1.6 import QtQuick.Layouts 1.3 -import QtQuick.Dialogs 1.0 +import QtQuick.Dialogs Row { diff --git a/tools/animedit/qml/fields/IdField.qml b/tools/animedit/qml/fields/IdField.qml index b3ee5b6ac98..cc61982bf4a 100644 --- a/tools/animedit/qml/fields/IdField.qml +++ b/tools/animedit/qml/fields/IdField.qml @@ -2,7 +2,7 @@ import QtQuick 2.12 import QtQuick.Window 2.12 import QtQuick.Controls 1.6 import QtQuick.Layouts 1.3 -import QtQuick.Dialogs 1.0 +import QtQuick.Dialogs Row { id: row diff --git a/tools/animedit/qml/fields/JSONField.qml b/tools/animedit/qml/fields/JSONField.qml index f7b16b82cda..8ba5bb6084f 100644 --- a/tools/animedit/qml/fields/JSONField.qml +++ b/tools/animedit/qml/fields/JSONField.qml @@ -2,7 +2,7 @@ import QtQuick 2.12 import QtQuick.Window 2.12 import QtQuick.Controls 1.6 import QtQuick.Layouts 1.3 -import QtQuick.Dialogs 1.0 +import QtQuick.Dialogs Column { diff --git a/tools/animedit/qml/fields/NumberArrayField.qml b/tools/animedit/qml/fields/NumberArrayField.qml index dd393dc2590..dd6661d137a 100644 --- a/tools/animedit/qml/fields/NumberArrayField.qml +++ b/tools/animedit/qml/fields/NumberArrayField.qml @@ -2,7 +2,7 @@ import QtQuick 2.12 import QtQuick.Window 2.12 import QtQuick.Controls 1.6 import QtQuick.Layouts 1.3 -import QtQuick.Dialogs 1.0 +import QtQuick.Dialogs Row { diff --git a/tools/animedit/qml/fields/NumberField.qml b/tools/animedit/qml/fields/NumberField.qml index 88c952d2056..cfbc0a4ebcf 100644 --- a/tools/animedit/qml/fields/NumberField.qml +++ b/tools/animedit/qml/fields/NumberField.qml @@ -2,7 +2,7 @@ import QtQuick 2.12 import QtQuick.Window 2.12 import QtQuick.Controls 1.6 import QtQuick.Layouts 1.3 -import QtQuick.Dialogs 1.0 +import QtQuick.Dialogs Row { diff --git a/tools/animedit/qml/fields/StringField.qml b/tools/animedit/qml/fields/StringField.qml index 7ce5cd4c6c0..ae01bc0a094 100644 --- a/tools/animedit/qml/fields/StringField.qml +++ b/tools/animedit/qml/fields/StringField.qml @@ -2,7 +2,7 @@ import QtQuick 2.12 import QtQuick.Window 2.12 import QtQuick.Controls 1.6 import QtQuick.Layouts 1.3 -import QtQuick.Dialogs 1.0 +import QtQuick.Dialogs Row { diff --git a/tools/animedit/qml/fields/TypeField.qml b/tools/animedit/qml/fields/TypeField.qml index 0a9a7110745..79ee1bd9a56 100644 --- a/tools/animedit/qml/fields/TypeField.qml +++ b/tools/animedit/qml/fields/TypeField.qml @@ -2,7 +2,7 @@ import QtQuick 2.12 import QtQuick.Window 2.12 import QtQuick.Controls 1.6 import QtQuick.Layouts 1.3 -import QtQuick.Dialogs 1.0 +import QtQuick.Dialogs Row { id: row diff --git a/tools/animedit/qml/main.qml b/tools/animedit/qml/main.qml index 2d0c277132f..aaa67b05fae 100644 --- a/tools/animedit/qml/main.qml +++ b/tools/animedit/qml/main.qml @@ -1,8 +1,8 @@ import QtQuick 2.12 import QtQuick.Window 2.12 -import QtQuick.Controls 1.6 -import QtQuick.Layouts 1.3 -import QtQuick.Dialogs 1.0 +import QtQuick.Controls +import QtQuick.Layouts +import QtQuick.Dialogs import "fields" import "nodes" diff --git a/tools/animedit/qml/nodes/BlendDirectional.qml b/tools/animedit/qml/nodes/BlendDirectional.qml index efdea746628..32be0b97ead 100644 --- a/tools/animedit/qml/nodes/BlendDirectional.qml +++ b/tools/animedit/qml/nodes/BlendDirectional.qml @@ -2,7 +2,7 @@ import QtQuick 2.12 import QtQuick.Window 2.12 import QtQuick.Controls 1.6 import QtQuick.Layouts 1.3 -import QtQuick.Dialogs 1.0 +import QtQuick.Dialogs import "../fields" Column { diff --git a/tools/animedit/qml/nodes/BlendLinear.qml b/tools/animedit/qml/nodes/BlendLinear.qml index 5ed15d9ea78..fa55db879be 100644 --- a/tools/animedit/qml/nodes/BlendLinear.qml +++ b/tools/animedit/qml/nodes/BlendLinear.qml @@ -2,7 +2,7 @@ import QtQuick 2.12 import QtQuick.Window 2.12 import QtQuick.Controls 1.6 import QtQuick.Layouts 1.3 -import QtQuick.Dialogs 1.0 +import QtQuick.Dialogs import "../fields" Column { diff --git a/tools/animedit/qml/nodes/BlendLinearMove.qml b/tools/animedit/qml/nodes/BlendLinearMove.qml index cde0c83fd73..b32edb59ac6 100644 --- a/tools/animedit/qml/nodes/BlendLinearMove.qml +++ b/tools/animedit/qml/nodes/BlendLinearMove.qml @@ -2,7 +2,7 @@ import QtQuick 2.12 import QtQuick.Window 2.12 import QtQuick.Controls 1.6 import QtQuick.Layouts 1.3 -import QtQuick.Dialogs 1.0 +import QtQuick.Dialogs import "../fields" Column { diff --git a/tools/animedit/qml/nodes/ClipData.qml b/tools/animedit/qml/nodes/ClipData.qml index 99490643936..c616dba3283 100644 --- a/tools/animedit/qml/nodes/ClipData.qml +++ b/tools/animedit/qml/nodes/ClipData.qml @@ -2,7 +2,7 @@ import QtQuick 2.12 import QtQuick.Window 2.12 import QtQuick.Controls 1.6 import QtQuick.Layouts 1.3 -import QtQuick.Dialogs 1.0 +import QtQuick.Dialogs import "../fields" Column { diff --git a/tools/animedit/qml/nodes/DefaultPose.qml b/tools/animedit/qml/nodes/DefaultPose.qml index eb16dfb0c0c..315629fa151 100644 --- a/tools/animedit/qml/nodes/DefaultPose.qml +++ b/tools/animedit/qml/nodes/DefaultPose.qml @@ -2,7 +2,7 @@ import QtQuick 2.12 import QtQuick.Window 2.12 import QtQuick.Controls 1.6 import QtQuick.Layouts 1.3 -import QtQuick.Dialogs 1.0 +import QtQuick.Dialogs import "../fields" Column { diff --git a/tools/animedit/qml/nodes/InverseKinematics.qml b/tools/animedit/qml/nodes/InverseKinematics.qml index b401869c32e..75395ea8082 100644 --- a/tools/animedit/qml/nodes/InverseKinematics.qml +++ b/tools/animedit/qml/nodes/InverseKinematics.qml @@ -2,7 +2,7 @@ import QtQuick 2.12 import QtQuick.Window 2.12 import QtQuick.Controls 1.6 import QtQuick.Layouts 1.3 -import QtQuick.Dialogs 1.0 +import QtQuick.Dialogs import "../fields" Column { diff --git a/tools/animedit/qml/nodes/Manipulator.qml b/tools/animedit/qml/nodes/Manipulator.qml index 6a651ae73b7..4e2a3fd912d 100644 --- a/tools/animedit/qml/nodes/Manipulator.qml +++ b/tools/animedit/qml/nodes/Manipulator.qml @@ -2,7 +2,7 @@ import QtQuick 2.12 import QtQuick.Window 2.12 import QtQuick.Controls 1.6 import QtQuick.Layouts 1.3 -import QtQuick.Dialogs 1.0 +import QtQuick.Dialogs import "../fields" Column { diff --git a/tools/animedit/qml/nodes/Overlay.qml b/tools/animedit/qml/nodes/Overlay.qml index 96c974d6e55..cb1f1b6990d 100644 --- a/tools/animedit/qml/nodes/Overlay.qml +++ b/tools/animedit/qml/nodes/Overlay.qml @@ -2,7 +2,7 @@ import QtQuick 2.12 import QtQuick.Window 2.12 import QtQuick.Controls 1.6 import QtQuick.Layouts 1.3 -import QtQuick.Dialogs 1.0 +import QtQuick.Dialogs import "../fields" Column { diff --git a/tools/animedit/qml/nodes/PoleVector.qml b/tools/animedit/qml/nodes/PoleVector.qml index bbf5e96a2fa..050cea6ccb9 100644 --- a/tools/animedit/qml/nodes/PoleVector.qml +++ b/tools/animedit/qml/nodes/PoleVector.qml @@ -2,7 +2,7 @@ import QtQuick 2.12 import QtQuick.Window 2.12 import QtQuick.Controls 1.6 import QtQuick.Layouts 1.3 -import QtQuick.Dialogs 1.0 +import QtQuick.Dialogs import "../fields" Column { diff --git a/tools/animedit/qml/nodes/RandomStateMachine.qml b/tools/animedit/qml/nodes/RandomStateMachine.qml index 357fc4b2bf8..a01b3158ca3 100644 --- a/tools/animedit/qml/nodes/RandomStateMachine.qml +++ b/tools/animedit/qml/nodes/RandomStateMachine.qml @@ -2,7 +2,7 @@ import QtQuick 2.12 import QtQuick.Window 2.12 import QtQuick.Controls 1.6 import QtQuick.Layouts 1.3 -import QtQuick.Dialogs 1.0 +import QtQuick.Dialogs import "../fields" Column { diff --git a/tools/animedit/qml/nodes/SplineIK.qml b/tools/animedit/qml/nodes/SplineIK.qml index df1c8931a3f..ceb9ead9297 100644 --- a/tools/animedit/qml/nodes/SplineIK.qml +++ b/tools/animedit/qml/nodes/SplineIK.qml @@ -2,7 +2,7 @@ import QtQuick 2.12 import QtQuick.Window 2.12 import QtQuick.Controls 1.6 import QtQuick.Layouts 1.3 -import QtQuick.Dialogs 1.0 +import QtQuick.Dialogs import "../fields" Column { diff --git a/tools/animedit/qml/nodes/StateMachine.qml b/tools/animedit/qml/nodes/StateMachine.qml index aec4c30d272..8a3ec4de103 100644 --- a/tools/animedit/qml/nodes/StateMachine.qml +++ b/tools/animedit/qml/nodes/StateMachine.qml @@ -2,7 +2,7 @@ import QtQuick 2.12 import QtQuick.Window 2.12 import QtQuick.Controls 1.6 import QtQuick.Layouts 1.3 -import QtQuick.Dialogs 1.0 +import QtQuick.Dialogs import "../fields" Column { diff --git a/tools/animedit/qml/nodes/TwoBoneIK.qml b/tools/animedit/qml/nodes/TwoBoneIK.qml index 6bdae5c1bed..4ea6445861f 100644 --- a/tools/animedit/qml/nodes/TwoBoneIK.qml +++ b/tools/animedit/qml/nodes/TwoBoneIK.qml @@ -2,7 +2,7 @@ import QtQuick 2.12 import QtQuick.Window 2.12 import QtQuick.Controls 1.6 import QtQuick.Layouts 1.3 -import QtQuick.Dialogs 1.0 +import QtQuick.Dialogs import "../fields" Column { diff --git a/unpublishedScripts/marketplace/spectator-camera/SpectatorCamera.qml b/unpublishedScripts/marketplace/spectator-camera/SpectatorCamera.qml index 54839bbd1c0..290a3d1b3a8 100644 --- a/unpublishedScripts/marketplace/spectator-camera/SpectatorCamera.qml +++ b/unpublishedScripts/marketplace/spectator-camera/SpectatorCamera.qml @@ -14,7 +14,7 @@ import Hifi 1.0 as Hifi import QtQuick 2.7 import QtQuick.Controls 2.2 -import QtGraphicalEffects 1.0 +import Qt5Compat.GraphicalEffects import stylesUit 1.0 as HifiStylesUit import controlsUit 1.0 as HifiControlsUit From fa8f6b07f4665879d112921641699d0150afbe1f Mon Sep 17 00:00:00 2001 From: Ada Date: Sun, 7 Sep 2025 15:14:54 +1000 Subject: [PATCH 003/111] Get Qt6 compiling and running, get QML starting --- interface/src/Application_Setup.cpp | 4 +++- interface/src/Menu.cpp | 3 +++ libraries/entities/src/EntityItem.cpp.in | 2 +- libraries/entities/src/EntityItemProperties.cpp.in | 6 ++++-- .../GraphicsScriptingInterface.cpp | 2 +- .../graphics/src/graphics/BufferViewHelpers.h | 2 +- libraries/model-serializers/src/FBXWriter.cpp | 2 +- libraries/networking/src/AssetClient.cpp | 2 +- libraries/networking/src/SocketType.h | 1 + libraries/qml/src/qml/impl/RenderEventHandler.cpp | 14 +++++--------- libraries/qml/src/qml/impl/SharedObject.cpp | 3 ++- libraries/shared/src/Trace.cpp | 2 +- 12 files changed, 24 insertions(+), 19 deletions(-) diff --git a/interface/src/Application_Setup.cpp b/interface/src/Application_Setup.cpp index 9a9ecefd62c..89ada7ef6d2 100644 --- a/interface/src/Application_Setup.cpp +++ b/interface/src/Application_Setup.cpp @@ -1314,7 +1314,9 @@ void Application::initialize(const QCommandLineParser &parser) { qCDebug(interfaceapp) << "Directory Service session ID is" << uuidStringWithoutCurlyBraces(accountManager->getSessionID()); - pauseUntilLoginDetermined(); + // FIXME: This shouldn't be here anymore. It makes debugging harder, and + // messes with settings at startup. (possibly while they're still loading) + //pauseUntilLoginDetermined(); } void Application::init() { diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index 40ba372b896..e30c2d68950 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -98,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, [] { @@ -108,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); diff --git a/libraries/entities/src/EntityItem.cpp.in b/libraries/entities/src/EntityItem.cpp.in index 93503843a2e..73b50f3beb0 100644 --- a/libraries/entities/src/EntityItem.cpp.in +++ b/libraries/entities/src/EntityItem.cpp.in @@ -1834,7 +1834,7 @@ QString EntityItem::actionsToDebugString() { const QUuid id = i.key(); EntityDynamicPointer action = _objectActions[id]; EntityDynamicType actionType = action->getType(); - result += QString("") + actionType + ":" + action->getID().toString() + " "; + result += QString("%1: %2 ").arg(actionType).arg(action->getID().toString()); i++; } return result; diff --git a/libraries/entities/src/EntityItemProperties.cpp.in b/libraries/entities/src/EntityItemProperties.cpp.in index 4af4207dfb0..84eb235f344 100644 --- a/libraries/entities/src/EntityItemProperties.cpp.in +++ b/libraries/entities/src/EntityItemProperties.cpp.in @@ -22,6 +22,7 @@ #include #include #include +#include #include #include #include @@ -469,7 +470,7 @@ QString EntityItemProperties::getCollisionMaskAsString() const { } void EntityItemProperties::setCollisionMaskFromString(const QString& maskString) { - QVector groups = maskString.splitRef(','); + auto groups = QStringRef(&maskString).split(','); uint16_t mask = 0x0000; for (auto groupName : groups) { mask |= getCollisionGroupAsBitMask(groupName); @@ -996,7 +997,8 @@ OctreeElement::AppendState EntityItemProperties::encodeEntityEditPacket(PacketTy int finalizedSize = packetData->getFinalizedSize(); if (finalizedSize <= buffer.size()) { - buffer.replace(0, finalizedSize, finalizedData, finalizedSize); + // QByteArray::replace allows both qsizetype and char* here, so force it to int + buffer.replace(static_cast(0), finalizedSize, finalizedData, finalizedSize); buffer.resize(finalizedSize); } else { qCDebug(entities) << "ERROR - encoded edit message doesn't fit in output buffer."; diff --git a/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingInterface.cpp b/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingInterface.cpp index 24486dcd614..b839b1f1606 100644 --- a/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingInterface.cpp +++ b/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingInterface.cpp @@ -265,7 +265,7 @@ scriptable::ScriptableMeshPointer GraphicsScriptingInterface::newMesh(const QVar if (texCoords0.size()) { mesh->addAttribute(gpu::Stream::TEXCOORD0, buffer_helpers::newFromVector(gpu::Buffer::VertexBuffer, texCoords0, gpu::Format::VEC2F_UV)); } - QVector parts = {{ 0, indices.size(), 0, topology }}; + QVector parts = {graphics::Mesh::Part(0, indices.size(), 0, topology)}; mesh->setPartBuffer(buffer_helpers::newFromVector(gpu::Buffer::IndirectBuffer, parts, gpu::Element::PART_DRAWCALL)); return scriptable::make_scriptowned(mesh, nullptr); } diff --git a/libraries/graphics/src/graphics/BufferViewHelpers.h b/libraries/graphics/src/graphics/BufferViewHelpers.h index 1a130672558..74738cff260 100644 --- a/libraries/graphics/src/graphics/BufferViewHelpers.h +++ b/libraries/graphics/src/graphics/BufferViewHelpers.h @@ -47,7 +47,7 @@ namespace buffer_helpers { template const T glmVecFromVariant(const QVariant& v) { - auto isMap = v.type() == (QVariant::Type)QMetaType::QVariantMap; + auto isMap = v.typeId() == QMetaType::QVariantMap; static const auto len = T().length(); const auto& components = isMap ? XYZW : ZERO123; T result; diff --git a/libraries/model-serializers/src/FBXWriter.cpp b/libraries/model-serializers/src/FBXWriter.cpp index 095d4073439..05708ec9391 100644 --- a/libraries/model-serializers/src/FBXWriter.cpp +++ b/libraries/model-serializers/src/FBXWriter.cpp @@ -132,7 +132,7 @@ void FBXWriter::encodeFBXProperty(QDataStream& out, const QVariant& prop) { out << prop.value(); break; - case QVariant::Type::Bool: + case QMetaType::Bool: out.device()->write("C", 1); out << prop.toBool(); break; diff --git a/libraries/networking/src/AssetClient.cpp b/libraries/networking/src/AssetClient.cpp index 4ae347bb77a..36dbbaab9fb 100644 --- a/libraries/networking/src/AssetClient.cpp +++ b/libraries/networking/src/AssetClient.cpp @@ -222,7 +222,7 @@ namespace { if (!dt.isValid()) { qDebug() << __FUNCTION__ << "unrecognized date format:" << dateString; } - dt.setTimeSpec(Qt::UTC); + dt.setTimeZone(QTimeZone::utc()); return dt; } QDateTime getHttpDateValue(const QVariantMap& headers, const QString& keyName, const QDateTime& defaultValue) { diff --git a/libraries/networking/src/SocketType.h b/libraries/networking/src/SocketType.h index 33da7c4ea03..30432eb2381 100644 --- a/libraries/networking/src/SocketType.h +++ b/libraries/networking/src/SocketType.h @@ -13,6 +13,7 @@ #ifndef overte_SocketType_h #define overte_SocketType_h +#include /// @addtogroup Networking /// @{ diff --git a/libraries/qml/src/qml/impl/RenderEventHandler.cpp b/libraries/qml/src/qml/impl/RenderEventHandler.cpp index 95f4abfe5de..f299e8ef540 100644 --- a/libraries/qml/src/qml/impl/RenderEventHandler.cpp +++ b/libraries/qml/src/qml/impl/RenderEventHandler.cpp @@ -130,6 +130,9 @@ void RenderEventHandler::qmlRender(bool sceneGraphSync) { PROFILE_RANGE(render_qml_gl, __FUNCTION__); gl::globalLock(); + + _shared->_renderControl->beginFrame(); + if (!_shared->preRender(sceneGraphSync)) { gl::globalRelease(); return; @@ -148,16 +151,9 @@ void RenderEventHandler::qmlRender(bool sceneGraphSync) { } else { glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); _shared->setRenderTarget(_fbo, _currentSize); - - // workaround for https://highfidelity.atlassian.net/browse/BUGZ-1119 - { - // Serialize QML rendering because of a crash caused by Qt bug - // https://bugreports.qt.io/browse/QTBUG-77469 - static std::mutex qmlRenderMutex; - std::unique_lock qmlRenderLock{ qmlRenderMutex }; - _shared->_renderControl->render(); - } + _shared->_renderControl->render(); } + _shared->_renderControl->endFrame(); _shared->_lastRenderTime = usecTimestampNow(); glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); glBindTexture(GL_TEXTURE_2D, texture); diff --git a/libraries/qml/src/qml/impl/SharedObject.cpp b/libraries/qml/src/qml/impl/SharedObject.cpp index 6ac567d4df4..e88f9aed7f1 100644 --- a/libraries/qml/src/qml/impl/SharedObject.cpp +++ b/libraries/qml/src/qml/impl/SharedObject.cpp @@ -14,6 +14,7 @@ #include #include #include +#include #include #include @@ -295,7 +296,7 @@ void SharedObject::initializeRenderControl(QOpenGLContext* context) { #ifndef DISABLE_QML if (!nsightActive()) { - // QT6TODO: Qt6 does not accept context as parameter here. I'm not sure if it's a problem. + _renderControl->window()->setGraphicsDevice(QQuickGraphicsDevice::fromOpenGLContext(context)); _renderControl->initialize(); } #endif diff --git a/libraries/shared/src/Trace.cpp b/libraries/shared/src/Trace.cpp index e9e77b55ae2..0cdf0096043 100644 --- a/libraries/shared/src/Trace.cpp +++ b/libraries/shared/src/Trace.cpp @@ -84,7 +84,7 @@ void TraceEvent::writeJson(QTextStream& out) const { QJsonObject ev { { "name", QJsonValue(name) }, { "cat", category.categoryName() }, - { "ph", QString(type) }, + { "ph", QString(static_cast(type)) }, { "ts", timestamp }, { "pid", processID }, { "tid", threadID } From 510754f01d11202f6a8e7fc5c69f8f2d28561abc Mon Sep 17 00:00:00 2001 From: Ada Date: Sun, 7 Sep 2025 17:14:05 +1000 Subject: [PATCH 004/111] Comment out enough QML to get the desktop starting, though framebuffers broken --- .../qml/+webengine/BrowserWebView.qml | 31 ++++----- interface/resources/qml/Browser.qml | 10 +-- interface/resources/qml/Web3DSurface.qml | 4 +- .../+webengine/FlickableWebViewCore.qml | 52 +++++++-------- .../resources/qml/controlsUit/WebView.qml | 5 +- interface/resources/qml/desktop/Desktop.qml | 13 ++-- .../resources/qml/dialogs/FileDialog.qml | 65 ++++++++++--------- .../qml/dialogs/TabletFileDialog.qml | 17 +++-- interface/resources/qml/hifi/Desktop.qml | 7 +- .../resources/qml/hifi/tablet/TabletRoot.qml | 10 +-- .../resources/qml/hifi/tablet/WindowRoot.qml | 3 +- .../resources/qml/hifi/toolbars/Toolbar.qml | 2 +- interface/src/Application_Setup.cpp | 4 +- .../qml/src/qml/impl/RenderEventHandler.cpp | 6 +- libraries/qml/src/qml/impl/SharedObject.cpp | 2 +- 15 files changed, 115 insertions(+), 116 deletions(-) diff --git a/interface/resources/qml/+webengine/BrowserWebView.qml b/interface/resources/qml/+webengine/BrowserWebView.qml index 291859c3ec6..509454730ae 100644 --- a/interface/resources/qml/+webengine/BrowserWebView.qml +++ b/interface/resources/qml/+webengine/BrowserWebView.qml @@ -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/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/Web3DSurface.qml b/interface/resources/qml/Web3DSurface.qml index 3380eabeac1..37464cfecee 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: "" @@ -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 9a08a3d0f8a..468a33509d2 100644 --- a/interface/resources/qml/controls/+webengine/FlickableWebViewCore.qml +++ b/interface/resources/qml/controls/+webengine/FlickableWebViewCore.qml @@ -112,31 +112,28 @@ 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 ] + 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: { webChannel.registerObject("eventBridge", eventBridge); @@ -172,9 +169,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 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 e21d7a65490..c2845fe162e 100644 --- a/interface/resources/qml/desktop/Desktop.qml +++ b/interface/resources/qml/desktop/Desktop.qml @@ -10,7 +10,6 @@ import QtQuick 2.7 import QtQuick.Controls 2.3 -import QtQuick.Controls 2.3 as QQC2 import "../dialogs" import "../js/Utils.js" as Utils @@ -519,27 +518,27 @@ FocusScope { ensureTitleBarVisible(targetWindow); } - Component { id: messageDialogBuilder; MessageDialog { } } + Component { id: messageDialogBuilder; Item {}}//MessageDialog { } } function messageBox(properties) { return messageDialogBuilder.createObject(desktop, properties); } - Component { id: inputDialogBuilder; QueryDialog { } } + Component { id: inputDialogBuilder; Item {}}//QueryDialog { } } function inputDialog(properties) { return inputDialogBuilder.createObject(desktop, properties); } - Component { id: customInputDialogBuilder; CustomQueryDialog { } } + Component { id: customInputDialogBuilder; Item {}}//CustomQueryDialog { } } function customInputDialog(properties) { return customInputDialogBuilder.createObject(desktop, properties); } - Component { id: fileDialogBuilder; FileDialog { } } + Component { id: fileDialogBuilder; Item {}}//FileDialog { } } function fileDialog(properties) { return fileDialogBuilder.createObject(desktop, properties); } - Component { id: assetDialogBuilder; AssetDialog { } } + Component { id: assetDialogBuilder; Item {}}//AssetDialog { } } function assetDialog(properties) { return assetDialogBuilder.createObject(desktop, properties); } @@ -584,7 +583,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/FileDialog.qml b/interface/resources/qml/dialogs/FileDialog.qml index 6141bbc37de..cee5c924e9a 100644 --- a/interface/resources/qml/dialogs/FileDialog.qml +++ b/interface/resources/qml/dialogs/FileDialog.qml @@ -12,7 +12,6 @@ import QtQuick 2.7 import Qt.labs.folderlistmodel 2.2 import Qt.labs.settings 1.0 import QtQuick.Dialogs as OriginalDialogs -import QtQuick.Controls 2.3 as QQC1 import QtQuick.Controls 2.3 import ".." @@ -496,7 +495,7 @@ ModalWindow { } } - Table { + TableView { id: fileTableView colorScheme: hifi.colorSchemes.light anchors { @@ -508,13 +507,13 @@ ModalWindow { bottomMargin: hifi.dimensions.contentSpacing.y + currentSelection.controlHeight - currentSelection.height } headerVisible: !selectDirectory - onDoubleClicked: navigateToRow(row); + //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 model: filesModel @@ -528,7 +527,7 @@ ModalWindow { onSortIndicatorOrderChanged: { updateSort(); } - itemDelegate: Item { + delegate: Item { clip: true FiraSansSemiBold { @@ -573,31 +572,33 @@ ModalWindow { } } - 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; diff --git a/interface/resources/qml/dialogs/TabletFileDialog.qml b/interface/resources/qml/dialogs/TabletFileDialog.qml index 222596be268..a5f912d367f 100644 --- a/interface/resources/qml/dialogs/TabletFileDialog.qml +++ b/interface/resources/qml/dialogs/TabletFileDialog.qml @@ -12,7 +12,6 @@ import QtQuick 2.7 import Qt.labs.folderlistmodel 2.2 import Qt.labs.settings 1.0 import QtQuick.Dialogs as OriginalDialogs -import QtQuick.Controls 2.3 as QQC1 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/hifi/Desktop.qml b/interface/resources/qml/hifi/Desktop.qml index 3239471a00f..13d58b8c879 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,6 +12,9 @@ import controlsUit 1.0 OriginalDesktop.Desktop { id: desktop + // Qt6 TODO: Nothing renders yet, and the desktop just eats all mouse inputs + visible: false + property alias toolbarObjectName: sysToolbar.objectName MouseArea { @@ -79,7 +82,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/tablet/TabletRoot.qml b/interface/resources/qml/hifi/tablet/TabletRoot.qml index 5e90696adc6..201c4e6a523 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; diff --git a/interface/resources/qml/hifi/tablet/WindowRoot.qml b/interface/resources/qml/hifi/tablet/WindowRoot.qml index dcdc711936e..eed07e5e3d3 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" 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/src/Application_Setup.cpp b/interface/src/Application_Setup.cpp index 89ada7ef6d2..9a9ecefd62c 100644 --- a/interface/src/Application_Setup.cpp +++ b/interface/src/Application_Setup.cpp @@ -1314,9 +1314,7 @@ void Application::initialize(const QCommandLineParser &parser) { qCDebug(interfaceapp) << "Directory Service session ID is" << uuidStringWithoutCurlyBraces(accountManager->getSessionID()); - // FIXME: This shouldn't be here anymore. It makes debugging harder, and - // messes with settings at startup. (possibly while they're still loading) - //pauseUntilLoginDetermined(); + pauseUntilLoginDetermined(); } void Application::init() { diff --git a/libraries/qml/src/qml/impl/RenderEventHandler.cpp b/libraries/qml/src/qml/impl/RenderEventHandler.cpp index f299e8ef540..5b06db3ff61 100644 --- a/libraries/qml/src/qml/impl/RenderEventHandler.cpp +++ b/libraries/qml/src/qml/impl/RenderEventHandler.cpp @@ -131,6 +131,7 @@ void RenderEventHandler::qmlRender(bool sceneGraphSync) { gl::globalLock(); + _shared->_renderControl->polishItems(); _shared->_renderControl->beginFrame(); if (!_shared->preRender(sceneGraphSync)) { @@ -151,7 +152,10 @@ void RenderEventHandler::qmlRender(bool sceneGraphSync) { } else { glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); _shared->setRenderTarget(_fbo, _currentSize); - _shared->_renderControl->render(); + // Qt6 TODO: Qt says that it doesn't have a valid render target + // on the deferred renderer, and on the forward one it just makes + // the screen gray as if the size hasn't been set properly + //_shared->_renderControl->render(); } _shared->_renderControl->endFrame(); _shared->_lastRenderTime = usecTimestampNow(); diff --git a/libraries/qml/src/qml/impl/SharedObject.cpp b/libraries/qml/src/qml/impl/SharedObject.cpp index e88f9aed7f1..73a0413e851 100644 --- a/libraries/qml/src/qml/impl/SharedObject.cpp +++ b/libraries/qml/src/qml/impl/SharedObject.cpp @@ -296,7 +296,7 @@ void SharedObject::initializeRenderControl(QOpenGLContext* context) { #ifndef DISABLE_QML if (!nsightActive()) { - _renderControl->window()->setGraphicsDevice(QQuickGraphicsDevice::fromOpenGLContext(context)); + _quickWindow->setGraphicsDevice(QQuickGraphicsDevice::fromOpenGLContext(context)); _renderControl->initialize(); } #endif From bd892bc36a8c0be458e34053e041bedeeb5b70cd Mon Sep 17 00:00:00 2001 From: ksuprynowicz Date: Wed, 10 Sep 2025 00:09:52 +0200 Subject: [PATCH 005/111] VrMenu fixes for Qt6, part 1 --- .../resources/qml/controls/WrappedMenu.qml | 10 +++++ interface/resources/qml/desktop/Desktop.qml | 4 ++ libraries/ui/src/VrMenu.cpp | 39 ++++++++++++++----- 3 files changed, 43 insertions(+), 10 deletions(-) create mode 100644 interface/resources/qml/controls/WrappedMenu.qml diff --git a/interface/resources/qml/controls/WrappedMenu.qml b/interface/resources/qml/controls/WrappedMenu.qml new file mode 100644 index 00000000000..7a74dc7662e --- /dev/null +++ b/interface/resources/qml/controls/WrappedMenu.qml @@ -0,0 +1,10 @@ +import QtQuick.Controls + +Menu { + id: wrappedMenu + objectName: "wrappedMenu" + + function addMenuWrap(menu) { + return addMenu(menu); + } +} \ No newline at end of file diff --git a/interface/resources/qml/desktop/Desktop.qml b/interface/resources/qml/desktop/Desktop.qml index c2845fe162e..979777df16f 100644 --- a/interface/resources/qml/desktop/Desktop.qml +++ b/interface/resources/qml/desktop/Desktop.qml @@ -61,6 +61,10 @@ FocusScope { } } + function addMenuWrap(menu) { + return addMenu(menu); + } + function addExclusionGroup(qmlAction, exclusionGroup) { var exclusionGroupId = exclusionGroup.toString(); diff --git a/libraries/ui/src/VrMenu.cpp b/libraries/ui/src/VrMenu.cpp index c29a71245b7..6b1e840dd23 100644 --- a/libraries/ui/src/VrMenu.cpp +++ b/libraries/ui/src/VrMenu.cpp @@ -143,6 +143,9 @@ QObject* VrMenu::findMenuObject(const QString& menuOption) { void VrMenu::addMenu(QMenu* menu) { + auto *ui = dynamic_cast(parent()); + Q_ASSERT(ui); + QQmlEngine *engine = ui->getSurfaceContext()->engine(); Q_ASSERT(!MenuUserData::hasData(menu->menuAction())); QObject* parent = menu->parent(); QObject* qmlParent = nullptr; @@ -159,20 +162,35 @@ void VrMenu::addMenu(QMenu* menu) { Q_ASSERT(false); } QVariant returnedValue; - bool invokeResult = QMetaObject::invokeMethod(qmlParent, "addMenu", Qt::DirectConnection, - Q_RETURN_ARG(QVariant, returnedValue), - Q_ARG(QVariant, QVariant::fromValue(menu->title()))); - Q_ASSERT(invokeResult); - Q_UNUSED(invokeResult); // FIXME - apparently we haven't upgraded the Qt on our unix Jenkins environments to 5.5.x - QObject* result = returnedValue.value(); - Q_ASSERT(result); - if (!result) { - qWarning() << "Unable to create QML menu for widget menu: " << menu->title(); + // QT6TODO: move to constructor, we need to load the component only once + QQmlComponent menuComponent(engine); + //menuComponent.loadFromModule("QtQuick.Controls", "Menu"); + menuComponent.loadUrl(PathUtils::qmlUrl("controls/WrappedMenu.qml")); + if (menuComponent.status() == QQmlComponent::Status::Error) { + qDebug() << "QML Menu component error: " << menuComponent.errorString(); return; } + Q_ASSERT(menuComponent.isReady()); + // QT6TODO: what deletes the item later? + QObject *menuObject = menuComponent.create(ui->getSurfaceContext()); + menuObject->setObjectName(menu->title()); + menuObject->setProperty("title", menu->title()); + menuObject->setParent(qmlParent); + qDebug() << "menuObject: " << QString(menuObject->metaObject()->metaType().name()); + bool invokeResult = QMetaObject::invokeMethod(qmlParent, "addMenuWrap", Qt::DirectConnection, + Q_ARG(QVariant, QVariant::fromValue(menuObject))); + Q_ASSERT(invokeResult); + Q_UNUSED(invokeResult); // FIXME - apparently we haven't upgraded the Qt on our unix Jenkins environments to 5.5.x + //QObject* result = returnedValue.value(); + //qDebug() << "menuObject: " << QString(returnedValue.metaType().name()); + //Q_ASSERT(result); + //if (!result) { + // qWarning() << "Unable to create QML menu for widget menu: " << menu->title(); + // return; + //} // Bind the QML and Widget together - new MenuUserData(menu->menuAction(), result, qmlParent); + new MenuUserData(menu->menuAction(), menuObject, qmlParent); } void bindActionToQmlAction(QObject* qmlAction, QAction* action, QObject* qmlParent) { @@ -255,6 +273,7 @@ void VrMenu::insertAction(QAction* before, QAction* action) { class QQuickMenuBase; class QQuickMenu1; +class QQuickMenu; void VrMenu::removeAction(QAction* action) { if (!action) { From db3589cd546d955c94cf7e474f87ac3bd9f3407a Mon Sep 17 00:00:00 2001 From: Karol Suprynowicz Date: Sun, 14 Sep 2025 20:10:52 +0200 Subject: [PATCH 006/111] VRMenu fixes for Qt6 --- .../resources/qml/controls/WrappedMenu.qml | 3 + interface/resources/qml/desktop/Desktop.qml | 7 +- .../resources/qml/hifi/tablet/TabletMenu.qml | 2 +- libraries/ui/src/VrMenu.cpp | 103 +++++++++++++----- 4 files changed, 85 insertions(+), 30 deletions(-) diff --git a/interface/resources/qml/controls/WrappedMenu.qml b/interface/resources/qml/controls/WrappedMenu.qml index 7a74dc7662e..43fc2d438e3 100644 --- a/interface/resources/qml/controls/WrappedMenu.qml +++ b/interface/resources/qml/controls/WrappedMenu.qml @@ -7,4 +7,7 @@ Menu { function addMenuWrap(menu) { return addMenu(menu); } + function addItemWrap(item) { + addItem(item); + } } \ No newline at end of file diff --git a/interface/resources/qml/desktop/Desktop.qml b/interface/resources/qml/desktop/Desktop.qml index 979777df16f..6ea6108f009 100644 --- a/interface/resources/qml/desktop/Desktop.qml +++ b/interface/resources/qml/desktop/Desktop.qml @@ -8,11 +8,12 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -import QtQuick 2.7 -import QtQuick.Controls 2.3 +import QtQuick +import QtQuick.Controls import "../dialogs" import "../js/Utils.js" as Utils +import "../controls" as OverteControls // This is our primary 'desktop' object to which all VR dialogs and windows are childed. FocusScope { @@ -50,7 +51,7 @@ 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" diff --git a/interface/resources/qml/hifi/tablet/TabletMenu.qml b/interface/resources/qml/hifi/tablet/TabletMenu.qml index d8d88272a41..bc97b515c7d 100644 --- a/interface/resources/qml/hifi/tablet/TabletMenu.qml +++ b/interface/resources/qml/hifi/tablet/TabletMenu.qml @@ -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/libraries/ui/src/VrMenu.cpp b/libraries/ui/src/VrMenu.cpp index 6b1e840dd23..1f03f2bae9a 100644 --- a/libraries/ui/src/VrMenu.cpp +++ b/libraries/ui/src/VrMenu.cpp @@ -173,6 +173,7 @@ void VrMenu::addMenu(QMenu* menu) { Q_ASSERT(menuComponent.isReady()); // QT6TODO: what deletes the item later? QObject *menuObject = menuComponent.create(ui->getSurfaceContext()); + Q_ASSERT(menuObject); menuObject->setObjectName(menu->title()); menuObject->setProperty("title", menu->title()); menuObject->setParent(qmlParent); @@ -180,14 +181,7 @@ void VrMenu::addMenu(QMenu* menu) { bool invokeResult = QMetaObject::invokeMethod(qmlParent, "addMenuWrap", Qt::DirectConnection, Q_ARG(QVariant, QVariant::fromValue(menuObject))); Q_ASSERT(invokeResult); - Q_UNUSED(invokeResult); // FIXME - apparently we haven't upgraded the Qt on our unix Jenkins environments to 5.5.x - //QObject* result = returnedValue.value(); - //qDebug() << "menuObject: " << QString(returnedValue.metaType().name()); - //Q_ASSERT(result); - //if (!result) { - // qWarning() << "Unable to create QML menu for widget menu: " << menu->title(); - // return; - //} + Q_UNUSED(invokeResult); // Bind the QML and Widget together new MenuUserData(menu->menuAction(), menuObject, qmlParent); @@ -209,6 +203,10 @@ void bindActionToQmlAction(QObject* qmlAction, QAction* action, QObject* qmlPare class QQuickMenuItem1; void VrMenu::addAction(QMenu* menu, QAction* action) { + auto *ui = dynamic_cast(parent()); + Q_ASSERT(ui); + QQmlEngine *engine = ui->getSurfaceContext()->engine(); + Q_ASSERT(!MenuUserData::hasData(action)); Q_ASSERT(MenuUserData::hasData(menu->menuAction())); @@ -218,20 +216,38 @@ void VrMenu::addAction(QMenu* menu, QAction* action) { } QObject* menuQml = findMenuObject(userData->uuid.toString()); Q_ASSERT(menuQml); - QQuickMenuItem1* returnedValue { nullptr }; - bool invokeResult = QMetaObject::invokeMethod(menuQml, "addItem", Qt::DirectConnection, - Q_RETURN_ARG(QQuickMenuItem1*, returnedValue), - Q_ARG(QString, action->text())); + qDebug() << "VrMenu::addAction menuQml " << menuQml->objectName(); + qDebug() << "VrMenu::addAction menuQml type " << menuQml->metaObject()->className(); + + QQmlComponent menuItemComponent(engine); + menuItemComponent.loadFromModule("QtQuick.Controls", "MenuItem"); + + if (menuItemComponent.status() == QQmlComponent::Status::Error) { + qDebug() << "QML Menu component error: " << menuItemComponent.errorString(); + return; + } + Q_ASSERT(menuItemComponent.isReady()); + // QT6TODO: I think parent deletes item later? Are there extra options for ownership? + QObject *menuItemObject = menuItemComponent.create(ui->getSurfaceContext()); + Q_ASSERT(menuItemObject); + menuItemObject->setObjectName(action->text()); + menuItemObject->setProperty("text", action->text()); + menuItemObject->setParent(menuQml); + + bool invokeResult = QMetaObject::invokeMethod(menuQml, "addItemWrap", Qt::DirectConnection, + Q_ARG(QVariant, QVariant::fromValue(menuItemObject))); Q_ASSERT(invokeResult); - Q_UNUSED(invokeResult); // FIXME - apparently we haven't upgraded the Qt on our unix Jenkins environments to 5.5.x - QObject* result = reinterpret_cast(returnedValue); // returnedValue.value(); - Q_ASSERT(result); + Q_UNUSED(invokeResult); // Bind the QML and Widget together - bindActionToQmlAction(result, action, _rootMenu); + bindActionToQmlAction(menuItemObject, action, _rootMenu); } void VrMenu::addSeparator(QMenu* menu) { + auto *ui = dynamic_cast(parent()); + Q_ASSERT(ui); + QQmlEngine *engine = ui->getSurfaceContext()->engine(); + Q_ASSERT(MenuUserData::hasData(menu->menuAction())); MenuUserData* userData = MenuUserData::forObject(menu->menuAction()); if (!userData) { @@ -240,12 +256,31 @@ void VrMenu::addSeparator(QMenu* menu) { QObject* menuQml = findMenuObject(userData->uuid.toString()); Q_ASSERT(menuQml); - bool invokeResult = QMetaObject::invokeMethod(menuQml, "addSeparator", Qt::DirectConnection); + QQmlComponent menuSeparatorComponent(engine); + menuSeparatorComponent.loadFromModule("QtQuick.Controls", "MenuSeparator"); + + if (menuSeparatorComponent.status() == QQmlComponent::Status::Error) { + qDebug() << "QML Menu component error: " << menuSeparatorComponent.errorString(); + return; + } + Q_ASSERT(menuSeparatorComponent.isReady()); + QObject *menuSeparatorObject = menuSeparatorComponent.create(ui->getSurfaceContext()); + Q_ASSERT(menuSeparatorObject); + menuSeparatorObject->setParent(menuQml); + qDebug() << "VrMenu::addSeparator menuQml " << menuQml->objectName(); + qDebug() << "VrMenu::addSeparator menuQml type " << menuQml->metaObject()->className(); + + bool invokeResult = QMetaObject::invokeMethod(menuQml, "addItemWrap", Qt::DirectConnection, + Q_ARG(QVariant, QVariant::fromValue(menuSeparatorObject))); Q_ASSERT(invokeResult); - Q_UNUSED(invokeResult); // FIXME - apparently we haven't upgraded the Qt on our unix Jenkins environments to 5.5.x + Q_UNUSED(invokeResult); } void VrMenu::insertAction(QAction* before, QAction* action) { + auto *ui = dynamic_cast(parent()); + Q_ASSERT(ui); + QQmlEngine *engine = ui->getSurfaceContext()->engine(); + QObject* beforeQml{ nullptr }; { MenuUserData* beforeUserData = MenuUserData::forObject(before); @@ -256,16 +291,32 @@ void VrMenu::insertAction(QAction* before, QAction* action) { beforeQml = findMenuObject(beforeUserData->uuid.toString()); } QObject* menu = beforeQml->parent(); - QQuickMenuItem1* returnedValue { nullptr }; - // FIXME this needs to find the index of the beforeQml item and call insertItem(int, object) + Q_ASSERT(menu); + + // QT6TODO: move to constructor, we need to load the component only once + QQmlComponent menuItemComponent(engine); + menuItemComponent.loadFromModule("QtQuick.Controls", "MenuItem"); + //menuComponent.loadUrl(PathUtils::qmlUrl("controls/WrappedMenu.qml")); + if (menuItemComponent.status() == QQmlComponent::Status::Error) { + qDebug() << "QML Menu component error: " << menuItemComponent.errorString(); + return; + } + Q_ASSERT(menuItemComponent.isReady()); + // QT6TODO: what deletes the item later? + QObject *menuItemObject = menuItemComponent.create(ui->getSurfaceContext()); + Q_ASSERT(menuItemObject); + menuItemObject->setObjectName(action->text()); + menuItemObject->setProperty("text", action->text()); + menuItemObject->setParent(menu); + + qDebug() << "menuObject: " << QString(menuItemObject->metaObject()->metaType().name()); bool invokeResult = QMetaObject::invokeMethod(menu, "addItem", Qt::DirectConnection, - Q_RETURN_ARG(QQuickMenuItem1*, returnedValue), - Q_ARG(QString, action->text())); + Q_ARG(QVariant, QVariant::fromValue(menuItemObject))); + // FIXME this needs to find the index of the beforeQml item and call insertItem(int, object) Q_ASSERT(invokeResult); - QObject* result = reinterpret_cast(returnedValue); // returnedValue.value(); - Q_ASSERT(result); - if ( result ) { - bindActionToQmlAction(result, action, _rootMenu); + + if (menuItemObject) { + bindActionToQmlAction(menuItemObject, action, _rootMenu); } else { qWarning() << "Failed to find addItem() method in object " << menu << ". Not inserting action " << action; } From 50a5d05337362f6dac5905a5bf02bd16daf6cc67 Mon Sep 17 00:00:00 2001 From: Karol Suprynowicz Date: Sun, 14 Sep 2025 23:15:50 +0200 Subject: [PATCH 007/111] QQuickWebEngineView fixes for Qt6 --- .../qml/+webengine/BrowserWebView.qml | 8 ++-- .../qml/+webengine/QmlWebWindowView.qml | 10 ++-- .../qml/+webengine/TabletBrowser.qml | 10 ++-- .../+webengine/FlickableWebViewCore.qml | 46 +++++++++++++++---- 4 files changed, 50 insertions(+), 24 deletions(-) diff --git a/interface/resources/qml/+webengine/BrowserWebView.qml b/interface/resources/qml/+webengine/BrowserWebView.qml index 509454730ae..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 diff --git a/interface/resources/qml/+webengine/QmlWebWindowView.qml b/interface/resources/qml/+webengine/QmlWebWindowView.qml index 84ab61ad28b..fa030564031 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" 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/controls/+webengine/FlickableWebViewCore.qml b/interface/resources/qml/controls/+webengine/FlickableWebViewCore.qml index 468a33509d2..b8c9cc03ef6 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 @@ -112,9 +112,12 @@ Item { settings.touchIconsEnabled: true settings.allowRunningInsecureContent: true - userScripts.collection: [ + // 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, @@ -132,10 +135,33 @@ Item { 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); From 2f6f4dd6c8a9972fab809743101b01a8aa59cd81 Mon Sep 17 00:00:00 2001 From: Karol Suprynowicz Date: Fri, 19 Sep 2025 12:26:06 +0200 Subject: [PATCH 008/111] Fix QML OpenGL contexts --- interface/CMakeLists.txt | 2 +- interface/src/Application_Graphics.cpp | 3 +++ .../src/scripting/DesktopScriptingInterface.cpp | 2 ++ libraries/gl/src/gl/GLWidget.cpp | 4 +++- .../src/model-baker/ParseFlowDataTask.cpp | 1 + .../src/model-baker/ParseMaterialMappingTask.cpp | 3 +++ libraries/qml/src/qml/impl/RenderEventHandler.cpp | 4 ++++ libraries/qml/src/qml/impl/SharedObject.cpp | 13 ++++++++++--- libraries/ui/src/VrMenu.cpp | 4 ++-- 9 files changed, 29 insertions(+), 7 deletions(-) diff --git a/interface/CMakeLists.txt b/interface/CMakeLists.txt index 49a4cd90aa9..bbbde87f8fa 100644 --- a/interface/CMakeLists.txt +++ b/interface/CMakeLists.txt @@ -297,7 +297,7 @@ endif () target_link_libraries( ${TARGET_NAME} - Qt6::Gui Qt6::Network Qt6::Multimedia Qt6::Widgets + 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/src/Application_Graphics.cpp b/interface/src/Application_Graphics.cpp index a660b319887..9495b96b24a 100644 --- a/interface/src/Application_Graphics.cpp +++ b/interface/src/Application_Graphics.cpp @@ -58,6 +58,8 @@ #include "Menu.h" #include "webbrowser/WebBrowserSuggestionsEngine.h" +#include + #if defined(Q_OS_ANDROID) #include "AndroidHelper.h" #endif @@ -165,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"); diff --git a/interface/src/scripting/DesktopScriptingInterface.cpp b/interface/src/scripting/DesktopScriptingInterface.cpp index ab45d0248f9..eaeef605500 100644 --- a/interface/src/scripting/DesktopScriptingInterface.cpp +++ b/interface/src/scripting/DesktopScriptingInterface.cpp @@ -116,6 +116,7 @@ void DesktopScriptingInterface::show(const QString& path, const QString& title) InteractiveWindowPointer DesktopScriptingInterface::createWindow(const QString& sourceUrl, const QVariantMap& properties) { if (QThread::currentThread() != thread()) { + qDebug() << "DesktopScriptingInterface::createWindow" << sourceUrl; InteractiveWindowPointer interactiveWindow = nullptr; BLOCKING_INVOKE_METHOD(this, "createWindowOnThread", Q_GENERIC_RETURN_ARG(InteractiveWindowPointer, interactiveWindow), @@ -141,6 +142,7 @@ InteractiveWindowPointer DesktopScriptingInterface::createWindowOnThread(const Q // The offscreen surface already validates against non-local QML sources, but we also need to ensure that // if we create top level QML, like dock widgets or other types of QQuickView containing desktop windows // that the source URL is permitted + qDebug() << "DesktopScriptingInterface::createWindowOnThread " << sourceUrl; const auto& urlValidator = OffscreenQmlSurface::getUrlValidator(); if (!urlValidator(sourceUrl)) { return nullptr; diff --git a/libraries/gl/src/gl/GLWidget.cpp b/libraries/gl/src/gl/GLWidget.cpp index d91d4b6b4ad..010e6c361af 100644 --- a/libraries/gl/src/gl/GLWidget.cpp +++ b/libraries/gl/src/gl/GLWidget.cpp @@ -63,7 +63,9 @@ void GLWidget::createContext(QOpenGLContext* shareContext) { _context = new gl::Context(); _context->setWindow(windowHandle()); _context->create(shareContext); - _context->makeCurrent(); + bool isCurrent = _context->makeCurrent(); + Q_ASSERT(isCurrent); + Q_UNUSED(isCurrent); _context->clear(); _context->doneCurrent(); } diff --git a/libraries/model-baker/src/model-baker/ParseFlowDataTask.cpp b/libraries/model-baker/src/model-baker/ParseFlowDataTask.cpp index 48466ebe07a..1949b62906c 100644 --- a/libraries/model-baker/src/model-baker/ParseFlowDataTask.cpp +++ b/libraries/model-baker/src/model-baker/ParseFlowDataTask.cpp @@ -12,6 +12,7 @@ void ParseFlowDataTask::run(const baker::BakeContextPointer& context, const Inpu FlowData flowData; static const QString FLOW_PHYSICS_FIELD = "flowPhysicsData"; static const QString FLOW_COLLISIONS_FIELD = "flowCollisionsData"; + // QT6TODO: currently crashes on Qt6 for (auto mappingIter = mapping.cbegin(); mappingIter != mapping.cend(); mappingIter++) { if (mappingIter.key() == FLOW_PHYSICS_FIELD || mappingIter.key() == FLOW_COLLISIONS_FIELD) { QByteArray data = mappingIter.value().toByteArray(); diff --git a/libraries/model-baker/src/model-baker/ParseMaterialMappingTask.cpp b/libraries/model-baker/src/model-baker/ParseMaterialMappingTask.cpp index c4c4f7f74b2..a6bbef97a99 100644 --- a/libraries/model-baker/src/model-baker/ParseMaterialMappingTask.cpp +++ b/libraries/model-baker/src/model-baker/ParseMaterialMappingTask.cpp @@ -71,6 +71,9 @@ void ParseMaterialMappingTask::run(const baker::BakeContextPointer& context, con const auto& url = input.get1(); MaterialMapping materialMapping; + // QT6TODO: even listing keys causes crash + qDebug() << "ParseMaterialMappingTask::run " << mapping.keys(); + auto mappingIter = mapping.find("materialMap"); if (mappingIter != mapping.end()) { QByteArray materialMapValue = mappingIter.value().toByteArray(); diff --git a/libraries/qml/src/qml/impl/RenderEventHandler.cpp b/libraries/qml/src/qml/impl/RenderEventHandler.cpp index 5b06db3ff61..74f45969a88 100644 --- a/libraries/qml/src/qml/impl/RenderEventHandler.cpp +++ b/libraries/qml/src/qml/impl/RenderEventHandler.cpp @@ -61,7 +61,11 @@ RenderEventHandler::RenderEventHandler(SharedObject* shared, QThread* targetThre moveToThread(targetThread); } +// I'm not sure if several contexts can be initalized with the same shared context concurrently, so better safe than sorry. +static std::mutex renderControlInitMutex; + void RenderEventHandler::onInitalize() { + std::lock_guard lock(renderControlInitMutex); if (_shared->isQuit()) { return; } diff --git a/libraries/qml/src/qml/impl/SharedObject.cpp b/libraries/qml/src/qml/impl/SharedObject.cpp index 73a0413e851..1d56afbbd41 100644 --- a/libraries/qml/src/qml/impl/SharedObject.cpp +++ b/libraries/qml/src/qml/impl/SharedObject.cpp @@ -70,11 +70,13 @@ SharedObject::SharedObject() { // NOTE: Must be created on the main thread so that OffscreenQmlSurface can send it events // NOTE: Must be created on the rendering thread or it will refuse to render, // so we wait until after its ctor to move object/context to this thread. - QQuickWindow::setDefaultAlphaBuffer(true); + // QT6TODO: QRhi fails to initialize with setDefaultAlphaBuffer + //QQuickWindow::setDefaultAlphaBuffer(true); _quickWindow = new QQuickWindow(_renderControl); + _quickWindow->setSurfaceType(QQuickWindow::OpenGLSurface); _quickWindow->setFormat(getDefaultOpenGLSurfaceFormat()); _quickWindow->setColor(Qt::transparent); - // QT6TODO: setClearBeforeRendering was reoved, what to do about this? + // QT6TODO: setClearBeforeRendering was removed, what to do about this? // https://doc.qt.io/qt-6/quick-changes-qt6.html //_quickWindow->setClearBeforeRendering(true); @@ -294,10 +296,15 @@ void SharedObject::initializeRenderControl(QOpenGLContext* context) { qFatal("QML rendering context has no share context"); } + Q_ASSERT(context->isValid()); + #ifndef DISABLE_QML if (!nsightActive()) { + _quickWindow->setFormat(context->format()); _quickWindow->setGraphicsDevice(QQuickGraphicsDevice::fromOpenGLContext(context)); - _renderControl->initialize(); + bool result = _renderControl->initialize(); + Q_ASSERT(result); + Q_UNUSED(result); } #endif } diff --git a/libraries/ui/src/VrMenu.cpp b/libraries/ui/src/VrMenu.cpp index 1f03f2bae9a..5076e9095b9 100644 --- a/libraries/ui/src/VrMenu.cpp +++ b/libraries/ui/src/VrMenu.cpp @@ -310,7 +310,7 @@ void VrMenu::insertAction(QAction* before, QAction* action) { menuItemObject->setParent(menu); qDebug() << "menuObject: " << QString(menuItemObject->metaObject()->metaType().name()); - bool invokeResult = QMetaObject::invokeMethod(menu, "addItem", Qt::DirectConnection, + bool invokeResult = QMetaObject::invokeMethod(menu, "addItemWrap", Qt::DirectConnection, Q_ARG(QVariant, QVariant::fromValue(menuItemObject))); // FIXME this needs to find the index of the beforeQml item and call insertItem(int, object) Q_ASSERT(invokeResult); @@ -318,7 +318,7 @@ void VrMenu::insertAction(QAction* before, QAction* action) { if (menuItemObject) { bindActionToQmlAction(menuItemObject, action, _rootMenu); } else { - qWarning() << "Failed to find addItem() method in object " << menu << ". Not inserting action " << action; + qWarning() << "Failed to find addItemWrap() method in object " << menu << ". Not inserting action " << action; } } From 226c7a42c8868d43e8248bc6097d2fe9cd254cb2 Mon Sep 17 00:00:00 2001 From: Ada Date: Fri, 19 Sep 2025 21:18:17 +1000 Subject: [PATCH 009/111] Fix a handful of Qt6 crashes, flow now works properly --- libraries/gl/src/gl/OffscreenGLCanvas.cpp | 4 ++++ libraries/model-baker/src/model-baker/Baker.cpp | 2 +- libraries/model-baker/src/model-baker/ParseFlowDataTask.h | 2 +- .../model-baker/src/model-baker/ParseMaterialMappingTask.h | 4 ++-- libraries/model-baker/src/model-baker/PrepareJointsTask.cpp | 6 +++--- libraries/model-baker/src/model-baker/PrepareJointsTask.h | 4 ++-- libraries/ui/src/VrMenu.cpp | 5 ++++- 7 files changed, 17 insertions(+), 10 deletions(-) diff --git a/libraries/gl/src/gl/OffscreenGLCanvas.cpp b/libraries/gl/src/gl/OffscreenGLCanvas.cpp index 17aef0724cd..4d05dbf9271 100644 --- a/libraries/gl/src/gl/OffscreenGLCanvas.cpp +++ b/libraries/gl/src/gl/OffscreenGLCanvas.cpp @@ -75,6 +75,10 @@ bool OffscreenGLCanvas::makeCurrent() { bool result = _context->makeCurrent(_offscreenSurface); if (result) { std::call_once(_reportOnce, [] { + // initialise glad before ContextInfo::init uses it, + // otherwise we crash on debug builds because glad + // has its own debug wrappers that need to be set up + gl::initModuleGl(); LOG_GL_CONTEXT_INFO(glLogging, gl::ContextInfo().init()); }); } diff --git a/libraries/model-baker/src/model-baker/Baker.cpp b/libraries/model-baker/src/model-baker/Baker.cpp index 1be39ad1f60..39d9ffd6e29 100644 --- a/libraries/model-baker/src/model-baker/Baker.cpp +++ b/libraries/model-baker/src/model-baker/Baker.cpp @@ -126,7 +126,7 @@ namespace baker { class BakerEngineBuilder { public: - using Input = VaryingSet3; + using Input = VaryingSet3; using Output = VaryingSet5, std::vector, std::vector>>; using JobModel = Task::ModelIO; void build(JobModel& model, const Varying& input, Varying& output) { diff --git a/libraries/model-baker/src/model-baker/ParseFlowDataTask.h b/libraries/model-baker/src/model-baker/ParseFlowDataTask.h index deabb63f79c..29e5224cd2d 100644 --- a/libraries/model-baker/src/model-baker/ParseFlowDataTask.h +++ b/libraries/model-baker/src/model-baker/ParseFlowDataTask.h @@ -18,7 +18,7 @@ class ParseFlowDataTask { public: - using Input = hifi::VariantHash; + using Input = hifi::VariantMultiHash; using Output = FlowData; using JobModel = baker::Job::ModelIO; diff --git a/libraries/model-baker/src/model-baker/ParseMaterialMappingTask.h b/libraries/model-baker/src/model-baker/ParseMaterialMappingTask.h index 3e967a7d3f0..0e453c53c95 100644 --- a/libraries/model-baker/src/model-baker/ParseMaterialMappingTask.h +++ b/libraries/model-baker/src/model-baker/ParseMaterialMappingTask.h @@ -22,11 +22,11 @@ class ParseMaterialMappingTask { public: - using Input = baker::VaryingSet2; + using Input = baker::VaryingSet2; using Output = MaterialMapping; using JobModel = baker::Job::ModelIO; void run(const baker::BakeContextPointer& context, const Input& input, Output& output); }; -#endif // hifi_ParseMaterialMappingTask_h \ No newline at end of file +#endif // hifi_ParseMaterialMappingTask_h diff --git a/libraries/model-baker/src/model-baker/PrepareJointsTask.cpp b/libraries/model-baker/src/model-baker/PrepareJointsTask.cpp index 662d5f36dc6..efbcc6360c6 100644 --- a/libraries/model-baker/src/model-baker/PrepareJointsTask.cpp +++ b/libraries/model-baker/src/model-baker/PrepareJointsTask.cpp @@ -13,7 +13,7 @@ #include "ModelBakerLogging.h" -QMap getJointNameMapping(const hifi::VariantHash& mapping) { +QMap getJointNameMapping(const hifi::VariantMultiHash& mapping) { static const QString JOINT_NAME_MAPPING_FIELD = "jointMap"; QMap hfmToHifiJointNameMap; if (!mapping.isEmpty() && mapping.contains(JOINT_NAME_MAPPING_FIELD) && mapping[JOINT_NAME_MAPPING_FIELD].type() == QVariant::Hash) { @@ -26,7 +26,7 @@ QMap getJointNameMapping(const hifi::VariantHash& mapping) { return hfmToHifiJointNameMap; } -QMap getJointRotationOffsets(const hifi::VariantHash& mapping) { +QMap getJointRotationOffsets(const hifi::VariantMultiHash& mapping) { QMap jointRotationOffsets; static const QString JOINT_ROTATION_OFFSET_FIELD = "jointRotationOffset"; static const QString JOINT_ROTATION_OFFSET2_FIELD = "jointRotationOffset2"; @@ -72,7 +72,7 @@ void PrepareJointsTask::run(const baker::BakeContextPointer& context, const Inpu auto& jointRotationOffsets = output.edit1(); static const QString JOINT_ROTATION_OFFSET2_FIELD = "jointRotationOffset2"; - QVariantHash fstHashMap = mapping; + hifi::VariantMultiHash fstHashMap = mapping; bool newJointRot = fstHashMap.contains(JOINT_ROTATION_OFFSET2_FIELD); // Get joint renames diff --git a/libraries/model-baker/src/model-baker/PrepareJointsTask.h b/libraries/model-baker/src/model-baker/PrepareJointsTask.h index 802dbb38260..76960b8f92d 100644 --- a/libraries/model-baker/src/model-baker/PrepareJointsTask.h +++ b/libraries/model-baker/src/model-baker/PrepareJointsTask.h @@ -29,7 +29,7 @@ class PrepareJointsConfig : public baker::JobConfig { class PrepareJointsTask { public: using Config = PrepareJointsConfig; - using Input = baker::VaryingSet2, hifi::VariantHash /*mapping*/>; + using Input = baker::VaryingSet2, hifi::VariantMultiHash /*mapping*/>; using Output = baker::VaryingSet3, QMap /*jointRotationOffsets*/, QHash /*jointIndices*/>; using JobModel = baker::Job::ModelIO; @@ -40,4 +40,4 @@ class PrepareJointsTask { bool _passthrough { false }; }; -#endif // hifi_PrepareJointsTask_h \ No newline at end of file +#endif // hifi_PrepareJointsTask_h diff --git a/libraries/ui/src/VrMenu.cpp b/libraries/ui/src/VrMenu.cpp index 5076e9095b9..56c6659d000 100644 --- a/libraries/ui/src/VrMenu.cpp +++ b/libraries/ui/src/VrMenu.cpp @@ -85,7 +85,10 @@ void MenuUserData::updateQmlItemFromAction() { _qml->setProperty("text", text); _qml->setProperty("shortcut", _action->shortcut().toString()); _qml->setProperty("checked", _action->isChecked()); - _qml->setProperty("visible", _action->isVisible()); + + // Qt6 TODO: Inconsistent segfault inside Qt, possible thread race condition? + // How come it's only on "visible" and not the other properties? + //_qml->setProperty("visible", _action->isVisible()); } void MenuUserData::clear() { From 05364f8414b40b1e6e91cc13f4bcb132d2eb0531 Mon Sep 17 00:00:00 2001 From: Karol Suprynowicz Date: Sat, 27 Sep 2025 17:37:02 +0200 Subject: [PATCH 010/111] Metaobject-related fixes --- .../src/scripting/DesktopScriptingInterface.cpp | 14 ++++++++++++-- libraries/controllers/src/controllers/Input.h | 2 ++ libraries/controllers/src/controllers/Pose.h | 2 +- .../controllers/src/controllers/UserInputMapper.h | 4 ---- libraries/script-engine/src/Quat.h | 2 ++ libraries/shared/src/DebugDraw.h | 5 ++++- libraries/shared/src/RegisteredMetaTypes.h | 9 +++++---- scripts/defaultScripts.js | 2 +- tests/script-engine/CMakeLists.txt | 4 +++- 9 files changed, 30 insertions(+), 14 deletions(-) diff --git a/interface/src/scripting/DesktopScriptingInterface.cpp b/interface/src/scripting/DesktopScriptingInterface.cpp index eaeef605500..1dbe4e9a76e 100644 --- a/interface/src/scripting/DesktopScriptingInterface.cpp +++ b/interface/src/scripting/DesktopScriptingInterface.cpp @@ -118,11 +118,20 @@ InteractiveWindowPointer DesktopScriptingInterface::createWindow(const QString& if (QThread::currentThread() != thread()) { qDebug() << "DesktopScriptingInterface::createWindow" << sourceUrl; InteractiveWindowPointer interactiveWindow = nullptr; - BLOCKING_INVOKE_METHOD(this, "createWindowOnThread", + // QT6TODO + /*BLOCKING_INVOKE_METHOD(this, "createWindowOnThread", Q_GENERIC_RETURN_ARG(InteractiveWindowPointer, interactiveWindow), Q_GENERIC_ARG(QString, sourceUrl), Q_GENERIC_ARG(QVariantMap, properties), - Q_GENERIC_ARG(QThread*, QThread::currentThread())); + Q_GENERIC_ARG(QThread*, QThread::currentThread()));*/ + QMetaObject::invokeMethod(this, &DesktopScriptingInterface::createWindowOnThread/*"createWindowOnThread"*/, + Qt::BlockingQueuedConnection, + Q_RETURN_ARG(InteractiveWindowPointer, interactiveWindow), + sourceUrl, + properties, + QThread::currentThread() + ); + return interactiveWindow; } @@ -142,6 +151,7 @@ InteractiveWindowPointer DesktopScriptingInterface::createWindowOnThread(const Q // The offscreen surface already validates against non-local QML sources, but we also need to ensure that // if we create top level QML, like dock widgets or other types of QQuickView containing desktop windows // that the source URL is permitted + // QT6TODO: crashes for some reason, temporarily disabled qDebug() << "DesktopScriptingInterface::createWindowOnThread " << sourceUrl; const auto& urlValidator = OffscreenQmlSurface::getUrlValidator(); if (!urlValidator(sourceUrl)) { diff --git a/libraries/controllers/src/controllers/Input.h b/libraries/controllers/src/controllers/Input.h index f2a5ca1296e..31030013ca8 100644 --- a/libraries/controllers/src/controllers/Input.h +++ b/libraries/controllers/src/controllers/Input.h @@ -95,4 +95,6 @@ struct Input { } +Q_DECLARE_METATYPE(controller::Input) + #endif diff --git a/libraries/controllers/src/controllers/Pose.h b/libraries/controllers/src/controllers/Pose.h index 7187dfa3f83..33fb50cfaad 100644 --- a/libraries/controllers/src/controllers/Pose.h +++ b/libraries/controllers/src/controllers/Pose.h @@ -51,6 +51,6 @@ namespace controller { }; } -//Q_DECLARE_METATYPE(controller::Pose); +Q_DECLARE_METATYPE(controller::Pose); #endif diff --git a/libraries/controllers/src/controllers/UserInputMapper.h b/libraries/controllers/src/controllers/UserInputMapper.h index 065d5b27763..1c6b2b99c55 100644 --- a/libraries/controllers/src/controllers/UserInputMapper.h +++ b/libraries/controllers/src/controllers/UserInputMapper.h @@ -232,11 +232,7 @@ namespace controller { } Q_DECLARE_METATYPE(controller::Input::NamedPair) -// QT6TODO: what to do about compiler error here? -//Q_DECLARE_METATYPE(controller::Pose) Q_DECLARE_METATYPE(QVector) -// QT6TODO: what to do about compiler error here? -//Q_DECLARE_METATYPE(controller::Input) Q_DECLARE_METATYPE(controller::Action) Q_DECLARE_METATYPE(QVector) Q_DECLARE_METATYPE(controller::Hand) diff --git a/libraries/script-engine/src/Quat.h b/libraries/script-engine/src/Quat.h index 75caf0e8af7..bf8380a6bd3 100644 --- a/libraries/script-engine/src/Quat.h +++ b/libraries/script-engine/src/Quat.h @@ -21,6 +21,8 @@ #include +#include + #include #include diff --git a/libraries/shared/src/DebugDraw.h b/libraries/shared/src/DebugDraw.h index c5ba2905144..e80b63a5b65 100644 --- a/libraries/shared/src/DebugDraw.h +++ b/libraries/shared/src/DebugDraw.h @@ -10,6 +10,10 @@ #ifndef hifi_DebugDraw_h #define hifi_DebugDraw_h +#include + +#include "RegisteredMetaTypes.h" + #include #include #include @@ -18,7 +22,6 @@ #include #include -#include #include /*@jsdoc diff --git a/libraries/shared/src/RegisteredMetaTypes.h b/libraries/shared/src/RegisteredMetaTypes.h index a9fc605c3cd..39783e2a090 100644 --- a/libraries/shared/src/RegisteredMetaTypes.h +++ b/libraries/shared/src/RegisteredMetaTypes.h @@ -20,6 +20,8 @@ #include #include +#include + #include "AACube.h" #include "ShapeInfo.h" #include "SharedUtil.h" @@ -31,13 +33,12 @@ class QColor; class QUrl; -// QT6TODO: Q_DECLARE_METATYPE(uint16_t) Q_DECLARE_METATYPE(glm::vec2) Q_DECLARE_METATYPE(glm::u8vec3) -//Q_DECLARE_METATYPE(glm::vec3) -//Q_DECLARE_METATYPE(glm::vec4) -//Q_DECLARE_METATYPE(glm::quat) +Q_DECLARE_METATYPE(glm::vec3) +Q_DECLARE_METATYPE(glm::vec4) +Q_DECLARE_METATYPE(glm::quat) Q_DECLARE_METATYPE(glm::mat4) Q_DECLARE_METATYPE(QVector) Q_DECLARE_METATYPE(unsigned int) diff --git a/scripts/defaultScripts.js b/scripts/defaultScripts.js index 6de377e6e8c..07e047760cd 100644 --- a/scripts/defaultScripts.js +++ b/scripts/defaultScripts.js @@ -46,7 +46,7 @@ var DEFAULT_SCRIPTS_SEPARATE = [ "communityScripts/notificationCore/notificationCore.js", "simplifiedUI/ui/simplifiedNametag/simplifiedNametag.js", {"stable": "system/more/app-more.js", "beta": "https://more.overte.org/more/app-more.js"}, - "communityScripts/armored-chat/armored_chat.js", + //"communityScripts/armored-chat/armored_chat.js", // QT6TODO: causes a crash for now "communityScripts/chatBubbles/chatBubbles.js", "communityScripts/contextMenu.js", //"system/chat.js" diff --git a/tests/script-engine/CMakeLists.txt b/tests/script-engine/CMakeLists.txt index 51743129aa4..218bfb1c567 100644 --- a/tests/script-engine/CMakeLists.txt +++ b/tests/script-engine/CMakeLists.txt @@ -5,7 +5,9 @@ macro (setup_testcase_dependencies) # V8TODO: replace most link_hifi_libraries with include_hifi_library_headers # link in the shared libraries - link_hifi_libraries(shared test-utils script-engine networking octree avatars entities model-networking material-networking model-serializers graphics gpu ktx shaders hfm image procedural) + link_hifi_libraries(shared test-utils script-engine networking octree avatars entities model-networking material-networking model-serializers graphics gpu ktx shaders hfm image procedural audio) + include_hifi_library_headers(entities) + include_hifi_library_headers(audio) package_libraries_for_deployment() From 7594f31e2fe88d5fa4e2ff52247dd73332f17451 Mon Sep 17 00:00:00 2001 From: Karol Suprynowicz Date: Sat, 27 Sep 2025 20:00:24 +0200 Subject: [PATCH 011/111] Add a check for catching MOC-related invoke bug --- .../script-engine/src/v8/ScriptObjectV8Proxy.cpp | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/libraries/script-engine/src/v8/ScriptObjectV8Proxy.cpp b/libraries/script-engine/src/v8/ScriptObjectV8Proxy.cpp index e53add90e7d..8b46773975c 100644 --- a/libraries/script-engine/src/v8/ScriptObjectV8Proxy.cpp +++ b/libraries/script-engine/src/v8/ScriptObjectV8Proxy.cpp @@ -1017,6 +1017,19 @@ void ScriptMethodV8Proxy::call(const v8::FunctionCallbackInfo& argume for (int i = 0; i < num_metas; i++) { const QMetaMethod& meta = _metas[i]; + + // This check is needed for catching issues caused by a bug in Qt6 that causes metamethod invocations to fail when typedefs are used in header files. +#if !defined(QT_NO_DEBUG) || defined(QT_FORCE_ASSERTS) + for (int parameterIndex = 0; parameterIndex < meta.parameterCount(); parameterIndex++) { + if (std::string(meta.parameterTypeName(parameterIndex)) != std::string(meta.parameterMetaType(parameterIndex).name())) { + qCritical() << "ScriptMethodV8Proxy::call: " << fullName() << " parameter " << parameterIndex + << " has a broken type " << meta.parameterTypeName(parameterIndex) + << " Correct type is " << meta.parameterMetaType(parameterIndex).name(); + Q_ASSERT(false); + } + } +#endif + int methodNumArgs = meta.parameterCount(); if (methodNumArgs != numArgs) { continue; From d002c022e844748649e1ed4e2be1a0f508294b67 Mon Sep 17 00:00:00 2001 From: Karol Suprynowicz Date: Sat, 27 Sep 2025 21:59:11 +0200 Subject: [PATCH 012/111] Remove glm::quat from scripting API --- .../src/octree/OctreeHeadlessViewer.h | 4 +- interface/src/SecondaryCamera.h | 8 +-- interface/src/avatar/MyAvatar.h | 46 +++++++-------- .../src/scripting/HMDScriptingInterface.h | 4 +- libraries/animation/src/AnimationObject.h | 4 +- .../src/avatars-renderer/Avatar.h | 28 ++++----- .../src/avatars-renderer/ScriptAvatar.h | 6 +- libraries/avatars/src/AvatarData.h | 24 ++++---- libraries/avatars/src/ScriptAvatarData.h | 16 ++--- .../src/controllers/ScriptingInterface.h | 4 +- .../src/controllers/impl/RouteBuilderProxy.h | 2 +- .../entities/src/EntityScriptingInterface.h | 16 ++--- .../graphics-scripting/ScriptableMeshPart.h | 2 +- libraries/script-engine/src/Mat4.h | 6 +- libraries/script-engine/src/Quat.h | 58 +++++++++---------- libraries/shared/src/DebugDraw.h | 12 ++-- libraries/shared/src/shared/Camera.h | 6 +- tests/script-engine/src/ScriptEngineTests.cpp | 2 +- 18 files changed, 124 insertions(+), 124 deletions(-) diff --git a/assignment-client/src/octree/OctreeHeadlessViewer.h b/assignment-client/src/octree/OctreeHeadlessViewer.h index 2c256c8516c..c9619c33c6d 100644 --- a/assignment-client/src/octree/OctreeHeadlessViewer.h +++ b/assignment-client/src/octree/OctreeHeadlessViewer.h @@ -50,7 +50,7 @@ public slots: * @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. @@ -106,7 +106,7 @@ public slots: * @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/interface/src/SecondaryCamera.h b/interface/src/SecondaryCamera.h index 463034527ac..b846367d373 100644 --- a/interface/src/SecondaryCamera.h +++ b/interface/src/SecondaryCamera.h @@ -22,7 +22,7 @@ class SecondaryCameraJobConfig : public render::Task::Config { // Exposes second 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::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. @@ -32,7 +32,7 @@ class SecondaryCameraJobConfig : public render::Task::Config { // Exposes second QUuid attachedEntityId; QUuid portalEntranceEntityId; glm::vec3 position; - glm::quat orientation; + glm::qua orientation; float vFoV { DEFAULT_FIELD_OF_VIEW_DEGREES }; float nearClipPlaneDistance { DEFAULT_NEAR_CLIP }; float farClipPlaneDistance { DEFAULT_FAR_CLIP }; @@ -47,8 +47,8 @@ class SecondaryCameraJobConfig : public render::Task::Config { // Exposes second public slots: glm::vec3 getPosition() { return position; } void setPosition(glm::vec3 pos); - glm::quat getOrientation() { return orientation; } - void setOrientation(glm::quat orient); + 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/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 84308094720..9dc62f9e0c0 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -338,7 +338,7 @@ class MyAvatar : public Avatar { 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::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) @@ -580,7 +580,7 @@ class MyAvatar : public Avatar { const glm::mat4& getHMDSensorMatrix() const { return _hmdSensorMatrix; } const glm::vec3& getHMDSensorPosition() const { return _hmdSensorPosition; } - const glm::quat& getHMDSensorOrientation() const { return _hmdSensorOrientation; } + const glm::qua& getHMDSensorOrientation() const { return _hmdSensorOrientation; } /*@jsdoc * Gets the avatar orientation. Suitable for use in QML. @@ -597,7 +597,7 @@ 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 @@ -1259,14 +1259,14 @@ 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 setJointRotations(const QVector>& jointRotations) override; + virtual void setJointData(int index, const glm::qua& rotation, const glm::vec3& translation) override; + virtual void setJointRotation(int index, const glm::qua& rotation) override; virtual void setJointTranslation(int index, const glm::vec3& 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 setJointData(const QString& name, const glm::qua& rotation, const glm::vec3& translation) override; + virtual void setJointRotation(const QString& name, const glm::qua& rotation) override; virtual void setJointTranslation(const QString& name, const glm::vec3& translation) override; virtual void clearJointData(const QString& name) override; virtual void clearJointsData() override; @@ -1280,7 +1280,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::vec3& position, const glm::qua& orientation); bool isJointPinned(int index); @@ -1333,7 +1333,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::vec3 position, glm::qua orientation); // Can be safely called at any time. void harvestResultsFromPhysicsSimulation(float deltaTime); const QString& getCollisionSoundURL() { return _collisionSoundURL; } @@ -1369,8 +1369,8 @@ class MyAvatar : public Avatar { 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::qua getCustomListenOrientation() { return _customListenOrientation; } + void setCustomListenOrientation(glm::qua customListenOrientation) { _customListenOrientation = customListenOrientation; } virtual void rebuildCollisionShape() override; @@ -1382,8 +1382,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; } @@ -1575,7 +1575,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. @@ -1676,7 +1676,7 @@ 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(); } void prepareAvatarEntityDataForReload(); @@ -1735,7 +1735,7 @@ class MyAvatar : public Avatar { */ Q_INVOKABLE bool setPointAt(const glm::vec3& pointAtTarget); - glm::quat getLookAtRotation() { return _lookAtYaw * _lookAtPitch; } + glm::qua getLookAtRotation() { return _lookAtYaw * _lookAtPitch; } /*@jsdoc * Creates a new grab that grabs an entity. @@ -1765,7 +1765,7 @@ class MyAvatar : public Avatar { * }, 10000); */ Q_INVOKABLE const QUuid grab(const QUuid& targetID, int parentJointIndex, - glm::vec3 positionalOffset, glm::quat rotationalOffset); + glm::vec3 positionalOffset, glm::qua rotationalOffset); /*@jsdoc * Releases (deletes) a grab to stop grabbing an entity. @@ -1845,7 +1845,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::vec3& position, const glm::qua& rotation); /*@jsdoc * Ends a sitting action for the avatar. @@ -1853,7 +1853,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::vec3& 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}. @@ -1957,7 +1957,7 @@ public slots: * the new position and orientate the avatar to face the position. */ void goToFeetLocation(const glm::vec3& newPosition, bool hasOrientation = false, - const glm::quat& newOrientation = glm::quat(), bool shouldFaceLocation = false); + const glm::qua& newOrientation = glm::qua(), bool shouldFaceLocation = false); /*@jsdoc * Moves the avatar to a new position and/or orientation in the domain. @@ -1970,7 +1970,7 @@ public slots: * @param {boolean} [withSafeLanding=true] - Set to false to disable safe landing when teleporting. */ void goToLocation(const glm::vec3& newPosition, - bool hasOrientation = false, const glm::quat& newOrientation = glm::quat(), + bool hasOrientation = false, const glm::qua& newOrientation = glm::qua(), bool shouldFaceLocation = false, bool withSafeLanding = true); /*@jsdoc * Moves the avatar to a new position and (optional) orientation in the domain, with safe landing. @@ -2219,7 +2219,7 @@ public slots: * @function MyAvatar.getOrientationForAudio * @returns {Quat} The orientation of your listening position. */ - glm::quat getOrientationForAudio(); + glm::qua getOrientationForAudio(); /*@jsdoc * @function MyAvatar.setModelScale diff --git a/interface/src/scripting/HMDScriptingInterface.h b/interface/src/scripting/HMDScriptingInterface.h index 81516ae441b..72a632150ed 100644 --- a/interface/src/scripting/HMDScriptingInterface.h +++ b/interface/src/scripting/HMDScriptingInterface.h @@ -91,7 +91,7 @@ class ScriptEngine; class HMDScriptingInterface : public AbstractHMDScriptingInterface, public Dependency { Q_OBJECT Q_PROPERTY(glm::vec3 position READ getPosition) - Q_PROPERTY(glm::quat orientation READ getOrientation) + Q_PROPERTY(glm::qua orientation READ getOrientation) Q_PROPERTY(bool showTablet READ getShouldShowTablet) Q_PROPERTY(bool tabletContextualMode READ getTabletContextualMode) Q_PROPERTY(QUuid tabletID READ getCurrentTabletFrameID WRITE setCurrentTabletFrameID) @@ -506,7 +506,7 @@ class HMDScriptingInterface : public AbstractHMDScriptingInterface, public Depen bool _miniTabletEnabled { true }; // Get the orientation of the HMD - glm::quat getOrientation() const; + glm::qua getOrientation() const; bool getHUDLookAtPosition3D(glm::vec3& result) const; glm::mat4 getWorldHMDMatrix() const; diff --git a/libraries/animation/src/AnimationObject.h b/libraries/animation/src/AnimationObject.h index d381cdc872a..0bddf69bb15 100644 --- a/libraries/animation/src/AnimationObject.h +++ b/libraries/animation/src/AnimationObject.h @@ -76,7 +76,7 @@ class AnimationObject : public QObject, protected Scriptable { /// Scriptable wrapper for animation frames. class AnimationFrameObject : public QObject, protected Scriptable { Q_OBJECT - Q_PROPERTY(QVector rotations READ getRotations) + Q_PROPERTY(QVector> rotations READ getRotations) public: @@ -85,7 +85,7 @@ class AnimationFrameObject : public QObject, protected Scriptable { * @function AnimationFrameObject.getRotations * @returns {Quat[]} The joint rotations in the animation frame. */ - Q_INVOKABLE QVector getRotations() const; + Q_INVOKABLE QVector> getRotations() const; }; void registerAnimationTypes(ScriptEngine* engine); diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.h b/libraries/avatars-renderer/src/avatars-renderer/Avatar.h index 7c1cfcd4403..b7d0c481c77 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.h +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.h @@ -194,9 +194,9 @@ class Avatar : public AvatarData, public scriptable::ModelProvider, public MetaM }; virtual void indicateLoadingStatus(LoadingStatus loadingStatus) { _loadingStatus = loadingStatus; } - virtual QVector getJointRotations() const override; + virtual QVector> getJointRotations() const override; using AvatarData::getJointRotation; - virtual glm::quat getJointRotation(int index) const override; + virtual glm::qua getJointRotation(int index) const override; virtual QVector getJointTranslations() const override; using AvatarData::getJointTranslation; virtual glm::vec3 getJointTranslation(int index) const override; @@ -213,7 +213,7 @@ class Avatar : public AvatarData, public scriptable::ModelProvider, public MetaM * @param {number} index - The joint index. * @returns {Quat} The default rotation of the joint if the joint index is valid, otherwise {@link Quat(0)|Quat.IDENTITY}. */ - Q_INVOKABLE virtual glm::quat getDefaultJointRotation(int index) const; + Q_INVOKABLE virtual glm::qua getDefaultJointRotation(int index) const; /*@jsdoc * Gets the default translation of a joint (in the current avatar) relative to its parent, in model coordinates. @@ -239,7 +239,7 @@ class Avatar : public AvatarData, public scriptable::ModelProvider, public MetaM * var defaultHeadRotation = MyAvatar.getAbsoluteDefaultJointRotationInObjectFrame(headIndex); * print("Default head rotation: " + JSON.stringify(Quat.safeEulerAngles(defaultHeadRotation))); // Degrees */ - Q_INVOKABLE virtual glm::quat getAbsoluteDefaultJointRotationInObjectFrame(int index) const; + Q_INVOKABLE virtual glm::qua getAbsoluteDefaultJointRotationInObjectFrame(int index) const; /*@jsdoc * Gets the default joint translations in avatar coordinates. @@ -257,7 +257,7 @@ class Avatar : public AvatarData, public scriptable::ModelProvider, public MetaM virtual glm::vec3 getAbsoluteJointScaleInObjectFrame(int index) const override; - virtual glm::quat getAbsoluteJointRotationInObjectFrame(int index) const override; + virtual glm::qua getAbsoluteJointRotationInObjectFrame(int index) const override; virtual glm::vec3 getAbsoluteJointTranslationInObjectFrame(int index) const override; /*@jsdoc @@ -268,7 +268,7 @@ class Avatar : public AvatarData, public scriptable::ModelProvider, public MetaM * @param {Quat} rotation - The rotation of the joint relative to the avatar. Not used. * @returns {boolean} false. */ - virtual bool setAbsoluteJointRotationInObjectFrame(int index, const glm::quat& rotation) override { return false; } + virtual bool setAbsoluteJointRotationInObjectFrame(int index, const glm::qua& rotation) override { return false; } /*@jsdoc * Sets the translation of a joint relative to the avatar. @@ -311,7 +311,7 @@ class Avatar : public AvatarData, public scriptable::ModelProvider, public MetaM * @param {number} [jointIndex=-1] - The index of the joint. * @returns {Quat} The rotation in the joint's coordinate system, or avatar coordinate system if no joint is specified. */ - Q_INVOKABLE glm::quat worldToJointRotation(const glm::quat& rotation, const int jointIndex = -1) const; + Q_INVOKABLE glm::qua worldToJointRotation(const glm::qua& rotation, const int jointIndex = -1) const; /*@jsdoc * Transforms a position in a joint's coordinates, or avatar coordinates if no joint is specified, to a position in world @@ -341,7 +341,7 @@ class Avatar : public AvatarData, public scriptable::ModelProvider, public MetaM * @param {number} [jointIndex=-1] - The index of the joint. * @returns {Quat} The rotation in world coordinates. */ - Q_INVOKABLE glm::quat jointToWorldRotation(const glm::quat& rotation, const int jointIndex = -1) const; + Q_INVOKABLE glm::qua jointToWorldRotation(const glm::qua& rotation, const int jointIndex = -1) const; Q_INVOKABLE virtual void setSkeletonModelURL(const QUrl& skeletonModelURL) override; @@ -417,7 +417,7 @@ class Avatar : public AvatarData, public scriptable::ModelProvider, public MetaM void scaleVectorRelativeToPosition(glm::vec3& positionToScale) const; void slamPosition(const glm::vec3& position); - virtual void updateAttitude(const glm::quat& orientation) override; + virtual void updateAttitude(const glm::qua& orientation) override; // Call this when updating Avatar position with a delta. This will allow us to // _accurately_ measure position changes and compute the resulting velocity @@ -439,7 +439,7 @@ class Avatar : public AvatarData, public scriptable::ModelProvider, public MetaM Q_INVOKABLE glm::vec3 getWorldFeetPosition(); void setPositionViaScript(const glm::vec3& position) override; - void setOrientationViaScript(const glm::quat& orientation) override; + void setOrientationViaScript(const glm::qua& orientation) override; /*@jsdoc * Gets the ID of the entity or avatar that the avatar is parented to. @@ -486,9 +486,9 @@ class Avatar : public AvatarData, public scriptable::ModelProvider, public MetaM // NOT thread safe, must be called on main thread. glm::vec3 getUncachedLeftPalmPosition() const; - glm::quat getUncachedLeftPalmRotation() const; + glm::qua getUncachedLeftPalmRotation() const; glm::vec3 getUncachedRightPalmPosition() const; - glm::quat getUncachedRightPalmRotation() const; + glm::qua getUncachedRightPalmRotation() const; uint64_t getLastRenderUpdateTime() const { return _lastRenderUpdateTime; } void setLastRenderUpdateTime(uint64_t time) { _lastRenderUpdateTime = time; } @@ -592,7 +592,7 @@ public slots: * @example Report the rotation of your avatar's left palm. * print(JSON.stringify(MyAvatar.getLeftPalmRotation())); */ - glm::quat getLeftPalmRotation() const; + glm::qua getLeftPalmRotation() const; /*@jsdoc * Gets the position of the right palm in world coordinates. @@ -610,7 +610,7 @@ public slots: * @example Report the rotation of your avatar's right palm. * print(JSON.stringify(MyAvatar.getRightPalmRotation())); */ - glm::quat getRightPalmRotation() const; + glm::qua getRightPalmRotation() const; /*@jsdoc * @function MyAvatar.setModelURLFinished diff --git a/libraries/avatars-renderer/src/avatars-renderer/ScriptAvatar.h b/libraries/avatars-renderer/src/avatars-renderer/ScriptAvatar.h index 906d3d438d3..7679969c3e0 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/ScriptAvatar.h +++ b/libraries/avatars-renderer/src/avatars-renderer/ScriptAvatar.h @@ -92,7 +92,7 @@ public slots: * @returns {Quat} The default rotation of the joint if avatar data are available and the joint index is valid, otherwise * {@link Quat(0)|Quat.IDENTITY}. */ - glm::quat getDefaultJointRotation(int index) const; + glm::qua getDefaultJointRotation(int index) const; /*@jsdoc * Gets the default translation of a joint in the avatar relative to its parent, in model coordinates. @@ -199,7 +199,7 @@ public slots: * @returns {Quat} The rotation of the left palm in world coordinates, or {@link Quat(0)|Quat.IDENTITY} if the avatar data * aren't available. */ - glm::quat getLeftPalmRotation() const; + glm::qua getLeftPalmRotation() const; /*@jsdoc * Gets the position of the right palm in world coordinates. @@ -215,7 +215,7 @@ public slots: * @returns {Quat} The rotation of the right palm in world coordinates, or {@link Quat(0)|Quat.IDENTITY} if the avatar data * aren't available. */ - glm::quat getRightPalmRotation() const; + glm::qua getRightPalmRotation() const; private: std::shared_ptr lockAvatar() const; diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index a31291e1fce..5b0eb99985c 100644 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -559,8 +559,8 @@ class AvatarData : public QObject, public SpatiallyNestable { Q_PROPERTY(float bodyPitch READ getBodyPitch WRITE setBodyPitch) Q_PROPERTY(float bodyRoll READ getBodyRoll WRITE setBodyRoll) - Q_PROPERTY(glm::quat orientation READ getWorldOrientation WRITE setOrientationViaScript) - Q_PROPERTY(glm::quat headOrientation READ getHeadOrientation WRITE setHeadOrientation) + Q_PROPERTY(glm::qua orientation READ getWorldOrientation WRITE setOrientationViaScript) + Q_PROPERTY(glm::qua headOrientation READ getHeadOrientation WRITE setHeadOrientation) Q_PROPERTY(float headPitch READ getHeadPitch WRITE setHeadPitch) Q_PROPERTY(float headYaw READ getHeadYaw WRITE setHeadYaw) Q_PROPERTY(float headRoll READ getHeadRoll WRITE setHeadRoll) @@ -805,7 +805,7 @@ class AvatarData : public QObject, public SpatiallyNestable { * * // Note: If using from the Avatar API, replace all occurrences of "MyAvatar" with "Avatar". */ - Q_INVOKABLE virtual void setJointData(int index, const glm::quat& rotation, const glm::vec3& translation); + Q_INVOKABLE virtual void setJointData(int index, const glm::qua& rotation, const glm::vec3& translation); /*@jsdoc * Sets a specific joint's rotation relative to its parent. @@ -818,7 +818,7 @@ class AvatarData : public QObject, public SpatiallyNestable { * @param {number} index - The index of the joint. * @param {Quat} rotation - The rotation of the joint relative to its parent. */ - Q_INVOKABLE virtual void setJointRotation(int index, const glm::quat& rotation); + Q_INVOKABLE virtual void setJointRotation(int index, const glm::qua& rotation); /*@jsdoc * Sets a specific joint's translation relative to its parent, in model coordinates. @@ -858,7 +858,7 @@ class AvatarData : public QObject, public SpatiallyNestable { * @param {number} index - The index of the joint. * @returns {Quat} The rotation of the joint relative to its parent. */ - Q_INVOKABLE virtual glm::quat getJointRotation(int index) const; + Q_INVOKABLE virtual glm::qua getJointRotation(int index) const; /*@jsdoc * Gets the translation of a joint relative to its parent, in model coordinates. @@ -884,7 +884,7 @@ class AvatarData : public QObject, public SpatiallyNestable { * @param {Quat} rotation - The rotation of the joint relative to its parent. * @param {Vec3} translation - The translation of the joint relative to its parent, in model coordinates. */ - Q_INVOKABLE virtual void setJointData(const QString& name, const glm::quat& rotation, const glm::vec3& translation); + Q_INVOKABLE virtual void setJointData(const QString& name, const glm::qua& rotation, const glm::vec3& translation); /*@jsdoc * Sets a specific joint's rotation relative to its parent. @@ -917,7 +917,7 @@ class AvatarData : public QObject, public SpatiallyNestable { * * // Note: If using from the Avatar API, replace all occurrences of "MyAvatar" with "Avatar". */ - Q_INVOKABLE virtual void setJointRotation(const QString& name, const glm::quat& rotation); + Q_INVOKABLE virtual void setJointRotation(const QString& name, const glm::qua& rotation); /*@jsdoc * Sets a specific joint's translation relative to its parent, in model coordinates. @@ -983,7 +983,7 @@ class AvatarData : public QObject, public SpatiallyNestable { * * // Note: If using from the Avatar API, replace "MyAvatar" with "Avatar". */ - Q_INVOKABLE virtual glm::quat getJointRotation(const QString& name) const; + Q_INVOKABLE virtual glm::qua getJointRotation(const QString& name) const; /*@jsdoc * Gets the translation of a joint relative to its parent, in model coordinates. @@ -1010,7 +1010,7 @@ class AvatarData : public QObject, public SpatiallyNestable { * * // Note: If using from the Avatar API, replace all "MyAvatar" with "Avatar". */ - Q_INVOKABLE virtual QVector getJointRotations() const; + Q_INVOKABLE virtual QVector> getJointRotations() const; /*@jsdoc * Gets the translations of all joints in the current avatar. Each joint's translation is relative to its parent joint, in @@ -1059,7 +1059,7 @@ class AvatarData : public QObject, public SpatiallyNestable { * * // Note: If using from the Avatar API, replace all occurrences of "MyAvatar" with "Avatar". */ - Q_INVOKABLE virtual void setJointRotations(const QVector& jointRotations); + Q_INVOKABLE virtual void setJointRotations(const QVector>& jointRotations); /*@jsdoc * Sets the translations of all joints in the current avatar. Each joint's translation is relative to its parent joint, in @@ -1496,7 +1496,7 @@ public slots: * @param {number} index - The index of the joint. Not used. * @returns {Quat} Quat.IDENTITY. */ - 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. @@ -1515,7 +1515,7 @@ public slots: * @param {Quat} rotation - The rotation of the joint relative to the avatar. Not used. * @returns {boolean} false. */ - virtual bool setAbsoluteJointRotationInObjectFrame(int index, const glm::quat& rotation) override { return false; } + virtual bool setAbsoluteJointRotationInObjectFrame(int index, const glm::qua& rotation) override { return false; } /*@jsdoc * Sets the translation of a joint relative to the avatar. diff --git a/libraries/avatars/src/ScriptAvatarData.h b/libraries/avatars/src/ScriptAvatarData.h index 960423a1eec..e28b8ebdf33 100644 --- a/libraries/avatars/src/ScriptAvatarData.h +++ b/libraries/avatars/src/ScriptAvatarData.h @@ -29,8 +29,8 @@ class ScriptAvatarData : public QObject { Q_PROPERTY(float bodyPitch READ getBodyPitch) Q_PROPERTY(float bodyYaw READ getBodyYaw) Q_PROPERTY(float bodyRoll READ getBodyRoll) - Q_PROPERTY(glm::quat orientation READ getOrientation) - Q_PROPERTY(glm::quat headOrientation READ getHeadOrientation) + Q_PROPERTY(glm::qua orientation READ getOrientation) + Q_PROPERTY(glm::qua headOrientation READ getHeadOrientation) Q_PROPERTY(float headPitch READ getHeadPitch) Q_PROPERTY(float headYaw READ getHeadYaw) Q_PROPERTY(float headRoll READ getHeadRoll) @@ -82,8 +82,8 @@ class ScriptAvatarData : public QObject { float getBodyPitch() const; float getBodyYaw() const; float getBodyRoll() const; - glm::quat getOrientation() const; - glm::quat getHeadOrientation() const; + glm::qua getOrientation() const; + glm::qua getHeadOrientation() const; float getHeadPitch() const; float getHeadYaw() const; float getHeadRoll() const; @@ -123,7 +123,7 @@ class ScriptAvatarData : public QObject { * @returns {Quat} The rotation of the joint relative to its parent, or {@link Quat(0)|Quat.IDENTITY} if the avatar data * aren't available. */ - Q_INVOKABLE glm::quat getJointRotation(int index) const; + Q_INVOKABLE glm::qua getJointRotation(int index) const; /*@jsdoc * Gets the translation of a joint relative to its parent, in model coordinates. @@ -145,7 +145,7 @@ class ScriptAvatarData : public QObject { * @returns {Quat} The rotation of the joint relative to its parent, or {@link Quat(0)|Quat.IDENTITY} if the avatar data * aren't available. */ - Q_INVOKABLE glm::quat getJointRotation(const QString& name) const; + Q_INVOKABLE glm::qua getJointRotation(const QString& name) const; /*@jsdoc * Gets the translation of a joint relative to its parent, in model coordinates. @@ -165,7 +165,7 @@ class ScriptAvatarData : public QObject { * @returns {Quat[]} The rotations of all joints relative to each's parent, or [] if the avatar data aren't * available. The values are in the same order as the array returned by {@link ScriptAvatar.getJointNames}. */ - Q_INVOKABLE QVector getJointRotations() const; + Q_INVOKABLE QVector> getJointRotations() const; /*@jsdoc * Gets the translations of all joints in the avatar. Each joint's translation is relative to its parent joint, in @@ -262,7 +262,7 @@ public slots: * @returns {Quat} The rotation of the joint relative to the avatar, or {@link Quat(0)|Quat.IDENTITY} if the avatar data * aren't available. */ - glm::quat getAbsoluteJointRotationInObjectFrame(int index) const; + glm::qua getAbsoluteJointRotationInObjectFrame(int index) const; /*@jsdoc * Gets the translation of a joint relative to the avatar. diff --git a/libraries/controllers/src/controllers/ScriptingInterface.h b/libraries/controllers/src/controllers/ScriptingInterface.h index 8db3df5011c..4ba8464636d 100644 --- a/libraries/controllers/src/controllers/ScriptingInterface.h +++ b/libraries/controllers/src/controllers/ScriptingInterface.h @@ -53,9 +53,9 @@ namespace controller { public slots: virtual bool isActive() const = 0; virtual glm::vec3 getAbsTranslation() const = 0; - virtual glm::quat getAbsRotation() const = 0; + virtual glm::qua getAbsRotation() const = 0; virtual glm::vec3 getLocTranslation() const = 0; - virtual glm::quat getLocRotation() const = 0; + virtual glm::qua getLocRotation() const = 0; signals: //void spatialEvent(const SpatialEvent& event); diff --git a/libraries/controllers/src/controllers/impl/RouteBuilderProxy.h b/libraries/controllers/src/controllers/impl/RouteBuilderProxy.h index da3e3ec26be..ce4508e5786 100644 --- a/libraries/controllers/src/controllers/impl/RouteBuilderProxy.h +++ b/libraries/controllers/src/controllers/impl/RouteBuilderProxy.h @@ -403,7 +403,7 @@ class RouteBuilderProxy : public QObject { * @returns {RouteObject} The RouteObject with the pre-rotation applied. */ // No JSDoc example because filter not currently used. - Q_INVOKABLE QObject* rotate(glm::quat rotation); + Q_INVOKABLE QObject* rotate(glm::qua rotation); /*@jsdoc * Filters {@link Pose} route values to be smoothed by a low velocity filter. The filter's rotation and translation diff --git a/libraries/entities/src/EntityScriptingInterface.h b/libraries/entities/src/EntityScriptingInterface.h index 458b39db2eb..3ce889cac80 100644 --- a/libraries/entities/src/EntityScriptingInterface.h +++ b/libraries/entities/src/EntityScriptingInterface.h @@ -1408,7 +1408,7 @@ public slots: * }, 2000); */ // FIXME move to a renderable entity interface - Q_INVOKABLE glm::quat getAbsoluteJointRotationInObjectFrame(const QUuid& entityID, int jointIndex); + Q_INVOKABLE glm::qua getAbsoluteJointRotationInObjectFrame(const QUuid& entityID, int jointIndex); /*@jsdoc * Sets the translation of a joint in a {@link Entities.EntityProperties-Model|Model} entity relative to the entity's @@ -1453,7 +1453,7 @@ public slots: * }, 2000); */ // FIXME move to a renderable entity interface - Q_INVOKABLE bool setAbsoluteJointRotationInObjectFrame(const QUuid& entityID, int jointIndex, glm::quat rotation); + Q_INVOKABLE bool setAbsoluteJointRotationInObjectFrame(const QUuid& entityID, int jointIndex, glm::qua rotation); /*@jsdoc @@ -1491,7 +1491,7 @@ public slots: * }, 2000); */ // FIXME move to a renderable entity interface - Q_INVOKABLE glm::quat getLocalJointRotation(const QUuid& entityID, int jointIndex); + Q_INVOKABLE glm::qua getLocalJointRotation(const QUuid& entityID, int jointIndex); /*@jsdoc * Sets the local translation of a joint in a {@link Entities.EntityProperties-Model|Model} entity. @@ -1533,7 +1533,7 @@ public slots: * }, 2000); */ // FIXME move to a renderable entity interface - Q_INVOKABLE bool setLocalJointRotation(const QUuid& entityID, int jointIndex, glm::quat rotation); + Q_INVOKABLE bool setLocalJointRotation(const QUuid& entityID, int jointIndex, glm::qua rotation); /*@jsdoc @@ -1588,7 +1588,7 @@ public slots: * }, 2000); */ // FIXME move to a renderable entity interface - Q_INVOKABLE bool setLocalJointRotations(const QUuid& entityID, const QVector& rotations); + Q_INVOKABLE bool setLocalJointRotations(const QUuid& entityID, const QVector>& rotations); /*@jsdoc * Sets the local rotations and translations of joints in a {@link Entities.EntityProperties-Model|Model} entity. This is @@ -1604,7 +1604,7 @@ public slots: */ // FIXME move to a renderable entity interface Q_INVOKABLE bool setLocalJointsData(const QUuid& entityID, - const QVector& rotations, + const QVector>& rotations, const QVector& translations); @@ -2078,7 +2078,7 @@ public slots: * @param {boolean} [scalesWithParent=false] - Not used in the calculation. * @returns {Quat} The rotation converted to local coordinates if successful, otherwise {@link Quat(0)|Quat.IDENTITY}. */ - Q_INVOKABLE glm::quat worldToLocalRotation(glm::quat worldRotation, const QUuid& parentID, + Q_INVOKABLE glm::qua worldToLocalRotation(glm::qua worldRotation, const QUuid& parentID, int parentJointIndex = -1, bool scalesWithParent = false); /*@jsdoc * Converts a velocity in world coordinates to a velocity in an avatar, entity, or joint's local coordinates. @@ -2142,7 +2142,7 @@ public slots: * @param {boolean} [scalesWithParent= false] - Not used in the calculation. * @returns {Quat} The rotation converted to local coordinates if successful, otherwise {@link Quat(0)|Quat.IDENTITY}. */ - Q_INVOKABLE glm::quat localToWorldRotation(glm::quat localRotation, const QUuid& parentID, + Q_INVOKABLE glm::qua localToWorldRotation(glm::qua localRotation, const QUuid& parentID, int parentJointIndex = -1, bool scalesWithParent = false); /*@jsdoc * Converts a velocity in an avatar, entity, or joint's local coordinate to a velocity in world coordinates. diff --git a/libraries/graphics-scripting/src/graphics-scripting/ScriptableMeshPart.h b/libraries/graphics-scripting/src/graphics-scripting/ScriptableMeshPart.h index 5c09c09ebe2..6cbf479affc 100644 --- a/libraries/graphics-scripting/src/graphics-scripting/ScriptableMeshPart.h +++ b/libraries/graphics-scripting/src/graphics-scripting/ScriptableMeshPart.h @@ -203,7 +203,7 @@ namespace scriptable { *

Warning: Currently doesn't work as expected.

* @returns {Graphics.MeshExtents} The resulting mesh extents, in model coordinates. */ - QVariantMap rotate(const glm::quat& rotation, const glm::vec3& origin = glm::vec3(NAN)); + QVariantMap rotate(const glm::qua& rotation, const glm::vec3& origin = glm::vec3(NAN)); /*@jsdoc * Scales, rotates, and translates the mesh. diff --git a/libraries/script-engine/src/Mat4.h b/libraries/script-engine/src/Mat4.h index 8c56f513ab8..3402b548d34 100644 --- a/libraries/script-engine/src/Mat4.h +++ b/libraries/script-engine/src/Mat4.h @@ -72,7 +72,7 @@ public slots: * // (0.739199, 0.280330, 0.612372, 0.000000), * // (10.000000, 11.000000, 12.000000, 1.000000)) */ - glm::mat4 createFromRotAndTrans(const glm::quat& rot, const glm::vec3& trans) const; + glm::mat4 createFromRotAndTrans(const glm::qua& rot, const glm::vec3& trans) const; /*@jsdoc * Creates a matrix that represents a scale, rotation, and translation. @@ -92,7 +92,7 @@ public slots: * // (1.478398, 0.560660, 1.224745, 0.000000), * // (10.000000, 11.000000, 12.000000, 1.000000)) */ - glm::mat4 createFromScaleRotAndTrans(const glm::vec3& scale, const glm::quat& rot, const glm::vec3& trans) const; + glm::mat4 createFromScaleRotAndTrans(const glm::vec3& scale, const glm::qua& rot, const glm::vec3& trans) const; /*@jsdoc * Creates a matrix from columns of values. @@ -170,7 +170,7 @@ public slots: * print("Rotation: " + JSON.stringify(Quat.safeEulerAngles(rot))); * // Rotation: {"x":29.999998092651367,"y":45.00000762939453,"z":60.000003814697266} */ - glm::quat extractRotation(const glm::mat4& m) const; + glm::qua extractRotation(const glm::mat4& m) const; /*@jsdoc * Extracts the scale from a matrix. diff --git a/libraries/script-engine/src/Quat.h b/libraries/script-engine/src/Quat.h index bf8380a6bd3..039e54a5055 100644 --- a/libraries/script-engine/src/Quat.h +++ b/libraries/script-engine/src/Quat.h @@ -62,7 +62,7 @@ /// Provides the Quat scripting interface class Quat : public QObject, protected Scriptable { Q_OBJECT - Q_PROPERTY(glm::quat IDENTITY READ IDENTITY CONSTANT) + Q_PROPERTY(glm::qua IDENTITY READ IDENTITY CONSTANT) public slots: @@ -79,7 +79,7 @@ public slots: * var handOrientation = Quat.multiply(MyAvatar.orientation, handPose.rotation); * } */ - glm::quat multiply(const glm::quat& q1, const glm::quat& q2); + glm::qua multiply(const glm::qua& q1, const glm::qua& q2); /*@jsdoc * Normalizes a quaternion. @@ -95,7 +95,7 @@ public slots: * // Use currentRotatation for something. * } */ - glm::quat normalize(const glm::quat& q); + glm::qua normalize(const glm::qua& q); /*@jsdoc * Calculates the conjugate of a quaternion. For a unit quaternion, its conjugate is the same as its @@ -111,7 +111,7 @@ public slots: * var identity = Quat.multiply(conjugate, quaternion); * Quat.print("identity", identity, true); // dvec3(0.000000, 0.000000, 0.000000) */ - glm::quat conjugate(const glm::quat& q); + glm::qua conjugate(const glm::qua& q); /*@jsdoc * Calculates a camera orientation given an eye position, point of interest, and "up" direction. The camera's negative @@ -127,7 +127,7 @@ public slots: * Camera.mode = "independent"; * Camera.orientation = Quat.lookAt(Camera.position, Vec3.ZERO, Vec3.UNIT_NEG_Y); */ - glm::quat lookAt(const glm::vec3& eye, const glm::vec3& center, const glm::vec3& up); + glm::qua lookAt(const glm::vec3& eye, const glm::vec3& center, const glm::vec3& up); /*@jsdoc * Calculates a camera orientation given an eye position and point of interest. The camera's negative z-axis is the forward @@ -143,7 +143,7 @@ public slots: * Camera.mode = "independent"; * Camera.orientation = Quat.lookAtSimple(Camera.position, Vec3.ZERO); */ - glm::quat lookAtSimple(const glm::vec3& eye, const glm::vec3& center); + glm::qua lookAtSimple(const glm::vec3& eye, const glm::vec3& center); /*@jsdoc * Calculates the shortest rotation from a first vector onto a second. @@ -160,7 +160,7 @@ public slots: * Entities.editEntity(entityID, properties); * entityVelocity = newVelocity; */ - glm::quat rotationBetween(const glm::vec3& v1, const glm::vec3& v2); + glm::qua rotationBetween(const glm::vec3& v1, const glm::vec3& v2); /*@jsdoc * Generates a quaternion from a {@link Vec3} of Euler angles in degrees. @@ -174,7 +174,7 @@ public slots: * eulerAngles.z = 0; * var newOrientation = Quat.fromVec3Degrees(eulerAngles); */ - glm::quat fromVec3Degrees(const glm::vec3& vec3); + glm::qua fromVec3Degrees(const glm::vec3& vec3); /*@jsdoc * Generates a quaternion from a {@link Vec3} of Euler angles in radians. @@ -185,7 +185,7 @@ public slots: * @example Create a rotation of 180 degrees about the y axis. * var rotation = Quat.fromVec3Radians({ x: 0, y: Math.PI, z: 0 }); */ - glm::quat fromVec3Radians(const glm::vec3& vec3); + glm::qua fromVec3Radians(const glm::vec3& vec3); /*@jsdoc * Generates a quaternion from pitch, yaw, and roll values in degrees. @@ -197,7 +197,7 @@ public slots: * @example Create a rotation of 180 degrees about the y axis. * var rotation = Quat.fromPitchYawRollDegrees(0, 180, 0 ); */ - glm::quat fromPitchYawRollDegrees(float pitch, float yaw, float roll); + glm::qua fromPitchYawRollDegrees(float pitch, float yaw, float roll); /*@jsdoc * Generates a quaternion from pitch, yaw, and roll values in radians. @@ -209,7 +209,7 @@ public slots: * @example Create a rotation of 180 degrees about the y axis. * var rotation = Quat.fromPitchYawRollRadians(0, Math.PI, 0); */ - glm::quat fromPitchYawRollRadians(float pitch, float yaw, float roll); + glm::qua fromPitchYawRollRadians(float pitch, float yaw, float roll); /*@jsdoc * Calculates the inverse of a quaternion. For a unit quaternion, its inverse is the same as its @@ -225,7 +225,7 @@ public slots: * var identity = Quat.multiply(inverse, quaternion); * Quat.print("identity", identity, true); // dvec3(0.000000, 0.000000, 0.000000) */ - glm::quat inverse(const glm::quat& q); + glm::qua inverse(const glm::qua& q); /*@jsdoc * Gets the "front" direction that the camera would face if its orientation was set to the quaternion value. @@ -235,7 +235,7 @@ public slots: * @param {Quat} orientation - A quaternion representing an orientation. * @returns {Vec3} The negative z-axis rotated by orientation. */ - glm::vec3 getFront(const glm::quat& orientation) { return getForward(orientation); } + glm::vec3 getFront(const glm::qua& orientation) { return getForward(orientation); } /*@jsdoc * Gets the "forward" direction that the camera would face if its orientation was set to the quaternion value. @@ -248,7 +248,7 @@ public slots: * var forward = Quat.getForward(Quat.IDENTITY); * print(JSON.stringify(forward)); // {"x":0,"y":0,"z":-1} */ - glm::vec3 getForward(const glm::quat& orientation); + glm::vec3 getForward(const glm::qua& orientation); /*@jsdoc * Gets the "right" direction that the camera would have if its orientation was set to the quaternion value. @@ -257,7 +257,7 @@ public slots: * @param {Quat} orientation - A quaternion representing an orientation. * @returns {Vec3} The x-axis rotated by orientation. */ - glm::vec3 getRight(const glm::quat& orientation); + glm::vec3 getRight(const glm::qua& orientation); /*@jsdoc * Gets the "up" direction that the camera would have if its orientation was set to the quaternion value. @@ -266,7 +266,7 @@ public slots: * @param {Quat} orientation - A quaternion representing an orientation. * @returns {Vec3} The y-axis rotated by orientation. */ - glm::vec3 getUp(const glm::quat& orientation); + glm::vec3 getUp(const glm::qua& orientation); /*@jsdoc * Calculates the Euler angles for the quaternion, in degrees. (The "safe" in the name signifies that the angle results @@ -279,7 +279,7 @@ public slots: * var eulerAngles = Quat.safeEulerAngles(Camera.orientation); * print("Camera yaw: " + eulerAngles.y); */ - glm::vec3 safeEulerAngles(const glm::quat& orientation); + glm::vec3 safeEulerAngles(const glm::qua& orientation); /*@jsdoc * Generates a quaternion given an angle to rotate through and an axis to rotate about. @@ -292,7 +292,7 @@ public slots: * @example Calculate a rotation of 90 degrees about the direction your camera is looking. * var rotation = Quat.angleAxis(90, Quat.getForward(Camera.orientation)); */ - glm::quat angleAxis(float angle, const glm::vec3& v); + glm::qua angleAxis(float angle, const glm::vec3& v); /*@jsdoc * Gets the rotation axis for a quaternion. @@ -306,7 +306,7 @@ public slots: * print("Forward: " + JSON.stringify(forward)); * print("Axis: " + JSON.stringify(axis)); // Same value as forward. */ - glm::vec3 axis(const glm::quat& orientation); + glm::vec3 axis(const glm::qua& orientation); /*@jsdoc * Gets the rotation angle for a quaternion. @@ -320,7 +320,7 @@ public slots: * var angle = Quat.angle(rotation); * print("Angle: " + angle * 180 / Math.PI); // 90 degrees. */ - float angle(const glm::quat& orientation); + float angle(const glm::qua& orientation); // spherical linear interpolation // alpha: 0.0 to 1.0? @@ -342,7 +342,7 @@ public slots: * } * var newRotation = Quat.mix(startRotation, endRotation, mixFactor); */ - glm::quat mix(const glm::quat& q1, const glm::quat& q2, float alpha); + glm::qua mix(const glm::qua& q1, const glm::qua& q2, float alpha); /*@jsdoc * Computes a spherical linear interpolation between two rotations, for rotations that are not very similar. @@ -355,7 +355,7 @@ public slots: * q1's value; 1.0 returns q2s's value. * @returns {Quat} A spherical linear interpolation between rotations q1 and q2. */ - glm::quat slerp(const glm::quat& q1, const glm::quat& q2, float alpha); + glm::qua slerp(const glm::qua& q1, const glm::qua& q2, float alpha); /*@jsdoc * Computes a spherical quadrangle interpolation between two rotations along a path oriented toward two other rotations. @@ -370,7 +370,7 @@ public slots: * @returns {Quat} A spherical quadrangle interpolation between rotations q1 and q2 using control * points s1 and s2. */ - glm::quat squad(const glm::quat& q1, const glm::quat& q2, const glm::quat& s1, const glm::quat& s2, float h); + glm::qua squad(const glm::qua& q1, const glm::qua& q2, const glm::qua& s1, const glm::qua& s2, float h); /*@jsdoc * Calculates the dot product of two quaternions. The closer the quaternions are to each other the more non-zero the value @@ -391,7 +391,7 @@ public slots: * var equal = Math.abs(1 - Math.abs(dot)) < 0.000001; * print(equal); // true */ - float dot(const glm::quat& q1, const glm::quat& q2); + float dot(const glm::qua& q1, const glm::qua& q2); /*@jsdoc * Prints to the program log a text label followed by a quaternion's pitch, yaw, and roll Euler angles. @@ -409,7 +409,7 @@ public slots: * // Quaternion: {"x":0,"y":45.000003814697266,"z":0} * print("Quaternion: " + JSON.stringify(Quat.safeEulerAngles(quaternion))); */ - void print(const QString& label, const glm::quat& q, bool asDegrees = false); + void print(const QString& label, const glm::qua& q, bool asDegrees = false); /*@jsdoc * Tests whether two quaternions are equal. @@ -431,7 +431,7 @@ public slots: * var equal = Math.abs(1 - Math.abs(dot)) < 0.000001; * print(equal); // true */ - bool equal(const glm::quat& q1, const glm::quat& q2); + bool equal(const glm::qua& q1, const glm::qua& q2); /*@jsdoc * Cancels out the roll and pitch component of a quaternion so that its completely horizontal with a yaw pointing in the @@ -451,7 +451,7 @@ public slots: * Quat.print("", lookAt, true); // dvec3(0.000000, 22.245996, 0.000000) * */ - glm::quat cancelOutRollAndPitch(const glm::quat& q); + glm::qua cancelOutRollAndPitch(const glm::qua& q); /*@jsdoc * Cancels out the roll component of a quaternion so that its horizontal axis is level. @@ -469,10 +469,10 @@ public slots: * var lookAt = Quat.lookAtSimple(Vec3.ZERO, front); * Quat.print("", lookAt, true); // dvec3(-1.033004, 22.245996, -0.000000) */ - glm::quat cancelOutRoll(const glm::quat& q); + glm::qua cancelOutRoll(const glm::qua& q); private: - const glm::quat& IDENTITY() const { return Quaternions::IDENTITY; } + const glm::qua& IDENTITY() const { return Quaternions::IDENTITY; } }; diff --git a/libraries/shared/src/DebugDraw.h b/libraries/shared/src/DebugDraw.h index e80b63a5b65..641ad76540d 100644 --- a/libraries/shared/src/DebugDraw.h +++ b/libraries/shared/src/DebugDraw.h @@ -87,7 +87,7 @@ class DebugDraw : public QObject { * }); */ Q_INVOKABLE void drawRays(const std::vector>& lines, const glm::vec4& color, - const glm::vec3& translation = glm::vec3(0.0f, 0.0f, 0.0f), const glm::quat& rotation = glm::quat(1.0f, 0.0f, 0.0f, 0.0f)); + const glm::vec3& translation = glm::vec3(0.0f, 0.0f, 0.0f), const glm::qua& rotation = glm::qua(1.0f, 0.0f, 0.0f, 0.0f)); /*@jsdoc * Adds or updates a debug marker in world coordinates. This marker is drawn every frame until it is removed using @@ -112,7 +112,7 @@ class DebugDraw : public QObject { * DebugDraw.removeMarker(MARKER_NAME); * }, 5000); */ - Q_INVOKABLE void addMarker(const QString& key, const glm::quat& rotation, const glm::vec3& position, + Q_INVOKABLE void addMarker(const QString& key, const glm::qua& rotation, const glm::vec3& position, const glm::vec4& color, float size = 1.0f); /*@jsdoc @@ -145,7 +145,7 @@ class DebugDraw : public QObject { * DebugDraw.removeMyAvatarMarker(MARKER_NAME); * }, 5000); */ - Q_INVOKABLE void addMyAvatarMarker(const QString& key, const glm::quat& rotation, const glm::vec3& position, + Q_INVOKABLE void addMyAvatarMarker(const QString& key, const glm::qua& rotation, const glm::vec3& position, const glm::vec4& color, float size = 1.0f); /*@jsdoc @@ -155,7 +155,7 @@ class DebugDraw : public QObject { */ Q_INVOKABLE void removeMyAvatarMarker(const QString& key); - using MarkerInfo = std::tuple; + using MarkerInfo = std::tuple, glm::vec3, glm::vec4, float>; using MarkerMap = std::map; using Ray = std::tuple; using Rays = std::vector; @@ -168,8 +168,8 @@ class DebugDraw : public QObject { MarkerMap getMyAvatarMarkerMap() const; void updateMyAvatarPos(const glm::vec3& pos) { _myAvatarPos = pos; } const glm::vec3& getMyAvatarPos() const { return _myAvatarPos; } - void updateMyAvatarRot(const glm::quat& rot) { _myAvatarRot = rot; } - const glm::quat& getMyAvatarRot() const { return _myAvatarRot; } + void updateMyAvatarRot(const glm::qua& rot) { _myAvatarRot = rot; } + const glm::qua& getMyAvatarRot() const { return _myAvatarRot; } Rays getRays() const; void clearRays(); diff --git a/libraries/shared/src/shared/Camera.h b/libraries/shared/src/shared/Camera.h index 54dfea77cf8..2410fc47d4c 100644 --- a/libraries/shared/src/shared/Camera.h +++ b/libraries/shared/src/shared/Camera.h @@ -42,7 +42,7 @@ class Camera : public QObject { Q_OBJECT Q_PROPERTY(glm::vec3 position READ getPosition WRITE setPosition) - Q_PROPERTY(glm::quat orientation READ getOrientation WRITE setOrientation) + Q_PROPERTY(glm::qua orientation READ getOrientation WRITE setOrientation) Q_PROPERTY(QString mode READ getModeString WRITE setModeString NOTIFY modeUpdated) Q_PROPERTY(QVariantMap frustum READ getViewFrustum CONSTANT) Q_PROPERTY(bool captureMouse READ getCaptureMouse WRITE setCaptureMouse NOTIFY captureMouseChanged) @@ -104,7 +104,7 @@ public slots: * @function Camera.getOrientation * @returns {Quat} The current camera orientation. */ - glm::quat getOrientation() const { return _orientation; } + glm::qua getOrientation() const { return _orientation; } /*@jsdoc * Sets the camera orientation. You can also set the orientation using the {@link Camera|Camera.orientation} property. Only @@ -112,7 +112,7 @@ public slots: * @function Camera.setOrientation * @param {Quat} orientation - The orientation to set the camera to. */ - void setOrientation(const glm::quat& orientation); + void setOrientation(const glm::qua& orientation); /*@jsdoc * Gets the current mouse capture state. diff --git a/tests/script-engine/src/ScriptEngineTests.cpp b/tests/script-engine/src/ScriptEngineTests.cpp index 8ad353f8464..c6e13452855 100644 --- a/tests/script-engine/src/ScriptEngineTests.cpp +++ b/tests/script-engine/src/ScriptEngineTests.cpp @@ -326,7 +326,7 @@ void ScriptEngineTests::testQuat() { }); connect(sm.get(), &ScriptManager::unhandledException, [](std::shared_ptr exception){ - QVERIFY(exception->errorMessage.contains("undefined to glm::quat")); + QVERIFY(exception->errorMessage.contains("undefined to glm::qua")); }); From d8dbf09c175f91470312e0e64509c818fa637fb8 Mon Sep 17 00:00:00 2001 From: Karol Suprynowicz Date: Sun, 28 Sep 2025 00:22:50 +0200 Subject: [PATCH 013/111] Remove glm::vec3 from scripting API --- .../src/octree/OctreeHeadlessViewer.h | 4 +- interface/src/SecondaryCamera.h | 8 +- interface/src/avatar/MyAvatar.h | 96 +++++++++---------- .../scripting/ClipboardScriptingInterface.h | 4 +- .../src/scripting/HMDScriptingInterface.h | 16 ++-- .../src/scripting/WindowScriptingInterface.h | 2 +- interface/src/ui/overlays/Overlays.h | 2 +- libraries/audio-client/src/AudioClient.h | 2 +- .../src/avatars-renderer/Avatar.h | 32 +++---- .../src/avatars-renderer/ScriptAvatar.h | 18 ++-- libraries/avatars/src/AvatarData.h | 30 +++--- libraries/avatars/src/AvatarHashMap.h | 4 +- libraries/avatars/src/ScriptAvatarData.h | 16 ++-- .../src/controllers/ScriptingInterface.h | 4 +- .../src/controllers/impl/RouteBuilderProxy.h | 2 +- .../src/ModelScriptingInterface.h | 4 +- .../entities/src/EntityScriptingInterface.h | 66 ++++++------- libraries/entities/src/EntityTree.h | 2 +- .../src/graphics-scripting/ScriptableMesh.h | 2 +- .../graphics-scripting/ScriptableMeshPart.h | 10 +- libraries/networking/src/AddressManager.h | 2 +- libraries/render-utils/src/HazeStage.h | 4 +- libraries/script-engine/src/Mat4.h | 20 ++-- libraries/script-engine/src/Quat.h | 24 ++--- libraries/script-engine/src/Vec3.h | 80 ++++++++-------- libraries/shared/src/DebugDraw.h | 12 +-- libraries/shared/src/shared/Camera.h | 10 +- 27 files changed, 238 insertions(+), 238 deletions(-) diff --git a/assignment-client/src/octree/OctreeHeadlessViewer.h b/assignment-client/src/octree/OctreeHeadlessViewer.h index c9619c33c6d..58044bf3d95 100644 --- a/assignment-client/src/octree/OctreeHeadlessViewer.h +++ b/assignment-client/src/octree/OctreeHeadlessViewer.h @@ -43,7 +43,7 @@ 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. @@ -99,7 +99,7 @@ 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. diff --git a/interface/src/SecondaryCamera.h b/interface/src/SecondaryCamera.h index b846367d373..f7eac000181 100644 --- a/interface/src/SecondaryCamera.h +++ b/interface/src/SecondaryCamera.h @@ -21,7 +21,7 @@ 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::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. @@ -31,7 +31,7 @@ class SecondaryCameraJobConfig : public render::Task::Config { // Exposes second public: QUuid attachedEntityId; QUuid portalEntranceEntityId; - glm::vec3 position; + glm::vec<3,float,glm::packed_highp> position; glm::qua orientation; float vFoV { DEFAULT_FIELD_OF_VIEW_DEGREES }; float nearClipPlaneDistance { DEFAULT_NEAR_CLIP }; @@ -45,8 +45,8 @@ class SecondaryCameraJobConfig : public render::Task::Config { // Exposes second signals: void dirty(); public slots: - glm::vec3 getPosition() { return position; } - void setPosition(glm::vec3 pos); + 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); diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 9dc62f9e0c0..ddd485e7334 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,7 +337,7 @@ 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::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) @@ -345,10 +345,10 @@ class MyAvatar : public Avatar { 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) @@ -579,7 +579,7 @@ class MyAvatar : public Avatar { void preDisplaySide(const RenderArgs* renderArgs); const glm::mat4& getHMDSensorMatrix() const { return _hmdSensorMatrix; } - const glm::vec3& getHMDSensorPosition() const { return _hmdSensorPosition; } + const glm::vec<3,float,glm::packed_highp>& getHMDSensorPosition() const { return _hmdSensorPosition; } const glm::qua& getHMDSensorOrientation() const { return _hmdSensorOrientation; } /*@jsdoc @@ -627,7 +627,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(); } @@ -1097,7 +1097,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. @@ -1137,7 +1137,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. @@ -1148,7 +1148,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. @@ -1169,7 +1169,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 @@ -1182,7 +1182,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 @@ -1191,7 +1191,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 @@ -1200,7 +1200,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 @@ -1260,14 +1260,14 @@ class MyAvatar : public Avatar { void clearLookAtTargetAvatar(); virtual void setJointRotations(const QVector>& jointRotations) override; - virtual void setJointData(int index, const glm::qua& rotation, const glm::vec3& translation) 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::vec3& translation) 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::qua& rotation, 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::vec3& translation) 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; @@ -1280,7 +1280,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::qua& orientation); + Q_INVOKABLE bool pinJoint(int index, const glm::vec<3,float,glm::packed_highp>& position, const glm::qua& orientation); bool isJointPinned(int index); @@ -1333,7 +1333,7 @@ class MyAvatar : public Avatar { void updateMotors(); void prepareForPhysicsSimulation(); - void nextAttitude(glm::vec3 position, glm::qua 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; } @@ -1367,8 +1367,8 @@ 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::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; } @@ -1588,7 +1588,7 @@ 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; @@ -1613,7 +1613,7 @@ class MyAvatar : public Avatar { glm::mat4 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. @@ -1628,7 +1628,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 @@ -1638,14 +1638,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; } @@ -1678,7 +1678,7 @@ class MyAvatar : public Avatar { float computeStandingHeightMode(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 @@ -1688,14 +1688,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. @@ -1710,14 +1710,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. @@ -1733,7 +1733,7 @@ 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::qua getLookAtRotation() { return _lookAtYaw * _lookAtPitch; } @@ -1765,7 +1765,7 @@ class MyAvatar : public Avatar { * }, 10000); */ Q_INVOKABLE const QUuid grab(const QUuid& targetID, int parentJointIndex, - glm::vec3 positionalOffset, glm::qua rotationalOffset); + glm::vec<3,float,glm::packed_highp> positionalOffset, glm::qua rotationalOffset); /*@jsdoc * Releases (deletes) a grab to stop grabbing an entity. @@ -1845,7 +1845,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::qua& 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. @@ -1853,7 +1853,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::qua& 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}. @@ -1878,8 +1878,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; @@ -1956,7 +1956,7 @@ public slots: * @param {boolean} [shouldFaceLocation=false] - Set to true to position the avatar a short distance away from * the new position and orientate the avatar to face the position. */ - void goToFeetLocation(const glm::vec3& newPosition, bool hasOrientation = false, + void goToFeetLocation(const glm::vec<3,float,glm::packed_highp>& newPosition, bool hasOrientation = false, const glm::qua& newOrientation = glm::qua(), bool shouldFaceLocation = false); /*@jsdoc @@ -1969,7 +1969,7 @@ public slots: * the new position and orientate the avatar to face the position. * @param {boolean} [withSafeLanding=true] - Set to false to disable safe landing when teleporting. */ - void goToLocation(const glm::vec3& newPosition, + void goToLocation(const glm::vec<3,float,glm::packed_highp>& newPosition, bool hasOrientation = false, const glm::qua& newOrientation = glm::qua(), bool shouldFaceLocation = false, bool withSafeLanding = true); /*@jsdoc @@ -1984,7 +1984,7 @@ public slots: * @function MyAvatar.goToLocationAndEnableCollisions * @param {Vec3} position - The new position for the avatar, in world coordinates. */ - void goToLocationAndEnableCollisions(const glm::vec3& newPosition); + void goToLocationAndEnableCollisions(const glm::vec<3,float,glm::packed_highp>& newPosition); /*@jsdoc * @function MyAvatar.safeLanding @@ -1992,7 +1992,7 @@ public slots: * @returns {boolean} true if the avatar was moved, false if it wasn't. * @deprecated This function is deprecated and will be removed. */ - bool safeLanding(const glm::vec3& position); + bool safeLanding(const glm::vec<3,float,glm::packed_highp>& position); /*@jsdoc @@ -2017,7 +2017,7 @@ public slots: * properties instead. */ // Set/Get update the thrust that will move the avatar around - void addThrust(glm::vec3 newThrust) { _thrust += newThrust; }; + void addThrust(glm::vec<3,float,glm::packed_highp> newThrust) { _thrust += newThrust; }; /*@jsdoc * Gets the thrust currently being applied to your avatar. @@ -2026,7 +2026,7 @@ public slots: * @deprecated This function is deprecated and will be removed. Use {@link MyAvatar|MyAvatar.motorVelocity} and related * properties instead. */ - glm::vec3 getThrust() { return _thrust; }; + glm::vec<3,float,glm::packed_highp> getThrust() { return _thrust; }; /*@jsdoc * Sets the thrust to be applied to your avatar for a short while. @@ -2035,7 +2035,7 @@ public slots: * @deprecated This function is deprecated and will be removed. Use {@link MyAvatar|MyAvatar.motorVelocity} and related * properties instead. */ - void setThrust(glm::vec3 newThrust) { _thrust = newThrust; } + void setThrust(glm::vec<3,float,glm::packed_highp> newThrust) { _thrust = newThrust; } /*@jsdoc @@ -2211,7 +2211,7 @@ public slots: * @function MyAvatar.getPositionForAudio * @returns {Vec3} Your listening position. */ - glm::vec3 getPositionForAudio(); + glm::vec<3,float,glm::packed_highp> getPositionForAudio(); /*@jsdoc * Gets the orientation of your listening position for spatialized audio. The orientation depends on the value of the diff --git a/interface/src/scripting/ClipboardScriptingInterface.h b/interface/src/scripting/ClipboardScriptingInterface.h index 41f50e34686..f7fc5cf2408 100644 --- a/interface/src/scripting/ClipboardScriptingInterface.h +++ b/interface/src/scripting/ClipboardScriptingInterface.h @@ -44,7 +44,7 @@ class ClipboardScriptingInterface : public QObject { * } * } */ - Q_INVOKABLE glm::vec3 getContentsDimensions(); + Q_INVOKABLE glm::vec<3,float,glm::packed_highp> getContentsDimensions(); /*@jsdoc * Gets the largest dimension of the extents of the entities held in the clipboard. @@ -121,7 +121,7 @@ class ClipboardScriptingInterface : public QObject { * @returns {Uuid[]} The IDs of the new entities that were created as a result of the paste operation. If entities couldn't * be created then an empty array is returned. */ - Q_INVOKABLE QVector pasteEntities(glm::vec3 position, const QString& entityHostType = "domain"); + Q_INVOKABLE QVector pasteEntities(glm::vec<3,float,glm::packed_highp> position, const QString& entityHostType = "domain"); }; #endif // hifi_ClipboardScriptingInterface_h diff --git a/interface/src/scripting/HMDScriptingInterface.h b/interface/src/scripting/HMDScriptingInterface.h index 72a632150ed..3cc7db442ca 100644 --- a/interface/src/scripting/HMDScriptingInterface.h +++ b/interface/src/scripting/HMDScriptingInterface.h @@ -90,7 +90,7 @@ class ScriptEngine; */ class HMDScriptingInterface : public AbstractHMDScriptingInterface, public Dependency { Q_OBJECT - Q_PROPERTY(glm::vec3 position READ getPosition) + Q_PROPERTY(glm::vec<3,float,glm::packed_highp> position READ getPosition) Q_PROPERTY(glm::qua orientation READ getOrientation) Q_PROPERTY(bool showTablet READ getShouldShowTablet) Q_PROPERTY(bool tabletContextualMode READ getTabletContextualMode) @@ -103,7 +103,7 @@ class HMDScriptingInterface : public AbstractHMDScriptingInterface, public Depen Q_PROPERTY(int miniTabletHand READ getCurrentMiniTabletHand WRITE setCurrentMiniTabletHand) Q_PROPERTY(bool miniTabletEnabled READ getMiniTabletEnabled WRITE setMiniTabletEnabled) Q_PROPERTY(QVariant playArea READ getPlayAreaRect); - Q_PROPERTY(QVector sensorPositions READ getSensorPositions); + Q_PROPERTY(QVector> sensorPositions READ getSensorPositions); Q_PROPERTY(float visionSqueezeRatioX READ getVisionSqueezeRatioX WRITE setVisionSqueezeRatioX); Q_PROPERTY(float visionSqueezeRatioY READ getVisionSqueezeRatioY WRITE setVisionSqueezeRatioY); @@ -142,9 +142,9 @@ class HMDScriptingInterface : public AbstractHMDScriptingInterface, public Depen * Overlays.deleteOverlay(square); * }); */ - Q_INVOKABLE glm::vec3 calculateRayUICollisionPoint(const glm::vec3& position, const glm::vec3& direction) const; + Q_INVOKABLE glm::vec<3,float,glm::packed_highp> calculateRayUICollisionPoint(const glm::vec<3,float,glm::packed_highp>& position, const glm::vec<3,float,glm::packed_highp>& direction) const; - glm::vec3 calculateParabolaUICollisionPoint(const glm::vec3& position, const glm::vec3& velocity, const glm::vec3& acceleration, float& parabolicDistance) const; + glm::vec<3,float,glm::packed_highp> calculateParabolaUICollisionPoint(const glm::vec<3,float,glm::packed_highp>& position, const glm::vec<3,float,glm::packed_highp>& velocity, const glm::vec<3,float,glm::packed_highp>& acceleration, float& parabolicDistance) const; /*@jsdoc * Gets the 2D HUD overlay coordinates of a 3D point on the HUD overlay. @@ -170,7 +170,7 @@ class HMDScriptingInterface : public AbstractHMDScriptingInterface, public Depen * Overlays.deleteOverlay(square); * }); */ - Q_INVOKABLE glm::vec2 overlayFromWorldPoint(const glm::vec3& position) const; + Q_INVOKABLE glm::vec2 overlayFromWorldPoint(const glm::vec<3,float,glm::packed_highp>& position) const; /*@jsdoc * Gets the 3D world coordinates of a 2D point on the HUD overlay. @@ -179,7 +179,7 @@ class HMDScriptingInterface : public AbstractHMDScriptingInterface, public Depen * @param {Vec2} coordinates - The point on the HUD overlay in HUD coordinates. * @returns {Vec3} The point on the HUD overlay in world coordinates. */ - Q_INVOKABLE glm::vec3 worldPointFromOverlay(const glm::vec2& overlay) const; + Q_INVOKABLE glm::vec<3,float,glm::packed_highp> worldPointFromOverlay(const glm::vec2& overlay) const; /*@jsdoc * Gets the 2D point on the HUD overlay represented by given spherical coordinates. @@ -488,9 +488,9 @@ class HMDScriptingInterface : public AbstractHMDScriptingInterface, public Depen bool getAwayStateWhenFocusLostInVREnabled(); QVariant getPlayAreaRect(); - QVector getSensorPositions(); + QVector> getSensorPositions(); - glm::vec3 getPosition() const; + glm::vec<3,float,glm::packed_highp> getPosition() const; private: bool _showTablet { false }; diff --git a/interface/src/scripting/WindowScriptingInterface.h b/interface/src/scripting/WindowScriptingInterface.h index af7f25ea9ed..c8215408c34 100644 --- a/interface/src/scripting/WindowScriptingInterface.h +++ b/interface/src/scripting/WindowScriptingInterface.h @@ -400,7 +400,7 @@ public slots: * ".jpeg", ".png", or ".webp" — or if not provided then in the format chosen in general settings, * Default is PNG. Animated images are saved in GIF format.

*/ - void takeSecondaryCamera360Snapshot(const glm::vec3& cameraPosition, const bool& cubemapOutputFormat = false, const bool& notify = true, const QString& filename = QString()); + void takeSecondaryCamera360Snapshot(const glm::vec<3,float,glm::packed_highp>& cameraPosition, const bool& cubemapOutputFormat = false, const bool& notify = true, const QString& filename = QString()); /*@jsdoc * Emits a {@link Window.connectionAdded|connectionAdded} or a {@link Window.connectionError|connectionError} signal that diff --git a/interface/src/ui/overlays/Overlays.h b/interface/src/ui/overlays/Overlays.h index 9b12a4f6ccd..21fed55c41b 100644 --- a/interface/src/ui/overlays/Overlays.h +++ b/interface/src/ui/overlays/Overlays.h @@ -371,7 +371,7 @@ public slots: * var overlaysFound = Overlays.findOverlays(MyAvatar.position, 5.0); * print("Overlays found: " + JSON.stringify(overlaysFound)); */ - QVector findOverlays(const glm::vec3& center, float radius); + QVector findOverlays(const glm::vec<3,float,glm::packed_highp>& center, float radius); /*@jsdoc * Checks whether an overlay's (or entity's) assets have been loaded. For example, for an diff --git a/libraries/audio-client/src/AudioClient.h b/libraries/audio-client/src/AudioClient.h index 0f09ed68036..48166fb886b 100644 --- a/libraries/audio-client/src/AudioClient.h +++ b/libraries/audio-client/src/AudioClient.h @@ -166,7 +166,7 @@ class AudioClient : public AbstractAudioInterface, public Dependency { void setIsPlayingBackRecording(bool isPlayingBackRecording) { _isPlayingBackRecording = isPlayingBackRecording; } - Q_INVOKABLE void setAvatarBoundingBoxParameters(glm::vec3 corner, glm::vec3 scale); + Q_INVOKABLE void setAvatarBoundingBoxParameters(glm::vec3 corner, glm::vec<3,float,glm::packed_highp> scale); bool outputLocalInjector(const AudioInjectorPointer& injector) override; diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.h b/libraries/avatars-renderer/src/avatars-renderer/Avatar.h index b7d0c481c77..298b75fe631 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.h +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.h @@ -136,7 +136,7 @@ class Avatar : public AvatarData, public scriptable::ModelProvider, public MetaM * @property {Vec3} skeletonOffset - Can be used to apply a translation offset between the avatar's position and the * registration point of the 3D model. */ - Q_PROPERTY(glm::vec3 skeletonOffset READ getSkeletonOffset WRITE setSkeletonOffset) + Q_PROPERTY(glm::vec<3,float,glm::packed_highp> skeletonOffset READ getSkeletonOffset WRITE setSkeletonOffset) public: static void setShowAvatars(bool render); @@ -225,7 +225,7 @@ class Avatar : public AvatarData, public scriptable::ModelProvider, public MetaM * @returns {Vec3} The default translation of the joint (in model coordinates) if the joint index is valid, otherwise * {@link Vec3(0)|Vec3.ZERO}. */ - Q_INVOKABLE virtual glm::vec3 getDefaultJointTranslation(int index) const; + Q_INVOKABLE virtual glm::vec<3,float,glm::packed_highp> getDefaultJointTranslation(int index) const; /*@jsdoc * Gets the default joint rotations in avatar coordinates. @@ -253,7 +253,7 @@ class Avatar : public AvatarData, public scriptable::ModelProvider, public MetaM * var defaultHeadTranslation = MyAvatar.getAbsoluteDefaultJointTranslationInObjectFrame(headIndex); * print("Default head translation: " + JSON.stringify(defaultHeadTranslation)); */ - Q_INVOKABLE virtual glm::vec3 getAbsoluteDefaultJointTranslationInObjectFrame(int index) const; + Q_INVOKABLE virtual glm::vec<3,float,glm::packed_highp> getAbsoluteDefaultJointTranslationInObjectFrame(int index) const; virtual glm::vec3 getAbsoluteJointScaleInObjectFrame(int index) const override; @@ -291,7 +291,7 @@ class Avatar : public AvatarData, public scriptable::ModelProvider, public MetaM * @param {number} [jointIndex=-1] - The index of the joint. * @returns {Vec3} The position in the joint's coordinate system, or avatar coordinate system if no joint is specified. */ - Q_INVOKABLE glm::vec3 worldToJointPoint(const glm::vec3& position, const int jointIndex = -1) const; + Q_INVOKABLE glm::vec<3,float,glm::packed_highp> worldToJointPoint(const glm::vec<3,float,glm::packed_highp>& position, const int jointIndex = -1) const; /*@jsdoc * Transforms a direction in world coordinates to a direction in a joint's coordinates, or avatar coordinates if no joint @@ -301,7 +301,7 @@ class Avatar : public AvatarData, public scriptable::ModelProvider, public MetaM * @param {number} [jointIndex=-1] - The index of the joint. * @returns {Vec3} The direction in the joint's coordinate system, or avatar coordinate system if no joint is specified. */ - Q_INVOKABLE glm::vec3 worldToJointDirection(const glm::vec3& direction, const int jointIndex = -1) const; + Q_INVOKABLE glm::vec<3,float,glm::packed_highp> worldToJointDirection(const glm::vec<3,float,glm::packed_highp>& direction, const int jointIndex = -1) const; /*@jsdoc * Transforms a rotation in world coordinates to a rotation in a joint's coordinates, or avatar coordinates if no joint is @@ -321,7 +321,7 @@ class Avatar : public AvatarData, public scriptable::ModelProvider, public MetaM * @param {number} [jointIndex=-1] - The index of the joint. * @returns {Vec3} The position in world coordinates. */ - Q_INVOKABLE glm::vec3 jointToWorldPoint(const glm::vec3& position, const int jointIndex = -1) const; + Q_INVOKABLE glm::vec<3,float,glm::packed_highp> jointToWorldPoint(const glm::vec<3,float,glm::packed_highp>& position, const int jointIndex = -1) const; /*@jsdoc * Transforms a direction in a joint's coordinates, or avatar coordinates if no joint is specified, to a direction in world @@ -331,7 +331,7 @@ class Avatar : public AvatarData, public scriptable::ModelProvider, public MetaM * @param {number} [jointIndex=-1] - The index of the joint. * @returns {Vec3} The direction in world coordinates. */ - Q_INVOKABLE glm::vec3 jointToWorldDirection(const glm::vec3& direction, const int jointIndex = -1) const; + Q_INVOKABLE glm::vec<3,float,glm::packed_highp> jointToWorldDirection(const glm::vec<3,float,glm::packed_highp>& direction, const int jointIndex = -1) const; /*@jsdoc * Transforms a rotation in a joint's coordinates, or avatar coordinates if no joint is specified, to a rotation in world @@ -364,7 +364,7 @@ class Avatar : public AvatarData, public scriptable::ModelProvider, public MetaM * MyAvatar.setSkeletonOffset(Vec3.ZERO); * }, 5000); */ - Q_INVOKABLE void setSkeletonOffset(const glm::vec3& offset); + Q_INVOKABLE void setSkeletonOffset(const glm::vec<3,float,glm::packed_highp>& offset); /*@jsdoc * Gets the offset applied to the current avatar. The offset adjusts the position that the avatar is rendered. For example, @@ -374,7 +374,7 @@ class Avatar : public AvatarData, public scriptable::ModelProvider, public MetaM * @example Report your avatar's current skeleton offset. * print(JSON.stringify(MyAvatar.getSkeletonOffset())); */ - Q_INVOKABLE glm::vec3 getSkeletonOffset() { return _skeletonOffset; } + Q_INVOKABLE glm::vec<3,float,glm::packed_highp> getSkeletonOffset() { return _skeletonOffset; } virtual glm::vec3 getSkeletonPosition() const; @@ -384,7 +384,7 @@ class Avatar : public AvatarData, public scriptable::ModelProvider, public MetaM * @param {number} index - The index of the joint. * @returns {Vec3} The position of the joint in world coordinates. */ - Q_INVOKABLE glm::vec3 getJointPosition(int index) const; + Q_INVOKABLE glm::vec<3,float,glm::packed_highp> getJointPosition(int index) const; /*@jsdoc * Gets the position of a joint in the current avatar. @@ -394,7 +394,7 @@ class Avatar : public AvatarData, public scriptable::ModelProvider, public MetaM * @example Report the position of your avatar's hips. * print(JSON.stringify(MyAvatar.getJointPosition("Hips"))); */ - Q_INVOKABLE glm::vec3 getJointPosition(const QString& name) const; + Q_INVOKABLE glm::vec<3,float,glm::packed_highp> getJointPosition(const QString& name) const; /*@jsdoc * Gets the position of the current avatar's neck in world coordinates. @@ -403,14 +403,14 @@ class Avatar : public AvatarData, public scriptable::ModelProvider, public MetaM * @example Report the position of your avatar's neck. * print(JSON.stringify(MyAvatar.getNeckPosition())); */ - Q_INVOKABLE glm::vec3 getNeckPosition() const; + Q_INVOKABLE glm::vec<3,float,glm::packed_highp> getNeckPosition() const; /*@jsdoc * Gets the current acceleration of the avatar. * @function MyAvatar.getAcceleration * @returns {Vec3} The current acceleration of the avatar. */ - Q_INVOKABLE glm::vec3 getAcceleration() const { return _acceleration; } + Q_INVOKABLE glm::vec<3,float,glm::packed_highp> getAcceleration() const { return _acceleration; } /// Scales a world space position vector relative to the avatar position and scale /// \param vector position to be scaled. Will store the result @@ -436,7 +436,7 @@ class Avatar : public AvatarData, public scriptable::ModelProvider, public MetaM * @function MyAvatar.getWorldFeetPosition * @returns {Vec3} The position of the avatar's feet in world coordinates. */ - Q_INVOKABLE glm::vec3 getWorldFeetPosition(); + Q_INVOKABLE glm::vec<3,float,glm::packed_highp> getWorldFeetPosition(); void setPositionViaScript(const glm::vec3& position) override; void setOrientationViaScript(const glm::qua& orientation) override; @@ -583,7 +583,7 @@ public slots: * @example Report the position of your avatar's left palm. * print(JSON.stringify(MyAvatar.getLeftPalmPosition())); */ - glm::vec3 getLeftPalmPosition() const; + glm::vec<3,float,glm::packed_highp> getLeftPalmPosition() const; /*@jsdoc * Gets the rotation of the left palm in world coordinates. @@ -601,7 +601,7 @@ public slots: * @example Report the position of your avatar's right palm. * print(JSON.stringify(MyAvatar.getRightPalmPosition())); */ - glm::vec3 getRightPalmPosition() const; + glm::vec<3,float,glm::packed_highp> getRightPalmPosition() const; /*@jsdoc * Get the rotation of the right palm in world coordinates. diff --git a/libraries/avatars-renderer/src/avatars-renderer/ScriptAvatar.h b/libraries/avatars-renderer/src/avatars-renderer/ScriptAvatar.h index 7679969c3e0..bfcf37943ed 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/ScriptAvatar.h +++ b/libraries/avatars-renderer/src/avatars-renderer/ScriptAvatar.h @@ -76,7 +76,7 @@ class ScriptAvatar : public ScriptAvatarData { Q_OBJECT - Q_PROPERTY(glm::vec3 skeletonOffset READ getSkeletonOffset) + Q_PROPERTY(glm::vec<3,float,glm::packed_highp> skeletonOffset READ getSkeletonOffset) public: ScriptAvatar(AvatarSharedPointer avatarData); @@ -104,7 +104,7 @@ public slots: * @returns {Vec3} The default translation of the joint (in model coordinates) if avatar data are available and the joint * index is valid, otherwise {@link Vec3(0)|Vec3.ZERO}. */ - glm::vec3 getDefaultJointTranslation(int index) const; + glm::vec<3,float,glm::packed_highp> getDefaultJointTranslation(int index) const; /*@jsdoc @@ -112,7 +112,7 @@ public slots: * @function ScriptAvatar.getSkeletonOffset * @returns {Vec3} The skeleton offset if avatar data are available, otherwise {@link Vec3(0)|Vec3.ZERO}. */ - glm::vec3 getSkeletonOffset() const; + glm::vec<3,float,glm::packed_highp> getSkeletonOffset() const; /*@jsdoc @@ -122,7 +122,7 @@ public slots: * @returns {Vec3} The position of the joint in world coordinates, or {@link Vec3(0)|Vec3.ZERO} if avatar data aren't * available. */ - glm::vec3 getJointPosition(int index) const; + glm::vec<3,float,glm::packed_highp> getJointPosition(int index) const; /*@jsdoc * Gets the position of a joint in the current avatar. @@ -131,7 +131,7 @@ public slots: * @returns {Vec3} The position of the joint in world coordinates, or {@link Vec3(0)|Vec3.ZERO} if avatar data aren't * available. */ - glm::vec3 getJointPosition(const QString& name) const; + glm::vec<3,float,glm::packed_highp> getJointPosition(const QString& name) const; /*@jsdoc * Gets the position of the current avatar's neck in world coordinates. @@ -139,7 +139,7 @@ public slots: * @returns {Vec3} The position of the neck in world coordinates, or {@link Vec3(0)|Vec3.ZERO} if avatar data aren't * available. */ - glm::vec3 getNeckPosition() const; + glm::vec<3,float,glm::packed_highp> getNeckPosition() const; /*@jsdoc @@ -147,7 +147,7 @@ public slots: * @function ScriptAvatar.getAcceleration * @returns {Vec3} The current acceleration of the avatar, or {@link Vec3(0)|Vec3.ZERO} if avatar data aren't available.. */ - glm::vec3 getAcceleration() const; + glm::vec<3,float,glm::packed_highp> getAcceleration() const; /*@jsdoc @@ -191,7 +191,7 @@ public slots: * @returns {Vec3} The position of the left palm in world coordinates, or {@link Vec3(0)|Vec3.ZERO} if avatar data aren't * available. */ - glm::vec3 getLeftPalmPosition() const; + glm::vec<3,float,glm::packed_highp> getLeftPalmPosition() const; /*@jsdoc * Gets the rotation of the left palm in world coordinates. @@ -207,7 +207,7 @@ public slots: * @returns {Vec3} The position of the right palm in world coordinates, or {@link Vec3(0)|Vec3.ZERO} if avatar data aren't * available. */ - glm::vec3 getRightPalmPosition() const; + glm::vec<3,float,glm::packed_highp> getRightPalmPosition() const; /*@jsdoc * Gets the rotation of the right palm in world coordinates. diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index 5b0eb99985c..f8ab3daaaf1 100644 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -551,10 +551,10 @@ class AvatarData : public QObject, public SpatiallyNestable { * this property to false to fully control the mouth facial blend shapes via the * {@link Avatar.setBlendshape} method. */ - Q_PROPERTY(glm::vec3 position READ getWorldPosition WRITE setPositionViaScript) + Q_PROPERTY(glm::vec<3,float,glm::packed_highp> position READ getWorldPosition WRITE setPositionViaScript) Q_PROPERTY(float scale READ getDomainLimitedScale WRITE setTargetScale) Q_PROPERTY(float density READ getDensity) - Q_PROPERTY(glm::vec3 handPosition READ getHandPosition WRITE setHandPosition) + Q_PROPERTY(glm::vec<3,float,glm::packed_highp> handPosition READ getHandPosition WRITE setHandPosition) Q_PROPERTY(float bodyYaw READ getBodyYaw WRITE setBodyYaw) Q_PROPERTY(float bodyPitch READ getBodyPitch WRITE setBodyPitch) Q_PROPERTY(float bodyRoll READ getBodyRoll WRITE setBodyRoll) @@ -565,8 +565,8 @@ class AvatarData : public QObject, public SpatiallyNestable { Q_PROPERTY(float headYaw READ getHeadYaw WRITE setHeadYaw) Q_PROPERTY(float headRoll READ getHeadRoll WRITE setHeadRoll) - Q_PROPERTY(glm::vec3 velocity READ getWorldVelocity WRITE setWorldVelocity) - Q_PROPERTY(glm::vec3 angularVelocity READ getWorldAngularVelocity WRITE setWorldAngularVelocity) + Q_PROPERTY(glm::vec<3,float,glm::packed_highp> velocity READ getWorldVelocity WRITE setWorldVelocity) + Q_PROPERTY(glm::vec<3,float,glm::packed_highp> angularVelocity READ getWorldAngularVelocity WRITE setWorldAngularVelocity) Q_PROPERTY(float audioLoudness READ getAudioLoudness WRITE setAudioLoudness) Q_PROPERTY(float audioAverageLoudness READ getAudioAverageLoudness WRITE setAudioAverageLoudness) @@ -612,7 +612,7 @@ class AvatarData : public QObject, public SpatiallyNestable { const QUuid getSessionUUID() const { return getID(); } - glm::vec3 getHandPosition() const; + glm::vec<3,float,glm::packed_highp> getHandPosition() const; void setHandPosition(const glm::vec3& handPosition); typedef enum { @@ -805,7 +805,7 @@ class AvatarData : public QObject, public SpatiallyNestable { * * // Note: If using from the Avatar API, replace all occurrences of "MyAvatar" with "Avatar". */ - Q_INVOKABLE virtual void setJointData(int index, const glm::qua& rotation, const glm::vec3& translation); + Q_INVOKABLE virtual void setJointData(int index, const glm::qua& rotation, const glm::vec<3,float,glm::packed_highp>& translation); /*@jsdoc * Sets a specific joint's rotation relative to its parent. @@ -832,7 +832,7 @@ class AvatarData : public QObject, public SpatiallyNestable { * @param {number} index - The index of the joint. * @param {Vec3} translation - The translation of the joint relative to its parent, in model coordinates. */ - Q_INVOKABLE virtual void setJointTranslation(int index, const glm::vec3& translation); + Q_INVOKABLE virtual void setJointTranslation(int index, const glm::vec<3,float,glm::packed_highp>& translation); /*@jsdoc * Clears joint translations and rotations set by script for a specific joint. This restores all motion from the default @@ -869,7 +869,7 @@ class AvatarData : public QObject, public SpatiallyNestable { * @param {number} index - The index of the joint. * @returns {Vec3} The translation of the joint relative to its parent, in model coordinates. */ - Q_INVOKABLE virtual glm::vec3 getJointTranslation(int index) const; + Q_INVOKABLE virtual glm::vec<3,float,glm::packed_highp> getJointTranslation(int index) const; /*@jsdoc * Sets a specific joint's rotation and position relative to its parent, in model coordinates. @@ -884,7 +884,7 @@ class AvatarData : public QObject, public SpatiallyNestable { * @param {Quat} rotation - The rotation of the joint relative to its parent. * @param {Vec3} translation - The translation of the joint relative to its parent, in model coordinates. */ - Q_INVOKABLE virtual void setJointData(const QString& name, const glm::qua& rotation, const glm::vec3& translation); + Q_INVOKABLE virtual void setJointData(const QString& name, const glm::qua& rotation, const glm::vec<3,float,glm::packed_highp>& translation); /*@jsdoc * Sets a specific joint's rotation relative to its parent. @@ -943,7 +943,7 @@ class AvatarData : public QObject, public SpatiallyNestable { * * // Note: If using from the Avatar API, replace all occurrences of "MyAvatar" with "Avatar". */ - Q_INVOKABLE virtual void setJointTranslation(const QString& name, const glm::vec3& translation); + Q_INVOKABLE virtual void setJointTranslation(const QString& name, const glm::vec<3,float,glm::packed_highp>& translation); /*@jsdoc * Clears joint translations and rotations set by script for a specific joint. This restores all motion from the default @@ -998,7 +998,7 @@ class AvatarData : public QObject, public SpatiallyNestable { * * // Note: If using from the Avatar API, replace "MyAvatar" with "Avatar". */ - Q_INVOKABLE virtual glm::vec3 getJointTranslation(const QString& name) const; + Q_INVOKABLE virtual glm::vec<3,float,glm::packed_highp> getJointTranslation(const QString& name) const; /*@jsdoc * Gets the rotations of all joints in the current avatar. Each joint's rotation is relative to its parent joint. @@ -1021,7 +1021,7 @@ class AvatarData : public QObject, public SpatiallyNestable { * same order as the array returned by {@link MyAvatar.getJointNames}, or {@link Avatar.getJointNames} if using the * Avatar API. */ - Q_INVOKABLE virtual QVector getJointTranslations() const; + Q_INVOKABLE virtual QVector> getJointTranslations() const; /*@jsdoc * Sets the rotations of all joints in the current avatar. Each joint's rotation is relative to its parent joint. @@ -1075,7 +1075,7 @@ class AvatarData : public QObject, public SpatiallyNestable { * the same order as the array returned by {@link MyAvatar.getJointNames}, or {@link Avatar.getJointNames} if using the * Avatar API. */ - Q_INVOKABLE virtual void setJointTranslations(const QVector& jointTranslations); + Q_INVOKABLE virtual void setJointTranslations(const QVector>& jointTranslations); /*@jsdoc * Clears all joint translations and rotations that have been set by script. This restores all motion from the default @@ -1505,7 +1505,7 @@ public slots: * @param {number} index - The index of the joint. Not used. * @returns {Vec3} Vec3.ZERO. */ - virtual glm::vec3 getAbsoluteJointTranslationInObjectFrame(int index) const override; + virtual glm::vec<3,float,glm::packed_highp> getAbsoluteJointTranslationInObjectFrame(int index) const override; /*@jsdoc * Sets the rotation of a joint relative to the avatar. @@ -1525,7 +1525,7 @@ public slots: * @param {Vec3} translation - The translation of the joint relative to the avatar. Not used. * @returns {boolean} false. */ - virtual bool setAbsoluteJointTranslationInObjectFrame(int index, const glm::vec3& translation) override { return false; } + virtual bool setAbsoluteJointTranslationInObjectFrame(int index, const glm::vec<3,float,glm::packed_highp>& translation) override { return false; } /*@jsdoc * Gets the target scale of the avatar without any restrictions on permissible values imposed by the domain. In contrast, the diff --git a/libraries/avatars/src/AvatarHashMap.h b/libraries/avatars/src/AvatarHashMap.h index dcb0604e504..99d546bc10d 100644 --- a/libraries/avatars/src/AvatarHashMap.h +++ b/libraries/avatars/src/AvatarHashMap.h @@ -105,7 +105,7 @@ class AvatarHashMap : public QObject, public Dependency { * var avatars = AvatarList.getAvatarsInRange(Vec3.ZERO, RANGE); * print("Avatars near the origin: " + JSON.stringify(avatars)); */ - Q_INVOKABLE QVector getAvatarsInRange(const glm::vec3& position, float rangeMeters) const; + Q_INVOKABLE QVector getAvatarsInRange(const glm::vec<3,float,glm::packed_highp>& position, float rangeMeters) const; /*@jsdoc * Gets information about an avatar. @@ -179,7 +179,7 @@ public slots: * @returns {boolean} true if there's an avatar within the specified distance of the point, false * if not. */ - bool isAvatarInRange(const glm::vec3 & position, const float range); + bool isAvatarInRange(const glm::vec<3,float,glm::packed_highp> & position, const float range); protected slots: diff --git a/libraries/avatars/src/ScriptAvatarData.h b/libraries/avatars/src/ScriptAvatarData.h index e28b8ebdf33..ed0e18f40bd 100644 --- a/libraries/avatars/src/ScriptAvatarData.h +++ b/libraries/avatars/src/ScriptAvatarData.h @@ -23,9 +23,9 @@ class ScriptAvatarData : public QObject { // // PHYSICAL PROPERTIES: POSITION AND ORIENTATION // - Q_PROPERTY(glm::vec3 position READ getPosition) + Q_PROPERTY(glm::vec<3,float,glm::packed_highp> position READ getPosition) Q_PROPERTY(float scale READ getTargetScale) - Q_PROPERTY(glm::vec3 handPosition READ getHandPosition) + Q_PROPERTY(glm::vec<3,float,glm::packed_highp> handPosition READ getHandPosition) Q_PROPERTY(float bodyPitch READ getBodyPitch) Q_PROPERTY(float bodyYaw READ getBodyYaw) Q_PROPERTY(float bodyRoll READ getBodyRoll) @@ -37,8 +37,8 @@ class ScriptAvatarData : public QObject { // // PHYSICAL PROPERTIES: VELOCITY // - Q_PROPERTY(glm::vec3 velocity READ getVelocity) - Q_PROPERTY(glm::vec3 angularVelocity READ getAngularVelocity) + Q_PROPERTY(glm::vec<3,float,glm::packed_highp> velocity READ getVelocity) + Q_PROPERTY(glm::vec<3,float,glm::packed_highp> angularVelocity READ getAngularVelocity) // // IDENTIFIER PROPERTIES @@ -135,7 +135,7 @@ class ScriptAvatarData : public QObject { * @returns {Vec3} The translation of the joint relative to its parent, in model coordinates, or {@link Vec3(0)|Vec3.ZERO} * if the avatar data aren't available. */ - Q_INVOKABLE glm::vec3 getJointTranslation(int index) const; + Q_INVOKABLE glm::vec<3,float,glm::packed_highp> getJointTranslation(int index) const; /*@jsdoc * Gets the rotation of a joint relative to its parent. For information on the joint hierarchy used, see @@ -157,7 +157,7 @@ class ScriptAvatarData : public QObject { * @returns {Vec3} The translation of the joint relative to its parent, in model coordinates, or {@link Vec3(0)|Vec3.ZERO} * if the avatar data aren't available. */ - Q_INVOKABLE glm::vec3 getJointTranslation(const QString& name) const; + Q_INVOKABLE glm::vec<3,float,glm::packed_highp> getJointTranslation(const QString& name) const; /*@jsdoc * Gets the rotations of all joints in the avatar. Each joint's rotation is relative to its parent joint. @@ -176,7 +176,7 @@ class ScriptAvatarData : public QObject { * the avatar data aren't available. The values are in the same order as the array returned by * {@link ScriptAvatar.getJointNames}. */ - Q_INVOKABLE QVector getJointTranslations() const; + Q_INVOKABLE QVector> getJointTranslations() const; /*@jsdoc * Checks that the data for a joint are valid. @@ -271,7 +271,7 @@ public slots: * @returns {Vec3} The translation of the joint relative to the avatar, or {@link Vec3(0)|Vec3.ZERO} if the avatar data * aren't available. */ - glm::vec3 getAbsoluteJointTranslationInObjectFrame(int index) const; + glm::vec<3,float,glm::packed_highp> getAbsoluteJointTranslationInObjectFrame(int index) const; protected: std::weak_ptr _avatarData; diff --git a/libraries/controllers/src/controllers/ScriptingInterface.h b/libraries/controllers/src/controllers/ScriptingInterface.h index 4ba8464636d..9c9483d776f 100644 --- a/libraries/controllers/src/controllers/ScriptingInterface.h +++ b/libraries/controllers/src/controllers/ScriptingInterface.h @@ -52,9 +52,9 @@ namespace controller { public slots: virtual bool isActive() const = 0; - virtual glm::vec3 getAbsTranslation() const = 0; + virtual glm::vec<3,float,glm::packed_highp> getAbsTranslation() const = 0; virtual glm::qua getAbsRotation() const = 0; - virtual glm::vec3 getLocTranslation() const = 0; + virtual glm::vec<3,float,glm::packed_highp> getLocTranslation() const = 0; virtual glm::qua getLocRotation() const = 0; signals: diff --git a/libraries/controllers/src/controllers/impl/RouteBuilderProxy.h b/libraries/controllers/src/controllers/impl/RouteBuilderProxy.h index ce4508e5786..8c7dba1a47d 100644 --- a/libraries/controllers/src/controllers/impl/RouteBuilderProxy.h +++ b/libraries/controllers/src/controllers/impl/RouteBuilderProxy.h @@ -376,7 +376,7 @@ class RouteBuilderProxy : public QObject { * @returns {RouteObject} The RouteObject with the pre-translation applied. */ // No JSDoc example because filter not currently used. - Q_INVOKABLE QObject* translate(glm::vec3 translate); + Q_INVOKABLE QObject* translate(glm::vec<3,float,glm::packed_highp> translate); /*@jsdoc * Filters {@link Pose} route values to have a pre-transform applied. diff --git a/libraries/entities-renderer/src/ModelScriptingInterface.h b/libraries/entities-renderer/src/ModelScriptingInterface.h index cf276e130e3..ce804c31e1e 100644 --- a/libraries/entities-renderer/src/ModelScriptingInterface.h +++ b/libraries/entities-renderer/src/ModelScriptingInterface.h @@ -82,8 +82,8 @@ class ModelScriptingInterface : public QObject { * @param {MeshFace[]} faces - The faces in the mesh. * @returns {MeshProxy} A new mesh. */ - Q_INVOKABLE ScriptValue newMesh(const QVector& vertices, - const QVector& normals, + Q_INVOKABLE ScriptValue newMesh(const QVector>& vertices, + const QVector>& normals, const QVector& faces); /*@jsdoc diff --git a/libraries/entities/src/EntityScriptingInterface.h b/libraries/entities/src/EntityScriptingInterface.h index 3ce889cac80..a72e4822b78 100644 --- a/libraries/entities/src/EntityScriptingInterface.h +++ b/libraries/entities/src/EntityScriptingInterface.h @@ -366,7 +366,7 @@ public slots: /// Deliberately not adding jsdoc, only used internally. // FIXME: Deprecate and remove from the API. Q_INVOKABLE QUuid addModelEntity(const QString& name, const QString& modelUrl, const QString& textures, const QString& shapeType, bool dynamic, - bool collisionless, bool grabbable, const glm::vec3& position, const glm::vec3& gravity); + bool collisionless, bool grabbable, const glm::vec<3,float,glm::packed_highp>& position, const glm::vec<3,float,glm::packed_highp>& gravity); /*@jsdoc * Creates a clone of an entity. The clone has the same properties as the original except that: it has a modified @@ -732,7 +732,7 @@ public slots: * print("Closest entity: " + entityID); */ /// this function will not find any models in script engine contexts which don't have access to models - Q_INVOKABLE QUuid findClosestEntity(const glm::vec3& center, float radius) const; + Q_INVOKABLE QUuid findClosestEntity(const glm::vec<3,float,glm::packed_highp>& center, float radius) const; /*@jsdoc * Finds all domain and avatar entities that intersect a sphere. @@ -748,7 +748,7 @@ public slots: * print("Number of entities within 10m: " + entityIDs.length); */ /// this function will not find any models in script engine contexts which don't have access to models - Q_INVOKABLE QVector findEntities(const glm::vec3& center, float radius) const; + Q_INVOKABLE QVector findEntities(const glm::vec<3,float,glm::packed_highp>& center, float radius) const; /*@jsdoc * Finds all domain and avatar entities whose axis-aligned boxes intersect a search axis-aligned box. @@ -761,7 +761,7 @@ public slots: * could be found. */ /// this function will not find any models in script engine contexts which don't have access to models - Q_INVOKABLE QVector findEntitiesInBox(const glm::vec3& corner, const glm::vec3& dimensions) const; + Q_INVOKABLE QVector findEntitiesInBox(const glm::vec<3,float,glm::packed_highp>& corner, const glm::vec<3,float,glm::packed_highp>& dimensions) const; /*@jsdoc * Finds all domain and avatar entities whose axis-aligned boxes intersect a search frustum. @@ -795,7 +795,7 @@ public slots: * print("Number of Model entities within 10m: " + entityIDs.length); */ /// this function will not find any entities in script engine contexts which don't have access to entities - Q_INVOKABLE QVector findEntitiesByType(const QString entityType, const glm::vec3& center, float radius) const; + Q_INVOKABLE QVector findEntitiesByType(const QString entityType, const glm::vec<3,float,glm::packed_highp>& center, float radius) const; /*@jsdoc * Finds all domain and avatar entities with a particular name that intersect a sphere. @@ -813,7 +813,7 @@ public slots: * var entityIDs = Entities.findEntitiesByName("Light-Target", MyAvatar.position, 10, false); * print("Number of entities with the name Light-Target: " + entityIDs.length); */ - Q_INVOKABLE QVector findEntitiesByName(const QString entityName, const glm::vec3& center, float radius, + Q_INVOKABLE QVector findEntitiesByName(const QString entityName, const glm::vec<3,float,glm::packed_highp>& center, float radius, bool caseSensitiveSearch = false) const; /*@jsdoc @@ -832,7 +832,7 @@ public slots: * var entityIDs = Entities.findEntitiesByTags(["Light-Target"], MyAvatar.position, 10, false); * print("Number of entities with the tag Light-Target: " + entityIDs.length); */ - Q_INVOKABLE QVector findEntitiesByTags(const QVector entityTags, const glm::vec3& center, float radius, + Q_INVOKABLE QVector findEntitiesByTags(const QVector entityTags, const glm::vec<3,float,glm::packed_highp>& center, float radius, bool caseSensitiveSearch = false) const; /*@jsdoc @@ -1017,7 +1017,7 @@ public slots: * Entities.setVoxelSphere(polyVox, position, 0.9, 255); */ // FIXME move to a renderable entity interface - Q_INVOKABLE bool setVoxelSphere(const QUuid& entityID, const glm::vec3& center, float radius, int value); + Q_INVOKABLE bool setVoxelSphere(const QUuid& entityID, const glm::vec<3,float,glm::packed_highp>& center, float radius, int value); /*@jsdoc * Sets the values of all voxels in a capsule-shaped portion of a {@link Entities.EntityProperties-PolyVox|PolyVox} entity. @@ -1041,7 +1041,7 @@ public slots: * Entities.setVoxelCapsule(polyVox, startPosition, endPosition, 0.5, 255); */ // FIXME move to a renderable entity interface - Q_INVOKABLE bool setVoxelCapsule(const QUuid& entityID, const glm::vec3& start, const glm::vec3& end, float radius, int value); + Q_INVOKABLE bool setVoxelCapsule(const QUuid& entityID, const glm::vec<3,float,glm::packed_highp>& start, const glm::vec<3,float,glm::packed_highp>& end, float radius, int value); /*@jsdoc * Sets the value of a particular voxel in a {@link Entities.EntityProperties-PolyVox|PolyVox} entity. @@ -1063,7 +1063,7 @@ public slots: * Entities.setVoxel(entity, { x: 0, y: 0, z: 0 }, 0); */ // FIXME move to a renderable entity interface - Q_INVOKABLE bool setVoxel(const QUuid& entityID, const glm::vec3& position, int value); + Q_INVOKABLE bool setVoxel(const QUuid& entityID, const glm::vec<3,float,glm::packed_highp>& position, int value); /*@jsdoc * Sets the values of all voxels in a {@link Entities.EntityProperties-PolyVox|PolyVox} entity. @@ -1106,7 +1106,7 @@ public slots: * Entities.setVoxelsInCuboid(polyVox, cuboidPosition, cuboidSize, 0); */ // FIXME move to a renderable entity interface - Q_INVOKABLE bool setVoxelsInCuboid(const QUuid& entityID, const glm::vec3& lowPosition, const glm::vec3& cuboidSize, int value); + Q_INVOKABLE bool setVoxelsInCuboid(const QUuid& entityID, const glm::vec<3,float,glm::packed_highp>& lowPosition, const glm::vec<3,float,glm::packed_highp>& cuboidSize, int value); /*@jsdoc * Converts voxel coordinates in a {@link Entities.EntityProperties-PolyVox|PolyVox} entity to world coordinates. Voxel @@ -1141,7 +1141,7 @@ public slots: * }); */ // FIXME move to a renderable entity interface - Q_INVOKABLE glm::vec3 voxelCoordsToWorldCoords(const QUuid& entityID, glm::vec3 voxelCoords); + Q_INVOKABLE glm::vec<3,float,glm::packed_highp> voxelCoordsToWorldCoords(const QUuid& entityID, glm::vec<3,float,glm::packed_highp> voxelCoords); /*@jsdoc * Converts world coordinates to voxel coordinates in a {@link Entities.EntityProperties-PolyVox|PolyVox} entity. Voxel @@ -1155,7 +1155,7 @@ public slots: * fractional and outside the entity's bounding box. */ // FIXME move to a renderable entity interface - Q_INVOKABLE glm::vec3 worldCoordsToVoxelCoords(const QUuid& entityID, glm::vec3 worldCoords); + Q_INVOKABLE glm::vec<3,float,glm::packed_highp> worldCoordsToVoxelCoords(const QUuid& entityID, glm::vec<3,float,glm::packed_highp> worldCoords); /*@jsdoc * Converts voxel coordinates in a {@link Entities.EntityProperties-PolyVox|PolyVox} entity to local coordinates. Local @@ -1179,7 +1179,7 @@ public slots: * print("Voxel dimensions: " + JSON.stringify(voxelDimensions)); */ // FIXME move to a renderable entity interface - Q_INVOKABLE glm::vec3 voxelCoordsToLocalCoords(const QUuid& entityID, glm::vec3 voxelCoords); + Q_INVOKABLE glm::vec<3,float,glm::packed_highp> voxelCoordsToLocalCoords(const QUuid& entityID, glm::vec<3,float,glm::packed_highp> voxelCoords); /*@jsdoc * Converts local coordinates to voxel coordinates in a {@link Entities.EntityProperties-PolyVox|PolyVox} entity. Local @@ -1194,7 +1194,7 @@ public slots: * fractional and outside the entity's bounding box. */ // FIXME move to a renderable entity interface - Q_INVOKABLE glm::vec3 localCoordsToVoxelCoords(const QUuid& entityID, glm::vec3 localCoords); + Q_INVOKABLE glm::vec<3,float,glm::packed_highp> localCoordsToVoxelCoords(const QUuid& entityID, glm::vec<3,float,glm::packed_highp> localCoords); /*@jsdoc * Sets all the points in a {@link Entities.EntityProperties-Line|Line} entity. @@ -1230,7 +1230,7 @@ public slots: * ]); * }, 2000); */ - Q_INVOKABLE bool setAllPoints(const QUuid& entityID, const QVector& points); + Q_INVOKABLE bool setAllPoints(const QUuid& entityID, const QVector>& points); /*@jsdoc * Appends a point to a {@link Entities.EntityProperties-Line|Line} entity. @@ -1262,7 +1262,7 @@ public slots: * Entities.appendPoint(entity, { x: 1, y: 1, z: 0 }); * }, 50); // Wait for the entity to be created. */ - Q_INVOKABLE bool appendPoint(const QUuid& entityID, const glm::vec3& point); + Q_INVOKABLE bool appendPoint(const QUuid& entityID, const glm::vec<3,float,glm::packed_highp>& point); /*@jsdoc * Restart a {@link Entities.EntityProperties-Sound|Sound} entity, locally only. It must also be localOnly. @@ -1369,7 +1369,7 @@ public slots: * {@link Vec3(0)|Vec3.ZERO}. */ // FIXME move to a renderable entity interface - Q_INVOKABLE glm::vec3 getAbsoluteJointTranslationInObjectFrame(const QUuid& entityID, int jointIndex); + Q_INVOKABLE glm::vec<3,float,glm::packed_highp> getAbsoluteJointTranslationInObjectFrame(const QUuid& entityID, int jointIndex); /*@jsdoc * Gets the index of the parent joint of a joint in a {@link Entities.EntityProperties-Model|Model} entity. @@ -1422,7 +1422,7 @@ public slots: * false. */ // FIXME move to a renderable entity interface - Q_INVOKABLE bool setAbsoluteJointTranslationInObjectFrame(const QUuid& entityID, int jointIndex, glm::vec3 translation); + Q_INVOKABLE bool setAbsoluteJointTranslationInObjectFrame(const QUuid& entityID, int jointIndex, glm::vec<3,float,glm::packed_highp> translation); /*@jsdoc * Sets the rotation of a joint in a {@link Entities.EntityProperties-Model|Model} entity relative to the entity's position @@ -1465,7 +1465,7 @@ public slots: * entity, the entity is loaded, and the joint index is valid; otherwise {@link Vec3(0)|Vec3.ZERO}. */ // FIXME move to a renderable entity interface - Q_INVOKABLE glm::vec3 getLocalJointTranslation(const QUuid& entityID, int jointIndex); + Q_INVOKABLE glm::vec<3,float,glm::packed_highp> getLocalJointTranslation(const QUuid& entityID, int jointIndex); /*@jsdoc * Gets the local rotation of a joint in a {@link Entities.EntityProperties-Model|Model} entity. @@ -1504,7 +1504,7 @@ public slots: * false. */ // FIXME move to a renderable entity interface - Q_INVOKABLE bool setLocalJointTranslation(const QUuid& entityID, int jointIndex, glm::vec3 translation); + Q_INVOKABLE bool setLocalJointTranslation(const QUuid& entityID, int jointIndex, glm::vec<3,float,glm::packed_highp> translation); /*@jsdoc * Sets the local rotation of a joint in a {@link Entities.EntityProperties-Model|Model} entity. @@ -1546,7 +1546,7 @@ public slots: * translations; otherwise false. */ // FIXME move to a renderable entity interface - Q_INVOKABLE bool setLocalJointTranslations(const QUuid& entityID, const QVector& translations); + Q_INVOKABLE bool setLocalJointTranslations(const QUuid& entityID, const QVector>& translations); /*@jsdoc * Sets the local rotations of joints in a {@link Entities.EntityProperties-Model|Model} entity. @@ -1605,7 +1605,7 @@ public slots: // FIXME move to a renderable entity interface Q_INVOKABLE bool setLocalJointsData(const QUuid& entityID, const QVector>& rotations, - const QVector& translations); + const QVector>& translations); /*@jsdoc @@ -1943,8 +1943,8 @@ public slots: * @param {number} radius - The radius of the capsule. * @returns {boolean} true if the AA box and capsule intersect, otherwise false. */ - Q_INVOKABLE bool AABoxIntersectsCapsule(const glm::vec3& low, const glm::vec3& dimensions, - const glm::vec3& start, const glm::vec3& end, float radius); + Q_INVOKABLE bool AABoxIntersectsCapsule(const glm::vec<3,float,glm::packed_highp>& low, const glm::vec<3,float,glm::packed_highp>& dimensions, + const glm::vec<3,float,glm::packed_highp>& start, const glm::vec<3,float,glm::packed_highp>& end, float radius); /*@jsdoc * Gets the meshes in a {@link Entities.EntityProperties-Model|Model} or {@link Entities.EntityProperties-PolyVox|PolyVox} @@ -2066,7 +2066,7 @@ public slots: * localPosition = Entities.getEntityProperties(childEntity, "localPosition").localPosition; * print("Local position: " + JSON.stringify(localPosition)); // The same. */ - Q_INVOKABLE glm::vec3 worldToLocalPosition(glm::vec3 worldPosition, const QUuid& parentID, + Q_INVOKABLE glm::vec<3,float,glm::packed_highp> worldToLocalPosition(glm::vec<3,float,glm::packed_highp> worldPosition, const QUuid& parentID, int parentJointIndex = -1, bool scalesWithParent = false); /*@jsdoc * Converts a rotation or orientation in world coordinates to rotation in an avatar, entity, or joint's local coordinates. @@ -2091,7 +2091,7 @@ public slots: * false for the local velocity to be at world scale. * @returns {Vec3} The velocity converted to local coordinates if successful, otherwise {@link Vec3(0)|Vec3.ZERO}. */ - Q_INVOKABLE glm::vec3 worldToLocalVelocity(glm::vec3 worldVelocity, const QUuid& parentID, + Q_INVOKABLE glm::vec<3,float,glm::packed_highp> worldToLocalVelocity(glm::vec<3,float,glm::packed_highp> worldVelocity, const QUuid& parentID, int parentJointIndex = -1, bool scalesWithParent = false); /*@jsdoc * Converts a Euler angular velocity in world coordinates to an angular velocity in an avatar, entity, or joint's local @@ -2105,7 +2105,7 @@ public slots: * @param {boolean} [scalesWithParent=false] - Not used in the calculation. * @returns {Vec3} The angular velocity converted to local coordinates if successful, otherwise {@link Vec3(0)|Vec3.ZERO}. */ - Q_INVOKABLE glm::vec3 worldToLocalAngularVelocity(glm::vec3 worldAngularVelocity, const QUuid& parentID, + Q_INVOKABLE glm::vec<3,float,glm::packed_highp> worldToLocalAngularVelocity(glm::vec<3,float,glm::packed_highp> worldAngularVelocity, const QUuid& parentID, int parentJointIndex = -1, bool scalesWithParent = false); /*@jsdoc * Converts dimensions in world coordinates to dimensions in an avatar or entity's local coordinates. @@ -2117,7 +2117,7 @@ public slots: * false for the local dimensions to be at world scale. * @returns {Vec3} The dimensions converted to local coordinates if successful, otherwise {@link Vec3(0)|Vec3.ZERO}. */ - Q_INVOKABLE glm::vec3 worldToLocalDimensions(glm::vec3 worldDimensions, const QUuid& parentID, + Q_INVOKABLE glm::vec<3,float,glm::packed_highp> worldToLocalDimensions(glm::vec<3,float,glm::packed_highp> worldDimensions, const QUuid& parentID, int parentJointIndex = -1, bool scalesWithParent = false); /*@jsdoc * Converts a position in an avatar, entity, or joint's local coordinate to a position in world coordinates. @@ -2130,7 +2130,7 @@ public slots: * false if the local dimensions are at world scale. * @returns {Vec3} The position converted to world coordinates if successful, otherwise {@link Vec3(0)|Vec3.ZERO}. */ - Q_INVOKABLE glm::vec3 localToWorldPosition(glm::vec3 localPosition, const QUuid& parentID, + Q_INVOKABLE glm::vec<3,float,glm::packed_highp> localToWorldPosition(glm::vec<3,float,glm::packed_highp> localPosition, const QUuid& parentID, int parentJointIndex = -1, bool scalesWithParent = false); /*@jsdoc * Converts a rotation or orientation in an avatar, entity, or joint's local coordinate to a rotation in world coordinates. @@ -2155,7 +2155,7 @@ public slots: * false if the local velocity is at world scale. * @returns {Vec3} The velocity converted to world coordinates it successful, otherwise {@link Vec3(0)|Vec3.ZERO}. */ - Q_INVOKABLE glm::vec3 localToWorldVelocity(glm::vec3 localVelocity, const QUuid& parentID, + Q_INVOKABLE glm::vec<3,float,glm::packed_highp> localToWorldVelocity(glm::vec<3,float,glm::packed_highp> localVelocity, const QUuid& parentID, int parentJointIndex = -1, bool scalesWithParent = false); /*@jsdoc * Converts a Euler angular velocity in an avatar, entity, or joint's local coordinate to an angular velocity in world @@ -2169,7 +2169,7 @@ public slots: * @param {boolean} [scalesWithParent= false] - Not used in the calculation. * @returns {Vec3} The angular velocity converted to world coordinates if successful, otherwise {@link Vec3(0)|Vec3.ZERO}. */ - Q_INVOKABLE glm::vec3 localToWorldAngularVelocity(glm::vec3 localAngularVelocity, const QUuid& parentID, + Q_INVOKABLE glm::vec<3,float,glm::packed_highp> localToWorldAngularVelocity(glm::vec<3,float,glm::packed_highp> localAngularVelocity, const QUuid& parentID, int parentJointIndex = -1, bool scalesWithParent = false); /*@jsdoc * Converts dimensions in an avatar or entity's local coordinates to dimensions in world coordinates. @@ -2181,7 +2181,7 @@ public slots: * scale, false if the local dimensions are at world scale. * @returns {Vec3} The dimensions converted to world coordinates if successful, otherwise {@link Vec3(0)|Vec3.ZERO}. */ - Q_INVOKABLE glm::vec3 localToWorldDimensions(glm::vec3 localDimensions, const QUuid& parentID, + Q_INVOKABLE glm::vec<3,float,glm::packed_highp> localToWorldDimensions(glm::vec<3,float,glm::packed_highp> localDimensions, const QUuid& parentID, int parentJointIndex = -1, bool scalesWithParent = false); diff --git a/libraries/entities/src/EntityTree.h b/libraries/entities/src/EntityTree.h index 99028adfb7a..a88e74a609b 100644 --- a/libraries/entities/src/EntityTree.h +++ b/libraries/entities/src/EntityTree.h @@ -40,7 +40,7 @@ class NewlyCreatedEntityHook { class SendEntitiesOperationArgs { public: - glm::vec3 root; + glm::vec<3,float,glm::packed_highp> root; QString entityHostType; EntityTree* ourTree; EntityTreePointer otherTree; diff --git a/libraries/graphics-scripting/src/graphics-scripting/ScriptableMesh.h b/libraries/graphics-scripting/src/graphics-scripting/ScriptableMesh.h index 922e7b79738..fe96341cd50 100644 --- a/libraries/graphics-scripting/src/graphics-scripting/ScriptableMesh.h +++ b/libraries/graphics-scripting/src/graphics-scripting/ScriptableMesh.h @@ -133,7 +133,7 @@ namespace scriptable { * origin it is considered to be "nearby". * @returns {number[]} The indices of nearby vertices. */ - QVector findNearbyVertexIndices(const glm::vec3& origin, float epsilon = 1e-6) const; + QVector findNearbyVertexIndices(const glm::vec<3,float,glm::packed_highp>& origin, float epsilon = 1e-6) const; /*@jsdoc * Adds an attribute for all vertices. diff --git a/libraries/graphics-scripting/src/graphics-scripting/ScriptableMeshPart.h b/libraries/graphics-scripting/src/graphics-scripting/ScriptableMeshPart.h index 6cbf479affc..4990437f93e 100644 --- a/libraries/graphics-scripting/src/graphics-scripting/ScriptableMeshPart.h +++ b/libraries/graphics-scripting/src/graphics-scripting/ScriptableMeshPart.h @@ -112,7 +112,7 @@ namespace scriptable { * origin it is considered to be "nearby". * @returns {number[]} The indices of nearby vertices. */ - QVector findNearbyPartVertexIndices(const glm::vec3& origin, float epsilon = 1e-6) const; + QVector findNearbyPartVertexIndices(const glm::vec<3,float,glm::packed_highp>& origin, float epsilon = 1e-6) const; /*@jsdoc * Gets the value of an attribute for all vertices in the whole mesh (i.e., parent and mesh parts). @@ -174,7 +174,7 @@ namespace scriptable { * @param {Vec3} translation - The translation to apply, in model coordinates. * @returns {Graphics.MeshExtents} The rseulting mesh extents, in model coordinates. */ - QVariantMap translate(const glm::vec3& translation); + QVariantMap translate(const glm::vec<3,float,glm::packed_highp>& translation); /*@jsdoc * Scales the mesh part. @@ -183,7 +183,7 @@ namespace scriptable { * @param {Vec3} [origin] - The origin to scale about. If not specified, the center of the mesh part is used. * @returns {Graphics.MeshExtents} The resulting mesh extents, in model coordinates. */ - QVariantMap scale(const glm::vec3& scale, const glm::vec3& origin = glm::vec3(NAN)); + QVariantMap scale(const glm::vec<3,float,glm::packed_highp>& scale, const glm::vec<3,float,glm::packed_highp>& origin = glm::vec<3,float,glm::packed_highp>(NAN)); /*@jsdoc * Rotates the mesh part, using Euler angles. @@ -193,7 +193,7 @@ namespace scriptable { *

Warning: Currently doesn't work as expected.

* @returns {Graphics.MeshExtents} The resulting mesh extents, in model coordinates. */ - QVariantMap rotateDegrees(const glm::vec3& eulerAngles, const glm::vec3& origin = glm::vec3(NAN)); + QVariantMap rotateDegrees(const glm::vec<3,float,glm::packed_highp>& eulerAngles, const glm::vec<3,float,glm::packed_highp>& origin = glm::vec<3,float,glm::packed_highp>(NAN)); /*@jsdoc * Rotates the mesh part, using a quaternion. @@ -203,7 +203,7 @@ namespace scriptable { *

Warning: Currently doesn't work as expected.

* @returns {Graphics.MeshExtents} The resulting mesh extents, in model coordinates. */ - QVariantMap rotate(const glm::qua& rotation, const glm::vec3& origin = glm::vec3(NAN)); + QVariantMap rotate(const glm::qua& rotation, const glm::vec<3,float,glm::packed_highp>& origin = glm::vec<3,float,glm::packed_highp>(NAN)); /*@jsdoc * Scales, rotates, and translates the mesh. diff --git a/libraries/networking/src/AddressManager.h b/libraries/networking/src/AddressManager.h index a78f9d7d73b..044790b175f 100644 --- a/libraries/networking/src/AddressManager.h +++ b/libraries/networking/src/AddressManager.h @@ -435,7 +435,7 @@ public slots: * * location.locationChangeRequired.connect(onLocationChangeRequired); */ - void locationChangeRequired(const glm::vec3& newPosition, + void locationChangeRequired(const glm::vec<3,float,glm::packed_highp>& newPosition, bool hasOrientationChange, const glm::quat& newOrientation, bool shouldFaceLocation); diff --git a/libraries/render-utils/src/HazeStage.h b/libraries/render-utils/src/HazeStage.h index 70a33b27eba..517218c8cd9 100644 --- a/libraries/render-utils/src/HazeStage.h +++ b/libraries/render-utils/src/HazeStage.h @@ -28,10 +28,10 @@ class HazeStageSetup : public render::StageSetup { class FetchHazeConfig : public render::Job::Config { Q_OBJECT - Q_PROPERTY(glm::vec3 hazeColor MEMBER hazeColor WRITE setHazeColor NOTIFY dirty); + Q_PROPERTY(glm::vec<3,float,glm::packed_highp> hazeColor MEMBER hazeColor WRITE setHazeColor NOTIFY dirty); Q_PROPERTY(float hazeGlareAngle MEMBER hazeGlareAngle WRITE setHazeGlareAngle NOTIFY dirty); - Q_PROPERTY(glm::vec3 hazeGlareColor MEMBER hazeGlareColor WRITE setHazeGlareColor NOTIFY dirty); + Q_PROPERTY(glm::vec<3,float,glm::packed_highp> hazeGlareColor MEMBER hazeGlareColor WRITE setHazeGlareColor NOTIFY dirty); Q_PROPERTY(float hazeBaseReference MEMBER hazeBaseReference WRITE setHazeBaseReference NOTIFY dirty); Q_PROPERTY(bool isHazeActive MEMBER isHazeActive WRITE setHazeActive NOTIFY dirty); diff --git a/libraries/script-engine/src/Mat4.h b/libraries/script-engine/src/Mat4.h index 3402b548d34..0208932613d 100644 --- a/libraries/script-engine/src/Mat4.h +++ b/libraries/script-engine/src/Mat4.h @@ -72,7 +72,7 @@ public slots: * // (0.739199, 0.280330, 0.612372, 0.000000), * // (10.000000, 11.000000, 12.000000, 1.000000)) */ - glm::mat4 createFromRotAndTrans(const glm::qua& rot, const glm::vec3& trans) const; + glm::mat4 createFromRotAndTrans(const glm::qua& rot, const glm::vec<3,float,glm::packed_highp>& trans) const; /*@jsdoc * Creates a matrix that represents a scale, rotation, and translation. @@ -92,7 +92,7 @@ public slots: * // (1.478398, 0.560660, 1.224745, 0.000000), * // (10.000000, 11.000000, 12.000000, 1.000000)) */ - glm::mat4 createFromScaleRotAndTrans(const glm::vec3& scale, const glm::qua& rot, const glm::vec3& trans) const; + glm::mat4 createFromScaleRotAndTrans(const glm::vec<3,float,glm::packed_highp>& scale, const glm::qua& rot, const glm::vec<3,float,glm::packed_highp>& trans) const; /*@jsdoc * Creates a matrix from columns of values. @@ -153,7 +153,7 @@ public slots: * print("Translation: " + JSON.stringify(trans)); * // Translation: {"x":10,"y":11,"z":12} */ - glm::vec3 extractTranslation(const glm::mat4& m) const; + glm::vec<3,float,glm::packed_highp> extractTranslation(const glm::mat4& m) const; /*@jsdoc * Extracts the rotation from a matrix. @@ -187,7 +187,7 @@ public slots: * print("Scale: " + JSON.stringify(scale)); * // Scale: {"x":1.9999998807907104,"y":1.9999998807907104,"z":1.9999998807907104} */ - glm::vec3 extractScale(const glm::mat4& m) const; + glm::vec<3,float,glm::packed_highp> extractScale(const glm::mat4& m) const; /*@jsdoc @@ -207,7 +207,7 @@ public slots: * print("Transformed point: " + JSON.stringify(transformedPoint)); * // Transformed point: { "x": 2.8284270763397217, "y": 12, "z": -2.384185791015625e-7 } */ - glm::vec3 transformPoint(const glm::mat4& m, const glm::vec3& point) const; + glm::vec<3,float,glm::packed_highp> transformPoint(const glm::mat4& m, const glm::vec<3,float,glm::packed_highp>& point) const; /*@jsdoc * Transforms a vector into a new coordinate system: the vector is scaled and rotated. @@ -226,7 +226,7 @@ public slots: * print("Transformed vector: " + JSON.stringify(transformedVector)); * // Transformed vector: { "x": 2.8284270763397217, "y": 2, "z": -2.384185791015625e-7 } */ - glm::vec3 transformVector(const glm::mat4& m, const glm::vec3& vector) const; + glm::vec<3,float,glm::packed_highp> transformVector(const glm::mat4& m, const glm::vec<3,float,glm::packed_highp>& vector) const; /*@jsdoc @@ -259,7 +259,7 @@ public slots: * @returns {Vec3} The negative z-axis rotated by orientation. */ // redundant, calls getForward which better describes the returned vector as a direction - glm::vec3 getFront(const glm::mat4& m) const { return getForward(m); } + glm::vec<3,float,glm::packed_highp> getFront(const glm::mat4& m) const { return getForward(m); } /*@jsdoc * Gets the "forward" direction that the camera would face if its orientation was set to the rotation contained in a @@ -275,7 +275,7 @@ public slots: * print("Forward: " + JSON.stringify(forward)); * // Forward: {"x":0,"y":0,"z":-1} */ - glm::vec3 getForward(const glm::mat4& m) const; + glm::vec<3,float,glm::packed_highp> getForward(const glm::mat4& m) const; /*@jsdoc * Gets the "right" direction that the camera would have if its orientation was set to the rotation contained in a matrix. @@ -284,7 +284,7 @@ public slots: * @param {Mat4} m - The matrix. * @returns {Vec3} The x-axis rotated by the rotation in the matrix. */ - glm::vec3 getRight(const glm::mat4& m) const; + glm::vec<3,float,glm::packed_highp> getRight(const glm::mat4& m) const; /*@jsdoc * Gets the "up" direction that the camera would have if its orientation was set to the rotation contained in a matrix. The @@ -293,7 +293,7 @@ public slots: * @param {Mat4} m - The matrix. * @returns {Vec3} The y-axis rotated by the rotation in the matrix. */ - glm::vec3 getUp(const glm::mat4& m) const; + glm::vec<3,float,glm::packed_highp> getUp(const glm::mat4& m) const; /*@jsdoc diff --git a/libraries/script-engine/src/Quat.h b/libraries/script-engine/src/Quat.h index 039e54a5055..bfe7329e28c 100644 --- a/libraries/script-engine/src/Quat.h +++ b/libraries/script-engine/src/Quat.h @@ -127,7 +127,7 @@ public slots: * Camera.mode = "independent"; * Camera.orientation = Quat.lookAt(Camera.position, Vec3.ZERO, Vec3.UNIT_NEG_Y); */ - glm::qua lookAt(const glm::vec3& eye, const glm::vec3& center, const glm::vec3& up); + glm::qua lookAt(const glm::vec<3,float,glm::packed_highp>& eye, const glm::vec<3,float,glm::packed_highp>& center, const glm::vec<3,float,glm::packed_highp>& up); /*@jsdoc * Calculates a camera orientation given an eye position and point of interest. The camera's negative z-axis is the forward @@ -143,7 +143,7 @@ public slots: * Camera.mode = "independent"; * Camera.orientation = Quat.lookAtSimple(Camera.position, Vec3.ZERO); */ - glm::qua lookAtSimple(const glm::vec3& eye, const glm::vec3& center); + glm::qua lookAtSimple(const glm::vec<3,float,glm::packed_highp>& eye, const glm::vec<3,float,glm::packed_highp>& center); /*@jsdoc * Calculates the shortest rotation from a first vector onto a second. @@ -160,7 +160,7 @@ public slots: * Entities.editEntity(entityID, properties); * entityVelocity = newVelocity; */ - glm::qua rotationBetween(const glm::vec3& v1, const glm::vec3& v2); + glm::qua rotationBetween(const glm::vec<3,float,glm::packed_highp>& v1, const glm::vec<3,float,glm::packed_highp>& v2); /*@jsdoc * Generates a quaternion from a {@link Vec3} of Euler angles in degrees. @@ -174,7 +174,7 @@ public slots: * eulerAngles.z = 0; * var newOrientation = Quat.fromVec3Degrees(eulerAngles); */ - glm::qua fromVec3Degrees(const glm::vec3& vec3); + glm::qua fromVec3Degrees(const glm::vec<3,float,glm::packed_highp>& vec3); /*@jsdoc * Generates a quaternion from a {@link Vec3} of Euler angles in radians. @@ -185,7 +185,7 @@ public slots: * @example Create a rotation of 180 degrees about the y axis. * var rotation = Quat.fromVec3Radians({ x: 0, y: Math.PI, z: 0 }); */ - glm::qua fromVec3Radians(const glm::vec3& vec3); + glm::qua fromVec3Radians(const glm::vec<3,float,glm::packed_highp>& vec3); /*@jsdoc * Generates a quaternion from pitch, yaw, and roll values in degrees. @@ -235,7 +235,7 @@ public slots: * @param {Quat} orientation - A quaternion representing an orientation. * @returns {Vec3} The negative z-axis rotated by orientation. */ - glm::vec3 getFront(const glm::qua& orientation) { return getForward(orientation); } + glm::vec<3,float,glm::packed_highp> getFront(const glm::qua& orientation) { return getForward(orientation); } /*@jsdoc * Gets the "forward" direction that the camera would face if its orientation was set to the quaternion value. @@ -248,7 +248,7 @@ public slots: * var forward = Quat.getForward(Quat.IDENTITY); * print(JSON.stringify(forward)); // {"x":0,"y":0,"z":-1} */ - glm::vec3 getForward(const glm::qua& orientation); + glm::vec<3,float,glm::packed_highp> getForward(const glm::qua& orientation); /*@jsdoc * Gets the "right" direction that the camera would have if its orientation was set to the quaternion value. @@ -257,7 +257,7 @@ public slots: * @param {Quat} orientation - A quaternion representing an orientation. * @returns {Vec3} The x-axis rotated by orientation. */ - glm::vec3 getRight(const glm::qua& orientation); + glm::vec<3,float,glm::packed_highp> getRight(const glm::qua& orientation); /*@jsdoc * Gets the "up" direction that the camera would have if its orientation was set to the quaternion value. @@ -266,7 +266,7 @@ public slots: * @param {Quat} orientation - A quaternion representing an orientation. * @returns {Vec3} The y-axis rotated by orientation. */ - glm::vec3 getUp(const glm::qua& orientation); + glm::vec<3,float,glm::packed_highp> getUp(const glm::qua& orientation); /*@jsdoc * Calculates the Euler angles for the quaternion, in degrees. (The "safe" in the name signifies that the angle results @@ -279,7 +279,7 @@ public slots: * var eulerAngles = Quat.safeEulerAngles(Camera.orientation); * print("Camera yaw: " + eulerAngles.y); */ - glm::vec3 safeEulerAngles(const glm::qua& orientation); + glm::vec<3,float,glm::packed_highp> safeEulerAngles(const glm::qua& orientation); /*@jsdoc * Generates a quaternion given an angle to rotate through and an axis to rotate about. @@ -292,7 +292,7 @@ public slots: * @example Calculate a rotation of 90 degrees about the direction your camera is looking. * var rotation = Quat.angleAxis(90, Quat.getForward(Camera.orientation)); */ - glm::qua angleAxis(float angle, const glm::vec3& v); + glm::qua angleAxis(float angle, const glm::vec<3,float,glm::packed_highp>& v); /*@jsdoc * Gets the rotation axis for a quaternion. @@ -306,7 +306,7 @@ public slots: * print("Forward: " + JSON.stringify(forward)); * print("Axis: " + JSON.stringify(axis)); // Same value as forward. */ - glm::vec3 axis(const glm::qua& orientation); + glm::vec<3,float,glm::packed_highp> axis(const glm::qua& orientation); /*@jsdoc * Gets the rotation angle for a quaternion. diff --git a/libraries/script-engine/src/Vec3.h b/libraries/script-engine/src/Vec3.h index e63a7cd7ca7..f9b99f0cbc2 100644 --- a/libraries/script-engine/src/Vec3.h +++ b/libraries/script-engine/src/Vec3.h @@ -113,7 +113,7 @@ public slots: * var reflected = Vec3.reflect(v, normal); * print(JSON.stringify(reflected)); // {"x":1,"y":-2,"z":3} */ - glm::vec3 reflect(const glm::vec3& v1, const glm::vec3& v2) { return glm::reflect(v1, v2); } + glm::vec<3,float,glm::packed_highp> reflect(const glm::vec<3,float,glm::packed_highp>& v1, const glm::vec<3,float,glm::packed_highp>& v2) { return glm::reflect(v1, v2); } /*@jsdoc * Calculates the cross product of two vectors. @@ -127,7 +127,7 @@ public slots: * var crossProduct = Vec3.cross(v1, v2); * print(JSON.stringify(crossProduct)); // {"x":0,"y":0,"z":1} */ - glm::vec3 cross(const glm::vec3& v1, const glm::vec3& v2) { return glm::cross(v1, v2); } + glm::vec<3,float,glm::packed_highp> cross(const glm::vec<3,float,glm::packed_highp>& v1, const glm::vec<3,float,glm::packed_highp>& v2) { return glm::cross(v1, v2); } /*@jsdoc * Calculates the dot product of two vectors. @@ -141,7 +141,7 @@ public slots: * var dotProduct = Vec3.dot(v1, v2); * print(dotProduct); // 0 */ - float dot(const glm::vec3& v1, const glm::vec3& v2) { return glm::dot(v1, v2); } + float dot(const glm::vec<3,float,glm::packed_highp>& v1, const glm::vec<3,float,glm::packed_highp>& v2) { return glm::dot(v1, v2); } /*@jsdoc * Multiplies a vector by a scale factor. @@ -150,7 +150,7 @@ public slots: * @param {number} scale - The scale factor. * @returns {Vec3} The vector with each ordinate value multiplied by the scale. */ - glm::vec3 multiply(const glm::vec3& v1, float f) { return v1 * f; } + glm::vec<3,float,glm::packed_highp> multiply(const glm::vec<3,float,glm::packed_highp>& v1, float f) { return v1 * f; } /*@jsdoc * Multiplies a vector by a scale factor. @@ -159,7 +159,7 @@ public slots: * @param {Vec3} v - The vector. * @returns {Vec3} The vector with each ordinate value multiplied by the scale. */ - glm::vec3 multiply(float f, const glm::vec3& v1) { return v1 * f; } + glm::vec<3,float,glm::packed_highp> multiply(float f, const glm::vec<3,float,glm::packed_highp>& v1) { return v1 * f; } /*@jsdoc * Multiplies two vectors. @@ -174,7 +174,7 @@ public slots: * var multiplied = Vec3.multiplyVbyV(v1, v2); * print(JSON.stringify(multiplied)); // {"x":1,"y":4,"z":9} */ - glm::vec3 multiplyVbyV(const glm::vec3& v1, const glm::vec3& v2) { return v1 * v2; } + glm::vec<3,float,glm::packed_highp> multiplyVbyV(const glm::vec<3,float,glm::packed_highp>& v1, const glm::vec<3,float,glm::packed_highp>& v2) { return v1 * v2; } /*@jsdoc * Rotates a vector. @@ -188,7 +188,7 @@ public slots: * var result = Vec3.multiplyQbyV(q, v); * print(JSON.stringify(result)); // {"x":0,"y":1.000,"z":1.19e-7} */ - glm::vec3 multiplyQbyV(const glm::quat& q, const glm::vec3& v) { return q * v; } + glm::vec<3,float,glm::packed_highp> multiplyQbyV(const glm::quat& q, const glm::vec<3,float,glm::packed_highp>& v) { return q * v; } /*@jsdoc * Calculates the sum of two vectors. @@ -197,7 +197,7 @@ public slots: * @param {Vec3} v2 - The second vector. * @returns {Vec3} The sum of the two vectors. */ - glm::vec3 sum(const glm::vec3& v1, const glm::vec3& v2) { return v1 + v2; } + glm::vec<3,float,glm::packed_highp> sum(const glm::vec<3,float,glm::packed_highp>& v1, const glm::vec<3,float,glm::packed_highp>& v2) { return v1 + v2; } /*@jsdoc * Calculates one vector subtracted from another. @@ -206,7 +206,7 @@ public slots: * @param {Vec3} v2 - The second vector. * @returns {Vec3} The second vector subtracted from the first. */ - glm::vec3 subtract(const glm::vec3& v1, const glm::vec3& v2) { return v1 - v2; } + glm::vec<3,float,glm::packed_highp> subtract(const glm::vec<3,float,glm::packed_highp>& v1, const glm::vec<3,float,glm::packed_highp>& v2) { return v1 - v2; } /*@jsdoc * Calculates the length of a vector @@ -214,7 +214,7 @@ public slots: * @param {Vec3} v - The vector. * @returns {number} The length of the vector. */ - float length(const glm::vec3& v) { return glm::length(v); } + float length(const glm::vec<3,float,glm::packed_highp>& v) { return glm::length(v); } /*@jsdoc * Calculates the distance between two points. @@ -232,7 +232,7 @@ public slots: * distance = Vec3.distance(p1, p2); * print(distance); // 10 */ - float distance(const glm::vec3& v1, const glm::vec3& v2) { return glm::distance(v1, v2); } + float distance(const glm::vec<3,float,glm::packed_highp>& v1, const glm::vec<3,float,glm::packed_highp>& v2) { return glm::distance(v1, v2); } /*@jsdoc * Calculates the angle of rotation from one vector onto another, with the sign depending on a reference vector. @@ -254,7 +254,7 @@ public slots: * print(Vec3.orientedAngle(v1, v2, { x: 1, y: 2, z: -1 })); // -45 * print(Vec3.orientedAngle(v1, v2, { x: 1, y: -2, z: -1 })); // 45 */ - float orientedAngle(const glm::vec3& v1, const glm::vec3& v2, const glm::vec3& v3); + float orientedAngle(const glm::vec<3,float,glm::packed_highp>& v1, const glm::vec<3,float,glm::packed_highp>& v2, const glm::vec<3,float,glm::packed_highp>& v3); /*@jsdoc * Normalizes a vector so that its length is 1. @@ -267,7 +267,7 @@ public slots: * print(JSON.stringify(normalized)); // {"x":0.7071,"y":0.7071,"z":0} * print(Vec3.length(normalized)); // 1 */ - glm::vec3 normalize(const glm::vec3& v) { return glm::normalize(v); }; + glm::vec<3,float,glm::packed_highp> normalize(const glm::vec<3,float,glm::packed_highp>& v) { return glm::normalize(v); }; /*@jsdoc * Calculates a linear interpolation between two vectors. @@ -282,7 +282,7 @@ public slots: * var interpolated = Vec3.mix(v1, v2, 0.75); // 1/4 of v1 and 3/4 of v2. * print(JSON.stringify(interpolated)); // {"x":2.5,"y":7.5","z":0} */ - glm::vec3 mix(const glm::vec3& v1, const glm::vec3& v2, float m) { return glm::mix(v1, v2, m); } + glm::vec<3,float,glm::packed_highp> mix(const glm::vec<3,float,glm::packed_highp>& v1, const glm::vec<3,float,glm::packed_highp>& v2, float m) { return glm::mix(v1, v2, m); } /*@jsdoc * Prints the vector to the program log, as a text label followed by the vector value. @@ -294,7 +294,7 @@ public slots: * Vec3.print("Vector: ", v); // dvec3(1.000000, 2.000000, 3.000000) * print("Vector: " + JSON.stringify(v)); // {"x":1,"y":2,"z":3} */ - void print(const QString& label, const glm::vec3& v); + void print(const QString& label, const glm::vec<3,float,glm::packed_highp>& v); /*@jsdoc * Tests whether two vectors are equal. @@ -315,7 +315,7 @@ public slots: * equal = Vec3.equal(v1, v2); * print(equal); // false */ - bool equal(const glm::vec3& v1, const glm::vec3& v2) { return v1 == v2; } + bool equal(const glm::vec<3,float,glm::packed_highp>& v1, const glm::vec<3,float,glm::packed_highp>& v2) { return v1 == v2; } /*@jsdoc * Tests whether two vectors are equal within a tolerance. @@ -336,7 +336,7 @@ public slots: * equal = Vec3.withinEpsilon(v1, v2, 0.001); * print(equal); // true */ - bool withinEpsilon(const glm::vec3& v1, const glm::vec3& v2, float epsilon); + bool withinEpsilon(const glm::vec<3,float,glm::packed_highp>& v1, const glm::vec<3,float,glm::packed_highp>& v2, float epsilon); /*@jsdoc * Calculates polar coordinates (elevation, azimuth, radius) that transform the unit z-axis vector onto a point. @@ -352,7 +352,7 @@ public slots: * print("Radius: " + polar.z); // 7.5 */ // FIXME misnamed, should be 'spherical' or 'euler' depending on the implementation - glm::vec3 toPolar(const glm::vec3& v); + glm::vec<3,float,glm::packed_highp> toPolar(const glm::vec<3,float,glm::packed_highp>& v); /*@jsdoc * Calculates the coordinates of a point from polar coordinate transformation of the unit z-axis vector. @@ -366,7 +366,7 @@ public slots: * print(JSON.stringify(p)); // {"x":5,"y":2.5,"z":5} */ // FIXME misnamed, should be 'spherical' or 'euler' depending on the implementation - glm::vec3 fromPolar(const glm::vec3& polar); + glm::vec<3,float,glm::packed_highp> fromPolar(const glm::vec<3,float,glm::packed_highp>& polar); /*@jsdoc * Calculates the unit vector corresponding to polar coordinates elevation and azimuth transformation of the unit z-axis @@ -384,7 +384,7 @@ public slots: * print(JSON.stringify(p)); // {"x":5,"y":2.5,"z":5} */ // FIXME misnamed, should be 'spherical' or 'euler' depending on the implementation - glm::vec3 fromPolar(float elevation, float azimuth); + glm::vec<3,float,glm::packed_highp> fromPolar(float elevation, float azimuth); /*@jsdoc * Calculates the angle between two vectors. @@ -398,28 +398,28 @@ public slots: * var angle = Vec3.getAngle(v1, v2); * print(angle * 180 / Math.PI); // 90 */ - float getAngle(const glm::vec3& v1, const glm::vec3& v2); + float getAngle(const glm::vec<3,float,glm::packed_highp>& v1, const glm::vec<3,float,glm::packed_highp>& v2); private: - const glm::vec3& UNIT_X() { return Vectors::UNIT_X; } - const glm::vec3& UNIT_Y() { return Vectors::UNIT_Y; } - const glm::vec3& UNIT_Z() { return Vectors::UNIT_Z; } - const glm::vec3& UNIT_NEG_X() { return Vectors::UNIT_NEG_X; } - const glm::vec3& UNIT_NEG_Y() { return Vectors::UNIT_NEG_Y; } - const glm::vec3& UNIT_NEG_Z() { return Vectors::UNIT_NEG_Z; } - const glm::vec3& UNIT_XY() { return Vectors::UNIT_XY; } - const glm::vec3& UNIT_XZ() { return Vectors::UNIT_XZ; } - const glm::vec3& UNIT_YZ() { return Vectors::UNIT_YZ; } - const glm::vec3& UNIT_XYZ() { return Vectors::UNIT_XYZ; } - const glm::vec3& FLOAT_MAX() { return Vectors::MAX; } - const glm::vec3& FLOAT_MIN() { return Vectors::MIN; } - const glm::vec3& ZERO() { return Vectors::ZERO; } - const glm::vec3& ONE() { return Vectors::ONE; } - const glm::vec3& TWO() { return Vectors::TWO; } - const glm::vec3& HALF() { return Vectors::HALF; } - const glm::vec3& RIGHT() { return Vectors::RIGHT; } - const glm::vec3& UP() { return Vectors::UP; } - const glm::vec3& FRONT() { return Vectors::FRONT; } + const glm::vec<3,float,glm::packed_highp>& UNIT_X() { return Vectors::UNIT_X; } + const glm::vec<3,float,glm::packed_highp>& UNIT_Y() { return Vectors::UNIT_Y; } + const glm::vec<3,float,glm::packed_highp>& UNIT_Z() { return Vectors::UNIT_Z; } + const glm::vec<3,float,glm::packed_highp>& UNIT_NEG_X() { return Vectors::UNIT_NEG_X; } + const glm::vec<3,float,glm::packed_highp>& UNIT_NEG_Y() { return Vectors::UNIT_NEG_Y; } + const glm::vec<3,float,glm::packed_highp>& UNIT_NEG_Z() { return Vectors::UNIT_NEG_Z; } + const glm::vec<3,float,glm::packed_highp>& UNIT_XY() { return Vectors::UNIT_XY; } + const glm::vec<3,float,glm::packed_highp>& UNIT_XZ() { return Vectors::UNIT_XZ; } + const glm::vec<3,float,glm::packed_highp>& UNIT_YZ() { return Vectors::UNIT_YZ; } + const glm::vec<3,float,glm::packed_highp>& UNIT_XYZ() { return Vectors::UNIT_XYZ; } + const glm::vec<3,float,glm::packed_highp>& FLOAT_MAX() { return Vectors::MAX; } + const glm::vec<3,float,glm::packed_highp>& FLOAT_MIN() { return Vectors::MIN; } + const glm::vec<3,float,glm::packed_highp>& ZERO() { return Vectors::ZERO; } + const glm::vec<3,float,glm::packed_highp>& ONE() { return Vectors::ONE; } + const glm::vec<3,float,glm::packed_highp>& TWO() { return Vectors::TWO; } + const glm::vec<3,float,glm::packed_highp>& HALF() { return Vectors::HALF; } + const glm::vec<3,float,glm::packed_highp>& RIGHT() { return Vectors::RIGHT; } + const glm::vec<3,float,glm::packed_highp>& UP() { return Vectors::UP; } + const glm::vec<3,float,glm::packed_highp>& FRONT() { return Vectors::FRONT; } public: virtual ~Vec3(); diff --git a/libraries/shared/src/DebugDraw.h b/libraries/shared/src/DebugDraw.h index 641ad76540d..4cb8105bdf0 100644 --- a/libraries/shared/src/DebugDraw.h +++ b/libraries/shared/src/DebugDraw.h @@ -61,7 +61,7 @@ class DebugDraw : public QObject { * DebugDraw.drawRay(start, end, color); * }); */ - Q_INVOKABLE void drawRay(const glm::vec3& start, const glm::vec3& end, const glm::vec4& color); + Q_INVOKABLE void drawRay(const glm::vec<3,float,glm::packed_highp>& start, const glm::vec<3,float,glm::packed_highp>& end, const glm::vec4& color); /*@jsdoc * Draws lines in world space, visible for a single frame. To make the lines visually persist, you need to repeatedly draw @@ -86,8 +86,8 @@ class DebugDraw : public QObject { * DebugDraw.drawRays(lines, color, translation, rotation); * }); */ - Q_INVOKABLE void drawRays(const std::vector>& lines, const glm::vec4& color, - const glm::vec3& translation = glm::vec3(0.0f, 0.0f, 0.0f), const glm::qua& rotation = glm::qua(1.0f, 0.0f, 0.0f, 0.0f)); + Q_INVOKABLE void drawRays(const std::vector, glm::vec<3,float,glm::packed_highp>>>& lines, const glm::vec4& color, + const glm::vec<3,float,glm::packed_highp>& translation = glm::vec<3,float,glm::packed_highp>(0.0f, 0.0f, 0.0f), const glm::qua& rotation = glm::qua(1.0f, 0.0f, 0.0f, 0.0f)); /*@jsdoc * Adds or updates a debug marker in world coordinates. This marker is drawn every frame until it is removed using @@ -112,7 +112,7 @@ class DebugDraw : public QObject { * DebugDraw.removeMarker(MARKER_NAME); * }, 5000); */ - Q_INVOKABLE void addMarker(const QString& key, const glm::qua& rotation, const glm::vec3& position, + Q_INVOKABLE void addMarker(const QString& key, const glm::qua& rotation, const glm::vec<3,float,glm::packed_highp>& position, const glm::vec4& color, float size = 1.0f); /*@jsdoc @@ -145,7 +145,7 @@ class DebugDraw : public QObject { * DebugDraw.removeMyAvatarMarker(MARKER_NAME); * }, 5000); */ - Q_INVOKABLE void addMyAvatarMarker(const QString& key, const glm::qua& rotation, const glm::vec3& position, + Q_INVOKABLE void addMyAvatarMarker(const QString& key, const glm::qua& rotation, const glm::vec<3,float,glm::packed_highp>& position, const glm::vec4& color, float size = 1.0f); /*@jsdoc @@ -155,7 +155,7 @@ class DebugDraw : public QObject { */ Q_INVOKABLE void removeMyAvatarMarker(const QString& key); - using MarkerInfo = std::tuple, glm::vec3, glm::vec4, float>; + using MarkerInfo = std::tuple; using MarkerMap = std::map; using Ray = std::tuple; using Rays = std::vector; diff --git a/libraries/shared/src/shared/Camera.h b/libraries/shared/src/shared/Camera.h index 2410fc47d4c..7abd45c442c 100644 --- a/libraries/shared/src/shared/Camera.h +++ b/libraries/shared/src/shared/Camera.h @@ -41,7 +41,7 @@ static int cameraModeId = qRegisterMetaType(); class Camera : public QObject { Q_OBJECT - Q_PROPERTY(glm::vec3 position READ getPosition WRITE setPosition) + Q_PROPERTY(glm::vec<3,float,glm::packed_highp> position READ getPosition WRITE setPosition) Q_PROPERTY(glm::qua orientation READ getOrientation WRITE setOrientation) Q_PROPERTY(QString mode READ getModeString WRITE setModeString NOTIFY modeUpdated) Q_PROPERTY(QVariantMap frustum READ getViewFrustum CONSTANT) @@ -88,7 +88,7 @@ public slots: * @function Camera.getPosition * @returns {Vec3} The current camera position. */ - glm::vec3 getPosition() const { return _position; } + glm::vec<3,float,glm::packed_highp> getPosition() const { return _position; } /*@jsdoc * Sets the camera position. You can also set the position using the {@link Camera|Camera.position} property. Only works if @@ -96,7 +96,7 @@ public slots: * @function Camera.setPosition * @param {Vec3} position - The position to set the camera at. */ - void setPosition(const glm::vec3& position); + void setPosition(const glm::vec<3,float,glm::packed_highp>& position); /*@jsdoc * Gets the current camera orientation. You can also get the orientation using the {@link Camera|Camera.orientation} @@ -202,7 +202,7 @@ public slots: * * Controller.mousePressEvent.connect(onMousePressEvent); */ - void lookAt(const glm::vec3& position); + void lookAt(const glm::vec<3,float,glm::packed_highp>& position); /*@jsdoc * Sets the camera to continue looking at the specified position even while the camera moves. Only works if @@ -210,7 +210,7 @@ public slots: * @function Camera.keepLookingAt * @param {Vec3} position - The position to keep looking at. */ - void keepLookingAt(const glm::vec3& position); + void keepLookingAt(const glm::vec<3,float,glm::packed_highp>& position); /*@jsdoc * Stops the camera from continually looking at the position that was set with {@link Camera.keepLookingAt}. From bab5c949d4212dcf7993b8bde158f1bedde50f5c Mon Sep 17 00:00:00 2001 From: Karol Suprynowicz Date: Sun, 28 Sep 2025 01:19:21 +0200 Subject: [PATCH 014/111] Smaller script API types fixes for Qt6 --- libraries/networking/src/ResourceCache.h | 4 ++-- libraries/script-engine/src/Vec3.h | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/libraries/networking/src/ResourceCache.h b/libraries/networking/src/ResourceCache.h index d2687f0964d..3b0a181f00d 100644 --- a/libraries/networking/src/ResourceCache.h +++ b/libraries/networking/src/ResourceCache.h @@ -238,7 +238,7 @@ protected slots: // Prefetches a resource to be held by the ScriptEngine. // Left as a protected member so subclasses can overload prefetch // and delegate to it (see TextureCache::prefetch(const QUrl&, int). - ScriptableResource* prefetch(const QUrl& url, void* extra, size_t extraHash); + ScriptableResource* prefetch(const QUrl& url, void* extra, ulong extraHash); // FIXME: The return type is not recognized by JavaScript. /// Loads a resource from the specified URL and returns it. @@ -383,7 +383,7 @@ class ScriptableResourceCache : public QObject { Q_INVOKABLE ScriptableResource* prefetch(const QUrl& url) { return prefetch(url, nullptr, std::numeric_limits::max()); } // FIXME: This function variation shouldn't be in the API. - Q_INVOKABLE ScriptableResource* prefetch(const QUrl& url, void* extra, size_t extraHash); + Q_INVOKABLE ScriptableResource* prefetch(const QUrl& url, void* extra, ulong extraHash); signals: diff --git a/libraries/script-engine/src/Vec3.h b/libraries/script-engine/src/Vec3.h index f9b99f0cbc2..df0eb79c7a8 100644 --- a/libraries/script-engine/src/Vec3.h +++ b/libraries/script-engine/src/Vec3.h @@ -188,7 +188,7 @@ public slots: * var result = Vec3.multiplyQbyV(q, v); * print(JSON.stringify(result)); // {"x":0,"y":1.000,"z":1.19e-7} */ - glm::vec<3,float,glm::packed_highp> multiplyQbyV(const glm::quat& q, const glm::vec<3,float,glm::packed_highp>& v) { return q * v; } + glm::vec<3,float,glm::packed_highp> multiplyQbyV(const glm::qua& q, const glm::vec<3,float,glm::packed_highp>& v) { return q * v; } /*@jsdoc * Calculates the sum of two vectors. From 010a2678f3f3d92cf2b4e7d6bcc6c8fca6bd9315 Mon Sep 17 00:00:00 2001 From: Karol Suprynowicz Date: Sun, 28 Sep 2025 21:32:58 +0200 Subject: [PATCH 015/111] Work around QVariant copy bug --- .../script-engine/src/v8/ScriptObjectV8Proxy.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/libraries/script-engine/src/v8/ScriptObjectV8Proxy.cpp b/libraries/script-engine/src/v8/ScriptObjectV8Proxy.cpp index 8b46773975c..2bcd7c36b5a 100644 --- a/libraries/script-engine/src/v8/ScriptObjectV8Proxy.cpp +++ b/libraries/script-engine/src/v8/ScriptObjectV8Proxy.cpp @@ -1017,6 +1017,7 @@ void ScriptMethodV8Proxy::call(const v8::FunctionCallbackInfo& argume for (int i = 0; i < num_metas; i++) { const QMetaMethod& meta = _metas[i]; + qVarArgLists[i].reserve(_numMaxParams); // This check is needed for catching issues caused by a bug in Qt6 that causes metamethod invocations to fail when typedefs are used in header files. #if !defined(QT_NO_DEBUG) || defined(QT_FORCE_ASSERTS) @@ -1051,19 +1052,18 @@ void ScriptMethodV8Proxy::call(const v8::FunctionCallbackInfo& argume qScriptArgLists[i].append(ScriptValue(new ScriptValueV8Wrapper(_engine, V8ScriptValue(_engine, argVal)))); qGenArgsVectors[i][arg] = Q_GENERIC_ARG(ScriptValue, qScriptArgLists[i].back()); } else if (methodArgTypeId == QMetaType::QVariant) { - QVariant varArgVal; - if (!_engine->castValueToVariant(V8ScriptValue(_engine, argVal), varArgVal, methodArgTypeId)) { + qVarArgLists[i].emplace_back(); + if (!_engine->castValueToVariant(V8ScriptValue(_engine, argVal), qVarArgLists[i].back(), methodArgTypeId)) { conversionFailures++; + qVarArgLists[i].pop_back(); } else { - qVarArgLists[i].append(varArgVal); qGenArgsVectors[i][arg] = Q_GENERIC_ARG(QVariant, qVarArgLists[i].back()); } } else { - QVariant varArgVal; - if (!_engine->castValueToVariant(V8ScriptValue(_engine, argVal), varArgVal, methodArgTypeId)) { + qVarArgLists[i].emplace_back(); + if (!_engine->castValueToVariant(V8ScriptValue(_engine, argVal), qVarArgLists[i].back(), methodArgTypeId)) { conversionFailures++; } else { - qVarArgLists[i].append(varArgVal); const QVariant& converted = qVarArgLists[i].back(); conversionPenaltyScore += _engine->computeCastPenalty(V8ScriptValue(_engine, argVal), methodArgTypeId); From 320bd98e38658d7a551918f0be55a165f8a3d943 Mon Sep 17 00:00:00 2001 From: Karol Suprynowicz Date: Sun, 28 Sep 2025 22:58:22 +0200 Subject: [PATCH 016/111] More workarounds for MOC typedef bug --- .../src/AgentScriptingInterface.h | 2 +- interface/src/avatar/MyAvatar.h | 2 +- .../scripting/ControllerScriptingInterface.h | 2 +- .../src/scripting/HMDScriptingInterface.h | 8 ++++---- .../src/scripting/WindowScriptingInterface.h | 2 +- interface/src/ui/InteractiveWindow.h | 18 +++++++++--------- interface/src/ui/overlays/Overlays.h | 2 +- libraries/audio/src/AudioScriptingInterface.h | 4 ++-- .../audio/src/SoundCacheScriptingInterface.h | 2 +- .../src/display-plugins/CompositorHelper.h | 4 ++-- .../render-utils/src/AmbientOcclusionEffect.h | 2 +- .../render-utils/src/AntialiasingEffect.h | 2 +- libraries/ui/src/QmlWindowClass.h | 14 +++++++------- 13 files changed, 32 insertions(+), 32 deletions(-) diff --git a/assignment-client/src/AgentScriptingInterface.h b/assignment-client/src/AgentScriptingInterface.h index 1146c4006ba..9bebad13ca0 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(QSharedPointer avatarSound) const { _agent->playAvatarSound(avatarSound); } private: Agent* _agent; diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index ddd485e7334..205243807fc 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -605,7 +605,7 @@ class MyAvatar : public Avatar { void updateFromHMDSensorMatrix(const glm::mat4& 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); diff --git a/interface/src/scripting/ControllerScriptingInterface.h b/interface/src/scripting/ControllerScriptingInterface.h index 4128976fdc5..364f299e9ab 100644 --- a/interface/src/scripting/ControllerScriptingInterface.h +++ b/interface/src/scripting/ControllerScriptingInterface.h @@ -345,7 +345,7 @@ public slots: * @function Controller.getViewportDimensions * @returns {Vec2} The dimensions of the Interface window interior if in desktop mode or HUD surface if in HMD mode. */ - virtual glm::vec2 getViewportDimensions() const; + virtual glm::vec<2,float,glm::packed_highp> getViewportDimensions() const; /*@jsdoc * Gets the recommended area to position UI on the HUD surface if in HMD mode or Interface's window interior if in desktop diff --git a/interface/src/scripting/HMDScriptingInterface.h b/interface/src/scripting/HMDScriptingInterface.h index 3cc7db442ca..5e8e597ed4c 100644 --- a/interface/src/scripting/HMDScriptingInterface.h +++ b/interface/src/scripting/HMDScriptingInterface.h @@ -170,7 +170,7 @@ class HMDScriptingInterface : public AbstractHMDScriptingInterface, public Depen * Overlays.deleteOverlay(square); * }); */ - Q_INVOKABLE glm::vec2 overlayFromWorldPoint(const glm::vec<3,float,glm::packed_highp>& position) const; + Q_INVOKABLE glm::vec<2,float,glm::packed_highp> overlayFromWorldPoint(const glm::vec<3,float,glm::packed_highp>& position) const; /*@jsdoc * Gets the 3D world coordinates of a 2D point on the HUD overlay. @@ -179,7 +179,7 @@ class HMDScriptingInterface : public AbstractHMDScriptingInterface, public Depen * @param {Vec2} coordinates - The point on the HUD overlay in HUD coordinates. * @returns {Vec3} The point on the HUD overlay in world coordinates. */ - Q_INVOKABLE glm::vec<3,float,glm::packed_highp> worldPointFromOverlay(const glm::vec2& overlay) const; + Q_INVOKABLE glm::vec<3,float,glm::packed_highp> worldPointFromOverlay(const glm::vec<2,float,glm::packed_highp>& overlay) const; /*@jsdoc * Gets the 2D point on the HUD overlay represented by given spherical coordinates. @@ -190,7 +190,7 @@ class HMDScriptingInterface : public AbstractHMDScriptingInterface, public Depen * @param {Vec2} sphericalPos - The point on the HUD overlay in spherical coordinates. * @returns {Vec2} The point on the HUD overlay in HUD coordinates. */ - Q_INVOKABLE glm::vec2 sphericalToOverlay(const glm::vec2 & sphericalPos) const; + Q_INVOKABLE glm::vec<2,float,glm::packed_highp> sphericalToOverlay(const glm::vec<2,float,glm::packed_highp> & sphericalPos) const; /*@jsdoc * Gets the spherical coordinates of a 2D point on the HUD overlay. @@ -201,7 +201,7 @@ class HMDScriptingInterface : public AbstractHMDScriptingInterface, public Depen * @param {Vec2} overlayPos - The point on the HUD overlay in HUD coordinates. * @returns {Vec2} The point on the HUD overlay in spherical coordinates. */ - Q_INVOKABLE glm::vec2 overlayToSpherical(const glm::vec2 & overlayPos) const; + Q_INVOKABLE glm::vec<2,float,glm::packed_highp> overlayToSpherical(const glm::vec<2,float,glm::packed_highp> & overlayPos) const; /*@jsdoc * Recenters the HMD HUD to the current HMD position and orientation. diff --git a/interface/src/scripting/WindowScriptingInterface.h b/interface/src/scripting/WindowScriptingInterface.h index c8215408c34..2819538def1 100644 --- a/interface/src/scripting/WindowScriptingInterface.h +++ b/interface/src/scripting/WindowScriptingInterface.h @@ -483,7 +483,7 @@ public slots: * @function Window.getDeviceSize * @returns {Vec2} The width and height of the Interface window or HMD rendering surface, in pixels. */ - glm::vec2 getDeviceSize() const; + glm::vec<2,float,glm::packed_highp> getDeviceSize() const; /*@jsdoc * Gets the last domain connection error when a connection is refused. diff --git a/interface/src/ui/InteractiveWindow.h b/interface/src/ui/InteractiveWindow.h index d2322d27c57..1c98c4d0cde 100644 --- a/interface/src/ui/InteractiveWindow.h +++ b/interface/src/ui/InteractiveWindow.h @@ -186,10 +186,10 @@ class InteractiveWindow : public QObject { Q_OBJECT Q_PROPERTY(QString title READ getTitle WRITE setTitle) - Q_PROPERTY(glm::vec2 position READ getPosition WRITE setPosition) + Q_PROPERTY(glm::vec<2,float,glm::packed_highp> position READ getPosition WRITE setPosition) Q_PROPERTY(RelativePositionAnchor relativePositionAnchor READ getRelativePositionAnchor WRITE setRelativePositionAnchor) - Q_PROPERTY(glm::vec2 relativePosition READ getRelativePosition WRITE setRelativePosition) - Q_PROPERTY(glm::vec2 size READ getSize WRITE setSize) + Q_PROPERTY(glm::vec<2,float,glm::packed_highp> relativePosition READ getRelativePosition WRITE setRelativePosition) + Q_PROPERTY(glm::vec<2,float,glm::packed_highp> size READ getSize WRITE setSize) Q_PROPERTY(bool visible READ isVisible WRITE setVisible) Q_PROPERTY(int presentationMode READ getPresentationMode WRITE setPresentationMode) @@ -202,8 +202,8 @@ class InteractiveWindow : public QObject { Q_INVOKABLE QString getTitle() const; Q_INVOKABLE void setTitle(const QString& title); - Q_INVOKABLE glm::vec2 getPosition() const; - Q_INVOKABLE void setPosition(const glm::vec2& position); + Q_INVOKABLE glm::vec<2,float,glm::packed_highp> getPosition() const; + Q_INVOKABLE void setPosition(const glm::vec<2,float,glm::packed_highp>& position); RelativePositionAnchor _relativePositionAnchor{ RelativePositionAnchor::NO_ANCHOR }; Q_INVOKABLE RelativePositionAnchor getRelativePositionAnchor() const; @@ -212,16 +212,16 @@ class InteractiveWindow : public QObject { // This "relative position" is relative to the "relative position anchor" and excludes the window frame. // This position will ALWAYS include the geometry of a docked widget, if one is present. glm::vec2 _relativePosition{ 0.0f, 0.0f }; - Q_INVOKABLE glm::vec2 getRelativePosition() const; - Q_INVOKABLE void setRelativePosition(const glm::vec2& position); + Q_INVOKABLE glm::vec<2,float,glm::packed_highp> getRelativePosition() const; + Q_INVOKABLE void setRelativePosition(const glm::vec<2,float,glm::packed_highp>& position); Q_INVOKABLE void setPositionUsingRelativePositionAndAnchor(const QRect& mainWindowGeometry); bool _isFullScreenWindow{ false }; Q_INVOKABLE void repositionAndResizeFullScreenWindow(); - Q_INVOKABLE glm::vec2 getSize() const; - Q_INVOKABLE void setSize(const glm::vec2& size); + Q_INVOKABLE glm::vec<2,float,glm::packed_highp> getSize() const; + Q_INVOKABLE void setSize(const glm::vec<2,float,glm::packed_highp>& size); Q_INVOKABLE void setVisible(bool visible); Q_INVOKABLE bool isVisible() const; diff --git a/interface/src/ui/overlays/Overlays.h b/interface/src/ui/overlays/Overlays.h index 21fed55c41b..aabdeaeba94 100644 --- a/interface/src/ui/overlays/Overlays.h +++ b/interface/src/ui/overlays/Overlays.h @@ -306,7 +306,7 @@ public slots: * print("Clicked: " + overlay); * }); */ - QUuid getOverlayAtPoint(const glm::vec2& point); + QUuid getOverlayAtPoint(const glm::vec<2,float,glm::packed_highp>& point); /*@jsdoc * Finds the closest 3D overlay (or local entity) intersected by a {@link PickRay}. diff --git a/libraries/audio/src/AudioScriptingInterface.h b/libraries/audio/src/AudioScriptingInterface.h index 1f43e5f45a0..b6ad0f17596 100644 --- a/libraries/audio/src/AudioScriptingInterface.h +++ b/libraries/audio/src/AudioScriptingInterface.h @@ -196,7 +196,7 @@ class AudioScriptingInterface : public QObject, public Dependency { * sound.ready.connect(onSoundReady); * } */ - Q_INVOKABLE ScriptAudioInjector* playSound(SharedSoundPointer sound, const AudioInjectorOptions& injectorOptions = AudioInjectorOptions()); + Q_INVOKABLE ScriptAudioInjector* playSound(QSharedPointer sound, const AudioInjectorOptions& injectorOptions = AudioInjectorOptions()); /*@jsdoc * Starts playing the content of an audio file locally (isn't sent to the audio mixer). This is the same as calling @@ -207,7 +207,7 @@ class AudioScriptingInterface : public QObject, public Dependency { * {@link SoundObject} for supported formats. * @returns {AudioInjector} The audio injector that plays the audio file. */ - Q_INVOKABLE ScriptAudioInjector* playSystemSound(SharedSoundPointer sound); + Q_INVOKABLE ScriptAudioInjector* playSystemSound(QSharedPointer sound); /*@jsdoc * Sets whether the audio input should be used in stereo. If the audio input doesn't support stereo then setting a value diff --git a/libraries/audio/src/SoundCacheScriptingInterface.h b/libraries/audio/src/SoundCacheScriptingInterface.h index 41ad7b69387..c9790dbb367 100644 --- a/libraries/audio/src/SoundCacheScriptingInterface.h +++ b/libraries/audio/src/SoundCacheScriptingInterface.h @@ -60,7 +60,7 @@ class SoundCacheScriptingInterface : public ScriptableResourceCache, public Depe * formats. * @returns {SoundObject} The sound ready for playback. */ - Q_INVOKABLE SharedSoundPointer getSound(const QUrl& url); + Q_INVOKABLE QSharedPointer getSound(const QUrl& url); }; #endif // hifi_SoundCacheScriptingInterface_h diff --git a/libraries/display-plugins/src/display-plugins/CompositorHelper.h b/libraries/display-plugins/src/display-plugins/CompositorHelper.h index a952b802383..c1f3a4dfec3 100644 --- a/libraries/display-plugins/src/display-plugins/CompositorHelper.h +++ b/libraries/display-plugins/src/display-plugins/CompositorHelper.h @@ -215,7 +215,7 @@ class ReticleInterface : public QObject { Q_PROPERTY(bool visible READ getVisible WRITE setVisible) Q_PROPERTY(float depth READ getDepth WRITE setDepth) Q_PROPERTY(float scale READ getScale WRITE setScale) - Q_PROPERTY(glm::vec2 maximumPosition READ getMaximumPosition) + Q_PROPERTY(glm::vec<2,float,glm::packed_highp> maximumPosition READ getMaximumPosition) Q_PROPERTY(bool mouseCaptured READ isMouseCaptured) Q_PROPERTY(bool allowMouseCapture READ getAllowMouseCapture WRITE setAllowMouseCapture) Q_PROPERTY(bool pointingAtSystemOverlay READ isPointingAtSystemOverlay) @@ -332,7 +332,7 @@ class ReticleInterface : public QObject { * @function Reticle.getMaximumPosition * @returns {Vec2} The maximum reticle coordinates on the display device in desktop mode or the HUD surface in HMD mode. */ - Q_INVOKABLE glm::vec2 getMaximumPosition() { return _compositor->getReticleMaximumPosition(); } + Q_INVOKABLE glm::vec<2,float,glm::packed_highp> getMaximumPosition() { return _compositor->getReticleMaximumPosition(); } private: CompositorHelper* _compositor; diff --git a/libraries/render-utils/src/AmbientOcclusionEffect.h b/libraries/render-utils/src/AmbientOcclusionEffect.h index 9debb203ec8..b555c45fa74 100644 --- a/libraries/render-utils/src/AmbientOcclusionEffect.h +++ b/libraries/render-utils/src/AmbientOcclusionEffect.h @@ -248,7 +248,7 @@ class DebugAmbientOcclusionConfig : public render::Job::Config { Q_OBJECT Q_PROPERTY(bool showCursorPixel MEMBER showCursorPixel NOTIFY dirty) - Q_PROPERTY(glm::vec2 debugCursorTexcoord MEMBER debugCursorTexcoord NOTIFY dirty) + Q_PROPERTY(glm::vec<2,float,glm::packed_highp> debugCursorTexcoord MEMBER debugCursorTexcoord NOTIFY dirty) public: DebugAmbientOcclusionConfig() : render::Job::Config(false) {} diff --git a/libraries/render-utils/src/AntialiasingEffect.h b/libraries/render-utils/src/AntialiasingEffect.h index b99e96a31cb..d414bc83a27 100644 --- a/libraries/render-utils/src/AntialiasingEffect.h +++ b/libraries/render-utils/src/AntialiasingEffect.h @@ -146,7 +146,7 @@ class AntialiasingConfig : public render::Job::Config { Q_PROPERTY(bool fxaaOnOff READ debugFXAA WRITE setDebugFXAA NOTIFY dirty) Q_PROPERTY(float debugShowVelocityThreshold MEMBER debugShowVelocityThreshold NOTIFY dirty) Q_PROPERTY(bool showCursorPixel MEMBER showCursorPixel NOTIFY dirty) - Q_PROPERTY(glm::vec2 debugCursorTexcoord MEMBER debugCursorTexcoord NOTIFY dirty) + Q_PROPERTY(glm::vec<2,float,glm::packed_highp> debugCursorTexcoord MEMBER debugCursorTexcoord NOTIFY dirty) Q_PROPERTY(float debugOrbZoom MEMBER debugOrbZoom NOTIFY dirty) Q_PROPERTY(bool showClosestFragment MEMBER showClosestFragment NOTIFY dirty) diff --git a/libraries/ui/src/QmlWindowClass.h b/libraries/ui/src/QmlWindowClass.h index a37559612b9..ebdf5b31fa9 100644 --- a/libraries/ui/src/QmlWindowClass.h +++ b/libraries/ui/src/QmlWindowClass.h @@ -50,8 +50,8 @@ class ScriptEngine; // FIXME refactor this class to be a QQuickItem derived type and eliminate the needless wrapping class QmlWindowClass : public QObject { Q_OBJECT - Q_PROPERTY(glm::vec2 position READ getPosition WRITE setPosition NOTIFY positionChanged) - Q_PROPERTY(glm::vec2 size READ getSize WRITE setSize NOTIFY sizeChanged) + Q_PROPERTY(glm::vec<2,float,glm::packed_highp> position READ getPosition WRITE setPosition NOTIFY positionChanged) + Q_PROPERTY(glm::vec<2,float,glm::packed_highp> size READ getSize WRITE setSize NOTIFY sizeChanged) Q_PROPERTY(bool visible READ isVisible WRITE setVisible NOTIFY visibleChanged) private: @@ -102,14 +102,14 @@ public slots: * @function OverlayWindow.getPosition * @returns {Vec2} The position of the window, in pixels. */ - glm::vec2 getPosition(); + glm::vec<2,float,glm::packed_highp> getPosition(); /*@jsdoc * Sets the position of the window, from a {@link Vec2}. * @function OverlayWindow.setPosition * @param {Vec2} position - The position of the window, in pixels. */ - void setPosition(const glm::vec2& position); + void setPosition(const glm::vec<2,float,glm::packed_highp>& position); /*@jsdoc * Sets the position of the window, from a pair of numbers. @@ -125,14 +125,14 @@ public slots: * @function OverlayWindow.getSize * @returns {Vec2} The size of the window interior, in pixels. */ - glm::vec2 getSize(); + glm::vec<2,float,glm::packed_highp> getSize(); /*@jsdoc * Sets the size of the window interior, from a {@link Vec2}. * @function OverlayWindow.setSize * @param {Vec2} size - The size of the window interior, in pixels. */ - void setSize(const glm::vec2& size); + void setSize(const glm::vec<2,float,glm::packed_highp>& size); /*@jsdoc * Sets the size of the window interior, from a pair of numbers. @@ -275,7 +275,7 @@ public slots: * @param {Vec2} position - The position of the window, in pixels. * @returns {Signal} */ - void moved(glm::vec2 position); + void moved(glm::vec<2,float,glm::packed_highp> position); /*@jsdoc * Triggered when the window changes size. From 1d734d5a851f4139603eb90a10e4b044f8ea4a62 Mon Sep 17 00:00:00 2001 From: Karol Suprynowicz Date: Tue, 30 Sep 2025 19:25:32 +0200 Subject: [PATCH 017/111] Fixed memory safety issue in ScriptObjectV8Proxy --- .../src/scripting/ControllerScriptingInterface.h | 2 ++ .../src/controllers/ScriptingInterface.h | 8 ++++---- .../script-engine/src/v8/ScriptObjectV8Proxy.cpp | 14 +++++++------- .../script-engine/src/v8/ScriptObjectV8Proxy.h | 6 +++--- 4 files changed, 16 insertions(+), 14 deletions(-) diff --git a/interface/src/scripting/ControllerScriptingInterface.h b/interface/src/scripting/ControllerScriptingInterface.h index 364f299e9ab..ef6bddf1c67 100644 --- a/interface/src/scripting/ControllerScriptingInterface.h +++ b/interface/src/scripting/ControllerScriptingInterface.h @@ -484,6 +484,8 @@ public slots: using InputKey = controller::InputController::Key; }; +Q_DECLARE_METATYPE(ControllerScriptingInterface); + const int NUMBER_OF_SPATIALCONTROLS_PER_PALM = 2; // the hand and the tip const int NUMBER_OF_JOYSTICKS_PER_PALM = 1; const int NUMBER_OF_TRIGGERS_PER_PALM = 1; diff --git a/libraries/controllers/src/controllers/ScriptingInterface.h b/libraries/controllers/src/controllers/ScriptingInterface.h index 9c9483d776f..4ae598aebb0 100644 --- a/libraries/controllers/src/controllers/ScriptingInterface.h +++ b/libraries/controllers/src/controllers/ScriptingInterface.h @@ -205,7 +205,7 @@ namespace controller { * var RIGHT_HAND = 1; * Controller.triggerHapticPulse(HAPTIC_STRENGTH, HAPTIC_DURATION, RIGHT_HAND); */ - Q_INVOKABLE bool triggerHapticPulse(float strength, float duration, uint16_t index = 2) const; + Q_INVOKABLE bool triggerHapticPulse(float strength, float duration, ushort index = 2) const; /*@jsdoc * Triggers a 250ms haptic pulse on connected and enabled devices that have the capability. @@ -216,7 +216,7 @@ namespace controller { * index = 1 is the right hand, and index = 2 is both hands. For other devices, * such as haptic vests, index will have a different meaning, defined by the input device. */ - Q_INVOKABLE bool triggerShortHapticPulse(float strength, uint16_t index = 2) const; + Q_INVOKABLE bool triggerShortHapticPulse(float strength, ushort index = 2) const; /*@jsdoc * Triggers a haptic pulse on a particular device if connected and enabled and it has the capability. @@ -236,7 +236,7 @@ namespace controller { * Controller.triggerHapticPulseOnDevice(deviceID, HAPTIC_STRENGTH, HAPTIC_DURATION, RIGHT_HAND); */ Q_INVOKABLE bool triggerHapticPulseOnDevice(unsigned int device, float strength, float duration, - uint16_t index = 2) const; + ushort index = 2) const; /*@jsdoc * Triggers a 250ms haptic pulse on a particular device if connected and enabled and it has the capability. @@ -248,7 +248,7 @@ namespace controller { * index = 1 is the right hand, and index = 2 is both hands. For other devices, * such as haptic vests, index will have a different meaning, defined by the input device. */ - Q_INVOKABLE bool triggerShortHapticPulseOnDevice(unsigned int device, float strength, uint16_t index = 2) const; + Q_INVOKABLE bool triggerShortHapticPulseOnDevice(unsigned int device, float strength, ushort index = 2) const; /*@jsdoc * Creates a new controller mapping. Routes can then be added to the mapping using {@link MappingObject} methods and diff --git a/libraries/script-engine/src/v8/ScriptObjectV8Proxy.cpp b/libraries/script-engine/src/v8/ScriptObjectV8Proxy.cpp index 2bcd7c36b5a..ac863fb2608 100644 --- a/libraries/script-engine/src/v8/ScriptObjectV8Proxy.cpp +++ b/libraries/script-engine/src/v8/ScriptObjectV8Proxy.cpp @@ -292,10 +292,10 @@ void ScriptObjectV8Proxy::investigate() { } PropertyDef& propDef = _props.insert(idx, PropertyDef(prop.name(), idx)).value(); - _propNameMap.insert(prop.name(), &propDef); propDef.flags = ScriptValue::Undeletable | ScriptValue::PropertyGetter | ScriptValue::PropertySetter | ScriptValue::QObjectMember; if (prop.isConstant()) propDef.flags |= ScriptValue::ReadOnly; + _propNameMap.insert(prop.name(), propDef); } // discover methods @@ -343,7 +343,7 @@ void ScriptObjectV8Proxy::investigate() { SignalDef& signalDef = _signals.insert(idx, SignalDef(szName, idx)).value(); signalDef.name = szName; signalDef.signal = method; - _signalNameMap.insert(szName, &signalDef); + _signalNameMap.insert(szName, signalDef); methodNames.insert(szName, idx); } else { int originalMethodId = nameLookup.value(); @@ -370,7 +370,7 @@ void ScriptObjectV8Proxy::investigate() { methodDef.name = szName; methodDef.numMaxParams = parameterCount; methodDef.methods.append(method); - _methodNameMap.insert(szName, &methodDef); + _methodNameMap.insert(szName, methodDef); methodNames.insert(szName, idx); } else { int originalMethodId = nameLookup.value(); @@ -434,16 +434,16 @@ ScriptObjectV8Proxy::QueryFlags ScriptObjectV8Proxy::queryProperty(const V8Scrip // check for methods MethodNameMap::const_iterator method = _methodNameMap.find(nameStr); if (method != _methodNameMap.cend()) { - const MethodDef* methodDef = method.value(); - *id = methodDef->_id | METHOD_TYPE; + const MethodDef &methodDef = method.value(); + *id = methodDef._id | METHOD_TYPE; return flags & (HandlesReadAccess | HandlesWriteAccess); } // check for properties PropertyNameMap::const_iterator prop = _propNameMap.find(nameStr); if (prop != _propNameMap.cend()) { - const PropertyDef* propDef = prop.value(); - *id = propDef->_id | PROPERTY_TYPE; + const PropertyDef &propDef = prop.value(); + *id = propDef._id | PROPERTY_TYPE; return flags & (HandlesReadAccess | HandlesWriteAccess); } diff --git a/libraries/script-engine/src/v8/ScriptObjectV8Proxy.h b/libraries/script-engine/src/v8/ScriptObjectV8Proxy.h index 14b8f1900eb..3d7584aae36 100644 --- a/libraries/script-engine/src/v8/ScriptObjectV8Proxy.h +++ b/libraries/script-engine/src/v8/ScriptObjectV8Proxy.h @@ -69,9 +69,9 @@ class ScriptObjectV8Proxy final { using MethodDefMap = QHash; using SignalDefMap = QHash; using InstanceMap = QHash >; - using PropertyNameMap = QHash; - using MethodNameMap = QHash; - using SignalNameMap = QHash; + using PropertyNameMap = QHash; + using MethodNameMap = QHash; + using SignalNameMap = QHash; static constexpr uint PROPERTY_TYPE = 0x1000; static constexpr uint METHOD_TYPE = 0x2000; From aa0f26c8ad026432308830b86746dd94b6eb5969 Mon Sep 17 00:00:00 2001 From: Karol Suprynowicz Date: Tue, 30 Sep 2025 23:47:43 +0200 Subject: [PATCH 018/111] Re-enable QML rendering --- interface/resources/qml/desktop/Desktop.qml | 10 ++++++++++ interface/src/Application_Setup.cpp | 3 ++- interface/src/main.cpp | 2 ++ interface/src/ui/overlays/Overlays.cpp | 3 ++- libraries/qml/src/qml/impl/RenderEventHandler.cpp | 2 +- 5 files changed, 17 insertions(+), 3 deletions(-) diff --git a/interface/resources/qml/desktop/Desktop.qml b/interface/resources/qml/desktop/Desktop.qml index 6ea6108f009..d608ce8c079 100644 --- a/interface/resources/qml/desktop/Desktop.qml +++ b/interface/resources/qml/desktop/Desktop.qml @@ -21,6 +21,16 @@ FocusScope { objectName: "desktop" anchors.fill: parent + /*Rectangle { + anchors.top: parent.top + anchors.left: parent.left + width: parent.width / 3 + height: parent.height / 3 + + color: "red" + radius: 8 + }*/ + readonly property int invalid_position: -9999; property rect recommendedRect: Qt.rect(0,0,0,0); property var expectedChildren; diff --git a/interface/src/Application_Setup.cpp b/interface/src/Application_Setup.cpp index 9a9ecefd62c..f563c310aec 100644 --- a/interface/src/Application_Setup.cpp +++ b/interface/src/Application_Setup.cpp @@ -762,7 +762,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(); diff --git a/interface/src/main.cpp b/interface/src/main.cpp index 2edcf7f09eb..b8c985eb297 100644 --- a/interface/src/main.cpp +++ b/interface/src/main.cpp @@ -82,6 +82,8 @@ int main(int argc, const char* argv[]) { qputenv("QSG_DISTANCEFIELD_ANTIALIASING", "gray"); } + QSurfaceFormat::setDefaultFormat(getDefaultOpenGLSurfaceFormat()); + // Setup QCoreApplication settings, install log message handler setupHifiApplication(BuildInfo::INTERFACE_NAME); diff --git a/interface/src/ui/overlays/Overlays.cpp b/interface/src/ui/overlays/Overlays.cpp index d4b0b4706bb..19da0e8991d 100644 --- a/interface/src/ui/overlays/Overlays.cpp +++ b/interface/src/ui/overlays/Overlays.cpp @@ -378,7 +378,8 @@ QUuid Overlays::getOverlayAtPoint(const glm::vec2& point) { if (QThread::currentThread() != thread()) { QUuid result; - BLOCKING_INVOKE_METHOD(this, "getOverlayAtPoint", Q_GENERIC_RETURN_ARG(QUuid, result), Q_GENERIC_ARG(const glm::vec2&, point)); + BLOCKING_INVOKE_METHOD(this, "getOverlayAtPoint", Q_GENERIC_RETURN_ARG(QUuid, result), + QArgument& >("const glm::vec<2,float,glm::packed_highp>&", point)); return result; } diff --git a/libraries/qml/src/qml/impl/RenderEventHandler.cpp b/libraries/qml/src/qml/impl/RenderEventHandler.cpp index 74f45969a88..b439b290fc1 100644 --- a/libraries/qml/src/qml/impl/RenderEventHandler.cpp +++ b/libraries/qml/src/qml/impl/RenderEventHandler.cpp @@ -159,7 +159,7 @@ void RenderEventHandler::qmlRender(bool sceneGraphSync) { // Qt6 TODO: Qt says that it doesn't have a valid render target // on the deferred renderer, and on the forward one it just makes // the screen gray as if the size hasn't been set properly - //_shared->_renderControl->render(); + _shared->_renderControl->render(); } _shared->_renderControl->endFrame(); _shared->_lastRenderTime = usecTimestampNow(); From 985582a6bc15da48acdd6c873092caaa53d42510 Mon Sep 17 00:00:00 2001 From: Ada Date: Wed, 1 Oct 2025 23:54:36 +1000 Subject: [PATCH 019/111] Qt6: QML render target now set up properly, QML web entities rendering --- libraries/qml/src/qml/impl/RenderEventHandler.cpp | 5 +---- libraries/qml/src/qml/impl/SharedObject.cpp | 4 ++-- libraries/qml/src/qml/impl/SharedObject.h | 4 +++- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/libraries/qml/src/qml/impl/RenderEventHandler.cpp b/libraries/qml/src/qml/impl/RenderEventHandler.cpp index b439b290fc1..03d46148643 100644 --- a/libraries/qml/src/qml/impl/RenderEventHandler.cpp +++ b/libraries/qml/src/qml/impl/RenderEventHandler.cpp @@ -155,10 +155,7 @@ void RenderEventHandler::qmlRender(bool sceneGraphSync) { glClear(GL_COLOR_BUFFER_BIT); } else { glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); - _shared->setRenderTarget(_fbo, _currentSize); - // Qt6 TODO: Qt says that it doesn't have a valid render target - // on the deferred renderer, and on the forward one it just makes - // the screen gray as if the size hasn't been set properly + _shared->setRenderTarget(texture, _currentSize); _shared->_renderControl->render(); } _shared->_renderControl->endFrame(); diff --git a/libraries/qml/src/qml/impl/SharedObject.cpp b/libraries/qml/src/qml/impl/SharedObject.cpp index 1d56afbbd41..ec23d3fad6a 100644 --- a/libraries/qml/src/qml/impl/SharedObject.cpp +++ b/libraries/qml/src/qml/impl/SharedObject.cpp @@ -320,9 +320,9 @@ void SharedObject::releaseTextureAndFence() { #endif } -void SharedObject::setRenderTarget(uint32_t fbo, const QSize& size) { +void SharedObject::setRenderTarget(uint32_t texture, const QSize& size) { #ifndef DISABLE_QML - _quickWindow->setRenderTarget(QQuickRenderTarget::fromOpenGLTexture(fbo, size)); + _quickWindow->setRenderTarget(QQuickRenderTarget::fromOpenGLTexture(texture, size)); #endif } diff --git a/libraries/qml/src/qml/impl/SharedObject.h b/libraries/qml/src/qml/impl/SharedObject.h index 4f287471e82..3c187f1707a 100644 --- a/libraries/qml/src/qml/impl/SharedObject.h +++ b/libraries/qml/src/qml/impl/SharedObject.h @@ -73,7 +73,9 @@ class SharedObject : public QObject { // Called by the render event handler, from the render thread void initializeRenderControl(QOpenGLContext* context); void releaseTextureAndFence(); - void setRenderTarget(uint32_t fbo, const QSize& size); + + // NOTE: On Qt5 this took an FBO handle, on Qt6 it takes a texture handle + void setRenderTarget(uint32_t texture, const QSize& size); QQmlEngine* acquireEngine(OffscreenSurface* surface); void releaseEngine(QQmlEngine* engine); From 5aac56392638b8560061512c3be7c4c51d627196 Mon Sep 17 00:00:00 2001 From: Karol Suprynowicz Date: Thu, 2 Oct 2025 00:10:00 +0200 Subject: [PATCH 020/111] Removed properties with missing font --- scripts/developer/utilities/lib/jet/qml/TaskTimeFrameView.qml | 3 ++- scripts/developer/utilities/lib/plotperf/PlotPerf.qml | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/scripts/developer/utilities/lib/jet/qml/TaskTimeFrameView.qml b/scripts/developer/utilities/lib/jet/qml/TaskTimeFrameView.qml index 2c8949d68e5..3a2966dbe5a 100644 --- a/scripts/developer/utilities/lib/jet/qml/TaskTimeFrameView.qml +++ b/scripts/developer/utilities/lib/jet/qml/TaskTimeFrameView.qml @@ -181,7 +181,8 @@ Rectangle { var ctx = getContext("2d"); ctx.clearRect(0, 0, width, height); - ctx.font="12px Verdana"; + // QT6TODO + //ctx.font="12px Verdana"; displayBackground(ctx); if (jobsArray.length > 0) { diff --git a/scripts/developer/utilities/lib/plotperf/PlotPerf.qml b/scripts/developer/utilities/lib/plotperf/PlotPerf.qml index 0d08678deae..05103f923d1 100644 --- a/scripts/developer/utilities/lib/plotperf/PlotPerf.qml +++ b/scripts/developer/utilities/lib/plotperf/PlotPerf.qml @@ -264,7 +264,8 @@ Item { var ctx = getContext("2d"); ctx.clearRect(0, 0, width, height); - ctx.font="12px Verdana"; + // QT6TODO + //ctx.font="12px Verdana"; displayBackground(ctx); From 6145db62acb3226c575621e3490f487121a431c8 Mon Sep 17 00:00:00 2001 From: Karol Suprynowicz Date: Thu, 2 Oct 2025 00:10:44 +0200 Subject: [PATCH 021/111] Fixed glm::mat4 typedefs for MOC --- interface/src/avatar/MyAvatar.h | 32 +++++++++---------- libraries/avatars/src/AvatarData.h | 12 +++---- libraries/avatars/src/ScriptAvatarData.h | 12 +++---- .../src/controllers/impl/RouteBuilderProxy.h | 4 +-- .../src/ModelScriptingInterface.h | 2 +- .../entities/src/EntityScriptingInterface.h | 4 +-- .../graphics-scripting/ScriptableMeshPart.h | 2 +- libraries/script-engine/src/Mat4.h | 32 +++++++++---------- .../script-engine/src/ScriptValueUtils.h | 4 +-- 9 files changed, 52 insertions(+), 52 deletions(-) diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 205243807fc..8de13371b20 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -578,7 +578,7 @@ 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::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; } @@ -602,13 +602,13 @@ class MyAvatar : public Avatar { // 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::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. @@ -1591,16 +1591,16 @@ class MyAvatar : public Avatar { 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 @@ -1609,16 +1609,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::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 diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index f8ab3daaaf1..c835b87b2a2 100644 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -582,9 +582,9 @@ class AvatarData : public QObject, public SpatiallyNestable { Q_PROPERTY(QUuid sessionUUID READ getSessionUUID NOTIFY sessionUUIDChanged) - Q_PROPERTY(glm::mat4 sensorToWorldMatrix READ getSensorToWorldMatrix) - Q_PROPERTY(glm::mat4 controllerLeftHandMatrix READ getControllerLeftHandMatrix) - Q_PROPERTY(glm::mat4 controllerRightHandMatrix READ getControllerRightHandMatrix) + Q_PROPERTY(glm::mat<4,4,float,glm::packed_highp> sensorToWorldMatrix READ getSensorToWorldMatrix) + Q_PROPERTY(glm::mat<4,4,float,glm::packed_highp> controllerLeftHandMatrix READ getControllerLeftHandMatrix) + Q_PROPERTY(glm::mat<4,4,float,glm::packed_highp> controllerRightHandMatrix READ getControllerRightHandMatrix) Q_PROPERTY(float sensorToWorldScale READ getSensorToWorldScale) @@ -1293,7 +1293,7 @@ class AvatarData : public QObject, public SpatiallyNestable { * // Note: If using from the Avatar API, replace "MyAvatar" with "Avatar". */ // thread safe - Q_INVOKABLE glm::mat4 getSensorToWorldMatrix() const; + Q_INVOKABLE glm::mat<4,4,float,glm::packed_highp> getSensorToWorldMatrix() const; /*@jsdoc * Gets the scale that transforms dimensions in the user's real world to the avatar's size in the virtual world. @@ -1318,7 +1318,7 @@ class AvatarData : public QObject, public SpatiallyNestable { * // Note: If using from the Avatar API, replace "MyAvatar" with "Avatar". */ // thread safe - Q_INVOKABLE glm::mat4 getControllerLeftHandMatrix() const; + Q_INVOKABLE glm::mat<4,4,float,glm::packed_highp> getControllerLeftHandMatrix() const; /*@jsdoc * Gets the rotation and translation of the right hand controller relative to the avatar. @@ -1326,7 +1326,7 @@ class AvatarData : public QObject, public SpatiallyNestable { * @returns {Mat4} The rotation and translation of the right hand controller relative to the avatar. */ // thread safe - Q_INVOKABLE glm::mat4 getControllerRightHandMatrix() const; + Q_INVOKABLE glm::mat<4,4,float,glm::packed_highp> getControllerRightHandMatrix() const; /*@jsdoc diff --git a/libraries/avatars/src/ScriptAvatarData.h b/libraries/avatars/src/ScriptAvatarData.h index ed0e18f40bd..205e6d43f9f 100644 --- a/libraries/avatars/src/ScriptAvatarData.h +++ b/libraries/avatars/src/ScriptAvatarData.h @@ -64,9 +64,9 @@ class ScriptAvatarData : public QObject { // // MATRIX PROPERTIES // - Q_PROPERTY(glm::mat4 sensorToWorldMatrix READ getSensorToWorldMatrix) - Q_PROPERTY(glm::mat4 controllerLeftHandMatrix READ getControllerLeftHandMatrix) - Q_PROPERTY(glm::mat4 controllerRightHandMatrix READ getControllerRightHandMatrix) + Q_PROPERTY(glm::mat<4,4,float,glm::packed_highp> sensorToWorldMatrix READ getSensorToWorldMatrix) + Q_PROPERTY(glm::mat<4,4,float,glm::packed_highp> controllerLeftHandMatrix READ getControllerLeftHandMatrix) + Q_PROPERTY(glm::mat<4,4,float,glm::packed_highp> controllerRightHandMatrix READ getControllerRightHandMatrix) Q_PROPERTY(bool hasPriority READ getHasPriority) @@ -216,9 +216,9 @@ class ScriptAvatarData : public QObject { // // MATRIX PROPERTIES // - glm::mat4 getSensorToWorldMatrix() const; - glm::mat4 getControllerLeftHandMatrix() const; - glm::mat4 getControllerRightHandMatrix() const; + glm::mat<4,4,float,glm::packed_highp> getSensorToWorldMatrix() const; + glm::mat<4,4,float,glm::packed_highp> getControllerLeftHandMatrix() const; + glm::mat<4,4,float,glm::packed_highp> getControllerRightHandMatrix() const; bool getHasPriority() const; diff --git a/libraries/controllers/src/controllers/impl/RouteBuilderProxy.h b/libraries/controllers/src/controllers/impl/RouteBuilderProxy.h index 8c7dba1a47d..ef5757c95d7 100644 --- a/libraries/controllers/src/controllers/impl/RouteBuilderProxy.h +++ b/libraries/controllers/src/controllers/impl/RouteBuilderProxy.h @@ -385,7 +385,7 @@ class RouteBuilderProxy : public QObject { * @returns {RouteObject} The RouteObject with the pre-transform applied. */ // No JSDoc example because filter not currently used. - Q_INVOKABLE QObject* transform(glm::mat4 transform); + Q_INVOKABLE QObject* transform(glm::mat<4,4,float,glm::packed_highp> transform); /*@jsdoc * Filters {@link Pose} route values to have a post-transform applied. @@ -394,7 +394,7 @@ class RouteBuilderProxy : public QObject { * @returns {RouteObject} The RouteObject with the post-transform applied. */ // No JSDoc example because filter not currently used. - Q_INVOKABLE QObject* postTransform(glm::mat4 transform); + Q_INVOKABLE QObject* postTransform(glm::mat<4,4,float,glm::packed_highp> transform); /*@jsdoc * Filters {@link Pose} route values to have a pre-rotation applied. diff --git a/libraries/entities-renderer/src/ModelScriptingInterface.h b/libraries/entities-renderer/src/ModelScriptingInterface.h index ce804c31e1e..328ad5351e5 100644 --- a/libraries/entities-renderer/src/ModelScriptingInterface.h +++ b/libraries/entities-renderer/src/ModelScriptingInterface.h @@ -72,7 +72,7 @@ class ModelScriptingInterface : public QObject { * @param {MeshProxy} mesh - The mesh to apply the transform to. * @returns {MeshProxy|boolean} The transformed mesh, if valid. false if an error. */ - Q_INVOKABLE ScriptValue transformMesh(glm::mat4 transform, MeshProxy* meshProxy); + Q_INVOKABLE ScriptValue transformMesh(glm::mat<4,4,float,glm::packed_highp> transform, MeshProxy* meshProxy); /*@jsdoc * Creates a new mesh. diff --git a/libraries/entities/src/EntityScriptingInterface.h b/libraries/entities/src/EntityScriptingInterface.h index a72e4822b78..a7a2b8b63af 100644 --- a/libraries/entities/src/EntityScriptingInterface.h +++ b/libraries/entities/src/EntityScriptingInterface.h @@ -1994,7 +1994,7 @@ public slots: * print("Rotation: " + JSON.stringify(Mat4.extractRotation(transform))); // Same as orientation. * print("Scale: " + JSON.stringify(Mat4.extractScale(transform))); // { x: 1, y: 1, z: 1 } */ - Q_INVOKABLE glm::mat4 getEntityTransform(const QUuid& entityID); + Q_INVOKABLE glm::mat<4,4,float,glm::packed_highp> getEntityTransform(const QUuid& entityID); /*@jsdoc * Gets the object to parent transform, excluding scale, of an entity. @@ -2029,7 +2029,7 @@ public slots: * print("Translation: " + JSON.stringify(Mat4.extractTranslation(transform))); // childTranslation * print("Rotation: " + JSON.stringify(Quat.safeEulerAngles(Mat4.extractRotation(transform)))); // childRotation * print("Scale: " + JSON.stringify(Mat4.extractScale(transform))); // { x: 1, y: 1, z: 1 } */ - Q_INVOKABLE glm::mat4 getEntityLocalTransform(const QUuid& entityID); + Q_INVOKABLE glm::mat<4,4,float,glm::packed_highp> getEntityLocalTransform(const QUuid& entityID); /*@jsdoc diff --git a/libraries/graphics-scripting/src/graphics-scripting/ScriptableMeshPart.h b/libraries/graphics-scripting/src/graphics-scripting/ScriptableMeshPart.h index 4990437f93e..3ff28c3d222 100644 --- a/libraries/graphics-scripting/src/graphics-scripting/ScriptableMeshPart.h +++ b/libraries/graphics-scripting/src/graphics-scripting/ScriptableMeshPart.h @@ -211,7 +211,7 @@ namespace scriptable { * @param {Mat4} transform - The scale, rotate, and translate transform to apply. * @returns {Graphics.MeshExtents} The resulting mesh extents, in model coordinates. */ - QVariantMap transform(const glm::mat4& transform); + QVariantMap transform(const glm::mat<4,4,float,glm::packed_highp>& transform); // @borrows jsdoc from GraphicsMesh. glm::uint32 addAttribute(const QString& attributeName, const QVariant& defaultValue = QVariant()); diff --git a/libraries/script-engine/src/Mat4.h b/libraries/script-engine/src/Mat4.h index 0208932613d..f94e50cb3aa 100644 --- a/libraries/script-engine/src/Mat4.h +++ b/libraries/script-engine/src/Mat4.h @@ -53,7 +53,7 @@ public slots: * @param {Mat4} m2 - The second matrix. * @returns {Mat4} m1 multiplied with m2. */ - glm::mat4 multiply(const glm::mat4& m1, const glm::mat4& m2) const; + glm::mat<4,4,float,glm::packed_highp> multiply(const glm::mat<4,4,float,glm::packed_highp>& m1, const glm::mat<4,4,float,glm::packed_highp>& m2) const; /*@jsdoc @@ -72,7 +72,7 @@ public slots: * // (0.739199, 0.280330, 0.612372, 0.000000), * // (10.000000, 11.000000, 12.000000, 1.000000)) */ - glm::mat4 createFromRotAndTrans(const glm::qua& rot, const glm::vec<3,float,glm::packed_highp>& trans) const; + glm::mat<4,4,float,glm::packed_highp> createFromRotAndTrans(const glm::qua& rot, const glm::vec<3,float,glm::packed_highp>& trans) const; /*@jsdoc * Creates a matrix that represents a scale, rotation, and translation. @@ -92,7 +92,7 @@ public slots: * // (1.478398, 0.560660, 1.224745, 0.000000), * // (10.000000, 11.000000, 12.000000, 1.000000)) */ - glm::mat4 createFromScaleRotAndTrans(const glm::vec<3,float,glm::packed_highp>& scale, const glm::qua& rot, const glm::vec<3,float,glm::packed_highp>& trans) const; + glm::mat<4,4,float,glm::packed_highp> createFromScaleRotAndTrans(const glm::vec<3,float,glm::packed_highp>& scale, const glm::qua& rot, const glm::vec<3,float,glm::packed_highp>& trans) const; /*@jsdoc * Creates a matrix from columns of values. @@ -114,7 +114,7 @@ public slots: * // (1.478398, 0.560660, 1.224745, 0.000000), * // (10.000000, 11.000000, 12.000000, 1.000000)) */ - glm::mat4 createFromColumns(const glm::vec4& col0, const glm::vec4& col1, const glm::vec4& col2, const glm::vec4& col3) const; + glm::mat<4,4,float,glm::packed_highp> createFromColumns(const glm::vec4& col0, const glm::vec4& col1, const glm::vec4& col2, const glm::vec4& col3) const; /*@jsdoc * Creates a matrix from an array of values. @@ -135,7 +135,7 @@ public slots: * // (1.478398, 0.560660, 1.224745, 0.000000), * // (10.000000, 11.000000, 12.000000, 1.000000)) */ - glm::mat4 createFromArray(const QVector& floats) const; + glm::mat<4,4,float,glm::packed_highp> createFromArray(const QVector& floats) const; /*@jsdoc @@ -153,7 +153,7 @@ public slots: * print("Translation: " + JSON.stringify(trans)); * // Translation: {"x":10,"y":11,"z":12} */ - glm::vec<3,float,glm::packed_highp> extractTranslation(const glm::mat4& m) const; + glm::vec<3,float,glm::packed_highp> extractTranslation(const glm::mat<4,4,float,glm::packed_highp>& m) const; /*@jsdoc * Extracts the rotation from a matrix. @@ -170,7 +170,7 @@ public slots: * print("Rotation: " + JSON.stringify(Quat.safeEulerAngles(rot))); * // Rotation: {"x":29.999998092651367,"y":45.00000762939453,"z":60.000003814697266} */ - glm::qua extractRotation(const glm::mat4& m) const; + glm::qua extractRotation(const glm::mat<4,4,float,glm::packed_highp>& m) const; /*@jsdoc * Extracts the scale from a matrix. @@ -187,7 +187,7 @@ public slots: * print("Scale: " + JSON.stringify(scale)); * // Scale: {"x":1.9999998807907104,"y":1.9999998807907104,"z":1.9999998807907104} */ - glm::vec<3,float,glm::packed_highp> extractScale(const glm::mat4& m) const; + glm::vec<3,float,glm::packed_highp> extractScale(const glm::mat<4,4,float,glm::packed_highp>& m) const; /*@jsdoc @@ -207,7 +207,7 @@ public slots: * print("Transformed point: " + JSON.stringify(transformedPoint)); * // Transformed point: { "x": 2.8284270763397217, "y": 12, "z": -2.384185791015625e-7 } */ - glm::vec<3,float,glm::packed_highp> transformPoint(const glm::mat4& m, const glm::vec<3,float,glm::packed_highp>& point) const; + glm::vec<3,float,glm::packed_highp> transformPoint(const glm::mat<4,4,float,glm::packed_highp>& m, const glm::vec<3,float,glm::packed_highp>& point) const; /*@jsdoc * Transforms a vector into a new coordinate system: the vector is scaled and rotated. @@ -226,7 +226,7 @@ public slots: * print("Transformed vector: " + JSON.stringify(transformedVector)); * // Transformed vector: { "x": 2.8284270763397217, "y": 2, "z": -2.384185791015625e-7 } */ - glm::vec<3,float,glm::packed_highp> transformVector(const glm::mat4& m, const glm::vec<3,float,glm::packed_highp>& vector) const; + glm::vec<3,float,glm::packed_highp> transformVector(const glm::mat<4,4,float,glm::packed_highp>& m, const glm::vec<3,float,glm::packed_highp>& vector) const; /*@jsdoc @@ -247,7 +247,7 @@ public slots: * // (0.000000, 0.000000, 1.000000, 0.000000), * // (0.000000, 0.000000, 0.000001, 1.000000)) */ - glm::mat4 inverse(const glm::mat4& m) const; + glm::mat<4,4,float,glm::packed_highp> inverse(const glm::mat<4,4,float,glm::packed_highp>& m) const; /*@jsdoc @@ -259,7 +259,7 @@ public slots: * @returns {Vec3} The negative z-axis rotated by orientation. */ // redundant, calls getForward which better describes the returned vector as a direction - glm::vec<3,float,glm::packed_highp> getFront(const glm::mat4& m) const { return getForward(m); } + glm::vec<3,float,glm::packed_highp> getFront(const glm::mat<4,4,float,glm::packed_highp>& m) const { return getForward(m); } /*@jsdoc * Gets the "forward" direction that the camera would face if its orientation was set to the rotation contained in a @@ -275,7 +275,7 @@ public slots: * print("Forward: " + JSON.stringify(forward)); * // Forward: {"x":0,"y":0,"z":-1} */ - glm::vec<3,float,glm::packed_highp> getForward(const glm::mat4& m) const; + glm::vec<3,float,glm::packed_highp> getForward(const glm::mat<4,4,float,glm::packed_highp>& m) const; /*@jsdoc * Gets the "right" direction that the camera would have if its orientation was set to the rotation contained in a matrix. @@ -284,7 +284,7 @@ public slots: * @param {Mat4} m - The matrix. * @returns {Vec3} The x-axis rotated by the rotation in the matrix. */ - glm::vec<3,float,glm::packed_highp> getRight(const glm::mat4& m) const; + glm::vec<3,float,glm::packed_highp> getRight(const glm::mat<4,4,float,glm::packed_highp>& m) const; /*@jsdoc * Gets the "up" direction that the camera would have if its orientation was set to the rotation contained in a matrix. The @@ -293,7 +293,7 @@ public slots: * @param {Mat4} m - The matrix. * @returns {Vec3} The y-axis rotated by the rotation in the matrix. */ - glm::vec<3,float,glm::packed_highp> getUp(const glm::mat4& m) const; + glm::vec<3,float,glm::packed_highp> getUp(const glm::mat<4,4,float,glm::packed_highp>& m) const; /*@jsdoc @@ -321,7 +321,7 @@ public slots: * // "r0c2": 1.4783978462219238, "r1c2": 0.5606603026390076, "r2c2": 1.2247447967529297, "r3c2": 0, * // "r0c3": 10, "r1c3": 11, "r2c3": 12, "r3c3": 1} */ - void print(const QString& label, const glm::mat4& m, bool transpose = false) const; + void print(const QString& label, const glm::mat<4,4,float,glm::packed_highp>& m, bool transpose = false) const; }; #endif // hifi_Mat4_h diff --git a/libraries/script-engine/src/ScriptValueUtils.h b/libraries/script-engine/src/ScriptValueUtils.h index 026f819d35a..fd0528c9e1c 100644 --- a/libraries/script-engine/src/ScriptValueUtils.h +++ b/libraries/script-engine/src/ScriptValueUtils.h @@ -53,8 +53,8 @@ void registerMetaTypes(ScriptEngine* engine); * @property {number} r2c3 - Row 2, column 3 value. * @property {number} r3c3 - Row 3, column 3 value. */ -ScriptValue mat4toScriptValue(ScriptEngine* engine, const glm::mat4& mat4); -bool mat4FromScriptValue(const ScriptValue& object, glm::mat4& mat4); +ScriptValue mat4toScriptValue(ScriptEngine* engine, const glm::mat<4,4,float,glm::packed_highp>& mat4); +bool mat4FromScriptValue(const ScriptValue& object, glm::mat<4,4,float,glm::packed_highp>& mat4); /*@jsdoc * A 2-dimensional vector. From ba58050ff176f2bb1494f2a6951b1f61084e0eb2 Mon Sep 17 00:00:00 2001 From: Karol Suprynowicz Date: Thu, 2 Oct 2025 23:27:13 +0200 Subject: [PATCH 022/111] Qt6 QML fixes --- interface/resources/qml/Web3DSurface.qml | 2 +- .../qml/controls/+webengine/FlickableWebViewCore.qml | 12 ++++++------ interface/resources/serverless/Scripts/Wizard.qml | 2 +- libraries/networking/src/ResourceCache.cpp | 2 +- libraries/ui/src/VrMenu.cpp | 2 -- 5 files changed, 9 insertions(+), 11 deletions(-) diff --git a/interface/resources/qml/Web3DSurface.qml b/interface/resources/qml/Web3DSurface.qml index 37464cfecee..fb845debfda 100644 --- a/interface/resources/qml/Web3DSurface.qml +++ b/interface/resources/qml/Web3DSurface.qml @@ -83,7 +83,7 @@ Item { if (root.webViewLoaded) { loader.item.url = "about:blank" } - loader.setSource(undefined); + loader.setSource(""); } if (url.match(/\.qml$/)) { diff --git a/interface/resources/qml/controls/+webengine/FlickableWebViewCore.qml b/interface/resources/qml/controls/+webengine/FlickableWebViewCore.qml index b8c9cc03ef6..6ac116928df 100644 --- a/interface/resources/qml/controls/+webengine/FlickableWebViewCore.qml +++ b/interface/resources/qml/controls/+webengine/FlickableWebViewCore.qml @@ -24,7 +24,7 @@ Item { property string urlTag: "noDownload=false"; signal newViewRequestedCallback(var request) - signal loadingChangedCallback(var loadRequest) + signal loadingChangedCallback(var loadingInfo) width: parent.width @@ -206,15 +206,15 @@ 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 + 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/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/libraries/networking/src/ResourceCache.cpp b/libraries/networking/src/ResourceCache.cpp index 9903548609d..4d9f81131f6 100644 --- a/libraries/networking/src/ResourceCache.cpp +++ b/libraries/networking/src/ResourceCache.cpp @@ -222,7 +222,7 @@ ScriptableResource* ResourceCache::prefetch(const QUrl& url, void* extra, size_t // Must be called in thread to ensure getResource returns a valid pointer BLOCKING_INVOKE_METHOD(this, "prefetch", Q_GENERIC_RETURN_ARG(ScriptableResource*, result), - Q_GENERIC_ARG(QUrl, url), Q_GENERIC_ARG(void*, extra), Q_GENERIC_ARG(size_t, extraHash)); + Q_GENERIC_ARG(QUrl, url), Q_GENERIC_ARG(void*, extra), Q_GENERIC_ARG(ulong, extraHash)); return result; } diff --git a/libraries/ui/src/VrMenu.cpp b/libraries/ui/src/VrMenu.cpp index 56c6659d000..bbbf9f01bb3 100644 --- a/libraries/ui/src/VrMenu.cpp +++ b/libraries/ui/src/VrMenu.cpp @@ -219,8 +219,6 @@ void VrMenu::addAction(QMenu* menu, QAction* action) { } QObject* menuQml = findMenuObject(userData->uuid.toString()); Q_ASSERT(menuQml); - qDebug() << "VrMenu::addAction menuQml " << menuQml->objectName(); - qDebug() << "VrMenu::addAction menuQml type " << menuQml->metaObject()->className(); QQmlComponent menuItemComponent(engine); menuItemComponent.loadFromModule("QtQuick.Controls", "MenuItem"); From cd36d604de6fba33dbd79511719608f56728af03 Mon Sep 17 00:00:00 2001 From: Karol Suprynowicz Date: Fri, 3 Oct 2025 15:30:47 +0200 Subject: [PATCH 023/111] Re-enable desktop on Qt6 --- interface/resources/qml/hifi/Desktop.qml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/interface/resources/qml/hifi/Desktop.qml b/interface/resources/qml/hifi/Desktop.qml index 13d58b8c879..6a5ab5936e8 100644 --- a/interface/resources/qml/hifi/Desktop.qml +++ b/interface/resources/qml/hifi/Desktop.qml @@ -12,8 +12,8 @@ import controlsUit 1.0 OriginalDesktop.Desktop { id: desktop - // Qt6 TODO: Nothing renders yet, and the desktop just eats all mouse inputs - visible: false + // QT6TODO: Desktop just eats all mouse inputs + visible: true property alias toolbarObjectName: sysToolbar.objectName From 30b38f93adff97ae3036b13489887175fc92bf70 Mon Sep 17 00:00:00 2001 From: Karol Suprynowicz Date: Sun, 5 Oct 2025 16:59:13 +0200 Subject: [PATCH 024/111] Fix webengine UI crash --- libraries/ui/src/ui/OffscreenQmlSurface.cpp | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/libraries/ui/src/ui/OffscreenQmlSurface.cpp b/libraries/ui/src/ui/OffscreenQmlSurface.cpp index ea25c92a152..732c0dcbccc 100644 --- a/libraries/ui/src/ui/OffscreenQmlSurface.cpp +++ b/libraries/ui/src/ui/OffscreenQmlSurface.cpp @@ -511,7 +511,7 @@ bool OffscreenQmlSurface::handlePointerEvent(const PointerEvent& event, class QP // - this was a release event and we aren't still hovering auto touchPoint = _activeTouchPoints.find(event.getID()); bool removeTouchPoint = - release || (touchPoint != _activeTouchPoints.end() && !touchPoint->second.hovering && state == Qt::TouchPointReleased); + release || (touchPoint != _activeTouchPoints.end() && !touchPoint->second.hovering && state == QEventPoint::State::Released); QEvent::Type touchType = QEvent::TouchUpdate; if (_activeTouchPoints.empty()) { // If the first active touch point is being created, send a begin @@ -529,9 +529,9 @@ bool OffscreenQmlSurface::handlePointerEvent(const PointerEvent& event, class QP //point.setPos(windowPoint); //point.setScreenPos(windowPoint); _activeTouchPoints[event.getID()].touchPoint = point; - if (state == Qt::TouchPointPressed) { + if (state == QEventPoint::State::Pressed) { _activeTouchPoints[event.getID()].pressed = true; - } else if (state == Qt::TouchPointReleased) { + } else if (state == QEventPoint::State::Released) { _activeTouchPoints[event.getID()].pressed = false; } } @@ -616,8 +616,10 @@ void OffscreenQmlSurface::focusDestroyed(QObject* obj) { void OffscreenQmlSurface::onFocusObjectChanged(QObject* object) { clearFocusItem(); - QQuickItem* item = static_cast(object); + QQuickItem* item = qobject_cast(object); if (!item) { + // QT6TODO: investigate when this happens and if it's a problem or not + qDebug() << "OffscreenQmlSurface::onFocusObjectChanged object is not QQuickItem"; setFocusText(false); return; } @@ -730,7 +732,7 @@ void OffscreenQmlSurface::setKeyboardRaised(QObject* object, bool raised, bool n if (!android || hmd) { // if HMD is being worn, allow keyboard to open. allow it to close, HMD or not. if (!raised || hmd) { - QQuickItem* item = dynamic_cast(object); + QQuickItem* item = qobject_cast(object); if (!item) { return; } From 2f2bd6111522c4a35dc574d1556c1cf0e820850c Mon Sep 17 00:00:00 2001 From: Karol Suprynowicz Date: Sun, 5 Oct 2025 22:43:16 +0200 Subject: [PATCH 025/111] QML fixes for Qt 6 --- .../+webengine/FlickableWebViewCore.qml | 3 + interface/resources/qml/controlsUit/Table.qml | 45 ++++++++----- .../dialogs/preferences/AvatarPreference.qml | 6 +- interface/resources/qml/hifi/AssetServer.qml | 4 +- interface/resources/qml/hifi/AvatarApp.qml | 8 +-- interface/resources/qml/hifi/NameCard.qml | 13 ++-- interface/resources/qml/hifi/Pal.qml | 64 +++++++++++-------- .../hifi/avatarapp/CreateFavoriteDialog.qml | 2 +- .../qml/hifi/avatarapp/MessageBox.qml | 3 +- .../qml/hifi/avatarapp/MessageBoxes.qml | 2 +- .../qml/hifi/avatarapp/TransparencyMask.qml | 7 +- .../qml/hifi/dialogs/TabletAssetServer.qml | 4 +- .../tablet/tabletWindows/TabletFileDialog.qml | 28 ++++---- .../tabletWindows/preferences/Section.qml | 2 +- interface/resources/qml/windows/Window.qml | 4 +- scripts/system/settings/qml/SettingNumber.qml | 4 +- .../settings/qml/pages/GraphicsSettings.qml | 26 ++++---- .../animedit/qml/fields/NumberArrayField.qml | 2 +- 18 files changed, 133 insertions(+), 94 deletions(-) diff --git a/interface/resources/qml/controls/+webengine/FlickableWebViewCore.qml b/interface/resources/qml/controls/+webengine/FlickableWebViewCore.qml index 6ac116928df..00cb44e48fa 100644 --- a/interface/resources/qml/controls/+webengine/FlickableWebViewCore.qml +++ b/interface/resources/qml/controls/+webengine/FlickableWebViewCore.qml @@ -207,6 +207,9 @@ Item { property bool safeLoading: false property bool loadingLatched: false 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; diff --git a/interface/resources/qml/controlsUit/Table.qml b/interface/resources/qml/controlsUit/Table.qml index 580d79cdb52..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 2.3 +import QtQuick +import QtQuick.Controls //import QtQuick.Controls.Styles -import QtQuick.Controls 2.3 as QQC2 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/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/hifi/AssetServer.qml b/interface/resources/qml/hifi/AssetServer.qml index 98de845fc6d..b1ca6493b98 100644 --- a/interface/resources/qml/hifi/AssetServer.qml +++ b/interface/resources/qml/hifi/AssetServer.qml @@ -695,8 +695,8 @@ Windows.ScrollingWindow { 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 160b5bd211a..582d1a468e9 100644 --- a/interface/resources/qml/hifi/AvatarApp.qml +++ b/interface/resources/qml/hifi/AvatarApp.qml @@ -1,7 +1,7 @@ -import QtQuick 2.6 -import QtQuick.Controls 2.2 -import QtQuick.Layouts 1.3 -import QtQml.Models 2.1 +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import QtQml.Models import Qt5Compat.GraphicalEffects import controlsUit 1.0 as HifiControls import stylesUit 1.0 diff --git a/interface/resources/qml/hifi/NameCard.qml b/interface/resources/qml/hifi/NameCard.qml index 6e7b3097ae9..e7f0100325f 100644 --- a/interface/resources/qml/hifi/NameCard.qml +++ b/interface/resources/qml/hifi/NameCard.qml @@ -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 6af28e7bbcc..39cbaacceb8 100644 --- a/interface/resources/qml/hifi/Pal.qml +++ b/interface/resources/qml/hifi/Pal.qml @@ -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/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 68df6d68f5b..5faaf91b5e1 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/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/TabletAssetServer.qml b/interface/resources/qml/hifi/dialogs/TabletAssetServer.qml index 474c4ffb903..c9e2572a24a 100644 --- a/interface/resources/qml/hifi/dialogs/TabletAssetServer.qml +++ b/interface/resources/qml/hifi/dialogs/TabletAssetServer.qml @@ -693,8 +693,8 @@ Rectangle { padding.right: readOnly ? 0 : hifi.dimensions.textPadding }*/ - validator: RegExpValidator { - regExp: /[^/]+/ + validator: RegularExpressionValidator { + regularExpression: /[^/]+/ } Keys.onPressed: { diff --git a/interface/resources/qml/hifi/tablet/tabletWindows/TabletFileDialog.qml b/interface/resources/qml/hifi/tablet/tabletWindows/TabletFileDialog.qml index 025b245786c..796a93fa39d 100644 --- a/interface/resources/qml/hifi/tablet/tabletWindows/TabletFileDialog.qml +++ b/interface/resources/qml/hifi/tablet/tabletWindows/TabletFileDialog.qml @@ -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/windows/Window.qml b/interface/resources/qml/windows/Window.qml index 3aef97e9b12..9ed245ae4c5 100644 --- a/interface/resources/qml/windows/Window.qml +++ b/interface/resources/qml/windows/Window.qml @@ -127,7 +127,7 @@ Fadable { propagateComposedEvents: true acceptedButtons: Qt.AllButtons enabled: window.visible - onPressed: { + function onPressed(mouse) { window.raise(); mouse.accepted = false; } @@ -142,7 +142,7 @@ Fadable { propagateComposedEvents: true acceptedButtons: Qt.AllButtons enabled: window.visible - onPressed: { + function onPressed(mouse) { frame.forceActiveFocus(); mouse.accepted = false; } diff --git a/scripts/system/settings/qml/SettingNumber.qml b/scripts/system/settings/qml/SettingNumber.qml index e1a05b021b1..e5faf2b8944 100644 --- a/scripts/system/settings/qml/SettingNumber.qml +++ b/scripts/system/settings/qml/SettingNumber.qml @@ -1,4 +1,4 @@ -import QtQuick 2.7 +import QtQuick import QtQuick.Controls 2.5 //import QtQuick.Controls.Styles import QtQuick.Layouts 1.3 @@ -67,7 +67,7 @@ Item { width: parent.width; clip: true; font.pixelSize: 22 - validator: RegExpValidator { regExp: /[0-9]*/ } + validator: RegularExpressionValidator { regularExpression: /[0-9]*/ } background: Rectangle { color: "#111"; diff --git a/scripts/system/settings/qml/pages/GraphicsSettings.qml b/scripts/system/settings/qml/pages/GraphicsSettings.qml index 95f5150813c..da623c2b9ff 100644 --- a/scripts/system/settings/qml/pages/GraphicsSettings.qml +++ b/scripts/system/settings/qml/pages/GraphicsSettings.qml @@ -1,4 +1,4 @@ -import QtQuick 2.15 +import QtQuick import QtQuick.Controls 2.15 import QtQuick.Layouts 1.3 import "../" @@ -74,7 +74,7 @@ Flickable { optionIndex: Performance.getPerformancePreset() - 1; options: ["Low Power", "Low", "Medium", "High", "Custom"]; - onValueChanged: { + function onValueChanged(index) { Performance.setPerformancePreset(index + 1); if (index !== 4) switchToAGraphicsPreset(); } @@ -163,7 +163,7 @@ Flickable { options: ["Economical", "Interactive", "Real-Time", "Custom"]; optionIndex: Performance.getRefreshRateProfile(); - onValueChanged: { + function onValueChanged(index) { Performance.setRefreshRateProfile(index); fpsAdvancedOptions.isEnabled = index == 3; } @@ -181,7 +181,7 @@ Flickable { suffixText: "fps"; settingValue: Performance.getCustomRefreshRate(0) - onValueChanged: { + function onValueChanged(value) { Performance.setCustomRefreshRate(0, value); } } @@ -193,7 +193,7 @@ Flickable { suffixText: "fps"; settingValue: Performance.getCustomRefreshRate(1) - onValueChanged: { + function onValueChanged(value) { Performance.setCustomRefreshRate(1, value); } } @@ -205,7 +205,7 @@ Flickable { suffixText: "fps"; settingValue: Performance.getCustomRefreshRate(2) - onValueChanged: { + function onValueChanged(value) { Performance.setCustomRefreshRate(2, value); } } @@ -217,7 +217,7 @@ Flickable { suffixText: "fps"; settingValue: Performance.getCustomRefreshRate(3) - onValueChanged: { + function onValueChanged(value) { Performance.setCustomRefreshRate(3, value); } } @@ -229,7 +229,7 @@ Flickable { suffixText: "fps"; settingValue: Performance.getCustomRefreshRate(4) - onValueChanged: { + function onValueChanged(value) { Performance.setCustomRefreshRate(4, value); } } @@ -241,7 +241,7 @@ Flickable { suffixText: "fps"; settingValue: Performance.getCustomRefreshRate(5) - onValueChanged: { + function onValueChanged(value) { Performance.setCustomRefreshRate(5, value); } } @@ -255,7 +255,7 @@ Flickable { maxValue: 2; settingValue: Render.viewportResolutionScale.toFixed(1) - onSliderValueChanged: { + function onSliderValueChanged(value) { Render.viewportResolutionScale = value.toFixed(1) } } @@ -265,7 +265,7 @@ Flickable { options: ["Low Detail", "Medium Detail", "High Detail" ]; optionIndex: LODManager.worldDetailQuality; - onValueChanged: { + function onValueChanged(index) { LODManager.worldDetailQuality = index; } @@ -305,7 +305,7 @@ Flickable { settingValue: Render.verticalFieldOfView.toFixed(1); roundDisplay: 0; - onSliderValueChanged: { + function onSliderValueChanged(value) { Render.verticalFieldOfView = value.toFixed(1); } } @@ -327,7 +327,7 @@ Flickable { options: ["None", "TAA", "FXAA"]; disabled: Render.renderMethod; - onValueChanged: { + function onValueChanged(index) { Render.antialiasingMode = index; } diff --git a/tools/animedit/qml/fields/NumberArrayField.qml b/tools/animedit/qml/fields/NumberArrayField.qml index dd6661d137a..4ac6f776ed6 100644 --- a/tools/animedit/qml/fields/NumberArrayField.qml +++ b/tools/animedit/qml/fields/NumberArrayField.qml @@ -43,7 +43,7 @@ Row { // then substitue the second into the \d+ of the first, yeilding this monstrocity. // ^\[\s*(\d+\.?\d*)(\s*,\s*(\d+\.?\d*))*\]$|\[\s*\] - //validator: RegExpValidator { regExp: /^\[\s*(\d+\.?\d*)(\s*,\s*(\d+\.?\d*))*\]$|\[\s*\]/ } + //validator: RegularExpressionValidator { regularExpression: /^\[\s*(\d+\.?\d*)(\s*,\s*(\d+\.?\d*))*\]$|\[\s*\]/ } text: JSON.stringify(value) onEditingFinished: { From 85c0dfcf57711775182d407b167b3f9f3e0a0196 Mon Sep 17 00:00:00 2001 From: Karol Suprynowicz Date: Sun, 5 Oct 2025 23:15:25 +0200 Subject: [PATCH 026/111] Workaround for broken window inputs --- interface/resources/qml/windows/Window.qml | 8 ++++++-- scripts/system/settings/qml/SettingNumber.qml | 2 +- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/interface/resources/qml/windows/Window.qml b/interface/resources/qml/windows/Window.qml index 9ed245ae4c5..7ae5fcf70a5 100644 --- a/interface/resources/qml/windows/Window.qml +++ b/interface/resources/qml/windows/Window.qml @@ -127,7 +127,9 @@ Fadable { propagateComposedEvents: true acceptedButtons: Qt.AllButtons enabled: window.visible - function onPressed(mouse) { + // QT6TODO: old style is deprecated but changing it to new one breaks window inputs somehow? + //function onPressed(mouse) { + onPressed: { window.raise(); mouse.accepted = false; } @@ -142,7 +144,9 @@ Fadable { propagateComposedEvents: true acceptedButtons: Qt.AllButtons enabled: window.visible - function onPressed(mouse) { + // QT6TODO: old style is deprecated but changing it to new one breaks window inputs somehow? + //function onPressed(mouse) { + onPressed: { frame.forceActiveFocus(); mouse.accepted = false; } diff --git a/scripts/system/settings/qml/SettingNumber.qml b/scripts/system/settings/qml/SettingNumber.qml index e5faf2b8944..e25a8897e1a 100644 --- a/scripts/system/settings/qml/SettingNumber.qml +++ b/scripts/system/settings/qml/SettingNumber.qml @@ -1,5 +1,5 @@ import QtQuick -import QtQuick.Controls 2.5 +import QtQuick.Controls //import QtQuick.Controls.Styles import QtQuick.Layouts 1.3 From 152ae537b214bf010244f84654a5925b0654d43f Mon Sep 17 00:00:00 2001 From: Karol Suprynowicz Date: Tue, 7 Oct 2025 00:03:16 +0200 Subject: [PATCH 027/111] Re-enable FileDialog on Qt6 --- interface/resources/qml/desktop/Desktop.qml | 2 +- interface/resources/qml/dialogs/FileDialog.qml | 15 +++++++++------ scripts/developer/utilities/render/transition.qml | 6 +++--- 3 files changed, 13 insertions(+), 10 deletions(-) diff --git a/interface/resources/qml/desktop/Desktop.qml b/interface/resources/qml/desktop/Desktop.qml index d608ce8c079..c2d1823e6ac 100644 --- a/interface/resources/qml/desktop/Desktop.qml +++ b/interface/resources/qml/desktop/Desktop.qml @@ -548,7 +548,7 @@ FocusScope { return customInputDialogBuilder.createObject(desktop, properties); } - Component { id: fileDialogBuilder; Item {}}//FileDialog { } } + Component { id: fileDialogBuilder; FileDialog { } } function fileDialog(properties) { return fileDialogBuilder.createObject(desktop, properties); } diff --git a/interface/resources/qml/dialogs/FileDialog.qml b/interface/resources/qml/dialogs/FileDialog.qml index cee5c924e9a..46ceca60ce3 100644 --- a/interface/resources/qml/dialogs/FileDialog.qml +++ b/interface/resources/qml/dialogs/FileDialog.qml @@ -8,11 +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 +import Qt.labs.folderlistmodel +import Qt.labs.settings +import Qt.labs.qmlmodels import QtQuick.Dialogs as OriginalDialogs -import QtQuick.Controls 2.3 +import QtQuick.Controls import ".." import controlsUit 1.0 @@ -497,7 +498,8 @@ ModalWindow { TableView { id: fileTableView - colorScheme: hifi.colorSchemes.light + // QT6TODO: + //colorScheme: hifi.colorSchemes.light anchors { top: navControls.bottom topMargin: hifi.dimensions.contentSpacing.y @@ -506,7 +508,8 @@ ModalWindow { bottom: currentSelection.top bottomMargin: hifi.dimensions.contentSpacing.y + currentSelection.controlHeight - currentSelection.height } - headerVisible: !selectDirectory + // QT6TODO: + //headerVisible: !selectDirectory //onDoubleClicked: navigateToRow(row); Keys.onReturnPressed: navigateToCurrentRow(); Keys.onEnterPressed: navigateToCurrentRow(); diff --git a/scripts/developer/utilities/render/transition.qml b/scripts/developer/utilities/render/transition.qml index 52b839dd3b0..ffb233c046c 100644 --- a/scripts/developer/utilities/render/transition.qml +++ b/scripts/developer/utilities/render/transition.qml @@ -8,9 +8,9 @@ // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or https://www.apache.org/licenses/LICENSE-2.0.html // -import QtQuick 2.7 -import QtQuick.Controls 2.3 -import QtQuick.Layouts 1.3 +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts import QtQuick.Dialogs import stylesUit 1.0 From ad2bb92ffb72f8776a9054c01f71564b56291a48 Mon Sep 17 00:00:00 2001 From: Karol Suprynowicz Date: Wed, 8 Oct 2025 00:45:53 +0200 Subject: [PATCH 028/111] Make file dialog show files on Qt6 --- .../resources/qml/dialogs/FileDialog.qml | 59 +++++++++++++++---- 1 file changed, 47 insertions(+), 12 deletions(-) diff --git a/interface/resources/qml/dialogs/FileDialog.qml b/interface/resources/qml/dialogs/FileDialog.qml index 46ceca60ce3..ffa5a60a88b 100644 --- a/interface/resources/qml/dialogs/FileDialog.qml +++ b/interface/resources/qml/dialogs/FileDialog.qml @@ -34,7 +34,7 @@ ModalWindow { HifiConstants { id: hifi } - property var filesModel: ListModel { } + property var filesModel: TableModel { } Settings { category: "FileDialog" @@ -72,12 +72,14 @@ ModalWindow { signal canceled(); signal selected(int button); function click(button) { + // QT6TODO clickedButton = button; selected(button); destroy(); } - - property int clickedButton: OriginalDialogs.StandardButton.NoButton; + + // QT6TODO + //property int clickedButton: OriginalDialogs.StandardButton.NoButton; Component.onCompleted: { fileDialogItem.keyboardEnabled = HMD.active; @@ -311,8 +313,9 @@ ModalWindow { } function clearSelection() { - fileTableView.selection.clear(); - fileTableView.currentRow = -1; + // QT6TODO + //fileTableView.selection.clear(); + //fileTableView.currentRow = -1; update(); } } @@ -374,7 +377,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 +450,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 +494,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 +503,7 @@ ModalWindow { } } - filesModel.insert(lower, { + filesModel.insertRow(lower, { fileName: fileName, fileModified: (fileIsDir ? new Date(0) : model.getItem(i, "fileModified")), fileSize: model.getItem(i, "fileSize"), @@ -518,6 +539,7 @@ ModalWindow { property var sortIndicatorOrder: Qt.AscendingOrder property bool sortIndicatorVisible: true + // QT6TODO model: filesModel function updateSort() { @@ -530,7 +552,20 @@ ModalWindow { onSortIndicatorOrderChanged: { updateSort(); } - delegate: 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 { @@ -573,7 +608,7 @@ ModalWindow { return size + " " + suffixes[suffixIndex]; } } - } + }*/ /*model: TableModel { TableModelColumn { From b5cd64652182f0f4982666d5b590454667859d2f Mon Sep 17 00:00:00 2001 From: Karol Suprynowicz Date: Mon, 13 Oct 2025 16:22:35 +0200 Subject: [PATCH 029/111] Fix Sampler.cpp for Qt6 --- libraries/shared/src/Sampler.cpp | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/libraries/shared/src/Sampler.cpp b/libraries/shared/src/Sampler.cpp index f106a218731..4e0a45237aa 100644 --- a/libraries/shared/src/Sampler.cpp +++ b/libraries/shared/src/Sampler.cpp @@ -126,45 +126,45 @@ QDebug& operator<<(QDebug& dbg, const Sampler& s) { QString result = "[ "; result += "borderColor: ("; - result += s.getBorderColor().r; + result += QString::number(s.getBorderColor().r); result += ", "; - result += s.getBorderColor().g; + result += QString::number(s.getBorderColor().g); result += ", "; - result += s.getBorderColor().b; + result += QString::number(s.getBorderColor().b); result += ", "; - result += s.getBorderColor().a; + result += QString::number(s.getBorderColor().a); result += "), "; result += "maxAnistropy: "; - result += s.getMaxAnisotropy(); + result += QString::number(s.getMaxAnisotropy()); result += ", "; result += "filter: "; - result += s.getFilter(); + result += QString::number(s.getFilter()); result += ", "; result += "comparisonFunction: "; - result += (uint8_t)s.getComparisonFunction(); + result += QString::number((uint8_t)s.getComparisonFunction()); result += ", "; result += "wrap: ("; - result += s.getWrapModeU(); + result += QString::number(s.getWrapModeU()); result += ", "; - result += s.getWrapModeV(); + result += QString::number(s.getWrapModeV()); result += ", "; - result += s.getWrapModeW(); + result += QString::number(s.getWrapModeW()); result += "), "; result += "mipOffset: "; - result += s.getMipOffset(); + result += QString::number(s.getMipOffset()); result += ", "; result += "minMip: "; - result += s.getMinMip(); + result += QString::number(s.getMinMip()); result += ", "; result += "maxMip: "; - result += s.getMaxMip(); + result += QString::number(s.getMaxMip()); result += ", "; result += "]"; From 9deb8b04d279192eac566e8bf16f99170df8f17e Mon Sep 17 00:00:00 2001 From: Karol Suprynowicz Date: Tue, 14 Oct 2025 22:36:23 +0200 Subject: [PATCH 030/111] Memory safety fixes --- conanfile.py | 8 +- interface/resources/qml/controlsUit/Tree.qml | 8 +- interface/resources/qml/hifi/AssetServer.qml | 6 +- interface/src/Application.h | 5 +- interface/src/Application_Entities.cpp | 39 ++- interface/src/Application_Setup.cpp | 22 +- .../src/octree/OctreePacketProcessor.cpp | 1 + interface/src/ui/DialogsManager.cpp | 7 +- interface/src/ui/OctreeStatsDialog.cpp | 274 +++++++++--------- interface/src/ui/OctreeStatsProvider.cpp | 190 ++++++------ interface/src/ui/Stats.cpp | 60 ++-- libraries/gpu/src/gpu/Texture.h | 2 +- libraries/graphics/src/graphics/Material.cpp | 35 ++- .../src/model-networking/ModelCache.h | 2 +- libraries/networking/src/LimitedNodeList.cpp | 6 + libraries/networking/src/LimitedNodeList.h | 2 +- libraries/octree/src/OctreePacketData.cpp | 16 +- .../procedural/ProceduralMaterialCache.cpp | 3 +- .../src/procedural/ProceduralMaterialCache.h | 1 + .../src/procedural/ReferenceMaterial.h | 2 +- .../render-utils/src/RenderPipelines.cpp | 144 +++++---- .../render-utils/src/RenderShadowTask.cpp | 2 +- libraries/render-utils/src/text/Font.cpp | 7 + libraries/task/src/task/Varying.h | 5 +- 24 files changed, 480 insertions(+), 367 deletions(-) diff --git a/conanfile.py b/conanfile.py index e183d0cab09..77c0a1a96fc 100644 --- a/conanfile.py +++ b/conanfile.py @@ -138,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", @@ -148,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/interface/resources/qml/controlsUit/Tree.qml b/interface/resources/qml/controlsUit/Tree.qml index 99b6fcb9319..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 2.3 +import QtQml.Models +import QtQuick +import QtQuick.Controls //import QtQuick.Controls.Styles -import QtQuick.Controls 2.2 as QQC2 +import QtQuick.Controls as QQC2 import "../stylesUit" diff --git a/interface/resources/qml/hifi/AssetServer.qml b/interface/resources/qml/hifi/AssetServer.qml index b1ca6493b98..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 2.3 +import QtQuick +import QtQuick.Controls //import QtQuick.Controls.Styles import QtQuick.Dialogs as OriginalDialogs -import Qt.labs.settings 1.0 +import Qt.labs.settings import stylesUit 1.0 import controlsUit 1.0 as HifiControls diff --git a/interface/src/Application.h b/interface/src/Application.h index aa7a52bd626..84125d27a20 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -94,6 +94,8 @@ class Application : public QApplication, ); ~Application(); + bool setupEssentials(const QCommandLineParser& parser, bool runningMarkerExisted); + /** * @brief Initialize everything * @@ -326,7 +328,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 diff --git a/interface/src/Application_Entities.cpp b/interface/src/Application_Entities.cpp index fec3dd5d99a..848906cbc43 100644 --- a/interface/src/Application_Entities.cpp +++ b/interface/src/Application_Entities.cpp @@ -36,6 +36,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); } @@ -231,9 +240,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(); @@ -331,16 +342,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_Setup.cpp b/interface/src/Application_Setup.cpp index f563c310aec..adeaab5d421 100644 --- a/interface/src/Application_Setup.cpp +++ b/interface/src/Application_Setup.cpp @@ -250,7 +250,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"); @@ -329,6 +329,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. @@ -414,7 +416,14 @@ bool setupEssentials(const QCommandLineParser& parser, bool runningMarkerExisted DependencyManager::set(); - DependencyManager::set(nullptr, qApp->getOcteeSceneStats()); + { + 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(); @@ -459,7 +468,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(); @@ -1198,10 +1206,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(); diff --git a/interface/src/octree/OctreePacketProcessor.cpp b/interface/src/octree/OctreePacketProcessor.cpp index b0f21e2574c..9cb68512dd7 100644 --- a/interface/src/octree/OctreePacketProcessor.cpp +++ b/interface/src/octree/OctreePacketProcessor.cpp @@ -26,6 +26,7 @@ OctreePacketProcessor::OctreePacketProcessor(): { setObjectName("Octree Packet Processor"); + Q_ASSERT(DependencyManager::get()); auto& packetReceiver = DependencyManager::get()->getPacketReceiver(); const PacketReceiver::PacketTypeList octreePackets = { PacketType::OctreeStats, PacketType::EntityData, PacketType::EntityErase, PacketType::EntityQueryInitialResultsComplete }; diff --git a/interface/src/ui/DialogsManager.cpp b/interface/src/ui/DialogsManager.cpp index 46ca7bbe039..6827192c5eb 100644 --- a/interface/src/ui/DialogsManager.cpp +++ b/interface/src/ui/DialogsManager.cpp @@ -163,7 +163,12 @@ void DialogsManager::showUpdateDialog() { void DialogsManager::octreeStatsDetails() { if (!_octreeStatsDialog) { - _octreeStatsDialog = new OctreeStatsDialog(qApp->getWindow(), qApp->getOcteeSceneStats()); + auto octreeSceneStats = qApp->getOcteeSceneStats(); + if (!octreeSceneStats) { + qCritical() << "DialogsManager::octreeStatsDetails(): Octree scene stats object not available yet. This should never happen."; + std::abort(); + } + _octreeStatsDialog = new OctreeStatsDialog(qApp->getWindow(), octreeSceneStats.value()); if (_hmdToolsDialog) { _hmdToolsDialog->watchWindow(_octreeStatsDialog->windowHandle()); diff --git a/interface/src/ui/OctreeStatsDialog.cpp b/interface/src/ui/OctreeStatsDialog.cpp index f68f5c8d927..24ec20f2caa 100644 --- a/interface/src/ui/OctreeStatsDialog.cpp +++ b/interface/src/ui/OctreeStatsDialog.cpp @@ -188,35 +188,37 @@ void OctreeStatsDialog::paintEvent(QPaintEvent* event) { unsigned long totalInternal = 0; unsigned long totalLeaves = 0; - NodeToOctreeSceneStats* sceneStats = qApp->getOcteeSceneStats(); - sceneStats->withReadLock([&] { - for (NodeToOctreeSceneStatsIterator i = sceneStats->begin(); i != sceneStats->end(); i++) { - //const QUuid& uuid = i->first; - OctreeSceneStats& stats = i->second; - serverCount++; - - // calculate server node totals - totalNodes += stats.getTotalElements(); - totalInternal += stats.getTotalInternal(); - totalLeaves += stats.getTotalLeaves(); - - // Sending mode - if (serverCount > 1) { - sendingMode << ","; - } - if (stats.isMoving()) { - sendingMode << "M"; - movingServerCount++; - } else { - sendingMode << "S"; - } - if (stats.isFullScene()) { - sendingMode << "F"; - } else { - sendingMode << "p"; + auto sceneStats = qApp->getOcteeSceneStats(); + if (sceneStats) { + sceneStats.value()->withReadLock([&] { + for (NodeToOctreeSceneStatsIterator i = sceneStats.value()->begin(); i != sceneStats.value()->end(); i++) { + //const QUuid& uuid = i->first; + OctreeSceneStats& stats = i->second; + serverCount++; + + // calculate server node totals + totalNodes += stats.getTotalElements(); + totalInternal += stats.getTotalInternal(); + totalLeaves += stats.getTotalLeaves(); + + // Sending mode + if (serverCount > 1) { + sendingMode << ","; + } + if (stats.isMoving()) { + sendingMode << "M"; + movingServerCount++; + } else { + sendingMode << "S"; + } + if (stats.isFullScene()) { + sendingMode << "F"; + } else { + sendingMode << "p"; + } } - } - }); + }); + } sendingMode << " - " << serverCount << " servers"; if (movingServerCount > 0) { sendingMode << " "; @@ -388,114 +390,116 @@ void OctreeStatsDialog::showOctreeServersOfType(NodeType_t serverType) { // now lookup stats details for this server... if (_extraServerDetails != LESS) { - NodeToOctreeSceneStats* sceneStats = qApp->getOcteeSceneStats(); - sceneStats->withReadLock([&] { - if (sceneStats->find(nodeUUID) != sceneStats->end()) { - OctreeSceneStats& stats = sceneStats->at(nodeUUID); - - switch (_extraServerDetails) { - case MOST: { - extraDetails << "
"; - - float lastFullEncode = stats.getLastFullTotalEncodeTime() / USECS_PER_MSEC; - float lastFullSend = stats.getLastFullElapsedTime() / USECS_PER_MSEC; - float lastFullSendInSeconds = stats.getLastFullElapsedTime() / USECS_PER_SECOND; - float lastFullPackets = stats.getLastFullTotalPackets(); - float lastFullPPS = lastFullPackets; - if (lastFullSendInSeconds > 0) { - lastFullPPS = lastFullPackets / lastFullSendInSeconds; - } - - QString lastFullEncodeString = locale.toString(lastFullEncode); - QString lastFullSendString = locale.toString(lastFullSend); - QString lastFullPacketsString = locale.toString(lastFullPackets); - QString lastFullBytesString = locale.toString((uint)stats.getLastFullTotalBytes()); - QString lastFullPPSString = locale.toString(lastFullPPS); - - extraDetails << "
" << "Last Full Scene... " << - "Encode: " << qPrintable(lastFullEncodeString) << " ms " << - "Send: " << qPrintable(lastFullSendString) << " ms " << - "Packets: " << qPrintable(lastFullPacketsString) << " " << - "Bytes: " << qPrintable(lastFullBytesString) << " " << - "Rate: " << qPrintable(lastFullPPSString) << " PPS"; - - for (int i = 0; i < OctreeSceneStats::ITEM_COUNT; i++) { - OctreeSceneStats::Item item = (OctreeSceneStats::Item)(i); - OctreeSceneStats::ItemInfo& itemInfo = stats.getItemInfo(item); - extraDetails << "
" << itemInfo.caption << " " << stats.getItemValue(item); - } - } // fall through... since MOST has all of MORE - /* fall-thru */ - case MORE: { - QString totalString = locale.toString((uint)stats.getTotalElements()); - QString internalString = locale.toString((uint)stats.getTotalInternal()); - QString leavesString = locale.toString((uint)stats.getTotalLeaves()); - - serverDetails << "
" << "Node UUID: " << qPrintable(nodeUUID.toString()) << " "; - - serverDetails << "
" << "Elements: " << - qPrintable(totalString) << " total " << - qPrintable(internalString) << " internal " << - qPrintable(leavesString) << " leaves "; - - QString incomingPacketsString = locale.toString((uint)stats.getIncomingPackets()); - QString incomingBytesString = locale.toString((uint)stats.getIncomingBytes()); - QString incomingWastedBytesString = locale.toString((uint)stats.getIncomingWastedBytes()); - const SequenceNumberStats& seqStats = stats.getIncomingOctreeSequenceNumberStats(); - QString incomingOutOfOrderString = locale.toString((uint)seqStats.getOutOfOrder()); - QString incomingLateString = locale.toString((uint)seqStats.getLate()); - QString incomingUnreasonableString = locale.toString((uint)seqStats.getUnreasonable()); - QString incomingEarlyString = locale.toString((uint)seqStats.getEarly()); - QString incomingLikelyLostString = locale.toString((uint)seqStats.getLost()); - QString incomingRecovered = locale.toString((uint)seqStats.getRecovered()); - - qint64 clockSkewInUsecs = node->getClockSkewUsec(); - QString formattedClockSkewString = formatUsecTime(clockSkewInUsecs); - qint64 clockSkewInMS = clockSkewInUsecs / (qint64)USECS_PER_MSEC; - QString incomingFlightTimeString = locale.toString((int)stats.getIncomingFlightTimeAverage()); - QString incomingPingTimeString = locale.toString(node->getPingMs()); - QString incomingClockSkewString = locale.toString(clockSkewInMS); - - serverDetails << "
" << "Incoming Packets: " << qPrintable(incomingPacketsString) << - "/ Lost: " << qPrintable(incomingLikelyLostString) << - "/ Recovered: " << qPrintable(incomingRecovered); - - serverDetails << "
" << " Out of Order: " << qPrintable(incomingOutOfOrderString) << - "/ Early: " << qPrintable(incomingEarlyString) << - "/ Late: " << qPrintable(incomingLateString) << - "/ Unreasonable: " << qPrintable(incomingUnreasonableString); - - serverDetails << "
" << - " Average Flight Time: " << qPrintable(incomingFlightTimeString) << " msecs"; - - serverDetails << "
" << - " Average Ping Time: " << qPrintable(incomingPingTimeString) << " msecs"; - - serverDetails << "
" << - " Average Clock Skew: " << qPrintable(incomingClockSkewString) << " msecs" << - " [" << qPrintable(formattedClockSkewString) << "]"; - - - serverDetails << "
" << "Incoming" << - " Bytes: " << qPrintable(incomingBytesString) << - " Wasted Bytes: " << qPrintable(incomingWastedBytesString); - - serverDetails << extraDetails.str(); - if (_extraServerDetails == MORE) { - linkDetails << " [most...]"; - linkDetails << " [less...]"; - } else { - linkDetails << " [less...]"; - linkDetails << " [least...]"; - } - - } break; - case LESS: { - // nothing - } break; + auto sceneStats = qApp->getOcteeSceneStats(); + if (sceneStats) { + sceneStats.value()->withReadLock([&] { + if (sceneStats.value()->find(nodeUUID) != sceneStats.value()->end()) { + OctreeSceneStats& stats = sceneStats.value()->at(nodeUUID); + + switch (_extraServerDetails) { + case MOST: { + extraDetails << "
"; + + float lastFullEncode = stats.getLastFullTotalEncodeTime() / USECS_PER_MSEC; + float lastFullSend = stats.getLastFullElapsedTime() / USECS_PER_MSEC; + float lastFullSendInSeconds = stats.getLastFullElapsedTime() / USECS_PER_SECOND; + float lastFullPackets = stats.getLastFullTotalPackets(); + float lastFullPPS = lastFullPackets; + if (lastFullSendInSeconds > 0) { + lastFullPPS = lastFullPackets / lastFullSendInSeconds; + } + + QString lastFullEncodeString = locale.toString(lastFullEncode); + QString lastFullSendString = locale.toString(lastFullSend); + QString lastFullPacketsString = locale.toString(lastFullPackets); + QString lastFullBytesString = locale.toString((uint)stats.getLastFullTotalBytes()); + QString lastFullPPSString = locale.toString(lastFullPPS); + + extraDetails << "
" << "Last Full Scene... " << + "Encode: " << qPrintable(lastFullEncodeString) << " ms " << + "Send: " << qPrintable(lastFullSendString) << " ms " << + "Packets: " << qPrintable(lastFullPacketsString) << " " << + "Bytes: " << qPrintable(lastFullBytesString) << " " << + "Rate: " << qPrintable(lastFullPPSString) << " PPS"; + + for (int i = 0; i < OctreeSceneStats::ITEM_COUNT; i++) { + OctreeSceneStats::Item item = (OctreeSceneStats::Item)(i); + OctreeSceneStats::ItemInfo& itemInfo = stats.getItemInfo(item); + extraDetails << "
" << itemInfo.caption << " " << stats.getItemValue(item); + } + } // fall through... since MOST has all of MORE + /* fall-thru */ + case MORE: { + QString totalString = locale.toString((uint)stats.getTotalElements()); + QString internalString = locale.toString((uint)stats.getTotalInternal()); + QString leavesString = locale.toString((uint)stats.getTotalLeaves()); + + serverDetails << "
" << "Node UUID: " << qPrintable(nodeUUID.toString()) << " "; + + serverDetails << "
" << "Elements: " << + qPrintable(totalString) << " total " << + qPrintable(internalString) << " internal " << + qPrintable(leavesString) << " leaves "; + + QString incomingPacketsString = locale.toString((uint)stats.getIncomingPackets()); + QString incomingBytesString = locale.toString((uint)stats.getIncomingBytes()); + QString incomingWastedBytesString = locale.toString((uint)stats.getIncomingWastedBytes()); + const SequenceNumberStats& seqStats = stats.getIncomingOctreeSequenceNumberStats(); + QString incomingOutOfOrderString = locale.toString((uint)seqStats.getOutOfOrder()); + QString incomingLateString = locale.toString((uint)seqStats.getLate()); + QString incomingUnreasonableString = locale.toString((uint)seqStats.getUnreasonable()); + QString incomingEarlyString = locale.toString((uint)seqStats.getEarly()); + QString incomingLikelyLostString = locale.toString((uint)seqStats.getLost()); + QString incomingRecovered = locale.toString((uint)seqStats.getRecovered()); + + qint64 clockSkewInUsecs = node->getClockSkewUsec(); + QString formattedClockSkewString = formatUsecTime(clockSkewInUsecs); + qint64 clockSkewInMS = clockSkewInUsecs / (qint64)USECS_PER_MSEC; + QString incomingFlightTimeString = locale.toString((int)stats.getIncomingFlightTimeAverage()); + QString incomingPingTimeString = locale.toString(node->getPingMs()); + QString incomingClockSkewString = locale.toString(clockSkewInMS); + + serverDetails << "
" << "Incoming Packets: " << qPrintable(incomingPacketsString) << + "/ Lost: " << qPrintable(incomingLikelyLostString) << + "/ Recovered: " << qPrintable(incomingRecovered); + + serverDetails << "
" << " Out of Order: " << qPrintable(incomingOutOfOrderString) << + "/ Early: " << qPrintable(incomingEarlyString) << + "/ Late: " << qPrintable(incomingLateString) << + "/ Unreasonable: " << qPrintable(incomingUnreasonableString); + + serverDetails << "
" << + " Average Flight Time: " << qPrintable(incomingFlightTimeString) << " msecs"; + + serverDetails << "
" << + " Average Ping Time: " << qPrintable(incomingPingTimeString) << " msecs"; + + serverDetails << "
" << + " Average Clock Skew: " << qPrintable(incomingClockSkewString) << " msecs" << + " [" << qPrintable(formattedClockSkewString) << "]"; + + + serverDetails << "
" << "Incoming" << + " Bytes: " << qPrintable(incomingBytesString) << + " Wasted Bytes: " << qPrintable(incomingWastedBytesString); + + serverDetails << extraDetails.str(); + if (_extraServerDetails == MORE) { + linkDetails << " [most...]"; + linkDetails << " [less...]"; + } else { + linkDetails << " [less...]"; + linkDetails << " [least...]"; + } + + } break; + case LESS: { + // nothing + } break; + } } - } - }); + }); + } } else { linkDetails << " [more...]"; linkDetails << " [most...]"; diff --git a/interface/src/ui/OctreeStatsProvider.cpp b/interface/src/ui/OctreeStatsProvider.cpp index 133af1f7fe0..f4cbda8d1b1 100644 --- a/interface/src/ui/OctreeStatsProvider.cpp +++ b/interface/src/ui/OctreeStatsProvider.cpp @@ -115,35 +115,37 @@ void OctreeStatsProvider::updateOctreeStatsData() { unsigned long totalLeaves = 0; m_sendingMode.clear(); - NodeToOctreeSceneStats* sceneStats = qApp->getOcteeSceneStats(); - sceneStats->withReadLock([&] { - for (NodeToOctreeSceneStatsIterator i = sceneStats->begin(); i != sceneStats->end(); i++) { - //const QUuid& uuid = i->first; - OctreeSceneStats& stats = i->second; - serverCount++; - - // calculate server node totals - totalNodes += stats.getTotalElements(); - totalInternal += stats.getTotalInternal(); - totalLeaves += stats.getTotalLeaves(); - - // Sending mode - if (serverCount > 1) { - m_sendingMode += ","; - } - if (stats.isMoving()) { - m_sendingMode += "M"; - movingServerCount++; - } else { - m_sendingMode += "S"; - } - if (stats.isFullScene()) { - m_sendingMode += "F"; - } else { - m_sendingMode += "p"; + auto sceneStats = qApp->getOcteeSceneStats(); + if (sceneStats) { + sceneStats.value()->withReadLock([&] { + for (NodeToOctreeSceneStatsIterator i = sceneStats.value()->begin(); i != sceneStats.value()->end(); i++) { + //const QUuid& uuid = i->first; + OctreeSceneStats& stats = i->second; + serverCount++; + + // calculate server node totals + totalNodes += stats.getTotalElements(); + totalInternal += stats.getTotalInternal(); + totalLeaves += stats.getTotalLeaves(); + + // Sending mode + if (serverCount > 1) { + m_sendingMode += ","; + } + if (stats.isMoving()) { + m_sendingMode += "M"; + movingServerCount++; + } else { + m_sendingMode += "S"; + } + if (stats.isFullScene()) { + m_sendingMode += "F"; + } else { + m_sendingMode += "p"; + } } - } - }); + }); + } m_sendingMode += QString(" - %1 servers").arg(serverCount); if (movingServerCount > 0) { m_sendingMode += " "; @@ -262,72 +264,74 @@ void OctreeStatsProvider::showOctreeServersOfType(NodeType_t serverType) { QUuid nodeUUID = node->getUUID(); // now lookup stats details for this server... - NodeToOctreeSceneStats* sceneStats = qApp->getOcteeSceneStats(); - sceneStats->withReadLock([&] { - if (sceneStats->find(nodeUUID) != sceneStats->end()) { - OctreeSceneStats& stats = sceneStats->at(nodeUUID); - - float lastFullEncode = stats.getLastFullTotalEncodeTime() / USECS_PER_MSEC; - float lastFullSend = stats.getLastFullElapsedTime() / USECS_PER_MSEC; - float lastFullSendInSeconds = stats.getLastFullElapsedTime() / USECS_PER_SECOND; - float lastFullPackets = stats.getLastFullTotalPackets(); - float lastFullPPS = lastFullPackets; - if (lastFullSendInSeconds > 0) { - lastFullPPS = lastFullPackets / lastFullSendInSeconds; - } - - mostDetails += QString("

Last Full Scene... Encode: %1 ms Send: %2 ms Packets: %3 Bytes: %4 Rate: %5 PPS") - .arg(lastFullEncode) - .arg(lastFullSend) - .arg(lastFullPackets) - .arg(stats.getLastFullTotalBytes()) - .arg(lastFullPPS); - - for (int i = 0; i < OctreeSceneStats::ITEM_COUNT; i++) { - OctreeSceneStats::Item item = (OctreeSceneStats::Item)(i); - OctreeSceneStats::ItemInfo& itemInfo = stats.getItemInfo(item); - mostDetails += QString("
%1 %2") - .arg(itemInfo.caption).arg(stats.getItemValue(item)); + auto sceneStats = qApp->getOcteeSceneStats(); + if (sceneStats) { + sceneStats.value()->withReadLock([&] { + if (sceneStats.value()->find(nodeUUID) != sceneStats.value()->end()) { + OctreeSceneStats& stats = sceneStats.value()->at(nodeUUID); + + float lastFullEncode = stats.getLastFullTotalEncodeTime() / USECS_PER_MSEC; + float lastFullSend = stats.getLastFullElapsedTime() / USECS_PER_MSEC; + float lastFullSendInSeconds = stats.getLastFullElapsedTime() / USECS_PER_SECOND; + float lastFullPackets = stats.getLastFullTotalPackets(); + float lastFullPPS = lastFullPackets; + if (lastFullSendInSeconds > 0) { + lastFullPPS = lastFullPackets / lastFullSendInSeconds; + } + + mostDetails += QString("

Last Full Scene... Encode: %1 ms Send: %2 ms Packets: %3 Bytes: %4 Rate: %5 PPS") + .arg(lastFullEncode) + .arg(lastFullSend) + .arg(lastFullPackets) + .arg(stats.getLastFullTotalBytes()) + .arg(lastFullPPS); + + for (int i = 0; i < OctreeSceneStats::ITEM_COUNT; i++) { + OctreeSceneStats::Item item = (OctreeSceneStats::Item)(i); + OctreeSceneStats::ItemInfo& itemInfo = stats.getItemInfo(item); + mostDetails += QString("
%1 %2") + .arg(itemInfo.caption).arg(stats.getItemValue(item)); + } + + moreDetails += "
Node UUID: " +nodeUUID.toString() + " "; + + moreDetails += QString("
Elements: %1 total %2 internal %3 leaves ") + .arg(stats.getTotalElements()) + .arg(stats.getTotalInternal()) + .arg(stats.getTotalLeaves()); + + const SequenceNumberStats& seqStats = stats.getIncomingOctreeSequenceNumberStats(); + qint64 clockSkewInUsecs = node->getClockSkewUsec(); + qint64 clockSkewInMS = clockSkewInUsecs / (qint64)USECS_PER_MSEC; + + moreDetails += QString("
Incoming Packets: %1/ Lost: %2/ Recovered: %3") + .arg(stats.getIncomingPackets()) + .arg(seqStats.getLost()) + .arg(seqStats.getRecovered()); + + moreDetails += QString("
Out of Order: %1/ Early: %2/ Late: %3/ Unreasonable: %4") + .arg(seqStats.getOutOfOrder()) + .arg(seqStats.getEarly()) + .arg(seqStats.getLate()) + .arg(seqStats.getUnreasonable()); + + moreDetails += QString("
Average Flight Time: %1 msecs") + .arg(stats.getIncomingFlightTimeAverage()); + + moreDetails += QString("
Average Ping Time: %1 msecs") + .arg(node->getPingMs()); + + moreDetails += QString("
Average Clock Skew: %1 msecs [%2]") + .arg(clockSkewInMS) + .arg(formatUsecTime(clockSkewInUsecs)); + + + moreDetails += QString("
Incoming Bytes: %1 Wasted Bytes: %2") + .arg(stats.getIncomingBytes()) + .arg(stats.getIncomingWastedBytes()); } - - moreDetails += "
Node UUID: " +nodeUUID.toString() + " "; - - moreDetails += QString("
Elements: %1 total %2 internal %3 leaves ") - .arg(stats.getTotalElements()) - .arg(stats.getTotalInternal()) - .arg(stats.getTotalLeaves()); - - const SequenceNumberStats& seqStats = stats.getIncomingOctreeSequenceNumberStats(); - qint64 clockSkewInUsecs = node->getClockSkewUsec(); - qint64 clockSkewInMS = clockSkewInUsecs / (qint64)USECS_PER_MSEC; - - moreDetails += QString("
Incoming Packets: %1/ Lost: %2/ Recovered: %3") - .arg(stats.getIncomingPackets()) - .arg(seqStats.getLost()) - .arg(seqStats.getRecovered()); - - moreDetails += QString("
Out of Order: %1/ Early: %2/ Late: %3/ Unreasonable: %4") - .arg(seqStats.getOutOfOrder()) - .arg(seqStats.getEarly()) - .arg(seqStats.getLate()) - .arg(seqStats.getUnreasonable()); - - moreDetails += QString("
Average Flight Time: %1 msecs") - .arg(stats.getIncomingFlightTimeAverage()); - - moreDetails += QString("
Average Ping Time: %1 msecs") - .arg(node->getPingMs()); - - moreDetails += QString("
Average Clock Skew: %1 msecs [%2]") - .arg(clockSkewInMS) - .arg(formatUsecTime(clockSkewInUsecs)); - - - moreDetails += QString("
Incoming Bytes: %1 Wasted Bytes: %2") - .arg(stats.getIncomingBytes()) - .arg(stats.getIncomingWastedBytes()); - } - }); + }); + } m_servers.append(lesserDetails); m_servers.append(moreDetails); m_servers.append(mostDetails); diff --git a/interface/src/ui/Stats.cpp b/interface/src/ui/Stats.cpp index 463e92b4aa4..822724c6e0f 100644 --- a/interface/src/ui/Stats.cpp +++ b/interface/src/ui/Stats.cpp @@ -339,35 +339,39 @@ void Stats::updateStats(bool force) { unsigned long totalLeaves = 0; std::stringstream sendingModeStream(""); sendingModeStream << "["; - NodeToOctreeSceneStats* octreeServerSceneStats = qApp->getOcteeSceneStats(); - for (NodeToOctreeSceneStatsIterator i = octreeServerSceneStats->begin(); i != octreeServerSceneStats->end(); i++) { - //const QUuid& uuid = i->first; - OctreeSceneStats& stats = i->second; - serverCount++; - if (_expanded) { - if (serverCount > 1) { - sendingModeStream << ","; - } - if (stats.isMoving()) { - sendingModeStream << "M"; - movingServerCount++; - } else { - sendingModeStream << "S"; - } - if (stats.isFullScene()) { - sendingModeStream << "F"; - } - else { - sendingModeStream << "p"; - } - } + auto octreeServerSceneStats = qApp->getOcteeSceneStats(); + if (octreeServerSceneStats) { + octreeServerSceneStats.value()->withReadLock([&] { + for (NodeToOctreeSceneStatsIterator i = octreeServerSceneStats.value()->begin(); i != octreeServerSceneStats.value()->end(); i++) { + //const QUuid& uuid = i->first; + OctreeSceneStats& stats = i->second; + serverCount++; + if (_expanded) { + if (serverCount > 1) { + sendingModeStream << ","; + } + if (stats.isMoving()) { + sendingModeStream << "M"; + movingServerCount++; + } else { + sendingModeStream << "S"; + } + if (stats.isFullScene()) { + sendingModeStream << "F"; + } + else { + sendingModeStream << "p"; + } + } - // calculate server node totals - totalNodes += stats.getTotalElements(); - if (_expanded) { - totalInternal += stats.getTotalInternal(); - totalLeaves += stats.getTotalLeaves(); - } + // calculate server node totals + totalNodes += stats.getTotalElements(); + if (_expanded) { + totalInternal += stats.getTotalInternal(); + totalLeaves += stats.getTotalLeaves(); + } + } + }); } if (_expanded || force) { if (serverCount == 0) { diff --git a/libraries/gpu/src/gpu/Texture.h b/libraries/gpu/src/gpu/Texture.h index c22781da8cb..6d2626a512b 100644 --- a/libraries/gpu/src/gpu/Texture.h +++ b/libraries/gpu/src/gpu/Texture.h @@ -659,7 +659,7 @@ class TextureSource { protected: gpu::TexturePointer _gpuTexture; std::function _gpuTextureOperator { nullptr }; - mutable bool _locked { false }; + mutable std::atomic _locked { false }; QUrl _imageUrl; int _type { 0 }; }; diff --git a/libraries/graphics/src/graphics/Material.cpp b/libraries/graphics/src/graphics/Material.cpp index 6f5b60a703d..6d9113625e1 100644 --- a/libraries/graphics/src/graphics/Material.cpp +++ b/libraries/graphics/src/graphics/Material.cpp @@ -246,22 +246,25 @@ bool Material::resetOpacityMap() const { _key.setOpacityMaskMap(false); _key.setTranslucentMap(false); - const auto& textureMap = getTextureMap(MaterialKey::ALBEDO_MAP); - if (textureMap && - textureMap->useAlphaChannel() && - textureMap->isDefined() && - textureMap->getTextureView().isValid()) { - - auto usage = textureMap->getTextureView()._texture->getUsage(); - if (usage.isAlpha()) { - if (usage.isAlphaMask()) { - // Texture has alpha, but it is just a mask - _key.setOpacityMaskMap(true); - _key.setTranslucentMap(false); - } else { - // Texture has alpha, it is a true translucency channel - _key.setOpacityMaskMap(false); - _key.setTranslucentMap(true); + const auto textureMap = getTextureMap(MaterialKey::ALBEDO_MAP); + if (textureMap) { + auto textureView = textureMap->getTextureView(); + if (textureView && + textureMap->useAlphaChannel() && + textureMap->isDefined() && + textureView.isValid()) { + + auto usage = textureView._texture->getUsage(); + if (usage.isAlpha()) { + if (usage.isAlphaMask()) { + // Texture has alpha, but it is just a mask + _key.setOpacityMaskMap(true); + _key.setTranslucentMap(false); + } else { + // Texture has alpha, it is a true translucency channel + _key.setOpacityMaskMap(false); + _key.setTranslucentMap(true); + } } } } diff --git a/libraries/model-networking/src/model-networking/ModelCache.h b/libraries/model-networking/src/model-networking/ModelCache.h index 194723c58d0..114b6927315 100644 --- a/libraries/model-networking/src/model-networking/ModelCache.h +++ b/libraries/model-networking/src/model-networking/ModelCache.h @@ -76,7 +76,7 @@ class Geometry { bool _waitForWearables { false }; private: - mutable bool _areTexturesLoaded { false }; + mutable std::atomic _areTexturesLoaded { false }; }; /// A geometry loaded from the network. diff --git a/libraries/networking/src/LimitedNodeList.cpp b/libraries/networking/src/LimitedNodeList.cpp index fcb48b00674..87c3c1fe912 100644 --- a/libraries/networking/src/LimitedNodeList.cpp +++ b/libraries/networking/src/LimitedNodeList.cpp @@ -237,6 +237,12 @@ QUdpSocket& LimitedNodeList::getDTLSSocket() { return *_dtlsSocket; } +PacketReceiver& LimitedNodeList::getPacketReceiver() { + Q_ASSERT(_packetReceiver); + return *_packetReceiver; +} + + #if defined(WEBRTC_DATA_CHANNELS) const WebRTCSocket* LimitedNodeList::getWebRTCSocket() { return _nodeSocket.getWebRTCSocket(); diff --git a/libraries/networking/src/LimitedNodeList.h b/libraries/networking/src/LimitedNodeList.h index cdb742a3c37..92822ae67d3 100644 --- a/libraries/networking/src/LimitedNodeList.h +++ b/libraries/networking/src/LimitedNodeList.h @@ -143,7 +143,7 @@ class LimitedNodeList : public QObject, public Dependency { #endif - PacketReceiver& getPacketReceiver() { return *_packetReceiver; } + PacketReceiver& getPacketReceiver(); virtual bool isDomainServer() const { return true; } virtual QUuid getDomainUUID() const { assert(false); return QUuid(); } diff --git a/libraries/octree/src/OctreePacketData.cpp b/libraries/octree/src/OctreePacketData.cpp index 16b46d8c165..a43f72775f3 100644 --- a/libraries/octree/src/OctreePacketData.cpp +++ b/libraries/octree/src/OctreePacketData.cpp @@ -807,7 +807,13 @@ int OctreePacketData::unpackDataFromBytes(const unsigned char* dataBytes, QVecto memcpy(&length, dataBytes, sizeof(uint16_t)); dataBytes += sizeof(length); result.resize(length); - memcpy(result.data(), dataBytes, length * sizeof(float)); + // It does a lot of zero-length writes to nullptr for some reason. + if (length != 0) { + // These were null sometimes, so it's best to check in debug builds. + Q_ASSERT(result.data()); + Q_ASSERT(dataBytes); + memcpy(result.data(), dataBytes, length * sizeof(float)); + } return sizeof(uint16_t) + length * sizeof(float); } @@ -834,7 +840,13 @@ int OctreePacketData::unpackDataFromBytes(const unsigned char* dataBytes, QVecto memcpy(&length, dataBytes, sizeof(uint16_t)); dataBytes += sizeof(length); result.resize(length); - memcpy(result.data(), dataBytes, length * sizeof(QUuid)); + // It does a lot of zero-length writes to nullptr for some reason. + if (length != 0) { + // These were null sometimes, so it's best to check in debug builds. + Q_ASSERT(result.data()); + Q_ASSERT(dataBytes); + memcpy(result.data(), dataBytes, length * sizeof(QUuid)); + } return sizeof(uint16_t) + length * sizeof(QUuid); } diff --git a/libraries/procedural/src/procedural/ProceduralMaterialCache.cpp b/libraries/procedural/src/procedural/ProceduralMaterialCache.cpp index 0925efa0561..70d0ece58bd 100644 --- a/libraries/procedural/src/procedural/ProceduralMaterialCache.cpp +++ b/libraries/procedural/src/procedural/ProceduralMaterialCache.cpp @@ -1136,7 +1136,8 @@ bool NetworkMaterial::isMissingTexture() { } // Failed texture downloads need to be considered as 'loaded' // or the object will never fade in - bool finished = texture->isFailed() || (texture->isLoaded() && texture->getGPUTexture() && texture->getGPUTexture()->isDefined()); + auto gpuTexture = texture->getGPUTexture(); + bool finished = texture->isFailed() || (texture->isLoaded() && gpuTexture && gpuTexture->isDefined()); if (!finished) { return true; } diff --git a/libraries/procedural/src/procedural/ProceduralMaterialCache.h b/libraries/procedural/src/procedural/ProceduralMaterialCache.h index 90d2a6964dc..9b0adabb5ae 100644 --- a/libraries/procedural/src/procedural/ProceduralMaterialCache.h +++ b/libraries/procedural/src/procedural/ProceduralMaterialCache.h @@ -51,6 +51,7 @@ class NetworkMaterial : public graphics::Material { } }; using Textures = std::unordered_map; + // TODO: investigate if this could pass reference instead Textures getTextures() { return _textures; } protected: diff --git a/libraries/procedural/src/procedural/ReferenceMaterial.h b/libraries/procedural/src/procedural/ReferenceMaterial.h index ce58e811c02..04bece16fd9 100644 --- a/libraries/procedural/src/procedural/ReferenceMaterial.h +++ b/libraries/procedural/src/procedural/ReferenceMaterial.h @@ -77,7 +77,7 @@ class ReferenceMaterial : public graphics::ProceduralMaterial { private: static std::function _unboundMaterialForUUIDOperator; std::function _materialForUUIDOperator; - mutable bool _locked { false }; + mutable std::atomic _locked { false }; graphics::MaterialPointer getMaterial() const; std::shared_ptr getNetworkMaterial() const; diff --git a/libraries/render-utils/src/RenderPipelines.cpp b/libraries/render-utils/src/RenderPipelines.cpp index cf934f6023b..e72d9cbd8fd 100644 --- a/libraries/render-utils/src/RenderPipelines.cpp +++ b/libraries/render-utils/src/RenderPipelines.cpp @@ -164,10 +164,12 @@ void RenderPipelines::updateMultiMaterial(graphics::MultiMaterial& multiMaterial if (itr != textureMaps.end()) { if (itr->second->isDefined()) { material->resetOpacityMap(); - drawMaterialTextures[layerIndex]->setTexture(gr::Texture::MaterialAlbedo, itr->second->getTextureView()); + auto textureView = itr->second->getTextureView(); + Q_ASSERT(textureView); + drawMaterialTextures[layerIndex]->setTexture(gr::Texture::MaterialAlbedo, textureView); multiMaterial.addSamplerFunc([=] () { material->applySampler(graphics::MaterialKey::ALBEDO_MAP); }); - if (itr->second->getTextureView().isReference()) { - multiMaterial.addReferenceTexture(itr->second->getTextureView().getTextureOperator()); + if (textureView.isReference()) { + multiMaterial.addReferenceTexture(textureView.getTextureOperator()); } wasSet = true; } else { @@ -188,10 +190,12 @@ void RenderPipelines::updateMultiMaterial(graphics::MultiMaterial& multiMaterial auto itr = textureMaps.find(graphics::MaterialKey::METALLIC_MAP); if (itr != textureMaps.end()) { if (itr->second->isDefined()) { - drawMaterialTextures[layerIndex]->setTexture(gr::Texture::MaterialMetallic, itr->second->getTextureView()); + auto textureView = itr->second->getTextureView(); + Q_ASSERT(textureView); + drawMaterialTextures[layerIndex]->setTexture(gr::Texture::MaterialMetallic, textureView); multiMaterial.addSamplerFunc([=] () { material->applySampler(graphics::MaterialKey::METALLIC_MAP); }); - if (itr->second->getTextureView().isReference()) { - multiMaterial.addReferenceTexture(itr->second->getTextureView().getTextureOperator()); + if (textureView.isReference()) { + multiMaterial.addReferenceTexture(textureView.getTextureOperator()); } wasSet = true; } else { @@ -210,10 +214,12 @@ void RenderPipelines::updateMultiMaterial(graphics::MultiMaterial& multiMaterial auto itr = textureMaps.find(graphics::MaterialKey::ROUGHNESS_MAP); if (itr != textureMaps.end()) { if (itr->second->isDefined()) { - drawMaterialTextures[layerIndex]->setTexture(gr::Texture::MaterialRoughness, itr->second->getTextureView()); + auto textureView = itr->second->getTextureView(); + Q_ASSERT(textureView); + drawMaterialTextures[layerIndex]->setTexture(gr::Texture::MaterialRoughness, textureView); multiMaterial.addSamplerFunc([=] () { material->applySampler(graphics::MaterialKey::ROUGHNESS_MAP); }); - if (itr->second->getTextureView().isReference()) { - multiMaterial.addReferenceTexture(itr->second->getTextureView().getTextureOperator()); + if (textureView.isReference()) { + multiMaterial.addReferenceTexture(textureView.getTextureOperator()); } wasSet = true; } else { @@ -232,10 +238,12 @@ void RenderPipelines::updateMultiMaterial(graphics::MultiMaterial& multiMaterial auto itr = textureMaps.find(graphics::MaterialKey::NORMAL_MAP); if (itr != textureMaps.end()) { if (itr->second->isDefined()) { - drawMaterialTextures[layerIndex]->setTexture(gr::Texture::MaterialNormal, itr->second->getTextureView()); + auto textureView = itr->second->getTextureView(); + Q_ASSERT(textureView); + drawMaterialTextures[layerIndex]->setTexture(gr::Texture::MaterialNormal, textureView); multiMaterial.addSamplerFunc([=] () { material->applySampler(graphics::MaterialKey::NORMAL_MAP); }); - if (itr->second->getTextureView().isReference()) { - multiMaterial.addReferenceTexture(itr->second->getTextureView().getTextureOperator()); + if (textureView.isReference()) { + multiMaterial.addReferenceTexture(textureView.getTextureOperator()); } wasSet = true; } else { @@ -254,10 +262,12 @@ void RenderPipelines::updateMultiMaterial(graphics::MultiMaterial& multiMaterial auto itr = textureMaps.find(graphics::MaterialKey::OCCLUSION_MAP); if (itr != textureMaps.end()) { if (itr->second->isDefined()) { - drawMaterialTextures[layerIndex]->setTexture(gr::Texture::MaterialOcclusion, itr->second->getTextureView()); + auto textureView = itr->second->getTextureView(); + Q_ASSERT(textureView); + drawMaterialTextures[layerIndex]->setTexture(gr::Texture::MaterialOcclusion, textureView); multiMaterial.addSamplerFunc([=] () { material->applySampler(graphics::MaterialKey::OCCLUSION_MAP); }); - if (itr->second->getTextureView().isReference()) { - multiMaterial.addReferenceTexture(itr->second->getTextureView().getTextureOperator()); + if (textureView.isReference()) { + multiMaterial.addReferenceTexture(textureView.getTextureOperator()); } wasSet = true; } else { @@ -276,10 +286,12 @@ void RenderPipelines::updateMultiMaterial(graphics::MultiMaterial& multiMaterial auto itr = textureMaps.find(graphics::MaterialKey::SCATTERING_MAP); if (itr != textureMaps.end()) { if (itr->second->isDefined()) { - drawMaterialTextures[layerIndex]->setTexture(gr::Texture::MaterialScattering, itr->second->getTextureView()); + auto textureView = itr->second->getTextureView(); + Q_ASSERT(textureView); + drawMaterialTextures[layerIndex]->setTexture(gr::Texture::MaterialScattering, textureView); multiMaterial.addSamplerFunc([=] () { material->applySampler(graphics::MaterialKey::SCATTERING_MAP); }); - if (itr->second->getTextureView().isReference()) { - multiMaterial.addReferenceTexture(itr->second->getTextureView().getTextureOperator()); + if (textureView.isReference()) { + multiMaterial.addReferenceTexture(textureView.getTextureOperator()); } wasSet = true; } else { @@ -299,10 +311,12 @@ void RenderPipelines::updateMultiMaterial(graphics::MultiMaterial& multiMaterial auto itr = textureMaps.find(graphics::MaterialKey::EMISSIVE_MAP); if (itr != textureMaps.end()) { if (itr->second->isDefined()) { - drawMaterialTextures[layerIndex]->setTexture(gr::Texture::MaterialEmissiveLightmap, itr->second->getTextureView()); + auto textureView = itr->second->getTextureView(); + Q_ASSERT(textureView); + drawMaterialTextures[layerIndex]->setTexture(gr::Texture::MaterialEmissiveLightmap, textureView); multiMaterial.addSamplerFunc([=] () { material->applySampler(graphics::MaterialKey::EMISSIVE_MAP); }); - if (itr->second->getTextureView().isReference()) { - multiMaterial.addReferenceTexture(itr->second->getTextureView().getTextureOperator()); + if (textureView.isReference()) { + multiMaterial.addReferenceTexture(textureView.getTextureOperator()); } wasSet = true; } else { @@ -324,10 +338,12 @@ void RenderPipelines::updateMultiMaterial(graphics::MultiMaterial& multiMaterial auto itr = textureMaps.find(graphics::MaterialKey::LIGHT_MAP); if (itr != textureMaps.end()) { if (itr->second->isDefined()) { - drawMaterialTextures[layerIndex]->setTexture(gr::Texture::MaterialEmissiveLightmap, itr->second->getTextureView()); + auto textureView = itr->second->getTextureView(); + Q_ASSERT(textureView); + drawMaterialTextures[layerIndex]->setTexture(gr::Texture::MaterialEmissiveLightmap, textureView); multiMaterial.addSamplerFunc([=] () { material->applySampler(graphics::MaterialKey::LIGHT_MAP); }); - if (itr->second->getTextureView().isReference()) { - multiMaterial.addReferenceTexture(itr->second->getTextureView().getTextureOperator()); + if (textureView.isReference()) { + multiMaterial.addReferenceTexture(textureView.getTextureOperator()); } wasSet = true; } else { @@ -345,10 +361,12 @@ void RenderPipelines::updateMultiMaterial(graphics::MultiMaterial& multiMaterial auto itr = textureMaps.find(graphics::MaterialKey::SPLAT_MAP); if (itr != textureMaps.end()) { if (itr->second->isDefined()) { - multiMaterial.setSplatMap(itr->second->getTextureView()._texture); + auto textureView = itr->second->getTextureView(); + Q_ASSERT(textureView); + multiMaterial.setSplatMap(textureView._texture); multiMaterial.addSamplerFunc([=]() { material->applySampler(graphics::MaterialKey::SPLAT_MAP); }); - if (itr->second->getTextureView().isReference()) { - multiMaterial.addReferenceTexture(itr->second->getTextureView().getTextureOperator()); + if (textureView.isReference()) { + multiMaterial.addReferenceTexture(textureView.getTextureOperator()); } wasSet = true; } else { @@ -430,10 +448,12 @@ void RenderPipelines::updateMultiMaterial(graphics::MultiMaterial& multiMaterial if (itr != textureMaps.end()) { if (itr->second->isDefined()) { material->resetOpacityMap(); - drawMaterialTextures[layerIndex]->setTexture(gr::Texture::MaterialAlbedo, itr->second->getTextureView()); + auto textureView = itr->second->getTextureView(); + Q_ASSERT(textureView); + drawMaterialTextures[layerIndex]->setTexture(gr::Texture::MaterialAlbedo, textureView); multiMaterial.addSamplerFunc([=] () { material->applySampler(graphics::MaterialKey::ALBEDO_MAP); }); - if (itr->second->getTextureView().isReference()) { - multiMaterial.addReferenceTexture(itr->second->getTextureView().getTextureOperator()); + if (textureView.isReference()) { + multiMaterial.addReferenceTexture(textureView.getTextureOperator()); } wasSet = true; } else { @@ -454,10 +474,12 @@ void RenderPipelines::updateMultiMaterial(graphics::MultiMaterial& multiMaterial auto itr = textureMaps.find(graphics::MaterialKey::NORMAL_MAP); if (itr != textureMaps.end()) { if (itr->second->isDefined()) { - drawMaterialTextures[layerIndex]->setTexture(gr::Texture::MaterialNormal, itr->second->getTextureView()); + auto textureView = itr->second->getTextureView(); + Q_ASSERT(textureView); + drawMaterialTextures[layerIndex]->setTexture(gr::Texture::MaterialNormal, textureView); multiMaterial.addSamplerFunc([=] () { material->applySampler(graphics::MaterialKey::NORMAL_MAP); }); - if (itr->second->getTextureView().isReference()) { - multiMaterial.addReferenceTexture(itr->second->getTextureView().getTextureOperator()); + if (textureView.isReference()) { + multiMaterial.addReferenceTexture(textureView.getTextureOperator()); } wasSet = true; } else { @@ -477,10 +499,12 @@ void RenderPipelines::updateMultiMaterial(graphics::MultiMaterial& multiMaterial auto itr = textureMaps.find(graphics::MaterialKey::EMISSIVE_MAP); if (itr != textureMaps.end()) { if (itr->second->isDefined()) { - drawMaterialTextures[layerIndex]->setTexture(gr::Texture::MaterialEmissiveLightmap, itr->second->getTextureView()); + auto textureView = itr->second->getTextureView(); + Q_ASSERT(textureView); + drawMaterialTextures[layerIndex]->setTexture(gr::Texture::MaterialEmissiveLightmap, textureView); multiMaterial.addSamplerFunc([=] () { material->applySampler(graphics::MaterialKey::EMISSIVE_MAP); }); - if (itr->second->getTextureView().isReference()) { - multiMaterial.addReferenceTexture(itr->second->getTextureView().getTextureOperator()); + if (textureView.isReference()) { + multiMaterial.addReferenceTexture(textureView.getTextureOperator()); } wasSet = true; } else { @@ -501,10 +525,12 @@ void RenderPipelines::updateMultiMaterial(graphics::MultiMaterial& multiMaterial auto itr = textureMaps.find(graphics::MaterialKey::SPLAT_MAP); if (itr != textureMaps.end()) { if (itr->second->isDefined()) { - multiMaterial.setSplatMap(itr->second->getTextureView()._texture); + auto textureView = itr->second->getTextureView(); + Q_ASSERT(textureView); + multiMaterial.setSplatMap(textureView._texture); multiMaterial.addSamplerFunc([=]() { material->applySampler(graphics::MaterialKey::SPLAT_MAP); }); - if (itr->second->getTextureView().isReference()) { - multiMaterial.addReferenceTexture(itr->second->getTextureView().getTextureOperator()); + if (textureView.isReference()) { + multiMaterial.addReferenceTexture(textureView.getTextureOperator()); } wasSet = true; } else { @@ -576,10 +602,12 @@ void RenderPipelines::updateMultiMaterial(graphics::MultiMaterial& multiMaterial auto itr = textureMaps.find((graphics::Material::MapChannel) NetworkMToonMaterial::SHADE_MAP); if (itr != textureMaps.end()) { if (itr->second->isDefined()) { - drawMaterialTextures[layerIndex]->setTexture(gr::Texture::MaterialShade, itr->second->getTextureView()); + auto textureView = itr->second->getTextureView(); + Q_ASSERT(textureView); + drawMaterialTextures[layerIndex]->setTexture(gr::Texture::MaterialShade, textureView); multiMaterial.addSamplerFunc([=] () { material->applySampler((graphics::Material::MapChannel) NetworkMToonMaterial::SHADE_MAP); }); - if (itr->second->getTextureView().isReference()) { - multiMaterial.addReferenceTexture(itr->second->getTextureView().getTextureOperator()); + if (textureView.isReference()) { + multiMaterial.addReferenceTexture(textureView.getTextureOperator()); } wasSet = true; } else { @@ -598,10 +626,12 @@ void RenderPipelines::updateMultiMaterial(graphics::MultiMaterial& multiMaterial auto itr = textureMaps.find((graphics::Material::MapChannel) NetworkMToonMaterial::SHADING_SHIFT_MAP); if (itr != textureMaps.end()) { if (itr->second->isDefined()) { - drawMaterialTextures[layerIndex]->setTexture(gr::Texture::MaterialShadingShift, itr->second->getTextureView()); + auto textureView = itr->second->getTextureView(); + Q_ASSERT(textureView); + drawMaterialTextures[layerIndex]->setTexture(gr::Texture::MaterialShadingShift, textureView); multiMaterial.addSamplerFunc([=] () { material->applySampler((graphics::Material::MapChannel) NetworkMToonMaterial::SHADING_SHIFT_MAP); }); - if (itr->second->getTextureView().isReference()) { - multiMaterial.addReferenceTexture(itr->second->getTextureView().getTextureOperator()); + if (textureView.isReference()) { + multiMaterial.addReferenceTexture(textureView.getTextureOperator()); } wasSet = true; } else { @@ -620,10 +650,12 @@ void RenderPipelines::updateMultiMaterial(graphics::MultiMaterial& multiMaterial auto itr = textureMaps.find((graphics::Material::MapChannel) NetworkMToonMaterial::MATCAP_MAP); if (itr != textureMaps.end()) { if (itr->second->isDefined()) { - drawMaterialTextures[layerIndex]->setTexture(gr::Texture::MaterialMatcap, itr->second->getTextureView()); + auto textureView = itr->second->getTextureView(); + Q_ASSERT(textureView); + drawMaterialTextures[layerIndex]->setTexture(gr::Texture::MaterialMatcap, textureView); multiMaterial.addSamplerFunc([=] () { material->applySampler((graphics::Material::MapChannel) NetworkMToonMaterial::MATCAP_MAP); }); - if (itr->second->getTextureView().isReference()) { - multiMaterial.addReferenceTexture(itr->second->getTextureView().getTextureOperator()); + if (textureView.isReference()) { + multiMaterial.addReferenceTexture(textureView.getTextureOperator()); } wasSet = true; } else { @@ -642,10 +674,12 @@ void RenderPipelines::updateMultiMaterial(graphics::MultiMaterial& multiMaterial auto itr = textureMaps.find((graphics::Material::MapChannel) NetworkMToonMaterial::RIM_MAP); if (itr != textureMaps.end()) { if (itr->second->isDefined()) { - drawMaterialTextures[layerIndex]->setTexture(gr::Texture::MaterialRim, itr->second->getTextureView()); + auto textureView = itr->second->getTextureView(); + Q_ASSERT(textureView); + drawMaterialTextures[layerIndex]->setTexture(gr::Texture::MaterialRim, textureView); multiMaterial.addSamplerFunc([=] () { material->applySampler((graphics::Material::MapChannel) NetworkMToonMaterial::RIM_MAP); }); - if (itr->second->getTextureView().isReference()) { - multiMaterial.addReferenceTexture(itr->second->getTextureView().getTextureOperator()); + if (textureView.isReference()) { + multiMaterial.addReferenceTexture(textureView.getTextureOperator()); } wasSet = true; } else { @@ -664,10 +698,12 @@ void RenderPipelines::updateMultiMaterial(graphics::MultiMaterial& multiMaterial auto itr = textureMaps.find((graphics::Material::MapChannel) NetworkMToonMaterial::UV_ANIMATION_MASK_MAP); if (itr != textureMaps.end()) { if (itr->second->isDefined()) { - drawMaterialTextures[layerIndex]->setTexture(gr::Texture::MaterialUVAnimationMask, itr->second->getTextureView()); + auto textureView = itr->second->getTextureView(); + Q_ASSERT(textureView); + drawMaterialTextures[layerIndex]->setTexture(gr::Texture::MaterialUVAnimationMask, textureView); multiMaterial.addSamplerFunc([=] () { material->applySampler((graphics::Material::MapChannel) NetworkMToonMaterial::UV_ANIMATION_MASK_MAP); }); - if (itr->second->getTextureView().isReference()) { - multiMaterial.addReferenceTexture(itr->second->getTextureView().getTextureOperator()); + if (textureView.isReference()) { + multiMaterial.addReferenceTexture(textureView.getTextureOperator()); } wasSet = true; } else { diff --git a/libraries/render-utils/src/RenderShadowTask.cpp b/libraries/render-utils/src/RenderShadowTask.cpp index 992de467ec7..514816a0d5a 100644 --- a/libraries/render-utils/src/RenderShadowTask.cpp +++ b/libraries/render-utils/src/RenderShadowTask.cpp @@ -59,7 +59,7 @@ void RenderShadowTask::build(JobModel& task, const render::Varying& input, rende const auto currentKeyLight = setupOutput.getN(4); // Fetch and cull the items from the scene - static const auto shadowCasterReceiverFilter = ItemFilter::Builder::visibleWorldItems().withOpaque().withoutLayered().withTagBits(tagBits, tagMask); + static const ItemFilter shadowCasterReceiverFilter = ItemFilter::Builder::visibleWorldItems().withOpaque().withoutLayered().withTagBits(tagBits, tagMask); const auto fetchInput = FetchSpatialTree::Inputs(shadowCasterReceiverFilter, queryResolution).asVarying(); const auto shadowSelection = task.addJob("FetchShadowTree", fetchInput); diff --git a/libraries/render-utils/src/text/Font.cpp b/libraries/render-utils/src/text/Font.cpp index ed487c4597b..bc8fab7abbc 100644 --- a/libraries/render-utils/src/text/Font.cpp +++ b/libraries/render-utils/src/text/Font.cpp @@ -106,6 +106,13 @@ int readHelper(void* dst, int length, void* data) { if (_readOffset.localData() + length > _readMax.localData()) { return -1; } + // Sometimes artery_font::decode issues zero-length reads to nullptr, which causes issues with memory sanitizers. + if (length == 0) { + return 0; + } + // These were null sometimes, so it's best to check in debug builds. + Q_ASSERT(dst); + Q_ASSERT(data); memcpy(dst, (char *)data + _readOffset.localData(), length); _readOffset.setLocalData(_readOffset.localData() + length); return length; diff --git a/libraries/task/src/task/Varying.h b/libraries/task/src/task/Varying.h index 686a00446bd..7f9e42c6c9d 100644 --- a/libraries/task/src/task/Varying.h +++ b/libraries/task/src/task/Varying.h @@ -32,7 +32,10 @@ class Varying { template Varying(const T& data, const std::string& name = "noname") : _concept(std::make_shared>(data, name)) {} template bool canCast() const { return !!std::dynamic_pointer_cast>(_concept); } - template const T& get() const { return std::static_pointer_cast>(_concept)->_data; } + template const T& get() const { + Q_ASSERT(std::dynamic_pointer_cast>(_concept)); + return std::static_pointer_cast>(_concept)->_data; + } template T& edit() { return std::static_pointer_cast>(_concept)->_data; } const std::string name() const { return _concept->name(); } From 448a21aa833ccfcb8fb071d256fac84cdf106f86 Mon Sep 17 00:00:00 2001 From: Karol Suprynowicz Date: Wed, 15 Oct 2025 15:38:00 +0200 Subject: [PATCH 031/111] Fix most of compiler warnings on Qt6 --- domain-server/src/DomainServer.cpp | 4 +- .../src/DomainServerSettingsManager.cpp | 28 +++++----- domain-server/src/NodeConnectionData.cpp | 2 +- interface/src/Application_Events.cpp | 8 +-- interface/src/avatar/AvatarDoctor.cpp | 2 +- interface/src/main.cpp | 7 ++- .../scripting/SettingsScriptingInterface.cpp | 2 +- interface/src/ui/JSConsole.cpp | 2 +- interface/src/ui/LogDialog.cpp | 20 +++---- interface/src/ui/Snapshot.cpp | 4 +- interface/src/ui/SnapshotAnimated.cpp | 2 +- .../WebBrowserSuggestionsEngine.cpp | 2 +- libraries/audio-client/src/AudioClient.cpp | 4 +- .../audio/src/MixedProcessedAudioStream.cpp | 4 +- libraries/baking/src/TextureBaker.cpp | 2 +- .../src/display-plugins/CompositorHelper.cpp | 2 +- .../src/EntityTreeRenderer.cpp | 12 ++-- .../src/RenderablePolyVoxEntityItem.cpp | 8 +-- .../entities/src/EntityDynamicInterface.cpp | 4 +- .../src/input-plugins/KeyboardMouseDevice.cpp | 8 +-- .../src/input-plugins/TouchscreenDevice.cpp | 13 +++-- .../TouchscreenVirtualPadDevice.cpp | 24 ++++---- .../src/material-networking/TextureCache.cpp | 6 +- .../src/model-baker/PrepareJointsTask.cpp | 6 +- libraries/model-serializers/src/FSTReader.cpp | 2 +- .../procedural/ProceduralMaterialCache.cpp | 2 +- libraries/qml/src/qml/OffscreenSurface.cpp | 4 +- libraries/script-engine/src/MouseEvent.cpp | 4 +- libraries/script-engine/src/ScriptEngines.cpp | 2 +- .../script-engine/src/ScriptValueUtils.cpp | 2 +- libraries/script-engine/src/TouchEvent.cpp | 2 +- .../src/VariantMapToScriptValue.cpp | 16 +++--- .../script-engine/src/WebSocketClass.cpp | 2 +- .../script-engine/src/v8/ScriptEngineV8.cpp | 10 ++-- .../src/v8/ScriptEngineV8_cast.cpp | 19 ++++--- .../src/v8/ScriptObjectV8Proxy.cpp | 2 +- libraries/shared/src/HifiConfigVariantMap.cpp | 4 +- libraries/shared/src/PathUtils.cpp | 3 +- libraries/shared/src/SettingHelpers.cpp | 56 ++++++++++--------- libraries/shared/src/ShapeInfo.cpp | 2 +- libraries/ui/src/OffscreenUi.cpp | 7 ++- libraries/ui/src/QmlWindowClass.cpp | 2 +- libraries/ui/src/ui/Menu.cpp | 2 +- libraries/ui/src/ui/OffscreenQmlSurface.cpp | 6 +- tools/nitpick/src/TestCreator.cpp | 4 +- 45 files changed, 169 insertions(+), 160 deletions(-) diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index 6feed867665..c22ed6ea835 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -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 diff --git a/domain-server/src/DomainServerSettingsManager.cpp b/domain-server/src/DomainServerSettingsManager.cpp index e740615938a..850dae7e43b 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."; @@ -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."; } @@ -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); } 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/src/Application_Events.cpp b/interface/src/Application_Events.cpp index 6106e267fd5..4a09e3bf4b7 100644 --- a/interface/src/Application_Events.cpp +++ b/interface/src/Application_Events.cpp @@ -652,7 +652,7 @@ void Application::mouseMoveEvent(QMouseEvent* event) { QMouseEvent mappedEvent(event->type(), transformedPos, - event->screenPos(), button, + event->globalPosition(), button, buttons, event->modifiers()); if (compositor.getReticleVisible() || !isHMDMode() || !compositor.getReticleOverDesktop() || @@ -687,7 +687,7 @@ 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()); QUuid result = getEntities()->mousePressEvent(&mappedEvent); setKeyboardFocusEntity(getEntities()->wantsKeyboardFocus(result) ? result : UNKNOWN_ENTITY_ID); @@ -726,7 +726,7 @@ void Application::mouseDoublePressEvent(QMouseEvent* event) { #endif QMouseEvent mappedEvent(event->type(), transformedPos, - event->screenPos(), event->button(), + event->globalPosition(), event->button(), event->buttons(), event->modifiers()); getEntities()->mouseDoublePressEvent(&mappedEvent); @@ -748,7 +748,7 @@ void Application::mouseReleaseEvent(QMouseEvent* event) { #endif QMouseEvent mappedEvent(event->type(), transformedPos, - event->screenPos(), event->button(), + event->globalPosition(), event->button(), event->buttons(), event->modifiers()); getEntities()->mouseReleaseEvent(&mappedEvent); diff --git a/interface/src/avatar/AvatarDoctor.cpp b/interface/src/avatar/AvatarDoctor.cpp index a46652c7d9b..ac431ef9e42 100644 --- a/interface/src/avatar/AvatarDoctor.cpp +++ b/interface/src/avatar/AvatarDoctor.cpp @@ -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()) { diff --git a/interface/src/main.cpp b/interface/src/main.cpp index b8c985eb297..e3b6c7d09ad 100644 --- a/interface/src/main.cpp +++ b/interface/src/main.cpp @@ -843,8 +843,11 @@ int main(int argc, const char* argv[]) { } QTranslator translator; - translator.load("i18n/interface_en"); - app.installTranslator(&translator); + if (translator.load("i18n/interface_en")) { + app.installTranslator(&translator); + } else { + qWarning() << " Failed to load QTranslator i18n/interface_en"; + } qCDebug(interfaceapp, "Created QT Application."); if (parser.isSet("abortAfterInit")) { return 99; diff --git a/interface/src/scripting/SettingsScriptingInterface.cpp b/interface/src/scripting/SettingsScriptingInterface.cpp index 00cdf009eb6..1f82c0956ee 100644 --- a/interface/src/scripting/SettingsScriptingInterface.cpp +++ b/interface/src/scripting/SettingsScriptingInterface.cpp @@ -56,7 +56,7 @@ void SettingsScriptingInterface::setValue(const QString& setting, const QVariant } // Make a deep-copy of the string. // Dangling pointers can occur with QStrings that are implicitly shared from a ScriptEngine. - QString deepCopy = QString::fromUtf16(setting.utf16()); + QString deepCopy = QString(setting.utf16()); Setting::Handle(deepCopy).set(value); emit valueChanged(setting, value); } diff --git a/interface/src/ui/JSConsole.cpp b/interface/src/ui/JSConsole.cpp index dcd3f02fd5a..4280d972810 100644 --- a/interface/src/ui/JSConsole.cpp +++ b/interface/src/ui/JSConsole.cpp @@ -64,7 +64,7 @@ void _writeLines(const QString& filename, const QList& lines) { file.open(QFile::WriteOnly); auto root = QJsonObject(); root["version"] = 1.0; - root["last-modified"] = QDateTime::currentDateTime().toTimeSpec(Qt::OffsetFromUTC).toString(Qt::ISODate); + root["last-modified"] = QDateTime::currentDateTime().toOffsetFromUtc(Qt::OffsetFromUTC).toString(Qt::ISODate); root[JSON_KEY] = QJsonArray::fromStringList(lines); auto json = QJsonDocument(root).toJson(); QTextStream(&file) << json; diff --git a/interface/src/ui/LogDialog.cpp b/interface/src/ui/LogDialog.cpp index 65a0d2ea37b..c6379cf8ed7 100644 --- a/interface/src/ui/LogDialog.cpp +++ b/interface/src/ui/LogDialog.cpp @@ -61,7 +61,7 @@ LogDialog::LogDialog(QWidget* parent, AbstractLoggerInterface* logger) : BaseLog _debugPrintBox->setCheckState(Qt::Checked); } _debugPrintBox->show(); - connect(_debugPrintBox, &QCheckBox::stateChanged, this, &LogDialog::handleDebugPrintBox); + connect(_debugPrintBox, &QCheckBox::checkStateChanged, this, &LogDialog::handleDebugPrintBox); _leftPad += DEBUG_CHECKBOX_WIDTH + BUTTON_MARGIN; _infoPrintBox = new QCheckBox("INFO", this); @@ -70,7 +70,7 @@ LogDialog::LogDialog(QWidget* parent, AbstractLoggerInterface* logger) : BaseLog _infoPrintBox->setCheckState(Qt::Checked); } _infoPrintBox->show(); - connect(_infoPrintBox, &QCheckBox::stateChanged, this, &LogDialog::handleInfoPrintBox); + connect(_infoPrintBox, &QCheckBox::checkStateChanged, this, &LogDialog::handleInfoPrintBox); _leftPad += INFO_CHECKBOX_WIDTH + BUTTON_MARGIN; _criticalPrintBox = new QCheckBox("CRITICAL", this); @@ -79,7 +79,7 @@ LogDialog::LogDialog(QWidget* parent, AbstractLoggerInterface* logger) : BaseLog _criticalPrintBox->setCheckState(Qt::Checked); } _criticalPrintBox->show(); - connect(_criticalPrintBox, &QCheckBox::stateChanged, this, &LogDialog::handleCriticalPrintBox); + connect(_criticalPrintBox, &QCheckBox::checkStateChanged, this, &LogDialog::handleCriticalPrintBox); _leftPad += CRITICAL_CHECKBOX_WIDTH + BUTTON_MARGIN; _warningPrintBox = new QCheckBox("WARNING", this); @@ -88,7 +88,7 @@ LogDialog::LogDialog(QWidget* parent, AbstractLoggerInterface* logger) : BaseLog _warningPrintBox->setCheckState(Qt::Checked); } _warningPrintBox->show(); - connect(_warningPrintBox, &QCheckBox::stateChanged, this, &LogDialog::handleWarningPrintBox); + connect(_warningPrintBox, &QCheckBox::checkStateChanged, this, &LogDialog::handleWarningPrintBox); _leftPad += WARNING_CHECKBOX_WIDTH + BUTTON_MARGIN; _suppressPrintBox = new QCheckBox("SUPPRESS", this); @@ -97,7 +97,7 @@ LogDialog::LogDialog(QWidget* parent, AbstractLoggerInterface* logger) : BaseLog _suppressPrintBox->setCheckState(Qt::Checked); } _suppressPrintBox->show(); - connect(_suppressPrintBox, &QCheckBox::stateChanged, this, &LogDialog::handleSuppressPrintBox); + connect(_suppressPrintBox, &QCheckBox::checkStateChanged, this, &LogDialog::handleSuppressPrintBox); _leftPad += SUPPRESS_CHECKBOX_WIDTH + BUTTON_MARGIN; _fatalPrintBox = new QCheckBox("FATAL", this); @@ -106,7 +106,7 @@ LogDialog::LogDialog(QWidget* parent, AbstractLoggerInterface* logger) : BaseLog _fatalPrintBox->setCheckState(Qt::Checked); } _fatalPrintBox->show(); - connect(_fatalPrintBox, &QCheckBox::stateChanged, this, &LogDialog::handleFatalPrintBox); + connect(_fatalPrintBox, &QCheckBox::checkStateChanged, this, &LogDialog::handleFatalPrintBox); _leftPad += FATAL_CHECKBOX_WIDTH + BUTTON_MARGIN; _unknownPrintBox = new QCheckBox("UNKNOWN", this); @@ -115,7 +115,7 @@ LogDialog::LogDialog(QWidget* parent, AbstractLoggerInterface* logger) : BaseLog _unknownPrintBox->setCheckState(Qt::Checked); } _unknownPrintBox->show(); - connect(_unknownPrintBox, &QCheckBox::stateChanged, this, &LogDialog::handleUnknownPrintBox); + connect(_unknownPrintBox, &QCheckBox::checkStateChanged, this, &LogDialog::handleUnknownPrintBox); _leftPad = MARGIN_LEFT; _filterDropdown = new QComboBox(this); @@ -156,7 +156,7 @@ LogDialog::LogDialog(QWidget* parent, AbstractLoggerInterface* logger) : BaseLog #ifdef Q_OS_WIN connect(_keepOnTopBox, &QCheckBox::stateChanged, qApp, &Application::recreateLogWindow); #else - connect(_keepOnTopBox, &QCheckBox::stateChanged, this, &LogDialog::handleKeepWindowOnTop); + connect(_keepOnTopBox, &QCheckBox::checkStateChanged, this, &LogDialog::handleKeepWindowOnTop); #endif _keepOnTopBox->show(); @@ -165,14 +165,14 @@ LogDialog::LogDialog(QWidget* parent, AbstractLoggerInterface* logger) : BaseLog _extraDebuggingBox->setCheckState(Qt::Checked); } _extraDebuggingBox->show(); - connect(_extraDebuggingBox, &QCheckBox::stateChanged, this, &LogDialog::handleExtraDebuggingCheckbox); + connect(_extraDebuggingBox, &QCheckBox::checkStateChanged, this, &LogDialog::handleExtraDebuggingCheckbox); _showSourceDebuggingBox = new QCheckBox("Show script sources", this); if (_logger->showSourceDebugging()) { _showSourceDebuggingBox->setCheckState(Qt::Checked); } _showSourceDebuggingBox->show(); - connect(_showSourceDebuggingBox, &QCheckBox::stateChanged, this, &LogDialog::handleShowSourceDebuggingCheckbox); + connect(_showSourceDebuggingBox, &QCheckBox::checkStateChanged, this, &LogDialog::handleShowSourceDebuggingCheckbox); _allLogsButton = new QPushButton("All Messages", this); // set object name for css styling diff --git a/interface/src/ui/Snapshot.cpp b/interface/src/ui/Snapshot.cpp index 48d41805b47..fbd45932a46 100644 --- a/interface/src/ui/Snapshot.cpp +++ b/interface/src/ui/Snapshot.cpp @@ -220,9 +220,9 @@ void Snapshot::takeNextSnapshot() { // Process six QImages if (_cubemapOutputFormat) { - QtConcurrent::run([this]() { convertToCubemap(); }); + QThreadPool::globalInstance()->start([this]() { convertToCubemap(); }); } else { - QtConcurrent::run([this]() { convertToEquirectangular(); }); + QThreadPool::globalInstance()->start([this]() { convertToEquirectangular(); }); } } } diff --git a/interface/src/ui/SnapshotAnimated.cpp b/interface/src/ui/SnapshotAnimated.cpp index 6d4c36afa1f..0709d0f99ef 100644 --- a/interface/src/ui/SnapshotAnimated.cpp +++ b/interface/src/ui/SnapshotAnimated.cpp @@ -94,7 +94,7 @@ void SnapshotAnimated::captureFrames() { emit SnapshotAnimated::snapshotAnimatedDM->processingGifStarted(SnapshotAnimated::snapshotStillPath); // Kick off the thread that'll pack the frames into the GIF - QtConcurrent::run(processFrames); + QThreadPool::globalInstance()->start(processFrames); // Stop the snapshot QTimer. This action by itself DOES NOT GUARANTEE // that the slot will not be called again in the future. // See: http://lists.qt-project.org/pipermail/qt-interest-old/2009-October/013926.html diff --git a/interface/src/webbrowser/WebBrowserSuggestionsEngine.cpp b/interface/src/webbrowser/WebBrowserSuggestionsEngine.cpp index 4e7b135cdff..5163874a5cc 100644 --- a/interface/src/webbrowser/WebBrowserSuggestionsEngine.cpp +++ b/interface/src/webbrowser/WebBrowserSuggestionsEngine.cpp @@ -67,7 +67,7 @@ void WebBrowserSuggestionsEngine::suggestionsFinished(QNetworkReply *reply) { QJsonDocument json = QJsonDocument::fromJson(response, &err); const QVariant res = json.toVariant(); - if (err.error != QJsonParseError::NoError || res.type() != QVariant::List) { + if (err.error != QJsonParseError::NoError || res.typeId() != QMetaType::QVariantList) { return; } diff --git a/libraries/audio-client/src/AudioClient.cpp b/libraries/audio-client/src/AudioClient.cpp index 5bdb2a1a147..c6b9285f564 100644 --- a/libraries/audio-client/src/AudioClient.cpp +++ b/libraries/audio-client/src/AudioClient.cpp @@ -368,7 +368,7 @@ AudioClient::AudioClient() { _checkDevicesTimer = new QTimer(this); const unsigned long DEVICE_CHECK_INTERVAL_MSECS = 2 * 1000; connect(_checkDevicesTimer, &QTimer::timeout, this, [=, this] { - QtConcurrent::run(QThreadPool::globalInstance(), [=, this] { + QThreadPool::globalInstance()->start([=, this] { checkDevices(); // On some systems (Ubuntu) checking all the audio devices can take more than 2 seconds. To // avoid consuming all of the thread pool, don't start the check interval until the previous @@ -382,7 +382,7 @@ AudioClient::AudioClient() { // start a thread to detect peak value changes _checkPeakValuesTimer = new QTimer(this); connect(_checkPeakValuesTimer, &QTimer::timeout, this, [this] { - QtConcurrent::run(QThreadPool::globalInstance(), [this] { checkPeakValues(); }); + QThreadPool::globalInstance()->start([this] { checkPeakValues(); }); }); const unsigned long PEAK_VALUES_CHECK_INTERVAL_MSECS = 50; _checkPeakValuesTimer->start(PEAK_VALUES_CHECK_INTERVAL_MSECS); diff --git a/libraries/audio/src/MixedProcessedAudioStream.cpp b/libraries/audio/src/MixedProcessedAudioStream.cpp index d1312edb956..a94dec10729 100644 --- a/libraries/audio/src/MixedProcessedAudioStream.cpp +++ b/libraries/audio/src/MixedProcessedAudioStream.cpp @@ -56,7 +56,7 @@ int MixedProcessedAudioStream::lostAudioData(int numPackets) { emit processSamples(decodedBuffer, outputBuffer); _ringBuffer.writeData(outputBuffer.data(), outputBuffer.size()); - qCDebug(audiostream, "Wrote %d samples to buffer (%d available)", outputBuffer.size() / (int)sizeof(int16_t), getSamplesAvailable()); + qCDebug(audiostream, "Wrote %d samples to buffer (%d available)", (int)outputBuffer.size() / (int)sizeof(int16_t), getSamplesAvailable()); } return 0; } @@ -81,7 +81,7 @@ int MixedProcessedAudioStream::parseAudioData(const QByteArray& packetAfterStrea emit processSamples(decodedBuffer, outputBuffer); _ringBuffer.writeData(outputBuffer.data(), outputBuffer.size()); - qCDebug(audiostream, "Wrote %d samples to buffer (%d available)", outputBuffer.size() / (int)sizeof(int16_t), getSamplesAvailable()); + qCDebug(audiostream, "Wrote %d samples to buffer (%d available)", (int)outputBuffer.size() / (int)sizeof(int16_t), getSamplesAvailable()); return packetAfterStreamProperties.size(); } diff --git a/libraries/baking/src/TextureBaker.cpp b/libraries/baking/src/TextureBaker.cpp index 02423d41486..0ae21a88ee5 100644 --- a/libraries/baking/src/TextureBaker.cpp +++ b/libraries/baking/src/TextureBaker.cpp @@ -136,7 +136,7 @@ void TextureBaker::processTexture() { // so we add that to the processed texture before handling it off to be serialized QCryptographicHash hasher(QCryptographicHash::Md5); hasher.addData(_originalTexture); - hasher.addData((const char*)&_textureType, sizeof(_textureType)); + hasher.addData(QByteArrayView((const char*)&_textureType, sizeof(_textureType))); auto hashData = hasher.result(); std::string hash = hashData.toHex().toStdString(); diff --git a/libraries/display-plugins/src/display-plugins/CompositorHelper.cpp b/libraries/display-plugins/src/display-plugins/CompositorHelper.cpp index ed16b5875c8..e9a9a1c0cf3 100644 --- a/libraries/display-plugins/src/display-plugins/CompositorHelper.cpp +++ b/libraries/display-plugins/src/display-plugins/CompositorHelper.cpp @@ -175,7 +175,7 @@ QPointF CompositorHelper::getMouseEventPosition(QMouseEvent* event) { QMutexLocker locker(&_reticleLock); return QPointF(_reticlePositionInHMD.x, _reticlePositionInHMD.y); } - return event->localPos(); + return event->position(); } static bool isWindowActive() { diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.cpp b/libraries/entities-renderer/src/EntityTreeRenderer.cpp index 6e36c360c63..b299f1ba596 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.cpp +++ b/libraries/entities-renderer/src/EntityTreeRenderer.cpp @@ -228,7 +228,7 @@ void EntityTreeRenderer::resetPersistentEntitiesScriptEngine() { // Clear the pointer before lambda is run on another thread. _persistentEntitiesScriptManager.reset(); if (scriptManager) { - QtConcurrent::run([manager = scriptManager] { + QThreadPool::globalInstance()->start([manager = scriptManager] { manager->unloadAllEntityScripts(true); manager->stop(); manager->waitTillDoneRunning(); @@ -262,7 +262,7 @@ void EntityTreeRenderer::resetNonPersistentEntitiesScriptEngine() { // Release the pointer as soon as possible. _nonPersistentEntitiesScriptManager.reset(); if (scriptManager) { - QtConcurrent::run([manager = scriptManager] { + QThreadPool::globalInstance()->start([manager = scriptManager] { manager->unloadAllEntityScripts(true); manager->stop(); manager->waitTillDoneRunning(); @@ -932,7 +932,7 @@ QUuid EntityTreeRenderer::mousePressEvent(QMouseEvent* event) { PerformanceTimer perfTimer("EntityTreeRenderer::mousePressEvent"); auto entityScriptingInterface = DependencyManager::get(); - PickRay ray = _viewState->computePickRay(event->x(), event->y()); + PickRay ray = _viewState->computePickRay(event->position().x(), event->position().y()); RayToEntityIntersectionResult rayPickResult = _getPrevRayPickResultOperator(_mouseRayPickID); EntityItemPointer entity; if (rayPickResult.intersects && (entity = getTree()->findEntityByID(rayPickResult.entityID))) { @@ -966,7 +966,7 @@ void EntityTreeRenderer::mouseDoublePressEvent(QMouseEvent* event) { PerformanceTimer perfTimer("EntityTreeRenderer::mouseDoublePressEvent"); auto entityScriptingInterface = DependencyManager::get(); - PickRay ray = _viewState->computePickRay(event->x(), event->y()); + PickRay ray = _viewState->computePickRay(event->position().x(), event->position().y()); RayToEntityIntersectionResult rayPickResult = _getPrevRayPickResultOperator(_mouseRayPickID); EntityItemPointer entity; if (rayPickResult.intersects && (entity = getTree()->findEntityByID(rayPickResult.entityID))) { @@ -998,7 +998,7 @@ void EntityTreeRenderer::mouseReleaseEvent(QMouseEvent* event) { PerformanceTimer perfTimer("EntityTreeRenderer::mouseReleaseEvent"); auto entityScriptingInterface = DependencyManager::get(); - PickRay ray = _viewState->computePickRay(event->x(), event->y()); + PickRay ray = _viewState->computePickRay(event->position().x(), event->position().y()); RayToEntityIntersectionResult rayPickResult = _getPrevRayPickResultOperator(_mouseRayPickID); EntityItemPointer entity; if (rayPickResult.intersects && (entity = getTree()->findEntityByID(rayPickResult.entityID))) { @@ -1043,7 +1043,7 @@ void EntityTreeRenderer::mouseMoveEvent(QMouseEvent* event) { PerformanceTimer perfTimer("EntityTreeRenderer::mouseMoveEvent"); auto entityScriptingInterface = DependencyManager::get(); - PickRay ray = _viewState->computePickRay(event->x(), event->y()); + PickRay ray = _viewState->computePickRay(event->position().x(), event->position().y()); RayToEntityIntersectionResult rayPickResult = _getPrevRayPickResultOperator(_mouseRayPickID); EntityItemPointer entity; if (rayPickResult.intersects && (entity = getTree()->findEntityByID(rayPickResult.entityID))) { diff --git a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp index 35b9364fb79..f308e81262d 100644 --- a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp @@ -1018,7 +1018,7 @@ void RenderablePolyVoxEntityItem::uncompressVolumeData() { voxelData = _voxelData; }); - QtConcurrent::run([=, this] { + QThreadPool::globalInstance()->start([=, this] { QDataStream reader(voxelData); quint16 voxelXSize, voxelYSize, voxelZSize; reader >> voxelXSize; @@ -1094,7 +1094,7 @@ void RenderablePolyVoxEntityItem::compressVolumeDataAndSendEditPacket() { qDebug() << "Compressing voxel and sending data packet"; #endif - QtConcurrent::run([voxelXSize, voxelYSize, voxelZSize, entity] { + QThreadPool::globalInstance()->start([voxelXSize, voxelYSize, voxelZSize, entity] { auto polyVoxEntity = std::static_pointer_cast(entity); QByteArray uncompressedData = polyVoxEntity->volDataToArray(voxelXSize, voxelYSize, voxelZSize); @@ -1310,7 +1310,7 @@ void RenderablePolyVoxEntityItem::recomputeMesh() { auto entity = std::static_pointer_cast(getThisPointer()); - QtConcurrent::run([entity, voxelSurfaceStyle] { + QThreadPool::globalInstance()->start([entity, voxelSurfaceStyle] { graphics::MeshPointer mesh(std::make_shared()); // A mesh object to hold the result of surface extraction @@ -1409,7 +1409,7 @@ void RenderablePolyVoxEntityItem::computeShapeInfoWorker() { mesh = _mesh; }); - QtConcurrent::run([entity, voxelSurfaceStyle, voxelVolumeSize, mesh] { + QThreadPool::globalInstance()->start([entity, voxelSurfaceStyle, voxelVolumeSize, mesh] { auto polyVoxEntity = std::static_pointer_cast(entity); QVector> pointCollection; AABox box; diff --git a/libraries/entities/src/EntityDynamicInterface.cpp b/libraries/entities/src/EntityDynamicInterface.cpp index c44c21d32ef..223e02c6388 100644 --- a/libraries/entities/src/EntityDynamicInterface.cpp +++ b/libraries/entities/src/EntityDynamicInterface.cpp @@ -217,7 +217,7 @@ glm::vec3 EntityDynamicInterface::extractVec3Argument(QString objectName, QVaria } QVariant resultV = arguments[argumentName]; - if (resultV.type() != (QVariant::Type) QMetaType::QVariantMap) { + if (resultV.typeId() != QMetaType::QVariantMap) { qCDebug(entities) << objectName << "argument" << argumentName << "must be a map"; ok = false; return glm::vec3(0.0f); @@ -266,7 +266,7 @@ glm::quat EntityDynamicInterface::extractQuatArgument(QString objectName, QVaria } QVariant resultV = arguments[argumentName]; - if (resultV.type() != (QVariant::Type) QMetaType::QVariantMap) { + if (resultV.typeId() != QMetaType::QVariantMap) { qCDebug(entities) << objectName << "argument" << argumentName << "must be a map, not" << resultV.typeName(); ok = false; return glm::quat(); diff --git a/libraries/input-plugins/src/input-plugins/KeyboardMouseDevice.cpp b/libraries/input-plugins/src/input-plugins/KeyboardMouseDevice.cpp index be6a12aa88e..e2f29f79d4d 100644 --- a/libraries/input-plugins/src/input-plugins/KeyboardMouseDevice.cpp +++ b/libraries/input-plugins/src/input-plugins/KeyboardMouseDevice.cpp @@ -204,7 +204,7 @@ glm::vec2 evalAverageTouchPoints(const QList& points) { glm::vec2 averagePoint(0.0f); if (points.count() > 0) { for (auto& point : points) { - averagePoint += glm::vec2(point.pos().x(), point.pos().y()); + averagePoint += glm::vec2(point.position().x(), point.position().y()); } averagePoint /= (float)(points.count()); } @@ -245,7 +245,7 @@ void KeyboardMouseDevice::touchGestureEvent(const QGestureEvent* event) { void KeyboardMouseDevice::touchBeginEvent(const QTouchEvent* event) { if (_enableTouch) { _isTouching = event->touchPointStates().testFlag(QEventPoint::State::Pressed); - _lastTouch = evalAverageTouchPoints(event->touchPoints()); + _lastTouch = evalAverageTouchPoints(event->points()); _lastTouchTime = _clock.now(); } } @@ -253,14 +253,14 @@ void KeyboardMouseDevice::touchBeginEvent(const QTouchEvent* event) { void KeyboardMouseDevice::touchEndEvent(const QTouchEvent* event) { if (_enableTouch) { _isTouching = false; - _lastTouch = evalAverageTouchPoints(event->touchPoints()); + _lastTouch = evalAverageTouchPoints(event->points()); _lastTouchTime = _clock.now(); } } void KeyboardMouseDevice::touchUpdateEvent(const QTouchEvent* event) { if (_enableTouch) { - auto currentPos = evalAverageTouchPoints(event->touchPoints()); + auto currentPos = evalAverageTouchPoints(event->points()); _lastTouchTime = _clock.now(); if (!_isTouching) { diff --git a/libraries/input-plugins/src/input-plugins/TouchscreenDevice.cpp b/libraries/input-plugins/src/input-plugins/TouchscreenDevice.cpp index c6a22a60ed2..29ca9520fbc 100644 --- a/libraries/input-plugins/src/input-plugins/TouchscreenDevice.cpp +++ b/libraries/input-plugins/src/input-plugins/TouchscreenDevice.cpp @@ -73,8 +73,8 @@ void TouchscreenDevice::InputDevice::focusOutEvent() { } void TouchscreenDevice::touchBeginEvent(const QTouchEvent* event) { - const QTouchEvent::TouchPoint& point = event->touchPoints().at(0); - _firstTouchVec = glm::vec2(point.pos().x(), point.pos().y()); + const QTouchEvent::TouchPoint& point = event->points().at(0); + _firstTouchVec = glm::vec2(point.position().x(), point.position().y()); KeyboardMouseDevice::enableTouch(false); // QT6TODO: I'm not sure how to do this part yet /*QScreen* eventScreen = event->window()->screen(); @@ -92,14 +92,15 @@ void TouchscreenDevice::touchEndEvent(const QTouchEvent* event) { } void TouchscreenDevice::touchUpdateEvent(const QTouchEvent* event) { - const QTouchEvent::TouchPoint& point = event->touchPoints().at(0); - _currentTouchVec = glm::vec2(point.pos().x(), point.pos().y()); - _touchPointCount = event->touchPoints().count(); + const QTouchEvent::TouchPoint& point = event->points().at(0); + _currentTouchVec = glm::vec2(point.position().x(), point.position().y()); + _touchPointCount = static_cast(event->points().count()); } void TouchscreenDevice::touchGestureEvent(const QGestureEvent* event) { if (QGesture* gesture = event->gesture(Qt::PinchGesture)) { - QPinchGesture* pinch = static_cast(gesture); + QPinchGesture* pinch = dynamic_cast(gesture); + Q_ASSERT(pinch); _pinchScale = pinch->totalScaleFactor(); } } diff --git a/libraries/input-plugins/src/input-plugins/TouchscreenVirtualPadDevice.cpp b/libraries/input-plugins/src/input-plugins/TouchscreenVirtualPadDevice.cpp index 56a435ebf45..12069042458 100644 --- a/libraries/input-plugins/src/input-plugins/TouchscreenVirtualPadDevice.cpp +++ b/libraries/input-plugins/src/input-plugins/TouchscreenVirtualPadDevice.cpp @@ -211,11 +211,11 @@ void TouchscreenVirtualPadDevice::InputDevice::focusOutEvent() { void TouchscreenVirtualPadDevice::debugPoints(const QTouchEvent* event, QString who) { // convert the touch points into an average - const QList& tPoints = event->touchPoints(); + const QList& tPoints = event->points(); QVector points; int touchPoints = tPoints.count(); for (int i = 0; i < touchPoints; ++i) { - glm::vec2 thisPoint(tPoints[i].pos().x(), tPoints[i].pos().y()); + glm::vec2 thisPoint(tPoints[i].position().x(), tPoints[i].position().y()); points << thisPoint; } // QT6TODO: I have no idea how to do this part yet @@ -288,9 +288,9 @@ void TouchscreenVirtualPadDevice::touchUpdateEvent(const QTouchEvent* event) { viewTouchEnd(); return; } - _touchPointCount = event->touchPoints().count(); + _touchPointCount = static_cast(event->points().count()); - const QList& tPoints = event->touchPoints(); + const QList& tPoints = event->points(); bool moveTouchFound = false; bool viewTouchFound = false; @@ -304,8 +304,8 @@ void TouchscreenVirtualPadDevice::touchUpdateEvent(const QTouchEvent* event) { std::map unusedTouchesInEvent; for (int i = 0; i < _touchPointCount; ++i) { - thisPoint.x = tPoints[i].pos().x(); - thisPoint.y = tPoints[i].pos().y(); + thisPoint.x = tPoints[i].position().x(); + thisPoint.y = tPoints[i].position().y(); thisPointId = tPoints[i].id(); if (!moveTouchFound && _moveHasValidTouch && _moveCurrentTouchId == thisPointId) { @@ -358,8 +358,8 @@ void TouchscreenVirtualPadDevice::touchUpdateEvent(const QTouchEvent* event) { if (idxMoveStartingPointCandidate != -1) { _moveCurrentTouchId = tPoints[idxMoveStartingPointCandidate].id(); _unusedTouches.erase(_moveCurrentTouchId); - thisPoint.x = tPoints[idxMoveStartingPointCandidate].pos().x(); - thisPoint.y = tPoints[idxMoveStartingPointCandidate].pos().y(); + thisPoint.x = tPoints[idxMoveStartingPointCandidate].position().x(); + thisPoint.y = tPoints[idxMoveStartingPointCandidate].position().y(); moveTouchBegin(thisPoint); } else { moveTouchEnd(); @@ -369,8 +369,8 @@ void TouchscreenVirtualPadDevice::touchUpdateEvent(const QTouchEvent* event) { if (idxViewStartingPointCandidate != -1) { _viewCurrentTouchId = tPoints[idxViewStartingPointCandidate].id(); _unusedTouches.erase(_viewCurrentTouchId); - thisPoint.x = tPoints[idxViewStartingPointCandidate].pos().x(); - thisPoint.y = tPoints[idxViewStartingPointCandidate].pos().y(); + thisPoint.x = tPoints[idxViewStartingPointCandidate].position().x(); + thisPoint.y = tPoints[idxViewStartingPointCandidate].position().y(); viewTouchBegin(thisPoint); } else { viewTouchEnd(); @@ -593,8 +593,8 @@ void TouchscreenVirtualPadDevice::TouchscreenButtonsManager::processBeginOrEnd( if (button._candidatePointIdx != -1) { button.currentTouchId = tPoints[button._candidatePointIdx].id(); globalUnusedTouches.erase(button.currentTouchId); - thisPoint.x = tPoints[button._candidatePointIdx].pos().x(); - thisPoint.y = tPoints[button._candidatePointIdx].pos().y(); + thisPoint.x = tPoints[button._candidatePointIdx].position().x(); + thisPoint.y = tPoints[button._candidatePointIdx].position().y(); button.touchBegin(thisPoint); } else { if (button.hasValidTouch) { diff --git a/libraries/material-networking/src/material-networking/TextureCache.cpp b/libraries/material-networking/src/material-networking/TextureCache.cpp index 51bbfdfbb3f..f13191f03b3 100644 --- a/libraries/material-networking/src/material-networking/TextureCache.cpp +++ b/libraries/material-networking/src/material-networking/TextureCache.cpp @@ -550,7 +550,7 @@ void NetworkTexture::makeRequest() { if (isLocalUrl(_activeUrl)) { auto self = _self; - QtConcurrent::run(QThreadPool::globalInstance(), [self] { + QThreadPool::globalInstance()->start([self] { auto resource = self.lock(); if (!resource) { return; @@ -849,7 +849,7 @@ void NetworkTexture::ktxMipRequestFinished() { auto mipLevel = _ktxMipLevelRangeInFlight.first; auto texture = _textureSource->getGPUTexture(); DependencyManager::get()->incrementStat("PendingProcessing"); - QtConcurrent::run(QThreadPool::globalInstance(), [self, data, mipLevel, url, texture] { + QThreadPool::globalInstance()->start([self, data, mipLevel, url, texture] { PROFILE_RANGE_EX(resource_parse_image, "NetworkTexture - Processing Mip Data", 0xffff0000, 0, { { "url", url.toString() } }); DependencyManager::get()->decrementStat("PendingProcessing"); CounterStat counter("Processing"); @@ -917,7 +917,7 @@ void NetworkTexture::handleFinishedInitialLoad() { auto self = _self; auto url = _url; DependencyManager::get()->incrementStat("PendingProcessing"); - QtConcurrent::run(QThreadPool::globalInstance(), [self, ktxHeaderData, ktxHighMipData, url] { + QThreadPool::globalInstance()->start([self, ktxHeaderData, ktxHighMipData, url] { PROFILE_RANGE_EX(resource_parse_image, "NetworkTexture - Processing Initial Data", 0xffff0000, 0, { { "url", url.toString() } }); DependencyManager::get()->decrementStat("PendingProcessing"); CounterStat counter("Processing"); diff --git a/libraries/model-baker/src/model-baker/PrepareJointsTask.cpp b/libraries/model-baker/src/model-baker/PrepareJointsTask.cpp index efbcc6360c6..9685d4369e2 100644 --- a/libraries/model-baker/src/model-baker/PrepareJointsTask.cpp +++ b/libraries/model-baker/src/model-baker/PrepareJointsTask.cpp @@ -16,7 +16,7 @@ QMap getJointNameMapping(const hifi::VariantMultiHash& mapping) { static const QString JOINT_NAME_MAPPING_FIELD = "jointMap"; QMap hfmToHifiJointNameMap; - if (!mapping.isEmpty() && mapping.contains(JOINT_NAME_MAPPING_FIELD) && mapping[JOINT_NAME_MAPPING_FIELD].type() == QVariant::Hash) { + if (!mapping.isEmpty() && mapping.contains(JOINT_NAME_MAPPING_FIELD) && mapping[JOINT_NAME_MAPPING_FIELD].typeId() == QMetaType::QVariantHash) { auto jointNames = mapping[JOINT_NAME_MAPPING_FIELD].toHash(); for (auto itr = jointNames.begin(); itr != jointNames.end(); itr++) { hfmToHifiJointNameMap.insert(itr.key(), itr.value().toString()); @@ -30,7 +30,9 @@ QMap getJointRotationOffsets(const hifi::VariantMultiHash& m QMap jointRotationOffsets; static const QString JOINT_ROTATION_OFFSET_FIELD = "jointRotationOffset"; static const QString JOINT_ROTATION_OFFSET2_FIELD = "jointRotationOffset2"; - if (!mapping.isEmpty() && ((mapping.contains(JOINT_ROTATION_OFFSET_FIELD) && mapping[JOINT_ROTATION_OFFSET_FIELD].type() == QVariant::Hash) || (mapping.contains(JOINT_ROTATION_OFFSET2_FIELD) && mapping[JOINT_ROTATION_OFFSET2_FIELD].type() == QVariant::Hash))) { + if (!mapping.isEmpty() && ((mapping.contains(JOINT_ROTATION_OFFSET_FIELD) && mapping[JOINT_ROTATION_OFFSET_FIELD].typeId() == QMetaType::QVariantHash) + || (mapping.contains(JOINT_ROTATION_OFFSET2_FIELD) && mapping[JOINT_ROTATION_OFFSET2_FIELD].typeId() == QMetaType::QVariantHash))) { + QHash offsets; if (mapping.contains(JOINT_ROTATION_OFFSET_FIELD)) { offsets = mapping[JOINT_ROTATION_OFFSET_FIELD].toHash(); diff --git a/libraries/model-serializers/src/FSTReader.cpp b/libraries/model-serializers/src/FSTReader.cpp index c63b6224873..058c25b63a0 100644 --- a/libraries/model-serializers/src/FSTReader.cpp +++ b/libraries/model-serializers/src/FSTReader.cpp @@ -206,7 +206,7 @@ FSTReader::ModelType FSTReader::predictModelType(const hifi::VariantMultiHash& m QVariantHash joints; - if (mapping.contains("joint") && mapping.value("joint").type() == QVariant::Hash) { + if (mapping.contains("joint") && mapping.value("joint").typeId() == QMetaType::QVariantHash) { joints = mapping.value("joint").toHash(); } diff --git a/libraries/procedural/src/procedural/ProceduralMaterialCache.cpp b/libraries/procedural/src/procedural/ProceduralMaterialCache.cpp index 70d0ece58bd..2f5815466c6 100644 --- a/libraries/procedural/src/procedural/ProceduralMaterialCache.cpp +++ b/libraries/procedural/src/procedural/ProceduralMaterialCache.cpp @@ -1130,7 +1130,7 @@ void NetworkMaterial::setTextures(const QVariantMap& textureMap) { bool NetworkMaterial::isMissingTexture() { for (auto& networkTexture : _textures) { - auto& texture = networkTexture.second.texture; + auto texture = networkTexture.second.texture; if (!texture) { continue; } diff --git a/libraries/qml/src/qml/OffscreenSurface.cpp b/libraries/qml/src/qml/OffscreenSurface.cpp index e9cdf4454d1..13ddd806dc0 100644 --- a/libraries/qml/src/qml/OffscreenSurface.cpp +++ b/libraries/qml/src/qml/OffscreenSurface.cpp @@ -190,8 +190,8 @@ bool OffscreenSurface::eventFilter(QObject* originalDestination, QEvent* event) } case QEvent::MouseMove: { QMouseEvent* mouseEvent = static_cast(event); - QPointF transformedPos = mapToVirtualScreen(mouseEvent->localPos()); - QMouseEvent mappedEvent(mouseEvent->type(), transformedPos, mouseEvent->screenPos(), mouseEvent->button(), + QPointF transformedPos = mapToVirtualScreen(mouseEvent->position()); + QMouseEvent mappedEvent(mouseEvent->type(), transformedPos, mouseEvent->globalPosition(), mouseEvent->button(), mouseEvent->buttons(), mouseEvent->modifiers()); mappedEvent.ignore(); if (QCoreApplication::sendEvent(_sharedObject->getWindow(), &mappedEvent)) { diff --git a/libraries/script-engine/src/MouseEvent.cpp b/libraries/script-engine/src/MouseEvent.cpp index 20f525e61b6..c3569a37a76 100644 --- a/libraries/script-engine/src/MouseEvent.cpp +++ b/libraries/script-engine/src/MouseEvent.cpp @@ -32,8 +32,8 @@ MouseEvent::MouseEvent() : MouseEvent::MouseEvent(const QMouseEvent& event) : - x(event.x()), - y(event.y()), + x(static_cast(event.position().x())), + y(static_cast(event.position().y())), isLeftButton(event.buttons().testFlag(Qt::LeftButton)), isRightButton(event.buttons().testFlag(Qt::RightButton)), isMiddleButton(event.buttons().testFlag(Qt::MiddleButton)), diff --git a/libraries/script-engine/src/ScriptEngines.cpp b/libraries/script-engine/src/ScriptEngines.cpp index 597bab81f4e..2e3e26b5b61 100644 --- a/libraries/script-engine/src/ScriptEngines.cpp +++ b/libraries/script-engine/src/ScriptEngines.cpp @@ -466,7 +466,7 @@ QStringList ScriptEngines::getRunningScripts() { } void ScriptEngines::stopAllScripts(bool restart) { - QtConcurrent::run([this, restart] { + QThreadPool::globalInstance()->start([this, restart] { QHash scriptManagersHashCopy; { diff --git a/libraries/script-engine/src/ScriptValueUtils.cpp b/libraries/script-engine/src/ScriptValueUtils.cpp index da48cba29f3..f227b9d1d32 100644 --- a/libraries/script-engine/src/ScriptValueUtils.cpp +++ b/libraries/script-engine/src/ScriptValueUtils.cpp @@ -794,7 +794,7 @@ bool qColorFromScriptValue(const ScriptValue& object, QColor& color) { color.setRgb(object.toUInt32()); return true; } else if (object.isString()) { - color.setNamedColor(object.toString()); + color = QColor::fromString(object.toString()); return true; } else if (object.isArray()) { auto length = object.property("length").toInt32(); diff --git a/libraries/script-engine/src/TouchEvent.cpp b/libraries/script-engine/src/TouchEvent.cpp index c4352698568..f21f2a6a86a 100644 --- a/libraries/script-engine/src/TouchEvent.cpp +++ b/libraries/script-engine/src/TouchEvent.cpp @@ -73,7 +73,7 @@ float angleBetweenPoints(const glm::vec2& a, const glm::vec2& b ) { void TouchEvent::initWithQTouchEvent(const QTouchEvent& event) { // convert the touch points into an average - const QList& tPoints = event.touchPoints(); + const QList& tPoints = event.points(); float touchAvgX = 0.0f; float touchAvgY = 0.0f; touchPoints = tPoints.count(); diff --git a/libraries/script-engine/src/VariantMapToScriptValue.cpp b/libraries/script-engine/src/VariantMapToScriptValue.cpp index b40f9fd735c..c27736b0984 100644 --- a/libraries/script-engine/src/VariantMapToScriptValue.cpp +++ b/libraries/script-engine/src/VariantMapToScriptValue.cpp @@ -18,26 +18,26 @@ #include "SharedLogging.h" ScriptValue variantToScriptValue(QVariant& qValue, ScriptEngine& scriptEngine) { - switch(qValue.type()) { - case QVariant::Bool: + switch(qValue.typeId()) { + case QMetaType::Bool: return scriptEngine.newValue(qValue.toBool()); break; - case QVariant::Int: + case QMetaType::Int: return scriptEngine.newValue(qValue.toInt()); break; - case QVariant::Double: + case QMetaType::Double: return scriptEngine.newValue(qValue.toDouble()); break; - case QVariant::String: - case QVariant::Url: + case QMetaType::QString: + case QMetaType::QUrl: return scriptEngine.newValue(qValue.toString()); break; - case QVariant::Map: { + case QMetaType::QVariantMap: { QVariantMap childMap = qValue.toMap(); return variantMapToScriptValue(childMap, scriptEngine); break; } - case QVariant::List: { + case QMetaType::QVariantList: { QVariantList childList = qValue.toList(); return variantListToScriptValue(childList, scriptEngine); break; diff --git a/libraries/script-engine/src/WebSocketClass.cpp b/libraries/script-engine/src/WebSocketClass.cpp index dada3029510..5e135d9d358 100644 --- a/libraries/script-engine/src/WebSocketClass.cpp +++ b/libraries/script-engine/src/WebSocketClass.cpp @@ -51,7 +51,7 @@ void WebSocketClass::initialize() { connect(_webSocket, &QWebSocket::textMessageReceived, this, &WebSocketClass::handleOnMessage); connect(_webSocket, &QWebSocket::binaryMessageReceived, this, &WebSocketClass::handleOnBinaryMessage); connect(_webSocket, &QWebSocket::connected, this, &WebSocketClass::handleOnOpen); - connect(_webSocket, static_cast(&QWebSocket::error), this, + connect(_webSocket, static_cast(&QWebSocket::errorOccurred), this, &WebSocketClass::handleOnError); _binaryType = QStringLiteral("arraybuffer"); } diff --git a/libraries/script-engine/src/v8/ScriptEngineV8.cpp b/libraries/script-engine/src/v8/ScriptEngineV8.cpp index 6b364cab6ba..2edc7d01c59 100644 --- a/libraries/script-engine/src/v8/ScriptEngineV8.cpp +++ b/libraries/script-engine/src/v8/ScriptEngineV8.cpp @@ -1424,7 +1424,7 @@ ScriptValue ScriptEngineV8::create(int type, const void* ptr) { v8::Context::Scope contextScope(getContext()); QVariant variant(QMetaType(type), ptr); V8ScriptValue scriptValue = castVariantToValue(variant); - return ScriptValue(new ScriptValueV8Wrapper(this, std::move(scriptValue))); + return {new ScriptValueV8Wrapper(this, std::move(scriptValue))}; } QVariant ScriptEngineV8::convert(const ScriptValue& value, int typeId) { @@ -1433,21 +1433,21 @@ QVariant ScriptEngineV8::convert(const ScriptValue& value, int typeId) { v8::Context::Scope contextScope(getContext()); ScriptValueV8Wrapper* unwrapped = ScriptValueV8Wrapper::unwrap(value); if (unwrapped == nullptr) { - return QVariant(); + return {}; } QVariant var; if (!castValueToVariant(unwrapped->toV8Value(), var, typeId)) { - return QVariant(); + return {}; } int destType = var.userType(); if (destType != typeId) { - var.convert(typeId); // if conversion fails then var is set to QVariant() + var.convert(QMetaType(typeId)); // if conversion fails then var is set to QVariant() } return var; - return QVariant(); + return {}; } void ScriptEngineV8::compileTest() { diff --git a/libraries/script-engine/src/v8/ScriptEngineV8_cast.cpp b/libraries/script-engine/src/v8/ScriptEngineV8_cast.cpp index 6eb5f7efd72..d58994cc9ac 100644 --- a/libraries/script-engine/src/v8/ScriptEngineV8_cast.cpp +++ b/libraries/script-engine/src/v8/ScriptEngineV8_cast.cpp @@ -300,7 +300,7 @@ bool ScriptEngineV8::castValueToVariant(const V8ScriptValue& v8Val, QVariant& de if (obj) { for (const QMetaObject* metaObject = obj->metaObject(); metaObject; metaObject = metaObject->superClass()) { QByteArray typeName = QByteArray(metaObject->className()) + "*"; - int typeId = QMetaType::type(typeName.constData()); + int typeId = QMetaType::fromName(typeName).id(); if (typeId != QMetaType::UnknownType) { destTypeId = typeId; break; @@ -382,7 +382,7 @@ bool ScriptEngineV8::castValueToVariant(const V8ScriptValue& v8Val, QVariant& de } } errorMessage = QString() + "Conversion failure: " + QString(*v8::String::Utf8Value(_v8Isolate, val->ToDetailString(getConstContext()).ToLocalChecked())) - + "to variant. Destination type: " + QMetaType::typeName(destTypeId) +" details: "+ scriptValueDebugDetailsV8(v8Val); + + "to variant. Destination type: " + QMetaType(destTypeId).name() +" details: "+ scriptValueDebugDetailsV8(v8Val); qCDebug(scriptengine_v8) << errorMessage; // V8TODO: this doesn't seem to be necessary anymore but I'm keeping it until all the API is tested @@ -501,12 +501,13 @@ bool ScriptEngineV8::castValueToVariant(const V8ScriptValue& v8Val, QVariant& de } } errorMessage = QString() + "Conversion to variant failed: " + QString(*v8::String::Utf8Value(_v8Isolate, val->ToDetailString(getConstContext()).ToLocalChecked())) - + " Destination type: " + QMetaType::typeName(destTypeId) + " Value details: " + scriptValueDebugDetailsV8(v8Val); + + " Destination type: " + QMetaType(destTypeId).name() + " Value details: " + scriptValueDebugDetailsV8(v8Val); qCDebug(scriptengine_v8) << errorMessage; return false; default: // check to see if this is a pointer to a QObject-derived object - if (QMetaType::typeFlags(destTypeId) & (QMetaType::PointerToQObject | QMetaType::TrackingPointerToQObject)) { + const auto destMetaType = QMetaType(destTypeId); + if (destMetaType.flags() & (QMetaType::PointerToQObject | QMetaType::TrackingPointerToQObject)) { /* Do we really want to permit regular passing of nullptr to native functions? if (!val.isValid() || val.isUndefined() || val.isNull()) { dest = QVariant::fromValue(nullptr); @@ -514,7 +515,7 @@ bool ScriptEngineV8::castValueToVariant(const V8ScriptValue& v8Val, QVariant& de }*/ QObject* obj = ScriptObjectV8Proxy::unwrap(v8Val); if (!obj) return false; - const QMetaObject* destMeta = QMetaType::metaObjectForType(destTypeId); + const QMetaObject* destMeta = destMetaType.metaObject(); Q_ASSERT(destMeta); obj = destMeta->cast(obj); if (!obj) return false; @@ -540,7 +541,7 @@ bool ScriptEngineV8::castValueToVariant(const V8ScriptValue& v8Val, QVariant& de // last chance, just convert it to a variant (impossible on V8) // V8TODO errorMessage = QString() + "Conversion failure: " + QString(*v8::String::Utf8Value(_v8Isolate, val->ToDetailString(getConstContext()).ToLocalChecked())) - + "to variant. Destination type: " + QMetaType::typeName(destTypeId); + + "to variant. Destination type: " + destMetaType.name(); qCDebug(scriptengine_v8) << errorMessage; if(destTypeId == QMetaType::QVariant) { Q_ASSERT(false); @@ -550,7 +551,7 @@ bool ScriptEngineV8::castValueToVariant(const V8ScriptValue& v8Val, QVariant& de } } - return destTypeId == QMetaType::UnknownType || dest.userType() == destTypeId || dest.convert(destTypeId); + return destTypeId == QMetaType::UnknownType || dest.userType() == destTypeId || dest.convert(QMetaType(destTypeId)); } bool ScriptEngineV8::convertJSArrayToVariant(v8::Local array, QVariant &dest) { @@ -731,7 +732,7 @@ V8ScriptValue ScriptEngineV8::castVariantToValue(const QVariant& val) { default: // check to see if this is a pointer to a QObject-derived object // WeakPointerToQObject and SharedPointerToQObject were causing trouble here because some values are handled by custom prototypes instead - if (QMetaType::typeFlags(valTypeId) & (QMetaType::PointerToQObject | QMetaType::TrackingPointerToQObject)) { + if (QMetaType(valTypeId).flags() & (QMetaType::PointerToQObject | QMetaType::TrackingPointerToQObject)) { QObject* obj = val.value(); if (obj == nullptr) return V8ScriptValue(this, v8::Null(_v8Isolate)); //V8TODO: what should be the ownership in this case? @@ -748,7 +749,7 @@ V8ScriptValue ScriptEngineV8::castVariantToValue(const QVariant& val) { } // just do a generic variant //V8TODO - qCDebug(scriptengine_v8) << "ScriptEngineV8::castVariantToValue failed for " << QMetaType::typeName(valTypeId); + qCDebug(scriptengine_v8) << "ScriptEngineV8::castVariantToValue failed for " << QMetaType(valTypeId).name(); logBacktrace("ScriptEngineV8::castVariantToValue failed"); //Q_ASSERT(false); return V8ScriptValue(this, v8::Undefined(_v8Isolate)); diff --git a/libraries/script-engine/src/v8/ScriptObjectV8Proxy.cpp b/libraries/script-engine/src/v8/ScriptObjectV8Proxy.cpp index ac863fb2608..9b9892622ee 100644 --- a/libraries/script-engine/src/v8/ScriptObjectV8Proxy.cpp +++ b/libraries/script-engine/src/v8/ScriptObjectV8Proxy.cpp @@ -1070,7 +1070,7 @@ void ScriptMethodV8Proxy::call(const v8::FunctionCallbackInfo& argume // a lot of type conversion assistance thanks to https://stackoverflow.com/questions/28457819/qt-invoke-method-with-qvariant // A const_cast is needed because calling data() would detach the QVariant. qGenArgsVectors[i][arg] = - QGenericArgument(QMetaType::typeName(converted.userType()), const_cast(converted.constData())); + QGenericArgument(QMetaType(converted.userType()).name(), const_cast(converted.constData())); } } } diff --git a/libraries/shared/src/HifiConfigVariantMap.cpp b/libraries/shared/src/HifiConfigVariantMap.cpp index 11c4f94a32c..c9030b69d5b 100644 --- a/libraries/shared/src/HifiConfigVariantMap.cpp +++ b/libraries/shared/src/HifiConfigVariantMap.cpp @@ -116,7 +116,7 @@ void HifiConfigVariantMap::addMissingValuesToExistingMap(QVariantMap& existingMa if (existingMap.contains(key)) { // if this is just a regular value, we're done - we don't ovveride - if (newMap[key].canConvert(QMetaType::QVariantMap) && existingMap[key].canConvert(QMetaType::QVariantMap)) { + if (newMap[key].canConvert(QMetaType(QMetaType::QVariantMap)) && existingMap[key].canConvert(QMetaType(QMetaType::QVariantMap))) { // there's a variant map below and the existing map has one too, so we need to keep recursing addMissingValuesToExistingMap(*static_cast(existingMap[key].data()), newMap[key].toMap()); } @@ -135,7 +135,7 @@ QVariant* valueForKeyPath(QVariantMap& variantMap, const QString& keyPath, bool if (dotIndex == -1) { return &variantMap[firstKey]; } - if (!variantMap[firstKey].canConvert(QMetaType::QVariantMap)) { + if (!variantMap[firstKey].canConvert(QMetaType(QMetaType::QVariantMap))) { variantMap[firstKey] = QVariantMap(); } return valueForKeyPath(*static_cast(variantMap[firstKey].data()), keyPath.mid(dotIndex + 1), diff --git a/libraries/shared/src/PathUtils.cpp b/libraries/shared/src/PathUtils.cpp index 6f0b65aa09a..283fabe3fd2 100644 --- a/libraries/shared/src/PathUtils.cpp +++ b/libraries/shared/src/PathUtils.cpp @@ -228,7 +228,6 @@ int PathUtils::removeTemporaryApplicationDirs(QString appName) { auto match = re.match(dirName); if (match.hasMatch()) { auto pid = match.capturedView("pid").toLongLong(); - auto timestamp = match.capturedView("timestamp"); if (!processIsRunning(pid)) { qDebug() << " Removing old temporary directory: " << dir.absoluteFilePath(); absoluteDirPath.removeRecursively(); @@ -246,7 +245,7 @@ QString fileNameWithoutExtension(const QString& fileName, const QVector QString fileNameLowered = fileName.toLower(); foreach (const QString possibleExtension, possibleExtensions) { if (fileNameLowered.endsWith(possibleExtension.toLower())) { - return fileName.left(fileName.count() - possibleExtension.count() - 1); + return fileName.left(fileName.size() - possibleExtension.size() - 1); } } return fileName; diff --git a/libraries/shared/src/SettingHelpers.cpp b/libraries/shared/src/SettingHelpers.cpp index ab84c1ca3df..8882aa630cd 100644 --- a/libraries/shared/src/SettingHelpers.cpp +++ b/libraries/shared/src/SettingHelpers.cpp @@ -65,10 +65,10 @@ void loadOldINIFile(QSettings& settings) { if (!iniSettings.allKeys().isEmpty()) { qCDebug(shared) << "No data in json settings file, trying to load old ini settings file."; - for (auto key : iniSettings.allKeys()) { + for (const auto &key : iniSettings.allKeys()) { auto variant = iniSettings.value(key); - if (variant.type() == QVariant::String) { + if (variant.typeId() == QMetaType::QString) { auto string = variant.toString(); if (string == "true") { variant = true; @@ -92,7 +92,7 @@ void loadOldINIFile(QSettings& settings) { } QStringList splitArgs(const QString& string, int idx) { - int length = string.length(); + qsizetype length = string.length(); Q_ASSERT(length > 0); Q_ASSERT(string.at(idx) == QLatin1Char('(')); Q_ASSERT(string.at(length - 1) == QLatin1Char(')')); @@ -121,42 +121,44 @@ QJsonDocument variantMapToJsonDocument(const QSettings::SettingsMap& map) { for (auto it = map.cbegin(); it != map.cend(); ++it) { auto& key = it.key(); auto& variant = it.value(); - auto variantType = variant.type(); + auto variantType = variant.typeId(); // Switch some types so they are readable/modifiable in the json file - if (variantType == QVariant(1.0f).type()) { // float - variantType = QVariant::Double; + if (variantType == QMetaType::Float) { // float + variantType = QMetaType::Double; } - if (variantType == QVariant((quint16)0).type()) { // uint16 - variantType = QVariant::UInt; + if (variantType == QMetaType::UShort) { // uint16 + variantType = QMetaType::UInt; } - if (variantType == QVariant::Url) { // QUrl - variantType = QVariant::String; + if (variantType == QMetaType::QUrl) { // QUrl + variantType = QMetaType::QString; + } + + if (!variant.isValid()) { + object.insert(key, QJsonValue()); + continue; } switch (variantType) { // QML has problems with QVariant::Hash - case QVariant::Hash: { + case QMetaType::QVariantHash: { qCritical() << "Unsupported variant type" << variant.typeName() << ";" << key << variant; Q_ASSERT(false); break; } - case QVariant::Invalid: - object.insert(key, QJsonValue()); - break; - case QVariant::LongLong: - case QVariant::ULongLong: - case QVariant::Int: - case QVariant::UInt: - case QVariant::Bool: - case QVariant::Double: - case QVariant::Map: - case QVariant::List: + case QMetaType::LongLong: + case QMetaType::ULongLong: + case QMetaType::Int: + case QMetaType::UInt: + case QMetaType::Bool: + case QMetaType::Double: + case QMetaType::QVariantMap: + case QMetaType::QVariantList: object.insert(key, QJsonValue::fromVariant(variant)); break; - case QVariant::String: { + case QMetaType::QString: { QString result = variant.toString(); if (result.startsWith(QLatin1Char('@'))) { result.prepend(QLatin1Char('@')); @@ -165,7 +167,7 @@ QJsonDocument variantMapToJsonDocument(const QSettings::SettingsMap& map) { break; } - case QVariant::ByteArray: { + case QMetaType::QByteArray: { QByteArray a = variant.toByteArray(); QString result = QLatin1String("@ByteArray("); int sz = a.size(); @@ -178,7 +180,7 @@ QJsonDocument variantMapToJsonDocument(const QSettings::SettingsMap& map) { object.insert(key, result); break; } - case QVariant::Rect: { + case QMetaType::QRect: { QRect r = qvariant_cast(variant); QString result = QLatin1String("@Rect("); result += QString::number(r.x()); @@ -192,7 +194,7 @@ QJsonDocument variantMapToJsonDocument(const QSettings::SettingsMap& map) { object.insert(key, result); break; } - case QVariant::Size: { + case QMetaType::QSize: { QSize s = qvariant_cast(variant); QString result = QLatin1String("@Size("); result += QString::number(s.width()); @@ -202,7 +204,7 @@ QJsonDocument variantMapToJsonDocument(const QSettings::SettingsMap& map) { object.insert(key, result); break; } - case QVariant::Point: { + case QMetaType::QPoint: { QPoint p = qvariant_cast(variant); QString result = QLatin1String("@Point("); result += QString::number(p.x()); diff --git a/libraries/shared/src/ShapeInfo.cpp b/libraries/shared/src/ShapeInfo.cpp index 0f2bae11703..6a719a01873 100644 --- a/libraries/shared/src/ShapeInfo.cpp +++ b/libraries/shared/src/ShapeInfo.cpp @@ -298,7 +298,7 @@ uint64_t ShapeInfo::getHash() const { QString url = _url.toString(); if (!url.isEmpty()) { QByteArray baUrl = url.toLocal8Bit(); - uint32_t urlHash = qChecksum(baUrl.data(), baUrl.size()); + uint32_t urlHash = qChecksum(baUrl); hasher.hashUint64((uint64_t)urlHash); } diff --git a/libraries/ui/src/OffscreenUi.cpp b/libraries/ui/src/OffscreenUi.cpp index d76a5d84722..31a180da569 100644 --- a/libraries/ui/src/OffscreenUi.cpp +++ b/libraries/ui/src/OffscreenUi.cpp @@ -1139,11 +1139,12 @@ bool OffscreenUi::eventFilter(QObject* originalDestination, QEvent* event) { case QEvent::MouseButtonPress: case QEvent::MouseButtonRelease: case QEvent::MouseMove: { - QMouseEvent* mouseEvent = static_cast(event); - QPointF transformedPos = mapToVirtualScreen(mouseEvent->localPos()); + QMouseEvent* mouseEvent = dynamic_cast(event); + Q_ASSERT(mouseEvent); + QPointF transformedPos = mapToVirtualScreen(mouseEvent->position()); // FIXME: touch events are always being accepted. Use mouse events on the OffScreenUi for now, and investigate properly switching to touch events // (using handlePointerEvent) later - QMouseEvent mappedEvent(mouseEvent->type(), transformedPos, mouseEvent->screenPos(), mouseEvent->button(), mouseEvent->buttons(), mouseEvent->modifiers()); + QMouseEvent mappedEvent(mouseEvent->type(), transformedPos, mouseEvent->globalPosition(), mouseEvent->button(), mouseEvent->buttons(), mouseEvent->modifiers()); mappedEvent.ignore(); if (QCoreApplication::sendEvent(getWindow(), &mappedEvent)) { return mappedEvent.isAccepted(); diff --git a/libraries/ui/src/QmlWindowClass.cpp b/libraries/ui/src/QmlWindowClass.cpp index f36e64d487e..821cadb1304 100644 --- a/libraries/ui/src/QmlWindowClass.cpp +++ b/libraries/ui/src/QmlWindowClass.cpp @@ -200,7 +200,7 @@ void QmlWindowClass::emitWebEvent(const QVariant& webMessage) { const QString RAISE_KEYBOARD = "_RAISE_KEYBOARD"; const QString RAISE_KEYBOARD_NUMERIC = "_RAISE_KEYBOARD_NUMERIC"; const QString LOWER_KEYBOARD = "_LOWER_KEYBOARD"; - QString messageString = webMessage.type() == QVariant::String ? webMessage.toString() : ""; + QString messageString = webMessage.typeId() == QMetaType::QString ? webMessage.toString() : ""; if (messageString.left(RAISE_KEYBOARD.length()) == RAISE_KEYBOARD) { QQuickItem *quickItem = asQuickItem(); if (quickItem) { diff --git a/libraries/ui/src/ui/Menu.cpp b/libraries/ui/src/ui/Menu.cpp index 1a2c9ef32aa..a112c8d6a62 100644 --- a/libraries/ui/src/ui/Menu.cpp +++ b/libraries/ui/src/ui/Menu.cpp @@ -595,7 +595,7 @@ QAction* MenuWrapper::addAction(const QString& menuName) { } QAction* MenuWrapper::addAction(const QString& menuName, const QObject* receiver, const char* member, const QKeySequence& shortcut) { - QAction* action = _realMenu->addAction(menuName, receiver, member, shortcut); + QAction* action = _realMenu->addAction(menuName, shortcut, receiver, member); auto offscreenUi = DependencyManager::get(); if (offscreenUi) { offscreenUi->addMenuInitializer([=, this](VrMenu* vrMenu) { diff --git a/libraries/ui/src/ui/OffscreenQmlSurface.cpp b/libraries/ui/src/ui/OffscreenQmlSurface.cpp index 732c0dcbccc..c9e1a60859d 100644 --- a/libraries/ui/src/ui/OffscreenQmlSurface.cpp +++ b/libraries/ui/src/ui/OffscreenQmlSurface.cpp @@ -434,7 +434,7 @@ unsigned int OffscreenQmlSurface::deviceIdByTouchPoint(qreal x, qreal y) { auto mapped = getRootItem()->mapFromGlobal(QPoint(x, y)); for (auto pair : _activeTouchPoints) { - if (mapped.x() == (int)pair.second.touchPoint.pos().x() && mapped.y() == (int)pair.second.touchPoint.pos().y()) { + if (mapped.x() == (int)pair.second.touchPoint.position().x() && mapped.y() == (int)pair.second.touchPoint.position().y()) { return pair.first; } } @@ -501,7 +501,7 @@ bool OffscreenQmlSurface::handlePointerEvent(const PointerEvent& event, class QP state = QEventPoint::State::Pressed; } else if (event.getType() == PointerEvent::Release && event.getButton() == PointerEvent::PrimaryButton) { state = QEventPoint::State::Released; - } else if (_activeTouchPoints.count(event.getID()) && windowPoint != _activeTouchPoints[event.getID()].touchPoint.pos()) { + } else if (_activeTouchPoints.count(event.getID()) && windowPoint != _activeTouchPoints[event.getID()].touchPoint.position()) { state = QEventPoint::State::Updated; // QT6TODO: is Updated equivalent to TouchPointMoved?; } @@ -784,7 +784,7 @@ void OffscreenQmlSurface::emitWebEvent(const QVariant& message) { const QString LOWER_KEYBOARD = "_LOWER_KEYBOARD"; const QString RAISE_KEYBOARD_NUMERIC_PASSWORD = "_RAISE_KEYBOARD_NUMERIC_PASSWORD"; const QString RAISE_KEYBOARD_PASSWORD = "_RAISE_KEYBOARD_PASSWORD"; - QString messageString = message.type() == QVariant::String ? message.toString() : ""; + QString messageString = message.typeId() == QMetaType::QString ? message.toString() : ""; if (messageString.left(RAISE_KEYBOARD.length()) == RAISE_KEYBOARD) { bool numeric = (messageString == RAISE_KEYBOARD_NUMERIC || messageString == RAISE_KEYBOARD_NUMERIC_PASSWORD); bool passwordField = (messageString == RAISE_KEYBOARD_PASSWORD || messageString == RAISE_KEYBOARD_NUMERIC_PASSWORD); diff --git a/tools/nitpick/src/TestCreator.cpp b/tools/nitpick/src/TestCreator.cpp index 0ae3e08353c..7fbcf6913bb 100644 --- a/tools/nitpick/src/TestCreator.cpp +++ b/tools/nitpick/src/TestCreator.cpp @@ -1051,7 +1051,7 @@ void TestCreator::createTestsOutline() { stream << "Directories with an appended (*) have an automatic test\n\n"; // We need to know our current depth, as this isn't given by QDirIterator - int rootDepth { _testsRootDirectory.count('/') }; + qsizetype rootDepth { _testsRootDirectory.count('/') }; // Each test is shown as the folder name linking to the matching GitHub URL, and the path to the associated test.md file QDirIterator it(_testsRootDirectory, QDirIterator::Subdirectories); @@ -1231,7 +1231,7 @@ QString TestCreator::getExpectedImagePartialSourceDirectory(const QString& filen // Note that the bottom-most "tests" folder is assumed to be the root // This is required because the tests folder is named hifi_tests - int i { filenameParts.length() - 1 }; + qsizetype i { filenameParts.length() - 1 }; while (i >= 0 && filenameParts[i] != "tests") { --i; } From f21d5eede1da83019683851efe4537faa88c6e92 Mon Sep 17 00:00:00 2001 From: Karol Suprynowicz Date: Fri, 17 Oct 2025 12:16:28 +0200 Subject: [PATCH 032/111] Move Reource cache from QSharedPointer to std::shared_ptr --- assignment-client/src/Agent.cpp | 2 +- .../src/AgentScriptingInterface.h | 2 +- .../src/avatars/ScriptableAvatar.cpp | 4 +- interface/src/avatar/AvatarDoctor.cpp | 16 +-- interface/src/raypick/CollisionPick.cpp | 4 +- interface/src/raypick/CollisionPick.h | 6 +- libraries/animation/src/AnimNodeLoader.cpp | 7 +- libraries/animation/src/AnimNodeLoader.h | 2 +- libraries/animation/src/AnimationCache.cpp | 14 ++- libraries/animation/src/AnimationCache.h | 7 +- libraries/audio/src/AudioScriptingInterface.h | 4 +- libraries/audio/src/Sound.cpp | 12 ++- libraries/audio/src/Sound.h | 7 +- libraries/audio/src/SoundCache.cpp | 14 ++- libraries/audio/src/SoundCache.h | 4 +- .../audio/src/SoundCacheScriptingInterface.h | 2 +- libraries/baking/src/MaterialBaker.cpp | 6 +- .../src/RenderableMaterialEntityItem.cpp | 2 +- .../src/RenderableZoneEntityItem.cpp | 4 +- libraries/entities/src/SoundEntityItem.cpp.in | 6 +- libraries/gpu/src/gpu/Buffer.h | 2 +- libraries/gpu/src/gpu/Texture.cpp | 9 ++ libraries/gpu/src/gpu/Texture.h | 5 +- .../src/material-networking/ShaderCache.cpp | 14 ++- .../src/material-networking/ShaderCache.h | 7 +- .../src/material-networking/TextureCache.cpp | 98 +++++++++++-------- .../src/material-networking/TextureCache.h | 10 +- .../src/model-networking/ModelCache.cpp | 48 ++++----- .../src/model-networking/ModelCache.h | 7 +- libraries/networking/src/ResourceCache.cpp | 84 ++++++++-------- libraries/networking/src/ResourceCache.h | 48 +++++---- libraries/plugins/src/plugins/DisplayPlugin.h | 2 +- .../procedural/ProceduralMaterialCache.cpp | 17 +++- .../src/procedural/ProceduralMaterialCache.h | 7 +- .../recording/src/recording/ClipCache.cpp | 15 ++- libraries/recording/src/recording/ClipCache.h | 7 +- .../recording/RecordingScriptingInterface.cpp | 12 ++- libraries/render-utils/src/Model.cpp | 2 +- libraries/script-engine/src/ScriptManager.cpp | 2 +- 39 files changed, 289 insertions(+), 232 deletions(-) diff --git a/assignment-client/src/Agent.cpp b/assignment-client/src/Agent.cpp index 246fd22cbb0..42e330cbe4e 100644 --- a/assignment-client/src/Agent.cpp +++ b/assignment-client/src/Agent.cpp @@ -748,7 +748,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.get(); _numAvatarSoundSentBytes = 0; _flushEncoder = true; diff --git a/assignment-client/src/AgentScriptingInterface.h b/assignment-client/src/AgentScriptingInterface.h index 9bebad13ca0..277ed3d773f 100644 --- a/assignment-client/src/AgentScriptingInterface.h +++ b/assignment-client/src/AgentScriptingInterface.h @@ -101,7 +101,7 @@ public slots: * }, 1000); * }()); */ - void playAvatarSound(QSharedPointer avatarSound) const { _agent->playAvatarSound(avatarSound); } + void playAvatarSound(std::shared_ptr avatarSound) const { _agent->playAvatarSound(avatarSound); } private: Agent* _agent; diff --git a/assignment-client/src/avatars/ScriptableAvatar.cpp b/assignment-client/src/avatars/ScriptableAvatar.cpp index fc5f576ddbc..112753b84df 100644 --- a/assignment-client/src/avatars/ScriptableAvatar.cpp +++ b/assignment-client/src/avatars/ScriptableAvatar.cpp @@ -64,7 +64,7 @@ void ScriptableAvatar::stopAnimation() { QMetaObject::invokeMethod(this, "stopAnimation"); return; } - _animation.clear(); + _animation.reset(); } AnimationDetails ScriptableAvatar::getAnimationDetails() { @@ -222,7 +222,7 @@ void ScriptableAvatar::update(float deltatime) { } } else { - _animation.clear(); + _animation.reset(); } } } diff --git a/interface/src/avatar/AvatarDoctor.cpp b/interface/src/avatar/AvatarDoctor.cpp index ac431ef9e42..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()); @@ -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/raypick/CollisionPick.cpp b/interface/src/raypick/CollisionPick.cpp index 961476b519b..e71a5274d82 100644 --- a/interface/src/raypick/CollisionPick.cpp +++ b/interface/src/raypick/CollisionPick.cpp @@ -134,7 +134,7 @@ bool CollisionPick::getShapeInfoReady(const CollisionRegion& pick) { return _mathPick.loaded; } -void CollisionPick::computeShapeInfoDimensionsOnly(const CollisionRegion& pick, ShapeInfo& shapeInfo, QSharedPointer resource) { +void CollisionPick::computeShapeInfoDimensionsOnly(const CollisionRegion& pick, ShapeInfo& shapeInfo, std::shared_ptr resource) { ShapeType type = shapeInfo.getType(); glm::vec3 dimensions = pick.transform.getScale(); QString modelURL = (resource ? resource->getURL().toString() : ""); @@ -147,7 +147,7 @@ void CollisionPick::computeShapeInfoDimensionsOnly(const CollisionRegion& pick, } } -void CollisionPick::computeShapeInfo(const CollisionRegion& pick, ShapeInfo& shapeInfo, QSharedPointer resource) { +void CollisionPick::computeShapeInfo(const CollisionRegion& pick, ShapeInfo& shapeInfo, std::shared_ptr resource) { // This code was copied and modified from RenderableModelEntityItem::computeShapeInfo // TODO: Move to some shared code area (in entities-renderer? model-networking?) // after we verify this is working and do a diff comparison with RenderableModelEntityItem::computeShapeInfo diff --git a/interface/src/raypick/CollisionPick.h b/interface/src/raypick/CollisionPick.h index 8825ea65d11..31a1a3fd688 100644 --- a/interface/src/raypick/CollisionPick.h +++ b/interface/src/raypick/CollisionPick.h @@ -65,14 +65,14 @@ class CollisionPick : public Pick { bool isLoaded() const; // Returns true if _mathPick.shapeInfo is valid. Otherwise, attempts to get the _mathPick ready for use. bool getShapeInfoReady(const CollisionRegion& pick); - void computeShapeInfo(const CollisionRegion& pick, ShapeInfo& shapeInfo, QSharedPointer resource); - void computeShapeInfoDimensionsOnly(const CollisionRegion& pick, ShapeInfo& shapeInfo, QSharedPointer resource); + void computeShapeInfo(const CollisionRegion& pick, ShapeInfo& shapeInfo, std::shared_ptr resource); + void computeShapeInfoDimensionsOnly(const CollisionRegion& pick, ShapeInfo& shapeInfo, std::shared_ptr resource); void filterIntersections(std::vector& intersections) const; bool _scaleWithParent; PhysicsEnginePointer _physicsEngine; - QSharedPointer _cachedResource; + std::shared_ptr _cachedResource; // Options for what information to get from collision results bool _includeNormals; diff --git a/libraries/animation/src/AnimNodeLoader.cpp b/libraries/animation/src/AnimNodeLoader.cpp index 066ccec056b..8a655fbe97f 100644 --- a/libraries/animation/src/AnimNodeLoader.cpp +++ b/libraries/animation/src/AnimNodeLoader.cpp @@ -1083,11 +1083,10 @@ bool processBlendDirectionalNode(AnimNode::Pointer node, const QJsonObject& json AnimNodeLoader::AnimNodeLoader(const QUrl& url) : _url(url) { - _resource = QSharedPointer::create(url); - _resource->setSelf(_resource); + _resource = std::make_shared(url); _resource->setLoadPriorityOperator(this, []() { return ANIM_GRAPH_LOAD_PRIORITY; }); - connect(_resource.data(), &Resource::loaded, this, &AnimNodeLoader::onRequestDone); - connect(_resource.data(), &Resource::failed, this, &AnimNodeLoader::onRequestError); + connect(_resource.get(), &Resource::loaded, this, &AnimNodeLoader::onRequestDone); + connect(_resource.get(), &Resource::failed, this, &AnimNodeLoader::onRequestError); _resource->ensureLoading(); } diff --git a/libraries/animation/src/AnimNodeLoader.h b/libraries/animation/src/AnimNodeLoader.h index ac27402cf71..d293133f2fd 100644 --- a/libraries/animation/src/AnimNodeLoader.h +++ b/libraries/animation/src/AnimNodeLoader.h @@ -42,7 +42,7 @@ protected slots: protected: QUrl _url; - QSharedPointer _resource; + std::shared_ptr _resource; private: Q_DISABLE_COPY(AnimNodeLoader) diff --git a/libraries/animation/src/AnimationCache.cpp b/libraries/animation/src/AnimationCache.cpp index f1c90b6a68c..6d25b1400e4 100644 --- a/libraries/animation/src/AnimationCache.cpp +++ b/libraries/animation/src/AnimationCache.cpp @@ -33,15 +33,19 @@ AnimationCache::AnimationCache(QObject* parent) : } AnimationPointer AnimationCache::getAnimation(const QUrl& url) { - return getResource(url).staticCast(); + auto animation = std::dynamic_pointer_cast(getResource(url)); + Q_ASSERT(animation); + return animation; } -QSharedPointer AnimationCache::createResource(const QUrl& url) { - return QSharedPointer(new Animation(url), &Resource::deleter); +std::shared_ptr AnimationCache::createResource(const QUrl& url) { + return std::shared_ptr(new Animation(url), Resource::sharedPtrDeleter); } -QSharedPointer AnimationCache::createResourceCopy(const QSharedPointer& resource) { - return QSharedPointer(new Animation(*resource.staticCast()), &Resource::deleter); +std::shared_ptr AnimationCache::createResourceCopy(const std::shared_ptr& resource) { + auto animation = std::dynamic_pointer_cast(resource); + Q_ASSERT(animation); + return std::shared_ptr(new Animation(*animation), Resource::sharedPtrDeleter); } AnimationReader::AnimationReader(const QUrl& url, const QByteArray& data) : diff --git a/libraries/animation/src/AnimationCache.h b/libraries/animation/src/AnimationCache.h index f879a7eea62..bf048b84a75 100644 --- a/libraries/animation/src/AnimationCache.h +++ b/libraries/animation/src/AnimationCache.h @@ -23,7 +23,7 @@ class Animation; -using AnimationPointer = QSharedPointer; +using AnimationPointer = std::shared_ptr; class AnimationCache : public ResourceCache, public Dependency { Q_OBJECT @@ -35,8 +35,8 @@ class AnimationCache : public ResourceCache, public Dependency { Q_INVOKABLE AnimationPointer getAnimation(const QUrl& url); protected: - virtual QSharedPointer createResource(const QUrl& url) override; - QSharedPointer createResourceCopy(const QSharedPointer& resource) override; + virtual std::shared_ptr createResource(const QUrl& url) override; + std::shared_ptr createResourceCopy(const std::shared_ptr& resource) override; private: explicit AnimationCache(QObject* parent = NULL); @@ -54,6 +54,7 @@ class Animation : public Resource { Animation(const Animation& other) : Resource(other), _hfmModel(other._hfmModel) {} Animation(const QUrl& url) : Resource(url) {} + virtual ~Animation() {} QString getType() const override { return "Animation"; } diff --git a/libraries/audio/src/AudioScriptingInterface.h b/libraries/audio/src/AudioScriptingInterface.h index b6ad0f17596..ea1fc8b89ce 100644 --- a/libraries/audio/src/AudioScriptingInterface.h +++ b/libraries/audio/src/AudioScriptingInterface.h @@ -196,7 +196,7 @@ class AudioScriptingInterface : public QObject, public Dependency { * sound.ready.connect(onSoundReady); * } */ - Q_INVOKABLE ScriptAudioInjector* playSound(QSharedPointer sound, const AudioInjectorOptions& injectorOptions = AudioInjectorOptions()); + Q_INVOKABLE ScriptAudioInjector* playSound(std::shared_ptr sound, const AudioInjectorOptions& injectorOptions = AudioInjectorOptions()); /*@jsdoc * Starts playing the content of an audio file locally (isn't sent to the audio mixer). This is the same as calling @@ -207,7 +207,7 @@ class AudioScriptingInterface : public QObject, public Dependency { * {@link SoundObject} for supported formats. * @returns {AudioInjector} The audio injector that plays the audio file. */ - Q_INVOKABLE ScriptAudioInjector* playSystemSound(QSharedPointer sound); + Q_INVOKABLE ScriptAudioInjector* playSystemSound(std::shared_ptr sound); /*@jsdoc * Sets whether the audio input should be used in stereo. If the audio input doesn't support stereo then setting a value diff --git a/libraries/audio/src/Sound.cpp b/libraries/audio/src/Sound.cpp index dcb67014984..9fa33396882 100644 --- a/libraries/audio/src/Sound.cpp +++ b/libraries/audio/src/Sound.cpp @@ -74,13 +74,15 @@ AudioData::AudioData(uint32_t numSamples, uint32_t numChannels, const AudioSampl {} void Sound::downloadFinished(const QByteArray& data) { - if (!_self) { + auto sharedSoundPointer = shared_from_this(); + + if (!sharedSoundPointer) { soundProcessError(301, "Sound object has gone out of scope"); return; } // this is a QRunnable, will delete itself after it has finished running - auto soundProcessor = new SoundProcessor(_self, data); + auto soundProcessor = new SoundProcessor(sharedSoundPointer, data); connect(soundProcessor, &SoundProcessor::onSuccess, this, &Sound::soundProcessSuccess); connect(soundProcessor, &SoundProcessor::onError, this, &Sound::soundProcessError); QThreadPool::globalInstance()->start(soundProcessor); @@ -102,14 +104,14 @@ void Sound::soundProcessError(int error, QString str) { } -SoundProcessor::SoundProcessor(QWeakPointer sound, QByteArray data) : +SoundProcessor::SoundProcessor(std::weak_ptr sound, QByteArray data) : _sound(sound), _data(data) { } void SoundProcessor::run() { - auto sound = qSharedPointerCast(_sound.lock()); + auto sound = std::dynamic_pointer_cast(_sound.lock()); if (!sound) { emit onError(301, "Sound object has gone out of scope"); return; @@ -440,7 +442,7 @@ bool soundSharedPointerFromScriptValue(const ScriptValue& object, SharedSoundPoi SoundScriptingInterface::SoundScriptingInterface(const SharedSoundPointer& sound) : _sound(sound) { // During shutdown we can sometimes get an empty sound pointer back if (_sound) { - QObject::connect(_sound.data(), &Sound::ready, this, &SoundScriptingInterface::ready); + QObject::connect(_sound.get(), &Sound::ready, this, &SoundScriptingInterface::ready); } } diff --git a/libraries/audio/src/Sound.h b/libraries/audio/src/Sound.h index 878343e722b..124c20a750b 100644 --- a/libraries/audio/src/Sound.h +++ b/libraries/audio/src/Sound.h @@ -67,6 +67,7 @@ class Sound : public Resource { public: Sound(const QUrl& url, bool isStereo = false, bool isAmbisonic = false); Sound(const Sound& other) : Resource(other), _audioData(other._audioData), _numChannels(other._numChannels) {} + virtual ~Sound() {} bool isReady() const { return (bool)_audioData; } @@ -103,7 +104,7 @@ class SoundProcessor : public QObject, public QRunnable { uint32_t sampleRate { 0 }; }; - SoundProcessor(QWeakPointer sound, QByteArray data); + SoundProcessor(std::weak_ptr sound, QByteArray data); virtual void run() override; @@ -119,11 +120,11 @@ class SoundProcessor : public QObject, public QRunnable { void onError(int error, QString str); private: - const QWeakPointer _sound; + const std::weak_ptr _sound; const QByteArray _data; }; -typedef QSharedPointer SharedSoundPointer; +typedef std::shared_ptr SharedSoundPointer; /*@jsdoc * An audio resource, created by {@link SoundCache.getSound}, to be played back using {@link Audio.playSound}. diff --git a/libraries/audio/src/SoundCache.cpp b/libraries/audio/src/SoundCache.cpp index 55a32e72375..a4876733dec 100644 --- a/libraries/audio/src/SoundCache.cpp +++ b/libraries/audio/src/SoundCache.cpp @@ -30,15 +30,19 @@ SoundCache::SoundCache(QObject* parent) : } SharedSoundPointer SoundCache::getSound(const QUrl& url) { - return getResource(url).staticCast(); + auto sound = std::dynamic_pointer_cast(getResource(url)); + Q_ASSERT(sound); + return sound; } -QSharedPointer SoundCache::createResource(const QUrl& url) { - auto resource = QSharedPointer(new Sound(url), &Resource::deleter); +std::shared_ptr SoundCache::createResource(const QUrl& url) { + auto resource = std::shared_ptr(new Sound(url), Resource::sharedPtrDeleter); resource->setLoadPriorityOperator(this, []() { return SOUNDS_LOADING_PRIORITY; }); return resource; } -QSharedPointer SoundCache::createResourceCopy(const QSharedPointer& resource) { - return QSharedPointer(new Sound(*resource.staticCast()), &Resource::deleter); +std::shared_ptr SoundCache::createResourceCopy(const std::shared_ptr& resource) { + auto sound = std::dynamic_pointer_cast(resource); + Q_ASSERT(sound); + return std::shared_ptr(new Sound(*sound), Resource::sharedPtrDeleter); } \ No newline at end of file diff --git a/libraries/audio/src/SoundCache.h b/libraries/audio/src/SoundCache.h index 973edc40487..f704d9c0921 100644 --- a/libraries/audio/src/SoundCache.h +++ b/libraries/audio/src/SoundCache.h @@ -26,8 +26,8 @@ class SoundCache : public ResourceCache, public Dependency { Q_INVOKABLE SharedSoundPointer getSound(const QUrl& url); protected: - virtual QSharedPointer createResource(const QUrl& url) override; - QSharedPointer createResourceCopy(const QSharedPointer& resource) override; + virtual std::shared_ptr createResource(const QUrl& url) override; + std::shared_ptr createResourceCopy(const std::shared_ptr& resource) override; private: SoundCache(QObject* parent = NULL); diff --git a/libraries/audio/src/SoundCacheScriptingInterface.h b/libraries/audio/src/SoundCacheScriptingInterface.h index c9790dbb367..628f8bcb578 100644 --- a/libraries/audio/src/SoundCacheScriptingInterface.h +++ b/libraries/audio/src/SoundCacheScriptingInterface.h @@ -60,7 +60,7 @@ class SoundCacheScriptingInterface : public ScriptableResourceCache, public Depe * formats. * @returns {SoundObject} The sound ready for playback. */ - Q_INVOKABLE QSharedPointer getSound(const QUrl& url); + Q_INVOKABLE std::shared_ptr getSound(const QUrl& url); }; #endif // hifi_SoundCacheScriptingInterface_h diff --git a/libraries/baking/src/MaterialBaker.cpp b/libraries/baking/src/MaterialBaker.cpp index 767eac0eb96..669163c6b5a 100644 --- a/libraries/baking/src/MaterialBaker.cpp +++ b/libraries/baking/src/MaterialBaker.cpp @@ -54,7 +54,7 @@ void MaterialBaker::bake() { if (_materialResource->isLoaded()) { processMaterial(); } else { - connect(_materialResource.data(), &Resource::finished, this, &MaterialBaker::originalMaterialLoaded); + connect(_materialResource.get(), &Resource::finished, this, &MaterialBaker::originalMaterialLoaded); } } } @@ -71,7 +71,7 @@ void MaterialBaker::loadMaterial() { if (!_isURL) { qCDebug(material_baking) << "Loading local material" << _materialData; - _materialResource = QSharedPointer::create(); + _materialResource = std::make_shared(); // TODO: add baseURL to allow these to reference relative files next to them _materialResource->parsedMaterials = NetworkMaterialResource::parseJSONMaterials(QJsonDocument::fromJson(_materialData.toUtf8()), QUrl()); } else { @@ -83,7 +83,7 @@ void MaterialBaker::loadMaterial() { if (_materialResource->isLoaded()) { emit originalMaterialLoaded(); } else { - connect(_materialResource.data(), &Resource::finished, this, &MaterialBaker::originalMaterialLoaded); + connect(_materialResource.get(), &Resource::finished, this, &MaterialBaker::originalMaterialLoaded); } } else { handleError("Error loading " + _materialData); diff --git a/libraries/entities-renderer/src/RenderableMaterialEntityItem.cpp b/libraries/entities-renderer/src/RenderableMaterialEntityItem.cpp index 1d2b771d4ed..38998d3fe73 100644 --- a/libraries/entities-renderer/src/RenderableMaterialEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableMaterialEntityItem.cpp @@ -171,7 +171,7 @@ void MaterialEntityRenderer::doRenderUpdateAsynchronousTyped(const TypedEntityPo if (_networkMaterial->isLoaded()) { onMaterialRequestFinished(!_networkMaterial->isFailed()); } else { - connect(_networkMaterial.data(), &Resource::finished, this, [this, onMaterialRequestFinished](bool success) { + connect(_networkMaterial.get(), &Resource::finished, this, [this, onMaterialRequestFinished](bool success) { onMaterialRequestFinished(success); }); } diff --git a/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp b/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp index 61578acfdac..64658d15884 100644 --- a/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp @@ -536,7 +536,7 @@ void ZoneEntityRenderer::setAmbientURL(const QString& ambientUrl) { if (_ambientTextureURL.isEmpty()) { _pendingAmbientTexture = false; - _ambientTexture.clear(); + _ambientTexture.get(); _ambientLight->setAmbientMap(nullptr); _ambientLight->setAmbientSpherePreset(gpu::SphericalHarmonics::BREEZEWAY); @@ -575,7 +575,7 @@ void ZoneEntityRenderer::setSkyboxURL(const QString& skyboxUrl) { if (_skyboxTextureURL.isEmpty()) { _pendingSkyboxTexture = false; - _skyboxTexture.clear(); + _skyboxTexture.get(); editSkybox()->setCubemap(nullptr); } else { diff --git a/libraries/entities/src/SoundEntityItem.cpp.in b/libraries/entities/src/SoundEntityItem.cpp.in index a308747bffe..841a2517e6e 100644 --- a/libraries/entities/src/SoundEntityItem.cpp.in +++ b/libraries/entities/src/SoundEntityItem.cpp.in @@ -119,7 +119,7 @@ void SoundEntityItem::update(const quint64& now) { if (_sound->isLoaded()) { updateSound(true); } else { - connect(_sound.data(), &Resource::finished, this, [&] { updateSound(true); }); + connect(_sound.get(), &Resource::finished, this, [&] { updateSound(true); }); } } } @@ -163,7 +163,7 @@ void SoundEntityItem::setSoundURL(const QString& value) { if (_sound->isLoaded()) { updateSound(true); } else { - connect(_sound.data(), &Resource::finished, this, [&] { updateSound(true); }); + connect(_sound.get(), &Resource::finished, this, [&] { updateSound(true); }); } } } @@ -334,7 +334,7 @@ void SoundEntityItem::setLocalOnly(bool value) { if (_sound->isLoaded()) { updateSound(true); } else { - connect(_sound.data(), &Resource::finished, this, [&] { updateSound(true); }); + connect(_sound.get(), &Resource::finished, this, [&] { updateSound(true); }); } } } diff --git a/libraries/gpu/src/gpu/Buffer.h b/libraries/gpu/src/gpu/Buffer.h index 3cf7b2a8142..1b87d38f819 100644 --- a/libraries/gpu/src/gpu/Buffer.h +++ b/libraries/gpu/src/gpu/Buffer.h @@ -77,7 +77,7 @@ class Buffer : public Resource { Buffer(uint32_t usage, Size size, const Byte* bytes, Size pageSize = PageManager::DEFAULT_PAGE_SIZE); Buffer(const Buffer& buf); // deep copy of the sysmem buffer Buffer& operator=(const Buffer& buf); // deep copy of the sysmem buffer - ~Buffer(); + virtual ~Buffer(); // The size in bytes of data stored in the buffer Size getSize() const override; diff --git a/libraries/gpu/src/gpu/Texture.cpp b/libraries/gpu/src/gpu/Texture.cpp index c731c404c91..955538ffdab 100644 --- a/libraries/gpu/src/gpu/Texture.cpp +++ b/libraries/gpu/src/gpu/Texture.cpp @@ -230,6 +230,9 @@ Texture::Texture(TextureUsageType usageType) : } Texture::~Texture() { + Q_ASSERT(!wasDeleted); + wasDeleted = true; + _textureCPUCount.decrement(); if (_usageType == TextureUsageType::EXTERNAL) { Texture::ExternalUpdates externalUpdates; @@ -900,10 +903,16 @@ const gpu::TexturePointer TextureSource::getGPUTexture() const { _locked = false; return gpuTexture; } + if (_gpuTexture) { + Q_ASSERT(!_gpuTexture->wasDeleted); + } return _gpuTexture; } void TextureSource::resetTexture(const gpu::TexturePointer& texture) { + if (texture) { + Q_ASSERT(!texture->wasDeleted); + } _gpuTexture = texture; _gpuTextureOperator = nullptr; } diff --git a/libraries/gpu/src/gpu/Texture.h b/libraries/gpu/src/gpu/Texture.h index 6d2626a512b..b144040119a 100644 --- a/libraries/gpu/src/gpu/Texture.h +++ b/libraries/gpu/src/gpu/Texture.h @@ -140,6 +140,7 @@ class Texture : public Resource { static void updateTextureCPUMemoryUsage(Size prevObjectSize, Size newObjectSize); public: + std::atomic wasDeleted{false}; static const uint32_t CUBE_FACE_COUNT { 6 }; static uint32_t getTextureCPUCount(); static Size getTextureCPUMemSize(); @@ -329,8 +330,8 @@ class Texture : public Resource { // After the texture has been created, it should be defined bool isDefined() const { return _defined; } - Texture(TextureUsageType usageType); - ~Texture(); + explicit Texture(TextureUsageType usageType); + virtual ~Texture(); Stamp getStamp() const { return _stamp; } Stamp getDataStamp() const { return _storage->getStamp(); } diff --git a/libraries/material-networking/src/material-networking/ShaderCache.cpp b/libraries/material-networking/src/material-networking/ShaderCache.cpp index 0b87b74ae3c..24ceb14af34 100644 --- a/libraries/material-networking/src/material-networking/ShaderCache.cpp +++ b/libraries/material-networking/src/material-networking/ShaderCache.cpp @@ -21,13 +21,17 @@ ShaderCache& ShaderCache::instance() { } NetworkShaderPointer ShaderCache::getShader(const QUrl& url) { - return ResourceCache::getResource(url).staticCast(); + auto shader = std::dynamic_pointer_cast(ResourceCache::getResource(url)); + Q_ASSERT(shader); + return shader; } -QSharedPointer ShaderCache::createResource(const QUrl& url) { - return QSharedPointer(new NetworkShader(url), &Resource::deleter); +std::shared_ptr ShaderCache::createResource(const QUrl& url) { + return std::shared_ptr(new NetworkShader(url), Resource::sharedPtrDeleter); } -QSharedPointer ShaderCache::createResourceCopy(const QSharedPointer& resource) { - return QSharedPointer(new NetworkShader(*resource.staticCast()), &Resource::deleter); +std::shared_ptr ShaderCache::createResourceCopy(const std::shared_ptr& resource) { + auto shader = std::dynamic_pointer_cast(resource); + Q_ASSERT(shader); + return std::shared_ptr(new NetworkShader(*shader), Resource::sharedPtrDeleter); } diff --git a/libraries/material-networking/src/material-networking/ShaderCache.h b/libraries/material-networking/src/material-networking/ShaderCache.h index 5129a7d8b40..a4bbaa2d31e 100644 --- a/libraries/material-networking/src/material-networking/ShaderCache.h +++ b/libraries/material-networking/src/material-networking/ShaderCache.h @@ -16,6 +16,7 @@ class NetworkShader : public Resource { public: NetworkShader(const QUrl& url); NetworkShader(const NetworkShader& other) : Resource(other), _source(other._source) {} + virtual ~NetworkShader() {} QString getType() const override { return "NetworkShader"; } @@ -24,7 +25,7 @@ class NetworkShader : public Resource { QString _source; }; -using NetworkShaderPointer = QSharedPointer; +using NetworkShaderPointer = std::shared_ptr; class ShaderCache : public ResourceCache { public: @@ -33,8 +34,8 @@ class ShaderCache : public ResourceCache { NetworkShaderPointer getShader(const QUrl& url); protected: - virtual QSharedPointer createResource(const QUrl& url) override; - QSharedPointer createResourceCopy(const QSharedPointer& resource) override; + virtual std::shared_ptr createResource(const QUrl& url) override; + std::shared_ptr createResourceCopy(const std::shared_ptr& resource) override; }; #endif diff --git a/libraries/material-networking/src/material-networking/TextureCache.cpp b/libraries/material-networking/src/material-networking/TextureCache.cpp index f13191f03b3..f28d7d37e7f 100644 --- a/libraries/material-networking/src/material-networking/TextureCache.cpp +++ b/libraries/material-networking/src/material-networking/TextureCache.cpp @@ -281,7 +281,10 @@ NetworkTexturePointer TextureCache::getTexture(const QUrl& url, image::TextureUs modifiedUrl.setQuery(query.toString()); } TextureExtra extra = { type, content, maxNumPixels, sourceChannel }; - return ResourceCache::getResource(modifiedUrl, QUrl(), &extra, std::hash()(extra)).staticCast(); + + auto texture = std::dynamic_pointer_cast(ResourceCache::getResource(modifiedUrl, QUrl(), &extra, std::hash()(extra))); + Q_ASSERT(texture); + return texture; } std::pair TextureCache::getTextureByHash(const std::string& hash) { @@ -289,6 +292,9 @@ std::pair TextureCache::getTextureByHash(const { std::unique_lock lock(_texturesByHashesMutex); weakPointer = _texturesByHashes[hash]; + if (weakPointer.first.lock()) { + Q_ASSERT(!weakPointer.first.lock()->wasDeleted); + } } return { weakPointer.first.lock(), weakPointer.second }; } @@ -296,6 +302,7 @@ std::pair TextureCache::getTextureByHash(const std::pair TextureCache::cacheTextureByHash(const std::string& hash, const std::pair& textureAndSize) { std::pair result; { + Q_ASSERT(!textureAndSize.first->wasDeleted); std::unique_lock lock(_texturesByHashesMutex); auto& value = _texturesByHashes[hash]; result = { value.first.lock(), value.second }; @@ -377,15 +384,17 @@ gpu::TexturePointer TextureCache::getImageTexture(const QString& path, image::Te return gpu::TexturePointer(loader(std::move(image), path.toStdString(), shouldCompress, target, false)); } -QSharedPointer TextureCache::createResource(const QUrl& url) { - return QSharedPointer(new NetworkTexture(url), &Resource::deleter); +std::shared_ptr TextureCache::createResource(const QUrl& url) { + return std::shared_ptr(new NetworkTexture(url), Resource::sharedPtrDeleter); } -QSharedPointer TextureCache::createResourceCopy(const QSharedPointer& resource) { - return QSharedPointer(new NetworkTexture(*resource.staticCast()), &Resource::deleter); +std::shared_ptr TextureCache::createResourceCopy(const std::shared_ptr& resource) { + auto texture = std::dynamic_pointer_cast(resource); + Q_ASSERT(texture); + return std::shared_ptr(new NetworkTexture(*texture), Resource::sharedPtrDeleter); } -int networkTexturePointerMetaTypeId = qRegisterMetaType>(); +int networkTexturePointerMetaTypeId = qRegisterMetaType>(); NetworkTexture::NetworkTexture(const QUrl& url, bool resourceTexture) : Resource(url), @@ -493,13 +502,17 @@ void NetworkTexture::setImage(gpu::TexturePointer texture, int originalWidth, finishedLoading(false); } - emit networkTextureCreated(qWeakPointerCast (_self)); + auto thisTexture = std::dynamic_pointer_cast(shared_from_this()); + Q_ASSERT(thisTexture); + emit networkTextureCreated(thisTexture); } void NetworkTexture::setImageOperator(std::function textureOperator) { _textureSource->resetTextureOperator(textureOperator); finishedLoading((bool)textureOperator); - emit networkTextureCreated(qWeakPointerCast (_self)); + auto thisTexture = std::dynamic_pointer_cast(shared_from_this()); + Q_ASSERT(thisTexture); + emit networkTextureCreated(thisTexture); } gpu::TexturePointer NetworkTexture::getFallbackTexture() const { @@ -508,7 +521,7 @@ gpu::TexturePointer NetworkTexture::getFallbackTexture() const { class ImageReader : public QRunnable { public: - ImageReader(const QWeakPointer& resource, const QUrl& url, + ImageReader(const std::weak_ptr& resource, const QUrl& url, const QByteArray& data, size_t extraHash, int maxNumPixels, image::ColorChannel sourceChannel); void run() override final; @@ -517,7 +530,7 @@ class ImageReader : public QRunnable { private: static void listSupportedImageFormats(); - QWeakPointer _resource; + std::weak_ptr _resource; QUrl _url; QByteArray _content; size_t _extraHash; @@ -537,7 +550,7 @@ NetworkTexture::~NetworkTexture() { _ktxMipRequest->deleteLater(); _ktxMipRequest = nullptr; } - TextureCache::requestCompleted(_self); + TextureCache::requestCompleted(weak_from_this()); } } @@ -549,14 +562,14 @@ void NetworkTexture::makeRequest() { } if (isLocalUrl(_activeUrl)) { - auto self = _self; + std::weak_ptr self = weak_from_this(); QThreadPool::globalInstance()->start([self] { auto resource = self.lock(); if (!resource) { return; } - NetworkTexture* networkTexture = static_cast(resource.data()); + NetworkTexture* networkTexture = static_cast(resource.get()); networkTexture->makeLocalRequest(); }); return; @@ -612,7 +625,7 @@ void NetworkTexture::makeRequest() { } void NetworkTexture::handleLocalRequestCompleted() { - TextureCache::requestCompleted(_self); + TextureCache::requestCompleted(weak_from_this()); } void NetworkTexture::makeLocalRequest() { @@ -700,7 +713,7 @@ bool NetworkTexture::handleFailedRequest(ResourceRequest::Result result) { } void NetworkTexture::startRequestForNextMipLevel() { - auto self = _self.lock(); + auto self = shared_from_this(); if (!self) { return; } @@ -783,7 +796,7 @@ void NetworkTexture::ktxInitialDataRequestFinished() { setSize(_bytesTotal); - TextureCache::requestCompleted(_self); + TextureCache::requestCompleted(weak_from_this()); auto result = _ktxHeaderRequest->getResult(); if (result == ResourceRequest::Success) { @@ -832,7 +845,7 @@ void NetworkTexture::ktxMipRequestFinished() { return; } - TextureCache::requestCompleted(_self); + TextureCache::requestCompleted(weak_from_this()); auto result = _ktxMipRequest->getResult(); if (result == ResourceRequest::Success) { @@ -843,7 +856,7 @@ void NetworkTexture::ktxMipRequestFinished() { _ktxResourceState = WAITING_FOR_MIP_REQUEST; - auto self = _self; + auto self = weak_from_this(); auto url = _url; auto data = _ktxMipRequest->getData(); auto mipLevel = _ktxMipLevelRangeInFlight.first; @@ -877,12 +890,12 @@ void NetworkTexture::ktxMipRequestFinished() { return; } - QMetaObject::invokeMethod(resource.data(), "setImage", + QMetaObject::invokeMethod(resource.get(), "setImage", Q_ARG(gpu::TexturePointer, texture), Q_ARG(int, texture->getWidth()), Q_ARG(int, texture->getHeight())); - QMetaObject::invokeMethod(resource.data(), "startRequestForNextMipLevel"); + QMetaObject::invokeMethod(resource.get(), "startRequestForNextMipLevel"); }); } else { qWarning(networking) << "Mip request finished in an unexpected state: " << _ktxResourceState; @@ -914,7 +927,7 @@ void NetworkTexture::handleFinishedInitialLoad() { _ktxResourceState = WAITING_FOR_MIP_REQUEST; - auto self = _self; + auto self = weak_from_this(); auto url = _url; DependencyManager::get()->incrementStat("PendingProcessing"); QThreadPool::globalInstance()->start([self, ktxHeaderData, ktxHighMipData, url] { @@ -938,7 +951,7 @@ void NetworkTexture::handleFinishedInitialLoad() { auto header = reinterpret_cast(ktxHeaderData.data()); if (!ktx::checkIdentifier(header->identifier)) { - QMetaObject::invokeMethod(resource.data(), "setImage", + QMetaObject::invokeMethod(resource.get(), "setImage", Q_ARG(gpu::TexturePointer, nullptr), Q_ARG(int, 0), Q_ARG(int, 0)); @@ -947,7 +960,7 @@ void NetworkTexture::handleFinishedInitialLoad() { auto kvSize = header->bytesOfKeyValueData; if (kvSize > (ktxHeaderData.size() - ktx::KTX_HEADER_SIZE)) { - QMetaObject::invokeMethod(resource.data(), "setImage", + QMetaObject::invokeMethod(resource.get(), "setImage", Q_ARG(gpu::TexturePointer, nullptr), Q_ARG(int, 0), Q_ARG(int, 0)); @@ -958,14 +971,14 @@ void NetworkTexture::handleFinishedInitialLoad() { auto imageDescriptors = header->generateImageDescriptors(); if (imageDescriptors.size() == 0) { - QMetaObject::invokeMethod(resource.data(), "setImage", + QMetaObject::invokeMethod(resource.get(), "setImage", Q_ARG(gpu::TexturePointer, nullptr), Q_ARG(int, 0), Q_ARG(int, 0)); return; } auto originalKtxDescriptor = new ktx::KTXDescriptor(*header, keyValues, imageDescriptors); - QMetaObject::invokeMethod(resource.data(), "setOriginalDescriptor", + QMetaObject::invokeMethod(resource.get(), "setOriginalDescriptor", Q_ARG(ktx::KTXDescriptor*, originalKtxDescriptor)); // Create bare ktx in memory @@ -976,7 +989,7 @@ void NetworkTexture::handleFinishedInitialLoad() { std::string hash; if (found == keyValues.end() || found->_value.size() != gpu::SOURCE_HASH_BYTES) { qWarning("Invalid source hash key found, bailing"); - QMetaObject::invokeMethod(resource.data(), "setImage", + QMetaObject::invokeMethod(resource.get(), "setImage", Q_ARG(gpu::TexturePointer, nullptr), Q_ARG(int, 0), Q_ARG(int, 0)); @@ -1009,7 +1022,7 @@ void NetworkTexture::handleFinishedInitialLoad() { auto memKtx = ktx::KTX::createBare(*header, keyValues); if (!memKtx) { qWarning() << " Ktx could not be created, bailing"; - QMetaObject::invokeMethod(resource.data(), "setImage", + QMetaObject::invokeMethod(resource.get(), "setImage", Q_ARG(gpu::TexturePointer, nullptr), Q_ARG(int, 0), Q_ARG(int, 0)); @@ -1023,7 +1036,7 @@ void NetworkTexture::handleFinishedInitialLoad() { auto& ktxCache = textureCache->_ktxCache; if (!memKtx || !(file = ktxCache->writeFile(data, KTXCache::Metadata(filename, length)))) { qCWarning(materialnetworking) << url << " failed to write cache file"; - QMetaObject::invokeMethod(resource.data(), "setImage", + QMetaObject::invokeMethod(resource.get(), "setImage", Q_ARG(gpu::TexturePointer, nullptr), Q_ARG(int, 0), Q_ARG(int, 0)); @@ -1058,12 +1071,12 @@ void NetworkTexture::handleFinishedInitialLoad() { textureAndSize = textureCache->cacheTextureByHash(filename, textureAndSize); } - QMetaObject::invokeMethod(resource.data(), "setImage", + QMetaObject::invokeMethod(resource.get(), "setImage", Q_ARG(gpu::TexturePointer, textureAndSize.first), Q_ARG(int, textureAndSize.second.x), Q_ARG(int, textureAndSize.second.y)); - QMetaObject::invokeMethod(resource.data(), "startRequestForNextMipLevel"); + QMetaObject::invokeMethod(resource.get(), "startRequestForNextMipLevel"); }); } @@ -1073,7 +1086,7 @@ void NetworkTexture::downloadFinished(const QByteArray& data) { } else if (_currentlyLoadingResourceType == ResourceType::ORIGINAL) { loadTextureContent(data); } else { - TextureCache::requestCompleted(_self); + TextureCache::requestCompleted(weak_from_this()); Resource::handleFailedRequest(ResourceRequest::Error); } } @@ -1104,7 +1117,7 @@ void NetworkTexture::loadMetaContent(const QByteArray& content) { _currentlyLoadingResourceType = ResourceType::KTX; _activeUrl = _activeUrl.resolved(url); auto textureCache = DependencyManager::get(); - auto self = _self.lock(); + auto self = shared_from_this(); if (!self) { return; } @@ -1120,7 +1133,7 @@ void NetworkTexture::loadMetaContent(const QByteArray& content) { _activeUrl = _activeUrl.resolved(meta.uncompressed); auto textureCache = DependencyManager::get(); - auto self = _self.lock(); + auto self = shared_from_this(); if (!self) { return; } @@ -1134,7 +1147,7 @@ void NetworkTexture::loadMetaContent(const QByteArray& content) { _activeUrl = _activeUrl.resolved(meta.original); auto textureCache = DependencyManager::get(); - auto self = _self.lock(); + auto self = shared_from_this(); if (!self) { return; } @@ -1152,7 +1165,7 @@ void NetworkTexture::loadTextureContent(const QByteArray& content) { return; } - QThreadPool::globalInstance()->start(new ImageReader(_self, _url, content, _extraHash, _maxNumPixels, _sourceChannel)); + QThreadPool::globalInstance()->start(new ImageReader(weak_from_this(), _url, content, _extraHash, _maxNumPixels, _sourceChannel)); } void NetworkTexture::refresh() { @@ -1170,14 +1183,14 @@ void NetworkTexture::refresh() { _ktxMipRequest->deleteLater(); _ktxMipRequest = nullptr; } - TextureCache::requestCompleted(_self); + TextureCache::requestCompleted(weak_from_this()); } _ktxResourceState = PENDING_INITIAL_LOAD; Resource::refresh(); } -ImageReader::ImageReader(const QWeakPointer& resource, const QUrl& url, const QByteArray& data, size_t extraHash, int maxNumPixels, image::ColorChannel sourceChannel) : +ImageReader::ImageReader(const std::weak_ptr& resource, const QUrl& url, const QByteArray& data, size_t extraHash, int maxNumPixels, image::ColorChannel sourceChannel) : _resource(resource), _url(url), _content(data), @@ -1236,7 +1249,8 @@ void ImageReader::read() { if (!resource) { return; } - auto networkTexture = resource.staticCast(); + auto networkTexture = std::dynamic_pointer_cast(resource); + Q_ASSERT(networkTexture); // Hash the source image and extraHash for KTX caching std::string hash; @@ -1269,7 +1283,7 @@ void ImageReader::read() { // If we found the texture either because it's in use or via KTX deserialization, // set the image and return immediately. if (textureAndSize.first) { - QMetaObject::invokeMethod(resource.data(), "setImage", + QMetaObject::invokeMethod(resource.get(), "setImage", Q_ARG(gpu::TexturePointer, textureAndSize.first), Q_ARG(int, textureAndSize.second.x), Q_ARG(int, textureAndSize.second.y)); @@ -1294,7 +1308,7 @@ void ImageReader::read() { textureAndSize = image::processImage(std::move(buffer), _url.toString().toStdString(), _sourceChannel, _maxNumPixels, networkTexture->getTextureType(), shouldCompress, target); if (!textureAndSize.first) { - QMetaObject::invokeMethod(resource.data(), "setImage", + QMetaObject::invokeMethod(resource.get(), "setImage", Q_ARG(gpu::TexturePointer, textureAndSize.first), Q_ARG(int, 0), Q_ARG(int, 0)); @@ -1326,7 +1340,7 @@ void ImageReader::read() { textureAndSize = textureCache->cacheTextureByHash(hash, textureAndSize); } - QMetaObject::invokeMethod(resource.data(), "setImage", + QMetaObject::invokeMethod(resource.get(), "setImage", Q_ARG(gpu::TexturePointer, textureAndSize.first), Q_ARG(int, textureAndSize.second.x), Q_ARG(int, textureAndSize.second.y)); @@ -1402,7 +1416,7 @@ NetworkTexturePointer TextureCache::getTextureByUUID(const QString& uuid) { if (!quuid.isNull()) { // We mark this as a resource texture because it's just a reference to another texture. The source // texture will be marked properly - NetworkTexturePointer toReturn = NetworkTexturePointer::create(uuid, true); + NetworkTexturePointer toReturn = std::make_shared(uuid, true); toReturn->setImageOperator(Texture::getTextureForUUIDOperator(QUuid(uuid))); return toReturn; } diff --git a/libraries/material-networking/src/material-networking/TextureCache.h b/libraries/material-networking/src/material-networking/TextureCache.h index d2279fbc89d..4df41117da7 100644 --- a/libraries/material-networking/src/material-networking/TextureCache.h +++ b/libraries/material-networking/src/material-networking/TextureCache.h @@ -74,7 +74,7 @@ class NetworkTexture : public Resource, public Texture { void setExtra(void* extra) override; signals: - void networkTextureCreated(const QWeakPointer& self); + void networkTextureCreated(const std::weak_ptr& self); public slots: void ktxInitialDataRequestFinished(); @@ -152,9 +152,9 @@ public slots: friend class TextureCache; }; -using NetworkTexturePointer = QSharedPointer; +using NetworkTexturePointer = std::shared_ptr; -Q_DECLARE_METATYPE(QWeakPointer) +Q_DECLARE_METATYPE(std::weak_ptr) /// Stores cached textures, including render-to-texture targets. @@ -214,8 +214,8 @@ class TextureCache : public ResourceCache, public Dependency { // Overload ResourceCache::prefetch to allow specifying texture type for loads Q_INVOKABLE ScriptableResource* prefetch(const QUrl& url, int type, int maxNumPixels = ABSOLUTE_MAX_TEXTURE_NUM_PIXELS, image::ColorChannel sourceChannel = image::ColorChannel::NONE); - virtual QSharedPointer createResource(const QUrl& url) override; - QSharedPointer createResourceCopy(const QSharedPointer& resource) override; + virtual std::shared_ptr createResource(const QUrl& url) override; + std::shared_ptr createResourceCopy(const std::shared_ptr& resource) override; private: friend class ImageReader; diff --git a/libraries/model-networking/src/model-networking/ModelCache.cpp b/libraries/model-networking/src/model-networking/ModelCache.cpp index 7ad10d4e9ff..f4ac0c4e7f7 100644 --- a/libraries/model-networking/src/model-networking/ModelCache.cpp +++ b/libraries/model-networking/src/model-networking/ModelCache.cpp @@ -98,7 +98,7 @@ namespace std { class GeometryReader : public QRunnable { public: - GeometryReader(const ModelLoader& modelLoader, QWeakPointer& resource, const QUrl& url, const GeometryMappingPair& mapping, + GeometryReader(const ModelLoader& modelLoader, std::weak_ptr resource, const QUrl& url, const GeometryMappingPair& mapping, const QByteArray& data, bool combineParts, const QString& webMediaType) : _modelLoader(modelLoader), _resource(resource), _url(url), _mapping(mapping), _data(data), _combineParts(combineParts), _webMediaType(webMediaType) { @@ -109,7 +109,7 @@ class GeometryReader : public QRunnable { private: ModelLoader _modelLoader; - QWeakPointer _resource; + std::weak_ptr _resource; QUrl _url; // QT6TODO: I'm not sure if _mapping should be QHash or QMultiHash GeometryMappingPair _mapping; @@ -131,7 +131,8 @@ void GeometryReader::run() { QThread::currentThread()->setPriority(originalPriority); }); - if (!_resource.toStrongRef().data()) { + if (!_resource.lock()) { + qCWarning(modelnetworking) << "Abandoning load of" << _url << "; could not get strong ref"; return; } @@ -141,7 +142,7 @@ void GeometryReader::run() { } // Ensure the resource has not been deleted - auto resource = _resource.toStrongRef(); + auto resource = _resource.lock(); if (!resource) { qCWarning(modelnetworking) << "Abandoning load of" << _url << "; could not get strong ref"; return; @@ -193,19 +194,19 @@ void GeometryReader::run() { auto processedHFMModel = modelBaker.getHFMModel(); auto materialMapping = modelBaker.getMaterialMapping(); - QMetaObject::invokeMethod(resource.data(), "setGeometryDefinition", + QMetaObject::invokeMethod(resource.get(), "setGeometryDefinition", Q_ARG(HFMModel::Pointer, processedHFMModel), Q_ARG(MaterialMapping, materialMapping)); } catch (const std::exception&) { - auto resource = _resource.toStrongRef(); + auto resource = _resource.lock(); if (resource) { - QMetaObject::invokeMethod(resource.data(), "finishedLoading", + QMetaObject::invokeMethod(resource.get(), "finishedLoading", Q_ARG(bool, false)); } } catch (QString& e) { qCWarning(modelnetworking) << "Exception while loading model --" << e; - auto resource = _resource.toStrongRef(); + auto resource = _resource.lock(); if (resource) { - QMetaObject::invokeMethod(resource.data(), "finishedLoading", + QMetaObject::invokeMethod(resource.get(), "finishedLoading", Q_ARG(bool, false)); } } @@ -281,7 +282,8 @@ void GeometryResource::downloadFinished(const QByteArray& data) { GeometryExtra extra { GeometryMappingPair(base, _mapping), _textureBaseURL, false }; // Get the raw GeometryResource - _geometryResource = modelCache->getResource(url, QUrl(), &extra, std::hash()(extra)).staticCast(); + _geometryResource = std::dynamic_pointer_cast(modelCache->getResource(url, QUrl(), &extra, std::hash()(extra))); + Q_ASSERT(_geometryResource); // Avoid caching nested resources - their references will be held by the parent _geometryResource->_isCacheable = false; @@ -292,7 +294,7 @@ void GeometryResource::downloadFinished(const QByteArray& data) { disconnect(_connection); } - _connection = connect(_geometryResource.data(), &Resource::finished, this, &GeometryResource::onGeometryMappingLoaded); + _connection = connect(_geometryResource.get(), &Resource::finished, this, &GeometryResource::onGeometryMappingLoaded); } } } else { @@ -300,7 +302,7 @@ void GeometryResource::downloadFinished(const QByteArray& data) { _url = _effectiveBaseURL; _textureBaseURL = _effectiveBaseURL; } - QThreadPool::globalInstance()->start(new GeometryReader(_modelLoader, _self, _effectiveBaseURL, _mappingPair, data, _combineParts, _request->getWebMediaType())); + QThreadPool::globalInstance()->start(new GeometryReader(_modelLoader, weak_from_this(), _effectiveBaseURL, _mappingPair, data, _combineParts, _request->getWebMediaType())); } } @@ -401,18 +403,20 @@ ModelCache::ModelCache() { modelFormatRegistry->addFormat(GLTFSerializer()); } -QSharedPointer ModelCache::createResource(const QUrl& url) { - return QSharedPointer(new GeometryResource(url, _modelLoader), &GeometryResource::deleter); +std::shared_ptr ModelCache::createResource(const QUrl& url) { + return std::shared_ptr(new GeometryResource(url, _modelLoader), GeometryResource::sharedPtrDeleter); } -QSharedPointer ModelCache::createResourceCopy(const QSharedPointer& resource) { - return QSharedPointer(new GeometryResource(*resource.staticCast()), &GeometryResource::deleter); +std::shared_ptr ModelCache::createResourceCopy(const std::shared_ptr& resource) { + auto geometryResource = std::dynamic_pointer_cast(resource); + Q_ASSERT(geometryResource); + return std::shared_ptr(new GeometryResource(*geometryResource), GeometryResource::sharedPtrDeleter); } GeometryResource::Pointer ModelCache::getGeometryResource(const QUrl& url, const GeometryMappingPair& mapping, const QUrl& textureBaseUrl) { bool combineParts = true; GeometryExtra geometryExtra = { mapping, textureBaseUrl, combineParts }; - GeometryResource::Pointer resource = getResource(url, QUrl(), &geometryExtra, std::hash()(geometryExtra)).staticCast(); + GeometryResource::Pointer resource = std::dynamic_pointer_cast(getResource(url, QUrl(), &geometryExtra, std::hash()(geometryExtra))); if (resource) { if (resource->isLoaded() && resource->shouldSetTextures()) { resource->setTextures(); @@ -426,7 +430,7 @@ GeometryResource::Pointer ModelCache::getCollisionGeometryResource(const QUrl& u const QUrl& textureBaseUrl) { bool combineParts = false; GeometryExtra geometryExtra = { mapping, textureBaseUrl, combineParts }; - GeometryResource::Pointer resource = getResource(url, QUrl(), &geometryExtra, std::hash()(geometryExtra)).staticCast(); + GeometryResource::Pointer resource = std::dynamic_pointer_cast(getResource(url, QUrl(), &geometryExtra, std::hash()(geometryExtra))); if (resource) { if (resource->isLoaded() && resource->shouldSetTextures()) { resource->setTextures(); @@ -545,16 +549,16 @@ const std::shared_ptr Geometry::getShapeMaterial(int partID) co } void GeometryResourceWatcher::startWatching() { - connect(_resource.data(), &Resource::finished, this, &GeometryResourceWatcher::resourceFinished); - connect(_resource.data(), &Resource::onRefresh, this, &GeometryResourceWatcher::resourceRefreshed); + connect(_resource.get(), &Resource::finished, this, &GeometryResourceWatcher::resourceFinished); + connect(_resource.get(), &Resource::onRefresh, this, &GeometryResourceWatcher::resourceRefreshed); if (_resource->isLoaded()) { resourceFinished(!_resource->getURL().isEmpty()); } } void GeometryResourceWatcher::stopWatching() { - disconnect(_resource.data(), &Resource::finished, this, &GeometryResourceWatcher::resourceFinished); - disconnect(_resource.data(), &Resource::onRefresh, this, &GeometryResourceWatcher::resourceRefreshed); + disconnect(_resource.get(), &Resource::finished, this, &GeometryResourceWatcher::resourceFinished); + disconnect(_resource.get(), &Resource::onRefresh, this, &GeometryResourceWatcher::resourceRefreshed); } void GeometryResourceWatcher::setResource(GeometryResource::Pointer resource) { diff --git a/libraries/model-networking/src/model-networking/ModelCache.h b/libraries/model-networking/src/model-networking/ModelCache.h index 114b6927315..9969ceba449 100644 --- a/libraries/model-networking/src/model-networking/ModelCache.h +++ b/libraries/model-networking/src/model-networking/ModelCache.h @@ -83,10 +83,11 @@ class Geometry { class GeometryResource : public Resource, public Geometry { Q_OBJECT public: - using Pointer = QSharedPointer; + using Pointer = std::shared_ptr; GeometryResource(const QUrl& url, const ModelLoader& modelLoader) : Resource(url), _modelLoader(modelLoader) {} GeometryResource(const GeometryResource& other); + virtual ~GeometryResource() {} QString getType() const override { return "Geometry"; } @@ -175,8 +176,8 @@ class ModelCache : public ResourceCache, public Dependency { protected: friend class GeometryResource; - virtual QSharedPointer createResource(const QUrl& url) override; - QSharedPointer createResourceCopy(const QSharedPointer& resource) override; + virtual std::shared_ptr createResource(const QUrl& url) override; + std::shared_ptr createResourceCopy(const std::shared_ptr& resource) override; private: ModelCache(); diff --git a/libraries/networking/src/ResourceCache.cpp b/libraries/networking/src/ResourceCache.cpp index 4d9f81131f6..d07ea77f555 100644 --- a/libraries/networking/src/ResourceCache.cpp +++ b/libraries/networking/src/ResourceCache.cpp @@ -30,7 +30,7 @@ #include "NetworkLogging.h" #include "NodeList.h" -bool ResourceCacheSharedItems::appendRequest(QWeakPointer resource, float priority) { +bool ResourceCacheSharedItems::appendRequest(std::weak_ptr resource, float priority) { Lock lock(_mutex); if ((uint32_t)_loadingRequests.size() < _requestLimit) { _loadingRequests.append({ resource, priority }); @@ -51,11 +51,11 @@ uint32_t ResourceCacheSharedItems::getRequestLimit() const { return _requestLimit; } -QList> ResourceCacheSharedItems::getPendingRequests() const { - QList> result; +QList> ResourceCacheSharedItems::getPendingRequests() const { + QList> result; Lock lock(_mutex); - foreach (QWeakPointer resource, _pendingRequests) { + foreach (std::weak_ptr resource, _pendingRequests) { auto locked = resource.lock(); if (locked) { result.append(locked); @@ -70,8 +70,8 @@ uint32_t ResourceCacheSharedItems::getPendingRequestsCount() const { return _pendingRequests.size(); } -QList, float>> ResourceCacheSharedItems::getLoadingRequests() const { - QList, float>> result; +QList, float>> ResourceCacheSharedItems::getLoadingRequests() const { + QList, float>> result; Lock lock(_mutex); foreach(auto resourcePair, _loadingRequests) { @@ -89,16 +89,16 @@ uint32_t ResourceCacheSharedItems::getLoadingRequestsCount() const { return _loadingRequests.size(); } -void ResourceCacheSharedItems::removeRequest(QWeakPointer resource) { +void ResourceCacheSharedItems::removeRequest(std::weak_ptr resource) { Lock lock(_mutex); // resource can only be removed if it still has a ref-count, as // QWeakPointer has no operator== implementation for two weak ptrs, so // manually loop in case resource has been freed. for (int i = 0; i < _loadingRequests.size();) { - auto request = _loadingRequests.at(i).first; + auto request = _loadingRequests.at(i).first.lock(); // Clear our resource and any freed resources - if (!request || request.toStrongRef().data() == resource.toStrongRef().data()) { + if (!request || request.get() == resource.lock().get()) { _loadingRequests.removeAt(i); continue; } @@ -106,11 +106,11 @@ void ResourceCacheSharedItems::removeRequest(QWeakPointer resource) { } } -std::pair, float> ResourceCacheSharedItems::getHighestPendingRequest() { +std::pair, float> ResourceCacheSharedItems::getHighestPendingRequest() { // look for the highest priority pending request int highestIndex = -1; float highestPriority = -FLT_MAX; - QSharedPointer highestResource; + std::shared_ptr highestResource; Lock lock(_mutex); bool currentHighestIsFile = false; @@ -237,16 +237,16 @@ ScriptableResource* ResourceCache::prefetch(const QUrl& url, void* extra, size_t result->finished(!resource->_failedToLoad); } else { result->_progressConnection = connect( - resource.data(), &Resource::onProgress, + resource.get(), &Resource::onProgress, result, &ScriptableResource::progressChanged); result->_loadingConnection = connect( - resource.data(), &Resource::loading, + resource.get(), &Resource::loading, result, &ScriptableResource::loadingChanged); result->_loadedConnection = connect( - resource.data(), &Resource::loaded, + resource.get(), &Resource::loaded, result, &ScriptableResource::loadedChanged); result->_finishedConnection = connect( - resource.data(), &Resource::finished, + resource.get(), &Resource::finished, result, &ScriptableResource::finished); } @@ -302,7 +302,7 @@ void ResourceCache::refreshAll() { clearUnusedResources(); resetUnusedResourceCounter(); - QHash>> allResources; + QHash>> allResources; { QReadLocker locker(&_resourcesLock); allResources = _resources; @@ -351,8 +351,8 @@ void ResourceCache::setRequestLimit(uint32_t limit) { } } -QSharedPointer ResourceCache::getResource(const QUrl& url, const QUrl& fallback, void* extra, size_t extraHash) { - QSharedPointer resource; +std::shared_ptr ResourceCache::getResource(const QUrl& url, const QUrl& fallback, void* extra, size_t extraHash) { + std::shared_ptr resource; { QWriteLocker locker(&_resourcesLock); auto& resourcesWithExtraHash = _resources[url]; @@ -367,10 +367,9 @@ QSharedPointer ResourceCache::getResource(const QUrl& url, const QUrl& resource = createResourceCopy(oldResource); resource->setExtra(extra); resource->setExtraHash(extraHash); - resource->setSelf(resource); resource->setCache(this); resource->moveToThread(qApp->thread()); - connect(resource.data(), &Resource::updateSize, this, &ResourceCache::updateTotalSize); + connect(resource.get(), &Resource::updateSize, this, &ResourceCache::updateTotalSize); resourcesWithExtraHash.insert(extraHash, resource); resource->ensureLoading(); } @@ -388,10 +387,9 @@ QSharedPointer ResourceCache::getResource(const QUrl& url, const QUrl& resource = createResource(url); resource->setExtra(extra); resource->setExtraHash(extraHash); - resource->setSelf(resource); resource->setCache(this); resource->moveToThread(qApp->thread()); - connect(resource.data(), &Resource::updateSize, this, &ResourceCache::updateTotalSize); + connect(resource.get(), &Resource::updateSize, this, &ResourceCache::updateTotalSize); { QWriteLocker locker(&_resourcesLock); _resources[url].insert(extraHash, resource); @@ -410,7 +408,7 @@ void ResourceCache::setUnusedResourceCacheSize(qint64 unusedResourcesMaxSize) { resetUnusedResourceCounter(); } -void ResourceCache::addUnusedResource(const QSharedPointer& resource) { +void ResourceCache::addUnusedResource(const std::shared_ptr& resource) { // If it doesn't fit or its size is unknown, remove it from the cache. if (resource->getBytes() == 0 || resource->getBytes() > _unusedResourcesMaxSize) { resource->setCache(nullptr); @@ -431,7 +429,7 @@ void ResourceCache::addUnusedResource(const QSharedPointer& resource) resetUnusedResourceCounter(); } -void ResourceCache::removeUnusedResource(const QSharedPointer& resource) { +void ResourceCache::removeUnusedResource(const std::shared_ptr& resource) { QWriteLocker locker(&_unusedResourcesLock); if (_unusedResources.contains(resource->getLRUKey())) { _unusedResources.remove(resource->getLRUKey()); @@ -447,7 +445,7 @@ void ResourceCache::reserveUnusedResource(qint64 resourceSize) { while (!_unusedResources.empty() && _unusedResourcesSize + resourceSize > _unusedResourcesMaxSize) { // unload the oldest resource - QMap >::iterator it = _unusedResources.begin(); + QMap >::iterator it = _unusedResources.begin(); it.value()->setCache(nullptr); auto size = it.value()->getBytes(); @@ -466,7 +464,7 @@ void ResourceCache::clearUnusedResources() { // list on destruction, so keep clearing until there are no references left QWriteLocker locker(&_unusedResourcesLock); while (!_unusedResources.isEmpty()) { - foreach (const QSharedPointer& resource, _unusedResources) { + foreach (const std::shared_ptr& resource, _unusedResources) { resource->setCache(nullptr); } _unusedResources.clear(); @@ -519,7 +517,7 @@ void ResourceCache::updateTotalSize(const qint64& deltaSize) { emit dirty(); } -QList, float>> ResourceCache::getLoadingRequests() { +QList, float>> ResourceCache::getLoadingRequests() { return DependencyManager::get()->getLoadingRequests(); } @@ -531,8 +529,8 @@ uint32_t ResourceCache::getLoadingRequestCount() { return DependencyManager::get()->getLoadingRequestsCount(); } -bool ResourceCache::attemptRequest(QSharedPointer resource, float priority) { - Q_ASSERT(!resource.isNull()); +bool ResourceCache::attemptRequest(std::shared_ptr resource, float priority) { + Q_ASSERT(resource); auto sharedItems = DependencyManager::get(); if (sharedItems->appendRequest(resource, priority)) { @@ -542,7 +540,7 @@ bool ResourceCache::attemptRequest(QSharedPointer resource, float prio return false; } -void ResourceCache::requestCompleted(QWeakPointer resource) { +void ResourceCache::requestCompleted(std::weak_ptr resource) { auto sharedItems = DependencyManager::get(); sharedItems->removeRequest(resource); @@ -577,6 +575,7 @@ Resource::Resource(const Resource& other) : _bytes(other._bytes), _requestID(++requestID), _extraHash(other._extraHash) { + // TODO: should we add an assert here to make sure this gets deleted on correct thread? if (!other._loaded) { _startedLoading = false; } @@ -591,11 +590,12 @@ Resource::Resource(const QUrl& url) : } Resource::~Resource() { + Q_ASSERT(QThread::currentThread() == thread()); if (_request) { _request->disconnect(this); _request->deleteLater(); _request = nullptr; - ResourceCache::requestCompleted(_self); + ResourceCache::requestCompleted(weak_from_this()); } } @@ -637,7 +637,7 @@ void Resource::refresh() { _request->disconnect(this); _request->deleteLater(); _request = nullptr; - ResourceCache::requestCompleted(_self); + ResourceCache::requestCompleted(shared_from_this()); } _activeUrl = _url; @@ -654,8 +654,7 @@ void Resource::allReferencesCleared() { if (_cache && isCacheable()) { // create and reinsert new shared pointer - QSharedPointer self(this, &Resource::deleter); - setSelf(self); + std::shared_ptr self(this, Resource::sharedPtrDeleter); reinsert(); // add to the unused list @@ -718,9 +717,9 @@ void Resource::attemptRequest() { << "- retrying asset load - attempt" << _attempts << " of " << MAX_ATTEMPTS; } - auto self = _self.lock(); - if (self) { - ResourceCache::attemptRequest(self); + auto self = shared_from_this(); + if (shared_from_this()) { + ResourceCache::attemptRequest(shared_from_this()); } } @@ -741,7 +740,7 @@ void Resource::setSize(const qint64& bytes) { void Resource::reinsert() { QWriteLocker locker(&_cache->_resourcesLock); - _cache->_resources[_url].insert(_extraHash, _self); + _cache->_resources[_url].insert(_extraHash, weak_from_this()); } @@ -758,7 +757,7 @@ void Resource::makeRequest() { this, _activeUrl, true, -1, "Resource::makeRequest"); if (!_request) { - ResourceCache::requestCompleted(_self); + ResourceCache::requestCompleted(weak_from_this()); finishedLoading(false); PROFILE_ASYNC_END(resource, "Resource:" + getType(), QString::number(_requestID)); return; @@ -793,7 +792,7 @@ void Resource::handleReplyFinished() { { "from_cache", false }, { "size_mb", _bytesTotal / 1000000.0 } }); - ResourceCache::requestCompleted(_self); + ResourceCache::requestCompleted(weak_from_this()); return; } @@ -803,8 +802,9 @@ void Resource::handleReplyFinished() { }); // Make sure we keep the Resource alive here - auto self = _self.lock(); - ResourceCache::requestCompleted(_self); + auto self = shared_from_this(); + Q_ASSERT(self); + ResourceCache::requestCompleted(weak_from_this()); auto result = _request->getResult(); if (result == ResourceRequest::Success) { diff --git a/libraries/networking/src/ResourceCache.h b/libraries/networking/src/ResourceCache.h index 3b0a181f00d..c18590c49c9 100644 --- a/libraries/networking/src/ResourceCache.h +++ b/libraries/networking/src/ResourceCache.h @@ -22,9 +22,7 @@ #include #include #include -#include #include -#include #include #include @@ -67,14 +65,14 @@ class ResourceCacheSharedItems : public Dependency { using Lock = std::unique_lock; public: - bool appendRequest(QWeakPointer newRequest, float priority); - void removeRequest(QWeakPointer doneRequest); + bool appendRequest(std::weak_ptr newRequest, float priority); + void removeRequest(std::weak_ptr doneRequest); void setRequestLimit(uint32_t limit); uint32_t getRequestLimit() const; - QList> getPendingRequests() const; - std::pair, float> getHighestPendingRequest(); + QList> getPendingRequests() const; + std::pair, float> getHighestPendingRequest(); uint32_t getPendingRequestsCount() const; - QList, float>> getLoadingRequests() const; + QList, float>> getLoadingRequests() const; uint32_t getLoadingRequestsCount() const; void clear(); @@ -82,8 +80,8 @@ class ResourceCacheSharedItems : public Dependency { ResourceCacheSharedItems() = default; mutable Mutex _mutex; - QList> _pendingRequests; - QList, float>> _loadingRequests; + QList> _pendingRequests; + QList, float>> _loadingRequests; const uint32_t DEFAULT_REQUEST_LIMIT = 10; uint32_t _requestLimit { DEFAULT_REQUEST_LIMIT }; }; @@ -142,7 +140,7 @@ class ScriptableResource : public QObject { const QUrl& getURL() const { return _url; } int getState() const { return (int)_state; } - const QSharedPointer& getResource() const { return _resource; } + const std::shared_ptr& getResource() const { return _resource; } bool isInScript() const; void setInScript(bool isInScript); @@ -180,7 +178,7 @@ private slots: friend class ResourceCache; // Holds a ref to the resource to keep it in scope - QSharedPointer _resource; + std::shared_ptr _resource; QMetaObject::Connection _progressConnection; QMetaObject::Connection _loadingConnection; @@ -217,7 +215,7 @@ class ResourceCache : public QObject { void setUnusedResourceCacheSize(qint64 unusedResourcesMaxSize); qint64 getUnusedResourceCacheSize() const { return _unusedResourcesMaxSize; } - static QList, float>> getLoadingRequests(); + static QList, float>> getLoadingRequests(); static uint32_t getPendingRequestCount(); static uint32_t getLoadingRequestCount(); @@ -246,8 +244,8 @@ protected slots: /// returns an empty smart pointer and loads its asynchronously. /// \param fallback a fallback URL to load if the desired one is unavailable // FIXME: std::numeric_limits::max() could be a valid extraHash - QSharedPointer getResource(const QUrl& url, const QUrl& fallback = QUrl()) { return getResource(url, fallback, nullptr, std::numeric_limits::max()); } - QSharedPointer getResource(const QUrl& url, const QUrl& fallback, void* extra, size_t extraHash); + std::shared_ptr getResource(const QUrl& url, const QUrl& fallback = QUrl()) { return getResource(url, fallback, nullptr, std::numeric_limits::max()); } + std::shared_ptr getResource(const QUrl& url, const QUrl& fallback, void* extra, size_t extraHash); private slots: void clearATPAssets(); @@ -261,16 +259,16 @@ private slots: Q_INVOKABLE ScriptableResource* prefetch(const QUrl& url) { return prefetch(url, nullptr, std::numeric_limits::max()); } /// Creates a new resource. - virtual QSharedPointer createResource(const QUrl& url) = 0; - virtual QSharedPointer createResourceCopy(const QSharedPointer& resource) = 0; + virtual std::shared_ptr createResource(const QUrl& url) = 0; + virtual std::shared_ptr createResourceCopy(const std::shared_ptr& resource) = 0; - void addUnusedResource(const QSharedPointer& resource); - void removeUnusedResource(const QSharedPointer& resource); + void addUnusedResource(const std::shared_ptr& resource); + void removeUnusedResource(const std::shared_ptr& resource); /// Attempt to load a resource if requests are below the limit, otherwise queue the resource for loading /// \return true if the resource began loading, otherwise false if the resource is in the pending queue - static bool attemptRequest(QSharedPointer resource, float priority = NAN); - static void requestCompleted(QWeakPointer resource); + static bool attemptRequest(std::shared_ptr resource, float priority = NAN); + static void requestCompleted(std::weak_ptr resource); static bool attemptHighestPriorityRequest(); private: @@ -285,7 +283,7 @@ private slots: void resetResourceCounters(); // Resources - QHash>> _resources; + QHash>> _resources; QReadWriteLock _resourcesLock { QReadWriteLock::Recursive }; int _lastLRUKey = 0; @@ -293,7 +291,7 @@ private slots: std::atomic _totalResourcesSize { 0 }; // Cached resources - QMap> _unusedResources; + QMap> _unusedResources; QReadWriteLock _unusedResourcesLock { QReadWriteLock::Recursive }; qint64 _unusedResourcesMaxSize = DEFAULT_UNUSED_MAX_SIZE; @@ -407,7 +405,7 @@ class ScriptableResourceCache : public QObject { }; /// Base class for resources. -class Resource : public QObject { +class Resource : public QObject, public std::enable_shared_from_this { Q_OBJECT public: @@ -451,10 +449,9 @@ class Resource : public QObject { /// Refreshes the resource. virtual void refresh(); - void setSelf(const QWeakPointer& self) { _self = self; } - void setCache(ResourceCache* cache) { _cache = cache; } + static void sharedPtrDeleter(Resource *object) { object->deleter(); }; virtual void deleter() { allReferencesCleared(); } const QUrl& getURL() const { return _url; } @@ -533,7 +530,6 @@ protected slots: bool _loaded = false; QHash, std::function> _loadPriorityOperators; - QWeakPointer _self; QPointer _cache; qint64 _bytesReceived { 0 }; diff --git a/libraries/plugins/src/plugins/DisplayPlugin.h b/libraries/plugins/src/plugins/DisplayPlugin.h index e2b27f4b268..3ede2c41e1c 100644 --- a/libraries/plugins/src/plugins/DisplayPlugin.h +++ b/libraries/plugins/src/plugins/DisplayPlugin.h @@ -68,7 +68,7 @@ namespace gpu { } class NetworkTexture; -using NetworkTexturePointer = QSharedPointer; +using NetworkTexturePointer = std::shared_ptr; typedef struct __GLsync *GLsync; // Stereo display functionality diff --git a/libraries/procedural/src/procedural/ProceduralMaterialCache.cpp b/libraries/procedural/src/procedural/ProceduralMaterialCache.cpp index 2f5815466c6..b5d98c36d4d 100644 --- a/libraries/procedural/src/procedural/ProceduralMaterialCache.cpp +++ b/libraries/procedural/src/procedural/ProceduralMaterialCache.cpp @@ -804,15 +804,19 @@ std::pair> NetworkMaterialResource } NetworkMaterialResourcePointer MaterialCache::getMaterial(const QUrl& url) { - return ResourceCache::getResource(url).staticCast(); + auto networkMaterialResource = std::dynamic_pointer_cast(getResource(url)); + Q_ASSERT(networkMaterialResource); + return networkMaterialResource; } -QSharedPointer MaterialCache::createResource(const QUrl& url) { - return QSharedPointer(new NetworkMaterialResource(url), &Resource::deleter); +std::shared_ptr MaterialCache::createResource(const QUrl& url) { + return std::shared_ptr(new NetworkMaterialResource(url), Resource::sharedPtrDeleter); } -QSharedPointer MaterialCache::createResourceCopy(const QSharedPointer& resource) { - return QSharedPointer(new NetworkMaterialResource(*resource.staticCast()), &Resource::deleter); +std::shared_ptr MaterialCache::createResourceCopy(const std::shared_ptr& resource) { + auto networkMaterialResource = std::dynamic_pointer_cast(resource); + Q_ASSERT(networkMaterialResource); + return std::shared_ptr(new NetworkMaterialResource(*networkMaterialResource), Resource::sharedPtrDeleter); } NetworkMaterial::NetworkMaterial(const NetworkMaterial& m) : @@ -1137,6 +1141,9 @@ bool NetworkMaterial::isMissingTexture() { // Failed texture downloads need to be considered as 'loaded' // or the object will never fade in auto gpuTexture = texture->getGPUTexture(); + if (gpuTexture) { + Q_ASSERT(!gpuTexture->wasDeleted); + } bool finished = texture->isFailed() || (texture->isLoaded() && gpuTexture && gpuTexture->isDefined()); if (!finished) { return true; diff --git a/libraries/procedural/src/procedural/ProceduralMaterialCache.h b/libraries/procedural/src/procedural/ProceduralMaterialCache.h index 9b0adabb5ae..f9e1c6f4cdb 100644 --- a/libraries/procedural/src/procedural/ProceduralMaterialCache.h +++ b/libraries/procedural/src/procedural/ProceduralMaterialCache.h @@ -203,6 +203,7 @@ class NetworkMaterialResource : public Resource { public: NetworkMaterialResource() : Resource() {} NetworkMaterialResource(const QUrl& url); + virtual ~NetworkMaterialResource() {} QString getType() const override { return "NetworkMaterial"; } @@ -228,7 +229,7 @@ class NetworkMaterialResource : public Resource { static std::pair> parseJSONMaterial(const QJsonValue& materialJSONValue, const QUrl& baseUrl = QUrl()); }; -using NetworkMaterialResourcePointer = QSharedPointer; +using NetworkMaterialResourcePointer = std::shared_ptr; using MaterialMapping = std::vector>; Q_DECLARE_METATYPE(MaterialMapping) @@ -240,8 +241,8 @@ class MaterialCache : public ResourceCache, public Dependency { NetworkMaterialResourcePointer getMaterial(const QUrl& url); protected: - virtual QSharedPointer createResource(const QUrl& url) override; - QSharedPointer createResourceCopy(const QSharedPointer& resource) override; + virtual std::shared_ptr createResource(const QUrl& url) override; + std::shared_ptr createResourceCopy(const std::shared_ptr& resource) override; }; #endif diff --git a/libraries/recording/src/recording/ClipCache.cpp b/libraries/recording/src/recording/ClipCache.cpp index 621bdbbccd6..933eb018e97 100644 --- a/libraries/recording/src/recording/ClipCache.cpp +++ b/libraries/recording/src/recording/ClipCache.cpp @@ -51,14 +51,19 @@ NetworkClipLoaderPointer ClipCache::getClipLoader(const QUrl& url) { return result; } - return getResource(url).staticCast(); + auto clipLoader = std::dynamic_pointer_cast(getResource(url)); + Q_ASSERT(clipLoader); + + return clipLoader; } -QSharedPointer ClipCache::createResource(const QUrl& url) { +std::shared_ptr ClipCache::createResource(const QUrl& url) { qCDebug(recordingLog) << "Loading recording at" << url; - return QSharedPointer(new NetworkClipLoader(url), &Resource::deleter); + return std::shared_ptr(new NetworkClipLoader(url), Resource::sharedPtrDeleter); } -QSharedPointer ClipCache::createResourceCopy(const QSharedPointer& resource) { - return QSharedPointer(new NetworkClipLoader(*resource.staticCast()), &Resource::deleter); +std::shared_ptr ClipCache::createResourceCopy(const std::shared_ptr& resource) { + auto clipLoader = std::dynamic_pointer_cast(resource); + Q_ASSERT(clipLoader); + return std::shared_ptr(new NetworkClipLoader(*clipLoader), Resource::sharedPtrDeleter); } \ No newline at end of file diff --git a/libraries/recording/src/recording/ClipCache.h b/libraries/recording/src/recording/ClipCache.h index e909af7e70a..1d780be8da8 100644 --- a/libraries/recording/src/recording/ClipCache.h +++ b/libraries/recording/src/recording/ClipCache.h @@ -36,6 +36,7 @@ class NetworkClipLoader : public Resource { public: NetworkClipLoader(const QUrl& url); NetworkClipLoader(const NetworkClipLoader& other) : Resource(other), _clip(other._clip) {} + virtual ~NetworkClipLoader() {} virtual void downloadFinished(const QByteArray& data) override; ClipPointer getClip() { return _clip; } @@ -48,7 +49,7 @@ class NetworkClipLoader : public Resource { const NetworkClip::Pointer _clip; }; -using NetworkClipLoaderPointer = QSharedPointer; +using NetworkClipLoaderPointer = std::shared_ptr; class ClipCache : public ResourceCache, public Dependency { Q_OBJECT @@ -58,8 +59,8 @@ public slots: NetworkClipLoaderPointer getClipLoader(const QUrl& url); protected: - virtual QSharedPointer createResource(const QUrl& url) override; - QSharedPointer createResourceCopy(const QSharedPointer& resource) override; + virtual std::shared_ptr createResource(const QUrl& url) override; + std::shared_ptr createResourceCopy(const std::shared_ptr& resource) override; private: ClipCache(QObject* parent = nullptr); diff --git a/libraries/recording/src/recording/RecordingScriptingInterface.cpp b/libraries/recording/src/recording/RecordingScriptingInterface.cpp index f5da3522026..68f6f55e28e 100644 --- a/libraries/recording/src/recording/RecordingScriptingInterface.cpp +++ b/libraries/recording/src/recording/RecordingScriptingInterface.cpp @@ -90,7 +90,7 @@ void RecordingScriptingInterface::loadRecording(const QString& url, const Script // hold a strong pointer to the loading clip so that it has a chance to load _clipLoaders.insert(clipLoader); - auto weakClipLoader = clipLoader.toWeakRef(); + std::weak_ptr weakClipLoader = clipLoader; auto manager = callback.engine()->manager(); if (!manager) { @@ -99,10 +99,11 @@ void RecordingScriptingInterface::loadRecording(const QString& url, const Script } // when clip loaded, call the callback with the URL and success boolean - connect(clipLoader.data(), &recording::NetworkClipLoader::clipLoaded, manager, + connect(clipLoader.get(), &recording::NetworkClipLoader::clipLoaded, manager, [this, weakClipLoader, url, callback]() mutable { - if (auto clipLoader = weakClipLoader.toStrongRef()) { + if (auto clipLoader = weakClipLoader.lock()) { + Q_ASSERT(clipLoader); qCDebug(scriptengine) << "Loaded recording from" << url; playClip(clipLoader, url, callback); @@ -113,7 +114,7 @@ void RecordingScriptingInterface::loadRecording(const QString& url, const Script }); // when clip load fails, call the callback with the URL and failure boolean - connect(clipLoader.data(), &recording::NetworkClipLoader::failed, manager, + connect(clipLoader.get(), &recording::NetworkClipLoader::failed, manager, [this, weakClipLoader, url, callback](QNetworkReply::NetworkError error) mutable { qCDebug(scriptengine) << "Failed to load recording from\"" << url << '"'; @@ -123,7 +124,8 @@ void RecordingScriptingInterface::loadRecording(const QString& url, const Script callback.call(ScriptValue(), args); } - if (auto clipLoader = weakClipLoader.toStrongRef()) { + if (auto clipLoader = weakClipLoader.lock()) { + Q_ASSERT(clipLoader); // drop out strong pointer to this clip so it is cleaned up _clipLoaders.remove(clipLoader); } diff --git a/libraries/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp index a4fd959a6c2..63345dd73bf 100644 --- a/libraries/render-utils/src/Model.cpp +++ b/libraries/render-utils/src/Model.cpp @@ -1783,7 +1783,7 @@ void Model::applyMaterialMapping() { if (networkMaterialResource->isLoaded()) { materialLoaded(); } else { - connect(networkMaterialResource.data(), &Resource::finished, materialLoaded); + connect(networkMaterialResource.get(), &Resource::finished, materialLoaded); } } } diff --git a/libraries/script-engine/src/ScriptManager.cpp b/libraries/script-engine/src/ScriptManager.cpp index 6f1a8c18ca0..ec383191dc5 100644 --- a/libraries/script-engine/src/ScriptManager.cpp +++ b/libraries/script-engine/src/ScriptManager.cpp @@ -676,7 +676,7 @@ static ScriptValue scriptableResourceToScriptValue(ScriptEngine* engine, auto manager = engine->manager(); if (data && manager && !resource->isInScript()) { resource->setInScript(true); - QObject::connect(data.data(), &Resource::updateSize, manager, &ScriptManager::updateMemoryCost); + QObject::connect(data.get(), &Resource::updateSize, manager, &ScriptManager::updateMemoryCost); } auto object = engine->newQObject(const_cast(resource), ScriptEngine::ScriptOwnership); From 2f37823a939c148255a4804b6fac14655de7fc15 Mon Sep 17 00:00:00 2001 From: ksuprynowicz Date: Sat, 18 Oct 2025 23:25:49 +0200 Subject: [PATCH 033/111] Fix issues with resource pointers --- assignment-client/src/Agent.cpp | 2 +- libraries/entities-renderer/src/RenderableZoneEntityItem.cpp | 4 ++-- libraries/networking/src/ResourceCache.cpp | 5 +++-- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/assignment-client/src/Agent.cpp b/assignment-client/src/Agent.cpp index 42e330cbe4e..b7379d39ab9 100644 --- a/assignment-client/src/Agent.cpp +++ b/assignment-client/src/Agent.cpp @@ -748,7 +748,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.get(); + _avatarSound.reset(); _numAvatarSoundSentBytes = 0; _flushEncoder = true; diff --git a/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp b/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp index 64658d15884..ad1dc017a34 100644 --- a/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp @@ -536,7 +536,7 @@ void ZoneEntityRenderer::setAmbientURL(const QString& ambientUrl) { if (_ambientTextureURL.isEmpty()) { _pendingAmbientTexture = false; - _ambientTexture.get(); + _ambientTexture.reset(); _ambientLight->setAmbientMap(nullptr); _ambientLight->setAmbientSpherePreset(gpu::SphericalHarmonics::BREEZEWAY); @@ -575,7 +575,7 @@ void ZoneEntityRenderer::setSkyboxURL(const QString& skyboxUrl) { if (_skyboxTextureURL.isEmpty()) { _pendingSkyboxTexture = false; - _skyboxTexture.get(); + _skyboxTexture.reset(); editSkybox()->setCubemap(nullptr); } else { diff --git a/libraries/networking/src/ResourceCache.cpp b/libraries/networking/src/ResourceCache.cpp index d07ea77f555..de625831b90 100644 --- a/libraries/networking/src/ResourceCache.cpp +++ b/libraries/networking/src/ResourceCache.cpp @@ -561,6 +561,7 @@ static int requestID = 0; Resource::Resource(const Resource& other) : QObject(), + std::enable_shared_from_this(), _url(other._url), _effectiveBaseURL(other._effectiveBaseURL), _activeUrl(other._activeUrl), @@ -718,8 +719,8 @@ void Resource::attemptRequest() { } auto self = shared_from_this(); - if (shared_from_this()) { - ResourceCache::attemptRequest(shared_from_this()); + if (self) { + ResourceCache::attemptRequest(self); } } From 85fe4354e4ed9280afdf1a54711044240416bdf9 Mon Sep 17 00:00:00 2001 From: Karol Suprynowicz Date: Tue, 21 Oct 2025 00:10:35 +0200 Subject: [PATCH 034/111] Resource shared_ptr fixes --- libraries/audio/src/Sound.cpp | 3 ++- .../src/material-networking/TextureCache.cpp | 7 ++----- libraries/networking/src/ResourceCache.cpp | 13 ++++++++++++- libraries/networking/src/ResourceCache.h | 5 +++++ 4 files changed, 21 insertions(+), 7 deletions(-) diff --git a/libraries/audio/src/Sound.cpp b/libraries/audio/src/Sound.cpp index 9fa33396882..fc755407785 100644 --- a/libraries/audio/src/Sound.cpp +++ b/libraries/audio/src/Sound.cpp @@ -74,9 +74,10 @@ AudioData::AudioData(uint32_t numSamples, uint32_t numChannels, const AudioSampl {} void Sound::downloadFinished(const QByteArray& data) { - auto sharedSoundPointer = shared_from_this(); + auto sharedSoundPointer = weak_from_this().lock(); if (!sharedSoundPointer) { + Q_ASSERT(!_wasDeleted); soundProcessError(301, "Sound object has gone out of scope"); return; } diff --git a/libraries/material-networking/src/material-networking/TextureCache.cpp b/libraries/material-networking/src/material-networking/TextureCache.cpp index f28d7d37e7f..f602af9cb85 100644 --- a/libraries/material-networking/src/material-networking/TextureCache.cpp +++ b/libraries/material-networking/src/material-networking/TextureCache.cpp @@ -292,9 +292,6 @@ std::pair TextureCache::getTextureByHash(const { std::unique_lock lock(_texturesByHashesMutex); weakPointer = _texturesByHashes[hash]; - if (weakPointer.first.lock()) { - Q_ASSERT(!weakPointer.first.lock()->wasDeleted); - } } return { weakPointer.first.lock(), weakPointer.second }; } @@ -302,7 +299,6 @@ std::pair TextureCache::getTextureByHash(const std::pair TextureCache::cacheTextureByHash(const std::string& hash, const std::pair& textureAndSize) { std::pair result; { - Q_ASSERT(!textureAndSize.first->wasDeleted); std::unique_lock lock(_texturesByHashesMutex); auto& value = _texturesByHashes[hash]; result = { value.first.lock(), value.second }; @@ -1117,8 +1113,9 @@ void NetworkTexture::loadMetaContent(const QByteArray& content) { _currentlyLoadingResourceType = ResourceType::KTX; _activeUrl = _activeUrl.resolved(url); auto textureCache = DependencyManager::get(); - auto self = shared_from_this(); + auto self = weak_from_this().lock(); if (!self) { + Q_ASSERT(!_wasDeleted); return; } QMetaObject::invokeMethod(this, "attemptRequest", Qt::QueuedConnection); diff --git a/libraries/networking/src/ResourceCache.cpp b/libraries/networking/src/ResourceCache.cpp index de625831b90..34ef81a6769 100644 --- a/libraries/networking/src/ResourceCache.cpp +++ b/libraries/networking/src/ResourceCache.cpp @@ -591,6 +591,9 @@ Resource::Resource(const QUrl& url) : } Resource::~Resource() { +#if !defined(QT_NO_DEBUG) || defined(QT_FORCE_ASSERTS) + _wasDeleted = true; +#endif Q_ASSERT(QThread::currentThread() == thread()); if (_request) { _request->disconnect(this); @@ -667,6 +670,13 @@ void Resource::allReferencesCleared() { _cache->resetTotalResourceCounter(); } + if (_request) { + _request->disconnect(this); + _request->deleteLater(); + _request = nullptr; + ResourceCache::requestCompleted(weak_from_this()); + } + disconnect(); deleteLater(); } } @@ -718,8 +728,9 @@ void Resource::attemptRequest() { << "- retrying asset load - attempt" << _attempts << " of " << MAX_ATTEMPTS; } - auto self = shared_from_this(); + auto self = weak_from_this().lock(); if (self) { + Q_ASSERT(!_wasDeleted); ResourceCache::attemptRequest(self); } } diff --git a/libraries/networking/src/ResourceCache.h b/libraries/networking/src/ResourceCache.h index c18590c49c9..9ce774d7ad1 100644 --- a/libraries/networking/src/ResourceCache.h +++ b/libraries/networking/src/ResourceCache.h @@ -516,6 +516,11 @@ protected slots: /// Return true if the resource will be retried virtual bool handleFailedRequest(ResourceRequest::Result result); + // Safeguard for debugging. +#if !defined(QT_NO_DEBUG) || defined(QT_FORCE_ASSERTS) + std::atomic _wasDeleted{ false }; +#endif + QUrl _url; QUrl _effectiveBaseURL { _url }; QUrl _activeUrl; From 857109817fcff53269a604c85c8e8e73b8222d92 Mon Sep 17 00:00:00 2001 From: Karol Suprynowicz Date: Wed, 22 Oct 2025 00:40:59 +0200 Subject: [PATCH 035/111] Resource pointer fixes --- cmake/macros/MemoryDebugger.cmake | 6 ++-- libraries/gpu/src/gpu/Texture.h | 6 ++-- .../src/material-networking/TextureCache.cpp | 17 ++++++++-- libraries/networking/src/ResourceCache.cpp | 32 ++++++++++++++----- 4 files changed, 45 insertions(+), 16 deletions(-) diff --git a/cmake/macros/MemoryDebugger.cmake b/cmake/macros/MemoryDebugger.cmake index dde55e26ddf..4225ef4f46d 100644 --- a/cmake/macros/MemoryDebugger.cmake +++ b/cmake/macros/MemoryDebugger.cmake @@ -49,9 +49,9 @@ if ( OVERTE_MEMORY_DEBUGGING) # 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/libraries/gpu/src/gpu/Texture.h b/libraries/gpu/src/gpu/Texture.h index b144040119a..6eaaac8c575 100644 --- a/libraries/gpu/src/gpu/Texture.h +++ b/libraries/gpu/src/gpu/Texture.h @@ -606,8 +606,10 @@ class TextureView { TextureView(Texture* newTexture, const Element& element) : _texture(newTexture), _subresource(0), - _element(element) - {}; + _element(element) { + // TODO: this can cause double delete when it's used with a pointer that is already assigned to another shared_ptr. + Q_ASSERT(false); + }; TextureView(const TexturePointer& texture, uint16 subresource, const Element& element) : _texture(texture), _subresource(subresource), diff --git a/libraries/material-networking/src/material-networking/TextureCache.cpp b/libraries/material-networking/src/material-networking/TextureCache.cpp index f602af9cb85..7fc472b09d2 100644 --- a/libraries/material-networking/src/material-networking/TextureCache.cpp +++ b/libraries/material-networking/src/material-networking/TextureCache.cpp @@ -498,7 +498,14 @@ void NetworkTexture::setImage(gpu::TexturePointer texture, int originalWidth, finishedLoading(false); } - auto thisTexture = std::dynamic_pointer_cast(shared_from_this()); + auto self = weak_from_this().lock(); + if (!self) { + // We need to make sure that texture was just added to unused pool and wasn't deleted yet. + Q_ASSERT(!_wasDeleted); + return; + //TODO: what to do when texture pointer has expired? + } + auto thisTexture = std::dynamic_pointer_cast(self); Q_ASSERT(thisTexture); emit networkTextureCreated(thisTexture); } @@ -678,7 +685,9 @@ void NetworkTexture::makeLocalRequest() { if (!textureAndSize.first) { qCDebug(networking).noquote() << "Failed load local KTX from" << path; - QMetaObject::invokeMethod(this, "setImage", + auto self = weak_from_this().lock(); + Q_ASSERT(self); + QMetaObject::invokeMethod(self.get(), "setImage", Q_ARG(gpu::TexturePointer, nullptr), Q_ARG(int, 0), Q_ARG(int, 0)); @@ -687,7 +696,9 @@ void NetworkTexture::makeLocalRequest() { _ktxResourceState = PENDING_MIP_REQUEST; _lowestKnownPopulatedMip = textureAndSize.first->minAvailableMipLevel(); - QMetaObject::invokeMethod(this, "setImage", + auto self = weak_from_this().lock(); + Q_ASSERT(self); + QMetaObject::invokeMethod(self.get(), "setImage", Q_ARG(gpu::TexturePointer, textureAndSize.first), Q_ARG(int, textureAndSize.second.x), Q_ARG(int, textureAndSize.second.y)); diff --git a/libraries/networking/src/ResourceCache.cpp b/libraries/networking/src/ResourceCache.cpp index 34ef81a6769..f4e48fbe906 100644 --- a/libraries/networking/src/ResourceCache.cpp +++ b/libraries/networking/src/ResourceCache.cpp @@ -796,6 +796,13 @@ void Resource::handleDownloadProgress(uint64_t bytesReceived, uint64_t bytesTota } void Resource::handleReplyFinished() { + // Make sure we keep the Resource alive here + auto self = weak_from_this().lock(); + if (!self) { + // Make sure the resource wasn't deleted yet, and it's just scheduled for deletion or pointer has expired. + Q_ASSERT(!_wasDeleted); + } + if (!_request || _request != sender()) { // This can happen in the edge case that a request is timed out, but a `finished` signal is emitted before it is deleted. qWarning(networking) << "Received signal Resource::handleReplyFinished from ResourceRequest that is not the current" @@ -804,7 +811,10 @@ void Resource::handleReplyFinished() { { "from_cache", false }, { "size_mb", _bytesTotal / 1000000.0 } }); - ResourceCache::requestCompleted(weak_from_this()); + // TODO: should we still emit requestCompleted if resource's shared_ptr has expired? + if (self) { + ResourceCache::requestCompleted(weak_from_this()); + } return; } @@ -813,10 +823,10 @@ void Resource::handleReplyFinished() { { "size_mb", _bytesTotal / 1000000.0 } }); - // Make sure we keep the Resource alive here - auto self = shared_from_this(); - Q_ASSERT(self); - ResourceCache::requestCompleted(weak_from_this()); + // TODO: should we still emit requestCompleted if resource's shared_ptr has expired? + if (self) { + ResourceCache::requestCompleted(weak_from_this()); + } auto result = _request->getResult(); if (result == ResourceRequest::Success) { @@ -835,10 +845,16 @@ void Resource::handleReplyFinished() { } setSize(_bytesTotal); - emit loaded(data); - downloadFinished(data); + // TODO: should we emit these after pointer has expired or delete_later() was called? + if (self) { + emit loaded(data); + downloadFinished(data); + } } else { - handleFailedRequest(result); + // TODO: should we emit these after pointer has expired or delete_later() was called? + if (self) { + handleFailedRequest(result); + } } _request->disconnect(this); From b287f4cd49cc4b4c7ed5dee26dbc11b78d33212e Mon Sep 17 00:00:00 2001 From: Karol Suprynowicz Date: Fri, 24 Oct 2025 17:15:32 +0200 Subject: [PATCH 036/111] Fixes for Resource Qt events after deleteLater() --- libraries/gpu/src/gpu/Texture.h | 3 ++ .../src/material-networking/TextureCache.cpp | 31 ++++++++++++++----- libraries/networking/src/ResourceCache.cpp | 20 +++++++----- libraries/networking/src/ResourceCache.h | 2 ++ 4 files changed, 41 insertions(+), 15 deletions(-) diff --git a/libraries/gpu/src/gpu/Texture.h b/libraries/gpu/src/gpu/Texture.h index 6eaaac8c575..74a82c25d5b 100644 --- a/libraries/gpu/src/gpu/Texture.h +++ b/libraries/gpu/src/gpu/Texture.h @@ -331,6 +331,9 @@ class Texture : public Resource { bool isDefined() const { return _defined; } explicit Texture(TextureUsageType usageType); + // Textures can be only created through `create...` functions + Texture() = delete; + Texture(const Texture &texture) = delete; virtual ~Texture(); Stamp getStamp() const { return _stamp; } diff --git a/libraries/material-networking/src/material-networking/TextureCache.cpp b/libraries/material-networking/src/material-networking/TextureCache.cpp index 7fc472b09d2..b85ce8e19e0 100644 --- a/libraries/material-networking/src/material-networking/TextureCache.cpp +++ b/libraries/material-networking/src/material-networking/TextureCache.cpp @@ -484,6 +484,8 @@ void NetworkTexture::setExtra(void* extra) { void NetworkTexture::setImage(gpu::TexturePointer texture, int originalWidth, int originalHeight) { + auto self = weak_from_this().lock(); + // Passing ownership _textureSource->resetTexture(texture); @@ -492,19 +494,24 @@ void NetworkTexture::setImage(gpu::TexturePointer texture, int originalWidth, _width = texture->getWidth(); _height = texture->getHeight(); setSize(texture->getStoredSize()); - finishedLoading(true); + if (!_isScheduledForDeletion) { + finishedLoading(true); + } } else { _width = _height = 0; - finishedLoading(false); + if (!_isScheduledForDeletion) { + finishedLoading(false); + } } - auto self = weak_from_this().lock(); if (!self) { // We need to make sure that texture was just added to unused pool and wasn't deleted yet. Q_ASSERT(!_wasDeleted); + Q_ASSERT(!_isScheduledForDeletion); return; //TODO: what to do when texture pointer has expired? } + auto thisTexture = std::dynamic_pointer_cast(self); Q_ASSERT(thisTexture); emit networkTextureCreated(thisTexture); @@ -512,10 +519,12 @@ void NetworkTexture::setImage(gpu::TexturePointer texture, int originalWidth, void NetworkTexture::setImageOperator(std::function textureOperator) { _textureSource->resetTextureOperator(textureOperator); - finishedLoading((bool)textureOperator); - auto thisTexture = std::dynamic_pointer_cast(shared_from_this()); - Q_ASSERT(thisTexture); - emit networkTextureCreated(thisTexture); + if (!_isScheduledForDeletion) { + finishedLoading((bool)textureOperator); + auto thisTexture = std::dynamic_pointer_cast(shared_from_this()); + Q_ASSERT(thisTexture); + emit networkTextureCreated(thisTexture); + } } gpu::TexturePointer NetworkTexture::getFallbackTexture() const { @@ -559,6 +568,14 @@ NetworkTexture::~NetworkTexture() { const uint16_t NetworkTexture::NULL_MIP_LEVEL = std::numeric_limits::max(); void NetworkTexture::makeRequest() { + // Prevent the texture from being scheduled from deletion while the request is being made + auto selfLocked = weak_from_this().lock(); + if (!selfLocked) { + qWarning() << "NetworkTexture::makeRequest: resource pointer has expired."; + Q_ASSERT(false); + return; + } + if (_currentlyLoadingResourceType != ResourceType::KTX) { Resource::makeRequest(); return; diff --git a/libraries/networking/src/ResourceCache.cpp b/libraries/networking/src/ResourceCache.cpp index f4e48fbe906..8d77ca71744 100644 --- a/libraries/networking/src/ResourceCache.cpp +++ b/libraries/networking/src/ResourceCache.cpp @@ -641,13 +641,15 @@ void Resource::refresh() { _request->disconnect(this); _request->deleteLater(); _request = nullptr; - ResourceCache::requestCompleted(shared_from_this()); + ResourceCache::requestCompleted(weak_from_this()); } _activeUrl = _url; init(); ensureLoading(); - emit onRefresh(); + if (!_isScheduledForDeletion) { + emit onRefresh(); + } } void Resource::allReferencesCleared() { @@ -676,6 +678,7 @@ void Resource::allReferencesCleared() { _request = nullptr; ResourceCache::requestCompleted(weak_from_this()); } + _isScheduledForDeletion = true; disconnect(); deleteLater(); } @@ -742,7 +745,9 @@ void Resource::finishedLoading(bool success) { } else { _failedToLoad = true; } - emit finished(success); + if (!_isScheduledForDeletion) { + emit finished(success); + } } void Resource::setSize(const qint64& bytes) { @@ -823,10 +828,7 @@ void Resource::handleReplyFinished() { { "size_mb", _bytesTotal / 1000000.0 } }); - // TODO: should we still emit requestCompleted if resource's shared_ptr has expired? - if (self) { - ResourceCache::requestCompleted(weak_from_this()); - } + ResourceCache::requestCompleted(weak_from_this()); auto result = _request->getResult(); if (result == ResourceRequest::Success) { @@ -895,7 +897,9 @@ bool Resource::handleFailedRequest(ResourceRequest::Result result) { qCDebug(networking) << "Error loading:" << metaEnum.valueToKey(result) << "resource:" << _url.toString(); auto error = (result == ResourceRequest::Timeout) ? QNetworkReply::TimeoutError : QNetworkReply::UnknownNetworkError; - emit failed(error); + if (!_isScheduledForDeletion) { + emit failed(error); + } willRetry = false; finishedLoading(false); break; diff --git a/libraries/networking/src/ResourceCache.h b/libraries/networking/src/ResourceCache.h index 9ce774d7ad1..031cdef815c 100644 --- a/libraries/networking/src/ResourceCache.h +++ b/libraries/networking/src/ResourceCache.h @@ -521,6 +521,8 @@ protected slots: std::atomic _wasDeleted{ false }; #endif + std::atomic_bool _isScheduledForDeletion{ false }; + QUrl _url; QUrl _effectiveBaseURL { _url }; QUrl _activeUrl; From 2d2fe5029015bae9871944e8739f1410d49a11a0 Mon Sep 17 00:00:00 2001 From: Karol Suprynowicz Date: Fri, 24 Oct 2025 19:59:56 +0200 Subject: [PATCH 037/111] Fix voxels memory issue --- libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp index f308e81262d..ce02f68985a 100644 --- a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp @@ -1064,7 +1064,7 @@ void RenderablePolyVoxEntityItem::setVoxelsFromData(QByteArray uncompressedData, low += 1; } loop3(ivec3(0), ivec3(voxelXSize, voxelYSize, voxelZSize), [&](const ivec3& v) { - int uncompressedIndex = (v.z * (voxelYSize) * (voxelXSize)) + (v.y * (voxelZSize)) + v.x; + int uncompressedIndex = (v.z * (voxelYSize) * (voxelXSize)) + (v.y * (voxelXSize)) + v.x; result |= setVoxelInternal(v, uncompressedData[uncompressedIndex]); }); From 533f30993cc28310d9dd218a36a840d9157c034b Mon Sep 17 00:00:00 2001 From: Karol Suprynowicz Date: Fri, 24 Oct 2025 20:57:21 +0200 Subject: [PATCH 038/111] Temporary workaround for text entity fade crash --- libraries/entities-renderer/src/RenderableTextEntityItem.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/libraries/entities-renderer/src/RenderableTextEntityItem.cpp b/libraries/entities-renderer/src/RenderableTextEntityItem.cpp index 7d28a6399ad..04533a8b91f 100644 --- a/libraries/entities-renderer/src/RenderableTextEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableTextEntityItem.cpp @@ -372,6 +372,11 @@ void entities::TextPayload::render(RenderArgs* args) { if (textColor.a <= 0.0f) { return; } + // QT6TODO: temporary workaround, since I don't know this part of code and how fade works + bool fadingWorkaroundCheck = (!forward && ShapeKey(args->_itemShapeKey).isFaded()) && !args->_shapePipeline; + if (fadingWorkaroundCheck) { + return; + } bool usePrimaryFrustum = args->_renderMode == RenderArgs::RenderMode::SHADOW_RENDER_MODE || args->_mirrorDepth > 0; transform.setRotation(BillboardModeHelpers::getBillboardRotation(transform.getTranslation(), transform.getRotation(), textRenderable->_billboardMode, From 6bf8d41cc5f848c3013c62a265cea72a66c4f290 Mon Sep 17 00:00:00 2001 From: Karol Suprynowicz Date: Fri, 24 Oct 2025 20:57:43 +0200 Subject: [PATCH 039/111] Fix webentities on Qt6 --- .../qml/+webengine/QmlWebWindowView.qml | 44 +++++++++---------- 1 file changed, 21 insertions(+), 23 deletions(-) diff --git a/interface/resources/qml/+webengine/QmlWebWindowView.qml b/interface/resources/qml/+webengine/QmlWebWindowView.qml index fa030564031..441267975a3 100644 --- a/interface/resources/qml/+webengine/QmlWebWindowView.qml +++ b/interface/resources/qml/+webengine/QmlWebWindowView.qml @@ -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") { From 3a2326061b49ff62c6d46011ad4a426cf6aa8bcc Mon Sep 17 00:00:00 2001 From: Ada Date: Thu, 9 Oct 2025 21:48:52 +1000 Subject: [PATCH 040/111] Fix message dialogs and partially fix graphics settings --- interface/resources/qml/InteractiveWindow.qml | 52 ++++++++++++------- .../resources/qml/controlsUit/TextField.qml | 2 +- interface/resources/qml/desktop/Desktop.qml | 16 ++---- .../resources/qml/dialogs/AssetDialog.qml | 2 +- .../resources/qml/dialogs/FileDialog.qml | 12 ++--- .../resources/qml/dialogs/MessageDialog.qml | 52 +++++++++---------- .../messageDialog/MessageDialogButton.qml | 2 +- interface/resources/qml/windows/Window.qml | 8 +-- .../system/settings/qml/SettingBoolean.qml | 2 +- .../system/settings/qml/SettingComboBox.qml | 4 +- scripts/system/settings/qml/SettingNumber.qml | 4 +- scripts/system/settings/qml/SettingSlider.qml | 4 +- 12 files changed, 79 insertions(+), 81 deletions(-) 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/controlsUit/TextField.qml b/interface/resources/qml/controlsUit/TextField.qml index 0d04f363e5a..6577bd9c293 100644 --- a/interface/resources/qml/controlsUit/TextField.qml +++ b/interface/resources/qml/controlsUit/TextField.qml @@ -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: diff --git a/interface/resources/qml/desktop/Desktop.qml b/interface/resources/qml/desktop/Desktop.qml index c2d1823e6ac..02845a8b178 100644 --- a/interface/resources/qml/desktop/Desktop.qml +++ b/interface/resources/qml/desktop/Desktop.qml @@ -21,16 +21,6 @@ FocusScope { objectName: "desktop" anchors.fill: parent - /*Rectangle { - anchors.top: parent.top - anchors.left: parent.left - width: parent.width / 3 - height: parent.height / 3 - - color: "red" - radius: 8 - }*/ - readonly property int invalid_position: -9999; property rect recommendedRect: Qt.rect(0,0,0,0); property var expectedChildren; @@ -533,17 +523,17 @@ FocusScope { ensureTitleBarVisible(targetWindow); } - Component { id: messageDialogBuilder; Item {}}//MessageDialog { } } + Component { id: messageDialogBuilder; MessageDialog { } } function messageBox(properties) { return messageDialogBuilder.createObject(desktop, properties); } - Component { id: inputDialogBuilder; Item {}}//QueryDialog { } } + Component { id: inputDialogBuilder; QueryDialog { } } function inputDialog(properties) { return inputDialogBuilder.createObject(desktop, properties); } - Component { id: customInputDialogBuilder; Item {}}//CustomQueryDialog { } } + Component { id: customInputDialogBuilder; CustomQueryDialog { } } function customInputDialog(properties) { return customInputDialogBuilder.createObject(desktop, properties); } 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/FileDialog.qml b/interface/resources/qml/dialogs/FileDialog.qml index ffa5a60a88b..c575058f39a 100644 --- a/interface/resources/qml/dialogs/FileDialog.qml +++ b/interface/resources/qml/dialogs/FileDialog.qml @@ -8,9 +8,9 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +import QtCore import QtQuick import Qt.labs.folderlistmodel -import Qt.labs.settings import Qt.labs.qmlmodels import QtQuick.Dialogs as OriginalDialogs import QtQuick.Controls @@ -72,14 +72,12 @@ ModalWindow { signal canceled(); signal selected(int button); function click(button) { - // QT6TODO clickedButton = button; selected(button); destroy(); } - // QT6TODO - //property int clickedButton: OriginalDialogs.StandardButton.NoButton; + property int clickedButton: OriginalDialogs.MessageDialog.NoButton; Component.onCompleted: { fileDialogItem.keyboardEnabled = HMD.active; @@ -263,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(); } } @@ -690,7 +688,7 @@ ModalWindow { onTriggered: fileTableView.prefix = ""; } - Keys.onPressed: { + Keys.onPressed: event => { switch (event.key) { case Qt.Key_Backspace: case Qt.Key_Tab: @@ -859,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 050c4cf0310..ed272669d7b 100644 --- a/interface/resources/qml/dialogs/MessageDialog.qml +++ b/interface/resources/qml/dialogs/MessageDialog.qml @@ -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/messageDialog/MessageDialogButton.qml b/interface/resources/qml/dialogs/messageDialog/MessageDialogButton.qml index d8537b7fec3..a791b1d0406 100644 --- a/interface/resources/qml/dialogs/messageDialog/MessageDialogButton.qml +++ b/interface/resources/qml/dialogs/messageDialog/MessageDialogButton.qml @@ -15,7 +15,7 @@ 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/windows/Window.qml b/interface/resources/qml/windows/Window.qml index 7ae5fcf70a5..4ce91945716 100644 --- a/interface/resources/qml/windows/Window.qml +++ b/interface/resources/qml/windows/Window.qml @@ -127,9 +127,7 @@ Fadable { propagateComposedEvents: true acceptedButtons: Qt.AllButtons enabled: window.visible - // QT6TODO: old style is deprecated but changing it to new one breaks window inputs somehow? - //function onPressed(mouse) { - onPressed: { + onPressed: mouse => { window.raise(); mouse.accepted = false; } @@ -144,9 +142,7 @@ Fadable { propagateComposedEvents: true acceptedButtons: Qt.AllButtons enabled: window.visible - // QT6TODO: old style is deprecated but changing it to new one breaks window inputs somehow? - //function onPressed(mouse) { - onPressed: { + onPressed: mouse => { frame.forceActiveFocus(); mouse.accepted = false; } diff --git a/scripts/system/settings/qml/SettingBoolean.qml b/scripts/system/settings/qml/SettingBoolean.qml index 541f1f98555..ad415d67cb7 100644 --- a/scripts/system/settings/qml/SettingBoolean.qml +++ b/scripts/system/settings/qml/SettingBoolean.qml @@ -88,7 +88,7 @@ Item { hoverEnabled: true; propagateComposedEvents: true; - onPressed: { + onPressed: mouse => { mouse.accepted = false } diff --git a/scripts/system/settings/qml/SettingComboBox.qml b/scripts/system/settings/qml/SettingComboBox.qml index fca268be8a1..1a482b131e7 100644 --- a/scripts/system/settings/qml/SettingComboBox.qml +++ b/scripts/system/settings/qml/SettingComboBox.qml @@ -152,7 +152,7 @@ Item { hoverEnabled: true; propagateComposedEvents: true; - onPressed: { + onPressed: mouse => { if (disabled) return; mouse.accepted = false; } @@ -202,4 +202,4 @@ Item { function setOptionIndex(index) { control.currentIndex = index; } -} \ No newline at end of file +} diff --git a/scripts/system/settings/qml/SettingNumber.qml b/scripts/system/settings/qml/SettingNumber.qml index e25a8897e1a..2a71ba4be9a 100644 --- a/scripts/system/settings/qml/SettingNumber.qml +++ b/scripts/system/settings/qml/SettingNumber.qml @@ -162,7 +162,7 @@ Item { hoverEnabled: true; propagateComposedEvents: true; - onPressed: { + onPressed: mouse => { mouse.accepted = false } @@ -184,4 +184,4 @@ Item { } } } -} \ No newline at end of file +} diff --git a/scripts/system/settings/qml/SettingSlider.qml b/scripts/system/settings/qml/SettingSlider.qml index 5654e8574b2..b8ca1cb3c3d 100644 --- a/scripts/system/settings/qml/SettingSlider.qml +++ b/scripts/system/settings/qml/SettingSlider.qml @@ -119,7 +119,7 @@ Item { hoverEnabled: true; propagateComposedEvents: true; - onPressed: { + onPressed: mouse => { mouse.accepted = false } @@ -139,4 +139,4 @@ Item { } } } -} \ No newline at end of file +} From db3ea3a4831d068d6327abcdf8144540696d82c8 Mon Sep 17 00:00:00 2001 From: Ada Date: Mon, 13 Oct 2025 22:29:53 +1000 Subject: [PATCH 041/111] Use new callback syntax in TabletHome, workaround for Qt::TimerType errors --- interface/resources/qml/hifi/tablet/TabletHome.qml | 4 ++-- libraries/script-engine/src/v8/ScriptEngineV8_cast.cpp | 9 +++++++++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/interface/resources/qml/hifi/tablet/TabletHome.qml b/interface/resources/qml/hifi/tablet/TabletHome.qml index c770c2dc0f2..10f8a5eb542 100644 --- a/interface/resources/qml/hifi/tablet/TabletHome.qml +++ b/interface/resources/qml/hifi/tablet/TabletHome.qml @@ -188,7 +188,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; } @@ -272,7 +272,7 @@ Item { Connections { target: modelData; - onPropertiesChanged: { + function onPropertiesChanged() { updateProperties(); } } diff --git a/libraries/script-engine/src/v8/ScriptEngineV8_cast.cpp b/libraries/script-engine/src/v8/ScriptEngineV8_cast.cpp index d58994cc9ac..ff0ee990859 100644 --- a/libraries/script-engine/src/v8/ScriptEngineV8_cast.cpp +++ b/libraries/script-engine/src/v8/ScriptEngineV8_cast.cpp @@ -655,6 +655,9 @@ QString ScriptEngineV8::valueType(const V8ScriptValue& v8Val) { return "undefined"; } +// QT6TODO: where does this belong? +Q_DECLARE_METATYPE(Qt::TimerType); + V8ScriptValue ScriptEngineV8::castVariantToValue(const QVariant& val) { Q_ASSERT(_v8Isolate->IsCurrent()); v8::HandleScope handleScope(_v8Isolate); @@ -738,6 +741,12 @@ V8ScriptValue ScriptEngineV8::castVariantToValue(const QVariant& val) { //V8TODO: what should be the ownership in this case? return ScriptObjectV8Proxy::newQObject(this, obj); } + + // enums need special treatment, Qt::TimerType fails without this + if (QMetaType(valTypeId).flags() & (QMetaType::IsEnumeration | QMetaType::IsUnsignedEnumeration)) { + return V8ScriptValue(this, v8::Integer::New(_v8Isolate, val.toInt())); + } + // have we set a prototyped variant? { _customTypeProtect.lockForRead(); From 1d8af75ccfe865c5695b745119017bf0aff03dc5 Mon Sep 17 00:00:00 2001 From: Karol Suprynowicz Date: Sat, 25 Oct 2025 19:34:47 +0200 Subject: [PATCH 042/111] Possible fix for QTimer* bug in script engine --- .../script-engine/src/ScriptManagerScriptingInterface.h | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/libraries/script-engine/src/ScriptManagerScriptingInterface.h b/libraries/script-engine/src/ScriptManagerScriptingInterface.h index 77d02656216..5f70c5c673c 100644 --- a/libraries/script-engine/src/ScriptManagerScriptingInterface.h +++ b/libraries/script-engine/src/ScriptManagerScriptingInterface.h @@ -338,7 +338,8 @@ class ScriptManagerScriptingInterface : public QObject { Q_INVOKABLE void clearInterval(QTimer* timer) { _manager->clearInterval(timer); } // Overloaded version is needed in case the timer has expired - Q_INVOKABLE void clearInterval(QVariantMap timer) { ; } + // QT6TODO: This errors out on Qt6. It seems to work without it, but I'm not sure if there isn't more of underlying bugs. + //Q_INVOKABLE void clearInterval(QVariantMap timer) { ; } /*@jsdoc * Stops a timeout timer set by {@link Script.setTimeout|setTimeout}. @@ -356,7 +357,8 @@ class ScriptManagerScriptingInterface : public QObject { Q_INVOKABLE void clearTimeout(QTimer* timer) { _manager->clearTimeout(timer); } // Overloaded version is needed in case the timer has expired - Q_INVOKABLE void clearTimeout(QVariantMap timer) { ; } + // QT6TODO: This errors out on Qt6. It seems to work without it, but I'm not sure if there isn't more of underlying bugs. + //Q_INVOKABLE void clearTimeout(QVariantMap timer) { ; } /*@jsdoc * Prints a message to the program log and emits {@link Script.printedMessage}. From 643c7b7ef8bfea2b929a23155e66f030e55343b3 Mon Sep 17 00:00:00 2001 From: Karol Suprynowicz Date: Sat, 25 Oct 2025 19:38:45 +0200 Subject: [PATCH 043/111] QNetworkReply signal fixes --- .../src/material-networking/TextureCache.cpp | 5 ++++- libraries/networking/src/AccountManager.cpp | 12 ++++++------ libraries/networking/src/udt/NetworkSocket.cpp | 2 +- libraries/script-engine/src/XMLHttpRequestClass.cpp | 4 ++-- libraries/shared/src/RegisteredMetaTypes.h | 2 ++ 5 files changed, 15 insertions(+), 10 deletions(-) diff --git a/libraries/material-networking/src/material-networking/TextureCache.cpp b/libraries/material-networking/src/material-networking/TextureCache.cpp index b85ce8e19e0..458477b89ba 100644 --- a/libraries/material-networking/src/material-networking/TextureCache.cpp +++ b/libraries/material-networking/src/material-networking/TextureCache.cpp @@ -504,10 +504,13 @@ void NetworkTexture::setImage(gpu::TexturePointer texture, int originalWidth, } } + if (_isScheduledForDeletion) { + return; + } + if (!self) { // We need to make sure that texture was just added to unused pool and wasn't deleted yet. Q_ASSERT(!_wasDeleted); - Q_ASSERT(!_isScheduledForDeletion); return; //TODO: what to do when texture pointer has expired? } diff --git a/libraries/networking/src/AccountManager.cpp b/libraries/networking/src/AccountManager.cpp index d90a1e7a6ac..81f6200ff25 100644 --- a/libraries/networking/src/AccountManager.cpp +++ b/libraries/networking/src/AccountManager.cpp @@ -624,7 +624,7 @@ void AccountManager::requestAccessTokenWithSteam(QByteArray authSessionTicket) { QNetworkReply* requestReply = networkAccessManager.post(request, postData); connect(requestReply, &QNetworkReply::finished, this, &AccountManager::requestAccessTokenFinished); - connect(requestReply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(requestAccessTokenError(QNetworkReply::NetworkError))); + connect(requestReply, SIGNAL(errorOccurred(QNetworkReply::NetworkError)), this, SLOT(requestAccessTokenError(QNetworkReply::NetworkError))); } void AccountManager::requestAccessTokenWithOculus(const QString& nonce, const QString &oculusID) { @@ -647,7 +647,7 @@ void AccountManager::requestAccessTokenWithOculus(const QString& nonce, const QS QNetworkReply* requestReply = networkAccessManager.post(request, postData); connect(requestReply, &QNetworkReply::finished, this, &AccountManager::requestAccessTokenFinished); - connect(requestReply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(requestAccessTokenError(QNetworkReply::NetworkError))); + connect(requestReply, SIGNAL(errorOccurred(QNetworkReply::NetworkError)), this, SLOT(requestAccessTokenError(QNetworkReply::NetworkError))); } void AccountManager::refreshAccessToken() { @@ -677,7 +677,7 @@ void AccountManager::refreshAccessToken() { QNetworkReply* requestReply = networkAccessManager.post(request, postData); connect(requestReply, &QNetworkReply::finished, this, &AccountManager::refreshAccessTokenFinished); - connect(requestReply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(refreshAccessTokenError(QNetworkReply::NetworkError))); + connect(requestReply, SIGNAL(errorOccurred(QNetworkReply::NetworkError)), this, SLOT(refreshAccessTokenError(QNetworkReply::NetworkError))); } else { qCWarning(networking) << "Cannot refresh access token without refresh token." << "Access token will need to be manually refreshed."; @@ -805,7 +805,7 @@ void AccountManager::requestProfile() { QNetworkReply* profileReply = networkAccessManager.get(profileRequest); connect(profileReply, &QNetworkReply::finished, this, &AccountManager::requestProfileFinished); - connect(profileReply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(requestProfileError(QNetworkReply::NetworkError))); + connect(profileReply, SIGNAL(errorOccurred(QNetworkReply::NetworkError)), this, SLOT(requestProfileError(QNetworkReply::NetworkError))); } void AccountManager::requestProfileFinished() { @@ -855,7 +855,7 @@ void AccountManager::requestAccountSettings() { QNetworkReply* lockerReply = networkAccessManager.get(lockerRequest); connect(lockerReply, &QNetworkReply::finished, this, &AccountManager::requestAccountSettingsFinished); - connect(lockerReply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(requestAccountSettingsError(QNetworkReply::NetworkError))); + connect(lockerReply, SIGNAL(errorOccurred(QNetworkReply::NetworkError)), this, SLOT(requestAccountSettingsError(QNetworkReply::NetworkError))); _settings.startedLoading(); } @@ -933,7 +933,7 @@ void AccountManager::postAccountSettings() { QNetworkReply* lockerReply = networkAccessManager.put(lockerRequest, postData); connect(lockerReply, &QNetworkReply::finished, this, &AccountManager::postAccountSettingsFinished); - connect(lockerReply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(postAccountSettingsError(QNetworkReply::NetworkError))); + connect(lockerReply, SIGNAL(errorOccurred(QNetworkReply::NetworkError)), this, SLOT(postAccountSettingsError(QNetworkReply::NetworkError))); } void AccountManager::postAccountSettingsFinished() { diff --git a/libraries/networking/src/udt/NetworkSocket.cpp b/libraries/networking/src/udt/NetworkSocket.cpp index c115d642b2d..c846c648b22 100644 --- a/libraries/networking/src/udt/NetworkSocket.cpp +++ b/libraries/networking/src/udt/NetworkSocket.cpp @@ -25,7 +25,7 @@ NetworkSocket::NetworkSocket(QObject* parent) : connect(&_udpSocket, &QUdpSocket::readyRead, this, &NetworkSocket::readyRead); connect(&_udpSocket, &QAbstractSocket::stateChanged, this, &NetworkSocket::onUDPStateChanged); // Use old SIGNAL/SLOT mechanism for Android builds. - connect(&_udpSocket, SIGNAL(error(QAbstractSocket::SocketError)), + connect(&_udpSocket, SIGNAL(errorOccurred(QAbstractSocket::SocketError)), this, SLOT(onUDPSocketError(QAbstractSocket::SocketError))); #if defined(WEBRTC_DATA_CHANNELS) diff --git a/libraries/script-engine/src/XMLHttpRequestClass.cpp b/libraries/script-engine/src/XMLHttpRequestClass.cpp index 2f99281e89d..95244eefe42 100644 --- a/libraries/script-engine/src/XMLHttpRequestClass.cpp +++ b/libraries/script-engine/src/XMLHttpRequestClass.cpp @@ -256,14 +256,14 @@ void XMLHttpRequestClass::abortRequest() { void XMLHttpRequestClass::connectToReply(QNetworkReply* reply) { connect(reply, SIGNAL(finished()), this, SLOT(requestFinished())); - connect(reply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(requestError(QNetworkReply::NetworkError))); + connect(reply, SIGNAL(errorOccurred(QNetworkReply::NetworkError)), this, SLOT(requestError(QNetworkReply::NetworkError))); connect(reply, SIGNAL(downloadProgress(qint64, qint64)), this, SLOT(requestDownloadProgress(qint64, qint64))); connect(reply, SIGNAL(metaDataChanged()), this, SLOT(requestMetaDataChanged())); } void XMLHttpRequestClass::disconnectFromReply(QNetworkReply* reply) { disconnect(reply, SIGNAL(finished()), this, SLOT(requestFinished())); - disconnect(reply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(requestError(QNetworkReply::NetworkError))); + disconnect(reply, SIGNAL(errorOccurred(QNetworkReply::NetworkError)), this, SLOT(requestError(QNetworkReply::NetworkError))); disconnect(reply, SIGNAL(downloadProgress(qint64, qint64)), this, SLOT(requestDownloadProgress(qint64, qint64))); disconnect(reply, SIGNAL(metaDataChanged()), this, SLOT(requestMetaDataChanged())); } diff --git a/libraries/shared/src/RegisteredMetaTypes.h b/libraries/shared/src/RegisteredMetaTypes.h index 39783e2a090..99b57224bbf 100644 --- a/libraries/shared/src/RegisteredMetaTypes.h +++ b/libraries/shared/src/RegisteredMetaTypes.h @@ -21,6 +21,7 @@ #include #include +#include #include "AACube.h" #include "ShapeInfo.h" @@ -46,6 +47,7 @@ Q_DECLARE_METATYPE(QVector) Q_DECLARE_METATYPE(AACube) Q_DECLARE_METATYPE(std::function); Q_DECLARE_METATYPE(std::function); +Q_DECLARE_METATYPE(QTimer*); // Mat4 /*@jsdoc From 93974930065aac795e1ee10d4c99a94e0e262ee8 Mon Sep 17 00:00:00 2001 From: Karol Suprynowicz Date: Sat, 25 Oct 2025 23:42:43 +0200 Subject: [PATCH 044/111] Fix vec4 in script engine --- libraries/script-engine/src/Mat4.h | 2 +- libraries/script-engine/src/ScriptValueUtils.h | 6 +++--- libraries/shared/src/DebugDraw.h | 8 ++++---- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/libraries/script-engine/src/Mat4.h b/libraries/script-engine/src/Mat4.h index f94e50cb3aa..73cbc11458e 100644 --- a/libraries/script-engine/src/Mat4.h +++ b/libraries/script-engine/src/Mat4.h @@ -114,7 +114,7 @@ public slots: * // (1.478398, 0.560660, 1.224745, 0.000000), * // (10.000000, 11.000000, 12.000000, 1.000000)) */ - glm::mat<4,4,float,glm::packed_highp> createFromColumns(const glm::vec4& col0, const glm::vec4& col1, const glm::vec4& col2, const glm::vec4& col3) const; + glm::mat<4,4,float,glm::packed_highp> createFromColumns(const glm::vec<4,float,glm::packed_highp>& col0, const glm::vec<4,float,glm::packed_highp>& col1, const glm::vec<4,float,glm::packed_highp>& col2, const glm::vec<4,float,glm::packed_highp>& col3) const; /*@jsdoc * Creates a matrix from an array of values. diff --git a/libraries/script-engine/src/ScriptValueUtils.h b/libraries/script-engine/src/ScriptValueUtils.h index fd0528c9e1c..20fc3eaa247 100644 --- a/libraries/script-engine/src/ScriptValueUtils.h +++ b/libraries/script-engine/src/ScriptValueUtils.h @@ -148,9 +148,9 @@ bool u8vec3FromScriptValue(const ScriptValue& object, glm::u8vec3& vec3); * @property {number} z - Z-coordinate of the vector. * @property {number} w - W-coordinate of the vector. */ -ScriptValue vec4toScriptValue(ScriptEngine* engine, const glm::vec4& vec4); -ScriptValue vec4ColorToScriptValue(ScriptEngine* engine, const glm::vec4& vec4); -bool vec4FromScriptValue(const ScriptValue& object, glm::vec4& vec4); +ScriptValue vec4toScriptValue(ScriptEngine* engine, const glm::vec<4,float,glm::packed_highp>& vec4); +ScriptValue vec4ColorToScriptValue(ScriptEngine* engine, const glm::vec<4,float,glm::packed_highp>& vec4); +bool vec4FromScriptValue(const ScriptValue& object, glm::vec<4,float,glm::packed_highp>& vec4); // Quaternions ScriptValue quatToScriptValue(ScriptEngine* engine, const glm::quat& quat); diff --git a/libraries/shared/src/DebugDraw.h b/libraries/shared/src/DebugDraw.h index 4cb8105bdf0..88681101135 100644 --- a/libraries/shared/src/DebugDraw.h +++ b/libraries/shared/src/DebugDraw.h @@ -61,7 +61,7 @@ class DebugDraw : public QObject { * DebugDraw.drawRay(start, end, color); * }); */ - Q_INVOKABLE void drawRay(const glm::vec<3,float,glm::packed_highp>& start, const glm::vec<3,float,glm::packed_highp>& end, const glm::vec4& color); + Q_INVOKABLE void drawRay(const glm::vec<3,float,glm::packed_highp>& start, const glm::vec<3,float,glm::packed_highp>& end, const glm::vec<4,float,glm::packed_highp>& color); /*@jsdoc * Draws lines in world space, visible for a single frame. To make the lines visually persist, you need to repeatedly draw @@ -86,7 +86,7 @@ class DebugDraw : public QObject { * DebugDraw.drawRays(lines, color, translation, rotation); * }); */ - Q_INVOKABLE void drawRays(const std::vector, glm::vec<3,float,glm::packed_highp>>>& lines, const glm::vec4& color, + Q_INVOKABLE void drawRays(const std::vector, glm::vec<3,float,glm::packed_highp>>>& lines, const glm::vec<4,float,glm::packed_highp>& color, const glm::vec<3,float,glm::packed_highp>& translation = glm::vec<3,float,glm::packed_highp>(0.0f, 0.0f, 0.0f), const glm::qua& rotation = glm::qua(1.0f, 0.0f, 0.0f, 0.0f)); /*@jsdoc @@ -113,7 +113,7 @@ class DebugDraw : public QObject { * }, 5000); */ Q_INVOKABLE void addMarker(const QString& key, const glm::qua& rotation, const glm::vec<3,float,glm::packed_highp>& position, - const glm::vec4& color, float size = 1.0f); + const glm::vec<4,float,glm::packed_highp>& color, float size = 1.0f); /*@jsdoc * Removes a debug marker that was added in world coordinates. @@ -146,7 +146,7 @@ class DebugDraw : public QObject { * }, 5000); */ Q_INVOKABLE void addMyAvatarMarker(const QString& key, const glm::qua& rotation, const glm::vec<3,float,glm::packed_highp>& position, - const glm::vec4& color, float size = 1.0f); + const glm::vec<4,float,glm::packed_highp>& color, float size = 1.0f); /*@jsdoc * Removes a debug marker that was added in avatar coordinates. From 70f4d2c8ad70f956d76b0e8ffe6286d76b0f0d42 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julian=20Gro=C3=9F?= Date: Tue, 21 Oct 2025 12:32:18 +0200 Subject: [PATCH 045/111] Add experimental Qt 6.8.3 package. --- conanfile.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/conanfile.py b/conanfile.py index 77c0a1a96fc..dd731e15a8a 100644 --- a/conanfile.py +++ b/conanfile.py @@ -98,7 +98,7 @@ def requirements(self): elif self.options.qt_source == "aqt": self.requires("qt/5.15.2@overte/aqt", force=True) else: - self.requires("qt/5.15.17-2025.06.07@overte/stable#550a40fc9cbe089ea59a727a3f038a31", force=True) + self.requires("qt/6.8.3@overte/experimental#40f35c8c20ff467331bf59998c972a6f", force=True) if self.settings.os == "Windows": self.requires("neuron/12.2@overte/prebuild") From 28ca7a95a2a8049ab0a9d624b1bffbaf5626ad1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julian=20Gro=C3=9F?= Date: Tue, 21 Oct 2025 14:50:09 +0200 Subject: [PATCH 046/111] Quazip is required for building. --- conanfile.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/conanfile.py b/conanfile.py index dd731e15a8a..efe30857e9c 100644 --- a/conanfile.py +++ b/conanfile.py @@ -73,8 +73,7 @@ def requirements(self): self.requires("openxr/1.1.46@overte/stable") self.requires("opus/1.4") self.requires("polyvox/2025.09.19@overte/experimental#76ce908c1078988dceae5ad32ead2909") - # QT6TODO - #self.requires("quazip/1.4") + self.requires("quazip/1.4") self.requires("scribe/2019.02@overte/stable") self.requires("sdl/2.32.8") self.requires("spirv-cross/1.3.268.0") From 95fc07274e4b9dd47603e5d63016ab4f35973d10 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julian=20Gro=C3=9F?= Date: Tue, 21 Oct 2025 16:45:18 +0200 Subject: [PATCH 047/111] Update conanfile.py --- conanfile.py | 1 + 1 file changed, 1 insertion(+) diff --git a/conanfile.py b/conanfile.py index efe30857e9c..8d73f6533b4 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", From d4f7115c1010ed8091a8e84692c707d8fd6b9a9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julian=20Gro=C3=9F?= Date: Tue, 21 Oct 2025 23:23:30 +0200 Subject: [PATCH 048/111] Use shared fontconfig and freetype to avoid Qt linking issues. --- conanfile.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/conanfile.py b/conanfile.py index 8d73f6533b4..cff26aa6512 100644 --- a/conanfile.py +++ b/conanfile.py @@ -39,6 +39,8 @@ class Overte(ConanFile): "qt*:qtwebview": "True", "qt*:qtxmlpatterns": "True", "qt*:qttools": "True", # windeployqt for Windows + "fontconfig*:shared": "True", # For Qt on Linux. Building with static fontconfig and freetype fails: https://github.com/conan-io/conan-center-index/issues/17142 + "freetype*:shared": "True", # For Qt on Linux. "glad*:spec": "gl", "glad*:gl_profile": "core", "glad*:gl_version": "4.5", From b11cdd0f1f01171ac3a5dde6eaea2e56cc5b5716 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julian=20Gro=C3=9F?= Date: Tue, 21 Oct 2025 23:31:23 +0200 Subject: [PATCH 049/111] Use Qt source package on Ubuntu 22.04 PR builds. --- .github/workflows/pr_build.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pr_build.yml b/.github/workflows/pr_build.yml index 4173052fa7e..5b84f4a8e64 100644 --- a/.github/workflows/pr_build.yml +++ b/.github/workflows/pr_build.yml @@ -45,7 +45,7 @@ jobs: runner: ubuntu-latest 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:2025-10-24-ubuntu-22.04-amd64 # Container image used for building. Built from /tools/ci-script/linux-ci/Dockerfile_x @@ -64,7 +64,7 @@ jobs: runner: ubuntu-24.04-arm arch: aarch64 build_type: full - qt_source: system + qt_source: source rendering_backend: GLES - os: Ubuntu 22.04 Vulkan image: docker.io/overte/overte-full-build:2026-01-01-ubuntu-22.04-aarch64 From 9b61a0b24456a8bac1df32d723f786e72c4b39d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julian=20Gro=C3=9F?= Date: Wed, 22 Oct 2025 21:52:42 +0200 Subject: [PATCH 050/111] Enable with_dbus as it is required by Qt WebEngine. --- conanfile.py | 1 + 1 file changed, 1 insertion(+) diff --git a/conanfile.py b/conanfile.py index cff26aa6512..a374b0748d3 100644 --- a/conanfile.py +++ b/conanfile.py @@ -39,6 +39,7 @@ class Overte(ConanFile): "qt*:qtwebview": "True", "qt*:qtxmlpatterns": "True", "qt*:qttools": "True", # windeployqt for Windows + "qt*:with_dbus": "True", # Required for Qt on Linux. Can be disabled for Windows. "fontconfig*:shared": "True", # For Qt on Linux. Building with static fontconfig and freetype fails: https://github.com/conan-io/conan-center-index/issues/17142 "freetype*:shared": "True", # For Qt on Linux. "glad*:spec": "gl", From 19e51c94d11b8c80f43964b81dad005ce45ad270 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julian=20Gro=C3=9F?= Date: Thu, 23 Oct 2025 00:43:10 +0200 Subject: [PATCH 051/111] Use shared NSS, as NSS' Conan recipe doesn't support static building yet. --- conanfile.py | 1 + 1 file changed, 1 insertion(+) diff --git a/conanfile.py b/conanfile.py index a374b0748d3..c0ea58f9830 100644 --- a/conanfile.py +++ b/conanfile.py @@ -42,6 +42,7 @@ class Overte(ConanFile): "qt*:with_dbus": "True", # Required for Qt on Linux. Can be disabled for Windows. "fontconfig*:shared": "True", # For Qt on Linux. Building with static fontconfig and freetype fails: https://github.com/conan-io/conan-center-index/issues/17142 "freetype*:shared": "True", # For Qt on Linux. + "nss*:shared": "True", # Dependency of Qt. "NSS recipe cannot yet build static library." "glad*:spec": "gl", "glad*:gl_profile": "core", "glad*:gl_version": "4.5", From 314666eb81e95794fa90be540a84099fb52c7f02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julian=20Gro=C3=9F?= Date: Thu, 23 Oct 2025 01:10:35 +0200 Subject: [PATCH 052/111] Use shared NSPR, since NSS cannot link against static NSPR. --- conanfile.py | 1 + 1 file changed, 1 insertion(+) diff --git a/conanfile.py b/conanfile.py index c0ea58f9830..eeb61af273e 100644 --- a/conanfile.py +++ b/conanfile.py @@ -43,6 +43,7 @@ class Overte(ConanFile): "fontconfig*:shared": "True", # For Qt on Linux. Building with static fontconfig and freetype fails: https://github.com/conan-io/conan-center-index/issues/17142 "freetype*:shared": "True", # For Qt on Linux. "nss*:shared": "True", # Dependency of Qt. "NSS recipe cannot yet build static library." + "nspr*:shared": "True", # NSS, which is a dependency of Qt, cannot link to statis NSPR. "glad*:spec": "gl", "glad*:gl_profile": "core", "glad*:gl_version": "4.5", From 698cc9279b7ce36325763d28c3bf0374de825355 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julian=20Gro=C3=9F?= Date: Fri, 24 Oct 2025 19:27:44 +0200 Subject: [PATCH 053/111] Avoid `undefined symbol` errors when building NSS. --- conanfile.py | 1 + 1 file changed, 1 insertion(+) diff --git a/conanfile.py b/conanfile.py index eeb61af273e..0b65b4eb50c 100644 --- a/conanfile.py +++ b/conanfile.py @@ -44,6 +44,7 @@ class Overte(ConanFile): "freetype*:shared": "True", # For Qt on Linux. "nss*:shared": "True", # Dependency of Qt. "NSS recipe cannot yet build static library." "nspr*:shared": "True", # NSS, which is a dependency of Qt, cannot link to statis NSPR. + "sqlite*:shared": "True", # Avoid `undefined symbol` errors when building NSS. "glad*:spec": "gl", "glad*:gl_profile": "core", "glad*:gl_version": "4.5", From 48f2b45758c05ff34b77792f15cd795068051663 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julian=20Gro=C3=9F?= Date: Fri, 24 Oct 2025 19:28:49 +0200 Subject: [PATCH 054/111] Use upstream Qt package, since all current changes have been upstreamed. --- conanfile.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/conanfile.py b/conanfile.py index 0b65b4eb50c..14331c91896 100644 --- a/conanfile.py +++ b/conanfile.py @@ -104,7 +104,7 @@ def requirements(self): elif self.options.qt_source == "aqt": self.requires("qt/5.15.2@overte/aqt", force=True) else: - self.requires("qt/6.8.3@overte/experimental#40f35c8c20ff467331bf59998c972a6f", force=True) + self.requires("qt/6.8.3", force=True) if self.settings.os == "Windows": self.requires("neuron/12.2@overte/prebuild") From 9dfd5157e99e527fff7b42e5ec68dc7e5d480716 Mon Sep 17 00:00:00 2001 From: RTUnreal Date: Sat, 25 Oct 2025 14:04:02 +0200 Subject: [PATCH 055/111] Fix `libraries\shared\src\shared\platform\WinHelper.cpp(30,10): error C3668: 'WinHelper::nativeEventFilter': method with override specifier 'override' did not override any base class methods` on MSVC. --- libraries/shared/src/shared/platform/WinHelper.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/libraries/shared/src/shared/platform/WinHelper.cpp b/libraries/shared/src/shared/platform/WinHelper.cpp index 3de82af0872..ca67c049aef 100644 --- a/libraries/shared/src/shared/platform/WinHelper.cpp +++ b/libraries/shared/src/shared/platform/WinHelper.cpp @@ -16,9 +16,7 @@ class WinHelper : public PlatformHelper, public QAbstractNativeEventFilter { public: - WinHelper() { - QAbstractEventDispatcher::instance()->installNativeEventFilter(this); - } + WinHelper() { QAbstractEventDispatcher::instance()->installNativeEventFilter(this); } ~WinHelper() { auto eventDispatcher = QAbstractEventDispatcher::instance(); @@ -27,7 +25,7 @@ class WinHelper : public PlatformHelper, public QAbstractNativeEventFilter { } } - bool nativeEventFilter(const QByteArray& eventType, void* message, long*) override { + bool nativeEventFilter(const QByteArray& eventType, void* message, qintptr*) override { MSG* msg = static_cast(message); if (msg->message == WM_POWERBROADCAST) { switch (msg->wParam) { From d250618fd051a67a80f513e4638d54fe164c4d49 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julian=20Gro=C3=9F?= Date: Tue, 28 Oct 2025 00:54:15 +0100 Subject: [PATCH 056/111] Fix building against system Quazip. --- cmake/macros/TargetQuazip.cmake | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/cmake/macros/TargetQuazip.cmake b/cmake/macros/TargetQuazip.cmake index 873769ef027..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-qt6) target_include_directories(${TARGET_NAME} SYSTEM PRIVATE ${QuaZip_INCLUDE_DIRS}) target_link_libraries(${TARGET_NAME} ${QuaZip_LINK_LIBRARIES}) else() - find_package(QuaZip-Qt6 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() From 755e69b126a53a3c4cb18f44b8ad8a585870d642 Mon Sep 17 00:00:00 2001 From: Karol Suprynowicz Date: Thu, 30 Oct 2025 00:22:29 +0100 Subject: [PATCH 057/111] Fix FBX support on Qt6 --- .../model-serializers/src/FBXSerializer.cpp | 90 +++++++++---------- .../src/FBXSerializer_Mesh.cpp | 10 +-- .../src/ModelSerializersTests.cpp | 60 +++++++++++++ .../src/ModelSerializersTests.h | 1 + 4 files changed, 111 insertions(+), 50 deletions(-) diff --git a/libraries/model-serializers/src/FBXSerializer.cpp b/libraries/model-serializers/src/FBXSerializer.cpp index 7523e742427..8115b8c541b 100644 --- a/libraries/model-serializers/src/FBXSerializer.cpp +++ b/libraries/model-serializers/src/FBXSerializer.cpp @@ -105,10 +105,10 @@ QString getModelName(const QVariantList& properties) { QString name; if (properties.size() == 3) { name = properties.at(1).toString(); - name = processID(name.left(name.indexOf(QChar('\0')))); } else { - name = processID(properties.at(0).toString()); + name = properties.at(0).toString(); } + name = processID(name.left(name.indexOf(QChar('\0')))); return name; } @@ -116,10 +116,10 @@ QString getMaterialName(const QVariantList& properties) { QString name; if (properties.size() == 1 || properties.at(1).toString().isEmpty()) { name = properties.at(0).toString(); - name = processID(name.left(name.indexOf(QChar('\0')))); } else { - name = processID(properties.at(1).toString()); + name = properties.at(1).toString(); } + name = processID(name.left(name.indexOf(QChar('\0')))); return name; } @@ -492,7 +492,7 @@ HFMModel* FBXSerializer::extractHFMModel(const hifi::VariantMultiHash& mapping, foreach (const FBXNode& subsubobject, subobject.children) { static const QVariant APPLICATION_NAME = QVariant(hifi::ByteArray("Original|ApplicationName")); if (subsubobject.name == "P" && subsubobject.properties.size() >= 5 && - subsubobject.properties.at(0) == APPLICATION_NAME) { + subsubobject.properties.at(0).toByteArray() == APPLICATION_NAME) { hfmModel.applicationName = subsubobject.properties.at(4).toString(); } } @@ -512,7 +512,7 @@ HFMModel* FBXSerializer::extractHFMModel(const hifi::VariantMultiHash& mapping, static const QVariant UNIT_SCALE_FACTOR = hifi::ByteArray("UnitScaleFactor"); static const QVariant AMBIENT_COLOR = hifi::ByteArray("AmbientColor"); static const QVariant UP_AXIS = hifi::ByteArray("UpAxis"); - const auto& subpropName = subobject.properties.at(0); + const auto& subpropName = subobject.properties.at(0).toByteArray(); if (subpropName == UNIT_SCALE_FACTOR) { unitScaleFactor = subobject.properties.at(index).toFloat(); } else if (subpropName == AMBIENT_COLOR) { @@ -535,7 +535,7 @@ HFMModel* FBXSerializer::extractHFMModel(const hifi::VariantMultiHash& mapping, } else if (child.name == "Objects") { foreach (const FBXNode& object, child.children) { if (object.name == "Geometry") { - if (object.properties.at(2) == "Mesh") { + if (object.properties.at(2).toByteArray() == "Mesh") { meshes.insert(getID(object.properties), extractMesh(object, meshIndex, deduplicateIndices)); } else { // object.properties.at(2) == "Shape" ExtractedBlendshape extracted = { getID(object.properties), extractBlendshape(object) }; @@ -569,7 +569,7 @@ HFMModel* FBXSerializer::extractHFMModel(const hifi::VariantMultiHash& mapping, glm::vec3 rotationMin, rotationMax; - bool isLimbNode = object.properties.size() >= 3 && object.properties.at(2) == "LimbNode"; + bool isLimbNode = object.properties.size() >= 3 && object.properties.at(2).toByteArray() == "LimbNode"; FBXModel fbxModel = { name, -1, glm::vec3(), glm::mat4(), glm::quat(), glm::quat(), glm::quat(), glm::mat4(), glm::vec3(), glm::vec3(), false, glm::vec3(), glm::quat(), glm::vec3(1.0f), isLimbNode }; @@ -612,7 +612,7 @@ HFMModel* FBXSerializer::extractHFMModel(const hifi::VariantMultiHash& mapping, static const QVariant PRE_ROTATION = hifi::ByteArray("PreRotation"); static const QVariant POST_ROTATION = hifi::ByteArray("PostRotation"); foreach(const FBXNode& property, subobject.children) { - const auto& childProperty = property.properties.at(0); + const auto& childProperty = property.properties.at(0).toByteArray(); if (property.name == propertyName) { if (childProperty == LCL_TRANSLATION) { translation = getVec3(property.properties, index); @@ -793,18 +793,18 @@ HFMModel* FBXSerializer::extractHFMModel(const hifi::VariantMultiHash& mapping, static const QVariant SCALING = hifi::ByteArray("Scaling"); if (property.name == propertyName) { QString v = property.properties.at(0).toString(); - if (property.properties.at(0) == UV_SET) { + if (property.properties.at(0).toByteArray() == UV_SET) { std::string uvName = property.properties.at(index).toString().toStdString(); tex.assign(tex.UVSet, property.properties.at(index).toString()); - } else if (property.properties.at(0) == CURRENT_TEXTURE_BLEND_MODE) { + } else if (property.properties.at(0).toByteArray() == CURRENT_TEXTURE_BLEND_MODE) { tex.assign(tex.currentTextureBlendMode, property.properties.at(index).value()); - } else if (property.properties.at(0) == USE_MATERIAL) { + } else if (property.properties.at(0).toByteArray() == USE_MATERIAL) { tex.assign(tex.useMaterial, property.properties.at(index).value()); - } else if (property.properties.at(0) == TRANSLATION) { + } else if (property.properties.at(0).toByteArray() == TRANSLATION) { tex.assign(tex.translation, tex.translation + getVec3(property.properties, index)); - } else if (property.properties.at(0) == ROTATION) { + } else if (property.properties.at(0).toByteArray() == ROTATION) { tex.assign(tex.rotation, getVec3(property.properties, index)); - } else if (property.properties.at(0) == SCALING) { + } else if (property.properties.at(0).toByteArray() == SCALING) { auto newScaling = getVec3(property.properties, index); if (newScaling.x == 0.0f) { newScaling.x = 1.0f; @@ -919,41 +919,41 @@ HFMModel* FBXSerializer::extractHFMModel(const hifi::VariantMultiHash& mapping, foreach(const FBXNode& property, subobject.children) { if (property.name == propertyName) { - if (property.properties.at(0) == DIFFUSE_COLOR) { + if (property.properties.at(0).toByteArray() == DIFFUSE_COLOR) { material.diffuseColor = getVec3(property.properties, index); - } else if (property.properties.at(0) == DIFFUSE_FACTOR) { + } else if (property.properties.at(0).toByteArray() == DIFFUSE_FACTOR) { material.diffuseFactor = property.properties.at(index).value(); - } else if (property.properties.at(0) == DIFFUSE) { + } else if (property.properties.at(0).toByteArray() == DIFFUSE) { // NOTE: this is uneeded but keep it for now for debug // material.diffuseColor = getVec3(property.properties, index); // material.diffuseFactor = 1.0; - } else if (property.properties.at(0) == SPECULAR_COLOR) { + } else if (property.properties.at(0).toByteArray() == SPECULAR_COLOR) { material.specularColor = getVec3(property.properties, index); - } else if (property.properties.at(0) == SPECULAR_FACTOR) { + } else if (property.properties.at(0).toByteArray() == SPECULAR_FACTOR) { material.specularFactor = property.properties.at(index).value(); - } else if (property.properties.at(0) == SPECULAR) { + } else if (property.properties.at(0).toByteArray() == SPECULAR) { // NOTE: this is uneeded but keep it for now for debug // material.specularColor = getVec3(property.properties, index); // material.specularFactor = 1.0; - } else if (property.properties.at(0) == EMISSIVE_COLOR) { + } else if (property.properties.at(0).toByteArray() == EMISSIVE_COLOR) { material.emissiveColor = getVec3(property.properties, index); - } else if (property.properties.at(0) == EMISSIVE_FACTOR) { + } else if (property.properties.at(0).toByteArray() == EMISSIVE_FACTOR) { material.emissiveFactor = property.properties.at(index).value(); - } else if (property.properties.at(0) == EMISSIVE) { + } else if (property.properties.at(0).toByteArray() == EMISSIVE) { // NOTE: this is uneeded but keep it for now for debug // material.emissiveColor = getVec3(property.properties, index); // material.emissiveFactor = 1.0; - } else if (property.properties.at(0) == AMBIENT_FACTOR) { + } else if (property.properties.at(0).toByteArray() == AMBIENT_FACTOR) { material.ambientFactor = property.properties.at(index).value(); // Detected just for Blender AO vs lightmap - } else if (property.properties.at(0) == SHININESS) { + } else if (property.properties.at(0).toByteArray() == SHININESS) { material.shininess = property.properties.at(index).value(); - } else if (property.properties.at(0) == OPACITY) { + } else if (property.properties.at(0).toByteArray() == OPACITY) { material.opacity = property.properties.at(index).value(); - } else if (property.properties.at(0) == REFLECTION_FACTOR) { + } else if (property.properties.at(0).toByteArray() == REFLECTION_FACTOR) { // Blender 2.79 and below set REFLECTION_FACTOR, but there is no way to actually change that value in their UI, // so we are falling back to non-PBS material. if (isBlenderVersionLower280 == true) { @@ -965,51 +965,51 @@ HFMModel* FBXSerializer::extractHFMModel(const hifi::VariantMultiHash& mapping, } // Sting Ray Material Properties!!!! - else if (property.properties.at(0) == MAYA_USE_NORMAL_MAP) { + else if (property.properties.at(0).toByteArray() == MAYA_USE_NORMAL_MAP) { material.isPBSMaterial = true; material.useNormalMap = (bool)property.properties.at(index).value(); - } else if (property.properties.at(0) == MAYA_BASE_COLOR) { + } else if (property.properties.at(0).toByteArray() == MAYA_BASE_COLOR) { material.isPBSMaterial = true; material.diffuseColor = getVec3(property.properties, index); - } else if (property.properties.at(0) == MAYA_USE_COLOR_MAP) { + } else if (property.properties.at(0).toByteArray() == MAYA_USE_COLOR_MAP) { material.isPBSMaterial = true; material.useAlbedoMap = (bool) property.properties.at(index).value(); - } else if (property.properties.at(0) == MAYA_ROUGHNESS) { + } else if (property.properties.at(0).toByteArray() == MAYA_ROUGHNESS) { material.isPBSMaterial = true; material.roughness = property.properties.at(index).value(); - } else if (property.properties.at(0) == MAYA_USE_ROUGHNESS_MAP) { + } else if (property.properties.at(0).toByteArray() == MAYA_USE_ROUGHNESS_MAP) { material.isPBSMaterial = true; material.useRoughnessMap = (bool)property.properties.at(index).value(); - } else if (property.properties.at(0) == MAYA_METALLIC) { + } else if (property.properties.at(0).toByteArray() == MAYA_METALLIC) { material.isPBSMaterial = true; material.metallic = property.properties.at(index).value(); - } else if (property.properties.at(0) == MAYA_USE_METALLIC_MAP) { + } else if (property.properties.at(0).toByteArray() == MAYA_USE_METALLIC_MAP) { material.isPBSMaterial = true; material.useMetallicMap = (bool)property.properties.at(index).value(); - } else if (property.properties.at(0) == MAYA_EMISSIVE) { + } else if (property.properties.at(0).toByteArray() == MAYA_EMISSIVE) { material.isPBSMaterial = true; material.emissiveColor = getVec3(property.properties, index); - } else if (property.properties.at(0) == MAYA_EMISSIVE_INTENSITY) { + } else if (property.properties.at(0).toByteArray() == MAYA_EMISSIVE_INTENSITY) { material.isPBSMaterial = true; material.emissiveIntensity = property.properties.at(index).value(); - } else if (property.properties.at(0) == MAYA_USE_EMISSIVE_MAP) { + } else if (property.properties.at(0).toByteArray() == MAYA_USE_EMISSIVE_MAP) { material.isPBSMaterial = true; material.useEmissiveMap = (bool)property.properties.at(index).value(); - } else if (property.properties.at(0) == MAYA_USE_AO_MAP) { + } else if (property.properties.at(0).toByteArray() == MAYA_USE_AO_MAP) { material.isPBSMaterial = true; material.useOcclusionMap = (bool)property.properties.at(index).value(); - } else if (property.properties.at(0) == MAYA_UV_SCALE) { + } else if (property.properties.at(0).toByteArray() == MAYA_UV_SCALE) { if (property.properties.size() == MAYA_UV_SCALE_PROPERTY_LENGTH) { // properties: { "Maya|uv_scale", "Vector2D", "Vector2", nothing, double, double } glm::vec3 scale = glm::vec3(property.properties.at(4).value(), property.properties.at(5).value(), 1.0); @@ -1024,7 +1024,7 @@ HFMModel* FBXSerializer::extractHFMModel(const hifi::VariantMultiHash& mapping, } materialParam.scaling *= scale; } - } else if (property.properties.at(0) == MAYA_UV_OFFSET) { + } else if (property.properties.at(0).toByteArray() == MAYA_UV_OFFSET) { if (property.properties.size() == MAYA_UV_OFFSET_PROPERTY_LENGTH) { // properties: { "Maya|uv_offset", "Vector2D", "Vector2", nothing, double, double } glm::vec3 translation = glm::vec3(property.properties.at(4).value(), property.properties.at(5).value(), 1.0); @@ -1078,7 +1078,7 @@ HFMModel* FBXSerializer::extractHFMModel(const hifi::VariantMultiHash& mapping, } } else if (object.name == "Deformer") { - if (object.properties.last() == "Cluster") { + if (object.properties.last().toByteArray() == "Cluster") { Cluster cluster; foreach (const FBXNode& subobject, object.children) { if (subobject.name == "Indexes") { @@ -1098,7 +1098,7 @@ HFMModel* FBXSerializer::extractHFMModel(const hifi::VariantMultiHash& mapping, clusters.insert(getID(object.properties), cluster); } - } else if (object.properties.last() == "BlendShapeChannel") { + } else if (object.properties.last().toByteArray() == "BlendShapeChannel") { hifi::ByteArray name = object.properties.at(1).toByteArray(); name = name.left(name.indexOf('\0')); @@ -1140,7 +1140,7 @@ HFMModel* FBXSerializer::extractHFMModel(const hifi::VariantMultiHash& mapping, static const QVariant OP = hifi::ByteArray("OP"); foreach (const FBXNode& connection, child.children) { if (connection.name == "C" || connection.name == "Connect") { - if (connection.properties.at(0) == OO) { + if (connection.properties.at(0).toByteArray() == OO) { QString childID = getID(connection.properties, 1); QString parentID = getID(connection.properties, 2); ooChildToParent.insert(childID, parentID); @@ -1154,7 +1154,7 @@ HFMModel* FBXSerializer::extractHFMModel(const hifi::VariantMultiHash& mapping, _lightmapOffset = glm::clamp((*lightIt).second.color.x, 0.f, 1.f); } } - } else if (connection.properties.at(0) == OP) { + } else if (connection.properties.at(0).toByteArray() == OP) { int counter = 0; hifi::ByteArray type = connection.properties.at(3).toByteArray().toLower(); if (type.contains("DiffuseFactor")) { diff --git a/libraries/model-serializers/src/FBXSerializer_Mesh.cpp b/libraries/model-serializers/src/FBXSerializer_Mesh.cpp index 90942c877ae..f2c1894dd1e 100644 --- a/libraries/model-serializers/src/FBXSerializer_Mesh.cpp +++ b/libraries/model-serializers/src/FBXSerializer_Mesh.cpp @@ -220,10 +220,10 @@ ExtractedMesh FBXSerializer::extractMesh(const FBXNode& object, unsigned int& me } else if (subdata.name == "NormalsIndex") { data.normalIndices = getIntVector(subdata); - } else if (subdata.name == "MappingInformationType" && subdata.properties.at(0) == BY_VERTICE) { + } else if (subdata.name == "MappingInformationType" && subdata.properties.at(0).toByteArray() == BY_VERTICE) { data.normalsByVertex = true; - } else if (subdata.name == "ReferenceInformationType" && subdata.properties.at(0) == INDEX_TO_DIRECT) { + } else if (subdata.name == "ReferenceInformationType" && subdata.properties.at(0).toByteArray() == INDEX_TO_DIRECT) { indexToDirect = true; } } @@ -240,10 +240,10 @@ ExtractedMesh FBXSerializer::extractMesh(const FBXNode& object, unsigned int& me } else if (subdata.name == "ColorsIndex" || subdata.name == "ColorIndex") { data.colorIndices = getIntVector(subdata); - } else if (subdata.name == "MappingInformationType" && subdata.properties.at(0) == BY_VERTICE) { + } else if (subdata.name == "MappingInformationType" && subdata.properties.at(0).toByteArray() == BY_VERTICE) { data.colorsByVertex = true; - } else if (subdata.name == "ReferenceInformationType" && subdata.properties.at(0) == INDEX_TO_DIRECT) { + } else if (subdata.name == "ReferenceInformationType" && subdata.properties.at(0).toByteArray() == INDEX_TO_DIRECT) { indexToDirect = true; } } @@ -334,7 +334,7 @@ ExtractedMesh FBXSerializer::extractMesh(const FBXNode& object, unsigned int& me if (subdata.name == "Materials") { materials = getIntVector(subdata); } else if (subdata.name == "MappingInformationType") { - if (subdata.properties.at(0) == BY_POLYGON) { + if (subdata.properties.at(0).toByteArray() == BY_POLYGON) { isMaterialPerPolygon = true; } } else { diff --git a/tests/model-serializers/src/ModelSerializersTests.cpp b/tests/model-serializers/src/ModelSerializersTests.cpp index 05e3633d7aa..8d537608999 100644 --- a/tests/model-serializers/src/ModelSerializersTests.cpp +++ b/tests/model-serializers/src/ModelSerializersTests.cpp @@ -159,3 +159,63 @@ void ModelSerializersTests::loadGLTF() { QVERIFY(expectWarnings == (model->loadWarningCount>0)); QVERIFY(expectErrors == (model->loadErrorCount>0)); } + +/*void ModelSerializersTests::loadFBX() { + // QTTODO: fix path, but how? + QString filename("/home/ksuprynowicz/overte/overte/interface/resources/serverless/Models/dome.fbx"); + QFile fbx_file(filename); + QVERIFY(fbx_file.open(QIODevice::ReadOnly)); + + QByteArray data = fbx_file.readAll(); + QByteArray uncompressedData; + QUrl url("https://example.com"); + + qInfo() << "URL: " << url; + + if (filename.toLower().endsWith(".gz")) { + url.setPath("/" + filename.chopped(3)); + + if (gunzip(data, uncompressedData)) { + qInfo() << "Uncompressed into" << uncompressedData.length(); + } else { + qCritical() << "Failed to uncompress"; + } + } else { + url.setPath("/" + filename); + uncompressedData = data; + } + + + ModelLoader loader; + QMultiHash serializerMapping; + std::string webMediaType; + + serializerMapping.insert("combineParts", true); + serializerMapping.insert("deduplicateIndices", true); + + qInfo() << "Loading model from" << uncompressedData.length() << "bytes data, url" << url; + + // Check that we can find a serializer for this + auto serializer = DependencyManager::get()->getSerializerForMediaType(uncompressedData, url, webMediaType); + QVERIFY(serializer); + + + + hfm::Model::Pointer model = loader.load(uncompressedData, serializerMapping, url, webMediaType); + QVERIFY(model); + + if (!model) { + // We expected this parse to fail, so nothing more to do here. + return; + } + + QVERIFY(!model->meshes.empty()); + QVERIFY(!model->joints.empty()); + + qInfo() << "Model was loaded with" << model->meshes.count() << "meshes and" << model->joints.count() << "joints. Found" << model->loadWarningCount << "warnings and" << model->loadErrorCount << "errors"; + + // Some models we test are expected to be broken. We're testing that we can load the model without blowing up, + // so loading it with errors is still a successful test. + QVERIFY(model->loadWarningCount == 0); + QVERIFY(model->loadErrorCount == 0); +}*/ diff --git a/tests/model-serializers/src/ModelSerializersTests.h b/tests/model-serializers/src/ModelSerializersTests.h index 45b913a5d7d..89a95919874 100644 --- a/tests/model-serializers/src/ModelSerializersTests.h +++ b/tests/model-serializers/src/ModelSerializersTests.h @@ -21,6 +21,7 @@ private slots: void initTestCase(); void loadGLTF_data(); void loadGLTF(); + //void loadFBX(); }; From d483e0aecdf13c809e362093be7316200574f6ae Mon Sep 17 00:00:00 2001 From: Ada Date: Sun, 26 Oct 2025 17:51:07 +1000 Subject: [PATCH 058/111] Add new QML files --- interface/resources/qml/overte/BodyText.qml | 23 + interface/resources/qml/overte/Button.qml | 53 ++ interface/resources/qml/overte/ComboBox.qml | 140 +++++ interface/resources/qml/overte/Dialog.qml | 81 +++ interface/resources/qml/overte/Label.qml | 9 + .../resources/qml/overte/MessageDialog.qml | 114 ++++ interface/resources/qml/overte/README.md | 17 + .../resources/qml/overte/RoundButton.qml | 52 ++ interface/resources/qml/overte/Ruler.qml | 29 + interface/resources/qml/overte/ScrollBar.qml | 150 +++++ interface/resources/qml/overte/Slider.qml | 70 +++ interface/resources/qml/overte/SpinBox.qml | 143 +++++ interface/resources/qml/overte/StackView.qml | 74 +++ interface/resources/qml/overte/Switch.qml | 74 +++ interface/resources/qml/overte/TabBar.qml | 22 + interface/resources/qml/overte/TabButton.qml | 56 ++ interface/resources/qml/overte/TextArea.qml | 35 ++ interface/resources/qml/overte/TextField.qml | 36 ++ interface/resources/qml/overte/Theme.qml | 192 ++++++ interface/resources/qml/overte/ToolTip.qml | 44 ++ interface/resources/qml/overte/WidgetZoo.qml | 138 ++++ .../qml/overte/avatar_picker/AvatarItem.qml | 115 ++++ .../qml/overte/avatar_picker/AvatarPicker.qml | 366 +++++++++++ .../resources/qml/overte/avatar_picker/qmldir | 3 + interface/resources/qml/overte/chat/Chat.qml | 127 ++++ .../resources/qml/overte/chat/ChatPage.qml | 222 +++++++ .../qml/overte/chat/MessageBlock.qml | 77 +++ .../qml/overte/chat/SettingsPage.qml | 84 +++ .../qml/overte/contacts/AccountAvatar.qml | 57 ++ .../qml/overte/contacts/AccountContact.qml | 107 ++++ .../qml/overte/contacts/ContactsList.qml | 207 ++++++ .../qml/overte/contacts/MyAccountInfo.qml | 136 ++++ .../qml/overte/contacts/SessionContact.qml | 88 +++ .../resources/qml/overte/contacts/qmldir | 6 + .../qml/overte/dialogs/AssetDialog.qml | 244 ++++++++ .../qml/overte/dialogs/FileDialog.qml | 591 ++++++++++++++++++ .../resources/qml/overte/dialogs/README.md | 1 + .../overte/dialogs/RunningScriptsDialog.qml | 286 +++++++++ interface/resources/qml/overte/dialogs/qmldir | 4 + .../resources/qml/overte/icons/add_friend.svg | 2 + .../qml/overte/icons/admin_shield.svg | 2 + .../resources/qml/overte/icons/arrow_up.svg | 2 + .../resources/qml/overte/icons/close.svg | 2 + interface/resources/qml/overte/icons/copy.svg | 2 + .../resources/qml/overte/icons/eye_closed.svg | 2 + .../resources/qml/overte/icons/eye_open.svg | 2 + .../resources/qml/overte/icons/folder.svg | 2 + .../resources/qml/overte/icons/gold_star.svg | 2 + interface/resources/qml/overte/icons/info.svg | 2 + .../qml/overte/icons/no_avatar_icon.svg | 2 + .../resources/qml/overte/icons/pause.svg | 2 + .../resources/qml/overte/icons/pencil.svg | 2 + interface/resources/qml/overte/icons/plus.svg | 2 + .../resources/qml/overte/icons/reload.svg | 2 + .../qml/overte/icons/remove_friend.svg | 2 + .../resources/qml/overte/icons/search.svg | 2 + interface/resources/qml/overte/icons/send.svg | 2 + .../qml/overte/icons/settings_cog.svg | 2 + .../qml/overte/icons/skip_backward.svg | 2 + .../qml/overte/icons/skip_forward.svg | 2 + .../qml/overte/icons/speaker_active.svg | 2 + .../qml/overte/icons/speaker_inactive.svg | 2 + .../qml/overte/icons/speaker_muted.svg | 2 + .../qml/overte/icons/triangle_down.svg | 2 + .../qml/overte/icons/triangle_left.svg | 2 + .../qml/overte/icons/triangle_right.svg | 2 + .../qml/overte/icons/triangle_up.svg | 2 + .../qml/overte/icons/unset_avatar.svg | 2 + .../qml/overte/icons_src/add_friend.svg | 2 + .../qml/overte/icons_src/admin_shield.svg | 78 +++ .../qml/overte/icons_src/arrow_up.svg | 67 ++ .../resources/qml/overte/icons_src/close.svg | 67 ++ .../resources/qml/overte/icons_src/copy.svg | 68 ++ .../qml/overte/icons_src/eye_closed.svg | 84 +++ .../qml/overte/icons_src/eye_open.svg | 69 ++ .../resources/qml/overte/icons_src/folder.svg | 154 +++++ .../qml/overte/icons_src/gold_star.svg | 122 ++++ .../resources/qml/overte/icons_src/info.svg | 68 ++ .../qml/overte/icons_src/no_avatar_icon.svg | 226 +++++++ .../resources/qml/overte/icons_src/pause.svg | 76 +++ .../resources/qml/overte/icons_src/pencil.svg | 77 +++ .../resources/qml/overte/icons_src/plus.svg | 72 +++ .../resources/qml/overte/icons_src/reload.svg | 76 +++ .../qml/overte/icons_src/remove_friend.svg | 81 +++ .../resources/qml/overte/icons_src/search.svg | 69 ++ .../resources/qml/overte/icons_src/send.svg | 62 ++ .../qml/overte/icons_src/settings_cog.svg | 197 ++++++ .../qml/overte/icons_src/skip_backward.svg | 68 ++ .../qml/overte/icons_src/skip_forward.svg | 68 ++ .../qml/overte/icons_src/speaker.svg | 94 +++ .../qml/overte/icons_src/triangle_down.svg | 63 ++ .../qml/overte/icons_src/triangle_left.svg | 63 ++ .../qml/overte/icons_src/triangle_right.svg | 62 ++ .../qml/overte/icons_src/triangle_up.svg | 63 ++ .../qml/overte/icons_src/unset_avatar.svg | 79 +++ .../qml/overte/keyboard/Keyboard.qml | 192 ++++++ .../qml/overte/keyboard/KeyboardKey.qml | 82 +++ .../qml/overte/keyboard/keyboard_ansi.json | 506 +++++++++++++++ .../resources/qml/overte/keyboard/qmldir | 3 + .../qml/overte/login/LoginScreen.qml | 52 ++ .../qml/overte/login/assets/background.png | Bin 0 -> 8113 bytes .../qml/overte/login/assets/logo_dark.png | Bin 0 -> 3498 bytes .../qml/overte/login/assets/logo_light.png | Bin 0 -> 3181 bytes .../qml/overte/login/pages/LoginPage.qml | 137 ++++ .../qml/overte/login/pages/ProgressPage.qml | 83 +++ .../qml/overte/login/pages/RegisterPage.qml | 137 ++++ .../qml/overte/login/pages/StartPage.qml | 69 ++ .../resources/qml/overte/login/pages/qmldir | 4 + interface/resources/qml/overte/login/qmldir | 2 + interface/resources/qml/overte/qmldir | 19 + .../qml/overte/settings/ComboSetting.qml | 37 ++ .../qml/overte/settings/FolderSetting.qml | 46 ++ .../resources/qml/overte/settings/Header.qml | 27 + .../qml/overte/settings/SettingNote.qml | 13 + .../qml/overte/settings/Settings.qml | 45 ++ .../qml/overte/settings/SettingsPage.qml | 23 + .../qml/overte/settings/SliderSetting.qml | 76 +++ .../qml/overte/settings/SpinBoxSetting.qml | 34 + .../qml/overte/settings/SwitchSetting.qml | 28 + .../qml/overte/settings/WideComboSetting.qml | 32 + .../qml/overte/settings/pages/Audio.qml | 46 ++ .../qml/overte/settings/pages/Controls.qml | 204 ++++++ .../qml/overte/settings/pages/General.qml | 145 +++++ .../qml/overte/settings/pages/Graphics.qml | 102 +++ .../qml/overte/settings/pages/qmldir | 5 + .../resources/qml/overte/settings/qmldir | 11 + .../qml/overte/staging/MediaPlayer.qml | 386 ++++++++++++ .../resources/qml/overte/staging/Node.qml | 181 ++++++ .../qml/overte/staging/NodeGraph.qml | 29 + .../resources/qml/overte/staging/NodePlug.qml | 52 ++ .../resources/qml/overte/staging/README.md | 1 + .../qml/overte/staging/desktop/AppToolbar.qml | 82 +++ .../qml/overte/staging/desktop/Desktop.qml | 70 +++ .../qml/overte/staging/desktop/qmldir | 3 + .../overte/staging/place_picker/PlaceItem.qml | 149 +++++ .../staging/place_picker/PlacePicker.qml | 135 ++++ .../qml/overte/staging/place_picker/qmldir | 3 + .../overte/staging/tutorial/AvatarPicker.qml | 6 + .../overte/staging/tutorial/ControlsGuide.qml | 72 +++ .../qml/overte/staging/tutorial/Welcome.qml | 46 ++ .../staging/tutorial/assets/controls_dark.png | Bin 0 -> 17199 bytes .../tutorial/assets/controls_light.png | Bin 0 -> 17584 bytes .../staging/tutorial/assets/logo_dark.png | Bin 0 -> 3498 bytes .../staging/tutorial/assets/logo_light.png | Bin 0 -> 3181 bytes .../qml/overte/staging/tutorial/qmldir | 4 + .../qml/overte/workarounds/README.md | 4 + .../workarounds/RunningScripts_Window.qml | 14 + 147 files changed, 10003 insertions(+) create mode 100644 interface/resources/qml/overte/BodyText.qml create mode 100644 interface/resources/qml/overte/Button.qml create mode 100644 interface/resources/qml/overte/ComboBox.qml create mode 100644 interface/resources/qml/overte/Dialog.qml create mode 100644 interface/resources/qml/overte/Label.qml create mode 100644 interface/resources/qml/overte/MessageDialog.qml create mode 100644 interface/resources/qml/overte/README.md create mode 100644 interface/resources/qml/overte/RoundButton.qml create mode 100644 interface/resources/qml/overte/Ruler.qml create mode 100644 interface/resources/qml/overte/ScrollBar.qml create mode 100644 interface/resources/qml/overte/Slider.qml create mode 100644 interface/resources/qml/overte/SpinBox.qml create mode 100644 interface/resources/qml/overte/StackView.qml create mode 100644 interface/resources/qml/overte/Switch.qml create mode 100644 interface/resources/qml/overte/TabBar.qml create mode 100644 interface/resources/qml/overte/TabButton.qml create mode 100644 interface/resources/qml/overte/TextArea.qml create mode 100644 interface/resources/qml/overte/TextField.qml create mode 100644 interface/resources/qml/overte/Theme.qml create mode 100644 interface/resources/qml/overte/ToolTip.qml create mode 100644 interface/resources/qml/overte/WidgetZoo.qml create mode 100644 interface/resources/qml/overte/avatar_picker/AvatarItem.qml create mode 100644 interface/resources/qml/overte/avatar_picker/AvatarPicker.qml create mode 100644 interface/resources/qml/overte/avatar_picker/qmldir create mode 100644 interface/resources/qml/overte/chat/Chat.qml create mode 100644 interface/resources/qml/overte/chat/ChatPage.qml create mode 100644 interface/resources/qml/overte/chat/MessageBlock.qml create mode 100644 interface/resources/qml/overte/chat/SettingsPage.qml create mode 100644 interface/resources/qml/overte/contacts/AccountAvatar.qml create mode 100644 interface/resources/qml/overte/contacts/AccountContact.qml create mode 100644 interface/resources/qml/overte/contacts/ContactsList.qml create mode 100644 interface/resources/qml/overte/contacts/MyAccountInfo.qml create mode 100644 interface/resources/qml/overte/contacts/SessionContact.qml create mode 100644 interface/resources/qml/overte/contacts/qmldir create mode 100644 interface/resources/qml/overte/dialogs/AssetDialog.qml create mode 100644 interface/resources/qml/overte/dialogs/FileDialog.qml create mode 100644 interface/resources/qml/overte/dialogs/README.md create mode 100644 interface/resources/qml/overte/dialogs/RunningScriptsDialog.qml create mode 100644 interface/resources/qml/overte/dialogs/qmldir create mode 100644 interface/resources/qml/overte/icons/add_friend.svg create mode 100644 interface/resources/qml/overte/icons/admin_shield.svg create mode 100644 interface/resources/qml/overte/icons/arrow_up.svg create mode 100644 interface/resources/qml/overte/icons/close.svg create mode 100644 interface/resources/qml/overte/icons/copy.svg create mode 100644 interface/resources/qml/overte/icons/eye_closed.svg create mode 100644 interface/resources/qml/overte/icons/eye_open.svg create mode 100644 interface/resources/qml/overte/icons/folder.svg create mode 100644 interface/resources/qml/overte/icons/gold_star.svg create mode 100644 interface/resources/qml/overte/icons/info.svg create mode 100644 interface/resources/qml/overte/icons/no_avatar_icon.svg create mode 100644 interface/resources/qml/overte/icons/pause.svg create mode 100644 interface/resources/qml/overte/icons/pencil.svg create mode 100644 interface/resources/qml/overte/icons/plus.svg create mode 100644 interface/resources/qml/overte/icons/reload.svg create mode 100644 interface/resources/qml/overte/icons/remove_friend.svg create mode 100644 interface/resources/qml/overte/icons/search.svg create mode 100644 interface/resources/qml/overte/icons/send.svg create mode 100644 interface/resources/qml/overte/icons/settings_cog.svg create mode 100644 interface/resources/qml/overte/icons/skip_backward.svg create mode 100644 interface/resources/qml/overte/icons/skip_forward.svg create mode 100644 interface/resources/qml/overte/icons/speaker_active.svg create mode 100644 interface/resources/qml/overte/icons/speaker_inactive.svg create mode 100644 interface/resources/qml/overte/icons/speaker_muted.svg create mode 100644 interface/resources/qml/overte/icons/triangle_down.svg create mode 100644 interface/resources/qml/overte/icons/triangle_left.svg create mode 100644 interface/resources/qml/overte/icons/triangle_right.svg create mode 100644 interface/resources/qml/overte/icons/triangle_up.svg create mode 100644 interface/resources/qml/overte/icons/unset_avatar.svg create mode 100644 interface/resources/qml/overte/icons_src/add_friend.svg create mode 100644 interface/resources/qml/overte/icons_src/admin_shield.svg create mode 100644 interface/resources/qml/overte/icons_src/arrow_up.svg create mode 100644 interface/resources/qml/overte/icons_src/close.svg create mode 100644 interface/resources/qml/overte/icons_src/copy.svg create mode 100644 interface/resources/qml/overte/icons_src/eye_closed.svg create mode 100644 interface/resources/qml/overte/icons_src/eye_open.svg create mode 100644 interface/resources/qml/overte/icons_src/folder.svg create mode 100644 interface/resources/qml/overte/icons_src/gold_star.svg create mode 100644 interface/resources/qml/overte/icons_src/info.svg create mode 100644 interface/resources/qml/overte/icons_src/no_avatar_icon.svg create mode 100644 interface/resources/qml/overte/icons_src/pause.svg create mode 100644 interface/resources/qml/overte/icons_src/pencil.svg create mode 100644 interface/resources/qml/overte/icons_src/plus.svg create mode 100644 interface/resources/qml/overte/icons_src/reload.svg create mode 100644 interface/resources/qml/overte/icons_src/remove_friend.svg create mode 100644 interface/resources/qml/overte/icons_src/search.svg create mode 100644 interface/resources/qml/overte/icons_src/send.svg create mode 100644 interface/resources/qml/overte/icons_src/settings_cog.svg create mode 100644 interface/resources/qml/overte/icons_src/skip_backward.svg create mode 100644 interface/resources/qml/overte/icons_src/skip_forward.svg create mode 100644 interface/resources/qml/overte/icons_src/speaker.svg create mode 100644 interface/resources/qml/overte/icons_src/triangle_down.svg create mode 100644 interface/resources/qml/overte/icons_src/triangle_left.svg create mode 100644 interface/resources/qml/overte/icons_src/triangle_right.svg create mode 100644 interface/resources/qml/overte/icons_src/triangle_up.svg create mode 100644 interface/resources/qml/overte/icons_src/unset_avatar.svg create mode 100644 interface/resources/qml/overte/keyboard/Keyboard.qml create mode 100644 interface/resources/qml/overte/keyboard/KeyboardKey.qml create mode 100644 interface/resources/qml/overte/keyboard/keyboard_ansi.json create mode 100644 interface/resources/qml/overte/keyboard/qmldir create mode 100644 interface/resources/qml/overte/login/LoginScreen.qml create mode 100644 interface/resources/qml/overte/login/assets/background.png create mode 100644 interface/resources/qml/overte/login/assets/logo_dark.png create mode 100644 interface/resources/qml/overte/login/assets/logo_light.png create mode 100644 interface/resources/qml/overte/login/pages/LoginPage.qml create mode 100644 interface/resources/qml/overte/login/pages/ProgressPage.qml create mode 100644 interface/resources/qml/overte/login/pages/RegisterPage.qml create mode 100644 interface/resources/qml/overte/login/pages/StartPage.qml create mode 100644 interface/resources/qml/overte/login/pages/qmldir create mode 100644 interface/resources/qml/overte/login/qmldir create mode 100644 interface/resources/qml/overte/qmldir create mode 100644 interface/resources/qml/overte/settings/ComboSetting.qml create mode 100644 interface/resources/qml/overte/settings/FolderSetting.qml create mode 100644 interface/resources/qml/overte/settings/Header.qml create mode 100644 interface/resources/qml/overte/settings/SettingNote.qml create mode 100644 interface/resources/qml/overte/settings/Settings.qml create mode 100644 interface/resources/qml/overte/settings/SettingsPage.qml create mode 100644 interface/resources/qml/overte/settings/SliderSetting.qml create mode 100644 interface/resources/qml/overte/settings/SpinBoxSetting.qml create mode 100644 interface/resources/qml/overte/settings/SwitchSetting.qml create mode 100644 interface/resources/qml/overte/settings/WideComboSetting.qml create mode 100644 interface/resources/qml/overte/settings/pages/Audio.qml create mode 100644 interface/resources/qml/overte/settings/pages/Controls.qml create mode 100644 interface/resources/qml/overte/settings/pages/General.qml create mode 100644 interface/resources/qml/overte/settings/pages/Graphics.qml create mode 100644 interface/resources/qml/overte/settings/pages/qmldir create mode 100644 interface/resources/qml/overte/settings/qmldir create mode 100644 interface/resources/qml/overte/staging/MediaPlayer.qml create mode 100644 interface/resources/qml/overte/staging/Node.qml create mode 100644 interface/resources/qml/overte/staging/NodeGraph.qml create mode 100644 interface/resources/qml/overte/staging/NodePlug.qml create mode 100644 interface/resources/qml/overte/staging/README.md create mode 100644 interface/resources/qml/overte/staging/desktop/AppToolbar.qml create mode 100644 interface/resources/qml/overte/staging/desktop/Desktop.qml create mode 100644 interface/resources/qml/overte/staging/desktop/qmldir create mode 100644 interface/resources/qml/overte/staging/place_picker/PlaceItem.qml create mode 100644 interface/resources/qml/overte/staging/place_picker/PlacePicker.qml create mode 100644 interface/resources/qml/overte/staging/place_picker/qmldir create mode 100644 interface/resources/qml/overte/staging/tutorial/AvatarPicker.qml create mode 100644 interface/resources/qml/overte/staging/tutorial/ControlsGuide.qml create mode 100644 interface/resources/qml/overte/staging/tutorial/Welcome.qml create mode 100644 interface/resources/qml/overte/staging/tutorial/assets/controls_dark.png create mode 100644 interface/resources/qml/overte/staging/tutorial/assets/controls_light.png create mode 100644 interface/resources/qml/overte/staging/tutorial/assets/logo_dark.png create mode 100644 interface/resources/qml/overte/staging/tutorial/assets/logo_light.png create mode 100644 interface/resources/qml/overte/staging/tutorial/qmldir create mode 100644 interface/resources/qml/overte/workarounds/README.md create mode 100644 interface/resources/qml/overte/workarounds/RunningScripts_Window.qml diff --git a/interface/resources/qml/overte/BodyText.qml b/interface/resources/qml/overte/BodyText.qml new file mode 100644 index 00000000000..2c26d00cb77 --- /dev/null +++ b/interface/resources/qml/overte/BodyText.qml @@ -0,0 +1,23 @@ +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: handle embedded links + // Qt doesn't make it easy to theme rich text + onLinkActivated: link => Qt.openUrlExternally(link) +} diff --git a/interface/resources/qml/overte/Button.qml b/interface/resources/qml/overte/Button.qml new file mode 100644 index 00000000000..971a728c6d7 --- /dev/null +++ b/interface/resources/qml/overte/Button.qml @@ -0,0 +1,53 @@ +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 { + 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..6d148145250 --- /dev/null +++ b/interface/resources/qml/overte/ComboBox.qml @@ -0,0 +1,140 @@ +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 + + 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..9585a5ec980 --- /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..32a5abd5ab8 --- /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..86518c90a71 --- /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..5bc55a4150d --- /dev/null +++ b/interface/resources/qml/overte/README.md @@ -0,0 +1,17 @@ +# TODO List + +## Critical +- [ ] Virtual keyboard support for text fields +- [x] Asset browser +- [x] Running scripts window + +## High +- [x] Avatar picker +- [x] People app +- [ ] World list + +## Low +- [ ] More app +- [ ] Desktop apps bar/tablet app picker +- [ ] Create app +- [ ] Dashboard diff --git a/interface/resources/qml/overte/RoundButton.qml b/interface/resources/qml/overte/RoundButton.qml new file mode 100644 index 00000000000..74ea21154cf --- /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..d0520789dc3 --- /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..8dfe272c488 --- /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..686862780ae --- /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..4cb793bc0ac --- /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: Qt.ImhFormattedNumbersOnly + } + + 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..2bcd8537d8d --- /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..d95d9b6736d --- /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..242f2eb9e0b --- /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..f02e3507997 --- /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..261375e3e7d --- /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..662e7c3391b --- /dev/null +++ b/interface/resources/qml/overte/TextField.qml @@ -0,0 +1,36 @@ +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 + + 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..25e00f02559 --- /dev/null +++ b/interface/resources/qml/overte/Theme.qml @@ -0,0 +1,192 @@ +import QtQuick + +pragma Singleton +QtObject { + property bool useSystemColorScheme: true + property bool useSystemContrastMode: true + + // https://github.com/overte-org/overte/issues/1733 + property bool darkMode: ( + useSystemColorScheme ? + Qt.application.styleHints.colorScheme !== Qt.ColorScheme.Light : + true + ) + property bool highContrast: ( + useSystemContrastMode ? + Qt.application.styleHints.accessibility.contrastPreference === Qt.ContrastPreference.HighContrast : + false + ) + property bool reducedMotion: false + + // font face for UI elements + readonly property string fontFamily: "DejaVu Sans" + + // font face for document text + readonly property string bodyFontFamily: "DejaVu Sans" + + // font face for code editors + readonly property string monoFontFamily: "DejaVu Sans Mono" + + readonly property int fontPixelSize: 18 + readonly property int fontPixelSizeSmall: 14 + 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 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 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 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 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 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 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 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" + } +} diff --git a/interface/resources/qml/overte/ToolTip.qml b/interface/resources/qml/overte/ToolTip.qml new file mode 100644 index 00000000000..ac1c745cba2 --- /dev/null +++ b/interface/resources/qml/overte/ToolTip.qml @@ -0,0 +1,44 @@ +import QtQuick +import QtQuick.Controls +import "." + +ToolTip { + id: control + visible: parent.hovered + delay: 500 + + 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..bd7b3cdf9b6 --- /dev/null +++ b/interface/resources/qml/overte/WidgetZoo.qml @@ -0,0 +1,138 @@ +import QtQuick +import QtQuick.Window +import QtQuick.Layouts +import QtQuick.Controls + +import "." as Overte + +// debugging test case to view the themed widgets +Window { + id: root + width: 480 + height: 720 + visible: true + + Rectangle { + 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: "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.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 + } + + 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..98b4a7396bc --- /dev/null +++ b/interface/resources/qml/overte/avatar_picker/AvatarItem.qml @@ -0,0 +1,115 @@ +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 + required property string description + + implicitWidth: GridView.view.cellWidth + implicitHeight: GridView.view.cellHeight + + Overte.Button { + anchors.fill: parent + anchors.margins: 4 + + id: avatarButton + + Overte.ToolTip { + visible: description !== "" && parent.hovered + text: description + delay: 500 + } + + 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 + } + + Image { + source: "../icons/no_avatar_icon.svg" + sourceSize.width: width + sourceSize.height: height + fillMode: Image.PreserveAspectFit + visible: buttonIcon.status === Image.Error || buttonIcon.status === Image.Null + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: buttonLabel.top + anchors.margins: 4 + } + + 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: AvatarBookmarks.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/close.svg" + icon.width: 32 + icon.height: 32 + icon.color: Overte.Theme.paletteActive.buttonText + + 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 + + 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..72d453c19ca --- /dev/null +++ b/interface/resources/qml/overte/avatar_picker/AvatarPicker.qml @@ -0,0 +1,366 @@ +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)) { + // TODO: replace this kinda hacky thing we currently do + // with real URLs stored either in the FST or bookmark entry + let iconUrl = new URL(avatar.avatarUrl); + iconUrl.pathname = iconUrl.pathname.replace(/[.](?:fst|glb|fbx|vrm)$/i, ".jpg"); + + tmp.push({ + name: name, + avatarUrl: avatar.avatarUrl, + iconUrl: iconUrl.toString(), + description: "", + }); + } + + tmp.sort((a, b) => a.name.localeCompare(b.name)); + + avatarModel = tmp; + } + + Component.onCompleted: updateBookmarkModel() + + Connections { + target: AvatarBookmarks + + function onBookmarkAdded() { updateBookmarkModel(); } + function onBookmarkDeleted() { updateBookmarkModel(); } + } + + ColumnLayout { + anchors.fill: parent + + 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 + + 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/no_avatar_icon.svg"; + } + + if (!modelItem.tags) { modelItem.tags = []; } + if (!modelItem.description) { modelItem.description = ""; } + + 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: { + editDialog.editExisting = false; + editDialog.avatarName = ""; + editDialog.avatarUrl = ""; + editDialog.avatarDescription = ""; + + avatarNameField.text = ""; + avatarUrlField.text = ""; + avatarDescriptionField.text = ""; + + editDialog.open(); + } + } + } + } + + property int requestedDeleteIndex: -1 + property int requestedEditIndex: -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) { + requestedEditIndex = index; + editDialog.editExisting = true; + editDialog.avatarName = avatarModel[index].name; + editDialog.avatarUrl = avatarModel[index].avatarUrl; + editDialog.avatarDescription = avatarModel[index].description; + editDialog.open(); + } + + 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: editDialog + anchors.fill: parent + maxWidth: -1 + + property bool editExisting: false + property string avatarName: "" + property string avatarUrl: "" + property string avatarDescription: "" + + signal accepted + signal rejected + + onAccepted: { + if (editExisting) { + AvatarBookmarks.removeBookmark(editDialog.avatarName); + } + + AvatarBookmarks.addBookmark(avatarNameField.text, editDialog.avatarUrl); + close(); + } + + onRejected: 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: editDialog.editExisting ? qsTr("Edit avatar") : qsTr("Add new avatar") + } + Overte.Ruler { Layout.fillWidth: true } + + Overte.TextField { + Layout.fillWidth: true + placeholderText: qsTr("Avatar name") + text: editDialog.avatarName + id: avatarNameField + } + + Overte.TextField { + Layout.fillWidth: true + placeholderText: qsTr("Avatar URL (.fst, .glb, .vrm, .fbx)") + text: editDialog.avatarUrl + id: avatarUrlField + } + + ScrollView { + // TODO: support avatar descriptions + visible: false + + Layout.preferredHeight: Overte.Theme.fontPixelSize * 8 + Layout.fillWidth: true + + ScrollBar.vertical: Overte.ScrollBar { + interactive: false + anchors.right: parent.right + anchors.top: parent.top + anchors.bottom: parent.bottom + } + + contentWidth: availableWidth + + Overte.TextArea { + id: avatarDescriptionField + placeholderText: qsTr("Description (optional)") + text: editDialog.avatarDescription + wrapMode: Text.Wrap + font.pixelSize: Overte.Theme.fontPixelSizeSmall + } + } + + RowLayout { + Layout.preferredWidth: 720 + Layout.fillWidth: true + + Overte.Button { + Layout.fillWidth: true + Layout.preferredWidth: 1 + text: qsTr("Cancel") + + onClicked: { + editDialog.rejected(); + requestedEditIndex = -1; + } + } + + Item { + visible: editDialog.editExisting + Layout.preferredWidth: 1 + Layout.fillWidth: true + } + + Overte.Button { + visible: !editDialog.editExisting + Layout.fillWidth: true + Layout.preferredWidth: 1 + + enabled: avatarNameField.text !== "" + text: qsTr("Add Current") + + onClicked: { + avatarUrlField.text = MyAvatar.skeletonModelURL; + editDialog.accepted(); + requestedEditIndex = -1; + } + } + + Overte.Button { + Layout.fillWidth: true + Layout.preferredWidth: 1 + + backgroundColor: Overte.Theme.paletteActive.buttonAdd + text: editDialog.editExisting ? qsTr("Apply") : qsTr("Add") + enabled: avatarNameField.text !== "" && avatarUrlField.text !== "" + + onClicked: { + editDialog.accepted(); + requestedEditIndex = -1; + } + } + } + } + } +} diff --git a/interface/resources/qml/overte/avatar_picker/qmldir b/interface/resources/qml/overte/avatar_picker/qmldir new file mode 100644 index 00000000000..c5538c484bb --- /dev/null +++ b/interface/resources/qml/overte/avatar_picker/qmldir @@ -0,0 +1,3 @@ +module AvatarPicker +AvatarPicker 1.0 AvatarPicker.qml +AvatarItem 1.0 AvatarItem.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..fc436418e6d --- /dev/null +++ b/interface/resources/qml/overte/chat/Chat.qml @@ -0,0 +1,127 @@ +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: true + + property var typingIndicatorNames: ({}) + + signal messagePushed(name: string, body: string, time: string) + signal notificationPushed(text: string, time: string) + signal messagesCleared() + + function toScript(obj) { + sendToScript(JSON.stringify(obj)); + + // for debugging standalone with the qml tool + /*console.debug(JSON.stringify(obj)); + + switch (obj.event) { + case "send_message": + fromScript({event: "recv_message", name: "ada.tv", body: obj.body}); + break; + + case "start_typing": + fromScript({event: "start_typing", name: "ada.tv", uuid: "ba"}); + break; + + case "end_typing": + fromScript({event: "end_typing", name: "ada.tv", uuid: "ba"}); + break; + }*/ + } + + function fromScript(rawObj) { + const obj = JSON.parse(rawObj); + const timestamp = (obj.timestamp ? new Date(obj.timestamp) : new Date()).toTimeString(); + + switch (obj.event) { + case "recv_message": + messagePushed(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..611f5d56446 --- /dev/null +++ b/interface/resources/qml/overte/chat/ChatPage.qml @@ -0,0 +1,222 @@ +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 + + Overte.Switch { + id: broadcastSwitch + text: qsTr("Broadcast") + + Overte.ToolTip { + text: qsTr("Whether your messages will be broadcast across the whole domain, rather than limited to a local range.") + } + + checked: root.settingBroadcast + onToggled: { + root.settingBroadcast = checked; + root.sendSettingsUpdate(); + } + } + + Item { Layout.fillWidth: true } + + Overte.RoundButton { + Layout.alignment: Qt.AlignRight + 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 {} + + Connections { + target: root + + function onMessagesCleared() { + chatLog.model.clear(); + } + + function onMessagePushed(name, body, timestamp) { + chatLog.model.append({ + name: name, + body: ( + body + .replace(/\&/gi, "&") + .replace(/\[/gi, "[") + .replace(/\]/gi, "]") + .replace(/\/gi, ">") + .replace(/\'/gi, "'") + .replace(/\"/gi, """) + .replace(/\n/gi, "
") + ), + notification: "", + timestamp: timestamp, + }); + chatLog.currentIndex = chatLog.model.count - 1; + } + + function onNotificationPushed(text, timestamp) { + chatLog.model.append({ + name: "", + body: "", + notification: text, + timestamp: timestamp, + }); + 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: ( + broadcastSwitch.checked ? + 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..6431ea9668f --- /dev/null +++ b/interface/resources/qml/overte/chat/MessageBlock.qml @@ -0,0 +1,77 @@ +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 string timestamp + + 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 { + text: notification ? notification : name + font.pixelSize: Overte.Theme.fontPixelSizeSmall + font.bold: true + Layout.fillWidth: true + wrapMode: Text.Wrap + } + + Overte.Label { + Layout.alignment: Qt.AlignRight + horizontalAlignment: Text.AlignRight + font.pixelSize: Overte.Theme.fontPixelSizeSmall + text: timestamp + } + } + + Overte.BodyText { + Layout.leftMargin: 6 + Layout.rightMargin: 16 + // if we used Layout.fillWidth, the whole text line 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. (maybe would work with QML desktop notifications?) + //textFormat: TextEdit.MarkdownText + textFormat: TextEdit.RichText + + text: { + if (notification) { return ""; } + + return ( + body + .replace( + /(https?:\/\/[^\s]+)/gi, + `$1` + ) + ); + } + } +} diff --git a/interface/resources/qml/overte/chat/SettingsPage.qml b/interface/resources/qml/overte/chat/SettingsPage.qml new file mode 100644 index 00000000000..8fe5682bae3 --- /dev/null +++ b/interface/resources/qml/overte/chat/SettingsPage.qml @@ -0,0 +1,84 @@ +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("Chat Settings") + } + + 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(); + } + } + + RowLayout { + anchors.left: parent.left + anchors.right: parent.right + + Overte.Button { + Layout.alignment: Qt.AlignHCenter + text: qsTr("Clear History") + backgroundColor: Overte.Theme.paletteActive.buttonDestructive + + onClicked: { root.messagesCleared(); } + } + } +} diff --git a/interface/resources/qml/overte/contacts/AccountAvatar.qml b/interface/resources/qml/overte/contacts/AccountAvatar.qml new file mode 100644 index 00000000000..39be2b5e7ff --- /dev/null +++ b/interface/resources/qml/overte/contacts/AccountAvatar.qml @@ -0,0 +1,57 @@ +import QtQuick +import QtQuick.Effects + +import ".." as Overte + +Rectangle { + required property url source + required property int status + + 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 + source: avatar.source + + 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..0f70a909105 --- /dev/null +++ b/interface/resources/qml/overte/contacts/AccountContact.qml @@ -0,0 +1,107 @@ +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 { + 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..e85acdb3305 --- /dev/null +++ b/interface/resources/qml/overte/contacts/ContactsList.qml @@ -0,0 +1,207 @@ +import QtQuick +import QtQuick.Layouts +import QtQuick.Controls + +import ".." as Overte +import "." + +Rectangle { + id: root + anchors.fill: parent + implicitWidth: 480 + implicitHeight: 720 + color: Overte.Theme.paletteActive.base + + property string localSearchExpression: ".*" + property string accountSearchExpression: ".*" + + property list localContactsModel: [ + {user: "ada.tv", name: "ada.tv", volume: 1.0, badgeIconSource: "../icons/gold_star.svg"}, + {user: "admin", name: "Admin", volume: 1.0, badgeIconSource: "../icons/admin_shield.svg"}, + {user: "{81cb9b67-d034-40f6-9ab7-a4ecb62f8961}", name: "Anonymous", volume: 1.0, badgeIconSource: ""}, + {user: "", name: "Not logged in", volume: 1.0, badgeIconSource: ""}, + + {user: "ScrollingTest", name: "Scroll test", volume: 1.0, badgeIconSource: ""}, + {user: "ScrollingTest", name: "Scroll test", volume: 1.0, badgeIconSource: ""}, + {user: "ScrollingTest", name: "Scroll test", volume: 1.0, badgeIconSource: ""}, + {user: "ScrollingTest", name: "Scroll test", volume: 1.0, badgeIconSource: ""}, + {user: "ScrollingTest", name: "Scroll test", volume: 1.0, badgeIconSource: ""}, + {user: "ScrollingTest", name: "Scroll test", volume: 1.0, badgeIconSource: ""}, + {user: "ScrollingTest", name: "Scroll test", volume: 1.0, badgeIconSource: ""}, + {user: "ScrollingTest", name: "Scroll test", volume: 1.0, badgeIconSource: ""}, + {user: "ScrollingTest", name: "Scroll test", volume: 1.0, badgeIconSource: ""}, + {user: "ScrollingTest", name: "Scroll test", volume: 1.0, badgeIconSource: ""}, + {user: "ScrollingTest", name: "Scroll test", volume: 1.0, badgeIconSource: ""}, + {user: "ScrollingTest", name: "Scroll test", volume: 1.0, badgeIconSource: ""}, + {user: "ScrollingTest", name: "Scroll test", volume: 1.0, badgeIconSource: ""}, + ] + + property list accountContactsModel: [ + /*{user: "ada.tv", avatarUrl: "file:///home/ada/art/doodles/bevy blep avi.png", status: 0, friend: true, currentPlaceName: "overte_hub"}, + {user: "x74hc595", avatarUrl: "https://cdn.discordapp.com/avatars/761127759367634965/915ea6b0e6b380458bce46616fa1fe35.webp?size=96", status: 2, friend: true, currentPlaceName: "overte_hub"}, + {user: "juliangro", avatarUrl: "https://cdn.discordapp.com/avatars/181488002831351808/7c17a81a149da388d078d8ac795f64fe.webp?size=96", status: 1, friend: true, currentPlaceName: "overte_hub"},*/ + {user: "AccountContact", avatarUrl: "file:///home/ada/art/doodles/bevy blep avi.png", status: 0, friend: false, currentPlaceName: ""}, + {user: "AccountContact", avatarUrl: "file:///home/ada/art/doodles/bevy blep avi.png", status: 1, friend: false, currentPlaceName: ""}, + {user: "AccountContact", avatarUrl: "file:///home/ada/art/doodles/bevy blep avi.png", status: 2, friend: false, currentPlaceName: "overte_hub"}, + {user: "AccountContact", avatarUrl: "file:///home/ada/art/doodles/bevy blep avi.png", status: 3, friend: false, currentPlaceName: "overte_hub"}, + {user: "Friend", avatarUrl: "file:///home/ada/art/doodles/bevy blep avi.png", status: 2, friend: true, currentPlaceName: "overte_hub"}, + ] + + ColumnLayout { + anchors.fill: root + + MyAccountInfo { + Layout.fillWidth: true + status: MyAccountInfo.Status.LoggedOut + 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 { + ListView { + Layout.fillWidth: true + Layout.fillHeight: true + clip: true + + ScrollBar.vertical: Overte.ScrollBar { + policy: ScrollBar.AsNeeded + } + + contentWidth: root.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: root.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") + } + } + } + } +} diff --git a/interface/resources/qml/overte/contacts/MyAccountInfo.qml b/interface/resources/qml/overte/contacts/MyAccountInfo.qml new file mode 100644 index 00000000000..062840d3ad5 --- /dev/null +++ b/interface/resources/qml/overte/contacts/MyAccountInfo.qml @@ -0,0 +1,136 @@ +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 + } + + property int status: MyAccountInfo.Status.Invisible + property string avatarImgSource: "file:///home/ada/art/doodles/bevy blep avi.png" + + 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.avatarImgSource : + "../icons/unset_avatar.svg" + ) + status: root.status + Layout.preferredWidth: 64 + Layout.preferredHeight: 64 + Layout.leftMargin: 8 + Layout.rightMargin: 8 + } + + ColumnLayout { + Layout.rightMargin: 8 + Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter + + Overte.TextField { + Layout.fillWidth: true + verticalAlignment: Text.AlignVCenter + + id: displayName + text: "ada.tv" + placeholderText: qsTr("Display Name") + } + + 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; + } + + 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..cb7a9aaa1f5 --- /dev/null +++ b/interface/resources/qml/overte/contacts/SessionContact.qml @@ -0,0 +1,88 @@ +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 + + // session UUID or directory username + required property string user + required property string name + required property real volume + required property url badgeIconSource + + 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: user !== "" + //elide: Text.ElideRight + text: user + palette.buttonText: Overte.Theme.paletteActive.link + opacity: Overte.Theme.highContrast ? 1.0 : 0.7 + } + } + } + + 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: volume + + onMoved: { + // TODO + } + } + + Overte.Label { + Layout.preferredWidth: Overte.Theme.fontPixelSize * 3 + text: `${Math.round(volumeSlider.value * 100)}%` + } + } + } +} 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..19a88644583 --- /dev/null +++ b/interface/resources/qml/overte/dialogs/AssetDialog.qml @@ -0,0 +1,244 @@ +import QtCore +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Qt.labs.folderlistmodel +import Qt.labs.qmlmodels + +import ".." + +Rectangle { + id: dialog + color: Theme.paletteActive.base + + property url selectedFile: "" + property string searchExpression: ".*" + + readonly property var spawnableRegex: /\.(glb|fbx|fst|png|jpeg|jpg|webp)$/i + + ColumnLayout { + anchors.fill: parent + + RowLayout { + Layout.fillWidth: true + Layout.margins: 4 + + Button { + backgroundColor: Theme.paletteActive.buttonAdd + text: qsTr("Upload File") + + // TODO + onClicked: {} + } + + TextField { + Layout.fillWidth: true + Layout.preferredHeight: parent.height + + id: searchField + placeholderText: qsTr("Search…") + + Keys.onEnterPressed: searchButton.click() + Keys.onReturnPressed: searchButton.click() + } + + RoundButton { + id: searchButton + icon.source: "../icons/search.svg" + icon.width: 24 + icon.height: 24 + icon.color: Theme.paletteActive.buttonText + + onClicked: { + searchExpression = searchField.text === "" ? /.*/ : new RegExp(searchField.text); + } + } + } + + ScrollView { + Layout.fillWidth: true + Layout.fillHeight: true + + contentWidth: availableWidth + rightPadding: ScrollBar.vertical.width + + background: Rectangle { + color: Qt.darker(Theme.paletteActive.base, 1.2) + } + + ScrollBar.horizontal.policy: ScrollBar.AlwaysOff + + ScrollBar.vertical: ScrollBar { + policy: ScrollBar.AlwaysOn + interactive: true + anchors.right: parent.right + anchors.top: parent.top + anchors.bottom: parent.bottom + } + + TableView { + id: tableView + clip: true + selectionBehavior: TableView.SelectRows + selectionMode: 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 + } + + // TODO + rows: [ + { fileName: "bevy2.glb", fileModified: (new Date()).toLocaleDateString(null, Locale.ShortFormat), fileSize: "2.1 MiB" }, + { fileName: "cheese.jpg", fileModified: (new Date()).toLocaleDateString(null, Locale.ShortFormat), fileSize: "12 KiB" }, + { fileName: "metal pipe.wav", fileModified: (new Date()).toLocaleDateString(null, Locale.ShortFormat), fileSize: "200 KiB" }, + ] + } + + selectionModel: ItemSelectionModel { + onCurrentChanged: (index, _) => { + if (index.row === -1) { return; } + + const data = tableView.model.getRow(index.row); + selectedFile = data.fileName; + } + } + + delegate: Rectangle { + required property bool selected + required property bool current + required property int row + + readonly property bool rowCurrent: tableView.currentRow === row + + color: ( + rowCurrent ? + Theme.paletteActive.highlight : + (row % 2 === 0 ? Theme.paletteActive.base : Theme.paletteActive.alternateBase) + ) + implicitHeight: Theme.fontPixelSize * 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; + } + + switch (column) { + case 0: return nameWidth; + case 1: return mtimeWidth; + case 2: return sizeWidth; + } + } + + Text { + anchors.margins: 8 + anchors.fill: parent + + verticalAlignment: Text.AlignVCenter + horizontalAlignment: column === 2 ? Text.AlignRight : Text.AlignLeft + font.family: Theme.fontFamily + font.pixelSize: Theme.fontPixelSize + color: rowCurrent ? Theme.paletteActive.highlightedText : Theme.paletteActive.text + text: display + } + } + } + } + + GridLayout { + rows: 2 + columns: 2 + Layout.fillWidth: true + Layout.margins: 8 + + Button { + Layout.fillWidth: true + Layout.preferredWidth: 1 + + enabled: tableView.currentRow !== -1 + text: qsTr("Delete") + backgroundColor: Theme.paletteActive.buttonDestructive + + // TODO + onClicked: { + console.log("Delete", tableView.model.data(tableView.model.index(tableView.currentRow, 0))); + } + } + + Button { + Layout.fillWidth: true + Layout.preferredWidth: 1 + + enabled: tableView.currentRow !== -1 + text: qsTr("Copy Link") + backgroundColor: Theme.paletteActive.buttonInfo + + // TODO + onClicked: { + console.log("Copy Link", tableView.model.data(tableView.model.index(tableView.currentRow, 0))); + } + } + + Button { + Layout.fillWidth: true + Layout.preferredWidth: 1 + + enabled: tableView.currentRow !== -1 + text: qsTr("Rename") + + // TODO + onClicked: { + console.log("Rename", tableView.model.data(tableView.model.index(tableView.currentRow, 0))); + } + } + + Button { + Layout.fillWidth: true + Layout.preferredWidth: 1 + + enabled: { + if (tableView.currentRow === -1) { return false; } + + const index = tableView.model.index(tableView.currentRow, 0); + const data = tableView.model.data(index); + return !!data.match(spawnableRegex); + } + text: qsTr("Create Entity") + backgroundColor: Theme.paletteActive.buttonAdd + + // TODO + onClicked: { + console.log("Create Entity", tableView.model.data(tableView.model.index(tableView.currentRow, 0))); + } + } + } + } + + MessageDialog { + id: replaceWarningDialog + anchors.fill: parent + } +} diff --git a/interface/resources/qml/overte/dialogs/FileDialog.qml b/interface/resources/qml/overte/dialogs/FileDialog.qml new file mode 100644 index 00000000000..53275aa27d9 --- /dev/null +++ b/interface/resources/qml/overte/dialogs/FileDialog.qml @@ -0,0 +1,591 @@ +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 + + signal accepted + signal rejected + + property url currentFolder: StandardPaths.writableLocation(StandardPaths.HomeLocation) + property list nameFilters: ["*"] + property int fileMode: FileDialog.SaveFile + + property url selectedFile: "" + property list selectedFiles: [] + + // TODO, FIXME + property list history: [] + property int historyIndex: 0 + + property string searchExpression: ".*" + + // DEBUG + onAccepted: { + console.info("ACCEPTED"); + console.info(`selectedFile: ${selectedFile.toString()}`); + console.info(`selectedFiles: ${JSON.stringify(selectedFiles)}`); + } + + onRejected: { + console.info("REJECTED"); + } + + 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 + + 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.concat() + 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: { + 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 + onClicked: { + 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..69318da219c --- /dev/null +++ b/interface/resources/qml/overte/dialogs/RunningScriptsDialog.qml @@ -0,0 +1,286 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import ".." as Overte + +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: url.startsWith("qrc:") ? `${qsTr("Built-in:")} ${url}` : url + } + } + + Row { + Layout.alignment: Qt.AlignRight | Qt.AlignVCenter + Layout.margins: 8 + spacing: 2 + + // TODO: url copy button + /*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.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.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) + } + } + } + } + + 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: { + console.warn("TODO"); + } + } + + 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: { + console.warn("TODO"); + } + } + } + } + + 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(); + } + } + } + } + } +} diff --git a/interface/resources/qml/overte/dialogs/qmldir b/interface/resources/qml/overte/dialogs/qmldir new file mode 100644 index 00000000000..4a1c3c63a0b --- /dev/null +++ b/interface/resources/qml/overte/dialogs/qmldir @@ -0,0 +1,4 @@ +module Dialogs +FileDialog 1.0 FileDialog.qml +RunningScriptsDialog 1.0 RunningScriptsDialog.qml +AssetDialog 1.0 AssetDialog.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/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/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/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/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/no_avatar_icon.svg b/interface/resources/qml/overte/icons/no_avatar_icon.svg new file mode 100644 index 00000000000..4e536f0afea --- /dev/null +++ b/interface/resources/qml/overte/icons/no_avatar_icon.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_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/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/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/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/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/no_avatar_icon.svg b/interface/resources/qml/overte/icons_src/no_avatar_icon.svg new file mode 100644 index 00000000000..1078d75e54e --- /dev/null +++ b/interface/resources/qml/overte/icons_src/no_avatar_icon.svg @@ -0,0 +1,226 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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/keyboard/Keyboard.qml b/interface/resources/qml/overte/keyboard/Keyboard.qml new file mode 100644 index 00000000000..424189a9a12 --- /dev/null +++ b/interface/resources/qml/overte/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/keyboard/KeyboardKey.qml b/interface/resources/qml/overte/keyboard/KeyboardKey.qml new file mode 100644 index 00000000000..c899c02dfad --- /dev/null +++ b/interface/resources/qml/overte/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/keyboard/keyboard_ansi.json b/interface/resources/qml/overte/keyboard/keyboard_ansi.json new file mode 100644 index 00000000000..e812db21438 --- /dev/null +++ b/interface/resources/qml/overte/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/keyboard/qmldir b/interface/resources/qml/overte/keyboard/qmldir new file mode 100644 index 00000000000..0cd7e02edd0 --- /dev/null +++ b/interface/resources/qml/overte/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/login/LoginScreen.qml b/interface/resources/qml/overte/login/LoginScreen.qml new file mode 100644 index 00000000000..4a1adb9e48e --- /dev/null +++ b/interface/resources/qml/overte/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/login/assets/background.png b/interface/resources/qml/overte/login/assets/background.png new file mode 100644 index 0000000000000000000000000000000000000000..9b18c52b57a90031007950056b7b1cb0e6d63de6 GIT binary patch literal 8113 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 Y_)z>JF3vg&s=))7jG+?6dBXkw0UQA#umAu6 literal 0 HcmV?d00001 diff --git a/interface/resources/qml/overte/login/assets/logo_dark.png b/interface/resources/qml/overte/login/assets/logo_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..bf3380a55d33d6b9c61947d6f711acdbae9f3c2e GIT binary patch literal 3498 zcmZvfc{tSj+sD7(VHlIO423z!Q5__^vM)*2aV(KtWi4C8WSuZk*{XviqO!~o!;vLp zjIowN9BUYBmYA`RWrj!pJkRrcp7Y0Nd0+Q^-PiTL|9Zb3Sy>wMz(wEy0PvVxy=)Bt z;N;Pt4C6Su$M^`sM-Nxf)$1VuATRanfapTqu%l*Ylgs+H=+TvNi)XW~r#L3NQ&Zjj zFVuY|Y#=7#av$x3pj7ah^3)_e2uddlOU7Xte^+X?*?uj-;!Sn4am@R9Nx=YZQN{!p zwP9w0TV*nt05H4dBMe{w4gll^VD11v`CkR(YbV(MQa`mO|_>z)IB_#V1Kx_yE|fO3$hVf2$P56%V$7tJ<=+R885Ewd~h`7su)T0 zkoRcL-s~AUpU#Cdc{z69y1L8=wd;`#+ISS#e()X|B^|lfkcxw!yf{3b$~3gfsbL4X zXSv-Zxjhx2Jc#TZir6^oqlOhq@{WdxEg8s%6s5oyC%z)_uf6W%Szd$2181&WVD5P^ z+tg%phQB7QZ#`t4MH7Z4J-v?p~sGeo|$YV~2VCD!LrosnM*` z%=%_N-KLQ(M$_RBb7uH zF~xyDcju`G8;h?G_tOr!-11BOl<&dl?_+T4Nl)sz%y!y6s(GWa??OnfM(l)D)t4Oe zoW}N)@oQv&B8pG|O5%=Olwd%aTkumLx0PhLs1yd2sFKQq3X${iGZLBkwxBqTei*Z< zCIo!M#XP}dunK_oCz8z3Cxtomf-gw-US7Gy0~p826(vtV1H4Z!F(AEGkmAU zWmI`w$YhUb5X0H(hmz)n@mSlDt8aWlR@&1j7KoQJ9-mDb_?;p8Kp=OL$L)p9=}lOF zDogR7>fA^UNq)~z-vDPoeYnIPODR-#9bGHu-sx?&xNJB*bLq=UhXWfybV^L#4mH!k z$4~BfNJ(7^bWrMac_qQ=SjBo&{K`z^_fS<JGYoVw)l$DM7FP%gwq zJk~nD2aul?ndJ|TeE3)<-}QVslXkwXz90qrlEK;Q1sxlO;+|=E#RbzV-Ydp;v>!s31ZH*hQtyY;npLzm(&zSbHSqPCL)ua~s^`kD zUB#)W%{84t7taqBAuI0@KD+@+@4_200a|0Y@_I#SbZEcpkUZk!Tfig1dRr?J)6X31w~cJ(UEU$+gL0|Lw=}Zwn(BVA z$ugTBS(K?{+334u1=May=0^E5mJ;c*;t+>v!=sjFXE{}8T_qTUs)R;r zTj|cAy`eGnceKWVs!OaTngsh4H9+4)XV&;Rsrpah$S0~>{lZNWwNxg^sUg~+^ApK5HxYggX?C$sU2wy|720DaHZ+OE8`a;5zN#xx5|rZLR(XJ4XZn>9`l4P zNMl06@^Ct`o{7)j+0=!l%xd+*ZWj|Qr?TJ7Yy}WMAD5%9X{gB2dGu~Hekn|kYX>}w zI|kJiqB7@1+U7#KV-0-hASHdl+lh|;08dep<;HOek7~R0 zQ2Z0Vvb9EFNM;|)b}rZadhV(R;`)Oj=MNU(SejF9{c8u_;fTjPmKWC606I9k zx=)lpdHw}mNa`-^f8dq5;?f(-eLkQT{-WgUQ`3u0jRS$**c<0c;du>R^?z;W%ifO; z7+PJT6le13o#!l=dTf`b$G1Z4k|DOfyaFY9uwU}~VpyDV#maNZ!Fk_kfosBAOCwCs z838lfkB4(W>?fZ3TlHNB!bkKG!@Y?HvT^+PMbk!-AVXo<`* zGCxFulAmvd0y2$1oW81rHk++{^J>!-T-P=g4MkBC25)}10uAwn+oMixHi`Z2*wVd5 zCE#iuccAioTu1salC*8mE~s{{fT)(e;_vxhd+s!*OWY$%s}DiKyX}j2=mY(I`I6_h z@DqGb@vjT=Yg}7gNQQr`D7PVSLOp@t7}-RsY`bCoS@PR(|4csA>a%o*?*R|7UM=>3 zO{C{D8MGVXQoo=M>-O|?2L_)8cT)(n1f-&bLu=|yry|v}$0E4p+UJdaW_LNvJL}n! z0*mKeZkrubN05t8kctX9q_p?PzTVg{PM|x(&cZSYZ+Sk9*4VwE)b4_R!Rw}1ziw)8 zydGb)B)S~Kw&D{4S-cnc3U1YV`>+RLGb|R7&V@s1w!fE&+!7n>w$QaF=@yqpbGg$# z6K&sG7EzjQx^I8x$gc6H>n)W)iV_nw^Gr2*D@DS5ka&c3Nf#*KBMNJZ!GxMc!M$; zL-vb;piPpN1Agz%DeGntED3M++SZxucj;`&yBCOBS!T|! z^Nx+_^It(4>Nfg;-!*8?@CBd8BA8n-;&*8N%ENzG*pd~;xu&d(8Oic?&9+MJT?&Bg zT`4R92*7_lDeBger*k@e;l0{OJk4?b62rl|{Q3Alj;irhvmK=Ld*!}*iZ;EY8lc89@`W|9#3-O{0Ox%uBq zm*sGK+R<4~-_bX5J%YI?hqVyn(5RM)=y5ApPKq92W1d)bPEaU}(w1E2T^`xAmp5N1 zlE}8*wDdMvpftu%Q;;lng4Dln^drwkekUR@i5dBDm?|{`NNlP7;BEiQF39IjBtKc~ z8_L3d$+6+&UIr%Vn||SM*Nqo1XL0Kdeu3R_P9{B$9=O^jn#x=@Fmyo3VMh8Mh_SYx zLJ_WyPiWpd8&T_Mt{|I^KVe=~aYLIDR?0dPVJ>fsjuh9Bqb^LJ7Hk%NK)gtY0_Si1ZoXDsq zpLgBoz9193juZI=Xc1KFf^z>Z0nHzB>Yv7Q3LGQ($eRpLIIe7fFh66zTsnrF{?KtX z0*!0Nc|c7%tlp)w3#p|c}{YBH`4P^KDQZ$qk6}9bIj7_ zd+tzw7=w`|7^j@LgD(9pvGz|We#~QzAg4X*SDWPzz5(zE8jrs06;gxfBe?Knh*9;K z1cqhhu}RALIxf#TyFXE)STeK@NjZj}lCO|7ejS&WbK`*a(@i)96SK4XwY=xxActBV z1_VSL&Y_IhWBTA|-&Kg7yc=_E#mazR7w!f`@J<&&9CjcQ3Cf&=Rj3|J+!hS{z&L|% zR1XIAvi>FDcZ!pB57sk;mD#LP-e$ftw+4@S010#uluH2lNww0^qJ7c{=TTgkNeeM@ z2wgblkl=Nb%6)#>0jO(p>HSwa_}{4T3qOvy;lD%me~SME6EWi;ha(obColoPCco&* zM1lO|Y{Or6FyVy(uo+#C=AOhgDu@x$vRg%V;516mppQN}CZ9C23w literal 0 HcmV?d00001 diff --git a/interface/resources/qml/overte/login/assets/logo_light.png b/interface/resources/qml/overte/login/assets/logo_light.png new file mode 100644 index 0000000000000000000000000000000000000000..35285a0a0e3bc44f069824a7e2528dcb64e78fa9 GIT binary patch literal 3181 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 N_xFdr8a>0a{{c>Pu+#tm literal 0 HcmV?d00001 diff --git a/interface/resources/qml/overte/login/pages/LoginPage.qml b/interface/resources/qml/overte/login/pages/LoginPage.qml new file mode 100644 index 00000000000..d7c538f5bde --- /dev/null +++ b/interface/resources/qml/overte/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/login/pages/ProgressPage.qml b/interface/resources/qml/overte/login/pages/ProgressPage.qml new file mode 100644 index 00000000000..8c0d15f37ab --- /dev/null +++ b/interface/resources/qml/overte/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/login/pages/RegisterPage.qml b/interface/resources/qml/overte/login/pages/RegisterPage.qml new file mode 100644 index 00000000000..efa2d890092 --- /dev/null +++ b/interface/resources/qml/overte/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/login/pages/StartPage.qml b/interface/resources/qml/overte/login/pages/StartPage.qml new file mode 100644 index 00000000000..855ac6ed603 --- /dev/null +++ b/interface/resources/qml/overte/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/login/pages/qmldir b/interface/resources/qml/overte/login/pages/qmldir new file mode 100644 index 00000000000..816c3d70cec --- /dev/null +++ b/interface/resources/qml/overte/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/login/qmldir b/interface/resources/qml/overte/login/qmldir new file mode 100644 index 00000000000..02fe9379c9c --- /dev/null +++ b/interface/resources/qml/overte/login/qmldir @@ -0,0 +1,2 @@ +module Login +LoginScreen 1.0 LoginScreen.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..e2745ca51d5 --- /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..212ae2f50b3 --- /dev/null +++ b/interface/resources/qml/overte/settings/FolderSetting.qml @@ -0,0 +1,46 @@ +import QtQuick +import QtQuick.Layouts + +import "../" as Overte + +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 + + // prevent binding loops + Component.onCompleted: value = value + + 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 + + // TODO + } + } +} diff --git a/interface/resources/qml/overte/settings/Header.qml b/interface/resources/qml/overte/settings/Header.qml new file mode 100644 index 00000000000..8f526c0f1a7 --- /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..6c59009aad7 --- /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..cdf44822644 --- /dev/null +++ b/interface/resources/qml/overte/settings/Settings.qml @@ -0,0 +1,45 @@ +import QtQuick +import QtQuick.Layouts +import QtQuick.Controls + +import "../" 1.0 as Overte +import "." as OverteSettings +import "./pages" as SettingsPages + +Rectangle { + id: root + 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 {} + } +} diff --git a/interface/resources/qml/overte/settings/SettingsPage.qml b/interface/resources/qml/overte/settings/SettingsPage.qml new file mode 100644 index 00000000000..fa40b9088a6 --- /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..cf1e4caa79b --- /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..314420c8708 --- /dev/null +++ b/interface/resources/qml/overte/settings/SpinBoxSetting.qml @@ -0,0 +1,34 @@ +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 + } +} diff --git a/interface/resources/qml/overte/settings/SwitchSetting.qml b/interface/resources/qml/overte/settings/SwitchSetting.qml new file mode 100644 index 00000000000..bcbbdbde116 --- /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..0ba552fa7df --- /dev/null +++ b/interface/resources/qml/overte/settings/WideComboSetting.qml @@ -0,0 +1,32 @@ +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 + } +} 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..37f6f634083 --- /dev/null +++ b/interface/resources/qml/overte/settings/pages/Audio.qml @@ -0,0 +1,46 @@ +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: AudioScriptingInterface.devices.output + + // TODO: how do these work??? + //currentIndex: 0 + //onCurrentIndexChanged: AudioScriptingInterface.setOutputDevice(currentIndex, false) + } + + WideComboSetting { + text: "Input Device" + model: AudioScriptingInterface.devices.input + + // 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..243e719dbad --- /dev/null +++ b/interface/resources/qml/overte/settings/pages/Controls.qml @@ -0,0 +1,204 @@ +import "../../" as Overte +import "../" + +SettingsPage { + Header { + text: qsTr("Desktop") + + // Hack to reduce the dead space on the top of the page + height: implicitHeight + } + + SwitchSetting { + // FIXME: setting isn't exposed to script api + enabled: false + + text: qsTr("Invert Y") + + // TODO + value: false + } + + SliderSetting { + // FIXME: setting isn't exposed to script api + enabled: false + + text: qsTr("Mouse Sensitivity") + stepSize: 0.1 + from: 0.1 + to: 5.0 + valueToText: () => `${value.toFixed(1)}`; + + // TODO + value: 1.0 + } + + Header { text: qsTr("VR User") } + + SliderSetting { + text: qsTr("Height") + fineTweakButtons: true + stepSize: 0.01 + from: 0.8 + to: 2.5 + + valueToText: () => { + let meters = value.toFixed(2); + let totalInches = Math.round(value * 39.37008); + + let feet = Math.floor(totalInches / 12); + let inches = totalInches % 12; + + return `${feet}'${inches}" ${meters.toLocaleString()}m`; + } + + // FIXME: QML complains about userHeight not being bindable + value: { 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") + } + + Header { text: qsTr("VR Movement") } + + SliderSetting { + // FIXME: setting isn't exposed to script api + enabled: false + + text: qsTr("Turning Speed") + stepSize: 10 + from: 0 + to: 400 + valueToText: () => value < 50 ? qsTr("Snap turning") : `${value.toFixed(1)}`; + + // FIXME: not exposed to scripts or QML + /*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.toFixed(1)} 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"), + ] + + // TODO + } + + /*SwitchSetting { + id: useAvatarDefaultWalkingSpeed + text: qsTr("Use equipped avatar's default walking speed if available") + + // TODO + value: false + }*/ + + Header { text: qsTr("VR UI") } + + ComboSetting { + // FIXME: setting isn't exposed to script api + enabled: false + + text: qsTr("Tablet Input") + model: [ + qsTr("Laser"), + qsTr("Stylus"), + qsTr("Finger Touch"), + ] + + // TODO + } + + ComboSetting { + // FIXME: setting isn't exposed to script api + enabled: false + + text: qsTr("Virtual Keyboard Input") + model: [ + qsTr("Lasers"), + qsTr("Mallets"), + ] + + // TODO + } + + SliderSetting { + // FIXME: setting isn't exposed to script api + enabled: false + + text: qsTr("Laser Smoothing Delay") + stepSize: 0.1 + from: 0.0 + to: 2.0 + valueToText: () => `${value.toFixed(1)}s`; + + // TODO + value: 0.3 + } + + // 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..ad87e137011 --- /dev/null +++ b/interface/resources/qml/overte/settings/pages/General.qml @@ -0,0 +1,145 @@ +import "../../" as Overte +import "../" + +SettingsPage { + 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"), + ] + + onCurrentIndexChanged: { + switch (currentIndex) { + case 0: + Overte.useSystemColorScheme = false; + Overte.Theme.darkMode = true; + break; + + case 1: + Overte.useSystemColorScheme = false; + Overte.Theme.darkMode = false; + break; + + case 2: + Overte.useSystemColorScheme = true; + Overte.Theme.darkMode = true; + break; + } + } + } + + ComboSetting { + text: qsTr("Contrast") + model: [ + qsTr("Standard"), + qsTr("High"), + qsTr("System"), + ] + + onCurrentIndexChanged: { + switch (currentIndex) { + case 0: + Overte.useSystemContrastMode = false; + Overte.Theme.highContrast = false; + break; + + case 1: + Overte.useSystemContrastMode = false; + Overte.Theme.highContrast = true; + break; + + case 2: + Overte.useSystemContrastMode = true; + Overte.Theme.highContrast = false; + break; + } + } + } + + SwitchSetting { + text: qsTr("Reduced Motion") + value: Overte.Theme.reducedMotion + onValueChanged: () => Overte.Theme.reducedMotion = value + } + + SettingNote { + text: qsTr("This setting will disable UI animations that slide or scale.") + } + + Header { text: qsTr("Screenshots") } + + FolderSetting { + // TODO + enabled: false + + text: qsTr("Folder") + // TODO + value: Snapshot.getSnapshotsLocation() + onValueChanged: Snapshot.setSnapshotsLocation(value) + } + + ComboSetting { + text: qsTr("Format") + model: [ + "png", + "jpg", + "webp", + ] + onCurrentIndexChanged: Snapshot.setSnapshotFormat(model[currentIndex]) + } + + SpinBoxSetting { + // FIXME: setting isn't exposed to script api + enabled: false + + text: qsTr("Animation Duration") + from: 1 + to: 30 + + // TODO + value: 3 + } + + SettingNote { + text: "Animated screenshots are saved as GIFs." + } + + Header { text: qsTr("Privacy") } + + SwitchSetting { + // FIXME: setting isn't exposed to script api + enabled: false + + text: qsTr("Send Crash Reports") + // TODO + value: false + onValueChanged: () => {} + } + + SettingNote { + text: qsTr("Sending crash reports helps Overte development.") + } + + SwitchSetting { + // FIXME: setting isn't exposed to script api + enabled: false + + text: qsTr("Discord Rich Presence") + // TODO + value: false + onValueChanged: () => {} + } + + SettingNote { + text: qsTr("If this setting is enabled, your current world will be shown on your Discord profile.") + } +} 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..cef1a34ea2a --- /dev/null +++ b/interface/resources/qml/overte/settings/pages/Graphics.qml @@ -0,0 +1,102 @@ +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 { + text: qsTr("Anti-Aliasing") + textRole: "text" + valueRole: "mode" + + model: [ + { text: qsTr("None"), mode: "none" }, + { text: qsTr("4x MSAA"), mode: "msaa" }, + { text: qsTr("TAA"), mode: "taa" }, + { text: qsTr("FXAA"), mode: "fxaa" }, + ] + + // TODO + } + + ComboSetting { + text: qsTr("LOD Culling") + textRole: "text" + valueRole: "mode" + + model: [ + { text: qsTr("High Detail"), mode: 0 }, + { text: qsTr("Medium Detail"), mode: 1 }, + { text: qsTr("Low Detail"), mode: 2 }, + ] + + currentIndex: { currentIndex = LODManager.worldDetailQuality } + onCurrentIndexChanged: LODManager.worldDetailQuality = model[currentIndex].mode + } + + SwitchSetting { + text: qsTr("Custom Shaders") + + value: Render.proceduralMaterialsEnabled + onValueChanged: Render.proceduralMaterialsEnabled = value + } + + Header { text: qsTr("Advanced") } + + SettingNote { + text: qsTr("These settings are incompatible with MSAA and may reduce performance.") + } + + SwitchSetting { + id: advRenderingEnabled + text: qsTr("Rendering Effects") + + value: Render.renderMethod === 0 + onValueChanged: Render.renderMethod = value ? 0 : 1 + } + + SwitchSetting { + enabled: advRenderingEnabled.value + text: qsTr("Shadows") + + value: Render.shadowsEnabled + onValueChanged: Render.shadowsEnabled = value + } + + SwitchSetting { + enabled: advRenderingEnabled.value + text: qsTr("Bloom") + + value: Render.bloomEnabled + onValueChanged: Render.bloomEnabled = value + } + + SwitchSetting { + enabled: advRenderingEnabled.value + text: qsTr("Ambient Occlusion") + + value: Render.ambientOcclusionEnabled + onValueChanged: Render.ambientOcclusionEnabled = 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..bb3e89d423c --- /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..061600f1207 --- /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..cd6c287f0b1 --- /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..001ba7d73d4 --- /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..9e6e3552ab8 --- /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..38ec168a1d3 --- /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/place_picker/PlaceItem.qml b/interface/resources/qml/overte/staging/place_picker/PlaceItem.qml new file mode 100644 index 00000000000..78844a614aa --- /dev/null +++ b/interface/resources/qml/overte/staging/place_picker/PlaceItem.qml @@ -0,0 +1,149 @@ +import QtQuick +import QtQuick.Layouts + +import ".." as Overte + +Rectangle { + id: item + color: index % 2 === 0 ? Overte.Theme.paletteActive.base : Overte.Theme.paletteActive.alternateBase + + required property int index + + required property string name + property string description: listView.model[index].description ?? "" + property url thumbnail: listView.model[index].thumbnail ?? "" + + property int currentUsers: listView.model[index].current_attendance ?? 0 + property int maxUsers: { + const capacity = listView.model[index].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 ? "#80000000" : "#80ffffff" + } + } + + anchors.left: parent ? parent.left : undefined + anchors.right: parent ? parent.right : undefined + anchors.leftMargin: 4 + anchors.rightMargin: Overte.Theme.scrollbarWidth + + implicitHeight: 128 + + Component.onCompleted: { + // Hide redundant default descriptions that don't say anything + if (description === `A place in ${name}`) { + description = ""; + } + } + + Image { + anchors.fill: parent + fillMode: Image.PreserveAspectCrop + visible: !Overte.Theme.highContrast + source: thumbnail + } + + ColumnLayout { + anchors.left: item.left + anchors.top: item.top + anchors.bottom: item.bottom + anchors.right: controls.left + anchors.margins: 4 + spacing: 8 + + Rectangle { + Layout.alignment: Qt.AlignLeft | Qt.AlignBottom + Layout.maximumWidth: parent.width + implicitWidth: titleText.implicitWidth + 16 + implicitHeight: titleText.implicitHeight + 16 + color: textBackgroundColor + radius: Overte.Theme.borderRadius + + Overte.Label { + anchors.margins: 8 + anchors.fill: parent + + id: titleText + text: name + horizontalAlignment: Text.AlignLeft + verticalAlignment: Text.AlignTop + style: Text.Outline + styleColor: Overte.Theme.darkMode ? "black" : "white" + } + } + + Item { Layout.fillHeight: true } + + Rectangle { + visible: description !== "" + + Layout.alignment: Qt.AlignLeft | Qt.AlignBottom + Layout.maximumWidth: parent.width + implicitWidth: descriptionText.implicitWidth + 16 + implicitHeight: descriptionText.implicitHeight + 16 + color: textBackgroundColor + radius: Overte.Theme.borderRadius + + Overte.Label { + anchors.margins: 8 + anchors.fill: parent + + id: descriptionText + text: description + horizontalAlignment: Text.AlignLeft + verticalAlignment: Text.AlignTop + style: Text.Outline + styleColor: Overte.Theme.darkMode ? "black" : "white" + wrapMode: Text.Wrap + elide: Text.ElideRight + font.pixelSize: Overte.Theme.fontPixelSizeSmall + } + } + } + + ColumnLayout { + id: controls + anchors.top: item.top + anchors.bottom: item.bottom + anchors.right: item.right + anchors.margins: 8 + spacing: 4 + + Overte.RoundButton { + Layout.alignment: Qt.AlignCenter + text: ">" + backgroundColor: Overte.Theme.paletteActive.buttonAdd + } + + Overte.RoundButton { + Layout.alignment: Qt.AlignCenter + text: "P" + backgroundColor: Overte.Theme.paletteActive.buttonInfo + } + + Item { Layout.fillHeight: true } + + Rectangle { + Layout.alignment: Qt.AlignCenter + implicitWidth: 12 + implicitHeight: 12 + radius: width + border.width: Overte.Theme.borderWidth + border.color: Qt.darker(Overte.Theme.paletteActive.base, Overte.Theme.borderDarker) + color: { + if (currentUsers === 0) { + return "#808080"; + } else if (currentUsers < maxUsers) { + return "#00ff00"; + } else { + return "#ff0000"; + } + } + } + } +} diff --git a/interface/resources/qml/overte/staging/place_picker/PlacePicker.qml b/interface/resources/qml/overte/staging/place_picker/PlacePicker.qml new file mode 100644 index 00000000000..c6b1f702afe --- /dev/null +++ b/interface/resources/qml/overte/staging/place_picker/PlacePicker.qml @@ -0,0 +1,135 @@ +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 + + readonly property string protocolSignature: "6xYA55jcXgPHValo3Ba3/A==" + + Component.onCompleted: { + let xhr = new XMLHttpRequest(); + + xhr.onreadystatechange = () => { + if (xhr.readyState === XMLHttpRequest.DONE) { + console.debug("Finished downloading place list"); + try { + const body = JSON.parse(xhr.responseText); + + let accum = []; + for (const place of body.data.places) { + if ( + place.domain.protocol_version === protocolSignature + ) { + accum.push(place); + } + } + + listView.model = accum; + + console.debug("Finished parsing place list"); + } catch (e) {} + } + }; + + console.debug("Downloading place list…"); + xhr.open("GET", "https://mv.overte.org/server/api/v1/places"); + xhr.send(); + } + + ColumnLayout { + anchors.fill: parent + + RowLayout { + Layout.margins: 4 + + Overte.RoundButton { + id: settingsButton + icon.source: "../icons/settings_cog.svg" + icon.width: 24 + icon.height: 24 + icon.color: Overte.Theme.paletteActive.buttonText + + onClicked: print("TODO") + } + + 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; + } + } + } + + Overte.TabBar { + Layout.fillWidth: true + id: tabBar + + Overte.TabButton { text: qsTr("Public") } + Overte.TabButton { text: qsTr("Bookmarks") } + } + + Overte.Label { + Layout.fillWidth: true + Layout.fillHeight: true + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + visible: listView.model.length === 0 + text: qsTr("Loading…") + } + + StackLayout { + Layout.fillWidth: true + Layout.fillHeight: true + + ListView { + id: listView + visible: model.length !== 0 + spacing: 2 + clip: true + + ScrollBar.vertical: Overte.ScrollBar {} + + model: [] + delegate: PlaceItem {} + } + } + + Overte.Label { + Layout.margins: 8 + Layout.fillWidth: true + visible: listView.model.length !== 0 + verticalAlignment: Text.AlignVCenter + text: qsTr("%1 place(s)").arg(listView.model.length) + } + } +} diff --git a/interface/resources/qml/overte/staging/place_picker/qmldir b/interface/resources/qml/overte/staging/place_picker/qmldir new file mode 100644 index 00000000000..5b37f768aef --- /dev/null +++ b/interface/resources/qml/overte/staging/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/staging/tutorial/AvatarPicker.qml b/interface/resources/qml/overte/staging/tutorial/AvatarPicker.qml new file mode 100644 index 00000000000..26aef6ba25b --- /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..77619993232 --- /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..480d0be8cab --- /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 0000000000000000000000000000000000000000..3914ae96af6f84d224ba5ddd466c90ab87bd0413 GIT binary patch literal 17199 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 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..52abbe450d8b5869d61476db9b276c44cf90393b GIT binary patch literal 17584 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

T<=fDpq zum+QO-Fm7&N4KUUyQmT|M~`*;=Dq8$t){~2{K&iUm?{M-qbeNy4Q>F1+sTy+hL0_%H$%CVEl$u*9ogt()tDhv2&Cy5L8Uvx9Sw|c)D=;vqb#oE9 zLFWl$*=!eGjFvAk2Z&=(xjS3Ixlz99(_p#zvv;7``=LRN@L)QnZ^4 zYB3Xo`V;u@36mKefZDpIz)hWYHYAq(AK{*f_kU>jObC%P<5(h;pyq%73-E&BuvUhZ zQc@PXKM;T_l7yv%X?oNt^47!Ym+pTWL{(iC;axkUAt-_;AgUf=tdDcx?VeL2@_rGI zjB+X|-j$Hh+qljqB{J9XZ&)vX~Hp*W*Axaa=G5Vw_N!4!00mOOrp4 zUt}d4;X^Q}<4FO{CnF+#ac4}yCnAJs_>z=ztNegfhM73gb;ZL|-wDwfHW!{3VP_Ez z;=}MM%lf4s13lgLbMKll)LTlk`Z9MUjV2rFtN(AwE}svp|1x?Y=2-47*D>(KxDANmI4FOMhf5o*B7mm zyx&_$bJl3s_X`Jl!A*uBVuk?2Cfvq@HYj2Y34|0*#~uSl;f*L^6xMi82}9dP3dRHi z46I)RbRBP!_v{mBQ>2$1t5n89fFY8fKY#u=Gg#Toxglr($QI~jg*OD5&8mS|*N<0t^R1cnDw^s4W5vh%W%l;vnB% zFrF|13>%A<>ploS19VVX|5`AH@y3t^4G+W@pbwc@!pmeun}tS9q@p8$2YkF2EL}h1 z3os1l*jqIeO$Z$ixEkgOLZ*nVSd>E&-}^q;><=(pgg^EMKNd{XGQ=0}7Y`VA@!SD+ z#0WEsTa#A3ElHc%2aN}Oq!;WGUkbqk%ptG{so^@*=J*N|(102h0~QYiX4 zfJ9Ln)#(sl07ST>_(BAPpeu;*?AM6+0<0^-dKkO@WJ2M)9)zCpfJb`5fA`Ig-%pG9 z0<41+A$u0-_%obcg*p}xH|85W;Qr$05wMz(wEy0PvVxy=)Bt z;N;Pt4C6Su$M^`sM-Nxf)$1VuATRanfapTqu%l*Ylgs+H=+TvNi)XW~r#L3NQ&Zjj zFVuY|Y#=7#av$x3pj7ah^3)_e2uddlOU7Xte^+X?*?uj-;!Sn4am@R9Nx=YZQN{!p zwP9w0TV*nt05H4dBMe{w4gll^VD11v`CkR(YbV(MQa`mO|_>z)IB_#V1Kx_yE|fO3$hVf2$P56%V$7tJ<=+R885Ewd~h`7su)T0 zkoRcL-s~AUpU#Cdc{z69y1L8=wd;`#+ISS#e()X|B^|lfkcxw!yf{3b$~3gfsbL4X zXSv-Zxjhx2Jc#TZir6^oqlOhq@{WdxEg8s%6s5oyC%z)_uf6W%Szd$2181&WVD5P^ z+tg%phQB7QZ#`t4MH7Z4J-v?p~sGeo|$YV~2VCD!LrosnM*` z%=%_N-KLQ(M$_RBb7uH zF~xyDcju`G8;h?G_tOr!-11BOl<&dl?_+T4Nl)sz%y!y6s(GWa??OnfM(l)D)t4Oe zoW}N)@oQv&B8pG|O5%=Olwd%aTkumLx0PhLs1yd2sFKQq3X${iGZLBkwxBqTei*Z< zCIo!M#XP}dunK_oCz8z3Cxtomf-gw-US7Gy0~p826(vtV1H4Z!F(AEGkmAU zWmI`w$YhUb5X0H(hmz)n@mSlDt8aWlR@&1j7KoQJ9-mDb_?;p8Kp=OL$L)p9=}lOF zDogR7>fA^UNq)~z-vDPoeYnIPODR-#9bGHu-sx?&xNJB*bLq=UhXWfybV^L#4mH!k z$4~BfNJ(7^bWrMac_qQ=SjBo&{K`z^_fS<JGYoVw)l$DM7FP%gwq zJk~nD2aul?ndJ|TeE3)<-}QVslXkwXz90qrlEK;Q1sxlO;+|=E#RbzV-Ydp;v>!s31ZH*hQtyY;npLzm(&zSbHSqPCL)ua~s^`kD zUB#)W%{84t7taqBAuI0@KD+@+@4_200a|0Y@_I#SbZEcpkUZk!Tfig1dRr?J)6X31w~cJ(UEU$+gL0|Lw=}Zwn(BVA z$ugTBS(K?{+334u1=May=0^E5mJ;c*;t+>v!=sjFXE{}8T_qTUs)R;r zTj|cAy`eGnceKWVs!OaTngsh4H9+4)XV&;Rsrpah$S0~>{lZNWwNxg^sUg~+^ApK5HxYggX?C$sU2wy|720DaHZ+OE8`a;5zN#xx5|rZLR(XJ4XZn>9`l4P zNMl06@^Ct`o{7)j+0=!l%xd+*ZWj|Qr?TJ7Yy}WMAD5%9X{gB2dGu~Hekn|kYX>}w zI|kJiqB7@1+U7#KV-0-hASHdl+lh|;08dep<;HOek7~R0 zQ2Z0Vvb9EFNM;|)b}rZadhV(R;`)Oj=MNU(SejF9{c8u_;fTjPmKWC606I9k zx=)lpdHw}mNa`-^f8dq5;?f(-eLkQT{-WgUQ`3u0jRS$**c<0c;du>R^?z;W%ifO; z7+PJT6le13o#!l=dTf`b$G1Z4k|DOfyaFY9uwU}~VpyDV#maNZ!Fk_kfosBAOCwCs z838lfkB4(W>?fZ3TlHNB!bkKG!@Y?HvT^+PMbk!-AVXo<`* zGCxFulAmvd0y2$1oW81rHk++{^J>!-T-P=g4MkBC25)}10uAwn+oMixHi`Z2*wVd5 zCE#iuccAioTu1salC*8mE~s{{fT)(e;_vxhd+s!*OWY$%s}DiKyX}j2=mY(I`I6_h z@DqGb@vjT=Yg}7gNQQr`D7PVSLOp@t7}-RsY`bCoS@PR(|4csA>a%o*?*R|7UM=>3 zO{C{D8MGVXQoo=M>-O|?2L_)8cT)(n1f-&bLu=|yry|v}$0E4p+UJdaW_LNvJL}n! z0*mKeZkrubN05t8kctX9q_p?PzTVg{PM|x(&cZSYZ+Sk9*4VwE)b4_R!Rw}1ziw)8 zydGb)B)S~Kw&D{4S-cnc3U1YV`>+RLGb|R7&V@s1w!fE&+!7n>w$QaF=@yqpbGg$# z6K&sG7EzjQx^I8x$gc6H>n)W)iV_nw^Gr2*D@DS5ka&c3Nf#*KBMNJZ!GxMc!M$; zL-vb;piPpN1Agz%DeGntED3M++SZxucj;`&yBCOBS!T|! z^Nx+_^It(4>Nfg;-!*8?@CBd8BA8n-;&*8N%ENzG*pd~;xu&d(8Oic?&9+MJT?&Bg zT`4R92*7_lDeBger*k@e;l0{OJk4?b62rl|{Q3Alj;irhvmK=Ld*!}*iZ;EY8lc89@`W|9#3-O{0Ox%uBq zm*sGK+R<4~-_bX5J%YI?hqVyn(5RM)=y5ApPKq92W1d)bPEaU}(w1E2T^`xAmp5N1 zlE}8*wDdMvpftu%Q;;lng4Dln^drwkekUR@i5dBDm?|{`NNlP7;BEiQF39IjBtKc~ z8_L3d$+6+&UIr%Vn||SM*Nqo1XL0Kdeu3R_P9{B$9=O^jn#x=@Fmyo3VMh8Mh_SYx zLJ_WyPiWpd8&T_Mt{|I^KVe=~aYLIDR?0dPVJ>fsjuh9Bqb^LJ7Hk%NK)gtY0_Si1ZoXDsq zpLgBoz9193juZI=Xc1KFf^z>Z0nHzB>Yv7Q3LGQ($eRpLIIe7fFh66zTsnrF{?KtX z0*!0Nc|c7%tlp)w3#p|c}{YBH`4P^KDQZ$qk6}9bIj7_ zd+tzw7=w`|7^j@LgD(9pvGz|We#~QzAg4X*SDWPzz5(zE8jrs06;gxfBe?Knh*9;K z1cqhhu}RALIxf#TyFXE)STeK@NjZj}lCO|7ejS&WbK`*a(@i)96SK4XwY=xxActBV z1_VSL&Y_IhWBTA|-&Kg7yc=_E#mazR7w!f`@J<&&9CjcQ3Cf&=Rj3|J+!hS{z&L|% zR1XIAvi>FDcZ!pB57sk;mD#LP-e$ftw+4@S010#uluH2lNww0^qJ7c{=TTgkNeeM@ z2wgblkl=Nb%6)#>0jO(p>HSwa_}{4T3qOvy;lD%me~SME6EWi;ha(obColoPCco&* zM1lO|Y{Or6FyVy(uo+#C=AOhgDu@x$vRg%V;516mppQN}CZ9C23w literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..35285a0a0e3bc44f069824a7e2528dcb64e78fa9 GIT binary patch literal 3181 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 N_xFdr8a>0a{{c>Pu+#tm literal 0 HcmV?d00001 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/overte/workarounds/README.md b/interface/resources/qml/overte/workarounds/README.md new file mode 100644 index 00000000000..b41235d5eb0 --- /dev/null +++ b/interface/resources/qml/overte/workarounds/README.md @@ -0,0 +1,4 @@ +These are to work around the current desktop system, which requires +components to be wrapped in a Window to have a draggable border and titlebar. + +This isn't required on the tablet because it doesn't support multiple windows at once. diff --git a/interface/resources/qml/overte/workarounds/RunningScripts_Window.qml b/interface/resources/qml/overte/workarounds/RunningScripts_Window.qml new file mode 100644 index 00000000000..b5a45d86ccc --- /dev/null +++ b/interface/resources/qml/overte/workarounds/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 {} +} From ddf7ce4f4a20e026260bfa696697a2a2b3ed391d Mon Sep 17 00:00:00 2001 From: Ada Date: Sun, 26 Oct 2025 17:54:25 +1000 Subject: [PATCH 059/111] Change window theme, new chat app, new settings app --- interface/CMakeLists.txt | 2 + .../resources/icons/tablet-icons/chat-a.svg | 2 + .../resources/icons/tablet-icons/chat-i.svg | 2 + .../resources/qml/hifi/tablet/TabletRoot.qml | 2 +- .../resources/qml/hifi/tablet/WindowRoot.qml | 7 +- .../qml/hifi/toolbars/ToolbarButton.qml | 2 + .../resources/qml/windows/Decoration.qml | 27 ++- .../qml/windows/DefaultFrameDecoration.qml | 119 +++++----- interface/resources/qml/windows/Frame.qml | 77 ++++--- .../resources/qml/windows/ScrollingWindow.qml | 18 +- interface/resources/qml/windows/Window.qml | 6 +- interface/src/Menu.cpp | 38 +--- .../chatBubbles/chatBubbles.js | 31 +-- scripts/defaultScripts.js | 9 +- scripts/system/bubble.js | 2 +- scripts/system/chat.js | 203 ++++++++++++++++++ scripts/system/systemApps.js | 96 +++++++++ 17 files changed, 445 insertions(+), 198 deletions(-) create mode 100644 interface/resources/icons/tablet-icons/chat-a.svg create mode 100644 interface/resources/icons/tablet-icons/chat-i.svg create mode 100644 scripts/system/chat.js create mode 100644 scripts/system/systemApps.js diff --git a/interface/CMakeLists.txt b/interface/CMakeLists.txt index bbbde87f8fa..62422d20201 100644 --- a/interface/CMakeLists.txt +++ b/interface/CMakeLists.txt @@ -295,6 +295,8 @@ if (ANDROID) target_link_libraries(${TARGET_NAME} ${ANDROID_LOG_LIB}) endif () +find_package(Qt6GuiPrivate) + target_link_libraries( ${TARGET_NAME} Qt6::Gui Qt6::GuiPrivate Qt6::Network Qt6::Multimedia Qt6::Widgets 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/hifi/tablet/TabletRoot.qml b/interface/resources/qml/hifi/tablet/TabletRoot.qml index 201c4e6a523..cc11fb7c617 100644 --- a/interface/resources/qml/hifi/tablet/TabletRoot.qml +++ b/interface/resources/qml/hifi/tablet/TabletRoot.qml @@ -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 eed07e5e3d3..4f3d10aee92 100644 --- a/interface/resources/qml/hifi/tablet/WindowRoot.qml +++ b/interface/resources/qml/hifi/tablet/WindowRoot.qml @@ -31,7 +31,7 @@ Windows.ScrollingWindow { id: settings category: "WindowRoot.Windows" property real width: 480 - property real height: 706 + property real height: 720 } onResizableChanged: { @@ -40,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 @@ -204,7 +204,6 @@ Windows.ScrollingWindow { } } - implicitWidth: 480 - implicitHeight: 706 + implicitHeight: 720 } 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/windows/Decoration.qml b/interface/resources/qml/windows/Decoration.qml index eca67925019..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 Qt5Compat.GraphicalEffects 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 3d4a01142e9..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 Qt5Compat.GraphicalEffects +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 8460deddf03..58c31540166 100644 --- a/interface/resources/qml/windows/Frame.qml +++ b/interface/resources/qml/windows/Frame.qml @@ -14,6 +14,9 @@ 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 3bc98336b7f..1ce15ec0cff 100644 --- a/interface/resources/qml/windows/ScrollingWindow.qml +++ b/interface/resources/qml/windows/ScrollingWindow.qml @@ -55,25 +55,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 +161,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/Window.qml b/interface/resources/qml/windows/Window.qml index 4ce91945716..64b26f28ab7 100644 --- a/interface/resources/qml/windows/Window.qml +++ b/interface/resources/qml/windows/Window.qml @@ -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 @@ -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/src/Menu.cpp b/interface/src/Menu.cpp index e30c2d68950..16709d1b1c1 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -153,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/workarounds/RunningScripts_Window.qml"); + static const QUrl tabletUrl("overte/dialogs/RunningScriptsDialog.qml"); static const QString name("RunningScripts"); qApp->showDialog(widgetUrl, tabletUrl, name); } @@ -259,40 +259,6 @@ Menu::Menu() { // Settings menu ---------------------------------- MenuWrapper* settingsMenu = addMenu("Settings"); - // Settings > General... - action = addActionToQMenuAndActionHash(settingsMenu, MenuOption::Preferences, static_cast(Qt::CTRL) | static_cast(Qt::Key_G), nullptr, nullptr); - connect(action, &QAction::triggered, [] { - if (!qApp->getLoginDialogPoppedUp()) { - qApp->showDialog(QString("hifi/dialogs/GeneralPreferencesDialog.qml"), - QString("hifi/tablet/TabletGeneralPreferences.qml"), "GeneralPreferencesDialog"); - } - }); - - // Settings > Controls... - action = addActionToQMenuAndActionHash(settingsMenu, "Controls..."); - connect(action, &QAction::triggered, [] { - auto tablet = DependencyManager::get()->getTablet("com.highfidelity.interface.tablet.system"); - auto hmd = DependencyManager::get(); - tablet->pushOntoStack("hifi/tablet/ControllerSettings.qml"); - - if (!hmd->getShouldShowTablet()) { - hmd->toggleShouldShowTablet(); - } - }); - - // Settings > Audio... - action = addActionToQMenuAndActionHash(settingsMenu, "Audio..."); - connect(action, &QAction::triggered, [] { - static const QUrl tabletUrl("hifi/audio/Audio.qml"); - auto tablet = DependencyManager::get()->getTablet("com.highfidelity.interface.tablet.system"); - auto hmd = DependencyManager::get(); - tablet->pushOntoStack(tabletUrl); - - if (!hmd->getShouldShowTablet()) { - hmd->toggleShouldShowTablet(); - } - }); - // Settings > Security... action = addActionToQMenuAndActionHash(settingsMenu, "Security..."); connect(action, &QAction::triggered, [] { diff --git a/scripts/communityScripts/chatBubbles/chatBubbles.js b/scripts/communityScripts/chatBubbles/chatBubbles.js index a9a5fb30640..e13d8215a0a 100644 --- a/scripts/communityScripts/chatBubbles/chatBubbles.js +++ b/scripts/communityScripts/chatBubbles/chatBubbles.js @@ -14,7 +14,7 @@ const CHAT_CHANNEL = "chat"; const TYPING_NOTIFICATION_CHANNEL = "Chat-Typing"; -const CONFIG_UPDATE_CHANNEL = "ChatBubbles-Config"; +const CONFIG_UPDATE_CHANNEL = "ChatBubbles-Enabled"; const BUBBLE_MIN_LIFETIME_SECS = 10; const BUBBLE_MAX_LIFETIME_SECS = 60; // failsafe to prevent really long perma-messages @@ -29,13 +29,12 @@ const BUBBLE_BG_ALPHA = 0.6; const MAX_DISTANCE = 20; const TYPING_NOTIF_HEAD_OFFSET_HEIGHT = 0.48; const MSG_HEAD_OFFSET_HEIGHT = 0.6; -const SELF_BUBBLES = false; +const SELF_BUBBLES = true; const NOTIFY_SOUND = SoundCache.getSound(Script.resolvePath("./assets/notify.wav")); -let settings = { - enabled: true, -}; +// NOTE: we don't call Settings.setValue here because chat.js does that for us +let settings = Settings.getValue("Chat", { chatBubbles: true }); let currentBubbles = {}; let typingIndicators = {}; @@ -312,21 +311,9 @@ function ChatBubbles_HideTypingIndicator(senderID) { } function ChatBubbles_RecvMsg(channel, msg, senderID, localOnly) { - // IPC between ArmoredChat's config window and this script + // IPC between the chat config and this script if (channel === CONFIG_UPDATE_CHANNEL && localOnly) { - let data; - try { - data = JSON.parse(msg); - } catch (e) { - console.error(e); - return; - } - - for (const [key, value] of Object.entries(data)) { - settings[key] = value; - } - - Settings.setValue("ChatBubbles-Config", settings); + settings.chatBubbles = msg === "true"; return; } @@ -345,14 +332,14 @@ function ChatBubbles_RecvMsg(channel, msg, senderID, localOnly) { } if (channel === TYPING_NOTIFICATION_CHANNEL) { - if (data.action === "typing_start") { + if (data.action === "typing_start" && settings.chatBubbles) { // don't spawn a bubble if they're too far away if (Vec3.distance(MyAvatar.position, data.position) > MAX_DISTANCE) { return; } ChatBubbles_ShowTypingIndicator(senderID); } else if (data.action === "typing_stop") { ChatBubbles_HideTypingIndicator(senderID); } - } else if (data.action === "send_chat_message" && settings.enabled) { + } else if (data.action === "send_chat_message" && settings.chatBubbles) { // don't spawn a bubble if they're too far away if (data.channel !== "local") { return; } if (Vec3.distance(MyAvatar.position, data.position) > MAX_DISTANCE) { return; } @@ -405,12 +392,10 @@ Window.domainConnectionRefused.connect((_msg, _code, _info) => ChatBubbles_Delet AvatarList.avatarRemovedEvent.connect(sessionID => ChatBubbles_Delete(sessionID)); AvatarList.avatarSessionChangedEvent.connect((_, oldSessionID) => ChatBubbles_Delete(oldSessionID)); -settings = Settings.getValue("ChatBubbles-Config", settings); Messages.messageReceived.connect(ChatBubbles_RecvMsg); Messages.subscribe(TYPING_NOTIFICATION_CHANNEL); Script.scriptEnding.connect(() => { - Settings.setValue("ChatBubbles-Config", settings); Messages.messageReceived.disconnect(ChatBubbles_RecvMsg); Messages.unsubscribe(TYPING_NOTIFICATION_CHANNEL); ChatBubbles_DeleteAll(); diff --git a/scripts/defaultScripts.js b/scripts/defaultScripts.js index 07e047760cd..55eaff1c9c0 100644 --- a/scripts/defaultScripts.js +++ b/scripts/defaultScripts.js @@ -22,17 +22,12 @@ var DEFAULT_SCRIPTS_COMBINED = [ "system/menu.js", "system/bubble.js", "system/snapshot.js", - "system/pal.js", // "system/mod.js", // older UX, if you prefer - "system/avatarapp.js", - "system/settings/settings.js", "system/makeUserConnection.js", "system/notifications.js", "system/create/edit.js", "system/dialTone.js", "system/firstPersonHMD.js", "system/tablet-ui/tabletUI.js", - "system/emote.js", - "system/miniTablet.js", "system/audioMuteOverlay.js", "system/inspect.js", "system/keyboardShortcuts/keyboardShortcuts.js", @@ -41,15 +36,15 @@ var DEFAULT_SCRIPTS_COMBINED = [ //"developer/debugging/scriptMemoryReport.js" ]; var DEFAULT_SCRIPTS_SEPARATE = [ + "system/systemApps.js", + "system/chat.js", "system/controllers/controllerScripts.js", "system/controllers/squeezeHands.js", "communityScripts/notificationCore/notificationCore.js", "simplifiedUI/ui/simplifiedNametag/simplifiedNametag.js", {"stable": "system/more/app-more.js", "beta": "https://more.overte.org/more/app-more.js"}, - //"communityScripts/armored-chat/armored_chat.js", // QT6TODO: causes a crash for now "communityScripts/chatBubbles/chatBubbles.js", "communityScripts/contextMenu.js", - //"system/chat.js" ]; if (Window.interstitialModeEnabled) { diff --git a/scripts/system/bubble.js b/scripts/system/bubble.js index 2dee944a855..6730c1425fc 100644 --- a/scripts/system/bubble.js +++ b/scripts/system/bubble.js @@ -190,7 +190,7 @@ icon: "icons/tablet-icons/bubble-i.svg", activeIcon: "icons/tablet-icons/bubble-a.svg", text: buttonName, - sortOrder: 4 + sortOrder: 2 }); onBubbleToggled(Users.getIgnoreRadiusEnabled(), true); // pass in true so we don't log this initial one in the UserActivity table diff --git a/scripts/system/chat.js b/scripts/system/chat.js new file mode 100644 index 00000000000..270b08334d2 --- /dev/null +++ b/scripts/system/chat.js @@ -0,0 +1,203 @@ +// +// chat.js +// +// Created by Ada on 2025-10-26 +// Copyright 2025 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 +"use strict"; + +const SystemTablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); + +let settings = Settings.getValue("Chat", { + joinNotifications: true, + broadcastEnabled: false, + chatBubbles: true, + desktopWindow: true, +}); + +function updateSetting(name, value) { + switch (name) { + case "join_notify": + settings.joinNotifications = value; + break; + + case "broadcast": + settings.broadcastEnabled = value; + break; + + case "chat_bubbles": + settings.chatBubbles = value; + Messages.sendLocalMessage("ChatBubbles-Enabled", JSON.stringify(value)); + break; + + case "desktop_window": + settings.desktopWindow = value; + // FIXME: this sometimes leaves an empty ghost window on the overlay? + appWindow.presentationMode = value ? + Desktop.PresentationMode.NATIVE : + Desktop.PresentationMode.VIRTUAL; + break; + } + + Settings.setValue("Chat", settings); +} + +function sendInitialSettings() { + const send = (name, value) => appWindow.sendToQml(JSON.stringify({ + event: "change_setting", + name: name, + value: value, + })); + + send("join_notify", settings.joinNotifications); + send("broadcast", settings.broadcastEnabled); + send("chat_bubbles", settings.chatBubbles); + send("desktop_window", settings.desktopWindow); +} + +function appWindowFromQml(rawMsg) { + const msg = JSON.parse(rawMsg); + + switch (msg.event) { + case "change_setting": + updateSetting(msg.setting, msg.value); + break; + + case "send_message": + Messages.sendMessage("chat", JSON.stringify({ + action: "send_chat_message", + position: MyAvatar.position, + displayName: MyAvatar.sessionDisplayName ? MyAvatar.sessionDisplayName : MyAvatar.displayName, + message: msg.body, + channel: settings.broadcastEnabled ? "domain" : "local", + timestamp: Date.now(), + })); + break; + + case "start_typing": + Messages.sendMessage("Chat-Typing", JSON.stringify({ + action: "typing_start", + position: MyAvatar.position, + })); + break; + + case "end_typing": + Messages.sendMessage("Chat-Typing", JSON.stringify({ + action: "typing_stop" + })); + break; + } +} + +function recreateAppWindow() { + appWindow = Desktop.createWindow( + `${Script.resourcesPath()}qml/overte/chat/Chat.qml`, + { + // TODO: translation support in JS + title: "Chat", + size: { x: 550, y: 400 }, + visible: appButton.buttonData.isActive, + presentationMode: settings.desktopWindow ? + Desktop.PresentationMode.NATIVE : + Desktop.PresentationMode.VIRTUAL, + additionalFlags: Desktop.ALWAYS_ON_TOP | Desktop.CLOSE_BUTTON_HIDES, + } + ); + + // https://github.com/overte-org/overte/issues/824 + appWindow.visible = appButton.buttonData.isActive; + + // FIXME: CLOSE_BUTTON_HIDES doesn't work with desktop windows + appWindow.closed.connect(() => { + appButton.buttonData.isActive = false; + appButton.button.editProperties({ isActive: false }); + appWindow = undefined; + }); + + appWindow.fromQml.connect(appWindowFromQml); + + sendInitialSettings(); +} + +const appButton = { + buttonData: { + isActive: false, + sortOrder: 6, + icon: "icons/tablet-icons/chat-i.svg", + activeIcon: "icons/tablet-icons/chat-a.svg", + + // TODO: translation support in JS + text: "CHAT", + }, + + qmlSource: "overte/settings/Settings.qml", + button: null, + + onClicked() { + this.buttonData.isActive = !this.buttonData.isActive; + this.button.editProperties({ isActive: this.buttonData.isActive }); + + if (!appWindow) { recreateAppWindow(); } + + appWindow.visible = this.buttonData.isActive; + }, +}; + +let appWindow; +recreateAppWindow(); + +appButton.button = SystemTablet.addButton(appButton.buttonData); +appButton.button.clicked.connect(() => appButton.onClicked()); + +Messages.subscribe("chat"); +Messages.subscribe("Chat-Typing"); + +Messages.messageReceived.connect((channel, rawMsg, senderID, _localOnly) => { + if (channel === "chat") { + if (!appWindow) { recreateAppWindow(); } + + const msg = JSON.parse(rawMsg); + appWindow.sendToQml(JSON.stringify({ + event: "recv_message", + name: msg.displayName, + body: msg.message, + timestamp: msg.timestamp, + })); + } else if (channel === "Chat-Typing") { + if (!appWindow) { recreateAppWindow(); } + + const avatar = AvatarManager.getAvatar(senderID); + const msg = JSON.parse(rawMsg); + appWindow.sendToQml(JSON.stringify({ + event: msg.action === "typing_start" ? "start_typing" : "end_typing", + name: avatar.sessionDisplayName ? avatar.sessionDisplayName : avatar.displayName, + uuid: senderID, + })); + } +}); + +AvatarManager.avatarAddedEvent.connect(uuid => { + if (!appWindow) { recreateAppWindow(); } + + appWindow.sendToQml(JSON.stringify({ + event: "user_joined", + name: AvatarManager.getAvatar(uuid).sessionDisplayName, + })); +}); + +AvatarManager.avatarRemovedEvent.connect(uuid => { + if (!appWindow) { recreateAppWindow(); } + + appWindow.sendToQml(JSON.stringify({ + event: "user_left", + name: AvatarManager.getAvatar(uuid).sessionDisplayName, + })); +}); + +Script.scriptEnding.connect(() => { + SystemTablet.removeButton(appButton.button); +}); diff --git a/scripts/system/systemApps.js b/scripts/system/systemApps.js new file mode 100644 index 00000000000..59351d22cd2 --- /dev/null +++ b/scripts/system/systemApps.js @@ -0,0 +1,96 @@ +// +// systemApps.js +// +// Created by Ada on 2025-10-26 +// Copyright 2025 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 +"use strict"; + +const SystemTablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); + +function defaultOnClicked() { + this.appButtonData.isActive = !this.appButtonData.isActive; + this.appButton.editProperties({ isActive: this.appButtonData.isActive }); + + if (this.appButtonData.isActive) { + SystemTablet.loadQMLSource(this.qmlSource, false); + } else { + SystemTablet.gotoHomeScreen(); + } +} + +function defaultOnScreenChanged(type, url) { + if (type != "QML" || url != this.qmlSource) { + this.appButtonData.isActive = false; + this.appButton.editProperties({ isActive: this.appButtonData.isActive }); + } +} + +const SYSTEM_APPS = { + settings: { + appButtonData: { + sortOrder: 3, + isActive: false, + icon: Script.resolvePath("./settings/img/icon_white.png"), + activeIcon: Script.resolvePath("./settings/img/icon_black.png"), + + // TODO: translation support in JS + text: "SETTINGS", + }, + + qmlSource: "overte/settings/Settings.qml", + appButton: null, + }, + + avatar: { + appButtonData: { + sortOrder: 4, + isActive: false, + icon: "icons/tablet-icons/avatar-i.svg", + activeIcon: "icons/tablet-icons/avatar-a.svg", + + // TODO: translation support in JS + text: "AVATAR", + }, + + qmlSource: "overte/avatar_picker/AvatarPicker.qml", + appButton: null, + }, + + contacts: { + appButtonData: { + sortOrder: 5, + isActive: false, + icon: "icons/tablet-icons/people-i.svg", + activeIcon: "icons/tablet-icons/people-a.svg", + + // TODO: translation support in JS + text: "CONTACTS", + }, + + qmlSource: "overte/contacts/ContactsList.qml", + appButton: null, + }, +}; + +for (let app of Object.values(SYSTEM_APPS)) { + if (!app.onClicked) { app.onClicked = defaultOnClicked; } + if (!app.onScreenChanged) { app.onScreenChanged = defaultOnScreenChanged; } + + let button = SystemTablet.addButton(app.appButtonData); + button.clicked.connect(() => app.onClicked()); + SystemTablet.screenChanged.connect((type, url) => app.onScreenChanged(type, url)); + app.appButton = button; +} + +Script.scriptEnding.connect(() => { + for (const app of Object.values(SYSTEM_APPS)) { + if (app.appButton) { + SystemTablet.removeButton(app.appButton); + } + } +}); From f5808a3e6f8dfb716fa2ebf6b55b5be67260da0e Mon Sep 17 00:00:00 2001 From: Ada Date: Sun, 26 Oct 2025 21:14:53 +1000 Subject: [PATCH 060/111] Move keyboard and login screen into staging dir --- .../qml/overte/{ => staging}/keyboard/Keyboard.qml | 0 .../overte/{ => staging}/keyboard/KeyboardKey.qml | 0 .../{ => staging}/keyboard/keyboard_ansi.json | 0 .../qml/overte/{ => staging}/keyboard/qmldir | 0 .../qml/overte/{ => staging}/login/LoginScreen.qml | 0 .../{ => staging}/login/assets/background.png | Bin .../overte/{ => staging}/login/assets/logo_dark.png | Bin .../{ => staging}/login/assets/logo_light.png | Bin .../overte/{ => staging}/login/pages/LoginPage.qml | 0 .../{ => staging}/login/pages/ProgressPage.qml | 0 .../{ => staging}/login/pages/RegisterPage.qml | 0 .../overte/{ => staging}/login/pages/StartPage.qml | 0 .../qml/overte/{ => staging}/login/pages/qmldir | 0 .../resources/qml/overte/{ => staging}/login/qmldir | 0 14 files changed, 0 insertions(+), 0 deletions(-) rename interface/resources/qml/overte/{ => staging}/keyboard/Keyboard.qml (100%) rename interface/resources/qml/overte/{ => staging}/keyboard/KeyboardKey.qml (100%) rename interface/resources/qml/overte/{ => staging}/keyboard/keyboard_ansi.json (100%) rename interface/resources/qml/overte/{ => staging}/keyboard/qmldir (100%) rename interface/resources/qml/overte/{ => staging}/login/LoginScreen.qml (100%) rename interface/resources/qml/overte/{ => staging}/login/assets/background.png (100%) rename interface/resources/qml/overte/{ => staging}/login/assets/logo_dark.png (100%) rename interface/resources/qml/overte/{ => staging}/login/assets/logo_light.png (100%) rename interface/resources/qml/overte/{ => staging}/login/pages/LoginPage.qml (100%) rename interface/resources/qml/overte/{ => staging}/login/pages/ProgressPage.qml (100%) rename interface/resources/qml/overte/{ => staging}/login/pages/RegisterPage.qml (100%) rename interface/resources/qml/overte/{ => staging}/login/pages/StartPage.qml (100%) rename interface/resources/qml/overte/{ => staging}/login/pages/qmldir (100%) rename interface/resources/qml/overte/{ => staging}/login/qmldir (100%) diff --git a/interface/resources/qml/overte/keyboard/Keyboard.qml b/interface/resources/qml/overte/staging/keyboard/Keyboard.qml similarity index 100% rename from interface/resources/qml/overte/keyboard/Keyboard.qml rename to interface/resources/qml/overte/staging/keyboard/Keyboard.qml diff --git a/interface/resources/qml/overte/keyboard/KeyboardKey.qml b/interface/resources/qml/overte/staging/keyboard/KeyboardKey.qml similarity index 100% rename from interface/resources/qml/overte/keyboard/KeyboardKey.qml rename to interface/resources/qml/overte/staging/keyboard/KeyboardKey.qml diff --git a/interface/resources/qml/overte/keyboard/keyboard_ansi.json b/interface/resources/qml/overte/staging/keyboard/keyboard_ansi.json similarity index 100% rename from interface/resources/qml/overte/keyboard/keyboard_ansi.json rename to interface/resources/qml/overte/staging/keyboard/keyboard_ansi.json diff --git a/interface/resources/qml/overte/keyboard/qmldir b/interface/resources/qml/overte/staging/keyboard/qmldir similarity index 100% rename from interface/resources/qml/overte/keyboard/qmldir rename to interface/resources/qml/overte/staging/keyboard/qmldir diff --git a/interface/resources/qml/overte/login/LoginScreen.qml b/interface/resources/qml/overte/staging/login/LoginScreen.qml similarity index 100% rename from interface/resources/qml/overte/login/LoginScreen.qml rename to interface/resources/qml/overte/staging/login/LoginScreen.qml diff --git a/interface/resources/qml/overte/login/assets/background.png b/interface/resources/qml/overte/staging/login/assets/background.png similarity index 100% rename from interface/resources/qml/overte/login/assets/background.png rename to interface/resources/qml/overte/staging/login/assets/background.png diff --git a/interface/resources/qml/overte/login/assets/logo_dark.png b/interface/resources/qml/overte/staging/login/assets/logo_dark.png similarity index 100% rename from interface/resources/qml/overte/login/assets/logo_dark.png rename to interface/resources/qml/overte/staging/login/assets/logo_dark.png diff --git a/interface/resources/qml/overte/login/assets/logo_light.png b/interface/resources/qml/overte/staging/login/assets/logo_light.png similarity index 100% rename from interface/resources/qml/overte/login/assets/logo_light.png rename to interface/resources/qml/overte/staging/login/assets/logo_light.png diff --git a/interface/resources/qml/overte/login/pages/LoginPage.qml b/interface/resources/qml/overte/staging/login/pages/LoginPage.qml similarity index 100% rename from interface/resources/qml/overte/login/pages/LoginPage.qml rename to interface/resources/qml/overte/staging/login/pages/LoginPage.qml diff --git a/interface/resources/qml/overte/login/pages/ProgressPage.qml b/interface/resources/qml/overte/staging/login/pages/ProgressPage.qml similarity index 100% rename from interface/resources/qml/overte/login/pages/ProgressPage.qml rename to interface/resources/qml/overte/staging/login/pages/ProgressPage.qml diff --git a/interface/resources/qml/overte/login/pages/RegisterPage.qml b/interface/resources/qml/overte/staging/login/pages/RegisterPage.qml similarity index 100% rename from interface/resources/qml/overte/login/pages/RegisterPage.qml rename to interface/resources/qml/overte/staging/login/pages/RegisterPage.qml diff --git a/interface/resources/qml/overte/login/pages/StartPage.qml b/interface/resources/qml/overte/staging/login/pages/StartPage.qml similarity index 100% rename from interface/resources/qml/overte/login/pages/StartPage.qml rename to interface/resources/qml/overte/staging/login/pages/StartPage.qml diff --git a/interface/resources/qml/overte/login/pages/qmldir b/interface/resources/qml/overte/staging/login/pages/qmldir similarity index 100% rename from interface/resources/qml/overte/login/pages/qmldir rename to interface/resources/qml/overte/staging/login/pages/qmldir diff --git a/interface/resources/qml/overte/login/qmldir b/interface/resources/qml/overte/staging/login/qmldir similarity index 100% rename from interface/resources/qml/overte/login/qmldir rename to interface/resources/qml/overte/staging/login/qmldir From ed9cdfd1344ad24294235db1dc14ad4696b9bc52 Mon Sep 17 00:00:00 2001 From: Ada Date: Mon, 27 Oct 2025 13:43:11 +1000 Subject: [PATCH 061/111] Fully functional running scripts dialog --- interface/resources/qml/overte/Button.qml | 2 + .../overte/dialogs/BuiltinScriptsDialog.qml | 195 ++++++++++++++++++ .../qml/overte/dialogs/FileDialog.qml | 16 +- .../overte/dialogs/RunningScriptsDialog.qml | 47 ++++- interface/resources/qml/overte/dialogs/qmldir | 3 +- interface/src/Application_UI.cpp | 14 ++ 6 files changed, 259 insertions(+), 18 deletions(-) create mode 100644 interface/resources/qml/overte/dialogs/BuiltinScriptsDialog.qml diff --git a/interface/resources/qml/overte/Button.qml b/interface/resources/qml/overte/Button.qml index 971a728c6d7..3f2e5255b96 100644 --- a/interface/resources/qml/overte/Button.qml +++ b/interface/resources/qml/overte/Button.qml @@ -17,6 +17,8 @@ Button { 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 diff --git a/interface/resources/qml/overte/dialogs/BuiltinScriptsDialog.qml b/interface/resources/qml/overte/dialogs/BuiltinScriptsDialog.qml new file mode 100644 index 00000000000..ef610ddc2ed --- /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) { 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 index 53275aa27d9..0543ff2ea60 100644 --- a/interface/resources/qml/overte/dialogs/FileDialog.qml +++ b/interface/resources/qml/overte/dialogs/FileDialog.qml @@ -19,13 +19,14 @@ Rectangle { 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.SaveFile + property int fileMode: FileDialog.OpenFile property url selectedFile: "" property list selectedFiles: [] @@ -36,15 +37,16 @@ Rectangle { property string searchExpression: ".*" - // DEBUG onAccepted: { - console.info("ACCEPTED"); - console.info(`selectedFile: ${selectedFile.toString()}`); - console.info(`selectedFiles: ${JSON.stringify(selectedFiles)}`); + visible = false; } onRejected: { - console.info("REJECTED"); + visible = false; + } + + function open() { + visible = true; } function urlToPathString(urlRaw) { @@ -537,7 +539,7 @@ Rectangle { Layout.rightMargin: 8 Layout.preferredHeight: parent.height - text: nameFilters.concat() + text: nameFilters.join(", ") verticalAlignment: Text.AlignVCenter elide: Text.ElideRight } diff --git a/interface/resources/qml/overte/dialogs/RunningScriptsDialog.qml b/interface/resources/qml/overte/dialogs/RunningScriptsDialog.qml index 69318da219c..9162203d6aa 100644 --- a/interface/resources/qml/overte/dialogs/RunningScriptsDialog.qml +++ b/interface/resources/qml/overte/dialogs/RunningScriptsDialog.qml @@ -3,6 +3,7 @@ import QtQuick.Controls import QtQuick.Layouts import ".." as Overte +import "." as OverteDialogs Rectangle { anchors.fill: parent @@ -73,7 +74,16 @@ Rectangle { font.pixelSize: Overte.Theme.fontPixelSizeSmall opacity: Overte.Theme.highContrast ? 1.0 : 0.6 elide: Text.ElideRight - text: url.startsWith("qrc:") ? `${qsTr("Built-in:")} ${url}` : url + text: { + if ( + url.startsWith("qrc:") || + url.startsWith("file://~/") + ) { + return `${qsTr("Built-in:")} ${url}`; + } else { + return url; + } + } } } @@ -82,8 +92,7 @@ Rectangle { Layout.margins: 8 spacing: 2 - // TODO: url copy button - /*Overte.RoundButton { + Overte.RoundButton { backgroundColor: Overte.Theme.paletteActive.buttonInfo icon.source: "../icons/copy.svg" @@ -97,7 +106,9 @@ Rectangle { verticalPadding: 0 onClicked: WindowScriptingInterface.copyToClipboard(url) - }*/ + + Overte.ToolTip { text: qsTr("Copy URL to clipboard") } + } Overte.RoundButton { icon.source: "../icons/reload.svg" @@ -112,6 +123,8 @@ Rectangle { // restart the script onClicked: ScriptDiscoveryService.stopScript(url, true) + + Overte.ToolTip { text: qsTr("Reload") } } Overte.RoundButton { @@ -128,6 +141,8 @@ Rectangle { verticalPadding: 0 onClicked: ScriptDiscoveryService.stopScript(url) + + Overte.ToolTip { text: qsTr("Stop and remove") } } } } @@ -174,9 +189,7 @@ Rectangle { text: qsTr("File") - onClicked: { - console.warn("TODO"); - } + onClicked: fileDialog.open() } Overte.Button { @@ -196,9 +209,7 @@ Rectangle { text: qsTr("Built-in") - onClicked: { - console.warn("TODO"); - } + onClicked: builtinDialog.open() } } } @@ -283,4 +294,20 @@ Rectangle { } } } + + 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 index 4a1c3c63a0b..86450bd753a 100644 --- a/interface/resources/qml/overte/dialogs/qmldir +++ b/interface/resources/qml/overte/dialogs/qmldir @@ -1,4 +1,5 @@ module Dialogs FileDialog 1.0 FileDialog.qml -RunningScriptsDialog 1.0 RunningScriptsDialog.qml AssetDialog 1.0 AssetDialog.qml +RunningScriptsDialog 1.0 RunningScriptsDialog.qml +BuiltinScriptsDialog 1.0 BuiltinScriptsDialog.qml diff --git a/interface/src/Application_UI.cpp b/interface/src/Application_UI.cpp index 66c8120303e..6f4a2e23d0a 100644 --- a/interface/src/Application_UI.cpp +++ b/interface/src/Application_UI.cpp @@ -258,7 +258,14 @@ 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("About", AboutUtil::getInstance()); surfaceContext->setContextProperty("HiFiAbout", AboutUtil::getInstance()); // Deprecated. @@ -912,7 +919,14 @@ 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()); surfaceContext->setContextProperty("Settings", new QMLSettingsScriptingInterface(surfaceContext)); From 8553e618569fbacb5c9e0c8c4648629633e6311d Mon Sep 17 00:00:00 2001 From: Ada Date: Mon, 27 Oct 2025 13:47:20 +1000 Subject: [PATCH 062/111] sed all of the tabs into 4-spaces (oops) --- interface/resources/qml/overte/BodyText.qml | 24 +- interface/resources/qml/overte/Button.qml | 90 +- interface/resources/qml/overte/ComboBox.qml | 266 ++-- interface/resources/qml/overte/Dialog.qml | 122 +- interface/resources/qml/overte/Label.qml | 6 +- .../resources/qml/overte/MessageDialog.qml | 212 +-- .../resources/qml/overte/RoundButton.qml | 88 +- interface/resources/qml/overte/Ruler.qml | 40 +- interface/resources/qml/overte/ScrollBar.qml | 288 ++-- interface/resources/qml/overte/Slider.qml | 112 +- interface/resources/qml/overte/SpinBox.qml | 272 ++-- interface/resources/qml/overte/StackView.qml | 120 +- interface/resources/qml/overte/Switch.qml | 128 +- interface/resources/qml/overte/TabBar.qml | 28 +- interface/resources/qml/overte/TabButton.qml | 90 +- interface/resources/qml/overte/TextArea.qml | 50 +- interface/resources/qml/overte/TextField.qml | 52 +- interface/resources/qml/overte/Theme.qml | 374 +++--- interface/resources/qml/overte/ToolTip.qml | 64 +- interface/resources/qml/overte/WidgetZoo.qml | 232 ++-- .../qml/overte/avatar_picker/AvatarItem.qml | 206 +-- .../qml/overte/avatar_picker/AvatarPicker.qml | 594 ++++----- interface/resources/qml/overte/chat/Chat.qml | 124 +- .../resources/qml/overte/chat/ChatPage.qml | 406 +++--- .../qml/overte/chat/MessageBlock.qml | 118 +- .../qml/overte/chat/SettingsPage.qml | 94 +- .../qml/overte/contacts/AccountAvatar.qml | 100 +- .../qml/overte/contacts/AccountContact.qml | 170 +-- .../qml/overte/contacts/ContactsList.qml | 396 +++--- .../qml/overte/contacts/MyAccountInfo.qml | 254 ++-- .../qml/overte/contacts/SessionContact.qml | 134 +- .../qml/overte/dialogs/AssetDialog.qml | 466 +++---- .../qml/overte/dialogs/FileDialog.qml | 1160 ++++++++--------- .../qml/overte/settings/ComboSetting.qml | 46 +- .../qml/overte/settings/FolderSetting.qml | 44 +- .../resources/qml/overte/settings/Header.qml | 34 +- .../qml/overte/settings/SettingNote.qml | 12 +- .../qml/overte/settings/Settings.qml | 70 +- .../qml/overte/settings/SettingsPage.qml | 28 +- .../qml/overte/settings/SliderSetting.qml | 136 +- .../qml/overte/settings/SpinBoxSetting.qml | 40 +- .../qml/overte/settings/SwitchSetting.qml | 34 +- .../qml/overte/settings/WideComboSetting.qml | 36 +- .../qml/overte/settings/pages/Audio.qml | 50 +- .../qml/overte/settings/pages/Controls.qml | 274 ++-- .../qml/overte/settings/pages/General.qml | 236 ++-- .../qml/overte/settings/pages/Graphics.qml | 134 +- .../qml/overte/staging/MediaPlayer.qml | 756 +++++------ .../resources/qml/overte/staging/Node.qml | 346 ++--- .../qml/overte/staging/NodeGraph.qml | 38 +- .../resources/qml/overte/staging/NodePlug.qml | 82 +- .../qml/overte/staging/desktop/AppToolbar.qml | 124 +- .../qml/overte/staging/desktop/Desktop.qml | 104 +- .../qml/overte/staging/keyboard/Keyboard.qml | 368 +++--- .../overte/staging/keyboard/KeyboardKey.qml | 128 +- .../qml/overte/staging/login/LoginScreen.qml | 84 +- .../overte/staging/login/pages/LoginPage.qml | 258 ++-- .../staging/login/pages/ProgressPage.qml | 126 +- .../staging/login/pages/RegisterPage.qml | 258 ++-- .../overte/staging/login/pages/StartPage.qml | 92 +- .../overte/staging/place_picker/PlaceItem.qml | 284 ++-- .../staging/place_picker/PlacePicker.qml | 250 ++-- .../overte/staging/tutorial/AvatarPicker.qml | 4 +- .../overte/staging/tutorial/ControlsGuide.qml | 116 +- .../qml/overte/staging/tutorial/Welcome.qml | 76 +- 65 files changed, 5774 insertions(+), 5774 deletions(-) diff --git a/interface/resources/qml/overte/BodyText.qml b/interface/resources/qml/overte/BodyText.qml index 2c26d00cb77..c90341a5daf 100644 --- a/interface/resources/qml/overte/BodyText.qml +++ b/interface/resources/qml/overte/BodyText.qml @@ -4,20 +4,20 @@ import QtQuick.Controls import "." TextEdit { - selectByMouse: true - readOnly: true + selectByMouse: true + readOnly: true - font.pixelSize: Theme.fontPixelSize - font.family: Theme.bodyFontFamily - color: Theme.paletteActive.text + font.pixelSize: Theme.fontPixelSize + font.family: Theme.bodyFontFamily + color: Theme.paletteActive.text - textFormat: TextEdit.PlainText - wrapMode: TextEdit.Wrap + textFormat: TextEdit.PlainText + wrapMode: TextEdit.Wrap - selectedTextColor: Theme.paletteActive.highlightedText - selectionColor: Theme.paletteActive.highlight + selectedTextColor: Theme.paletteActive.highlightedText + selectionColor: Theme.paletteActive.highlight - // TODO: handle embedded links - // Qt doesn't make it easy to theme rich text - onLinkActivated: link => Qt.openUrlExternally(link) + // TODO: handle embedded links + // Qt doesn't make it easy to theme rich text + onLinkActivated: link => Qt.openUrlExternally(link) } diff --git a/interface/resources/qml/overte/Button.qml b/interface/resources/qml/overte/Button.qml index 3f2e5255b96..bcc255b0fbb 100644 --- a/interface/resources/qml/overte/Button.qml +++ b/interface/resources/qml/overte/Button.qml @@ -3,53 +3,53 @@ import QtQuick.Controls import "." Button { - id: button - property color backgroundColor: Theme.paletteActive.button - property color color: Theme.paletteActive.buttonText + 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 + palette.buttonText: color + font.family: Theme.fontFamily + font.pixelSize: Theme.fontPixelSize + horizontalPadding: 12 + verticalPadding: 8 + hoverEnabled: true - opacity: enabled ? 1.0 : 0.5 + opacity: enabled ? 1.0 : 0.5 - background: Rectangle { - opacity: flat ? 0.0 : 1.0 + 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) - } - } - } + 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 index 6d148145250..016837dedf7 100644 --- a/interface/resources/qml/overte/ComboBox.qml +++ b/interface/resources/qml/overte/ComboBox.qml @@ -3,138 +3,138 @@ 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 - - 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 - } - } - } + 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 + + 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 index 9585a5ec980..6df695b25bb 100644 --- a/interface/resources/qml/overte/Dialog.qml +++ b/interface/resources/qml/overte/Dialog.qml @@ -5,77 +5,77 @@ import QtQuick.Dialogs import "." Rectangle { - id: dialog - visible: false + id: dialog + visible: false - default property alias content: dialogWindow.children + default property alias content: dialogWindow.children - property int maxWidth: 480 - property int maxHeight: -1 + property int maxWidth: 480 + property int maxHeight: -1 - property bool shadedBackground: true + property bool shadedBackground: true - function open() { - visible = true; - opacity = Theme.reducedMotion ? 1 : 0; - } + function open() { + visible = true; + opacity = Theme.reducedMotion ? 1 : 0; + } - function close() { - visible = false; - opacity = 0; - } + function close() { + visible = false; + opacity = 0; + } - color: !shadedBackground ? "transparent" : Theme.paletteActive.dialogShade + 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 - } + 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 - } + // 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 + 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) + 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 - } + 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 - } - } + 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 index 32a5abd5ab8..86fc51694cd 100644 --- a/interface/resources/qml/overte/Label.qml +++ b/interface/resources/qml/overte/Label.qml @@ -3,7 +3,7 @@ import QtQuick.Controls import "." Label { - font.pixelSize: Theme.fontPixelSize - font.family: Theme.fontFamily - color: Theme.paletteActive.text + 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 index 86518c90a71..33cd5dcde0d 100644 --- a/interface/resources/qml/overte/MessageDialog.qml +++ b/interface/resources/qml/overte/MessageDialog.qml @@ -5,110 +5,110 @@ 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); - } - } - } - } + 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/RoundButton.qml b/interface/resources/qml/overte/RoundButton.qml index 74ea21154cf..5deec714cde 100644 --- a/interface/resources/qml/overte/RoundButton.qml +++ b/interface/resources/qml/overte/RoundButton.qml @@ -3,50 +3,50 @@ import QtQuick.Controls import "." Button { - id: button - property color backgroundColor: Theme.paletteActive.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 + 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) - } - } - } + 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 index d0520789dc3..522c6208169 100644 --- a/interface/resources/qml/overte/Ruler.qml +++ b/interface/resources/qml/overte/Ruler.qml @@ -3,27 +3,27 @@ import QtQuick import "." Item { - property color color: ( - Theme.highContrast ? - Theme.paletteActive.text : - Theme.paletteActive.base - ) + property color color: ( + Theme.highContrast ? + Theme.paletteActive.text : + Theme.paletteActive.base + ) - implicitHeight: 4 + 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: 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) - } + 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 index 8dfe272c488..6fa5d7f985c 100644 --- a/interface/resources/qml/overte/ScrollBar.qml +++ b/interface/resources/qml/overte/ScrollBar.qml @@ -3,148 +3,148 @@ 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); - } - } + 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 index 686862780ae..89ce00528e7 100644 --- a/interface/resources/qml/overte/Slider.qml +++ b/interface/resources/qml/overte/Slider.qml @@ -4,67 +4,67 @@ import QtQuick.Controls import "." Slider { - id: control + id: control - background: Rectangle { - y: control.topPadding + control.availableHeight / 2 - height / 2 - implicitWidth: 200 - height: 8 - radius: height / 2 - color: Theme.paletteActive.base + 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) + 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 + 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 - } - } + border.color: Qt.darker(color, Theme.borderDarker) + border.width: Theme.borderWidth + } + } - handle: Rectangle { - implicitWidth: 26 - implicitHeight: 26 - radius: height / 2 + 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 + 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) - } - } - } + 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 index 4cb793bc0ac..edcf5cdc70b 100644 --- a/interface/resources/qml/overte/SpinBox.qml +++ b/interface/resources/qml/overte/SpinBox.qml @@ -4,140 +4,140 @@ 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: Qt.ImhFormattedNumbersOnly - } - - 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: "+" - } - } + 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: Qt.ImhFormattedNumbersOnly + } + + 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 index 2bcd8537d8d..197a5931107 100644 --- a/interface/resources/qml/overte/StackView.qml +++ b/interface/resources/qml/overte/StackView.qml @@ -6,69 +6,69 @@ 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 - } + 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" + 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 - } - } + 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 - } + 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" + 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 - } - } + 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 index d95d9b6736d..5ff772162ed 100644 --- a/interface/resources/qml/overte/Switch.qml +++ b/interface/resources/qml/overte/Switch.qml @@ -3,72 +3,72 @@ import QtQuick.Controls import "." Switch { - id: control - font.pixelSize: Theme.fontPixelSize - font.family: Theme.fontFamily - opacity: control.enabled ? 1.0 : 0.5 + 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 - } + 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 + 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 - } - } + 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 - } + 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 index 242f2eb9e0b..9f7a3c60fec 100644 --- a/interface/resources/qml/overte/TabBar.qml +++ b/interface/resources/qml/overte/TabBar.qml @@ -3,20 +3,20 @@ import QtQuick.Controls import "." TabBar { - id: tabBar - spacing: 2 - clip: true + 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) - } - } + 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 + implicitHeight: Theme.fontPixelSize + 16 } diff --git a/interface/resources/qml/overte/TabButton.qml b/interface/resources/qml/overte/TabButton.qml index f02e3507997..a3a4bb550fc 100644 --- a/interface/resources/qml/overte/TabButton.qml +++ b/interface/resources/qml/overte/TabButton.qml @@ -3,54 +3,54 @@ import QtQuick.Controls import "." TabButton { - id: button - property color backgroundColor: Theme.paletteActive.base + id: button + property color backgroundColor: Theme.paletteActive.base - readonly property int borderWidth: checked ? Theme.borderWidth * 2 : Theme.borderWidth + readonly property int borderWidth: checked ? Theme.borderWidth * 2 : Theme.borderWidth - font.family: Theme.fontFamily - font.pixelSize: Theme.fontPixelSize - horizontalPadding: 12 - verticalPadding: 8 + 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 + 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 - } + 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 - } - } - } + 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 index 261375e3e7d..785b1f3ea99 100644 --- a/interface/resources/qml/overte/TextArea.qml +++ b/interface/resources/qml/overte/TextArea.qml @@ -3,33 +3,33 @@ import QtQuick.Controls import "." TextArea { - id: textArea - selectByMouse: true + id: textArea + selectByMouse: true - property color backgroundColor: Theme.paletteActive.window + 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 + 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 + 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) } - } - } + 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 index 662e7c3391b..0e57c268edc 100644 --- a/interface/resources/qml/overte/TextField.qml +++ b/interface/resources/qml/overte/TextField.qml @@ -3,34 +3,34 @@ import QtQuick.Controls import "." TextField { - id: textField - selectByMouse: true + id: textField + selectByMouse: true - property color backgroundColor: Theme.paletteActive.window + 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 + 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 + 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) } - } - } + 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 index 25e00f02559..9e8cc6e4280 100644 --- a/interface/resources/qml/overte/Theme.qml +++ b/interface/resources/qml/overte/Theme.qml @@ -2,191 +2,191 @@ import QtQuick pragma Singleton QtObject { - property bool useSystemColorScheme: true - property bool useSystemContrastMode: true - - // https://github.com/overte-org/overte/issues/1733 - property bool darkMode: ( - useSystemColorScheme ? - Qt.application.styleHints.colorScheme !== Qt.ColorScheme.Light : - true - ) - property bool highContrast: ( - useSystemContrastMode ? - Qt.application.styleHints.accessibility.contrastPreference === Qt.ContrastPreference.HighContrast : - false - ) - property bool reducedMotion: false - - // font face for UI elements - readonly property string fontFamily: "DejaVu Sans" - - // font face for document text - readonly property string bodyFontFamily: "DejaVu Sans" - - // font face for code editors - readonly property string monoFontFamily: "DejaVu Sans Mono" - - readonly property int fontPixelSize: 18 - readonly property int fontPixelSizeSmall: 14 - 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 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 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 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 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 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 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 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" - } + property bool useSystemColorScheme: true + property bool useSystemContrastMode: true + + // https://github.com/overte-org/overte/issues/1733 + property bool darkMode: ( + useSystemColorScheme ? + Qt.application.styleHints.colorScheme !== Qt.ColorScheme.Light : + true + ) + property bool highContrast: ( + useSystemContrastMode ? + Qt.application.styleHints.accessibility.contrastPreference === Qt.ContrastPreference.HighContrast : + false + ) + property bool reducedMotion: false + + // font face for UI elements + readonly property string fontFamily: "DejaVu Sans" + + // font face for document text + readonly property string bodyFontFamily: "DejaVu Sans" + + // font face for code editors + readonly property string monoFontFamily: "DejaVu Sans Mono" + + readonly property int fontPixelSize: 18 + readonly property int fontPixelSizeSmall: 14 + 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 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 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 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 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 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 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 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" + } } diff --git a/interface/resources/qml/overte/ToolTip.qml b/interface/resources/qml/overte/ToolTip.qml index ac1c745cba2..8f59c13ee79 100644 --- a/interface/resources/qml/overte/ToolTip.qml +++ b/interface/resources/qml/overte/ToolTip.qml @@ -3,42 +3,42 @@ import QtQuick.Controls import "." ToolTip { - id: control - visible: parent.hovered - delay: 500 + id: control + visible: parent.hovered + delay: 500 - font.family: Theme.fontFamily - font.pixelSize: Theme.fontPixelSizeSmall + font.family: Theme.fontFamily + font.pixelSize: Theme.fontPixelSizeSmall - background: Item { - // drop shadow - Rectangle { - x: 3 - y: 3 - width: parent.width - height: parent.height + background: Item { + // drop shadow + Rectangle { + x: 3 + y: 3 + width: parent.width + height: parent.height - radius: Theme.borderRadius - color: "#a0000000" - } + radius: Theme.borderRadius + color: "#a0000000" + } - Rectangle { - x: 0 - y: 0 - width: parent.width - height: parent.height + 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 - } - } + 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 - } + 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 index bd7b3cdf9b6..f4da29120c5 100644 --- a/interface/resources/qml/overte/WidgetZoo.qml +++ b/interface/resources/qml/overte/WidgetZoo.qml @@ -7,132 +7,132 @@ import "." as Overte // debugging test case to view the themed widgets Window { - id: root - width: 480 - height: 720 - visible: true + id: root + width: 480 + height: 720 + visible: true - Rectangle { - anchors.fill: parent - color: Overte.Theme.paletteActive.base - } + Rectangle { + 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: "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 } - } + 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 + 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 - } - } + 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.Switch { - text: "Switch" - } - Overte.Slider { - value: 0.5 - } - Overte.SpinBox { - editable: true - } - Overte.Ruler { Layout.fillWidth: true } - Row { - spacing: 16 + Overte.TextField { + Layout.fillWidth: true + placeholderText: "Text field" + } + Overte.TextArea { + Layout.fillWidth: true + Layout.preferredHeight: 64 + placeholderText: "Text area" + } + 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.ComboBox { + model: [ + "ComboBox", + "Singing Dogs", + "Golden Combs", + "Swirly Bombs", + ] + } - Overte.Label { - font.pixelSize: Overte.Theme.fontPixelSizeSmall - text: "This is a note label." - } - } + 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." - } + 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 + ScrollView { + id: scrollView + Layout.fillWidth: true + Layout.preferredHeight: 256 - clip: true - contentWidth: 1024 - contentHeight: 1024 + 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 - } + 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 + } - Overte.Label { - text: "ScrollView and ScrollBar" - } - } - } + 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 index 98b4a7396bc..75ddc6acfcc 100644 --- a/interface/resources/qml/overte/avatar_picker/AvatarItem.qml +++ b/interface/resources/qml/overte/avatar_picker/AvatarItem.qml @@ -5,111 +5,111 @@ 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 - required property string description - - implicitWidth: GridView.view.cellWidth - implicitHeight: GridView.view.cellHeight - - Overte.Button { - anchors.fill: parent - anchors.margins: 4 - - id: avatarButton - - Overte.ToolTip { - visible: description !== "" && parent.hovered - text: description - delay: 500 - } - - 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 - } - - Image { - source: "../icons/no_avatar_icon.svg" + id: item + required property int index + required property string name + required property url avatarUrl + required property url iconUrl + required property list tags + required property string description + + implicitWidth: GridView.view.cellWidth + implicitHeight: GridView.view.cellHeight + + Overte.Button { + anchors.fill: parent + anchors.margins: 4 + + id: avatarButton + + Overte.ToolTip { + visible: description !== "" && parent.hovered + text: description + delay: 500 + } + + 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 + } + + Image { + source: "../icons/no_avatar_icon.svg" sourceSize.width: width sourceSize.height: height - fillMode: Image.PreserveAspectFit + fillMode: Image.PreserveAspectFit visible: buttonIcon.status === Image.Error || buttonIcon.status === Image.Null - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right - anchors.bottom: buttonLabel.top - anchors.margins: 4 - } - - 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: AvatarBookmarks.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/close.svg" - icon.width: 32 - icon.height: 32 - icon.color: Overte.Theme.paletteActive.buttonText - - 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 - - onClicked: { - root.requestEdit(item.index); - } - } + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: buttonLabel.top + anchors.margins: 4 + } + + 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: AvatarBookmarks.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/close.svg" + icon.width: 32 + icon.height: 32 + icon.color: Overte.Theme.paletteActive.buttonText + + 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 + + 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 index 72d453c19ca..3432fe01267 100644 --- a/interface/resources/qml/overte/avatar_picker/AvatarPicker.qml +++ b/interface/resources/qml/overte/avatar_picker/AvatarPicker.qml @@ -7,16 +7,16 @@ import "../" as Overte import "." Rectangle { - id: root - anchors.fill: parent - color: Overte.Theme.paletteActive.base - implicitWidth: 480 - implicitHeight: 720 + 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: [] + property bool editable: true + property list availableTags: [] + property string searchExpression: ".*" + property var avatarModel: [] function updateBookmarkModel() { const data = AvatarBookmarks.getBookmarks(); @@ -50,317 +50,317 @@ Rectangle { function onBookmarkDeleted() { updateBookmarkModel(); } } - ColumnLayout { - anchors.fill: parent - - 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 - - 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/no_avatar_icon.svg"; - } - - if (!modelItem.tags) { modelItem.tags = []; } - if (!modelItem.description) { modelItem.description = ""; } - - 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: { - editDialog.editExisting = false; - editDialog.avatarName = ""; - editDialog.avatarUrl = ""; - editDialog.avatarDescription = ""; + ColumnLayout { + anchors.fill: parent + + 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 + + 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/no_avatar_icon.svg"; + } + + if (!modelItem.tags) { modelItem.tags = []; } + if (!modelItem.description) { modelItem.description = ""; } + + 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: { + editDialog.editExisting = false; + editDialog.avatarName = ""; + editDialog.avatarUrl = ""; + editDialog.avatarDescription = ""; avatarNameField.text = ""; avatarUrlField.text = ""; avatarDescriptionField.text = ""; - editDialog.open(); - } - } - } - } - - property int requestedDeleteIndex: -1 - property int requestedEditIndex: -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) { - requestedEditIndex = index; - editDialog.editExisting = true; - editDialog.avatarName = avatarModel[index].name; - editDialog.avatarUrl = avatarModel[index].avatarUrl; - editDialog.avatarDescription = avatarModel[index].description; - editDialog.open(); - } - - Overte.MessageDialog { - id: deleteWarningDialog - anchors.fill: parent - buttons: QtDialogs.MessageDialog.Yes | QtDialogs.MessageDialog.No - - onAccepted: { + editDialog.open(); + } + } + } + } + + property int requestedDeleteIndex: -1 + property int requestedEditIndex: -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) { + requestedEditIndex = index; + editDialog.editExisting = true; + editDialog.avatarName = avatarModel[index].name; + editDialog.avatarUrl = avatarModel[index].avatarUrl; + editDialog.avatarDescription = avatarModel[index].description; + editDialog.open(); + } + + Overte.MessageDialog { + id: deleteWarningDialog + anchors.fill: parent + buttons: QtDialogs.MessageDialog.Yes | QtDialogs.MessageDialog.No + + onAccepted: { AvatarBookmarks.removeBookmark(avatarModel[requestedDeleteIndex].name); - requestedDeleteIndex = -1; - } - } + requestedDeleteIndex = -1; + } + } - Overte.Dialog { - id: editDialog - anchors.fill: parent - maxWidth: -1 + Overte.Dialog { + id: editDialog + anchors.fill: parent + maxWidth: -1 - property bool editExisting: false - property string avatarName: "" - property string avatarUrl: "" - property string avatarDescription: "" + property bool editExisting: false + property string avatarName: "" + property string avatarUrl: "" + property string avatarDescription: "" - signal accepted - signal rejected + signal accepted + signal rejected - onAccepted: { + onAccepted: { if (editExisting) { AvatarBookmarks.removeBookmark(editDialog.avatarName); } AvatarBookmarks.addBookmark(avatarNameField.text, editDialog.avatarUrl); close(); - } - - onRejected: 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: editDialog.editExisting ? qsTr("Edit avatar") : qsTr("Add new avatar") - } - Overte.Ruler { Layout.fillWidth: true } - - Overte.TextField { - Layout.fillWidth: true - placeholderText: qsTr("Avatar name") - text: editDialog.avatarName - id: avatarNameField - } - - Overte.TextField { - Layout.fillWidth: true - placeholderText: qsTr("Avatar URL (.fst, .glb, .vrm, .fbx)") - text: editDialog.avatarUrl - id: avatarUrlField - } - - ScrollView { + } + + onRejected: 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: editDialog.editExisting ? qsTr("Edit avatar") : qsTr("Add new avatar") + } + Overte.Ruler { Layout.fillWidth: true } + + Overte.TextField { + Layout.fillWidth: true + placeholderText: qsTr("Avatar name") + text: editDialog.avatarName + id: avatarNameField + } + + Overte.TextField { + Layout.fillWidth: true + placeholderText: qsTr("Avatar URL (.fst, .glb, .vrm, .fbx)") + text: editDialog.avatarUrl + id: avatarUrlField + } + + ScrollView { // TODO: support avatar descriptions visible: false - Layout.preferredHeight: Overte.Theme.fontPixelSize * 8 - Layout.fillWidth: true - - ScrollBar.vertical: Overte.ScrollBar { - interactive: false - anchors.right: parent.right - anchors.top: parent.top - anchors.bottom: parent.bottom - } - - contentWidth: availableWidth - - Overte.TextArea { - id: avatarDescriptionField - placeholderText: qsTr("Description (optional)") - text: editDialog.avatarDescription - wrapMode: Text.Wrap - font.pixelSize: Overte.Theme.fontPixelSizeSmall - } - } - - RowLayout { - Layout.preferredWidth: 720 - Layout.fillWidth: true - - Overte.Button { - Layout.fillWidth: true - Layout.preferredWidth: 1 - text: qsTr("Cancel") - - onClicked: { - editDialog.rejected(); - requestedEditIndex = -1; - } - } - - Item { + Layout.preferredHeight: Overte.Theme.fontPixelSize * 8 + Layout.fillWidth: true + + ScrollBar.vertical: Overte.ScrollBar { + interactive: false + anchors.right: parent.right + anchors.top: parent.top + anchors.bottom: parent.bottom + } + + contentWidth: availableWidth + + Overte.TextArea { + id: avatarDescriptionField + placeholderText: qsTr("Description (optional)") + text: editDialog.avatarDescription + wrapMode: Text.Wrap + font.pixelSize: Overte.Theme.fontPixelSizeSmall + } + } + + RowLayout { + Layout.preferredWidth: 720 + Layout.fillWidth: true + + Overte.Button { + Layout.fillWidth: true + Layout.preferredWidth: 1 + text: qsTr("Cancel") + + onClicked: { + editDialog.rejected(); + requestedEditIndex = -1; + } + } + + Item { visible: editDialog.editExisting - Layout.preferredWidth: 1 - Layout.fillWidth: true - } + Layout.preferredWidth: 1 + Layout.fillWidth: true + } - Overte.Button { + Overte.Button { visible: !editDialog.editExisting - Layout.fillWidth: true - Layout.preferredWidth: 1 + Layout.fillWidth: true + Layout.preferredWidth: 1 - enabled: avatarNameField.text !== "" - text: qsTr("Add Current") + enabled: avatarNameField.text !== "" + text: qsTr("Add Current") - onClicked: { + onClicked: { avatarUrlField.text = MyAvatar.skeletonModelURL; - editDialog.accepted(); - requestedEditIndex = -1; - } - } - - Overte.Button { - Layout.fillWidth: true - Layout.preferredWidth: 1 - - backgroundColor: Overte.Theme.paletteActive.buttonAdd - text: editDialog.editExisting ? qsTr("Apply") : qsTr("Add") - enabled: avatarNameField.text !== "" && avatarUrlField.text !== "" - - onClicked: { - editDialog.accepted(); - requestedEditIndex = -1; - } - } - } - } - } + editDialog.accepted(); + requestedEditIndex = -1; + } + } + + Overte.Button { + Layout.fillWidth: true + Layout.preferredWidth: 1 + + backgroundColor: Overte.Theme.paletteActive.buttonAdd + text: editDialog.editExisting ? qsTr("Apply") : qsTr("Add") + enabled: avatarNameField.text !== "" && avatarUrlField.text !== "" + + onClicked: { + editDialog.accepted(); + requestedEditIndex = -1; + } + } + } + } + } } diff --git a/interface/resources/qml/overte/chat/Chat.qml b/interface/resources/qml/overte/chat/Chat.qml index fc436418e6d..4d9a63e0e1f 100644 --- a/interface/resources/qml/overte/chat/Chat.qml +++ b/interface/resources/qml/overte/chat/Chat.qml @@ -5,91 +5,91 @@ import "../" as Overte import "." as OverteChat Rectangle { - id: root - anchors.fill: parent - color: Overte.Theme.paletteActive.base + 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 settingJoinNotifications: true property bool settingBroadcast: false - property bool settingChatBubbles: true - property bool settingDesktopWindow: true + property bool settingChatBubbles: true + property bool settingDesktopWindow: true - property var typingIndicatorNames: ({}) + property var typingIndicatorNames: ({}) - signal messagePushed(name: string, body: string, time: string) - signal notificationPushed(text: string, time: string) - signal messagesCleared() + signal messagePushed(name: string, body: string, time: string) + signal notificationPushed(text: string, time: string) + signal messagesCleared() - function toScript(obj) { + function toScript(obj) { sendToScript(JSON.stringify(obj)); // for debugging standalone with the qml tool - /*console.debug(JSON.stringify(obj)); + /*console.debug(JSON.stringify(obj)); - switch (obj.event) { - case "send_message": - fromScript({event: "recv_message", name: "ada.tv", body: obj.body}); - break; + switch (obj.event) { + case "send_message": + fromScript({event: "recv_message", name: "ada.tv", body: obj.body}); + break; - case "start_typing": - fromScript({event: "start_typing", name: "ada.tv", uuid: "ba"}); - break; + case "start_typing": + fromScript({event: "start_typing", name: "ada.tv", uuid: "ba"}); + break; - case "end_typing": - fromScript({event: "end_typing", name: "ada.tv", uuid: "ba"}); - break; + case "end_typing": + fromScript({event: "end_typing", name: "ada.tv", uuid: "ba"}); + break; }*/ - } + } - function fromScript(rawObj) { + function fromScript(rawObj) { const obj = JSON.parse(rawObj); - const timestamp = (obj.timestamp ? new Date(obj.timestamp) : new Date()).toTimeString(); - - switch (obj.event) { - case "recv_message": - messagePushed(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; + const timestamp = (obj.timestamp ? new Date(obj.timestamp) : new Date()).toTimeString(); + + switch (obj.event) { + case "recv_message": + messagePushed(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}"`); + default: + console.error(`fromScript: Unknown event type "${obj.event}"`); console.error(JSON.stringify(obj)); - break; - } - } + break; + } + } function updateSetting(name, value) { switch (name) { diff --git a/interface/resources/qml/overte/chat/ChatPage.qml b/interface/resources/qml/overte/chat/ChatPage.qml index 611f5d56446..40a3b2ae0b3 100644 --- a/interface/resources/qml/overte/chat/ChatPage.qml +++ b/interface/resources/qml/overte/chat/ChatPage.qml @@ -7,216 +7,216 @@ import "../settings" as OverteSettings import "." as OverteChat ColumnLayout { - id: chatPage + id: chatPage - RowLayout { - Layout.fillWidth: true + RowLayout { + Layout.fillWidth: true - Overte.Switch { - id: broadcastSwitch - text: qsTr("Broadcast") + Overte.Switch { + id: broadcastSwitch + text: qsTr("Broadcast") - Overte.ToolTip { - text: qsTr("Whether your messages will be broadcast across the whole domain, rather than limited to a local range.") - } + Overte.ToolTip { + text: qsTr("Whether your messages will be broadcast across the whole domain, rather than limited to a local range.") + } checked: root.settingBroadcast onToggled: { root.settingBroadcast = checked; root.sendSettingsUpdate(); } - } - - Item { Layout.fillWidth: true } - - Overte.RoundButton { - Layout.alignment: Qt.AlignRight - 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 {} - - Connections { - target: root - - function onMessagesCleared() { - chatLog.model.clear(); - } - - function onMessagePushed(name, body, timestamp) { - chatLog.model.append({ - name: name, - body: ( - body - .replace(/\&/gi, "&") - .replace(/\[/gi, "[") - .replace(/\]/gi, "]") - .replace(/\/gi, ">") - .replace(/\'/gi, "'") - .replace(/\"/gi, """) - .replace(/\n/gi, "
") - ), - notification: "", - timestamp: timestamp, - }); - chatLog.currentIndex = chatLog.model.count - 1; - } - - function onNotificationPushed(text, timestamp) { - chatLog.model.append({ - name: "", - body: "", - notification: text, - timestamp: timestamp, - }); - 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: ( - broadcastSwitch.checked ? - 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}); - } - } - } + } + + Item { Layout.fillWidth: true } + + Overte.RoundButton { + Layout.alignment: Qt.AlignRight + 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 {} + + Connections { + target: root + + function onMessagesCleared() { + chatLog.model.clear(); + } + + function onMessagePushed(name, body, timestamp) { + chatLog.model.append({ + name: name, + body: ( + body + .replace(/\&/gi, "&") + .replace(/\[/gi, "[") + .replace(/\]/gi, "]") + .replace(/\/gi, ">") + .replace(/\'/gi, "'") + .replace(/\"/gi, """) + .replace(/\n/gi, "
") + ), + notification: "", + timestamp: timestamp, + }); + chatLog.currentIndex = chatLog.model.count - 1; + } + + function onNotificationPushed(text, timestamp) { + chatLog.model.append({ + name: "", + body: "", + notification: text, + timestamp: timestamp, + }); + 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: ( + broadcastSwitch.checked ? + 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 index 6431ea9668f..f49a392e89e 100644 --- a/interface/resources/qml/overte/chat/MessageBlock.qml +++ b/interface/resources/qml/overte/chat/MessageBlock.qml @@ -4,74 +4,74 @@ 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 string timestamp + required property int index + required property string name + required property string body + required property string notification + required property string timestamp - anchors.left: parent ? parent.left : undefined - anchors.right: parent ? parent.right : undefined - anchors.leftMargin: 4 - anchors.rightMargin: Overte.Theme.scrollbarWidth + 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 + Rectangle { + Layout.fillWidth: true + implicitHeight: 3 + color: Overte.Theme.paletteActive.highlight - ColorAnimation on color { - to: Overte.Theme.paletteActive.alternateBase - duration: 500 - } - } + 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 + RowLayout { + Layout.leftMargin: 6 + Layout.rightMargin: 8 + Layout.fillWidth: true + opacity: Overte.Theme.highContrast ? 1.0 : 0.6 - Overte.Label { - text: notification ? notification : name - font.pixelSize: Overte.Theme.fontPixelSizeSmall - font.bold: true - Layout.fillWidth: true - wrapMode: Text.Wrap - } + Overte.Label { + text: notification ? notification : name + font.pixelSize: Overte.Theme.fontPixelSizeSmall + font.bold: true + Layout.fillWidth: true + wrapMode: Text.Wrap + } - Overte.Label { - Layout.alignment: Qt.AlignRight - horizontalAlignment: Text.AlignRight - font.pixelSize: Overte.Theme.fontPixelSizeSmall - text: timestamp - } - } + Overte.Label { + Layout.alignment: Qt.AlignRight + horizontalAlignment: Text.AlignRight + font.pixelSize: Overte.Theme.fontPixelSizeSmall + text: timestamp + } + } - Overte.BodyText { - Layout.leftMargin: 6 - Layout.rightMargin: 16 - // if we used Layout.fillWidth, the whole text line 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 + Overte.BodyText { + Layout.leftMargin: 6 + Layout.rightMargin: 16 + // if we used Layout.fillWidth, the whole text line 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 + visible: text.length > 0 - // MD support is cool, but it'd only work properly in the QML chat app - // and not chat bubbles. (maybe would work with QML desktop notifications?) - //textFormat: TextEdit.MarkdownText - textFormat: TextEdit.RichText + // MD support is cool, but it'd only work properly in the QML chat app + // and not chat bubbles. (maybe would work with QML desktop notifications?) + //textFormat: TextEdit.MarkdownText + textFormat: TextEdit.RichText - text: { - if (notification) { return ""; } + text: { + if (notification) { return ""; } - return ( - body - .replace( - /(https?:\/\/[^\s]+)/gi, - `$1` - ) - ); - } - } + return ( + body + .replace( + /(https?:\/\/[^\s]+)/gi, + `$1` + ) + ); + } + } } diff --git a/interface/resources/qml/overte/chat/SettingsPage.qml b/interface/resources/qml/overte/chat/SettingsPage.qml index 8fe5682bae3..943a0eb082a 100644 --- a/interface/resources/qml/overte/chat/SettingsPage.qml +++ b/interface/resources/qml/overte/chat/SettingsPage.qml @@ -6,79 +6,79 @@ import "../" as Overte import "../settings" as OverteSettings Column { - id: settingsPage - spacing: 16 + id: settingsPage + spacing: 16 - Item { - anchors.left: parent.left - anchors.right: parent.right - implicitHeight: Math.max(settingsBackBtn.implicitHeight, settingsTitleLabel.implicitHeight) + Item { + anchors.left: parent.left + anchors.right: parent.right + implicitHeight: Math.max(settingsBackBtn.implicitHeight, settingsTitleLabel.implicitHeight) - Overte.Label { - anchors.fill: parent + Overte.Label { + anchors.fill: parent - id: settingsTitleLabel + id: settingsTitleLabel - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - text: qsTr("Chat Settings") - } + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + text: qsTr("Chat Settings") + } - Overte.Button { - id: settingsBackBtn - icon.source: "../icons/triangle_left.svg" - icon.width: 24 - icon.height: 24 - icon.color: Overte.Theme.paletteActive.buttonText + 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") + text: qsTr("Back", "Return to previous page") - onClicked: { - stack.pop(); - } - } - } + onClicked: { + stack.pop(); + } + } + } - OverteSettings.SwitchSetting { - text: qsTr("Join/Leave Notifications") + OverteSettings.SwitchSetting { + text: qsTr("Join/Leave Notifications") - value: root.settingJoinNotifications + value: root.settingJoinNotifications onValueChanged: { root.settingJoinNotifications = value; root.sendSettingsUpdate(); } - } + } - OverteSettings.SwitchSetting { - text: qsTr("Chat Bubbles") + OverteSettings.SwitchSetting { + text: qsTr("Chat Bubbles") - value: root.settingChatBubbles + value: root.settingChatBubbles onValueChanged: { root.settingChatBubbles = value; root.sendSettingsUpdate(); } - } + } - OverteSettings.SwitchSetting { - text: qsTr("Desktop Window") + OverteSettings.SwitchSetting { + text: qsTr("Desktop Window") - value: root.settingDesktopWindow + value: root.settingDesktopWindow onValueChanged: { root.settingDesktopWindow = value; root.sendSettingsUpdate(); } - } + } - RowLayout { - anchors.left: parent.left - anchors.right: parent.right + RowLayout { + anchors.left: parent.left + anchors.right: parent.right - Overte.Button { - Layout.alignment: Qt.AlignHCenter - text: qsTr("Clear History") - backgroundColor: Overte.Theme.paletteActive.buttonDestructive + Overte.Button { + Layout.alignment: Qt.AlignHCenter + text: qsTr("Clear History") + backgroundColor: Overte.Theme.paletteActive.buttonDestructive - onClicked: { root.messagesCleared(); } - } - } + onClicked: { root.messagesCleared(); } + } + } } diff --git a/interface/resources/qml/overte/contacts/AccountAvatar.qml b/interface/resources/qml/overte/contacts/AccountAvatar.qml index 39be2b5e7ff..b30a781d834 100644 --- a/interface/resources/qml/overte/contacts/AccountAvatar.qml +++ b/interface/resources/qml/overte/contacts/AccountAvatar.qml @@ -4,54 +4,54 @@ import QtQuick.Effects import ".." as Overte Rectangle { - required property url source - required property int status - - 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 - source: avatar.source - - 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" - } - } + required property url source + required property int status + + 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 + source: avatar.source + + 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 index 0f70a909105..a73defb081b 100644 --- a/interface/resources/qml/overte/contacts/AccountContact.qml +++ b/interface/resources/qml/overte/contacts/AccountContact.qml @@ -6,102 +6,102 @@ 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 + 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 int index - // directory username - required property string user - required property url avatarUrl - required property int status - required property bool friend - required property string currentPlaceName + // 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 + ColumnLayout { + anchors.fill: parent + anchors.margins: 4 + anchors.leftMargin: 8 + anchors.rightMargin: 8 - RowLayout { - Layout.fillWidth: true - spacing: 16 + RowLayout { + Layout.fillWidth: true + spacing: 16 - AccountAvatar { - source: avatarUrl - status: control.status - } + AccountAvatar { + source: avatarUrl + status: control.status + } - ColumnLayout { - Layout.fillWidth: true - Layout.fillHeight: true + 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 + elide: Text.ElideRight + verticalAlignment: Text.AlignVCenter + text: user + } - Overte.Label { - Layout.fillWidth: true - Layout.fillHeight: true - verticalAlignment: Text.AlignVCenter + 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; - } - } + 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") - } - } - } + 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.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 { - 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 + Overte.RoundButton { + 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; - } - } - } - } + onClicked: { + // TODO + control.friend = !control.friend; + } + } + } + } } diff --git a/interface/resources/qml/overte/contacts/ContactsList.qml b/interface/resources/qml/overte/contacts/ContactsList.qml index e85acdb3305..566db0faa85 100644 --- a/interface/resources/qml/overte/contacts/ContactsList.qml +++ b/interface/resources/qml/overte/contacts/ContactsList.qml @@ -6,202 +6,202 @@ import ".." as Overte import "." Rectangle { - id: root - anchors.fill: parent - implicitWidth: 480 - implicitHeight: 720 - color: Overte.Theme.paletteActive.base - - property string localSearchExpression: ".*" - property string accountSearchExpression: ".*" - - property list localContactsModel: [ - {user: "ada.tv", name: "ada.tv", volume: 1.0, badgeIconSource: "../icons/gold_star.svg"}, - {user: "admin", name: "Admin", volume: 1.0, badgeIconSource: "../icons/admin_shield.svg"}, - {user: "{81cb9b67-d034-40f6-9ab7-a4ecb62f8961}", name: "Anonymous", volume: 1.0, badgeIconSource: ""}, - {user: "", name: "Not logged in", volume: 1.0, badgeIconSource: ""}, - - {user: "ScrollingTest", name: "Scroll test", volume: 1.0, badgeIconSource: ""}, - {user: "ScrollingTest", name: "Scroll test", volume: 1.0, badgeIconSource: ""}, - {user: "ScrollingTest", name: "Scroll test", volume: 1.0, badgeIconSource: ""}, - {user: "ScrollingTest", name: "Scroll test", volume: 1.0, badgeIconSource: ""}, - {user: "ScrollingTest", name: "Scroll test", volume: 1.0, badgeIconSource: ""}, - {user: "ScrollingTest", name: "Scroll test", volume: 1.0, badgeIconSource: ""}, - {user: "ScrollingTest", name: "Scroll test", volume: 1.0, badgeIconSource: ""}, - {user: "ScrollingTest", name: "Scroll test", volume: 1.0, badgeIconSource: ""}, - {user: "ScrollingTest", name: "Scroll test", volume: 1.0, badgeIconSource: ""}, - {user: "ScrollingTest", name: "Scroll test", volume: 1.0, badgeIconSource: ""}, - {user: "ScrollingTest", name: "Scroll test", volume: 1.0, badgeIconSource: ""}, - {user: "ScrollingTest", name: "Scroll test", volume: 1.0, badgeIconSource: ""}, - {user: "ScrollingTest", name: "Scroll test", volume: 1.0, badgeIconSource: ""}, - ] - - property list accountContactsModel: [ - /*{user: "ada.tv", avatarUrl: "file:///home/ada/art/doodles/bevy blep avi.png", status: 0, friend: true, currentPlaceName: "overte_hub"}, - {user: "x74hc595", avatarUrl: "https://cdn.discordapp.com/avatars/761127759367634965/915ea6b0e6b380458bce46616fa1fe35.webp?size=96", status: 2, friend: true, currentPlaceName: "overte_hub"}, - {user: "juliangro", avatarUrl: "https://cdn.discordapp.com/avatars/181488002831351808/7c17a81a149da388d078d8ac795f64fe.webp?size=96", status: 1, friend: true, currentPlaceName: "overte_hub"},*/ - {user: "AccountContact", avatarUrl: "file:///home/ada/art/doodles/bevy blep avi.png", status: 0, friend: false, currentPlaceName: ""}, - {user: "AccountContact", avatarUrl: "file:///home/ada/art/doodles/bevy blep avi.png", status: 1, friend: false, currentPlaceName: ""}, - {user: "AccountContact", avatarUrl: "file:///home/ada/art/doodles/bevy blep avi.png", status: 2, friend: false, currentPlaceName: "overte_hub"}, - {user: "AccountContact", avatarUrl: "file:///home/ada/art/doodles/bevy blep avi.png", status: 3, friend: false, currentPlaceName: "overte_hub"}, - {user: "Friend", avatarUrl: "file:///home/ada/art/doodles/bevy blep avi.png", status: 2, friend: true, currentPlaceName: "overte_hub"}, - ] - - ColumnLayout { - anchors.fill: root - - MyAccountInfo { - Layout.fillWidth: true - status: MyAccountInfo.Status.LoggedOut - 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 { - ListView { - Layout.fillWidth: true - Layout.fillHeight: true - clip: true - - ScrollBar.vertical: Overte.ScrollBar { - policy: ScrollBar.AsNeeded - } - - contentWidth: root.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: root.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") - } - } - } - } + id: root + anchors.fill: parent + implicitWidth: 480 + implicitHeight: 720 + color: Overte.Theme.paletteActive.base + + property string localSearchExpression: ".*" + property string accountSearchExpression: ".*" + + property list localContactsModel: [ + {user: "ada.tv", name: "ada.tv", volume: 1.0, badgeIconSource: "../icons/gold_star.svg"}, + {user: "admin", name: "Admin", volume: 1.0, badgeIconSource: "../icons/admin_shield.svg"}, + {user: "{81cb9b67-d034-40f6-9ab7-a4ecb62f8961}", name: "Anonymous", volume: 1.0, badgeIconSource: ""}, + {user: "", name: "Not logged in", volume: 1.0, badgeIconSource: ""}, + + {user: "ScrollingTest", name: "Scroll test", volume: 1.0, badgeIconSource: ""}, + {user: "ScrollingTest", name: "Scroll test", volume: 1.0, badgeIconSource: ""}, + {user: "ScrollingTest", name: "Scroll test", volume: 1.0, badgeIconSource: ""}, + {user: "ScrollingTest", name: "Scroll test", volume: 1.0, badgeIconSource: ""}, + {user: "ScrollingTest", name: "Scroll test", volume: 1.0, badgeIconSource: ""}, + {user: "ScrollingTest", name: "Scroll test", volume: 1.0, badgeIconSource: ""}, + {user: "ScrollingTest", name: "Scroll test", volume: 1.0, badgeIconSource: ""}, + {user: "ScrollingTest", name: "Scroll test", volume: 1.0, badgeIconSource: ""}, + {user: "ScrollingTest", name: "Scroll test", volume: 1.0, badgeIconSource: ""}, + {user: "ScrollingTest", name: "Scroll test", volume: 1.0, badgeIconSource: ""}, + {user: "ScrollingTest", name: "Scroll test", volume: 1.0, badgeIconSource: ""}, + {user: "ScrollingTest", name: "Scroll test", volume: 1.0, badgeIconSource: ""}, + {user: "ScrollingTest", name: "Scroll test", volume: 1.0, badgeIconSource: ""}, + ] + + property list accountContactsModel: [ + /*{user: "ada.tv", avatarUrl: "file:///home/ada/art/doodles/bevy blep avi.png", status: 0, friend: true, currentPlaceName: "overte_hub"}, + {user: "x74hc595", avatarUrl: "https://cdn.discordapp.com/avatars/761127759367634965/915ea6b0e6b380458bce46616fa1fe35.webp?size=96", status: 2, friend: true, currentPlaceName: "overte_hub"}, + {user: "juliangro", avatarUrl: "https://cdn.discordapp.com/avatars/181488002831351808/7c17a81a149da388d078d8ac795f64fe.webp?size=96", status: 1, friend: true, currentPlaceName: "overte_hub"},*/ + {user: "AccountContact", avatarUrl: "file:///home/ada/art/doodles/bevy blep avi.png", status: 0, friend: false, currentPlaceName: ""}, + {user: "AccountContact", avatarUrl: "file:///home/ada/art/doodles/bevy blep avi.png", status: 1, friend: false, currentPlaceName: ""}, + {user: "AccountContact", avatarUrl: "file:///home/ada/art/doodles/bevy blep avi.png", status: 2, friend: false, currentPlaceName: "overte_hub"}, + {user: "AccountContact", avatarUrl: "file:///home/ada/art/doodles/bevy blep avi.png", status: 3, friend: false, currentPlaceName: "overte_hub"}, + {user: "Friend", avatarUrl: "file:///home/ada/art/doodles/bevy blep avi.png", status: 2, friend: true, currentPlaceName: "overte_hub"}, + ] + + ColumnLayout { + anchors.fill: root + + MyAccountInfo { + Layout.fillWidth: true + status: MyAccountInfo.Status.LoggedOut + 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 { + ListView { + Layout.fillWidth: true + Layout.fillHeight: true + clip: true + + ScrollBar.vertical: Overte.ScrollBar { + policy: ScrollBar.AsNeeded + } + + contentWidth: root.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: root.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") + } + } + } + } } diff --git a/interface/resources/qml/overte/contacts/MyAccountInfo.qml b/interface/resources/qml/overte/contacts/MyAccountInfo.qml index 062840d3ad5..caba0bfdc30 100644 --- a/interface/resources/qml/overte/contacts/MyAccountInfo.qml +++ b/interface/resources/qml/overte/contacts/MyAccountInfo.qml @@ -6,131 +6,131 @@ 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 - } - - property int status: MyAccountInfo.Status.Invisible - property string avatarImgSource: "file:///home/ada/art/doodles/bevy blep avi.png" - - 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.avatarImgSource : - "../icons/unset_avatar.svg" - ) - status: root.status - Layout.preferredWidth: 64 - Layout.preferredHeight: 64 - Layout.leftMargin: 8 - Layout.rightMargin: 8 - } - - ColumnLayout { - Layout.rightMargin: 8 - Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter - - Overte.TextField { - Layout.fillWidth: true - verticalAlignment: Text.AlignVCenter - - id: displayName - text: "ada.tv" - placeholderText: qsTr("Display Name") - } - - 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; - } - - 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") - } - } - } - } + 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 + } + + property int status: MyAccountInfo.Status.Invisible + property string avatarImgSource: "file:///home/ada/art/doodles/bevy blep avi.png" + + 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.avatarImgSource : + "../icons/unset_avatar.svg" + ) + status: root.status + Layout.preferredWidth: 64 + Layout.preferredHeight: 64 + Layout.leftMargin: 8 + Layout.rightMargin: 8 + } + + ColumnLayout { + Layout.rightMargin: 8 + Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter + + Overte.TextField { + Layout.fillWidth: true + verticalAlignment: Text.AlignVCenter + + id: displayName + text: "ada.tv" + placeholderText: qsTr("Display Name") + } + + 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; + } + + 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 index cb7a9aaa1f5..53d5dcc059e 100644 --- a/interface/resources/qml/overte/contacts/SessionContact.qml +++ b/interface/resources/qml/overte/contacts/SessionContact.qml @@ -5,84 +5,84 @@ 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 + 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 int index - // session UUID or directory username - required property string user - required property string name - required property real volume - required property url badgeIconSource + // session UUID or directory username + required property string user + required property string name + required property real volume + required property url badgeIconSource - ColumnLayout { - anchors.fill: parent - anchors.margins: 4 - anchors.leftMargin: 8 - anchors.rightMargin: 8 + ColumnLayout { + anchors.fill: parent + anchors.margins: 4 + anchors.leftMargin: 8 + anchors.rightMargin: 8 - RowLayout { - Layout.fillWidth: true - spacing: 16 + RowLayout { + Layout.fillWidth: true + spacing: 16 - Image { - Layout.leftMargin: 6 - Layout.preferredWidth: 24 - Layout.preferredHeight: 24 - source: badgeIconSource - } + Image { + Layout.leftMargin: 6 + Layout.preferredWidth: 24 + Layout.preferredHeight: 24 + source: badgeIconSource + } - ColumnLayout { - Layout.fillWidth: true + ColumnLayout { + Layout.fillWidth: true - Overte.Label { - Layout.fillWidth: true - elide: Text.ElideRight - text: name - } + Overte.Label { + Layout.fillWidth: true + elide: Text.ElideRight + text: name + } - Overte.BodyText { - font.pixelSize: Overte.Theme.fontPixelSizeSmall - visible: user !== "" - //elide: Text.ElideRight - text: user - palette.buttonText: Overte.Theme.paletteActive.link - opacity: Overte.Theme.highContrast ? 1.0 : 0.7 - } - } - } + Overte.BodyText { + font.pixelSize: Overte.Theme.fontPixelSizeSmall + visible: user !== "" + //elide: Text.ElideRight + text: user + palette.buttonText: Overte.Theme.paletteActive.link + opacity: Overte.Theme.highContrast ? 1.0 : 0.7 + } + } + } - 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 + 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 - } + 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: volume + Overte.Slider { + Layout.fillWidth: true + id: volumeSlider + from: 0.0 + to: 1.2 + stepSize: 0.1 + snapMode: Slider.SnapAlways + value: volume - onMoved: { - // TODO - } - } + onMoved: { + // TODO + } + } - Overte.Label { - Layout.preferredWidth: Overte.Theme.fontPixelSize * 3 - text: `${Math.round(volumeSlider.value * 100)}%` - } - } - } + Overte.Label { + Layout.preferredWidth: Overte.Theme.fontPixelSize * 3 + text: `${Math.round(volumeSlider.value * 100)}%` + } + } + } } diff --git a/interface/resources/qml/overte/dialogs/AssetDialog.qml b/interface/resources/qml/overte/dialogs/AssetDialog.qml index 19a88644583..5212992d1ee 100644 --- a/interface/resources/qml/overte/dialogs/AssetDialog.qml +++ b/interface/resources/qml/overte/dialogs/AssetDialog.qml @@ -8,237 +8,237 @@ import Qt.labs.qmlmodels import ".." Rectangle { - id: dialog - color: Theme.paletteActive.base - - property url selectedFile: "" - property string searchExpression: ".*" - - readonly property var spawnableRegex: /\.(glb|fbx|fst|png|jpeg|jpg|webp)$/i - - ColumnLayout { - anchors.fill: parent - - RowLayout { - Layout.fillWidth: true - Layout.margins: 4 - - Button { - backgroundColor: Theme.paletteActive.buttonAdd - text: qsTr("Upload File") - - // TODO - onClicked: {} - } - - TextField { - Layout.fillWidth: true - Layout.preferredHeight: parent.height - - id: searchField - placeholderText: qsTr("Search…") - - Keys.onEnterPressed: searchButton.click() - Keys.onReturnPressed: searchButton.click() - } - - RoundButton { - id: searchButton - icon.source: "../icons/search.svg" - icon.width: 24 - icon.height: 24 - icon.color: Theme.paletteActive.buttonText - - onClicked: { - searchExpression = searchField.text === "" ? /.*/ : new RegExp(searchField.text); - } - } - } - - ScrollView { - Layout.fillWidth: true - Layout.fillHeight: true - - contentWidth: availableWidth - rightPadding: ScrollBar.vertical.width - - background: Rectangle { - color: Qt.darker(Theme.paletteActive.base, 1.2) - } - - ScrollBar.horizontal.policy: ScrollBar.AlwaysOff - - ScrollBar.vertical: ScrollBar { - policy: ScrollBar.AlwaysOn - interactive: true - anchors.right: parent.right - anchors.top: parent.top - anchors.bottom: parent.bottom - } - - TableView { - id: tableView - clip: true - selectionBehavior: TableView.SelectRows - selectionMode: 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 - } - - // TODO - rows: [ - { fileName: "bevy2.glb", fileModified: (new Date()).toLocaleDateString(null, Locale.ShortFormat), fileSize: "2.1 MiB" }, - { fileName: "cheese.jpg", fileModified: (new Date()).toLocaleDateString(null, Locale.ShortFormat), fileSize: "12 KiB" }, - { fileName: "metal pipe.wav", fileModified: (new Date()).toLocaleDateString(null, Locale.ShortFormat), fileSize: "200 KiB" }, - ] - } - - selectionModel: ItemSelectionModel { - onCurrentChanged: (index, _) => { - if (index.row === -1) { return; } - - const data = tableView.model.getRow(index.row); - selectedFile = data.fileName; - } - } - - delegate: Rectangle { - required property bool selected - required property bool current - required property int row - - readonly property bool rowCurrent: tableView.currentRow === row - - color: ( - rowCurrent ? - Theme.paletteActive.highlight : - (row % 2 === 0 ? Theme.paletteActive.base : Theme.paletteActive.alternateBase) - ) - implicitHeight: Theme.fontPixelSize * 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; - } - - switch (column) { - case 0: return nameWidth; - case 1: return mtimeWidth; - case 2: return sizeWidth; - } - } - - Text { - anchors.margins: 8 - anchors.fill: parent - - verticalAlignment: Text.AlignVCenter - horizontalAlignment: column === 2 ? Text.AlignRight : Text.AlignLeft - font.family: Theme.fontFamily - font.pixelSize: Theme.fontPixelSize - color: rowCurrent ? Theme.paletteActive.highlightedText : Theme.paletteActive.text - text: display - } - } - } - } - - GridLayout { - rows: 2 - columns: 2 - Layout.fillWidth: true - Layout.margins: 8 - - Button { - Layout.fillWidth: true - Layout.preferredWidth: 1 - - enabled: tableView.currentRow !== -1 - text: qsTr("Delete") - backgroundColor: Theme.paletteActive.buttonDestructive - - // TODO - onClicked: { - console.log("Delete", tableView.model.data(tableView.model.index(tableView.currentRow, 0))); - } - } - - Button { - Layout.fillWidth: true - Layout.preferredWidth: 1 - - enabled: tableView.currentRow !== -1 - text: qsTr("Copy Link") - backgroundColor: Theme.paletteActive.buttonInfo - - // TODO - onClicked: { - console.log("Copy Link", tableView.model.data(tableView.model.index(tableView.currentRow, 0))); - } - } - - Button { - Layout.fillWidth: true - Layout.preferredWidth: 1 - - enabled: tableView.currentRow !== -1 - text: qsTr("Rename") - - // TODO - onClicked: { - console.log("Rename", tableView.model.data(tableView.model.index(tableView.currentRow, 0))); - } - } - - Button { - Layout.fillWidth: true - Layout.preferredWidth: 1 - - enabled: { - if (tableView.currentRow === -1) { return false; } - - const index = tableView.model.index(tableView.currentRow, 0); - const data = tableView.model.data(index); - return !!data.match(spawnableRegex); - } - text: qsTr("Create Entity") - backgroundColor: Theme.paletteActive.buttonAdd - - // TODO - onClicked: { - console.log("Create Entity", tableView.model.data(tableView.model.index(tableView.currentRow, 0))); - } - } - } - } - - MessageDialog { - id: replaceWarningDialog - anchors.fill: parent - } + id: dialog + color: Theme.paletteActive.base + + property url selectedFile: "" + property string searchExpression: ".*" + + readonly property var spawnableRegex: /\.(glb|fbx|fst|png|jpeg|jpg|webp)$/i + + ColumnLayout { + anchors.fill: parent + + RowLayout { + Layout.fillWidth: true + Layout.margins: 4 + + Button { + backgroundColor: Theme.paletteActive.buttonAdd + text: qsTr("Upload File") + + // TODO + onClicked: {} + } + + TextField { + Layout.fillWidth: true + Layout.preferredHeight: parent.height + + id: searchField + placeholderText: qsTr("Search…") + + Keys.onEnterPressed: searchButton.click() + Keys.onReturnPressed: searchButton.click() + } + + RoundButton { + id: searchButton + icon.source: "../icons/search.svg" + icon.width: 24 + icon.height: 24 + icon.color: Theme.paletteActive.buttonText + + onClicked: { + searchExpression = searchField.text === "" ? /.*/ : new RegExp(searchField.text); + } + } + } + + ScrollView { + Layout.fillWidth: true + Layout.fillHeight: true + + contentWidth: availableWidth + rightPadding: ScrollBar.vertical.width + + background: Rectangle { + color: Qt.darker(Theme.paletteActive.base, 1.2) + } + + ScrollBar.horizontal.policy: ScrollBar.AlwaysOff + + ScrollBar.vertical: ScrollBar { + policy: ScrollBar.AlwaysOn + interactive: true + anchors.right: parent.right + anchors.top: parent.top + anchors.bottom: parent.bottom + } + + TableView { + id: tableView + clip: true + selectionBehavior: TableView.SelectRows + selectionMode: 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 + } + + // TODO + rows: [ + { fileName: "bevy2.glb", fileModified: (new Date()).toLocaleDateString(null, Locale.ShortFormat), fileSize: "2.1 MiB" }, + { fileName: "cheese.jpg", fileModified: (new Date()).toLocaleDateString(null, Locale.ShortFormat), fileSize: "12 KiB" }, + { fileName: "metal pipe.wav", fileModified: (new Date()).toLocaleDateString(null, Locale.ShortFormat), fileSize: "200 KiB" }, + ] + } + + selectionModel: ItemSelectionModel { + onCurrentChanged: (index, _) => { + if (index.row === -1) { return; } + + const data = tableView.model.getRow(index.row); + selectedFile = data.fileName; + } + } + + delegate: Rectangle { + required property bool selected + required property bool current + required property int row + + readonly property bool rowCurrent: tableView.currentRow === row + + color: ( + rowCurrent ? + Theme.paletteActive.highlight : + (row % 2 === 0 ? Theme.paletteActive.base : Theme.paletteActive.alternateBase) + ) + implicitHeight: Theme.fontPixelSize * 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; + } + + switch (column) { + case 0: return nameWidth; + case 1: return mtimeWidth; + case 2: return sizeWidth; + } + } + + Text { + anchors.margins: 8 + anchors.fill: parent + + verticalAlignment: Text.AlignVCenter + horizontalAlignment: column === 2 ? Text.AlignRight : Text.AlignLeft + font.family: Theme.fontFamily + font.pixelSize: Theme.fontPixelSize + color: rowCurrent ? Theme.paletteActive.highlightedText : Theme.paletteActive.text + text: display + } + } + } + } + + GridLayout { + rows: 2 + columns: 2 + Layout.fillWidth: true + Layout.margins: 8 + + Button { + Layout.fillWidth: true + Layout.preferredWidth: 1 + + enabled: tableView.currentRow !== -1 + text: qsTr("Delete") + backgroundColor: Theme.paletteActive.buttonDestructive + + // TODO + onClicked: { + console.log("Delete", tableView.model.data(tableView.model.index(tableView.currentRow, 0))); + } + } + + Button { + Layout.fillWidth: true + Layout.preferredWidth: 1 + + enabled: tableView.currentRow !== -1 + text: qsTr("Copy Link") + backgroundColor: Theme.paletteActive.buttonInfo + + // TODO + onClicked: { + console.log("Copy Link", tableView.model.data(tableView.model.index(tableView.currentRow, 0))); + } + } + + Button { + Layout.fillWidth: true + Layout.preferredWidth: 1 + + enabled: tableView.currentRow !== -1 + text: qsTr("Rename") + + // TODO + onClicked: { + console.log("Rename", tableView.model.data(tableView.model.index(tableView.currentRow, 0))); + } + } + + Button { + Layout.fillWidth: true + Layout.preferredWidth: 1 + + enabled: { + if (tableView.currentRow === -1) { return false; } + + const index = tableView.model.index(tableView.currentRow, 0); + const data = tableView.model.data(index); + return !!data.match(spawnableRegex); + } + text: qsTr("Create Entity") + backgroundColor: Theme.paletteActive.buttonAdd + + // TODO + onClicked: { + console.log("Create Entity", tableView.model.data(tableView.model.index(tableView.currentRow, 0))); + } + } + } + } + + MessageDialog { + id: replaceWarningDialog + anchors.fill: parent + } } diff --git a/interface/resources/qml/overte/dialogs/FileDialog.qml b/interface/resources/qml/overte/dialogs/FileDialog.qml index 0543ff2ea60..aaf12e12667 100644 --- a/interface/resources/qml/overte/dialogs/FileDialog.qml +++ b/interface/resources/qml/overte/dialogs/FileDialog.qml @@ -10,584 +10,584 @@ 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 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 - - 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: { - 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 - onClicked: { - 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(); - } - } + 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 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 + + 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: { + 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 + onClicked: { + 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/settings/ComboSetting.qml b/interface/resources/qml/overte/settings/ComboSetting.qml index e2745ca51d5..467aa35cda0 100644 --- a/interface/resources/qml/overte/settings/ComboSetting.qml +++ b/interface/resources/qml/overte/settings/ComboSetting.qml @@ -5,33 +5,33 @@ 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 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 + 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 + Overte.Label { + // equally sized items + Layout.preferredWidth: 1 + Layout.fillWidth: true - id: labelItem - wrapMode: Text.Wrap - } + id: labelItem + wrapMode: Text.Wrap + } - Overte.ComboBox { - // equally sized items - Layout.preferredWidth: 1 - Layout.fillWidth: true + Overte.ComboBox { + // equally sized items + Layout.preferredWidth: 1 + Layout.fillWidth: true - id: comboItem - } + id: comboItem + } } diff --git a/interface/resources/qml/overte/settings/FolderSetting.qml b/interface/resources/qml/overte/settings/FolderSetting.qml index 212ae2f50b3..e0f8c248efd 100644 --- a/interface/resources/qml/overte/settings/FolderSetting.qml +++ b/interface/resources/qml/overte/settings/FolderSetting.qml @@ -4,43 +4,43 @@ import QtQuick.Layouts import "../" as Overte ColumnLayout { - property alias text: labelItem.text - property alias value: textFieldItem.text + 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 + id: item + anchors.left: parent.left + anchors.right: parent.right + anchors.margins: 16 + spacing: 4 // prevent binding loops Component.onCompleted: value = value - Overte.Label { - Layout.alignment: Qt.AlignBottom - id: labelItem - wrapMode: Text.Wrap - } + Overte.Label { + Layout.alignment: Qt.AlignBottom + id: labelItem + wrapMode: Text.Wrap + } - RowLayout { - Layout.fillWidth: true + RowLayout { + Layout.fillWidth: true - Overte.TextField { - Layout.fillWidth: true + Overte.TextField { + Layout.fillWidth: true - id: textFieldItem + id: textFieldItem enabled: item.enabled - } + } Overte.RoundButton { enabled: item.enabled - icon.source: "../icons/folder.svg" - icon.width: 24 + icon.source: "../icons/folder.svg" + icon.width: 24 icon.height: 24 // TODO - } - } + } + } } diff --git a/interface/resources/qml/overte/settings/Header.qml b/interface/resources/qml/overte/settings/Header.qml index 8f526c0f1a7..e5e5e9901c7 100644 --- a/interface/resources/qml/overte/settings/Header.qml +++ b/interface/resources/qml/overte/settings/Header.qml @@ -4,24 +4,24 @@ import QtQuick.Layouts import "../" as Overte ColumnLayout { - property alias text: labelItem.text + 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 + 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.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 - } + 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 index 6c59009aad7..4844b6af065 100644 --- a/interface/resources/qml/overte/settings/SettingNote.qml +++ b/interface/resources/qml/overte/settings/SettingNote.qml @@ -4,10 +4,10 @@ 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 + 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 index cdf44822644..5edc6991d7c 100644 --- a/interface/resources/qml/overte/settings/Settings.qml +++ b/interface/resources/qml/overte/settings/Settings.qml @@ -7,39 +7,39 @@ import "." as OverteSettings import "./pages" as SettingsPages Rectangle { - id: root - 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 {} - } + id: root + 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 {} + } } diff --git a/interface/resources/qml/overte/settings/SettingsPage.qml b/interface/resources/qml/overte/settings/SettingsPage.qml index fa40b9088a6..9ce3826428c 100644 --- a/interface/resources/qml/overte/settings/SettingsPage.qml +++ b/interface/resources/qml/overte/settings/SettingsPage.qml @@ -4,20 +4,20 @@ import QtQuick.Controls import "../" as Overte ScrollView { - default property alias children: column.children + 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 + 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 - } + 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 index cf1e4caa79b..da70e463cfe 100644 --- a/interface/resources/qml/overte/settings/SliderSetting.qml +++ b/interface/resources/qml/overte/settings/SliderSetting.qml @@ -5,72 +5,72 @@ 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 {} + 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 index 314420c8708..afe9fb1e922 100644 --- a/interface/resources/qml/overte/settings/SpinBoxSetting.qml +++ b/interface/resources/qml/overte/settings/SpinBoxSetting.qml @@ -5,30 +5,30 @@ 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 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 + 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 + Overte.Label { + // equally sized items + Layout.preferredWidth: 1 + Layout.fillWidth: true - id: labelItem - wrapMode: Text.Wrap - } + id: labelItem + wrapMode: Text.Wrap + } - Overte.SpinBox { - Layout.alignment: Qt.AlignRight + Overte.SpinBox { + Layout.alignment: Qt.AlignRight - id: spinboxItem - } + id: spinboxItem + } } diff --git a/interface/resources/qml/overte/settings/SwitchSetting.qml b/interface/resources/qml/overte/settings/SwitchSetting.qml index bcbbdbde116..3d928cf80fa 100644 --- a/interface/resources/qml/overte/settings/SwitchSetting.qml +++ b/interface/resources/qml/overte/settings/SwitchSetting.qml @@ -4,25 +4,25 @@ import QtQuick.Layouts import "../" as Overte RowLayout { - property alias text: labelItem.text - property alias value: switchItem.checked - property bool enabled: true + 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 + id: item + anchors.left: parent.left + anchors.right: parent.right + anchors.margins: 16 + spacing: 16 - Overte.Label { - Layout.fillWidth: true + Overte.Label { + Layout.fillWidth: true - id: labelItem - wrapMode: Text.Wrap - } + id: labelItem + wrapMode: Text.Wrap + } - Overte.Switch { - id: switchItem - enabled: item.enabled - } + 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 index 0ba552fa7df..e7e6814535d 100644 --- a/interface/resources/qml/overte/settings/WideComboSetting.qml +++ b/interface/resources/qml/overte/settings/WideComboSetting.qml @@ -5,28 +5,28 @@ 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 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 + id: item + anchors.left: parent.left + anchors.right: parent.right + anchors.margins: 16 + spacing: 4 - Overte.Label { - Layout.fillWidth: true + Overte.Label { + Layout.fillWidth: true - id: labelItem - } + id: labelItem + } - Overte.ComboBox { - Layout.fillWidth: true + Overte.ComboBox { + Layout.fillWidth: true - id: comboItem - } + id: comboItem + } } diff --git a/interface/resources/qml/overte/settings/pages/Audio.qml b/interface/resources/qml/overte/settings/pages/Audio.qml index 37f6f634083..35a3c97f595 100644 --- a/interface/resources/qml/overte/settings/pages/Audio.qml +++ b/interface/resources/qml/overte/settings/pages/Audio.qml @@ -2,45 +2,45 @@ import "../../" as Overte import "../" SettingsPage { - SwitchSetting { - text: "Mute Microphone" + SwitchSetting { + text: "Mute Microphone" - value: AudioScriptingInterface.muted - onValueChanged: AudioScriptingInterface.muted = value - } + value: AudioScriptingInterface.muted + onValueChanged: AudioScriptingInterface.muted = value + } - SwitchSetting { - text: "Push-to-Talk" + SwitchSetting { + text: "Push-to-Talk" - value: AudioScriptingInterface.pushToTalk - onValueChanged: AudioScriptingInterface.pushToTalk = value - } + value: AudioScriptingInterface.pushToTalk + onValueChanged: AudioScriptingInterface.pushToTalk = value + } - SettingNote { - text: "Push [T] or squeeze both controller grips to talk." - } + SettingNote { + text: "Push [T] or squeeze both controller grips to talk." + } - Header { text: qsTr("Audio Devices") } + Header { text: qsTr("Audio Devices") } - WideComboSetting { - text: "Output Device" - model: AudioScriptingInterface.devices.output + WideComboSetting { + text: "Output Device" + model: AudioScriptingInterface.devices.output // TODO: how do these work??? //currentIndex: 0 //onCurrentIndexChanged: AudioScriptingInterface.setOutputDevice(currentIndex, false) - } + } - WideComboSetting { - text: "Input Device" - model: AudioScriptingInterface.devices.input + WideComboSetting { + text: "Input Device" + model: AudioScriptingInterface.devices.input // 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.") - } + 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 index 243e719dbad..71eff3c0d39 100644 --- a/interface/resources/qml/overte/settings/pages/Controls.qml +++ b/interface/resources/qml/overte/settings/pages/Controls.qml @@ -2,102 +2,102 @@ import "../../" as Overte import "../" SettingsPage { - Header { - text: qsTr("Desktop") + Header { + text: qsTr("Desktop") - // Hack to reduce the dead space on the top of the page - height: implicitHeight - } + // Hack to reduce the dead space on the top of the page + height: implicitHeight + } - SwitchSetting { + SwitchSetting { // FIXME: setting isn't exposed to script api enabled: false - text: qsTr("Invert Y") + text: qsTr("Invert Y") - // TODO - value: false - } + // TODO + value: false + } - SliderSetting { + SliderSetting { // FIXME: setting isn't exposed to script api enabled: false - text: qsTr("Mouse Sensitivity") - stepSize: 0.1 - from: 0.1 - to: 5.0 - valueToText: () => `${value.toFixed(1)}`; + text: qsTr("Mouse Sensitivity") + stepSize: 0.1 + from: 0.1 + to: 5.0 + valueToText: () => `${value.toFixed(1)}`; - // TODO - value: 1.0 - } + // TODO + value: 1.0 + } - Header { text: qsTr("VR User") } + Header { text: qsTr("VR User") } - SliderSetting { - text: qsTr("Height") - fineTweakButtons: true - stepSize: 0.01 - from: 0.8 - to: 2.5 + SliderSetting { + text: qsTr("Height") + fineTweakButtons: true + stepSize: 0.01 + from: 0.8 + to: 2.5 - valueToText: () => { - let meters = value.toFixed(2); - let totalInches = Math.round(value * 39.37008); + valueToText: () => { + let meters = value.toFixed(2); + let totalInches = Math.round(value * 39.37008); - let feet = Math.floor(totalInches / 12); - let inches = totalInches % 12; + let feet = Math.floor(totalInches / 12); + let inches = totalInches % 12; - return `${feet}'${inches}" ${meters.toLocaleString()}m`; - } + return `${feet}'${inches}" ${meters.toLocaleString()}m`; + } // FIXME: QML complains about userHeight not being bindable - value: { value = MyAvatar.userHeight } + value: { value = MyAvatar.userHeight } onValueChanged: MyAvatar.userHeight = value - } + } - ComboSetting { - text: qsTr("Dominant Hand") - model: [ - qsTr("Left"), - qsTr("Right"), - ] + ComboSetting { + text: qsTr("Dominant Hand") + model: [ + qsTr("Left"), + qsTr("Right"), + ] currentIndex: MyAvatar.getDominantHand() === "left" ? 0 : 1 onCurrentIndexChanged: MyAvatar.setDominantHand(currentIndex === 0 ? "left" : "right") - } + } - Header { text: qsTr("VR Movement") } + Header { text: qsTr("VR Movement") } - SliderSetting { + SliderSetting { // FIXME: setting isn't exposed to script api enabled: false - text: qsTr("Turning Speed") - stepSize: 10 - from: 0 - to: 400 - valueToText: () => value < 50 ? qsTr("Snap turning") : `${value.toFixed(1)}`; + text: qsTr("Turning Speed") + stepSize: 10 + from: 0 + to: 400 + valueToText: () => value < 50 ? qsTr("Snap turning") : `${value.toFixed(1)}`; // FIXME: not exposed to scripts or QML - /*value: MyAvatar.HMDYawSpeed + /*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.toFixed(1)} m/s`; - //enabled: !useAvatarDefaultWalkingSpeed.value - - value: MyAvatar.vrWalkSpeed + } + + SliderSetting { + id: walkingSpeed + text: qsTr("Walking Speed") + stepSize: 0.5 + from: 1.0 + to: 9 + valueToText: () => value < 1.5 ? qsTr("Teleport Only") : `${value.toFixed(1)} m/s`; + //enabled: !useAvatarDefaultWalkingSpeed.value + + value: MyAvatar.vrWalkSpeed onValueChanged: { if (value === 0.0) { MyAvatar.useAdvancedMovementControls = false; @@ -106,99 +106,99 @@ SettingsPage { MyAvatar.vrWalkSpeed = value; } } - } + } - ComboSetting { - text: qsTr("Movement Relative To") - enabled: walkingSpeed.value >= 1.5 - model: [ - qsTr("Head"), - qsTr("Hand"), - ] + ComboSetting { + text: qsTr("Movement Relative To") + enabled: walkingSpeed.value >= 1.5 + model: [ + qsTr("Head"), + qsTr("Hand"), + ] - // TODO - } + // TODO + } - /*SwitchSetting { - id: useAvatarDefaultWalkingSpeed - text: qsTr("Use equipped avatar's default walking speed if available") + /*SwitchSetting { + id: useAvatarDefaultWalkingSpeed + text: qsTr("Use equipped avatar's default walking speed if available") - // TODO - value: false + // TODO + value: false }*/ - Header { text: qsTr("VR UI") } + Header { text: qsTr("VR UI") } - ComboSetting { + ComboSetting { // FIXME: setting isn't exposed to script api enabled: false - text: qsTr("Tablet Input") - model: [ - qsTr("Laser"), - qsTr("Stylus"), - qsTr("Finger Touch"), - ] + text: qsTr("Tablet Input") + model: [ + qsTr("Laser"), + qsTr("Stylus"), + qsTr("Finger Touch"), + ] - // TODO - } + // TODO + } - ComboSetting { + ComboSetting { // FIXME: setting isn't exposed to script api enabled: false - text: qsTr("Virtual Keyboard Input") - model: [ - qsTr("Lasers"), - qsTr("Mallets"), - ] + text: qsTr("Virtual Keyboard Input") + model: [ + qsTr("Lasers"), + qsTr("Mallets"), + ] - // TODO - } + // TODO + } - SliderSetting { + SliderSetting { // FIXME: setting isn't exposed to script api enabled: false - text: qsTr("Laser Smoothing Delay") - stepSize: 0.1 - from: 0.0 - to: 2.0 - valueToText: () => `${value.toFixed(1)}s`; - - // TODO - value: 0.3 - } - - // 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.") - } - */ + text: qsTr("Laser Smoothing Delay") + stepSize: 0.1 + from: 0.0 + to: 2.0 + valueToText: () => `${value.toFixed(1)}s`; + + // TODO + value: 0.3 + } + + // 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 index ad87e137011..a0ca353c0c8 100644 --- a/interface/resources/qml/overte/settings/pages/General.qml +++ b/interface/resources/qml/overte/settings/pages/General.qml @@ -2,144 +2,144 @@ import "../../" as Overte import "../" SettingsPage { - 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"), - ] - - onCurrentIndexChanged: { - switch (currentIndex) { - case 0: - Overte.useSystemColorScheme = false; - Overte.Theme.darkMode = true; - break; - - case 1: - Overte.useSystemColorScheme = false; - Overte.Theme.darkMode = false; - break; - - case 2: - Overte.useSystemColorScheme = true; - Overte.Theme.darkMode = true; - break; - } - } - } - - ComboSetting { - text: qsTr("Contrast") - model: [ - qsTr("Standard"), - qsTr("High"), - qsTr("System"), - ] - - onCurrentIndexChanged: { - switch (currentIndex) { - case 0: - Overte.useSystemContrastMode = false; - Overte.Theme.highContrast = false; - break; - - case 1: - Overte.useSystemContrastMode = false; - Overte.Theme.highContrast = true; - break; - - case 2: - Overte.useSystemContrastMode = true; - Overte.Theme.highContrast = false; - break; - } - } - } - - SwitchSetting { - text: qsTr("Reduced Motion") - value: Overte.Theme.reducedMotion - onValueChanged: () => Overte.Theme.reducedMotion = value - } - - SettingNote { - text: qsTr("This setting will disable UI animations that slide or scale.") - } - - Header { text: qsTr("Screenshots") } - - FolderSetting { + 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"), + ] + + onCurrentIndexChanged: { + switch (currentIndex) { + case 0: + Overte.useSystemColorScheme = false; + Overte.Theme.darkMode = true; + break; + + case 1: + Overte.useSystemColorScheme = false; + Overte.Theme.darkMode = false; + break; + + case 2: + Overte.useSystemColorScheme = true; + Overte.Theme.darkMode = true; + break; + } + } + } + + ComboSetting { + text: qsTr("Contrast") + model: [ + qsTr("Standard"), + qsTr("High"), + qsTr("System"), + ] + + onCurrentIndexChanged: { + switch (currentIndex) { + case 0: + Overte.useSystemContrastMode = false; + Overte.Theme.highContrast = false; + break; + + case 1: + Overte.useSystemContrastMode = false; + Overte.Theme.highContrast = true; + break; + + case 2: + Overte.useSystemContrastMode = true; + Overte.Theme.highContrast = false; + break; + } + } + } + + SwitchSetting { + text: qsTr("Reduced Motion") + value: Overte.Theme.reducedMotion + onValueChanged: () => Overte.Theme.reducedMotion = value + } + + SettingNote { + text: qsTr("This setting will disable UI animations that slide or scale.") + } + + Header { text: qsTr("Screenshots") } + + FolderSetting { // TODO enabled: false - text: qsTr("Folder") - // TODO - value: Snapshot.getSnapshotsLocation() - onValueChanged: Snapshot.setSnapshotsLocation(value) - } - - ComboSetting { - text: qsTr("Format") - model: [ - "png", - "jpg", - "webp", - ] + text: qsTr("Folder") + // TODO + value: Snapshot.getSnapshotsLocation() + onValueChanged: Snapshot.setSnapshotsLocation(value) + } + + ComboSetting { + text: qsTr("Format") + model: [ + "png", + "jpg", + "webp", + ] onCurrentIndexChanged: Snapshot.setSnapshotFormat(model[currentIndex]) - } + } - SpinBoxSetting { + SpinBoxSetting { // FIXME: setting isn't exposed to script api enabled: false - text: qsTr("Animation Duration") - from: 1 - to: 30 + text: qsTr("Animation Duration") + from: 1 + to: 30 - // TODO - value: 3 + // TODO + value: 3 } - SettingNote { - text: "Animated screenshots are saved as GIFs." - } + SettingNote { + text: "Animated screenshots are saved as GIFs." + } - Header { text: qsTr("Privacy") } + Header { text: qsTr("Privacy") } - SwitchSetting { + SwitchSetting { // FIXME: setting isn't exposed to script api enabled: false - text: qsTr("Send Crash Reports") - // TODO - value: false - onValueChanged: () => {} - } + text: qsTr("Send Crash Reports") + // TODO + value: false + onValueChanged: () => {} + } - SettingNote { - text: qsTr("Sending crash reports helps Overte development.") - } + SettingNote { + text: qsTr("Sending crash reports helps Overte development.") + } - SwitchSetting { + SwitchSetting { // FIXME: setting isn't exposed to script api enabled: false - text: qsTr("Discord Rich Presence") - // TODO - value: false - onValueChanged: () => {} - } + text: qsTr("Discord Rich Presence") + // TODO + value: false + onValueChanged: () => {} + } - SettingNote { - text: qsTr("If this setting is enabled, your current world will be shown on your Discord profile.") - } + SettingNote { + text: qsTr("If this setting is enabled, your current world will be shown on your Discord profile.") + } } diff --git a/interface/resources/qml/overte/settings/pages/Graphics.qml b/interface/resources/qml/overte/settings/pages/Graphics.qml index cef1a34ea2a..1e46504b5ae 100644 --- a/interface/resources/qml/overte/settings/pages/Graphics.qml +++ b/interface/resources/qml/overte/settings/pages/Graphics.qml @@ -2,101 +2,101 @@ import "../../" as Overte import "../" SettingsPage { - SliderSetting { - text: qsTr("Field of View") - stepSize: 5 - from: 20 - to: 130 - valueToText: () => `${value}°`; + 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}%`; + SliderSetting { + text: qsTr("Resolution Scale") + stepSize: 10 + from: 10 + to: 200 + valueToText: () => `${value}%`; value: Render.viewportResolutionScale * 100 onValueChanged: Render.viewportResolutionScale = value / 100 - } - - ComboSetting { - text: qsTr("Anti-Aliasing") - textRole: "text" - valueRole: "mode" - - model: [ - { text: qsTr("None"), mode: "none" }, - { text: qsTr("4x MSAA"), mode: "msaa" }, - { text: qsTr("TAA"), mode: "taa" }, - { text: qsTr("FXAA"), mode: "fxaa" }, - ] - - // TODO - } - - ComboSetting { - text: qsTr("LOD Culling") - textRole: "text" - valueRole: "mode" - - model: [ - { text: qsTr("High Detail"), mode: 0 }, - { text: qsTr("Medium Detail"), mode: 1 }, - { text: qsTr("Low Detail"), mode: 2 }, - ] + } + + ComboSetting { + text: qsTr("Anti-Aliasing") + textRole: "text" + valueRole: "mode" + + model: [ + { text: qsTr("None"), mode: "none" }, + { text: qsTr("4x MSAA"), mode: "msaa" }, + { text: qsTr("TAA"), mode: "taa" }, + { text: qsTr("FXAA"), mode: "fxaa" }, + ] + + // TODO + } + + ComboSetting { + text: qsTr("LOD Culling") + textRole: "text" + valueRole: "mode" + + model: [ + { text: qsTr("High Detail"), mode: 0 }, + { text: qsTr("Medium Detail"), mode: 1 }, + { text: qsTr("Low Detail"), mode: 2 }, + ] currentIndex: { currentIndex = LODManager.worldDetailQuality } onCurrentIndexChanged: LODManager.worldDetailQuality = model[currentIndex].mode - } + } - SwitchSetting { - text: qsTr("Custom Shaders") + SwitchSetting { + text: qsTr("Custom Shaders") value: Render.proceduralMaterialsEnabled onValueChanged: Render.proceduralMaterialsEnabled = value - } + } - Header { text: qsTr("Advanced") } + Header { text: qsTr("Advanced") } - SettingNote { - text: qsTr("These settings are incompatible with MSAA and may reduce performance.") - } + SettingNote { + text: qsTr("These settings are incompatible with MSAA and may reduce performance.") + } - SwitchSetting { - id: advRenderingEnabled - text: qsTr("Rendering Effects") + SwitchSetting { + id: advRenderingEnabled + text: qsTr("Rendering Effects") value: Render.renderMethod === 0 onValueChanged: Render.renderMethod = value ? 0 : 1 - } + } - SwitchSetting { - enabled: advRenderingEnabled.value - text: qsTr("Shadows") + SwitchSetting { + enabled: advRenderingEnabled.value + text: qsTr("Shadows") - value: Render.shadowsEnabled + value: Render.shadowsEnabled onValueChanged: Render.shadowsEnabled = value - } + } - SwitchSetting { - enabled: advRenderingEnabled.value - text: qsTr("Bloom") + SwitchSetting { + enabled: advRenderingEnabled.value + text: qsTr("Bloom") - value: Render.bloomEnabled + value: Render.bloomEnabled onValueChanged: Render.bloomEnabled = value - } + } - SwitchSetting { - enabled: advRenderingEnabled.value - text: qsTr("Ambient Occlusion") + SwitchSetting { + enabled: advRenderingEnabled.value + text: qsTr("Ambient Occlusion") - value: Render.ambientOcclusionEnabled + value: Render.ambientOcclusionEnabled onValueChanged: Render.ambientOcclusionEnabled = value - } + } } diff --git a/interface/resources/qml/overte/staging/MediaPlayer.qml b/interface/resources/qml/overte/staging/MediaPlayer.qml index bb3e89d423c..0e4a6c39a14 100644 --- a/interface/resources/qml/overte/staging/MediaPlayer.qml +++ b/interface/resources/qml/overte/staging/MediaPlayer.qml @@ -5,382 +5,382 @@ 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; - } - } - } - } - } + 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 index 061600f1207..3c8a07ba543 100644 --- a/interface/resources/qml/overte/staging/Node.qml +++ b/interface/resources/qml/overte/staging/Node.qml @@ -5,177 +5,177 @@ 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" - } - } - } + 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 index cd6c287f0b1..974fdcbff60 100644 --- a/interface/resources/qml/overte/staging/NodeGraph.qml +++ b/interface/resources/qml/overte/staging/NodeGraph.qml @@ -4,26 +4,26 @@ import ".." as Overte import "." Rectangle { - id: nodeGraph - color: Qt.darker(Overte.Theme.paletteActive.base, 1.1) + id: nodeGraph + color: Qt.darker(Overte.Theme.paletteActive.base, 1.1) - Overte.Switch { - onToggled: Overte.Theme.darkMode = checked - } + Overte.Switch { + onToggled: Overte.Theme.darkMode = checked + } - Node { - x: 64 - y: 64 + 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" }, - ] - } + 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 index 001ba7d73d4..d80277d3593 100644 --- a/interface/resources/qml/overte/staging/NodePlug.qml +++ b/interface/resources/qml/overte/staging/NodePlug.qml @@ -3,50 +3,50 @@ import QtQuick import ".." as Overte Rectangle { - property bool active: false + property bool active: false - implicitWidth: 24 - implicitHeight: 24 + implicitWidth: 24 + implicitHeight: 24 - id: plug - radius: height / 2 - border.width: Overte.Theme.borderWidth - border.color: Qt.darker(color, Overte.Theme.borderDarker) - color: "#a000a0" + 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) - } - } + 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 + 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) - } - } - } + 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/desktop/AppToolbar.qml b/interface/resources/qml/overte/staging/desktop/AppToolbar.qml index 9e6e3552ab8..07a0b1b5d3e 100644 --- a/interface/resources/qml/overte/staging/desktop/AppToolbar.qml +++ b/interface/resources/qml/overte/staging/desktop/AppToolbar.qml @@ -4,79 +4,79 @@ import QtQuick.Controls import ".." as Overte Item { - readonly property int buttonSize: 80 + 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 + 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 - } + Rectangle { + anchors.fill: root + radius: Overte.Theme.borderRadius + color: Overte.Theme.paletteActive.dialogShade + } - ListView { - anchors.fill: parent - anchors.margins: 4 + ListView { + anchors.fill: parent + anchors.margins: 4 - id: buttonList - orientation: Qt.Horizontal - spacing: 4 - clip: true + id: buttonList + orientation: Qt.Horizontal + spacing: 4 + clip: true - ScrollBar.horizontal: Overte.ScrollBar { - policy: ScrollBar.AsNeeded - } + 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}, + 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 + {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 + implicitWidth: buttonSize + implicitHeight: buttonSize - Image { - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right - anchors.bottom: buttonLabel.top - anchors.margins: 2 + 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 + source: iconSource + sourceSize.width: width + sourceSize.height: height - fillMode: Image.PreserveAspectFit - } + 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 + 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 - } - } - } + 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 index 38ec168a1d3..00e52b1085d 100644 --- a/interface/resources/qml/overte/staging/desktop/Desktop.qml +++ b/interface/resources/qml/overte/staging/desktop/Desktop.qml @@ -5,66 +5,66 @@ import "." //Item { Rectangle { - color: "#303030" + color: "#303030" - anchors.fill: parent - implicitWidth: 800 - implicitHeight: 600 + anchors.fill: parent + implicitWidth: 800 + implicitHeight: 600 - id: desktopRoot + id: desktopRoot - Item { - id: toolbarContainer - anchors.horizontalCenter: desktopRoot.horizontalCenter - anchors.bottom: toolbarToggleButton.top - anchors.margins: 4 - height: appToolbar.height + 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 + 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 - } - } + states: State { + name: "open" + when: toolbarToggleButton.checked + PropertyChanges { + target: appToolbar + y: 0 + opacity: 1.0 + } + } - transitions: Transition { - reversible: true + transitions: Transition { + reversible: true - NumberAnimation { - properties: "y" - easing.type: Easing.OutExpo - duration: 500 - } + NumberAnimation { + properties: "y" + easing.type: Easing.OutExpo + duration: 500 + } - NumberAnimation { - properties: "opacity" - 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 + 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 - } + 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/keyboard/Keyboard.qml b/interface/resources/qml/overte/staging/keyboard/Keyboard.qml index 424189a9a12..ce92428a7d7 100644 --- a/interface/resources/qml/overte/staging/keyboard/Keyboard.qml +++ b/interface/resources/qml/overte/staging/keyboard/Keyboard.qml @@ -5,188 +5,188 @@ 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: "🗙" - } + 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 index c899c02dfad..9aaa7e7b6bb 100644 --- a/interface/resources/qml/overte/staging/keyboard/KeyboardKey.qml +++ b/interface/resources/qml/overte/staging/keyboard/KeyboardKey.qml @@ -3,80 +3,80 @@ import QtQuick import "../" Button { - property int unshifted_keycode: 0 - property int shifted_keycode: 0 + property int unshifted_keycode: 0 + property int shifted_keycode: 0 - property string unshifted_char: "" - property string shifted_char: "" + property string unshifted_char: "" + property string shifted_char: "" - property string legend: "" - property real span: 1.0 + property string legend: "" + property real span: 1.0 - id: control - text: legend - focusPolicy: Qt.NoFocus - horizontalPadding: 4 - verticalPadding: 4 + id: control + text: legend + focusPolicy: Qt.NoFocus + horizontalPadding: 4 + verticalPadding: 4 - onPressed: keyboardRoot.keyPressed(this) - onReleased: keyboardRoot.keyReleased(this) + onPressed: keyboardRoot.keyPressed(this) + onReleased: keyboardRoot.keyReleased(this) - Connections { - target: keyboardRoot + Connections { + target: keyboardRoot - function onModifiersChanged() { - const value = keyboardRoot.modifiers; + function onModifiersChanged() { + const value = keyboardRoot.modifiers; - switch (unshifted_keycode) { - case Qt.Key_Shift: - checked = (value & Qt.ShiftModifier) !== 0; - break; + 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_Control: + checked = (value & Qt.ControlModifier) !== 0; + break; - case Qt.Key_Alt: - checked = (value & Qt.AltModifier) !== 0; - break; + case Qt.Key_Alt: + checked = (value & Qt.AltModifier) !== 0; + break; - case Qt.Key_Meta: - checked = (value & Qt.MetaModifier) !== 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 - } + 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) - } - } - } + 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/login/LoginScreen.qml b/interface/resources/qml/overte/staging/login/LoginScreen.qml index 4a1adb9e48e..550126e7528 100644 --- a/interface/resources/qml/overte/staging/login/LoginScreen.qml +++ b/interface/resources/qml/overte/staging/login/LoginScreen.qml @@ -7,46 +7,46 @@ 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 {} - } - } + 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/pages/LoginPage.qml b/interface/resources/qml/overte/staging/login/pages/LoginPage.qml index d7c538f5bde..a22dc08c53a 100644 --- a/interface/resources/qml/overte/staging/login/pages/LoginPage.qml +++ b/interface/resources/qml/overte/staging/login/pages/LoginPage.qml @@ -5,133 +5,133 @@ 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); - } - } - } - } + // 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 index 8c0d15f37ab..2dfd8e4e5fd 100644 --- a/interface/resources/qml/overte/staging/login/pages/ProgressPage.qml +++ b/interface/resources/qml/overte/staging/login/pages/ProgressPage.qml @@ -5,79 +5,79 @@ 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" + 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; - } + 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."); - } - } + // 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 + ColumnLayout { + anchors.fill: parent + anchors.margins: 32 + spacing: Overte.Theme.fontPixelSize * 2 - Overte.Label { - Layout.fillWidth: true - Layout.fillHeight: true + Overte.Label { + Layout.fillWidth: true + Layout.fillHeight: true - id: statusText - text: mode === "register" ? qsTr("Registering…") : qsTr("Logging in…") - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - } + 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 + RowLayout { + spacing: 16 + Layout.minimumHeight: Overte.Theme.fontPixelSize * 3 + Layout.maximumHeight: Overte.Theme.fontPixelSize * 3 - Overte.Button { - id: backButton - visible: false + Overte.Button { + id: backButton + visible: false - // make the buttons equal size - Layout.preferredWidth: 1 - Layout.preferredHeight: 1 - Layout.fillWidth: true - Layout.fillHeight: true + // 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 + 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(); - } - } + onClicked: { + stack.pop(); + } + } - Item { - // make the buttons equal size - Layout.preferredWidth: 1 - Layout.preferredHeight: 1 - Layout.fillWidth: true - Layout.fillHeight: true - } - } - } + 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 index efa2d890092..250aa3d9c08 100644 --- a/interface/resources/qml/overte/staging/login/pages/RegisterPage.qml +++ b/interface/resources/qml/overte/staging/login/pages/RegisterPage.qml @@ -5,133 +5,133 @@ 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); - } - } - } - } + // 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 index 855ac6ed603..9ab8c4b8624 100644 --- a/interface/resources/qml/overte/staging/login/pages/StartPage.qml +++ b/interface/resources/qml/overte/staging/login/pages/StartPage.qml @@ -5,65 +5,65 @@ import QtQuick.Layouts import "../../" as Overte Item { - readonly property url codeOfConduct: "https://overte.org/code_of_conduct.html" + 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 + 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 + 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 - } + 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 + RowLayout { + Layout.fillWidth: true + Layout.alignment: Qt.AlignHCenter + spacing: 16 - Overte.Button { - Layout.alignment: Qt.AlignCenter - Layout.fillWidth: true + Overte.Button { + Layout.alignment: Qt.AlignCenter + Layout.fillWidth: true - // force equally sized buttons - Layout.preferredWidth: 1 + // force equally sized buttons + Layout.preferredWidth: 1 - text: qsTr("Register") + text: qsTr("Register") - onClicked: { - stack.push("./RegisterPage.qml"); - } - } + onClicked: { + stack.push("./RegisterPage.qml"); + } + } - Overte.Button { - Layout.alignment: Qt.AlignCenter - Layout.fillWidth: true + Overte.Button { + Layout.alignment: Qt.AlignCenter + Layout.fillWidth: true - // force equally sized buttons - Layout.preferredWidth: 1 + // force equally sized buttons + Layout.preferredWidth: 1 - text: qsTr("Log in") + text: qsTr("Log in") - onClicked: { - stack.push("./LoginPage.qml"); - } - } - } + onClicked: { + stack.push("./LoginPage.qml"); + } + } + } - Overte.Button { - Layout.alignment: Qt.AlignCenter - Layout.fillWidth: true + Overte.Button { + Layout.alignment: Qt.AlignCenter + Layout.fillWidth: true - text: qsTr("Code of Conduct") + text: qsTr("Code of Conduct") - onClicked: () => Qt.openUrlExternally(codeOfConduct) - } - } + onClicked: () => Qt.openUrlExternally(codeOfConduct) + } + } } diff --git a/interface/resources/qml/overte/staging/place_picker/PlaceItem.qml b/interface/resources/qml/overte/staging/place_picker/PlaceItem.qml index 78844a614aa..807d5c4e03c 100644 --- a/interface/resources/qml/overte/staging/place_picker/PlaceItem.qml +++ b/interface/resources/qml/overte/staging/place_picker/PlaceItem.qml @@ -4,146 +4,146 @@ import QtQuick.Layouts import ".." as Overte Rectangle { - id: item - color: index % 2 === 0 ? Overte.Theme.paletteActive.base : Overte.Theme.paletteActive.alternateBase - - required property int index - - required property string name - property string description: listView.model[index].description ?? "" - property url thumbnail: listView.model[index].thumbnail ?? "" - - property int currentUsers: listView.model[index].current_attendance ?? 0 - property int maxUsers: { - const capacity = listView.model[index].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 ? "#80000000" : "#80ffffff" - } - } - - anchors.left: parent ? parent.left : undefined - anchors.right: parent ? parent.right : undefined - anchors.leftMargin: 4 - anchors.rightMargin: Overte.Theme.scrollbarWidth - - implicitHeight: 128 - - Component.onCompleted: { - // Hide redundant default descriptions that don't say anything - if (description === `A place in ${name}`) { - description = ""; - } - } - - Image { - anchors.fill: parent - fillMode: Image.PreserveAspectCrop - visible: !Overte.Theme.highContrast - source: thumbnail - } - - ColumnLayout { - anchors.left: item.left - anchors.top: item.top - anchors.bottom: item.bottom - anchors.right: controls.left - anchors.margins: 4 - spacing: 8 - - Rectangle { - Layout.alignment: Qt.AlignLeft | Qt.AlignBottom - Layout.maximumWidth: parent.width - implicitWidth: titleText.implicitWidth + 16 - implicitHeight: titleText.implicitHeight + 16 - color: textBackgroundColor - radius: Overte.Theme.borderRadius - - Overte.Label { - anchors.margins: 8 - anchors.fill: parent - - id: titleText - text: name - horizontalAlignment: Text.AlignLeft - verticalAlignment: Text.AlignTop - style: Text.Outline - styleColor: Overte.Theme.darkMode ? "black" : "white" - } - } - - Item { Layout.fillHeight: true } - - Rectangle { - visible: description !== "" - - Layout.alignment: Qt.AlignLeft | Qt.AlignBottom - Layout.maximumWidth: parent.width - implicitWidth: descriptionText.implicitWidth + 16 - implicitHeight: descriptionText.implicitHeight + 16 - color: textBackgroundColor - radius: Overte.Theme.borderRadius - - Overte.Label { - anchors.margins: 8 - anchors.fill: parent - - id: descriptionText - text: description - horizontalAlignment: Text.AlignLeft - verticalAlignment: Text.AlignTop - style: Text.Outline - styleColor: Overte.Theme.darkMode ? "black" : "white" - wrapMode: Text.Wrap - elide: Text.ElideRight - font.pixelSize: Overte.Theme.fontPixelSizeSmall - } - } - } - - ColumnLayout { - id: controls - anchors.top: item.top - anchors.bottom: item.bottom - anchors.right: item.right - anchors.margins: 8 - spacing: 4 - - Overte.RoundButton { - Layout.alignment: Qt.AlignCenter - text: ">" - backgroundColor: Overte.Theme.paletteActive.buttonAdd - } - - Overte.RoundButton { - Layout.alignment: Qt.AlignCenter - text: "P" - backgroundColor: Overte.Theme.paletteActive.buttonInfo - } - - Item { Layout.fillHeight: true } - - Rectangle { - Layout.alignment: Qt.AlignCenter - implicitWidth: 12 - implicitHeight: 12 - radius: width - border.width: Overte.Theme.borderWidth - border.color: Qt.darker(Overte.Theme.paletteActive.base, Overte.Theme.borderDarker) - color: { - if (currentUsers === 0) { - return "#808080"; - } else if (currentUsers < maxUsers) { - return "#00ff00"; - } else { - return "#ff0000"; - } - } - } - } + id: item + color: index % 2 === 0 ? Overte.Theme.paletteActive.base : Overte.Theme.paletteActive.alternateBase + + required property int index + + required property string name + property string description: listView.model[index].description ?? "" + property url thumbnail: listView.model[index].thumbnail ?? "" + + property int currentUsers: listView.model[index].current_attendance ?? 0 + property int maxUsers: { + const capacity = listView.model[index].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 ? "#80000000" : "#80ffffff" + } + } + + anchors.left: parent ? parent.left : undefined + anchors.right: parent ? parent.right : undefined + anchors.leftMargin: 4 + anchors.rightMargin: Overte.Theme.scrollbarWidth + + implicitHeight: 128 + + Component.onCompleted: { + // Hide redundant default descriptions that don't say anything + if (description === `A place in ${name}`) { + description = ""; + } + } + + Image { + anchors.fill: parent + fillMode: Image.PreserveAspectCrop + visible: !Overte.Theme.highContrast + source: thumbnail + } + + ColumnLayout { + anchors.left: item.left + anchors.top: item.top + anchors.bottom: item.bottom + anchors.right: controls.left + anchors.margins: 4 + spacing: 8 + + Rectangle { + Layout.alignment: Qt.AlignLeft | Qt.AlignBottom + Layout.maximumWidth: parent.width + implicitWidth: titleText.implicitWidth + 16 + implicitHeight: titleText.implicitHeight + 16 + color: textBackgroundColor + radius: Overte.Theme.borderRadius + + Overte.Label { + anchors.margins: 8 + anchors.fill: parent + + id: titleText + text: name + horizontalAlignment: Text.AlignLeft + verticalAlignment: Text.AlignTop + style: Text.Outline + styleColor: Overte.Theme.darkMode ? "black" : "white" + } + } + + Item { Layout.fillHeight: true } + + Rectangle { + visible: description !== "" + + Layout.alignment: Qt.AlignLeft | Qt.AlignBottom + Layout.maximumWidth: parent.width + implicitWidth: descriptionText.implicitWidth + 16 + implicitHeight: descriptionText.implicitHeight + 16 + color: textBackgroundColor + radius: Overte.Theme.borderRadius + + Overte.Label { + anchors.margins: 8 + anchors.fill: parent + + id: descriptionText + text: description + horizontalAlignment: Text.AlignLeft + verticalAlignment: Text.AlignTop + style: Text.Outline + styleColor: Overte.Theme.darkMode ? "black" : "white" + wrapMode: Text.Wrap + elide: Text.ElideRight + font.pixelSize: Overte.Theme.fontPixelSizeSmall + } + } + } + + ColumnLayout { + id: controls + anchors.top: item.top + anchors.bottom: item.bottom + anchors.right: item.right + anchors.margins: 8 + spacing: 4 + + Overte.RoundButton { + Layout.alignment: Qt.AlignCenter + text: ">" + backgroundColor: Overte.Theme.paletteActive.buttonAdd + } + + Overte.RoundButton { + Layout.alignment: Qt.AlignCenter + text: "P" + backgroundColor: Overte.Theme.paletteActive.buttonInfo + } + + Item { Layout.fillHeight: true } + + Rectangle { + Layout.alignment: Qt.AlignCenter + implicitWidth: 12 + implicitHeight: 12 + radius: width + border.width: Overte.Theme.borderWidth + border.color: Qt.darker(Overte.Theme.paletteActive.base, Overte.Theme.borderDarker) + color: { + if (currentUsers === 0) { + return "#808080"; + } else if (currentUsers < maxUsers) { + return "#00ff00"; + } else { + return "#ff0000"; + } + } + } + } } diff --git a/interface/resources/qml/overte/staging/place_picker/PlacePicker.qml b/interface/resources/qml/overte/staging/place_picker/PlacePicker.qml index c6b1f702afe..35b5859f0e8 100644 --- a/interface/resources/qml/overte/staging/place_picker/PlacePicker.qml +++ b/interface/resources/qml/overte/staging/place_picker/PlacePicker.qml @@ -7,129 +7,129 @@ import "../" as Overte import "." Rectangle { - id: root - anchors.fill: parent - color: Overte.Theme.paletteActive.base - implicitWidth: 480 - implicitHeight: 720 - - readonly property string protocolSignature: "6xYA55jcXgPHValo3Ba3/A==" - - Component.onCompleted: { - let xhr = new XMLHttpRequest(); - - xhr.onreadystatechange = () => { - if (xhr.readyState === XMLHttpRequest.DONE) { - console.debug("Finished downloading place list"); - try { - const body = JSON.parse(xhr.responseText); - - let accum = []; - for (const place of body.data.places) { - if ( - place.domain.protocol_version === protocolSignature - ) { - accum.push(place); - } - } - - listView.model = accum; - - console.debug("Finished parsing place list"); - } catch (e) {} - } - }; - - console.debug("Downloading place list…"); - xhr.open("GET", "https://mv.overte.org/server/api/v1/places"); - xhr.send(); - } - - ColumnLayout { - anchors.fill: parent - - RowLayout { - Layout.margins: 4 - - Overte.RoundButton { - id: settingsButton - icon.source: "../icons/settings_cog.svg" - icon.width: 24 - icon.height: 24 - icon.color: Overte.Theme.paletteActive.buttonText - - onClicked: print("TODO") - } - - 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; - } - } - } - - Overte.TabBar { - Layout.fillWidth: true - id: tabBar - - Overte.TabButton { text: qsTr("Public") } - Overte.TabButton { text: qsTr("Bookmarks") } - } - - Overte.Label { - Layout.fillWidth: true - Layout.fillHeight: true - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - visible: listView.model.length === 0 - text: qsTr("Loading…") - } - - StackLayout { - Layout.fillWidth: true - Layout.fillHeight: true - - ListView { - id: listView - visible: model.length !== 0 - spacing: 2 - clip: true - - ScrollBar.vertical: Overte.ScrollBar {} - - model: [] - delegate: PlaceItem {} - } - } - - Overte.Label { - Layout.margins: 8 - Layout.fillWidth: true - visible: listView.model.length !== 0 - verticalAlignment: Text.AlignVCenter - text: qsTr("%1 place(s)").arg(listView.model.length) - } - } + id: root + anchors.fill: parent + color: Overte.Theme.paletteActive.base + implicitWidth: 480 + implicitHeight: 720 + + readonly property string protocolSignature: "6xYA55jcXgPHValo3Ba3/A==" + + Component.onCompleted: { + let xhr = new XMLHttpRequest(); + + xhr.onreadystatechange = () => { + if (xhr.readyState === XMLHttpRequest.DONE) { + console.debug("Finished downloading place list"); + try { + const body = JSON.parse(xhr.responseText); + + let accum = []; + for (const place of body.data.places) { + if ( + place.domain.protocol_version === protocolSignature + ) { + accum.push(place); + } + } + + listView.model = accum; + + console.debug("Finished parsing place list"); + } catch (e) {} + } + }; + + console.debug("Downloading place list…"); + xhr.open("GET", "https://mv.overte.org/server/api/v1/places"); + xhr.send(); + } + + ColumnLayout { + anchors.fill: parent + + RowLayout { + Layout.margins: 4 + + Overte.RoundButton { + id: settingsButton + icon.source: "../icons/settings_cog.svg" + icon.width: 24 + icon.height: 24 + icon.color: Overte.Theme.paletteActive.buttonText + + onClicked: print("TODO") + } + + 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; + } + } + } + + Overte.TabBar { + Layout.fillWidth: true + id: tabBar + + Overte.TabButton { text: qsTr("Public") } + Overte.TabButton { text: qsTr("Bookmarks") } + } + + Overte.Label { + Layout.fillWidth: true + Layout.fillHeight: true + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + visible: listView.model.length === 0 + text: qsTr("Loading…") + } + + StackLayout { + Layout.fillWidth: true + Layout.fillHeight: true + + ListView { + id: listView + visible: model.length !== 0 + spacing: 2 + clip: true + + ScrollBar.vertical: Overte.ScrollBar {} + + model: [] + delegate: PlaceItem {} + } + } + + Overte.Label { + Layout.margins: 8 + Layout.fillWidth: true + visible: listView.model.length !== 0 + verticalAlignment: Text.AlignVCenter + text: qsTr("%1 place(s)").arg(listView.model.length) + } + } } diff --git a/interface/resources/qml/overte/staging/tutorial/AvatarPicker.qml b/interface/resources/qml/overte/staging/tutorial/AvatarPicker.qml index 26aef6ba25b..0702431e715 100644 --- a/interface/resources/qml/overte/staging/tutorial/AvatarPicker.qml +++ b/interface/resources/qml/overte/staging/tutorial/AvatarPicker.qml @@ -1,6 +1,6 @@ import "../avatar_picker" AvatarPicker { - editable: false - availableTags: ["Human", "Furry", "Anime", "Robot", "Other"] + 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 index 77619993232..02d16d4fd5d 100644 --- a/interface/resources/qml/overte/staging/tutorial/ControlsGuide.qml +++ b/interface/resources/qml/overte/staging/tutorial/ControlsGuide.qml @@ -4,69 +4,69 @@ import QtQuick.Layouts import "../" as Overte Rectangle { - id: root - implicitWidth: 720 - implicitHeight: 480 - color: Overte.Theme.paletteActive.base + 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.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") } - } + 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 + 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" - } + // 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" - } + // 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" - } + // 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" - } - } + // 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 index 480d0be8cab..610cd8e14b0 100644 --- a/interface/resources/qml/overte/staging/tutorial/Welcome.qml +++ b/interface/resources/qml/overte/staging/tutorial/Welcome.qml @@ -5,42 +5,42 @@ 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 - } - } - } + 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 + } + } + } } From 3d71e1bd61533a92e27ee66b17544f672bbcedcf Mon Sep 17 00:00:00 2001 From: Ada Date: Mon, 27 Oct 2025 21:05:02 +1000 Subject: [PATCH 063/111] Mostly-functional contacts app, easy profile picture setting --- interface/resources/qml/overte/ComboBox.qml | 1 + .../qml/overte/contacts/AccountAvatar.qml | 2 + .../qml/overte/contacts/AccountContact.qml | 4 + .../qml/overte/contacts/ContactsList.qml | 296 +++++++++++++++--- .../qml/overte/contacts/MyAccountInfo.qml | 85 ++++- .../qml/overte/contacts/SessionContact.qml | 46 ++- scripts/system/systemApps.js | 51 +++ 7 files changed, 434 insertions(+), 51 deletions(-) diff --git a/interface/resources/qml/overte/ComboBox.qml b/interface/resources/qml/overte/ComboBox.qml index 016837dedf7..89a6741ad93 100644 --- a/interface/resources/qml/overte/ComboBox.qml +++ b/interface/resources/qml/overte/ComboBox.qml @@ -21,6 +21,7 @@ ComboBox { horizontalPadding: 2 verticalPadding: 2 focusPolicy: Qt.NoFocus + flat: control.flat icon.source: "./icons/triangle_down.svg" icon.width: 24 diff --git a/interface/resources/qml/overte/contacts/AccountAvatar.qml b/interface/resources/qml/overte/contacts/AccountAvatar.qml index b30a781d834..be42983945a 100644 --- a/interface/resources/qml/overte/contacts/AccountAvatar.qml +++ b/interface/resources/qml/overte/contacts/AccountAvatar.qml @@ -32,6 +32,8 @@ Rectangle { id: avatarImage source: avatar.source + sourceSize.width: width + sourceSize.height: height layer.enabled: true layer.effect: MultiEffect { diff --git a/interface/resources/qml/overte/contacts/AccountContact.qml b/interface/resources/qml/overte/contacts/AccountContact.qml index a73defb081b..fce5036b66d 100644 --- a/interface/resources/qml/overte/contacts/AccountContact.qml +++ b/interface/resources/qml/overte/contacts/AccountContact.qml @@ -83,6 +83,10 @@ Rectangle { } Overte.RoundButton { + // TODO + visible: false + enabled: false + backgroundColor: ( control.friend ? Overte.Theme.paletteActive.buttonDestructive : diff --git a/interface/resources/qml/overte/contacts/ContactsList.qml b/interface/resources/qml/overte/contacts/ContactsList.qml index 566db0faa85..aa9de3aed6f 100644 --- a/interface/resources/qml/overte/contacts/ContactsList.qml +++ b/interface/resources/qml/overte/contacts/ContactsList.qml @@ -6,7 +6,7 @@ import ".." as Overte import "." Rectangle { - id: root + id: contactsList anchors.fill: parent implicitWidth: 480 implicitHeight: 720 @@ -15,44 +15,166 @@ Rectangle { property string localSearchExpression: ".*" property string accountSearchExpression: ".*" - property list localContactsModel: [ - {user: "ada.tv", name: "ada.tv", volume: 1.0, badgeIconSource: "../icons/gold_star.svg"}, - {user: "admin", name: "Admin", volume: 1.0, badgeIconSource: "../icons/admin_shield.svg"}, - {user: "{81cb9b67-d034-40f6-9ab7-a4ecb62f8961}", name: "Anonymous", volume: 1.0, badgeIconSource: ""}, - {user: "", name: "Not logged in", volume: 1.0, badgeIconSource: ""}, - - {user: "ScrollingTest", name: "Scroll test", volume: 1.0, badgeIconSource: ""}, - {user: "ScrollingTest", name: "Scroll test", volume: 1.0, badgeIconSource: ""}, - {user: "ScrollingTest", name: "Scroll test", volume: 1.0, badgeIconSource: ""}, - {user: "ScrollingTest", name: "Scroll test", volume: 1.0, badgeIconSource: ""}, - {user: "ScrollingTest", name: "Scroll test", volume: 1.0, badgeIconSource: ""}, - {user: "ScrollingTest", name: "Scroll test", volume: 1.0, badgeIconSource: ""}, - {user: "ScrollingTest", name: "Scroll test", volume: 1.0, badgeIconSource: ""}, - {user: "ScrollingTest", name: "Scroll test", volume: 1.0, badgeIconSource: ""}, - {user: "ScrollingTest", name: "Scroll test", volume: 1.0, badgeIconSource: ""}, - {user: "ScrollingTest", name: "Scroll test", volume: 1.0, badgeIconSource: ""}, - {user: "ScrollingTest", name: "Scroll test", volume: 1.0, badgeIconSource: ""}, - {user: "ScrollingTest", name: "Scroll test", volume: 1.0, badgeIconSource: ""}, - {user: "ScrollingTest", name: "Scroll test", volume: 1.0, badgeIconSource: ""}, - ] - - property list accountContactsModel: [ - /*{user: "ada.tv", avatarUrl: "file:///home/ada/art/doodles/bevy blep avi.png", status: 0, friend: true, currentPlaceName: "overte_hub"}, - {user: "x74hc595", avatarUrl: "https://cdn.discordapp.com/avatars/761127759367634965/915ea6b0e6b380458bce46616fa1fe35.webp?size=96", status: 2, friend: true, currentPlaceName: "overte_hub"}, - {user: "juliangro", avatarUrl: "https://cdn.discordapp.com/avatars/181488002831351808/7c17a81a149da388d078d8ac795f64fe.webp?size=96", status: 1, friend: true, currentPlaceName: "overte_hub"},*/ - {user: "AccountContact", avatarUrl: "file:///home/ada/art/doodles/bevy blep avi.png", status: 0, friend: false, currentPlaceName: ""}, - {user: "AccountContact", avatarUrl: "file:///home/ada/art/doodles/bevy blep avi.png", status: 1, friend: false, currentPlaceName: ""}, - {user: "AccountContact", avatarUrl: "file:///home/ada/art/doodles/bevy blep avi.png", status: 2, friend: false, currentPlaceName: "overte_hub"}, - {user: "AccountContact", avatarUrl: "file:///home/ada/art/doodles/bevy blep avi.png", status: 3, friend: false, currentPlaceName: "overte_hub"}, - {user: "Friend", avatarUrl: "file:///home/ada/art/doodles/bevy blep avi.png", status: 2, friend: true, currentPlaceName: "overte_hub"}, - ] + 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: root + anchors.fill: contactsList MyAccountInfo { Layout.fillWidth: true - status: MyAccountInfo.Status.LoggedOut id: myAccountInfo } @@ -69,16 +191,31 @@ Rectangle { // 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: root.width - Overte.Theme.scrollbarWidth + contentWidth: contactsList.width - Overte.Theme.scrollbarWidth model: { const regex = new RegExp(localSearchExpression, "i"); @@ -141,7 +278,7 @@ Rectangle { policy: ScrollBar.AsNeeded } - contentWidth: root.width - Overte.Theme.scrollbarWidth + contentWidth: contactsList.width - Overte.Theme.scrollbarWidth model: { const regex = new RegExp(accountSearchExpression, "i"); @@ -204,4 +341,91 @@ Rectangle { } } } + + 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 index caba0bfdc30..a83ddbdb641 100644 --- a/interface/resources/qml/overte/contacts/MyAccountInfo.qml +++ b/interface/resources/qml/overte/contacts/MyAccountInfo.qml @@ -1,3 +1,4 @@ +import QtCore import QtQuick import QtQuick.Layouts import QtQuick.Controls @@ -23,8 +24,53 @@ Rectangle { Everyone } - property int status: MyAccountInfo.Status.Invisible - property string avatarImgSource: "file:///home/ada/art/doodles/bevy blep avi.png" + // cache the last avatar image url so there's not a few + // seconds of placeholder avatar while the profile loads + Settings { + id: settings + 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) { @@ -71,7 +117,7 @@ Rectangle { id: avatarImg source: ( root.status !== MyAccountInfo.Status.LoggedOut ? - root.avatarImgSource : + root.avatarUrl : "../icons/unset_avatar.svg" ) status: root.status @@ -79,6 +125,25 @@ Rectangle { 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 { @@ -90,8 +155,10 @@ Rectangle { verticalAlignment: Text.AlignVCenter id: displayName - text: "ada.tv" - placeholderText: qsTr("Display Name") + text: MyAvatar.displayName + placeholderText: AccountServices.username + + onEditingFinished: MyAvatar.displayName = text } RowLayout { @@ -114,8 +181,16 @@ Rectangle { 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") }, diff --git a/interface/resources/qml/overte/contacts/SessionContact.qml b/interface/resources/qml/overte/contacts/SessionContact.qml index 53d5dcc059e..e0c37949f66 100644 --- a/interface/resources/qml/overte/contacts/SessionContact.qml +++ b/interface/resources/qml/overte/contacts/SessionContact.qml @@ -12,11 +12,21 @@ Rectangle { required property int index - // session UUID or directory username - required property string user + required property string uuid required property string name required property real volume - required property url badgeIconSource + + 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 @@ -46,13 +56,29 @@ Rectangle { Overte.BodyText { font.pixelSize: Overte.Theme.fontPixelSizeSmall - visible: user !== "" + visible: username !== "" //elide: Text.ElideRight - text: user + 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 { @@ -72,16 +98,16 @@ Rectangle { to: 1.2 stepSize: 0.1 snapMode: Slider.SnapAlways - value: volume + value: dbToReal(volume) - onMoved: { - // TODO - } + onMoved: Users.setAvatarGain(control.uuid, realToDb(value)) } Overte.Label { Layout.preferredWidth: Overte.Theme.fontPixelSize * 3 - text: `${Math.round(volumeSlider.value * 100)}%` + //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/scripts/system/systemApps.js b/scripts/system/systemApps.js index 59351d22cd2..2ac2f6d73e4 100644 --- a/scripts/system/systemApps.js +++ b/scripts/system/systemApps.js @@ -30,6 +30,55 @@ function defaultOnScreenChanged(type, url) { } } +// has absolutely nothing to do with HTTP cookies, +// just here to keep track of what requests are being handled +let waitingRequestCookies = new Set(); + +function defaultFromQml(message) { + const data = JSON.parse(message); + + switch (data.action) { + // QML's XMLHttpRequest doesn't have the authentication header that's + // needed for interacting with the directory server. This passes a + // request through the JS XMLHttpRequest so it can use the account auth. + case "system:auth_request": { + // sometimes requests get sent twice? don't let that happen + if (waitingRequestCookies.has(data.data.cookie)) { + return; + } else { + waitingRequestCookies.add(data.data.cookie); + } + + let xhr = new XMLHttpRequest(); + + xhr.onreadystatechange = () => { + if (xhr.readyState === 4 /* DONE */) { + SystemTablet.sendToQml(JSON.stringify({ + action: "system:auth_request", + data: { + status: xhr.status, + statusText: xhr.statusText, + responseText: xhr.responseText, + responseURL: data.data.url, + cookie: data.data.cookie, + }, + })); + + waitingRequestCookies.delete(data.data.cookie); + } + }; + + xhr.open(data.data.method, data.data.url); + + if (data.data.method === "POST") { + xhr.setRequestHeader("Content-Type", "application/json;charset=utf-8"); + } + + xhr.send(data.data.body); + } break; + } +} + const SYSTEM_APPS = { settings: { appButtonData: { @@ -80,10 +129,12 @@ const SYSTEM_APPS = { for (let app of Object.values(SYSTEM_APPS)) { if (!app.onClicked) { app.onClicked = defaultOnClicked; } if (!app.onScreenChanged) { app.onScreenChanged = defaultOnScreenChanged; } + if (!app.fromQml) { app.fromQml = defaultFromQml; } let button = SystemTablet.addButton(app.appButtonData); button.clicked.connect(() => app.onClicked()); SystemTablet.screenChanged.connect((type, url) => app.onScreenChanged(type, url)); + SystemTablet.fromQml.connect(message => app.fromQml(message)); app.appButton = button; } From 29ba9312b50841f8353e8a28648b4b1d32897863 Mon Sep 17 00:00:00 2001 From: Ada Date: Mon, 27 Oct 2025 22:12:57 +1000 Subject: [PATCH 064/111] Functional file picker --- interface/resources/qml/desktop/Desktop.qml | 4 +- .../qml/overte/compat/CompatFileDialog.qml | 72 +++++++++++++++++++ .../RunningScripts_Window.qml | 0 interface/resources/qml/overte/compat/qmldir | 3 + .../qml/overte/dialogs/FileDialog.qml | 17 +++-- .../qml/overte/workarounds/README.md | 4 -- .../resources/qml/windows/ScrollingWindow.qml | 2 + interface/src/Menu.cpp | 2 +- 8 files changed, 94 insertions(+), 10 deletions(-) create mode 100644 interface/resources/qml/overte/compat/CompatFileDialog.qml rename interface/resources/qml/overte/{workarounds => compat}/RunningScripts_Window.qml (100%) create mode 100644 interface/resources/qml/overte/compat/qmldir delete mode 100644 interface/resources/qml/overte/workarounds/README.md diff --git a/interface/resources/qml/desktop/Desktop.qml b/interface/resources/qml/desktop/Desktop.qml index 02845a8b178..e15d3263c49 100644 --- a/interface/resources/qml/desktop/Desktop.qml +++ b/interface/resources/qml/desktop/Desktop.qml @@ -15,6 +15,8 @@ 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 { id: desktop @@ -538,7 +540,7 @@ FocusScope { return customInputDialogBuilder.createObject(desktop, properties); } - Component { id: fileDialogBuilder; FileDialog { } } + Component { id: fileDialogBuilder; OverteCompat.CompatFileDialog { } } function fileDialog(properties) { return fileDialogBuilder.createObject(desktop, properties); } 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/workarounds/RunningScripts_Window.qml b/interface/resources/qml/overte/compat/RunningScripts_Window.qml similarity index 100% rename from interface/resources/qml/overte/workarounds/RunningScripts_Window.qml rename to interface/resources/qml/overte/compat/RunningScripts_Window.qml diff --git a/interface/resources/qml/overte/compat/qmldir b/interface/resources/qml/overte/compat/qmldir new file mode 100644 index 00000000000..9ab4c045405 --- /dev/null +++ b/interface/resources/qml/overte/compat/qmldir @@ -0,0 +1,3 @@ +module Compat +RunningScripts_Window 1.0 RunningScripts_Window.qml +CompatFileDialog 1.0 CompatFileDialog.qml diff --git a/interface/resources/qml/overte/dialogs/FileDialog.qml b/interface/resources/qml/overte/dialogs/FileDialog.qml index aaf12e12667..dc29d689e90 100644 --- a/interface/resources/qml/overte/dialogs/FileDialog.qml +++ b/interface/resources/qml/overte/dialogs/FileDialog.qml @@ -557,6 +557,10 @@ Rectangle { 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; @@ -571,11 +575,16 @@ Rectangle { return text; } - enabled: tableView.selectionModel.hasSelection + enabled: tableView.selectionModel.hasSelection || fileMode === FileDialog.FileMode.OpenFolder onClicked: { - const currentRow = tableView.selectionModel.currentIndex.row; - const currentData = tableView.model.getRow(currentRow); - directoryRowActivated(currentData); + if (fileMode === FileDialog.FileMode.OpenFolder) { + selectedFile = currentFolder; + fileDialog.accepted(); + } else { + const currentRow = tableView.selectionModel.currentIndex.row; + const currentData = tableView.model.getRow(currentRow); + directoryRowActivated(currentData); + } } } } diff --git a/interface/resources/qml/overte/workarounds/README.md b/interface/resources/qml/overte/workarounds/README.md deleted file mode 100644 index b41235d5eb0..00000000000 --- a/interface/resources/qml/overte/workarounds/README.md +++ /dev/null @@ -1,4 +0,0 @@ -These are to work around the current desktop system, which requires -components to be wrapped in a Window to have a draggable border and titlebar. - -This isn't required on the tablet because it doesn't support multiple windows at once. diff --git a/interface/resources/qml/windows/ScrollingWindow.qml b/interface/resources/qml/windows/ScrollingWindow.qml index 1ce15ec0cff..51a62c5d075 100644 --- a/interface/resources/qml/windows/ScrollingWindow.qml +++ b/interface/resources/qml/windows/ScrollingWindow.qml @@ -17,6 +17,8 @@ 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? diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index 16709d1b1c1..bfb9ff950de 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -153,7 +153,7 @@ 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("overte/workarounds/RunningScripts_Window.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); From a2ae63b7b9995b83d68e451dc0466bdd0e446f40 Mon Sep 17 00:00:00 2001 From: Ada Date: Mon, 27 Oct 2025 23:14:05 +1000 Subject: [PATCH 065/111] Asset dialog opens, not functional yet --- interface/resources/qml/desktop/Desktop.qml | 1 + .../qml/overte/compat/CompatAssetDialog.qml | 34 +++ interface/resources/qml/overte/compat/qmldir | 1 + .../qml/overte/dialogs/AssetDialog.qml | 255 ++++++++++-------- interface/src/Application_UI.cpp | 4 +- 5 files changed, 176 insertions(+), 119 deletions(-) create mode 100644 interface/resources/qml/overte/compat/CompatAssetDialog.qml diff --git a/interface/resources/qml/desktop/Desktop.qml b/interface/resources/qml/desktop/Desktop.qml index e15d3263c49..0a04f76863b 100644 --- a/interface/resources/qml/desktop/Desktop.qml +++ b/interface/resources/qml/desktop/Desktop.qml @@ -545,6 +545,7 @@ FocusScope { return fileDialogBuilder.createObject(desktop, properties); } + // TODO: is this actually used? Component { id: assetDialogBuilder; Item {}}//AssetDialog { } } function assetDialog(properties) { return assetDialogBuilder.createObject(desktop, properties); diff --git a/interface/resources/qml/overte/compat/CompatAssetDialog.qml b/interface/resources/qml/overte/compat/CompatAssetDialog.qml new file mode 100644 index 00000000000..46fbd3b7559 --- /dev/null +++ b/interface/resources/qml/overte/compat/CompatAssetDialog.qml @@ -0,0 +1,34 @@ +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) + 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/qmldir b/interface/resources/qml/overte/compat/qmldir index 9ab4c045405..66496dbc57d 100644 --- a/interface/resources/qml/overte/compat/qmldir +++ b/interface/resources/qml/overte/compat/qmldir @@ -1,3 +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/dialogs/AssetDialog.qml b/interface/resources/qml/overte/dialogs/AssetDialog.qml index 5212992d1ee..92898630f34 100644 --- a/interface/resources/qml/overte/dialogs/AssetDialog.qml +++ b/interface/resources/qml/overte/dialogs/AssetDialog.qml @@ -5,16 +5,114 @@ import QtQuick.Layouts import Qt.labs.folderlistmodel import Qt.labs.qmlmodels -import ".." +import ".." as Overte Rectangle { id: dialog - color: Theme.paletteActive.base + color: Overte.Theme.paletteActive.base property url selectedFile: "" property string searchExpression: ".*" - readonly property var spawnableRegex: /\.(glb|fbx|fst|png|jpeg|jpg|webp)$/i + readonly property var spawnableRegex: /\.(gltf|glb|vrm|fbx|fst|obj|png|jpeg|jpg|webp)$/i + + // TODO: how does this work? + readonly property var assetProxyModel: Assets.proxyModel + + function getDirectoryModel(parentIndex = undefined) { + const DISPLAY_ROLE = 0x100; + const PATH_ROLE = 0x103; + const sourceModel = assetProxyModel; + 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)); + + console.log(JSON.stringify(tmp)); + + 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 @@ -23,15 +121,15 @@ Rectangle { Layout.fillWidth: true Layout.margins: 4 - Button { - backgroundColor: Theme.paletteActive.buttonAdd + Overte.Button { + backgroundColor: Overte.Theme.paletteActive.buttonAdd text: qsTr("Upload File") // TODO onClicked: {} } - TextField { + Overte.TextField { Layout.fillWidth: true Layout.preferredHeight: parent.height @@ -42,12 +140,12 @@ Rectangle { Keys.onReturnPressed: searchButton.click() } - RoundButton { + Overte.RoundButton { id: searchButton icon.source: "../icons/search.svg" icon.width: 24 icon.height: 24 - icon.color: Theme.paletteActive.buttonText + icon.color: Overte.Theme.paletteActive.buttonText onClicked: { searchExpression = searchField.text === "" ? /.*/ : new RegExp(searchField.text); @@ -55,117 +153,40 @@ Rectangle { } } - ScrollView { + TreeView { Layout.fillWidth: true Layout.fillHeight: true - contentWidth: availableWidth - rightPadding: ScrollBar.vertical.width + id: tableView + clip: true + selectionBehavior: TableView.SelectRows + selectionMode: TableView.SingleSelection + pixelAligned: true + rowSpacing: 1 + columnSpacing: 0 + boundsBehavior: Flickable.StopAtBounds - background: Rectangle { - color: Qt.darker(Theme.paletteActive.base, 1.2) - } - - ScrollBar.horizontal.policy: ScrollBar.AlwaysOff + // 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: ScrollBar { + ScrollBar.vertical: Overte.ScrollBar { policy: ScrollBar.AlwaysOn interactive: true anchors.right: parent.right anchors.top: parent.top anchors.bottom: parent.bottom } + contentWidth: width - ScrollBar.vertical.width - TableView { - id: tableView - clip: true - selectionBehavior: TableView.SelectRows - selectionMode: 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 - } - - // TODO - rows: [ - { fileName: "bevy2.glb", fileModified: (new Date()).toLocaleDateString(null, Locale.ShortFormat), fileSize: "2.1 MiB" }, - { fileName: "cheese.jpg", fileModified: (new Date()).toLocaleDateString(null, Locale.ShortFormat), fileSize: "12 KiB" }, - { fileName: "metal pipe.wav", fileModified: (new Date()).toLocaleDateString(null, Locale.ShortFormat), fileSize: "200 KiB" }, - ] - } + model: TreeModel { + TableModelColumn { display: "name" } - selectionModel: ItemSelectionModel { - onCurrentChanged: (index, _) => { - if (index.row === -1) { return; } - - const data = tableView.model.getRow(index.row); - selectedFile = data.fileName; - } - } - - delegate: Rectangle { - required property bool selected - required property bool current - required property int row - - readonly property bool rowCurrent: tableView.currentRow === row - - color: ( - rowCurrent ? - Theme.paletteActive.highlight : - (row % 2 === 0 ? Theme.paletteActive.base : Theme.paletteActive.alternateBase) - ) - implicitHeight: Theme.fontPixelSize * 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; - } - - switch (column) { - case 0: return nameWidth; - case 1: return mtimeWidth; - case 2: return sizeWidth; - } - } - - Text { - anchors.margins: 8 - anchors.fill: parent - - verticalAlignment: Text.AlignVCenter - horizontalAlignment: column === 2 ? Text.AlignRight : Text.AlignLeft - font.family: Theme.fontFamily - font.pixelSize: Theme.fontPixelSize - color: rowCurrent ? Theme.paletteActive.highlightedText : Theme.paletteActive.text - text: display - } - } + rows: getDirectoryModel() } + + selectionModel: ItemSelectionModel {} + delegate: TreeDelegate {} } GridLayout { @@ -174,35 +195,35 @@ Rectangle { Layout.fillWidth: true Layout.margins: 8 - Button { + Overte.Button { Layout.fillWidth: true Layout.preferredWidth: 1 enabled: tableView.currentRow !== -1 text: qsTr("Delete") - backgroundColor: Theme.paletteActive.buttonDestructive + backgroundColor: Overte.Theme.paletteActive.buttonDestructive // TODO onClicked: { - console.log("Delete", tableView.model.data(tableView.model.index(tableView.currentRow, 0))); + console.log("Delete"); } } - Button { + Overte.Button { Layout.fillWidth: true Layout.preferredWidth: 1 enabled: tableView.currentRow !== -1 text: qsTr("Copy Link") - backgroundColor: Theme.paletteActive.buttonInfo + backgroundColor: Overte.Theme.paletteActive.buttonInfo // TODO onClicked: { - console.log("Copy Link", tableView.model.data(tableView.model.index(tableView.currentRow, 0))); + console.log("Copy Link"); } } - Button { + Overte.Button { Layout.fillWidth: true Layout.preferredWidth: 1 @@ -211,11 +232,11 @@ Rectangle { // TODO onClicked: { - console.log("Rename", tableView.model.data(tableView.model.index(tableView.currentRow, 0))); + console.log("Rename"); } } - Button { + Overte.Button { Layout.fillWidth: true Layout.preferredWidth: 1 @@ -227,17 +248,17 @@ Rectangle { return !!data.match(spawnableRegex); } text: qsTr("Create Entity") - backgroundColor: Theme.paletteActive.buttonAdd + backgroundColor: Overte.Theme.paletteActive.buttonAdd // TODO onClicked: { - console.log("Create Entity", tableView.model.data(tableView.model.index(tableView.currentRow, 0))); + console.log("Create Entity"); } } } } - MessageDialog { + Overte.MessageDialog { id: replaceWarningDialog anchors.fill: parent } diff --git a/interface/src/Application_UI.cpp b/interface/src/Application_UI.cpp index 6f4a2e23d0a..6b0e1effe51 100644 --- a/interface/src/Application_UI.cpp +++ b/interface/src/Application_UI.cpp @@ -587,7 +587,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()) { @@ -603,7 +603,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); } From 382b89c203aa09335a4c7ef2119de4c88ace3e1a Mon Sep 17 00:00:00 2001 From: Ada Date: Wed, 29 Oct 2025 14:08:54 +1000 Subject: [PATCH 066/111] Kinda-functional asset browser --- .../qml/overte/compat/CompatAssetDialog.qml | 5 +- .../qml/overte/dialogs/AssetDialog.qml | 305 ++++++++++++------ .../AssetMappingsScriptingInterface.h | 4 +- 3 files changed, 217 insertions(+), 97 deletions(-) diff --git a/interface/resources/qml/overte/compat/CompatAssetDialog.qml b/interface/resources/qml/overte/compat/CompatAssetDialog.qml index 46fbd3b7559..addf54b4102 100644 --- a/interface/resources/qml/overte/compat/CompatAssetDialog.qml +++ b/interface/resources/qml/overte/compat/CompatAssetDialog.qml @@ -11,8 +11,8 @@ HifiWindows.Window { id: root resizable: true implicitWidth: 480 - implicitHeight: 360 - minSize: Qt.vector2d(360, 240) + implicitHeight: 480 + minSize: Qt.vector2d(480, 360) destroyOnHidden: true objectName: "AssetServer" title: qsTr("Asset Browser") @@ -28,7 +28,6 @@ HifiWindows.Window { OverteDialogs.AssetDialog { anchors.fill: parent - id: assetDialog } } diff --git a/interface/resources/qml/overte/dialogs/AssetDialog.qml b/interface/resources/qml/overte/dialogs/AssetDialog.qml index 92898630f34..5fda22bdceb 100644 --- a/interface/resources/qml/overte/dialogs/AssetDialog.qml +++ b/interface/resources/qml/overte/dialogs/AssetDialog.qml @@ -14,57 +14,55 @@ Rectangle { property url selectedFile: "" property string searchExpression: ".*" - readonly property var spawnableRegex: /\.(gltf|glb|vrm|fbx|fst|obj|png|jpeg|jpg|webp)$/i + // 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 - // TODO: how does this work? - readonly property var assetProxyModel: Assets.proxyModel + property var assetMappingModel: ({}) - function getDirectoryModel(parentIndex = undefined) { - const DISPLAY_ROLE = 0x100; - const PATH_ROLE = 0x103; - const sourceModel = assetProxyModel; - const sourceModelRootLength = sourceModel.rowCount(parentIndex); - let tmp = []; + Component.onCompleted: refreshModel() - for (let i = 0; i < sourceModelRootLength; i++) { - const index = sourceModel.index(i, 0, parentIndex); + function refreshModel() { + Assets.getAllMappings((error, maps) => { + if (error === "") { + assetMappingModel = maps; + tableModel.rows = getDirectoryModel(); + } else { + console.error(error); + } + }); + } - const name = sourceModel.data(index, DISPLAY_ROLE); - const path = sourceModel.data(index, PATH_ROLE); - const hasChildren = sourceModel.hasChildren(index); + // this seems suboptimal but i can't figure out how else to use the Assets models + function getDirectoryModel() { + let tmp = []; - if (hasChildren) { - tmp.push({ - name: name, - path: path, - rows: getDirectoryModel(index) - }); - } else { - tmp.push({ - name: name, - path: path, - }); + for (const [name, hash] of Object.entries(assetMappingModel)) { + if (!name.slice(1).match(new RegExp(searchExpression, "i"))) { + continue; } - } - tmp.sort((a, b) => ((b.rows ? 1 : 0) - (a.rows ? 1 : 0)) || a.name.localeCompare(b.name)); + tmp.push({ + // trim off the leading slash + name: name.slice(1), + path: name, + hash: hash, + }); + } - console.log(JSON.stringify(tmp)); + tmp.sort((a, b) => a.name.localeCompare(b.name)); return tmp; } - component TreeDelegate: Rectangle { - required property TreeView treeView - required property int depth + component TableDelegate: Rectangle { + required property TableView tableView 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 + property string name: tableModel.rows[row]?.name ?? "undefined" - implicitWidth: treeView.contentWidth + implicitWidth: Math.max(tableView.contentWidth, label.implicitWidth) implicitHeight: Overte.Theme.fontPixelSize * 2 color: { @@ -77,36 +75,12 @@ Rectangle { } } - // 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 { + id: label anchors.top: parent.top anchors.bottom: parent.bottom anchors.right: parent.right - anchors.left: indicator.right + anchors.left: parent.left anchors.leftMargin: 4 verticalAlignment: Text.AlignVCenter color: current ? Overte.Theme.paletteActive.highlightedText : Overte.Theme.paletteActive.buttonText @@ -126,6 +100,7 @@ Rectangle { text: qsTr("Upload File") // TODO + enabled: false onClicked: {} } @@ -136,8 +111,15 @@ Rectangle { id: searchField placeholderText: qsTr("Search…") - Keys.onEnterPressed: searchButton.click() - Keys.onReturnPressed: searchButton.click() + Keys.onEnterPressed: { + searchButton.click(); + searchField.forceActiveFocus(); + } + + Keys.onReturnPressed: { + searchButton.click(); + searchField.forceActiveFocus(); + } } Overte.RoundButton { @@ -148,12 +130,15 @@ Rectangle { icon.color: Overte.Theme.paletteActive.buttonText onClicked: { - searchExpression = searchField.text === "" ? /.*/ : new RegExp(searchField.text); + searchExpression = searchField.text === "" ? ".*" : searchField.text; + + // refresh the listing model + tableModel.rows = getDirectoryModel(); } } } - TreeView { + TableView { Layout.fillWidth: true Layout.fillHeight: true @@ -166,27 +151,28 @@ Rectangle { columnSpacing: 0 boundsBehavior: Flickable.StopAtBounds - // 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 interactive: true - anchors.right: parent.right - anchors.top: parent.top - anchors.bottom: parent.bottom } + + ScrollBar.horizontal: Overte.ScrollBar { + policy: ScrollBar.AsNeeded + interactive: true + } + contentWidth: width - ScrollBar.vertical.width - model: TreeModel { + model: TableModel { + id: tableModel + TableModelColumn { display: "name" } - rows: getDirectoryModel() + rows: [] } selectionModel: ItemSelectionModel {} - delegate: TreeDelegate {} + delegate: TableDelegate {} } GridLayout { @@ -199,13 +185,16 @@ Rectangle { Layout.fillWidth: true Layout.preferredWidth: 1 - enabled: tableView.currentRow !== -1 - text: qsTr("Delete") + // 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") - // TODO onClicked: { - console.log("Delete"); + const data = tableModel.rows[tableView.currentRow]; + deleteWarningDialog.entryToDelete = data.name; + deleteWarningDialog.open(); } } @@ -214,12 +203,12 @@ Rectangle { Layout.preferredWidth: 1 enabled: tableView.currentRow !== -1 - text: qsTr("Copy Link") backgroundColor: Overte.Theme.paletteActive.buttonInfo + text: qsTr("Copy Link") - // TODO onClicked: { - console.log("Copy Link"); + const data = tableModel.rows[tableView.currentRow]; + WindowScriptingInterface.copyToClipboard(`atp:${data.path}`); } } @@ -227,12 +216,15 @@ Rectangle { Layout.fillWidth: true Layout.preferredWidth: 1 - enabled: tableView.currentRow !== -1 + // TODO: might work, don't have a way of testing atm + // enabled: tableView.currentRow !== -1 + enabled: false text: qsTr("Rename") - // TODO onClicked: { - console.log("Rename"); + const data = tableModel.rows[tableView.currentRow]; + renameDialog.entryToRename = data.name; + renameDialog.open(); } } @@ -242,24 +234,151 @@ Rectangle { enabled: { if (tableView.currentRow === -1) { return false; } - - const index = tableView.model.index(tableView.currentRow, 0); - const data = tableView.model.data(index); - return !!data.match(spawnableRegex); + return tableModel.rows[tableView.currentRow].name.match(spawnableRegex); } - text: qsTr("Create Entity") backgroundColor: Overte.Theme.paletteActive.buttonAdd + text: qsTr("Create Entity") - // TODO onClicked: { - console.log("Create Entity"); + const data = tableModel.rows[tableView.currentRow]; + spawnEntity(data.path); } } } } Overte.MessageDialog { - id: replaceWarningDialog + 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/src/scripting/AssetMappingsScriptingInterface.h b/interface/src/scripting/AssetMappingsScriptingInterface.h index bc624b0dd08..921b393ad49 100644 --- a/interface/src/scripting/AssetMappingsScriptingInterface.h +++ b/interface/src/scripting/AssetMappingsScriptingInterface.h @@ -78,7 +78,9 @@ class AssetMappingsScriptingInterface : public QObject, public Dependency { Q_INVOKABLE void getMapping(QString path, QJSValue callback = QJSValue()); Q_INVOKABLE void uploadFile(QString path, QString mapping, QJSValue startedCallback = QJSValue(), QJSValue completedCallback = QJSValue(), bool dropEvent = false); Q_INVOKABLE void deleteMappings(QStringList paths, QJSValue callback = QJSValue()); - Q_INVOKABLE void deleteMapping(QString path, QJSValue callback) { deleteMappings(QStringList(path), callback = QJSValue()); } + Q_INVOKABLE void deleteMapping(QString path, QJSValue callback = QJSValue()) { + deleteMappings(QStringList(path), callback); + } Q_INVOKABLE void getAllMappings(QJSValue callback = QJSValue()); Q_INVOKABLE void renameMapping(QString oldPath, QString newPath, QJSValue callback = QJSValue()); Q_INVOKABLE void setBakingEnabled(QStringList paths, bool enabled, QJSValue callback = QJSValue()); From 37cafb5d701dd0927dcf11cdac09e131da87db38 Mon Sep 17 00:00:00 2001 From: Ada Date: Wed, 29 Oct 2025 16:49:58 +1000 Subject: [PATCH 067/111] Mostly-functional settings menu --- .../qml/overte/dialogs/FileDialog.qml | 8 ++ .../qml/overte/settings/FolderSetting.qml | 11 ++- .../qml/overte/settings/Settings.qml | 30 ++++++- .../qml/overte/settings/WideComboSetting.qml | 1 + .../qml/overte/settings/pages/Audio.qml | 22 ++++- .../qml/overte/settings/pages/Controls.qml | 88 ++++++++++--------- .../qml/overte/settings/pages/General.qml | 39 ++++---- .../qml/overte/settings/pages/Graphics.qml | 26 +++--- interface/src/Application_UI.cpp | 6 ++ interface/src/avatar/MyAvatar.cpp | 34 +++++++ interface/src/avatar/MyAvatar.h | 20 +++++ interface/src/scripting/AudioDevices.cpp | 14 +-- .../scripting/KeyboardScriptingInterface.cpp | 4 + .../scripting/KeyboardScriptingInterface.h | 3 +- 14 files changed, 214 insertions(+), 92 deletions(-) diff --git a/interface/resources/qml/overte/dialogs/FileDialog.qml b/interface/resources/qml/overte/dialogs/FileDialog.qml index dc29d689e90..beca7d6d55a 100644 --- a/interface/resources/qml/overte/dialogs/FileDialog.qml +++ b/interface/resources/qml/overte/dialogs/FileDialog.qml @@ -49,6 +49,10 @@ Rectangle { visible = true; } + function close() { + visible = false; + } + function urlToPathString(urlRaw) { const url = new URL(urlRaw); @@ -283,6 +287,10 @@ Rectangle { 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 diff --git a/interface/resources/qml/overte/settings/FolderSetting.qml b/interface/resources/qml/overte/settings/FolderSetting.qml index e0f8c248efd..dbf7001c290 100644 --- a/interface/resources/qml/overte/settings/FolderSetting.qml +++ b/interface/resources/qml/overte/settings/FolderSetting.qml @@ -2,6 +2,7 @@ import QtQuick import QtQuick.Layouts import "../" as Overte +import "../dialogs" as OverteDialogs ColumnLayout { property alias text: labelItem.text @@ -14,9 +15,6 @@ ColumnLayout { anchors.margins: 16 spacing: 4 - // prevent binding loops - Component.onCompleted: value = value - Overte.Label { Layout.alignment: Qt.AlignBottom id: labelItem @@ -40,7 +38,12 @@ ColumnLayout { icon.width: 24 icon.height: 24 - // TODO + onClicked: settingsRoot.openFolderPicker( + folder => { + value = folder; + }, + value + ); } } } diff --git a/interface/resources/qml/overte/settings/Settings.qml b/interface/resources/qml/overte/settings/Settings.qml index 5edc6991d7c..b69937cf422 100644 --- a/interface/resources/qml/overte/settings/Settings.qml +++ b/interface/resources/qml/overte/settings/Settings.qml @@ -2,12 +2,13 @@ import QtQuick import QtQuick.Layouts import QtQuick.Controls -import "../" 1.0 as Overte +import "../" as Overte import "." as OverteSettings import "./pages" as SettingsPages +import "../dialogs" as OverteDialogs Rectangle { - id: root + id: settingsRoot width: 480 height: 720 visible: true @@ -42,4 +43,29 @@ Rectangle { 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/WideComboSetting.qml b/interface/resources/qml/overte/settings/WideComboSetting.qml index e7e6814535d..15490ba2c6d 100644 --- a/interface/resources/qml/overte/settings/WideComboSetting.qml +++ b/interface/resources/qml/overte/settings/WideComboSetting.qml @@ -28,5 +28,6 @@ ColumnLayout { 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 index 35a3c97f595..0d8c8affe1a 100644 --- a/interface/resources/qml/overte/settings/pages/Audio.qml +++ b/interface/resources/qml/overte/settings/pages/Audio.qml @@ -24,7 +24,16 @@ SettingsPage { WideComboSetting { text: "Output Device" - model: AudioScriptingInterface.devices.output + 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 @@ -33,7 +42,16 @@ SettingsPage { WideComboSetting { text: "Input Device" - model: AudioScriptingInterface.devices.input + 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 diff --git a/interface/resources/qml/overte/settings/pages/Controls.qml b/interface/resources/qml/overte/settings/pages/Controls.qml index 71eff3c0d39..6b80c57d7fa 100644 --- a/interface/resources/qml/overte/settings/pages/Controls.qml +++ b/interface/resources/qml/overte/settings/pages/Controls.qml @@ -1,6 +1,9 @@ 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") @@ -10,27 +13,28 @@ SettingsPage { } SwitchSetting { - // FIXME: setting isn't exposed to script api - enabled: false - + id: invertMouseY text: qsTr("Invert Y") - // TODO - value: false + value: MyAvatar.pitchSpeed < 0 + onValueChanged: { + MyAvatar.pitchSpeed = mouseSensitivity.value * (value ? -75 : 75); + } } SliderSetting { - // FIXME: setting isn't exposed to script api - enabled: false - + id: mouseSensitivity text: qsTr("Mouse Sensitivity") stepSize: 0.1 from: 0.1 to: 5.0 - valueToText: () => `${value.toFixed(1)}`; + valueToText: () => `${value.toLocaleString()}`; - // TODO - value: 1.0 + value: MyAvatar.yawSpeed / 75 + onValueChanged: { + MyAvatar.yawSpeed = value * 75; + MyAvatar.pitchSpeed = value * (invertMouseY.value ? -75 : 75); + } } Header { text: qsTr("VR User") } @@ -43,7 +47,7 @@ SettingsPage { to: 2.5 valueToText: () => { - let meters = value.toFixed(2); + let meters = value.toLocaleString(); let totalInches = Math.round(value * 39.37008); let feet = Math.floor(totalInches / 12); @@ -52,8 +56,7 @@ SettingsPage { return `${feet}'${inches}" ${meters.toLocaleString()}m`; } - // FIXME: QML complains about userHeight not being bindable - value: { value = MyAvatar.userHeight } + value: MyAvatar.userHeight onValueChanged: MyAvatar.userHeight = value } @@ -68,24 +71,28 @@ SettingsPage { onCurrentIndexChanged: MyAvatar.setDominantHand(currentIndex === 0 ? "left" : "right") } + SwitchSetting { + text: qsTr("Seated Mode") + value: MyAvatar.userRecenterModel === MyAvatar.ForceSit + onValueChanged: { + MyAvatar.userRecenterModel = value ? MyAvatar.ForceSit : MyAvatar.ForceStand; + } + } + Header { text: qsTr("VR Movement") } SliderSetting { - // FIXME: setting isn't exposed to script api - enabled: false - text: qsTr("Turning Speed") stepSize: 10 - from: 0 + from: 40 to: 400 - valueToText: () => value < 50 ? qsTr("Snap turning") : `${value.toFixed(1)}`; + valueToText: () => value < 50 ? qsTr("Snap turning") : `${value.toLocaleString()}`; - // FIXME: not exposed to scripts or QML - /*value: MyAvatar.HMDYawSpeed + value: MyAvatar.hmdYawSpeed onValueChanged: { MyAvatar.setSnapTurn(value < 50); MyAvatar.HMDYawSpeed = value; - }*/ + } } SliderSetting { @@ -94,16 +101,17 @@ SettingsPage { stepSize: 0.5 from: 1.0 to: 9 - valueToText: () => value < 1.5 ? qsTr("Teleport Only") : `${value.toFixed(1)} m/s`; + valueToText: () => value < 1.5 ? qsTr("Teleport Only") : `${value.toLocaleString()} m/s`; //enabled: !useAvatarDefaultWalkingSpeed.value - value: MyAvatar.vrWalkSpeed + // QT6TODO: change to vrWalkSpeed when we rebase on master + value: MyAvatar.analogPlusWalkSpeed onValueChanged: { if (value === 0.0) { MyAvatar.useAdvancedMovementControls = false; } else { MyAvatar.useAdvancedMovementControls = true; - MyAvatar.vrWalkSpeed = value; + MyAvatar.analogPlusWalkSpeed = value; } } } @@ -116,7 +124,8 @@ SettingsPage { qsTr("Hand"), ] - // TODO + currentIndex: MyAvatar.getMovementReference() + onCurrentIndexChanged: MyAvatar.setMovementReference(currentIndex) } /*SwitchSetting { @@ -130,44 +139,41 @@ SettingsPage { Header { text: qsTr("VR UI") } ComboSetting { - // FIXME: setting isn't exposed to script api - enabled: false - text: qsTr("Tablet Input") + + // keep this in the same order as MyAvatar.TabletInputMode model: [ qsTr("Laser"), qsTr("Stylus"), qsTr("Finger Touch"), ] - // TODO + currentIndex: MyAvatar.tabletInputMode + onCurrentIndexChanged: MyAvatar.tabletInputMode = currentIndex; } ComboSetting { - // FIXME: setting isn't exposed to script api - enabled: false - text: qsTr("Virtual Keyboard Input") model: [ qsTr("Lasers"), qsTr("Mallets"), ] - // TODO + currentIndex: KeyboardScriptingInterface.preferMalletsOverLasers ? 1 : 0 + onCurrentIndexChanged: { + KeyboardScriptingInterface.preferMalletsOverLasers = currentIndex == 1; + } } SliderSetting { - // FIXME: setting isn't exposed to script api - enabled: false - text: qsTr("Laser Smoothing Delay") - stepSize: 0.1 + stepSize: 0.05 from: 0.0 to: 2.0 - valueToText: () => `${value.toFixed(1)}s`; + valueToText: () => `${value.toLocaleString()}s`; - // TODO - value: 0.3 + value: PickScriptingInterface.handLaserDelay + onValueChanged: PickScriptingInterface.handLaserDelay = value } // for later, once the gesture scripts are stable and merged diff --git a/interface/resources/qml/overte/settings/pages/General.qml b/interface/resources/qml/overte/settings/pages/General.qml index a0ca353c0c8..d84e1900bc2 100644 --- a/interface/resources/qml/overte/settings/pages/General.qml +++ b/interface/resources/qml/overte/settings/pages/General.qml @@ -2,6 +2,8 @@ import "../../" as Overte import "../" SettingsPage { + id: page + Header { text: qsTr("UI") @@ -78,11 +80,7 @@ SettingsPage { Header { text: qsTr("Screenshots") } FolderSetting { - // TODO - enabled: false - text: qsTr("Folder") - // TODO value: Snapshot.getSnapshotsLocation() onValueChanged: Snapshot.setSnapshotsLocation(value) } @@ -94,19 +92,27 @@ SettingsPage { "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 { - // FIXME: setting isn't exposed to script api + // FIXME enabled: false text: qsTr("Animation Duration") from: 1 to: 30 - // TODO - value: 3 + //value: Settings.getValue("snapshotAnimatedDuration", 3) + //onValueChanged: Settings.setValue("snapshotAnimatedDuration", value) } SettingNote { @@ -116,13 +122,13 @@ SettingsPage { Header { text: qsTr("Privacy") } SwitchSetting { - // FIXME: setting isn't exposed to script api - enabled: false - text: qsTr("Send Crash Reports") - // TODO - value: false - onValueChanged: () => {} + + // 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 { @@ -130,13 +136,12 @@ SettingsPage { } SwitchSetting { - // FIXME: setting isn't exposed to script api + // FIXME enabled: false text: qsTr("Discord Rich Presence") - // TODO - value: false - onValueChanged: () => {} + //value: Settings.getValue("useDiscordPresence", true) + //onValueChanged: Settings.setValue("useDiscordPresence", value) } SettingNote { diff --git a/interface/resources/qml/overte/settings/pages/Graphics.qml b/interface/resources/qml/overte/settings/pages/Graphics.qml index 1e46504b5ae..13050a5ec8c 100644 --- a/interface/resources/qml/overte/settings/pages/Graphics.qml +++ b/interface/resources/qml/overte/settings/pages/Graphics.qml @@ -25,32 +25,34 @@ SettingsPage { } ComboSetting { + enabled: advRenderingEnabled.value + text: qsTr("Anti-Aliasing") - textRole: "text" - valueRole: "mode" model: [ - { text: qsTr("None"), mode: "none" }, - { text: qsTr("4x MSAA"), mode: "msaa" }, - { text: qsTr("TAA"), mode: "taa" }, - { text: qsTr("FXAA"), mode: "fxaa" }, + // TODO: separate none and MSAA? + // RenderMainView.PreparePrimaryBufferForward.numSamples is a massive hack + advRenderingEnabled.value ? qsTr("None") : qsTr("4x MSAA"), + qsTr("TAA"), + qsTr("FXAA"), ] - // TODO + currentIndex: advRenderingEnabled.value ? Render.getAntialiasingMode() : 0 + onCurrentIndexChanged: Render.setAntialiasingMode(currentIndex) } ComboSetting { - text: qsTr("LOD Culling") + text: qsTr("Level-of-Detail") textRole: "text" valueRole: "mode" model: [ - { text: qsTr("High Detail"), mode: 0 }, - { text: qsTr("Medium Detail"), mode: 1 }, - { text: qsTr("Low Detail"), mode: 2 }, + { text: qsTr("High"), mode: 0 }, + { text: qsTr("Medium"), mode: 1 }, + { text: qsTr("Low"), mode: 2 }, ] - currentIndex: { currentIndex = LODManager.worldDetailQuality } + currentIndex: LODManager.worldDetailQuality onCurrentIndexChanged: LODManager.worldDetailQuality = model[currentIndex].mode } diff --git a/interface/src/Application_UI.cpp b/interface/src/Application_UI.cpp index 6b0e1effe51..f56efc67766 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" @@ -267,6 +268,7 @@ void Application::setupQmlSurface(QQmlContext* surfaceContext, bool setAdditiona 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()); @@ -908,6 +910,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()); @@ -955,6 +960,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()); diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 3dfc07aee2f..b943c880a78 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -6686,3 +6686,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 8de13371b20..8933b33d3f5 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -387,6 +387,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"; @@ -537,6 +539,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(); @@ -994,6 +1003,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); } @@ -2465,6 +2477,14 @@ public slots: */ void disableHandTouchForIDChanged(const QUuid& entityID, bool disable); + /*@jsdoc + * Triggered when {@link MyAvatar.tabletInputMode} is changed. + * @function MyAvatar.tabletInputModeChanged + * @param {MyAvatar.TabletInputMode} + * @returns {Signal} + */ + void tabletInputModeChanged(MyAvatar::TabletInputMode mode); + private slots: void leaveDomain(); void updateCollisionCapsuleCache(); diff --git a/interface/src/scripting/AudioDevices.cpp b/interface/src/scripting/AudioDevices.cpp index 90e09ef69dd..9255b4b1bc1 100644 --- a/interface/src/scripting/AudioDevices.cpp +++ b/interface/src/scripting/AudioDevices.cpp @@ -311,19 +311,7 @@ void AudioDeviceList::onDevicesChanged(QAudioDevice::Mode mode, const QList()->getPreferMalletsOverLasers(); } +void KeyboardScriptingInterface::setPreferMalletsOverLasers(bool mallets) { + return DependencyManager::get()->setPreferMalletsOverLasers(mallets); +} + bool KeyboardScriptingInterface::containsID(const QUuid& id) const { return DependencyManager::get()->containsID(id); } diff --git a/interface/src/scripting/KeyboardScriptingInterface.h b/interface/src/scripting/KeyboardScriptingInterface.h index 196c55ffa4a..57da8a5bff0 100644 --- a/interface/src/scripting/KeyboardScriptingInterface.h +++ b/interface/src/scripting/KeyboardScriptingInterface.h @@ -42,7 +42,7 @@ class KeyboardScriptingInterface : public QObject, public Dependency { Q_PROPERTY(bool raised READ isRaised WRITE setRaised) Q_PROPERTY(bool password READ isPassword WRITE setPassword) Q_PROPERTY(bool use3DKeyboard READ getUse3DKeyboard CONSTANT); - Q_PROPERTY(bool preferMalletsOverLasers READ getPreferMalletsOverLasers CONSTANT) + Q_PROPERTY(bool preferMalletsOverLasers READ getPreferMalletsOverLasers WROTE setPreferMalletsOverLasers) public: KeyboardScriptingInterface() = default; @@ -104,6 +104,7 @@ class KeyboardScriptingInterface : public QObject, public Dependency { private: bool getPreferMalletsOverLasers() const; + void setPreferMalletsOverLasers(bool mallets); bool isRaised() const; void setRaised(bool raised); From f40082830122620cdba8a0cd176a2426c1717583 Mon Sep 17 00:00:00 2001 From: Ada Date: Wed, 29 Oct 2025 17:33:56 +1000 Subject: [PATCH 068/111] Polish --- interface/resources/qml/overte/chat/Chat.qml | 2 +- .../qml/overte/chat/MessageBlock.qml | 3 +-- .../overte/dialogs/RunningScriptsDialog.qml | 11 ++++---- .../qml/overte/settings/pages/General.qml | 26 ++++++++++++++++--- .../chatBubbles/chatBubbles.js | 2 +- 5 files changed, 31 insertions(+), 13 deletions(-) diff --git a/interface/resources/qml/overte/chat/Chat.qml b/interface/resources/qml/overte/chat/Chat.qml index 4d9a63e0e1f..fe2bb7aa69b 100644 --- a/interface/resources/qml/overte/chat/Chat.qml +++ b/interface/resources/qml/overte/chat/Chat.qml @@ -46,7 +46,7 @@ Rectangle { function fromScript(rawObj) { const obj = JSON.parse(rawObj); - const timestamp = (obj.timestamp ? new Date(obj.timestamp) : new Date()).toTimeString(); + const timestamp = (obj.timestamp ? new Date(obj.timestamp) : new Date()).toLocaleTimeString(undefined, Locale.ShortFormat); switch (obj.event) { case "recv_message": diff --git a/interface/resources/qml/overte/chat/MessageBlock.qml b/interface/resources/qml/overte/chat/MessageBlock.qml index f49a392e89e..a12958dc6b5 100644 --- a/interface/resources/qml/overte/chat/MessageBlock.qml +++ b/interface/resources/qml/overte/chat/MessageBlock.qml @@ -57,8 +57,7 @@ ColumnLayout { visible: text.length > 0 - // MD support is cool, but it'd only work properly in the QML chat app - // and not chat bubbles. (maybe would work with QML desktop notifications?) + // MD support is cool, but it'd only work properly in the QML chat app and not chat bubbles. //textFormat: TextEdit.MarkdownText textFormat: TextEdit.RichText diff --git a/interface/resources/qml/overte/dialogs/RunningScriptsDialog.qml b/interface/resources/qml/overte/dialogs/RunningScriptsDialog.qml index 9162203d6aa..674d0cbd4a4 100644 --- a/interface/resources/qml/overte/dialogs/RunningScriptsDialog.qml +++ b/interface/resources/qml/overte/dialogs/RunningScriptsDialog.qml @@ -65,7 +65,7 @@ Rectangle { Layout.alignment: Qt.AlignLeft | Qt.AlignTop Layout.fillWidth: true elide: Text.ElideRight - text: name.replace(/(.*).js/, "$1") + text: name.replace(/(.*).js$/, "$1"); } Overte.Label { @@ -75,11 +75,10 @@ Rectangle { opacity: Overte.Theme.highContrast ? 1.0 : 0.6 elide: Text.ElideRight text: { - if ( - url.startsWith("qrc:") || - url.startsWith("file://~/") - ) { - return `${qsTr("Built-in:")} ${url}`; + 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; } diff --git a/interface/resources/qml/overte/settings/pages/General.qml b/interface/resources/qml/overte/settings/pages/General.qml index d84e1900bc2..8b02978c790 100644 --- a/interface/resources/qml/overte/settings/pages/General.qml +++ b/interface/resources/qml/overte/settings/pages/General.qml @@ -19,20 +19,30 @@ SettingsPage { qsTr("System"), ] + currentIndex: { + if (Overte.Theme.useSystemColorScheme) { + return 2; + } else if (Overte.Theme.darkMode) { + return 0; + } else { + return 1; + } + } + onCurrentIndexChanged: { switch (currentIndex) { case 0: - Overte.useSystemColorScheme = false; + Overte.Theme.useSystemColorScheme = false; Overte.Theme.darkMode = true; break; case 1: - Overte.useSystemColorScheme = false; + Overte.Theme.useSystemColorScheme = false; Overte.Theme.darkMode = false; break; case 2: - Overte.useSystemColorScheme = true; + Overte.Theme.useSystemColorScheme = true; Overte.Theme.darkMode = true; break; } @@ -47,6 +57,16 @@ SettingsPage { qsTr("System"), ] + currentIndex: { + if (Overte.Theme.useSystemContrastMode) { + return 2; + } else if (Overte.Theme.highContrast) { + return 1; + } else { + return 0; + } + } + onCurrentIndexChanged: { switch (currentIndex) { case 0: diff --git a/scripts/communityScripts/chatBubbles/chatBubbles.js b/scripts/communityScripts/chatBubbles/chatBubbles.js index e13d8215a0a..c13e6590b3a 100644 --- a/scripts/communityScripts/chatBubbles/chatBubbles.js +++ b/scripts/communityScripts/chatBubbles/chatBubbles.js @@ -29,7 +29,7 @@ const BUBBLE_BG_ALPHA = 0.6; const MAX_DISTANCE = 20; const TYPING_NOTIF_HEAD_OFFSET_HEIGHT = 0.48; const MSG_HEAD_OFFSET_HEIGHT = 0.6; -const SELF_BUBBLES = true; +const SELF_BUBBLES = false; const NOTIFY_SOUND = SoundCache.getSound(Script.resolvePath("./assets/notify.wav")); From 76f3d66b66bb4c4f0efa9c5a6eb1bdd2339c3483 Mon Sep 17 00:00:00 2001 From: Ada Date: Thu, 30 Oct 2025 19:28:58 +1000 Subject: [PATCH 069/111] moc macro typo --- interface/src/scripting/KeyboardScriptingInterface.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/src/scripting/KeyboardScriptingInterface.h b/interface/src/scripting/KeyboardScriptingInterface.h index 57da8a5bff0..b261409c302 100644 --- a/interface/src/scripting/KeyboardScriptingInterface.h +++ b/interface/src/scripting/KeyboardScriptingInterface.h @@ -42,7 +42,7 @@ class KeyboardScriptingInterface : public QObject, public Dependency { Q_PROPERTY(bool raised READ isRaised WRITE setRaised) Q_PROPERTY(bool password READ isPassword WRITE setPassword) Q_PROPERTY(bool use3DKeyboard READ getUse3DKeyboard CONSTANT); - Q_PROPERTY(bool preferMalletsOverLasers READ getPreferMalletsOverLasers WROTE setPreferMalletsOverLasers) + Q_PROPERTY(bool preferMalletsOverLasers READ getPreferMalletsOverLasers WRITE setPreferMalletsOverLasers) public: KeyboardScriptingInterface() = default; From 41619cee2e3872c4e7fda12baec661db9badd383 Mon Sep 17 00:00:00 2001 From: Ada Date: Thu, 30 Oct 2025 19:30:43 +1000 Subject: [PATCH 070/111] Use and bundle Roboto instead of DejaVu Sans --- interface/resources/fonts/Roboto-Bold.ttf | Bin 0 -> 146768 bytes .../resources/fonts/Roboto-BoldItalic.ttf | Bin 0 -> 153104 bytes interface/resources/fonts/Roboto-Italic.ttf | Bin 0 -> 152208 bytes interface/resources/fonts/Roboto-Regular.ttf | Bin 0 -> 146004 bytes interface/resources/fonts/Roboto.license | 93 ++++++++++++++++++ interface/resources/fonts/RobotoMono-Bold.ttf | Bin 0 -> 87696 bytes .../resources/fonts/RobotoMono-BoldItalic.ttf | Bin 0 -> 95100 bytes .../resources/fonts/RobotoMono-Italic.ttf | Bin 0 -> 94836 bytes .../resources/fonts/RobotoMono-Regular.ttf | Bin 0 -> 87540 bytes interface/resources/qml/overte/Theme.qml | 6 +- interface/resources/qml/overte/WidgetZoo.qml | 7 ++ interface/src/Application_Setup.cpp | 8 ++ 12 files changed, 111 insertions(+), 3 deletions(-) create mode 100644 interface/resources/fonts/Roboto-Bold.ttf create mode 100644 interface/resources/fonts/Roboto-BoldItalic.ttf create mode 100644 interface/resources/fonts/Roboto-Italic.ttf create mode 100644 interface/resources/fonts/Roboto-Regular.ttf create mode 100644 interface/resources/fonts/Roboto.license create mode 100644 interface/resources/fonts/RobotoMono-Bold.ttf create mode 100644 interface/resources/fonts/RobotoMono-BoldItalic.ttf create mode 100644 interface/resources/fonts/RobotoMono-Italic.ttf create mode 100644 interface/resources/fonts/RobotoMono-Regular.ttf diff --git a/interface/resources/fonts/Roboto-Bold.ttf b/interface/resources/fonts/Roboto-Bold.ttf new file mode 100644 index 0000000000000000000000000000000000000000..4658f9a67b1414dc8f567cee8da7772494277329 GIT binary patch literal 146768 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

  • 3&w8iGp1NGpe7r6CSgu{r=P(}6=_toPY*A zpVg7*bWbpG86SzCZgr&n?R=ynjAZ858%H|89#iE+>QFgh90@0qM$!ofSODi&sSIP8 z<-`QTZyK5FQXDIGf&EL_BG}=Q)iCzdz%H(%f}-jQPnt#(t6){_rZtYhXuC|<<0QfQ zBD&M_7wB)or=0GRJ!m@L1T~%XGTlNzjWj~JvxOK-MBi-|W`r7mTSz>pBYLS8-{FBg zE-RQ5piB~MU>~5iXubuMstX<-h#B}EV{xblOk@2OP^-DzQOD{Sh*8HP4b>!f23`PE zr{+HMSRDgi1OKA22u@6 z784~rlyQuDysK+O#Qqm}!k%Or5%-JLyvxCY7 z@R{T;l5gZo6Q6Y`5%O4W<3LO0aHEv7MmX@}1ryv#@K6-z4kX`V)?Oh&wWlp`!pE6t zw+y?mN_Pk7Qe%V)WOop5WOa0!pjIkcIf~=P#{zDh%6*s!RYe z&*ZgmupHzkVK{D(%9A-}LBPyx0d64)!nwpaJ23!IS*BG&2hzlG+aZ~+vO{=L zvJ9}l^eifJ&jpP^1OU>@y=04c^TBr-br!QyblA*#nE&A6Ah z@g2t@92yS3gz5LqhZG(Jb{_-kR2C_u}gR4ninmu;I$AZT0L0GX& zjAYi>iJD|>&h1=BG#24vG6uRt?cSpr)AqyG!4~>-uE!gbsEjIKI z9SgKSsN6-TtuUHsf4@lzr5k=6IvU`@lsgD+MC7nNx^b2Jgoo8tk|o=0#%&nIKzoR1 zV3vbbnsuN^MWQGK@XsKsV#ZC%y@T>`qX{z`L%+CLQw+Ff6;61KuRtU(!cznLO>mJ) z_R#U*{pQ)TPl^_}3(WVPjglfhJUE)LPFP=1rkJB}NylbtGuQQTm< z3Caemtk$l8gfe2r45-Tu3GXG-?el;MGu6vU-ec9vd0059dXGb(znCKj+@r#MIOGR` z52sf3D`AypzY-sb%N}*44q-o+JPY*A zkNFB23XTl=E!2@n_RvTqdlsnFLWGWO8$Q-1^H>MjNFK{TD;*3r7|T$7M&#pEM>{Bd zZS--5W4N_69G5*RZlt5BxE*1sz>Pd{ZWyo03`gVDa9O5~cSLv>*pat~oKW2HMGx48 zUS+rZ4y-52uVRfiHS`zIZ@-p+9f(lB}#HlNGeICCehrUQ4c&_N2)` zT@r4CedmB)^fJ6DiKzbJ7!qyY#T>O=KEA%P?16MWig2q=A7f0{G0-{P6y_#uDXg8A z=6ow;#ebpR;}7-?No~8PRj-+l$^zV@GJ{*DMOT_#A%5`vkShdl#MAKz^ols^>Dwz~ zC*3nke8y0iP!Q!*)~6i3$=-uz5RA-(OZ=qZ-3&NnFWbiXNcKeb$t)9x_(Fp}%O&Vo z6-xM(^AU$KdjduCos(;XTC4ITl~y&Mli_|qNKh9p*L|Qp-Y^oy71WU^;-qqrABN&4 z1Lrh&0_1%hd%WM_l6@VYQL;mc7qeaihpY4^$9}vK3hRM9<4|V3MjeYzlseV{Hj|YQ zl)J^CWkHpqr8l`HrjB(0vWe_{4t2^zFLf-s;ncDA33sb_xnL}lj;xMl#tTvw<`G6B z7?Vzej8Q>XtSi7sD^%RYKd}NpY00T=-XHi`w41o*1AjWvLG0un4QT?T`bHC0tpf)% zX}aYY3oo5HwJx&dG-Ha}Fh6sbT%hR()E!2sn1OzRNR#~tIey}g`e#rte8Q? zYG5Ltk6Xc}J)oW;VxJib(Ht+DkK0uyn1Ky|xzh|omZ7sc64}6J3m&{D-cDE<=bJGQ z0Vujd$app5MbwhJwAfJwi3AQhEUZS}Jm#dxu4y!Svmr0#bN-@YFFe8LrBFKbkxD6;!IP~eGbzDM3S!En6P`DUPdlS+MmfSIYU*4g8D zeaG?YWpR8)$qu%gX-4&=XwDf=3Ou*XzMuq;)o>MS@H9D@Pt$&3n>rGm-&Rg9R1i^c z&knvO55`SvuSQrbq&|B)bQn?l)bXq}HC8eFQTZ)>r?+}(E`DIXS`k(u2kL~IAD=6C z-s(o>bpwbdbgsFSovQ8vMG?&WF&qlJFrM#1?9DJl6Lq8m@ZQ7`ZJn~ z&T#*_Ds@aS3)B)C_?sv-2{jM-5)FH_<7VNIA$_}#hQn>%+9+TBVlChiP zg0%1@HU%Z!)}oCMS)I>AP1+%aHjv=%K+*TEi&v8&%WtzkE``rJw!Z1?(K5i+( zT)m}wnR=%)^1ltvZn#ESp(+#~^$un0Eq;5SQ8qFM{I;17`R&E`NWDusgzsvM4pMB%+?rY`0Oa2h^?*_sHhJKCsU1+bIFLG4Rmg)=M zV&V&~<#KEZ4l63=E3z9ZF9Mak!Lc|VS%MXIc>=gQiJwHebD^6yb3KOcWz#uSVfY@a zFdZfQ6q5JAbPA2*&G^ zgbz6yL+tQqkPaxI_lTRvYeJVtyhvP{h_klmgt4g zD>1_v{yEJ284-K@Jd%Ir7CrwAi(ZANCDoH8EeV^Z^Y+g4v*u7&%lCvN43OR_$=ZxeDZ+L<)8mwZPx;wRduEB zxvw7#NO&0@M&8d5KnO?zgb;$rJA^bSs366P0X0&CT18tF5kdQ`;R18>mkca!y&OOL+6o$ABbLb!tAg$p;-m4 zI)uLm&F_H}I**=mIF|M}nD30VMY?w~CVCTli?=&Cdk^JK2EDnFv4Rpay_)6r*n_u_ zej&?oWEO&wZjo~OA@H^%cs}M0y_TK|JrnCW2<&OSf=m8_mo0zwwTDSnV!0D4a>M=r zH!>!jueCqqO2_uCwsl;LB~%}$zmgv51u)wq^KlYX8Oq7ie#LSPZx!5%URh^6(jW8b zHx0dtrG5;LZaF9N%XxGEIO!VA)q1NPVy&qs2(TW!QwV;I(_Wm;>YK!ESK(D+52v0? zl7kakoX-A@?^b$O6_hue^Zm!y&#vD5((3J>t&83mvtr(y6(vTU)SJ7K@_*{)p&N zcx$2+^uu9!I?I3%ge8Qdd<4u{?J69o1%H+CshyYm|tA| zT@mVN9Z$h&Va{XgPC@H@me0+!{=2=bol9YY=}<{d zB!ooo(0e8i7aL1*dW<&%(qnQizLdPVJ+pb6-jw+6UgXU1@OpASFQ!5r=v7Y=9ob$@MOTTZG&mSQs-NOSKXmeU`(8&LV(jMf zP}XUc(NDxn8I({O)cR9rBeDnO@AlmC(`8HkTT2Q4P9QEyZK3$0sV%HBebpAWL(keX z@mwE3>HlNJRH#Lgv8NWP%Gs`CPq6IR8v~uakJk7p_T!mR|KR5w=`XVk*##%1^0B(_ z&KBO+tqro7rXqy)dwvfYi_P zZ)81n(ag3Rf?vyuT>l>E->ag&{yo8qXvMz@uDimlPp_BpSqW{i=Z90He%?FStq*iQ zjj0BwZx*UauB?0;)+dRog5ojuiIWMDm{> zE%rn9+jA_pnz?Zc2M7F=c)PvD{@L!9Q?fxJ_DP8co~)z87dVT2qEu!#WkxDHi?qh( zZXq=k*s8Dp>nH(ev|@=xQLN)d7|Ki*(4wg3A20FjG+IEPxS2ce*_~#(ZREcRS!>yJg#UfVFV^y9h?o`4SBeaJg^+R4jxbj5A_b{ z`CUcMdRb-I9&{?hdRIng`#oV$3?bfN@l}elu2w15o2`&yLid2Bt3r-~ds1CMN8Luf z8{u6mW2+X{JF{EI3f6lk_!rQLc4JRpk|xzWxV%4T(P%s9FRHeq!P%$&qU~^F`?!!v z|EpDa2v}kjaN9BSYZ(DT^J+q)R!G{ZNcGN#Y^Q6mO3!MiVo5vI!qht#vz`wd)(<$JkT&x7R6FPaey2RYKu^di$Xv*i z7u@U%ekb+GccfcDx%=K^(CxH6TZVP;fmBzt^Zl5vIhIo;Rtf7YoxEG~3HjHv_WC0A z7nzK+myDdas-~QDRo&p+PdT|g;jd=pq^lC~vN=!Xlt(Wcp|M^zx;_^Xjt=w zor%^G(_KxS^jx9K;7Jaqj$EA}_CxGmbXU%t-yC$noR1SKo$?#w6CuoB_vOs^&HgCN z_&7Pz34cREr*`yd^rqU;rl1Rbsk{z2ZM|GNNou7P4Q~LITG1x|X8aZN)&y)D{vn}K ztiMmN+qKpRy*c3%NwVA_6KFkH6di+48k1~BJZKGD%*l5v0;-%TH~HN z&&J}%auF$Wod~%&10_CJzN4%Qp7f@1akpVloogd@^*iNXtShDN)*+Hx*5l}n?7ofV z-VpPj0^RxX7?S>I-MbmQxhIG>_uKH=$QW1t7C7&j5thG8bNe^tTK*OwpN@`7;g82T z+QS&*i`s}~R6Lm}N-Q2739M!YGBc~5yNJTX$(xLF!Q&xPmc-(f3?#;tOt!>&TZ!2S z$SjG`^GPHoUtYlRZ)G;y%1o-HXS05}HrgW5PvA{gAkF^k$S#93=g#yCzCIbz)1plh zdjz`aG4f{AgtO%lm@M|D?a0>^&2tv7$vD3tZr_myj zGq+~=zY|S@zcX4ct$ZLQ>l;Oz@7!!U6HNl+c#URxJ2R9vDBApCVnbXbfpHldqJ8$7 zMXPmgHSH{KQxo1|2}m0ht#*Xi5SJl)85=_F;29%$gI+NQg=i$Tq~OUw9?jaDzJ5=g zZ?jUw5*B@-*BkJ~h7=ioCt6eRq&rTvW>1~5@$32>R`>4kl;}?}^{mO;Nc}MZ464;1 zte)MT;T52|EvC9!<9>w7o>Jq6)~FY>Vl{hf;SUO(O_A&C65U=IZOMw)LF>E=IR@NT zW1ueD6bz^DICg2$nxGJ^!R#mT;#p&$GFtCX;aJg?T(iFg_Cx4!LAy77b5gIa3}u#7 zyr!TZ>?P*EV$j+6GOi0bODbBEzg=g^V$^EKieO!wl39{i(C|N)Qy@{j6{fpAwMfuD zpiT-JG<>ekk|P0THQXtXIE>Ui7Olp4g|j5qR$y&LqDASufi{J+q@vZ#(OFVLF)|vi z?UY$kd7JONZ1TyQI9!z{Xq*>*z*$n!=FieuQi3ru8m?%sShQN_RfG2{WZeg}!8vGC zI7=#8?HrvYg}01`Q>IB?5o$PcfmqpMwi^d>$QHAm%?_fPN-1-e1iona44oxK*76;g zfvn{_ATa(wzpFvtKA)0VXbZKd(Y#I#_sXzDnZCF(fFa>Me(O7anr+hU9188l%@xY>Ec!n=ovngLq{)5y;?+jpBZB)KW zEY?ovPmE;n&H@%Muq_t7@&yZ)`+K~#ssfU=+%*_;&xgG&QZpm%uri$ps;n_r5#8wT z_P1NUWXyeuG3S-ySPm8{8FP)WOYm0*T`Bvu+5eJb*mPGpCv?j}2d{C^ofFf+hR7He zC%deA)t_F*u;Mjgik2)f*nDNJbk#dwdQH)q{QGqb7eY%|rSu1|!uj+kzBiWn0s0fW zy>SoDNlK`I6^sc!0@k*ess`szkR~!)XHK?SV^x{{iq-equ&SIbiZ%FXEXjSOd^H8d zjDvOi0L$8B@)cfc`D*eXz`Yyz8U(CuiC!yT6D`(G=b)(ol{*}pemkdY8zt&o+6c|4 z{ZNoffMjD_8l!dLe9kb+RddjX5_kJiGk%;U$zY5xGoAKVvwtTg=?y`RS(0?4M|o?T z+_pCRuQ7H_Ha_)lxVBJi&2Q!`8k8!3bV{DgT0&L+?l=tcFR8l+ta=2E3qq(sA_I4)glc+ zq(@dvk-LK4<-pGacU;7ZcJ_+4dH==U!MNgylP})IxGS8Gj;2Z< zh=vBcWQI4h6w7@MvPF$$Cpk{y8Ipb$-b%Y=emAq@8V{wn3RP*_43*^MjQ7~Ug7^5F zD8GP68F_CMUO1=r#IZ|WJ7gZofvxE)vF7nc7`n z-!$uveLrdWz!ZN}*HBv8P#5hoH7!%T`&RAWu)JwaRq)u6vWgXx%P%Z*E1A(Sr{?Yz zc}dM$F%nt+|5WUz-$#of8RS@K>zq=7ES)Le4ct|}eOyk`NlGiFZODw;fK+&WBn{q2 z@*-TzHs$^$xkW?#PK<&_ihvR#$9D2WT{enWjd#o5bvsLXyMO!UXMR~U8EN_8et|Qh zU>11M7tuO8$p0d5qNT$8jv@X@-d>YfAW4@S=l1NRPj-RDKAFelL=Z0?{h(aS+$4iv z&ZBbv5j4Y3{}N1G0L4t|FRm!?!&Nyf=nHe?Acuh>G*CLp*r(=%gPlh|Hy53nBq+>% zaxPfOo=p5wkVUl}vI%x*XZ)0xmlt|%+*M3(Aq^iq;$3!YWj3MqjMkA z&+FW|A8T;$jow}RwizC6e(~wWnmPUBae8aj{GxH_WmY{@c(Pk4A+V8 zrg5)^cTg*kq9ZW=N#4mPw+%eUmEM!w90jGFl6IIr=()D8^x4%ogzID$MCX{FM-N4R z!9P396Vch;K50|rn{Qf%xAb+xmcP0O+}B#})LuIUlEdw9c6}%LaebPEF;m}U`yjnm zc=i)}&FE;qsfhNy_+qpV|K9J-ZkYnL++(JPIg^quW6IaH5a=JgIB$YI0MH14>;5C-C4*H z5jx-=Wt`3WV_DWS<5K*-1h^rrk^Fu!ey?P?JIhz0yiDIg=pw(bLV0%Z67mb5%5q)$ zKmFk>7n1&m_;^l^ z3>9{bix%a?$5)x*+$EWII9RrKoAqdsoM^@o?YI@(N-Som$lPtpiA|~>sCZ)I?DE%R zPL>lYas8l6sjR-!AEG@0b5!6w1^n;wj=<|j_84+Bd51JW=#X7a?llJj5Bp=G^XFmB zEawVx+`)rR;=KpF(|EIm-7xVa%1HLxa^PX{BHWm`wZzjM>n_hBp3Cj8#fxxb;!X|@ zpx)aQ&;1ztU%`_N@Pn2=-786ZN5}l#!}j8Q8TO>s(s|%-3fqfa2HVj!Zm(=IEq@NQ z18W+=8ynLV#dzXWoaw*C;O`CM)hV5Kd5qT${Oz>$ejoMzfq2kJ*4s!t*A$>%EFN?W zdDDn@PE6M+#*+oK#f$z7`A()U;;YN#e)W+k1!>~^2d7-(K{(JE$nTr|X5Hb-iZW|= z(12aUL~7BQG2KVHix}+--HK$q3Etbp8ywRuRlL54*~r41f?b4aC01K#U$g%Jv=ZZY zG*?-fQ#1H5XW_xOy}$Y}4g)4nMEgK@Vh;x8lWAEcv>`zhc}#b+-7q)V-E4dDSY?jPg`kps*%z`~5Sq-)6m7w+ zuJo`XH6uHUoWr~AX?bi9w~w~a`*olrSYlDmKPFv2Xy-xVjaRzhP15<@;kc4DDhg5W zFSsvS6w^&RCf#&s!2QH4kL%q>x_H!D{$_x`mx+fv9rCyASai`+oMK!NydfShXMzs- zd)jbDlv6y%>6B=3`gLe=Q&5N~FYL8w(Z7n`hDjfRK0O@t_7VLxLH|B4I*7jy0{x>w z4}YME;=fOi-o)R4fy;aBG$iIIa(|2XP zvd*|Y=!JUm6aeWrMhAc&37?2U358Fz1k;a?w}DZHW!17o)S+C-@;Lk+N?GCtVtOR~ zM;S;cUkQFC+9{NHt@w9|oC+m!O3T56EFVtFj86(BiV43@Lc6X*L`^8MMDh2dN&h9w zeNewd&4dy)6Z2oq^2hw$#qZ~`j5P^<-+&qY1Y&;K57ozsz~Es5#|?VNdrqjNH!vcy zmGRuY~G^wBuo^TUx5p%F!0#Wi@NTdL6uM zM=UktOgiHHC#gkr?kJte)~RFZ#2VJxHmOx!SA$Ndj>NBQgDb4h!Bs1r;eOKU2^&+kP`ry5DBd+kz{RdZhs+^) z?nD0f%>-8BBLBc}$v@x>!v3jSMPB#(>jwAlKXhooTqTn9e-cxw2eEWOnp9^QluoHL_J~)It5d&SUmXJYVg_oCJuNv&HW?Z>yY=g5B9>TH~gvN5| zW=9XHWc0uvYk|0AwVqlQQ49`SN#t$3@n6?j{|t z?nU58vT8&9y>eWrP~2i?s&+{_R895S&kRL4l@I@HaO15 zJTN7}nuZAfY#)FfCSVVnVfa^qmE-|{9CXIsVX)U0jfj@grqSl39XoKUD|0^A9s6nk$`>;a zL}QI&G%B$tG3tHa%8o@G!G^u|BF^(kntAN?&Mrfq~6Cd3X+u+;h^U^L<*ee$MfT(1G^wFwmo z3CKrCu1US3x;WaMrR; zxgiI-<*NOhc$-v&qmOczmB`KSSlr8&uP?eRGQ}f57f@mUQM_qV6y#HGX~KqePv}jN z-mqT16L6z_5~BN4hBtgk9%4Xzyo+aP6H6y}S^%U;t5!{jAGq<_Ku|qBjf)rp=yDk*YHBvzEQA{%RKW8| z5H$&8LVw_dH&Oa?_~*y-eZ<~J4w9v~c9Z$IN~otvWG_jA&m!_INhJ&E4KhzXZJ=&s zJlRZEkm=-0vI})qkjz1>PslW~l3Y{%OUVvLDyzvPvI^#-I+l_(WSf{lyih7x3!b>+#{z*z5`R($z70uNxP5U^5{HYDA-E{=8VC0?TohDK zf;$x#MNaeJE&xRoCHxBR8nPDddeF~F3N8xT_P{-Wi;_dQC^ zUm#z@{T3G`m%%SeZsMXO0~aN~kb7|NlfU4;#zlz^Iir+Qv;u0PE^rIbB5-}E4_pf^ z2Ddn5iqldw2zVum)=jI@s&H%3+HmX9hH#tErf@@ODBL!*4cu_FOG>*?aD&Fs7`WYO zceuT2Z@7JFf4BqbK)7)fT%s50PmtaRO0)$N5O5FX0XG*z`(*`KA-F}DH{7DEC|p10 z2iMB{;RZ3##>%sba4R!V$m+BDa2v5kaGS6uaGNvG#zwHwaA&YD;C{)#0Zur$$`5gH zl^@|};hyI|!oAF&!~K*03HL97)+U5N+Y>GVREb;yIzTKH%iyjME8%`6P;0`bxf7~+ zYM@s0)&jv{yLKrBF|h!=ASn!Wy9oj$`KsYb)-|40+~i>W)5@dUjjE%YKdGggM}DN6 zN7M>753jA8ht>YbJhY0@Jfx;>PN><-9ABfAd2kKg{Bc#?99R9Uc~EuTJg{0<^MGn+ z&HbzC=6==6nEO^MWbRX0H}|fnn|oC}YwlS|H}|NZo4c3S&9UW^%rWJAnWM|<=BTn` z&E3lB=C0-Dn7fqG&7Di@=1xJnxnrrb=EzdIIii$@xkE|a+&)k@w<}TH9A2V~In2MS zxvjr$ZsVV1ZXKYTTLp|Yw=AxkL#;vP7S<$lh*dW?x3)Jo!*kPOy19u(H#fEnHaGIq z%?29uIr)^^NWAy2#96};Nz ziS&x}sO;WOt1Q~NM~X;v%?~`qPr?uKQe`} z25llz1fLlhpFo00P*9M($0rPt5AxCOAu;#vcQvwymHiu4cVURLi#svO2xtQyL>+yU?2&|EY( z^`|9hd0L$Y(=ghN_M)HCF?1IFg07@C`iTBTb>_{gu}HR;onWWfRrVChjB^*Bi(7dj zpU%JFE4hvD;-~mI{tf?L_=|F)k!U4i#2_(3OcK+?a-tXgeeCspKzGZf2JEKji_#e9ne6>C~7s#p*I^i1~R$Y0`(uD^zT z;^cq@99RhsY(@`x0-F36@(B9bi{_yLG>}%HHK1$5X%sZ5odYZA7I5GteZ>knIB*;s z_z@f+Ssa)E4$S0p_*XoI@8;?JYjA*w08w5v7Oh3Bh!Z2l6fqq-Y@^sJ_J||mv^Xz* z6hDg>8q;$765l$$V|`cnKJk494iJm0#mkb{QqW?t_=5u#Ej1h*NU(fmS!+oG2XwcH?QlF~AAE zfNs>aH-Egj&s^}_9Oh|{Hw|f>*FV1g;q|H4+g`7Kz3%m@*Gpd~g0+Q-lET-n$m7pf zZ(jXH$gACet$=lam9JL7h28n;bHFsjY5)iZ-j$Gy*BSdV24*bGU>Wl>7Tg?sGydiv z*mC`E_Pg2VX7sJ2HzRNDz1jMv|IK3Xbq5%4k{kMsv>Qn`Hr!ZteeR7pH+o$^dTrD7 zwCg*s+pZ^HUwM7v^?}#>T`}hU+AX)?BD+d&sx|>bE648%@K-2V03bLAv;aOZM?9eN z`CY&47__s*(esXlyTI;GVZqTq(C93H`qGcJ1|$H+0;U6I16BYq68QU%t^T_o;?A|h z`@;UwkFiB;F)@J6Rgr z#dgDzn#_)|<7oXSA^quO3Yp4Ivoq{0JIBtGMD`^ttm$k%nZe#L9W8@$C|_8Lv&d}j z&ONv%G=&$>&GW!!{DSA@`FMU_fEVP2VDHW4C3qkY;-z^RUY0Fj3)u>`hx_vYUXqtW zJKe%p5*uHI*0maT+E$*#H*r{9Fa}@Waxo<2pY{zTv0Hw`jko`5D+K-;s;t zdwyOJeu4Y|i~dJwwr|NL{vB+-pU4${k)I=1`S<(>{v*ExOXeqj1-9UIewAM%zry}| zKpyhzBojLJ5x+qmlPBaUdB$&&=lo}Wi@e~s$?rTvP=1Fp{tMLUU4D=M%I{M_HEQ4w z1mh2>QQC`QiQq!eg0v7VOp6Fj7=%d}#bR2TmZ4>7Ia*%YNYGssrR50Q$(2@t^;nHo zht^#o+=RRMLd+2!!c(jitHdJOf`*C(Vj=7+FOgf!6<^Z!v;&P0KEhiR6~4kOEW(dQ z(vD)Dm@le|YP1vWEUiq~U#n?1QC+N|gJ4mPqvORov6iNY&*@IcU&XAe3mHRpfQDnR z5hK`CwwwLJ4ZIky%-irDsFCkQ5ivvT(cHCx+H`}#(8~~yK7Fm>h~Zabed9AzkZF$T zhKs*TN0-SiS6of5MO{N($Gd*zddZErg}Kded+1)(eVO|K_op63JR&_Ndn9{Y_4w1% z!?UDkOV4he@t${b)yp+2*L|;GuXwMF+}7Noxu@kmnEU%YweyV1b1JWY-f?+%|e`5Y0^4~8|v%u&As|y+nwlBE1kg<@p(7{5_3XdrKL*ZLRSdooI z_7-{S?d{#(d%yQLK81X0__X&K@3YwFhR*|^*F`;xnu`uCda~#p-=@A(d>@(@S(v4s zWvS&azi7Ytew+Mm6!R!nu2@{Lqt=?%Z>%qiw=DjZe=h$){yPFp0Tlv13s@a+G~i6Y zlM-Gf>XjH&VoQnhCG^0&fq{V)0=or{3S1PpIq-4dpC$8@tY30X$ulKilqyiFWvPv& zehZ2SI#aqv>E5NUlnE`fvdqo04a*KJd#GH^a>L7=FK;eCviz3vnH8E;m{pNh>{sz< zrFxZSSNgSbXyv7qZ&h)x(!R=;DzB@?Rh?J$n`&Ow`c|7??b~Xvs#mN&r26ja&uWC$ z*i}QXIke`jny+d#uC<`Hsdm}gF}0V~zEJzsM*$x-{OC@dymdnB46U=N&ZD|j>JF-V zJh*J|kl;)8%GLY4-p}<*)^A%sr9tin9UAOyXldBF;k<_T8-3JhUE^GhXEw>zWI^_1nbm@|7}nxQs7Gk+(2k+gLRW_FXgQ$ep_W%${@JQltBI|i zv@Y1XcI(lt_qIOY`sdb}82&V9^I4mNZC+aji($pX7KeL=`-N8y?;pN8 zd}nxiyL|0Bx0~OdweQ=0R|l;_p$O|BpQBR|Jv|seS7>}4TF%4rvW7@}bi|HNnam>h= z&tg``?1=d$MvpBW+aq>h?6BCeu~TEeh+Q1JIyO0WN9=*vld)gNUhdAjm+n5H`@tT? zdX()kxyScC%k>=J^M_tVdqwwJ-Md`x@x34RDb;6KpBa6Y_1V@Zy>GR??fOpdyR2_| zzwZ53_9y+z^^fg8zkhoFANoHZkb6MU0UZY{9-t5GJMjFV$%7`xt&Mx|vH!=-Kc4yV zFN328j~;w6-Z#E!{Eql%37!dE5~d~WOSm_r)R532pAK0%lniY+bmh=X!%7Sb88&*@ z;$dfo{W;uYc=h2^hHo2DVZ^u*heli*={d63$i$JUBQJlF_miHVY#vo`RQFM7qw|lB z9=&?>rB91|+Vj(0V*EQi!1x8@Uwl^O zvmu|QOz@vjVM4zNNfRDV44gP+;-yI?CQY7nYjVxW>nGoz(sat;DchzRrut2tKK0tv zM~P(HVg!oc{BSpc!*!9G!7fAkZkIcO|_v)8TzMT7I>O9YR9p`PIUt)g3 z{0j?8FBrWbb7Ad;4Hu>@ytAnKqQQ&KFXoGz;%dEk<>DtxYAlIa^3{?ZOHM7hvgEg= zd}+a@WtIjn4O`lC>4>E>mabTuy7c(c%S#_FBg^tG3s_cTS?IE;W%0`uv8mN#AAY5BnAW0%iezHa%hS;18n236)qmBPRiCd~y=v#G^i@|@{q`06 zD*so3UrqSx)#_ZU{Z?0A-FS86>i(<8to~y4+SR*OpILo<^^-N)nnG)W*3?EHX?X|7e#;hHLrCI^-LO(G(Bl~($=K?N$E)!lkO({waH_Xc~hlLO*VyX>b7a{riq*8Z`!zN-=_1M ze%|yV*(KR0xmZH(>u+wix!2~An`dlZzBy&{kmKQb`TYg&!TP<4?TbQkkZGkPxcEI+H z?V9aT3Qx(O;+Ik>rAbO;N{^IbDN|FHq}Wo9ZuQ<;c5Cp~u&q6}j@UYF>+-FsTMumg zcIz)&U#5DdT2q5kE2Y*-ZIBw08kX8AH8!s7Td8dox7FNMe_Q))eYTC-Hf!6eZ9BG|+FovZwe2C>!?t(YK5+ZE?NhgZzJ1~L zmD{&%-@EsPwoDCcgF5N_jv5_+f#W@<2@bs4A?Vn z&zE~P?Ag2L!k*iE9_%H1^X)CMx8~lKd!zRb**j(LlD%8@9@+c--lzL=?X&Kywl8E~ zw|(*ZChuFkZ_B#|Xh#ty! zsKlWfhe8iU9~yFK>Y*ivwj4Tq=;EQfhyFV3dD!o8<-?5+cRW1s@c6^?4sSTT_wf0{ zHxIu#lKV*UBh`*HKhot$+>y_Y%saB|$jzhHqfL(vIJ)KN(__Vtbv*XPu~WysK9+Ip z;j!n(ULV(vdmPVyyy$WN<7JKq9}hd;^Z1D4Gmfu3zWw;A<5!PAKB1i`e4@;WdMDbQ z=yPJoiK!=+o=7=y?8K!L4^PsQ`A-I(taY;0$=H*_PEI?y{AB9M6DNN<`P(UWD*vgH zr#?E>=2VYUBTmgYwer-qQ^!wTK9!ly(hH=QO0SdNHoa?l-}IsBF3hFPrsIaCp|O$Mf#i5hSQ#>3!E->`lHjWPsg6_cRK#`>`$rVPd;IA$~i#-L=Y>6`U zQr(o`zy&F8YVE)^5@0Ijzzw9BNv;LRG>s(Kc+!EJh`VvG19u?-#(56hjpQ?qci`?h za1UC{5asaqBvlR0^b2Rxh9S4R!Pg!KbHJ_!H9I3*u&&^4aL|CsQIi3y8j>C=FxUUU zfosH7``LjTh^O|Q12+h0+?A*C_TZ^xUG>g2WqZ$@fkPTKC@oKz-G zUWH(VqpHKd1u#O|VwN)okN_w{8o(Wh^`{hi;R{+s&ErsAKu`|s=F-*NrJl>Zx!WK;fcV!w}DEg`>yAs15e|K2+O z?qkW(Vk~d*{e5w>>2q@SpWpfSG2g9=e@;7Ro&0m`oOScQ(q;4fpVP|b#DDXav&P;| zKU=2X_Rr4a{XG27{Q59&K16YLejkeQPxNP};H21jf0x#r)U|~^>jiBlHTPhWop*L> z*?vw6a@J)B=#1{rRJ}JPQc}))Q$BQ}p#fSRbTnc_)SL@EPLhjRpgk^vJkjXjLt6Z{^58XUei% zv5KmAxTO)RJ??{%dLNa$g;XGA6Dv(*S7=Mqcqqu@CYZh}7+Ut}IOg{AE9#^Ua(+ za&`$M*JLj&dvvM82BROh;N6~ZbG}{nogUpOWjUqdT~5OOA?0z^AhnQMMPLA(N1LQF#x-T6|3Ya zUP)vuDTzTy^MwoM&zn4H;)Kt}j~hGY)6t_o898G3u%SZ|;s<{mH)!C1{{8y)>D{Ym zkM6NC(NW#HcIn)yV`M~!_U*#M+O%%fGPFfV^JYz(G!8U-xw@35DQ>O}tPOg&mMKkA zT-|`Wl_^bah6XmH^0c)IvS4XC!Y`z4M5Bh@etwbORzF*?P53vG5Zyg7))^3qB=8pA zMG_%l){r(GBP@*)qtsjQ34iO!9$sChsKOkWjWr05ur&^XzY4KGH&M@sXn$<_PH;14 zu+?HCtrHWwrx5OsWP-g@s6w;`(;`ulNUJR-$m(Z}=z*kCFvRi;k7@wS-HB046Xak? z^CB^T*bde->cBci*ep>!Bby>NVg5Gt7nVkU=;#C0Y8)7Z>&9bpd+>kAzm_M7h#lub)eb)1D8;d!LC}cDcB|0 z9Xoqa7t(7he0EdptD_{qhK2DM1Z?zJ1sm@ZWMjm`1{iyGqZHOM$eJzIZ6Z*CjZ$dKpeTrT2p+kABMZdZ z7918K<3@Qy=KUfg8-1KdnRRfKvu{o2#`) zeVd!LepX0bQr8}0kRe8EeH$%c=W`=#BTJz^iLurg$Xsyih+f`3BcqYFE!Y}u6W03P zDT35Tdn!axh(;--We`de0;y{i)VdRzr7XTBF|nZ~C0GPR$41NNhJI-2iH;y^!-le% zI^SqyNwft=$3`JWqevCGOlgpi*vJ}f=?-;);(}LUR=KIAqs%3|V??5RcWZYmcp4m> z7>#0kTVf-<6C-04k3kMdBV|f!+4{(#j+kuq{;@sbpdrLWS!3)TvOT`**Xv!6o`{vx z&)QsO2V$E`s&P+jZf(>ZF(gEHw{ggwpQSr?+DIWGt(B(uU?e1w70H4+Rf#8h)sm7T zpAfE|@PNNWTd%htduKg1Mlggy_?NZIicJJaS&Q(q_4T&(kCgIaj}mPg8)Hedcwya6 zI!ex(Ahb!8P4jPJ8yg#qazG0~_TbSR9+rq0NG}p>9F^#lIJ_nTvU0KwMBV2|5=xYY zBWva_xnLXH+7cCMiHd?Rx&c3Li%rAb(lc5LmsGT^kw5-gL)+sPofw99iEJ|7HY0R? z&*&ajKd5u~MJm3k>O=0$At4bq;+>diO|(&v=-(KTk$iy75YSBC@D~?kjqZU;l=)er zd#HCCgIvWONuPHkt6wCdFn`H+$vEhT80o|&LK?F9^>9uESkm=jp)t`&M&E$`Ywh7Q+4>ro;IMY-(h_=52l0dG# z0Kk!z?Ahd>DoB*$LHL91NKbW!vH-H?gQL;vqpjX)`oY%dfurygWsQuK+2aplC*M#> zC)yL1>_W<>_p-u~lRa}cf14X($s*W2?5?Z74Sy&UXqI7i#vu!MDSBl-4(@>}`2@}> zzA4tp7r6gqSrKTYj&|3>+txcWsJlHegZnA1&0-4+ zLbsvHHd(z901=leL`o%XY$c5$Lk@%#AU0xcik&&urUXsP+SJDIn1xxlGD1z(>hk7d zt)9ZD5!xO)*y`os4n?0B8`a(3HG|8fx_5199DyIGX#AmWhf38H9-(;)*|N~0Y{TqT zie?4A$Ou{C!=MQzx9prW$*{z%K*>n;vg9_JrgDb{nULP!-%2$7yUZX8sy1ydiV&%d z0!)&y4`+qVHL`-5%M5U1_F^=bMbW@$wCTje*l5^PT|8wAbq~l5|2!yD4HT`0qio>K zD3rUk%pEkK!0ItPLKfc*LZF1l4FO)@;~~3fyCKjE1wRCZ=Ab)G*ChiTvF)q_f03S& zb4Rdh7&0;W-msuZV2ve2Ay#7vj+W%+Xr=D&XnjZW_BwTWJJ6bynC$4SSt-h=6zUH1 zS9oj4F~H&lcGXl>7l4xBIk9F6H3m2$Kq9?R%$kXbZdQAjVwLKD7seSxnUf?E+WBn{ z+h|mMVxr08{UN4z<9nzVWaCFIdt}{ZDvsvh+Q8=4K-w>;K%0xyLuFCFqmM~(gH$MU zB1dV^C0}-Q*fAWn;W*QsaXxmOikv8c^IN?v*i~QKJ`l3nB`yqofbfr~?Oh`hBZ)Nqo{!X5U=2ej4+u}R zSiEv0DAAG!LkrtP*%XB%%&Pp*iyHzQv1AD*1|=ri<4G&roh62ZfeCVq=33p`6*Ogy zxE2Kcr(kqW4Z@+X(nxRXa6ie5G}6^N0_H*ktIcBR45f&!K8=L>L?$MpOHH)OkyLv* zw1F!FX&Nc-Bkcoe%Vfnb>Vwf)P7ik^fU>3ONj`F@l%4OmtbB(dUr9ltGvhSU>%G}Z zk)xfYtNv6`(+GNFtDO)L;K)0%b7Dsf5dG4Kugu;-k9_v@k!oBKG)EFd2%m>p630*O zu2efL3u9ep1Q{+Ckb+oms-^8CDOwTYgFT%i*$&d3?b7G-Nu;N!Ny^fD`Yi5AqF68~ z$!_X{5WgMZSGi6FC=AF4XaHyphzFDdOkg#!MwDOw3STP(;JK}!#E)Mk6AaI>K2}X% zAfDknoqVLN=0@_1E`a)YZcaLBpOF0cIwDZC$I8@i@UKMj8%`r^B%mW{r%`ne6MrJ? z1hI_Xf~{k4e3np_uskBA~c><1$F zYvO|asI^2S(tZfqx01p9scz$U^)t9%XETXEeNHTFroK`$k~XZOenpH#K1=nNT9p1u zlq5y5w>U%G&~Nj#3~k9weog=0*i@e9{N@7H3`$!kv>{A;wHM0@*-Kk0-5UtdIRxZ3G6IwB56l?da=c%g{Y@56n>6wRBoHC5CP znW}=GKgd*V05O7>d9{=J1mhunxj0PPNI_nYFp;5;F>KIxLcVf|9+17RsEeL@igsI; z8P5Rzf;88XA-h=17A;7up&#J}?Ah0RNT^{L;qNNz(5Qbcrs%tEg5T*9zP-(uK; zclzQP=@_F)TfAErbzQ*lka%f7p>F;46^Osk*o62Ssv?bp_-Y92z%Gmg8c&hJ{Ixy} za<)oqPr9H@ln}4TYRPldPZ#jIq_Gh^ckGrqxl(-csb zke4k1Ul=^WxBQ?Bbch>h+fksoHOb4rMBV*_vJ}M^ppp7=V^Mvlb`Jc{k9Bz~`rh2am?yCScz)rwnz&C(vfTMt?ytMu!`&q$$#`If&6;fXsT0sxnh@WW~ z_!UYj8;(I%5=axY*M=fL+Uf#QR14KFYa{ebEfxFQzbCnjS5b$*kZ0l^8HGJ1`whFv zaKl9-^^LT~S^`AlGqLkvRvig>>yaYwuSLo9}#Yp3pI zg-!oMyQKfYm*BY}?g#Y85CJ!lg6A>V2eKY_`Cc3;!l&!M@r`)yt}j9wW#yfJuHO`U z;O7rrx|vkcdXR1+Nq?q|BayPdGnUm48Uv8W4CF84bw}S7tB*D`($|Vo$Y-1WQZ$AC zX8nL@MaF5pflngc44>$K7)#>4IMT@Ag*cIbXzb^SLwGm+s3@X8HS9(jlJ0wm_X*nL zbCk0h%DY4V-0(T_M>=AX{s+EXYb=srM+70iB}!j=;Clcc;~#j>MgJar&ICLMpD7?W zzz4tp74VJ2Y&Mr_d>``h)`yG9q`c;ZcFWP81Bj*>ly1>PTZGI0Jp}j3`Yf7>FBry= zKcTCNqYt@_?-JbjUG!-;(9eR0d<>EBCuIJEZ9#+FLq9}9zZBHgkj~m#(n*A(ZFPg} zR@KKMp9`Xyepz^uDl#v6Uf*WujCvVQLYT{&r_d?u0AB-s1iVJuYzXiJGy^mMlmpyh zHFd&j00K#27O0PvcU6ugQUiLk1@st)%@ZWzQNF=|;m}uuppl51@M5ukOmhRzA!pKdl(wK&5`7*3?|V!8i>=TvNt^D206S0Fc+$>u zfVA;)LY93eZM>WyZMz&Gq-`hdw-14|>0r<01ZmGWz*F9{Y&luqIRSPY`hhG^b{uRu zWv?Q?zujfIke{?;-+Py~?K99L%PeX7hr8Ac@&Oz4gLl}*%0896^R?T_s$BNx!mN=# zn|bP=NWK~tqfYapj$lVCn@rl$uxF(`Ep1x+UEiSYs~yk3qF!NVOa8ojhu>eaUL?QX zy-QnM+Qas{zAWn*_PMl?rJepC?@ETi_nh~W7?+^lWqthHyR`qM4K8Iw)}4|Sj02=C zF2@5o@6skVzy^lR;)#CU2kn2n0;y-w{yzvPztkl;LF!uv7?titTT%K|>e!qh^>|K@ zdOl}fIb~7e&`CM-apF?1zXj0sIcRWWuKIfUEc*i0CqT!_{=o^dEoVVa`efhY1lecg z1j!dC2<^a`h4(X$`>-reBK_90L)E{US_32s&PNrC_zfj>A532To zddeCDLT29``$-v5^@?$xlyikkyC1f`w9k}{Uz;>l;}^+8IZndZOzJm;l@Kya`s1!_ zeAy;3zK}BN)JL+-sQy8=8QAez^vQULFKv897uuuJRTzt?ajDb;l0G>GmHJ7xd1&te* zY+;Q3QQvvVI%s7zgV=zNKN?i{0k=uqS|VG-6qE* zu;JgHN18&q!^UeMrjQA^_sQxDo&A!WJCbe7(U*d!2br%v#K}WPAM|@xpF#9)a;^#S zIqaDHa=s_)?&wd|Jd^Aj-|8dvAG6w%GjC~^VE#$9W!2~D=d$`X@P}b8NZBfwi&FDJ zj=QrBJNqDQ#hc%y-sgYn3E)XTR$Ct+*62$yCa*5G>q)9lfE_CJz0#o${VsL6wDpzl zmU>%`0i;fLj2Bej16=hJ($0tecJ>R-zChaOS?NnVR^|a4UZpF?3^HA5=gYLE-jeBK zETU|E87}>hhqB3~?JsS9Ic_*W+@#GY?M0cd>#N)X{Ra*iK)rB|R)6ZMTp?ZZ zM!A0=a4>l?2siQ|PP%vE2gJsa8}aewE0D`@E0XWvRwC!%RwgImRw0MsRwaAkRwFy& zhs4AaTl|o?c(O4;rm$L-#m)uH8i9Ad%FUzP+{*P*ZZ73|D%V4~I4c41r94sxPx30< zN4fau1pY?lYRZ*|X;CF<3}<&&1*v*Esj?6?4(ae`a>y109Daini$VW7v6^Pizv&Y?@dvHsJJ+BsNxT1_$MN83n+s&LE2;3*lgM)rD8KEd7J(%Ib(o+#6b$Zkwy=sF$Q1K zV5X6E2mQ>>DMhX8+o`aG98?Ygox?zBpKNLrZJ-Bf;2S8(Z%{TC^*N1A!#v4)P=zy} z9JPe6r(kPO!MOtRY%h+z#vyD%j!<9p6x~=fi()Y>mW^ZM*=KA5o5&`y$%y4nN?^Ze z5UE6}V0Um6(v*adFzhsLmm>uqaK0&Pmcz#tK8+d9xB`XcNUN+6)H}o3S-5aomxDej z3z9NQB1$`&7w~EQ2I%hA44lJXvH+ER$TMf z0<;ncF|dp5d-e;v%lq>9teEge+TaXM11g@KyP+b!iX++!?SXb#Td&R2rfVIwV5F2! z{0cc+Eyj!9qCHN6F!4-&lyBu@`B2`Ucf@HRzBnPol|4dUM{#ONZ`KtpwHWiF*XS9# zl}@2UX@8t|7C~Fm;^ZZ{fV132VGl2OC=P;>4MZcwp(hKW1ny!Ad^UB4kdeJe0B=%M$sE?5<3oeJysPY zB`4s%#dUTJJ{v`saE9K+h5k>(ru2IF6=gVbrPsq|Hb1p+f zx(boBO8uCuB=lq!hK2OM(Z3UZn=odVRE5J$Vv+;vMO(PZY(HElCyz=WfzpAK$N}pV zGEC+rCFb24+77=>EDi2P%ofOWrO$edXk@xO;5yU2C4G3X^2ynbX2EBpm;pBlYY6h) zX>gO-Hn>@(=b0+xrVQbaREV92SAlO7*QGyfj>_m614%J^EelzyO}w0nA$x!%W`nz4 zjE0-c6qm#mNXy178l|R8<|S3xoi!rMpUnMLjhKONfF^QE(Ox`n65p#B-YSOd+3no= zPGRrTv|GM`bty@8FSto83GRA~_9ac5;BFA#I%vwlCAJYEIOR*l{aVJw9J7>KclicD zwt@4?|1ag^r2ee(GpM-Ytq|Fgk~xzh;*<(`yY0z3-Na;T-iYyxq;xIZ^&%E-GFu1N zUQQ|d$pBEm(5jJ|KS8$!BI3j%n+K)s2WE-{T zB=y$2@+YNa6I%**qo@Tpi7kS=Ueth_jJYg(y18+_C)WPZJC}hDF9$g>pi6LftS(iMH_I9#QPJpHo_POO>)8Iz}fmLoH8l_v|x zVtiq80aq2Y5_`YLaPF0XeS#A!N3qdtq!tKm;tc7Hy(YDhp0tpqUFN*d!c-jDgUWO` zXh%tKt{zpVO;S7=Kw&7{mbe6a#a)lD$IsedX)O zpI#yN*>o1lTCfnDt63i%tWMw1*Yp+6<9kW}q<_%g=?nUtKBG_R6Z#mZ)cr;?=|i0E z`6-*pL)dIKi#MmQXP!7~(Tn9~d01Z7 zj+J16tRyR?<;7{CWms8Oj#Xfl*yp?j59KX+E8ZICKegpyJe;@V?Rf_t!6VV%cjBFS z7mN_P@hBe6V|Xm@j#Hv~@?Lx?@6G$b*60VjU;rP;2k|)mF(1t1c>*5-Yhf54&PVW( z{1ZNkkJgItF|Z-VVYKlXpTH;bNqjP&!l&Yd!f806ZwAijo5g2qMfvAAuW&BT>-&<= zxFdcMcf~#NRJ;(+#B=dd{3Tw&YPl~SXqslyTyS!g zr%lZUg5mj zH#iRwqD&c1tQGPsGMqwIo>s+pKK0dK^c@Mc*j!tLh=Ffu*F592)Aqx={@&QI`b{5rqEZ_4x8_-&rS z@9FY!gu zbb6ouO7GFT^cQ-EX3*R87XA5uYSWByHdD4~_PM=9%ht>c_EwBGY;V0eTVuYgR*VyU zKhS#L*J8_~%~nLKt-`9Z>Z}@X&O`p^_WNJ7UjFI-b^BfVo))aytkiyX%_p9qP0JSj zLi{eZo}*>wXv3_qYQ z^lbrZ>{$|hRSlTdTf^3}b!>*C( zeaxP+=j?a(C;N-NW^Xu#^IYJBKofVxd6AwtD>5%mh%Cqpb8lXho4FrGmi{;=vLw!l zEW^w33cM1p!mIHbycYk6*X6;yK5xhy^QJh5c9lFSlCR-w`8vLVZ?vBmDNl=(XGLO^ znJQ0-)kSB3st>e5$oLqaJU%=U|IOUOlhtnOu&v~*VPIb)WkN6Y*4CgDl;Vi{s zB2bjYDT+(QN{o?P`MUFmGdStRe85-A(?`F!bG(4(b~7I;OU=9 zBVYl_Q~MdTy(`@6DzBp?3U|x{>PHcu=u-f4KJcBN^jAMQM`j>y7^CIE+3H*A1UivU zqLVQ;pGp(yOgf9d;J@Q+7ls%HToiHL;CkU=n6t*tHoTb!_*Ps(o+XL-HKc;BXA+$R z4LX@l1)fL~fg?T4zszNGko6h zDd!{q_S0`Wz4Tj7EXQ5W4dXvqNJr0lh`AUh9|4ZWSUp5c_ z9+n+bKOk}aNBv;7pYnMMU&xiyb%e@w3H8(aJFySn^<$99b241t0?0w(zvYR3OFy8G zLaREhUjnuQD{@3nQYq!pkE1qo>t<*lyN3H28RbEor}|Uk0hvs~n^~>#!&EBz-q+@G zyrQ^+^%nh!ei!aFz+LEz@z^H}9fGGh0J##Qe)?>^Hl7bkU;MDwN#69^`fB}4y|2DY z->!$@c_m~@;xfA<0ciEEd+QLZ(-pV8CxBv85C-$Mceg`cK zYg_t*9NN)C&-hUMxBNZin;-f?FTSTf?b7`AJ30OI-<)ANCDrNYyzAQ}hC0~e^s(Qw zHA^=3c8q^|(r?;zC+hZ|z%TqNx%C!pZ+@>^`ZJS-0x?nlSAqq$?32x)DP|NL%;t!o&5K5*!xpw zyZ&eU<3qLsQvNS&hwSztHR1p8W0(bzcBHh-q#Y^e2^q#vl`%h89pfY~%%?QL%4#!Q zMKGJv0yBhdFhl5r8N&9k^E%@4gLU2&c3C1We`O^GDC@I?vO5En#aRk-X-`NQ%&q;7 z@5NryzeqWZvo%ryb8yY^1wkkaC6%zc*o{=i3S$hZBF9Oj8b(SB@Ga3Ic97J>j7K`Y zYdX)aW7pYD_A41KM?3hs>38-!_E-PO{v?wy?)eMf6THTM$C-lB4+& z(NQ_F3L~TnA zy?I}92(xU1a9aLYJ^`oYPvMCq9dl)K$XSelGI3J=Bh2fc$4KZ2xxk;n7XBKx_#1Lj z)WGcXW&F2`Sc-3Y#9;g@pOIoD%_ByO(KIjqwaXZqPfQcDX@2|#zk5N|2gCbT3K8cmuVGo zMckxS#Vv80Vnu`E`vY7aN2_tdr`NC+t7E;1DqZq}1gv%2WXP=L51dG23WR77DF9fcfy>r3* zT^Qst92dUC!o@I?*8zGc5|=^AqB~@{Zx?6(LW|3hu$o_g=IIhW2m_yBCh5RTwb?cn&k}=h+3U z(SOapMhf4sZxHiahSe0z2!03rBD)Cud-gr>AFvmjVwMoAJD5$l1pG3?>JR1>egb}l zT>*ZTT?KxPT?2lD-GKWu`x)+S_1(lBb_e)fb{FoiXpIbW5cd)P0eb-aA$tfslVRNo z^Af)Sf5aXEe~dK}iaE$9Sk;#8vj}D#J&62|H@V=-|778UISTCcz&vC=V#I7ke&7Xo z0pJDkof5^|WFg>YZYExspR|x%n8ok|UW^w5Zsk_s#j#$V3o{%3zyq)XnhSFrC4dL= zK;W`%=T&VxuWH+QRogaUcC$P&s1|O*JZDAVvYneS<5?NFZ0#n@eO3i7+q?<0pw)rP zmT$s*Xiea<{pUveha_Wmv<^;Bt;_2I59YxnA7)GIVLxPjULSY^-T-(*-Vk^r=!=4w zV`+-gg+gn%VIDsmD;rXGXqdI{Nn9~!--{IDQjfS{)_x%Thw$NWr5@2R1OGX81WRq= zgW2~*h>s)Di8tonm*Op{Uvew`l3VE)e5=HtfvV?Nt9Do8eh)S7_u#MarK1afjeWkH zzk&7<&^|S=_p&C|Gli&y6(%lfW1UlokHkm7>xeqQ>x#O-gGDg#diWlbi~9IdSBM6p z0q};RA@D|`5%9*MG4Lj$3Gk*kxmJi~q8ae!qB-yofxX_M1$OQV5h_A~x5QUh0{`!* z74X&qJ1|6Bxne8A|!$-N9xYZH_B z6f5`!sk?!X6=Q*q7g#Y96T}3#lf)#rQ^XXwi6Rm1G%*dk0j7)T*mp5Q%m6-9%mhA5 z%mO}J%myy!Z1Rg#k&16Awux=v*mkiU9NQ_d_Kp5vH)z-+_JE!P*jHc>M+H{8#c{dP zEiQ-)cp*tOX3o~E=8|^Z$GYxE1>qOxQe~H*TgmCcpdA`_%=@5 z0De>4#D45s_&PYBxQ%@h1)*6VK(890h23zutA2uErZxZ;gzPOmVQG|vmY4Ij_CCXi zS=(~hT_R7`%cW*-DeRESur*}Q;iY;GP1Yd7cr<_B)_s^@~WQ0P4yhbRL|k&=sBcClV4dh7S%)eslLHo^$PAv*B4Rx-31oS zOKAMRVAm9NXnASZxF{{}s_Yt1Wy^Re{mztr_r$F48tCG+Y$J4{)bd89_ z&!e=wr_%CyU{&1&JyOeSm|4CBT?um| zj6DOL&taEQWtUN9mr-SxQDv9mU~cGrqq55iDZ4C}vdeNQyDXQo%W}ak!`>~`H<&PQ z?Fn4=5GKrEdjXgIgh}-f2GuthRIgxAc3fU%$K_RaTwbN?y_Bx^Qo7zt>3XBm@>A?snzU26TZ9I2K>f zje{jvKxuFnrMruKKvR1tP3@*MwY$>P?))$Q7x*CcwMpq~rt~#e`kE>1(F+HCOtYDSgeAzGg~abEU7D($`$+ zYo_!y7j3Y^!LGG65r&-)Lg{Wzw8LHqp)|NAI*1Oyr5@J={%Ij_sm(RfNpu1(b-E_H zU{APEnq9;HH0%ak>UT{cn;VoKHz*BmPF`cl!kRv8rDN;SU1?$n9Eig)}%BnQyP{F{EG+B1G^icQo|ag-3^U- z2;csC<3IBqhF(4)P9XH8I0+qkN}R&^3b4W9!Ul(iJ&(OBRB2(8_*#4oT zug)%ti%0<$I?|N7*h}eRV-8*HmO~f2y+;>QSfv7Eyk{PjJ<58Nb-(NWjr&pe{WzH~ zkE?LWbl>lC8~+okkm-f#w&|SdsA-aExT&hCys^EpwP9m{?+biySZ$bRXlkf$2sG5! z?zr#QW^12%lojDJHQZZb)rLVDpI|-r8h@bD-jDS6n_eKNoYzrzo8wa9D$XQRdHbdE zS&h8pRo_tG@#ayaCoj-zD*rAN_+FO4eo0E@<-Xsr+6kZ=7fL3tbEZj#`l@upO~YmD z5iLP+&I^8y%kC@7WWSWZXo)oBg>>v?M{YUG_cs0>df%cy2c1rOop-}(JFSvlLw%Kt zO@vCI_@HXN~IA%V7m8+w2Rn)m6S{*&9b2YRs z`pD+!BU`B+G7P<91p35I=nK1{_rn^F?Dx>?$yKN)*ctO2z1~aobNIis=-V(}|A$r0 ztpA6H{>{0MA?N-Dj9cEbw)r#qpxfws?#P|7=xcsu&vLA1+IK7D!Du1tzuq}^E!bBx zwRX0e+YiQV7(57{vIU7JrHi1SA zf%agUG%4Pg8qa~1WX1@0Zi4Ou=Ds5@B+S)s{?orc!7HO z9q>|rBBtw4#Z16#z%{@Pq~k@(Vm`2j{(#v4DS)kjRQ&4MU>{&V-~iwt;77n^0O*I5Jb;uufRsFdlstfxJb;uufRtcX0+0!K z1bBk-wgrR(+5;j09RZyIT>(*m1i(Y*%6 zFnit<&Wu{CZo_k#(}$&@V|7Y>IdlzoCy;GKL%}f57<47<~)#AJ@jntu?i54SuyD=9JP=e zzCWwfOhoAEc-jjp&LQoJNV@~lZj7{7A?=kYVRfwU)dX!H=@-~G{Q@tkALT)SG64AT zx`1c+N_`q&24EK8GEOGBsy`Ih@m?{ca0MycCbb|JAL&;y!{^CD0o(MeNbL$zyMolN zaD*ept4Q%GM>uZ@_&@BucU)A*`UiYw=A5nUQkIT@NC)Y#OOXyL3QDmHD%cep_LgX_ zJ!+!S#B`0OSG$QV#TYee5;Z2VArVVNqu3G^&c5F{yI7Lk-@U)z```O{7oMH7XV0FQ zdFJW!%$%-(Vn7L?6i@~j1{e+)0RX)+&?^JIq7|pniqmMtX|&=rT5$@ks7EX6#X8i@ zSGE|@I}hNeuXQ)%%qn2)2C&}<{M=N=;tPDbfKQi%w*@EpzTg4~wswHr@PmhqJYdWs zbO25}0tx_~0cC*hfF788>5Xv$9SIeH6*%`I;ANcKVC_KW$@j@3z*4|7@_i~5|l*ds1i1IkfpYfX$fKz}ofU|&K0rddP zgMzmjz*`OAtp@N`19+F zur7eEfMP%ipcGIB7zP*)7y-DAYn!bdm~1^GrdT_OsVLJ>wnLeYG6Q8M$}E)GD05Ke zqRc~?kFq_=4k$aKEI`=_WoMLyD2q^bLD>~$G0IYF17t%3WJ3dFLjz<(17t%3WJ3dF zLj$CD1EhBYq;~_PcLSt%1EhBYq;~`4Mg!zV1LQ^nBzXg5M}zo;^|Ck%@DyMUU>;xr zU=d(3WXe*&vw%&gA7c(#(*O<7AnwNZdr)Fr;n%oAlU;%)yTmmEq(@)tWl%9J7*KUB z&KW>~+16V^CA6ueusggA6X!~F(W+#v+^$1_i-h(C$bTCLw53P zj%)usqixSj`NvEkwBq4xJu-{<4DF*W#iw^3Z^gg;z}fYa28n{|I_Q?toT1&Wu33&N(k1AE#D$n0JG68pKPUEC?{Hrt)s0L>rO$< z&sjTJhv5CeOt=+$+$ra0;C{%AX_>>e-o=Pqd~`mZU!}~hW8U5Nd(6D^7iQlvD}NLt zjnDRP^vGCQhpdB@peiRN(`Su!J+fTN_z#e|a@D$$AMqDv(61^SCGfGw=kuThTR-?` z%qdEt*`!v3c`*zL%M{{mU?iuWj<$NGoM$eF$={s}pP`6c}Q&-t4sWF#XhjI88M zxOO}KM65~J3CrgT{OOgat&4ct`nQ;2{BJTn`TV0ZdV%>|WVovlpSI91d-W!J z6LHzM*jtDxzRR$J2`kJ$fHk>+ZNT}>Y%|VpVOubRwvBDWF&;pQ)sx%VcFalbWIHhr zwVUmR)$#@V0`p3H*dCnunthEkd)Zzb|1HB5__%->hs)w+!GJX&SiJ%4T*aBY*r7v* z9P)eE6R!z7(LmZFg2D4o-7z}rK{q_R)kDD!Rj|Vpyl51>sD*Fv=cdHrU6kHxmEKAi zyET~EdV{@zz9E|#5mer1s`Ob@`pl5o{2u!AK6~HZe`od~`v_Nl%sxg>kn4^L9;dMmEsII>|!t>8tc;6gOHntAl(i_yBieiyu~WthK#3OIJ?{Wgzyz&2YVr@zOjzSuRg%9rr=l8@T(eF z0lTbC!XC)BZ>)SB;5O*n1IPs9N8jSAdid6V^)epyEVfy{7j{5j?6TfNU3<|_16ueS z>O77*FX67A;eNlNPJXu&sPh}{fpG3a1Y;jyKOzjD0(K+1@HJXWg`L>(@(bu%e_X|9 zyBu)sC16pn)UpSy+skEx8En zJEHcBsQn@`z%|JH)?fz+4YIbu!#MAXXhfZ$2kgXHeF@BfbAgR}xYst}TkCy9C+aY& zb%;*XAv#fq=tLd%gt?DiUuAnx?giYzxjz8+a33$+@dWO80`*_VT~FbzSCn?_MeFwA z+f({YPp75PN0?(sO5y>rLKhS zwH^20iCS%AdO`RCl<_6B&sP}7uTjrl^l=}q*$+4X%ja9`Y1DZb<9iI_dlL1WV%x1h zpsv%v`f=1(hx$$f^T$yqav<=ldyu@G^7u%8gFavsaefaxF5udl0&6M-UeF<76W|+o zUGuCdh@Ye&ev*RtNeWwIJ;dI{cUoLkkE`lI!@uGz_H?!$#W(eAGs-R2BWxR}O%gW3 zj@oAZ5;ZunH?0@g27DHA_9o64aONh?+_YQ4+rUwuqR%!i+Xo)mjPJIh=X=mnSjy0n zf4#;*7$q+iX2^%}%_#Kj6u7}r_)4|{TV~-Kxe0x)0G32xNh=)2XV5wF^S{8oaDTjS z1?=IU*juozHv_goIxF_EqhcT9X~3|L9ZqRI;U)9kRUW?VR<1oa)b_~*IKfei?SoJJ zrP#qZdxhK8|L$e8fE9a3$#H&ooz3=CY)X5!;y-0eTYutK;Xhl3u+L!Yfx48Jay%c0 z_8#yfSW-VIU;ZuI&}N4!-~F4nf0wlfJMaRpvnA(LsaMGhe0U>RY-=BW`s)$$qwSfR zfBoiv^ZCE9;U3%$Hf}NOT|u#QN63ZvLMH+-}18Vvwqle%uDM#)}7pjw!P#DC|#7S?gRXc^-aKbeg!|m z%jQ|}vq$*H|1?J2L*m!{yBG4}VVV9L+PU%Xeq}pvoeBaZk(s zdf#jaI$&Q^d@gu@2x{d{#Uu>l4;8_|hZrd*nsJzv62HTEj-Lzy7a23o^&Kx2JeK z))4D2I9~MNo?P}SwEIX_`Gb6MQ0s2qL(r@N6z`9=I%BjINzmfmb1zQ8KC(CA^YgVC zt6>TK?Pbf5R@PhmH8)%^E9@?7`7pKba51AX~7X>Yys*I0&KBRKNBd>&JmwY&pAPGf`EDr3@MjQ?<4*_doxrb8;$h0uFzNX65@35a9;?T!fq0i+}*Lf1u_+@kg*H!Clz=d|v_rY|F5tae#Mh27p-> zbX#x%{w18z5bWKCaX{WT@gN>J_9UKy38UgAcwl6_5qIz*J~;17eDUr_{BQ?<;*aA1 z5`gzMqz%qQ66A$r1QW2QRwBXL0_=K#eZ8!OKrJK5NSql%#^B6Yf@g0iF%-R$<0mTl-x?*;PgL^1;WKfM zNr@_WVSf%Id^SgltYz%cf&H+svxmFjh@Cn@@g6}Va3+#Q;@h^gEn?A8Gz#aVX*A9! z(nNdYMGxvcgq+>)5lc2=jr0$&_J3yBOAOR`67Q$jDdh5=W~UL0IK$2eZlG4|^@H6M z&LYB4$Lf&B{44tv$LH8NoUdod*24}9=Mk^Dz>uGfofawnYpf5akRuBG^1aI)D zgP_Hp1Q=QDOW=fEN}VNVzVET*iZgB!-vf{eBNZNQiQe(ZR4avVeH9s}Rk)NX{HenZ z0rl|vF5pkAaH(G5Qc>Ya2S_~by>mV^2v+<#U^pQQS3yafc6AEvI%19g5y2np{Eysx>K8>!whxheEYfp<1R;t*B6~qe8VV3e`F)ROVPLTdlh|X{t)hRSe6&l6<-k?#c(5OzKQ5!9~LOVQ< z3_nhVdT5E4u%q=W;wy-KzACO!?qHAfY#(x?li4blS1ED0(Nvc%V+y^BSB&wkeK zFy?4Ic@&ks;WUFE^ZT+D_Ve-VXXP`#HB1usV$Fr^^9p2VY3MX#4(&y|i0jma;$p2Q z?V{ZwF2-lv9pAPU7q{F|+)I0Lpk4T%QsWA=0>8su7qN=<7jgZ$Q!#VJ>ju zAY`^=u0e}(tK&HAj>mWKc-;C>_U{lL-OH!ZzJm|ny@%T*5S^SeAF)f?{1=wVT)Pzl zUU{$!#y?viEj!~}1s&V$j(@T`{#P~!sFII*SnNR6?;*N^PBqJ3mQUStsG{CrGq)Hi<>HLNd;$S~rp`>uS=;I)fBiUm``; zYSPPkf%LbIL<^Rq1s{^(*6E}gQgM`Z85s>v1DPN2h3zxL_{CLNOutxj(2lA2-H*`m zu&qcnuJIK9Kn@xMAUBNop=<-l0U+;-R02jKvRezNLv;5XO5~YghxtsAAg@aeZ~%A% zS^<0kp~(9RM;VDS8J-`nxmLJfJ&XFzLZV-`{scUH3LN}~K5syukD$-{&`;jSuhF-9 z^y?t{brAje7X7(~Xjw~a?iXu6U{=Cib^@~n!c|!YW=*)`Fkse*yY>WTO~9-gm<<4C zji|*LwNTXZ0Wj+Z%(eq&ji_-1Fk66H9e~+PQY1H`_RmRw`5IafhZgh)W_7@WEk<;zej){jrdYvdmrj;1O{x`P|o%ooU4c--$53T7PNy^^k_!|>bs3P zu7KKJg8j;O2l)lrKNk>pxMMwlE1o6=XiX>UQ)uZtjOb~!^aiQ2zD5S&`)ccA{CYF$ zxdSS@izu!P3c)jeaQ~C|bu{T{J%(Q&MLpNiuN%0!OiZ#K6IbF|SK)V1@Ev$Pci|t& z)?Xj|G7)iT{_8xH9j*J7JM_cxBX?ov@dPhtfIlF~x*vDkuVCpouyh<)I*vXaM-=-w zqS(g~#XgRR^>IY3k0WAz9CH(Vw)}T+7mv=#)>}jbS9zmML`(AkCHUMAoKoz z9x?E%)?1AhD=(~-xe?qJHQRjZtxgT}zN8c}^?-xO5w29S!`lpK^D!3~{Ia|ym1fX|hHk$_Rw zbFjmm!GV8*`~C#?{Rz(d6P))aIPXtz-k;#SKf!s}w;qs$XAUHzT!}gz(Gpj*#0f3A zj#k(t7=gC}9}6}AhFY(q)>_o+gqnb3Sg1+Bo<5)`nnK;19q(%mxuWMFbuTXY>Qx{elIM~jZ4Mc2`yqiDr- zw4ebkXh7|JXFnApVtPo9If|aT4KBM4F1rmbLw+{k55PTKLlij_DqI0?aGJP|vJr3- za0|fmV(vgo{E6}|%6lm91De30oF@f<1c(Hf0mu&|?NDX_I^wt!pbW>|0X+fbfKeEq zv*6cX0T%(NTcm(MKrkQ)GjQ91$q0MU>sv4D8Ma@6CGTF;@@^QiS4YW)?po=2_cm0E9LzFPz&K*Oa#dgY?b zM_G)r1m`PIA{QDui`RM{wO+Q@dJeUoNB?-u=TY z<9AqtJA4m1+YRlt8+^4Je6<^VwHs}{0vg*5-r5b`+AT_u{m3#0hOPiZSHwi?1#t!H zN`?392uxjowQU5ZE+`uP1ddMw&OnCN0b>_%9^3)0bb+LHMC&es11~8wC&HrRzOENq zmmnB{%?oJpMZ|!Nz~}|E{5-ht5?X&A9C!&ms7D0I2x;z!D31~NxBz@yKzzptoLoSU zE+VpHgp_xLhpJ~kpge_{RI~@ZxB$#wK4j-eQX3}-9jI4p^w)=UAI78w=ilqF={t4YMi4Q z(U_zBql3GF-z%pONuxovupR6#!tf?gYibKKTW!#F#k&1Z$97`J0M zK8|BN(+A`1gfXr|yMYy0b6gYOMbEkR)nL4o7$Qn9oby5H3-AX708(%+8)ZJq4zM&k zqAWn!31tz=V%&pk?OwoQ6|gr5ns*33kHBxL@fj_{SUYizj&Vi|4fzu%0jI2Ifrqmq z>KF9@1D^55V^lmg6@t%UfCxY$o^P3i>y`sn;CLmKS9psq&iSi!cK0pSgBF_^NwI02{2)$B|QN00)+6altB^9Ui z!;q@=kgD|<=^K!&jdrT%6n_a)wjNTp9#Xa*Qnnrxa08OI5!kp0Y+SWdJYq4(!dIvq zdf_ax3VsEg2Sr>&i8ez5*8?LLfr*Qtj2n=~ji8Pjuqhj%tLh<FqI`bOv&;UF%FxcV1MFVWxYtWh3 z(Dw%Py#c!O8g%D1=uWPg8qn`u=yLNUf_AQX?TRwv*JV6YAttOEw?fWgDSU>z`62MpE$gLS}Q9WYo241NO))&Yau zLa77xh5~zsfW10kuMXI|4$KXGO!sjqT88hs19}3=0TsBe3h|ObpdXu5<=FeqE?F-F zTSI}Z>%bOfi~tt_XfLo+2kd+U?0f+1TnA>Z11ojFN*%Cr8N75EymVR7l@^>^ZXF7& zTmV+;fRzuBZLWf6p$8nnobGV|*8^u@ncC#(F<_+;w1xepQSyE0ZUSxrZUbzz)dx zm&05Jm*ISOKu08(Axpf+W}y-5g0uN?&Ny?90s0+p><}z5{{B05V)eqDg9I=L?uhET!?Qes z5kC$rr2z8L^Hb>ga-73t9DqN^MhCw%2v5=>n5{zu4_fxoClLMryC465efMJ(o|Pc5 zFzFLemKrv+BkU>71?G1EO}kLF2qQS-;Cv_yH6 zJ~5l`6-%EeFwbn3%I__fCfER;Xb@KQWhiyv{#jZ|)dqcyKx>4^$kT^oV&k$|poxW; z6U^=&?y5jl2+jEZ@-tUyMNU>ZrdV?_%cW164D9B;@A7(*lI+}`FxPO`DXfkjRuNDz zGGaGke|bE3BH>UxZl}imECI?pdCs$HO@=2GJgZh`x~s@4c?wzh0)Jn=6mebjRs7wJi`}GZ1Q*^)=aiTP4)M+jv2o-1Oc?%1fCU>vSpo)=n_FeWX62pw zG#UKZ&8|s7_7QL^=hEt}#hTsu)8J zq5l4^-rmerLbZnyVw@9PQc{!y7mpNvY)2M4NX#K2q0!WmK|TDKI@ladLqk+5waVSy z!;h+sG&Gu70#tr9!QE_T-C|#inHuq8B(Z2h3uA|TI8dW`=G2qJULG7H$``5HH!Uuu zTY_2>)+J`p2Lsi*W#?v%dS_fRWC}HecCe&Wq^Vwze<7iFUzcxU#;`cGG}S$(=B+2^ zY_FuW)06KFBq7}kVhD}zZcgo*>=8TR?U@TdtDrRhiH}Ca^l2YSqRX(5Nmy_FP0Eyb zE?6MeLUa&13&3|ld}k>wFFfAE*jkcm;td?%@q6N(_QWSR<1aR@Oz9$3D}ACCrHAN~ z#ljJQE|dTCJqaaSRN6kv`}UrdtHQyuzCCB>9nevy$dK@~AeEjuwhaw$7eqR%H08=g zl3Lq)dEZ{RNW+}l2DMEKl=RfGZAf@}FzME`d;uFHb}s6(s52#9=Jzb>v#=8-Mf2Ad z2V{g25}Fm%rA=ly4sx2x@m*&^yUgoZ)OTTLN{i;ND`}k(Mrd$$XqPq_{Gu#YbpORt zaR?+ZqI}|3X`hfI1ZBAfx(JSKh{`)QHqzNS%~YfIW4;=GifVNO`H71D2>!_5p&q=SK2PF zJt0vA;pSoeBbv_A1zAg9DjC0_iqUS%2NX=L3_a(O(IGgorF6HDs$*l&aLB84Og% z$yrMc5)HGZwa zWAFARIPK+PRvlm=EWjiNwQVxDrJrn*Z*OCf_wSRZ@1GKb(DP^H7crNA6Lx@RJ?4F5 z4#ma|=7bZh*YRY2b3eO_KKQj){p(5f-k zf3Ye>Mr*Msh6pj4xIGjSt7^J*l6gv>+&!v_Lf^ZCe+nU8Jg^H1v`nPR-$klX+2jeA zBiU*r3yl^nmJH_MClZrc5t(2Orh=H{j3Q=-xOAPjchT_YDq__7V3*=~-z-#)<@Ir* zR`O|=*s*_#pEef9-Z&nR;>UX3pHjDJD5IUGzrMbA)8w-o2QykQ?X^$jxukPY-+mYp@|qB$r8*~-H|VTCBGI< zE2WM1ip0uMzyH90#h|UaKtlKnURe%KP7ZET4fFS>{KwEtk&q00z%a-dFS~G5OI$xd zA}}>!zXvs?_)|ml@DiQcGp}98K`C1KH|+~o7th!;La!+r|5Unsvaar1axH4~%>1N^ zc7zs8%%V$wnaXIz#((vBWzqB$`64jw1z)2Sm~Mq=O+=QvPb)uvkc+RcOF~RcxS_^i z&;s*lQ+y1US6p#Id!ct8|J1I)P_>0?Fg`fUBDr#zrAW4rQ1aHI+);TNbwHVv8$_I^64ZeqU?H`ggY1n`MCHRlg?u&#v5#Ur3}Zf9 zR^b#NF~9uuL)A$uV1}*C5uh#Z@X8Wiq?~e#~FnY|xl&kdl zMVj~I`s$le)s>lq#+40!|Zx7B0cCV{7B zpbz6Qf^P%MDlQKQo(v<_Fon|;J1$!dl^I|W-}Y|ymJiZ%&FzDvPu{(ESNslp)Cn=@ ze|PYj6QZcGLY5GaH~=?*i?nnqr*Y)~;%pJAMB+t-Am7Tl!DjKS};oP@hy=D($5^*BZZ_6;8ZyW9&8@^oXp2u zNOlhhNOp*nYLe|Zvyb`Y1c*wDsYQCWj6BpYXJFoa=wYK67+CLEy>USgrx1zyWVf?) zOKl@cnl{-3xTGv9eoyYl5oSBLJRI#rL*qiSbxsn|dyJ5Vwf_s(&VQS>f zY9u^h4QedLCbc9+EWwi6LWl$H64i9qpH(!^ydB8q(>V=t6B)C4c74hLG75JqC0Sw= zveLNrHEvd^T^Mx|G7~cG`j9|Oh*4optHPMC@_wwoUM{Y$$1el0oAl4vsg$?FElV$H zu(cK=>PMRqY$9qY*n&KaYt0`Z`Yp2m(V!-UW*7Z*wDP-?2F z95hH@QQ_;`E<6?{1(!E533!8AQ6!Iz++ZdtCLbqBb2JMLft*&katR58kQ=-V=UvR^ zAW)?Xd2ne9v5Foxa}mFzs+XKPPy4VYw|}xp`|LN*szhCz!2E%^p;{A39KC7w-1FO? z9pK@XFn04(J>M_(>GE~oHH~X~IeYp{+0;OM-dJ_<^o=fsa1$9s8I<7E=`d00Kdyq&i-dHgqhtTwdc|l>6cAmBb#)fqjWZ~D4g`C0+LU@*|s8;I?22A5S zI8Y6v&^;QPMC1B8B?Xwa;Tb|Q!Ne?Z{z1MhsCkRLi|G!BZP0%~Oy|OzP2u7@_j_?y zSN!I;d&ShR0nbSk+^kmdd(`Fy*@?Qm!b50iC1XnSK++~>YMEXe(KqAgWAe!+a;D!W3p%US({@&$ zl2czINjc-Y=(MdmXUIRpaf`27R;bp!ai2bUm;`t4nAatcko#%UCxk@wSy4Udg+f(x z(q$6Wr+r%QI7SYWVyWx1gJ1c)7nh8s@Lath8AHLHIxjaTRud#hTHxzo0{)=~gHG_P6E?U=hSoW(+D;Ud|X2*0L zFzt=Z3Gg~4#MkKP=tpaOIf^->+q~#XI}#NQ4J;}`nu-i)CO^wNB1qtZ&qkWGT2;d6 ze)6Vp(sudJi_A_M*O_0-a%wY>dzsEYI=w^TtMj@sJRFaW`}~gu9hS>GhfC`fqeevJ zP!RW`B`uL8ST=f{PUUD+(;7xq%7{Z@DkE;UQ+egwcH)$EE4UdwC7bAJ^B@5>OXYYWna&P3SeSOm%uas8}IlrPrVXW!tKrQv z!dXICb8@&XK-0YZ@?|<2H^w;kMJ`Av#yOJXGBP4UvW>Me)bSW;XnW}Z5u zl(AOij@e>@oWIvM`-|h1D}R_C;cPHfe{|!emwuiK3s#C9&}G1)3bWpzB~c^#NB1fX zikI&M)4R>Py7mr&*Sh!a{Jx8=-;86d=Eqf}-yXpw`y#fEj2mh6mwLpUvoz z>^2Zx^8pEZ-}KytrB8o80QyCA&Cbs2XCb@TjV8y%KTf1<)V@U|xiX#5+-iFdpHW)E zC045}CzTox^wX$7w6sRoBCTvXns<+{FTuZs*aljBqPdGajS@ZC^u7H374ocov?JwJ zB%VZ*(-2l4tZ!P&c?bwSsMMt9{QWo@!9NeFl^qOkwuF;Jg&|rf)7DlM=R&>?$w}cu zwNcV*Y8*tQw{x)?Z=-zRCBX!V8c5(6La=i3eC7A`G!R=CtM6u2-q6R7{?8VJd?vLQs3Pw!QvSztn9`!yK zj=p5Y`}{8KWK%E#szF%#fxg3Qmf~P5xPj&|j-jen&a6hoMH}ZL+zX)(MUNu#0X+&e zD%nCHHj1~0n4|!W>*x))UtV!&au|fhn2onz(BKUS+{52E$NuM=D7hP>+uYKHZkwB z2>g2RK10SSdPGqoHVuMyLZ-Foj$m|Nf|h_xrfYg|uIkEc&e zb})Q^O~h1q%xBV&qU>Q=%%%+3xbK#~{{3K#X_?%lV1_}9#c+?iwHr5|BAE_^F2IHY zes~aLFi{ioUtx(wlkVTh8|X%zY4pb1FReHVOslmHQeGLk0X3hMXm91M|B_6s??=r&3qH{dNBZ$EJ!hG7o#_ zr;RJoX(Bs!Z&UKZgbbB-$Qz#ztlqV>h>>8?C%tca+UPE1PEtWakfUGW@Fo4LUmF}t z>8Nj>&Z$hIB(x+eCNDO?;9ETExymuG4vZ!=YwG$MlHNDNp9FQ~yz?BWG94J^8V_;< z>#4Z-wTVo-mCD(WYqzJl=%mNP?>}kRIyQaN72KDDJGdzxs8vDX5SCmJEwr-cF{#1j5;K_~i;D2L>MqT(r~O}KN3q*pJ8^;{ zhHB(9$~_>4)JZs!5Giy-+o3C+9EgtAm|Tpa)~4tcfv0dsf(HpSnbd)h&Zs~+vXGa^ z(N}b`>wBFd;&=C8Uy?N>tohtdT}8*C-7DvZdED|Iwm$^VKL#m zFi4mYf~57%GO9%#5i$3}@z&BACDz2HED7MN1PQtN86J;X-rPW<H3u}uP_elQ&ldR_6#5eGJq-8O080)cLS}fhX^TRQw{Z!-F&Eq9yN%>UQq(-B zmwddx`~wV>5%l?{Zn#wdTgokNJTD0=YdCdq{RN5_9UQan+ve%J($_!zY5GZ($+;qp z{=$lIf16LO)xZYE?)``hXC8`I{0b1ZO#y}`m}rQMB*r!G>Z_#Z*NN-pF#1z-IJsHh z9F7*$Sc91!bJaYqjHkJ9)2os*lRl(rN$AOw=*xHFK{^XF2TagyV6z~Vy%Dp{Hia_M z_{_1>DnUL-NIcr4LmuV{Jm13yd4%CvZjo*MLj6)x9Yf>vHJ)C&8Z5_CD_Ytf(MfPt z^ypwyi{OzmcvOc+iapd)h}onLHNwxdbEM)-^1x359abp0SmL8PL~CK{^uXuc%RF)iq6!K zJ^uZPL+5q!W|C8bXmuR&6pnYpA z85_Fh)9x{SK7V5%ql4D$8s{EWU6oB)+b#o>=44JQp)7sSWQ<`X#^y41@ek!X7I9El zb&UwxQKkrN5oB-&4+#fzd$Psq#*8PQ{%diu$eE-vvzBPJ3xAk6@$DhucpP!JC7(~7 zxuJr}f09+`~wBmKtif3Z8GqjpRqalI{!rVSi# z_Kg}fFq>mE5*S_i2u2M7qUf#%PSrI;L8zj#&_0--ftZN22fuhEEck)z2g?cC3{9o4 zoFP8)o#y7Zs3CXa2a^WRDewVGJ5OFaY}C_*?j(eY#3Qp^;pkkwNzw)8TXHK>1DYi4 zQbslI0}s4aC5pq}{Io2#_s%s{j16ABW2{@`$bs366-~>X8{Mxok+96clQ@sZBC`z7 zMu1J{pXD^vL{ugh7r_gy4iXf++N`Gj6|r&oEf_`64vOCv3wr3xU{;M%Va$ff$r{n2 z$FpBg9k;qFNbl6N!=Hvp8t;zn^82-GtrjJxn6k{``Q|60o?h)rOP21a4j*^$RfHRR zt=Ktq%ICz((mxfU<{^_UVM)WgAwHR#yeh9Je-d10pn0ImTj0<8LgdTUO;ux1ks1Pu zwNqaDIZsvK(smAx)IZ*VfBe2Nc2 zBsg<_8&?&}B*&!=mOu=FoB4CljmZwf6veI!$_W z_^4+}yh#WaTl(g;>pCvaU=nq~9a3{EvqHqw`wc6PR5Kb@l=4<;Hl^)G7qYhUcDEt> z*A2i(ui4Q%rq8Z5I2`o)PVmvls!Tj1V|xDF*Z~NHvkVM6wIbjtN{TG>=Gx1dy6e<6 zOsJs>D(%)hmE##vkf4G%6}crg-R+y@+~Rv`RlulDL1}zsrp+QC0a_M{ znNYW5(K;U?9y1co;Vv|~O&bqA&<)+v{`2zdfeU#7q;K zb$+7CB|SB(A}fUDHtpoiqLsT|>_u7rgymhEyU@)^rLlx2_8P&hl(u|+3-xoq*J*1j zXMIhmO0faB5J1YwZN@D#$5MYt8!HS4k(DZ@X3PdvhR!&Z}F> zlKXzKmLIO(Io7T1$f_LZ@veiC7N<|{LP^F@v|EToyW2nBZkwhQjWuZcL+itYVDjID zYKt+|f?^t4mv>KYYPv}sawmQ`ap;^*K3t4WSvS0TNoRKwN+qr;yN=3+7}W%}PsyoB z3y=p$hC}y4*!ymS_pPfG+0eD0b&u)0YYj}1!EZoWwjDVrm$Jf{`Ez3Wm&8LKak^r*z_GPy<6G>vnVB%>3uF|k#aF2v1?SXF~lXYMlkt&1BEj36S zsOAU0ss$EMAe>jaC#d=5puNQF6Hyx!;uhiV8def9V0w~POg(sz=n^vZ)7ZdLt@}K=m z^cmHwNA;f8(MvOtcuitRr3&jlHj}upu={(-ub3%?V4~S$o-L)@o3q=b$NCc1c^0o5 z`xIek1zz_py(C&+KVGQv6F zy<*mZv?A_u+9sb8)3TnhDqQ7l%|_Jxy?h7n_n1m)$B%L&(~}tZQb;?%J`$;qd(A3% z%`M%sdCjn6flMAhBrB#|ASv?nlk%_g8{ru~e*_ZsQ_`XN*XDZCM}CW%sR%iKQ0|Cl z)ucj?d2;UYaCd{p?5V18)WEP(@WpRD82vD^z&1+elucQin3`cSb)@IREf8!+;@%R?fpdtW2wZI?bV#+BqXbKWamj-bYPhR9UM--yL-D zU#}LhRpP|H2WM`Zao7FTpt;+|9ZK$#EWId}yf*1j+LS?%9j)aX4}09G7pr zLyX9U%2DmYsH~BnqhnO=&$SxZwrS{hG6xt;@n zd3o>FrH_0%&ETVU%1qZ+D-EVG@|DQk(zZGur}>Ac>MI93wsIQwp|s}ZpgG&d9!86? z*GBQnlMkg$9dy^Y#;gCdoP$rj+McZxC-yR8CF^ zVDOArZ>K}H83fMUT;AHUCAi4tD$QMq`Th2-6xZ{q9wjr)zWLGdS%K=q{BzH)vlD&u zqvErJnI|b+Ikf|!g(?0>UF#lB3Rg_d;~)JjB((?l+gXUrwF5zAuyiq&_(*xqVYxtKea{Wl15J zSq!|dmd}tjuhWR4crtB6e{O?(F~Vh4E$>Ae zp7WxTB;xNb)i@Xo6kZi)XiWX;LdrVMTHEW13!4U0HvE&5 z6P~!Rc_?EeJ~>6lfBDjv_qTre;urFpzdZFbi~8lM6VL~~!1QMdC#$hal6${VS?+o; zf!f8*O=ILSLT6Wp8m;q#Tr*BOK>lH#7j4tDeBP962sKl}tI&t+m~Yc6f0oGK$@^p# zNozj2F8r?KHCdlvPd0aG*3v&XPn?%;PzQUf z44Vj_P82dvpFvQqEYO5BfI!pP`-|?sF{f#cd;{C-2MxzhOexMpme~FCFy-)De-OJE8&17Lys-O7Dg1`wyMhH2}KinD`6ZEn#;L%xxi# ztJONXK@37h1LcbJ!IB90%)yTw|453HlHeqFH7AymlT`UYi}69~X6{5tzQxj!kUX=c z0Po6iF8H}GW*Salp3fg#(*aQ-qpv6i2j{vP3^6e&%Jd88YaT#WsLR6V{1in1iK`B^ z=b76>p22@*GRzDj!G^l+& z%KJBWa*Ub$+01`k+&a*F|%1bZjIy%kyQNH%_^Os)ArQOK$?@Da+ z#{(x0-!PK0cb}!D0}1UsDQmK2dM^QHU^Uh^qfEkCyM%z5KD>U+p3Dig{6~HRk0eXdBbUt>#Wf3 zNSjIx4=K)d)OZw@Ck~oL=;$|!Sq16QCMA&4_#{K=@Hk4^PcM`AM)!#!UCEg7#3Lap zCYbn{11Cmj222_{U|1M7;z4HKlX%v_t6~UQUT4LggUhjE*@`~RB9pOyT96{QUACyu zSd0Pah$q(AGruLSZ1(vz9rtB^8M`xB&^zxG$*ai#SgC#NwBZN-&BgQXld^pLeLP!> zkod$g++avbDsymjEGRS8WELo-!c$)rO5vu&%$E7XK#K<#yinPpsEbd)a#X1l6A47b zv4(6>W#PtuK|zUUUEd7vo~=O~G;+zfXXg^3>%3ydI*v$`)S}kOCD47s6B>yzMwY2W zQLQpJ&vc5JvVDd^QjXD6KAZ6`8%Z4_&abSz_;Q}3)7)e7HBi?}d35O=0!px(46n2X8Ln@S7E0U3H`sCz*b^$KI9fK_$(h50+G5SAI*zd=uQKnEBW~U`R z^7}u+Xgrn=&-QpV5A=~fTFY||&apRWBP6CHJRHZ(CWp_xM1}kg^6-O=;*>a~MdfaZ zhu8~2=<_`3GOMV=DCA{#exj(un9QY;IWD*t-6oZkpJ8lG{##IUgL(|+~I)3FJE6Jpw zrmaEjo!@>V6-@;1DxSA+#uMqj3Pq}|8>H?40)!95%GCWSJ*dHcB+7_5+jx($rT zmfonYiJ3Jy+6)9qLFSk$d>R#(Qljx*0@X{(prb4yBZ6BI@AV@UTI zd4wd4DC;!3X9%9VE>zN`Oq6yBUXWpJvm9D^3WB${Q`_+HASX0ovtF-3Vipule2ll# zL1ikr#Z*sFS_2e5Y)zn&cTUx=ilhs*8I}t5lEDT3?IKjF#K535@#gOT(YIiR?(R|tvf zPKbYLmtG@cD7B0#B~hUyHZ~x{3BCjowqy3{suKItZ6NSHmD#H@xM16_kzDy}@gPZ* zdGz`xL{{d)*WnQ%KX@b#zLMasH`OqgnpWH=;i(!5n=3A{;uF(QQ-WPe!$1gNBe_?6 z&%472ZCu6k)!7qEwNA@L>XB-0KQKGAc{lNrk*dz)utfK}*n9ix0Z617m`7M-ug-{> zMRyxbpSTyo+x!&#uCvm5*eu2^eA?BvKn?~%WuS>`3Q2x?v;eT*2lZX?#=z(-QnVg#DhmSDPM_TB-zeaSfK zU6q4&mCy** zyU+-W6^-zmglT#59Ki@dC+tP!!6bbkhLW@3)>T3%SwTF3J2jr;vQoKX3$BgeUM@c-!B%`YeW6u37gwvMYP=$8RXxlqm zqjE`#v2=;i5ikARBeN%NsnV(|=I4ba`jbk&9+YC0P-X?4N4w4)mlRywKRps_3~5|x z9P*5Tt6uNS==aPBC3A#X$)D9a( zEZ^eO?hvYzL$O0LvfJKW&c-d5YnvBuq7(Li%p5;yYGffVHT}Y+y!8!fp2T-2^nrv# zbP<%S3q;a4GM4yk8alu|2h3EO?Df^mY*_EX~B(!Bd!_KF*a zX_a)P$s4A(cXiMy`AU;EOzYq(f2mW6PH}xZRxU0_#*$ie#`2NM#T91h>fOz%&do0E zm&nb?@}i|rZ#pvZ84y{32|UVLqg zqtVE`M5sodt-?FO{Z5y@{Zh>pz4|7F=Z3)*9S0iF zSud-GVur&Nb4C3GOIE9%2?_BY?ekrO1s3j+?~(85kH~XjMn+5W1J6Z)kEyJCctGtB zb0C6kU1ki)58MQHSJr{R23EvAOCbNnO!Cwol8ZU6Z7~*}UE+(lN+*BZ+fee7-}d<7 zU9_se?2w>tT~qbr!=L-OY|xwKk~sL~P2Fhz*!M=q=(L8OOTL&qd{sq|-mB?E8>&%h zjA<#^{W5~oT9qNXEURQ$hqT1`&u*s9Z975Rwn~d~u6U{>h_C^lOqV^#`mJ!4+o!Zm z@**CQ?SfzG(k}vb)bi?~b2|Gq4Ji$)Vs-{oCX|A)KADUf^7g&g(%p1U70(}>Ib`iPvsP0$d;OS^tNV(BXi!FBa8_kb zs7i7O&P&K0m=mgTbP2BBO#3u{kWRZxWhcTv`cppgcITR(-owJ%&QEU66qB z?l2aa5oRJJw)@DG$ljf?93peXG|TWO&TXHcCa<1J`a_+g7f*>D#h-A*tx?lfFB4Yi zGaf}fy*J)zZ;N@D2s0ECoV%k~t^eRy>=@SM8AcXVWpaX;3&u8#QT zbt9xpPF@+NSerfUA8@LO*WSunBbvp}anzD$i98p~90>yEuX)(Oo@2q~*59U$Y=TO` zBbQyGxoO$^Y)sR;)Y#{_Z>CRpcSM99hbz9BI^n$$5z4HQUx$pm%B&$?gx%-pK1 z|A(@#fRF0R{-1l_d*g9;&%~7^#3w>R3yq}(n5u{wA(`4 zr7l~c?$l+s+md=ZW|GOgE9ahjM%z?(p_sd_98n|x8VwR^{T@@&*oy+DNW_dby`F(&x zkf_URDu~p9Y&xPeqX3yYwd&ukn*Du=9_`QHeO;!GcJ6<2Ix=4E3~|FUbqP%sfH3BF z&4q$xxE9GBV^?yFsnE1e3bG|NWe~Q7<=GjQ`+q~XuxoG-!o^rT=e{|et2x4@eEv-k zE+Sx?9N{9I3$oS)*>El%9Sr9pOjd9%2U+TF-#LpXa4twWRxYyRT=Kf+3ZbV?u^EKJ zO8MRvvtkSuK-vkk7q@~Q`6P>j-UbU~m&Sm1!p3UH#7o}?{CQ`f@KKc2KjujW(-fES zug@GmF7Leo9q$7Z_(z^~K(^kFalncurl7;0 z+3^Fs6k^~;ftl`>3KRiqeXcw%e+Al2>oNHQ<9iFZwtzQmc z+9`ZH6pva_0d3%A^CzBg!A{FyN_OFfDw$&T`onH z!fAY`!70Ig?%@a*3^%zBh(eA)W_d3bcfumF`y3-g{t0)AEmg$sQ{qe_0k|hcZ_%)~ ze?_Ug)6}y|qbf}%uOy$8f^aPd_7~0g^RcP;E_wc=lZZk4rAC`NDi*q0C6}VZ0K*x? zKvz5ZdpKDlft488JR2eo8+Z+8aQ)-$m5${2CYJ4$mWCS5Q}<78o|osWo9R61)RM@G z2$ug%fl`?%5zo~4qKV~+HVA@-HlN!IleDUL_b6-eyob90Ir&7ROC1?UpNJ^WH&G#F z>@*0lsgbl>DQ_a4K2?>!YQK{4tZHm-dJX~EpeRQbXVn`79Juav$g!I7Q zWjShyH`XwP!=%EPVzH$}St5tUT4Ax=Xs`kak zttCq@tjTmvbhDiNQp`qSj#BbqrQa)sIZ;-p80k3&%@rpXlGU|tg#1#`a+q*}v+-Mv z9!1s%-&#zPR=j_p{%W{gpa#l!F!s#;Z`98r;~t|jt7=j4=Y}I(ngfVh&S)QlAz5R| z;no%=$fv~tE&l^j4Yt<*f!h!tn{jkXf?hO^U4MS=q}^>1M(@7Mp;WEby5?n@8#9B! zf^Uc#kvnF4Y2N4$u7(N@jhinlnsaPYG`mH%Tv#~k_@o#T1?+);Nq%u_ez-=EXaH`J zw)`+RI(GB9NzqeZysJSFMy!6YegAn9klK_@1#{QzA*LadK!G8~ho3FZpurxqE^$D} zu4kv%Z<-y?^m^c?F(V(tZkojD9cJ7#Oj|RiCY2DW_JOzQcxDhdj-_8%zQwlZp$N5+ z5c#X;oh*+ojINF{c_(?Llqc%M-Sh8jA`aSjk)bI~WS>`zcwescoKhX?qypkW9c_rrrsqfeue5cWI=A#Ru zM@PkYC;23o#%qyLztlHw)`RT~D)xa|M-nPxg@cz`=ik>z9CYvCWDQGfg+z~mM8~N^ zffxvZ=Kwt?snyZZDs*14q&c%?~ zV3}dXnR}1<{GW%f{GhJ+*b?oirnsxWFr$o}EN-7QZN0$kvse;^POWK$-y}@Bt1eD9@OGD zXiu2dw9Nqd+P~IYsI@2clmEWQv1?X_9D>pd?;~>G-8YtAsEM%in<1B6j~8UEJM)LpA`1Yy&*mk}bR(RY9_Gm7{>yY<4|V z4v0g-QA4`xGMG9(Gc1FR@oVut;Wbz+T9x}*+yNpoqgob@O<@^V2ng{o znKT*`p_hpC&XSX#BD03#g74kLO&diI;*3xDQT$N&Q4IzomFpRz?$w~#5hAk)PQJv- znY=QPat?!|_s*Q-zZ3cFL$XZNs*QI@uJU)p!?nNPo-{nYnjVU-UBvd@p>7@%&`&}(-A0ih>MnSSC1IzJQ z{}$h&K0wkMRJv1wj!_Z}3c13Y`-9ZvWC6!8COfNEYRp6WV0cb$B_SoSpMDi5(E~uj zI>3H&e8@}ebulOd!AA^Xyd<6;h@i7Hn3_dD5cL37?bp1n?3%^w%c5ygTujn%jXFLq zu~dr7X&XATHfMbL)Vk=LqJ-%DQb^n(xsgPxa*zRcd`Romix_dDEu@Rs0!tcsrn6H1 zFr?S==*C?kRoQC6!`Cy))jf1%RO4(UJbQ9-y;6(feS?sC(d_YbCsUeAr@*%)s`tcC zg|{Rt#tth3KOlNnvy<;|SE<(lByV19O<9Z*kP}=VO0W_SHH9XysFITTLm($z7;-?& z=&Fb)$juLnk2M^XGP}yd&3WeVxCF_nFIqH$1Vs3OMkYdAxM&nAu*3Z*0sf1s_KL5I zE>b_vKC99pQZMXKY$Io>gXs*TlaOCw-#W@KSpW7N(aGP5uakKB4fc=a)vfXjG7-HK zmF%#SuGfQQ^|%{Ts&FT{t2Kkx9vKca94jxKM0(0fdwRqm|DaS!^bH73|Guh|j4P{X zCk+*TG0B0x@g!A6ti!}h@^9Qyzpj2pBghlizx^&m{3Rq{@V-~YODe7HeFK?_0yhc2 z`Hd9I=UJ+P$S?Kx_kAUP+W)C!MQ5+WeL&RUZyytX>@QNULLAEZVRxh|f%xH5U||Il zwv?yzGoS#l;_{9U1n=SuN%V=0cXp1OGZQ)N_B`=NatqM~gn0Yv30X}XBFghhhe31F zz60Xt{clP?*w$Ag^om@1`1*#yoxnf&mcH-c_#1yePeR4diC+E}(W^+`?egu`7yIuT zoP`)RD5%l5fwtvlPat$cPWFVK3Rzm+T)@6EAu18C&^Ofuc&h#sQm^)^BUGD}tRni$ z;uTUSAH#32i0_IO>euldi>m-h=H}z$z?KhKBPMvhFWKUW(DA4$8i=S2PWIDLZA5xS z7Fo6*;ggZb{L|`dFKVRQNU8kj& zWeL_Kf*=-=$5oXe53U*!PT{IIQ5r6t_?FQlAkuLJ4oU{TWA~^7A{`E>HbV`zgV88a zmP1r3VL1~Lgc=3T0Qj2VVGH+LhssY2iTn1*@|eRU`Sa+$snJs8pD61?)VsXv&rzuE z$Lg>eBf$LU+|(~X|5d?4O(bsk$3@fgwkISRG{_}NwBa<=+B zV~aHb(Iv{Fv?$wDDI3Nk;n3vmpt>Zse-6>YlQGudDEgksni?Z4K+zzb0dV73lnfFe z9#qm6e5TEx7YDckluM5Lz#(qZ7OQ;u4eN_<%9kMn7}IlH3^1k-Fs27=V{&HbFh?9& z4H!L)46cN7cZdR$Xyq&7M%XRxb7~m+5>g@j65Y2VO3aSwdnB5?ORbxJ@~1BHBi_PmJfedw76JPHPDY4AdKAZ7-QRk60o*J+gDA%GRHe6hIf{5!W9kccC3r_T{u?pquC*)79 zNfIa-y}2WQQdOL2V7VM<{Yj|P(BL~nJebYnuF*L7Yb^l;#h>i3Acw(?3Rs&|G;!Y? z-@?hlO_!bl@U5Hp35gu(cTRryiIuBfTAS%Sl7x@YIi`1yS}|*1i_2CvCX}xxJTCvV zg%F`}|9h**8**IfimqIdlJUoS$Qyhl0<@b->_b!`9$1$&6;O#$uC9sE(L-EZQWH{K zE$RTCFUOd$Z`e3R4shWO#3mUmUo$AM@5mCY$PI^JYr(L3r%^-v%S$S!ltyVC)UHxW zj>F7DohApP%cOfgT-*D^f<$~f@!%Va9ZM(VhRd(SkSj4)&qtGAVg#`{b?IE>{Yz%A z&238CePCtKChuFGes@y+l27hKY2L*9-=AAQ@AGp@TsDQz+q~CW@*VyMnMguKUJhah zp734^RK8}11oe>Y>;PATCCXmEfmwheP^sZSw8W8_mGELYvmCA)J2VjN3kn|)H!t`$ zWD3T@Irq;U*;5><7euGRDZ4s*Zm$AZkV@Y9IoVAG(R#h04l2qjZpjKJU;n<8NYu?s z0wJ8=(4gF=EPpbd{NdUD@_k*RP%(ew=Dhft#}Bqsp>_8oGd$wET5_n6Ja%GgMeLNe z0wNSmTSWe44Rnf$VS-8Ca)A4rT013^!tMe(b&9wOI?*2$tAS?6G!;-a{{Be;f}2tq znQy}(GkcK1lVG*5ZQ^MeND)WjxbdHdUBl(DQvmTo)roob>~q~ZUGJM~8+T006eo#g zb8?Gj4A*P@D+)Rvnfp|aR(sp4GZwgI6^zT!N`3`H@>+BBzsq0!%WeB-0&*&2?^&|; zg=vUTOx@n(P*NXFhISO@w&$Y5YZh%dzI5Ty+6_4HZTU&8&LXEX^Urb8fz{6=(j}-S z%HzcX9vRg#tJ3VKH3Ex*ey|Q|3*ljNrP8_+Bh7%mRB-k*v0nbNejoZtCRsna5IfrG zkzLXvq?+|6gg7-*mJPN#7fJb?Jn*$dk&eF6+S9Yr*?}>a9@0~7R_3l zw_Th!^Z3+69d+n9JdMPggQIb|29@R&wG~Dp*|Nc z(*!PsaB&3u+I$xAR)yr{34&)@tj1zQfvPf7T$;+QBWYk5@@evjNCZC;IL8u`nLJkN zRpQdHwv6|ugZX1IuZ_OT(^t91M81+RJSwK4NGo~8XZSn|7J=o&J2x{dIS@@(vXaU8mB_}?kn#l$^)t=B zWrPOgPAF|%S{8t^xlxP8lDJe)-^`@oSa0O5F~d@Mp~zUHqp+7AW}OF(1JXwc8nXZu z6Z}*Si1qk3DMx@=?272KdC?33YD!r3SfJdRKYO&JTI-t_IxK%qnJA8(mvy^$5{`pg zgtxhP+Q@K?p?lZpH|W=>29QqNC$|)=yK`Jbc6^AVk7ru*$nq7F!e@D>=Z5-)dT8A} z%=L3B7QTIM40-=!j8la?kda52{gHDwdb4UgB#jYdb^#)U<%}^>$nv%LG72vfc&gnV zQl39lUp)KBw1#;ouS0F*CmQB@H?-R#H6r=_sa$Gx| zpC?E{3p53M9I!P8fTu8Oa#HZ*q$CHG?_?EY+Kx?RfIUty8psqPAeu=0=ZRiC{}cj! zpFjoAJgAQlT$_QEzS6z9B|&G1a2(p$dwgm)=uoxFi+h_&$7cm=HRKj_?G^p9zX20; zgqK+|xJ8YQ z8NZkn5?NitlKcqN@!LitnB{mCQU5M<0Qga@f!!Jj9SU9^)I`%@No8?DZ3eG<;ptkg z5TIzhND2--lqMdQiiLm z9vtJUB6+Tuf&B|aHyC7-gIt`PLPAm{KYlecc%E{GH3`&yKE1p&wu763%rjo_!;M9{ z*(ngj40Bk?@QAc2qjVa-^pdcH0jq}}SYInj^&3w#rFBe@U)<-nxPJds2W@#xax3nb?qnGtr zawkcf(`s(Ya`AS}7++Pkd@?&Td12lmo?5qnVQm|_79VYkVG=u`|DTc{zX4cQ2wIv& zzmQo1lM&7iGm#ji4X1_~8SLc1JQ@%IR#7JTMP8j6K`ye48_;BSdGLfhM7J$U(~4Pd zy~UGdgQS9v+R%b%1Iya_Ag*Y(7yNdXsxrO#yoR4qNgp>(>-ElO)W=?e&WoezI1q+s6@I& z_zI(qN1cP8PU1ncOI-Egx*Aj?g4N-7{eS}G*lj#)=>v75{BOZ4H?yE6H&i3&0&B_& z7F6M$nVWfsS8600Y=?R*y?3O8;83^e8F2*TK}fDgt$ud z+Y5W?6Y8l=TK()D0W+W9Rg0aBhBbCV%mZpCL6yR?gfZ$A#OU2^HxO1+F~bI3uQ%os z94T`VQUw+eo*x*S964gzP_>|M*!ZMnHWDu=Z4&{sHy2<M{8K_eH>UoFiz5-5_q4J7L)S~;4>1>K8*AgN@qt$J;g8{xhLGw2BPG;}7nr^_zo+Ir$YIh|Lp-y-{Ln?Bw9W!MaBo1NX{DqKm z9(ChN5N0H_AF~QUh|r4UBCX@yDMCb<@2l0CR7bEI0ueh*EHva zRZeOtsA#BPpyp#dT0M4EIqF9@pP#z@!_6gxj@tdnmafP4=j~3mEG=%ly$v)FBM&Z0 z=xWMU>s$i=`~^FcB8N+x`Fe%2oe9xkwSt}bUBI%OxbxYXvB6=Wz(gxj&qodg9s+jE z8iQ~7lE&7%myULFbqe`2Bv{nDt$0cPbNhxb?<#SnK*ULTzvq_(8nHj(*ESVN9= z=h5ry_(*9)2qGnpl<6*v7r$e>E5b4npChWs2(6YsaLNoDqp$ zl8bv^8u2Jt<|*okYkYJ@MS_ns^tCj{obJ1(x2>*@lJv%$CNWnSdGCXDQ{H-@Q>5Jo z9vy=~+@auc7wWm=4G(Nvmg~*dE7uiQ?oOn90LrW2@K1!F^>c1nd@dS zWpMQfgUt35@m_uY@`4>duhCP(v@`SS7gzY}+T6E(y`_Es)MR56F-B{HtA`cWr|3Xv zs`c@SO!AeeCTCHdxmQU2-pz;5Z3L*egYa*D&t z9SzLNmPo}6hzUGZ8UfM|$T*S@Ft_M03+4A0tRXI;&5LVHm65rQ{*HbzVUF^|XQb(l z4;@u|275XMI5~zCg_ZY|&?l@#^a+1dbxhBvE{4kh@3@D0T1$zbx4!c!p^v!-LFv%X z^hT21%|^^5?MXJ$@|IWpB>9rT*Nqj~C~8LcZuMv6KnepweU0xNg#iP|yu?Vei%oX-Bo>K=YM zytO$OfXcegme3VM%_Y!lY->X{Xs??l#=iXU`1AY5At5$*+kp{W{sh&!{_oHS{)Ea8 zzDyWq?h+K{ElG}{VS!pp7_~U^tTe1>rRD@PhX7(fFees@;O0Iv}JjTxSq9V1hiq?JO8sJ1bp540m#PkgMA@v_x zn=@6jxu?xT;1N zdF6_{ML1w%gk3S=R_pUK1-b%^u%j213S!OeM@Dv~End(GPMCtlkfr{o`};LNU`1I~ zVFJfjgoNl4JU!9WMr%O~EzAbJVUapzRc-Mo#@fvs10`x_(`w9f@#5+>(iA~HAQ6W= z(yAAoURoCtGHO~$te-)c7;PU8T@Rub{%n^h~u8g=N6H2H#VK6%|l`TZ`o{ETP! zbL&I2lF7Xr7@Qx(CrYqgppDdlflv@`NEf)fxbTbtR3}$Pm?GCk_*A9lOE7VX;h|TU z%Ms#(Q7oZwxv!mMt^IhJK|lGPS0EsLB=XHSE0>SOs<)V#fbeXC^?M?8J-2z@>G|1Q zBuLiO5rt^T0Etlduab95hinp|^s0(r(rs{Y2?i<|>H^Hi&h8Z8GJbRM5#kG1@+=me z%IA-9;(ME4;V5ZXcudF;_eb}sqw6NhKh#RQdgWgxu8X$TMGNyGFIMXsV%p%5S~Or5+J*VqbQvW`rlo^|@ z#4{&uh`?jKo-R6UjmpW%-3S6kM@0SDE-`})DM%JS=jdS)0eis&tA@70SYJ(j955bs$dYJjC>ErAlPo?05~ zi=u0r5Jhypc5X|Ja0Pm@7kZNI)ZYHDq)8Y7Vg`iiFfbtc#U!7Diqb8mZO1~Gx z5OcgZ${H#^BZ|RH3y5lT$a z9#>UZc_9v1VuEU`tR>H`%yMv;dFJa?@`a^;Tb;wczp`lh;dV!@OU8sUlGAet-#VtX zmPgA!qK?BwLUd}}p|=*U`e1(poV&YTU$pv@!%aVS9G_xdFdn=&t#@~u7k45napI46 z)KOS3jeLM>Ly5x{ONv&MEc4~>^JYi)#a~m6-&h|Z zwhN5>c)XZyOz3 z2R-i(2qmJK8-SLBRu#d8fz>LeYidkuIj z&$uZ7U*)+>@$!Gf3HqJf0{#9LS^XvnxBjtOnT4X|F%wt$Apb-ORs!xWqomb}5N0QX ztpJYeBd8+#I`Knd`*h8Wx$+;mb4^zh{_>PhtWT5iAB(TB=L|e+Re!&9hMgc}Gw>`I zS0~Zm-$@0%=XAq832IU1?K0y|xkc=D$EqlD)#OPs*L>1z)J-||$~1O?M7;541uhVM zT(em|jJ7SG$S*Hupd3zg-1M8?<*6cPLJOaDQ ziy%x(%q3ZCKJLXGc8uL&%*c7GVtK7W7sv0gb=W_-{I@$)_)ky9exnPv_|(W!0(qd(uIlk*+>RvVTDFX-a<1={E*eT) zjEdu;t%qae01_J4Svvvk3k;4LwPSpJ>A2O^F7gLHr-*U?=I3WcI6JvbzW0ZnyFXbZ zRKeogQ9U0`3~FvWI;v~PqJ^FLXphxfNA`ccm3}}?@I!b0QrF+B(z3mqlh&!#rOWLDCHoBOE)hp6KUy;Wm!w)#&skzrfR5GBe2g$Qf5bBZ`9}?}UC#3H%NHK}Od1hT{Yj%)m{rMw8J%+?bCwLI@ z1gX%bcaI*oxD-S(1angfw_|1srbo>Qf9)Z3j90ilO0L|7>IE_D)4qFHRz?w9FF{5USw{3j^EE1oIw*}`XCS>?9AB#XIsKim&gKPFOq={GkKrh{BFL#m` zNeGfKv*mR8qjSWE+)cNG+jNI@5|c4#71Z+b?`#a{|8or#&_dtx4Z38#D1U4`L8AAc z=?8xACM8gi<&!wqY|JcDV7JTlie0Xxlm3fcF03+H|1{Cp+0$e~JQq2|6pUZj(%~PH zUY;D}id534pK?L?sA|t`Q*y|sRLZRF>fm`ShNn7(=Ylr?I9*`0pvTpW)z2WnNI#TADNaF=V{?voh0iO?~zK#Wxw~lX^ zUluCKA5sBu)U^20)+~qE;NiJ>W77Pu{!7rs4)4CBxoLip5A|^@oV>YZ^6Fu(1PtDy zYsS!m@wpDs#A{e#L2a6!87zms^8M7$W8BmGk)0X4c3;JV)emlK5@^}{O}iUC1IILt zOeIqOcQl46ep0UrSmNPzHd zBjdIbzjM!7a2TRaG#%+r%rZYf8u6tZZchd5gdYsaUZw6X&nbSHumED(i=a{bkjX!2P@!3j(wt-dUPm8ilLL+;S z?O{|^1)cO3KMQuB$6pkcNIsu|t+I{TtT+@mjabs{ChLbLayJQ?@#HkErs0kUnq=p5 z#BclAA4_{efOfy&EpdYrhEkb{z@{E3c{e!6|W}?;~3=ifMUU5 z99YSldp&l)i(pepnKA-(;~ft)%Fd4y-)$IyS*Hmoh?k#|y!m3@7OX77UD0YKC-OgO00A-*gRBU;F#yU^^(?+Cd<==H{Uz;=QNS8@3I@E7`X8z&_8ziEo zUU?ZvvWI|;iux2h6(p|?5~h*Z%4F7SfC^1JpP6LuHSoj&lGwnL?6cAW;(Iy^e!KsE zcs#zSB6GxO**#|Sc)}6!_DO_m#k4(0f|cws0~IkB!7e>)lXzU&Zj}Eu$xeTbvOXV4 zGT3I3Qo*qS#Hb{ z-&1#eiTsYI2TJ58%mtIRQX0#7~aa?Nzu+=B7t{}D5iHxL`+u2I?LGg2#3dS^S z)Dm8jMhPR49F#&VmEWXW$cN|TueTjdXn^1a=SGs$Q2G0c&2tMxsqv0`slWAeF`K}k zY$7zQC?y(wj>)qK4puL%ZM&@jbx0y=DM~m$KFf;4p)VnjQb~zXAn5c^S={yLI-n5N zLQV2)=Z#g!td-1Psq+bwil>R+7^g|7gi;EDQ_W%8%E65KQ<|?a>gQw^wd9ABJApWy^b0-AK}j*FuqG%bM+}4jrz>v$OFyw~TcXmXRSD-=fCba6#1sxlkVwna#Oh`y^dcsf^ zL52l}xDoCueky|*hT4N?p9G$a8nBnx*lk{j?Nl;b?51NE%A%}aM;&Em6#a?>W3XCv z(}oA?WhXw^6sT5h7zTTu?u5~Hkexi+0NXb<4-9ZvnleC)&kFIjcYa{}U=D!+w%!je z3e3$e)Bjn7|Bnt3&Y_K+tcwdEgJQ7W|>cQ!lqylEOc^DG}G z@c-DoO}dCrr~FmnfECg={S)gW(t&ID_UQgEH4AvWEC~|t7#F0oxTD)(q_3kbk%@bo zu(=hRg_L&^|!yANcXOW7U%Tr%paRJ=u7SMBF2Zfq6qJI)>_n zQ6%D+`{u8AOx)TMW;piaF-aYiKcsxZP(3mzft{z;0G>5``Gb>$&-(n)%Ln+IVY7v3EB@*Qn!NBHWed^JUMACj0{9OsRj z*V;S&*oxtxtX+0^JY8;G8Zxph1*4piMORpR*Pv;dFk;1_diI-$QNt4{i7g$=qUoOf zvZzJ%xfkLlh!Z&I!I!1?_4Cskh($l(f-w2#T8H1@)D(h|U&|V^V_ZdoXFV}RtK0g~ zQrQ^+YTvKVX0+sKwSM_Y@_BNZDMYmB73z;JUiVM~Rr~yT5uI5_WF`%Z5(s^qaeW9X z=l&}iBd{tg8=s^KHoHLgB_>fzqBel#}Ds86KjVXCMh7W##A`i^QkuswC zlB2{B!kV=Hyw{eNpK||?gPXLju4hqBtueN~v}t8c-{UMpD>NYNe5-KabJnl*;=fjO z+@kbr{cRlRZNZWg(CU9ET0JWW1O|d< zzPylmb9DZxQ}USqjVoH{;Z~j4zaP5iVVq5+Hp~Sx0sR|V3gg(b8Zjpc1yCGr!%b_h zC9yA1L@0qm4>880#+gYwt@Gu-(I57=JX>?*pC`ku`f&1d^DoU($e$naVC%3cNL|kS z``^b|zo%}v&38c@-o~l2KNlDu_FQlwt1Zq5km_v@Wo8n)RFo$Q*PcjSW^LVqFp}RLUpiw`tk?;ta1BUu);GNE2B*dCj>Sd4|9tQ{NDGF=p5 zE8j=P4sT7R;>1N_^Y`CdL+OWoy^KG58RyUDJlKq1RVC$N-I=b0t$>^0}gU$_1f2}XD!QNSYk(h|~DavLI7AH1At^gFB%uOR!hxC8%o8%nbH#+KT4u&a~FAs4) zAm2s)vE_1)>%bO9&)@1MhOuVRYzs$PEk}CU7T!abuaw_kC4_-naXmX1XrNnt^rdFh z^uBo4f7ni|s&`;JSu9bdY2>F!qhi>!C|*aGXAX=A4GW8p*F?p|`6b(C&w^bZNl9p+ zt!SPbXRmBA(?sm-GlBdc_gUN@-e(RcPTb3Gm%sFPyXB*;WauqVHahvsUn+ZkvAhj? zJ}1N)jVK7)^Zvc%rxUhf&!d~M=d|;cR|p5hU%l}Ev;RW>ZE}&AuelxaY*v-2V$JT} z4o0(C6&t4;R#a3tyr8$#i&;1+AKIuA^0`~e6>ciek%SEbjYPQFfFkxh7D^hJtTXrx z$BPiJj0_LZB%~3;ca8U~Dw8xFcRt>7kN+z3%qpEOp?-+`i)m^7L)*q`G~;(YIWaq> zMyC$W2yUufQXok^$Jz=Kn=6Otw7xlYse4HF+++Wi&z?pT-xD;dc0*Us)&>)57Gmm( zb4r6Lkx?WaUbA7!^us-AfIg(8M-7c~y2pFM#^x!jaw+jnu1FnPo#u;@64wEm23$G) z2jyzvirNA3lmf&%Bh4-jKE{FqReVudNpWHCaBo}_%5tQnWF~TTiaoDUCe9Xv1vOB~ z0dS9PeQY^B`&y(ZZ!m?i#o(L~FJ39xdn;g&9j z4J~ucJ}9Oqez|F3v0geNze(KEC)JNzUKv7YLRMr(jPve*jy2V7^V5k#U|LLcDyr|r z622JU5>Hp?gcjH^L(nbV3nzp1^mR2xPfiJ*oRVTxc~4de7Ne4$#VQQc9;qn<5!-1; zBLTI={6mJMr(sbD14+X?4^g|bM52%}_tc_cV7hUej=b#L(_kOv72N+^H@3Ga+nGS+ zv=Ye>KTiIA>~j17Y~|K{goyh1@zQgQ=H~C+$jw?WfpZk4+m|Ml+)jkQ&-q0toVlS) zgO>4nsqmKB6@419XwIf`jX@(B)P=XstHit*>rEJJGY{x+)dNA+tu(5nF%don<2Sn-H_mLJxEzr$ zW0f?nTFDJ?7s2Anp%ok*!pxsMlou<7LL|(Oh6%Kj6qu&nH7!2SCn6)tQKlc!8OB%N zAsgs>R@n^4<$(llts7vYSjFyJH+R=r1GM6qMp?mEfd| zkmp0?!mexSpDztjNAtTU2%SE{&Ddh3Uws9OMw6PV=E?t;aIq{-jP+*!zl46aK0eZ~`ms@hTH(0mQf36V(nJ5;rEn`fqt!^xNt{n9&8SyP zj9aM{e7EXoIg?zx8Z0N0*T@+L0=P6s6sm7MeQ8oD7c}8I!T`o@TV0yIbTKDa+IS4} zi|${A3eJc4{8+RP^V4X;!_^4!!Kq0UcuO#4UdsFy+vdmGa3H6FY=>R&ZW8YMwrXz` z8Fkkvc_n8qTYMcA=A>AK3X}0G4NzgSZ^eh?67pEz4mP)^I3Ffj#$3mTc>~o_dx4D) zQv?UrR~kPJYBh0c3RIn{RI^Xh2%Tk2a&p{Iou#m-$OXQhpQmSNh+-HoPE7S?KBIz~ zW2FYzzEn+E0{TP&N??L2zzhk{_fUk$D@5R*ozxD+1_Qo+9+fh%61=touD{LA$KhjD zmQp|dG}|(B=FpTP9bHB>%ckU7X3UcF#{{J6T@M(wY31@$zLlMshEbB}pO?mB0NFoe zIaV#ZuV>2PX^F-Wo;!XNJl!W9nwnr7>D+k7xmjIXo5I+8Bz0i^kj$E_FwH2n;FqU< zaIF-T7@C=aEYPG`=>C%wA&<@vDTy0LI^!#|_k2rA=M0mb7@;~9OXd`kE_rtck0(^F zJk?Ep+Wr2V?|zUfSDfsYou{5zJxrjZ*4#TD@7fhti#P*~sTh|`eom+>jfd=@t_Yl$ z@A0)RRCTenZXRi{j7>?2%QRSuii-!|I zi)~N2q3XaMlwjAIRPL*F$+1kIo|#&trM*mdhCMtPt2u*MX< zzaY4P7pO4STkZ3xA}=P2oB}_uD?A-R|w%J7V;H4i$@zOlny?%tPnsOx6-=N)FFVaU+qFEy{A&fGlSiI3zOa6SQ6Cd)_84N)StOKkYBcd7S}mx={GDtNOyrHAiI_+$k_-r_CPWja z8IV(3;S$98x}c5{gUApK@+mK&!9zaab${tVcxpzH5}uL*u7#(LyCFQ)wapfu(gd1Q z%iA;b>K!XrE}f)=s38jQUH63S5#bLM5H#v&>XxX&5|9 zHD<}+K!~jTFSGpbv8z`3RWHHcMSjflelrVW%{(x%V{1#q&B9oVUKtd|f*HlaSW?2u zmCNQ>myJ8V{Q6KkuDIJ4$|}WO%tKkY!^-9D;v2A7Lg1Sr3+3tRAXwnl zhZDAmOxFVNjOe5W7K)AGd!Wb?rFz&Z816K|qvKA?FmGeLTi3yNSMPnZOCw*Fe8-Nh zm^mUrpP&|8V#?)q`EtkG->Zu)+aDN5LKk1YXFL@af4rxBVL7D*(^eM9?kI4_x2=nvIM6+TM}%H*tV2#} z$^8wY{5uLiGYVUB!qB0adKX@8m%nO%`Ezy4_NONE3Qj?o`ow&aSPwKw4r_azJY`(I z#(II`{w26^RpKL39_$M3Nhcuc+3mxMjfF6K11-=qcxi-e`RBfxzHc5SrmLf6V(c5! z_v0D)&Hh>;R=)kdS;vr_+IU~LL(1_`qRoXvC$WN2|aL$mOM`R!abzU{-mxUt!f-u~5 z(6vgRC35XmSftubGw?gSBJ34REWDGwy6&BWUYTxuC;yazzmt7uXr?#TPo+IVKvGf~ zOjON`h6cQ({>h1H)zdW78tZ%L6zZRplrF!2-An7XnOgkmz;BP@Pa7M0@Jaq?DJuVb z?aj%F={eKcC-Kk4W5i$Uy9k+BPor}P$(GOCU*(b<01eoyF8q$YdWF4WZ^R4vu|e+> zf3m+|A7TA)(EG0YBZMvf+$^_~o#4J<5kh7idn2dHA{eS+8D5ZK4LYQd3?M}#G@1z3 z6E4CF(kz+q3zS)8c)4k;%U3%cpGc0Zayr&cf{l8T<@{%ZQ7b>^B25`5kc#|2-)*My z88ekSU0Vz2$6apY3v$qi^Mo2DhGbM<J4f{mX?NRME%bvWeN5KX z+Eyapp96j_z1$>=B#&-ZzYFR|1au&<`Ux%d)LcmZ#3S} zkGX60_5GMpDmu`Q85Q6q>8v3$`MVr9g1kvS_+R@nD{SwqF*A1D4b!t~!eGznAWwRV zzKlQ`qxJ|jYA-8F6YGhpD0~&I-SU^+m!hnz*$P5+-bcsC>lG$A3OQ2=-$?VW`y{o6 zx5oX;Ckfw-lfM}MxD9zK&9i<&gXF&{_*39gXwM@-6_SfV$?bX1VfZPO*EzaYZ2H}=$RpWop7w13s) z(u%3zAXukNAANiF$$MC37*6kdxI{E`iuxriuHlU2hlZ%T*=fZY?>{T=VTf3~>j&js z()z!?3v0Ps>SdorOYmEKmWb*-Xb4Yv55r&LuYXqlT0px8JVS87U8_6;+7SiQ63gJQ z7%$6eB$~a@b;V#5(NWg`(s~0liq?Y8d5Q(dVp_ZzP9jk1{KCa*fJ-Y3M}Q&xVBOjX z+iAwZl?SPXEVAAzhZx9=P?`A4+gTh|Y(uZ&0_55CuN*}&n#hr&|F%V1*%*M%W@8vU z1mutv9snf?U>gFL-kKXm;1=>4@d2=s^lh59nS4epva6hBBn?TwydfWADG6b`^%h|? zqtbtgvFMAcjmSm(GH4B;lboHL&^1>8YmmhOyd;Xx3JaLW^!U6H$Yp}Srj6P>g4(=q zqO7k((VuxmUJoJ}+w`B2C(g@yPQ_@A`HwqiFv3#k>J zlEyv_epbfdXGuFua%;?4hQT;)2gq_{{HJoobs$-GSIYNk?!~-V{2z9?R;P2fu_iJw z1IlW-hA6Pjg50R5q|M0J*{BpzCCzlzilL59Z3o_4<27PNMT}0TPZ~dhp9oH}*!RGH zQdex=@|S9c1ZG{U{Nay6QUblay_wz$@K;;>uGd?bJ~P-cqU&@QTZmNKa0Osg-M4(d z(x|DJv$?8=>#&q*mCpd=Yf?bIn^{_zJoY&eCcnN9s;u7s=>Vv*kyA4%T|Nl<3vU4& zo@x(N>l6W3<(O$ShA~2ULK3{VdyLW8(m$P=$nZ!+6qpx-@D-wT3V#7KJJYd)DoC#7 z=>w^>+x4LQ{`*tB0v*d1+&g2=t{NYsuh-@e|Q#83Kh4Un(%7aq)BB|Ft<;N`uox+R}^8kIsIRV zFTh{BHd{nN*=)I+j56MV*)oa@+gu$vLkPRWZZcEb+{Fd@rB_Zixwz}I#;>Vsm{$bu zB$qj--=91C@nu;K28U6-_jdcmWJh^Rj%q{PXmioLad~=oR{b0l5+C3sdOGGVJb2p? zv1HvV8+TN0UsOtkx?A^EY?}Yfx)Oob?YLlR?YytIoPMoME*%-smOiZozF+L<=>^+7 zii%UzLR3zH-`laP69vKuE7uXNnC`eT`Al$@%gRxvWOh zijsYr@do;WFui&;^M*oAJg**D=%2WjFyMKpvw|*i&`U5@G#Y1-!wyv1!u+z6vw|Sw#C< zsoJqt7BL&|`op%O#mc+(Ep zB9?M2rv`pQnAur{;z_&_5BPg zagj*kYzr=Wkrm>~1$Y)k%>Em6M7#hyIvxIjs}umT?*O&GzdjrV^XO5gtO9#F9*>n^ zsXrDD(;&MAO<-ph1LlR>u`D=YZ`0!CNE4ryyvvJSm+UAMd~f@3Yrbog+su=nuip4% zcYupeUw1U^(dacXwZkeGHDns14ekZAYug@e^hw{e9oSd%gly?0^}MJ0!bsDRFOh;+ z`tdO^t;~D&=f!)!CZX9gT82^~t9$t{xo_l}4uFrtf_{t$)xkgL{|rn3JD|<93P-Gn zk|2M7$lM%TW_Lur(x)raTB4{OZ})rxAT_|bWz)@3Y7xNW~Mo^(vtJfiM7*=MEI z8_xAa&wOcby+G@?otrrSJn@aH$OqzQ|p^TWaIyN2dy5 zm^D2xh&cCNo;sy;lyP%#4})zJJ3V$Q?k_huP!Y@=skX++Z|a{U=d3$pi1UhzxWyei z?!LUnvuZ|JtWK*su?OIlnzhq(o;324r zl3y41am}PtxHUXIbPV3DhpA0~USgF9HX}2Z<6*BG-F3IQtx?p=*0b^t#sehNx*?i) zZ2EDPQByg0%ZRR=iKTjD+Vl#g&_c2Qtj)O1yI$H?w{>o<-+8~v?vhd6Lnzbs=Mjfc z?7|C@8Z!0{w@}5V15hkM=$h!VocNgY@)K& z3*GGXlHx+}g=g8q5H>M}&K$&8z;rNiBjG(XeJY9Qcj`I2%v2Q_iFPch=5Wmv>xolRXdZP2KoXtul4;Ra^x~$uj6MQ*5S>>X4vVi*Gni%8hHTI{6 zgsL*N@xNSidpK`k|I{VEuU129=t)0k}O4->T*&@A8m))SvL%1kG01UPuR&clkM8 z+Y796kMuMDtY2+A_K5N>A26s6>_sscj0U>>_!`iG!+;Ku76VgOgV5pw$wbBidSgOS z&75EQ*^Vo_^Oa6M8~=0TpWZgs_$PbEZs}Rl`Ov?%ZC>-wCwl~8+PU9u`7fP+Sezmp z)yU@|H5x?cabq(|f`|x}zpKuov3sA4I$JrjAUzKQ-;x_x^I6|t#zhul&pa^G(L{oT z17X(hE_;z>*3*~vZ+K&xQP+Lw!o;?_7Z!*lltzxoGS42RGe&k)L+mff>NR8xk?(XA zX1=}=tws8iW9XP%nyyZofTkn7M&h>9h)$1l6e739g?)he5P-s+jcHz9yIzwdFm`!5V zv(~;fAefdn8R_&_@0iYqnBB9tJ+mQ|lH|7H(Q^vn*FzHH5WiM)={Y$@rHZ8HJ3SB; zm`0jdSKoeFE)d?~Qu7^{eoWNdKdrx=|K7d-4{r4hzh?EZp9|0NzdM1GHDKqlpVQ}I zyAbRZqD$B{Zh+P0MZaQ7=m;pXL?m(H==Zml!%DaU)~B9raZ{VC{z@jOF7&ij5M?*4K@4+AA34g8aS`{HA(wQBr4jS0Xj9TU)UGi{(u7 z7a-3z2K}r&fhsA5esWGNHdwObiG|UZo9y)EVCzLD{4ez7cGrrncRW%=C=w0;Q%< zHgDLfgtjrLvoD&u@Ky29)#i|j3_;AFI48ON#dgyEeEZB7_taC;&TOaSu$`X6&IN<# z7I$(K_<_)`1!;LpmV+ZVX)$q|>4d$*Ht}e00yQD<3+?16vZORHlxaK)$8!E$u2@WP z(EQ0$)|g}EE9;CJzYH+Q9Ev5`Z70_a*JyIaS>~i^CAHR&HCLuGS%ocQl zTTDo5_0AR@x$P}Nz0wk+(=br*73Qp)HKHmiGC~&?s#EK>xR|o^WJq>-vG*`KrATy2Ve=(;&113|G9E>efv`dC7(d zTJi~r8((Q|P7%l_Xkz%thMKOtxTFZRn>!k{tM8yycQ2n3QC%6}O+sp${~u%T0T@NK z{g3axGqW|>luhq#vzuNvy>3E!FC-)+q4%B;AOQlQcNC;33W6XC76hcIU_qY+u%coY z5CQ8`c|{>R`G4-rhN6Cd@Be(+ER)@tTh2Z8bIvhOC>oV29(j50xyq{KQIoRX#caF9 zd$|8#mji(2$zfF|(}Dt#hemRuwDvugs`g{f%sj$m+0m6Y*zVuPg#Lc6!tJ@fE*=j4 zE_P_XMirW87T*pezLi_L3wXggrXxj#se1bits4ILXdNmDsU3*;tq#FetumyTmIWD> zj@%fSlc)&a+0!OTBTQfoQE??2&DRy zr{x9S|MJ|ADyox4P0Geh`_CgI2R?RK*6xZ#X3d9(2gGSQJQ0&+<#gooYs?8JO%(ZD zch(hTj4MD2iQIi~|4qVnyQiSEU{6zUNX^uuXp|N#jkG-wK}H-U#-kkP92%I@lo6tG z<_yG3uX1!oKEvF(d&}&_1vk`xPnGuKfP`#%LgPQ!k&Sy(`w&L&epoKBhr(qpBl2Ajj3^RQR*LI@z0{QW#Qh-eRcYT9Tt zUSg?fYP!uVhM9?a4)LBt1&_!OT=rEtLDSVb=^P!adJoQMSw9@P^R^2pGcZNYP8%|g zWv6}ntu2%O!-;gh$7g)pFL|GfxFB{S*Q5w_qo**j1}!k*<+#)zz@O#9k9 z+Sk5oce#ek|DJ1Nrq9jS151;!)??e_?99L8^g~UQjycqYwf+U2_`LY|1*-adCaa^KeriE`ODkGB#M5&O9@1YBdvstWPH`O8?3 zh4hKVgP$Hu8umAAJvS9&jO)L~{R0-eG{#^ACRHkh4AGItI96t(@@mO-dT)(5Gz4;l z8R*nGGA~l4K~ZfazzYtG-e{ls%&a7x;4tEWw`P~D&n-yk%*Y#49H~K5)X185{L&*V z6D=s1Z0Xtw&uGJ@SEfaEyuNubC4T15f&$XoW~4`2rnUh)LKUlkPm^lqLE58Enwy)0 zKf_JgqbPPFwENutju!|n5jS^5{BWFv9_6TWSOAWFDBO0g#NA2h=rrV>Q**l?fyI66 zl&2&F$&`2Nl*^RI#xfcExl5uvX)FeV&X1v+U};0>4xH_Qv0zgXs=c+3%&g!AbKiRN zzS%gsygoK2w#X8oa?rUGwX285p?1lW;~9xDx1da1#&QdgX@hFarKovbw4rqL<8ueu zX^Wx*@va9^=>M;zn%fx;FwaA%YlA&`)=MHKw z0}s)`7SfaU_WK>qm^L(1ql~C%jk5N%IFc~+^ry~_8}Z7HW|eBnW3Q3wuPXPfssV8K z@CW02zFJAduSnYP^xPF=bZXzsn%EDJhW8Nhv<%5lE{k?2=Eefjv3op=gpYZ+ea4m{ zMpkvPVR`GEJ!7H(K0mfHyE>ebu!fT4l4uXYED-n-N`wz33k1FdW-IWsBpc!&YzW!* zfcHSMEm%)7s`!fU;EIG#fVXi1rbiGE_o6BW2XcN};#a8#BnChag2KlR9r=oGe@nbB zKIO32S?gr3Dazv)`;biHCBFKfVWcU&C9*1F9kYZ+Akh01y~IlyQGfn7&e29Xlt09= zs>|ww98*Uimq7~rq4+-McrN>!Z}6G1bf|FUub&aFSZ};!l|DoJ>&4NW9y4LwP5&(A zdeLYrvfu^K`;3Bt@q`2vRCoO)~!Ujbt~?EnD?Xc%1~60Q6Cggm>t+5sQ#$J zjtx~E_S9M1(w4`TI{EN^n@X3@imNSHQ@5FMsSVtJs2+})JHD64^kWGwOZA?*+_tn8 zarmNAv9)Y@C;MXU1L%KnMErmxE55n&dt+~Xk0kpz zytlaj64oJ;JrBR6aYDC}ynw0KzVdZ&30@MF>#I^KwpDH@E=%ZMmXGy*f%n5&g!18J zsS5i|mXaDq2Z|lsf4GTC1+Fi1WY?E5+Z5LIA@L=;jIeq-?h1#)&H|E<;u589ez(RL zeCr*_2y#HvrPXnRoP%k6u!D6t-j_Lt5alZs>*gialx%5y^;E@Lyer+@_$sKZ4VG$w zT}pztV}Th*l3LhFbu6*@a7nSv;>?%DE0<}Cu`kCcJNCrY*RHYsCFmb9#eNSR(|mO7 zJ}B$$3PC3M?xLjXZIWM+1Oq7wO@ZC9TKpWvER8fZ4EN+RL;G?#cBwXIG_gCFi8|#&zCy>$*w4&i>AUnDE>Q&;lRj1k_r=IY6x# z_S`!f5>6y-dAWcsu~#~^ppKA=eCd@v<4M%%a|BOwf##76yS7;|8JM;Rt6_L!W8#F$ z%7Tc9%tS{|y*@|~ki`d0UwXlBxw04|cZRUQzM9}3kZd}}>m=0}RXI@E;{W+~;eY&{ zL)+DZN_FF!=EmMaL8)oo*xWET=Xnk19Az?R8I@YjDcWSt4kMjPb?e5V4V`JcQd7OO zxnXhPTUzQ8ZH&qc#fR|eh%6(jJE^NzG&L?NfFaqk0iVt|@TU)DhIjRCSiw!_%PSgJ z*FY0k*-+8EvI154SL~qj0rq zP*y`j)#&~R1gG5TMr9&Mi{z5&RPmk92`TA*{IT=g4sjaH-zDN~aVjB83^?7)r>vK;j-}r>=?PUDw6|A{8Bs~_t+=o<6g3nGl)H0fY@WeIq5&An8Cdz zm~m$GhZP(=Q!+XlhYTCSy~WmO8vpUlZ5TBucNtwrMd2l6I$l)Fqz~fC1o|NTMR-Q3 z0vuJo(n;5ID(On5{mN3ho_;5s!e_GVSMH$?au(?`+4d{5@EM!%B0iI2zp@9<`a`;s z%dTL}mf_A(LY>6F86K6QJGY9pT9u+%Qep`^$B43VQ319tfAaRcXXo*S`}Y$^TNZr{ zCukY>5adh|?r@hRp_c+}rvmt*{rmBh$()+rg2GJ;b?gOGkO$&{Rk4mpaTew7A|R za3`HRe|*`pEN|VzEv{*EyJy7Bi&(MyF?Q~=NjK?J`u+LNvLt~Nf`JAV7p@lYC4K#t z_R2x*)+&uUJiMid-THHO>%=A2b-P+z)4RH6#PvwGrivDzt!4QuK|6**yQ%0=1u(a<^0i7RLYd5%#~|108VP*AeCg?vu-;mU>nQ{onU z<`HoVEs#F*wf)L7;wseo!?$R<{mL={Re_i@KW6zfDpo9LMw(m(3EtT%*EMvW8t-wwL5((6S z1U5~tur{YSaBAFgXhB1yI>Dvm!jZTonaj2|8Va|pNn2o?zvUsIDQ`g5K8Hvt2X9D- zA{-W6ZQF>=cXyo{9hlDmitewlq{F!`xw>42aSGn>QnP zUQ900wYR}Rw1e$N?gdnc`q8KTcF z51~_*zJW-@xZ?{*e4Pb8=HdT%@Imok4?c)d0wbxMZkOxO%dJS|SL*c0$Z~Yi^Br18 zHd+b%mF}8;*O?_SqooL)K{?&;kKg@-_k92Oub*(HPrNKneS!s){m6Y!a_6^i7hm47 zgY!l`@z499RP=>$U*NgH{XZ$!;kgWr8v~obH6+5(k2UIn-$?J^#CPcNWEpja!bGfd zH<~7UVq4gOG2y{7OqZz`0}kO7$WWbbJc5}hWn#yaH_@e>=FyYi!%4vxF~ zxeH;oZDDT^ucvu+Sh#znyPLT-V#M5JHC%|l0a`t|G-p@}FtP}H1N`&Gy*oacMlEO0 z65UNPB!M+r0=f!|8_{1i&o*@J1tj#A#BzarA= z=I^f6x++l}g1Wf!9R^=twF_Har~{Jy{|;~&QW3U1`<}x~f{uj1xP)?ei`B+EL#lG6 z-yb*qgb&y&sr4oEx2nXBpvds&-08MR)M+-`euH=Q36D~~8%zJ`_U(O769xawZ#VPd zs)+fLX?5)1lIJ4!EB+(EFe&^L{mQ2#k5CA_C{8H!oi2#WQW0Lu8S=U$?3j9Ogkp-m9KwFrWLYK*vUvv>vAypIOTAqk7#_>FqT&!@X=Bc^X36~lDT;{KocgE&*6 z3KJ}U=$?^LPzhDuI8bOQE&GsJ<&siHrUx)L*UN zd}c0bUiSI+dg|)@f2D(M5u~R@G(+Q<*chkCg zYP4DpPrk#$0mH_&_eh>#*L0*$4G2W($iww-r*)&9)&coeYJ6{?VsoqijRk)6UU6jv zX)c>ztJ8!u6pBBe!;zyNtG?)V&_5~O;P2bM{o;-t;$Rk2O1c~Q{qDw{?~Y@mw1QWy zkVeUHbum`=z@Q*hmJyUnABz8YXBU8W?8guzm2?6|qmqu#R+8L0k#=7_PLVrXU%q&7 z-{=OkcVFTAapl}EJNujb)^QY#MA+H1LB?ow|9Fmh7M;XL{wJ zv6&PdIJuGk*u4JbPH)>y7vY&#Ucn$0{{2_^5zx( zgm3}$27Q=}Ilwfj1f)6$sdOEyq(D98XX8J;{3)~5aA5eiaaLI09xtm-?d~3c&RBX9 zzzQJ9M_J^3(Gw8 z;#4ek>|n^Oi4k4 zj#CGR#-;6pS##gerrrWUr5U=RrD;*YX${Ppu;|QCf!RLc5!psk)VFaZOqOt3-<3;f z?TWg}#x>QH4C<;Ok-3CK$=ENX1$ETuYRXL@V%ZF6TZ&vK74a|w41olc*2(wqh z^9F1YB2`QNRk(;QL~h5ty}U@61C&mty)rh%S_|9(iqdnu)X+%^aO%NXnTzrs-?96< zQys=SWRJgZRB3xz2roFS{Gd;?b-K3y@a{ST`der{3;`w&2Y#eV2rjrT{+G|`cytP# z*0_6dDTk(ibBm%@eDdh@40@dAEI2Vg$~i2^gQv!nbT8WuOJkl9d*=qB^|A0OIDtDj zYXbJpB`Q8Pl5)PTVH!Eu0$_2-N;>&@aCwRcg@VqX-Ghxxoc)dPGjO#oA~~}_lw{5k@$b_rWB{OW_Q2&WyH90sDPo57v*%WxE{h{}H zTD2jbo_v5_s!5LQdVNxI7NsM$zdCmE+mDXmxM2^zINHI} zK|E?nL4=xXqos-B_arDO1DeW<{nv$ug>AfvTu_wL&*rRDzGj zComsXSO0Z%J~_slXu4t*-6VZ>2m1^@`?Ta$S&mPH2~Xk^nXn+YN}pI`zuPwHZuHjO zj^fF`2)pswY{fRZUivKLj`U;*q1#W!`enmo-3Ws{0$yTZ8zU78*W^INN?aAPjAv0| z@D8k3N-W2$CZ;h1POXFwWdKbr_x5EjVdl?Hjpb-t)v{!&2}m8B6G;Xj3 z^(MWY&UlA76EgYWrx(U#w!Ly-C5rGjY&bc|bMo_RD=(w&{^T^b_%TDU1%sw8NS_g8 z33J2I_wMHf7N>%j^aumeQ)U56iT@0j2~AokN8!wGzZ0Fm6E72;@XQ~Bh4*g6E1!gB zwp08~FiElxcqkT2)??x-C)(ivcr3~l0don0W+}={E*Ii-G6_s1m=H;|@Po`Ze>U^~ zG53X;X(rcayWmIni1l9Y#9X{6e0lL=-|^7Cr*_g0=s#9qTNLZ)P|o&OuPG$ zUSD)S1>*-A4IG5kH}|k=H9CY{)oQ7s6YN}RAY%zif8+dzqFr4uEK%xl#WwTB#C+XD z3yzt|IP;JEYsd!qukGxs^uL!cUuLqT1Rk|FaDF^d8Ln6{oN0P3DJcO4T}RcRK~XYJ zL;^44m~|~^Uf#H7 zT4kJybFG`baZf|X<5QwZ04Hd|D{~WC>INk^DtWi4oQlXn6Ut(gHJoGH<~O<<*G#F5 zRq?KMT^n~dv_C#MM!ZM`jj6N18#OD6eS8Q<^QWzO5Fg*Mc5)`+q>sl> z9hOaqF}JQc%FoGnI!M-J(1a3&PdU>t`JAj;J+-d&YSul*FnnbDkMab(+tW8WL3= z$G_XPA2B*d^`}Zzcu^cqYn? zeM(8Oq=3hHSP~@@jUf*)2xQ~^pMU9cPwXwupOKz4F1vhQl2`1sLD`d%(x&FstXk}z zFs~?oMw(?}R>i^;uei>F?1@Qf)AA~p)BV>H(n(a1e?(=%)wryPfWRnHDw?k*WRU1U z|A>mjfs18K_pMV*;A8mxc*lCj1zH_pcOobW5di|hp^~k!TiPx(4ps=6ohd|s#~DWHBhxCpH3ILmzJ zcj6Kgq4-PQNkK(YjRLaBSf&bzq}4HfzXx%xwgvwQ6Sci;SkByXL5D(*3o z+}HczhZwsHp7RG^_m}73A$Zi^JqIbjD#4hlBFxO-oNQ0Va4x}p57L!?g!Q#^R(8ti zk=M_QXSTM#8vmGhrs>={>^1CLj&)W%7@6#*v^fgf-a%V2XA9C|uo0lz(NuD~zlW0@! z!NX4v9`hWY=BhZ(UB~l>!*e;CPnUMf`6%3!(2^vrl0A~S)PnItO3HUskTc~w%K3B{ zK2NeAoRC-gClU>D0JR5JG2!kVmkC5;$O zIrQtNl|G0mfM76|GRl5SwjI9-Xf|i)7ha%zCoiR^wgX@plu{-r4bR*b_z%()DE!-J zE?lW%6a;2(Afv@^Uoouxa!viN8f#8(e%m6K(_yx5BOisltQYu6>s}s-xT4234?og2 zEaB`~N#VznF(UpdDw4WNwFB{FZ{%&dDLV9cE)W&6kOYii;K>kz1E*c`A27qB^ab6yL&u4Pev543P(RWU8|mgQFF=f^r#uu!5+Dlv42;KKA}It*FCC^k zmnY&tUx*T1H6AHfh~Uqp+0j-+B19++Im}9&O;7Y#wH*wqJzRGfL4B#-`%t z7Gs}FgncS(KV=#Rg8Hsqy?ZW+cK*s#lRH=Z_H3fuEj1~(XY&ti#iF|1durCy?5as0 zk*{N0;Vb_4YM5bvyjeSeryEd5kRBz>TSP(Et+7p^VkxSjq6q0nZxwGECeUMu+$O z*cl-HtPQI-CzQu(PP6^$c%Gg!+8zvhop>|fn$bPV&0wyH93i!(hX?ITk{9_7$#l`1 zi{fsM<+pV|(IN&*p0)mvMAVz(CC?hIS=$C~#`k}(d=Y1i+1Lq+@rneiZ)T!(%!n3U z2PrJIsyf1BbR7ZCu)E!~s8K3y4|{=$$^5FUgH)&lxrKHp`BFo1{Gb%FN#O|IJt={? z6zwXx?0HqO4P-hbeDq9-kbBiIBm(zAud6jirL14LxukXTq!cP%pe9!_8b5F}^EWYZEoOEg@N5Bh%5hcKP((>=`5q~8AjskEu^V9W!m)V#O3VS z=Dz1cl2OwSPDuwHA)_81<`b6c2lOKj`opf_(l&_1@qh!&f zmc_MDHyU;$2kD-QoIpt2`5~uxRaPe0+;@tO*_?tV? z14zMsaTjBz#rzUIExNB#{MDT}_3enB5yO8cy(iPfU)+iFjkuT@F$ZI&N2`4BS@A#V zV=~p_tLrCn&8V^xlWF_+L;U3EAzF1#$b{NqBlgBMXA+W7gl5wm?-cWjWkGbgMim`4 zUs$Cy@aUuIm5e=FD88c7EFCjq7LQ+}htHnmB*a+<)#;p+@p*&l_zlFrvs3)$kBcPp zbbI@0G>mI+R}SgC+4;vG^tT%~ZVYRuxqtk@dv$hp_Wj(~N4jp@5Lfo~T_^L1xU8y$ z)HiTZi6mo2F`=P}QEr5YaTGORymM01OfH_h6VLyP!`~v}&7qVa&?NBAoR>>zLQ@Hv zd5`QNbpD9xqvqk)J00nOw>9UqXP_lQ6KJN@_L1tmdM5ae9P@zTRn{(PbqF~fxn-?CWXK0QoEj`&Bm+k-7 z>4j*WL6>mv_9gTs(61~xzQ#p$y<2{_v zE1jZ@0BWI~q_{RH;Vlv*`8VFZq)W&aLl9YKq!36IzaI|8H~+ z#?BB04u?Gf9Dthm8pJ1yOM(j_qn3@CVW1g_OEk`iJcTZG)z;lJX862Rm8-69J$rM> zo#%riqmq4~PjMlU(J1Np`JQ2$fSeq1@6bKNH#Z=mY@>MdXkc*?p$TPScmw=a98?^l z7~20&?kv9x2v>M9+yVl^NrKiPe8C-Fj5)BhSdq);74D+`3-rS8Vrx zmj+cVX!|y_C@q9!54Xfs#uAs{_<)EZ%gPBI{lv5duTJA=#gZpRR`2-bkvc>(P}}8a z?i{}pno$U%F9pz19nGGed3wE&p0FSgaGC)JjJyXDPnTUVjQJ1D0rp7>PQN4z*g{K! zfk}AC3-&i|4U&rY){Q$kTi~15?x`-_&{ZUUrwp~4VrtTq$`GqLrZ$bV@bya`D9c~f zkq@-W=}DsxP7%N6y_1Zl{1`#-N(ze@6irHpFReFHD8!vDi)!)4SkavsmF7o+tZ|W< zfg$;*+&ZTs1pUiOW>n4DT0}|Vx+0x<_E3-lXFc*xW=3 zq>Znm79q@MW+ziNYesF-3Yi8@3|kjEC&PYJNYRzU^c|DvUmZCre)^6om1gxPJ>!qh;*e}qVNI`#R|>xQ$?-$b zIF;6Y{e_2H%id{=PmD%7%LCJCUJ4tSVt z2E2TdPGtcPL}l}rO?^{cX!md8TjIBL@oDku8S>$!SLr8WFE;1c&6&6nq6&SW;LmX8 z{1w@ZdIJ>-|9}A3q)45{_4dJ&sn=8DVKN6ml!%=*Jr`)Qz4~cRr8wa+Y17Sbw2v; zW)hR(PlEC-rsND(4@0RdDlxx~V{j4Pu`E0fyns7Vu&l08-2 z5_8|WQ1Y&^sZgWM%}S|`SBqCPE6;UJeSWH1D4E)m{{F>_Z<6l7>NZPOA&IL^pgl)M zQd;p~S55Dv*2o7L^&lsoi{jT{&$Ne?J(Qgh5DfN3NHua%r^Krp#It6a%&)mQi_0s% zbb#$|EKbOW;3n?SfAmNETo#z%5nvpIoh^BRW#)m(!4fORX@L#{CJ3&`y%nslT5w>- zg!|jVbU{v)3lGegxOG^l_*@uI(32vmB-Te6##6VX_@t6JZ~ENT35S*zgPkpXa`d$c zhnAIav~tp1bl#6!b#|z!?-}#Oix)Y4pUW=pM*#6S zR4-NV+i-8$v*&AdR-+*jmd;U49JCz*VW%$2MTS$|Rb~DIvI^z5KiyvSQp$beRcaX_ zUO~BDI=1hhoQrMl8qV2vzwHYe#;9Qfp0OIwU}s0GLb?X85jX|zjMQZ5&gdRPfY4_V zZ}RvOq9cPvlwuZN*(ClbK0(g^hhHZyCG&~x8QW_BZuQV+YQZfPOAvE)=hs6Y(MsJt zgRFX2-kIom2Z97^(t`?ha<%^frw%-nC^Rj(DDgb|yHr%+-n7n3oV2CDEAi<}K!PU| zcQIkNw>AFZ{Jf3a&BywD`1Ln$tmN1AIdK2N@m+uO`XZjJ0o}0Gd)K~tPDkKKCOyDX zv4?_i*b_K|0Tqa~Ei&D`8 zUvVzV#o``P)-d#dTXq?Gy0j3VUe`^RS1o*NveQDmSNWFZYS(LdY7%5f+%0}+Q;gfclVUA1W`{* zS!`5YHpw+vLOs;($u+YZhb|ie^kDVQ`6Qt*%r`wIG%mzsT+S-q@byg1xQ0@0jKtqRPGes?!)!bgvjz}NY8h*Z`rY8khz#h7}LkkFE} zxLnw4eGQaSQHPXfP7&W9!H);8;V_aocADXVjEd4~X^{H@oMu^|$OWgy!lzH};y(jU zZCO||?n4idm>0xP=orCiPu=oMX0v=~zK8;N0Lmb#!p*aU2*hNZ>Yt;TBqlqcP%(CK8%7vQVoIDvUpml{!t{ z?>sd_t!lgP5Kz)sq`<3@TB^FvGFNmPzLu(4lrsC;i6S_Xyb!#ck?Zi^F zZ9lUsF?sg*yO+W`X%XXTY%LCBEl}g-D5n;PYJiT|VL*6?9Vueq51$r)7GGS#X6-@- zEn%mM{%Ui=QFWpyRmQRV2%qCVN^nQC|FvqUUMo1Lc0j^?jgIc+cv%+ZvAOyg!nCg{g*T1l~{0s?dlY>js zB^-Rf_#t1gyKvWSFN<&be@LpfUH^_0%M8V~SG-O(#0KTQeM*UMB`yrX%Nqb`;|77En2V~y^{#ogBOuqwfKm{F$C^g>$bBs}& z9LQxVo`Fr2E!S?dZKe63G|%6l;hm;WUc5*yvPF{?4o^?&|Aik09Vn9RYD8qD;O)F1 zjM;a$R7QKhE=jYXp2!%N!6RUko*qDjBP5+$lJV3Fb@Pt2$2zL@!ykBe;nGv>TJgFN zS)Elnr7T*bRw>PmrIryvS1K$O+Ly8|wIcNa#7|_QvSMjR#UByn~XeC5#T z;Ts?!-V5{y0I?{6tfkk1Ym4XXp0HqFEiYc?v~fwXIpJ;>$*$eIhh7}Eak6oOLsoTd(61x+&daCV#N%^G z#1QOxWMK`uj{g(e4z3{WjDzF}M-)#f!4uuI8W+jHa!qI8^*fPitTmEdXJUju#J}%L ze|i(Iet=Y`kwnq*kT5xA(+Tp)2=VH0I)2ZdYrA*vyR8Vi*tSZFpv;GUwLf2fM5W|p z?Od{8*|}mgCbNz>vu)FD6@=>t#(?LfZ>klnhSLAg~PiLvO8!l(TRI9W8maw10NSDNmn{y=&rsnl`xmMNq^O_8nn847K0^RIBTO z%6iAVB^hC70G!%nQ^)@vMssjr*4SIvF@HIpe+4FAH9%)jDw`qHIK+!Gfs+CFl#9eRYk zN_MRPCBU!{X5)zrAH{M`HE8V~)f3Xy!b3Y1Wo0uWhc@Xy?3eVOZ0av3dtfd(F|7t)m^Z$up|jBw2*o11VFV=vw^z z>{Lq9XCCNUd}K!Q$H_C>2G2-GqtWgWbrX{)DU)RjqN5n)DvXje!Kt90JMJmwg&+tEAnMOlt`MCwrclEv>>3?Jy}QcYw%I!deU9 zhbvJDYI99);e^r{$UM#?Gfh3}f$?!_UN>R)>FJXW^kl1fhv8daoT90lkRe_%lhfut zm6^O1K_|qfjz`Tbn$R#KJ1%+pxO(q)*WvdZ7#T9-`DI0vmMuLrIduAStB2^@LuYQb z{S06Vi{~qv`@a{W?35P@E#1-6Ll@)-@TnFFwGuUBg_$gu*#ax62Z$H*A<9F)<+A0| z(f{4N=rr*??P5rn*PGgNYLZ4ZY}4@(9+4GUW~DZ=DI;zA)Z2tlzWUnFUrR{qp{3LA zYYwO9iNTy3W37hg-qjykaWr^UE%XZ36&EK~L5RlRZGl>j)rZzUA+jtU8Py_ zTR?a|Z5F#t1kiZrhksl;@4!TpgUYdO>)Soc-k7Ntzl&_JmQ4UkM5QFY=xx<`fglZcP~^Z;vPa)RkQjyJv;v?!V}KaXI6KGB zUzoD$wKk_D@k+ABzqUAa`V?mI%R1};6^=QzUOY<5idvBs4e~>r_IGhFf6_h*R+Sdg z*a;E?-hVBGCfio1cG4MVu-9ONkx35xX8fKW86{pBMNToU4}>$b5^l2B&uwRDvU0%e zuyeWo8~rxk*}e~W3Ntx%dM$QhfX5BY7fb?M03HapFekt$W=>!u_y(PYRVL<`#uU<9 zePOrw{b_Q3_;cGQ=v^ng47iG|+f0AIM0|6Shn7>qY#u^mMs69`yQ}h;LO4heFmaE=s-L3A^F0)o`4XT_9k9 z62;285_^@cAq7vn!HHg71P8&B1CUsM5#gt4|gA=LI7&SFb_8c=0C?B9&Vkx zEDh}A!8mSZ`bcpHAxFZ>>N(?Wl|LBmcPvbBG4sGg>%R3`lrR)cb(!5AOewsNYNKXi}pKjnV z9EJChokkry1qu#@4Yh;{f~1P!5J);te8L>!Nf;HdpL(wGVBb^pe&Tsr{Lc1I;&EF1 zeh8o1bklB+LLIehQMdOO5?~D5aw!Y7B$849y_Z^Or)Tdthjs*VA zZn5XQgpS9DBsyX}Ad%Z(aMmJKoKyF(=8jB{xFz8*c0=$TJes@Acq{EY1D7EP(|@WV zv0~=_E{kX2pk}aT&454?h0(9DYmcTrrkH*FU{B0V05(Il6|d1GD#mux#EHgUTpu62mNhGz0brMvQIQpU(JJ zD0-mW=3M->PVtY7*(NByw^)~?v0h}S@|MR$VqKDchFao`Q+vW!=7ta!rSx4#7=8ZT6=D?a~knUSNqq=jG5h8bpP5r0Lf@=uWTVh#C?UW zaU5~+XR`x8SYY2r#*-l%zz3XIIZ!+p%PP%_o%jKNkF=c_h2U?#`GlDpE1mbr?6t3p zz1}q=X;uO_{~_h$cdQ|FWUH%-qoaEdkA`*5&JGR+gGO3PX&a@wuxv+VIdJ!Svga!w z?-Xxj%rW!-#}NLqvi8+2?6=wFpbw)TbklHh)w;lMfr!FDOtYTtU9bvaF~INbazR!l zrkcq+CM@4`W@3sPoJ4wSF76S(J54@W_}e~%>-d)#;fl*I;lvgsH?<6ig`vk*^gP}O z%p#LYN>Dd4NJ1D&kl1+G{qSQkY2|{|@le2O06}XVkokon-E#LZEdlv+*l(uPFm=qB zO1nE=8t-b@|Jy<_;xzHteWA9OBn^%eKQ(a%uJQ4n6~~5ByOPqdk5W0zlLSWzsbdA`J7VxXdcj5t9Z4MOepvpR}yt3uq zCmPB7IC#6amCtuXtFtH)MyqWHY4y`Yg-Q%AE*hV@u7**o8%=~M1B5HDipvhBk|eq= zjZM}IB}v5EIbeNN0}d82Rkq4@dC#;%{Lo5faC4~sz1j3STG`lT1giVoMCTeM5Mqby zI)>aWgK5K=RzC{yhlB)QFE4jjS7#>&jmDr)sOquLfJsq1i@L3c z{CSLo`-Y9|evItTBK7|mi_DD$A3HZ@JwD(A#nZJ4*o=4=dlsd=(YH@C7Zu}0`F zBDt8cMlAgozVu&qb?q9%9?uW4eJjR0kRc>Z66xGdTP^pHM9g<8VTg-1K}Fnq$Ma#q z$g{^&u}^a773ida8hr?RL4!8lxi_Sjyl)0v493Z~w6SC^e8t6&*n0k+$T2T$tf$=I^@m2*Z2WOAFd+30ezi8M`}F2|`UA-v zH7mNps8vD5GvE;$U@fec z*4;@FVs+7>W-ba6I>E~cyolEEPJ)xd-i+Ap2z_%OULASWHuG=d-!iHFO$=O6G>eCX zf8&`0dW}1t>Y(A7a>Jk(l5zuCgnjrsam>&H!oP2+I15Yz*4Y`LSJs>Q z+RT5|b0qu#JW+N{huNgiR9UfnG$3$Ps$A_#Bs*Oajmz}Sc)(wEAL_HKYbU)jpa6AP z>EX2tfU0VaV|bsT7OT4n^oiF3dIZIB!<#qGA2G@ zG-U69mj&{JbP8F}DvxlZlgEISyAhNw70HL&7$RbTy5Wvv zI67|S{I~DP*E>2-+WT?$+@l>adheU!=oQt0!e_iwdKO0n$ME5$#nbC7T7%XzcXCfU#HC(b^-8AWvlZ+>I;o#s-h?f z@cuY300<^UIHLN!{C?TXiCWp-I$hXZs4xl9CxMG#Nh8}zM!QHDK&TQd6OT^nIlC5^ zJEzHeKb$-F=*$?s57%tAz4fP&mey^rup}>C!SAP48{Y1nFz56pHq`sxoW1WN@hYBX z)g2kE-Jc+YNzkjJ>`M4B87{2+?(E9s3^HIz-wj##TlA~6cDigNw>s}R? zF8q@IPH1dnPu;kC&`X_04O!NjnC=}^n39?YNICNl8liy=;~8QoOrik2&NwN?e^N}0 z)HxgJL{e#cxf?k1jmYZ=yH-L^D$xl%DLEFcvzbnsk{av=WHU!MGu=hULI>x1C5=X@ zhek84y1mSeaFl#Of~(I~%qTNZj)*3th#DeF`0W&WOZ%Ur*B7~h_CW&Ff2}fWYKhI2 z=C|hyC~v6dV;YMhAtM}dZc{OS*?NssKt@QBAWgXc0#~ip1u2+zrp=D;o$(+B>4t_z zK>!2JMSJlQ(C{R`zH9#UXaBzM{JHf~1WOEdQ`ar2>AWL&)&0Z}Ui_BR$0x?-nB3^u zJ$ruMy;}^qNN)Z$gf-&nZW39UD2K2xTEG5Zxf}c=zy`{dAsm4NkBo{!X@~_1$$+%$ zVv?l6!LldcvLk4(3^7m?k}#IX^q}sN~3PjK*YZr$`+S4Mk!Ox#FI-l%-D z+FvOI7m-;1zW@5uui5K|DDZy)sY9f6@u8`wI$vB_O5B{jaB?9rH50S`VQDRorfGZj zT;DBOoe&Ri0C_o(ZyjNEMZR^6lxy7+8Kd=eb)~HMlGIO^DZ!AC|NnEY!AWI>%AiX3 zv7Pbp5;UV988>oGkxI~x-Zgf_>f&eByhnCgMnjTHDR}0jWDK^57w$$RkeNuk-kLuK z#jCldEyMFiKLDso1gQ0$Mpq0f_Vr@t~&rEJ~!OnPYFKSRkD zavEhqtJ32K837O(41fq7ynF80jl*LGz4aE;%YW{_PVeQQlf%03wrYJ93U41DwR3bB zR`qNmNLLbg^}rp;c*EN$a2Y>I-6bpB1Q##jHxgg|tu!zzLa9m$HD>6~Dw|i77OZSW zKgyLkE7i{9tP5(%D7gdu}G&4G;PObEc_a`3_x5!Xa2nh@()nnsOMqoruMN>G5 zX#l*XuBvGg{Nt%p>qv}=#K#ARJG1)w|Hklpkc-W{N1j#d5?j*e-=@Alzi^T>~W;tQ$1xd9bZ5Ox~6VQ|T!!Q3e> zCMqporHr-&rjw>9vT^%8jxYkUrk6MMqGD^x#0nY_L<&;kvk1$4cEK2Cb0+xw4Mz=r zegFj6_X3qqm`*375HG=#0nL|$$!#bv(++8%S+5$hwpH)UIhsu&EK@Q*)-Nqu zm!d41W-S|^NV%4c^}`qELd9xab(k|fO8A5sL%TxK5&8^H4T?=7hA>|*>*$oC&f+0# zob?-8lcqe{p26114{QH2)=3G}J&@Eu!(h!ZIHXVmM@?V$6zjewhsE*!ocjXXL^_k~ z0j>-lVartYb4PC#51}v~lpT?o=hO ztzPoX6wmtz7e1o?;eb@6!I*P`0^@vLgT38RWIn&MtZ@bE1PfyUtIh=ap8vp|)3_?6ia2vL$9UlKR*yttEe*Os@F*|p`JQ@GHuaDWkm!Xh$+U(UXLt$r#Q zTU9@KWKD1aNjC;3WylsE&x04r(o~9xzy?=3AxsTJoGJc{w=+q~>>t^y$m9?E;YOHk zv+T$#`mo9NrkQ?bwso3rdEmqx#DGo#Tf_HviKToE^5tEjb+jM~Y_a;Z4Oey;!@azm zta*8{_4Q6pLRqR)M=)yMN=b5n@W_E+=KW7+nVGk%P(ml6cD>HN$LwfwNU)NWJvo+X z3SoW>DbqvF50DNma7$gdsU7x*S#* zo7kL{-V&0FytjDIgRMJ;34GU)f7E6@xUr4WDGybq1SEn9gk%@^hqkeL?*S=+&u4d{ z{$t?;uE9%O+hB_Z&lA2RwWemwS0?I&qa?=XhGJ?p;$8E1)bv);aJ{05M**}KoCha{r zB(m+r?PzQ@?Ed4O?$P6ip{r_2^Mr)F$Vp>>?HM#}o|y*NF4-o**@_?;OV&D`h4glwvrbF5A8EN}*2PW;->g;(m;e$_PhdlJ=JmfdVY zy0_GRxaHW|nfEn%5AxW;)$HjVZ>%o$4KRx~nHsT~9aaVVaH`5<3fu16!gUXV)3p(&ev zE+cHQ;vuJ2n)Z%35^jQP_R0LVmM%sajSoijy~Cct=F*J3*;}h-Uokcta`$W`g@Vl| zn9YYRT8#KB=HtW_Vm`b1$4KWA@$7Z7KI@M z>I9fZUmqt*^iC3x9GfBm)<@bKTS!qlD1OX_WUZ?)L3G`_7|}m@Lp>VJsw11r#IMan zG3WV(={mu1Nc^#xIsY!w`uoRPtTS6tk?#`W8n?XJnXuQk9*BDw{*VI$*as6(x8Cl+ zQ)2!Aivoy_t@@vMhoAn8bXc(T?~x7&)&4EY0kvha#?Q~WjdGCJ#7kO}1cljZ@biuE z25a#RM|CJ)?lwZ&Gd+$~Qv(Z@b^*t4fQ?G04-k1Il-$0Gm4ms$`rOb$@pvH7#5Gyf z>cHH{mm~o7?uv(c%Xxmvqb(=ISu2Sy224}!mmMMA3?crBwaY6xp=te-_ud7H3=x}ZXG`_o!U+VckPpz7?TR%(MeK~KC@}u@`^x21n&Vo`C>kDJ_yk1jtV8` z;6UI@7U1E77{ke6kWJ}q5DOri_<7sl209bhgLZXyYT zBjw=RZ!>-4JYR!4;NHmD&>j+cQE8V|XJA5!Rq<1)&bz6jOOiLiZRp9@tgy}*XROMZ zThs&kQ~bfd#oe!*0r_rFxE^seXcdkOPdopM#E-pLg@;tTDlcUfn-Ef_Up+5O+g??+ zaTIrimS<*u*w-a{#))T z<$UQ*uq#v`98TFwnSv_aODcxJZWU@YqBt^r@t@Dn-*=5c#s{U|umSR21r*?s5E!AUIR028`sxqQ z_8zoT*-0vAF9)le!QkiMpzOt@P;o|zGx(r@zEqFIJ_;k-B;~D5Li#w{ zBzoevXBQv1M6!wdC?Oy%Dm>pB7T~8D%Lk@K8T0eQ0s@4$FVIE3mwpd1mc*mhTNuy9 zdifbk6G(Wdz$MsY^Csana9n%bAD|aVDU^}1`#bIae5?oKdW^N>c<`oS;?-f|J5q>4 zg0~8<*{)J=@h!&MSe^}4{La5|2YtI3kcDNZZ?BL4O5Y$iJAIck`X*(W*`q#t;Ip%+ zV|o{T=bs=iu)DL@QG#XUb&1GH)M&YPX!+uHT+TCX= z6yz@D0ApuiIIHNT7li3MD$DO1OOMg2^vu!0&r!GhRDlnOjne5uCQK|w4t%ii3>`WS zh4Z4v4B@ilNc_;Lg!k}6W;kN`D!&X%WSp14s=+Lyt5F&I1nU1@M78Ssab*I%ACsEmzz+EQg~cI6&=KPqFhZ?VeQ?8+>B#wNUgD>?Qn zd+@A3q${j4_Wj^XE$AMYhq{Ilh~H{`JY2YraD~8H2cEUy|JY3<1R2Up!2#+jS#NhB z_8F=Yb`p02aPLFnZ@apW_7r&?gb7$UI<dx-kIIUJq&@m5kY zHJ5|m=)ho0&WibCvrtCH-xr=CNgEYBq5H7960XPmoSdP-0X~NMN2sg9N~%Bs1ufmB zC!j;Nq$qqLJ{L(mrW{_;;H-4;C|@;RJQYD=@BeOFzO!Ih{i*mJb9`xypLhnOHX+!Q}gJ*qB=f3v6hvtUw{0)Nl_i=Us#JY=el*r-rW?Pv3LO*{c%m3o*5jS zzO;8zKF1B+3OpAF+T)~ga?q@j+5Q^6IM&c80lg~;OYyW9qk;7ZycN8!G9mtD;BJf# zk&(KRWWOn%rn@-j!Hb_czo7g1rByzTywl)i2hR6&v44nXc{Ox{v6W-DO|d$1YVY*1 zmE#|nV*Oe?`2fkMdwn}UKe-;W7<|uj=cf41{QSf^X-dz1u-rd$>B32QpwF#`8~rku zEuMl|wA_D`Np{o>a8RL zmC=$8a@K6Q+*7rsD_OCqP8^RDKbR}7r~pW=(+ z1kR~nye3}y=0)yZu~t-a`)(8nC)oTxL!Y_^+|$G~c=(~Ik3mkYmnVn0NnS#ZaX2Y) zQ=T59D(S3Z-T%Mp-aEd^Dv29^&eLuhDWnq;NKXi)11W?Q0s*O^385o3fP_e>f(<)1 zPy|_l&@31Q8|Y%$U9qfKR^4Vpch>^wuD!v1?)#lN&-3Iaz^L%CUCYS!9-Cj#RI+gMq?Ewm1^eGwwfL2(3(D>oQMYnx86M6{t1HPE zR4SH6Xdx%97ZTBYWYl#J%~<`?ZAk4@^?#!fiKsr~ng{33993~`F&3a~C zQ7>}v(t}nRSwoAm;ozlZ8jpy{3wo;}Z~jDWK!GR_OWD~&w`|pR_R=-2ANc>cwO!;Y zuRz-a4nfG|+07+4As6?jXT9QmOU*?XjQc2GnS1n|Tsd6;i%_Z*RK=B)mKLmw>C-1R zKd5hUaYlkL&5ZRC39{}R8#aZkPzpL|=lP+W-mzzF6>!4t7KFna;`|d04ex~hrzdpJ zZJ5%~XXMfmy#o<*CyyN5XUfzmxuFBrtQoN8&{Dodx%2HSD%W4BrXppPDFVouQFmo| zU(QtD%ko}^+T?$!o&Xcsm-$!J6!vXdP>c3AK=luSKWbBV;Fjr{w|7iJ1Gcl{oMK-$ zyCsz2*3I(_?*$)DscnpuHpykT*1&a4S|`mN+RRkvWzgq8Vf~KK zH{$bxAL;Youxb+d^ZWq%?1dHWs<-0%xxe7EQj71e=kFJW(dT5;UXIZB;QK2+r_aN1 z#+qb2k5HrA&-8f^WKhlDFZqc+qozZjV7zRalrzWYPhsOXGd**L;j>S%>Vyvzn#ynJ zd*?IY`7?H2x`SDU&$#N2@2|Ax@aGKrEKFtZ2dImAQs}Cp46xP6~#KkB=% zW@509XIDjqWn^?oO$|)I6NWkrKxE*0Zu7v3;{UQLwA-M9H=OMVw2}hzDZkm@PGYmQ zhC<1@yp#;F@oMYeSD_=+w}F?va?5qEjI}oGi+v+@Kw8DH%pr5*%aikl_AvwVC*;oT z(J*rBnjyNU>8Z=AR;eewvFb^o-tf{QP1`WnT47%EZntD1>K1sz-iYa*JG^&kS&R^m z1mmvp`iqx8GE?mdn^g)q%z>`|&yEx4UNO@riO^}=-oAGIp+#B2YUuRsZ(p(tkblrC09Wt$_QxCa=1^X5=n3B08_KZz3XLkW!vKAN!~>3@9L-CrYz&& zfnUz(uc@%b8l?5PUI?An5ijN;3m=*s<7v(bhM>@`kh=P!10A=)#qGz`3m5vyS_Aaw z>h{m!KkDgP$gs5sTjpLnyt`&Z3~kxFXxZlacU>3la$R~-aj#K$?Pg{vp2Z;fFC!zBGpy}fe-*|5TJJ&tBeBOqO5;P-x_{zNtmTVf^U1Y*#OB!4;qIpONE|p}~ zl?<9SDC3M_{GzBq(eqx}J5$#d?scj&;r%gU!;J0|oRZ;~2v(dCC)7GGC~)^g#)SL|E31Ai*Ry=cEor+rAmlM!uuWrL+O|gz*ihE{g+T2~got)S3+%B{spS$asWjsYy zAAb3e4f*g@RF$TP^_vEbslpt&H^Jc!#{}m&B#>^oD~UW6m${FxUHTYn%OlOTEiLB zc-Q-F^qiOW&eGLO?tNl>$&}yiy#%dEc0Su2od>N!pBpT~jg>9%2OX`M39X^dwl3XL z)Ai71?)~amj!5Lm|CgGm13s$z$vS)XLss>c`PU8WCRKgmrFV=^a8!Ntvf3`J7^Std zYcj2wMwi!q$^cf%Up%WCny`*EVN4-3VeU$@9eKVl z(6X*x=|#39vTIi*9;-7=VcQ`$qmETzhk(ZD*h#s- zdgSviN+a~k<$eX)sGknRZvA2B=xucaiGu8!NN*nWmVWMVHj5`iRR1Z^)ZD2Fr2a+!jPi*IHxrHTp2jHJzjo8jY&19=gZY z8+6e?PL9I1WRaev#YWO{EX13R)R#X~%Y|EJdXOB{*PP8mrsfrIYxVR&dEuu#(aDKn z$>BLYt9zE!_f6^Y_n?fDw1||5F6l${vR^WccSF@)!)EtCyDq*cGeAFK=(?wR=Ar`a z;*{bf)NB1EAcQ=ohp;U+7>~h2ibkea;ANB3`a<~j)rfP0OG?61*2f1GNp(SeCcSDx z@9x60O$V3xRGJqfXy+5xSaw%D26i3y=n=6n|5vot-nz0**M>Bfi-eR>9=%_B-U#eX z`CZi<>mOE4W^J}^G~GXW!q%yVR^EWln9;owVRHtiOt^dH$dG>rUpljEdeo#pcNHmh zmyNw>>4dcVqbu?v$$^^w-8_I-N*f6>leb z=O(qEgg^J-(+}dRi*FwO(cQ%@`!W|*#UT%igK&=-6=~u>UBYnqj@!<5;Vt4KoLh8! z(}pt&cKkz*O%XpAL~i5WOz*5r_0~-9flT#7`uA~Yt@k@k92=48y(&XX+GU;Eb>hSc z@t==A63?ADam^Vtsq&V5@PUfUa3G9M%P{}9n8tX@XoQ7_i*-;DV*5& z-gIb!Af6xdy^MZT&+)F5hm{_jmxrb~efprC8M`v5fJLb0`|Zwor>f_T`Z3@Qa3(lfW){;o&<$|*WS$X-k5ceisZGg zU$s8}qnOn5%&Zxj97u9W0VJvRUO#7ET(uIRT~NSB2<=Aq zhFzE`U+hI>6rn(NN+eEKeek=x;iMx)Nv(>&;eBFCa&jV=kfb&z2C(0RI3Hm?rRVJ= zs$+`n!&bE5qgvr-FVKWE_?pR8|pS$&n%RwRPXgvVe|h0g(&~iQ{gXDFP0?i1YMF6 zV}h^^D9sV7?3SKW$n90J*3p-S-DOCaB0+4&lm$^MrA6_7c0~k^k92V(QWDh^Gd(+%?uaUVWtR1awp_ym*oN%VUp$hRx7vycUOl zqmv?>DriAyx1u%+Dat(V@J0$o!XiDsnVzgt|B`*?ifk>D#*t_Kr%!voC+*dIfA@W< zzF-Un#%M&zxJ?y=`johM969T{?2n&^D9wIo1Q^}Bh5fNXP=9Qm;tOfAYxhAtkY21SrpiWMHER@91qh~Tww zIc;#ev(k3S|=!fTsxF6B~%1(E%wLtS5JXE;yx5U7t@H{foqXJ)NxOU z3)LvP09Tj8x|RxLs|c*Po&MWgxIJQ@mVoYg zM|_VSWY}V?WH4u1iq^>Cp~ zrKONz$QHOTp|C4IxiB7Ohc827B3tl)KPJ<7$sMoJR|hE4MY07?I1=E@b|FiBEa)6<7}Q{4<_Bng}rcjN>F|MpjyOG3DvtZR68#4bO2+@1B}a4 z!j!=@(RiS#o@purjH`)E6Z2it)Tm6gHL)6C?R+l>t|3g-WSjPTB~?i<7Q37oc7|#Z z^%AQ4g`q$@7ACmR126|VU}C!Dc#R68D%Si0fE{-EejFZiCDS#H>1q*4azr$TcIoml z6k{VOQ)w)cxN-s1j*Gnq^PiBl^d6d&uh?#C;gI5fhca4|KN=4KitLH0G!^-Xda@>e zvSDmLq_>NyRb;Vc!h7#RjPu9jHGZ%$d6ZVAQPN%oT>Y{r)qP$mP>tI*Ag` zq|>LZq>9+0L4CUQz||Vh_LwNKXiD9pkyv_4kkz2QtB*kxQ>Kgo#j#0cWnqIZ3Tw_R z2yQMPFd!aJz!+g%1ddFre3^&BZRD|?dyhCjhXafmnkLoz_QNChI7)Z&JF%1)MCCWi z`#MjmFK+%V7-4knU)ZNEH_-ajn6h!-=w*jygbWW1idC&IB1KEWZ8;N$=9xxZQCiXL zp`iofhD~p(nta#Ax;}gR;fA}nrHm{$)u@E%tjNfeQCXP%;KkpGPkN8&ql)B;UTVWN zQ>yQgWMN0iG(;IIYo9ey)e2gOB z40~(5ry3d{eKYbztI3*W;Z46Kj-F8dKa_P`TI?2)%z7fSaB%I~jn24sC|^iiIl#r6 zChaJ|rJZtdVeQ4x+7tBthHEz@Ch}M$RlCL8VhlkQ0MpfeRB5MJdnHx7m9t7XLjhN- z10`_{adGWLZ73& zl-!fr07ZKeR(elb>AV9vE8X^W^Ic4>%6Cka9*vbgy(1=C>2h~#RVK@QvH+W#Tuo^GA(H&LQ;bf9BP(5HT5aoqO z;ALgiw9N)DK?=>}8YEkR$dAG?`rkQAtUoRYY2Rgg!?HwQiqh48XL*-pX%n-(;k{m< z)dSnnVfCCkbxMDc%AUNB{Oez8{pr)cY&_qJz{y3vwT92Sa*%;cuZrv%85vw$9@)w6R?8J2h-4&6KvHyzfb*Ozkgs;#^!$&qkS^DV867=Ppc>A%{%XZC93l2Yfw1=8y z1auoZVDR(-J#iWqo;R*?%=P`&_qlnOz_bP!T698GHW`KN$z&9w)cRYGF3w(d{NBkp z^|w%$aW&KTGM#oU~eM;!foasXsB0 zxvSNriK(V(%N*;Ym_4XMNZef?Q+%h5OtvLCygqg)KggAq=ePcSUnAQ-iPt~gPJ>SG zz~k^(9W{9I%3c2w6GhPyQ!6S)c+JxKpJD5v#Kh~LFwyR{l&z9h3tX;FOI&12B(B}K zC(O2_ub(Y(aADO-T)Ra#)@f}haJjZ*iuY%{JW7!);}Y+PR_k^p^l{PG5aNh$EBm;} z0?0W&?qE|#iNVTtnq?)hZFSa>jNT~Xg|PwsI}npo#HfXVt1F#ZdJz|AdsYK#gw54S zVl|-DB=`gw4larqr+A71B_qatzU$bYh^~NgtsF+w!i_HgC4JX@zH)TEg+(DKwC~Ab zpPz7*vTc&MntaP8F7j|)+XN3+{nEJC#nt4?_I*XT2 zD*Sl-U0^4j$*~|xlvtX#$ygB0%|xR!2h@Y8|296s98mv3P`*UpCp3#_1eWg37Hw4+ zxAE8r?`RJZv#ocaXId;O-;Ve`v-M2Ttt@>tM?Z+(7%s$m&W3|NzT?1=+>d+uV$^&d zwMA)?@pu5%_*6$$k${@&xe-ufY^bH;Mxu?PmSql9G|J^rM2-0~F3^^3-HS0*O2{7Z z-R-P_2P{+Vk2gMTY~mf?Nrn^juxL9R?Kzs)c+{ayyrX`Hx)11gga=eOqY`a*@UF_V z0qO?cRUtFzjswN>WDNmrkj-v!srOxy8GfDb(87O@_1-6KAMz(;XJr1QMNN=03qt5I z%f`x)RW(~IimW_Jix>~=ppY_bZsZZ|^%-|CUl{PSqVTkw-$9$a=4mTrB`D*LoAKO)MdaYrnYK&Jj^sYA_*Vn?-cJZTMVl`<34V z8|5h|Xyt{)aL^F_B;)JHqqe}qA+D-uS>j3@k&dFZvcxA!DCKcUTWkl3Ly{+1;*1ND zES8eY#D$`E4fpN9iV4-nLgI`KE8#;4hnFt^=j?OFbbB_PTW*SQ(LK7wG-QADehb`P90R;3~8I zWn^<~fJ@s><$XCzii9V*w1Hz0%zNS(c%Q!WkLP0Z|?WIiLoam%{RO^HPk7ZI8s%qN5lb>oA;2I z$o5D~O+(rCQ2yr=hjvL5(Ry^IsRuhG%?RgK0iEIyHBHMGNq&k3qcGHWbRproFV5(CAK}vH+GIR6X>oZbZmQwwr1XiCBFS)7HFePUJUSE?7|Us z@3SqF`1UKuK^@sLN@8*13_i1}jJAGb)F8s58Y|q1NJ9RFUL{k4u+iANLgxTCZF;Um!Py|WkgffN1=gT;rOOEVMi59mvJBkcDQH~@M zfC3waa`e!j9YR`a zJsy=ciyi%G#%epwHgSrCQodp>V<)$y!yQp<^}EBj z?1U)<`dY!`H7b9#^$XRPGN2$m^nD1rG^Tza!?lQKc|IO_>^odcx+Hjf*@UiEF3w zvZN~txSaiuaOvIH1`{sWU~C@1L@=($vjy^*@2LT%t0e8}=b{EsCZ)_>`2^uj<@xMA z<2(jZY*8CIC!)=O&A-KwVyJJoah>N%;JU_!N)%Vph{dQ{=EX?fe|KNA-p@D-s2e4e z$n~8isC+cHyV;rH)$)e8nW0_;UV79j>l@!}NaO;4cits4GsHK5H`S`J?SQ@w97r&e zfQ4cloIof~<1`;*(HW?#F$6W$52{5hl~82sJ3#eyp|EdX!k8jE!;CcJ?eSp2S9Pb+ z5<0`w7~i=xHG*47uvxlBE)x&Ox+iI0>B8+1;mQhzBhl0oqg=Qv%|j9noT^s7_I(dN z_qK|uyFdPpI34@oYB2>>D?WZ#L+5$a*gUz7XWQZQn4t>%pjyNr3FUu!%uqot6nIiX zJkcp1;^+>;#PED+Jmm8y(QFM)lx=WkfvN^Y{6Lrl{B)9(Zv~IXF|s2Ag3{t#D4Z4b zqVYKDa$yh)sb9G;6gT#em?%Pa))`^K4N*wJ9D<`G#;?Bcm_JJDmFIX&J~y7?tdGu6En>NZA_x6~P&<@`5{hDiPEbUb9C06d z786}6K`retBF|DnH7YmoEH$++z?Qq*r^?e7rXNp<@j7{c$7HX<TV3;mBUL#7-CHH#(%;mo9kI8g>$#k{A%JHfTMP%oU$g4cZ_@lj2ko%pV z2u|=x5IbOG$!Rqem{IO(YK2F~m}vb@V}9ZM<~HoR?L7-pvGaz>^P2!|U05jIV3Kc- za?PDQ1EEUL_SOaGJGD7aI|H=|hc;y<9ol4fjY=6!#}-IYp4;WXy7@x;D)q0JM4X}3 zO%_=!lCq%F2vq&CF5i(l!@0-)f-u`y>>KLiTc(02-l(`&(1lZLr?c1|anZzr>q@ z`m;Qvv-XU>l=$h)+L;lwOs97UfFBtobaH=3dCemK1A44g@UMSiIkhAcH&n_cy4DN7 zr8gDvTY{nAzHV*jC>T4;5#L>Yyan7Q2}fh71==;v7|!o0W2ac&5%TZ)V?4IpxmpC- ze(;YncI~5kf1xT?UF>AwMUpB=T12x*R~QA`<8;B zU|TO@?p6ZEU4tRW@KoPIlssgS?jIfH*o85H-o4ak|b@C@M5vR-^I zb73-je7mg|((`3p1cw<7|v~RVeY$DwO+qtFlpYj4(N?&>jsQtqrwDvDl+UmH=R`gc#aOyZo^_}F+Hnr<*oP#g<9cQU>eEB`{RR$@?Vz4+_MSJUN5@J! zWkl~jlviBnIphr#5!=V4c0?=&FG7|6c^^r(K7?Vf$wj3F9Un$6nliL;6xnRVL}oN= z)qhBy=y7m{d88*G?r|(hCyxhO#U8E}34f;}40})H6~Ut(PU{5m1AtuLNxt>X<9)&7 zwTJ;c9!I6_I(ZYTshZcOgxZZ#L9&8cEFxUj3Q8ziH3S76t0NuLq5$Kp8d*nURw1sF z6*&+47Gl;oi3!EI!x7&=$Btc&n7Wr)gPd9R9OA>nR)55jp^xj~@Z691Djm2h0QbFF z3Ao;KXze^~mH8}KCd8>~n>h%7?_>Xd0CWL+k_)(CgfrCBHnwNv?D{2k!1jahd+lQLD7f$k z=K3SPVBaYv99=AAMdWVQUex+Zej)p562E|pY9`*~&l&PFB!j8|e2;7{;6DTf8h=L2 zPw@DRQxtsPn?K_$lF$51I)$^p$H^GJ&vQPTAM@t|=d=C$LjKHW5yne^FLJ-v86Jxb z_%>ra%GR2ct;J`~#_(t4P(a@!_Cwz+32sS%#~T4|Cx(HU&YV$0VbWk|CTxX zut@M(q=hxuk?wKil!<(PnmG_q&q*ld3|CC5cn6~$O2N&>5XSX88|oXzrPA5J!w#+y zzVFq8X0|O$Q+fEk&q*)3Vz1cdzzjvV#~J3UfO!>|Oy!ZNpKWQEz?i$zR$?H{XZiN} zt;`)h9YSTNNoP9X-d{n{wfy}G{vLTSJ)b`}@b~t;G#pOd#NTH^&$=4V+q8L}TZc?(?H&t{2@OG5of z9O(gME=Q_-pP4ZxRS~={afz?^R6?yn?b#NGZ*;o!BG+xpT8ax*l*a<@F|GnX#MoNI zBl1+-gy$)946q)2@?yM*>@%{v@?H7BX1pwq);F^05;DBV?$Sj=x)I04YRUcNEq?C* zAUl-cJA=AGprNH!5|}!8La&i`t*JFVi|;!&(+;Q_T<6k%j$yya)wVxslR*q zKr?XO?&mLA{OqkGHIc3-R7|NTzjTz?(05o-T3FJEg}2ODykl;@sxEoy=9*b(QkGd? zl~*YU4iM>tj2m+|_;sXFk0a|XI# zLSv-QLH6P{Pch^IdjJ`?h=TY9bN2!!jg(A75(626Fv82W8Y_Vv@n|YKcoly;c3&!82?Ep=U1~j z^vbpgn!4oZ8c{&)0?IGBtY2cEC6`oVt}=_x_GcQ+)prnc&Ed~e+j;RTYQRoNa5@ru8P%qkR5^mr@xbycD!Z-kP&1OjzZ=EB zBdfcENB>%mE++Bl@JWQY$sXOVIHof$%oR4gbu^zCkjA?wTo1wp@ju6?#W?|;mg#Xg z$6%L~=xl}t`k!Oe+V$WJ<;FvuakZ#VNL)yhUl>=5I17ED>?)u-;iBEMoM+gGHurQ= zX6QK}j7v%RM2#t9d0}UP92!LgusLvti4_bdcVnvaz^)FsT6E6fQ^8(*hMd{$qD(z$Sb(|vLPy9IzQP9O)6=2}k9>q{0XXqL#f3y_i z6lnMj)3DQ5BrAXD7K`JPDtBl#W! zL{h@K;wr-qH$J3&qO~XOQ>}8JTziDeU3&|`5f59$3gt*8$f~->rZWInY2CSB{nKf9nQNy=|yKKPX-) z%s2LbmHE~T^gE*dM9=ViI_=}Wor)4L%##la-(~)^s+Y244SIrTy0fwlLmL0&J>}mL zN<7LkV<>k|>5O|Tcz?`8XNyu_|3BYh!0IDhzvBKQ-FMP|#l0xs76^Gh^9hj~tr$8> z5gbZ`>mYE;l=dNac;DD*SG@^E+FHblXtMuu&Zc8Fo2r8hs$dYg{-fMe@3(6dT1> zNYR0F@hS1Bl6le-131=gjQc98cWG^nhxK-2c_j?=dW}B=jQQvxe(#5E z2Unv?+tGxq5mm~I5S;iPvNa6V1?08t2QIP-I4v3XlLPan10$^v%~N7(#BE8Mrz+sX zx6jkYMe~#vrcr6)c}lqUdCEGsE`TeN{Jp^aMfR0=mx66zIZTC(Abxbb;WQ1geKC1* z$o-K{WkMHINvDv#pwIlhClq>s?+K5{11Bg)G8*#a19IN&_;)lO{f@_*$#U1VYQXJt z)KHP6 z)CAPSIg+ydl7}$rG>i&K#c~x7!MIgC(qAV@NlcB(&nziT&Gna*jSDliah>;8l|tB> zJf$wS{bHt#jndt-oq0002f6NsDDx=$DI36YA~+86n7_|r?^w%G-p7(}VuXz^4_=JB zUX)9qll?eK{h4RlxPVrRruM<6+^$7E%+wyx=R$*_3j+{F{KkeNTxN#nZU>iI zj~heLtkHDo+s=vNcd*LFd~>Fa%~b1iPcSE|fo!8IKVZXtahHp2ta2K=Hmwgj-sduB zmLEqz<_s6x*xY|IHY#Ym-XEKBqlc^j&r=--s~g7H=x}w61IIhYadVrCZCtOvGd4;YxX5$q zyR-v;zw%Hm^kC9U^R1MniJtwEShXwS{x7 zjt|nNA3}}-Iq675Jx6?3I4eKj+DJE^Fv2h!^Ge7Jxo7AG;GC5&^BtaN04IH#1Ny&3 zDb}=3=b)Dpi)okQe1f^b#dbg^j~M%P9~^TyE8oUNc^ipqpYj^-6?zhIxn}}|%fq#! zL>W$U3OR>e22B1rH{%=6Jq{+=kR$NvDF%!JDpq}mE@PrO%ccu9B=I9asUjc9TpME3 zMXQGVr&$uxLQuwjESDNU2Nl&$3!G!qsw`m zo^oi@e{0elB(`yJWJ6Rzm-Ad3V$=4Pi*3B}1Rf!vnG$V-UD~7sXbuwF_-`*z627?UEAP0liu7 zl6^3$yHUi(D;|*>l<@CAP8Ua+jp59_<=V*tUA!bg_-YoxgCVjY_r#cEI*7?~)SR zxc+jNq{|-d8ZK$0Jy*_jyz-;KYe$$V(U#Q#TRQKO65IF^xl0ml?HbN9b^4yLhND*r zYbwbWyIl#uAzSQrZub(YsVrsQC4rwbe1NpLBy0K|h=Hu>cfi2+8|3fCLsGrj(}XXA zky_M1)^M#1E?OOtPZpB=^Dh|tHVGvrU=(;z{ZO64f+DESxN5}&iEF@r!&R&7lDJCF z#YN|LlCA?r5nEfW9Jsjfyqzx6MoHHJ{YutGtv_(N+9>HF%Pn#36A^q{MKBib+!J5zT2g%O}`9hFMumW zV$N?L8;v!LdU(Kd*l<*@o-Fa4MDQ`Hd^@5hNo)u7wQ>z7LQ8l^;>T#;=jKyfk&KvvH6xNMlGUC#C+AIypvn>H zFF>v}1GhyPN{sQHCe1)M^Y+|UXI!;ny2O?8-*D9`+a<27b8%4~O44<}sNkEnYBF%S zHks)%FLmiUps&HX9O%jfuI-LqOS&ptT>C_KQ3WV97h%74Nih;?! zLo?dC#(WCbm7U;BZN`AHJO;UrhF`}O&@3GpzSJI3MTjB?m zp0rzpux6~V@gV|C&?{LpWCYk|l%mEG);%SF&cmN_#s#m?_lGn?;ya*!DK#SqyHAtN z4;h`ZW=L!Y^kGsna0$3wGbAp$MI&+TQ;y49G*m(FR=vyqA=J}{aDi)Cw4PMDY4)Hq z-brhl5zV(85&0>ZYLz&umOvjp17%$p!T>@PFbgK3L z*~Yb5LgB%!cBoiDJ;t~WNhk#w2+$=0bw9dPM6Mf|E7b%~d2P)g@Wjso^&%_9c zOT7!S#T?6!7ji2{{)zhXyJ>$HBW=2#_iZDn(m+2bFQNm3V{G6;okkR@95&%54!!k@ znw{2Q}XR#!~NR zXGO+4?~J%=LU3Ttv?^=Ac$k{qY4-|k-my(9A6r!}jDu&&MA?#2HPebzq1JHk#tu}( zR|Pv%NFvbO^Zz~(cmWoifyB`NAyA2_LN`;7j#Ij3-F;w@T5BfTtsV5ovb`JLu|7B} z9+2JqPSab6-tCx<>TAR47^Lc8!lPQiWTH>H^HifhCT(MuHR3Mov!mh&VbaU~arS<_ z%&JD8L7ndE8+U>x`37Kwg8nu13ZTafk1+8c=eM|(Qdxj>Agi`vD6W^HsD2o5`zASVIe~IXu-jz7N9nBD-L2nUMxRf z7b&>CCr?_u@fa@ljP!1pWc_7^^(UkpDWbIb32h7Gy>*R?6W{UiV z5}}}rFY1w1ql?pC8|{I(8kz2PVTjboNKdN9oXwkLeL88Um?rYQCtl1M8+POY^#Phu zgJp;_ztz&uyohGVPkoDjT+Op_&bDz9hc}0-&{o6{Iy#1j1{#{85+|vjNNj0WcRnIn zBUXZg;>)MSWHHryTI5R*!i%vrZnw1aV&M zKVFb-k4JsmJB~-4QIpYa6ski5-nr^iQEIJy&-%ypUt7n-mbcU|iHlIvr$ik+R4N** z{c1l|w}yk0u(w?GgNjO%s(z65q0!se8unt_ROL&y#*!Ztie4f|M1Zq9%~czWPf?+3 z;-)C!q1^Z=t`Laf&+#}H7>iiPq-qL$ABFGx0j~-BMV|-3KV8G0p|$ut2cJvj8yHc} z_X~}eado&mf8OBxTCe5Li44CG@H27dWhznpedoR)E!ZTvn@4Z#=1ZL2t7Bgfs^q9- zE6zPLT(6iSYX2MiwPGH}RU!nbXy-hXZ|q|_*j>gFr*M+Ei*a$!jP~bfPexZX-y)m7`+2^|NW)bf@50IRiZ1{+&K~!qJzok-UHTN7?@8ug zKO0}QjgN|!UHeRJnKK8{}Fk<7@S!k82{GJ~M4_T__Kzg~7*fr_blb zJjknYI?c4{YvnxX`e-{G$^BQJNph}PSIpAHO zE!3R^aAPE#ofoq6M{aHu__$gt12>jgSBwWG@9;XKA3j9`+-%f8Whk-&qs@3!!u9Jg zF6Mi?Eeq7AlI|Hg>@y+}*%ieZmt}!2A;2ds7-C*Ij~*TI$u&(UV7#W)F-8!w31pin z|I*;DEs~ke@h9qDSsJYzGm1EvFem=xw=wSITr;Y;80tP~JY<%Sh3dxbr0?i^fidN} zZ_~F*+LF2Ng*%7Wt-Go31>oemZ`15`LGNxK@wri4D%OR6Ux+sSD6b1qkwb89PA$;7 zTJ!G=(WYO->rs?ucaX(qYqNhe16_Fll^2NOqzj-~GLP|&G3*>Ok9GNW@OZ0-;@>jn-ah368UMy%Z*t>boQLT7-1V~oP>AHZ zeMj*H!8kjUa=W#~*vcH1nA-HeOAdEo%j0lZnF2{rJ#7ZvmmqD@hn^SiL2HYrFXJnh zaK>SV)8kR>(!MH`W{i89@qtR#&YtIkL;sR3T(#}o)FuwIMUku4q0e^$tMPlrH{GVM z_Iz;GkH{jfH2$hXi-%xbvCM*{W-ZQ*P?WBY7V~|_1OvBe=;kwI`mlA}dLJfzIe1!S z^we?tZh-uIu5|)`UxdGZWpvXsgk}Fd-TFKJz63jF4#5kr{r60UzZbW4mKllo`;Q0; z3I8O--tF;qduQe6FU?bQ8h;{&D_n(PyeOxQ{;p}W)_q2#S>>?~}Xy7SEsH2}7W!H6=tgG6_eOuCySDfQ4HaGXbvx9^=`bS`P3(XPVT`xA8UL``6*R#e{5of0(>#bOTB zuh9(u#l;#nRQnU3@5i2b%a zs2a|k?W8ll4myH8E!axY&f*=HBy@AA!Hdd~J@87>z=6?~#l=15pes2@i?8#Ahp$?5e_=%lNJU$Vrj{PL=s)B}r z0RdTcS(!tML#%(96K`m(A9nY;i*#+&+D+C+f2h57@jzXlx#i%5ad$7T6(XirUGKt~ zg96N$+{)B#f1Yi9I$O;V*9y_SvTttxI1!zfDW+U8I#sAiLsm|%Uo|RYs8Ev!FC8`K z`ubE{dl|oBN?u8vNUABx>emwufUGI+NrKB;o~Gbh^Y#9but(;Ds*c zhQE&6)-n!8I>ddgLtrl2o-FSTS%>%`>~H>PefCE&WES4O?D$&8*#SJ5ihc;W)DJ<) zM3y72DmXrTeO6jpuU-cCQlZBVDyacXhiBPh{hnpByV-h(hhK`g@SR_5So_Gr%n(nP zX?MT2e#1+b1z0D0O{ltPL2X8W#}o;}ht$j)w0O4l*(`C3xaqf3HFfH}uP!TJ@z##X zc!22WhN_Em5f#n9eBgk-D;CsfYWBphm{6>ZPBujuVx=HPY{Ob9AX|<+;R)aM&^y*{ zzDX^$y9vFF_~v^kw42>Lccn2Dtu#~-%_SJM8`hZ%1CPj9!5WQKPqx7KrGYg~R-Z-` zXOY|_O`nGfvDSAO}}I?yLJ(80=L@0?6a`#{W6EoIVBMFm_GQyBC*|*b-T@ edAH=d_4Ie;b0q-tfDiBYR%ZLzZU-M;QT`7)H%ZX| literal 0 HcmV?d00001 diff --git a/interface/resources/fonts/Roboto-Italic.ttf b/interface/resources/fonts/Roboto-Italic.ttf new file mode 100644 index 0000000000000000000000000000000000000000..c3abaefb284004df30fc3017c7fa7985b412405d GIT binary patch literal 152208 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]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 pSMMfWC3h031oSW{sOP-O*@UYa+3X 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�egE!tu*v~K}gWeK5h%eT<6pQ9}X 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�PJ}HG?Q&#wR5zLv9qnRi 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 Z?uN)pxi0>?VH-^Ty3==XU7v${{}1B`zwrP7 literal 0 HcmV?d00001 diff --git a/interface/resources/fonts/RobotoMono-BoldItalic.ttf b/interface/resources/fonts/RobotoMono-BoldItalic.ttf new file mode 100644 index 0000000000000000000000000000000000000000..642dd0589f0053acfd4f32100e7004c6e82df8fe GIT binary patch literal 95100 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 literal 0 HcmV?d00001 diff --git a/interface/resources/fonts/RobotoMono-Regular.ttf b/interface/resources/fonts/RobotoMono-Regular.ttf new file mode 100644 index 0000000000000000000000000000000000000000..3806bfb1101f9022b63ed62e0bdd0103bc0db5f1 GIT binary patch literal 87540 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 H^|Jhb<`UPU literal 0 HcmV?d00001 diff --git a/interface/resources/qml/overte/Theme.qml b/interface/resources/qml/overte/Theme.qml index 9e8cc6e4280..95982c2cdc6 100644 --- a/interface/resources/qml/overte/Theme.qml +++ b/interface/resources/qml/overte/Theme.qml @@ -19,13 +19,13 @@ QtObject { property bool reducedMotion: false // font face for UI elements - readonly property string fontFamily: "DejaVu Sans" + readonly property string fontFamily: "Roboto" // font face for document text - readonly property string bodyFontFamily: "DejaVu Sans" + readonly property string bodyFontFamily: "Roboto" // font face for code editors - readonly property string monoFontFamily: "DejaVu Sans Mono" + readonly property string monoFontFamily: "Roboto Mono" readonly property int fontPixelSize: 18 readonly property int fontPixelSizeSmall: 14 diff --git a/interface/resources/qml/overte/WidgetZoo.qml b/interface/resources/qml/overte/WidgetZoo.qml index f4da29120c5..5d8ff0cbd73 100644 --- a/interface/resources/qml/overte/WidgetZoo.qml +++ b/interface/resources/qml/overte/WidgetZoo.qml @@ -75,6 +75,13 @@ Window { 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" } diff --git a/interface/src/Application_Setup.cpp b/interface/src/Application_Setup.cpp index adeaab5d421..0d800fff4a3 100644 --- a/interface/src/Application_Setup.cpp +++ b/interface/src/Application_Setup.cpp @@ -666,6 +666,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 From 919baccb7a75dbe566b268e98f452028d90be7f4 Mon Sep 17 00:00:00 2001 From: Ada Date: Thu, 30 Oct 2025 19:54:44 +1000 Subject: [PATCH 071/111] Add the old settings menu items back as a fallback --- interface/src/Menu.cpp | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index bfb9ff950de..ee655fbb3ad 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -259,6 +259,43 @@ 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(legacySettingsMenu, MenuOption::Preferences, Qt::CTRL | Qt::Key_G, nullptr, nullptr); + connect(action, &QAction::triggered, [] { + if (!qApp->getLoginDialogPoppedUp()) { + qApp->showDialog(QString("hifi/dialogs/GeneralPreferencesDialog.qml"), + QString("hifi/tablet/TabletGeneralPreferences.qml"), "GeneralPreferencesDialog"); + } + }); + + // Settings > Controls... + action = addActionToQMenuAndActionHash(legacySettingsMenu, "Controls..."); + connect(action, &QAction::triggered, [] { + auto tablet = DependencyManager::get()->getTablet("com.highfidelity.interface.tablet.system"); + auto hmd = DependencyManager::get(); + tablet->pushOntoStack("hifi/tablet/ControllerSettings.qml"); + + if (!hmd->getShouldShowTablet()) { + hmd->toggleShouldShowTablet(); + } + }); + + // Settings > 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"); + auto hmd = DependencyManager::get(); + tablet->pushOntoStack(tabletUrl); + + if (!hmd->getShouldShowTablet()) { + hmd->toggleShouldShowTablet(); + } + }); + // Settings > Security... action = addActionToQMenuAndActionHash(settingsMenu, "Security..."); connect(action, &QAction::triggered, [] { From f63523203565a3f14ae03d431acc82cb4218d923 Mon Sep 17 00:00:00 2001 From: RTUnreal Date: Fri, 7 Nov 2025 18:36:28 +0100 Subject: [PATCH 072/111] fix removed qt5 headers --- libraries/gl/src/gl/ContextQt.cpp | 6 +----- libraries/gl/src/gl/QOpenGLContextWrapper.cpp | 6 ------ 2 files changed, 1 insertion(+), 11 deletions(-) diff --git a/libraries/gl/src/gl/ContextQt.cpp b/libraries/gl/src/gl/ContextQt.cpp index 1dbe074a3ef..795f4920bbb 100644 --- a/libraries/gl/src/gl/ContextQt.cpp +++ b/libraries/gl/src/gl/ContextQt.cpp @@ -6,7 +6,7 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -// Note, `gl::Context is split into two files because a single file cannot include both the GLAD headers +// Note, `gl::Context is split into two files because a single file cannot include both the GLAD headers // and the QOpenGLContext definition headers #include "Context.h" @@ -16,10 +16,6 @@ #include #include "QOpenGLContextWrapper.h" -#ifdef Q_OS_WIN -#include -#endif - #include #include "GLHelpers.h" diff --git a/libraries/gl/src/gl/QOpenGLContextWrapper.cpp b/libraries/gl/src/gl/QOpenGLContextWrapper.cpp index 6d2cc7c03c1..e7b4473b2dc 100644 --- a/libraries/gl/src/gl/QOpenGLContextWrapper.cpp +++ b/libraries/gl/src/gl/QOpenGLContextWrapper.cpp @@ -13,15 +13,10 @@ #include -#ifdef Q_OS_WIN -#include -#endif - QOpenGLContextWrapper::Pointer QOpenGLContextWrapper::currentContextWrapper() { return std::make_shared(QOpenGLContext::currentContext()); } - QOpenGLContextWrapper::NativeContextPointer QOpenGLContextWrapper::getNativeContext() const { QOpenGLContextWrapper::NativeContextPointer result; // QT6TODO: @@ -33,7 +28,6 @@ QOpenGLContextWrapper::NativeContextPointer QOpenGLContextWrapper::getNativeCont return result; } - uint32_t QOpenGLContextWrapper::currentContextVersion() { QOpenGLContext* context = QOpenGLContext::currentContext(); if (!context) { From ed2537b51113bd7236091805139931e794945bc2 Mon Sep 17 00:00:00 2001 From: RTUnreal Date: Fri, 7 Nov 2025 18:38:40 +0100 Subject: [PATCH 073/111] fix incorrect initialization of array --- libraries/gl/src/gl/GLHelpers.cpp | 18 +++++++++--------- libraries/networking/src/FingerprintUtils.cpp | 4 ++-- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/libraries/gl/src/gl/GLHelpers.cpp b/libraries/gl/src/gl/GLHelpers.cpp index bb583f9eef7..a6fe2c0a4ba 100644 --- a/libraries/gl/src/gl/GLHelpers.cpp +++ b/libraries/gl/src/gl/GLHelpers.cpp @@ -113,7 +113,7 @@ GLAPI PFNWGLCHOOSEPIXELFORMATARBPROC wglChoosePixelFormatARB; GLAPI PFNWGLCREATECONTEXTATTRIBSARBPROC wglCreateContextAttribsARB; static bool setupPixelFormatSimple(HDC hdc) { - // FIXME build the PFD based on the + // FIXME build the PFD based on the static const PIXELFORMATDESCRIPTOR pfd = // pfd Tells Windows How We Want Things To Be { sizeof(PIXELFORMATDESCRIPTOR), // Size Of This Pixel Format Descriptor @@ -128,7 +128,7 @@ static bool setupPixelFormatSimple(HDC hdc) { 0, // Shift Bit Ignored 0, // No Accumulation Buffer 0, 0, 0, 0, // Accumulation Bits Ignored - 24, // 24 Bit Z-Buffer (Depth Buffer) + 24, // 24 Bit Z-Buffer (Depth Buffer) 8, // 8 Bit Stencil Buffer 0, // No Auxiliary Buffer PFD_MAIN_PLANE, // Main Drawing Layer @@ -188,9 +188,9 @@ uint16_t gl::getAvailableVersion() { major = 4; minor = 1; #elif defined(Q_OS_WIN) - // + // HINSTANCE hInstance = GetModuleHandle(nullptr); - const auto windowClassName = "OpenGLVersionCheck"; + const wchar_t windowClassName[] = L"OpenGLVersionCheck"; WNDCLASS wc = { }; wc.lpfnWndProc = DefWindowProc; wc.hInstance = hInstance; @@ -199,7 +199,7 @@ uint16_t gl::getAvailableVersion() { using Handle = std::shared_ptr; HWND rawHwnd = CreateWindowEx( - WS_EX_APPWINDOW, // extended style + WS_EX_APPWINDOW, // extended style windowClassName, // class name windowClassName, // title WS_CLIPSIBLINGS | WS_CLIPCHILDREN | CS_OWNDC | WS_POPUP, // style @@ -242,7 +242,7 @@ uint16_t gl::getAvailableVersion() { return; } - // The only two versions we care about on Windows + // The only two versions we care about on Windows // are 4.5 and 4.1 if (GLAD_GL_VERSION_4_5) { major = 4; @@ -321,7 +321,7 @@ namespace gl { GLenum error = glGetError(); if (!error) { return false; - } + } switch (error) { case GL_INVALID_ENUM: qCWarning(glLogging) << "GLBackend" << name << ": An unacceptable value is specified for an enumerated argument.The offending command is ignored and has no other side effect than to set the error flag."; @@ -352,8 +352,8 @@ namespace gl { bool checkGLErrorDebug(const char* name) { - // Disabling error checking macro on Android debug builds for now, - // as it throws off performance testing, which must be done on + // Disabling error checking macro on Android debug builds for now, + // as it throws off performance testing, which must be done on // Debug builds #if defined(DEBUG) && !defined(Q_OS_ANDROID) return checkGLError(name); diff --git a/libraries/networking/src/FingerprintUtils.cpp b/libraries/networking/src/FingerprintUtils.cpp index 5bb530d332b..3bd6785e3e1 100644 --- a/libraries/networking/src/FingerprintUtils.cpp +++ b/libraries/networking/src/FingerprintUtils.cpp @@ -110,11 +110,11 @@ QString FingerprintUtils::getMachineFingerprintString() { bool success = false; // try and open the key that contains the machine GUID - if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, "SOFTWARE\\Microsoft\\Cryptography", 0, KEY_READ, &cryptoKey) == ERROR_SUCCESS) { + if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\Cryptography", 0, KEY_READ, &cryptoKey) == ERROR_SUCCESS) { DWORD type; DWORD guidSize; - const char* MACHINE_GUID_KEY = "MachineGuid"; + const wchar_t MACHINE_GUID_KEY[] = L"MachineGuid"; // try and retrieve the size of the GUID value if (RegQueryValueEx(cryptoKey, MACHINE_GUID_KEY, NULL, &type, NULL, &guidSize) == ERROR_SUCCESS) { From f0781e32e0b11ca13bc309f9d6795c455e6909d4 Mon Sep 17 00:00:00 2001 From: RTUnreal Date: Fri, 7 Nov 2025 18:59:19 +0100 Subject: [PATCH 074/111] fix wrong typing? --- libraries/audio-client/src/AudioClient.cpp | 34 +++++++++++----------- libraries/midi/src/Midi.cpp | 14 ++++----- libraries/networking/src/ResourceCache.cpp | 8 ++--- libraries/shared/src/SharedUtil.h | 3 +- 4 files changed, 28 insertions(+), 31 deletions(-) diff --git a/libraries/audio-client/src/AudioClient.cpp b/libraries/audio-client/src/AudioClient.cpp index c6b9285f564..593e6f1341f 100644 --- a/libraries/audio-client/src/AudioClient.cpp +++ b/libraries/audio-client/src/AudioClient.cpp @@ -102,7 +102,7 @@ void AudioClient::setHmdAudioName(QAudioDevice::Mode mode, const QString& name) // thread-safe QList getAvailableDevices(QAudioDevice::Mode mode, const QString& hmdName) { //get hmd device name prior to locking device mutex. in case of shutdown, this thread will be locked and audio client - //cannot properly shut down. + //cannot properly shut down. QString defDeviceName = defaultAudioDeviceName(mode); // NOTE: availableDevices() clobbers the Qt internal device list @@ -422,7 +422,6 @@ AudioClient::AudioClient() { } AudioClient::~AudioClient() { - stop(); if (_codec && _encoder) { @@ -492,7 +491,7 @@ QString getWinDeviceName(IMMDevice* pEndpoint) { HRESULT hr = pPropertyStore->GetValue(PKEY_Device_FriendlyName, &pv); pPropertyStore->Release(); pPropertyStore = nullptr; - deviceName = QString::fromWCharArray((wchar_t*)pv.pwszVal); + deviceName = QString::fromWCharArray(pv.pwszVal); if (!IsWindows8OrGreater()) { // Windows 7 provides only the 31 first characters of the device name. const DWORD QT_WIN7_MAX_AUDIO_DEVICENAME_LEN = 31; @@ -528,8 +527,8 @@ QString AudioClient::getWinDeviceName(wchar_t* guid) { HifiAudioDeviceInfo defaultAudioDeviceForMode(QAudioDevice::Mode mode, const QString& hmdName) { QString deviceName = defaultAudioDeviceName(mode); -#if defined (Q_OS_ANDROID) - if (mode == QAudio::AudioInput) { +#if defined(Q_OS_ANDROID) + if (mode == QAudioDevice::Mode::Input) { Setting::Handle enableAEC(SETTING_AEC_KEY, DEFAULT_AEC_ENABLED); bool aecEnabled = enableAEC.get(); auto audioClient = DependencyManager::get(); @@ -550,7 +549,7 @@ QString defaultAudioDeviceName(QAudioDevice::Mode mode) { #ifdef __APPLE__ QAudioDeviceInfo device; - if (mode == QAudio::AudioInput) { + if (mode == QAudioDevice::Mode::Input) { device = QAudioDeviceInfo::defaultInputDevice(); } else { device = QAudioDeviceInfo::defaultOutputDevice(); @@ -569,7 +568,7 @@ QString defaultAudioDeviceName(QAudioDevice::Mode mode) { kAudioObjectPropertyElementMaster }; - if (mode == QAudio::AudioOutput) { + if (mode == QAudioDevice::Mode::Output) { propertyAddress.mSelector = kAudioHardwarePropertyDefaultOutputDevice; } @@ -596,8 +595,8 @@ QString defaultAudioDeviceName(QAudioDevice::Mode mode) { #endif #ifdef _WIN32 //Check for Windows Vista or higher, IMMDeviceEnumerator doesn't work below that. - if (!IsWindowsVistaOrGreater()) { // lower then vista - if (mode == QAudio::AudioInput) { + if (!IsWindowsVistaOrGreater()) { // lower then vista + if (mode == QAudioDevice::Mode::Input) { WAVEINCAPS wic; // first use WAVE_MAPPER to get the default devices manufacturer ID waveInGetDevCaps(WAVE_MAPPER, &wic, sizeof(wic)); @@ -606,7 +605,7 @@ QString defaultAudioDeviceName(QAudioDevice::Mode mode) { #if !defined(NDEBUG) qCDebug(audioclient) << "input device:" << wic.szPname; #endif - deviceName = wic.szPname; + deviceName = QString::fromWCharArray(wic.szPname); } else { WAVEOUTCAPS woc; // first use WAVE_MAPPER to get the default devices manufacturer ID @@ -616,7 +615,7 @@ QString defaultAudioDeviceName(QAudioDevice::Mode mode) { #if !defined(NDEBUG) qCDebug(audioclient) << "output device:" << woc.szPname; #endif - deviceName = woc.szPname; + deviceName = QString::fromWCharArray(woc.szPname); } } else { HRESULT hr = S_OK; @@ -624,7 +623,8 @@ QString defaultAudioDeviceName(QAudioDevice::Mode mode) { IMMDeviceEnumerator* pMMDeviceEnumerator = NULL; CoCreateInstance(__uuidof(MMDeviceEnumerator), NULL, CLSCTX_ALL, __uuidof(IMMDeviceEnumerator), (void**)&pMMDeviceEnumerator); IMMDevice* pEndpoint; - hr = pMMDeviceEnumerator->GetDefaultAudioEndpoint(mode == QAudio::AudioOutput ? eRender : eCapture, eMultimedia, &pEndpoint); + hr = pMMDeviceEnumerator->GetDefaultAudioEndpoint(mode == QAudioDevice::Mode::Output ? eRender : eCapture, eMultimedia, + &pEndpoint); if (hr == E_NOTFOUND) { printf("Audio Error: device not found\n"); deviceName = QString("NONE"); @@ -638,21 +638,21 @@ QString defaultAudioDeviceName(QAudioDevice::Mode mode) { CoUninitialize(); } -#if !defined(NDEBUG) - qCDebug(audioclient) << "defaultAudioDeviceForMode mode: " << (mode == QAudio::AudioOutput ? "Output" : "Input") - << " [" << deviceName << "] [" << "]"; +#if !defined(NDEBUG) + qCDebug(audioclient) << "defaultAudioDeviceForMode mode: " << (mode == QAudioDevice::Mode::Output ? "Output" : "Input") + << " [" << deviceName << "] [" << "]"; #endif #endif #ifdef Q_OS_LINUX - if ( mode == QAudioDevice::Mode::Input ) { + if (mode == QAudioDevice::Mode::Input) { deviceName = QMediaDevices::defaultAudioInput().description(); } else { deviceName = QMediaDevices::defaultAudioOutput().description(); } #endif - return deviceName; + return deviceName; } bool AudioClient::getNamedAudioDeviceForModeExists(QAudioDevice::Mode mode, const QString& deviceName) { diff --git a/libraries/midi/src/Midi.cpp b/libraries/midi/src/Midi.cpp index 05940324014..80fd22f668a 100644 --- a/libraries/midi/src/Midi.cpp +++ b/libraries/midi/src/Midi.cpp @@ -198,15 +198,14 @@ void Midi::MidiSetup() { MIDIINCAPS incaps; for (unsigned int i = 0; i < midiInGetNumDevs(); i++) { if (MMSYSERR_NOERROR == midiInGetDevCaps(i, &incaps, sizeof(MIDIINCAPS))) { - bool found = false; for (int j = 0; j < midiInExclude.size(); j++) { - if (midiInExclude[j].toStdString().compare(incaps.szPname) == 0) { + if (midiInExclude[j].toStdWString().compare(incaps.szPname) == 0) { found = true; break; } } - if (!found) { // EXCLUDE AN INPUT BY NAME + if (!found) { // EXCLUDE AN INPUT BY NAME HMIDIIN tmphin; if (MMSYSERR_NOERROR == midiInOpen(&tmphin, i, (DWORD_PTR)MidiInProc, NULL, CALLBACK_FUNCTION)) { if (MMSYSERR_NOERROR == midiInStart(tmphin)) { @@ -220,15 +219,14 @@ void Midi::MidiSetup() { MIDIOUTCAPS outcaps; for (unsigned int i = 0; i < midiOutGetNumDevs(); i++) { if (MMSYSERR_NOERROR == midiOutGetDevCaps(i, &outcaps, sizeof(MIDIOUTCAPS))) { - bool found = false; for (int j = 0; j < midiOutExclude.size(); j++) { - if (midiOutExclude[j].toStdString().compare(outcaps.szPname) == 0) { + if (midiOutExclude[j].toStdWString().compare(outcaps.szPname) == 0) { found = true; break; } } - if (!found) { // EXCLUDE AN OUTPUT BY NAME + if (!found) { // EXCLUDE AN OUTPUT BY NAME HMIDIOUT tmphout; if (MMSYSERR_NOERROR == midiOutOpen(&tmphout, i, (DWORD_PTR)MidiOutProc, NULL, CALLBACK_FUNCTION)) { midihout.push_back(tmphout); @@ -395,13 +393,13 @@ QStringList Midi::listMidiDevices(bool output) { MIDIOUTCAPS outcaps; for (unsigned int i = 0; i < midiOutGetNumDevs(); i++) { midiOutGetDevCaps(i, &outcaps, sizeof(MIDIINCAPS)); - rv.append(outcaps.szPname); + rv.append(QString::fromWCharArray(outcaps.szPname)); } } else { MIDIINCAPS incaps; for (unsigned int i = 0; i < midiInGetNumDevs(); i++) { midiInGetDevCaps(i, &incaps, sizeof(MIDIINCAPS)); - rv.append(incaps.szPname); + rv.append(QString::fromWCharArray(incaps.szPname)); } } #endif diff --git a/libraries/networking/src/ResourceCache.cpp b/libraries/networking/src/ResourceCache.cpp index 8d77ca71744..1cda90c257b 100644 --- a/libraries/networking/src/ResourceCache.cpp +++ b/libraries/networking/src/ResourceCache.cpp @@ -162,7 +162,7 @@ void ScriptableResourceCache::updateTotalSize(const qint64& deltaSize) { _resourceCache->updateTotalSize(deltaSize); } -ScriptableResource* ScriptableResourceCache::prefetch(const QUrl& url, void* extra, size_t extraHash) { +ScriptableResource* ScriptableResourceCache::prefetch(const QUrl& url, void* extra, ulong extraHash) { return _resourceCache->prefetch(url, extra, extraHash); } @@ -215,10 +215,10 @@ void ScriptableResource::disconnectHelper() { } } -ScriptableResource* ResourceCache::prefetch(const QUrl& url, void* extra, size_t extraHash) { +ScriptableResource* ResourceCache::prefetch(const QUrl& url, void* extra, ulong extraHash) { ScriptableResource* result = nullptr; - if (QThread::currentThread() != thread()) { + if (QThread::currentThread() != this->thread()) { // Must be called in thread to ensure getResource returns a valid pointer BLOCKING_INVOKE_METHOD(this, "prefetch", Q_GENERIC_RETURN_ARG(ScriptableResource*, result), @@ -908,6 +908,6 @@ bool Resource::handleFailedRequest(ResourceRequest::Result result) { return willRetry; } -uint qHash(const QPointer& value, uint seed) { +size_t qHash(const QPointer& value, size_t seed) { return qHash(value.data(), seed); } diff --git a/libraries/shared/src/SharedUtil.h b/libraries/shared/src/SharedUtil.h index ac440759559..653856bddd1 100644 --- a/libraries/shared/src/SharedUtil.h +++ b/libraries/shared/src/SharedUtil.h @@ -189,8 +189,7 @@ QString formatSecondsElapsed(float seconds); bool similarStrings(const QString& stringA, const QString& stringB); template -uint qHash(const std::shared_ptr& ptr, uint seed = 0) -{ +size_t qHash(const std::shared_ptr& ptr, size_t seed = 0) { return qHash(ptr.get(), seed); } From 22acdfea1dc32e5cdfe6fcaa5e6a6a03cf098208 Mon Sep 17 00:00:00 2001 From: RTUnreal Date: Fri, 7 Nov 2025 19:03:00 +0100 Subject: [PATCH 075/111] fix debug only vars being used in non debug env --- libraries/audio/src/Sound.cpp | 2 ++ .../src/material-networking/TextureCache.cpp | 4 ++++ libraries/networking/src/ResourceCache.cpp | 4 ++++ 3 files changed, 10 insertions(+) diff --git a/libraries/audio/src/Sound.cpp b/libraries/audio/src/Sound.cpp index fc755407785..83481f9bd41 100644 --- a/libraries/audio/src/Sound.cpp +++ b/libraries/audio/src/Sound.cpp @@ -77,7 +77,9 @@ void Sound::downloadFinished(const QByteArray& data) { auto sharedSoundPointer = weak_from_this().lock(); if (!sharedSoundPointer) { +#if !defined(QT_NO_DEBUG) || defined(QT_FORCE_ASSERTS) Q_ASSERT(!_wasDeleted); +#endif soundProcessError(301, "Sound object has gone out of scope"); return; } diff --git a/libraries/material-networking/src/material-networking/TextureCache.cpp b/libraries/material-networking/src/material-networking/TextureCache.cpp index 458477b89ba..719f67db1b0 100644 --- a/libraries/material-networking/src/material-networking/TextureCache.cpp +++ b/libraries/material-networking/src/material-networking/TextureCache.cpp @@ -510,7 +510,9 @@ void NetworkTexture::setImage(gpu::TexturePointer texture, int originalWidth, if (!self) { // We need to make sure that texture was just added to unused pool and wasn't deleted yet. +#if !defined(QT_NO_DEBUG) || defined(QT_FORCE_ASSERTS) Q_ASSERT(!_wasDeleted); +#endif return; //TODO: what to do when texture pointer has expired? } @@ -1146,7 +1148,9 @@ void NetworkTexture::loadMetaContent(const QByteArray& content) { auto textureCache = DependencyManager::get(); auto self = weak_from_this().lock(); if (!self) { +#if !defined(QT_NO_DEBUG) || defined(QT_FORCE_ASSERTS) Q_ASSERT(!_wasDeleted); +#endif return; } QMetaObject::invokeMethod(this, "attemptRequest", Qt::QueuedConnection); diff --git a/libraries/networking/src/ResourceCache.cpp b/libraries/networking/src/ResourceCache.cpp index 1cda90c257b..c8dbaf41ca4 100644 --- a/libraries/networking/src/ResourceCache.cpp +++ b/libraries/networking/src/ResourceCache.cpp @@ -733,7 +733,9 @@ void Resource::attemptRequest() { auto self = weak_from_this().lock(); if (self) { +#if !defined(QT_NO_DEBUG) || defined(QT_FORCE_ASSERTS) Q_ASSERT(!_wasDeleted); +#endif ResourceCache::attemptRequest(self); } } @@ -805,7 +807,9 @@ void Resource::handleReplyFinished() { auto self = weak_from_this().lock(); if (!self) { // Make sure the resource wasn't deleted yet, and it's just scheduled for deletion or pointer has expired. +#if !defined(QT_NO_DEBUG) || defined(QT_FORCE_ASSERTS) Q_ASSERT(!_wasDeleted); +#endif } if (!_request || _request != sender()) { From 25811b5b9fc8729b53f3386334c56b0c2dde66e8 Mon Sep 17 00:00:00 2001 From: RTUnreal Date: Tue, 28 Oct 2025 21:14:51 +0100 Subject: [PATCH 076/111] fix usage of not (yet) supported c++20 syntax --- libraries/qml/src/qml/OffscreenSurface.cpp | 2 +- libraries/script-engine/src/CanvasCommand.h | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/libraries/qml/src/qml/OffscreenSurface.cpp b/libraries/qml/src/qml/OffscreenSurface.cpp index 13ddd806dc0..cff4a0570b9 100644 --- a/libraries/qml/src/qml/OffscreenSurface.cpp +++ b/libraries/qml/src/qml/OffscreenSurface.cpp @@ -94,7 +94,7 @@ void OffscreenSurface::setSharedContext(QOpenGLContext* sharedContext) { std::function OffscreenSurface::getDiscardLambda() { return [](uint32_t texture, void* fence) { - SharedObject::getTextureCache().releaseTexture({ texture, static_cast(fence) }); + SharedObject::getTextureCache().releaseTexture(TextureCache::Value(texture, static_cast(fence))); }; } diff --git a/libraries/script-engine/src/CanvasCommand.h b/libraries/script-engine/src/CanvasCommand.h index 838af044f22..b6022577af4 100644 --- a/libraries/script-engine/src/CanvasCommand.h +++ b/libraries/script-engine/src/CanvasCommand.h @@ -417,7 +417,8 @@ struct CanvasCommand { static CanvasCommand fillRect(const QRectF& rect) { CanvasCommand cmd; cmd.kind = FillRect; - return CanvasCommand { .kind = FillRect, ._rect = rect }; + cmd._rect = rect; + return cmd; } static CanvasCommand fillEllipse(const QRectF& rect) { From 449033ab5e6246b2a9b14a764200072acf7cc4fd Mon Sep 17 00:00:00 2001 From: RTUnreal Date: Fri, 7 Nov 2025 19:05:27 +0100 Subject: [PATCH 077/111] fix windows gl.h not being initialized properly? --- libraries/qml/src/qml/OffscreenSurface.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/libraries/qml/src/qml/OffscreenSurface.cpp b/libraries/qml/src/qml/OffscreenSurface.cpp index cff4a0570b9..99c8768136f 100644 --- a/libraries/qml/src/qml/OffscreenSurface.cpp +++ b/libraries/qml/src/qml/OffscreenSurface.cpp @@ -10,6 +10,9 @@ #include #include +#ifdef Q_OS_WIN +#include +#endif //Q_OS_WIN #include #include From b7846938fb1b398f0b137778577f690aed3a0c64 Mon Sep 17 00:00:00 2001 From: RTUnreal Date: Wed, 29 Oct 2025 20:31:31 +0100 Subject: [PATCH 078/111] fix file encoding issue --- libraries/audio/src/flump3dec.cpp | 2 +- libraries/audio/src/flump3dec.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/libraries/audio/src/flump3dec.cpp b/libraries/audio/src/flump3dec.cpp index 7349cda7e42..79fec0992a9 100644 --- a/libraries/audio/src/flump3dec.cpp +++ b/libraries/audio/src/flump3dec.cpp @@ -101,7 +101,7 @@ * As Fluendo can not assure that any of the activities you undertake do not * infringe any patents or other industrial or intellectual property rights, * Fluendo hereby disclaims any liability for any patent infringement that may be - * claimed to you or to any other person from any legitimate rights owner, as + * claimed to you or to any other person from any legitimate right's owner, as * stated in MIT license. So it is your responsibility to get information and to * acquire the necessary patent licenses to undertake your activities legally. */ diff --git a/libraries/audio/src/flump3dec.h b/libraries/audio/src/flump3dec.h index 688831da17d..e7c19cea4b3 100644 --- a/libraries/audio/src/flump3dec.h +++ b/libraries/audio/src/flump3dec.h @@ -101,7 +101,7 @@ * As Fluendo can not assure that any of the activities you undertake do not * infringe any patents or other industrial or intellectual property rights, * Fluendo hereby disclaims any liability for any patent infringement that may be - * claimed to you or to any other person from any legitimate rights owner, as + * claimed to you or to any other person from any legitimate right's owner, as * stated in MIT license. So it is your responsibility to get information and to * acquire the necessary patent licenses to undertake your activities legally. */ From 613f6105fbdb91a2cf28a99c58e38012e4267038 Mon Sep 17 00:00:00 2001 From: RTUnreal Date: Fri, 31 Oct 2025 23:14:20 +0100 Subject: [PATCH 079/111] fix some plugin errors --- plugins/hifiOsc/src/OscPlugin.cpp | 8 ++++---- plugins/openvr/src/OpenVrHelpers.cpp | 4 ++++ 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/plugins/hifiOsc/src/OscPlugin.cpp b/plugins/hifiOsc/src/OscPlugin.cpp index a261674feb3..3e8ba8c44af 100644 --- a/plugins/hifiOsc/src/OscPlugin.cpp +++ b/plugins/hifiOsc/src/OscPlugin.cpp @@ -85,7 +85,7 @@ enum class FaceCap { // used to mirror left/right shapes from FaceCap. // i.e. right and left shapes are swapped. -FaceCap faceMirrorMap[static_cast(FaceCap::BlendshapeCount)] = { +FaceCap faceMirrorMap[static_cast(FaceCap::BlendshapeCount)] = { FaceCap::BrowsU_C, FaceCap::BrowsD_R, FaceCap::BrowsD_L, @@ -140,7 +140,7 @@ FaceCap faceMirrorMap[static_cast(FaceCap::BlendshapeCount)] = { FaceCap::TongueOut }; -static const char* STRINGS[static_cast(FaceCap::BlendshapeCount)] = { +static const char* STRINGS[static_cast(FaceCap::BlendshapeCount)] = { "BrowsU_C", "BrowsD_L", "BrowsD_R", @@ -195,7 +195,7 @@ static const char* STRINGS[static_cast(FaceCap::BlendshapeCount)] = { "TongueOut" }; -static enum controller::StandardAxisChannel CHANNELS[static_cast(FaceCap::BlendshapeCount)] = { +static enum controller::StandardAxisChannel CHANNELS[static_cast(FaceCap::BlendshapeCount)] = { controller::BROWSU_C, controller::BROWSD_L, controller::BROWSD_R, @@ -582,7 +582,7 @@ controller::Input::NamedVector OscPlugin::InputDevice::getAvailableInputs() cons static controller::Input::NamedVector availableInputs; if (availableInputs.size() == 0) { for (int i = 0; i < static_cast(FaceCap::BlendshapeCount); i++) { - availableInputs.push_back(makePair(CHANNELS[i], QString(STRINGS[i]))); + availableInputs.push_back(makePair(static_cast(CHANNELS[i]), QString(STRINGS[i]))); } } availableInputs.push_back(makePair(controller::HEAD, QString("Head"))); diff --git a/plugins/openvr/src/OpenVrHelpers.cpp b/plugins/openvr/src/OpenVrHelpers.cpp index 5f2eedeea33..8b8e67eaa41 100644 --- a/plugins/openvr/src/OpenVrHelpers.cpp +++ b/plugins/openvr/src/OpenVrHelpers.cpp @@ -28,6 +28,10 @@ #include #include "../../interface/src/Menu.h" +#ifdef Q_OS_WIN +#include +#endif + Q_DECLARE_LOGGING_CATEGORY(displayplugins) Q_LOGGING_CATEGORY(displayplugins, "hifi.plugins.display") From 06987f91c8017de92d58106cce150f99aa8b7b3e Mon Sep 17 00:00:00 2001 From: RTUnreal Date: Fri, 7 Nov 2025 19:15:29 +0100 Subject: [PATCH 080/111] fix application event handler --- interface/src/ApplicationEventHandler.h | 12 +++++++----- interface/src/WindowsSystemInfo.h | 2 +- interface/src/main.cpp | 4 ++-- 3 files changed, 10 insertions(+), 8 deletions(-) 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/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/main.cpp b/interface/src/main.cpp index e3b6c7d09ad..b5db2399135 100644 --- a/interface/src/main.cpp +++ b/interface/src/main.cpp @@ -744,7 +744,7 @@ int main(int argc, const char* argv[]) { if (parser.isSet(checkMinSpecOption)) { QString appPath; { - char filename[MAX_PATH]; + wchar_t filename[MAX_PATH]; GetModuleFileName(NULL, filename, MAX_PATH); QFileInfo appInfo(filename); appPath = appInfo.absolutePath(); @@ -752,7 +752,7 @@ int main(int argc, const char* argv[]) { QString openvrDllPath = appPath + "/plugins/openvr.dll"; HMODULE openvrDll; CHECKMINSPECPROC checkMinSpecPtr; - if ((openvrDll = LoadLibrary(openvrDllPath.toLocal8Bit().data())) && + if ((openvrDll = LoadLibrary(qUtf16Printable(openvrDllPath.toLocal8Bit().data()))) && (checkMinSpecPtr = (CHECKMINSPECPROC)GetProcAddress(openvrDll, "CheckMinSpec"))) { if (!checkMinSpecPtr()) { return -1; From abe480137796b5800ea2eb3aa2e3c410ecfcc864 Mon Sep 17 00:00:00 2001 From: RTUnreal Date: Fri, 7 Nov 2025 19:08:40 +0100 Subject: [PATCH 081/111] fix correct debug output of ScriptException --- libraries/script-engine/src/ScriptException.h | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/libraries/script-engine/src/ScriptException.h b/libraries/script-engine/src/ScriptException.h index f533d7a4f0e..77eb21d225a 100644 --- a/libraries/script-engine/src/ScriptException.h +++ b/libraries/script-engine/src/ScriptException.h @@ -11,6 +11,7 @@ #include #include #include +#include #include "ScriptValue.h" @@ -146,20 +147,16 @@ class ScriptRuntimeException : public ScriptException { * * @return std::shared_ptr */ - virtual std::shared_ptr clone() const override { - return std::make_shared(*this); - } + virtual std::shared_ptr clone() const override { return std::make_shared(*this); } }; inline QDebug operator<<(QDebug debug, const ScriptException& e) { - debug << "Exception:" - << e.errorMessage - << (e.additionalInfo.isEmpty() ? QString("") : "[" + e.additionalInfo + "]") - << " at line " << e.errorLine << ", column " << e.errorColumn; + debug << "Exception:" << e.errorMessage << (e.additionalInfo.isEmpty() ? QString("") : "[" + e.additionalInfo + "]") + << " at line " << e.errorLine << ", column " << e.errorColumn; if (e.backtrace.length()) { debug << "Backtrace:"; - debug << e.backtrace; + debug << e.backtrace.join("\n"); } return debug; From 079106f3276f2b0deff0ab68d6cedae10faa3e7f Mon Sep 17 00:00:00 2001 From: RTUnreal Date: Fri, 7 Nov 2025 19:10:26 +0100 Subject: [PATCH 082/111] fix typing and implement more QDebug stuff --- libraries/entities/src/EntityItemPropertiesMacros.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/libraries/entities/src/EntityItemPropertiesMacros.h b/libraries/entities/src/EntityItemPropertiesMacros.h index 8b7447cd607..66e67d3c335 100644 --- a/libraries/entities/src/EntityItemPropertiesMacros.h +++ b/libraries/entities/src/EntityItemPropertiesMacros.h @@ -21,6 +21,7 @@ #include #include #include +#include #include "Sampler.h" const quint64 UNKNOWN_CREATED_TIME = 0; @@ -287,6 +288,7 @@ typedef QVector qVectorFloat; typedef QVector qVectorQUuid; typedef QVector qVectorQString; typedef QSet qSetQString; +inline QDebug &operator<<(QDebug& debug, qSetQString& stringSet) { for (const auto item: stringSet) { debug << item; } } inline float float_convertFromScriptValue(const ScriptValue& v, bool& isValid) { return v.toVariant().toFloat(&isValid); } inline quint64 quint64_convertFromScriptValue(const ScriptValue& v, bool& isValid) { return v.toVariant().toULongLong(&isValid); } inline quint32 quint32_convertFromScriptValue(const ScriptValue& v, bool& isValid) { From f2d3ab3bb262f37e466716d40c4159a5a6ff686f Mon Sep 17 00:00:00 2001 From: RTUnreal Date: Tue, 4 Nov 2025 19:13:27 +0100 Subject: [PATCH 083/111] fix more errors --- interface/src/main.cpp | 2 +- interface/src/ui/InteractiveWindow.cpp | 2 +- libraries/entities/src/EntityItemPropertiesMacros.h | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/interface/src/main.cpp b/interface/src/main.cpp index b5db2399135..49099bff2cd 100644 --- a/interface/src/main.cpp +++ b/interface/src/main.cpp @@ -746,7 +746,7 @@ int main(int argc, const char* argv[]) { { wchar_t filename[MAX_PATH]; GetModuleFileName(NULL, filename, MAX_PATH); - QFileInfo appInfo(filename); + QFileInfo appInfo(QString::fromWCharArray(filename)); appPath = appInfo.absolutePath(); } QString openvrDllPath = appPath + "/plugins/openvr.dll"; diff --git a/interface/src/ui/InteractiveWindow.cpp b/interface/src/ui/InteractiveWindow.cpp index e39287abb47..8cad284ffe2 100644 --- a/interface/src/ui/InteractiveWindow.cpp +++ b/interface/src/ui/InteractiveWindow.cpp @@ -34,7 +34,7 @@ #include "MainWindow.h" #ifdef Q_OS_WIN -#include +#include #endif STATIC_SCRIPT_TYPES_INITIALIZER(+[](ScriptManager* manager){ diff --git a/libraries/entities/src/EntityItemPropertiesMacros.h b/libraries/entities/src/EntityItemPropertiesMacros.h index 66e67d3c335..546b6ce647a 100644 --- a/libraries/entities/src/EntityItemPropertiesMacros.h +++ b/libraries/entities/src/EntityItemPropertiesMacros.h @@ -288,7 +288,7 @@ typedef QVector qVectorFloat; typedef QVector qVectorQUuid; typedef QVector qVectorQString; typedef QSet qSetQString; -inline QDebug &operator<<(QDebug& debug, qSetQString& stringSet) { for (const auto item: stringSet) { debug << item; } } +inline QDebug &operator<<(QDebug& debug, const qSetQString& stringSet) { for (const auto item: stringSet) { debug << item; } return debug; } inline float float_convertFromScriptValue(const ScriptValue& v, bool& isValid) { return v.toVariant().toFloat(&isValid); } inline quint64 quint64_convertFromScriptValue(const ScriptValue& v, bool& isValid) { return v.toVariant().toULongLong(&isValid); } inline quint32 quint32_convertFromScriptValue(const ScriptValue& v, bool& isValid) { From 3255202fdca1e75973ffaca62082f1e1aacdd998 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julian=20Gro=C3=9F?= Date: Wed, 5 Nov 2025 11:06:01 +0100 Subject: [PATCH 084/111] Angle was removed from Qt6 for Windows. https://doc.qt.io/qt-6/opengl-changes-qt6.html#removal-of-angle The `--no-angle` option now throws an error: `Unknown option 'no-angle'.` --- cmake/macros/PackageLibrariesForDeployment.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake/macros/PackageLibrariesForDeployment.cmake b/cmake/macros/PackageLibrariesForDeployment.cmake index 1c78adf8783..e4207190602 100644 --- a/cmake/macros/PackageLibrariesForDeployment.cmake +++ b/cmake/macros/PackageLibrariesForDeployment.cmake @@ -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( From c688af9a36ece51999b3d614b498145aef4e4790 Mon Sep 17 00:00:00 2001 From: RTUnreal Date: Wed, 5 Nov 2025 20:22:50 +0100 Subject: [PATCH 085/111] switch from `fromUint128` to default constructor --- domain-server/src/DomainGatekeeper.cpp | 4 ++-- .../src/DomainServerSettingsManager.cpp | 16 ++++++++-------- domain-server/src/DomainServerSettingsManager.h | 10 +++++----- libraries/networking/src/NodePermissions.cpp | 8 ++++---- 4 files changed, 19 insertions(+), 19 deletions(-) diff --git a/domain-server/src/DomainGatekeeper.cpp b/domain-server/src/DomainGatekeeper.cpp index 004cfce6e49..432e8a57495 100644 --- a/domain-server/src/DomainGatekeeper.cpp +++ b/domain-server/src/DomainGatekeeper.cpp @@ -341,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, QUuid::fromUInt128(0))); + NodePermissions userPerms(NodePermissionsKey(verifiedUsername, QUuid())); if (node->getPermissions().isAssignment) { // this node is an assignment-client @@ -470,7 +470,7 @@ SharedNodePointer DomainGatekeeper::processAgentConnectRequest(const NodeConnect auto limitedNodeList = DependencyManager::get(); // start with empty permissions - NodePermissions userPerms(NodePermissionsKey(username, QUuid::fromUInt128(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 diff --git a/domain-server/src/DomainServerSettingsManager.cpp b/domain-server/src/DomainServerSettingsManager.cpp index 850dae7e43b..7eec5c1c56d 100644 --- a/domain-server/src/DomainServerSettingsManager.cpp +++ b/domain-server/src/DomainServerSettingsManager.cpp @@ -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, QUuid::fromUInt128(0))].reset(new NodePermissions(allowedUser)); - _agentPermissions[NodePermissionsKey(allowedUser, QUuid::fromUInt128(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, QUuid::fromUInt128(0)); + NodePermissionsKey editorKey(allowedEditor, QUuid()); if (!_agentPermissions.contains(editorKey)) { _agentPermissions[editorKey].reset(new NodePermissions(allowedEditor)); if (isRestrictedAccess) { @@ -969,7 +969,7 @@ void DomainServerSettingsManager::processNodeKickRequestPacket(QSharedPointergetMachineFingerprint().toString(), QUuid::fromUInt128(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, QUuid::fromUInt128(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(), QUuid::fromUInt128(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, QUuid::fromUInt128(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(), QUuid::fromUInt128(0)); + NodePermissionsKey fingerprintKey = NodePermissionsKey(machineFingerprint.toString(), QUuid()); if (_machineFingerprintPermissions.contains(fingerprintKey)) { return *(_machineFingerprintPermissions[fingerprintKey].get()); } diff --git a/domain-server/src/DomainServerSettingsManager.h b/domain-server/src/DomainServerSettingsManager.h index 7cd70b14e15..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, QUuid::fromUInt128(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, QUuid::fromUInt128(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(), QUuid::fromUInt128(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, QUuid::fromUInt128(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(), QUuid::fromUInt128(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/libraries/networking/src/NodePermissions.cpp b/libraries/networking/src/NodePermissions.cpp index 64653c0c29b..f29cbc60cc8 100644 --- a/libraries/networking/src/NodePermissions.cpp +++ b/libraries/networking/src/NodePermissions.cpp @@ -32,10 +32,10 @@ size_t std::hash::operator()(const NodePermissionsKey& key) } -NodePermissionsKey NodePermissions::standardNameLocalhost = NodePermissionsKey("localhost", QUuid::fromUInt128(0)); -NodePermissionsKey NodePermissions::standardNameLoggedIn = NodePermissionsKey("logged-in", QUuid::fromUInt128(0)); -NodePermissionsKey NodePermissions::standardNameAnonymous = NodePermissionsKey("anonymous", QUuid::fromUInt128(0)); -NodePermissionsKey NodePermissions::standardNameFriends = NodePermissionsKey("friends", QUuid::fromUInt128(0)); +NodePermissionsKey NodePermissions::standardNameLocalhost = NodePermissionsKey("localhost", QUuid()); +NodePermissionsKey NodePermissions::standardNameLoggedIn = NodePermissionsKey("logged-in", QUuid()); +NodePermissionsKey NodePermissions::standardNameAnonymous = NodePermissionsKey("anonymous", QUuid()); +NodePermissionsKey NodePermissions::standardNameFriends = NodePermissionsKey("friends", QUuid()); QStringList NodePermissions::standardNames = QList() << NodePermissions::standardNameLocalhost.first From 1256a5af0b5407173f7b0bfde4b3e691b47ad99e Mon Sep 17 00:00:00 2001 From: RTUnreal Date: Thu, 6 Nov 2025 00:28:02 +0100 Subject: [PATCH 086/111] explicitly construct QByteArray --- domain-server/src/DomainServer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index c22ed6ea835..9b71a18027e 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -2951,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 << "-" From 4dc3940af57ce441aa9133c44719e4279b8c000d Mon Sep 17 00:00:00 2001 From: RTUnreal Date: Fri, 7 Nov 2025 19:13:07 +0100 Subject: [PATCH 087/111] fix changed class name --- tools/nitpick/src/TestRunnerMobile.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/tools/nitpick/src/TestRunnerMobile.cpp b/tools/nitpick/src/TestRunnerMobile.cpp index 53a74da82f5..a9a52b5bfa7 100644 --- a/tools/nitpick/src/TestRunnerMobile.cpp +++ b/tools/nitpick/src/TestRunnerMobile.cpp @@ -13,6 +13,9 @@ #include #include #include +#if defined Q_OS_WIN || defined Q_OS_MAC +#include +#endif #include "Nitpick.h" extern Nitpick* nitpick; @@ -104,15 +107,15 @@ void TestRunnerMobile::connectDevice() { if (line2.contains("unauthorized")) { QMessageBox::critical(0, "Unauthorized device detected", "Please allow USB debugging on device"); } else if (line2.contains(DEVICE)) { - // Make sure only 1 device + // Make sure only 1 device QString line3 = devicesFile.readLine(); if (line3.contains(DEVICE)) { QMessageBox::critical(0, "Too many devices detected", "Tests will run only if a single device is attached"); } else { // Line looks like this: 988a1b47335239434b device product:dream2qlteue model:SM_G955U1 device:dream2qlteue transport_id:2 - QStringList tokens = line2.split(QRegExp("[\r\n\t ]+")); + QStringList tokens = line2.split(QRegularExpression("[\r\n\t ]+")); QString deviceID = tokens[0]; - + // Find the model entry _modelName = "UNKNOWN"; for (int i = 0; i < tokens.size(); ++i) { From ebf228ee1b61d40d9285d3d299d8936920c70a65 Mon Sep 17 00:00:00 2001 From: Karol Suprynowicz Date: Sat, 8 Nov 2025 16:24:21 +0100 Subject: [PATCH 088/111] Fixed mouse movement issue on Qt6 --- interface/resources/qml/hifi/Desktop.qml | 5 +++-- interface/src/Application_Events.cpp | 1 + .../display-plugins/src/display-plugins/CompositorHelper.h | 1 + scripts/system/controllers/controllerModules/mouseHMD.js | 1 + 4 files changed, 6 insertions(+), 2 deletions(-) diff --git a/interface/resources/qml/hifi/Desktop.qml b/interface/resources/qml/hifi/Desktop.qml index 6a5ab5936e8..e3644504e23 100644 --- a/interface/resources/qml/hifi/Desktop.qml +++ b/interface/resources/qml/hifi/Desktop.qml @@ -17,7 +17,8 @@ OriginalDesktop.Desktop { property alias toolbarObjectName: sysToolbar.objectName - MouseArea { + // QT6TODO: this breaks VR mouse cursor since + /*MouseArea { id: hoverWatch anchors.fill: parent hoverEnabled: true @@ -26,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" diff --git a/interface/src/Application_Events.cpp b/interface/src/Application_Events.cpp index 4a09e3bf4b7..acce6226026 100644 --- a/interface/src/Application_Events.cpp +++ b/interface/src/Application_Events.cpp @@ -657,6 +657,7 @@ void Application::mouseMoveEvent(QMouseEvent* event) { 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); } diff --git a/libraries/display-plugins/src/display-plugins/CompositorHelper.h b/libraries/display-plugins/src/display-plugins/CompositorHelper.h index c1f3a4dfec3..fcfef875a44 100644 --- a/libraries/display-plugins/src/display-plugins/CompositorHelper.h +++ b/libraries/display-plugins/src/display-plugins/CompositorHelper.h @@ -258,6 +258,7 @@ class ReticleInterface : public QObject { * @returns {boolean} true if the mouse cursor is pointing at UI in the Interface window in desktop mode or on * the HUD surface in HMD mode, false if it isn't. */ + // QT6TODO: Reticle.isPointingAtSystemOverlay does not work currently Q_INVOKABLE bool isPointingAtSystemOverlay() { return !_compositor->getReticleOverDesktop(); } /*@jsdoc diff --git a/scripts/system/controllers/controllerModules/mouseHMD.js b/scripts/system/controllers/controllerModules/mouseHMD.js index 0eccec01a6b..cb8aefb5ab5 100644 --- a/scripts/system/controllers/controllerModules/mouseHMD.js +++ b/scripts/system/controllers/controllerModules/mouseHMD.js @@ -59,6 +59,7 @@ }; this.adjustReticleDepth = function(controllerData) { + // QT6TODO: Reticle.isPointingAtSystemOverlay does not work currently if (Reticle.isPointingAtSystemOverlay(Reticle.position)) { var reticlePositionOnHUD = HMD.worldPointFromOverlay(Reticle.position); Reticle.depth = Vec3.distance(reticlePositionOnHUD, HMD.position); From c5d4fc6bf542c15f35883c3ae5708cfc93481d1c Mon Sep 17 00:00:00 2001 From: Ada Date: Fri, 14 Nov 2025 05:32:58 +1000 Subject: [PATCH 089/111] Fix mouse and touches on Web entities --- .../src/RenderableWebEntityItem.cpp | 10 ++--- libraries/ui/CMakeLists.txt | 4 +- libraries/ui/src/ui/OffscreenQmlSurface.cpp | 44 ++++++++++--------- 3 files changed, 30 insertions(+), 28 deletions(-) diff --git a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp index dd281c93ce2..e21bfda00ae 100644 --- a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp @@ -140,9 +140,9 @@ WebEntityRenderer::WebEntityRenderer(const EntityItemPointer& entity) : Parent(e std::call_once(once, [&]{ CUSTOM_PIPELINE_NUMBER = render::ShapePipeline::registerCustomShapePipelineFactory(webPipelineFactory); _touchDevice = std::make_shared("OffscreenUiTouchDevice", 0, - QInputDevice::DeviceType::TouchScreen, - QPointingDevice::PointerType::Pen, - QInputDevice::Capability::Position, + QInputDevice::DeviceType::AllDevices, + QPointingDevice::PointerType::AllPointerTypes, + QInputDevice::Capability::All, 4, //maxPoints 2 // buttonCount ); @@ -555,10 +555,10 @@ void WebEntityRenderer::handlePointerEventAsMouse(const PointerEvent& event) { if (type == QEvent::Wheel) { const auto& scroll = event.getScroll() * POINTEREVENT_SCROLL_SENSITIVITY; - QWheelEvent wheelEvent(windowPoint, windowPoint, QPoint(), QPoint(scroll.x, scroll.y), buttons, event.getKeyboardModifiers(), Qt::ScrollPhase::NoScrollPhase, false); + QWheelEvent wheelEvent(windowPoint, windowPoint, QPoint(), QPoint(scroll.x, scroll.y), buttons, event.getKeyboardModifiers(), Qt::ScrollPhase::NoScrollPhase, false, Qt::MouseEventSynthesizedByApplication, _touchDevice.get()); QCoreApplication::sendEvent(_webSurface->getWindow(), &wheelEvent); } else { - QMouseEvent mouseEvent(type, windowPoint, windowPoint, windowPoint, button, buttons, event.getKeyboardModifiers()); + QMouseEvent mouseEvent(type, windowPoint, windowPoint, windowPoint, button, buttons, event.getKeyboardModifiers(), _touchDevice.get()); QCoreApplication::sendEvent(_webSurface->getWindow(), &mouseEvent); } } diff --git a/libraries/ui/CMakeLists.txt b/libraries/ui/CMakeLists.txt index a7e8eb1cf8e..ab8975e0e84 100644 --- a/libraries/ui/CMakeLists.txt +++ b/libraries/ui/CMakeLists.txt @@ -1,9 +1,9 @@ # Copyright 2013-2018, High Fidelity, Inc. -# Copyright 2020-2023 Overte e.V. +# Copyright 2020-2025 Overte e.V. # SPDX-License-Identifier: Apache-2.0 set(TARGET_NAME ui) -setup_hifi_library(OpenGL Multimedia Network Qml Quick WebChannel WebSockets Xml WebEngineQuick ${PLATFORM_QT_COMPONENTS}) +setup_hifi_library(OpenGL Multimedia Network Qml Quick WebChannel WebSockets Xml WebEngineQuick GuiPrivate ${PLATFORM_QT_COMPONENTS}) link_hifi_libraries(shared networking qml gl audio audio-client plugins pointers script-engine) include_hifi_library_headers(controllers) diff --git a/libraries/ui/src/ui/OffscreenQmlSurface.cpp b/libraries/ui/src/ui/OffscreenQmlSurface.cpp index c9e1a60859d..0358f6061ca 100644 --- a/libraries/ui/src/ui/OffscreenQmlSurface.cpp +++ b/libraries/ui/src/ui/OffscreenQmlSurface.cpp @@ -34,6 +34,7 @@ //#include #include #include +#include #include #include #include @@ -503,7 +504,6 @@ bool OffscreenQmlSurface::handlePointerEvent(const PointerEvent& event, class QP state = QEventPoint::State::Released; } else if (_activeTouchPoints.count(event.getID()) && windowPoint != _activeTouchPoints[event.getID()].touchPoint.position()) { state = QEventPoint::State::Updated; - // QT6TODO: is Updated equivalent to TouchPointMoved?; } // Remove the touch point if: @@ -522,12 +522,19 @@ bool OffscreenQmlSurface::handlePointerEvent(const PointerEvent& event, class QP } { - // QT6TODO: I'm not sure about positions - QTouchEvent::TouchPoint point(event.getID(), state, windowPoint, windowPoint); - //point.setId(event.getID()); - //point.setState(state); - //point.setPos(windowPoint); - //point.setScreenPos(windowPoint); + // Synthetic touch events aren't documented anywhere, + // and I don't think Qt really expects you to create them. + // + // qtbase/tests/auto/widgets/util/qscroller/tst_qscroller.cpp + // tst_QScroller::kineticScroll + QTouchEvent::TouchPoint point(0); + QMutableEventPoint::setState(point, QEventPoint::State::Pressed); + QMutableEventPoint::setPosition(point, windowPoint); + QMutableEventPoint::setScenePosition(point, windowPoint); + QMutableEventPoint::setGlobalPosition(point, windowPoint); + QMutableEventPoint::setId(point, event.getID()); + QMutableEventPoint::setState(point, state); + _activeTouchPoints[event.getID()].touchPoint = point; if (state == QEventPoint::State::Pressed) { _activeTouchPoints[event.getID()].pressed = true; @@ -536,20 +543,15 @@ bool OffscreenQmlSurface::handlePointerEvent(const PointerEvent& event, class QP } } - QList touchPoints; - //Qt::TouchPointStates touchPointStates; - for (const auto& entry : _activeTouchPoints) { - //touchPointStates |= entry.second.touchPoint.state(); - touchPoints.push_back(entry.second.touchPoint); - } + QList touchPoints; + for (const auto& entry : _activeTouchPoints) { + touchPoints.push_back(entry.second.touchPoint); + } - QTouchEvent touchEvent(touchType, &device, event.getKeyboardModifiers(), touchPoints); + QTouchEvent touchEvent(touchType, &device, event.getKeyboardModifiers(), touchPoints); - // QT6TODO: I'm not sure about what to do about this, there doesn't seem to be a way to set window and target - //touchEvent.setWindow(getWindow()); - //ouchEvent.setTarget(getRootItem()); - touchEvent.setTimestamp((ulong)QDateTime::currentMSecsSinceEpoch()); - touchEvent.ignore(); + touchEvent.setTimestamp((ulong)QDateTime::currentMSecsSinceEpoch()); + touchEvent.ignore(); // Send mouse events to the surface so that HTML dialog elements work with mouse press and hover. // @@ -569,7 +571,7 @@ bool OffscreenQmlSurface::handlePointerEvent(const PointerEvent& event, class QP if (event.getType() == PointerEvent::Move) { QMouseEvent mouseEvent(QEvent::MouseMove, windowPoint, windowPoint, windowPoint, button, buttons, - event.getKeyboardModifiers()); + event.getKeyboardModifiers(), &device); // TODO - this line necessary for the QML Tooltop to work (which is not currently being used), but it causes interface to crash on launch on a fresh install // need to investigate into why this crash is happening. //_qmlContext->setContextProperty("lastMousePosition", windowPoint); @@ -595,7 +597,7 @@ bool OffscreenQmlSurface::handlePointerEvent(const PointerEvent& event, class QP if (event.getType() == PointerEvent::Scroll) { auto scroll = event.getScroll() * POINTEREVENT_SCROLL_SENSITIVITY; - QWheelEvent wheelEvent(windowPoint, windowPoint, QPoint(), QPoint(scroll.x, scroll.y), buttons, event.getKeyboardModifiers(), Qt::ScrollPhase::NoScrollPhase, false); + QWheelEvent wheelEvent(windowPoint, windowPoint, QPoint(), QPoint(scroll.x, scroll.y), buttons, event.getKeyboardModifiers(), Qt::ScrollPhase::NoScrollPhase, false, Qt::MouseEventSynthesizedByApplication, &device); if (QCoreApplication::sendEvent(getWindow(), &wheelEvent)) { eventSent = true; eventsAccepted &= wheelEvent.isAccepted(); From 59d094b0bfd90d71b577bb513eccc145e24a89a5 Mon Sep 17 00:00:00 2001 From: Ada Date: Fri, 14 Nov 2025 06:13:30 +1000 Subject: [PATCH 090/111] Use virtual pointer device on QML desktop This won't change anything on desktop, but the VR desktop should work properly now --- interface/src/Application.cpp | 9 ++++++++ interface/src/Application.h | 4 ++++ interface/src/Application_Events.cpp | 23 +++++++++++++++---- interface/src/Application_Setup.cpp | 4 ++-- .../src/RenderableWebEntityItem.cpp | 15 +++++++----- libraries/qml/src/qml/OffscreenSurface.cpp | 22 +++++++++++++++--- libraries/ui/src/OffscreenUi.cpp | 2 +- 7 files changed, 62 insertions(+), 17 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index abdde2bd000..7216b8fb2ff 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -236,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), diff --git a/interface/src/Application.h b/interface/src/Application.h index 84125d27a20..3fdd259924e 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -180,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); @@ -793,6 +795,8 @@ private slots: bool _cursorNeedsChanging { false }; bool _useSystemCursor { false }; + std::shared_ptr _virtualPointingDevice; + DialogsManagerScriptingInterface* _dialogsManagerScriptingInterface; QPointer _logDialog; diff --git a/interface/src/Application_Events.cpp b/interface/src/Application_Events.cpp index acce6226026..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; } @@ -653,7 +658,8 @@ void Application::mouseMoveEvent(QMouseEvent* event) { QMouseEvent mappedEvent(event->type(), transformedPos, event->globalPosition(), button, - buttons, event->modifiers()); + buttons, event->modifiers(), + getVirtualPointingDevice().get()); if (compositor.getReticleVisible() || !isHMDMode() || !compositor.getReticleOverDesktop() || getOverlays().getOverlayAtPoint(glm::vec2(transformedPos.x(), transformedPos.y())) != UNKNOWN_ENTITY_ID) { @@ -688,7 +694,12 @@ void Application::mousePressEvent(QMouseEvent* event) { QPointF transformedPos; #endif - QMouseEvent mappedEvent(event->type(), transformedPos, event->globalPosition(), 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); @@ -728,7 +739,8 @@ void Application::mouseDoublePressEvent(QMouseEvent* event) { QMouseEvent mappedEvent(event->type(), transformedPos, event->globalPosition(), event->button(), - event->buttons(), event->modifiers()); + event->buttons(), event->modifiers(), + getVirtualPointingDevice().get()); getEntities()->mouseDoublePressEvent(&mappedEvent); // if one of our scripts have asked to capture this event, then stop processing it @@ -750,7 +762,8 @@ void Application::mouseReleaseEvent(QMouseEvent* event) { QMouseEvent mappedEvent(event->type(), transformedPos, event->globalPosition(), event->button(), - event->buttons(), event->modifiers()); + event->buttons(), event->modifiers(), + getVirtualPointingDevice().get()); getEntities()->mouseReleaseEvent(&mappedEvent); diff --git a/interface/src/Application_Setup.cpp b/interface/src/Application_Setup.cpp index 0d800fff4a3..f85ddb42a9e 100644 --- a/interface/src/Application_Setup.cpp +++ b/interface/src/Application_Setup.cpp @@ -2029,11 +2029,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/libraries/entities-renderer/src/RenderableWebEntityItem.cpp b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp index e21bfda00ae..8e87b17c743 100644 --- a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp @@ -59,6 +59,7 @@ static uint8_t YOUTUBE_MAX_FPS = 30; static std::atomic _currentWebCount(0); static const uint32_t MAX_CONCURRENT_WEB_VIEWS = 20; +// QT6TODO: Use one shared virtual pointing device, where do we put it though? static std::shared_ptr _touchDevice; static uint8_t CUSTOM_PIPELINE_NUMBER; @@ -139,12 +140,14 @@ WebEntityRenderer::WebEntityRenderer(const EntityItemPointer& entity) : Parent(e static std::once_flag once; std::call_once(once, [&]{ CUSTOM_PIPELINE_NUMBER = render::ShapePipeline::registerCustomShapePipelineFactory(webPipelineFactory); - _touchDevice = std::make_shared("OffscreenUiTouchDevice", 0, - QInputDevice::DeviceType::AllDevices, - QPointingDevice::PointerType::AllPointerTypes, - QInputDevice::Capability::All, - 4, //maxPoints - 2 // buttonCount + _touchDevice = std::make_shared( + "WebEntityPointingDevice", + 1, + QInputDevice::DeviceType::AllDevices, + QPointingDevice::PointerType::AllPointerTypes, + QInputDevice::Capability::All, + 4, //maxPoints + 2 // buttonCount ); }); _geometryId = DependencyManager::get()->allocateID(); diff --git a/libraries/qml/src/qml/OffscreenSurface.cpp b/libraries/qml/src/qml/OffscreenSurface.cpp index 99c8768136f..b493869e764 100644 --- a/libraries/qml/src/qml/OffscreenSurface.cpp +++ b/libraries/qml/src/qml/OffscreenSurface.cpp @@ -40,6 +40,9 @@ using namespace hifi::qml; using namespace hifi::qml::impl; +// QT6TODO: Use one shared virtual pointing device, where do we put it though? +static std::shared_ptr _touchDevice; + QmlUrlValidator OffscreenSurface::validator = [](const QUrl& url) -> bool { if (url.isRelative()) { return true; @@ -103,6 +106,18 @@ std::function OffscreenSurface::getDiscardLambda() { OffscreenSurface::OffscreenSurface() : _sharedObject(new impl::SharedObject()) { + static std::once_flag once; + std::call_once(once, [&] { + _touchDevice = std::make_shared( + "OffscreenSurfacePointingDevice", + 1, + QInputDevice::DeviceType::AllDevices, + QPointingDevice::PointerType::AllPointerTypes, + QInputDevice::Capability::All, + 4, //maxPoints + 2 // buttonCount + ); + }); } OffscreenSurface::~OffscreenSurface() { @@ -183,7 +198,7 @@ bool OffscreenSurface::eventFilter(QObject* originalDestination, QEvent* event) QWheelEvent mappedEvent(transformedPos, wheelEvent->globalPosition(), wheelEvent->pixelDelta(), wheelEvent->angleDelta(), wheelEvent->buttons(), wheelEvent->modifiers(), wheelEvent->phase(), - wheelEvent->inverted(), wheelEvent->source()); + wheelEvent->inverted(), wheelEvent->source(), _touchDevice.get()); mappedEvent.ignore(); if (QCoreApplication::sendEvent(_sharedObject->getWindow(), &mappedEvent)) { @@ -195,7 +210,8 @@ bool OffscreenSurface::eventFilter(QObject* originalDestination, QEvent* event) QMouseEvent* mouseEvent = static_cast(event); QPointF transformedPos = mapToVirtualScreen(mouseEvent->position()); QMouseEvent mappedEvent(mouseEvent->type(), transformedPos, mouseEvent->globalPosition(), mouseEvent->button(), - mouseEvent->buttons(), mouseEvent->modifiers()); + mouseEvent->buttons(), mouseEvent->modifiers(), + _touchDevice.get()); mappedEvent.ignore(); if (QCoreApplication::sendEvent(_sharedObject->getWindow(), &mappedEvent)) { return mappedEvent.isAccepted(); @@ -228,7 +244,7 @@ bool OffscreenSurface::eventFilter(QObject* originalDestination, QEvent* event) Q_UNREACHABLE(); } // Same case as OffscreenUi.cpp::eventFilter: touch events are always being accepted so we now use mouse events and consider one touch, touchPoints()[0]. - QMouseEvent fakeMouseEvent(fakeMouseEventType, originalEvent->touchPoints()[0].pos(), fakeMouseButton, fakeMouseButtons, Qt::NoModifier); + QMouseEvent fakeMouseEvent(fakeMouseEventType, originalEvent->touchPoints()[0].pos(), fakeMouseButton, fakeMouseButtons, Qt::NoModifier, _touchDevice.get()); fakeMouseEvent.ignore(); if (QCoreApplication::sendEvent(_sharedObject->getWindow(), &fakeMouseEvent)) { /*qInfo() << __FUNCTION__ << "sent fake touch event:" << fakeMouseEvent.type() diff --git a/libraries/ui/src/OffscreenUi.cpp b/libraries/ui/src/OffscreenUi.cpp index 31a180da569..3c5540a33e5 100644 --- a/libraries/ui/src/OffscreenUi.cpp +++ b/libraries/ui/src/OffscreenUi.cpp @@ -1144,7 +1144,7 @@ bool OffscreenUi::eventFilter(QObject* originalDestination, QEvent* event) { QPointF transformedPos = mapToVirtualScreen(mouseEvent->position()); // FIXME: touch events are always being accepted. Use mouse events on the OffScreenUi for now, and investigate properly switching to touch events // (using handlePointerEvent) later - QMouseEvent mappedEvent(mouseEvent->type(), transformedPos, mouseEvent->globalPosition(), mouseEvent->button(), mouseEvent->buttons(), mouseEvent->modifiers()); + QMouseEvent mappedEvent(mouseEvent->type(), transformedPos, mouseEvent->globalPosition(), mouseEvent->button(), mouseEvent->buttons(), mouseEvent->modifiers(), _touchDevice.get()); mappedEvent.ignore(); if (QCoreApplication::sendEvent(getWindow(), &mappedEvent)) { return mappedEvent.isAccepted(); From ef710c104765a11a0b565562f02feaf9e43bbe84 Mon Sep 17 00:00:00 2001 From: Karol Suprynowicz Date: Sat, 15 Nov 2025 17:03:54 +0100 Subject: [PATCH 091/111] Fix timestamps for offscreen UI --- libraries/ui/src/OffscreenUi.cpp | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/libraries/ui/src/OffscreenUi.cpp b/libraries/ui/src/OffscreenUi.cpp index 3c5540a33e5..97c85f6a8b0 100644 --- a/libraries/ui/src/OffscreenUi.cpp +++ b/libraries/ui/src/OffscreenUi.cpp @@ -30,6 +30,8 @@ #include "ui/ToolbarScriptingInterface.h" #include +#include + #include "MainWindow.h" /*@jsdoc @@ -124,13 +126,15 @@ static std::shared_ptr _touchDevice; OffscreenUi::OffscreenUi() { static std::once_flag once; std::call_once(once, [&] { - _touchDevice = std::make_shared("OffscreenUiTouchDevice", 0, - QInputDevice::DeviceType::TouchScreen, - QPointingDevice::PointerType::Pen, - QInputDevice::Capability::Position, + _touchDevice = std::make_shared( + "OffscreenUiTouchDevice", + 1, + QInputDevice::DeviceType::AllDevices, + QPointingDevice::PointerType::AllPointerTypes, + QInputDevice::Capability::All, 4, //maxPoints 2 // buttonCount - ); + ); }); auto pointerManager = DependencyManager::get(); @@ -1146,7 +1150,17 @@ bool OffscreenUi::eventFilter(QObject* originalDestination, QEvent* event) { // (using handlePointerEvent) later QMouseEvent mappedEvent(mouseEvent->type(), transformedPos, mouseEvent->globalPosition(), mouseEvent->button(), mouseEvent->buttons(), mouseEvent->modifiers(), _touchDevice.get()); mappedEvent.ignore(); + mappedEvent.setTimestamp(mouseEvent->timestamp()); + QMutableEventPoint::setPosition(mappedEvent.point(0), transformedPos); + QMutableEventPoint::setScenePosition(mappedEvent.point(0), transformedPos); if (QCoreApplication::sendEvent(getWindow(), &mappedEvent)) { + + // QT6TODO: I added this as a fix for Qt6 but is it needed? + // Mouse release events are always accepted for some reason, so a workaround is needed. + //if (event->type() == QEvent::MouseButtonRelease) { + // return false; + //} + return mappedEvent.isAccepted(); } break; From 001ade696e2b9251f0e000c03c9483ae2090affb Mon Sep 17 00:00:00 2001 From: Karol Suprynowicz Date: Sat, 15 Nov 2025 17:51:12 +0100 Subject: [PATCH 092/111] Qt6 input events debugging and fixes --- libraries/qml/src/qml/OffscreenSurface.cpp | 6 +++-- libraries/qml/src/qml/impl/SharedObject.cpp | 26 +++++++++++++++++++++ libraries/qml/src/qml/impl/SharedObject.h | 25 ++++++++++++++++++++ libraries/ui/src/ui/OffscreenQmlSurface.cpp | 7 +++--- 4 files changed, 59 insertions(+), 5 deletions(-) diff --git a/libraries/qml/src/qml/OffscreenSurface.cpp b/libraries/qml/src/qml/OffscreenSurface.cpp index b493869e764..a3a6ed5788f 100644 --- a/libraries/qml/src/qml/OffscreenSurface.cpp +++ b/libraries/qml/src/qml/OffscreenSurface.cpp @@ -206,7 +206,8 @@ bool OffscreenSurface::eventFilter(QObject* originalDestination, QEvent* event) } break; } - case QEvent::MouseMove: { + // QT6TODO: this may be not necessary, mouse move events were doubled with it and without it nothing seems to break. + /*case QEvent::MouseMove: { QMouseEvent* mouseEvent = static_cast(event); QPointF transformedPos = mapToVirtualScreen(mouseEvent->position()); QMouseEvent mappedEvent(mouseEvent->type(), transformedPos, mouseEvent->globalPosition(), mouseEvent->button(), @@ -217,9 +218,10 @@ bool OffscreenSurface::eventFilter(QObject* originalDestination, QEvent* event) return mappedEvent.isAccepted(); } break; - } + }*/ #if defined(Q_OS_ANDROID) + // QT6TODO: Cases above needed to be deleted for Qt6 due to doubled events, so maybe these are not needed too? case QEvent::TouchBegin: case QEvent::TouchUpdate: case QEvent::TouchEnd: { diff --git a/libraries/qml/src/qml/impl/SharedObject.cpp b/libraries/qml/src/qml/impl/SharedObject.cpp index ec23d3fad6a..362fbeaaf8d 100644 --- a/libraries/qml/src/qml/impl/SharedObject.cpp +++ b/libraries/qml/src/qml/impl/SharedObject.cpp @@ -119,6 +119,10 @@ SharedObject::~SharedObject() { #ifndef DISABLE_QML if (_quickWindow) { +#ifdef ENABLE_SHARED_OBJECT_EVENT_DEBUG + // Remove event filter that was installed if event debugging is enabled. + _quickWindow->removeEventFilter(&_eventDebugFilter); +#endif _quickWindow->destroy(); delete _quickWindow; _quickWindow = nullptr; @@ -451,6 +455,11 @@ void SharedObject::wake() { void SharedObject::onInitialize() { #ifndef DISABLE_QML +#ifdef ENABLE_SHARED_OBJECT_EVENT_DEBUG + // Install event filter if event debugging is enabled. + _quickWindow->installEventFilter(&_eventDebugFilter); +#endif + // Associate root item with the window. _rootItem->setParentItem(_quickWindow->contentItem()); _renderControl->prepareThread(_renderThread); @@ -547,3 +556,20 @@ void SharedObject::resume() { bool SharedObject::isPaused() const { return _paused; } + +#ifdef ENABLE_SHARED_OBJECT_EVENT_DEBUG +bool SharedObjectEventDebug::eventFilter(QObject *object, QEvent *event) { + QMouseEvent *mouseEvent = dynamic_cast(event); + if (mouseEvent) { + /*if (mouseEvent->device()->name() == "WebEntityPointingDevice") { + qDebug() << "SharedObjectEventDebug QMouseEevent: " << mouseEvent << mouseEvent->buttons(); + }*/ + qDebug() << "SharedObjectEventDebug QMouseEevent: " << mouseEvent << mouseEvent->buttons(); + } + QTouchEvent *touchEvent = dynamic_cast(event); + if (touchEvent) { + qDebug() << "SharedObjectEventDebug QTouchEvent: " << touchEvent; + } + return false; +}; +#endif diff --git a/libraries/qml/src/qml/impl/SharedObject.h b/libraries/qml/src/qml/impl/SharedObject.h index 3c187f1707a..90178e5eb22 100644 --- a/libraries/qml/src/qml/impl/SharedObject.h +++ b/libraries/qml/src/qml/impl/SharedObject.h @@ -32,6 +32,27 @@ namespace impl { class RenderControl; class RenderEventHandler; +// Uncomment to show events received by the shared object. +//#define ENABLE_SHARED_OBJECT_EVENT_DEBUG + +/** + * @brief A class used for debugging input events being sent to shared object. + * + **/ + +#ifdef ENABLE_SHARED_OBJECT_EVENT_DEBUG +class SharedObjectEventDebug : public QObject { + Q_OBJECT +public: + bool eventFilter(QObject *object, QEvent *event); +}; +#endif + +/** + * @brief A class containing virtual Qt window for webentiies and in-game overlay. + * + **/ + class SharedObject : public QObject { Q_OBJECT @@ -100,6 +121,10 @@ class SharedObject : public QObject { mutable QMutex _mutex; QWaitCondition _cond; +#ifdef ENABLE_SHARED_OBJECT_EVENT_DEBUG + SharedObjectEventDebug _eventDebugFilter; +#endif + #ifndef DISABLE_QML QWindow* _proxyWindow { nullptr }; RenderControl* _renderControl { nullptr }; diff --git a/libraries/ui/src/ui/OffscreenQmlSurface.cpp b/libraries/ui/src/ui/OffscreenQmlSurface.cpp index 0358f6061ca..11f1d5aab61 100644 --- a/libraries/ui/src/ui/OffscreenQmlSurface.cpp +++ b/libraries/ui/src/ui/OffscreenQmlSurface.cpp @@ -259,7 +259,7 @@ void OffscreenQmlSurface::initializeEngine(QQmlEngine* engine) { fileSelector->setExtraSelectors(FileUtils::getFileSelectors()); static std::once_flag once; - std::call_once(once, [] { + std::call_once(once, [] { qRegisterMetaType(); qRegisterMetaType(); qmlRegisterType("Hifi", 1, 0, "SoundEffect"); @@ -569,7 +569,8 @@ bool OffscreenQmlSurface::handlePointerEvent(const PointerEvent& event, class QP bool eventSent = false; bool eventsAccepted = true; - if (event.getType() == PointerEvent::Move) { + // QT6TODO: I think this is not needed? + /*if (event.getType() == PointerEvent::Move) { QMouseEvent mouseEvent(QEvent::MouseMove, windowPoint, windowPoint, windowPoint, button, buttons, event.getKeyboardModifiers(), &device); // TODO - this line necessary for the QML Tooltop to work (which is not currently being used), but it causes interface to crash on launch on a fresh install @@ -580,7 +581,7 @@ bool OffscreenQmlSurface::handlePointerEvent(const PointerEvent& event, class QP eventSent = true; eventsAccepted &= mouseEvent.isAccepted(); } - } + }*/ if (touchType == QEvent::TouchBegin) { _touchBeginAccepted = QCoreApplication::sendEvent(getWindow(), &touchEvent); From b2de7d2a1ff75707981095d9a03c307490eb9e5e Mon Sep 17 00:00:00 2001 From: Karol Suprynowicz Date: Sun, 16 Nov 2025 19:10:08 +0100 Subject: [PATCH 093/111] Fix MOC issue with pointer scripting on Qt6 --- interface/src/raypick/PointerScriptingInterface.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/src/raypick/PointerScriptingInterface.h b/interface/src/raypick/PointerScriptingInterface.h index b45bd46dcff..38d686bb91e 100644 --- a/interface/src/raypick/PointerScriptingInterface.h +++ b/interface/src/raypick/PointerScriptingInterface.h @@ -421,7 +421,7 @@ class PointerScriptingInterface : public QObject, public Dependency { * @param {Mat4} [offset] - The offset of the target point from the center of the target item. If not specified, the * pointer locks on to the center of the target item. */ - Q_INVOKABLE void setLockEndUUID(unsigned int uid, const QUuid& objectID, bool isAvatar, const glm::mat4& offsetMat = glm::mat4()) const { DependencyManager::get()->setLockEndUUID(uid, objectID, isAvatar, offsetMat); } + Q_INVOKABLE void setLockEndUUID(unsigned int uid, const QUuid& objectID, bool isAvatar, const glm::mat<4,4,float,glm::packed_highp>& offsetMat = glm::mat4()) const { DependencyManager::get()->setLockEndUUID(uid, objectID, isAvatar, offsetMat); } /*@jsdoc * Sets the delay of a Ray pointer. From ef188e7df2fb10771661586ed9364339307e8c1b Mon Sep 17 00:00:00 2001 From: Karol Suprynowicz Date: Sat, 29 Nov 2025 23:43:30 +0100 Subject: [PATCH 094/111] Fix web entity inputs --- interface/resources/qml/overte/WidgetZoo.qml | 2 +- .../src/RenderableWebEntityItem.cpp | 30 +++++++++++++++---- .../entities/src/EntityItemPropertiesMacros.h | 2 +- libraries/qml/src/qml/impl/SharedObject.cpp | 6 ++-- libraries/ui/src/OffscreenUi.cpp | 13 +++++++- libraries/ui/src/ui/OffscreenQmlSurface.cpp | 5 ++++ 6 files changed, 46 insertions(+), 12 deletions(-) diff --git a/interface/resources/qml/overte/WidgetZoo.qml b/interface/resources/qml/overte/WidgetZoo.qml index 5d8ff0cbd73..3606633cee3 100644 --- a/interface/resources/qml/overte/WidgetZoo.qml +++ b/interface/resources/qml/overte/WidgetZoo.qml @@ -6,7 +6,7 @@ import QtQuick.Controls import "." as Overte // debugging test case to view the themed widgets -Window { +Item { id: root width: 480 height: 720 diff --git a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp index 8e87b17c743..c3330b7078e 100644 --- a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp @@ -33,6 +33,7 @@ #include "EntitiesRendererLogging.h" #include #include +#include using namespace render; using namespace render::entities; @@ -61,6 +62,7 @@ static const uint32_t MAX_CONCURRENT_WEB_VIEWS = 20; // QT6TODO: Use one shared virtual pointing device, where do we put it though? static std::shared_ptr _touchDevice; +static std::shared_ptr _mouseDevice; static uint8_t CUSTOM_PIPELINE_NUMBER; // transparent, forward, shadow, fade @@ -141,12 +143,21 @@ WebEntityRenderer::WebEntityRenderer(const EntityItemPointer& entity) : Parent(e std::call_once(once, [&]{ CUSTOM_PIPELINE_NUMBER = render::ShapePipeline::registerCustomShapePipelineFactory(webPipelineFactory); _touchDevice = std::make_shared( - "WebEntityPointingDevice", - 1, - QInputDevice::DeviceType::AllDevices, + "WebEntityTouchDevice", + 1002, + QInputDevice::DeviceType::TouchScreen, // QT6TODO: test if touchscreen or stylus works better. QPointingDevice::PointerType::AllPointerTypes, QInputDevice::Capability::All, - 4, //maxPoints + 1, //maxPoints + 2 // buttonCount + ); + _mouseDevice = std::make_shared( + "WebEntityMouseDevice", + 1003, + QInputDevice::DeviceType::Mouse, + QPointingDevice::PointerType::Cursor, + QInputDevice::Capability::All, + 1, //maxPoints 2 // buttonCount ); }); @@ -558,10 +569,17 @@ void WebEntityRenderer::handlePointerEventAsMouse(const PointerEvent& event) { if (type == QEvent::Wheel) { const auto& scroll = event.getScroll() * POINTEREVENT_SCROLL_SENSITIVITY; - QWheelEvent wheelEvent(windowPoint, windowPoint, QPoint(), QPoint(scroll.x, scroll.y), buttons, event.getKeyboardModifiers(), Qt::ScrollPhase::NoScrollPhase, false, Qt::MouseEventSynthesizedByApplication, _touchDevice.get()); + QWheelEvent wheelEvent(windowPoint, windowPoint, QPoint(), QPoint(scroll.x, scroll.y), buttons, event.getKeyboardModifiers(), Qt::ScrollPhase::NoScrollPhase, false, Qt::MouseEventSynthesizedByApplication, _mouseDevice.get()); + wheelEvent.setTimestamp((ulong)QDateTime::currentMSecsSinceEpoch()); + QMutableEventPoint::setTimestamp(wheelEvent.point(0), wheelEvent.timestamp()); QCoreApplication::sendEvent(_webSurface->getWindow(), &wheelEvent); } else { - QMouseEvent mouseEvent(type, windowPoint, windowPoint, windowPoint, button, buttons, event.getKeyboardModifiers(), _touchDevice.get()); + QMouseEvent mouseEvent(type, windowPoint, windowPoint, windowPoint, button, buttons, event.getKeyboardModifiers(), _mouseDevice.get()); + //QMouseEvent mouseEvent(type, QPointF(), QPointF(), QPointF(), button, buttons, event.getKeyboardModifiers(), _mouseDevice.get()); + mouseEvent.setTimestamp((ulong)QDateTime::currentMSecsSinceEpoch()); + QMutableEventPoint::setTimestamp(mouseEvent.point(0), mouseEvent.timestamp()); + //QMutableEventPoint::setPosition(mouseEvent.point(0), windowPoint); + //QMutableEventPoint::setScenePosition(mouseEvent.point(0), windowPoint); QCoreApplication::sendEvent(_webSurface->getWindow(), &mouseEvent); } } diff --git a/libraries/entities/src/EntityItemPropertiesMacros.h b/libraries/entities/src/EntityItemPropertiesMacros.h index 546b6ce647a..afc5c15d87f 100644 --- a/libraries/entities/src/EntityItemPropertiesMacros.h +++ b/libraries/entities/src/EntityItemPropertiesMacros.h @@ -288,7 +288,7 @@ typedef QVector qVectorFloat; typedef QVector qVectorQUuid; typedef QVector qVectorQString; typedef QSet qSetQString; -inline QDebug &operator<<(QDebug& debug, const qSetQString& stringSet) { for (const auto item: stringSet) { debug << item; } return debug; } +inline QDebug &operator<<(QDebug& debug, const qSetQString& stringSet) { for (const auto &item: stringSet) { debug << item; } return debug; } inline float float_convertFromScriptValue(const ScriptValue& v, bool& isValid) { return v.toVariant().toFloat(&isValid); } inline quint64 quint64_convertFromScriptValue(const ScriptValue& v, bool& isValid) { return v.toVariant().toULongLong(&isValid); } inline quint32 quint32_convertFromScriptValue(const ScriptValue& v, bool& isValid) { diff --git a/libraries/qml/src/qml/impl/SharedObject.cpp b/libraries/qml/src/qml/impl/SharedObject.cpp index 362fbeaaf8d..202e47f3759 100644 --- a/libraries/qml/src/qml/impl/SharedObject.cpp +++ b/libraries/qml/src/qml/impl/SharedObject.cpp @@ -561,10 +561,10 @@ bool SharedObject::isPaused() const { bool SharedObjectEventDebug::eventFilter(QObject *object, QEvent *event) { QMouseEvent *mouseEvent = dynamic_cast(event); if (mouseEvent) { - /*if (mouseEvent->device()->name() == "WebEntityPointingDevice") { + if (mouseEvent->device()->name() == "WebEntityMouseDevice") { qDebug() << "SharedObjectEventDebug QMouseEevent: " << mouseEvent << mouseEvent->buttons(); - }*/ - qDebug() << "SharedObjectEventDebug QMouseEevent: " << mouseEvent << mouseEvent->buttons(); + } + //qDebug() << "SharedObjectEventDebug QMouseEevent: " << mouseEvent << mouseEvent->buttons(); } QTouchEvent *touchEvent = dynamic_cast(event); if (touchEvent) { diff --git a/libraries/ui/src/OffscreenUi.cpp b/libraries/ui/src/OffscreenUi.cpp index 97c85f6a8b0..e9d84bec17b 100644 --- a/libraries/ui/src/OffscreenUi.cpp +++ b/libraries/ui/src/OffscreenUi.cpp @@ -126,7 +126,8 @@ static std::shared_ptr _touchDevice; OffscreenUi::OffscreenUi() { static std::once_flag once; std::call_once(once, [&] { - _touchDevice = std::make_shared( + // QT6TODO: choose the best parameters for this: + /*_touchDevice = std::make_shared( "OffscreenUiTouchDevice", 1, QInputDevice::DeviceType::AllDevices, @@ -134,6 +135,15 @@ OffscreenUi::OffscreenUi() { QInputDevice::Capability::All, 4, //maxPoints 2 // buttonCount + );*/ + _touchDevice = std::make_shared( + "OffscreenUiTouchDevice", + 1, + QInputDevice::DeviceType::Mouse, + QPointingDevice::PointerType::Cursor, + QInputDevice::Capability::None, + 1, //maxPoints + 2 // buttonCount ); }); @@ -1153,6 +1163,7 @@ bool OffscreenUi::eventFilter(QObject* originalDestination, QEvent* event) { mappedEvent.setTimestamp(mouseEvent->timestamp()); QMutableEventPoint::setPosition(mappedEvent.point(0), transformedPos); QMutableEventPoint::setScenePosition(mappedEvent.point(0), transformedPos); + QMutableEventPoint::setTimestamp(mappedEvent.point(0), mouseEvent->timestamp()); if (QCoreApplication::sendEvent(getWindow(), &mappedEvent)) { // QT6TODO: I added this as a fix for Qt6 but is it needed? diff --git a/libraries/ui/src/ui/OffscreenQmlSurface.cpp b/libraries/ui/src/ui/OffscreenQmlSurface.cpp index 11f1d5aab61..f52a187881a 100644 --- a/libraries/ui/src/ui/OffscreenQmlSurface.cpp +++ b/libraries/ui/src/ui/OffscreenQmlSurface.cpp @@ -534,12 +534,17 @@ bool OffscreenQmlSurface::handlePointerEvent(const PointerEvent& event, class QP QMutableEventPoint::setGlobalPosition(point, windowPoint); QMutableEventPoint::setId(point, event.getID()); QMutableEventPoint::setState(point, state); + QMutableEventPoint::setTimestamp(point, (ulong)QDateTime::currentMSecsSinceEpoch()); + QMutableEventPoint::setGlobalLastPosition(point, touchPoint->second.touchPoint.globalPosition()); + QMutableEventPoint::setGlobalPressPosition(point, touchPoint->second.touchPoint.globalPosition()); _activeTouchPoints[event.getID()].touchPoint = point; if (state == QEventPoint::State::Pressed) { _activeTouchPoints[event.getID()].pressed = true; + QMutableEventPoint::setPressure(_activeTouchPoints[event.getID()].touchPoint, 1.0); } else if (state == QEventPoint::State::Released) { _activeTouchPoints[event.getID()].pressed = false; + QMutableEventPoint::setPressure(_activeTouchPoints[event.getID()].touchPoint, 0); } } From cc7f1db98cbacdaf979eb0c7afc3c1be109f24ac Mon Sep 17 00:00:00 2001 From: ksuprynowicz Date: Mon, 5 Jan 2026 23:44:18 +0100 Subject: [PATCH 095/111] Post-rebase fixes --- interface/src/Application_Graphics.cpp | 2 +- tools/gpu-frame-player/src/RenderThread.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/interface/src/Application_Graphics.cpp b/interface/src/Application_Graphics.cpp index 9495b96b24a..401c0d50dfd 100644 --- a/interface/src/Application_Graphics.cpp +++ b/interface/src/Application_Graphics.cpp @@ -84,7 +84,7 @@ void Application::initializeGL() { } #ifdef USE_GL - _glWidget->windowHandle()->setSurfaceType(QSurface::OpenGLSurface); + _primaryWidget->windowHandle()->setSurfaceType(QSurface::OpenGLSurface); _primaryWidget->windowHandle()->setFormat(getDefaultOpenGLSurfaceFormat()); #else //_primaryWidget->windowHandle()->setFormat(getDefaultOpenGLSurfaceFormat()); // VKTODO diff --git a/tools/gpu-frame-player/src/RenderThread.cpp b/tools/gpu-frame-player/src/RenderThread.cpp index 5240ae6ea75..aa46245e6e6 100644 --- a/tools/gpu-frame-player/src/RenderThread.cpp +++ b/tools/gpu-frame-player/src/RenderThread.cpp @@ -11,7 +11,7 @@ #include #include #ifdef Q_OS_LINUX -#include +//#include #endif #ifdef USE_GL #include From 8026ecc253fcf6479bc0750377551f146ad48e0d Mon Sep 17 00:00:00 2001 From: Ada Date: Fri, 7 Nov 2025 03:17:14 +1000 Subject: [PATCH 096/111] Fixes for existing overhauled QML --- .../qml/overte/avatar_picker/AvatarItem.qml | 16 +- .../qml/overte/avatar_picker/AvatarPicker.qml | 3 + interface/resources/qml/overte/chat/Chat.qml | 32 ++- .../qml/overte/contacts/AccountAvatar.qml | 4 +- .../qml/overte/contacts/MyAccountInfo.qml | 2 + .../overte/dialogs/BuiltinScriptsDialog.qml | 4 +- .../resources/qml/overte/icons/delete.svg | 2 + .../resources/qml/overte/icons/filter.svg | 2 + interface/resources/qml/overte/icons/home.svg | 2 + .../qml/overte/icons/no_avatar_icon.svg | 2 - .../resources/qml/overte/icons_src/delete.svg | 108 +++++++++ .../resources/qml/overte/icons_src/filter.svg | 68 ++++++ .../resources/qml/overte/icons_src/home.svg | 68 ++++++ .../qml/overte/icons_src/no_avatar_icon.svg | 226 ------------------ .../qml/overte/settings/pages/General.qml | 14 +- interface/src/Application_UI.cpp | 7 + scripts/system/chat.js | 9 +- 17 files changed, 316 insertions(+), 253 deletions(-) create mode 100644 interface/resources/qml/overte/icons/delete.svg create mode 100644 interface/resources/qml/overte/icons/filter.svg create mode 100644 interface/resources/qml/overte/icons/home.svg delete mode 100644 interface/resources/qml/overte/icons/no_avatar_icon.svg create mode 100644 interface/resources/qml/overte/icons_src/delete.svg create mode 100644 interface/resources/qml/overte/icons_src/filter.svg create mode 100644 interface/resources/qml/overte/icons_src/home.svg delete mode 100644 interface/resources/qml/overte/icons_src/no_avatar_icon.svg diff --git a/interface/resources/qml/overte/avatar_picker/AvatarItem.qml b/interface/resources/qml/overte/avatar_picker/AvatarItem.qml index 75ddc6acfcc..2c6ddf08e32 100644 --- a/interface/resources/qml/overte/avatar_picker/AvatarItem.qml +++ b/interface/resources/qml/overte/avatar_picker/AvatarItem.qml @@ -40,18 +40,18 @@ Item { anchors.margins: 4 } - Image { - source: "../icons/no_avatar_icon.svg" - sourceSize.width: width - sourceSize.height: height - fillMode: Image.PreserveAspectFit - 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.Ready + opacity: Overte.Theme.highContrast ? 1.0 : 0.6 + wrapMode: Text.Wrap + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + text: buttonIcon.status === Image.Loading ? qsTr("Loading…") : qsTr("No icon") } Overte.Label { @@ -83,7 +83,7 @@ Item { implicitWidth: 44 implicitHeight: 44 - icon.source: "../icons/close.svg" + icon.source: "../icons/delete.svg" icon.width: 32 icon.height: 32 icon.color: Overte.Theme.paletteActive.buttonText diff --git a/interface/resources/qml/overte/avatar_picker/AvatarPicker.qml b/interface/resources/qml/overte/avatar_picker/AvatarPicker.qml index 3432fe01267..c5430233a07 100644 --- a/interface/resources/qml/overte/avatar_picker/AvatarPicker.qml +++ b/interface/resources/qml/overte/avatar_picker/AvatarPicker.qml @@ -123,6 +123,9 @@ Rectangle { 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)) diff --git a/interface/resources/qml/overte/chat/Chat.qml b/interface/resources/qml/overte/chat/Chat.qml index fe2bb7aa69b..26a501f93ee 100644 --- a/interface/resources/qml/overte/chat/Chat.qml +++ b/interface/resources/qml/overte/chat/Chat.qml @@ -19,6 +19,24 @@ Rectangle { 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("private/chat/eventsLog") ?? []; + for (let event of savedEvents) { + fromScript(event); + } + } + + Component.onDestruction: { + SettingsInterface.setValue("private/chat/eventsLog", eventsLog); + } + + onMessagesCleared: eventsLog = [] + signal messagePushed(name: string, body: string, time: string) signal notificationPushed(text: string, time: string) signal messagesCleared() @@ -45,9 +63,21 @@ Rectangle { } function fromScript(rawObj) { - const obj = JSON.parse(rawObj); + let obj = (typeof(rawObj) === "string") ? JSON.parse(rawObj) : rawObj; const timestamp = (obj.timestamp ? new Date(obj.timestamp) : new Date()).toLocaleTimeString(undefined, Locale.ShortFormat); + // keep chat events in the log + if ( + obj.event !== "start_typing" && + obj.event !== "end_typing" && + obj.event !== "change_setting" + ) { + if (!obj.timestamp) { + obj.timestamp = Date.now(); + } + eventsLog.push(obj); + } + switch (obj.event) { case "recv_message": messagePushed(obj.name ?? "", obj.body, timestamp); diff --git a/interface/resources/qml/overte/contacts/AccountAvatar.qml b/interface/resources/qml/overte/contacts/AccountAvatar.qml index be42983945a..b1530d75937 100644 --- a/interface/resources/qml/overte/contacts/AccountAvatar.qml +++ b/interface/resources/qml/overte/contacts/AccountAvatar.qml @@ -4,8 +4,9 @@ import QtQuick.Effects import ".." as Overte Rectangle { - required property url source required property int status + property alias source: avatarImage.source + property alias retainWhileLoading: avatarImage.retainWhileLoading color: "transparent" @@ -31,7 +32,6 @@ Rectangle { fillMode: Image.PreserveAspectCrop id: avatarImage - source: avatar.source sourceSize.width: width sourceSize.height: height diff --git a/interface/resources/qml/overte/contacts/MyAccountInfo.qml b/interface/resources/qml/overte/contacts/MyAccountInfo.qml index a83ddbdb641..3e154b24513 100644 --- a/interface/resources/qml/overte/contacts/MyAccountInfo.qml +++ b/interface/resources/qml/overte/contacts/MyAccountInfo.qml @@ -28,6 +28,7 @@ Rectangle { // seconds of placeholder avatar while the profile loads Settings { id: settings + category: "MyAccountInfo" property url cachedAvatarUrl: "../icons/unset_avatar.svg" } @@ -121,6 +122,7 @@ Rectangle { "../icons/unset_avatar.svg" ) status: root.status + retainWhileLoading: true Layout.preferredWidth: 64 Layout.preferredHeight: 64 Layout.leftMargin: 8 diff --git a/interface/resources/qml/overte/dialogs/BuiltinScriptsDialog.qml b/interface/resources/qml/overte/dialogs/BuiltinScriptsDialog.qml index ef610ddc2ed..0a19fa61147 100644 --- a/interface/resources/qml/overte/dialogs/BuiltinScriptsDialog.qml +++ b/interface/resources/qml/overte/dialogs/BuiltinScriptsDialog.qml @@ -177,10 +177,10 @@ Rectangle { Layout.preferredWidth: 128 enabled: { - if (!treeView.selectionModel.currentIndex) { return false; } + if (treeView.selectionModel.currentIndex === -1) { return false; } const data = treeModel.getRow(treeView.selectionModel.currentIndex); - return !data.rows; + return !data?.rows; } text: qsTr("Load") backgroundColor: Overte.Theme.paletteActive.buttonAdd 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/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/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/no_avatar_icon.svg b/interface/resources/qml/overte/icons/no_avatar_icon.svg deleted file mode 100644 index 4e536f0afea..00000000000 --- a/interface/resources/qml/overte/icons/no_avatar_icon.svg +++ /dev/null @@ -1,2 +0,0 @@ - - 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/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/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/no_avatar_icon.svg b/interface/resources/qml/overte/icons_src/no_avatar_icon.svg deleted file mode 100644 index 1078d75e54e..00000000000 --- a/interface/resources/qml/overte/icons_src/no_avatar_icon.svg +++ /dev/null @@ -1,226 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/interface/resources/qml/overte/settings/pages/General.qml b/interface/resources/qml/overte/settings/pages/General.qml index 8b02978c790..a8f9e5f4c2a 100644 --- a/interface/resources/qml/overte/settings/pages/General.qml +++ b/interface/resources/qml/overte/settings/pages/General.qml @@ -124,15 +124,12 @@ SettingsPage { } SpinBoxSetting { - // FIXME - enabled: false - text: qsTr("Animation Duration") from: 1 to: 30 - //value: Settings.getValue("snapshotAnimatedDuration", 3) - //onValueChanged: Settings.setValue("snapshotAnimatedDuration", value) + value: SettingsInterface.getValue("snapshotAnimatedDuration", 3) + onValueChanged: SettingsInterface.setValue("snapshotAnimatedDuration", value) } SettingNote { @@ -156,12 +153,9 @@ SettingsPage { } SwitchSetting { - // FIXME - enabled: false - text: qsTr("Discord Rich Presence") - //value: Settings.getValue("useDiscordPresence", true) - //onValueChanged: Settings.setValue("useDiscordPresence", value) + value: SettingsInterface.getValue("useDiscordPresence", true) + onValueChanged: SettingsInterface.setValue("useDiscordPresence", value) } SettingNote { diff --git a/interface/src/Application_UI.cpp b/interface/src/Application_UI.cpp index f56efc67766..534acc599be 100644 --- a/interface/src/Application_UI.cpp +++ b/interface/src/Application_UI.cpp @@ -230,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()); @@ -934,7 +937,11 @@ void Application::onDesktopRootContextCreated(QQmlContext* surfaceContext) { 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()); diff --git a/scripts/system/chat.js b/scripts/system/chat.js index 270b08334d2..a00f2acb6b2 100644 --- a/scripts/system/chat.js +++ b/scripts/system/chat.js @@ -134,7 +134,6 @@ const appButton = { text: "CHAT", }, - qmlSource: "overte/settings/Settings.qml", button: null, onClicked() { @@ -148,7 +147,11 @@ const appButton = { }; let appWindow; -recreateAppWindow(); + +// postpone the window creation until there's a chat event, +// if it's opened too quickly then it can spawn visible +// rather than hidden +//recreateAppWindow(); appButton.button = SystemTablet.addButton(appButton.buttonData); appButton.button.clicked.connect(() => appButton.onClicked()); @@ -186,6 +189,7 @@ AvatarManager.avatarAddedEvent.connect(uuid => { appWindow.sendToQml(JSON.stringify({ event: "user_joined", name: AvatarManager.getAvatar(uuid).sessionDisplayName, + timestamp: Date.now(), })); }); @@ -195,6 +199,7 @@ AvatarManager.avatarRemovedEvent.connect(uuid => { appWindow.sendToQml(JSON.stringify({ event: "user_left", name: AvatarManager.getAvatar(uuid).sessionDisplayName, + timestamp: Date.now(), })); }); From 485d2edbb5f12741a376e22dad14ddcb5ce0d135 Mon Sep 17 00:00:00 2001 From: Ada Date: Fri, 7 Nov 2025 03:18:43 +1000 Subject: [PATCH 097/111] First pass of More app and Places app --- interface/resources/qml/overte/Theme.qml | 40 ++ .../qml/overte/more_apps/AppDelegate.qml | 229 +++++++ .../qml/overte/more_apps/MoreApps.qml | 230 +++++++ .../resources/qml/overte/more_apps/qmldir | 3 + .../qml/overte/place_picker/PlaceItem.qml | 182 ++++++ .../qml/overte/place_picker/PlacePicker.qml | 614 ++++++++++++++++++ .../overte/{staging => }/place_picker/qmldir | 0 .../overte/staging/place_picker/PlaceItem.qml | 149 ----- .../staging/place_picker/PlacePicker.qml | 135 ---- scripts/defaultScripts.js | 2 - scripts/system/systemApps.js | 41 ++ 11 files changed, 1339 insertions(+), 286 deletions(-) create mode 100644 interface/resources/qml/overte/more_apps/AppDelegate.qml create mode 100644 interface/resources/qml/overte/more_apps/MoreApps.qml create mode 100644 interface/resources/qml/overte/more_apps/qmldir create mode 100644 interface/resources/qml/overte/place_picker/PlaceItem.qml create mode 100644 interface/resources/qml/overte/place_picker/PlacePicker.qml rename interface/resources/qml/overte/{staging => }/place_picker/qmldir (100%) delete mode 100644 interface/resources/qml/overte/staging/place_picker/PlaceItem.qml delete mode 100644 interface/resources/qml/overte/staging/place_picker/PlacePicker.qml diff --git a/interface/resources/qml/overte/Theme.qml b/interface/resources/qml/overte/Theme.qml index 95982c2cdc6..0ba77a78863 100644 --- a/interface/resources/qml/overte/Theme.qml +++ b/interface/resources/qml/overte/Theme.qml @@ -89,6 +89,16 @@ QtObject { 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 { @@ -122,6 +132,16 @@ QtObject { 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 { @@ -155,6 +175,16 @@ QtObject { 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 { @@ -188,5 +218,15 @@ QtObject { 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/more_apps/AppDelegate.qml b/interface/resources/qml/overte/more_apps/AppDelegate.qml new file mode 100644 index 00000000000..d1f6f878f8c --- /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, true); + } + + 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, true); + + // 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..348d0e93cb9 --- /dev/null +++ b/interface/resources/qml/overte/more_apps/MoreApps.qml @@ -0,0 +1,230 @@ +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 + + property list repoSources: { + // the default setting argument doesn't work in qml, so emulate it + let sourcesList = SettingsInterface.getValue("private/moreApp/repoSources"); + if (!sourcesList || sourcesList.length < 1) { + sourcesList = [ "https://more.overte.org" ]; + } + + return sourcesList; + } + + property string searchExpression: ".*" + property list rawListModel: [] + property list filteredModel: [] + + // can't be list or equality tests fail + property list installedScripts: SettingsInterface.getValue("private/moreApp/installedScripts") ?? [] + 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) { + if (list.version !== 2) { + console.warn(`App list "${list}" is version ${list.version}, expected 2`); + return; + } + const repoHost = (new URL(list.baseApiUrl)).hostname; + + let tmp = []; + + for (const item of list.applicationList) { + if (!item.appActive) { continue; } + + const scriptUrl = `${list.baseApiUrl}/${item.appBaseDirectory}/${item.appScriptVersions.Stable}`; + + tmp.push({ + appID: item.appBaseDirectory, + name: item.appName, + description: item.appDescription, + scriptUrl: scriptUrl, + author: item.appAuthor, + icon: `${list.baseApiUrl}/${item.appBaseDirectory}/${item.appIcon}`, + repoHost: repoHost, + }); + } + + rawListModel = rawListModel.concat(tmp); + } + + function fetchList(repo) { + let xhr = new XMLHttpRequest(); + + xhr.onreadystatechange = () => { + if (xhr.readyState === XMLHttpRequest.DONE) { + let data; + try { + data = JSON.parse(xhr.response); + } catch(e) { + console.error(repo, e); + return; + } + parseList(data); + } + }; + + xhr.open("GET", `${repo}/applications/metadata.json`); + xhr.send(); + } + + function fetchAllLists() { + rawListModel = []; + + for (const repo of repoSources) { + fetchList(repo); + } + } + + onRepoSourcesChanged: { + SettingsInterface.setValue("private/moreApp/repoSources", repoSources); + refreshRunningScripts(); + fetchAllLists(); + } + + onInstalledScriptsChanged: SettingsInterface.setValue("private/moreApp/installedScripts", installedScripts) + + ColumnLayout { + anchors.fill: parent + + RowLayout { + Layout.margins: 4 + + // TODO + Overte.RoundButton { + icon.source: "../icons/settings_cog.svg" + icon.width: 24 + icon.height: 24 + + onClicked: console.warn("TODO") + visible: false + } + + Overte.RoundButton { + // TODO + icon.source: checked ? "../icons/eye_closed.svg" : "../icons/eye_open.svg" + icon.width: 24 + icon.height: 24 + + id: onlyInstalled + checkable: true + onToggled: refreshFilteredModel() + + 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 {} + } + } +} 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..bfdeef0d50a --- /dev/null +++ b/interface/resources/qml/overte/place_picker/PlaceItem.qml @@ -0,0 +1,182 @@ +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 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 ? "#a0000000" : "#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 `${currentUsers}`; + } else { + return `${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.fontPixelSizeSmall + horizontalAlignment: Text.AlignLeft + verticalAlignment: Text.AlignTop + } + } + + 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..c35f2a65a96 --- /dev/null +++ b/interface/resources/qml/overte/place_picker/PlacePicker.qml @@ -0,0 +1,614 @@ +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: "PlacePicker" + property string searchExpression: ".*" + property int minUsers: 0 + property bool includeIncompatible: false + property list maturity: [ + "everyone", + "teen", + "mature", + "adult", + "unrated", + ] + } + + 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 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; + const recentHeartbeat = ((Date.now() - parseInt(place.domain.time_of_last_heartbeat_s)) / 1000) < ONE_DAY_SECS; + const filterName = Boolean(place.name.match(searchExpression)); + const filterMaturity = filters.maturity.includes(place.maturity); + const filterMinUsers = place.current_attendance >= filters.minUsers; + + if ( + (compatibleProtocol || filters.includeIncompatible) && + recentHeartbeat && + filterName && + filterMaturity && + filterMinUsers + ) { + place.directoryHost = hostname; + place.compatibleProtocol = compatibleProtocol; + tmp.push(place); + } + } + + tmp.sort((a, b) => ( + // sort by compatibility, active users, then by name + ((b.compatibleProtocol ? 1 : 0) - (a.compatibleProtocol ? 1 : 0)) || + b.current_attendance - a.current_attendance || + a.name.localeCompare(b.name) + )); + + 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.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.startsWith("hifi://") ? "../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: filterButton + icon.source: "../icons/filter.svg" + icon.width: 24 + icon.height: 24 + icon.color: Overte.Theme.paletteActive.buttonText + + onClicked: filterDialog.open() + } + } + + 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: gridView.model.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: model.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() { + filterControlMinUsers.value = filters.minUsers; + 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() { + filters.minUsers = filterControlMinUsers.value; + 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("Minimum user count") + } + + Overte.SpinBox { id: filterControlMinUsers } + } + + 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 + + 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}`; + + 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 { + // TODO: separate place portal handler, the old one was + // baked into places.js so we can't reuse it + visible: false + enabled: false + + Layout.fillWidth: true + Layout.fillHeight: true + Layout.preferredWidth: 1 + backgroundColor: Overte.Theme.paletteActive.buttonInfo + text: qsTr("Portal") + /*onClicked: { + // FIXME: This is defined in the previous places.js, + // we'll need a separate script for handling portal spawns + Messages.sendMessage("com.overte.places.portalRezzer", JSON.stringify({ + action: "REZ_PORTAL", + position: Vec3.sum( + MyAvatar.position, + Vec3.multiply(2, Quat.getForward(MyAvatar.orientation)) + ), + url: infoDialog.placeUrl, + name: infoDialog.placeName, + placeID: infoDialog.placeName, + })); + }*/ + } + + 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: placePicker.goToLocation(infoDialog.placeUrl) + } + } + } + + Overte.RoundButton { + anchors.top: parent.top + anchors.right: parent.right + anchors.margins: 8 + implicitWidth: 28 + implicitHeight: 28 + + backgroundColor: Overte.Theme.paletteActive.buttonDestructive + + icon.source: "../icons/close.svg" + icon.width: 12 + icon.height: 12 + icon.color: Overte.Theme.paletteActive.buttonText + + onClicked: infoDialog.close() + } + } +} diff --git a/interface/resources/qml/overte/staging/place_picker/qmldir b/interface/resources/qml/overte/place_picker/qmldir similarity index 100% rename from interface/resources/qml/overte/staging/place_picker/qmldir rename to interface/resources/qml/overte/place_picker/qmldir diff --git a/interface/resources/qml/overte/staging/place_picker/PlaceItem.qml b/interface/resources/qml/overte/staging/place_picker/PlaceItem.qml deleted file mode 100644 index 807d5c4e03c..00000000000 --- a/interface/resources/qml/overte/staging/place_picker/PlaceItem.qml +++ /dev/null @@ -1,149 +0,0 @@ -import QtQuick -import QtQuick.Layouts - -import ".." as Overte - -Rectangle { - id: item - color: index % 2 === 0 ? Overte.Theme.paletteActive.base : Overte.Theme.paletteActive.alternateBase - - required property int index - - required property string name - property string description: listView.model[index].description ?? "" - property url thumbnail: listView.model[index].thumbnail ?? "" - - property int currentUsers: listView.model[index].current_attendance ?? 0 - property int maxUsers: { - const capacity = listView.model[index].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 ? "#80000000" : "#80ffffff" - } - } - - anchors.left: parent ? parent.left : undefined - anchors.right: parent ? parent.right : undefined - anchors.leftMargin: 4 - anchors.rightMargin: Overte.Theme.scrollbarWidth - - implicitHeight: 128 - - Component.onCompleted: { - // Hide redundant default descriptions that don't say anything - if (description === `A place in ${name}`) { - description = ""; - } - } - - Image { - anchors.fill: parent - fillMode: Image.PreserveAspectCrop - visible: !Overte.Theme.highContrast - source: thumbnail - } - - ColumnLayout { - anchors.left: item.left - anchors.top: item.top - anchors.bottom: item.bottom - anchors.right: controls.left - anchors.margins: 4 - spacing: 8 - - Rectangle { - Layout.alignment: Qt.AlignLeft | Qt.AlignBottom - Layout.maximumWidth: parent.width - implicitWidth: titleText.implicitWidth + 16 - implicitHeight: titleText.implicitHeight + 16 - color: textBackgroundColor - radius: Overte.Theme.borderRadius - - Overte.Label { - anchors.margins: 8 - anchors.fill: parent - - id: titleText - text: name - horizontalAlignment: Text.AlignLeft - verticalAlignment: Text.AlignTop - style: Text.Outline - styleColor: Overte.Theme.darkMode ? "black" : "white" - } - } - - Item { Layout.fillHeight: true } - - Rectangle { - visible: description !== "" - - Layout.alignment: Qt.AlignLeft | Qt.AlignBottom - Layout.maximumWidth: parent.width - implicitWidth: descriptionText.implicitWidth + 16 - implicitHeight: descriptionText.implicitHeight + 16 - color: textBackgroundColor - radius: Overte.Theme.borderRadius - - Overte.Label { - anchors.margins: 8 - anchors.fill: parent - - id: descriptionText - text: description - horizontalAlignment: Text.AlignLeft - verticalAlignment: Text.AlignTop - style: Text.Outline - styleColor: Overte.Theme.darkMode ? "black" : "white" - wrapMode: Text.Wrap - elide: Text.ElideRight - font.pixelSize: Overte.Theme.fontPixelSizeSmall - } - } - } - - ColumnLayout { - id: controls - anchors.top: item.top - anchors.bottom: item.bottom - anchors.right: item.right - anchors.margins: 8 - spacing: 4 - - Overte.RoundButton { - Layout.alignment: Qt.AlignCenter - text: ">" - backgroundColor: Overte.Theme.paletteActive.buttonAdd - } - - Overte.RoundButton { - Layout.alignment: Qt.AlignCenter - text: "P" - backgroundColor: Overte.Theme.paletteActive.buttonInfo - } - - Item { Layout.fillHeight: true } - - Rectangle { - Layout.alignment: Qt.AlignCenter - implicitWidth: 12 - implicitHeight: 12 - radius: width - border.width: Overte.Theme.borderWidth - border.color: Qt.darker(Overte.Theme.paletteActive.base, Overte.Theme.borderDarker) - color: { - if (currentUsers === 0) { - return "#808080"; - } else if (currentUsers < maxUsers) { - return "#00ff00"; - } else { - return "#ff0000"; - } - } - } - } -} diff --git a/interface/resources/qml/overte/staging/place_picker/PlacePicker.qml b/interface/resources/qml/overte/staging/place_picker/PlacePicker.qml deleted file mode 100644 index 35b5859f0e8..00000000000 --- a/interface/resources/qml/overte/staging/place_picker/PlacePicker.qml +++ /dev/null @@ -1,135 +0,0 @@ -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 - - readonly property string protocolSignature: "6xYA55jcXgPHValo3Ba3/A==" - - Component.onCompleted: { - let xhr = new XMLHttpRequest(); - - xhr.onreadystatechange = () => { - if (xhr.readyState === XMLHttpRequest.DONE) { - console.debug("Finished downloading place list"); - try { - const body = JSON.parse(xhr.responseText); - - let accum = []; - for (const place of body.data.places) { - if ( - place.domain.protocol_version === protocolSignature - ) { - accum.push(place); - } - } - - listView.model = accum; - - console.debug("Finished parsing place list"); - } catch (e) {} - } - }; - - console.debug("Downloading place list…"); - xhr.open("GET", "https://mv.overte.org/server/api/v1/places"); - xhr.send(); - } - - ColumnLayout { - anchors.fill: parent - - RowLayout { - Layout.margins: 4 - - Overte.RoundButton { - id: settingsButton - icon.source: "../icons/settings_cog.svg" - icon.width: 24 - icon.height: 24 - icon.color: Overte.Theme.paletteActive.buttonText - - onClicked: print("TODO") - } - - 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; - } - } - } - - Overte.TabBar { - Layout.fillWidth: true - id: tabBar - - Overte.TabButton { text: qsTr("Public") } - Overte.TabButton { text: qsTr("Bookmarks") } - } - - Overte.Label { - Layout.fillWidth: true - Layout.fillHeight: true - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - visible: listView.model.length === 0 - text: qsTr("Loading…") - } - - StackLayout { - Layout.fillWidth: true - Layout.fillHeight: true - - ListView { - id: listView - visible: model.length !== 0 - spacing: 2 - clip: true - - ScrollBar.vertical: Overte.ScrollBar {} - - model: [] - delegate: PlaceItem {} - } - } - - Overte.Label { - Layout.margins: 8 - Layout.fillWidth: true - visible: listView.model.length !== 0 - verticalAlignment: Text.AlignVCenter - text: qsTr("%1 place(s)").arg(listView.model.length) - } - } -} diff --git a/scripts/defaultScripts.js b/scripts/defaultScripts.js index 55eaff1c9c0..6ce06f83504 100644 --- a/scripts/defaultScripts.js +++ b/scripts/defaultScripts.js @@ -32,7 +32,6 @@ var DEFAULT_SCRIPTS_COMBINED = [ "system/inspect.js", "system/keyboardShortcuts/keyboardShortcuts.js", "system/onEscape.js", - "system/places/places.js" //"developer/debugging/scriptMemoryReport.js" ]; var DEFAULT_SCRIPTS_SEPARATE = [ @@ -42,7 +41,6 @@ var DEFAULT_SCRIPTS_SEPARATE = [ "system/controllers/squeezeHands.js", "communityScripts/notificationCore/notificationCore.js", "simplifiedUI/ui/simplifiedNametag/simplifiedNametag.js", - {"stable": "system/more/app-more.js", "beta": "https://more.overte.org/more/app-more.js"}, "communityScripts/chatBubbles/chatBubbles.js", "communityScripts/contextMenu.js", ]; diff --git a/scripts/system/systemApps.js b/scripts/system/systemApps.js index 2ac2f6d73e4..44b7e94048d 100644 --- a/scripts/system/systemApps.js +++ b/scripts/system/systemApps.js @@ -76,6 +76,16 @@ function defaultFromQml(message) { xhr.send(data.data.body); } break; + + case "system:location_go_back": location.goBack(); break; + case "system:location_go_forward": location.goForward(); break; + case "system:location_go_to": { + location.handleLookupString(data.data.path); + + // hide the tablet after travelling + SystemTablet.gotoHomeScreen(); + SystemTablet.tabletShown = false; + } break; } } @@ -124,6 +134,37 @@ const SYSTEM_APPS = { qmlSource: "overte/contacts/ContactsList.qml", appButton: null, }, + + places: { + appButtonData: { + sortOrder: 6, + isActive: false, + // TODO: put these somewhere more global + icon: Script.resolvePath("./places/icons/appicon_i.png"), + activeIcon: Script.resolvePath("./places/icons/appicon_a.png"), + + // TODO: translation support in JS + text: "PLACES", + }, + + qmlSource: "overte/place_picker/PlacePicker.qml", + appButton: null, + }, + + more: { + appButtonData: { + isActive: false, + // TODO: put these somewhere more global + icon: Script.resolvePath("./more/appicon_i.png"), + activeIcon: Script.resolvePath("./more/appicon_a.png"), + + // TODO: translation support in JS + text: "MORE", + }, + + qmlSource: "overte/more_apps/MoreApps.qml", + appButton: null, + }, }; for (let app of Object.values(SYSTEM_APPS)) { From c77cb76e12b5d75327904c229e504212e7e9c7c5 Mon Sep 17 00:00:00 2001 From: Ada Date: Fri, 7 Nov 2025 18:12:37 +1000 Subject: [PATCH 098/111] Sort by ID instead of alphabetically, quicker 'has users' button, place favorite button --- interface/resources/qml/overte/Theme.qml | 4 + .../resources/qml/overte/icons/users.svg | 2 + .../resources/qml/overte/icons_src/users.svg | 91 +++++++++++++++ .../qml/overte/place_picker/PlacePicker.qml | 107 ++++++++++++------ 4 files changed, 172 insertions(+), 32 deletions(-) create mode 100644 interface/resources/qml/overte/icons/users.svg create mode 100644 interface/resources/qml/overte/icons_src/users.svg diff --git a/interface/resources/qml/overte/Theme.qml b/interface/resources/qml/overte/Theme.qml index 0ba77a78863..5da5f32e608 100644 --- a/interface/resources/qml/overte/Theme.qml +++ b/interface/resources/qml/overte/Theme.qml @@ -78,6 +78,7 @@ QtObject { 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" @@ -121,6 +122,7 @@ QtObject { 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" @@ -164,6 +166,7 @@ QtObject { 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" @@ -207,6 +210,7 @@ QtObject { 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" 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/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/place_picker/PlacePicker.qml b/interface/resources/qml/overte/place_picker/PlacePicker.qml index c35f2a65a96..107c96d8f98 100644 --- a/interface/resources/qml/overte/place_picker/PlacePicker.qml +++ b/interface/resources/qml/overte/place_picker/PlacePicker.qml @@ -22,7 +22,6 @@ Rectangle { id: filters category: "PlacePicker" property string searchExpression: ".*" - property int minUsers: 0 property bool includeIncompatible: false property list maturity: [ "everyone", @@ -31,6 +30,7 @@ Rectangle { "adult", "unrated", ] + property list favoritedPlaceIds: [] } function goBack() { @@ -79,17 +79,17 @@ Rectangle { for (let place of rawPlaces) { const compatibleProtocol = place.domain.protocol_version === protocolSignature; - const recentHeartbeat = ((Date.now() - parseInt(place.domain.time_of_last_heartbeat_s)) / 1000) < ONE_DAY_SECS; + // ?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 filterMaturity = filters.maturity.includes(place.maturity); - const filterMinUsers = place.current_attendance >= filters.minUsers; + const filterHasUsers = !onlyShowActiveButton.checked || place.current_attendance > 0; if ( (compatibleProtocol || filters.includeIncompatible) && - recentHeartbeat && filterName && filterMaturity && - filterMinUsers + filterHasUsers ) { place.directoryHost = hostname; place.compatibleProtocol = compatibleProtocol; @@ -98,10 +98,12 @@ Rectangle { } tmp.sort((a, b) => ( - // sort by compatibility, active users, then by name + // if "show incompatible servers" is on, sort compatible ones first ((b.compatibleProtocol ? 1 : 0) - (a.compatibleProtocol ? 1 : 0)) || - b.current_attendance - a.current_attendance || - a.name.localeCompare(b.name) + // sort favorited places up + ((filters.favoritedPlaceIds.includes(b.placeId) ? 1 : 0) - (filters.favoritedPlaceIds.includes(a.placeId) ? 1 : 0)) || + // sort by UUID, so the listing has a stable order that can't be cheated + a.placeId.localeCompare(b.placeId) )); gridView.model = tmp; @@ -177,6 +179,8 @@ Rectangle { icon.color: Overte.Theme.paletteActive.buttonText onClicked: placePicker.goToLocation(LocationBookmarks.getHomeLocationAddress()) + + Overte.ToolTip { text: qsTr("Go to Home bookmark") } } Overte.TextField { @@ -213,6 +217,19 @@ Rectangle { } } + 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" @@ -221,6 +238,8 @@ Rectangle { icon.color: Overte.Theme.paletteActive.buttonText onClicked: filterDialog.open() + + Overte.ToolTip { text: qsTr("Search options") } } } @@ -235,7 +254,7 @@ Rectangle { ColumnLayout { Layout.fillWidth: true Layout.fillHeight: true - visible: gridView.model.length === 0 + visible: rawPlaces.length === 0 Item { Layout.fillHeight: true } @@ -281,7 +300,7 @@ Rectangle { interactive: false id: gridView - visible: model.length !== 0 + visible: rawPlaces.length !== 0 // fit two cells onto the default tablet cellWidth: (480 - Overte.Theme.scrollbarWidth) / 2 cellHeight: Math.floor(cellWidth * 0.6) @@ -327,7 +346,6 @@ Rectangle { anchors.fill: placePicker function open() { - filterControlMinUsers.value = filters.minUsers; filterControlMaturityEveryone.checked = filters.maturity.includes("everyone"); filterControlMaturityTeen.checked = filters.maturity.includes("teen"); filterControlMaturityMature.checked = filters.maturity.includes("mature"); @@ -339,7 +357,6 @@ Rectangle { } function accept() { - filters.minUsers = filterControlMinUsers.value; let maturityList = []; if (filterControlMaturityEveryone.checked) { @@ -414,17 +431,6 @@ Rectangle { } } - RowLayout { - Layout.fillWidth: true - - Overte.Label { - Layout.fillWidth: true - text: qsTr("Minimum user count") - } - - Overte.SpinBox { id: filterControlMinUsers } - } - RowLayout { Layout.fillWidth: true @@ -473,6 +479,7 @@ Rectangle { property list managers: [] property url placeUrl: "" property bool compatible: true + property string placeId: "" function open(index) { const data = gridView.model[index]; @@ -494,6 +501,7 @@ Rectangle { 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; @@ -594,21 +602,56 @@ Rectangle { } } - Overte.RoundButton { + Row { anchors.top: parent.top anchors.right: parent.right anchors.margins: 8 - implicitWidth: 28 - implicitHeight: 28 + spacing: 4 - backgroundColor: Overte.Theme.paletteActive.buttonDestructive + 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 + ) - icon.source: "../icons/close.svg" - icon.width: 12 - icon.height: 12 - icon.color: Overte.Theme.paletteActive.buttonText + 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(); + } - onClicked: infoDialog.close() + 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() + } } } } From e6f0f40c9b33833684fb8846273c8b1df255652f Mon Sep 17 00:00:00 2001 From: Ada Date: Fri, 7 Nov 2025 20:53:03 +1000 Subject: [PATCH 099/111] More app retries, mistake fixups --- .../qml/overte/avatar_picker/AvatarPicker.qml | 29 +++++++--- interface/resources/qml/overte/chat/Chat.qml | 2 +- .../qml/overte/contacts/MyAccountInfo.qml | 4 +- .../qml/overte/more_apps/AppDelegate.qml | 4 +- .../qml/overte/more_apps/MoreApps.qml | 25 ++++---- .../qml/overte/place_picker/PlacePicker.qml | 8 ++- interface/src/AvatarBookmarks.cpp | 47 +++++++++++++++ interface/src/AvatarBookmarks.h | 16 +++++ scripts/system/chat.js | 2 +- scripts/system/systemApps.js | 58 ++++++++++++++----- 10 files changed, 150 insertions(+), 45 deletions(-) diff --git a/interface/resources/qml/overte/avatar_picker/AvatarPicker.qml b/interface/resources/qml/overte/avatar_picker/AvatarPicker.qml index c5430233a07..98bc4ca4b1f 100644 --- a/interface/resources/qml/overte/avatar_picker/AvatarPicker.qml +++ b/interface/resources/qml/overte/avatar_picker/AvatarPicker.qml @@ -23,15 +23,20 @@ Rectangle { let tmp = []; for (const [name, avatar] of Object.entries(data)) { - // TODO: replace this kinda hacky thing we currently do - // with real URLs stored either in the FST or bookmark entry - let iconUrl = new URL(avatar.avatarUrl); - iconUrl.pathname = iconUrl.pathname.replace(/[.](?:fst|glb|fbx|vrm)$/i, ".jpg"); + 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.toString(), + iconUrl: iconUrl, description: "", }); } @@ -253,11 +258,20 @@ Rectangle { signal rejected onAccepted: { - if (editExisting) { + const prevData = AvatarBookmarks.getBookmark(editDialog.avatarName); + + if (editDialog.avatarName !== avatarNameField.text) { AvatarBookmarks.removeBookmark(editDialog.avatarName); } - AvatarBookmarks.addBookmark(avatarNameField.text, editDialog.avatarUrl); + // Qt's V4 doesn't support { ...spread } syntax :( + let newData = prevData; + + if (avatarUrlField.text !== "") { + newData.avatarUrl = avatarUrlField.text; + } + + AvatarBookmarks.setBookmarkData(avatarNameField.text, newData); close(); } @@ -340,6 +354,7 @@ Rectangle { Layout.fillWidth: true Layout.preferredWidth: 1 + backgroundColor: Overte.Theme.paletteActive.buttonAdd enabled: avatarNameField.text !== "" text: qsTr("Add Current") diff --git a/interface/resources/qml/overte/chat/Chat.qml b/interface/resources/qml/overte/chat/Chat.qml index 26a501f93ee..bd2e35d5e09 100644 --- a/interface/resources/qml/overte/chat/Chat.qml +++ b/interface/resources/qml/overte/chat/Chat.qml @@ -15,7 +15,7 @@ Rectangle { property bool settingJoinNotifications: true property bool settingBroadcast: false property bool settingChatBubbles: true - property bool settingDesktopWindow: true + property bool settingDesktopWindow: false property var typingIndicatorNames: ({}) diff --git a/interface/resources/qml/overte/contacts/MyAccountInfo.qml b/interface/resources/qml/overte/contacts/MyAccountInfo.qml index 3e154b24513..ded4de11954 100644 --- a/interface/resources/qml/overte/contacts/MyAccountInfo.qml +++ b/interface/resources/qml/overte/contacts/MyAccountInfo.qml @@ -1,4 +1,4 @@ -import QtCore +import QtCore as QtCore import QtQuick import QtQuick.Layouts import QtQuick.Controls @@ -26,7 +26,7 @@ Rectangle { // cache the last avatar image url so there's not a few // seconds of placeholder avatar while the profile loads - Settings { + QtCore.Settings { id: settings category: "MyAccountInfo" property url cachedAvatarUrl: "../icons/unset_avatar.svg" diff --git a/interface/resources/qml/overte/more_apps/AppDelegate.qml b/interface/resources/qml/overte/more_apps/AppDelegate.qml index d1f6f878f8c..d2b1197d41d 100644 --- a/interface/resources/qml/overte/more_apps/AppDelegate.qml +++ b/interface/resources/qml/overte/more_apps/AppDelegate.qml @@ -159,7 +159,7 @@ Rectangle { if (running) { ScriptDiscoveryService.stopScript(scriptUrl, false); } else { - ScriptDiscoveryService.loadScript(scriptUrl, true); + ScriptDiscoveryService.loadScript(scriptUrl, false); } processing = true; @@ -181,7 +181,7 @@ Rectangle { onClicked: { processing = true; - ScriptDiscoveryService.loadScript(scriptUrl, true); + ScriptDiscoveryService.loadScript(scriptUrl, false); // only variable assignments are automatically tracked by Qt moreApps.installedScripts.push(scriptUrl); diff --git a/interface/resources/qml/overte/more_apps/MoreApps.qml b/interface/resources/qml/overte/more_apps/MoreApps.qml index 348d0e93cb9..5c840a3b904 100644 --- a/interface/resources/qml/overte/more_apps/MoreApps.qml +++ b/interface/resources/qml/overte/more_apps/MoreApps.qml @@ -12,23 +12,23 @@ Rectangle { implicitHeight: 720 color: Overte.Theme.paletteActive.base - property list repoSources: { - // the default setting argument doesn't work in qml, so emulate it - let sourcesList = SettingsInterface.getValue("private/moreApp/repoSources"); - if (!sourcesList || sourcesList.length < 1) { - sourcesList = [ "https://more.overte.org" ]; - } - - return sourcesList; + QtCore.Settings { + id: settings + category: "moreApp" + property list installedScripts: [] + property list repoSources: [ + "https://more.overte.org", + ] } + property alias installedScripts: settings.installedScripts + property alias repoSources: settings.repoSources + property string searchExpression: ".*" property list rawListModel: [] property list filteredModel: [] - // can't be list or equality tests fail - property list installedScripts: SettingsInterface.getValue("private/moreApp/installedScripts") ?? [] - property list runningScripts: [] + property list runningScripts: [] // the running scripts list doesn't immediately update, so give it a bit to update Timer { @@ -136,13 +136,10 @@ Rectangle { } onRepoSourcesChanged: { - SettingsInterface.setValue("private/moreApp/repoSources", repoSources); refreshRunningScripts(); fetchAllLists(); } - onInstalledScriptsChanged: SettingsInterface.setValue("private/moreApp/installedScripts", installedScripts) - ColumnLayout { anchors.fill: parent diff --git a/interface/resources/qml/overte/place_picker/PlacePicker.qml b/interface/resources/qml/overte/place_picker/PlacePicker.qml index 107c96d8f98..2442004ae36 100644 --- a/interface/resources/qml/overte/place_picker/PlacePicker.qml +++ b/interface/resources/qml/overte/place_picker/PlacePicker.qml @@ -20,7 +20,7 @@ Rectangle { QtCore.Settings { id: filters - category: "PlacePicker" + category: "placesApp" property string searchExpression: ".*" property bool includeIncompatible: false property list maturity: [ @@ -201,7 +201,11 @@ Rectangle { Overte.RoundButton { id: searchButton - icon.source: searchField.text.startsWith("hifi://") ? "../icons/send.svg" : "../icons/search.svg" + 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 diff --git a/interface/src/AvatarBookmarks.cpp b/interface/src/AvatarBookmarks.cpp index 63ced1c9369..14dad5201f6 100644 --- a/interface/src/AvatarBookmarks.cpp +++ b/interface/src/AvatarBookmarks.cpp @@ -368,3 +368,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/scripts/system/chat.js b/scripts/system/chat.js index a00f2acb6b2..9d606e905ad 100644 --- a/scripts/system/chat.js +++ b/scripts/system/chat.js @@ -16,7 +16,7 @@ let settings = Settings.getValue("Chat", { joinNotifications: true, broadcastEnabled: false, chatBubbles: true, - desktopWindow: true, + desktopWindow: false, }); function updateSetting(name, value) { diff --git a/scripts/system/systemApps.js b/scripts/system/systemApps.js index 44b7e94048d..bdf40af9756 100644 --- a/scripts/system/systemApps.js +++ b/scripts/system/systemApps.js @@ -167,22 +167,48 @@ const SYSTEM_APPS = { }, }; -for (let app of Object.values(SYSTEM_APPS)) { - if (!app.onClicked) { app.onClicked = defaultOnClicked; } - if (!app.onScreenChanged) { app.onScreenChanged = defaultOnScreenChanged; } - if (!app.fromQml) { app.fromQml = defaultFromQml; } - - let button = SystemTablet.addButton(app.appButtonData); - button.clicked.connect(() => app.onClicked()); - SystemTablet.screenChanged.connect((type, url) => app.onScreenChanged(type, url)); - SystemTablet.fromQml.connect(message => app.fromQml(message)); - app.appButton = button; -} +function setupButtons() { + for (let app of Object.values(SYSTEM_APPS)) { + if (!app.onClicked) { app.onClicked = defaultOnClicked; } + if (!app.onScreenChanged) { app.onScreenChanged = defaultOnScreenChanged; } + if (!app.fromQml) { app.fromQml = defaultFromQml; } + + let button = SystemTablet.addButton(app.appButtonData); + button.clicked.connect(() => app.onClicked()); + SystemTablet.screenChanged.connect((type, url) => app.onScreenChanged(type, url)); + SystemTablet.fromQml.connect(message => app.fromQml(message)); + app.appButton = button; + } -Script.scriptEnding.connect(() => { - for (const app of Object.values(SYSTEM_APPS)) { - if (app.appButton) { - SystemTablet.removeButton(app.appButton); + Script.scriptEnding.connect(() => { + for (const app of Object.values(SYSTEM_APPS)) { + if (app.appButton) { + SystemTablet.removeButton(app.appButton); + } } + }); +} + +function loadInstalledMoreApps() { + const RETRY_DELAY_SECS = 5; + const installedScripts = Settings.getValue("moreApp/installedScripts", []); + + for (const scriptUrl of installedScripts) { + ScriptDiscoveryService.loadScript(scriptUrl, false); } -}); + + // Check that all of the installed scripts are running. + // If there's some that aren't, then give them one more try. + Script.setTimeout(() => { + const running = ScriptDiscoveryService.getRunning(); + + for (const scriptUrl of installedScripts) { + if (!running.includes(scriptUrl)) { + ScriptDiscoveryService.loadScript(scriptUrl, false); + } + } + }, RETRY_DELAY_SECS * 1000); +} + +loadInstalledMoreApps(); +setupButtons(); From 7a5cc2f0d46c520bab63bf91ea9dc2d0bdb003c6 Mon Sep 17 00:00:00 2001 From: Ada Date: Fri, 7 Nov 2025 21:43:11 +1000 Subject: [PATCH 100/111] Use periodic shuffling in Places app, prefer places with thumbnails --- .../qml/overte/place_picker/PlacePicker.qml | 24 +++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/interface/resources/qml/overte/place_picker/PlacePicker.qml b/interface/resources/qml/overte/place_picker/PlacePicker.qml index 2442004ae36..9d23de7c7e3 100644 --- a/interface/resources/qml/overte/place_picker/PlacePicker.qml +++ b/interface/resources/qml/overte/place_picker/PlacePicker.qml @@ -70,6 +70,24 @@ Rectangle { 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; @@ -102,8 +120,10 @@ Rectangle { ((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)) || - // sort by UUID, so the listing has a stable order that can't be cheated - a.placeId.localeCompare(b.placeId) + // 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; From 90401d75d71f69aeb040dee92d279c8f856d7a30 Mon Sep 17 00:00:00 2001 From: Ada Date: Fri, 7 Nov 2025 22:12:08 +1000 Subject: [PATCH 101/111] Add nametag visibility to settings --- .../qml/overte/settings/pages/General.qml | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/interface/resources/qml/overte/settings/pages/General.qml b/interface/resources/qml/overte/settings/pages/General.qml index a8f9e5f4c2a..5ff32a56952 100644 --- a/interface/resources/qml/overte/settings/pages/General.qml +++ b/interface/resources/qml/overte/settings/pages/General.qml @@ -97,6 +97,28 @@ SettingsPage { 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 { From 5ee7859deda760a1280b4d528433a0c2b7e2d03d Mon Sep 17 00:00:00 2001 From: Ada Date: Sat, 8 Nov 2025 04:06:31 +1000 Subject: [PATCH 102/111] Show domain in user count, place portals --- interface/resources/qml/overte/Theme.qml | 2 + .../qml/overte/place_picker/PlaceItem.qml | 10 +- .../qml/overte/place_picker/PlacePicker.qml | 34 ++- scripts/defaultScripts.js | 1 + scripts/system/placePortals.js | 204 ++++++++++++++++++ 5 files changed, 227 insertions(+), 24 deletions(-) create mode 100644 scripts/system/placePortals.js diff --git a/interface/resources/qml/overte/Theme.qml b/interface/resources/qml/overte/Theme.qml index 5da5f32e608..53d70dcbc11 100644 --- a/interface/resources/qml/overte/Theme.qml +++ b/interface/resources/qml/overte/Theme.qml @@ -29,6 +29,8 @@ QtObject { 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 diff --git a/interface/resources/qml/overte/place_picker/PlaceItem.qml b/interface/resources/qml/overte/place_picker/PlaceItem.qml index bfdeef0d50a..bdfcc20f433 100644 --- a/interface/resources/qml/overte/place_picker/PlaceItem.qml +++ b/interface/resources/qml/overte/place_picker/PlaceItem.qml @@ -14,6 +14,7 @@ Rectangle { 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}` @@ -28,7 +29,7 @@ Rectangle { if (Overte.Theme.highContrast) { return Overte.Theme.darkMode ? "black" : "white" } else { - return Overte.Theme.darkMode ? "#a0000000" : "#e0ffffff" + return Overte.Theme.darkMode ? "#d0000000" : "#e0ffffff" } } @@ -94,9 +95,9 @@ Rectangle { id: userCountText text: { if (maxUsers === 9999) { - return `${currentUsers}`; + return `${domainName}: ${currentUsers}`; } else { - return `${currentUsers}/${maxUsers}`; + return `${domainName}: ${currentUsers}/${maxUsers}`; } } color: { @@ -109,9 +110,10 @@ Rectangle { } } font.bold: true - font.pixelSize: Overte.Theme.fontPixelSizeSmall + font.pixelSize: Overte.Theme.fontPixelSizeXSmall horizontalAlignment: Text.AlignLeft verticalAlignment: Text.AlignTop + elide: Text.ElideLeft } } diff --git a/interface/resources/qml/overte/place_picker/PlacePicker.qml b/interface/resources/qml/overte/place_picker/PlacePicker.qml index 9d23de7c7e3..0cef61fe0fa 100644 --- a/interface/resources/qml/overte/place_picker/PlacePicker.qml +++ b/interface/resources/qml/overte/place_picker/PlacePicker.qml @@ -100,12 +100,14 @@ Rectangle { // ?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 && + (filterName || filterDomain || filterDesc) && filterMaturity && filterHasUsers ) { @@ -588,30 +590,19 @@ Rectangle { } Overte.Button { - // TODO: separate place portal handler, the old one was - // baked into places.js so we can't reuse it - visible: false - enabled: false - Layout.fillWidth: true Layout.fillHeight: true Layout.preferredWidth: 1 backgroundColor: Overte.Theme.paletteActive.buttonInfo + enabled: infoDialog.compatible text: qsTr("Portal") - /*onClicked: { - // FIXME: This is defined in the previous places.js, - // we'll need a separate script for handling portal spawns - Messages.sendMessage("com.overte.places.portalRezzer", JSON.stringify({ - action: "REZ_PORTAL", - position: Vec3.sum( - MyAvatar.position, - Vec3.multiply(2, Quat.getForward(MyAvatar.orientation)) - ), - url: infoDialog.placeUrl, - name: infoDialog.placeName, - placeID: infoDialog.placeName, + onClicked: { + infoDialog.close(); + Messages.sendMessage("org.overte.PlacePortal.Create", JSON.stringify({ + place_name: infoDialog.placeName, + place_url: infoDialog.placeUrl, })); - }*/ + } } Overte.Button { @@ -621,7 +612,10 @@ Rectangle { backgroundColor: Overte.Theme.paletteActive.buttonAdd enabled: infoDialog.compatible text: infoDialog.compatible ? qsTr("Join") : qsTr("Incompatible") - onClicked: placePicker.goToLocation(infoDialog.placeUrl) + onClicked: { + infoDialog.close(); + placePicker.goToLocation(infoDialog.placeUrl); + } } } } diff --git a/scripts/defaultScripts.js b/scripts/defaultScripts.js index 6ce06f83504..22c0b80367b 100644 --- a/scripts/defaultScripts.js +++ b/scripts/defaultScripts.js @@ -37,6 +37,7 @@ var DEFAULT_SCRIPTS_COMBINED = [ var DEFAULT_SCRIPTS_SEPARATE = [ "system/systemApps.js", "system/chat.js", + "system/placePortals.js", "system/controllers/controllerScripts.js", "system/controllers/squeezeHands.js", "communityScripts/notificationCore/notificationCore.js", diff --git a/scripts/system/placePortals.js b/scripts/system/placePortals.js new file mode 100644 index 00000000000..97d192a2422 --- /dev/null +++ b/scripts/system/placePortals.js @@ -0,0 +1,204 @@ +// +// placePortals.js +// +// Created by Ada on 2025-11-07 +// Copyright 2025 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 +"use strict"; + +const PORTAL_LIFETIME_SECS = 20; +const MESSAGE_CHANNEL = "org.overte.PlacePortal"; + +// convenience function because QML can't use the vec3 or quat we normally use +const CREATE_MESSAGE_CHANNEL = "org.overte.PlacePortal.Create"; + +const ROOT_DEFAULT_PROPS = { + type: "Shape", + shape: "Cylinder", + grab: { grabbable: false }, + ignorePickIntersection: true, + dimensions: [1, 2, 1], + alpha: 0, +}; + +const VISUAL_DEFAULT_PROPS = { + type: "ParticleEffect", + // FIXME: why is this oriented weird? + emitDimensions: [0.5, 0.5, 1.5], + textures: Script.resolvePath("./places/icons/portalFX.png"), + emitRate: 100, + lifespan: 3, + maxParticles: 500, + polarStart: 0, + polarFinish: Math.PI, + emitAcceleration: [0, 0, 0], + radiusStart: 1.0, + particleRadius: 0.5, + radiusFinish: 0.3, + alphaStart: 0.1, + alpha: 0.1, + alphaFinish: 0.1, + emitSpeed: -0.1, + speedSpread: 0, + colorStart: [255, 0, 0], + color: [255, 0, 0], + colorFinish: [255, 255, 255], +}; + +const TITLE_DEFAULT_PROPS = { + type: "Text", + grab: { grabbable: false }, + ignorePickIntersection: true, + dimensions: [1, 0.5, 0.1], + localPosition: [0, 1, 0], + billboardMode: "yaw", + backgroundAlpha: 0.0, + lineHeight: 0.1, + unlit: true, + textColor: "white", + textEffect: "outline fill", + textEffectColor: "black", + textEffectThickness: 0.3, + alignment: "center", +}; + +// key is the UUID of the collider, +// value is { +// titleEntity: UUID, +// visualEntity: UUID, +// placeUrl: string, +// onTick: function, +// lifetime: int, +// tickInterval: setInterval, +// } +let portalInfo = new Map(); + +function deleteAllPortals() { + for (const [id, props] of portalInfo) { + Entities.deleteEntity(id); + Entities.deleteEntity(props.titleEntity); + } + + portalInfo.clear(); +} + +function colorHash(x) { + const FNV_PRIME = 0x01000193; + const FNV_OFFSET = 0x811c9dc5; + + let value = FNV_OFFSET; + for (const c of x) { + value ^= c; + value = (value * FNV_PRIME) & 0xffffffff; + } + value /= 0x7fffffff; + + const TAU = 2 * Math.PI; + + const r = 127 * Math.sin((value - (1 / 3)) * TAU) + 128; + const g = 127 * Math.sin((value + 0) * TAU) + 128; + const b = 127 * Math.sin((value + (1 / 3)) * TAU) + 128; + + return [r, g, b]; +} + +function createPortal(placeName, placeUrl, position) { + const color = colorHash(placeName); + + // TODO: translation support + const goingToText = `Going to ${placeName}`; + + const root = Entities.addEntity({ + position: Vec3.sum(position, [0, 1, 0]), + // enterEntity can't be connected to through Entities, is that a bug? + script: `(function(){ + this.enterEntity = _id => { + Window.displayAnnouncement(${JSON.stringify(goingToText)}); + location.handleLookupString(${JSON.stringify(placeUrl)}); + }; +})`, + ...ROOT_DEFAULT_PROPS, + }, "local"); + + const title = Entities.addEntity({ + parentID: root, + text: `${placeName}\n${PORTAL_LIFETIME_SECS}`, + ...TITLE_DEFAULT_PROPS, + }, "local"); + + const visual = Entities.addEntity({ + parentID: root, + ...VISUAL_DEFAULT_PROPS, + colorStart: color, + color: color, + }, "local"); + + let props = { + rootEntity: root, + titleEntity: title, + visualEntity: visual, + placeUrl: placeUrl, + + lifetime: PORTAL_LIFETIME_SECS, + + onTick() { + this.lifetime -= 1; + Entities.editEntity(this.titleEntity, { text: `${placeName}\n${this.lifetime}` }); + + if (this.lifetime <= 0) { + portalInfo.delete(this.rootEntity); + Script.clearInterval(this.tickInterval); + + Entities.deleteEntity(this.titleEntity); + Entities.deleteEntity(this.visualEntity); + Entities.deleteEntity(this.rootEntity); + } + }, + }; + + props.tickInterval = Script.setInterval(() => props.onTick(), 1000); + + portalInfo.set(root, props); +} + +Messages.messageReceived.connect((channel, rawMsg, _senderID, _localOnly) => { + if (channel === MESSAGE_CHANNEL) { + try { + const data = JSON.parse(rawMsg); + + if (data.place_name && data.place_url && data.position) { + createPortal(data.place_name, data.place_url, data.position); + } else { + console.warn(MESSAGE_CHANNEL, "Necessary data missing, can't create portal"); + } + } catch (e) { console.error(e); } + } else if (channel === CREATE_MESSAGE_CHANNEL) { + try { + const data = JSON.parse(rawMsg); + + if (data.place_name && data.place_url) { + createPortal( + data.place_name, + data.place_url, + Vec3.sum( + MyAvatar.feetPosition, + Vec3.multiply( + 1.5 * MyAvatar.sensorToWorldScale, + Quat.getForward(MyAvatar.orientation) + ) + ) + ); + } else { + console.warn(MESSAGE_CHANNEL, "Necessary data missing, can't create portal"); + } + } catch (e) { console.error(e); } + } +}); + +Messages.subscribe(MESSAGE_CHANNEL); +Script.scriptEnding.connect(() => deleteAllPortals()); +Window.domainChanged.connect(_url => deleteAllPortals()); From f5e02e1d5ff47e1a985dcb92d0bb9d518c553b26 Mon Sep 17 00:00:00 2001 From: Ada Date: Sat, 8 Nov 2025 04:27:56 +1000 Subject: [PATCH 103/111] Fix avatar app edit having empty fields if the "add new" button was clicked --- interface/resources/qml/overte/avatar_picker/AvatarItem.qml | 4 +--- interface/resources/qml/overte/avatar_picker/AvatarPicker.qml | 3 +++ 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/interface/resources/qml/overte/avatar_picker/AvatarItem.qml b/interface/resources/qml/overte/avatar_picker/AvatarItem.qml index 2c6ddf08e32..d4f957c70d6 100644 --- a/interface/resources/qml/overte/avatar_picker/AvatarItem.qml +++ b/interface/resources/qml/overte/avatar_picker/AvatarItem.qml @@ -108,8 +108,6 @@ Item { icon.height: 32 icon.color: Overte.Theme.paletteActive.buttonText - onClicked: { - root.requestEdit(item.index); - } + 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 index 98bc4ca4b1f..f0833f4f675 100644 --- a/interface/resources/qml/overte/avatar_picker/AvatarPicker.qml +++ b/interface/resources/qml/overte/avatar_picker/AvatarPicker.qml @@ -230,6 +230,9 @@ Rectangle { editDialog.avatarName = avatarModel[index].name; editDialog.avatarUrl = avatarModel[index].avatarUrl; editDialog.avatarDescription = avatarModel[index].description; + avatarNameField.text = editDialog.avatarName; + avatarUrlField.text = editDialog.avatarUrl; + avatarDescriptionField.text = editDialog.avatarDescription; editDialog.open(); } From 6c59bb51bc0021c128f7f61a65464a8df53ca25c Mon Sep 17 00:00:00 2001 From: Ada Date: Sun, 9 Nov 2025 01:33:53 +1000 Subject: [PATCH 104/111] Widen portal text entity, update todo list --- interface/resources/qml/overte/README.md | 6 ++++-- scripts/system/placePortals.js | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/interface/resources/qml/overte/README.md b/interface/resources/qml/overte/README.md index 5bc55a4150d..4fb88540c35 100644 --- a/interface/resources/qml/overte/README.md +++ b/interface/resources/qml/overte/README.md @@ -4,14 +4,16 @@ - [ ] 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 -- [ ] World list +- [x] World list ## Low -- [ ] More app +- [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/scripts/system/placePortals.js b/scripts/system/placePortals.js index 97d192a2422..adb976080fc 100644 --- a/scripts/system/placePortals.js +++ b/scripts/system/placePortals.js @@ -53,7 +53,7 @@ const TITLE_DEFAULT_PROPS = { type: "Text", grab: { grabbable: false }, ignorePickIntersection: true, - dimensions: [1, 0.5, 0.1], + dimensions: [2, 0.5, 0.1], localPosition: [0, 1, 0], billboardMode: "yaw", backgroundAlpha: 0.0, From 9ddc48875b8e08a5b7b61b9efcc287633b0cde53 Mon Sep 17 00:00:00 2001 From: Ada Date: Sun, 9 Nov 2025 04:36:19 +1000 Subject: [PATCH 105/111] Restore HTML places app and shelve the QML one for later --- scripts/defaultScripts.js | 2 +- scripts/system/systemApps.js | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/scripts/defaultScripts.js b/scripts/defaultScripts.js index 22c0b80367b..ad672cb4f9e 100644 --- a/scripts/defaultScripts.js +++ b/scripts/defaultScripts.js @@ -37,7 +37,7 @@ var DEFAULT_SCRIPTS_COMBINED = [ var DEFAULT_SCRIPTS_SEPARATE = [ "system/systemApps.js", "system/chat.js", - "system/placePortals.js", + "system/places/places.js", "system/controllers/controllerScripts.js", "system/controllers/squeezeHands.js", "communityScripts/notificationCore/notificationCore.js", diff --git a/scripts/system/systemApps.js b/scripts/system/systemApps.js index bdf40af9756..5ae59e5b199 100644 --- a/scripts/system/systemApps.js +++ b/scripts/system/systemApps.js @@ -135,7 +135,8 @@ const SYSTEM_APPS = { appButton: null, }, - places: { + // TODO: to be picked up again in a later PR + /*places: { appButtonData: { sortOrder: 6, isActive: false, @@ -149,7 +150,7 @@ const SYSTEM_APPS = { qmlSource: "overte/place_picker/PlacePicker.qml", appButton: null, - }, + },*/ more: { appButtonData: { From 1d7063d9caee798bda915e30c796e8bd336a4a11 Mon Sep 17 00:00:00 2001 From: Ada Date: Sat, 15 Nov 2025 03:42:16 +1000 Subject: [PATCH 106/111] External app list support in More app --- .../qml/overte/chat/SettingsPage.qml | 2 +- .../qml/overte/more_apps/MoreApps.qml | 372 ++++++++++++++---- 2 files changed, 302 insertions(+), 72 deletions(-) diff --git a/interface/resources/qml/overte/chat/SettingsPage.qml b/interface/resources/qml/overte/chat/SettingsPage.qml index 943a0eb082a..9ebf475a4fc 100644 --- a/interface/resources/qml/overte/chat/SettingsPage.qml +++ b/interface/resources/qml/overte/chat/SettingsPage.qml @@ -21,7 +21,7 @@ Column { horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter - text: qsTr("Chat Settings") + text: qsTr("Settings - Chat") } Overte.Button { diff --git a/interface/resources/qml/overte/more_apps/MoreApps.qml b/interface/resources/qml/overte/more_apps/MoreApps.qml index 5c840a3b904..1abc6464cf6 100644 --- a/interface/resources/qml/overte/more_apps/MoreApps.qml +++ b/interface/resources/qml/overte/more_apps/MoreApps.qml @@ -16,9 +16,7 @@ Rectangle { id: settings category: "moreApp" property list installedScripts: [] - property list repoSources: [ - "https://more.overte.org", - ] + property list repoSources: [] } property alias installedScripts: settings.installedScripts @@ -79,19 +77,20 @@ Rectangle { onRawListModelChanged: refreshFilteredModel() - function parseList(list) { + function parseList(list, repo) { if (list.version !== 2) { console.warn(`App list "${list}" is version ${list.version}, expected 2`); return; } - const repoHost = (new URL(list.baseApiUrl)).hostname; + const repoHost = (new URL(repo)).hostname; let tmp = []; for (const item of list.applicationList) { if (!item.appActive) { continue; } - const scriptUrl = `${list.baseApiUrl}/${item.appBaseDirectory}/${item.appScriptVersions.Stable}`; + const baseUrl = `${repo}/applications`; + const scriptUrl = `${baseUrl}/${item.appBaseDirectory}/${item.appScriptVersions.Stable}`; tmp.push({ appID: item.appBaseDirectory, @@ -99,7 +98,7 @@ Rectangle { description: item.appDescription, scriptUrl: scriptUrl, author: item.appAuthor, - icon: `${list.baseApiUrl}/${item.appBaseDirectory}/${item.appIcon}`, + icon: `${baseUrl}/${item.appBaseDirectory}/${item.appIcon}`, repoHost: repoHost, }); } @@ -107,28 +106,72 @@ Rectangle { 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.DONE) { - let data; + 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 { - data = JSON.parse(xhr.response); + const data = JSON.parse(xhr.response); + parseList(data, repo); } catch(e) { console.error(repo, e); - return; } - parseList(data); + + 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); @@ -140,88 +183,275 @@ Rectangle { fetchAllLists(); } - ColumnLayout { - anchors.fill: parent + 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(); + } - RowLayout { - Layout.margins: 4 + Component { + id: settingsPage + + ColumnLayout { + RowLayout { + Layout.fillWidth: true - // TODO - Overte.RoundButton { - icon.source: "../icons/settings_cog.svg" - icon.width: 24 - icon.height: 24 + 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") + } + } - onClicked: console.warn("TODO") - visible: false + Item { + Layout.fillWidth: true + implicitHeight: 12 } - Overte.RoundButton { - // TODO - icon.source: checked ? "../icons/eye_closed.svg" : "../icons/eye_open.svg" - icon.width: 24 - icon.height: 24 - - id: onlyInstalled - checkable: true - onToggled: refreshFilteredModel() - - Overte.ToolTip { text: qsTr("Only show installed and running apps") } + Overte.Label { + Layout.fillWidth: true + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignBottom + text: qsTr("App Sources") + opacity: Overte.Theme.highContrast ? 1.0 : 0.6 } - Overte.TextField { + Overte.Ruler { Layout.fillWidth: true } + + ListView { Layout.fillWidth: true - placeholderText: qsTr("Search…") - id: searchField - Keys.onEnterPressed: { - searchButton.clicked(); - forceActiveFocus(); - } + // QT6TODO: remove this when mouse inputs are working properly + interactive: false - Keys.onReturnPressed: { - searchButton.clicked(); - forceActiveFocus(); + 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() + } + } } } - Overte.RoundButton { - icon.source: "../icons/search.svg" - icon.width: 24 - icon.height: 24 - icon.color: Overte.Theme.paletteActive.buttonText - id: searchButton + 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.searchExpression = searchField.text; - moreApps.refreshFilteredModel(); + 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() + + 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 - 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 + // 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 - ListView { - Layout.fillWidth: true - Layout.fillHeight: true - visible: rawListModel.length > 0 - clip: true + id: downloadProgressLabel + visible: Object.values(pendingRequests).length !== 0 - // QT6TODO: broken mouse input, remove when that's fixed - interactive: false + function updateText() { + const requests = Object.values(pendingRequests); - ScrollBar.vertical: Overte.ScrollBar {} - contentWidth: width - ScrollBar.vertical.width + if (requests.length === 0) { + this.text = ""; + return; + } + + let accum = 0; + + for (const req of requests) { + accum = (req.currentSize / req.expectedSize); + } - model: filteredModel - delegate: AppDelegate {} + const percent = Math.round((accum / requests.length) * 100); + this.text = `${qsTr("Downloading…")} ${percent}%`; } } } From a09cdcbb41e6de7dcb2b24c6e57488404d1f451d Mon Sep 17 00:00:00 2001 From: Ada Date: Sat, 15 Nov 2025 22:39:04 +1000 Subject: [PATCH 107/111] More settings --- interface/resources/qml/overte/WidgetZoo.qml | 8 +- .../qml/overte/settings/pages/General.qml | 11 +++ .../qml/overte/settings/pages/Graphics.qml | 99 +++++++++++++++++++ 3 files changed, 112 insertions(+), 6 deletions(-) diff --git a/interface/resources/qml/overte/WidgetZoo.qml b/interface/resources/qml/overte/WidgetZoo.qml index 3606633cee3..1fc2c416219 100644 --- a/interface/resources/qml/overte/WidgetZoo.qml +++ b/interface/resources/qml/overte/WidgetZoo.qml @@ -6,16 +6,12 @@ import QtQuick.Controls import "." as Overte // debugging test case to view the themed widgets -Item { +Rectangle { id: root width: 480 height: 720 visible: true - - Rectangle { - anchors.fill: parent - color: Overte.Theme.paletteActive.base - } + color: Overte.Theme.paletteActive.base Overte.TabBar { anchors.left: parent.left diff --git a/interface/resources/qml/overte/settings/pages/General.qml b/interface/resources/qml/overte/settings/pages/General.qml index 5ff32a56952..e23139cb223 100644 --- a/interface/resources/qml/overte/settings/pages/General.qml +++ b/interface/resources/qml/overte/settings/pages/General.qml @@ -119,6 +119,17 @@ SettingsPage { onCurrentIndexChanged: SettingsInterface.setValue("simplifiedNametag/avatarNametagMode", model[currentIndex].value) } + SliderSetting { + text: qsTr("VR Tablet Scale") + stepSize: 5 + from: 50 + to: 150 + valueToText: () => `${value}%` + + value: SettingsInterface.getValue("hmdTabletScale", 75) + onValueChanged: SettingsInterface.setValue("hmdTabletScale", value) + } + Header { text: qsTr("Screenshots") } FolderSetting { diff --git a/interface/resources/qml/overte/settings/pages/Graphics.qml b/interface/resources/qml/overte/settings/pages/Graphics.qml index 13050a5ec8c..6333cef4a2a 100644 --- a/interface/resources/qml/overte/settings/pages/Graphics.qml +++ b/interface/resources/qml/overte/settings/pages/Graphics.qml @@ -63,6 +63,13 @@ SettingsPage { onValueChanged: Render.proceduralMaterialsEnabled = value } + SwitchSetting { + text: qsTr("Allow third-person camera to pass through walls") + + value: !Render.getCameraClippingEnabled() + onValueChanged: !Render.setCameraClippingEnabled(value) + } + Header { text: qsTr("Advanced") } SettingNote { @@ -100,5 +107,97 @@ SettingsPage { value: Render.ambientOcclusionEnabled onValueChanged: Render.ambientOcclusionEnabled = 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) + } } From 81813843b116696c92179026a7c593ca8e949be6 Mon Sep 17 00:00:00 2001 From: Ada Date: Sun, 16 Nov 2025 01:37:51 +1000 Subject: [PATCH 108/111] Remove YouTube forced 30 FPS on Web entities, WidgetZoo scroll background --- interface/resources/qml/overte/WidgetZoo.qml | 7 +++++++ .../src/RenderableWebEntityItem.cpp | 16 +++------------- 2 files changed, 10 insertions(+), 13 deletions(-) diff --git a/interface/resources/qml/overte/WidgetZoo.qml b/interface/resources/qml/overte/WidgetZoo.qml index 1fc2c416219..c2d08878a50 100644 --- a/interface/resources/qml/overte/WidgetZoo.qml +++ b/interface/resources/qml/overte/WidgetZoo.qml @@ -133,6 +133,13 @@ Rectangle { 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/libraries/entities-renderer/src/RenderableWebEntityItem.cpp b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp index c3330b7078e..c69c334c015 100644 --- a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp @@ -54,8 +54,6 @@ const float METERS_TO_INCHES = 39.3701f; // If a web-view hasn't been rendered for 30 seconds, de-allocate the framebuffer static uint64_t MAX_NO_RENDER_INTERVAL = 30 * USECS_PER_SECOND; -static uint8_t YOUTUBE_MAX_FPS = 30; - // Don't allow more than 20 concurrent web views static std::atomic _currentWebCount(0); static const uint32_t MAX_CONCURRENT_WEB_VIEWS = 20; @@ -283,7 +281,7 @@ void WebEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& scene if (_webSurface) { if (_webSurface->getRootItem()) { - if (_contentType == ContentType::HtmlContent && _sourceURL != newSourceURL) { + if (_sourceURL != newSourceURL) { if (localSafeContext) { ::hifi::scripting::setLocalAccessSafeThread(true); } @@ -292,11 +290,9 @@ void WebEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& scene _webSurface->getRootItem()->setProperty(USE_BACKGROUND_PROPERTY, _useBackground); _webSurface->getRootItem()->setProperty(USER_AGENT_PROPERTY, _userAgent); _webSurface->getSurfaceContext()->setContextProperty(GLOBAL_POSITION_PROPERTY, vec3toVariant(_contextPosition)); - _webSurface->setMaxFps((QUrl(newSourceURL).host().endsWith("youtube.com", Qt::CaseInsensitive)) ? YOUTUBE_MAX_FPS : _maxFPS); + _webSurface->setMaxFps(_maxFPS); ::hifi::scripting::setLocalAccessSafeThread(false); _sourceURL = newSourceURL; - } else if (_contentType != ContentType::HtmlContent) { - _sourceURL = newSourceURL; } { @@ -310,13 +306,7 @@ void WebEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& scene { auto maxFPS = entity->getMaxFPS(); if (_maxFPS != maxFPS) { - // We special case YouTube URLs since we know they are videos that we should play with at least 30 FPS. - // FIXME this doesn't handle redirects or shortened URLs, consider using a signaling method from the web entity - if (QUrl(_sourceURL).host().endsWith("youtube.com", Qt::CaseInsensitive)) { - _webSurface->setMaxFps(YOUTUBE_MAX_FPS); - } else { - _webSurface->setMaxFps(maxFPS); - } + _webSurface->setMaxFps(maxFPS); _maxFPS = maxFPS; } } From d552a22ffdf4e14656bf2779381d10d19dd010fa Mon Sep 17 00:00:00 2001 From: Ada Date: Tue, 25 Nov 2025 15:09:36 +1000 Subject: [PATCH 109/111] Image embeds in chat app --- interface/resources/qml/overte/BodyText.qml | 8 +- interface/resources/qml/overte/ToolTip.qml | 5 +- interface/resources/qml/overte/chat/Chat.qml | 42 +++---- .../resources/qml/overte/chat/ChatPage.qml | 106 ++++++++++++----- .../qml/overte/chat/MessageBlock.qml | 111 ++++++++++++++++-- .../qml/overte/chat/SettingsPage.qml | 13 -- .../resources/qml/overte/icons/broadcast.svg | 2 + .../qml/overte/icons_src/broadcast.svg | 103 ++++++++++++++++ .../qml/overte/more_apps/MoreApps.qml | 11 ++ 9 files changed, 325 insertions(+), 76 deletions(-) create mode 100644 interface/resources/qml/overte/icons/broadcast.svg create mode 100644 interface/resources/qml/overte/icons_src/broadcast.svg diff --git a/interface/resources/qml/overte/BodyText.qml b/interface/resources/qml/overte/BodyText.qml index c90341a5daf..303301567bd 100644 --- a/interface/resources/qml/overte/BodyText.qml +++ b/interface/resources/qml/overte/BodyText.qml @@ -17,7 +17,13 @@ TextEdit { selectedTextColor: Theme.paletteActive.highlightedText selectionColor: Theme.paletteActive.highlight - // TODO: handle embedded links + // 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/ToolTip.qml b/interface/resources/qml/overte/ToolTip.qml index 8f59c13ee79..01391f8204f 100644 --- a/interface/resources/qml/overte/ToolTip.qml +++ b/interface/resources/qml/overte/ToolTip.qml @@ -4,9 +4,12 @@ import "." ToolTip { id: control - visible: parent.hovered 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 diff --git a/interface/resources/qml/overte/chat/Chat.qml b/interface/resources/qml/overte/chat/Chat.qml index bd2e35d5e09..d46f2062571 100644 --- a/interface/resources/qml/overte/chat/Chat.qml +++ b/interface/resources/qml/overte/chat/Chat.qml @@ -25,46 +25,36 @@ Rectangle { property list eventsLog: [] Component.onCompleted: { - const savedEvents = SettingsInterface.getValue("private/chat/eventsLog") ?? []; + // fullPrivate so it's never accessible from other scripts, + // we don't want entity client scripts that are able to scrape chat history + const savedEvents = SettingsInterface.getValue("fullPrivate/chat/eventsLog") ?? []; for (let event of savedEvents) { fromScript(event); } } Component.onDestruction: { - SettingsInterface.setValue("private/chat/eventsLog", eventsLog); + SettingsInterface.setValue("fullPrivate/chat/eventsLog", eventsLog); } - onMessagesCleared: eventsLog = [] + onMessagesCleared: { + eventsLog = []; + SettingsInterface.setValue("fullPrivate/chat/eventsLog", eventsLog); + } - signal messagePushed(name: string, body: string, time: string) - signal notificationPushed(text: string, time: string) + // 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)); - - // for debugging standalone with the qml tool - /*console.debug(JSON.stringify(obj)); - - switch (obj.event) { - case "send_message": - fromScript({event: "recv_message", name: "ada.tv", body: obj.body}); - break; - - case "start_typing": - fromScript({event: "start_typing", name: "ada.tv", uuid: "ba"}); - break; - - case "end_typing": - fromScript({event: "end_typing", name: "ada.tv", uuid: "ba"}); - break; - }*/ } function fromScript(rawObj) { let obj = (typeof(rawObj) === "string") ? JSON.parse(rawObj) : rawObj; - const timestamp = (obj.timestamp ? new Date(obj.timestamp) : new Date()).toLocaleTimeString(undefined, Locale.ShortFormat); + const timestamp = obj.timestamp ?? Date.now(); // keep chat events in the log if ( @@ -76,11 +66,15 @@ Rectangle { obj.timestamp = Date.now(); } eventsLog.push(obj); + + // TODO: is this a performance problem? i'm not sure how else we could handle this robustly + // FIXME: every time this is set it logs "SettingsScriptingInterface::setValue -- allowing restricted write" + SettingsInterface.setValue("fullPrivate/chat/eventsLog", eventsLog); } switch (obj.event) { case "recv_message": - messagePushed(obj.name ?? "", obj.body, timestamp); + messagePushed(obj.name ? obj.name : "", obj.body, timestamp); break; case "user_joined": if (settingJoinNotifications) { diff --git a/interface/resources/qml/overte/chat/ChatPage.qml b/interface/resources/qml/overte/chat/ChatPage.qml index 40a3b2ae0b3..d9cfd6ee440 100644 --- a/interface/resources/qml/overte/chat/ChatPage.qml +++ b/interface/resources/qml/overte/chat/ChatPage.qml @@ -12,14 +12,56 @@ ColumnLayout { RowLayout { Layout.fillWidth: true - Overte.Switch { - id: broadcastSwitch - text: qsTr("Broadcast") + // push everything to the right + Item { Layout.fillWidth: true } - Overte.ToolTip { - text: qsTr("Whether your messages will be broadcast across the whole domain, rather than limited to a local range.") - } + 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; @@ -27,28 +69,26 @@ ColumnLayout { } } - Item { Layout.fillWidth: true } - Overte.RoundButton { - Layout.alignment: Qt.AlignRight + 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"); - } + onClicked: stack.push("./SettingsPage.qml") } } Overte.Label { Layout.fillWidth: true Layout.fillHeight: true - visible: chatLog.model.count === 0 + visible: chatLog.model.length === 0 id: noMessagesLabel text: qsTr("No messages") @@ -68,51 +108,59 @@ ColumnLayout { anchors.top: parent.top anchors.bottom: parent.bottom } - visible: chatLog.model.count > 0 + visible: chatLog.model.length > 0 ListView { id: chatLog clip: true spacing: 8 - model: ListModel {} + model: [] delegate: MessageBlock {} Connections { target: root function onMessagesCleared() { - chatLog.model.clear(); + chatLog.model = []; } function onMessagePushed(name, body, timestamp) { - chatLog.model.append({ + const imageUrlRegex = /(https?:\/\/\S+\.(?:png|jpg|jpeg|gif|webp|svg)\S*)/gmi; + let detectedImageUrls = body.match(imageUrlRegex) ?? []; + + chatLog.model.push({ name: name, body: ( body - .replace(/\&/gi, "&") - .replace(/\[/gi, "[") - .replace(/\]/gi, "]") - .replace(/\/gi, ">") - .replace(/\'/gi, "'") - .replace(/\"/gi, """) - .replace(/\n/gi, "
    ") + .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, }); - chatLog.currentIndex = chatLog.model.count - 1; + chatLog.currentIndex = chatLog.model.length - 1; } function onNotificationPushed(text, timestamp) { - chatLog.model.append({ + chatLog.model.push({ name: "", body: "", notification: text, timestamp: timestamp, + imageEmbeds: [], }); - chatLog.currentIndex = chatLog.model.count - 1; + chatLog.currentIndex = chatLog.model.length - 1; } } } @@ -165,7 +213,7 @@ ColumnLayout { Overte.TextArea { id: messageInput placeholderText: ( - broadcastSwitch.checked ? + root.settingBroadcast ? qsTr("Broadcast chat message…") : qsTr("Local chat message…") ) diff --git a/interface/resources/qml/overte/chat/MessageBlock.qml b/interface/resources/qml/overte/chat/MessageBlock.qml index a12958dc6b5..29f43f0d7ca 100644 --- a/interface/resources/qml/overte/chat/MessageBlock.qml +++ b/interface/resources/qml/overte/chat/MessageBlock.qml @@ -8,7 +8,10 @@ ColumnLayout { required property string name required property string body required property string notification - required property string timestamp + required property real timestamp + required property list imageEmbeds + + id: messageBlock anchors.left: parent ? parent.left : undefined anchors.right: parent ? parent.right : undefined @@ -33,31 +36,57 @@ ColumnLayout { 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 - Layout.fillWidth: true - wrapMode: Text.Wrap + 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: timestamp + 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, the whole text line would be selectable - // with this hack we only use the width we really need to + // 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. + // 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 @@ -67,10 +96,76 @@ ColumnLayout { return ( body .replace( - /(https?:\/\/[^\s]+)/gi, + /(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 index 9ebf475a4fc..b014635307a 100644 --- a/interface/resources/qml/overte/chat/SettingsPage.qml +++ b/interface/resources/qml/overte/chat/SettingsPage.qml @@ -68,17 +68,4 @@ Column { root.sendSettingsUpdate(); } } - - RowLayout { - anchors.left: parent.left - anchors.right: parent.right - - Overte.Button { - Layout.alignment: Qt.AlignHCenter - text: qsTr("Clear History") - backgroundColor: Overte.Theme.paletteActive.buttonDestructive - - onClicked: { root.messagesCleared(); } - } - } } 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_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/more_apps/MoreApps.qml b/interface/resources/qml/overte/more_apps/MoreApps.qml index 1abc6464cf6..7adff94d1c9 100644 --- a/interface/resources/qml/overte/more_apps/MoreApps.qml +++ b/interface/resources/qml/overte/more_apps/MoreApps.qml @@ -367,6 +367,17 @@ Rectangle { 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") } } From 2e2afb1bcf9d37f5a6b0de0ab54f307cc9d0042e Mon Sep 17 00:00:00 2001 From: RTUnreal Date: Sun, 21 Sep 2025 21:39:54 +0200 Subject: [PATCH 110/111] nix: update to qt6 --- flake.lock | 18 +++++++++--------- flake.nix | 4 +++- nix/overte.nix | 14 ++++++-------- 3 files changed, 18 insertions(+), 18 deletions(-) diff --git a/flake.lock b/flake.lock index cc94b9578f5..e94db617d34 100644 --- a/flake.lock +++ b/flake.lock @@ -5,11 +5,11 @@ "nixpkgs-lib": "nixpkgs-lib" }, "locked": { - "lastModified": 1754487366, - "narHash": "sha256-pHYj8gUBapuUzKV/kN/tR3Zvqc7o6gdFB9XKXIp1SQ8=", + "lastModified": 1767609335, + "narHash": "sha256-feveD98mQpptwrAEggBQKJTYbvwwglSbOv53uCfH9PY=", "owner": "hercules-ci", "repo": "flake-parts", - "rev": "af66ad14b28a127c5c0f3bbb298218fc63528a18", + "rev": "250481aafeb741edfe23d29195671c19b36b6dca", "type": "github" }, "original": { @@ -20,11 +20,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1756542300, - "narHash": "sha256-tlOn88coG5fzdyqz6R93SQL5Gpq+m/DsWpekNFhqPQk=", + "lastModified": 1767640445, + "narHash": "sha256-UWYqmD7JFBEDBHWYcqE6s6c77pWdcU/i+bwD6XxMb8A=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "d7600c775f877cd87b4f5a831c28aa94137377aa", + "rev": "9f0c42f8bc7151b8e7e5840fb3bd454ad850d8c5", "type": "github" }, "original": { @@ -36,11 +36,11 @@ }, "nixpkgs-lib": { "locked": { - "lastModified": 1753579242, - "narHash": "sha256-zvaMGVn14/Zz8hnp4VWT9xVnhc8vuL3TStRqwk22biA=", + "lastModified": 1765674936, + "narHash": "sha256-k00uTP4JNfmejrCLJOwdObYC9jHRrr/5M/a/8L2EIdo=", "owner": "nix-community", "repo": "nixpkgs.lib", - "rev": "0f36c44e01a6129be94e3ade315a5883f0228a6e", + "rev": "2075416fcb47225d9b68ac469a5c4801a9c4dd85", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index 4e95ccfcffd..6690c711e14 100644 --- a/flake.nix +++ b/flake.nix @@ -67,8 +67,10 @@ ]; inputsFrom = [ self'.packages.overte-full ]; - buildInputs = [ pkgs.libsForQt5.full ]; + buildInputs = [ (pkgs.qt6.env "overte-devenv" [ self'.packages.overte-full.buildInputs ]) ]; + # TODO: remote set QT_QPA_PLATOFORM, when wayland works + QT_QPA_PLATFORM = "xcb"; inherit (self'.packages.overte-full) NVTT_DIR CXXFLAGS diff --git a/nix/overte.nix b/nix/overte.nix index efaa42ce787..0f6b029d46f 100644 --- a/nix/overte.nix +++ b/nix/overte.nix @@ -29,7 +29,7 @@ openxr-loader, SDL2, libopus, - libsForQt5, + qt6Packages, libv8, # tools for shader compilation @@ -52,7 +52,7 @@ stdenv.mkDerivation { cmake pkg-config python3 - libsForQt5.wrapQtAppsHook + qt6Packages.wrapQtAppsHook nodejs autoPatchelfHook ]; @@ -60,20 +60,16 @@ stdenv.mkDerivation { # TODO: make dependencies minimal for !buildClient buildInputs = builtins.attrValues { - inherit (libsForQt5) + inherit (qt6Packages) qtbase qtmultimedia qtdeclarative qtwebsockets qtsvg quazip - ; - inherit (libsForQt5.qt5) qtwebchannel qtwebengine - qtxmlpatterns - qtquickcontrols2 - qtgraphicaleffects + qt5compat ; } ++ [ @@ -120,6 +116,7 @@ stdenv.mkDerivation { dontWrapQtApps = true; # TODO: remove set QT_PLUGIN_PATH after qt6 update + # TODO: remote set QT_QPA_PLATOFORM, when wayland works installPhase = '' runHook preInstall @@ -140,6 +137,7 @@ stdenv.mkDerivation { ln -s "$I"/interface $out/bin/overte-client makeWrapper "$I"/interface $out/bin/overte-client \ --set QT_PLUGIN_PATH ''' \ + --set QT_QPA_PLATFORM 'xcb' \ "''${qtWrapperArgs[@]}" '' ) From af1f0b91db949be6e5c9098ebda614919ae8dfbc Mon Sep 17 00:00:00 2001 From: RTUnreal Date: Sat, 15 Nov 2025 14:25:13 +0100 Subject: [PATCH 111/111] downgrade to cmake 3 --- flake.nix | 9 +- nix/cmake3/000-nixpkgs-cmake-prefix-path.diff | 28 +++ nix/cmake3/001-search-path.diff | 95 +++++++ ...ore-target-properties-from-pkg-config.diff | 32 +++ ...ted-type-for-CURLOPT_PROXYTYPE-values.diff | 13 + nix/cmake3/check-pc-files-hook.sh | 18 ++ nix/cmake3/default.nix | 232 ++++++++++++++++++ nix/cmake3/setup-hook.sh | 188 ++++++++++++++ 8 files changed, 611 insertions(+), 4 deletions(-) create mode 100644 nix/cmake3/000-nixpkgs-cmake-prefix-path.diff create mode 100644 nix/cmake3/001-search-path.diff create mode 100644 nix/cmake3/008-FindCURL-Add-more-target-properties-from-pkg-config.diff create mode 100644 nix/cmake3/009-cmCTestCurl-Avoid-using-undocumented-type-for-CURLOPT_PROXYTYPE-values.diff create mode 100644 nix/cmake3/check-pc-files-hook.sh create mode 100644 nix/cmake3/default.nix create mode 100644 nix/cmake3/setup-hook.sh diff --git a/flake.nix b/flake.nix index 6690c711e14..992c02657b2 100644 --- a/flake.nix +++ b/flake.nix @@ -24,14 +24,15 @@ }: { packages = { - glad = pkgs.callPackage ./nix/glad.nix { }; - etc2comp = pkgs.callPackage ./nix/etc2comp.nix { }; + cmake3 = pkgs.callPackage ./nix/cmake3 { }; + glad = pkgs.callPackage ./nix/glad.nix { cmake = self'.packages.cmake3; }; + etc2comp = pkgs.callPackage ./nix/etc2comp.nix { cmake = self'.packages.cmake3; }; cgltf = pkgs.callPackage ./nix/cgltf.nix { }; artery-font-format = pkgs.callPackage ./nix/artery-font-format.nix { }; - polyvox = pkgs.callPackage ./nix/polyvox.nix { }; + polyvox = pkgs.callPackage ./nix/polyvox.nix { cmake = self'.packages.cmake3; }; gif_creator = pkgs.callPackage ./nix/gif_creator.nix { }; @@ -55,7 +56,7 @@ # TODO: update/remove when overte updates to more modern version draco = pkgs.callPackage ./nix/draco.nix { }; - glm = pkgs.callPackage ./nix/glm.nix { }; + glm = pkgs.callPackage ./nix/glm.nix { cmake = self'.packages.cmake3; }; default = self'.packages.overte-full; }; diff --git a/nix/cmake3/000-nixpkgs-cmake-prefix-path.diff b/nix/cmake3/000-nixpkgs-cmake-prefix-path.diff new file mode 100644 index 00000000000..4ebdcced055 --- /dev/null +++ b/nix/cmake3/000-nixpkgs-cmake-prefix-path.diff @@ -0,0 +1,28 @@ +diff --git a/Source/cmFindBase.cxx b/Source/cmFindBase.cxx +index 8840cdcb..c34b7ee9 100644 +--- a/Source/cmFindBase.cxx ++++ b/Source/cmFindBase.cxx +@@ -280,6 +280,11 @@ void cmFindBase::FillCMakeEnvironmentPath() + // Add CMAKE_*_PATH environment variables + std::string var = cmStrCat("CMAKE_", this->CMakePathName, "_PATH"); + paths.AddEnvPrefixPath("CMAKE_PREFIX_PATH"); ++ if (this->CMakePathName != "PROGRAM") { ++ // Like CMAKE_PREFIX_PATH except when searching for programs. Programs need ++ // to be located via PATH ++ paths.AddEnvPrefixPath("NIXPKGS_CMAKE_PREFIX_PATH"); ++ } + paths.AddEnvPath(var); + + if (this->CMakePathName == "PROGRAM") { +diff --git a/Source/cmFindPackageCommand.cxx b/Source/cmFindPackageCommand.cxx +index 9b51b1ad..6acc676c 100644 +--- a/Source/cmFindPackageCommand.cxx ++++ b/Source/cmFindPackageCommand.cxx +@@ -2039,6 +2039,7 @@ void cmFindPackageCommand::FillPrefixesCMakeEnvironment() + + // And now the general CMake environment variables + paths.AddEnvPath("CMAKE_PREFIX_PATH"); ++ paths.AddEnvPath("NIXPKGS_CMAKE_PREFIX_PATH"); + if (this->DebugMode) { + debugBuffer = cmStrCat(debugBuffer, + "CMAKE_PREFIX_PATH env variable " diff --git a/nix/cmake3/001-search-path.diff b/nix/cmake3/001-search-path.diff new file mode 100644 index 00000000000..04ab0847a70 --- /dev/null +++ b/nix/cmake3/001-search-path.diff @@ -0,0 +1,95 @@ +diff --git a/Modules/Platform/UnixPaths.cmake b/Modules/Platform/UnixPaths.cmake +index b9381c3d7d..5e944640b5 100644 +--- a/Modules/Platform/UnixPaths.cmake ++++ b/Modules/Platform/UnixPaths.cmake +@@ -26,9 +26,6 @@ get_filename_component(_CMAKE_INSTALL_DIR "${_CMAKE_INSTALL_DIR}" PATH) + # please make sure to keep Help/variable/CMAKE_SYSTEM_PREFIX_PATH.rst + # synchronized + list(APPEND CMAKE_SYSTEM_PREFIX_PATH +- # Standard +- /usr/local /usr / +- + # CMake install location + "${_CMAKE_INSTALL_DIR}" + ) +@@ -47,48 +44,49 @@ endif() + + # Non "standard" but common install prefixes + list(APPEND CMAKE_SYSTEM_PREFIX_PATH +- /usr/X11R6 +- /usr/pkg +- /opt + ) + + # List common include file locations not under the common prefixes. ++if(DEFINED ENV{NIX_CC} ++ AND IS_DIRECTORY "$ENV{NIX_CC}" ++ AND EXISTS "$ENV{NIX_CC}/nix-support/orig-libc" ++ AND EXISTS "$ENV{NIX_CC}/nix-support/orig-libc-dev") ++ file(STRINGS "$ENV{NIX_CC}/nix-support/orig-libc" _nix_cmake_libc) ++ file(STRINGS "$ENV{NIX_CC}/nix-support/orig-libc-dev" _nix_cmake_libc_dev) ++else() ++ set(_nix_cmake_libc @libc_lib@) ++ set(_nix_cmake_libc_dev @libc_dev@) ++endif() ++ + list(APPEND CMAKE_SYSTEM_INCLUDE_PATH +- # X11 +- /usr/include/X11 ++ "${_nix_cmake_libc_dev}/include" + ) + + list(APPEND CMAKE_SYSTEM_LIBRARY_PATH +- # X11 +- /usr/lib/X11 ++ "${_nix_cmake_libc}/lib" + ) + + list(APPEND CMAKE_PLATFORM_IMPLICIT_LINK_DIRECTORIES +- /lib /lib32 /lib64 /usr/lib /usr/lib32 /usr/lib64 ++ "${_nix_cmake_libc}/lib" + ) + +-if(CMAKE_SYSROOT_COMPILE) +- set(_cmake_sysroot_compile "${CMAKE_SYSROOT_COMPILE}") +-else() +- set(_cmake_sysroot_compile "${CMAKE_SYSROOT}") +-endif() +- + # Default per-language values. These may be later replaced after + # parsing the implicit directory information from compiler output. + set(_CMAKE_C_IMPLICIT_INCLUDE_DIRECTORIES_INIT + ${CMAKE_C_IMPLICIT_INCLUDE_DIRECTORIES} +- "${_cmake_sysroot_compile}/usr/include" ++ "${_nix_cmake_libc_dev}/include" + ) + set(_CMAKE_CXX_IMPLICIT_INCLUDE_DIRECTORIES_INIT + ${CMAKE_CXX_IMPLICIT_INCLUDE_DIRECTORIES} +- "${_cmake_sysroot_compile}/usr/include" ++ "${_nix_cmake_libc_dev}/include" + ) + set(_CMAKE_CUDA_IMPLICIT_INCLUDE_DIRECTORIES_INIT + ${CMAKE_CUDA_IMPLICIT_INCLUDE_DIRECTORIES} +- "${_cmake_sysroot_compile}/usr/include" ++ "${_nix_cmake_libc_dev}/include" + ) + +-unset(_cmake_sysroot_compile) ++unset(_nix_cmake_libc) ++unset(_nix_cmake_libc_dev) + + # Reminder when adding new locations computed from environment variables + # please make sure to keep Help/variable/CMAKE_SYSTEM_PREFIX_PATH.rst +diff --git a/Modules/Platform/WindowsPaths.cmake b/Modules/Platform/WindowsPaths.cmake +index b9e2f17979..ab517cd4a7 100644 +--- a/Modules/Platform/WindowsPaths.cmake ++++ b/Modules/Platform/WindowsPaths.cmake +@@ -70,7 +70,7 @@ endif() + + if(CMAKE_CROSSCOMPILING AND NOT CMAKE_HOST_SYSTEM_NAME MATCHES "Windows") + # MinGW (useful when cross compiling from linux with CMAKE_FIND_ROOT_PATH set) +- list(APPEND CMAKE_SYSTEM_PREFIX_PATH /) ++ # list(APPEND CMAKE_SYSTEM_PREFIX_PATH /) + endif() + + list(APPEND CMAKE_SYSTEM_INCLUDE_PATH diff --git a/nix/cmake3/008-FindCURL-Add-more-target-properties-from-pkg-config.diff b/nix/cmake3/008-FindCURL-Add-more-target-properties-from-pkg-config.diff new file mode 100644 index 00000000000..8dfc354a9c2 --- /dev/null +++ b/nix/cmake3/008-FindCURL-Add-more-target-properties-from-pkg-config.diff @@ -0,0 +1,32 @@ +diff --git a/Modules/FindCURL.cmake b/Modules/FindCURL.cmake +index 5ce8a9046b..f7361308b7 100644 +--- a/Modules/FindCURL.cmake ++++ b/Modules/FindCURL.cmake +@@ -239,9 +239,24 @@ if(CURL_FOUND) + IMPORTED_LOCATION_DEBUG "${CURL_LIBRARY_DEBUG}") + endif() + +- if(CURL_USE_STATIC_LIBS AND MSVC) +- set_target_properties(CURL::libcurl PROPERTIES +- INTERFACE_LINK_LIBRARIES "normaliz.lib;ws2_32.lib;wldap32.lib") ++ if(PC_CURL_FOUND) ++ if(PC_CURL_LINK_LIBRARIES) ++ set_property(TARGET CURL::libcurl PROPERTY ++ INTERFACE_LINK_LIBRARIES "${PC_CURL_LINK_LIBRARIES}") ++ endif() ++ if(PC_CURL_LDFLAGS_OTHER) ++ set_property(TARGET CURL::libcurl PROPERTY ++ INTERFACE_LINK_OPTIONS "${PC_CURL_LDFLAGS_OTHER}") ++ endif() ++ if(PC_CURL_CFLAGS_OTHER) ++ set_property(TARGET CURL::libcurl PROPERTY ++ INTERFACE_COMPILE_OPTIONS "${PC_CURL_CFLAGS_OTHER}") ++ endif() ++ else() ++ if(CURL_USE_STATIC_LIBS AND MSVC) ++ set_target_properties(CURL::libcurl PROPERTIES ++ INTERFACE_LINK_LIBRARIES "normaliz.lib;ws2_32.lib;wldap32.lib") ++ endif() + endif() + + endif() diff --git a/nix/cmake3/009-cmCTestCurl-Avoid-using-undocumented-type-for-CURLOPT_PROXYTYPE-values.diff b/nix/cmake3/009-cmCTestCurl-Avoid-using-undocumented-type-for-CURLOPT_PROXYTYPE-values.diff new file mode 100644 index 00000000000..feabba28b0d --- /dev/null +++ b/nix/cmake3/009-cmCTestCurl-Avoid-using-undocumented-type-for-CURLOPT_PROXYTYPE-values.diff @@ -0,0 +1,13 @@ +diff --git a/Source/CTest/cmCTestCurl.h b/Source/CTest/cmCTestCurl.h +index 7836f4b9c78a1d103eee515d618856a6712b4480..9113890b5a12cb157b691b66d96e25d0fd4b50ef 100644 +--- a/Source/CTest/cmCTestCurl.h ++++ b/Source/CTest/cmCTestCurl.h +@@ -52,7 +52,7 @@ private: + std::vector HttpHeaders; + std::string HTTPProxyAuth; + std::string HTTPProxy; +- curl_proxytype HTTPProxyType; ++ long HTTPProxyType; + bool UseHttp10 = false; + bool Quiet = false; + int TimeOutSeconds = 0; diff --git a/nix/cmake3/check-pc-files-hook.sh b/nix/cmake3/check-pc-files-hook.sh new file mode 100644 index 00000000000..94d1b7b5355 --- /dev/null +++ b/nix/cmake3/check-pc-files-hook.sh @@ -0,0 +1,18 @@ +cmakePcfileCheckPhase() { + while IFS= read -rd $'\0' file; do + grepout=$(grep --line-number '}//nix/store' "$file" || true) + if [ -n "$grepout" ]; then + { + echo "Broken paths found in a .pc file! $file" + echo "The following lines have issues (specifically '//' in paths)." + echo "$grepout" + echo "It is very likely that paths are being joined improperly." + echo 'ex: "${prefix}/@CMAKE_INSTALL_LIBDIR@" should be "@CMAKE_INSTALL_FULL_LIBDIR@"' + echo "Please see https://github.com/NixOS/nixpkgs/issues/144170 for more details." + exit 1 + } 1>&2 + fi + done < <(find "${!outputDev}" -iname "*.pc" -print0) +} + +postFixupHooks+=(cmakePcfileCheckPhase) diff --git a/nix/cmake3/default.nix b/nix/cmake3/default.nix new file mode 100644 index 00000000000..4fac4e38712 --- /dev/null +++ b/nix/cmake3/default.nix @@ -0,0 +1,232 @@ +{ + lib, + stdenv, + fetchurl, + replaceVars, + buildPackages, + bzip2, + curlMinimal, + expat, + libarchive, + libuv, + ncurses, + openssl, + pkg-config, + ps, + rhash, + sphinx, + texinfo, + xz, + zlib, + isBootstrap ? null, + isMinimalBuild ? ( + if isBootstrap != null then + lib.warn "isBootstrap argument is deprecated and will be removed; use isMinimalBuild instead" isBootstrap + else + false + ), + useOpenSSL ? !isMinimalBuild, + useSharedLibraries ? (!isMinimalBuild && !stdenv.hostPlatform.isCygwin), + uiToolkits ? [ ], # can contain "ncurses" and/or "qt5" + buildDocs ? !(isMinimalBuild || (uiToolkits == [ ])), + libsForQt5, + gitUpdater, +}: + +let + inherit (libsForQt5) qtbase wrapQtAppsHook; + cursesUI = lib.elem "ncurses" uiToolkits; + qt5UI = lib.elem "qt5" uiToolkits; +in +# Accepts only "ncurses" and "qt5" as possible uiToolkits +assert lib.subtractLists [ "ncurses" "qt5" ] uiToolkits == [ ]; +# Minimal, bootstrap cmake does not have toolkits +assert isMinimalBuild -> (uiToolkits == [ ]); +stdenv.mkDerivation (finalAttrs: { + pname = + "cmake" + + lib.optionalString isMinimalBuild "-minimal" + + lib.optionalString cursesUI "-cursesUI" + + lib.optionalString qt5UI "-qt5UI"; + version = "3.31.7"; + + src = fetchurl { + url = "https://cmake.org/files/v${lib.versions.majorMinor finalAttrs.version}/cmake-${finalAttrs.version}.tar.gz"; + hash = "sha256-ptLrHr65kTDf5j71o0DD/bEUMczj18oUhSTBJZJM6mg="; + }; + + patches = [ + # Add NIXPKGS_CMAKE_PREFIX_PATH to cmake which is like CMAKE_PREFIX_PATH + # except it is not searched for programs + ./000-nixpkgs-cmake-prefix-path.diff + # Don't search in non-Nix locations such as /usr, but do search in our libc. + ./001-search-path.diff + ] + # On Darwin, always set CMAKE_SHARED_LIBRARY_RUNTIME_C_FLAG. + ++ lib.optional stdenv.hostPlatform.isDarwin ./006-darwin-always-set-runtime-c-flag.diff + # On platforms where ps is not part of stdenv, patch the invocation of ps to use an absolute path. + ++ lib.optional (stdenv.hostPlatform.isDarwin || stdenv.hostPlatform.isFreeBSD) ( + replaceVars ./007-darwin-bsd-ps-abspath.diff { + ps = lib.getExe ps; + } + ) + ++ [ + # Backport of https://gitlab.kitware.com/cmake/cmake/-/merge_requests/9900 + # Needed to correctly link curl in pkgsStatic. + ./008-FindCURL-Add-more-target-properties-from-pkg-config.diff + # Backport of https://gitlab.kitware.com/cmake/cmake/-/merge_requests/11134 + # Fixes build against curl 8.16 and later + ./009-cmCTestCurl-Avoid-using-undocumented-type-for-CURLOPT_PROXYTYPE-values.diff + ]; + + outputs = [ + "out" + ] + ++ lib.optionals buildDocs [ + "man" + "info" + ]; + separateDebugInfo = true; + setOutputFlags = false; + + setupHooks = [ + ./setup-hook.sh + ./check-pc-files-hook.sh + ]; + + depsBuildBuild = [ buildPackages.stdenv.cc ]; + + nativeBuildInputs = + finalAttrs.setupHooks + ++ [ + pkg-config + ] + ++ lib.optionals buildDocs [ texinfo ] + ++ lib.optionals qt5UI [ wrapQtAppsHook ]; + + buildInputs = + lib.optionals useSharedLibraries [ + bzip2 + curlMinimal + expat + libarchive + xz + zlib + libuv + rhash + ] + ++ lib.optional useOpenSSL openssl + ++ lib.optional cursesUI ncurses + ++ lib.optional qt5UI qtbase; + + preConfigure = '' + fixCmakeFiles . + substituteInPlace Modules/Platform/UnixPaths.cmake \ + --subst-var-by libc_bin ${lib.getBin stdenv.cc.libc} \ + --subst-var-by libc_dev ${lib.getDev stdenv.cc.libc} \ + --subst-var-by libc_lib ${lib.getLib stdenv.cc.libc} + # CC_FOR_BUILD and CXX_FOR_BUILD are used to bootstrap cmake + configureFlags="--parallel=''${NIX_BUILD_CORES:-1} CC=$CC_FOR_BUILD CXX=$CXX_FOR_BUILD $configureFlags $cmakeFlags" + ''; + + # The configuration script is not autoconf-based, although being similar; + # triples and other interesting info are passed via CMAKE_* environment + # variables and commandline switches + configurePlatforms = [ ]; + + configureFlags = [ + "CXXFLAGS=-Wno-elaborated-enum-base" + "--docdir=share/doc/${finalAttrs.pname}-${finalAttrs.version}" + ] + ++ ( + if useSharedLibraries then + [ + "--no-system-cppdap" + "--no-system-jsoncpp" + "--system-libs" + ] + else + [ + "--no-system-libs" + ] + ) # FIXME: cleanup + ++ lib.optional qt5UI "--qt-gui" + ++ lib.optionals buildDocs [ + "--sphinx-build=${sphinx}/bin/sphinx-build" + "--sphinx-info" + "--sphinx-man" + ] + # Workaround https://gitlab.kitware.com/cmake/cmake/-/issues/20568 + ++ lib.optionals stdenv.hostPlatform.is32bit [ + "CFLAGS=-D_FILE_OFFSET_BITS=64" + "CXXFLAGS=-D_FILE_OFFSET_BITS=64" + ] + ++ [ + "--" + # We should set the proper `CMAKE_SYSTEM_NAME`. + # http://www.cmake.org/Wiki/CMake_Cross_Compiling + # + # Unfortunately cmake seems to expect absolute paths for ar, ranlib, and + # strip. Otherwise they are taken to be relative to the source root of the + # package being built. + (lib.cmakeFeature "CMAKE_CXX_COMPILER" "${stdenv.cc.targetPrefix}c++") + (lib.cmakeFeature "CMAKE_C_COMPILER" "${stdenv.cc.targetPrefix}cc") + (lib.cmakeFeature "CMAKE_AR" "${lib.getBin stdenv.cc.bintools.bintools}/bin/${stdenv.cc.targetPrefix}ar") + (lib.cmakeFeature "CMAKE_RANLIB" "${lib.getBin stdenv.cc.bintools.bintools}/bin/${stdenv.cc.targetPrefix}ranlib") + (lib.cmakeFeature "CMAKE_STRIP" "${lib.getBin stdenv.cc.bintools.bintools}/bin/${stdenv.cc.targetPrefix}strip") + + (lib.cmakeBool "CMAKE_USE_OPENSSL" useOpenSSL) + (lib.cmakeBool "BUILD_CursesDialog" cursesUI) + ]; + + # `pkgsCross.musl64.cmake.override { stdenv = pkgsCross.musl64.llvmPackages_16.libcxxStdenv; }` + # fails with `The C++ compiler does not support C++11 (e.g. std::unique_ptr).` + # The cause is a compiler warning `warning: argument unused during compilation: '-pie' [-Wunused-command-line-argument]` + # interfering with the feature check. + env.NIX_CFLAGS_COMPILE = "-Wno-unused-command-line-argument"; + + # make install attempts to use the just-built cmake + preInstall = lib.optionalString (stdenv.hostPlatform != stdenv.buildPlatform) '' + sed -i 's|bin/cmake|${buildPackages.cmakeMinimal}/bin/cmake|g' Makefile + ''; + + # Undo some of `fixCmakeFiles` for Darwin to make sure that checks for libraries in the SDK find them + # (e.g., `find_library(MATH_LIBRARY m)` should find `$SDKROOT/usr/lib/libm.tbd`). + postFixup = lib.optionalString stdenv.hostPlatform.isDarwin '' + substituteInPlace "$out/share/cmake-${lib.versions.majorMinor finalAttrs.version}/Modules/Platform/Darwin.cmake" \ + --replace-fail '/var/empty/include' '/usr/include' \ + --replace-fail '/var/empty/lib' '/usr/lib' + ''; + + dontUseCmakeConfigure = true; + enableParallelBuilding = true; + + doCheck = false; # fails + + passthru.updateScript = gitUpdater { + url = "https://gitlab.kitware.com/cmake/cmake.git"; + rev-prefix = "v"; + ignoredVersions = "-"; # -rc1 and friends + }; + + meta = { + homepage = "https://cmake.org/"; + description = "Cross-platform, open-source build system generator"; + longDescription = '' + CMake is an open-source, cross-platform family of tools designed to build, + test and package software. CMake is used to control the software + compilation process using simple platform and compiler independent + configuration files, and generate native makefiles and workspaces that can + be used in the compiler environment of your choice. + ''; + changelog = "https://cmake.org/cmake/help/v${lib.versions.majorMinor finalAttrs.version}/release/${lib.versions.majorMinor finalAttrs.version}.html"; + license = lib.licenses.bsd3; + maintainers = with lib.maintainers; [ + ttuegel + lnl7 + ]; + platforms = lib.platforms.all; + mainProgram = "cmake"; + broken = (qt5UI && stdenv.hostPlatform.isDarwin); + }; +}) diff --git a/nix/cmake3/setup-hook.sh b/nix/cmake3/setup-hook.sh new file mode 100644 index 00000000000..ea2d3d2a45b --- /dev/null +++ b/nix/cmake3/setup-hook.sh @@ -0,0 +1,188 @@ +addCMakeParams() { + # NIXPKGS_CMAKE_PREFIX_PATH is like CMAKE_PREFIX_PATH except cmake + # will not search it for programs + addToSearchPath NIXPKGS_CMAKE_PREFIX_PATH $1 +} + +fixCmakeFiles() { + # Replace occurences of /usr and /opt by /var/empty. + echo "fixing cmake files..." + find "$1" -type f \( -name "*.cmake" -o -name "*.cmake.in" -o -name CMakeLists.txt \) -print | + while read fn; do + sed -e 's^/usr\([ /]\|$\)^/var/empty\1^g' -e 's^/opt\([ /]\|$\)^/var/empty\1^g' < "$fn" > "$fn.tmp" + mv "$fn.tmp" "$fn" + done +} + +cmakeConfigurePhase() { + runHook preConfigure + + # default to CMake defaults if unset + : ${cmakeBuildDir:=build} + + export CTEST_OUTPUT_ON_FAILURE=1 + if [ -n "${enableParallelChecking-1}" ]; then + export CTEST_PARALLEL_LEVEL=$NIX_BUILD_CORES + fi + + if [ -z "${dontFixCmake-}" ]; then + fixCmakeFiles . + fi + + if [ -z "${dontUseCmakeBuildDir-}" ]; then + mkdir -p "$cmakeBuildDir" + cd "$cmakeBuildDir" + : ${cmakeDir:=..} + else + : ${cmakeDir:=.} + fi + + if [ -z "${dontAddPrefix-}" ]; then + prependToVar cmakeFlags "-DCMAKE_INSTALL_PREFIX=$prefix" + fi + + # We should set the proper `CMAKE_SYSTEM_NAME`. + # http://www.cmake.org/Wiki/CMake_Cross_Compiling + # + # Unfortunately cmake seems to expect absolute paths for ar, ranlib, and + # strip. Otherwise they are taken to be relative to the source root of the + # package being built. + prependToVar cmakeFlags "-DCMAKE_CXX_COMPILER=$CXX" + prependToVar cmakeFlags "-DCMAKE_C_COMPILER=$CC" + prependToVar cmakeFlags "-DCMAKE_AR=$(command -v $AR)" + prependToVar cmakeFlags "-DCMAKE_RANLIB=$(command -v $RANLIB)" + prependToVar cmakeFlags "-DCMAKE_STRIP=$(command -v $STRIP)" + + # on macOS we want to prefer Unix-style headers to Frameworks + # because we usually do not package the framework + prependToVar cmakeFlags "-DCMAKE_FIND_FRAMEWORK=LAST" + + # correctly detect our clang compiler + prependToVar cmakeFlags "-DCMAKE_POLICY_DEFAULT_CMP0025=NEW" + + # This installs shared libraries with a fully-specified install + # name. By default, cmake installs shared libraries with just the + # basename as the install name, which means that, on Darwin, they + # can only be found by an executable at runtime if the shared + # libraries are in a system path or in the same directory as the + # executable. This flag makes the shared library accessible from its + # nix/store directory. + prependToVar cmakeFlags "-DCMAKE_INSTALL_NAME_DIR=${!outputLib}/lib" + + # The docdir flag needs to include PROJECT_NAME as per GNU guidelines, + # try to extract it from CMakeLists.txt. + if [[ -z "$shareDocName" ]]; then + local cmakeLists="${cmakeDir}/CMakeLists.txt" + if [[ -f "$cmakeLists" ]]; then + local shareDocName="$(grep --only-matching --perl-regexp --ignore-case '\bproject\s*\(\s*"?\K([^[:space:]")]+)' < "$cmakeLists" | head -n1)" + fi + # The argument sometimes contains garbage or variable interpolation. + # When that is the case, let’s fall back to the derivation name. + if [[ -z "$shareDocName" ]] || echo "$shareDocName" | grep -q '[^a-zA-Z0-9_+-]'; then + if [[ -n "${pname-}" ]]; then + shareDocName="$pname" + else + shareDocName="$(echo "$name" | sed 's/-[^a-zA-Z].*//')" + fi + fi + fi + + # This ensures correct paths with multiple output derivations + # It requires the project to use variables from GNUInstallDirs module + # https://cmake.org/cmake/help/latest/module/GNUInstallDirs.html + prependToVar cmakeFlags "-DCMAKE_INSTALL_BINDIR=${!outputBin}/bin" + prependToVar cmakeFlags "-DCMAKE_INSTALL_SBINDIR=${!outputBin}/sbin" + prependToVar cmakeFlags "-DCMAKE_INSTALL_INCLUDEDIR=${!outputInclude}/include" + prependToVar cmakeFlags "-DCMAKE_INSTALL_MANDIR=${!outputMan}/share/man" + prependToVar cmakeFlags "-DCMAKE_INSTALL_INFODIR=${!outputInfo}/share/info" + prependToVar cmakeFlags "-DCMAKE_INSTALL_DOCDIR=${!outputDoc}/share/doc/${shareDocName}" + prependToVar cmakeFlags "-DCMAKE_INSTALL_LIBDIR=${!outputLib}/lib" + prependToVar cmakeFlags "-DCMAKE_INSTALL_LIBEXECDIR=${!outputLib}/libexec" + prependToVar cmakeFlags "-DCMAKE_INSTALL_LOCALEDIR=${!outputLib}/share/locale" + + # Don’t build tests when doCheck = false + if [ -z "${doCheck-}" ]; then + prependToVar cmakeFlags "-DBUILD_TESTING=OFF" + fi + + # Always build Release, to ensure optimisation flags + prependToVar cmakeFlags "-DCMAKE_BUILD_TYPE=${cmakeBuildType:-Release}" + + # Disable user package registry to avoid potential side effects + # and unecessary attempts to access non-existent home folder + # https://cmake.org/cmake/help/latest/manual/cmake-packages.7.html#disabling-the-package-registry + prependToVar cmakeFlags "-DCMAKE_EXPORT_NO_PACKAGE_REGISTRY=ON" + prependToVar cmakeFlags "-DCMAKE_FIND_USE_PACKAGE_REGISTRY=OFF" + prependToVar cmakeFlags "-DCMAKE_FIND_USE_SYSTEM_PACKAGE_REGISTRY=OFF" + + if [ "${buildPhase-}" = ninjaBuildPhase ]; then + prependToVar cmakeFlags "-GNinja" + fi + + local flagsArray=() + concatTo flagsArray cmakeFlags cmakeFlagsArray + + echoCmd 'cmake flags' "${flagsArray[@]}" + + cmake "$cmakeDir" "${flagsArray[@]}" + + if ! [[ -v enableParallelBuilding ]]; then + enableParallelBuilding=1 + echo "cmake: enabled parallel building" + fi + if [[ "$enableParallelBuilding" -ne 0 ]]; then + export CMAKE_BUILD_PARALLEL_LEVEL=$NIX_BUILD_CORES + fi + + if ! [[ -v enableParallelInstalling ]]; then + enableParallelInstalling=1 + echo "cmake: enabled parallel installing" + fi + + runHook postConfigure +} + +if [ -z "${dontUseCmakeConfigure-}" -a -z "${configurePhase-}" ]; then + setOutputFlags= + configurePhase=cmakeConfigurePhase +fi + +addEnvHooks "$targetOffset" addCMakeParams + +makeCmakeFindLibs() { + isystem_seen= + iframework_seen= + for flag in ${NIX_CFLAGS_COMPILE-} ${NIX_LDFLAGS-}; do + if test -n "$isystem_seen" && test -d "$flag"; then + isystem_seen= + addToSearchPath CMAKE_INCLUDE_PATH "${flag}" + elif test -n "$iframework_seen" && test -d "$flag"; then + iframework_seen= + addToSearchPath CMAKE_FRAMEWORK_PATH "${flag}" + else + isystem_seen= + iframework_seen= + case $flag in + -I*) + addToSearchPath CMAKE_INCLUDE_PATH "${flag:2}" + ;; + -L*) + addToSearchPath CMAKE_LIBRARY_PATH "${flag:2}" + ;; + -F*) + addToSearchPath CMAKE_FRAMEWORK_PATH "${flag:2}" + ;; + -isystem) + isystem_seen=1 + ;; + -iframework) + iframework_seen=1 + ;; + esac + fi + done +} + +# not using setupHook, because it could be a setupHook adding additional +# include flags to NIX_CFLAGS_COMPILE +postHooks+=(makeCmakeFindLibs)