| 1 | #include "ParticleEffectUI.h"
|
| 2 | #include "BasicParticleEditor.h"
|
| 3 | #include "CurveEdit.h"
|
| 4 | #include "ParticleEmitterModelEdit.h"
|
| 5 | #include "DirectionEdit.h"
|
| 6 | #include "EmitterListEdit.h"
|
| 7 | #include "Slider.h"
|
| 8 | #include "FloatSliderEdit.h"
|
| 9 | #include "ParticleEffectsWriter.h"
|
| 10 | #include "ExplosionUpdateMsg.h"
|
| 11 | #include "BaseSFXEditor.h"
|
| 12 |
|
| 13 | #include "Communication/SFXComSlot.h"
|
| 14 |
|
| 15 | // core
|
| 16 | #include "../../SDK/PlatformIndependent/Core/ProductInformation.h"
|
| 17 | #include "../../SDK/PlatformIndependent/Core/ServiceManager.h"
|
| 18 | #include "../../SDK/PlatformIndependent/Core/Label.h"
|
| 19 | #include "../../SDK/PlatformIndependent/Core/XMLParser.h"
|
| 20 | #include "../../SDK/PlatformIndependent/Core/XMLElement.h"
|
| 21 | #include "../../SDK/SDKRuntime.h"
|
| 22 | #include "../../SDK/PlatformIndependent/Core/Thread.h"
|
| 23 | #include "../../SDK/PlatformIndependent/Core/Timer.h"
|
| 24 | #include "../../SDK/PlatformIndependent/SceneManagement/SceneManager.h"
|
| 25 |
|
| 26 | // simulation facade
|
| 27 | #include "../../SimulationFacade/ExplosionConfig.h"
|
| 28 | #include "../../SimulationFacade/GenericObject.h"
|
| 29 |
|
| 30 | #include <Wt/WPanel>
|
| 31 | #include <Wt/WBreak>
|
| 32 | #include <Wt/WContainerWidget>
|
| 33 | #include <Wt/WLineEdit>
|
| 34 | #include <Wt/WPushButton>
|
| 35 | #include <Wt/WText>
|
| 36 | #include <Wt/WSlider>
|
| 37 | #include <Wt/WTable>
|
| 38 | #include <Wt/WTree>
|
| 39 | #include <Wt/WTreeNode>
|
| 40 | #include <Wt/WIconPair>
|
| 41 | #include <Wt/WGridLayout>
|
| 42 | #include <Wt/WHBoxLayout>
|
| 43 | #include <Wt/WVBoxLayout>
|
| 44 | #include <Wt/WDoubleValidator>
|
| 45 | #include <Wt/WRegExpValidator>
|
| 46 | #include <Wt/WSignalMapper>
|
| 47 | #include <Wt/WTimer>
|
| 48 | #include <Wt/WLogger>
|
| 49 | #include <Wt/WStandardItemModel>
|
| 50 | #include <Wt/Chart/WCartesianChart>
|
| 51 | #include <Wt/WPointF>
|
| 52 | #include <Wt/WPainterPath>
|
| 53 | #include <Wt/WPainter>
|
| 54 | #include <Wt/WScrollArea>
|
| 55 | #include <Wt/WGroupBox>
|
| 56 | #include <Wt/WPopupMenu>
|
| 57 | #include <Wt/WMessageBox>
|
| 58 | #include <Wt/WDialog>
|
| 59 | #include <Wt/WStackedWidget>
|
| 60 |
|
| 61 | #include <Wt/Ext/Button>
|
| 62 | #include <Wt/Ext/LineEdit>
|
| 63 | #include <Wt/Ext/ComboBox>
|
| 64 | #include <Wt/Ext/CheckBox>
|
| 65 | #include <Wt/Ext/RadioButton>
|
| 66 | #include <Wt/Ext/Calendar>
|
| 67 | #include <Wt/Ext/DateField>
|
| 68 | #include <Wt/Ext/Menu>
|
| 69 | #include <Wt/Ext/ToolBar>
|
| 70 | #include <Wt/Ext/NumberField>
|
| 71 |
|
| 72 |
|
| 73 | using namespace Wt;
|
| 74 | using namespace Wt::Chart;
|
| 75 | using namespace TVNG;
|
| 76 |
|
| 77 | //-------------------------------------------------------------------------
|
| 78 | ParticleEffectUI::ParticleEffectUI(const char* panelName, SFXComData::PAGE_CODE pageCode, unsigned int emitterFuncCode,
|
| 79 | unsigned int parameterFuncCode)
|
| 80 | :
|
| 81 | pageCode_ (pageCode),
|
| 82 | emitterFuncCode_ (emitterFuncCode),
|
| 83 | parameterFuncCode_ (parameterFuncCode),
|
| 84 | editor_ (NULL),
|
| 85 | messageBox_(NULL),
|
| 86 | currentSelectedNode_(NULL),
|
| 87 | com_ (BaseSFXEditor::sfxEditorInstance_->GetComSlot ())
|
| 88 | {
|
| 89 | WVBoxLayout* layout = new WVBoxLayout();
|
| 90 | layout->setContentsMargins(0, 0, 0, 0);
|
| 91 | setLayout(layout);
|
| 92 |
|
| 93 | // toolbar
|
| 94 | Ext::ToolBar *toolBar = new Ext::ToolBar();
|
| 95 | Ext::Button *button = toolBar->addButton("Save");
|
| 96 | button->clicked().connect(this, &ParticleEffectUI::SaveClicked);
|
| 97 | layout->addWidget(toolBar);
|
| 98 |
|
| 99 | WContainerWidget* container = new WContainerWidget();
|
| 100 | WHBoxLayout *boxLayout = new WHBoxLayout(container);
|
| 101 | boxLayout->setContentsMargins(0, 0, 0, 0);
|
| 102 |
|
| 103 | layout->addWidget(container, 1);
|
| 104 | dynamicView_ = new WStackedWidget();
|
| 105 |
|
| 106 | // panel
|
| 107 | WPanel* panel = new WPanel();
|
| 108 | panel->setTitle(panelName);
|
| 109 | modelTree_ = new WTree();
|
| 110 | modelTree_->setSelectionMode(SingleSelection);
|
| 111 |
|
| 112 | WTreeNode* rootnode = new WTreeNode("");
|
| 113 | modelsRoot_ = rootnode;
|
| 114 | rootnode->setNodeVisible(false);
|
| 115 | rootnode->setSelectable(false);
|
| 116 | modelTree_->setTreeRoot(rootnode);
|
| 117 |
|
| 118 | boxLayout->addWidget(panel);
|
| 119 | boxLayout->setResizable(0, true);
|
| 120 | boxLayout->addWidget(dynamicView_, 1);
|
| 121 |
|
| 122 | WScrollArea* scrollArea = new WScrollArea();
|
| 123 | scrollArea->setScrollBarPolicy(WScrollArea::ScrollBarAsNeeded);
|
| 124 | scrollArea->setWidget(modelTree_);
|
| 125 | panel->setCentralWidget(scrollArea);
|
| 126 |
|
| 127 | boxLayout->setResizable(0, true);
|
| 128 |
|
| 129 | pemEdit_ = new ParticleEmitterModelEdit();
|
| 130 | pemEdit_->MinMaxParamChanged().connect(this, &ParticleEffectUI::MinMaxParamChanged);
|
| 131 | pemEdit_->FloatParamChanged().connect(this, &ParticleEffectUI::FloatParamChanged);
|
| 132 | pemEdit_->Vector3ParamChanged().connect(this, &ParticleEffectUI::Vector3ParamChanged);
|
| 133 | pemEdit_->IntParamChanged().connect(this, &ParticleEffectUI::IntParamChanged);
|
| 134 |
|
| 135 | dynamicView_->addWidget(new WContainerWidget());
|
| 136 | dynamicView_->addWidget(pemEdit_);
|
| 137 |
|
| 138 | mouseUpMapper_ = new WSignalMapper< WTreeNode*, WMouseEvent >(this);
|
| 139 | mouseUpMapper_->mapped().connect(this, &ParticleEffectUI::MouseUpBasicEmitter);
|
| 140 |
|
| 141 | popupActionMapper_ = new Wt::WSignalMapper< int >(this);
|
| 142 | popupActionMapper_->mapped().connect(this, &ParticleEffectUI::PopupAction);
|
| 143 |
|
| 144 | popup_ = new WPopupMenu();
|
| 145 | WPopupMenuItem* item = popup_->addItem("Copy");
|
| 146 | popupActionMapper_->mapConnect(item->triggered(), POPUP_COPY);
|
| 147 | item = popup_->addItem("Paste");
|
| 148 | popupActionMapper_->mapConnect(item->triggered(), POPUP_PASTE);
|
| 149 | item = popup_->addItem("Clone");
|
| 150 | popupActionMapper_->mapConnect(item->triggered(), POPUP_CLONE);
|
| 151 | popup_->addSeparator();
|
| 152 | item = popup_->addItem("Rename");
|
| 153 | popupActionMapper_->mapConnect(item->triggered(), POPUP_RENAME);
|
| 154 | item = popup_->addItem("Delete");
|
| 155 | item->setChecked(true);
|
| 156 | popupActionMapper_->mapConnect(item->triggered(), POPUP_DELETE);
|
| 157 |
|
| 158 | renameDialog_ = new WDialog();
|
| 159 | renameDialog_->setWindowTitle("Change Name");
|
| 160 | WContainerWidget* content = renameDialog_->contents();
|
| 161 | WVBoxLayout* dialogLayout = new WVBoxLayout(content);
|
| 162 | {
|
| 163 | WHBoxLayout* layout = new WHBoxLayout();
|
| 164 | dialogLayout->addLayout(layout);
|
| 165 |
|
| 166 | WText* text = new WText("Name :");
|
| 167 | text->setWordWrap(false);
|
| 168 | layout->addWidget(text, 0, AlignMiddle);
|
| 169 | renameEdit_ = new WLineEdit();
|
| 170 | renameEdit_->setTextSize(30);
|
| 171 | layout->addWidget(renameEdit_, 1, AlignMiddle);
|
| 172 | }
|
| 173 | {
|
| 174 | WHBoxLayout* layout = new WHBoxLayout();
|
| 175 | dialogLayout->addLayout(layout);
|
| 176 |
|
| 177 | WPushButton* ok = new WPushButton("OK");
|
| 178 | WPushButton* cancel = new WPushButton("Cancel");
|
| 179 | ok->clicked().connect(this, &ParticleEffectUI::RenameOK);
|
| 180 | cancel->clicked().connect(this, &ParticleEffectUI::RenameCancel);
|
| 181 | layout->addStretch(1);
|
| 182 | layout->addWidget(ok, 0, AlignMiddle);
|
| 183 | layout->addWidget(cancel, 0, AlignMiddle);
|
| 184 | }
|
| 185 | }
|
| 186 |
|
| 187 | //-------------------------------------------------------------------------
|
| 188 | ParticleEffectUI::~ParticleEffectUI()
|
| 189 | {
|
| 190 | delete popup_;
|
| 191 | delete renameDialog_;
|
| 192 |
|
| 193 | if (editor_ != NULL)
|
| 194 | delete editor_;
|
| 195 | }
|
| 196 |
|
| 197 | //-------------------------------------------------------------------------
|
| 198 | void ParticleEffectUI::NodeSelectionChanged(bool selected)
|
| 199 | {
|
| 200 | if (!selected)
|
| 201 | currentSelectedNode_ = NULL;
|
| 202 | else
|
| 203 | {
|
| 204 | const WTree::WTreeNodeSet& selectedNodes = modelTree_->selectedNodes();
|
| 205 | currentSelectedNode_ = *selectedNodes.begin();
|
| 206 | WTreeNode* modelNode = GetModelNode(currentSelectedNode_);
|
| 207 | UpdateSelectModel (emitterFuncCode_, modelNode->label()->text().toUTF8().c_str(), currentSelectedNode_->label()->text().toUTF8().c_str());
|
| 208 | dynamicView_->setCurrentWidget(pemEdit_);
|
| 209 | pemEdit_->UpdateUI(editor_->GetCurrentParticleEmitterModel ());
|
| 210 | }
|
| 211 | }
|
| 212 |
|
| 213 | //-------------------------------------------------------------------------
|
| 214 | void ParticleEffectUI::RenameOK()
|
| 215 | {
|
| 216 | WTreeNode* node = currentSelectedNode_;
|
| 217 |
|
| 218 | renameDialog_->setHidden(true);
|
| 219 |
|
| 220 | std::string name( renameEdit_->text().toUTF8() );
|
| 221 | if (name.empty()) return;
|
| 222 | if (node->label()->text().toUTF8() == name) return;
|
| 223 |
|
| 224 | if (RenameEmitterModel(name.c_str()) == false) return;
|
| 225 |
|
| 226 | node->label()->setText(name.c_str());
|
| 227 |
|
| 228 | // keep nodes sorted by name
|
| 229 | WTreeNode* parent = node->parentNode();
|
| 230 | parent->removeChildNode(node);
|
| 231 | const std::vector< WTreeNode * > &childNodes = parent->childNodes();
|
| 232 | for (int index(0); index<childNodes.size(); index++)
|
| 233 | {
|
| 234 | WTreeNode* child = childNodes.at(index);
|
| 235 | if (child->label()->text().toUTF8() > name)
|
| 236 | break;
|
| 237 | }
|
| 238 | parent->insertChildNode(index, node);
|
| 239 | modelTree_->select(node);
|
| 240 | }
|
| 241 |
|
| 242 | //-------------------------------------------------------------------------
|
| 243 | void ParticleEffectUI::RenameCancel()
|
| 244 | {
|
| 245 | renameDialog_->setHidden(true);
|
| 246 | }
|
| 247 |
|
| 248 | //-------------------------------------------------------------------------
|
| 249 | void ParticleEffectUI::MouseUpBasicEmitter(WTreeNode* node, const WMouseEvent& evt)
|
| 250 | {
|
| 251 | if (evt.button() == WMouseEvent::RightButton)
|
| 252 | {
|
| 253 | if (currentSelectedNode_ != node)
|
| 254 | {
|
| 255 | modelTree_->select(node);
|
| 256 | }
|
| 257 | if (popup_->isHidden())
|
| 258 | popup_->popup(evt);
|
| 259 | else
|
| 260 | popup_->hide();
|
| 261 | }
|
| 262 | else
|
| 263 | popup_->hide();
|
| 264 | }
|
| 265 |
|
| 266 | //-------------------------------------------------------------------------
|
| 267 | WTreeNode* ParticleEffectUI::GetModelNode(WTreeNode* emitterModelNode) const
|
| 268 | {
|
| 269 | WTreeNode* node = emitterModelNode;
|
| 270 | do
|
| 271 | {
|
| 272 | node = node->parentNode();
|
| 273 | }
|
| 274 | while (node->parentNode() != modelsRoot_);
|
| 275 |
|
| 276 | return node;
|
| 277 | }
|
| 278 |
|
| 279 | //-------------------------------------------------------------------------
|
| 280 | void ParticleEffectUI::UpdateSelectModel(unsigned int funcCode, const char* modelName, const char* emitterModelName)
|
| 281 | {
|
| 282 | ModelNamesData data (pageCode_, funcCode, modelName, emitterModelName);
|
| 283 | com_->Write (data);
|
| 284 |
|
| 285 | if (editor_ != NULL)
|
| 286 | {
|
| 287 | editor_->SetCurrentModel (modelName, emitterModelName);
|
| 288 | }
|
| 289 | }
|
| 290 |
|
| 291 | //-------------------------------------------------------------------------
|
| 292 | void ParticleEffectUI::MinMaxParamChanged(unsigned int paramId, float minValue, float maxValue, const TVNG::FloatAnimationCurve* curve)
|
| 293 | {
|
| 294 | ParameterData data (pageCode_, parameterFuncCode_, paramId, minValue, maxValue, curve);
|
| 295 | com_->Write (data);
|
| 296 | editor_->SetParam(paramId, minValue, maxValue, curve);
|
| 297 | }
|
| 298 |
|
| 299 | //-------------------------------------------------------------------------
|
| 300 | void ParticleEffectUI::Vector3ParamChanged(unsigned int paramId, const Vector3f& value)
|
| 301 | {
|
| 302 | Vector3fParameterData data (pageCode_, parameterFuncCode_, paramId, value);
|
| 303 | com_->Write (data);
|
| 304 | editor_->SetParam(paramId, value);
|
| 305 | }
|
| 306 |
|
| 307 | //-------------------------------------------------------------------------
|
| 308 | void ParticleEffectUI::FloatParamChanged(unsigned int paramId, float value)
|
| 309 | {
|
| 310 | FloatParameterData data (pageCode_, parameterFuncCode_, paramId, value);
|
| 311 | com_->Write (data);
|
| 312 | editor_->SetParam(paramId, value);
|
| 313 | }
|
| 314 |
|
| 315 | //-------------------------------------------------------------------------
|
| 316 | void ParticleEffectUI::IntParamChanged(unsigned int paramId, int value)
|
| 317 | {
|
| 318 | IntParameterData data (pageCode_, parameterFuncCode_, paramId, value);
|
| 319 | com_->Write (data);
|
| 320 | editor_->SetParam(paramId, value);
|
| 321 | }
|
| 322 |
|
| 323 | //-------------------------------------------------------------------------
|
| 324 | void ParticleEffectUI::SaveClicked()
|
| 325 | {
|
| 326 | messageBox_ = new WMessageBox("Save", "Do you want to overwrite the configuration file ?", Wt::NoIcon, Wt::Ok | Wt::Cancel);
|
| 327 | messageBox_->textWidget()->setWordWrap(false);
|
| 328 | messageBox_->buttonClicked().connect(this, &ParticleEffectUI::SaveConfirmationDone);
|
| 329 | messageBox_->show();
|
| 330 | }
|
| 331 |
|
| 332 | //-------------------------------------------------------------------------
|
| 333 | void ParticleEffectUI::SaveConfirmationDone(StandardButton result)
|
| 334 | {
|
| 335 | if (result == Ok)
|
| 336 | editor_->Save();
|
| 337 |
|
| 338 | delete messageBox_;
|
| 339 | messageBox_ = 0;
|
| 340 | }
|
| 341 |
|
| 342 | //-------------------------------------------------------------------------
|
| 343 | WTreeNode* ParticleEffectUI::AddTreeNode (const char* nodeName)
|
| 344 | {
|
| 345 | WIconPair *modelIcon = new WIconPair("resources/icons/b-2-icon.png", "resources/icons/b-2-icon.png", false);
|
| 346 | WTreeNode* modelNode = new WTreeNode(nodeName, modelIcon);
|
| 347 | modelNode->setSelectable(false);
|
| 348 | modelTree_->treeRoot()->addChildNode(modelNode);
|
| 349 |
|
| 350 | return modelNode;
|
| 351 | }
|
| 352 |
|
| 353 | //-------------------------------------------------------------------------
|
| 354 | WTreeNode* ParticleEffectUI::AddTreeNode (WTreeNode* father, const char* nodeName, bool isSelectable /*=false*/)
|
| 355 | {
|
| 356 | WTreeNode* node = new WTreeNode(nodeName);
|
| 357 | node->setSelectable(isSelectable);
|
| 358 | father->addChildNode(node);
|
| 359 | return node;
|
| 360 | }
|
| 361 |
|
| 362 | //-------------------------------------------------------------------------
|
| 363 | WTreeNode* ParticleEffectUI::CreateTreeNode(WTreeNode* father, const ParticleEmitterModelPtr& emitterModel )
|
| 364 | {
|
| 365 | WTreeNode* node = new WTreeNode( emitterModel->GetName() );
|
| 366 |
|
| 367 | node->selected().connect(this, &ParticleEffectUI::NodeSelectionChanged);
|
| 368 |
|
| 369 | mouseUpMapper_->mapConnect1(node->label()->mouseWentUp(), node);
|
| 370 |
|
| 371 | node->label()->setAttributeValue("oncontextmenu", "event.cancelBubble = true; event.returnValue = false; return false;");
|
| 372 | unsigned int count = emitterModel->GetChildrenCount();
|
| 373 | for (unsigned int i(0); i<count; i++)
|
| 374 | {
|
| 375 | CreateTreeNode( node, emitterModel->GetChild(i) );
|
| 376 | }
|
| 377 | father->addChildNode( node );
|
| 378 | return node;
|
| 379 | }
|
| 380 |
|
| 381 | //-------------------------------------------------------------------------
|
| 382 | void ParticleEffectUI::ExpandTree ()
|
| 383 | {
|
| 384 | modelTree_->treeRoot()->expand();
|
| 385 | }
|
| 386 |
|
| 387 | //-------------------------------------------------------------------------
|
| 388 | void ParticleEffectUI::PopupAction(int item)
|
| 389 | {
|
| 390 | popup_->hide();
|
| 391 | if (popup_->result() == NULL) return;
|
| 392 |
|
| 393 | switch (item)
|
| 394 | {
|
| 395 | case POPUP_RENAME:
|
| 396 | {
|
| 397 | renameEdit_->setText(currentSelectedNode_->label()->text().toUTF8());
|
| 398 | renameDialog_->show();
|
| 399 | }
|
| 400 | break;
|
| 401 | case POPUP_DELETE:
|
| 402 | {
|
| 403 | std::string message = "Delete " + currentSelectedNode_->label()->text().toUTF8() + " ?";
|
| 404 | messageBox_ = new WMessageBox("Delete Confirmation", message.c_str(), Wt::NoIcon, Wt::Ok | Wt::Cancel);
|
| 405 | messageBox_->textWidget()->setWordWrap(false);
|
| 406 | messageBox_->buttonClicked().connect(this, &ParticleEffectUI::DeleteConfirmationDone);
|
| 407 | messageBox_->show();
|
| 408 | }
|
| 409 | break;
|
| 410 | }
|
| 411 | }
|
| 412 |
|
| 413 | //-------------------------------------------------------------------------
|
| 414 | void ParticleEffectUI::DeleteConfirmationDone(StandardButton result)
|
| 415 | {
|
| 416 | if (result == Ok)
|
| 417 | {
|
| 418 | bool deleted = DeleteEmitterModel(currentSelectedNode_->label()->text().toUTF8().c_str());
|
| 419 | if (deleted)
|
| 420 | {
|
| 421 | WTreeNode* parentNode = currentSelectedNode_->parentNode();
|
| 422 | parentNode->removeChildNode(currentSelectedNode_);
|
| 423 | dynamicView_->setCurrentIndex(0);
|
| 424 | }
|
| 425 | }
|
| 426 |
|
| 427 | delete messageBox_;
|
| 428 | messageBox_ = 0;
|
| 429 | }
|
| 430 |
|
| 431 |
|