Merge remote-tracking branch 'public/release_candidate' into release
[CRYENGINE.git] / Code / Sandbox / Plugins / MaterialEditorPlugin / MaterialEditor.cpp
blobe7b5c42b5e8d122a906e324eab67fa788d05a4f0
1 // Copyright 2001-2019 Crytek GmbH / Crytek Group. All rights reserved.
2 #include "StdAfx.h"
3 #include "MaterialEditor.h"
5 #include <AssetSystem/Asset.h>
6 #include <AssetSystem/EditableAsset.h>
7 #include <PathUtils.h>
9 #include <EditorFramework/Events.h>
10 #include <EditorFramework/Inspector.h>
11 #include <LevelEditor/LevelEditorSharedState.h>
13 #include "SubMaterialView.h"
14 #include "MaterialPreview.h"
15 #include "MaterialProperties.h"
16 #include "PickMaterialTool.h"
18 #include "Dialogs/QNumericBoxDialog.h"
20 #include "IUndoObject.h"
22 #include <CryCore/CryTypeInfo.h>
24 #include <QHBoxLayout>
25 #include <QLabel>
26 #include <QLineEdit>
27 #include <QToolButton>
29 namespace Private_MaterialEditor
32 REGISTER_VIEWPANE_FACTORY(CMaterialEditor, "Material Editor", "Tools", false);
34 class CSession : public IAssetEditingSession
36 public:
37 CSession(CMaterial* pMaterial) : m_pMaterial(pMaterial) {}
39 virtual const char* GetEditorName() const override
41 return "Material Editor";
44 virtual bool OnSaveAsset(IEditableAsset& asset) override
46 if (!m_pMaterial || !m_pMaterial->Save(false))
48 return false;
51 //Fill metadata and dependencies to update cryasset file
52 asset.SetFiles({ m_pMaterial->GetFilename() });
54 std::vector<SAssetDependencyInfo> filenames;
56 //TODO : not all dependencies are found, some paths are relative to same folder which is not good ...
57 int textureCount = m_pMaterial->GetTextureDependencies(filenames);
59 std::vector<std::pair<string, string>> details;
60 details.push_back(std::pair<string, string>("subMaterialCount", ToString(m_pMaterial->GetSubMaterialCount())));
61 details.push_back(std::pair<string, string>("textureCount", ToString(textureCount)));
63 asset.SetDetails(details);
64 asset.SetDependencies(filenames);
65 return true;
68 virtual void DiscardChanges(IEditableAsset& asset) override
70 if (!m_pMaterial)
72 return;
75 m_pMaterial->Reload();
78 virtual bool OnCopyAsset(INewAsset& asset) override
80 CMaterial* pMaterial = GetIEditor()->GetMaterialManager()->DuplicateMaterial(PathUtil::Make(asset.GetFolder(), asset.GetName()), m_pMaterial);
81 CSession session(pMaterial);
82 return session.OnSaveAsset(asset);
85 static CMaterial* GetSessionMaterial(CAsset* pAsset)
87 IAssetEditingSession* pSession = pAsset->GetEditingSession();
88 if (!pSession || strcmp(pSession->GetEditorName(), "Material Editor") != 0)
90 return nullptr;
92 return static_cast<CSession*>(pSession)->GetMaterial();
95 private:
96 CMaterial* GetMaterial() { return m_pMaterial; }
98 _smart_ptr<CMaterial> m_pMaterial;
103 CMaterialEditor::CMaterialEditor()
104 : CAssetEditor("Material")
105 , m_pMaterial(nullptr)
107 RegisterActions();
108 GetIEditor()->GetMaterialManager()->AddListener(this);
111 CMaterialEditor::~CMaterialEditor()
113 GetIEditor()->GetMaterialManager()->RemoveListener(this);
116 void CMaterialEditor::RegisterActions()
118 RegisterAction("general.undo", &CMaterialEditor::OnUndo);
119 RegisterAction("general.redo", &CMaterialEditor::OnRedo);
122 void CMaterialEditor::InitMenuBar()
124 AddToMenu({ CEditor::MenuItems::FileMenu, CEditor::MenuItems::SaveAs, CEditor::MenuItems::EditMenu, CEditor::MenuItems::Undo, CEditor::MenuItems::Redo });
126 CAbstractMenu* pFileMenu = GetMenu(CEditor::MenuItems::FileMenu);
127 QAction* pAction = pFileMenu->CreateAction(CryIcon("icons:General/Picker.ico"), tr("Pick Material From Scene"), 0, 1);
128 connect(pAction, &QAction::triggered, this, &CMaterialEditor::OnPickMaterial);
130 //Add a material actions menu
131 //TODO: consider adding a toolbar for material actions
132 CAbstractMenu* materialMenu = GetRootMenu()->CreateMenu(tr("Material"), 0, 3);
133 materialMenu->signalAboutToShow.Connect(this, &CMaterialEditor::FillMaterialMenu);
136 void CMaterialEditor::OnEditorNotifyEvent(EEditorNotifyEvent event)
138 switch (event)
140 case eNotify_OnEndSceneOpen:
141 case eNotify_OnEndNewScene:
142 //HACK : Due to the questionable behavior of material manager, which clears itself when the level is loaded
143 //The materials used in the scene (and the material editor) will be recreated after the scene is loaded,
144 //making the material editor appear to be not in sync. A quick hack is to reload from filename, to get the updated material.
145 if (GetAssetBeingEdited())
147 OnOpenAsset(GetAssetBeingEdited());
152 void CMaterialEditor::OnDataBaseItemEvent(IDataBaseItem* pItem, EDataBaseItemEvent event)
154 if (!pItem || !m_pMaterial || event == EDB_ITEM_EVENT_SELECTED)
155 return;
157 CMaterial* item = (CMaterial*)pItem;
159 if (pItem == m_pMaterial.get())
161 switch (event)
163 case EDB_ITEM_EVENT_CHANGED:
164 case EDB_ITEM_EVENT_UPDATE_PROPERTIES:
165 signalMaterialPropertiesChanged(item);
166 if (GetAssetBeingEdited())
168 GetAssetBeingEdited()->SetModified(true);
170 break;
171 case EDB_ITEM_EVENT_DELETE:
172 //Note: this happens on loading of the level. We will not handle it but things may be unexpected if the item is actually being deleted from the old material editor
173 /*CRY_ASSERT_MESSAGE(0, "Material was deleted by other means while it was being edited, this case is not well handled");
174 TryCloseAsset();*/
175 break;
176 default:
177 break;
180 else if (item->GetParent() == m_pMaterial.get())
182 switch (event)
184 case EDB_ITEM_EVENT_CHANGED:
185 case EDB_ITEM_EVENT_UPDATE_PROPERTIES:
186 signalMaterialPropertiesChanged(item);
187 if (GetAssetBeingEdited())
189 GetAssetBeingEdited()->SetModified(true);
191 break;
192 case EDB_ITEM_EVENT_DELETE: //deleted from DB, what to do ?
193 if (item == m_pEditedMaterial)
195 SelectMaterialForEdit(nullptr);
197 break;
198 default:
199 break;
204 void CMaterialEditor::OnSubMaterialsChanged(CMaterial::SubMaterialChange change)
206 if (GetAssetBeingEdited())
208 GetAssetBeingEdited()->SetModified(true);
211 switch (change)
213 case CMaterial::MaterialConverted:
214 if (m_pMaterial->IsMultiSubMaterial())
216 if (m_pMaterial->GetSubMaterialCount() > 0)
218 SelectMaterialForEdit(m_pMaterial->GetSubMaterial(0));
220 else
222 SelectMaterialForEdit(nullptr);
225 else
227 SelectMaterialForEdit(m_pMaterial);
229 break;
230 case CMaterial::SubMaterialSet:
231 //If the material we are currently editing is no longer a child of loaded material, clear it
232 if (m_pEditedMaterial && m_pMaterial->IsMultiSubMaterial() && m_pEditedMaterial->GetParent() != m_pMaterial)
234 SelectMaterialForEdit(nullptr);
236 break;
237 case CMaterial::SlotCountChanged:
238 if (m_pMaterial->IsMultiSubMaterial() && m_pMaterial->GetSubMaterialCount() == 0)
240 SelectMaterialForEdit(nullptr);
242 break;
243 default:
244 break;
248 void CMaterialEditor::OnInitialize()
250 RegisterDockableWidget("Properties", [&]()
252 CInspector* pInspector = new CInspector(this);
253 pInspector->SetLockable(false);
255 if (m_pEditedMaterial)
257 BroadcastPopulateInspector();
260 return pInspector;
261 }, true);
263 RegisterDockableWidget("Material", [&]() { return new CSubMaterialView(this); }, true);
264 RegisterDockableWidget("Preview", [&]() { return new CMaterialPreviewWidget(this); });
266 InitMenuBar();
269 void CMaterialEditor::OnCreateDefaultLayout(CDockableContainer* pSender, QWidget* pAssetBrowser)
271 QWidget* pCenterWidget = pSender->SpawnWidget("Properties", pAssetBrowser, QToolWindowAreaReference::VSplitRight);
272 pSender->SpawnWidget("Preview", pCenterWidget, QToolWindowAreaReference::Right);
273 QWidget* pMaterialWidget = pSender->SpawnWidget("Material", pCenterWidget, QToolWindowAreaReference::Top);
274 pSender->SetSplitterSizes(pMaterialWidget, { 1, 4 });
277 void CMaterialEditor::OnLayoutChange(const QVariantMap& state)
279 //Rebroadcast on layout change
280 //Do not update inspector as the properties pane is unique anyway
281 if (m_pMaterial)
283 signalMaterialLoaded(m_pMaterial);
284 if (m_pEditedMaterial)
286 signalMaterialForEditChanged(m_pEditedMaterial);
289 CAssetEditor::OnLayoutChange(state);
292 bool CMaterialEditor::OnOpenAsset(CAsset* pAsset)
294 using namespace Private_MaterialEditor;
296 CAbstractMenu* materialMenu = GetMenu(tr("Material"));
297 CRY_ASSERT(materialMenu);
298 materialMenu->SetEnabled(!pAsset->IsImmutable());
300 const auto& filename = pAsset->GetFile(0);
301 CRY_ASSERT(filename && *filename);
303 const string materialName = GetIEditor()->GetMaterialManager()->FilenameToMaterial(filename);
305 CMaterial* pMaterial = CSession::GetSessionMaterial(pAsset);
306 if (!pMaterial)
308 pMaterial = static_cast<CMaterial*>(GetIEditor()->GetMaterialManager()->FindItemByName(materialName));
311 if (!pMaterial)
313 pMaterial = GetIEditor()->GetMaterialManager()->LoadMaterial(materialName, false);
315 else if (!pAsset->IsModified())
317 pMaterial->Reload(); //Enforce loading from file every time as we cannot assume the file has not changed compared to in-memory material
320 if (!pMaterial)
322 return false;
325 SetMaterial(pMaterial);
327 return true;
330 void CMaterialEditor::OnCloseAsset()
332 SetMaterial(nullptr);
335 void CMaterialEditor::OnDiscardAssetChanges(CEditableAsset& editAsset)
337 CRY_ASSERT(GetAssetBeingEdited());
338 CRY_ASSERT(GetAssetBeingEdited()->GetEditingSession());
340 GetAssetBeingEdited()->GetEditingSession()->DiscardChanges(editAsset);
343 std::unique_ptr<IAssetEditingSession> CMaterialEditor::CreateEditingSession()
345 using namespace Private_MaterialEditor;
347 if (!m_pMaterial)
349 return nullptr;
352 CSession* pSession = new CSession(m_pMaterial);
353 return std::unique_ptr<IAssetEditingSession>(pSession);
356 void CMaterialEditor::SelectMaterialForEdit(CMaterial* pMaterial)
358 if (pMaterial != m_pEditedMaterial)
360 m_pEditedMaterial = pMaterial;
362 signalMaterialForEditChanged(pMaterial);
364 BroadcastPopulateInspector();
368 void CMaterialEditor::BroadcastPopulateInspector()
370 if (m_pEditedMaterial)
372 m_pMaterialSerializer.reset(new CMaterialSerializer(m_pEditedMaterial, !m_pEditedMaterial->CanModify()));
373 string title = m_pMaterial->GetName();
374 if (m_pMaterial->IsMultiSubMaterial())
376 title += " [";
377 title += m_pEditedMaterial->GetName();
378 title += "]";
381 PopulateInspectorEvent event([this](CInspector& inspector)
383 inspector.AddPropertyTree(m_pMaterialSerializer->CreatePropertyTree());
384 }, title);
385 event.Broadcast(this);
387 else
389 m_pMaterialSerializer.reset();
390 ClearInspectorEvent().Broadcast(this);
394 void CMaterialEditor::SetMaterial(CMaterial* pMaterial)
396 if (pMaterial != m_pMaterial)
398 if (m_pMaterial)
400 m_pMaterial->signalSubMaterialsChanged.DisconnectObject(this);
403 SelectMaterialForEdit(nullptr);
405 m_pMaterial = pMaterial;
407 signalMaterialLoaded(m_pMaterial);
409 if (m_pMaterial)
411 m_pMaterial->signalSubMaterialsChanged.Connect(this, &CMaterialEditor::OnSubMaterialsChanged);
413 if (m_pMaterial->IsMultiSubMaterial())
415 if (m_pMaterial->GetSubMaterialCount() > 0)
416 SelectMaterialForEdit(m_pMaterial->GetSubMaterial(0));
417 else
418 SelectMaterialForEdit(nullptr);
420 else
421 SelectMaterialForEdit(m_pMaterial);
426 void CMaterialEditor::FillMaterialMenu(CAbstractMenu* menu)
428 menu->Clear();
430 int sec = menu->GetNextEmptySection();
432 auto action = menu->CreateAction(tr("Convert To Multi-Material"), sec);
433 action->setEnabled(m_pMaterial && !m_pMaterial->IsMultiSubMaterial());
434 connect(action, &QAction::triggered, this, &CMaterialEditor::OnConvertToMultiMaterial);
436 sec = menu->GetNextEmptySection();
438 action = menu->CreateAction(tr("Convert To Single Material"), sec);
439 action->setEnabled(m_pMaterial && m_pMaterial->IsMultiSubMaterial());
440 connect(action, &QAction::triggered, this, &CMaterialEditor::OnConvertToSingleMaterial);
442 action = menu->CreateAction(tr("Add Sub-Material"), sec);
443 action->setEnabled(m_pMaterial && m_pMaterial->IsMultiSubMaterial());
444 connect(action, &QAction::triggered, this, &CMaterialEditor::OnAddSubMaterial);
446 action = menu->CreateAction(tr("Set Sub-Material Slot Count..."), sec);
447 action->setEnabled(m_pMaterial && m_pMaterial->IsMultiSubMaterial());
448 connect(action, &QAction::triggered, this, &CMaterialEditor::OnSetSubMaterialSlotCount);
451 void CMaterialEditor::OnConvertToMultiMaterial()
453 CUndo undo("Convert To Multi-Material");
454 CRY_ASSERT(m_pMaterial && !m_pMaterial->IsMultiSubMaterial());
455 m_pMaterial->ConvertToMultiMaterial();
458 void CMaterialEditor::OnConvertToSingleMaterial()
460 CUndo undo("Convert To Single Material");
461 CRY_ASSERT(m_pMaterial && m_pMaterial->IsMultiSubMaterial());
462 m_pMaterial->ConvertToSingleMaterial();
465 void CMaterialEditor::OnAddSubMaterial()
467 CUndo undo("Add Sub-Material");
468 CRY_ASSERT(m_pMaterial && m_pMaterial->IsMultiSubMaterial());
470 const auto count = m_pMaterial->GetSubMaterialCount();
471 m_pMaterial->SetSubMaterialCount(count + 1);
472 m_pMaterial->ResetSubMaterial(count, false);
475 void CMaterialEditor::OnSetSubMaterialSlotCount()
477 CRY_ASSERT(m_pMaterial && m_pMaterial->IsMultiSubMaterial());
478 QNumericBoxDialog dialog("Set Sub-Material Slot Count", m_pMaterial->GetSubMaterialCount(), this);
480 dialog.RestrictToInt();
481 dialog.SetMin(0);
483 if (dialog.Execute())
485 CUndo undo("Set Sub-Material Slot Count");
486 const int newCount = dialog.GetValue();
487 m_pMaterial->SetSubMaterialCount(newCount);
489 for (int i = 0; i < newCount; i++)
491 m_pMaterial->ResetSubMaterial(i, true);
496 void CMaterialEditor::OnPickMaterial()
498 auto pickMaterialTool = new CPickMaterialTool();
499 pickMaterialTool->SetActiveEditor(this);
501 GetIEditor()->GetLevelEditorSharedState()->SetEditTool(pickMaterialTool);
504 void CMaterialEditor::OnResetSubMaterial(int slot)
506 CUndo undo("Reset Sub-Material");
507 CRY_ASSERT(m_pMaterial && m_pMaterial->IsMultiSubMaterial());
508 m_pMaterial->ResetSubMaterial(slot, false);
511 void CMaterialEditor::OnRemoveSubMaterial(int slot)
513 CUndo undo("Remove Sub-Material");
514 CRY_ASSERT(m_pMaterial && m_pMaterial->IsMultiSubMaterial());
515 m_pMaterial->SetSubMaterial(slot, nullptr);