Initial samples: TodoApp and ShellDemo
Two sample applications demonstrating OpenMaui Linux: TodoApp: - Full task manager with NavigationPage - CollectionView with XAML data binding - DisplayAlert dialogs - Grid layouts with star sizing ShellDemo: - Comprehensive control showcase - Shell with flyout navigation - All core MAUI controls demonstrated - Real-time event logging Both samples reference OpenMaui.Controls.Linux via NuGet. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
commit
b18a178dd1
|
|
@ -0,0 +1,53 @@
|
||||||
|
# Build results
|
||||||
|
[Bb]in/
|
||||||
|
[Oo]bj/
|
||||||
|
[Oo]ut/
|
||||||
|
[Ll]og/
|
||||||
|
[Ll]ogs/
|
||||||
|
|
||||||
|
# .NET
|
||||||
|
*.nupkg
|
||||||
|
*.snupkg
|
||||||
|
*.user
|
||||||
|
*.suo
|
||||||
|
*.userprefs
|
||||||
|
*.cache
|
||||||
|
project.lock.json
|
||||||
|
project.fragment.lock.json
|
||||||
|
artifacts/
|
||||||
|
|
||||||
|
# Visual Studio
|
||||||
|
.vs/
|
||||||
|
*.csproj.user
|
||||||
|
*.dbmdl
|
||||||
|
*.jfm
|
||||||
|
|
||||||
|
# JetBrains Rider
|
||||||
|
.idea/
|
||||||
|
*.sln.iml
|
||||||
|
|
||||||
|
# Visual Studio Code
|
||||||
|
.vscode/
|
||||||
|
|
||||||
|
# macOS
|
||||||
|
.DS_Store
|
||||||
|
|
||||||
|
# User-specific files
|
||||||
|
*.rsuser
|
||||||
|
*.suo
|
||||||
|
*.user
|
||||||
|
*.userosscache
|
||||||
|
*.sln.docstates
|
||||||
|
|
||||||
|
# Test Results
|
||||||
|
[Tt]est[Rr]esult*/
|
||||||
|
[Bb]uild[Ll]og.*
|
||||||
|
TestResults/
|
||||||
|
|
||||||
|
# NuGet
|
||||||
|
*.nupkg
|
||||||
|
**/[Pp]ackages/*
|
||||||
|
!**/[Pp]ackages/build/
|
||||||
|
|
||||||
|
# Publish output
|
||||||
|
publish/
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
@ -0,0 +1,76 @@
|
||||||
|
# OpenMaui Linux Samples
|
||||||
|
|
||||||
|
Sample applications demonstrating [OpenMaui Linux](https://github.com/open-maui/maui-linux) - .NET MAUI on Linux.
|
||||||
|
|
||||||
|
## Samples
|
||||||
|
|
||||||
|
| Sample | Description |
|
||||||
|
|--------|-------------|
|
||||||
|
| [TodoApp](./TodoApp/) | Full-featured task manager with NavigationPage, XAML data binding, and CollectionView |
|
||||||
|
| [ShellDemo](./ShellDemo/) | Comprehensive control showcase with Shell navigation and flyout menu |
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
- .NET 9.0 SDK
|
||||||
|
- Linux with X11 (Ubuntu, Fedora, etc.)
|
||||||
|
- SkiaSharp dependencies: `libfontconfig1-dev libfreetype6-dev`
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Clone the samples
|
||||||
|
git clone https://github.com/open-maui/maui-linux-samples.git
|
||||||
|
cd maui-linux-samples
|
||||||
|
|
||||||
|
# Run TodoApp
|
||||||
|
cd TodoApp
|
||||||
|
dotnet run
|
||||||
|
|
||||||
|
# Or run ShellDemo
|
||||||
|
cd ../ShellDemo
|
||||||
|
dotnet run
|
||||||
|
```
|
||||||
|
|
||||||
|
## Building for Deployment
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Build for Linux ARM64
|
||||||
|
dotnet publish -c Release -r linux-arm64
|
||||||
|
|
||||||
|
# Build for Linux x64
|
||||||
|
dotnet publish -c Release -r linux-x64
|
||||||
|
```
|
||||||
|
|
||||||
|
## TodoApp
|
||||||
|
|
||||||
|
A complete task management application demonstrating:
|
||||||
|
- 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
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## ShellDemo
|
||||||
|
|
||||||
|
A comprehensive control gallery demonstrating:
|
||||||
|
- 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
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## Related
|
||||||
|
|
||||||
|
- [OpenMaui Linux Framework](https://github.com/open-maui/maui-linux) - The core framework
|
||||||
|
- [NuGet Package](https://www.nuget.org/packages/OpenMaui.Controls.Linux) - Install via NuGet
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
MIT License - See [LICENSE](LICENSE) for details.
|
||||||
|
|
@ -0,0 +1,78 @@
|
||||||
|
// ShellDemo App - Comprehensive Control Demo
|
||||||
|
|
||||||
|
using Microsoft.Maui.Controls;
|
||||||
|
|
||||||
|
namespace ShellDemo;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Main application class with Shell navigation.
|
||||||
|
/// </summary>
|
||||||
|
public class App : Application
|
||||||
|
{
|
||||||
|
public App()
|
||||||
|
{
|
||||||
|
MainPage = new AppShell();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Shell definition with flyout menu - comprehensive control demo.
|
||||||
|
/// </summary>
|
||||||
|
public class AppShell : Shell
|
||||||
|
{
|
||||||
|
public AppShell()
|
||||||
|
{
|
||||||
|
FlyoutBehavior = FlyoutBehavior.Flyout;
|
||||||
|
Title = "OpenMaui Controls Demo";
|
||||||
|
|
||||||
|
// Register routes for push navigation (pages not in flyout)
|
||||||
|
Routing.RegisterRoute("detail", typeof(DetailPage));
|
||||||
|
|
||||||
|
// Home
|
||||||
|
Items.Add(CreateFlyoutItem("Home", typeof(HomePage)));
|
||||||
|
|
||||||
|
// Buttons Demo
|
||||||
|
Items.Add(CreateFlyoutItem("Buttons", typeof(ButtonsPage)));
|
||||||
|
|
||||||
|
// Text Input Demo
|
||||||
|
Items.Add(CreateFlyoutItem("Text Input", typeof(TextInputPage)));
|
||||||
|
|
||||||
|
// Selection Controls Demo
|
||||||
|
Items.Add(CreateFlyoutItem("Selection", typeof(SelectionPage)));
|
||||||
|
|
||||||
|
// Pickers Demo
|
||||||
|
Items.Add(CreateFlyoutItem("Pickers", typeof(PickersPage)));
|
||||||
|
|
||||||
|
// Lists Demo
|
||||||
|
Items.Add(CreateFlyoutItem("Lists", typeof(ListsPage)));
|
||||||
|
|
||||||
|
// Progress Demo
|
||||||
|
Items.Add(CreateFlyoutItem("Progress", typeof(ProgressPage)));
|
||||||
|
|
||||||
|
// Grids Demo
|
||||||
|
Items.Add(CreateFlyoutItem("Grids", typeof(GridsPage)));
|
||||||
|
|
||||||
|
// About
|
||||||
|
Items.Add(CreateFlyoutItem("About", typeof(AboutPage)));
|
||||||
|
}
|
||||||
|
|
||||||
|
private FlyoutItem CreateFlyoutItem(string title, Type pageType)
|
||||||
|
{
|
||||||
|
// Route is required for Shell.GoToAsync navigation to work
|
||||||
|
var route = title.Replace(" ", "");
|
||||||
|
return new FlyoutItem
|
||||||
|
{
|
||||||
|
Title = title,
|
||||||
|
Route = route,
|
||||||
|
Items =
|
||||||
|
{
|
||||||
|
new ShellContent
|
||||||
|
{
|
||||||
|
Title = title,
|
||||||
|
Route = route,
|
||||||
|
ContentTemplate = new DataTemplate(pageType)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
// MauiProgram.cs - Shared MAUI app configuration
|
||||||
|
// Works across all platforms (iOS, Android, Windows, Linux)
|
||||||
|
|
||||||
|
using Microsoft.Maui.Hosting;
|
||||||
|
using Microsoft.Maui.Platform.Linux.Hosting;
|
||||||
|
|
||||||
|
namespace ShellDemo;
|
||||||
|
|
||||||
|
public static class MauiProgram
|
||||||
|
{
|
||||||
|
public static MauiApp CreateMauiApp()
|
||||||
|
{
|
||||||
|
var builder = MauiApp.CreateBuilder();
|
||||||
|
|
||||||
|
// Configure the app (shared across all platforms)
|
||||||
|
builder.UseMauiApp<App>();
|
||||||
|
|
||||||
|
// Add Linux platform support
|
||||||
|
// On other platforms, this would be iOS/Android/Windows specific
|
||||||
|
builder.UseLinux();
|
||||||
|
|
||||||
|
return builder.Build();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,115 @@
|
||||||
|
// AboutPage - Information about OpenMaui Linux
|
||||||
|
|
||||||
|
using Microsoft.Maui.Controls;
|
||||||
|
using Microsoft.Maui.Graphics;
|
||||||
|
|
||||||
|
namespace ShellDemo;
|
||||||
|
|
||||||
|
public class AboutPage : ContentPage
|
||||||
|
{
|
||||||
|
public AboutPage()
|
||||||
|
{
|
||||||
|
Title = "About";
|
||||||
|
|
||||||
|
Content = new ScrollView
|
||||||
|
{
|
||||||
|
Content = new VerticalStackLayout
|
||||||
|
{
|
||||||
|
Padding = new Thickness(20),
|
||||||
|
Spacing = 20,
|
||||||
|
Children =
|
||||||
|
{
|
||||||
|
new Label
|
||||||
|
{
|
||||||
|
Text = "OpenMaui Linux",
|
||||||
|
FontSize = 32,
|
||||||
|
FontAttributes = FontAttributes.Bold,
|
||||||
|
TextColor = Color.FromArgb("#1A237E"),
|
||||||
|
HorizontalOptions = LayoutOptions.Center
|
||||||
|
},
|
||||||
|
new Label
|
||||||
|
{
|
||||||
|
Text = "Version 1.0.0",
|
||||||
|
FontSize = 16,
|
||||||
|
TextColor = Colors.Gray,
|
||||||
|
HorizontalOptions = LayoutOptions.Center
|
||||||
|
},
|
||||||
|
new BoxView { HeightRequest = 1, Color = Colors.LightGray },
|
||||||
|
new Label
|
||||||
|
{
|
||||||
|
Text = "OpenMaui Linux brings .NET MAUI to Linux desktops using SkiaSharp for rendering. " +
|
||||||
|
"It provides a native Linux experience while maintaining compatibility with MAUI's cross-platform API.",
|
||||||
|
FontSize = 14,
|
||||||
|
LineBreakMode = LineBreakMode.WordWrap
|
||||||
|
},
|
||||||
|
CreateInfoCard("Platform", "Linux (X11/Wayland)"),
|
||||||
|
CreateInfoCard("Rendering", "SkiaSharp"),
|
||||||
|
CreateInfoCard("Framework", ".NET MAUI"),
|
||||||
|
CreateInfoCard("License", "MIT License"),
|
||||||
|
new BoxView { HeightRequest = 1, Color = Colors.LightGray },
|
||||||
|
new Label
|
||||||
|
{
|
||||||
|
Text = "Features",
|
||||||
|
FontSize = 20,
|
||||||
|
FontAttributes = FontAttributes.Bold
|
||||||
|
},
|
||||||
|
CreateFeatureItem("Full XAML support with styles and resources"),
|
||||||
|
CreateFeatureItem("Shell navigation with flyout menus"),
|
||||||
|
CreateFeatureItem("All standard MAUI controls"),
|
||||||
|
CreateFeatureItem("Data binding and MVVM"),
|
||||||
|
CreateFeatureItem("Keyboard and mouse input"),
|
||||||
|
CreateFeatureItem("High DPI support"),
|
||||||
|
new BoxView { HeightRequest = 1, Color = Colors.LightGray },
|
||||||
|
new Label
|
||||||
|
{
|
||||||
|
Text = "https://github.com/pablotoledo/OpenMaui-Linux",
|
||||||
|
FontSize = 12,
|
||||||
|
TextColor = Colors.Blue,
|
||||||
|
HorizontalOptions = LayoutOptions.Center
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private Frame CreateInfoCard(string label, string value)
|
||||||
|
{
|
||||||
|
return new Frame
|
||||||
|
{
|
||||||
|
CornerRadius = 8,
|
||||||
|
Padding = new Thickness(15),
|
||||||
|
BackgroundColor = Color.FromArgb("#F5F5F5"),
|
||||||
|
HasShadow = false,
|
||||||
|
Content = new HorizontalStackLayout
|
||||||
|
{
|
||||||
|
Children =
|
||||||
|
{
|
||||||
|
new Label
|
||||||
|
{
|
||||||
|
Text = label + ":",
|
||||||
|
FontAttributes = FontAttributes.Bold,
|
||||||
|
WidthRequest = 100
|
||||||
|
},
|
||||||
|
new Label
|
||||||
|
{
|
||||||
|
Text = value,
|
||||||
|
TextColor = Colors.Gray
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private View CreateFeatureItem(string text)
|
||||||
|
{
|
||||||
|
return new HorizontalStackLayout
|
||||||
|
{
|
||||||
|
Spacing = 10,
|
||||||
|
Children =
|
||||||
|
{
|
||||||
|
new Label { Text = "✓", TextColor = Color.FromArgb("#4CAF50"), FontSize = 16 },
|
||||||
|
new Label { Text = text, FontSize = 14 }
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,229 @@
|
||||||
|
// ButtonsPage - Comprehensive Button Control Demo
|
||||||
|
|
||||||
|
using Microsoft.Maui.Controls;
|
||||||
|
using Microsoft.Maui.Graphics;
|
||||||
|
|
||||||
|
namespace ShellDemo;
|
||||||
|
|
||||||
|
public class ButtonsPage : ContentPage
|
||||||
|
{
|
||||||
|
private readonly Label _eventLog;
|
||||||
|
private int _eventCount = 0;
|
||||||
|
|
||||||
|
public ButtonsPage()
|
||||||
|
{
|
||||||
|
Title = "Buttons Demo";
|
||||||
|
|
||||||
|
_eventLog = new Label
|
||||||
|
{
|
||||||
|
Text = "Events will appear here...",
|
||||||
|
FontSize = 11,
|
||||||
|
TextColor = Colors.Gray,
|
||||||
|
LineBreakMode = LineBreakMode.WordWrap
|
||||||
|
};
|
||||||
|
|
||||||
|
Content = new Grid
|
||||||
|
{
|
||||||
|
RowDefinitions =
|
||||||
|
{
|
||||||
|
new RowDefinition { Height = new GridLength(1, GridUnitType.Star) },
|
||||||
|
new RowDefinition { Height = new GridLength(120) }
|
||||||
|
},
|
||||||
|
Children =
|
||||||
|
{
|
||||||
|
CreateMainContent(),
|
||||||
|
CreateEventLogPanel()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Grid.SetRow((View)((Grid)Content).Children[0], 0);
|
||||||
|
Grid.SetRow((View)((Grid)Content).Children[1], 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
private View CreateMainContent()
|
||||||
|
{
|
||||||
|
return new ScrollView
|
||||||
|
{
|
||||||
|
Content = new VerticalStackLayout
|
||||||
|
{
|
||||||
|
Padding = new Thickness(20),
|
||||||
|
Spacing = 20,
|
||||||
|
Children =
|
||||||
|
{
|
||||||
|
new Label { Text = "Button Styles & Events", FontSize = 24, FontAttributes = FontAttributes.Bold },
|
||||||
|
|
||||||
|
// Basic Buttons
|
||||||
|
CreateSection("Basic Buttons", CreateBasicButtons()),
|
||||||
|
|
||||||
|
// Styled Buttons
|
||||||
|
CreateSection("Styled Buttons", CreateStyledButtons()),
|
||||||
|
|
||||||
|
// Button States
|
||||||
|
CreateSection("Button States", CreateButtonStates()),
|
||||||
|
|
||||||
|
// Button with Icons (text simulation)
|
||||||
|
CreateSection("Button Variations", CreateButtonVariations())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private View CreateBasicButtons()
|
||||||
|
{
|
||||||
|
var layout = new VerticalStackLayout { Spacing = 10 };
|
||||||
|
|
||||||
|
var defaultBtn = new Button { Text = "Default Button" };
|
||||||
|
defaultBtn.Clicked += (s, e) => LogEvent("Default Button clicked");
|
||||||
|
defaultBtn.Pressed += (s, e) => LogEvent("Default Button pressed");
|
||||||
|
defaultBtn.Released += (s, e) => LogEvent("Default Button released");
|
||||||
|
|
||||||
|
var textBtn = new Button { Text = "Text Only", BackgroundColor = Colors.Transparent, TextColor = Colors.Blue };
|
||||||
|
textBtn.Clicked += (s, e) => LogEvent("Text Button clicked");
|
||||||
|
|
||||||
|
layout.Children.Add(defaultBtn);
|
||||||
|
layout.Children.Add(textBtn);
|
||||||
|
|
||||||
|
return layout;
|
||||||
|
}
|
||||||
|
|
||||||
|
private View CreateStyledButtons()
|
||||||
|
{
|
||||||
|
var layout = new HorizontalStackLayout { Spacing = 10 };
|
||||||
|
|
||||||
|
var colors = new[]
|
||||||
|
{
|
||||||
|
("#2196F3", "Primary"),
|
||||||
|
("#4CAF50", "Success"),
|
||||||
|
("#FF9800", "Warning"),
|
||||||
|
("#F44336", "Danger"),
|
||||||
|
("#9C27B0", "Purple")
|
||||||
|
};
|
||||||
|
|
||||||
|
foreach (var (color, name) in colors)
|
||||||
|
{
|
||||||
|
var btn = new Button
|
||||||
|
{
|
||||||
|
Text = name,
|
||||||
|
BackgroundColor = Color.FromArgb(color),
|
||||||
|
TextColor = Colors.White,
|
||||||
|
CornerRadius = 5
|
||||||
|
};
|
||||||
|
btn.Clicked += (s, e) => LogEvent($"{name} button clicked");
|
||||||
|
layout.Children.Add(btn);
|
||||||
|
}
|
||||||
|
|
||||||
|
return layout;
|
||||||
|
}
|
||||||
|
|
||||||
|
private View CreateButtonStates()
|
||||||
|
{
|
||||||
|
var layout = new VerticalStackLayout { Spacing = 10 };
|
||||||
|
|
||||||
|
var enabledBtn = new Button { Text = "Enabled Button", IsEnabled = true };
|
||||||
|
enabledBtn.Clicked += (s, e) => LogEvent("Enabled button clicked");
|
||||||
|
|
||||||
|
var disabledBtn = new Button { Text = "Disabled Button", IsEnabled = false };
|
||||||
|
|
||||||
|
var toggleBtn = new Button { Text = "Toggle Above Button" };
|
||||||
|
toggleBtn.Clicked += (s, e) =>
|
||||||
|
{
|
||||||
|
disabledBtn.IsEnabled = !disabledBtn.IsEnabled;
|
||||||
|
disabledBtn.Text = disabledBtn.IsEnabled ? "Now Enabled!" : "Disabled Button";
|
||||||
|
LogEvent($"Toggled button to: {(disabledBtn.IsEnabled ? "Enabled" : "Disabled")}");
|
||||||
|
};
|
||||||
|
|
||||||
|
layout.Children.Add(enabledBtn);
|
||||||
|
layout.Children.Add(disabledBtn);
|
||||||
|
layout.Children.Add(toggleBtn);
|
||||||
|
|
||||||
|
return layout;
|
||||||
|
}
|
||||||
|
|
||||||
|
private View CreateButtonVariations()
|
||||||
|
{
|
||||||
|
var layout = new VerticalStackLayout { Spacing = 10 };
|
||||||
|
|
||||||
|
var wideBtn = new Button
|
||||||
|
{
|
||||||
|
Text = "Wide Button",
|
||||||
|
HorizontalOptions = LayoutOptions.Fill,
|
||||||
|
BackgroundColor = Color.FromArgb("#673AB7"),
|
||||||
|
TextColor = Colors.White
|
||||||
|
};
|
||||||
|
wideBtn.Clicked += (s, e) => LogEvent("Wide button clicked");
|
||||||
|
|
||||||
|
var tallBtn = new Button
|
||||||
|
{
|
||||||
|
Text = "Tall Button",
|
||||||
|
HeightRequest = 60,
|
||||||
|
BackgroundColor = Color.FromArgb("#009688"),
|
||||||
|
TextColor = Colors.White
|
||||||
|
};
|
||||||
|
tallBtn.Clicked += (s, e) => LogEvent("Tall button clicked");
|
||||||
|
|
||||||
|
var roundBtn = new Button
|
||||||
|
{
|
||||||
|
Text = "Round",
|
||||||
|
WidthRequest = 80,
|
||||||
|
HeightRequest = 80,
|
||||||
|
CornerRadius = 40,
|
||||||
|
BackgroundColor = Color.FromArgb("#E91E63"),
|
||||||
|
TextColor = Colors.White
|
||||||
|
};
|
||||||
|
roundBtn.Clicked += (s, e) => LogEvent("Round button clicked");
|
||||||
|
|
||||||
|
layout.Children.Add(wideBtn);
|
||||||
|
layout.Children.Add(tallBtn);
|
||||||
|
layout.Children.Add(new HorizontalStackLayout { Children = { roundBtn } });
|
||||||
|
|
||||||
|
return layout;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Frame CreateSection(string title, View content)
|
||||||
|
{
|
||||||
|
return new Frame
|
||||||
|
{
|
||||||
|
CornerRadius = 8,
|
||||||
|
Padding = new Thickness(15),
|
||||||
|
BackgroundColor = Colors.White,
|
||||||
|
Content = new VerticalStackLayout
|
||||||
|
{
|
||||||
|
Spacing = 10,
|
||||||
|
Children =
|
||||||
|
{
|
||||||
|
new Label { Text = title, FontSize = 16, FontAttributes = FontAttributes.Bold },
|
||||||
|
content
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private View CreateEventLogPanel()
|
||||||
|
{
|
||||||
|
return new Frame
|
||||||
|
{
|
||||||
|
BackgroundColor = Color.FromArgb("#F5F5F5"),
|
||||||
|
Padding = new Thickness(10),
|
||||||
|
CornerRadius = 0,
|
||||||
|
Content = new VerticalStackLayout
|
||||||
|
{
|
||||||
|
Children =
|
||||||
|
{
|
||||||
|
new Label { Text = "Event Log:", FontSize = 12, FontAttributes = FontAttributes.Bold },
|
||||||
|
new ScrollView
|
||||||
|
{
|
||||||
|
HeightRequest = 80,
|
||||||
|
Content = _eventLog
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private void LogEvent(string message)
|
||||||
|
{
|
||||||
|
_eventCount++;
|
||||||
|
var timestamp = DateTime.Now.ToString("HH:mm:ss");
|
||||||
|
_eventLog.Text = $"[{timestamp}] {_eventCount}. {message}\n{_eventLog.Text}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,203 @@
|
||||||
|
// ControlsPage - Demonstrates various MAUI controls
|
||||||
|
|
||||||
|
using Microsoft.Maui.Controls;
|
||||||
|
using Microsoft.Maui.Graphics;
|
||||||
|
|
||||||
|
namespace ShellDemo;
|
||||||
|
|
||||||
|
public class ControlsPage : ContentPage
|
||||||
|
{
|
||||||
|
public ControlsPage()
|
||||||
|
{
|
||||||
|
Title = "Controls";
|
||||||
|
|
||||||
|
Content = new ScrollView
|
||||||
|
{
|
||||||
|
Content = new VerticalStackLayout
|
||||||
|
{
|
||||||
|
Padding = new Thickness(20),
|
||||||
|
Spacing = 15,
|
||||||
|
Children =
|
||||||
|
{
|
||||||
|
new Label
|
||||||
|
{
|
||||||
|
Text = "Control Gallery",
|
||||||
|
FontSize = 24,
|
||||||
|
FontAttributes = FontAttributes.Bold
|
||||||
|
},
|
||||||
|
|
||||||
|
// Buttons
|
||||||
|
CreateSection("Buttons", new View[]
|
||||||
|
{
|
||||||
|
CreateButtonRow()
|
||||||
|
}),
|
||||||
|
|
||||||
|
// CheckBox & Switch
|
||||||
|
CreateSection("Selection", new View[]
|
||||||
|
{
|
||||||
|
CreateCheckBoxRow(),
|
||||||
|
CreateSwitchRow()
|
||||||
|
}),
|
||||||
|
|
||||||
|
// Slider
|
||||||
|
CreateSection("Slider", new View[]
|
||||||
|
{
|
||||||
|
CreateSliderRow()
|
||||||
|
}),
|
||||||
|
|
||||||
|
// Picker
|
||||||
|
CreateSection("Picker", new View[]
|
||||||
|
{
|
||||||
|
CreatePickerRow()
|
||||||
|
}),
|
||||||
|
|
||||||
|
// Progress
|
||||||
|
CreateSection("Progress", new View[]
|
||||||
|
{
|
||||||
|
CreateProgressRow()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private Frame CreateSection(string title, View[] content)
|
||||||
|
{
|
||||||
|
var layout = new VerticalStackLayout { Spacing = 10 };
|
||||||
|
layout.Children.Add(new Label
|
||||||
|
{
|
||||||
|
Text = title,
|
||||||
|
FontSize = 18,
|
||||||
|
FontAttributes = FontAttributes.Bold
|
||||||
|
});
|
||||||
|
|
||||||
|
foreach (var view in content)
|
||||||
|
{
|
||||||
|
layout.Children.Add(view);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Frame
|
||||||
|
{
|
||||||
|
CornerRadius = 8,
|
||||||
|
Padding = new Thickness(15),
|
||||||
|
BackgroundColor = Colors.White,
|
||||||
|
Content = layout
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private View CreateButtonRow()
|
||||||
|
{
|
||||||
|
var resultLabel = new Label { TextColor = Colors.Gray, FontSize = 12 };
|
||||||
|
|
||||||
|
var layout = new VerticalStackLayout { Spacing = 10 };
|
||||||
|
|
||||||
|
var buttonRow = new HorizontalStackLayout { Spacing = 10 };
|
||||||
|
|
||||||
|
var primaryBtn = new Button { Text = "Primary", BackgroundColor = Color.FromArgb("#2196F3"), TextColor = Colors.White };
|
||||||
|
primaryBtn.Clicked += (s, e) => resultLabel.Text = "Primary clicked!";
|
||||||
|
|
||||||
|
var successBtn = new Button { Text = "Success", BackgroundColor = Color.FromArgb("#4CAF50"), TextColor = Colors.White };
|
||||||
|
successBtn.Clicked += (s, e) => resultLabel.Text = "Success clicked!";
|
||||||
|
|
||||||
|
var dangerBtn = new Button { Text = "Danger", BackgroundColor = Color.FromArgb("#F44336"), TextColor = Colors.White };
|
||||||
|
dangerBtn.Clicked += (s, e) => resultLabel.Text = "Danger clicked!";
|
||||||
|
|
||||||
|
buttonRow.Children.Add(primaryBtn);
|
||||||
|
buttonRow.Children.Add(successBtn);
|
||||||
|
buttonRow.Children.Add(dangerBtn);
|
||||||
|
|
||||||
|
layout.Children.Add(buttonRow);
|
||||||
|
layout.Children.Add(resultLabel);
|
||||||
|
|
||||||
|
return layout;
|
||||||
|
}
|
||||||
|
|
||||||
|
private View CreateCheckBoxRow()
|
||||||
|
{
|
||||||
|
var layout = new HorizontalStackLayout { Spacing = 20 };
|
||||||
|
|
||||||
|
var cb1 = new CheckBox { IsChecked = true };
|
||||||
|
var cb2 = new CheckBox { IsChecked = false };
|
||||||
|
|
||||||
|
layout.Children.Add(cb1);
|
||||||
|
layout.Children.Add(new Label { Text = "Option 1", VerticalOptions = LayoutOptions.Center });
|
||||||
|
layout.Children.Add(cb2);
|
||||||
|
layout.Children.Add(new Label { Text = "Option 2", VerticalOptions = LayoutOptions.Center });
|
||||||
|
|
||||||
|
return layout;
|
||||||
|
}
|
||||||
|
|
||||||
|
private View CreateSwitchRow()
|
||||||
|
{
|
||||||
|
var label = new Label { Text = "Off", VerticalOptions = LayoutOptions.Center };
|
||||||
|
var sw = new Switch { IsToggled = false };
|
||||||
|
sw.Toggled += (s, e) => label.Text = e.Value ? "On" : "Off";
|
||||||
|
|
||||||
|
return new HorizontalStackLayout
|
||||||
|
{
|
||||||
|
Spacing = 10,
|
||||||
|
Children = { sw, label }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private View CreateSliderRow()
|
||||||
|
{
|
||||||
|
var label = new Label { Text = "Value: 50" };
|
||||||
|
var slider = new Slider { Minimum = 0, Maximum = 100, Value = 50 };
|
||||||
|
slider.ValueChanged += (s, e) => label.Text = $"Value: {(int)e.NewValue}";
|
||||||
|
|
||||||
|
return new VerticalStackLayout
|
||||||
|
{
|
||||||
|
Spacing = 5,
|
||||||
|
Children = { slider, label }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private View CreatePickerRow()
|
||||||
|
{
|
||||||
|
var label = new Label { Text = "Selected: (none)", TextColor = Colors.Gray };
|
||||||
|
var picker = new Picker { Title = "Select a fruit" };
|
||||||
|
picker.Items.Add("Apple");
|
||||||
|
picker.Items.Add("Banana");
|
||||||
|
picker.Items.Add("Cherry");
|
||||||
|
picker.Items.Add("Date");
|
||||||
|
picker.Items.Add("Elderberry");
|
||||||
|
|
||||||
|
picker.SelectedIndexChanged += (s, e) =>
|
||||||
|
{
|
||||||
|
if (picker.SelectedIndex >= 0)
|
||||||
|
label.Text = $"Selected: {picker.Items[picker.SelectedIndex]}";
|
||||||
|
};
|
||||||
|
|
||||||
|
return new VerticalStackLayout
|
||||||
|
{
|
||||||
|
Spacing = 5,
|
||||||
|
Children = { picker, label }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private View CreateProgressRow()
|
||||||
|
{
|
||||||
|
var progress = new ProgressBar { Progress = 0.7 };
|
||||||
|
var activity = new ActivityIndicator { IsRunning = true };
|
||||||
|
|
||||||
|
return new VerticalStackLayout
|
||||||
|
{
|
||||||
|
Spacing = 10,
|
||||||
|
Children =
|
||||||
|
{
|
||||||
|
progress,
|
||||||
|
new Label { Text = "70% Complete", FontSize = 12, TextColor = Colors.Gray },
|
||||||
|
new HorizontalStackLayout
|
||||||
|
{
|
||||||
|
Spacing = 10,
|
||||||
|
Children =
|
||||||
|
{
|
||||||
|
activity,
|
||||||
|
new Label { Text = "Loading...", VerticalOptions = LayoutOptions.Center, TextColor = Colors.Gray }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,123 @@
|
||||||
|
// DetailPage - Demonstrates push/pop navigation
|
||||||
|
|
||||||
|
using Microsoft.Maui.Controls;
|
||||||
|
using Microsoft.Maui.Graphics;
|
||||||
|
using Microsoft.Maui.Platform.Linux.Hosting;
|
||||||
|
|
||||||
|
namespace ShellDemo;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A detail page that can be pushed onto the navigation stack.
|
||||||
|
/// </summary>
|
||||||
|
public class DetailPage : ContentPage
|
||||||
|
{
|
||||||
|
private readonly string _itemName;
|
||||||
|
|
||||||
|
public DetailPage() : this("Detail Item")
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public DetailPage(string itemName)
|
||||||
|
{
|
||||||
|
_itemName = itemName;
|
||||||
|
Title = "Detail Page";
|
||||||
|
|
||||||
|
Content = new VerticalStackLayout
|
||||||
|
{
|
||||||
|
Padding = new Thickness(30),
|
||||||
|
Spacing = 20,
|
||||||
|
VerticalOptions = LayoutOptions.Center,
|
||||||
|
Children =
|
||||||
|
{
|
||||||
|
new Label
|
||||||
|
{
|
||||||
|
Text = "Pushed Page",
|
||||||
|
FontSize = 28,
|
||||||
|
FontAttributes = FontAttributes.Bold,
|
||||||
|
HorizontalOptions = LayoutOptions.Center,
|
||||||
|
TextColor = Color.FromArgb("#9C27B0")
|
||||||
|
},
|
||||||
|
|
||||||
|
new Label
|
||||||
|
{
|
||||||
|
Text = $"You navigated to: {_itemName}",
|
||||||
|
FontSize = 16,
|
||||||
|
HorizontalOptions = LayoutOptions.Center
|
||||||
|
},
|
||||||
|
|
||||||
|
new Label
|
||||||
|
{
|
||||||
|
Text = "This page was pushed onto the navigation stack using Shell.Current.GoToAsync()",
|
||||||
|
FontSize = 14,
|
||||||
|
TextColor = Colors.Gray,
|
||||||
|
HorizontalTextAlignment = TextAlignment.Center,
|
||||||
|
LineBreakMode = LineBreakMode.WordWrap
|
||||||
|
},
|
||||||
|
|
||||||
|
new BoxView
|
||||||
|
{
|
||||||
|
HeightRequest = 2,
|
||||||
|
Color = Color.FromArgb("#E0E0E0"),
|
||||||
|
Margin = new Thickness(0, 20)
|
||||||
|
},
|
||||||
|
|
||||||
|
CreateBackButton(),
|
||||||
|
|
||||||
|
new Label
|
||||||
|
{
|
||||||
|
Text = "Use the back button above or the hardware/gesture back to pop this page",
|
||||||
|
FontSize = 12,
|
||||||
|
TextColor = Colors.Gray,
|
||||||
|
HorizontalTextAlignment = TextAlignment.Center,
|
||||||
|
Margin = new Thickness(0, 20, 0, 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private Button CreateBackButton()
|
||||||
|
{
|
||||||
|
var backBtn = new Button
|
||||||
|
{
|
||||||
|
Text = "Go Back (Pop)",
|
||||||
|
BackgroundColor = Color.FromArgb("#9C27B0"),
|
||||||
|
TextColor = Colors.White,
|
||||||
|
HorizontalOptions = LayoutOptions.Center,
|
||||||
|
Padding = new Thickness(30, 10)
|
||||||
|
};
|
||||||
|
|
||||||
|
backBtn.Clicked += (s, e) =>
|
||||||
|
{
|
||||||
|
// Pop this page off the navigation stack using LinuxViewRenderer
|
||||||
|
Console.WriteLine("[DetailPage] Go Back clicked");
|
||||||
|
var success = LinuxViewRenderer.PopPage();
|
||||||
|
Console.WriteLine($"[DetailPage] PopPage result: {success}");
|
||||||
|
};
|
||||||
|
|
||||||
|
return backBtn;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Query property for passing data to DetailPage.
|
||||||
|
/// </summary>
|
||||||
|
[QueryProperty(nameof(ItemName), "item")]
|
||||||
|
public class DetailPageWithQuery : DetailPage
|
||||||
|
{
|
||||||
|
private string _itemName = "Item";
|
||||||
|
|
||||||
|
public string ItemName
|
||||||
|
{
|
||||||
|
get => _itemName;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_itemName = value;
|
||||||
|
// Update the title when the property is set
|
||||||
|
Title = $"Detail: {value}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public DetailPageWithQuery() : base()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,594 @@
|
||||||
|
// GridsPage - Demonstrates Grid layouts with various options
|
||||||
|
|
||||||
|
using Microsoft.Maui.Controls;
|
||||||
|
using Microsoft.Maui.Graphics;
|
||||||
|
|
||||||
|
namespace ShellDemo;
|
||||||
|
|
||||||
|
public class GridsPage : ContentPage
|
||||||
|
{
|
||||||
|
public GridsPage()
|
||||||
|
{
|
||||||
|
Title = "Grids";
|
||||||
|
|
||||||
|
Content = new ScrollView
|
||||||
|
{
|
||||||
|
Orientation = ScrollOrientation.Both,
|
||||||
|
Content = new VerticalStackLayout
|
||||||
|
{
|
||||||
|
Spacing = 25,
|
||||||
|
Children =
|
||||||
|
{
|
||||||
|
CreateSectionHeader("Basic Grid (2x2)"),
|
||||||
|
CreateBasicGrid(),
|
||||||
|
|
||||||
|
CreateSectionHeader("Column Definitions"),
|
||||||
|
CreateColumnDefinitionsDemo(),
|
||||||
|
|
||||||
|
CreateSectionHeader("Row Definitions"),
|
||||||
|
CreateRowDefinitionsDemo(),
|
||||||
|
|
||||||
|
CreateSectionHeader("Auto Rows (Empty vs Content)"),
|
||||||
|
CreateAutoRowsDemo(),
|
||||||
|
|
||||||
|
CreateSectionHeader("Star Sizing (Proportional)"),
|
||||||
|
CreateStarSizingDemo(),
|
||||||
|
|
||||||
|
CreateSectionHeader("Row & Column Spacing"),
|
||||||
|
CreateSpacingDemo(),
|
||||||
|
|
||||||
|
CreateSectionHeader("Row & Column Span"),
|
||||||
|
CreateSpanDemo(),
|
||||||
|
|
||||||
|
CreateSectionHeader("Mixed Sizing"),
|
||||||
|
CreateMixedSizingDemo(),
|
||||||
|
|
||||||
|
CreateSectionHeader("Nested Grids"),
|
||||||
|
CreateNestedGridDemo(),
|
||||||
|
|
||||||
|
new BoxView { HeightRequest = 20 } // Bottom padding
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private Label CreateSectionHeader(string text)
|
||||||
|
{
|
||||||
|
return new Label
|
||||||
|
{
|
||||||
|
Text = text,
|
||||||
|
FontSize = 18,
|
||||||
|
FontAttributes = FontAttributes.Bold,
|
||||||
|
TextColor = Color.FromArgb("#2196F3"),
|
||||||
|
Margin = new Thickness(0, 10, 0, 5)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private View CreateBasicGrid()
|
||||||
|
{
|
||||||
|
var grid = new Grid
|
||||||
|
{
|
||||||
|
RowDefinitions =
|
||||||
|
{
|
||||||
|
new RowDefinition { Height = GridLength.Auto },
|
||||||
|
new RowDefinition { Height = GridLength.Auto }
|
||||||
|
},
|
||||||
|
ColumnDefinitions =
|
||||||
|
{
|
||||||
|
new ColumnDefinition { Width = GridLength.Star },
|
||||||
|
new ColumnDefinition { Width = GridLength.Star }
|
||||||
|
},
|
||||||
|
BackgroundColor = Color.FromArgb("#F5F5F5")
|
||||||
|
};
|
||||||
|
|
||||||
|
var cell1 = CreateCell("Row 0, Col 0", "#E3F2FD");
|
||||||
|
var cell2 = CreateCell("Row 0, Col 1", "#E8F5E9");
|
||||||
|
var cell3 = CreateCell("Row 1, Col 0", "#FFF3E0");
|
||||||
|
var cell4 = CreateCell("Row 1, Col 1", "#FCE4EC");
|
||||||
|
|
||||||
|
Grid.SetRow(cell1, 0); Grid.SetColumn(cell1, 0);
|
||||||
|
Grid.SetRow(cell2, 0); Grid.SetColumn(cell2, 1);
|
||||||
|
Grid.SetRow(cell3, 1); Grid.SetColumn(cell3, 0);
|
||||||
|
Grid.SetRow(cell4, 1); Grid.SetColumn(cell4, 1);
|
||||||
|
|
||||||
|
grid.Children.Add(cell1);
|
||||||
|
grid.Children.Add(cell2);
|
||||||
|
grid.Children.Add(cell3);
|
||||||
|
grid.Children.Add(cell4);
|
||||||
|
|
||||||
|
return CreateDemoContainer(grid, "Equal columns using Star sizing");
|
||||||
|
}
|
||||||
|
|
||||||
|
private View CreateColumnDefinitionsDemo()
|
||||||
|
{
|
||||||
|
var stack = new VerticalStackLayout { Spacing = 15 };
|
||||||
|
|
||||||
|
// Auto width columns
|
||||||
|
var autoGrid = new Grid
|
||||||
|
{
|
||||||
|
ColumnDefinitions =
|
||||||
|
{
|
||||||
|
new ColumnDefinition { Width = GridLength.Auto },
|
||||||
|
new ColumnDefinition { Width = GridLength.Auto },
|
||||||
|
new ColumnDefinition { Width = GridLength.Auto }
|
||||||
|
},
|
||||||
|
BackgroundColor = Color.FromArgb("#F5F5F5")
|
||||||
|
};
|
||||||
|
|
||||||
|
var a1 = CreateCell("Auto", "#BBDEFB");
|
||||||
|
var a2 = CreateCell("Auto Width", "#C8E6C9");
|
||||||
|
var a3 = CreateCell("A", "#FFECB3");
|
||||||
|
Grid.SetColumn(a1, 0);
|
||||||
|
Grid.SetColumn(a2, 1);
|
||||||
|
Grid.SetColumn(a3, 2);
|
||||||
|
autoGrid.Children.Add(a1);
|
||||||
|
autoGrid.Children.Add(a2);
|
||||||
|
autoGrid.Children.Add(a3);
|
||||||
|
|
||||||
|
stack.Children.Add(new Label { Text = "Auto: Sizes to content", FontSize = 12, TextColor = Colors.Gray });
|
||||||
|
stack.Children.Add(autoGrid);
|
||||||
|
|
||||||
|
// Absolute width columns
|
||||||
|
var absoluteGrid = new Grid
|
||||||
|
{
|
||||||
|
ColumnDefinitions =
|
||||||
|
{
|
||||||
|
new ColumnDefinition { Width = new GridLength(50) },
|
||||||
|
new ColumnDefinition { Width = new GridLength(100) },
|
||||||
|
new ColumnDefinition { Width = new GridLength(150) }
|
||||||
|
},
|
||||||
|
BackgroundColor = Color.FromArgb("#F5F5F5")
|
||||||
|
};
|
||||||
|
|
||||||
|
var b1 = CreateCell("50px", "#BBDEFB");
|
||||||
|
var b2 = CreateCell("100px", "#C8E6C9");
|
||||||
|
var b3 = CreateCell("150px", "#FFECB3");
|
||||||
|
Grid.SetColumn(b1, 0);
|
||||||
|
Grid.SetColumn(b2, 1);
|
||||||
|
Grid.SetColumn(b3, 2);
|
||||||
|
absoluteGrid.Children.Add(b1);
|
||||||
|
absoluteGrid.Children.Add(b2);
|
||||||
|
absoluteGrid.Children.Add(b3);
|
||||||
|
|
||||||
|
stack.Children.Add(new Label { Text = "Absolute: Fixed pixel widths (50, 100, 150)", FontSize = 12, TextColor = Colors.Gray, Margin = new Thickness(0, 10, 0, 0) });
|
||||||
|
stack.Children.Add(absoluteGrid);
|
||||||
|
|
||||||
|
return stack;
|
||||||
|
}
|
||||||
|
|
||||||
|
private View CreateRowDefinitionsDemo()
|
||||||
|
{
|
||||||
|
var grid = new Grid
|
||||||
|
{
|
||||||
|
WidthRequest = 200,
|
||||||
|
RowDefinitions =
|
||||||
|
{
|
||||||
|
new RowDefinition { Height = new GridLength(30) },
|
||||||
|
new RowDefinition { Height = new GridLength(50) },
|
||||||
|
new RowDefinition { Height = GridLength.Auto },
|
||||||
|
new RowDefinition { Height = new GridLength(40) }
|
||||||
|
},
|
||||||
|
ColumnDefinitions =
|
||||||
|
{
|
||||||
|
new ColumnDefinition { Width = GridLength.Star }
|
||||||
|
},
|
||||||
|
BackgroundColor = Color.FromArgb("#F5F5F5")
|
||||||
|
};
|
||||||
|
|
||||||
|
var r1 = CreateCell("30px height", "#BBDEFB");
|
||||||
|
var r2 = CreateCell("50px height", "#C8E6C9");
|
||||||
|
var r3 = CreateCell("Auto height\n(fits content)", "#FFECB3");
|
||||||
|
var r4 = CreateCell("40px height", "#F8BBD9");
|
||||||
|
|
||||||
|
Grid.SetRow(r1, 0);
|
||||||
|
Grid.SetRow(r2, 1);
|
||||||
|
Grid.SetRow(r3, 2);
|
||||||
|
Grid.SetRow(r4, 3);
|
||||||
|
|
||||||
|
grid.Children.Add(r1);
|
||||||
|
grid.Children.Add(r2);
|
||||||
|
grid.Children.Add(r3);
|
||||||
|
grid.Children.Add(r4);
|
||||||
|
|
||||||
|
return CreateDemoContainer(grid, "Different row heights: 30px, 50px, Auto, 40px");
|
||||||
|
}
|
||||||
|
|
||||||
|
private View CreateAutoRowsDemo()
|
||||||
|
{
|
||||||
|
var stack = new VerticalStackLayout { Spacing = 15 };
|
||||||
|
|
||||||
|
// Grid with empty Auto row
|
||||||
|
var emptyAutoGrid = new Grid
|
||||||
|
{
|
||||||
|
WidthRequest = 250,
|
||||||
|
RowDefinitions =
|
||||||
|
{
|
||||||
|
new RowDefinition { Height = new GridLength(40) },
|
||||||
|
new RowDefinition { Height = GridLength.Auto }, // Empty - should collapse
|
||||||
|
new RowDefinition { Height = new GridLength(40) }
|
||||||
|
},
|
||||||
|
ColumnDefinitions =
|
||||||
|
{
|
||||||
|
new ColumnDefinition { Width = GridLength.Star }
|
||||||
|
},
|
||||||
|
BackgroundColor = Color.FromArgb("#E0E0E0")
|
||||||
|
};
|
||||||
|
|
||||||
|
var r1 = CreateCell("Row 0: 40px", "#BBDEFB");
|
||||||
|
// Row 1 is Auto with NO content - should be 0 height
|
||||||
|
var r3 = CreateCell("Row 2: 40px", "#C8E6C9");
|
||||||
|
|
||||||
|
Grid.SetRow(r1, 0);
|
||||||
|
Grid.SetRow(r3, 2); // Skip row 1
|
||||||
|
|
||||||
|
emptyAutoGrid.Children.Add(r1);
|
||||||
|
emptyAutoGrid.Children.Add(r3);
|
||||||
|
|
||||||
|
stack.Children.Add(new Label { Text = "Empty Auto row (Row 1) should collapse to 0 height:", FontSize = 12, TextColor = Colors.Gray });
|
||||||
|
stack.Children.Add(emptyAutoGrid);
|
||||||
|
|
||||||
|
// Grid with Auto row that has content
|
||||||
|
var contentAutoGrid = new Grid
|
||||||
|
{
|
||||||
|
WidthRequest = 250,
|
||||||
|
RowDefinitions =
|
||||||
|
{
|
||||||
|
new RowDefinition { Height = new GridLength(40) },
|
||||||
|
new RowDefinition { Height = GridLength.Auto }, // Has content
|
||||||
|
new RowDefinition { Height = new GridLength(40) }
|
||||||
|
},
|
||||||
|
ColumnDefinitions =
|
||||||
|
{
|
||||||
|
new ColumnDefinition { Width = GridLength.Star }
|
||||||
|
},
|
||||||
|
BackgroundColor = Color.FromArgb("#E0E0E0")
|
||||||
|
};
|
||||||
|
|
||||||
|
var c1 = CreateCell("Row 0: 40px", "#BBDEFB");
|
||||||
|
var c2 = CreateCell("Row 1: Auto (sized to this content)", "#FFECB3");
|
||||||
|
var c3 = CreateCell("Row 2: 40px", "#C8E6C9");
|
||||||
|
|
||||||
|
Grid.SetRow(c1, 0);
|
||||||
|
Grid.SetRow(c2, 1);
|
||||||
|
Grid.SetRow(c3, 2);
|
||||||
|
|
||||||
|
contentAutoGrid.Children.Add(c1);
|
||||||
|
contentAutoGrid.Children.Add(c2);
|
||||||
|
contentAutoGrid.Children.Add(c3);
|
||||||
|
|
||||||
|
stack.Children.Add(new Label { Text = "Auto row with content sizes to fit:", FontSize = 12, TextColor = Colors.Gray, Margin = new Thickness(0, 10, 0, 0) });
|
||||||
|
stack.Children.Add(contentAutoGrid);
|
||||||
|
|
||||||
|
return stack;
|
||||||
|
}
|
||||||
|
|
||||||
|
private View CreateStarSizingDemo()
|
||||||
|
{
|
||||||
|
var grid = new Grid
|
||||||
|
{
|
||||||
|
ColumnDefinitions =
|
||||||
|
{
|
||||||
|
new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) },
|
||||||
|
new ColumnDefinition { Width = new GridLength(2, GridUnitType.Star) },
|
||||||
|
new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) }
|
||||||
|
},
|
||||||
|
BackgroundColor = Color.FromArgb("#F5F5F5")
|
||||||
|
};
|
||||||
|
|
||||||
|
var s1 = CreateCell("1*", "#BBDEFB");
|
||||||
|
var s2 = CreateCell("2* (double)", "#C8E6C9");
|
||||||
|
var s3 = CreateCell("1*", "#FFECB3");
|
||||||
|
|
||||||
|
Grid.SetColumn(s1, 0);
|
||||||
|
Grid.SetColumn(s2, 1);
|
||||||
|
Grid.SetColumn(s3, 2);
|
||||||
|
|
||||||
|
grid.Children.Add(s1);
|
||||||
|
grid.Children.Add(s2);
|
||||||
|
grid.Children.Add(s3);
|
||||||
|
|
||||||
|
return CreateDemoContainer(grid, "Star proportions: 1* | 2* | 1* = 25% | 50% | 25%");
|
||||||
|
}
|
||||||
|
|
||||||
|
private View CreateSpacingDemo()
|
||||||
|
{
|
||||||
|
var stack = new VerticalStackLayout { Spacing = 15 };
|
||||||
|
|
||||||
|
// No spacing
|
||||||
|
var noSpacing = new Grid
|
||||||
|
{
|
||||||
|
RowSpacing = 0,
|
||||||
|
ColumnSpacing = 0,
|
||||||
|
RowDefinitions =
|
||||||
|
{
|
||||||
|
new RowDefinition { Height = GridLength.Auto },
|
||||||
|
new RowDefinition { Height = GridLength.Auto }
|
||||||
|
},
|
||||||
|
ColumnDefinitions =
|
||||||
|
{
|
||||||
|
new ColumnDefinition { Width = GridLength.Star },
|
||||||
|
new ColumnDefinition { Width = GridLength.Star }
|
||||||
|
}
|
||||||
|
};
|
||||||
|
AddFourCells(noSpacing);
|
||||||
|
stack.Children.Add(new Label { Text = "No spacing (RowSpacing=0, ColumnSpacing=0)", FontSize = 12, TextColor = Colors.Gray });
|
||||||
|
stack.Children.Add(noSpacing);
|
||||||
|
|
||||||
|
// With spacing
|
||||||
|
var withSpacing = new Grid
|
||||||
|
{
|
||||||
|
RowSpacing = 10,
|
||||||
|
ColumnSpacing = 10,
|
||||||
|
RowDefinitions =
|
||||||
|
{
|
||||||
|
new RowDefinition { Height = GridLength.Auto },
|
||||||
|
new RowDefinition { Height = GridLength.Auto }
|
||||||
|
},
|
||||||
|
ColumnDefinitions =
|
||||||
|
{
|
||||||
|
new ColumnDefinition { Width = GridLength.Star },
|
||||||
|
new ColumnDefinition { Width = GridLength.Star }
|
||||||
|
}
|
||||||
|
};
|
||||||
|
AddFourCells(withSpacing);
|
||||||
|
stack.Children.Add(new Label { Text = "With spacing (RowSpacing=10, ColumnSpacing=10)", FontSize = 12, TextColor = Colors.Gray, Margin = new Thickness(0, 10, 0, 0) });
|
||||||
|
stack.Children.Add(withSpacing);
|
||||||
|
|
||||||
|
// Different row/column spacing
|
||||||
|
var mixedSpacing = new Grid
|
||||||
|
{
|
||||||
|
RowSpacing = 5,
|
||||||
|
ColumnSpacing = 20,
|
||||||
|
RowDefinitions =
|
||||||
|
{
|
||||||
|
new RowDefinition { Height = GridLength.Auto },
|
||||||
|
new RowDefinition { Height = GridLength.Auto }
|
||||||
|
},
|
||||||
|
ColumnDefinitions =
|
||||||
|
{
|
||||||
|
new ColumnDefinition { Width = GridLength.Star },
|
||||||
|
new ColumnDefinition { Width = GridLength.Star }
|
||||||
|
}
|
||||||
|
};
|
||||||
|
AddFourCells(mixedSpacing);
|
||||||
|
stack.Children.Add(new Label { Text = "Mixed spacing (RowSpacing=5, ColumnSpacing=20)", FontSize = 12, TextColor = Colors.Gray, Margin = new Thickness(0, 10, 0, 0) });
|
||||||
|
stack.Children.Add(mixedSpacing);
|
||||||
|
|
||||||
|
return stack;
|
||||||
|
}
|
||||||
|
|
||||||
|
private View CreateSpanDemo()
|
||||||
|
{
|
||||||
|
var grid = new Grid
|
||||||
|
{
|
||||||
|
RowSpacing = 5,
|
||||||
|
ColumnSpacing = 5,
|
||||||
|
RowDefinitions =
|
||||||
|
{
|
||||||
|
new RowDefinition { Height = GridLength.Auto },
|
||||||
|
new RowDefinition { Height = GridLength.Auto },
|
||||||
|
new RowDefinition { Height = GridLength.Auto }
|
||||||
|
},
|
||||||
|
ColumnDefinitions =
|
||||||
|
{
|
||||||
|
new ColumnDefinition { Width = GridLength.Star },
|
||||||
|
new ColumnDefinition { Width = GridLength.Star },
|
||||||
|
new ColumnDefinition { Width = GridLength.Star }
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Spanning header
|
||||||
|
var header = CreateCell("ColumnSpan=3 (Header)", "#1976D2", Colors.White);
|
||||||
|
Grid.SetRow(header, 0);
|
||||||
|
Grid.SetColumn(header, 0);
|
||||||
|
Grid.SetColumnSpan(header, 3);
|
||||||
|
|
||||||
|
// Left sidebar spanning 2 rows
|
||||||
|
var sidebar = CreateCell("RowSpan=2\n(Sidebar)", "#388E3C", Colors.White);
|
||||||
|
Grid.SetRow(sidebar, 1);
|
||||||
|
Grid.SetColumn(sidebar, 0);
|
||||||
|
Grid.SetRowSpan(sidebar, 2);
|
||||||
|
|
||||||
|
// Content cells
|
||||||
|
var content1 = CreateCell("Content 1", "#E3F2FD");
|
||||||
|
Grid.SetRow(content1, 1);
|
||||||
|
Grid.SetColumn(content1, 1);
|
||||||
|
|
||||||
|
var content2 = CreateCell("Content 2", "#E8F5E9");
|
||||||
|
Grid.SetRow(content2, 1);
|
||||||
|
Grid.SetColumn(content2, 2);
|
||||||
|
|
||||||
|
var content3 = CreateCell("Content 3", "#FFF3E0");
|
||||||
|
Grid.SetRow(content3, 2);
|
||||||
|
Grid.SetColumn(content3, 1);
|
||||||
|
|
||||||
|
var content4 = CreateCell("Content 4", "#FCE4EC");
|
||||||
|
Grid.SetRow(content4, 2);
|
||||||
|
Grid.SetColumn(content4, 2);
|
||||||
|
|
||||||
|
grid.Children.Add(header);
|
||||||
|
grid.Children.Add(sidebar);
|
||||||
|
grid.Children.Add(content1);
|
||||||
|
grid.Children.Add(content2);
|
||||||
|
grid.Children.Add(content3);
|
||||||
|
grid.Children.Add(content4);
|
||||||
|
|
||||||
|
return CreateDemoContainer(grid, "Header spans 3 columns, Sidebar spans 2 rows");
|
||||||
|
}
|
||||||
|
|
||||||
|
private View CreateMixedSizingDemo()
|
||||||
|
{
|
||||||
|
var grid = new Grid
|
||||||
|
{
|
||||||
|
ColumnSpacing = 5,
|
||||||
|
ColumnDefinitions =
|
||||||
|
{
|
||||||
|
new ColumnDefinition { Width = new GridLength(60) }, // Fixed
|
||||||
|
new ColumnDefinition { Width = GridLength.Star }, // Fill
|
||||||
|
new ColumnDefinition { Width = GridLength.Auto }, // Auto
|
||||||
|
new ColumnDefinition { Width = new GridLength(60) } // Fixed
|
||||||
|
},
|
||||||
|
BackgroundColor = Color.FromArgb("#F5F5F5")
|
||||||
|
};
|
||||||
|
|
||||||
|
var c1 = CreateCell("60px", "#BBDEFB");
|
||||||
|
var c2 = CreateCell("Star (fills remaining)", "#C8E6C9");
|
||||||
|
var c3 = CreateCell("Auto", "#FFECB3");
|
||||||
|
var c4 = CreateCell("60px", "#F8BBD9");
|
||||||
|
|
||||||
|
Grid.SetColumn(c1, 0);
|
||||||
|
Grid.SetColumn(c2, 1);
|
||||||
|
Grid.SetColumn(c3, 2);
|
||||||
|
Grid.SetColumn(c4, 3);
|
||||||
|
|
||||||
|
grid.Children.Add(c1);
|
||||||
|
grid.Children.Add(c2);
|
||||||
|
grid.Children.Add(c3);
|
||||||
|
grid.Children.Add(c4);
|
||||||
|
|
||||||
|
return CreateDemoContainer(grid, "Mixed: 60px | Star | Auto | 60px");
|
||||||
|
}
|
||||||
|
|
||||||
|
private View CreateNestedGridDemo()
|
||||||
|
{
|
||||||
|
var outerGrid = new Grid
|
||||||
|
{
|
||||||
|
RowSpacing = 10,
|
||||||
|
ColumnSpacing = 10,
|
||||||
|
RowDefinitions =
|
||||||
|
{
|
||||||
|
new RowDefinition { Height = GridLength.Auto },
|
||||||
|
new RowDefinition { Height = GridLength.Auto }
|
||||||
|
},
|
||||||
|
ColumnDefinitions =
|
||||||
|
{
|
||||||
|
new ColumnDefinition { Width = GridLength.Star },
|
||||||
|
new ColumnDefinition { Width = GridLength.Star }
|
||||||
|
},
|
||||||
|
BackgroundColor = Color.FromArgb("#E0E0E0"),
|
||||||
|
Padding = new Thickness(10)
|
||||||
|
};
|
||||||
|
|
||||||
|
// Nested grid 1
|
||||||
|
var innerGrid1 = new Grid
|
||||||
|
{
|
||||||
|
RowSpacing = 2,
|
||||||
|
ColumnSpacing = 2,
|
||||||
|
RowDefinitions =
|
||||||
|
{
|
||||||
|
new RowDefinition { Height = GridLength.Auto },
|
||||||
|
new RowDefinition { Height = GridLength.Auto }
|
||||||
|
},
|
||||||
|
ColumnDefinitions =
|
||||||
|
{
|
||||||
|
new ColumnDefinition { Width = GridLength.Star },
|
||||||
|
new ColumnDefinition { Width = GridLength.Star }
|
||||||
|
}
|
||||||
|
};
|
||||||
|
var i1a = CreateCell("A", "#BBDEFB", null, 8);
|
||||||
|
var i1b = CreateCell("B", "#90CAF9", null, 8);
|
||||||
|
var i1c = CreateCell("C", "#64B5F6", null, 8);
|
||||||
|
var i1d = CreateCell("D", "#42A5F5", null, 8);
|
||||||
|
Grid.SetRow(i1a, 0); Grid.SetColumn(i1a, 0);
|
||||||
|
Grid.SetRow(i1b, 0); Grid.SetColumn(i1b, 1);
|
||||||
|
Grid.SetRow(i1c, 1); Grid.SetColumn(i1c, 0);
|
||||||
|
Grid.SetRow(i1d, 1); Grid.SetColumn(i1d, 1);
|
||||||
|
innerGrid1.Children.Add(i1a);
|
||||||
|
innerGrid1.Children.Add(i1b);
|
||||||
|
innerGrid1.Children.Add(i1c);
|
||||||
|
innerGrid1.Children.Add(i1d);
|
||||||
|
|
||||||
|
// Nested grid 2
|
||||||
|
var innerGrid2 = new Grid
|
||||||
|
{
|
||||||
|
RowSpacing = 2,
|
||||||
|
ColumnSpacing = 2,
|
||||||
|
RowDefinitions =
|
||||||
|
{
|
||||||
|
new RowDefinition { Height = GridLength.Auto },
|
||||||
|
new RowDefinition { Height = GridLength.Auto }
|
||||||
|
},
|
||||||
|
ColumnDefinitions =
|
||||||
|
{
|
||||||
|
new ColumnDefinition { Width = GridLength.Star },
|
||||||
|
new ColumnDefinition { Width = GridLength.Star }
|
||||||
|
}
|
||||||
|
};
|
||||||
|
var i2a = CreateCell("1", "#C8E6C9", null, 8);
|
||||||
|
var i2b = CreateCell("2", "#A5D6A7", null, 8);
|
||||||
|
var i2c = CreateCell("3", "#81C784", null, 8);
|
||||||
|
var i2d = CreateCell("4", "#66BB6A", null, 8);
|
||||||
|
Grid.SetRow(i2a, 0); Grid.SetColumn(i2a, 0);
|
||||||
|
Grid.SetRow(i2b, 0); Grid.SetColumn(i2b, 1);
|
||||||
|
Grid.SetRow(i2c, 1); Grid.SetColumn(i2c, 0);
|
||||||
|
Grid.SetRow(i2d, 1); Grid.SetColumn(i2d, 1);
|
||||||
|
innerGrid2.Children.Add(i2a);
|
||||||
|
innerGrid2.Children.Add(i2b);
|
||||||
|
innerGrid2.Children.Add(i2c);
|
||||||
|
innerGrid2.Children.Add(i2d);
|
||||||
|
|
||||||
|
Grid.SetRow(innerGrid1, 0); Grid.SetColumn(innerGrid1, 0);
|
||||||
|
Grid.SetRow(innerGrid2, 0); Grid.SetColumn(innerGrid2, 1);
|
||||||
|
|
||||||
|
var label1 = new Label { Text = "Outer Grid Row 1", HorizontalOptions = LayoutOptions.Center };
|
||||||
|
var label2 = new Label { Text = "Spans both columns", HorizontalOptions = LayoutOptions.Center };
|
||||||
|
Grid.SetRow(label1, 1); Grid.SetColumn(label1, 0);
|
||||||
|
Grid.SetRow(label2, 1); Grid.SetColumn(label2, 1);
|
||||||
|
|
||||||
|
outerGrid.Children.Add(innerGrid1);
|
||||||
|
outerGrid.Children.Add(innerGrid2);
|
||||||
|
outerGrid.Children.Add(label1);
|
||||||
|
outerGrid.Children.Add(label2);
|
||||||
|
|
||||||
|
return CreateDemoContainer(outerGrid, "Outer grid contains two nested 2x2 grids");
|
||||||
|
}
|
||||||
|
|
||||||
|
private Border CreateCell(string text, string bgColor, Color? textColor = null, float fontSize = 12)
|
||||||
|
{
|
||||||
|
return new Border
|
||||||
|
{
|
||||||
|
BackgroundColor = Color.FromArgb(bgColor),
|
||||||
|
Padding = new Thickness(10, 8),
|
||||||
|
StrokeThickness = 0,
|
||||||
|
Content = new Label
|
||||||
|
{
|
||||||
|
Text = text,
|
||||||
|
FontSize = fontSize,
|
||||||
|
TextColor = textColor ?? Colors.Black,
|
||||||
|
HorizontalTextAlignment = TextAlignment.Center,
|
||||||
|
VerticalTextAlignment = TextAlignment.Center
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AddFourCells(Grid grid)
|
||||||
|
{
|
||||||
|
var c1 = CreateCell("0,0", "#BBDEFB");
|
||||||
|
var c2 = CreateCell("0,1", "#C8E6C9");
|
||||||
|
var c3 = CreateCell("1,0", "#FFECB3");
|
||||||
|
var c4 = CreateCell("1,1", "#F8BBD9");
|
||||||
|
|
||||||
|
Grid.SetRow(c1, 0); Grid.SetColumn(c1, 0);
|
||||||
|
Grid.SetRow(c2, 0); Grid.SetColumn(c2, 1);
|
||||||
|
Grid.SetRow(c3, 1); Grid.SetColumn(c3, 0);
|
||||||
|
Grid.SetRow(c4, 1); Grid.SetColumn(c4, 1);
|
||||||
|
|
||||||
|
grid.Children.Add(c1);
|
||||||
|
grid.Children.Add(c2);
|
||||||
|
grid.Children.Add(c3);
|
||||||
|
grid.Children.Add(c4);
|
||||||
|
}
|
||||||
|
|
||||||
|
private View CreateDemoContainer(View content, string description)
|
||||||
|
{
|
||||||
|
return new VerticalStackLayout
|
||||||
|
{
|
||||||
|
Spacing = 5,
|
||||||
|
Children =
|
||||||
|
{
|
||||||
|
new Label { Text = description, FontSize = 12, TextColor = Colors.Gray },
|
||||||
|
content
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,265 @@
|
||||||
|
// HomePage - Welcome page for the demo
|
||||||
|
|
||||||
|
using Microsoft.Maui.Controls;
|
||||||
|
using Microsoft.Maui.Graphics;
|
||||||
|
using Microsoft.Maui.Platform.Linux.Hosting;
|
||||||
|
|
||||||
|
namespace ShellDemo;
|
||||||
|
|
||||||
|
public class HomePage : ContentPage
|
||||||
|
{
|
||||||
|
public HomePage()
|
||||||
|
{
|
||||||
|
Title = "Home";
|
||||||
|
|
||||||
|
Content = new ScrollView
|
||||||
|
{
|
||||||
|
Orientation = ScrollOrientation.Both, // Enable horizontal scrolling when window is too narrow
|
||||||
|
Content = new VerticalStackLayout
|
||||||
|
{
|
||||||
|
Padding = new Thickness(30),
|
||||||
|
Spacing = 20,
|
||||||
|
Children =
|
||||||
|
{
|
||||||
|
new Label
|
||||||
|
{
|
||||||
|
Text = "OpenMaui Linux",
|
||||||
|
FontSize = 32,
|
||||||
|
FontAttributes = FontAttributes.Bold,
|
||||||
|
HorizontalOptions = LayoutOptions.Center,
|
||||||
|
TextColor = Color.FromArgb("#2196F3")
|
||||||
|
},
|
||||||
|
|
||||||
|
new Label
|
||||||
|
{
|
||||||
|
Text = "Controls Demo",
|
||||||
|
FontSize = 20,
|
||||||
|
HorizontalOptions = LayoutOptions.Center,
|
||||||
|
TextColor = Colors.Gray
|
||||||
|
},
|
||||||
|
|
||||||
|
new BoxView
|
||||||
|
{
|
||||||
|
HeightRequest = 2,
|
||||||
|
Color = Color.FromArgb("#E0E0E0"),
|
||||||
|
Margin = new Thickness(0, 10)
|
||||||
|
},
|
||||||
|
|
||||||
|
new Label
|
||||||
|
{
|
||||||
|
Text = "Welcome to the comprehensive controls demonstration for OpenMaui Linux. " +
|
||||||
|
"This app showcases all the major UI controls available in the framework.",
|
||||||
|
FontSize = 14,
|
||||||
|
LineBreakMode = LineBreakMode.WordWrap,
|
||||||
|
HorizontalTextAlignment = TextAlignment.Center
|
||||||
|
},
|
||||||
|
|
||||||
|
CreateFeatureSection(),
|
||||||
|
|
||||||
|
new Label
|
||||||
|
{
|
||||||
|
Text = "Use the flyout menu (swipe from left or tap the hamburger icon) to navigate between different control demos.",
|
||||||
|
FontSize = 12,
|
||||||
|
TextColor = Colors.Gray,
|
||||||
|
LineBreakMode = LineBreakMode.WordWrap,
|
||||||
|
HorizontalTextAlignment = TextAlignment.Center,
|
||||||
|
Margin = new Thickness(0, 20, 0, 0)
|
||||||
|
},
|
||||||
|
|
||||||
|
CreateQuickLinksSection(),
|
||||||
|
|
||||||
|
CreateNavigationDemoSection()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private View CreateFeatureSection()
|
||||||
|
{
|
||||||
|
var grid = new Grid
|
||||||
|
{
|
||||||
|
ColumnDefinitions =
|
||||||
|
{
|
||||||
|
new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) },
|
||||||
|
new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) }
|
||||||
|
},
|
||||||
|
RowDefinitions =
|
||||||
|
{
|
||||||
|
new RowDefinition { Height = GridLength.Auto },
|
||||||
|
new RowDefinition { Height = GridLength.Auto },
|
||||||
|
new RowDefinition { Height = GridLength.Auto }
|
||||||
|
},
|
||||||
|
ColumnSpacing = 15,
|
||||||
|
RowSpacing = 15,
|
||||||
|
Margin = new Thickness(0, 20)
|
||||||
|
};
|
||||||
|
|
||||||
|
var features = new[]
|
||||||
|
{
|
||||||
|
("Buttons", "Various button styles and events"),
|
||||||
|
("Text Input", "Entry, Editor, SearchBar"),
|
||||||
|
("Selection", "CheckBox, Switch, Slider"),
|
||||||
|
("Pickers", "Picker, DatePicker, TimePicker"),
|
||||||
|
("Lists", "CollectionView with selection"),
|
||||||
|
("Progress", "ProgressBar, ActivityIndicator")
|
||||||
|
};
|
||||||
|
|
||||||
|
for (int i = 0; i < features.Length; i++)
|
||||||
|
{
|
||||||
|
var (title, desc) = features[i];
|
||||||
|
var card = CreateFeatureCard(title, desc);
|
||||||
|
Grid.SetRow(card, i / 2);
|
||||||
|
Grid.SetColumn(card, i % 2);
|
||||||
|
grid.Children.Add(card);
|
||||||
|
}
|
||||||
|
|
||||||
|
return grid;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Frame CreateFeatureCard(string title, string description)
|
||||||
|
{
|
||||||
|
return new Frame
|
||||||
|
{
|
||||||
|
CornerRadius = 8,
|
||||||
|
Padding = new Thickness(15),
|
||||||
|
BackgroundColor = Colors.White,
|
||||||
|
HasShadow = true,
|
||||||
|
Content = new VerticalStackLayout
|
||||||
|
{
|
||||||
|
Spacing = 5,
|
||||||
|
Children =
|
||||||
|
{
|
||||||
|
new Label
|
||||||
|
{
|
||||||
|
Text = title,
|
||||||
|
FontSize = 14,
|
||||||
|
FontAttributes = FontAttributes.Bold,
|
||||||
|
TextColor = Color.FromArgb("#2196F3")
|
||||||
|
},
|
||||||
|
new Label
|
||||||
|
{
|
||||||
|
Text = description,
|
||||||
|
FontSize = 11,
|
||||||
|
TextColor = Colors.Gray,
|
||||||
|
LineBreakMode = LineBreakMode.WordWrap
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private View CreateQuickLinksSection()
|
||||||
|
{
|
||||||
|
var layout = new VerticalStackLayout
|
||||||
|
{
|
||||||
|
Spacing = 10,
|
||||||
|
Margin = new Thickness(0, 20, 0, 0)
|
||||||
|
};
|
||||||
|
|
||||||
|
layout.Children.Add(new Label
|
||||||
|
{
|
||||||
|
Text = "Quick Actions",
|
||||||
|
FontSize = 16,
|
||||||
|
FontAttributes = FontAttributes.Bold,
|
||||||
|
HorizontalOptions = LayoutOptions.Center
|
||||||
|
});
|
||||||
|
|
||||||
|
var buttonRow = new HorizontalStackLayout
|
||||||
|
{
|
||||||
|
Spacing = 10,
|
||||||
|
HorizontalOptions = LayoutOptions.Center
|
||||||
|
};
|
||||||
|
|
||||||
|
var buttonsBtn = new Button
|
||||||
|
{
|
||||||
|
Text = "Try Buttons",
|
||||||
|
BackgroundColor = Color.FromArgb("#2196F3"),
|
||||||
|
TextColor = Colors.White
|
||||||
|
};
|
||||||
|
buttonsBtn.Clicked += (s, e) => LinuxViewRenderer.NavigateToRoute("Buttons");
|
||||||
|
|
||||||
|
var listsBtn = new Button
|
||||||
|
{
|
||||||
|
Text = "Try Lists",
|
||||||
|
BackgroundColor = Color.FromArgb("#4CAF50"),
|
||||||
|
TextColor = Colors.White
|
||||||
|
};
|
||||||
|
listsBtn.Clicked += (s, e) => LinuxViewRenderer.NavigateToRoute("Lists");
|
||||||
|
|
||||||
|
buttonRow.Children.Add(buttonsBtn);
|
||||||
|
buttonRow.Children.Add(listsBtn);
|
||||||
|
layout.Children.Add(buttonRow);
|
||||||
|
|
||||||
|
return layout;
|
||||||
|
}
|
||||||
|
|
||||||
|
private View CreateNavigationDemoSection()
|
||||||
|
{
|
||||||
|
var frame = new Frame
|
||||||
|
{
|
||||||
|
CornerRadius = 8,
|
||||||
|
Padding = new Thickness(20),
|
||||||
|
BackgroundColor = Color.FromArgb("#F3E5F5"),
|
||||||
|
Margin = new Thickness(0, 20, 0, 0),
|
||||||
|
Content = new VerticalStackLayout
|
||||||
|
{
|
||||||
|
Spacing = 15,
|
||||||
|
Children =
|
||||||
|
{
|
||||||
|
new Label
|
||||||
|
{
|
||||||
|
Text = "Navigation Stack Demo",
|
||||||
|
FontSize = 18,
|
||||||
|
FontAttributes = FontAttributes.Bold,
|
||||||
|
TextColor = Color.FromArgb("#9C27B0"),
|
||||||
|
HorizontalOptions = LayoutOptions.Center
|
||||||
|
},
|
||||||
|
|
||||||
|
new Label
|
||||||
|
{
|
||||||
|
Text = "Demonstrate push/pop navigation using Shell.GoToAsync()",
|
||||||
|
FontSize = 12,
|
||||||
|
TextColor = Colors.Gray,
|
||||||
|
HorizontalTextAlignment = TextAlignment.Center
|
||||||
|
},
|
||||||
|
|
||||||
|
CreatePushButton("Push Detail Page", "detail"),
|
||||||
|
|
||||||
|
new Label
|
||||||
|
{
|
||||||
|
Text = "Click the button to push a new page onto the navigation stack. " +
|
||||||
|
"Use the back button or 'Go Back' to pop it off.",
|
||||||
|
FontSize = 11,
|
||||||
|
TextColor = Colors.Gray,
|
||||||
|
HorizontalTextAlignment = TextAlignment.Center,
|
||||||
|
LineBreakMode = LineBreakMode.WordWrap
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return frame;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Button CreatePushButton(string text, string route)
|
||||||
|
{
|
||||||
|
var btn = new Button
|
||||||
|
{
|
||||||
|
Text = text,
|
||||||
|
BackgroundColor = Color.FromArgb("#9C27B0"),
|
||||||
|
TextColor = Colors.White,
|
||||||
|
HorizontalOptions = LayoutOptions.Center,
|
||||||
|
Padding = new Thickness(30, 10)
|
||||||
|
};
|
||||||
|
|
||||||
|
btn.Clicked += (s, e) =>
|
||||||
|
{
|
||||||
|
Console.WriteLine($"[HomePage] Push button clicked, navigating to {route}");
|
||||||
|
// Use LinuxViewRenderer.PushPage for Skia-based navigation
|
||||||
|
var success = LinuxViewRenderer.PushPage(new DetailPage());
|
||||||
|
Console.WriteLine($"[HomePage] PushPage result: {success}");
|
||||||
|
};
|
||||||
|
|
||||||
|
return btn;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,249 @@
|
||||||
|
// ListsPage - CollectionView and ListView Demo
|
||||||
|
|
||||||
|
using Microsoft.Maui.Controls;
|
||||||
|
using Microsoft.Maui.Graphics;
|
||||||
|
|
||||||
|
namespace ShellDemo;
|
||||||
|
|
||||||
|
public class ListsPage : ContentPage
|
||||||
|
{
|
||||||
|
private readonly Label _eventLog;
|
||||||
|
private int _eventCount = 0;
|
||||||
|
|
||||||
|
public ListsPage()
|
||||||
|
{
|
||||||
|
Title = "Lists";
|
||||||
|
|
||||||
|
_eventLog = new Label
|
||||||
|
{
|
||||||
|
Text = "Events will appear here...",
|
||||||
|
FontSize = 11,
|
||||||
|
TextColor = Colors.Gray,
|
||||||
|
LineBreakMode = LineBreakMode.WordWrap
|
||||||
|
};
|
||||||
|
|
||||||
|
Content = new Grid
|
||||||
|
{
|
||||||
|
RowDefinitions =
|
||||||
|
{
|
||||||
|
new RowDefinition { Height = new GridLength(1, GridUnitType.Star) },
|
||||||
|
new RowDefinition { Height = new GridLength(120) }
|
||||||
|
},
|
||||||
|
Children =
|
||||||
|
{
|
||||||
|
CreateMainContent(),
|
||||||
|
CreateEventLogPanel()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Grid.SetRow((View)((Grid)Content).Children[0], 0);
|
||||||
|
Grid.SetRow((View)((Grid)Content).Children[1], 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
private View CreateMainContent()
|
||||||
|
{
|
||||||
|
return new ScrollView
|
||||||
|
{
|
||||||
|
Content = new VerticalStackLayout
|
||||||
|
{
|
||||||
|
Padding = new Thickness(20),
|
||||||
|
Spacing = 20,
|
||||||
|
Children =
|
||||||
|
{
|
||||||
|
new Label { Text = "List Controls", FontSize = 24, FontAttributes = FontAttributes.Bold },
|
||||||
|
|
||||||
|
CreateSection("CollectionView - Fruits", CreateFruitsCollectionView()),
|
||||||
|
CreateSection("CollectionView - Colors", CreateColorsCollectionView()),
|
||||||
|
CreateSection("CollectionView - Contacts", CreateContactsCollectionView())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private View CreateFruitsCollectionView()
|
||||||
|
{
|
||||||
|
var layout = new VerticalStackLayout { Spacing = 10 };
|
||||||
|
|
||||||
|
var fruits = new List<string>
|
||||||
|
{
|
||||||
|
"Apple", "Banana", "Cherry", "Date", "Elderberry",
|
||||||
|
"Fig", "Grape", "Honeydew", "Kiwi", "Lemon",
|
||||||
|
"Mango", "Nectarine", "Orange", "Papaya", "Quince"
|
||||||
|
};
|
||||||
|
|
||||||
|
var selectedLabel = new Label { Text = "Tap a fruit to select", TextColor = Colors.Gray };
|
||||||
|
|
||||||
|
var collectionView = new CollectionView
|
||||||
|
{
|
||||||
|
ItemsSource = fruits,
|
||||||
|
HeightRequest = 200,
|
||||||
|
SelectionMode = SelectionMode.Single,
|
||||||
|
BackgroundColor = Color.FromArgb("#FAFAFA")
|
||||||
|
};
|
||||||
|
|
||||||
|
collectionView.SelectionChanged += (s, e) =>
|
||||||
|
{
|
||||||
|
if (e.CurrentSelection.Count > 0)
|
||||||
|
{
|
||||||
|
var item = e.CurrentSelection[0]?.ToString();
|
||||||
|
selectedLabel.Text = $"Selected: {item}";
|
||||||
|
LogEvent($"Fruit selected: {item}");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
layout.Children.Add(collectionView);
|
||||||
|
layout.Children.Add(selectedLabel);
|
||||||
|
|
||||||
|
return layout;
|
||||||
|
}
|
||||||
|
|
||||||
|
private View CreateColorsCollectionView()
|
||||||
|
{
|
||||||
|
var layout = new VerticalStackLayout { Spacing = 10 };
|
||||||
|
|
||||||
|
var colors = new List<ColorItem>
|
||||||
|
{
|
||||||
|
new("Red", "#F44336"),
|
||||||
|
new("Pink", "#E91E63"),
|
||||||
|
new("Purple", "#9C27B0"),
|
||||||
|
new("Deep Purple", "#673AB7"),
|
||||||
|
new("Indigo", "#3F51B5"),
|
||||||
|
new("Blue", "#2196F3"),
|
||||||
|
new("Cyan", "#00BCD4"),
|
||||||
|
new("Teal", "#009688"),
|
||||||
|
new("Green", "#4CAF50"),
|
||||||
|
new("Light Green", "#8BC34A"),
|
||||||
|
new("Lime", "#CDDC39"),
|
||||||
|
new("Yellow", "#FFEB3B"),
|
||||||
|
new("Amber", "#FFC107"),
|
||||||
|
new("Orange", "#FF9800"),
|
||||||
|
new("Deep Orange", "#FF5722")
|
||||||
|
};
|
||||||
|
|
||||||
|
var collectionView = new CollectionView
|
||||||
|
{
|
||||||
|
ItemsSource = colors,
|
||||||
|
HeightRequest = 180,
|
||||||
|
SelectionMode = SelectionMode.Single,
|
||||||
|
BackgroundColor = Colors.White
|
||||||
|
};
|
||||||
|
|
||||||
|
collectionView.SelectionChanged += (s, e) =>
|
||||||
|
{
|
||||||
|
if (e.CurrentSelection.Count > 0 && e.CurrentSelection[0] is ColorItem item)
|
||||||
|
{
|
||||||
|
LogEvent($"Color selected: {item.Name} ({item.Hex})");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
layout.Children.Add(collectionView);
|
||||||
|
layout.Children.Add(new Label { Text = "Scroll to see all colors", FontSize = 11, TextColor = Colors.Gray });
|
||||||
|
|
||||||
|
return layout;
|
||||||
|
}
|
||||||
|
|
||||||
|
private View CreateContactsCollectionView()
|
||||||
|
{
|
||||||
|
var layout = new VerticalStackLayout { Spacing = 10 };
|
||||||
|
|
||||||
|
var contacts = new List<ContactItem>
|
||||||
|
{
|
||||||
|
new("Alice Johnson", "alice@example.com", "Engineering"),
|
||||||
|
new("Bob Smith", "bob@example.com", "Marketing"),
|
||||||
|
new("Carol Williams", "carol@example.com", "Design"),
|
||||||
|
new("David Brown", "david@example.com", "Sales"),
|
||||||
|
new("Eva Martinez", "eva@example.com", "Engineering"),
|
||||||
|
new("Frank Lee", "frank@example.com", "Support"),
|
||||||
|
new("Grace Kim", "grace@example.com", "HR"),
|
||||||
|
new("Henry Wilson", "henry@example.com", "Finance")
|
||||||
|
};
|
||||||
|
|
||||||
|
var collectionView = new CollectionView
|
||||||
|
{
|
||||||
|
ItemsSource = contacts,
|
||||||
|
HeightRequest = 200,
|
||||||
|
SelectionMode = SelectionMode.Single,
|
||||||
|
BackgroundColor = Colors.White
|
||||||
|
};
|
||||||
|
|
||||||
|
collectionView.SelectionChanged += (s, e) =>
|
||||||
|
{
|
||||||
|
if (e.CurrentSelection.Count > 0 && e.CurrentSelection[0] is ContactItem contact)
|
||||||
|
{
|
||||||
|
LogEvent($"Contact: {contact.Name} - {contact.Department}");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
layout.Children.Add(collectionView);
|
||||||
|
|
||||||
|
// Action buttons
|
||||||
|
var buttonRow = new HorizontalStackLayout { Spacing = 10 };
|
||||||
|
var addBtn = new Button { Text = "Add Contact", BackgroundColor = Colors.Green, TextColor = Colors.White };
|
||||||
|
addBtn.Clicked += (s, e) => LogEvent("Add contact clicked");
|
||||||
|
var deleteBtn = new Button { Text = "Delete Selected", BackgroundColor = Colors.Red, TextColor = Colors.White };
|
||||||
|
deleteBtn.Clicked += (s, e) => LogEvent("Delete contact clicked");
|
||||||
|
buttonRow.Children.Add(addBtn);
|
||||||
|
buttonRow.Children.Add(deleteBtn);
|
||||||
|
layout.Children.Add(buttonRow);
|
||||||
|
|
||||||
|
return layout;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Frame CreateSection(string title, View content)
|
||||||
|
{
|
||||||
|
return new Frame
|
||||||
|
{
|
||||||
|
CornerRadius = 8,
|
||||||
|
Padding = new Thickness(15),
|
||||||
|
BackgroundColor = Colors.White,
|
||||||
|
Content = new VerticalStackLayout
|
||||||
|
{
|
||||||
|
Spacing = 10,
|
||||||
|
Children =
|
||||||
|
{
|
||||||
|
new Label { Text = title, FontSize = 16, FontAttributes = FontAttributes.Bold },
|
||||||
|
content
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private View CreateEventLogPanel()
|
||||||
|
{
|
||||||
|
return new Frame
|
||||||
|
{
|
||||||
|
BackgroundColor = Color.FromArgb("#F5F5F5"),
|
||||||
|
Padding = new Thickness(10),
|
||||||
|
CornerRadius = 0,
|
||||||
|
Content = new VerticalStackLayout
|
||||||
|
{
|
||||||
|
Children =
|
||||||
|
{
|
||||||
|
new Label { Text = "Event Log:", FontSize = 12, FontAttributes = FontAttributes.Bold },
|
||||||
|
new ScrollView
|
||||||
|
{
|
||||||
|
HeightRequest = 80,
|
||||||
|
Content = _eventLog
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private void LogEvent(string message)
|
||||||
|
{
|
||||||
|
_eventCount++;
|
||||||
|
var timestamp = DateTime.Now.ToString("HH:mm:ss");
|
||||||
|
_eventLog.Text = $"[{timestamp}] {_eventCount}. {message}\n{_eventLog.Text}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public record ColorItem(string Name, string Hex)
|
||||||
|
{
|
||||||
|
public override string ToString() => Name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public record ContactItem(string Name, string Email, string Department)
|
||||||
|
{
|
||||||
|
public override string ToString() => $"{Name} ({Department})";
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,261 @@
|
||||||
|
// PickersPage - Picker, DatePicker, TimePicker Demo
|
||||||
|
|
||||||
|
using Microsoft.Maui.Controls;
|
||||||
|
using Microsoft.Maui.Graphics;
|
||||||
|
|
||||||
|
namespace ShellDemo;
|
||||||
|
|
||||||
|
public class PickersPage : ContentPage
|
||||||
|
{
|
||||||
|
private readonly Label _eventLog;
|
||||||
|
private int _eventCount = 0;
|
||||||
|
|
||||||
|
public PickersPage()
|
||||||
|
{
|
||||||
|
Title = "Pickers";
|
||||||
|
|
||||||
|
_eventLog = new Label
|
||||||
|
{
|
||||||
|
Text = "Events will appear here...",
|
||||||
|
FontSize = 11,
|
||||||
|
TextColor = Colors.Gray,
|
||||||
|
LineBreakMode = LineBreakMode.WordWrap
|
||||||
|
};
|
||||||
|
|
||||||
|
Content = new Grid
|
||||||
|
{
|
||||||
|
RowDefinitions =
|
||||||
|
{
|
||||||
|
new RowDefinition { Height = new GridLength(1, GridUnitType.Star) },
|
||||||
|
new RowDefinition { Height = new GridLength(120) }
|
||||||
|
},
|
||||||
|
Children =
|
||||||
|
{
|
||||||
|
CreateMainContent(),
|
||||||
|
CreateEventLogPanel()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Grid.SetRow((View)((Grid)Content).Children[0], 0);
|
||||||
|
Grid.SetRow((View)((Grid)Content).Children[1], 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
private View CreateMainContent()
|
||||||
|
{
|
||||||
|
return new ScrollView
|
||||||
|
{
|
||||||
|
Content = new VerticalStackLayout
|
||||||
|
{
|
||||||
|
Padding = new Thickness(20),
|
||||||
|
Spacing = 20,
|
||||||
|
Children =
|
||||||
|
{
|
||||||
|
new Label { Text = "Picker Controls", FontSize = 24, FontAttributes = FontAttributes.Bold },
|
||||||
|
|
||||||
|
CreateSection("Picker", CreatePickerDemo()),
|
||||||
|
CreateSection("DatePicker", CreateDatePickerDemo()),
|
||||||
|
CreateSection("TimePicker", CreateTimePickerDemo())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private View CreatePickerDemo()
|
||||||
|
{
|
||||||
|
var layout = new VerticalStackLayout { Spacing = 15 };
|
||||||
|
|
||||||
|
// Basic picker
|
||||||
|
var selectedLabel = new Label { Text = "Selected: (none)", TextColor = Colors.Gray };
|
||||||
|
var picker1 = new Picker { Title = "Select a fruit" };
|
||||||
|
picker1.Items.Add("Apple");
|
||||||
|
picker1.Items.Add("Banana");
|
||||||
|
picker1.Items.Add("Cherry");
|
||||||
|
picker1.Items.Add("Date");
|
||||||
|
picker1.Items.Add("Elderberry");
|
||||||
|
picker1.Items.Add("Fig");
|
||||||
|
picker1.Items.Add("Grape");
|
||||||
|
picker1.SelectedIndexChanged += (s, e) =>
|
||||||
|
{
|
||||||
|
if (picker1.SelectedIndex >= 0)
|
||||||
|
{
|
||||||
|
var item = picker1.Items[picker1.SelectedIndex];
|
||||||
|
selectedLabel.Text = $"Selected: {item}";
|
||||||
|
LogEvent($"Fruit selected: {item}");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
layout.Children.Add(picker1);
|
||||||
|
layout.Children.Add(selectedLabel);
|
||||||
|
|
||||||
|
// Picker with default selection
|
||||||
|
layout.Children.Add(new Label { Text = "With Default Selection:", FontSize = 12, Margin = new Thickness(0, 10, 0, 0) });
|
||||||
|
var picker2 = new Picker { Title = "Select a color" };
|
||||||
|
picker2.Items.Add("Red");
|
||||||
|
picker2.Items.Add("Green");
|
||||||
|
picker2.Items.Add("Blue");
|
||||||
|
picker2.Items.Add("Yellow");
|
||||||
|
picker2.Items.Add("Purple");
|
||||||
|
picker2.SelectedIndex = 2; // Blue
|
||||||
|
picker2.SelectedIndexChanged += (s, e) =>
|
||||||
|
{
|
||||||
|
if (picker2.SelectedIndex >= 0)
|
||||||
|
LogEvent($"Color selected: {picker2.Items[picker2.SelectedIndex]}");
|
||||||
|
};
|
||||||
|
layout.Children.Add(picker2);
|
||||||
|
|
||||||
|
// Styled picker
|
||||||
|
layout.Children.Add(new Label { Text = "Styled Picker:", FontSize = 12, Margin = new Thickness(0, 10, 0, 0) });
|
||||||
|
var picker3 = new Picker
|
||||||
|
{
|
||||||
|
Title = "Select size",
|
||||||
|
TextColor = Colors.DarkBlue,
|
||||||
|
TitleColor = Colors.Gray
|
||||||
|
};
|
||||||
|
picker3.Items.Add("Small");
|
||||||
|
picker3.Items.Add("Medium");
|
||||||
|
picker3.Items.Add("Large");
|
||||||
|
picker3.Items.Add("Extra Large");
|
||||||
|
picker3.SelectedIndexChanged += (s, e) =>
|
||||||
|
{
|
||||||
|
if (picker3.SelectedIndex >= 0)
|
||||||
|
LogEvent($"Size selected: {picker3.Items[picker3.SelectedIndex]}");
|
||||||
|
};
|
||||||
|
layout.Children.Add(picker3);
|
||||||
|
|
||||||
|
return layout;
|
||||||
|
}
|
||||||
|
|
||||||
|
private View CreateDatePickerDemo()
|
||||||
|
{
|
||||||
|
var layout = new VerticalStackLayout { Spacing = 15 };
|
||||||
|
|
||||||
|
// Basic date picker
|
||||||
|
var dateLabel = new Label { Text = $"Selected: {DateTime.Today:d}" };
|
||||||
|
var datePicker1 = new DatePicker { Date = DateTime.Today };
|
||||||
|
datePicker1.DateSelected += (s, e) =>
|
||||||
|
{
|
||||||
|
dateLabel.Text = $"Selected: {e.NewDate:d}";
|
||||||
|
LogEvent($"Date selected: {e.NewDate:d}");
|
||||||
|
};
|
||||||
|
layout.Children.Add(datePicker1);
|
||||||
|
layout.Children.Add(dateLabel);
|
||||||
|
|
||||||
|
// Date picker with range
|
||||||
|
layout.Children.Add(new Label { Text = "With Date Range (this month only):", FontSize = 12, Margin = new Thickness(0, 10, 0, 0) });
|
||||||
|
var startOfMonth = new DateTime(DateTime.Today.Year, DateTime.Today.Month, 1);
|
||||||
|
var endOfMonth = startOfMonth.AddMonths(1).AddDays(-1);
|
||||||
|
var datePicker2 = new DatePicker
|
||||||
|
{
|
||||||
|
MinimumDate = startOfMonth,
|
||||||
|
MaximumDate = endOfMonth,
|
||||||
|
Date = DateTime.Today
|
||||||
|
};
|
||||||
|
datePicker2.DateSelected += (s, e) => LogEvent($"Date (limited): {e.NewDate:d}");
|
||||||
|
layout.Children.Add(datePicker2);
|
||||||
|
|
||||||
|
// Styled date picker
|
||||||
|
layout.Children.Add(new Label { Text = "Styled DatePicker:", FontSize = 12, Margin = new Thickness(0, 10, 0, 0) });
|
||||||
|
var datePicker3 = new DatePicker
|
||||||
|
{
|
||||||
|
Date = DateTime.Today.AddDays(7),
|
||||||
|
TextColor = Colors.DarkGreen
|
||||||
|
};
|
||||||
|
datePicker3.DateSelected += (s, e) => LogEvent($"Styled date: {e.NewDate:d}");
|
||||||
|
layout.Children.Add(datePicker3);
|
||||||
|
|
||||||
|
return layout;
|
||||||
|
}
|
||||||
|
|
||||||
|
private View CreateTimePickerDemo()
|
||||||
|
{
|
||||||
|
var layout = new VerticalStackLayout { Spacing = 15 };
|
||||||
|
|
||||||
|
// Basic time picker
|
||||||
|
var timeLabel = new Label { Text = $"Selected: {DateTime.Now:t}" };
|
||||||
|
var timePicker1 = new TimePicker { Time = DateTime.Now.TimeOfDay };
|
||||||
|
timePicker1.PropertyChanged += (s, e) =>
|
||||||
|
{
|
||||||
|
if (e.PropertyName == nameof(TimePicker.Time))
|
||||||
|
{
|
||||||
|
var time = timePicker1.Time;
|
||||||
|
timeLabel.Text = $"Selected: {time:hh\\:mm}";
|
||||||
|
LogEvent($"Time selected: {time:hh\\:mm}");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
layout.Children.Add(timePicker1);
|
||||||
|
layout.Children.Add(timeLabel);
|
||||||
|
|
||||||
|
// Styled time picker
|
||||||
|
layout.Children.Add(new Label { Text = "Styled TimePicker:", FontSize = 12, Margin = new Thickness(0, 10, 0, 0) });
|
||||||
|
var timePicker2 = new TimePicker
|
||||||
|
{
|
||||||
|
Time = new TimeSpan(14, 30, 0),
|
||||||
|
TextColor = Colors.DarkBlue
|
||||||
|
};
|
||||||
|
timePicker2.PropertyChanged += (s, e) =>
|
||||||
|
{
|
||||||
|
if (e.PropertyName == nameof(TimePicker.Time))
|
||||||
|
LogEvent($"Styled time: {timePicker2.Time:hh\\:mm}");
|
||||||
|
};
|
||||||
|
layout.Children.Add(timePicker2);
|
||||||
|
|
||||||
|
// Morning alarm example
|
||||||
|
layout.Children.Add(new Label { Text = "Alarm Time:", FontSize = 12, Margin = new Thickness(0, 10, 0, 0) });
|
||||||
|
var alarmRow = new HorizontalStackLayout { Spacing = 10 };
|
||||||
|
var alarmPicker = new TimePicker { Time = new TimeSpan(7, 0, 0) };
|
||||||
|
var alarmBtn = new Button { Text = "Set Alarm", BackgroundColor = Colors.Orange, TextColor = Colors.White };
|
||||||
|
alarmBtn.Clicked += (s, e) => LogEvent($"Alarm set for {alarmPicker.Time:hh\\:mm}");
|
||||||
|
alarmRow.Children.Add(alarmPicker);
|
||||||
|
alarmRow.Children.Add(alarmBtn);
|
||||||
|
layout.Children.Add(alarmRow);
|
||||||
|
|
||||||
|
return layout;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Frame CreateSection(string title, View content)
|
||||||
|
{
|
||||||
|
return new Frame
|
||||||
|
{
|
||||||
|
CornerRadius = 8,
|
||||||
|
Padding = new Thickness(15),
|
||||||
|
BackgroundColor = Colors.White,
|
||||||
|
Content = new VerticalStackLayout
|
||||||
|
{
|
||||||
|
Spacing = 10,
|
||||||
|
Children =
|
||||||
|
{
|
||||||
|
new Label { Text = title, FontSize = 16, FontAttributes = FontAttributes.Bold },
|
||||||
|
content
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private View CreateEventLogPanel()
|
||||||
|
{
|
||||||
|
return new Frame
|
||||||
|
{
|
||||||
|
BackgroundColor = Color.FromArgb("#F5F5F5"),
|
||||||
|
Padding = new Thickness(10),
|
||||||
|
CornerRadius = 0,
|
||||||
|
Content = new VerticalStackLayout
|
||||||
|
{
|
||||||
|
Children =
|
||||||
|
{
|
||||||
|
new Label { Text = "Event Log:", FontSize = 12, FontAttributes = FontAttributes.Bold },
|
||||||
|
new ScrollView
|
||||||
|
{
|
||||||
|
HeightRequest = 80,
|
||||||
|
Content = _eventLog
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private void LogEvent(string message)
|
||||||
|
{
|
||||||
|
_eventCount++;
|
||||||
|
var timestamp = DateTime.Now.ToString("HH:mm:ss");
|
||||||
|
_eventLog.Text = $"[{timestamp}] {_eventCount}. {message}\n{_eventLog.Text}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,261 @@
|
||||||
|
// ProgressPage - ProgressBar and ActivityIndicator Demo
|
||||||
|
|
||||||
|
using Microsoft.Maui.Controls;
|
||||||
|
using Microsoft.Maui.Graphics;
|
||||||
|
|
||||||
|
namespace ShellDemo;
|
||||||
|
|
||||||
|
public class ProgressPage : ContentPage
|
||||||
|
{
|
||||||
|
private readonly Label _eventLog;
|
||||||
|
private int _eventCount = 0;
|
||||||
|
private ProgressBar? _animatedProgress;
|
||||||
|
private bool _isAnimating = false;
|
||||||
|
|
||||||
|
public ProgressPage()
|
||||||
|
{
|
||||||
|
Title = "Progress";
|
||||||
|
|
||||||
|
_eventLog = new Label
|
||||||
|
{
|
||||||
|
Text = "Events will appear here...",
|
||||||
|
FontSize = 11,
|
||||||
|
TextColor = Colors.Gray,
|
||||||
|
LineBreakMode = LineBreakMode.WordWrap
|
||||||
|
};
|
||||||
|
|
||||||
|
Content = new Grid
|
||||||
|
{
|
||||||
|
RowDefinitions =
|
||||||
|
{
|
||||||
|
new RowDefinition { Height = new GridLength(1, GridUnitType.Star) },
|
||||||
|
new RowDefinition { Height = new GridLength(120) }
|
||||||
|
},
|
||||||
|
Children =
|
||||||
|
{
|
||||||
|
CreateMainContent(),
|
||||||
|
CreateEventLogPanel()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Grid.SetRow((View)((Grid)Content).Children[0], 0);
|
||||||
|
Grid.SetRow((View)((Grid)Content).Children[1], 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
private View CreateMainContent()
|
||||||
|
{
|
||||||
|
return new ScrollView
|
||||||
|
{
|
||||||
|
Content = new VerticalStackLayout
|
||||||
|
{
|
||||||
|
Padding = new Thickness(20),
|
||||||
|
Spacing = 20,
|
||||||
|
Children =
|
||||||
|
{
|
||||||
|
new Label { Text = "Progress Indicators", FontSize = 24, FontAttributes = FontAttributes.Bold },
|
||||||
|
|
||||||
|
CreateSection("ProgressBar", CreateProgressBarDemo()),
|
||||||
|
CreateSection("ActivityIndicator", CreateActivityIndicatorDemo()),
|
||||||
|
CreateSection("Interactive Demo", CreateInteractiveDemo())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private View CreateProgressBarDemo()
|
||||||
|
{
|
||||||
|
var layout = new VerticalStackLayout { Spacing = 15 };
|
||||||
|
|
||||||
|
// Various progress values
|
||||||
|
var values = new[] { 0.0, 0.25, 0.5, 0.75, 1.0 };
|
||||||
|
foreach (var value in values)
|
||||||
|
{
|
||||||
|
var row = new HorizontalStackLayout { Spacing = 10 };
|
||||||
|
var progress = new ProgressBar { Progress = value, WidthRequest = 200 };
|
||||||
|
var label = new Label { Text = $"{value * 100:0}%", VerticalOptions = LayoutOptions.Center, WidthRequest = 50 };
|
||||||
|
row.Children.Add(progress);
|
||||||
|
row.Children.Add(label);
|
||||||
|
layout.Children.Add(row);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Colored progress bars
|
||||||
|
layout.Children.Add(new Label { Text = "Colored Progress Bars:", FontSize = 12, Margin = new Thickness(0, 10, 0, 0) });
|
||||||
|
|
||||||
|
var colors = new[] { Colors.Red, Colors.Green, Colors.Blue, Colors.Orange, Colors.Purple };
|
||||||
|
foreach (var color in colors)
|
||||||
|
{
|
||||||
|
var progress = new ProgressBar { Progress = 0.7, ProgressColor = color };
|
||||||
|
layout.Children.Add(progress);
|
||||||
|
}
|
||||||
|
|
||||||
|
return layout;
|
||||||
|
}
|
||||||
|
|
||||||
|
private View CreateActivityIndicatorDemo()
|
||||||
|
{
|
||||||
|
var layout = new VerticalStackLayout { Spacing = 15 };
|
||||||
|
|
||||||
|
// Running indicator
|
||||||
|
var runningRow = new HorizontalStackLayout { Spacing = 15 };
|
||||||
|
var runningIndicator = new ActivityIndicator { IsRunning = true };
|
||||||
|
runningRow.Children.Add(runningIndicator);
|
||||||
|
runningRow.Children.Add(new Label { Text = "Loading...", VerticalOptions = LayoutOptions.Center });
|
||||||
|
layout.Children.Add(runningRow);
|
||||||
|
|
||||||
|
// Toggle indicator
|
||||||
|
var toggleRow = new HorizontalStackLayout { Spacing = 15 };
|
||||||
|
var toggleIndicator = new ActivityIndicator { IsRunning = false };
|
||||||
|
var toggleBtn = new Button { Text = "Start/Stop" };
|
||||||
|
toggleBtn.Clicked += (s, e) =>
|
||||||
|
{
|
||||||
|
toggleIndicator.IsRunning = !toggleIndicator.IsRunning;
|
||||||
|
LogEvent($"ActivityIndicator: {(toggleIndicator.IsRunning ? "Started" : "Stopped")}");
|
||||||
|
};
|
||||||
|
toggleRow.Children.Add(toggleIndicator);
|
||||||
|
toggleRow.Children.Add(toggleBtn);
|
||||||
|
layout.Children.Add(toggleRow);
|
||||||
|
|
||||||
|
// Colored indicators
|
||||||
|
layout.Children.Add(new Label { Text = "Colored Indicators:", FontSize = 12, Margin = new Thickness(0, 10, 0, 0) });
|
||||||
|
var colorRow = new HorizontalStackLayout { Spacing = 20 };
|
||||||
|
var indicatorColors = new[] { Colors.Red, Colors.Green, Colors.Blue, Colors.Orange };
|
||||||
|
foreach (var color in indicatorColors)
|
||||||
|
{
|
||||||
|
var indicator = new ActivityIndicator { IsRunning = true, Color = color };
|
||||||
|
colorRow.Children.Add(indicator);
|
||||||
|
}
|
||||||
|
layout.Children.Add(colorRow);
|
||||||
|
|
||||||
|
return layout;
|
||||||
|
}
|
||||||
|
|
||||||
|
private View CreateInteractiveDemo()
|
||||||
|
{
|
||||||
|
var layout = new VerticalStackLayout { Spacing = 15 };
|
||||||
|
|
||||||
|
// Slider-controlled progress
|
||||||
|
var progressLabel = new Label { Text = "Progress: 50%" };
|
||||||
|
_animatedProgress = new ProgressBar { Progress = 0.5 };
|
||||||
|
|
||||||
|
var slider = new Slider { Minimum = 0, Maximum = 100, Value = 50 };
|
||||||
|
slider.ValueChanged += (s, e) =>
|
||||||
|
{
|
||||||
|
var value = e.NewValue / 100.0;
|
||||||
|
_animatedProgress.Progress = value;
|
||||||
|
progressLabel.Text = $"Progress: {e.NewValue:0}%";
|
||||||
|
};
|
||||||
|
|
||||||
|
layout.Children.Add(_animatedProgress);
|
||||||
|
layout.Children.Add(slider);
|
||||||
|
layout.Children.Add(progressLabel);
|
||||||
|
|
||||||
|
// Animated progress buttons
|
||||||
|
var buttonRow = new HorizontalStackLayout { Spacing = 10, Margin = new Thickness(0, 10, 0, 0) };
|
||||||
|
|
||||||
|
var resetBtn = new Button { Text = "Reset", BackgroundColor = Colors.Gray, TextColor = Colors.White };
|
||||||
|
resetBtn.Clicked += async (s, e) =>
|
||||||
|
{
|
||||||
|
_animatedProgress.Progress = 0;
|
||||||
|
slider.Value = 0;
|
||||||
|
LogEvent("Progress reset to 0%");
|
||||||
|
};
|
||||||
|
|
||||||
|
var animateBtn = new Button { Text = "Animate to 100%", BackgroundColor = Colors.Blue, TextColor = Colors.White };
|
||||||
|
animateBtn.Clicked += async (s, e) =>
|
||||||
|
{
|
||||||
|
if (_isAnimating) return;
|
||||||
|
_isAnimating = true;
|
||||||
|
LogEvent("Animation started");
|
||||||
|
|
||||||
|
for (int i = (int)(slider.Value); i <= 100; i += 5)
|
||||||
|
{
|
||||||
|
_animatedProgress.Progress = i / 100.0;
|
||||||
|
slider.Value = i;
|
||||||
|
await Task.Delay(100);
|
||||||
|
}
|
||||||
|
|
||||||
|
_isAnimating = false;
|
||||||
|
LogEvent("Animation completed");
|
||||||
|
};
|
||||||
|
|
||||||
|
var simulateBtn = new Button { Text = "Simulate Download", BackgroundColor = Colors.Green, TextColor = Colors.White };
|
||||||
|
simulateBtn.Clicked += async (s, e) =>
|
||||||
|
{
|
||||||
|
if (_isAnimating) return;
|
||||||
|
_isAnimating = true;
|
||||||
|
LogEvent("Download simulation started");
|
||||||
|
|
||||||
|
_animatedProgress.Progress = 0;
|
||||||
|
slider.Value = 0;
|
||||||
|
|
||||||
|
var random = new Random();
|
||||||
|
double progress = 0;
|
||||||
|
while (progress < 1.0)
|
||||||
|
{
|
||||||
|
progress += random.NextDouble() * 0.1;
|
||||||
|
if (progress > 1.0) progress = 1.0;
|
||||||
|
_animatedProgress.Progress = progress;
|
||||||
|
slider.Value = progress * 100;
|
||||||
|
await Task.Delay(200 + random.Next(300));
|
||||||
|
}
|
||||||
|
|
||||||
|
_isAnimating = false;
|
||||||
|
LogEvent("Download simulation completed");
|
||||||
|
};
|
||||||
|
|
||||||
|
buttonRow.Children.Add(resetBtn);
|
||||||
|
buttonRow.Children.Add(animateBtn);
|
||||||
|
buttonRow.Children.Add(simulateBtn);
|
||||||
|
layout.Children.Add(buttonRow);
|
||||||
|
|
||||||
|
return layout;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Frame CreateSection(string title, View content)
|
||||||
|
{
|
||||||
|
return new Frame
|
||||||
|
{
|
||||||
|
CornerRadius = 8,
|
||||||
|
Padding = new Thickness(15),
|
||||||
|
BackgroundColor = Colors.White,
|
||||||
|
Content = new VerticalStackLayout
|
||||||
|
{
|
||||||
|
Spacing = 10,
|
||||||
|
Children =
|
||||||
|
{
|
||||||
|
new Label { Text = title, FontSize = 16, FontAttributes = FontAttributes.Bold },
|
||||||
|
content
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private View CreateEventLogPanel()
|
||||||
|
{
|
||||||
|
return new Frame
|
||||||
|
{
|
||||||
|
BackgroundColor = Color.FromArgb("#F5F5F5"),
|
||||||
|
Padding = new Thickness(10),
|
||||||
|
CornerRadius = 0,
|
||||||
|
Content = new VerticalStackLayout
|
||||||
|
{
|
||||||
|
Children =
|
||||||
|
{
|
||||||
|
new Label { Text = "Event Log:", FontSize = 12, FontAttributes = FontAttributes.Bold },
|
||||||
|
new ScrollView
|
||||||
|
{
|
||||||
|
HeightRequest = 80,
|
||||||
|
Content = _eventLog
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private void LogEvent(string message)
|
||||||
|
{
|
||||||
|
_eventCount++;
|
||||||
|
var timestamp = DateTime.Now.ToString("HH:mm:ss");
|
||||||
|
_eventLog.Text = $"[{timestamp}] {_eventCount}. {message}\n{_eventLog.Text}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,239 @@
|
||||||
|
// SelectionPage - CheckBox, Switch, Slider Demo
|
||||||
|
|
||||||
|
using Microsoft.Maui.Controls;
|
||||||
|
using Microsoft.Maui.Graphics;
|
||||||
|
|
||||||
|
namespace ShellDemo;
|
||||||
|
|
||||||
|
public class SelectionPage : ContentPage
|
||||||
|
{
|
||||||
|
private readonly Label _eventLog;
|
||||||
|
private int _eventCount = 0;
|
||||||
|
|
||||||
|
public SelectionPage()
|
||||||
|
{
|
||||||
|
Title = "Selection Controls";
|
||||||
|
|
||||||
|
_eventLog = new Label
|
||||||
|
{
|
||||||
|
Text = "Events will appear here...",
|
||||||
|
FontSize = 11,
|
||||||
|
TextColor = Colors.Gray,
|
||||||
|
LineBreakMode = LineBreakMode.WordWrap
|
||||||
|
};
|
||||||
|
|
||||||
|
Content = new Grid
|
||||||
|
{
|
||||||
|
RowDefinitions =
|
||||||
|
{
|
||||||
|
new RowDefinition { Height = new GridLength(1, GridUnitType.Star) },
|
||||||
|
new RowDefinition { Height = new GridLength(120) }
|
||||||
|
},
|
||||||
|
Children =
|
||||||
|
{
|
||||||
|
CreateMainContent(),
|
||||||
|
CreateEventLogPanel()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Grid.SetRow((View)((Grid)Content).Children[0], 0);
|
||||||
|
Grid.SetRow((View)((Grid)Content).Children[1], 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
private View CreateMainContent()
|
||||||
|
{
|
||||||
|
return new ScrollView
|
||||||
|
{
|
||||||
|
Content = new VerticalStackLayout
|
||||||
|
{
|
||||||
|
Padding = new Thickness(20),
|
||||||
|
Spacing = 20,
|
||||||
|
Children =
|
||||||
|
{
|
||||||
|
new Label { Text = "Selection Controls", FontSize = 24, FontAttributes = FontAttributes.Bold },
|
||||||
|
|
||||||
|
CreateSection("CheckBox", CreateCheckBoxDemo()),
|
||||||
|
CreateSection("Switch", CreateSwitchDemo()),
|
||||||
|
CreateSection("Slider", CreateSliderDemo())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private View CreateCheckBoxDemo()
|
||||||
|
{
|
||||||
|
var layout = new VerticalStackLayout { Spacing = 15 };
|
||||||
|
|
||||||
|
// Basic checkboxes
|
||||||
|
var basicRow = new HorizontalStackLayout { Spacing = 20 };
|
||||||
|
|
||||||
|
var cb1 = new CheckBox { IsChecked = false };
|
||||||
|
cb1.CheckedChanged += (s, e) => LogEvent($"Checkbox 1: {(e.Value ? "Checked" : "Unchecked")}");
|
||||||
|
basicRow.Children.Add(cb1);
|
||||||
|
basicRow.Children.Add(new Label { Text = "Option 1", VerticalOptions = LayoutOptions.Center });
|
||||||
|
|
||||||
|
var cb2 = new CheckBox { IsChecked = true };
|
||||||
|
cb2.CheckedChanged += (s, e) => LogEvent($"Checkbox 2: {(e.Value ? "Checked" : "Unchecked")}");
|
||||||
|
basicRow.Children.Add(cb2);
|
||||||
|
basicRow.Children.Add(new Label { Text = "Option 2 (default checked)", VerticalOptions = LayoutOptions.Center });
|
||||||
|
|
||||||
|
layout.Children.Add(basicRow);
|
||||||
|
|
||||||
|
// Colored checkboxes
|
||||||
|
var colorRow = new HorizontalStackLayout { Spacing = 20 };
|
||||||
|
var colors = new[] { Colors.Red, Colors.Green, Colors.Blue, Colors.Purple };
|
||||||
|
foreach (var color in colors)
|
||||||
|
{
|
||||||
|
var cb = new CheckBox { Color = color, IsChecked = true };
|
||||||
|
cb.CheckedChanged += (s, e) => LogEvent($"{color} checkbox: {(e.Value ? "Checked" : "Unchecked")}");
|
||||||
|
colorRow.Children.Add(cb);
|
||||||
|
}
|
||||||
|
layout.Children.Add(new Label { Text = "Colored Checkboxes:", FontSize = 12 });
|
||||||
|
layout.Children.Add(colorRow);
|
||||||
|
|
||||||
|
// Disabled checkbox
|
||||||
|
var disabledRow = new HorizontalStackLayout { Spacing = 10 };
|
||||||
|
var disabledCb = new CheckBox { IsChecked = true, IsEnabled = false };
|
||||||
|
disabledRow.Children.Add(disabledCb);
|
||||||
|
disabledRow.Children.Add(new Label { Text = "Disabled (checked)", VerticalOptions = LayoutOptions.Center, TextColor = Colors.Gray });
|
||||||
|
layout.Children.Add(disabledRow);
|
||||||
|
|
||||||
|
return layout;
|
||||||
|
}
|
||||||
|
|
||||||
|
private View CreateSwitchDemo()
|
||||||
|
{
|
||||||
|
var layout = new VerticalStackLayout { Spacing = 15 };
|
||||||
|
|
||||||
|
// Basic switch
|
||||||
|
var basicRow = new HorizontalStackLayout { Spacing = 15 };
|
||||||
|
var statusLabel = new Label { Text = "Off", VerticalOptions = LayoutOptions.Center, WidthRequest = 50 };
|
||||||
|
var sw1 = new Switch { IsToggled = false };
|
||||||
|
sw1.Toggled += (s, e) =>
|
||||||
|
{
|
||||||
|
statusLabel.Text = e.Value ? "On" : "Off";
|
||||||
|
LogEvent($"Switch toggled: {(e.Value ? "ON" : "OFF")}");
|
||||||
|
};
|
||||||
|
basicRow.Children.Add(sw1);
|
||||||
|
basicRow.Children.Add(statusLabel);
|
||||||
|
layout.Children.Add(basicRow);
|
||||||
|
|
||||||
|
// Colored switches
|
||||||
|
var colorRow = new HorizontalStackLayout { Spacing = 20 };
|
||||||
|
var switchColors = new[] { Colors.Green, Colors.Orange, Colors.Purple };
|
||||||
|
foreach (var color in switchColors)
|
||||||
|
{
|
||||||
|
var sw = new Switch { IsToggled = true, OnColor = color };
|
||||||
|
sw.Toggled += (s, e) => LogEvent($"{color} switch: {(e.Value ? "ON" : "OFF")}");
|
||||||
|
colorRow.Children.Add(sw);
|
||||||
|
}
|
||||||
|
layout.Children.Add(new Label { Text = "Colored Switches:", FontSize = 12 });
|
||||||
|
layout.Children.Add(colorRow);
|
||||||
|
|
||||||
|
// Disabled switch
|
||||||
|
var disabledRow = new HorizontalStackLayout { Spacing = 10 };
|
||||||
|
var disabledSw = new Switch { IsToggled = true, IsEnabled = false };
|
||||||
|
disabledRow.Children.Add(disabledSw);
|
||||||
|
disabledRow.Children.Add(new Label { Text = "Disabled (on)", VerticalOptions = LayoutOptions.Center, TextColor = Colors.Gray });
|
||||||
|
layout.Children.Add(disabledRow);
|
||||||
|
|
||||||
|
return layout;
|
||||||
|
}
|
||||||
|
|
||||||
|
private View CreateSliderDemo()
|
||||||
|
{
|
||||||
|
var layout = new VerticalStackLayout { Spacing = 15 };
|
||||||
|
|
||||||
|
// Basic slider
|
||||||
|
var valueLabel = new Label { Text = "Value: 50" };
|
||||||
|
var slider1 = new Slider { Minimum = 0, Maximum = 100, Value = 50 };
|
||||||
|
slider1.ValueChanged += (s, e) =>
|
||||||
|
{
|
||||||
|
valueLabel.Text = $"Value: {(int)e.NewValue}";
|
||||||
|
LogEvent($"Slider value: {(int)e.NewValue}");
|
||||||
|
};
|
||||||
|
layout.Children.Add(slider1);
|
||||||
|
layout.Children.Add(valueLabel);
|
||||||
|
|
||||||
|
// Slider with custom range
|
||||||
|
layout.Children.Add(new Label { Text = "Temperature (0-40°C):", FontSize = 12, Margin = new Thickness(0, 10, 0, 0) });
|
||||||
|
var tempLabel = new Label { Text = "20°C" };
|
||||||
|
var tempSlider = new Slider { Minimum = 0, Maximum = 40, Value = 20 };
|
||||||
|
tempSlider.ValueChanged += (s, e) =>
|
||||||
|
{
|
||||||
|
tempLabel.Text = $"{(int)e.NewValue}°C";
|
||||||
|
LogEvent($"Temperature: {(int)e.NewValue}°C");
|
||||||
|
};
|
||||||
|
layout.Children.Add(tempSlider);
|
||||||
|
layout.Children.Add(tempLabel);
|
||||||
|
|
||||||
|
// Colored slider
|
||||||
|
layout.Children.Add(new Label { Text = "Colored Slider:", FontSize = 12, Margin = new Thickness(0, 10, 0, 0) });
|
||||||
|
var colorSlider = new Slider
|
||||||
|
{
|
||||||
|
Minimum = 0,
|
||||||
|
Maximum = 100,
|
||||||
|
Value = 75,
|
||||||
|
MinimumTrackColor = Colors.Green,
|
||||||
|
MaximumTrackColor = Colors.LightGray,
|
||||||
|
ThumbColor = Colors.DarkGreen
|
||||||
|
};
|
||||||
|
colorSlider.ValueChanged += (s, e) => LogEvent($"Colored slider: {(int)e.NewValue}");
|
||||||
|
layout.Children.Add(colorSlider);
|
||||||
|
|
||||||
|
// Disabled slider
|
||||||
|
layout.Children.Add(new Label { Text = "Disabled Slider:", FontSize = 12, Margin = new Thickness(0, 10, 0, 0) });
|
||||||
|
var disabledSlider = new Slider { Minimum = 0, Maximum = 100, Value = 30, IsEnabled = false };
|
||||||
|
layout.Children.Add(disabledSlider);
|
||||||
|
|
||||||
|
return layout;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Frame CreateSection(string title, View content)
|
||||||
|
{
|
||||||
|
return new Frame
|
||||||
|
{
|
||||||
|
CornerRadius = 8,
|
||||||
|
Padding = new Thickness(15),
|
||||||
|
BackgroundColor = Colors.White,
|
||||||
|
Content = new VerticalStackLayout
|
||||||
|
{
|
||||||
|
Spacing = 10,
|
||||||
|
Children =
|
||||||
|
{
|
||||||
|
new Label { Text = title, FontSize = 16, FontAttributes = FontAttributes.Bold },
|
||||||
|
content
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private View CreateEventLogPanel()
|
||||||
|
{
|
||||||
|
return new Frame
|
||||||
|
{
|
||||||
|
BackgroundColor = Color.FromArgb("#F5F5F5"),
|
||||||
|
Padding = new Thickness(10),
|
||||||
|
CornerRadius = 0,
|
||||||
|
Content = new VerticalStackLayout
|
||||||
|
{
|
||||||
|
Children =
|
||||||
|
{
|
||||||
|
new Label { Text = "Event Log:", FontSize = 12, FontAttributes = FontAttributes.Bold },
|
||||||
|
new ScrollView
|
||||||
|
{
|
||||||
|
HeightRequest = 80,
|
||||||
|
Content = _eventLog
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private void LogEvent(string message)
|
||||||
|
{
|
||||||
|
_eventCount++;
|
||||||
|
var timestamp = DateTime.Now.ToString("HH:mm:ss");
|
||||||
|
_eventLog.Text = $"[{timestamp}] {_eventCount}. {message}\n{_eventLog.Text}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,166 @@
|
||||||
|
// TextInputPage - Demonstrates text input controls
|
||||||
|
|
||||||
|
using Microsoft.Maui.Controls;
|
||||||
|
using Microsoft.Maui.Graphics;
|
||||||
|
|
||||||
|
namespace ShellDemo;
|
||||||
|
|
||||||
|
public class TextInputPage : ContentPage
|
||||||
|
{
|
||||||
|
private Label _entryOutput;
|
||||||
|
private Label _searchOutput;
|
||||||
|
private Label _editorOutput;
|
||||||
|
|
||||||
|
public TextInputPage()
|
||||||
|
{
|
||||||
|
Title = "Text Input";
|
||||||
|
|
||||||
|
_entryOutput = new Label { TextColor = Colors.Gray, FontSize = 12 };
|
||||||
|
_searchOutput = new Label { TextColor = Colors.Gray, FontSize = 12 };
|
||||||
|
_editorOutput = new Label { TextColor = Colors.Gray, FontSize = 12 };
|
||||||
|
|
||||||
|
Content = new ScrollView
|
||||||
|
{
|
||||||
|
Content = new VerticalStackLayout
|
||||||
|
{
|
||||||
|
Padding = new Thickness(20),
|
||||||
|
Spacing = 15,
|
||||||
|
Children =
|
||||||
|
{
|
||||||
|
new Label
|
||||||
|
{
|
||||||
|
Text = "Text Input Controls",
|
||||||
|
FontSize = 24,
|
||||||
|
FontAttributes = FontAttributes.Bold
|
||||||
|
},
|
||||||
|
new Label
|
||||||
|
{
|
||||||
|
Text = "Click on any field and start typing. All keyboard input is handled by the framework.",
|
||||||
|
FontSize = 14,
|
||||||
|
TextColor = Colors.Gray
|
||||||
|
},
|
||||||
|
|
||||||
|
// Entry Section
|
||||||
|
new BoxView { HeightRequest = 1, Color = Colors.LightGray },
|
||||||
|
new Label { Text = "Entry (Single Line)", FontSize = 18, FontAttributes = FontAttributes.Bold },
|
||||||
|
CreateEntry("Enter your name...", e => _entryOutput.Text = $"You typed: {e.Text}"),
|
||||||
|
_entryOutput,
|
||||||
|
|
||||||
|
CreateEntry("Enter your email...", null, Keyboard.Email),
|
||||||
|
new Label { Text = "Email keyboard type", FontSize = 12, TextColor = Colors.Gray },
|
||||||
|
|
||||||
|
CreatePasswordEntry("Enter password..."),
|
||||||
|
new Label { Text = "Password field (text hidden)", FontSize = 12, TextColor = Colors.Gray },
|
||||||
|
|
||||||
|
// SearchBar Section
|
||||||
|
new BoxView { HeightRequest = 1, Color = Colors.LightGray },
|
||||||
|
new Label { Text = "SearchBar", FontSize = 18, FontAttributes = FontAttributes.Bold },
|
||||||
|
CreateSearchBar(),
|
||||||
|
_searchOutput,
|
||||||
|
|
||||||
|
// Editor Section
|
||||||
|
new BoxView { HeightRequest = 1, Color = Colors.LightGray },
|
||||||
|
new Label { Text = "Editor (Multi-line)", FontSize = 18, FontAttributes = FontAttributes.Bold },
|
||||||
|
CreateEditor(),
|
||||||
|
_editorOutput,
|
||||||
|
|
||||||
|
// Instructions
|
||||||
|
new BoxView { HeightRequest = 1, Color = Colors.LightGray },
|
||||||
|
new Frame
|
||||||
|
{
|
||||||
|
BackgroundColor = Color.FromArgb("#E3F2FD"),
|
||||||
|
CornerRadius = 8,
|
||||||
|
Padding = new Thickness(15),
|
||||||
|
Content = new VerticalStackLayout
|
||||||
|
{
|
||||||
|
Spacing = 5,
|
||||||
|
Children =
|
||||||
|
{
|
||||||
|
new Label
|
||||||
|
{
|
||||||
|
Text = "Keyboard Shortcuts",
|
||||||
|
FontAttributes = FontAttributes.Bold
|
||||||
|
},
|
||||||
|
new Label { Text = "Ctrl+A: Select all" },
|
||||||
|
new Label { Text = "Ctrl+C: Copy" },
|
||||||
|
new Label { Text = "Ctrl+V: Paste" },
|
||||||
|
new Label { Text = "Ctrl+X: Cut" },
|
||||||
|
new Label { Text = "Home/End: Move to start/end" },
|
||||||
|
new Label { Text = "Shift+Arrow: Select text" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private Entry CreateEntry(string placeholder, Action<Entry>? onTextChanged, Keyboard? keyboard = null)
|
||||||
|
{
|
||||||
|
var entry = new Entry
|
||||||
|
{
|
||||||
|
Placeholder = placeholder,
|
||||||
|
FontSize = 14
|
||||||
|
};
|
||||||
|
|
||||||
|
if (keyboard != null)
|
||||||
|
{
|
||||||
|
entry.Keyboard = keyboard;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (onTextChanged != null)
|
||||||
|
{
|
||||||
|
entry.TextChanged += (s, e) => onTextChanged(entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
return entry;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Entry CreatePasswordEntry(string placeholder)
|
||||||
|
{
|
||||||
|
return new Entry
|
||||||
|
{
|
||||||
|
Placeholder = placeholder,
|
||||||
|
FontSize = 14,
|
||||||
|
IsPassword = true
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private SearchBar CreateSearchBar()
|
||||||
|
{
|
||||||
|
var searchBar = new SearchBar
|
||||||
|
{
|
||||||
|
Placeholder = "Search for items..."
|
||||||
|
};
|
||||||
|
|
||||||
|
searchBar.TextChanged += (s, e) =>
|
||||||
|
{
|
||||||
|
_searchOutput.Text = $"Searching: {e.NewTextValue}";
|
||||||
|
};
|
||||||
|
|
||||||
|
searchBar.SearchButtonPressed += (s, e) =>
|
||||||
|
{
|
||||||
|
_searchOutput.Text = $"Search submitted: {searchBar.Text}";
|
||||||
|
};
|
||||||
|
|
||||||
|
return searchBar;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Editor CreateEditor()
|
||||||
|
{
|
||||||
|
var editor = new Editor
|
||||||
|
{
|
||||||
|
Placeholder = "Enter multiple lines of text here...\nPress Enter to create new lines.",
|
||||||
|
HeightRequest = 120,
|
||||||
|
FontSize = 14
|
||||||
|
};
|
||||||
|
|
||||||
|
editor.TextChanged += (s, e) =>
|
||||||
|
{
|
||||||
|
var lineCount = string.IsNullOrEmpty(e.NewTextValue) ? 0 : e.NewTextValue.Split('\n').Length;
|
||||||
|
_editorOutput.Text = $"Lines: {lineCount}, Characters: {e.NewTextValue?.Length ?? 0}";
|
||||||
|
};
|
||||||
|
|
||||||
|
return editor;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
// Platforms/Linux/Program.cs - Linux platform entry point
|
||||||
|
// Same pattern as Android's MainActivity or iOS's AppDelegate
|
||||||
|
|
||||||
|
using Microsoft.Maui.Platform.Linux;
|
||||||
|
using Microsoft.Maui.Platform.Linux.Hosting;
|
||||||
|
|
||||||
|
namespace ShellDemo;
|
||||||
|
|
||||||
|
class Program
|
||||||
|
{
|
||||||
|
static void Main(string[] args)
|
||||||
|
{
|
||||||
|
// Create the shared MAUI app
|
||||||
|
var app = MauiProgram.CreateMauiApp();
|
||||||
|
|
||||||
|
// Run on Linux platform
|
||||||
|
LinuxApplication.Run(app, args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,157 @@
|
||||||
|
# ShellDemo Sample
|
||||||
|
|
||||||
|
A comprehensive control showcase application demonstrating all OpenMaui Linux controls with Shell navigation and flyout menu.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- **Shell Navigation** - Flyout menu with multiple pages
|
||||||
|
- **Route-Based Navigation** - Push navigation with registered routes
|
||||||
|
- **All Core Controls** - Button, Entry, Editor, CheckBox, Switch, Slider, Picker, etc.
|
||||||
|
- **CollectionView** - Lists with selection and data binding
|
||||||
|
- **Progress Indicators** - ProgressBar and ActivityIndicator with animations
|
||||||
|
- **Grid Layouts** - Complex multi-column/row layouts
|
||||||
|
- **Event Logging** - Real-time event feedback panel
|
||||||
|
|
||||||
|
## Pages
|
||||||
|
|
||||||
|
| Page | Controls Demonstrated |
|
||||||
|
|------|----------------------|
|
||||||
|
| **Home** | Welcome screen, navigation overview |
|
||||||
|
| **Buttons** | Button styles, colors, states, click/press/release events |
|
||||||
|
| **Text Input** | Entry, Editor, SearchBar, password fields, keyboard types |
|
||||||
|
| **Selection** | CheckBox, Switch, Slider with colors and states |
|
||||||
|
| **Pickers** | Picker, DatePicker, TimePicker with styling |
|
||||||
|
| **Lists** | CollectionView with selection, custom items |
|
||||||
|
| **Progress** | ProgressBar, ActivityIndicator, animated demos |
|
||||||
|
| **Grids** | Grid layouts with row/column definitions |
|
||||||
|
| **About** | App information |
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
```
|
||||||
|
ShellDemo/
|
||||||
|
├── App.cs # AppShell definition with flyout
|
||||||
|
├── Program.cs # Linux platform bootstrap
|
||||||
|
├── MauiProgram.cs # MAUI app builder
|
||||||
|
└── Pages/
|
||||||
|
├── HomePage.cs # Welcome page
|
||||||
|
├── ButtonsPage.cs # Button demonstrations
|
||||||
|
├── TextInputPage.cs # Entry, Editor, SearchBar
|
||||||
|
├── SelectionPage.cs # CheckBox, Switch, Slider
|
||||||
|
├── PickersPage.cs # Picker, DatePicker, TimePicker
|
||||||
|
├── ListsPage.cs # CollectionView demos
|
||||||
|
├── ProgressPage.cs # ProgressBar, ActivityIndicator
|
||||||
|
├── GridsPage.cs # Grid layout demos
|
||||||
|
├── DetailPage.cs # Push navigation target
|
||||||
|
└── AboutPage.cs # About information
|
||||||
|
```
|
||||||
|
|
||||||
|
## Shell Configuration
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
public class AppShell : Shell
|
||||||
|
{
|
||||||
|
public AppShell()
|
||||||
|
{
|
||||||
|
FlyoutBehavior = FlyoutBehavior.Flyout;
|
||||||
|
Title = "OpenMaui Controls Demo";
|
||||||
|
|
||||||
|
// Register routes for push navigation
|
||||||
|
Routing.RegisterRoute("detail", typeof(DetailPage));
|
||||||
|
|
||||||
|
// Add flyout items
|
||||||
|
Items.Add(CreateFlyoutItem("Home", typeof(HomePage)));
|
||||||
|
Items.Add(CreateFlyoutItem("Buttons", typeof(ButtonsPage)));
|
||||||
|
// ...more items
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Control Demonstrations
|
||||||
|
|
||||||
|
### Buttons Page
|
||||||
|
- Default, styled, and transparent buttons
|
||||||
|
- Color variations (Primary, Success, Warning, Danger)
|
||||||
|
- Enabled/disabled state toggling
|
||||||
|
- Wide, tall, and round button shapes
|
||||||
|
- Pressed, clicked, released event handling
|
||||||
|
|
||||||
|
### Text Input Page
|
||||||
|
- Entry with placeholder and text change events
|
||||||
|
- Password entry with hidden text
|
||||||
|
- Email keyboard type
|
||||||
|
- SearchBar with search button
|
||||||
|
- Multi-line Editor
|
||||||
|
- Keyboard shortcuts guide
|
||||||
|
|
||||||
|
### Selection Page
|
||||||
|
- CheckBox with colors and disabled state
|
||||||
|
- Switch with OnColor customization
|
||||||
|
- Slider with min/max range and track colors
|
||||||
|
|
||||||
|
### Pickers Page
|
||||||
|
- Picker with items and selection events
|
||||||
|
- DatePicker with date range limits
|
||||||
|
- TimePicker with time selection
|
||||||
|
- Styled pickers with custom colors
|
||||||
|
|
||||||
|
### Lists Page
|
||||||
|
- CollectionView with string items
|
||||||
|
- CollectionView with custom data types (ColorItem, ContactItem)
|
||||||
|
- Selection handling and event feedback
|
||||||
|
|
||||||
|
### Progress Page
|
||||||
|
- ProgressBar at various percentages
|
||||||
|
- Colored progress bars
|
||||||
|
- ActivityIndicator running/stopped states
|
||||||
|
- Colored activity indicators
|
||||||
|
- Interactive slider-controlled progress
|
||||||
|
- Animated progress simulation
|
||||||
|
|
||||||
|
## Building and Running
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# From the maui-linux-push directory
|
||||||
|
cd samples/ShellDemo
|
||||||
|
dotnet publish -c Release -r linux-arm64
|
||||||
|
|
||||||
|
# Run on Linux
|
||||||
|
./bin/Release/net9.0/linux-arm64/publish/ShellDemo
|
||||||
|
```
|
||||||
|
|
||||||
|
## Event Logging
|
||||||
|
|
||||||
|
Each page features an event log panel that displays control interactions in real-time:
|
||||||
|
|
||||||
|
```
|
||||||
|
[14:32:15] 3. Button clicked: Primary
|
||||||
|
[14:32:12] 2. Slider value: 75
|
||||||
|
[14:32:08] 1. CheckBox: Checked
|
||||||
|
```
|
||||||
|
|
||||||
|
## Controls Reference
|
||||||
|
|
||||||
|
| Control | Properties Demonstrated |
|
||||||
|
|---------|------------------------|
|
||||||
|
| Button | Text, BackgroundColor, TextColor, CornerRadius, IsEnabled, WidthRequest, HeightRequest |
|
||||||
|
| Entry | Placeholder, Text, IsPassword, Keyboard, FontSize |
|
||||||
|
| Editor | Placeholder, Text, HeightRequest |
|
||||||
|
| SearchBar | Placeholder, Text, SearchButtonPressed |
|
||||||
|
| CheckBox | IsChecked, Color, IsEnabled |
|
||||||
|
| Switch | IsToggled, OnColor, IsEnabled |
|
||||||
|
| Slider | Minimum, Maximum, Value, MinimumTrackColor, MaximumTrackColor, ThumbColor |
|
||||||
|
| Picker | Title, Items, SelectedIndex, TextColor, TitleColor |
|
||||||
|
| DatePicker | Date, MinimumDate, MaximumDate, TextColor |
|
||||||
|
| TimePicker | Time, TextColor |
|
||||||
|
| CollectionView | ItemsSource, SelectionMode, SelectionChanged, HeightRequest |
|
||||||
|
| ProgressBar | Progress, ProgressColor |
|
||||||
|
| ActivityIndicator | IsRunning, Color |
|
||||||
|
| Label | Text, FontSize, FontAttributes, TextColor |
|
||||||
|
| Frame | CornerRadius, Padding, BackgroundColor |
|
||||||
|
| Grid | RowDefinitions, ColumnDefinitions, RowSpacing, ColumnSpacing |
|
||||||
|
| StackLayout | Spacing, Padding, Orientation |
|
||||||
|
| ScrollView | Content scrolling |
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
MIT License - See repository root for details.
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<OutputType>Exe</OutputType>
|
||||||
|
<TargetFramework>net9.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="OpenMaui.Controls.Linux" Version="1.0.0-preview.*" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
// TodoApp - Main Application with NavigationPage
|
||||||
|
|
||||||
|
using Microsoft.Maui.Controls;
|
||||||
|
|
||||||
|
namespace TodoApp;
|
||||||
|
|
||||||
|
public class App : Application
|
||||||
|
{
|
||||||
|
public static NavigationPage? NavigationPage { get; private set; }
|
||||||
|
|
||||||
|
public App()
|
||||||
|
{
|
||||||
|
NavigationPage = new NavigationPage(new TodoListPage())
|
||||||
|
{
|
||||||
|
Title = "OpenMaui Todo App",
|
||||||
|
BarBackgroundColor = Color.FromArgb("#2196F3"),
|
||||||
|
BarTextColor = Colors.White
|
||||||
|
};
|
||||||
|
MainPage = NavigationPage;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
// MauiProgram.cs - MAUI app configuration
|
||||||
|
|
||||||
|
using Microsoft.Maui.Hosting;
|
||||||
|
using Microsoft.Maui.Platform.Linux.Hosting;
|
||||||
|
|
||||||
|
namespace TodoApp;
|
||||||
|
|
||||||
|
public static class MauiProgram
|
||||||
|
{
|
||||||
|
public static MauiApp CreateMauiApp()
|
||||||
|
{
|
||||||
|
var builder = MauiApp.CreateBuilder();
|
||||||
|
|
||||||
|
// Configure the app
|
||||||
|
builder.UseMauiApp<App>();
|
||||||
|
|
||||||
|
// Add Linux platform support with all handlers
|
||||||
|
builder.UseLinux();
|
||||||
|
|
||||||
|
return builder.Build();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,79 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8" ?>
|
||||||
|
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||||
|
x:Class="TodoApp.NewTodoPage"
|
||||||
|
Title="New Task"
|
||||||
|
BackgroundColor="#F5F7FA">
|
||||||
|
|
||||||
|
<ContentPage.Resources>
|
||||||
|
<Color x:Key="PrimaryColor">#5C6BC0</Color>
|
||||||
|
<Color x:Key="AccentColor">#26A69A</Color>
|
||||||
|
<Color x:Key="TextPrimary">#212121</Color>
|
||||||
|
<Color x:Key="TextSecondary">#757575</Color>
|
||||||
|
<Color x:Key="CardBackground">#FFFFFF</Color>
|
||||||
|
<Color x:Key="BorderColor">#E8EAF6</Color>
|
||||||
|
</ContentPage.Resources>
|
||||||
|
|
||||||
|
<ContentPage.ToolbarItems>
|
||||||
|
<ToolbarItem Text="Save" Clicked="OnSaveClicked" />
|
||||||
|
</ContentPage.ToolbarItems>
|
||||||
|
|
||||||
|
<Grid RowDefinitions="Auto,Auto,Auto,*" RowSpacing="16" Padding="20">
|
||||||
|
|
||||||
|
<!-- Header -->
|
||||||
|
<VerticalStackLayout Grid.Row="0" Spacing="4">
|
||||||
|
<Label Text="Create a new task"
|
||||||
|
FontSize="24"
|
||||||
|
TextColor="{StaticResource TextPrimary}" />
|
||||||
|
<Label Text="Fill in the details below"
|
||||||
|
FontSize="14"
|
||||||
|
TextColor="{StaticResource TextSecondary}" />
|
||||||
|
</VerticalStackLayout>
|
||||||
|
|
||||||
|
<!-- Title Section -->
|
||||||
|
<VerticalStackLayout Grid.Row="1" Spacing="8">
|
||||||
|
<Label Text="TITLE"
|
||||||
|
FontSize="13"
|
||||||
|
FontAttributes="Bold"
|
||||||
|
TextColor="{StaticResource PrimaryColor}" />
|
||||||
|
<Border BackgroundColor="{StaticResource CardBackground}"
|
||||||
|
Stroke="{StaticResource BorderColor}"
|
||||||
|
StrokeThickness="1"
|
||||||
|
Padding="16,12">
|
||||||
|
<Border.StrokeShape>
|
||||||
|
<RoundRectangle CornerRadius="10" />
|
||||||
|
</Border.StrokeShape>
|
||||||
|
<Entry x:Name="TitleEntry"
|
||||||
|
Placeholder="What needs to be done?"
|
||||||
|
FontSize="18"
|
||||||
|
TextColor="{StaticResource TextPrimary}"
|
||||||
|
PlaceholderColor="{StaticResource TextSecondary}" />
|
||||||
|
</Border>
|
||||||
|
</VerticalStackLayout>
|
||||||
|
|
||||||
|
<!-- Notes Label -->
|
||||||
|
<Label Grid.Row="2"
|
||||||
|
Text="NOTES"
|
||||||
|
FontSize="13"
|
||||||
|
FontAttributes="Bold"
|
||||||
|
TextColor="{StaticResource PrimaryColor}" />
|
||||||
|
|
||||||
|
<!-- Notes Section (fills remaining space) -->
|
||||||
|
<Border Grid.Row="3"
|
||||||
|
BackgroundColor="{StaticResource CardBackground}"
|
||||||
|
Stroke="{StaticResource BorderColor}"
|
||||||
|
StrokeThickness="1"
|
||||||
|
Padding="16,12">
|
||||||
|
<Border.StrokeShape>
|
||||||
|
<RoundRectangle CornerRadius="10" />
|
||||||
|
</Border.StrokeShape>
|
||||||
|
<Editor x:Name="NotesEditor"
|
||||||
|
Placeholder="Add notes (optional)..."
|
||||||
|
FontSize="14"
|
||||||
|
TextColor="{StaticResource TextPrimary}"
|
||||||
|
PlaceholderColor="{StaticResource TextSecondary}"
|
||||||
|
VerticalOptions="Fill" />
|
||||||
|
</Border>
|
||||||
|
|
||||||
|
</Grid>
|
||||||
|
</ContentPage>
|
||||||
|
|
@ -0,0 +1,31 @@
|
||||||
|
// NewTodoPage - Create a new todo item
|
||||||
|
|
||||||
|
using Microsoft.Maui.Controls;
|
||||||
|
using Microsoft.Maui.Graphics;
|
||||||
|
|
||||||
|
namespace TodoApp;
|
||||||
|
|
||||||
|
public partial class NewTodoPage : ContentPage
|
||||||
|
{
|
||||||
|
private readonly TodoService _service = TodoService.Instance;
|
||||||
|
|
||||||
|
public NewTodoPage()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void OnSaveClicked(object? sender, EventArgs e)
|
||||||
|
{
|
||||||
|
var title = TitleEntry.Text?.Trim();
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(title))
|
||||||
|
{
|
||||||
|
TitleEntry.Placeholder = "Title is required!";
|
||||||
|
TitleEntry.PlaceholderColor = Colors.Red;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_service.AddTodo(title, NotesEditor.Text ?? "");
|
||||||
|
await Navigation.PopAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,67 @@
|
||||||
|
// Program.cs - Linux platform entry point
|
||||||
|
|
||||||
|
using Microsoft.Maui.Platform.Linux;
|
||||||
|
|
||||||
|
namespace TodoApp;
|
||||||
|
|
||||||
|
class Program
|
||||||
|
{
|
||||||
|
static void Main(string[] args)
|
||||||
|
{
|
||||||
|
// Redirect console output to a log file for debugging
|
||||||
|
var logPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), "todoapp.log");
|
||||||
|
using var logWriter = new StreamWriter(logPath, append: false) { AutoFlush = true };
|
||||||
|
var multiWriter = new MultiTextWriter(Console.Out, logWriter);
|
||||||
|
Console.SetOut(multiWriter);
|
||||||
|
Console.SetError(multiWriter);
|
||||||
|
|
||||||
|
// Global exception handler
|
||||||
|
AppDomain.CurrentDomain.UnhandledException += (sender, e) =>
|
||||||
|
{
|
||||||
|
var ex = e.ExceptionObject as Exception;
|
||||||
|
Console.WriteLine($"[FATAL] Unhandled exception: {ex?.GetType().Name}: {ex?.Message}");
|
||||||
|
Console.WriteLine($"[FATAL] Stack trace: {ex?.StackTrace}");
|
||||||
|
if (ex?.InnerException != null)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"[FATAL] Inner exception: {ex.InnerException.GetType().Name}: {ex.InnerException.Message}");
|
||||||
|
Console.WriteLine($"[FATAL] Inner stack trace: {ex.InnerException.StackTrace}");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
TaskScheduler.UnobservedTaskException += (sender, e) =>
|
||||||
|
{
|
||||||
|
Console.WriteLine($"[FATAL] Unobserved task exception: {e.Exception?.GetType().Name}: {e.Exception?.Message}");
|
||||||
|
Console.WriteLine($"[FATAL] Stack trace: {e.Exception?.StackTrace}");
|
||||||
|
e.SetObserved(); // Prevent crash
|
||||||
|
};
|
||||||
|
|
||||||
|
Console.WriteLine($"[Program] Starting TodoApp at {DateTime.Now}");
|
||||||
|
Console.WriteLine($"[Program] Log file: {logPath}");
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Create the MAUI app with all handlers registered
|
||||||
|
var app = MauiProgram.CreateMauiApp();
|
||||||
|
|
||||||
|
// Run on Linux platform
|
||||||
|
LinuxApplication.Run(app, args);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"[FATAL] Exception in Main: {ex.GetType().Name}: {ex.Message}");
|
||||||
|
Console.WriteLine($"[FATAL] Stack trace: {ex.StackTrace}");
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper to write to both console and file
|
||||||
|
class MultiTextWriter : TextWriter
|
||||||
|
{
|
||||||
|
private readonly TextWriter[] _writers;
|
||||||
|
public MultiTextWriter(params TextWriter[] writers) => _writers = writers;
|
||||||
|
public override System.Text.Encoding Encoding => System.Text.Encoding.UTF8;
|
||||||
|
public override void Write(char value) { foreach (var w in _writers) w.Write(value); }
|
||||||
|
public override void WriteLine(string? value) { foreach (var w in _writers) w.WriteLine(value); }
|
||||||
|
public override void Flush() { foreach (var w in _writers) w.Flush(); }
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,111 @@
|
||||||
|
# TodoApp Sample
|
||||||
|
|
||||||
|
A complete task management application demonstrating OpenMaui Linux capabilities with real-world XAML patterns.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- **NavigationPage** - Full page navigation with back button support
|
||||||
|
- **CollectionView** - Scrollable list with data binding and selection
|
||||||
|
- **XAML Data Binding** - Value converters for dynamic styling
|
||||||
|
- **DisplayAlert Dialogs** - Confirmation dialogs for delete actions
|
||||||
|
- **Grid Layouts** - Complex layouts with star sizing for expanding content
|
||||||
|
- **Entry & Editor** - Single and multi-line text input
|
||||||
|
- **Border with RoundRectangle** - Modern card-style UI
|
||||||
|
- **ToolbarItems** - Navigation bar actions
|
||||||
|
|
||||||
|
## Screenshots
|
||||||
|
|
||||||
|
The app consists of three pages:
|
||||||
|
|
||||||
|
1. **TodoListPage** - Shows all tasks with completion status indicators
|
||||||
|
2. **NewTodoPage** - Create a new task with title and notes
|
||||||
|
3. **TodoDetailPage** - View/edit task details, mark complete, or delete
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
```
|
||||||
|
TodoApp/
|
||||||
|
├── App.cs # Application entry with NavigationPage
|
||||||
|
├── Program.cs # Linux platform bootstrap
|
||||||
|
├── MauiProgram.cs # MAUI app builder
|
||||||
|
├── TodoItem.cs # Data model
|
||||||
|
├── TodoService.cs # In-memory data store
|
||||||
|
├── TodoListPage.xaml(.cs) # Main list view
|
||||||
|
├── NewTodoPage.xaml(.cs) # Create task page
|
||||||
|
└── TodoDetailPage.xaml(.cs) # Task detail/edit page
|
||||||
|
```
|
||||||
|
|
||||||
|
## XAML Highlights
|
||||||
|
|
||||||
|
### Value Converters
|
||||||
|
The app uses custom converters for dynamic styling based on completion status:
|
||||||
|
- `CompletedToColorConverter` - Gray text for completed items
|
||||||
|
- `CompletedToTextDecorationsConverter` - Strikethrough for completed items
|
||||||
|
- `CompletedToOpacityConverter` - Fade completed items
|
||||||
|
- `AlternatingRowColorConverter` - Alternating background colors
|
||||||
|
|
||||||
|
### ResourceDictionary
|
||||||
|
```xml
|
||||||
|
<ContentPage.Resources>
|
||||||
|
<Color x:Key="PrimaryColor">#5C6BC0</Color>
|
||||||
|
<Color x:Key="AccentColor">#26A69A</Color>
|
||||||
|
<Color x:Key="TextPrimary">#212121</Color>
|
||||||
|
</ContentPage.Resources>
|
||||||
|
```
|
||||||
|
|
||||||
|
### CollectionView with DataTemplate
|
||||||
|
```xml
|
||||||
|
<CollectionView SelectionMode="Single" SelectionChanged="OnSelectionChanged">
|
||||||
|
<CollectionView.ItemTemplate>
|
||||||
|
<DataTemplate x:DataType="local:TodoItem">
|
||||||
|
<Border BackgroundColor="{StaticResource CardBackground}">
|
||||||
|
<Label Text="{Binding Title}" />
|
||||||
|
</Border>
|
||||||
|
</DataTemplate>
|
||||||
|
</CollectionView.ItemTemplate>
|
||||||
|
</CollectionView>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Grid with Star Rows (Expanding Editor)
|
||||||
|
```xml
|
||||||
|
<Grid RowDefinitions="Auto,Auto,*,Auto,Auto" Padding="20">
|
||||||
|
<!-- Row 0: Title section (Auto) -->
|
||||||
|
<!-- Row 1: Notes label (Auto) -->
|
||||||
|
<!-- Row 2: Editor - expands to fill (*) -->
|
||||||
|
<!-- Row 3: Status section (Auto) -->
|
||||||
|
<!-- Row 4: Created date (Auto) -->
|
||||||
|
</Grid>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Building and Running
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# From the maui-linux-push directory
|
||||||
|
cd samples/TodoApp
|
||||||
|
dotnet publish -c Release -r linux-arm64
|
||||||
|
|
||||||
|
# Run on Linux
|
||||||
|
./bin/Release/net9.0/linux-arm64/publish/TodoApp
|
||||||
|
```
|
||||||
|
|
||||||
|
## Controls Demonstrated
|
||||||
|
|
||||||
|
| Control | Usage |
|
||||||
|
|---------|-------|
|
||||||
|
| NavigationPage | App navigation container |
|
||||||
|
| ContentPage | Individual screens |
|
||||||
|
| CollectionView | Task list with selection |
|
||||||
|
| Grid | Page layouts |
|
||||||
|
| VerticalStackLayout | Vertical grouping |
|
||||||
|
| HorizontalStackLayout | Horizontal grouping |
|
||||||
|
| Label | Text display |
|
||||||
|
| Entry | Single-line input |
|
||||||
|
| Editor | Multi-line input |
|
||||||
|
| Button | Toolbar actions |
|
||||||
|
| Border | Card styling with rounded corners |
|
||||||
|
| CheckBox | Completion toggle |
|
||||||
|
| BoxView | Visual separators |
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
MIT License - See repository root for details.
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<OutputType>Exe</OutputType>
|
||||||
|
<TargetFramework>net9.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="OpenMaui.Controls.Linux" Version="1.0.0-preview.*" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
|
|
@ -0,0 +1,106 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8" ?>
|
||||||
|
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||||
|
x:Class="TodoApp.TodoDetailPage"
|
||||||
|
Title="Task Details"
|
||||||
|
BackgroundColor="#F5F7FA">
|
||||||
|
|
||||||
|
<ContentPage.Resources>
|
||||||
|
<Color x:Key="PrimaryColor">#5C6BC0</Color>
|
||||||
|
<Color x:Key="AccentColor">#26A69A</Color>
|
||||||
|
<Color x:Key="DangerColor">#EF5350</Color>
|
||||||
|
<Color x:Key="TextPrimary">#212121</Color>
|
||||||
|
<Color x:Key="TextSecondary">#757575</Color>
|
||||||
|
<Color x:Key="CardBackground">#FFFFFF</Color>
|
||||||
|
<Color x:Key="BorderColor">#E8EAF6</Color>
|
||||||
|
</ContentPage.Resources>
|
||||||
|
|
||||||
|
<ContentPage.ToolbarItems>
|
||||||
|
<ToolbarItem Text="Delete" Clicked="OnDeleteClicked" />
|
||||||
|
<ToolbarItem Text="Save" Clicked="OnSaveClicked" />
|
||||||
|
</ContentPage.ToolbarItems>
|
||||||
|
|
||||||
|
<Grid RowDefinitions="Auto,Auto,*,Auto,Auto" RowSpacing="16" Padding="20">
|
||||||
|
|
||||||
|
<!-- Title Section -->
|
||||||
|
<VerticalStackLayout Grid.Row="0" Spacing="8">
|
||||||
|
<Label Text="TITLE"
|
||||||
|
FontSize="13"
|
||||||
|
FontAttributes="Bold"
|
||||||
|
TextColor="{StaticResource PrimaryColor}" />
|
||||||
|
<Border BackgroundColor="{StaticResource CardBackground}"
|
||||||
|
Stroke="{StaticResource BorderColor}"
|
||||||
|
StrokeThickness="1"
|
||||||
|
Padding="16,12">
|
||||||
|
<Border.StrokeShape>
|
||||||
|
<RoundRectangle CornerRadius="10" />
|
||||||
|
</Border.StrokeShape>
|
||||||
|
<Entry x:Name="TitleEntry"
|
||||||
|
Placeholder="Task title"
|
||||||
|
FontSize="18"
|
||||||
|
TextColor="{StaticResource TextPrimary}"
|
||||||
|
PlaceholderColor="{StaticResource TextSecondary}" />
|
||||||
|
</Border>
|
||||||
|
</VerticalStackLayout>
|
||||||
|
|
||||||
|
<!-- Notes Label -->
|
||||||
|
<Label Grid.Row="1"
|
||||||
|
Text="NOTES"
|
||||||
|
FontSize="13"
|
||||||
|
FontAttributes="Bold"
|
||||||
|
TextColor="{StaticResource PrimaryColor}" />
|
||||||
|
|
||||||
|
<!-- Notes Section (fills remaining space) -->
|
||||||
|
<Border Grid.Row="2"
|
||||||
|
BackgroundColor="{StaticResource CardBackground}"
|
||||||
|
Stroke="{StaticResource BorderColor}"
|
||||||
|
StrokeThickness="1"
|
||||||
|
Padding="16,12">
|
||||||
|
<Border.StrokeShape>
|
||||||
|
<RoundRectangle CornerRadius="10" />
|
||||||
|
</Border.StrokeShape>
|
||||||
|
<Editor x:Name="NotesEditor"
|
||||||
|
Placeholder="Add notes here..."
|
||||||
|
FontSize="14"
|
||||||
|
TextColor="{StaticResource TextPrimary}"
|
||||||
|
PlaceholderColor="{StaticResource TextSecondary}"
|
||||||
|
VerticalOptions="Fill" />
|
||||||
|
</Border>
|
||||||
|
|
||||||
|
<!-- Status Section -->
|
||||||
|
<VerticalStackLayout Grid.Row="3" Spacing="8">
|
||||||
|
<Label Text="STATUS"
|
||||||
|
FontSize="13"
|
||||||
|
FontAttributes="Bold"
|
||||||
|
TextColor="{StaticResource PrimaryColor}" />
|
||||||
|
<Border BackgroundColor="{StaticResource CardBackground}"
|
||||||
|
Stroke="{StaticResource BorderColor}"
|
||||||
|
StrokeThickness="1"
|
||||||
|
Padding="16,12">
|
||||||
|
<Border.StrokeShape>
|
||||||
|
<RoundRectangle CornerRadius="10" />
|
||||||
|
</Border.StrokeShape>
|
||||||
|
<HorizontalStackLayout Spacing="12">
|
||||||
|
<CheckBox x:Name="CompletedCheckBox"
|
||||||
|
Color="{StaticResource AccentColor}"
|
||||||
|
VerticalOptions="Center"
|
||||||
|
CheckedChanged="OnCompletedChanged" />
|
||||||
|
<Label x:Name="StatusLabel"
|
||||||
|
Text="In Progress"
|
||||||
|
FontSize="16"
|
||||||
|
TextColor="{StaticResource TextPrimary}"
|
||||||
|
VerticalOptions="Center" />
|
||||||
|
</HorizontalStackLayout>
|
||||||
|
</Border>
|
||||||
|
</VerticalStackLayout>
|
||||||
|
|
||||||
|
<!-- Created Date -->
|
||||||
|
<Label Grid.Row="4"
|
||||||
|
x:Name="CreatedLabel"
|
||||||
|
FontSize="13"
|
||||||
|
TextColor="{StaticResource TextSecondary}"
|
||||||
|
HorizontalOptions="Center"
|
||||||
|
HorizontalTextAlignment="Center" />
|
||||||
|
|
||||||
|
</Grid>
|
||||||
|
</ContentPage>
|
||||||
|
|
@ -0,0 +1,91 @@
|
||||||
|
// TodoDetailPage - View and edit a todo item
|
||||||
|
|
||||||
|
using Microsoft.Maui.Controls;
|
||||||
|
using Microsoft.Maui.Graphics;
|
||||||
|
using Microsoft.Maui.Platform;
|
||||||
|
|
||||||
|
namespace TodoApp;
|
||||||
|
|
||||||
|
public partial class TodoDetailPage : ContentPage
|
||||||
|
{
|
||||||
|
private readonly TodoItem _todo;
|
||||||
|
private readonly TodoService _service = TodoService.Instance;
|
||||||
|
|
||||||
|
// Colors for status label
|
||||||
|
private static readonly Color AccentColor = Color.FromArgb("#26A69A");
|
||||||
|
private static readonly Color TextPrimary = Color.FromArgb("#212121");
|
||||||
|
|
||||||
|
public TodoDetailPage(TodoItem todo)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Console.WriteLine($"[TodoDetailPage] Constructor starting for: {todo.Title}");
|
||||||
|
InitializeComponent();
|
||||||
|
Console.WriteLine($"[TodoDetailPage] InitializeComponent complete");
|
||||||
|
|
||||||
|
_todo = todo;
|
||||||
|
|
||||||
|
// Populate fields
|
||||||
|
Console.WriteLine($"[TodoDetailPage] Setting TitleEntry.Text");
|
||||||
|
TitleEntry.Text = _todo.Title;
|
||||||
|
Console.WriteLine($"[TodoDetailPage] Setting NotesEditor.Text");
|
||||||
|
NotesEditor.Text = _todo.Notes;
|
||||||
|
Console.WriteLine($"[TodoDetailPage] Setting CompletedCheckBox.IsChecked");
|
||||||
|
CompletedCheckBox.IsChecked = _todo.IsCompleted;
|
||||||
|
Console.WriteLine($"[TodoDetailPage] Calling UpdateStatusLabel");
|
||||||
|
UpdateStatusLabel(_todo.IsCompleted);
|
||||||
|
Console.WriteLine($"[TodoDetailPage] Setting CreatedLabel.Text");
|
||||||
|
CreatedLabel.Text = $"Created {_todo.CreatedAt:MMMM d, yyyy} at {_todo.CreatedAt:h:mm tt}";
|
||||||
|
Console.WriteLine($"[TodoDetailPage] Constructor complete");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"[TodoDetailPage] EXCEPTION in constructor: {ex.GetType().Name}: {ex.Message}");
|
||||||
|
Console.WriteLine($"[TodoDetailPage] Stack trace: {ex.StackTrace}");
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnCompletedChanged(object? sender, Microsoft.Maui.Controls.CheckedChangedEventArgs e)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"[TodoDetailPage] OnCompletedChanged: {e.Value}");
|
||||||
|
UpdateStatusLabel(e.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateStatusLabel(bool isCompleted)
|
||||||
|
{
|
||||||
|
if (StatusLabel == null)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"[TodoDetailPage] UpdateStatusLabel: StatusLabel is null, skipping");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Console.WriteLine($"[TodoDetailPage] UpdateStatusLabel: setting to {(isCompleted ? "Completed" : "In Progress")}");
|
||||||
|
StatusLabel.Text = isCompleted ? "Completed" : "In Progress";
|
||||||
|
StatusLabel.TextColor = isCompleted ? AccentColor : TextPrimary;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void OnSaveClicked(object? sender, EventArgs e)
|
||||||
|
{
|
||||||
|
_todo.Title = TitleEntry.Text ?? "";
|
||||||
|
_todo.Notes = NotesEditor.Text ?? "";
|
||||||
|
_todo.IsCompleted = CompletedCheckBox.IsChecked;
|
||||||
|
|
||||||
|
await Navigation.PopAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void OnDeleteClicked(object? sender, EventArgs e)
|
||||||
|
{
|
||||||
|
// Show confirmation dialog
|
||||||
|
var confirmed = await LinuxDialogService.ShowAlertAsync(
|
||||||
|
"Delete Task",
|
||||||
|
$"Are you sure you want to delete \"{_todo.Title}\"? This action cannot be undone.",
|
||||||
|
"Delete",
|
||||||
|
"Cancel");
|
||||||
|
|
||||||
|
if (confirmed)
|
||||||
|
{
|
||||||
|
_service.DeleteTodo(_todo);
|
||||||
|
await Navigation.PopAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,81 @@
|
||||||
|
// TodoItem - Data model for a todo item
|
||||||
|
|
||||||
|
using System.ComponentModel;
|
||||||
|
|
||||||
|
namespace TodoApp;
|
||||||
|
|
||||||
|
public class TodoItem : INotifyPropertyChanged
|
||||||
|
{
|
||||||
|
private string _title = "";
|
||||||
|
private string _notes = "";
|
||||||
|
private bool _isCompleted;
|
||||||
|
private DateTime _dueDate;
|
||||||
|
|
||||||
|
public int Id { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Index in the collection for alternating row colors.
|
||||||
|
/// </summary>
|
||||||
|
public int Index { get; set; }
|
||||||
|
|
||||||
|
public string Title
|
||||||
|
{
|
||||||
|
get => _title;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (_title != value)
|
||||||
|
{
|
||||||
|
_title = value;
|
||||||
|
OnPropertyChanged(nameof(Title));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Notes
|
||||||
|
{
|
||||||
|
get => _notes;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (_notes != value)
|
||||||
|
{
|
||||||
|
_notes = value;
|
||||||
|
OnPropertyChanged(nameof(Notes));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsCompleted
|
||||||
|
{
|
||||||
|
get => _isCompleted;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (_isCompleted != value)
|
||||||
|
{
|
||||||
|
_isCompleted = value;
|
||||||
|
OnPropertyChanged(nameof(IsCompleted));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public DateTime DueDate
|
||||||
|
{
|
||||||
|
get => _dueDate;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (_dueDate != value)
|
||||||
|
{
|
||||||
|
_dueDate = value;
|
||||||
|
OnPropertyChanged(nameof(DueDate));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public DateTime CreatedAt { get; set; } = DateTime.Now;
|
||||||
|
|
||||||
|
public event PropertyChangedEventHandler? PropertyChanged;
|
||||||
|
|
||||||
|
protected void OnPropertyChanged(string propertyName)
|
||||||
|
{
|
||||||
|
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,129 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8" ?>
|
||||||
|
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||||
|
xmlns:local="clr-namespace:TodoApp"
|
||||||
|
x:Class="TodoApp.TodoListPage"
|
||||||
|
Title="My Tasks"
|
||||||
|
BackgroundColor="#F5F7FA">
|
||||||
|
|
||||||
|
<ContentPage.Resources>
|
||||||
|
<ResourceDictionary>
|
||||||
|
<!-- Colors -->
|
||||||
|
<Color x:Key="PrimaryColor">#5C6BC0</Color>
|
||||||
|
<Color x:Key="PrimaryDark">#3949AB</Color>
|
||||||
|
<Color x:Key="AccentColor">#26A69A</Color>
|
||||||
|
<Color x:Key="TextPrimary">#212121</Color>
|
||||||
|
<Color x:Key="TextSecondary">#757575</Color>
|
||||||
|
<Color x:Key="CardBackground">#FFFFFF</Color>
|
||||||
|
<Color x:Key="DividerColor">#E0E0E0</Color>
|
||||||
|
<Color x:Key="CompletedColor">#9E9E9E</Color>
|
||||||
|
|
||||||
|
<!-- Converters -->
|
||||||
|
<local:AlternatingRowColorConverter x:Key="AlternatingRowColorConverter" />
|
||||||
|
<local:CompletedToColorConverter x:Key="CompletedToColorConverter" />
|
||||||
|
<local:CompletedToTextDecorationsConverter x:Key="CompletedToTextDecorationsConverter" />
|
||||||
|
<local:CompletedToOpacityConverter x:Key="CompletedToOpacityConverter" />
|
||||||
|
</ResourceDictionary>
|
||||||
|
</ContentPage.Resources>
|
||||||
|
|
||||||
|
<ContentPage.ToolbarItems>
|
||||||
|
<ToolbarItem Text="+ Add" Clicked="OnAddClicked" />
|
||||||
|
</ContentPage.ToolbarItems>
|
||||||
|
|
||||||
|
<Grid RowDefinitions="*,Auto" Padding="0">
|
||||||
|
|
||||||
|
<!-- Task List -->
|
||||||
|
<CollectionView Grid.Row="0"
|
||||||
|
x:Name="TodoCollectionView"
|
||||||
|
SelectionMode="Single"
|
||||||
|
SelectionChanged="OnSelectionChanged"
|
||||||
|
VerticalOptions="FillAndExpand"
|
||||||
|
BackgroundColor="Transparent"
|
||||||
|
Margin="16,16,16,0">
|
||||||
|
|
||||||
|
<CollectionView.EmptyView>
|
||||||
|
<VerticalStackLayout VerticalOptions="Center"
|
||||||
|
HorizontalOptions="Center"
|
||||||
|
Padding="40">
|
||||||
|
<Label Text="No tasks yet"
|
||||||
|
FontSize="22"
|
||||||
|
TextColor="{StaticResource TextSecondary}"
|
||||||
|
HorizontalOptions="Center" />
|
||||||
|
<Label Text="Tap '+ Add' to create your first task"
|
||||||
|
FontSize="14"
|
||||||
|
TextColor="{StaticResource TextSecondary}"
|
||||||
|
HorizontalOptions="Center"
|
||||||
|
Margin="0,8,0,0" />
|
||||||
|
</VerticalStackLayout>
|
||||||
|
</CollectionView.EmptyView>
|
||||||
|
|
||||||
|
<CollectionView.ItemTemplate>
|
||||||
|
<DataTemplate x:DataType="local:TodoItem">
|
||||||
|
<!-- Card-style item -->
|
||||||
|
<Grid Padding="0,6" BackgroundColor="Transparent">
|
||||||
|
<Border StrokeThickness="0"
|
||||||
|
BackgroundColor="{StaticResource CardBackground}"
|
||||||
|
Padding="16,14"
|
||||||
|
Opacity="{Binding IsCompleted, Converter={StaticResource CompletedToOpacityConverter}}">
|
||||||
|
<Border.StrokeShape>
|
||||||
|
<RoundRectangle CornerRadius="12" />
|
||||||
|
</Border.StrokeShape>
|
||||||
|
|
||||||
|
<Grid ColumnDefinitions="Auto,*" ColumnSpacing="16">
|
||||||
|
<!-- Completion indicator -->
|
||||||
|
<Border Grid.Column="0"
|
||||||
|
WidthRequest="8"
|
||||||
|
HeightRequest="44"
|
||||||
|
Margin="0"
|
||||||
|
BackgroundColor="{Binding IsCompleted, Converter={StaticResource CompletedToColorConverter}, ConverterParameter=indicator}"
|
||||||
|
VerticalOptions="Center">
|
||||||
|
<Border.StrokeShape>
|
||||||
|
<RoundRectangle CornerRadius="4" />
|
||||||
|
</Border.StrokeShape>
|
||||||
|
</Border>
|
||||||
|
|
||||||
|
<!-- Content -->
|
||||||
|
<VerticalStackLayout Grid.Column="1"
|
||||||
|
Spacing="4"
|
||||||
|
VerticalOptions="Center">
|
||||||
|
<!-- Title -->
|
||||||
|
<Label Text="{Binding Title}"
|
||||||
|
FontSize="16"
|
||||||
|
TextColor="{Binding IsCompleted, Converter={StaticResource CompletedToColorConverter}}"
|
||||||
|
TextDecorations="{Binding IsCompleted, Converter={StaticResource CompletedToTextDecorationsConverter}}" />
|
||||||
|
|
||||||
|
<!-- Notes preview -->
|
||||||
|
<Label Text="{Binding Notes}"
|
||||||
|
FontSize="13"
|
||||||
|
TextColor="{Binding IsCompleted, Converter={StaticResource CompletedToColorConverter}, ConverterParameter=notes}"
|
||||||
|
MaxLines="2"
|
||||||
|
LineBreakMode="TailTruncation" />
|
||||||
|
</VerticalStackLayout>
|
||||||
|
</Grid>
|
||||||
|
</Border>
|
||||||
|
</Grid>
|
||||||
|
</DataTemplate>
|
||||||
|
</CollectionView.ItemTemplate>
|
||||||
|
</CollectionView>
|
||||||
|
|
||||||
|
<!-- Footer Stats -->
|
||||||
|
<Border Grid.Row="1"
|
||||||
|
BackgroundColor="{StaticResource PrimaryColor}"
|
||||||
|
StrokeThickness="0"
|
||||||
|
Padding="24,14"
|
||||||
|
Margin="0">
|
||||||
|
<Grid ColumnDefinitions="Auto,Auto" ColumnSpacing="6" HorizontalOptions="Center" VerticalOptions="Center">
|
||||||
|
<Label Grid.Column="0"
|
||||||
|
Text="Tasks:"
|
||||||
|
FontSize="15"
|
||||||
|
TextColor="White" />
|
||||||
|
<Label Grid.Column="1"
|
||||||
|
x:Name="StatsLabel"
|
||||||
|
FontSize="15"
|
||||||
|
TextColor="White"
|
||||||
|
Opacity="0.9" />
|
||||||
|
</Grid>
|
||||||
|
</Border>
|
||||||
|
|
||||||
|
</Grid>
|
||||||
|
</ContentPage>
|
||||||
|
|
@ -0,0 +1,174 @@
|
||||||
|
// TodoListPage - Main page for viewing todos with XAML support
|
||||||
|
|
||||||
|
using Microsoft.Maui.Controls;
|
||||||
|
using Microsoft.Maui.Graphics;
|
||||||
|
using System.Globalization;
|
||||||
|
|
||||||
|
namespace TodoApp;
|
||||||
|
|
||||||
|
public partial class TodoListPage : ContentPage
|
||||||
|
{
|
||||||
|
private readonly TodoService _service = TodoService.Instance;
|
||||||
|
|
||||||
|
public TodoListPage()
|
||||||
|
{
|
||||||
|
Console.WriteLine("[TodoListPage] Constructor starting");
|
||||||
|
InitializeComponent();
|
||||||
|
|
||||||
|
TodoCollectionView.ItemsSource = _service.Todos;
|
||||||
|
UpdateStats();
|
||||||
|
|
||||||
|
Console.WriteLine("[TodoListPage] Constructor finished");
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnAppearing()
|
||||||
|
{
|
||||||
|
Console.WriteLine("[TodoListPage] OnAppearing called - refreshing CollectionView");
|
||||||
|
base.OnAppearing();
|
||||||
|
|
||||||
|
// Refresh indexes for alternating row colors
|
||||||
|
_service.RefreshIndexes();
|
||||||
|
|
||||||
|
// Refresh the collection view
|
||||||
|
TodoCollectionView.ItemsSource = null;
|
||||||
|
TodoCollectionView.ItemsSource = _service.Todos;
|
||||||
|
Console.WriteLine($"[TodoListPage] ItemsSource set with {_service.Todos.Count} items");
|
||||||
|
UpdateStats();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void OnAddClicked(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
await Navigation.PushAsync(new NewTodoPage());
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void OnSelectionChanged(object? sender, SelectionChangedEventArgs e)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Console.WriteLine($"[TodoListPage] OnSelectionChanged: {e.CurrentSelection.Count} items selected");
|
||||||
|
if (e.CurrentSelection.FirstOrDefault() is TodoItem todo)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"[TodoListPage] Navigating to TodoDetailPage for: {todo.Title}");
|
||||||
|
TodoCollectionView.SelectedItem = null; // Deselect
|
||||||
|
var detailPage = new TodoDetailPage(todo);
|
||||||
|
Console.WriteLine($"[TodoListPage] Created TodoDetailPage, pushing...");
|
||||||
|
await Navigation.PushAsync(detailPage);
|
||||||
|
Console.WriteLine($"[TodoListPage] Navigation complete");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"[TodoListPage] EXCEPTION in OnSelectionChanged: {ex.GetType().Name}: {ex.Message}");
|
||||||
|
Console.WriteLine($"[TodoListPage] Stack trace: {ex.StackTrace}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateStats()
|
||||||
|
{
|
||||||
|
var completed = _service.CompletedCount;
|
||||||
|
var total = _service.TotalCount;
|
||||||
|
|
||||||
|
if (total == 0)
|
||||||
|
{
|
||||||
|
StatsLabel.Text = "";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
StatsLabel.Text = $"{completed} of {total} completed";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Converter for alternating row background colors.
|
||||||
|
/// </summary>
|
||||||
|
public class AlternatingRowColorConverter : IValueConverter
|
||||||
|
{
|
||||||
|
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||||
|
{
|
||||||
|
if (value is int index)
|
||||||
|
{
|
||||||
|
return index % 2 == 0 ? Colors.White : Color.FromArgb("#F5F5F5");
|
||||||
|
}
|
||||||
|
return Colors.White;
|
||||||
|
}
|
||||||
|
|
||||||
|
public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Converter for completed task text color and indicator color.
|
||||||
|
/// </summary>
|
||||||
|
public class CompletedToColorConverter : IValueConverter
|
||||||
|
{
|
||||||
|
// Define colors
|
||||||
|
private static readonly Color PrimaryColor = Color.FromArgb("#5C6BC0");
|
||||||
|
private static readonly Color AccentColor = Color.FromArgb("#26A69A");
|
||||||
|
private static readonly Color CompletedColor = Color.FromArgb("#9E9E9E");
|
||||||
|
private static readonly Color TextPrimary = Color.FromArgb("#212121");
|
||||||
|
private static readonly Color TextSecondary = Color.FromArgb("#757575");
|
||||||
|
|
||||||
|
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||||
|
{
|
||||||
|
bool isCompleted = value is bool b && b;
|
||||||
|
string param = parameter as string ?? "";
|
||||||
|
|
||||||
|
// Indicator bar color
|
||||||
|
if (param == "indicator")
|
||||||
|
{
|
||||||
|
return isCompleted ? CompletedColor : AccentColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Text colors
|
||||||
|
if (isCompleted)
|
||||||
|
{
|
||||||
|
return CompletedColor;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return param == "notes" ? TextSecondary : TextPrimary;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Converter for completed task text decorations (strikethrough).
|
||||||
|
/// </summary>
|
||||||
|
public class CompletedToTextDecorationsConverter : IValueConverter
|
||||||
|
{
|
||||||
|
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||||
|
{
|
||||||
|
bool isCompleted = value is bool b && b;
|
||||||
|
return isCompleted ? TextDecorations.Strikethrough : TextDecorations.None;
|
||||||
|
}
|
||||||
|
|
||||||
|
public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Converter for completed task opacity (slightly faded when complete).
|
||||||
|
/// </summary>
|
||||||
|
public class CompletedToOpacityConverter : IValueConverter
|
||||||
|
{
|
||||||
|
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||||
|
{
|
||||||
|
bool isCompleted = value is bool b && b;
|
||||||
|
return isCompleted ? 0.7 : 1.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,61 @@
|
||||||
|
// TodoService - Manages todo items
|
||||||
|
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
|
|
||||||
|
namespace TodoApp;
|
||||||
|
|
||||||
|
public class TodoService
|
||||||
|
{
|
||||||
|
private static TodoService? _instance;
|
||||||
|
public static TodoService Instance => _instance ??= new TodoService();
|
||||||
|
|
||||||
|
private int _nextId = 1;
|
||||||
|
|
||||||
|
public ObservableCollection<TodoItem> Todos { get; } = new();
|
||||||
|
|
||||||
|
private TodoService()
|
||||||
|
{
|
||||||
|
// Add sample todos with varying lengths to test MaxLines=2 with ellipsis
|
||||||
|
AddTodo("Learn OpenMaui Linux", "Explore the SkiaSharp-based rendering engine for .NET MAUI on Linux desktop. This is a very long description that should wrap to multiple lines and demonstrate the ellipsis truncation feature when MaxLines is set to 2.");
|
||||||
|
AddTodo("Build amazing apps", "Create cross-platform applications that run on Windows, macOS, iOS, Android, and Linux! With OpenMaui, you can write once and deploy everywhere.");
|
||||||
|
AddTodo("Share with the community", "Contribute to the open-source project and help others build great Linux apps. Join our growing community of developers who are passionate about bringing .NET MAUI to Linux.");
|
||||||
|
}
|
||||||
|
|
||||||
|
public TodoItem AddTodo(string title, string notes = "")
|
||||||
|
{
|
||||||
|
var todo = new TodoItem
|
||||||
|
{
|
||||||
|
Id = _nextId++,
|
||||||
|
Index = Todos.Count, // Set index for alternating row colors
|
||||||
|
Title = title,
|
||||||
|
Notes = notes,
|
||||||
|
DueDate = DateTime.Today.AddDays(7)
|
||||||
|
};
|
||||||
|
Todos.Add(todo);
|
||||||
|
return todo;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Refreshes the Index property on all items for alternating row colors.
|
||||||
|
/// </summary>
|
||||||
|
public void RefreshIndexes()
|
||||||
|
{
|
||||||
|
for (int i = 0; i < Todos.Count; i++)
|
||||||
|
{
|
||||||
|
Todos[i].Index = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public TodoItem? GetTodo(int id)
|
||||||
|
{
|
||||||
|
return Todos.FirstOrDefault(t => t.Id == id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void DeleteTodo(TodoItem todo)
|
||||||
|
{
|
||||||
|
Todos.Remove(todo);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int CompletedCount => Todos.Count(t => t.IsCompleted);
|
||||||
|
public int TotalCount => Todos.Count;
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue