feat: move back to textbox, add caret handling on click and focus.

This commit is contained in:
rabbitism
2023-02-24 12:34:43 +08:00
parent f53076ae43
commit 5dfbd6dd3c
5 changed files with 141 additions and 87 deletions

View File

@@ -8,8 +8,9 @@
d:DesignHeight="450" d:DesignHeight="450"
d:DesignWidth="800" d:DesignWidth="800"
mc:Ignorable="d"> mc:Ignorable="d">
<StackPanel> <StackPanel HorizontalAlignment="Left">
<TextBlock /> <TextBlock />
<u:IPv4Box Width="500" /> <u:IPv4Box />
<u:IPv4Box ShowPort="True" />
</StackPanel> </StackPanel>
</UserControl> </UserControl>

View File

@@ -23,7 +23,7 @@
<!--Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration.--> <!--Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration.-->
<PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="11.0.0-preview5" /> <PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="11.0.0-preview5" />
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.1.0" /> <PackageReference Include="CommunityToolkit.Mvvm" Version="8.1.0" />
<PackageReference Include="Semi.Avalonia" Version="0.1.0-preview5.1" /> <PackageReference Include="Semi.Avalonia" Version="0.1.0-preview5.2" />
<PackageReference Include="XamlNameReferenceGenerator" Version="1.5.1" /> <PackageReference Include="XamlNameReferenceGenerator" Version="1.5.1" />
</ItemGroup> </ItemGroup>

View File

@@ -3,55 +3,88 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:u="https://irihi.tech/ursa"> xmlns:u="https://irihi.tech/ursa">
<!-- Add Resources Here --> <!-- Add Resources Here -->
<Design.PreviewWith>
<StackPanel Margin="20">
<TextBlock Text="Hello World" />
</StackPanel>
</Design.PreviewWith>
<ControlTheme x:Key="{x:Type u:IPv4Box}" TargetType="{x:Type u:IPv4Box}"> <ControlTheme x:Key="{x:Type u:IPv4Box}" TargetType="{x:Type u:IPv4Box}">
<Setter Property="u:IPv4Box.Focusable" Value="True" />
<Setter Property="u:IPv4Box.HorizontalAlignment" Value="Left" />
<Setter Property="u:IPv4Box.Template"> <Setter Property="u:IPv4Box.Template">
<ControlTemplate TargetType="u:IPv4Box"> <ControlTemplate TargetType="u:IPv4Box">
<Border <Border
Name="PART_Border"
MinHeight="30"
HorizontalAlignment="{TemplateBinding HorizontalAlignment}"
Background="Transparent"
BorderBrush="Black" BorderBrush="Black"
BorderThickness="1" BorderThickness="1"
CornerRadius="3"> CornerRadius="3">
<Grid Width="{TemplateBinding Width}" ColumnDefinitions="*, Auto, *, Auto, *, Auto, *, Auto, * "> <Grid Width="{TemplateBinding Width}" ColumnDefinitions="1*, Auto, 1*, Auto, 1*, Auto, 1*, Auto, * ">
<TextBox <TextPresenter
Name="{x:Static u:IPv4Box.PART_FirstTextBox}" Name="{x:Static u:IPv4Box.PART_FirstTextPresenter}"
Grid.Column="0" Grid.Column="0"
BorderThickness="0" /> MinWidth="48"
<TextPresenter VerticalAlignment="Center"
Cursor="IBeam"
Text="123"
TextAlignment="Center" />
<TextBlock
Grid.Column="1" Grid.Column="1"
Margin="0,4"
VerticalAlignment="Bottom" VerticalAlignment="Bottom"
Focusable="False"
Text="." /> Text="." />
<TextBox <TextPresenter
Name="{x:Static u:IPv4Box.PART_SecondTextBox}" Name="{x:Static u:IPv4Box.PART_SecondTextPresenter}"
Grid.Column="2" Grid.Column="2"
BorderThickness="0" /> MinWidth="48"
<TextPresenter VerticalAlignment="Center"
Cursor="IBeam"
Text="123" />
<TextBlock
Grid.Column="3" Grid.Column="3"
Margin="0,4"
VerticalAlignment="Bottom" VerticalAlignment="Bottom"
Text="." /> Text="." />
<TextBox
Name="{x:Static u:IPv4Box.PART_ThirdTextBox}"
Grid.Column="4"
BorderThickness="0" />
<TextPresenter <TextPresenter
Name="{x:Static u:IPv4Box.PART_ThirdTextPresenter}"
Grid.Column="4"
MinWidth="48"
VerticalAlignment="Center"
Cursor="IBeam" />
<TextBlock
Grid.Column="5" Grid.Column="5"
Margin="0,4"
VerticalAlignment="Bottom" VerticalAlignment="Bottom"
Text="." /> Text="." />
<TextBox <TextPresenter
Name="{x:Static u:IPv4Box.PART_FourthTextBox}" Name="{x:Static u:IPv4Box.PART_FourthTextPresenter}"
Grid.Column="6" Grid.Column="6"
BorderThickness="0" /> MinWidth="48"
VerticalAlignment="Center"
Cursor="IBeam" />
<Rectangle <Rectangle
Grid.Column="7" Grid.Column="7"
Width="1" Width="1"
Margin="0,2" Margin="0,4"
VerticalAlignment="Stretch" VerticalAlignment="Stretch"
Fill="Black" /> Fill="Black"
<TextBox IsVisible="{TemplateBinding ShowPort}" />
Name="{x:Static u:IPv4Box.PART_PortTextBox}" <TextPresenter
Name="{x:Static u:IPv4Box.PART_PortTextPresenter}"
Grid.Column="8" Grid.Column="8"
BorderThickness="0" /> MinWidth="30"
VerticalAlignment="Center"
Cursor="IBeam"
IsVisible="{TemplateBinding ShowPort}" />
</Grid> </Grid>
</Border> </Border>
</ControlTemplate> </ControlTemplate>
</Setter> </Setter>
<Style Selector="^:focus-within /template/ Border#PART_Border">
<Setter Property="BorderBrush" Value="Red" />
</Style>
</ControlTheme> </ControlTheme>
</ResourceDictionary> </ResourceDictionary>

