Preview 3: Complete control implementation with XAML data binding

Major milestone adding full control functionality:

Controls Enhanced:
- Entry/Editor: Full keyboard input, cursor navigation, selection, clipboard
- CollectionView: Data binding, selection highlighting, scrolling
- CheckBox/Switch/Slider: Interactive state management
- Picker/DatePicker/TimePicker: Dropdown selection with popup overlays
- ProgressBar/ActivityIndicator: Animated progress display
- Button: Press/release visual states
- Border/Frame: Rounded corners, stroke styling
- Label: Text wrapping, alignment, decorations
- Grid/StackLayout: Margin and padding support

Features Added:
- DisplayAlert dialogs with button actions
- NavigationPage with toolbar and back navigation
- Shell with flyout menu navigation
- XAML value converters for data binding
- Margin support in all layout containers
- Popup overlay system for pickers

New Samples:
- TodoApp: Full CRUD task manager with NavigationPage
- ShellDemo: Comprehensive control showcase

Removed:
- ControlGallery (replaced by ShellDemo)
- LinuxDemo (replaced by TodoApp)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
logikonline
2025-12-21 13:26:56 -05:00
parent f945d2a537
commit 1d55ac672a
142 changed files with 38925 additions and 4201 deletions

View File

@@ -1,20 +0,0 @@
<?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="ControlGallery.App">
<Application.Resources>
<ResourceDictionary>
<Color x:Key="Primary">#512BD4</Color>
<Color x:Key="PrimaryDark">#3B1F9E</Color>
<Color x:Key="Secondary">#DFD8F7</Color>
<Color x:Key="Tertiary">#2B0B98</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>
</ResourceDictionary>
</Application.Resources>
</Application>

View File

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

View File

@@ -1,39 +0,0 @@
<?xml version="1.0" encoding="UTF-8" ?>
<Shell xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:pages="clr-namespace:ControlGallery.Pages"
x:Class="ControlGallery.AppShell"
Title="OpenMaui Control Gallery">
<FlyoutItem Title="Home" Icon="home.png">
<ShellContent Title="Home" ContentTemplate="{DataTemplate pages:HomePage}" />
</FlyoutItem>
<FlyoutItem Title="Basic Controls" Icon="controls.png">
<ShellContent Title="Buttons" ContentTemplate="{DataTemplate pages:ButtonsPage}" />
<ShellContent Title="Labels" ContentTemplate="{DataTemplate pages:LabelsPage}" />
<ShellContent Title="Entry &amp; Editor" ContentTemplate="{DataTemplate pages:EntryPage}" />
</FlyoutItem>
<FlyoutItem Title="Selection Controls" Icon="selection.png">
<ShellContent Title="Pickers" ContentTemplate="{DataTemplate pages:PickersPage}" />
<ShellContent Title="Sliders" ContentTemplate="{DataTemplate pages:SlidersPage}" />
<ShellContent Title="Toggles" ContentTemplate="{DataTemplate pages:TogglesPage}" />
</FlyoutItem>
<FlyoutItem Title="Display Controls" Icon="display.png">
<ShellContent Title="Progress" ContentTemplate="{DataTemplate pages:ProgressPage}" />
<ShellContent Title="Images" ContentTemplate="{DataTemplate pages:ImagesPage}" />
</FlyoutItem>
<FlyoutItem Title="Collections" Icon="list.png">
<ShellContent Title="CollectionView" ContentTemplate="{DataTemplate pages:CollectionViewPage}" />
<ShellContent Title="CarouselView" ContentTemplate="{DataTemplate pages:CarouselViewPage}" />
</FlyoutItem>
<FlyoutItem Title="Gestures" Icon="gesture.png">
<ShellContent Title="SwipeView" ContentTemplate="{DataTemplate pages:SwipeViewPage}" />
<ShellContent Title="RefreshView" ContentTemplate="{DataTemplate pages:RefreshViewPage}" />
</FlyoutItem>
</Shell>

View File

@@ -1,9 +0,0 @@
namespace ControlGallery;
public partial class AppShell : Shell
{
public AppShell()
{
InitializeComponent();
}
}

View File

@@ -1,21 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<OutputType>Exe</OutputType>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<RootNamespace>ControlGallery</RootNamespace>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="OpenMaui.Controls.Linux" Version="1.0.0-preview.*" />
</ItemGroup>
<ItemGroup>
<MauiXaml Include="**/*.xaml" />
<MauiImage Include="Resources\Images\*" />
<MauiFont Include="Resources\Fonts\*" />
</ItemGroup>
</Project>

View File

@@ -1,22 +0,0 @@
using Microsoft.Maui.Hosting;
using OpenMaui.Platform.Linux.Hosting;
namespace ControlGallery;
public static class MauiProgram
{
public static MauiApp CreateMauiApp()
{
var builder = MauiApp.CreateBuilder();
builder
.UseMauiApp<App>()
.UseOpenMauiLinux()
.ConfigureFonts(fonts =>
{
fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold");
});
return builder.Build();
}
}

View File

@@ -1,73 +0,0 @@
<?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="ControlGallery.Pages.ButtonsPage"
Title="Buttons">
<ScrollView>
<VerticalStackLayout Spacing="20" Padding="20">
<Label Text="Button Controls" FontSize="24" FontAttributes="Bold" />
<!-- Basic Button -->
<Frame Padding="15" CornerRadius="8">
<VerticalStackLayout Spacing="10">
<Label Text="Basic Button" FontAttributes="Bold" />
<Button Text="Click Me" Clicked="OnButtonClicked" />
<Label x:Name="ButtonResultLabel" Text="Button not clicked yet" TextColor="{StaticResource Gray400}" />
</VerticalStackLayout>
</Frame>
<!-- Styled Buttons -->
<Frame Padding="15" CornerRadius="8">
<VerticalStackLayout Spacing="10">
<Label Text="Styled Buttons" FontAttributes="Bold" />
<HorizontalStackLayout Spacing="10">
<Button Text="Primary" BackgroundColor="{StaticResource Primary}" TextColor="White" />
<Button Text="Secondary" BackgroundColor="{StaticResource Secondary}" TextColor="{StaticResource Primary}" />
<Button Text="Tertiary" BackgroundColor="{StaticResource Tertiary}" TextColor="White" />
</HorizontalStackLayout>
</VerticalStackLayout>
</Frame>
<!-- Button States -->
<Frame Padding="15" CornerRadius="8">
<VerticalStackLayout Spacing="10">
<Label Text="Button States" FontAttributes="Bold" />
<HorizontalStackLayout Spacing="10">
<Button Text="Normal" />
<Button Text="Disabled" IsEnabled="False" />
</HorizontalStackLayout>
</VerticalStackLayout>
</Frame>
<!-- ImageButton -->
<Frame Padding="15" CornerRadius="8">
<VerticalStackLayout Spacing="10">
<Label Text="ImageButton" FontAttributes="Bold" />
<Label Text="ImageButton combines image and button functionality" TextColor="{StaticResource Gray500}" />
<ImageButton Source="dotnet_bot.png"
WidthRequest="100"
HeightRequest="100"
BackgroundColor="{StaticResource Gray100}"
CornerRadius="10"
Clicked="OnImageButtonClicked" />
</VerticalStackLayout>
</Frame>
<!-- Corner Radius -->
<Frame Padding="15" CornerRadius="8">
<VerticalStackLayout Spacing="10">
<Label Text="Corner Radius" FontAttributes="Bold" />
<HorizontalStackLayout Spacing="10">
<Button Text="Square" CornerRadius="0" />
<Button Text="Rounded" CornerRadius="8" />
<Button Text="Pill" CornerRadius="20" />
</HorizontalStackLayout>
</VerticalStackLayout>
</Frame>
</VerticalStackLayout>
</ScrollView>
</ContentPage>

View File

@@ -1,22 +0,0 @@
namespace ControlGallery.Pages;
public partial class ButtonsPage : ContentPage
{
private int _clickCount = 0;
public ButtonsPage()
{
InitializeComponent();
}
private void OnButtonClicked(object sender, EventArgs e)
{
_clickCount++;
ButtonResultLabel.Text = $"Button clicked {_clickCount} time(s)";
}
private async void OnImageButtonClicked(object sender, EventArgs e)
{
await DisplayAlert("ImageButton", "You clicked the ImageButton!", "OK");
}
}

View File

@@ -1,58 +0,0 @@
<?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="ControlGallery.Pages.CarouselViewPage"
Title="CarouselView">
<VerticalStackLayout Spacing="20" Padding="20">
<Label Text="CarouselView" FontSize="24" FontAttributes="Bold" />
<Label Text="Swipe left or right to navigate" TextColor="{StaticResource Gray500}" />
<CarouselView x:Name="Carousel"
HeightRequest="300"
Loop="True"
CurrentItemChanged="OnCurrentItemChanged">
<CarouselView.ItemTemplate>
<DataTemplate>
<Frame Margin="20" Padding="0" CornerRadius="20" HasShadow="True">
<Grid BackgroundColor="{Binding Color}">
<VerticalStackLayout VerticalOptions="Center" HorizontalOptions="Center" Spacing="10">
<Label Text="{Binding Icon}"
FontSize="64"
HorizontalOptions="Center" />
<Label Text="{Binding Title}"
FontSize="28"
FontAttributes="Bold"
TextColor="White"
HorizontalOptions="Center" />
<Label Text="{Binding Description}"
FontSize="16"
TextColor="White"
Opacity="0.8"
HorizontalOptions="Center" />
</VerticalStackLayout>
</Grid>
</Frame>
</DataTemplate>
</CarouselView.ItemTemplate>
</CarouselView>
<IndicatorView x:Name="CarouselIndicator"
HorizontalOptions="Center"
IndicatorColor="{StaticResource Gray300}"
SelectedIndicatorColor="{StaticResource Primary}" />
<Label x:Name="CurrentItemLabel"
Text="Slide 1 of 5"
HorizontalOptions="Center"
TextColor="{StaticResource Gray500}" />
<HorizontalStackLayout HorizontalOptions="Center" Spacing="20">
<Button Text="Previous" Clicked="OnPreviousClicked" />
<Button Text="Next" Clicked="OnNextClicked" />
</HorizontalStackLayout>
</VerticalStackLayout>
</ContentPage>

View File

@@ -1,70 +0,0 @@
namespace ControlGallery.Pages;
public partial class CarouselViewPage : ContentPage
{
private readonly List<CarouselItem> _items;
public CarouselViewPage()
{
InitializeComponent();
_items = new List<CarouselItem>
{
new() { Title = "Welcome", Description = "Get started with OpenMaui", Icon = "👋", Color = Color.FromArgb("#512BD4") },
new() { Title = "Controls", Description = "35+ beautiful controls", Icon = "🎨", Color = Color.FromArgb("#2196F3") },
new() { Title = "Native", Description = "X11 & Wayland support", Icon = "🐧", Color = Color.FromArgb("#4CAF50") },
new() { Title = "Fast", Description = "Hardware accelerated", Icon = "⚡", Color = Color.FromArgb("#FF9800") },
new() { Title = "Accessible", Description = "Screen reader support", Icon = "♿", Color = Color.FromArgb("#9C27B0") },
};
Carousel.ItemsSource = _items;
Carousel.IndicatorView = CarouselIndicator;
UpdateCurrentItemLabel();
}
private void OnCurrentItemChanged(object sender, CurrentItemChangedEventArgs e)
{
UpdateCurrentItemLabel();
}
private void UpdateCurrentItemLabel()
{
var index = _items.IndexOf(Carousel.CurrentItem as CarouselItem);
CurrentItemLabel.Text = $"Slide {index + 1} of {_items.Count}";
}
private void OnPreviousClicked(object sender, EventArgs e)
{
var index = _items.IndexOf(Carousel.CurrentItem as CarouselItem);
if (index > 0)
{
Carousel.ScrollTo(index - 1);
}
else
{
Carousel.ScrollTo(_items.Count - 1);
}
}
private void OnNextClicked(object sender, EventArgs e)
{
var index = _items.IndexOf(Carousel.CurrentItem as CarouselItem);
if (index < _items.Count - 1)
{
Carousel.ScrollTo(index + 1);
}
else
{
Carousel.ScrollTo(0);
}
}
}
public class CarouselItem
{
public string Title { get; set; } = string.Empty;
public string Description { get; set; } = string.Empty;
public string Icon { get; set; } = string.Empty;
public Color Color { get; set; } = Colors.Gray;
}

View File

@@ -1,47 +0,0 @@
<?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="ControlGallery.Pages.CollectionViewPage"
Title="CollectionView">
<Grid RowDefinitions="Auto,*">
<VerticalStackLayout Padding="20" Spacing="10">
<Label Text="CollectionView" FontSize="24" FontAttributes="Bold" />
<Label Text="Displaying a list of items with custom templates" TextColor="{StaticResource Gray500}" />
</VerticalStackLayout>
<CollectionView Grid.Row="1" x:Name="ItemsCollection" SelectionMode="Single" SelectionChanged="OnSelectionChanged">
<CollectionView.ItemTemplate>
<DataTemplate>
<Frame Margin="10,5" Padding="15" CornerRadius="8">
<Grid ColumnDefinitions="50,*,Auto">
<BoxView WidthRequest="40"
HeightRequest="40"
Color="{Binding Color}"
CornerRadius="20" />
<VerticalStackLayout Grid.Column="1" Margin="15,0" VerticalOptions="Center">
<Label Text="{Binding Title}" FontAttributes="Bold" />
<Label Text="{Binding Description}" TextColor="{StaticResource Gray500}" FontSize="12" />
</VerticalStackLayout>
<Label Grid.Column="2"
Text="{Binding Price, StringFormat='{0:C}'}"
FontAttributes="Bold"
VerticalOptions="Center" />
</Grid>
</Frame>
</DataTemplate>
</CollectionView.ItemTemplate>
<CollectionView.EmptyView>
<VerticalStackLayout HorizontalOptions="Center" VerticalOptions="Center">
<Label Text="No items found" FontSize="18" TextColor="{StaticResource Gray400}" />
</VerticalStackLayout>
</CollectionView.EmptyView>
</CollectionView>
</Grid>
</ContentPage>

View File

@@ -1,40 +0,0 @@
namespace ControlGallery.Pages;
public partial class CollectionViewPage : ContentPage
{
public CollectionViewPage()
{
InitializeComponent();
var items = new List<ItemModel>
{
new() { Title = "Product A", Description = "High quality item", Price = 29.99m, Color = Colors.Purple },
new() { Title = "Product B", Description = "Best seller", Price = 49.99m, Color = Colors.Blue },
new() { Title = "Product C", Description = "New arrival", Price = 19.99m, Color = Colors.Green },
new() { Title = "Product D", Description = "Limited edition", Price = 99.99m, Color = Colors.Orange },
new() { Title = "Product E", Description = "Customer favorite", Price = 39.99m, Color = Colors.Red },
new() { Title = "Product F", Description = "Eco-friendly", Price = 24.99m, Color = Colors.Teal },
new() { Title = "Product G", Description = "Premium quality", Price = 79.99m, Color = Colors.Indigo },
new() { Title = "Product H", Description = "Budget friendly", Price = 9.99m, Color = Colors.Pink },
};
ItemsCollection.ItemsSource = items;
}
private async void OnSelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (e.CurrentSelection.FirstOrDefault() is ItemModel item)
{
await DisplayAlert("Selected", $"You selected {item.Title}", "OK");
((CollectionView)sender).SelectedItem = null;
}
}
}
public class ItemModel
{
public string Title { get; set; } = string.Empty;
public string Description { get; set; } = string.Empty;
public decimal Price { get; set; }
public Color Color { get; set; } = Colors.Gray;
}

View File

@@ -1,77 +0,0 @@
<?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="ControlGallery.Pages.EntryPage"
Title="Entry &amp; Editor">
<ScrollView>
<VerticalStackLayout Spacing="20" Padding="20">
<Label Text="Text Input Controls" FontSize="24" FontAttributes="Bold" />
<!-- Basic Entry -->
<Frame Padding="15" CornerRadius="8">
<VerticalStackLayout Spacing="10">
<Label Text="Basic Entry" FontAttributes="Bold" />
<Entry Placeholder="Enter text here..." />
</VerticalStackLayout>
</Frame>
<!-- Entry with Binding -->
<Frame Padding="15" CornerRadius="8">
<VerticalStackLayout Spacing="10">
<Label Text="Two-Way Binding" FontAttributes="Bold" />
<Entry x:Name="BoundEntry" Placeholder="Type something..." TextChanged="OnEntryTextChanged" />
<Label x:Name="BoundLabel" Text="You typed: " TextColor="{StaticResource Gray500}" />
</VerticalStackLayout>
</Frame>
<!-- Password Entry -->
<Frame Padding="15" CornerRadius="8">
<VerticalStackLayout Spacing="10">
<Label Text="Password Entry" FontAttributes="Bold" />
<Entry Placeholder="Enter password..." IsPassword="True" />
</VerticalStackLayout>
</Frame>
<!-- Keyboard Types -->
<Frame Padding="15" CornerRadius="8">
<VerticalStackLayout Spacing="10">
<Label Text="Keyboard Types" FontAttributes="Bold" />
<Entry Placeholder="Email" Keyboard="Email" />
<Entry Placeholder="Numeric" Keyboard="Numeric" />
<Entry Placeholder="Telephone" Keyboard="Telephone" />
<Entry Placeholder="URL" Keyboard="Url" />
</VerticalStackLayout>
</Frame>
<!-- Max Length -->
<Frame Padding="15" CornerRadius="8">
<VerticalStackLayout Spacing="10">
<Label Text="Max Length (10 characters)" FontAttributes="Bold" />
<Entry Placeholder="Max 10 chars" MaxLength="10" />
</VerticalStackLayout>
</Frame>
<!-- Editor (Multiline) -->
<Frame Padding="15" CornerRadius="8">
<VerticalStackLayout Spacing="10">
<Label Text="Editor (Multiline)" FontAttributes="Bold" />
<Editor Placeholder="Enter multiple lines of text..."
HeightRequest="100"
AutoSize="TextChanges" />
</VerticalStackLayout>
</Frame>
<!-- Read-Only -->
<Frame Padding="15" CornerRadius="8">
<VerticalStackLayout Spacing="10">
<Label Text="Read-Only Entry" FontAttributes="Bold" />
<Entry Text="This text cannot be edited" IsReadOnly="True" />
</VerticalStackLayout>
</Frame>
</VerticalStackLayout>
</ScrollView>
</ContentPage>

View File

@@ -1,14 +0,0 @@
namespace ControlGallery.Pages;
public partial class EntryPage : ContentPage
{
public EntryPage()
{
InitializeComponent();
}
private void OnEntryTextChanged(object sender, TextChangedEventArgs e)
{
BoundLabel.Text = $"You typed: {e.NewTextValue}";
}
}

View File

@@ -1,60 +0,0 @@
<?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="ControlGallery.Pages.HomePage"
Title="OpenMaui Control Gallery">
<ScrollView>
<VerticalStackLayout Spacing="20" Padding="30">
<Label Text="OpenMaui"
FontSize="48"
FontAttributes="Bold"
TextColor="{StaticResource Primary}"
HorizontalTextAlignment="Center" />
<Label Text="Control Gallery"
FontSize="24"
TextColor="{StaticResource Gray500}"
HorizontalTextAlignment="Center" />
<BoxView HeightRequest="2" Color="{StaticResource Gray200}" Margin="0,10" />
<Label Text="Welcome to the OpenMaui Control Gallery! This sample app demonstrates all 35+ controls available in the OpenMaui Linux platform."
FontSize="16"
HorizontalTextAlignment="Center"
LineBreakMode="WordWrap" />
<Frame BackgroundColor="{StaticResource Secondary}" Padding="20" CornerRadius="10">
<VerticalStackLayout Spacing="10">
<Label Text="Features" FontSize="20" FontAttributes="Bold" />
<Label Text="• Full SkiaSharp rendering" />
<Label Text="• X11 and Wayland support" />
<Label Text="• AT-SPI2 accessibility" />
<Label Text="• High DPI support" />
<Label Text="• Platform services (clipboard, notifications, etc.)" />
</VerticalStackLayout>
</Frame>
<Label Text="Use the flyout menu to explore different control categories."
FontSize="14"
TextColor="{StaticResource Gray400}"
HorizontalTextAlignment="Center" />
<Button Text="Get Started"
BackgroundColor="{StaticResource Primary}"
TextColor="White"
FontAttributes="Bold"
CornerRadius="8"
Clicked="OnGetStartedClicked" />
<Label Text="Developed by MarketAlly LLC"
FontSize="12"
TextColor="{StaticResource Gray400}"
HorizontalTextAlignment="Center"
Margin="0,20,0,0" />
</VerticalStackLayout>
</ScrollView>
</ContentPage>

View File

@@ -1,14 +0,0 @@
namespace ControlGallery.Pages;
public partial class HomePage : ContentPage
{
public HomePage()
{
InitializeComponent();
}
private async void OnGetStartedClicked(object sender, EventArgs e)
{
await Shell.Current.GoToAsync("//ButtonsPage");
}
}

View File

@@ -1,102 +0,0 @@
<?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="ControlGallery.Pages.ImagesPage"
Title="Images">
<ScrollView>
<VerticalStackLayout Spacing="20" Padding="20">
<Label Text="Image Controls" FontSize="24" FontAttributes="Bold" />
<!-- Basic Image -->
<Frame Padding="15" CornerRadius="8">
<VerticalStackLayout Spacing="10">
<Label Text="Basic Image" FontAttributes="Bold" />
<Image Source="dotnet_bot.png"
WidthRequest="150"
HeightRequest="150"
HorizontalOptions="Center" />
</VerticalStackLayout>
</Frame>
<!-- Image Aspect Ratios -->
<Frame Padding="15" CornerRadius="8">
<VerticalStackLayout Spacing="10">
<Label Text="Aspect Ratios" FontAttributes="Bold" />
<HorizontalStackLayout Spacing="10">
<VerticalStackLayout>
<Label Text="AspectFit" FontSize="12" HorizontalOptions="Center" />
<Image Source="dotnet_bot.png"
Aspect="AspectFit"
WidthRequest="80"
HeightRequest="80"
BackgroundColor="{StaticResource Gray100}" />
</VerticalStackLayout>
<VerticalStackLayout>
<Label Text="AspectFill" FontSize="12" HorizontalOptions="Center" />
<Image Source="dotnet_bot.png"
Aspect="AspectFill"
WidthRequest="80"
HeightRequest="80"
BackgroundColor="{StaticResource Gray100}" />
</VerticalStackLayout>
<VerticalStackLayout>
<Label Text="Fill" FontSize="12" HorizontalOptions="Center" />
<Image Source="dotnet_bot.png"
Aspect="Fill"
WidthRequest="80"
HeightRequest="80"
BackgroundColor="{StaticResource Gray100}" />
</VerticalStackLayout>
</HorizontalStackLayout>
</VerticalStackLayout>
</Frame>
<!-- Image in Border -->
<Frame Padding="15" CornerRadius="8">
<VerticalStackLayout Spacing="10">
<Label Text="Image with Border" FontAttributes="Bold" />
<Border StrokeThickness="3"
Stroke="{StaticResource Primary}"
StrokeShape="RoundRectangle 20"
HorizontalOptions="Center">
<Image Source="dotnet_bot.png"
WidthRequest="120"
HeightRequest="120"
Aspect="AspectFill" />
</Border>
</VerticalStackLayout>
</Frame>
<!-- Circular Image -->
<Frame Padding="15" CornerRadius="8">
<VerticalStackLayout Spacing="10">
<Label Text="Circular Image (Avatar Style)" FontAttributes="Bold" />
<Border StrokeThickness="0"
StrokeShape="RoundRectangle 60"
HorizontalOptions="Center"
WidthRequest="120"
HeightRequest="120">
<Image Source="dotnet_bot.png"
Aspect="AspectFill" />
</Border>
</VerticalStackLayout>
</Frame>
<!-- Image Loading States -->
<Frame Padding="15" CornerRadius="8">
<VerticalStackLayout Spacing="10">
<Label Text="Image Loading" FontAttributes="Bold" />
<Label Text="Images support loading indicators and error handling" TextColor="{StaticResource Gray500}" />
<Image Source="https://via.placeholder.com/150"
WidthRequest="150"
HeightRequest="150"
HorizontalOptions="Center" />
</VerticalStackLayout>
</Frame>
</VerticalStackLayout>
</ScrollView>
</ContentPage>

View File

@@ -1,9 +0,0 @@
namespace ControlGallery.Pages;
public partial class ImagesPage : ContentPage
{
public ImagesPage()
{
InitializeComponent();
}
}

View File

@@ -1,82 +0,0 @@
<?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="ControlGallery.Pages.LabelsPage"
Title="Labels">
<ScrollView>
<VerticalStackLayout Spacing="20" Padding="20">
<Label Text="Label Controls" FontSize="24" FontAttributes="Bold" />
<!-- Font Sizes -->
<Frame Padding="15" CornerRadius="8">
<VerticalStackLayout Spacing="10">
<Label Text="Font Sizes" FontAttributes="Bold" />
<Label Text="Caption (12)" FontSize="12" />
<Label Text="Body (14)" FontSize="14" />
<Label Text="Subtitle (18)" FontSize="18" />
<Label Text="Title (24)" FontSize="24" />
<Label Text="Header (32)" FontSize="32" />
</VerticalStackLayout>
</Frame>
<!-- Font Attributes -->
<Frame Padding="15" CornerRadius="8">
<VerticalStackLayout Spacing="10">
<Label Text="Font Attributes" FontAttributes="Bold" />
<Label Text="Normal Text" />
<Label Text="Bold Text" FontAttributes="Bold" />
<Label Text="Italic Text" FontAttributes="Italic" />
<Label Text="Bold Italic" FontAttributes="Bold,Italic" />
</VerticalStackLayout>
</Frame>
<!-- Text Colors -->
<Frame Padding="15" CornerRadius="8">
<VerticalStackLayout Spacing="10">
<Label Text="Text Colors" FontAttributes="Bold" />
<Label Text="Primary Color" TextColor="{StaticResource Primary}" />
<Label Text="Tertiary Color" TextColor="{StaticResource Tertiary}" />
<Label Text="Gray 500" TextColor="{StaticResource Gray500}" />
<Label Text="Custom Red" TextColor="#FF5722" />
<Label Text="Custom Green" TextColor="#4CAF50" />
</VerticalStackLayout>
</Frame>
<!-- Text Alignment -->
<Frame Padding="15" CornerRadius="8">
<VerticalStackLayout Spacing="10">
<Label Text="Text Alignment" FontAttributes="Bold" />
<Label Text="Left Aligned" HorizontalTextAlignment="Start" BackgroundColor="{StaticResource Gray100}" />
<Label Text="Center Aligned" HorizontalTextAlignment="Center" BackgroundColor="{StaticResource Gray100}" />
<Label Text="Right Aligned" HorizontalTextAlignment="End" BackgroundColor="{StaticResource Gray100}" />
</VerticalStackLayout>
</Frame>
<!-- Text Decorations -->
<Frame Padding="15" CornerRadius="8">
<VerticalStackLayout Spacing="10">
<Label Text="Text Decorations" FontAttributes="Bold" />
<Label Text="Underlined Text" TextDecorations="Underline" />
<Label Text="Strikethrough Text" TextDecorations="Strikethrough" />
<Label Text="Both Decorations" TextDecorations="Underline,Strikethrough" />
</VerticalStackLayout>
</Frame>
<!-- Line Break Modes -->
<Frame Padding="15" CornerRadius="8">
<VerticalStackLayout Spacing="10">
<Label Text="Line Break Modes" FontAttributes="Bold" />
<Label Text="This is a long text that will wrap to multiple lines when it exceeds the available width."
LineBreakMode="WordWrap" />
<Label Text="This is truncated at the tail..."
LineBreakMode="TailTruncation"
MaxLines="1" />
</VerticalStackLayout>
</Frame>
</VerticalStackLayout>
</ScrollView>
</ContentPage>

View File

@@ -1,9 +0,0 @@
namespace ControlGallery.Pages;
public partial class LabelsPage : ContentPage
{
public LabelsPage()
{
InitializeComponent();
}
}

View File

@@ -1,59 +0,0 @@
<?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="ControlGallery.Pages.PickersPage"
Title="Pickers">
<ScrollView>
<VerticalStackLayout Spacing="20" Padding="20">
<Label Text="Picker Controls" FontSize="24" FontAttributes="Bold" />
<!-- Basic Picker -->
<Frame Padding="15" CornerRadius="8">
<VerticalStackLayout Spacing="10">
<Label Text="Basic Picker" FontAttributes="Bold" />
<Picker Title="Select a color" SelectedIndexChanged="OnColorPickerChanged">
<Picker.Items>
<x:String>Red</x:String>
<x:String>Green</x:String>
<x:String>Blue</x:String>
<x:String>Yellow</x:String>
<x:String>Purple</x:String>
</Picker.Items>
</Picker>
<Label x:Name="ColorResultLabel" Text="No color selected" TextColor="{StaticResource Gray500}" />
</VerticalStackLayout>
</Frame>
<!-- DatePicker -->
<Frame Padding="15" CornerRadius="8">
<VerticalStackLayout Spacing="10">
<Label Text="DatePicker" FontAttributes="Bold" />
<DatePicker DateSelected="OnDateSelected" />
<Label x:Name="DateResultLabel" Text="Select a date" TextColor="{StaticResource Gray500}" />
</VerticalStackLayout>
</Frame>
<!-- TimePicker -->
<Frame Padding="15" CornerRadius="8">
<VerticalStackLayout Spacing="10">
<Label Text="TimePicker" FontAttributes="Bold" />
<TimePicker PropertyChanged="OnTimeChanged" />
<Label x:Name="TimeResultLabel" Text="Select a time" TextColor="{StaticResource Gray500}" />
</VerticalStackLayout>
</Frame>
<!-- DatePicker with Range -->
<Frame Padding="15" CornerRadius="8">
<VerticalStackLayout Spacing="10">
<Label Text="DatePicker with Range" FontAttributes="Bold" />
<Label Text="Limited to next 30 days" TextColor="{StaticResource Gray500}" />
<DatePicker x:Name="RangeDatePicker" />
</VerticalStackLayout>
</Frame>
</VerticalStackLayout>
</ScrollView>
</ContentPage>

View File

@@ -1,36 +0,0 @@
namespace ControlGallery.Pages;
public partial class PickersPage : ContentPage
{
public PickersPage()
{
InitializeComponent();
// Set date range
RangeDatePicker.MinimumDate = DateTime.Today;
RangeDatePicker.MaximumDate = DateTime.Today.AddDays(30);
}
private void OnColorPickerChanged(object sender, EventArgs e)
{
var picker = (Picker)sender;
if (picker.SelectedIndex >= 0)
{
ColorResultLabel.Text = $"Selected: {picker.Items[picker.SelectedIndex]}";
}
}
private void OnDateSelected(object sender, DateChangedEventArgs e)
{
DateResultLabel.Text = $"Selected: {e.NewDate:MMMM dd, yyyy}";
}
private void OnTimeChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == nameof(TimePicker.Time))
{
var picker = (TimePicker)sender;
TimeResultLabel.Text = $"Selected: {picker.Time:hh\\:mm}";
}
}
}

View File

@@ -1,88 +0,0 @@
<?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="ControlGallery.Pages.ProgressPage"
Title="Progress">
<ScrollView>
<VerticalStackLayout Spacing="20" Padding="20">
<Label Text="Progress Controls" FontSize="24" FontAttributes="Bold" />
<!-- ProgressBar -->
<Frame Padding="15" CornerRadius="8">
<VerticalStackLayout Spacing="10">
<Label Text="ProgressBar" FontAttributes="Bold" />
<ProgressBar x:Name="DemoProgress" Progress="0.5" />
<HorizontalStackLayout Spacing="10" HorizontalOptions="Center">
<Button Text="0%" Clicked="OnProgress0" />
<Button Text="50%" Clicked="OnProgress50" />
<Button Text="100%" Clicked="OnProgress100" />
</HorizontalStackLayout>
</VerticalStackLayout>
</Frame>
<!-- Styled ProgressBars -->
<Frame Padding="15" CornerRadius="8">
<VerticalStackLayout Spacing="10">
<Label Text="Styled ProgressBars" FontAttributes="Bold" />
<ProgressBar Progress="0.3" ProgressColor="{StaticResource Primary}" />
<ProgressBar Progress="0.6" ProgressColor="#4CAF50" />
<ProgressBar Progress="0.9" ProgressColor="#FF5722" />
</VerticalStackLayout>
</Frame>
<!-- ActivityIndicator -->
<Frame Padding="15" CornerRadius="8">
<VerticalStackLayout Spacing="10">
<Label Text="ActivityIndicator" FontAttributes="Bold" />
<HorizontalStackLayout Spacing="20" HorizontalOptions="Center">
<ActivityIndicator IsRunning="True" Color="{StaticResource Primary}" />
<ActivityIndicator IsRunning="True" Color="#4CAF50" />
<ActivityIndicator IsRunning="True" Color="#FF5722" />
</HorizontalStackLayout>
</VerticalStackLayout>
</Frame>
<!-- Controlled ActivityIndicator -->
<Frame Padding="15" CornerRadius="8">
<VerticalStackLayout Spacing="10">
<Label Text="Controlled ActivityIndicator" FontAttributes="Bold" />
<ActivityIndicator x:Name="ControlledIndicator" IsRunning="False" Color="{StaticResource Primary}" />
<HorizontalStackLayout Spacing="10" HorizontalOptions="Center">
<Button Text="Start" Clicked="OnStartIndicator" />
<Button Text="Stop" Clicked="OnStopIndicator" />
</HorizontalStackLayout>
</VerticalStackLayout>
</Frame>
<!-- Animated Progress Demo -->
<Frame Padding="15" CornerRadius="8">
<VerticalStackLayout Spacing="10">
<Label Text="Animated Progress" FontAttributes="Bold" />
<ProgressBar x:Name="AnimatedProgress" Progress="0" />
<Button Text="Animate to 100%" Clicked="OnAnimateProgress" />
</VerticalStackLayout>
</Frame>
<!-- Loading Overlay Demo -->
<Frame Padding="15" CornerRadius="8" BackgroundColor="{StaticResource Secondary}">
<VerticalStackLayout Spacing="10">
<Label Text="Loading State Demo" FontAttributes="Bold" />
<Grid>
<VerticalStackLayout x:Name="ContentPanel">
<Label Text="This is some content that can be loading..." />
<Button Text="Show Loading" Clicked="OnShowLoading" />
</VerticalStackLayout>
<VerticalStackLayout x:Name="LoadingPanel" IsVisible="False" HorizontalOptions="Center" VerticalOptions="Center">
<ActivityIndicator IsRunning="True" Color="{StaticResource Primary}" />
<Label Text="Loading..." HorizontalOptions="Center" />
</VerticalStackLayout>
</Grid>
</VerticalStackLayout>
</Frame>
</VerticalStackLayout>
</ScrollView>
</ContentPage>

View File

@@ -1,33 +0,0 @@
namespace ControlGallery.Pages;
public partial class ProgressPage : ContentPage
{
public ProgressPage()
{
InitializeComponent();
}
private void OnProgress0(object sender, EventArgs e) => DemoProgress.Progress = 0;
private void OnProgress50(object sender, EventArgs e) => DemoProgress.Progress = 0.5;
private void OnProgress100(object sender, EventArgs e) => DemoProgress.Progress = 1.0;
private void OnStartIndicator(object sender, EventArgs e) => ControlledIndicator.IsRunning = true;
private void OnStopIndicator(object sender, EventArgs e) => ControlledIndicator.IsRunning = false;
private async void OnAnimateProgress(object sender, EventArgs e)
{
AnimatedProgress.Progress = 0;
await AnimatedProgress.ProgressTo(1.0, 2000, Easing.Linear);
}
private async void OnShowLoading(object sender, EventArgs e)
{
ContentPanel.IsVisible = false;
LoadingPanel.IsVisible = true;
await Task.Delay(2000);
LoadingPanel.IsVisible = false;
ContentPanel.IsVisible = true;
}
}

View File

@@ -1,68 +0,0 @@
<?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="ControlGallery.Pages.RefreshViewPage"
Title="RefreshView">
<RefreshView x:Name="RefreshContainer"
IsRefreshing="{Binding IsRefreshing}"
Refreshing="OnRefreshing"
RefreshColor="{StaticResource Primary}">
<ScrollView>
<VerticalStackLayout Spacing="20" Padding="20">
<Label Text="RefreshView" FontSize="24" FontAttributes="Bold" />
<Label Text="Pull down to refresh the content" TextColor="{StaticResource Gray500}" />
<Label x:Name="LastRefreshLabel"
Text="Last refreshed: Never"
TextColor="{StaticResource Gray400}"
HorizontalOptions="Center" />
<BoxView HeightRequest="1" Color="{StaticResource Gray200}" />
<!-- Simulated content that gets refreshed -->
<Frame Padding="15" CornerRadius="8" BackgroundColor="{StaticResource Secondary}">
<VerticalStackLayout Spacing="10">
<Label Text="News Feed" FontAttributes="Bold" FontSize="18" />
<Frame Padding="10" CornerRadius="5" BackgroundColor="White">
<VerticalStackLayout>
<Label x:Name="NewsItem1" Text="Loading..." FontAttributes="Bold" />
<Label Text="2 minutes ago" TextColor="{StaticResource Gray400}" FontSize="12" />
</VerticalStackLayout>
</Frame>
<Frame Padding="10" CornerRadius="5" BackgroundColor="White">
<VerticalStackLayout>
<Label x:Name="NewsItem2" Text="Loading..." FontAttributes="Bold" />
<Label Text="15 minutes ago" TextColor="{StaticResource Gray400}" FontSize="12" />
</VerticalStackLayout>
</Frame>
<Frame Padding="10" CornerRadius="5" BackgroundColor="White">
<VerticalStackLayout>
<Label x:Name="NewsItem3" Text="Loading..." FontAttributes="Bold" />
<Label Text="1 hour ago" TextColor="{StaticResource Gray400}" FontSize="12" />
</VerticalStackLayout>
</Frame>
</VerticalStackLayout>
</Frame>
<Button Text="Manual Refresh"
Clicked="OnManualRefreshClicked"
BackgroundColor="{StaticResource Primary}"
TextColor="White" />
<Label Text="Tip: You can also pull down on this page to trigger a refresh."
FontSize="12"
TextColor="{StaticResource Gray400}"
HorizontalTextAlignment="Center" />
</VerticalStackLayout>
</ScrollView>
</RefreshView>
</ContentPage>

View File

@@ -1,48 +0,0 @@
namespace ControlGallery.Pages;
public partial class RefreshViewPage : ContentPage
{
private readonly Random _random = new();
private readonly string[] _headlines = new[]
{
"OpenMaui 1.0 Released!",
"Linux Desktop Apps Made Easy",
"SkiaSharp Powers Modern UIs",
"Cross-Platform Development Grows",
".NET 9 Performance Boost",
"XAML Hot Reload Coming Soon",
"Wayland Support Expanding",
"Community Contributions Welcome",
"New Controls Added Weekly",
"Accessibility Features Improved"
};
public RefreshViewPage()
{
InitializeComponent();
UpdateNews();
}
private async void OnRefreshing(object sender, EventArgs e)
{
// Simulate network delay
await Task.Delay(1500);
UpdateNews();
LastRefreshLabel.Text = $"Last refreshed: {DateTime.Now:HH:mm:ss}";
RefreshContainer.IsRefreshing = false;
}
private void OnManualRefreshClicked(object sender, EventArgs e)
{
RefreshContainer.IsRefreshing = true;
}
private void UpdateNews()
{
NewsItem1.Text = _headlines[_random.Next(_headlines.Length)];
NewsItem2.Text = _headlines[_random.Next(_headlines.Length)];
NewsItem3.Text = _headlines[_random.Next(_headlines.Length)];
}
}

View File

@@ -1,71 +0,0 @@
<?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="ControlGallery.Pages.SlidersPage"
Title="Sliders">
<ScrollView>
<VerticalStackLayout Spacing="20" Padding="20">
<Label Text="Slider Controls" FontSize="24" FontAttributes="Bold" />
<!-- Basic Slider -->
<Frame Padding="15" CornerRadius="8">
<VerticalStackLayout Spacing="10">
<Label Text="Basic Slider" FontAttributes="Bold" />
<Slider x:Name="BasicSlider" Minimum="0" Maximum="100" ValueChanged="OnSliderValueChanged" />
<Label x:Name="SliderValueLabel" Text="Value: 0" TextColor="{StaticResource Gray500}" />
</VerticalStackLayout>
</Frame>
<!-- Styled Slider -->
<Frame Padding="15" CornerRadius="8">
<VerticalStackLayout Spacing="10">
<Label Text="Styled Slider" FontAttributes="Bold" />
<Slider Minimum="0" Maximum="100"
MinimumTrackColor="#4CAF50"
MaximumTrackColor="#E0E0E0"
ThumbColor="#388E3C" />
</VerticalStackLayout>
</Frame>
<!-- Stepper -->
<Frame Padding="15" CornerRadius="8">
<VerticalStackLayout Spacing="10">
<Label Text="Stepper" FontAttributes="Bold" />
<HorizontalStackLayout Spacing="15">
<Stepper x:Name="MyStepper" Minimum="0" Maximum="10" Increment="1" ValueChanged="OnStepperValueChanged" />
<Label x:Name="StepperValueLabel" Text="Value: 0" VerticalOptions="Center" />
</HorizontalStackLayout>
</VerticalStackLayout>
</Frame>
<!-- Stepper with Custom Increment -->
<Frame Padding="15" CornerRadius="8">
<VerticalStackLayout Spacing="10">
<Label Text="Stepper (Increment: 0.5)" FontAttributes="Bold" />
<HorizontalStackLayout Spacing="15">
<Stepper x:Name="DecimalStepper" Minimum="0" Maximum="5" Increment="0.5" ValueChanged="OnDecimalStepperValueChanged" />
<Label x:Name="DecimalStepperLabel" Text="Value: 0.0" VerticalOptions="Center" />
</HorizontalStackLayout>
</VerticalStackLayout>
</Frame>
<!-- Interactive Demo -->
<Frame Padding="15" CornerRadius="8">
<VerticalStackLayout Spacing="10">
<Label Text="Interactive Demo" FontAttributes="Bold" />
<Label Text="Adjust the slider to change the box size:" TextColor="{StaticResource Gray500}" />
<Slider x:Name="SizeSlider" Minimum="50" Maximum="200" Value="100" ValueChanged="OnSizeSliderChanged" />
<BoxView x:Name="DemoBox"
WidthRequest="100"
HeightRequest="100"
Color="{StaticResource Primary}"
HorizontalOptions="Center" />
</VerticalStackLayout>
</Frame>
</VerticalStackLayout>
</ScrollView>
</ContentPage>

View File

@@ -1,30 +0,0 @@
namespace ControlGallery.Pages;
public partial class SlidersPage : ContentPage
{
public SlidersPage()
{
InitializeComponent();
}
private void OnSliderValueChanged(object sender, ValueChangedEventArgs e)
{
SliderValueLabel.Text = $"Value: {e.NewValue:F0}";
}
private void OnStepperValueChanged(object sender, ValueChangedEventArgs e)
{
StepperValueLabel.Text = $"Value: {e.NewValue:F0}";
}
private void OnDecimalStepperValueChanged(object sender, ValueChangedEventArgs e)
{
DecimalStepperLabel.Text = $"Value: {e.NewValue:F1}";
}
private void OnSizeSliderChanged(object sender, ValueChangedEventArgs e)
{
DemoBox.WidthRequest = e.NewValue;
DemoBox.HeightRequest = e.NewValue;
}
}

View File

@@ -1,121 +0,0 @@
<?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="ControlGallery.Pages.SwipeViewPage"
Title="SwipeView">
<VerticalStackLayout Spacing="20" Padding="20">
<Label Text="SwipeView" FontSize="24" FontAttributes="Bold" />
<Label Text="Swipe items left or right to reveal actions" TextColor="{StaticResource Gray500}" />
<ScrollView>
<VerticalStackLayout Spacing="10">
<!-- Swipe to Delete -->
<Label Text="Swipe Left to Delete" FontAttributes="Bold" Margin="0,10,0,5" />
<SwipeView>
<SwipeView.RightItems>
<SwipeItems Mode="Execute">
<SwipeItem Text="Delete"
BackgroundColor="#FF5252"
Invoked="OnDeleteInvoked" />
</SwipeItems>
</SwipeView.RightItems>
<Frame Padding="15" CornerRadius="8">
<HorizontalStackLayout Spacing="10">
<BoxView WidthRequest="40" HeightRequest="40" Color="{StaticResource Primary}" CornerRadius="20" />
<VerticalStackLayout VerticalOptions="Center">
<Label Text="Email from John" FontAttributes="Bold" />
<Label Text="Meeting tomorrow at 10am" TextColor="{StaticResource Gray500}" FontSize="12" />
</VerticalStackLayout>
</HorizontalStackLayout>
</Frame>
</SwipeView>
<!-- Swipe for Multiple Actions -->
<Label Text="Swipe Left for Multiple Actions" FontAttributes="Bold" Margin="0,10,0,5" />
<SwipeView>
<SwipeView.RightItems>
<SwipeItems>
<SwipeItem Text="Archive"
BackgroundColor="#FFC107"
Invoked="OnArchiveInvoked" />
<SwipeItem Text="Delete"
BackgroundColor="#FF5252"
Invoked="OnDeleteInvoked" />
</SwipeItems>
</SwipeView.RightItems>
<Frame Padding="15" CornerRadius="8">
<HorizontalStackLayout Spacing="10">
<BoxView WidthRequest="40" HeightRequest="40" Color="#4CAF50" CornerRadius="20" />
<VerticalStackLayout VerticalOptions="Center">
<Label Text="Email from Sarah" FontAttributes="Bold" />
<Label Text="Project update" TextColor="{StaticResource Gray500}" FontSize="12" />
</VerticalStackLayout>
</HorizontalStackLayout>
</Frame>
</SwipeView>
<!-- Swipe Right to Favorite -->
<Label Text="Swipe Right to Favorite" FontAttributes="Bold" Margin="0,10,0,5" />
<SwipeView>
<SwipeView.LeftItems>
<SwipeItems>
<SwipeItem Text="★ Favorite"
BackgroundColor="#2196F3"
Invoked="OnFavoriteInvoked" />
</SwipeItems>
</SwipeView.LeftItems>
<Frame Padding="15" CornerRadius="8">
<HorizontalStackLayout Spacing="10">
<BoxView WidthRequest="40" HeightRequest="40" Color="#9C27B0" CornerRadius="20" />
<VerticalStackLayout VerticalOptions="Center">
<Label Text="Email from Mike" FontAttributes="Bold" />
<Label Text="Check out this article" TextColor="{StaticResource Gray500}" FontSize="12" />
</VerticalStackLayout>
</HorizontalStackLayout>
</Frame>
</SwipeView>
<!-- Bidirectional Swipe -->
<Label Text="Swipe Both Directions" FontAttributes="Bold" Margin="0,10,0,5" />
<SwipeView>
<SwipeView.LeftItems>
<SwipeItems>
<SwipeItem Text="Reply"
BackgroundColor="#4CAF50"
Invoked="OnReplyInvoked" />
</SwipeItems>
</SwipeView.LeftItems>
<SwipeView.RightItems>
<SwipeItems>
<SwipeItem Text="Forward"
BackgroundColor="#2196F3"
Invoked="OnForwardInvoked" />
<SwipeItem Text="Delete"
BackgroundColor="#FF5252"
Invoked="OnDeleteInvoked" />
</SwipeItems>
</SwipeView.RightItems>
<Frame Padding="15" CornerRadius="8">
<HorizontalStackLayout Spacing="10">
<BoxView WidthRequest="40" HeightRequest="40" Color="#FF9800" CornerRadius="20" />
<VerticalStackLayout VerticalOptions="Center">
<Label Text="Email from Boss" FontAttributes="Bold" />
<Label Text="Quarterly review scheduled" TextColor="{StaticResource Gray500}" FontSize="12" />
</VerticalStackLayout>
</HorizontalStackLayout>
</Frame>
</SwipeView>
</VerticalStackLayout>
</ScrollView>
</VerticalStackLayout>
</ContentPage>

View File

@@ -1,34 +0,0 @@
namespace ControlGallery.Pages;
public partial class SwipeViewPage : ContentPage
{
public SwipeViewPage()
{
InitializeComponent();
}
private async void OnDeleteInvoked(object sender, EventArgs e)
{
await DisplayAlert("Delete", "Item would be deleted", "OK");
}
private async void OnArchiveInvoked(object sender, EventArgs e)
{
await DisplayAlert("Archive", "Item would be archived", "OK");
}
private async void OnFavoriteInvoked(object sender, EventArgs e)
{
await DisplayAlert("Favorite", "Item added to favorites", "OK");
}
private async void OnReplyInvoked(object sender, EventArgs e)
{
await DisplayAlert("Reply", "Opening reply composer", "OK");
}
private async void OnForwardInvoked(object sender, EventArgs e)
{
await DisplayAlert("Forward", "Opening forward dialog", "OK");
}
}

View File

@@ -1,102 +0,0 @@
<?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="ControlGallery.Pages.TogglesPage"
Title="Toggles">
<ScrollView>
<VerticalStackLayout Spacing="20" Padding="20">
<Label Text="Toggle Controls" FontSize="24" FontAttributes="Bold" />
<!-- CheckBox -->
<Frame Padding="15" CornerRadius="8">
<VerticalStackLayout Spacing="10">
<Label Text="CheckBox" FontAttributes="Bold" />
<HorizontalStackLayout Spacing="10">
<CheckBox x:Name="AgreeCheckBox" CheckedChanged="OnCheckBoxChanged" />
<Label Text="I agree to the terms" VerticalOptions="Center" />
</HorizontalStackLayout>
<Label x:Name="CheckBoxLabel" Text="Not agreed" TextColor="{StaticResource Gray500}" />
</VerticalStackLayout>
</Frame>
<!-- Styled CheckBoxes -->
<Frame Padding="15" CornerRadius="8">
<VerticalStackLayout Spacing="10">
<Label Text="Styled CheckBoxes" FontAttributes="Bold" />
<HorizontalStackLayout Spacing="20">
<CheckBox Color="{StaticResource Primary}" IsChecked="True" />
<CheckBox Color="#4CAF50" IsChecked="True" />
<CheckBox Color="#FF5722" IsChecked="True" />
<CheckBox Color="#2196F3" IsChecked="True" />
</HorizontalStackLayout>
</VerticalStackLayout>
</Frame>
<!-- Switch -->
<Frame Padding="15" CornerRadius="8">
<VerticalStackLayout Spacing="10">
<Label Text="Switch" FontAttributes="Bold" />
<HorizontalStackLayout Spacing="15">
<Switch x:Name="NotificationSwitch" Toggled="OnSwitchToggled" />
<Label Text="Enable Notifications" VerticalOptions="Center" />
</HorizontalStackLayout>
<Label x:Name="SwitchLabel" Text="Notifications: Off" TextColor="{StaticResource Gray500}" />
</VerticalStackLayout>
</Frame>
<!-- Styled Switches -->
<Frame Padding="15" CornerRadius="8">
<VerticalStackLayout Spacing="10">
<Label Text="Styled Switches" FontAttributes="Bold" />
<HorizontalStackLayout Spacing="20">
<Switch OnColor="{StaticResource Primary}" IsToggled="True" />
<Switch OnColor="#4CAF50" IsToggled="True" />
<Switch OnColor="#FF5722" IsToggled="True" />
</HorizontalStackLayout>
</VerticalStackLayout>
</Frame>
<!-- RadioButton -->
<Frame Padding="15" CornerRadius="8">
<VerticalStackLayout Spacing="10">
<Label Text="RadioButton Group" FontAttributes="Bold" />
<RadioButton Content="Option A" GroupName="Options" CheckedChanged="OnRadioButtonChecked" />
<RadioButton Content="Option B" GroupName="Options" CheckedChanged="OnRadioButtonChecked" />
<RadioButton Content="Option C" GroupName="Options" CheckedChanged="OnRadioButtonChecked" />
<Label x:Name="RadioLabel" Text="No option selected" TextColor="{StaticResource Gray500}" />
</VerticalStackLayout>
</Frame>
<!-- Settings Demo -->
<Frame Padding="15" CornerRadius="8" BackgroundColor="{StaticResource Secondary}">
<VerticalStackLayout Spacing="15">
<Label Text="Settings Demo" FontAttributes="Bold" FontSize="18" />
<Grid ColumnDefinitions="*,Auto">
<Label Text="Dark Mode" VerticalOptions="Center" />
<Switch Grid.Column="1" />
</Grid>
<Grid ColumnDefinitions="*,Auto">
<Label Text="Push Notifications" VerticalOptions="Center" />
<Switch Grid.Column="1" IsToggled="True" />
</Grid>
<Grid ColumnDefinitions="*,Auto">
<Label Text="Auto-Update" VerticalOptions="Center" />
<Switch Grid.Column="1" IsToggled="True" />
</Grid>
<Grid ColumnDefinitions="*,Auto">
<Label Text="Analytics" VerticalOptions="Center" />
<CheckBox Grid.Column="1" IsChecked="True" />
</Grid>
</VerticalStackLayout>
</Frame>
</VerticalStackLayout>
</ScrollView>
</ContentPage>

View File

@@ -1,27 +0,0 @@
namespace ControlGallery.Pages;
public partial class TogglesPage : ContentPage
{
public TogglesPage()
{
InitializeComponent();
}
private void OnCheckBoxChanged(object sender, CheckedChangedEventArgs e)
{
CheckBoxLabel.Text = e.Value ? "Agreed!" : "Not agreed";
}
private void OnSwitchToggled(object sender, ToggledEventArgs e)
{
SwitchLabel.Text = $"Notifications: {(e.Value ? "On" : "Off")}";
}
private void OnRadioButtonChecked(object sender, CheckedChangedEventArgs e)
{
if (e.Value && sender is RadioButton rb)
{
RadioLabel.Text = $"Selected: {rb.Content}";
}
}
}

View File

@@ -1,41 +0,0 @@
# OpenMaui Control Gallery
A comprehensive sample application demonstrating all 35+ controls available in OpenMaui for Linux.
## Features
This gallery showcases:
- **Basic Controls**: Buttons, Labels, Entry, Editor
- **Selection Controls**: Picker, DatePicker, TimePicker, Slider, Stepper
- **Toggle Controls**: CheckBox, Switch, RadioButton
- **Progress Controls**: ProgressBar, ActivityIndicator
- **Image Controls**: Image, ImageButton with various aspect ratios
- **Collection Controls**: CollectionView, CarouselView with IndicatorView
- **Gesture Controls**: SwipeView, RefreshView
## Running the Sample
```bash
cd samples/ControlGallery
dotnet run
```
## Requirements
- .NET 9.0 SDK
- Linux with X11 or Wayland
- OpenMaui.Controls.Linux NuGet package
## Screenshots
The gallery uses Shell navigation with a flyout menu to organize controls by category.
## Adding Images
Before running, add the following images to `Resources/Images/`:
- `dotnet_bot.png` - From official MAUI templates
## License
MIT License - Copyright 2025 MarketAlly LLC

View File

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

View File

@@ -1,2 +0,0 @@
# Add images here
# Required: dotnet_bot.png

View File

@@ -1,797 +0,0 @@
using System.Runtime.InteropServices;
using Microsoft.Maui.Platform;
using SkiaSharp;
var demo = new AllControlsDemo();
demo.Run();
class AllControlsDemo
{
private IntPtr _display, _window, _gc;
private int _screen, _width = 1024, _height = 768;
private bool _running = true;
private IntPtr _wmDeleteMessage, _pixelBuffer = IntPtr.Zero;
private int _bufferSize = 0;
private SkiaScrollView _scrollView = null!;
private SkiaStackLayout _rootLayout = null!;
private SkiaView? _pressedView = null;
private SkiaView? _focusedView = null;
private SkiaCollectionView _collectionView = null!;
private SkiaDatePicker _datePicker = null!;
private SkiaTimePicker _timePicker = null!;
private SkiaPicker _picker = null!;
private SkiaEntry _entry = null!;
private SkiaSearchBar _searchBar = null!;
private DateTime _lastMotionRender = DateTime.MinValue;
public void Run()
{
try { InitializeX11(); CreateUI(); RunEventLoop(); }
catch (Exception ex) { Console.WriteLine($"Error: {ex}"); }
finally { Cleanup(); }
}
private void InitializeX11()
{
_display = XOpenDisplay(IntPtr.Zero);
if (_display == IntPtr.Zero) throw new Exception("Cannot open X11 display");
_screen = XDefaultScreen(_display);
var root = XRootWindow(_display, _screen);
_window = XCreateSimpleWindow(_display, root, 50, 50, (uint)_width, (uint)_height, 1,
XBlackPixel(_display, _screen), XWhitePixel(_display, _screen));
XStoreName(_display, _window, "MAUI Linux Demo - All Controls");
XSelectInput(_display, _window, ExposureMask | KeyPressMask | KeyReleaseMask |
ButtonPressMask | ButtonReleaseMask | PointerMotionMask | StructureNotifyMask);
_gc = XCreateGC(_display, _window, 0, IntPtr.Zero);
_wmDeleteMessage = XInternAtom(_display, "WM_DELETE_WINDOW", false);
XSetWMProtocols(_display, _window, ref _wmDeleteMessage, 1);
EnsurePixelBuffer(_width, _height);
XMapWindow(_display, _window);
XFlush(_display);
}
private void EnsurePixelBuffer(int w, int h)
{
int needed = w * h * 4;
if (_pixelBuffer == IntPtr.Zero || _bufferSize < needed) {
if (_pixelBuffer != IntPtr.Zero) Marshal.FreeHGlobal(_pixelBuffer);
_pixelBuffer = Marshal.AllocHGlobal(needed);
_bufferSize = needed;
}
}
private void CreateUI()
{
_scrollView = new SkiaScrollView { BackgroundColor = new SKColor(250, 250, 250) };
_rootLayout = new SkiaStackLayout {
Orientation = Microsoft.Maui.Platform.StackOrientation.Vertical,
Spacing = 12, Padding = new SKRect(24, 24, 24, 24),
BackgroundColor = new SKColor(250, 250, 250)
};
// Title
_rootLayout.AddChild(new SkiaLabel { Text = "MAUI Linux Demo", FontSize = 28, IsBold = true,
TextColor = new SKColor(25, 118, 210), RequestedHeight = 40 });
// Basic Controls
AddSection("Basic Controls");
var button = new SkiaButton { Text = "Click Me!", RequestedHeight = 44 };
button.Clicked += (s, e) => Console.WriteLine("Button clicked!");
_rootLayout.AddChild(button);
_rootLayout.AddChild(new SkiaLabel { Text = "This is a Label with some text", RequestedHeight = 24 });
_entry = new SkiaEntry { Placeholder = "Type here...", RequestedHeight = 44 };
_rootLayout.AddChild(_entry);
// Toggle Controls
AddSection("Toggle Controls");
var checkbox = new SkiaCheckBox { IsChecked = true, RequestedHeight = 32 };
_rootLayout.AddChild(checkbox);
var switchCtrl = new SkiaSwitch { IsOn = true, RequestedHeight = 32 };
_rootLayout.AddChild(switchCtrl);
// Sliders
AddSection("Sliders & Progress");
var slider = new SkiaSlider { Value = 0.5, Minimum = 0, Maximum = 1, RequestedHeight = 40 };
_rootLayout.AddChild(slider);
var progress = new SkiaProgressBar { Progress = 0.7f, RequestedHeight = 16 };
_rootLayout.AddChild(progress);
// Pickers - These are the ones with popups
AddSection("Pickers (click to open popups)");
_datePicker = new SkiaDatePicker { Date = DateTime.Today, RequestedHeight = 44 };
_rootLayout.AddChild(_datePicker);
_timePicker = new SkiaTimePicker { Time = DateTime.Now.TimeOfDay, RequestedHeight = 44 };
_rootLayout.AddChild(_timePicker);
_picker = new SkiaPicker { Title = "Select a fruit...", RequestedHeight = 44 };
_picker.SetItems(new[] { "Apple", "Banana", "Cherry", "Date", "Elderberry", "Fig", "Grape" });
_rootLayout.AddChild(_picker);
// CollectionView
AddSection("CollectionView (scroll with mouse wheel)");
_collectionView = new SkiaCollectionView { RequestedHeight = 180, ItemHeight = 36 };
var items = new List<string>();
for (int i = 1; i <= 50; i++) items.Add($"Collection Item #{i}");
_collectionView.ItemsSource = items;
_rootLayout.AddChild(_collectionView);
// Activity Indicator
AddSection("Activity Indicator");
var activity = new SkiaActivityIndicator { IsRunning = true, RequestedHeight = 50 };
_rootLayout.AddChild(activity);
// SearchBar
AddSection("SearchBar");
_searchBar = new SkiaSearchBar { Placeholder = "Search...", RequestedHeight = 44 };
_rootLayout.AddChild(_searchBar);
// Footer
_rootLayout.AddChild(new SkiaLabel {
Text = "Scroll this page to see all controls. ESC to exit.",
FontSize = 12, TextColor = new SKColor(128, 128, 128), RequestedHeight = 30
});
_scrollView.Content = _rootLayout;
}
private void AddSection(string title)
{
_rootLayout.AddChild(new SkiaLabel {
Text = title, FontSize = 16, IsBold = true,
TextColor = new SKColor(55, 71, 79), RequestedHeight = 32
});
}
private void RunEventLoop()
{
Console.WriteLine("MAUI Linux Demo running... ESC to quit");
Console.WriteLine("- Click DatePicker/TimePicker/Picker to test popups");
Console.WriteLine("- Use mouse wheel on CollectionView to scroll it");
Console.WriteLine("- Use mouse wheel elsewhere to scroll the page");
Render();
var lastRender = DateTime.Now;
while (_running) {
while (XPending(_display) > 0) { XNextEvent(_display, out var ev); HandleEvent(ref ev); }
// Continuous rendering for animations (ActivityIndicator, cursor blink, etc.)
var now = DateTime.Now;
if ((now - lastRender).TotalMilliseconds >= 50) // ~20 FPS for animations
{
lastRender = now;
Render();
}
Thread.Sleep(8);
}
}
private void HandleEvent(ref XEvent e)
{
switch (e.type)
{
case Expose: if (e.xexpose.count == 0) Render(); break;
case ConfigureNotify:
if (e.xconfigure.width != _width || e.xconfigure.height != _height) {
_width = e.xconfigure.width; _height = e.xconfigure.height;
EnsurePixelBuffer(_width, _height); Render();
}
break;
case KeyPress:
var keysym = XLookupKeysym(ref e.xkey, 0);
if (keysym == 0xFF1B) { _running = false; break; } // ESC
// Forward to focused view
if (_focusedView != null)
{
var key = KeysymToKey(keysym);
if (key != Key.Unknown)
{
_focusedView.OnKeyDown(new KeyEventArgs(key));
Render();
}
// Handle text input for printable characters
var ch = KeysymToChar(keysym, e.xkey.state);
if (ch != '\0')
{
_focusedView.OnTextInput(new TextInputEventArgs(ch.ToString()));
Render();
}
}
break;
case ButtonPress:
float sx = e.xbutton.x, sy = e.xbutton.y;
if (e.xbutton.button == 4 || e.xbutton.button == 5) {
// Mouse wheel
var cvBounds = _collectionView.GetAbsoluteBounds();
bool overCV = sx >= cvBounds.Left && sx <= cvBounds.Right &&
sy >= cvBounds.Top && sy <= cvBounds.Bottom;
float delta = (e.xbutton.button == 4) ? -1.5f : 1.5f;
if (overCV) {
_collectionView.OnScroll(new ScrollEventArgs(sx, sy, 0, delta));
} else {
_scrollView.ScrollY = Math.Max(0, _scrollView.ScrollY + (delta > 0 ? 40 : -40));
}
Render();
} else {
// Check if clicking on popup areas first
bool handledPopup = HandlePopupClick(sx, sy);
if (!handledPopup) {
_pressedView = _scrollView.HitTest(sx, sy);
if (_pressedView != null && _pressedView != _scrollView) {
// Update focus
if (_pressedView != _focusedView && _pressedView.IsFocusable)
{
_focusedView?.OnFocusLost();
_focusedView = _pressedView;
_focusedView.OnFocusGained();
}
_pressedView.OnPointerPressed(new Microsoft.Maui.Platform.PointerEventArgs(sx, sy, Microsoft.Maui.Platform.PointerButton.Left));
}
else if (_pressedView == null || _pressedView == _scrollView)
{
// Clicked on empty area - clear focus
_focusedView?.OnFocusLost();
_focusedView = null;
}
}
Render();
}
break;
case MotionNotify:
// Forward drag events to pressed view (for sliders, etc.)
if (_pressedView != null) {
// Close any open popups during drag to prevent glitches
if (_datePicker.IsOpen) _datePicker.IsOpen = false;
if (_timePicker.IsOpen) _timePicker.IsOpen = false;
if (_picker.IsOpen) _picker.IsOpen = false;
_pressedView.OnPointerMoved(new Microsoft.Maui.Platform.PointerEventArgs(e.xmotion.x, e.xmotion.y, Microsoft.Maui.Platform.PointerButton.Left));
// Throttle motion renders to prevent overwhelming the system
var now = DateTime.Now;
if ((now - _lastMotionRender).TotalMilliseconds >= 16) // ~60 FPS max for drag
{
_lastMotionRender = now;
Render();
}
}
break;
case ButtonRelease:
if (e.xbutton.button != 4 && e.xbutton.button != 5 && _pressedView != null) {
_pressedView.OnPointerReleased(new Microsoft.Maui.Platform.PointerEventArgs(e.xbutton.x, e.xbutton.y, Microsoft.Maui.Platform.PointerButton.Left));
_pressedView = null;
Render();
}
break;
case ClientMessage:
if (e.xclient.data_l0 == (long)_wmDeleteMessage) _running = false;
break;
}
}
private bool HandlePopupClick(float x, float y)
{
// Handle date picker popup clicks
if (_datePicker.IsOpen)
{
var bounds = _datePicker.GetAbsoluteBounds();
var popupRect = new SKRect(bounds.Left, bounds.Bottom + 4, bounds.Left + 280, bounds.Bottom + 324);
if (x >= popupRect.Left && x <= popupRect.Right && y >= popupRect.Top && y <= popupRect.Bottom)
{
// Click inside popup - handle calendar navigation/selection
HandleDatePickerPopupClick(x, y, bounds);
return true;
}
else if (y >= bounds.Top && y <= bounds.Bottom && x >= bounds.Left && x <= bounds.Right)
{
// Click on picker button - toggle
_datePicker.IsOpen = false;
return true;
}
else
{
// Click outside - close
_datePicker.IsOpen = false;
return true;
}
}
// Handle time picker popup clicks
if (_timePicker.IsOpen)
{
var bounds = _timePicker.GetAbsoluteBounds();
var popupRect = new SKRect(bounds.Left, bounds.Bottom + 4, bounds.Left + 280, bounds.Bottom + 364);
if (y < popupRect.Top)
{
_timePicker.IsOpen = false;
return true;
}
}
// Handle dropdown picker popup clicks
if (_picker.IsOpen)
{
var bounds = _picker.GetAbsoluteBounds();
var dropdownRect = new SKRect(bounds.Left, bounds.Bottom + 4, bounds.Right, bounds.Bottom + 204);
if (x >= dropdownRect.Left && x <= dropdownRect.Right && y >= dropdownRect.Top && y <= dropdownRect.Bottom)
{
// Click on item
int itemIndex = (int)((y - dropdownRect.Top) / 40);
if (itemIndex >= 0 && itemIndex < 7)
{
_picker.SelectedIndex = itemIndex;
}
_picker.IsOpen = false;
return true;
}
else if (y < dropdownRect.Top)
{
_picker.IsOpen = false;
return true;
}
}
return false;
}
private DateTime _displayMonth = DateTime.Today;
private void HandleDatePickerPopupClick(float x, float y, SKRect pickerBounds)
{
var popupTop = pickerBounds.Bottom + 4;
var headerHeight = 48f;
var weekdayHeight = 30f;
// Navigation arrows
if (y >= popupTop && y < popupTop + headerHeight)
{
if (x < pickerBounds.Left + 40)
{
_displayMonth = _displayMonth.AddMonths(-1);
}
else if (x > pickerBounds.Left + 240)
{
_displayMonth = _displayMonth.AddMonths(1);
}
return;
}
// Day selection
var daysTop = popupTop + headerHeight + weekdayHeight;
if (y >= daysTop)
{
var cellWidth = 280f / 7;
var cellHeight = 38f;
var col = (int)((x - pickerBounds.Left) / cellWidth);
var row = (int)((y - daysTop) / cellHeight);
var firstDay = new DateTime(_displayMonth.Year, _displayMonth.Month, 1);
var startDayOfWeek = (int)firstDay.DayOfWeek;
var dayIndex = row * 7 + col - startDayOfWeek + 1;
var daysInMonth = DateTime.DaysInMonth(_displayMonth.Year, _displayMonth.Month);
if (dayIndex >= 1 && dayIndex <= daysInMonth)
{
_datePicker.Date = new DateTime(_displayMonth.Year, _displayMonth.Month, dayIndex);
_datePicker.IsOpen = false;
}
}
}
private void Render()
{
_scrollView.Measure(new SKSize(_width, _height));
_scrollView.Arrange(new SKRect(0, 0, _width, _height));
var info = new SKImageInfo(_width, _height, SKColorType.Bgra8888, SKAlphaType.Premul);
using var surface = SKSurface.Create(info, _pixelBuffer, _width * 4);
if (surface == null) return;
var canvas = surface.Canvas;
canvas.Clear(new SKColor(250, 250, 250));
_scrollView.Draw(canvas);
// Draw popups on top (outside of scrollview clipping)
DrawPopups(canvas);
canvas.Flush();
var image = XCreateImage(_display, XDefaultVisual(_display, _screen),
(uint)XDefaultDepth(_display, _screen), 2, 0, _pixelBuffer, (uint)_width, (uint)_height, 32, _width * 4);
if (image != IntPtr.Zero) {
XPutImage(_display, _window, _gc, image, 0, 0, 0, 0, (uint)_width, (uint)_height);
XFree(image);
}
XFlush(_display);
}
private void DrawPopups(SKCanvas canvas)
{
// Draw DatePicker calendar popup
if (_datePicker.IsOpen)
{
var bounds = _datePicker.GetAbsoluteBounds();
DrawCalendarPopup(canvas, bounds);
}
// Draw TimePicker clock popup
if (_timePicker.IsOpen)
{
var bounds = _timePicker.GetAbsoluteBounds();
DrawTimePickerPopup(canvas, bounds);
}
// Draw Picker dropdown
if (_picker.IsOpen)
{
var bounds = _picker.GetAbsoluteBounds();
DrawPickerDropdown(canvas, bounds);
}
}
private void DrawCalendarPopup(SKCanvas canvas, SKRect pickerBounds)
{
var popupRect = new SKRect(
pickerBounds.Left, pickerBounds.Bottom + 4,
pickerBounds.Left + 280, pickerBounds.Bottom + 324);
// Shadow
using var shadowPaint = new SKPaint {
Color = new SKColor(0, 0, 0, 50),
MaskFilter = SKMaskFilter.CreateBlur(SKBlurStyle.Normal, 6)
};
canvas.DrawRoundRect(new SKRoundRect(
new SKRect(popupRect.Left + 3, popupRect.Top + 3, popupRect.Right + 3, popupRect.Bottom + 3), 8), shadowPaint);
// Background
using var bgPaint = new SKPaint { Color = SKColors.White, Style = SKPaintStyle.Fill, IsAntialias = true };
canvas.DrawRoundRect(new SKRoundRect(popupRect, 8), bgPaint);
// Border
using var borderPaint = new SKPaint { Color = new SKColor(200, 200, 200), Style = SKPaintStyle.Stroke, StrokeWidth = 1 };
canvas.DrawRoundRect(new SKRoundRect(popupRect, 8), borderPaint);
// Header with month/year
var headerRect = new SKRect(popupRect.Left, popupRect.Top, popupRect.Right, popupRect.Top + 48);
using var headerPaint = new SKPaint { Color = new SKColor(33, 150, 243), Style = SKPaintStyle.Fill };
canvas.Save();
canvas.ClipRoundRect(new SKRoundRect(new SKRect(headerRect.Left, headerRect.Top, headerRect.Right, headerRect.Top + 16), 8));
canvas.DrawRect(headerRect, headerPaint);
canvas.Restore();
canvas.DrawRect(new SKRect(headerRect.Left, headerRect.Top + 8, headerRect.Right, headerRect.Bottom), headerPaint);
// Month/year text
using var headerFont = new SKFont(SKTypeface.Default, 18);
using var headerTextPaint = new SKPaint(headerFont) { Color = SKColors.White, IsAntialias = true };
var monthYear = _displayMonth.ToString("MMMM yyyy");
var textBounds = new SKRect();
headerTextPaint.MeasureText(monthYear, ref textBounds);
canvas.DrawText(monthYear, headerRect.MidX - textBounds.MidX, headerRect.MidY - textBounds.MidY, headerTextPaint);
// Navigation arrows
using var arrowPaint = new SKPaint { Color = SKColors.White, Style = SKPaintStyle.Stroke, StrokeWidth = 2, IsAntialias = true };
// Left arrow
canvas.DrawLine(popupRect.Left + 24, headerRect.MidY, popupRect.Left + 18, headerRect.MidY, arrowPaint);
canvas.DrawLine(popupRect.Left + 18, headerRect.MidY, popupRect.Left + 22, headerRect.MidY - 4, arrowPaint);
canvas.DrawLine(popupRect.Left + 18, headerRect.MidY, popupRect.Left + 22, headerRect.MidY + 4, arrowPaint);
// Right arrow
canvas.DrawLine(popupRect.Right - 24, headerRect.MidY, popupRect.Right - 18, headerRect.MidY, arrowPaint);
canvas.DrawLine(popupRect.Right - 18, headerRect.MidY, popupRect.Right - 22, headerRect.MidY - 4, arrowPaint);
canvas.DrawLine(popupRect.Right - 18, headerRect.MidY, popupRect.Right - 22, headerRect.MidY + 4, arrowPaint);
// Weekday headers
var dayNames = new[] { "Su", "Mo", "Tu", "We", "Th", "Fr", "Sa" };
var cellWidth = 280f / 7;
var weekdayTop = popupRect.Top + 48;
using var weekdayFont = new SKFont(SKTypeface.Default, 12);
using var weekdayPaint = new SKPaint(weekdayFont) { Color = new SKColor(128, 128, 128), IsAntialias = true };
for (int i = 0; i < 7; i++)
{
var dayBounds = new SKRect();
weekdayPaint.MeasureText(dayNames[i], ref dayBounds);
var x = popupRect.Left + i * cellWidth + cellWidth / 2 - dayBounds.MidX;
canvas.DrawText(dayNames[i], x, weekdayTop + 20, weekdayPaint);
}
// Days grid
var daysTop = weekdayTop + 30;
var cellHeight = 38f;
var firstDay = new DateTime(_displayMonth.Year, _displayMonth.Month, 1);
var daysInMonth = DateTime.DaysInMonth(_displayMonth.Year, _displayMonth.Month);
var startDayOfWeek = (int)firstDay.DayOfWeek;
var today = DateTime.Today;
var selectedDate = _datePicker.Date;
using var dayFont = new SKFont(SKTypeface.Default, 14);
using var dayPaint = new SKPaint(dayFont) { IsAntialias = true };
using var circlePaint = new SKPaint { Style = SKPaintStyle.Fill, IsAntialias = true };
for (int day = 1; day <= daysInMonth; day++)
{
var dayDate = new DateTime(_displayMonth.Year, _displayMonth.Month, day);
var cellIndex = startDayOfWeek + day - 1;
var row = cellIndex / 7;
var col = cellIndex % 7;
var cellX = popupRect.Left + col * cellWidth;
var cellY = daysTop + row * cellHeight;
var cellCenterX = cellX + cellWidth / 2;
var cellCenterY = cellY + cellHeight / 2;
var isSelected = dayDate.Date == selectedDate.Date;
var isToday = dayDate.Date == today;
// Draw selection/today circle
if (isSelected)
{
circlePaint.Color = new SKColor(33, 150, 243);
canvas.DrawCircle(cellCenterX, cellCenterY, 16, circlePaint);
}
else if (isToday)
{
circlePaint.Color = new SKColor(33, 150, 243, 60);
canvas.DrawCircle(cellCenterX, cellCenterY, 16, circlePaint);
}
// Draw day number
dayPaint.Color = isSelected ? SKColors.White : SKColors.Black;
var dayText = day.ToString();
var dayBounds = new SKRect();
dayPaint.MeasureText(dayText, ref dayBounds);
canvas.DrawText(dayText, cellCenterX - dayBounds.MidX, cellCenterY - dayBounds.MidY, dayPaint);
}
}
private void DrawTimePickerPopup(SKCanvas canvas, SKRect pickerBounds)
{
var popupRect = new SKRect(
pickerBounds.Left, pickerBounds.Bottom + 4,
pickerBounds.Left + 280, pickerBounds.Bottom + 364);
// Shadow
using var shadowPaint = new SKPaint {
Color = new SKColor(0, 0, 0, 50),
MaskFilter = SKMaskFilter.CreateBlur(SKBlurStyle.Normal, 6)
};
canvas.DrawRoundRect(new SKRoundRect(
new SKRect(popupRect.Left + 3, popupRect.Top + 3, popupRect.Right + 3, popupRect.Bottom + 3), 8), shadowPaint);
// Background
using var bgPaint = new SKPaint { Color = SKColors.White, Style = SKPaintStyle.Fill, IsAntialias = true };
canvas.DrawRoundRect(new SKRoundRect(popupRect, 8), bgPaint);
// Header
var headerRect = new SKRect(popupRect.Left, popupRect.Top, popupRect.Right, popupRect.Top + 80);
using var headerPaint = new SKPaint { Color = new SKColor(33, 150, 243), Style = SKPaintStyle.Fill };
canvas.Save();
canvas.ClipRoundRect(new SKRoundRect(new SKRect(headerRect.Left, headerRect.Top, headerRect.Right, headerRect.Top + 16), 8));
canvas.DrawRect(headerRect, headerPaint);
canvas.Restore();
canvas.DrawRect(new SKRect(headerRect.Left, headerRect.Top + 8, headerRect.Right, headerRect.Bottom), headerPaint);
// Time display
using var timeFont = new SKFont(SKTypeface.Default, 32);
using var timePaint = new SKPaint(timeFont) { Color = SKColors.White, IsAntialias = true };
var time = _timePicker.Time;
var timeText = $"{time.Hours:D2}:{time.Minutes:D2}";
var timeBounds = new SKRect();
timePaint.MeasureText(timeText, ref timeBounds);
canvas.DrawText(timeText, headerRect.MidX - timeBounds.MidX, headerRect.MidY - timeBounds.MidY, timePaint);
// Clock face
var clockCenterX = popupRect.MidX;
var clockCenterY = popupRect.Top + 80 + 140;
var clockRadius = 100f;
using var clockBgPaint = new SKPaint { Color = new SKColor(245, 245, 245), Style = SKPaintStyle.Fill, IsAntialias = true };
canvas.DrawCircle(clockCenterX, clockCenterY, clockRadius + 20, clockBgPaint);
// Hour numbers
using var numFont = new SKFont(SKTypeface.Default, 14);
using var numPaint = new SKPaint(numFont) { Color = SKColors.Black, IsAntialias = true };
for (int i = 1; i <= 12; i++)
{
var angle = (i * 30 - 90) * Math.PI / 180;
var x = clockCenterX + (float)(clockRadius * Math.Cos(angle));
var y = clockCenterY + (float)(clockRadius * Math.Sin(angle));
var numText = i.ToString();
var numBounds = new SKRect();
numPaint.MeasureText(numText, ref numBounds);
canvas.DrawText(numText, x - numBounds.MidX, y - numBounds.MidY, numPaint);
}
// Clock hand
var selectedHour = time.Hours % 12;
if (selectedHour == 0) selectedHour = 12;
var handAngle = (selectedHour * 30 - 90) * Math.PI / 180;
var handEndX = clockCenterX + (float)((clockRadius - 20) * Math.Cos(handAngle));
var handEndY = clockCenterY + (float)((clockRadius - 20) * Math.Sin(handAngle));
using var handPaint = new SKPaint { Color = new SKColor(33, 150, 243), Style = SKPaintStyle.Stroke, StrokeWidth = 2, IsAntialias = true };
canvas.DrawLine(clockCenterX, clockCenterY, handEndX, handEndY, handPaint);
// Center dot
handPaint.Style = SKPaintStyle.Fill;
canvas.DrawCircle(clockCenterX, clockCenterY, 6, handPaint);
// Selected hour highlight
using var selPaint = new SKPaint { Color = new SKColor(33, 150, 243), Style = SKPaintStyle.Fill, IsAntialias = true };
var selX = clockCenterX + (float)(clockRadius * Math.Cos(handAngle));
var selY = clockCenterY + (float)(clockRadius * Math.Sin(handAngle));
canvas.DrawCircle(selX, selY, 18, selPaint);
numPaint.Color = SKColors.White;
var selText = selectedHour.ToString();
var selBounds = new SKRect();
numPaint.MeasureText(selText, ref selBounds);
canvas.DrawText(selText, selX - selBounds.MidX, selY - selBounds.MidY, numPaint);
}
private void DrawPickerDropdown(SKCanvas canvas, SKRect pickerBounds)
{
var items = new[] { "Apple", "Banana", "Cherry", "Date", "Elderberry", "Fig", "Grape" };
var itemHeight = 40f;
var dropdownHeight = items.Length * itemHeight;
var dropdownRect = new SKRect(
pickerBounds.Left, pickerBounds.Bottom + 4,
pickerBounds.Right, pickerBounds.Bottom + 4 + dropdownHeight);
// Shadow
using var shadowPaint = new SKPaint {
Color = new SKColor(0, 0, 0, 50),
MaskFilter = SKMaskFilter.CreateBlur(SKBlurStyle.Normal, 6)
};
canvas.DrawRoundRect(new SKRoundRect(
new SKRect(dropdownRect.Left + 3, dropdownRect.Top + 3, dropdownRect.Right + 3, dropdownRect.Bottom + 3), 4), shadowPaint);
// Background
using var bgPaint = new SKPaint { Color = SKColors.White, Style = SKPaintStyle.Fill, IsAntialias = true };
canvas.DrawRoundRect(new SKRoundRect(dropdownRect, 4), bgPaint);
// Border
using var borderPaint = new SKPaint { Color = new SKColor(200, 200, 200), Style = SKPaintStyle.Stroke, StrokeWidth = 1 };
canvas.DrawRoundRect(new SKRoundRect(dropdownRect, 4), borderPaint);
// Items
using var itemFont = new SKFont(SKTypeface.Default, 14);
using var itemPaint = new SKPaint(itemFont) { Color = SKColors.Black, IsAntialias = true };
using var selBgPaint = new SKPaint { Color = new SKColor(33, 150, 243, 40), Style = SKPaintStyle.Fill };
for (int i = 0; i < items.Length; i++)
{
var itemTop = dropdownRect.Top + i * itemHeight;
var itemRect = new SKRect(dropdownRect.Left, itemTop, dropdownRect.Right, itemTop + itemHeight);
if (i == _picker.SelectedIndex)
{
canvas.DrawRect(itemRect, selBgPaint);
}
var textBounds = new SKRect();
itemPaint.MeasureText(items[i], ref textBounds);
canvas.DrawText(items[i], itemRect.Left + 12, itemRect.MidY - textBounds.MidY, itemPaint);
}
}
private Key KeysymToKey(ulong keysym)
{
return keysym switch
{
0xFF08 => Key.Backspace,
0xFF09 => Key.Tab,
0xFF0D => Key.Enter,
0xFF1B => Key.Escape,
0xFFFF => Key.Delete,
0xFF50 => Key.Home,
0xFF51 => Key.Left,
0xFF52 => Key.Up,
0xFF53 => Key.Right,
0xFF54 => Key.Down,
0xFF55 => Key.PageUp,
0xFF56 => Key.PageDown,
0xFF57 => Key.End,
0x0020 => Key.Space,
_ => Key.Unknown
};
}
private char KeysymToChar(ulong keysym, uint state)
{
bool shift = (state & 1) != 0; // ShiftMask
bool capsLock = (state & 2) != 0; // LockMask
// Letters a-z / A-Z
if (keysym >= 0x61 && keysym <= 0x7A) // a-z
{
char ch = (char)keysym;
if (shift ^ capsLock) ch = char.ToUpper(ch);
return ch;
}
// Numbers and symbols
if (keysym >= 0x20 && keysym <= 0x7E)
{
if (shift)
{
return keysym switch
{
0x31 => '!', 0x32 => '@', 0x33 => '#', 0x34 => '$', 0x35 => '%',
0x36 => '^', 0x37 => '&', 0x38 => '*', 0x39 => '(', 0x30 => ')',
0x2D => '_', 0x3D => '+', 0x5B => '{', 0x5D => '}', 0x5C => '|',
0x3B => ':', 0x27 => '"', 0x60 => '~', 0x2C => '<', 0x2E => '>',
0x2F => '?',
_ => (char)keysym
};
}
return (char)keysym;
}
// Numpad
if (keysym >= 0xFFB0 && keysym <= 0xFFB9)
return (char)('0' + (keysym - 0xFFB0));
return '\0';
}
private void Cleanup()
{
if (_pixelBuffer != IntPtr.Zero) Marshal.FreeHGlobal(_pixelBuffer);
if (_gc != IntPtr.Zero) XFreeGC(_display, _gc);
if (_window != IntPtr.Zero) XDestroyWindow(_display, _window);
if (_display != IntPtr.Zero) XCloseDisplay(_display);
}
const string LibX11 = "libX11.so.6";
[DllImport(LibX11)] static extern IntPtr XOpenDisplay(IntPtr d);
[DllImport(LibX11)] static extern int XCloseDisplay(IntPtr d);
[DllImport(LibX11)] static extern int XDefaultScreen(IntPtr d);
[DllImport(LibX11)] static extern IntPtr XRootWindow(IntPtr d, int s);
[DllImport(LibX11)] static extern ulong XBlackPixel(IntPtr d, int s);
[DllImport(LibX11)] static extern ulong XWhitePixel(IntPtr d, int s);
[DllImport(LibX11)] static extern IntPtr XCreateSimpleWindow(IntPtr d, IntPtr p, int x, int y, uint w, uint h, uint bw, ulong b, ulong bg);
[DllImport(LibX11)] static extern int XMapWindow(IntPtr d, IntPtr w);
[DllImport(LibX11)] static extern int XStoreName(IntPtr d, IntPtr w, string n);
[DllImport(LibX11)] static extern int XSelectInput(IntPtr d, IntPtr w, long m);
[DllImport(LibX11)] static extern IntPtr XCreateGC(IntPtr d, IntPtr dr, ulong vm, IntPtr v);
[DllImport(LibX11)] static extern int XFreeGC(IntPtr d, IntPtr gc);
[DllImport(LibX11)] static extern int XFlush(IntPtr d);
[DllImport(LibX11)] static extern int XPending(IntPtr d);
[DllImport(LibX11)] static extern int XNextEvent(IntPtr d, out XEvent e);
[DllImport(LibX11)] static extern ulong XLookupKeysym(ref XKeyEvent k, int i);
[DllImport(LibX11)] static extern int XDestroyWindow(IntPtr d, IntPtr w);
[DllImport(LibX11)] static extern IntPtr XDefaultVisual(IntPtr d, int s);
[DllImport(LibX11)] static extern int XDefaultDepth(IntPtr d, int s);
[DllImport(LibX11)] static extern IntPtr XCreateImage(IntPtr d, IntPtr v, uint dp, int f, int o, IntPtr data, uint w, uint h, int bp, int bpl);
[DllImport(LibX11)] static extern int XPutImage(IntPtr d, IntPtr dr, IntPtr gc, IntPtr i, int sx, int sy, int dx, int dy, uint w, uint h);
[DllImport(LibX11)] static extern int XFree(IntPtr data);
[DllImport(LibX11)] static extern IntPtr XInternAtom(IntPtr d, string n, bool o);
[DllImport(LibX11)] static extern int XSetWMProtocols(IntPtr d, IntPtr w, ref IntPtr p, int c);
const long ExposureMask = 1L<<15, KeyPressMask = 1L<<0, KeyReleaseMask = 1L<<1;
const long ButtonPressMask = 1L<<2, ButtonReleaseMask = 1L<<3, PointerMotionMask = 1L<<6, StructureNotifyMask = 1L<<17;
const int KeyPress = 2, ButtonPress = 4, ButtonRelease = 5, MotionNotify = 6, Expose = 12, ConfigureNotify = 22, ClientMessage = 33;
[StructLayout(LayoutKind.Explicit, Size = 192)] struct XEvent {
[FieldOffset(0)] public int type; [FieldOffset(0)] public XExposeEvent xexpose;
[FieldOffset(0)] public XConfigureEvent xconfigure; [FieldOffset(0)] public XKeyEvent xkey;
[FieldOffset(0)] public XButtonEvent xbutton; [FieldOffset(0)] public XMotionEvent xmotion;
[FieldOffset(0)] public XClientMessageEvent xclient;
}
[StructLayout(LayoutKind.Sequential)] struct XExposeEvent { public int type; public ulong serial; public int send_event; public IntPtr display, window; public int x, y, width, height, count; }
[StructLayout(LayoutKind.Sequential)] struct XConfigureEvent { public int type; public ulong serial; public int send_event; public IntPtr display, evt, window; public int x, y, width, height, border_width; public IntPtr above; public int override_redirect; }
[StructLayout(LayoutKind.Sequential)] struct XKeyEvent { public int type; public ulong serial; public int send_event; public IntPtr display, window, root, subwindow; public ulong time; public int x, y, x_root, y_root; public uint state, keycode; public int same_screen; }
[StructLayout(LayoutKind.Sequential)] struct XButtonEvent { public int type; public ulong serial; public int send_event; public IntPtr display, window, root, subwindow; public ulong time; public int x, y, x_root, y_root; public uint state, button; public int same_screen; }
[StructLayout(LayoutKind.Sequential)] struct XMotionEvent { public int type; public ulong serial; public int send_event; public IntPtr display, window, root, subwindow; public ulong time; public int x, y, x_root, y_root; public uint state; public byte is_hint; public int same_screen; }
[StructLayout(LayoutKind.Sequential)] struct XClientMessageEvent { public int type; public ulong serial; public int send_event; public IntPtr display, window, message_type; public int format; public long data_l0, data_l1, data_l2, data_l3, data_l4; }
}

78
samples/ShellDemo/App.cs Normal file
View File

@@ -0,0 +1,78 @@
// ShellDemo App - Comprehensive Control Demo
using Microsoft.Maui.Controls;
namespace ShellDemo;
/// <summary>
/// Main application class with Shell navigation.
/// </summary>
public class App : Application
{
public App()
{
MainPage = new AppShell();
}
}
/// <summary>
/// Shell definition with flyout menu - comprehensive control demo.
/// </summary>
public class AppShell : Shell
{
public AppShell()
{
FlyoutBehavior = FlyoutBehavior.Flyout;
Title = "OpenMaui Controls Demo";
// Register routes for push navigation (pages not in flyout)
Routing.RegisterRoute("detail", typeof(DetailPage));
// Home
Items.Add(CreateFlyoutItem("Home", typeof(HomePage)));
// Buttons Demo
Items.Add(CreateFlyoutItem("Buttons", typeof(ButtonsPage)));
// Text Input Demo
Items.Add(CreateFlyoutItem("Text Input", typeof(TextInputPage)));
// Selection Controls Demo
Items.Add(CreateFlyoutItem("Selection", typeof(SelectionPage)));
// Pickers Demo
Items.Add(CreateFlyoutItem("Pickers", typeof(PickersPage)));
// Lists Demo
Items.Add(CreateFlyoutItem("Lists", typeof(ListsPage)));
// Progress Demo
Items.Add(CreateFlyoutItem("Progress", typeof(ProgressPage)));
// Grids Demo
Items.Add(CreateFlyoutItem("Grids", typeof(GridsPage)));
// About
Items.Add(CreateFlyoutItem("About", typeof(AboutPage)));
}
private FlyoutItem CreateFlyoutItem(string title, Type pageType)
{
// Route is required for Shell.GoToAsync navigation to work
var route = title.Replace(" ", "");
return new FlyoutItem
{
Title = title,
Route = route,
Items =
{
new ShellContent
{
Title = title,
Route = route,
ContentTemplate = new DataTemplate(pageType)
}
}
};
}
}

View File

@@ -0,0 +1,24 @@
// MauiProgram.cs - Shared MAUI app configuration
// Works across all platforms (iOS, Android, Windows, Linux)
using Microsoft.Maui.Hosting;
using Microsoft.Maui.Platform.Linux.Hosting;
namespace ShellDemo;
public static class MauiProgram
{
public static MauiApp CreateMauiApp()
{
var builder = MauiApp.CreateBuilder();
// Configure the app (shared across all platforms)
builder.UseMauiApp<App>();
// Add Linux platform support
// On other platforms, this would be iOS/Android/Windows specific
builder.UseLinux();
return builder.Build();
}
}

View File

@@ -0,0 +1,115 @@
// AboutPage - Information about OpenMaui Linux
using Microsoft.Maui.Controls;
using Microsoft.Maui.Graphics;
namespace ShellDemo;
public class AboutPage : ContentPage
{
public AboutPage()
{
Title = "About";
Content = new ScrollView
{
Content = new VerticalStackLayout
{
Padding = new Thickness(20),
Spacing = 20,
Children =
{
new Label
{
Text = "OpenMaui Linux",
FontSize = 32,
FontAttributes = FontAttributes.Bold,
TextColor = Color.FromArgb("#1A237E"),
HorizontalOptions = LayoutOptions.Center
},
new Label
{
Text = "Version 1.0.0",
FontSize = 16,
TextColor = Colors.Gray,
HorizontalOptions = LayoutOptions.Center
},
new BoxView { HeightRequest = 1, Color = Colors.LightGray },
new Label
{
Text = "OpenMaui Linux brings .NET MAUI to Linux desktops using SkiaSharp for rendering. " +
"It provides a native Linux experience while maintaining compatibility with MAUI's cross-platform API.",
FontSize = 14,
LineBreakMode = LineBreakMode.WordWrap
},
CreateInfoCard("Platform", "Linux (X11/Wayland)"),
CreateInfoCard("Rendering", "SkiaSharp"),
CreateInfoCard("Framework", ".NET MAUI"),
CreateInfoCard("License", "MIT License"),
new BoxView { HeightRequest = 1, Color = Colors.LightGray },
new Label
{
Text = "Features",
FontSize = 20,
FontAttributes = FontAttributes.Bold
},
CreateFeatureItem("Full XAML support with styles and resources"),
CreateFeatureItem("Shell navigation with flyout menus"),
CreateFeatureItem("All standard MAUI controls"),
CreateFeatureItem("Data binding and MVVM"),
CreateFeatureItem("Keyboard and mouse input"),
CreateFeatureItem("High DPI support"),
new BoxView { HeightRequest = 1, Color = Colors.LightGray },
new Label
{
Text = "https://github.com/pablotoledo/OpenMaui-Linux",
FontSize = 12,
TextColor = Colors.Blue,
HorizontalOptions = LayoutOptions.Center
}
}
}
};
}
private Frame CreateInfoCard(string label, string value)
{
return new Frame
{
CornerRadius = 8,
Padding = new Thickness(15),
BackgroundColor = Color.FromArgb("#F5F5F5"),
HasShadow = false,
Content = new HorizontalStackLayout
{
Children =
{
new Label
{
Text = label + ":",
FontAttributes = FontAttributes.Bold,
WidthRequest = 100
},
new Label
{
Text = value,
TextColor = Colors.Gray
}
}
}
};
}
private View CreateFeatureItem(string text)
{
return new HorizontalStackLayout
{
Spacing = 10,
Children =
{
new Label { Text = "✓", TextColor = Color.FromArgb("#4CAF50"), FontSize = 16 },
new Label { Text = text, FontSize = 14 }
}
};
}
}

View File

@@ -0,0 +1,229 @@
// ButtonsPage - Comprehensive Button Control Demo
using Microsoft.Maui.Controls;
using Microsoft.Maui.Graphics;
namespace ShellDemo;
public class ButtonsPage : ContentPage
{
private readonly Label _eventLog;
private int _eventCount = 0;
public ButtonsPage()
{
Title = "Buttons Demo";
_eventLog = new Label
{
Text = "Events will appear here...",
FontSize = 11,
TextColor = Colors.Gray,
LineBreakMode = LineBreakMode.WordWrap
};
Content = new Grid
{
RowDefinitions =
{
new RowDefinition { Height = new GridLength(1, GridUnitType.Star) },
new RowDefinition { Height = new GridLength(120) }
},
Children =
{
CreateMainContent(),
CreateEventLogPanel()
}
};
Grid.SetRow((View)((Grid)Content).Children[0], 0);
Grid.SetRow((View)((Grid)Content).Children[1], 1);
}
private View CreateMainContent()
{
return new ScrollView
{
Content = new VerticalStackLayout
{
Padding = new Thickness(20),
Spacing = 20,
Children =
{
new Label { Text = "Button Styles & Events", FontSize = 24, FontAttributes = FontAttributes.Bold },
// Basic Buttons
CreateSection("Basic Buttons", CreateBasicButtons()),
// Styled Buttons
CreateSection("Styled Buttons", CreateStyledButtons()),
// Button States
CreateSection("Button States", CreateButtonStates()),
// Button with Icons (text simulation)
CreateSection("Button Variations", CreateButtonVariations())
}
}
};
}
private View CreateBasicButtons()
{
var layout = new VerticalStackLayout { Spacing = 10 };
var defaultBtn = new Button { Text = "Default Button" };
defaultBtn.Clicked += (s, e) => LogEvent("Default Button clicked");
defaultBtn.Pressed += (s, e) => LogEvent("Default Button pressed");
defaultBtn.Released += (s, e) => LogEvent("Default Button released");
var textBtn = new Button { Text = "Text Only", BackgroundColor = Colors.Transparent, TextColor = Colors.Blue };
textBtn.Clicked += (s, e) => LogEvent("Text Button clicked");
layout.Children.Add(defaultBtn);
layout.Children.Add(textBtn);
return layout;
}
private View CreateStyledButtons()
{
var layout = new HorizontalStackLayout { Spacing = 10 };
var colors = new[]
{
("#2196F3", "Primary"),
("#4CAF50", "Success"),
("#FF9800", "Warning"),
("#F44336", "Danger"),
("#9C27B0", "Purple")
};
foreach (var (color, name) in colors)
{
var btn = new Button
{
Text = name,
BackgroundColor = Color.FromArgb(color),
TextColor = Colors.White,
CornerRadius = 5
};
btn.Clicked += (s, e) => LogEvent($"{name} button clicked");
layout.Children.Add(btn);
}
return layout;
}
private View CreateButtonStates()
{
var layout = new VerticalStackLayout { Spacing = 10 };
var enabledBtn = new Button { Text = "Enabled Button", IsEnabled = true };
enabledBtn.Clicked += (s, e) => LogEvent("Enabled button clicked");
var disabledBtn = new Button { Text = "Disabled Button", IsEnabled = false };
var toggleBtn = new Button { Text = "Toggle Above Button" };
toggleBtn.Clicked += (s, e) =>
{
disabledBtn.IsEnabled = !disabledBtn.IsEnabled;
disabledBtn.Text = disabledBtn.IsEnabled ? "Now Enabled!" : "Disabled Button";
LogEvent($"Toggled button to: {(disabledBtn.IsEnabled ? "Enabled" : "Disabled")}");
};
layout.Children.Add(enabledBtn);
layout.Children.Add(disabledBtn);
layout.Children.Add(toggleBtn);
return layout;
}
private View CreateButtonVariations()
{
var layout = new VerticalStackLayout { Spacing = 10 };
var wideBtn = new Button
{
Text = "Wide Button",
HorizontalOptions = LayoutOptions.Fill,
BackgroundColor = Color.FromArgb("#673AB7"),
TextColor = Colors.White
};
wideBtn.Clicked += (s, e) => LogEvent("Wide button clicked");
var tallBtn = new Button
{
Text = "Tall Button",
HeightRequest = 60,
BackgroundColor = Color.FromArgb("#009688"),
TextColor = Colors.White
};
tallBtn.Clicked += (s, e) => LogEvent("Tall button clicked");
var roundBtn = new Button
{
Text = "Round",
WidthRequest = 80,
HeightRequest = 80,
CornerRadius = 40,
BackgroundColor = Color.FromArgb("#E91E63"),
TextColor = Colors.White
};
roundBtn.Clicked += (s, e) => LogEvent("Round button clicked");
layout.Children.Add(wideBtn);
layout.Children.Add(tallBtn);
layout.Children.Add(new HorizontalStackLayout { Children = { roundBtn } });
return layout;
}
private Frame CreateSection(string title, View content)
{
return new Frame
{
CornerRadius = 8,
Padding = new Thickness(15),
BackgroundColor = Colors.White,
Content = new VerticalStackLayout
{
Spacing = 10,
Children =
{
new Label { Text = title, FontSize = 16, FontAttributes = FontAttributes.Bold },
content
}
}
};
}
private View CreateEventLogPanel()
{
return new Frame
{
BackgroundColor = Color.FromArgb("#F5F5F5"),
Padding = new Thickness(10),
CornerRadius = 0,
Content = new VerticalStackLayout
{
Children =
{
new Label { Text = "Event Log:", FontSize = 12, FontAttributes = FontAttributes.Bold },
new ScrollView
{
HeightRequest = 80,
Content = _eventLog
}
}
}
};
}
private void LogEvent(string message)
{
_eventCount++;
var timestamp = DateTime.Now.ToString("HH:mm:ss");
_eventLog.Text = $"[{timestamp}] {_eventCount}. {message}\n{_eventLog.Text}";
}
}

View File

