#include "CWorldEditor.h"
#include "ui_CWorldEditor.h"
#include "CConfirmUnlinkDialog.h"
#include "CLayerEditor.h"
#include "CTemplateMimeData.h"
#include "WCreateTab.h"
#include "WModifyTab.h"
#include "WInstancesTab.h"

#include "Editor/CAboutDialog.h"
#include "Editor/CBasicViewport.h"
#include "Editor/CExportGameDialog.h"
#include "Editor/CNodeCopyMimeData.h"
#include "Editor/CProjectSettingsDialog.h"
#include "Editor/CQuickplayPropertyEditor.h"
#include "Editor/CSelectionIterator.h"
#include "Editor/UICommon.h"
#include "Editor/PropertyEdit/CPropertyView.h"
#include "Editor/ResourceBrowser/CResourceBrowser.h"
#include "Editor/Widgets/WDraggableSpinBox.h"
#include "Editor/Widgets/WVectorEditor.h"
#include "Editor/Undo/UndoCommands.h"

#include <Common/Log.h>
#include <Core/GameProject/CGameProject.h>
#include <Core/Render/CDrawUtil.h>
#include <Core/Resource/Script/NGameList.h>
#include <Core/Scene/CSceneIterator.h>

#include <QButtonGroup>
#include <QClipboard>
#include <QComboBox>
#include <QFontMetrics>
#include <QMessageBox>
#include <QSettings>
#include <QToolButton>

CWorldEditor::CWorldEditor(QWidget *parent)
    : INodeEditor(parent)
    , ui(new Ui::CWorldEditor)
    , mpArea(nullptr)
    , mpWorld(nullptr)
    , mpLinkDialog(new CLinkDialog(this, this))
    , mpGeneratePropertyNamesDialog(new CGeneratePropertyNamesDialog(this))
    , mpTweakEditor(new CTweakEditor(this))
    , mIsMakingLink(false)
    , mpNewLinkSender(nullptr)
    , mpNewLinkReceiver(nullptr)
{
    debugf("Creating World Editor");
    ui->setupUi(this);
    UpdateWindowTitle();

    mpSelection->SetAllowedNodeTypes(ENodeType::Script | ENodeType::Light);

    // Add resource browser to the layout
    QVBoxLayout *pLayout = new QVBoxLayout();
    pLayout->setContentsMargins(0,0,0,0);

    CResourceBrowser *pResourceBrowser = gpEdApp->ResourceBrowser();
    pResourceBrowser->setParent(this);
    pLayout->addWidget( pResourceBrowser );

    ui->ResourceBrowserContainer->setLayout(pLayout);

    // Initialize splitter
    QList<int> SplitterSizes;
    SplitterSizes << width() * 0.25 << width() * 0.53 << width() * 0.22;
    ui->splitter->setSizes(SplitterSizes);

    // Initialize UI stuff
    ResetCamera();
    ui->MainViewport->SetScene(this, &mScene);
    ui->MainViewport->setAcceptDrops(true);
    ui->TransformSpinBox->SetOrientation(Qt::Horizontal);
    ui->TransformSpinBox->layout()->setContentsMargins(0,0,0,0);
    ui->CamSpeedSpinBox->SetDefaultValue(1.0);

    mpTransformCombo->setMinimumWidth(75);
    ui->MainToolBar->addActions(mGizmoActions);
    ui->MainToolBar->addWidget(mpTransformCombo);
    AddUndoActions(ui->menuEdit, ui->ActionCut);
    ui->menuEdit->insertSeparator(ui->ActionCut);

    // Initialize sidebar
    mpCurSidebar = nullptr;
    mpRightSidebarLayout = new QVBoxLayout();
    mpRightSidebarLayout->setContentsMargins(0, 0, 0, 0);
    ui->RightSidebarFrame->setLayout(mpRightSidebarLayout);

    mpWorldInfoSidebar = new CWorldInfoSidebar(this);
    mpScriptSidebar = new CScriptEditSidebar(this);
    mpPoiMapSidebar = new CPoiMapSidebar(this);

    // Initialize edit mode toolbar
    mpEditModeButtonGroup = new QButtonGroup(this);
    connect(mpEditModeButtonGroup, SIGNAL(buttonClicked(int)), this, SLOT(ChangeEditMode(int)));

    AddEditModeButton( QIcon(":/icons/World.svg"), "Edit World Info",eWEM_EditWorldInfo );
    AddEditModeButton( QIcon(":/icons/Modify.svg"), "Edit Script", eWEM_EditScript );
    mpPoiMapAction = AddEditModeButton( QIcon(":/icons/PoiSymbol_24px.svg"), "Edit POI Mappings", eWEM_EditPOIMappings );
    mpPoiMapAction->setVisible(false);

    ChangeEditMode(eWEM_EditWorldInfo);

    // Initialize actions
    addAction(ui->ActionIncrementGizmo);
    addAction(ui->ActionDecrementGizmo);

    AddUndoActions(ui->MainToolBar, ui->ActionLink);
    ui->MainToolBar->insertSeparator(ui->ActionLink);

    ui->ActionCut->setAutoRepeat(false);
    ui->ActionCut->setShortcut(QKeySequence::Cut);
    ui->ActionCopy->setAutoRepeat(false);
    ui->ActionCopy->setShortcut(QKeySequence::Copy);
    ui->ActionPaste->setAutoRepeat(false);
    ui->ActionPaste->setShortcut(QKeySequence::Paste);
    ui->ActionDelete->setAutoRepeat(false);
    ui->ActionDelete->setShortcut(QKeySequence::Delete);

    mpCollisionDialog = new CCollisionRenderSettingsDialog(this, this);

    // Quickplay buttons
    QToolButton* pQuickplayButton = new QToolButton(this);
    pQuickplayButton->setIcon( QIcon(":/icons/Play_32px.svg") );
    pQuickplayButton->setPopupMode( QToolButton::MenuButtonPopup );
    pQuickplayButton->setMenu( new CQuickplayPropertyEditor(mQuickplayParms, this) );
    pQuickplayButton->setToolTip( "Quickplay" );

    ui->MainToolBar->addSeparator();
    mpQuickplayAction = ui->MainToolBar->addWidget(pQuickplayButton);
    mpQuickplayAction->setVisible(false);
    mpQuickplayAction->setEnabled(false);

    connect(pQuickplayButton, SIGNAL(pressed()), this, SLOT(LaunchQuickplay()));

    // "Open Recent" menu
    mpOpenRecentMenu = new QMenu(this);
    ui->ActionOpenRecent->setMenu(mpOpenRecentMenu);

    for (uint32 iAct = 0; iAct < mskMaxRecentProjects; iAct++)
    {
        QAction *pAction = new QAction(this);
        pAction->setVisible(false);
        pAction->setData((int) iAct);
        connect(pAction, SIGNAL(triggered(bool)), this, SLOT(OpenRecentProject()));

        mpOpenRecentMenu->addAction(pAction);
        mRecentProjectsActions[iAct] = pAction;
    }
    UpdateOpenRecentActions();

    // Connect signals and slots
    connect(gpEdApp, SIGNAL(ActiveProjectChanged(CGameProject*)), this, SLOT(OnActiveProjectChanged(CGameProject*)));
    connect(gpEdApp->clipboard(), SIGNAL(dataChanged()), this, SLOT(OnClipboardDataModified()));

    connect(ui->MainViewport, SIGNAL(ViewportClick(SRayIntersection,QMouseEvent*)), this, SLOT(OnViewportClick(SRayIntersection,QMouseEvent*)));
    connect(ui->MainViewport, SIGNAL(InputProcessed(SRayIntersection,QMouseEvent*)), this, SLOT(OnViewportInputProcessed(SRayIntersection,QMouseEvent*)));
    connect(ui->MainViewport, SIGNAL(InputProcessed(SRayIntersection,QMouseEvent*)), this, SLOT(UpdateGizmoUI()) );
    connect(ui->MainViewport, SIGNAL(InputProcessed(SRayIntersection,QMouseEvent*)), this, SLOT(UpdateStatusBar()) );
    connect(ui->MainViewport, SIGNAL(InputProcessed(SRayIntersection,QMouseEvent*)), this, SLOT(UpdateCursor()) );
    connect(ui->MainViewport, SIGNAL(GizmoMoved()), this, SLOT(OnGizmoMoved()));
    connect(ui->MainViewport, SIGNAL(CameraOrbit()), this, SLOT(UpdateCameraOrbit()));
    connect(this, SIGNAL(SelectionModified()), this, SLOT(OnSelectionModified()));
    connect(this, SIGNAL(SelectionTransformed()), this, SLOT(UpdateCameraOrbit()));
    connect(this, SIGNAL(PickModeEntered(QCursor)), this, SLOT(OnPickModeEnter(QCursor)));
    connect(this, SIGNAL(PickModeExited()), this, SLOT(OnPickModeExit()));
    connect(ui->TransformSpinBox, SIGNAL(ValueChanged(CVector3f)), this, SLOT(OnTransformSpinBoxModified(CVector3f)));
    connect(ui->TransformSpinBox, SIGNAL(EditingDone(CVector3f)), this, SLOT(OnTransformSpinBoxEdited(CVector3f)));
    connect(ui->CamSpeedSpinBox, SIGNAL(valueChanged(double)), this, SLOT(OnCameraSpeedChange(double)));
    connect(&UndoStack(), SIGNAL(indexChanged(int)), this, SLOT(OnUndoStackIndexChanged()));

    connect(ui->ActionOpenProject, SIGNAL(triggered()), this, SLOT(OpenProject()));
    connect(ui->ActionSave, SIGNAL(triggered()) , this, SLOT(Save()));
    connect(ui->ActionSaveAndRepack, SIGNAL(triggered()), this, SLOT(SaveAndRepack()));
    connect(ui->ActionExportGame, SIGNAL(triggered()), this, SLOT(ExportGame()));
    connect(ui->ActionProjectSettings, SIGNAL(triggered()), this, SLOT(OpenProjectSettings()));
    connect(ui->ActionCloseProject, SIGNAL(triggered()), this, SLOT(CloseProject()));
    connect(ui->ActionExit, SIGNAL(triggered()), this, SLOT(close()));

    connect(ui->ActionCut, SIGNAL(triggered()), this, SLOT(Cut()));
    connect(ui->ActionCopy, SIGNAL(triggered()), this, SLOT(Copy()));
    connect(ui->ActionPaste, SIGNAL(triggered()), this, SLOT(Paste()));
    connect(ui->ActionDelete, SIGNAL(triggered()), this, SLOT(DeleteSelection()));
    connect(ui->ActionSelectAll, SIGNAL(triggered()), this, SLOT(SelectAllTriggered()));
    connect(ui->ActionInvertSelection, SIGNAL(triggered()), this, SLOT(InvertSelectionTriggered()));
    connect(ui->ActionLink, SIGNAL(toggled(bool)), this, SLOT(OnLinkButtonToggled(bool)));
    connect(ui->ActionUnlink, SIGNAL(triggered()), this, SLOT(OnUnlinkClicked()));

    connect(ui->ActionEditTweaks, SIGNAL(triggered()), mpTweakEditor, SLOT(show()));
    connect(ui->ActionEditLayers, SIGNAL(triggered()), this, SLOT(EditLayers()));
    if (gTemplatesWritable)
        connect(ui->ActionGeneratePropertyNames, SIGNAL(triggered()), mpGeneratePropertyNamesDialog, SLOT(show()));
    else
        ui->ActionGeneratePropertyNames->setEnabled(false);

    connect(ui->ActionDrawWorld, SIGNAL(triggered()), this, SLOT(ToggleDrawWorld()));
    connect(ui->ActionDrawObjects, SIGNAL(triggered()), this, SLOT(ToggleDrawObjects()));
    connect(ui->ActionDrawCollision, SIGNAL(triggered()), this, SLOT(ToggleDrawCollision()));
    connect(ui->ActionDrawObjectCollision, SIGNAL(triggered()), this, SLOT(ToggleDrawObjectCollision()));
    connect(ui->ActionDrawLights, SIGNAL(triggered()), this, SLOT(ToggleDrawLights()));
    connect(ui->ActionDrawSky, SIGNAL(triggered()), this, SLOT(ToggleDrawSky()));
    connect(ui->ActionGameMode, SIGNAL(triggered()), this, SLOT(ToggleGameMode()));
    connect(ui->ActionDisableAlpha, SIGNAL(triggered()), this, SLOT(ToggleDisableAlpha()));
    connect(ui->ActionNoLighting, SIGNAL(triggered()), this, SLOT(SetNoLighting()));
    connect(ui->ActionBasicLighting, SIGNAL(triggered()), this, SLOT(SetBasicLighting()));
    connect(ui->ActionWorldLighting, SIGNAL(triggered()), this, SLOT(SetWorldLighting()));
    connect(ui->ActionNoBloom, SIGNAL(triggered()), this, SLOT(SetNoBloom()));
    connect(ui->ActionBloomMaps, SIGNAL(triggered()), this, SLOT(SetBloomMaps()));
    connect(ui->ActionFakeBloom, SIGNAL(triggered()), this, SLOT(SetFakeBloom()));
    connect(ui->ActionBloom, SIGNAL(triggered()), this, SLOT(SetBloom()));
    connect(ui->ActionIncrementGizmo, SIGNAL(triggered()), this, SLOT(IncrementGizmo()));
    connect(ui->ActionDecrementGizmo, SIGNAL(triggered()), this, SLOT(DecrementGizmo()));
    connect(ui->ActionCollisionRenderSettings, SIGNAL(triggered()), mpCollisionDialog, SLOT(show()));

    connect(ui->ActionAbout, SIGNAL(triggered(bool)), this, SLOT(About()));
}

