More fixes

This commit is contained in:
2026-01-17 08:06:22 +00:00
parent f1a368a6c2
commit dc52f7f2bc
18 changed files with 1022 additions and 65 deletions

View File

@@ -110,6 +110,7 @@ public class LinuxApplication : IDisposable
/// <summary>
/// Requests a redraw of the application.
/// Thread-safe - will marshal to GTK thread if needed.
/// </summary>
public static void RequestRedraw()
{
@@ -117,6 +118,27 @@ public class LinuxApplication : IDisposable
if (_isRedrawing)
return;
// Check if we're on the GTK thread
int currentThread = Environment.CurrentManagedThreadId;
if (_gtkThreadId != 0 && currentThread != _gtkThreadId)
{
// We're on a background thread - use IdleAdd to marshal to GTK thread
GLibNative.IdleAdd(() =>
{
RequestRedrawInternal();
return false; // Don't repeat
});
return;
}
RequestRedrawInternal();
}
private static void RequestRedrawInternal()
{
if (_isRedrawing)
return;
_isRedrawing = true;
try
{
@@ -197,7 +219,18 @@ public class LinuxApplication : IDisposable
Current = this;
// Set up dialog service invalidation callback
LinuxDialogService.SetInvalidateCallback(() => _renderingEngine?.InvalidateAll());
// This callback will work for both GTK and X11 modes
LinuxDialogService.SetInvalidateCallback(() =>
{
if (_useGtk)
{
_gtkWindow?.RequestRedraw();
}
else
{
_renderingEngine?.InvalidateAll();
}
});
}
/// <summary>
@@ -265,6 +298,20 @@ public class LinuxApplication : IDisposable
currentProperty.SetValue(null, mauiApplication);
}
// Set initial theme based on system theme
var systemTheme = SystemThemeService.Instance.CurrentTheme;
Console.WriteLine($"[LinuxApplication] System theme detected at startup: {systemTheme}");
if (systemTheme == SystemTheme.Dark)
{
mauiApplication.UserAppTheme = AppTheme.Dark;
Console.WriteLine("[LinuxApplication] Set initial UserAppTheme to Dark based on system theme");
}
else
{
mauiApplication.UserAppTheme = AppTheme.Light;
Console.WriteLine("[LinuxApplication] Set initial UserAppTheme to Light based on system theme");
}
// Handle user-initiated theme changes
((BindableObject)mauiApplication).PropertyChanged += (s, e) =>
{
@@ -272,7 +319,19 @@ public class LinuxApplication : IDisposable
{
Console.WriteLine($"[LinuxApplication] User theme changed to: {mauiApplication.UserAppTheme}");
LinuxViewRenderer.CurrentSkiaShell?.RefreshTheme();
linuxApp._renderingEngine?.InvalidateAll();
// Force re-render the entire page to pick up theme changes
linuxApp.RefreshPageForThemeChange();
// Invalidate to redraw - use correct method based on mode
if (linuxApp._useGtk)
{
linuxApp._gtkWindow?.RequestRedraw();
}
else
{
linuxApp._renderingEngine?.InvalidateAll();
}
}
};
@@ -280,15 +339,73 @@ public class LinuxApplication : IDisposable
SystemThemeService.Instance.ThemeChanged += (s, e) =>
{
Console.WriteLine($"[LinuxApplication] System theme changed to: {e.NewTheme}");
// Notify MAUI framework that system theme changed
// This will cause AppThemeBinding to re-evaluate
LinuxViewRenderer.CurrentSkiaShell?.RefreshTheme();
linuxApp._renderingEngine?.InvalidateAll();
// Update MAUI's UserAppTheme to match system theme
// This will trigger the PropertyChanged handler which does the refresh
var newAppTheme = e.NewTheme == SystemTheme.Dark ? AppTheme.Dark : AppTheme.Light;
if (mauiApplication.UserAppTheme != newAppTheme)
{
Console.WriteLine($"[LinuxApplication] Setting UserAppTheme to {newAppTheme} to match system");
mauiApplication.UserAppTheme = newAppTheme;
}
else
{
// If UserAppTheme didn't change (user manually set it), still refresh
LinuxViewRenderer.CurrentSkiaShell?.RefreshTheme();
linuxApp.RefreshPageForThemeChange();
if (linuxApp._useGtk)
{
linuxApp._gtkWindow?.RequestRedraw();
}
else
{
linuxApp._renderingEngine?.InvalidateAll();
}
}
};
if (mauiApplication.MainPage != null)
// Get the main page - prefer CreateWindow() over deprecated MainPage
Page? mainPage = null;
// Try CreateWindow() first (the modern MAUI pattern)
try
{
var mainPage = mauiApplication.MainPage;
// CreateWindow is protected, use reflection
var createWindowMethod = typeof(Application).GetMethod("CreateWindow",
BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public,
null, new[] { typeof(IActivationState) }, null);
if (createWindowMethod != null)
{
var mauiWindow = createWindowMethod.Invoke(mauiApplication, new object?[] { null }) as Microsoft.Maui.Controls.Window;
if (mauiWindow != null)
{
Console.WriteLine($"[LinuxApplication] Got Window from CreateWindow: {mauiWindow.GetType().Name}");
mainPage = mauiWindow.Page;
Console.WriteLine($"[LinuxApplication] Window.Page: {mainPage?.GetType().Name}");
// Add to windows list
var windowsField = typeof(Application).GetField("_windows",
BindingFlags.NonPublic | BindingFlags.Instance);
var windowsList = windowsField?.GetValue(mauiApplication) as List<Microsoft.Maui.Controls.Window>;
if (windowsList != null && !windowsList.Contains(mauiWindow))
{
windowsList.Add(mauiWindow);
mauiWindow.Parent = mauiApplication;
}
}
}
}
catch (Exception ex)
{
Console.WriteLine($"[LinuxApplication] CreateWindow failed: {ex.Message}");
}
// Fall back to deprecated MainPage if CreateWindow didn't work
if (mainPage == null && mauiApplication.MainPage != null)
{
Console.WriteLine($"[LinuxApplication] Falling back to MainPage: {mauiApplication.MainPage.GetType().Name}");
mainPage = mauiApplication.MainPage;
var windowsField = typeof(Application).GetField("_windows",
BindingFlags.NonPublic | BindingFlags.Instance);
@@ -304,7 +421,10 @@ public class LinuxApplication : IDisposable
{
windowsList[0].Page = mainPage;
}
}
if (mainPage != null)
{
var renderer = new LinuxViewRenderer(mauiContext);
rootView = renderer.RenderPage(mainPage);
@@ -547,6 +667,133 @@ public class LinuxApplication : IDisposable
}
}
/// <summary>
/// Forces all views to refresh their theme-dependent properties.
/// This is needed because AppThemeBinding may not automatically trigger
/// property mappers on all platforms.
/// </summary>
private void RefreshPageForThemeChange()
{
Console.WriteLine("[LinuxApplication] RefreshPageForThemeChange - forcing property updates");
// First, try to trigger MAUI's RequestedThemeChanged event using reflection
// This ensures AppThemeBinding bindings re-evaluate
TriggerMauiThemeChanged();
if (_rootView == null) return;
// Traverse the visual tree and force theme-dependent properties to update
RefreshViewTheme(_rootView);
}
/// <summary>
/// Triggers MAUI's internal RequestedThemeChanged event to force AppThemeBinding updates.
/// </summary>
private void TriggerMauiThemeChanged()
{
try
{
var app = Application.Current;
if (app == null) return;
var currentTheme = app.UserAppTheme;
Console.WriteLine($"[LinuxApplication] Triggering theme changed event for: {currentTheme}");
// Try to find and invoke the RequestedThemeChanged event
var eventField = typeof(Application).GetField("RequestedThemeChanged",
BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
if (eventField != null)
{
var eventDelegate = eventField.GetValue(app) as MulticastDelegate;
if (eventDelegate != null)
{
var args = new AppThemeChangedEventArgs(currentTheme);
foreach (var handler in eventDelegate.GetInvocationList())
{
handler.DynamicInvoke(app, args);
}
Console.WriteLine("[LinuxApplication] Successfully invoked RequestedThemeChanged handlers");
}
}
else
{
// Try alternative approach - trigger OnPropertyChanged for RequestedTheme
var onPropertyChangedMethod = typeof(BindableObject).GetMethod("OnPropertyChanged",
BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public,
null, new[] { typeof(string) }, null);
if (onPropertyChangedMethod != null)
{
onPropertyChangedMethod.Invoke(app, new object[] { "RequestedTheme" });
Console.WriteLine("[LinuxApplication] Triggered OnPropertyChanged for RequestedTheme");
}
}
}
catch (Exception ex)
{
Console.WriteLine($"[LinuxApplication] Error triggering theme changed: {ex.Message}");
}
}
private void RefreshViewTheme(SkiaView view)
{
// Get the associated MAUI view and handler
var mauiView = view.MauiView;
var handler = mauiView?.Handler;
if (handler != null && mauiView != null)
{
// Force key properties to be re-mapped
// This ensures theme-dependent bindings are re-evaluated
try
{
// Background/BackgroundColor
handler.UpdateValue(nameof(IView.Background));
// For ImageButton, force Source to be re-mapped
if (mauiView is Microsoft.Maui.Controls.ImageButton)
{
handler.UpdateValue(nameof(IImageSourcePart.Source));
}
// For Image, force Source to be re-mapped
if (mauiView is Microsoft.Maui.Controls.Image)
{
handler.UpdateValue(nameof(IImageSourcePart.Source));
}
// For views with text colors
if (mauiView is ITextStyle)
{
handler.UpdateValue(nameof(ITextStyle.TextColor));
}
// For Entry/Editor placeholder colors
if (mauiView is IPlaceholder)
{
handler.UpdateValue(nameof(IPlaceholder.PlaceholderColor));
}
// For Border stroke
if (mauiView is IBorderStroke)
{
handler.UpdateValue(nameof(IBorderStroke.Stroke));
}
}
catch (Exception ex)
{
Console.WriteLine($"[LinuxApplication] Error refreshing theme for {mauiView.GetType().Name}: {ex.Message}");
}
}
// Recursively process children
foreach (var child in view.Children)
{
RefreshViewTheme(child);
}
}
private void UpdateAnimations()
{
// Update cursor blink for entry controls
@@ -802,6 +1049,16 @@ public class LinuxApplication : IDisposable
string buttonName = e.Button == 1 ? "Left" : e.Button == 2 ? "Middle" : e.Button == 3 ? "Right" : $"Unknown({e.Button})";
Console.WriteLine($"[LinuxApplication.GTK] PointerPressed at ({e.X:F1}, {e.Y:F1}), Button={e.Button} ({buttonName})");
// Route to dialog if one is active
if (LinuxDialogService.HasActiveDialog)
{
var button = e.Button == 1 ? PointerButton.Left : e.Button == 2 ? PointerButton.Middle : PointerButton.Right;
var args = new PointerEventArgs((float)e.X, (float)e.Y, button);
LinuxDialogService.TopDialog?.OnPointerPressed(args);
_gtkWindow?.RequestRedraw();
return;
}
if (LinuxDialogService.HasContextMenu)
{
var button = e.Button == 1 ? PointerButton.Left : e.Button == 2 ? PointerButton.Middle : PointerButton.Right;
@@ -842,6 +1099,17 @@ public class LinuxApplication : IDisposable
private void OnGtkPointerReleased(object? sender, (double X, double Y, int Button) e)
{
Console.WriteLine("[DIAG] >>> OnGtkPointerReleased ENTER");
// Route to dialog if one is active
if (LinuxDialogService.HasActiveDialog)
{
var button = e.Button == 1 ? PointerButton.Left : e.Button == 2 ? PointerButton.Middle : PointerButton.Right;
var args = new PointerEventArgs((float)e.X, (float)e.Y, button);
LinuxDialogService.TopDialog?.OnPointerReleased(args);
_gtkWindow?.RequestRedraw();
return;
}
if (_rootView == null) return;
if (_capturedView != null)
@@ -870,6 +1138,15 @@ public class LinuxApplication : IDisposable
private void OnGtkPointerMoved(object? sender, (double X, double Y) e)
{
// Route to dialog if one is active
if (LinuxDialogService.HasActiveDialog)
{
var args = new PointerEventArgs((float)e.X, (float)e.Y);
LinuxDialogService.TopDialog?.OnPointerMoved(args);
_gtkWindow?.RequestRedraw();
return;
}
if (LinuxDialogService.HasContextMenu)
{
var args = new PointerEventArgs((float)e.X, (float)e.Y);
@@ -907,11 +1184,20 @@ public class LinuxApplication : IDisposable
private void OnGtkKeyPressed(object? sender, (uint KeyVal, uint KeyCode, uint State) e)
{
var key = ConvertGdkKey(e.KeyVal);
var modifiers = ConvertGdkModifiers(e.State);
var args = new KeyEventArgs(key, modifiers);
// Route to dialog if one is active
if (LinuxDialogService.HasActiveDialog)
{
LinuxDialogService.TopDialog?.OnKeyDown(args);
_gtkWindow?.RequestRedraw();
return;
}
if (_focusedView != null)
{
var key = ConvertGdkKey(e.KeyVal);
var modifiers = ConvertGdkModifiers(e.State);
var args = new KeyEventArgs(key, modifiers);
_focusedView.OnKeyDown(args);
_gtkWindow?.RequestRedraw();
}
@@ -919,11 +1205,20 @@ public class LinuxApplication : IDisposable
private void OnGtkKeyReleased(object? sender, (uint KeyVal, uint KeyCode, uint State) e)
{
var key = ConvertGdkKey(e.KeyVal);
var modifiers = ConvertGdkModifiers(e.State);
var args = new KeyEventArgs(key, modifiers);
// Route to dialog if one is active
if (LinuxDialogService.HasActiveDialog)
{
LinuxDialogService.TopDialog?.OnKeyUp(args);
_gtkWindow?.RequestRedraw();
return;
}
if (_focusedView != null)
{
var key = ConvertGdkKey(e.KeyVal);
var modifiers = ConvertGdkModifiers(e.State);
var args = new KeyEventArgs(key, modifiers);
_focusedView.OnKeyUp(args);
_gtkWindow?.RequestRedraw();
}