Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix Window Positioning with Multiple Montiors #2756

Merged
merged 24 commits into from
Jun 25, 2024

Conversation

onesounds
Copy link
Contributor

@onesounds onesounds commented Jun 4, 2024

Review Guide

  • You can review it in 30 minutes or less. (Repeated resolution changes are required to test this pr.)
  • This code affects the handling of window positioning with "remember last position"
  • It is a combination of two things: bug correction and feature improvement.

What's the PR

  • Fixed handling when monitors are arranged vertically
  • When you change the resolution or dpi after the program ends, adjust the resetting and main windows so that they are not located outside the screen.
    • I usually set this to "last remember position" because it's the most convenient, but it was very annoying when the screen resolution changes when using moonlight.

Test Cases

  • After the program ends, change the horizontal resolution to be narrower, and then the window position does not deviate from the screen when the program is executed.

    1. Select the highest resolution in the monitor.
    2. Run flow and place the window (main window and settings window) in the upper right corner.
    3. Exit the program.
    4. Change the system resolution to one less than the 1 setting.
    5. Run flow and open the flow and settings windows. The location of each window should be within the screen, not outside the monitor.
  • Remember last position should operate as before

  • Other settings other than the last position setting must operate normally.

  • When you select Top-Left/Center-Top/Right as the custom monitor position in a vertical dual-monitor alignment, both monitors should display in their normal positions.

  • Both the setup window and the main window must work.

ETC

  • This pr initially tried to readjust the window position slightly to the ratio at the time of resolution change, but decided that it was difficult to adjust it to the ratio. Basically, when changing the resolution, it is readjusted so that the window is located inside, so this is not handled separately.
  • However, if the resolution changes after the program is terminated, the window may be located outside according to the existing logic, so this part has been improved.

Summary by CodeRabbit

  • New Features

    • Enhanced screen resolution and DPI settings, allowing better handling of window positioning across different screen dimensions.
  • Bug Fixes

    • Improved window initialization logic to adapt to changes in screen resolution and DPI, preventing positioning issues when screens are resized or display settings are altered.

@onesounds onesounds added bug Something isn't working enhancement New feature or request labels Jun 4, 2024
@onesounds onesounds self-assigned this Jun 4, 2024
Comment on lines 52 to 55
private double _previousScreenWidth;
private double _previousScreenHeight;
private double _previousDpiX;
private double _previousDpiY;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Store previously used resolution and dpi values.

Comment on lines -309 to +336
Top = 10;
Top = VerticalTop(screen);
Copy link
Contributor Author

@onesounds onesounds Jun 4, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because this value is a fixed value (not related screen number), there is a problem with vertical alignment monitor situation. This code will try to locate the window and set the position with screen

