BsGUIFloatDistributionField.cpp 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572
  1. //********************************** Banshee Engine (www.banshee3d.com) **************************************************//
  2. //**************** Copyright (c) 2018 Marko Pintera ([email protected]). All rights reserved. **********************//
  3. #include "GUI/BsGUIFloatDistributionField.h"
  4. #include "GUI/BsGUILayout.h"
  5. #include "GUI/BsGUILayoutY.h"
  6. #include "GUI/BsGUILabel.h"
  7. #include "GUI/BsGUIColor.h"
  8. #include "GUI/BsGUIFloatField.h"
  9. #include "GUI/BsGUIVector2Field.h"
  10. #include "GUI/BsGUIVector3Field.h"
  11. #include "GUI/BsGUICurvesField.h"
  12. #include "GUI/BsGUIButton.h"
  13. #include "GUI/BsGUIContextMenu.h"
  14. #include "GUI/BsGUISpace.h"
  15. using namespace std::placeholders;
  16. namespace bs
  17. {
  18. /** Style type name for the internal float fields. */
  19. static constexpr const char* FLOAT_FIELD_STYLE_TYPE = "FloatField";
  20. /** Style type name for the internal curve display fields. */
  21. static constexpr const char* CURVES_FIELD_STYLE_TYPES[4] =
  22. { "CurvesFieldX", "CurvesFieldY", "CurvesFieldZ", "CurvesFieldW" };
  23. /** Style type name for the internal drop down button. */
  24. static constexpr const char* DROP_DOWN_FIELD_STYLE_TYPE = "DropDownButton";
  25. /** Spacing between two internal elements, in pixels. */
  26. static constexpr UINT32 ELEM_SPACING = 5;
  27. namespace impl
  28. {
  29. template<class T, class F>
  30. void addOnConfirmCallback(T* field, F func)
  31. {
  32. field->onConfirm.connect([func]() { func(VectorComponent::X); });
  33. }
  34. template<class F>
  35. void addOnConfirmCallback(GUIVector2Field* field, F func)
  36. {
  37. field->onConfirm.connect([func](VectorComponent x) { func(x); });
  38. }
  39. template<class F>
  40. void addOnConfirmCallback(GUIVector3Field* field, F func)
  41. {
  42. field->onConfirm.connect([func](VectorComponent x) { func(x); });
  43. }
  44. template<class T, class F>
  45. void addOnValueChangedCallback(T* field, F func)
  46. {
  47. field->onValueChanged.connect([func](float val) { func(val, VectorComponent::X); });
  48. }
  49. template<class F>
  50. void addOnValueChangedCallback(GUIVector2Field* field, F func)
  51. {
  52. field->onComponentChanged.connect([func](float val, VectorComponent x) { func(val, x); });
  53. }
  54. template<class F>
  55. void addOnValueChangedCallback(GUIVector3Field* field, F func)
  56. {
  57. field->onComponentChanged.connect([func](float val, VectorComponent x) { func(val, x); });
  58. }
  59. template<class T, class F>
  60. void addOnInputChangedCallback(T* field, F func)
  61. {
  62. field->onFocusChanged.connect([func](bool val) { func(val, VectorComponent::X); });
  63. }
  64. template<class F>
  65. void addOnInputChangedCallback(GUIVector2Field* field, F func)
  66. {
  67. field->onComponentFocusChanged.connect([func](bool val, VectorComponent x) { func(val, x); });
  68. }
  69. template<class F>
  70. void addOnInputChangedCallback(GUIVector3Field* field, F func)
  71. {
  72. field->onComponentFocusChanged.connect([func](bool val, VectorComponent x) { func(val, x); });
  73. }
  74. template<class T>
  75. void setFocus(T* field, VectorComponent component, bool focus)
  76. {
  77. field->setFocus(focus);
  78. }
  79. template<>
  80. void setFocus(GUIVector2Field* field, VectorComponent component, bool focus)
  81. {
  82. field->setInputFocus(component, focus);
  83. }
  84. template<>
  85. void setFocus(GUIVector3Field* field, VectorComponent component, bool focus)
  86. {
  87. field->setInputFocus(component, focus);
  88. }
  89. }
  90. template<class T, class SELF>
  91. TGUIDistributionField<T, SELF>::TGUIDistributionField(const PrivatelyConstruct& dummy, const GUIContent& labelContent,
  92. UINT32 labelWidth, const String& style, const GUIDimensions& dimensions, bool withLabel)
  93. : TGUIField<SELF>(dummy, labelContent, labelWidth, style, dimensions, withLabel)
  94. {
  95. mContextMenu = bs_shared_ptr_new<GUIContextMenu>();
  96. mContextMenu->addMenuItem("Constant", [this]()
  97. {
  98. mValue = TDistribution<T>(mMinConstant);
  99. mPropertyType = PDT_Constant;
  100. rebuild();
  101. }, 50);
  102. mContextMenu->addMenuItem("Range", [this]()
  103. {
  104. mValue = TDistribution<T>(mMinConstant, mMaxConstant);
  105. mPropertyType = PDT_RandomRange;
  106. rebuild();
  107. }, 40);
  108. mContextMenu->addMenuItem("Curve", [this]()
  109. {
  110. TAnimationCurve<T> combinedCurve;
  111. AnimationUtility::combineCurve<T>(mMinCurve, combinedCurve);
  112. mValue = TDistribution<T>(combinedCurve);
  113. mPropertyType = PDT_Curve;
  114. rebuild();
  115. }, 30);
  116. mContextMenu->addMenuItem("Curve range", [this]()
  117. {
  118. TAnimationCurve<T> combinedCurveMin;
  119. AnimationUtility::combineCurve<T>(mMinCurve, combinedCurveMin);
  120. TAnimationCurve<T> combinedCurveMax;
  121. AnimationUtility::combineCurve<T>(mMaxCurve, combinedCurveMax);
  122. mValue = TDistribution<T>(combinedCurveMin, combinedCurveMax);
  123. mPropertyType = PDT_RandomCurveRange;
  124. rebuild();
  125. }, 20);
  126. rebuild();
  127. }
  128. template<class T, class SELF>
  129. void TGUIDistributionField<T, SELF>::setValue(const TDistribution<T>& value)
  130. {
  131. if(mValue == value)
  132. return;
  133. mValue = value;
  134. switch (mPropertyType)
  135. {
  136. default:
  137. case PDT_Constant:
  138. mMinConstant = mValue.getMinConstant();
  139. mMaxConstant = mMinConstant;
  140. for(UINT32 i = 0; i < NumComponents; i++)
  141. {
  142. mMinCurve[i] = TAnimationCurve<float>({
  143. { TCurveProperties<T>::getComponent(mMinConstant, i), 0.0f, 0.0f, 0.0f},
  144. { TCurveProperties<T>::getComponent(mMinConstant, i), 0.0f, 0.0f, 1.0f} });
  145. mMaxCurve[i] = TAnimationCurve<float>({
  146. { TCurveProperties<T>::getComponent(mMinConstant, i), 0.0f, 0.0f, 0.0f},
  147. { TCurveProperties<T>::getComponent(mMinConstant, i), 0.0f, 0.0f, 1.0f} });
  148. }
  149. mMinInput->setValue(mMinConstant);
  150. break;
  151. case PDT_RandomRange:
  152. mMinConstant = mValue.getMinConstant();
  153. mMaxConstant = mValue.getMaxConstant();
  154. for(UINT32 i = 0; i < NumComponents; i++)
  155. {
  156. mMinCurve[i] = TAnimationCurve<float>({
  157. { TCurveProperties<T>::getComponent(mMinConstant, i), 0.0f, 0.0f, 0.0f},
  158. { TCurveProperties<T>::getComponent(mMinConstant, i), 0.0f, 0.0f, 1.0f} });
  159. mMaxCurve[i] = TAnimationCurve<float>({
  160. { TCurveProperties<T>::getComponent(mMaxConstant, i), 0.0f, 0.0f, 0.0f},
  161. { TCurveProperties<T>::getComponent(mMaxConstant, i), 0.0f, 0.0f, 1.0f} });
  162. }
  163. mMinInput->setValue(mMinConstant);
  164. mMaxInput->setValue(mMaxConstant);
  165. break;
  166. case PDT_Curve:
  167. AnimationUtility::splitCurve(mValue.getMinCurve(), mMinCurve);
  168. AnimationUtility::splitCurve(mValue.getMinCurve(), mMaxCurve);
  169. for(UINT32 i = 0; i < NumComponents; i++)
  170. {
  171. TCurveProperties<T>::setComponent(mMinConstant, i, mMinCurve[i].evaluate(0.0f));
  172. TCurveProperties<T>::setComponent(mMaxConstant, i, mMaxCurve[i].evaluate(0.0f));
  173. mCurveDisplay[i]->setCurve(mMinCurve[i]);
  174. }
  175. break;
  176. case PDT_RandomCurveRange:
  177. AnimationUtility::splitCurve(mValue.getMinCurve(), mMinCurve);
  178. AnimationUtility::splitCurve(mValue.getMaxCurve(), mMaxCurve);
  179. for(UINT32 i = 0; i < NumComponents; i++)
  180. {
  181. TCurveProperties<T>::setComponent(mMinConstant, i, mMinCurve[i].evaluate(0.0f));
  182. TCurveProperties<T>::setComponent(mMaxConstant, i, mMaxCurve[i].evaluate(0.0f));
  183. mCurveDisplay[i]->setCurveRange(mMinCurve[i], mMaxCurve[i]);
  184. }
  185. break;
  186. }
  187. }
  188. template<class T, class SELF>
  189. bool TGUIDistributionField<T, SELF>::hasInputFocus() const
  190. {
  191. if(mMinInput && mMinInput->hasInputFocus())
  192. return true;
  193. if(mMaxInput && mMaxInput->hasInputFocus())
  194. return true;
  195. return false;
  196. }
  197. template <class T, class SELF>
  198. void TGUIDistributionField<T, SELF>::setInputFocus(RangeComponent rangeComponent, VectorComponent vectorComponent, bool focus)
  199. {
  200. switch(mPropertyType)
  201. {
  202. case PDT_Constant:
  203. case PDT_RandomRange:
  204. if(rangeComponent == RangeComponent::Min)
  205. impl::setFocus(mMinInput, vectorComponent, focus);
  206. else
  207. impl::setFocus(mMaxInput, vectorComponent, focus);
  208. break;
  209. case PDT_Curve:
  210. case PDT_RandomCurveRange:
  211. {
  212. for (UINT32 i = 0; i < NumComponents; i++)
  213. {
  214. if ((VectorComponent)i == vectorComponent && mCurveDisplay[i])
  215. mCurveDisplay[i]->setFocus(focus);
  216. }
  217. }
  218. break;
  219. }
  220. }
  221. template<class T, class SELF>
  222. void TGUIDistributionField<T, SELF>::setTint(const Color& color)
  223. {
  224. mDropDownButton->setTint(color);
  225. if (this->mLabel)
  226. this->mLabel->setTint(color);
  227. if(mMinInput)
  228. mMinInput->setTint(color);
  229. if(mMaxInput)
  230. mMaxInput->setTint(color);
  231. for(auto& entry : mLabels)
  232. {
  233. if(entry)
  234. entry->setTint(color);
  235. }
  236. for(int i = 0; i < NumComponents; i++)
  237. {
  238. if(mCurveDisplay[i])
  239. mCurveDisplay[i]->setTint(color);
  240. }
  241. }
  242. template<class T, class SELF>
  243. Vector2I TGUIDistributionField<T, SELF>::_getOptimalSize() const
  244. {
  245. Vector2I optimalsize = Vector2I::ZERO;
  246. if (mMinInput)
  247. {
  248. Vector2I elemOptimal = mMinInput->_calculateLayoutSizeRange().optimal;
  249. optimalsize.x = std::max(optimalsize.x, elemOptimal.x);
  250. optimalsize.y += elemOptimal.y;
  251. }
  252. if (mMaxInput)
  253. {
  254. Vector2I elemOptimal = mMaxInput->_calculateLayoutSizeRange().optimal;
  255. optimalsize.x = std::max(optimalsize.x, elemOptimal.x);
  256. optimalsize.y += elemOptimal.y;
  257. optimalsize.y += ELEM_SPACING;
  258. }
  259. for (auto& entry : mLabels)
  260. {
  261. if (!entry)
  262. continue;
  263. Vector2I elemOptimal = entry->_calculateLayoutSizeRange().optimal;
  264. optimalsize.x = std::max(optimalsize.x, elemOptimal.x);
  265. optimalsize.y += elemOptimal.y;
  266. }
  267. for (UINT32 i = 0; i < NumComponents; i++)
  268. {
  269. if (mCurveDisplay[i])
  270. {
  271. Vector2I elemOptimal = mCurveDisplay[i]->_calculateLayoutSizeRange().optimal;
  272. optimalsize.x = std::max(optimalsize.x, elemOptimal.x);
  273. optimalsize.y += elemOptimal.y;
  274. if (i != 0)
  275. optimalsize.y += ELEM_SPACING;
  276. }
  277. }
  278. Vector2I dropDownSize = mDropDownButton->_calculateLayoutSizeRange().optimal;
  279. optimalsize.x += dropDownSize.x;
  280. optimalsize.y = std::max(optimalsize.y, dropDownSize.y);
  281. if (this->mLabel)
  282. {
  283. Vector2I elemOptimal = this->mLabel->_calculateLayoutSizeRange().optimal;
  284. optimalsize.x += elemOptimal.x;
  285. optimalsize.y = std::max(optimalsize.y, elemOptimal.y);
  286. }
  287. return optimalsize;
  288. }
  289. template<class T, class SELF>
  290. void TGUIDistributionField<T, SELF>::styleUpdated()
  291. {
  292. mDropDownButton->setStyle(this->getSubStyleName(DROP_DOWN_FIELD_STYLE_TYPE));
  293. if (this->mLabel)
  294. this->mLabel->setStyle(this->getSubStyleName(this->getLabelStyleType()));
  295. if (mMinInput)
  296. mMinInput->setStyle(this->getSubStyleName(FLOAT_FIELD_STYLE_TYPE));
  297. if (mMaxInput)
  298. mMaxInput->setStyle(this->getSubStyleName(FLOAT_FIELD_STYLE_TYPE));
  299. for (int i = 0; i < NumComponents; i++)
  300. {
  301. if (mCurveDisplay[i])
  302. mCurveDisplay[i]->setStyle(this->getSubStyleName(CURVES_FIELD_STYLE_TYPES[i]));
  303. }
  304. }
  305. template<class T, class SELF>
  306. void TGUIDistributionField<T, SELF>::rebuild()
  307. {
  308. constexpr const char* COMP_NAMES[] = { "X", "Y", "Z", "W" };
  309. constexpr UINT32 ELEMENT_LABEL_WIDTH = 15;
  310. if(this->mLabel)
  311. this->mLayout->removeElement(this->mLabel);
  312. this->mLayout->clear();
  313. this->mLayout->addElement(this->mLabel);
  314. GUILayout* valueLayout = this->mLayout->template addNewElement<GUILayoutY>();
  315. switch (mValue.getType())
  316. {
  317. default:
  318. case PDT_Constant:
  319. mMinInput = GUIConstantType::create(GUIOptions(), this->getSubStyleName(FLOAT_FIELD_STYLE_TYPE));
  320. mMaxInput = nullptr;
  321. mLabels = { nullptr, nullptr };
  322. for(int i = 0; i < NumComponents; i++)
  323. mCurveDisplay[i] = nullptr;
  324. mMinInput->setValue(mMinConstant);
  325. impl::addOnValueChangedCallback(mMinInput, [this](float value, VectorComponent component)
  326. {
  327. mMinConstant = mMinInput->getValue();
  328. mValue = TDistribution<T>(mMinConstant);
  329. onConstantModified(RangeComponent::Min, component);
  330. });
  331. impl::addOnConfirmCallback(mMinInput, [this](VectorComponent component)
  332. {
  333. onConstantConfirmed(RangeComponent::Min, component);
  334. });
  335. impl::addOnInputChangedCallback(mMinInput, [this](bool focus, VectorComponent component)
  336. {
  337. onConstantFocusChanged(focus, RangeComponent::Min, component);
  338. });
  339. valueLayout->addElement(mMinInput);
  340. break;
  341. case PDT_RandomRange:
  342. mMinInput = GUIConstantType::create(GUIOptions(), this->getSubStyleName(FLOAT_FIELD_STYLE_TYPE));
  343. mMaxInput = GUIConstantType::create(GUIOptions(), this->getSubStyleName(FLOAT_FIELD_STYLE_TYPE));
  344. for(int i = 0; i < NumComponents; i++)
  345. mCurveDisplay[i] = nullptr;
  346. mMinInput->setValue(mMinConstant);
  347. impl::addOnValueChangedCallback(mMinInput, [this](float value, VectorComponent component)
  348. {
  349. mMinConstant = mMinInput->getValue();
  350. mValue = TDistribution<T>(mMinConstant, mMaxConstant);
  351. onConstantModified(RangeComponent::Min, component);
  352. });
  353. impl::addOnConfirmCallback(mMinInput, [this](VectorComponent component)
  354. {
  355. onConstantConfirmed(RangeComponent::Min, component);
  356. });
  357. impl::addOnInputChangedCallback(mMinInput, [this](bool focus, VectorComponent component)
  358. {
  359. onConstantFocusChanged(focus, RangeComponent::Min, component);
  360. });
  361. mMaxInput->setValue(mMaxConstant);
  362. impl::addOnValueChangedCallback(mMaxInput, [this](float value, VectorComponent component)
  363. {
  364. mMaxConstant = mMaxInput->getValue();
  365. mValue = TDistribution<T>(mMinConstant, mMaxConstant);
  366. onConstantModified(RangeComponent::Max, component);
  367. });
  368. impl::addOnConfirmCallback(mMaxInput, [this](VectorComponent component)
  369. {
  370. onConstantConfirmed(RangeComponent::Max, component);
  371. });
  372. impl::addOnInputChangedCallback(mMaxInput, [this](bool focus, VectorComponent component)
  373. {
  374. onConstantFocusChanged(focus, RangeComponent::Max, component);
  375. });
  376. mLabels[0] = valueLayout->addNewElement<GUILabel>(HString("Min."), "HeaderLight");
  377. valueLayout->addElement(mMinInput);
  378. valueLayout->addNewElement<GUIFixedSpace>(ELEM_SPACING);
  379. mLabels[1] = valueLayout->addNewElement<GUILabel>(HString("Max."), "HeaderLight");
  380. valueLayout->addElement(mMaxInput);
  381. break;
  382. case PDT_Curve:
  383. mMinInput = nullptr;
  384. mMaxInput = nullptr;
  385. mLabels = { nullptr, nullptr };
  386. for(int i = 0; i < NumComponents; i++)
  387. {
  388. if(NumComponents > 1)
  389. {
  390. mCurveDisplay[i] = GUICurvesField::create(CurveDrawOption::DrawMarkers, HString(COMP_NAMES[i]),
  391. ELEMENT_LABEL_WIDTH, this->getSubStyleName(CURVES_FIELD_STYLE_TYPES[i]));
  392. }
  393. else
  394. {
  395. mCurveDisplay[i] = GUICurvesField::create(CurveDrawOption::DrawMarkers,
  396. this->getSubStyleName(CURVES_FIELD_STYLE_TYPES[i]));
  397. }
  398. mCurveDisplay[i]->setCurve(mMinCurve[i]);
  399. mCurveDisplay[i]->setPadding(3);
  400. mCurveDisplay[i]->centerAndZoom();
  401. mCurveDisplay[i]->onClicked.connect([this,i]() { onClicked((VectorComponent)i); });
  402. if(i != 0)
  403. valueLayout->addNewElement<GUIFixedSpace>(ELEM_SPACING);
  404. valueLayout->addElement(mCurveDisplay[i]);
  405. }
  406. break;
  407. case PDT_RandomCurveRange:
  408. mMinInput = nullptr;
  409. mMaxInput = nullptr;
  410. mLabels = { nullptr, nullptr };
  411. for(int i = 0; i < NumComponents; i++)
  412. {
  413. if(NumComponents > 1)
  414. {
  415. mCurveDisplay[i] = GUICurvesField::create(CurveDrawOption::DrawMarkers, HString(COMP_NAMES[i]),
  416. ELEMENT_LABEL_WIDTH, this->getSubStyleName(CURVES_FIELD_STYLE_TYPES[i]));
  417. }
  418. else
  419. {
  420. mCurveDisplay[i] = GUICurvesField::create(CurveDrawOption::DrawMarkers,
  421. this->getSubStyleName(CURVES_FIELD_STYLE_TYPES[i]));
  422. }
  423. mCurveDisplay[i]->setCurveRange(mMinCurve[i], mMaxCurve[i]);
  424. mCurveDisplay[i]->setPadding(3);
  425. mCurveDisplay[i]->centerAndZoom();
  426. mCurveDisplay[i]->onClicked.connect([this,i]() { onClicked((VectorComponent)i); });
  427. if(i != 0)
  428. valueLayout->addNewElement<GUIFixedSpace>(ELEM_SPACING);
  429. valueLayout->addElement(mCurveDisplay[i]);
  430. }
  431. break;
  432. }
  433. mDropDownButton = GUIButton::create(HString::dummy(), this->getSubStyleName(DROP_DOWN_FIELD_STYLE_TYPE));
  434. mDropDownButton->onClick.connect([this]()
  435. {
  436. const Rect2I bounds = mDropDownButton->getBounds(this->mParentWidget->getPanel());
  437. const Vector2I center(bounds.x + bounds.width / 2, bounds.y + bounds.height / 2);
  438. mContextMenu->open(center, *(this->mParentWidget));
  439. });
  440. this->mLayout->template addNewElement<GUIFixedSpace>(10);
  441. this->mLayout->addElement(mDropDownButton);
  442. }
  443. template class BS_ED_EXPORT TGUIDistributionField<float, GUIFloatDistributionField>;
  444. template class BS_ED_EXPORT TGUIDistributionField<Vector2, GUIVector2DistributionField>;
  445. template class BS_ED_EXPORT TGUIDistributionField<Vector3, GUIVector3DistributionField>;
  446. const String& GUIFloatDistributionField::getGUITypeName()
  447. {
  448. static String typeName = "GUIFloatDistributionField";
  449. return typeName;
  450. }
  451. const String& GUIVector2DistributionField::getGUITypeName()
  452. {
  453. static String typeName = "GUIVector2DistributionField";
  454. return typeName;
  455. }
  456. const String& GUIVector3DistributionField::getGUITypeName()
  457. {
  458. static String typeName = "GUIVector3DistributionField";
  459. return typeName;
  460. }
  461. }