CWorldEditor::~CWorldEditor()
{
    mScene.ClearScene();
    mpArea = nullptr;
    mpWorld = nullptr;
    if (gpResourceStore)
        gpResourceStore->DestroyUnreferencedResources(); // this should destroy the area!

    delete mpScriptSidebar; // For some reason WCreateTab filters an event during the viewport's destructor
    delete ui;
}

bool CWorldEditor::CloseWorld()
{
    if (CheckUnsavedChanges())
    {
        ExitPickMode();
        ClearSelection();
        ui->MainViewport->ResetHover();
        mScene.ClearScene();

        UndoStack().clear();
        mpCollisionDialog->close();
        mpLinkDialog->close();
        mpQuickplayAction->setEnabled(false);

        mpArea = nullptr;
        mpWorld = nullptr;
        if (gpResourceStore)
            gpResourceStore->DestroyUnreferencedResources(); // this should destroy the area!
        UpdateWindowTitle();

        ui->ActionSave->setEnabled(false);
        ui->ActionSaveAndRepack->setEnabled(false);
        ui->ActionEditLayers->setEnabled(false);
        emit MapChanged(mpWorld, mpArea);
        return true;
    }
    else return false;
}

bool CWorldEditor::SetArea(CWorld *pWorld, int AreaIndex)
{
    if (!CloseWorld())
        return false;

    ExitPickMode();
    ui->MainViewport->ResetHover();
    ClearSelection();
    UndoStack().clear();

    // Load new area
    mpWorld = pWorld;
    CAssetID AreaID = mpWorld->AreaResourceID(AreaIndex);
    CResourceEntry *pAreaEntry = gpResourceStore->FindEntry(AreaID);
    ASSERT(pAreaEntry);

    mpArea = pAreaEntry->Load();
    ASSERT(mpArea);
    mpWorld->SetAreaLayerInfo(mpArea);
    mScene.SetActiveArea(mpWorld, mpArea);

    // Snap camera to new area
    CCamera *pCamera = &ui->MainViewport->Camera();

    if (pCamera->MoveMode() == ECameraMoveMode::Free)
    {
        CTransform4f AreaTransform = mpArea->Transform();
        CVector3f AreaPosition(AreaTransform[0][3], AreaTransform[1][3], AreaTransform[2][3]);
        pCamera->Snap(AreaPosition);
    }

    UpdateCameraOrbit();

    // Update UI stuff
    UpdateWindowTitle();

    CGameTemplate *pGame = NGameList::GetGameTemplate(mpArea->Game());
    mpLinkDialog->SetGame(pGame);

    QString AreaName = TO_QSTRING(mpWorld->AreaInGameName(AreaIndex));

    if (CurrentGame() < EGame::DKCReturns)
        debugf("Loaded area: %s (%s)", *mpArea->Entry()->Name(), *TO_TSTRING(AreaName));
    else
        debugf("Loaded level: World %s / Area %s (%s)", *mpWorld->Entry()->Name(), *mpArea->Entry()->Name(), *TO_TSTRING(AreaName));

    // Update paste action
    OnClipboardDataModified();

    // Update toolbar actions
    ui->ActionSave->setEnabled(true);
    ui->ActionSaveAndRepack->setEnabled(true);
    ui->ActionEditLayers->setEnabled(true);
    mpQuickplayAction->setEnabled(true);

    // Emit signals
    emit MapChanged(mpWorld, mpArea);
    emit LayersModified();

    return true;
}

void CWorldEditor::ResetCamera()
{
    ui->MainViewport->Camera().Snap(CVector3f(0.f, 5.f, 1.f));
}

bool CWorldEditor::HasAnyScriptNodesSelected() const
{
    for (CSelectionIterator It(mpSelection); It; ++It)
    {
        if (It->NodeType() == ENodeType::Script)
            return true;
    }

    return false;
}

bool CWorldEditor::IsQuickplayEnabled() const
{
    return mpQuickplayAction->isVisible() && mpQuickplayAction->isEnabled();
}

CSceneViewport* CWorldEditor::Viewport() const
{
    return ui->MainViewport;
}

// ************ PUBLIC SLOTS ************
void CWorldEditor::EditorTick(float)
{
    // Update new link line
    UpdateNewLinkLine();
}

void CWorldEditor::NotifyNodeAboutToBeDeleted(CSceneNode *pNode)
{
    INodeEditor::NotifyNodeAboutToBeDeleted(pNode);

    if (ui->MainViewport->HoverNode() == pNode)
        ui->MainViewport->ResetHover();
}

bool CWorldEditor::Save()
{
    if (!mpArea)
        return true;

    bool SaveAreaSuccess = mpArea->Entry()->Save();
    bool SaveEGMCSuccess = mpArea->PoiToWorldMap() ? mpArea->PoiToWorldMap()->Entry()->Save() : true;
    bool SaveWorldSuccess = mpWorld->Entry()->Save();

    if (SaveAreaSuccess)
        mpArea->ClearExtraDependencies();

    if (SaveAreaSuccess || SaveEGMCSuccess || SaveWorldSuccess)
        gpEdApp->NotifyAssetsModified();

    if (SaveAreaSuccess && SaveEGMCSuccess && SaveWorldSuccess)
    {
        UndoStack().setClean();
        setWindowModified(false);
        return true;
    }
    else
    {
        UICommon::ErrorMsg(this, "Area failed to save!");
        return false;
    }
}

void CWorldEditor::Cut()
{
    if (!mpSelection->IsEmpty())
    {
        Copy();
        UndoStack().push(new CDeleteSelectionCommand(this, "Cut"));
    }
}

void CWorldEditor::Copy()
{
    if (!mpSelection->IsEmpty())
    {
        CNodeCopyMimeData *pMimeData = new CNodeCopyMimeData(this);
        qApp->clipboard()->setMimeData(pMimeData);
    }
}

void CWorldEditor::Paste()
{
    if (const CNodeCopyMimeData *pkMimeData =
            qobject_cast<const CNodeCopyMimeData*>(qApp->clipboard()->mimeData()))
    {
        if (pkMimeData->Game() == CurrentGame())
        {
            CVector3f PastePoint;

            if (ui->MainViewport->underMouse() && !ui->MainViewport->IsMouseInputActive())
                PastePoint = ui->MainViewport->HoverPoint();

            else
            {
                CRay Ray = ui->MainViewport->Camera().CastRay(CVector2f(0.f, 0.f));
                SRayIntersection Intersect = ui->MainViewport->SceneRayCast(Ray);

                if (Intersect.Hit)
                    PastePoint = Intersect.HitPoint;
                else
                    PastePoint = Ray.PointOnRay(10.f);
            }

            CPasteNodesCommand *pCmd = new CPasteNodesCommand(this, mpScriptSidebar->CreateTab()->SpawnLayer(), PastePoint);
            UndoStack().push(pCmd);
        }
    }
}

