Changed payload editor to Avalon
- Syntax highlighting - Highlight for JSON validation errors - Much improved tab handling and all the other advantages of using a proper editor
This commit is contained in:
parent
78de8e5196
commit
b549729bf5
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 1.8 KiB |
@ -19,7 +19,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Remove="Images\Connecting.svg" />
|
||||
<None Remove="Images\Busy.svg" />
|
||||
<None Remove="Images\Dock.svg" />
|
||||
<None Remove="Images\Error.svg" />
|
||||
<None Remove="Images\Ok.svg" />
|
||||
@ -40,6 +40,7 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="AvalonEdit" Version="6.1.2.30" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
|
||||
<PackageReference Include="SharpVectors" Version="1.7.7" />
|
||||
<PackageReference Include="SimpleInjector" Version="5.3.2" />
|
||||
@ -54,7 +55,7 @@
|
||||
|
||||
<ItemGroup>
|
||||
<Resource Include="Images\Undock.svg" />
|
||||
<Resource Include="Images\Connecting.svg" />
|
||||
<Resource Include="Images\Busy.svg" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
@ -1,6 +1,7 @@
|
||||
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:ui="clr-namespace:PettingZoo.UI">
|
||||
xmlns:ui="clr-namespace:PettingZoo.UI"
|
||||
xmlns:avalonedit="http://icsharpcode.net/sharpdevelop/avalonedit">
|
||||
<!-- Global styling -->
|
||||
<Style x:Key="WindowStyle" TargetType="{x:Type Window}">
|
||||
<Setter Property="Background" Value="{DynamicResource {x:Static SystemColors.ControlBrushKey}}"/>
|
||||
@ -83,10 +84,11 @@
|
||||
</Style>
|
||||
|
||||
|
||||
<Style x:Key="Payload" TargetType="{x:Type TextBox}">
|
||||
<Setter Property="AcceptsReturn" Value="True" />
|
||||
<Setter Property="AcceptsTab" Value="True" />
|
||||
<Setter Property="VerticalScrollBarVisibility" Value="Visible" />
|
||||
<Style x:Key="Payload" TargetType="{x:Type avalonedit:TextEditor}">
|
||||
<Setter Property="FontFamily" Value="Consolas,Courier New" />
|
||||
</Style>
|
||||
|
||||
<Style x:Key="ControlBorder" TargetType="{x:Type Border}">
|
||||
<Setter Property="BorderThickness" Value="1" />
|
||||
</Style>
|
||||
</ResourceDictionary>
|
37
PettingZoo/UI/ErrorHighlightingTransformer.cs
Normal file
37
PettingZoo/UI/ErrorHighlightingTransformer.cs
Normal file
@ -0,0 +1,37 @@
|
||||
using System;
|
||||
using System.Windows.Media;
|
||||
using ICSharpCode.AvalonEdit.Document;
|
||||
using ICSharpCode.AvalonEdit.Rendering;
|
||||
|
||||
namespace PettingZoo.UI
|
||||
{
|
||||
public class ErrorHighlightingTransformer : DocumentColorizingTransformer
|
||||
{
|
||||
public Brush BackgroundBrush { get; set; }
|
||||
public TextPosition? ErrorPosition { get; set; }
|
||||
|
||||
|
||||
public ErrorHighlightingTransformer()
|
||||
{
|
||||
BackgroundBrush = new SolidColorBrush(Color.FromRgb(255, 230, 230));
|
||||
}
|
||||
|
||||
|
||||
protected override void ColorizeLine(DocumentLine line)
|
||||
{
|
||||
if (ErrorPosition == null)
|
||||
return;
|
||||
|
||||
if (line.LineNumber != Math.Max(ErrorPosition.Value.Row, 1))
|
||||
return;
|
||||
|
||||
var lineStartOffset = line.Offset;
|
||||
|
||||
ChangeLinePart(lineStartOffset, lineStartOffset + line.Length,
|
||||
element =>
|
||||
{
|
||||
element.BackgroundBrush = BackgroundBrush;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
@ -69,6 +69,8 @@ namespace PettingZoo.UI
|
||||
public void Dispose()
|
||||
{
|
||||
BindingOperations.ClearBinding(this, ItemsSourceProperty);
|
||||
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
|
||||
|
@ -79,7 +79,7 @@
|
||||
<StatusBar DockPanel.Dock="Bottom">
|
||||
<StatusBarItem>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<Image Source="{svgc:SvgImage Source=/Images/Connecting.svg, AppName=PettingZoo}" Width="16" Height="16" Margin="4" Visibility="{Binding ConnectionStatusConnecting}" />
|
||||
<Image Source="{svgc:SvgImage Source=/Images/Busy.svg, AppName=PettingZoo}" Width="16" Height="16" Margin="4" Visibility="{Binding ConnectionStatusConnecting}" />
|
||||
<Image Source="{svgc:SvgImage Source=/Images/Ok.svg, AppName=PettingZoo}" Width="16" Height="16" Margin="4" Visibility="{Binding ConnectionStatusOk}" />
|
||||
<Image Source="{svgc:SvgImage Source=/Images/Error.svg, AppName=PettingZoo}" Width="16" Height="16" Margin="4" Visibility="{Binding ConnectionStatusError}" />
|
||||
<TextBlock Text="{Binding ConnectionStatus}" VerticalAlignment="Center"/>
|
||||
|
@ -115,6 +115,8 @@ namespace PettingZoo.UI.Main
|
||||
private static T? GetParent<T>(object originalSource) where T : DependencyObject
|
||||
{
|
||||
var current = originalSource as DependencyObject;
|
||||
if (current is not Visual)
|
||||
return null;
|
||||
|
||||
while (current != null)
|
||||
{
|
||||
|
@ -17,15 +17,23 @@
|
||||
<RadioButton Content="JSON" Style="{StaticResource TypeSelection}" IsChecked="{Binding ContentTypeSelection, Converter={StaticResource EnumBooleanConverter}, ConverterParameter={x:Static publisher:PayloadEditorContentType.Json}}" />
|
||||
<RadioButton Content="Plain text" Style="{StaticResource TypeSelection}" IsChecked="{Binding ContentTypeSelection, Converter={StaticResource EnumBooleanConverter}, ConverterParameter={x:Static publisher:PayloadEditorContentType.Plain}}" />
|
||||
<RadioButton Content="Other" Style="{StaticResource TypeSelection}" IsChecked="{Binding ContentTypeSelection, Converter={StaticResource EnumBooleanConverter}, ConverterParameter={x:Static publisher:PayloadEditorContentType.Other}}" />
|
||||
<TextBox Width="200" Text="{Binding ContentType, UpdateSourceTrigger=PropertyChanged}" />
|
||||
<TextBox Name="TextBoxForBorder" Width="200" Text="{Binding ContentType, UpdateSourceTrigger=PropertyChanged}" />
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel Orientation="Horizontal" DockPanel.Dock="Bottom" Visibility="{Binding JsonValidationVisibility}" Margin="0,8,0,0">
|
||||
<Image Source="{svgc:SvgImage Source=/Images/Ok.svg, AppName=PettingZoo}" Width="16" Height="16" Margin="4" Visibility="{Binding JsonValidationOk}" />
|
||||
<Image Source="{svgc:SvgImage Source=/Images/Error.svg, AppName=PettingZoo}" Width="16" Height="16" Margin="4" Visibility="{Binding JsonValidationError}" />
|
||||
<TextBlock Text="{Binding JsonValidationMessage}" Margin="4" />
|
||||
<StackPanel Orientation="Horizontal" DockPanel.Dock="Bottom" Visibility="{Binding ValidationVisibility}" Margin="0,8,0,0">
|
||||
<Image Source="{svgc:SvgImage Source=/Images/Ok.svg, AppName=PettingZoo}" Width="16" Height="16" Margin="4" Visibility="{Binding ValidationOk}" />
|
||||
<Image Source="{svgc:SvgImage Source=/Images/Error.svg, AppName=PettingZoo}" Width="16" Height="16" Margin="4" Visibility="{Binding ValidationError}" />
|
||||
<Image Source="{svgc:SvgImage Source=/Images/Busy.svg, AppName=PettingZoo}" Width="16" Height="16" Margin="4" Visibility="{Binding ValidationValidating}" />
|
||||
<TextBlock Text="{Binding ValidationMessage}" Margin="4" />
|
||||
</StackPanel>
|
||||
|
||||
<TextBox Text="{Binding Payload, UpdateSourceTrigger=PropertyChanged}" Style="{StaticResource Payload}" />
|
||||
<Border Style="{StaticResource ControlBorder}" Name="EditorBorder">
|
||||
<avalonedit:TextEditor
|
||||
xmlns:avalonedit="http://icsharpcode.net/sharpdevelop/avalonedit"
|
||||
Name="Editor"
|
||||
SyntaxHighlighting="{Binding SyntaxHighlighting}"
|
||||
Style="{StaticResource Payload}"
|
||||
/>
|
||||
</Border>
|
||||
</DockPanel>
|
||||
</UserControl>
|
||||
|
@ -86,6 +86,9 @@ namespace PettingZoo.UI.Tab.Publisher
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private readonly ErrorHighlightingTransformer errorHighlightingTransformer = new();
|
||||
|
||||
public PayloadEditorControl()
|
||||
{
|
||||
// Keep the exposed properties in sync with the ViewModel
|
||||
@ -133,6 +136,42 @@ namespace PettingZoo.UI.Tab.Publisher
|
||||
|
||||
InitializeComponent();
|
||||
|
||||
// I'm not sure how to get a standard control border, all I could find were workaround:
|
||||
// https://social.msdn.microsoft.com/Forums/en-US/5e007497-8d5a-401d-ac5b-9e1356fe9b64/default-borderbrush-for-textbox-listbox-etc
|
||||
// So I'll just copy it from another TextBox. I truly hate WPF some times for making standard things so complicated. </rant>
|
||||
EditorBorder.BorderBrush = TextBoxForBorder.BorderBrush;
|
||||
|
||||
Editor.Options.IndentationSize = 2;
|
||||
Editor.TextArea.TextView.LineTransformers.Add(errorHighlightingTransformer);
|
||||
|
||||
// Avalon doesn't play nice with bindings it seems:
|
||||
// https://stackoverflow.com/questions/18964176/two-way-binding-to-avalonedit-document-text-using-mvvm
|
||||
Editor.Document.Text = Payload;
|
||||
|
||||
Editor.TextChanged += (_, _) =>
|
||||
{
|
||||
Payload = Editor.Document.Text;
|
||||
};
|
||||
|
||||
|
||||
viewModel.PropertyChanged += (_, args) =>
|
||||
{
|
||||
if (args.PropertyName != nameof(viewModel.ValidationInfo))
|
||||
return;
|
||||
|
||||
Dispatcher.Invoke(() =>
|
||||
{
|
||||
if (errorHighlightingTransformer.ErrorPosition == viewModel.ValidationInfo.ErrorPosition)
|
||||
return;
|
||||
|
||||
errorHighlightingTransformer.ErrorPosition = viewModel.ValidationInfo.ErrorPosition;
|
||||
|
||||
// TODO this can probably be optimized to only redraw the affected line
|
||||
Editor.TextArea.TextView.Redraw();
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
// Setting the DataContext for the UserControl is a major PITA when binding the control's properties,
|
||||
// so I've moved the ViewModel one level down to get the best of both worlds...
|
||||
DataContextContainer.DataContext = viewModel;
|
||||
|
@ -88,20 +88,29 @@ namespace PettingZoo.UI.Tab.Publisher {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Invalid JSON: {0}.
|
||||
/// Looks up a localized string similar to Invalid: {0}.
|
||||
/// </summary>
|
||||
internal static string JsonValidationError {
|
||||
internal static string ValidationError {
|
||||
get {
|
||||
return ResourceManager.GetString("JsonValidationError", resourceCulture);
|
||||
return ResourceManager.GetString("ValidationError", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Valid JSON.
|
||||
/// Looks up a localized string similar to Valid.
|
||||
/// </summary>
|
||||
internal static string JsonValidationOk {
|
||||
internal static string ValidationOk {
|
||||
get {
|
||||
return ResourceManager.GetString("JsonValidationOk", resourceCulture);
|
||||
return ResourceManager.GetString("ValidationOk", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Validating....
|
||||
/// </summary>
|
||||
internal static string ValidationValidating {
|
||||
get {
|
||||
return ResourceManager.GetString("ValidationValidating", resourceCulture);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -112,10 +112,10 @@
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<data name="ContentTypeJson" xml:space="preserve">
|
||||
<value>JSON</value>
|
||||
@ -126,10 +126,13 @@
|
||||
<data name="ContentTypePlain" xml:space="preserve">
|
||||
<value>Plain text</value>
|
||||
</data>
|
||||
<data name="JsonValidationError" xml:space="preserve">
|
||||
<value>Invalid JSON: {0}</value>
|
||||
<data name="ValidationError" xml:space="preserve">
|
||||
<value>Invalid: {0}</value>
|
||||
</data>
|
||||
<data name="JsonValidationOk" xml:space="preserve">
|
||||
<value>Valid JSON</value>
|
||||
<data name="ValidationOk" xml:space="preserve">
|
||||
<value>Valid</value>
|
||||
</data>
|
||||
<data name="ValidationValidating" xml:space="preserve">
|
||||
<value>Validating...</value>
|
||||
</data>
|
||||
</root>
|
@ -1,8 +1,9 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Reactive.Linq;
|
||||
using System.Reactive.Subjects;
|
||||
using System.Windows;
|
||||
using ICSharpCode.AvalonEdit.Highlighting;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace PettingZoo.UI.Tab.Publisher
|
||||
@ -15,6 +16,38 @@ namespace PettingZoo.UI.Tab.Publisher
|
||||
};
|
||||
|
||||
|
||||
public enum ValidationStatus
|
||||
{
|
||||
NotSupported,
|
||||
Validating,
|
||||
Ok,
|
||||
Error
|
||||
}
|
||||
|
||||
|
||||
public readonly struct ValidationInfo
|
||||
{
|
||||
public ValidationStatus Status { get; }
|
||||
public string Message { get; }
|
||||
public TextPosition? ErrorPosition { get; }
|
||||
|
||||
|
||||
public ValidationInfo(ValidationStatus status, string? message = null, TextPosition? errorPosition = null)
|
||||
{
|
||||
Status = status;
|
||||
Message = message ?? status switch
|
||||
{
|
||||
ValidationStatus.NotSupported => "",
|
||||
ValidationStatus.Validating => PayloadEditorStrings.ValidationValidating,
|
||||
ValidationStatus.Ok => PayloadEditorStrings.ValidationOk,
|
||||
ValidationStatus.Error => throw new InvalidOperationException(@"Message required for Error validation status"),
|
||||
_ => throw new ArgumentException(@"Unsupported validation status", nameof(status))
|
||||
};
|
||||
ErrorPosition = errorPosition;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public class PayloadEditorViewModel : BaseViewModel
|
||||
{
|
||||
private const string ContentTypeJson = "application/json";
|
||||
@ -24,8 +57,7 @@ namespace PettingZoo.UI.Tab.Publisher
|
||||
private PayloadEditorContentType contentTypeSelection = PayloadEditorContentType.Json;
|
||||
private bool fixedJson;
|
||||
|
||||
private bool jsonValid = true;
|
||||
private string jsonValidationMessage;
|
||||
private ValidationInfo validationInfo = new(ValidationStatus.Ok);
|
||||
|
||||
private string payload = "";
|
||||
|
||||
@ -59,7 +91,7 @@ namespace PettingZoo.UI.Tab.Publisher
|
||||
get => contentTypeSelection;
|
||||
set
|
||||
{
|
||||
if (!SetField(ref contentTypeSelection, value, otherPropertiesChanged: new [] { nameof(JsonValidationVisibility) }))
|
||||
if (!SetField(ref contentTypeSelection, value, otherPropertiesChanged: new [] { nameof(ValidationVisibility), nameof(SyntaxHighlighting) }))
|
||||
return;
|
||||
|
||||
ContentType = ContentTypeSelection switch
|
||||
@ -80,23 +112,22 @@ namespace PettingZoo.UI.Tab.Publisher
|
||||
set => SetField(ref fixedJson, value);
|
||||
}
|
||||
|
||||
public Visibility JsonValidationVisibility => ContentTypeSelection == PayloadEditorContentType.Json ? Visibility.Visible : Visibility.Collapsed;
|
||||
public Visibility JsonValidationOk => JsonValid ? Visibility.Visible : Visibility.Collapsed;
|
||||
public Visibility JsonValidationError => !JsonValid ? Visibility.Visible : Visibility.Collapsed;
|
||||
|
||||
|
||||
public string JsonValidationMessage
|
||||
public ValidationInfo ValidationInfo
|
||||
{
|
||||
get => jsonValidationMessage;
|
||||
private set => SetField(ref jsonValidationMessage, value);
|
||||
get => validationInfo;
|
||||
private set => SetField(ref validationInfo, value, otherPropertiesChanged: new[] { nameof(ValidationOk), nameof(ValidationError), nameof(ValidationValidating), nameof(ValidationMessage) });
|
||||
}
|
||||
|
||||
|
||||
public bool JsonValid
|
||||
{
|
||||
get => jsonValid;
|
||||
private set => SetField(ref jsonValid, value, otherPropertiesChanged: new[] { nameof(JsonValidationOk), nameof(JsonValidationError) });
|
||||
}
|
||||
public Visibility ValidationVisibility => ContentTypeSelection == PayloadEditorContentType.Json ? Visibility.Visible : Visibility.Collapsed;
|
||||
|
||||
public string ValidationMessage => ValidationInfo.Message;
|
||||
|
||||
public Visibility ValidationOk => ValidationInfo.Status == ValidationStatus.Ok ? Visibility.Visible : Visibility.Collapsed;
|
||||
public Visibility ValidationError => ValidationInfo.Status == ValidationStatus.Error ? Visibility.Visible : Visibility.Collapsed;
|
||||
public Visibility ValidationValidating => ValidationInfo.Status == ValidationStatus.Validating ? Visibility.Visible : Visibility.Collapsed;
|
||||
|
||||
|
||||
public Visibility ContentTypeVisibility => FixedJson ? Visibility.Collapsed : Visibility.Visible;
|
||||
|
||||
@ -108,26 +139,48 @@ namespace PettingZoo.UI.Tab.Publisher
|
||||
}
|
||||
|
||||
|
||||
public IHighlightingDefinition? SyntaxHighlighting => ContentTypeSelection == PayloadEditorContentType.Json
|
||||
? HighlightingManager.Instance.GetDefinition(@"Json")
|
||||
: null;
|
||||
|
||||
|
||||
|
||||
public PayloadEditorViewModel()
|
||||
{
|
||||
jsonValidationMessage = PayloadEditorStrings.JsonValidationOk;
|
||||
|
||||
Observable.FromEventPattern<PropertyChangedEventHandler, PropertyChangedEventArgs>(
|
||||
var observable = Observable.FromEventPattern<PropertyChangedEventHandler, PropertyChangedEventArgs>(
|
||||
h => PropertyChanged += h,
|
||||
h => PropertyChanged -= h)
|
||||
.Where(e => e.EventArgs.PropertyName == nameof(Payload))
|
||||
.Where(e => e.EventArgs.PropertyName == nameof(Payload));
|
||||
|
||||
observable
|
||||
.Subscribe(_ => ValidatingPayload());
|
||||
|
||||
observable
|
||||
.Throttle(TimeSpan.FromMilliseconds(500))
|
||||
.Subscribe(_ => ValidatePayload());
|
||||
}
|
||||
|
||||
|
||||
private void ValidatingPayload()
|
||||
{
|
||||
if (ValidationInfo.Status == ValidationStatus.Validating)
|
||||
return;
|
||||
|
||||
if (ContentTypeSelection != PayloadEditorContentType.Json)
|
||||
{
|
||||
ValidationInfo = new ValidationInfo(ValidationStatus.NotSupported);
|
||||
return;
|
||||
}
|
||||
|
||||
ValidationInfo = new ValidationInfo(ValidationStatus.Validating);
|
||||
}
|
||||
|
||||
|
||||
private void ValidatePayload()
|
||||
{
|
||||
if (ContentTypeSelection != PayloadEditorContentType.Json)
|
||||
{
|
||||
JsonValid = true;
|
||||
JsonValidationMessage = PayloadEditorStrings.JsonValidationOk;
|
||||
ValidationInfo = new ValidationInfo(ValidationStatus.NotSupported);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -136,13 +189,15 @@ namespace PettingZoo.UI.Tab.Publisher
|
||||
if (!string.IsNullOrEmpty(Payload))
|
||||
JToken.Parse(Payload);
|
||||
|
||||
JsonValid = true;
|
||||
JsonValidationMessage = PayloadEditorStrings.JsonValidationOk;
|
||||
ValidationInfo = new ValidationInfo(ValidationStatus.Ok);
|
||||
}
|
||||
catch (JsonReaderException e)
|
||||
{
|
||||
ValidationInfo = new ValidationInfo(ValidationStatus.Error, e.Message, new TextPosition(e.LineNumber, e.LinePosition));
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
JsonValid = false;
|
||||
JsonValidationMessage = string.Format(PayloadEditorStrings.JsonValidationError, e.Message);
|
||||
ValidationInfo = new ValidationInfo(ValidationStatus.Error, e.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
43
PettingZoo/UI/TextPosition.cs
Normal file
43
PettingZoo/UI/TextPosition.cs
Normal file
@ -0,0 +1,43 @@
|
||||
using System;
|
||||
|
||||
namespace PettingZoo.UI
|
||||
{
|
||||
public readonly struct TextPosition : IEquatable<TextPosition>
|
||||
{
|
||||
public int Row { get; }
|
||||
public int Column { get; }
|
||||
|
||||
|
||||
public TextPosition(int row, int column)
|
||||
{
|
||||
Row = row;
|
||||
Column = column;
|
||||
}
|
||||
|
||||
|
||||
public bool Equals(TextPosition other)
|
||||
{
|
||||
return Row == other.Row && Column == other.Column;
|
||||
}
|
||||
|
||||
public override bool Equals(object? obj)
|
||||
{
|
||||
return obj is TextPosition other && Equals(other);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return HashCode.Combine(Row, Column);
|
||||
}
|
||||
|
||||
public static bool operator ==(TextPosition left, TextPosition right)
|
||||
{
|
||||
return left.Equals(right);
|
||||
}
|
||||
|
||||
public static bool operator !=(TextPosition left, TextPosition right)
|
||||
{
|
||||
return !(left == right);
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user