Fixes for GTK - still wrong

This commit is contained in:
2026-01-24 06:13:13 +00:00
parent 38c48fc99f
commit f1e3630d1b
11 changed files with 845 additions and 41 deletions

View File

@@ -154,6 +154,9 @@ public sealed class GtkWebViewPlatformView : IDisposable
};
GtkNative.gtk_window_set_title(gtkDialog, title);
// Apply theme-aware CSS styling based on app's current theme
ApplyDialogTheme(gtkDialog);
// Make dialog modal to parent if we have a parent
if (parentWindow != IntPtr.Zero)
{
@@ -211,6 +214,9 @@ public sealed class GtkWebViewPlatformView : IDisposable
return false;
}
// Apply theme-aware CSS styling
ApplyDialogTheme(gtkDialog);
// Get the content area
IntPtr contentArea = GtkNative.gtk_dialog_get_content_area(gtkDialog);
@@ -277,6 +283,134 @@ public sealed class GtkWebViewPlatformView : IDisposable
}
}
/// <summary>
/// Applies theme-aware CSS styling to a GTK dialog based on the app's current theme.
/// </summary>
private void ApplyDialogTheme(IntPtr gtkDialog)
{
try
{
// Check the app's current theme (not the system theme)
bool isDark = Microsoft.Maui.Controls.Application.Current?.UserAppTheme == Microsoft.Maui.ApplicationModel.AppTheme.Dark;
// If UserAppTheme is Unspecified, fall back to RequestedTheme
if (Microsoft.Maui.Controls.Application.Current?.UserAppTheme == Microsoft.Maui.ApplicationModel.AppTheme.Unspecified)
{
isDark = Microsoft.Maui.Controls.Application.Current?.RequestedTheme == Microsoft.Maui.ApplicationModel.AppTheme.Dark;
}
Console.WriteLine($"[GtkWebViewPlatformView] ApplyDialogTheme: isDark={isDark}, UserAppTheme={Microsoft.Maui.Controls.Application.Current?.UserAppTheme}");
// Create comprehensive CSS based on the theme - targeting all dialog elements
string css = isDark
? @"
* {
background-color: #303030;
color: #E0E0E0;
}
window, dialog, messagedialog, .background {
background-color: #303030;
color: #E0E0E0;
}
headerbar, headerbar *, .titlebar, .titlebar * {
background-color: #252525;
background-image: none;
color: #E0E0E0;
border-color: #404040;
box-shadow: none;
}
headerbar button, .titlebar button {
background-color: #353535;
background-image: none;
color: #E0E0E0;
}
.dialog-action-area, .dialog-action-box, actionbar {
background-color: #303030;
}
label, .message-dialog-message, .message-dialog-secondary-message {
color: #E0E0E0;
}
button {
background-image: none;
background-color: #505050;
color: #E0E0E0;
border-color: #606060;
}
button:hover {
background-color: #606060;
}
entry {
background-color: #404040;
color: #E0E0E0;
}
"
: @"
* {
background-color: #FFFFFF;
color: #212121;
}
window, dialog, messagedialog, .background {
background-color: #FFFFFF;
color: #212121;
}
headerbar, headerbar *, .titlebar, .titlebar * {
background-color: #F5F5F5;
background-image: none;
color: #212121;
border-color: #E0E0E0;
box-shadow: none;
}
headerbar button, .titlebar button {
background-color: #EBEBEB;
background-image: none;
color: #212121;
}
.dialog-action-area, .dialog-action-box, actionbar {
background-color: #FFFFFF;
}
label, .message-dialog-message, .message-dialog-secondary-message {
color: #212121;
}
button {
background-image: none;
background-color: #F5F5F5;
color: #212121;
border-color: #E0E0E0;
}
button:hover {
background-color: #E0E0E0;
}
entry {
background-color: #FFFFFF;
color: #212121;
}
";
// Create CSS provider and apply to the screen so all child widgets inherit it
IntPtr cssProvider = GtkNative.gtk_css_provider_new();
if (cssProvider != IntPtr.Zero)
{
GtkNative.gtk_css_provider_load_from_data(cssProvider, css, -1, IntPtr.Zero);
// Get the screen from the dialog and apply CSS to entire screen for this dialog
IntPtr screen = GtkNative.gtk_widget_get_screen(gtkDialog);
if (screen == IntPtr.Zero)
{
screen = GtkNative.gdk_screen_get_default();
}
if (screen != IntPtr.Zero)
{
GtkNative.gtk_style_context_add_provider_for_screen(screen, cssProvider, GtkNative.GTK_STYLE_PROVIDER_PRIORITY_USER);
}
}
}
catch (Exception ex)
{
Console.WriteLine($"[GtkWebViewPlatformView] Error applying dialog theme: {ex.Message}");
}
}
private void OnLoadChanged(IntPtr webView, int loadEvent, IntPtr userData)
{
try

View File

@@ -310,12 +310,19 @@ public class LinuxApplication : IDisposable
Console.WriteLine("[LinuxApplication] Set initial UserAppTheme to Light based on system theme");
}
// Initialize GTK theme service and apply initial CSS
GtkThemeService.ApplyTheme();
// Handle user-initiated theme changes
((BindableObject)mauiApplication).PropertyChanged += (s, e) =>
{
if (e.PropertyName == "UserAppTheme")
{
Console.WriteLine($"[LinuxApplication] User theme changed to: {mauiApplication.UserAppTheme}");
// Apply GTK CSS for dialogs, menus, and window decorations
GtkThemeService.ApplyTheme();
LinuxViewRenderer.CurrentSkiaShell?.RefreshTheme();
// Force re-render the entire page to pick up theme changes

View File

@@ -235,6 +235,31 @@ internal static class GtkNative
[DllImport("libgtk-3.so.0")]
public static extern void gtk_window_set_modal(IntPtr window, bool modal);
// CSS styling for dialogs
[DllImport("libgtk-3.so.0")]
public static extern IntPtr gtk_css_provider_new();
[DllImport("libgtk-3.so.0")]
public static extern bool gtk_css_provider_load_from_data(IntPtr provider, string data, int length, IntPtr error);
[DllImport("libgtk-3.so.0")]
public static extern IntPtr gtk_widget_get_style_context(IntPtr widget);
[DllImport("libgtk-3.so.0")]
public static extern void gtk_style_context_add_provider(IntPtr context, IntPtr provider, uint priority);
[DllImport("libgtk-3.so.0")]
public static extern void gtk_style_context_add_provider_for_screen(IntPtr screen, IntPtr provider, uint priority);
[DllImport("libgtk-3.so.0")]
public static extern IntPtr gtk_widget_get_screen(IntPtr widget);
[DllImport("libgdk-3.so.0")]
public static extern IntPtr gdk_screen_get_default();
public const uint GTK_STYLE_PROVIDER_PRIORITY_APPLICATION = 600;
public const uint GTK_STYLE_PROVIDER_PRIORITY_USER = 800;
// Dialog with custom content (for prompt dialogs)
[DllImport("libgtk-3.so.0")]
public static extern IntPtr gtk_dialog_new_with_buttons(

View File

@@ -55,6 +55,11 @@
<None Include="assets/icon.png" Pack="true" PackagePath="" />
</ItemGroup>
<!-- Include build targets in package - auto-imports when package is referenced -->
<ItemGroup>
<None Include="build\OpenMaui.Controls.Linux.targets" Pack="true" PackagePath="build\OpenMaui.Controls.Linux.targets" />
</ItemGroup>
<!-- Exclude old handler files, samples, templates, and VSIX -->
<ItemGroup>
<Compile Remove="Handlers/*.Linux.cs" />

View File

@@ -75,6 +75,9 @@ public static class GtkContextMenuService
GtkNative.gtk_widget_show(menuItem);
}
// Apply theme-aware CSS styling
ApplyMenuTheme(menu);
GtkNative.gtk_widget_show(menu);
IntPtr currentEvent = GtkNative.gtk_get_current_event();
@@ -87,4 +90,81 @@ public static class GtkContextMenuService
Console.WriteLine($"[GtkContextMenuService] Showed GTK menu with {items.Count} items");
}
/// <summary>
/// Applies theme-aware CSS styling to a GTK menu based on the app's current theme.
/// </summary>
private static void ApplyMenuTheme(IntPtr menu)
{
try
{
// Check the app's current theme (not the system theme)
bool isDark = Microsoft.Maui.Controls.Application.Current?.UserAppTheme == Microsoft.Maui.ApplicationModel.AppTheme.Dark;
// If UserAppTheme is Unspecified, fall back to RequestedTheme
if (Microsoft.Maui.Controls.Application.Current?.UserAppTheme == Microsoft.Maui.ApplicationModel.AppTheme.Unspecified)
{
isDark = Microsoft.Maui.Controls.Application.Current?.RequestedTheme == Microsoft.Maui.ApplicationModel.AppTheme.Dark;
}
Console.WriteLine($"[GtkContextMenuService] ApplyMenuTheme: isDark={isDark}");
// Create comprehensive CSS based on the theme
string css = isDark
? @"
* {
background-color: #303030;
color: #E0E0E0;
}
menu, menuitem, .menu, .menuitem {
background-color: #303030;
color: #E0E0E0;
}
menuitem:hover, .menuitem:hover {
background-color: #505050;
}
separator {
background-color: #505050;
}
"
: @"
* {
background-color: #FFFFFF;
color: #212121;
}
menu, menuitem, .menu, .menuitem {
background-color: #FFFFFF;
color: #212121;
}
menuitem:hover, .menuitem:hover {
background-color: #E0E0E0;
}
separator {
background-color: #E0E0E0;
}
";
// Create CSS provider and apply to the screen
IntPtr cssProvider = GtkNative.gtk_css_provider_new();
if (cssProvider != IntPtr.Zero)
{
GtkNative.gtk_css_provider_load_from_data(cssProvider, css, -1, IntPtr.Zero);
IntPtr screen = GtkNative.gtk_widget_get_screen(menu);
if (screen == IntPtr.Zero)
{
screen = GtkNative.gdk_screen_get_default();
}
if (screen != IntPtr.Zero)
{
GtkNative.gtk_style_context_add_provider_for_screen(screen, cssProvider, GtkNative.GTK_STYLE_PROVIDER_PRIORITY_USER);
}
}
}
catch (Exception ex)
{
Console.WriteLine($"[GtkContextMenuService] Error applying menu theme: {ex.Message}");
}
}
}

544
Services/GtkThemeService.cs Normal file
View File

@@ -0,0 +1,544 @@
using System;
using Microsoft.Maui.Platform.Linux.Native;
namespace Microsoft.Maui.Platform.Linux.Services;
/// <summary>
/// Service for applying theme-aware CSS to GTK widgets.
/// </summary>
public static class GtkThemeService
{
private static IntPtr _currentCssProvider = IntPtr.Zero;
private static bool _initialized = false;
/// <summary>
/// Initializes the GTK theme service and applies initial CSS.
/// </summary>
public static void Initialize()
{
if (_initialized) return;
_initialized = true;
ApplyTheme();
}
/// <summary>
/// Applies GTK CSS based on the app's current theme.
/// Call this whenever the app theme changes.
/// </summary>
public static void ApplyTheme()
{
try
{
// Check the app's current theme
bool isDark = Microsoft.Maui.Controls.Application.Current?.UserAppTheme == Microsoft.Maui.ApplicationModel.AppTheme.Dark;
// If UserAppTheme is Unspecified, fall back to RequestedTheme
if (Microsoft.Maui.Controls.Application.Current?.UserAppTheme == Microsoft.Maui.ApplicationModel.AppTheme.Unspecified)
{
isDark = Microsoft.Maui.Controls.Application.Current?.RequestedTheme == Microsoft.Maui.ApplicationModel.AppTheme.Dark;
}
Console.WriteLine($"[GtkThemeService] ApplyTheme: isDark={isDark}");
// Create comprehensive CSS based on the theme
string css = isDark ? GetDarkCss() : GetLightCss();
// Get the default screen
IntPtr screen = GtkNative.gdk_screen_get_default();
if (screen == IntPtr.Zero)
{
Console.WriteLine("[GtkThemeService] Failed to get default screen");
return;
}
// Create new CSS provider
IntPtr newProvider = GtkNative.gtk_css_provider_new();
if (newProvider == IntPtr.Zero)
{
Console.WriteLine("[GtkThemeService] Failed to create CSS provider");
return;
}
// Load CSS data
if (!GtkNative.gtk_css_provider_load_from_data(newProvider, css, -1, IntPtr.Zero))
{
Console.WriteLine("[GtkThemeService] Failed to load CSS data");
return;
}
// Apply to screen (this affects all GTK widgets)
GtkNative.gtk_style_context_add_provider_for_screen(screen, newProvider, GtkNative.GTK_STYLE_PROVIDER_PRIORITY_USER);
// Store reference to current provider
_currentCssProvider = newProvider;
Console.WriteLine("[GtkThemeService] CSS applied successfully");
}
catch (Exception ex)
{
Console.WriteLine($"[GtkThemeService] Error applying theme: {ex.Message}");
}
}
private static string GetDarkCss()
{
return @"
/* Dark theme - base */
* {
background-color: #303030;
color: #E0E0E0;
}
/* Windows and dialogs */
window, dialog, .background {
background-color: #303030;
color: #E0E0E0;
}
/* Message dialogs - all parts same color */
messagedialog,
messagedialog.background,
messagedialog .background,
messagedialog box,
messagedialog grid,
messagedialog .dialog-vbox,
messagedialog .message-area,
messagedialog .dialog-action-area,
messagedialog .dialog-action-box,
messagedialog actionbar,
messagedialog buttonbox,
messagedialog box.vertical,
messagedialog box.horizontal,
messagedialog .linked,
messagedialog .linked button,
dialog box,
dialog .dialog-vbox,
dialog .dialog-action-area {
background-color: #303030;
background-image: none;
border: none;
border-style: none;
box-shadow: none;
}
messagedialog separator,
dialog separator {
background-color: transparent;
background-image: none;
min-height: 0;
min-width: 0;
border: none;
}
messagedialog .dialog-action-area,
dialog .dialog-action-area {
background-color: #303030;
background-image: none;
border: none;
border-top-style: none;
margin: 0;
padding: 8px;
}
messagedialog .dialog-action-area button,
dialog .dialog-action-area button {
background-color: #505050;
background-image: none;
}
/* Header bars and title bars */
headerbar, .titlebar {
background-color: #252525;
background-image: none;
color: #E0E0E0;
border: none;
box-shadow: none;
}
headerbar *, .titlebar * {
background-color: transparent;
background-image: none;
}
headerbar button, .titlebar button {
background-color: #353535;
background-image: none;
color: #E0E0E0;
border-color: #353535;
border-radius: 6px;
}
headerbar button:hover, .titlebar button:hover {
background-color: #454545;
border-color: #454545;
}
/* Window control buttons */
windowcontrols button,
button.titlebutton,
.titlebutton {
background-color: #252525;
background-image: none;
border-color: #252525;
border-radius: 50%;
min-width: 24px;
min-height: 24px;
padding: 4px;
}
windowcontrols button *,
button.titlebutton *,
.titlebutton * {
background-color: transparent;
background-image: none;
}
windowcontrols button:hover,
button.titlebutton:hover,
.titlebutton:hover {
background-color: #404040;
border-color: #404040;
}
/* Dialog action areas */
.dialog-action-area,
.dialog-action-box,
actionbar {
background-color: #303030;
background-image: none;
border: none;
}
/* Labels */
label {
background-color: transparent;
color: #E0E0E0;
}
/* Buttons */
button {
background-image: none;
background-color: #505050;
color: #E0E0E0;
border-color: #505050;
border-radius: 6px;
}
button * {
background-color: transparent;
background-image: none;
}
button:hover {
background-color: #606060;
border-color: #606060;
}
button:hover * {
background-color: transparent;
}
button:active {
background-color: #404040;
border-color: #404040;
}
/* Entries and text inputs */
entry, textview, text {
background-color: #404040;
color: #E0E0E0;
border-color: #505050;
border-radius: 4px;
}
/* Menus and context menus */
menu, .menu, .popup {
background-color: #303030;
color: #E0E0E0;
border: none;
border-radius: 8px;
padding: 4px;
}
menuitem {
background-color: transparent;
color: #E0E0E0;
border: none;
border-radius: 6px;
padding: 6px 12px;
margin: 2px 4px;
}
menuitem * {
background-color: transparent;
background-image: none;
}
menuitem:hover {
background-color: #505050;
}
menuitem:hover * {
background-color: transparent;
}
/* Popover */
popover, popover.background {
background-color: #303030;
border: none;
border-radius: 8px;
}
/* Separators */
separator {
background-color: #505050;
}
/* Scrollbars */
scrollbar {
background-color: #252525;
}
scrollbar slider {
background-color: #505050;
border-radius: 4px;
}
/* Focus styling */
*:focus {
outline-color: #606060;
}
";
}
private static string GetLightCss()
{
return @"
/* Light theme - base */
* {
background-color: #FFFFFF;
color: #212121;
}
/* Windows and dialogs */
window, dialog, .background {
background-color: #FFFFFF;
color: #212121;
}
/* Message dialogs - all parts same color */
messagedialog,
messagedialog.background,
messagedialog .background,
messagedialog box,
messagedialog grid,
messagedialog .dialog-vbox,
messagedialog .message-area,
messagedialog .dialog-action-area,
messagedialog .dialog-action-box,
messagedialog actionbar,
messagedialog buttonbox,
messagedialog box.vertical,
messagedialog box.horizontal,
messagedialog .linked,
messagedialog .linked button,
dialog box,
dialog .dialog-vbox,
dialog .dialog-action-area {
background-color: #FFFFFF;
background-image: none;
border: none;
border-style: none;
box-shadow: none;
}
messagedialog separator,
dialog separator {
background-color: transparent;
background-image: none;
min-height: 0;
min-width: 0;
border: none;
}
messagedialog .dialog-action-area,
dialog .dialog-action-area {
background-color: #FFFFFF;
background-image: none;
border: none;
border-top-style: none;
margin: 0;
padding: 8px;
}
messagedialog .dialog-action-area button,
dialog .dialog-action-area button {
background-color: #F5F5F5;
background-image: none;
}
/* Header bars and title bars */
headerbar, .titlebar {
background-color: #F5F5F5;
background-image: none;
color: #212121;
border: none;
box-shadow: none;
}
headerbar *, .titlebar * {
background-color: transparent;
background-image: none;
}
headerbar button, .titlebar button {
background-color: #EBEBEB;
background-image: none;
color: #212121;
border-color: #EBEBEB;
border-radius: 6px;
}
headerbar button:hover, .titlebar button:hover {
background-color: #DDDDDD;
border-color: #DDDDDD;
}
/* Window control buttons */
windowcontrols button,
button.titlebutton,
.titlebutton {
background-color: #F5F5F5;
background-image: none;
border-color: #F5F5F5;
border-radius: 50%;
min-width: 24px;
min-height: 24px;
padding: 4px;
}
windowcontrols button *,
button.titlebutton *,
.titlebutton * {
background-color: transparent;
background-image: none;
}
windowcontrols button:hover,
button.titlebutton:hover,
.titlebutton:hover {
background-color: #E0E0E0;
border-color: #E0E0E0;
}
/* Dialog action areas */
.dialog-action-area,
.dialog-action-box,
actionbar {
background-color: #FFFFFF;
background-image: none;
border: none;
}
/* Labels */
label {
background-color: transparent;
color: #212121;
}
/* Buttons */
button {
background-image: none;
background-color: #F5F5F5;
color: #212121;
border-color: #E0E0E0;
border-radius: 6px;
}
button * {
background-color: transparent;
background-image: none;
}
button:hover {
background-color: #E0E0E0;
border-color: #D0D0D0;
}
button:hover * {
background-color: transparent;
}
button:active {
background-color: #D0D0D0;
border-color: #C0C0C0;
}
/* Entries and text inputs */
entry, textview, text {
background-color: #FFFFFF;
color: #212121;
border-color: #E0E0E0;
border-radius: 4px;
}
/* Menus and context menus */
menu, .menu, .popup {
background-color: #FFFFFF;
color: #212121;
border: none;
border-radius: 8px;
padding: 4px;
}
menuitem {
background-color: transparent;
color: #212121;
border: none;
border-radius: 6px;
padding: 6px 12px;
margin: 2px 4px;
}
menuitem * {
background-color: transparent;
background-image: none;
}
menuitem:hover {
background-color: #E8E8E8;
}
menuitem:hover * {
background-color: transparent;
}
/* Popover */
popover, popover.background {
background-color: #FFFFFF;
border: none;
border-radius: 8px;
}
/* Separators */
separator {
background-color: #E0E0E0;
}
/* Scrollbars */
scrollbar {
background-color: #F5F5F5;
}
scrollbar slider {
background-color: #C0C0C0;
border-radius: 4px;
}
/* Focus styling */
*:focus {
outline-color: #C0C0C0;
}
";
}
}

