2
0

Compare commits

17 Commits

Author SHA1 Message Date
c8b2df000b docs(ci): add comprehensive guide for OpenMaui Linux samples
Some checks failed
CI / Build Samples (macOS) (push) Failing after 8s
CI / Build Samples (Linux x64) (push) Failing after 17s
CI / Build Samples (Linux arm64) (push) Failing after 23s
CI / Build Samples (Windows) (push) Failing after 7h59m59s
Add 1200+ line GUIDE.md documenting OpenMaui Linux sample applications. Covers prerequisites, installation, building, and running three sample apps (ShellDemo, TodoApp, WebViewDemo).

Includes detailed sections on:
- Creating new Linux MAUI applications
- Adding OpenMaui Linux support to existing projects
- Implementing navigation patterns (Shell and NavigationPage)
- Working with themes and styling
- API reference for MauiProgram, platform entry points, and dialogs
- Troubleshooting common issues

Update README.md with improved overview and links to user guide.
2026-01-29 23:19:02 -05:00
0c9563ccb5 Remove file with invalid Windows filename (pipe character)
Some checks failed
CI / Build Samples (Windows) (push) Failing after 8h0m6s
CI / Build Samples (macOS) (push) Failing after 8s
CI / Build Samples (Linux x64) (push) Failing after 15s
CI / Build Samples (Linux arm64) (push) Failing after 22s
2026-01-29 22:58:56 -05:00
dcabf31c7b Move CLAUDE.md to vault
Some checks failed
CI / Build Samples (Linux x64) (push) Failing after 17s
CI / Build Samples (Linux arm64) (push) Failing after 34s
CI / Build Samples (Windows) (push) Failing after 7h59m44s
CI / Build Samples (macOS) (push) Failing after 11s
2026-01-30 03:49:28 +00:00
8a3e3f6283 auto app building
Some checks failed
CI / Build Samples (Windows) (push) Failing after 7h59m55s
CI / Build Samples (Linux x64) (push) Failing after 18s
CI / Build Samples (Linux arm64) (push) Has been cancelled
CI / Build Samples (macOS) (push) Has been cancelled
2026-01-24 08:00:33 +00:00
73ee0b2bb3 workflows set 2026-01-24 07:54:02 +00:00
8e51e5ba22 Build target fixes 2026-01-24 06:13:28 +00:00
01dec998f8 Update .gitignore 2026-01-24 04:37:59 +00:00
cdda247309 Fixes for appimage installs 2026-01-24 04:04:42 +00:00
751b3d544d Flyout fixes 2026-01-24 03:17:48 +00:00
ca48355f8a todo app fixes 2026-01-17 15:06:30 +00:00
26b39a403a web finalized 2026-01-17 08:34:04 +00:00
e9d4b84790 Browser work 2026-01-17 07:52:05 +00:00
71f6aa1179 Improvements 2026-01-17 05:42:44 +00:00
fc113166d6 Add missing images 2026-01-16 03:07:56 +00:00
8531f2b972 Icon work - and apptheming 2026-01-11 13:34:36 -05:00
50cc186d6d AppTheming and XAML 2026-01-11 12:33:48 -05:00
f87ea17a6f Restructured sample pages folder 2026-01-11 10:37:01 -05:00
119 changed files with 6494 additions and 3112 deletions

72
.gitea/workflows/ci.yml Normal file
View File

@@ -0,0 +1,72 @@
# OpenMaui Linux Samples CI
name: CI
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
env:
DOTNET_NOLOGO: true
DOTNET_CLI_TELEMETRY_OPTOUT: true
jobs:
build-linux-x64:
name: Build Samples (Linux x64)
runs-on: linux-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Restore dependencies
run: dotnet restore
- name: Build ShellDemo
run: dotnet build ShellDemo/ShellDemo.csproj --configuration Release
- name: Build TodoApp
run: dotnet build TodoApp/TodoApp.csproj --configuration Release
- name: Build WebViewDemo
run: dotnet build WebViewDemo/WebViewDemo.csproj --configuration Release
build-linux-arm64:
name: Build Samples (Linux arm64)
runs-on: macos-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Build in Docker (arm64)
run: |
docker run --rm --platform linux/arm64 \
-v "$PWD:/src" -w /src \
mcr.microsoft.com/dotnet/sdk:9.0 \
bash -c "dotnet restore && dotnet build --configuration Release"
build-windows:
name: Build Samples (Windows)
runs-on: windows-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Restore dependencies
run: dotnet restore
- name: Build all samples
run: dotnet build --configuration Release
build-macos:
name: Build Samples (macOS)
runs-on: macos-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Restore dependencies
run: dotnet restore
- name: Build all samples
run: dotnet build --configuration Release

View File

