| File: Compass\Compass.shared.cs | Web Access |
| Project: src\src\Essentials\src\Essentials.csproj (Microsoft.Maui.Essentials) |
#nullable enable using System; using Microsoft.Maui.ApplicationModel; namespace Microsoft.Maui.Devices.Sensors { /// <summary> /// Monitor changes to the orientation of the user's device. /// </summary> public interface ICompass { /// <summary> /// Gets a value indicating whether reading the compass is supported on this device. /// </summary> bool IsSupported { get; } /// <summary> /// Gets if compass is actively being monitored. /// </summary> bool IsMonitoring { get; } /// <summary> /// Start monitoring for changes to the compass. /// </summary> /// <param name="sensorSpeed">The speed to monitor for changes.</param> void Start(SensorSpeed sensorSpeed); /// <summary> /// Start monitoring for changes to the compass. /// </summary> /// <param name="sensorSpeed">The speed to monitor for changes.</param> /// <param name="applyLowPassFilter">Whether or not to apply a moving average filter (only used on Android).</param> void Start(SensorSpeed sensorSpeed, bool applyLowPassFilter); /// <summary> /// Stop monitoring for changes to the compass. /// </summary> void Stop(); /// <summary> /// Occurs when compass reading changes. /// </summary> event EventHandler<CompassChangedEventArgs> ReadingChanged; } /// <summary> /// Platform-specific APIs for use with <see cref="ICompass"/>. /// </summary> public interface IPlatformCompass { #if IOS || MACCATALYST /// <summary> /// Gets or sets if heading calibration should be shown. /// </summary> bool ShouldDisplayHeadingCalibration { get; set; } #endif } /// <summary> /// Monitor changes to the orientation of the user's device. /// </summary> public static class Compass { /// <summary> /// Occurs when compass reading changes. /// </summary> public static event EventHandler<CompassChangedEventArgs> ReadingChanged { add => Current.ReadingChanged += value; remove => Current.ReadingChanged -= value; } /// <summary> /// Gets a value indicating whether reading the compass is supported on this device. /// </summary> public static bool IsSupported => Current.IsSupported; /// <summary> /// Gets a value indicating whether the compass is actively being monitored. /// </summary> public static bool IsMonitoring => Current.IsMonitoring; /// <summary> /// Start monitoring for changes to the compass. /// </summary> /// <remarks> /// Will throw <see cref="FeatureNotSupportedException"/> if not supported on device. /// Will throw <see cref="InvalidOperationException"/> if <see cref="IsMonitoring"/> is <see langword="true"/>. /// </remarks> /// <param name="sensorSpeed">The speed to monitor for changes.</param> public static void Start(SensorSpeed sensorSpeed) => Start(sensorSpeed, true); /// <summary> /// Start monitoring for changes to the compass. /// </summary> /// <remarks> /// Will throw <see cref="FeatureNotSupportedException"/> if not supported on device. /// Will throw <see cref="InvalidOperationException"/> if <see cref="IsMonitoring"/> is <see langword="true"/>. /// </remarks> /// <param name="sensorSpeed">The speed to monitor for changes.</param> /// <param name="applyLowPassFilter">Whether or not to apply a moving average filter (only used on Android).</param> public static void Start(SensorSpeed sensorSpeed, bool applyLowPassFilter) => Current.Start(sensorSpeed, applyLowPassFilter); /// <summary> /// Stop monitoring for changes to the compass. /// </summary> public static void Stop() => Current.Stop(); #if IOS || MACCATALYST /// <summary> /// Gets or sets a value specifying whether the calibration screen should be displayed. /// </summary> /// <remarks>Only available on iOS.</remarks> public static bool ShouldDisplayHeadingCalibration { get { if (Current is IPlatformCompass c) return c.ShouldDisplayHeadingCalibration; return false; } set { if (Current is IPlatformCompass c) c.ShouldDisplayHeadingCalibration = value; } } #endif static ICompass Current => Devices.Sensors.Compass.Default; static ICompass? defaultImplementation; /// <summary> /// Provides the default implementation for static usage of this API. /// </summary> public static ICompass Default => defaultImplementation ??= new CompassImplementation(); internal static void SetDefault(ICompass? implementation) => defaultImplementation = implementation; } /// <summary> /// This class contains static extension methods for use with <see cref="ICompass"/>. /// </summary> public static class CompassExtensions { #if IOS || MACCATALYST /// <summary> /// Gets or sets a value specifying whether the calibration screen should be displayed. /// </summary> /// <remarks>Only available on iOS.</remarks> public static void SetShouldDisplayHeadingCalibration(this ICompass compass, bool shouldDisplay) { if (compass is IPlatformCompass platform) { platform.ShouldDisplayHeadingCalibration = shouldDisplay; } } #endif } /// <summary> /// Event arguments when compass reading changes. /// </summary> public class CompassChangedEventArgs : EventArgs { /// <summary> /// Initializes a new instance of the <see cref="CompassChangedEventArgs"/> class. /// </summary> /// <param name="reading">The compass data reading.</param> public CompassChangedEventArgs(CompassData reading) => Reading = reading; /// <summary> /// The current values of the compass. /// </summary> public CompassData Reading { get; } } /// <summary> /// Contains the orientation of the user's device. /// </summary> public readonly struct CompassData : IEquatable<CompassData> { /// <summary> /// Initializes a new instance of the <see cref="CompassData"/> class. /// </summary> /// <param name="headingMagneticNorth">A reading of compass data for heading magnetic north.</param> public CompassData(double headingMagneticNorth) => HeadingMagneticNorth = headingMagneticNorth; /// <summary> /// The heading (measured in degrees) relative to magnetic north. /// </summary> public double HeadingMagneticNorth { get; } /// <summary> /// Compares the underlying <see cref="CompassData"/> instances. /// </summary> /// <param name="obj">Object to compare with.</param> /// <returns><see langword="true"/> if they are equal, otherwise <see langword="false"/>.</returns> public override bool Equals(object? obj) => (obj is CompassData data) && Equals(data); /// <summary> /// Compares the underlying <see cref="CompassData.HeadingMagneticNorth"/> instances. /// </summary> /// <param name="other">Object to compare with.</param> /// <returns><see langword="true"/> if they are equal, otherwise <see langword="false"/>.</returns> public bool Equals(CompassData other) => HeadingMagneticNorth.Equals(other.HeadingMagneticNorth); /// <summary> /// Equality operator for equals. /// </summary> /// <param name="left">Left to compare.</param> /// <param name="right">Right to compare.</param> /// <returns><see langword="true"/> if objects are equal, otherwise <see langword="false"/>.</returns> public static bool operator ==(CompassData left, CompassData right) => left.Equals(right); /// <summary> /// Inequality operator. /// </summary> /// <param name="left">Left to compare.</param> /// <param name="right">Right to compare.</param> /// <returns><see langword="true"/> if objects are not equal, otherwise <see langword="false"/>.</returns> public static bool operator !=(CompassData left, CompassData right) => !left.Equals(right); /// <inheritdoc cref="object.GetHashCode"/> public override int GetHashCode() => HeadingMagneticNorth.GetHashCode(); /// <summary> /// Returns a string representation of the current values of <see cref="CompassData.HeadingMagneticNorth"/>. /// </summary> /// <returns>A string representation of this instance in the format of <c>HeadingMagneticNorth: {value}</c>.</returns> public override string ToString() => $"{nameof(HeadingMagneticNorth)}: {HeadingMagneticNorth}"; } partial class CompassImplementation : ICompass { bool UseSyncContext => SensorSpeed == SensorSpeed.Default || SensorSpeed == SensorSpeed.UI; public event EventHandler<CompassChangedEventArgs>? ReadingChanged; public bool IsSupported => PlatformIsSupported; public bool IsMonitoring { get; private set; } SensorSpeed SensorSpeed { get; set; } public void Start(SensorSpeed sensorSpeed) => Start(sensorSpeed, true); public void Start(SensorSpeed sensorSpeed, bool applyLowPassFilter) { if (!PlatformIsSupported) throw new FeatureNotSupportedException(); if (IsMonitoring) throw new InvalidOperationException("Compass has already been started."); IsMonitoring = true; try { PlatformStart(sensorSpeed, applyLowPassFilter); } catch { IsMonitoring = false; throw; } } public void Stop() { if (!PlatformIsSupported) throw new FeatureNotSupportedException(); if (!IsMonitoring) return; IsMonitoring = false; try { PlatformStop(); } catch { IsMonitoring = true; throw; } } internal void RaiseReadingChanged(CompassData data) { var args = new CompassChangedEventArgs(data); if (UseSyncContext) MainThread.BeginInvokeOnMainThread(() => ReadingChanged?.Invoke(null, args)); else ReadingChanged?.Invoke(null, args); } } }