// 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.Controls; using Microsoft.Maui.Controls.Shapes; using Microsoft.Maui.Graphics; using Microsoft.Maui.Platform.Linux; using Microsoft.Maui.Platform.Linux.Handlers; using Microsoft.Maui.Platform.Linux.Native; using Microsoft.Maui.Platform.Linux.Rendering; using Microsoft.Maui.Platform.Linux.Services; using Microsoft.Maui.Platform.Linux.Window; using SkiaSharp; namespace Microsoft.Maui.Platform; /// /// Base class for all Skia-rendered views on Linux. /// Inherits from BindableObject to enable XAML styling, data binding, and Visual State Manager. /// Implements IAccessible for screen reader support. /// public abstract partial class SkiaView : BindableObject, IDisposable, IAccessible { #region BindableProperties /// /// Bindable property for IsVisible. /// public static readonly BindableProperty IsVisibleProperty = BindableProperty.Create( nameof(IsVisible), typeof(bool), typeof(SkiaView), true, propertyChanged: (b, o, n) => ((SkiaView)b).OnVisibilityChanged()); /// /// Bindable property for IsEnabled. /// public static readonly BindableProperty IsEnabledProperty = BindableProperty.Create( nameof(IsEnabled), typeof(bool), typeof(SkiaView), true, propertyChanged: (b, o, n) => ((SkiaView)b).OnEnabledChanged()); /// /// Bindable property for Opacity. /// public static readonly BindableProperty OpacityProperty = BindableProperty.Create( nameof(Opacity), typeof(float), typeof(SkiaView), 1.0f, coerceValue: (b, v) => Math.Clamp((float)v, 0f, 1f), propertyChanged: (b, o, n) => ((SkiaView)b).Invalidate()); /// /// Bindable property for BackgroundColor. /// Uses Microsoft.Maui.Graphics.Color for MAUI compliance. /// public static readonly BindableProperty BackgroundColorProperty = BindableProperty.Create( nameof(BackgroundColor), typeof(Color), typeof(SkiaView), null, propertyChanged: (b, o, n) => ((SkiaView)b).OnBackgroundColorChanged()); /// /// Bindable property for WidthRequest. /// public static readonly BindableProperty WidthRequestProperty = BindableProperty.Create( nameof(WidthRequest), typeof(double), typeof(SkiaView), -1.0, propertyChanged: (b, o, n) => ((SkiaView)b).InvalidateMeasure()); /// /// Bindable property for HeightRequest. /// public static readonly BindableProperty HeightRequestProperty = BindableProperty.Create( nameof(HeightRequest), typeof(double), typeof(SkiaView), -1.0, propertyChanged: (b, o, n) => ((SkiaView)b).InvalidateMeasure()); /// /// Bindable property for MinimumWidthRequest. /// Default is -1 (unset) to match MAUI View.MinimumWidthRequest. /// public static readonly BindableProperty MinimumWidthRequestProperty = BindableProperty.Create( nameof(MinimumWidthRequest), typeof(double), typeof(SkiaView), -1.0, propertyChanged: (b, o, n) => ((SkiaView)b).InvalidateMeasure()); /// /// Bindable property for MinimumHeightRequest. /// Default is -1 (unset) to match MAUI View.MinimumHeightRequest. /// public static readonly BindableProperty MinimumHeightRequestProperty = BindableProperty.Create( nameof(MinimumHeightRequest), typeof(double), typeof(SkiaView), -1.0, propertyChanged: (b, o, n) => ((SkiaView)b).InvalidateMeasure()); /// /// Bindable property for IsFocusable. /// public static readonly BindableProperty IsFocusableProperty = BindableProperty.Create( nameof(IsFocusable), typeof(bool), typeof(SkiaView), false); /// /// Bindable property for Margin. /// public static readonly BindableProperty MarginProperty = BindableProperty.Create( nameof(Margin), typeof(Thickness), typeof(SkiaView), default(Thickness), propertyChanged: (b, o, n) => ((SkiaView)b).InvalidateMeasure()); /// /// Bindable property for HorizontalOptions. /// public static readonly BindableProperty HorizontalOptionsProperty = BindableProperty.Create( nameof(HorizontalOptions), typeof(LayoutOptions), typeof(SkiaView), LayoutOptions.Fill, propertyChanged: (b, o, n) => ((SkiaView)b).InvalidateMeasure()); /// /// Bindable property for VerticalOptions. /// public static readonly BindableProperty VerticalOptionsProperty = BindableProperty.Create( nameof(VerticalOptions), typeof(LayoutOptions), typeof(SkiaView), LayoutOptions.Fill, propertyChanged: (b, o, n) => ((SkiaView)b).InvalidateMeasure()); /// /// Bindable property for Name (used for template child lookup). /// public static readonly BindableProperty NameProperty = BindableProperty.Create( nameof(Name), typeof(string), typeof(SkiaView), string.Empty); /// /// Bindable property for Scale. /// public static readonly BindableProperty ScaleProperty = BindableProperty.Create( nameof(Scale), typeof(double), typeof(SkiaView), 1.0, propertyChanged: (b, o, n) => ((SkiaView)b).Invalidate()); /// /// Bindable property for ScaleX. /// public static readonly BindableProperty ScaleXProperty = BindableProperty.Create( nameof(ScaleX), typeof(double), typeof(SkiaView), 1.0, propertyChanged: (b, o, n) => ((SkiaView)b).Invalidate()); /// /// Bindable property for ScaleY. /// public static readonly BindableProperty ScaleYProperty = BindableProperty.Create( nameof(ScaleY), typeof(double), typeof(SkiaView), 1.0, propertyChanged: (b, o, n) => ((SkiaView)b).Invalidate()); /// /// Bindable property for Rotation. /// public static readonly BindableProperty RotationProperty = BindableProperty.Create( nameof(Rotation), typeof(double), typeof(SkiaView), 0.0, propertyChanged: (b, o, n) => ((SkiaView)b).Invalidate()); /// /// Bindable property for RotationX. /// public static readonly BindableProperty RotationXProperty = BindableProperty.Create( nameof(RotationX), typeof(double), typeof(SkiaView), 0.0, propertyChanged: (b, o, n) => ((SkiaView)b).Invalidate()); /// /// Bindable property for RotationY. /// public static readonly BindableProperty RotationYProperty = BindableProperty.Create( nameof(RotationY), typeof(double), typeof(SkiaView), 0.0, propertyChanged: (b, o, n) => ((SkiaView)b).Invalidate()); /// /// Bindable property for TranslationX. /// public static readonly BindableProperty TranslationXProperty = BindableProperty.Create( nameof(TranslationX), typeof(double), typeof(SkiaView), 0.0, propertyChanged: (b, o, n) => ((SkiaView)b).Invalidate()); /// /// Bindable property for TranslationY. /// public static readonly BindableProperty TranslationYProperty = BindableProperty.Create( nameof(TranslationY), typeof(double), typeof(SkiaView), 0.0, propertyChanged: (b, o, n) => ((SkiaView)b).Invalidate()); /// /// Bindable property for AnchorX. /// public static readonly BindableProperty AnchorXProperty = BindableProperty.Create( nameof(AnchorX), typeof(double), typeof(SkiaView), 0.5, propertyChanged: (b, o, n) => ((SkiaView)b).Invalidate()); /// /// Bindable property for AnchorY. /// public static readonly BindableProperty AnchorYProperty = BindableProperty.Create( nameof(AnchorY), typeof(double), typeof(SkiaView), 0.5, propertyChanged: (b, o, n) => ((SkiaView)b).Invalidate()); /// /// Bindable property for InputTransparent. /// When true, the view does not receive input events and they pass through to views below. /// public static readonly BindableProperty InputTransparentProperty = BindableProperty.Create( nameof(InputTransparent), typeof(bool), typeof(SkiaView), false); /// /// Bindable property for FlowDirection. /// Controls the layout direction for RTL language support. /// public static readonly BindableProperty FlowDirectionProperty = BindableProperty.Create( nameof(FlowDirection), typeof(FlowDirection), typeof(SkiaView), FlowDirection.MatchParent, propertyChanged: (b, o, n) => ((SkiaView)b).InvalidateMeasure()); /// /// Bindable property for ZIndex. /// Controls the rendering order within a layout. /// public static readonly BindableProperty ZIndexProperty = BindableProperty.Create( nameof(ZIndex), typeof(int), typeof(SkiaView), 0, propertyChanged: (b, o, n) => ((SkiaView)b).Parent?.Invalidate()); /// /// Bindable property for MaximumWidthRequest. /// public static readonly BindableProperty MaximumWidthRequestProperty = BindableProperty.Create( nameof(MaximumWidthRequest), typeof(double), typeof(SkiaView), double.PositiveInfinity, propertyChanged: (b, o, n) => ((SkiaView)b).InvalidateMeasure()); /// /// Bindable property for MaximumHeightRequest. /// public static readonly BindableProperty MaximumHeightRequestProperty = BindableProperty.Create( nameof(MaximumHeightRequest), typeof(double), typeof(SkiaView), double.PositiveInfinity, propertyChanged: (b, o, n) => ((SkiaView)b).InvalidateMeasure()); /// /// Bindable property for AutomationId. /// Used for UI testing and accessibility. /// public static readonly BindableProperty AutomationIdProperty = BindableProperty.Create( nameof(AutomationId), typeof(string), typeof(SkiaView), string.Empty); /// /// Bindable property for Padding. /// public static readonly BindableProperty PaddingProperty = BindableProperty.Create( nameof(Padding), typeof(Thickness), typeof(SkiaView), default(Thickness), propertyChanged: (b, o, n) => ((SkiaView)b).InvalidateMeasure()); /// /// Bindable property for Background (Brush). /// public static readonly BindableProperty BackgroundProperty = BindableProperty.Create( nameof(Background), typeof(Brush), typeof(SkiaView), null, propertyChanged: (b, o, n) => ((SkiaView)b).Invalidate()); /// /// Bindable property for Clip geometry. /// public static readonly BindableProperty ClipProperty = BindableProperty.Create( nameof(Clip), typeof(Geometry), typeof(SkiaView), null, propertyChanged: (b, o, n) => ((SkiaView)b).Invalidate()); /// /// Bindable property for Shadow. /// public static readonly BindableProperty ShadowProperty = BindableProperty.Create( nameof(Shadow), typeof(Shadow), typeof(SkiaView), null, propertyChanged: (b, o, n) => ((SkiaView)b).Invalidate()); /// /// Bindable property for Visual. /// public static readonly BindableProperty VisualProperty = BindableProperty.Create( nameof(Visual), typeof(IVisual), typeof(SkiaView), VisualMarker.Default); #endregion private bool _disposed; private Rect _bounds; private SkiaView? _parent; private readonly List _children = new(); /// /// Gets the absolute bounds of this view in screen coordinates. /// public Rect GetAbsoluteBounds() { var bounds = Bounds; var current = Parent; while (current != null) { // Adjust for scroll offset if parent is a ScrollView if (current is SkiaScrollView scrollView) { bounds = new Rect( bounds.Left - scrollView.ScrollX, bounds.Top - scrollView.ScrollY, bounds.Width, bounds.Height); } current = current.Parent; } return bounds; } /// /// Gets or sets the bounds of this view in parent coordinates. /// Uses MAUI Rect for public API compliance. /// public Rect Bounds { get => _bounds; set { if (_bounds != value) { _bounds = value; OnBoundsChanged(); } } } /// /// Gets the bounds as SKRect for internal SkiaSharp rendering. /// internal SKRect BoundsSK => new SKRect( (float)_bounds.Left, (float)_bounds.Top, (float)_bounds.Right, (float)_bounds.Bottom); /// /// Gets or sets whether this view is visible. /// public bool IsVisible { get => (bool)GetValue(IsVisibleProperty); set => SetValue(IsVisibleProperty, value); } /// /// Gets or sets whether this view is enabled for interaction. /// public bool IsEnabled { get => (bool)GetValue(IsEnabledProperty); set => SetValue(IsEnabledProperty, value); } /// /// Gets or sets the opacity of this view (0.0 to 1.0). /// public float Opacity { get => (float)GetValue(OpacityProperty); set => SetValue(OpacityProperty, value); } /// /// Gets or sets the background color. /// Uses Microsoft.Maui.Graphics.Color for MAUI compliance. /// private SKColor _backgroundColorSK = SKColors.Transparent; public Color? BackgroundColor { get => (Color?)GetValue(BackgroundColorProperty); set => SetValue(BackgroundColorProperty, value); } /// /// Called when BackgroundColor changes. /// private void OnBackgroundColorChanged() { var color = BackgroundColor; _backgroundColorSK = color != null ? color.ToSKColor() : SKColors.Transparent; Invalidate(); } /// /// Gets the effective background color as SKColor for rendering. /// protected SKColor GetEffectiveBackgroundColor() => _backgroundColorSK; /// /// Gets or sets the requested width. /// public double WidthRequest { get => (double)GetValue(WidthRequestProperty); set => SetValue(WidthRequestProperty, value); } /// /// Gets or sets the requested height. /// public double HeightRequest { get => (double)GetValue(HeightRequestProperty); set => SetValue(HeightRequestProperty, value); } /// /// Gets or sets the minimum width request. /// public double MinimumWidthRequest { get => (double)GetValue(MinimumWidthRequestProperty); set => SetValue(MinimumWidthRequestProperty, value); } /// /// Gets or sets the minimum height request. /// public double MinimumHeightRequest { get => (double)GetValue(MinimumHeightRequestProperty); set => SetValue(MinimumHeightRequestProperty, value); } /// /// Gets or sets the requested width (backwards compatibility alias). /// public double RequestedWidth { get => WidthRequest; set => WidthRequest = value; } /// /// Gets or sets the requested height (backwards compatibility alias). /// public double RequestedHeight { get => HeightRequest; set => HeightRequest = value; } /// /// Gets or sets whether this view can receive keyboard focus. /// public bool IsFocusable { get => (bool)GetValue(IsFocusableProperty); set => SetValue(IsFocusableProperty, value); } /// /// Gets or sets the margin around this view. /// public Thickness Margin { get => (Thickness)GetValue(MarginProperty); set => SetValue(MarginProperty, value); } /// /// Gets or sets the horizontal layout options. /// public LayoutOptions HorizontalOptions { get => (LayoutOptions)GetValue(HorizontalOptionsProperty); set => SetValue(HorizontalOptionsProperty, value); } /// /// Gets or sets the vertical layout options. /// public LayoutOptions VerticalOptions { get => (LayoutOptions)GetValue(VerticalOptionsProperty); set => SetValue(VerticalOptionsProperty, value); } /// /// Gets or sets the name of this view (used for template child lookup). /// public string Name { get => (string)GetValue(NameProperty); set => SetValue(NameProperty, value); } /// /// Gets or sets the uniform scale factor. /// public double Scale { get => (double)GetValue(ScaleProperty); set => SetValue(ScaleProperty, value); } /// /// Gets or sets the X-axis scale factor. /// public double ScaleX { get => (double)GetValue(ScaleXProperty); set => SetValue(ScaleXProperty, value); } /// /// Gets or sets the Y-axis scale factor. /// public double ScaleY { get => (double)GetValue(ScaleYProperty); set => SetValue(ScaleYProperty, value); } /// /// Gets or sets the rotation in degrees around the Z-axis. /// public double Rotation { get => (double)GetValue(RotationProperty); set => SetValue(RotationProperty, value); } /// /// Gets or sets the rotation in degrees around the X-axis. /// public double RotationX { get => (double)GetValue(RotationXProperty); set => SetValue(RotationXProperty, value); } /// /// Gets or sets the rotation in degrees around the Y-axis. /// public double RotationY { get => (double)GetValue(RotationYProperty); set => SetValue(RotationYProperty, value); } /// /// Gets or sets the X translation offset. /// public double TranslationX { get => (double)GetValue(TranslationXProperty); set => SetValue(TranslationXProperty, value); } /// /// Gets or sets the Y translation offset. /// public double TranslationY { get => (double)GetValue(TranslationYProperty); set => SetValue(TranslationYProperty, value); } /// /// Gets or sets the X anchor point for transforms (0.0 to 1.0). /// public double AnchorX { get => (double)GetValue(AnchorXProperty); set => SetValue(AnchorXProperty, value); } /// /// Gets or sets the Y anchor point for transforms (0.0 to 1.0). /// public double AnchorY { get => (double)GetValue(AnchorYProperty); set => SetValue(AnchorYProperty, value); } /// /// Gets or sets whether this view is transparent to input. /// When true, input events pass through to views below. /// public bool InputTransparent { get => (bool)GetValue(InputTransparentProperty); set => SetValue(InputTransparentProperty, value); } /// /// Gets or sets the flow direction for RTL support. /// public FlowDirection FlowDirection { get => (FlowDirection)GetValue(FlowDirectionProperty); set => SetValue(FlowDirectionProperty, value); } /// /// Gets or sets the Z-index for rendering order. /// Higher values render on top of lower values. /// public int ZIndex { get => (int)GetValue(ZIndexProperty); set => SetValue(ZIndexProperty, value); } /// /// Gets or sets the maximum width request. /// public double MaximumWidthRequest { get => (double)GetValue(MaximumWidthRequestProperty); set => SetValue(MaximumWidthRequestProperty, value); } /// /// Gets or sets the maximum height request. /// public double MaximumHeightRequest { get => (double)GetValue(MaximumHeightRequestProperty); set => SetValue(MaximumHeightRequestProperty, value); } /// /// Gets or sets the automation ID for UI testing. /// public string AutomationId { get => (string)GetValue(AutomationIdProperty); set => SetValue(AutomationIdProperty, value); } /// /// Gets or sets the padding inside the view. /// public Thickness Padding { get => (Thickness)GetValue(PaddingProperty); set => SetValue(PaddingProperty, value); } /// /// Gets or sets the background brush. /// public Brush? Background { get => (Brush?)GetValue(BackgroundProperty); set => SetValue(BackgroundProperty, value); } /// /// Gets or sets the clip geometry. /// public Geometry? Clip { get => (Geometry?)GetValue(ClipProperty); set => SetValue(ClipProperty, value); } /// /// Gets or sets the shadow. /// public Shadow? Shadow { get => (Shadow?)GetValue(ShadowProperty); set => SetValue(ShadowProperty, value); } /// /// Gets or sets the visual style. /// public IVisual Visual { get => (IVisual)GetValue(VisualProperty); set => SetValue(VisualProperty, value); } /// /// Gets or sets the cursor type when hovering over this view. /// public CursorType CursorType { get; set; } /// /// Gets or sets the MAUI View this platform view represents. /// Used for gesture processing. /// public View? MauiView { get; set; } /// /// Gets or sets whether this view currently has keyboard focus. /// public bool IsFocused { get; internal set; } /// /// Gets or sets the parent view. /// public SkiaView? Parent { get => _parent; internal set => _parent = value; } /// /// Gets the bounds of this view in screen coordinates (accounting for scroll offsets). /// public Rect ScreenBounds { get { var bounds = Bounds; var parent = _parent; // Walk up the tree and adjust for scroll offsets while (parent != null) { if (parent is SkiaScrollView scrollView) { bounds = new Rect( bounds.Left - scrollView.ScrollX, bounds.Top - scrollView.ScrollY, bounds.Width, bounds.Height); } parent = parent.Parent; } return bounds; } } /// /// Gets the desired size calculated during measure. /// Uses MAUI Size for public API compliance. /// public Size DesiredSize { get; protected set; } /// /// Gets the desired size as SKSize for internal SkiaSharp rendering. /// internal SKSize DesiredSizeSK => new SKSize((float)DesiredSize.Width, (float)DesiredSize.Height); /// /// Gets the child views. /// public IReadOnlyList Children => _children; /// /// Event raised when this view needs to be redrawn. /// public event EventHandler? Invalidated; /// /// Called when visibility changes. /// protected virtual void OnVisibilityChanged() { Invalidate(); } /// /// Called when enabled state changes. /// protected virtual void OnEnabledChanged() { Invalidate(); } /// /// Called when binding context changes. Propagates to children. /// protected override void OnBindingContextChanged() { base.OnBindingContextChanged(); // Propagate binding context to children foreach (var child in _children) { SetInheritedBindingContext(child, BindingContext); } } /// /// Adds a child view. /// public void AddChild(SkiaView child) { if (child._parent != null) throw new InvalidOperationException("View already has a parent"); child._parent = this; _children.Add(child); // Propagate binding context to new child if (BindingContext != null) { SetInheritedBindingContext(child, BindingContext); } Invalidate(); } /// /// Removes a child view. /// public void RemoveChild(SkiaView child) { if (child._parent != this) return; child._parent = null; _children.Remove(child); Invalidate(); } /// /// Inserts a child view at the specified index. /// public void InsertChild(int index, SkiaView child) { if (child._parent != null) throw new InvalidOperationException("View already has a parent"); child._parent = this; _children.Insert(index, child); // Propagate binding context to new child if (BindingContext != null) { SetInheritedBindingContext(child, BindingContext); } Invalidate(); } /// /// Removes all child views. /// public void ClearChildren() { foreach (var child in _children) { child._parent = null; } _children.Clear(); Invalidate(); } /// /// Requests that this view be redrawn. /// Thread-safe - will marshal to GTK thread if needed. /// public void Invalidate() { // Check if we're on the GTK thread - if not, marshal the entire call int currentThread = Environment.CurrentManagedThreadId; int gtkThread = LinuxApplication.GtkThreadId; if (gtkThread != 0 && currentThread != gtkThread) { GLibNative.IdleAdd(() => { InvalidateInternal(); return false; }); return; } InvalidateInternal(); } private void InvalidateInternal() { LinuxApplication.LogInvalidate(GetType().Name); Invalidated?.Invoke(this, EventArgs.Empty); // Notify rendering engine of dirty region if (Bounds.Width > 0 && Bounds.Height > 0) { SkiaRenderingEngine.Current?.InvalidateRegion(new SKRect( (float)Bounds.Left, (float)Bounds.Top, (float)Bounds.Right, (float)Bounds.Bottom)); } if (_parent != null) { _parent.InvalidateInternal(); } else { LinuxApplication.RequestRedraw(); } } /// /// Invalidates the cached measurement. /// public void InvalidateMeasure() { DesiredSize = Size.Zero; _parent?.InvalidateMeasure(); Invalidate(); } /// /// Called when the bounds change. /// protected virtual void OnBoundsChanged() { Invalidate(); } /// /// Measures the desired size of this view. /// Uses MAUI Size for public API compliance. /// public Size Measure(Size availableSize) { DesiredSize = MeasureOverride(availableSize); return DesiredSize; } /// /// Override to provide custom measurement. /// Uses MAUI Size for public API compliance. /// protected virtual Size MeasureOverride(Size availableSize) { var width = WidthRequest >= 0 ? WidthRequest : 0; var height = HeightRequest >= 0 ? HeightRequest : 0; return new Size(width, height); } /// /// Arranges this view within the given bounds. /// Uses MAUI Rect for public API compliance. /// public virtual void Arrange(Rect bounds) { Bounds = ArrangeOverride(bounds); } /// /// Override to customize arrangement within the given bounds. /// Uses MAUI Rect for public API compliance. /// protected virtual Rect ArrangeOverride(Rect bounds) { return bounds; } /// /// Performs hit testing to find the view at the given point. /// Uses MAUI Point for public API compliance. /// public virtual SkiaView? HitTest(Point point) { return HitTest((float)point.X, (float)point.Y); } /// /// Performs hit testing to find the view at the given coordinates. /// Coordinates are in absolute window space, matching how Bounds are stored. /// public virtual SkiaView? HitTest(float x, float y) { if (!IsVisible || !IsEnabled) return null; if (!Bounds.Contains(x, y)) return null; // Check children in reverse order (top-most first) // Coordinates stay in absolute space since children have absolute Bounds for (int i = _children.Count - 1; i >= 0; i--) { var hit = _children[i].HitTest(x, y); if (hit != null) return hit; } // If InputTransparent, don't capture input - let it pass through if (InputTransparent) return null; return this; } } /// /// Event args for pointer events. /// public class PointerEventArgs : EventArgs { public float X { get; } public float Y { get; } public PointerButton Button { get; } public bool Handled { get; set; } public PointerEventArgs(float x, float y, PointerButton button = PointerButton.None) { X = x; Y = y; Button = button; } } /// /// Mouse button flags. /// [Flags] public enum PointerButton { None = 0, Left = 1, Middle = 2, Right = 4, XButton1 = 8, XButton2 = 16 } /// /// Event args for scroll events. /// public class ScrollEventArgs : EventArgs { public float X { get; } public float Y { get; } public float DeltaX { get; } public float DeltaY { get; } public KeyModifiers Modifiers { get; } public bool Handled { get; set; } /// /// Gets whether the Control key is pressed during this scroll event. /// public bool IsControlPressed => (Modifiers & KeyModifiers.Control) != 0; public ScrollEventArgs(float x, float y, float deltaX, float deltaY, KeyModifiers modifiers = KeyModifiers.None) { X = x; Y = y; DeltaX = deltaX; DeltaY = deltaY; Modifiers = modifiers; } } /// /// Event args for keyboard events. /// public class KeyEventArgs : EventArgs { public Key Key { get; } public KeyModifiers Modifiers { get; } public bool Handled { get; set; } public KeyEventArgs(Key key, KeyModifiers modifiers = KeyModifiers.None) { Key = key; Modifiers = modifiers; } } /// /// Event args for text input events. /// public class TextInputEventArgs : EventArgs { public string Text { get; } public bool Handled { get; set; } public TextInputEventArgs(string text) { Text = text; } } /// /// Keyboard modifier flags. /// [Flags] public enum KeyModifiers { None = 0, Shift = 1, Control = 2, Alt = 4, Super = 8, CapsLock = 16, NumLock = 32 }