diff --git a/Hosting/LinuxViewRenderer.cs b/Hosting/LinuxViewRenderer.cs
index f7d4184..a1bac7a 100644
--- a/Hosting/LinuxViewRenderer.cs
+++ b/Hosting/LinuxViewRenderer.cs
@@ -442,6 +442,7 @@ public class LinuxViewRenderer
}
// Create handler for the view
+ // The handler's ConnectHandler and property mappers handle child views automatically
var handler = view.ToHandler(_mauiContext);
if (handler?.PlatformView is not SkiaView skiaView)
@@ -450,98 +451,8 @@ public class LinuxViewRenderer
return CreateFallbackView(view);
}
- // Recursively render children for layout views
- if (view is ILayout layout && skiaView is SkiaLayoutView layoutView)
- {
-
- // For StackLayout, copy orientation and spacing
- if (layoutView is SkiaStackLayout skiaStack)
- {
- if (view is Controls.VerticalStackLayout)
- {
- skiaStack.Orientation = StackOrientation.Vertical;
- }
- else if (view is Controls.HorizontalStackLayout)
- {
- skiaStack.Orientation = StackOrientation.Horizontal;
- }
- else if (view is Controls.StackLayout sl)
- {
- skiaStack.Orientation = sl.Orientation == Microsoft.Maui.Controls.StackOrientation.Vertical
- ? StackOrientation.Vertical : StackOrientation.Horizontal;
- }
-
- if (view is IStackLayout stackLayout)
- {
- skiaStack.Spacing = (float)stackLayout.Spacing;
- }
- }
-
- // For Grid, set up row/column definitions
- if (view is Controls.Grid mauiGrid && layoutView is SkiaGrid skiaGrid)
- {
- // Copy row definitions
- foreach (var rowDef in mauiGrid.RowDefinitions)
- {
- skiaGrid.RowDefinitions.Add(new GridLength((float)rowDef.Height.Value,
- rowDef.Height.IsAbsolute ? GridUnitType.Absolute :
- rowDef.Height.IsStar ? GridUnitType.Star : GridUnitType.Auto));
- }
- // Copy column definitions
- foreach (var colDef in mauiGrid.ColumnDefinitions)
- {
- skiaGrid.ColumnDefinitions.Add(new GridLength((float)colDef.Width.Value,
- colDef.Width.IsAbsolute ? GridUnitType.Absolute :
- colDef.Width.IsStar ? GridUnitType.Star : GridUnitType.Auto));
- }
- skiaGrid.RowSpacing = (float)mauiGrid.RowSpacing;
- skiaGrid.ColumnSpacing = (float)mauiGrid.ColumnSpacing;
- }
-
- foreach (var child in layout)
- {
- if (child is IView childViewElement)
- {
- var childView = RenderView(childViewElement);
- if (childView != null)
- {
- // For Grid, get attached properties for position
- if (layoutView is SkiaGrid grid && child is BindableObject bindable)
- {
- var row = Controls.Grid.GetRow(bindable);
- var col = Controls.Grid.GetColumn(bindable);
- var rowSpan = Controls.Grid.GetRowSpan(bindable);
- var colSpan = Controls.Grid.GetColumnSpan(bindable);
- grid.AddChild(childView, row, col, rowSpan, colSpan);
- }
- else
- {
- layoutView.AddChild(childView);
- }
- }
- }
- }
- }
- else if (view is IContentView contentView && contentView.Content is IView contentElement)
- {
- var content = RenderView(contentElement);
- if (content != null)
- {
- if (skiaView is SkiaBorder border)
- {
- border.AddChild(content);
- }
- else if (skiaView is SkiaFrame frame)
- {
- frame.AddChild(content);
- }
- else if (skiaView is SkiaScrollView scrollView)
- {
- scrollView.Content = content;
- }
- }
- }
-
+ // Handlers manage their own children via ConnectHandler and property mappers
+ // No manual child rendering needed here - that caused "View already has a parent" errors
return skiaView;
}
catch (Exception)
diff --git a/OpenMaui.Controls.Linux.csproj b/OpenMaui.Controls.Linux.csproj
index 4c1f694..7ae1bf9 100644
--- a/OpenMaui.Controls.Linux.csproj
+++ b/OpenMaui.Controls.Linux.csproj
@@ -12,7 +12,7 @@
OpenMaui.Controls.Linux
- 1.0.0-preview.1
+ 1.0.0-preview.4
MarketAlly LLC, David H. Friedel Jr.
MarketAlly LLC
OpenMaui Linux Controls
@@ -23,7 +23,7 @@
https://git.marketally.com/open-maui/maui-linux.git
git
maui;linux;desktop;skia;gui;cross-platform;dotnet;x11;wayland;openmaui
- Initial preview release with 35+ controls and full platform services.
+ Preview 4: Fixed handler rendering for layouts, text wrapping, and scrollbar measurement issues.
README.md
icon.png
false
diff --git a/Views/SkiaLabel.cs b/Views/SkiaLabel.cs
index 9945af2..4d2ca5c 100644
--- a/Views/SkiaLabel.cs
+++ b/Views/SkiaLabel.cs
@@ -429,9 +429,10 @@ public class SkiaLabel : SkiaView
bounds.Bottom - Padding.Bottom);
// Handle single line vs multiline
- // Use DrawSingleLine for normal labels (MaxLines <= 1 or unlimited) without newlines
- // Use DrawMultiLineWithWrapping only when MaxLines > 1 (word wrap needed) or text has newlines
- bool needsMultiLine = MaxLines > 1 || Text.Contains('\n');
+ // Use DrawMultiLineWithWrapping when: MaxLines > 1, text has newlines, OR WordWrap is enabled
+ bool needsMultiLine = MaxLines > 1 || Text.Contains('\n') ||
+ LineBreakMode == LineBreakMode.WordWrap ||
+ LineBreakMode == LineBreakMode.CharacterWrap;
if (needsMultiLine)
{
DrawMultiLineWithWrapping(canvas, paint, font, contentBounds);
@@ -771,8 +772,10 @@ public class SkiaLabel : SkiaView
using var font = new SKFont(typeface, FontSize);
using var paint = new SKPaint(font);
- // Use same logic as OnDraw: multiline only when MaxLines > 1 or text has newlines
- bool needsMultiLine = MaxLines > 1 || Text.Contains('\n');
+ // Use multiline when: MaxLines > 1, text has newlines, OR WordWrap is enabled
+ bool needsMultiLine = MaxLines > 1 || Text.Contains('\n') ||
+ LineBreakMode == LineBreakMode.WordWrap ||
+ LineBreakMode == LineBreakMode.CharacterWrap;
if (!needsMultiLine)
{
var textBounds = new SKRect();
diff --git a/Views/SkiaScrollView.cs b/Views/SkiaScrollView.cs
index 3aa9887..6f412a7 100644
--- a/Views/SkiaScrollView.cs
+++ b/Views/SkiaScrollView.cs
@@ -268,8 +268,16 @@ public class SkiaScrollView : SkiaView
if (_content != null)
{
// Ensure content is measured and arranged
- var availableSize = new SKSize(bounds.Width, float.PositiveInfinity);
- _content.Measure(availableSize);
+ // Account for vertical scrollbar width to prevent horizontal scrollbar from appearing
+ var effectiveWidth = bounds.Width;
+ if (Orientation != ScrollOrientation.Horizontal && VerticalScrollBarVisibility != ScrollBarVisibility.Never)
+ {
+ // Reserve space for vertical scrollbar if content might be taller than viewport
+ effectiveWidth -= ScrollBarWidth;
+ }
+ var availableSize = new SKSize(effectiveWidth, float.PositiveInfinity);
+ // Update ContentSize with the properly constrained measurement
+ ContentSize = _content.Measure(availableSize);
// Apply content's margin
var margin = _content.Margin;
@@ -669,12 +677,18 @@ public class SkiaScrollView : SkiaView
case ScrollOrientation.Both:
// For Both: first measure with viewport width to get responsive layout
// Content can still exceed viewport if it has minimum width constraints
+ // Reserve space for vertical scrollbar to prevent horizontal scrollbar
contentWidth = float.IsInfinity(availableSize.Width) ? 800f : availableSize.Width;
+ if (VerticalScrollBarVisibility != ScrollBarVisibility.Never)
+ contentWidth -= ScrollBarWidth;
contentHeight = float.PositiveInfinity;
break;
case ScrollOrientation.Vertical:
default:
+ // Reserve space for vertical scrollbar to prevent horizontal scrollbar
contentWidth = float.IsInfinity(availableSize.Width) ? 800f : availableSize.Width;
+ if (VerticalScrollBarVisibility != ScrollBarVisibility.Never)
+ contentWidth -= ScrollBarWidth;
contentHeight = float.PositiveInfinity;
break;
}
diff --git a/Views/SkiaShell.cs b/Views/SkiaShell.cs
index 27eaabd..74a051c 100644
--- a/Views/SkiaShell.cs
+++ b/Views/SkiaShell.cs
@@ -287,14 +287,14 @@ public class SkiaShell : SkiaLayoutView
protected override SKSize MeasureOverride(SKSize availableSize)
{
- // Measure current content
+ // Measure current content with padding accounted for (consistent with ArrangeOverride)
if (_currentContent != null)
{
float contentTop = NavBarIsVisible ? NavBarHeight : 0;
float contentBottom = TabBarIsVisible ? TabBarHeight : 0;
var contentSize = new SKSize(
- availableSize.Width,
- availableSize.Height - contentTop - contentBottom);
+ availableSize.Width - (float)Padding.Left - (float)Padding.Right,
+ availableSize.Height - contentTop - contentBottom - (float)Padding.Top - (float)Padding.Bottom);
_currentContent.Measure(contentSize);
}