View File

@@ -10,23 +10,30 @@ using Avalonia.Interactivity;
namespace Ursa.Controls; namespace Ursa.Controls;
[TemplatePart(PART_FirstTextBox, typeof(TextBox))] [TemplatePart(PART_FirstTextPresenter, typeof(TextPresenter))]
[TemplatePart(PART_SecondTextBox, typeof(TextBox))] [TemplatePart(PART_SecondTextPresenter, typeof(TextPresenter))]
[TemplatePart(PART_ThirdTextBox, typeof(TextBox))] [TemplatePart(PART_ThirdTextPresenter, typeof(TextPresenter))]
[TemplatePart(PART_FourthTextBox, typeof(TextBox))] [TemplatePart(PART_FourthTextPresenter, typeof(TextPresenter))]
[TemplatePart(PART_PortTextBox, typeof(TextBox))] [TemplatePart(PART_PortTextPresenter, typeof(TextPresenter))]
public class IPv4Box: TemplatedControl public class IPv4Box: TemplatedControl
{ {
public const string PART_FirstTextBox = "PART_FirstTextPresenter"; public const string PART_FirstTextPresenter = "PART_FirstTextPresenter";
public const string PART_SecondTextBox = "PART_SecondTextPresenter"; public const string PART_SecondTextPresenter = "PART_SecondTextPresenter";
public const string PART_ThirdTextBox = "PART_ThirdTextPresenter"; public const string PART_ThirdTextPresenter = "PART_ThirdTextPresenter";
public const string PART_FourthTextBox = "PART_FourthTextPresenter"; public const string PART_FourthTextPresenter = "PART_FourthTextPresenter";
public const string PART_PortTextBox = "PART_PortNumericInput"; public const string PART_PortTextPresenter = "PART_PortTextPresenter";
private TextBox? _firstText; private TextPresenter? _firstText;
private TextBox? _secondText; private TextPresenter? _secondText;
private TextBox? _thirdText; private TextPresenter? _thirdText;
private TextBox? _fourthText; private TextPresenter? _fourthText;
private TextBox? _portText; private TextPresenter? _portText;
private byte _firstByte;
private byte _secondByte;
private byte _thridByte;
private byte _fourthByte;
private int _port;
private TextPresenter?[] _presenters = new TextPresenter?[5];
private TextPresenter? _currentActivePresenter;
public static readonly StyledProperty<bool> ShowPortProperty = AvaloniaProperty.Register<IPv4Box, bool>( public static readonly StyledProperty<bool> ShowPortProperty = AvaloniaProperty.Register<IPv4Box, bool>(
nameof(ShowPort)); nameof(ShowPort));
@@ -37,10 +44,10 @@ public class IPv4Box: TemplatedControl
set => SetValue(ShowPortProperty, value); set => SetValue(ShowPortProperty, value);
} }
public static readonly StyledProperty<IPAddress> IPAddressProperty = AvaloniaProperty.Register<IPv4Box, IPAddress>( public static readonly StyledProperty<IPAddress?> IPAddressProperty = AvaloniaProperty.Register<IPv4Box, IPAddress?>(
nameof(IPAddress)); nameof(IPAddress));
public IPAddress IPAddress public IPAddress? IPAddress
{ {
get => GetValue(IPAddressProperty); get => GetValue(IPAddressProperty);
set => SetValue(IPAddressProperty, value); set => SetValue(IPAddressProperty, value);
@@ -55,69 +62,78 @@ public class IPv4Box: TemplatedControl
set => SetValue(PortProperty, value); set => SetValue(PortProperty, value);
} }
protected override void OnApplyTemplate(TemplateAppliedEventArgs e) protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{ {
base.OnApplyTemplate(e); base.OnApplyTemplate(e);
_firstText = e.NameScope.Find<TextBox>(PART_FirstTextBox); ClearTextPresenterEvents(_firstText);
_secondText = e.NameScope.Find<TextBox>(PART_SecondTextBox); ClearTextPresenterEvents(_secondText);
_thirdText = e.NameScope.Find<TextBox>(PART_ThirdTextBox); ClearTextPresenterEvents(_thirdText);
_fourthText = e.NameScope.Find<TextBox>(PART_FourthTextBox); ClearTextPresenterEvents(_fourthText);
_portText = e.NameScope.Find<TextBox>(PART_PortTextBox); ClearTextPresenterEvents(_portText);
_firstText = e.NameScope.Find<TextPresenter>(PART_FirstTextPresenter);
_firstText.LostFocus += OnTextLostFocus; _secondText = e.NameScope.Find<TextPresenter>(PART_SecondTextPresenter);
_thirdText = e.NameScope.Find<TextPresenter>(PART_ThirdTextPresenter);
_fourthText = e.NameScope.Find<TextPresenter>(PART_FourthTextPresenter);
_portText = e.NameScope.Find<TextPresenter>(PART_PortTextPresenter);
_presenters[0] = _portText;
_presenters[1] = _firstText;
_presenters[2] = _secondText;
_presenters[3] = _thirdText;
_presenters[4] = _fourthText;
RegisterTextPresenterEvents(_firstText);
RegisterTextPresenterEvents(_secondText);
RegisterTextPresenterEvents(_thirdText);
RegisterTextPresenterEvents(_fourthText);
RegisterTextPresenterEvents(_portText);
} }
private void OnTextKeyDown(object sender, KeyEventArgs args) private void ClearTextPresenterEvents(TextPresenter? presenter)
{ {
if (args.Key == Key.Right) if (presenter is null) return;
presenter.LostFocus -= OnTextPresenterLostFocus;
}
private void RegisterTextPresenterEvents(TextPresenter? presenter)
{
if(presenter is null) return;
presenter.LostFocus += OnTextPresenterLostFocus;
}
private void OnTextPresenterLostFocus(object? sender, RoutedEventArgs args)
{
if (sender is TextPresenter p)
{ {
_secondText?.Focus(); p.HideCaret();
} }
} }
private void OnTextLostFocus(object? sender, RoutedEventArgs args) protected override void OnPointerPressed(PointerPressedEventArgs e)
{ {
if (sender?.Equals(_firstText)??false) var source = e.Source;
Point position = e.GetPosition(_firstText);
foreach (var presenter in _presenters)
{ {
Debug.WriteLine("FIRST"); if (presenter?.Bounds.Contains(position) ?? false)
}
}
protected override void OnKeyDown(KeyEventArgs e)
{
if (e.Key == Key.Left)
{
if ((_firstText?.IsFocused??false) && _firstText.SelectionStart == 0)
{ {
_secondText?.Focus(); presenter?.ShowCaret();
_currentActivePresenter = presenter;
}
else
{
presenter?.HideCaret();
} }
} }
else if (e.Key == Key.Right) Debug.WriteLine(_currentActivePresenter?.Name);
{ base.OnPointerPressed(e);
if ((_firstText?.IsFocused ?? false) && _firstText?.SelectionEnd == 2)
{
_firstText.Focus();
}
}
else if (e.Key == Key.Tab)
{
}
else if (!(e.Key is >= Key.D0 and <= Key.D9 || e.Key is >= Key.NumPad0 and <= Key.NumPad9))
{
return;
}
base.OnKeyDown(e);
} }
protected override void OnTextInput(TextInputEventArgs e) protected override void OnLostFocus(RoutedEventArgs e)
{ {
if (_firstText != null) _firstText?.HideCaret();
{ _secondText?.HideCaret();
_firstText.Text = e.Text; _thirdText?.HideCaret();
} _fourthText?.HideCaret();
base.OnTextInput(e); _portText?.HideCaret();
_currentActivePresenter = null;
} }
} }

View File

@@ -12,4 +12,8 @@
<PackageReference Include="XamlNameReferenceGenerator" Version="1.5.1" /> <PackageReference Include="XamlNameReferenceGenerator" Version="1.5.1" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<Folder Include="TypeConverters" />
</ItemGroup>
</Project> </Project>