@@ -0,0 +1,155 @@
# OpenMaui Linux Samples Release - Build AppImages
name: Release
on:
release:
types: [published]
push:
tags:
- 'v*'
env:
DOTNET_NOLOGO: true
DOTNET_CLI_TELEMETRY_OPTOUT: true
GITEA_PACKAGES_URL: https://git.marketally.com/api/packages/AuroraOSS/nuget/index.json
jobs:
build-appimages-x64:
name: Build AppImages (Linux x64)
runs-on: linux-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Install appimagetool
run: |
wget -q https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage -O /tmp/appimagetool
chmod +x /tmp/appimagetool
sudo mv /tmp/appimagetool /usr/local/bin/appimagetool
- name: Add Gitea NuGet source
run: |
dotnet nuget add source "$GITEA_PACKAGES_URL" --name gitea || true
- name: Install OpenMaui.AppImage tool
run: |
dotnet tool install --global OpenMaui.AppImage || \
dotnet tool install --global OpenMaui.AppImage --add-source "$GITEA_PACKAGES_URL" || \
dotnet tool update --global OpenMaui.AppImage
- name: Publish ShellDemo
run: dotnet publish ShellDemo/ShellDemo.csproj -c Release -r linux-x64 --self-contained -o ./publish/ShellDemo
- name: Publish TodoApp
run: dotnet publish TodoApp/TodoApp.csproj -c Release -r linux-x64 --self-contained -o ./publish/TodoApp
- name: Publish WebViewDemo
run: dotnet publish WebViewDemo/WebViewDemo.csproj -c Release -r linux-x64 --self-contained -o ./publish/WebViewDemo
- name: Create ShellDemo AppImage
run: |
mkdir -p ./appimages
~/.dotnet/tools/openmaui-appimage \
-i ./publish/ShellDemo \
-o ./appimages/ShellDemo-x64.AppImage \
-n "Shell Demo"
- name: Create TodoApp AppImage
run: |
~/.dotnet/tools/openmaui-appimage \
-i ./publish/TodoApp \
-o ./appimages/TodoApp-x64.AppImage \
-n "Todo App"
- name: Create WebViewDemo AppImage
run: |
~/.dotnet/tools/openmaui-appimage \
-i ./publish/WebViewDemo \
-o ./appimages/WebViewDemo-x64.AppImage \
-n "WebView Demo"
- name: Upload AppImages
uses: actions/upload-artifact@v3
with:
name: appimages-x64
path: ./appimages/*.AppImage
build-appimages-arm64:
name: Build AppImages (Linux arm64)
runs-on: macos-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Build and package in Docker (arm64)
run: |
mkdir -p ./appimages
docker run --rm --platform linux/arm64 \
-v "$PWD:/src" -w /src \
-e GITEA_PACKAGES_URL="$GITEA_PACKAGES_URL" \
mcr.microsoft.com/dotnet/sdk:9.0 \
bash -c "
set -e
# Install appimagetool
apt-get update && apt-get install -y wget file
wget -q https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-aarch64.AppImage -O /tmp/appimagetool
chmod +x /tmp/appimagetool
mv /tmp/appimagetool /usr/local/bin/appimagetool
# Add Gitea source and install tool
dotnet nuget add source \"\$GITEA_PACKAGES_URL\" --name gitea || true
dotnet tool install --global OpenMaui.AppImage || \
dotnet tool install --global OpenMaui.AppImage --add-source \"\$GITEA_PACKAGES_URL\" || true
export PATH=\"\$PATH:/root/.dotnet/tools\"
# Publish samples
dotnet publish ShellDemo/ShellDemo.csproj -c Release -r linux-arm64 --self-contained -o ./publish/ShellDemo
dotnet publish TodoApp/TodoApp.csproj -c Release -r linux-arm64 --self-contained -o ./publish/TodoApp
dotnet publish WebViewDemo/WebViewDemo.csproj -c Release -r linux-arm64 --self-contained -o ./publish/WebViewDemo
# Create AppImages
mkdir -p ./appimages
openmaui-appimage -i ./publish/ShellDemo -o ./appimages/ShellDemo-arm64.AppImage -n 'Shell Demo'
openmaui-appimage -i ./publish/TodoApp -o ./appimages/TodoApp-arm64.AppImage -n 'Todo App'
openmaui-appimage -i ./publish/WebViewDemo -o ./appimages/WebViewDemo-arm64.AppImage -n 'WebView Demo'
"
- name: Upload AppImages
uses: actions/upload-artifact@v3
with:
name: appimages-arm64
path: ./appimages/*.AppImage
create-release:
name: Attach to Release
needs: [build-appimages-x64, build-appimages-arm64]
runs-on: linux-latest
if: github.event_name == 'release'
steps:
- name: Download x64 AppImages
uses: actions/download-artifact@v3
with:
name: appimages-x64
path: ./release
- name: Download arm64 AppImages
uses: actions/download-artifact@v3
with:
name: appimages-arm64
path: ./release
- name: List release files
run: ls -la ./release/
- name: Upload to Gitea Release
run: |
for file in ./release/*.AppImage; do
filename=$(basename "$file")
curl -X POST \
-H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \
-H "Content-Type: application/octet-stream" \
--data-binary @"$file" \
"https://git.marketally.com/api/v1/repos/${{ github.repository }}/releases/${{ github.event.release.id }}/assets?name=$filename"
done

9
.gitignore vendored
View File

@@ -51,3 +51,12 @@ TestResults/
# Publish output # Publish output
publish/ publish/
# Flatpak build artifacts
.flatpak-builder/
*.flatpak
repo/
# AppImage artifacts
*.AppImage
*.AppDir/

5
Directory.Build.targets Normal file
View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<Project>
<!-- Auto-import OpenMaui targets for all sample projects on Linux -->
<Import Project="../maui-linux/build/OpenMaui.Controls.Linux.targets" Condition="$([MSBuild]::IsOSPlatform('Linux'))" />
</Project>

1226
GUIDE.md Normal file
View File

File diff suppressed because it is too large Load Diff

15
NuGet.config Normal file
View File

@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" protocolVersion="3" />
<add key="gitea" value="https://git.marketally.com/api/packages/AuroraOSS/nuget/index.json" />
</packageSources>
<packageSourceMapping>
<packageSource key="nuget.org">
<package pattern="*" />
</packageSource>
<packageSource key="gitea">
<package pattern="OpenMaui.*" />
</packageSource>
</packageSourceMapping>
</configuration>

792
README.md
View File

@@ -1,76 +1,782 @@
# OpenMaui Linux Samples # OpenMaui Linux Samples
Sample applications demonstrating [OpenMaui Linux](https://git.marketally.com/open-maui/maui-linux) - .NET MAUI on Linux. Sample applications demonstrating [OpenMaui Linux](https://git.marketally.com/open-maui/maui-linux) - .NET MAUI on Linux desktop with SkiaSharp rendering.
## Samples ## Table of Contents
| Sample | Description | - [Overview](#overview)
|--------|-------------| - [Sample Applications](#sample-applications)
| [TodoApp](./TodoApp/) | Full-featured task manager with NavigationPage, XAML data binding, and CollectionView | - [Requirements](#requirements)
| [ShellDemo](./ShellDemo/) | Comprehensive control showcase with Shell navigation and flyout menu | - [Installation](#installation)
- [Quick Start](#quick-start)
- [Building and Deployment](#building-and-deployment)
- [Sample Details](#sample-details)
- [TodoApp](#todoapp)
- [ShellDemo](#shelldemo)
- [WebViewDemo](#webviewdemo)
- [Project Structure](#project-structure)
- [Development Guide](#development-guide)
- [API Usage Examples](#api-usage-examples)
- [Related Resources](#related-resources)
- [License](#license)
## Overview
This repository contains production-ready sample applications showcasing **OpenMaui Linux** - a .NET MAUI implementation for Linux desktop environments. These samples demonstrate how to build cross-platform applications using familiar MAUI APIs with native Linux rendering via SkiaSharp and optional GTK integration.
**Key Features:**
- Full .NET MAUI API compatibility
- SkiaSharp-based rendering for high-performance graphics
- X11 window management support
- GTK integration for WebView and native dialogs
- Light/Dark theme support with dynamic switching
- Navigation (NavigationPage, Shell, push/pop)
- Data binding and MVVM patterns
- Comprehensive control coverage
## Sample Applications
| Sample | Description | Key Features |
|--------|-------------|--------------|
| [TodoApp](./TodoApp/) | Full-featured task manager | NavigationPage, XAML data binding, CollectionView, value converters, theme switching |
| [ShellDemo](./ShellDemo/) | Comprehensive control showcase | Shell navigation, flyout menu, all core controls, event logging |
| [WebViewDemo](./WebViewDemo/) | Web browser with WebKitGTK | WebView, JavaScript evaluation, GTK integration, HTML rendering |
## Requirements ## Requirements
- .NET 9.0 SDK **Software:**
- Linux with X11 (Ubuntu, Fedora, etc.) - .NET 9.0 SDK or later
- SkiaSharp dependencies: `libfontconfig1-dev libfreetype6-dev` - Linux with X11 display server (Wayland not yet supported)
- Supported distributions: Ubuntu 20.04+, Fedora 35+, Debian 11+, or similar
**System Dependencies:**
```bash
# Ubuntu/Debian
sudo apt-get install libfontconfig1-dev libfreetype6-dev
# Fedora/RHEL
sudo dnf install fontconfig-devel freetype-devel
# For WebView support (WebViewDemo)
sudo apt-get install libwebkit2gtk-4.0-dev # Ubuntu/Debian
sudo dnf install webkit2gtk3-devel # Fedora/RHEL
```
## Installation
### Clone the Repository
```bash
git clone https://git.marketally.com/open-maui/maui-linux-samples.git
cd maui-linux-samples
```
### Install .NET SDK
If you don't have .NET 9.0 SDK installed:
```bash
# Download and install .NET SDK
wget https://dot.net/v1/dotnet-install.sh
chmod +x dotnet-install.sh
./dotnet-install.sh --channel 9.0
# Add to PATH (add to ~/.bashrc for persistence)
export DOTNET_ROOT=$HOME/.dotnet
export PATH=$DOTNET_ROOT:$PATH
```
### Verify Installation
```bash
dotnet --version # Should show 9.0.x or later
```
## Quick Start ## Quick Start
```bash ### Run TodoApp
# Clone the samples
git clone https://git.marketally.com/open-maui/maui-linux-samples.git
cd maui-linux-samples
# Run TodoApp ```bash
cd TodoApp cd TodoApp
dotnet run dotnet run
```
# Or run ShellDemo The todo application will launch with sample tasks. You can:
- Add new tasks with the "+" button
- Tap tasks to view/edit details
- Mark tasks as completed
- Delete tasks
- Toggle between light and dark themes
### Run ShellDemo
```bash
cd ../ShellDemo cd ../ShellDemo
dotnet run dotnet run
``` ```
## Building for Deployment The control gallery will open with a flyout menu. Navigate through different pages to explore:
- Button styles and events
- Text input controls (Entry, Editor, SearchBar)
- Selection controls (CheckBox, Switch, Slider)
- Pickers (Picker, DatePicker, TimePicker)
- CollectionView with dynamic data
- Progress indicators
- Grid layouts with various configurations
### Run WebViewDemo
```bash ```bash
# Build for Linux ARM64 cd ../WebViewDemo
dotnet publish -c Release -r linux-arm64 dotnet run
# Build for Linux x64
dotnet publish -c Release -r linux-x64
``` ```
## TodoApp A web browser window will open. You can:
- Navigate to any URL
- Use back/forward buttons
- Load custom HTML content
- Execute JavaScript
- Toggle themes
A complete task management application demonstrating: ## Building and Deployment
- NavigationPage with toolbar and back navigation
- CollectionView with data binding and selection
- XAML value converters for dynamic styling
- DisplayAlert dialogs
- Grid layouts with star sizing
- Entry and Editor text input
![TodoApp Screenshot](docs/images/todoapp.png) ### Debug Build
## ShellDemo ```bash
# Build without running
dotnet build
A comprehensive control gallery demonstrating: # Output location: bin/Debug/net9.0/
- Shell with flyout menu navigation ```
- All core MAUI controls (Button, Entry, CheckBox, Switch, Slider, etc.)
- Picker, DatePicker, TimePicker
- CollectionView with various item types
- ProgressBar and ActivityIndicator
- Grid layouts
- Real-time event logging
![ShellDemo Screenshot](docs/images/shelldemo.png) ### Release Build
## Related ```bash
# Build optimized release version
dotnet build -c Release
```
- [OpenMaui Linux Framework](https://git.marketally.com/open-maui/maui-linux) - The core framework ### Publish for Distribution
- [NuGet Package](https://www.nuget.org/packages/OpenMaui.Controls.Linux) - Install via NuGet
Create self-contained executables for specific Linux architectures:
```bash
# Linux x64 (most desktops)
dotnet publish -c Release -r linux-x64 --self-contained
# Linux ARM64 (Raspberry Pi, ARM servers)
dotnet publish -c Release -r linux-arm64 --self-contained
# Framework-dependent (requires .NET runtime installed)
dotnet publish -c Release -r linux-x64 --no-self-contained
```
Published applications will be in `bin/Release/net9.0/linux-x64/publish/` and can be distributed as standalone executables.
### Create Desktop Launcher
```bash
# Make the run script executable
chmod +x TodoApp/run.sh
# Create a .desktop file for your application
cat > ~/.local/share/applications/openmaui-todo.desktop << EOF
[Desktop Entry]
Type=Application
Name=OpenMaui Todo
Exec=/path/to/maui-linux-samples/TodoApp/run.sh
Icon=utilities-terminal
Terminal=false
Categories=Utility;
EOF
```
## Sample Details
### TodoApp
A complete task management application demonstrating production-ready patterns.
**Features:**
- **NavigationPage** with toolbar and back navigation
- **CollectionView** with item selection and data templates
- **XAML data binding** with INotifyPropertyChanged
- **Value converters** for dynamic styling (alternating rows, completed tasks)
- **DisplayAlert** dialogs for confirmations
- **Theme switching** (Light/Dark) with AppThemeBinding
- **Grid layouts** with star sizing
- **Entry and Editor** for text input
- **Alternating row colors** with theme support
**Architecture:**
- `TodoItem.cs` - Data model with property change notifications
- `TodoService.cs` - Business logic with ObservableCollection
- `TodoListPage.xaml` - Main list view with data templates
- `TodoDetailPage.xaml` - Detail/edit view
- `NewTodoPage.xaml` - Create new task view
- Value converters for visual state management
**Code Example - Data Binding:**
```xaml
<!-- TodoListPage.xaml -->
<CollectionView ItemsSource="{Binding Todos}" SelectionMode="Single">
<CollectionView.ItemTemplate>
<DataTemplate>
<Grid Padding="15,10"
BackgroundColor="{Binding Index, Converter={StaticResource AlternatingRowColorConverter}}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="4"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<!-- Status indicator -->
<BoxView Grid.Column="0"
BackgroundColor="{Binding IsCompleted, Converter={StaticResource CompletedToColorConverter}, ConverterParameter=indicator}"/>
<!-- Task content -->
<VerticalStackLayout Grid.Column="1" Spacing="4">
<Label Text="{Binding Title}"
FontSize="16"
TextColor="{Binding IsCompleted, Converter={StaticResource CompletedToColorConverter}}"
TextDecorations="{Binding IsCompleted, Converter={StaticResource CompletedToTextDecorationsConverter}}"/>
<Label Text="{Binding Notes}"
FontSize="12"
MaxLines="2"
LineBreakMode="TailTruncation"
TextColor="{Binding IsCompleted, Converter={StaticResource CompletedToColorConverter}, ConverterParameter=notes}"/>
</VerticalStackLayout>
</Grid>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
```
**Logging:**
Application logs are written to `~/todoapp.log` for debugging.
### ShellDemo
A comprehensive control gallery showcasing all supported MAUI controls with Shell navigation.
**Features:**
- **Shell navigation** with flyout menu
- **All core controls**: Button, Entry, Editor, SearchBar, CheckBox, Switch, Slider, Stepper
- **Pickers**: Picker, DatePicker, TimePicker
- **CollectionView** with various data types and selection modes
- **ProgressBar and ActivityIndicator**
- **Grid layouts** with demonstrations of:
- Auto, Star, and Absolute sizing
- Row and column spanning
- Spacing and padding
- Mixed sizing strategies
- **Real-time event logging** for all interactions
- **Push/pop navigation** examples
- **Theme switching** support
**Navigation Structure:**
```csharp
// AppShell.xaml.cs - Shell with flyout menu
public partial class AppShell : Shell
{
public AppShell()
{
InitializeComponent();
// Register routes for push navigation
Routing.RegisterRoute("detail", typeof(DetailPage));
}
}
```
**Code Example - Grid Layouts:**
```csharp
// GridsPage.xaml.cs - Programmatic grid creation
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) }
}
};
// Add cells with proportional sizing: 25% | 50% | 25%
grid.Children.Add(CreateCell("1*", "#BBDEFB"));
grid.Children.Add(CreateCell("2* (double)", "#C8E6C9"));
grid.Children.Add(CreateCell("1*", "#FFECB3"));
return grid;
}
```
**Logging:**
Application logs are written to `~/shelldemo.log` for debugging.
### WebViewDemo
A web browser application demonstrating WebView integration with WebKitGTK.
**Features:**
- **WebView** with full HTML5 support
- **WebKitGTK** backend (same engine as GNOME Web)
- **Navigation controls** (back, forward, reload)
- **URL entry** with automatic https:// prefix
- **JavaScript evaluation** via `EvaluateJavaScriptAsync`
- **HTML content loading** from strings
- **Progress indicator** with animations
- **Theme switching** support
- **GTK mode** for native WebView rendering
**Important:** Requires GTK mode enabled in `Program.cs`:
```csharp
// WebViewDemo/Program.cs
LinuxApplication.Run(app, args, options =>
{
options.UseGtk = true; // Required for WebView
});
```
**Code Example - JavaScript Evaluation:**
```csharp
private async void OnEvalJsClicked(object? sender, EventArgs e)
{
try
{
var result = await MainWebView.EvaluateJavaScriptAsync("document.title");
StatusLabel.Text = $"JS Result: {result ?? "(null)"}";
}
catch (Exception ex)
{
StatusLabel.Text = $"JS Error: {ex.Message}";
}
}
```
**Code Example - Loading HTML:**
```csharp
private void OnLoadHtmlClicked(object? sender, EventArgs e)
{
var html = @"
<!DOCTYPE html>
<html>
<head>
<style>
body { font-family: Arial; margin: 40px; }
button { padding: 15px 30px; font-size: 1.1em; }
</style>
</head>
<body>
<h1>Hello from OpenMaui!</h1>
<button onclick=""alert('Hello from JavaScript!')"">Click Me!</button>
</body>
</html>";
MainWebView.Source = new HtmlWebViewSource { Html = html };
}
```
**Logging:**
Application logs are written to `~/webviewdemo.log` for debugging.
## Project Structure
Each sample follows a similar structure:
```
SampleApp/
├── Program.cs # Linux platform entry point
├── MauiProgram.cs # MAUI app configuration
├── App.xaml # Application resources and theme definitions
├── App.xaml.cs # Application lifecycle
├── SampleApp.csproj # Project configuration
├── Pages/ # XAML pages and code-behind
│ ├── MainPage.xaml
│ └── MainPage.xaml.cs
├── Resources/ # Images, fonts, icons
│ ├── AppIcon/
│ └── Images/
└── run.sh # Launcher script
```
### Project Configuration
All samples use conditional compilation for cross-platform support:
```xml
<!-- SampleApp.csproj -->
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<OutputType>Exe</OutputType>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<!-- Linux: Use OpenMaui -->
<PropertyGroup Condition="$([MSBuild]::IsOSPlatform('Linux'))">
<RuntimeIdentifiers>linux-x64;linux-arm64</RuntimeIdentifiers>
</PropertyGroup>
<!-- Local development -->
<ItemGroup Condition="$([MSBuild]::IsOSPlatform('Linux')) AND Exists('../../maui-linux/OpenMaui.Controls.Linux.csproj')">
<ProjectReference Include="../../maui-linux/OpenMaui.Controls.Linux.csproj" />
</ItemGroup>
<!-- CI/CD or standalone -->
<ItemGroup Condition="$([MSBuild]::IsOSPlatform('Linux')) AND !Exists('../../maui-linux/OpenMaui.Controls.Linux.csproj')">
<PackageReference Include="OpenMaui.Controls.Linux" Version="*" />
</ItemGroup>
</Project>
```
## Development Guide
### Creating a New Linux MAUI App
1. **Create project structure:**
```bash
mkdir MyApp
cd MyApp
dotnet new console -n MyApp
```
2. **Edit MyApp.csproj:**
```xml
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net9.0</TargetFramework>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<EnableDefaultXamlItems>true</EnableDefaultXamlItems>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="OpenMaui.Controls.Linux" Version="*" />
</ItemGroup>
</Project>
```
3. **Create Program.cs:**
```csharp
using Microsoft.Maui.Platform.Linux;
namespace MyApp;
class Program
{
static void Main(string[] args)
{
var app = MauiProgram.CreateMauiApp();
LinuxApplication.Run(app, args);
}
}
```
4. **Create MauiProgram.cs:**
```csharp
using Microsoft.Maui.Hosting;
using Microsoft.Maui.Platform.Linux.Hosting;
namespace MyApp;
public static class MauiProgram
{
public static MauiApp CreateMauiApp()
{
var builder = MauiApp.CreateBuilder();
builder.UseMauiApp<App>();
builder.UseLinux();
return builder.Build();
}
}
```
5. **Create App.xaml and App.xaml.cs** (see samples for examples)
6. **Build and run:**
```bash
dotnet build
dotnet run
```
### Exception Handling
All samples include comprehensive exception handling:
```csharp
// Global exception handlers in Program.cs
AppDomain.CurrentDomain.UnhandledException += (sender, e) =>
{
var ex = e.ExceptionObject as Exception;
Console.WriteLine($"[FATAL] Unhandled exception: {ex?.Message}");
Console.WriteLine($"[FATAL] Stack trace: {ex?.StackTrace}");
};
TaskScheduler.UnobservedTaskException += (sender, e) =>
{
Console.WriteLine($"[FATAL] Unobserved task exception: {e.Exception?.Message}");
e.SetObserved(); // Prevent crash
};
```
### Logging
All samples redirect console output to log files in the user's home directory:
```csharp
var logPath = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.UserProfile),
"myapp.log");
using var logWriter = new StreamWriter(logPath, append: false) { AutoFlush = true };
var multiWriter = new MultiTextWriter(Console.Out, logWriter);
Console.SetOut(multiWriter);
Console.SetError(multiWriter);
```
## API Usage Examples
### Navigation
**NavigationPage (TodoApp):**
```csharp
// App.xaml.cs - Create NavigationPage
protected override Window CreateWindow(IActivationState? activationState)
{
NavigationPage = new NavigationPage(new TodoListPage())
{
BarBackgroundColor = Color.FromArgb("#5C6BC0"),
BarTextColor = Colors.White
};
return new Window(NavigationPage);
}
// Push a page
await Navigation.PushAsync(new TodoDetailPage(todo));
// Pop back
await Navigation.PopAsync();
```
**Shell Navigation (ShellDemo):**
```csharp
// AppShell.xaml.cs - Register routes
Routing.RegisterRoute("detail", typeof(DetailPage));
// Navigate using route
await Shell.Current.GoToAsync("detail?item=MyItem");
// Navigate with parameters
await Shell.Current.GoToAsync($"detail?item={Uri.EscapeDataString(itemName)}");
// Use LinuxViewRenderer for direct navigation
LinuxViewRenderer.NavigateToRoute("Buttons");
LinuxViewRenderer.PushPage(new DetailPage());
LinuxViewRenderer.PopPage();
```
### Data Binding
**Observable Collections:**
```csharp
public class TodoService
{
public ObservableCollection<TodoItem> Todos { get; } = new();
public TodoItem AddTodo(string title, string notes = "")
{
var todo = new TodoItem { Title = title, Notes = notes };
Todos.Add(todo);
return todo;
}
}
```
**INotifyPropertyChanged:**
```csharp
public class TodoItem : INotifyPropertyChanged
{
private bool _isCompleted;
public bool IsCompleted
{
get => _isCompleted;
set
{
if (_isCompleted != value)
{
_isCompleted = value;
OnPropertyChanged(nameof(IsCompleted));
}
}
}
public event PropertyChangedEventHandler? PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
```
### Value Converters
**Theme-Aware Color Converter:**
```csharp
public class CompletedToColorConverter : IValueConverter
{
private static readonly Color AccentColorLight = Color.FromArgb("#26A69A");
private static readonly Color AccentColorDark = Color.FromArgb("#4DB6AC");
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
{
bool isCompleted = value is bool b && b;
bool isDarkMode = Application.Current?.RequestedTheme == AppTheme.Dark;
if (isCompleted)
return Color.FromArgb("#9E9E9E");
return isDarkMode ? AccentColorDark : AccentColorLight;
}
public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
```
**Register in App.xaml:**
```xaml
<Application.Resources>
<local:CompletedToColorConverter x:Key="CompletedToColorConverter"/>
</Application.Resources>
```
### Theme Switching
**Toggle Theme:**
```csharp
private void OnThemeToggleClicked(object? sender, EventArgs e)
{
if (Application.Current == null) return;
var isDarkMode = Application.Current.UserAppTheme == AppTheme.Dark;
Application.Current.UserAppTheme = isDarkMode ? AppTheme.Light : AppTheme.Dark;
}
```
**AppThemeBinding in XAML:**
```xaml
<Border BackgroundColor="{AppThemeBinding Light=#FFFFFF, Dark=#1E1E1E}">
<Label TextColor="{AppThemeBinding Light=#212121, Dark=#FFFFFF}"
Text="Theme-aware text"/>
</Border>
```
**Programmatic AppThemeBinding:**
```csharp
label.SetAppThemeColor(Label.TextColorProperty,
Colors.Black, // Light theme
Colors.White); // Dark theme
```
### Dialogs
**DisplayAlert (using LinuxDialogService):**
```csharp
private async void OnDeleteClicked(object? sender, EventArgs e)
{
var confirmed = await LinuxDialogService.ShowAlertAsync(
"Delete Task",
$"Are you sure you want to delete \"{_todo.Title}\"?",
"Delete",
"Cancel");
if (confirmed)
{
_service.DeleteTodo(_todo);
await Navigation.PopAsync();
}
}
```
### WebView
**Load URL:**
```csharp
MainWebView.Source = new UrlWebViewSource { Url = "https://dotnet.microsoft.com" };
```
**Load HTML:**
```csharp
MainWebView.Source = new HtmlWebViewSource
{
Html = "<html><body><h1>Hello!</h1></body></html>"
};
```
**Execute JavaScript:**
```csharp
var result = await MainWebView.EvaluateJavaScriptAsync("document.title");
```
**Navigation Events:**
```csharp
private void OnNavigating(object? sender, WebNavigatingEventArgs e)
{
Console.WriteLine($"Navigating to: {e.Url}");
// e.Cancel = true; // Cancel navigation if needed
}
private void OnNavigated(object? sender, WebNavigatedEventArgs e)
{
Console.WriteLine($"Navigated: {e.Result} - {e.Url}");
}
```
## Related Resources
- **[OpenMaui Linux Framework](https://git.marketally.com/open-maui/maui-linux)** - The core framework repository
- **[NuGet Package](https://www.nuget.org/packages/OpenMaui.Controls.Linux)** - Install via NuGet
- **[.NET MAUI Documentation](https://learn.microsoft.com/dotnet/maui/)** - Official Microsoft documentation
- **[SkiaSharp Documentation](https://learn.microsoft.com/xamarin/xamarin-forms/user-interface/graphics/skiasharp/)** - Graphics rendering engine
## License ## License
MIT License - See [LICENSE](LICENSE) for details. MIT License - See [LICENSE](LICENSE) for details.
Copyright (c) 2024 OpenMaui
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@@ -1,78 +0,0 @@
// 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)
}
}
};
}
}

150
ShellDemo/App.xaml Normal file
View File

@@ -0,0 +1,150 @@
<?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="ShellDemo.App">
<Application.Resources>
<ResourceDictionary>
<!-- Primary Colors -->
<Color x:Key="PrimaryColor">#2196F3</Color>
<Color x:Key="PrimaryDarkColor">#1976D2</Color>
<Color x:Key="AccentColor">#FF4081</Color>
<Color x:Key="SuccessColor">#4CAF50</Color>
<Color x:Key="WarningColor">#FF9800</Color>
<Color x:Key="DangerColor">#F44336</Color>
<Color x:Key="PurpleColor">#9C27B0</Color>
<!-- Light Theme Colors -->
<Color x:Key="PageBackgroundLight">#F8F8F8</Color>
<Color x:Key="CardBackgroundLight">#FFFFFF</Color>
<Color x:Key="TextPrimaryLight">#212121</Color>
<Color x:Key="TextSecondaryLight">#757575</Color>
<Color x:Key="BorderLight">#E0E0E0</Color>
<Color x:Key="EntryBackgroundLight">#F9F9F9</Color>
<Color x:Key="ShellBackgroundLight">#FFFFFF</Color>
<Color x:Key="FlyoutBackgroundLight">#FFFFFF</Color>
<Color x:Key="FlyoutHeaderBackgroundLight">#1976D2</Color>
<Color x:Key="ProgressTrackLight">#E0E0E0</Color>
<!-- Dark Theme Colors -->
<Color x:Key="PageBackgroundDark">#121212</Color>
<Color x:Key="CardBackgroundDark">#1E1E1E</Color>
<Color x:Key="TextPrimaryDark">#FFFFFF</Color>
<Color x:Key="TextSecondaryDark">#B0B0B0</Color>
<Color x:Key="BorderDark">#424242</Color>
<Color x:Key="EntryBackgroundDark">#2C2C2C</Color>
<Color x:Key="ShellBackgroundDark">#1E1E1E</Color>
<Color x:Key="FlyoutBackgroundDark">#1E1E1E</Color>
<Color x:Key="FlyoutHeaderBackgroundDark">#0D47A1</Color>
<Color x:Key="ProgressTrackDark">#424242</Color>
<!-- Themed Entry Style -->
<Style x:Key="ThemedEntry" TargetType="Entry">
<Setter Property="BackgroundColor" Value="{AppThemeBinding Light={StaticResource EntryBackgroundLight}, Dark={StaticResource EntryBackgroundDark}}" />
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource TextPrimaryLight}, Dark={StaticResource TextPrimaryDark}}" />
<Setter Property="PlaceholderColor" Value="{AppThemeBinding Light={StaticResource TextSecondaryLight}, Dark={StaticResource TextSecondaryDark}}" />
</Style>
<!-- Themed Editor Style -->
<Style x:Key="ThemedEditor" TargetType="Editor">
<Setter Property="BackgroundColor" Value="{AppThemeBinding Light={StaticResource EntryBackgroundLight}, Dark={StaticResource EntryBackgroundDark}}" />
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource TextPrimaryLight}, Dark={StaticResource TextPrimaryDark}}" />
<Setter Property="PlaceholderColor" Value="{AppThemeBinding Light={StaticResource TextSecondaryLight}, Dark={StaticResource TextSecondaryDark}}" />
</Style>
<!-- Title Label Style -->
<Style x:Key="TitleLabel" TargetType="Label">
<Setter Property="FontSize" Value="24" />
<Setter Property="FontAttributes" Value="Bold" />
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource TextPrimaryLight}, Dark={StaticResource TextPrimaryDark}}" />
</Style>
<!-- Subtitle Label Style -->
<Style x:Key="SubtitleLabel" TargetType="Label">
<Setter Property="FontSize" Value="16" />
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource TextSecondaryLight}, Dark={StaticResource TextSecondaryDark}}" />
</Style>
<!-- Body Label Style -->
<Style x:Key="BodyLabel" TargetType="Label">
<Setter Property="FontSize" Value="14" />
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource TextPrimaryLight}, Dark={StaticResource TextPrimaryDark}}" />
</Style>
<!-- Caption Label Style -->
<Style x:Key="CaptionLabel" TargetType="Label">
<Setter Property="FontSize" Value="12" />
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource TextSecondaryLight}, Dark={StaticResource TextSecondaryDark}}" />
</Style>
<!-- Themed Frame Style -->
<Style x:Key="ThemedFrame" TargetType="Frame">
<Setter Property="BackgroundColor" Value="{AppThemeBinding Light={StaticResource CardBackgroundLight}, Dark={StaticResource CardBackgroundDark}}" />
<Setter Property="BorderColor" Value="{AppThemeBinding Light={StaticResource BorderLight}, Dark={StaticResource BorderDark}}" />
<Setter Property="CornerRadius" Value="8" />
<Setter Property="Padding" Value="15" />
</Style>
<!-- Themed Border Style -->
<Style x:Key="ThemedBorder" TargetType="Border">
<Setter Property="BackgroundColor" Value="{AppThemeBinding Light={StaticResource CardBackgroundLight}, Dark={StaticResource CardBackgroundDark}}" />
<Setter Property="Stroke" Value="{AppThemeBinding Light={StaticResource BorderLight}, Dark={StaticResource BorderDark}}" />
<Setter Property="StrokeThickness" Value="1" />
</Style>
<!-- Themed Card Border Style -->
<Style x:Key="ThemedCard" TargetType="Border">
<Setter Property="BackgroundColor" Value="{AppThemeBinding Light={StaticResource CardBackgroundLight}, Dark={StaticResource CardBackgroundDark}}" />
<Setter Property="StrokeThickness" Value="0" />
<Setter Property="Padding" Value="15" />
<Setter Property="StrokeShape">
<Setter.Value>
<RoundRectangle CornerRadius="8" />
</Setter.Value>
</Setter>
</Style>
<!-- Themed ProgressBar Style -->
<Style x:Key="ThemedProgressBar" TargetType="ProgressBar">
<Setter Property="ProgressColor" Value="{StaticResource PrimaryColor}" />
</Style>
<!-- Primary Button Style -->
<Style x:Key="PrimaryButton" TargetType="Button">
<Setter Property="BackgroundColor" Value="{StaticResource PrimaryColor}" />
<Setter Property="TextColor" Value="White" />
<Setter Property="CornerRadius" Value="5" />
</Style>
<!-- Secondary Button Style -->
<Style x:Key="SecondaryButton" TargetType="Button">
<Setter Property="BackgroundColor" Value="{AppThemeBinding Light=#E0E0E0, Dark=#424242}" />
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource TextPrimaryLight}, Dark={StaticResource TextPrimaryDark}}" />
<Setter Property="CornerRadius" Value="5" />
</Style>
<!-- Success Button Style -->
<Style x:Key="SuccessButton" TargetType="Button">
<Setter Property="BackgroundColor" Value="{StaticResource SuccessColor}" />
<Setter Property="TextColor" Value="White" />
<Setter Property="CornerRadius" Value="5" />
</Style>
<!-- Danger Button Style -->
<Style x:Key="DangerButton" TargetType="Button">
<Setter Property="BackgroundColor" Value="{StaticResource DangerColor}" />
<Setter Property="TextColor" Value="White" />
<Setter Property="CornerRadius" Value="5" />
</Style>
<!-- Themed BoxView Divider -->
<Style x:Key="ThemedDivider" TargetType="BoxView">
<Setter Property="HeightRequest" Value="1" />
<Setter Property="Color" Value="{AppThemeBinding Light={StaticResource BorderLight}, Dark={StaticResource BorderDark}}" />
</Style>
</ResourceDictionary>
</Application.Resources>
</Application>

18
ShellDemo/App.xaml.cs Normal file
View File

@@ -0,0 +1,18 @@
// App.xaml.cs - Main Application code-behind
using Microsoft.Maui.Controls;
namespace ShellDemo;
public partial class App : Application
{
public App()
{
InitializeComponent();
}
protected override Window CreateWindow(IActivationState? activationState)
{
return new Window(new AppShell());
}
}

104
ShellDemo/AppShell.xaml Normal file
View File

@@ -0,0 +1,104 @@
<?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:local="clr-namespace:ShellDemo"
x:Class="ShellDemo.AppShell"
Title="OpenMaui Controls Demo"
FlyoutBehavior="Flyout"
FlyoutBackgroundColor="{AppThemeBinding Light={StaticResource FlyoutBackgroundLight}, Dark={StaticResource FlyoutBackgroundDark}}"
Shell.BackgroundColor="{AppThemeBinding Light={StaticResource ShellBackgroundLight}, Dark={StaticResource ShellBackgroundDark}}"
Shell.ForegroundColor="{AppThemeBinding Light={StaticResource TextPrimaryLight}, Dark={StaticResource TextPrimaryDark}}"
Shell.TitleColor="{AppThemeBinding Light={StaticResource TextPrimaryLight}, Dark={StaticResource TextPrimaryDark}}">
<!-- Flyout Header -->
<Shell.FlyoutHeader>
<Grid BackgroundColor="{AppThemeBinding Light=#E8E8E8, Dark=#2A2A2A}"
HeightRequest="120">
<VerticalStackLayout VerticalOptions="Center" HorizontalOptions="Fill">
<Image Source="logo_only.png"
WidthRequest="80"
HeightRequest="80"
HorizontalOptions="Center" />
<Label Text="OpenMaui"
FontSize="18"
FontAttributes="Bold"
HorizontalOptions="Center"
HorizontalTextAlignment="Center"
TextColor="{AppThemeBinding Light=#212121, Dark=#E0E0E0}" />
<Label Text="Controls Demo"
FontSize="11"
HorizontalOptions="Center"
HorizontalTextAlignment="Center"
TextColor="{AppThemeBinding Light=#757575, Dark=#B0B0B0}" />
</VerticalStackLayout>
</Grid>
</Shell.FlyoutHeader>
<!-- Flyout Footer -->
<Shell.FlyoutFooter>
<Grid BackgroundColor="{AppThemeBinding Light={StaticResource CardBackgroundLight}, Dark={StaticResource CardBackgroundDark}}"
HeightRequest="50"
Padding="16,0">
<Label Text="{Binding AppVersion}"
FontSize="12"
TextColor="{AppThemeBinding Light={StaticResource TextSecondaryLight}, Dark={StaticResource TextSecondaryDark}}"
VerticalOptions="Center"
HorizontalOptions="Start" />
</Grid>
</Shell.FlyoutFooter>
<!-- Home -->
<FlyoutItem Title="Home" Route="Home"
Icon="{AppThemeBinding Light=home_light.svg, Dark=home_dark.svg}">
<ShellContent ContentTemplate="{DataTemplate local:HomePage}" />
</FlyoutItem>
<!-- Buttons -->
<FlyoutItem Title="Buttons" Route="Buttons"
Icon="{AppThemeBinding Light=smart_button_light.svg, Dark=smart_button_dark.svg}">
<ShellContent ContentTemplate="{DataTemplate local:ButtonsPage}" />
</FlyoutItem>
<!-- Text Input -->
<FlyoutItem Title="Text Input" Route="TextInput"
Icon="{AppThemeBinding Light=edit_note_light.svg, Dark=edit_note_dark.svg}">
<ShellContent ContentTemplate="{DataTemplate local:TextInputPage}" />
</FlyoutItem>
<!-- Selection -->
<FlyoutItem Title="Selection" Route="Selection"
Icon="{AppThemeBinding Light=check_box_light.svg, Dark=check_box_dark.svg}">
<ShellContent ContentTemplate="{DataTemplate local:SelectionPage}" />
</FlyoutItem>
<!-- Pickers -->
<FlyoutItem Title="Pickers" Route="Pickers"
Icon="{AppThemeBinding Light=calendar_month_light.svg, Dark=calendar_month_dark.svg}">
<ShellContent ContentTemplate="{DataTemplate local:PickersPage}" />
</FlyoutItem>
<!-- Lists -->
<FlyoutItem Title="Lists" Route="Lists"
Icon="{AppThemeBinding Light=format_list_bulleted_light.svg, Dark=format_list_bulleted_dark.svg}">
<ShellContent ContentTemplate="{DataTemplate local:ListsPage}" />
</FlyoutItem>
<!-- Progress -->
<FlyoutItem Title="Progress" Route="Progress"
Icon="{AppThemeBinding Light=hourglass_empty_light.svg, Dark=hourglass_empty_dark.svg}">
<ShellContent ContentTemplate="{DataTemplate local:ProgressPage}" />
</FlyoutItem>
<!-- Grids -->
<FlyoutItem Title="Grids" Route="Grids"
Icon="{AppThemeBinding Light=grid_view_light.svg, Dark=grid_view_dark.svg}">
<ShellContent ContentTemplate="{DataTemplate local:GridsPage}" />
</FlyoutItem>
<!-- About -->
<FlyoutItem Title="About" Route="About"
Icon="{AppThemeBinding Light=info_light.svg, Dark=info_dark.svg}">
<ShellContent ContentTemplate="{DataTemplate local:AboutPage}" />
</FlyoutItem>
</Shell>

View File

@@ -0,0 +1,25 @@
// AppShell - Shell navigation with flyout menu
using System.Reflection;
using Microsoft.Maui.Controls;
namespace ShellDemo;
public partial class AppShell : Shell
{
public string AppVersion { get; }
public AppShell()
{
// Get app version from assembly
var version = Assembly.GetExecutingAssembly().GetName().Version;
AppVersion = $"OpenMaui v{version?.Major ?? 1}.{version?.Minor ?? 0}.{version?.Build ?? 0}";
BindingContext = this;
InitializeComponent();
// Register routes for push navigation (pages not in flyout)
Routing.RegisterRoute("detail", typeof(DetailPage));
}
}

View File

@@ -1,115 +0,0 @@
// 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,118 @@
<?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="ShellDemo.AboutPage"
Title="About"
BackgroundColor="{AppThemeBinding Light={StaticResource PageBackgroundLight}, Dark={StaticResource PageBackgroundDark}}">
<ScrollView>
<VerticalStackLayout Padding="20" Spacing="20">
<!-- Header -->
<Label Text="OpenMaui Linux"
FontSize="32"
FontAttributes="Bold"
TextColor="{AppThemeBinding Light=#1A237E, Dark=#7986CB}"
HorizontalOptions="Center" />
<Label Text="Version 1.0.0"
FontSize="16"
TextColor="{AppThemeBinding Light={StaticResource TextSecondaryLight}, Dark={StaticResource TextSecondaryDark}}"
HorizontalOptions="Center" />
<BoxView Style="{StaticResource ThemedDivider}" />
<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="WordWrap"
TextColor="{AppThemeBinding Light={StaticResource TextPrimaryLight}, Dark={StaticResource TextPrimaryDark}}" />
<!-- Info Cards -->
<Border Style="{StaticResource ThemedCard}">
<HorizontalStackLayout Spacing="10">
<Label Text="Platform:" FontAttributes="Bold" WidthRequest="100"
TextColor="{AppThemeBinding Light={StaticResource TextPrimaryLight}, Dark={StaticResource TextPrimaryDark}}" />
<Label Text="Linux (X11/Wayland)" TextColor="{AppThemeBinding Light={StaticResource TextSecondaryLight}, Dark={StaticResource TextSecondaryDark}}" />
</HorizontalStackLayout>
</Border>
<Border Style="{StaticResource ThemedCard}">
<HorizontalStackLayout Spacing="10">
<Label Text="Rendering:" FontAttributes="Bold" WidthRequest="100"
TextColor="{AppThemeBinding Light={StaticResource TextPrimaryLight}, Dark={StaticResource TextPrimaryDark}}" />
<Label Text="SkiaSharp" TextColor="{AppThemeBinding Light={StaticResource TextSecondaryLight}, Dark={StaticResource TextSecondaryDark}}" />
</HorizontalStackLayout>
</Border>
<Border Style="{StaticResource ThemedCard}">
<HorizontalStackLayout Spacing="10">
<Label Text="Framework:" FontAttributes="Bold" WidthRequest="100"
TextColor="{AppThemeBinding Light={StaticResource TextPrimaryLight}, Dark={StaticResource TextPrimaryDark}}" />
<Label Text=".NET MAUI" TextColor="{AppThemeBinding Light={StaticResource TextSecondaryLight}, Dark={StaticResource TextSecondaryDark}}" />
</HorizontalStackLayout>
</Border>
<Border Style="{StaticResource ThemedCard}">
<HorizontalStackLayout Spacing="10">
<Label Text="License:" FontAttributes="Bold" WidthRequest="100"
TextColor="{AppThemeBinding Light={StaticResource TextPrimaryLight}, Dark={StaticResource TextPrimaryDark}}" />
<Label Text="MIT License" TextColor="{AppThemeBinding Light={StaticResource TextSecondaryLight}, Dark={StaticResource TextSecondaryDark}}" />
</HorizontalStackLayout>
</Border>
<BoxView Style="{StaticResource ThemedDivider}" />
<!-- Features Section -->
<Label Text="Features"
FontSize="20"
FontAttributes="Bold"
TextColor="{AppThemeBinding Light={StaticResource TextPrimaryLight}, Dark={StaticResource TextPrimaryDark}}" />
<HorizontalStackLayout Spacing="10">
<Label Text="•" TextColor="{StaticResource SuccessColor}" FontSize="16" />
<Label Text="Full XAML support with styles and resources" FontSize="14" TextColor="{AppThemeBinding Light={StaticResource TextPrimaryLight}, Dark={StaticResource TextPrimaryDark}}" />
</HorizontalStackLayout>
<HorizontalStackLayout Spacing="10">
<Label Text="•" TextColor="{StaticResource SuccessColor}" FontSize="16" />
<Label Text="Shell navigation with flyout menus" FontSize="14" TextColor="{AppThemeBinding Light={StaticResource TextPrimaryLight}, Dark={StaticResource TextPrimaryDark}}" />
</HorizontalStackLayout>
<HorizontalStackLayout Spacing="10">
<Label Text="•" TextColor="{StaticResource SuccessColor}" FontSize="16" />
<Label Text="All standard MAUI controls" FontSize="14" TextColor="{AppThemeBinding Light={StaticResource TextPrimaryLight}, Dark={StaticResource TextPrimaryDark}}" />
</HorizontalStackLayout>
<HorizontalStackLayout Spacing="10">
<Label Text="•" TextColor="{StaticResource SuccessColor}" FontSize="16" />
<Label Text="Data binding and MVVM" FontSize="14" TextColor="{AppThemeBinding Light={StaticResource TextPrimaryLight}, Dark={StaticResource TextPrimaryDark}}" />
</HorizontalStackLayout>
<HorizontalStackLayout Spacing="10">
<Label Text="•" TextColor="{StaticResource SuccessColor}" FontSize="16" />
<Label Text="Keyboard and mouse input" FontSize="14"
TextColor="{AppThemeBinding Light={StaticResource TextPrimaryLight}, Dark={StaticResource TextPrimaryDark}}" />
</HorizontalStackLayout>
<HorizontalStackLayout Spacing="10">
<Label Text="•" TextColor="{StaticResource SuccessColor}" FontSize="16" />
<Label Text="High DPI support" FontSize="14"
TextColor="{AppThemeBinding Light={StaticResource TextPrimaryLight}, Dark={StaticResource TextPrimaryDark}}" />
</HorizontalStackLayout>
<BoxView Style="{StaticResource ThemedDivider}" />
<Label x:Name="WebsiteLink"
Text="https://www.openmaui.net"
FontSize="12"
TextColor="{StaticResource PrimaryColor}"
TextDecorations="Underline"
HorizontalOptions="Center">
<Label.GestureRecognizers>
<TapGestureRecognizer Tapped="OnWebsiteLinkTapped" />
</Label.GestureRecognizers>
</Label>
</VerticalStackLayout>
</ScrollView>
</ContentPage>

View File

@@ -0,0 +1,25 @@
// AboutPage - Information about OpenMaui Linux
using Microsoft.Maui.Controls;
namespace ShellDemo;
public partial class AboutPage : ContentPage
{
public AboutPage()
{
InitializeComponent();
}
private async void OnWebsiteLinkTapped(object? sender, TappedEventArgs e)
{
try
{
await Microsoft.Maui.Platform.Linux.Services.Browser.OpenAsync("https://www.openmaui.net", BrowserLaunchMode.SystemPreferred);
}
catch (Exception ex)
{
Console.WriteLine($"Failed to open URL: {ex.Message}");
}
}
}

View File

@@ -1,229 +0,0 @@
// 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,150 @@
<?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="ShellDemo.ButtonsPage"
Title="Buttons Demo"
BackgroundColor="{AppThemeBinding Light={StaticResource PageBackgroundLight}, Dark={StaticResource PageBackgroundDark}}">
<Grid RowDefinitions="*,120">
<!-- Main Content -->
<ScrollView Grid.Row="0">
<VerticalStackLayout Padding="20" Spacing="20">
<Label Text="Button Styles &amp; Events"
FontSize="24"
FontAttributes="Bold"
TextColor="{AppThemeBinding Light={StaticResource TextPrimaryLight}, Dark={StaticResource TextPrimaryDark}}" />
<!-- Basic Buttons Section -->
<Border Style="{StaticResource ThemedCard}">
<VerticalStackLayout Spacing="10">
<Label Text="Basic Buttons"
FontSize="16"
FontAttributes="Bold"
TextColor="{AppThemeBinding Light={StaticResource TextPrimaryLight}, Dark={StaticResource TextPrimaryDark}}" />
<Button x:Name="DefaultButton"
Text="Default Button"
Clicked="OnDefaultButtonClicked" />
<Button x:Name="TextButton"
Text="Text Only"
BackgroundColor="Transparent"
TextColor="{StaticResource PrimaryColor}" />
</VerticalStackLayout>
</Border>
<!-- Styled Buttons Section -->
<Border Style="{StaticResource ThemedCard}">
<VerticalStackLayout Spacing="10">
<Label Text="Styled Buttons"
FontSize="16"
FontAttributes="Bold"
TextColor="{AppThemeBinding Light={StaticResource TextPrimaryLight}, Dark={StaticResource TextPrimaryDark}}" />
<HorizontalStackLayout Spacing="10">
<Button Text="Primary"
Style="{StaticResource PrimaryButton}"
Clicked="OnStyledButtonClicked" />
<Button Text="Success"
Style="{StaticResource SuccessButton}"
Clicked="OnStyledButtonClicked" />
<Button Text="Warning"
BackgroundColor="{StaticResource WarningColor}"
TextColor="White"
CornerRadius="5"
Clicked="OnStyledButtonClicked" />
<Button Text="Danger"
Style="{StaticResource DangerButton}"
Clicked="OnStyledButtonClicked" />
<Button Text="Purple"
BackgroundColor="{StaticResource PurpleColor}"
TextColor="White"
CornerRadius="5"
Clicked="OnStyledButtonClicked" />
</HorizontalStackLayout>
</VerticalStackLayout>
</Border>
<!-- Button States Section -->
<Border Style="{StaticResource ThemedCard}">
<VerticalStackLayout Spacing="10">
<Label Text="Button States"
FontSize="16"
FontAttributes="Bold"
TextColor="{AppThemeBinding Light={StaticResource TextPrimaryLight}, Dark={StaticResource TextPrimaryDark}}" />
<Button x:Name="EnabledButton"
Text="Enabled Button"
IsEnabled="True"
Clicked="OnEnabledButtonClicked" />
<Button x:Name="DisabledButton"
Text="Disabled Button"
IsEnabled="False" />
<Button x:Name="ToggleButton"
Text="Toggle Above Button"
Style="{StaticResource SecondaryButton}"
Clicked="OnToggleButtonClicked" />
</VerticalStackLayout>
</Border>
<!-- Button Variations Section -->
<Border Style="{StaticResource ThemedCard}">
<VerticalStackLayout Spacing="10">
<Label Text="Button Variations"
FontSize="16"
FontAttributes="Bold"
TextColor="{AppThemeBinding Light={StaticResource TextPrimaryLight}, Dark={StaticResource TextPrimaryDark}}" />
<Button Text="Wide Button"
HorizontalOptions="Fill"
BackgroundColor="#673AB7"
TextColor="White"
Clicked="OnVariationButtonClicked" />
<Button Text="Tall Button"
HeightRequest="60"
BackgroundColor="#009688"
TextColor="White"
Clicked="OnVariationButtonClicked" />
<HorizontalStackLayout>
<Button Text="Round"
WidthRequest="80"
HeightRequest="80"
CornerRadius="40"
BackgroundColor="#E91E63"
TextColor="White"
Clicked="OnVariationButtonClicked" />
</HorizontalStackLayout>
</VerticalStackLayout>
</Border>
</VerticalStackLayout>
</ScrollView>
<!-- Event Log Panel -->
<Border Grid.Row="1"
BackgroundColor="{AppThemeBinding Light=#F5F5F5, Dark=#2C2C2C}"
StrokeThickness="0"
Padding="10">
<VerticalStackLayout>
<Label Text="Event Log:"
FontSize="12"
FontAttributes="Bold"
TextColor="{AppThemeBinding Light={StaticResource TextPrimaryLight}, Dark={StaticResource TextPrimaryDark}}" />
<ScrollView HeightRequest="80">
<Label x:Name="EventLogLabel"
Text="Events will appear here..."
FontSize="11"
TextColor="{AppThemeBinding Light={StaticResource TextSecondaryLight}, Dark={StaticResource TextSecondaryDark}}"
LineBreakMode="WordWrap" />
</ScrollView>
</VerticalStackLayout>
</Border>
</Grid>
</ContentPage>

View File

@@ -0,0 +1,55 @@
// ButtonsPage - Comprehensive Button Control Demo
using Microsoft.Maui.Controls;
namespace ShellDemo;
public partial class ButtonsPage : ContentPage
{
private int _eventCount = 0;
public ButtonsPage()
{
InitializeComponent();
}
private void OnDefaultButtonClicked(object? sender, EventArgs e)
{
LogEvent("Default Button clicked");
}
private void OnStyledButtonClicked(object? sender, EventArgs e)
{
if (sender is Button btn)
{
LogEvent($"{btn.Text} button clicked");
}
}
private void OnEnabledButtonClicked(object? sender, EventArgs e)
{
LogEvent("Enabled button clicked");
}
private void OnToggleButtonClicked(object? sender, EventArgs e)
{
DisabledButton.IsEnabled = !DisabledButton.IsEnabled;
DisabledButton.Text = DisabledButton.IsEnabled ? "Now Enabled!" : "Disabled Button";
LogEvent($"Toggled button to: {(DisabledButton.IsEnabled ? "Enabled" : "Disabled")}");
}
private void OnVariationButtonClicked(object? sender, EventArgs e)
{
if (sender is Button btn)
{
LogEvent($"{btn.Text} button clicked");
}
}
private void LogEvent(string message)
{
_eventCount++;
var timestamp = DateTime.Now.ToString("HH:mm:ss");
EventLogLabel.Text = $"[{timestamp}] {_eventCount}. {message}\n{EventLogLabel.Text}";
}
}

View File

@@ -1,203 +0,0 @@
// 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,132 @@
<?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="ShellDemo.ControlsPage"
Title="Controls"
BackgroundColor="{AppThemeBinding Light={StaticResource PageBackgroundLight}, Dark={StaticResource PageBackgroundDark}}">
<ScrollView>
<VerticalStackLayout Padding="20" Spacing="15">
<Label Text="Control Gallery"
FontSize="24"
FontAttributes="Bold"
TextColor="{AppThemeBinding Light={StaticResource TextPrimaryLight}, Dark={StaticResource TextPrimaryDark}}" />
<!-- Buttons Section -->
<Border Style="{StaticResource ThemedCard}">
<VerticalStackLayout Spacing="10">
<Label Text="Buttons"
FontSize="18"
FontAttributes="Bold"
TextColor="{AppThemeBinding Light={StaticResource TextPrimaryLight}, Dark={StaticResource TextPrimaryDark}}" />
<HorizontalStackLayout Spacing="10">
<Button Text="Primary"
Style="{StaticResource PrimaryButton}"
Clicked="OnButtonClicked" />
<Button Text="Success"
Style="{StaticResource SuccessButton}"
Clicked="OnButtonClicked" />
<Button Text="Danger"
Style="{StaticResource DangerButton}"
Clicked="OnButtonClicked" />
</HorizontalStackLayout>
<Label x:Name="ButtonResultLabel"
FontSize="12"
TextColor="{AppThemeBinding Light={StaticResource TextSecondaryLight}, Dark={StaticResource TextSecondaryDark}}" />
</VerticalStackLayout>
</Border>
<!-- Selection Section -->
<Border Style="{StaticResource ThemedCard}">
<VerticalStackLayout Spacing="10">
<Label Text="Selection"
FontSize="18"
FontAttributes="Bold"
TextColor="{AppThemeBinding Light={StaticResource TextPrimaryLight}, Dark={StaticResource TextPrimaryDark}}" />
<!-- CheckBox Row -->
<HorizontalStackLayout Spacing="20">
<CheckBox x:Name="Checkbox1" IsChecked="True" />
<Label Text="Option 1" VerticalOptions="Center"
TextColor="{AppThemeBinding Light={StaticResource TextPrimaryLight}, Dark={StaticResource TextPrimaryDark}}" />
<CheckBox x:Name="Checkbox2" IsChecked="False" />
<Label Text="Option 2" VerticalOptions="Center"
TextColor="{AppThemeBinding Light={StaticResource TextPrimaryLight}, Dark={StaticResource TextPrimaryDark}}" />
</HorizontalStackLayout>
<!-- Switch Row -->
<HorizontalStackLayout Spacing="10">
<Switch x:Name="MainSwitch" IsToggled="False" Toggled="OnSwitchToggled" />
<Label x:Name="SwitchLabel" Text="Off" VerticalOptions="Center"
TextColor="{AppThemeBinding Light={StaticResource TextPrimaryLight}, Dark={StaticResource TextPrimaryDark}}" />
</HorizontalStackLayout>
</VerticalStackLayout>
</Border>
<!-- Slider Section -->
<Border Style="{StaticResource ThemedCard}">
<VerticalStackLayout Spacing="10">
<Label Text="Slider"
FontSize="18"
FontAttributes="Bold"
TextColor="{AppThemeBinding Light={StaticResource TextPrimaryLight}, Dark={StaticResource TextPrimaryDark}}" />
<Slider x:Name="MainSlider" Minimum="0" Maximum="100" Value="50" ValueChanged="OnSliderValueChanged" />
<Label x:Name="SliderLabel" Text="Value: 50"
TextColor="{AppThemeBinding Light={StaticResource TextPrimaryLight}, Dark={StaticResource TextPrimaryDark}}" />
</VerticalStackLayout>
</Border>
<!-- Picker Section -->
<Border Style="{StaticResource ThemedCard}">
<VerticalStackLayout Spacing="10">
<Label Text="Picker"
FontSize="18"
FontAttributes="Bold"
TextColor="{AppThemeBinding Light={StaticResource TextPrimaryLight}, Dark={StaticResource TextPrimaryDark}}" />
<Picker x:Name="MainPicker"
Title="Select a fruit"
SelectedIndexChanged="OnPickerSelectedIndexChanged">
<Picker.ItemsSource>
<x:Array Type="{x:Type x:String}">
<x:String>Apple</x:String>
<x:String>Banana</x:String>
<x:String>Cherry</x:String>
<x:String>Date</x:String>
<x:String>Elderberry</x:String>
</x:Array>
</Picker.ItemsSource>
</Picker>
<Label x:Name="PickerLabel" Text="Selected: (none)"
TextColor="{AppThemeBinding Light={StaticResource TextSecondaryLight}, Dark={StaticResource TextSecondaryDark}}" />
</VerticalStackLayout>
</Border>
<!-- Progress Section -->
<Border Style="{StaticResource ThemedCard}">
<VerticalStackLayout Spacing="10">
<Label Text="Progress"
FontSize="18"
FontAttributes="Bold"
TextColor="{AppThemeBinding Light={StaticResource TextPrimaryLight}, Dark={StaticResource TextPrimaryDark}}" />
<ProgressBar Progress="0.7" ProgressColor="{StaticResource PrimaryColor}" />
<Label Text="70% Complete"
FontSize="12"
TextColor="{AppThemeBinding Light={StaticResource TextSecondaryLight}, Dark={StaticResource TextSecondaryDark}}" />
<HorizontalStackLayout Spacing="10">
<ActivityIndicator IsRunning="True" Color="{StaticResource PrimaryColor}" />
<Label Text="Loading..." VerticalOptions="Center"
TextColor="{AppThemeBinding Light={StaticResource TextSecondaryLight}, Dark={StaticResource TextSecondaryDark}}" />
</HorizontalStackLayout>
</VerticalStackLayout>
</Border>
</VerticalStackLayout>
</ScrollView>
</ContentPage>

View File

@@ -0,0 +1,39 @@
// ControlsPage - Demonstrates various MAUI controls
using Microsoft.Maui.Controls;
namespace ShellDemo;
public partial class ControlsPage : ContentPage
{
public ControlsPage()
{
InitializeComponent();
}
private void OnButtonClicked(object? sender, EventArgs e)
{
if (sender is Button btn)
{
ButtonResultLabel.Text = $"{btn.Text} clicked!";
}
}
private void OnSwitchToggled(object? sender, ToggledEventArgs e)
{
SwitchLabel.Text = e.Value ? "On" : "Off";
}
private void OnSliderValueChanged(object? sender, ValueChangedEventArgs e)
{
SliderLabel.Text = $"Value: {(int)e.NewValue}";
}
private void OnPickerSelectedIndexChanged(object? sender, EventArgs e)
{
if (MainPicker.SelectedIndex >= 0)
{
PickerLabel.Text = $"Selected: {MainPicker.ItemsSource[MainPicker.SelectedIndex]}";
}
}
}

View File

@@ -1,123 +0,0 @@
// 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,48 @@
<?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="ShellDemo.DetailPage"
Title="Detail Page"
BackgroundColor="{AppThemeBinding Light={StaticResource PageBackgroundLight}, Dark={StaticResource PageBackgroundDark}}">
<VerticalStackLayout Padding="30"
Spacing="20"
VerticalOptions="Center">
<Label Text="Pushed Page"
FontSize="28"
FontAttributes="Bold"
HorizontalOptions="Center"
TextColor="{StaticResource PurpleColor}" />
<Label x:Name="ItemNameLabel"
Text="You navigated to: Detail Item"
FontSize="16"
HorizontalOptions="Center"
TextColor="{AppThemeBinding Light={StaticResource TextPrimaryLight}, Dark={StaticResource TextPrimaryDark}}" />
<Label Text="This page was pushed onto the navigation stack using Shell.Current.GoToAsync()"
FontSize="14"
TextColor="{AppThemeBinding Light={StaticResource TextSecondaryLight}, Dark={StaticResource TextSecondaryDark}}"
HorizontalTextAlignment="Center"
LineBreakMode="WordWrap" />
<BoxView Style="{StaticResource ThemedDivider}" Margin="0,20" />
<Button x:Name="BackButton"
Text="Go Back (Pop)"
BackgroundColor="{StaticResource PurpleColor}"
TextColor="White"
HorizontalOptions="Center"
Padding="30,10"
CornerRadius="5"
Clicked="OnBackClicked" />
<Label Text="Use the back button above or the hardware/gesture back to pop this page"
FontSize="12"
TextColor="{AppThemeBinding Light={StaticResource TextSecondaryLight}, Dark={StaticResource TextSecondaryDark}}"
HorizontalTextAlignment="Center"
Margin="0,20,0,0" />
</VerticalStackLayout>
</ContentPage>

View File

@@ -0,0 +1,55 @@
// DetailPage - Demonstrates push/pop navigation
using Microsoft.Maui.Controls;
using Microsoft.Maui.Platform.Linux.Hosting;
namespace ShellDemo;
/// <summary>
/// A detail page that can be pushed onto the navigation stack.
/// </summary>
public partial class DetailPage : ContentPage
{
private readonly string _itemName;
public DetailPage() : this("Detail Item")
{
}
public DetailPage(string itemName)
{
_itemName = itemName;
InitializeComponent();
ItemNameLabel.Text = $"You navigated to: {_itemName}";
}
private void OnBackClicked(object? sender, EventArgs e)
{
Console.WriteLine("[DetailPage] Go Back clicked");
var success = LinuxViewRenderer.PopPage();
Console.WriteLine($"[DetailPage] PopPage result: {success}");
}
}
/// <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;
Title = $"Detail: {value}";
}
}
public DetailPageWithQuery() : base()
{
}
}

View File

@@ -1,594 +0,0 @@
// 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,13 @@
<?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="ShellDemo.GridsPage"
Title="Grids"
BackgroundColor="{AppThemeBinding Light={StaticResource PageBackgroundLight}, Dark={StaticResource PageBackgroundDark}}">
<ScrollView Orientation="Both">
<VerticalStackLayout x:Name="MainContent" Spacing="25" Padding="20">
<!-- Content will be added dynamically with proper theming -->
</VerticalStackLayout>
</ScrollView>
</ContentPage>

View File

@@ -0,0 +1,431 @@
// GridsPage - Demonstrates Grid layouts with various options
using Microsoft.Maui.Controls;
using Microsoft.Maui.Graphics;
namespace ShellDemo;
public partial class GridsPage : ContentPage
{
public GridsPage()
{
InitializeComponent();
BuildContent();
}
private void BuildContent()
{
MainContent.Children.Add(CreateSectionHeader("Basic Grid (2x2)"));
MainContent.Children.Add(CreateBasicGrid());
MainContent.Children.Add(CreateSectionHeader("Column Definitions"));
MainContent.Children.Add(CreateColumnDefinitionsDemo());
MainContent.Children.Add(CreateSectionHeader("Row Definitions"));
MainContent.Children.Add(CreateRowDefinitionsDemo());
MainContent.Children.Add(CreateSectionHeader("Star Sizing (Proportional)"));
MainContent.Children.Add(CreateStarSizingDemo());
MainContent.Children.Add(CreateSectionHeader("Row & Column Spacing"));
MainContent.Children.Add(CreateSpacingDemo());
MainContent.Children.Add(CreateSectionHeader("Row & Column Span"));
MainContent.Children.Add(CreateSpanDemo());
MainContent.Children.Add(CreateSectionHeader("Mixed Sizing"));
MainContent.Children.Add(CreateMixedSizingDemo());
MainContent.Children.Add(new BoxView { HeightRequest = 20 });
}
private Label CreateSectionHeader(string text)
{
var label = new Label
{
Text = text,
FontSize = 18,
FontAttributes = FontAttributes.Bold,
Margin = new Thickness(0, 10, 0, 5)
};
label.SetAppThemeColor(Label.TextColorProperty,
Color.FromArgb("#2196F3"),
Color.FromArgb("#64B5F6"));
return label;
}
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 }
}
};
grid.SetAppThemeColor(Grid.BackgroundColorProperty,
Color.FromArgb("#F5F5F5"),
Color.FromArgb("#2C2C2C"));
var cell1 = CreateCell("Row 0, Col 0", "#E3F2FD", "#1A3A5C");
var cell2 = CreateCell("Row 0, Col 1", "#E8F5E9", "#1A3C1A");
var cell3 = CreateCell("Row 1, Col 0", "#FFF3E0", "#3C2A1A");
var cell4 = CreateCell("Row 1, Col 1", "#FCE4EC", "#3C1A2A");
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 }
}
};
autoGrid.SetAppThemeColor(Grid.BackgroundColorProperty,
Color.FromArgb("#F5F5F5"),
Color.FromArgb("#2C2C2C"));
var a1 = CreateCell("Auto", "#BBDEFB", "#1A3A5C");
var a2 = CreateCell("Auto Width", "#C8E6C9", "#1A3C1A");
var a3 = CreateCell("A", "#FFECB3", "#3C3A1A");
Grid.SetColumn(a1, 0);
Grid.SetColumn(a2, 1);
Grid.SetColumn(a3, 2);
autoGrid.Children.Add(a1);
autoGrid.Children.Add(a2);
autoGrid.Children.Add(a3);
var autoLabel = new Label { Text = "Auto: Sizes to content", FontSize = 12 };
autoLabel.SetAppThemeColor(Label.TextColorProperty,
Color.FromArgb("#757575"),
Color.FromArgb("#B0B0B0"));
stack.Children.Add(autoLabel);
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) }
}
};
absoluteGrid.SetAppThemeColor(Grid.BackgroundColorProperty,
Color.FromArgb("#F5F5F5"),
Color.FromArgb("#2C2C2C"));
var b1 = CreateCell("50px", "#BBDEFB", "#1A3A5C");
var b2 = CreateCell("100px", "#C8E6C9", "#1A3C1A");
var b3 = CreateCell("150px", "#FFECB3", "#3C3A1A");
Grid.SetColumn(b1, 0);
Grid.SetColumn(b2, 1);
Grid.SetColumn(b3, 2);
absoluteGrid.Children.Add(b1);
absoluteGrid.Children.Add(b2);
absoluteGrid.Children.Add(b3);
var absLabel = new Label { Text = "Absolute: Fixed pixel widths (50, 100, 150)", FontSize = 12, Margin = new Thickness(0, 10, 0, 0) };
absLabel.SetAppThemeColor(Label.TextColorProperty,
Color.FromArgb("#757575"),
Color.FromArgb("#B0B0B0"));
stack.Children.Add(absLabel);
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 }
}
};
grid.SetAppThemeColor(Grid.BackgroundColorProperty,
Color.FromArgb("#F5F5F5"),
Color.FromArgb("#2C2C2C"));
var r1 = CreateCell("30px height", "#BBDEFB", "#1A3A5C");
var r2 = CreateCell("50px height", "#C8E6C9", "#1A3C1A");
var r3 = CreateCell("Auto height\n(fits content)", "#FFECB3", "#3C3A1A");
var r4 = CreateCell("40px height", "#F8BBD9", "#3C1A3C");
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 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) }
}
};
grid.SetAppThemeColor(Grid.BackgroundColorProperty,
Color.FromArgb("#F5F5F5"),
Color.FromArgb("#2C2C2C"));
var s1 = CreateCell("1*", "#BBDEFB", "#1A3A5C");
var s2 = CreateCell("2* (double)", "#C8E6C9", "#1A3C1A");
var s3 = CreateCell("1*", "#FFECB3", "#3C3A1A");
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 = CreateSpacedGrid(0, 0);
var noLabel = new Label { Text = "No spacing (RowSpacing=0, ColumnSpacing=0)", FontSize = 12 };
noLabel.SetAppThemeColor(Label.TextColorProperty,
Color.FromArgb("#757575"),
Color.FromArgb("#B0B0B0"));
stack.Children.Add(noLabel);
stack.Children.Add(noSpacing);
// With spacing
var withSpacing = CreateSpacedGrid(10, 10);
var withLabel = new Label { Text = "With spacing (RowSpacing=10, ColumnSpacing=10)", FontSize = 12, Margin = new Thickness(0, 10, 0, 0) };
withLabel.SetAppThemeColor(Label.TextColorProperty,
Color.FromArgb("#757575"),
Color.FromArgb("#B0B0B0"));
stack.Children.Add(withLabel);
stack.Children.Add(withSpacing);
return stack;
}
private Grid CreateSpacedGrid(int rowSpacing, int columnSpacing)
{
var grid = new Grid
{
RowSpacing = rowSpacing,
ColumnSpacing = columnSpacing,
RowDefinitions =
{
new RowDefinition { Height = GridLength.Auto },
new RowDefinition { Height = GridLength.Auto }
},
ColumnDefinitions =
{
new ColumnDefinition { Width = GridLength.Star },
new ColumnDefinition { Width = GridLength.Star }
}
};
var c1 = CreateCell("0,0", "#BBDEFB", "#1A3A5C");
var c2 = CreateCell("0,1", "#C8E6C9", "#1A3C1A");
var c3 = CreateCell("1,0", "#FFECB3", "#3C3A1A");
var c4 = CreateCell("1,1", "#F8BBD9", "#3C1A3C");
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);
return grid;
}
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", "#0D47A1", 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", "#1B5E20", Colors.White);
Grid.SetRow(sidebar, 1);
Grid.SetColumn(sidebar, 0);
Grid.SetRowSpan(sidebar, 2);
// Content cells
var content1 = CreateCell("Content 1", "#E3F2FD", "#1A3A5C");
Grid.SetRow(content1, 1); Grid.SetColumn(content1, 1);
var content2 = CreateCell("Content 2", "#E8F5E9", "#1A3C1A");
Grid.SetRow(content2, 1); Grid.SetColumn(content2, 2);
var content3 = CreateCell("Content 3", "#FFF3E0", "#3C2A1A");
Grid.SetRow(content3, 2); Grid.SetColumn(content3, 1);
var content4 = CreateCell("Content 4", "#FCE4EC", "#3C1A2A");
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) },
new ColumnDefinition { Width = GridLength.Star },
new ColumnDefinition { Width = GridLength.Auto },
new ColumnDefinition { Width = new GridLength(60) }
}
};
grid.SetAppThemeColor(Grid.BackgroundColorProperty,
Color.FromArgb("#F5F5F5"),
Color.FromArgb("#2C2C2C"));
var c1 = CreateCell("60px", "#BBDEFB", "#1A3A5C");
var c2 = CreateCell("Star (fills remaining)", "#C8E6C9", "#1A3C1A");
var c3 = CreateCell("Auto", "#FFECB3", "#3C3A1A");
var c4 = CreateCell("60px", "#F8BBD9", "#3C1A3C");
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 Border CreateCell(string text, string lightBgColor, string darkBgColor, Color? textColor = null)
{
var label = new Label
{
Text = text,
FontSize = 12,
HorizontalTextAlignment = TextAlignment.Center,
VerticalTextAlignment = TextAlignment.Center
};
if (textColor != null)
{
label.TextColor = textColor;
}
else
{
label.SetAppThemeColor(Label.TextColorProperty,
Colors.Black,
Colors.White);
}
var border = new Border
{
Padding = new Thickness(10, 8),
StrokeThickness = 0,
Content = label
};
border.SetAppThemeColor(Border.BackgroundColorProperty,
Color.FromArgb(lightBgColor),
Color.FromArgb(darkBgColor));
return border;
}
private View CreateDemoContainer(View content, string description)
{
var descLabel = new Label { Text = description, FontSize = 12 };
descLabel.SetAppThemeColor(Label.TextColorProperty,
Color.FromArgb("#757575"),
Color.FromArgb("#B0B0B0"));
return new VerticalStackLayout
{
Spacing = 5,
Children = { descLabel, content }
};
}
}

View File

@@ -1,265 +0,0 @@
// 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,120 @@
<?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="ShellDemo.HomePage"
Title="Home"
BackgroundColor="{AppThemeBinding Light={StaticResource PageBackgroundLight}, Dark={StaticResource PageBackgroundDark}}">
<ScrollView Orientation="Vertical">
<VerticalStackLayout Padding="30" Spacing="20">
<!-- Header -->
<Label Text="OpenMaui Linux"
FontSize="32"
FontAttributes="Bold"
HorizontalOptions="Center"
TextColor="{StaticResource PrimaryColor}" />
<Label Text="Controls Demo"
FontSize="20"
HorizontalOptions="Center"
TextColor="{AppThemeBinding Light={StaticResource TextSecondaryLight}, Dark={StaticResource TextSecondaryDark}}" />
<!-- Theme Toggle -->
<HorizontalStackLayout HorizontalOptions="Center" Spacing="10">
<Label Text="Light"
FontSize="14"
VerticalOptions="Center"
TextColor="{AppThemeBinding Light={StaticResource TextPrimaryLight}, Dark={StaticResource TextSecondaryDark}}" />
<Switch x:Name="ThemeSwitch"
Toggled="OnThemeSwitchToggled"
OnColor="{StaticResource PrimaryColor}" />
<Label Text="Dark"
FontSize="14"
VerticalOptions="Center"
TextColor="{AppThemeBinding Light={StaticResource TextSecondaryLight}, Dark={StaticResource TextPrimaryDark}}" />
</HorizontalStackLayout>
<BoxView Style="{StaticResource ThemedDivider}" Margin="0,10" />
<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="WordWrap"
HorizontalTextAlignment="Center"
TextColor="{AppThemeBinding Light={StaticResource TextPrimaryLight}, Dark={StaticResource TextPrimaryDark}}" />
<!-- Feature Grid -->
<Grid x:Name="FeatureGrid"
ColumnDefinitions="*,*"
RowDefinitions="Auto,Auto,Auto"
ColumnSpacing="15"
RowSpacing="15"
Margin="0,20" />
<Label Text="Use the flyout menu (swipe from left or tap the hamburger icon) to navigate between different control demos."
FontSize="12"
TextColor="{AppThemeBinding Light={StaticResource TextSecondaryLight}, Dark={StaticResource TextSecondaryDark}}"
LineBreakMode="WordWrap"
HorizontalTextAlignment="Center"
Margin="0,20,0,0" />
<!-- Quick Actions Section -->
<VerticalStackLayout Spacing="10" Margin="0,20,0,0">
<Label Text="Quick Actions"
FontSize="16"
FontAttributes="Bold"
HorizontalOptions="Center"
TextColor="{AppThemeBinding Light={StaticResource TextPrimaryLight}, Dark={StaticResource TextPrimaryDark}}" />
<HorizontalStackLayout Spacing="10" HorizontalOptions="Center">
<Button x:Name="TryButtonsBtn"
Text="Try Buttons"
Style="{StaticResource PrimaryButton}"
Clicked="OnTryButtonsClicked" />
<Button x:Name="TryListsBtn"
Text="Try Lists"
Style="{StaticResource SuccessButton}"
Clicked="OnTryListsClicked" />
</HorizontalStackLayout>
</VerticalStackLayout>
<!-- Navigation Demo Section -->
<Border BackgroundColor="{AppThemeBinding Light=#F3E5F5, Dark=#2D1F3D}"
StrokeThickness="0"
Padding="20"
Margin="0,20,0,0">
<Border.StrokeShape>
<RoundRectangle CornerRadius="8" />
</Border.StrokeShape>
<VerticalStackLayout Spacing="15">
<Label Text="Navigation Stack Demo"
FontSize="18"
FontAttributes="Bold"
TextColor="{StaticResource PurpleColor}"
HorizontalOptions="Center" />
<Label Text="Demonstrate push/pop navigation using Shell.GoToAsync()"
FontSize="12"
TextColor="{AppThemeBinding Light={StaticResource TextSecondaryLight}, Dark={StaticResource TextSecondaryDark}}"
HorizontalTextAlignment="Center" />
<Button x:Name="PushDetailBtn"
Text="Push Detail Page"
BackgroundColor="{StaticResource PurpleColor}"
TextColor="White"
HorizontalOptions="Center"
Padding="30,10"
Clicked="OnPushDetailClicked" />
<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="{AppThemeBinding Light={StaticResource TextSecondaryLight}, Dark={StaticResource TextSecondaryDark}}"
HorizontalTextAlignment="Center"
LineBreakMode="WordWrap" />
</VerticalStackLayout>
</Border>
</VerticalStackLayout>
</ScrollView>
</ContentPage>

View File

@@ -0,0 +1,154 @@
// HomePage - Welcome page for the demo
using Microsoft.Maui.Controls;
using Microsoft.Maui.Controls.Shapes;
using Microsoft.Maui.Graphics;
using Microsoft.Maui.Platform.Linux.Hosting;
namespace ShellDemo;
public partial class HomePage : ContentPage
{
private bool _isUpdatingThemeSwitch;
public HomePage()
{
InitializeComponent();
CreateFeatureCards();
UpdateThemeSwitchState();
}
protected override void OnAppearing()
{
base.OnAppearing();
UpdateThemeSwitchState();
}
private void UpdateThemeSwitchState()
{
_isUpdatingThemeSwitch = true;
var isDark = Application.Current?.UserAppTheme == AppTheme.Dark ||
(Application.Current?.UserAppTheme == AppTheme.Unspecified &&
Application.Current?.RequestedTheme == AppTheme.Dark);
ThemeSwitch.IsToggled = isDark;
_isUpdatingThemeSwitch = false;
}
private void OnThemeSwitchToggled(object? sender, ToggledEventArgs e)
{
if (_isUpdatingThemeSwitch || Application.Current == null) return;
Application.Current.UserAppTheme = e.Value ? AppTheme.Dark : AppTheme.Light;
}
private void CreateFeatureCards()
{
// Title, Description, Route
var features = new[]
{
("Buttons", "Various button styles and events", "Buttons"),
("Text Input", "Entry, Editor, SearchBar", "TextInput"),
("Selection", "CheckBox, Switch, Slider", "Selection"),
("Pickers", "Picker, DatePicker, TimePicker", "Pickers"),
("Lists", "CollectionView with selection", "Lists"),
("Progress", "ProgressBar, ActivityIndicator", "Progress")
};
for (int i = 0; i < features.Length; i++)
{
var (title, desc, route) = features[i];
var card = CreateFeatureCard(title, desc, route);
Grid.SetRow(card, i / 2);
Grid.SetColumn(card, i % 2);
FeatureGrid.Children.Add(card);
}
}
private Border CreateFeatureCard(string title, string description, string route)
{
// Use AppThemeBinding for card colors
var cardBackground = new AppThemeBindingExtension
{
Light = Application.Current?.Resources["CardBackgroundLight"] as Color ?? Colors.White,
Dark = Application.Current?.Resources["CardBackgroundDark"] as Color ?? Color.FromArgb("#1E1E1E")
};
var textPrimary = new AppThemeBindingExtension
{
Light = Application.Current?.Resources["TextPrimaryLight"] as Color ?? Colors.Black,
Dark = Application.Current?.Resources["TextPrimaryDark"] as Color ?? Colors.White
};
var textSecondary = new AppThemeBindingExtension
{
Light = Application.Current?.Resources["TextSecondaryLight"] as Color ?? Colors.Gray,
Dark = Application.Current?.Resources["TextSecondaryDark"] as Color ?? Color.FromArgb("#B0B0B0")
};
var titleLabel = new Label
{
Text = title,
FontSize = 14,
FontAttributes = FontAttributes.Bold,
TextColor = Color.FromArgb("#2196F3")
};
var descLabel = new Label
{
Text = description,
FontSize = 11,
LineBreakMode = LineBreakMode.WordWrap
};
descLabel.SetBinding(Label.TextColorProperty, new Binding { Source = textSecondary, Path = "." });
descLabel.SetAppThemeColor(Label.TextColorProperty,
Application.Current?.Resources["TextSecondaryLight"] as Color ?? Colors.Gray,
Application.Current?.Resources["TextSecondaryDark"] as Color ?? Color.FromArgb("#B0B0B0"));
var border = new Border
{
Padding = new Thickness(15),
StrokeThickness = 0,
StrokeShape = new RoundRectangle { CornerRadius = 8 },
Shadow = new Shadow
{
Brush = new SolidColorBrush(Colors.Black),
Opacity = 0.1f,
Radius = 4,
Offset = new Point(0, 2)
},
Content = new VerticalStackLayout
{
Spacing = 5,
Children = { titleLabel, descLabel }
}
};
border.SetAppThemeColor(Border.BackgroundColorProperty,
Application.Current?.Resources["CardBackgroundLight"] as Color ?? Colors.White,
Application.Current?.Resources["CardBackgroundDark"] as Color ?? Color.FromArgb("#1E1E1E"));
// Add tap gesture for navigation
var tapGesture = new TapGestureRecognizer();
tapGesture.Tapped += (s, e) => LinuxViewRenderer.NavigateToRoute(route);
border.GestureRecognizers.Add(tapGesture);
return border;
}
private void OnTryButtonsClicked(object? sender, EventArgs e)
{
LinuxViewRenderer.NavigateToRoute("Buttons");
}
private void OnTryListsClicked(object? sender, EventArgs e)
{
LinuxViewRenderer.NavigateToRoute("Lists");
}
private void OnPushDetailClicked(object? sender, EventArgs e)
{
Console.WriteLine("[HomePage] Push button clicked, navigating to detail");
var success = LinuxViewRenderer.PushPage(new DetailPage());
Console.WriteLine($"[HomePage] PushPage result: {success}");
}
}

View File

@@ -1,249 +0,0 @@
// 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,133 @@
<?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="ShellDemo.ListsPage"
Title="Lists"
BackgroundColor="{AppThemeBinding Light={StaticResource PageBackgroundLight}, Dark={StaticResource PageBackgroundDark}}">
<Grid RowDefinitions="*,120">
<!-- Main Content -->
<ScrollView Grid.Row="0">
<VerticalStackLayout Padding="20" Spacing="20">
<Label Text="List Controls"
FontSize="24"
FontAttributes="Bold"
TextColor="{AppThemeBinding Light={StaticResource TextPrimaryLight}, Dark={StaticResource TextPrimaryDark}}" />
<!-- Fruits CollectionView -->
<Border Style="{StaticResource ThemedCard}">
<VerticalStackLayout Spacing="10">
<Label Text="CollectionView - Fruits"
FontSize="16"
FontAttributes="Bold"
TextColor="{AppThemeBinding Light={StaticResource TextPrimaryLight}, Dark={StaticResource TextPrimaryDark}}" />
<CollectionView x:Name="FruitsCollectionView"
HeightRequest="200"
SelectionMode="Single"
BackgroundColor="{AppThemeBinding Light=#FAFAFA, Dark=#2C2C2C}"
SelectionChanged="OnFruitsSelectionChanged">
<CollectionView.ItemTemplate>
<DataTemplate x:DataType="x:String">
<Label Text="{Binding}"
Padding="10"
TextColor="{AppThemeBinding Light={StaticResource TextPrimaryLight}, Dark={StaticResource TextPrimaryDark}}" />
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
<Label x:Name="FruitsSelectedLabel"
Text="Tap a fruit to select"
TextColor="{AppThemeBinding Light={StaticResource TextSecondaryLight}, Dark={StaticResource TextSecondaryDark}}" />
</VerticalStackLayout>
</Border>
<!-- Colors CollectionView -->
<Border Style="{StaticResource ThemedCard}">
<VerticalStackLayout Spacing="10">
<Label Text="CollectionView - Colors"
FontSize="16"
FontAttributes="Bold"
TextColor="{AppThemeBinding Light={StaticResource TextPrimaryLight}, Dark={StaticResource TextPrimaryDark}}" />
<CollectionView x:Name="ColorsCollectionView"
HeightRequest="180"
SelectionMode="Single"
BackgroundColor="{AppThemeBinding Light=White, Dark=#1E1E1E}"
SelectionChanged="OnColorsSelectionChanged">
<CollectionView.ItemTemplate>
<DataTemplate x:DataType="x:String">
<Label Text="{Binding}"
Padding="10"
TextColor="{AppThemeBinding Light={StaticResource TextPrimaryLight}, Dark={StaticResource TextPrimaryDark}}" />
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
<Label Text="Scroll to see all colors"
FontSize="11"
TextColor="{AppThemeBinding Light={StaticResource TextSecondaryLight}, Dark={StaticResource TextSecondaryDark}}" />
</VerticalStackLayout>
</Border>
<!-- Contacts CollectionView -->
<Border Style="{StaticResource ThemedCard}">
<VerticalStackLayout Spacing="10">
<Label Text="CollectionView - Contacts"
FontSize="16"
FontAttributes="Bold"
TextColor="{AppThemeBinding Light={StaticResource TextPrimaryLight}, Dark={StaticResource TextPrimaryDark}}" />
<CollectionView x:Name="ContactsCollectionView"
HeightRequest="200"
SelectionMode="Single"
BackgroundColor="{AppThemeBinding Light=White, Dark=#1E1E1E}"
SelectionChanged="OnContactsSelectionChanged">
<CollectionView.ItemTemplate>
<DataTemplate x:DataType="x:String">
<Label Text="{Binding}"
Padding="10"
TextColor="{AppThemeBinding Light={StaticResource TextPrimaryLight}, Dark={StaticResource TextPrimaryDark}}" />
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
<!-- Action buttons -->
<HorizontalStackLayout Spacing="10">
<Button Text="Add Contact"
Style="{StaticResource SuccessButton}"
Clicked="OnAddContactClicked" />
<Button Text="Delete Selected"
Style="{StaticResource DangerButton}"
Clicked="OnDeleteContactClicked" />
</HorizontalStackLayout>
</VerticalStackLayout>
</Border>
</VerticalStackLayout>
</ScrollView>
<!-- Event Log Panel -->
<Border Grid.Row="1"
BackgroundColor="{AppThemeBinding Light=#F5F5F5, Dark=#2C2C2C}"
StrokeThickness="0"
Padding="10">
<VerticalStackLayout>
<Label Text="Event Log:"
FontSize="12"
FontAttributes="Bold"
TextColor="{AppThemeBinding Light={StaticResource TextPrimaryLight}, Dark={StaticResource TextPrimaryDark}}" />
<ScrollView HeightRequest="80">
<Label x:Name="EventLogLabel"
Text="Events will appear here..."
FontSize="11"
TextColor="{AppThemeBinding Light={StaticResource TextSecondaryLight}, Dark={StaticResource TextSecondaryDark}}"
LineBreakMode="WordWrap" />
</ScrollView>
</VerticalStackLayout>
</Border>
</Grid>
</ContentPage>

View File

@@ -0,0 +1,104 @@
// ListsPage - CollectionView and ListView Demo
using Microsoft.Maui.Controls;
using System.Collections.ObjectModel;
namespace ShellDemo;
public partial class ListsPage : ContentPage
{
private int _eventCount = 0;
private ObservableCollection<string> _contacts;
public ListsPage()
{
InitializeComponent();
// Set up fruits
var fruits = new List<string>
{
"Apple", "Banana", "Cherry", "Date", "Elderberry",
"Fig", "Grape", "Honeydew", "Kiwi", "Lemon",
"Mango", "Nectarine", "Orange", "Papaya", "Quince"
};
FruitsCollectionView.ItemsSource = fruits;
// Set up colors
var colors = new List<string>
{
"Red", "Pink", "Purple", "Deep Purple", "Indigo",
"Blue", "Cyan", "Teal", "Green", "Light Green",
"Lime", "Yellow", "Amber", "Orange", "Deep Orange"
};
ColorsCollectionView.ItemsSource = colors;
// Set up contacts
_contacts = new ObservableCollection<string>
{
"Alice Johnson (Engineering)",
"Bob Smith (Marketing)",
"Carol Williams (Design)",
"David Brown (Sales)",
"Eva Martinez (Engineering)",
"Frank Lee (Support)",
"Grace Kim (HR)",
"Henry Wilson (Finance)"
};
ContactsCollectionView.ItemsSource = _contacts;
}
private void OnFruitsSelectionChanged(object? sender, SelectionChangedEventArgs e)
{
if (e.CurrentSelection.Count > 0)
{
var item = e.CurrentSelection[0]?.ToString();
FruitsSelectedLabel.Text = $"Selected: {item}";
LogEvent($"Fruit selected: {item}");
}
}
private void OnColorsSelectionChanged(object? sender, SelectionChangedEventArgs e)
{
if (e.CurrentSelection.Count > 0)
{
var item = e.CurrentSelection[0]?.ToString();
LogEvent($"Color selected: {item}");
}
}
private void OnContactsSelectionChanged(object? sender, SelectionChangedEventArgs e)
{
if (e.CurrentSelection.Count > 0)
{
var contact = e.CurrentSelection[0]?.ToString();
LogEvent($"Contact: {contact}");
}
}
private void OnAddContactClicked(object? sender, EventArgs e)
{
var newContact = $"New Contact {_contacts.Count + 1} (New)";
_contacts.Add(newContact);
LogEvent($"Added: {newContact}");
}
private void OnDeleteContactClicked(object? sender, EventArgs e)
{
if (ContactsCollectionView.SelectedItem is string selected && _contacts.Contains(selected))
{
_contacts.Remove(selected);
LogEvent($"Deleted: {selected}");
}
else
{
LogEvent("No contact selected to delete");
}
}
private void LogEvent(string message)
{
_eventCount++;
var timestamp = DateTime.Now.ToString("HH:mm:ss");
EventLogLabel.Text = $"[{timestamp}] {_eventCount}. {message}\n{EventLogLabel.Text}";
}
}

View File

@@ -1,261 +0,0 @@
// 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,185 @@
<?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="ShellDemo.PickersPage"
Title="Pickers"
BackgroundColor="{AppThemeBinding Light={StaticResource PageBackgroundLight}, Dark={StaticResource PageBackgroundDark}}">
<Grid RowDefinitions="*,120">
<!-- Main Content -->
<ScrollView Grid.Row="0">
<VerticalStackLayout Padding="20" Spacing="20">
<Label Text="Picker Controls"
FontSize="24"
FontAttributes="Bold"
TextColor="{AppThemeBinding Light={StaticResource TextPrimaryLight}, Dark={StaticResource TextPrimaryDark}}" />
<!-- Picker Section -->
<Border Style="{StaticResource ThemedCard}">
<VerticalStackLayout Spacing="15">
<Label Text="Picker"
FontSize="16"
FontAttributes="Bold"
TextColor="{AppThemeBinding Light={StaticResource TextPrimaryLight}, Dark={StaticResource TextPrimaryDark}}" />
<!-- Basic picker -->
<Picker x:Name="FruitPicker"
Title="Select a fruit"
SelectedIndexChanged="OnFruitPickerChanged">
<Picker.ItemsSource>
<x:Array Type="{x:Type x:String}">
<x:String>Apple</x:String>
<x:String>Banana</x:String>
<x:String>Cherry</x:String>
<x:String>Date</x:String>
<x:String>Elderberry</x:String>
<x:String>Fig</x:String>
<x:String>Grape</x:String>
</x:Array>
</Picker.ItemsSource>
</Picker>
<Label x:Name="FruitSelectedLabel"
Text="Selected: (none)"
TextColor="{AppThemeBinding Light={StaticResource TextSecondaryLight}, Dark={StaticResource TextSecondaryDark}}" />
<!-- Picker with default selection -->
<Label Text="With Default Selection:"
FontSize="12"
Margin="0,10,0,0"
TextColor="{AppThemeBinding Light={StaticResource TextSecondaryLight}, Dark={StaticResource TextSecondaryDark}}" />
<Picker x:Name="ColorPicker"
Title="Select a color"
SelectedIndex="2"
SelectedIndexChanged="OnColorPickerChanged">
<Picker.ItemsSource>
<x:Array Type="{x:Type x:String}">
<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>
</x:Array>
</Picker.ItemsSource>
</Picker>
<!-- Styled picker -->
<Label Text="Styled Picker:"
FontSize="12"
Margin="0,10,0,0"
TextColor="{AppThemeBinding Light={StaticResource TextSecondaryLight}, Dark={StaticResource TextSecondaryDark}}" />
<Picker x:Name="SizePicker"
Title="Select size"
TextColor="{AppThemeBinding Light=DarkBlue, Dark=LightBlue}"
TitleColor="{AppThemeBinding Light={StaticResource TextSecondaryLight}, Dark={StaticResource TextSecondaryDark}}"
SelectedIndexChanged="OnSizePickerChanged">
<Picker.ItemsSource>
<x:Array Type="{x:Type x:String}">
<x:String>Small</x:String>
<x:String>Medium</x:String>
<x:String>Large</x:String>
<x:String>Extra Large</x:String>
</x:Array>
</Picker.ItemsSource>
</Picker>
</VerticalStackLayout>
</Border>
<!-- DatePicker Section -->
<Border Style="{StaticResource ThemedCard}">
<VerticalStackLayout Spacing="15">
<Label Text="DatePicker"
FontSize="16"
FontAttributes="Bold"
TextColor="{AppThemeBinding Light={StaticResource TextPrimaryLight}, Dark={StaticResource TextPrimaryDark}}" />
<!-- Basic date picker -->
<DatePicker x:Name="BasicDatePicker"
DateSelected="OnDateSelected" />
<Label x:Name="DateSelectedLabel"
TextColor="{AppThemeBinding Light={StaticResource TextPrimaryLight}, Dark={StaticResource TextPrimaryDark}}" />
<!-- Date picker with range -->
<Label Text="With Date Range (this month only):"
FontSize="12"
Margin="0,10,0,0"
TextColor="{AppThemeBinding Light={StaticResource TextSecondaryLight}, Dark={StaticResource TextSecondaryDark}}" />
<DatePicker x:Name="RangeDatePicker"
DateSelected="OnRangeDateSelected" />
<!-- Styled date picker -->
<Label Text="Styled DatePicker:"
FontSize="12"
Margin="0,10,0,0"
TextColor="{AppThemeBinding Light={StaticResource TextSecondaryLight}, Dark={StaticResource TextSecondaryDark}}" />
<DatePicker x:Name="StyledDatePicker"
TextColor="{AppThemeBinding Light=DarkGreen, Dark=LightGreen}"
DateSelected="OnStyledDateSelected" />
</VerticalStackLayout>
</Border>
<!-- TimePicker Section -->
<Border Style="{StaticResource ThemedCard}">
<VerticalStackLayout Spacing="15">
<Label Text="TimePicker"
FontSize="16"
FontAttributes="Bold"
TextColor="{AppThemeBinding Light={StaticResource TextPrimaryLight}, Dark={StaticResource TextPrimaryDark}}" />
<!-- Basic time picker -->
<TimePicker x:Name="BasicTimePicker"
PropertyChanged="OnTimePickerPropertyChanged" />
<Label x:Name="TimeSelectedLabel"
TextColor="{AppThemeBinding Light={StaticResource TextPrimaryLight}, Dark={StaticResource TextPrimaryDark}}" />
<!-- Styled time picker -->
<Label Text="Styled TimePicker:"
FontSize="12"
Margin="0,10,0,0"
TextColor="{AppThemeBinding Light={StaticResource TextSecondaryLight}, Dark={StaticResource TextSecondaryDark}}" />
<TimePicker x:Name="StyledTimePicker"
Time="14:30:00"
TextColor="{AppThemeBinding Light=DarkBlue, Dark=LightBlue}"
PropertyChanged="OnStyledTimePickerPropertyChanged" />
<!-- Alarm time -->
<Label Text="Alarm Time:"
FontSize="12"
Margin="0,10,0,0"
TextColor="{AppThemeBinding Light={StaticResource TextSecondaryLight}, Dark={StaticResource TextSecondaryDark}}" />
<HorizontalStackLayout Spacing="10">
<TimePicker x:Name="AlarmTimePicker" Time="07:00:00" />
<Button Text="Set Alarm"
BackgroundColor="{StaticResource WarningColor}"
TextColor="White"
Clicked="OnSetAlarmClicked" />
</HorizontalStackLayout>
</VerticalStackLayout>
</Border>
</VerticalStackLayout>
</ScrollView>
<!-- Event Log Panel -->
<Border Grid.Row="1"
BackgroundColor="{AppThemeBinding Light=#F5F5F5, Dark=#2C2C2C}"
StrokeThickness="0"
Padding="10">
<VerticalStackLayout>
<Label Text="Event Log:"
FontSize="12"
FontAttributes="Bold"
TextColor="{AppThemeBinding Light={StaticResource TextPrimaryLight}, Dark={StaticResource TextPrimaryDark}}" />
<ScrollView HeightRequest="80">
<Label x:Name="EventLogLabel"
Text="Events will appear here..."
FontSize="11"
TextColor="{AppThemeBinding Light={StaticResource TextSecondaryLight}, Dark={StaticResource TextSecondaryDark}}"
LineBreakMode="WordWrap" />
</ScrollView>
</VerticalStackLayout>
</Border>
</Grid>
</ContentPage>

View File

@@ -0,0 +1,107 @@
// PickersPage - Picker, DatePicker, TimePicker Demo
using Microsoft.Maui.Controls;
namespace ShellDemo;
public partial class PickersPage : ContentPage
{
private int _eventCount = 0;
public PickersPage()
{
InitializeComponent();
// Set up date picker with current date
BasicDatePicker.Date = DateTime.Today;
DateSelectedLabel.Text = $"Selected: {DateTime.Today:d}";
// Set up range date picker
var startOfMonth = new DateTime(DateTime.Today.Year, DateTime.Today.Month, 1);
var endOfMonth = startOfMonth.AddMonths(1).AddDays(-1);
RangeDatePicker.MinimumDate = startOfMonth;
RangeDatePicker.MaximumDate = endOfMonth;
RangeDatePicker.Date = DateTime.Today;
// Set up styled date picker
StyledDatePicker.Date = DateTime.Today.AddDays(7);
// Set up time picker
BasicTimePicker.Time = DateTime.Now.TimeOfDay;
TimeSelectedLabel.Text = $"Selected: {DateTime.Now:t}";
}
private void OnFruitPickerChanged(object? sender, EventArgs e)
{
if (FruitPicker.SelectedIndex >= 0)
{
var item = FruitPicker.ItemsSource[FruitPicker.SelectedIndex]?.ToString();
FruitSelectedLabel.Text = $"Selected: {item}";
LogEvent($"Fruit selected: {item}");
}
}
private void OnColorPickerChanged(object? sender, EventArgs e)
{
if (ColorPicker.SelectedIndex >= 0)
{
var item = ColorPicker.ItemsSource[ColorPicker.SelectedIndex]?.ToString();
LogEvent($"Color selected: {item}");
}
}
private void OnSizePickerChanged(object? sender, EventArgs e)
{
if (SizePicker.SelectedIndex >= 0)
{
var item = SizePicker.ItemsSource[SizePicker.SelectedIndex]?.ToString();
LogEvent($"Size selected: {item}");
}
}
private void OnDateSelected(object? sender, DateChangedEventArgs e)
{
DateSelectedLabel.Text = $"Selected: {e.NewDate:d}";
LogEvent($"Date selected: {e.NewDate:d}");
}
private void OnRangeDateSelected(object? sender, DateChangedEventArgs e)
{
LogEvent($"Date (limited): {e.NewDate:d}");
}
private void OnStyledDateSelected(object? sender, DateChangedEventArgs e)
{
LogEvent($"Styled date: {e.NewDate:d}");
}
private void OnTimePickerPropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e)
{
if (e.PropertyName == nameof(TimePicker.Time))
{
var time = BasicTimePicker.Time;
TimeSelectedLabel.Text = $"Selected: {time:hh\\:mm}";
LogEvent($"Time selected: {time:hh\\:mm}");
}
}
private void OnStyledTimePickerPropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e)
{
if (e.PropertyName == nameof(TimePicker.Time))
{
LogEvent($"Styled time: {StyledTimePicker.Time:hh\\:mm}");
}
}
private void OnSetAlarmClicked(object? sender, EventArgs e)
{
LogEvent($"Alarm set for {AlarmTimePicker.Time:hh\\:mm}");
}
private void LogEvent(string message)
{
_eventCount++;
var timestamp = DateTime.Now.ToString("HH:mm:ss");
EventLogLabel.Text = $"[{timestamp}] {_eventCount}. {message}\n{EventLogLabel.Text}";
}
}

View File

@@ -1,261 +0,0 @@
// 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,167 @@
<?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="ShellDemo.ProgressPage"
Title="Progress"
BackgroundColor="{AppThemeBinding Light={StaticResource PageBackgroundLight}, Dark={StaticResource PageBackgroundDark}}">
<Grid RowDefinitions="*,120">
<!-- Main Content -->
<ScrollView Grid.Row="0">
<VerticalStackLayout Padding="20" Spacing="20">
<Label Text="Progress Indicators"
FontSize="24"
FontAttributes="Bold"
TextColor="{AppThemeBinding Light={StaticResource TextPrimaryLight}, Dark={StaticResource TextPrimaryDark}}" />
<!-- ProgressBar Section -->
<Border Style="{StaticResource ThemedCard}">
<VerticalStackLayout Spacing="15">
<Label Text="ProgressBar"
FontSize="16"
FontAttributes="Bold"
TextColor="{AppThemeBinding Light={StaticResource TextPrimaryLight}, Dark={StaticResource TextPrimaryDark}}" />
<!-- Various progress values -->
<HorizontalStackLayout Spacing="10">
<ProgressBar Progress="0" WidthRequest="200" />
<Label Text="0%" VerticalOptions="Center" WidthRequest="50"
TextColor="{AppThemeBinding Light={StaticResource TextPrimaryLight}, Dark={StaticResource TextPrimaryDark}}" />
</HorizontalStackLayout>
<HorizontalStackLayout Spacing="10">
<ProgressBar Progress="0.25" WidthRequest="200" />
<Label Text="25%" VerticalOptions="Center" WidthRequest="50"
TextColor="{AppThemeBinding Light={StaticResource TextPrimaryLight}, Dark={StaticResource TextPrimaryDark}}" />
</HorizontalStackLayout>
<HorizontalStackLayout Spacing="10">
<ProgressBar Progress="0.5" WidthRequest="200" />
<Label Text="50%" VerticalOptions="Center" WidthRequest="50"
TextColor="{AppThemeBinding Light={StaticResource TextPrimaryLight}, Dark={StaticResource TextPrimaryDark}}" />
</HorizontalStackLayout>
<HorizontalStackLayout Spacing="10">
<ProgressBar Progress="0.75" WidthRequest="200" />
<Label Text="75%" VerticalOptions="Center" WidthRequest="50"
TextColor="{AppThemeBinding Light={StaticResource TextPrimaryLight}, Dark={StaticResource TextPrimaryDark}}" />
</HorizontalStackLayout>
<HorizontalStackLayout Spacing="10">
<ProgressBar Progress="1.0" WidthRequest="200" />
<Label Text="100%" VerticalOptions="Center" WidthRequest="50"
TextColor="{AppThemeBinding Light={StaticResource TextPrimaryLight}, Dark={StaticResource TextPrimaryDark}}" />
</HorizontalStackLayout>
<!-- Colored progress bars -->
<Label Text="Colored Progress Bars:"
FontSize="12"
Margin="0,10,0,0"
TextColor="{AppThemeBinding Light={StaticResource TextSecondaryLight}, Dark={StaticResource TextSecondaryDark}}" />
<ProgressBar Progress="0.7" ProgressColor="Red" />
<ProgressBar Progress="0.7" ProgressColor="Green" />
<ProgressBar Progress="0.7" ProgressColor="Blue" />
<ProgressBar Progress="0.7" ProgressColor="Orange" />
<ProgressBar Progress="0.7" ProgressColor="Purple" />
</VerticalStackLayout>
</Border>
<!-- ActivityIndicator Section -->
<Border Style="{StaticResource ThemedCard}">
<VerticalStackLayout Spacing="15">
<Label Text="ActivityIndicator"
FontSize="16"
FontAttributes="Bold"
TextColor="{AppThemeBinding Light={StaticResource TextPrimaryLight}, Dark={StaticResource TextPrimaryDark}}" />
<!-- Running indicator -->
<HorizontalStackLayout Spacing="15">
<ActivityIndicator IsRunning="True" />
<Label Text="Loading..." VerticalOptions="Center"
TextColor="{AppThemeBinding Light={StaticResource TextPrimaryLight}, Dark={StaticResource TextPrimaryDark}}" />
</HorizontalStackLayout>
<!-- Toggle indicator -->
<HorizontalStackLayout Spacing="15">
<ActivityIndicator x:Name="ToggleIndicator" IsRunning="False" />
<Button Text="Start/Stop"
Style="{StaticResource SecondaryButton}"
Clicked="OnToggleIndicatorClicked" />
</HorizontalStackLayout>
<!-- Colored indicators -->
<Label Text="Colored Indicators:"
FontSize="12"
Margin="0,10,0,0"
TextColor="{AppThemeBinding Light={StaticResource TextSecondaryLight}, Dark={StaticResource TextSecondaryDark}}" />
<HorizontalStackLayout Spacing="20">
<ActivityIndicator IsRunning="True" Color="Red" />
<ActivityIndicator IsRunning="True" Color="Green" />
<ActivityIndicator IsRunning="True" Color="Blue" />
<ActivityIndicator IsRunning="True" Color="Orange" />
</HorizontalStackLayout>
</VerticalStackLayout>
</Border>
<!-- Interactive Demo Section -->
<Border Style="{StaticResource ThemedCard}">
<VerticalStackLayout Spacing="15">
<Label Text="Interactive Demo"
FontSize="16"
FontAttributes="Bold"
TextColor="{AppThemeBinding Light={StaticResource TextPrimaryLight}, Dark={StaticResource TextPrimaryDark}}" />
<!-- Slider-controlled progress -->
<ProgressBar x:Name="AnimatedProgress" Progress="0.5" />
<Slider x:Name="ProgressSlider"
Minimum="0"
Maximum="100"
Value="50"
ValueChanged="OnProgressSliderValueChanged" />
<Label x:Name="ProgressLabel"
Text="Progress: 50%"
TextColor="{AppThemeBinding Light={StaticResource TextPrimaryLight}, Dark={StaticResource TextPrimaryDark}}" />
<!-- Animated progress buttons -->
<HorizontalStackLayout Spacing="10" Margin="0,10,0,0">
<Button Text="Reset"
Style="{StaticResource SecondaryButton}"
Clicked="OnResetClicked" />
<Button Text="Animate to 100%"
Style="{StaticResource PrimaryButton}"
Clicked="OnAnimateClicked" />
<Button Text="Simulate Download"
Style="{StaticResource SuccessButton}"
Clicked="OnSimulateDownloadClicked" />
</HorizontalStackLayout>
</VerticalStackLayout>
</Border>
</VerticalStackLayout>
</ScrollView>
<!-- Event Log Panel -->
<Border Grid.Row="1"
BackgroundColor="{AppThemeBinding Light=#F5F5F5, Dark=#2C2C2C}"
StrokeThickness="0"
Padding="10">
<VerticalStackLayout>
<Label Text="Event Log:"
FontSize="12"
FontAttributes="Bold"
TextColor="{AppThemeBinding Light={StaticResource TextPrimaryLight}, Dark={StaticResource TextPrimaryDark}}" />
<ScrollView HeightRequest="80">
<Label x:Name="EventLogLabel"
Text="Events will appear here..."
FontSize="11"
TextColor="{AppThemeBinding Light={StaticResource TextSecondaryLight}, Dark={StaticResource TextSecondaryDark}}"
LineBreakMode="WordWrap" />
</ScrollView>
</VerticalStackLayout>
</Border>
</Grid>
</ContentPage>

View File

@@ -0,0 +1,83 @@
// ProgressPage - ProgressBar and ActivityIndicator Demo
using Microsoft.Maui.Controls;
namespace ShellDemo;
public partial class ProgressPage : ContentPage
{
private int _eventCount = 0;
private bool _isAnimating = false;
public ProgressPage()
{
InitializeComponent();
}
private void OnToggleIndicatorClicked(object? sender, EventArgs e)
{
ToggleIndicator.IsRunning = !ToggleIndicator.IsRunning;
LogEvent($"ActivityIndicator: {(ToggleIndicator.IsRunning ? "Started" : "Stopped")}");
}
private void OnProgressSliderValueChanged(object? sender, ValueChangedEventArgs e)
{
AnimatedProgress.Progress = e.NewValue / 100.0;
ProgressLabel.Text = $"Progress: {(int)e.NewValue}%";
}
private void OnResetClicked(object? sender, EventArgs e)
{
AnimatedProgress.Progress = 0;
ProgressSlider.Value = 0;
LogEvent("Progress reset to 0%");
}
private async void OnAnimateClicked(object? sender, EventArgs e)
{
if (_isAnimating) return;
_isAnimating = true;
LogEvent("Animation started");
for (int i = (int)ProgressSlider.Value; i <= 100; i += 5)
{
AnimatedProgress.Progress = i / 100.0;
ProgressSlider.Value = i;
await Task.Delay(100);
}
_isAnimating = false;
LogEvent("Animation completed");
}
private async void OnSimulateDownloadClicked(object? sender, EventArgs e)
{
if (_isAnimating) return;
_isAnimating = true;
LogEvent("Download simulation started");
AnimatedProgress.Progress = 0;
ProgressSlider.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;
ProgressSlider.Value = progress * 100;
await Task.Delay(200 + random.Next(300));
}
_isAnimating = false;
LogEvent("Download simulation completed");
}
private void LogEvent(string message)
{
_eventCount++;
var timestamp = DateTime.Now.ToString("HH:mm:ss");
EventLogLabel.Text = $"[{timestamp}] {_eventCount}. {message}\n{EventLogLabel.Text}";
}
}

View File

@@ -1,239 +0,0 @@
// 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,157 @@
<?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="ShellDemo.SelectionPage"
Title="Selection Controls"
BackgroundColor="{AppThemeBinding Light={StaticResource PageBackgroundLight}, Dark={StaticResource PageBackgroundDark}}">
<Grid RowDefinitions="*,120">
<!-- Main Content -->
<ScrollView Grid.Row="0">
<VerticalStackLayout Padding="20" Spacing="20">
<Label Text="Selection Controls"
FontSize="24"
FontAttributes="Bold"
TextColor="{AppThemeBinding Light={StaticResource TextPrimaryLight}, Dark={StaticResource TextPrimaryDark}}" />
<!-- CheckBox Section -->
<Border Style="{StaticResource ThemedCard}">
<VerticalStackLayout Spacing="15">
<Label Text="CheckBox"
FontSize="16"
FontAttributes="Bold"
TextColor="{AppThemeBinding Light={StaticResource TextPrimaryLight}, Dark={StaticResource TextPrimaryDark}}" />
<!-- Basic checkboxes -->
<HorizontalStackLayout Spacing="20">
<CheckBox x:Name="Checkbox1" IsChecked="False" CheckedChanged="OnCheckboxChanged" />
<Label Text="Option 1" VerticalOptions="Center"
TextColor="{AppThemeBinding Light={StaticResource TextPrimaryLight}, Dark={StaticResource TextPrimaryDark}}" />
<CheckBox x:Name="Checkbox2" IsChecked="True" CheckedChanged="OnCheckboxChanged" />
<Label Text="Option 2 (default checked)" VerticalOptions="Center"
TextColor="{AppThemeBinding Light={StaticResource TextPrimaryLight}, Dark={StaticResource TextPrimaryDark}}" />
</HorizontalStackLayout>
<!-- Colored checkboxes -->
<Label Text="Colored Checkboxes:"
FontSize="12"
TextColor="{AppThemeBinding Light={StaticResource TextSecondaryLight}, Dark={StaticResource TextSecondaryDark}}" />
<HorizontalStackLayout Spacing="20">
<CheckBox Color="Red" IsChecked="True" CheckedChanged="OnColoredCheckboxChanged" />
<CheckBox Color="Green" IsChecked="True" CheckedChanged="OnColoredCheckboxChanged" />
<CheckBox Color="Blue" IsChecked="True" CheckedChanged="OnColoredCheckboxChanged" />
<CheckBox Color="Purple" IsChecked="True" CheckedChanged="OnColoredCheckboxChanged" />
</HorizontalStackLayout>
<!-- Disabled checkbox -->
<HorizontalStackLayout Spacing="10">
<CheckBox IsChecked="True" IsEnabled="False" />
<Label Text="Disabled (checked)" VerticalOptions="Center"
TextColor="{AppThemeBinding Light={StaticResource TextSecondaryLight}, Dark={StaticResource TextSecondaryDark}}" />
</HorizontalStackLayout>
</VerticalStackLayout>
</Border>
<!-- Switch Section -->
<Border Style="{StaticResource ThemedCard}">
<VerticalStackLayout Spacing="15">
<Label Text="Switch"
FontSize="16"
FontAttributes="Bold"
TextColor="{AppThemeBinding Light={StaticResource TextPrimaryLight}, Dark={StaticResource TextPrimaryDark}}" />
<!-- Basic switch -->
<HorizontalStackLayout Spacing="15">
<Switch x:Name="BasicSwitch" IsToggled="False" Toggled="OnSwitchToggled" />
<Label x:Name="SwitchStatusLabel" Text="Off" VerticalOptions="Center" WidthRequest="50"
TextColor="{AppThemeBinding Light={StaticResource TextPrimaryLight}, Dark={StaticResource TextPrimaryDark}}" />
</HorizontalStackLayout>
<!-- Colored switches -->
<Label Text="Colored Switches:"
FontSize="12"
TextColor="{AppThemeBinding Light={StaticResource TextSecondaryLight}, Dark={StaticResource TextSecondaryDark}}" />
<HorizontalStackLayout Spacing="20">
<Switch IsToggled="True" OnColor="Green" Toggled="OnColoredSwitchToggled" />
<Switch IsToggled="True" OnColor="Orange" Toggled="OnColoredSwitchToggled" />
<Switch IsToggled="True" OnColor="Purple" Toggled="OnColoredSwitchToggled" />
</HorizontalStackLayout>
<!-- Disabled switch -->
<HorizontalStackLayout Spacing="10">
<Switch IsToggled="True" IsEnabled="False" />
<Label Text="Disabled (on)" VerticalOptions="Center"
TextColor="{AppThemeBinding Light={StaticResource TextSecondaryLight}, Dark={StaticResource TextSecondaryDark}}" />
</HorizontalStackLayout>
</VerticalStackLayout>
</Border>
<!-- Slider Section -->
<Border Style="{StaticResource ThemedCard}">
<VerticalStackLayout Spacing="15">
<Label Text="Slider"
FontSize="16"
FontAttributes="Bold"
TextColor="{AppThemeBinding Light={StaticResource TextPrimaryLight}, Dark={StaticResource TextPrimaryDark}}" />
<!-- Basic slider -->
<Slider x:Name="BasicSlider" Minimum="0" Maximum="100" Value="50" ValueChanged="OnSliderValueChanged" />
<Label x:Name="SliderValueLabel" Text="Value: 50"
TextColor="{AppThemeBinding Light={StaticResource TextPrimaryLight}, Dark={StaticResource TextPrimaryDark}}" />
<!-- Temperature slider -->
<Label Text="Temperature (0-40°C):"
FontSize="12"
Margin="0,10,0,0"
TextColor="{AppThemeBinding Light={StaticResource TextSecondaryLight}, Dark={StaticResource TextSecondaryDark}}" />
<Slider x:Name="TempSlider" Minimum="0" Maximum="40" Value="20" ValueChanged="OnTempSliderValueChanged" />
<Label x:Name="TempLabel" Text="20°C"
TextColor="{AppThemeBinding Light={StaticResource TextPrimaryLight}, Dark={StaticResource TextPrimaryDark}}" />
<!-- Colored slider -->
<Label Text="Colored Slider:"
FontSize="12"
Margin="0,10,0,0"
TextColor="{AppThemeBinding Light={StaticResource TextSecondaryLight}, Dark={StaticResource TextSecondaryDark}}" />
<Slider Minimum="0" Maximum="100" Value="75"
MinimumTrackColor="Green"
MaximumTrackColor="LightGray"
ThumbColor="DarkGreen"
ValueChanged="OnColoredSliderValueChanged" />
<!-- Disabled slider -->
<Label Text="Disabled Slider:"
FontSize="12"
Margin="0,10,0,0"
TextColor="{AppThemeBinding Light={StaticResource TextSecondaryLight}, Dark={StaticResource TextSecondaryDark}}" />
<Slider Minimum="0" Maximum="100" Value="30" IsEnabled="False" />
</VerticalStackLayout>
</Border>
</VerticalStackLayout>
</ScrollView>
<!-- Event Log Panel -->
<Border Grid.Row="1"
BackgroundColor="{AppThemeBinding Light=#F5F5F5, Dark=#2C2C2C}"
StrokeThickness="0"
Padding="10">
<VerticalStackLayout>
<Label Text="Event Log:"
FontSize="12"
FontAttributes="Bold"
TextColor="{AppThemeBinding Light={StaticResource TextPrimaryLight}, Dark={StaticResource TextPrimaryDark}}" />
<ScrollView HeightRequest="80">
<Label x:Name="EventLogLabel"
Text="Events will appear here..."
FontSize="11"
TextColor="{AppThemeBinding Light={StaticResource TextSecondaryLight}, Dark={StaticResource TextSecondaryDark}}"
LineBreakMode="WordWrap" />
</ScrollView>
</VerticalStackLayout>
</Border>
</Grid>
</ContentPage>

View File

@@ -0,0 +1,65 @@
// SelectionPage - CheckBox, Switch, Slider Demo
using Microsoft.Maui.Controls;
namespace ShellDemo;
public partial class SelectionPage : ContentPage
{
private int _eventCount = 0;
public SelectionPage()
{
InitializeComponent();
}
private void OnCheckboxChanged(object? sender, CheckedChangedEventArgs e)
{
if (sender == Checkbox1)
LogEvent($"Checkbox 1: {(e.Value ? "Checked" : "Unchecked")}");
else if (sender == Checkbox2)
LogEvent($"Checkbox 2: {(e.Value ? "Checked" : "Unchecked")}");
}
private void OnColoredCheckboxChanged(object? sender, CheckedChangedEventArgs e)
{
if (sender is CheckBox cb)
LogEvent($"{cb.Color} checkbox: {(e.Value ? "Checked" : "Unchecked")}");
}
private void OnSwitchToggled(object? sender, ToggledEventArgs e)
{
SwitchStatusLabel.Text = e.Value ? "On" : "Off";
LogEvent($"Switch toggled: {(e.Value ? "ON" : "OFF")}");
}
private void OnColoredSwitchToggled(object? sender, ToggledEventArgs e)
{
if (sender is Switch sw)
LogEvent($"{sw.OnColor} switch: {(e.Value ? "ON" : "OFF")}");
}
private void OnSliderValueChanged(object? sender, ValueChangedEventArgs e)
{
SliderValueLabel.Text = $"Value: {(int)e.NewValue}";
LogEvent($"Slider value: {(int)e.NewValue}");
}
private void OnTempSliderValueChanged(object? sender, ValueChangedEventArgs e)
{
TempLabel.Text = $"{(int)e.NewValue}°C";
LogEvent($"Temperature: {(int)e.NewValue}°C");
}
private void OnColoredSliderValueChanged(object? sender, ValueChangedEventArgs e)
{
LogEvent($"Colored slider: {(int)e.NewValue}");
}
private void LogEvent(string message)
{
_eventCount++;
var timestamp = DateTime.Now.ToString("HH:mm:ss");
EventLogLabel.Text = $"[{timestamp}] {_eventCount}. {message}\n{EventLogLabel.Text}";
}
}

View File

@@ -1,166 +0,0 @@
// 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,121 @@
<?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="ShellDemo.TextInputPage"
Title="Text Input"
BackgroundColor="{AppThemeBinding Light={StaticResource PageBackgroundLight}, Dark={StaticResource PageBackgroundDark}}">
<ScrollView>
<VerticalStackLayout Padding="20" Spacing="15">
<Label Text="Text Input Controls"
FontSize="24"
FontAttributes="Bold"
TextColor="{AppThemeBinding Light={StaticResource TextPrimaryLight}, Dark={StaticResource TextPrimaryDark}}" />
<Label Text="Click on any field and start typing. All keyboard input is handled by the framework."
FontSize="14"
TextColor="{AppThemeBinding Light={StaticResource TextSecondaryLight}, Dark={StaticResource TextSecondaryDark}}" />
<!-- Entry Section -->
<BoxView Style="{StaticResource ThemedDivider}" />
<Label Text="Entry (Single Line)"
FontSize="18"
FontAttributes="Bold"
TextColor="{AppThemeBinding Light={StaticResource TextPrimaryLight}, Dark={StaticResource TextPrimaryDark}}" />
<Entry x:Name="NameEntry"
Placeholder="Enter your name..."
Style="{StaticResource ThemedEntry}"
TextChanged="OnNameEntryTextChanged" />
<Label x:Name="EntryOutputLabel"
FontSize="12"
TextColor="{AppThemeBinding Light={StaticResource TextSecondaryLight}, Dark={StaticResource TextSecondaryDark}}" />
<Entry x:Name="EmailEntry"
Placeholder="Enter your email..."
Keyboard="Email"
Style="{StaticResource ThemedEntry}" />
<Label Text="Email keyboard type"
FontSize="12"
TextColor="{AppThemeBinding Light={StaticResource TextSecondaryLight}, Dark={StaticResource TextSecondaryDark}}" />
<Entry x:Name="PasswordEntry"
Placeholder="Enter password..."
IsPassword="True"
Style="{StaticResource ThemedEntry}" />
<Label Text="Password field (text hidden)"
FontSize="12"
TextColor="{AppThemeBinding Light={StaticResource TextSecondaryLight}, Dark={StaticResource TextSecondaryDark}}" />
<!-- SearchBar Section -->
<BoxView Style="{StaticResource ThemedDivider}" />
<Label Text="SearchBar"
FontSize="18"
FontAttributes="Bold"
TextColor="{AppThemeBinding Light={StaticResource TextPrimaryLight}, Dark={StaticResource TextPrimaryDark}}" />
<SearchBar x:Name="MainSearchBar"
Placeholder="Search for items..."
TextChanged="OnSearchBarTextChanged"
SearchButtonPressed="OnSearchButtonPressed" />
<Label x:Name="SearchOutputLabel"
FontSize="12"
TextColor="{AppThemeBinding Light={StaticResource TextSecondaryLight}, Dark={StaticResource TextSecondaryDark}}" />
<!-- Editor Section -->
<BoxView Style="{StaticResource ThemedDivider}" />
<Label Text="Editor (Multi-line)"
FontSize="18"
FontAttributes="Bold"
TextColor="{AppThemeBinding Light={StaticResource TextPrimaryLight}, Dark={StaticResource TextPrimaryDark}}" />
<Editor x:Name="MainEditor"
Placeholder="Enter multiple lines of text here...&#10;Press Enter to create new lines."
HeightRequest="120"
Style="{StaticResource ThemedEditor}"
TextChanged="OnEditorTextChanged" />
<Label x:Name="EditorOutputLabel"
FontSize="12"
TextColor="{AppThemeBinding Light={StaticResource TextSecondaryLight}, Dark={StaticResource TextSecondaryDark}}" />
<!-- Keyboard Shortcuts -->
<BoxView Style="{StaticResource ThemedDivider}" />
<Border BackgroundColor="{AppThemeBinding Light=#E3F2FD, Dark=#1A3A5C}"
StrokeThickness="0"
Padding="15">
<Border.StrokeShape>
<RoundRectangle CornerRadius="8" />
</Border.StrokeShape>
<VerticalStackLayout Spacing="5">
<Label Text="Keyboard Shortcuts"
FontAttributes="Bold"
TextColor="{AppThemeBinding Light={StaticResource TextPrimaryLight}, Dark={StaticResource TextPrimaryDark}}" />
<Label Text="Ctrl+A: Select all"
TextColor="{AppThemeBinding Light={StaticResource TextPrimaryLight}, Dark={StaticResource TextPrimaryDark}}" />
<Label Text="Ctrl+C: Copy"
TextColor="{AppThemeBinding Light={StaticResource TextPrimaryLight}, Dark={StaticResource TextPrimaryDark}}" />
<Label Text="Ctrl+V: Paste"
TextColor="{AppThemeBinding Light={StaticResource TextPrimaryLight}, Dark={StaticResource TextPrimaryDark}}" />
<Label Text="Ctrl+X: Cut"
TextColor="{AppThemeBinding Light={StaticResource TextPrimaryLight}, Dark={StaticResource TextPrimaryDark}}" />
<Label Text="Home/End: Move to start/end"
TextColor="{AppThemeBinding Light={StaticResource TextPrimaryLight}, Dark={StaticResource TextPrimaryDark}}" />
<Label Text="Shift+Arrow: Select text"
TextColor="{AppThemeBinding Light={StaticResource TextPrimaryLight}, Dark={StaticResource TextPrimaryDark}}" />
</VerticalStackLayout>
</Border>
</VerticalStackLayout>
</ScrollView>
</ContentPage>

View File

@@ -0,0 +1,34 @@
// TextInputPage - Demonstrates text input controls
using Microsoft.Maui.Controls;
namespace ShellDemo;
public partial class TextInputPage : ContentPage
{
public TextInputPage()
{
InitializeComponent();
}
private void OnNameEntryTextChanged(object? sender, TextChangedEventArgs e)
{
EntryOutputLabel.Text = $"You typed: {e.NewTextValue}";
}
private void OnSearchBarTextChanged(object? sender, TextChangedEventArgs e)
{
SearchOutputLabel.Text = $"Searching: {e.NewTextValue}";
}
private void OnSearchButtonPressed(object? sender, EventArgs e)
{
SearchOutputLabel.Text = $"Search submitted: {MainSearchBar.Text}";
}
private void OnEditorTextChanged(object? sender, TextChangedEventArgs e)
{
var lineCount = string.IsNullOrEmpty(e.NewTextValue) ? 0 : e.NewTextValue.Split('\n').Length;
EditorOutputLabel.Text = $"Lines: {lineCount}, Characters: {e.NewTextValue?.Length ?? 0}";
}
}

View File

@@ -1,19 +0,0 @@
// 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);
}
}

67
ShellDemo/Program.cs Normal file
View File

@@ -0,0 +1,67 @@
// Program.cs - Linux platform entry point
using Microsoft.Maui.Platform.Linux;
namespace ShellDemo;
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), "shelldemo.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 Shell Demo 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(); }
}

View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="456" height="456" viewBox="0 0 456 456" xmlns="http://www.w3.org/2000/svg">
<rect width="456" height="456" fill="#1B79C4"/>
</svg>

After

Width:  |  Height:  |  Size: 184 B

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="256" height="256" viewBox="0 0 256 256" xmlns="http://www.w3.org/2000/svg">
<rect width="256" height="256" rx="48" fill="#1B79C4"/>
<g transform="translate(48, 48) scale(6.67)">
<path fill="white" d="M3 13h8V3H3v10zm0 8h8v-6H3v6zm10 0h8V11h-8v10zm0-18v6h8V3h-8z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 338 B

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="456" height="456" viewBox="0 0 456 456" xmlns="http://www.w3.org/2000/svg">
<!-- Dashboard icon (Material Icons) -->
<g transform="translate(88, 88) scale(11.67)">
<path fill="#000000" d="M3 13h8V3H3v10zm0 8h8v-6H3v6zm10 0h8V11h-8v10zm0-18v6h8V3h-8z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 326 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path fill="#FFFFFF" d="M200-80q-33 0-56.5-23.5T120-160v-560q0-33 23.5-56.5T200-800h40v-80h80v80h320v-80h80v80h40q33 0 56.5 23.5T840-720v560q0 33-23.5 56.5T760-80H200Zm0-80h560v-400H200v400Zm0-480h560v-80H200v80Zm0 0v-80 80Zm280 240q-17 0-28.5-11.5T440-440q0-17 11.5-28.5T480-480q17 0 28.5 11.5T520-440q0 17-11.5 28.5T480-400Zm-160 0q-17 0-28.5-11.5T280-440q0-17 11.5-28.5T320-480q17 0 28.5 11.5T360-440q0 17-11.5 28.5T320-400Zm320 0q-17 0-28.5-11.5T600-440q0-17 11.5-28.5T640-480q17 0 28.5 11.5T680-440q0 17-11.5 28.5T640-400ZM480-240q-17 0-28.5-11.5T440-280q0-17 11.5-28.5T480-320q17 0 28.5 11.5T520-280q0 17-11.5 28.5T480-240Zm-160 0q-17 0-28.5-11.5T280-280q0-17 11.5-28.5T320-320q17 0 28.5 11.5T360-280q0 17-11.5 28.5T320-240Zm320 0q-17 0-28.5-11.5T600-280q0-17 11.5-28.5T640-320q17 0 28.5 11.5T680-280q0 17-11.5 28.5T640-240Z"/></svg>

After

Width:  |  Height:  |  Size: 927 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path fill="#212121" d="M200-80q-33 0-56.5-23.5T120-160v-560q0-33 23.5-56.5T200-800h40v-80h80v80h320v-80h80v80h40q33 0 56.5 23.5T840-720v560q0 33-23.5 56.5T760-80H200Zm0-80h560v-400H200v400Zm0-480h560v-80H200v80Zm0 0v-80 80Zm280 240q-17 0-28.5-11.5T440-440q0-17 11.5-28.5T480-480q17 0 28.5 11.5T520-440q0 17-11.5 28.5T480-400Zm-160 0q-17 0-28.5-11.5T280-440q0-17 11.5-28.5T320-480q17 0 28.5 11.5T360-440q0 17-11.5 28.5T320-400Zm320 0q-17 0-28.5-11.5T600-440q0-17 11.5-28.5T640-480q17 0 28.5 11.5T680-440q0 17-11.5 28.5T640-400ZM480-240q-17 0-28.5-11.5T440-280q0-17 11.5-28.5T480-320q17 0 28.5 11.5T520-280q0 17-11.5 28.5T480-240Zm-160 0q-17 0-28.5-11.5T280-280q0-17 11.5-28.5T320-320q17 0 28.5 11.5T360-280q0 17-11.5 28.5T320-240Zm320 0q-17 0-28.5-11.5T600-280q0-17 11.5-28.5T640-320q17 0 28.5 11.5T680-280q0 17-11.5 28.5T640-240Z"/></svg>

After

Width:  |  Height:  |  Size: 927 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path fill="#FFFFFF" d="m424-312 282-282-56-56-226 226-114-114-56 56 170 170ZM200-120q-33 0-56.5-23.5T120-200v-560q0-33 23.5-56.5T200-840h560q33 0 56.5 23.5T840-760v560q0 33-23.5 56.5T760-120H200Zm0-80h560v-560H200v560Zm0-560v560-560Z"/></svg>

After

Width:  |  Height:  |  Size: 331 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path fill="#212121" d="m424-312 282-282-56-56-226 226-114-114-56 56 170 170ZM200-120q-33 0-56.5-23.5T120-200v-560q0-33 23.5-56.5T200-840h560q33 0 56.5 23.5T840-760v560q0 33-23.5 56.5T760-120H200Zm0-80h560v-560H200v560Zm0-560v560-560Z"/></svg>

After

Width:  |  Height:  |  Size: 331 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path fill="#FFFFFF" d="M160-400v-80h280v80H160Zm0-160v-80h440v80H160Zm0-160v-80h440v80H160Zm360 560v-123l221-220q9-9 20-13t22-4q12 0 23 4.5t20 13.5l37 37q8 9 12.5 20t4.5 22q0 11-4 22.5T863-380L643-160H520Zm300-263-37-37 37 37ZM580-220h38l121-122-18-19-19-18-122 121v38Zm141-141-19-18 37 37-18-19Z"/></svg>

After

Width:  |  Height:  |  Size: 394 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path fill="#212121" d="M160-400v-80h280v80H160Zm0-160v-80h440v80H160Zm0-160v-80h440v80H160Zm360 560v-123l221-220q9-9 20-13t22-4q12 0 23 4.5t20 13.5l37 37q8 9 12.5 20t4.5 22q0 11-4 22.5T863-380L643-160H520Zm300-263-37-37 37 37ZM580-220h38l121-122-18-19-19-18-122 121v38Zm141-141-19-18 37 37-18-19Z"/></svg>

After

Width:  |  Height:  |  Size: 394 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path fill="#FFFFFF" d="M360-200v-80h480v80H360Zm0-240v-80h480v80H360Zm0-240v-80h480v80H360ZM200-160q-33 0-56.5-23.5T120-240q0-33 23.5-56.5T200-320q33 0 56.5 23.5T280-240q0 33-23.5 56.5T200-160Zm0-240q-33 0-56.5-23.5T120-480q0-33 23.5-56.5T200-560q33 0 56.5 23.5T280-480q0 33-23.5 56.5T200-400Zm0-240q-33 0-56.5-23.5T120-720q0-33 23.5-56.5T200-800q33 0 56.5 23.5T280-720q0 33-23.5 56.5T200-640Z"/></svg>

After

Width:  |  Height:  |  Size: 491 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path fill="#212121" d="M360-200v-80h480v80H360Zm0-240v-80h480v80H360Zm0-240v-80h480v80H360ZM200-160q-33 0-56.5-23.5T120-240q0-33 23.5-56.5T200-320q33 0 56.5 23.5T280-240q0 33-23.5 56.5T200-160Zm0-240q-33 0-56.5-23.5T120-480q0-33 23.5-56.5T200-560q33 0 56.5 23.5T280-480q0 33-23.5 56.5T200-400Zm0-240q-33 0-56.5-23.5T120-720q0-33 23.5-56.5T200-800q33 0 56.5 23.5T280-720q0 33-23.5 56.5T200-640Z"/></svg>

After

Width:  |  Height:  |  Size: 491 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path fill="#FFFFFF" d="M120-520v-320h320v320H120Zm0 400v-320h320v320H120Zm400-400v-320h320v320H520Zm0 400v-320h320v320H520ZM200-600h160v-160H200v160Zm400 0h160v-160H600v160Zm0 400h160v-160H600v160Zm-400 0h160v-160H200v160Zm400-400Zm0 240Zm-240 0Zm0-240Z"/></svg>

After

Width:  |  Height:  |  Size: 351 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path fill="#212121" d="M120-520v-320h320v320H120Zm0 400v-320h320v320H120Zm400-400v-320h320v320H520Zm0 400v-320h320v320H520ZM200-600h160v-160H200v160Zm400 0h160v-160H600v160Zm0 400h160v-160H600v160Zm-400 0h160v-160H200v160Zm400-400Zm0 240Zm-240 0Zm0-240Z"/></svg>

After

Width:  |  Height:  |  Size: 351 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path fill="#FFFFFF" d="M240-200h120v-240h240v240h120v-360L480-740 240-560v360Zm-80 80v-480l320-240 320 240v480H520v-240h-80v240H160Zm320-350Z"/></svg>

After

Width:  |  Height:  |  Size: 239 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path fill="#212121" d="M240-200h120v-240h240v240h120v-360L480-740 240-560v360Zm-80 80v-480l320-240 320 240v480H520v-240h-80v240H160Zm320-350Z"/></svg>

After

Width:  |  Height:  |  Size: 239 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path fill="#FFFFFF" d="M320-160h320v-120q0-66-47-113t-113-47q-66 0-113 47t-47 113v120Zm160-360q66 0 113-47t47-113v-120H320v120q0 66 47 113t113 47ZM160-80v-80h80v-120q0-61 28.5-114.5T348-480q-51-32-79.5-85.5T240-680v-120h-80v-80h640v80h-80v120q0 61-28.5 114.5T612-480q51 32 79.5 85.5T720-280v120h80v80H160Z"/></svg>

After

Width:  |  Height:  |  Size: 403 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path fill="#212121" d="M320-160h320v-120q0-66-47-113t-113-47q-66 0-113 47t-47 113v120Zm160-360q66 0 113-47t47-113v-120H320v120q0 66 47 113t113 47ZM160-80v-80h80v-120q0-61 28.5-114.5T348-480q-51-32-79.5-85.5T240-680v-120h-80v-80h640v80h-80v120q0 61-28.5 114.5T612-480q51 32 79.5 85.5T720-280v120h80v80H160Z"/></svg>

After

Width:  |  Height:  |  Size: 403 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path fill="#FFFFFF" d="M440-280h80v-240h-80v240Zm40-320q17 0 28.5-11.5T520-640q0-17-11.5-28.5T480-680q-17 0-28.5 11.5T440-640q0 17 11.5 28.5T480-600Zm0 520q-83 0-156-31.5T197-197q-54-54-85.5-127T80-480q0-83 31.5-156T197-763q54-54 127-85.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 83-31.5 156T763-197q-54 54-127 85.5T480-80Zm0-80q134 0 227-93t93-227q0-134-93-227t-227-93q-134 0-227 93t-93 227q0 134 93 227t227 93Zm0-320Z"/></svg>

After

Width:  |  Height:  |  Size: 531 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path fill="#212121" d="M440-280h80v-240h-80v240Zm40-320q17 0 28.5-11.5T520-640q0-17-11.5-28.5T480-680q-17 0-28.5 11.5T440-640q0 17 11.5 28.5T480-600Zm0 520q-83 0-156-31.5T197-197q-54-54-85.5-127T80-480q0-83 31.5-156T197-763q54-54 127-85.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 83-31.5 156T763-197q-54 54-127 85.5T480-80Zm0-80q134 0 227-93t93-227q0-134-93-227t-227-93q-134 0-227 93t-93 227q0 134 93 227t227 93Zm0-320Z"/></svg>

After

Width:  |  Height:  |  Size: 531 B

View File

@@ -0,0 +1,44 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 13.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 14948) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="904px" height="904px" viewBox="0 0 904 904" style="enable-background:new 0 0 904 904;" xml:space="preserve">
<path style="fill:#1B79C4;" d="M352.47,305.72c2.59,0.01,5.18,0,7.77-0.02c15.57-0.02,30.94,1.71,46.17,5.54
c0.94,0.23,1.88,0.46,2.85,0.7c21.6,5.47,42.63,13.46,63.64,21.34c26.31,9.85,54.17,20.02,82.04,20.19c0.7,0.01,1.41,0.01,2.13,0.02
c21.87,0.24,21.87,0.24,43.52-2.93c1.15-0.26,1.15-0.26,2.32-0.53c27.81-6.62,56.02-18.76,79.07-38.33
c2.47-1.99,2.47-1.99,4.04-1.99c0.15,9.24,0.26,18.48,0.32,27.73c0.03,4.29,0.08,8.58,0.15,12.88c0.07,4.14,0.11,8.29,0.12,12.43
c0.01,1.58,0.03,3.16,0.07,4.74c0.25,12.36,0.25,12.36-2.02,16.31c-1.58,1.62-3.07,2.82-4.91,4c-0.75,0.63-1.49,1.27-2.26,1.92
c-2.37,1.8-4.75,3.58-7.15,5.34c-0.86,0.63-1.72,1.27-2.61,1.92c-24.4,17.65-51.89,29.41-80.47,33.48
c-0.54,0.09-1.08,0.17-1.64,0.26c-7.03,0.98-14.12,0.87-21.19,0.88c-0.7,0-1.4,0-2.12,0c-11.18-0.01-22.02-0.68-33.05-2.95
c-0.73-0.14-1.47-0.28-2.22-0.43c-21.91-4.28-43.2-12.21-64.33-20.02c-68.48-25.32-136.76-38.11-203.72,1.23
c-8.4,5.1-16.65,10.72-24.29,17.22c-2.49,2-2.49,2-4.05,2c-0.24-9.43-0.42-18.86-0.53-28.29c-0.05-4.38-0.13-8.76-0.24-13.14
c-0.11-4.23-0.17-8.46-0.2-12.7c-0.02-1.61-0.06-3.22-0.11-4.83c-0.4-12.22-0.4-12.22,2.05-16.13c2.73-3.18,5.86-5.34,9.22-7.53
c1.55-1.17,3.1-2.34,4.65-3.51c1.55-1.09,3.1-2.17,4.66-3.24c0.84-0.58,1.68-1.16,2.55-1.75c20.5-13.88,42.33-23.81,65.73-28.72
c0.57-0.12,1.13-0.24,1.72-0.36C330.94,306.24,341.52,305.65,352.47,305.72z"/>
<path style="fill:#1D5DB3;" d="M462.25,230.05c36.11,13.71,36.11,13.71,73.29,22.46c0.56,0.1,1.12,0.19,1.7,0.29
c48.86,8.29,104.44-2.97,144.83-37.25c1.6-1.17,1.6-1.17,3.95-1.17c0.2,8.99,0.35,17.98,0.45,26.97c0.04,4.18,0.1,8.35,0.2,12.52
c0.09,4.03,0.15,8.06,0.17,12.1c0.01,1.54,0.05,3.07,0.09,4.6c0.35,12.01,0.35,12.01-2.78,16.23c-2.19,1.87-4.33,3.33-6.76,4.75
c-1.08,0.8-2.14,1.61-3.2,2.44c-0.97,0.71-1.95,1.4-2.92,2.1c-0.54,0.38-1.07,0.76-1.62,1.16c-1.09,0.77-2.17,1.54-3.26,2.31
c-1.39,0.97-2.76,1.96-4.14,2.95c-20.16,13.99-43.31,22.55-66.36,27.18c-0.56,0.11-1.11,0.23-1.69,0.34
c-6.97,1.38-13.98,2.08-21.04,2.38c-0.91,0.04-0.91,0.04-1.83,0.08c-37.4,1.35-72.7-11.12-107.89-24.14
c-69.35-25.65-137.32-37.43-204.66,2.34c-8.73,5.24-16.85,10.97-24.57,18.06c-1.19,0.94-1.19,0.94-2.76,0.94
c-0.22-8.72-0.39-17.44-0.49-26.16c-0.05-4.05-0.12-8.1-0.22-12.15c-0.1-3.92-0.16-7.83-0.18-11.74c-0.02-1.49-0.05-2.97-0.1-4.46
c-0.37-11.31-0.37-11.31,1.91-15.08c2.5-3.08,5.41-5.32,8.49-7.57c1.18-1.01,2.35-2.02,3.51-3.05
C312.28,194.21,387.12,201.79,462.25,230.05z"/>
<path style="fill:#0F839E;" d="M452.04,425.6c4.85,1.82,9.72,3.61,14.58,5.4c1.43,0.52,1.43,0.52,2.88,1.06
c23.64,8.69,47.6,16.84,72.3,20.19c0.93,0.13,0.93,0.13,1.88,0.26c6.69,0.82,13.39,0.83,20.11,0.82c0.61,0,1.22,0,1.85,0
c8.87-0.02,17.54-0.33,26.32-1.99c1.01-0.16,2.02-0.33,3.05-0.5c30.94-5.41,63.41-19.49,88.12-42.05c1.31-1.03,1.31-1.03,2.88-1.03
c0.22,9.21,0.39,18.42,0.49,27.63c0.05,4.28,0.11,8.55,0.22,12.83c0.1,4.13,0.16,8.26,0.18,12.4c0.02,1.57,0.05,3.15,0.1,4.72
c0.38,12.28,0.38,12.28-3.01,16.87c-2.39,2.1-4.75,3.8-7.39,5.45c-1.37,1.03-2.73,2.07-4.08,3.12c-1.31,0.95-2.63,1.9-3.96,2.83
c-0.71,0.5-1.42,1.01-2.15,1.53c-6.11,4.29-12.31,8.14-18.81,11.58c-0.6,0.32-1.2,0.65-1.82,0.98
c-36.64,19.49-78.25,26.49-118.1,18.09c-0.81-0.16-1.63-0.32-2.46-0.49c-23.79-4.8-46.79-13.74-69.75-22.22
c-29.8-11-61.27-21.77-92.8-21.96c-1.03-0.01-1.03-0.01-2.08-0.02c-2.2-0.02-4.41-0.02-6.61-0.02c-1.13,0-1.13,0-2.29,0
c-11.52,0.01-22.71,0.52-34.06,2.95c-1.12,0.23-1.12,0.23-2.26,0.46c-19.2,3.95-37.33,10.82-54.96,20.42
c-0.88,0.47-0.88,0.47-1.77,0.95c-9.51,5.14-18.5,11.37-27.23,18.11c-1.5-1.74-0.92-4.89-0.93-7.19c-0.01-0.66-0.01-1.31-0.02-1.99
c-0.02-2.18-0.03-4.37-0.04-6.55c0-0.75-0.01-1.49-0.01-2.26c-0.02-3.94-0.04-7.89-0.05-11.83c-0.01-4.07-0.05-8.15-0.09-12.22
c-0.03-3.13-0.04-6.26-0.04-9.39c-0.01-1.5-0.02-3-0.04-4.5c-0.03-2.1-0.03-4.21-0.02-6.31c-0.01-1.2-0.01-2.4-0.02-3.63
c0.6-3.91,1.8-5.17,4.39-7.67c0.56-0.54,1.12-1.09,1.69-1.65C300.38,393.44,379.63,398.41,452.04,425.6z"/>
<path style="fill:#0F839E;" d="M230.65,512.16c0.26,0,0.52,0,0.78,0c0,3.36,0,6.72,0,10.19c0.78,0.26,1.55,0.52,2.35,0.78
c-0.78,0.52-1.55,1.04-2.35,1.57c-0.78-0.78-0.78-0.78-0.86-3.43c0-1.09,0.01-2.19,0.03-3.28c0-0.56,0.01-1.11,0.01-1.69
C230.62,514.92,230.63,513.54,230.65,512.16z"/>
</svg>

After

Width:  |  Height:  |  Size: 4.6 KiB

View File

@@ -0,0 +1,10 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
<defs>
<linearGradient id="gradient" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" style="stop-color:#2196F3"/>
<stop offset="100%" style="stop-color:#1976D2"/>
</linearGradient>
</defs>
<circle cx="50" cy="50" r="45" fill="url(#gradient)"/>
<text x="50" y="62" font-family="Arial, sans-serif" font-size="32" font-weight="bold" fill="white" text-anchor="middle">OM</text>
</svg>

After

Width:  |  Height:  |  Size: 479 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path fill="#FFFFFF" d="M160-280q-33 0-56.5-23.5T80-360v-240q0-33 23.5-56.5T160-680h640q33 0 56.5 23.5T880-600v240q0 33-23.5 56.5T800-280h-40v-80h40v-240H160v240h240v80H160Zm420 80-44-96-96-44 96-44 44-96 44 96 96 44-96 44-44 96Zm100-200-25-55-55-25 55-25 25-55 25 55 55 25-55 25-25 55Z"/></svg>

After

Width:  |  Height:  |  Size: 383 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path fill="#212121" d="M160-280q-33 0-56.5-23.5T80-360v-240q0-33 23.5-56.5T160-680h640q33 0 56.5 23.5T880-600v240q0 33-23.5 56.5T800-280h-40v-80h40v-240H160v240h240v80H160Zm420 80-44-96-96-44 96-44 44-96 44 96 96 44-96 44-44 96Zm100-200-25-55-55-25 55-25 25-55 25 55 55 25-55 25-25 55Z"/></svg>

After

Width:  |  Height:  |  Size: 383 B

View File

@@ -6,10 +6,52 @@
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks> <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<!-- Application Info -->
<ApplicationTitle>OpenMaui Shell Demo</ApplicationTitle>
<ApplicationId>com.openmaui.shelldemo</ApplicationId>
<ApplicationVersion>1.0.0</ApplicationVersion>
<!-- MAUI Settings (conditional based on platform) -->
<SingleProject>true</SingleProject>
<EnableDefaultXamlItems>true</EnableDefaultXamlItems>
</PropertyGroup> </PropertyGroup>
<!-- Non-Linux platforms: Use official MAUI workload -->
<PropertyGroup Condition="!$([MSBuild]::IsOSPlatform('Linux'))">
<UseMaui>true</UseMaui>
<TargetFrameworks>net9.0-android;net9.0-ios;net9.0-maccatalyst;net9.0-windows10.0.19041.0</TargetFrameworks>
</PropertyGroup>
<!-- Linux: Use OpenMaui -->
<PropertyGroup Condition="$([MSBuild]::IsOSPlatform('Linux'))">
<RuntimeIdentifiers>linux-x64;linux-arm64</RuntimeIdentifiers>
</PropertyGroup>
<!-- OpenMaui Linux Platform - Local development (ProjectReference) -->
<ItemGroup Condition="$([MSBuild]::IsOSPlatform('Linux')) AND Exists('../../maui-linux/OpenMaui.Controls.Linux.csproj') AND '$(UsePackageReference)' != 'true'">
<ProjectReference Include="../../maui-linux/OpenMaui.Controls.Linux.csproj" />
</ItemGroup>
<!-- OpenMaui Linux Platform - CI/CD builds (PackageReference) -->
<ItemGroup Condition="$([MSBuild]::IsOSPlatform('Linux')) AND (!Exists('../../maui-linux/OpenMaui.Controls.Linux.csproj') OR '$(UsePackageReference)' == 'true')">
<PackageReference Include="OpenMaui.Controls.Linux" Version="*" />
</ItemGroup>
<!-- XAML Files -->
<ItemGroup> <ItemGroup>
<PackageReference Include="OpenMaui.Controls.Linux" Version="1.0.0-preview.*" /> <MauiXaml Update="**/*.xaml" />
</ItemGroup>
<!-- App Icon -->
<ItemGroup>
<MauiIcon Include="Resources\AppIcon\appicon.svg" ForegroundFile="Resources\AppIcon\appiconfg.svg" Color="#FFFFFF" BackgroundColor="#1B79C4" />
</ItemGroup>
<!-- Embedded Resources (images) -->
<ItemGroup>
<EmbeddedResource Include="Resources\Images\*.svg" />
<EmbeddedResource Include="Resources\Images\*.png" />
</ItemGroup> </ItemGroup>
</Project> </Project>

