committed by
GitHub
159 changed files with 5024 additions and 1219 deletions
@ -0,0 +1,113 @@ |
|||
#include "common.h" |
|||
|
|||
@interface CocoaThemeObserver : NSObject |
|||
-(id)initWithCallback:(IAvnActionCallback *)callback; |
|||
@end |
|||
|
|||
class PlatformSettings : public ComSingleObject<IAvnPlatformSettings, &IID_IAvnPlatformSettings> |
|||
{ |
|||
CocoaThemeObserver* observer; |
|||
|
|||
public: |
|||
FORWARD_IUNKNOWN() |
|||
virtual AvnPlatformThemeVariant GetPlatformTheme() override |
|||
{ |
|||
@autoreleasepool |
|||
{ |
|||
if (@available(macOS 10.14, *)) |
|||
{ |
|||
if (NSApplication.sharedApplication.effectiveAppearance.name == NSAppearanceNameAqua |
|||
|| NSApplication.sharedApplication.effectiveAppearance.name == NSAppearanceNameVibrantLight) { |
|||
return AvnPlatformThemeVariant::Light; |
|||
} else if (NSApplication.sharedApplication.effectiveAppearance.name == NSAppearanceNameDarkAqua |
|||
|| NSApplication.sharedApplication.effectiveAppearance.name == NSAppearanceNameVibrantDark) { |
|||
return AvnPlatformThemeVariant::Dark; |
|||
} else if (NSApplication.sharedApplication.effectiveAppearance.name == NSAppearanceNameAccessibilityHighContrastAqua |
|||
|| NSApplication.sharedApplication.effectiveAppearance.name == NSAppearanceNameAccessibilityHighContrastVibrantLight) { |
|||
return AvnPlatformThemeVariant::HighContrastLight; |
|||
} else if (NSApplication.sharedApplication.effectiveAppearance.name == NSAppearanceNameAccessibilityHighContrastDarkAqua |
|||
|| NSApplication.sharedApplication.effectiveAppearance.name == NSAppearanceNameAccessibilityHighContrastVibrantDark) { |
|||
return AvnPlatformThemeVariant::HighContrastDark; |
|||
} |
|||
} |
|||
return AvnPlatformThemeVariant::Light; |
|||
} |
|||
} |
|||
|
|||
virtual unsigned int GetAccentColor() override |
|||
{ |
|||
@autoreleasepool |
|||
{ |
|||
if (@available(macOS 10.14, *)) |
|||
{ |
|||
auto color = [NSColor controlAccentColor]; |
|||
return to_argb(color); |
|||
} |
|||
else |
|||
{ |
|||
return 0; |
|||
} |
|||
} |
|||
} |
|||
|
|||
virtual void RegisterColorsChange(IAvnActionCallback *callback) override |
|||
{ |
|||
if (@available(macOS 10.14, *)) |
|||
{ |
|||
observer = [[CocoaThemeObserver alloc] initWithCallback: callback]; |
|||
[[NSApplication sharedApplication] addObserver:observer forKeyPath:@"effectiveAppearance" options:NSKeyValueObservingOptionNew context:nil]; |
|||
} |
|||
} |
|||
|
|||
private: |
|||
unsigned int to_argb(NSColor* color) |
|||
{ |
|||
const CGFloat* components = CGColorGetComponents(color.CGColor); |
|||
unsigned int alpha = static_cast<unsigned int>(CGColorGetAlpha(color.CGColor) * 0xFF); |
|||
unsigned int red = static_cast<unsigned int>(components[0] * 0xFF); |
|||
unsigned int green = static_cast<unsigned int>(components[1] * 0xFF); |
|||
unsigned int blue = static_cast<unsigned int>(components[2] * 0xFF); |
|||
return (alpha << 24) + (red << 16) + (green << 8) + blue; |
|||
} |
|||
}; |
|||
|
|||
@implementation CocoaThemeObserver |
|||
{ |
|||
ComPtr<IAvnActionCallback> _callback; |
|||
} |
|||
- (id) initWithCallback:(IAvnActionCallback *)callback{ |
|||
self = [super init]; |
|||
if (self) { |
|||
_callback = callback; |
|||
} |
|||
return self; |
|||
} |
|||
|
|||
/*- (void)didChangeValueForKey:(NSString *)key { |
|||
if([key isEqualToString:@"effectiveAppearance"]) { |
|||
_callback->Run(); |
|||
} |
|||
else { |
|||
[super didChangeValueForKey:key]; |
|||
} |
|||
}*/ |
|||
|
|||
- (void)observeValueForKeyPath:(NSString *)keyPath |
|||
ofObject:(id)object |
|||
change:(NSDictionary *)change |
|||
context:(void *)context { |
|||
if([keyPath isEqualToString:@"effectiveAppearance"]) { |
|||
_callback->Run(); |
|||
} else { |
|||
[super observeValueForKeyPath:keyPath |
|||
ofObject:object |
|||
change:change |
|||
context:context]; |
|||
} |
|||
} |
|||
@end |
|||
|
|||
extern IAvnPlatformSettings* CreatePlatformSettings() |
|||
{ |
|||
return new PlatformSettings(); |
|||
} |
|||
@ -0,0 +1,14 @@ |
|||
using System; |
|||
|
|||
namespace Avalonia.Android |
|||
{ |
|||
public interface IActivityNavigationService |
|||
{ |
|||
event EventHandler<AndroidBackRequestedEventArgs> BackRequested; |
|||
} |
|||
|
|||
public class AndroidBackRequestedEventArgs : EventArgs |
|||
{ |
|||
public bool Handled { get; set; } |
|||
} |
|||
} |
|||
@ -0,0 +1,94 @@ |
|||
using System; |
|||
using Android; |
|||
using Android.Content; |
|||
using Android.Content.Res; |
|||
using Android.Graphics; |
|||
using Android.Provider; |
|||
using Android.Views.Accessibility; |
|||
using AndroidX.Core.Content.Resources; |
|||
using Avalonia.Media; |
|||
using Avalonia.Platform; |
|||
using Color = Avalonia.Media.Color; |
|||
|
|||
namespace Avalonia.Android.Platform; |
|||
|
|||
// TODO: ideally should be created per view/activity.
|
|||
internal class AndroidPlatformSettings : DefaultPlatformSettings |
|||
{ |
|||
private PlatformColorValues _latestValues; |
|||
|
|||
public AndroidPlatformSettings() |
|||
{ |
|||
_latestValues = base.GetColorValues(); |
|||
} |
|||
|
|||
public override PlatformColorValues GetColorValues() |
|||
{ |
|||
return _latestValues; |
|||
} |
|||
|
|||
internal void OnViewConfigurationChanged(Context context) |
|||
{ |
|||
if (context.Resources?.Configuration is null) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
var systemTheme = (context.Resources.Configuration.UiMode & UiMode.NightMask) switch |
|||
{ |
|||
UiMode.NightYes => PlatformThemeVariant.Dark, |
|||
UiMode.NightNo => PlatformThemeVariant.Light, |
|||
_ => throw new ArgumentOutOfRangeException() |
|||
}; |
|||
|
|||
if (OperatingSystem.IsAndroidVersionAtLeast(31)) |
|||
{ |
|||
// See https://developer.android.com/reference/android/R.color
|
|||
var accent1 = context.Resources.GetColor(17170494, context.Theme); // Resource.Color.SystemAccent1500
|
|||
var accent2 = context.Resources.GetColor(17170507, context.Theme); // Resource.Color.SystemAccent2500
|
|||
var accent3 = context.Resources.GetColor(17170520, context.Theme); // Resource.Color.SystemAccent3500
|
|||
|
|||
_latestValues = new PlatformColorValues |
|||
{ |
|||
ThemeVariant = systemTheme, |
|||
ContrastPreference = IsHighContrast(context), |
|||
AccentColor1 = new Color(accent1.A, accent1.R, accent1.G, accent1.B), |
|||
AccentColor2 = new Color(accent2.A, accent2.R, accent2.G, accent2.B), |
|||
AccentColor3 = new Color(accent3.A, accent3.R, accent3.G, accent3.B), |
|||
}; |
|||
} |
|||
else if (OperatingSystem.IsAndroidVersionAtLeast(23)) |
|||
{ |
|||
// See https://developer.android.com/reference/android/R.attr
|
|||
var array = context.Theme.ObtainStyledAttributes(new[] { 16843829 }); // Resource.Attribute.ColorAccent
|
|||
var accent = array.GetColor(0, 0); |
|||
|
|||
_latestValues = new PlatformColorValues |
|||
{ |
|||
ThemeVariant = systemTheme, |
|||
ContrastPreference = IsHighContrast(context), |
|||
AccentColor1 = new Color(accent.A, accent.R, accent.G, accent.B) |
|||
}; |
|||
array.Recycle(); |
|||
} |
|||
else |
|||
{ |
|||
_latestValues = _latestValues with { ThemeVariant = systemTheme }; |
|||
} |
|||
|
|||
OnColorValuesChanged(_latestValues); |
|||
} |
|||
|
|||
private static ColorContrastPreference IsHighContrast(Context context) |
|||
{ |
|||
try |
|||
{ |
|||
return Settings.Secure.GetInt(context.ContentResolver, "high_text_contrast_enabled", 0) == 1 |
|||
? ColorContrastPreference.High : ColorContrastPreference.NoPreference; |
|||
} |
|||
catch |
|||
{ |
|||
return ColorContrastPreference.NoPreference; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,28 @@ |
|||
using System; |
|||
using Avalonia.Interactivity; |
|||
using Avalonia.Platform; |
|||
|
|||
namespace Avalonia.Android.Platform |
|||
{ |
|||
internal class AndroidSystemNavigationManager : ISystemNavigationManager |
|||
{ |
|||
public event EventHandler<RoutedEventArgs> BackRequested; |
|||
|
|||
public AndroidSystemNavigationManager(IActivityNavigationService? navigationService) |
|||
{ |
|||
if(navigationService != null) |
|||
{ |
|||
navigationService.BackRequested += OnBackRequested; |
|||
} |
|||
} |
|||
|
|||
private void OnBackRequested(object sender, AndroidBackRequestedEventArgs e) |
|||
{ |
|||
var routedEventArgs = new RoutedEventArgs(); |
|||
|
|||
BackRequested?.Invoke(this, routedEventArgs); |
|||
|
|||
e.Handled = routedEventArgs.Handled; |
|||
} |
|||
} |
|||
} |
|||
@ -1,275 +0,0 @@ |
|||
using System; |
|||
using System.Collections; |
|||
using System.Collections.Generic; |
|||
using System.Runtime.CompilerServices; |
|||
using Avalonia.Utilities; |
|||
|
|||
namespace Avalonia.Media.TextFormatting |
|||
{ |
|||
public readonly struct CharacterBufferRange : IReadOnlyList<char> |
|||
{ |
|||
/// <summary>
|
|||
/// Getting an empty character string
|
|||
/// </summary>
|
|||
public static CharacterBufferRange Empty => new CharacterBufferRange(); |
|||
|
|||
/// <summary>
|
|||
/// Construct <see cref="CharacterBufferRange"/> from character array
|
|||
/// </summary>
|
|||
/// <param name="characterArray">character array</param>
|
|||
/// <param name="offsetToFirstChar">character buffer offset to the first character</param>
|
|||
/// <param name="characterLength">character length</param>
|
|||
public CharacterBufferRange( |
|||
char[] characterArray, |
|||
int offsetToFirstChar, |
|||
int characterLength |
|||
) |
|||
: this( |
|||
new CharacterBufferReference(characterArray, offsetToFirstChar), |
|||
characterLength |
|||
) |
|||
{ } |
|||
|
|||
/// <summary>
|
|||
/// Construct <see cref="CharacterBufferRange"/> from string
|
|||
/// </summary>
|
|||
/// <param name="characterString">character string</param>
|
|||
/// <param name="offsetToFirstChar">character buffer offset to the first character</param>
|
|||
/// <param name="characterLength">character length</param>
|
|||
public CharacterBufferRange( |
|||
string characterString, |
|||
int offsetToFirstChar, |
|||
int characterLength |
|||
) |
|||
: this( |
|||
new CharacterBufferReference(characterString, offsetToFirstChar), |
|||
characterLength |
|||
) |
|||
{ } |
|||
|
|||
/// <summary>
|
|||
/// Construct a <see cref="CharacterBufferRange"/> from <see cref="CharacterBufferReference"/>
|
|||
/// </summary>
|
|||
/// <param name="characterBufferReference">character buffer reference</param>
|
|||
/// <param name="characterLength">number of characters</param>
|
|||
public CharacterBufferRange( |
|||
CharacterBufferReference characterBufferReference, |
|||
int characterLength |
|||
) |
|||
{ |
|||
if (characterLength < 0) |
|||
{ |
|||
throw new ArgumentOutOfRangeException("characterLength", "ParameterCannotBeNegative"); |
|||
} |
|||
|
|||
int maxLength = characterBufferReference.CharacterBuffer.Length > 0 ? |
|||
characterBufferReference.CharacterBuffer.Length - characterBufferReference.OffsetToFirstChar : |
|||
0; |
|||
|
|||
if (characterLength > maxLength) |
|||
{ |
|||
throw new ArgumentOutOfRangeException("characterLength", $"ParameterCannotBeGreaterThan {maxLength}"); |
|||
} |
|||
|
|||
CharacterBufferReference = characterBufferReference; |
|||
Length = characterLength; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Construct a <see cref="CharacterBufferRange"/> from part of another <see cref="CharacterBufferRange"/>
|
|||
/// </summary>
|
|||
internal CharacterBufferRange( |
|||
CharacterBufferRange characterBufferRange, |
|||
int offsetToFirstChar, |
|||
int characterLength |
|||
) : |
|||
this( |
|||
characterBufferRange.CharacterBuffer, |
|||
characterBufferRange.OffsetToFirstChar + offsetToFirstChar, |
|||
characterLength |
|||
) |
|||
{ } |
|||
|
|||
|
|||
/// <summary>
|
|||
/// Construct a <see cref="CharacterBufferRange"/> from string
|
|||
/// </summary>
|
|||
internal CharacterBufferRange( |
|||
string charString |
|||
) : |
|||
this( |
|||
charString, |
|||
0, |
|||
charString.Length |
|||
) |
|||
{ } |
|||
|
|||
|
|||
/// <summary>
|
|||
/// Construct <see cref="CharacterBufferRange"/> from memory buffer
|
|||
/// </summary>
|
|||
internal CharacterBufferRange( |
|||
ReadOnlyMemory<char> charBuffer, |
|||
int offsetToFirstChar, |
|||
int characterLength |
|||
) : |
|||
this( |
|||
new CharacterBufferReference(charBuffer, offsetToFirstChar), |
|||
characterLength |
|||
) |
|||
{ } |
|||
|
|||
|
|||
/// <summary>
|
|||
/// Construct a <see cref="CharacterBufferRange"/> by extracting text info from a text run
|
|||
/// </summary>
|
|||
internal CharacterBufferRange(TextRun textRun) |
|||
{ |
|||
CharacterBufferReference = textRun.CharacterBufferReference; |
|||
Length = textRun.Length; |
|||
} |
|||
|
|||
public char this[int index] |
|||
{ |
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
get |
|||
{ |
|||
#if DEBUG
|
|||
if (index.CompareTo(0) < 0 || index.CompareTo(Length) > 0) |
|||
{ |
|||
throw new ArgumentOutOfRangeException(nameof(index)); |
|||
} |
|||
#endif
|
|||
return CharacterBuffer.Span[CharacterBufferReference.OffsetToFirstChar + index]; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets a reference to the character buffer
|
|||
/// </summary>
|
|||
public CharacterBufferReference CharacterBufferReference { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the number of characters in text source character store
|
|||
/// </summary>
|
|||
public int Length { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets a span from the character buffer range
|
|||
/// </summary>
|
|||
public ReadOnlySpan<char> Span => CharacterBuffer.Span.Slice(OffsetToFirstChar, Length); |
|||
|
|||
/// <summary>
|
|||
/// Gets the character memory buffer
|
|||
/// </summary>
|
|||
internal ReadOnlyMemory<char> CharacterBuffer => CharacterBufferReference.CharacterBuffer; |
|||
|
|||
/// <summary>
|
|||
/// Gets the character offset relative to the beginning of buffer to
|
|||
/// the first character of the run
|
|||
/// </summary>
|
|||
internal int OffsetToFirstChar => CharacterBufferReference.OffsetToFirstChar; |
|||
|
|||
/// <summary>
|
|||
/// Indicate whether the character buffer range is empty
|
|||
/// </summary>
|
|||
internal bool IsEmpty => CharacterBuffer.Length == 0 || Length <= 0; |
|||
|
|||
internal CharacterBufferRange Take(int length) |
|||
{ |
|||
if (IsEmpty) |
|||
{ |
|||
return this; |
|||
} |
|||
|
|||
if (length > Length) |
|||
{ |
|||
throw new ArgumentOutOfRangeException(nameof(length)); |
|||
} |
|||
|
|||
return new CharacterBufferRange(CharacterBufferReference, length); |
|||
} |
|||
|
|||
internal CharacterBufferRange Skip(int length) |
|||
{ |
|||
if (IsEmpty) |
|||
{ |
|||
return this; |
|||
} |
|||
|
|||
if (length > Length) |
|||
{ |
|||
throw new ArgumentOutOfRangeException(nameof(length)); |
|||
} |
|||
|
|||
if (length == Length) |
|||
{ |
|||
return new CharacterBufferRange(new CharacterBufferReference(), 0); |
|||
} |
|||
|
|||
var characterBufferReference = new CharacterBufferReference(CharacterBuffer, OffsetToFirstChar + length); |
|||
|
|||
return new CharacterBufferRange(characterBufferReference, Length - length); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Compute hash code
|
|||
/// </summary>
|
|||
public override int GetHashCode() |
|||
{ |
|||
return CharacterBufferReference.GetHashCode() ^ Length; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Test equality with the input object
|
|||
/// </summary>
|
|||
/// <param name="obj"> The object to test </param>
|
|||
public override bool Equals(object? obj) |
|||
{ |
|||
if (obj is CharacterBufferRange range) |
|||
{ |
|||
return Equals(range); |
|||
} |
|||
|
|||
return false; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Test equality with the input CharacterBufferRange
|
|||
/// </summary>
|
|||
/// <param name="value"> The CharacterBufferRange value to test </param>
|
|||
public bool Equals(CharacterBufferRange value) |
|||
{ |
|||
return CharacterBufferReference.Equals(value.CharacterBufferReference) |
|||
&& Length == value.Length; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Compare two CharacterBufferRange for equality
|
|||
/// </summary>
|
|||
/// <param name="left">left operand</param>
|
|||
/// <param name="right">right operand</param>
|
|||
/// <returns>whether or not two operands are equal</returns>
|
|||
public static bool operator ==(CharacterBufferRange left, CharacterBufferRange right) |
|||
{ |
|||
return left.Equals(right); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Compare two CharacterBufferRange for inequality
|
|||
/// </summary>
|
|||
/// <param name="left">left operand</param>
|
|||
/// <param name="right">right operand</param>
|
|||
/// <returns>whether or not two operands are equal</returns>
|
|||
public static bool operator !=(CharacterBufferRange left, CharacterBufferRange right) |
|||
{ |
|||
return !(left == right); |
|||
} |
|||
|
|||
int IReadOnlyCollection<char>.Count => Length; |
|||
|
|||
public IEnumerator<char> GetEnumerator() => new ImmutableReadOnlyListStructEnumerator<char>(this); |
|||
|
|||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); |
|||
} |
|||
} |
|||
@ -1,115 +0,0 @@ |
|||
using System; |
|||
|
|||
namespace Avalonia.Media.TextFormatting |
|||
{ |
|||
/// <summary>
|
|||
/// Text character buffer reference
|
|||
/// </summary>
|
|||
public readonly struct CharacterBufferReference : IEquatable<CharacterBufferReference> |
|||
{ |
|||
/// <summary>
|
|||
/// Construct character buffer reference from character array
|
|||
/// </summary>
|
|||
/// <param name="characterArray">character array</param>
|
|||
/// <param name="offsetToFirstChar">character buffer offset to the first character</param>
|
|||
public CharacterBufferReference(char[] characterArray, int offsetToFirstChar = 0) |
|||
: this(characterArray.AsMemory(), offsetToFirstChar) |
|||
{ } |
|||
|
|||
/// <summary>
|
|||
/// Construct character buffer reference from string
|
|||
/// </summary>
|
|||
/// <param name="characterString">character string</param>
|
|||
/// <param name="offsetToFirstChar">character buffer offset to the first character</param>
|
|||
public CharacterBufferReference(string characterString, int offsetToFirstChar = 0) |
|||
: this(characterString.AsMemory(), offsetToFirstChar) |
|||
{ } |
|||
|
|||
/// <summary>
|
|||
/// Construct character buffer reference from memory buffer
|
|||
/// </summary>
|
|||
internal CharacterBufferReference(ReadOnlyMemory<char> characterBuffer, int offsetToFirstChar = 0) |
|||
{ |
|||
if (offsetToFirstChar < 0) |
|||
{ |
|||
throw new ArgumentOutOfRangeException("offsetToFirstChar", "ParameterCannotBeNegative"); |
|||
} |
|||
|
|||
// maximum offset is one less than CharacterBuffer.Count, except that zero is always a valid offset
|
|||
// even in the case of an empty or null character buffer
|
|||
var maxOffset = characterBuffer.Length == 0 ? 0 : Math.Max(0, characterBuffer.Length - 1); |
|||
if (offsetToFirstChar > maxOffset) |
|||
{ |
|||
throw new ArgumentOutOfRangeException("offsetToFirstChar", $"ParameterCannotBeGreaterThan, {maxOffset}"); |
|||
} |
|||
|
|||
CharacterBuffer = characterBuffer; |
|||
OffsetToFirstChar = offsetToFirstChar; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the character memory buffer
|
|||
/// </summary>
|
|||
public ReadOnlyMemory<char> CharacterBuffer { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the character offset relative to the beginning of buffer to
|
|||
/// the first character of the run
|
|||
/// </summary>
|
|||
public int OffsetToFirstChar { get; } |
|||
|
|||
/// <summary>
|
|||
/// Compute hash code
|
|||
/// </summary>
|
|||
public override int GetHashCode() |
|||
{ |
|||
return CharacterBuffer.IsEmpty ? 0 : CharacterBuffer.GetHashCode(); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Test equality with the input object
|
|||
/// </summary>
|
|||
/// <param name="obj"> The object to test. </param>
|
|||
public override bool Equals(object? obj) |
|||
{ |
|||
if (obj is CharacterBufferReference reference) |
|||
{ |
|||
return Equals(reference); |
|||
} |
|||
|
|||
return false; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Test equality with the input CharacterBufferReference
|
|||
/// </summary>
|
|||
/// <param name="value"> The characterBufferReference value to test </param>
|
|||
public bool Equals(CharacterBufferReference value) |
|||
{ |
|||
return CharacterBuffer.Equals(value.CharacterBuffer); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Compare two CharacterBufferReference for equality
|
|||
/// </summary>
|
|||
/// <param name="left">left operand</param>
|
|||
/// <param name="right">right operand</param>
|
|||
/// <returns>whether or not two operands are equal</returns>
|
|||
public static bool operator ==(CharacterBufferReference left, CharacterBufferReference right) |
|||
{ |
|||
return left.Equals(right); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Compare two CharacterBufferReference for inequality
|
|||
/// </summary>
|
|||
/// <param name="left">left operand</param>
|
|||
/// <param name="right">right operand</param>
|
|||
/// <returns>whether or not two operands are equal</returns>
|
|||
public static bool operator !=(CharacterBufferReference left, CharacterBufferReference right) |
|||
{ |
|||
return !(left == right); |
|||
} |
|||
} |
|||
} |
|||
|
|||
@ -0,0 +1,68 @@ |
|||
using Avalonia.Media; |
|||
|
|||
namespace Avalonia.Platform; |
|||
|
|||
/// <summary>
|
|||
/// System theme variant or mode.
|
|||
/// </summary>
|
|||
public enum PlatformThemeVariant |
|||
{ |
|||
Light, |
|||
Dark |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// System high contrast preference.
|
|||
/// </summary>
|
|||
public enum ColorContrastPreference |
|||
{ |
|||
NoPreference, |
|||
High |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Information about current system color values, including information about dark mode and accent colors.
|
|||
/// </summary>
|
|||
public record PlatformColorValues |
|||
{ |
|||
private static Color DefaultAccent => new(255, 0, 120, 215); |
|||
private Color _accentColor2, _accentColor3; |
|||
|
|||
/// <summary>
|
|||
/// System theme variant or mode.
|
|||
/// </summary>
|
|||
public PlatformThemeVariant ThemeVariant { get; init; } |
|||
|
|||
/// <summary>
|
|||
/// System high contrast preference.
|
|||
/// </summary>
|
|||
public ColorContrastPreference ContrastPreference { get; init; } |
|||
|
|||
/// <summary>
|
|||
/// Primary system accent color.
|
|||
/// </summary>
|
|||
public Color AccentColor1 { get; init; } |
|||
|
|||
/// <summary>
|
|||
/// Secondary system accent color. On some platforms can return the same value as <see cref="AccentColor1"/>.
|
|||
/// </summary>
|
|||
public Color AccentColor2 |
|||
{ |
|||
get => _accentColor2 != default ? _accentColor2 : AccentColor1; |
|||
init => _accentColor2 = value; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Tertiary system accent color. On some platforms can return the same value as <see cref="AccentColor1"/>.
|
|||
/// </summary>
|
|||
public Color AccentColor3 |
|||
{ |
|||
get => _accentColor3 != default ? _accentColor3 : AccentColor1; |
|||
init => _accentColor3 = value; |
|||
} |
|||
|
|||
public PlatformColorValues() |
|||
{ |
|||
AccentColor1 = DefaultAccent; |
|||
} |
|||
} |
|||
@ -0,0 +1,18 @@ |
|||
using System; |
|||
using Avalonia.Interactivity; |
|||
using Avalonia.Metadata; |
|||
|
|||
namespace Avalonia.Platform |
|||
{ |
|||
[Unstable] |
|||
public interface ITopLevelWithSystemNavigationManager |
|||
{ |
|||
ISystemNavigationManager SystemNavigationManager { get; } |
|||
} |
|||
|
|||
[Unstable] |
|||
public interface ISystemNavigationManager |
|||
{ |
|||
public event EventHandler<RoutedEventArgs>? BackRequested; |
|||
} |
|||
} |
|||
@ -1,105 +1,116 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.IO; |
|||
using System.Runtime.Serialization; |
|||
using System.Xml.Linq; |
|||
using System.Linq; |
|||
using System.Diagnostics.CodeAnalysis; |
|||
using System.Text; |
|||
|
|||
namespace Avalonia.Utilities |
|||
{ |
|||
#if !BUILDTASK
|
|||
#if !BUILDTASK
|
|||
public |
|||
#endif
|
|||
#endif
|
|||
static class AvaloniaResourcesIndexReaderWriter |
|||
{ |
|||
private const int LastKnownVersion = 1; |
|||
public static List<AvaloniaResourcesIndexEntry> Read(Stream stream) |
|||
private const int XmlLegacyVersion = 1; |
|||
private const int BinaryCurrentVersion = 2; |
|||
|
|||
public static List<AvaloniaResourcesIndexEntry> ReadIndex(Stream stream) |
|||
{ |
|||
var ver = new BinaryReader(stream).ReadInt32(); |
|||
if (ver > LastKnownVersion) |
|||
throw new Exception("Resources index format version is not known"); |
|||
|
|||
var assetDoc = XDocument.Load(stream); |
|||
XNamespace assetNs = assetDoc.Root!.Attribute("xmlns")!.Value; |
|||
List<AvaloniaResourcesIndexEntry> entries = |
|||
(from entry in assetDoc.Root.Element(assetNs + "Entries")!.Elements(assetNs + "AvaloniaResourcesIndexEntry") |
|||
select new AvaloniaResourcesIndexEntry |
|||
{ |
|||
Path = entry.Element(assetNs + "Path")!.Value, |
|||
Offset = int.Parse(entry.Element(assetNs + "Offset")!.Value), |
|||
Size = int.Parse(entry.Element(assetNs + "Size")!.Value) |
|||
}).ToList(); |
|||
using var reader = new BinaryReader(stream, Encoding.UTF8, leaveOpen: true); |
|||
|
|||
return entries; |
|||
var version = reader.ReadInt32(); |
|||
return version switch |
|||
{ |
|||
XmlLegacyVersion => ReadXmlIndex(), |
|||
BinaryCurrentVersion => ReadBinaryIndex(reader), |
|||
_ => throw new Exception($"Unknown resources index format version {version}") |
|||
}; |
|||
} |
|||
|
|||
[RequiresUnreferencedCode("AvaloniaResources uses Data Contract Serialization, which might require unreferenced code")] |
|||
public static void Write(Stream stream, List<AvaloniaResourcesIndexEntry> entries) |
|||
private static List<AvaloniaResourcesIndexEntry> ReadXmlIndex() |
|||
=> throw new NotSupportedException("Found legacy resources index format: please recompile your XAML files"); |
|||
|
|||
private static List<AvaloniaResourcesIndexEntry> ReadBinaryIndex(BinaryReader reader) |
|||
{ |
|||
new BinaryWriter(stream).Write(LastKnownVersion); |
|||
new DataContractSerializer(typeof(AvaloniaResourcesIndex)).WriteObject(stream, |
|||
new AvaloniaResourcesIndex() |
|||
{ |
|||
Entries = entries |
|||
var entryCount = reader.ReadInt32(); |
|||
var entries = new List<AvaloniaResourcesIndexEntry>(entryCount); |
|||
|
|||
for (var i = 0; i < entryCount; ++i) |
|||
{ |
|||
entries.Add(new AvaloniaResourcesIndexEntry { |
|||
Path = reader.ReadString(), |
|||
Offset = reader.ReadInt32(), |
|||
Size = reader.ReadInt32() |
|||
}); |
|||
} |
|||
|
|||
return entries; |
|||
} |
|||
|
|||
[RequiresUnreferencedCode("AvaloniaResources uses Data Contract Serialization, which might require unreferenced code")] |
|||
public static byte[] Create(Dictionary<string, byte[]> data) |
|||
public static void WriteIndex(Stream output, List<AvaloniaResourcesIndexEntry> entries) |
|||
{ |
|||
var sources = data.ToList(); |
|||
var offsets = new Dictionary<string, int>(); |
|||
var coffset = 0; |
|||
foreach (var s in sources) |
|||
using var writer = new BinaryWriter(output, Encoding.UTF8, leaveOpen: true); |
|||
|
|||
WriteIndex(writer, entries); |
|||
} |
|||
|
|||
private static void WriteIndex(BinaryWriter writer, List<AvaloniaResourcesIndexEntry> entries) |
|||
{ |
|||
writer.Write(BinaryCurrentVersion); |
|||
writer.Write(entries.Count); |
|||
|
|||
foreach (var entry in entries) |
|||
{ |
|||
offsets[s.Key] = coffset; |
|||
coffset += s.Value.Length; |
|||
writer.Write(entry.Path ?? string.Empty); |
|||
writer.Write(entry.Offset); |
|||
writer.Write(entry.Size); |
|||
} |
|||
var index = sources.Select(s => new AvaloniaResourcesIndexEntry |
|||
{ |
|||
Path = s.Key, |
|||
Size = s.Value.Length, |
|||
Offset = offsets[s.Key] |
|||
}).ToList(); |
|||
var output = new MemoryStream(); |
|||
var ms = new MemoryStream(); |
|||
AvaloniaResourcesIndexReaderWriter.Write(ms, index); |
|||
new BinaryWriter(output).Write((int)ms.Length); |
|||
ms.Position = 0; |
|||
ms.CopyTo(output); |
|||
foreach (var s in sources) |
|||
} |
|||
|
|||
public static void WriteResources(Stream output, List<(string Path, int Size, Func<Stream> Open)> resources) |
|||
{ |
|||
var entries = new List<AvaloniaResourcesIndexEntry>(resources.Count); |
|||
var offset = 0; |
|||
|
|||
foreach (var resource in resources) |
|||
{ |
|||
output.Write(s.Value,0,s.Value.Length); |
|||
entries.Add(new AvaloniaResourcesIndexEntry |
|||
{ |
|||
Path = resource.Path, |
|||
Offset = offset, |
|||
Size = resource.Size |
|||
}); |
|||
offset += resource.Size; |
|||
} |
|||
|
|||
return output.ToArray(); |
|||
} |
|||
} |
|||
using var writer = new BinaryWriter(output, Encoding.UTF8, leaveOpen: true); |
|||
writer.Write(0); // index size placeholder, overwritten below
|
|||
|
|||
[DataContract] |
|||
#if !BUILDTASK
|
|||
public |
|||
#endif
|
|||
class AvaloniaResourcesIndex |
|||
{ |
|||
[DataMember] |
|||
public List<AvaloniaResourcesIndexEntry> Entries { get; set; } = new List<AvaloniaResourcesIndexEntry>(); |
|||
var posBeforeEntries = output.Position; |
|||
WriteIndex(writer, entries); |
|||
|
|||
var posAfterEntries = output.Position; |
|||
var indexSize = (int) (posAfterEntries - posBeforeEntries); |
|||
output.Position = 0L; |
|||
writer.Write(indexSize); |
|||
output.Position = posAfterEntries; |
|||
|
|||
foreach (var resource in resources) |
|||
{ |
|||
using var resourceStream = resource.Open(); |
|||
resourceStream.CopyTo(output); |
|||
} |
|||
} |
|||
} |
|||
|
|||
[DataContract] |
|||
#if !BUILDTASK
|
|||
public |
|||
#endif
|
|||
class AvaloniaResourcesIndexEntry |
|||
{ |
|||
[DataMember] |
|||
public string? Path { get; set; } |
|||
|
|||
[DataMember] |
|||
|
|||
public int Offset { get; set; } |
|||
|
|||
[DataMember] |
|||
|
|||
public int Size { get; set; } |
|||
} |
|||
} |
|||
|
|||
@ -0,0 +1,77 @@ |
|||
using System; |
|||
using System.Threading.Tasks; |
|||
using Avalonia.Logging; |
|||
using Avalonia.Platform; |
|||
|
|||
namespace Avalonia.FreeDesktop; |
|||
|
|||
internal class DBusPlatformSettings : DefaultPlatformSettings |
|||
{ |
|||
private readonly IDBusSettings? _settings; |
|||
private PlatformColorValues? _lastColorValues; |
|||
|
|||
public DBusPlatformSettings() |
|||
{ |
|||
_settings = DBusHelper.TryInitialize()? |
|||
.CreateProxy<IDBusSettings>("org.freedesktop.portal.Desktop", "/org/freedesktop/portal/desktop"); |
|||
|
|||
if (_settings is not null) |
|||
{ |
|||
_ = _settings.WatchSettingChangedAsync(SettingsChangedHandler); |
|||
|
|||
_ = TryGetInitialValue(); |
|||
} |
|||
} |
|||
|
|||
public override PlatformColorValues GetColorValues() |
|||
{ |
|||
return _lastColorValues ?? base.GetColorValues(); |
|||
} |
|||
|
|||
private async Task TryGetInitialValue() |
|||
{ |
|||
var colorSchemeTask = _settings!.ReadAsync("org.freedesktop.appearance", "color-scheme"); |
|||
if (colorSchemeTask.Status == TaskStatus.RanToCompletion) |
|||
{ |
|||
_lastColorValues = GetColorValuesFromSetting(colorSchemeTask.Result); |
|||
} |
|||
else |
|||
{ |
|||
try |
|||
{ |
|||
var value = await colorSchemeTask; |
|||
_lastColorValues = GetColorValuesFromSetting(value); |
|||
OnColorValuesChanged(_lastColorValues); |
|||
} |
|||
catch (Exception ex) |
|||
{ |
|||
_lastColorValues = base.GetColorValues(); |
|||
Logger.TryGet(LogEventLevel.Error, LogArea.FreeDesktopPlatform)?.Log(this, "Unable to get setting value", ex); |
|||
} |
|||
} |
|||
} |
|||
|
|||
private void SettingsChangedHandler((string @namespace, string key, object value) tuple) |
|||
{ |
|||
if (tuple.@namespace == "org.freedesktop.appearance" |
|||
&& tuple.key == "color-scheme") |
|||
{ |
|||
/* |
|||
<member>0: No preference</member> |
|||
<member>1: Prefer dark appearance</member> |
|||
<member>2: Prefer light appearance</member> |
|||
*/ |
|||
_lastColorValues = GetColorValuesFromSetting(tuple.value); |
|||
OnColorValuesChanged(_lastColorValues); |
|||
} |
|||
} |
|||
|
|||
private static PlatformColorValues GetColorValuesFromSetting(object value) |
|||
{ |
|||
var isDark = value?.ToString() == "1"; |
|||
return new PlatformColorValues |
|||
{ |
|||
ThemeVariant = isDark ? PlatformThemeVariant.Dark : PlatformThemeVariant.Light |
|||
}; |
|||
} |
|||
} |
|||
@ -0,0 +1,16 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Threading.Tasks; |
|||
using Tmds.DBus; |
|||
|
|||
namespace Avalonia.FreeDesktop; |
|||
|
|||
[DBusInterface("org.freedesktop.portal.Settings")] |
|||
internal interface IDBusSettings : IDBusObject |
|||
{ |
|||
Task<(string @namespace, IDictionary<string, object>)> ReadAllAsync(string[] namespaces); |
|||
|
|||
Task<object> ReadAsync(string @namespace, string key); |
|||
|
|||
Task<IDisposable> WatchSettingChangedAsync(Action<(string @namespace, string key, object value)> handler, Action<Exception>? onError = null); |
|||
} |
|||
@ -0,0 +1,77 @@ |
|||
using System; |
|||
using Avalonia.Media; |
|||
using Avalonia.Native.Interop; |
|||
using Avalonia.Platform; |
|||
|
|||
namespace Avalonia.Native; |
|||
|
|||
internal class NativePlatformSettings : DefaultPlatformSettings |
|||
{ |
|||
private readonly IAvnPlatformSettings _platformSettings; |
|||
private PlatformColorValues _lastColorValues; |
|||
|
|||
public NativePlatformSettings(IAvnPlatformSettings platformSettings) |
|||
{ |
|||
_platformSettings = platformSettings; |
|||
platformSettings.RegisterColorsChange(new ColorsChangeCallback(this)); |
|||
} |
|||
|
|||
public override PlatformColorValues GetColorValues() |
|||
{ |
|||
var (theme, contrast) = _platformSettings.PlatformTheme switch |
|||
{ |
|||
AvnPlatformThemeVariant.Dark => (PlatformThemeVariant.Dark, ColorContrastPreference.NoPreference), |
|||
AvnPlatformThemeVariant.Light => (PlatformThemeVariant.Light, ColorContrastPreference.NoPreference), |
|||
AvnPlatformThemeVariant.HighContrastDark => (PlatformThemeVariant.Dark, ColorContrastPreference.High), |
|||
AvnPlatformThemeVariant.HighContrastLight => (PlatformThemeVariant.Dark, ColorContrastPreference.High), |
|||
_ => throw new ArgumentOutOfRangeException() |
|||
}; |
|||
var color = _platformSettings.AccentColor; |
|||
|
|||
if (color > 0) |
|||
{ |
|||
_lastColorValues = new PlatformColorValues |
|||
{ |
|||
ThemeVariant = theme, |
|||
ContrastPreference = contrast, |
|||
AccentColor1 = Color.FromUInt32(color) |
|||
}; |
|||
} |
|||
else |
|||
{ |
|||
_lastColorValues = new PlatformColorValues |
|||
{ |
|||
ThemeVariant = theme, |
|||
ContrastPreference = contrast |
|||
}; |
|||
} |
|||
|
|||
return _lastColorValues; |
|||
} |
|||
|
|||
public void OnColorValuesChanged() |
|||
{ |
|||
var oldColorValues = _lastColorValues; |
|||
var colorValues = GetColorValues(); |
|||
|
|||
if (oldColorValues != colorValues) |
|||
{ |
|||
OnColorValuesChanged(colorValues); |
|||
} |
|||
} |
|||
|
|||
private class ColorsChangeCallback : NativeCallbackBase, IAvnActionCallback |
|||
{ |
|||
private readonly NativePlatformSettings _settings; |
|||
|
|||
public ColorsChangeCallback(NativePlatformSettings settings) |
|||
{ |
|||
_settings = settings; |
|||
} |
|||
|
|||
public void Run() |
|||
{ |
|||
_settings.OnColorValuesChanged(); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,31 @@ |
|||
using Avalonia.Browser.Interop; |
|||
using Avalonia.Platform; |
|||
|
|||
namespace Avalonia.Browser; |
|||
|
|||
internal class BrowserPlatformSettings : DefaultPlatformSettings |
|||
{ |
|||
private bool _isDarkMode; |
|||
private bool _isHighContrast; |
|||
|
|||
public BrowserPlatformSettings() |
|||
{ |
|||
var obj = DomHelper.ObserveDarkMode((isDarkMode, isHighContrast) => |
|||
{ |
|||
_isDarkMode = isDarkMode; |
|||
_isHighContrast = isHighContrast; |
|||
OnColorValuesChanged(GetColorValues()); |
|||
}); |
|||
_isDarkMode = obj.GetPropertyAsBoolean("isDarkMode"); |
|||
_isHighContrast = obj.GetPropertyAsBoolean("isHighContrast"); |
|||
} |
|||
|
|||
public override PlatformColorValues GetColorValues() |
|||
{ |
|||
return base.GetColorValues() with |
|||
{ |
|||
ThemeVariant = _isDarkMode ? PlatformThemeVariant.Dark : PlatformThemeVariant.Light, |
|||
ContrastPreference = _isHighContrast ? ColorContrastPreference.High : ColorContrastPreference.NoPreference |
|||
}; |
|||
} |
|||
} |
|||
@ -0,0 +1,24 @@ |
|||
using System; |
|||
using Avalonia.Browser.Interop; |
|||
using Avalonia.Interactivity; |
|||
using Avalonia.Platform; |
|||
|
|||
namespace Avalonia.Browser |
|||
{ |
|||
internal class BrowserSystemNavigationManager : ISystemNavigationManager |
|||
{ |
|||
public event EventHandler<RoutedEventArgs>? BackRequested; |
|||
|
|||
public BrowserSystemNavigationManager() |
|||
{ |
|||
NavigationHelper.AddBackHandler(() => |
|||
{ |
|||
var routedEventArgs = new RoutedEventArgs(); |
|||
|
|||
BackRequested?.Invoke(this, routedEventArgs); |
|||
|
|||
return routedEventArgs.Handled; |
|||
}); |
|||
} |
|||
} |
|||
} |
|||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue