feat(界面): 实现遮罩窗口布局的相对比例定位

添加相对比例定位功能,将遮罩窗口中的状态列表和日志文本框从绝对坐标改为基于窗口宽高的比例坐标
新增OverlayRelativeOrAbsoluteConverter转换器处理坐标转换
修改相关视图模型和配置以支持比例定位
This commit is contained in:
辉鸭蛋
2026-01-17 22:07:27 +08:00
parent 73c142d33f
commit 4e24701b1d
5 changed files with 184 additions and 33 deletions

View File

@@ -1,4 +1,4 @@
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.ComponentModel;
using OpenCvSharp;
using System;
@@ -80,26 +80,26 @@ public partial class MaskWindowConfig : ObservableObject
private bool _overlayLayoutEditEnabled = false;
[ObservableProperty]
private double _logTextBoxLeft = 20;
private double _logTextBoxLeftRatio = 20.0 / 1920;
[ObservableProperty]
private double _logTextBoxTop = 832;
private double _logTextBoxTopRatio = 832.0 / 1080;
[ObservableProperty]
private double _logTextBoxWidth = 477;
private double _logTextBoxWidthRatio = 477.0 / 1920;
[ObservableProperty]
private double _logTextBoxHeight = 188;
private double _logTextBoxHeightRatio = 188.0 / 1080;
[ObservableProperty]
private double _statusListLeft = 20;
private double _statusListLeftRatio = 20.0 / 1920;
[ObservableProperty]
private double _statusListTop = 807;
private double _statusListTopRatio = 807.0 / 1080;
[ObservableProperty]
private double _statusListWidth = 477;
private double _statusListWidthRatio = 477.0 / 1920;
[ObservableProperty]
private double _statusListHeight = 24;
private double _statusListHeightRatio = 24.0 / 1080;
}

View File

@@ -0,0 +1,69 @@
using System;
using System.Globalization;
using System.Windows;
using System.Windows.Data;
namespace BetterGenshinImpact.View.Converters
{
public class OverlayRelativeOrAbsoluteConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object? parameter, CultureInfo culture)
{
if (values.Length < 2)
{
return DependencyProperty.UnsetValue;
}
if (values.Length == 2)
{
var ratio = ToDouble(values[0]);
var baseSize = ToDouble(values[1]);
if (double.IsNaN(ratio) || double.IsNaN(baseSize) || baseSize <= 0)
{
return DependencyProperty.UnsetValue;
}
return ratio * baseSize;
}
var ratio3 = ToDouble(values[0]);
var absolute = ToDouble(values[1]);
var baseSize3 = ToDouble(values[2]);
if (double.IsNaN(baseSize3) || baseSize3 <= 0)
{
return absolute;
}
if (!double.IsNaN(ratio3))
{
return ratio3 * baseSize3;
}
return absolute;
}
public object[] ConvertBack(object value, Type[] targetTypes, object? parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
private static double ToDouble(object value)
{
if (value == DependencyProperty.UnsetValue)
{
return double.NaN;
}
return value switch
{
double d => d,
float f => f,
int i => i,
long l => l,
_ => double.NaN
};
}
}
}

View File