void CWorldEditor::OpenProject()
{
    UICommon::OpenProject();
}

void CWorldEditor::OpenRecentProject()
{
    QAction *pSender = qobject_cast<QAction*>(sender());

    if (pSender)
    {
        QSettings Settings;
        QStringList RecentProjectsList = Settings.value("WorldEditor/RecentProjectsList").toStringList();

        int ProjIndex = pSender->data().toInt();
        QString ProjPath = RecentProjectsList[ProjIndex];
        gpEdApp->OpenProject(ProjPath);
    }
}

void CWorldEditor::ExportGame()
{
    QString IsoPath = UICommon::OpenFileDialog(this, "Select ISO", "*.iso *.gcm *.tgc *.wbfs *.nfs");
    if (IsoPath.isEmpty()) return;

    QString ExportDir = UICommon::OpenDirDialog(this, "Select output export directory");
    if (ExportDir.isEmpty()) return;

    CExportGameDialog ExportDialog(IsoPath, ExportDir, this);
    if (ExportDialog.HasValidDisc()) ExportDialog.exec();

    if (ExportDialog.ExportSucceeded())
    {
        int OpenChoice = QMessageBox::information(this, "Export complete", "Export finished successfully! Open new project?", QMessageBox::Yes, QMessageBox::No);

        if (OpenChoice == QMessageBox::Yes)
            gpEdApp->OpenProject(ExportDialog.ProjectPath());
    }
}

void CWorldEditor:: CloseProject()
{
    gpEdApp->CloseProject();
    SET_WINDOWTITLE_APPVARS( QString("%APP_FULL_NAME%") );
}

void CWorldEditor::About()
{
    CAboutDialog Dialog(this);
    Dialog.exec();
}

void CWorldEditor::ChangeEditMode(int Mode)
{
    // This function is connected to the edit mode QButtonGroup.
    ChangeEditMode((EWorldEditorMode) Mode);
}

void CWorldEditor::ChangeEditMode(EWorldEditorMode Mode)
{
    mpEditModeButtonGroup->blockSignals(true);
    mpEditModeButtonGroup->button(Mode)->setChecked(true);
    mpEditModeButtonGroup->blockSignals(false);

    switch (Mode)
    {
    case eWEM_EditWorldInfo:
        SetSidebar(mpWorldInfoSidebar);
        break;

    case eWEM_EditScript:
        SetSidebar(mpScriptSidebar);
        break;

    case eWEM_EditPOIMappings:
        SetSidebar(mpPoiMapSidebar);
        break;

    default:
        ASSERT(false);
        break;
    }
}

void CWorldEditor::SetRenderingMergedWorld(bool RenderMerged)
{
    Viewport()->SetRenderMergedWorld(RenderMerged);
}

void CWorldEditor::OpenProjectSettings()
{
    CProjectSettingsDialog *pDialog = gpEdApp->ProjectDialog();
    pDialog->show();
    pDialog->raise();
}

void CWorldEditor::OnActiveProjectChanged(CGameProject *pProj)
{
    ui->ActionProjectSettings->setEnabled( pProj != nullptr );
    ui->ActionCloseProject->setEnabled( pProj != nullptr );
    mpPoiMapAction->setVisible( pProj != nullptr && pProj->Game() >= EGame::EchoesDemo && pProj->Game() <= EGame::Corruption );
    mpQuickplayAction->setVisible( pProj != nullptr && NDolphinIntegration::IsQuickplaySupported(pProj) );
    ResetCamera();
    UpdateWindowTitle();

    // Update tweak editor
    // We update this here to ensure we can update the menu item correctly without risking
    // that this function runs before the tweak editor has a chance to update its tweak list.
    mpTweakEditor->OnProjectChanged(pProj);
    ui->ActionEditTweaks->setEnabled( mpTweakEditor->HasTweaks() );

    // Default bloom to Fake Bloom for Metroid Prime 3; disable for other games
    bool AllowBloom = (CurrentGame() == EGame::CorruptionProto || CurrentGame() == EGame::Corruption);
    AllowBloom ? SetFakeBloom() : SetNoBloom();
    ui->menuBloom->setEnabled(AllowBloom);

    // Update recent projects list
    UpdateOpenRecentActions();

    // Reset editor mode
    ChangeEditMode(eWEM_EditWorldInfo);
}

void CWorldEditor::OnLinksModified(const QList<CScriptObject*>& rkInstances)
{
    foreach (CScriptObject *pInstance, rkInstances)
    {
        CScriptNode *pNode = mScene.NodeForInstance(pInstance);
        pNode->LinksModified();
    }

    if (!rkInstances.isEmpty())
        emit InstanceLinksModified(rkInstances);
}

void CWorldEditor::OnPropertyModified(IProperty *pProp)
{
    bool ShouldUpdateSelection = false;

    for (CSelectionIterator It(mpSelection); It; ++It)
    {
        CSceneNode* pNode = *It;

        if (pNode && pNode->NodeType() == ENodeType::Script)
        {
            CScriptNode* pScript = static_cast<CScriptNode*>(pNode);
            pScript->PropertyModified(pProp);

            // If this is the name, update other parts of the UI to reflect the new value.
            if ( pProp->Name() == "Name" )
            {
                UpdateStatusBar();
                UpdateSelectionUI();
            }
            else if (pProp->Name() == "Position" ||
                     pProp->Name() == "Rotation" ||
                     pProp->Name() == "Scale")
            {
                mpSelection->UpdateBounds();
            }

            // Emit signal so other widgets can react to the property change
            emit PropertyModified(pProp, pScript->Instance());
        }

        // If this is a model/character, then we'll treat this as a modified selection. This is to make sure the selection bounds updates.
        if (pProp->Type() == EPropertyType::Asset)
        {
            CAssetProperty *pAsset = TPropCast<CAssetProperty>(pProp);
            const CResTypeFilter& rkFilter = pAsset->GetTypeFilter();

            if (rkFilter.Accepts(EResourceType::Model) || rkFilter.Accepts(EResourceType::AnimSet) || rkFilter.Accepts(EResourceType::Character))
                ShouldUpdateSelection = true;
        }
        else if (pProp->Type() == EPropertyType::AnimationSet)
            ShouldUpdateSelection = true;
    }

    if (ShouldUpdateSelection)
    {
        SelectionModified();
    }
}

