Compare commits
No commits in common. "main" and "v1.0.0-preview.3" have entirely different histories.
main
...
v1.0.0-pre
|
|
@ -1,37 +0,0 @@
|
||||||
# OpenMaui Linux CI/CD Pipeline for Gitea
|
|
||||||
name: CI
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches: [main, develop]
|
|
||||||
pull_request:
|
|
||||||
branches: [main]
|
|
||||||
|
|
||||||
env:
|
|
||||||
DOTNET_NOLOGO: true
|
|
||||||
DOTNET_CLI_TELEMETRY_OPTOUT: true
|
|
||||||
DOTNET_ROOT: C:\dotnet
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
name: Build and Test
|
|
||||||
runs-on: windows
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Restore dependencies
|
|
||||||
run: C:\dotnet\dotnet.exe restore
|
|
||||||
|
|
||||||
- name: Build
|
|
||||||
run: C:\dotnet\dotnet.exe build --configuration Release --no-restore
|
|
||||||
|
|
||||||
- name: Run tests
|
|
||||||
run: C:\dotnet\dotnet.exe test --configuration Release --no-build --verbosity normal
|
|
||||||
continue-on-error: true
|
|
||||||
|
|
||||||
- name: Pack NuGet (preview)
|
|
||||||
run: C:\dotnet\dotnet.exe pack --configuration Release --no-build -o ./nupkg
|
|
||||||
|
|
||||||
- name: List NuGet packages
|
|
||||||
run: dir .\nupkg\
|
|
||||||
|
|
@ -1,46 +0,0 @@
|
||||||
# OpenMaui Linux Release - Publish to NuGet
|
|
||||||
name: Release to NuGet
|
|
||||||
|
|
||||||
on:
|
|
||||||
release:
|
|
||||||
types: [published]
|
|
||||||
push:
|
|
||||||
tags:
|
|
||||||
- 'v*'
|
|
||||||
|
|
||||||
env:
|
|
||||||
DOTNET_NOLOGO: true
|
|
||||||
DOTNET_CLI_TELEMETRY_OPTOUT: true
|
|
||||||
DOTNET_ROOT: C:\dotnet
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
release:
|
|
||||||
name: Build and Publish to NuGet
|
|
||||||
runs-on: windows
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Extract version from tag
|
|
||||||
id: version
|
|
||||||
shell: pwsh
|
|
||||||
run: |
|
|
||||||
$tag = "${{ github.ref_name }}"
|
|
||||||
$version = $tag -replace '^v', ''
|
|
||||||
echo "VERSION=$version" >> $env:GITHUB_OUTPUT
|
|
||||||
echo "Building version: $version"
|
|
||||||
|
|
||||||
- name: Restore dependencies
|
|
||||||
run: C:\dotnet\dotnet.exe restore
|
|
||||||
|
|
||||||
- name: Build
|
|
||||||
run: C:\dotnet\dotnet.exe build --configuration Release --no-restore
|
|
||||||
|
|
||||||
- name: Run tests
|
|
||||||
run: C:\dotnet\dotnet.exe test --configuration Release --no-build --verbosity normal
|
|
||||||
|
|
||||||
- name: Pack NuGet package
|
|
||||||
run: C:\dotnet\dotnet.exe pack --configuration Release --no-build -o ./nupkg /p:PackageVersion=${{ steps.version.outputs.VERSION }}
|
|
||||||
|
|
||||||
- name: Publish to NuGet.org
|
|
||||||
run: C:\dotnet\dotnet.exe nuget push ./nupkg/*.nupkg --api-key ${{ secrets.NUGET_API_KEY }} --source https://api.nuget.org/v3/index.json --skip-duplicate
|
|
||||||
|
|
@ -442,7 +442,6 @@ 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)
|
||||||
|
|
@ -451,8 +450,98 @@ public class LinuxViewRenderer
|
||||||
return CreateFallbackView(view);
|
return CreateFallbackView(view);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handlers manage their own children via ConnectHandler and property mappers
|
// Recursively render children for layout views
|
||||||
// No manual child rendering needed here - that caused "View already has a parent" errors
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return skiaView;
|
return skiaView;
|
||||||
}
|
}
|
||||||
catch (Exception)
|
catch (Exception)
|
||||||
|
|
|
||||||
|
|
@ -12,18 +12,18 @@
|
||||||
|
|
||||||
<!-- NuGet Package Properties -->
|
<!-- NuGet Package Properties -->
|
||||||
<PackageId>OpenMaui.Controls.Linux</PackageId>
|
<PackageId>OpenMaui.Controls.Linux</PackageId>
|
||||||
<Version>1.0.0-preview.4</Version>
|
<Version>1.0.0-preview.1</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>
|
||||||
<Description>Linux desktop support for .NET MAUI applications using SkiaSharp rendering. Supports X11 and Wayland display servers with 35+ controls, platform services, and accessibility support.</Description>
|
<Description>Linux desktop support for .NET MAUI applications using SkiaSharp rendering. Supports X11 and Wayland display servers with 35+ controls, platform services, and accessibility support.</Description>
|
||||||
<Copyright>Copyright 2025 MarketAlly LLC</Copyright>
|
<Copyright>Copyright 2025 MarketAlly LLC</Copyright>
|
||||||
<PackageLicenseExpression>MIT</PackageLicenseExpression>
|
<PackageLicenseExpression>MIT</PackageLicenseExpression>
|
||||||
<PackageProjectUrl>https://git.marketally.com/open-maui/maui-linux</PackageProjectUrl>
|
<PackageProjectUrl>https://github.com/open-maui/maui-linux</PackageProjectUrl>
|
||||||
<RepositoryUrl>https://git.marketally.com/open-maui/maui-linux.git</RepositoryUrl>
|
<RepositoryUrl>https://github.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>Preview 4: Fixed handler rendering for layouts, text wrapping, and scrollbar measurement issues.</PackageReleaseNotes>
|
<PackageReleaseNotes>Initial preview release with 35+ controls and full platform services.</PackageReleaseNotes>
|
||||||
<PackageReadmeFile>README.md</PackageReadmeFile>
|
<PackageReadmeFile>README.md</PackageReadmeFile>
|
||||||
<PackageIcon>icon.png</PackageIcon>
|
<PackageIcon>icon.png</PackageIcon>
|
||||||
<GeneratePackageOnBuild>false</GeneratePackageOnBuild>
|
<GeneratePackageOnBuild>false</GeneratePackageOnBuild>
|
||||||
|
|
|
||||||
13
README.md
13
README.md
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
A comprehensive Linux platform implementation for .NET MAUI using SkiaSharp rendering.
|
A comprehensive Linux platform implementation for .NET MAUI using SkiaSharp rendering.
|
||||||
|
|
||||||
|
[](https://github.com/open-maui/maui-linux/actions)
|
||||||
[](https://www.nuget.org/packages/OpenMaui.Controls.Linux)
|
[](https://www.nuget.org/packages/OpenMaui.Controls.Linux)
|
||||||
[](LICENSE)
|
[](LICENSE)
|
||||||
|
|
||||||
|
|
@ -135,12 +136,12 @@ sudo dnf install libX11-devel libXrandr-devel libXcursor-devel libXi-devel mesa-
|
||||||
|
|
||||||
## Sample Applications
|
## Sample Applications
|
||||||
|
|
||||||
Full sample applications are available in the [maui-linux-samples](https://git.marketally.com/open-maui/maui-linux-samples) repository:
|
Full sample applications are available in the [maui-linux-samples](https://github.com/open-maui/maui-linux-samples) repository:
|
||||||
|
|
||||||
| Sample | Description |
|
| Sample | Description |
|
||||||
|--------|-------------|
|
|--------|-------------|
|
||||||
| **[TodoApp](https://git.marketally.com/open-maui/maui-linux-samples/src/branch/main/TodoApp)** | Task manager with NavigationPage, XAML data binding, CollectionView |
|
| **[TodoApp](https://github.com/open-maui/maui-linux-samples/tree/main/TodoApp)** | Task manager with NavigationPage, XAML data binding, CollectionView |
|
||||||
| **[ShellDemo](https://git.marketally.com/open-maui/maui-linux-samples/src/branch/main/ShellDemo)** | Control showcase with Shell navigation and flyout menu |
|
| **[ShellDemo](https://github.com/open-maui/maui-linux-samples/tree/main/ShellDemo)** | Control showcase with Shell navigation and flyout menu |
|
||||||
|
|
||||||
## Quick Example
|
## Quick Example
|
||||||
|
|
||||||
|
|
@ -179,7 +180,7 @@ app.Run();
|
||||||
## Building from Source
|
## Building from Source
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git clone https://git.marketally.com/open-maui/maui-linux.git
|
git clone https://github.com/open-maui/maui-linux.git
|
||||||
cd maui-linux
|
cd maui-linux
|
||||||
dotnet build
|
dotnet build
|
||||||
dotnet test
|
dotnet test
|
||||||
|
|
@ -233,7 +234,3 @@ Copyright (c) 2025 MarketAlly LLC. Licensed under the MIT License - see the [LIC
|
||||||
- [SkiaSharp](https://github.com/mono/SkiaSharp) - 2D graphics library
|
- [SkiaSharp](https://github.com/mono/SkiaSharp) - 2D graphics library
|
||||||
- [.NET MAUI](https://github.com/dotnet/maui) - Cross-platform UI framework
|
- [.NET MAUI](https://github.com/dotnet/maui) - Cross-platform UI framework
|
||||||
- The .NET community
|
- The .NET community
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -429,10 +429,9 @@ public class SkiaLabel : SkiaView
|
||||||
bounds.Bottom - Padding.Bottom);
|
bounds.Bottom - Padding.Bottom);
|
||||||
|
|
||||||
// Handle single line vs multiline
|
// Handle single line vs multiline
|
||||||
// Use DrawMultiLineWithWrapping when: MaxLines > 1, text has newlines, OR WordWrap is enabled
|
// Use DrawSingleLine for normal labels (MaxLines <= 1 or unlimited) without newlines
|
||||||
bool needsMultiLine = MaxLines > 1 || Text.Contains('\n') ||
|
// Use DrawMultiLineWithWrapping only when MaxLines > 1 (word wrap needed) or text has newlines
|
||||||
LineBreakMode == LineBreakMode.WordWrap ||
|
bool needsMultiLine = MaxLines > 1 || Text.Contains('\n');
|
||||||
LineBreakMode == LineBreakMode.CharacterWrap;
|
|
||||||
if (needsMultiLine)
|
if (needsMultiLine)
|
||||||
{
|
{
|
||||||
DrawMultiLineWithWrapping(canvas, paint, font, contentBounds);
|
DrawMultiLineWithWrapping(canvas, paint, font, contentBounds);
|
||||||
|
|
@ -772,10 +771,8 @@ 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 multiline when: MaxLines > 1, text has newlines, OR WordWrap is enabled
|
// Use same logic as OnDraw: multiline only when MaxLines > 1 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)
|
||||||
{
|
{
|
||||||
var textBounds = new SKRect();
|
var textBounds = new SKRect();
|
||||||
|
|
|
||||||
|
|
@ -268,16 +268,8 @@ public class SkiaScrollView : SkiaView
|
||||||
if (_content != null)
|
if (_content != null)
|
||||||
{
|
{
|
||||||
// Ensure content is measured and arranged
|
// Ensure content is measured and arranged
|
||||||
// Account for vertical scrollbar width to prevent horizontal scrollbar from appearing
|
var availableSize = new SKSize(bounds.Width, float.PositiveInfinity);
|
||||||
var effectiveWidth = bounds.Width;
|
_content.Measure(availableSize);
|
||||||
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;
|
||||||
|
|
@ -677,18 +669,12 @@ 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 with padding accounted for (consistent with ArrangeOverride)
|
// Measure current content
|
||||||
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 - (float)Padding.Left - (float)Padding.Right,
|
availableSize.Width,
|
||||||
availableSize.Height - contentTop - contentBottom - (float)Padding.Top - (float)Padding.Bottom);
|
availableSize.Height - contentTop - contentBottom);
|
||||||
_currentContent.Measure(contentSize);
|
_currentContent.Measure(contentSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue