modified each other mode has own side and offset

This commit is contained in:
2026-08-08 23:27:28 +05:00
parent 5ecc46f7a3
commit 2af494d2f6
7 changed files with 110 additions and 35 deletions
+11 -1
View File
@@ -17,13 +17,23 @@ public sealed partial class AppSettings : ObservableObject
[ObservableProperty] [ObservableProperty]
private PopupPlacementMode _placementMode = PopupPlacementMode.AtCursor; private PopupPlacementMode _placementMode = PopupPlacementMode.AtCursor;
// Сторона и отступ хранятся отдельно для каждого режима: у курсора и у
// каретки удобны разные настройки, и переключение режима их не сбрасывает
[ObservableProperty] [ObservableProperty]
private CursorCorner _cursorCorner = CursorCorner.BottomRight; private AnchorSide _cursorSide = AnchorSide.BottomRight;
/// <summary>Отступ от курсора в единицах WPF.</summary> /// <summary>Отступ от курсора в единицах WPF.</summary>
[ObservableProperty] [ObservableProperty]
private double _cursorOffset = 16; private double _cursorOffset = 16;
[ObservableProperty]
private AnchorSide _caretSide = AnchorSide.BottomRight;
/// <summary>Отступ от каретки в единицах WPF.</summary>
[ObservableProperty]
private double _caretOffset = 16;
[ObservableProperty] [ObservableProperty]
private ScreenPosition _screenPosition = ScreenPosition.BottomRight; private ScreenPosition _screenPosition = ScreenPosition.BottomRight;
+6 -5
View File
@@ -21,13 +21,14 @@ public enum PopupPlacementMode
/// <summary> /// <summary>
/// С какой стороны от курсора или каретки показывать подсказку. /// С какой стороны от курсора или каретки показывать подсказку.
/// </summary> /// </summary>
public enum AnchorSide
public enum CursorCorner
{ {
BottomRight,
BottomLeft,
TopRight,
TopLeft, TopLeft,
TopRight,
Left,
Right,
BottomLeft,
BottomRight,
} }
/// <summary> /// <summary>
+10 -4
View File
@@ -85,18 +85,24 @@
<data name="CursorCornerLabel" xml:space="preserve"> <data name="CursorCornerLabel" xml:space="preserve">
<value>Side</value> <value>Side</value>
</data> </data>
<data name="CursorCorner_BottomRight" xml:space="preserve"> <data name="AnchorSide_BottomRight" xml:space="preserve">
<value>Bottom right</value> <value>Bottom right</value>
</data> </data>
<data name="CursorCorner_BottomLeft" xml:space="preserve"> <data name="AnchorSide_BottomLeft" xml:space="preserve">
<value>Bottom left</value> <value>Bottom left</value>
</data> </data>
<data name="CursorCorner_TopRight" xml:space="preserve"> <data name="AnchorSide_TopRight" xml:space="preserve">
<value>Top right</value> <value>Top right</value>
</data> </data>
<data name="CursorCorner_TopLeft" xml:space="preserve"> <data name="AnchorSide_TopLeft" xml:space="preserve">
<value>Top left</value> <value>Top left</value>
</data> </data>
<data name="AnchorSide_Left" xml:space="preserve">
<value>Left</value>
</data>
<data name="AnchorSide_Right" xml:space="preserve">
<value>Right</value>
</data>
<data name="CursorOffsetLabel" xml:space="preserve"> <data name="CursorOffsetLabel" xml:space="preserve">
<value>Offset</value> <value>Offset</value>
</data> </data>
+10 -4
View File
@@ -85,18 +85,24 @@
<data name="CursorCornerLabel" xml:space="preserve"> <data name="CursorCornerLabel" xml:space="preserve">
<value>Сторона</value> <value>Сторона</value>
</data> </data>
<data name="CursorCorner_BottomRight" xml:space="preserve"> <data name="AnchorSide_BottomRight" xml:space="preserve">
<value>Справа снизу</value> <value>Справа снизу</value>
</data> </data>
<data name="CursorCorner_BottomLeft" xml:space="preserve"> <data name="AnchorSide_BottomLeft" xml:space="preserve">
<value>Слева снизу</value> <value>Слева снизу</value>
</data> </data>
<data name="CursorCorner_TopRight" xml:space="preserve"> <data name="AnchorSide_TopRight" xml:space="preserve">
<value>Справа сверху</value> <value>Справа сверху</value>
</data> </data>
<data name="CursorCorner_TopLeft" xml:space="preserve"> <data name="AnchorSide_TopLeft" xml:space="preserve">
<value>Слева сверху</value> <value>Слева сверху</value>
</data> </data>
<data name="AnchorSide_Left" xml:space="preserve">
<value>Слева</value>
</data>
<data name="AnchorSide_Right" xml:space="preserve">
<value>Справа</value>
</data>
<data name="CursorOffsetLabel" xml:space="preserve"> <data name="CursorOffsetLabel" xml:space="preserve">
<value>Отступ</value> <value>Отступ</value>
</data> </data>
+3 -3
View File
@@ -63,7 +63,7 @@ public sealed partial class SettingsViewModel : ObservableObject, IDisposable
Localization.PropertyChanged += OnLocalizationChanged; Localization.PropertyChanged += OnLocalizationChanged;
PlacementModes = CreateOptions<PopupPlacementMode>(); PlacementModes = CreateOptions<PopupPlacementMode>();
CursorCorners = CreateOptions<CursorCorner>(); AnchorSides = CreateOptions<AnchorSide>();
ScreenPositions = CreateOptions<ScreenPosition>(); ScreenPositions = CreateOptions<ScreenPosition>();
} }
@@ -75,7 +75,7 @@ public sealed partial class SettingsViewModel : ObservableObject, IDisposable
public IReadOnlyList<EnumOption<PopupPlacementMode>> PlacementModes { get; } public IReadOnlyList<EnumOption<PopupPlacementMode>> PlacementModes { get; }
public IReadOnlyList<EnumOption<CursorCorner>> CursorCorners { get; } public IReadOnlyList<EnumOption<AnchorSide>> AnchorSides { get; }
public IReadOnlyList<EnumOption<ScreenPosition>> ScreenPositions { get; } public IReadOnlyList<EnumOption<ScreenPosition>> ScreenPositions { get; }
@@ -103,7 +103,7 @@ public sealed partial class SettingsViewModel : ObservableObject, IDisposable
} }
Translate(PlacementModes); Translate(PlacementModes);
Translate(CursorCorners); Translate(AnchorSides);
Translate(ScreenPositions); Translate(ScreenPositions);
} }
+20 -5
View File
@@ -109,15 +109,30 @@ public partial class LayoutPopupWindow : Window
var anchorPoint = new PopupWindowNative.Point { X = anchor.Left, Y = anchor.Top }; var anchorPoint = new PopupWindowNative.Point { X = anchor.Left, Y = anchor.Top };
double scale = PopupWindowNative.GetScaleAt(anchorPoint); double scale = PopupWindowNative.GetScaleAt(anchorPoint);
// У каждого режима привязки своя сторона и свой отступ
bool atCaret = _settings.PlacementMode == PopupPlacementMode.AtCaret;
AnchorSide side = atCaret ? _settings.CaretSide : _settings.CursorSide;
int offset = ToPixels(atCaret ? _settings.CaretOffset : _settings.CursorOffset, scale);
int width = ToPixels(_contentSize.Width, scale); int width = ToPixels(_contentSize.Width, scale);
int height = ToPixels(_contentSize.Height, scale); int height = ToPixels(_contentSize.Height, scale);
int offset = ToPixels(_settings.CursorOffset, scale);
bool toRight = _settings.CursorCorner is CursorCorner.BottomRight or CursorCorner.TopRight; int toLeftOf = anchor.Left - offset - width;
bool toBottom = _settings.CursorCorner is CursorCorner.BottomRight or CursorCorner.BottomLeft; int toRightOf = anchor.Right + offset;
int above = anchor.Top - offset - height;
int below = anchor.Bottom + offset;
// Для сторон «слева» и «справа» подсказка встаёт вровень с точкой привязки
int middle = anchor.Top + (((anchor.Bottom - anchor.Top) - height) / 2);
int x = toRight ? anchor.Right + offset : anchor.Left - offset - width; (int x, int y) = side switch
int y = toBottom ? anchor.Bottom + offset : anchor.Top - offset - height; {
AnchorSide.TopLeft => (toLeftOf, above),
AnchorSide.TopRight => (toRightOf, above),
AnchorSide.Left => (toLeftOf, middle),
AnchorSide.Right => (toRightOf, middle),
AnchorSide.BottomLeft => (toLeftOf, below),
_ => (toRightOf, below),
};
PopupWindowNative.SetBounds(handle, x, y, width, height); PopupWindowNative.SetBounds(handle, x, y, width, height);
} }
+50 -13
View File
@@ -71,6 +71,8 @@
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions> </Grid.RowDefinitions>
<TextBlock Style="{StaticResource FieldLabel}" <TextBlock Style="{StaticResource FieldLabel}"
@@ -81,49 +83,84 @@
SelectedValuePath="Value" SelectedValuePath="Value"
SelectedValue="{Binding Settings.PlacementMode}" /> SelectedValue="{Binding Settings.PlacementMode}" />
<!-- Привязка к точке: курсор или каретка --> <!-- Режим «у курсора»: своя сторона и свой отступ -->
<TextBlock Grid.Row="1" Margin="0,12,12,0" <TextBlock Grid.Row="1" Margin="0,12,12,0"
Style="{StaticResource FieldLabel}" Style="{StaticResource FieldLabel}"
Text="{Binding Localization[CursorCornerLabel]}" Text="{Binding Localization[CursorCornerLabel]}"
Visibility="{Binding Settings.PlacementMode, Visibility="{Binding Settings.PlacementMode,
Converter={StaticResource EnumToVisibility}, Converter={StaticResource EnumToVisibility},
ConverterParameter='AtCursor,AtCaret'}" /> ConverterParameter=AtCursor}" />
<ComboBox Grid.Row="1" Grid.Column="1" Grid.ColumnSpan="2" Margin="0,12,0,0" <ComboBox Grid.Row="1" Grid.Column="1" Grid.ColumnSpan="2" Margin="0,12,0,0"
ItemsSource="{Binding CursorCorners}" ItemsSource="{Binding AnchorSides}"
DisplayMemberPath="Display" DisplayMemberPath="Display"
SelectedValuePath="Value" SelectedValuePath="Value"
SelectedValue="{Binding Settings.CursorCorner}" SelectedValue="{Binding Settings.CursorSide}"
Visibility="{Binding Settings.PlacementMode, Visibility="{Binding Settings.PlacementMode,
Converter={StaticResource EnumToVisibility}, Converter={StaticResource EnumToVisibility},
ConverterParameter='AtCursor,AtCaret'}" /> ConverterParameter=AtCursor}" />
<TextBlock Grid.Row="2" Margin="0,12,12,0" <TextBlock Grid.Row="2" Margin="0,12,12,0"
Style="{StaticResource FieldLabel}" Style="{StaticResource FieldLabel}"
Text="{Binding Localization[CursorOffsetLabel]}" Text="{Binding Localization[CursorOffsetLabel]}"
Visibility="{Binding Settings.PlacementMode, Visibility="{Binding Settings.PlacementMode,
Converter={StaticResource EnumToVisibility}, Converter={StaticResource EnumToVisibility},
ConverterParameter='AtCursor,AtCaret'}" /> ConverterParameter=AtCursor}" />
<Slider Grid.Row="2" Grid.Column="1" Margin="0,12,0,0" <Slider Grid.Row="2" Grid.Column="1" Margin="0,12,0,0"
Minimum="0" Maximum="80" TickFrequency="1" Minimum="0" Maximum="80" TickFrequency="1"
Value="{Binding Settings.CursorOffset}" Value="{Binding Settings.CursorOffset}"
Visibility="{Binding Settings.PlacementMode, Visibility="{Binding Settings.PlacementMode,
Converter={StaticResource EnumToVisibility}, Converter={StaticResource EnumToVisibility},
ConverterParameter='AtCursor,AtCaret'}" /> ConverterParameter=AtCursor}" />
<TextBlock Grid.Row="2" Grid.Column="2" Margin="12,12,0,0" <TextBlock Grid.Row="2" Grid.Column="2" Margin="12,12,0,0"
Style="{StaticResource FieldValue}" Style="{StaticResource FieldValue}"
Text="{Binding Settings.CursorOffset, StringFormat={}{0:F0}}" Text="{Binding Settings.CursorOffset, StringFormat={}{0:F0}}"
Visibility="{Binding Settings.PlacementMode, Visibility="{Binding Settings.PlacementMode,
Converter={StaticResource EnumToVisibility}, Converter={StaticResource EnumToVisibility},
ConverterParameter='AtCursor,AtCaret'}" /> ConverterParameter=AtCursor}" />
<!-- Режим «у каретки»: свои сторона и отступ -->
<TextBlock Grid.Row="3" Margin="0,12,12,0"
Style="{StaticResource FieldLabel}"
Text="{Binding Localization[CursorCornerLabel]}"
Visibility="{Binding Settings.PlacementMode,
Converter={StaticResource EnumToVisibility},
ConverterParameter=AtCaret}" />
<ComboBox Grid.Row="3" Grid.Column="1" Grid.ColumnSpan="2" Margin="0,12,0,0"
ItemsSource="{Binding AnchorSides}"
DisplayMemberPath="Display"
SelectedValuePath="Value"
SelectedValue="{Binding Settings.CaretSide}"
Visibility="{Binding Settings.PlacementMode,
Converter={StaticResource EnumToVisibility},
ConverterParameter=AtCaret}" />
<TextBlock Grid.Row="4" Margin="0,12,12,0"
Style="{StaticResource FieldLabel}"
Text="{Binding Localization[CursorOffsetLabel]}"
Visibility="{Binding Settings.PlacementMode,
Converter={StaticResource EnumToVisibility},
ConverterParameter=AtCaret}" />
<Slider Grid.Row="4" Grid.Column="1" Margin="0,12,0,0"
Minimum="0" Maximum="80" TickFrequency="1"
Value="{Binding Settings.CaretOffset}"
Visibility="{Binding Settings.PlacementMode,
Converter={StaticResource EnumToVisibility},
ConverterParameter=AtCaret}" />
<TextBlock Grid.Row="4" Grid.Column="2" Margin="12,12,0,0"
Style="{StaticResource FieldValue}"
Text="{Binding Settings.CaretOffset, StringFormat={}{0:F0}}"
Visibility="{Binding Settings.PlacementMode,
Converter={StaticResource EnumToVisibility},
ConverterParameter=AtCaret}" />
<!-- Режим «фиксированная точка» --> <!-- Режим «фиксированная точка» -->
<TextBlock Grid.Row="3" Margin="0,12,12,0" <TextBlock Grid.Row="5" Margin="0,12,12,0"
Style="{StaticResource FieldLabel}" Style="{StaticResource FieldLabel}"
Text="{Binding Localization[ScreenPositionLabel]}" Text="{Binding Localization[ScreenPositionLabel]}"
Visibility="{Binding Settings.PlacementMode, Visibility="{Binding Settings.PlacementMode,
Converter={StaticResource EnumToVisibility}, Converter={StaticResource EnumToVisibility},
ConverterParameter=FixedPoint}" /> ConverterParameter=FixedPoint}" />
<ComboBox Grid.Row="3" Grid.Column="1" Grid.ColumnSpan="2" Margin="0,12,0,0" <ComboBox Grid.Row="5" Grid.Column="1" Grid.ColumnSpan="2" Margin="0,12,0,0"
ItemsSource="{Binding ScreenPositions}" ItemsSource="{Binding ScreenPositions}"
DisplayMemberPath="Display" DisplayMemberPath="Display"
SelectedValuePath="Value" SelectedValuePath="Value"
@@ -132,19 +169,19 @@
Converter={StaticResource EnumToVisibility}, Converter={StaticResource EnumToVisibility},
ConverterParameter=FixedPoint}" /> ConverterParameter=FixedPoint}" />
<TextBlock Grid.Row="4" Margin="0,12,12,0" <TextBlock Grid.Row="6" Margin="0,12,12,0"
Style="{StaticResource FieldLabel}" Style="{StaticResource FieldLabel}"
Text="{Binding Localization[ScreenMarginLabel]}" Text="{Binding Localization[ScreenMarginLabel]}"
Visibility="{Binding Settings.PlacementMode, Visibility="{Binding Settings.PlacementMode,
Converter={StaticResource EnumToVisibility}, Converter={StaticResource EnumToVisibility},
ConverterParameter=FixedPoint}" /> ConverterParameter=FixedPoint}" />
<Slider Grid.Row="4" Grid.Column="1" Margin="0,12,0,0" <Slider Grid.Row="6" Grid.Column="1" Margin="0,12,0,0"
Minimum="0" Maximum="200" TickFrequency="1" Minimum="0" Maximum="200" TickFrequency="1"
Value="{Binding Settings.ScreenMargin}" Value="{Binding Settings.ScreenMargin}"
Visibility="{Binding Settings.PlacementMode, Visibility="{Binding Settings.PlacementMode,
Converter={StaticResource EnumToVisibility}, Converter={StaticResource EnumToVisibility},
ConverterParameter=FixedPoint}" /> ConverterParameter=FixedPoint}" />
<TextBlock Grid.Row="4" Grid.Column="2" Margin="12,12,0,0" <TextBlock Grid.Row="6" Grid.Column="2" Margin="12,12,0,0"
Style="{StaticResource FieldValue}" Style="{StaticResource FieldValue}"
Text="{Binding Settings.ScreenMargin, StringFormat={}{0:F0}}" Text="{Binding Settings.ScreenMargin, StringFormat={}{0:F0}}"
Visibility="{Binding Settings.PlacementMode, Visibility="{Binding Settings.PlacementMode,