@@ -180,6 +180,10 @@ int Monitor::x() const
180180void Monitor::setX (int x)
181181{
182182 impl->x = x;
183+ impl->needsAutoPosition = false ;
184+
185+ if (impl->iface )
186+ impl->iface ->onXChanged (x);
183187}
184188
185189/* ! Returns the monitor's y-coordinate. */
@@ -192,6 +196,10 @@ int Monitor::y() const
192196void Monitor::setY (int y)
193197{
194198 impl->y = y;
199+ impl->needsAutoPosition = false ;
200+
201+ if (impl->iface )
202+ impl->iface ->onYChanged (y);
195203}
196204
197205/* ! Returns true if the monitor is visible. */
@@ -245,15 +253,130 @@ void Monitor::setDiscrete(bool discrete)
245253 impl->discrete = discrete;
246254}
247255
248- /* ! Returns the initial position of a monitor. */
249- Rect Monitor::getInitialPosition ( const std::vector<std::shared_ptr<Monitor>> &other, int monitorWidth, int monitorHeight)
256+ /* ! Returns true if the monitor needs auto positioning. The renderer should call autoPosition() as soon as it knows the monitor size . */
257+ bool Monitor::needsAutoPosition () const
250258{
251- // TODO: Implement this like Scratch has: https://github.com/scratchfoundation/scratch-gui/blob/010e27937ecff531f23bfcf3c711cd6e565cc7f9/src/reducers/monitor-layout.js#L161-L243
252- // Place the monitor randomly
259+ return impl->needsAutoPosition ;
260+ }
261+
262+ /* !
263+ * Auto-positions the monitor with the other monitors.
264+ * \note Call this only when the monitor size is known.
265+ */
266+ void Monitor::autoPosition (const std::vector<std::shared_ptr<Monitor>> &allMonitors)
267+ {
268+ // https://github.com/scratchfoundation/scratch-gui/blob/010e27937ecff531f23bfcf3c711cd6e565cc7f9/src/reducers/monitor-layout.js#L161-L243
269+ if (!impl->needsAutoPosition )
270+ std::cout << " warning: auto-positioning already positioned monitor (" << impl->name << " )" << std::endl;
271+
272+ impl->needsAutoPosition = false ;
273+
274+ // Try all starting positions for the new monitor to find one that doesn't intersect others
275+ std::vector<int > endXs = { 0 };
276+ std::vector<int > endYs = { 0 };
277+ int lastX = 0 ;
278+ int lastY = 0 ;
279+ bool haveLastX = false ;
280+ bool haveLastY = false ;
281+
282+ for (const auto monitor : allMonitors) {
283+ if (monitor.get () != this ) {
284+ int x = monitor->x () + monitor->width ();
285+ x = std::ceil (x / 50.0 ) * 50 ; // Try to choose a sensible "tab width" so more monitors line up
286+ endXs.push_back (x);
287+ endYs.push_back (std::ceil (monitor->y () + monitor->height ()));
288+ }
289+ }
290+
291+ std::sort (endXs.begin (), endXs.end ());
292+ std::sort (endYs.begin (), endYs.end ());
293+
294+ // We'll use plan B if the monitor doesn't fit anywhere (too long or tall)
295+ bool planB = false ;
296+ Rect planBRect;
297+
298+ for (const int x : endXs) {
299+ if (haveLastX && x == lastX)
300+ continue ;
301+
302+ lastX = x;
303+ haveLastX = true ;
304+
305+ for (const int y : endYs) {
306+ if (haveLastY && y == lastY)
307+ continue ;
308+
309+ lastY = y;
310+ haveLastY = true ;
311+
312+ const Rect monitorRect (x + PADDING, y + PADDING, x + PADDING + impl->width , y + PADDING + impl->height );
313+
314+ // Intersection testing rect that includes padding
315+ const Rect rect (x, y, x + impl->width + 2 * PADDING, y + impl->height + 2 * PADDING);
316+
317+ bool intersected = false ;
318+
319+ for (const auto monitor : allMonitors) {
320+ if (monitor.get () != this ) {
321+ const Rect currentRect (monitor->x (), monitor->y (), monitor->x () + monitor->width (), monitor->y () + monitor->height ());
322+
323+ if (monitorRectsIntersect (currentRect, rect)) {
324+ intersected = true ;
325+ break ;
326+ }
327+ }
328+ }
329+
330+ if (intersected) {
331+ continue ;
332+ }
333+
334+ // If the rect overlaps the ends of the screen
335+ if (rect.right () > SCREEN_WIDTH || rect.bottom () > SCREEN_HEIGHT) {
336+ // If rect is not too close to completely off-screen, set it as plan B
337+ if (!planB && !(rect.left () + SCREEN_EDGE_BUFFER > SCREEN_WIDTH || rect.top () + SCREEN_EDGE_BUFFER > SCREEN_HEIGHT)) {
338+ planBRect = monitorRect;
339+ planB = true ;
340+ }
341+
342+ continue ;
343+ }
344+
345+ setX (monitorRect.left ());
346+ setY (monitorRect.top ());
347+ return ;
348+ }
349+ }
350+
351+ // If the monitor is too long to fit anywhere, put it in the leftmost spot available
352+ // that intersects the right or bottom edge and isn't too close to the edge.
353+ if (planB) {
354+ setX (planBRect.left ());
355+ setY (planBRect.top ());
356+ return ;
357+ }
358+
359+ // If plan B fails and there's nowhere reasonable to put it, plan C is to place the monitor randomly
253360 if (!MonitorPrivate::rng)
254361 MonitorPrivate::rng = RandomGenerator::instance ().get ();
255362
256- const double randX = std::ceil (MonitorPrivate::rng->randintDouble (0 , SCREEN_WIDTH / 2.0 ));
257- const double randY = std::ceil (MonitorPrivate::rng->randintDouble (0 , SCREEN_HEIGHT - SCREEN_EDGE_BUFFER));
258- return Rect (randX, randY, randX + monitorWidth, randY + monitorHeight);
363+ const int randX = std::ceil (MonitorPrivate::rng->randintDouble (0 , SCREEN_WIDTH / 2.0 ));
364+ const int randY = std::ceil (MonitorPrivate::rng->randintDouble (0 , SCREEN_HEIGHT - SCREEN_EDGE_BUFFER));
365+ setX (randX);
366+ setY (randY);
367+ return ;
368+ }
369+
370+ bool Monitor::monitorRectsIntersect (const Rect &a, const Rect &b)
371+ {
372+ // https://github.com/scratchfoundation/scratch-gui/blob/010e27937ecff531f23bfcf3c711cd6e565cc7f9/src/reducers/monitor-layout.js#L152-L158
373+ // If one rectangle is on left side of other
374+ if (a.left () >= b.right () || b.left () >= a.right ())
375+ return false ;
376+
377+ // If one rectangle is above other
378+ if (a.top () >= b.bottom () || b.top () >= a.bottom ())
379+ return false ;
380+
381+ return true ;
259382}
0 commit comments