@@ -8,6 +8,7 @@
xmlns:viewModel="clr-namespace:BetterGenshinImpact.ViewModel"
xmlns:platform="clr-namespace:BetterGenshinImpact.Platform.Wine"
xmlns:overlay="clr-namespace:BetterGenshinImpact.View.Controls.Overlay"
xmlns:converters="clr-namespace:BetterGenshinImpact.View.Converters"
Title="MaskWindow"
Width="500"
Height="800"
@@ -28,6 +29,9 @@
<b:EventTrigger EventName="Loaded">
<b:InvokeCommandAction Command="{Binding LoadedCommand}" CommandParameter="{Binding}" />
</b:EventTrigger>
<b:EventTrigger EventName="SizeChanged">
<b:InvokeCommandAction Command="{Binding WindowSizeChangedCommand}" PassEventArgsToCommand="True" />
</b:EventTrigger>
</b:Interaction.Triggers>
<Window.Background>
@@ -51,6 +55,7 @@
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="/View/Controls/Overlay/AdjustableOverlayItemStyles.xaml" />
</ResourceDictionary.MergedDictionaries>
<converters:OverlayRelativeOrAbsoluteConverter x:Key="OverlayRelativeOrAbsoluteConverter" />
</ResourceDictionary>
</Window.Resources>
@@ -78,10 +83,6 @@
Grid.ColumnSpan="6"
ClipToBounds="True">
<overlay:AdjustableOverlayItem x:Name="StatusWrapper"
Canvas.Left="{Binding Config.MaskWindowConfig.StatusListLeft, Mode=OneWay}"
Canvas.Top="{Binding Config.MaskWindowConfig.StatusListTop, Mode=OneWay}"
Width="{Binding Config.MaskWindowConfig.StatusListWidth, Mode=OneWay}"
Height="{Binding Config.MaskWindowConfig.StatusListHeight, Mode=OneWay}"
Padding="4,2"
Background="#00000000"
BorderBrush="#33000000"
@@ -91,6 +92,30 @@
IsEditEnabled="{Binding Config.MaskWindowConfig.OverlayLayoutEditEnabled}"
LayoutKey="StatusList"
Visibility="{Binding Config.MaskWindowConfig.ShowStatus, Converter={StaticResource BooleanToVisibilityConverter}}">
<Canvas.Left>
<MultiBinding Converter="{StaticResource OverlayRelativeOrAbsoluteConverter}">
<Binding Path="Config.MaskWindowConfig.StatusListLeftRatio" />
<Binding RelativeSource="{RelativeSource AncestorType=Window}" Path="ActualWidth" />
</MultiBinding>
</Canvas.Left>
<Canvas.Top>
<MultiBinding Converter="{StaticResource OverlayRelativeOrAbsoluteConverter}">
<Binding Path="Config.MaskWindowConfig.StatusListTopRatio" />
<Binding RelativeSource="{RelativeSource AncestorType=Window}" Path="ActualHeight" />
</MultiBinding>
</Canvas.Top>
<overlay:AdjustableOverlayItem.Width>
<MultiBinding Converter="{StaticResource OverlayRelativeOrAbsoluteConverter}">
<Binding Path="Config.MaskWindowConfig.StatusListWidthRatio" />
<Binding RelativeSource="{RelativeSource AncestorType=Window}" Path="ActualWidth" />
</MultiBinding>
</overlay:AdjustableOverlayItem.Width>
<overlay:AdjustableOverlayItem.Height>
<MultiBinding Converter="{StaticResource OverlayRelativeOrAbsoluteConverter}">
<Binding Path="Config.MaskWindowConfig.StatusListHeightRatio" />
<Binding RelativeSource="{RelativeSource AncestorType=Window}" Path="ActualHeight" />
</MultiBinding>
</overlay:AdjustableOverlayItem.Height>
<b:Interaction.Triggers>
<b:EventTrigger EventName="LayoutCommitted">
<b:InvokeCommandAction Command="{Binding OverlayLayoutCommittedCommand}" PassEventArgsToCommand="True" />
@@ -156,10 +181,6 @@
</overlay:AdjustableOverlayItem>
<overlay:AdjustableOverlayItem x:Name="LogTextBoxWrapper"
Canvas.Left="{Binding Config.MaskWindowConfig.LogTextBoxLeft, Mode=OneWay}"
Canvas.Top="{Binding Config.MaskWindowConfig.LogTextBoxTop, Mode=OneWay}"
Width="{Binding Config.MaskWindowConfig.LogTextBoxWidth, Mode=OneWay}"
Height="{Binding Config.MaskWindowConfig.LogTextBoxHeight, Mode=OneWay}"
Padding="0"
Background="#00000000"
BorderBrush="#33000000"
@@ -169,6 +190,30 @@
IsEditEnabled="{Binding Config.MaskWindowConfig.OverlayLayoutEditEnabled}"
LayoutKey="LogTextBox"
Visibility="{Binding Config.MaskWindowConfig.ShowLogBox, Converter={StaticResource BooleanToVisibilityConverter}}">
<Canvas.Left>
<MultiBinding Converter="{StaticResource OverlayRelativeOrAbsoluteConverter}">
<Binding Path="Config.MaskWindowConfig.LogTextBoxLeftRatio" />
<Binding RelativeSource="{RelativeSource AncestorType=Window}" Path="ActualWidth" />
</MultiBinding>
</Canvas.Left>
<Canvas.Top>
<MultiBinding Converter="{StaticResource OverlayRelativeOrAbsoluteConverter}">
<Binding Path="Config.MaskWindowConfig.LogTextBoxTopRatio" />
<Binding RelativeSource="{RelativeSource AncestorType=Window}" Path="ActualHeight" />
</MultiBinding>
</Canvas.Top>
<overlay:AdjustableOverlayItem.Width>
<MultiBinding Converter="{StaticResource OverlayRelativeOrAbsoluteConverter}">
<Binding Path="Config.MaskWindowConfig.LogTextBoxWidthRatio" />
<Binding RelativeSource="{RelativeSource AncestorType=Window}" Path="ActualWidth" />
</MultiBinding>
</overlay:AdjustableOverlayItem.Width>
<overlay:AdjustableOverlayItem.Height>
<MultiBinding Converter="{StaticResource OverlayRelativeOrAbsoluteConverter}">
<Binding Path="Config.MaskWindowConfig.LogTextBoxHeightRatio" />
<Binding RelativeSource="{RelativeSource AncestorType=Window}" Path="ActualHeight" />
</MultiBinding>
</overlay:AdjustableOverlayItem.Height>
<b:Interaction.Triggers>
<b:EventTrigger EventName="LayoutCommitted">
<b:InvokeCommandAction Command="{Binding OverlayLayoutCommittedCommand}" PassEventArgsToCommand="True" />

