Files
o3de/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Document/AtomToolsDocument.cpp
T
Guthrie Adams c9c172794e Atom Tools: Document system exposes reflected object data
• This removes a direct dependency on dynamic property groups and data from the document system.
• Added support for names, descriptions, and nesting to dynamic property groups.
• Moved property related functions from base document classes into material editor document classes because dynamic property groups are an implementation detail of the material editor document to support material type flexible data format.
• Change material document to use a table of dynamic property groups instead of a map of properties.
• Added functions to traverse groups and properties.
• This keeps groups and properties organized consistently with the material type file as well as what’s expected in the UI.
• Document data can now be maps directly to the inspector reflective property editors instead of copying one property at a time out of the document and keeping those synchronized.

Signed-off-by: Guthrie Adams <guthadam@amazon.com>
2022-02-03 21:42:27 -06:00

350 lines
12 KiB
C++

/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include <Atom/RPI.Edit/Common/AssetUtils.h>
#include <AtomToolsFramework/Document/AtomToolsDocument.h>
#include <AtomToolsFramework/Document/AtomToolsDocumentNotificationBus.h>
#include <AzToolsFramework/SourceControl/SourceControlAPI.h>
namespace AtomToolsFramework
{
AtomToolsDocument::AtomToolsDocument()
{
AtomToolsDocumentRequestBus::Handler::BusConnect(m_id);
AtomToolsDocumentNotificationBus::Broadcast(&AtomToolsDocumentNotificationBus::Events::OnDocumentCreated, m_id);
}
AtomToolsDocument::~AtomToolsDocument()
{
AzToolsFramework::AssetSystemBus::Handler::BusDisconnect();
AtomToolsDocumentNotificationBus::Broadcast(&AtomToolsDocumentNotificationBus::Events::OnDocumentDestroyed, m_id);
AtomToolsDocumentRequestBus::Handler::BusDisconnect();
}
const AZ::Uuid& AtomToolsDocument::GetId() const
{
return m_id;
}
AZStd::string_view AtomToolsDocument::GetAbsolutePath() const
{
return m_absolutePath;
}
AZStd::vector<DocumentObjectInfo> AtomToolsDocument::GetObjectInfo() const
{
AZ_Warning("AtomToolsDocument", false, "%s not implemented.", __FUNCTION__);
return AZStd::vector<DocumentObjectInfo>();
}
bool AtomToolsDocument::Open(AZStd::string_view loadPath)
{
Clear();
m_absolutePath = loadPath;
if (!AzFramework::StringFunc::Path::Normalize(m_absolutePath))
{
AZ_Error("AtomToolsDocument", false, "Document path could not be normalized: '%s'.", m_absolutePath.c_str());
return OpenFailed();
}
if (AzFramework::StringFunc::Path::IsRelative(m_absolutePath.c_str()))
{
AZ_Error("AtomToolsDocument", false, "Document path must be absolute: '%s'.", m_absolutePath.c_str());
return OpenFailed();
}
return true;
}
bool AtomToolsDocument::Reopen()
{
if (!ReopenRecordState())
{
return false;
}
const auto loadPath = m_absolutePath;
if (!Open(loadPath))
{
return false;
}
if (!ReopenRestoreState())
{
return false;
}
AtomToolsFramework::AtomToolsDocumentNotificationBus::Broadcast(
&AtomToolsFramework::AtomToolsDocumentNotificationBus::Events::OnDocumentModified, m_id);
AtomToolsFramework::AtomToolsDocumentNotificationBus::Broadcast(
&AtomToolsFramework::AtomToolsDocumentNotificationBus::Events::OnDocumentUndoStateChanged, m_id);
return true;
}
bool AtomToolsDocument::Save()
{
m_savePathNormalized = m_absolutePath;
if (!AzFramework::StringFunc::Path::Normalize(m_savePathNormalized))
{
AZ_Error("AtomToolsDocument", false, "Document save path could not be normalized: '%s'.", m_savePathNormalized.c_str());
return SaveFailed();
}
if (!IsOpen())
{
AZ_Error("AtomToolsDocument", false, "Document is not open to be saved: '%s'.", m_absolutePath.c_str());
return SaveFailed();
}
if (!IsSavable())
{
AZ_Error("AtomToolsDocument", false, "Document type can not be saved: '%s'.", m_absolutePath.c_str());
return SaveFailed();
}
return true;
}
bool AtomToolsDocument::SaveAsCopy(AZStd::string_view savePath)
{
m_savePathNormalized = savePath;
if (!AzFramework::StringFunc::Path::Normalize(m_savePathNormalized))
{
AZ_Error("AtomToolsDocument", false, "Document save path could not be normalized: '%s'.", m_savePathNormalized.c_str());
return SaveFailed();
}
if (!IsOpen())
{
AZ_Error("AtomToolsDocument", false, "Document is not open to be saved: '%s'.", m_absolutePath.c_str());
return SaveFailed();
}
if (!IsSavable())
{
AZ_Error("AtomToolsDocument", false, "Document type can not be saved: '%s'.", m_absolutePath.c_str());
return SaveFailed();
}
return true;
}
bool AtomToolsDocument::SaveAsChild(AZStd::string_view savePath)
{
m_savePathNormalized = savePath;
if (!AzFramework::StringFunc::Path::Normalize(m_savePathNormalized))
{
AZ_Error("AtomToolsDocument", false, "Document save path could not be normalized: '%s'.", m_savePathNormalized.c_str());
return SaveFailed();
}
if (!IsOpen())
{
AZ_Error("AtomToolsDocument", false, "Document is not open to be saved: '%s'.", m_absolutePath.c_str());
return SaveFailed();
}
if (m_absolutePath == m_savePathNormalized || m_sourceDependencies.find(m_savePathNormalized) != m_sourceDependencies.end())
{
AZ_Error("AtomToolsDocument", false, "Document can not be saved over a dependancy: '%s'.", m_savePathNormalized.c_str());
return SaveFailed();
}
return true;
}
bool AtomToolsDocument::Close()
{
if (!IsOpen())
{
AZ_Error("AtomToolsDocument", false, "Document is not open.");
return false;
}
AZ_TracePrintf("AtomToolsDocument", "Document closed: '%s'.\n", m_absolutePath.c_str());
AtomToolsFramework::AtomToolsDocumentNotificationBus::Broadcast(
&AtomToolsFramework::AtomToolsDocumentNotificationBus::Events::OnDocumentClosed, m_id);
// Clearing after notification so paths are still available
Clear();
return true;
}
bool AtomToolsDocument::IsOpen() const
{
return !m_id.IsNull() && !m_absolutePath.empty();
}
bool AtomToolsDocument::IsModified() const
{
return false;
}
bool AtomToolsDocument::IsSavable() const
{
return false;
}
bool AtomToolsDocument::CanUndo() const
{
// Undo will only be allowed if something has been recorded and we're not at the beginning of history
return IsOpen() && !m_undoHistory.empty() && m_undoHistoryIndex > 0;
}
bool AtomToolsDocument::CanRedo() const
{
// Redo will only be allowed if something has been recorded and we're not at the end of history
return IsOpen() && !m_undoHistory.empty() && m_undoHistoryIndex < m_undoHistory.size();
}
bool AtomToolsDocument::Undo()
{
if (CanUndo())
{
// The history index is one beyond the last executed command. Decrement the index then execute undo.
m_undoHistory[--m_undoHistoryIndex].first();
AZ_TracePrintf("AtomToolsDocument", "Document undo: '%s'.\n", m_absolutePath.c_str());
AtomToolsFramework::AtomToolsDocumentNotificationBus::Broadcast(
&AtomToolsFramework::AtomToolsDocumentNotificationBus::Events::OnDocumentUndoStateChanged, m_id);
return true;
}
return false;
}
bool AtomToolsDocument::Redo()
{
if (CanRedo())
{
// Execute the current redo command then move the history index to the next position.
m_undoHistory[m_undoHistoryIndex++].second();
AZ_TracePrintf("AtomToolsDocument", "Document redo: '%s'.\n", m_absolutePath.c_str());
AtomToolsFramework::AtomToolsDocumentNotificationBus::Broadcast(
&AtomToolsFramework::AtomToolsDocumentNotificationBus::Events::OnDocumentUndoStateChanged, m_id);
return true;
}
return false;
}
bool AtomToolsDocument::BeginEdit()
{
AZ_Warning("AtomToolsDocument", false, "%s not implemented.", __FUNCTION__);
return false;
}
bool AtomToolsDocument::EndEdit()
{
AZ_Warning("AtomToolsDocument", false, "%s not implemented.", __FUNCTION__);
return false;
}
void AtomToolsDocument::Clear()
{
AzToolsFramework::AssetSystemBus::Handler::BusDisconnect();
m_absolutePath.clear();
m_sourceDependencies.clear();
m_ignoreSourceFileChangeToSelf = {};
m_undoHistory.clear();
m_undoHistoryIndex = {};
}
bool AtomToolsDocument::OpenSucceeded()
{
AZ_TracePrintf("AtomToolsDocument", "Document opened: '%s'.\n", m_absolutePath.c_str());
AzToolsFramework::AssetSystemBus::Handler::BusConnect();
AtomToolsFramework::AtomToolsDocumentNotificationBus::Broadcast(
&AtomToolsFramework::AtomToolsDocumentNotificationBus::Events::OnDocumentOpened, m_id);
return true;
}
bool AtomToolsDocument::OpenFailed()
{
AZ_TracePrintf("AtomToolsDocument", "Document could not opened: '%s'.\n", m_absolutePath.c_str());
Clear();
return false;
}
bool AtomToolsDocument::SaveSucceeded()
{
m_ignoreSourceFileChangeToSelf = true;
AZ_TracePrintf("AtomToolsDocument", "Document saved: '%s'.\n", m_savePathNormalized.c_str());
// Auto add or checkout saved file
AzToolsFramework::SourceControlCommandBus::Broadcast(
&AzToolsFramework::SourceControlCommandBus::Events::RequestEdit, m_savePathNormalized.c_str(), true,
[](bool, const AzToolsFramework::SourceControlFileInfo&) {});
AtomToolsFramework::AtomToolsDocumentNotificationBus::Broadcast(
&AtomToolsFramework::AtomToolsDocumentNotificationBus::Events::OnDocumentSaved, m_id);
return true;
}
bool AtomToolsDocument::SaveFailed()
{
AZ_TracePrintf("AtomToolsDocument", "Document not saved: '%s'.\n", m_savePathNormalized.c_str());
return false;
}
bool AtomToolsDocument::ReopenRecordState()
{
m_undoHistoryBeforeReopen = m_undoHistory;
m_undoHistoryIndexBeforeReopen = m_undoHistoryIndex;
return true;
}
bool AtomToolsDocument::ReopenRestoreState()
{
m_undoHistory = m_undoHistoryBeforeReopen;
m_undoHistoryIndex = m_undoHistoryIndexBeforeReopen;
m_undoHistoryBeforeReopen = {};
m_undoHistoryIndexBeforeReopen = {};
return true;
}
void AtomToolsDocument::AddUndoRedoHistory(const UndoRedoFunction& undoCommand, const UndoRedoFunction& redoCommand)
{
// Wipe any state beyond the current history index
m_undoHistory.erase(m_undoHistory.begin() + m_undoHistoryIndex, m_undoHistory.end());
// Add undo and redo operations using functions that capture state and restore it when executed
m_undoHistory.emplace_back(undoCommand, redoCommand);
// Assign the index to the end of history
m_undoHistoryIndex = aznumeric_cast<int>(m_undoHistory.size());
AtomToolsFramework::AtomToolsDocumentNotificationBus::Broadcast(
&AtomToolsFramework::AtomToolsDocumentNotificationBus::Events::OnDocumentUndoStateChanged, m_id);
}
void AtomToolsDocument::SourceFileChanged(AZStd::string relativePath, AZStd::string scanFolder, [[maybe_unused]] AZ::Uuid sourceUUID)
{
const auto sourcePath = AZ::RPI::AssetUtils::ResolvePathReference(scanFolder, relativePath);
if (m_absolutePath == sourcePath)
{
// ignore notifications caused by saving the open document
if (!m_ignoreSourceFileChangeToSelf)
{
AZ_TracePrintf("AtomToolsDocument", "Document changed externally: '%s'.\n", m_absolutePath.c_str());
AtomToolsFramework::AtomToolsDocumentNotificationBus::Broadcast(
&AtomToolsFramework::AtomToolsDocumentNotificationBus::Events::OnDocumentExternallyModified, m_id);
}
m_ignoreSourceFileChangeToSelf = false;
}
else if (m_sourceDependencies.find(sourcePath) != m_sourceDependencies.end())
{
AZ_TracePrintf("AtomToolsDocument", "Document dependency changed: '%s'.\n", m_absolutePath.c_str());
AtomToolsFramework::AtomToolsDocumentNotificationBus::Broadcast(
&AtomToolsFramework::AtomToolsDocumentNotificationBus::Events::OnDocumentDependencyModified, m_id);
}
}
} // namespace AtomToolsFramework