using System.Reactive.Disposables; using AtomUI.Desktop.Controls.Themes; using AtomUI.Data; using Avalonia; using Avalonia.Controls; using Avalonia.Controls.Primitives; using Avalonia.Media; using Avalonia.VisualTree; namespace AtomUI.Desktop.Controls; using AvaloniaTextBlock = Avalonia.Controls.TextBlock; public class GradientColorPicker : AbstractColorPicker { #region 公共属性定义 public static readonly StyledProperty DefaultValueProperty = GradientColorPickerView.DefaultValueProperty.AddOwner(); public static readonly StyledProperty ValueProperty = GradientColorPickerView.ValueProperty.AddOwner(); public static readonly AttachedProperty?> ColorTextFormatterProperty = AvaloniaProperty.RegisterAttached?>("ColorTextFormatter"); public LinearGradientBrush? DefaultValue { get => GetValue(DefaultValueProperty); set => SetValue(DefaultValueProperty, value); } public LinearGradientBrush? Value { get => GetValue(ValueProperty); private set => SetValue(ValueProperty, value); } public static Action? GetColorTextFormatter(GradientColorPicker colorPicker) { return colorPicker.GetValue(ColorTextFormatterProperty); } public static void SetPresetColor(GradientColorPicker colorPicker, Func formatter) { colorPicker.SetValue(ColorTextFormatterProperty, formatter); } #endregion #region 公共事件定义 /// /// Keep distributing as long as there are changes /// public event EventHandler? GradientValueChanged; /// /// Dispatched once when Flyout is closed /// public event EventHandler? ValueSelected; #endregion #region 内部事件定义 internal static readonly StyledProperty InactiveStopTextColorProperty = AvaloniaProperty.Register(nameof(InactiveStopTextColor)); internal IBrush? InactiveStopTextColor { get => GetValue(InactiveStopTextColorProperty); set => SetValue(InactiveStopTextColorProperty, value); } #endregion #region 内部属性定义 internal static readonly DirectProperty ActivatedStopIndexProperty = AvaloniaProperty.RegisterDirect( nameof(ActivatedStopIndex), o => o.ActivatedStopIndex, (o, v) => o.ActivatedStopIndex = v); private int? _activatedStopIndex; internal int? ActivatedStopIndex { get => _activatedStopIndex; set => SetAndRaise(ActivatedStopIndexProperty, ref _activatedStopIndex, value); } #endregion private GradientColorPickerView? _presenter; private CompositeDisposable? _flyoutBindingDisposables; private WrapPanel? _textPanel; private IDisposable? _activeStopIndexChangedDisposable; private int? _latestActivatedStopIndex; private IDisposable? _emptyTextBindingDisposable; private ColorBlock? _colorIndicator; static GradientColorPicker() { AffectsMeasure(ColorTextFormatterProperty); } protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) { base.OnPropertyChanged(change); if (change.Property == ValueProperty) { NotifyValueChanged(new GradientColorChangedEventArgs(change.GetOldValue(), change.GetNewValue())); } if (change.Property == ColorTextFormatterProperty) { GenerateValueText(); } if (this.IsAttachedToVisualTree()) { if (change.Property == DefaultValueProperty) { Value ??= DefaultValue; } else if (change.Property == ValueProperty) { if (Value != null) { Value.StartPoint = new RelativePoint(0, 0.5, RelativeUnit.Relative); Value.EndPoint = new RelativePoint(1, 0.5, RelativeUnit.Relative); } GenerateValueText(); GenerateColorBlockBackground(); } if (change.Property == ActivatedStopIndexProperty) { HandleGradientActiveStopChanged(ActivatedStopIndex); } } } protected override void GenerateValueText() { if (_textPanel == null || !IsShowText) { return; } var textRunCount = _textPanel.Children.Count; if (Value == null || Value.GradientStops.Count == 0) { _textPanel.Children.Clear(); } else { var stops = Value.GradientStops; var stopsCount = stops.Count; if (stopsCount < textRunCount) { // 删除多于的 var delta = textRunCount - stopsCount; for (var i = 0; i < delta; i++) { _textPanel.Children.Remove(_textPanel.Children.Last()); } } else if (stopsCount > textRunCount) { var delta = stopsCount - textRunCount; for (var i = 0; i < delta; i++) { var textBlock = new AvaloniaTextBlock(); _textPanel.Children.Add(textBlock); } } } textRunCount = _textPanel.Children.Count; if (Value == null || Value.GradientStops.Count == 0) { var emptyTextBlock = new AvaloniaTextBlock(); _emptyTextBindingDisposable?.Dispose(); _emptyTextBindingDisposable = BindUtils.RelayBind(this, EmptyColorTextProperty, emptyTextBlock, AvaloniaTextBlock.TextProperty); _textPanel.Children.Add(emptyTextBlock); } else { var customFormatter = GetColorTextFormatter(this); if (customFormatter != null) { customFormatter(Value, Format, _textPanel.Children); } else { for (var i = 0; i < textRunCount; i++) { var stop = Value.GradientStops[i]; var textBlock = _textPanel.Children[i] as AvaloniaTextBlock; var colorText = FormatColor(stop.Color, Format); var percent = $"{stop.Offset * 100:0}%"; if (textBlock != null) { if (i < textRunCount - 1) { textBlock.Text = $"{colorText} {percent}, "; } else { textBlock.Text = $"{colorText} {percent}"; } } } } } } protected override void GenerateColorBlockBackground() { if (_colorIndicator != null) { _colorIndicator.SetCurrentValue(ColorBlock.IsEmptyColorModeProperty, false); } if (Value == null) { SetCurrentValue(ColorBlockBackgroundProperty, new SolidColorBrush(Colors.Transparent)); } else { SetCurrentValue(ColorBlockBackgroundProperty, Value); } } protected override Flyout CreatePickerFlyout() { var flyout = new GradientColorPickerFlyout(); flyout.IsDetectMouseClickEnabled = false; _flyoutBindingDisposables?.Dispose(); _flyoutBindingDisposables = new CompositeDisposable(6); _flyoutBindingDisposables.Add(BindUtils.RelayBind(this, IsMotionEnabledProperty, flyout, GradientColorPickerFlyout.IsMotionEnabledProperty)); _flyoutBindingDisposables.Add(BindUtils.RelayBind(this, IsClearEnabledProperty, flyout, GradientColorPickerFlyout.IsClearEnabledProperty)); _flyoutBindingDisposables.Add(BindUtils.RelayBind(this, FormatProperty, flyout, GradientColorPickerFlyout.FormatProperty)); _flyoutBindingDisposables.Add(BindUtils.RelayBind(this, IsAlphaEnabledProperty, flyout, GradientColorPickerFlyout.IsAlphaEnabledProperty)); _flyoutBindingDisposables.Add(BindUtils.RelayBind(this, IsFormatEnabledProperty, flyout, GradientColorPickerFlyout.IsFormatEnabledProperty)); _flyoutBindingDisposables.Add(BindUtils.RelayBind(this, IsPaletteGroupEnabledProperty, flyout, GradientColorPickerFlyout.IsPaletteGroupEnabledProperty)); _flyoutBindingDisposables.Add(BindUtils.RelayBind(this, PaletteGroupProperty, flyout, GradientColorPickerFlyout.PaletteGroupProperty)); return flyout; } protected override void NotifyFlyoutPresenterCreated(Control control) { if (control is FlyoutPresenter flyoutPresenter && flyoutPresenter.Content is GradientColorPickerView presenter) { _presenter = presenter; } } private void HandleColorPickerViewValueChanged(object? sender, GradientColorChangedEventArgs args) { if (IsFlyoutOpen) { SetCurrentValue(ValueProperty, args.NewColor); } } protected override void NotifyFlyoutOpened() { if (_presenter != null) { _presenter.GradientValueChanged += HandleColorPickerViewValueChanged; _presenter.ColorValueCleared += HandleColorCleared; var effectiveColor = Value ?? DefaultValue; _activeStopIndexChangedDisposable = BindUtils.RelayBind(_presenter, GradientColorPickerView.ActivatedStopIndexProperty, this, ActivatedStopIndexProperty); if (effectiveColor != null) { _presenter.SetCurrentValue(GradientColorPickerView.ValueProperty, effectiveColor); _presenter.SetCurrentValue(GradientColorPickerView.ActivatedStopIndexProperty, _latestActivatedStopIndex ?? 0); } } } protected override void NotifyFlyoutClosed() { if (_presenter != null) { if (Value != null) { ValueSelected?.Invoke(this, new GradientColorSelectedEventArgs(Value)); } _latestActivatedStopIndex = ActivatedStopIndex; _presenter.GradientValueChanged -= HandleColorPickerViewValueChanged; _presenter.ColorValueCleared -= HandleColorCleared; _activeStopIndexChangedDisposable?.Dispose(); _activeStopIndexChangedDisposable = null; SetCurrentValue(ActivatedStopIndexProperty, null); } } private void HandleGradientActiveStopChanged(int? index) { if (_textPanel != null) { for (var i = 0; i < _textPanel.Children.Count; i++) { var textBlock = _textPanel.Children[i] as AvaloniaTextBlock; if (textBlock != null) { if (i == index) { textBlock.Foreground = InactiveStopTextColor; } else { textBlock.Foreground = Foreground; } } } } } internal void NotifyValueChanged(GradientColorChangedEventArgs e) { GradientValueChanged?.Invoke(this, e); } protected override void OnApplyTemplate(TemplateAppliedEventArgs e) { base.OnApplyTemplate(e); Value ??= DefaultValue; _textPanel = e.NameScope.Find(ColorPickerThemeConstants.ColorTextPanelPart); _colorIndicator = e.NameScope.Find(ColorPickerThemeConstants.ColorIndicatorPart); GenerateValueText(); GenerateColorBlockBackground(); } private void HandleColorCleared(object? sender, EventArgs args) { if (_colorIndicator != null) { _colorIndicator.SetCurrentValue(ColorBlock.IsEmptyColorModeProperty, true); _textPanel?.Children.Clear(); _emptyTextBindingDisposable?.Dispose(); var emptyTextBlock = new AvaloniaTextBlock(); _emptyTextBindingDisposable = BindUtils.RelayBind(this, EmptyColorTextProperty, emptyTextBlock, AvaloniaTextBlock.TextProperty); _textPanel?.Children.Add(emptyTextBlock); } } }