@@ -0,0 +1,203 @@
// ControlsPage - Demonstrates various MAUI controls
using Microsoft.Maui.Controls;
using Microsoft.Maui.Graphics;
namespace ShellDemo;
public class ControlsPage : ContentPage
{
public ControlsPage()
{
Title = "Controls";
Content = new ScrollView
{
Content = new VerticalStackLayout
{
Padding = new Thickness(20),
Spacing = 15,
Children =
{
new Label
{
Text = "Control Gallery",
FontSize = 24,
FontAttributes = FontAttributes.Bold
},
// Buttons
CreateSection("Buttons", new View[]
{
CreateButtonRow()
}),
// CheckBox & Switch
CreateSection("Selection", new View[]
{
CreateCheckBoxRow(),
CreateSwitchRow()
}),
// Slider
CreateSection("Slider", new View[]
{
CreateSliderRow()
}),
// Picker
CreateSection("Picker", new View[]
{
CreatePickerRow()
}),
// Progress
CreateSection("Progress", new View[]
{
CreateProgressRow()
})
}
}
};
}
private Frame CreateSection(string title, View[] content)
{
var layout = new VerticalStackLayout { Spacing = 10 };
layout.Children.Add(new Label
{
Text = title,
FontSize = 18,
FontAttributes = FontAttributes.Bold
});
foreach (var view in content)
{
layout.Children.Add(view);
}
return new Frame
{
CornerRadius = 8,
Padding = new Thickness(15),
BackgroundColor = Colors.White,
Content = layout
};
}
private View CreateButtonRow()
{
var resultLabel = new Label { TextColor = Colors.Gray, FontSize = 12 };
var layout = new VerticalStackLayout { Spacing = 10 };
var buttonRow = new HorizontalStackLayout { Spacing = 10 };
var primaryBtn = new Button { Text = "Primary", BackgroundColor = Color.FromArgb("#2196F3"), TextColor = Colors.White };
primaryBtn.Clicked += (s, e) => resultLabel.Text = "Primary clicked!";
var successBtn = new Button { Text = "Success", BackgroundColor = Color.FromArgb("#4CAF50"), TextColor = Colors.White };
successBtn.Clicked += (s, e) => resultLabel.Text = "Success clicked!";
var dangerBtn = new Button { Text = "Danger", BackgroundColor = Color.FromArgb("#F44336"), TextColor = Colors.White };
dangerBtn.Clicked += (s, e) => resultLabel.Text = "Danger clicked!";
buttonRow.Children.Add(primaryBtn);
buttonRow.Children.Add(successBtn);
buttonRow.Children.Add(dangerBtn);
layout.Children.Add(buttonRow);
layout.Children.Add(resultLabel);
return layout;
}
private View CreateCheckBoxRow()
{
var layout = new HorizontalStackLayout { Spacing = 20 };
var cb1 = new CheckBox { IsChecked = true };
var cb2 = new CheckBox { IsChecked = false };
layout.Children.Add(cb1);
layout.Children.Add(new Label { Text = "Option 1", VerticalOptions = LayoutOptions.Center });
layout.Children.Add(cb2);
layout.Children.Add(new Label { Text = "Option 2", VerticalOptions = LayoutOptions.Center });
return layout;
}
private View CreateSwitchRow()
{
var label = new Label { Text = "Off", VerticalOptions = LayoutOptions.Center };
var sw = new Switch { IsToggled = false };
sw.Toggled += (s, e) => label.Text = e.Value ? "On" : "Off";
return new HorizontalStackLayout
{
Spacing = 10,
Children = { sw, label }
};
}
private View CreateSliderRow()
{
var label = new Label { Text = "Value: 50" };
var slider = new Slider { Minimum = 0, Maximum = 100, Value = 50 };
slider.ValueChanged += (s, e) => label.Text = $"Value: {(int)e.NewValue}";
return new VerticalStackLayout
{
Spacing = 5,
Children = { slider, label }
};
}
private View CreatePickerRow()
{
var label = new Label { Text = "Selected: (none)", TextColor = Colors.Gray };
var picker = new Picker { Title = "Select a fruit" };
picker.Items.Add("Apple");
picker.Items.Add("Banana");
picker.Items.Add("Cherry");
picker.Items.Add("Date");
picker.Items.Add("Elderberry");
picker.SelectedIndexChanged += (s, e) =>
{
if (picker.SelectedIndex >= 0)
label.Text = $"Selected: {picker.Items[picker.SelectedIndex]}";
};
return new VerticalStackLayout
{
Spacing = 5,
Children = { picker, label }
};
}
private View CreateProgressRow()
{
var progress = new ProgressBar { Progress = 0.7 };
var activity = new ActivityIndicator { IsRunning = true };
return new VerticalStackLayout
{
Spacing = 10,
Children =
{
progress,
new Label { Text = "70% Complete", FontSize = 12, TextColor = Colors.Gray },
new HorizontalStackLayout
{
Spacing = 10,
Children =
{
activity,
new Label { Text = "Loading...", VerticalOptions = LayoutOptions.Center, TextColor = Colors.Gray }
}
}
}
};
}
}

View File

@@ -0,0 +1,123 @@
// DetailPage - Demonstrates push/pop navigation
using Microsoft.Maui.Controls;
using Microsoft.Maui.Graphics;
using Microsoft.Maui.Platform.Linux.Hosting;
namespace ShellDemo;
/// <summary>
/// A detail page that can be pushed onto the navigation stack.
/// </summary>
public class DetailPage : ContentPage
{
private readonly string _itemName;
public DetailPage() : this("Detail Item")
{
}
public DetailPage(string itemName)
{
_itemName = itemName;
Title = "Detail Page";
Content = new VerticalStackLayout
{
Padding = new Thickness(30),
Spacing = 20,
VerticalOptions = LayoutOptions.Center,
Children =
{
new Label
{
Text = "Pushed Page",
FontSize = 28,
FontAttributes = FontAttributes.Bold,
HorizontalOptions = LayoutOptions.Center,
TextColor = Color.FromArgb("#9C27B0")
},
new Label
{
Text = $"You navigated to: {_itemName}",
FontSize = 16,
HorizontalOptions = LayoutOptions.Center
},
new Label
{
Text = "This page was pushed onto the navigation stack using Shell.Current.GoToAsync()",
FontSize = 14,
TextColor = Colors.Gray,
HorizontalTextAlignment = TextAlignment.Center,
LineBreakMode = LineBreakMode.WordWrap
},
new BoxView
{
HeightRequest = 2,
Color = Color.FromArgb("#E0E0E0"),
Margin = new Thickness(0, 20)
},
CreateBackButton(),
new Label
{
Text = "Use the back button above or the hardware/gesture back to pop this page",
FontSize = 12,
TextColor = Colors.Gray,
HorizontalTextAlignment = TextAlignment.Center,
Margin = new Thickness(0, 20, 0, 0)
}
}
};
}
private Button CreateBackButton()
{
var backBtn = new Button
{
Text = "Go Back (Pop)",
BackgroundColor = Color.FromArgb("#9C27B0"),
TextColor = Colors.White,
HorizontalOptions = LayoutOptions.Center,
Padding = new Thickness(30, 10)
};
backBtn.Clicked += (s, e) =>
{
// Pop this page off the navigation stack using LinuxViewRenderer
Console.WriteLine("[DetailPage] Go Back clicked");
var success = LinuxViewRenderer.PopPage();
Console.WriteLine($"[DetailPage] PopPage result: {success}");
};
return backBtn;
}
}
/// <summary>
/// Query property for passing data to DetailPage.
/// </summary>
[QueryProperty(nameof(ItemName), "item")]
public class DetailPageWithQuery : DetailPage
{
private string _itemName = "Item";
public string ItemName
{
get => _itemName;
set
{
_itemName = value;
// Update the title when the property is set
Title = $"Detail: {value}";
}
}
public DetailPageWithQuery() : base()
{
}
}

View File

@@ -0,0 +1,594 @@
// GridsPage - Demonstrates Grid layouts with various options
using Microsoft.Maui.Controls;
using Microsoft.Maui.Graphics;
namespace ShellDemo;
public class GridsPage : ContentPage
{
public GridsPage()
{
Title = "Grids";
Content = new ScrollView
{
Orientation = ScrollOrientation.Both,
Content = new VerticalStackLayout
{
Spacing = 25,
Children =
{
CreateSectionHeader("Basic Grid (2x2)"),
CreateBasicGrid(),
CreateSectionHeader("Column Definitions"),
CreateColumnDefinitionsDemo(),
CreateSectionHeader("Row Definitions"),
CreateRowDefinitionsDemo(),
CreateSectionHeader("Auto Rows (Empty vs Content)"),
CreateAutoRowsDemo(),
CreateSectionHeader("Star Sizing (Proportional)"),
CreateStarSizingDemo(),
CreateSectionHeader("Row & Column Spacing"),
CreateSpacingDemo(),
CreateSectionHeader("Row & Column Span"),
CreateSpanDemo(),
CreateSectionHeader("Mixed Sizing"),
CreateMixedSizingDemo(),
CreateSectionHeader("Nested Grids"),
CreateNestedGridDemo(),
new BoxView { HeightRequest = 20 } // Bottom padding
}
}
};
}
private Label CreateSectionHeader(string text)
{
return new Label
{
Text = text,
FontSize = 18,
FontAttributes = FontAttributes.Bold,
TextColor = Color.FromArgb("#2196F3"),
Margin = new Thickness(0, 10, 0, 5)
};
}
private View CreateBasicGrid()
{
var grid = new Grid
{
RowDefinitions =
{
new RowDefinition { Height = GridLength.Auto },
new RowDefinition { Height = GridLength.Auto }
},
ColumnDefinitions =
{
new ColumnDefinition { Width = GridLength.Star },
new ColumnDefinition { Width = GridLength.Star }
},
BackgroundColor = Color.FromArgb("#F5F5F5")
};
var cell1 = CreateCell("Row 0, Col 0", "#E3F2FD");
var cell2 = CreateCell("Row 0, Col 1", "#E8F5E9");
var cell3 = CreateCell("Row 1, Col 0", "#FFF3E0");
var cell4 = CreateCell("Row 1, Col 1", "#FCE4EC");
Grid.SetRow(cell1, 0); Grid.SetColumn(cell1, 0);
Grid.SetRow(cell2, 0); Grid.SetColumn(cell2, 1);
Grid.SetRow(cell3, 1); Grid.SetColumn(cell3, 0);
Grid.SetRow(cell4, 1); Grid.SetColumn(cell4, 1);
grid.Children.Add(cell1);
grid.Children.Add(cell2);
grid.Children.Add(cell3);
grid.Children.Add(cell4);
return CreateDemoContainer(grid, "Equal columns using Star sizing");
}
private View CreateColumnDefinitionsDemo()
{
var stack = new VerticalStackLayout { Spacing = 15 };
// Auto width columns
var autoGrid = new Grid
{
ColumnDefinitions =
{
new ColumnDefinition { Width = GridLength.Auto },
new ColumnDefinition { Width = GridLength.Auto },
new ColumnDefinition { Width = GridLength.Auto }
},
BackgroundColor = Color.FromArgb("#F5F5F5")
};
var a1 = CreateCell("Auto", "#BBDEFB");
var a2 = CreateCell("Auto Width", "#C8E6C9");
var a3 = CreateCell("A", "#FFECB3");
Grid.SetColumn(a1, 0);
Grid.SetColumn(a2, 1);
Grid.SetColumn(a3, 2);
autoGrid.Children.Add(a1);
autoGrid.Children.Add(a2);
autoGrid.Children.Add(a3);
stack.Children.Add(new Label { Text = "Auto: Sizes to content", FontSize = 12, TextColor = Colors.Gray });
stack.Children.Add(autoGrid);
// Absolute width columns
var absoluteGrid = new Grid
{
ColumnDefinitions =
{
new ColumnDefinition { Width = new GridLength(50) },
new ColumnDefinition { Width = new GridLength(100) },
new ColumnDefinition { Width = new GridLength(150) }
},
BackgroundColor = Color.FromArgb("#F5F5F5")
};
var b1 = CreateCell("50px", "#BBDEFB");
var b2 = CreateCell("100px", "#C8E6C9");
var b3 = CreateCell("150px", "#FFECB3");
Grid.SetColumn(b1, 0);
Grid.SetColumn(b2, 1);
Grid.SetColumn(b3, 2);
absoluteGrid.Children.Add(b1);
absoluteGrid.Children.Add(b2);
absoluteGrid.Children.Add(b3);
stack.Children.Add(new Label { Text = "Absolute: Fixed pixel widths (50, 100, 150)", FontSize = 12, TextColor = Colors.Gray, Margin = new Thickness(0, 10, 0, 0) });
stack.Children.Add(absoluteGrid);
return stack;
}
private View CreateRowDefinitionsDemo()
{
var grid = new Grid
{
WidthRequest = 200,
RowDefinitions =
{
new RowDefinition { Height = new GridLength(30) },
new RowDefinition { Height = new GridLength(50) },
new RowDefinition { Height = GridLength.Auto },
new RowDefinition { Height = new GridLength(40) }
},
ColumnDefinitions =
{
new ColumnDefinition { Width = GridLength.Star }
},
BackgroundColor = Color.FromArgb("#F5F5F5")
};
var r1 = CreateCell("30px height", "#BBDEFB");
var r2 = CreateCell("50px height", "#C8E6C9");
var r3 = CreateCell("Auto height\n(fits content)", "#FFECB3");
var r4 = CreateCell("40px height", "#F8BBD9");
Grid.SetRow(r1, 0);
Grid.SetRow(r2, 1);
Grid.SetRow(r3, 2);
Grid.SetRow(r4, 3);
grid.Children.Add(r1);
grid.Children.Add(r2);
grid.Children.Add(r3);
grid.Children.Add(r4);
return CreateDemoContainer(grid, "Different row heights: 30px, 50px, Auto, 40px");
}
private View CreateAutoRowsDemo()
{
var stack = new VerticalStackLayout { Spacing = 15 };
// Grid with empty Auto row
var emptyAutoGrid = new Grid
{
WidthRequest = 250,
RowDefinitions =
{
new RowDefinition { Height = new GridLength(40) },
new RowDefinition { Height = GridLength.Auto }, // Empty - should collapse
new RowDefinition { Height = new GridLength(40) }
},
ColumnDefinitions =
{
new ColumnDefinition { Width = GridLength.Star }
},
BackgroundColor = Color.FromArgb("#E0E0E0")
};
var r1 = CreateCell("Row 0: 40px", "#BBDEFB");
// Row 1 is Auto with NO content - should be 0 height
var r3 = CreateCell("Row 2: 40px", "#C8E6C9");
Grid.SetRow(r1, 0);
Grid.SetRow(r3, 2); // Skip row 1
emptyAutoGrid.Children.Add(r1);
emptyAutoGrid.Children.Add(r3);
stack.Children.Add(new Label { Text = "Empty Auto row (Row 1) should collapse to 0 height:", FontSize = 12, TextColor = Colors.Gray });
stack.Children.Add(emptyAutoGrid);
// Grid with Auto row that has content
var contentAutoGrid = new Grid
{
WidthRequest = 250,
RowDefinitions =
{
new RowDefinition { Height = new GridLength(40) },
new RowDefinition { Height = GridLength.Auto }, // Has content
new RowDefinition { Height = new GridLength(40) }
},
ColumnDefinitions =
{
new ColumnDefinition { Width = GridLength.Star }
},
BackgroundColor = Color.FromArgb("#E0E0E0")
};
var c1 = CreateCell("Row 0: 40px", "#BBDEFB");
var c2 = CreateCell("Row 1: Auto (sized to this content)", "#FFECB3");
var c3 = CreateCell("Row 2: 40px", "#C8E6C9");
Grid.SetRow(c1, 0);
Grid.SetRow(c2, 1);
Grid.SetRow(c3, 2);
contentAutoGrid.Children.Add(c1);
contentAutoGrid.Children.Add(c2);
contentAutoGrid.Children.Add(c3);
stack.Children.Add(new Label { Text = "Auto row with content sizes to fit:", FontSize = 12, TextColor = Colors.Gray, Margin = new Thickness(0, 10, 0, 0) });
stack.Children.Add(contentAutoGrid);
return stack;
}
private View CreateStarSizingDemo()
{
var grid = new Grid
{
ColumnDefinitions =
{
new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) },
new ColumnDefinition { Width = new GridLength(2, GridUnitType.Star) },
new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) }
},
BackgroundColor = Color.FromArgb("#F5F5F5")
};
var s1 = CreateCell("1*", "#BBDEFB");
var s2 = CreateCell("2* (double)", "#C8E6C9");
var s3 = CreateCell("1*", "#FFECB3");
Grid.SetColumn(s1, 0);
Grid.SetColumn(s2, 1);
Grid.SetColumn(s3, 2);
grid.Children.Add(s1);
grid.Children.Add(s2);
grid.Children.Add(s3);
return CreateDemoContainer(grid, "Star proportions: 1* | 2* | 1* = 25% | 50% | 25%");
}
private View CreateSpacingDemo()
{
var stack = new VerticalStackLayout { Spacing = 15 };
// No spacing
var noSpacing = new Grid
{
RowSpacing = 0,
ColumnSpacing = 0,
RowDefinitions =
{
new RowDefinition { Height = GridLength.Auto },
new RowDefinition { Height = GridLength.Auto }
},
ColumnDefinitions =
{
new ColumnDefinition { Width = GridLength.Star },
new ColumnDefinition { Width = GridLength.Star }
}
};
AddFourCells(noSpacing);
stack.Children.Add(new Label { Text = "No spacing (RowSpacing=0, ColumnSpacing=0)", FontSize = 12, TextColor = Colors.Gray });
stack.Children.Add(noSpacing);
// With spacing
var withSpacing = new Grid
{
RowSpacing = 10,
ColumnSpacing = 10,
RowDefinitions =
{
new RowDefinition { Height = GridLength.Auto },
new RowDefinition { Height = GridLength.Auto }
},
ColumnDefinitions =
{
new ColumnDefinition { Width = GridLength.Star },
new ColumnDefinition { Width = GridLength.Star }
}
};
AddFourCells(withSpacing);
stack.Children.Add(new Label { Text = "With spacing (RowSpacing=10, ColumnSpacing=10)", FontSize = 12, TextColor = Colors.Gray, Margin = new Thickness(0, 10, 0, 0) });
stack.Children.Add(withSpacing);
// Different row/column spacing
var mixedSpacing = new Grid
{
RowSpacing = 5,
ColumnSpacing = 20,
RowDefinitions =
{
new RowDefinition { Height = GridLength.Auto },
new RowDefinition { Height = GridLength.Auto }
},
ColumnDefinitions =
{
new ColumnDefinition { Width = GridLength.Star },
new ColumnDefinition { Width = GridLength.Star }
}
};
AddFourCells(mixedSpacing);
stack.Children.Add(new Label { Text = "Mixed spacing (RowSpacing=5, ColumnSpacing=20)", FontSize = 12, TextColor = Colors.Gray, Margin = new Thickness(0, 10, 0, 0) });
stack.Children.Add(mixedSpacing);
return stack;
}
private View CreateSpanDemo()
{
var grid = new Grid
{
RowSpacing = 5,
ColumnSpacing = 5,
RowDefinitions =
{
new RowDefinition { Height = GridLength.Auto },
new RowDefinition { Height = GridLength.Auto },
new RowDefinition { Height = GridLength.Auto }
},
ColumnDefinitions =
{
new ColumnDefinition { Width = GridLength.Star },
new ColumnDefinition { Width = GridLength.Star },
new ColumnDefinition { Width = GridLength.Star }
}
};
// Spanning header
var header = CreateCell("ColumnSpan=3 (Header)", "#1976D2", Colors.White);
Grid.SetRow(header, 0);
Grid.SetColumn(header, 0);
Grid.SetColumnSpan(header, 3);
// Left sidebar spanning 2 rows
var sidebar = CreateCell("RowSpan=2\n(Sidebar)", "#388E3C", Colors.White);
Grid.SetRow(sidebar, 1);
Grid.SetColumn(sidebar, 0);
Grid.SetRowSpan(sidebar, 2);
// Content cells
var content1 = CreateCell("Content 1", "#E3F2FD");
Grid.SetRow(content1, 1);
Grid.SetColumn(content1, 1);
var content2 = CreateCell("Content 2", "#E8F5E9");
Grid.SetRow(content2, 1);
Grid.SetColumn(content2, 2);
var content3 = CreateCell("Content 3", "#FFF3E0");
Grid.SetRow(content3, 2);
Grid.SetColumn(content3, 1);
var content4 = CreateCell("Content 4", "#FCE4EC");
Grid.SetRow(content4, 2);
Grid.SetColumn(content4, 2);
grid.Children.Add(header);
grid.Children.Add(sidebar);
grid.Children.Add(content1);
grid.Children.Add(content2);
grid.Children.Add(content3);
grid.Children.Add(content4);
return CreateDemoContainer(grid, "Header spans 3 columns, Sidebar spans 2 rows");
}
private View CreateMixedSizingDemo()
{
var grid = new Grid
{
ColumnSpacing = 5,
ColumnDefinitions =
{
new ColumnDefinition { Width = new GridLength(60) }, // Fixed
new ColumnDefinition { Width = GridLength.Star }, // Fill
new ColumnDefinition { Width = GridLength.Auto }, // Auto
new ColumnDefinition { Width = new GridLength(60) } // Fixed
},
BackgroundColor = Color.FromArgb("#F5F5F5")
};
var c1 = CreateCell("60px", "#BBDEFB");
var c2 = CreateCell("Star (fills remaining)", "#C8E6C9");
var c3 = CreateCell("Auto", "#FFECB3");
var c4 = CreateCell("60px", "#F8BBD9");
Grid.SetColumn(c1, 0);
Grid.SetColumn(c2, 1);
Grid.SetColumn(c3, 2);
Grid.SetColumn(c4, 3);
grid.Children.Add(c1);
grid.Children.Add(c2);
grid.Children.Add(c3);
grid.Children.Add(c4);
return CreateDemoContainer(grid, "Mixed: 60px | Star | Auto | 60px");
}
private View CreateNestedGridDemo()
{
var outerGrid = new Grid
{
RowSpacing = 10,
ColumnSpacing = 10,
RowDefinitions =
{
new RowDefinition { Height = GridLength.Auto },
new RowDefinition { Height = GridLength.Auto }
},
ColumnDefinitions =
{
new ColumnDefinition { Width = GridLength.Star },
new ColumnDefinition { Width = GridLength.Star }
},
BackgroundColor = Color.FromArgb("#E0E0E0"),
Padding = new Thickness(10)
};
// Nested grid 1
var innerGrid1 = new Grid
{
RowSpacing = 2,
ColumnSpacing = 2,
RowDefinitions =
{
new RowDefinition { Height = GridLength.Auto },
new RowDefinition { Height = GridLength.Auto }
},
ColumnDefinitions =
{
new ColumnDefinition { Width = GridLength.Star },
new ColumnDefinition { Width = GridLength.Star }
}
};
var i1a = CreateCell("A", "#BBDEFB", null, 8);
var i1b = CreateCell("B", "#90CAF9", null, 8);
var i1c = CreateCell("C", "#64B5F6", null, 8);
var i1d = CreateCell("D", "#42A5F5", null, 8);
Grid.SetRow(i1a, 0); Grid.SetColumn(i1a, 0);
Grid.SetRow(i1b, 0); Grid.SetColumn(i1b, 1);
Grid.SetRow(i1c, 1); Grid.SetColumn(i1c, 0);
Grid.SetRow(i1d, 1); Grid.SetColumn(i1d, 1);
innerGrid1.Children.Add(i1a);
innerGrid1.Children.Add(i1b);
innerGrid1.Children.Add(i1c);
innerGrid1.Children.Add(i1d);
// Nested grid 2
var innerGrid2 = new Grid
{
RowSpacing = 2,
ColumnSpacing = 2,
RowDefinitions =
{
new RowDefinition { Height = GridLength.Auto },
new RowDefinition { Height = GridLength.Auto }
},
ColumnDefinitions =
{
new ColumnDefinition { Width = GridLength.Star },
new ColumnDefinition { Width = GridLength.Star }
}
};
var i2a = CreateCell("1", "#C8E6C9", null, 8);
var i2b = CreateCell("2", "#A5D6A7", null, 8);
var i2c = CreateCell("3", "#81C784", null, 8);
var i2d = CreateCell("4", "#66BB6A", null, 8);
Grid.SetRow(i2a, 0); Grid.SetColumn(i2a, 0);
Grid.SetRow(i2b, 0); Grid.SetColumn(i2b, 1);
Grid.SetRow(i2c, 1); Grid.SetColumn(i2c, 0);
Grid.SetRow(i2d, 1); Grid.SetColumn(i2d, 1);
innerGrid2.Children.Add(i2a);
innerGrid2.Children.Add(i2b);
innerGrid2.Children.Add(i2c);
innerGrid2.Children.Add(i2d);
Grid.SetRow(innerGrid1, 0); Grid.SetColumn(innerGrid1, 0);
Grid.SetRow(innerGrid2, 0); Grid.SetColumn(innerGrid2, 1);
var label1 = new Label { Text = "Outer Grid Row 1", HorizontalOptions = LayoutOptions.Center };
var label2 = new Label { Text = "Spans both columns", HorizontalOptions = LayoutOptions.Center };
Grid.SetRow(label1, 1); Grid.SetColumn(label1, 0);
Grid.SetRow(label2, 1); Grid.SetColumn(label2, 1);
outerGrid.Children.Add(innerGrid1);
outerGrid.Children.Add(innerGrid2);
outerGrid.Children.Add(label1);
outerGrid.Children.Add(label2);
return CreateDemoContainer(outerGrid, "Outer grid contains two nested 2x2 grids");
}
private Border CreateCell(string text, string bgColor, Color? textColor = null, float fontSize = 12)
{
return new Border
{
BackgroundColor = Color.FromArgb(bgColor),
Padding = new Thickness(10, 8),
StrokeThickness = 0,
Content = new Label
{
Text = text,
FontSize = fontSize,
TextColor = textColor ?? Colors.Black,
HorizontalTextAlignment = TextAlignment.Center,
VerticalTextAlignment = TextAlignment.Center
}
};
}
private void AddFourCells(Grid grid)
{
var c1 = CreateCell("0,0", "#BBDEFB");
var c2 = CreateCell("0,1", "#C8E6C9");
var c3 = CreateCell("1,0", "#FFECB3");
var c4 = CreateCell("1,1", "#F8BBD9");
Grid.SetRow(c1, 0); Grid.SetColumn(c1, 0);
Grid.SetRow(c2, 0); Grid.SetColumn(c2, 1);
Grid.SetRow(c3, 1); Grid.SetColumn(c3, 0);
Grid.SetRow(c4, 1); Grid.SetColumn(c4, 1);
grid.Children.Add(c1);
grid.Children.Add(c2);
grid.Children.Add(c3);
grid.Children.Add(c4);
}
private View CreateDemoContainer(View content, string description)
{
return new VerticalStackLayout
{
Spacing = 5,
Children =
{
new Label { Text = description, FontSize = 12, TextColor = Colors.Gray },
content
}
};
}
}

