Merge branch 'development' of https://github.com/o3de/o3de into daimini/settings-registry-origin-tracking

# Conflicts:
#	Code/Framework/AzCore/AzCore/Settings/SettingsRegistryImpl.cpp
This commit is contained in:
Danilo Aimini
2021-08-13 15:04:47 -07:00
234 changed files with 2293 additions and 4711 deletions
@@ -58,6 +58,12 @@ namespace AZ
Event& operator=(Event&& rhs);
//! Take the handlers registered with the other event
//! and move them to this event. The other will event
//! will be cleared after call
//! @param other event to move handlers
Event& ClaimHandlers(Event&& other);
//! Returns true if at least one handler is connected to this event.
bool HasHandlerConnected() const;
@@ -207,6 +207,32 @@ namespace AZ
}
template <typename... Params>
auto Event<Params...>::ClaimHandlers(Event&& other) -> Event&
{
auto handlers = AZStd::move(other.m_handlers);
auto addList = AZStd::move(other.m_addList);
other.m_freeList = {};
other.m_updating = false;
AZStd::array handlerContainers{ &handlers, &addList };
for (AZStd::vector<Handler*>* handlerList : handlerContainers)
{
for (Handler* handler : *handlerList)
{
if (handler != nullptr)
{
handler->m_index = 0;
handler->m_event = this;
Connect(*handler);
}
}
}
return *this;
}
template <typename... Params>
bool Event<Params...>::HasHandlerConnected() const
{
@@ -20,7 +20,7 @@
namespace AZ
{
template<typename T>
bool SettingsRegistryImpl::SetValueInternal(AZStd::string_view path, T value, SettingsRegistryInterface::Type type)
bool SettingsRegistryImpl::SetValueInternal(AZStd::string_view path, T value)
{
if (path.empty())
{
@@ -56,7 +56,6 @@ namespace AZ
static_assert(!AZStd::is_same_v<T, T>, "SettingsRegistryImpl::SetValueInternal called with unsupported type.");
}
m_notifiers.Signal(path, type);
return true;
}
return false;
@@ -157,11 +156,11 @@ namespace AZ
// Setting to empty string to prevent assert
path = "";
}
AZStd::scoped_lock lock(m_settingMutex);
rapidjson::Pointer pointer(path.data(), path.length());
if (pointer.IsValid())
{
AZStd::scoped_lock lock(m_settingMutex);
const rapidjson::Value* value = pointer.Get(m_settings);
if (value)
{
@@ -207,7 +206,7 @@ namespace AZ
{
NotifyEventHandler notifyHandler{ callback };
{
AZStd::scoped_lock lock(m_settingMutex);
AZStd::scoped_lock lock(m_notifierMutex);
notifyHandler.Connect(m_notifiers);
}
return notifyHandler;
@@ -217,7 +216,7 @@ namespace AZ
{
NotifyEventHandler notifyHandler{ AZStd::move(callback) };
{
AZStd::scoped_lock lock(m_settingMutex);
AZStd::scoped_lock lock(m_notifierMutex);
notifyHandler.Connect(m_notifiers);
}
return notifyHandler;
@@ -225,7 +224,7 @@ namespace AZ
void SettingsRegistryImpl::ClearNotifiers()
{
AZStd::scoped_lock lock(m_settingMutex);
AZStd::scoped_lock lock(m_notifierMutex);
m_notifiers.DisconnectAllHandlers();
}
@@ -276,6 +275,31 @@ namespace AZ
m_postMergeEvent.DisconnectAllHandlers();
}
void SettingsRegistryImpl::SignalNotifier(AZStd::string_view jsonPath, Type type)
{
// Move the Notifier AZ::Event to a local AZ::Event in order to allow
// the notifier handlers to be signaled outside of the notifier mutex
// This allows other threads to register notifiers while this thread
// is invoking the handlers
decltype(m_notifiers) localNotifierEvent;
{
AZStd::scoped_lock lock(m_notifierMutex);
localNotifierEvent = AZStd::move(m_notifiers);
}
localNotifierEvent.Signal(jsonPath, type);
{
// Swap the local handlers with the current m_notifiers which
// will contain any handlers added during the signaling of the
// local event
AZStd::scoped_lock lock(m_notifierMutex);
AZStd::swap(m_notifiers, localNotifierEvent);
// Append any added handlers to the m_notifier structure
m_notifiers.ClaimHandlers(AZStd::move(localNotifierEvent));
}
}
SettingsRegistryInterface::Type SettingsRegistryImpl::GetType(AZStd::string_view path) const
{
if (path.empty())
@@ -286,11 +310,11 @@ namespace AZ
path = "";
}
AZStd::scoped_lock lock(m_settingMutex);
rapidjson::Pointer pointer(path.data(), path.length());
if (pointer.IsValid())
{
AZStd::scoped_lock lock(m_settingMutex);
const rapidjson::Value* value = pointer.Get(m_settings);
if (value)
{
@@ -363,11 +387,11 @@ namespace AZ
// Setting to empty string to prevent assert
path = "";
}
AZStd::scoped_lock lock(m_settingMutex);
rapidjson::Pointer pointer(path.data(), path.length());
if (pointer.IsValid())
{
AZStd::scoped_lock lock(m_settingMutex);
const rapidjson::Value* value = pointer.Get(m_settings);
if (value)
{
@@ -380,32 +404,52 @@ namespace AZ
bool SettingsRegistryImpl::Set(AZStd::string_view path, bool value)
{
AZStd::scoped_lock lock(m_settingMutex);
return SetValueInternal(path, value, Type::Boolean);
if (AZStd::scoped_lock lock(m_settingMutex); !SetValueInternal(path, value))
{
return false;
}
SignalNotifier(path, Type::Boolean);
return true;
}
bool SettingsRegistryImpl::Set(AZStd::string_view path, s64 value)
{
AZStd::scoped_lock lock(m_settingMutex);
return SetValueInternal(path, value, Type::Integer);
if (AZStd::scoped_lock lock(m_settingMutex); !SetValueInternal(path, value))
{
return false;
}
SignalNotifier(path, Type::Integer);
return true;
}
bool SettingsRegistryImpl::Set(AZStd::string_view path, u64 value)
{
AZStd::scoped_lock lock(m_settingMutex);
return SetValueInternal(path, value, Type::Integer);
if (AZStd::scoped_lock lock(m_settingMutex); !SetValueInternal(path, value))
{
return false;
}
SignalNotifier(path, Type::Integer);
return true;
}
bool SettingsRegistryImpl::Set(AZStd::string_view path, double value)
{
AZStd::scoped_lock lock(m_settingMutex);
return SetValueInternal(path, value, Type::FloatingPoint);
if (AZStd::scoped_lock lock(m_settingMutex); !SetValueInternal(path, value))
{
return false;
}
SignalNotifier(path, Type::FloatingPoint);
return true;
}
bool SettingsRegistryImpl::Set(AZStd::string_view path, AZStd::string_view value)
{
AZStd::scoped_lock lock(m_settingMutex);
return SetValueInternal(path, value, Type::String);
if (AZStd::scoped_lock lock(m_settingMutex); !SetValueInternal(path, value))
{
return false;
}
SignalNotifier(path, Type::String);
return true;
}
bool SettingsRegistryImpl::Set(AZStd::string_view path, const char* value)
@@ -423,7 +467,6 @@ namespace AZ
path = "";
}
AZStd::scoped_lock lock(m_settingMutex);
rapidjson::Pointer pointer(path.data(), path.length());
if (pointer.IsValid())
@@ -433,9 +476,10 @@ namespace AZ
value, nullptr, valueTypeID, m_serializationSettings);
if (jsonResult.GetProcessing() != JsonSerializationResult::Processing::Halted)
{
AZStd::scoped_lock lock(m_settingMutex);
rapidjson::Value& setting = pointer.Create(m_settings, m_settings.GetAllocator());
setting = AZStd::move(store);
m_notifiers.Signal(path, Type::Object);
SignalNotifier(path, Type::Object);
return true;
}
}
@@ -451,13 +495,13 @@ namespace AZ
// Setting to empty string to prevent assert
path = "";
}
AZStd::scoped_lock lock(m_settingMutex);
rapidjson::Pointer pointerPath(path.data(), path.size());
if (!pointerPath.IsValid())
{
return false;
}
AZStd::scoped_lock lock(m_settingMutex);
return pointerPath.Erase(m_settings);
}
@@ -587,7 +631,7 @@ namespace AZ
return false;
}
m_notifiers.Signal("", Type::Object);
SignalNotifier("", Type::Object);
return true;
}
@@ -609,8 +653,6 @@ namespace AZ
scratchBuffer = &buffer;
}
AZStd::scoped_lock lock(m_settingMutex);
bool result = false;
if (path[path.length()] == 0)
{
@@ -624,6 +666,8 @@ namespace AZ
R"(Path "%.*s" is too long. Either make sure that the provided path is terminated or use a shorter path.)",
static_cast<int>(path.length()), path.data());
Pointer pointer(AZ_SETTINGS_REGISTRY_HISTORY_KEY "/-");
AZStd::scoped_lock lock(m_settingMutex);
Value pathValue(path.data(), aznumeric_caster(path.length()), m_settings.GetAllocator());
pointer.Create(m_settings, m_settings.GetAllocator()).SetObject()
.AddMember(StringRef("Error"), StringRef("Unable to read registry file."), m_settings.GetAllocator())
@@ -669,6 +713,7 @@ namespace AZ
{
AZ_Error("Settings Registry", false, "Folder path for the Setting Registry is too long: %.*s",
static_cast<int>(path.size()), path.data());
AZStd::scoped_lock lock(m_settingMutex);
pointer.Create(m_settings, m_settings.GetAllocator()).SetObject()
.AddMember(StringRef("Error"), StringRef("Folder path for the Setting Registry is too long."), m_settings.GetAllocator())
.AddMember(StringRef("Path"), Value(path.data(), aznumeric_caster(path.length()), m_settings.GetAllocator()), m_settings.GetAllocator());
@@ -706,6 +751,7 @@ namespace AZ
if (fileList.size() >= MaxRegistryFolderEntries)
{
AZ_Error("Settings Registry", false, "Too many files in registry folder.");
AZStd::scoped_lock lock(m_settingMutex);
pointer.Create(m_settings, m_settings.GetAllocator()).SetObject()
.AddMember(StringRef("Error"), StringRef("Too many files in registry folder."), m_settings.GetAllocator())
.AddMember(StringRef("Path"), Value(folderPath.c_str(), aznumeric_caster(folderPath.size()), m_settings.GetAllocator()), m_settings.GetAllocator())
@@ -725,7 +771,6 @@ namespace AZ
SystemFile::FindFiles(folderPath.c_str(), callback);
AZStd::scoped_lock lock(m_settingMutex);
if (!platform.empty())
{
// Move the folderPath prefix back to the supplied path before the wildcard
@@ -743,6 +788,7 @@ namespace AZ
if (fileList.size() >= MaxRegistryFolderEntries)
{
AZ_Error("Settings Registry", false, "Too many files in registry folder.");
AZStd::scoped_lock lock(m_settingMutex);
pointer.Create(m_settings, m_settings.GetAllocator()).SetObject()
.AddMember(StringRef("Error"), StringRef("Too many files in registry folder."), m_settings.GetAllocator())
.AddMember(StringRef("Path"), Value(folderPath.c_str(), aznumeric_caster(folderPath.size()), m_settings.GetAllocator()), m_settings.GetAllocator())
@@ -970,6 +1016,8 @@ namespace AZ
collisionFound = true;
AZ_Error("Settings Registry", false, R"(Two registry files in "%.*s" point to the same specialization: "%s" and "%s")",
AZ_STRING_ARG(folderPath), lhs.m_relativePath.c_str(), rhs.m_relativePath.c_str());
AZStd::scoped_lock lock(m_settingMutex);
historyPointer.Create(m_settings, m_settings.GetAllocator()).SetObject()
.AddMember(StringRef("Error"), StringRef("Too many files in registry folder."), m_settings.GetAllocator())
.AddMember(StringRef("Path"),
@@ -1124,6 +1172,7 @@ namespace AZ
}
}
AZStd::scoped_lock lock(m_settingMutex);
pointer.Create(m_settings, m_settings.GetAllocator()).SetObject()
.AddMember(StringRef("Error"), StringRef("Unable to parse registry file due to invalid json."), m_settings.GetAllocator())
.AddMember(StringRef("Path"), Value(path, m_settings.GetAllocator()), m_settings.GetAllocator())
@@ -1149,6 +1198,7 @@ namespace AZ
R"(To merge the supplied settings registry file, the settings within it must be placed within a JSON Object '{}')"
R"( in order to allow moving of its fields using the root-key as an anchor.)", path);
AZStd::scoped_lock lock(m_settingMutex);
pointer.Create(m_settings, m_settings.GetAllocator()).SetObject()
.AddMember(StringRef("Error"), StringRef("Cannot merge registry file with a root which is not a JSON Object,"
" an empty root key and a merge approach of JsonMergePatch. Otherwise the Settings Registry would be overridden."
@@ -1167,6 +1217,7 @@ namespace AZ
JsonSerializationResult::ResultCode mergeResult(JsonSerializationResult::Tasks::Merge);
if (rootKey.empty())
{
AZStd::scoped_lock lock(m_settingMutex);
mergeResult = JsonSerialization::ApplyPatch(m_settings, m_settings.GetAllocator(), jsonPatch, mergeApproach, m_applyPatchSettings);
}
else
@@ -1174,6 +1225,7 @@ namespace AZ
Pointer root(rootKey.data(), rootKey.length());
if (root.IsValid())
{
AZStd::scoped_lock lock(m_settingMutex);
Value& rootValue = root.Create(m_settings, m_settings.GetAllocator());
mergeResult = JsonSerialization::ApplyPatch(rootValue, m_settings.GetAllocator(), jsonPatch, mergeApproach, m_applyPatchSettings);
}
@@ -1181,6 +1233,7 @@ namespace AZ
{
AZ_Error("Settings Registry", false, R"(Failed to root path "%.*s" is invalid.)",
aznumeric_cast<int>(rootKey.length()), rootKey.data());
AZStd::scoped_lock lock(m_settingMutex);
pointer.Create(m_settings, m_settings.GetAllocator()).SetObject()
.AddMember(StringRef("Error"), StringRef("Invalid root key."), m_settings.GetAllocator())
.AddMember(StringRef("Path"), Value(path, m_settings.GetAllocator()), m_settings.GetAllocator());
@@ -1190,15 +1243,19 @@ namespace AZ
if (mergeResult.GetProcessing() != JsonSerializationResult::Processing::Completed)
{
AZ_Error("Settings Registry", false, R"(Failed to fully merge registry file "%s".)", path);
AZStd::scoped_lock lock(m_settingMutex);
pointer.Create(m_settings, m_settings.GetAllocator()).SetObject()
.AddMember(StringRef("Error"), StringRef("Failed to fully merge registry file."), m_settings.GetAllocator())
.AddMember(StringRef("Path"), Value(path, m_settings.GetAllocator()), m_settings.GetAllocator());
return false;
}
pointer.Create(m_settings, m_settings.GetAllocator()).SetString(path, m_settings.GetAllocator());
{
AZStd::scoped_lock lock(m_settingMutex);
pointer.Create(m_settings, m_settings.GetAllocator()).SetString(path, m_settings.GetAllocator());
}
m_notifiers.Signal("", Type::Object);
SignalNotifier("", Type::Object);
return true;
}
@@ -95,7 +95,7 @@ namespace AZ
using RegistryFileList = AZStd::fixed_vector<RegistryFile, MaxRegistryFolderEntries>;
template<typename T>
bool SetValueInternal(AZStd::string_view path, T value, SettingsRegistryInterface::Type type);
bool SetValueInternal(AZStd::string_view path, T value);
template<typename T>
bool GetValueInternal(T& result, AZStd::string_view path) const;
VisitResponse Visit(Visitor& visitor, StackedString& path, AZStd::string_view valueName,
@@ -106,8 +106,11 @@ namespace AZ
const rapidjson::Pointer& historyPointer, AZStd::string_view folderPath);
bool ExtractFileDescription(RegistryFile& output, const char* filename, const Specializations& specializations);
bool MergeSettingsFileInternal(const char* path, Format format, AZStd::string_view rootKey, AZStd::vector<char>& scratchBuffer);
void SignalNotifier(AZStd::string_view jsonPath, Type type);
mutable AZStd::recursive_mutex m_settingMutex;
mutable AZStd::recursive_mutex m_notifierMutex;
NotifyEvent m_notifiers;
PreMergeEvent m_preMergeEvent;
PostMergeEvent m_postMergeEvent;
@@ -240,6 +240,37 @@ namespace UnitTest
static_assert(!AZStd::is_copy_assignable_v<AZ::Event<int32_t>>, "AZ Events should not be copy assignable");
}
TEST_F(EventTests, TestClaimHandlers_TakesAllSourceHandlers)
{
AZ::Event<> testEvent1;
AZ::Event<> testEvent2;
int32_t handlerInvokeCount{};
auto handlerCallback = [&handlerInvokeCount]()
{
++handlerInvokeCount;
};
AZ::Event<>::Handler testHandler1(handlerCallback);
AZ::Event<>::Handler testHandler2(handlerCallback);
testHandler1.Connect(testEvent1);
testHandler2.Connect(testEvent2);
EXPECT_TRUE(testEvent1.HasHandlerConnected());
EXPECT_TRUE(testEvent2.HasHandlerConnected());
testEvent1.ClaimHandlers(AZStd::move(testEvent2));
EXPECT_TRUE(testEvent1.HasHandlerConnected());
EXPECT_FALSE(testEvent2.HasHandlerConnected());
// testEvent1 should have both handlers
testEvent1.Signal();
EXPECT_EQ(2, handlerInvokeCount);
// testEvent2 should have neither of the handlers
testEvent2.Signal();
EXPECT_EQ(2, handlerInvokeCount);
}
TEST_F(EventTests, HandlerMoveAssignment_ProperlyDisconnectsFromOldEvent)
{
AZ::Event<> testEvent1;
@@ -10,6 +10,17 @@
#include <AzCore/Console/IConsole.h>
void OnVsyncIntervalChanged(uint32_t const& interval)
{
AzFramework::WindowNotificationBus::Broadcast(
&AzFramework::WindowNotificationBus::Events::OnVsyncIntervalChanged, AZ::GetClamp(interval, 0u, 4u));
}
// NOTE: On change, broadcasts the new requested vsync interval to all windows.
// The value of the vsync interval is constrained between 0 and 4
// Vsync intervals greater than 1 are not currently supported on the Vulkan RHI (see #2061 for discussion)
AZ_CVAR(uint32_t, vsync_interval, 1, OnVsyncIntervalChanged, AZ::ConsoleFunctorFlags::Null, "Set swapchain vsync interval");
namespace AzFramework
{
//////////////////////////////////////////////////////////////////////////
@@ -122,6 +133,16 @@ namespace AzFramework
return m_pimpl->GetDpiScaleFactor();
}
uint32_t NativeWindow::GetDisplayRefreshRate() const
{
return m_pimpl->GetDisplayRefreshRate();
}
uint32_t NativeWindow::GetSyncInterval() const
{
return vsync_interval;
}
/*static*/ bool NativeWindow::GetFullScreenStateOfDefaultWindow()
{
NativeWindowHandle defaultWindowHandle = nullptr;
@@ -240,4 +261,10 @@ namespace AzFramework
return 1.0f;
}
uint32_t NativeWindow::Implementation::GetDisplayRefreshRate() const
{
// Default to 60
return 60;
}
} // namespace AzFramework
@@ -130,6 +130,8 @@ namespace AzFramework
bool CanToggleFullScreenState() const override;
void ToggleFullScreenState() override;
float GetDpiScaleFactor() const override;
uint32_t GetSyncInterval() const override;
uint32_t GetDisplayRefreshRate() const override;
//! Get the full screen state of the default window.
//! \return True if the default window is currently in full screen, false otherwise.
@@ -172,6 +174,7 @@ namespace AzFramework
virtual void SetFullScreenState(bool fullScreenState);
virtual bool CanToggleFullScreenState() const;
virtual float GetDpiScaleFactor() const;
virtual uint32_t GetDisplayRefreshRate() const;
protected:
uint32_t m_width = 0;
@@ -74,6 +74,12 @@ namespace AzFramework
//! to a "standard" value of 96, the default for Windows in a DPI unaware setting. This can
//! be used to scale user interface elements to ensure legibility on high density displays.
virtual float GetDpiScaleFactor() const = 0;
//! Returns the sync interval which tells the drivers the number of v-blanks to synchronize with
virtual uint32_t GetSyncInterval() const = 0;
//! Returns the refresh rate of the main display
virtual uint32_t GetDisplayRefreshRate() const = 0;
};
using WindowRequestBus = AZ::EBus<WindowRequests>;
@@ -101,6 +107,9 @@ namespace AzFramework
//! This is called when vsync interval is changed.
virtual void OnVsyncIntervalChanged(uint32_t interval) { AZ_UNUSED(interval); };
//! This is called if the main display's refresh rate changes
virtual void OnRefreshRateChanged([[maybe_unused]] uint32_t refreshRate) {}
};
using WindowNotificationBus = AZ::EBus<WindowNotifications>;
@@ -25,7 +25,7 @@ namespace AzFramework
const WindowGeometry& geometry,
const WindowStyleMasks& styleMasks) override;
NativeWindowHandle GetWindowHandle() const override;
uint32_t GetDisplayRefreshRate() const override;
private:
ANativeWindow* m_nativeWindow = nullptr;
};
@@ -55,4 +55,9 @@ namespace AzFramework
return reinterpret_cast<NativeWindowHandle>(m_nativeWindow);
}
uint32_t NativeWindowImpl_Android::GetDisplayRefreshRate() const
{
// Using 60 for now until proper support is added
return 60;
}
} // namespace AzFramework
@@ -23,6 +23,7 @@ namespace AzFramework
const WindowGeometry& geometry,
const WindowStyleMasks& styleMasks) override;
NativeWindowHandle GetWindowHandle() const override;
uint32_t GetDisplayRefreshRate() const override;
};
NativeWindow::Implementation* NativeWindow::Implementation::Create()
@@ -44,4 +45,9 @@ namespace AzFramework
return nullptr;
}
uint32_t NativeWindowImpl_Linux::GetDisplayRefreshRate() const
{
//Using 60 for now until proper support is added
return 60;
}
} // namespace AzFramework
@@ -34,12 +34,14 @@ namespace AzFramework
bool GetFullScreenState() const override;
void SetFullScreenState(bool fullScreenState) override;
bool CanToggleFullScreenState() const override { return true; }
uint32_t GetDisplayRefreshRate() const override;
private:
static NSWindowStyleMask ConvertToNSWindowStyleMask(const WindowStyleMasks& styleMasks);
NSWindow* m_nativeWindow;
NSString* m_windowTitle;
uint32_t m_mainDisplayRefreshRate = 0;
};
NativeWindow::Implementation* NativeWindow::Implementation::Create()
@@ -76,6 +78,17 @@ namespace AzFramework
// Make the window active
[m_nativeWindow makeKeyAndOrderFront:nil];
m_nativeWindow.title = m_windowTitle;
CGDirectDisplayID display = CGMainDisplayID();
CGDisplayModeRef currentMode = CGDisplayCopyDisplayMode(display);
m_mainDisplayRefreshRate = CGDisplayModeGetRefreshRate(currentMode);
// Assume 60hz if 0 is returned.
// This can happen on OSX. In future we can hopefully use maximumFramesPerSecond which wont have this issue
if (m_mainDisplayRefreshRate == 0)
{
m_mainDisplayRefreshRate = 60;
}
}
NativeWindowHandle NativeWindowImpl_Darwin::GetWindowHandle() const
@@ -128,4 +141,9 @@ namespace AzFramework
const NSWindowStyleMask defaultMask = NSWindowStyleMaskResizable | NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | NSWindowStyleMaskMiniaturizable;
return nativeMask ? nativeMask : defaultMask;
}
uint32_t NativeWindowImpl_Darwin::GetDisplayRefreshRate() const
{
return m_mainDisplayRefreshRate;
}
} // namespace AzFramework
@@ -117,9 +117,11 @@ namespace AzFramework
, m_hasFocus(false)
, m_hasTextEntryStarted(false)
{
static const char* s_keyboardCountEnvironmentVarName = "InputDeviceKeyboardInstanceCount";
s_instanceCount = AZ::Environment::FindVariable<int>(s_keyboardCountEnvironmentVarName);
if (!s_instanceCount)
{
s_instanceCount = AZ::Environment::CreateVariable<int>("InputDeviceKeyboardInstanceCount", 1);
s_instanceCount = AZ::Environment::CreateVariable<int>(s_keyboardCountEnvironmentVarName, 1);
// Register for raw keyboard input
RAWINPUTDEVICE rawInputDevice;
@@ -138,9 +138,11 @@ namespace AzFramework
{
memset(&m_lastClientRect, 0, sizeof(m_lastClientRect));
static const char* s_mouseCountEnvironmentVarName = "InputDeviceMouseInstanceCount";
s_instanceCount = AZ::Environment::FindVariable<int>(s_mouseCountEnvironmentVarName);
if (!s_instanceCount)
{
s_instanceCount = AZ::Environment::CreateVariable<int>("InputDeviceMouseInstanceCount", 1);
s_instanceCount = AZ::Environment::CreateVariable<int>(s_mouseCountEnvironmentVarName, 1);
// Register for raw mouse input
RAWINPUTDEVICE rawInputDevice;
@@ -37,6 +37,7 @@ namespace AzFramework
void SetFullScreenState(bool fullScreenState) override;
bool CanToggleFullScreenState() const override { return true; }
float GetDpiScaleFactor() const override;
uint32_t GetDisplayRefreshRate() const override;
private:
static DWORD ConvertToWin32WindowStyleMask(const WindowStyleMasks& styleMasks);
@@ -56,6 +57,7 @@ namespace AzFramework
using GetDpiForWindowType = UINT(HWND hwnd);
GetDpiForWindowType* m_getDpiFunction = nullptr;
uint32_t m_mainDisplayRefreshRate = 0;
};
const wchar_t* NativeWindowImpl_Win32::s_defaultClassName = L"O3DEWin32Class";
@@ -144,6 +146,10 @@ namespace AzFramework
{
SetWindowLongPtr(m_win32Handle, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(this));
}
DEVMODE DisplayConfig;
EnumDisplaySettings(NULL, ENUM_CURRENT_SETTINGS, &DisplayConfig);
m_mainDisplayRefreshRate = DisplayConfig.dmDisplayFrequency;
}
void NativeWindowImpl_Win32::Activate()
@@ -263,6 +269,15 @@ namespace AzFramework
WindowNotificationBus::Event(nativeWindowImpl->GetWindowHandle(), &WindowNotificationBus::Events::OnDpiScaleFactorChanged, newScaleFactor);
break;
}
case WM_WINDOWPOSCHANGED:
{
DEVMODE DisplayConfig;
EnumDisplaySettings(NULL, ENUM_CURRENT_SETTINGS, &DisplayConfig);
uint32_t refreshRate = DisplayConfig.dmDisplayFrequency;
WindowNotificationBus::Event(
nativeWindowImpl->GetWindowHandle(), &WindowNotificationBus::Events::OnRefreshRateChanged, refreshRate);
break;
}
default:
return DefWindowProc(hWnd, message, wParam, lParam);
break;
@@ -367,6 +382,11 @@ namespace AzFramework
return aznumeric_cast<float>(dotsPerInch) / aznumeric_cast<float>(defaultDotsPerInch);
}
uint32_t NativeWindowImpl_Win32::GetDisplayRefreshRate() const
{
return m_mainDisplayRefreshRate;
}
void NativeWindowImpl_Win32::EnterBorderlessWindowFullScreen()
{
if (m_isInBorderlessWindowFullScreenState)
@@ -27,9 +27,11 @@ namespace AzFramework
const WindowGeometry& geometry,
const WindowStyleMasks& styleMasks) override;
NativeWindowHandle GetWindowHandle() const override;
uint32_t GetDisplayRefreshRate() const override;
private:
UIWindow* m_nativeWindow;
uint32_t m_mainDisplayRefreshRate = 0;
};
NativeWindow::Implementation* NativeWindow::Implementation::Create()
@@ -56,6 +58,7 @@ namespace AzFramework
m_width = geometry.m_width;
m_height = geometry.m_height;
m_mainDisplayRefreshRate = [[UIScreen mainScreen] maximumFramesPerSecond];
}
NativeWindowHandle NativeWindowImpl_Ios::GetWindowHandle() const
@@ -63,5 +66,9 @@ namespace AzFramework
return m_nativeWindow;
}
uint32_t NativeWindowImpl_Ios::GetDisplayRefreshRate() const
{
return m_mainDisplayRefreshRate;
}
} // namespace AzFramework
@@ -8,10 +8,13 @@
#include <AzQtComponents/Components/Widgets/TreeView.h>
#include <QDrag>
#include <QEvent>
#include <QSettings>
#include <QPainter>
#include <AzCore/std/algorithm.h>
#include <AzQtComponents/Components/Style.h>
#include <AzQtComponents/Components/StyleManager.h>
#include <AzQtComponents/Components/ConfigHelpers.h>
@@ -252,5 +255,109 @@ namespace AzQtComponents
return qobject_cast<QTreeView*>(widget) && !qobject_cast<TableView*>(widget);
}
StyledTreeView::StyledTreeView(QWidget* parent)
: QTreeView(parent)
{
}
void StyledTreeView::startDrag(Qt::DropActions supportedActions)
{
if (!selectionModel()->selectedIndexes().empty())
{
StartCustomDrag(selectionModel()->selectedIndexes(), supportedActions);
}
}
void StyledTreeView::StartCustomDrag(const QModelIndexList& indexList, Qt::DropActions supportedActions)
{
StartCustomDragInternal(this, indexList, supportedActions);
}
void StyledTreeView::StartCustomDragInternal(QAbstractItemView* itemView, const QModelIndexList& indexList, Qt::DropActions supportedActions)
{
QMimeData* mimeData = itemView->model()->mimeData(indexList);
if (mimeData)
{
QDrag* drag = new QDrag(itemView);
drag->setPixmap(QPixmap::fromImage(CreateDragImage(itemView, indexList)));
drag->setMimeData(mimeData);
Qt::DropAction defDropAction = Qt::IgnoreAction;
if (itemView->defaultDropAction() != Qt::IgnoreAction && (supportedActions & itemView->defaultDropAction()))
{
defDropAction = itemView->defaultDropAction();
}
else if (supportedActions & Qt::CopyAction && itemView->dragDropMode() != QAbstractItemView::InternalMove)
{
defDropAction = Qt::CopyAction;
}
drag->exec(supportedActions, defDropAction);
}
}
QImage StyledTreeView::CreateDragImage(QAbstractItemView* itemView, const QModelIndexList& indexList)
{
// Generate a drag image of the item icon and text, normally done internally, and inaccessible
QRect rect(0, 0, 0, 0);
for (const auto& index : indexList)
{
if (index.column() != 0)
{
continue;
}
QRect itemRect = itemView->visualRect(index);
rect.setHeight(rect.height() + itemRect.height());
rect.setWidth(AZStd::GetMax(rect.width(), itemRect.width()));
}
QImage dragImage(rect.size(), QImage::Format_ARGB32_Premultiplied);
QPainter dragPainter(&dragImage);
dragPainter.setCompositionMode(QPainter::CompositionMode_Source);
dragPainter.fillRect(dragImage.rect(), Qt::transparent);
dragPainter.setCompositionMode(QPainter::CompositionMode_SourceOver);
dragPainter.setOpacity(0.35f);
dragPainter.fillRect(rect, QColor("#222222"));
dragPainter.setOpacity(1.0f);
int imageY = 0;
for (const auto& index : indexList)
{
if (index.column() != 0)
{
continue;
}
QRect itemRect = itemView->visualRect(index);
dragPainter.drawPixmap(QPoint(0, imageY),
itemView->model()->data(index, Qt::DecorationRole).value<QIcon>().pixmap(QSize(16, 16)));
dragPainter.setPen(
itemView->model()->data(index, Qt::ForegroundRole).value<QBrush>().color());
dragPainter.setFont(
itemView->font());
dragPainter.drawText(QRect(20, imageY, rect.width() - 20, rect.height()),
itemView->model()->data(index, Qt::DisplayRole).value<QString>());
imageY += itemRect.height();
}
dragPainter.end();
return dragImage;
}
StyledTreeWidget::StyledTreeWidget(QWidget* parent)
: QTreeWidget(parent)
{
}
void StyledTreeWidget::startDrag(Qt::DropActions supportedActions)
{
if (!selectionModel()->selectedIndexes().empty())
{
StyledTreeView::StartCustomDragInternal(this, selectionModel()->selectedIndexes(), supportedActions);
}
}
} // namespace AzQtComponents
#include <Components/Widgets/moc_TreeView.cpp>
@@ -9,8 +9,11 @@
#pragma once
#if !defined(Q_MOC_RUN)
#include <AzCore/Memory/SystemAllocator.h>
#include <AzQtComponents/AzQtComponentsAPI.h>
#include <AzQtComponents/Components/Widgets/TableView.h>
#include <QTreeWidget>
#endif
namespace AzQtComponents
@@ -68,4 +71,46 @@ namespace AzQtComponents
void updateEditorGeometry(QWidget* editor, const QStyleOptionViewItem& option, const QModelIndex& index) const override;
};
//! For most of the custom QTreeView styling, we override in AzQtComponents::Style class,
//! but there are some cases (e.g. drag/drop) that can only be overriden by an actual
//! subclass of the QTreeView
class AZ_QT_COMPONENTS_API StyledTreeView
: public QTreeView
{
Q_OBJECT
public:
AZ_CLASS_ALLOCATOR(StyledTreeView, AZ::SystemAllocator, 0);
explicit StyledTreeView(QWidget* parent = nullptr);
//! NOTE: QTreeWidget derives from QTreeView, but because we need a custom dervied class
//! of QTreeView, then we can't inherit our custom drag methods in our custom derived
//! class of QTreeWidget, so these functions are made static so they can be shared
static void StartCustomDragInternal(QAbstractItemView* itemView, const QModelIndexList& indexList, Qt::DropActions supportedActions);
static QImage CreateDragImage(QAbstractItemView* itemView, const QModelIndexList& indexList);
protected:
void startDrag(Qt::DropActions supportedActions) override;
virtual void StartCustomDrag(const QModelIndexList& indexList, Qt::DropActions supportedActions);
};
//! For most of the custom QTreeWidget styling, we override in AzQtComponents::Style class,
//! but there are some cases (e.g. drag/drop) that can only be overriden by an actual
//! subclass of the QTreeWidget.
class AZ_QT_COMPONENTS_API StyledTreeWidget
: public QTreeWidget
{
Q_OBJECT
public:
AZ_CLASS_ALLOCATOR(StyledTreeWidget, AZ::SystemAllocator, 0);
explicit StyledTreeWidget(QWidget* parent = nullptr);
protected:
void startDrag(Qt::DropActions supportedActions) override;
};
} // namespace AzQtComponents
@@ -27,7 +27,7 @@
namespace AzToolsFramework
{
EntityOutlinerTreeView::EntityOutlinerTreeView(QWidget* pParent)
: QTreeView(pParent)
: AzQtComponents::StyledTreeView(pParent)
, m_queuedMouseEvent(nullptr)
, m_draggingUnselectedItem(false)
{
@@ -144,16 +144,12 @@ namespace AzToolsFramework
if (!selectionModel()->isSelected(index))
{
startCustomDrag({ index }, supportedActions);
StartCustomDrag({ index }, supportedActions);
return;
}
}
if (!selectionModel()->selectedIndexes().empty())
{
startCustomDrag(selectionModel()->selectedIndexes(), supportedActions);
return;
}
StyledTreeView::startDrag(supportedActions);
}
void EntityOutlinerTreeView::dragMoveEvent(QDragMoveEvent* event)
@@ -243,14 +239,14 @@ namespace AzToolsFramework
QTreeView::mousePressEvent(&mousePressedEvent);
}
void EntityOutlinerTreeView::startCustomDrag(const QModelIndexList& indexList, Qt::DropActions supportedActions)
void EntityOutlinerTreeView::StartCustomDrag(const QModelIndexList& indexList, Qt::DropActions supportedActions)
{
m_draggingUnselectedItem = true;
//sort by container entity depth and order in hierarchy for proper drag image and drop order
QModelIndexList indexListSorted = indexList;
AZStd::unordered_map<AZ::EntityId, AZStd::list<AZ::u64>> locations;
for (auto index : indexListSorted)
for (const auto& index : indexListSorted)
{
AZ::EntityId entityId(index.data(EntityOutlinerListModel::EntityIdRole).value<AZ::u64>());
AzToolsFramework::GetEntityLocationInHierarchy(entityId, locations[entityId]);
@@ -263,76 +259,8 @@ namespace AzToolsFramework
return AZStd::lexicographical_compare(locationsE1.begin(), locationsE1.end(), locationsE2.begin(), locationsE2.end());
});
//get the data for the unselected item(s)
QMimeData* mimeData = model()->mimeData(indexListSorted);
if (mimeData)
{
//initiate drag/drop for the item
QDrag* drag = new QDrag(this);
drag->setPixmap(QPixmap::fromImage(createDragImage(indexListSorted)));
drag->setMimeData(mimeData);
Qt::DropAction defDropAction = Qt::IgnoreAction;
if (defaultDropAction() != Qt::IgnoreAction && (supportedActions & defaultDropAction()))
{
defDropAction = defaultDropAction();
}
else if (supportedActions & Qt::CopyAction && dragDropMode() != QAbstractItemView::InternalMove)
{
defDropAction = Qt::CopyAction;
}
drag->exec(supportedActions, defDropAction);
}
StyledTreeView::StartCustomDrag(indexListSorted, supportedActions);
}
QImage EntityOutlinerTreeView::createDragImage(const QModelIndexList& indexList)
{
//generate a drag image of the item icon and text, normally done internally, and inaccessible
QRect rect(0, 0, 0, 0);
for (auto index : indexList)
{
if (index.column() != 0)
{
continue;
}
QRect itemRect = visualRect(index);
rect.setHeight(rect.height() + itemRect.height());
rect.setWidth(AZStd::GetMax(rect.width(), itemRect.width()));
}
QImage dragImage(rect.size(), QImage::Format_ARGB32_Premultiplied);
QPainter dragPainter(&dragImage);
dragPainter.setCompositionMode(QPainter::CompositionMode_Source);
dragPainter.fillRect(dragImage.rect(), Qt::transparent);
dragPainter.setCompositionMode(QPainter::CompositionMode_SourceOver);
dragPainter.setOpacity(0.35f);
dragPainter.fillRect(rect, QColor("#222222"));
dragPainter.setOpacity(1.0f);
int imageY = 0;
for (auto index : indexList)
{
if (index.column() != 0)
{
continue;
}
QRect itemRect = visualRect(index);
dragPainter.drawPixmap(QPoint(0, imageY),
model()->data(index, Qt::DecorationRole).value<QIcon>().pixmap(QSize(16, 16)));
dragPainter.setPen(
model()->data(index, Qt::ForegroundRole).value<QBrush>().color());
dragPainter.setFont(
font());
dragPainter.drawText(QRect(20, imageY, rect.width() - 20, rect.height()),
model()->data(index, Qt::DisplayRole).value<QString>());
imageY += itemRect.height();
}
dragPainter.end();
return dragImage;
}
}
#include <UI/Outliner/moc_EntityOutlinerTreeView.cpp>
@@ -14,7 +14,8 @@
#include <QBasicTimer>
#include <QEvent>
#include <QTreeView>
#include <AzQtComponents/Components/Widgets/TreeView.h>
#endif
#pragma once
@@ -33,7 +34,7 @@ namespace AzToolsFramework
//! allow for dragging and dropping of entities from the outliner into the property editor
//! of other entities. If the selection updates instantly, this would never be possible.
class EntityOutlinerTreeView
: public QTreeView
: public AzQtComponents::StyledTreeView
{
Q_OBJECT;
public:
@@ -68,9 +69,7 @@ namespace AzToolsFramework
void processQueuedMousePressedEvent(QMouseEvent* event);
void startCustomDrag(const QModelIndexList& indexList, Qt::DropActions supportedActions);
QImage createDragImage(const QModelIndexList& indexList);
void StartCustomDrag(const QModelIndexList& indexList, Qt::DropActions supportedActions) override;
void PaintBranchBackground(QPainter* painter, const QRect& rect, const QModelIndex& index) const;
@@ -7,6 +7,7 @@
*/
#include <unistd.h>
#include <AzCore/std/string/string.h>
namespace GridMate
{
@@ -7,6 +7,7 @@
*/
#include <unistd.h>
#include <AzCore/std/string/string.h>
namespace GridMate
{