Processing Ajax...

Title
Close Dialog

Message

Confirm
Close Dialog

Confirm
Close Dialog

Cycle Left to Unoccupied Position

Description
For landscape monitors there are 4 positions, for vertical monitors there are 2 positions, wraps around, supports either direction.
Language
C#.net
Minimum Version
Created By
Derek Ziemba
Contributors
-
Date Created
May 18, 2022
Date Last Modified
May 18, 2022

Scripted Function (Macro) Code

using System;
using System.Drawing;

// Breaks landscape monitors up into 4 sections and vertical monitors into two, 
// It then cycles through those positions bypassesing any already occupied positions. 
// If all positions occupied, it falls back to only bypassing positions occupied by the same app (so chrome won't go over chrome)
// If every position is occupied by the same app already, it just goes one position over. 
public static class DisplayFusionFunction {


  public static void Run(IntPtr windowHandle) {
	// Sigh... doesn't trigger unless the exact keycombo is used
	// requiring this to be hard-coded and the script duplicated for each direction
	// bummer because I assigned a mouse button for this, so ctrl and shift would've been so easy to work with
	bool cycleLeft = true; //BFS.Input.IsKeyDown("17"); // Is CTRL key Down
	 // If shift is down, don't divide up monitor, move it full sized instead
	bool fullsize = false; //BFS.Input.IsKeyDown(""); 
	  
	Span<Rectangle> allWorkAreas = BFS.Monitor.GetMonitorWorkAreas();  
	if (cycleLeft) { allWorkAreas.Reverse(); }
	
	Span<Rectangle> sections = fullsize ? allWorkAreas : GetSections(stackalloc Rectangle[allWorkAreas.Length * 4], allWorkAreas, cycleLeft);  
	
	int idxNextSection = GetNextSection(windowHandle, allWorkAreas, sections);
	
	Span<IntPtr> existingWindowHandles 	= BFS.Window.GetVisibleWindowHandles();
	Span<Rectangle> occupiedPositions 	= stackalloc Rectangle[existingWindowHandles.Length];
	for (int i = 0; i < existingWindowHandles.Length; ++i) { occupiedPositions[i] = BFS.Window.GetBounds(existingWindowHandles[i]); }
	
	int idxNextPosition = idxNextSection;
	if(TryFindUnoccupiedPosition(sections, occupiedPositions, idxNextSection, out int idxUnoccupiedPosition)) {
		idxNextPosition = idxUnoccupiedPosition;
	} 
	else if (TryFindPositionUnoccupiedByCurrentApp(windowHandle, existingWindowHandles, 
								 sections, occupiedPositions, idxNextSection, out int idxDifferentAppsOccupiedPosition)) {
		idxNextPosition = idxDifferentAppsOccupiedPosition;							 
	}
	
	    
	ref Rectangle targetArea = ref sections[idxNextPosition % sections.Length];	
    BFS.Window.SetSizeAndLocation(windowHandle, targetArea.X, targetArea.Y, targetArea.Width, targetArea.Height);
  }

  private static Span<Rectangle> GetSections(
	  Span<Rectangle> sections, 
	  Span<Rectangle> allWorkAreas, 
	  bool cycleLeft) 
  {
	int sectionsLength = 0;
    for (int i = 0; i < allWorkAreas.Length; ++i) {
      ref Rectangle area = ref allWorkAreas[i];
	  int halfheight = area.Height/2;
	  int halfwidth = area.Width/2;
		
	  // I offset 2px vertically to prevent accidentally resizing the window when I instinctively go to grab the top. 
      if (halfwidth> halfheight) {
        sections[sectionsLength++] = new Rectangle(area.X, 				area.Y-2, 			 halfwidth, halfheight+2);
        sections[sectionsLength++] = new Rectangle(area.X, 				area.Y + halfheight, halfwidth, halfheight	);
        sections[sectionsLength++] = new Rectangle(area.X + halfwidth, 	area.Y-2, 			 halfwidth, halfheight+2);
        sections[sectionsLength++] = new Rectangle(area.X + halfwidth, 	area.Y + halfheight, halfwidth, halfheight	);
		if (cycleLeft) {
			sections.Slice(sectionsLength-4, 4).Reverse();
		}
      } else {	
		sections[sectionsLength++] = new Rectangle(area.X, 				area.Y-2 			 , area.Width, halfheight+2);		 
		sections[sectionsLength++] = new Rectangle(area.X, 				area.Y + halfheight-2, area.Width, halfheight+2);	
		if (cycleLeft) {
			sections.Slice(sectionsLength-2, 2).Reverse();
		}
      }
    }	 
	return sections.Slice(0, sectionsLength);
  }
  
  private static int GetNextSection(
	  IntPtr windowHandle, 
	  Span<Rectangle> allWorkAreas, 
	  Span<Rectangle> sections) 
  {
	Rectangle primary = BFS.Monitor.GetPrimaryMonitorWorkArea();
    Rectangle bounds = BFS.Window.GetBounds(windowHandle);
    Point midpoint = new Point(bounds.X + bounds.Width / 2, bounds.Y + bounds.Height / 2);  
	int idxNextSection = 0;
	for (int  i = 0; i < sections.Length; ++i) { 
		if (sections[i].Contains(midpoint)) { 
			idxNextSection = i;
			if (sections[i].FuzzyContains(bounds)) { 
				return (i + 1) % sections.Length;
			} else if (primary.Contains(midpoint)) { 
				// If you just undocked for instance, a chrome tab, 
				//  chances are you want it to immediately move out of the way to another monitor
				for (int j = 0; j <= sections.Length; ++j) {
					int idx = (i+j) % sections.Length;
					if (!primary.FuzzyContains(sections[idx])) {
						return idx;
					}
				}
			} 
			break;
		} 
	}
	return idxNextSection;	
  }

  
  private static bool TryFindUnoccupiedPosition(
	  Span<Rectangle> sections, 
	  Span<Rectangle> occupiedPositions, 
	  int idxSection, 
	  out int idxNextSection) 
  {
	idxNextSection = idxSection;
    for (int i = 0; i < sections.Length; ++i) {
	  idxNextSection = (idxSection + i) % sections.Length;
	  if (occupiedPositions.IndexOf(sections[idxNextSection]) == -1 ) { 
		  return true; 
	  }
    }		
	return false;
  }
	
  
  private static bool TryFindPositionUnoccupiedByCurrentApp(
	  IntPtr windowHandle, 
	  Span<IntPtr> existingWindowHandles,
	  Span<Rectangle> sections, 
	  Span<Rectangle> occupiedPositions, 
	  int idxSection, 
	  out int idxNextSection) 
  {
	string currentAppName = BFS.Application.GetMainFileByWindow(windowHandle);	
	idxNextSection = idxSection;
    for (int i = 0; i < sections.Length; ++i) {
	  idxNextSection = (idxSection + i) % sections.Length;
	  int otherAppIdx = occupiedPositions.IndexOf(sections[idxNextSection]);			
	  if (otherAppIdx >= 0) { 
		  string otherAppName = BFS.Application.GetMainFileByWindow(existingWindowHandles[otherAppIdx]);
		  if (currentAppName != otherAppName) {
			  return true;
		  }
	  }
    }		
	return false;
  }
 
  
  private static bool FuzzyContains(this Rectangle A, Rectangle B, int slopPx = 4) {
	return A.Contains(new Rectangle(B.X+slopPx/2, B.Y+slopPx/2, B.Width-slopPx, B.Height-slopPx));
  }
    
}