void CWorldEditor::SetSelectionActive(bool Active)
{
    // Gather list of selected objects that actually have Active properties
    QVector<CScriptObject*> Objects;

    for (CSelectionIterator It(mpSelection); It; ++It)
    {
        if (It->NodeType() == ENodeType::Script)
        {
            CScriptNode* pScript = static_cast<CScriptNode*>(*It);
            CScriptObject* pInst = pScript->Instance();
            Objects << pInst;
        }
    }

    if (!Objects.isEmpty())
    {
        UndoStack().beginMacro("Toggle Active");

        while (!Objects.isEmpty())
        {
            QVector<CScriptObject*> CommandObjects;
            CScriptTemplate* pTemplate = Objects[0]->Template();
            CBoolProperty* pActiveProperty = pTemplate->ActiveProperty();

            for (int ObjIdx = 0; ObjIdx < Objects.size(); ObjIdx++)
            {
                if (Objects[ObjIdx]->Template() == pTemplate)
                {
                    CommandObjects << Objects[ObjIdx];
                    Objects.removeAt(ObjIdx);
                    ObjIdx--;
                }
            }

            if (pActiveProperty)
            {
                CPropertyModel* pModel = qobject_cast<CPropertyModel*>(
                            mpScriptSidebar->ModifyTab()->PropertyView()->model()
                        );

                CEditScriptPropertyCommand* pCommand = new CEditScriptPropertyCommand(
                            pActiveProperty,
                            CommandObjects,
                            pModel
                        );

                pCommand->SaveOldData();

                foreach (CScriptObject* pInstance, CommandObjects)
                    pInstance->SetActive(Active);

                pCommand->SaveNewData();

                UndoStack().push(pCommand);
            }
        }

        UndoStack().endMacro();
    }
}

void CWorldEditor::SetSelectionInstanceNames(const QString& rkNewName, bool IsDone)
{
    // todo: this only supports one node at a time because a macro prevents us from merging undo commands
    // this is fine right now because this function is only ever called with a selection of one node, but probably want to fix in the future
    /*if (mpSelection->Size() == 1 && mpSelection->Front()->NodeType() == eScriptNode)
    {
        CScriptNode *pNode = static_cast<CScriptNode*>(mpSelection->Front());
        CScriptObject *pInst = pNode->Instance();

        if (pName)
        {
            TString NewName = TO_TSTRING(rkNewName);
            IPropertyValue *pOld = pName->RawValue()->Clone();
            pInst->SetName(NewName);
            UndoStack().push(new CEditScriptPropertyCommand(pName, this, pOld, IsDone, "Edit Instance Name"));
        }
    }*/
}

void CWorldEditor::SetSelectionLayer(CScriptLayer *pLayer)
{
    QList<CScriptNode*> ScriptNodes;

    for (CSelectionIterator It(mpSelection); It; ++It)
    {
        if (It->NodeType() == ENodeType::Script)
            ScriptNodes << static_cast<CScriptNode*>(*It);
    }

    if (!ScriptNodes.isEmpty())
        UndoStack().push(new CChangeLayerCommand(this, ScriptNodes, pLayer));
}

void CWorldEditor::DeleteSelection()
{
    if (HasAnyScriptNodesSelected())
    {
        CDeleteSelectionCommand *pCmd = new CDeleteSelectionCommand(this);
        UndoStack().push(pCmd);
    }
}

void CWorldEditor::UpdateOpenRecentActions()
{
    QSettings Settings;
    QStringList RecentProjectsList = Settings.value("WorldEditor/RecentProjectsList").toStringList();

    // Bump the current project to the front
    CGameProject *pProj = gpEdApp->ActiveProject();

    if (pProj)
    {
        QString ProjPath = TO_QSTRING(pProj->ProjectPath());
        RecentProjectsList.removeAll(ProjPath);
        RecentProjectsList.prepend(ProjPath);
    }

    // Remove projects that don't exist anymore
    foreach (const QString& rkProj, RecentProjectsList)
    {
        if (!FileUtil::Exists( TO_TSTRING(rkProj) ) || rkProj.contains('\\') )
            RecentProjectsList.removeAll(rkProj);
    }

    Settings.setValue("WorldEditor/RecentProjectsList", RecentProjectsList);

    // Set up the menu actions
    for (int iProj = 0; iProj < mskMaxRecentProjects; iProj++)
    {
        QAction *pAction = mRecentProjectsActions[iProj];

        if (iProj < RecentProjectsList.size())
        {
            QString ActionText = QString("&%1 %2").arg(iProj).arg(RecentProjectsList[iProj]);
            pAction->setText(ActionText);
            pAction->setVisible(true);
        }

        else
            pAction->setVisible(false);
    }
}

void CWorldEditor::UpdateWindowTitle()
{
    QString WindowTitle = "%APP_FULL_NAME%";
    CGameProject *pProj = gpEdApp->ActiveProject();

    if (pProj)
    {
        WindowTitle += " - " + TO_QSTRING( pProj->Name() );

        if (mpWorld)
        {
            WindowTitle += " - " + TO_QSTRING(mpWorld->InGameName());

            if (mpArea && CurrentGame() < EGame::DKCReturns)
                WindowTitle += " - " + TO_QSTRING( mpWorld->AreaInGameName(mpArea->WorldIndex()) );
        }
    }

    WindowTitle += "[*]";
    SET_WINDOWTITLE_APPVARS(WindowTitle);
}

void CWorldEditor::UpdateStatusBar()
{
    // Would be cool to do more frequent status bar updates with more info. Unfortunately, this causes lag.
    QString StatusText = "";

    if (!mGizmoHovering)
    {
        if (ui->MainViewport->underMouse())
        {
            CSceneNode *pHoverNode = ui->MainViewport->HoverNode();

            if (pHoverNode && mpSelection->IsAllowedType(pHoverNode))
                StatusText = TO_QSTRING(pHoverNode->Name());
        }
    }

    if (ui->statusbar->currentMessage() != StatusText)
        ui->statusbar->showMessage(StatusText);
}

