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.

349 lines
12KB

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