View File

@@ -0,0 +1,265 @@
// HomePage - Welcome page for the demo
using Microsoft.Maui.Controls;
using Microsoft.Maui.Graphics;
using Microsoft.Maui.Platform.Linux.Hosting;
namespace ShellDemo;
public class HomePage : ContentPage
{
public HomePage()
{
Title = "Home";
Content = new ScrollView
{
Orientation = ScrollOrientation.Both, // Enable horizontal scrolling when window is too narrow
Content = new VerticalStackLayout
{
Padding = new Thickness(30),
Spacing = 20,
Children =
{
new Label
{
Text = "OpenMaui Linux",
FontSize = 32,
FontAttributes = FontAttributes.Bold,
HorizontalOptions = LayoutOptions.Center,
TextColor = Color.FromArgb("#2196F3")
},
new Label
{
Text = "Controls Demo",
FontSize = 20,
HorizontalOptions = LayoutOptions.Center,
TextColor = Colors.Gray
},
new BoxView
{
HeightRequest = 2,
Color = Color.FromArgb("#E0E0E0"),
Margin = new Thickness(0, 10)
},
new Label
{
Text = "Welcome to the comprehensive controls demonstration for OpenMaui Linux. " +
"This app showcases all the major UI controls available in the framework.",
FontSize = 14,
LineBreakMode = LineBreakMode.WordWrap,
HorizontalTextAlignment = TextAlignment.Center
},
CreateFeatureSection(),
new Label
{
Text = "Use the flyout menu (swipe from left or tap the hamburger icon) to navigate between different control demos.",
FontSize = 12,
TextColor = Colors.Gray,
LineBreakMode = LineBreakMode.WordWrap,
HorizontalTextAlignment = TextAlignment.Center,
Margin = new Thickness(0, 20, 0, 0)
},
CreateQuickLinksSection(),
CreateNavigationDemoSection()
}
}
};
}
private View CreateFeatureSection()
{
var grid = new Grid
{
ColumnDefinitions =
{
new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) },
new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) }
},
RowDefinitions =
{
new RowDefinition { Height = GridLength.Auto },
new RowDefinition { Height = GridLength.Auto },
new RowDefinition { Height = GridLength.Auto }
},
ColumnSpacing = 15,
RowSpacing = 15,
Margin = new Thickness(0, 20)
};
var features = new[]
{
("Buttons", "Various button styles and events"),
("Text Input", "Entry, Editor, SearchBar"),
("Selection", "CheckBox, Switch, Slider"),
("Pickers", "Picker, DatePicker, TimePicker"),
("Lists", "CollectionView with selection"),
("Progress", "ProgressBar, ActivityIndicator")
};
for (int i = 0; i < features.Length; i++)
{
var (title, desc) = features[i];
var card = CreateFeatureCard(title, desc);
Grid.SetRow(card, i / 2);
Grid.SetColumn(card, i % 2);
grid.Children.Add(card);
}
return grid;
}
private Frame CreateFeatureCard(string title, string description)
{
return new Frame
{
CornerRadius = 8,
Padding = new Thickness(15),
BackgroundColor = Colors.White,
HasShadow = true,
Content = new VerticalStackLayout
{
Spacing = 5,
Children =
{
new Label
{
Text = title,
FontSize = 14,
FontAttributes = FontAttributes.Bold,
TextColor = Color.FromArgb("#2196F3")
},
new Label
{
Text = description,
FontSize = 11,
TextColor = Colors.Gray,
LineBreakMode = LineBreakMode.WordWrap
}
}
}
};
}
private View CreateQuickLinksSection()
{
var layout = new VerticalStackLayout
{
Spacing = 10,
Margin = new Thickness(0, 20, 0, 0)
};
layout.Children.Add(new Label
{
Text = "Quick Actions",
FontSize = 16,
FontAttributes = FontAttributes.Bold,
HorizontalOptions = LayoutOptions.Center
});
var buttonRow = new HorizontalStackLayout
{
Spacing = 10,
HorizontalOptions = LayoutOptions.Center
};
var buttonsBtn = new Button
{
Text = "Try Buttons",
BackgroundColor = Color.FromArgb("#2196F3"),
TextColor = Colors.White
};
buttonsBtn.Clicked += (s, e) => LinuxViewRenderer.NavigateToRoute("Buttons");
var listsBtn = new Button
{
Text = "Try Lists",
BackgroundColor = Color.FromArgb("#4CAF50"),
TextColor = Colors.White
};
listsBtn.Clicked += (s, e) => LinuxViewRenderer.NavigateToRoute("Lists");
buttonRow.Children.Add(buttonsBtn);
buttonRow.Children.Add(listsBtn);
layout.Children.Add(buttonRow);
return layout;
}
private View CreateNavigationDemoSection()
{
var frame = new Frame
{
CornerRadius = 8,
Padding = new Thickness(20),
BackgroundColor = Color.FromArgb("#F3E5F5"),
Margin = new Thickness(0, 20, 0, 0),
Content = new VerticalStackLayout
{
Spacing = 15,
Children =
{
new Label
{
Text = "Navigation Stack Demo",
FontSize = 18,
FontAttributes = FontAttributes.Bold,
TextColor = Color.FromArgb("#9C27B0"),
HorizontalOptions = LayoutOptions.Center
},
new Label
{
Text = "Demonstrate push/pop navigation using Shell.GoToAsync()",
FontSize = 12,
TextColor = Colors.Gray,
HorizontalTextAlignment = TextAlignment.Center
},
CreatePushButton("Push Detail Page", "detail"),
new Label
{
Text = "Click the button to push a new page onto the navigation stack. " +
"Use the back button or 'Go Back' to pop it off.",
FontSize = 11,
TextColor = Colors.Gray,
HorizontalTextAlignment = TextAlignment.Center,
LineBreakMode = LineBreakMode.WordWrap
}
}
}
};
return frame;
}
private Button CreatePushButton(string text, string route)
{
var btn = new Button
{
Text = text,
BackgroundColor = Color.FromArgb("#9C27B0"),
TextColor = Colors.White,
HorizontalOptions = LayoutOptions.Center,
Padding = new Thickness(30, 10)
};
btn.Clicked += (s, e) =>
{
Console.WriteLine($"[HomePage] Push button clicked, navigating to {route}");
// Use LinuxViewRenderer.PushPage for Skia-based navigation
var success = LinuxViewRenderer.PushPage(new DetailPage());
Console.WriteLine($"[HomePage] PushPage result: {success}");
};
return btn;
}
}

View File

@@ -0,0 +1,249 @@
// ListsPage - CollectionView and ListView Demo
using Microsoft.Maui.Controls;
using Microsoft.Maui.Graphics;
namespace ShellDemo;
public class ListsPage : ContentPage
{
private readonly Label _eventLog;
private int _eventCount = 0;
public ListsPage()
{
Title = "Lists";
_eventLog = new Label
{
Text = "Events will appear here...",
FontSize = 11,
TextColor = Colors.Gray,
LineBreakMode = LineBreakMode.WordWrap
};
Content = new Grid
{
RowDefinitions =
{
new RowDefinition { Height = new GridLength(1, GridUnitType.Star) },
new RowDefinition { Height = new GridLength(120) }
},
Children =
{
CreateMainContent(),
CreateEventLogPanel()
}
};
Grid.SetRow((View)((Grid)Content).Children[0], 0);
Grid.SetRow((View)((Grid)Content).Children[1], 1);
}
private View CreateMainContent()
{
return new ScrollView
{
Content = new VerticalStackLayout
{
Padding = new Thickness(20),
Spacing = 20,
Children =
{
new Label { Text = "List Controls", FontSize = 24, FontAttributes = FontAttributes.Bold },
CreateSection("CollectionView - Fruits", CreateFruitsCollectionView()),
CreateSection("CollectionView - Colors", CreateColorsCollectionView()),
CreateSection("CollectionView - Contacts", CreateContactsCollectionView())
}
}
};
}
private View CreateFruitsCollectionView()
{
var layout = new VerticalStackLayout { Spacing = 10 };
var fruits = new List<string>
{
"Apple", "Banana", "Cherry", "Date", "Elderberry",
"Fig", "Grape", "Honeydew", "Kiwi", "Lemon",
"Mango", "Nectarine", "Orange", "Papaya", "Quince"
};
var selectedLabel = new Label { Text = "Tap a fruit to select", TextColor = Colors.Gray };
var collectionView = new CollectionView
{
ItemsSource = fruits,
HeightRequest = 200,
SelectionMode = SelectionMode.Single,
BackgroundColor = Color.FromArgb("#FAFAFA")
};
collectionView.SelectionChanged += (s, e) =>
{
if (e.CurrentSelection.Count > 0)
{
var item = e.CurrentSelection[0]?.ToString();
selectedLabel.Text = $"Selected: {item}";
LogEvent($"Fruit selected: {item}");
}
};
layout.Children.Add(collectionView);
layout.Children.Add(selectedLabel);
return layout;
}
private View CreateColorsCollectionView()
{
var layout = new VerticalStackLayout { Spacing = 10 };
var colors = new List<ColorItem>
{
new("Red", "#F44336"),
new("Pink", "#E91E63"),
new("Purple", "#9C27B0"),
new("Deep Purple", "#673AB7"),
new("Indigo", "#3F51B5"),
new("Blue", "#2196F3"),
new("Cyan", "#00BCD4"),
new("Teal", "#009688"),
new("Green", "#4CAF50"),
new("Light Green", "#8BC34A"),
new("Lime", "#CDDC39"),
new("Yellow", "#FFEB3B"),
new("Amber", "#FFC107"),
new("Orange", "#FF9800"),
new("Deep Orange", "#FF5722")
};
var collectionView = new CollectionView
{
ItemsSource = colors,
HeightRequest = 180,
SelectionMode = SelectionMode.Single,
BackgroundColor = Colors.White
};
collectionView.SelectionChanged += (s, e) =>
{
if (e.CurrentSelection.Count > 0 && e.CurrentSelection[0] is ColorItem item)
{
LogEvent($"Color selected: {item.Name} ({item.Hex})");
}
};
layout.Children.Add(collectionView);
layout.Children.Add(new Label { Text = "Scroll to see all colors", FontSize = 11, TextColor = Colors.Gray });
return layout;
}
private View CreateContactsCollectionView()
{
var layout = new VerticalStackLayout { Spacing = 10 };
var contacts = new List<ContactItem>
{
new("Alice Johnson", "alice@example.com", "Engineering"),
new("Bob Smith", "bob@example.com", "Marketing"),
new("Carol Williams", "carol@example.com", "Design"),
new("David Brown", "david@example.com", "Sales"),
new("Eva Martinez", "eva@example.com", "Engineering"),
new("Frank Lee", "frank@example.com", "Support"),
new("Grace Kim", "grace@example.com", "HR"),
new("Henry Wilson", "henry@example.com", "Finance")
};
var collectionView = new CollectionView
{
ItemsSource = contacts,
HeightRequest = 200,
SelectionMode = SelectionMode.Single,
BackgroundColor = Colors.White
};
collectionView.SelectionChanged += (s, e) =>
{
if (e.CurrentSelection.Count > 0 && e.CurrentSelection[0] is ContactItem contact)
{
LogEvent($"Contact: {contact.Name} - {contact.Department}");
}
};
layout.Children.Add(collectionView);
// Action buttons
var buttonRow = new HorizontalStackLayout { Spacing = 10 };
var addBtn = new Button { Text = "Add Contact", BackgroundColor = Colors.Green, TextColor = Colors.White };
addBtn.Clicked += (s, e) => LogEvent("Add contact clicked");
var deleteBtn = new Button { Text = "Delete Selected", BackgroundColor = Colors.Red, TextColor = Colors.White };
deleteBtn.Clicked += (s, e) => LogEvent("Delete contact clicked");
buttonRow.Children.Add(addBtn);
buttonRow.Children.Add(deleteBtn);
layout.Children.Add(buttonRow);
return layout;
}
private Frame CreateSection(string title, View content)
{
return new Frame
{
CornerRadius = 8,
Padding = new Thickness(15),
BackgroundColor = Colors.White,
Content = new VerticalStackLayout
{
Spacing = 10,
Children =
{
new Label { Text = title, FontSize = 16, FontAttributes = FontAttributes.Bold },
content
}
}
};
}
private View CreateEventLogPanel()
{
return new Frame
{
BackgroundColor = Color.FromArgb("#F5F5F5"),
Padding = new Thickness(10),
CornerRadius = 0,
Content = new VerticalStackLayout
{
Children =
{
new Label { Text = "Event Log:", FontSize = 12, FontAttributes = FontAttributes.Bold },
new ScrollView
{
HeightRequest = 80,
Content = _eventLog
}
}
}
};
}
private void LogEvent(string message)
{
_eventCount++;
var timestamp = DateTime.Now.ToString("HH:mm:ss");
_eventLog.Text = $"[{timestamp}] {_eventCount}. {message}\n{_eventLog.Text}";
}
}
public record ColorItem(string Name, string Hex)
{
public override string ToString() => Name;
}
public record ContactItem(string Name, string Email, string Department)
{
public override string ToString() => $"{Name} ({Department})";
}

View File

@@ -0,0 +1,261 @@
// PickersPage - Picker, DatePicker, TimePicker Demo
using Microsoft.Maui.Controls;
using Microsoft.Maui.Graphics;
namespace ShellDemo;
public class PickersPage : ContentPage
{
private readonly Label _eventLog;
private int _eventCount = 0;
public PickersPage()
{
Title = "Pickers";
_eventLog = new Label
{
Text = "Events will appear here...",
FontSize = 11,
TextColor = Colors.Gray,
LineBreakMode = LineBreakMode.WordWrap
};
Content = new Grid
{
RowDefinitions =
{
new RowDefinition { Height = new GridLength(1, GridUnitType.Star) },
new RowDefinition { Height = new GridLength(120) }
},
Children =
{
CreateMainContent(),
CreateEventLogPanel()
}
};
Grid.SetRow((View)((Grid)Content).Children[0], 0);
Grid.SetRow((View)((Grid)Content).Children[1], 1);
}
private View CreateMainContent()
{
return new ScrollView
{
Content = new VerticalStackLayout
{
Padding = new Thickness(20),
Spacing = 20,
Children =
{
new Label { Text = "Picker Controls", FontSize = 24, FontAttributes = FontAttributes.Bold },
CreateSection("Picker", CreatePickerDemo()),
CreateSection("DatePicker", CreateDatePickerDemo()),
CreateSection("TimePicker", CreateTimePickerDemo())
}
}
};
}
private View CreatePickerDemo()
{
var layout = new VerticalStackLayout { Spacing = 15 };
// Basic picker
var selectedLabel = new Label { Text = "Selected: (none)", TextColor = Colors.Gray };
var picker1 = new Picker { Title = "Select a fruit" };
picker1.Items.Add("Apple");
picker1.Items.Add("Banana");
picker1.Items.Add("Cherry");
picker1.Items.Add("Date");
picker1.Items.Add("Elderberry");
picker1.Items.Add("Fig");
picker1.Items.Add("Grape");
picker1.SelectedIndexChanged += (s, e) =>
{
if (picker1.SelectedIndex >= 0)
{
var item = picker1.Items[picker1.SelectedIndex];
selectedLabel.Text = $"Selected: {item}";
LogEvent($"Fruit selected: {item}");
}
};
layout.Children.Add(picker1);
layout.Children.Add(selectedLabel);
// Picker with default selection
layout.Children.Add(new Label { Text = "With Default Selection:", FontSize = 12, Margin = new Thickness(0, 10, 0, 0) });
var picker2 = new Picker { Title = "Select a color" };
picker2.Items.Add("Red");
picker2.Items.Add("Green");
picker2.Items.Add("Blue");
picker2.Items.Add("Yellow");
picker2.Items.Add("Purple");
picker2.SelectedIndex = 2; // Blue
picker2.SelectedIndexChanged += (s, e) =>
{
if (picker2.SelectedIndex >= 0)
LogEvent($"Color selected: {picker2.Items[picker2.SelectedIndex]}");
};
layout.Children.Add(picker2);
// Styled picker
layout.Children.Add(new Label { Text = "Styled Picker:", FontSize = 12, Margin = new Thickness(0, 10, 0, 0) });
var picker3 = new Picker
{
Title = "Select size",
TextColor = Colors.DarkBlue,
TitleColor = Colors.Gray
};
picker3.Items.Add("Small");
picker3.Items.Add("Medium");
picker3.Items.Add("Large");
picker3.Items.Add("Extra Large");
picker3.SelectedIndexChanged += (s, e) =>
{
if (picker3.SelectedIndex >= 0)
LogEvent($"Size selected: {picker3.Items[picker3.SelectedIndex]}");
};
layout.Children.Add(picker3);
return layout;
}
private View CreateDatePickerDemo()
{
var layout = new VerticalStackLayout { Spacing = 15 };
// Basic date picker
var dateLabel = new Label { Text = $"Selected: {DateTime.Today:d}" };
var datePicker1 = new DatePicker { Date = DateTime.Today };
datePicker1.DateSelected += (s, e) =>
{
dateLabel.Text = $"Selected: {e.NewDate:d}";
LogEvent($"Date selected: {e.NewDate:d}");
};
layout.Children.Add(datePicker1);
layout.Children.Add(dateLabel);
// Date picker with range
layout.Children.Add(new Label { Text = "With Date Range (this month only):", FontSize = 12, Margin = new Thickness(0, 10, 0, 0) });
var startOfMonth = new DateTime(DateTime.Today.Year, DateTime.Today.Month, 1);
var endOfMonth = startOfMonth.AddMonths(1).AddDays(-1);
var datePicker2 = new DatePicker
{
MinimumDate = startOfMonth,
MaximumDate = endOfMonth,
Date = DateTime.Today
};
datePicker2.DateSelected += (s, e) => LogEvent($"Date (limited): {e.NewDate:d}");
layout.Children.Add(datePicker2);
// Styled date picker
layout.Children.Add(new Label { Text = "Styled DatePicker:", FontSize = 12, Margin = new Thickness(0, 10, 0, 0) });
var datePicker3 = new DatePicker
{
Date = DateTime.Today.AddDays(7),
TextColor = Colors.DarkGreen
};
datePicker3.DateSelected += (s, e) => LogEvent($"Styled date: {e.NewDate:d}");
layout.Children.Add(datePicker3);
return layout;
}
private View CreateTimePickerDemo()
{
var layout = new VerticalStackLayout { Spacing = 15 };
// Basic time picker
var timeLabel = new Label { Text = $"Selected: {DateTime.Now:t}" };
var timePicker1 = new TimePicker { Time = DateTime.Now.TimeOfDay };
timePicker1.PropertyChanged += (s, e) =>
{
if (e.PropertyName == nameof(TimePicker.Time))
{
var time = timePicker1.Time;
timeLabel.Text = $"Selected: {time:hh\\:mm}";
LogEvent($"Time selected: {time:hh\\:mm}");
}
};
layout.Children.Add(timePicker1);
layout.Children.Add(timeLabel);
// Styled time picker
layout.Children.Add(new Label { Text = "Styled TimePicker:", FontSize = 12, Margin = new Thickness(0, 10, 0, 0) });
var timePicker2 = new TimePicker
{
Time = new TimeSpan(14, 30, 0),
TextColor = Colors.DarkBlue
};
timePicker2.PropertyChanged += (s, e) =>
{
if (e.PropertyName == nameof(TimePicker.Time))
LogEvent($"Styled time: {timePicker2.Time:hh\\:mm}");
};
layout.Children.Add(timePicker2);
// Morning alarm example
layout.Children.Add(new Label { Text = "Alarm Time:", FontSize = 12, Margin = new Thickness(0, 10, 0, 0) });
var alarmRow = new HorizontalStackLayout { Spacing = 10 };
var alarmPicker = new TimePicker { Time = new TimeSpan(7, 0, 0) };
var alarmBtn = new Button { Text = "Set Alarm", BackgroundColor = Colors.Orange, TextColor = Colors.White };
alarmBtn.Clicked += (s, e) => LogEvent($"Alarm set for {alarmPicker.Time:hh\\:mm}");
alarmRow.Children.Add(alarmPicker);
alarmRow.Children.Add(alarmBtn);
layout.Children.Add(alarmRow);
return layout;
}
private Frame CreateSection(string title, View content)
{
return new Frame
{
CornerRadius = 8,
Padding = new Thickness(15),
BackgroundColor = Colors.White,
Content = new VerticalStackLayout
{
Spacing = 10,
Children =
{
new Label { Text = title, FontSize = 16, FontAttributes = FontAttributes.Bold },
content
}
}
};
}
private View CreateEventLogPanel()
{
return new Frame
{
BackgroundColor = Color.FromArgb("#F5F5F5"),
Padding = new Thickness(10),
CornerRadius = 0,
Content = new VerticalStackLayout
{
Children =
{
new Label { Text = "Event Log:", FontSize = 12, FontAttributes = FontAttributes.Bold },
new ScrollView
{
HeightRequest = 80,
Content = _eventLog
}
}
}
};
}
private void LogEvent(string message)
{
_eventCount++;
var timestamp = DateTime.Now.ToString("HH:mm:ss");
_eventLog.Text = $"[{timestamp}] {_eventCount}. {message}\n{_eventLog.Text}";
}
}

View File

