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 1d59b21

Browse files
authored
Merge pull request #208 from reactivemarbles/AddNewControlsToAvaloniaUI
Add UI extension methods for Avalonia controls
2 parents ed00690 + 6d02e3e commit 1d59b21

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)