Fix layout rendering, text wrapping, and scrollbar issues
- LinuxViewRenderer: Remove redundant child rendering that caused "View already has a parent" errors - SkiaLabel: Consider LineBreakMode.WordWrap for multi-line measurement and rendering - SkiaScrollView: Update ContentSize in OnDraw with properly constrained measurement - SkiaShell: Account for padding in MeasureOverride (consistent with ArrangeOverride) - Version bump to 1.0.0-preview.4 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
02b3da17d4
commit
afbf8f6782
|
|
@ -442,6 +442,7 @@ public class LinuxViewRenderer
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create handler for the view
|
// Create handler for the view
|
||||||
|
// The handler's ConnectHandler and property mappers handle child views automatically
|
||||||
var handler = view.ToHandler(_mauiContext);
|
var handler = view.ToHandler(_mauiContext);
|
||||||
|
|
||||||
if (handler?.PlatformView is not SkiaView skiaView)
|
if (handler?.PlatformView is not SkiaView skiaView)
|
||||||
|
|
@ -450,98 +451,8 @@ public class LinuxViewRenderer
|
||||||
return CreateFallbackView(view);
|
return CreateFallbackView(view);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Recursively render children for layout views
|
// Handlers manage their own children via ConnectHandler and property mappers
|
||||||
if (view is ILayout layout && skiaView is SkiaLayoutView layoutView)
|
// No manual child rendering needed here - that caused "View already has a parent" errors
|
||||||
{
|
|
||||||
|
|
||||||
// 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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return skiaView;
|
return skiaView;
|
||||||
}
|
}
|
||||||
catch (Exception)
|
catch (Exception)
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@
|
||||||
|
|
||||||
<!-- NuGet Package Properties -->
|
<!-- NuGet Package Properties -->
|
||||||
<PackageId>OpenMaui.Controls.Linux</PackageId>
|
<PackageId>OpenMaui.Controls.Linux</PackageId>
|
||||||
<Version>1.0.0-preview.1</Version>
|
<Version>1.0.0-preview.4</Version>
|
||||||
<Authors>MarketAlly LLC, David H. Friedel Jr.</Authors>
|
<Authors>MarketAlly LLC, David H. Friedel Jr.</Authors>
|
||||||
<Company>MarketAlly LLC</Company>
|
<Company>MarketAlly LLC</Company>
|
||||||
<Product>OpenMaui Linux Controls</Product>
|
<Product>OpenMaui Linux Controls</Product>
|
||||||
|
|
@ -23,7 +23,7 @@
|
||||||
<RepositoryUrl>https://git.marketally.com/open-maui/maui-linux.git</RepositoryUrl>
|
<RepositoryUrl>https://git.marketally.com/open-maui/maui-linux.git</RepositoryUrl>
|
||||||
<RepositoryType>git</RepositoryType>
|
<RepositoryType>git</RepositoryType>
|
||||||
<PackageTags>maui;linux;desktop;skia;gui;cross-platform;dotnet;x11;wayland;openmaui</PackageTags>
|
<PackageTags>maui;linux;desktop;skia;gui;cross-platform;dotnet;x11;wayland;openmaui</PackageTags>
|
||||||
<PackageReleaseNotes>Initial preview release with 35+ controls and full platform services.</PackageReleaseNotes>
|
<PackageReleaseNotes>Preview 4: Fixed handler rendering for layouts, text wrapping, and scrollbar measurement issues.</PackageReleaseNotes>
|
||||||
<PackageReadmeFile>README.md</PackageReadmeFile>
|
<PackageReadmeFile>README.md</PackageReadmeFile>
|
||||||
<PackageIcon>icon.png</PackageIcon>
|
<PackageIcon>icon.png</PackageIcon>
|
||||||
<GeneratePackageOnBuild>false</GeneratePackageOnBuild>
|
<GeneratePackageOnBuild>false</GeneratePackageOnBuild>
|
||||||
|
|
|
||||||
|
|
@ -429,9 +429,10 @@ public class SkiaLabel : SkiaView
|
||||||
bounds.Bottom - Padding.Bottom);
|
bounds.Bottom - Padding.Bottom);
|
||||||
|
|
||||||
// Handle single line vs multiline
|
// Handle single line vs multiline
|
||||||
// Use DrawSingleLine for normal labels (MaxLines <= 1 or unlimited) without newlines
|
// Use DrawMultiLineWithWrapping when: MaxLines > 1, text has newlines, OR WordWrap is enabled
|
||||||
// Use DrawMultiLineWithWrapping only when MaxLines > 1 (word wrap needed) or text has newlines
|
bool needsMultiLine = MaxLines > 1 || Text.Contains('\n') ||
|
||||||
bool needsMultiLine = MaxLines > 1 || Text.Contains('\n');
|
LineBreakMode == LineBreakMode.WordWrap ||
|
||||||
|
LineBreakMode == LineBreakMode.CharacterWrap;
|
||||||
if (needsMultiLine)
|
if (needsMultiLine)
|
||||||
{
|
{
|
||||||
DrawMultiLineWithWrapping(canvas, paint, font, contentBounds);
|
DrawMultiLineWithWrapping(canvas, paint, font, contentBounds);
|
||||||
|
|
@ -771,8 +772,10 @@ public class SkiaLabel : SkiaView
|
||||||
using var font = new SKFont(typeface, FontSize);
|
using var font = new SKFont(typeface, FontSize);
|
||||||
using var paint = new SKPaint(font);
|
using var paint = new SKPaint(font);
|
||||||
|
|
||||||
// Use same logic as OnDraw: multiline only when MaxLines > 1 or text has newlines
|
// Use multiline when: MaxLines > 1, text has newlines, OR WordWrap is enabled
|
||||||
bool needsMultiLine = MaxLines > 1 || Text.Contains('\n');
|
bool needsMultiLine = MaxLines > 1 || Text.Contains('\n') ||
|
||||||
|
LineBreakMode == LineBreakMode.WordWrap ||
|
||||||
|
LineBreakMode == LineBreakMode.CharacterWrap;
|
||||||
if (!needsMultiLine)
|
if (!needsMultiLine)
|
||||||
{
|
{
|
||||||
var textBounds = new SKRect();
|
var textBounds = new SKRect();
|
||||||
|
|
|
||||||
|
|
@ -268,8 +268,16 @@ public class SkiaScrollView : SkiaView
|
||||||
if (_content != null)
|
if (_content != null)
|
||||||
{
|
{
|
||||||
// Ensure content is measured and arranged
|
// Ensure content is measured and arranged
|
||||||
var availableSize = new SKSize(bounds.Width, float.PositiveInfinity);
|
// Account for vertical scrollbar width to prevent horizontal scrollbar from appearing
|
||||||
_content.Measure(availableSize);
|
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
|
// Apply content's margin
|
||||||
var margin = _content.Margin;
|
var margin = _content.Margin;
|
||||||
|
|
@ -669,12 +677,18 @@ public class SkiaScrollView : SkiaView
|
||||||
case ScrollOrientation.Both:
|
case ScrollOrientation.Both:
|
||||||
// For Both: first measure with viewport width to get responsive layout
|
// For Both: first measure with viewport width to get responsive layout
|
||||||
// Content can still exceed viewport if it has minimum width constraints
|
// 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;
|
contentWidth = float.IsInfinity(availableSize.Width) ? 800f : availableSize.Width;
|
||||||
|
if (VerticalScrollBarVisibility != ScrollBarVisibility.Never)
|
||||||
|
contentWidth -= ScrollBarWidth;
|
||||||
contentHeight = float.PositiveInfinity;
|
contentHeight = float.PositiveInfinity;
|
||||||
break;
|
break;
|
||||||
case ScrollOrientation.Vertical:
|
case ScrollOrientation.Vertical:
|
||||||
default:
|
default:
|
||||||
|
// Reserve space for vertical scrollbar to prevent horizontal scrollbar
|
||||||
contentWidth = float.IsInfinity(availableSize.Width) ? 800f : availableSize.Width;
|
contentWidth = float.IsInfinity(availableSize.Width) ? 800f : availableSize.Width;
|
||||||
|
if (VerticalScrollBarVisibility != ScrollBarVisibility.Never)
|
||||||
|
contentWidth -= ScrollBarWidth;
|
||||||
contentHeight = float.PositiveInfinity;
|
contentHeight = float.PositiveInfinity;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -287,14 +287,14 @@ public class SkiaShell : SkiaLayoutView
|
||||||
|
|
||||||
protected override SKSize MeasureOverride(SKSize availableSize)
|
protected override SKSize MeasureOverride(SKSize availableSize)
|
||||||
{
|
{
|
||||||
// Measure current content
|
// Measure current content with padding accounted for (consistent with ArrangeOverride)
|
||||||
if (_currentContent != null)
|
if (_currentContent != null)
|
||||||
{
|
{
|
||||||
float contentTop = NavBarIsVisible ? NavBarHeight : 0;
|
float contentTop = NavBarIsVisible ? NavBarHeight : 0;
|
||||||
float contentBottom = TabBarIsVisible ? TabBarHeight : 0;
|
float contentBottom = TabBarIsVisible ? TabBarHeight : 0;
|
||||||
var contentSize = new SKSize(
|
var contentSize = new SKSize(
|
||||||
availableSize.Width,
|
availableSize.Width - (float)Padding.Left - (float)Padding.Right,
|
||||||
availableSize.Height - contentTop - contentBottom);
|
availableSize.Height - contentTop - contentBottom - (float)Padding.Top - (float)Padding.Bottom);
|
||||||
_currentContent.Measure(contentSize);
|
_currentContent.Measure(contentSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue