// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using Microsoft.Maui.Platform.Linux.Rendering; using Microsoft.Maui.Platform.Linux.Window; using Microsoft.Maui.Platform.Linux.Services; using Microsoft.Maui.Platform; namespace Microsoft.Maui.Platform.Linux; /// /// Main Linux application class that bootstraps the MAUI application. /// public class LinuxApplication : IDisposable { private X11Window? _mainWindow; private SkiaRenderingEngine? _renderingEngine; private SkiaView? _rootView; private SkiaView? _focusedView; private SkiaView? _hoveredView; private bool _disposed; /// /// Gets the current application instance. /// public static LinuxApplication? Current { get; private set; } /// /// Gets the main window. /// public X11Window? MainWindow => _mainWindow; /// /// Gets the rendering engine. /// public SkiaRenderingEngine? RenderingEngine => _renderingEngine; /// /// Gets or sets the root view. /// public SkiaView? RootView { get => _rootView; set { _rootView = value; if (_rootView != null && _mainWindow != null) { _rootView.Arrange(new SkiaSharp.SKRect( 0, 0, _mainWindow.Width, _mainWindow.Height)); } } } /// /// Gets or sets the currently focused view. /// public SkiaView? FocusedView { get => _focusedView; set { if (_focusedView != value) { if (_focusedView != null) { _focusedView.IsFocused = false; } _focusedView = value; if (_focusedView != null) { _focusedView.IsFocused = true; } } } } /// /// Creates a new Linux application. /// public LinuxApplication() { Current = this; } /// /// Initializes the application with the specified options. /// public void Initialize(LinuxApplicationOptions options) { // Create the main window _mainWindow = new X11Window( options.Title ?? "MAUI Application", options.Width, options.Height); // Create the rendering engine _renderingEngine = new SkiaRenderingEngine(_mainWindow); // Wire up events _mainWindow.Resized += OnWindowResized; _mainWindow.Exposed += OnWindowExposed; _mainWindow.KeyDown += OnKeyDown; _mainWindow.KeyUp += OnKeyUp; _mainWindow.TextInput += OnTextInput; _mainWindow.PointerMoved += OnPointerMoved; _mainWindow.PointerPressed += OnPointerPressed; _mainWindow.PointerReleased += OnPointerReleased; _mainWindow.Scroll += OnScroll; _mainWindow.CloseRequested += OnCloseRequested; // Register platform services RegisterServices(); } private void RegisterServices() { // Platform services would be registered with the DI container here // For now, we create singleton instances } /// /// Shows the main window and runs the event loop. /// public void Run() { if (_mainWindow == null) throw new InvalidOperationException("Application not initialized"); _mainWindow.Show(); // Initial render Render(); // Run the event loop while (_mainWindow.IsRunning) { _mainWindow.ProcessEvents(); // Update animations and render UpdateAnimations(); Render(); // Small delay to prevent 100% CPU usage Thread.Sleep(1); } } private void UpdateAnimations() { // Update cursor blink for entry controls if (_focusedView is SkiaEntry entry) { entry.UpdateCursorBlink(); } } private void Render() { if (_renderingEngine != null && _rootView != null) { _renderingEngine.Render(_rootView); } } private void OnWindowResized(object? sender, (int Width, int Height) size) { if (_rootView != null) { _rootView.Arrange(new SkiaSharp.SKRect(0, 0, size.Width, size.Height)); } _renderingEngine?.InvalidateAll(); } private void OnWindowExposed(object? sender, EventArgs e) { Render(); } private void OnKeyDown(object? sender, KeyEventArgs e) { if (_focusedView != null) { _focusedView.OnKeyDown(e); } } private void OnKeyUp(object? sender, KeyEventArgs e) { if (_focusedView != null) { _focusedView.OnKeyUp(e); } } private void OnTextInput(object? sender, TextInputEventArgs e) { if (_focusedView != null) { _focusedView.OnTextInput(e); } } private void OnPointerMoved(object? sender, PointerEventArgs e) { if (_rootView != null) { var hitView = _rootView.HitTest(e.X, e.Y); // Track hover state changes if (hitView != _hoveredView) { _hoveredView?.OnPointerExited(e); _hoveredView = hitView; _hoveredView?.OnPointerEntered(e); } hitView?.OnPointerMoved(e); } } private void OnPointerPressed(object? sender, PointerEventArgs e) { if (_rootView != null) { var hitView = _rootView.HitTest(e.X, e.Y); if (hitView != null) { // Update focus if (hitView.IsFocusable) { FocusedView = hitView; } hitView.OnPointerPressed(e); } else { FocusedView = null; } } } private void OnPointerReleased(object? sender, PointerEventArgs e) { if (_rootView != null) { var hitView = _rootView.HitTest(e.X, e.Y); hitView?.OnPointerReleased(e); } } private void OnScroll(object? sender, ScrollEventArgs e) { if (_rootView != null) { var hitView = _rootView.HitTest(e.X, e.Y); // Bubble scroll events up to find a ScrollView var view = hitView; while (view != null) { if (view is SkiaScrollView scrollView) { scrollView.OnScroll(e); return; } view.OnScroll(e); if (e.Handled) return; view = view.Parent; } } } private void OnCloseRequested(object? sender, EventArgs e) { _mainWindow?.Stop(); } public void Dispose() { if (!_disposed) { _renderingEngine?.Dispose(); _mainWindow?.Dispose(); if (Current == this) Current = null; _disposed = true; } } } /// /// Options for Linux application initialization. /// public class LinuxApplicationOptions { /// /// Gets or sets the window title. /// public string? Title { get; set; } = "MAUI Application"; /// /// Gets or sets the initial window width. /// public int Width { get; set; } = 800; /// /// Gets or sets the initial window height. /// public int Height { get; set; } = 600; /// /// Gets or sets whether to use hardware acceleration. /// public bool UseHardwareAcceleration { get; set; } = true; /// /// Gets or sets the display server type. /// public DisplayServerType DisplayServer { get; set; } = DisplayServerType.Auto; } /// /// Display server type options. /// public enum DisplayServerType { /// /// Automatically detect the display server. /// Auto, /// /// Use X11 (Xorg). /// X11, /// /// Use Wayland. /// Wayland }