using Avalonia.Animation; using Avalonia.Controls.Metadata; using Avalonia.Controls.Presenters; using Avalonia.Controls.Primitives; using Avalonia.Controls.Templates; using Avalonia.Interactivity; using Avalonia.LogicalTree; namespace Avalonia.Controls { /// /// A Toggle Switch control. /// [TemplatePart("PART_MovingKnobs", typeof(Panel))] [TemplatePart("PART_OffContentPresenter", typeof(ContentPresenter))] [TemplatePart("PART_OnContentPresenter", typeof(ContentPresenter))] [TemplatePart("PART_SwitchKnob", typeof(Panel))] [PseudoClasses(":dragging")] public class ToggleSwitch : ToggleButton { private Panel? _knobsPanel; private Panel? _switchKnob; private bool _knobsPanelPressed = false; private Point _switchStartPoint = new Point(); private double _initLeft = -1; private bool _isDragging = false; static ToggleSwitch() { OffContentProperty.Changed.AddClassHandler((x, e) => x.OffContentChanged(e)); OnContentProperty.Changed.AddClassHandler((x, e) => x.OnContentChanged(e)); IsCheckedProperty.Changed.AddClassHandler((x, e) => { if ((e.NewValue != null) && (e.NewValue is bool val)) { x.UpdateKnobPos(val); } }); BoundsProperty.Changed.AddClassHandler((x, e) => { if (x.IsChecked != null) { x.UpdateKnobPos(x.IsChecked.Value); } }); KnobTransitionsProperty.Changed.AddClassHandler((x, e) => { x.UpdateKnobTransitions(); }); } /// /// Defines the property. /// public static readonly StyledProperty OffContentProperty = AvaloniaProperty.Register(nameof(OffContent), defaultValue: "Off"); /// /// Defines the property. /// public static readonly StyledProperty OffContentTemplateProperty = AvaloniaProperty.Register(nameof(OffContentTemplate)); /// /// Defines the property. /// public static readonly StyledProperty OnContentProperty = AvaloniaProperty.Register(nameof(OnContent), defaultValue: "On"); /// /// Defines the property. /// public static readonly StyledProperty OnContentTemplateProperty = AvaloniaProperty.Register(nameof(OnContentTemplate)); /// /// Defines the property. /// public static readonly StyledProperty KnobTransitionsProperty = AvaloniaProperty.Register(nameof(KnobTransitions)); /// /// Gets or Sets the Content that is displayed when in the On State. /// public object? OnContent { get { return GetValue(OnContentProperty); } set { SetValue(OnContentProperty, value); } } /// /// Gets or Sets the Content that is displayed when in the Off State. /// public object? OffContent { get { return GetValue(OffContentProperty); } set { SetValue(OffContentProperty, value); } } public ContentPresenter? OffContentPresenter { get; private set; } public ContentPresenter? OnContentPresenter { get; private set; } /// /// Gets or Sets the used to display the . /// public IDataTemplate? OffContentTemplate { get { return GetValue(OffContentTemplateProperty); } set { SetValue(OffContentTemplateProperty, value); } } /// /// Gets or Sets the used to display the . /// public IDataTemplate? OnContentTemplate { get { return GetValue(OnContentTemplateProperty); } set { SetValue(OnContentTemplateProperty, value); } } /// /// Gets or Sets the of switching knob. /// public Transitions KnobTransitions { get { return GetValue(KnobTransitionsProperty); } set { SetValue(KnobTransitionsProperty, value); } } private void OffContentChanged(AvaloniaPropertyChangedEventArgs e) { if (e.OldValue is ILogical oldChild) { LogicalChildren.Remove(oldChild); } if (e.NewValue is ILogical newChild) { LogicalChildren.Add(newChild); } } private void OnContentChanged(AvaloniaPropertyChangedEventArgs e) { if (e.OldValue is ILogical oldChild) { LogicalChildren.Remove(oldChild); } if (e.NewValue is ILogical newChild) { LogicalChildren.Add(newChild); } } protected override bool RegisterContentPresenter(ContentPresenter presenter) { var result = base.RegisterContentPresenter(presenter); if (presenter.Name == "Part_OnContentPresenter") { OnContentPresenter = presenter; result = true; } else if (presenter.Name == "PART_OffContentPresenter") { OffContentPresenter = presenter; result = true; } return result; } protected override void OnApplyTemplate(TemplateAppliedEventArgs e) { base.OnApplyTemplate(e); _switchKnob = e.NameScope.Find("PART_SwitchKnob"); _knobsPanel = e.NameScope.Get("PART_MovingKnobs"); _knobsPanel.PointerPressed += KnobsPanel_PointerPressed; _knobsPanel.PointerReleased += KnobsPanel_PointerReleased; _knobsPanel.PointerMoved += KnobsPanel_PointerMoved; if (IsChecked.HasValue) { UpdateKnobPos(IsChecked.Value); } } /// protected override void OnLoaded(RoutedEventArgs e) { base.OnLoaded(e); UpdateKnobTransitions(); } private void UpdateKnobTransitions() { if (_knobsPanel != null) { _knobsPanel.Transitions = KnobTransitions; } } private void KnobsPanel_PointerPressed(object? sender, Input.PointerPressedEventArgs e) { _switchStartPoint = e.GetPosition(_switchKnob); _initLeft = Canvas.GetLeft(_knobsPanel!); _isDragging = false; _knobsPanelPressed = true; } private void KnobsPanel_PointerReleased(object? sender, Input.PointerReleasedEventArgs e) { if (_isDragging) { bool shouldBecomeChecked = Canvas.GetLeft(_knobsPanel!) >= (_switchKnob!.Bounds.Width / 2); _knobsPanel!.ClearValue(Canvas.LeftProperty); PseudoClasses.Set(":dragging", false); if (shouldBecomeChecked == IsChecked) { UpdateKnobPos(shouldBecomeChecked); } else { SetCurrentValue(IsCheckedProperty, shouldBecomeChecked); } UpdateKnobTransitions(); } _isDragging = false; _knobsPanelPressed = false; } private void KnobsPanel_PointerMoved(object? sender, Input.PointerEventArgs e) { if (_knobsPanelPressed) { if(_knobsPanel != null) { _knobsPanel.Transitions = null; } var difference = e.GetPosition(_switchKnob) - _switchStartPoint; if ((!_isDragging) && (System.Math.Abs(difference.X) > 3)) { _isDragging = true; PseudoClasses.Set(":dragging", true); } if (_isDragging) { Canvas.SetLeft(_knobsPanel!, System.Math.Min(_switchKnob!.Bounds.Width, System.Math.Max(0, (_initLeft + difference.X)))); } } } private void UpdateKnobPos(bool value) { if ((_switchKnob != null) && (_knobsPanel != null)) { if (value) { Canvas.SetLeft(_knobsPanel, _switchKnob.Bounds.Width); } else { Canvas.SetLeft(_knobsPanel, 0); } } } } }