@@ -0,0 +1,261 @@
// ProgressPage - ProgressBar and ActivityIndicator Demo
using Microsoft.Maui.Controls;
using Microsoft.Maui.Graphics;
namespace ShellDemo;
public class ProgressPage : ContentPage
{
private readonly Label _eventLog;
private int _eventCount = 0;
private ProgressBar? _animatedProgress;
private bool _isAnimating = false;
public ProgressPage()
{
Title = "Progress";
_eventLog = new Label
{
Text = "Events will appear here...",
FontSize = 11,
TextColor = Colors.Gray,
LineBreakMode = LineBreakMode.WordWrap
};
Content = new Grid
{
RowDefinitions =
{
new RowDefinition { Height = new GridLength(1, GridUnitType.Star) },
new RowDefinition { Height = new GridLength(120) }
},
Children =
{
CreateMainContent(),
CreateEventLogPanel()
}
};
Grid.SetRow((View)((Grid)Content).Children[0], 0);
Grid.SetRow((View)((Grid)Content).Children[1], 1);
}
private View CreateMainContent()
{
return new ScrollView
{
Content = new VerticalStackLayout
{
Padding = new Thickness(20),
Spacing = 20,
Children =
{
new Label { Text = "Progress Indicators", FontSize = 24, FontAttributes = FontAttributes.Bold },
CreateSection("ProgressBar", CreateProgressBarDemo()),
CreateSection("ActivityIndicator", CreateActivityIndicatorDemo()),
CreateSection("Interactive Demo", CreateInteractiveDemo())
}
}
};
}
private View CreateProgressBarDemo()
{
var layout = new VerticalStackLayout { Spacing = 15 };
// Various progress values
var values = new[] { 0.0, 0.25, 0.5, 0.75, 1.0 };
foreach (var value in values)
{
var row = new HorizontalStackLayout { Spacing = 10 };
var progress = new ProgressBar { Progress = value, WidthRequest = 200 };
var label = new Label { Text = $"{value * 100:0}%", VerticalOptions = LayoutOptions.Center, WidthRequest = 50 };
row.Children.Add(progress);
row.Children.Add(label);
layout.Children.Add(row);
}
// Colored progress bars
layout.Children.Add(new Label { Text = "Colored Progress Bars:", FontSize = 12, Margin = new Thickness(0, 10, 0, 0) });
var colors = new[] { Colors.Red, Colors.Green, Colors.Blue, Colors.Orange, Colors.Purple };
foreach (var color in colors)
{
var progress = new ProgressBar { Progress = 0.7, ProgressColor = color };
layout.Children.Add(progress);
}
return layout;
}
private View CreateActivityIndicatorDemo()
{
var layout = new VerticalStackLayout { Spacing = 15 };
// Running indicator
var runningRow = new HorizontalStackLayout { Spacing = 15 };
var runningIndicator = new ActivityIndicator { IsRunning = true };
runningRow.Children.Add(runningIndicator);
runningRow.Children.Add(new Label { Text = "Loading...", VerticalOptions = LayoutOptions.Center });
layout.Children.Add(runningRow);
// Toggle indicator
var toggleRow = new HorizontalStackLayout { Spacing = 15 };
var toggleIndicator = new ActivityIndicator { IsRunning = false };
var toggleBtn = new Button { Text = "Start/Stop" };
toggleBtn.Clicked += (s, e) =>
{
toggleIndicator.IsRunning = !toggleIndicator.IsRunning;
LogEvent($"ActivityIndicator: {(toggleIndicator.IsRunning ? "Started" : "Stopped")}");
};
toggleRow.Children.Add(toggleIndicator);
toggleRow.Children.Add(toggleBtn);
layout.Children.Add(toggleRow);
// Colored indicators
layout.Children.Add(new Label { Text = "Colored Indicators:", FontSize = 12, Margin = new Thickness(0, 10, 0, 0) });
var colorRow = new HorizontalStackLayout { Spacing = 20 };
var indicatorColors = new[] { Colors.Red, Colors.Green, Colors.Blue, Colors.Orange };
foreach (var color in indicatorColors)
{
var indicator = new ActivityIndicator { IsRunning = true, Color = color };
colorRow.Children.Add(indicator);
}
layout.Children.Add(colorRow);
return layout;
}
private View CreateInteractiveDemo()
{
var layout = new VerticalStackLayout { Spacing = 15 };
// Slider-controlled progress
var progressLabel = new Label { Text = "Progress: 50%" };
_animatedProgress = new ProgressBar { Progress = 0.5 };
var slider = new Slider { Minimum = 0, Maximum = 100, Value = 50 };
slider.ValueChanged += (s, e) =>
{
var value = e.NewValue / 100.0;
_animatedProgress.Progress = value;
progressLabel.Text = $"Progress: {e.NewValue:0}%";
};
layout.Children.Add(_animatedProgress);
layout.Children.Add(slider);
layout.Children.Add(progressLabel);
// Animated progress buttons
var buttonRow = new HorizontalStackLayout { Spacing = 10, Margin = new Thickness(0, 10, 0, 0) };
var resetBtn = new Button { Text = "Reset", BackgroundColor = Colors.Gray, TextColor = Colors.White };
resetBtn.Clicked += async (s, e) =>
{
_animatedProgress.Progress = 0;
slider.Value = 0;
LogEvent("Progress reset to 0%");
};
var animateBtn = new Button { Text = "Animate to 100%", BackgroundColor = Colors.Blue, TextColor = Colors.White };
animateBtn.Clicked += async (s, e) =>
{
if (_isAnimating) return;
_isAnimating = true;
LogEvent("Animation started");
for (int i = (int)(slider.Value); i <= 100; i += 5)
{
_animatedProgress.Progress = i / 100.0;
slider.Value = i;
await Task.Delay(100);
}
_isAnimating = false;
LogEvent("Animation completed");
};
var simulateBtn = new Button { Text = "Simulate Download", BackgroundColor = Colors.Green, TextColor = Colors.White };
simulateBtn.Clicked += async (s, e) =>
{
if (_isAnimating) return;
_isAnimating = true;
LogEvent("Download simulation started");
_animatedProgress.Progress = 0;
slider.Value = 0;
var random = new Random();
double progress = 0;
while (progress < 1.0)
{
progress += random.NextDouble() * 0.1;
if (progress > 1.0) progress = 1.0;
_animatedProgress.Progress = progress;
slider.Value = progress * 100;
await Task.Delay(200 + random.Next(300));
}
_isAnimating = false;
LogEvent("Download simulation completed");
};
buttonRow.Children.Add(resetBtn);
buttonRow.Children.Add(animateBtn);
buttonRow.Children.Add(simulateBtn);
layout.Children.Add(buttonRow);
return layout;
}
private Frame CreateSection(string title, View content)
{
return new Frame
{
CornerRadius = 8,
Padding = new Thickness(15),
BackgroundColor = Colors.White,
Content = new VerticalStackLayout
{
Spacing = 10,
Children =
{
new Label { Text = title, FontSize = 16, FontAttributes = FontAttributes.Bold },
content
}
}
};
}
private View CreateEventLogPanel()
{
return new Frame
{
BackgroundColor = Color.FromArgb("#F5F5F5"),
Padding = new Thickness(10),
CornerRadius = 0,
Content = new VerticalStackLayout
{
Children =
{
new Label { Text = "Event Log:", FontSize = 12, FontAttributes = FontAttributes.Bold },
new ScrollView
{
HeightRequest = 80,
Content = _eventLog
}
}
}
};
}
private void LogEvent(string message)
{
_eventCount++;
var timestamp = DateTime.Now.ToString("HH:mm:ss");
_eventLog.Text = $"[{timestamp}] {_eventCount}. {message}\n{_eventLog.Text}";
}
}

View File

@@ -0,0 +1,239 @@
// SelectionPage - CheckBox, Switch, Slider Demo
using Microsoft.Maui.Controls;
using Microsoft.Maui.Graphics;
namespace ShellDemo;
public class SelectionPage : ContentPage
{
private readonly Label _eventLog;
private int _eventCount = 0;
public SelectionPage()
{
Title = "Selection Controls";
_eventLog = new Label
{
Text = "Events will appear here...",
FontSize = 11,
TextColor = Colors.Gray,
LineBreakMode = LineBreakMode.WordWrap
};
Content = new Grid
{
RowDefinitions =
{
new RowDefinition { Height = new GridLength(1, GridUnitType.Star) },
new RowDefinition { Height = new GridLength(120) }
},
Children =
{
CreateMainContent(),
CreateEventLogPanel()
}
};
Grid.SetRow((View)((Grid)Content).Children[0], 0);
Grid.SetRow((View)((Grid)Content).Children[1], 1);
}
private View CreateMainContent()
{
return new ScrollView
{
Content = new VerticalStackLayout
{
Padding = new Thickness(20),
Spacing = 20,
Children =
{
new Label { Text = "Selection Controls", FontSize = 24, FontAttributes = FontAttributes.Bold },
CreateSection("CheckBox", CreateCheckBoxDemo()),
CreateSection("Switch", CreateSwitchDemo()),
CreateSection("Slider", CreateSliderDemo())
}
}
};
}
private View CreateCheckBoxDemo()
{
var layout = new VerticalStackLayout { Spacing = 15 };
// Basic checkboxes
var basicRow = new HorizontalStackLayout { Spacing = 20 };
var cb1 = new CheckBox { IsChecked = false };
cb1.CheckedChanged += (s, e) => LogEvent($"Checkbox 1: {(e.Value ? "Checked" : "Unchecked")}");
basicRow.Children.Add(cb1);
basicRow.Children.Add(new Label { Text = "Option 1", VerticalOptions = LayoutOptions.Center });
var cb2 = new CheckBox { IsChecked = true };
cb2.CheckedChanged += (s, e) => LogEvent($"Checkbox 2: {(e.Value ? "Checked" : "Unchecked")}");
basicRow.Children.Add(cb2);
basicRow.Children.Add(new Label { Text = "Option 2 (default checked)", VerticalOptions = LayoutOptions.Center });
layout.Children.Add(basicRow);
// Colored checkboxes
var colorRow = new HorizontalStackLayout { Spacing = 20 };
var colors = new[] { Colors.Red, Colors.Green, Colors.Blue, Colors.Purple };
foreach (var color in colors)
{
var cb = new CheckBox { Color = color, IsChecked = true };
cb.CheckedChanged += (s, e) => LogEvent($"{color} checkbox: {(e.Value ? "Checked" : "Unchecked")}");
colorRow.Children.Add(cb);
}
layout.Children.Add(new Label { Text = "Colored Checkboxes:", FontSize = 12 });
layout.Children.Add(colorRow);
// Disabled checkbox
var disabledRow = new HorizontalStackLayout { Spacing = 10 };
var disabledCb = new CheckBox { IsChecked = true, IsEnabled = false };
disabledRow.Children.Add(disabledCb);
disabledRow.Children.Add(new Label { Text = "Disabled (checked)", VerticalOptions = LayoutOptions.Center, TextColor = Colors.Gray });
layout.Children.Add(disabledRow);
return layout;
}
private View CreateSwitchDemo()
{
var layout = new VerticalStackLayout { Spacing = 15 };
// Basic switch
var basicRow = new HorizontalStackLayout { Spacing = 15 };
var statusLabel = new Label { Text = "Off", VerticalOptions = LayoutOptions.Center, WidthRequest = 50 };
var sw1 = new Switch { IsToggled = false };
sw1.Toggled += (s, e) =>
{
statusLabel.Text = e.Value ? "On" : "Off";
LogEvent($"Switch toggled: {(e.Value ? "ON" : "OFF")}");
};
basicRow.Children.Add(sw1);
basicRow.Children.Add(statusLabel);
layout.Children.Add(basicRow);
// Colored switches
var colorRow = new HorizontalStackLayout { Spacing = 20 };
var switchColors = new[] { Colors.Green, Colors.Orange, Colors.Purple };
foreach (var color in switchColors)
{
var sw = new Switch { IsToggled = true, OnColor = color };
sw.Toggled += (s, e) => LogEvent($"{color} switch: {(e.Value ? "ON" : "OFF")}");
colorRow.Children.Add(sw);
}
layout.Children.Add(new Label { Text = "Colored Switches:", FontSize = 12 });
layout.Children.Add(colorRow);
// Disabled switch
var disabledRow = new HorizontalStackLayout { Spacing = 10 };
var disabledSw = new Switch { IsToggled = true, IsEnabled = false };
disabledRow.Children.Add(disabledSw);
disabledRow.Children.Add(new Label { Text = "Disabled (on)", VerticalOptions = LayoutOptions.Center, TextColor = Colors.Gray });
layout.Children.Add(disabledRow);
return layout;
}
private View CreateSliderDemo()
{
var layout = new VerticalStackLayout { Spacing = 15 };
// Basic slider
var valueLabel = new Label { Text = "Value: 50" };
var slider1 = new Slider { Minimum = 0, Maximum = 100, Value = 50 };
slider1.ValueChanged += (s, e) =>
{
valueLabel.Text = $"Value: {(int)e.NewValue}";
LogEvent($"Slider value: {(int)e.NewValue}");
};
layout.Children.Add(slider1);
layout.Children.Add(valueLabel);
// Slider with custom range
layout.Children.Add(new Label { Text = "Temperature (0-40°C):", FontSize = 12, Margin = new Thickness(0, 10, 0, 0) });
var tempLabel = new Label { Text = "20°C" };
var tempSlider = new Slider { Minimum = 0, Maximum = 40, Value = 20 };
tempSlider.ValueChanged += (s, e) =>
{
tempLabel.Text = $"{(int)e.NewValue}°C";
LogEvent($"Temperature: {(int)e.NewValue}°C");
};
layout.Children.Add(tempSlider);
layout.Children.Add(tempLabel);
// Colored slider
layout.Children.Add(new Label { Text = "Colored Slider:", FontSize = 12, Margin = new Thickness(0, 10, 0, 0) });
var colorSlider = new Slider
{
Minimum = 0,
Maximum = 100,
Value = 75,
MinimumTrackColor = Colors.Green,
MaximumTrackColor = Colors.LightGray,
ThumbColor = Colors.DarkGreen
};
colorSlider.ValueChanged += (s, e) => LogEvent($"Colored slider: {(int)e.NewValue}");
layout.Children.Add(colorSlider);
// Disabled slider
layout.Children.Add(new Label { Text = "Disabled Slider:", FontSize = 12, Margin = new Thickness(0, 10, 0, 0) });
var disabledSlider = new Slider { Minimum = 0, Maximum = 100, Value = 30, IsEnabled = false };
layout.Children.Add(disabledSlider);
return layout;
}
private Frame CreateSection(string title, View content)
{
return new Frame
{
CornerRadius = 8,
Padding = new Thickness(15),
BackgroundColor = Colors.White,
Content = new VerticalStackLayout
{
Spacing = 10,
Children =
{
new Label { Text = title, FontSize = 16, FontAttributes = FontAttributes.Bold },
content
}
}
};
}
private View CreateEventLogPanel()
{
return new Frame
{
BackgroundColor = Color.FromArgb("#F5F5F5"),
Padding = new Thickness(10),
CornerRadius = 0,
Content = new VerticalStackLayout
{
Children =
{
new Label { Text = "Event Log:", FontSize = 12, FontAttributes = FontAttributes.Bold },
new ScrollView
{
HeightRequest = 80,
Content = _eventLog
}
}
}
};
}
private void LogEvent(string message)
{
_eventCount++;
var timestamp = DateTime.Now.ToString("HH:mm:ss");
_eventLog.Text = $"[{timestamp}] {_eventCount}. {message}\n{_eventLog.Text}";
}
}

View File

@@ -0,0 +1,166 @@
// TextInputPage - Demonstrates text input controls
using Microsoft.Maui.Controls;
using Microsoft.Maui.Graphics;
namespace ShellDemo;
public class TextInputPage : ContentPage
{
private Label _entryOutput;
private Label _searchOutput;
private Label _editorOutput;
public TextInputPage()
{
Title = "Text Input";
_entryOutput = new Label { TextColor = Colors.Gray, FontSize = 12 };
_searchOutput = new Label { TextColor = Colors.Gray, FontSize = 12 };
_editorOutput = new Label { TextColor = Colors.Gray, FontSize = 12 };
Content = new ScrollView
{
Content = new VerticalStackLayout
{
Padding = new Thickness(20),
Spacing = 15,
Children =
{
new Label
{
Text = "Text Input Controls",
FontSize = 24,
FontAttributes = FontAttributes.Bold
},
new Label
{
Text = "Click on any field and start typing. All keyboard input is handled by the framework.",
FontSize = 14,
TextColor = Colors.Gray
},
// Entry Section
new BoxView { HeightRequest = 1, Color = Colors.LightGray },
new Label { Text = "Entry (Single Line)", FontSize = 18, FontAttributes = FontAttributes.Bold },
CreateEntry("Enter your name...", e => _entryOutput.Text = $"You typed: {e.Text}"),
_entryOutput,
CreateEntry("Enter your email...", null, Keyboard.Email),
new Label { Text = "Email keyboard type", FontSize = 12, TextColor = Colors.Gray },
CreatePasswordEntry("Enter password..."),
new Label { Text = "Password field (text hidden)", FontSize = 12, TextColor = Colors.Gray },
// SearchBar Section
new BoxView { HeightRequest = 1, Color = Colors.LightGray },
new Label { Text = "SearchBar", FontSize = 18, FontAttributes = FontAttributes.Bold },
CreateSearchBar(),
_searchOutput,
// Editor Section
new BoxView { HeightRequest = 1, Color = Colors.LightGray },
new Label { Text = "Editor (Multi-line)", FontSize = 18, FontAttributes = FontAttributes.Bold },
CreateEditor(),
_editorOutput,
// Instructions
new BoxView { HeightRequest = 1, Color = Colors.LightGray },
new Frame
{
BackgroundColor = Color.FromArgb("#E3F2FD"),
CornerRadius = 8,
Padding = new Thickness(15),
Content = new VerticalStackLayout
{
Spacing = 5,
Children =
{
new Label
{
Text = "Keyboard Shortcuts",
FontAttributes = FontAttributes.Bold
},
new Label { Text = "Ctrl+A: Select all" },
new Label { Text = "Ctrl+C: Copy" },
new Label { Text = "Ctrl+V: Paste" },
new Label { Text = "Ctrl+X: Cut" },
new Label { Text = "Home/End: Move to start/end" },
new Label { Text = "Shift+Arrow: Select text" }
}
}
}
}
}
};
}
private Entry CreateEntry(string placeholder, Action<Entry>? onTextChanged, Keyboard? keyboard = null)
{
var entry = new Entry
{
Placeholder = placeholder,
FontSize = 14
};
if (keyboard != null)
{
entry.Keyboard = keyboard;
}
if (onTextChanged != null)
{
entry.TextChanged += (s, e) => onTextChanged(entry);
}
return entry;
}
private Entry CreatePasswordEntry(string placeholder)
{
return new Entry
{
Placeholder = placeholder,
FontSize = 14,
IsPassword = true
};
}
private SearchBar CreateSearchBar()
{
var searchBar = new SearchBar
{
Placeholder = "Search for items..."
};
searchBar.TextChanged += (s, e) =>
{
_searchOutput.Text = $"Searching: {e.NewTextValue}";
};
searchBar.SearchButtonPressed += (s, e) =>
{
_searchOutput.Text = $"Search submitted: {searchBar.Text}";
};
return searchBar;
}
private Editor CreateEditor()
{
var editor = new Editor
{
Placeholder = "Enter multiple lines of text here...\nPress Enter to create new lines.",
HeightRequest = 120,
FontSize = 14
};
editor.TextChanged += (s, e) =>
{
var lineCount = string.IsNullOrEmpty(e.NewTextValue) ? 0 : e.NewTextValue.Split('\n').Length;
_editorOutput.Text = $"Lines: {lineCount}, Characters: {e.NewTextValue?.Length ?? 0}";
};
return editor;
}
}

View File

@@ -0,0 +1,19 @@
// Platforms/Linux/Program.cs - Linux platform entry point
// Same pattern as Android's MainActivity or iOS's AppDelegate
using Microsoft.Maui.Platform.Linux;
using Microsoft.Maui.Platform.Linux.Hosting;
namespace ShellDemo;
class Program
{
static void Main(string[] args)
{
// Create the shared MAUI app
var app = MauiProgram.CreateMauiApp();
// Run on Linux platform
LinuxApplication.Run(app, args);
}
}

157
samples/ShellDemo/README.md Normal file
View File

@@ -0,0 +1,157 @@
# ShellDemo Sample
A comprehensive control showcase application demonstrating all OpenMaui Linux controls with Shell navigation and flyout menu.
## Features
- **Shell Navigation** - Flyout menu with multiple pages
- **Route-Based Navigation** - Push navigation with registered routes
- **All Core Controls** - Button, Entry, Editor, CheckBox, Switch, Slider, Picker, etc.
- **CollectionView** - Lists with selection and data binding
- **Progress Indicators** - ProgressBar and ActivityIndicator with animations
- **Grid Layouts** - Complex multi-column/row layouts
- **Event Logging** - Real-time event feedback panel
## Pages
| Page | Controls Demonstrated |
|------|----------------------|
| **Home** | Welcome screen, navigation overview |
| **Buttons** | Button styles, colors, states, click/press/release events |
| **Text Input** | Entry, Editor, SearchBar, password fields, keyboard types |
| **Selection** | CheckBox, Switch, Slider with colors and states |
| **Pickers** | Picker, DatePicker, TimePicker with styling |
| **Lists** | CollectionView with selection, custom items |
| **Progress** | ProgressBar, ActivityIndicator, animated demos |
| **Grids** | Grid layouts with row/column definitions |
| **About** | App information |
## Architecture
```
ShellDemo/
├── App.cs # AppShell definition with flyout
├── Program.cs # Linux platform bootstrap
├── MauiProgram.cs # MAUI app builder
└── Pages/
├── HomePage.cs # Welcome page
├── ButtonsPage.cs # Button demonstrations
├── TextInputPage.cs # Entry, Editor, SearchBar
├── SelectionPage.cs # CheckBox, Switch, Slider
├── PickersPage.cs # Picker, DatePicker, TimePicker
├── ListsPage.cs # CollectionView demos
├── ProgressPage.cs # ProgressBar, ActivityIndicator
├── GridsPage.cs # Grid layout demos
├── DetailPage.cs # Push navigation target
└── AboutPage.cs # About information
```
## Shell Configuration
```csharp
public class AppShell : Shell
{
public AppShell()
{
FlyoutBehavior = FlyoutBehavior.Flyout;
Title = "OpenMaui Controls Demo";
// Register routes for push navigation
Routing.RegisterRoute("detail", typeof(DetailPage));
// Add flyout items
Items.Add(CreateFlyoutItem("Home", typeof(HomePage)));
Items.Add(CreateFlyoutItem("Buttons", typeof(ButtonsPage)));
// ...more items
}
}
```
## Control Demonstrations
### Buttons Page
- Default, styled, and transparent buttons
- Color variations (Primary, Success, Warning, Danger)
- Enabled/disabled state toggling
- Wide, tall, and round button shapes
- Pressed, clicked, released event handling
### Text Input Page
- Entry with placeholder and text change events
- Password entry with hidden text
- Email keyboard type
- SearchBar with search button
- Multi-line Editor
- Keyboard shortcuts guide
### Selection Page
- CheckBox with colors and disabled state
- Switch with OnColor customization
- Slider with min/max range and track colors
### Pickers Page
- Picker with items and selection events
- DatePicker with date range limits
- TimePicker with time selection
- Styled pickers with custom colors
### Lists Page
- CollectionView with string items
- CollectionView with custom data types (ColorItem, ContactItem)
- Selection handling and event feedback
### Progress Page
- ProgressBar at various percentages
- Colored progress bars
- ActivityIndicator running/stopped states
- Colored activity indicators
- Interactive slider-controlled progress
- Animated progress simulation
## Building and Running
```bash
# From the maui-linux-push directory
cd samples/ShellDemo
dotnet publish -c Release -r linux-arm64
# Run on Linux
./bin/Release/net9.0/linux-arm64/publish/ShellDemo
```
## Event Logging
Each page features an event log panel that displays control interactions in real-time:
```
[14:32:15] 3. Button clicked: Primary
[14:32:12] 2. Slider value: 75
[14:32:08] 1. CheckBox: Checked
```
## Controls Reference
| Control | Properties Demonstrated |
|---------|------------------------|
| Button | Text, BackgroundColor, TextColor, CornerRadius, IsEnabled, WidthRequest, HeightRequest |
| Entry | Placeholder, Text, IsPassword, Keyboard, FontSize |
| Editor | Placeholder, Text, HeightRequest |
| SearchBar | Placeholder, Text, SearchButtonPressed |
| CheckBox | IsChecked, Color, IsEnabled |
| Switch | IsToggled, OnColor, IsEnabled |
| Slider | Minimum, Maximum, Value, MinimumTrackColor, MaximumTrackColor, ThumbColor |
| Picker | Title, Items, SelectedIndex, TextColor, TitleColor |
| DatePicker | Date, MinimumDate, MaximumDate, TextColor |
| TimePicker | Time, TextColor |
| CollectionView | ItemsSource, SelectionMode, SelectionChanged, HeightRequest |
| ProgressBar | Progress, ProgressColor |
| ActivityIndicator | IsRunning, Color |
| Label | Text, FontSize, FontAttributes, TextColor |
| Frame | CornerRadius, Padding, BackgroundColor |
| Grid | RowDefinitions, ColumnDefinitions, RowSpacing, ColumnSpacing |
| StackLayout | Spacing, Padding, Orientation |
| ScrollView | Content scrolling |
## License
MIT License - See repository root for details.

View File

@@ -9,7 +9,7 @@
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="../../Microsoft.Maui.Controls.Linux.csproj" />
<ProjectReference Include="../../OpenMaui.Controls.Linux.csproj" />
</ItemGroup>
</Project>

21
samples/TodoApp/App.cs Normal file
View File

@@ -0,0 +1,21 @@
// TodoApp - Main Application with NavigationPage
using Microsoft.Maui.Controls;
namespace TodoApp;
public class App : Application
{
public static NavigationPage? NavigationPage { get; private set; }
public App()
{
NavigationPage = new NavigationPage(new TodoListPage())
{
Title = "OpenMaui Todo App",
BarBackgroundColor = Color.FromArgb("#2196F3"),
BarTextColor = Colors.White
};
MainPage = NavigationPage;
}
}

View File

@@ -0,0 +1,22 @@
// MauiProgram.cs - MAUI app configuration
using Microsoft.Maui.Hosting;
using Microsoft.Maui.Platform.Linux.Hosting;
namespace TodoApp;
public static class MauiProgram
{
public static MauiApp CreateMauiApp()
{
var builder = MauiApp.CreateBuilder();
// Configure the app
builder.UseMauiApp<App>();
// Add Linux platform support with all handlers
builder.UseLinux();
return builder.Build();
}
}

View File

@@ -0,0 +1,79 @@
<?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="TodoApp.NewTodoPage"
Title="New Task"
BackgroundColor="#F5F7FA">
<ContentPage.Resources>
<Color x:Key="PrimaryColor">#5C6BC0</Color>
<Color x:Key="AccentColor">#26A69A</Color>
<Color x:Key="TextPrimary">#212121</Color>
<Color x:Key="TextSecondary">#757575</Color>
<Color x:Key="CardBackground">#FFFFFF</Color>
<Color x:Key="BorderColor">#E8EAF6</Color>
</ContentPage.Resources>
<ContentPage.ToolbarItems>
<ToolbarItem Text="Save" Clicked="OnSaveClicked" />
</ContentPage.ToolbarItems>
<Grid RowDefinitions="Auto,Auto,Auto,*" RowSpacing="16" Padding="20">
<!-- Header -->
<VerticalStackLayout Grid.Row="0" Spacing="4">
<Label Text="Create a new task"
FontSize="24"
TextColor="{StaticResource TextPrimary}" />
<Label Text="Fill in the details below"
FontSize="14"
TextColor="{StaticResource TextSecondary}" />
</VerticalStackLayout>
<!-- Title Section -->
<VerticalStackLayout Grid.Row="1" Spacing="8">
<Label Text="TITLE"
FontSize="13"
FontAttributes="Bold"
TextColor="{StaticResource PrimaryColor}" />
<Border BackgroundColor="{StaticResource CardBackground}"
Stroke="{StaticResource BorderColor}"
StrokeThickness="1"
Padding="16,12">
<Border.StrokeShape>
<RoundRectangle CornerRadius="10" />
</Border.StrokeShape>
<Entry x:Name="TitleEntry"
Placeholder="What needs to be done?"
FontSize="18"
TextColor="{StaticResource TextPrimary}"
PlaceholderColor="{StaticResource TextSecondary}" />
</Border>
</VerticalStackLayout>
<!-- Notes Label -->
<Label Grid.Row="2"
Text="NOTES"
FontSize="13"
FontAttributes="Bold"
TextColor="{StaticResource PrimaryColor}" />
<!-- Notes Section (fills remaining space) -->
<Border Grid.Row="3"
BackgroundColor="{StaticResource CardBackground}"
Stroke="{StaticResource BorderColor}"
StrokeThickness="1"
Padding="16,12">
<Border.StrokeShape>
<RoundRectangle CornerRadius="10" />
</Border.StrokeShape>
<Editor x:Name="NotesEditor"
Placeholder="Add notes (optional)..."
FontSize="14"
TextColor="{StaticResource TextPrimary}"
PlaceholderColor="{StaticResource TextSecondary}"
VerticalOptions="Fill" />
</Border>
</Grid>
</ContentPage>

