Audio plugin host https://kx.studio/carla
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.

382 lines
14KB

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