More fixes
This commit is contained in:
@@ -30,6 +30,8 @@ public partial class BorderHandler : ViewHandler<IBorderView, SkiaBorder>
|
||||
[nameof(IView.Background)] = MapBackground,
|
||||
["BackgroundColor"] = MapBackgroundColor,
|
||||
[nameof(IPadding.Padding)] = MapPadding,
|
||||
["WidthRequest"] = MapWidthRequest,
|
||||
["HeightRequest"] = MapHeightRequest,
|
||||
};
|
||||
|
||||
public static CommandMapper<IBorderView, BorderHandler> CommandMapper =
|
||||
@@ -59,6 +61,27 @@ public partial class BorderHandler : ViewHandler<IBorderView, SkiaBorder>
|
||||
platformView.MauiView = view;
|
||||
}
|
||||
platformView.Tapped += OnPlatformViewTapped;
|
||||
|
||||
// Explicitly map properties since they may be set before handler creation
|
||||
if (VirtualView is VisualElement ve)
|
||||
{
|
||||
if (ve.BackgroundColor != null)
|
||||
{
|
||||
platformView.BackgroundColor = ve.BackgroundColor;
|
||||
}
|
||||
else if (ve.Background is SolidColorBrush brush && brush.Color != null)
|
||||
{
|
||||
platformView.BackgroundColor = brush.Color;
|
||||
}
|
||||
if (ve.WidthRequest >= 0)
|
||||
{
|
||||
platformView.WidthRequest = ve.WidthRequest;
|
||||
}
|
||||
if (ve.HeightRequest >= 0)
|
||||
{
|
||||
platformView.HeightRequest = ve.HeightRequest;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override void DisconnectHandler(SkiaBorder platformView)
|
||||
@@ -130,12 +153,17 @@ public partial class BorderHandler : ViewHandler<IBorderView, SkiaBorder>
|
||||
{
|
||||
if (handler.PlatformView is null) return;
|
||||
|
||||
if (border is VisualElement ve && ve.BackgroundColor != null)
|
||||
if (border is VisualElement ve)
|
||||
{
|
||||
handler.PlatformView.BackgroundColor = ve.BackgroundColor;
|
||||
var bgColor = ve.BackgroundColor;
|
||||
Console.WriteLine($"[BorderHandler] MapBackgroundColor: {bgColor}");
|
||||
if (bgColor != null)
|
||||
{
|
||||
handler.PlatformView.BackgroundColor = bgColor;
|
||||
handler.PlatformView.Invalidate();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapPadding(BorderHandler handler, IBorderView border)
|
||||
{
|
||||
@@ -239,4 +267,26 @@ public partial class BorderHandler : ViewHandler<IBorderView, SkiaBorder>
|
||||
}
|
||||
handler.PlatformView.Invalidate();
|
||||
}
|
||||
|
||||
public static void MapWidthRequest(BorderHandler handler, IBorderView border)
|
||||
{
|
||||
if (handler.PlatformView is null) return;
|
||||
|
||||
if (border is VisualElement ve && ve.WidthRequest >= 0)
|
||||
{
|
||||
handler.PlatformView.WidthRequest = ve.WidthRequest;
|
||||
handler.PlatformView.InvalidateMeasure();
|
||||
}
|
||||
}
|
||||
|
||||
public static void MapHeightRequest(BorderHandler handler, IBorderView border)
|
||||
{
|
||||
if (handler.PlatformView is null) return;
|
||||
|
||||
if (border is VisualElement ve && ve.HeightRequest >= 0)
|
||||
{
|
||||
handler.PlatformView.HeightRequest = ve.HeightRequest;
|
||||
handler.PlatformView.InvalidateMeasure();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -688,53 +688,17 @@ public class LinuxApplication : IDisposable
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Triggers MAUI's internal RequestedThemeChanged event to force AppThemeBinding updates.
|
||||
/// Called after theme change to refresh views.
|
||||
/// Note: MAUI's Application.UserAppTheme setter automatically triggers RequestedThemeChanged
|
||||
/// via WeakEventManager, which AppThemeBinding subscribes to. This method handles
|
||||
/// any additional platform-specific refresh needed.
|
||||
/// </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}");
|
||||
}
|
||||
Console.WriteLine($"[LinuxApplication] Theme is now: {app.UserAppTheme}, RequestedTheme: {app.RequestedTheme}");
|
||||
}
|
||||
|
||||
private void RefreshViewTheme(SkiaView view)
|
||||
@@ -749,8 +713,9 @@ public class LinuxApplication : IDisposable
|
||||
// This ensures theme-dependent bindings are re-evaluated
|
||||
try
|
||||
{
|
||||
// Background/BackgroundColor
|
||||
// Background/BackgroundColor - both need updating for AppThemeBinding
|
||||
handler.UpdateValue(nameof(IView.Background));
|
||||
handler.UpdateValue("BackgroundColor");
|
||||
|
||||
// For ImageButton, force Source to be re-mapped
|
||||
if (mauiView is Microsoft.Maui.Controls.ImageButton)
|
||||
@@ -788,8 +753,29 @@ public class LinuxApplication : IDisposable
|
||||
}
|
||||
}
|
||||
|
||||
// Special handling for ItemsViews (CollectionView, ListView)
|
||||
// Their item views are cached separately and need to be refreshed
|
||||
if (view is SkiaItemsView itemsView)
|
||||
{
|
||||
itemsView.RefreshTheme();
|
||||
}
|
||||
|
||||
// Special handling for NavigationPage - it stores content in _currentPage
|
||||
if (view is SkiaNavigationPage navPage && navPage.CurrentPage != null)
|
||||
{
|
||||
RefreshViewTheme(navPage.CurrentPage);
|
||||
}
|
||||
|
||||
// Special handling for ContentPage - it stores content in Content property
|
||||
if (view is SkiaPage page && page.Content != null)
|
||||
{
|
||||
RefreshViewTheme(page.Content);
|
||||
}
|
||||
|
||||
// Recursively process children
|
||||
foreach (var child in view.Children)
|
||||
// Note: SkiaLayoutView hides SkiaView.Children with 'new', so we need to cast
|
||||
IReadOnlyList<SkiaView> children = view is SkiaLayoutView layout ? layout.Children : view.Children;
|
||||
foreach (var child in children)
|
||||
{
|
||||
RefreshViewTheme(child);
|
||||
}
|
||||
|
||||
@@ -341,6 +341,14 @@ public class SkiaBorder : SkiaLayoutView
|
||||
return path;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Override to skip rectangular background - OnDraw handles it with the correct shape.
|
||||
/// </summary>
|
||||
protected override void DrawBackground(SKCanvas canvas, SKRect bounds)
|
||||
{
|
||||
// Don't draw rectangular background - OnDraw draws background with shape path
|
||||
}
|
||||
|
||||
protected override void OnDraw(SKCanvas canvas, SKRect bounds)
|
||||
{
|
||||
float strokeThickness = (float)StrokeThickness;
|
||||
|
||||
@@ -77,7 +77,7 @@ public class SkiaEditor : SkiaView, IInputContext
|
||||
nameof(BorderColor),
|
||||
typeof(Color),
|
||||
typeof(SkiaEditor),
|
||||
Color.FromRgb(0xBD, 0xBD, 0xBD),
|
||||
Colors.Transparent,
|
||||
BindingMode.TwoWay,
|
||||
propertyChanged: (b, o, n) => ((SkiaEditor)b).Invalidate());
|
||||
|
||||
@@ -301,7 +301,7 @@ public class SkiaEditor : SkiaView, IInputContext
|
||||
nameof(EditorBackgroundColor),
|
||||
typeof(Color),
|
||||
typeof(SkiaEditor),
|
||||
Colors.White,
|
||||
Colors.Transparent,
|
||||
BindingMode.TwoWay,
|
||||
propertyChanged: (b, o, n) => ((SkiaEditor)b).Invalidate());
|
||||
|
||||
@@ -891,7 +891,9 @@ public class SkiaEditor : SkiaView, IInputContext
|
||||
};
|
||||
canvas.DrawRoundRect(new SKRoundRect(bounds, cornerRadius), bgPaint);
|
||||
|
||||
// Draw border
|
||||
// Draw border only if BorderColor is not transparent
|
||||
if (BorderColor != null && BorderColor != Colors.Transparent && BorderColor.Alpha > 0)
|
||||
{
|
||||
using var borderPaint = new SKPaint
|
||||
{
|
||||
Color = IsFocused ? ToSKColor(CursorColor) : ToSKColor(BorderColor),
|
||||
@@ -900,6 +902,7 @@ public class SkiaEditor : SkiaView, IInputContext
|
||||
IsAntialias = true
|
||||
};
|
||||
canvas.DrawRoundRect(new SKRoundRect(bounds, cornerRadius), borderPaint);
|
||||
}
|
||||
|
||||
// Setup text rendering
|
||||
using var font = new SKFont(SKTypeface.Default, fontSize);
|
||||
@@ -1085,6 +1088,13 @@ public class SkiaEditor : SkiaView, IInputContext
|
||||
{
|
||||
if (!IsEnabled) return;
|
||||
|
||||
// Handle right-click context menu
|
||||
if (e.Button == PointerButton.Right)
|
||||
{
|
||||
ShowContextMenu(e.X, e.Y);
|
||||
return;
|
||||
}
|
||||
|
||||
IsFocused = true;
|
||||
|
||||
// Use screen coordinates for proper hit detection
|
||||
@@ -1491,6 +1501,39 @@ public class SkiaEditor : SkiaView, IInputContext
|
||||
_selectionLength = 0;
|
||||
}
|
||||
|
||||
private void ShowContextMenu(float x, float y)
|
||||
{
|
||||
Console.WriteLine($"[SkiaEditor] ShowContextMenu at ({x}, {y})");
|
||||
bool hasSelection = _selectionLength != 0;
|
||||
bool hasText = !string.IsNullOrEmpty(Text);
|
||||
bool hasClipboard = !string.IsNullOrEmpty(SystemClipboard.GetText());
|
||||
bool isEditable = !IsReadOnly;
|
||||
|
||||
GtkContextMenuService.ShowContextMenu(new List<GtkMenuItem>
|
||||
{
|
||||
new GtkMenuItem("Cut", () =>
|
||||
{
|
||||
CutToClipboard();
|
||||
Invalidate();
|
||||
}, hasSelection && isEditable),
|
||||
new GtkMenuItem("Copy", () =>
|
||||
{
|
||||
CopyToClipboard();
|
||||
}, hasSelection),
|
||||
new GtkMenuItem("Paste", () =>
|
||||
{
|
||||
PasteFromClipboard();
|
||||
Invalidate();
|
||||
}, hasClipboard && isEditable),
|
||||
GtkMenuItem.Separator,
|
||||
new GtkMenuItem("Select All", () =>
|
||||
{
|
||||
SelectAll();
|
||||
Invalidate();
|
||||
}, hasText)
|
||||
});
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -129,6 +129,18 @@ public class SkiaItemsView : SkiaView
|
||||
_scrollOffset = 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when theme changes to refresh all cached item views.
|
||||
/// Clears the item view cache so items are recreated with new theme colors.
|
||||
/// </summary>
|
||||
public virtual void RefreshTheme()
|
||||
{
|
||||
// Clear cached views to force recreation with new AppThemeBinding values
|
||||
_itemViewCache.Clear();
|
||||
_itemHeights.Clear();
|
||||
Invalidate();
|
||||
}
|
||||
|
||||
private void OnCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
|
||||
{
|
||||
RefreshItems();
|
||||
|
||||
@@ -433,24 +433,50 @@ public class SkiaContentPage : SkiaPage
|
||||
};
|
||||
|
||||
float rightEdge = navBarBounds.Right - 16;
|
||||
const float iconSize = 24f;
|
||||
const float itemPadding = 12f;
|
||||
|
||||
foreach (var item in primaryItems.AsEnumerable().Reverse())
|
||||
{
|
||||
var textBounds = new SKRect();
|
||||
textPaint.MeasureText(item.Text, ref textBounds);
|
||||
float itemWidth;
|
||||
float itemLeft;
|
||||
|
||||
var itemWidth = textBounds.Width + 24; // Padding
|
||||
var itemLeft = rightEdge - itemWidth;
|
||||
if (item.Icon != null)
|
||||
{
|
||||
// Icon-based toolbar item
|
||||
itemWidth = iconSize + itemPadding * 2;
|
||||
itemLeft = rightEdge - itemWidth;
|
||||
|
||||
// Store hit area for click handling
|
||||
item.HitBounds = new SKRect(itemLeft, navBarBounds.Top, rightEdge, navBarBounds.Bottom);
|
||||
|
||||
// Draw icon centered in the hit area
|
||||
var iconX = itemLeft + itemPadding;
|
||||
var iconY = navBarBounds.MidY - iconSize / 2;
|
||||
var destRect = new SKRect(iconX, iconY, iconX + iconSize, iconY + iconSize);
|
||||
canvas.DrawBitmap(item.Icon, destRect);
|
||||
|
||||
Console.WriteLine($"[SkiaContentPage] Drew toolbar icon '{item.Text}' at ({iconX}, {iconY})");
|
||||
}
|
||||
else
|
||||
{
|
||||
// Text-based toolbar item (fallback)
|
||||
var textBounds = new SKRect();
|
||||
textPaint.MeasureText(item.Text, ref textBounds);
|
||||
|
||||
itemWidth = textBounds.Width + 24;
|
||||
itemLeft = rightEdge - itemWidth;
|
||||
|
||||
// Store hit area for click handling
|
||||
item.HitBounds = new SKRect(itemLeft, navBarBounds.Top, rightEdge, navBarBounds.Bottom);
|
||||
Console.WriteLine($"[SkiaContentPage] Toolbar item '{item.Text}' HitBounds set to {item.HitBounds}");
|
||||
|
||||
// Draw text
|
||||
var x = itemLeft + 12;
|
||||
var y = navBarBounds.MidY - textBounds.MidY;
|
||||
canvas.DrawText(item.Text, x, y, textPaint);
|
||||
}
|
||||
|
||||
Console.WriteLine($"[SkiaContentPage] Toolbar item '{item.Text}' HitBounds set to {item.HitBounds}");
|
||||
rightEdge = itemLeft - 8; // Gap between items
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user