!XF (Sandbox) (DEV-7472) Integrating ce_task_proptree2_improvements to ce/main....
[CRYENGINE.git] / Code / Sandbox / Plugins / MaterialEditorPlugin / MaterialEditor.cpp
blobfc24ae28ce3e5e46384ee763d621f9837198e955
1 // Copyright 2001-2018 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 InitMenuBar();
108 CreateToolbar();
109 EnableDockingSystem();
111 RegisterDockableWidget("Properties", [&]()
113 CInspector* pInspector = new CInspector(this);
114 pInspector->SetLockable(false);
115 return pInspector;
116 }, true);
118 RegisterDockableWidget("Material", [&]() { return new CSubMaterialView(this); }, true);
119 RegisterDockableWidget("Preview", [&]() { return new CMaterialPreviewWidget(this); });
121 GetIEditor()->GetMaterialManager()->AddListener(this);
124 void CMaterialEditor::CreateToolbar()
126 const auto pToolbar = new QWidget;
127 const auto pSpacer = new QWidget();
128 pSpacer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
129 auto toolbarLayout = new QHBoxLayout(pToolbar);
130 toolbarLayout->addWidget(pSpacer);
131 toolbarLayout->addWidget(CreateInstantEditorToolbar());
132 toolbarLayout->setContentsMargins(0, 0, 0, 0);
134 QVBoxLayout* pMainLayout = new QVBoxLayout();
135 pMainLayout->setContentsMargins(1, 1, 1, 1);
136 pMainLayout->addWidget(pToolbar);
137 SetContent(pMainLayout);
140 CMaterialEditor::~CMaterialEditor()
142 GetIEditor()->GetMaterialManager()->RemoveListener(this);
145 void CMaterialEditor::InitMenuBar()
147 AddToMenu(CEditor::MenuItems::SaveAs);
148 AddToMenu(CEditor::MenuItems::EditMenu);
149 AddToMenu(CEditor::MenuItems::Undo);
150 AddToMenu(CEditor::MenuItems::Redo);
152 CAbstractMenu* pFileMenu = GetMenu(CEditor::MenuItems::FileMenu);
153 QAction* pAction = pFileMenu->CreateAction(CryIcon("icons:General/Picker.ico"), tr("Pick Material From Scene"), 0, 1);
154 connect(pAction, &QAction::triggered, this, &CMaterialEditor::OnPickMaterial);
156 //Add a material actions menu
157 //TODO: consider adding a toolbar for material actions
158 CAbstractMenu* materialMenu = GetRootMenu()->CreateMenu(tr("Material"), 0, 3);
159 materialMenu->signalAboutToShow.Connect(this, &CMaterialEditor::FillMaterialMenu);
161 // Enable instant editing if possible
162 CAbstractMenu* const pEditMenu = GetMenu(CEditor::MenuItems::EditMenu);
163 pEditMenu->AddCommandAction(m_pLockAction);
166 void CMaterialEditor::OnEditorNotifyEvent(EEditorNotifyEvent event)
168 switch (event)
170 case eNotify_OnEndSceneOpen:
171 case eNotify_OnEndNewScene:
172 //HACK : Due to the questionable behavior of material manager, which clears itself when the level is loaded
173 //The materials used in the scene (and the material editor) will be recreated after the scene is loaded,
174 //making the material editor appear to be not in sync. A quick hack is to reload from filename, to get the updated material.
175 if (GetAssetBeingEdited())
177 OnOpenAsset(GetAssetBeingEdited());
182 void CMaterialEditor::OnDataBaseItemEvent(IDataBaseItem* pItem, EDataBaseItemEvent event)
184 if (!pItem || !m_pMaterial || event == EDB_ITEM_EVENT_SELECTED)
185 return;
187 CMaterial* item = (CMaterial*)pItem;
189 if (pItem == m_pMaterial.get())
191 switch (event)
193 case EDB_ITEM_EVENT_CHANGED:
194 case EDB_ITEM_EVENT_UPDATE_PROPERTIES:
195 signalMaterialPropertiesChanged(item);
196 if (GetAssetBeingEdited())
198 GetAssetBeingEdited()->SetModified(true);
200 break;
201 case EDB_ITEM_EVENT_DELETE:
202 //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
203 /*CRY_ASSERT_MESSAGE(0, "Material was deleted by other means while it was being edited, this case is not well handled");
204 TryCloseAsset();*/
205 break;
206 default:
207 break;
210 else if (item->GetParent() == m_pMaterial.get())
212 switch (event)
214 case EDB_ITEM_EVENT_CHANGED:
215 case EDB_ITEM_EVENT_UPDATE_PROPERTIES:
216 signalMaterialPropertiesChanged(item);
217 if (GetAssetBeingEdited())
219 GetAssetBeingEdited()->SetModified(true);
221 break;
222 case EDB_ITEM_EVENT_DELETE: //deleted from DB, what to do ?
223 if (item == m_pEditedMaterial)
225 SelectMaterialForEdit(nullptr);
227 break;
228 default:
229 break;
234 void CMaterialEditor::OnSubMaterialsChanged(CMaterial::SubMaterialChange change)
236 if (GetAssetBeingEdited())
238 GetAssetBeingEdited()->SetModified(true);
241 switch (change)
243 case CMaterial::MaterialConverted:
244 if (m_pMaterial->IsMultiSubMaterial())
246 if (m_pMaterial->GetSubMaterialCount() > 0)
248 SelectMaterialForEdit(m_pMaterial->GetSubMaterial(0));
250 else
252 SelectMaterialForEdit(nullptr);
255 else
257 SelectMaterialForEdit(m_pMaterial);
259 break;
260 case CMaterial::SubMaterialSet:
261 //If the material we are currently editing is no longer a child of loaded material, clear it
262 if (m_pEditedMaterial && m_pMaterial->IsMultiSubMaterial() && m_pEditedMaterial->GetParent() != m_pMaterial)
264 SelectMaterialForEdit(nullptr);
266 break;
267 case CMaterial::SlotCountChanged:
268 if (m_pMaterial->IsMultiSubMaterial() && m_pMaterial->GetSubMaterialCount() == 0)
270 SelectMaterialForEdit(nullptr);
272 break;
273 default:
274 break;
278 void CMaterialEditor::CreateDefaultLayout(CDockableContainer* pSender)
280 QWidget* pCenterWidget = pSender->SpawnWidget("Properties");
281 pSender->SpawnWidget("Preview", pCenterWidget, QToolWindowAreaReference::Right);
282 QWidget* pMaterialWidget = pSender->SpawnWidget("Material", pCenterWidget, QToolWindowAreaReference::Top);
283 pSender->SetSplitterSizes(pMaterialWidget, { 1, 4 });
286 void CMaterialEditor::OnLayoutChange(const QVariantMap& state)
288 //Rebroadcast on layout change
289 //Do not update inspector as the properties pane is unique anyway
290 if (m_pMaterial)
292 signalMaterialLoaded(m_pMaterial);
293 if (m_pEditedMaterial)
295 signalMaterialForEditChanged(m_pEditedMaterial);
296 BroadcastPopulateInspector();
299 CAssetEditor::OnLayoutChange(state);
302 bool CMaterialEditor::OnOpenAsset(CAsset* pAsset)
304 using namespace Private_MaterialEditor;
306 CAbstractMenu* materialMenu = GetMenu(tr("Material"));
307 CRY_ASSERT(materialMenu);
308 materialMenu->SetEnabled(!pAsset->IsImmutable());
310 const auto& filename = pAsset->GetFile(0);
311 CRY_ASSERT(filename && *filename);
313 const string materialName = GetIEditor()->GetMaterialManager()->FilenameToMaterial(filename);
315 CMaterial* pMaterial = CSession::GetSessionMaterial(pAsset);
316 if (!pMaterial)
318 pMaterial = static_cast<CMaterial*>(GetIEditor()->GetMaterialManager()->FindItemByName(materialName));
321 if (!pMaterial)
323 pMaterial = GetIEditor()->GetMaterialManager()->LoadMaterial(materialName, false);
325 else if (!pAsset->IsModified())
327 pMaterial->Reload(); //Enforce loading from file every time as we cannot assume the file has not changed compared to in-memory material
330 if (!pMaterial)
332 return false;
335 SetMaterial(pMaterial);
337 return true;
340 bool CMaterialEditor::OnSaveAsset(CEditableAsset& editAsset)
342 return GetAssetBeingEdited()->GetEditingSession()->OnSaveAsset(editAsset);
345 void CMaterialEditor::OnCloseAsset()
347 SetMaterial(nullptr);
350 void CMaterialEditor::OnDiscardAssetChanges(CEditableAsset& editAsset)
352 CRY_ASSERT(GetAssetBeingEdited());
353 CRY_ASSERT(GetAssetBeingEdited()->GetEditingSession());
355 GetAssetBeingEdited()->GetEditingSession()->DiscardChanges(editAsset);
358 std::unique_ptr<IAssetEditingSession> CMaterialEditor::CreateEditingSession()
360 using namespace Private_MaterialEditor;
362 if (!m_pMaterial)
364 return nullptr;
367 CSession* pSession = new CSession(m_pMaterial);
368 return std::unique_ptr<IAssetEditingSession>(pSession);
371 void CMaterialEditor::SelectMaterialForEdit(CMaterial* pMaterial)
373 if (pMaterial != m_pEditedMaterial)
375 m_pEditedMaterial = pMaterial;
377 signalMaterialForEditChanged(pMaterial);
379 BroadcastPopulateInspector();
383 void CMaterialEditor::BroadcastPopulateInspector()
385 if (m_pEditedMaterial)
387 m_pMaterialSerializer.reset(new CMaterialSerializer(m_pEditedMaterial, !m_pEditedMaterial->CanModify()));
388 string title = m_pMaterial->GetName();
389 if (m_pMaterial->IsMultiSubMaterial())
391 title += " [";
392 title += m_pEditedMaterial->GetName();
393 title += "]";
396 PopulateInspectorEvent event([this](CInspector& inspector)
398 inspector.AddPropertyTree(m_pMaterialSerializer->CreatePropertyTree());
399 }, title);
400 event.Broadcast(this);
402 else
404 m_pMaterialSerializer.reset();
405 ClearInspectorEvent().Broadcast(this);
409 void CMaterialEditor::SetMaterial(CMaterial* pMaterial)
411 if (pMaterial != m_pMaterial)
413 if (m_pMaterial)
415 m_pMaterial->signalSubMaterialsChanged.DisconnectObject(this);
418 SelectMaterialForEdit(nullptr);
420 m_pMaterial = pMaterial;
422 signalMaterialLoaded(m_pMaterial);
424 if (m_pMaterial)
426 m_pMaterial->signalSubMaterialsChanged.Connect(this, &CMaterialEditor::OnSubMaterialsChanged);
428 if (m_pMaterial->IsMultiSubMaterial())
430 if (m_pMaterial->GetSubMaterialCount() > 0)
431 SelectMaterialForEdit(m_pMaterial->GetSubMaterial(0));
432 else
433 SelectMaterialForEdit(nullptr);
435 else
436 SelectMaterialForEdit(m_pMaterial);
441 void CMaterialEditor::FillMaterialMenu(CAbstractMenu* menu)
443 menu->Clear();
445 int sec = menu->GetNextEmptySection();
447 auto action = menu->CreateAction(tr("Convert To Multi-Material"), sec);
448 action->setEnabled(m_pMaterial && !m_pMaterial->IsMultiSubMaterial());
449 connect(action, &QAction::triggered, this, &CMaterialEditor::OnConvertToMultiMaterial);
451 sec = menu->GetNextEmptySection();
453 action = menu->CreateAction(tr("Convert To Single Material"), sec);
454 action->setEnabled(m_pMaterial && m_pMaterial->IsMultiSubMaterial());
455 connect(action, &QAction::triggered, this, &CMaterialEditor::OnConvertToSingleMaterial);
457 action = menu->CreateAction(tr("Add Sub-Material"), sec);
458 action->setEnabled(m_pMaterial && m_pMaterial->IsMultiSubMaterial());
459 connect(action, &QAction::triggered, this, &CMaterialEditor::OnAddSubMaterial);
461 action = menu->CreateAction(tr("Set Sub-Material Slot Count..."), sec);
462 action->setEnabled(m_pMaterial && m_pMaterial->IsMultiSubMaterial());
463 connect(action, &QAction::triggered, this, &CMaterialEditor::OnSetSubMaterialSlotCount);
466 void CMaterialEditor::OnConvertToMultiMaterial()
468 CUndo undo("Convert To Multi-Material");
469 CRY_ASSERT(m_pMaterial && !m_pMaterial->IsMultiSubMaterial());
470 m_pMaterial->ConvertToMultiMaterial();
473 void CMaterialEditor::OnConvertToSingleMaterial()
475 CUndo undo("Convert To Single Material");
476 CRY_ASSERT(m_pMaterial && m_pMaterial->IsMultiSubMaterial());
477 m_pMaterial->ConvertToSingleMaterial();
480 void CMaterialEditor::OnAddSubMaterial()
482 CUndo undo("Add Sub-Material");
483 CRY_ASSERT(m_pMaterial && m_pMaterial->IsMultiSubMaterial());
485 const auto count = m_pMaterial->GetSubMaterialCount();
486 m_pMaterial->SetSubMaterialCount(count + 1);
487 m_pMaterial->ResetSubMaterial(count, false);
490 void CMaterialEditor::OnSetSubMaterialSlotCount()
492 CRY_ASSERT(m_pMaterial && m_pMaterial->IsMultiSubMaterial());
493 QNumericBoxDialog dialog("Set Sub-Material Slot Count", m_pMaterial->GetSubMaterialCount(), this);
495 dialog.RestrictToInt();
496 dialog.SetMin(0);
498 if (dialog.Execute())
500 CUndo undo("Set Sub-Material Slot Count");
501 const int newCount = dialog.GetValue();
502 m_pMaterial->SetSubMaterialCount(newCount);
504 for (int i = 0; i < newCount; i++)
506 m_pMaterial->ResetSubMaterial(i, true);
511 void CMaterialEditor::OnPickMaterial()
513 auto pickMaterialTool = new CPickMaterialTool();
514 pickMaterialTool->SetActiveEditor(this);
516 GetIEditor()->GetLevelEditorSharedState()->SetEditTool(pickMaterialTool);
519 void CMaterialEditor::OnResetSubMaterial(int slot)
521 CUndo undo("Reset Sub-Material");
522 CRY_ASSERT(m_pMaterial && m_pMaterial->IsMultiSubMaterial());
523 m_pMaterial->ResetSubMaterial(slot, false);
526 void CMaterialEditor::OnRemoveSubMaterial(int slot)
528 CUndo undo("Remove Sub-Material");
529 CRY_ASSERT(m_pMaterial && m_pMaterial->IsMultiSubMaterial());
530 m_pMaterial->SetSubMaterial(slot, nullptr);