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:
@@ -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>;
|
||||
|
||||
|
||||
+6
-1
@@ -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
|
||||
|
||||
+3
-1
@@ -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;
|
||||
|
||||
+3
-1
@@ -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;
|
||||
|
||||
+20
@@ -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
|
||||
|
||||
+6
-78
@@ -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>
|
||||
|
||||
+4
-5
@@ -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
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user