void CWorldEditor::UpdateGizmoUI()
{
    // Update transform XYZ spin boxes
    if (!ui->TransformSpinBox->IsBeingEdited())
    {
        CVector3f SpinBoxValue = CVector3f::skZero;

        // If the gizmo is transforming, use the total transform amount
        // Otherwise, use the first selected node transform, or 0 if no selection
        if (mShowGizmo)
        {
            switch (mGizmo.Mode())
            {
            case CGizmo::EGizmoMode::Translate:
                if (mGizmoTransforming && mGizmo.HasTransformed())
                    SpinBoxValue = mGizmo.TotalTranslation();
                else if (!mpSelection->IsEmpty())
                    SpinBoxValue = mpSelection->Front()->AbsolutePosition();
                break;

            case CGizmo::EGizmoMode::Rotate:
                if (mGizmoTransforming && mGizmo.HasTransformed())
                    SpinBoxValue = mGizmo.TotalRotation();
                else if (!mpSelection->IsEmpty())
                    SpinBoxValue = mpSelection->Front()->AbsoluteRotation().ToEuler();
                break;

            case CGizmo::EGizmoMode::Scale:
                if (mGizmoTransforming && mGizmo.HasTransformed())
                    SpinBoxValue = mGizmo.TotalScale();
                else if (!mpSelection->IsEmpty())
                    SpinBoxValue = mpSelection->Front()->AbsoluteScale();
                break;

            default: break;
            }
        }
        else if (!mpSelection->IsEmpty()) SpinBoxValue = mpSelection->Front()->AbsolutePosition();

        ui->TransformSpinBox->blockSignals(true);
        ui->TransformSpinBox->SetValue(SpinBoxValue);
        ui->TransformSpinBox->blockSignals(false);
    }

    // Update gizmo
    if (!mGizmoTransforming)
    {
        // Set gizmo transform
        if (!mpSelection->IsEmpty())
        {
            mGizmo.SetPosition(mpSelection->Front()->AbsolutePosition());
            mGizmo.SetLocalRotation(mpSelection->Front()->AbsoluteRotation());
        }
    }
}

void CWorldEditor::UpdateSelectionUI()
{
    // Update selection info text
    QString SelectionText;

    if (mpSelection->Size() == 1)
        SelectionText = TO_QSTRING(mpSelection->Front()->Name());
    else if (mpSelection->Size() > 1)
        SelectionText = QString("%1 objects selected").arg(mpSelection->Size());

    QFontMetrics Metrics(ui->SelectionInfoLabel->font());
    SelectionText = Metrics.elidedText(SelectionText, Qt::ElideRight, ui->SelectionInfoFrame->width() - 10);

    if (ui->SelectionInfoLabel->text() != SelectionText)
        ui->SelectionInfoLabel->setText(SelectionText);

    // Update gizmo stuff
    UpdateGizmoUI();
}

void CWorldEditor::UpdateCursor()
{
    if (ui->MainViewport->IsCursorVisible() && !mPickMode)
    {
        CSceneNode *pHoverNode = ui->MainViewport->HoverNode();

        if (ui->MainViewport->IsHoveringGizmo())
            ui->MainViewport->SetCursorState(Qt::SizeAllCursor);
        else if ((pHoverNode) && mpSelection->IsAllowedType(pHoverNode))
            ui->MainViewport->SetCursorState(Qt::PointingHandCursor);
        else
            ui->MainViewport->SetCursorState(Qt::ArrowCursor);
    }
}

void CWorldEditor::UpdateNewLinkLine()
{
    // Check if there is a sender+receiver
    if (mpLinkDialog->isVisible() && mpLinkDialog->Sender() && mpLinkDialog->Receiver() && !mpLinkDialog->IsPicking())
    {
        CVector3f Start = mScene.NodeForInstance(mpLinkDialog->Sender())->CenterPoint();
        CVector3f End = mScene.NodeForInstance(mpLinkDialog->Receiver())->CenterPoint();
        ui->MainViewport->SetLinkLineEnabled(true);
        ui->MainViewport->SetLinkLine(Start, End);
    }

    // Otherwise check whether there's just a sender or just a receiver
    else
    {
        CScriptObject *pSender = nullptr;
        CScriptObject *pReceiver = nullptr;

        if (mpLinkDialog->isVisible())
        {
            if (mpLinkDialog->Sender() && !mpLinkDialog->IsPickingSender())
                pSender = mpLinkDialog->Sender();
            if (mpLinkDialog->Receiver() && !mpLinkDialog->IsPickingReceiver())
                pReceiver = mpLinkDialog->Receiver();
        }
        else if (mIsMakingLink && mpNewLinkSender)
            pSender = mpNewLinkSender;
        else if (mpScriptSidebar->ModifyTab()->IsPicking() && mpScriptSidebar->ModifyTab()->EditNode()->NodeType() == ENodeType::Script)
            pSender = static_cast<CScriptNode*>(mpScriptSidebar->ModifyTab()->EditNode())->Instance();

        // No sender and no receiver = no line
        if (!pSender && !pReceiver)
            ui->MainViewport->SetLinkLineEnabled(false);

        // Yes sender and yes receiver = yes line
        else if (pSender && pReceiver)
        {
            ui->MainViewport->SetLinkLineEnabled(true);
            ui->MainViewport->SetLinkLine( mScene.NodeForInstance(pSender)->CenterPoint(), mScene.NodeForInstance(pReceiver)->CenterPoint() );
        }

        // Compensate for missing sender or missing receiver
        else
        {
            bool IsPicking = (mIsMakingLink || mpLinkDialog->IsPicking() || mpScriptSidebar->ModifyTab()->IsPicking());

            if (ui->MainViewport->underMouse() && !ui->MainViewport->IsMouseInputActive() && IsPicking)
            {
                CSceneNode *pHoverNode = ui->MainViewport->HoverNode();
                CScriptObject *pInst = (pSender ? pSender : pReceiver);

                CVector3f Start = mScene.NodeForInstance(pInst)->CenterPoint();
                CVector3f End = (pHoverNode && pHoverNode->NodeType() == ENodeType::Script ? pHoverNode->CenterPoint() : ui->MainViewport->HoverPoint());
                ui->MainViewport->SetLinkLineEnabled(true);
                ui->MainViewport->SetLinkLine(Start, End);
            }

            else
                ui->MainViewport->SetLinkLineEnabled(false);
        }
    }
}

void CWorldEditor::LaunchQuickplay()
{
    CVector3f CameraPosition = Viewport()->Camera().Position();
    LaunchQuickplayFromLocation(CameraPosition, false);
}

void CWorldEditor::LaunchQuickplayFromLocation(CVector3f Location, bool ForceAsSpawnPosition)
{
    // This function should not be called if a level is not open in a project.
    ASSERT( gpEdApp->ActiveProject() != nullptr );
    ASSERT( mpWorld && mpArea );

    // Fill in parameters and start running
    SQuickplayParameters Parameters = mQuickplayParms;
    Parameters.BootWorldAssetID = mpWorld->ID().ToLong();
    Parameters.BootAreaAssetID = mpArea->ID().ToLong();
    Parameters.SpawnTransform = Viewport()->Camera().GetCameraTransform();
    Parameters.SpawnTransform.SetTranslation(Location);

    if (ForceAsSpawnPosition)
    {
        Parameters.Features.SetFlag(EQuickplayFeature::JumpToArea);
        Parameters.Features.SetFlag(EQuickplayFeature::SetSpawnPosition);
    }

    NDolphinIntegration::LaunchQuickplay(this, gpEdApp->ActiveProject(), Parameters);
}

