WARNING: THIS SITE IS A MIRROR OF GITHUB.COM / IT CANNOT LOGIN OR REGISTER ACCOUNTS / THE CONTENTS ARE PROVIDED AS-IS / THIS SITE ASSUMES NO RESPONSIBILITY FOR ANY DISPLAYED CONTENT OR LINKS / IF YOU FOUND SOMETHING MAY NOT GOOD FOR EVERYONE, CONTACT ADMIN AT ilovescratch@foxmail.com
Skip to content

Commit 6d02e3e

Browse files
committed
Add UI extension methods for Avalonia controls
Introduces extension classes for Color, ContentDialogService, Control, NavigationService, SnackbarService, and Symbol types in CrissCross.Avalonia.UI. These provide utility methods for color manipulation, dialog and snackbar display, navigation, control tree traversal, and symbol conversion to improve developer productivity and code readability.
1 parent 8814d9d commit 6d02e3e

File tree

6 files changed

+488
-0
lines changed

6 files changed

+488
-0
lines changed
Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
// Copyright (c) 2019-2025 ReactiveUI Association Incorporated. All rights reserved.
2+
// ReactiveUI Association Incorporated licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for full license information.
4+
5+
using Avalonia.Media;
6+
7+
namespace CrissCross.Avalonia.UI.Extensions;
8+
9+
/// <summary>
10+
/// Adds an extension for <see cref="Color"/> that allows manipulation with HSL and HSV color spaces.
11+
/// </summary>
12+
public static class ColorExtensions
13+
{
14+
private const float ByteMax = (float)byte.MaxValue;
15+
16+
/// <summary>
17+
/// Creates a <see cref="SolidColorBrush"/> from a <see cref="Color"/>.
18+
/// </summary>
19+
/// <param name="color">Input color.</param>
20+
/// <returns>Brush converted to color.</returns>
21+
public static SolidColorBrush ToBrush(this Color color) => new(color);
22+
23+
/// <summary>
24+
/// Creates a <see cref="SolidColorBrush"/> from a <see cref="Color"/> with defined brush opacity.
25+
/// </summary>
26+
/// <param name="color">Input color.</param>
27+
/// <param name="opacity">Degree of opacity.</param>
28+
/// <returns>Brush converted to color with modified opacity.</returns>
29+
public static SolidColorBrush ToBrush(this Color color, double opacity) => new(color, opacity);
30+
31+
/// <summary>
32+
/// Gets <see cref="Color"/> luminance based on HSL space.
33+
/// </summary>
34+
/// <param name="color">Input color.</param>
35+
/// <returns>Luminance value.</returns>
36+
public static double GetLuminance(this Color color)
37+
{
38+
var hsl = color.ToHsl();
39+
return hsl.L;
40+
}
41+
42+
/// <summary>
43+
/// Gets <see cref="Color"/> brightness based on HSV space.
44+
/// </summary>
45+
/// <param name="color">Input color.</param>
46+
/// <returns>Brightness value.</returns>
47+
public static double GetBrightness(this Color color)
48+
{
49+
var hsv = color.ToHsv();
50+
return hsv.V;
51+
}
52+
53+
/// <summary>
54+
/// Gets <see cref="Color"/> hue based on HSV space.
55+
/// </summary>
56+
/// <param name="color">Input color.</param>
57+
/// <returns>Hue value.</returns>
58+
public static double GetHue(this Color color)
59+
{
60+
var hsv = color.ToHsv();
61+
return hsv.H;
62+
}
63+
64+
/// <summary>
65+
/// Gets <see cref="Color"/> saturation based on HSV space.
66+
/// </summary>
67+
/// <param name="color">Input color.</param>
68+
/// <returns>Saturation value.</returns>
69+
public static double GetSaturation(this Color color)
70+
{
71+
var hsv = color.ToHsv();
72+
return hsv.S;
73+
}
74+
75+
/// <summary>
76+
/// Allows to change the luminance by a factor based on the HSL color space.
77+
/// </summary>
78+
/// <param name="color">Input color.</param>
79+
/// <param name="factor">The value of the luminance change factor from 100 to -100.</param>
80+
/// <returns>Updated <see cref="Color"/>.</returns>
81+
public static Color UpdateLuminance(this Color color, float factor)
82+
{
83+
ArgumentOutOfRangeException.ThrowIfGreaterThan(factor, 100f);
84+
ArgumentOutOfRangeException.ThrowIfLessThan(factor, -100f);
85+
86+
var hsl = color.ToHsl();
87+
var newL = Math.Clamp(hsl.L + (factor / 100.0), 0.0, 1.0);
88+
return HslColor.ToRgb(hsl.H, hsl.S, newL, hsl.A);
89+
}
90+
91+
/// <summary>
92+
/// Allows to change the saturation by a factor based on the HSL color space.
93+
/// </summary>
94+
/// <param name="color">Input color.</param>
95+
/// <param name="factor">The value of the saturation change factor from 100 to -100.</param>
96+
/// <returns>Updated <see cref="Color"/>.</returns>
97+
public static Color UpdateSaturation(this Color color, float factor)
98+
{
99+
ArgumentOutOfRangeException.ThrowIfGreaterThan(factor, 100f);
100+
ArgumentOutOfRangeException.ThrowIfLessThan(factor, -100f);
101+
102+
var hsl = color.ToHsl();
103+
var newS = Math.Clamp(hsl.S + (factor / 100.0), 0.0, 1.0);
104+
return HslColor.ToRgb(hsl.H, newS, hsl.L, hsl.A);
105+
}
106+
107+
/// <summary>
108+
/// Allows to change the brightness by a factor based on the HSV color space.
109+
/// </summary>
110+
/// <param name="color">Input color.</param>
111+
/// <param name="factor">The value of the brightness change factor from 100 to -100.</param>
112+
/// <returns>Updated <see cref="Color"/>.</returns>
113+
public static Color UpdateBrightness(this Color color, float factor)
114+
{
115+
ArgumentOutOfRangeException.ThrowIfGreaterThan(factor, 100f);
116+
ArgumentOutOfRangeException.ThrowIfLessThan(factor, -100f);
117+
118+
var hsv = color.ToHsv();
119+
var newV = Math.Clamp(hsv.V + (factor / 100.0), 0.0, 1.0);
120+
return HsvColor.ToRgb(hsv.H, hsv.S, newV, hsv.A);
121+
}
122+
123+
/// <summary>
124+
/// Converts a hex string to a Color.
125+
/// </summary>
126+
/// <param name="hex">The hex string.</param>
127+
/// <returns>The color, or null if parsing fails.</returns>
128+
public static Color? FromHex(string hex)
129+
{
130+
if (Color.TryParse(hex, out var color))
131+
{
132+
return color;
133+
}
134+
135+
return null;
136+
}
137+
138+
/// <summary>
139+
/// Converts a Color to a hex string.
140+
/// </summary>
141+
/// <param name="color">The color.</param>
142+
/// <returns>The hex string representation.</returns>
143+
public static string ToHex(this Color color) => color.ToString();
144+
145+
/// <summary>
146+
/// Converts a Color to a hex string without alpha.
147+
/// </summary>
148+
/// <param name="color">The color.</param>
149+
/// <returns>The hex string representation without alpha.</returns>
150+
public static string ToHexWithoutAlpha(this Color color) =>
151+
$"#{color.R:X2}{color.G:X2}{color.B:X2}";
152+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// Copyright (c) 2019-2025 ReactiveUI Association Incorporated. All rights reserved.
2+
// ReactiveUI Association Incorporated licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for full license information.
4+
5+
using CrissCross.Avalonia.UI.Controls;
6+
7+
namespace CrissCross.Avalonia.UI.Extensions;
8+
9+
/// <summary>
10+
/// Extensions for <see cref="IContentDialogService"/>.
11+
/// </summary>
12+
public static class ContentDialogServiceExtensions
13+
{
14+
/// <summary>
15+
/// Shows the content dialog asynchronously.
16+
/// </summary>
17+
/// <param name="contentDialogService">The content dialog service.</param>
18+
/// <param name="dialog">The dialog to show.</param>
19+
/// <returns>A task that represents the asynchronous operation. The task result contains the dialog result.</returns>
20+
public static Task<ContentDialogResult> ShowAsync(
21+
this IContentDialogService contentDialogService,
22+
ContentDialog dialog)
23+
{
24+
ArgumentNullException.ThrowIfNull(contentDialogService);
25+
return contentDialogService.ShowAsync(dialog, CancellationToken.None);
26+
}
27+
}
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
// Copyright (c) 2019-2025 ReactiveUI Association Incorporated. All rights reserved.
2+
// ReactiveUI Association Incorporated licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for full license information.
4+
5+
using Avalonia;
6+
using Avalonia.Controls;
7+
using Avalonia.VisualTree;
8+
9+
namespace CrissCross.Avalonia.UI.Extensions;
10+
11+
/// <summary>
12+
/// Extension methods for <see cref="Control"/>.
13+
/// </summary>
14+
public static class ControlExtensions
15+
{
16+
/// <summary>
17+
/// Finds a parent of the given type.
18+
/// </summary>
19+
/// <typeparam name="T">The type of parent to find.</typeparam>
20+
/// <param name="control">The control to start from.</param>
21+
/// <returns>The parent of the specified type, or null if not found.</returns>
22+
public static T? FindParent<T>(this Control control)
23+
where T : Control
24+
{
25+
ArgumentNullException.ThrowIfNull(control);
26+
27+
var parent = control.Parent;
28+
while (parent is not null)
29+
{
30+
if (parent is T typedParent)
31+
{
32+
return typedParent;
33+
}
34+
35+
parent = parent.Parent;
36+
}
37+
38+
return null;
39+
}
40+
41+
/// <summary>
42+
/// Finds a child of the given type.
43+
/// </summary>
44+
/// <typeparam name="T">The type of child to find.</typeparam>
45+
/// <param name="control">The control to start from.</param>
46+
/// <returns>The child of the specified type, or null if not found.</returns>
47+
public static T? FindChild<T>(this Control control)
48+
where T : Control
49+
{
50+
ArgumentNullException.ThrowIfNull(control);
51+
52+
if (control is not Panel panel)
53+
{
54+
return null;
55+
}
56+
57+
foreach (var child in panel.Children)
58+
{
59+
if (child is T typedChild)
60+
{
61+
return typedChild;
62+
}
63+
64+
if (child is Control childControl)
65+
{
66+
var result = childControl.FindChild<T>();
67+
if (result is not null)
68+
{
69+
return result;
70+
}
71+
}
72+
}
73+
74+
return null;
75+
}
76+
77+
/// <summary>
78+
/// Gets the bounds of the control relative to the root visual.
79+
/// </summary>
80+
/// <param name="control">The control.</param>
81+
/// <returns>The bounds in screen coordinates.</returns>
82+
public static Rect GetBoundsRelativeToRoot(this Control control)
83+
{
84+
ArgumentNullException.ThrowIfNull(control);
85+
86+
var root = control.GetVisualRoot();
87+
if (root is null)
88+
{
89+
return default;
90+
}
91+
92+
var transform = control.TransformToVisual((Visual)root);
93+
if (transform.HasValue)
94+
{
95+
return new Rect(control.Bounds.Size).TransformToAABB(transform.Value);
96+
}
97+
98+
return default;
99+
}
100+
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
// Copyright (c) 2019-2025 ReactiveUI Association Incorporated. All rights reserved.
2+
// ReactiveUI Association Incorporated licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for full license information.
4+
5+
namespace CrissCross.Avalonia.UI.Extensions;
6+
7+
/// <summary>
8+
/// Extensions for <see cref="INavigationService"/>.
9+
/// </summary>
10+
public static class NavigationServiceExtensions
11+
{
12+
/// <summary>
13+
/// Lets you navigate to the selected page based on it's type.
14+
/// </summary>
15+
/// <typeparam name="T">Type of the page.</typeparam>
16+
/// <param name="navigationService">The navigation service.</param>
17+
/// <returns><see langword="true"/> if the operation succeeds. <see langword="false"/> otherwise.</returns>
18+
public static bool Navigate<T>(this INavigationService navigationService)
19+
{
20+
ArgumentNullException.ThrowIfNull(navigationService);
21+
return navigationService.Navigate(typeof(T));
22+
}
23+
24+
/// <summary>
25+
/// Lets you navigate to the selected page based on it's type.
26+
/// </summary>
27+
/// <typeparam name="T">Type of the page.</typeparam>
28+
/// <param name="navigationService">The navigation service.</param>
29+
/// <param name="dataContext">DataContext <see cref="object"/>.</param>
30+
/// <returns><see langword="true"/> if the operation succeeds. <see langword="false"/> otherwise.</returns>
31+
public static bool Navigate<T>(this INavigationService navigationService, object? dataContext)
32+
{
33+
ArgumentNullException.ThrowIfNull(navigationService);
34+
return navigationService.Navigate(typeof(T), dataContext);
35+
}
36+
37+
/// <summary>
38+
/// Synchronously adds an element to the navigation stack and navigates current navigation Frame to the page represented by the element.
39+
/// </summary>
40+
/// <typeparam name="T">Type of control to be synchronously added to the navigation stack.</typeparam>
41+
/// <param name="navigationService">The navigation service.</param>
42+
/// <returns><see langword="true"/> if the operation succeeds. <see langword="false"/> otherwise.</returns>
43+
public static bool NavigateWithHierarchy<T>(this INavigationService navigationService)
44+
{
45+
ArgumentNullException.ThrowIfNull(navigationService);
46+
return navigationService.NavigateWithHierarchy(typeof(T));
47+
}
48+
49+
/// <summary>
50+
/// Synchronously adds an element to the navigation stack and navigates current navigation Frame to the page represented by the element.
51+
/// </summary>
52+
/// <typeparam name="T">Type of control to be synchronously added to the navigation stack.</typeparam>
53+
/// <param name="navigationService">The navigation service.</param>
54+
/// <param name="dataContext">DataContext <see cref="object"/>.</param>
55+
/// <returns><see langword="true"/> if the operation succeeds. <see langword="false"/> otherwise.</returns>
56+
public static bool NavigateWithHierarchy<T>(this INavigationService navigationService, object? dataContext)
57+
{
58+
ArgumentNullException.ThrowIfNull(navigationService);
59+
return navigationService.NavigateWithHierarchy(typeof(T), dataContext);
60+
}
61+
}

0 commit comments

Comments
 (0)