View File

@@ -63,8 +63,10 @@ public static class LinuxDialogService
public static void DrawDialogsOnly(SKCanvas canvas, SKRect bounds)
{
Console.WriteLine($"[LinuxDialogService] DrawDialogsOnly: {_activeDialogs.Count} dialogs, IsDarkMode={SkiaTheme.IsDarkMode}");
foreach (var dialog in _activeDialogs)
{
Console.WriteLine($"[LinuxDialogService] Drawing dialog: IsVisible={dialog.IsVisible}, Opacity={dialog.Opacity}");
dialog.Measure(new Size(bounds.Width, bounds.Height));
dialog.Arrange(new Rect(bounds.Left, bounds.Top, bounds.Width, bounds.Height));
dialog.Draw(canvas);

View File

@@ -22,17 +22,17 @@ public class SkiaAlertDialog : SkiaView
private bool _cancelHovered;
private bool _acceptHovered;
// Dialog styling - using SkiaTheme for MAUI-compliant theming
private static readonly SKColor OverlayColor = SkiaTheme.Overlay50SK;
private static readonly SKColor DialogBackground = SkiaTheme.BackgroundWhiteSK;
private static readonly SKColor TitleColor = SkiaTheme.TextPrimarySK;
private static readonly SKColor MessageColor = SkiaTheme.TextSecondarySK;
private static readonly SKColor ButtonColor = SkiaTheme.PrimarySK;
private static readonly SKColor ButtonHoverColor = SkiaTheme.PrimaryDarkSK;
private static readonly SKColor ButtonTextColor = SkiaTheme.BackgroundWhiteSK;
private static readonly SKColor CancelButtonColor = SkiaTheme.ButtonCancelSK;
private static readonly SKColor CancelButtonHoverColor = SkiaTheme.ButtonCancelHoverSK;
private static readonly SKColor BorderColor = SkiaTheme.BorderLightSK;
// Dialog styling - theme-aware colors (evaluated at draw time)
private static SKColor OverlayColor => SkiaTheme.Overlay50SK;
private static SKColor DialogBackground => SkiaTheme.CurrentSurfaceSK;
private static SKColor TitleColor => SkiaTheme.CurrentTextSK;
private static SKColor MessageColor => SkiaTheme.IsDarkMode ? SkiaTheme.Gray400SK : SkiaTheme.TextSecondarySK;
private static SKColor ButtonColor => SkiaTheme.PrimarySK;
private static SKColor ButtonHoverColor => SkiaTheme.PrimaryDarkSK;
private static SKColor ButtonTextColor => SKColors.White;
private static SKColor CancelButtonColor => SkiaTheme.IsDarkMode ? SkiaTheme.Gray600SK : SkiaTheme.ButtonCancelSK;
private static SKColor CancelButtonHoverColor => SkiaTheme.IsDarkMode ? SkiaTheme.Gray700SK : SkiaTheme.ButtonCancelHoverSK;
private static SKColor BorderColor => SkiaTheme.CurrentBorderSK;
private const float DialogWidth = 400;
private const float DialogPadding = 24;
@@ -61,6 +61,9 @@ public class SkiaAlertDialog : SkiaView
protected override void OnDraw(SKCanvas canvas, SKRect bounds)
{
var app = Application.Current;
Console.WriteLine($"[SkiaAlertDialog] OnDraw: app={app != null}, UserAppTheme={app?.UserAppTheme}, RequestedTheme={app?.RequestedTheme}, IsDarkMode={SkiaTheme.IsDarkMode}, DialogBg={DialogBackground}");
// Draw semi-transparent overlay covering entire screen
using var overlayPaint = new SKPaint
{

View File

@@ -12,15 +12,13 @@ public class SkiaContextMenu : SkiaView
private int _hoveredIndex = -1;
private SKRect[] _itemBounds = Array.Empty<SKRect>();
private static readonly SKColor MenuBackground = SkiaTheme.BackgroundWhiteSK;
private static readonly SKColor MenuBackgroundDark = SkiaTheme.DarkBackgroundSK;
private static readonly SKColor ItemHoverBackground = SkiaTheme.PrimarySelectionSK;
private static readonly SKColor ItemHoverBackgroundDark = SkiaTheme.DarkHoverSK;
private static readonly SKColor ItemTextColor = SkiaTheme.TextPrimarySK;
private static readonly SKColor ItemTextColorDark = SkiaTheme.DarkTextSK;
private static readonly SKColor DisabledTextColor = SkiaTheme.TextDisabledSK;
private static readonly SKColor SeparatorColor = SkiaTheme.Gray300SK;
private static readonly SKColor ShadowColor = SkiaTheme.Shadow25SK;
// Theme-aware colors (evaluated at draw time)
private static SKColor MenuBackground => SkiaTheme.CurrentSurfaceSK;
private static SKColor ItemHoverBackground => SkiaTheme.IsDarkMode ? SkiaTheme.DarkHoverSK : SkiaTheme.PrimarySelectionSK;
private static SKColor ItemTextColor => SkiaTheme.CurrentTextSK;
private static SKColor DisabledTextColor => SkiaTheme.TextDisabledSK;
private static SKColor SeparatorColor => SkiaTheme.IsDarkMode ? SkiaTheme.Gray600SK : SkiaTheme.Gray300SK;
private static SKColor ShadowColor => SkiaTheme.Shadow25SK;
private const float MenuPadding = 4f;
private const float ItemHeight = 32f;
@@ -29,14 +27,12 @@ public class SkiaContextMenu : SkiaView
private const float CornerRadius = 4f;
private const float MinWidth = 120f;
private bool _isDarkTheme;
public SkiaContextMenu(float x, float y, List<ContextMenuItem> items, bool isDarkTheme = false)
{
_x = x;
_y = y;
_items = items;
_isDarkTheme = isDarkTheme;
// isDarkTheme parameter kept for API compatibility but ignored - we use SkiaTheme.IsDarkMode instead
IsFocusable = true;
}
@@ -73,7 +69,7 @@ public class SkiaContextMenu : SkiaView
// Draw background
using (var bgPaint = new SKPaint
{
Color = _isDarkTheme ? MenuBackgroundDark : MenuBackground,
Color = MenuBackground,
IsAntialias = true
})
{
@@ -120,7 +116,7 @@ public class SkiaContextMenu : SkiaView
{
using (var hoverPaint = new SKPaint
{
Color = _isDarkTheme ? ItemHoverBackgroundDark : ItemHoverBackground,
Color = ItemHoverBackground,
IsAntialias = true
})
{
@@ -131,7 +127,7 @@ public class SkiaContextMenu : SkiaView
// Draw text
using (var textPaint = new SKPaint
{
Color = !item.IsEnabled ? DisabledTextColor : (_isDarkTheme ? ItemTextColorDark : ItemTextColor),
Color = !item.IsEnabled ? DisabledTextColor : ItemTextColor,
TextSize = 14f,
IsAntialias = true,
Typeface = SKTypeface.Default

View File

@@ -322,8 +322,23 @@ public static class SkiaTheme
/// <summary>
/// Returns true if dark mode is currently active.
/// Checks UserAppTheme first, falls back to system RequestedTheme.
/// </summary>
public static bool IsDarkMode => Application.Current?.UserAppTheme == AppTheme.Dark;
public static bool IsDarkMode
{
get
{
var app = Application.Current;
if (app == null) return false;
// If user explicitly set a theme, use that
if (app.UserAppTheme != AppTheme.Unspecified)
return app.UserAppTheme == AppTheme.Dark;
// Otherwise use system theme
return app.RequestedTheme == AppTheme.Dark;
}
}
/// <summary>
/// Gets the appropriate button background color for the current theme.

View File

@@ -1,28 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<!--
<!--
OpenMaui Linux Build Targets
Handles MauiImage items for Linux platform since we don't use the full MAUI SDK targets.
-->
<!-- Copy MauiImage items to output directory -->
<!-- Copy MauiImage items to output directory after Build -->
<Target Name="CopyMauiImagesToOutput" AfterTargets="Build" Condition="@(MauiImage) != ''">
<Message Importance="normal" Text="OpenMaui: Copying MauiImage items to output..." />
<Copy SourceFiles="@(MauiImage)"
DestinationFolder="$(OutputPath)"
SkipUnchangedFiles="true" />
<!-- Also copy to Resources/Images subfolder for alternate lookup -->
<Copy SourceFiles="@(MauiImage)"
DestinationFolder="$(OutputPath)Resources/Images"
SkipUnchangedFiles="true" />
<Copy SourceFiles="@(MauiImage)" DestinationFolder="$(OutputPath)" SkipUnchangedFiles="true" />
</Target>
<!-- Ensure the Resources/Images directory exists -->
<Target Name="EnsureResourcesImagesDirectory" BeforeTargets="CopyMauiImagesToOutput">
<MakeDir Directories="$(OutputPath)Resources/Images" Condition="!Exists('$(OutputPath)Resources/Images')" />
<!-- Copy MauiImage items to publish directory after Publish -->
<Target Name="CopyMauiImagesToPublish" AfterTargets="Publish" Condition="@(MauiImage) != ''">
<Message Importance="normal" Text="OpenMaui: Copying MauiImage items to publish folder..." />
<Copy SourceFiles="@(MauiImage)" DestinationFolder="$(PublishDir)" SkipUnchangedFiles="true" />
</Target>
</Project>