8
ShellDemo/run.sh Executable file
View File

@@ -0,0 +1,8 @@
#!/bin/bash
# Set .NET environment for desktop launcher compatibility
export DOTNET_ROOT="$HOME/.dotnet"
export PATH="$DOTNET_ROOT:$PATH"
cd "$(dirname "$0")/bin/Debug/net9.0"
exec ./ShellDemo "$@"

View File

@@ -1,21 +0,0 @@
// 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;
}
}

76
TodoApp/App.xaml Normal file
View File

@@ -0,0 +1,76 @@
<?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="TodoApp.App">
<Application.Resources>
<ResourceDictionary>
<!-- Primary Brand Colors -->
<Color x:Key="PrimaryColor">#5C6BC0</Color>
<Color x:Key="PrimaryDarkColor">#3949AB</Color>
<Color x:Key="AccentColor">#26A69A</Color>
<Color x:Key="DangerColor">#EF5350</Color>
<Color x:Key="CompletedColor">#9E9E9E</Color>
<!-- Light Theme Colors -->
<Color x:Key="PageBackgroundLight">#F5F7FA</Color>
<Color x:Key="CardBackgroundLight">#FFFFFF</Color>
<Color x:Key="TextPrimaryLight">#212121</Color>
<Color x:Key="TextSecondaryLight">#757575</Color>
<Color x:Key="BorderLight">#E8EAF6</Color>
<Color x:Key="DividerLight">#E0E0E0</Color>
<Color x:Key="AlternateRowLight">#F5F5F5</Color>
<!-- Dark Theme Colors -->
<Color x:Key="PageBackgroundDark">#121212</Color>
<Color x:Key="CardBackgroundDark">#1E1E1E</Color>
<Color x:Key="TextPrimaryDark">#FFFFFF</Color>
<Color x:Key="TextSecondaryDark">#B0B0B0</Color>
<Color x:Key="BorderDark">#424242</Color>
<Color x:Key="DividerDark">#424242</Color>
<Color x:Key="AlternateRowDark">#2A2A2A</Color>
<!-- Navigation Bar Colors -->
<Color x:Key="NavBarBackgroundLight">#5C6BC0</Color>
<Color x:Key="NavBarBackgroundDark">#3949AB</Color>
<!-- Themed Styles -->
<Style x:Key="ThemedCard" TargetType="Border">
<Setter Property="BackgroundColor" Value="{AppThemeBinding Light={StaticResource CardBackgroundLight}, Dark={StaticResource CardBackgroundDark}}" />
<Setter Property="Stroke" Value="{AppThemeBinding Light={StaticResource BorderLight}, Dark={StaticResource BorderDark}}" />
<Setter Property="StrokeThickness" Value="1" />
<Setter Property="Padding" Value="16,12" />
<Setter Property="StrokeShape">
<Setter.Value>
<RoundRectangle CornerRadius="10" />
</Setter.Value>
</Setter>
</Style>
<Style x:Key="ThemedEntry" TargetType="Entry">
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource TextPrimaryLight}, Dark={StaticResource TextPrimaryDark}}" />
<Setter Property="PlaceholderColor" Value="{AppThemeBinding Light={StaticResource TextSecondaryLight}, Dark={StaticResource TextSecondaryDark}}" />
<Setter Property="BackgroundColor" Value="Transparent" />
</Style>
<Style x:Key="ThemedEditor" TargetType="Editor">
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource TextPrimaryLight}, Dark={StaticResource TextPrimaryDark}}" />
<Setter Property="PlaceholderColor" Value="{AppThemeBinding Light={StaticResource TextSecondaryLight}, Dark={StaticResource TextSecondaryDark}}" />
<Setter Property="BackgroundColor" Value="Transparent" />
</Style>
<Style x:Key="PrimaryButton" TargetType="Button">
<Setter Property="BackgroundColor" Value="{StaticResource PrimaryColor}" />
<Setter Property="TextColor" Value="White" />
<Setter Property="FontAttributes" Value="Bold" />
<Setter Property="Padding" Value="20,12" />
</Style>
<Style x:Key="DangerButton" TargetType="Button">
<Setter Property="BackgroundColor" Value="{StaticResource DangerColor}" />
<Setter Property="TextColor" Value="White" />
<Setter Property="FontAttributes" Value="Bold" />
<Setter Property="Padding" Value="20,12" />
</Style>
</ResourceDictionary>
</Application.Resources>
</Application>

38
TodoApp/App.xaml.cs Normal file
View File

@@ -0,0 +1,38 @@
// TodoApp - Main Application with NavigationPage and Theme Support
using Microsoft.Maui.Controls;
namespace TodoApp;
public partial class App : Application
{
public static NavigationPage? NavigationPage { get; private set; }
public App()
{
InitializeComponent();
}
protected override Window CreateWindow(IActivationState? activationState)
{
// Determine current theme for navigation bar colors
var isDarkMode = Current?.RequestedTheme == AppTheme.Dark;
var barBackground = isDarkMode ? Color.FromArgb("#3949AB") : Color.FromArgb("#5C6BC0");
NavigationPage = new NavigationPage(new TodoListPage())
{
Title = "OpenMaui Todo App",
BarBackgroundColor = barBackground,
BarTextColor = Colors.White
};
// Update navigation bar when theme changes
Current!.RequestedThemeChanged += (s, e) =>
{
var dark = e.RequestedTheme == AppTheme.Dark;
NavigationPage.BarBackgroundColor = dark ? Color.FromArgb("#3949AB") : Color.FromArgb("#5C6BC0");
};
return new Window(NavigationPage);
}
}

View File

@@ -3,19 +3,12 @@
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="TodoApp.NewTodoPage" x:Class="TodoApp.NewTodoPage"
Title="New Task" Title="New Task"
BackgroundColor="#F5F7FA"> BackgroundColor="{AppThemeBinding Light={StaticResource PageBackgroundLight}, Dark={StaticResource PageBackgroundDark}}">
<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> <ContentPage.ToolbarItems>
<ToolbarItem Text="Save" Clicked="OnSaveClicked" /> <ToolbarItem Text="Save"
IconImageSource="save_white.svg"
Clicked="OnSaveClicked" />
</ContentPage.ToolbarItems> </ContentPage.ToolbarItems>
<Grid RowDefinitions="Auto,Auto,Auto,*" RowSpacing="16" Padding="20"> <Grid RowDefinitions="Auto,Auto,Auto,*" RowSpacing="16" Padding="20">
@@ -24,10 +17,10 @@
<VerticalStackLayout Grid.Row="0" Spacing="4"> <VerticalStackLayout Grid.Row="0" Spacing="4">
<Label Text="Create a new task" <Label Text="Create a new task"
FontSize="24" FontSize="24"
TextColor="{StaticResource TextPrimary}" /> TextColor="{AppThemeBinding Light={StaticResource TextPrimaryLight}, Dark={StaticResource TextPrimaryDark}}" />
<Label Text="Fill in the details below" <Label Text="Fill in the details below"
FontSize="14" FontSize="14"
TextColor="{StaticResource TextSecondary}" /> TextColor="{AppThemeBinding Light={StaticResource TextSecondaryLight}, Dark={StaticResource TextSecondaryDark}}" />
</VerticalStackLayout> </VerticalStackLayout>
<!-- Title Section --> <!-- Title Section -->
@@ -36,8 +29,8 @@
FontSize="13" FontSize="13"
FontAttributes="Bold" FontAttributes="Bold"
TextColor="{StaticResource PrimaryColor}" /> TextColor="{StaticResource PrimaryColor}" />
<Border BackgroundColor="{StaticResource CardBackground}" <Border BackgroundColor="{AppThemeBinding Light={StaticResource CardBackgroundLight}, Dark={StaticResource CardBackgroundDark}}"
Stroke="{StaticResource BorderColor}" Stroke="{AppThemeBinding Light={StaticResource BorderLight}, Dark={StaticResource BorderDark}}"
StrokeThickness="1" StrokeThickness="1"
Padding="16,12"> Padding="16,12">
<Border.StrokeShape> <Border.StrokeShape>
@@ -46,8 +39,8 @@
<Entry x:Name="TitleEntry" <Entry x:Name="TitleEntry"
Placeholder="What needs to be done?" Placeholder="What needs to be done?"
FontSize="18" FontSize="18"
TextColor="{StaticResource TextPrimary}" TextColor="{AppThemeBinding Light={StaticResource TextPrimaryLight}, Dark={StaticResource TextPrimaryDark}}"
PlaceholderColor="{StaticResource TextSecondary}" /> PlaceholderColor="{AppThemeBinding Light={StaticResource TextSecondaryLight}, Dark={StaticResource TextSecondaryDark}}" />
</Border> </Border>
</VerticalStackLayout> </VerticalStackLayout>
@@ -60,8 +53,8 @@
<!-- Notes Section (fills remaining space) --> <!-- Notes Section (fills remaining space) -->
<Border Grid.Row="3" <Border Grid.Row="3"
BackgroundColor="{StaticResource CardBackground}" BackgroundColor="{AppThemeBinding Light={StaticResource CardBackgroundLight}, Dark={StaticResource CardBackgroundDark}}"
Stroke="{StaticResource BorderColor}" Stroke="{AppThemeBinding Light={StaticResource BorderLight}, Dark={StaticResource BorderDark}}"
StrokeThickness="1" StrokeThickness="1"
Padding="16,12"> Padding="16,12">
<Border.StrokeShape> <Border.StrokeShape>
@@ -70,8 +63,8 @@
<Editor x:Name="NotesEditor" <Editor x:Name="NotesEditor"
Placeholder="Add notes (optional)..." Placeholder="Add notes (optional)..."
FontSize="14" FontSize="14"
TextColor="{StaticResource TextPrimary}" TextColor="{AppThemeBinding Light={StaticResource TextPrimaryLight}, Dark={StaticResource TextPrimaryDark}}"
PlaceholderColor="{StaticResource TextSecondary}" PlaceholderColor="{AppThemeBinding Light={StaticResource TextSecondaryLight}, Dark={StaticResource TextSecondaryDark}}"
VerticalOptions="Fill" /> VerticalOptions="Fill" />
</Border> </Border>

