// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using Microsoft.Extensions.DependencyInjection; using Microsoft.Maui.Animations; using Microsoft.Maui.Dispatching; using Microsoft.Maui.Platform; using SkiaSharp; namespace Microsoft.Maui.Platform.Linux.Hosting; /// /// Linux-specific implementation of IMauiContext. /// Provides the infrastructure for creating handlers and accessing platform services. /// public class LinuxMauiContext : IMauiContext { private readonly IServiceProvider _services; private readonly IMauiHandlersFactory _handlers; private readonly LinuxApplication _linuxApp; private IAnimationManager? _animationManager; private IDispatcher? _dispatcher; public LinuxMauiContext(IServiceProvider services, LinuxApplication linuxApp) { _services = services ?? throw new ArgumentNullException(nameof(services)); _linuxApp = linuxApp ?? throw new ArgumentNullException(nameof(linuxApp)); _handlers = services.GetRequiredService(); } /// public IServiceProvider Services => _services; /// public IMauiHandlersFactory Handlers => _handlers; /// /// Gets the Linux application instance. /// public LinuxApplication LinuxApp => _linuxApp; /// /// Gets the animation manager. /// public IAnimationManager AnimationManager { get { _animationManager ??= _services.GetService() ?? new LinuxAnimationManager(new LinuxTicker()); return _animationManager; } } /// /// Gets the dispatcher for UI thread operations. /// public IDispatcher Dispatcher { get { _dispatcher ??= _services.GetService() ?? new LinuxDispatcher(); return _dispatcher; } } } /// /// Scoped MAUI context for a specific window or view hierarchy. /// public class ScopedLinuxMauiContext : IMauiContext { private readonly LinuxMauiContext _parent; public ScopedLinuxMauiContext(LinuxMauiContext parent) { _parent = parent ?? throw new ArgumentNullException(nameof(parent)); } public IServiceProvider Services => _parent.Services; public IMauiHandlersFactory Handlers => _parent.Handlers; } /// /// Linux dispatcher for UI thread operations. /// internal class LinuxDispatcher : IDispatcher { private readonly object _lock = new(); private readonly Queue _queue = new(); private bool _isDispatching; public bool IsDispatchRequired => false; // Linux uses single-threaded event loop public IDispatcherTimer CreateTimer() { return new LinuxDispatcherTimer(); } public bool Dispatch(Action action) { if (action == null) return false; lock (_lock) { _queue.Enqueue(action); } ProcessQueue(); return true; } public bool DispatchDelayed(TimeSpan delay, Action action) { if (action == null) return false; Task.Delay(delay).ContinueWith(_ => Dispatch(action)); return true; } private void ProcessQueue() { if (_isDispatching) return; _isDispatching = true; try { while (true) { Action? action; lock (_lock) { if (_queue.Count == 0) break; action = _queue.Dequeue(); } action?.Invoke(); } } finally { _isDispatching = false; } } } /// /// Linux dispatcher timer implementation. /// internal class LinuxDispatcherTimer : IDispatcherTimer { private Timer? _timer; private TimeSpan _interval = TimeSpan.FromMilliseconds(16); // ~60fps default private bool _isRunning; private bool _isRepeating = true; public TimeSpan Interval { get => _interval; set => _interval = value; } public bool IsRunning => _isRunning; public bool IsRepeating { get => _isRepeating; set => _isRepeating = value; } public event EventHandler? Tick; public void Start() { if (_isRunning) return; _isRunning = true; _timer = new Timer(OnTimerCallback, null, _interval, _isRepeating ? _interval : Timeout.InfiniteTimeSpan); } public void Stop() { _isRunning = false; _timer?.Dispose(); _timer = null; } private void OnTimerCallback(object? state) { Tick?.Invoke(this, EventArgs.Empty); if (!_isRepeating) { Stop(); } } } /// /// Linux animation manager. /// internal class LinuxAnimationManager : IAnimationManager { private readonly List _animations = new(); private readonly ITicker _ticker; public LinuxAnimationManager(ITicker ticker) { _ticker = ticker; _ticker.Fire = OnTickerFire; } public double SpeedModifier { get; set; } = 1.0; public bool AutoStartTicker { get; set; } = true; public ITicker Ticker => _ticker; public void Add(Microsoft.Maui.Animations.Animation animation) { _animations.Add(animation); if (AutoStartTicker && !_ticker.IsRunning) { _ticker.Start(); } } public void Remove(Microsoft.Maui.Animations.Animation animation) { _animations.Remove(animation); if (_animations.Count == 0 && _ticker.IsRunning) { _ticker.Stop(); } } private void OnTickerFire() { var animations = _animations.ToArray(); foreach (var animation in animations) { animation.Tick(16.0 / 1000.0 * SpeedModifier); // ~60fps if (animation.HasFinished) { Remove(animation); } } } } /// /// Linux ticker for animation timing. /// internal class LinuxTicker : ITicker { private Timer? _timer; private bool _isRunning; private int _maxFps = 60; public bool IsRunning => _isRunning; public bool SystemEnabled => true; public int MaxFps { get => _maxFps; set => _maxFps = Math.Max(1, Math.Min(120, value)); } public Action? Fire { get; set; } public void Start() { if (_isRunning) return; _isRunning = true; var interval = TimeSpan.FromMilliseconds(1000.0 / _maxFps); _timer = new Timer(OnTimerCallback, null, TimeSpan.Zero, interval); } public void Stop() { _isRunning = false; _timer?.Dispose(); _timer = null; } private void OnTimerCallback(object? state) { Fire?.Invoke(); } }