Add full XAML support for .NET MAUI compatibility

New features:
- MauiAppBuilderExtensions.UseOpenMauiLinux() for standard MAUI integration
- New template: openmaui-linux-xaml with full XAML support
- Standard MAUI XAML syntax (ContentPage, VerticalStackLayout, etc.)
- Resource dictionaries (Colors.xaml, Styles.xaml)
- Compiled XAML support via MauiXaml items

Templates now available:
- dotnet new openmaui-linux      (code-based UI)
- dotnet new openmaui-linux-xaml (XAML-based UI)

Usage:
    var builder = MauiApp.CreateBuilder();
    builder
        .UseMauiApp<App>()
        .UseOpenMauiLinux();  // Enable Linux with XAML
This commit is contained in:
logikonline 2025-12-19 05:17:50 -05:00
parent ae5c9ab738
commit 1d9338d823
15 changed files with 646 additions and 7 deletions

View File

@ -0,0 +1,193 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// Copyright (c) 2025 MarketAlly LLC
using Microsoft.Maui;
using Microsoft.Maui.Controls;
using Microsoft.Maui.Controls.Hosting;
using Microsoft.Maui.Hosting;
using OpenMaui.Platform.Linux.Handlers;
namespace OpenMaui.Platform.Linux.Hosting;
/// <summary>
/// Extension methods for configuring OpenMaui Linux platform in a MAUI application.
/// This enables full XAML support by registering Linux-specific handlers.
/// </summary>
public static class MauiAppBuilderExtensions
{
/// <summary>
/// Configures the application to use OpenMaui Linux platform with full XAML support.
/// </summary>
/// <param name="builder">The MAUI app builder.</param>
/// <returns>The configured MAUI app builder.</returns>
/// <example>
/// <code>
/// var builder = MauiApp.CreateBuilder();
/// builder
/// .UseMauiApp&lt;App&gt;()
/// .UseOpenMauiLinux(); // Enable Linux support with XAML
/// </code>
/// </example>
public static MauiAppBuilder UseOpenMauiLinux(this MauiAppBuilder builder)
{
builder.ConfigureMauiHandlers(handlers =>
{
// Register all Linux platform handlers
// These map MAUI virtual views to our Skia platform views
// Basic Controls
handlers.AddHandler<Button, ButtonHandler>();
handlers.AddHandler<Label, LabelHandler>();
handlers.AddHandler<Entry, EntryHandler>();
handlers.AddHandler<Editor, EditorHandler>();
handlers.AddHandler<CheckBox, CheckBoxHandler>();
handlers.AddHandler<Switch, SwitchHandler>();
handlers.AddHandler<RadioButton, RadioButtonHandler>();
// Selection Controls
handlers.AddHandler<Slider, SliderHandler>();
handlers.AddHandler<Stepper, StepperHandler>();
handlers.AddHandler<Picker, PickerHandler>();
handlers.AddHandler<DatePicker, DatePickerHandler>();
handlers.AddHandler<TimePicker, TimePickerHandler>();
// Display Controls
handlers.AddHandler<Image, ImageHandler>();
handlers.AddHandler<ImageButton, ImageButtonHandler>();
handlers.AddHandler<ActivityIndicator, ActivityIndicatorHandler>();
handlers.AddHandler<ProgressBar, ProgressBarHandler>();
// Layout Controls
handlers.AddHandler<ScrollView, ScrollViewHandler>();
handlers.AddHandler<Border, BorderHandler>();
handlers.AddHandler<ContentView, ContentViewHandler>();
handlers.AddHandler<Frame, FrameHandler>();
// Collection Controls
handlers.AddHandler<CollectionView, CollectionViewHandler>();
handlers.AddHandler<CarouselView, CarouselViewHandler>();
handlers.AddHandler<IndicatorView, IndicatorViewHandler>();
handlers.AddHandler<RefreshView, RefreshViewHandler>();
handlers.AddHandler<SwipeView, SwipeViewHandler>();
// Navigation Controls
handlers.AddHandler<NavigationPage, NavigationPageHandler>();
handlers.AddHandler<TabbedPage, TabbedPageHandler>();
handlers.AddHandler<FlyoutPage, FlyoutPageHandler>();
handlers.AddHandler<Shell, ShellHandler>();
// Page Controls
handlers.AddHandler<Page, PageHandler>();
handlers.AddHandler<ContentPage, PageHandler>();
// Graphics
handlers.AddHandler<GraphicsView, GraphicsViewHandler>();
// Search
handlers.AddHandler<SearchBar, SearchBarHandler>();
// Window
handlers.AddHandler<Window, WindowHandler>();
});
// Register Linux-specific services
builder.Services.AddSingleton<ILinuxPlatformServices, LinuxPlatformServices>();
return builder;
}
/// <summary>
/// Configures the application to use OpenMaui Linux with custom handler configuration.
/// </summary>
/// <param name="builder">The MAUI app builder.</param>
/// <param name="configureHandlers">Action to configure additional handlers.</param>
/// <returns>The configured MAUI app builder.</returns>
public static MauiAppBuilder UseOpenMauiLinux(
this MauiAppBuilder builder,
Action<IMauiHandlersCollection>? configureHandlers)
{
builder.UseOpenMauiLinux();
if (configureHandlers != null)
{
builder.ConfigureMauiHandlers(configureHandlers);
}
return builder;
}
}
/// <summary>
/// Interface for Linux platform services.
/// </summary>
public interface ILinuxPlatformServices
{
/// <summary>
/// Gets the display server type (X11 or Wayland).
/// </summary>
DisplayServerType DisplayServer { get; }
/// <summary>
/// Gets the current DPI scale factor.
/// </summary>
float ScaleFactor { get; }
/// <summary>
/// Gets whether high contrast mode is enabled.
/// </summary>
bool IsHighContrastEnabled { get; }
}
/// <summary>
/// Display server types supported by OpenMaui.
/// </summary>
public enum DisplayServerType
{
/// <summary>X11 display server.</summary>
X11,
/// <summary>Wayland display server.</summary>
Wayland,
/// <summary>Auto-detected display server.</summary>
Auto
}
/// <summary>
/// Implementation of Linux platform services.
/// </summary>
internal class LinuxPlatformServices : ILinuxPlatformServices
{
public DisplayServerType DisplayServer => DetectDisplayServer();
public float ScaleFactor => DetectScaleFactor();
public bool IsHighContrastEnabled => DetectHighContrast();
private static DisplayServerType DetectDisplayServer()
{
var waylandDisplay = Environment.GetEnvironmentVariable("WAYLAND_DISPLAY");
if (!string.IsNullOrEmpty(waylandDisplay))
return DisplayServerType.Wayland;
var display = Environment.GetEnvironmentVariable("DISPLAY");
if (!string.IsNullOrEmpty(display))
return DisplayServerType.X11;
return DisplayServerType.Auto;
}
private static float DetectScaleFactor()
{
// Try GDK_SCALE first
var gdkScale = Environment.GetEnvironmentVariable("GDK_SCALE");
if (float.TryParse(gdkScale, out var scale))
return scale;
// Default to 1.0
return 1.0f;
}
private static bool DetectHighContrast()
{
var highContrast = Environment.GetEnvironmentVariable("GTK_THEME");
return highContrast?.Contains("HighContrast", StringComparison.OrdinalIgnoreCase) ?? false;
}
}

View File

@ -27,14 +27,14 @@ This project brings .NET MAUI to Linux desktops with native X11/Wayland support,
### Installation
```bash
# Install the template
# Install the templates
dotnet new install OpenMaui.Linux.Templates
# Create a new project
dotnet new openmaui-linux -n MyApp
cd MyApp
# Create a new project (choose one):
dotnet new openmaui-linux -n MyApp # Code-based UI
dotnet new openmaui-linux-xaml -n MyApp # XAML-based UI (recommended)
# Run
cd MyApp
dotnet run
```
@ -44,6 +44,32 @@ dotnet run
dotnet add package OpenMaui.Controls.Linux --prerelease
```
## XAML Support
OpenMaui fully supports standard .NET MAUI XAML syntax. Use the familiar XAML workflow:
```xml
<!-- MainPage.xaml -->
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="MyApp.MainPage">
<VerticalStackLayout>
<Label Text="Hello, OpenMaui!" FontSize="32" />
<Button Text="Click me" Clicked="OnButtonClicked" />
<Entry Placeholder="Enter text..." />
<Slider Minimum="0" Maximum="100" />
</VerticalStackLayout>
</ContentPage>
```
```csharp
// MauiProgram.cs
var builder = MauiApp.CreateBuilder();
builder
.UseMauiApp<App>()
.UseOpenMauiLinux(); // Enable Linux with XAML support
```
## Supported Controls
| Category | Controls |

View File

@ -7,8 +7,12 @@
<Title>OpenMaui Linux Project Templates</Title>
<Authors>MarketAlly LLC, David H. Friedel Jr.</Authors>
<Company>MarketAlly LLC</Company>
<Description>Project templates for building .NET MAUI applications on Linux desktop using OpenMaui.</Description>
<PackageTags>dotnet-new;templates;maui;linux;desktop;openmaui</PackageTags>
<Description>Project templates for building .NET MAUI applications on Linux desktop using OpenMaui.
Templates included:
- openmaui-linux: Basic Linux app with code-based UI
- openmaui-linux-xaml: Full XAML support with standard MAUI syntax</Description>
<PackageTags>dotnet-new;templates;maui;linux;desktop;openmaui;xaml</PackageTags>
<PackageProjectUrl>https://github.com/open-maui/maui-linux</PackageProjectUrl>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<Copyright>Copyright 2025 MarketAlly LLC</Copyright>
@ -23,7 +27,10 @@
</PropertyGroup>
<ItemGroup>
<!-- Code-based template -->
<Content Include="openmaui-linux-app\**\*" Exclude="openmaui-linux-app\**\bin\**;openmaui-linux-app\**\obj\**" />
<!-- XAML-based template -->
<Content Include="openmaui-linux-xaml-app\**\*" Exclude="openmaui-linux-xaml-app\**\bin\**;openmaui-linux-xaml-app\**\obj\**" />
<Compile Remove="**\*" />
</ItemGroup>

View File

@ -0,0 +1,41 @@
{
"$schema": "http://json.schemastore.org/template",
"author": "MarketAlly LLC",
"classifications": ["MAUI", "Linux", "Desktop", "App", "OpenMaui", "XAML"],
"identity": "OpenMaui.Linux.XamlApp",
"name": "OpenMaui Linux XAML Application",
"shortName": "openmaui-linux-xaml",
"description": "A .NET MAUI application for Linux using standard XAML syntax with OpenMaui platform support.",
"tags": {
"language": "C#",
"type": "project"
},
"sourceName": "OpenMauiXamlApp",
"preferNameDirectory": true,
"symbols": {
"Framework": {
"type": "parameter",
"description": "The target framework for the project.",
"datatype": "choice",
"choices": [
{
"choice": "net9.0",
"description": "Target .NET 9.0"
}
],
"defaultValue": "net9.0",
"replaces": "net9.0"
}
},
"primaryOutputs": [
{ "path": "OpenMauiXamlApp.csproj" }
],
"postActions": [
{
"description": "Restore NuGet packages required by this project.",
"manualInstructions": [{ "text": "Run 'dotnet restore'" }],
"actionId": "210D431B-A78B-4D2F-B762-4ED3E3EA9025",
"continueOnError": true
}
]
}

View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8" ?>
<Application xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="OpenMauiXamlApp.App">
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Resources/Styles/Colors.xaml" />
<ResourceDictionary Source="Resources/Styles/Styles.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
</Application>

View File

@ -0,0 +1,10 @@
namespace OpenMauiXamlApp;
public partial class App : Application
{
public App()
{
InitializeComponent();
MainPage = new MainPage();
}
}

View File

@ -0,0 +1,82 @@
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="OpenMauiXamlApp.MainPage"
Title="Home">
<ScrollView>
<VerticalStackLayout
Padding="30,0"
Spacing="25"
VerticalOptions="Center">
<Image
Source="dotnet_bot.png"
HeightRequest="185"
Aspect="AspectFit"
SemanticProperties.Description="dot net bot waving hi to you!" />
<Label
Text="Hello, OpenMaui!"
Style="{StaticResource Headline}"
SemanticProperties.HeadingLevel="Level1"
HorizontalOptions="Center" />
<Label
Text="Welcome to .NET MAUI on Linux"
Style="{StaticResource SubHeadline}"
SemanticProperties.Description="Welcome to dot net MAUI on Linux"
HorizontalOptions="Center" />
<Button
x:Name="CounterBtn"
Text="Click me"
SemanticProperties.Hint="Counts the number of times you click"
Clicked="OnCounterClicked"
HorizontalOptions="Fill" />
<HorizontalStackLayout
Spacing="10"
HorizontalOptions="Center">
<CheckBox
x:Name="AgreeCheckBox"
IsChecked="False" />
<Label
Text="I agree to the terms"
VerticalOptions="Center" />
</HorizontalStackLayout>
<Entry
x:Name="NameEntry"
Placeholder="Enter your name"
HorizontalOptions="Fill" />
<Slider
x:Name="VolumeSlider"
Minimum="0"
Maximum="100"
Value="50"
HorizontalOptions="Fill" />
<Label
x:Name="VolumeLabel"
Text="Volume: 50"
HorizontalOptions="Center" />
<Switch
x:Name="DarkModeSwitch"
IsToggled="False"
HorizontalOptions="Center" />
<ProgressBar
x:Name="LoadingProgress"
Progress="0.5"
ProgressColor="{StaticResource Primary}"
HorizontalOptions="Fill" />
</VerticalStackLayout>
</ScrollView>
</ContentPage>

View File

@ -0,0 +1,30 @@
namespace OpenMauiXamlApp;
public partial class MainPage : ContentPage
{
private int _count = 0;
public MainPage()
{
InitializeComponent();
// Wire up slider value changed
VolumeSlider.ValueChanged += OnVolumeChanged;
}
private void OnCounterClicked(object sender, EventArgs e)
{
_count++;
CounterBtn.Text = _count == 1
? $"Clicked {_count} time"
: $"Clicked {_count} times";
SemanticScreenReader.Announce(CounterBtn.Text);
}
private void OnVolumeChanged(object? sender, ValueChangedEventArgs e)
{
VolumeLabel.Text = $"Volume: {e.NewValue:F0}";
}
}

View File

@ -0,0 +1,24 @@
using Microsoft.Maui.Controls.Hosting;
using Microsoft.Maui.Hosting;
using OpenMaui.Platform.Linux.Hosting;
namespace OpenMauiXamlApp;
public static class MauiProgram
{
public static MauiApp CreateMauiApp()
{
var builder = MauiApp.CreateBuilder();
builder
.UseMauiApp<App>()
.UseOpenMauiLinux() // Enable Linux platform with full XAML support
.ConfigureFonts(fonts =>
{
fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold");
});
return builder.Build();
}
}

View File

@ -0,0 +1,38 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<OutputType>Exe</OutputType>
<RootNamespace>OpenMauiXamlApp</RootNamespace>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<!-- Enable XAML compilation -->
<EnableDefaultXamlItems>true</EnableDefaultXamlItems>
<!-- Linux Runtime -->
<RuntimeIdentifiers>linux-x64;linux-arm64</RuntimeIdentifiers>
</PropertyGroup>
<ItemGroup>
<!-- OpenMaui Linux Platform -->
<PackageReference Include="OpenMaui.Controls.Linux" Version="1.0.0-preview.*" />
<!-- Core MAUI packages (includes XAML support) -->
<PackageReference Include="Microsoft.Maui.Controls" Version="9.0.*" />
<PackageReference Include="Microsoft.Maui.Controls.Compatibility" Version="9.0.*" />
</ItemGroup>
<!-- XAML Files -->
<ItemGroup>
<MauiXaml Update="**/*.xaml" />
<MauiXaml Update="App.xaml" />
<MauiXaml Update="MainPage.xaml" />
</ItemGroup>
<!-- Embedded Resources -->
<ItemGroup>
<EmbeddedResource Include="Resources\**\*" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,16 @@
using OpenMaui.Platform.Linux;
namespace OpenMauiXamlApp;
public class Program
{
public static void Main(string[] args)
{
// Create the MAUI app using standard MAUI bootstrapping
var app = MauiProgram.CreateMauiApp();
// Run with Linux platform
// This connects MAUI's virtual views to our Skia platform views
LinuxApplication.Run(app);
}
}

View File

@ -0,0 +1,4 @@
# Add your fonts here
# Recommended:
# - OpenSans-Regular.ttf
# - OpenSans-Semibold.ttf

View File

@ -0,0 +1,2 @@
# Add your images here
# Required: dotnet_bot.png (from MAUI template)

View File

@ -0,0 +1,49 @@
<?xml version="1.0" encoding="UTF-8" ?>
<?xaml-comp compile="true" ?>
<ResourceDictionary
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml">
<!-- Primary Colors -->
<Color x:Key="Primary">#512BD4</Color>
<Color x:Key="PrimaryDark">#3B1F9E</Color>
<Color x:Key="PrimaryLight">#7B5EDF</Color>
<!-- Secondary Colors -->
<Color x:Key="Secondary">#DFD8F7</Color>
<Color x:Key="SecondaryDark">#9880E5</Color>
<!-- Accent Colors -->
<Color x:Key="Accent">#FF6B35</Color>
<!-- Neutral Colors -->
<Color x:Key="White">White</Color>
<Color x:Key="Black">Black</Color>
<Color x:Key="Gray100">#E1E1E1</Color>
<Color x:Key="Gray200">#C8C8C8</Color>
<Color x:Key="Gray300">#ACACAC</Color>
<Color x:Key="Gray400">#919191</Color>
<Color x:Key="Gray500">#6E6E6E</Color>
<Color x:Key="Gray600">#404040</Color>
<Color x:Key="Gray900">#212121</Color>
<Color x:Key="Gray950">#141414</Color>
<!-- Semantic Colors -->
<Color x:Key="Success">#4CAF50</Color>
<Color x:Key="Warning">#FF9800</Color>
<Color x:Key="Error">#F44336</Color>
<Color x:Key="Info">#2196F3</Color>
<!-- Light Theme -->
<Color x:Key="LightBackground">White</Color>
<Color x:Key="LightSurface">#F5F5F5</Color>
<Color x:Key="LightOnBackground">#212121</Color>
<Color x:Key="LightOnSurface">#424242</Color>
<!-- Dark Theme -->
<Color x:Key="DarkBackground">#121212</Color>
<Color x:Key="DarkSurface">#1E1E1E</Color>
<Color x:Key="DarkOnBackground">#FFFFFF</Color>
<Color x:Key="DarkOnSurface">#E0E0E0</Color>
</ResourceDictionary>

View File

@ -0,0 +1,104 @@
<?xml version="1.0" encoding="UTF-8" ?>
<?xaml-comp compile="true" ?>
<ResourceDictionary
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml">
<!-- Headline Style -->
<Style x:Key="Headline" TargetType="Label">
<Setter Property="TextColor" Value="{StaticResource Primary}" />
<Setter Property="FontSize" Value="32" />
<Setter Property="FontAttributes" Value="Bold" />
<Setter Property="HorizontalTextAlignment" Value="Center" />
</Style>
<!-- SubHeadline Style -->
<Style x:Key="SubHeadline" TargetType="Label">
<Setter Property="TextColor" Value="{StaticResource Gray500}" />
<Setter Property="FontSize" Value="18" />
<Setter Property="HorizontalTextAlignment" Value="Center" />
</Style>
<!-- Default Button Style -->
<Style TargetType="Button">
<Setter Property="TextColor" Value="White" />
<Setter Property="BackgroundColor" Value="{StaticResource Primary}" />
<Setter Property="FontSize" Value="14" />
<Setter Property="FontAttributes" Value="Bold" />
<Setter Property="CornerRadius" Value="8" />
<Setter Property="Padding" Value="14,10" />
<Setter Property="MinimumHeightRequest" Value="44" />
<Setter Property="MinimumWidthRequest" Value="44" />
<Setter Property="VisualStateManager.VisualStateGroups">
<VisualStateGroupList>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal" />
<VisualState x:Name="Disabled">
<VisualState.Setters>
<Setter Property="TextColor" Value="{StaticResource Gray300}" />
<Setter Property="BackgroundColor" Value="{StaticResource Gray100}" />
</VisualState.Setters>
</VisualState>
<VisualState x:Name="PointerOver">
<VisualState.Setters>
<Setter Property="BackgroundColor" Value="{StaticResource PrimaryDark}" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateGroupList>
</Setter>
</Style>
<!-- Default Entry Style -->
<Style TargetType="Entry">
<Setter Property="TextColor" Value="{StaticResource Gray900}" />
<Setter Property="PlaceholderColor" Value="{StaticResource Gray400}" />
<Setter Property="BackgroundColor" Value="Transparent" />
<Setter Property="FontSize" Value="14" />
<Setter Property="MinimumHeightRequest" Value="44" />
</Style>
<!-- Default Label Style -->
<Style TargetType="Label">
<Setter Property="TextColor" Value="{StaticResource Gray900}" />
<Setter Property="FontSize" Value="14" />
</Style>
<!-- Default CheckBox Style -->
<Style TargetType="CheckBox">
<Setter Property="Color" Value="{StaticResource Primary}" />
</Style>
<!-- Default Switch Style -->
<Style TargetType="Switch">
<Setter Property="OnColor" Value="{StaticResource Primary}" />
<Setter Property="ThumbColor" Value="White" />
</Style>
<!-- Default Slider Style -->
<Style TargetType="Slider">
<Setter Property="MinimumTrackColor" Value="{StaticResource Primary}" />
<Setter Property="MaximumTrackColor" Value="{StaticResource Gray200}" />
<Setter Property="ThumbColor" Value="{StaticResource Primary}" />
</Style>
<!-- Default ProgressBar Style -->
<Style TargetType="ProgressBar">
<Setter Property="ProgressColor" Value="{StaticResource Primary}" />
</Style>
<!-- Default ActivityIndicator Style -->
<Style TargetType="ActivityIndicator">
<Setter Property="Color" Value="{StaticResource Primary}" />
</Style>
<!-- Page Background -->
<Style TargetType="Page" ApplyToDerivedTypes="True">
<Setter Property="BackgroundColor" Value="{StaticResource LightBackground}" />
</Style>
<Style TargetType="ContentPage" ApplyToDerivedTypes="True">
<Setter Property="BackgroundColor" Value="{StaticResource LightBackground}" />
</Style>
</ResourceDictionary>