UI.cpp 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540
  1. //
  2. // Urho3D Engine
  3. // Copyright (c) 2008-2011 Lasse Öörni
  4. //
  5. // Permission is hereby granted, free of charge, to any person obtaining a copy
  6. // of this software and associated documentation files (the "Software"), to deal
  7. // in the Software without restriction, including without limitation the rights
  8. // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  9. // copies of the Software, and to permit persons to whom the Software is
  10. // furnished to do so, subject to the following conditions:
  11. //
  12. // The above copyright notice and this permission notice shall be included in
  13. // all copies or substantial portions of the Software.
  14. //
  15. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  16. // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  17. // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  18. // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  19. // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  20. // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  21. // THE SOFTWARE.
  22. //
  23. #include "Precompiled.h"
  24. #include "BaseUIElementFactory.h"
  25. #include "Cursor.h"
  26. #include "Font.h"
  27. #include "InputEvents.h"
  28. #include "Log.h"
  29. #include "Matrix4.h"
  30. #include "PixelShader.h"
  31. #include "Profiler.h"
  32. #include "Renderer.h"
  33. #include "RendererEvents.h"
  34. #include "RendererImpl.h"
  35. #include "ResourceCache.h"
  36. #include "StringUtils.h"
  37. #include "Texture2D.h"
  38. #include "UI.h"
  39. #include "UIEvents.h"
  40. #include "VertexShader.h"
  41. #include <algorithm>
  42. #include "DebugNew.h"
  43. static bool compareUIElements(const UIElement* lhs, const UIElement* rhs)
  44. {
  45. return lhs->getPriority() < rhs->getPriority();
  46. }
  47. UI::UI(Renderer* renderer, ResourceCache* cache) :
  48. mRenderer(renderer),
  49. mCache(cache),
  50. mMouseDrag(false),
  51. mMouseDragElement(0),
  52. mMouseButtons(0),
  53. mQualifiers(0)
  54. {
  55. if (!mRenderer)
  56. EXCEPTION("Null renderer for UI");
  57. if (!mCache)
  58. EXCEPTION("Null resource cache for UI");
  59. LOGINFO("UI created");
  60. mRootElement = new UIElement();
  61. mRootElement->setSize(mRenderer->getWidth(), mRenderer->getHeight());
  62. subscribeToEvent(EVENT_WINDOWRESIZED, EVENT_HANDLER(UI, handleWindowResized));
  63. subscribeToEvent(EVENT_MOUSEMOVE, EVENT_HANDLER(UI, handleMouseMove));
  64. subscribeToEvent(EVENT_MOUSEBUTTONDOWN, EVENT_HANDLER(UI, handleMouseButtonDown));
  65. subscribeToEvent(EVENT_MOUSEBUTTONUP, EVENT_HANDLER(UI, handleMouseButtonUp));
  66. subscribeToEvent(EVENT_KEYDOWN, EVENT_HANDLER(UI, handleKeyDown));
  67. subscribeToEvent(EVENT_CHAR, EVENT_HANDLER(UI, handleChar));
  68. mNoTextureVS = mCache->getResource<VertexShader>("Shaders/SM2/Basic_VCol.vs2");
  69. mDiffTextureVS = mCache->getResource<VertexShader>("Shaders/SM2/Basic_DiffVCol.vs2");
  70. mNoTexturePS = mCache->getResource<PixelShader>("Shaders/SM2/Basic_VCol.ps2");
  71. mDiffTexturePS = mCache->getResource<PixelShader>("Shaders/SM2/Basic_DiffVCol.ps2");
  72. mAlphaTexturePS = mCache->getResource<PixelShader>("Shaders/SM2/Basic_AlphaVCol.ps2");
  73. // Add the base element factory
  74. addElementFactory(new BaseUIElementFactory());
  75. }
  76. UI::~UI()
  77. {
  78. LOGINFO("UI shut down");
  79. }
  80. void UI::setCursor(Cursor* cursor)
  81. {
  82. // Remove old cursor (if any) and set new
  83. if (mCursor)
  84. {
  85. mRootElement->removeChild(mCursor);
  86. mCursor.reset();
  87. }
  88. if (cursor)
  89. {
  90. mRootElement->addChild(cursor);
  91. mCursor = cursor;
  92. IntVector2 pos = mCursor->getPosition();
  93. const IntVector2& rootSize = mRootElement->getSize();
  94. pos.mX = clamp(pos.mX, 0, rootSize.mX - 1);
  95. pos.mY = clamp(pos.mY, 0, rootSize.mY - 1);
  96. mCursor->setPosition(pos);
  97. }
  98. }
  99. void UI::setFocusElement(UIElement* element)
  100. {
  101. using namespace TryFocus;
  102. VariantMap eventData;
  103. eventData[P_ELEMENT] = (void*)element;
  104. sendEvent(EVENT_TRYFOCUS, eventData);
  105. if (element)
  106. {
  107. if ((element->hasFocus()) || (!element->isFocusable()))
  108. return;
  109. }
  110. std::vector<UIElement*> allChildren = mRootElement->getChildren(true);
  111. // Go through all elements to clear the old focus
  112. for (std::vector<UIElement*>::iterator i = allChildren.begin(); i != allChildren.end(); ++i)
  113. {
  114. UIElement* other = *i;
  115. if ((other != element) && (other->hasFocus()))
  116. other->setFocus(false);
  117. }
  118. if (element)
  119. element->setFocus(true);
  120. }
  121. void UI::clear()
  122. {
  123. mRootElement->removeAllChildren();
  124. if (mCursor)
  125. mRootElement->addChild(mCursor);
  126. }
  127. void UI::update(float timeStep)
  128. {
  129. PROFILE(UI_Update);
  130. // If device lost, do not perform update
  131. if (mRenderer->isDeviceLost())
  132. return;
  133. if ((mCursor) && (mCursor->isVisible()))
  134. {
  135. IntVector2 pos = mCursor->getPosition();
  136. UIElement* element = getElementAt(pos);
  137. if (element)
  138. {
  139. // If a drag is going on, transmit hover only to the element being dragged
  140. if ((!mMouseDragElement) || (mMouseDragElement == element))
  141. element->onHover(element->screenToElement(pos), pos, mMouseButtons, mQualifiers);
  142. }
  143. }
  144. {
  145. PROFILE(UI_UpdateElements);
  146. update(timeStep, mRootElement);
  147. }
  148. {
  149. PROFILE(UI_GetBatches);
  150. mBatches.clear();
  151. mQuads.clear();
  152. const IntVector2& rootSize = mRootElement->getSize();
  153. getBatches(mRootElement, IntRect(0, 0, rootSize.mX, rootSize.mY));
  154. }
  155. }
  156. void UI::render()
  157. {
  158. PROFILE(UI_Render);
  159. static const Vector2 scale(2.0f, -2.0f);
  160. static const Vector2 offset(-1.0f, 1.0f);
  161. Matrix4 projection;
  162. memset(&projection, 0, sizeof(projection));
  163. projection.m00 = scale.mX;
  164. projection.m03 = offset.mX;
  165. projection.m11 = scale.mY;
  166. projection.m13 = offset.mY;
  167. projection.m22 = 1.0f;
  168. projection.m23 = 0.0f;
  169. projection.m33 = 1.0f;
  170. mRenderer->resetRenderTargets();
  171. mRenderer->setCullMode(CULL_CCW);
  172. mRenderer->setDepthTest(CMP_ALWAYS);
  173. mRenderer->setDepthWrite(false);
  174. mRenderer->setFillMode(FILL_SOLID);
  175. mRenderer->setStencilTest(false);
  176. mRenderer->setVertexShaderConstant(getVSRegister(VSP_MODELVIEWPROJ), projection);
  177. mRenderer->setPixelShaderConstant(getPSRegister(PSP_MATDIFFCOLOR), Color(1.0f, 1.0f, 1.0f, 1.0f));
  178. PixelShader* ps = 0;
  179. VertexShader* vs = 0;
  180. for (unsigned i = 0; i < mBatches.size(); ++i)
  181. {
  182. // Choose shaders here so that UIBatch does not need to look up shaders each time
  183. if (!mBatches[i].mTexture)
  184. {
  185. ps = mNoTexturePS;
  186. vs = mNoTextureVS;
  187. }
  188. else
  189. {
  190. // If texture contains only an alpha channel, use the alpha pixel shader
  191. vs = mDiffTextureVS;
  192. if (mBatches[i].mTexture->getFormat() == D3DFMT_A8)
  193. ps = mAlphaTexturePS;
  194. else
  195. ps = mDiffTexturePS;
  196. }
  197. mBatches[i].draw(mRenderer, vs, ps);
  198. }
  199. }
  200. void UI::addElementFactory(UIElementFactory* factory)
  201. {
  202. if (!factory)
  203. return;
  204. mFactories.push_back(SharedPtr<UIElementFactory>(factory));
  205. }
  206. SharedPtr<UIElement> UI::createElement(ShortStringHash type, const std::string& name)
  207. {
  208. SharedPtr<UIElement> element;
  209. for (unsigned i = 0; i < mFactories.size(); ++i)
  210. {
  211. element = mFactories[i]->createElement(type, name);
  212. if (element)
  213. return element;
  214. }
  215. EXCEPTION("Could not create unknown UI element type " + toString(type));
  216. }
  217. SharedPtr<UIElement> UI::loadLayout(XMLFile* file, XMLFile* styleFile)
  218. {
  219. PROFILE(UI_LoadLayout);
  220. SharedPtr<UIElement> root;
  221. if (!file)
  222. {
  223. LOGERROR("Null UI layout XML file");
  224. return root;
  225. }
  226. LOGDEBUG("Loading UI layout " + file->getName());
  227. XMLElement rootElem = file->getRootElement();
  228. XMLElement childElem = rootElem.getChildElement("element");
  229. if (!childElem)
  230. {
  231. LOGERROR("No root UI element in " + file->getName());
  232. return root;
  233. }
  234. root = createElement(ShortStringHash(childElem.getString("type")), childElem.getString("name"));
  235. // First set the base style from the style file if exists, then apply UI layout overrides
  236. if (styleFile)
  237. root->setStyleAuto(styleFile, mCache);
  238. root->setStyle(childElem, mCache);
  239. // Load rest of the elements recursively
  240. loadLayout(root, childElem, styleFile);
  241. if (childElem.getNextElement("element"))
  242. LOGWARNING("Ignored additional root UI elements in " + file->getName());
  243. return root;
  244. }
  245. UIElement* UI::getElementAt(const IntVector2& position, bool enabledOnly)
  246. {
  247. UIElement* result = 0;
  248. getElementAt(result, mRootElement, position, enabledOnly);
  249. return result;
  250. }
  251. UIElement* UI::getElementAt(int x, int y, bool enabledOnly)
  252. {
  253. UIElement* result = 0;
  254. getElementAt(result, mRootElement, IntVector2(x, y), enabledOnly);
  255. return result;
  256. }
  257. UIElement* UI::getFocusElement()
  258. {
  259. std::vector<UIElement*> allChildren = mRootElement->getChildren(true);
  260. for (std::vector<UIElement*>::iterator i = allChildren.begin(); i != allChildren.end(); ++i)
  261. {
  262. if ((*i)->hasFocus())
  263. return *i;
  264. }
  265. return 0;
  266. }
  267. IntVector2 UI::getCursorPosition()
  268. {
  269. if (!mCursor)
  270. return IntVector2::sZero;
  271. else
  272. return mCursor->getPosition();
  273. }
  274. void UI::update(float timeStep, UIElement* element)
  275. {
  276. element->update(timeStep);
  277. const std::vector<UIElement*> children = element->getChildren();
  278. for (std::vector<UIElement*>::const_iterator i = children.begin(); i != children.end(); ++i)
  279. update(timeStep, *i);
  280. }
  281. void UI::getBatches(UIElement* element, IntRect currentScissor)
  282. {
  283. // If hidden, do not draw this element or its children
  284. if (!element->isVisible())
  285. return;
  286. element->getBatches(mBatches, mQuads, currentScissor);
  287. // Set clipping scissor for child elements, then get child element batches in low-to-high priority order
  288. element->adjustScissor(currentScissor);
  289. std::vector<UIElement*> children = element->getChildren();
  290. std::sort(children.begin(), children.end(), compareUIElements);
  291. for (std::vector<UIElement*>::const_iterator i = children.begin(); i != children.end(); ++i)
  292. getBatches(*i, currentScissor);
  293. }
  294. void UI::getElementAt(UIElement*& result, UIElement* current, const IntVector2& position, bool enabledOnly)
  295. {
  296. if (!current)
  297. return;
  298. // Get children from lowest priority to highest
  299. std::vector<UIElement*> children = current->getChildren(false);
  300. std::sort(children.begin(), children.end(), compareUIElements);
  301. for (std::vector<UIElement*>::const_iterator i = children.begin(); i != children.end(); ++i)
  302. {
  303. UIElement* element = *i;
  304. if ((element != mCursor.getPtr()) && (element->isVisible()))
  305. {
  306. if (element->isInside(position, true))
  307. {
  308. // Store the current result, then recurse into its children. Because children
  309. // are sorted from lowest to highest priority, we should be left with the topmost match
  310. if ((element->isEnabled()) || (!enabledOnly))
  311. result = element;
  312. }
  313. if (element->isInsideCombined(position, true))
  314. getElementAt(result, element, position, enabledOnly);
  315. }
  316. }
  317. }
  318. UIElement* UI::verifyElement(UIElement* element)
  319. {
  320. std::vector<UIElement*> allChildren = mRootElement->getChildren(true);
  321. for (std::vector<UIElement*>::iterator i = allChildren.begin(); i != allChildren.end(); ++i)
  322. {
  323. if ((*i) == element)
  324. return *i;
  325. }
  326. return 0;
  327. }
  328. void UI::handleWindowResized(StringHash eventType, VariantMap& eventData)
  329. {
  330. using namespace WindowResized;
  331. mRootElement->setSize(eventData[P_WIDTH].getInt(), eventData[P_HEIGHT].getInt());
  332. }
  333. void UI::handleMouseMove(StringHash eventType, VariantMap& eventData)
  334. {
  335. using namespace MouseMove;
  336. mMouseButtons = eventData[P_BUTTONS].getInt();
  337. mQualifiers = eventData[P_QUALIFIERS].getInt();
  338. if ((mCursor) && (mCursor->isVisible()))
  339. {
  340. IntVector2 pos = mCursor->getPosition();
  341. pos.mX += eventData[P_X].getInt();
  342. pos.mY += eventData[P_Y].getInt();
  343. const IntVector2& rootSize = mRootElement->getSize();
  344. pos.mX = clamp(pos.mX, 0, rootSize.mX - 1);
  345. pos.mY = clamp(pos.mY, 0, rootSize.mY - 1);
  346. mCursor->setPosition(pos);
  347. if ((mMouseDrag) && (mMouseButtons))
  348. {
  349. UIElement* element = verifyElement(mMouseDragElement);
  350. if ((element) && (element->isEnabled()) && (element->isVisible()))
  351. element->onDragMove(element->screenToElement(pos), pos, mMouseButtons, mQualifiers);
  352. else
  353. {
  354. mMouseDrag = false;
  355. mMouseDragElement = 0;
  356. }
  357. }
  358. }
  359. }
  360. void UI::handleMouseButtonDown(StringHash eventType, VariantMap& eventData)
  361. {
  362. using namespace MouseButtonDown;
  363. mMouseButtons = eventData[P_BUTTONS].getInt();
  364. mQualifiers = eventData[P_QUALIFIERS].getInt();
  365. int button = eventData[P_BUTTON].getInt();
  366. if ((mCursor) && (mCursor->isVisible()))
  367. {
  368. IntVector2 pos = mCursor->getPosition();
  369. UIElement* element = getElementAt(pos);
  370. if (element)
  371. {
  372. // Handle focusing & bringing to front
  373. if (button == MOUSEB_LEFT)
  374. {
  375. setFocusElement(element);
  376. element->bringToFront();
  377. }
  378. // Handle click
  379. element->onClick(element->screenToElement(pos), pos, mMouseButtons, mQualifiers);
  380. // Handle start of drag
  381. if (!mMouseDrag)
  382. {
  383. mMouseDrag = true;
  384. mMouseDragElement = element;
  385. element->onDragStart(element->screenToElement(pos), pos, mMouseButtons, mQualifiers);
  386. }
  387. }
  388. else
  389. {
  390. // If clicked over no element, or a disabled element, lose focus
  391. setFocusElement(0);
  392. }
  393. }
  394. }
  395. void UI::handleMouseButtonUp(StringHash eventType, VariantMap& eventData)
  396. {
  397. using namespace MouseButtonUp;
  398. mMouseButtons = eventData[P_BUTTONS].getInt();
  399. mQualifiers = eventData[P_QUALIFIERS].getInt();
  400. if ((mCursor) && (mCursor->isVisible()))
  401. {
  402. IntVector2 pos = mCursor->getPosition();
  403. if ((mMouseDrag) && (!mMouseButtons))
  404. {
  405. UIElement* element = verifyElement(mMouseDragElement);
  406. if ((element) && (element->isEnabled()) && (element->isVisible()))
  407. element->onDragEnd(element->screenToElement(pos), pos);
  408. mMouseDrag = false;
  409. mMouseDragElement = 0;
  410. }
  411. }
  412. }
  413. void UI::handleKeyDown(StringHash eventType, VariantMap& eventData)
  414. {
  415. using namespace KeyDown;
  416. mMouseButtons = eventData[P_BUTTONS].getInt();
  417. mQualifiers = eventData[P_QUALIFIERS].getInt();
  418. UIElement* element = getFocusElement();
  419. if (element)
  420. element->onKey(eventData[P_KEY].getInt(), mMouseButtons, mQualifiers);
  421. }
  422. void UI::handleChar(StringHash eventType, VariantMap& eventData)
  423. {
  424. using namespace Char;
  425. mMouseButtons = eventData[P_BUTTONS].getInt();
  426. mQualifiers = eventData[P_QUALIFIERS].getInt();
  427. UIElement* element = getFocusElement();
  428. if (element)
  429. element->onChar(eventData[P_CHAR].getInt(), mMouseButtons, mQualifiers);
  430. }
  431. void UI::loadLayout(UIElement* current, const XMLElement& elem, XMLFile* styleFile)
  432. {
  433. XMLElement childElem = elem.getChildElement("element");
  434. while (childElem)
  435. {
  436. // Create and add to the hierarchy
  437. SharedPtr<UIElement> child = createElement(ShortStringHash(childElem.getString("type")), childElem.getString("name"));
  438. current->addChild(child);
  439. // First set the base style from the style file if exists, then apply UI layout overrides
  440. if (styleFile)
  441. child->setStyleAuto(styleFile, mCache);
  442. child->setStyle(childElem, mCache);
  443. // Load the children recursively
  444. loadLayout(child, childElem, styleFile);
  445. childElem = childElem.getNextElement("element");
  446. }
  447. }