View File

@@ -3,21 +3,15 @@
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="TodoApp.TodoDetailPage" x:Class="TodoApp.TodoDetailPage"
Title="Task Details" Title="Task Details"
BackgroundColor="#F5F7FA"> BackgroundColor="{AppThemeBinding Light={StaticResource PageBackgroundLight}, Dark={StaticResource PageBackgroundDark}}">
<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> <ContentPage.ToolbarItems>
<ToolbarItem Text="Delete" Clicked="OnDeleteClicked" /> <ToolbarItem Text="Delete"
<ToolbarItem Text="Save" Clicked="OnSaveClicked" /> IconImageSource="delete_white.svg"
Clicked="OnDeleteClicked" />
<ToolbarItem Text="Save"
IconImageSource="save_white.svg"
Clicked="OnSaveClicked" />
</ContentPage.ToolbarItems> </ContentPage.ToolbarItems>
<Grid RowDefinitions="Auto,Auto,*,Auto,Auto" RowSpacing="16" Padding="20"> <Grid RowDefinitions="Auto,Auto,*,Auto,Auto" RowSpacing="16" Padding="20">
@@ -28,8 +22,8 @@
FontSize="13" FontSize="13"
FontAttributes="Bold" FontAttributes="Bold"
TextColor="{StaticResource PrimaryColor}" /> TextColor="{StaticResource PrimaryColor}" />
<Border BackgroundColor="{StaticResource CardBackground}" <Border BackgroundColor="{AppThemeBinding Light={StaticResource CardBackgroundLight}, Dark={StaticResource CardBackgroundDark}}"
Stroke="{StaticResource BorderColor}" Stroke="{AppThemeBinding Light={StaticResource BorderLight}, Dark={StaticResource BorderDark}}"
StrokeThickness="1" StrokeThickness="1"
Padding="16,12"> Padding="16,12">
<Border.StrokeShape> <Border.StrokeShape>
@@ -38,8 +32,8 @@
<Entry x:Name="TitleEntry" <Entry x:Name="TitleEntry"
Placeholder="Task title" Placeholder="Task title"
FontSize="18" FontSize="18"
TextColor="{StaticResource TextPrimary}" TextColor="{AppThemeBinding Light={StaticResource TextPrimaryLight}, Dark={StaticResource TextPrimaryDark}}"
PlaceholderColor="{StaticResource TextSecondary}" /> PlaceholderColor="{AppThemeBinding Light={StaticResource TextSecondaryLight}, Dark={StaticResource TextSecondaryDark}}" />
</Border> </Border>
</VerticalStackLayout> </VerticalStackLayout>
@@ -52,8 +46,8 @@
<!-- Notes Section (fills remaining space) --> <!-- Notes Section (fills remaining space) -->
<Border Grid.Row="2" <Border Grid.Row="2"
BackgroundColor="{StaticResource CardBackground}" BackgroundColor="{AppThemeBinding Light={StaticResource CardBackgroundLight}, Dark={StaticResource CardBackgroundDark}}"
Stroke="{StaticResource BorderColor}" Stroke="{AppThemeBinding Light={StaticResource BorderLight}, Dark={StaticResource BorderDark}}"
StrokeThickness="1" StrokeThickness="1"
Padding="16,12"> Padding="16,12">
<Border.StrokeShape> <Border.StrokeShape>
@@ -62,8 +56,8 @@
<Editor x:Name="NotesEditor" <Editor x:Name="NotesEditor"
Placeholder="Add notes here..." Placeholder="Add notes here..."
FontSize="14" FontSize="14"
TextColor="{StaticResource TextPrimary}" TextColor="{AppThemeBinding Light={StaticResource TextPrimaryLight}, Dark={StaticResource TextPrimaryDark}}"
PlaceholderColor="{StaticResource TextSecondary}" PlaceholderColor="{AppThemeBinding Light={StaticResource TextSecondaryLight}, Dark={StaticResource TextSecondaryDark}}"
VerticalOptions="Fill" /> VerticalOptions="Fill" />
</Border> </Border>
@@ -73,8 +67,8 @@
FontSize="13" FontSize="13"
FontAttributes="Bold" FontAttributes="Bold"
TextColor="{StaticResource PrimaryColor}" /> TextColor="{StaticResource PrimaryColor}" />
<Border BackgroundColor="{StaticResource CardBackground}" <Border BackgroundColor="{AppThemeBinding Light={StaticResource CardBackgroundLight}, Dark={StaticResource CardBackgroundDark}}"
Stroke="{StaticResource BorderColor}" Stroke="{AppThemeBinding Light={StaticResource BorderLight}, Dark={StaticResource BorderDark}}"
StrokeThickness="1" StrokeThickness="1"
Padding="16,12"> Padding="16,12">
<Border.StrokeShape> <Border.StrokeShape>
@@ -88,7 +82,8 @@
<Label x:Name="StatusLabel" <Label x:Name="StatusLabel"
Text="In Progress" Text="In Progress"
FontSize="16" FontSize="16"
TextColor="{StaticResource TextPrimary}" LineBreakMode="NoWrap"
TextColor="{AppThemeBinding Light={StaticResource TextPrimaryLight}, Dark={StaticResource TextPrimaryDark}}"
VerticalOptions="Center" /> VerticalOptions="Center" />
</HorizontalStackLayout> </HorizontalStackLayout>
</Border> </Border>
@@ -98,7 +93,8 @@
<Label Grid.Row="4" <Label Grid.Row="4"
x:Name="CreatedLabel" x:Name="CreatedLabel"
FontSize="13" FontSize="13"
TextColor="{StaticResource TextSecondary}" LineBreakMode="NoWrap"
TextColor="{AppThemeBinding Light={StaticResource TextSecondaryLight}, Dark={StaticResource TextSecondaryDark}}"
HorizontalOptions="Center" HorizontalOptions="Center"
HorizontalTextAlignment="Center" /> HorizontalTextAlignment="Center" />

