using System.Reactive.Disposables; using AtomUI.Animations; using AtomUI.Controls; using AtomUI.Data; using AtomUI.Media; using AtomUI.Theme; using AtomUI.Utils; using Avalonia; using Avalonia.Controls; using Avalonia.Controls.Converters; using Avalonia.Controls.Diagnostics; using Avalonia.Controls.Primitives; using Avalonia.Controls.Primitives.PopupPositioning; using Avalonia.Input.Raw; using Avalonia.Interactivity; using Avalonia.LogicalTree; using Avalonia.Media; using Avalonia.VisualTree; namespace AtomUI.Desktop.Controls; using AvaloniaButton = Avalonia.Controls.Button; using AtomUIFlyout = AtomUI.Desktop.Controls.Flyout; public enum ColorPickerValueSyncMode { Immediate, OnCompleted } public abstract class AbstractColorPicker : AvaloniaButton, ISizeTypeAware, IMotionAwareControl, IControlSharedTokenResourcesHost { #region 公共属性定义 public static readonly StyledProperty FormatProperty = AbstractColorPickerView.FormatProperty.AddOwner(); public static readonly StyledProperty TriggerTypeProperty = FlyoutStateHelper.TriggerTypeProperty.AddOwner(); public static readonly StyledProperty IsShowArrowProperty = ArrowDecoratedBox.IsShowArrowProperty.AddOwner(); public static readonly StyledProperty IsPointAtCenterProperty = AtomUIFlyout.IsPointAtCenterProperty.AddOwner(); public static readonly StyledProperty PlacementProperty = Popup.PlacementProperty.AddOwner(); public static readonly StyledProperty PlacementGravityProperty = Popup.PlacementGravityProperty.AddOwner(); public static readonly StyledProperty MarginToAnchorProperty = Popup.MarginToAnchorProperty.AddOwner(); public static readonly StyledProperty MouseEnterDelayProperty = FlyoutStateHelper.MouseEnterDelayProperty.AddOwner(); public static readonly StyledProperty MouseLeaveDelayProperty = FlyoutStateHelper.MouseLeaveDelayProperty.AddOwner(); public static readonly StyledProperty IsAlphaEnabledProperty = AbstractColorPickerView.IsAlphaEnabledProperty.AddOwner(); public static readonly StyledProperty IsFormatEnabledProperty = AbstractColorPickerView.IsFormatEnabledProperty.AddOwner(); public static readonly StyledProperty IsShowTextProperty = AvaloniaProperty.Register(nameof(IsShowText)); public static readonly StyledProperty IsClearEnabledProperty = AbstractColorPickerView.IsClearEnabledProperty.AddOwner(); public static readonly StyledProperty SizeTypeProperty = SizeTypeControlProperty.SizeTypeProperty.AddOwner(); public static readonly StyledProperty IsMotionEnabledProperty = MotionAwareControlProperty.IsMotionEnabledProperty.AddOwner(); public static readonly StyledProperty EmptyColorTextProperty = AvaloniaProperty.Register(nameof(EmptyColorText), string.Empty); public static readonly StyledProperty IsPaletteGroupEnabledProperty = AvaloniaProperty.Register(nameof(IsPaletteGroupEnabled)); public static readonly StyledProperty?> PaletteGroupProperty = ColorPickerPaletteGroup.PaletteGroupProperty.AddOwner(); public ColorFormat Format { get => GetValue(FormatProperty); set => SetValue(FormatProperty, value); } public FlyoutTriggerType TriggerType { get => GetValue(TriggerTypeProperty); set => SetValue(TriggerTypeProperty, value); } public bool IsShowArrow { get => GetValue(IsShowArrowProperty); set => SetValue(IsShowArrowProperty, value); } public bool IsPointAtCenter { get => GetValue(IsPointAtCenterProperty); set => SetValue(IsPointAtCenterProperty, value); } public PlacementMode Placement { get => GetValue(PlacementProperty); set => SetValue(PlacementProperty, value); } public PopupGravity PlacementGravity { get => GetValue(PlacementGravityProperty); set => SetValue(PlacementGravityProperty, value); } public double MarginToAnchor { get => GetValue(MarginToAnchorProperty); set => SetValue(MarginToAnchorProperty, value); } public int MouseEnterDelay { get => GetValue(MouseEnterDelayProperty); set => SetValue(MouseEnterDelayProperty, value); } public int MouseLeaveDelay { get => GetValue(MouseLeaveDelayProperty); set => SetValue(MouseLeaveDelayProperty, value); } public bool IsAlphaEnabled { get => GetValue(IsAlphaEnabledProperty); set => SetValue(IsAlphaEnabledProperty, value); } public bool IsFormatEnabled { get => GetValue(IsFormatEnabledProperty); set => SetValue(IsFormatEnabledProperty, value); } public bool IsShowText { get => GetValue(IsShowTextProperty); set => SetValue(IsShowTextProperty, value); } public bool IsClearEnabled { get => GetValue(IsClearEnabledProperty); set => SetValue(IsClearEnabledProperty, value); } public SizeType SizeType { get => GetValue(SizeTypeProperty); set => SetValue(SizeTypeProperty, value); } public bool IsMotionEnabled { get => GetValue(IsMotionEnabledProperty); set => SetValue(IsMotionEnabledProperty, value); } public string EmptyColorText { get => GetValue(EmptyColorTextProperty); set => SetValue(EmptyColorTextProperty, value); } public bool IsPaletteGroupEnabled { get => GetValue(IsPaletteGroupEnabledProperty); set => SetValue(IsPaletteGroupEnabledProperty, value); } public List? PaletteGroup { get => GetValue(PaletteGroupProperty); set => SetValue(PaletteGroupProperty, value); } #endregion #region 内部属性定义 internal static readonly DirectProperty ColorBlockBackgroundProperty = AvaloniaProperty.RegisterDirect( nameof(ColorBlockBackground), o => o.ColorBlockBackground, (o, v) => o.ColorBlockBackground = v); private IBrush? _colorBlockBackground; internal IBrush? ColorBlockBackground { get => _colorBlockBackground; set => SetAndRaise(ColorBlockBackgroundProperty, ref _colorBlockBackground, value); } Control IControlSharedTokenResourcesHost.HostControl => this; string IControlSharedTokenResourcesHost.TokenId => ColorPickerToken.ID; #endregion private readonly FlyoutStateHelper _flyoutStateHelper; private protected Flyout? PickerFlyout; private protected bool IsFlyoutOpen; private CompositeDisposable? _flyoutBindingDisposables; private CompositeDisposable? _flyoutHelperBindingDisposables; static AbstractColorPicker() { AffectsMeasure(IsShowTextProperty, FormatProperty, ColorBlockBackgroundProperty); } public AbstractColorPicker() { this.RegisterResources(); _flyoutStateHelper = new FlyoutStateHelper(); _flyoutStateHelper.FlyoutAboutToShow += HandleFlyoutAboutToShow; _flyoutStateHelper.FlyoutAboutToClose += HandleFlyoutAboutToClose; _flyoutStateHelper.FlyoutOpened += HandleFlyoutOpened; _flyoutStateHelper.FlyoutClosed += HandleFlyoutClosed; _flyoutStateHelper.OpenFlyoutPredicate = FlyoutOpenPredicate; _flyoutStateHelper.ClickHideFlyoutPredicate = ClickHideFlyoutPredicate; } protected override void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e) { base.OnAttachedToLogicalTree(e); _flyoutHelperBindingDisposables?.Dispose(); _flyoutHelperBindingDisposables = new CompositeDisposable(3); _flyoutHelperBindingDisposables.Add(BindUtils.RelayBind(this, TriggerTypeProperty, _flyoutStateHelper, FlyoutStateHelper.TriggerTypeProperty)); _flyoutHelperBindingDisposables.Add(BindUtils.RelayBind(this, MouseEnterDelayProperty, _flyoutStateHelper, FlyoutStateHelper.MouseEnterDelayProperty)); _flyoutHelperBindingDisposables.Add(BindUtils.RelayBind(this, MouseLeaveDelayProperty, _flyoutStateHelper, FlyoutStateHelper.MouseLeaveDelayProperty)); } protected override void OnDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e) { base.OnDetachedFromLogicalTree(e); _flyoutHelperBindingDisposables?.Dispose(); } protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) { base.OnAttachedToVisualTree(e); _flyoutStateHelper.NotifyAttachedToVisualTree(); } protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e) { base.OnDetachedFromVisualTree(e); _flyoutStateHelper.NotifyDetachedFromVisualTree(); } protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) { base.OnPropertyChanged(change); if (IsLoaded) { if (change.Property == IsMotionEnabledProperty) { ConfigureTransitions(true); } } if (change.Property == IsShowTextProperty || change.Property == FormatProperty) { GenerateValueText(); } } protected abstract void GenerateValueText(); protected abstract void GenerateColorBlockBackground(); private void ConfigureTransitions(bool force) { if (IsMotionEnabled) { if (force || Transitions == null) { Transitions = [ TransitionUtils.CreateTransition(BorderBrushProperty) ]; } } else { Transitions = null; } } protected override void OnLoaded(RoutedEventArgs e) { base.OnLoaded(e); ConfigureTransitions(false); } protected override void OnUnloaded(RoutedEventArgs e) { base.OnUnloaded(e); Transitions = null; } protected override void OnApplyTemplate(TemplateAppliedEventArgs e) { base.OnApplyTemplate(e); if (PickerFlyout is null) { PickerFlyout = CreatePickerFlyout(); PickerFlyout.Opened += (sender, args) => { IsFlyoutOpen = true; UpdatePseudoClasses(); }; PickerFlyout.Closed += (sender, args) => { IsFlyoutOpen = false; UpdatePseudoClasses(); }; PickerFlyout.PresenterCreated += (sender, args) => { NotifyFlyoutPresenterCreated(args.Presenter); }; _flyoutStateHelper.Flyout = PickerFlyout; } _flyoutStateHelper.AnchorTarget = this; SetupFlyoutProperties(); } protected virtual void SetupFlyoutProperties() { if (PickerFlyout is not null) { _flyoutBindingDisposables?.Dispose(); _flyoutBindingDisposables = new CompositeDisposable(5); _flyoutBindingDisposables.Add(BindUtils.RelayBind(this, PlacementProperty, PickerFlyout, PopupFlyoutBase.PlacementProperty)); _flyoutBindingDisposables.Add(BindUtils.RelayBind(this, IsShowArrowProperty, PickerFlyout)); _flyoutBindingDisposables.Add(BindUtils.RelayBind(this, IsPointAtCenterProperty, PickerFlyout)); _flyoutBindingDisposables.Add(BindUtils.RelayBind(this, MarginToAnchorProperty, PickerFlyout)); _flyoutBindingDisposables.Add(BindUtils.RelayBind(this, IsMotionEnabledProperty, PickerFlyout, AtomUIFlyout.IsMotionEnabledProperty)); } } protected abstract Flyout CreatePickerFlyout(); protected virtual void NotifyFlyoutPresenterCreated(Control flyoutPresenter) { } protected virtual void NotifyFlyoutClosed() { } private void HandleFlyoutClosed(object? sender, EventArgs args) { NotifyFlyoutClosed(); } private void HandleFlyoutOpened(object? sender, EventArgs args) { NotifyFlyoutOpened(); } protected virtual void NotifyFlyoutOpened() { } private void HandleFlyoutAboutToClose(object? sender, EventArgs args) { NotifyFlyoutAboutToClose(); } protected virtual void NotifyFlyoutAboutToClose() { } private void HandleFlyoutAboutToShow(object? sender, EventArgs args) { NotifyFlyoutAboutToShow(); } protected virtual void NotifyFlyoutAboutToShow() { } public void ClosePickerFlyout() { _flyoutStateHelper.HideFlyout(true); } protected virtual bool FlyoutOpenPredicate(Point position) { if (!IsEnabled) { return false; } var pos = this.TranslatePoint(new Point(0, 0), TopLevel.GetTopLevel(this)!); if (!pos.HasValue) { return false; } var region = new Rect(pos.Value, Bounds.Size); return region.Contains(position); } protected virtual bool ClickHideFlyoutPredicate(IPopupHostProvider hostProvider, RawPointerEventArgs args) { if (hostProvider.PopupHost != args.Root) { if (args.Root is PopupRoot root) { var parent = root.Parent; while (parent != null) { if (parent == hostProvider.PopupHost) { return false; } if (parent is Control parentControl && parentControl.GetVisualRoot() == hostProvider.PopupHost) { return false; } parent = parent.Parent; } } return true; } return false; } protected virtual void UpdatePseudoClasses() { PseudoClasses.Set(StdPseudoClass.FlyoutOpen, IsFlyoutOpen); } public static string FormatColor(Color color, ColorFormat format) { if (format == ColorFormat.Hex) { return ColorToHexConverter.ToHexString(color, AlphaComponentPosition.Leading, false, true); } if (format == ColorFormat.Rgba) { return $"rgba({(int)color.R}, {(int)color.G}, {(int)color.B}, {color.GetAlphaF():0.00})"; } var hsvColor = color.ToHsv(); return $"hsva({hsvColor.H:0}, {hsvColor.S * 100:0}%, {hsvColor.V * 100:0}%, {hsvColor.A:0.00})"; } }