Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Automatic repositioning of the tray context menu (#86)
- Loading branch information
Showing
4 changed files
with
251 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,119 @@ | ||
using System.Collections.Generic; | ||
using System.Drawing; | ||
using KeeTrayTOTP.Libraries; | ||
using Microsoft.VisualStudio.TestTools.UnitTesting; | ||
|
||
namespace KeeTrayTOTP.Tests | ||
{ | ||
[TestClass] | ||
public class TrayContextMenuPositionTests | ||
{ | ||
[DataTestMethod] | ||
[DynamicData(nameof(DropDownLocationTestData))] | ||
public void CalculatedLocationForDropDown_Should_ReturnTheCorrectLocation(Size controlSize, Point mousePosition, | ||
Rectangle screenRect, Point expectedPoint) | ||
{ | ||
var dropDownLocationCalculator = new DropDownLocationCalculator(controlSize); | ||
var sut = dropDownLocationCalculator.CalculateLocationForDropDown(mousePosition, screenRect); | ||
|
||
Assert.AreEqual(expectedPoint, sut); | ||
} | ||
|
||
private static IEnumerable<object[]> DropDownLocationTestData | ||
{ | ||
get | ||
{ | ||
var controlSize = new Size(300, 300); | ||
var screenRect = new Rectangle(0, 0, 1920, 1080); | ||
|
||
// top-left | ||
yield return new object[] | ||
{ | ||
controlSize, new Point(400, 500), screenRect, | ||
// expected | ||
new Point(400 - controlSize.Width, 500 - controlSize.Height) | ||
}; | ||
|
||
// top-right | ||
yield return new object[] | ||
{ | ||
controlSize, new Point(200, 500), screenRect, | ||
// expected | ||
new Point(200, 500 - controlSize.Height) | ||
}; | ||
|
||
// bottom-right | ||
yield return new object[] | ||
{ | ||
controlSize, new Point(200, 200), screenRect, | ||
// expected | ||
new Point(200, 200) | ||
}; | ||
|
||
// bottom-left | ||
yield return new object[] | ||
{ | ||
controlSize, new Point(400, 200), screenRect, | ||
// expected | ||
new Point(400 - controlSize.Width, 200) | ||
}; | ||
|
||
// top-left | ||
yield return new object[] | ||
{ | ||
controlSize, new Point(300, 300), screenRect, | ||
// expected | ||
new Point(0, 0) | ||
}; | ||
|
||
// top-left | ||
yield return new object[] | ||
{ | ||
controlSize, new Point(screenRect.Width, screenRect.Height), screenRect, | ||
// expected | ||
new Point(screenRect.Width - controlSize.Width, screenRect.Height - controlSize.Height) | ||
}; | ||
|
||
// edge case control size bigger than screen size | ||
yield return new object[] | ||
{ | ||
controlSize, new Point(0, 0), new Rectangle(0, 0, 200, 200), | ||
// expected | ||
new Point(0, 200 - controlSize.Height) | ||
}; | ||
|
||
// edge case control height bigger than screen height | ||
yield return new object[] | ||
{ | ||
controlSize, new Point(50, 0), new Rectangle(0, 0, 1920, 200), | ||
// expected | ||
new Point(50, 200 - controlSize.Height) | ||
}; | ||
|
||
// edge case control width bigger than screen width | ||
yield return new object[] | ||
{ | ||
controlSize, new Point(0, 0), new Rectangle(0, 0, 200, 1080), | ||
// expected | ||
new Point(0, 0) | ||
}; | ||
|
||
// multi monitor test | ||
yield return new object[] | ||
{ | ||
controlSize, new Point(2500, 500), screenRect, | ||
// expected | ||
new Point(2500 - controlSize.Width, 500 - controlSize.Height) | ||
}; | ||
|
||
// multi monitor test control bigger than screen | ||
yield return new object[] | ||
{ | ||
controlSize, new Point(1200, 150), new Rectangle(1080, 0, 200, 200), | ||
// expected | ||
new Point(1080, 200 - controlSize.Height) | ||
}; | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,120 @@ | ||
using System.Drawing; | ||
using System.Windows.Forms; | ||
|
||
namespace KeeTrayTOTP.Libraries | ||
{ | ||
/// <summary> | ||
/// Calculator for the location of a dropdown control based on the location of the mouse and screen space available | ||
/// </summary> | ||
public class DropDownLocationCalculator | ||
{ | ||
private Size _controlSize; | ||
|
||
/// <summary> | ||
/// Initializes a calculator which can calculate the location of a dropdown control based on the location of the mouse | ||
/// and the screen space available. | ||
/// </summary> | ||
/// <param name="controlSize">The size of the control which will be dropdown-ed</param> | ||
public DropDownLocationCalculator(Size controlSize) | ||
{ | ||
_controlSize = controlSize; | ||
} | ||
|
||
/// <summary> | ||
/// Calculate the position for a dropdown control based on the given mouse position. | ||
/// The default behavior is to calculate the location for opening the dropdown to the top-left. | ||
/// </summary> | ||
/// <param name="mousePosition">The position in which the control will be anchored</param> | ||
/// <returns>The new location for the control</returns> | ||
internal Point CalculateLocationForDropDown(Point mousePosition) | ||
{ | ||
var screen = Screen.FromPoint(mousePosition); | ||
return CalculateLocationForDropDown(mousePosition, screen.Bounds); | ||
} | ||
|
||
/// <summary> | ||
/// Calculate the position for a dropdown control based on the given mouse position and screen rectangle | ||
/// The default behavior is to calculate the location for opening the dropdown to the top-left. | ||
/// </summary> | ||
/// <param name="mousePosition">The position in which the control will be anchored</param> | ||
/// <param name="screenRect"> | ||
/// The screen rectangle which is used for the calculation. This should be the screen on which the | ||
/// control will be opened | ||
/// </param> | ||
/// <returns></returns> | ||
internal Point CalculateLocationForDropDown(Point mousePosition, Rectangle screenRect) | ||
{ | ||
var screenHeight = screenRect.Height; | ||
var screenWidth = screenRect.Width; | ||
|
||
// after calculating the offset, the mouse position is relative to the screen containing the cursor | ||
// this allows for correct calculations even on multi-monitor systems | ||
mousePosition.Offset(-screenRect.X, -screenRect.Y); | ||
|
||
int calculatedX = CalculateX(mousePosition, screenWidth); | ||
int calculatedY = CalculateY(mousePosition, screenHeight); | ||
|
||
return new Point(calculatedX + screenRect.X, calculatedY + screenRect.Y); | ||
} | ||
|
||
private int CalculateY(Point mousePosition, int screenHeight) | ||
{ | ||
int calculatedY; | ||
if (IsEnoughSpaceToTheTop(mousePosition)) | ||
{ | ||
calculatedY = mousePosition.Y - _controlSize.Height; | ||
} | ||
else if (IsEnoughSpaceToTheBottom(mousePosition, screenHeight)) | ||
{ | ||
calculatedY = mousePosition.Y; | ||
} | ||
else | ||
{ | ||
// the control height is higher than the screen height | ||
calculatedY = screenHeight - _controlSize.Height; | ||
} | ||
|
||
return calculatedY; | ||
} | ||
|
||
private int CalculateX(Point mousePosition, int screenWidth) | ||
{ | ||
int calculatedX; | ||
if (IsEnoughSpaceToTheLeft(mousePosition)) | ||
{ | ||
calculatedX = mousePosition.X - _controlSize.Width; | ||
} | ||
else if (IsEnoughSpaceToTheRight(mousePosition, screenWidth)) | ||
{ | ||
calculatedX = mousePosition.X; | ||
} | ||
else | ||
{ | ||
// the control width is bigger than the screen width | ||
calculatedX = 0; | ||
} | ||
|
||
return calculatedX; | ||
} | ||
|
||
private bool IsEnoughSpaceToTheLeft(Point mousePosition) | ||
{ | ||
return (mousePosition.X - _controlSize.Width) >= 0; | ||
} | ||
|
||
private bool IsEnoughSpaceToTheTop(Point mousePosition) | ||
{ | ||
return (mousePosition.Y - _controlSize.Height) >= 0; | ||
} | ||
|
||
private bool IsEnoughSpaceToTheRight(Point mousePosition, int screenWidth) | ||
{ | ||
return (mousePosition.X + _controlSize.Width) <= screenWidth; | ||
} | ||
|
||
private bool IsEnoughSpaceToTheBottom(Point mousePosition, int screenHeight) | ||
{ | ||
return (mousePosition.Y + _controlSize.Height) <= screenHeight; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters