Processing Ajax...

Title

Message

Confirm

Confirm

Confirm

Confirm

Are you sure you want to delete this item?

Confirm

Are you sure you want to delete this item?

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));
  }
    
}