View File

@@ -4,20 +4,10 @@
xmlns:local="clr-namespace:TodoApp" xmlns:local="clr-namespace:TodoApp"
x:Class="TodoApp.TodoListPage" x:Class="TodoApp.TodoListPage"
Title="My Tasks" Title="My Tasks"
BackgroundColor="#F5F7FA"> BackgroundColor="{AppThemeBinding Light={StaticResource PageBackgroundLight}, Dark={StaticResource PageBackgroundDark}}">
<ContentPage.Resources> <ContentPage.Resources>
<ResourceDictionary> <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 --> <!-- Converters -->
<local:AlternatingRowColorConverter x:Key="AlternatingRowColorConverter" /> <local:AlternatingRowColorConverter x:Key="AlternatingRowColorConverter" />
<local:CompletedToColorConverter x:Key="CompletedToColorConverter" /> <local:CompletedToColorConverter x:Key="CompletedToColorConverter" />
@@ -27,7 +17,9 @@
</ContentPage.Resources> </ContentPage.Resources>
<ContentPage.ToolbarItems> <ContentPage.ToolbarItems>
<ToolbarItem Text="+ Add" Clicked="OnAddClicked" /> <ToolbarItem Text="Add"
IconImageSource="add_white.svg"
Clicked="OnAddClicked" />
</ContentPage.ToolbarItems> </ContentPage.ToolbarItems>
<Grid RowDefinitions="*,Auto" Padding="0"> <Grid RowDefinitions="*,Auto" Padding="0">
@@ -47,11 +39,11 @@
Padding="40"> Padding="40">
<Label Text="No tasks yet" <Label Text="No tasks yet"
FontSize="22" FontSize="22"
TextColor="{StaticResource TextSecondary}" TextColor="{AppThemeBinding Light={StaticResource TextSecondaryLight}, Dark={StaticResource TextSecondaryDark}}"
HorizontalOptions="Center" /> HorizontalOptions="Center" />
<Label Text="Tap '+ Add' to create your first task" <Label Text="Tap '+ Add' to create your first task"
FontSize="14" FontSize="14"
TextColor="{StaticResource TextSecondary}" TextColor="{AppThemeBinding Light={StaticResource TextSecondaryLight}, Dark={StaticResource TextSecondaryDark}}"
HorizontalOptions="Center" HorizontalOptions="Center"
Margin="0,8,0,0" /> Margin="0,8,0,0" />
</VerticalStackLayout> </VerticalStackLayout>
@@ -62,7 +54,7 @@
<!-- Card-style item --> <!-- Card-style item -->
<Grid Padding="0,6" BackgroundColor="Transparent"> <Grid Padding="0,6" BackgroundColor="Transparent">
<Border StrokeThickness="0" <Border StrokeThickness="0"
BackgroundColor="{StaticResource CardBackground}" BackgroundColor="{AppThemeBinding Light=#FFFFFF, Dark=#1E1E1E}"
Padding="16,14" Padding="16,14"
Opacity="{Binding IsCompleted, Converter={StaticResource CompletedToOpacityConverter}}"> Opacity="{Binding IsCompleted, Converter={StaticResource CompletedToOpacityConverter}}">
<Border.StrokeShape> <Border.StrokeShape>
@@ -75,7 +67,7 @@
WidthRequest="8" WidthRequest="8"
HeightRequest="44" HeightRequest="44"
Margin="0" Margin="0"
BackgroundColor="{Binding IsCompleted, Converter={StaticResource CompletedToColorConverter}, ConverterParameter=indicator}" BackgroundColor="#26A69A"
VerticalOptions="Center"> VerticalOptions="Center">
<Border.StrokeShape> <Border.StrokeShape>
<RoundRectangle CornerRadius="4" /> <RoundRectangle CornerRadius="4" />
@@ -106,22 +98,30 @@
</CollectionView.ItemTemplate> </CollectionView.ItemTemplate>
</CollectionView> </CollectionView>
<!-- Footer Stats --> <!-- Footer Stats with Theme Toggle -->
<Border Grid.Row="1" <Border Grid.Row="1"
BackgroundColor="{StaticResource PrimaryColor}" BackgroundColor="{StaticResource PrimaryColor}"
StrokeThickness="0" StrokeThickness="0"
Padding="24,14" Padding="20,10"
Margin="0"> Margin="0">
<Grid ColumnDefinitions="Auto,Auto" ColumnSpacing="6" HorizontalOptions="Center" VerticalOptions="Center"> <Grid ColumnDefinitions="*,Auto" VerticalOptions="Center">
<!-- Stats on the left -->
<Label Grid.Column="0" <Label Grid.Column="0"
Text="Tasks:"
FontSize="15"
TextColor="White" />
<Label Grid.Column="1"
x:Name="StatsLabel" x:Name="StatsLabel"
FontSize="15" FontSize="15"
TextColor="White" TextColor="White"
Opacity="0.9" /> VerticalOptions="Center"
LineBreakMode="NoWrap" />
<!-- Theme Toggle on the right -->
<ImageButton Grid.Column="1"
x:Name="ThemeToggleButton"
Source="light_mode_white.svg"
WidthRequest="32"
HeightRequest="32"
BackgroundColor="Transparent"
Clicked="OnThemeToggleClicked"
VerticalOptions="Center" />
</Grid> </Grid>
</Border> </Border>

