Cursor.cpp 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292
  1. // Copyright (c) 2008-2022 the Urho3D project
  2. // License: MIT
  3. #include "../Precompiled.h"
  4. #include "../Core/Context.h"
  5. #include "../GraphicsAPI/Texture2D.h"
  6. #include "../Input/Input.h"
  7. #include "../IO/Log.h"
  8. #include "../Resource/ResourceCache.h"
  9. #include "../UI/UI.h"
  10. #include <SDL/SDL_mouse.h>
  11. #include "../DebugNew.h"
  12. namespace Urho3D
  13. {
  14. static const char* shapeNames[] =
  15. {
  16. "Normal",
  17. "IBeam",
  18. "Cross",
  19. "ResizeVertical",
  20. "ResizeDiagonalTopRight",
  21. "ResizeHorizontal",
  22. "ResizeDiagonalTopLeft",
  23. "ResizeAll",
  24. "AcceptDrop",
  25. "RejectDrop",
  26. "Busy",
  27. "BusyArrow"
  28. };
  29. #if !defined(__ANDROID__) && !defined(IOS) && !defined(TVOS)
  30. // OS cursor shape lookup table matching cursor shape enumeration
  31. static const int osCursorLookup[CS_MAX_SHAPES] =
  32. {
  33. SDL_SYSTEM_CURSOR_ARROW, // CS_NORMAL
  34. SDL_SYSTEM_CURSOR_IBEAM, // CS_IBEAM
  35. SDL_SYSTEM_CURSOR_CROSSHAIR, // CS_CROSS
  36. SDL_SYSTEM_CURSOR_SIZENS, // CS_RESIZEVERTICAL
  37. SDL_SYSTEM_CURSOR_SIZENESW, // CS_RESIZEDIAGONAL_TOPRIGHT
  38. SDL_SYSTEM_CURSOR_SIZEWE, // CS_RESIZEHORIZONTAL
  39. SDL_SYSTEM_CURSOR_SIZENWSE, // CS_RESIZEDIAGONAL_TOPLEFT
  40. SDL_SYSTEM_CURSOR_SIZEALL, // CS_RESIZE_ALL
  41. SDL_SYSTEM_CURSOR_HAND, // CS_ACCEPTDROP
  42. SDL_SYSTEM_CURSOR_NO, // CS_REJECTDROP
  43. SDL_SYSTEM_CURSOR_WAIT, // CS_BUSY
  44. SDL_SYSTEM_CURSOR_WAITARROW // CS_BUSY_ARROW
  45. };
  46. #endif
  47. extern const char* UI_CATEGORY;
  48. Cursor::Cursor(Context* context) :
  49. BorderImage(context),
  50. shape_(shapeNames[CS_NORMAL]),
  51. useSystemShapes_(false),
  52. osShapeDirty_(false)
  53. {
  54. // Define the defaults for system cursor usage.
  55. for (unsigned i = 0; i < CS_MAX_SHAPES; i++)
  56. shapeInfos_[shapeNames[i]] = CursorShapeInfo(i);
  57. // Subscribe to OS mouse cursor visibility changes to be able to reapply the cursor shape
  58. SubscribeToEvent(E_MOUSEVISIBLECHANGED, URHO3D_HANDLER(Cursor, HandleMouseVisibleChanged));
  59. }
  60. Cursor::~Cursor()
  61. {
  62. for (HashMap<String, CursorShapeInfo>::Iterator i = shapeInfos_.Begin(); i != shapeInfos_.End(); ++i)
  63. {
  64. if (i->second_.osCursor_)
  65. {
  66. SDL_FreeCursor(i->second_.osCursor_);
  67. i->second_.osCursor_ = nullptr;
  68. }
  69. }
  70. }
  71. void Cursor::RegisterObject(Context* context)
  72. {
  73. context->RegisterFactory<Cursor>(UI_CATEGORY);
  74. URHO3D_COPY_BASE_ATTRIBUTES(BorderImage);
  75. URHO3D_UPDATE_ATTRIBUTE_DEFAULT_VALUE("Priority", M_MAX_INT);
  76. URHO3D_ACCESSOR_ATTRIBUTE("Use System Shapes", GetUseSystemShapes, SetUseSystemShapes, false, AM_FILE);
  77. URHO3D_ACCESSOR_ATTRIBUTE("Shapes", GetShapesAttr, SetShapesAttr, Variant::emptyVariantVector, AM_FILE);
  78. }
  79. void Cursor::GetBatches(Vector<UIBatch>& batches, Vector<float>& vertexData, const IntRect& currentScissor)
  80. {
  81. i32 initialSize = vertexData.Size();
  82. const IntVector2& offset = shapeInfos_[shape_].hotSpot_;
  83. Vector2 floatOffset(-(float)offset.x_, -(float)offset.y_);
  84. BorderImage::GetBatches(batches, vertexData, currentScissor);
  85. for (i32 i = initialSize; i < vertexData.Size(); i += 6)
  86. {
  87. vertexData[i] += floatOffset.x_;
  88. vertexData[i + 1] += floatOffset.y_;
  89. }
  90. }
  91. void Cursor::DefineShape(CursorShape shape, Image* image, const IntRect& imageRect, const IntVector2& hotSpot)
  92. {
  93. if (shape < CS_NORMAL || shape >= CS_MAX_SHAPES)
  94. {
  95. URHO3D_LOGERROR("Shape index out of bounds, can not define cursor shape");
  96. return;
  97. }
  98. DefineShape(shapeNames[shape], image, imageRect, hotSpot);
  99. }
  100. void Cursor::DefineShape(const String& shape, Image* image, const IntRect& imageRect, const IntVector2& hotSpot)
  101. {
  102. if (!image)
  103. return;
  104. auto* cache = GetSubsystem<ResourceCache>();
  105. if (!shapeInfos_.Contains(shape))
  106. shapeInfos_[shape] = CursorShapeInfo();
  107. CursorShapeInfo& info = shapeInfos_[shape];
  108. // Prefer to get the texture with same name from cache to prevent creating several copies of the texture
  109. info.texture_ = cache->GetResource<Texture2D>(image->GetName(), false);
  110. if (!info.texture_)
  111. {
  112. auto* texture = new Texture2D(context_);
  113. texture->SetData(SharedPtr<Image>(image));
  114. info.texture_ = texture;
  115. }
  116. info.image_ = image;
  117. info.imageRect_ = imageRect;
  118. info.hotSpot_ = hotSpot;
  119. // Remove existing SDL cursor
  120. if (info.osCursor_)
  121. {
  122. SDL_FreeCursor(info.osCursor_);
  123. info.osCursor_ = nullptr;
  124. }
  125. // Reset current shape if it was edited
  126. if (shape_ == shape)
  127. {
  128. shape_ = String::EMPTY;
  129. SetShape(shape);
  130. }
  131. }
  132. void Cursor::SetShape(const String& shape)
  133. {
  134. if (shape == String::EMPTY || shape.Empty() || shape_ == shape || !shapeInfos_.Contains(shape))
  135. return;
  136. shape_ = shape;
  137. CursorShapeInfo& info = shapeInfos_[shape_];
  138. texture_ = info.texture_;
  139. imageRect_ = info.imageRect_;
  140. SetSize(info.imageRect_.Size());
  141. // To avoid flicker, the UI subsystem will apply the OS shape once per frame. Exception: if we are using the
  142. // busy shape, set it immediately as we may block before that
  143. osShapeDirty_ = true;
  144. if (shape_ == shapeNames[CS_BUSY])
  145. ApplyOSCursorShape();
  146. }
  147. void Cursor::SetShape(CursorShape shape)
  148. {
  149. if (shape < CS_NORMAL || shape >= CS_MAX_SHAPES || shape_ == shapeNames[shape])
  150. return;
  151. SetShape(shapeNames[shape]);
  152. }
  153. void Cursor::SetUseSystemShapes(bool enable)
  154. {
  155. if (enable != useSystemShapes_)
  156. {
  157. useSystemShapes_ = enable;
  158. // Reapply current shape
  159. osShapeDirty_ = true;
  160. }
  161. }
  162. void Cursor::SetShapesAttr(const VariantVector& value)
  163. {
  164. if (!value.Size())
  165. return;
  166. for (VariantVector::ConstIterator i = value.Begin(); i != value.End(); ++i)
  167. {
  168. VariantVector shapeVector = i->GetVariantVector();
  169. if (shapeVector.Size() >= 4)
  170. {
  171. String shape = shapeVector[0].GetString();
  172. ResourceRef ref = shapeVector[1].GetResourceRef();
  173. IntRect imageRect = shapeVector[2].GetIntRect();
  174. IntVector2 hotSpot = shapeVector[3].GetIntVector2();
  175. DefineShape(shape, GetSubsystem<ResourceCache>()->GetResource<Image>(ref.name_), imageRect, hotSpot);
  176. }
  177. }
  178. }
  179. VariantVector Cursor::GetShapesAttr() const
  180. {
  181. VariantVector ret;
  182. for (HashMap<String, CursorShapeInfo>::ConstIterator i = shapeInfos_.Begin(); i != shapeInfos_.End(); ++i)
  183. {
  184. if (i->second_.imageRect_ != IntRect::ZERO)
  185. {
  186. // Could use a map but this simplifies the UI xml.
  187. VariantVector shape;
  188. shape.Push(i->first_);
  189. shape.Push(GetResourceRef(i->second_.texture_, Texture2D::GetTypeStatic()));
  190. shape.Push(i->second_.imageRect_);
  191. shape.Push(i->second_.hotSpot_);
  192. ret.Push(shape);
  193. }
  194. }
  195. return ret;
  196. }
  197. void Cursor::ApplyOSCursorShape()
  198. {
  199. // Mobile platforms do not support applying OS cursor shapes: comment out to avoid log error messages
  200. #if !defined(__ANDROID__) && !defined(IOS) && !defined(TVOS)
  201. if (!osShapeDirty_ || !GetSubsystem<Input>()->IsMouseVisible() || GetSubsystem<UI>()->GetCursor() != this)
  202. return;
  203. CursorShapeInfo& info = shapeInfos_[shape_];
  204. // Remove existing SDL cursor if is not a system shape while we should be using those, or vice versa
  205. if (info.osCursor_ && info.systemDefined_ != useSystemShapes_)
  206. {
  207. SDL_FreeCursor(info.osCursor_);
  208. info.osCursor_ = nullptr;
  209. }
  210. // Create SDL cursor now if necessary
  211. if (!info.osCursor_)
  212. {
  213. // Create a system default shape
  214. if (useSystemShapes_ && info.systemCursor_ >= 0 && info.systemCursor_ < CS_MAX_SHAPES)
  215. {
  216. info.osCursor_ = SDL_CreateSystemCursor((SDL_SystemCursor)osCursorLookup[info.systemCursor_]);
  217. info.systemDefined_ = true;
  218. if (!info.osCursor_)
  219. URHO3D_LOGERROR("Could not create system cursor");
  220. }
  221. // Create from image
  222. else if (info.image_)
  223. {
  224. SDL_Surface* surface = info.image_->GetSDLSurface(info.imageRect_);
  225. if (surface)
  226. {
  227. info.osCursor_ = SDL_CreateColorCursor(surface, info.hotSpot_.x_, info.hotSpot_.y_);
  228. info.systemDefined_ = false;
  229. if (!info.osCursor_)
  230. URHO3D_LOGERROR("Could not create cursor from image " + info.image_->GetName());
  231. SDL_FreeSurface(surface);
  232. }
  233. }
  234. }
  235. if (info.osCursor_)
  236. SDL_SetCursor(info.osCursor_);
  237. osShapeDirty_ = false;
  238. #endif
  239. }
  240. void Cursor::HandleMouseVisibleChanged(StringHash eventType, VariantMap& eventData)
  241. {
  242. ApplyOSCursorShape();
  243. }
  244. }