Update with recovered code from VM binaries (Jan 1)

Recovered from decompiled OpenMaui.Controls.Linux.dll:
- SkiaShell.cs: FlyoutHeader, FlyoutFooter, scroll support (918 -> 1325 lines)
- X11Window.cs: Cursor support (XCreateFontCursor, XDefineCursor)
- All handlers with dark mode support
- All services with latest implementations
- LinuxApplication with theme change handling
This commit is contained in:
2026-01-01 06:22:48 -05:00
parent 1e84c6168a
commit 1f096c38dc
254 changed files with 49359 additions and 38457 deletions

View File

@@ -1,343 +1,372 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using SkiaSharp;
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Maui.Platform.Linux.Window;
using Microsoft.Maui.Platform;
using System.Runtime.InteropServices;
using SkiaSharp;
namespace Microsoft.Maui.Platform.Linux.Rendering;
/// <summary>
/// Manages Skia rendering to an X11 window with dirty region optimization.
/// </summary>
public class SkiaRenderingEngine : IDisposable
{
private readonly X11Window _window;
private SKBitmap? _bitmap;
private SKBitmap? _backBuffer;
private SKCanvas? _canvas;
private SKImageInfo _imageInfo;
private bool _disposed;
private bool _fullRedrawNeeded = true;
private readonly X11Window _window;
// Dirty region tracking for optimized rendering
private readonly List<SKRect> _dirtyRegions = new();
private readonly object _dirtyLock = new();
private const int MaxDirtyRegions = 32;
private const float RegionMergeThreshold = 0.3f; // Merge if overlap > 30%
private SKBitmap? _bitmap;
public static SkiaRenderingEngine? Current { get; private set; }
public ResourceCache ResourceCache { get; }
public int Width => _imageInfo.Width;
public int Height => _imageInfo.Height;
private SKBitmap? _backBuffer;
/// <summary>
/// Gets or sets whether dirty region optimization is enabled.
/// When disabled, full redraws occur (useful for debugging).
/// </summary>
public bool EnableDirtyRegionOptimization { get; set; } = true;
private SKCanvas? _canvas;
/// <summary>
/// Gets the number of dirty regions in the current frame.
/// </summary>
public int DirtyRegionCount
{
get { lock (_dirtyLock) return _dirtyRegions.Count; }
}
private SKImageInfo _imageInfo;
public SkiaRenderingEngine(X11Window window)
{
_window = window;
ResourceCache = new ResourceCache();
Current = this;
private bool _disposed;
CreateSurface(window.Width, window.Height);
private bool _fullRedrawNeeded = true;
_window.Resized += OnWindowResized;
_window.Exposed += OnWindowExposed;
}
private readonly List<SKRect> _dirtyRegions = new List<SKRect>();
private void CreateSurface(int width, int height)
{
_bitmap?.Dispose();
_backBuffer?.Dispose();
_canvas?.Dispose();
private readonly object _dirtyLock = new object();
_imageInfo = new SKImageInfo(
Math.Max(1, width),
Math.Max(1, height),
SKColorType.Bgra8888,
SKAlphaType.Premul);
private const int MaxDirtyRegions = 32;
_bitmap = new SKBitmap(_imageInfo);
_backBuffer = new SKBitmap(_imageInfo);
_canvas = new SKCanvas(_bitmap);
_fullRedrawNeeded = true;
private const float RegionMergeThreshold = 0.3f;
lock (_dirtyLock)
{
_dirtyRegions.Clear();
}
}
public static SkiaRenderingEngine? Current { get; private set; }
private void OnWindowResized(object? sender, (int Width, int Height) size)
{
CreateSurface(size.Width, size.Height);
}
public ResourceCache ResourceCache { get; }
private void OnWindowExposed(object? sender, EventArgs e)
{
_fullRedrawNeeded = true;
}
public int Width => ((SKImageInfo)(ref _imageInfo)).Width;
/// <summary>
/// Marks the entire surface as needing redraw.
/// </summary>
public void InvalidateAll()
{
_fullRedrawNeeded = true;
}
public int Height => ((SKImageInfo)(ref _imageInfo)).Height;
/// <summary>
/// Marks a specific region as needing redraw.
/// Multiple regions are tracked and merged for efficiency.
/// </summary>
public void InvalidateRegion(SKRect region)
{
if (region.IsEmpty || region.Width <= 0 || region.Height <= 0)
return;
public bool EnableDirtyRegionOptimization { get; set; } = true;
// Clamp to surface bounds
region = SKRect.Intersect(region, new SKRect(0, 0, Width, Height));
if (region.IsEmpty)
return;
public int DirtyRegionCount
{
get
{
lock (_dirtyLock)
{
return _dirtyRegions.Count;
}
}
}
lock (_dirtyLock)
{
// If we have too many regions, just do a full redraw
if (_dirtyRegions.Count >= MaxDirtyRegions)
{
_fullRedrawNeeded = true;
_dirtyRegions.Clear();
return;
}
public SkiaRenderingEngine(X11Window window)
{
_window = window;
ResourceCache = new ResourceCache();
Current = this;
CreateSurface(window.Width, window.Height);
_window.Resized += OnWindowResized;
_window.Exposed += OnWindowExposed;
}
// Try to merge with existing regions
for (int i = 0; i < _dirtyRegions.Count; i++)
{
var existing = _dirtyRegions[i];
if (ShouldMergeRegions(existing, region))
{
_dirtyRegions[i] = SKRect.Union(existing, region);
return;
}
}
private void CreateSurface(int width, int height)
{
//IL_0044: Unknown result type (might be due to invalid IL or missing references)
//IL_0049: Unknown result type (might be due to invalid IL or missing references)
//IL_0050: Unknown result type (might be due to invalid IL or missing references)
//IL_0055: Unknown result type (might be due to invalid IL or missing references)
//IL_005f: Expected O, but got Unknown
//IL_0061: Unknown result type (might be due to invalid IL or missing references)
//IL_0066: Unknown result type (might be due to invalid IL or missing references)
//IL_0070: Expected O, but got Unknown
//IL_0077: Unknown result type (might be due to invalid IL or missing references)
//IL_0081: Expected O, but got Unknown
SKBitmap? bitmap = _bitmap;
if (bitmap != null)
{
((SKNativeObject)bitmap).Dispose();
}
SKBitmap? backBuffer = _backBuffer;
if (backBuffer != null)
{
((SKNativeObject)backBuffer).Dispose();
}
SKCanvas? canvas = _canvas;
if (canvas != null)
{
((SKNativeObject)canvas).Dispose();
}
_imageInfo = new SKImageInfo(Math.Max(1, width), Math.Max(1, height), (SKColorType)6, (SKAlphaType)2);
_bitmap = new SKBitmap(_imageInfo);
_backBuffer = new SKBitmap(_imageInfo);
_canvas = new SKCanvas(_bitmap);
_fullRedrawNeeded = true;
lock (_dirtyLock)
{
_dirtyRegions.Clear();
}
}
_dirtyRegions.Add(region);
}
}
private void OnWindowResized(object? sender, (int Width, int Height) size)
{
CreateSurface(size.Width, size.Height);
}
private bool ShouldMergeRegions(SKRect a, SKRect b)
{
// Check if regions overlap
var intersection = SKRect.Intersect(a, b);
if (intersection.IsEmpty)
{
// Check if they're adjacent (within a few pixels)
var expanded = new SKRect(a.Left - 4, a.Top - 4, a.Right + 4, a.Bottom + 4);
return expanded.IntersectsWith(b);
}
private void OnWindowExposed(object? sender, EventArgs e)
{
_fullRedrawNeeded = true;
}
// Merge if intersection is significant relative to either region
var intersectionArea = intersection.Width * intersection.Height;
var aArea = a.Width * a.Height;
var bArea = b.Width * b.Height;
var minArea = Math.Min(aArea, bArea);
public void InvalidateAll()
{
_fullRedrawNeeded = true;
}
return intersectionArea / minArea >= RegionMergeThreshold;
}
public void InvalidateRegion(SKRect region)
{
//IL_0026: Unknown result type (might be due to invalid IL or missing references)
//IL_003f: Unknown result type (might be due to invalid IL or missing references)
//IL_0044: Unknown result type (might be due to invalid IL or missing references)
//IL_0049: Unknown result type (might be due to invalid IL or missing references)
//IL_0094: Unknown result type (might be due to invalid IL or missing references)
//IL_0099: Unknown result type (might be due to invalid IL or missing references)
//IL_009b: Unknown result type (might be due to invalid IL or missing references)
//IL_009c: Unknown result type (might be due to invalid IL or missing references)
//IL_00d1: Unknown result type (might be due to invalid IL or missing references)
//IL_00ab: Unknown result type (might be due to invalid IL or missing references)
//IL_00ac: Unknown result type (might be due to invalid IL or missing references)
//IL_00ad: Unknown result type (might be due to invalid IL or missing references)
if (((SKRect)(ref region)).IsEmpty || ((SKRect)(ref region)).Width <= 0f || ((SKRect)(ref region)).Height <= 0f)
{
return;
}
region = SKRect.Intersect(region, new SKRect(0f, 0f, (float)Width, (float)Height));
if (((SKRect)(ref region)).IsEmpty)
{
return;
}
lock (_dirtyLock)
{
if (_dirtyRegions.Count >= 32)
{
_fullRedrawNeeded = true;
_dirtyRegions.Clear();
return;
}
for (int i = 0; i < _dirtyRegions.Count; i++)
{
SKRect val = _dirtyRegions[i];
if (ShouldMergeRegions(val, region))
{
_dirtyRegions[i] = SKRect.Union(val, region);
return;
}
}
_dirtyRegions.Add(region);
}
}
/// <summary>
/// Renders the view tree, optionally using dirty region optimization.
/// </summary>
public void Render(SkiaView rootView)
{
if (_canvas == null || _bitmap == null)
return;
private bool ShouldMergeRegions(SKRect a, SKRect b)
{
//IL_0000: Unknown result type (might be due to invalid IL or missing references)
//IL_0001: Unknown result type (might be due to invalid IL or missing references)
//IL_0002: Unknown result type (might be due to invalid IL or missing references)
//IL_0007: Unknown result type (might be due to invalid IL or missing references)
//IL_004e: Unknown result type (might be due to invalid IL or missing references)
SKRect val = SKRect.Intersect(a, b);
if (((SKRect)(ref val)).IsEmpty)
{
SKRect val2 = default(SKRect);
((SKRect)(ref val2))._002Ector(((SKRect)(ref a)).Left - 4f, ((SKRect)(ref a)).Top - 4f, ((SKRect)(ref a)).Right + 4f, ((SKRect)(ref a)).Bottom + 4f);
return ((SKRect)(ref val2)).IntersectsWith(b);
}
float num = ((SKRect)(ref val)).Width * ((SKRect)(ref val)).Height;
float val3 = ((SKRect)(ref a)).Width * ((SKRect)(ref a)).Height;
float val4 = ((SKRect)(ref b)).Width * ((SKRect)(ref b)).Height;
float num2 = Math.Min(val3, val4);
return num / num2 >= 0.3f;
}
// Measure and arrange
var availableSize = new SKSize(Width, Height);
rootView.Measure(availableSize);
rootView.Arrange(new SKRect(0, 0, Width, Height));
public void Render(SkiaView rootView)
{
//IL_0027: Unknown result type (might be due to invalid IL or missing references)
//IL_0028: Unknown result type (might be due to invalid IL or missing references)
//IL_0047: Unknown result type (might be due to invalid IL or missing references)
//IL_0099: Unknown result type (might be due to invalid IL or missing references)
//IL_0100: Unknown result type (might be due to invalid IL or missing references)
//IL_0105: Unknown result type (might be due to invalid IL or missing references)
//IL_0109: Unknown result type (might be due to invalid IL or missing references)
//IL_015a: Unknown result type (might be due to invalid IL or missing references)
if (_canvas == null || _bitmap == null)
{
return;
}
SKSize availableSize = default(SKSize);
((SKSize)(ref availableSize))._002Ector((float)Width, (float)Height);
rootView.Measure(availableSize);
rootView.Arrange(new SKRect(0f, 0f, (float)Width, (float)Height));
bool flag = _fullRedrawNeeded || !EnableDirtyRegionOptimization;
List<SKRect> list;
lock (_dirtyLock)
{
if (flag)
{
list = new List<SKRect>
{
new SKRect(0f, 0f, (float)Width, (float)Height)
};
_dirtyRegions.Clear();
_fullRedrawNeeded = false;
}
else
{
if (_dirtyRegions.Count == 0)
{
return;
}
list = MergeOverlappingRegions(_dirtyRegions.ToList());
_dirtyRegions.Clear();
}
}
foreach (SKRect item in list)
{
RenderRegion(rootView, item, flag);
}
SkiaView.DrawPopupOverlays(_canvas);
if (LinuxDialogService.HasActiveDialog)
{
LinuxDialogService.DrawDialogs(_canvas, new SKRect(0f, 0f, (float)Width, (float)Height));
}
_canvas.Flush();
PresentToWindow();
}
// Determine what to redraw
List<SKRect> regionsToRedraw;
bool isFullRedraw = _fullRedrawNeeded || !EnableDirtyRegionOptimization;
private void RenderRegion(SkiaView rootView, SKRect region, bool isFullRedraw)
{
//IL_0026: Unknown result type (might be due to invalid IL or missing references)
//IL_002b: Unknown result type (might be due to invalid IL or missing references)
//IL_002c: Unknown result type (might be due to invalid IL or missing references)
//IL_0036: Unknown result type (might be due to invalid IL or missing references)
//IL_003e: Expected O, but got Unknown
//IL_001e: Unknown result type (might be due to invalid IL or missing references)
//IL_0044: Unknown result type (might be due to invalid IL or missing references)
if (_canvas == null)
{
return;
}
_canvas.Save();
if (!isFullRedraw)
{
_canvas.ClipRect(region, (SKClipOperation)1, false);
}
SKPaint val = new SKPaint
{
Color = SKColors.White,
Style = (SKPaintStyle)0
};
try
{
_canvas.DrawRect(region, val);
rootView.Draw(_canvas);
_canvas.Restore();
}
finally
{
((IDisposable)val)?.Dispose();
}
}
lock (_dirtyLock)
{
if (isFullRedraw)
{
regionsToRedraw = new List<SKRect> { new SKRect(0, 0, Width, Height) };
_dirtyRegions.Clear();
_fullRedrawNeeded = false;
}
else if (_dirtyRegions.Count == 0)
{
// Nothing to redraw
return;
}
else
{
regionsToRedraw = MergeOverlappingRegions(_dirtyRegions.ToList());
_dirtyRegions.Clear();
}
}
private List<SKRect> MergeOverlappingRegions(List<SKRect> regions)
{
//IL_0028: Unknown result type (might be due to invalid IL or missing references)
//IL_002d: Unknown result type (might be due to invalid IL or missing references)
//IL_0043: Unknown result type (might be due to invalid IL or missing references)
//IL_0047: Unknown result type (might be due to invalid IL or missing references)
//IL_007f: Unknown result type (might be due to invalid IL or missing references)
//IL_0053: Unknown result type (might be due to invalid IL or missing references)
//IL_0057: Unknown result type (might be due to invalid IL or missing references)
//IL_005c: Unknown result type (might be due to invalid IL or missing references)
//IL_0061: Unknown result type (might be due to invalid IL or missing references)
if (regions.Count <= 1)
{
return regions;
}
List<SKRect> list = new List<SKRect>();
bool[] array = new bool[regions.Count];
for (int i = 0; i < regions.Count; i++)
{
if (array[i])
{
continue;
}
SKRect val = regions[i];
array[i] = true;
bool flag;
do
{
flag = false;
for (int j = i + 1; j < regions.Count; j++)
{
if (!array[j] && ShouldMergeRegions(val, regions[j]))
{
val = SKRect.Union(val, regions[j]);
array[j] = true;
flag = true;
}
}
}
while (flag);
list.Add(val);
}
return list;
}
// Render dirty regions
foreach (var region in regionsToRedraw)
{
RenderRegion(rootView, region, isFullRedraw);
}
private void PresentToWindow()
{
if (_bitmap != null)
{
IntPtr pixels = _bitmap.GetPixels();
if (pixels != IntPtr.Zero)
{
_window.DrawPixels(pixels, ((SKImageInfo)(ref _imageInfo)).Width, ((SKImageInfo)(ref _imageInfo)).Height, ((SKImageInfo)(ref _imageInfo)).RowBytes);
}
}
}
// Draw popup overlays (always on top, full redraw)
SkiaView.DrawPopupOverlays(_canvas);
public SKCanvas? GetCanvas()
{
return _canvas;
}
// Draw modal dialogs on top of everything
if (LinuxDialogService.HasActiveDialog)
{
LinuxDialogService.DrawDialogs(_canvas, new SKRect(0, 0, Width, Height));
}
protected virtual void Dispose(bool disposing)
{
if (_disposed)
{
return;
}
if (disposing)
{
_window.Resized -= OnWindowResized;
_window.Exposed -= OnWindowExposed;
SKCanvas? canvas = _canvas;
if (canvas != null)
{
((SKNativeObject)canvas).Dispose();
}
SKBitmap? bitmap = _bitmap;
if (bitmap != null)
{
((SKNativeObject)bitmap).Dispose();
}
SKBitmap? backBuffer = _backBuffer;
if (backBuffer != null)
{
((SKNativeObject)backBuffer).Dispose();
}
ResourceCache.Dispose();
if (Current == this)
{
Current = null;
}
}
_disposed = true;
}
_canvas.Flush();
// Present to X11 window
PresentToWindow();
}
private void RenderRegion(SkiaView rootView, SKRect region, bool isFullRedraw)
{
if (_canvas == null) return;
_canvas.Save();
if (!isFullRedraw)
{
// Clip to dirty region for partial updates
_canvas.ClipRect(region);
}
// Clear the region
using var clearPaint = new SKPaint { Color = SKColors.White, Style = SKPaintStyle.Fill };
_canvas.DrawRect(region, clearPaint);
// Draw the view tree (views will naturally clip to their bounds)
rootView.Draw(_canvas);
_canvas.Restore();
}
private List<SKRect> MergeOverlappingRegions(List<SKRect> regions)
{
if (regions.Count <= 1)
return regions;
var merged = new List<SKRect>();
var used = new bool[regions.Count];
for (int i = 0; i < regions.Count; i++)
{
if (used[i]) continue;
var current = regions[i];
used[i] = true;
// Keep merging until no more merges possible
bool didMerge;
do
{
didMerge = false;
for (int j = i + 1; j < regions.Count; j++)
{
if (used[j]) continue;
if (ShouldMergeRegions(current, regions[j]))
{
current = SKRect.Union(current, regions[j]);
used[j] = true;
didMerge = true;
}
}
} while (didMerge);
merged.Add(current);
}
return merged;
}
private void PresentToWindow()
{
if (_bitmap == null) return;
var pixels = _bitmap.GetPixels();
if (pixels == IntPtr.Zero) return;
_window.DrawPixels(pixels, _imageInfo.Width, _imageInfo.Height, _imageInfo.RowBytes);
}
public SKCanvas? GetCanvas() => _canvas;
protected virtual void Dispose(bool disposing)
{
if (!_disposed)
{
if (disposing)
{
_window.Resized -= OnWindowResized;
_window.Exposed -= OnWindowExposed;
_canvas?.Dispose();
_bitmap?.Dispose();
_backBuffer?.Dispose();
ResourceCache.Dispose();
if (Current == this) Current = null;
}
_disposed = true;
}
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
}
public class ResourceCache : IDisposable
{
private readonly Dictionary<string, SKTypeface> _typefaces = new();
private bool _disposed;
public SKTypeface GetTypeface(string fontFamily, SKFontStyle style)
{
var key = $"{fontFamily}_{style.Weight}_{style.Width}_{style.Slant}";
if (!_typefaces.TryGetValue(key, out var typeface))
{
typeface = SKTypeface.FromFamilyName(fontFamily, style) ?? SKTypeface.Default;
_typefaces[key] = typeface;
}
return typeface;
}
public void Clear()
{
foreach (var tf in _typefaces.Values) tf.Dispose();
_typefaces.Clear();
}
public void Dispose()
{
if (!_disposed) { Clear(); _disposed = true; }
}
public void Dispose()
{
Dispose(disposing: true);
GC.SuppressFinalize(this);
}
}