View File

@@ -0,0 +1,228 @@
// 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;
private bool _isNavigating; // Guard against double navigation
public TodoListPage()
{
Console.WriteLine("[TodoListPage] Constructor starting");
InitializeComponent();
TodoCollectionView.ItemsSource = _service.Todos;
UpdateStats();
// Subscribe to theme changes to verify event is firing
if (Application.Current != null)
{
Application.Current.RequestedThemeChanged += (s, e) =>
{
Console.WriteLine($"[TodoListPage] RequestedThemeChanged event received! NewTheme={e.RequestedTheme}");
};
}
Console.WriteLine("[TodoListPage] Constructor finished");
}
protected override void OnAppearing()
{
base.OnAppearing();
// Reset navigation guard when page reappears
_isNavigating = false;
// Refresh indexes for alternating row colors
_service.RefreshIndexes();
// Refresh the collection view
TodoCollectionView.ItemsSource = null;
TodoCollectionView.ItemsSource = _service.Todos;
UpdateStats();
UpdateThemeIcon();
}
private async void OnAddClicked(object sender, EventArgs e)
{
await Navigation.PushAsync(new NewTodoPage());
}
private async void OnSelectionChanged(object? sender, SelectionChangedEventArgs e)
{
// Guard against double navigation
if (_isNavigating)
{
return;
}
if (e.CurrentSelection.FirstOrDefault() is TodoItem todo)
{
_isNavigating = true;
TodoCollectionView.SelectedItem = null; // Deselect immediately
await Navigation.PushAsync(new TodoDetailPage(todo));
// Note: _isNavigating is reset in OnAppearing when we return
}
}
private void UpdateStats()
{
var completed = _service.CompletedCount;
var total = _service.TotalCount;
if (total == 0)
{
StatsLabel.Text = "No tasks";
}
else
{
StatsLabel.Text = $"Tasks: {completed} of {total} completed";
}
}
private void UpdateThemeIcon()
{
// Check UserAppTheme first, fall back to RequestedTheme
var userTheme = Application.Current?.UserAppTheme ?? AppTheme.Unspecified;
var effectiveTheme = userTheme != AppTheme.Unspecified ? userTheme : Application.Current?.RequestedTheme ?? AppTheme.Light;
var isDarkMode = effectiveTheme == AppTheme.Dark;
// Show sun icon in dark mode (to switch to light), moon icon in light mode (to switch to dark)
ThemeToggleButton.Source = isDarkMode ? "light_mode_white.svg" : "dark_mode_white.svg";
Console.WriteLine($"[TodoListPage] UpdateThemeIcon: UserAppTheme={userTheme}, RequestedTheme={Application.Current?.RequestedTheme}, isDarkMode={isDarkMode}");
}
private void OnThemeToggleClicked(object? sender, EventArgs e)
{
if (Application.Current == null) return;
// Check current effective theme
var userTheme = Application.Current.UserAppTheme;
var effectiveTheme = userTheme != AppTheme.Unspecified ? userTheme : Application.Current.RequestedTheme;
var isDarkMode = effectiveTheme == AppTheme.Dark;
// Toggle to the opposite theme
var newTheme = isDarkMode ? AppTheme.Light : AppTheme.Dark;
Application.Current.UserAppTheme = newTheme;
Console.WriteLine($"[TodoListPage] Theme toggled from {effectiveTheme} to {newTheme}");
UpdateThemeIcon();
}
}
/// <summary>
/// Converter for alternating row background colors with Light/Dark mode support.
/// </summary>
public class AlternatingRowColorConverter : IValueConverter
{
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
{
bool isDarkMode = Application.Current?.RequestedTheme == AppTheme.Dark;
if (value is int index)
{
if (isDarkMode)
{
return index % 2 == 0 ? Color.FromArgb("#1E1E1E") : Color.FromArgb("#2A2A2A");
}
return index % 2 == 0 ? Colors.White : Color.FromArgb("#F5F5F5");
}
return isDarkMode ? Color.FromArgb("#1E1E1E") : 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 with Light/Dark mode support.
/// </summary>
public class CompletedToColorConverter : IValueConverter
{
// Light theme colors
private static readonly Color AccentColorLight = Color.FromArgb("#26A69A");
private static readonly Color AccentColorDark = Color.FromArgb("#4DB6AC");
private static readonly Color CompletedColor = Color.FromArgb("#9E9E9E");
private static readonly Color TextPrimaryLight = Color.FromArgb("#212121");
private static readonly Color TextSecondaryLight = Color.FromArgb("#757575");
// Dark theme colors
private static readonly Color TextPrimaryDark = Color.FromArgb("#FFFFFF");
private static readonly Color TextSecondaryDark = Color.FromArgb("#B0B0B0");
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
{
bool isCompleted = value is bool b && b;
string param = parameter as string ?? "";
bool isDarkMode = Application.Current?.RequestedTheme == AppTheme.Dark;
// Indicator bar color - theme-aware accent color
if (param == "indicator")
{
var color = isCompleted ? CompletedColor : (isDarkMode ? AccentColorDark : AccentColorLight);
Console.WriteLine($"[CompletedToColorConverter] indicator: isCompleted={isCompleted}, isDarkMode={isDarkMode}, color={color}");
return color;
}
// Text colors with theme support
if (isCompleted)
{
return CompletedColor;
}
else
{
if (param == "notes")
{
return isDarkMode ? TextSecondaryDark : TextSecondaryLight;
}
return isDarkMode ? TextPrimaryDark : TextPrimaryLight;
}
}
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,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="456" height="456" viewBox="0 0 456 456" xmlns="http://www.w3.org/2000/svg">
<rect width="456" height="456" fill="#0F839E"/>
</svg>

After

Width:  |  Height:  |  Size: 184 B

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="256" height="256" viewBox="0 0 256 256" xmlns="http://www.w3.org/2000/svg">
<rect width="256" height="256" rx="48" fill="#0F839E"/>
<g transform="translate(48, 48) scale(6.67)">
<path fill="white" d="M22 5.18L10.59 16.6l-4.24-4.24-1.41 1.41 5.66 5.66 12.83-12.83L22 5.18zM12 20c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8m0-18C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 472 B

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="456" height="456" viewBox="0 0 456 456" xmlns="http://www.w3.org/2000/svg">
<!-- Task/Check circle icon (Material Icons) -->
<g transform="translate(88, 88) scale(11.67)">
<path fill="#000000" d="M22 5.18L10.59 16.6l-4.24-4.24-1.41 1.41 5.66 5.66 12.83-12.83L22 5.18zM12 20c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8m0-18C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 468 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path fill="#FFFFFF" d="M440-440H200v-80h240v-240h80v240h240v80H520v240h-80v-240Z"/></svg>

After

Width:  |  Height:  |  Size: 178 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path fill="#FFFFFF" d="M480-120q-150 0-255-105T120-480q0-150 105-255t255-105q14 0 27.5 1t26.5 3q-41 29-65.5 75.5T444-660q0 90 63 153t153 63q55 0 101-24.5t75-65.5q2 13 3 26.5t1 27.5q0 150-105 255T480-120Zm0-80q88 0 158-48.5T740-375q-20 5-40 8t-40 3q-123 0-209.5-86.5T364-660q0-20 3-40t8-40q-78 32-126.5 102T200-480q0 116 82 198t198 82Zm-10-270Z"/></svg>

After

Width:  |  Height:  |  Size: 442 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path fill="#FFFFFF" d="M280-120q-33 0-56.5-23.5T200-200v-520h-40v-80h200v-40h240v40h200v80h-40v520q0 33-23.5 56.5T680-120H280Zm400-600H280v520h400v-520ZM360-280h80v-360h-80v360Zm160 0h80v-360h-80v360ZM280-720v520-520Z"/></svg>

After

Width:  |  Height:  |  Size: 315 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path fill="#FFFFFF" d="M480-360q50 0 85-35t35-85q0-50-35-85t-85-35q-50 0-85 35t-35 85q0 50 35 85t85 35Zm0 80q-83 0-141.5-58.5T280-480q0-83 58.5-141.5T480-680q83 0 141.5 58.5T680-480q0 83-58.5 141.5T480-280ZM200-440H40v-80h160v80Zm720 0H760v-80h160v80ZM440-760v-160h80v160h-80Zm0 720v-160h80v160h-80ZM256-650l-101-97 57-59 96 100-52 56Zm492 496-97-101 53-55 101 97-57 59Zm-98-550 97-101 59 57-100 96-56-52ZM154-212l101-97 55 53-97 101-59-57Zm326-268Z"/></svg>

After

Width:  |  Height:  |  Size: 548 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path fill="#FFFFFF" d="M382-240 154-468l57-57 171 171 367-367 57 57-424 424Z"/></svg>

After

Width:  |  Height:  |  Size: 174 B

View File

@@ -6,10 +6,51 @@
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks> <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<!-- Application Info -->
<ApplicationTitle>OpenMaui Todo</ApplicationTitle>
<ApplicationId>com.openmaui.todoapp</ApplicationId>
<ApplicationVersion>1.0.0</ApplicationVersion>
<!-- MAUI Settings (conditional based on platform) -->
<SingleProject>true</SingleProject>
<EnableDefaultXamlItems>true</EnableDefaultXamlItems>
</PropertyGroup> </PropertyGroup>
<!-- Non-Linux platforms: Use official MAUI workload -->
<PropertyGroup Condition="!$([MSBuild]::IsOSPlatform('Linux'))">
<UseMaui>true</UseMaui>
<TargetFrameworks>net9.0-android;net9.0-ios;net9.0-maccatalyst;net9.0-windows10.0.19041.0</TargetFrameworks>
</PropertyGroup>
<!-- Linux: Use OpenMaui -->
<PropertyGroup Condition="$([MSBuild]::IsOSPlatform('Linux'))">
<RuntimeIdentifiers>linux-x64;linux-arm64</RuntimeIdentifiers>
</PropertyGroup>
<!-- OpenMaui Linux Platform - Local development (ProjectReference) -->
<ItemGroup Condition="$([MSBuild]::IsOSPlatform('Linux')) AND Exists('../../maui-linux/OpenMaui.Controls.Linux.csproj') AND '$(UsePackageReference)' != 'true'">
<ProjectReference Include="../../maui-linux/OpenMaui.Controls.Linux.csproj" />
</ItemGroup>
<!-- OpenMaui Linux Platform - CI/CD builds (PackageReference) -->
<ItemGroup Condition="$([MSBuild]::IsOSPlatform('Linux')) AND (!Exists('../../maui-linux/OpenMaui.Controls.Linux.csproj') OR '$(UsePackageReference)' == 'true')">
<PackageReference Include="OpenMaui.Controls.Linux" Version="*" />
</ItemGroup>
<!-- XAML Files -->
<ItemGroup> <ItemGroup>
<PackageReference Include="OpenMaui.Controls.Linux" Version="1.0.0-preview.*" /> <MauiXaml Update="**/*.xaml" />
</ItemGroup>
<!-- App Icon -->
<ItemGroup>
<MauiIcon Include="Resources\AppIcon\appicon.svg" ForegroundFile="Resources\AppIcon\appiconfg.svg" Color="#FFFFFF" BackgroundColor="#0F839E" />
</ItemGroup>
<!-- Images -->
<ItemGroup>
<MauiImage Include="Resources\Images\*.svg" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@@ -78,4 +78,15 @@ public class TodoItem : INotifyPropertyChanged
{ {
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
} }
/// <summary>
/// Forces all bindings to re-evaluate by notifying all properties changed.
/// Used when external factors (like app theme) change.
/// </summary>
public void RefreshBindings()
{
OnPropertyChanged(nameof(IsCompleted));
OnPropertyChanged(nameof(Title));
OnPropertyChanged(nameof(Notes));
}
} }

View File

@@ -1,174 +0,0 @@
// 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();
}
}