// ************ PROTECTED ************
QAction* CWorldEditor::AddEditModeButton(QIcon Icon, QString ToolTip, EWorldEditorMode Mode)
{
    ASSERT(mpEditModeButtonGroup->button(Mode) == nullptr);

    QPushButton *pButton = new QPushButton(Icon, "", this);
    pButton->setCheckable(true);
    pButton->setToolTip(ToolTip);
    pButton->setIconSize(QSize(24, 24));

    QAction *pAction = ui->EditModeToolBar->addWidget(pButton);
    mpEditModeButtonGroup->addButton(pButton, Mode);
    return pAction;
}

void CWorldEditor::SetSidebar(CWorldEditorSidebar *pSidebar)
{
    if (mpCurSidebar == pSidebar)
        return;

    if (mpCurSidebar)
    {
        mpCurSidebar->SidebarClose();
        mpRightSidebarLayout->removeWidget(mpCurSidebar);
        mpCurSidebar->setHidden(true);
    }

    mpCurSidebar = pSidebar;

    if (mpCurSidebar)
    {
        mpCurSidebar->SidebarOpen();
        mpRightSidebarLayout->addWidget(mpCurSidebar);
        mpCurSidebar->setHidden(false);
    }
}

void CWorldEditor::GizmoModeChanged(CGizmo::EGizmoMode mode)
{
    ui->TransformSpinBox->SetSingleStep( (mode == CGizmo::EGizmoMode::Rotate ? 1.0 : 0.1) );
    ui->TransformSpinBox->SetDefaultValue( (mode == CGizmo::EGizmoMode::Scale ? 1.0 : 0.0) );
}

// ************ PRIVATE SLOTS ************
void CWorldEditor::OnClipboardDataModified()
{
    const QMimeData *pkClipboardMimeData = qApp->clipboard()->mimeData();
    const CNodeCopyMimeData *pkMimeData = qobject_cast<const CNodeCopyMimeData*>(pkClipboardMimeData);
    bool ValidMimeData = (pkMimeData && pkMimeData->Game() == CurrentGame());
    ui->ActionPaste->setEnabled(ValidMimeData);
}

void CWorldEditor::OnSelectionModified()
{
    ui->TransformSpinBox->setEnabled(!mpSelection->IsEmpty());

    bool HasScriptNode = HasAnyScriptNodesSelected();
    ui->ActionCut->setEnabled(HasScriptNode);
    ui->ActionCopy->setEnabled(HasScriptNode);
    ui->ActionDelete->setEnabled(HasScriptNode);

    UpdateCameraOrbit();
}

void CWorldEditor::OnLinkButtonToggled(bool Enabled)
{
    if (Enabled)
    {
        EnterPickMode(ENodeType::Script, true, false, false);
        connect(this, SIGNAL(PickModeClick(SRayIntersection,QMouseEvent*)), this, SLOT(OnLinkClick(SRayIntersection)));
        connect(this, SIGNAL(PickModeExited()), this, SLOT(OnLinkEnd()));
        mIsMakingLink = true;
        mpNewLinkSender = nullptr;
        mpNewLinkReceiver = nullptr;
    }

    else
    {
        if (mIsMakingLink)
            ExitPickMode();
    }
}

void CWorldEditor::OnLinkClick(const SRayIntersection& rkIntersect)
{
    if (!mpNewLinkSender)
    {
        mpNewLinkSender = static_cast<CScriptNode*>(rkIntersect.pNode)->Instance();
    }

    else
    {
        mpNewLinkReceiver = static_cast<CScriptNode*>(rkIntersect.pNode)->Instance();
        mpLinkDialog->NewLink(mpNewLinkSender, mpNewLinkReceiver);
        mpLinkDialog->show();
        ExitPickMode();
    }
}

void CWorldEditor::OnLinkEnd()
{
    disconnect(this, SIGNAL(PickModeClick(SRayIntersection,QMouseEvent*)), this, SLOT(OnLinkClick(SRayIntersection)));
    disconnect(this, SIGNAL(PickModeExited()), this, SLOT(OnLinkEnd()));
    ui->ActionLink->setChecked(false);
    mIsMakingLink = false;
    mpNewLinkSender = nullptr;
    mpNewLinkReceiver = nullptr;
}

void CWorldEditor::OnUnlinkClicked()
{
    QList<CScriptNode*> SelectedScriptNodes;

    for (CSelectionIterator It(mpSelection); It; ++It)
    {
        if (It->NodeType() == ENodeType::Script)
            SelectedScriptNodes << static_cast<CScriptNode*>(*It);
    }

    if (!SelectedScriptNodes.isEmpty())
    {
        CConfirmUnlinkDialog Dialog(this);
        Dialog.exec();

        if (Dialog.UserChoice() != CConfirmUnlinkDialog::EChoice::Cancel)
        {
            UndoStack().beginMacro("Unlink");
            bool UnlinkIncoming = (Dialog.UserChoice() != CConfirmUnlinkDialog::EChoice::OutgoingOnly);
            bool UnlinkOutgoing = (Dialog.UserChoice() != CConfirmUnlinkDialog::EChoice::IncomingOnly);

            foreach (CScriptNode *pNode, SelectedScriptNodes)
            {
                CScriptObject *pInst = pNode->Instance();

                if (UnlinkIncoming)
                {
                    QVector<uint32> LinkIndices;
                    for (uint32 iLink = 0; iLink < pInst->NumLinks(ELinkType::Incoming); iLink++)
                        LinkIndices << iLink;

                    CDeleteLinksCommand *pCmd = new CDeleteLinksCommand(this, pInst, ELinkType::Incoming, LinkIndices);
                    UndoStack().push(pCmd);
                }

                if (UnlinkOutgoing)
                {
                    QVector<uint32> LinkIndices;
                    for (uint32 iLink = 0; iLink < pInst->NumLinks(ELinkType::Outgoing); iLink++)
                        LinkIndices << iLink;

                    CDeleteLinksCommand *pCmd = new CDeleteLinksCommand(this, pInst, ELinkType::Outgoing, LinkIndices);
                    UndoStack().push(pCmd);
                }
            }

            UndoStack().endMacro();
        }
    }
}

void CWorldEditor::OnPickModeEnter(QCursor Cursor)
{
    ui->MainViewport->SetCursorState(Cursor);
}

void CWorldEditor::OnPickModeExit()
{
    UpdateCursor();
}

void CWorldEditor::UpdateCameraOrbit()
{
    CCamera *pCamera = &ui->MainViewport->Camera();

    if (!mpSelection->IsEmpty())
        pCamera->SetOrbit(mpSelection->Bounds());
    else if (mpArea)
        pCamera->SetOrbit(mpArea->AABox(), 1.2f);
    else
        pCamera->ResetOrbit();
}

void CWorldEditor::OnCameraSpeedChange(double Speed)
{
    static const double skDefaultSpeed = 1.0;
    ui->MainViewport->Camera().SetMoveSpeed(skDefaultSpeed * Speed);

    ui->CamSpeedSpinBox->blockSignals(true);
    ui->CamSpeedSpinBox->setValue(Speed);
    ui->CamSpeedSpinBox->blockSignals(false);
}