View File

@@ -0,0 +1,31 @@
// NewTodoPage - Create a new todo item
using Microsoft.Maui.Controls;
using Microsoft.Maui.Graphics;
namespace TodoApp;
public partial class NewTodoPage : ContentPage
{
private readonly TodoService _service = TodoService.Instance;
public NewTodoPage()
{
InitializeComponent();
}
private async void OnSaveClicked(object? sender, EventArgs e)
{
var title = TitleEntry.Text?.Trim();
if (string.IsNullOrEmpty(title))
{
TitleEntry.Placeholder = "Title is required!";
TitleEntry.PlaceholderColor = Colors.Red;
return;
}
_service.AddTodo(title, NotesEditor.Text ?? "");
await Navigation.PopAsync();
}
}

View File

@@ -0,0 +1,67 @@
// Program.cs - Linux platform entry point
using Microsoft.Maui.Platform.Linux;
namespace TodoApp;
class Program
{
static void Main(string[] args)
{
// Redirect console output to a log file for debugging
var logPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), "todoapp.log");
using var logWriter = new StreamWriter(logPath, append: false) { AutoFlush = true };
var multiWriter = new MultiTextWriter(Console.Out, logWriter);
Console.SetOut(multiWriter);
Console.SetError(multiWriter);
// Global exception handler
AppDomain.CurrentDomain.UnhandledException += (sender, e) =>
{
var ex = e.ExceptionObject as Exception;
Console.WriteLine($"[FATAL] Unhandled exception: {ex?.GetType().Name}: {ex?.Message}");
Console.WriteLine($"[FATAL] Stack trace: {ex?.StackTrace}");
if (ex?.InnerException != null)
{
Console.WriteLine($"[FATAL] Inner exception: {ex.InnerException.GetType().Name}: {ex.InnerException.Message}");
Console.WriteLine($"[FATAL] Inner stack trace: {ex.InnerException.StackTrace}");
}
};
TaskScheduler.UnobservedTaskException += (sender, e) =>
{
Console.WriteLine($"[FATAL] Unobserved task exception: {e.Exception?.GetType().Name}: {e.Exception?.Message}");
Console.WriteLine($"[FATAL] Stack trace: {e.Exception?.StackTrace}");
e.SetObserved(); // Prevent crash
};
Console.WriteLine($"[Program] Starting TodoApp at {DateTime.Now}");
Console.WriteLine($"[Program] Log file: {logPath}");
try
{
// Create the MAUI app with all handlers registered
var app = MauiProgram.CreateMauiApp();
// Run on Linux platform
LinuxApplication.Run(app, args);
}
catch (Exception ex)
{
Console.WriteLine($"[FATAL] Exception in Main: {ex.GetType().Name}: {ex.Message}");
Console.WriteLine($"[FATAL] Stack trace: {ex.StackTrace}");
throw;
}
}
}
// Helper to write to both console and file
class MultiTextWriter : TextWriter
{
private readonly TextWriter[] _writers;
public MultiTextWriter(params TextWriter[] writers) => _writers = writers;
public override System.Text.Encoding Encoding => System.Text.Encoding.UTF8;
public override void Write(char value) { foreach (var w in _writers) w.Write(value); }
public override void WriteLine(string? value) { foreach (var w in _writers) w.WriteLine(value); }
public override void Flush() { foreach (var w in _writers) w.Flush(); }
}

111
samples/TodoApp/README.md Normal file
View File

@@ -0,0 +1,111 @@
# TodoApp Sample
A complete task management application demonstrating OpenMaui Linux capabilities with real-world XAML patterns.
## Features
- **NavigationPage** - Full page navigation with back button support
- **CollectionView** - Scrollable list with data binding and selection
- **XAML Data Binding** - Value converters for dynamic styling
- **DisplayAlert Dialogs** - Confirmation dialogs for delete actions
- **Grid Layouts** - Complex layouts with star sizing for expanding content
- **Entry & Editor** - Single and multi-line text input
- **Border with RoundRectangle** - Modern card-style UI
- **ToolbarItems** - Navigation bar actions
## Screenshots
The app consists of three pages:
1. **TodoListPage** - Shows all tasks with completion status indicators
2. **NewTodoPage** - Create a new task with title and notes
3. **TodoDetailPage** - View/edit task details, mark complete, or delete
## Architecture
```
TodoApp/
├── App.cs # Application entry with NavigationPage
├── Program.cs # Linux platform bootstrap
├── MauiProgram.cs # MAUI app builder
├── TodoItem.cs # Data model
├── TodoService.cs # In-memory data store
├── TodoListPage.xaml(.cs) # Main list view
├── NewTodoPage.xaml(.cs) # Create task page
└── TodoDetailPage.xaml(.cs) # Task detail/edit page
```
## XAML Highlights
### Value Converters
The app uses custom converters for dynamic styling based on completion status:
- `CompletedToColorConverter` - Gray text for completed items
- `CompletedToTextDecorationsConverter` - Strikethrough for completed items
- `CompletedToOpacityConverter` - Fade completed items
- `AlternatingRowColorConverter` - Alternating background colors
### ResourceDictionary
```xml
<ContentPage.Resources>
<Color x:Key="PrimaryColor">#5C6BC0</Color>
<Color x:Key="AccentColor">#26A69A</Color>
<Color x:Key="TextPrimary">#212121</Color>
</ContentPage.Resources>
```
### CollectionView with DataTemplate
```xml
<CollectionView SelectionMode="Single" SelectionChanged="OnSelectionChanged">
<CollectionView.ItemTemplate>
<DataTemplate x:DataType="local:TodoItem">
<Border BackgroundColor="{StaticResource CardBackground}">
<Label Text="{Binding Title}" />
</Border>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
```
### Grid with Star Rows (Expanding Editor)
```xml
<Grid RowDefinitions="Auto,Auto,*,Auto,Auto" Padding="20">
<!-- Row 0: Title section (Auto) -->
<!-- Row 1: Notes label (Auto) -->
<!-- Row 2: Editor - expands to fill (*) -->
<!-- Row 3: Status section (Auto) -->
<!-- Row 4: Created date (Auto) -->
</Grid>
```
## Building and Running
```bash
# From the maui-linux-push directory
cd samples/TodoApp
dotnet publish -c Release -r linux-arm64
# Run on Linux
./bin/Release/net9.0/linux-arm64/publish/TodoApp
```
## Controls Demonstrated
| Control | Usage |
|---------|-------|
| NavigationPage | App navigation container |
| ContentPage | Individual screens |
| CollectionView | Task list with selection |
| Grid | Page layouts |
| VerticalStackLayout | Vertical grouping |
| HorizontalStackLayout | Horizontal grouping |
| Label | Text display |
| Entry | Single-line input |
| Editor | Multi-line input |
| Button | Toolbar actions |
| Border | Card styling with rounded corners |
| CheckBox | Completion toggle |
| BoxView | Visual separators |
## License
MIT License - See repository root for details.

View File

@@ -0,0 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="../../OpenMaui.Controls.Linux.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,106 @@
<?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="TodoApp.TodoDetailPage"
Title="Task Details"
BackgroundColor="#F5F7FA">
<ContentPage.Resources>
<Color x:Key="PrimaryColor">#5C6BC0</Color>
<Color x:Key="AccentColor">#26A69A</Color>
<Color x:Key="DangerColor">#EF5350</Color>
<Color x:Key="TextPrimary">#212121</Color>
<Color x:Key="TextSecondary">#757575</Color>
<Color x:Key="CardBackground">#FFFFFF</Color>
<Color x:Key="BorderColor">#E8EAF6</Color>
</ContentPage.Resources>
<ContentPage.ToolbarItems>
<ToolbarItem Text="Delete" Clicked="OnDeleteClicked" />
<ToolbarItem Text="Save" Clicked="OnSaveClicked" />
</ContentPage.ToolbarItems>
<Grid RowDefinitions="Auto,Auto,*,Auto,Auto" RowSpacing="16" Padding="20">
<!-- Title Section -->
<VerticalStackLayout Grid.Row="0" Spacing="8">
<Label Text="TITLE"
FontSize="13"
FontAttributes="Bold"
TextColor="{StaticResource PrimaryColor}" />
<Border BackgroundColor="{StaticResource CardBackground}"
Stroke="{StaticResource BorderColor}"
StrokeThickness="1"
Padding="16,12">
<Border.StrokeShape>
<RoundRectangle CornerRadius="10" />
</Border.StrokeShape>
<Entry x:Name="TitleEntry"
Placeholder="Task title"
FontSize="18"
TextColor="{StaticResource TextPrimary}"
PlaceholderColor="{StaticResource TextSecondary}" />
</Border>
</VerticalStackLayout>
<!-- Notes Label -->
<Label Grid.Row="1"
Text="NOTES"
FontSize="13"
FontAttributes="Bold"
TextColor="{StaticResource PrimaryColor}" />
<!-- Notes Section (fills remaining space) -->
<Border Grid.Row="2"
BackgroundColor="{StaticResource CardBackground}"
Stroke="{StaticResource BorderColor}"
StrokeThickness="1"
Padding="16,12">
<Border.StrokeShape>
<RoundRectangle CornerRadius="10" />
</Border.StrokeShape>
<Editor x:Name="NotesEditor"
Placeholder="Add notes here..."
FontSize="14"
TextColor="{StaticResource TextPrimary}"
PlaceholderColor="{StaticResource TextSecondary}"
VerticalOptions="Fill" />
</Border>
<!-- Status Section -->
<VerticalStackLayout Grid.Row="3" Spacing="8">
<Label Text="STATUS"
FontSize="13"
FontAttributes="Bold"
TextColor="{StaticResource PrimaryColor}" />
<Border BackgroundColor="{StaticResource CardBackground}"
Stroke="{StaticResource BorderColor}"
StrokeThickness="1"
Padding="16,12">
<Border.StrokeShape>
<RoundRectangle CornerRadius="10" />
</Border.StrokeShape>
<HorizontalStackLayout Spacing="12">
<CheckBox x:Name="CompletedCheckBox"
Color="{StaticResource AccentColor}"
VerticalOptions="Center"
CheckedChanged="OnCompletedChanged" />
<Label x:Name="StatusLabel"
Text="In Progress"
FontSize="16"
TextColor="{StaticResource TextPrimary}"
VerticalOptions="Center" />
</HorizontalStackLayout>
</Border>
</VerticalStackLayout>
<!-- Created Date -->
<Label Grid.Row="4"
x:Name="CreatedLabel"
FontSize="13"
TextColor="{StaticResource TextSecondary}"
HorizontalOptions="Center"
HorizontalTextAlignment="Center" />
</Grid>
</ContentPage>

View File

@@ -0,0 +1,91 @@
// TodoDetailPage - View and edit a todo item
using Microsoft.Maui.Controls;
using Microsoft.Maui.Graphics;
using Microsoft.Maui.Platform;
namespace TodoApp;
public partial class TodoDetailPage : ContentPage
{
private readonly TodoItem _todo;
private readonly TodoService _service = TodoService.Instance;
// Colors for status label
private static readonly Color AccentColor = Color.FromArgb("#26A69A");
private static readonly Color TextPrimary = Color.FromArgb("#212121");
public TodoDetailPage(TodoItem todo)
{
try
{
Console.WriteLine($"[TodoDetailPage] Constructor starting for: {todo.Title}");
InitializeComponent();
Console.WriteLine($"[TodoDetailPage] InitializeComponent complete");
_todo = todo;
// Populate fields
Console.WriteLine($"[TodoDetailPage] Setting TitleEntry.Text");
TitleEntry.Text = _todo.Title;
Console.WriteLine($"[TodoDetailPage] Setting NotesEditor.Text");
NotesEditor.Text = _todo.Notes;
Console.WriteLine($"[TodoDetailPage] Setting CompletedCheckBox.IsChecked");
CompletedCheckBox.IsChecked = _todo.IsCompleted;
Console.WriteLine($"[TodoDetailPage] Calling UpdateStatusLabel");
UpdateStatusLabel(_todo.IsCompleted);
Console.WriteLine($"[TodoDetailPage] Setting CreatedLabel.Text");
CreatedLabel.Text = $"Created {_todo.CreatedAt:MMMM d, yyyy} at {_todo.CreatedAt:h:mm tt}";
Console.WriteLine($"[TodoDetailPage] Constructor complete");
}
catch (Exception ex)
{
Console.WriteLine($"[TodoDetailPage] EXCEPTION in constructor: {ex.GetType().Name}: {ex.Message}");
Console.WriteLine($"[TodoDetailPage] Stack trace: {ex.StackTrace}");
throw;
}
}
private void OnCompletedChanged(object? sender, Microsoft.Maui.Controls.CheckedChangedEventArgs e)
{
Console.WriteLine($"[TodoDetailPage] OnCompletedChanged: {e.Value}");
UpdateStatusLabel(e.Value);
}
private void UpdateStatusLabel(bool isCompleted)
{
if (StatusLabel == null)
{
Console.WriteLine($"[TodoDetailPage] UpdateStatusLabel: StatusLabel is null, skipping");
return;
}
Console.WriteLine($"[TodoDetailPage] UpdateStatusLabel: setting to {(isCompleted ? "Completed" : "In Progress")}");
StatusLabel.Text = isCompleted ? "Completed" : "In Progress";
StatusLabel.TextColor = isCompleted ? AccentColor : TextPrimary;
}
private async void OnSaveClicked(object? sender, EventArgs e)
{
_todo.Title = TitleEntry.Text ?? "";
_todo.Notes = NotesEditor.Text ?? "";
_todo.IsCompleted = CompletedCheckBox.IsChecked;
await Navigation.PopAsync();
}
private async void OnDeleteClicked(object? sender, EventArgs e)
{
// Show confirmation dialog
var confirmed = await LinuxDialogService.ShowAlertAsync(
"Delete Task",
$"Are you sure you want to delete \"{_todo.Title}\"? This action cannot be undone.",
"Delete",
"Cancel");
if (confirmed)
{
_service.DeleteTodo(_todo);
await Navigation.PopAsync();
}
}
}

View File

@@ -0,0 +1,81 @@
// TodoItem - Data model for a todo item
using System.ComponentModel;
namespace TodoApp;
public class TodoItem : INotifyPropertyChanged
{
private string _title = "";
private string _notes = "";
private bool _isCompleted;
private DateTime _dueDate;
public int Id { get; set; }
/// <summary>
/// Index in the collection for alternating row colors.
/// </summary>
public int Index { get; set; }
public string Title
{
get => _title;
set
{
if (_title != value)
{
_title = value;
OnPropertyChanged(nameof(Title));
}
}
}
public string Notes
{
get => _notes;
set
{
if (_notes != value)
{
_notes = value;
OnPropertyChanged(nameof(Notes));
}
}
}
public bool IsCompleted
{
get => _isCompleted;
set
{
if (_isCompleted != value)
{
_isCompleted = value;
OnPropertyChanged(nameof(IsCompleted));
}
}
}
public DateTime DueDate
{
get => _dueDate;
set
{
if (_dueDate != value)
{
_dueDate = value;
OnPropertyChanged(nameof(DueDate));
}
}
}
public DateTime CreatedAt { get; set; } = DateTime.Now;
public event PropertyChangedEventHandler? PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}

View File

@@ -0,0 +1,129 @@
<?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"
xmlns:local="clr-namespace:TodoApp"
x:Class="TodoApp.TodoListPage"
Title="My Tasks"
BackgroundColor="#F5F7FA">
<ContentPage.Resources>
<ResourceDictionary>
<!-- Colors -->
<Color x:Key="PrimaryColor">#5C6BC0</Color>
<Color x:Key="PrimaryDark">#3949AB</Color>
<Color x:Key="AccentColor">#26A69A</Color>
<Color x:Key="TextPrimary">#212121</Color>
<Color x:Key="TextSecondary">#757575</Color>
<Color x:Key="CardBackground">#FFFFFF</Color>
<Color x:Key="DividerColor">#E0E0E0</Color>
<Color x:Key="CompletedColor">#9E9E9E</Color>
<!-- Converters -->
<local:AlternatingRowColorConverter x:Key="AlternatingRowColorConverter" />
<local:CompletedToColorConverter x:Key="CompletedToColorConverter" />
<local:CompletedToTextDecorationsConverter x:Key="CompletedToTextDecorationsConverter" />
<local:CompletedToOpacityConverter x:Key="CompletedToOpacityConverter" />
</ResourceDictionary>
</ContentPage.Resources>
<ContentPage.ToolbarItems>
<ToolbarItem Text="+ Add" Clicked="OnAddClicked" />
</ContentPage.ToolbarItems>
<Grid RowDefinitions="*,Auto" Padding="0">
<!-- Task List -->
<CollectionView Grid.Row="0"
x:Name="TodoCollectionView"
SelectionMode="Single"
SelectionChanged="OnSelectionChanged"
VerticalOptions="FillAndExpand"
BackgroundColor="Transparent"
Margin="16,16,16,0">
<CollectionView.EmptyView>
<VerticalStackLayout VerticalOptions="Center"
HorizontalOptions="Center"
Padding="40">
<Label Text="No tasks yet"
FontSize="22"
TextColor="{StaticResource TextSecondary}"
HorizontalOptions="Center" />
<Label Text="Tap '+ Add' to create your first task"
FontSize="14"
TextColor="{StaticResource TextSecondary}"
HorizontalOptions="Center"
Margin="0,8,0,0" />
</VerticalStackLayout>
</CollectionView.EmptyView>
<CollectionView.ItemTemplate>
<DataTemplate x:DataType="local:TodoItem">
<!-- Card-style item -->
<Grid Padding="0,6" BackgroundColor="Transparent">
<Border StrokeThickness="0"
BackgroundColor="{StaticResource CardBackground}"
Padding="16,14"
Opacity="{Binding IsCompleted, Converter={StaticResource CompletedToOpacityConverter}}">
<Border.StrokeShape>
<RoundRectangle CornerRadius="12" />
</Border.StrokeShape>
<Grid ColumnDefinitions="Auto,*" ColumnSpacing="16">
<!-- Completion indicator -->
<Border Grid.Column="0"
WidthRequest="8"
HeightRequest="44"
Margin="0"
BackgroundColor="{Binding IsCompleted, Converter={StaticResource CompletedToColorConverter}, ConverterParameter=indicator}"
VerticalOptions="Center">
<Border.StrokeShape>
<RoundRectangle CornerRadius="4" />
</Border.StrokeShape>
</Border>
<!-- Content -->
<VerticalStackLayout Grid.Column="1"
Spacing="4"
VerticalOptions="Center">
<!-- Title -->
<Label Text="{Binding Title}"
FontSize="16"
TextColor="{Binding IsCompleted, Converter={StaticResource CompletedToColorConverter}}"
TextDecorations="{Binding IsCompleted, Converter={StaticResource CompletedToTextDecorationsConverter}}" />
<!-- Notes preview -->
<Label Text="{Binding Notes}"
FontSize="13"
TextColor="{Binding IsCompleted, Converter={StaticResource CompletedToColorConverter}, ConverterParameter=notes}"
MaxLines="2"
LineBreakMode="TailTruncation" />
</VerticalStackLayout>
</Grid>
</Border>
</Grid>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
<!-- Footer Stats -->
<Border Grid.Row="1"
BackgroundColor="{StaticResource PrimaryColor}"
StrokeThickness="0"
Padding="24,14"
Margin="0">
<Grid ColumnDefinitions="Auto,Auto" ColumnSpacing="6" HorizontalOptions="Center" VerticalOptions="Center">
<Label Grid.Column="0"
Text="Tasks:"
FontSize="15"
TextColor="White" />
<Label Grid.Column="1"
x:Name="StatsLabel"
FontSize="15"
TextColor="White"
Opacity="0.9" />
</Grid>
</Border>
</Grid>
</ContentPage>

View File

@@ -0,0 +1,174 @@
// TodoListPage - Main page for viewing todos with XAML support
using Microsoft.Maui.Controls;
using Microsoft.Maui.Graphics;
using System.Globalization;
namespace TodoApp;
public partial class TodoListPage : ContentPage
{
private readonly TodoService _service = TodoService.Instance;
public TodoListPage()
{
Console.WriteLine("[TodoListPage] Constructor starting");
InitializeComponent();
TodoCollectionView.ItemsSource = _service.Todos;
UpdateStats();
Console.WriteLine("[TodoListPage] Constructor finished");
}
protected override void OnAppearing()
{
Console.WriteLine("[TodoListPage] OnAppearing called - refreshing CollectionView");
base.OnAppearing();
// Refresh indexes for alternating row colors
_service.RefreshIndexes();
// Refresh the collection view
TodoCollectionView.ItemsSource = null;
TodoCollectionView.ItemsSource = _service.Todos;
Console.WriteLine($"[TodoListPage] ItemsSource set with {_service.Todos.Count} items");
UpdateStats();
}
private async void OnAddClicked(object sender, EventArgs e)
{
await Navigation.PushAsync(new NewTodoPage());
}
private async void OnSelectionChanged(object? sender, SelectionChangedEventArgs e)
{
try
{
Console.WriteLine($"[TodoListPage] OnSelectionChanged: {e.CurrentSelection.Count} items selected");
if (e.CurrentSelection.FirstOrDefault() is TodoItem todo)
{
Console.WriteLine($"[TodoListPage] Navigating to TodoDetailPage for: {todo.Title}");
TodoCollectionView.SelectedItem = null; // Deselect
var detailPage = new TodoDetailPage(todo);
Console.WriteLine($"[TodoListPage] Created TodoDetailPage, pushing...");
await Navigation.PushAsync(detailPage);
Console.WriteLine($"[TodoListPage] Navigation complete");
}
}
catch (Exception ex)
{
Console.WriteLine($"[TodoListPage] EXCEPTION in OnSelectionChanged: {ex.GetType().Name}: {ex.Message}");
Console.WriteLine($"[TodoListPage] Stack trace: {ex.StackTrace}");
}
}
private void UpdateStats()
{
var completed = _service.CompletedCount;
var total = _service.TotalCount;
if (total == 0)
{
StatsLabel.Text = "";
}
else
{
StatsLabel.Text = $"{completed} of {total} completed";
}
}
}
/// <summary>
/// Converter for alternating row background colors.
/// </summary>
public class AlternatingRowColorConverter : IValueConverter
{
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
{
if (value is int index)
{
return index % 2 == 0 ? Colors.White : Color.FromArgb("#F5F5F5");
}
return Colors.White;
}
public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
/// <summary>
/// Converter for completed task text color and indicator color.
/// </summary>
public class CompletedToColorConverter : IValueConverter
{
// Define colors
private static readonly Color PrimaryColor = Color.FromArgb("#5C6BC0");
private static readonly Color AccentColor = Color.FromArgb("#26A69A");
private static readonly Color CompletedColor = Color.FromArgb("#9E9E9E");
private static readonly Color TextPrimary = Color.FromArgb("#212121");
private static readonly Color TextSecondary = Color.FromArgb("#757575");
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
{
bool isCompleted = value is bool b && b;
string param = parameter as string ?? "";
// Indicator bar color
if (param == "indicator")
{
return isCompleted ? CompletedColor : AccentColor;
}
// Text colors
if (isCompleted)
{
return CompletedColor;
}
else
{
return param == "notes" ? TextSecondary : TextPrimary;
}
}
public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
/// <summary>
/// Converter for completed task text decorations (strikethrough).
/// </summary>
public class CompletedToTextDecorationsConverter : IValueConverter
{
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
{
bool isCompleted = value is bool b && b;
return isCompleted ? TextDecorations.Strikethrough : TextDecorations.None;
}
public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
/// <summary>
/// Converter for completed task opacity (slightly faded when complete).
/// </summary>
public class CompletedToOpacityConverter : IValueConverter
{
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
{
bool isCompleted = value is bool b && b;
return isCompleted ? 0.7 : 1.0;
}
public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}

View File

@@ -0,0 +1,61 @@
// TodoService - Manages todo items
using System.Collections.ObjectModel;
namespace TodoApp;
public class TodoService
{
private static TodoService? _instance;
public static TodoService Instance => _instance ??= new TodoService();
private int _nextId = 1;
public ObservableCollection<TodoItem> Todos { get; } = new();
private TodoService()
{
// Add sample todos with varying lengths to test MaxLines=2 with ellipsis
AddTodo("Learn OpenMaui Linux", "Explore the SkiaSharp-based rendering engine for .NET MAUI on Linux desktop. This is a very long description that should wrap to multiple lines and demonstrate the ellipsis truncation feature when MaxLines is set to 2.");
AddTodo("Build amazing apps", "Create cross-platform applications that run on Windows, macOS, iOS, Android, and Linux! With OpenMaui, you can write once and deploy everywhere.");
AddTodo("Share with the community", "Contribute to the open-source project and help others build great Linux apps. Join our growing community of developers who are passionate about bringing .NET MAUI to Linux.");
}
public TodoItem AddTodo(string title, string notes = "")
{
var todo = new TodoItem
{
Id = _nextId++,
Index = Todos.Count, // Set index for alternating row colors
Title = title,
Notes = notes,
DueDate = DateTime.Today.AddDays(7)
};
Todos.Add(todo);
return todo;
}
/// <summary>
/// Refreshes the Index property on all items for alternating row colors.
/// </summary>
public void RefreshIndexes()
{
for (int i = 0; i < Todos.Count; i++)
{
Todos[i].Index = i;
}
}
public TodoItem? GetTodo(int id)
{
return Todos.FirstOrDefault(t => t.Id == id);
}
public void DeleteTodo(TodoItem todo)
{
Todos.Remove(todo);
}
public int CompletedCount => Todos.Count(t => t.IsCompleted);
public int TotalCount => Todos.Count;
}