@@ -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 (" name  << " )" 
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