The JUCE cross-platform C++ framework, with DISTRHO/KXStudio specific changes
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

445 lines
16KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2022 - Raw Material Software Limited
  5. JUCE is an open source library subject to commercial or open-source
  6. licensing.
  7. By using JUCE, you agree to the terms of both the JUCE 7 End-User License
  8. Agreement and JUCE Privacy Policy.
  9. End User License Agreement: www.juce.com/juce-7-licence
  10. Privacy Policy: www.juce.com/juce-privacy-policy
  11. Or: You may also use this code under the terms of the GPL v3 (see
  12. www.gnu.org/licenses).
  13. JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
  14. EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
  15. DISCLAIMED.
  16. ==============================================================================
  17. */
  18. namespace juce
  19. {
  20. template <typename This>
  21. auto* getPrimaryDisplayImpl (This& t)
  22. {
  23. JUCE_ASSERT_MESSAGE_MANAGER_IS_LOCKED
  24. const auto iter = std::find_if (t.displays.begin(), t.displays.end(), [] (auto& d) { return d.isMain; });
  25. return iter != t.displays.end() ? std::addressof (*iter) : nullptr;
  26. }
  27. Displays::Displays (Desktop& desktop)
  28. {
  29. init (desktop);
  30. }
  31. void Displays::init (Desktop& desktop)
  32. {
  33. findDisplays (desktop.getGlobalScaleFactor());
  34. }
  35. const Displays::Display* Displays::getDisplayForRect (Rectangle<int> rect, bool isPhysical) const noexcept
  36. {
  37. int maxArea = -1;
  38. const Display* foundDisplay = nullptr;
  39. for (auto& display : displays)
  40. {
  41. auto displayArea = display.totalArea;
  42. if (isPhysical)
  43. displayArea = (displayArea.withZeroOrigin() * display.scale) + display.topLeftPhysical;
  44. displayArea = displayArea.getIntersection (rect);
  45. auto area = displayArea.getWidth() * displayArea.getHeight();
  46. if (area >= maxArea)
  47. {
  48. maxArea = area;
  49. foundDisplay = &display;
  50. }
  51. }
  52. return foundDisplay;
  53. }
  54. const Displays::Display* Displays::getDisplayForPoint (Point<int> point, bool isPhysical) const noexcept
  55. {
  56. auto minDistance = std::numeric_limits<int>::max();
  57. const Display* foundDisplay = nullptr;
  58. for (auto& display : displays)
  59. {
  60. auto displayArea = display.totalArea;
  61. if (isPhysical)
  62. displayArea = (displayArea.withZeroOrigin() * display.scale) + display.topLeftPhysical;
  63. if (displayArea.contains (point))
  64. return &display;
  65. auto distance = displayArea.getCentre().getDistanceFrom (point);
  66. if (distance <= minDistance)
  67. {
  68. minDistance = distance;
  69. foundDisplay = &display;
  70. }
  71. }
  72. return foundDisplay;
  73. }
  74. Rectangle<int> Displays::physicalToLogical (Rectangle<int> rect, const Display* useScaleFactorOfDisplay) const noexcept
  75. {
  76. return physicalToLogical (rect.toFloat(), useScaleFactorOfDisplay).toNearestInt();
  77. }
  78. Rectangle<float> Displays::physicalToLogical (Rectangle<float> rect, const Display* useScaleFactorOfDisplay) const noexcept
  79. {
  80. const auto* display = useScaleFactorOfDisplay != nullptr ? useScaleFactorOfDisplay
  81. : getDisplayForRect (rect.toNearestInt(), true);
  82. if (display == nullptr)
  83. return rect;
  84. auto globalScale = Desktop::getInstance().getGlobalScaleFactor();
  85. return ((rect - display->topLeftPhysical.toFloat()) / (display->scale / globalScale))
  86. + (display->totalArea.getTopLeft().toFloat() * globalScale);
  87. }
  88. Rectangle<int> Displays::logicalToPhysical (Rectangle<int> rect, const Display* useScaleFactorOfDisplay) const noexcept
  89. {
  90. return logicalToPhysical (rect.toFloat(), useScaleFactorOfDisplay).toNearestInt();
  91. }
  92. Rectangle<float> Displays::logicalToPhysical (Rectangle<float> rect, const Display* useScaleFactorOfDisplay) const noexcept
  93. {
  94. const auto* display = useScaleFactorOfDisplay != nullptr ? useScaleFactorOfDisplay
  95. : getDisplayForRect (rect.toNearestInt(), false);
  96. if (display == nullptr)
  97. return rect;
  98. auto globalScale = Desktop::getInstance().getGlobalScaleFactor();
  99. return ((rect.toFloat() - (display->totalArea.getTopLeft().toFloat() * globalScale)) * (display->scale / globalScale))
  100. + display->topLeftPhysical.toFloat();
  101. }
  102. template <typename ValueType>
  103. Point<ValueType> Displays::physicalToLogical (Point<ValueType> point, const Display* useScaleFactorOfDisplay) const noexcept
  104. {
  105. const auto* display = useScaleFactorOfDisplay != nullptr ? useScaleFactorOfDisplay
  106. : getDisplayForPoint (point.roundToInt(), true);
  107. if (display == nullptr)
  108. return point;
  109. auto globalScale = Desktop::getInstance().getGlobalScaleFactor();
  110. Point<ValueType> logicalTopLeft (static_cast<ValueType> (display->totalArea.getX()), static_cast<ValueType> (display->totalArea.getY()));
  111. Point<ValueType> physicalTopLeft (static_cast<ValueType> (display->topLeftPhysical.getX()), static_cast<ValueType> (display->topLeftPhysical.getY()));
  112. return ((point - physicalTopLeft) / (display->scale / globalScale)) + (logicalTopLeft * globalScale);
  113. }
  114. template <typename ValueType>
  115. Point<ValueType> Displays::logicalToPhysical (Point<ValueType> point, const Display* useScaleFactorOfDisplay) const noexcept
  116. {
  117. const auto* display = useScaleFactorOfDisplay != nullptr ? useScaleFactorOfDisplay
  118. : getDisplayForPoint (point.roundToInt(), false);
  119. if (display == nullptr)
  120. return point;
  121. auto globalScale = Desktop::getInstance().getGlobalScaleFactor();
  122. Point<ValueType> logicalTopLeft (static_cast<ValueType> (display->totalArea.getX()), static_cast<ValueType> (display->totalArea.getY()));
  123. Point<ValueType> physicalTopLeft (static_cast<ValueType> (display->topLeftPhysical.getX()), static_cast<ValueType> (display->topLeftPhysical.getY()));
  124. return ((point - (logicalTopLeft * globalScale)) * (display->scale / globalScale)) + physicalTopLeft;
  125. }
  126. const Displays::Display* Displays::getPrimaryDisplay() const noexcept
  127. {
  128. return getPrimaryDisplayImpl (*this);
  129. }
  130. RectangleList<int> Displays::getRectangleList (bool userAreasOnly) const
  131. {
  132. JUCE_ASSERT_MESSAGE_MANAGER_IS_LOCKED
  133. RectangleList<int> rl;
  134. for (auto& d : displays)
  135. rl.addWithoutMerging (userAreasOnly ? d.userArea : d.totalArea);
  136. return rl;
  137. }
  138. Rectangle<int> Displays::getTotalBounds (bool userAreasOnly) const
  139. {
  140. return getRectangleList (userAreasOnly).getBounds();
  141. }
  142. void Displays::refresh()
  143. {
  144. Array<Display> oldDisplays;
  145. oldDisplays.swapWith (displays);
  146. init (Desktop::getInstance());
  147. if (oldDisplays != displays)
  148. {
  149. for (auto i = ComponentPeer::getNumPeers(); --i >= 0;)
  150. if (auto* peer = ComponentPeer::getPeer (i))
  151. peer->handleScreenSizeChange();
  152. }
  153. }
  154. static auto tie (const Displays::Display& d)
  155. {
  156. return std::tie (d.dpi,
  157. d.isMain,
  158. d.keyboardInsets,
  159. d.safeAreaInsets,
  160. d.scale,
  161. d.topLeftPhysical,
  162. d.totalArea,
  163. d.userArea);
  164. }
  165. static bool operator== (const Displays::Display& d1, const Displays::Display& d2) noexcept
  166. {
  167. return tie (d1) == tie (d2);
  168. }
  169. //==============================================================================
  170. // These methods are used for converting the totalArea and userArea Rectangles in Display from physical to logical
  171. // pixels. We do this by constructing a graph of connected displays where the root node has position (0, 0); this can be
  172. // safely converted to logical pixels using its scale factor and we can then traverse the graph and work out the logical pixels
  173. // for all the other connected displays. We need to do this as the logical bounds of a display depend not only on its scale
  174. // factor but also the scale factor of the displays connected to it.
  175. /**
  176. Represents a node in our graph of displays.
  177. */
  178. struct DisplayNode
  179. {
  180. /** The Display object that this represents. */
  181. Displays::Display* display;
  182. /** True if this represents the 'root' display with position (0, 0). */
  183. bool isRoot = false;
  184. /** The parent node of this node in our display graph. This will have a correct logicalArea. */
  185. DisplayNode* parent = nullptr;
  186. /** The logical area to be calculated. This will be valid after processDisplay() has
  187. been called on this node.
  188. */
  189. Rectangle<double> logicalArea;
  190. };
  191. /** Recursive - will calculate and set the logicalArea member of current. */
  192. static void processDisplay (DisplayNode* currentNode, Array<DisplayNode>& allNodes)
  193. {
  194. const auto physicalArea = currentNode->display->totalArea.toDouble();
  195. const auto scale = currentNode->display->scale;
  196. if (! currentNode->isRoot)
  197. {
  198. const auto logicalWidth = physicalArea.getWidth() / scale;
  199. const auto logicalHeight = physicalArea.getHeight() / scale;
  200. const auto physicalParentArea = currentNode->parent->display->totalArea.toDouble();
  201. const auto logicalParentArea = currentNode->parent->logicalArea; // logical area of parent has already been calculated
  202. const auto parentScale = currentNode->parent->display->scale;
  203. Rectangle<double> logicalArea (0.0, 0.0, logicalWidth, logicalHeight);
  204. if (physicalArea.getRight() == physicalParentArea.getX()) logicalArea.setPosition ({ logicalParentArea.getX() - logicalWidth, physicalArea.getY() / parentScale }); // on left
  205. else if (physicalArea.getX() == physicalParentArea.getRight()) logicalArea.setPosition ({ logicalParentArea.getRight(), physicalArea.getY() / parentScale }); // on right
  206. else if (physicalArea.getBottom() == physicalParentArea.getY()) logicalArea.setPosition ({ physicalArea.getX() / parentScale, logicalParentArea.getY() - logicalHeight }); // on top
  207. else if (physicalArea.getY() == physicalParentArea.getBottom()) logicalArea.setPosition ({ physicalArea.getX() / parentScale, logicalParentArea.getBottom() }); // on bottom
  208. else jassertfalse;
  209. currentNode->logicalArea = logicalArea;
  210. }
  211. else
  212. {
  213. // If currentNode is the root (position (0, 0)) then we can just scale the physical area
  214. currentNode->logicalArea = physicalArea / scale;
  215. currentNode->parent = currentNode;
  216. }
  217. // Find child nodes
  218. Array<DisplayNode*> children;
  219. for (auto& node : allNodes)
  220. {
  221. // Already calculated
  222. if (node.parent != nullptr)
  223. continue;
  224. const auto otherPhysicalArea = node.display->totalArea.toDouble();
  225. // If the displays are touching on any side
  226. if (otherPhysicalArea.getX() == physicalArea.getRight() || otherPhysicalArea.getRight() == physicalArea.getX()
  227. || otherPhysicalArea.getY() == physicalArea.getBottom() || otherPhysicalArea.getBottom() == physicalArea.getY())
  228. {
  229. node.parent = currentNode;
  230. children.add (&node);
  231. }
  232. }
  233. // Recursively process all child nodes
  234. for (auto child : children)
  235. processDisplay (child, allNodes);
  236. }
  237. /** This is called when the displays Array has been filled out with the info for all connected displays and the
  238. totalArea and userArea Rectangles need to be converted from physical to logical coordinates.
  239. */
  240. void Displays::updateToLogical()
  241. {
  242. if (displays.size() == 1)
  243. {
  244. auto& display = displays.getReference (0);
  245. display.totalArea = (display.totalArea.toDouble() / display.scale).toNearestInt();
  246. display.userArea = (display.userArea.toDouble() / display.scale).toNearestInt();
  247. return;
  248. }
  249. Array<DisplayNode> displayNodes;
  250. for (auto& d : displays)
  251. {
  252. DisplayNode node;
  253. node.display = &d;
  254. if (d.totalArea.getTopLeft() == Point<int>())
  255. node.isRoot = true;
  256. displayNodes.add (node);
  257. }
  258. auto* root = [&displayNodes]() -> DisplayNode*
  259. {
  260. for (auto& node : displayNodes)
  261. if (node.isRoot)
  262. return &node;
  263. auto minDistance = std::numeric_limits<int>::max();
  264. DisplayNode* retVal = nullptr;
  265. for (auto& node : displayNodes)
  266. {
  267. auto distance = node.display->totalArea.getTopLeft().getDistanceFrom ({});
  268. if (distance < minDistance)
  269. {
  270. minDistance = distance;
  271. retVal = &node;
  272. }
  273. }
  274. if (retVal != nullptr)
  275. retVal->isRoot = true;
  276. return retVal;
  277. }();
  278. // Must have a root node!
  279. jassert (root != nullptr);
  280. // Recursively traverse the display graph from the root and work out logical bounds
  281. processDisplay (root, displayNodes);
  282. for (auto& node : displayNodes)
  283. {
  284. // All of the nodes should have a parent
  285. jassert (node.parent != nullptr);
  286. auto relativeUserArea = (node.display->userArea.toDouble() - node.display->totalArea.toDouble().getTopLeft()) / node.display->scale;
  287. // Now set Display::totalArea and ::userArea using the logical area that we have calculated
  288. node.display->topLeftPhysical = node.display->totalArea.getTopLeft();
  289. node.display->totalArea = node.logicalArea.toNearestInt();
  290. node.display->userArea = (relativeUserArea + node.logicalArea.getTopLeft()).toNearestInt();
  291. }
  292. }
  293. #ifndef DOXYGEN
  294. // explicit template instantiations
  295. template Point<int> Displays::physicalToLogical (Point<int>, const Display*) const noexcept;
  296. template Point<float> Displays::physicalToLogical (Point<float>, const Display*) const noexcept;
  297. template Point<int> Displays::logicalToPhysical (Point<int>, const Display*) const noexcept;
  298. template Point<float> Displays::logicalToPhysical (Point<float>, const Display*) const noexcept;
  299. #endif
  300. //==============================================================================
  301. // Deprecated methods
  302. const Displays::Display& Displays::getDisplayContaining (Point<int> position) const noexcept
  303. {
  304. JUCE_ASSERT_MESSAGE_MANAGER_IS_LOCKED
  305. const auto* best = &displays.getReference (0);
  306. auto bestDistance = std::numeric_limits<int>::max();
  307. for (auto& d : displays)
  308. {
  309. if (d.totalArea.contains (position))
  310. {
  311. best = &d;
  312. break;
  313. }
  314. auto distance = d.totalArea.getCentre().getDistanceFrom (position);
  315. if (distance < bestDistance)
  316. {
  317. bestDistance = distance;
  318. best = &d;
  319. }
  320. }
  321. return *best;
  322. }
  323. const Displays::Display& Displays::findDisplayForRect (Rectangle<int> rect, bool isPhysical) const noexcept
  324. {
  325. if (auto* display = getDisplayForRect (rect, isPhysical))
  326. return *display;
  327. return emptyDisplay;
  328. }
  329. const Displays::Display& Displays::findDisplayForPoint (Point<int> point, bool isPhysical) const noexcept
  330. {
  331. if (auto* display = getDisplayForPoint (point, isPhysical))
  332. return *display;
  333. return emptyDisplay;
  334. }
  335. const Displays::Display& Displays::getMainDisplay() const noexcept
  336. {
  337. if (auto* display = getPrimaryDisplay())
  338. return *display;
  339. return emptyDisplay;
  340. }
  341. } // namespace juce