Add TimeBox Control
This commit is contained in:
58
demo/Ursa.Demo/Pages/TimeBoxDemo.axaml
Normal file
58
demo/Ursa.Demo/Pages/TimeBoxDemo.axaml
Normal file
@@ -0,0 +1,58 @@
|
||||
<UserControl
|
||||
x:Class="Ursa.Demo.Pages.TimeBoxDemo"
|
||||
xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:u="https://irihi.tech/ursa"
|
||||
xmlns:vm="clr-namespace:Ursa.Demo.ViewModels;assembly=Ursa.Demo"
|
||||
x:DataType="vm:TimeBoxDemoViewModel"
|
||||
x:CompileBindings="True"
|
||||
d:DesignHeight="450"
|
||||
d:DesignWidth="800"
|
||||
mc:Ignorable="d">
|
||||
<StackPanel HorizontalAlignment="Left">
|
||||
<ToggleButton
|
||||
Name="format"
|
||||
Margin="0,0,0,10"
|
||||
Content="Show Leading Zeroes" />
|
||||
<ToggleButton
|
||||
Name="allowDrag"
|
||||
Margin="0,0,0,10"
|
||||
Content="Allow Drag" />
|
||||
<ToggleButton
|
||||
Name="isTimeLoop"
|
||||
Margin="0,0,0,50"
|
||||
Content="Is Time Loop" />
|
||||
<TextBlock Classes="" Text="Normal" />
|
||||
<u:TimeBox
|
||||
Name="box"
|
||||
Width="200"
|
||||
ShowLeadingZero="{Binding #format.IsChecked}"
|
||||
AllowDrag="{Binding #allowDrag.IsChecked}"
|
||||
IsTimeLoop="{Binding #isTimeLoop.IsChecked}"/>
|
||||
|
||||
<TextBlock Text="Time: " />
|
||||
<TextBlock Text="{Binding #box.Time}" />
|
||||
<TextBlock Classes="" Text="Fast input" />
|
||||
<u:TimeBox
|
||||
Width="200"
|
||||
InputMode="Fast"
|
||||
ShowLeadingZero="{Binding #format.IsChecked}"
|
||||
AllowDrag="{Binding #allowDrag.IsChecked}"
|
||||
IsTimeLoop="{Binding #isTimeLoop.IsChecked}"/>
|
||||
|
||||
<TextBlock Classes="" Text="Binding From Source" />
|
||||
<RepeatButton Command="{Binding ChangeRandomTimeCommand}" Content="Random" />
|
||||
<u:TimeBox
|
||||
Width="200"
|
||||
Time="{Binding TimeSpan}"
|
||||
ShowLeadingZero="{Binding #format.IsChecked}"
|
||||
AllowDrag="{Binding #allowDrag.IsChecked}"
|
||||
IsTimeLoop="{Binding #isTimeLoop.IsChecked}"/>
|
||||
<TextBlock Classes="" Text="Disabled" />
|
||||
<u:TimeBox Width="200" IsEnabled="False" Time="{Binding TimeSpan}"
|
||||
AllowDrag="{Binding #allowDrag.IsChecked}"
|
||||
IsTimeLoop="{Binding #isTimeLoop.IsChecked}"/>
|
||||
</StackPanel>
|
||||
</UserControl>
|
||||
13
demo/Ursa.Demo/Pages/TimeBoxDemo.axaml.cs
Normal file
13
demo/Ursa.Demo/Pages/TimeBoxDemo.axaml.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Markup.Xaml;
|
||||
|
||||
namespace Ursa.Demo.Pages;
|
||||
|
||||
public partial class TimeBoxDemo : UserControl
|
||||
{
|
||||
public TimeBoxDemo()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
@@ -58,6 +58,7 @@ public class MainViewViewModel : ViewModelBase
|
||||
MenuKeys.MenuKeyTwoTonePathIcon => new TwoTonePathIconDemoViewModel(),
|
||||
MenuKeys.MenuKeyThemeToggler => new ThemeTogglerDemoViewModel(),
|
||||
MenuKeys.MenuKeyToolBar => new ToolBarDemoViewModel(),
|
||||
MenuKeys.MenuKeyTimeBox => new TimeBoxDemoViewModel(),
|
||||
MenuKeys.MenuKeyVerificationCode => new VerificationCodeDemoViewModel(),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -45,6 +45,7 @@ public class MenuViewModel: ViewModelBase
|
||||
new() { MenuHeader = "Timeline", Key = MenuKeys.MenuKeyTimeline },
|
||||
new() { MenuHeader = "TwoTonePathIcon", Key = MenuKeys.MenuKeyTwoTonePathIcon},
|
||||
new() { MenuHeader = "ToolBar", Key = MenuKeys.MenuKeyToolBar },
|
||||
new() { MenuHeader = "Time Box", Key = MenuKeys.MenuKeyTimeBox, Status = "New" },
|
||||
new() { MenuHeader = "Verification Code", Key = MenuKeys.MenuKeyVerificationCode, Status = "New" },
|
||||
};
|
||||
}
|
||||
@@ -87,5 +88,6 @@ public static class MenuKeys
|
||||
public const string MenuKeyThemeToggler = "ThemeToggler";
|
||||
public const string MenuKeyToolBar = "ToolBar";
|
||||
public const string MenuKeyVerificationCode = "VerificationCode";
|
||||
public const string MenuKeyTimeBox = "TimeBox";
|
||||
|
||||
}
|
||||
22
demo/Ursa.Demo/ViewModels/TimeBoxDemoViewModel.cs
Normal file
22
demo/Ursa.Demo/ViewModels/TimeBoxDemoViewModel.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
using System;
|
||||
using System.Net;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
|
||||
namespace Ursa.Demo.ViewModels;
|
||||
|
||||
public partial class TimeBoxDemoViewModel : ObservableObject
|
||||
{
|
||||
[ObservableProperty] private TimeSpan? _timeSpan;
|
||||
|
||||
[RelayCommand]
|
||||
private void ChangeRandomTime()
|
||||
{
|
||||
TimeSpan = new TimeSpan(Random.Shared.NextInt64(0x00000000FFFFFFFF));
|
||||
}
|
||||
|
||||
public TimeBoxDemoViewModel()
|
||||
{
|
||||
TimeSpan = new TimeSpan(0, 21, 11, 36, 54);
|
||||
}
|
||||
}
|
||||
149
src/Ursa.Themes.Semi/Controls/TimeBox.axaml
Normal file
149
src/Ursa.Themes.Semi/Controls/TimeBox.axaml
Normal file
@@ -0,0 +1,149 @@
|
||||
<ResourceDictionary xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:u="https://irihi.tech/ursa">
|
||||
<!-- Add Resources Here -->
|
||||
<Design.PreviewWith>
|
||||
<StackPanel Margin="20">
|
||||
<TextBlock Text="Hello World"/>
|
||||
</StackPanel>
|
||||
</Design.PreviewWith>
|
||||
|
||||
<ControlTheme x:Key="{x:Type u:TimeBox}" TargetType="{x:Type u:TimeBox}">
|
||||
<Setter Property="u:TimeBox.Focusable" Value="True"/>
|
||||
<Setter Property="u:TimeBox.ShowLeadingZero" Value="True"/>
|
||||
<Setter Property="u:TimeBox.TextAlignment" Value="Center"/>
|
||||
<Setter Property="u:TimeBox.HorizontalAlignment" Value="Left"/>
|
||||
<Setter Property="u:TimeBox.CornerRadius" Value="{DynamicResource TimeBoxCornerRadius}" />
|
||||
<Setter Property="u:TimeBox.Background" Value="{DynamicResource TimeBoxBackground}" />
|
||||
<Setter Property="u:TimeBox.MinHeight" Value="{DynamicResource TimeBoxDefaultMinHeight}" />
|
||||
<Setter Property="u:TimeBox.BorderThickness" Value="{DynamicResource TimeBoxBorderThickness}" />
|
||||
<Setter Property="u:TimeBox.SelectionBrush" Value="{DynamicResource TimeBoxSelectionBrush}" />
|
||||
<Setter Property="u:TimeBox.SelectionForegroundBrush" Value="{DynamicResource TimeBoxSelectionForeground}" />
|
||||
<Setter Property="u:TimeBox.CaretBrush" Value="{DynamicResource TimeBoxCaretBrush}" />
|
||||
<Setter Property="u:TimeBox.Template">
|
||||
<ControlTemplate TargetType="u:TimeBox">
|
||||
<Border Name="PART_Border"
|
||||
Background="{TemplateBinding Background}"
|
||||
BorderBrush="{TemplateBinding BorderBrush}"
|
||||
BorderThickness="{TemplateBinding BorderThickness}"
|
||||
CornerRadius="{TemplateBinding CornerRadius}">
|
||||
<Grid Width="{TemplateBinding Width}" ColumnDefinitions="1*, Auto, 1*, Auto, 1*, Auto, 1*">
|
||||
<Border Name="{x:Static u:TimeBox.PART_HourBorder}"
|
||||
Grid.Column="0"
|
||||
Background="Transparent"
|
||||
BorderBrush="Transparent"
|
||||
BorderThickness="0"
|
||||
CornerRadius="{TemplateBinding CornerRadius}">
|
||||
<Grid>
|
||||
<TextPresenter Name="{x:Static u:TimeBox.PART_HoursTextPresenter}"
|
||||
MinWidth="8"
|
||||
VerticalAlignment="Center"
|
||||
CaretBrush="{TemplateBinding CaretBrush}"
|
||||
Cursor="IBeam"
|
||||
SelectionBrush="{TemplateBinding SelectionBrush}"
|
||||
SelectionForegroundBrush="{TemplateBinding SelectionForegroundBrush}"
|
||||
TextAlignment="{TemplateBinding TextAlignment}"/>
|
||||
<Panel Name="{x:Static u:TimeBox.PART_HourDragPanel}"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
Background="Transparent"
|
||||
Cursor="SizeWestEast"/>
|
||||
</Grid>
|
||||
</Border>
|
||||
<TextBlock
|
||||
Grid.Column="1"
|
||||
Margin="0,4"
|
||||
VerticalAlignment="Center"
|
||||
Focusable="False"
|
||||
Text=":" />
|
||||
<Border Name="{x:Static u:TimeBox.PART_MinuteBorder}"
|
||||
Grid.Column="2"
|
||||
Background="Transparent"
|
||||
BorderBrush="Transparent"
|
||||
BorderThickness="0"
|
||||
CornerRadius="{TemplateBinding CornerRadius}">
|
||||
<Grid>
|
||||
<TextPresenter Name="{x:Static u:TimeBox.PART_MinuteTextPresenter}"
|
||||
MinWidth="8"
|
||||
VerticalAlignment="Center"
|
||||
CaretBrush="{TemplateBinding CaretBrush}"
|
||||
Cursor="IBeam"
|
||||
SelectionBrush="{TemplateBinding SelectionBrush}"
|
||||
SelectionForegroundBrush="{TemplateBinding SelectionForegroundBrush}"
|
||||
TextAlignment="{TemplateBinding TextAlignment}"/>
|
||||
<Panel Name="{x:Static u:TimeBox.PART_MinuteDragPanel}"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
Background="Transparent"
|
||||
Cursor="SizeWestEast"/>
|
||||
</Grid>
|
||||
</Border>
|
||||
<TextBlock
|
||||
Grid.Column="3"
|
||||
Margin="0,4"
|
||||
VerticalAlignment="Center"
|
||||
Focusable="False"
|
||||
Text=":" />
|
||||
<Border Name="{x:Static u:TimeBox.PART_SecondBorder}"
|
||||
Grid.Column="4"
|
||||
Background="Transparent"
|
||||
BorderBrush="Transparent"
|
||||
BorderThickness="0"
|
||||
CornerRadius="{TemplateBinding CornerRadius}">
|
||||
<Grid>
|
||||
<TextPresenter Name="{x:Static u:TimeBox.PART_SecondTextPresenter}"
|
||||
MinWidth="8"
|
||||
VerticalAlignment="Center"
|
||||
CaretBrush="{TemplateBinding CaretBrush}"
|
||||
Cursor="IBeam"
|
||||
SelectionBrush="{TemplateBinding SelectionBrush}"
|
||||
SelectionForegroundBrush="{TemplateBinding SelectionForegroundBrush}"
|
||||
TextAlignment="{TemplateBinding TextAlignment}"/>
|
||||
<Panel Name="{x:Static u:TimeBox.PART_SecondDragPanel}"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
Background="Transparent"
|
||||
Cursor="SizeWestEast"/>
|
||||
</Grid>
|
||||
</Border>
|
||||
<TextBlock
|
||||
Grid.Column="5"
|
||||
Margin="0,4"
|
||||
VerticalAlignment="Center"
|
||||
Focusable="False"
|
||||
Text="." />
|
||||
<Border Name="{x:Static u:TimeBox.PART_MilliSecondBorder}"
|
||||
Grid.Column="6"
|
||||
Background="Transparent"
|
||||
BorderBrush="Transparent"
|
||||
BorderThickness="0"
|
||||
CornerRadius="{TemplateBinding CornerRadius}">
|
||||
<Grid>
|
||||
<TextPresenter Name="{x:Static u:TimeBox.PART_MillisecondTextPresenter}"
|
||||
MinWidth="8"
|
||||
VerticalAlignment="Center"
|
||||
CaretBrush="{TemplateBinding CaretBrush}"
|
||||
Cursor="IBeam"
|
||||
SelectionBrush="{TemplateBinding SelectionBrush}"
|
||||
SelectionForegroundBrush="{TemplateBinding SelectionForegroundBrush}"
|
||||
TextAlignment="{TemplateBinding TextAlignment}"/>
|
||||
<Panel Name="{x:Static u:TimeBox.PART_MilliSecondDragPanel}"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
Background="Transparent"
|
||||
Cursor="SizeWestEast"/>
|
||||
</Grid>
|
||||
</Border>
|
||||
</Grid>
|
||||
</Border>
|
||||
</ControlTemplate>
|
||||
</Setter>
|
||||
<Style Selector="^:focus-within">
|
||||
<Setter Property="Border.BorderBrush" Value="{DynamicResource TimeBoxFocusBorderBrush}" />
|
||||
</Style>
|
||||
<Style Selector="^:disabled">
|
||||
<Setter Property="Background" Value="{DynamicResource TimeBoxDisabledBackground}" />
|
||||
<Setter Property="Foreground" Value="{DynamicResource SemiColorDisabledText}" />
|
||||
</Style>
|
||||
</ControlTheme>
|
||||
</ResourceDictionary>
|
||||
@@ -35,6 +35,7 @@
|
||||
<ResourceInclude Source="Skeleton.axaml" />
|
||||
<ResourceInclude Source="TwoTonePathIcon.axaml" />
|
||||
<ResourceInclude Source="ToolBar.axaml" />
|
||||
<ResourceInclude Source="TimeBox.axaml"/>
|
||||
<ResourceInclude Source="VerificationCode.axaml" />
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
</ResourceDictionary>
|
||||
|
||||
21
src/Ursa.Themes.Semi/Styles/TimeBox.axaml
Normal file
21
src/Ursa.Themes.Semi/Styles/TimeBox.axaml
Normal file
@@ -0,0 +1,21 @@
|
||||
<Styles xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:u="https://irihi.tech/ursa">
|
||||
<Design.PreviewWith>
|
||||
<Border Padding="20">
|
||||
<!-- Add Controls for Previewer Here -->
|
||||
</Border>
|
||||
</Design.PreviewWith>
|
||||
<Style Selector="u|TimeBox /template/ Border#PART_HourBorder:pointerover">
|
||||
<Setter Property="Background" Value="{DynamicResource TimeBoxPointeroverBackground}"/>
|
||||
</Style>
|
||||
<Style Selector="u|TimeBox /template/ Border#PART_MinuteBorder:pointerover">
|
||||
<Setter Property="Background" Value="{DynamicResource TimeBoxPointeroverBackground}"/>
|
||||
</Style>
|
||||
<Style Selector="u|TimeBox /template/ Border#PART_SecondBorder:pointerover">
|
||||
<Setter Property="Background" Value="{DynamicResource TimeBoxPointeroverBackground}"/>
|
||||
</Style>
|
||||
<Style Selector="u|TimeBox /template/ Border#PART_MilliSecondBorder:pointerover">
|
||||
<Setter Property="Background" Value="{DynamicResource TimeBoxPointeroverBackground}"/>
|
||||
</Style>
|
||||
</Styles>
|
||||
@@ -8,5 +8,6 @@
|
||||
<StyleInclude Source="ButtonGroup.axaml" />
|
||||
<StyleInclude Source="Skeleton.axaml" />
|
||||
<StyleInclude Source="ToolBar.axaml"/>
|
||||
<StyleInclude Source="TimeBox.axaml"/>
|
||||
<!-- Add Styles Here -->
|
||||
</Styles>
|
||||
|
||||
12
src/Ursa.Themes.Semi/Themes/Dark/TimeBox.axaml
Normal file
12
src/Ursa.Themes.Semi/Themes/Dark/TimeBox.axaml
Normal file
@@ -0,0 +1,12 @@
|
||||
<ResourceDictionary xmlns="https://github.com/avaloniaui" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||
<!-- Add Resources Here -->
|
||||
<SolidColorBrush x:Key="TimeBoxBackground" Opacity="0.12" Color="White" />
|
||||
<SolidColorBrush x:Key="TimeBoxPointeroverBackground" Opacity="0.16" Color="White" />
|
||||
<SolidColorBrush x:Key="TimeBoxPressedBackground" Opacity="0.2" Color="White" />
|
||||
<SolidColorBrush x:Key="TimeBoxBorderBrush" Color="Transparent" />
|
||||
<SolidColorBrush x:Key="TimeBoxDisabledBackground" Opacity="0.04" Color="#E6E8EA" />
|
||||
<SolidColorBrush x:Key="TimeBoxFocusBorderBrush" Color="#FF54A9FF" />
|
||||
<SolidColorBrush x:Key="TimeBoxSelectionBrush" Color="#FF54A9FF" />
|
||||
<SolidColorBrush x:Key="TimeBoxSelectionForeground" Color="White" />
|
||||
<SolidColorBrush x:Key="TimeBoxCaretBrush" Color="White" />
|
||||
</ResourceDictionary>
|
||||
@@ -15,5 +15,6 @@
|
||||
<MergeResourceInclude Source="TagInput.axaml" />
|
||||
<MergeResourceInclude Source="Timeline.axaml" />
|
||||
<MergeResourceInclude Source="Skeleton.axaml" />
|
||||
<MergeResourceInclude Source="TimeBox.axaml"/>
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
</ResourceDictionary>
|
||||
|
||||
12
src/Ursa.Themes.Semi/Themes/Light/TimeBox.axaml
Normal file
12
src/Ursa.Themes.Semi/Themes/Light/TimeBox.axaml
Normal file
@@ -0,0 +1,12 @@
|
||||
<ResourceDictionary xmlns="https://github.com/avaloniaui" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||
<!-- Add Resources Here -->
|
||||
<SolidColorBrush x:Key="TimeBoxBackground" Opacity="0.05" Color="#FF2E3238" />
|
||||
<SolidColorBrush x:Key="TimeBoxPointeroverBackground" Opacity="0.09" Color="#FF2E3238" />
|
||||
<SolidColorBrush x:Key="TimeBoxPressedBackground" Opacity="0.13" Color="#FF2E3238" />
|
||||
<SolidColorBrush x:Key="TimeBoxBorderBrush" Color="Transparent" />
|
||||
<SolidColorBrush x:Key="TimeBoxDisabledBackground" Opacity="0.02" Color="#2E3238" />
|
||||
<SolidColorBrush x:Key="TimeBoxFocusBorderBrush" Color="#FF0077FA" />
|
||||
<SolidColorBrush x:Key="TimeBoxSelectionBrush" Color="#FF0077FA" />
|
||||
<SolidColorBrush x:Key="TimeBoxSelectionForeground" Color="White" />
|
||||
<SolidColorBrush x:Key="TimeBoxCaretBrush" Color="Black" />
|
||||
</ResourceDictionary>
|
||||
@@ -15,5 +15,6 @@
|
||||
<MergeResourceInclude Source="TagInput.axaml" />
|
||||
<MergeResourceInclude Source="Timeline.axaml" />
|
||||
<MergeResourceInclude Source="Skeleton.axaml" />
|
||||
<MergeResourceInclude Source="TimeBox.axaml"/>
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
</ResourceDictionary>
|
||||
|
||||
8
src/Ursa.Themes.Semi/Themes/Shared/TimeBox.axaml
Normal file
8
src/Ursa.Themes.Semi/Themes/Shared/TimeBox.axaml
Normal file
@@ -0,0 +1,8 @@
|
||||
<ResourceDictionary xmlns="https://github.com/avaloniaui" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||
<!-- Add Resources Here -->
|
||||
<x:Double x:Key="TimeBoxDefaultMinHeight">32</x:Double>
|
||||
<x:Double x:Key="TimeBoxSmallMinHeight">24</x:Double>
|
||||
<x:Double x:Key="TimeBoxLargeMinHeight">40</x:Double>
|
||||
<Thickness x:Key="TimeBoxBorderThickness">1</Thickness>
|
||||
<CornerRadius x:Key="TimeBoxCornerRadius">3</CornerRadius>
|
||||
</ResourceDictionary>
|
||||
@@ -18,5 +18,6 @@
|
||||
<MergeResourceInclude Source="Skeleton.axaml" />
|
||||
<MergeResourceInclude Source="ThemeSelector.axaml" />
|
||||
<MergeResourceInclude Source="ToolBar.axaml" />
|
||||
<MergeResourceInclude Source="TimeBox.axaml"/>
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
</ResourceDictionary>
|
||||
|
||||
657
src/Ursa/Controls/TimeBox.cs
Normal file
657
src/Ursa/Controls/TimeBox.cs
Normal file
@@ -0,0 +1,657 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Controls.Metadata;
|
||||
using Avalonia.Controls.Presenters;
|
||||
using Avalonia.Controls.Primitives;
|
||||
using Avalonia.Data;
|
||||
using Avalonia.Input;
|
||||
using Avalonia.Input.Raw;
|
||||
using Avalonia.Interactivity;
|
||||
using Avalonia.Media;
|
||||
using Avalonia.Media.TextFormatting;
|
||||
using Avalonia.Metadata;
|
||||
using Irihi.Avalonia.Shared.Helpers;
|
||||
|
||||
namespace Ursa.Controls;
|
||||
|
||||
public enum TimeBoxInputMode
|
||||
{
|
||||
Normal,
|
||||
|
||||
// In fast mode, automatically move to next session after 2 digits input.
|
||||
Fast,
|
||||
}
|
||||
|
||||
[TemplatePart(PART_HoursTextPresenter, typeof(TextPresenter))]
|
||||
[TemplatePart(PART_MinuteTextPresenter, typeof(TextPresenter))]
|
||||
[TemplatePart(PART_SecondTextPresenter, typeof(TextPresenter))]
|
||||
[TemplatePart(PART_MillisecondTextPresenter, typeof(TextPresenter))]
|
||||
[TemplatePart(PART_HourBorder, typeof(Border))]
|
||||
[TemplatePart(PART_MinuteBorder, typeof(Border))]
|
||||
[TemplatePart(PART_SecondBorder, typeof(Border))]
|
||||
[TemplatePart(PART_MilliSecondBorder, typeof(Border))]
|
||||
[TemplatePart(PART_HourDragPanel, typeof(Panel))]
|
||||
[TemplatePart(PART_MinuteDragPanel, typeof(Panel))]
|
||||
[TemplatePart(PART_SecondDragPanel, typeof(Panel))]
|
||||
[TemplatePart(PART_MilliSecondDragPanel, typeof(Panel))]
|
||||
public class TimeBox : TemplatedControl
|
||||
{
|
||||
public const string PART_HoursTextPresenter = "PART_HourTextPresenter";
|
||||
public const string PART_MinuteTextPresenter = "PART_MinuteTextPresenter";
|
||||
public const string PART_SecondTextPresenter = "PART_SecondTextPresenter";
|
||||
public const string PART_MillisecondTextPresenter = "PART_MillisecondTextPresenter";
|
||||
public const string PART_HourBorder = "PART_HourBorder";
|
||||
public const string PART_MinuteBorder = "PART_MinuteBorder";
|
||||
public const string PART_SecondBorder = "PART_SecondBorder";
|
||||
public const string PART_MilliSecondBorder = "PART_MilliSecondBorder";
|
||||
public const string PART_HourDragPanel = "PART_HourDragPanel";
|
||||
public const string PART_MinuteDragPanel = "PART_MinuteDragPanel";
|
||||
public const string PART_SecondDragPanel = "PART_SecondDragPanel";
|
||||
public const string PART_MilliSecondDragPanel = "PART_MilliSecondDragPanel";
|
||||
private TextPresenter? _hourText;
|
||||
private TextPresenter? _minuteText;
|
||||
private TextPresenter? _secondText;
|
||||
private TextPresenter? _milliSecondText;
|
||||
private Border? _hourBorder;
|
||||
private Border? _minuteBorder;
|
||||
private Border? _secondBorder;
|
||||
private Border? _milliSecondBorder;
|
||||
private Panel? _hourDragPanel;
|
||||
private Panel? _minuteDragPanel;
|
||||
private Panel? _secondDragPanel;
|
||||
private Panel? _milliSecondDragPanel;
|
||||
private readonly TextPresenter?[] _presenters = new TextPresenter?[4];
|
||||
private readonly Border?[] _borders = new Border?[4];
|
||||
private readonly Panel?[] _dragPanels = new Panel?[4];
|
||||
private readonly int[] _limits = new[] { 24, 60, 60, 100 };
|
||||
private int[] _values = new int[4];
|
||||
private bool[] _isShowedCaret = new bool[4];
|
||||
private int? _currentActiveSectionIndex;
|
||||
private bool _isAlreadyDrag = false;
|
||||
private Point _pressedPosition = new Point();
|
||||
private Point? _lastDragPoint;
|
||||
|
||||
public static readonly StyledProperty<TimeSpan?> TimeProperty = AvaloniaProperty.Register<TimeBox, TimeSpan?>(
|
||||
nameof(Time), defaultBindingMode: BindingMode.TwoWay);
|
||||
|
||||
public TimeSpan? Time
|
||||
{
|
||||
get => GetValue(TimeProperty);
|
||||
set => SetValue(TimeProperty, value);
|
||||
}
|
||||
|
||||
public static readonly StyledProperty<TextAlignment> TextAlignmentProperty =
|
||||
TextBox.TextAlignmentProperty.AddOwner<TimeBox>();
|
||||
|
||||
public TextAlignment TextAlignment
|
||||
{
|
||||
get => GetValue(TextAlignmentProperty);
|
||||
set => SetValue(TextAlignmentProperty, value);
|
||||
}
|
||||
|
||||
public static readonly StyledProperty<IBrush?> SelectionBrushProperty =
|
||||
TextBox.SelectionBrushProperty.AddOwner<IPv4Box>();
|
||||
|
||||
public IBrush? SelectionBrush
|
||||
{
|
||||
get => GetValue(SelectionBrushProperty);
|
||||
set => SetValue(SelectionBrushProperty, value);
|
||||
}
|
||||
|
||||
public static readonly StyledProperty<IBrush?> SelectionForegroundBrushProperty =
|
||||
TextBox.SelectionForegroundBrushProperty.AddOwner<IPv4Box>();
|
||||
|
||||
public IBrush? SelectionForegroundBrush
|
||||
{
|
||||
get => GetValue(SelectionForegroundBrushProperty);
|
||||
set => SetValue(SelectionForegroundBrushProperty, value);
|
||||
}
|
||||
|
||||
public static readonly StyledProperty<IBrush?> CaretBrushProperty = TextBox.CaretBrushProperty.AddOwner<IPv4Box>();
|
||||
|
||||
public IBrush? CaretBrush
|
||||
{
|
||||
get => GetValue(CaretBrushProperty);
|
||||
set => SetValue(CaretBrushProperty, value);
|
||||
}
|
||||
|
||||
public static readonly StyledProperty<bool> ShowLeadingZeroProperty = AvaloniaProperty.Register<TimeBox, bool>(
|
||||
nameof(ShowLeadingZero));
|
||||
|
||||
public bool ShowLeadingZero
|
||||
{
|
||||
get => GetValue(ShowLeadingZeroProperty);
|
||||
set => SetValue(ShowLeadingZeroProperty, value);
|
||||
}
|
||||
|
||||
public static readonly StyledProperty<TimeBoxInputMode> InputModeProperty =
|
||||
AvaloniaProperty.Register<TimeBox, TimeBoxInputMode>(
|
||||
nameof(InputMode));
|
||||
|
||||
public TimeBoxInputMode InputMode
|
||||
{
|
||||
get => GetValue(InputModeProperty);
|
||||
set => SetValue(InputModeProperty, value);
|
||||
}
|
||||
|
||||
public static readonly StyledProperty<bool> AllowDragProperty = AvaloniaProperty.Register<TimeBox, bool>(
|
||||
nameof(AllowDrag), defaultBindingMode: BindingMode.TwoWay);
|
||||
|
||||
public bool AllowDrag
|
||||
{
|
||||
get => GetValue(AllowDragProperty);
|
||||
set => SetValue(AllowDragProperty, value);
|
||||
}
|
||||
|
||||
public static readonly StyledProperty<bool> IsReadOnlyProperty = AvaloniaProperty.Register<TimeBox, bool>(
|
||||
nameof(IsReadOnly), defaultValue: false, defaultBindingMode: BindingMode.TwoWay);
|
||||
|
||||
public bool IsReadOnly
|
||||
{
|
||||
get => GetValue(IsReadOnlyProperty);
|
||||
set => SetValue(IsReadOnlyProperty, value);
|
||||
}
|
||||
|
||||
public static readonly StyledProperty<bool> IsTimeLoopProperty = AvaloniaProperty.Register<TimeBox, bool>(
|
||||
nameof(IsTimeLoop), defaultBindingMode: BindingMode.TwoWay);
|
||||
|
||||
public bool IsTimeLoop
|
||||
{
|
||||
get => GetValue(IsTimeLoopProperty);
|
||||
set => SetValue(IsTimeLoopProperty, value);
|
||||
}
|
||||
|
||||
static TimeBox()
|
||||
{
|
||||
ShowLeadingZeroProperty.Changed.AddClassHandler<TimeBox>((o, e) => o.OnFormatChange(e));
|
||||
TimeProperty.Changed.AddClassHandler<TimeBox>((o, e) => o.OnTimeChanged(e));
|
||||
AllowDragProperty.Changed.AddClassHandler<TimeBox, bool>((o, e) => o.OnAllowDragChange(e));
|
||||
}
|
||||
|
||||
private void OnAllowDragChange(AvaloniaPropertyChangedEventArgs<bool> args)
|
||||
{
|
||||
IsVisibleProperty.SetValue(args.NewValue.Value, _dragPanels);
|
||||
}
|
||||
|
||||
#region Overrides
|
||||
|
||||
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
|
||||
{
|
||||
base.OnApplyTemplate(e);
|
||||
_hourText = e.NameScope.Find<TextPresenter>(PART_HoursTextPresenter);
|
||||
_minuteText = e.NameScope.Find<TextPresenter>(PART_MinuteTextPresenter);
|
||||
_secondText = e.NameScope.Find<TextPresenter>(PART_SecondTextPresenter);
|
||||
_milliSecondText = e.NameScope.Find<TextPresenter>(PART_MillisecondTextPresenter);
|
||||
_hourBorder = e.NameScope.Find<Border>(PART_HourBorder);
|
||||
_minuteBorder = e.NameScope.Find<Border>(PART_MinuteBorder);
|
||||
_secondBorder = e.NameScope.Find<Border>(PART_SecondBorder);
|
||||
_milliSecondBorder = e.NameScope.Find<Border>(PART_MilliSecondBorder);
|
||||
_hourDragPanel = e.NameScope.Find<Panel>(PART_HourDragPanel);
|
||||
_minuteDragPanel = e.NameScope.Find<Panel>(PART_MinuteDragPanel);
|
||||
_secondDragPanel = e.NameScope.Find<Panel>(PART_SecondDragPanel);
|
||||
_milliSecondDragPanel = e.NameScope.Find<Panel>(PART_MilliSecondDragPanel);
|
||||
_presenters[0] = _hourText;
|
||||
_presenters[1] = _minuteText;
|
||||
_presenters[2] = _secondText;
|
||||
_presenters[3] = _milliSecondText;
|
||||
_borders[0] = _hourBorder;
|
||||
_borders[1] = _minuteBorder;
|
||||
_borders[2] = _secondBorder;
|
||||
_borders[3] = _milliSecondBorder;
|
||||
_dragPanels[0] = _hourDragPanel;
|
||||
_dragPanels[1] = _minuteDragPanel;
|
||||
_dragPanels[2] = _secondDragPanel;
|
||||
_dragPanels[3] = _milliSecondDragPanel;
|
||||
IsVisibleProperty.SetValue(AllowDrag, _dragPanels);
|
||||
|
||||
|
||||
if (_hourText != null) _hourText.Text = Time != null ? Time.Value.Hours.ToString() : "0";
|
||||
if (_minuteText != null) _minuteText.Text = Time != null ? Time.Value.Minutes.ToString() : "0";
|
||||
if (_secondText != null) _secondText.Text = Time != null ? Time.Value.Seconds.ToString() : "0";
|
||||
if (_milliSecondText != null)
|
||||
_milliSecondText.Text = Time != null ? ClampMilliSecond(Time.Value.Milliseconds).ToString() : "0";
|
||||
ParseTimeSpan(ShowLeadingZero);
|
||||
|
||||
PointerMovedEvent.AddHandler(OnDragPanelPointerMoved, _dragPanels);
|
||||
}
|
||||
|
||||
protected override void OnKeyDown(KeyEventArgs e)
|
||||
{
|
||||
if (_currentActiveSectionIndex is null) return;
|
||||
var keymap = TopLevel.GetTopLevel(this)?.PlatformSettings?.HotkeyConfiguration;
|
||||
bool Match(List<KeyGesture> gestures) => gestures.Any(g => g.Matches(e));
|
||||
if (e.Key is Key.Enter or Key.Return)
|
||||
{
|
||||
ParseTimeSpan(ShowLeadingZero);
|
||||
SetTimeSpanInternal();
|
||||
base.OnKeyDown(e);
|
||||
return;
|
||||
}
|
||||
|
||||
if (e.Key == Key.Tab)
|
||||
{
|
||||
if (_currentActiveSectionIndex.Value == 3)
|
||||
{
|
||||
base.OnKeyDown(e);
|
||||
return;
|
||||
}
|
||||
|
||||
MoveToNextSection(_currentActiveSectionIndex.Value);
|
||||
e.Handled = true;
|
||||
}
|
||||
else if (e.Key == Key.Back)
|
||||
{
|
||||
DeleteImplementation(_currentActiveSectionIndex.Value);
|
||||
}
|
||||
else if (e.Key == Key.Right)
|
||||
{
|
||||
OnPressRightKey();
|
||||
}
|
||||
else if (e.Key == Key.Left)
|
||||
{
|
||||
OnPressLeftKey();
|
||||
}
|
||||
else
|
||||
{
|
||||
base.OnKeyDown(e);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnTextInput(TextInputEventArgs e)
|
||||
{
|
||||
if (e.Handled) return;
|
||||
string? s = e.Text;
|
||||
if (string.IsNullOrEmpty(s)) return;
|
||||
if (!char.IsNumber(s![0])) return;
|
||||
if (_currentActiveSectionIndex.HasValue && _presenters[_currentActiveSectionIndex.Value] != null)
|
||||
{
|
||||
int caretIndex = Math.Min(_presenters[_currentActiveSectionIndex.Value].CaretIndex
|
||||
, _presenters[_currentActiveSectionIndex.Value].Text.Length);
|
||||
string? oldText = _presenters[_currentActiveSectionIndex.Value].Text;
|
||||
if (oldText is null)
|
||||
{
|
||||
_presenters[_currentActiveSectionIndex.Value].Text = s;
|
||||
_presenters[_currentActiveSectionIndex.Value].MoveCaretHorizontal();
|
||||
}
|
||||
else
|
||||
{
|
||||
_presenters[_currentActiveSectionIndex.Value].DeleteSelection();
|
||||
_presenters[_currentActiveSectionIndex.Value].ClearSelection();
|
||||
oldText = _presenters[_currentActiveSectionIndex.Value].Text;
|
||||
|
||||
string newText = string.IsNullOrEmpty(oldText)
|
||||
? s
|
||||
: oldText?.Substring(0, caretIndex) + s + oldText?.Substring(Math.Min(caretIndex, oldText.Length));
|
||||
if (newText.Length > 2)
|
||||
{
|
||||
newText = newText.Substring(0, 2);
|
||||
}
|
||||
|
||||
_presenters[_currentActiveSectionIndex.Value].Text = newText;
|
||||
Console.WriteLine(
|
||||
$"OnTextInput @ _secondText HashCode: {_presenters[_currentActiveSectionIndex.Value]?.GetHashCode()}");
|
||||
_presenters[_currentActiveSectionIndex.Value].MoveCaretHorizontal();
|
||||
if (_presenters[_currentActiveSectionIndex.Value].CaretIndex == 2 && InputMode == TimeBoxInputMode.Fast)
|
||||
{
|
||||
MoveToNextSection(_currentActiveSectionIndex.Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnPointerPressed(PointerPressedEventArgs e)
|
||||
{
|
||||
_pressedPosition = e.GetPosition(_hourBorder);
|
||||
_lastDragPoint = _pressedPosition;
|
||||
for (int i = 0; i < 4; ++i)
|
||||
{
|
||||
if (_borders[i]?.Bounds.Contains(_pressedPosition) ?? false)
|
||||
{
|
||||
_currentActiveSectionIndex = i;
|
||||
}
|
||||
else
|
||||
{
|
||||
LeaveSection(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnPointerReleased(PointerReleasedEventArgs e)
|
||||
{
|
||||
if (_currentActiveSectionIndex is null) return;
|
||||
if (_isAlreadyDrag)
|
||||
{
|
||||
_isAlreadyDrag = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
EnterSection(_currentActiveSectionIndex.Value);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnLostFocus(RoutedEventArgs e)
|
||||
{
|
||||
for (int i = 0; i < 4; ++i)
|
||||
{
|
||||
LeaveSection(i);
|
||||
}
|
||||
|
||||
_currentActiveSectionIndex = null;
|
||||
ParseTimeSpan(ShowLeadingZero);
|
||||
SetTimeSpanInternal();
|
||||
}
|
||||
|
||||
protected override void OnGotFocus(GotFocusEventArgs e)
|
||||
{
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private void OnFormatChange(AvaloniaPropertyChangedEventArgs arg)
|
||||
{
|
||||
bool showLeadingZero = arg.GetNewValue<bool>();
|
||||
ParseTimeSpan(showLeadingZero);
|
||||
}
|
||||
|
||||
private void OnTimeChanged(AvaloniaPropertyChangedEventArgs arg)
|
||||
{
|
||||
TimeSpan? timeSpan = arg.GetNewValue<TimeSpan?>();
|
||||
if (timeSpan is null)
|
||||
{
|
||||
if (_hourText != null) _hourText.Text = String.Empty;
|
||||
if (_minuteText != null) _minuteText.Text = String.Empty;
|
||||
if (_secondText != null) _secondText.Text = String.Empty;
|
||||
if (_milliSecondText != null) _milliSecondText.Text = String.Empty;
|
||||
ParseTimeSpan(ShowLeadingZero);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_hourText != null) _hourText.Text = timeSpan.Value.Hours.ToString();
|
||||
if (_minuteText != null) _minuteText.Text = timeSpan.Value.Minutes.ToString();
|
||||
if (_secondText != null) _secondText.Text = timeSpan.Value.Seconds.ToString();
|
||||
if (_milliSecondText != null) _milliSecondText.Text = (timeSpan.Value.Milliseconds / 10).ToString();
|
||||
ParseTimeSpan(ShowLeadingZero);
|
||||
}
|
||||
}
|
||||
|
||||
private void ParseTimeSpan(bool showLeadingZero, bool skipParseFromText = false)
|
||||
{
|
||||
string format = showLeadingZero ? "D2" : "";
|
||||
Console.WriteLine($"ParseTimeSpan @ _secondText HashCode: {_secondText?.GetHashCode()}");
|
||||
if (_hourText is null || _minuteText is null || _secondText is null || _milliSecondText is null)
|
||||
{
|
||||
_values[0] = 0;
|
||||
_values[1] = 0;
|
||||
_values[2] = 0;
|
||||
_values[3] = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!skipParseFromText)
|
||||
{
|
||||
_values[0] = int.TryParse(_hourText.Text, out int hour) ? hour : 0;
|
||||
_values[1] = int.TryParse(_minuteText.Text, out int minute) ? minute : 0;
|
||||
_values[2] = int.TryParse(_secondText.Text, out int second) ? second : 0;
|
||||
_values[3] = int.TryParse(_milliSecondText.Text, out int millisecond) ? millisecond : 0;
|
||||
}
|
||||
|
||||
VerifyTimeValue();
|
||||
|
||||
_hourText.Text = _values[0].ToString(format);
|
||||
_minuteText.Text = _values[1].ToString(format);
|
||||
_secondText.Text = _values[2].ToString(format);
|
||||
_milliSecondText.Text = _values[3].ToString(format);
|
||||
}
|
||||
private void OnDragPanelPointerMoved(object sender, PointerEventArgs e)
|
||||
{
|
||||
if (!AllowDrag || IsReadOnly) return;
|
||||
if (!e.GetCurrentPoint(this).Properties.IsLeftButtonPressed) return;
|
||||
var point = e.GetPosition(this);
|
||||
var delta = point - _lastDragPoint;
|
||||
if (delta is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int d = GetDelta(delta.Value);
|
||||
if (d > 0)
|
||||
{
|
||||
Increase();
|
||||
_isAlreadyDrag = true;
|
||||
}
|
||||
else if (d < 0)
|
||||
{
|
||||
Decrease();
|
||||
_isAlreadyDrag = true;
|
||||
}
|
||||
|
||||
_lastDragPoint = point;
|
||||
}
|
||||
|
||||
private int GetDelta(Point point)
|
||||
{
|
||||
return point.X switch
|
||||
{
|
||||
> 0 => 1,
|
||||
< 0 => -1,
|
||||
_ => 0
|
||||
};
|
||||
}
|
||||
|
||||
private void EnterSection(int index)
|
||||
{
|
||||
if (!_isShowedCaret[index])
|
||||
{
|
||||
if (AllowDrag && _dragPanels[index] != null)
|
||||
_dragPanels[index].IsVisible = false;
|
||||
_presenters[index].ShowCaret();
|
||||
_isShowedCaret[index] = true;
|
||||
_presenters[index].SelectAll();
|
||||
}
|
||||
else
|
||||
{
|
||||
_presenters[index].ClearSelection();
|
||||
var caretPosition =
|
||||
_pressedPosition.WithX(_pressedPosition.X - _borders[index].Bounds.X);
|
||||
_presenters[index].MoveCaretToPoint(caretPosition);
|
||||
}
|
||||
}
|
||||
|
||||
private void LeaveSection(int index)
|
||||
{
|
||||
if (_presenters[index] is null) return;
|
||||
_presenters[index].ClearSelection();
|
||||
if (_isShowedCaret[index])
|
||||
{
|
||||
_presenters[index].HideCaret();
|
||||
_isShowedCaret[index] = false;
|
||||
}
|
||||
|
||||
if (AllowDrag && _dragPanels[index] != null)
|
||||
_dragPanels[index].IsVisible = true;
|
||||
}
|
||||
|
||||
private bool MoveToNextSection(int index)
|
||||
{
|
||||
if (_presenters[index] is null) return false;
|
||||
if (index == 3) return false;
|
||||
LeaveSection(index);
|
||||
_currentActiveSectionIndex = index + 1;
|
||||
EnterSection(_currentActiveSectionIndex.Value);
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool MoveToPreviousSection(int index)
|
||||
{
|
||||
if (_presenters[index] is null) return false;
|
||||
if (index == 0) return false;
|
||||
LeaveSection(index);
|
||||
_currentActiveSectionIndex = index - 1;
|
||||
EnterSection(_currentActiveSectionIndex.Value);
|
||||
return true;
|
||||
}
|
||||
|
||||
private void OnPressRightKey()
|
||||
{
|
||||
if (_currentActiveSectionIndex is null) return;
|
||||
var index = _currentActiveSectionIndex.Value;
|
||||
if (_presenters[index].IsTextSelected())
|
||||
{
|
||||
int end = _presenters[index].SelectionEnd;
|
||||
_presenters[index].ClearSelection();
|
||||
_presenters[index].MoveCaretToTextPosition(end);
|
||||
return;
|
||||
}
|
||||
|
||||
if (_presenters[index].CaretIndex >= _presenters[index].Text?.Length)
|
||||
{
|
||||
MoveToNextSection(index);
|
||||
}
|
||||
else
|
||||
{
|
||||
_presenters[index].ClearSelection();
|
||||
_presenters[index].CaretIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnPressLeftKey()
|
||||
{
|
||||
if (_currentActiveSectionIndex is null) return;
|
||||
var index = _currentActiveSectionIndex.Value;
|
||||
if (_presenters[index].IsTextSelected())
|
||||
{
|
||||
int start = _presenters[index].SelectionStart;
|
||||
_presenters[index].ClearSelection();
|
||||
_presenters[index].MoveCaretToTextPosition(start);
|
||||
return;
|
||||
}
|
||||
|
||||
if (_presenters[index].CaretIndex == 0)
|
||||
{
|
||||
MoveToPreviousSection(index);
|
||||
}
|
||||
else
|
||||
{
|
||||
_presenters[index].ClearSelection();
|
||||
_presenters[index].CaretIndex--;
|
||||
}
|
||||
}
|
||||
|
||||
private void SetTimeSpanInternal()
|
||||
{
|
||||
try
|
||||
{
|
||||
Time = new TimeSpan(0, _values[0], _values[1], _values[2], _values[3] * 10);
|
||||
}
|
||||
catch
|
||||
{
|
||||
Time = TimeSpan.Zero;
|
||||
}
|
||||
}
|
||||
|
||||
private void DeleteImplementation(int index)
|
||||
{
|
||||
if (_presenters[index] is null) return;
|
||||
var oldText = _presenters[index].Text;
|
||||
if (_presenters[index].SelectionStart != _presenters[index].SelectionEnd)
|
||||
{
|
||||
_presenters[index].DeleteSelection();
|
||||
_presenters[index].ClearSelection();
|
||||
}
|
||||
else if (string.IsNullOrWhiteSpace(oldText) || _presenters[index].CaretIndex == 0)
|
||||
{
|
||||
MoveToPreviousSection(index);
|
||||
}
|
||||
else
|
||||
{
|
||||
int caretIndex = _presenters[index].CaretIndex;
|
||||
string newText = oldText?.Substring(0, caretIndex - 1) +
|
||||
oldText?.Substring(Math.Min(caretIndex, oldText.Length));
|
||||
_presenters[index].MoveCaretHorizontal(LogicalDirection.Backward);
|
||||
_presenters[index].Text = newText;
|
||||
}
|
||||
}
|
||||
|
||||
private bool HandlingCarry(int index, int lowerCarry = 0)
|
||||
{
|
||||
if (index < 0)
|
||||
return IsTimeLoop;
|
||||
_values[index] += lowerCarry;
|
||||
int carry = _values[index] >= 0 ? _values[index] / _limits[index] : -1 + (_values[index] / _limits[index]);
|
||||
if (carry == 0) return true;
|
||||
bool success = false;
|
||||
if (carry > 0)
|
||||
{
|
||||
success = HandlingCarry(index - 1, carry);
|
||||
if (success)
|
||||
{
|
||||
_values[index] %= _limits[index];
|
||||
}
|
||||
else
|
||||
{
|
||||
_values[index] = _limits[index] - 1;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
success = HandlingCarry(index - 1, carry);
|
||||
if (success)
|
||||
{
|
||||
_values[index] += _limits[index];
|
||||
}
|
||||
else
|
||||
{
|
||||
_values[index] = 0;
|
||||
}
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
private void VerifyTimeValue()
|
||||
{
|
||||
for (int i = 3; i >= 0; --i)
|
||||
{
|
||||
HandlingCarry(i);
|
||||
}
|
||||
}
|
||||
|
||||
private void Increase()
|
||||
{
|
||||
if(_currentActiveSectionIndex is null)return;
|
||||
if(_currentActiveSectionIndex.Value == 0)
|
||||
_values[0] += 1;
|
||||
else if(_currentActiveSectionIndex.Value == 1)
|
||||
_values[1] += 1;
|
||||
else if(_currentActiveSectionIndex.Value == 2)
|
||||
_values[2] += 1;
|
||||
else if(_currentActiveSectionIndex.Value == 3)
|
||||
_values[3] += 1;
|
||||
ParseTimeSpan(ShowLeadingZero, true);
|
||||
SetTimeSpanInternal();
|
||||
}
|
||||
|
||||
private void Decrease()
|
||||
{
|
||||
if(_currentActiveSectionIndex is null)return;
|
||||
if(_currentActiveSectionIndex.Value == 0)
|
||||
_values[0] -= 1;
|
||||
else if(_currentActiveSectionIndex.Value == 1)
|
||||
_values[1] -= 1;
|
||||
else if(_currentActiveSectionIndex.Value == 2)
|
||||
_values[2] -= 1;
|
||||
else if(_currentActiveSectionIndex.Value == 3)
|
||||
_values[3] -= 1;
|
||||
ParseTimeSpan(ShowLeadingZero, true);
|
||||
SetTimeSpanInternal();
|
||||
}
|
||||
|
||||
private int ClampMilliSecond(int milliSecond)
|
||||
{
|
||||
while (milliSecond % 100 != milliSecond)
|
||||
{
|
||||
milliSecond /= 10;
|
||||
}
|
||||
|
||||
return milliSecond;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user