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