void CWorldEditor::OnTransformSpinBoxModified(CVector3f Value)
{
    if (mpSelection->IsEmpty()) return;

    switch (mGizmo.Mode())
    {
        // Use absolute position/rotation, but relative scale. (This way spinbox doesn't show preview multiplier)
        case CGizmo::EGizmoMode::Translate:
        {
            CVector3f Delta = Value - mpSelection->Front()->AbsolutePosition();
            UndoStack().push(new CTranslateNodeCommand(this, mpSelection->SelectedNodeList(), Delta, mTranslateSpace));
            break;
        }

        case CGizmo::EGizmoMode::Rotate:
        {
            CQuaternion Delta = CQuaternion::FromEuler(Value) * mpSelection->Front()->AbsoluteRotation().Inverse();
            UndoStack().push(new CRotateNodeCommand(this, mpSelection->SelectedNodeList(), true, mGizmo.Position(), mGizmo.Rotation(), Delta, mRotateSpace));
            break;
        }

        case CGizmo::EGizmoMode::Scale:
        {
            CVector3f Delta = Value / mpSelection->Front()->AbsoluteScale();
            UndoStack().push(new CScaleNodeCommand(this, mpSelection->SelectedNodeList(), true, mGizmo.Position(), Delta));
            break;
        }
        default: break;
    }

    UpdateGizmoUI();
}

void CWorldEditor::OnTransformSpinBoxEdited(CVector3f)
{
    if (mpSelection->IsEmpty()) return;

    if (mGizmo.Mode() == CGizmo::EGizmoMode::Translate)   UndoStack().push(CTranslateNodeCommand::End());
    else if (mGizmo.Mode() == CGizmo::EGizmoMode::Rotate) UndoStack().push(CRotateNodeCommand::End());
    else if (mGizmo.Mode() == CGizmo::EGizmoMode::Scale)  UndoStack().push(CScaleNodeCommand::End());

    UpdateGizmoUI();
}

void CWorldEditor::SelectAllTriggered()
{
    FNodeFlags NodeFlags = CScene::NodeFlagsForShowFlags(ui->MainViewport->ShowFlags());
    NodeFlags &= ~(ENodeType::Model | ENodeType::Static | ENodeType::Collision);
    SelectAll(NodeFlags);
}

void CWorldEditor::InvertSelectionTriggered()
{
    FNodeFlags NodeFlags = CScene::NodeFlagsForShowFlags(ui->MainViewport->ShowFlags());
    NodeFlags &= ~(ENodeType::Model | ENodeType::Static | ENodeType::Collision);
    InvertSelection(NodeFlags);
}

void CWorldEditor::ToggleDrawWorld()
{
    ui->MainViewport->SetShowWorld(ui->ActionDrawWorld->isChecked());
}

void CWorldEditor::ToggleDrawObjects()
{
    ui->MainViewport->SetShowFlag(EShowFlag::ObjectGeometry, ui->ActionDrawObjects->isChecked());
}

void CWorldEditor::ToggleDrawCollision()
{
    ui->MainViewport->SetShowFlag(EShowFlag::WorldCollision, ui->ActionDrawCollision->isChecked());
}

void CWorldEditor::ToggleDrawObjectCollision()
{
    ui->MainViewport->SetShowFlag(EShowFlag::ObjectCollision, ui->ActionDrawObjectCollision->isChecked());
}

void CWorldEditor::ToggleDrawLights()
{
    ui->MainViewport->SetShowFlag(EShowFlag::Lights, ui->ActionDrawLights->isChecked());
}

void CWorldEditor::ToggleDrawSky()
{
    ui->MainViewport->SetShowFlag(EShowFlag::Sky, ui->ActionDrawSky->isChecked());
}

void CWorldEditor::ToggleGameMode()
{
    ui->MainViewport->SetGameMode(ui->ActionGameMode->isChecked());
}

void CWorldEditor::ToggleDisableAlpha()
{
    ui->MainViewport->Renderer()->ToggleAlphaDisabled(ui->ActionDisableAlpha->isChecked());
}

void CWorldEditor::SetNoLighting()
{
    CGraphics::sLightMode = CGraphics::ELightingMode::None;
    ui->ActionNoLighting->setChecked(true);
    ui->ActionBasicLighting->setChecked(false);
    ui->ActionWorldLighting->setChecked(false);
}

void CWorldEditor::SetBasicLighting()
{
    CGraphics::sLightMode = CGraphics::ELightingMode::Basic;
    ui->ActionNoLighting->setChecked(false);
    ui->ActionBasicLighting->setChecked(true);
    ui->ActionWorldLighting->setChecked(false);
}

void CWorldEditor::SetWorldLighting()
{
    CGraphics::sLightMode = CGraphics::ELightingMode::World;
    ui->ActionNoLighting->setChecked(false);
    ui->ActionBasicLighting->setChecked(false);
    ui->ActionWorldLighting->setChecked(true);
}

void CWorldEditor::SetNoBloom()
{
    ui->MainViewport->Renderer()->SetBloom(EBloomMode::NoBloom);
    ui->ActionNoBloom->setChecked(true);
    ui->ActionBloomMaps->setChecked(false);
    ui->ActionFakeBloom->setChecked(false);
    ui->ActionBloom->setChecked(false);
}

void CWorldEditor::SetBloomMaps()
{
    ui->MainViewport->Renderer()->SetBloom(EBloomMode::BloomMaps);
    ui->ActionNoBloom->setChecked(false);
    ui->ActionBloomMaps->setChecked(true);
    ui->ActionFakeBloom->setChecked(false);
    ui->ActionBloom->setChecked(false);
}

void CWorldEditor::SetFakeBloom()
{
    ui->MainViewport->Renderer()->SetBloom(EBloomMode::FakeBloom);
    ui->ActionNoBloom->setChecked(false);
    ui->ActionBloomMaps->setChecked(false);
    ui->ActionFakeBloom->setChecked(true);
    ui->ActionBloom->setChecked(false);
}

void CWorldEditor::SetBloom()
{
    ui->MainViewport->Renderer()->SetBloom(EBloomMode::Bloom);
    ui->ActionNoBloom->setChecked(false);
    ui->ActionBloomMaps->setChecked(false);
    ui->ActionFakeBloom->setChecked(false);
    ui->ActionBloom->setChecked(true);
}

void CWorldEditor::IncrementGizmo()
{
    mGizmo.IncrementSize();
}

void CWorldEditor::DecrementGizmo()
{
    mGizmo.DecrementSize();
}

void CWorldEditor::EditLayers()
{
    // Launch layer editor
    CLayerEditor Editor(this);
    Editor.SetArea(mpArea);
    Editor.exec();
}
