More fixes
This commit is contained in:
@@ -30,6 +30,8 @@ public partial class BorderHandler : ViewHandler<IBorderView, SkiaBorder>
|
|||||||
[nameof(IView.Background)] = MapBackground,
|
[nameof(IView.Background)] = MapBackground,
|
||||||
["BackgroundColor"] = MapBackgroundColor,
|
["BackgroundColor"] = MapBackgroundColor,
|
||||||
[nameof(IPadding.Padding)] = MapPadding,
|
[nameof(IPadding.Padding)] = MapPadding,
|
||||||
|
["WidthRequest"] = MapWidthRequest,
|
||||||
|
["HeightRequest"] = MapHeightRequest,
|
||||||
};
|
};
|
||||||
|
|
||||||
public static CommandMapper<IBorderView, BorderHandler> CommandMapper =
|
public static CommandMapper<IBorderView, BorderHandler> CommandMapper =
|
||||||
@@ -59,6 +61,27 @@ public partial class BorderHandler : ViewHandler<IBorderView, SkiaBorder>
|
|||||||
platformView.MauiView = view;
|
platformView.MauiView = view;
|
||||||
}
|
}
|
||||||
platformView.Tapped += OnPlatformViewTapped;
|
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)
|
protected override void DisconnectHandler(SkiaBorder platformView)
|
||||||
@@ -130,10 +153,15 @@ public partial class BorderHandler : ViewHandler<IBorderView, SkiaBorder>
|
|||||||
{
|
{
|
||||||
if (handler.PlatformView is null) return;
|
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;
|
||||||
handler.PlatformView.Invalidate();
|
Console.WriteLine($"[BorderHandler] MapBackgroundColor: {bgColor}");
|
||||||
|
if (bgColor != null)
|
||||||
|
{
|
||||||
|
handler.PlatformView.BackgroundColor = bgColor;
|
||||||
|
handler.PlatformView.Invalidate();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -239,4 +267,26 @@ public partial class BorderHandler : ViewHandler<IBorderView, SkiaBorder>
|
|||||||
}
|
}
|
||||||
handler.PlatformView.Invalidate();
|
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>
|
/// <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>
|
/// </summary>
|
||||||
private void TriggerMauiThemeChanged()
|
private void TriggerMauiThemeChanged()
|
||||||
{
|
{
|
||||||
try
|
var app = Application.Current;
|
||||||
{
|
if (app == null) return;
|
||||||
var app = Application.Current;
|
|
||||||
if (app == null) return;
|
|
||||||
|
|
||||||
var currentTheme = app.UserAppTheme;
|
Console.WriteLine($"[LinuxApplication] Theme is now: {app.UserAppTheme}, RequestedTheme: {app.RequestedTheme}");
|
||||||
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)
|
private void RefreshViewTheme(SkiaView view)
|
||||||
@@ -749,8 +713,9 @@ public class LinuxApplication : IDisposable
|
|||||||
// This ensures theme-dependent bindings are re-evaluated
|
// This ensures theme-dependent bindings are re-evaluated
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// Background/BackgroundColor
|
// Background/BackgroundColor - both need updating for AppThemeBinding
|
||||||
handler.UpdateValue(nameof(IView.Background));
|
handler.UpdateValue(nameof(IView.Background));
|
||||||
|
handler.UpdateValue("BackgroundColor");
|
||||||
|
|
||||||
// For ImageButton, force Source to be re-mapped
|
// For ImageButton, force Source to be re-mapped
|
||||||
if (mauiView is Microsoft.Maui.Controls.ImageButton)
|
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
|
// 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);
|
RefreshViewTheme(child);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -341,6 +341,14 @@ public class SkiaBorder : SkiaLayoutView
|
|||||||
return path;
|
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)
|
protected override void OnDraw(SKCanvas canvas, SKRect bounds)
|
||||||
{
|
{
|
||||||
float strokeThickness = (float)StrokeThickness;
|
float strokeThickness = (float)StrokeThickness;
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ public class SkiaEditor : SkiaView, IInputContext
|
|||||||
nameof(BorderColor),
|
nameof(BorderColor),
|
||||||
typeof(Color),
|
typeof(Color),
|
||||||
typeof(SkiaEditor),
|
typeof(SkiaEditor),
|
||||||
Color.FromRgb(0xBD, 0xBD, 0xBD),
|
Colors.Transparent,
|
||||||
BindingMode.TwoWay,
|
BindingMode.TwoWay,
|
||||||
propertyChanged: (b, o, n) => ((SkiaEditor)b).Invalidate());
|
propertyChanged: (b, o, n) => ((SkiaEditor)b).Invalidate());
|
||||||
|
|
||||||
@@ -301,7 +301,7 @@ public class SkiaEditor : SkiaView, IInputContext
|
|||||||
nameof(EditorBackgroundColor),
|
nameof(EditorBackgroundColor),
|
||||||
typeof(Color),
|
typeof(Color),
|
||||||
typeof(SkiaEditor),
|
typeof(SkiaEditor),
|
||||||
Colors.White,
|
Colors.Transparent,
|
||||||
BindingMode.TwoWay,
|
BindingMode.TwoWay,
|
||||||
propertyChanged: (b, o, n) => ((SkiaEditor)b).Invalidate());
|
propertyChanged: (b, o, n) => ((SkiaEditor)b).Invalidate());
|
||||||
|
|
||||||
@@ -891,15 +891,18 @@ public class SkiaEditor : SkiaView, IInputContext
|
|||||||
};
|
};
|
||||||
canvas.DrawRoundRect(new SKRoundRect(bounds, cornerRadius), bgPaint);
|
canvas.DrawRoundRect(new SKRoundRect(bounds, cornerRadius), bgPaint);
|
||||||
|
|
||||||
// Draw border
|
// Draw border only if BorderColor is not transparent
|
||||||
using var borderPaint = new SKPaint
|
if (BorderColor != null && BorderColor != Colors.Transparent && BorderColor.Alpha > 0)
|
||||||
{
|
{
|
||||||
Color = IsFocused ? ToSKColor(CursorColor) : ToSKColor(BorderColor),
|
using var borderPaint = new SKPaint
|
||||||
Style = SKPaintStyle.Stroke,
|
{
|
||||||
StrokeWidth = IsFocused ? 2 : 1,
|
Color = IsFocused ? ToSKColor(CursorColor) : ToSKColor(BorderColor),
|
||||||
IsAntialias = true
|
Style = SKPaintStyle.Stroke,
|
||||||
};
|
StrokeWidth = IsFocused ? 2 : 1,
|
||||||
canvas.DrawRoundRect(new SKRoundRect(bounds, cornerRadius), borderPaint);
|
IsAntialias = true
|
||||||
|
};
|
||||||
|
canvas.DrawRoundRect(new SKRoundRect(bounds, cornerRadius), borderPaint);
|
||||||
|
}
|
||||||
|
|
||||||
// Setup text rendering
|
// Setup text rendering
|
||||||
using var font = new SKFont(SKTypeface.Default, fontSize);
|
using var font = new SKFont(SKTypeface.Default, fontSize);
|
||||||
@@ -1085,6 +1088,13 @@ public class SkiaEditor : SkiaView, IInputContext
|
|||||||
{
|
{
|
||||||
if (!IsEnabled) return;
|
if (!IsEnabled) return;
|
||||||
|
|
||||||
|
// Handle right-click context menu
|
||||||
|
if (e.Button == PointerButton.Right)
|
||||||
|
{
|
||||||
|
ShowContextMenu(e.X, e.Y);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
IsFocused = true;
|
IsFocused = true;
|
||||||
|
|
||||||
// Use screen coordinates for proper hit detection
|
// Use screen coordinates for proper hit detection
|
||||||
@@ -1491,6 +1501,39 @@ public class SkiaEditor : SkiaView, IInputContext
|
|||||||
_selectionLength = 0;
|
_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
|
#endregion
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -129,6 +129,18 @@ public class SkiaItemsView : SkiaView
|
|||||||
_scrollOffset = 0;
|
_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)
|
private void OnCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
|
||||||
{
|
{
|
||||||
RefreshItems();
|
RefreshItems();
|
||||||
|
|||||||
@@ -433,24 +433,50 @@ public class SkiaContentPage : SkiaPage
|
|||||||
};
|
};
|
||||||
|
|
||||||
float rightEdge = navBarBounds.Right - 16;
|
float rightEdge = navBarBounds.Right - 16;
|
||||||
|
const float iconSize = 24f;
|
||||||
|
const float itemPadding = 12f;
|
||||||
|
|
||||||
foreach (var item in primaryItems.AsEnumerable().Reverse())
|
foreach (var item in primaryItems.AsEnumerable().Reverse())
|
||||||
{
|
{
|
||||||
var textBounds = new SKRect();
|
float itemWidth;
|
||||||
textPaint.MeasureText(item.Text, ref textBounds);
|
float itemLeft;
|
||||||
|
|
||||||
var itemWidth = textBounds.Width + 24; // Padding
|
if (item.Icon != null)
|
||||||
var itemLeft = rightEdge - itemWidth;
|
{
|
||||||
|
// 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);
|
||||||
|
|
||||||
|
// Draw text
|
||||||
|
var x = itemLeft + 12;
|
||||||
|
var y = navBarBounds.MidY - textBounds.MidY;
|
||||||
|
canvas.DrawText(item.Text, x, y, textPaint);
|
||||||
|
}
|
||||||
|
|
||||||
// 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}");
|
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);
|
|
||||||
|
|
||||||
rightEdge = itemLeft - 8; // Gap between items
|
rightEdge = itemLeft - 8; // Gap between items
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user