View File

@@ -27,6 +27,10 @@ namespace BetterGenshinImpact.ViewModel
[ObservableProperty] private string _fps = "0";
[ObservableProperty] private double _maskWindowWidth;
[ObservableProperty] private double _maskWindowHeight;
public MaskWindowViewModel()
{
WeakReferenceMessenger.Default.Register<PropertyChangedMessage<object>>(this, (sender, msg) =>
@@ -111,23 +115,40 @@ namespace BetterGenshinImpact.ViewModel
return;
}
if (MaskWindowWidth <= 0 || MaskWindowHeight <= 0)
{
return;
}
var leftRatio = ToRatio(args.Left, MaskWindowWidth);
var topRatio = ToRatio(args.Top, MaskWindowHeight);
var widthRatio = ToRatio(args.Width, MaskWindowWidth);
var heightRatio = ToRatio(args.Height, MaskWindowHeight);
switch (args.LayoutKey)
{
case "LogTextBox":
Config.MaskWindowConfig.LogTextBoxLeft = args.Left;
Config.MaskWindowConfig.LogTextBoxTop = args.Top;
Config.MaskWindowConfig.LogTextBoxWidth = args.Width;
Config.MaskWindowConfig.LogTextBoxHeight = args.Height;
Config.MaskWindowConfig.LogTextBoxLeftRatio = leftRatio;
Config.MaskWindowConfig.LogTextBoxTopRatio = topRatio;
Config.MaskWindowConfig.LogTextBoxWidthRatio = widthRatio;
Config.MaskWindowConfig.LogTextBoxHeightRatio = heightRatio;
break;
case "StatusList":
Config.MaskWindowConfig.StatusListLeft = args.Left;
Config.MaskWindowConfig.StatusListTop = args.Top;
Config.MaskWindowConfig.StatusListWidth = args.Width;
Config.MaskWindowConfig.StatusListHeight = args.Height;
Config.MaskWindowConfig.StatusListLeftRatio = leftRatio;
Config.MaskWindowConfig.StatusListTopRatio = topRatio;
Config.MaskWindowConfig.StatusListWidthRatio = widthRatio;
Config.MaskWindowConfig.StatusListHeightRatio = heightRatio;
break;
}
}
[RelayCommand]
private void OnWindowSizeChanged(SizeChangedEventArgs args)
{
MaskWindowWidth = args.NewSize.Width;
MaskWindowHeight = args.NewSize.Height;
}
[RelayCommand]
private void OnExitOverlayLayoutEditMode()
{
@@ -139,5 +160,21 @@ namespace BetterGenshinImpact.ViewModel
Config.MaskWindowConfig.OverlayLayoutEditEnabled = false;
SystemControl.ActivateWindow();
}
private static double ToRatio(double value, double baseSize)
{
if (double.IsNaN(value) || double.IsNaN(baseSize) || baseSize <= 0)
{
return 0;
}
var ratio = value / baseSize;
return ratio switch
{
< 0 => 0,
> 1 => 1,
_ => ratio
};
}
}
}

View File

@@ -213,15 +213,15 @@ public partial class CommonSettingsPageViewModel : ViewModel
private void OnResetMaskOverlayLayout()
{
var c = Config.MaskWindowConfig;
c.StatusListLeft = 20;
c.StatusListTop = 807;
c.StatusListWidth = 477;
c.StatusListHeight = 24;
c.StatusListLeftRatio = 20.0 / 1920;
c.StatusListTopRatio = 807.0 / 1080;
c.StatusListWidthRatio = 477.0 / 1920;
c.StatusListHeightRatio = 24.0 / 1080;
c.LogTextBoxLeft = 20;
c.LogTextBoxTop = 832;
c.LogTextBoxWidth = 477;
c.LogTextBoxHeight = 188;
c.LogTextBoxLeftRatio = 20.0 / 1920;
c.LogTextBoxTopRatio = 832.0 / 1080;
c.LogTextBoxWidthRatio = 477.0 / 1920;
c.LogTextBoxHeightRatio = 188.0 / 1080;
OnRefreshMaskSettings();
}