Comment on lines 310 to 316
if (!(
_previousScreenWidth == currentScreenWidth &&
_previousScreenHeight == currentScreenHeight &&
_previousDpiX == currentDpi.X &&
_previousDpiY == currentDpi.Y
))
{
Copy link
Contributor Author

@onesounds onesounds Jun 4, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This line means, “If anything is different from the previous information”

_previousDpiY == currentDpi.Y
))
{
if (_previousScreenWidth != 0 && _previousScreenHeight != 0 && _previousDpiX != 0 && _previousDpiY != 0)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure why it's there...unless they're all non-zero. chatgpt wrote.

Comment on lines 319 to 325
double widthRatio = currentScreenWidth / _previousScreenWidth;
double heightRatio = currentScreenHeight / _previousScreenHeight;
double dpiXRatio = currentDpi.X / _previousDpiX;
double dpiYRatio = currentDpi.Y / _previousDpiY;

_settings.WindowLeft *= widthRatio * dpiXRatio;
_settings.WindowTop *= heightRatio * dpiYRatio;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rescales previously stored values to a ratio.

@onesounds
Copy link
Contributor Author

onesounds commented Jun 5, 2024

This function is intended to be applied when the resolution is changed even when the program is turned off. It didn't work well, so I stopped. I'll think about the logic a little more.

@Yusyuriv
Copy link
Member

Yusyuriv commented Jun 5, 2024

This PR breaks these settings.
image

onesounds added 4 commits June 6, 2024 06:02
This reverts commit 45dd84a.
…ution or dpi has changed, the position will be rescaled proportionally"

This reverts commit 63ce735.
@onesounds
Copy link
Contributor Author

onesounds commented Jun 5, 2024

This PR breaks these settings. image

Reworking...

_settings.PreviousDpiY = GetDpiY();

// If previous screen width, height, DPI X, or DPI Y are not zero
if (previousScreenWidth != 0 && previousScreenHeight != 0 &&
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what does it mean to be 0?

Copy link

gitstream-cm bot commented Jun 16, 2024

Be a legend 🏆 by adding a before and after screenshot of the changes you made, especially if they are around UI/UX.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

Outside diff range and nitpick comments (2)
Flow.Launcher/SettingWindow.xaml.cs (2)

153-153: Validate the calculation for window left position.

Ensure that the calculation for the window's left position correctly handles different screen resolutions and DPI settings. It's crucial that this calculation is accurate to prevent the window from appearing off-screen in multi-monitor setups.


162-162: Validate the calculation for window top position.

Similar to the left position, validate that the top position calculation is robust against various screen sizes and configurations, particularly in vertical multi-monitor setups where the Y-coordinate can significantly vary.

Comment on lines +115 to +122
SetWindowPosition(WindowTop(), WindowLeft());
}
else
{
Top = _settings.SettingWindowTop.Value;
Left = _settings.SettingWindowLeft.Value;
double left = _settings.SettingWindowLeft.Value;
double top = _settings.SettingWindowTop.Value;
AdjustWindowPosition(ref top, ref left);
SetWindowPosition(top, left);
Copy link
Contributor

@coderabbitai coderabbitai bot Jun 16, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ensure proper handling of null values for window position settings.

Given that _settings.SettingWindowTop and _settings.SettingWindowLeft can potentially be null, it's crucial to ensure they are handled properly before using their Value property directly in lines 119 and 120. This could potentially throw a InvalidOperationException if they are null when accessed.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe the condition in above line 113 needs to be 'and' not ''or'? Because one of them could be a null and still passes through the condition check.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jjw24 You're absolutely right that changing the condition to 'and' would ensure that we don't attempt to use a null value inadvertently, which would enhance the robustness of the code. However, we should also consider scenarios where only one of the two settings (_settings.SettingWindowTop or _settings.SettingWindowLeft) is null. In such cases, we might still want to adjust the position based on the non-null value. Here's a modified approach that could handle both scenarios:

public void InitializePosition()
{
    double? top = _settings.SettingWindowTop;
    double? left = _settings.SettingWindowLeft;

    if (top == null || left == null)
    {
        // If either is null, calculate both to ensure the window is positioned safely
        top = WindowTop();
        left = WindowLeft();
    }

    // Now we can safely assume both top and left are not null
    AdjustWindowPosition(ref top.Value, ref left.Value);
    SetWindowPosition(top.Value, left.Value);
}

This approach ensures that if either top or left is null, both values are recalculated, thus keeping the window within safe screen boundaries. What do you think of this implementation?

Comment on lines +139 to +146
private void AdjustWindowPosition(ref double top, ref double left)
{
// Adjust window position if it exceeds screen boundaries
top = Math.Max(top, SystemParameters.VirtualScreenTop);
left = Math.Max(left, SystemParameters.VirtualScreenLeft);
top = Math.Min(top, SystemParameters.VirtualScreenHeight - ActualHeight);
left = Math.Min(left, SystemParameters.VirtualScreenWidth - ActualWidth);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Optimize the adjustment of window position to ensure it remains within screen boundaries.

Similar to the previous comment, this logic is duplicated and should be handled by a single helper method to ensure consistency and reduce errors.

Comment on lines +127 to +137
private void SetWindowPosition(double top, double left)
{
// Ensure window does not exceed screen boundaries
top = Math.Max(top, SystemParameters.VirtualScreenTop);
left = Math.Max(left, SystemParameters.VirtualScreenLeft);
top = Math.Min(top, SystemParameters.VirtualScreenHeight - ActualHeight);
left = Math.Min(left, SystemParameters.VirtualScreenWidth - ActualWidth);

Top = top;
Left = left;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Refactor window boundary checks to a helper method to avoid code duplication.

The code for ensuring the window does not exceed screen boundaries is duplicated in both SetWindowPosition and AdjustWindowPosition. Consider refactoring this into a helper method to reduce redundancy and improve maintainability.

- top = Math.Max(top, SystemParameters.VirtualScreenTop);
- left = Math.Max(left, SystemParameters.VirtualScreenLeft);
- top = Math.Min(top, SystemParameters.VirtualScreenHeight - ActualHeight);
- left = Math.Min(left, SystemParameters.VirtualScreenWidth - ActualWidth);
+ EnsureWithinScreenBounds(ref top, ref left);

Committable suggestion was skipped due to low confidence.

Comment on lines 208 to 211
public double PreviousScreenWidth { get; set; }
public double PreviousScreenHeight { get; set; }
public double PreviousDpiX { get; set; }
public double PreviousDpiY { get; set; }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Properly initialize new settings properties to default values.

The newly added properties PreviousScreenWidth, PreviousScreenHeight, PreviousDpiX, and PreviousDpiY should be initialized to sensible defaults to prevent issues when they are first accessed before being explicitly set.

+ public double PreviousScreenWidth { get; set; } = 0;
+ public double PreviousScreenHeight { get; set; } = 0;
+ public double PreviousDpiX { get; set; } = 96; // Assuming default DPI
+ public double PreviousDpiY { get; set; } = 96; // Assuming default DPI
Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
public double PreviousScreenWidth { get; set; }
public double PreviousScreenHeight { get; set; }
public double PreviousDpiX { get; set; }
public double PreviousDpiY { get; set; }
public double PreviousScreenWidth { get; set; } = 0;
public double PreviousScreenHeight { get; set; } = 0;
public double PreviousDpiX { get; set; } = 96; // Assuming default DPI
public double PreviousDpiY { get; set; } = 96; // Assuming default DPI

Comment on lines 296 to 317
double previousScreenWidth = _settings.PreviousScreenWidth;
double previousScreenHeight = _settings.PreviousScreenHeight;
double previousDpiX, previousDpiY;
GetDpi(out previousDpiX, out previousDpiY);

_settings.PreviousScreenWidth = SystemParameters.VirtualScreenWidth;
_settings.PreviousScreenHeight = SystemParameters.VirtualScreenHeight;
double currentDpiX, currentDpiY;
GetDpi(out currentDpiX, out currentDpiY);

if (previousScreenWidth != 0 && previousScreenHeight != 0 &&
previousDpiX != 0 && previousDpiY != 0 &&
(previousScreenWidth != SystemParameters.VirtualScreenWidth ||
previousScreenHeight != SystemParameters.VirtualScreenHeight ||
previousDpiX != currentDpiX || previousDpiY != currentDpiY))
{
AdjustPositionForResolutionChange();
return;
}

Left = _settings.WindowLeft;
Top = _settings.WindowTop;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ensure DPI and screen size changes are handled accurately.

The logic to handle resolution and DPI changes appears complex and could benefit from additional error handling and validation to ensure it behaves correctly across all scenarios, especially with unusual DPI settings or rapid changes in configuration.

Comment on lines 350 to 377
private void AdjustPositionForResolutionChange()
{
double screenWidth = SystemParameters.VirtualScreenWidth;
double screenHeight = SystemParameters.VirtualScreenHeight;
double currentDpiX, currentDpiY;
GetDpi(out currentDpiX, out currentDpiY);

double previousLeft = _settings.WindowLeft;
double previousTop = _settings.WindowTop;
double previousDpiX, previousDpiY;
GetDpi(out previousDpiX, out previousDpiY);

double widthRatio = screenWidth / _settings.PreviousScreenWidth;
double heightRatio = screenHeight / _settings.PreviousScreenHeight;
double dpiXRatio = currentDpiX / previousDpiX;
double dpiYRatio = currentDpiY / previousDpiY;

double newLeft = previousLeft * widthRatio * dpiXRatio;
double newTop = previousTop * heightRatio * dpiYRatio;

double screenLeft = SystemParameters.VirtualScreenLeft;
double screenTop = SystemParameters.VirtualScreenTop;

double maxX = screenLeft + screenWidth - ActualWidth;
double maxY = screenTop + screenHeight - ActualHeight;

Left = Math.Max(screenLeft, Math.Min(newLeft, maxX));
Top = Math.Max(screenTop, Math.Min(newTop, maxY));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Refactor resolution change adjustment logic to enhance clarity and reduce complexity.

The method AdjustPositionForResolutionChange contains complex calculations that could be simplified or broken down into smaller, more manageable methods. This would improve readability and maintainability.

- double widthRatio = screenWidth / _settings.PreviousScreenWidth;
- double heightRatio = screenHeight / _settings.PreviousScreenHeight;
- double dpiXRatio = currentDpiX / previousDpiX;
- double dpiYRatio = currentDpiY / previousDpiY;
+ CalculateRatios(screenWidth, screenHeight, currentDpiX, currentDpiY);

Committable suggestion was skipped due to low confidence.

Comment on lines 797 to 802
public double VerticalTop(Screen screen)
{
var dip1 = WindowsInteropHelper.TransformPixelsToDIP(this, 0, screen.WorkingArea.Y);
var top = dip1.Y +10;
return top;
}
Copy link
Contributor

@coderabbitai coderabbitai bot Jun 16, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ensure vertical positioning takes into account taskbar and window decorations.

The method VerticalTop calculates the top position but does not account for potential obstructions like the taskbar or window decorations that might obscure the window. Consider adjusting the calculation to ensure the window is fully visible.

- var top = dip1.Y +10;
+ var top = Math.Max(dip1.Y + 10, SystemParameters.WorkArea.Top);
Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
public double VerticalTop(Screen screen)
{
var dip1 = WindowsInteropHelper.TransformPixelsToDIP(this, 0, screen.WorkingArea.Y);
var top = dip1.Y +10;
return top;
}
public double VerticalTop(Screen screen)
{
var dip1 = WindowsInteropHelper.TransformPixelsToDIP(this, 0, screen.WorkingArea.Y);
var top = Math.Max(dip1.Y + 10, SystemParameters.WorkArea.Top);
return top;
}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe you want Math.Min?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@taooceros, you're right to consider Math.Min in the context of taskbar positioning, especially if the taskbar is at the top of the screen. Using Math.Min would ensure that the window does not overlap with a top-positioned taskbar by limiting the window's top boundary to be no lower than the top of the work area.

Here's the adjusted method with Math.Min:

public double VerticalTop(Screen screen)
{
    var dip1 = WindowsInteropHelper.TransformPixelsToDIP(this, 0, screen.WorkingArea.Y);
    var top = Math.Min(dip1.Y + 10, SystemParameters.WorkArea.Top);
    return top;
}

This change will make sure that the window respects the upper boundary of the work area, which is particularly useful if the taskbar is placed at the top of the screen. If this modification suits the typical setup for your users, it would indeed be a beneficial change.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry I am wrong.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@taooceros, no worries at all! It's great that we're on the same page now regarding the positioning. Using Math.Max ensures that the window does not overlap with a top-positioned taskbar, maintaining visibility and accessibility. If there's anything else you'd like to discuss or adjust, feel free to let me know! 😊

@onesounds
Copy link
Contributor Author

onesounds commented Jun 18, 2024

@onesounds is this ready for review?

  1. This PR was reviewable by my standards.

  2. @taooceros said he would fix some things. and commented like this.
    image
    https://stackoverflow.com/questions/60872044/how-to-get-scaling-factor-for-each-monitor-e-g-1-1-25-1-5/60987055#60987055

  3. I did not understand what the problem was that Taoo found and what they were trying to fix. I don't know what to do about this PR because there has been no comment from Taoo since then.

  4. I'm sure he just forgot about this task. This is a classic Taoo way of interrupting someone else's work, so we might as well just review and merge it and let him fix it later.

This comment has been minimized.

@taooceros
Copy link
Member

The problem I found exists in dev branch. I will try to fix that later in another pr. I will review this pr first.

This comment has been minimized.

This comment has been minimized.

Copy link

gitstream-cm bot commented Jun 19, 2024

🥷 Code experts: Yusyuriv, VictoriousRaptor

onesounds, Yusyuriv have most 👩‍💻 activity in the files.
onesounds, Yusyuriv have most 🧠 knowledge in the files.

See details

Flow.Launcher.Infrastructure/UserSettings/Settings.cs

Activity based on git-commit:

onesounds Yusyuriv
JUN
MAY 24 additions & 6 deletions 6 additions & 22 deletions
APR 12 additions & 2 deletions 126 additions & 49 deletions
MAR
FEB
JAN

Knowledge based on git-blame:
onesounds: 25%
Yusyuriv: 14%

Flow.Launcher/MainWindow.xaml.cs

Activity based on git-commit:

onesounds Yusyuriv
JUN
MAY 128 additions & 21 deletions 12 additions & 22 deletions
APR 37 additions & 22 deletions
MAR
FEB
JAN

Knowledge based on git-blame:
onesounds: 38%
Yusyuriv: 1%

Flow.Launcher/SettingWindow.xaml.cs

Activity based on git-commit:

onesounds Yusyuriv
JUN
MAY 9 additions & 9 deletions 132 additions & 501 deletions
APR 115 additions & 91 deletions 22 additions & 43 deletions
MAR
FEB
JAN

Knowledge based on git-blame:
Yusyuriv: 74%
onesounds: 15%

Flow.Launcher/ViewModel/MainViewModel.cs

Activity based on git-commit:

onesounds Yusyuriv
JUN 16 additions & 1 deletions
MAY 115 additions & 28 deletions 23 additions & 12 deletions
APR 70 additions & 4 deletions
MAR
FEB
JAN

Knowledge based on git-blame:
onesounds: 11%
Yusyuriv: 2%

To learn more about /:\ gitStream - Visit our Docs

Copy link

gitstream-cm bot commented Jun 19, 2024

This PR is 74.04580152671755% new code.

This comment has been minimized.


var currentDpi = GetDpi();

if (previousScreen == default ||
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should reset the position if no previous record is found.

This comment has been minimized.

@taooceros
Copy link
Member

@onesounds I made some change to group Left and Top operation mainly without change of logic except some very obvious one. You can take a look and merge as you wish.

Copy link

@check-spelling-bot Report

🔴 Please review

See the 📂 files view, the 📜action log, or 📝 job summary for details.

❌ Errors Count
❌ ignored-expect-variant 1
⚠️ non-alpha-in-dictionary 13

See ❌ Event descriptions for more information.

If the flagged items are 🤯 false positives

If items relate to a ...

  • binary file (or some other file you wouldn't want to check at all).

    Please add a file path to the excludes.txt file matching the containing file.

    File paths are Perl 5 Regular Expressions - you can test yours before committing to verify it will match your files.

    ^ refers to the file's path from the root of the repository, so ^README\.md$ would exclude README.md (on whichever branch you're using).

  • well-formed pattern.

    If you can write a pattern that would match it,
    try adding it to the patterns.txt file.

    Patterns are Perl 5 Regular Expressions - you can test yours before committing to verify it will match your lines.

    Note that patterns can't match multiline strings.

@onesounds onesounds merged commit 3830453 into Flow-Launcher:dev Jun 25, 2024
4 checks passed
@jjw24 jjw24 removed 10 min review enhancement New feature or request labels Jun 26, 2024
@jjw24 jjw24 added this to the 1.19.0 milestone Jun 26, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
Status: Done
Development

Successfully merging this pull request may close these issues.

BUG: Wrong positioning on multimonitor setup
4 participants