8
TodoApp/run.sh Executable file
View File

@@ -0,0 +1,8 @@
#!/bin/bash
# Set .NET environment for desktop launcher compatibility
export DOTNET_ROOT="$HOME/.dotnet"
export PATH="$DOTNET_ROOT:$PATH"
cd "$(dirname "$0")/bin/Debug/net9.0"
exec ./TodoApp "$@"

71
WebViewDemo/App.xaml Normal file
View File

@@ -0,0 +1,71 @@
<?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="WebViewDemo.App">
<Application.Resources>
<ResourceDictionary>
<!-- Primary Brand Colors -->
<Color x:Key="PrimaryColor">#5C6BC0</Color>
<Color x:Key="PrimaryDarkColor">#3949AB</Color>
<Color x:Key="AccentColor">#26A69A</Color>
<Color x:Key="ButtonColor">#667EEA</Color>
<!-- Light Theme Colors -->
<Color x:Key="PageBackgroundLight">#F5F7FA</Color>
<Color x:Key="CardBackgroundLight">#FFFFFF</Color>
<Color x:Key="TextPrimaryLight">#212121</Color>
<Color x:Key="TextSecondaryLight">#757575</Color>
<Color x:Key="BorderLight">#E0E0E0</Color>
<!-- Dark Theme Colors -->
<Color x:Key="PageBackgroundDark">#121212</Color>
<Color x:Key="CardBackgroundDark">#1E1E1E</Color>
<Color x:Key="TextPrimaryDark">#FFFFFF</Color>
<Color x:Key="TextSecondaryDark">#B0B0B0</Color>
<Color x:Key="BorderDark">#424242</Color>
<!-- Navigation Bar Colors -->
<Color x:Key="NavBarBackgroundLight">#5C6BC0</Color>
<Color x:Key="NavBarBackgroundDark">#3949AB</Color>
<!-- Themed Styles -->
<Style x:Key="ThemedCard" TargetType="Border">
<Setter Property="BackgroundColor" Value="{AppThemeBinding Light={StaticResource CardBackgroundLight}, Dark={StaticResource CardBackgroundDark}}" />
<Setter Property="StrokeThickness" Value="0" />
<Setter Property="Padding" Value="12" />
<Setter Property="StrokeShape">
<Setter.Value>
<RoundRectangle CornerRadius="8" />
</Setter.Value>
</Setter>
</Style>
<Style x:Key="ThemedEntry" TargetType="Entry">
<Setter Property="TextColor" Value="{AppThemeBinding Light={StaticResource TextPrimaryLight}, Dark={StaticResource TextPrimaryDark}}" />
<Setter Property="PlaceholderColor" Value="{AppThemeBinding Light={StaticResource TextSecondaryLight}, Dark={StaticResource TextSecondaryDark}}" />
<Setter Property="BackgroundColor" Value="Transparent" />
</Style>
<Style x:Key="NavButton" TargetType="Button">
<Setter Property="BackgroundColor" Value="{StaticResource ButtonColor}" />
<Setter Property="TextColor" Value="White" />
<Setter Property="HeightRequest" Value="36" />
<Setter Property="FontSize" Value="13" />
</Style>
<Style x:Key="PrimaryButton" TargetType="Button">
<Setter Property="BackgroundColor" Value="{StaticResource PrimaryColor}" />
<Setter Property="TextColor" Value="White" />
<Setter Property="HeightRequest" Value="36" />
<Setter Property="FontSize" Value="13" />
</Style>
<Style x:Key="AccentButton" TargetType="Button">
<Setter Property="BackgroundColor" Value="{StaticResource AccentColor}" />
<Setter Property="TextColor" Value="White" />
<Setter Property="HeightRequest" Value="36" />
<Setter Property="FontSize" Value="13" />
</Style>
</ResourceDictionary>
</Application.Resources>
</Application>

30
WebViewDemo/App.xaml.cs Normal file
View File

@@ -0,0 +1,30 @@
// App.xaml.cs - Main Application with Theme Support
using Microsoft.Maui.Controls;
namespace WebViewDemo;
public partial class App : Application
{
public App()
{
InitializeComponent();
}
protected override Window CreateWindow(IActivationState? activationState)
{
return new Window(new WebViewPage())
{
Title = "OpenMaui Browser"
};
}
public static void ToggleTheme()
{
if (Current is null) return;
Current.UserAppTheme = Current.UserAppTheme == AppTheme.Dark
? AppTheme.Light
: AppTheme.Dark;
}
}

View File

@@ -0,0 +1,22 @@
// MauiProgram.cs - MAUI app configuration
using Microsoft.Maui.Hosting;
using Microsoft.Maui.Platform.Linux.Hosting;
namespace WebViewDemo;
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,153 @@
<?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="WebViewDemo.WebViewPage"
BackgroundColor="{AppThemeBinding Light={StaticResource PageBackgroundLight}, Dark={StaticResource PageBackgroundDark}}">
<Grid RowDefinitions="Auto,Auto,*,Auto">
<!-- Browser Toolbar -->
<Grid Grid.Row="0"
ColumnDefinitions="Auto,Auto,Auto,*,Auto,Auto,Auto,Auto"
ColumnSpacing="4"
Padding="8"
BackgroundColor="{AppThemeBinding Light={StaticResource CardBackgroundLight}, Dark={StaticResource CardBackgroundDark}}">
<!-- Back Button -->
<ImageButton Grid.Column="0"
x:Name="BackButton"
Source="{AppThemeBinding Light=arrow_back_light.svg, Dark=arrow_back_dark.svg}"
WidthRequest="36"
HeightRequest="36"
Padding="8"
BackgroundColor="Transparent"
VerticalOptions="Center"
Clicked="OnBackClicked"
ToolTipProperties.Text="Go Back" />
<!-- Forward Button -->
<ImageButton Grid.Column="1"
x:Name="ForwardButton"
Source="{AppThemeBinding Light=arrow_forward_light.svg, Dark=arrow_forward_dark.svg}"
WidthRequest="36"
HeightRequest="36"
Padding="8"
BackgroundColor="Transparent"
VerticalOptions="Center"
Clicked="OnForwardClicked"
ToolTipProperties.Text="Go Forward" />
<!-- Reload Button -->
<ImageButton Grid.Column="2"
x:Name="ReloadButton"
Source="{AppThemeBinding Light=refresh_light.svg, Dark=refresh_dark.svg}"
WidthRequest="36"
HeightRequest="36"
Padding="8"
BackgroundColor="Transparent"
VerticalOptions="Center"
Clicked="OnReloadClicked"
ToolTipProperties.Text="Reload Page" />
<!-- URL Bar -->
<Border Grid.Column="3"
BackgroundColor="{AppThemeBinding Light={StaticResource CardBackgroundLight}, Dark={StaticResource CardBackgroundDark}}"
StrokeThickness="1"
Stroke="{AppThemeBinding Light={StaticResource BorderLight}, Dark={StaticResource BorderDark}}"
Padding="8,4"
Margin="4,0"
VerticalOptions="Center">
<Border.StrokeShape>
<RoundRectangle CornerRadius="18" />
</Border.StrokeShape>
<Entry x:Name="UrlEntry"
Placeholder="Enter URL or search..."
Text="https://dotnet.microsoft.com"
FontSize="14"
TextColor="{AppThemeBinding Light={StaticResource TextPrimaryLight}, Dark={StaticResource TextPrimaryDark}}"
PlaceholderColor="{AppThemeBinding Light={StaticResource TextSecondaryLight}, Dark={StaticResource TextSecondaryDark}}"
BackgroundColor="Transparent"
VerticalOptions="Center"
Completed="OnUrlSubmitted" />
</Border>
<!-- Go Button -->
<ImageButton Grid.Column="4"
x:Name="GoButton"
Source="{AppThemeBinding Light=send_light.svg, Dark=send_dark.svg}"
WidthRequest="36"
HeightRequest="36"
Padding="8"
BackgroundColor="{StaticResource AccentColor}"
CornerRadius="18"
VerticalOptions="Center"
Clicked="OnGoClicked"
ToolTipProperties.Text="Navigate" />
<!-- Load HTML Demo Button -->
<ImageButton Grid.Column="5"
x:Name="LoadHtmlButton"
Source="{AppThemeBinding Light=code_light.svg, Dark=code_dark.svg}"
WidthRequest="36"
HeightRequest="36"
Padding="8"
BackgroundColor="Transparent"
VerticalOptions="Center"
Clicked="OnLoadHtmlClicked"
ToolTipProperties.Text="Load Demo HTML" />
<!-- Eval JS Demo Button -->
<ImageButton Grid.Column="6"
x:Name="EvalJsButton"
Source="{AppThemeBinding Light=play_arrow_light.svg, Dark=play_arrow_dark.svg}"
WidthRequest="36"
HeightRequest="36"
Padding="8"
BackgroundColor="Transparent"
VerticalOptions="Center"
Clicked="OnEvalJsClicked"
ToolTipProperties.Text="Run JavaScript" />
<!-- Theme Toggle Button -->
<ImageButton Grid.Column="7"
x:Name="ThemeToggleButton"
Source="{AppThemeBinding Light=dark_mode.svg, Dark=light_mode.svg}"
WidthRequest="36"
HeightRequest="36"
Padding="8"
BackgroundColor="Transparent"
VerticalOptions="Center"
Clicked="OnThemeToggleClicked"
ToolTipProperties.Text="Toggle Theme" />
</Grid>
<!-- Loading Progress Bar (own row above WebView) -->
<ProgressBar Grid.Row="1"
x:Name="LoadingProgress"
HeightRequest="3"
ProgressColor="{StaticResource AccentColor}"
Progress="0"
IsVisible="False" />
<!-- WebView - Main Content Area -->
<WebView Grid.Row="2"
x:Name="MainWebView"
VerticalOptions="Fill"
HorizontalOptions="Fill"
Navigating="OnNavigating"
Navigated="OnNavigated" />
<!-- Status Bar -->
<Grid Grid.Row="3"
Padding="8,4"
BackgroundColor="{AppThemeBinding Light=#E8E8E8, Dark=#1A1A1A}">
<Label x:Name="StatusLabel"
Text="Ready"
FontSize="12"
TextColor="{AppThemeBinding Light={StaticResource TextSecondaryLight}, Dark={StaticResource TextSecondaryDark}}"
VerticalOptions="Center" />
</Grid>
</Grid>
</ContentPage>

View File

@@ -0,0 +1,271 @@
// WebViewPage - Main page demonstrating WebView with WebKitGTK
using Microsoft.Maui.Controls;
namespace WebViewDemo;
public partial class WebViewPage : ContentPage
{
private CancellationTokenSource? _progressCts;
public WebViewPage()
{
Console.WriteLine("[WebViewPage] Constructor starting");
InitializeComponent();
// Set initial URL
MainWebView.Source = new UrlWebViewSource { Url = "https://dotnet.microsoft.com" };
// Configure URL entry to select all on double-click (like a browser address bar)
EntryExtensions.SetSelectAllOnDoubleClick(UrlEntry, true);
Console.WriteLine("[WebViewPage] Constructor finished");
}
private void OnBackClicked(object? sender, EventArgs e)
{
Console.WriteLine("[WebViewPage] Back button clicked");
if (MainWebView.CanGoBack)
{
MainWebView.GoBack();
}
}
private void OnForwardClicked(object? sender, EventArgs e)
{
Console.WriteLine("[WebViewPage] Forward button clicked");
if (MainWebView.CanGoForward)
{
MainWebView.GoForward();
}
}
private void OnReloadClicked(object? sender, EventArgs e)
{
Console.WriteLine("[WebViewPage] Reload button clicked");
MainWebView.Reload();
}
private void OnGoClicked(object? sender, EventArgs e)
{
Navigate();
}
private void OnUrlSubmitted(object? sender, EventArgs e)
{
Navigate();
}
private void Navigate()
{
var url = UrlEntry.Text?.Trim();
if (string.IsNullOrEmpty(url))
return;
// Add https:// if not present
if (!url.StartsWith("http://") && !url.StartsWith("https://"))
url = "https://" + url;
Console.WriteLine($"[WebViewPage] Navigating to: {url}");
MainWebView.Source = new UrlWebViewSource { Url = url };
UrlEntry.Text = url;
}
private void OnNavigating(object? sender, WebNavigatingEventArgs e)
{
StatusLabel.Text = $"Loading: {e.Url}";
Console.WriteLine($"[WebViewPage] Navigating to: {e.Url}");
// Start progress animation
StartProgressAnimation();
}
private void OnNavigated(object? sender, WebNavigatedEventArgs e)
{
// Stop progress animation and complete
StopProgressAnimation(e.Result == WebNavigationResult.Success);
StatusLabel.Text = e.Result == WebNavigationResult.Success
? $"Loaded: {e.Url}"
: $"Failed: {e.Result}";
UrlEntry.Text = e.Url;
Console.WriteLine($"[WebViewPage] Navigated: {e.Result} - {e.Url}");
}
private void StartProgressAnimation()
{
// Cancel any existing animation
_progressCts?.Cancel();
_progressCts = new CancellationTokenSource();
// Show and reset progress bar
LoadingProgress.Progress = 0;
LoadingProgress.IsVisible = true;
// Animate progress (simulated since WebView doesn't report actual progress)
var token = _progressCts.Token;
Dispatcher.Dispatch(async () =>
{
try
{
// Quick initial progress
await LoadingProgress.ProgressTo(0.3, 200, Easing.CubicOut);
if (token.IsCancellationRequested) return;
// Slower middle progress
await LoadingProgress.ProgressTo(0.6, 500, Easing.Linear);
if (token.IsCancellationRequested) return;
// Even slower as we wait
await LoadingProgress.ProgressTo(0.85, 1000, Easing.CubicIn);
}
catch (TaskCanceledException)
{
// Expected when navigation completes
}
});
}
private void StopProgressAnimation(bool success)
{
_progressCts?.Cancel();
Dispatcher.Dispatch(async () =>
{
if (success)
{
// Complete the progress bar
await LoadingProgress.ProgressTo(1.0, 150, Easing.CubicOut);
await Task.Delay(100);
}
// Hide the progress bar
LoadingProgress.IsVisible = false;
LoadingProgress.Progress = 0;
});
}
private async void OnLoadHtmlClicked(object? sender, EventArgs e)
{
Console.WriteLine("[WebViewPage] Load HTML button clicked");
StatusLabel.Text = "Loading demo HTML...";
// Show quick progress for HTML loading
LoadingProgress.Progress = 0;
LoadingProgress.IsVisible = true;
await LoadingProgress.ProgressTo(0.5, 100, Easing.CubicOut);
var html = @"
<!DOCTYPE html>
<html>
<head>
<title>OpenMaui WebView</title>
<style>
body {
font-family: 'Segoe UI', Arial, sans-serif;
margin: 40px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
min-height: 100vh;
}
h1 {
font-size: 2.5em;
margin-bottom: 20px;
}
p {
font-size: 1.2em;
line-height: 1.6;
}
.feature-list {
background: rgba(255,255,255,0.1);
padding: 20px;
border-radius: 10px;
margin-top: 20px;
}
li {
margin: 10px 0;
font-size: 1.1em;
}
button {
background: #4CAF50;
color: white;
border: none;
padding: 15px 30px;
font-size: 1.1em;
border-radius: 5px;
cursor: pointer;
margin-top: 20px;
}
button:hover {
background: #45a049;
}
</style>
</head>
<body>
<h1>Hello from OpenMaui Linux!</h1>
<p>This HTML content is rendered by WebKitGTK inside your .NET MAUI application.</p>
<div class='feature-list'>
<h2>WebView Features:</h2>
<ul>
<li>Full HTML5 support</li>
<li>CSS3 animations and transitions</li>
<li>JavaScript execution</li>
<li>Navigation history (back/forward)</li>
<li>WebGL and canvas support</li>
</ul>
</div>
<button onclick=""alert('Hello from JavaScript!')"">Click Me!</button>
<p style='margin-top: 30px; opacity: 0.8;'>
Powered by WebKitGTK - the same engine used by GNOME Web (Epiphany)
</p>
</body>
</html>";
MainWebView.Source = new HtmlWebViewSource { Html = html };
// Complete progress
await LoadingProgress.ProgressTo(1.0, 100, Easing.CubicOut);
await Task.Delay(100);
LoadingProgress.IsVisible = false;
StatusLabel.Text = "Loaded demo HTML";
UrlEntry.Text = "about:demo";
}
private async void OnEvalJsClicked(object? sender, EventArgs e)
{
Console.WriteLine("[WebViewPage] Run JS button clicked");
try
{
var result = await MainWebView.EvaluateJavaScriptAsync("document.title");
StatusLabel.Text = $"JS Result: {result ?? "(null)"}";
Console.WriteLine($"[WebViewPage] JS Eval result: {result}");
}
catch (Exception ex)
{
StatusLabel.Text = $"JS Error: {ex.Message}";
Console.WriteLine($"[WebViewPage] JS Error: {ex.Message}");
}
}
private void OnThemeToggleClicked(object? sender, EventArgs e)
{
Console.WriteLine("[WebViewPage] Theme toggle clicked");
Console.WriteLine($"[WebViewPage] Before: UserAppTheme={Application.Current?.UserAppTheme}, RequestedTheme={Application.Current?.RequestedTheme}");
App.ToggleTheme();
Console.WriteLine($"[WebViewPage] After: UserAppTheme={Application.Current?.UserAppTheme}, RequestedTheme={Application.Current?.RequestedTheme}");
var theme = Application.Current?.UserAppTheme == AppTheme.Dark ? "Dark" : "Light";
StatusLabel.Text = $"Theme: {theme}";
// Debug: Check what the ImageButton Source is now
Console.WriteLine($"[WebViewPage] ThemeToggleButton.Source = {ThemeToggleButton.Source}");
}
}

70
WebViewDemo/Program.cs Normal file
View File

@@ -0,0 +1,70 @@
// Program.cs - Linux platform entry point
using Microsoft.Maui.Platform.Linux;
namespace WebViewDemo;
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), "webviewdemo.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 WebView Demo 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 with GTK mode for WebView support
LinuxApplication.Run(app, args, options =>
{
options.UseGtk = true;
});
}
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(); }
}

View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="456" height="456" viewBox="0 0 456 456" xmlns="http://www.w3.org/2000/svg">
<rect width="456" height="456" fill="#1D5DB3"/>
</svg>

After

Width:  |  Height:  |  Size: 184 B

Some files were not shown because too many files have changed in this diff Show More